diff --git a/assets/css/admin/links-manager.css b/assets/css/admin/links-manager.css
new file mode 100644
index 00000000..9d7f5d61
--- /dev/null
+++ b/assets/css/admin/links-manager.css
@@ -0,0 +1,30 @@
+/* Keep the native category filter useful even before links are assigned. */
+#cat_id {
+ min-width: 180px;
+}
+
+/* The pending count lengthens this sortable header beyond WordPress's default 8% column. */
+.wp-list-table th.column-visible {
+ width: 170px;
+ min-width: 170px;
+}
+
+.wp-list-table th.column-visible a {
+ display: inline-flex;
+ align-items: center;
+ gap: 4px;
+ white-space: nowrap;
+}
+
+.wp-list-table th.column-visible a > span {
+ float: none;
+}
+
+.wp-list-table th.column-visible .sorting-indicators {
+ display: grid;
+ flex: 0 0 auto;
+}
+
+.wp-list-table th.column-visible .sorting-indicator {
+ margin-left: 0;
+}
diff --git a/src/Admin/LinkManager.php b/src/Admin/LinkManager.php
index 867b7e83..6cebeaa8 100644
--- a/src/Admin/LinkManager.php
+++ b/src/Admin/LinkManager.php
@@ -12,11 +12,56 @@ defined('ABSPATH') || exit;
final class LinkManager {
public static function init(): void {
add_action('admin_bar_menu', [self::class, 'add_toolbar_node'], 999);
+ add_action('admin_enqueue_scripts', [self::class, 'enqueue_assets']);
add_filter('manage_link-manager_columns', [self::class, 'filter_columns']);
+ add_filter('get_terms_args', [self::class, 'show_empty_categories'], 10, 2);
+ add_filter('wp_dropdown_cats', [self::class, 'render_empty_category_option'], 10, 2);
add_action('manage_link_custom_column', [self::class, 'render_column'], 10, 2);
add_action('add_meta_boxes_link', [self::class, 'add_edit_help']);
}
+ public static function enqueue_assets(string $hook): void {
+ if ($hook !== 'link-manager.php') {
+ return;
+ }
+ $path = get_template_directory() . '/assets/css/admin/links-manager.css';
+ if (is_file($path)) {
+ wp_enqueue_style(
+ 'jio-links-manager',
+ get_template_directory_uri() . '/assets/css/admin/links-manager.css',
+ [],
+ (string) filemtime($path)
+ );
+ }
+ }
+
+ /** Include empty link categories in the link-manager filter. */
+ public static function show_empty_categories(array $args, array $taxonomies): array {
+ if (self::is_link_manager_screen() && in_array('link_category', $taxonomies, true)) {
+ $args['hide_empty'] = false;
+ }
+ return $args;
+ }
+
+ /** Give the category filter a readable option when no categories exist. */
+ public static function render_empty_category_option(string $output, array $args): string {
+ if (!self::is_link_manager_screen()
+ || ($args['taxonomy'] ?? '') !== 'link_category'
+ || ($args['name'] ?? '') !== 'cat_id'
+ || preg_match('/';
+ $updated = preg_replace('/(', $output, 1);
+ return is_string($updated) ? $updated : $output;
+ }
+
+ private static function is_link_manager_screen(): bool {
+ return is_admin() && ($GLOBALS['pagenow'] ?? '') === 'link-manager.php';
+ }
+
public static function add_toolbar_node(\WP_Admin_Bar $admin_bar): void {
if (!current_user_can('manage_links')) {
return;
diff --git a/tests/widget-system/link-manager-admin-contract.test.cjs b/tests/widget-system/link-manager-admin-contract.test.cjs
new file mode 100644
index 00000000..dcd2fc58
--- /dev/null
+++ b/tests/widget-system/link-manager-admin-contract.test.cjs
@@ -0,0 +1,23 @@
+const test = require('node:test')
+const assert = require('node:assert/strict')
+const fs = require('node:fs')
+const path = require('node:path')
+
+const root = path.resolve(__dirname, '..', '..')
+const read = (file) => fs.readFileSync(path.join(root, file), 'utf8')
+
+test('native link manager explains an empty category filter and keeps visibility sorting inline', () => {
+ const manager = read('src/Admin/LinkManager.php')
+ const css = read('assets/css/admin/links-manager.css')
+
+ assert.match(manager, /add_action\('admin_enqueue_scripts', \[self::class, 'enqueue_assets'\]\)/)
+ assert.match(manager, /add_filter\('get_terms_args', \[self::class, 'show_empty_categories'/)
+ assert.match(manager, /add_filter\('wp_dropdown_cats', \[self::class, 'render_empty_category_option'/)
+ assert.match(manager, /\$args\['hide_empty'\] = false/)
+ assert.match(manager, /暂无链接分类/)
+ assert.match(manager, /\$hook !== 'link-manager\.php'/)
+
+ assert.match(css, /\.wp-list-table th\.column-visible\s*\{[^}]*min-width:\s*170px/s)
+ assert.match(css, /\.wp-list-table th\.column-visible a\s*\{[^}]*white-space:\s*nowrap/s)
+ assert.match(css, /\.sorting-indicators\s*\{[^}]*display:\s*grid/s)
+})