@xenonbyte/da-vinci-workflow 0.1.21 → 0.1.23
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 +33 -0
- package/README.md +25 -1
- package/README.zh-CN.md +25 -1
- package/docs/constraint-files.md +109 -0
- package/docs/dv-command-reference.md +17 -0
- package/docs/workflow-examples.md +29 -13
- package/docs/workflow-overview.md +2 -0
- package/docs/zh-CN/constraint-files.md +111 -0
- package/docs/zh-CN/dv-command-reference.md +17 -0
- package/docs/zh-CN/workflow-examples.md +29 -13
- package/docs/zh-CN/workflow-overview.md +2 -0
- package/examples/greenfield-spec-markupflow/DA-VINCI.md +9 -0
- package/examples/greenfield-spec-markupflow/README.md +7 -0
- package/examples/greenfield-spec-markupflow/pencil-design.md +5 -0
- package/lib/audit-parsers.js +452 -0
- package/lib/audit.js +102 -448
- package/lib/cli.js +188 -1
- package/lib/fs-safety.js +116 -0
- package/lib/icon-aliases.js +147 -0
- package/lib/icon-search.js +353 -0
- package/lib/icon-sync.js +361 -0
- package/lib/icon-text.js +27 -0
- package/lib/install.js +18 -10
- package/lib/pencil-preflight.js +167 -18
- package/package.json +6 -2
- package/references/artifact-templates.md +24 -0
- package/references/icon-aliases.example.json +12 -0
- package/scripts/fixtures/mock-pencil.js +49 -0
- package/scripts/test-audit-safety.js +92 -0
- package/scripts/test-icon-aliases.js +96 -0
- package/scripts/test-icon-search.js +77 -0
- package/scripts/test-icon-sync.js +178 -0
- package/scripts/test-pen-persistence.js +7 -3
- package/scripts/test-pencil-preflight.js +16 -0
|
@@ -0,0 +1,353 @@
|
|
|
1
|
+
const { normalizeIconText, tokenizeIconText } = require("./icon-text");
|
|
2
|
+
|
|
3
|
+
const MATERIAL_ROUNDED = "Material Symbols Rounded";
|
|
4
|
+
const MATERIAL_OUTLINED = "Material Symbols Outlined";
|
|
5
|
+
const MATERIAL_SHARP = "Material Symbols Sharp";
|
|
6
|
+
|
|
7
|
+
const FAMILY_BY_ID = {
|
|
8
|
+
msr: MATERIAL_ROUNDED,
|
|
9
|
+
mso: MATERIAL_OUTLINED,
|
|
10
|
+
mss: MATERIAL_SHARP,
|
|
11
|
+
lucide: "lucide",
|
|
12
|
+
feather: "feather",
|
|
13
|
+
phosphor: "phosphor"
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
const FAMILY_ORDER = [
|
|
17
|
+
MATERIAL_ROUNDED,
|
|
18
|
+
"lucide",
|
|
19
|
+
"feather",
|
|
20
|
+
"phosphor",
|
|
21
|
+
MATERIAL_OUTLINED,
|
|
22
|
+
MATERIAL_SHARP
|
|
23
|
+
];
|
|
24
|
+
|
|
25
|
+
const FAMILY_ALIAS_TO_SET = new Map([
|
|
26
|
+
["all", new Set(FAMILY_ORDER)],
|
|
27
|
+
["*", new Set(FAMILY_ORDER)],
|
|
28
|
+
["material", new Set([MATERIAL_ROUNDED, MATERIAL_OUTLINED, MATERIAL_SHARP])],
|
|
29
|
+
["material-symbols", new Set([MATERIAL_ROUNDED, MATERIAL_OUTLINED, MATERIAL_SHARP])],
|
|
30
|
+
["material symbols", new Set([MATERIAL_ROUNDED, MATERIAL_OUTLINED, MATERIAL_SHARP])],
|
|
31
|
+
["material symbols rounded", new Set([MATERIAL_ROUNDED])],
|
|
32
|
+
["material symbols outlined", new Set([MATERIAL_OUTLINED])],
|
|
33
|
+
["material symbols sharp", new Set([MATERIAL_SHARP])],
|
|
34
|
+
["rounded", new Set([MATERIAL_ROUNDED])],
|
|
35
|
+
["outlined", new Set([MATERIAL_OUTLINED])],
|
|
36
|
+
["sharp", new Set([MATERIAL_SHARP])],
|
|
37
|
+
["msr", new Set([MATERIAL_ROUNDED])],
|
|
38
|
+
["mso", new Set([MATERIAL_OUTLINED])],
|
|
39
|
+
["mss", new Set([MATERIAL_SHARP])],
|
|
40
|
+
["lucide", new Set(["lucide"])],
|
|
41
|
+
["feather", new Set(["feather"])],
|
|
42
|
+
["phosphor", new Set(["phosphor"])]
|
|
43
|
+
]);
|
|
44
|
+
|
|
45
|
+
function icon(semantic, namesByFamily, tags = []) {
|
|
46
|
+
return {
|
|
47
|
+
semantic,
|
|
48
|
+
namesByFamily,
|
|
49
|
+
tags
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
const ICON_ENTRIES = [
|
|
54
|
+
icon("settings", { msr: "settings", mso: "settings", mss: "settings", lucide: "settings", feather: "settings", phosphor: "gear" }, ["config", "preferences", "gear", "cog", "设置", "配置", "偏好", "齿轮"]),
|
|
55
|
+
icon("sliders", { msr: "tune", mso: "tune", mss: "tune", lucide: "sliders-horizontal", feather: "sliders", phosphor: "sliders-horizontal" }, ["adjust", "filter", "controls", "调节", "筛选", "控制"]),
|
|
56
|
+
icon("search", { msr: "search", mso: "search", mss: "search", lucide: "search", feather: "search", phosphor: "magnifying-glass" }, ["find", "lookup", "搜索", "查找"]),
|
|
57
|
+
icon("home", { msr: "home", mso: "home", mss: "home", lucide: "house", feather: "home", phosphor: "house" }, ["dashboard", "main", "首页", "主页"]),
|
|
58
|
+
icon("lock", { msr: "lock", mso: "lock", mss: "lock", lucide: "lock", feather: "lock", phosphor: "lock" }, ["secure", "security", "加密", "锁定", "安全"]),
|
|
59
|
+
icon("unlock", { msr: "lock_open", mso: "lock_open", mss: "lock_open", lucide: "lock-open", feather: "unlock", phosphor: "lock-open" }, ["open lock", "解锁", "开锁"]),
|
|
60
|
+
icon("key", { msr: "key", mso: "key", mss: "key", lucide: "key-round", feather: "key", phosphor: "key" }, ["passcode", "credential", "密码", "密钥"]),
|
|
61
|
+
icon("shield", { msr: "shield", mso: "shield", mss: "shield", lucide: "shield", feather: "shield", phosphor: "shield" }, ["protect", "defense", "保护", "防护"]),
|
|
62
|
+
icon("shield-check", { msr: "verified_user", mso: "verified_user", mss: "verified_user", lucide: "shield-check", feather: "shield", phosphor: "shield-check" }, ["verified", "trusted", "已验证", "可信"]),
|
|
63
|
+
icon("warning", { msr: "warning", mso: "warning", mss: "warning", lucide: "triangle-alert", feather: "alert-triangle", phosphor: "warning" }, ["alert", "caution", "警告", "提醒"]),
|
|
64
|
+
icon("error", { msr: "error", mso: "error", mss: "error", lucide: "circle-x", feather: "x-circle", phosphor: "x-circle" }, ["danger", "failed", "错误", "失败"]),
|
|
65
|
+
icon("info", { msr: "info", mso: "info", mss: "info", lucide: "info", feather: "info", phosphor: "info" }, ["hint", "about", "信息", "说明"]),
|
|
66
|
+
icon("help", { msr: "help", mso: "help", mss: "help", lucide: "circle-help", feather: "help-circle", phosphor: "question" }, ["question", "faq", "帮助", "问号"]),
|
|
67
|
+
icon("check", { msr: "check", mso: "check", mss: "check", lucide: "check", feather: "check", phosphor: "check" }, ["ok", "done", "confirm", "确认", "完成"]),
|
|
68
|
+
icon("check-circle", { msr: "check_circle", mso: "check_circle", mss: "check_circle", lucide: "circle-check", feather: "check-circle", phosphor: "check-circle" }, ["success", "pass", "成功", "通过"]),
|
|
69
|
+
icon("close", { msr: "close", mso: "close", mss: "close", lucide: "x", feather: "x", phosphor: "x" }, ["dismiss", "cancel", "关闭", "取消"]),
|
|
70
|
+
icon("add", { msr: "add", mso: "add", mss: "add", lucide: "plus", feather: "plus", phosphor: "plus" }, ["create", "new", "新增", "添加"]),
|
|
71
|
+
icon("remove", { msr: "remove", mso: "remove", mss: "remove", lucide: "minus", feather: "minus", phosphor: "minus" }, ["subtract", "删除项", "移除"]),
|
|
72
|
+
icon("edit", { msr: "edit", mso: "edit", mss: "edit", lucide: "pencil", feather: "edit-3", phosphor: "pencil" }, ["modify", "rename", "编辑", "修改"]),
|
|
73
|
+
icon("delete", { msr: "delete", mso: "delete", mss: "delete", lucide: "trash-2", feather: "trash-2", phosphor: "trash" }, ["trash", "remove", "删除", "垃圾桶"]),
|
|
74
|
+
icon("refresh", { msr: "refresh", mso: "refresh", mss: "refresh", lucide: "refresh-cw", feather: "refresh-cw", phosphor: "arrow-clockwise" }, ["reload", "retry", "刷新", "重试"]),
|
|
75
|
+
icon("sync", { msr: "sync", mso: "sync", mss: "sync", lucide: "rotate-cw", feather: "rotate-cw", phosphor: "arrows-clockwise" }, ["synchronize", "同步"]),
|
|
76
|
+
icon("download", { msr: "download", mso: "download", mss: "download", lucide: "download", feather: "download", phosphor: "download" }, ["save", "export", "下载", "导出"]),
|
|
77
|
+
icon("upload", { msr: "upload", mso: "upload", mss: "upload", lucide: "upload", feather: "upload", phosphor: "upload" }, ["import", "发送", "上传", "导入"]),
|
|
78
|
+
icon("cloud-upload", { msr: "cloud_upload", mso: "cloud_upload", mss: "cloud_upload", lucide: "cloud-upload", feather: "upload-cloud", phosphor: "cloud-arrow-up" }, ["backup", "云上传", "备份"]),
|
|
79
|
+
icon("cloud-download", { msr: "cloud_download", mso: "cloud_download", mss: "cloud_download", lucide: "cloud-download", feather: "download-cloud", phosphor: "cloud-arrow-down" }, ["sync down", "云下载"]),
|
|
80
|
+
icon("folder", { msr: "folder", mso: "folder", mss: "folder", lucide: "folder", feather: "folder", phosphor: "folder" }, ["directory", "文件夹"]),
|
|
81
|
+
icon("file", { msr: "description", mso: "description", mss: "description", lucide: "file", feather: "file", phosphor: "file" }, ["document", "文档", "文件"]),
|
|
82
|
+
icon("file-text", { msr: "article", mso: "article", mss: "article", lucide: "file-text", feather: "file-text", phosphor: "file-text" }, ["note", "文本", "说明文档"]),
|
|
83
|
+
icon("image", { msr: "image", mso: "image", mss: "image", lucide: "image", feather: "image", phosphor: "image" }, ["photo", "picture", "图片", "照片"]),
|
|
84
|
+
icon("camera", { msr: "photo_camera", mso: "photo_camera", mss: "photo_camera", lucide: "camera", feather: "camera", phosphor: "camera" }, ["capture", "拍照", "相机"]),
|
|
85
|
+
icon("bell", { msr: "notifications", mso: "notifications", mss: "notifications", lucide: "bell", feather: "bell", phosphor: "bell" }, ["notification", "提醒", "通知"]),
|
|
86
|
+
icon("mail", { msr: "mail", mso: "mail", mss: "mail", lucide: "mail", feather: "mail", phosphor: "envelope" }, ["email", "邮箱", "邮件"]),
|
|
87
|
+
icon("message", { msr: "chat", mso: "chat", mss: "chat", lucide: "message-circle", feather: "message-circle", phosphor: "chat-circle" }, ["chat", "sms", "消息", "聊天"]),
|
|
88
|
+
icon("phone", { msr: "phone", mso: "phone", mss: "phone", lucide: "phone", feather: "phone", phosphor: "phone" }, ["call", "拨号", "电话"]),
|
|
89
|
+
icon("location", { msr: "location_on", mso: "location_on", mss: "location_on", lucide: "map-pin", feather: "map-pin", phosphor: "map-pin" }, ["address", "gps", "定位", "位置"]),
|
|
90
|
+
icon("navigation", { msr: "navigation", mso: "navigation", mss: "navigation", lucide: "navigation", feather: "navigation", phosphor: "navigation-arrow" }, ["direction", "route", "导航", "方向"]),
|
|
91
|
+
icon("calendar", { msr: "calendar_today", mso: "calendar_today", mss: "calendar_today", lucide: "calendar", feather: "calendar", phosphor: "calendar" }, ["date", "schedule", "日期", "日历"]),
|
|
92
|
+
icon("clock", { msr: "schedule", mso: "schedule", mss: "schedule", lucide: "clock-3", feather: "clock", phosphor: "clock" }, ["time", "hour", "时间", "时钟"]),
|
|
93
|
+
icon("timer", { msr: "timer", mso: "timer", mss: "timer", lucide: "timer", feather: "clock", phosphor: "timer" }, ["countdown", "倒计时", "计时器"]),
|
|
94
|
+
icon("eye", { msr: "visibility", mso: "visibility", mss: "visibility", lucide: "eye", feather: "eye", phosphor: "eye" }, ["show", "preview", "可见", "显示"]),
|
|
95
|
+
icon("eye-off", { msr: "visibility_off", mso: "visibility_off", mss: "visibility_off", lucide: "eye-off", feather: "eye-off", phosphor: "eye-slash" }, ["hide", "private", "隐藏", "不可见"]),
|
|
96
|
+
icon("fingerprint", { msr: "fingerprint", mso: "fingerprint", mss: "fingerprint", lucide: "fingerprint", feather: "circle", phosphor: "fingerprint" }, ["biometric", "touch id", "指纹", "生物识别"]),
|
|
97
|
+
icon("qr", { msr: "qr_code_scanner", mso: "qr_code_scanner", mss: "qr_code_scanner", lucide: "qr-code", feather: "maximize-2", phosphor: "qr-code" }, ["scan", "扫码", "二维码"]),
|
|
98
|
+
icon("copy", { msr: "content_copy", mso: "content_copy", mss: "content_copy", lucide: "copy", feather: "copy", phosphor: "copy" }, ["duplicate", "复制"]),
|
|
99
|
+
icon("share", { msr: "share", mso: "share", mss: "share", lucide: "share-2", feather: "share-2", phosphor: "share-network" }, ["send", "分享"]),
|
|
100
|
+
icon("star", { msr: "star", mso: "star", mss: "star", lucide: "star", feather: "star", phosphor: "star" }, ["favorite", "rate", "收藏", "星标"]),
|
|
101
|
+
icon("heart", { msr: "favorite", mso: "favorite", mss: "favorite", lucide: "heart", feather: "heart", phosphor: "heart" }, ["like", "love", "喜欢", "点赞"]),
|
|
102
|
+
icon("bookmark", { msr: "bookmark", mso: "bookmark", mss: "bookmark", lucide: "bookmark", feather: "bookmark", phosphor: "bookmark-simple" }, ["save later", "书签"]),
|
|
103
|
+
icon("user", { msr: "person", mso: "person", mss: "person", lucide: "user", feather: "user", phosphor: "user" }, ["profile", "account", "个人", "用户", "账户"]),
|
|
104
|
+
icon("users", { msr: "groups", mso: "groups", mss: "groups", lucide: "users", feather: "users", phosphor: "users" }, ["team", "group", "团队", "群组"]),
|
|
105
|
+
icon("login", { msr: "login", mso: "login", mss: "login", lucide: "log-in", feather: "log-in", phosphor: "sign-in" }, ["sign in", "enter", "登录"]),
|
|
106
|
+
icon("logout", { msr: "logout", mso: "logout", mss: "logout", lucide: "log-out", feather: "log-out", phosphor: "sign-out" }, ["sign out", "exit", "退出", "注销"]),
|
|
107
|
+
icon("menu", { msr: "menu", mso: "menu", mss: "menu", lucide: "menu", feather: "menu", phosphor: "list" }, ["hamburger", "导航菜单", "菜单"]),
|
|
108
|
+
icon("more", { msr: "more_horiz", mso: "more_horiz", mss: "more_horiz", lucide: "ellipsis", feather: "more-horizontal", phosphor: "dots-three-outline" }, ["ellipsis", "overflow", "更多"]),
|
|
109
|
+
icon("chevron-left", { msr: "chevron_left", mso: "chevron_left", mss: "chevron_left", lucide: "chevron-left", feather: "chevron-left", phosphor: "caret-left" }, ["back", "prev", "返回", "左箭头"]),
|
|
110
|
+
icon("chevron-right", { msr: "chevron_right", mso: "chevron_right", mss: "chevron_right", lucide: "chevron-right", feather: "chevron-right", phosphor: "caret-right" }, ["next", "forward", "前进", "右箭头"]),
|
|
111
|
+
icon("chevron-up", { msr: "expand_less", mso: "expand_less", mss: "expand_less", lucide: "chevron-up", feather: "chevron-up", phosphor: "caret-up" }, ["up", "collapse", "上箭头"]),
|
|
112
|
+
icon("chevron-down", { msr: "expand_more", mso: "expand_more", mss: "expand_more", lucide: "chevron-down", feather: "chevron-down", phosphor: "caret-down" }, ["down", "expand", "下箭头"]),
|
|
113
|
+
icon("play", { msr: "play_arrow", mso: "play_arrow", mss: "play_arrow", lucide: "play", feather: "play", phosphor: "play" }, ["start", "播放"]),
|
|
114
|
+
icon("pause", { msr: "pause", mso: "pause", mss: "pause", lucide: "pause", feather: "pause", phosphor: "pause" }, ["stop briefly", "暂停"]),
|
|
115
|
+
icon("mic", { msr: "mic", mso: "mic", mss: "mic", lucide: "mic", feather: "mic", phosphor: "microphone" }, ["voice", "record", "语音", "麦克风"]),
|
|
116
|
+
icon("volume", { msr: "volume_up", mso: "volume_up", mss: "volume_up", lucide: "volume-2", feather: "volume-2", phosphor: "speaker-high" }, ["sound", "audio", "声音", "音量"]),
|
|
117
|
+
icon("wifi", { msr: "wifi", mso: "wifi", mss: "wifi", lucide: "wifi", feather: "wifi", phosphor: "wifi-high" }, ["network", "internet", "网络", "无线"]),
|
|
118
|
+
icon("bluetooth", { msr: "bluetooth", mso: "bluetooth", mss: "bluetooth", lucide: "bluetooth", feather: "bluetooth", phosphor: "bluetooth" }, ["bt", "蓝牙"]),
|
|
119
|
+
icon("battery", { msr: "battery_full", mso: "battery_full", mss: "battery_full", lucide: "battery-full", feather: "battery", phosphor: "battery-full" }, ["power", "电池", "电量"]),
|
|
120
|
+
icon("sun", { msr: "light_mode", mso: "light_mode", mss: "light_mode", lucide: "sun", feather: "sun", phosphor: "sun" }, ["day", "light", "白天", "亮色"]),
|
|
121
|
+
icon("moon", { msr: "dark_mode", mso: "dark_mode", mss: "dark_mode", lucide: "moon", feather: "moon", phosphor: "moon" }, ["night", "dark", "夜间", "暗色"]),
|
|
122
|
+
icon("cart", { msr: "shopping_cart", mso: "shopping_cart", mss: "shopping_cart", lucide: "shopping-cart", feather: "shopping-cart", phosphor: "shopping-cart" }, ["shop", "buy", "购物车"]),
|
|
123
|
+
icon("credit-card", { msr: "credit_card", mso: "credit_card", mss: "credit_card", lucide: "credit-card", feather: "credit-card", phosphor: "credit-card" }, ["payment", "billing", "支付", "账单"]),
|
|
124
|
+
icon("vault", { msr: "inventory_2", mso: "inventory_2", mss: "inventory_2", lucide: "vault", feather: "archive", phosphor: "vault" }, ["safe box", "secure storage", "保险箱", "金库"])
|
|
125
|
+
];
|
|
126
|
+
|
|
127
|
+
const normalize = normalizeIconText;
|
|
128
|
+
const tokenize = tokenizeIconText;
|
|
129
|
+
|
|
130
|
+
function unique(values) {
|
|
131
|
+
return Array.from(new Set(values.filter(Boolean)));
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
function createCandidate(entry, family, name) {
|
|
135
|
+
const semanticTokens = tokenize(entry.semantic);
|
|
136
|
+
const nameTokens = tokenize(name);
|
|
137
|
+
const tagTokens = entry.tags.flatMap((tag) => tokenize(tag));
|
|
138
|
+
const searchableTokens = unique([...semanticTokens, ...nameTokens, ...tagTokens]);
|
|
139
|
+
return {
|
|
140
|
+
family,
|
|
141
|
+
name,
|
|
142
|
+
semantic: entry.semantic,
|
|
143
|
+
tags: unique(entry.tags),
|
|
144
|
+
searchableTokens,
|
|
145
|
+
searchableText: searchableTokens.join(" ")
|
|
146
|
+
};
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
const ICON_CANDIDATES = ICON_ENTRIES.flatMap((entry) =>
|
|
150
|
+
Object.entries(entry.namesByFamily).flatMap(([familyId, name]) => {
|
|
151
|
+
const family = FAMILY_BY_ID[familyId];
|
|
152
|
+
if (!family || !name) {
|
|
153
|
+
return [];
|
|
154
|
+
}
|
|
155
|
+
return [createCandidate(entry, family, name)];
|
|
156
|
+
})
|
|
157
|
+
);
|
|
158
|
+
|
|
159
|
+
function buildCandidatesFromCatalogRecords(records = []) {
|
|
160
|
+
if (!Array.isArray(records)) {
|
|
161
|
+
return [];
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
return records
|
|
165
|
+
.map((record) => {
|
|
166
|
+
if (!record || typeof record !== "object") {
|
|
167
|
+
return null;
|
|
168
|
+
}
|
|
169
|
+
const family = String(record.family || "").trim();
|
|
170
|
+
const name = String(record.name || "").trim();
|
|
171
|
+
if (!family || !name) {
|
|
172
|
+
return null;
|
|
173
|
+
}
|
|
174
|
+
const semantic = String(record.semantic || name).trim();
|
|
175
|
+
const tags = Array.isArray(record.tags) ? record.tags.map((tag) => String(tag)) : [];
|
|
176
|
+
return createCandidate(
|
|
177
|
+
{
|
|
178
|
+
semantic,
|
|
179
|
+
tags
|
|
180
|
+
},
|
|
181
|
+
family,
|
|
182
|
+
name
|
|
183
|
+
);
|
|
184
|
+
})
|
|
185
|
+
.filter(Boolean);
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
function mergeCandidates(dynamicCandidates = []) {
|
|
189
|
+
const merged = new Map();
|
|
190
|
+
|
|
191
|
+
for (const candidate of ICON_CANDIDATES) {
|
|
192
|
+
merged.set(`${candidate.family}::${candidate.name}`, candidate);
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
for (const candidate of dynamicCandidates) {
|
|
196
|
+
const key = `${candidate.family}::${candidate.name}`;
|
|
197
|
+
if (!merged.has(key)) {
|
|
198
|
+
merged.set(key, candidate);
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
return Array.from(merged.values());
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
function resolveFamilySet(filter) {
|
|
206
|
+
if (!filter) {
|
|
207
|
+
return new Set(FAMILY_ORDER);
|
|
208
|
+
}
|
|
209
|
+
const normalized = normalize(filter);
|
|
210
|
+
const resolved = FAMILY_ALIAS_TO_SET.get(normalized);
|
|
211
|
+
if (!resolved) {
|
|
212
|
+
const allowed = Array.from(FAMILY_ALIAS_TO_SET.keys()).sort().join(", ");
|
|
213
|
+
throw new Error(`Unknown icon family filter: ${filter}. Allowed values include: ${allowed}`);
|
|
214
|
+
}
|
|
215
|
+
return new Set(resolved);
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
function buildIconNodeSuggestion(match) {
|
|
219
|
+
const suggestion = {
|
|
220
|
+
type: "icon_font",
|
|
221
|
+
iconFontFamily: match.family,
|
|
222
|
+
iconFontName: match.name,
|
|
223
|
+
width: 24,
|
|
224
|
+
height: 24
|
|
225
|
+
};
|
|
226
|
+
if (match.family.startsWith("Material Symbols")) {
|
|
227
|
+
suggestion.weight = 700;
|
|
228
|
+
}
|
|
229
|
+
return suggestion;
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
function scoreMatch(candidate, queryText, queryTokens) {
|
|
233
|
+
let score = 0;
|
|
234
|
+
const nameText = normalize(candidate.name);
|
|
235
|
+
const semanticText = normalize(candidate.semantic);
|
|
236
|
+
|
|
237
|
+
if (queryText && (queryText === nameText || queryText === semanticText)) {
|
|
238
|
+
score += 120;
|
|
239
|
+
}
|
|
240
|
+
if (queryText && (nameText.startsWith(queryText) || semanticText.startsWith(queryText))) {
|
|
241
|
+
score += 40;
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
for (const token of queryTokens) {
|
|
245
|
+
if (candidate.searchableTokens.includes(token)) {
|
|
246
|
+
score += 30;
|
|
247
|
+
continue;
|
|
248
|
+
}
|
|
249
|
+
if (nameText.includes(token) || semanticText.includes(token)) {
|
|
250
|
+
score += 16;
|
|
251
|
+
continue;
|
|
252
|
+
}
|
|
253
|
+
if (candidate.searchableText.includes(token)) {
|
|
254
|
+
score += 8;
|
|
255
|
+
continue;
|
|
256
|
+
}
|
|
257
|
+
score -= 8;
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
if (candidate.family === MATERIAL_ROUNDED) {
|
|
261
|
+
score += 4;
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
return score;
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
function compareMatches(a, b) {
|
|
268
|
+
if (a.score !== b.score) {
|
|
269
|
+
return b.score - a.score;
|
|
270
|
+
}
|
|
271
|
+
const familyIndexA = FAMILY_ORDER.indexOf(a.family);
|
|
272
|
+
const familyIndexB = FAMILY_ORDER.indexOf(b.family);
|
|
273
|
+
if (familyIndexA !== familyIndexB) {
|
|
274
|
+
return familyIndexA - familyIndexB;
|
|
275
|
+
}
|
|
276
|
+
return a.name.localeCompare(b.name);
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
function searchIconLibrary(query, options = {}) {
|
|
280
|
+
const queryText = normalize(query);
|
|
281
|
+
const queryTokens = unique([
|
|
282
|
+
...tokenize(queryText),
|
|
283
|
+
...tokenize((options.extraQueryTokens || []).join(" "))
|
|
284
|
+
]);
|
|
285
|
+
if (!queryText) {
|
|
286
|
+
throw new Error("`icon-search` requires a non-empty query.");
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
const familySet = resolveFamilySet(options.family || "all");
|
|
290
|
+
const top = Number.isFinite(Number(options.top)) ? Math.max(1, Math.min(50, Number(options.top))) : 8;
|
|
291
|
+
const catalogCandidates = buildCandidatesFromCatalogRecords(options.catalog || []);
|
|
292
|
+
const candidatePool = mergeCandidates(catalogCandidates);
|
|
293
|
+
|
|
294
|
+
const ranked = candidatePool.filter((candidate) => familySet.has(candidate.family))
|
|
295
|
+
.map((candidate) => ({
|
|
296
|
+
...candidate,
|
|
297
|
+
score: scoreMatch(candidate, queryText, queryTokens)
|
|
298
|
+
}))
|
|
299
|
+
.filter((match) => match.score > 0)
|
|
300
|
+
.sort(compareMatches);
|
|
301
|
+
|
|
302
|
+
const topMatches = ranked.slice(0, top).map((match) => ({
|
|
303
|
+
family: match.family,
|
|
304
|
+
name: match.name,
|
|
305
|
+
semantic: match.semantic,
|
|
306
|
+
tags: match.tags,
|
|
307
|
+
score: match.score,
|
|
308
|
+
node: buildIconNodeSuggestion(match)
|
|
309
|
+
}));
|
|
310
|
+
|
|
311
|
+
return {
|
|
312
|
+
query: String(query),
|
|
313
|
+
normalizedQuery: queryText,
|
|
314
|
+
familyFilter: options.family || "all",
|
|
315
|
+
top,
|
|
316
|
+
totalMatches: ranked.length,
|
|
317
|
+
matches: topMatches
|
|
318
|
+
};
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
function formatIconSearchReport(result) {
|
|
322
|
+
const lines = [
|
|
323
|
+
"Icon Search",
|
|
324
|
+
`Query: ${result.query}`,
|
|
325
|
+
`Family filter: ${result.familyFilter}`,
|
|
326
|
+
`Matches: ${result.matches.length}/${result.totalMatches}`
|
|
327
|
+
];
|
|
328
|
+
|
|
329
|
+
if (result.matches.length === 0) {
|
|
330
|
+
lines.push("No candidates scored above threshold. Try broader terms or a different family filter.");
|
|
331
|
+
return lines.join("\n");
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
for (let index = 0; index < result.matches.length; index += 1) {
|
|
335
|
+
const match = result.matches[index];
|
|
336
|
+
lines.push(`${index + 1}. ${match.family} / ${match.name} (score ${match.score})`);
|
|
337
|
+
lines.push(` semantic: ${match.semantic}`);
|
|
338
|
+
lines.push(` tags: ${match.tags.slice(0, 8).join(", ")}`);
|
|
339
|
+
lines.push(` node: ${JSON.stringify(match.node)}`);
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
return lines.join("\n");
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
module.exports = {
|
|
346
|
+
MATERIAL_ROUNDED,
|
|
347
|
+
MATERIAL_OUTLINED,
|
|
348
|
+
MATERIAL_SHARP,
|
|
349
|
+
FAMILY_ORDER,
|
|
350
|
+
buildCandidatesFromCatalogRecords,
|
|
351
|
+
searchIconLibrary,
|
|
352
|
+
formatIconSearchReport
|
|
353
|
+
};
|