feat(icons): add the FA5 catalog, REST endpoint and unified icon helpers

Mirror the reference theme's universal picker on FA5: a generated full
catalog of the bundled FA5 Free 5.15.4 fonts (src/Icons/fa5-icons.php,
generated from the shipped CSS + SVG fonts), a zibsvg group parsed from
the runtime sprite so it cannot drift, and GET /aksrt/v1/icons/catalog.

Shared admin pieces: aksrt_icon_markup()/aksrt_sanitize_icon_value()
with an SVG allowlist, the picker field/modal assets, and one canonical
aksrt-font-awesome handle registered through aksrt_enqueue_fontawesome().
This commit is contained in:
2026-09-06 12:59:03 +08:00
parent 9496db5171
commit f629a4f373
12 changed files with 2541 additions and 18 deletions
+11 -5
View File
@@ -111,11 +111,16 @@ class Admin {
}
// Use the same local FA5 pack as the public stack.
wp_enqueue_style(
'aksrt-font-awesome',
get_template_directory_uri() . '/assets/fontawesome5/css/all.min.css',
[],
'5.15.4'
aksrt_enqueue_fontawesome();
// Frontend SVG sprite (defines window.aksrt_svgs and injects the symbol
// sheet) so schema icon fields can preview built-in zibsvg values.
wp_enqueue_script(
'aksrt-svg-sprite',
get_template_directory_uri() . '/assets/js/runtime/svg-icon.js',
['jquery'],
(string) filemtime(get_template_directory() . '/assets/js/runtime/svg-icon.js'),
true
);
wp_enqueue_script(
@@ -186,6 +191,7 @@ class Admin {
CommentController::class,
MessageController::class,
AIController::class,
\AKSRT\Icons\IconController::class,
];
foreach ($controllers as $class_name) {
+2 -7
View File
@@ -25,17 +25,12 @@ trait ScriptsPart1b {
[],
'3.4.1'
);
wp_enqueue_style(
'fontawesome5',
$base_uri . '/assets/fontawesome5/css/all.min.css',
['bootstrap'],
'5.15.4'
);
aksrt_enqueue_fontawesome(['bootstrap']);
$main_css_path = get_template_directory() . '/assets/css/main.min.css';
wp_enqueue_style(
'aksrt-main',
$base_uri . '/assets/css/main.min.css',
['fontawesome5'],
['aksrt-font-awesome'],
file_exists($main_css_path) ? (string) filemtime($main_css_path) : $version
);
wp_enqueue_style(
+66
View File
@@ -0,0 +1,66 @@
<?php
/**
* @package AKSRT
*/
namespace AKSRT\Icons;
defined('ABSPATH') || exit;
/**
* Icon catalog for the universal icon picker.
*
* Two families:
* - Font Awesome 5 Free (bundled, 5.15.4): static generated data in fa5-icons.php.
* - Theme built-in SVG sprite: keys parsed from assets/js/runtime/svg-icon.js
* (aksrt_svgs['key'] = '<path ...>') so the picker can never drift from the
* frontend sprite. Parsed per file mtime and cached.
*/
final class IconCatalog {
/**
* @return array<int, array{id:string,title:string,type:string,icons:string[]}>
*/
public static function groups(): array {
$fa = include AKSRT_DIR . '/src/Icons/fa5-icons.php';
if (!is_array($fa)) {
$fa = ['fas' => [], 'far' => [], 'fab' => []];
}
return [
['id' => 'fas', 'title' => __('FA 实色', 'aksrt'), 'type' => 'fa', 'icons' => array_values((array) ($fa['fas'] ?? []))],
['id' => 'far', 'title' => __('FA 常规', 'aksrt'), 'type' => 'fa', 'icons' => array_values((array) ($fa['far'] ?? []))],
['id' => 'fab', 'title' => __('FA 品牌', 'aksrt'), 'type' => 'fa', 'icons' => array_values((array) ($fa['fab'] ?? []))],
['id' => 'zibsvg', 'title' => __('主题内置 SVG', 'aksrt'), 'type' => 'svg', 'icons' => self::sprite_keys()],
['id' => 'custom', 'title' => __('自定义 SVG', 'aksrt'), 'type' => 'custom', 'icons' => []],
];
}
/**
* Sprite symbol keys, e.g. ['like', 'view', ...] — derived from the
* frontend sprite file, cached until the file changes.
*
* @return string[]
*/
public static function sprite_keys(): array {
$file = AKSRT_DIR . '/assets/js/runtime/svg-icon.js';
if (!is_readable($file)) {
return [];
}
$mtime = (string) filemtime($file);
$cache = get_transient('aksrt_sprite_keys_v1');
if (is_array($cache) && ($cache['mtime'] ?? '') === $mtime && isset($cache['keys'])) {
return array_values((array) $cache['keys']);
}
$src = (string) file_get_contents($file);
$keys = [];
if (preg_match_all("/aksrt_svgs\\['([A-Za-z0-9_]+)'\\]\\s*=/", $src, $m)) {
$keys = array_values(array_unique($m[1]));
}
set_transient('aksrt_sprite_keys_v1', ['mtime' => $mtime, 'keys' => $keys], MONTH_IN_SECONDS);
return $keys;
}
}
+37
View File
@@ -0,0 +1,37 @@
<?php
/**
* @package AKSRT
*/
namespace AKSRT\Icons;
use AKSRT\Admin\ControllerBase;
defined('ABSPATH') || exit;
/**
* REST endpoint serving the icon picker catalog.
*/
class IconController extends ControllerBase {
private static $instance = null;
public static function get_instance(): self {
if (null === self::$instance) self::$instance = new self();
return self::$instance;
}
public function register_routes(): void {
register_rest_route(self::API_NAMESPACE, '/icons/catalog', [
'methods' => 'GET',
'callback' => [$this, 'get_catalog'],
'permission_callback' => [$this, 'check_permission'],
]);
}
public function get_catalog(): \WP_REST_Response {
return $this->success([
'groups' => IconCatalog::groups(),
]);
}
}
File diff suppressed because it is too large Load Diff