sbuilder-mcp 0.80.0 → 0.81.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +16 -0
- package/CHANGELOG.vi.md +16 -0
- package/README.md +1 -1
- package/README.vi.md +1 -1
- package/dist/catalog/element-search.js +3 -0
- package/dist/catalog/icons.generated.js +99 -0
- package/dist/domains/site/builder.js +26 -11
- package/dist/domains/site/findings.js +2 -0
- package/dist/domains/site/icons.js +124 -0
- package/dist/domains/site/review.js +18 -0
- package/dist/domains/site/writecheck.js +11 -4
- package/dist/tools/chrome.js +31 -1
- package/dist/tools/live.js +8 -3
- package/dist/tools/page.js +114 -19
- package/dist/tools/store.js +3 -2
- package/dist/transport/media.js +7 -1
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -6,6 +6,22 @@ All notable changes to this project are documented in this file.
|
|
|
6
6
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
|
|
7
7
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
8
8
|
|
|
9
|
+
## [0.81.1] - 2026-10-10
|
|
10
|
+
|
|
11
|
+
### Fixed
|
|
12
|
+
- `sb_set` and `sb_add` suggest the right icon for a misspelled name, ranking names that contain the typed word first, then close typos by edit distance, and honoring a `Fill` or `Line` suffix the caller wrote.
|
|
13
|
+
- `sb_duplicate` with `to_page_id` reports that `parent_id` is not on the target page and says to omit it or read ids with `sb_outline`.
|
|
14
|
+
- `sb_media_upload` of a font file tells the caller how to create a font family instead of suggesting `sb_set` with `src`.
|
|
15
|
+
|
|
16
|
+
## [0.81.0] - 2026-10-10
|
|
17
|
+
|
|
18
|
+
### Added
|
|
19
|
+
- `sb_set` and `sb_add` warn with `unknown_icon` and the nearest valid name when an icon key is not in the editor's icon list, and `sb_review` reports the same finding.
|
|
20
|
+
- `sb_catalog_search` finds icons with an `icon:<word>` query, and `sb_traits_for` returns `icon_keys` for elements that take an icon.
|
|
21
|
+
- `sb_duplicate` accepts `to_page_id` and `to_site_id` to paste a section onto another page or another site, running every check before the target page changes.
|
|
22
|
+
- `sb_store` with `action:"template_save"` accepts `template_id` to overwrite an existing template's content, guarded by its revision.
|
|
23
|
+
- `sb_media_upload` accepts font files (woff2, woff, ttf, otf).
|
|
24
|
+
|
|
9
25
|
## [0.80.0] - 2026-10-10
|
|
10
26
|
|
|
11
27
|
### Added
|
package/CHANGELOG.vi.md
CHANGED
|
@@ -6,6 +6,22 @@ Mọi thay đổi đáng chú ý của dự án được ghi lại trong file n
|
|
|
6
6
|
Định dạng dựa trên [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
|
|
7
7
|
và dự án tuân theo [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
8
8
|
|
|
9
|
+
## [0.81.1] - 2026-10-10
|
|
10
|
+
|
|
11
|
+
### Fixed
|
|
12
|
+
- `sb_set` và `sb_add` gợi ý đúng icon cho tên gõ sai, ưu tiên tên chứa nguyên từ đã gõ, rồi đến lỗi gõ gần theo khoảng cách sửa, và tôn trọng đuôi `Fill` hoặc `Line` do người gọi viết.
|
|
13
|
+
- `sb_duplicate` với `to_page_id` báo `parent_id` không có trên trang đích và hướng dẫn bỏ qua nó hoặc đọc id bằng `sb_outline`.
|
|
14
|
+
- `sb_media_upload` với file font hướng dẫn cách tạo họ font thay vì gợi ý `sb_set` với `src`.
|
|
15
|
+
|
|
16
|
+
## [0.81.0] - 2026-10-10
|
|
17
|
+
|
|
18
|
+
### Added
|
|
19
|
+
- `sb_set` và `sb_add` cảnh báo `unknown_icon` kèm tên hợp lệ gần nhất khi khoá icon không có trong danh sách icon của editor, và `sb_review` báo cùng phát hiện đó.
|
|
20
|
+
- `sb_catalog_search` tìm icon bằng truy vấn `icon:<từ>`, và `sb_traits_for` trả về `icon_keys` cho các element nhận icon.
|
|
21
|
+
- `sb_duplicate` nhận `to_page_id` và `to_site_id` để dán section sang trang hoặc site khác, chạy mọi kiểm tra trước khi đổi trang đích.
|
|
22
|
+
- `sb_store` với `action:"template_save"` nhận `template_id` để ghi đè nội dung template có sẵn, có rào theo rev.
|
|
23
|
+
- `sb_media_upload` nhận file font (woff2, woff, ttf, otf).
|
|
24
|
+
|
|
9
25
|
## [0.80.0] - 2026-10-10
|
|
10
26
|
|
|
11
27
|
### Added
|
package/README.md
CHANGED
|
@@ -93,7 +93,7 @@ in this client: one would put the very key the platform exists to hold back into
|
|
|
93
93
|
| `sb_set` | Write style/config/specials. Per breakpoint by default |
|
|
94
94
|
| `sb_move` | Move a node to another parent |
|
|
95
95
|
| `sb_remove` | Remove a node and its subtree |
|
|
96
|
-
| `sb_duplicate` | Copy a node and its subtree under fresh ids, right after the original |
|
|
96
|
+
| `sb_duplicate` | Copy a node and its subtree under fresh ids, right after the original — or onto another page or site, as the editor's copy/paste does (assets copied, references that cannot travel listed) |
|
|
97
97
|
| `sb_templates` | The store's saved section templates, plus the BUILT-IN layouts — hero, feature trio, stats, FAQ, CTA band, product shelf, category strip, brand wall, trust band — composed against the page's own tokens |
|
|
98
98
|
| `sb_template_use` | Instantiate a template into a page |
|
|
99
99
|
| `sb_page_list` | Every page on the site |
|
package/README.vi.md
CHANGED
|
@@ -90,7 +90,7 @@ nhét ngược lại vào mọi bản cài.
|
|
|
90
90
|
| `sb_set` | Ghi style/config/specials. Mặc định theo breakpoint |
|
|
91
91
|
| `sb_move` | Chuyển node sang cha khác |
|
|
92
92
|
| `sb_remove` | Xoá node và cả cây con |
|
|
93
|
-
| `sb_duplicate` | Nhân bản một node và cả cây con dưới id mới, ngay sau bản gốc |
|
|
93
|
+
| `sb_duplicate` | Nhân bản một node và cả cây con dưới id mới, ngay sau bản gốc — hoặc sang trang/site khác như copy/paste của editor (ảnh được copy, tham chiếu không mang theo được sẽ được liệt kê) |
|
|
94
94
|
| `sb_templates` | Section template đã lưu của cửa hàng, cộng bộ layout DỰNG SẴN — hero, ba lợi ích, số liệu, FAQ, dải kêu gọi, kệ sản phẩm, dải danh mục, dải logo, dải cam kết — soạn theo token của chính trang |
|
|
95
95
|
| `sb_template_use` | Thả một template vào trang |
|
|
96
96
|
| `sb_page_list` | Mọi trang của site |
|
|
@@ -4,6 +4,7 @@ import { isBaseOnlyConfig } from '../domains/site/baseonly.js';
|
|
|
4
4
|
import { bindableFields } from '../domains/site/writecheck.js';
|
|
5
5
|
import { animationValues, animationVocabulary, elementVocabularies, sharedVocabularies, vocabulariesForWrites, } from '../domains/site/vocabulary.js';
|
|
6
6
|
import { neverTranslatedOn, translatableSpecials } from '../domains/site/translate.js';
|
|
7
|
+
import { iconKeysOf } from '../domains/site/icons.js';
|
|
7
8
|
/** Eight to choose from; the hints for the chosen one come with sb_traits_for. */
|
|
8
9
|
export const DEFAULT_CATALOG_LIMIT = 8;
|
|
9
10
|
/**
|
|
@@ -302,6 +303,8 @@ export function traitsFor(type, control) {
|
|
|
302
303
|
...(el.bindingEvents ? { binding_events: el.bindingEvents } : {}),
|
|
303
304
|
// The specials a record binding may target on this element.
|
|
304
305
|
...(bindable.length ? { bindable } : {}),
|
|
306
|
+
// Keys holding a RemixIcon name — sb_catalog_search query "icon:<word>" finds one.
|
|
307
|
+
...(iconKeysOf(el.type).length ? { icon_keys: iconKeysOf(el.type) } : {}),
|
|
305
308
|
// The keys this element actually seeds. For `config` and `specials` —
|
|
306
309
|
// which, unlike `style`, are NOT open — this is the machine-readable
|
|
307
310
|
// answer to "what does this element store", and often the only one.
|
|
@@ -15,3 +15,102 @@ export const ICON_SOURCE = {
|
|
|
15
15
|
* skipped, because the page then carries an empty box nobody put there.
|
|
16
16
|
*/
|
|
17
17
|
export const ICON_NAMES = new Set(["24HoursFill", "24HoursLine", "4kFill", "4kLine", "AB", "AccessibilityFill", "AccessibilityLine", "AccountBox2Fill", "AccountBox2Line", "AccountBoxFill", "AccountBoxLine", "AccountCircle2Fill", "AccountCircle2Line", "AccountCircleFill", "AccountCircleLine", "AccountPinBoxFill", "AccountPinBoxLine", "AccountPinCircleFill", "AccountPinCircleLine", "AddBoxFill", "AddBoxLine", "AddCircleFill", "AddCircleLine", "AddFill", "AddLargeFill", "AddLargeLine", "AddLine", "AdminFill", "AdminLine", "AdvertisementFill", "AdvertisementLine", "AedElectrodesFill", "AedElectrodesLine", "AedFill", "AedLine", "Ai", "AiAgentFill", "AiAgentLine", "AiGenerate", "AiGenerate2", "AiGenerate2Fill", "AiGenerate2Line", "AiGenerate3dFill", "AiGenerate3dLine", "AiGenerateText", "AirplayFill", "AirplayLine", "AlarmAddFill", "AlarmAddLine", "AlarmFill", "AlarmLine", "AlarmSnoozeFill", "AlarmSnoozeLine", "AlarmWarningFill", "AlarmWarningLine", "AlbumFill", "AlbumLine", "AlertFill", "AlertLine", "AlibabaCloudFill", "AlibabaCloudLine", "AliensFill", "AliensLine", "AlignBottom", "AlignCenter", "AlignItemBottomFill", "AlignItemBottomLine", "AlignItemHorizontalCenterFill", "AlignItemHorizontalCenterLine", "AlignItemLeftFill", "AlignItemLeftLine", "AlignItemRightFill", "AlignItemRightLine", "AlignItemTopFill", "AlignItemTopLine", "AlignItemVerticalCenterFill", "AlignItemVerticalCenterLine", "AlignJustify", "AlignLeft", "AlignRight", "AlignTop", "AlignVertically", "AlipayFill", "AlipayLine", "AmazonFill", "AmazonLine", "AnchorFill", "AnchorLine", "AncientGateFill", "AncientGateLine", "AncientPavilionFill", "AncientPavilionLine", "AndroidFill", "AndroidLine", "AngularjsFill", "AngularjsLine", "AnthropicFill", "AnthropicLine", "Anticlockwise2Fill", "Anticlockwise2Line", "AnticlockwiseFill", "AnticlockwiseLine", "AppStoreFill", "AppStoreLine", "AppleFill", "AppleLine", "Apps2AddFill", "Apps2AddLine", "Apps2AiFill", "Apps2AiLine", "Apps2Fill", "Apps2Line", "AppsAiFill", "AppsAiLine", "AppsFill", "AppsLine", "Archive2Fill", "Archive2Line", "ArchiveDrawerFill", "ArchiveDrawerLine", "ArchiveFill", "ArchiveLine", "ArchiveStackFill", "ArchiveStackLine", "AreaChartFill", "AreaChartLine", "ArmchairFill", "ArmchairLine", "ArrowDownBoxFill", "ArrowDownBoxLine", "ArrowDownCircleFill", "ArrowDownCircleLine", "ArrowDownDoubleFill", "ArrowDownDoubleLine", "ArrowDownFill", "ArrowDownLine", "ArrowDownLongFill", "ArrowDownLongLine", "ArrowDownSFill", "ArrowDownSLine", "ArrowDownWideFill", "ArrowDownWideLine", "ArrowDropDownFill", "ArrowDropDownLine", "ArrowDropLeftFill", "ArrowDropLeftLine", "ArrowDropRightFill", "ArrowDropRightLine", "ArrowDropUpFill", "ArrowDropUpLine", "ArrowGoBackFill", "ArrowGoBackLine", "ArrowGoForwardFill", "ArrowGoForwardLine", "ArrowLeftBoxFill", "ArrowLeftBoxLine", "ArrowLeftCircleFill", "ArrowLeftCircleLine", "ArrowLeftDoubleFill", "ArrowLeftDoubleLine", "ArrowLeftDownBoxFill", "ArrowLeftDownBoxLine", "ArrowLeftDownFill", "ArrowLeftDownLine", "ArrowLeftDownLongFill", "ArrowLeftDownLongLine", "ArrowLeftFill", "ArrowLeftLine", "ArrowLeftLongFill", "ArrowLeftLongLine", "ArrowLeftRightFill", "ArrowLeftRightLine", "ArrowLeftSFill", "ArrowLeftSLine", "ArrowLeftUpBoxFill", "ArrowLeftUpBoxLine", "ArrowLeftUpFill", "ArrowLeftUpLine", "ArrowLeftUpLongFill", "ArrowLeftUpLongLine", "ArrowLeftWideFill", "ArrowLeftWideLine", "ArrowRightBoxFill", "ArrowRightBoxLine", "ArrowRightCircleFill", "ArrowRightCircleLine", "ArrowRightDoubleFill", "ArrowRightDoubleLine", "ArrowRightDownBoxFill", "ArrowRightDownBoxLine", "ArrowRightDownFill", "ArrowRightDownLine", "ArrowRightDownLongFill", "ArrowRightDownLongLine", "ArrowRightFill", "ArrowRightLine", "ArrowRightLongFill", "ArrowRightLongLine", "ArrowRightSFill", "ArrowRightSLine", "ArrowRightUpBoxFill", "ArrowRightUpBoxLine", "ArrowRightUpFill", "ArrowRightUpLine", "ArrowRightUpLongFill", "ArrowRightUpLongLine", "ArrowRightWideFill", "ArrowRightWideLine", "ArrowTurnBackFill", "ArrowTurnBackLine", "ArrowTurnForwardFill", "ArrowTurnForwardLine", "ArrowUpBoxFill", "ArrowUpBoxLine", "ArrowUpCircleFill", "ArrowUpCircleLine", "ArrowUpDoubleFill", "ArrowUpDoubleLine", "ArrowUpDownFill", "ArrowUpDownLine", "ArrowUpFill", "ArrowUpLine", "ArrowUpLongFill", "ArrowUpLongLine", "ArrowUpSFill", "ArrowUpSLine", "ArrowUpWideFill", "ArrowUpWideLine", "Artboard2Fill", "Artboard2Line", "ArtboardFill", "ArtboardLine", "ArticleFill", "ArticleLine", "AspectRatioFill", "AspectRatioLine", "Asterisk", "AtFill", "AtLine", "AtomFill", "AtomLine", "Attachment2", "AttachmentFill", "AttachmentLine", "AuctionFill", "AuctionLine", "AwardFill", "AwardLine", "BaiduFill", "BaiduLine", "BallPenFill", "BallPenLine", "BankCard2Fill", "BankCard2Line", "BankCardFill", "BankCardLine", "BankFill", "BankLine", "BarChart2Fill", "BarChart2Line", "BarChartBoxAiFill", "BarChartBoxAiLine", "BarChartBoxFill", "BarChartBoxLine", "BarChartFill", "BarChartGroupedFill", "BarChartGroupedLine", "BarChartHorizontalFill", "BarChartHorizontalLine", "BarChartLine", "BarcodeBoxFill", "BarcodeBoxLine", "BarcodeFill", "BarcodeLine", "BardFill", "BardLine", "BarricadeFill", "BarricadeLine", "BaseStationFill", "BaseStationLine", "BaseballFill", "BaseballLine", "BasketballFill", "BasketballLine", "Battery2ChargeFill", "Battery2ChargeLine", "Battery2Fill", "Battery2Line", "BatteryChargeFill", "BatteryChargeLine", "BatteryFill", "BatteryLine", "BatteryLowFill", "BatteryLowLine", "BatterySaverFill", "BatterySaverLine", "BatteryShareFill", "BatteryShareLine", "BearSmileFill", "BearSmileLine", "BeerFill", "BeerLine", "BehanceFill", "BehanceLine", "BellFill", "BellLine", "BikeFill", "BikeLine", "BilibiliFill", "BilibiliLine", "BillFill", "BillLine", "BilliardsFill", "BilliardsLine", "BinocularsFill", "BinocularsLine", "BitCoinFill", "BitCoinLine", "BlazeFill", "BlazeLine", "BlenderFill", "BlenderLine", "BloggerFill", "BloggerLine", "BlueskyFill", "BlueskyLine", "BluetoothConnectFill", "BluetoothConnectLine", "BluetoothFill", "BluetoothLine", "BlurOffFill", "BlurOffLine", "BnbFill", "BnbLine", "BodyScanFill", "BodyScanLine", "Bold", "Book2Fill", "Book2Line", "Book3Fill", "Book3Line", "BookAiFill", "BookAiLine", "BookFill", "BookLine", "BookMarkedFill", "BookMarkedLine", "BookOpenFill", "BookOpenLine", "BookReadFill", "BookReadLine", "BookShelfFill", "BookShelfLine", "BookletFill", "BookletLine", "Bookmark2Fill", "Bookmark2Line", "Bookmark3Fill", "Bookmark3Line", "BookmarkFill", "BookmarkLine", "BootstrapFill", "BootstrapLine", "BowlFill", "BowlLine", "Box1Fill", "Box1Line", "Box2Fill", "Box2Line", "Box3Fill", "Box3Line", "BoxingFill", "BoxingLine", "BracesFill", "BracesLine", "BracketsFill", "BracketsLine", "Brain2Fill", "Brain2Line", "Brain3Fill", "Brain3Line", "Brain4Fill", "Brain4Line", "BrainAi3Fill", "BrainAi3Line", "BrainFill", "BrainLine", "BreadFill", "BreadLine", "Briefcase2Fill", "Briefcase2Line", "Briefcase3Fill", "Briefcase3Line", "Briefcase4Fill", "Briefcase4Line", "Briefcase5Fill", "Briefcase5Line", "BriefcaseFill", "BriefcaseLine", "BringForward", "BringToFront", "BroadcastFill", "BroadcastLine", "Brush2Fill", "Brush2Line", "Brush3Fill", "Brush3Line", "Brush4Fill", "Brush4Line", "BrushAi3Fill", "BrushAi3Line", "BrushAiFill", "BrushAiLine", "BrushFill", "BrushLine", "BtcFill", "BtcLine", "BubbleChartFill", "BubbleChartLine", "Bug2Fill", "Bug2Line", "BugFill", "BugLine", "Building2Fill", "Building2Line", "Building3Fill", "Building3Line", "Building4Fill", "Building4Line", "BuildingFill", "BuildingLine", "Bus2Fill", "Bus2Line", "BusFill", "BusLine", "BusWifiFill", "BusWifiLine", "CactusFill", "CactusLine", "Cake2Fill", "Cake2Line", "Cake3Fill", "Cake3Line", "CakeFill", "CakeLine", "CalculatorFill", "CalculatorLine", "Calendar2Fill", "Calendar2Line", "CalendarCheckFill", "CalendarCheckLine", "CalendarCloseFill", "CalendarCloseLine", "CalendarEventFill", "CalendarEventLine", "CalendarFill", "CalendarLine", "CalendarScheduleFill", "CalendarScheduleLine", "CalendarTodoFill", "CalendarTodoLine", "CalendarView", "Camera2Fill", "Camera2Line", "Camera3Fill", "Camera3Line", "Camera4Fill", "Camera4Line", "CameraAi2Fill", "CameraAi2Line", "CameraAiFill", "CameraAiLine", "CameraFill", "CameraLensAiFill", "CameraLensAiLine", "CameraLensFill", "CameraLensLine", "CameraLine", "CameraOffFill", "CameraOffLine", "CameraSwitchFill", "CameraSwitchLine", "CandleFill", "CandleLine", "CapsuleFill", "CapsuleLine", "CarFill", "CarLine", "CarWashingFill", "CarWashingLine", "CaravanFill", "CaravanLine", "CarouselView", "CashFill", "CashLine", "CastFill", "CastLine", "CellphoneFill", "CellphoneLine", "CelsiusFill", "CelsiusLine", "CentosFill", "CentosLine", "Certificate2Fill", "Certificate2Line", "CertificateFill", "CertificateLine", "CharacterRecognitionFill", "CharacterRecognitionLine", "ChargingPile2Fill", "ChargingPile2Line", "ChargingPileFill", "ChargingPileLine", "Chat1Fill", "Chat1Line", "Chat2Fill", "Chat2Line", "Chat3Fill", "Chat3Line", "Chat4Fill", "Chat4Line", "ChatAi2Fill", "ChatAi2Line", "ChatAi3Fill", "ChatAi3Line", "ChatAi4Fill", "ChatAi4Line", "ChatAiFill", "ChatAiLine", "ChatCheckFill", "ChatCheckLine", "ChatDeleteFill", "ChatDeleteLine", "ChatDownloadFill", "ChatDownloadLine", "ChatFollowUpFill", "ChatFollowUpLine", "ChatForwardFill", "ChatForwardLine", "ChatHeartFill", "ChatHeartLine", "ChatHistoryFill", "ChatHistoryLine", "ChatNewFill", "ChatNewLine", "ChatOffFill", "ChatOffLine", "ChatPollFill", "ChatPollLine", "ChatPrivateFill", "ChatPrivateLine", "ChatQuoteFill", "ChatQuoteLine", "ChatSearchFill", "ChatSearchLine", "ChatSettingsFill", "ChatSettingsLine", "ChatSmile2Fill", "ChatSmile2Line", "ChatSmile3Fill", "ChatSmile3Line", "ChatSmileAi3Fill", "ChatSmileAi3Line", "ChatSmileAiFill", "ChatSmileAiLine", "ChatSmileFill", "ChatSmileLine", "ChatThreadFill", "ChatThreadLine", "ChatUnreadFill", "ChatUnreadLine", "ChatUploadFill", "ChatUploadLine", "ChatVoiceAiFill", "ChatVoiceAiLine", "ChatVoiceFill", "ChatVoiceLine", "CheckDoubleFill", "CheckDoubleLine", "CheckFill", "CheckLine", "CheckboxBlankCircleFill", "CheckboxBlankCircleLine", "CheckboxBlankFill", "CheckboxBlankLine", "CheckboxCircleFill", "CheckboxCircleLine", "CheckboxFill", "CheckboxIndeterminateFill", "CheckboxIndeterminateLine", "CheckboxLine", "CheckboxMultipleBlankFill", "CheckboxMultipleBlankLine", "CheckboxMultipleFill", "CheckboxMultipleLine", "ChessFill", "ChessLine", "ChinaRailwayFill", "ChinaRailwayLine", "ChromeFill", "ChromeLine", "CircleFill", "CircleLine", "ClapperboardAiFill", "ClapperboardAiLine", "ClapperboardFill", "ClapperboardLine", "ClaudeFill", "ClaudeLine", "ClipboardFill", "ClipboardLine", "Clockwise2Fill", "Clockwise2Line", "ClockwiseFill", "ClockwiseLine", "CloseCircleFill", "CloseCircleLine", "CloseFill", "CloseLargeFill", "CloseLargeLine", "CloseLine", "ClosedCaptioningAiFill", "ClosedCaptioningAiLine", "ClosedCaptioningFill", "ClosedCaptioningLine", "CloudFill", "CloudLine", "CloudOffFill", "CloudOffLine", "CloudWindyFill", "CloudWindyLine", "Cloudy2Fill", "Cloudy2Line", "CloudyFill", "CloudyLine", "CodeAiFill", "CodeAiLine", "CodeBlock", "CodeBoxFill", "CodeBoxLine", "CodeFill", "CodeLine", "CodeSFill", "CodeSLine", "CodeSSlashFill", "CodeSSlashLine", "CodeView", "CodepenFill", "CodepenLine", "CoinFill", "CoinLine", "CoinsFill", "CoinsLine", "CollageFill", "CollageLine", "CollapseDiagonal2Fill", "CollapseDiagonal2Line", "CollapseDiagonalFill", "CollapseDiagonalLine", "CollapseHorizontalFill", "CollapseHorizontalLine", "CollapseVerticalFill", "CollapseVerticalLine", "ColorFilterAiFill", "ColorFilterAiLine", "ColorFilterFill", "ColorFilterLine", "CommandFill", "CommandLine", "CommunityFill", "CommunityLine", "Compass2Fill", "Compass2Line", "Compass3Fill", "Compass3Line", "Compass4Fill", "Compass4Line", "CompassDiscoverFill", "CompassDiscoverLine", "CompassFill", "CompassLine", "Compasses2Fill", "Compasses2Line", "CompassesFill", "CompassesLine", "ComputerFill", "ComputerLine", "ContactsBook2Fill", "ContactsBook2Line", "ContactsBook3Fill", "ContactsBook3Line", "ContactsBookFill", "ContactsBookLine", "ContactsBookUploadFill", "ContactsBookUploadLine", "ContactsFill", "ContactsLine", "ContractFill", "ContractLeftFill", "ContractLeftLine", "ContractLeftRightFill", "ContractLeftRightLine", "ContractLine", "ContractRightFill", "ContractRightLine", "ContractUpDownFill", "ContractUpDownLine", "Contrast2Fill", "Contrast2Line", "ContrastDrop2Fill", "ContrastDrop2Line", "ContrastDropFill", "ContrastDropLine", "ContrastFill", "ContrastLine", "CookieFill", "CookieLine", "CopilotFill", "CopilotLine", "CopperCoinFill", "CopperCoinLine", "CopperDiamondFill", "CopperDiamondLine", "CopyleftFill", "CopyleftLine", "CopyrightFill", "CopyrightLine", "CoreosFill", "CoreosLine", "CornerDownLeftFill", "CornerDownLeftLine", "CornerDownRightFill", "CornerDownRightLine", "CornerLeftDownFill", "CornerLeftDownLine", "CornerLeftUpFill", "CornerLeftUpLine", "CornerRightDownFill", "CornerRightDownLine", "CornerRightUpFill", "CornerRightUpLine", "CornerUpLeftDoubleFill", "CornerUpLeftDoubleLine", "CornerUpLeftFill", "CornerUpLeftLine", "CornerUpRightDoubleFill", "CornerUpRightDoubleLine", "CornerUpRightFill", "CornerUpRightLine", "Coupon2Fill", "Coupon2Line", "Coupon3Fill", "Coupon3Line", "Coupon4Fill", "Coupon4Line", "Coupon5Fill", "Coupon5Line", "CouponFill", "CouponLine", "CpuFill", "CpuLine", "CreativeCommonsByFill", "CreativeCommonsByLine", "CreativeCommonsFill", "CreativeCommonsLine", "CreativeCommonsNcFill", "CreativeCommonsNcLine", "CreativeCommonsNdFill", "CreativeCommonsNdLine", "CreativeCommonsSaFill", "CreativeCommonsSaLine", "CreativeCommonsZeroFill", "CreativeCommonsZeroLine", "CriminalFill", "CriminalLine", "Crop2Fill", "Crop2Line", "CropFill", "CropLine", "CrossFill", "CrossLine", "Crosshair2Fill", "Crosshair2Line", "CrosshairFill", "CrosshairLine", "Css3Fill", "Css3Line", "CupFill", "CupLine", "CurrencyFill", "CurrencyLine", "CursorAiFill", "CursorAiLine", "CursorFill", "CursorHand", "CursorLine", "CustomSize", "CustomerService2Fill", "CustomerService2Line", "CustomerServiceFill", "CustomerServiceLine", "Dashboard2Fill", "Dashboard2Line", "Dashboard3Fill", "Dashboard3Line", "DashboardFill", "DashboardHorizontalFill", "DashboardHorizontalLine", "DashboardLine", "Database2Fill", "Database2Line", "DatabaseFill", "DatabaseLine", "DeepseekFill", "DeepseekLine", "DeleteBack2Fill", "DeleteBack2Line", "DeleteBackFill", "DeleteBackLine", "DeleteBin2Fill", "DeleteBin2Line", "DeleteBin3Fill", "DeleteBin3Line", "DeleteBin4Fill", "DeleteBin4Line", "DeleteBin5Fill", "DeleteBin5Line", "DeleteBin6Fill", "DeleteBin6Line", "DeleteBin7Fill", "DeleteBin7Line", "DeleteBinFill", "DeleteBinLine", "DeleteColumn", "DeleteRow", "DeviceFill", "DeviceLine", "DeviceRecoverFill", "DeviceRecoverLine", "DiamondFill", "DiamondLine", "DiamondRingFill", "DiamondRingLine", "Dice1Fill", "Dice1Line", "Dice2Fill", "Dice2Line", "Dice3Fill", "Dice3Line", "Dice4Fill", "Dice4Line", "Dice5Fill", "Dice5Line", "Dice6Fill", "Dice6Line", "DiceFill", "DiceLine", "DingdingFill", "DingdingLine", "DirectionFill", "DirectionLine", "DiscFill", "DiscLine", "DiscordFill", "DiscordLine", "DiscountPercentFill", "DiscountPercentLine", "DiscussFill", "DiscussLine", "DislikeFill", "DislikeLine", "DisqusFill", "DisqusLine", "DivideFill", "DivideLine", "DnaFill", "DnaLine", "DonutChartFill", "DonutChartLine", "DoorClosedFill", "DoorClosedLine", "DoorFill", "DoorLine", "DoorLockBoxFill", "DoorLockBoxLine", "DoorLockFill", "DoorLockLine", "DoorOpenFill", "DoorOpenLine", "DossierFill", "DossierLine", "DoubanFill", "DoubanLine", "DoubleQuotesL", "DoubleQuotesR", "Download2Fill", "Download2Line", "DownloadCloud2Fill", "DownloadCloud2Line", "DownloadCloudFill", "DownloadCloudLine", "DownloadFill", "DownloadLine", "DraftFill", "DraftLine", "DragDropFill", "DragDropLine", "DragMove2Fill", "DragMove2Line", "DragMoveFill", "DragMoveLine", "Draggable", "DrawFill", "DrawLine", "DribbbleFill", "DribbbleLine", "Drinks2Fill", "Drinks2Line", "DrinksFill", "DrinksLine", "DriveFill", "DriveLine", "DrizzleFill", "DrizzleLine", "DropFill", "DropLine", "DropboxFill", "DropboxLine", "DropdownList", "DropperFill", "DropperLine", "DualSim1Fill", "DualSim1Line", "DualSim2Fill", "DualSim2Line", "DvFill", "DvLine", "DvdAiFill", "DvdAiLine", "DvdFill", "DvdLine", "EBike2Fill", "EBike2Line", "EBikeFill", "EBikeLine", "EarthFill", "EarthLine", "EarthquakeFill", "EarthquakeLine", "EdgeFill", "EdgeLine", "EdgeNewFill", "EdgeNewLine", "Edit2Fill", "Edit2Line", "EditBoxFill", "EditBoxLine", "EditCircleFill", "EditCircleLine", "EditFill", "EditLine", "EjectFill", "EjectLine", "EmojiStickerFill", "EmojiStickerLine", "Emotion2Fill", "Emotion2Line", "EmotionAddFill", "EmotionAddLine", "EmotionFill", "EmotionHappyFill", "EmotionHappyLine", "EmotionLaughFill", "EmotionLaughLine", "EmotionLine", "EmotionNormalFill", "EmotionNormalLine", "EmotionSadFill", "EmotionSadLine", "EmotionUnhappyFill", "EmotionUnhappyLine", "EmpathizeFill", "EmpathizeLine", "Emphasis", "EmphasisCn", "EnglishInput", "EqualFill", "EqualLine", "Equalizer2Fill", "Equalizer2Line", "Equalizer3Fill", "Equalizer3Line", "EqualizerFill", "EqualizerLine", "EraserFill", "EraserLine", "ErrorWarningFill", "ErrorWarningLine", "EthFill", "EthLine", "EvernoteFill", "EvernoteLine", "Exchange2Fill", "Exchange2Line", "ExchangeBoxFill", "ExchangeBoxLine", "ExchangeCnyFill", "ExchangeCnyLine", "ExchangeDollarFill", "ExchangeDollarLine", "ExchangeFill", "ExchangeFundsFill", "ExchangeFundsLine", "ExchangeLine", "ExpandDiagonal2Fill", "ExpandDiagonal2Line", "ExpandDiagonalFill", "ExpandDiagonalLine", "ExpandDiagonalS2Fill", "ExpandDiagonalS2Line", "ExpandDiagonalSFill", "ExpandDiagonalSLine", "ExpandHeightFill", "ExpandHeightLine", "ExpandHorizontalFill", "ExpandHorizontalLine", "ExpandHorizontalSFill", "ExpandHorizontalSLine", "ExpandLeftFill", "ExpandLeftLine", "ExpandLeftRightFill", "ExpandLeftRightLine", "ExpandRightFill", "ExpandRightLine", "ExpandUpDownFill", "ExpandUpDownLine", "ExpandVerticalFill", "ExpandVerticalLine", "ExpandVerticalSFill", "ExpandVerticalSLine", "ExpandWidthFill", "ExpandWidthLine", "ExportFill", "ExportLine", "ExternalLinkFill", "ExternalLinkLine", "Eye2Fill", "Eye2Line", "EyeCloseFill", "EyeCloseLine", "EyeFill", "EyeLine", "EyeOffFill", "EyeOffLine", "FacebookBoxFill", "FacebookBoxLine", "FacebookCircleFill", "FacebookCircleLine", "FacebookFill", "FacebookLine", "FahrenheitFill", "FahrenheitLine", "FediverseFill", "FediverseLine", "FeedbackFill", "FeedbackLine", "FigmaFill", "FigmaLine", "File2Fill", "File2Line", "File3Fill", "File3Line", "File4Fill", "File4Line", "FileAddFill", "FileAddLine", "FileAi2Fill", "FileAi2Line", "FileAiFill", "FileAiLine", "FileChart2Fill", "FileChart2Line", "FileChartFill", "FileChartLine", "FileCheckFill", "FileCheckLine", "FileCloseFill", "FileCloseLine", "FileCloudFill", "FileCloudLine", "FileCodeFill", "FileCodeLine", "FileCopy2Fill", "FileCopy2Line", "FileCopyFill", "FileCopyLine", "FileDamageFill", "FileDamageLine", "FileDownloadFill", "FileDownloadLine", "FileEditFill", "FileEditLine", "FileExcel2Fill", "FileExcel2Line", "FileExcelFill", "FileExcelLine", "FileFill", "FileForbidFill", "FileForbidLine", "FileGifFill", "FileGifLine", "FileHistoryFill", "FileHistoryLine", "FileHwpFill", "FileHwpLine", "FileImageFill", "FileImageLine", "FileInfoFill", "FileInfoLine", "FileLine", "FileList2Fill", "FileList2Line", "FileList3Fill", "FileList3Line", "FileListFill", "FileListLine", "FileLockFill", "FileLockLine", "FileMarkedFill", "FileMarkedLine", "FileMusicFill", "FileMusicLine", "FilePaper2Fill", "FilePaper2Line", "FilePaperFill", "FilePaperLine", "FilePdf2Fill", "FilePdf2Line", "FilePdfFill", "FilePdfLine", "FilePpt2Fill", "FilePpt2Line", "FilePptFill", "FilePptLine", "FileReduceFill", "FileReduceLine", "FileScanFill", "FileScanLine", "FileSearchFill", "FileSearchLine", "FileSettingsFill", "FileSettingsLine", "FileShield2Fill", "FileShield2Line", "FileShieldFill", "FileShieldLine", "FileShredFill", "FileShredLine", "FileTextFill", "FileTextLine", "FileTransferFill", "FileTransferLine", "FileUnknowFill", "FileUnknowLine", "FileUploadFill", "FileUploadLine", "FileUserFill", "FileUserLine", "FileVideoFill", "FileVideoLine", "FileWarningFill", "FileWarningLine", "FileWord2Fill", "FileWord2Line", "FileWordFill", "FileWordLine", "FileZipFill", "FileZipLine", "FilmAiFill", "FilmAiLine", "FilmFill", "FilmLine", "Filter2Fill", "Filter2Line", "Filter3Fill", "Filter3Line", "FilterFill", "FilterLine", "FilterOffFill", "FilterOffLine", "FindReplaceFill", "FindReplaceLine", "FinderFill", "FinderLine", "Fingerprint2Fill", "Fingerprint2Line", "FingerprintFill", "FingerprintLine", "FireFill", "FireLine", "FirebaseFill", "FirebaseLine", "FirefoxBrowserFill", "FirefoxBrowserLine", "FirefoxFill", "FirefoxLine", "FirstAidKitFill", "FirstAidKitLine", "FiverrFill", "FiverrLine", "Flag2Fill", "Flag2Line", "FlagFill", "FlagLine", "FlagOffFill", "FlagOffLine", "FlashlightFill", "FlashlightLine", "FlaskFill", "FlaskLine", "FlickrFill", "FlickrLine", "FlightLandFill", "FlightLandLine", "FlightTakeoffFill", "FlightTakeoffLine", "FlipHorizontal2Fill", "FlipHorizontal2Line", "FlipHorizontalFill", "FlipHorizontalLine", "FlipVertical2Fill", "FlipVertical2Line", "FlipVerticalFill", "FlipVerticalLine", "FloodFill", "FloodLine", "FlowChart", "FlowerFill", "FlowerLine", "FlutterFill", "FlutterLine", "Focus2Fill", "Focus2Line", "Focus3Fill", "Focus3Line", "FocusFill", "FocusLine", "FocusMode", "FoggyFill", "FoggyLine", "Folder2Fill", "Folder2Line", "Folder3Fill", "Folder3Line", "Folder4Fill", "Folder4Line", "Folder5Fill", "Folder5Line", "Folder6Fill", "Folder6Line", "FolderAddFill", "FolderAddLine", "FolderChart2Fill", "FolderChart2Line", "FolderChartFill", "FolderChartLine", "FolderCheckFill", "FolderCheckLine", "FolderCloseFill", "FolderCloseLine", "FolderCloudFill", "FolderCloudLine", "FolderDownloadFill", "FolderDownloadLine", "FolderFill", "FolderForbidFill", "FolderForbidLine", "FolderHistoryFill", "FolderHistoryLine", "FolderImageFill", "FolderImageLine", "FolderInfoFill", "FolderInfoLine", "FolderKeyholeFill", "FolderKeyholeLine", "FolderLine", "FolderLockFill", "FolderLockLine", "FolderMusicFill", "FolderMusicLine", "FolderOpenFill", "FolderOpenLine", "FolderReceivedFill", "FolderReceivedLine", "FolderReduceFill", "FolderReduceLine", "FolderSettingsFill", "FolderSettingsLine", "FolderSharedFill", "FolderSharedLine", "FolderShield2Fill", "FolderShield2Line", "FolderShieldFill", "FolderShieldLine", "FolderTransferFill", "FolderTransferLine", "FolderUnknowFill", "FolderUnknowLine", "FolderUploadFill", "FolderUploadLine", "FolderUserFill", "FolderUserLine", "FolderVideoFill", "FolderVideoLine", "FolderWarningFill", "FolderWarningLine", "FolderZipFill", "FolderZipLine", "FoldersFill", "FoldersLine", "FontColor", "FontFamily", "FontMono", "FontSansSerif", "FontSerif", "FontSize", "FontSize2", "FontSizeAi", "FootballFill", "FootballLine", "FootprintFill", "FootprintLine", "Forbid2Fill", "Forbid2Line", "ForbidFill", "ForbidLine", "FormatClear", "Formula", "Forward10Fill", "Forward10Line", "Forward15Fill", "Forward15Line", "Forward30Fill", "Forward30Line", "Forward5Fill", "Forward5Line", "ForwardEndFill", "ForwardEndLine", "ForwardEndMiniFill", "ForwardEndMiniLine", "FridgeFill", "FridgeLine", "FriendicaFill", "FriendicaLine", "FullscreenExitFill", "FullscreenExitLine", "FullscreenFill", "FullscreenLine", "FunctionAddFill", "FunctionAddLine", "FunctionAiFill", "FunctionAiLine", "FunctionFill", "FunctionLine", "Functions", "FundsBoxFill", "FundsBoxLine", "FundsFill", "FundsLine", "GalleryFill", "GalleryLine", "GalleryUploadFill", "GalleryUploadLine", "GalleryView", "GalleryView2", "Game2Fill", "Game2Line", "GameFill", "GameLine", "GamepadFill", "GamepadLine", "GasStationFill", "GasStationLine", "GatsbyFill", "GatsbyLine", "GeminiFill", "GeminiLine", "GenderlessFill", "GenderlessLine", "Ghost2Fill", "Ghost2Line", "Ghost3Fill", "Ghost3Line", "Ghost4Fill", "Ghost4Line", "GhostFill", "GhostLine", "GhostSmileFill", "GhostSmileLine", "Gift2Fill", "Gift2Line", "GiftFill", "GiftLine", "GitBranchFill", "GitBranchLine", "GitClosePullRequestFill", "GitClosePullRequestLine", "GitCommitFill", "GitCommitLine", "GitForkFill", "GitForkLine", "GitMergeFill", "GitMergeLine", "GitPrDraftFill", "GitPrDraftLine", "GitPullRequestFill", "GitPullRequestLine", "GitRepositoryCommitsFill", "GitRepositoryCommitsLine", "GitRepositoryFill", "GitRepositoryLine", "GitRepositoryPrivateFill", "GitRepositoryPrivateLine", "GitbookFill", "GitbookLine", "GiteeFill", "GiteeLine", "GithubFill", "GithubLine", "GitlabFill", "GitlabLine", "Glasses2Fill", "Glasses2Line", "GlassesFill", "GlassesLine", "GlobalFill", "GlobalLine", "GlobalOffFill", "GlobalOffLine", "GlobeFill", "GlobeLine", "Goblet2Fill", "Goblet2Line", "GobletBrokenFill", "GobletBrokenLine", "GobletFill", "GobletLine", "GogglesFill", "GogglesLine", "GolfBallFill", "GolfBallLine", "GoogleFill", "GoogleLine", "GooglePlayFill", "GooglePlayLine", "GovernmentFill", "GovernmentLine", "GpsFill", "GpsLine", "GradienterFill", "GradienterLine", "GraduationCapFill", "GraduationCapLine", "GridFill", "GridLine", "GrokAiFill", "GrokAiLine", "Group2Fill", "Group2Line", "Group3Fill", "Group3Line", "GroupFill", "GroupLine", "GuideFill", "GuideLine", "H1", "H2", "H3", "H4", "H5", "H6", "HailFill", "HailLine", "HammerFill", "HammerLine", "Hand", "Hand2", "HandCoinFill", "HandCoinLine", "HandHeartFill", "HandHeartLine", "HandSanitizerFill", "HandSanitizerLine", "HandbagFill", "HandbagLine", "HardDrive2Fill", "HardDrive2Line", "HardDrive3Fill", "HardDrive3Line", "HardDriveFill", "HardDriveLine", "Hashtag", "Haze2Fill", "Haze2Line", "HazeFill", "HazeLine", "HdFill", "HdLine", "Heading", "HeadphoneFill", "HeadphoneLine", "HealthBookFill", "HealthBookLine", "Heart2Fill", "Heart2Line", "Heart3Fill", "Heart3Line", "HeartAdd2Fill", "HeartAdd2Line", "HeartAddFill", "HeartAddLine", "HeartFill", "HeartLine", "HeartPulseFill", "HeartPulseLine", "HeartsFill", "HeartsLine", "HeavyShowersFill", "HeavyShowersLine", "HexagonFill", "HexagonLine", "HistoryFill", "HistoryLine", "Home2Fill", "Home2Line", "Home3Fill", "Home3Line", "Home4Fill", "Home4Line", "Home5Fill", "Home5Line", "Home6Fill", "Home6Line", "Home7Fill", "Home7Line", "Home8Fill", "Home8Line", "Home9Fill", "Home9Line", "HomeFill", "HomeGearFill", "HomeGearLine", "HomeHeartFill", "HomeHeartLine", "HomeLine", "HomeOfficeFill", "HomeOfficeLine", "HomeSmile2Fill", "HomeSmile2Line", "HomeSmileFill", "HomeSmileLine", "HomeWifiFill", "HomeWifiLine", "HonorOfKingsFill", "HonorOfKingsLine", "HonourFill", "HonourLine", "HospitalFill", "HospitalLine", "HotelBedFill", "HotelBedLine", "HotelFill", "HotelLine", "HotspotFill", "HotspotLine", "Hourglass2Fill", "Hourglass2Line", "HourglassFill", "HourglassLine", "HqFill", "HqLine", "Html5Fill", "Html5Line", "IdCardFill", "IdCardLine", "IeFill", "IeLine", "Image2Fill", "Image2Line", "ImageAddFill", "ImageAddLine", "ImageAiFill", "ImageAiLine", "ImageCircleAiFill", "ImageCircleAiLine", "ImageCircleFill", "ImageCircleLine", "ImageDownloadFill", "ImageDownloadLine", "ImageEditFill", "ImageEditLine", "ImageFill", "ImageLine", "ImageUploadFill", "ImageUploadLine", "ImportFill", "ImportLine", "Inbox2Fill", "Inbox2Line", "InboxArchiveFill", "InboxArchiveLine", "InboxFill", "InboxLine", "InboxUnarchiveFill", "InboxUnarchiveLine", "IncreaseDecreaseFill", "IncreaseDecreaseLine", "IndentDecrease", "IndentIncrease", "IndeterminateCircleFill", "IndeterminateCircleLine", "InfinityFill", "InfinityLine", "InfoCardFill", "InfoCardLine", "InfoI", "Information2Fill", "Information2Line", "InformationFill", "InformationLine", "InformationOffFill", "InformationOffLine", "InfraredThermometerFill", "InfraredThermometerLine", "InkBottleFill", "InkBottleLine", "InputCursorMove", "InputField", "InputMethodFill", "InputMethodLine", "InsertColumnLeft", "InsertColumnRight", "InsertRowBottom", "InsertRowTop", "InstagramFill", "InstagramLine", "InstallFill", "InstallLine", "InstanceFill", "InstanceLine", "InvisionFill", "InvisionLine", "IssuesFill", "IssuesLine", "IssuesReopenFill", "IssuesReopenLine", "Italic", "JavaFill", "JavaLine", "JavascriptFill", "JavascriptLine", "JewelryFill", "JewelryLine", "KakaoTalkFill", "KakaoTalkLine", "KanbanView", "KanbanView2", "Key2Fill", "Key2Line", "KeyFill", "KeyLine", "KeyboardBoxFill", "KeyboardBoxLine", "KeyboardFill", "KeyboardLine", "KeynoteFill", "KeynoteLine", "KickFill", "KickLine", "KnifeBloodFill", "KnifeBloodLine", "KnifeFill", "KnifeLine", "LandscapeAiFill", "LandscapeAiLine", "LandscapeFill", "LandscapeLine", "Layout2Fill", "Layout2Line", "Layout3Fill", "Layout3Line", "Layout4Fill", "Layout4Line", "Layout5Fill", "Layout5Line", "Layout6Fill", "Layout6Line", "LayoutBottom2Fill", "LayoutBottom2Line", "LayoutBottomFill", "LayoutBottomLine", "LayoutColumnFill", "LayoutColumnLine", "LayoutFill", "LayoutGrid2Fill", "LayoutGrid2Line", "LayoutGridFill", "LayoutGridLine", "LayoutHorizontalFill", "LayoutHorizontalLine", "LayoutLeft2Fill", "LayoutLeft2Line", "LayoutLeftFill", "LayoutLeftLine", "LayoutLine", "LayoutMasonryFill", "LayoutMasonryLine", "LayoutRight2Fill", "LayoutRight2Line", "LayoutRightFill", "LayoutRightLine", "LayoutRowFill", "LayoutRowLine", "LayoutTop2Fill", "LayoutTop2Line", "LayoutTopFill", "LayoutTopLine", "LayoutVerticalFill", "LayoutVerticalLine", "LeafFill", "LeafLine", "LetterSpacing2", "LifebuoyFill", "LifebuoyLine", "LightbulbAiFill", "LightbulbAiLine", "LightbulbFill", "LightbulbFlashFill", "LightbulbFlashLine", "LightbulbLine", "LineChartFill", "LineChartLine", "LineFill", "LineHeight", "LineHeight2", "LineLine", "Link", "LinkM", "LinkUnlink", "LinkUnlinkM", "LinkedinBoxFill", "LinkedinBoxLine", "LinkedinFill", "LinkedinLine", "LinksFill", "LinksLine", "ListCheck", "ListCheck2", "ListCheck3", "ListIndefinite", "ListOrdered", "ListOrdered2", "ListRadio", "ListSettingsFill", "ListSettingsLine", "ListUnordered", "ListView", "LiveFill", "LiveLine", "Loader2Fill", "Loader2Line", "Loader3Fill", "Loader3Line", "Loader4Fill", "Loader4Line", "Loader5Fill", "Loader5Line", "LoaderFill", "LoaderLine", "Lock2Fill", "Lock2Line", "LockFill", "LockLine", "LockPasswordFill", "LockPasswordLine", "LockStarFill", "LockStarLine", "LockUnlockFill", "LockUnlockLine", "LoginBoxFill", "LoginBoxLine", "LoginCircleFill", "LoginCircleLine", "LogoutBoxFill", "LogoutBoxLine", "LogoutBoxRFill", "LogoutBoxRLine", "LogoutCircleFill", "LogoutCircleLine", "LogoutCircleRFill", "LogoutCircleRLine", "LoopLeftAiFill", "LoopLeftAiLine", "LoopLeftFill", "LoopLeftLine", "LoopRightAiFill", "LoopRightAiLine", "LoopRightFill", "LoopRightLine", "LuggageCartFill", "LuggageCartLine", "LuggageDepositFill", "LuggageDepositLine", "LungsFill", "LungsLine", "MacFill", "MacLine", "MacbookFill", "MacbookLine", "MagicFill", "MagicLine", "MailAddFill", "MailAddLine", "MailAiFill", "MailAiLine", "MailCheckFill", "MailCheckLine", "MailCloseFill", "MailCloseLine", "MailDownloadFill", "MailDownloadLine", "MailFill", "MailForbidFill", "MailForbidLine", "MailLine", "MailLockFill", "MailLockLine", "MailOpenFill", "MailOpenLine", "MailSendFill", "MailSendLine", "MailSettingsFill", "MailSettingsLine", "MailStarFill", "MailStarLine", "MailUnreadFill", "MailUnreadLine", "MailVolumeFill", "MailVolumeLine", "Map2Fill", "Map2Line", "MapFill", "MapLine", "MapPin2Fill", "MapPin2Line", "MapPin3Fill", "MapPin3Line", "MapPin4Fill", "MapPin4Line", "MapPin5Fill", "MapPin5Line", "MapPinAddFill", "MapPinAddLine", "MapPinFill", "MapPinLine", "MapPinRangeFill", "MapPinRangeLine", "MapPinTimeFill", "MapPinTimeLine", "MapPinUserFill", "MapPinUserLine", "MarkPenFill", "MarkPenLine", "MarkdownFill", "MarkdownLine", "MarkupFill", "MarkupLine", "MastercardFill", "MastercardLine", "MastodonFill", "MastodonLine", "Medal2Fill", "Medal2Line", "MedalFill", "MedalLine", "MedicineBottleFill", "MedicineBottleLine", "MediumFill", "MediumLine", "Megaphone2Fill", "Megaphone2Line", "MegaphoneFill", "MegaphoneLine", "MemoriesFill", "MemoriesLine", "MenFill", "MenLine", "MentalHealthFill", "MentalHealthLine", "Menu2Fill", "Menu2Line", "Menu3Fill", "Menu3Line", "Menu4Fill", "Menu4Line", "Menu5Fill", "Menu5Line", "MenuAddFill", "MenuAddLine", "MenuFill", "MenuFold2Fill", "MenuFold2Line", "MenuFold3Fill", "MenuFold3Line", "MenuFold4Fill", "MenuFold4Line", "MenuFoldFill", "MenuFoldLine", "MenuLine", "MenuSearchFill", "MenuSearchLine", "MenuUnfold2Fill", "MenuUnfold2Line", "MenuUnfold3Fill", "MenuUnfold3Line", "MenuUnfold4Fill", "MenuUnfold4Line", "MenuUnfoldFill", "MenuUnfoldLine", "MergeCellsHorizontal", "MergeCellsVertical", "Message2Fill", "Message2Line", "Message3Fill", "Message3Line", "MessageAi3Fill", "MessageAi3Line", "MessageFill", "MessageLine", "MessengerFill", "MessengerLine", "MetaFill", "MetaLine", "MeteorFill", "MeteorLine", "Mic2AiFill", "Mic2AiLine", "Mic2Fill", "Mic2Line", "MicAiFill", "MicAiLine", "MicFill", "MicLine", "MicOffFill", "MicOffLine", "MickeyFill", "MickeyLine", "MicroscopeFill", "MicroscopeLine", "MicrosoftCopilotFill", "MicrosoftCopilotLine", "MicrosoftFill", "MicrosoftLine", "MicrosoftLoopFill", "MicrosoftLoopLine", "MindMap", "MiniProgramFill", "MiniProgramLine", "MistFill", "MistLine", "MixtralFill", "MixtralLine", "MobileDownloadFill", "MobileDownloadLine", "MoneyCnyBoxFill", "MoneyCnyBoxLine", "MoneyCnyCircleFill", "MoneyCnyCircleLine", "MoneyDollarBoxFill", "MoneyDollarBoxLine", "MoneyDollarCircleFill", "MoneyDollarCircleLine", "MoneyEuroBoxFill", "MoneyEuroBoxLine", "MoneyEuroCircleFill", "MoneyEuroCircleLine", "MoneyPoundBoxFill", "MoneyPoundBoxLine", "MoneyPoundCircleFill", "MoneyPoundCircleLine", "MoneyRupeeCircleFill", "MoneyRupeeCircleLine", "MoonClearFill", "MoonClearLine", "MoonCloudyFill", "MoonCloudyLine", "MoonFill", "MoonFoggyFill", "MoonFoggyLine", "MoonLine", "More2Fill", "More2Line", "MoreFill", "MoreLine", "MosaicFill", "MosaicLine", "MotorbikeFill", "MotorbikeLine", "MouseFill", "MouseLine", "Movie2AiFill", "Movie2AiLine", "Movie2Fill", "Movie2Line", "MovieAiFill", "MovieAiLine", "MovieFill", "MovieLine", "MultiImageFill", "MultiImageLine", "Music2Fill", "Music2Line", "MusicAiFill", "MusicAiLine", "MusicFill", "MusicLine", "MvAiFill", "MvAiLine", "MvFill", "MvLine", "NavigationFill", "NavigationLine", "NeteaseCloudMusicFill", "NeteaseCloudMusicLine", "NetflixFill", "NetflixLine", "NetworkErrorFill", "NetworkErrorLine", "NetworkFill", "NetworkLine", "NetworkOffFill", "NetworkOffLine", "NewsFill", "NewsLine", "NewspaperFill", "NewspaperLine", "NextjsFill", "NextjsLine", "NftFill", "NftLine", "NoCreditCardFill", "NoCreditCardLine", "NodeTree", "NodejsFill", "NodejsLine", "Notification2Fill", "Notification2Line", "Notification3Fill", "Notification3Line", "Notification4Fill", "Notification4Line", "NotificationBadgeFill", "NotificationBadgeLine", "NotificationFill", "NotificationLine", "NotificationOffFill", "NotificationOffLine", "NotificationSnoozeFill", "NotificationSnoozeLine", "NotionFill", "NotionLine", "NpmjsFill", "NpmjsLine", "Number0", "Number1", "Number2", "Number3", "Number4", "Number5", "Number6", "Number7", "Number8", "Number9", "NumbersFill", "NumbersLine", "NurseFill", "NurseLine", "OctagonFill", "OctagonLine", "OilFill", "OilLine", "Omega", "OpenArmFill", "OpenArmLine", "OpenSourceFill", "OpenSourceLine", "OpenaiFill", "OpenaiLine", "OpenbaseFill", "OpenbaseLine", "OperaFill", "OperaLine", "OrderPlayFill", "OrderPlayLine", "OrganizationChart", "Outlet2Fill", "Outlet2Line", "OutletFill", "OutletLine", "Overline", "P2pFill", "P2pLine", "PageSeparator", "PagesFill", "PagesLine", "PaintBrushFill", "PaintBrushLine", "PaintFill", "PaintLine", "PaintingAiFill", "PaintingAiLine", "PaintingFill", "PaintingLine", "PaletteFill", "PaletteLine", "PantoneFill", "PantoneLine", "Paragraph", "ParentFill", "ParentLine", "ParenthesesFill", "ParenthesesLine", "ParkingBoxFill", "ParkingBoxLine", "ParkingFill", "ParkingLine", "PassExpiredFill", "PassExpiredLine", "PassPendingFill", "PassPendingLine", "PassValidFill", "PassValidLine", "PassportFill", "PassportLine", "PatreonFill", "PatreonLine", "PauseCircleFill", "PauseCircleLine", "PauseFill", "PauseLargeFill", "PauseLargeLine", "PauseLine", "PauseMiniFill", "PauseMiniLine", "PaypalFill", "PaypalLine", "PenNibFill", "PenNibLine", "PencilAi2Fill", "PencilAi2Line", "PencilAiFill", "PencilAiLine", "PencilFill", "PencilLine", "PencilRuler2Fill", "PencilRuler2Line", "PencilRulerFill", "PencilRulerLine", "PentagonFill", "PentagonLine", "PercentFill", "PercentLine", "PerplexityFill", "PerplexityLine", "PhoneCameraFill", "PhoneCameraLine", "PhoneFill", "PhoneFindFill", "PhoneFindLine", "PhoneLine", "PhoneLockFill", "PhoneLockLine", "PhpFill", "PhpLine", "PianoFill", "PianoGrandFill", "PianoGrandLine", "PianoLine", "PictureInPicture2Fill", "PictureInPicture2Line", "PictureInPictureExitFill", "PictureInPictureExitLine", "PictureInPictureFill", "PictureInPictureLine", "PieChart2Fill", "PieChart2Line", "PieChartBoxFill", "PieChartBoxLine", "PieChartFill", "PieChartLine", "PinDistanceFill", "PinDistanceLine", "PingPongFill", "PingPongLine", "PinterestFill", "PinterestLine", "PinyinInput", "PixFill", "PixLine", "PixelfedFill", "PixelfedLine", "PlaneFill", "PlaneLine", "PlanetFill", "PlanetLine", "PlantFill", "PlantLine", "PlayCircleFill", "PlayCircleLine", "PlayFill", "PlayLargeFill", "PlayLargeLine", "PlayLine", "PlayList2Fill", "PlayList2Line", "PlayListAddFill", "PlayListAddLine", "PlayListFill", "PlayListLine", "PlayMiniFill", "PlayMiniLine", "PlayReverseFill", "PlayReverseLargeFill", "PlayReverseLargeLine", "PlayReverseLine", "PlayReverseMiniFill", "PlayReverseMiniLine", "PlaystationFill", "PlaystationLine", "Plug2Fill", "Plug2Line", "Plug3Fill", "Plug3Line", "PlugFill", "PlugLine", "PokerClubsFill", "PokerClubsLine", "PokerDiamondsFill", "PokerDiamondsLine", "PokerHeartsFill", "PokerHeartsLine", "PokerSpadesFill", "PokerSpadesLine", "Polaroid2Fill", "Polaroid2Line", "PolaroidFill", "PolaroidLine", "PoliceBadgeFill", "PoliceBadgeLine", "PoliceCarFill", "PoliceCarLine", "PresentationFill", "PresentationLine", "PriceTag2Fill", "PriceTag2Line", "PriceTag3Fill", "PriceTag3Line", "PriceTagFill", "PriceTagLine", "PrinterCloudFill", "PrinterCloudLine", "PrinterFill", "PrinterLine", "ProductHuntFill", "ProductHuntLine", "ProfileFill", "ProfileLine", "Progress1Fill", "Progress1Line", "Progress2Fill", "Progress2Line", "Progress3Fill", "Progress3Line", "Progress4Fill", "Progress4Line", "Progress5Fill", "Progress5Line", "Progress6Fill", "Progress6Line", "Progress7Fill", "Progress7Line", "Progress8Fill", "Progress8Line", "Prohibited2Fill", "Prohibited2Line", "ProhibitedFill", "ProhibitedLine", "Projector2Fill", "Projector2Line", "ProjectorFill", "ProjectorLine", "PsychotherapyFill", "PsychotherapyLine", "PulseAiFill", "PulseAiLine", "PulseFill", "PulseLine", "Pushpin2Fill", "Pushpin2Line", "PushpinFill", "PushpinLine", "Puzzle2Fill", "Puzzle2Line", "PuzzleFill", "PuzzleLine", "QqFill", "QqLine", "QrCodeFill", "QrCodeLine", "QrScan2Fill", "QrScan2Line", "QrScanAiFill", "QrScanAiLine", "QrScanFill", "QrScanLine", "QuestionAnswerFill", "QuestionAnswerLine", "QuestionFill", "QuestionLine", "QuestionMark", "QuestionnaireFill", "QuestionnaireLine", "QuillPenAiFill", "QuillPenAiLine", "QuillPenFill", "QuillPenLine", "QuoteText", "QwenAiFill", "QwenAiLine", "RadarFill", "RadarLine", "Radio2Fill", "Radio2Line", "RadioButtonFill", "RadioButtonLine", "RadioFill", "RadioLine", "RainbowFill", "RainbowLine", "RainyFill", "RainyLine", "Ram2Fill", "Ram2Line", "RamFill", "RamLine", "ReactjsFill", "ReactjsLine", "ReceiptFill", "ReceiptLine", "RecordCircleFill", "RecordCircleLine", "RecordMailFill", "RecordMailLine", "RectangleFill", "RectangleLine", "RecycleFill", "RecycleLine", "RedPacketFill", "RedPacketLine", "Reddit2Fill", "Reddit2Line", "RedditFill", "RedditLine", "RefreshFill", "RefreshLine", "Refund2Fill", "Refund2Line", "RefundFill", "RefundLine", "RegisteredFill", "RegisteredLine", "RemixFill", "RemixLine", "RemixRunFill", "RemixRunLine", "RemixiconFill", "RemixiconLine", "RemoteControl2Fill", "RemoteControl2Line", "RemoteControlFill", "RemoteControlLine", "Repeat2Fill", "Repeat2Line", "RepeatFill", "RepeatLine", "RepeatOneFill", "RepeatOneLine", "Replay10Fill", "Replay10Line", "Replay15Fill", "Replay15Line", "Replay30Fill", "Replay30Line", "Replay5Fill", "Replay5Line", "ReplyAllFill", "ReplyAllLine", "ReplyFill", "ReplyLine", "ReservedFill", "ReservedLine", "ResetLeftFill", "ResetLeftLine", "ResetRightFill", "ResetRightLine", "RestTimeFill", "RestTimeLine", "RestartFill", "RestartLine", "Restaurant2Fill", "Restaurant2Line", "RestaurantFill", "RestaurantLine", "RewindFill", "RewindLine", "RewindMiniFill", "RewindMiniLine", "RewindStartFill", "RewindStartLine", "RewindStartMiniFill", "RewindStartMiniLine", "RfidFill", "RfidLine", "RhythmFill", "RhythmLine", "RidingFill", "RidingLine", "RoadMapFill", "RoadMapLine", "RoadsterFill", "RoadsterLine", "Robot2Fill", "Robot2Line", "Robot3Fill", "Robot3Line", "RobotFill", "RobotLine", "Rocket2Fill", "Rocket2Line", "RocketFill", "RocketLine", "RotateLockFill", "RotateLockLine", "RoundedCorner", "RouteFill", "RouteLine", "RouterFill", "RouterLine", "RssFill", "RssLine", "Ruler2Fill", "Ruler2Line", "RulerFill", "RulerLine", "RunFill", "RunLine", "SafariFill", "SafariLine", "Safe2Fill", "Safe2Line", "Safe3Fill", "Safe3Line", "SafeFill", "SafeLine", "SailboatFill", "SailboatLine", "Save2Fill", "Save2Line", "Save3Fill", "Save3Line", "SaveFill", "SaveLine", "Scales2Fill", "Scales2Line", "Scales3Fill", "Scales3Line", "ScalesFill", "ScalesLine", "Scan2Fill", "Scan2Line", "ScanFill", "ScanLine", "SchoolFill", "SchoolLine", "Scissors2Fill", "Scissors2Line", "ScissorsCutFill", "ScissorsCutLine", "ScissorsFill", "ScissorsLine", "Screenshot2Fill", "Screenshot2Line", "ScreenshotFill", "ScreenshotLine", "ScrollToBottomFill", "ScrollToBottomLine", "SdCardFill", "SdCardLine", "SdCardMiniFill", "SdCardMiniLine", "Search2Fill", "Search2Line", "SearchAi2Fill", "SearchAi2Line", "SearchAi3Fill", "SearchAi3Line", "SearchAi4Fill", "SearchAi4Line", "SearchAiFill", "SearchAiLine", "SearchEyeFill", "SearchEyeLine", "SearchFill", "SearchLine", "SecurePaymentFill", "SecurePaymentLine", "SeedlingFill", "SeedlingLine", "SendBackward", "SendInsFill", "SendInsLine", "SendPlane2Fill", "SendPlane2Line", "SendPlaneFill", "SendPlaneLine", "SendToBack", "SensorFill", "SensorLine", "SeoFill", "SeoLine", "Separator", "ServerFill", "ServerLine", "ServiceBellFill", "ServiceBellLine", "ServiceFill", "ServiceLine", "Settings2Fill", "Settings2Line", "Settings3Fill", "Settings3Line", "Settings4Fill", "Settings4Line", "Settings5Fill", "Settings5Line", "Settings6Fill", "Settings6Line", "SettingsFill", "SettingsLine", "ShadowFill", "ShadowLine", "ShakeHandsFill", "ShakeHandsLine", "Shape2Fill", "Shape2Line", "ShapeFill", "ShapeLine", "ShapesFill", "ShapesLine", "Share2Fill", "Share2Line", "ShareBoxFill", "ShareBoxLine", "ShareCircleFill", "ShareCircleLine", "ShareFill", "ShareForward2Fill", "ShareForward2Line", "ShareForwardBoxFill", "ShareForwardBoxLine", "ShareForwardFill", "ShareForwardLine", "ShareLine", "ShieldCheckFill", "ShieldCheckLine", "ShieldCrossFill", "ShieldCrossLine", "ShieldFill", "ShieldFlashFill", "ShieldFlashLine", "ShieldKeyholeFill", "ShieldKeyholeLine", "ShieldLine", "ShieldStarFill", "ShieldStarLine", "ShieldUserFill", "ShieldUserLine", "Shining2Fill", "Shining2Line", "ShiningFill", "ShiningLine", "Ship2Fill", "Ship2Line", "ShipFill", "ShipLine", "ShirtFill", "ShirtLine", "ShoppingBag2Fill", "ShoppingBag2Line", "ShoppingBag3Fill", "ShoppingBag3Line", "ShoppingBag4Fill", "ShoppingBag4Line", "ShoppingBagFill", "ShoppingBagLine", "ShoppingBasket2Fill", "ShoppingBasket2Line", "ShoppingBasketFill", "ShoppingBasketLine", "ShoppingCart2Fill", "ShoppingCart2Line", "ShoppingCartFill", "ShoppingCartLine", "ShowersFill", "ShowersLine", "ShuffleFill", "ShuffleLine", "ShutDownFill", "ShutDownLine", "SideBarFill", "SideBarLine", "SidebarFoldFill", "SidebarFoldLine", "SidebarUnfoldFill", "SidebarUnfoldLine", "SignalCellular1Fill", "SignalCellular1Line", "SignalCellular2Fill", "SignalCellular2Line", "SignalCellular3Fill", "SignalCellular3Line", "SignalCellularOffFill", "SignalCellularOffLine", "SignalTowerFill", "SignalTowerLine", "SignalWifi1Fill", "SignalWifi1Line", "SignalWifi2Fill", "SignalWifi2Line", "SignalWifi3Fill", "SignalWifi3Line", "SignalWifiErrorFill", "SignalWifiErrorLine", "SignalWifiFill", "SignalWifiLine", "SignalWifiOffFill", "SignalWifiOffLine", "SignpostFill", "SignpostLine", "SimCard2Fill", "SimCard2Line", "SimCardFill", "SimCardLine", "SimCardWarningFill", "SimCardWarningLine", "SingleQuotesL", "SingleQuotesR", "SipFill", "SipLine", "Sketching", "SkipBackFill", "SkipBackLine", "SkipBackMiniFill", "SkipBackMiniLine", "SkipDownFill", "SkipDownLine", "SkipForwardFill", "SkipForwardLine", "SkipForwardMiniFill", "SkipForwardMiniLine", "SkipLeftFill", "SkipLeftLine", "SkipRightFill", "SkipRightLine", "SkipUpFill", "SkipUpLine", "Skull2Fill", "Skull2Line", "SkullFill", "SkullLine", "SkypeFill", "SkypeLine", "SlackFill", "SlackLine", "SlashCommands", "SlashCommands2", "SliceFill", "SliceLine", "Slideshow2Fill", "Slideshow2Line", "Slideshow3Fill", "Slideshow3Line", "Slideshow4Fill", "Slideshow4Line", "SlideshowFill", "SlideshowLine", "SlideshowView", "SlowDownFill", "SlowDownLine", "SmartphoneFill", "SmartphoneLine", "SnapchatFill", "SnapchatLine", "SnowflakeFill", "SnowflakeLine", "SnowyFill", "SnowyLine", "SofaFill", "SofaLine", "SortAlphabetAsc", "SortAlphabetDesc", "SortAsc", "SortDesc", "SortNumberAsc", "SortNumberDesc", "SoundModuleFill", "SoundModuleLine", "SoundcloudFill", "SoundcloudLine", "Space", "SpaceShip2Fill", "SpaceShip2Line", "SpaceShipFill", "SpaceShipLine", "Spam2Fill", "Spam2Line", "Spam3Fill", "Spam3Line", "SpamFill", "SpamLine", "Sparkling2Fill", "Sparkling2Line", "SparklingFill", "SparklingLine", "SpeakAiFill", "SpeakAiLine", "SpeakFill", "SpeakLine", "Speaker2Fill", "Speaker2Line", "Speaker3Fill", "Speaker3Line", "SpeakerFill", "SpeakerLine", "SpectrumFill", "SpectrumLine", "SpeechToTextFill", "SpeechToTextLine", "SpeedFill", "SpeedLine", "SpeedMiniFill", "SpeedMiniLine", "SpeedUpFill", "SpeedUpLine", "SplitCellsHorizontal", "SplitCellsVertical", "SpotifyFill", "SpotifyLine", "SpyFill", "SpyLine", "SquareFill", "SquareLine", "SquareRoot", "StackFill", "StackLine", "StackOverflowFill", "StackOverflowLine", "StackedChartFill", "StackedChartLine", "StackedView", "StackshareFill", "StackshareLine", "StairsFill", "StairsLine", "StarFill", "StarHalfFill", "StarHalfLine", "StarHalfSFill", "StarHalfSLine", "StarLine", "StarOffFill", "StarOffLine", "StarSFill", "StarSLine", "StarSmileFill", "StarSmileLine", "SteamFill", "SteamLine", "Steering2Fill", "Steering2Line", "SteeringFill", "SteeringLine", "StethoscopeFill", "StethoscopeLine", "StickyNote2Fill", "StickyNote2Line", "StickyNoteAddFill", "StickyNoteAddLine", "StickyNoteFill", "StickyNoteLine", "StockFill", "StockLine", "StopCircleFill", "StopCircleLine", "StopFill", "StopLargeFill", "StopLargeLine", "StopLine", "StopMiniFill", "StopMiniLine", "Store2Fill", "Store2Line", "Store3Fill", "Store3Line", "StoreFill", "StoreLine", "Strikethrough", "Strikethrough2", "SubredditFill", "SubredditLine", "Subscript", "Subscript2", "SubtractFill", "SubtractLine", "SubwayFill", "SubwayLine", "SubwayWifiFill", "SubwayWifiLine", "Suitcase2Fill", "Suitcase2Line", "Suitcase3Fill", "Suitcase3Line", "SuitcaseFill", "SuitcaseLine", "SunCloudyFill", "SunCloudyLine", "SunFill", "SunFoggyFill", "SunFoggyLine", "SunLine", "SupabaseFill", "SupabaseLine", "Superscript", "Superscript2", "SurgicalMaskFill", "SurgicalMaskLine", "SurroundSoundFill", "SurroundSoundLine", "SurveyFill", "SurveyLine", "SvelteFill", "SvelteLine", "Swap2Fill", "Swap2Line", "Swap3Fill", "Swap3Line", "SwapBoxFill", "SwapBoxLine", "SwapFill", "SwapLine", "SwitchFill", "SwitchLine", "SwordFill", "SwordLine", "SyringeFill", "SyringeLine", "TBoxFill", "TBoxLine", "TShirt2Fill", "TShirt2Line", "TShirtAirFill", "TShirtAirLine", "TShirtFill", "TShirtLine", "Table2", "Table3", "TableAltFill", "TableAltLine", "TableFill", "TableLine", "TableView", "TabletFill", "TabletLine", "TailwindCssFill", "TailwindCssLine", "TakeawayFill", "TakeawayLine", "TaobaoFill", "TaobaoLine", "TapeFill", "TapeLine", "TargetFill", "TargetLine", "TaskFill", "TaskLine", "TaxiFill", "TaxiLine", "TaxiWifiFill", "TaxiWifiLine", "TeamFill", "TeamLine", "Telegram2Fill", "Telegram2Line", "TelegramFill", "TelegramLine", "TempColdFill", "TempColdLine", "TempHotFill", "TempHotLine", "TentFill", "TentLine", "TerminalBoxFill", "TerminalBoxLine", "TerminalFill", "TerminalLine", "TerminalWindowFill", "TerminalWindowLine", "TestTubeFill", "TestTubeLine", "Text", "TextBlock", "TextDirectionL", "TextDirectionR", "TextSnippet", "TextSpacing", "TextToSpeechFill", "TextToSpeechLine", "TextWrap", "ThermometerFill", "ThermometerLine", "ThreadsFill", "ThreadsLine", "ThumbDownFill", "ThumbDownLine", "ThumbUpFill", "ThumbUpLine", "ThunderstormsFill", "ThunderstormsLine", "Ticket2Fill", "Ticket2Line", "TicketFill", "TicketLine", "TiktokFill", "TiktokLine", "TimeFill", "TimeLine", "TimeZoneFill", "TimeZoneLine", "TimelineView", "Timer2Fill", "Timer2Line", "TimerFill", "TimerFlashFill", "TimerFlashLine", "TimerLine", "TodoFill", "TodoLine", "ToggleFill", "ToggleLine", "TokenSwapFill", "TokenSwapLine", "ToolsFill", "ToolsLine", "ToothFill", "ToothLine", "TornadoFill", "TornadoLine", "TrademarkFill", "TrademarkLine", "TrafficLightFill", "TrafficLightLine", "TrainFill", "TrainLine", "TrainWifiFill", "TrainWifiLine", "Translate", "Translate2", "TranslateAi", "TranslateAi2", "TravestiFill", "TravestiLine", "TreasureMapFill", "TreasureMapLine", "TreeFill", "TreeLine", "TrelloFill", "TrelloLine", "TriangleFill", "TriangleLine", "TriangularFlagFill", "TriangularFlagLine", "TrophyFill", "TrophyLine", "TruckFill", "TruckLine", "TumblrFill", "TumblrLine", "Tv2Fill", "Tv2Line", "TvFill", "TvLine", "TwitchFill", "TwitchLine", "TwitterFill", "TwitterLine", "TwitterXFill", "TwitterXLine", "TyphoonFill", "TyphoonLine", "UDiskFill", "UDiskLine", "UbuntuFill", "UbuntuLine", "UmbrellaFill", "UmbrellaLine", "Underline", "UninstallFill", "UninstallLine", "UnpinFill", "UnpinLine", "UnsplashFill", "UnsplashLine", "Upload2Fill", "Upload2Line", "UploadCloud2Fill", "UploadCloud2Line", "UploadCloudFill", "UploadCloudLine", "UploadFill", "UploadLine", "UpworkFill", "UpworkLine", "UsbFill", "UsbLine", "User2Fill", "User2Line", "User3Fill", "User3Line", "User4Fill", "User4Line", "User5Fill", "User5Line", "User6Fill", "User6Line", "UserAddFill", "UserAddLine", "UserCommunityFill", "UserCommunityLine", "UserFill", "UserFollowFill", "UserFollowLine", "UserForbidFill", "UserForbidLine", "UserHeartFill", "UserHeartLine", "UserLine", "UserLocationFill", "UserLocationLine", "UserMinusFill", "UserMinusLine", "UserReceived2Fill", "UserReceived2Line", "UserReceivedFill", "UserReceivedLine", "UserSearchFill", "UserSearchLine", "UserSettingsFill", "UserSettingsLine", "UserShared2Fill", "UserShared2Line", "UserSharedFill", "UserSharedLine", "UserSmileFill", "UserSmileLine", "UserStarFill", "UserStarLine", "UserUnfollowFill", "UserUnfollowLine", "UserVoiceFill", "UserVoiceLine", "VercelFill", "VercelLine", "VerifiedBadgeFill", "VerifiedBadgeLine", "VideoAddFill", "VideoAddLine", "VideoAiFill", "VideoAiLine", "VideoChatFill", "VideoChatLine", "VideoDownloadFill", "VideoDownloadLine", "VideoFill", "VideoLine", "VideoOffFill", "VideoOffLine", "VideoOnAiFill", "VideoOnAiLine", "VideoOnFill", "VideoOnLine", "VideoUploadFill", "VideoUploadLine", "Vidicon2Fill", "Vidicon2Line", "VidiconFill", "VidiconLine", "VimeoFill", "VimeoLine", "VipCrown2Fill", "VipCrown2Line", "VipCrownFill", "VipCrownLine", "VipDiamondFill", "VipDiamondLine", "VipFill", "VipLine", "VirusFill", "VirusLine", "VisaFill", "VisaLine", "VkFill", "VkLine", "VoiceAiFill", "VoiceAiLine", "VoiceRecognitionFill", "VoiceRecognitionLine", "VoiceprintFill", "VoiceprintLine", "VolumeDownFill", "VolumeDownLine", "VolumeMuteFill", "VolumeMuteLine", "VolumeOffVibrateFill", "VolumeOffVibrateLine", "VolumeUpFill", "VolumeUpLine", "VolumeVibrateFill", "VolumeVibrateLine", "VuejsFill", "VuejsLine", "WalkFill", "WalkLine", "Wallet2Fill", "Wallet2Line", "Wallet3Fill", "Wallet3Line", "WalletFill", "WalletLine", "WaterFlashFill", "WaterFlashLine", "WaterPercentFill", "WaterPercentLine", "WebcamFill", "WebcamLine", "WebhookFill", "WebhookLine", "Wechat2Fill", "Wechat2Line", "WechatChannelsFill", "WechatChannelsLine", "WechatFill", "WechatLine", "WechatPayFill", "WechatPayLine", "WeiboFill", "WeiboLine", "WeightFill", "WeightLine", "WhatsappFill", "WhatsappLine", "WheelchairFill", "WheelchairLine", "WifiFill", "WifiLine", "WifiOffFill", "WifiOffLine", "Window2Fill", "Window2Line", "WindowFill", "WindowLine", "WindowsFill", "WindowsLine", "WindyFill", "WindyLine", "WirelessChargingFill", "WirelessChargingLine", "WomenFill", "WomenLine", "WordpressFill", "WordpressLine", "WrenchFill", "WrenchLine", "WubiInput", "XboxFill", "XboxLine", "XingFill", "XingLine", "XrpFill", "XrpLine", "XtzFill", "XtzLine", "YoutubeFill", "YoutubeLine", "YuqueFill", "YuqueLine", "ZcoolFill", "ZcoolLine", "ZhihuFill", "ZhihuLine", "ZhipuAiFill", "ZhipuAiLine", "ZoomInFill", "ZoomInLine", "ZoomOutFill", "ZoomOutLine", "ZzzFill", "ZzzLine"]);
|
|
18
|
+
/**
|
|
19
|
+
* Which keys hold an icon name, per element type — read off every `IconPicker`
|
|
20
|
+
* the editor's inspector renders (`editor/src/trait/widgets.ts` and the rows it
|
|
21
|
+
* composes). A value outside `ICON_NAMES` written to one of these renders the
|
|
22
|
+
* default star or nothing (`RenderIconSVG` / `HasIcon`), never the icon asked
|
|
23
|
+
* for; `allowNone` marks a key whose picker offers "None" (`''`).
|
|
24
|
+
*/
|
|
25
|
+
export const ICON_KEYS = {
|
|
26
|
+
"breadcrumb": [
|
|
27
|
+
{
|
|
28
|
+
"ns": "specials",
|
|
29
|
+
"key": "icon",
|
|
30
|
+
"allowNone": true
|
|
31
|
+
}
|
|
32
|
+
],
|
|
33
|
+
"button": [
|
|
34
|
+
{
|
|
35
|
+
"ns": "specials",
|
|
36
|
+
"key": "icon"
|
|
37
|
+
}
|
|
38
|
+
],
|
|
39
|
+
"chat-widget": [
|
|
40
|
+
{
|
|
41
|
+
"ns": "specials",
|
|
42
|
+
"key": "launcherIcon",
|
|
43
|
+
"allowNone": true
|
|
44
|
+
}
|
|
45
|
+
],
|
|
46
|
+
"divider": [
|
|
47
|
+
{
|
|
48
|
+
"ns": "specials",
|
|
49
|
+
"key": "iconName"
|
|
50
|
+
}
|
|
51
|
+
],
|
|
52
|
+
"form-calendar": [
|
|
53
|
+
{
|
|
54
|
+
"ns": "specials",
|
|
55
|
+
"key": "iconName"
|
|
56
|
+
}
|
|
57
|
+
],
|
|
58
|
+
"form-number": [
|
|
59
|
+
{
|
|
60
|
+
"ns": "specials",
|
|
61
|
+
"key": "iconName"
|
|
62
|
+
}
|
|
63
|
+
],
|
|
64
|
+
"form-step-button": [
|
|
65
|
+
{
|
|
66
|
+
"ns": "specials",
|
|
67
|
+
"key": "icon"
|
|
68
|
+
}
|
|
69
|
+
],
|
|
70
|
+
"form-submit": [
|
|
71
|
+
{
|
|
72
|
+
"ns": "specials",
|
|
73
|
+
"key": "icon"
|
|
74
|
+
}
|
|
75
|
+
],
|
|
76
|
+
"form-text": [
|
|
77
|
+
{
|
|
78
|
+
"ns": "specials",
|
|
79
|
+
"key": "iconName"
|
|
80
|
+
}
|
|
81
|
+
],
|
|
82
|
+
"icon": [
|
|
83
|
+
{
|
|
84
|
+
"ns": "specials",
|
|
85
|
+
"key": "name"
|
|
86
|
+
}
|
|
87
|
+
],
|
|
88
|
+
"list-dataset": [
|
|
89
|
+
{
|
|
90
|
+
"ns": "config",
|
|
91
|
+
"key": "listNavIcon",
|
|
92
|
+
"allowNone": true
|
|
93
|
+
}
|
|
94
|
+
],
|
|
95
|
+
"list-item": [
|
|
96
|
+
{
|
|
97
|
+
"ns": "specials",
|
|
98
|
+
"key": "icon",
|
|
99
|
+
"allowNone": true
|
|
100
|
+
}
|
|
101
|
+
],
|
|
102
|
+
"popup": [
|
|
103
|
+
{
|
|
104
|
+
"ns": "specials",
|
|
105
|
+
"key": "closeIcon",
|
|
106
|
+
"allowNone": true
|
|
107
|
+
}
|
|
108
|
+
],
|
|
109
|
+
"text-marquee-item": [
|
|
110
|
+
{
|
|
111
|
+
"ns": "specials",
|
|
112
|
+
"key": "icon",
|
|
113
|
+
"allowNone": true
|
|
114
|
+
}
|
|
115
|
+
]
|
|
116
|
+
};
|
|
@@ -536,6 +536,18 @@ function rebindPatch(doc, id, keys) {
|
|
|
536
536
|
* (not part of this document at all).
|
|
537
537
|
*/
|
|
538
538
|
export function duplicateNode(doc, id, guard) {
|
|
539
|
+
const parentId = doc.has(id) ? doc.node(id).data.parent : null;
|
|
540
|
+
const at = parentId && doc.has(parentId) ? doc.node(parentId).data.nodes.indexOf(id) : -1;
|
|
541
|
+
return copyNodeInto(doc, id, doc, parentId ?? '', at < 0 ? APPEND : at + 1, guard);
|
|
542
|
+
}
|
|
543
|
+
/**
|
|
544
|
+
* Copy `id` and everything under it — satellites included — out of `src` into
|
|
545
|
+
* `target` under `parentId` at `index`, under fresh ids. `src === target` is a
|
|
546
|
+
* duplicate; a different document is the editor's paste onto another page. The
|
|
547
|
+
* patches address `target` only, and read `src` only.
|
|
548
|
+
*/
|
|
549
|
+
export function copyNodeInto(src, id, target, parentId, index, guard) {
|
|
550
|
+
const doc = src;
|
|
539
551
|
const n = doc.node(id);
|
|
540
552
|
if (id === doc.doc.root_node_id)
|
|
541
553
|
throw new Error('sbuilder: cannot duplicate ROOT');
|
|
@@ -551,15 +563,24 @@ export function duplicateNode(doc, id, guard) {
|
|
|
551
563
|
'reduce back to a reference on save. Remove the block, duplicate, then add the block ' +
|
|
552
564
|
'again through its app.');
|
|
553
565
|
}
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
566
|
+
if (!parentId || !target.has(parentId)) {
|
|
567
|
+
throw new Error(src === target
|
|
568
|
+
? `sbuilder: ${id} has no parent to be duplicated beside`
|
|
569
|
+
: `sbuilder: parent "${parentId}" is not on the target page — omit parent_id to paste at the page's end, or read its ids with sb_outline.`);
|
|
570
|
+
}
|
|
571
|
+
if (src !== target) {
|
|
572
|
+
// Onto ANOTHER document the parent is the caller's choice, so it gets the
|
|
573
|
+
// checks sb_add gives one.
|
|
574
|
+
if (parentId !== target.doc.root_node_id)
|
|
575
|
+
requireContainer(target.node(parentId).data.type, parentId);
|
|
576
|
+
requireAllowed(target.node(parentId).data.type, n.data.type, guard);
|
|
577
|
+
soft(guard, () => refuseAppBlockParent(target, parentId, 'pasting into'));
|
|
557
578
|
}
|
|
558
579
|
// A copy lands BESIDE the original, so duplicating a repeater's template makes
|
|
559
580
|
// the second child the renderer will never draw. sb_add and sb_move already
|
|
560
581
|
// refuse that; duplicate is the likeliest way to reach for it, since
|
|
561
582
|
// "duplicate the card" is the move a designer makes constantly.
|
|
562
|
-
soft(guard, () => refuseSecondTemplate(
|
|
583
|
+
soft(guard, () => refuseSecondTemplate(target.doc, parentId, 'Duplicating into'));
|
|
563
584
|
const patches = [];
|
|
564
585
|
const ids = [];
|
|
565
586
|
// One pass, parent-first, so a child's `parent` always names an id already
|
|
@@ -610,13 +631,7 @@ export function duplicateNode(doc, id, guard) {
|
|
|
610
631
|
return clone.id;
|
|
611
632
|
};
|
|
612
633
|
const rootId = copy(id, parentId);
|
|
613
|
-
|
|
614
|
-
patches.push({
|
|
615
|
-
op: 'insert',
|
|
616
|
-
path: ['nodes', parentId, 'data', 'nodes'],
|
|
617
|
-
index: at < 0 ? APPEND : at + 1,
|
|
618
|
-
value: rootId,
|
|
619
|
-
});
|
|
634
|
+
patches.push({ op: 'insert', path: ['nodes', parentId, 'data', 'nodes'], index, value: rootId });
|
|
620
635
|
return { patches, ids };
|
|
621
636
|
}
|
|
622
637
|
export function moveNode(doc, id, newParentId, index, guard) {
|
|
@@ -106,6 +106,8 @@ export const FIX = {
|
|
|
106
106
|
'post, blog). Off those it renders its placeholder for ever.',
|
|
107
107
|
unread_value: 'Write a value the renderer reads: sb_traits_for "<type>" lists them; sb_set id "<id>" ' +
|
|
108
108
|
'with one, at the same breakpoint it is stored at.',
|
|
109
|
+
unknown_icon: 'Write a real icon name: sb_set id "<id>", namespace <ns>, keys { "<key>": "<name>" } — the ' +
|
|
110
|
+
'problem lists the nearest ones; sb_catalog_search query "icon:<word>" lists more.',
|
|
109
111
|
// ADVICE, never a blocker: the snippet works. The cost is that nobody can edit
|
|
110
112
|
// it in the inspector, it ignores the theme, and it is invisible to sb_review.
|
|
111
113
|
custom_code_native: 'Consider rebuilding it natively: sb_catalog_search "<key>" → sb_traits_for → sb_add, ' +
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
import { ICON_KEYS, ICON_NAMES } from '../../catalog/icons.generated.js';
|
|
2
|
+
/**
|
|
3
|
+
* ICON NAMES — which keys hold one, whether a value is one, and the nearest real
|
|
4
|
+
* ones when it is not.
|
|
5
|
+
*
|
|
6
|
+
* A name outside the manifest is stored, saved and published, then drawn as the
|
|
7
|
+
* DEFAULT star (`RenderIconSVG` falls back to `DefaultIcon`) or as nothing
|
|
8
|
+
* (`button`'s `generated.HasIcon` guard) — never the icon asked for, and with no
|
|
9
|
+
* error at any step. A WARNING, like every vocabulary note here: the manifest can
|
|
10
|
+
* be older than the deployment. `''` is never flagged: it is what the "None" cell
|
|
11
|
+
* writes, what `form-text` seeds, and the `icon` element draws its default for it.
|
|
12
|
+
*/
|
|
13
|
+
/** `true` when `<ns>.<key>` holds an icon name on this element (ICON_KEYS). */
|
|
14
|
+
export function isIconKey(type, ns, key) {
|
|
15
|
+
return ICON_KEYS[type]?.some((k) => k.ns === ns && k.key === key) ?? false;
|
|
16
|
+
}
|
|
17
|
+
/** The `<ns>.<key>` list `sb_traits_for` reports; empty when the element has none. */
|
|
18
|
+
export function iconKeysOf(type) {
|
|
19
|
+
return (ICON_KEYS[type] ?? []).map((k) => `${k.ns}.${k.key}`);
|
|
20
|
+
}
|
|
21
|
+
const norm = (s) => s.replace(/^ri-/i, '').toLowerCase().replace(/[^a-z0-9]/g, '');
|
|
22
|
+
const stemOf = (s) => norm(s).replace(/(fill|line)$/, '');
|
|
23
|
+
let index = null;
|
|
24
|
+
/**
|
|
25
|
+
* Names whose stem (lower-case, separators and the Fill/Line suffix dropped)
|
|
26
|
+
* equals, starts with, or contains the query's — in that order, shorter first.
|
|
27
|
+
* Nothing found → retried on the query's longest word, so "ShoppingBasketIcon"
|
|
28
|
+
* still lands near the basket.
|
|
29
|
+
*
|
|
30
|
+
* Substring ranking only — a SEARCH is a word someone chose. A misspelled NAME is
|
|
31
|
+
* `nearestIcons`' job, which tries edit distance first.
|
|
32
|
+
*/
|
|
33
|
+
export function searchIcons(query, cap = 30, retryOnWord = true) {
|
|
34
|
+
index ??= [...ICON_NAMES].map((n) => [n, stemOf(n)]);
|
|
35
|
+
const rank = (q) => !q
|
|
36
|
+
? []
|
|
37
|
+
: index
|
|
38
|
+
.map(([n, s]) => ({ n, s, r: s === q ? 3 : s.startsWith(q) ? 2 : s.includes(q) ? 1 : 0 }))
|
|
39
|
+
.filter((x) => x.r > 0)
|
|
40
|
+
.sort((a, b) => b.r - a.r || a.s.length - b.s.length || a.n.localeCompare(b.n))
|
|
41
|
+
.map((x) => x.n);
|
|
42
|
+
let hits = rank(stemOf(query));
|
|
43
|
+
if (!hits.length && retryOnWord) {
|
|
44
|
+
const words = query.replace(/([a-z])([A-Z])/g, '$1 $2').toLowerCase().split(/[^a-z0-9]+/);
|
|
45
|
+
const longest = words.filter((w) => w.length > 2).sort((a, b) => b.length - a.length)[0];
|
|
46
|
+
if (longest)
|
|
47
|
+
hits = rank(longest);
|
|
48
|
+
}
|
|
49
|
+
return hits.slice(0, cap);
|
|
50
|
+
}
|
|
51
|
+
/** Edit distance, capped: stops early past `max` (a name's stem is short). */
|
|
52
|
+
function distance(a, b, max) {
|
|
53
|
+
if (Math.abs(a.length - b.length) > max)
|
|
54
|
+
return max + 1;
|
|
55
|
+
let prev = Array.from({ length: b.length + 1 }, (_, j) => j);
|
|
56
|
+
for (let i = 1; i <= a.length; i++) {
|
|
57
|
+
const cur = [i];
|
|
58
|
+
let best = i;
|
|
59
|
+
for (let j = 1; j <= b.length; j++) {
|
|
60
|
+
cur[j] = Math.min(prev[j] + 1, cur[j - 1] + 1, prev[j - 1] + (a[i - 1] === b[j - 1] ? 0 : 1));
|
|
61
|
+
best = Math.min(best, cur[j]);
|
|
62
|
+
}
|
|
63
|
+
if (best > max)
|
|
64
|
+
return max + 1;
|
|
65
|
+
prev = cur;
|
|
66
|
+
}
|
|
67
|
+
return prev[b.length];
|
|
68
|
+
}
|
|
69
|
+
/**
|
|
70
|
+
* The names a WRONG value most likely meant. A typo first ("ShoppingCartt" is
|
|
71
|
+
* one edit from ShoppingCart, while a word search on "shopping" ranks the shorter
|
|
72
|
+
* ShoppingBag ahead of it — measured on a live platform), then the search.
|
|
73
|
+
* Fill before Line on a tie: the platform's own default (StarFill) is a Fill.
|
|
74
|
+
*/
|
|
75
|
+
export function nearestIcons(query) {
|
|
76
|
+
index ??= [...ICON_NAMES].map((n) => [n, stemOf(n)]);
|
|
77
|
+
const q = stemOf(query);
|
|
78
|
+
const max = Math.max(1, Math.floor(q.length / 4));
|
|
79
|
+
// The suffix the caller wrote wins a tie; otherwise Fill, the default's own.
|
|
80
|
+
const want = /line$/i.test(norm(query)) ? 'Line' : 'Fill';
|
|
81
|
+
const typos = q
|
|
82
|
+
? index
|
|
83
|
+
.map(([n, s]) => ({ n, s, d: distance(q, s, max) }))
|
|
84
|
+
.filter((x) => x.d <= max)
|
|
85
|
+
.sort((a, b) => a.d - b.d ||
|
|
86
|
+
// ShoppingCart over ShoppingCart2 for "ShoppingCartt": a stem the query
|
|
87
|
+
// extends is a closer guess than a sibling the same distance away.
|
|
88
|
+
Number(q.startsWith(b.s) || b.s.startsWith(q)) - Number(q.startsWith(a.s) || a.s.startsWith(q)) ||
|
|
89
|
+
Number(b.n.endsWith(want)) - Number(a.n.endsWith(want)) ||
|
|
90
|
+
a.s.length - b.s.length ||
|
|
91
|
+
a.n.localeCompare(b.n))
|
|
92
|
+
.map((x) => x.n)
|
|
93
|
+
: [];
|
|
94
|
+
// A value that is a WHOLE WORD of real names ("cart" in Shopping·Cart, "user")
|
|
95
|
+
// means those names, not Car/Cast one edit away; a fragment inside another word
|
|
96
|
+
// ("hart" in c·hart) is more likely a typo (Heart). So: word hits, then typos,
|
|
97
|
+
// then whatever else the search finds.
|
|
98
|
+
const wordsOf = (n) => n.replace(/([a-z0-9])([A-Z])/g, '$1 $2').toLowerCase().split(' ');
|
|
99
|
+
const words = q
|
|
100
|
+
? index
|
|
101
|
+
.filter(([n, st]) => st.startsWith(q) || wordsOf(n).includes(q))
|
|
102
|
+
.sort(([an, as], [bn, bs]) => as.length - bs.length || Number(bn.endsWith(want)) - Number(an.endsWith(want)) || an.localeCompare(bn))
|
|
103
|
+
.map(([n]) => n)
|
|
104
|
+
: [];
|
|
105
|
+
const order = [...words, ...typos, ...searchIcons(query, 5)];
|
|
106
|
+
return [...new Set(order)].slice(0, 5);
|
|
107
|
+
}
|
|
108
|
+
/**
|
|
109
|
+
* The problem sentence for `value` written to `<ns>.<key>` on `type`, or null
|
|
110
|
+
* when the key holds no icon or the value is a real name (or `''`).
|
|
111
|
+
*/
|
|
112
|
+
export function iconNote(type, ns, key, value) {
|
|
113
|
+
if (!isIconKey(type, ns, key) || value === '' || value === undefined || value === null)
|
|
114
|
+
return null;
|
|
115
|
+
if (typeof value === 'string' && ICON_NAMES.has(value))
|
|
116
|
+
return null;
|
|
117
|
+
const near = typeof value === 'string' ? nearestIcons(value) : [];
|
|
118
|
+
return {
|
|
119
|
+
near,
|
|
120
|
+
problem: `${ns}.${key} = ${JSON.stringify(value)} is not an icon name this platform has, so ${type} ` +
|
|
121
|
+
'publishes the default star or no icon at all, with no error. ' +
|
|
122
|
+
(near.length ? `Nearest: ${near.join(', ')}.` : 'Nothing close — search with sb_catalog_search query "icon:<word>".'),
|
|
123
|
+
};
|
|
124
|
+
}
|
|
@@ -7,6 +7,7 @@ import { fill } from './findings.js';
|
|
|
7
7
|
import { formFieldFindings } from './formdoc.js';
|
|
8
8
|
import { deadNavigation, liveEventTable, PAYLOAD_NEEDS } from './navhref.js';
|
|
9
9
|
import { unknownValueNote, unknownWriteNote, vocabularyFor, vocabularyForWrite } from './vocabulary.js';
|
|
10
|
+
import { iconNote } from './icons.js';
|
|
10
11
|
/**
|
|
11
12
|
* A defect somebody looking at the page would see.
|
|
12
13
|
*
|
|
@@ -466,6 +467,23 @@ opts = {}) {
|
|
|
466
467
|
const said = new Set();
|
|
467
468
|
for (const [ns, slot] of layers) {
|
|
468
469
|
for (const [k, v] of Object.entries(slot ?? {})) {
|
|
470
|
+
// An icon name the manifest lacks draws the default star or nothing.
|
|
471
|
+
const icon = iconNote(type, ns, k, v);
|
|
472
|
+
if (icon) {
|
|
473
|
+
if (said.has(`${ns}.${k}=${String(v)}`))
|
|
474
|
+
continue;
|
|
475
|
+
said.add(`${ns}.${k}=${String(v)}`);
|
|
476
|
+
out.push({
|
|
477
|
+
code: 'unknown_icon',
|
|
478
|
+
nodeId: id,
|
|
479
|
+
type,
|
|
480
|
+
problem: icon.problem,
|
|
481
|
+
key: `${ns}.${k}`,
|
|
482
|
+
fix: fill('unknown_icon', { id, ns, key: k }),
|
|
483
|
+
...(inOverlay.has(id) ? { overlay: true } : {}),
|
|
484
|
+
});
|
|
485
|
+
continue;
|
|
486
|
+
}
|
|
469
487
|
const unread = unknownWriteNote(type, ns, k, v) ?? (ns === 'config' ? unknownValueNote(k, v) : null);
|
|
470
488
|
if (!unread || said.has(`${ns}.${k}=${String(v)}`))
|
|
471
489
|
continue;
|
|
@@ -3,6 +3,8 @@ import { FIELD_SKIN_BY_NODE } from '../../catalog/fieldskin.generated.js';
|
|
|
3
3
|
import { FORM_RULE_VOCAB } from '../../catalog/formrules.generated.js';
|
|
4
4
|
import { fill } from './findings.js';
|
|
5
5
|
import { collectsValue, formFieldFindings, formFields, nameFromLabel } from './formdoc.js';
|
|
6
|
+
import { ICON_KEYS } from '../../catalog/icons.generated.js';
|
|
7
|
+
import { iconNote } from './icons.js';
|
|
6
8
|
import { animationNote, deadKeyNote, preconditionNotes, unknownValueNote, unknownWriteNote, unsupportedSettingNote, } from './vocabulary.js';
|
|
7
9
|
/**
|
|
8
10
|
* Keys any node may carry, read by shared helpers rather than one element:
|
|
@@ -23,15 +25,13 @@ const UNIVERSAL = {
|
|
|
23
25
|
* stores the picked site text style as `config.textGlobalStyle`, the key
|
|
24
26
|
* `schema/src/theme.ts` names for every text-bearing element). Keyed by the
|
|
25
27
|
* GENERATED control name, so a key is known only where the element offers the row.
|
|
28
|
+
* Every icon picker's key is generated (`ICON_KEYS`) and added in `keysOf`.
|
|
26
29
|
*
|
|
27
30
|
* ponytail: hand-read, and only the rows the platform's own seeds exercise
|
|
28
31
|
* (test/seed-sweep.test.ts). Upgrade: have codegen read every widget's
|
|
29
|
-
* `specialKey`/`configKey` into the catalog
|
|
32
|
+
* `specialKey`/`configKey` into the catalog, as it now does for IconPicker.
|
|
30
33
|
*/
|
|
31
34
|
const WIDGET_WRITES = {
|
|
32
|
-
button_icon: ['specials', 'icon'],
|
|
33
|
-
breadcrumb_icon: ['specials', 'icon'],
|
|
34
|
-
divider_icon: ['specials', 'iconName'],
|
|
35
35
|
text_global_style: ['config', 'textGlobalStyle'],
|
|
36
36
|
};
|
|
37
37
|
const known = new Map();
|
|
@@ -64,6 +64,8 @@ function keysOf(type) {
|
|
|
64
64
|
for (const c of el.controls)
|
|
65
65
|
if (WIDGET_WRITES[c])
|
|
66
66
|
add(WIDGET_WRITES[c][0], [WIDGET_WRITES[c][1]]);
|
|
67
|
+
for (const k of ICON_KEYS[type] ?? [])
|
|
68
|
+
add(k.ns, [k.key]);
|
|
67
69
|
// A control the trait registry does not describe is often an inspector row
|
|
68
70
|
// named after the key it writes (`{ key: 'quickviewId', visible: false }` on
|
|
69
71
|
// list-dataset), so its NAME counts as a key in either namespace.
|
|
@@ -134,6 +136,11 @@ export function writeCheck(type, namespace, keys, opts = {}) {
|
|
|
134
136
|
'"style". Ignore this if the key is newer than this catalog.');
|
|
135
137
|
continue;
|
|
136
138
|
}
|
|
139
|
+
const icon = iconNote(type, namespace, k, v);
|
|
140
|
+
if (icon) {
|
|
141
|
+
push(`icon:${type}.${namespace}.${k}=${JSON.stringify(v)}`, 'unknown_icon', k, icon.problem, icon.near.length ? `Write one of: ${icon.near.join(', ')}.` : 'Find a name with sb_catalog_search query "icon:<word>".');
|
|
142
|
+
continue;
|
|
143
|
+
}
|
|
137
144
|
const vn = type ? unknownWriteNote(type, namespace, k, v) : null;
|
|
138
145
|
if (vn) {
|
|
139
146
|
push(`value:${type}.${namespace}.${k}=${JSON.stringify(v)}`, 'unknown_value', k, vn, 'Write one of the listed values.');
|
package/dist/tools/chrome.js
CHANGED
|
@@ -727,10 +727,40 @@ export async function promoteGlobal(ctx, session, siteId, nodeId, kind, name, op
|
|
|
727
727
|
/**
|
|
728
728
|
* Save a section on the OPEN page as a reusable section template — the editor's
|
|
729
729
|
* "Save as template". The page is untouched; `sb_template_use` places a copy.
|
|
730
|
+
*
|
|
731
|
+
* With `templateId` it REWRITES that template's content instead — the editor's
|
|
732
|
+
* template edit mode. The agent's way in is the full toolset: place the template
|
|
733
|
+
* on a page with sb_template_use, edit it there, save it back over the template.
|
|
734
|
+
* The PUT is fenced on the template's `rev`, read from the list (there is no
|
|
735
|
+
* single-template GET), so an edit somebody else saved in between is refused.
|
|
730
736
|
*/
|
|
731
737
|
export async function saveTemplate(ctx, session, siteId, nodeId, name, opts) {
|
|
732
738
|
const doc = session.current();
|
|
733
739
|
const id = liftable(doc.doc, nodeId, 'template_save');
|
|
740
|
+
const base = `/api/sites/${encodeURIComponent(siteId)}/section-templates`;
|
|
741
|
+
if (opts.templateId) {
|
|
742
|
+
const list = (await request({ base: ctx.base, method: 'GET', path: base, token: siteToken(ctx), fetchImpl: ctx.fetchImpl }));
|
|
743
|
+
const t = (list.sectionTemplates ?? []).find((x) => x.id === opts.templateId);
|
|
744
|
+
if (!t)
|
|
745
|
+
throw new Error(`sbuilder: this site has no section template "${opts.templateId}" — sb_templates lists them.`);
|
|
746
|
+
if (t.source && t.source !== 'site') {
|
|
747
|
+
throw new Error(`sbuilder: template "${opts.templateId}" is a ${t.source} template, not this site's own — save a new one instead.`);
|
|
748
|
+
}
|
|
749
|
+
const document = sectionDocument(doc.doc, id);
|
|
750
|
+
const nodes = Object.keys(document.nodes).length;
|
|
751
|
+
const path = `${base}/${encodeURIComponent(opts.templateId)}/document`;
|
|
752
|
+
if (opts.dryRun) {
|
|
753
|
+
return { dry_run: true, nodes, next: `Nothing was sent. Re-call with dry_run:false to replace "${t.name ?? opts.templateId}"'s content.` };
|
|
754
|
+
}
|
|
755
|
+
await request({ base: ctx.base, method: 'PUT', path, token: siteToken(ctx), body: { document, rev: t.rev ?? 0 }, fetchImpl: ctx.fetchImpl });
|
|
756
|
+
return {
|
|
757
|
+
template: { id: opts.templateId, name: t.name ?? '' },
|
|
758
|
+
nodes,
|
|
759
|
+
next: 'Pages that placed this template earlier keep their own copy; only new placements get this content. ' +
|
|
760
|
+
'A template shared with the organization goes back to review after a content change.' +
|
|
761
|
+
(name?.trim() ? ' `name` was not applied — a rewrite changes content only.' : ''),
|
|
762
|
+
};
|
|
763
|
+
}
|
|
734
764
|
if (!name?.trim())
|
|
735
765
|
throw new Error('sbuilder: action:"template_save" needs name — the template\'s name in the library.');
|
|
736
766
|
const document = sectionDocument(doc.doc, id);
|
|
@@ -741,7 +771,7 @@ export async function saveTemplate(ctx, session, siteId, nodeId, name, opts) {
|
|
|
741
771
|
const made = (await request({
|
|
742
772
|
base: ctx.base,
|
|
743
773
|
method: 'POST',
|
|
744
|
-
path:
|
|
774
|
+
path: base,
|
|
745
775
|
token: siteToken(ctx),
|
|
746
776
|
body: { name: name.trim(), document },
|
|
747
777
|
fetchImpl: ctx.fetchImpl,
|
package/dist/tools/live.js
CHANGED
|
@@ -652,11 +652,16 @@ export function registerLiveTools(server, ctx, session) {
|
|
|
652
652
|
});
|
|
653
653
|
}
|
|
654
654
|
const asset = await uploadMedia(ctx, site_id, { path, url, name, folderId: folder_id });
|
|
655
|
+
const font = asset.mediaType === 'font';
|
|
655
656
|
return text({
|
|
656
657
|
asset,
|
|
657
|
-
next: asset.url
|
|
658
|
-
?
|
|
659
|
-
:
|
|
658
|
+
next: !asset.url
|
|
659
|
+
? 'Uploaded, but the server returned no url — read it back with sb_media_list.'
|
|
660
|
+
: font
|
|
661
|
+
? `A font: give it a family — POST /api/sites/{siteId}/fonts {name, source:"upload"}, then PUT ` +
|
|
662
|
+
`…/fonts/{groupId}/files {weight, url: ${JSON.stringify(asset.url)}, format, sizeBytes}, ` +
|
|
663
|
+
'and use the family by NAME (settings fontFamily or a theme text style).'
|
|
664
|
+
: `Use it: sb_set id "<node>", namespace specials, keys { "src": ${JSON.stringify(asset.url)} }`,
|
|
660
665
|
});
|
|
661
666
|
});
|
|
662
667
|
server.registerTool('sb_event', {
|
package/dist/tools/page.js
CHANGED
|
@@ -8,7 +8,7 @@ import { bandIds, proveLive } from '../domains/site/liveproof.js';
|
|
|
8
8
|
import { canvasVerdict, driftOf } from '../domains/site/pagestate.js';
|
|
9
9
|
import { PageDoc } from '../domains/site/document.js';
|
|
10
10
|
import { canonicalRoot, PAGE_ROOT_ID } from '../domains/site/ids.js';
|
|
11
|
-
import { addSubtree, setMany, baseOnlyKeys, moveNode, removeNode, duplicateNode, } from '../domains/site/builder.js';
|
|
11
|
+
import { addSubtree, setMany, baseOnlyKeys, moveNode, removeNode, duplicateNode, copyNodeInto, } from '../domains/site/builder.js';
|
|
12
12
|
import { baseOnlyNote } from '../domains/site/baseonly.js';
|
|
13
13
|
import { detachNote, presetIdOf, presetLayer } from '../domains/site/theme.js';
|
|
14
14
|
import { inertHintsFor } from '../domains/site/inert.js';
|
|
@@ -18,7 +18,7 @@ import { skinLevelNote } from '../domains/site/fieldskin.js';
|
|
|
18
18
|
import { siteTheme } from '../domains/site/theme-fetch.js';
|
|
19
19
|
import { ensureSiteTheme } from './theme.js';
|
|
20
20
|
import { request, redact, watchPageWrites, onPageSourceWrite, onGlobalWrite, onFormDocumentWrite } from '../transport/http.js';
|
|
21
|
-
import { SPEC_GLOBAL_ID, SPEC_GLOBAL_REV, SPEC_OVERLAY_ID, SPEC_OVERLAY_REV } from '../core/tree.js';
|
|
21
|
+
import { SPEC_GLOBAL_ID, SPEC_GLOBAL_REV, SPEC_OVERLAY_ID, SPEC_OVERLAY_REV, subtreeIds } from '../core/tree.js';
|
|
22
22
|
import { siteToken } from './credentialpick.js';
|
|
23
23
|
import { validateForSave } from '../domains/site/validate.js';
|
|
24
24
|
import { formDocChecks, sayChecks, specCheck, writeCheck } from '../domains/site/writecheck.js';
|
|
@@ -28,6 +28,7 @@ import { readinessGaps, READINESS_NOTICE, siteLanguage } from '../domains/site/r
|
|
|
28
28
|
import { gatherReadiness } from '../domains/site/readiness-fetch.js';
|
|
29
29
|
import { globalWarning, restampPatches, RESPONSIVE_NOTICE } from '../domains/site/traps.js';
|
|
30
30
|
import { catalogBrowse, catalogMatches, searchWithTemplates, traitsFor } from '../catalog/element-search.js';
|
|
31
|
+
import { searchIcons } from '../domains/site/icons.js';
|
|
31
32
|
import { layoutForPageName, missingUsualPages, purposeTypeForName } from '../domains/site/inventory.js';
|
|
32
33
|
import { LAYOUT_PATTERNS, PATTERN_BY_ID, THEME_TOKENS, } from '../domains/site/patterns.js';
|
|
33
34
|
import { tokensFromPage } from '../domains/site/importmap.js';
|
|
@@ -1364,7 +1365,7 @@ export function registerPageTools(server, ctx) {
|
|
|
1364
1365
|
server.registerTool('sb_catalog_search', {
|
|
1365
1366
|
description: 'Find an element type by what it does — or OMIT query to browse every type, the only ' +
|
|
1366
1367
|
'way to meet one you would not have searched for. detail:true adds the AI hints, as ' +
|
|
1367
|
-
'does sb_traits_for.',
|
|
1368
|
+
'does sb_traits_for. query "icon:<word>" lists icon names.',
|
|
1368
1369
|
inputSchema: {
|
|
1369
1370
|
query: z.string().optional(),
|
|
1370
1371
|
limit: z.number().int().min(1).max(60).optional().describe('Default 8'),
|
|
@@ -1376,15 +1377,17 @@ export function registerPageTools(server, ctx) {
|
|
|
1376
1377
|
// whole catalogue instead — see catalogBrowse for the measurement that made
|
|
1377
1378
|
// this necessary: a store built with these tools used 17 element types and
|
|
1378
1379
|
// hand-assembled what a dozen purpose-built ones already do.
|
|
1379
|
-
async ({ query, limit, detail }) => text(query
|
|
1380
|
-
?
|
|
1381
|
-
:
|
|
1382
|
-
|
|
1383
|
-
|
|
1384
|
-
|
|
1385
|
-
|
|
1386
|
-
|
|
1387
|
-
|
|
1380
|
+
async ({ query, limit, detail }) => text(/^\s*icon:/i.test(query ?? '')
|
|
1381
|
+
? { icons: searchIcons(query.replace(/^\s*icon:/i, '').trim(), limit ?? 30) }
|
|
1382
|
+
: query && query.trim()
|
|
1383
|
+
? searchWithTemplates(query, catalogMatches(query, { limit, detail }))
|
|
1384
|
+
: {
|
|
1385
|
+
elements: catalogBrowse(),
|
|
1386
|
+
note: 'Every element type, grouped as the palette groups them. Pass a TYPE as query ' +
|
|
1387
|
+
'for the fields to choose by, or a CATEGORY NAME with limit 60 to read that ' +
|
|
1388
|
+
"whole group's descriptions at once — which is how you find out what the ones " +
|
|
1389
|
+
'you have never used are for. sb_traits_for then has the controls and hints.',
|
|
1390
|
+
}));
|
|
1388
1391
|
server.registerTool('sb_traits_for', {
|
|
1389
1392
|
description: "This element's INSPECTOR, as a person sees it: tabs, groups, and every control name — " +
|
|
1390
1393
|
'with what each DECLARED control writes, and the AI hints for using the element. Read ' +
|
|
@@ -1813,24 +1816,116 @@ export function registerPageTools(server, ctx) {
|
|
|
1813
1816
|
});
|
|
1814
1817
|
server.registerTool('sb_duplicate', {
|
|
1815
1818
|
description: 'Copy a node and everything under it, under fresh ids, right after the original. The ' +
|
|
1816
|
-
'move a designer makes constantly — build one card, duplicate it twice.'
|
|
1819
|
+
'move a designer makes constantly — build one card, duplicate it twice. to_page_id ' +
|
|
1820
|
+
'(and to_site_id) pastes the copy onto another page instead, as the editor\'s copy/paste ' +
|
|
1821
|
+
'does: a cross-site copy has its images copied into the target library and references ' +
|
|
1822
|
+
'that cannot travel blanked and listed; the open page becomes the target.',
|
|
1817
1823
|
inputSchema: {
|
|
1818
1824
|
id: z.string(),
|
|
1825
|
+
to_page_id: z.string().optional(),
|
|
1826
|
+
to_site_id: z.string().optional(),
|
|
1827
|
+
parent_id: z.string().optional().describe('with to_page_id: where on that page; default ROOT, before a footer'),
|
|
1828
|
+
index: z.number().int().min(0).optional(),
|
|
1819
1829
|
dry_run: z.boolean().optional(),
|
|
1820
1830
|
force: z.boolean().optional().describe('Override a render-inference guard; reported as forced'),
|
|
1821
1831
|
},
|
|
1822
1832
|
annotations: { readOnlyHint: false, destructiveHint: false },
|
|
1823
|
-
}, async ({ id, dry_run, force }) => {
|
|
1833
|
+
}, async ({ id, to_page_id, to_site_id, parent_id, index, dry_run, force }) => {
|
|
1824
1834
|
const d = session.current();
|
|
1825
1835
|
const guard = { force, forced: [] };
|
|
1826
|
-
const
|
|
1827
|
-
|
|
1836
|
+
const forcedOf = () => (guard.forced.length ? { forced: [...new Set(guard.forced)] } : {});
|
|
1837
|
+
if (!to_page_id) {
|
|
1838
|
+
if (to_site_id || parent_id || index !== undefined) {
|
|
1839
|
+
throw new Error('sbuilder: to_site_id, parent_id and index go with to_page_id — the page the copy lands on.');
|
|
1840
|
+
}
|
|
1841
|
+
const { patches, ids } = duplicateNode(d, id, guard);
|
|
1842
|
+
if (dry_run !== false) {
|
|
1843
|
+
const refuse = session.wouldRefuse(patches);
|
|
1844
|
+
return text({ dry_run: true, would_copy: ids.length, ...(refuse ? { would_refuse: refuse } : {}), ...forcedOf() });
|
|
1845
|
+
}
|
|
1846
|
+
await session.applyAndSave(patches);
|
|
1847
|
+
return text({ duplicated: id, into: ids[0], nodes: ids.length, rev: d.rev, ...forcedOf() });
|
|
1848
|
+
}
|
|
1849
|
+
// ONTO ANOTHER PAGE — the editor's paste (`useCanvasClipboard.ts`).
|
|
1850
|
+
const from = session.location();
|
|
1851
|
+
const toSite = to_site_id ?? from.siteId;
|
|
1852
|
+
const crossSite = toSite !== from.siteId;
|
|
1853
|
+
if (!crossSite && to_page_id === from.pageId) {
|
|
1854
|
+
throw new Error('sbuilder: that is the open page — omit to_page_id to duplicate in place.');
|
|
1855
|
+
}
|
|
1856
|
+
if (crossSite && ctx.apiKey) {
|
|
1857
|
+
// A key belongs to exactly ONE site, and every /api/sites route prefers it.
|
|
1858
|
+
throw new Error('sbuilder: a cross-site paste needs a session login — an API key (SB_TOKEN) opens one site only. ' +
|
|
1859
|
+
'Unset SB_TOKEN and connect with SB_EMAIL/SB_PASSWORD as a member of both sites.');
|
|
1860
|
+
}
|
|
1861
|
+
// A FROZEN copy of the source: opening the target replaces the session's document.
|
|
1862
|
+
let src = PageDoc.from(structuredClone(d.doc));
|
|
1863
|
+
const nodes = subtreeIds(src.doc, id).length;
|
|
1864
|
+
const place = (target) => {
|
|
1865
|
+
const parent = parent_id ?? target.doc.root_node_id;
|
|
1866
|
+
const at = index ?? (parent === target.doc.root_node_id ? middleEnd(target.doc) : Number.MAX_SAFE_INTEGER);
|
|
1867
|
+
return copyNodeInto(src, id, target, parent, at, guard);
|
|
1868
|
+
};
|
|
1869
|
+
// EVERY REFUSAL BEFORE ANYTHING MOVES — the editor's own order
|
|
1870
|
+
// (useCanvasClipboard.ts `canAddNodeTree` before `reconcileClipboard`). The
|
|
1871
|
+
// reconcile copies images into the target library as it answers, and opening
|
|
1872
|
+
// the target replaces the session's page: a paste refused after either leaves
|
|
1873
|
+
// orphaned assets or a caller on the wrong page. Checked on the ORIGINAL
|
|
1874
|
+
// document, so the app-block and overlay checks see real ancestors.
|
|
1875
|
+
const preview = PageDoc.from((await loadSource(ctx, toSite, to_page_id)).document);
|
|
1876
|
+
const planned = place(preview);
|
|
1877
|
+
const refuse = saveRefusal(preview, planned.patches).refusal;
|
|
1828
1878
|
if (dry_run !== false) {
|
|
1829
|
-
|
|
1830
|
-
|
|
1879
|
+
return text({
|
|
1880
|
+
dry_run: true,
|
|
1881
|
+
would_copy: nodes,
|
|
1882
|
+
onto: { site_id: toSite, page_id: to_page_id },
|
|
1883
|
+
...(crossSite ? { cross_site: 'images are copied into the target library; product, menu and page references that cannot travel are blanked and listed' } : {}),
|
|
1884
|
+
...(refuse ? { would_refuse: refuse } : {}),
|
|
1885
|
+
...forcedOf(),
|
|
1886
|
+
});
|
|
1887
|
+
}
|
|
1888
|
+
if (refuse)
|
|
1889
|
+
throw new Error(`sbuilder: ${refuse}`);
|
|
1890
|
+
let reconciled;
|
|
1891
|
+
if (crossSite) {
|
|
1892
|
+
const tree = { rootId: id, nodes: Object.fromEntries(subtreeIds(src.doc, id).map((n) => [n, src.doc.nodes[n]])) };
|
|
1893
|
+
const got = (await request({
|
|
1894
|
+
base: ctx.base,
|
|
1895
|
+
method: 'POST',
|
|
1896
|
+
path: `/api/sites/${encodeURIComponent(toSite)}/clipboard/reconcile`,
|
|
1897
|
+
token: siteToken(ctx),
|
|
1898
|
+
body: { sourceSiteId: from.siteId, tree },
|
|
1899
|
+
fetchImpl: ctx.fetchImpl,
|
|
1900
|
+
}));
|
|
1901
|
+
const r = got.reconcile;
|
|
1902
|
+
if (!r?.tree?.nodes || !r.tree.rootId || !(r.tree.rootId in r.tree.nodes)) {
|
|
1903
|
+
throw new Error('sbuilder: the platform answered the reconcile with no tree — nothing was pasted.');
|
|
1904
|
+
}
|
|
1905
|
+
// A holder root so the copy can read the reconciled subtree as a document.
|
|
1906
|
+
const holder = '__clipboard';
|
|
1907
|
+
const rn = structuredClone(r.tree.nodes);
|
|
1908
|
+
rn[r.tree.rootId].data.parent = holder;
|
|
1909
|
+
src = PageDoc.from({
|
|
1910
|
+
schema_version: 2,
|
|
1911
|
+
root_node_id: holder,
|
|
1912
|
+
nodes: { ...rn, [holder]: { id: holder, data: { type: 'root', parent: null, nodes: [r.tree.rootId] } } },
|
|
1913
|
+
});
|
|
1914
|
+
if (r.tree.rootId !== id)
|
|
1915
|
+
id = r.tree.rootId;
|
|
1916
|
+
reconciled = { copiedAssets: r.copiedAssets ?? 0, droppedRefs: r.droppedRefs ?? [] };
|
|
1831
1917
|
}
|
|
1918
|
+
await session.open(toSite, to_page_id);
|
|
1919
|
+
const { patches, ids } = place(session.current());
|
|
1832
1920
|
await session.applyAndSave(patches);
|
|
1833
|
-
return text({
|
|
1921
|
+
return text({
|
|
1922
|
+
copied: nodes,
|
|
1923
|
+
into: ids[0],
|
|
1924
|
+
onto: { site_id: toSite, page_id: to_page_id },
|
|
1925
|
+
open_page: `${to_page_id} — the session now edits the page the copy landed on`,
|
|
1926
|
+
...(reconciled ?? {}),
|
|
1927
|
+
...forcedOf(),
|
|
1928
|
+
});
|
|
1834
1929
|
});
|
|
1835
1930
|
server.registerTool('sb_templates', {
|
|
1836
1931
|
description: "The store's saved section templates — designed sections a person starts from rather " +
|
package/dist/tools/store.js
CHANGED
|
@@ -577,6 +577,7 @@ export function registerStoreTools(server, ctx, session) {
|
|
|
577
577
|
.optional()
|
|
578
578
|
.describe('action:"menu" — the menu node; "global_promote"/"template_save" — the section'),
|
|
579
579
|
global_kind: z.enum(['header', 'footer', 'custom']).optional(),
|
|
580
|
+
template_id: z.string().optional().describe('action:"template_save" — overwrite this template\'s content'),
|
|
580
581
|
menu_id: z.string().optional(),
|
|
581
582
|
kind: z
|
|
582
583
|
.enum(['popup', 'quickview'])
|
|
@@ -608,7 +609,7 @@ export function registerStoreTools(server, ctx, session) {
|
|
|
608
609
|
dry_run: z.boolean().optional(),
|
|
609
610
|
},
|
|
610
611
|
annotations: { readOnlyHint: false, destructiveHint: false, openWorldHint: true },
|
|
611
|
-
}, async ({ action, site_id: given, language, page_name, headline, template, name, footer, node_id, menu_id, kind, overlay_id, list_id, app_key, global_id, global_kind, relocalize, settings, dry_run, }) => {
|
|
612
|
+
}, async ({ action, site_id: given, language, page_name, headline, template, name, footer, node_id, menu_id, kind, overlay_id, list_id, app_key, global_id, global_kind, template_id, relocalize, settings, dry_run, }) => {
|
|
612
613
|
const siteId = siteFor(ctx, given);
|
|
613
614
|
if (action === 'global_attach' || action === 'global_detach') {
|
|
614
615
|
// THE COMPOSED STAMP IS NEVER THE CALLER'S TO WRITE. A page references
|
|
@@ -625,7 +626,7 @@ export function registerStoreTools(server, ctx, session) {
|
|
|
625
626
|
return text(await promoteGlobal(ctx, session, siteId, node_id, global_kind ?? 'custom', name, { dryRun: dry_run !== false }));
|
|
626
627
|
}
|
|
627
628
|
if (action === 'template_save') {
|
|
628
|
-
return text(await saveTemplate(ctx, session, siteId, node_id, name, { dryRun: dry_run !== false }));
|
|
629
|
+
return text(await saveTemplate(ctx, session, siteId, node_id, name, { dryRun: dry_run !== false, templateId: template_id }));
|
|
629
630
|
}
|
|
630
631
|
if (action === 'app') {
|
|
631
632
|
if (!app_key) {
|
package/dist/transport/media.js
CHANGED
|
@@ -47,6 +47,12 @@ const TYPE_BY_EXT = {
|
|
|
47
47
|
'.mp4': 'video/mp4',
|
|
48
48
|
'.webm': 'video/webm',
|
|
49
49
|
'.mov': 'video/quicktime',
|
|
50
|
+
// A site's own font files (`server/internal/media/ingest.go fontExtensions`) —
|
|
51
|
+
// the platform gates fonts on the extension and stores these canonical types.
|
|
52
|
+
'.woff2': 'font/woff2',
|
|
53
|
+
'.woff': 'font/woff',
|
|
54
|
+
'.ttf': 'font/ttf',
|
|
55
|
+
'.otf': 'font/otf',
|
|
50
56
|
};
|
|
51
57
|
function typeForName(name) {
|
|
52
58
|
const dot = name.lastIndexOf('.');
|
|
@@ -158,7 +164,7 @@ export async function uploadMedia(ctx, siteId, source) {
|
|
|
158
164
|
// `application/octet-stream` for a PNG is ordinary, and it is exactly the
|
|
159
165
|
// value the platform refuses — so "the server declared a type" is not the
|
|
160
166
|
// question; "the server declared a type that identifies the file" is.
|
|
161
|
-
const usable = declared.startsWith('image/') || declared.startsWith('video/');
|
|
167
|
+
const usable = declared.startsWith('image/') || declared.startsWith('video/') || declared.startsWith('font/');
|
|
162
168
|
const type = (usable ? declared : '') || typeForName(filename) || declared;
|
|
163
169
|
const form = new FormData();
|
|
164
170
|
// THE BLOB'S TYPE IS THE UPLOAD'S CONTENT TYPE, and omitting it broke every
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "sbuilder-mcp",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.81.1",
|
|
4
4
|
"description": "MCP server that designs and operates a Store Builder site — pages, data, theme and publish — through the platform's own API and live-edit protocol.",
|
|
5
5
|
"mcpName": "io.github.vuluu2k/sbuilder-mcp",
|
|
6
6
|
"type": "module",
|