@keyflow2/keyflow-kit-store 0.1.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/LICENSE +202 -0
- package/icons/icon-128.png +0 -0
- package/icons/icon-256.png +0 -0
- package/icons/icon-48.png +0 -0
- package/icons/icon-64.png +0 -0
- package/icons/icon-96.png +0 -0
- package/manifest.json +106 -0
- package/package.json +25 -0
- package/ui/app/index.html +423 -0
- package/ui/app/main.js +968 -0
- package/ui/app/styles.css +969 -0
package/ui/app/main.js
ADDED
|
@@ -0,0 +1,968 @@
|
|
|
1
|
+
(() => {
|
|
2
|
+
"use strict";
|
|
3
|
+
|
|
4
|
+
const kitId = "kit-store";
|
|
5
|
+
const surface = "panel";
|
|
6
|
+
const DEFAULT_CATALOG_SOURCE = "npm:@keyflow2/keyflow-kit-catalog";
|
|
7
|
+
const CATEGORY_LABELS = Object.freeze({
|
|
8
|
+
ai: "AI",
|
|
9
|
+
chat: "聊天",
|
|
10
|
+
clipboard: "剪贴板",
|
|
11
|
+
download: "下载",
|
|
12
|
+
file: "文件",
|
|
13
|
+
files: "文件",
|
|
14
|
+
image: "图片",
|
|
15
|
+
install: "安装",
|
|
16
|
+
local: "本地",
|
|
17
|
+
network: "联网",
|
|
18
|
+
ocr: "识图",
|
|
19
|
+
paste: "粘贴",
|
|
20
|
+
productivity: "效率",
|
|
21
|
+
reply: "回复",
|
|
22
|
+
rewrite: "改写",
|
|
23
|
+
snippet: "短语",
|
|
24
|
+
store: "商店",
|
|
25
|
+
summarize: "总结",
|
|
26
|
+
system: "系统",
|
|
27
|
+
template: "模板",
|
|
28
|
+
tone: "语气",
|
|
29
|
+
tool: "工具",
|
|
30
|
+
tools: "工具",
|
|
31
|
+
translate: "翻译",
|
|
32
|
+
translation: "翻译",
|
|
33
|
+
upload: "上传",
|
|
34
|
+
wechat: "微信",
|
|
35
|
+
writing: "写作"
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
const featuredKitIds = new Set(["ai-smart-write", "clipboard-plus", "chat-auto-reply", "quick-phrases"]);
|
|
39
|
+
|
|
40
|
+
const preview = {
|
|
41
|
+
sources: [],
|
|
42
|
+
packages: [],
|
|
43
|
+
installed: []
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
let kit = null;
|
|
47
|
+
let toastTimer = null;
|
|
48
|
+
let updateToastShown = false;
|
|
49
|
+
|
|
50
|
+
function safeText(value) {
|
|
51
|
+
return typeof value === "string" ? value.trim() : "";
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
function normalizeSemver(value) {
|
|
55
|
+
const raw = safeText(value);
|
|
56
|
+
if (!raw) return "";
|
|
57
|
+
if (raw.startsWith("v") || raw.startsWith("V")) return raw.slice(1);
|
|
58
|
+
return raw;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
function parseSemver(value) {
|
|
62
|
+
const raw = normalizeSemver(value);
|
|
63
|
+
const match = /^(\d+)\.(\d+)\.(\d+)(?:-([0-9A-Za-z.-]+))?(?:\+([0-9A-Za-z.-]+))?$/.exec(raw);
|
|
64
|
+
if (!match) return null;
|
|
65
|
+
const major = Number(match[1]);
|
|
66
|
+
const minor = Number(match[2]);
|
|
67
|
+
const patch = Number(match[3]);
|
|
68
|
+
if (!Number.isFinite(major) || !Number.isFinite(minor) || !Number.isFinite(patch)) return null;
|
|
69
|
+
const prerelease = match[4] ? match[4].split(".") : [];
|
|
70
|
+
return { major, minor, patch, prerelease };
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
function isNumericIdentifier(value) {
|
|
74
|
+
return /^[0-9]+$/.test(value);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
function comparePrereleaseIdentifiers(a, b) {
|
|
78
|
+
if (a === b) return 0;
|
|
79
|
+
const aNum = isNumericIdentifier(a);
|
|
80
|
+
const bNum = isNumericIdentifier(b);
|
|
81
|
+
if (aNum && bNum) return Number(a) - Number(b);
|
|
82
|
+
if (aNum && !bNum) return -1;
|
|
83
|
+
if (!aNum && bNum) return 1;
|
|
84
|
+
return a < b ? -1 : 1;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
function compareSemver(aValue, bValue) {
|
|
88
|
+
const a = parseSemver(aValue);
|
|
89
|
+
const b = parseSemver(bValue);
|
|
90
|
+
if (!a || !b) return 0;
|
|
91
|
+
if (a.major !== b.major) return a.major - b.major;
|
|
92
|
+
if (a.minor !== b.minor) return a.minor - b.minor;
|
|
93
|
+
if (a.patch !== b.patch) return a.patch - b.patch;
|
|
94
|
+
|
|
95
|
+
const aPre = a.prerelease;
|
|
96
|
+
const bPre = b.prerelease;
|
|
97
|
+
if (aPre.length === 0 && bPre.length === 0) return 0;
|
|
98
|
+
if (aPre.length === 0) return 1; // release > prerelease
|
|
99
|
+
if (bPre.length === 0) return -1;
|
|
100
|
+
|
|
101
|
+
const limit = Math.max(aPre.length, bPre.length);
|
|
102
|
+
for (let i = 0; i < limit; i++) {
|
|
103
|
+
const left = aPre[i];
|
|
104
|
+
const right = bPre[i];
|
|
105
|
+
if (left == null) return -1;
|
|
106
|
+
if (right == null) return 1;
|
|
107
|
+
const diff = comparePrereleaseIdentifiers(left, right);
|
|
108
|
+
if (diff !== 0) return diff;
|
|
109
|
+
}
|
|
110
|
+
return 0;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
function parseNpmSpecVersion(value) {
|
|
114
|
+
const raw = safeText(value);
|
|
115
|
+
if (!raw) return "";
|
|
116
|
+
if (!/^npm:/i.test(raw)) return "";
|
|
117
|
+
const rest = raw.slice(4);
|
|
118
|
+
const at = rest.lastIndexOf("@");
|
|
119
|
+
if (at <= 0 || at === rest.length - 1) return "";
|
|
120
|
+
return safeText(rest.slice(at + 1));
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
function getInstalledVersion(record) {
|
|
124
|
+
if (!record || typeof record !== "object") return "";
|
|
125
|
+
const direct = safeText(record.version ?? record.manifest?.version ?? record.pkg?.version ?? "");
|
|
126
|
+
if (direct) return direct;
|
|
127
|
+
return parseNpmSpecVersion(record.installKey ?? record.source ?? record.installSource ?? "");
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
function getCatalogVersion(pkg) {
|
|
131
|
+
if (!pkg || typeof pkg !== "object") return "";
|
|
132
|
+
return safeText(pkg.version ?? pkg?.npm?.version ?? "");
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
function buildLatestPackageByKitId(packages) {
|
|
136
|
+
const list = Array.isArray(packages) ? packages : [];
|
|
137
|
+
const latest = new Map();
|
|
138
|
+
for (const pkg of list) {
|
|
139
|
+
const id = normalizeKitId(pkg?.kitId ?? pkg?.id);
|
|
140
|
+
if (!id) continue;
|
|
141
|
+
|
|
142
|
+
const existing = latest.get(id);
|
|
143
|
+
if (!existing) {
|
|
144
|
+
latest.set(id, pkg);
|
|
145
|
+
continue;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
const existingVersion = getCatalogVersion(existing);
|
|
149
|
+
const nextVersion = getCatalogVersion(pkg);
|
|
150
|
+
if (!existingVersion || !nextVersion) continue;
|
|
151
|
+
if (compareSemver(nextVersion, existingVersion) > 0) {
|
|
152
|
+
latest.set(id, pkg);
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
return latest;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
function computeUpdateCount(installed, packages) {
|
|
159
|
+
const installedList = Array.isArray(installed) ? installed : [];
|
|
160
|
+
const latestById = buildLatestPackageByKitId(packages);
|
|
161
|
+
let count = 0;
|
|
162
|
+
for (const record of installedList) {
|
|
163
|
+
const id = normalizeKitId(record?.kitId ?? record?.id);
|
|
164
|
+
if (!id) continue;
|
|
165
|
+
const pkg = latestById.get(id) ?? null;
|
|
166
|
+
if (!pkg) continue;
|
|
167
|
+
const installedVersion = getInstalledVersion(record);
|
|
168
|
+
const latestVersion = getCatalogVersion(pkg);
|
|
169
|
+
if (!installedVersion || !latestVersion) continue;
|
|
170
|
+
if (compareSemver(installedVersion, latestVersion) < 0) count++;
|
|
171
|
+
}
|
|
172
|
+
return count;
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
function normalizeKitId(value) {
|
|
176
|
+
const text = safeText(value);
|
|
177
|
+
return text || null;
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
function normalizeTextList(values) {
|
|
181
|
+
if (!Array.isArray(values)) return [];
|
|
182
|
+
const seen = new Set();
|
|
183
|
+
const list = [];
|
|
184
|
+
for (const value of values) {
|
|
185
|
+
const text = safeText(value);
|
|
186
|
+
if (!text) continue;
|
|
187
|
+
const key = text.toLowerCase();
|
|
188
|
+
if (seen.has(key)) continue;
|
|
189
|
+
seen.add(key);
|
|
190
|
+
list.push(text);
|
|
191
|
+
}
|
|
192
|
+
return list;
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
function humanizeToken(value) {
|
|
196
|
+
const raw = safeText(value);
|
|
197
|
+
if (!raw) return "";
|
|
198
|
+
if (/[^\x00-\x7F]/.test(raw)) return raw;
|
|
199
|
+
return raw
|
|
200
|
+
.split(/[._-]+/)
|
|
201
|
+
.filter(Boolean)
|
|
202
|
+
.map((part) => (part.length <= 3 ? part.toUpperCase() : `${part.slice(0, 1).toUpperCase()}${part.slice(1)}`))
|
|
203
|
+
.join(" ");
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
function displayTagLabel(value) {
|
|
207
|
+
const raw = safeText(value);
|
|
208
|
+
if (!raw) return "";
|
|
209
|
+
return CATEGORY_LABELS[raw.toLowerCase()] ?? humanizeToken(raw);
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
function formatBytes(bytes) {
|
|
213
|
+
const value = typeof bytes === "number" ? bytes : typeof bytes === "string" ? Number(bytes) : NaN;
|
|
214
|
+
if (!Number.isFinite(value) || value <= 0) return "";
|
|
215
|
+
const kb = 1024;
|
|
216
|
+
const mb = kb * 1024;
|
|
217
|
+
if (value >= mb) return `${(value / mb).toFixed(1)}M`;
|
|
218
|
+
if (value >= kb) return `${(value / kb).toFixed(1)}K`;
|
|
219
|
+
return `${Math.round(value)}B`;
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
function buildLocalAssetUrl(assetPath) {
|
|
223
|
+
const raw = safeText(assetPath).replace(/^\/+/, "");
|
|
224
|
+
if (!raw) return null;
|
|
225
|
+
return `https://function-kit.local/assets/${raw}`;
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
function resolveIconAssetPath(kitRecord) {
|
|
229
|
+
const preferred = kitRecord?.preferredIconAssetPath ?? kitRecord?.icon ?? null;
|
|
230
|
+
if (typeof preferred === "string" && preferred.trim()) return preferred.trim();
|
|
231
|
+
const icons = kitRecord?.icons;
|
|
232
|
+
if (!icons || typeof icons !== "object") return null;
|
|
233
|
+
const sized = Object.entries(icons)
|
|
234
|
+
.map(([size, path]) => ({ size: Number(size), path: safeText(path) }))
|
|
235
|
+
.filter((item) => Number.isFinite(item.size) && item.size > 0 && item.path)
|
|
236
|
+
.sort((a, b) => {
|
|
237
|
+
const aBucket = a.size >= 128 ? 0 : 1;
|
|
238
|
+
const bBucket = b.size >= 128 ? 0 : 1;
|
|
239
|
+
if (aBucket !== bBucket) return aBucket - bBucket;
|
|
240
|
+
return Math.abs(a.size - 128) - Math.abs(b.size - 128);
|
|
241
|
+
});
|
|
242
|
+
return sized[0]?.path ?? null;
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
function resolveIconUrl(kitRecord) {
|
|
246
|
+
const raw = resolveIconAssetPath(kitRecord);
|
|
247
|
+
if (!raw) return null;
|
|
248
|
+
if (/^https?:\/\//i.test(raw)) return raw;
|
|
249
|
+
if (/^data:/i.test(raw)) return raw;
|
|
250
|
+
return buildLocalAssetUrl(raw);
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
function isNpmSpec(value) {
|
|
254
|
+
return /^npm:/i.test(safeText(value));
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
function emojiFor(kitIdValue) {
|
|
258
|
+
const id = (kitIdValue ?? "").toString();
|
|
259
|
+
if (id.includes("ocr")) return "📷";
|
|
260
|
+
if (id.includes("clip") || id.includes("clipboard")) return "📋";
|
|
261
|
+
if (id.includes("meme") || id.includes("dou")) return "🤡";
|
|
262
|
+
if (id.includes("ai") || id.includes("chat") || id.includes("reply")) return "✨";
|
|
263
|
+
return "🤖";
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
function deriveTagsFromRuntimePermissions(permissions) {
|
|
267
|
+
const list = Array.isArray(permissions) ? permissions : [];
|
|
268
|
+
const tags = [];
|
|
269
|
+
if (list.includes("ai.request")) tags.push("AI");
|
|
270
|
+
if (list.includes("network.fetch")) tags.push("效率");
|
|
271
|
+
if (list.includes("storage.read") || list.includes("storage.write")) tags.push("系统");
|
|
272
|
+
if (list.includes("files.pick")) tags.push("工具");
|
|
273
|
+
return tags.slice(0, 2);
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
function deriveCatalogTags(pkg) {
|
|
277
|
+
const directTags = normalizeTextList(pkg?.tags).map(displayTagLabel);
|
|
278
|
+
const categoryTags = normalizeTextList(pkg?.categories).map(displayTagLabel);
|
|
279
|
+
const primaryTag = displayTagLabel(pkg?.tag);
|
|
280
|
+
const permissionTags = deriveTagsFromRuntimePermissions(pkg?.runtimePermissions);
|
|
281
|
+
return normalizeTextList([...directTags, ...categoryTags, primaryTag, ...permissionTags]).slice(0, 3);
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
function safeCreateKit() {
|
|
285
|
+
const sdk = globalThis.FunctionKitRuntimeSDK;
|
|
286
|
+
if (!sdk || typeof sdk.createKit !== "function") {
|
|
287
|
+
return null;
|
|
288
|
+
}
|
|
289
|
+
try {
|
|
290
|
+
return sdk.createKit({
|
|
291
|
+
kitId,
|
|
292
|
+
surface,
|
|
293
|
+
connect: { timeoutMs: 20000, retries: 2 },
|
|
294
|
+
preview: {
|
|
295
|
+
kitId,
|
|
296
|
+
surface,
|
|
297
|
+
grantAll: true,
|
|
298
|
+
kits: preview.installed,
|
|
299
|
+
catalogSources: preview.sources,
|
|
300
|
+
catalogPackages: preview.packages
|
|
301
|
+
}
|
|
302
|
+
});
|
|
303
|
+
} catch {
|
|
304
|
+
return null;
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
function getInstalledKits() {
|
|
309
|
+
const list = kit?.kits?.list?.() ?? [];
|
|
310
|
+
if (Array.isArray(list) && list.length > 0) return list;
|
|
311
|
+
return preview.installed;
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
function getCatalogSources() {
|
|
315
|
+
const sources = kit?.state?.catalog?.sources;
|
|
316
|
+
if (Array.isArray(sources)) return sources;
|
|
317
|
+
return preview.sources;
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
function getCatalogPackages() {
|
|
321
|
+
const packages = kit?.state?.catalog?.packages;
|
|
322
|
+
if (Array.isArray(packages) && packages.length > 0) return packages;
|
|
323
|
+
return preview.packages;
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
const store = globalThis.PetiteVue.reactive({
|
|
327
|
+
route: "home",
|
|
328
|
+
tab: "discover",
|
|
329
|
+
searchOpen: false,
|
|
330
|
+
searchQuery: "",
|
|
331
|
+
detailTab: "overview",
|
|
332
|
+
selected: { kind: null, kitId: null },
|
|
333
|
+
installed: [],
|
|
334
|
+
updateCount: 0,
|
|
335
|
+
catalogSources: preview.sources.slice(),
|
|
336
|
+
catalogPackages: [],
|
|
337
|
+
catalogAddUrl: "",
|
|
338
|
+
importUrl: "",
|
|
339
|
+
toast: { open: false, text: "" },
|
|
340
|
+
reviews: [
|
|
341
|
+
{ name: "Alex", text: "回答户神器!", stars: 5 },
|
|
342
|
+
{ name: "Bob", text: "偶尔说废话", stars: 4 }
|
|
343
|
+
],
|
|
344
|
+
securityLinks: [
|
|
345
|
+
{ label: "隐私政策", kind: "blue", icon: "upright" },
|
|
346
|
+
{ label: "问题反馈", kind: "blue", icon: "upright" },
|
|
347
|
+
{ label: "违规举报", kind: "danger", icon: "right" }
|
|
348
|
+
],
|
|
349
|
+
|
|
350
|
+
get discoverItems() {
|
|
351
|
+
const installed = Array.isArray(this.installed) ? this.installed : [];
|
|
352
|
+
const installedById = new Map(
|
|
353
|
+
installed
|
|
354
|
+
.map((record) => {
|
|
355
|
+
const id = normalizeKitId(record?.kitId ?? record?.id);
|
|
356
|
+
return id ? [id, record] : null;
|
|
357
|
+
})
|
|
358
|
+
.filter(Boolean)
|
|
359
|
+
);
|
|
360
|
+
|
|
361
|
+
const packages = Array.isArray(this.catalogPackages) ? this.catalogPackages : [];
|
|
362
|
+
const latestById = buildLatestPackageByKitId(packages);
|
|
363
|
+
const seen = new Set();
|
|
364
|
+
return packages
|
|
365
|
+
.map((pkg) => {
|
|
366
|
+
const id = normalizeKitId(pkg?.kitId ?? pkg?.id);
|
|
367
|
+
if (!id) return null;
|
|
368
|
+
const latestPkg = latestById.get(id) ?? null;
|
|
369
|
+
if (!latestPkg || latestPkg !== pkg) return null;
|
|
370
|
+
if (seen.has(id)) return null;
|
|
371
|
+
seen.add(id);
|
|
372
|
+
const title = (pkg?.name ?? pkg?.displayName ?? id).toString();
|
|
373
|
+
const installedRecord = installedById.get(id) ?? null;
|
|
374
|
+
const isInstalled = Boolean(installedRecord);
|
|
375
|
+
const isEnabled = installedRecord?.enabled !== false;
|
|
376
|
+
const installedVersion = getInstalledVersion(installedRecord);
|
|
377
|
+
const latestVersion = getCatalogVersion(latestPkg);
|
|
378
|
+
const updateAvailable =
|
|
379
|
+
Boolean(installedRecord) &&
|
|
380
|
+
Boolean(installedVersion) &&
|
|
381
|
+
Boolean(latestVersion) &&
|
|
382
|
+
compareSemver(installedVersion, latestVersion) < 0;
|
|
383
|
+
const tags = deriveCatalogTags(latestPkg);
|
|
384
|
+
return {
|
|
385
|
+
kind: "package",
|
|
386
|
+
kitId: id,
|
|
387
|
+
featured: featuredKitIds.has(id),
|
|
388
|
+
updateAvailable,
|
|
389
|
+
installedVersion: installedVersion || null,
|
|
390
|
+
latestVersion: latestVersion || null,
|
|
391
|
+
title,
|
|
392
|
+
iconUrl: resolveIconUrl(latestPkg),
|
|
393
|
+
iconClass: "kit-icon--meme",
|
|
394
|
+
sub: {
|
|
395
|
+
tags,
|
|
396
|
+
tag: tags[0] ?? "",
|
|
397
|
+
desc: (latestPkg?.description ?? "从 catalog 下载并安装到输入法。").toString()
|
|
398
|
+
},
|
|
399
|
+
action: !isInstalled
|
|
400
|
+
? { label: "获取", kind: "install" }
|
|
401
|
+
: updateAvailable
|
|
402
|
+
? { label: "更新", kind: "update" }
|
|
403
|
+
: !isEnabled
|
|
404
|
+
? { label: "启用", kind: "enable" }
|
|
405
|
+
: { label: "打开", kind: "open" },
|
|
406
|
+
pkg: latestPkg
|
|
407
|
+
};
|
|
408
|
+
})
|
|
409
|
+
.filter(Boolean);
|
|
410
|
+
},
|
|
411
|
+
|
|
412
|
+
get manageItems() {
|
|
413
|
+
const installed = Array.isArray(this.installed) ? this.installed : [];
|
|
414
|
+
const latestById = buildLatestPackageByKitId(this.catalogPackages);
|
|
415
|
+
return installed
|
|
416
|
+
.map((record) => {
|
|
417
|
+
const id = normalizeKitId(record?.kitId ?? record?.id);
|
|
418
|
+
if (!id) return null;
|
|
419
|
+
const pkg = latestById.get(id) ?? null;
|
|
420
|
+
const title = (record?.displayName ?? record?.name ?? id).toString();
|
|
421
|
+
const desc = (record?.description ?? "").toString();
|
|
422
|
+
const enabled = record?.enabled !== false;
|
|
423
|
+
const installedVersion = getInstalledVersion(record);
|
|
424
|
+
const latestVersion = getCatalogVersion(pkg);
|
|
425
|
+
const updateAvailable =
|
|
426
|
+
Boolean(pkg) && Boolean(installedVersion) && Boolean(latestVersion) && compareSemver(installedVersion, latestVersion) < 0;
|
|
427
|
+
return {
|
|
428
|
+
kind: "installed",
|
|
429
|
+
kitId: id,
|
|
430
|
+
featured: featuredKitIds.has(id),
|
|
431
|
+
updateAvailable,
|
|
432
|
+
installedVersion: installedVersion || null,
|
|
433
|
+
latestVersion: latestVersion || null,
|
|
434
|
+
title,
|
|
435
|
+
iconUrl: resolveIconUrl(record),
|
|
436
|
+
iconClass: id.includes("ocr") ? "kit-icon--ocr" : id.includes("clip") ? "kit-icon--clip" : "kit-icon--ai",
|
|
437
|
+
sub: {
|
|
438
|
+
tags: deriveTagsFromRuntimePermissions(record?.runtimePermissions),
|
|
439
|
+
desc: desc || ""
|
|
440
|
+
},
|
|
441
|
+
action: updateAvailable ? { label: "更新", kind: "update" } : enabled ? { label: "打开", kind: "open" } : { label: "启用", kind: "enable" },
|
|
442
|
+
record,
|
|
443
|
+
pkg
|
|
444
|
+
};
|
|
445
|
+
})
|
|
446
|
+
.filter(Boolean);
|
|
447
|
+
},
|
|
448
|
+
|
|
449
|
+
get searchItems() {
|
|
450
|
+
const query = safeText(this.searchQuery).toLowerCase();
|
|
451
|
+
const base = this.tab === "discover" ? this.discoverItems : this.manageItems;
|
|
452
|
+
if (!query) return base;
|
|
453
|
+
return base.filter((item) =>
|
|
454
|
+
[
|
|
455
|
+
item.title,
|
|
456
|
+
(item.sub && item.sub.desc) || "",
|
|
457
|
+
...(((item.sub && item.sub.tags) || []).filter(Boolean)),
|
|
458
|
+
(item.sub && item.sub.tag) || ""
|
|
459
|
+
]
|
|
460
|
+
.join(" ")
|
|
461
|
+
.toLowerCase()
|
|
462
|
+
.includes(query)
|
|
463
|
+
);
|
|
464
|
+
},
|
|
465
|
+
|
|
466
|
+
get selectedItem() {
|
|
467
|
+
const selectedKitId = this.selected?.kitId;
|
|
468
|
+
const kind = this.selected?.kind;
|
|
469
|
+
if (!selectedKitId || !kind) return null;
|
|
470
|
+
if (kind === "package") {
|
|
471
|
+
return this.discoverItems.find((item) => item.kitId === selectedKitId) ?? null;
|
|
472
|
+
}
|
|
473
|
+
return this.manageItems.find((item) => item.kitId === selectedKitId) ?? null;
|
|
474
|
+
},
|
|
475
|
+
|
|
476
|
+
get detailMeta() {
|
|
477
|
+
const current = this.selectedItem;
|
|
478
|
+
if (!current) return "";
|
|
479
|
+
return current.kind === "installed" ? "IME 官方 · 4.8★" : "Catalog · 4.8★";
|
|
480
|
+
},
|
|
481
|
+
|
|
482
|
+
get overviewText() {
|
|
483
|
+
const current = this.selectedItem;
|
|
484
|
+
if (!current) return "";
|
|
485
|
+
if (current.kind === "installed") {
|
|
486
|
+
return (current.record?.description ?? current.sub?.desc ?? "").toString();
|
|
487
|
+
}
|
|
488
|
+
return (current.pkg?.description ?? current.sub?.desc ?? "").toString();
|
|
489
|
+
},
|
|
490
|
+
|
|
491
|
+
get versionTitle() {
|
|
492
|
+
const current = this.selectedItem;
|
|
493
|
+
if (!current) return "";
|
|
494
|
+
if (current.kind === "package") {
|
|
495
|
+
const versionText = safeText(current.latestVersion ?? current.pkg?.version ?? "");
|
|
496
|
+
if (current.updateAvailable && current.installedVersion && versionText) {
|
|
497
|
+
return `已安装 ${current.installedVersion} · 最新 ${versionText}`;
|
|
498
|
+
}
|
|
499
|
+
return versionText ? `版本 ${versionText}` : "版本";
|
|
500
|
+
}
|
|
501
|
+
const installedVersion = safeText(current.installedVersion ?? "");
|
|
502
|
+
const latestVersion = safeText(current.latestVersion ?? "");
|
|
503
|
+
if (current.updateAvailable && installedVersion && latestVersion) {
|
|
504
|
+
return `已安装 ${installedVersion} · 最新 ${latestVersion}`;
|
|
505
|
+
}
|
|
506
|
+
return installedVersion ? `版本 ${installedVersion}` : "版本";
|
|
507
|
+
},
|
|
508
|
+
|
|
509
|
+
get versionSize() {
|
|
510
|
+
const current = this.selectedItem;
|
|
511
|
+
if (!current) return "";
|
|
512
|
+
if (current.kind === "package") {
|
|
513
|
+
return formatBytes(current.pkg?.sizeBytes);
|
|
514
|
+
}
|
|
515
|
+
return "";
|
|
516
|
+
},
|
|
517
|
+
|
|
518
|
+
get detailPermissions() {
|
|
519
|
+
const current = this.selectedItem;
|
|
520
|
+
if (!current || current.kind !== "installed") return [];
|
|
521
|
+
return Array.isArray(current.record?.runtimePermissions) ? current.record.runtimePermissions : [];
|
|
522
|
+
},
|
|
523
|
+
|
|
524
|
+
get permissionRisky() {
|
|
525
|
+
return this.detailPermissions.some((permission) => this.isHighRiskPermission(permission));
|
|
526
|
+
},
|
|
527
|
+
|
|
528
|
+
stars(count) {
|
|
529
|
+
const safeCount = Number.isFinite(count) ? count : Number(count ?? 0);
|
|
530
|
+
const resolved = Math.max(0, Math.min(5, safeCount));
|
|
531
|
+
return "★".repeat(resolved);
|
|
532
|
+
},
|
|
533
|
+
|
|
534
|
+
emojiFor(value) {
|
|
535
|
+
return emojiFor(value);
|
|
536
|
+
},
|
|
537
|
+
|
|
538
|
+
permissionLabel(permission) {
|
|
539
|
+
const map = {
|
|
540
|
+
"network.fetch": "网络访问(拉取资源)",
|
|
541
|
+
"ai.request": "网络访问(AI推理)",
|
|
542
|
+
"context.read": "读取上下文",
|
|
543
|
+
"input.insert": "写入输入框",
|
|
544
|
+
"input.replace": "写入输入框",
|
|
545
|
+
"files.pick": "读取本地文件",
|
|
546
|
+
"files.download": "下载文件(缓存)",
|
|
547
|
+
"kits.manage": "管理功能件(特权)",
|
|
548
|
+
"storage.read": "读取存储",
|
|
549
|
+
"storage.write": "写入存储"
|
|
550
|
+
};
|
|
551
|
+
return map[permission] ?? permission;
|
|
552
|
+
},
|
|
553
|
+
|
|
554
|
+
isHighRiskPermission(permission) {
|
|
555
|
+
return ["kits.manage", "ai.request", "network.fetch", "send.intercept.ime_action"].includes(permission);
|
|
556
|
+
},
|
|
557
|
+
|
|
558
|
+
showToast(message) {
|
|
559
|
+
const text = safeText(message) || "已完成";
|
|
560
|
+
this.toast.text = text;
|
|
561
|
+
this.toast.open = true;
|
|
562
|
+
clearTimeout(toastTimer);
|
|
563
|
+
toastTimer = setTimeout(() => {
|
|
564
|
+
this.toast.open = false;
|
|
565
|
+
}, 1400);
|
|
566
|
+
},
|
|
567
|
+
|
|
568
|
+
setRoute(route) {
|
|
569
|
+
this.route = safeText(route) || "home";
|
|
570
|
+
},
|
|
571
|
+
|
|
572
|
+
setTab(tab) {
|
|
573
|
+
const next = safeText(tab) || "discover";
|
|
574
|
+
this.tab = next === "manage" ? "manage" : "discover";
|
|
575
|
+
},
|
|
576
|
+
|
|
577
|
+
setDetailTab(tab) {
|
|
578
|
+
const next = safeText(tab) || "overview";
|
|
579
|
+
const allowed = new Set(["overview", "permissions", "reviews", "security"]);
|
|
580
|
+
this.detailTab = allowed.has(next) ? next : "overview";
|
|
581
|
+
},
|
|
582
|
+
|
|
583
|
+
openSearch() {
|
|
584
|
+
this.searchOpen = true;
|
|
585
|
+
setTimeout(() => document.getElementById("searchInput")?.focus(), 30);
|
|
586
|
+
},
|
|
587
|
+
|
|
588
|
+
closeSearch() {
|
|
589
|
+
this.searchOpen = false;
|
|
590
|
+
this.searchQuery = "";
|
|
591
|
+
},
|
|
592
|
+
|
|
593
|
+
openSettings() {
|
|
594
|
+
refreshRuntimeSnapshot();
|
|
595
|
+
this.setRoute("settings");
|
|
596
|
+
},
|
|
597
|
+
|
|
598
|
+
openImport() {
|
|
599
|
+
this.setRoute("import");
|
|
600
|
+
},
|
|
601
|
+
|
|
602
|
+
backFromSubView() {
|
|
603
|
+
this.setRoute("home");
|
|
604
|
+
this.closeSearch();
|
|
605
|
+
},
|
|
606
|
+
|
|
607
|
+
backFromDetail() {
|
|
608
|
+
this.setRoute("home");
|
|
609
|
+
},
|
|
610
|
+
|
|
611
|
+
openDetail(item) {
|
|
612
|
+
const kind = safeText(item?.kind);
|
|
613
|
+
const id = normalizeKitId(item?.kitId);
|
|
614
|
+
if (!kind || !id) return;
|
|
615
|
+
this.selected = { kind, kitId: id };
|
|
616
|
+
this.setDetailTab("overview");
|
|
617
|
+
this.setRoute("detail");
|
|
618
|
+
},
|
|
619
|
+
|
|
620
|
+
async handleCardAction(item) {
|
|
621
|
+
const action = safeText(item?.action?.kind);
|
|
622
|
+
if (action === "install") {
|
|
623
|
+
await this.installPackage(item?.pkg);
|
|
624
|
+
return;
|
|
625
|
+
}
|
|
626
|
+
if (action === "update") {
|
|
627
|
+
await this.installPackage(item?.pkg);
|
|
628
|
+
return;
|
|
629
|
+
}
|
|
630
|
+
if (action === "enable") {
|
|
631
|
+
await this.enableKit(item?.kitId);
|
|
632
|
+
return;
|
|
633
|
+
}
|
|
634
|
+
if (action === "open") {
|
|
635
|
+
await this.openKit(item?.kitId);
|
|
636
|
+
return;
|
|
637
|
+
}
|
|
638
|
+
this.openDetail(item);
|
|
639
|
+
},
|
|
640
|
+
|
|
641
|
+
async collapsePanel() {
|
|
642
|
+
try {
|
|
643
|
+
await kit?.panel?.updateState?.({ action: "collapse" });
|
|
644
|
+
} catch {
|
|
645
|
+
// ignore
|
|
646
|
+
}
|
|
647
|
+
this.showToast("已收起");
|
|
648
|
+
},
|
|
649
|
+
|
|
650
|
+
async openKit(targetKitId) {
|
|
651
|
+
const id = normalizeKitId(targetKitId);
|
|
652
|
+
if (!id) return;
|
|
653
|
+
if (!kit?.kits?.open) {
|
|
654
|
+
this.showToast("当前版本不支持打开功能件");
|
|
655
|
+
return;
|
|
656
|
+
}
|
|
657
|
+
try {
|
|
658
|
+
await kit.kits.open({ task: { title: `打开:${id}` }, kitId: id });
|
|
659
|
+
} catch (error) {
|
|
660
|
+
this.showToast(error?.message ?? "打开失败");
|
|
661
|
+
}
|
|
662
|
+
},
|
|
663
|
+
|
|
664
|
+
async enableKit(targetKitId) {
|
|
665
|
+
const id = normalizeKitId(targetKitId);
|
|
666
|
+
if (!id) return;
|
|
667
|
+
try {
|
|
668
|
+
await kit?.kits?.updateSettings?.({ task: { title: `启用:${id}` }, kitId: id, patch: { enabled: true } });
|
|
669
|
+
this.showToast("已启用");
|
|
670
|
+
await syncData();
|
|
671
|
+
} catch (error) {
|
|
672
|
+
this.showToast(error?.message ?? "启用失败");
|
|
673
|
+
}
|
|
674
|
+
},
|
|
675
|
+
|
|
676
|
+
async installPackage(pkg) {
|
|
677
|
+
const resolvedPkg = pkg && typeof pkg === "object" ? pkg : null;
|
|
678
|
+
const id = normalizeKitId(resolvedPkg?.kitId ?? resolvedPkg?.id);
|
|
679
|
+
const url = safeText(resolvedPkg?.resolvedZipUrl ?? resolvedPkg?.zipUrl ?? resolvedPkg?.dist?.tarball ?? "");
|
|
680
|
+
const sha256 = safeText(resolvedPkg?.sha256 ?? resolvedPkg?.dist?.sha256 ?? "");
|
|
681
|
+
const integrity = safeText(resolvedPkg?.integrity ?? resolvedPkg?.dist?.integrity ?? "");
|
|
682
|
+
const installKey = safeText(resolvedPkg?.installKey ?? "");
|
|
683
|
+
const npmName = safeText(resolvedPkg?.npm?.name ?? "");
|
|
684
|
+
const npmVersion = safeText(resolvedPkg?.npm?.version ?? resolvedPkg?.version ?? "");
|
|
685
|
+
const npmSpec = npmName && npmVersion ? `npm:${npmName}@${npmVersion}` : "";
|
|
686
|
+
if (!id) {
|
|
687
|
+
this.showToast("无法安装:缺少 kitId");
|
|
688
|
+
return;
|
|
689
|
+
}
|
|
690
|
+
try {
|
|
691
|
+
if (npmSpec) {
|
|
692
|
+
try {
|
|
693
|
+
await kit?.kits?.install?.({
|
|
694
|
+
task: { title: `安装:${resolvedPkg?.name ?? id}` },
|
|
695
|
+
source: {
|
|
696
|
+
kind: "npm",
|
|
697
|
+
spec: npmSpec,
|
|
698
|
+
sha256: sha256 || undefined,
|
|
699
|
+
integrity: integrity || undefined,
|
|
700
|
+
installKey: installKey || undefined
|
|
701
|
+
}
|
|
702
|
+
});
|
|
703
|
+
} catch (error) {
|
|
704
|
+
if (!url) throw error;
|
|
705
|
+
await kit?.kits?.install?.({
|
|
706
|
+
task: { title: `安装:${resolvedPkg?.name ?? id}` },
|
|
707
|
+
source: {
|
|
708
|
+
kind: "url",
|
|
709
|
+
url,
|
|
710
|
+
sha256: sha256 || undefined,
|
|
711
|
+
integrity: integrity || undefined,
|
|
712
|
+
installKey: installKey || undefined
|
|
713
|
+
}
|
|
714
|
+
});
|
|
715
|
+
}
|
|
716
|
+
} else {
|
|
717
|
+
if (!url) {
|
|
718
|
+
this.showToast("无法安装:缺少安装包 URL");
|
|
719
|
+
return;
|
|
720
|
+
}
|
|
721
|
+
await kit?.kits?.install?.({
|
|
722
|
+
task: { title: `安装:${resolvedPkg?.name ?? id}` },
|
|
723
|
+
source: {
|
|
724
|
+
kind: "url",
|
|
725
|
+
url,
|
|
726
|
+
sha256: sha256 || undefined,
|
|
727
|
+
integrity: integrity || undefined,
|
|
728
|
+
installKey: installKey || undefined
|
|
729
|
+
}
|
|
730
|
+
});
|
|
731
|
+
}
|
|
732
|
+
this.showToast("已安装");
|
|
733
|
+
await syncData();
|
|
734
|
+
} catch (error) {
|
|
735
|
+
this.showToast(error?.message ?? "安装失败");
|
|
736
|
+
}
|
|
737
|
+
},
|
|
738
|
+
|
|
739
|
+
async uninstallKit(targetKitId) {
|
|
740
|
+
const id = normalizeKitId(targetKitId);
|
|
741
|
+
if (!id) return;
|
|
742
|
+
try {
|
|
743
|
+
await kit?.kits?.uninstall?.({ task: { title: `卸载:${id}` }, kitId: id });
|
|
744
|
+
this.showToast("已卸载");
|
|
745
|
+
this.setRoute("home");
|
|
746
|
+
await syncData();
|
|
747
|
+
} catch (error) {
|
|
748
|
+
this.showToast(error?.message ?? "卸载失败");
|
|
749
|
+
}
|
|
750
|
+
},
|
|
751
|
+
|
|
752
|
+
async uninstallSelected() {
|
|
753
|
+
const current = this.selectedItem;
|
|
754
|
+
if (!current) return;
|
|
755
|
+
if (current.kind !== "installed" || current.record?.userInstalled !== true) {
|
|
756
|
+
this.showToast("仅支持卸载外部导入的功能件");
|
|
757
|
+
return;
|
|
758
|
+
}
|
|
759
|
+
await this.uninstallKit(current.kitId);
|
|
760
|
+
},
|
|
761
|
+
|
|
762
|
+
async setCatalogSources(sources) {
|
|
763
|
+
const next = Array.isArray(sources) ? sources : [];
|
|
764
|
+
try {
|
|
765
|
+
await kit?.catalog?.setSources?.({ task: { title: "更新 Catalog 源" }, sources: next });
|
|
766
|
+
this.catalogSources = next;
|
|
767
|
+
this.showToast("已更新");
|
|
768
|
+
await syncData();
|
|
769
|
+
return true;
|
|
770
|
+
} catch (error) {
|
|
771
|
+
this.showToast(error?.message ?? "更新失败");
|
|
772
|
+
return false;
|
|
773
|
+
}
|
|
774
|
+
},
|
|
775
|
+
|
|
776
|
+
async addCatalogSource() {
|
|
777
|
+
const value = safeText(this.catalogAddUrl);
|
|
778
|
+
if (!value) return;
|
|
779
|
+
if (!/^https?:\/\//i.test(value) && !/^npm:/i.test(value)) {
|
|
780
|
+
this.showToast("请输入 URL 或 npm:xxx");
|
|
781
|
+
return;
|
|
782
|
+
}
|
|
783
|
+
|
|
784
|
+
const sources = Array.isArray(this.catalogSources) ? [...this.catalogSources] : [];
|
|
785
|
+
if (!sources.some((entry) => safeText(entry?.url) === value)) {
|
|
786
|
+
sources.push({ url: value, enabled: true });
|
|
787
|
+
}
|
|
788
|
+
|
|
789
|
+
const ok = await this.setCatalogSources(sources);
|
|
790
|
+
if (ok) {
|
|
791
|
+
this.catalogAddUrl = "";
|
|
792
|
+
}
|
|
793
|
+
},
|
|
794
|
+
|
|
795
|
+
async removeCatalogSource(url) {
|
|
796
|
+
const target = safeText(url);
|
|
797
|
+
if (!target) return;
|
|
798
|
+
const sources = Array.isArray(this.catalogSources) ? this.catalogSources : [];
|
|
799
|
+
await this.setCatalogSources(sources.filter((entry) => safeText(entry?.url) !== target));
|
|
800
|
+
},
|
|
801
|
+
|
|
802
|
+
async resetCatalogSources() {
|
|
803
|
+
await this.setCatalogSources([{ url: DEFAULT_CATALOG_SOURCE, enabled: true }]);
|
|
804
|
+
},
|
|
805
|
+
|
|
806
|
+
async installImportUrl() {
|
|
807
|
+
const value = safeText(this.importUrl);
|
|
808
|
+
if (!value) {
|
|
809
|
+
this.showToast("请输入 URL 或 npm:包名@版本");
|
|
810
|
+
return;
|
|
811
|
+
}
|
|
812
|
+
const npmSpec = isNpmSpec(value);
|
|
813
|
+
try {
|
|
814
|
+
await kit?.kits?.install?.({
|
|
815
|
+
task: { title: npmSpec ? "安装:npm" : "安装:URL" },
|
|
816
|
+
source: npmSpec ? { kind: "npm", spec: value } : { kind: "url", url: value }
|
|
817
|
+
});
|
|
818
|
+
this.showToast("已安装");
|
|
819
|
+
this.setRoute("home");
|
|
820
|
+
await syncData();
|
|
821
|
+
} catch (error) {
|
|
822
|
+
this.showToast(error?.message ?? "安装失败");
|
|
823
|
+
}
|
|
824
|
+
},
|
|
825
|
+
|
|
826
|
+
async installFromZip() {
|
|
827
|
+
try {
|
|
828
|
+
const pick = await kit?.files?.pick?.({ acceptMimeTypes: ["application/zip"], multiple: false });
|
|
829
|
+
const fileId = pick?.files?.[0]?.fileId;
|
|
830
|
+
if (!fileId) {
|
|
831
|
+
this.showToast("已取消");
|
|
832
|
+
return;
|
|
833
|
+
}
|
|
834
|
+
await kit?.kits?.install?.({ task: { title: "安装:本地 ZIP" }, source: { kind: "file", fileId } });
|
|
835
|
+
this.showToast("已安装");
|
|
836
|
+
this.setRoute("home");
|
|
837
|
+
await syncData();
|
|
838
|
+
} catch (error) {
|
|
839
|
+
this.showToast(error?.message ?? "安装失败");
|
|
840
|
+
}
|
|
841
|
+
}
|
|
842
|
+
});
|
|
843
|
+
|
|
844
|
+
function refreshRuntimeSnapshot() {
|
|
845
|
+
const installed = getInstalledKits();
|
|
846
|
+
store.installed = Array.isArray(installed) ? [...installed] : [];
|
|
847
|
+
|
|
848
|
+
const sources = getCatalogSources();
|
|
849
|
+
store.catalogSources = Array.isArray(sources) ? [...sources] : [];
|
|
850
|
+
|
|
851
|
+
const packages = getCatalogPackages();
|
|
852
|
+
store.catalogPackages = Array.isArray(packages) ? [...packages] : [];
|
|
853
|
+
|
|
854
|
+
store.updateCount = computeUpdateCount(store.installed, store.catalogPackages);
|
|
855
|
+
}
|
|
856
|
+
|
|
857
|
+
async function syncData() {
|
|
858
|
+
if (!kit) {
|
|
859
|
+
refreshRuntimeSnapshot();
|
|
860
|
+
return;
|
|
861
|
+
}
|
|
862
|
+
|
|
863
|
+
try {
|
|
864
|
+
await kit.kits.sync({ includeDisabled: true });
|
|
865
|
+
} catch {
|
|
866
|
+
// ignore
|
|
867
|
+
}
|
|
868
|
+
|
|
869
|
+
try {
|
|
870
|
+
await kit.catalog.getSources({ task: { title: "读取 Catalog 源" } });
|
|
871
|
+
} catch {
|
|
872
|
+
// ignore
|
|
873
|
+
}
|
|
874
|
+
|
|
875
|
+
try {
|
|
876
|
+
await kit.catalog.refresh({ task: { title: "刷新目录" } });
|
|
877
|
+
} catch (error) {
|
|
878
|
+
store.showToast(error?.message ?? "刷新目录失败");
|
|
879
|
+
}
|
|
880
|
+
|
|
881
|
+
refreshRuntimeSnapshot();
|
|
882
|
+
}
|
|
883
|
+
|
|
884
|
+
function initSwipeNavigation() {
|
|
885
|
+
const homeView = document.getElementById("homeView");
|
|
886
|
+
if (!homeView) return;
|
|
887
|
+
|
|
888
|
+
let swipeStartX = null;
|
|
889
|
+
let swipeStartY = null;
|
|
890
|
+
|
|
891
|
+
homeView.addEventListener(
|
|
892
|
+
"touchstart",
|
|
893
|
+
(event) => {
|
|
894
|
+
if (store.route !== "home") return;
|
|
895
|
+
if (event.touches.length !== 1) return;
|
|
896
|
+
const target = event.target;
|
|
897
|
+
const tag = target?.tagName?.toUpperCase?.();
|
|
898
|
+
if (tag === "INPUT" || tag === "TEXTAREA") return;
|
|
899
|
+
swipeStartX = event.touches[0].clientX;
|
|
900
|
+
swipeStartY = event.touches[0].clientY;
|
|
901
|
+
},
|
|
902
|
+
{ passive: true }
|
|
903
|
+
);
|
|
904
|
+
|
|
905
|
+
homeView.addEventListener(
|
|
906
|
+
"touchend",
|
|
907
|
+
(event) => {
|
|
908
|
+
if (store.route !== "home") return;
|
|
909
|
+
if (swipeStartX == null || swipeStartY == null) return;
|
|
910
|
+
const touch = event.changedTouches?.[0];
|
|
911
|
+
if (!touch) return;
|
|
912
|
+
const dx = touch.clientX - swipeStartX;
|
|
913
|
+
const dy = touch.clientY - swipeStartY;
|
|
914
|
+
swipeStartX = null;
|
|
915
|
+
swipeStartY = null;
|
|
916
|
+
|
|
917
|
+
if (Math.abs(dx) < 60) return;
|
|
918
|
+
if (Math.abs(dx) <= Math.abs(dy) * 1.2) return;
|
|
919
|
+
|
|
920
|
+
if (dx < 0 && store.tab === "discover") {
|
|
921
|
+
store.setTab("manage");
|
|
922
|
+
return;
|
|
923
|
+
}
|
|
924
|
+
|
|
925
|
+
if (dx > 0 && store.tab === "manage") {
|
|
926
|
+
store.setTab("discover");
|
|
927
|
+
}
|
|
928
|
+
},
|
|
929
|
+
{ passive: true }
|
|
930
|
+
);
|
|
931
|
+
}
|
|
932
|
+
|
|
933
|
+
globalThis.PetiteVue.createApp(store).mount("#app");
|
|
934
|
+
|
|
935
|
+
async function boot() {
|
|
936
|
+
kit = safeCreateKit();
|
|
937
|
+
try {
|
|
938
|
+
await kit?.connect?.();
|
|
939
|
+
} catch {
|
|
940
|
+
// preview / disconnected ok
|
|
941
|
+
}
|
|
942
|
+
|
|
943
|
+
try {
|
|
944
|
+
kit?.on?.("kits.sync", () => {
|
|
945
|
+
refreshRuntimeSnapshot();
|
|
946
|
+
});
|
|
947
|
+
kit?.on?.("catalog.sync", () => {
|
|
948
|
+
refreshRuntimeSnapshot();
|
|
949
|
+
});
|
|
950
|
+
kit?.on?.("catalog.sources.sync", () => {
|
|
951
|
+
refreshRuntimeSnapshot();
|
|
952
|
+
});
|
|
953
|
+
} catch {
|
|
954
|
+
// ignore
|
|
955
|
+
}
|
|
956
|
+
|
|
957
|
+
refreshRuntimeSnapshot();
|
|
958
|
+
initSwipeNavigation();
|
|
959
|
+
await syncData();
|
|
960
|
+
|
|
961
|
+
if (!updateToastShown && store.updateCount > 0) {
|
|
962
|
+
updateToastShown = true;
|
|
963
|
+
store.showToast(`发现 ${store.updateCount} 个更新`);
|
|
964
|
+
}
|
|
965
|
+
}
|
|
966
|
+
|
|
967
|
+
boot();
|
|
968
|
+
})();
|