From f629a4f3730e7af1faf7ee13abfa5474ccdcfb00 Mon Sep 17 00:00:00 2001 From: unknown Date: Sun, 6 Sep 2026 12:59:03 +0800 Subject: [PATCH 1/7] 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(). --- assets/css/admin-icon-picker.css | 195 ++++ assets/js/admin-icon-picker.js | 238 +++++ inc/bootstrap.php | 1 + inc/core-hooks.php | 7 +- inc/helpers/aksrt-icon-picker.php | 135 +++ inc/helpers/aksrt-icons.php | 140 +++ src/Admin/Admin.php | 16 +- src/Core/ScriptsPart1b.php | 9 +- src/Icons/IconCatalog.php | 66 ++ src/Icons/IconController.php | 37 + src/Icons/fa5-icons.php | 1631 +++++++++++++++++++++++++++++ tools/generate-fa5-icons.php | 84 ++ 12 files changed, 2541 insertions(+), 18 deletions(-) create mode 100644 assets/css/admin-icon-picker.css create mode 100644 assets/js/admin-icon-picker.js create mode 100644 inc/helpers/aksrt-icon-picker.php create mode 100644 src/Icons/IconCatalog.php create mode 100644 src/Icons/IconController.php create mode 100644 src/Icons/fa5-icons.php create mode 100644 tools/generate-fa5-icons.php diff --git a/assets/css/admin-icon-picker.css b/assets/css/admin-icon-picker.css new file mode 100644 index 00000000..fe7f46c9 --- /dev/null +++ b/assets/css/admin-icon-picker.css @@ -0,0 +1,195 @@ +/* AKSRT universal icon picker (admin) */ + +#aksrt-modal-icon.aksrt-icon-modal { + display: none; +} + +#aksrt-modal-icon.aksrt-icon-modal:not(.hidden) { + display: flex; + position: fixed; + inset: 0; + z-index: 100100; + align-items: center; + justify-content: center; +} + +#aksrt-modal-icon .aksrt-icon-modal__overlay { + position: absolute; + inset: 0; + background: rgba(0, 0, 0, 0.55); +} + +#aksrt-modal-icon .aksrt-icon-modal__inner { + position: relative; + z-index: 1; + display: flex; + flex-direction: column; + width: min(720px, calc(100vw - 32px)); + max-height: min(76vh, 640px); + background: #fff; + border-radius: 8px; + box-shadow: 0 12px 40px rgba(0, 0, 0, 0.28); + overflow: hidden; +} + +#aksrt-modal-icon .aksrt-icon-modal__header { + display: flex; + align-items: center; + justify-content: space-between; + padding: 12px 16px; + border-bottom: 1px solid #e2e4e7; + font-size: 14px; +} + +#aksrt-modal-icon .aksrt-icon-modal__close { + font-size: 20px; + line-height: 1; + text-decoration: none; + cursor: pointer; +} + +#aksrt-modal-icon .aksrt-icon-modal__toolbar { + display: flex; + flex-wrap: wrap; + gap: 8px; + align-items: center; + padding: 10px 16px; + border-bottom: 1px solid #e2e4e7; +} + +#aksrt-modal-icon .aksrt-icon-modal__tabs { + display: flex; + flex-wrap: wrap; + gap: 6px; +} + +#aksrt-modal-icon .aksrt-icon-modal__tab.is-active { + border-color: #2271b1; + color: #2271b1; +} + +#aksrt-modal-icon .aksrt-icon-modal__body { + position: relative; + overflow-y: auto; + padding: 12px 16px 16px; +} + +#aksrt-modal-icon .aksrt-icon-modal__loading { + padding: 24px 0; + text-align: center; +} + +#aksrt-modal-icon .aksrt-icon-modal__error { + color: #b32d2e; +} + +#aksrt-modal-icon .aksrt-icon-modal__grid { + display: grid; + grid-template-columns: repeat(auto-fill, minmax(42px, 1fr)); + gap: 4px; + margin-top: 8px; +} + +#aksrt-modal-icon .aksrt-icon-picker__item { + display: flex; + align-items: center; + justify-content: center; + width: 42px; + height: 42px; + padding: 0; + border: 1px solid transparent; + border-radius: 6px; + background: #f6f7f7; + font-size: 18px; + color: #3c434a; + cursor: pointer; +} + +#aksrt-modal-icon .aksrt-icon-picker__item:hover { + border-color: #2271b1; + color: #2271b1; +} + +#aksrt-modal-icon .aksrt-icon-picker__item .icon { + width: 20px; + height: 20px; + fill: currentColor; +} + +#aksrt-modal-icon .aksrt-icon-picker__empty { + grid-column: 1 / -1; + padding: 20px 0; + color: #787c82; + text-align: center; +} + +/* Custom SVG tab */ + +#aksrt-modal-icon .aksrt-icon-modal__custom { + margin-top: 4px; +} + +#aksrt-modal-icon .aksrt-icon-modal__custom textarea { + margin-top: 4px; +} + +#aksrt-modal-icon .aksrt-icon-modal__custom-actions { + display: flex; + align-items: center; + gap: 10px; + margin-top: 8px; +} + +#aksrt-modal-icon .aksrt-icon-modal__custom-preview { + display: inline-flex; + align-items: center; + justify-content: center; + width: 32px; + height: 32px; + border: 1px dashed #c3c4c7; + border-radius: 4px; + font-size: 18px; +} + +#aksrt-modal-icon .aksrt-icon-modal__custom-preview svg { + width: 20px; + height: 20px; +} + +#aksrt-modal-icon .aksrt-icon-modal__grid--session { + margin-top: 12px; + padding-top: 8px; + border-top: 1px solid #e2e4e7; +} + +/* Field markup (widgets, nav menus, meta boxes) */ + +.aksrt-icon-picker { + display: flex; + flex-wrap: wrap; + align-items: center; + gap: 8px; +} + +.aksrt-icon-picker .aksrt-icon-picker__preview { + display: inline-flex; + align-items: center; + justify-content: center; + width: 30px; + height: 30px; + border: 1px solid #dcdcde; + border-radius: 4px; + background: #f6f7f7; + font-size: 16px; +} + +.aksrt-icon-picker .aksrt-icon-picker__preview svg { + width: 18px; + height: 18px; + fill: currentColor; +} + +.aksrt-icon-picker .aksrt-icon-picker__value { + flex: 1 1 160px; + min-width: 0; +} diff --git a/assets/js/admin-icon-picker.js b/assets/js/admin-icon-picker.js new file mode 100644 index 00000000..fa3edaa6 --- /dev/null +++ b/assets/js/admin-icon-picker.js @@ -0,0 +1,238 @@ +/** + * AKSRT universal icon picker — shared admin modal for FA5, the built-in + * SVG sprite and inline custom SVG values ("fa fas fa-x" / "zibsvg-key" / + * "customsvg-"). + * + * Any admin screen: call aksrt_icon_picker_assets() (PHP) and render fields + * via aksrt_icon_picker_field(); interaction is fully delegated, so lazy + * loaded widget forms and repeater rows work without re-init. + */ +(function ($) { + 'use strict'; + + const config = window.aksrtIconPicker || {}; + const i18n = config.i18n || {}; + + const state = { + loaded: false, + loading: false, + activeTab: 'fas', + tabs: [], + grids: {}, + sessionCustom: [], + $target: null, + }; + + function modal() { + return $('#aksrt-modal-icon'); + } + + function setBusy(busy) { + modal().find('.aksrt-icon-modal__loading').each((_i, el) => { el.hidden = !busy; }); + } + + /* Custom SVG cleanup — strip authoring leftovers, keep pure vector markup. */ + function cleanSvg(raw) { + const text = String(raw || '').trim(); + if (!text) return ''; + let doc; + try { + doc = new window.DOMParser().parseFromString(text, 'image/svg+xml'); + } catch (e) { + return ''; + } + if (doc.querySelector('parsererror')) return ''; + const svg = doc.querySelector('svg'); + if (!svg || svg.querySelector('script, foreignObject, animate')) return ''; + ['class', 'id', 'style', 't', 'version', 'p-id'].forEach((attr) => svg.removeAttribute(attr)); + Array.prototype.slice.call(svg.attributes).forEach((attr) => { + if (attr.name.indexOf('data-') === 0) svg.removeAttribute(attr.name); + }); + const keep = new window.XMLSerializer().serializeToString(svg); + return /"']/g, (ch) => ({ + '&': '&', '<': '<', '>': '>', '"': '"', "'": ''', + }[ch])); + } + + function previewMarkup(value) { + value = String(value || '').trim(); + if (!value) return ''; + if (value.indexOf('customsvg-') === 0) return cleanSvg(value.slice(10)); + if (value.indexOf('zibsvg-') === 0) { + const key = value.slice(7).replace(/[^a-zA-Z0-9_-]/g, ''); + return key ? `` : ''; + } + const el = document.createElement('i'); + el.className = !/\bfa\b/.test(value) && value.indexOf('fa-') !== -1 ? `fa ${value}` : value; + el.setAttribute('aria-hidden', 'true'); + return el.outerHTML; + } + + function seedSessionCustom() { + $('input.aksrt-icon-picker__value').each(function () { + const value = String(this.value || '').trim(); + if (value.indexOf('customsvg-') !== 0) return; + const svg = value.slice(10); + if (svg && state.sessionCustom.indexOf(svg) === -1) state.sessionCustom.push(svg); + }); + } + + function itemButton(value, name, innerHtml) { + return $(''); + (group.icons || []).forEach((name) => { + if (group.type === 'fa') { + const value = `fa ${group.id} fa-${name}`; + $grid.append(itemButton(value, name, ``)); + } else if (group.type === 'svg') { + const value = `zibsvg-${name}`; + const key = escapeText(name).replace(/[^a-zA-Z0-9_-]/g, ''); + $grid.append(itemButton(value, name, ``)); + } + }); + $wrap.append($grid); + state.grids[group.id] = $grid; + + const $tab = $(' + +
+ +
+
+
+ + +
+ +
+ + + +
+ + + + + + +
+ > + */ +function aksrt_svg_kses_tags(): array { + static $tags = null; + if ($tags !== null) { + return $tags; + } + + $tags = [ + 'svg' => ['class' => true, 'viewbox' => true, 'width' => true, 'height' => true, 'fill' => true, 'xmlns' => true, 'aria-hidden' => true], + 'use' => ['href' => true, 'xlink:href' => true], + 'path' => ['d' => true, 'fill' => true, 'stroke' => true, 'stroke-width' => true, 'stroke-linecap' => true, 'stroke-linejoin' => true], + 'g' => ['fill' => true, 'transform' => true], + ]; + return $tags; +} +endif; + +if (!function_exists('aksrt_icon_markup')) : +/** + * Render an icon-picker value of any supported format. + * + * Supported values: + * - "fa fas fa-star" / "fa fab fa-weixin" — FA5 class string + * - "fa-star" — bare glyph, rendered as solid FA5 + * - "zibsvg-hot" — theme built-in SVG sprite key + * - "customsvg-" — inline user SVG markup + * + * @param string $value Raw stored value. + * @param array|string $extra_classes Extra classes for the rendered element. + * @return string Safe HTML, empty string for empty values. + */ +function aksrt_icon_markup(string $value, $extra_classes = []): string { + $value = trim($value); + if ($value === '') { + return ''; + } + + $extra = ''; + if (is_string($extra_classes) && $extra_classes !== '') { + $extra_classes = preg_split('/\s+/', $extra_classes) ?: []; + } + if (is_array($extra_classes)) { + $clean = array_filter(array_map('sanitize_html_class', $extra_classes)); + if ($clean !== []) { + $extra = ' ' . implode(' ', $clean); + } + } + + if (str_starts_with($value, 'customsvg-')) { + $svg = wp_kses(substr($value, 10), aksrt_svg_kses_tags()); + if ($svg === '') { + return ''; + } + if ($extra !== '' && stripos($svg, ''; + } + + if (!str_contains($value, 'fa ') && str_contains($value, 'fa-')) { + $value = 'fa ' . $value; + } + return ''; +} +endif; + +if (!function_exists('aksrt_sanitize_icon_value')) : +/** + * Sanitize an icon-picker value. Keeps the SVG markup of customsvg- values + * (plain sanitize_text_field would strip the tags), filters FA/zip keys. + * + * @param string $value Raw value from a form/REST payload. + * @return string Sanitized value. + */ +function aksrt_sanitize_icon_value(string $value): string { + $value = trim($value); + if ($value === '') { + return ''; + } + + if (str_starts_with($value, 'customsvg-')) { + $svg = wp_kses(substr($value, 10), aksrt_svg_kses_tags()); + return $svg === '' ? '' : 'customsvg-' . $svg; + } + + return sanitize_text_field($value); +} +endif; + +if (!function_exists('aksrt_empty_state_svgs')) : +/** + * Shared empty-state SVG icons (24px stroke style, not sprite keys). + * + * @return array{post:string,comment:string,favorite:string,follow:string} + */ +function aksrt_empty_state_svgs(): array { + return [ + 'post' => '', + 'comment' => '', + 'favorite' => '', + 'follow' => '', + ]; +} +endif; + // ============================================================ // Public social links (author hero / profile) // ============================================================ diff --git a/src/Admin/Admin.php b/src/Admin/Admin.php index bb8ffa19..fc68c0d5 100644 --- a/src/Admin/Admin.php +++ b/src/Admin/Admin.php @@ -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) { diff --git a/src/Core/ScriptsPart1b.php b/src/Core/ScriptsPart1b.php index 626504a1..5ac8321e 100644 --- a/src/Core/ScriptsPart1b.php +++ b/src/Core/ScriptsPart1b.php @@ -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( diff --git a/src/Icons/IconCatalog.php b/src/Icons/IconCatalog.php new file mode 100644 index 00000000..752c2da1 --- /dev/null +++ b/src/Icons/IconCatalog.php @@ -0,0 +1,66 @@ +') so the picker can never drift from the + * frontend sprite. Parsed per file mtime and cached. + */ +final class IconCatalog { + + /** + * @return array + */ + 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; + } +} diff --git a/src/Icons/IconController.php b/src/Icons/IconController.php new file mode 100644 index 00000000..0569a5ca --- /dev/null +++ b/src/Icons/IconController.php @@ -0,0 +1,37 @@ + 'GET', + 'callback' => [$this, 'get_catalog'], + 'permission_callback' => [$this, 'check_permission'], + ]); + } + + public function get_catalog(): \WP_REST_Response { + return $this->success([ + 'groups' => IconCatalog::groups(), + ]); + } +} diff --git a/src/Icons/fa5-icons.php b/src/Icons/fa5-icons.php new file mode 100644 index 00000000..5b3c6596 --- /dev/null +++ b/src/Icons/fa5-icons.php @@ -0,0 +1,1631 @@ + + array ( + 0 => 'ad', + 1 => 'address-book', + 2 => 'address-card', + 3 => 'adjust', + 4 => 'air-freshener', + 5 => 'align-center', + 6 => 'align-justify', + 7 => 'align-left', + 8 => 'align-right', + 9 => 'allergies', + 10 => 'ambulance', + 11 => 'american-sign-language-interpreting', + 12 => 'anchor', + 13 => 'angle-double-down', + 14 => 'angle-double-left', + 15 => 'angle-double-right', + 16 => 'angle-double-up', + 17 => 'angle-down', + 18 => 'angle-left', + 19 => 'angle-right', + 20 => 'angle-up', + 21 => 'angry', + 22 => 'ankh', + 23 => 'apple-alt', + 24 => 'archive', + 25 => 'archway', + 26 => 'arrow-alt-circle-down', + 27 => 'arrow-alt-circle-left', + 28 => 'arrow-alt-circle-right', + 29 => 'arrow-alt-circle-up', + 30 => 'arrow-circle-down', + 31 => 'arrow-circle-left', + 32 => 'arrow-circle-right', + 33 => 'arrow-circle-up', + 34 => 'arrow-down', + 35 => 'arrow-left', + 36 => 'arrow-right', + 37 => 'arrow-up', + 38 => 'arrows-alt', + 39 => 'arrows-alt-h', + 40 => 'arrows-alt-v', + 41 => 'assistive-listening-systems', + 42 => 'asterisk', + 43 => 'at', + 44 => 'atlas', + 45 => 'atom', + 46 => 'audio-description', + 47 => 'award', + 48 => 'baby', + 49 => 'baby-carriage', + 50 => 'backspace', + 51 => 'backward', + 52 => 'bacon', + 53 => 'bacteria', + 54 => 'bacterium', + 55 => 'bahai', + 56 => 'balance-scale', + 57 => 'balance-scale-left', + 58 => 'balance-scale-right', + 59 => 'ban', + 60 => 'band-aid', + 61 => 'barcode', + 62 => 'bars', + 63 => 'baseball-ball', + 64 => 'basketball-ball', + 65 => 'bath', + 66 => 'battery-empty', + 67 => 'battery-full', + 68 => 'battery-half', + 69 => 'battery-quarter', + 70 => 'battery-three-quarters', + 71 => 'bed', + 72 => 'beer', + 73 => 'bell', + 74 => 'bell-slash', + 75 => 'bezier-curve', + 76 => 'bible', + 77 => 'bicycle', + 78 => 'biking', + 79 => 'binoculars', + 80 => 'biohazard', + 81 => 'birthday-cake', + 82 => 'blender', + 83 => 'blender-phone', + 84 => 'blind', + 85 => 'blog', + 86 => 'bold', + 87 => 'bolt', + 88 => 'bomb', + 89 => 'bone', + 90 => 'bong', + 91 => 'book', + 92 => 'book-dead', + 93 => 'book-medical', + 94 => 'book-open', + 95 => 'book-reader', + 96 => 'bookmark', + 97 => 'border-all', + 98 => 'border-none', + 99 => 'border-style', + 100 => 'bowling-ball', + 101 => 'box', + 102 => 'box-open', + 103 => 'box-tissue', + 104 => 'boxes', + 105 => 'braille', + 106 => 'brain', + 107 => 'bread-slice', + 108 => 'briefcase', + 109 => 'briefcase-medical', + 110 => 'broadcast-tower', + 111 => 'broom', + 112 => 'brush', + 113 => 'bug', + 114 => 'building', + 115 => 'bullhorn', + 116 => 'bullseye', + 117 => 'burn', + 118 => 'bus', + 119 => 'bus-alt', + 120 => 'business-time', + 121 => 'calculator', + 122 => 'calendar', + 123 => 'calendar-alt', + 124 => 'calendar-check', + 125 => 'calendar-day', + 126 => 'calendar-minus', + 127 => 'calendar-plus', + 128 => 'calendar-times', + 129 => 'calendar-week', + 130 => 'camera', + 131 => 'camera-retro', + 132 => 'campground', + 133 => 'candy-cane', + 134 => 'cannabis', + 135 => 'capsules', + 136 => 'car', + 137 => 'car-alt', + 138 => 'car-battery', + 139 => 'car-crash', + 140 => 'car-side', + 141 => 'caravan', + 142 => 'caret-down', + 143 => 'caret-left', + 144 => 'caret-right', + 145 => 'caret-square-down', + 146 => 'caret-square-left', + 147 => 'caret-square-right', + 148 => 'caret-square-up', + 149 => 'caret-up', + 150 => 'carrot', + 151 => 'cart-arrow-down', + 152 => 'cart-plus', + 153 => 'cash-register', + 154 => 'cat', + 155 => 'certificate', + 156 => 'chair', + 157 => 'chalkboard', + 158 => 'chalkboard-teacher', + 159 => 'charging-station', + 160 => 'chart-area', + 161 => 'chart-bar', + 162 => 'chart-line', + 163 => 'chart-pie', + 164 => 'check', + 165 => 'check-circle', + 166 => 'check-double', + 167 => 'check-square', + 168 => 'cheese', + 169 => 'chess', + 170 => 'chess-bishop', + 171 => 'chess-board', + 172 => 'chess-king', + 173 => 'chess-knight', + 174 => 'chess-pawn', + 175 => 'chess-queen', + 176 => 'chess-rook', + 177 => 'chevron-circle-down', + 178 => 'chevron-circle-left', + 179 => 'chevron-circle-right', + 180 => 'chevron-circle-up', + 181 => 'chevron-down', + 182 => 'chevron-left', + 183 => 'chevron-right', + 184 => 'chevron-up', + 185 => 'child', + 186 => 'church', + 187 => 'circle', + 188 => 'circle-notch', + 189 => 'city', + 190 => 'clinic-medical', + 191 => 'clipboard', + 192 => 'clipboard-check', + 193 => 'clipboard-list', + 194 => 'clock', + 195 => 'clone', + 196 => 'closed-captioning', + 197 => 'cloud', + 198 => 'cloud-download-alt', + 199 => 'cloud-meatball', + 200 => 'cloud-moon', + 201 => 'cloud-moon-rain', + 202 => 'cloud-rain', + 203 => 'cloud-showers-heavy', + 204 => 'cloud-sun', + 205 => 'cloud-sun-rain', + 206 => 'cloud-upload-alt', + 207 => 'cocktail', + 208 => 'code', + 209 => 'code-branch', + 210 => 'coffee', + 211 => 'cog', + 212 => 'cogs', + 213 => 'coins', + 214 => 'columns', + 215 => 'comment', + 216 => 'comment-alt', + 217 => 'comment-dollar', + 218 => 'comment-dots', + 219 => 'comment-medical', + 220 => 'comment-slash', + 221 => 'comments', + 222 => 'comments-dollar', + 223 => 'compact-disc', + 224 => 'compass', + 225 => 'compress', + 226 => 'compress-alt', + 227 => 'compress-arrows-alt', + 228 => 'concierge-bell', + 229 => 'cookie', + 230 => 'cookie-bite', + 231 => 'copy', + 232 => 'copyright', + 233 => 'couch', + 234 => 'credit-card', + 235 => 'crop', + 236 => 'crop-alt', + 237 => 'cross', + 238 => 'crosshairs', + 239 => 'crow', + 240 => 'crown', + 241 => 'crutch', + 242 => 'cube', + 243 => 'cubes', + 244 => 'cut', + 245 => 'database', + 246 => 'deaf', + 247 => 'democrat', + 248 => 'desktop', + 249 => 'dharmachakra', + 250 => 'diagnoses', + 251 => 'dice', + 252 => 'dice-d6', + 253 => 'dice-d20', + 254 => 'dice-five', + 255 => 'dice-four', + 256 => 'dice-one', + 257 => 'dice-six', + 258 => 'dice-three', + 259 => 'dice-two', + 260 => 'digital-tachograph', + 261 => 'directions', + 262 => 'disease', + 263 => 'divide', + 264 => 'dizzy', + 265 => 'dna', + 266 => 'dog', + 267 => 'dollar-sign', + 268 => 'dolly', + 269 => 'dolly-flatbed', + 270 => 'donate', + 271 => 'door-closed', + 272 => 'door-open', + 273 => 'dot-circle', + 274 => 'dove', + 275 => 'download', + 276 => 'drafting-compass', + 277 => 'dragon', + 278 => 'draw-polygon', + 279 => 'drum', + 280 => 'drum-steelpan', + 281 => 'drumstick-bite', + 282 => 'dumbbell', + 283 => 'dumpster', + 284 => 'dumpster-fire', + 285 => 'dungeon', + 286 => 'edit', + 287 => 'egg', + 288 => 'eject', + 289 => 'ellipsis-h', + 290 => 'ellipsis-v', + 291 => 'envelope', + 292 => 'envelope-open', + 293 => 'envelope-open-text', + 294 => 'envelope-square', + 295 => 'equals', + 296 => 'eraser', + 297 => 'ethernet', + 298 => 'euro-sign', + 299 => 'exchange-alt', + 300 => 'exclamation', + 301 => 'exclamation-circle', + 302 => 'exclamation-triangle', + 303 => 'expand', + 304 => 'expand-alt', + 305 => 'expand-arrows-alt', + 306 => 'external-link-alt', + 307 => 'external-link-square-alt', + 308 => 'eye', + 309 => 'eye-dropper', + 310 => 'eye-slash', + 311 => 'fan', + 312 => 'fast-backward', + 313 => 'fast-forward', + 314 => 'faucet', + 315 => 'fax', + 316 => 'feather', + 317 => 'feather-alt', + 318 => 'female', + 319 => 'fighter-jet', + 320 => 'file', + 321 => 'file-alt', + 322 => 'file-archive', + 323 => 'file-audio', + 324 => 'file-code', + 325 => 'file-contract', + 326 => 'file-csv', + 327 => 'file-download', + 328 => 'file-excel', + 329 => 'file-export', + 330 => 'file-image', + 331 => 'file-import', + 332 => 'file-invoice', + 333 => 'file-invoice-dollar', + 334 => 'file-medical', + 335 => 'file-medical-alt', + 336 => 'file-pdf', + 337 => 'file-powerpoint', + 338 => 'file-prescription', + 339 => 'file-signature', + 340 => 'file-upload', + 341 => 'file-video', + 342 => 'file-word', + 343 => 'fill', + 344 => 'fill-drip', + 345 => 'film', + 346 => 'filter', + 347 => 'fingerprint', + 348 => 'fire', + 349 => 'fire-alt', + 350 => 'fire-extinguisher', + 351 => 'first-aid', + 352 => 'fish', + 353 => 'fist-raised', + 354 => 'flag', + 355 => 'flag-checkered', + 356 => 'flag-usa', + 357 => 'flask', + 358 => 'flushed', + 359 => 'folder', + 360 => 'folder-minus', + 361 => 'folder-open', + 362 => 'folder-plus', + 363 => 'font', + 364 => 'football-ball', + 365 => 'forward', + 366 => 'frog', + 367 => 'frown', + 368 => 'frown-open', + 369 => 'funnel-dollar', + 370 => 'futbol', + 371 => 'gamepad', + 372 => 'gas-pump', + 373 => 'gavel', + 374 => 'gem', + 375 => 'genderless', + 376 => 'ghost', + 377 => 'gift', + 378 => 'gifts', + 379 => 'glass-cheers', + 380 => 'glass-martini', + 381 => 'glass-martini-alt', + 382 => 'glass-whiskey', + 383 => 'glasses', + 384 => 'globe', + 385 => 'globe-africa', + 386 => 'globe-americas', + 387 => 'globe-asia', + 388 => 'globe-europe', + 389 => 'golf-ball', + 390 => 'gopuram', + 391 => 'graduation-cap', + 392 => 'greater-than', + 393 => 'greater-than-equal', + 394 => 'grimace', + 395 => 'grin', + 396 => 'grin-alt', + 397 => 'grin-beam', + 398 => 'grin-beam-sweat', + 399 => 'grin-hearts', + 400 => 'grin-squint', + 401 => 'grin-squint-tears', + 402 => 'grin-stars', + 403 => 'grin-tears', + 404 => 'grin-tongue', + 405 => 'grin-tongue-squint', + 406 => 'grin-tongue-wink', + 407 => 'grin-wink', + 408 => 'grip-horizontal', + 409 => 'grip-lines', + 410 => 'grip-lines-vertical', + 411 => 'grip-vertical', + 412 => 'guitar', + 413 => 'h-square', + 414 => 'hamburger', + 415 => 'hammer', + 416 => 'hamsa', + 417 => 'hand-holding', + 418 => 'hand-holding-heart', + 419 => 'hand-holding-medical', + 420 => 'hand-holding-usd', + 421 => 'hand-holding-water', + 422 => 'hand-lizard', + 423 => 'hand-middle-finger', + 424 => 'hand-paper', + 425 => 'hand-peace', + 426 => 'hand-point-down', + 427 => 'hand-point-left', + 428 => 'hand-point-right', + 429 => 'hand-point-up', + 430 => 'hand-pointer', + 431 => 'hand-rock', + 432 => 'hand-scissors', + 433 => 'hand-sparkles', + 434 => 'hand-spock', + 435 => 'hands', + 436 => 'hands-helping', + 437 => 'hands-wash', + 438 => 'handshake', + 439 => 'handshake-alt-slash', + 440 => 'handshake-slash', + 441 => 'hanukiah', + 442 => 'hard-hat', + 443 => 'hashtag', + 444 => 'hat-cowboy', + 445 => 'hat-cowboy-side', + 446 => 'hat-wizard', + 447 => 'hdd', + 448 => 'head-side-cough', + 449 => 'head-side-cough-slash', + 450 => 'head-side-mask', + 451 => 'head-side-virus', + 452 => 'heading', + 453 => 'headphones', + 454 => 'headphones-alt', + 455 => 'headset', + 456 => 'heart', + 457 => 'heart-broken', + 458 => 'heartbeat', + 459 => 'helicopter', + 460 => 'highlighter', + 461 => 'hiking', + 462 => 'hippo', + 463 => 'history', + 464 => 'hockey-puck', + 465 => 'holly-berry', + 466 => 'home', + 467 => 'horse', + 468 => 'horse-head', + 469 => 'hospital', + 470 => 'hospital-alt', + 471 => 'hospital-symbol', + 472 => 'hospital-user', + 473 => 'hot-tub', + 474 => 'hotdog', + 475 => 'hotel', + 476 => 'hourglass', + 477 => 'hourglass-end', + 478 => 'hourglass-half', + 479 => 'hourglass-start', + 480 => 'house-damage', + 481 => 'house-user', + 482 => 'hryvnia', + 483 => 'i-cursor', + 484 => 'ice-cream', + 485 => 'icicles', + 486 => 'icons', + 487 => 'id-badge', + 488 => 'id-card', + 489 => 'id-card-alt', + 490 => 'igloo', + 491 => 'image', + 492 => 'images', + 493 => 'inbox', + 494 => 'indent', + 495 => 'industry', + 496 => 'infinity', + 497 => 'info', + 498 => 'info-circle', + 499 => 'italic', + 500 => 'jedi', + 501 => 'joint', + 502 => 'journal-whills', + 503 => 'kaaba', + 504 => 'key', + 505 => 'keyboard', + 506 => 'khanda', + 507 => 'kiss', + 508 => 'kiss-beam', + 509 => 'kiss-wink-heart', + 510 => 'kiwi-bird', + 511 => 'landmark', + 512 => 'language', + 513 => 'laptop', + 514 => 'laptop-code', + 515 => 'laptop-house', + 516 => 'laptop-medical', + 517 => 'laugh', + 518 => 'laugh-beam', + 519 => 'laugh-squint', + 520 => 'laugh-wink', + 521 => 'layer-group', + 522 => 'leaf', + 523 => 'lemon', + 524 => 'less-than', + 525 => 'less-than-equal', + 526 => 'level-down-alt', + 527 => 'level-up-alt', + 528 => 'life-ring', + 529 => 'lightbulb', + 530 => 'link', + 531 => 'lira-sign', + 532 => 'list', + 533 => 'list-alt', + 534 => 'list-ol', + 535 => 'list-ul', + 536 => 'location-arrow', + 537 => 'lock', + 538 => 'lock-open', + 539 => 'long-arrow-alt-down', + 540 => 'long-arrow-alt-left', + 541 => 'long-arrow-alt-right', + 542 => 'long-arrow-alt-up', + 543 => 'low-vision', + 544 => 'luggage-cart', + 545 => 'lungs', + 546 => 'lungs-virus', + 547 => 'magic', + 548 => 'magnet', + 549 => 'mail-bulk', + 550 => 'male', + 551 => 'map', + 552 => 'map-marked', + 553 => 'map-marked-alt', + 554 => 'map-marker', + 555 => 'map-marker-alt', + 556 => 'map-pin', + 557 => 'map-signs', + 558 => 'marker', + 559 => 'mars', + 560 => 'mars-double', + 561 => 'mars-stroke', + 562 => 'mars-stroke-h', + 563 => 'mars-stroke-v', + 564 => 'mask', + 565 => 'medal', + 566 => 'medkit', + 567 => 'meh', + 568 => 'meh-blank', + 569 => 'meh-rolling-eyes', + 570 => 'memory', + 571 => 'menorah', + 572 => 'mercury', + 573 => 'meteor', + 574 => 'microchip', + 575 => 'microphone', + 576 => 'microphone-alt', + 577 => 'microphone-alt-slash', + 578 => 'microphone-slash', + 579 => 'microscope', + 580 => 'minus', + 581 => 'minus-circle', + 582 => 'minus-square', + 583 => 'mitten', + 584 => 'mobile', + 585 => 'mobile-alt', + 586 => 'money-bill', + 587 => 'money-bill-alt', + 588 => 'money-bill-wave', + 589 => 'money-bill-wave-alt', + 590 => 'money-check', + 591 => 'money-check-alt', + 592 => 'monument', + 593 => 'moon', + 594 => 'mortar-pestle', + 595 => 'mosque', + 596 => 'motorcycle', + 597 => 'mountain', + 598 => 'mouse', + 599 => 'mouse-pointer', + 600 => 'mug-hot', + 601 => 'music', + 602 => 'network-wired', + 603 => 'neuter', + 604 => 'newspaper', + 605 => 'not-equal', + 606 => 'notes-medical', + 607 => 'object-group', + 608 => 'object-ungroup', + 609 => 'oil-can', + 610 => 'om', + 611 => 'otter', + 612 => 'outdent', + 613 => 'pager', + 614 => 'paint-brush', + 615 => 'paint-roller', + 616 => 'palette', + 617 => 'pallet', + 618 => 'paper-plane', + 619 => 'paperclip', + 620 => 'parachute-box', + 621 => 'paragraph', + 622 => 'parking', + 623 => 'passport', + 624 => 'pastafarianism', + 625 => 'paste', + 626 => 'pause', + 627 => 'pause-circle', + 628 => 'paw', + 629 => 'peace', + 630 => 'pen', + 631 => 'pen-alt', + 632 => 'pen-fancy', + 633 => 'pen-nib', + 634 => 'pen-square', + 635 => 'pencil-alt', + 636 => 'pencil-ruler', + 637 => 'people-arrows', + 638 => 'people-carry', + 639 => 'pepper-hot', + 640 => 'percent', + 641 => 'percentage', + 642 => 'person-booth', + 643 => 'phone', + 644 => 'phone-alt', + 645 => 'phone-slash', + 646 => 'phone-square', + 647 => 'phone-square-alt', + 648 => 'phone-volume', + 649 => 'photo-video', + 650 => 'piggy-bank', + 651 => 'pills', + 652 => 'pizza-slice', + 653 => 'place-of-worship', + 654 => 'plane', + 655 => 'plane-arrival', + 656 => 'plane-departure', + 657 => 'plane-slash', + 658 => 'play', + 659 => 'play-circle', + 660 => 'plug', + 661 => 'plus', + 662 => 'plus-circle', + 663 => 'plus-square', + 664 => 'podcast', + 665 => 'poll', + 666 => 'poll-h', + 667 => 'poo', + 668 => 'poo-storm', + 669 => 'poop', + 670 => 'portrait', + 671 => 'pound-sign', + 672 => 'power-off', + 673 => 'pray', + 674 => 'praying-hands', + 675 => 'prescription', + 676 => 'prescription-bottle', + 677 => 'prescription-bottle-alt', + 678 => 'print', + 679 => 'procedures', + 680 => 'project-diagram', + 681 => 'pump-medical', + 682 => 'pump-soap', + 683 => 'puzzle-piece', + 684 => 'qrcode', + 685 => 'question', + 686 => 'question-circle', + 687 => 'quidditch', + 688 => 'quote-left', + 689 => 'quote-right', + 690 => 'quran', + 691 => 'radiation', + 692 => 'radiation-alt', + 693 => 'rainbow', + 694 => 'random', + 695 => 'receipt', + 696 => 'record-vinyl', + 697 => 'recycle', + 698 => 'redo', + 699 => 'redo-alt', + 700 => 'registered', + 701 => 'remove-format', + 702 => 'reply', + 703 => 'reply-all', + 704 => 'republican', + 705 => 'restroom', + 706 => 'retweet', + 707 => 'ribbon', + 708 => 'ring', + 709 => 'road', + 710 => 'robot', + 711 => 'rocket', + 712 => 'route', + 713 => 'rss', + 714 => 'rss-square', + 715 => 'ruble-sign', + 716 => 'ruler', + 717 => 'ruler-combined', + 718 => 'ruler-horizontal', + 719 => 'ruler-vertical', + 720 => 'running', + 721 => 'rupee-sign', + 722 => 'sad-cry', + 723 => 'sad-tear', + 724 => 'satellite', + 725 => 'satellite-dish', + 726 => 'save', + 727 => 'school', + 728 => 'screwdriver', + 729 => 'scroll', + 730 => 'sd-card', + 731 => 'search', + 732 => 'search-dollar', + 733 => 'search-location', + 734 => 'search-minus', + 735 => 'search-plus', + 736 => 'seedling', + 737 => 'server', + 738 => 'shapes', + 739 => 'share', + 740 => 'share-alt', + 741 => 'share-alt-square', + 742 => 'share-square', + 743 => 'shekel-sign', + 744 => 'shield-alt', + 745 => 'shield-virus', + 746 => 'ship', + 747 => 'shipping-fast', + 748 => 'shoe-prints', + 749 => 'shopping-bag', + 750 => 'shopping-basket', + 751 => 'shopping-cart', + 752 => 'shower', + 753 => 'shuttle-van', + 754 => 'sign', + 755 => 'sign-in-alt', + 756 => 'sign-language', + 757 => 'sign-out-alt', + 758 => 'signal', + 759 => 'signature', + 760 => 'sim-card', + 761 => 'sink', + 762 => 'sitemap', + 763 => 'skating', + 764 => 'skiing', + 765 => 'skiing-nordic', + 766 => 'skull', + 767 => 'skull-crossbones', + 768 => 'slash', + 769 => 'sleigh', + 770 => 'sliders-h', + 771 => 'smile', + 772 => 'smile-beam', + 773 => 'smile-wink', + 774 => 'smog', + 775 => 'smoking', + 776 => 'smoking-ban', + 777 => 'sms', + 778 => 'snowboarding', + 779 => 'snowflake', + 780 => 'snowman', + 781 => 'snowplow', + 782 => 'soap', + 783 => 'socks', + 784 => 'solar-panel', + 785 => 'sort', + 786 => 'sort-alpha-down', + 787 => 'sort-alpha-down-alt', + 788 => 'sort-alpha-up', + 789 => 'sort-alpha-up-alt', + 790 => 'sort-amount-down', + 791 => 'sort-amount-down-alt', + 792 => 'sort-amount-up', + 793 => 'sort-amount-up-alt', + 794 => 'sort-down', + 795 => 'sort-numeric-down', + 796 => 'sort-numeric-down-alt', + 797 => 'sort-numeric-up', + 798 => 'sort-numeric-up-alt', + 799 => 'sort-up', + 800 => 'spa', + 801 => 'space-shuttle', + 802 => 'spell-check', + 803 => 'spider', + 804 => 'spinner', + 805 => 'splotch', + 806 => 'spray-can', + 807 => 'square', + 808 => 'square-full', + 809 => 'square-root-alt', + 810 => 'stamp', + 811 => 'star', + 812 => 'star-and-crescent', + 813 => 'star-half', + 814 => 'star-half-alt', + 815 => 'star-of-david', + 816 => 'star-of-life', + 817 => 'step-backward', + 818 => 'step-forward', + 819 => 'stethoscope', + 820 => 'sticky-note', + 821 => 'stop', + 822 => 'stop-circle', + 823 => 'stopwatch', + 824 => 'stopwatch-20', + 825 => 'store', + 826 => 'store-alt', + 827 => 'store-alt-slash', + 828 => 'store-slash', + 829 => 'stream', + 830 => 'street-view', + 831 => 'strikethrough', + 832 => 'stroopwafel', + 833 => 'subscript', + 834 => 'subway', + 835 => 'suitcase', + 836 => 'suitcase-rolling', + 837 => 'sun', + 838 => 'superscript', + 839 => 'surprise', + 840 => 'swatchbook', + 841 => 'swimmer', + 842 => 'swimming-pool', + 843 => 'synagogue', + 844 => 'sync', + 845 => 'sync-alt', + 846 => 'syringe', + 847 => 'table', + 848 => 'table-tennis', + 849 => 'tablet', + 850 => 'tablet-alt', + 851 => 'tablets', + 852 => 'tachometer-alt', + 853 => 'tag', + 854 => 'tags', + 855 => 'tape', + 856 => 'tasks', + 857 => 'taxi', + 858 => 'teeth', + 859 => 'teeth-open', + 860 => 'temperature-high', + 861 => 'temperature-low', + 862 => 'tenge', + 863 => 'terminal', + 864 => 'text-height', + 865 => 'text-width', + 866 => 'th', + 867 => 'th-large', + 868 => 'th-list', + 869 => 'theater-masks', + 870 => 'thermometer', + 871 => 'thermometer-empty', + 872 => 'thermometer-full', + 873 => 'thermometer-half', + 874 => 'thermometer-quarter', + 875 => 'thermometer-three-quarters', + 876 => 'thumbs-down', + 877 => 'thumbs-up', + 878 => 'thumbtack', + 879 => 'ticket-alt', + 880 => 'times', + 881 => 'times-circle', + 882 => 'tint', + 883 => 'tint-slash', + 884 => 'tired', + 885 => 'toggle-off', + 886 => 'toggle-on', + 887 => 'toilet', + 888 => 'toilet-paper', + 889 => 'toilet-paper-slash', + 890 => 'toolbox', + 891 => 'tools', + 892 => 'tooth', + 893 => 'torah', + 894 => 'torii-gate', + 895 => 'tractor', + 896 => 'trademark', + 897 => 'traffic-light', + 898 => 'trailer', + 899 => 'train', + 900 => 'tram', + 901 => 'transgender', + 902 => 'transgender-alt', + 903 => 'trash', + 904 => 'trash-alt', + 905 => 'trash-restore', + 906 => 'trash-restore-alt', + 907 => 'tree', + 908 => 'trophy', + 909 => 'truck', + 910 => 'truck-loading', + 911 => 'truck-monster', + 912 => 'truck-moving', + 913 => 'truck-pickup', + 914 => 'tshirt', + 915 => 'tty', + 916 => 'tv', + 917 => 'umbrella', + 918 => 'umbrella-beach', + 919 => 'underline', + 920 => 'undo', + 921 => 'undo-alt', + 922 => 'universal-access', + 923 => 'university', + 924 => 'unlink', + 925 => 'unlock', + 926 => 'unlock-alt', + 927 => 'upload', + 928 => 'user', + 929 => 'user-alt', + 930 => 'user-alt-slash', + 931 => 'user-astronaut', + 932 => 'user-check', + 933 => 'user-circle', + 934 => 'user-clock', + 935 => 'user-cog', + 936 => 'user-edit', + 937 => 'user-friends', + 938 => 'user-graduate', + 939 => 'user-injured', + 940 => 'user-lock', + 941 => 'user-md', + 942 => 'user-minus', + 943 => 'user-ninja', + 944 => 'user-nurse', + 945 => 'user-plus', + 946 => 'user-secret', + 947 => 'user-shield', + 948 => 'user-slash', + 949 => 'user-tag', + 950 => 'user-tie', + 951 => 'user-times', + 952 => 'users', + 953 => 'users-cog', + 954 => 'users-slash', + 955 => 'utensil-spoon', + 956 => 'utensils', + 957 => 'vector-square', + 958 => 'venus', + 959 => 'venus-double', + 960 => 'venus-mars', + 961 => 'vest', + 962 => 'vest-patches', + 963 => 'vial', + 964 => 'vials', + 965 => 'video', + 966 => 'video-slash', + 967 => 'vihara', + 968 => 'virus', + 969 => 'virus-slash', + 970 => 'viruses', + 971 => 'voicemail', + 972 => 'volleyball-ball', + 973 => 'volume-down', + 974 => 'volume-mute', + 975 => 'volume-off', + 976 => 'volume-up', + 977 => 'vote-yea', + 978 => 'vr-cardboard', + 979 => 'walking', + 980 => 'wallet', + 981 => 'warehouse', + 982 => 'water', + 983 => 'wave-square', + 984 => 'weight', + 985 => 'weight-hanging', + 986 => 'wheelchair', + 987 => 'wifi', + 988 => 'wind', + 989 => 'window-close', + 990 => 'window-maximize', + 991 => 'window-minimize', + 992 => 'window-restore', + 993 => 'wine-bottle', + 994 => 'wine-glass', + 995 => 'wine-glass-alt', + 996 => 'won-sign', + 997 => 'wrench', + 998 => 'x-ray', + 999 => 'yen-sign', + 1000 => 'yin-yang', + ), + 'far' => + array ( + 0 => 'address-book', + 1 => 'address-card', + 2 => 'angry', + 3 => 'arrow-alt-circle-down', + 4 => 'arrow-alt-circle-left', + 5 => 'arrow-alt-circle-right', + 6 => 'arrow-alt-circle-up', + 7 => 'bell', + 8 => 'bell-slash', + 9 => 'bookmark', + 10 => 'building', + 11 => 'calendar', + 12 => 'calendar-alt', + 13 => 'calendar-check', + 14 => 'calendar-minus', + 15 => 'calendar-plus', + 16 => 'calendar-times', + 17 => 'caret-square-down', + 18 => 'caret-square-left', + 19 => 'caret-square-right', + 20 => 'caret-square-up', + 21 => 'chart-bar', + 22 => 'check-circle', + 23 => 'check-square', + 24 => 'circle', + 25 => 'clipboard', + 26 => 'clock', + 27 => 'clone', + 28 => 'closed-captioning', + 29 => 'comment', + 30 => 'comment-alt', + 31 => 'comment-dots', + 32 => 'comments', + 33 => 'compass', + 34 => 'copy', + 35 => 'copyright', + 36 => 'credit-card', + 37 => 'dizzy', + 38 => 'dot-circle', + 39 => 'edit', + 40 => 'envelope', + 41 => 'envelope-open', + 42 => 'eye', + 43 => 'eye-slash', + 44 => 'file', + 45 => 'file-alt', + 46 => 'file-archive', + 47 => 'file-audio', + 48 => 'file-code', + 49 => 'file-excel', + 50 => 'file-image', + 51 => 'file-pdf', + 52 => 'file-powerpoint', + 53 => 'file-video', + 54 => 'file-word', + 55 => 'flag', + 56 => 'flushed', + 57 => 'folder', + 58 => 'folder-open', + 59 => 'frown', + 60 => 'frown-open', + 61 => 'futbol', + 62 => 'gem', + 63 => 'grimace', + 64 => 'grin', + 65 => 'grin-alt', + 66 => 'grin-beam', + 67 => 'grin-beam-sweat', + 68 => 'grin-hearts', + 69 => 'grin-squint', + 70 => 'grin-squint-tears', + 71 => 'grin-stars', + 72 => 'grin-tears', + 73 => 'grin-tongue', + 74 => 'grin-tongue-squint', + 75 => 'grin-tongue-wink', + 76 => 'grin-wink', + 77 => 'hand-lizard', + 78 => 'hand-paper', + 79 => 'hand-peace', + 80 => 'hand-point-down', + 81 => 'hand-point-left', + 82 => 'hand-point-right', + 83 => 'hand-point-up', + 84 => 'hand-pointer', + 85 => 'hand-rock', + 86 => 'hand-scissors', + 87 => 'hand-spock', + 88 => 'handshake', + 89 => 'hdd', + 90 => 'heart', + 91 => 'hospital', + 92 => 'hourglass', + 93 => 'id-badge', + 94 => 'id-card', + 95 => 'image', + 96 => 'images', + 97 => 'keyboard', + 98 => 'kiss', + 99 => 'kiss-beam', + 100 => 'kiss-wink-heart', + 101 => 'laugh', + 102 => 'laugh-beam', + 103 => 'laugh-squint', + 104 => 'laugh-wink', + 105 => 'lemon', + 106 => 'life-ring', + 107 => 'lightbulb', + 108 => 'list-alt', + 109 => 'map', + 110 => 'meh', + 111 => 'meh-blank', + 112 => 'meh-rolling-eyes', + 113 => 'minus-square', + 114 => 'money-bill-alt', + 115 => 'moon', + 116 => 'newspaper', + 117 => 'object-group', + 118 => 'object-ungroup', + 119 => 'paper-plane', + 120 => 'pause-circle', + 121 => 'play-circle', + 122 => 'plus-square', + 123 => 'question-circle', + 124 => 'registered', + 125 => 'sad-cry', + 126 => 'sad-tear', + 127 => 'save', + 128 => 'share-square', + 129 => 'smile', + 130 => 'smile-beam', + 131 => 'smile-wink', + 132 => 'snowflake', + 133 => 'square', + 134 => 'star', + 135 => 'star-half', + 136 => 'sticky-note', + 137 => 'stop-circle', + 138 => 'sun', + 139 => 'surprise', + 140 => 'thumbs-down', + 141 => 'thumbs-up', + 142 => 'times-circle', + 143 => 'tired', + 144 => 'trash-alt', + 145 => 'user', + 146 => 'user-circle', + 147 => 'window-close', + 148 => 'window-maximize', + 149 => 'window-minimize', + 150 => 'window-restore', + ), + 'fab' => + array ( + 0 => '500px', + 1 => 'accessible-icon', + 2 => 'accusoft', + 3 => 'acquisitions-incorporated', + 4 => 'adn', + 5 => 'adversal', + 6 => 'affiliatetheme', + 7 => 'airbnb', + 8 => 'algolia', + 9 => 'alipay', + 10 => 'amazon', + 11 => 'amazon-pay', + 12 => 'amilia', + 13 => 'android', + 14 => 'angellist', + 15 => 'angrycreative', + 16 => 'angular', + 17 => 'app-store', + 18 => 'app-store-ios', + 19 => 'apper', + 20 => 'apple', + 21 => 'apple-pay', + 22 => 'artstation', + 23 => 'asymmetrik', + 24 => 'atlassian', + 25 => 'audible', + 26 => 'autoprefixer', + 27 => 'avianex', + 28 => 'aviato', + 29 => 'aws', + 30 => 'bandcamp', + 31 => 'battle-net', + 32 => 'behance', + 33 => 'behance-square', + 34 => 'bimobject', + 35 => 'bitbucket', + 36 => 'bitcoin', + 37 => 'bity', + 38 => 'black-tie', + 39 => 'blackberry', + 40 => 'blogger', + 41 => 'blogger-b', + 42 => 'bluetooth', + 43 => 'bluetooth-b', + 44 => 'bootstrap', + 45 => 'btc', + 46 => 'buffer', + 47 => 'buromobelexperte', + 48 => 'buy-n-large', + 49 => 'buysellads', + 50 => 'canadian-maple-leaf', + 51 => 'cc-amazon-pay', + 52 => 'cc-amex', + 53 => 'cc-apple-pay', + 54 => 'cc-diners-club', + 55 => 'cc-discover', + 56 => 'cc-jcb', + 57 => 'cc-mastercard', + 58 => 'cc-paypal', + 59 => 'cc-stripe', + 60 => 'cc-visa', + 61 => 'centercode', + 62 => 'centos', + 63 => 'chrome', + 64 => 'chromecast', + 65 => 'cloudflare', + 66 => 'cloudscale', + 67 => 'cloudsmith', + 68 => 'cloudversify', + 69 => 'codepen', + 70 => 'codiepie', + 71 => 'confluence', + 72 => 'connectdevelop', + 73 => 'contao', + 74 => 'cotton-bureau', + 75 => 'cpanel', + 76 => 'creative-commons', + 77 => 'creative-commons-by', + 78 => 'creative-commons-nc', + 79 => 'creative-commons-nc-eu', + 80 => 'creative-commons-nc-jp', + 81 => 'creative-commons-nd', + 82 => 'creative-commons-pd', + 83 => 'creative-commons-pd-alt', + 84 => 'creative-commons-remix', + 85 => 'creative-commons-sa', + 86 => 'creative-commons-sampling', + 87 => 'creative-commons-sampling-plus', + 88 => 'creative-commons-share', + 89 => 'creative-commons-zero', + 90 => 'critical-role', + 91 => 'css3', + 92 => 'css3-alt', + 93 => 'cuttlefish', + 94 => 'd-and-d', + 95 => 'd-and-d-beyond', + 96 => 'dailymotion', + 97 => 'dashcube', + 98 => 'deezer', + 99 => 'delicious', + 100 => 'deploydog', + 101 => 'deskpro', + 102 => 'dev', + 103 => 'deviantart', + 104 => 'dhl', + 105 => 'diaspora', + 106 => 'digg', + 107 => 'digital-ocean', + 108 => 'discord', + 109 => 'discourse', + 110 => 'dochub', + 111 => 'docker', + 112 => 'draft2digital', + 113 => 'dribbble', + 114 => 'dribbble-square', + 115 => 'dropbox', + 116 => 'drupal', + 117 => 'dyalog', + 118 => 'earlybirds', + 119 => 'ebay', + 120 => 'edge', + 121 => 'edge-legacy', + 122 => 'elementor', + 123 => 'ello', + 124 => 'ember', + 125 => 'empire', + 126 => 'envira', + 127 => 'erlang', + 128 => 'ethereum', + 129 => 'etsy', + 130 => 'evernote', + 131 => 'expeditedssl', + 132 => 'facebook', + 133 => 'facebook-f', + 134 => 'facebook-messenger', + 135 => 'facebook-square', + 136 => 'fantasy-flight-games', + 137 => 'fedex', + 138 => 'fedora', + 139 => 'figma', + 140 => 'firefox', + 141 => 'firefox-browser', + 142 => 'first-order', + 143 => 'first-order-alt', + 144 => 'firstdraft', + 145 => 'flickr', + 146 => 'flipboard', + 147 => 'fly', + 148 => 'font-awesome', + 149 => 'font-awesome-alt', + 150 => 'font-awesome-flag', + 151 => 'fonticons', + 152 => 'fonticons-fi', + 153 => 'fort-awesome', + 154 => 'fort-awesome-alt', + 155 => 'forumbee', + 156 => 'foursquare', + 157 => 'free-code-camp', + 158 => 'freebsd', + 159 => 'fulcrum', + 160 => 'galactic-republic', + 161 => 'galactic-senate', + 162 => 'get-pocket', + 163 => 'gg', + 164 => 'gg-circle', + 165 => 'git', + 166 => 'git-alt', + 167 => 'git-square', + 168 => 'github', + 169 => 'github-alt', + 170 => 'github-square', + 171 => 'gitkraken', + 172 => 'gitlab', + 173 => 'gitter', + 174 => 'glide', + 175 => 'glide-g', + 176 => 'gofore', + 177 => 'goodreads', + 178 => 'goodreads-g', + 179 => 'google', + 180 => 'google-drive', + 181 => 'google-pay', + 182 => 'google-play', + 183 => 'google-plus', + 184 => 'google-plus-g', + 185 => 'google-plus-square', + 186 => 'google-wallet', + 187 => 'gratipay', + 188 => 'grav', + 189 => 'gripfire', + 190 => 'grunt', + 191 => 'guilded', + 192 => 'gulp', + 193 => 'hacker-news', + 194 => 'hacker-news-square', + 195 => 'hackerrank', + 196 => 'hips', + 197 => 'hire-a-helper', + 198 => 'hive', + 199 => 'hooli', + 200 => 'hornbill', + 201 => 'hotjar', + 202 => 'houzz', + 203 => 'html5', + 204 => 'hubspot', + 205 => 'ideal', + 206 => 'imdb', + 207 => 'innosoft', + 208 => 'instagram', + 209 => 'instagram-square', + 210 => 'instalod', + 211 => 'intercom', + 212 => 'internet-explorer', + 213 => 'invision', + 214 => 'ioxhost', + 215 => 'itch-io', + 216 => 'itunes', + 217 => 'itunes-note', + 218 => 'java', + 219 => 'jedi-order', + 220 => 'jenkins', + 221 => 'jira', + 222 => 'joget', + 223 => 'joomla', + 224 => 'js', + 225 => 'js-square', + 226 => 'jsfiddle', + 227 => 'kaggle', + 228 => 'keybase', + 229 => 'keycdn', + 230 => 'kickstarter', + 231 => 'kickstarter-k', + 232 => 'korvue', + 233 => 'laravel', + 234 => 'lastfm', + 235 => 'lastfm-square', + 236 => 'leanpub', + 237 => 'less', + 238 => 'line', + 239 => 'linkedin', + 240 => 'linkedin-in', + 241 => 'linode', + 242 => 'linux', + 243 => 'lyft', + 244 => 'magento', + 245 => 'mailchimp', + 246 => 'mandalorian', + 247 => 'markdown', + 248 => 'mastodon', + 249 => 'maxcdn', + 250 => 'mdb', + 251 => 'medapps', + 252 => 'medium', + 253 => 'medium-m', + 254 => 'medrt', + 255 => 'meetup', + 256 => 'megaport', + 257 => 'mendeley', + 258 => 'microblog', + 259 => 'microsoft', + 260 => 'mix', + 261 => 'mixcloud', + 262 => 'mixer', + 263 => 'mizuni', + 264 => 'modx', + 265 => 'monero', + 266 => 'napster', + 267 => 'neos', + 268 => 'nimblr', + 269 => 'node', + 270 => 'node-js', + 271 => 'npm', + 272 => 'ns8', + 273 => 'nutritionix', + 274 => 'octopus-deploy', + 275 => 'odnoklassniki', + 276 => 'odnoklassniki-square', + 277 => 'old-republic', + 278 => 'opencart', + 279 => 'openid', + 280 => 'opera', + 281 => 'optin-monster', + 282 => 'orcid', + 283 => 'osi', + 284 => 'page4', + 285 => 'pagelines', + 286 => 'palfed', + 287 => 'patreon', + 288 => 'paypal', + 289 => 'penny-arcade', + 290 => 'perbyte', + 291 => 'periscope', + 292 => 'phabricator', + 293 => 'phoenix-framework', + 294 => 'phoenix-squadron', + 295 => 'php', + 296 => 'pied-piper', + 297 => 'pied-piper-alt', + 298 => 'pied-piper-hat', + 299 => 'pied-piper-pp', + 300 => 'pied-piper-square', + 301 => 'pinterest', + 302 => 'pinterest-p', + 303 => 'pinterest-square', + 304 => 'playstation', + 305 => 'product-hunt', + 306 => 'pushed', + 307 => 'python', + 308 => 'qq', + 309 => 'quinscape', + 310 => 'quora', + 311 => 'r-project', + 312 => 'raspberry-pi', + 313 => 'ravelry', + 314 => 'react', + 315 => 'reacteurope', + 316 => 'readme', + 317 => 'rebel', + 318 => 'red-river', + 319 => 'reddit', + 320 => 'reddit-alien', + 321 => 'reddit-square', + 322 => 'redhat', + 323 => 'renren', + 324 => 'replyd', + 325 => 'researchgate', + 326 => 'resolving', + 327 => 'rev', + 328 => 'rocketchat', + 329 => 'rockrms', + 330 => 'rust', + 331 => 'safari', + 332 => 'salesforce', + 333 => 'sass', + 334 => 'schlix', + 335 => 'scribd', + 336 => 'searchengin', + 337 => 'sellcast', + 338 => 'sellsy', + 339 => 'servicestack', + 340 => 'shirtsinbulk', + 341 => 'shopify', + 342 => 'shopware', + 343 => 'simplybuilt', + 344 => 'sistrix', + 345 => 'sith', + 346 => 'sketch', + 347 => 'skyatlas', + 348 => 'skype', + 349 => 'slack', + 350 => 'slack-hash', + 351 => 'slideshare', + 352 => 'snapchat', + 353 => 'snapchat-ghost', + 354 => 'snapchat-square', + 355 => 'soundcloud', + 356 => 'sourcetree', + 357 => 'speakap', + 358 => 'speaker-deck', + 359 => 'spotify', + 360 => 'squarespace', + 361 => 'stack-exchange', + 362 => 'stack-overflow', + 363 => 'stackpath', + 364 => 'staylinked', + 365 => 'steam', + 366 => 'steam-square', + 367 => 'steam-symbol', + 368 => 'sticker-mule', + 369 => 'strava', + 370 => 'stripe', + 371 => 'stripe-s', + 372 => 'studiovinari', + 373 => 'stumbleupon', + 374 => 'stumbleupon-circle', + 375 => 'superpowers', + 376 => 'supple', + 377 => 'suse', + 378 => 'swift', + 379 => 'symfony', + 380 => 'teamspeak', + 381 => 'telegram', + 382 => 'telegram-plane', + 383 => 'tencent-weibo', + 384 => 'the-red-yeti', + 385 => 'themeco', + 386 => 'themeisle', + 387 => 'think-peaks', + 388 => 'tiktok', + 389 => 'trade-federation', + 390 => 'trello', + 391 => 'tumblr', + 392 => 'tumblr-square', + 393 => 'twitch', + 394 => 'twitter', + 395 => 'twitter-square', + 396 => 'typo3', + 397 => 'uber', + 398 => 'ubuntu', + 399 => 'uikit', + 400 => 'umbraco', + 401 => 'uncharted', + 402 => 'uniregistry', + 403 => 'unity', + 404 => 'unsplash', + 405 => 'untappd', + 406 => 'ups', + 407 => 'usb', + 408 => 'usps', + 409 => 'ussunnah', + 410 => 'vaadin', + 411 => 'viacoin', + 412 => 'viadeo', + 413 => 'viadeo-square', + 414 => 'viber', + 415 => 'vimeo', + 416 => 'vimeo-square', + 417 => 'vimeo-v', + 418 => 'vine', + 419 => 'vk', + 420 => 'vnv', + 421 => 'vuejs', + 422 => 'watchman-monitoring', + 423 => 'waze', + 424 => 'weebly', + 425 => 'weibo', + 426 => 'weixin', + 427 => 'whatsapp', + 428 => 'whatsapp-square', + 429 => 'whmcs', + 430 => 'wikipedia-w', + 431 => 'windows', + 432 => 'wix', + 433 => 'wizards-of-the-coast', + 434 => 'wodu', + 435 => 'wolf-pack-battalion', + 436 => 'wordpress', + 437 => 'wordpress-simple', + 438 => 'wpbeginner', + 439 => 'wpexplorer', + 440 => 'wpforms', + 441 => 'wpressr', + 442 => 'xbox', + 443 => 'xing', + 444 => 'xing-square', + 445 => 'y-combinator', + 446 => 'yahoo', + 447 => 'yammer', + 448 => 'yandex', + 449 => 'yandex-international', + 450 => 'yarn', + 451 => 'yelp', + 452 => 'yoast', + 453 => 'youtube', + 454 => 'youtube-square', + 455 => 'zhihu', + ), +); diff --git a/tools/generate-fa5-icons.php b/tools/generate-fa5-icons.php new file mode 100644 index 00000000..f097293d --- /dev/null +++ b/tools/generate-fa5-icons.php @@ -0,0 +1,84 @@ + 0xf015) +$name_to_code = []; +if (!preg_match_all('/\.fa-([a-z0-9-]+):before\{content:"\\\\([0-9a-f]{4})"\}/i', $css, $m, PREG_SET_ORDER)) { + fwrite(STDERR, "No glyph rules matched in all.min.css\n"); + exit(1); +} +foreach ($m as $hit) { + $name_to_code[$hit[1]] = hexdec($hit[2]); +} + +// style → set of codepoints, parsed from the SVG font files +$code_to_style = ['fas' => [], 'far' => [], 'fab' => []]; +foreach (array_keys($code_to_style) as $style) { + $file = $fonts_path . '/fa-' . ($style === 'fab' ? 'brands-400' : ($style === 'far' ? 'regular-400' : 'solid-900')) . '.svg'; + $src = (string) file_get_contents($file); + if (!preg_match_all('/]*unicode="&#x([0-9a-f]+);"/i', $src, $g, PREG_SET_ORDER)) { + fwrite(STDERR, "No glyphs matched in {$file}\n"); + exit(1); + } + foreach ($g as $hit) { + $code_to_style[$style][hexdec($hit[1])] = true; + } +} + +$groups = ['fas' => [], 'far' => [], 'fab' => []]; +foreach ($name_to_code as $name => $code) { + foreach ($groups as $style => $unused) { + if (!empty($code_to_style[$style][$code])) { + $groups[$style][] = $name; + } + } +} +foreach ($groups as $style => $names) { + sort($groups[$style], SORT_NATURAL | SORT_FLAG_CASE); +} + +$export = var_export($groups, true); + +$php = << Date: Sun, 6 Sep 2026 12:59:12 +0800 Subject: [PATCH 2/7] feat(widgets): render icon fields with the universal icon picker The widget FormRenderer icon case and the nav-menu item icon field now emit the shared picker (preview + choose/clear + editable text input, delegated events so lazy forms and repeater rows work). Icon values are sanitized through aksrt_sanitize_icon_value on both save paths so inline customsvg- markup survives; the widget admin screen enqueues the picker. --- assets/widgets/admin.css | 1 - src/Admin/NavMenuOptions.php | 36 +++++++++++++----------- src/Admin/SchemaProvider.php | 6 +++- src/WidgetSystem/AdminFormController.php | 1 + src/WidgetSystem/FieldSchema.php | 4 ++- src/WidgetSystem/FormRenderer.php | 2 +- 6 files changed, 30 insertions(+), 20 deletions(-) diff --git a/assets/widgets/admin.css b/assets/widgets/admin.css index a486c5af..f95c8c17 100644 --- a/assets/widgets/admin.css +++ b/assets/widgets/admin.css @@ -86,7 +86,6 @@ } .aksrt-widget-number, -.aksrt-widget-icon, .aksrt-widget-link, .aksrt-widget-background { display: grid; diff --git a/src/Admin/NavMenuOptions.php b/src/Admin/NavMenuOptions.php index d5930068..01080607 100644 --- a/src/Admin/NavMenuOptions.php +++ b/src/Admin/NavMenuOptions.php @@ -21,18 +21,18 @@ class NavMenuOptions { if ($hook !== 'nav-menus.php') { return; } - $path = get_template_directory() . '/assets/js/admin-nav-menu-senior.js'; - if (!file_exists($path)) { - return; - } wp_enqueue_media(); - wp_enqueue_script( - 'aksrt-admin-nav-menu-senior', - get_template_directory_uri() . '/assets/js/admin-nav-menu-senior.js', - ['jquery'], - (string) filemtime($path), - true - ); + aksrt_icon_picker_assets(); + $path = get_template_directory() . '/assets/js/admin-nav-menu-senior.js'; + if (file_exists($path)) { + wp_enqueue_script( + 'aksrt-admin-nav-menu-senior', + get_template_directory_uri() . '/assets/js/admin-nav-menu-senior.js', + ['jquery'], + (string) filemtime($path), + true + ); + } } /** @@ -57,11 +57,15 @@ class NavMenuOptions { ?>

+

'; + echo aksrt_icon_picker_field($field_name, $field_id, (string) $value); break; case 'link': self::link($widget, $key, is_array($value) ? $value : []); From 5b5db0f8f542dd9b1561c327435383b3531eb1fd Mon Sep 17 00:00:00 2001 From: unknown Date: Sun, 6 Sep 2026 12:59:30 +0800 Subject: [PATCH 3/7] feat(admin): back the SPA icon picker with the server catalog FaIconPicker drops its duplicated hard-coded FA subset and loads GET /aksrt/v1/icons/catalog: full solid/regular/brands sets, the built-in zibsvg sprite tab and an inline custom SVG tab. Props/emit surface is unchanged; the system-notice title icon schema drift (input vs icon) is fixed on the JS side. --- assets/admin/src/api/modules/icons.js | 8 + assets/admin/src/api/modules/index.js | 2 + .../src/components/form/FaIconPicker.vue | 233 +++++++++++------- .../schema/sections/global/system-notice.js | 2 +- assets/admin/src/utils/icons.js | 62 +++++ 5 files changed, 211 insertions(+), 96 deletions(-) create mode 100644 assets/admin/src/api/modules/icons.js create mode 100644 assets/admin/src/utils/icons.js diff --git a/assets/admin/src/api/modules/icons.js b/assets/admin/src/api/modules/icons.js new file mode 100644 index 00000000..2111f7a6 --- /dev/null +++ b/assets/admin/src/api/modules/icons.js @@ -0,0 +1,8 @@ +/** + * Icon catalog API — serves the universal icon picker (FA5 + sprite + custom). + */ +export const iconsApiMethods = { + getIconCatalog() { + return this.request('/icons/catalog') + }, +} diff --git a/assets/admin/src/api/modules/index.js b/assets/admin/src/api/modules/index.js index 0c07bee0..9aeb8de0 100644 --- a/assets/admin/src/api/modules/index.js +++ b/assets/admin/src/api/modules/index.js @@ -6,6 +6,7 @@ import { paymentApiMethods } from './payment.js' import { extensionsApiMethods } from './extensions.js' import { toolsApiMethods } from './tools.js' import { schemaApiMethods } from './schema.js' +import { iconsApiMethods } from './icons.js' export const apiMethods = { ...globalApiMethods, @@ -16,4 +17,5 @@ export const apiMethods = { ...extensionsApiMethods, ...toolsApiMethods, ...schemaApiMethods, + ...iconsApiMethods, } diff --git a/assets/admin/src/components/form/FaIconPicker.vue b/assets/admin/src/components/form/FaIconPicker.vue index 1a45f5c5..436d01ff 100644 --- a/assets/admin/src/components/form/FaIconPicker.vue +++ b/assets/admin/src/components/form/FaIconPicker.vue @@ -1,6 +1,6 @@