dsh-folia 4.8.0 → 4.8.2
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/lib/client.js +83 -16
- package/lib/routes/inventory.js +21 -3
- package/package.json +1 -1
package/lib/client.js
CHANGED
|
@@ -745,6 +745,41 @@ window.__ModuleLoader__.load({
|
|
|
745
745
|
"站点上还没有桌宠。": "No pets on the site yet.",
|
|
746
746
|
"站点不可达": "Site unreachable",
|
|
747
747
|
"预览尺寸": "Preview-size",
|
|
748
|
+
|
|
749
|
+
// ── 第二轮补词:来源筛选 / 页签引导条 / 桌宠提示 / 模型名(专有名词不译,但必须在册)──
|
|
750
|
+
"开机动画": "Boot animation",
|
|
751
|
+
"桌宠": "Desktop pet",
|
|
752
|
+
"打开设置 ›": "Open settings ›",
|
|
753
|
+
"开机动画的全部参数都在侧栏「壁纸引擎」设置里": "All boot-animation options live in the \"Wallpaper Engine\" section of the settings panel",
|
|
754
|
+
"桌宠的开关 / 大小 / 声音都在「桌宠」页签里调整": "The pet toggle, size and sound all live in the \"Desktop pet\" tab",
|
|
755
|
+
"下载一条并「使用」后,每次启动都会先播它;默认播完才淡出进入界面。点击或按 Esc 可立即跳过。「带声音」若被浏览器的自动播放策略拦下,会自动退回静音播放。": "Once downloaded and set as current, it plays at every start; by default it finishes before fading into the app. Click or press Esc to skip. If the browser blocks audible autoplay it falls back to muted playback.",
|
|
756
|
+
"未做 faststart,播放可能更慢": "Not faststart-optimised; playback may start slower",
|
|
757
|
+
"素材来自站点;站点不可达时这里为空,但不影响启动": "Clips come from the site; when it is unreachable this list is empty and startup is unaffected",
|
|
758
|
+
"桌宠由本插件自己绘制(雪碧图逐帧),不依赖其它宠物插件。它会跟着模型状态换动作:干活时换成工作行、干完变完成、出错变失败;安装后出现在右下角,可拖动,点一下会挥手并出声。": "This plugin draws the pet itself (frame by frame from a spritesheet) with no other pet plugin. It follows model activity: working plays the work row, finishing plays the done row, errors play the failed row. After install it sits in the bottom-right, can be dragged, and waves with a sound when clicked.",
|
|
759
|
+
"来源": "Source",
|
|
760
|
+
"来源筛选": "Source filter",
|
|
761
|
+
"全部来源": "All sources",
|
|
762
|
+
"本机": "Local",
|
|
763
|
+
"站点": "Site",
|
|
764
|
+
"按来源过滤:本机 Wallpaper Engine 库,还是从我们站点拉下来的壁纸": "Filter by source: your local Wallpaper Engine library, or wallpapers pulled from our site",
|
|
765
|
+
"按来源筛选:本机 Wallpaper Engine 库,还是从我们站点拉下来的壁纸": "Filter by source: your local Wallpaper Engine library, or wallpapers pulled from our site",
|
|
766
|
+
"本机 Wallpaper Engine({n})": "Local Wallpaper Engine ({n})",
|
|
767
|
+
"站点壁纸({n})": "Site wallpapers ({n})",
|
|
768
|
+
"这张图只有 {size},当壁纸会糊 —— 建议在站点上传原图": "This image is only {size}; it will look blurry as a wallpaper — upload the original on the site",
|
|
769
|
+
"{type}{status}": "{type}{status}",
|
|
770
|
+
"DeepSeek": "DeepSeek",
|
|
771
|
+
"Deepseek_Pro": "Deepseek_Pro",
|
|
772
|
+
"Claude": "Claude",
|
|
773
|
+
"Gemini": "Gemini",
|
|
774
|
+
"GPT": "GPT",
|
|
775
|
+
"GLM": "GLM",
|
|
776
|
+
"Grok": "Grok",
|
|
777
|
+
"Kimi": "Kimi",
|
|
778
|
+
"Qwen": "Qwen",
|
|
779
|
+
"Folia": "Folia",
|
|
780
|
+
"…": "…",
|
|
781
|
+
// 开机动画:已开启但无选片时的提示(4.8.2)
|
|
782
|
+
"已开启,但还没有选片 —— 下载一条后点「使用」": "Enabled, but no clip is selected yet — download one, then press Use",
|
|
748
783
|
};
|
|
749
784
|
|
|
750
785
|
// ── 内联模块:src/i18n-copy-picker.js —— 英文词表域:壁纸选择器与快捷面板(picker-* · quick-panel · sidebar-right · theme-follow)(构建期注入;勿手改本段)──
|
|
@@ -17094,7 +17129,9 @@ window.__ModuleLoader__.load({
|
|
|
17094
17129
|
|
|
17095
17130
|
async function act(kind, payload, label) {
|
|
17096
17131
|
if (busy) return;
|
|
17097
|
-
|
|
17132
|
+
// 只有结构性操作(下载 / 选择 / 删除)才锁 UI;开关与参数是幂等的,锁了反而闪。
|
|
17133
|
+
const structural = kind === "download" || kind === "select" || kind === "remove";
|
|
17134
|
+
if (structural) setBusy(payload && payload.id ? payload.id : kind);
|
|
17098
17135
|
setTick(0);
|
|
17099
17136
|
setNote(null);
|
|
17100
17137
|
try {
|
|
@@ -17106,7 +17143,13 @@ window.__ModuleLoader__.load({
|
|
|
17106
17143
|
if (kind === "remove") setNote({ ok: true, text: weT("已删除:{name}", { name: label }) });
|
|
17107
17144
|
if (kind === "toggle") setNote({ ok: true, text: payload.enabled ? weT("开机动画已开启") : weT("开机动画已关闭") });
|
|
17108
17145
|
if (kind === "settings") setNote({ ok: true, text: weT("已保存") });
|
|
17109
|
-
|
|
17146
|
+
// 开关/参数类:宿主把整份 boot 配置回给我们({ ok, ...boot }),直接并进 state,
|
|
17147
|
+
// **不再 load(true) 重拉整表** —— 那次重拉会让所有控件先禁用再恢复,视觉上就是闪一下。
|
|
17148
|
+
if (kind === "toggle" || kind === "settings") {
|
|
17149
|
+
setState((prev) => (prev ? { ...prev, ...r } : prev));
|
|
17150
|
+
} else {
|
|
17151
|
+
await load(true);
|
|
17152
|
+
}
|
|
17110
17153
|
} else {
|
|
17111
17154
|
const code = (r && r.error) || "failed";
|
|
17112
17155
|
setNote({ ok: false, text: r && r.detail ? weT("操作失败:{code}({detail})", { code, detail: String(r.detail) }) : weT("操作失败:{code}", { code }) });
|
|
@@ -17139,13 +17182,18 @@ window.__ModuleLoader__.load({
|
|
|
17139
17182
|
},
|
|
17140
17183
|
React.createElement("label", { style: { display: "flex", gap: "6px", alignItems: "center", fontSize: "12px" } },
|
|
17141
17184
|
React.createElement("input", {
|
|
17142
|
-
type: "checkbox", checked: enabled,
|
|
17185
|
+
type: "checkbox", checked: enabled,
|
|
17186
|
+
// 不因"还没选片"而禁用:开关本身是幂等的,宿主在无选片时不会注入任何脚本。
|
|
17187
|
+
// (实测反馈:勾不动,因为此时 activeId 为空 —— 那是把两件事绑在一起了。)
|
|
17188
|
+
disabled: Boolean(busy),
|
|
17143
17189
|
onChange: (e) => act("toggle", { enabled: e.target.checked }, ""),
|
|
17144
17190
|
}),
|
|
17145
17191
|
weT("启用(下次启动播放)"),
|
|
17146
17192
|
),
|
|
17147
17193
|
React.createElement("span", { className: "we-picker__hint" },
|
|
17148
|
-
activeId
|
|
17194
|
+
activeId
|
|
17195
|
+
? weT("当前:{name}", { name: (clips.find((c) => c.id === activeId) || {}).title || activeId })
|
|
17196
|
+
: (enabled ? weT("已开启,但还没有选片 —— 下载一条后点「使用」") : weT("未选择"))),
|
|
17149
17197
|
));
|
|
17150
17198
|
|
|
17151
17199
|
children.push(React.createElement("div", { key: "hint", className: "we-picker__hint", style: { margin: "2px 0 6px", opacity: 0.75 } },
|
|
@@ -17156,9 +17204,17 @@ window.__ModuleLoader__.load({
|
|
|
17156
17204
|
weT("候选清单读不到:{msg}", { msg: String(state.error) })));
|
|
17157
17205
|
}
|
|
17158
17206
|
|
|
17159
|
-
|
|
17160
|
-
|
|
17161
|
-
|
|
17207
|
+
// 提示行**常驻且占位**:它出现/消失会改变面板高度,视觉上就是"点一下闪一下"。
|
|
17208
|
+
// 固定 minHeight 之后文案变了、布局不动。
|
|
17209
|
+
children.push(React.createElement("div", {
|
|
17210
|
+
key: "note",
|
|
17211
|
+
className: "we-picker__hint",
|
|
17212
|
+
style: {
|
|
17213
|
+
color: note && !note.ok ? "#ff9a9a" : "#8ee6a8",
|
|
17214
|
+
margin: "4px 0 8px", minHeight: "18px", lineHeight: "18px",
|
|
17215
|
+
opacity: note ? 1 : 0, transition: "opacity 120ms linear",
|
|
17216
|
+
},
|
|
17217
|
+
}, note ? note.text : "\u00a0"));
|
|
17162
17218
|
|
|
17163
17219
|
// 淡出 / 停留
|
|
17164
17220
|
if (state) {
|
|
@@ -17315,7 +17371,9 @@ window.__ModuleLoader__.load({
|
|
|
17315
17371
|
React.useEffect(() => { load(false); }, [load]);
|
|
17316
17372
|
|
|
17317
17373
|
const act = async (kind, payload, label) => {
|
|
17318
|
-
|
|
17374
|
+
// 只有安装/卸载/导入这类**结构性**操作才锁 UI;开关与大小是幂等的。
|
|
17375
|
+
const structural = kind === "install" || kind === "remove" || kind === "import";
|
|
17376
|
+
if (structural) setBusy(kind + ":" + (payload.id || ""));
|
|
17319
17377
|
setNote(null);
|
|
17320
17378
|
try {
|
|
17321
17379
|
const resp = await apiPostJson("/pets/" + kind, payload || {}, { parse: "always" });
|
|
@@ -17325,7 +17383,13 @@ window.__ModuleLoader__.load({
|
|
|
17325
17383
|
if (kind === "remove") setNote({ ok: true, text: weT("已卸载:{name}", { name: label || "" }) });
|
|
17326
17384
|
if (kind === "import") setNote({ ok: true, text: weT("已导入并启用:{name}", { name: label || "" }) });
|
|
17327
17385
|
if (kind === "config") setNote({ ok: true, text: weT("已保存") });
|
|
17328
|
-
|
|
17386
|
+
// config 的响应里带新的 config({ ok, config })⇒ 直接并进 state,
|
|
17387
|
+
// 不再 load(true) 重拉整个清单(那次重拉会让所有按钮先禁用再恢复 = 闪动)。
|
|
17388
|
+
if (kind === "config" && r && r.config) {
|
|
17389
|
+
setState((prev) => (prev ? { ...prev, config: r.config } : prev));
|
|
17390
|
+
} else {
|
|
17391
|
+
await load(true);
|
|
17392
|
+
}
|
|
17329
17393
|
} else {
|
|
17330
17394
|
const code = (r && r.error) || "failed";
|
|
17331
17395
|
setNote({ ok: false, text: weT("操作失败:{code}", { code }) });
|
|
@@ -17411,13 +17475,16 @@ window.__ModuleLoader__.load({
|
|
|
17411
17475
|
),
|
|
17412
17476
|
));
|
|
17413
17477
|
|
|
17414
|
-
|
|
17415
|
-
|
|
17416
|
-
|
|
17417
|
-
|
|
17418
|
-
|
|
17419
|
-
|
|
17420
|
-
|
|
17478
|
+
// 与开机动画页签同理:提示行常驻占位,避免开关时整块高度跳动。
|
|
17479
|
+
children.push(React.createElement("div", {
|
|
17480
|
+
key: "note",
|
|
17481
|
+
className: "we-picker__hint",
|
|
17482
|
+
style: {
|
|
17483
|
+
color: note && !note.ok ? "rgba(255,140,140,0.95)" : "rgba(120,220,150,0.95)",
|
|
17484
|
+
margin: "2px 0 8px", minHeight: "18px", lineHeight: "18px",
|
|
17485
|
+
opacity: note ? 1 : 0, transition: "opacity 120ms linear",
|
|
17486
|
+
},
|
|
17487
|
+
}, note ? note.text : "\u00a0"));
|
|
17421
17488
|
|
|
17422
17489
|
const btn = (text, onClick, disabled) => React.createElement("button", {
|
|
17423
17490
|
type: "button", className: "we-picker__btn", disabled: Boolean(disabled),
|
package/lib/routes/inventory.js
CHANGED
|
@@ -216,15 +216,27 @@ function foliaAbs(base, u) {
|
|
|
216
216
|
*
|
|
217
217
|
* 站点目录的条目长这样:`{ slug, title, kind: 'dynamic', mime: 'video/mp4', url: '/media/...' }` ——
|
|
218
218
|
* 它没有 `file` 字段,而且 `kind: 'dynamic'` 既可能是视频也可能是有动画的图,所以 mime 最可靠。
|
|
219
|
+
*
|
|
220
|
+
* ⚠️ 实测踩过的坑:WE **工程包**(zip)的 `kind` 也是 `dynamic`、`mime` 是 `application/zip`。
|
|
221
|
+
* 早先的顺序是「kind=dynamic ⇒ video」,于是 56 MB 的 zip 被当成视频塞进 `<video>`,
|
|
222
|
+
* 用户看到的是「浏览器无法解码这段视频」。⇒ **非 image/ 非 video/ 的 mime 一律不猜视频**,
|
|
223
|
+
* 而是判成 `package`(可显示、不可播放,客户端会给出明确原因而不是解码失败)。
|
|
219
224
|
*/
|
|
220
225
|
function foliaTypeOfEntry(it) {
|
|
221
226
|
const mime = String((it && it.mime) || '').toLowerCase();
|
|
222
227
|
if (mime.startsWith('video/')) return 'video';
|
|
223
228
|
if (mime.startsWith('image/')) return 'image';
|
|
229
|
+
// 已知的"包"形态:工程包 / 压缩包。它们不是可直接播放的媒体。
|
|
230
|
+
if (/^application\/(zip|x-zip|x-zip-compressed)$/.test(mime)) return 'package';
|
|
224
231
|
const kind = String((it && it.kind) || '').toLowerCase();
|
|
232
|
+
const probe = String((it && (it.file || it.url || it.slug)) || '');
|
|
233
|
+
if (/\.zip$/i.test(probe)) return 'package';
|
|
234
|
+
// `application/octet-stream` 是"不知道是什么的二进制":按后缀与 kind 继续判,
|
|
235
|
+
// 不当成包(上传工具经常把视频标成 octet-stream)。
|
|
236
|
+
// 其余"有 mime 但不是媒体"的(例如 application/json)一律不猜 —— 宁可丢也不塞进播放器。
|
|
237
|
+
if (mime && mime !== 'application/octet-stream') return null;
|
|
225
238
|
if (kind === 'video' || kind === 'dynamic') return 'video';
|
|
226
239
|
if (kind === 'image') return 'image';
|
|
227
|
-
const probe = String((it && (it.file || it.url || it.slug)) || '');
|
|
228
240
|
if (/\.(mp4|webm|mov|m4v)$/i.test(probe)) return 'video';
|
|
229
241
|
if (/\.(jpg|jpeg|png|webp|gif|avif)$/i.test(probe)) return 'image';
|
|
230
242
|
return null;
|
|
@@ -269,9 +281,12 @@ function foliaItemFromCatalog(it, base) {
|
|
|
269
281
|
return {
|
|
270
282
|
id: 'folia-' + (slug || media),
|
|
271
283
|
title: String((it && it.title) || slug || 'Folia'),
|
|
272
|
-
type, // 'video' | 'image'
|
|
284
|
+
type, // 'video' | 'image' | 'package'
|
|
273
285
|
contentrating: null,
|
|
274
|
-
|
|
286
|
+
// 工程包(zip)不是可直接播放的媒体:标成不可播放,客户端据此**不塞进 <video>**,
|
|
287
|
+
// 而是显示「工程包 · 暂不支持」的明确说明(而不是让浏览器报解码失败)。
|
|
288
|
+
playable: type !== 'package',
|
|
289
|
+
unsupportedReason: type === 'package' ? 'we-package' : null,
|
|
275
290
|
media, // 绝对 URL
|
|
276
291
|
mediaExt: '.' + (extHit ? extHit[1].toLowerCase() : (type === 'video' ? 'mp4' : 'jpg')),
|
|
277
292
|
// 站点目录可能带 preview/poster;图片没封面时用自己当预览,视频没有就留空(卡片会是黑块)
|
|
@@ -472,6 +487,9 @@ function warmInventorySiteCache() {
|
|
|
472
487
|
}
|
|
473
488
|
|
|
474
489
|
export { warmInventorySiteCache };
|
|
490
|
+
// 类型判定与条目构造导出**只为测试**(test/inventory-types.mjs):它们是纯函数,
|
|
491
|
+
// 用它做判据比"起一个假站点再走 HTTP"稳定得多 —— 后者要伪造缓存目录与时点。
|
|
492
|
+
export { foliaTypeOfEntry, foliaItemFromCatalog };
|
|
475
493
|
export function registerInventoryRoutes(webServer, c) {
|
|
476
494
|
const { disposers, base: BASE, mediaOriginApi, buildInventory, componentsProbe } = c;
|
|
477
495
|
|