@my-life-buddies/cli 0.14.1 → 0.15.0
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/README.md +63 -15
- package/dist/bin/buddy.js +28 -9
- package/dist/bin/buddy.js.map +2 -2
- package/dist/bin/core.js +1293 -603
- package/dist/bin/core.js.map +4 -4
- package/dist/bin/preview.js +43 -8
- package/dist/bin/preview.js.map +2 -2
- package/dist/web/app.js +11 -6
- package/dist/web/widgets.css +59 -28
- package/dist/web/widgets.js +230 -76
- package/package.json +3 -3
- package/resources/README.md +6 -50
- package/resources/buddy-creator/INSTALL.md +0 -25
- package/resources/buddy-creator/PATCHES.md +0 -53
- package/resources/buddy-creator/SKILL.md +0 -76
- package/resources/buddy-creator/THIRD_PARTY_NOTICES.md +0 -154
- package/resources/buddy-creator/agents/openai.yaml +0 -4
- package/resources/buddy-creator/assets/licenses/bail.txt +0 -26
- package/resources/buddy-creator/assets/licenses/ccount.txt +0 -26
- package/resources/buddy-creator/assets/licenses/character-entities-html4.txt +0 -26
- package/resources/buddy-creator/assets/licenses/character-entities-legacy.txt +0 -26
- package/resources/buddy-creator/assets/licenses/character-entities.txt +0 -26
- package/resources/buddy-creator/assets/licenses/character-reference-invalid.txt +0 -26
- package/resources/buddy-creator/assets/licenses/comma-separated-tokens.txt +0 -26
- package/resources/buddy-creator/assets/licenses/debug.txt +0 -24
- package/resources/buddy-creator/assets/licenses/decode-named-character-reference.txt +0 -26
- package/resources/buddy-creator/assets/licenses/dequal.txt +0 -25
- package/resources/buddy-creator/assets/licenses/devlop.txt +0 -26
- package/resources/buddy-creator/assets/licenses/escape-string-regexp.txt +0 -13
- package/resources/buddy-creator/assets/licenses/estree-util-is-identifier-name.txt +0 -26
- package/resources/buddy-creator/assets/licenses/extend.txt +0 -27
- package/resources/buddy-creator/assets/licenses/hast-util-to-jsx-runtime.txt +0 -26
- package/resources/buddy-creator/assets/licenses/hast-util-whitespace.txt +0 -26
- package/resources/buddy-creator/assets/licenses/html-url-attributes.txt +0 -25
- package/resources/buddy-creator/assets/licenses/inline-style-parser.txt +0 -13
- package/resources/buddy-creator/assets/licenses/is-alphabetical.txt +0 -26
- package/resources/buddy-creator/assets/licenses/is-alphanumerical.txt +0 -26
- package/resources/buddy-creator/assets/licenses/is-decimal.txt +0 -26
- package/resources/buddy-creator/assets/licenses/is-hexadecimal.txt +0 -26
- package/resources/buddy-creator/assets/licenses/is-plain-obj.txt +0 -13
- package/resources/buddy-creator/assets/licenses/longest-streak.txt +0 -26
- package/resources/buddy-creator/assets/licenses/markdown-table.txt +0 -26
- package/resources/buddy-creator/assets/licenses/mdast-util-find-and-replace.txt +0 -26
- package/resources/buddy-creator/assets/licenses/mdast-util-from-markdown.txt +0 -26
- package/resources/buddy-creator/assets/licenses/mdast-util-gfm-autolink-literal.txt +0 -26
- package/resources/buddy-creator/assets/licenses/mdast-util-gfm-footnote.txt +0 -26
- package/resources/buddy-creator/assets/licenses/mdast-util-gfm-strikethrough.txt +0 -26
- package/resources/buddy-creator/assets/licenses/mdast-util-gfm-table.txt +0 -26
- package/resources/buddy-creator/assets/licenses/mdast-util-gfm-task-list-item.txt +0 -26
- package/resources/buddy-creator/assets/licenses/mdast-util-gfm.txt +0 -26
- package/resources/buddy-creator/assets/licenses/mdast-util-mdx-expression.txt +0 -26
- package/resources/buddy-creator/assets/licenses/mdast-util-mdx-jsx.txt +0 -26
- package/resources/buddy-creator/assets/licenses/mdast-util-mdxjs-esm.txt +0 -26
- package/resources/buddy-creator/assets/licenses/mdast-util-phrasing.txt +0 -27
- package/resources/buddy-creator/assets/licenses/mdast-util-to-hast.txt +0 -26
- package/resources/buddy-creator/assets/licenses/mdast-util-to-markdown.txt +0 -26
- package/resources/buddy-creator/assets/licenses/mdast-util-to-string.txt +0 -26
- package/resources/buddy-creator/assets/licenses/micromark-core-commonmark.txt +0 -26
- package/resources/buddy-creator/assets/licenses/micromark-extension-gfm-autolink-literal.txt +0 -26
- package/resources/buddy-creator/assets/licenses/micromark-extension-gfm-footnote.txt +0 -26
- package/resources/buddy-creator/assets/licenses/micromark-extension-gfm-strikethrough.txt +0 -26
- package/resources/buddy-creator/assets/licenses/micromark-extension-gfm-table.txt +0 -26
- package/resources/buddy-creator/assets/licenses/micromark-extension-gfm-tagfilter.txt +0 -26
- package/resources/buddy-creator/assets/licenses/micromark-extension-gfm-task-list-item.txt +0 -26
- package/resources/buddy-creator/assets/licenses/micromark-extension-gfm.txt +0 -26
- package/resources/buddy-creator/assets/licenses/micromark-factory-destination.txt +0 -26
- package/resources/buddy-creator/assets/licenses/micromark-factory-label.txt +0 -26
- package/resources/buddy-creator/assets/licenses/micromark-factory-space.txt +0 -26
- package/resources/buddy-creator/assets/licenses/micromark-factory-title.txt +0 -26
- package/resources/buddy-creator/assets/licenses/micromark-factory-whitespace.txt +0 -26
- package/resources/buddy-creator/assets/licenses/micromark-util-character.txt +0 -26
- package/resources/buddy-creator/assets/licenses/micromark-util-chunked.txt +0 -26
- package/resources/buddy-creator/assets/licenses/micromark-util-classify-character.txt +0 -26
- package/resources/buddy-creator/assets/licenses/micromark-util-combine-extensions.txt +0 -26
- package/resources/buddy-creator/assets/licenses/micromark-util-decode-numeric-character-reference.txt +0 -26
- package/resources/buddy-creator/assets/licenses/micromark-util-decode-string.txt +0 -26
- package/resources/buddy-creator/assets/licenses/micromark-util-encode.txt +0 -26
- package/resources/buddy-creator/assets/licenses/micromark-util-html-tag-name.txt +0 -26
- package/resources/buddy-creator/assets/licenses/micromark-util-normalize-identifier.txt +0 -26
- package/resources/buddy-creator/assets/licenses/micromark-util-resolve-all.txt +0 -26
- package/resources/buddy-creator/assets/licenses/micromark-util-sanitize-uri.txt +0 -26
- package/resources/buddy-creator/assets/licenses/micromark-util-subtokenize.txt +0 -26
- package/resources/buddy-creator/assets/licenses/micromark-util-symbol.txt +0 -26
- package/resources/buddy-creator/assets/licenses/micromark-util-types.txt +0 -26
- package/resources/buddy-creator/assets/licenses/micromark.txt +0 -26
- package/resources/buddy-creator/assets/licenses/ms.txt +0 -25
- package/resources/buddy-creator/assets/licenses/parse-entities.txt +0 -26
- package/resources/buddy-creator/assets/licenses/phosphor-icons--react.txt +0 -25
- package/resources/buddy-creator/assets/licenses/property-information.txt +0 -26
- package/resources/buddy-creator/assets/licenses/react-dom.txt +0 -25
- package/resources/buddy-creator/assets/licenses/react-markdown.txt +0 -25
- package/resources/buddy-creator/assets/licenses/react.txt +0 -25
- package/resources/buddy-creator/assets/licenses/remark-gfm.txt +0 -26
- package/resources/buddy-creator/assets/licenses/remark-parse.txt +0 -25
- package/resources/buddy-creator/assets/licenses/remark-rehype.txt +0 -26
- package/resources/buddy-creator/assets/licenses/remark-stringify.txt +0 -25
- package/resources/buddy-creator/assets/licenses/scheduler.txt +0 -25
- package/resources/buddy-creator/assets/licenses/space-separated-tokens.txt +0 -26
- package/resources/buddy-creator/assets/licenses/stringify-entities.txt +0 -26
- package/resources/buddy-creator/assets/licenses/style-to-js.txt +0 -26
- package/resources/buddy-creator/assets/licenses/style-to-object.txt +0 -26
- package/resources/buddy-creator/assets/licenses/trim-lines.txt +0 -26
- package/resources/buddy-creator/assets/licenses/trough.txt +0 -25
- package/resources/buddy-creator/assets/licenses/types--debug.txt +0 -25
- package/resources/buddy-creator/assets/licenses/types--estree-jsx.txt +0 -25
- package/resources/buddy-creator/assets/licenses/types--estree.txt +0 -25
- package/resources/buddy-creator/assets/licenses/types--hast.txt +0 -25
- package/resources/buddy-creator/assets/licenses/types--mdast.txt +0 -25
- package/resources/buddy-creator/assets/licenses/types--ms.txt +0 -25
- package/resources/buddy-creator/assets/licenses/types--unist.txt +0 -25
- package/resources/buddy-creator/assets/licenses/ungap--structured-clone.txt +0 -19
- package/resources/buddy-creator/assets/licenses/unified.txt +0 -25
- package/resources/buddy-creator/assets/licenses/unist-util-is.txt +0 -26
- package/resources/buddy-creator/assets/licenses/unist-util-position.txt +0 -26
- package/resources/buddy-creator/assets/licenses/unist-util-stringify-position.txt +0 -26
- package/resources/buddy-creator/assets/licenses/unist-util-visit-parents.txt +0 -26
- package/resources/buddy-creator/assets/licenses/unist-util-visit.txt +0 -26
- package/resources/buddy-creator/assets/licenses/vfile-message.txt +0 -26
- package/resources/buddy-creator/assets/licenses/vfile.txt +0 -25
- package/resources/buddy-creator/assets/licenses/zwitch.txt +0 -26
- package/resources/buddy-creator/assets/preview/assets/index-B5VmkoSx.js +0 -39
- package/resources/buddy-creator/assets/preview/assets/index-DWKP6b-3.css +0 -4
- package/resources/buddy-creator/assets/preview/brand-logo.png +0 -0
- package/resources/buddy-creator/assets/preview/brand.css +0 -100
- package/resources/buddy-creator/assets/preview/index.html +0 -7
- package/resources/buddy-creator/assets/preview/reader.css +0 -165
- package/resources/buddy-creator/assets/preview/reader.js +0 -135
- package/resources/buddy-creator/assets/preview/widgets.css +0 -1
- package/resources/buddy-creator/assets/preview/widgets.js +0 -168
- package/resources/buddy-creator/assets/widget-reference/conversation.png +0 -0
- package/resources/buddy-creator/assets/widget-reference/expanded.png +0 -0
- package/resources/buddy-creator/assets/widget-v2/app.js +0 -62
- package/resources/buddy-creator/assets/widget-v2/style.css +0 -1
- package/resources/buddy-creator/assets/widget-v3/app.js +0 -65
- package/resources/buddy-creator/assets/widget-v3/style.css +0 -103
- package/resources/buddy-creator/assets/widget-v4/app.js +0 -66
- package/resources/buddy-creator/assets/widget-v4/style.css +0 -187
- package/resources/buddy-creator/references/artifact-schema.md +0 -70
- package/resources/buddy-creator/references/catalog.json +0 -401
- package/resources/buddy-creator/references/definition.md +0 -24
- package/resources/buddy-creator/references/dialogue-examples.md +0 -133
- package/resources/buddy-creator/references/host-guide.md +0 -219
- package/resources/buddy-creator/references/interview.md +0 -235
- package/resources/buddy-creator/references/knowledge.md +0 -40
- package/resources/buddy-creator/references/methods.md +0 -77
- package/resources/buddy-creator/references/opening.md +0 -14
- package/resources/buddy-creator/references/preview-panel.md +0 -66
- package/resources/buddy-creator/references/recovery.md +0 -46
- package/resources/buddy-creator/references/service.md +0 -76
- package/resources/buddy-creator/references/widgets-protocol.md +0 -83
- package/resources/buddy-creator/references/widgets.md +0 -99
- package/resources/buddy-creator/scripts/buddy.py +0 -108
- package/resources/buddy-creator/scripts/buddy_client.py +0 -67
- package/resources/buddy-creator/scripts/buddy_core.py +0 -865
- package/resources/buddy-creator/scripts/completion.py +0 -296
- package/resources/buddy-creator/scripts/interview.py +0 -215
- package/resources/buddy-creator/scripts/preview.py +0 -617
- package/resources/buddy-creator/scripts/preview_panel.py +0 -80
- package/resources/buddy-creator/scripts/sources.py +0 -239
- package/resources/buddy-creator/scripts/widget_render.py +0 -66
- package/resources/buddy-creator/scripts/widget_render_v1.py +0 -176
- package/resources/buddy-creator/scripts/widget_render_v2.py +0 -98
- package/resources/buddy-creator/scripts/widget_render_v3.py +0 -86
- package/resources/buddy-creator/scripts/widget_render_v4.py +0 -121
- package/resources/buddy-creator/scripts/widgets.py +0 -227
- package/resources/buddy-creator/version.json +0 -8
- package/resources/buddy-creator.manifest.json +0 -791
- package/resources/dada-object-avatar/SKILL.md +0 -37
- package/resources/dada-object-avatar/agents/openai.yaml +0 -4
- package/resources/dada-object-avatar/assets/examples/cat.png +0 -0
- package/resources/dada-object-avatar/assets/examples/cooking.png +0 -0
- package/resources/dada-object-avatar/assets/examples/divination.png +0 -0
- package/resources/dada-object-avatar/assets/examples/doctor.png +0 -0
- package/resources/dada-object-avatar/assets/examples/relationship.png +0 -0
- package/resources/dada-object-avatar/assets/examples/work.png +0 -0
- package/resources/dada-object-avatar/assets/preview.png +0 -0
- package/resources/dada-object-avatar/references/evaluation-rubric.md +0 -22
- package/resources/dada-object-avatar/references/examples.json +0 -8
- package/resources/dada-object-avatar/references/prompt-template.md +0 -27
- package/resources/dada-object-avatar/references/style-system.md +0 -54
- package/resources/dada-object-avatar/references/subject-design.md +0 -23
- package/resources/dada-object-avatar/references/training-guide.md +0 -14
- package/resources/dada-object-avatar/references/training-log.jsonl +0 -0
- package/resources/dada-object-avatar/references/training-record.schema.json +0 -20
- package/resources/dada-object-avatar/scripts/choose_gaze.py +0 -36
- package/resources/dada-object-avatar/scripts/record_example.py +0 -22
- package/resources/dada-object-avatar/scripts/validate_png.py +0 -52
- package/resources/dada-object-avatar.manifest.json +0 -85
package/dist/web/app.js
CHANGED
|
@@ -191,6 +191,8 @@
|
|
|
191
191
|
idle: hosted ? "已连接但 DEV 未启动" : "未启动",
|
|
192
192
|
disconnected: "未连接本地工程",
|
|
193
193
|
starting: "正在启动",
|
|
194
|
+
reloading: "正在重载",
|
|
195
|
+
reload_failed: "重载失败 · 等待修正",
|
|
194
196
|
ready: "DEV 运行中",
|
|
195
197
|
stopping: "正在停止",
|
|
196
198
|
failed: "启动失败",
|
|
@@ -201,6 +203,7 @@
|
|
|
201
203
|
if (!next || !Number.isFinite(next.revision)) return false;
|
|
202
204
|
if (Number.isFinite(state.dev?.revision) && next.revision < state.dev.revision) return false;
|
|
203
205
|
if (next.phase === "ready" && state.dev.phase !== "ready") state.simulatorLoaded = false;
|
|
206
|
+
if (state.dev.phase === "reload_failed" && ["ready", "reloading"].includes(next.phase)) elements.errorBanner.hidden = true;
|
|
204
207
|
state.dev = next;
|
|
205
208
|
if (next.ready?.buddy) state.buddy = next.ready.buddy;
|
|
206
209
|
return true;
|
|
@@ -254,8 +257,8 @@
|
|
|
254
257
|
elements.connectionState.dataset.phase = phase;
|
|
255
258
|
elements.connectionLabel.textContent = phase === "disconnected" ? "未连接本地工程" : phaseCopy(phase);
|
|
256
259
|
elements.devControl.disabled = !connectionReady || Boolean(devAction) || phase === "starting" || phase === "stopping";
|
|
257
|
-
elements.devControl.dataset.action =
|
|
258
|
-
elements.devControl.textContent = devAction === "start" ? "正在启动…" : devAction === "stop" ? "正在停止…" : phase === "failed" ? "重试启动" :
|
|
260
|
+
elements.devControl.dataset.action = ["ready", "reloading", "reload_failed"].includes(phase) ? "stop" : "start";
|
|
261
|
+
elements.devControl.textContent = devAction === "start" ? "正在启动…" : devAction === "stop" ? "正在停止…" : phase === "failed" ? "重试启动" : ["ready", "reloading", "reload_failed"].includes(phase) ? "停止 DEV" : phase === "starting" ? "正在启动…" : phase === "stopping" ? "正在停止…" : "启动 DEV";
|
|
259
262
|
elements.environmentAgent.textContent = phase === "ready"
|
|
260
263
|
? `${state.dev.ready?.agentId || "已就绪"} · CLI 托管`
|
|
261
264
|
: phaseCopy(phase);
|
|
@@ -277,7 +280,7 @@
|
|
|
277
280
|
updateSendButton();
|
|
278
281
|
syncPolling();
|
|
279
282
|
if (
|
|
280
|
-
phase === "failed"
|
|
283
|
+
(phase === "failed" || phase === "reload_failed")
|
|
281
284
|
&& state.dev.error?.message
|
|
282
285
|
&& state.reportedFailureRevision !== state.dev.revision
|
|
283
286
|
) {
|
|
@@ -301,6 +304,8 @@
|
|
|
301
304
|
const copy = !connectionChecked ? ["正在检查连接", "读取本地工程的连接状态。"]
|
|
302
305
|
: phase === "disconnected" ? [connectionInstance ? "本地连接已断开" : "连接本地工程", connectionFailure || (connectionInstance ? "正在等待重新连接。保留当前记录,连接恢复后继续调试。" : "点击上方「连接工程」,复制指令到终端运行。")]
|
|
303
306
|
: phase === "starting" ? ["正在启动调试", "正在准备运行环境,启动完成后即可发送消息。"]
|
|
307
|
+
: phase === "reloading" ? ["正在重载", "本地文件已修改,正在重新加载;当前回复可能中断。"]
|
|
308
|
+
: phase === "reload_failed" ? ["重载失败", state.dev.error?.message || "修正文件并保存后自动重试,也可停止 DEV。"]
|
|
304
309
|
: phase === "stopping" ? ["正在停止调试", "等待运行环境退出,当前记录会保留。"]
|
|
305
310
|
: phase === "failed" ? ["调试未能启动", "查看上方错误信息,修复工程后点击「重试启动」。"]
|
|
306
311
|
: phase === "idle" ? ["工程已连接", "点击上方「启动 DEV」,开始体验搭子。"]
|
|
@@ -311,7 +316,7 @@
|
|
|
311
316
|
guide.hidden = phase === "ready" && (runs.length > 0 || (state.debug.diagnostics?.records || []).some(record => record.kind !== "log"));
|
|
312
317
|
$("#workbench-state-title").textContent = copy[0];
|
|
313
318
|
$("#workbench-state-copy").textContent = copy[1];
|
|
314
|
-
$("#connection-description").textContent = !connectionChecked ? "检查中" : phase === "disconnected" ? "等待连接" : "
|
|
319
|
+
$("#connection-description").textContent = !connectionChecked ? "检查中" : phase === "disconnected" ? "等待连接" : "保存自动重载,可能中断回复;Widget 数据不自动迁移。";
|
|
315
320
|
const connectButton = $("#get-connect-command");
|
|
316
321
|
connectButton.className = `button ${connectionReady ? "button-secondary" : "button-primary"}`;
|
|
317
322
|
connectButton.textContent = connectionReady ? "连接指令" : connectionInstance ? "重新连接" : "连接工程";
|
|
@@ -556,7 +561,7 @@
|
|
|
556
561
|
}
|
|
557
562
|
|
|
558
563
|
const widgets = window.createSimulatorWidgets?.({ api, avatar: () => messageAvatar("assistant") });
|
|
559
|
-
function renderWidgets() { widgets?.update({ active: state.dev.phase === "ready" && !state.resetting, conversationRevision: state.simulator.conversationRevision, devRevision: state.dev.revision }); }
|
|
564
|
+
function renderWidgets() { widgets?.update({ reloading: ["reloading", "reload_failed"].includes(state.dev.phase), active: state.dev.phase === "ready" && !state.resetting, conversationRevision: state.simulator.conversationRevision, devRevision: state.dev.revision }); }
|
|
560
565
|
|
|
561
566
|
let messageSignature = "";
|
|
562
567
|
let followMessages = true;
|
|
@@ -689,7 +694,7 @@
|
|
|
689
694
|
async function controlDev() {
|
|
690
695
|
if (!connectionReady || state.dev.phase === "starting" || state.dev.phase === "stopping") return;
|
|
691
696
|
if (devAction) return;
|
|
692
|
-
devAction = state.dev.phase
|
|
697
|
+
devAction = ["ready", "reloading", "reload_failed"].includes(state.dev.phase) ? "stop" : "start";
|
|
693
698
|
elements.errorBanner.hidden = true;
|
|
694
699
|
renderDev();
|
|
695
700
|
try {
|
package/dist/web/widgets.css
CHANGED
|
@@ -1,31 +1,62 @@
|
|
|
1
|
-
/*
|
|
2
|
-
.widget-shortcut { position:absolute; z-index:3; top:
|
|
3
|
-
.widget-shortcut[hidden], .widget-overlay[hidden] { display:none; }
|
|
4
|
-
.widget-pill { display:flex; align-items:center; gap:
|
|
5
|
-
.widget-pill .widget-pill-avatar { width:
|
|
1
|
+
/* iOS ff1845b: dimensions in native points, scaled once with the device canvas. */
|
|
2
|
+
.widget-shortcut { position:absolute; z-index:3; top:calc(var(--widget-unit, 1px) * 100); left:calc(var(--widget-unit, 1px) * 12); max-width:calc(100% - calc(var(--widget-unit, 1px) * 24)); }
|
|
3
|
+
.widget-shortcut[hidden], .widget-overlay[hidden], .widget-more[hidden] { display:none; }
|
|
4
|
+
.widget-pill { display:flex; align-items:center; gap:calc(var(--widget-unit, 1px) * 8); height:calc(var(--widget-unit, 1px) * 52); max-width:calc(var(--widget-unit, 1px) * 260); border-radius:99px; padding:0 calc(var(--widget-unit, 1px) * 20) 0 calc(var(--widget-unit, 1px) * 16); color:#000; background:linear-gradient(110deg,#fff,#bbf1a7); border:calc(var(--widget-unit, 1px) * .5) solid #0000001f; font-size:calc(var(--widget-unit, 1px) * 16); font-weight:400; box-shadow:none; transition:padding .22s ease-in-out; }
|
|
5
|
+
.widget-pill .widget-pill-avatar { width:calc(var(--widget-unit, 1px) * 28); height:calc(var(--widget-unit, 1px) * 28); flex:none; border-radius:50%; }
|
|
6
6
|
.widget-pill-title { white-space:nowrap; overflow:hidden; text-overflow:ellipsis; }
|
|
7
|
-
.widget-chevron { margin-left:
|
|
8
|
-
.widget-
|
|
9
|
-
.widget-
|
|
10
|
-
.widget-
|
|
11
|
-
.widget-
|
|
12
|
-
.widget-
|
|
13
|
-
.widget-
|
|
14
|
-
.widget-
|
|
15
|
-
.widget-
|
|
16
|
-
.widget-
|
|
17
|
-
.widget-
|
|
18
|
-
.widget-modal
|
|
19
|
-
.widget-
|
|
20
|
-
.widget-
|
|
21
|
-
.widget-
|
|
22
|
-
.widget-
|
|
23
|
-
.widget-
|
|
24
|
-
.widget-
|
|
25
|
-
.widget-
|
|
26
|
-
.widget-
|
|
27
|
-
.widget-
|
|
28
|
-
.widget-
|
|
7
|
+
.widget-chevron { margin-left:calc(var(--widget-unit, 1px) * 8); font-size:calc(var(--widget-unit, 1px) * 18); }
|
|
8
|
+
.widget-shortcut[data-phase="collapsed"] .widget-pill { width:calc(var(--widget-unit, 1px) * 52); padding:0 calc(var(--widget-unit, 1px) * 12); }
|
|
9
|
+
.widget-shortcut[data-phase="collapsed"] :is(.widget-pill-title,.widget-chevron) { display:none; }
|
|
10
|
+
.widget-choices,.widget-options { position:absolute; z-index:5; background:#fff; border:calc(var(--widget-unit, 1px) * .3) solid #0000001f; border-radius:calc(var(--widget-unit, 1px) * 16); box-shadow:0 calc(var(--widget-unit, 1px) * 4) calc(var(--widget-unit, 1px) * 16) #0000000d; overflow:auto; }
|
|
11
|
+
.widget-choices { top:calc(100% + calc(var(--widget-unit, 1px) * 8)); left:0; width:calc(var(--widget-unit, 1px) * 240); max-height:calc(var(--widget-unit, 1px) * 320); padding:calc(var(--widget-unit, 1px) * 4) 0; }
|
|
12
|
+
.widget-choices button { display:flex; align-items:center; justify-content:space-between; gap:calc(var(--widget-unit, 1px) * 8); width:100%; min-height:calc(var(--widget-unit, 1px) * 48); padding:calc(var(--widget-unit, 1px) * 12) calc(var(--widget-unit, 1px) * 16); text-align:left; background:transparent; color:#000; font-size:calc(var(--widget-unit, 1px) * 16); font-weight:400; }
|
|
13
|
+
.widget-choices button>span:first-child { overflow:hidden; text-overflow:ellipsis; white-space:nowrap; }
|
|
14
|
+
.widget-arrow { color:#0009; }
|
|
15
|
+
.widget-choices button:hover,.widget-options button:hover { background:#f1f3f4; }
|
|
16
|
+
.widget-overlay { position:absolute; z-index:10; inset:0; display:flex; align-items:center; justify-content:center; background:#0003; opacity:1; animation:widget-fade .22s ease-in-out; transition:opacity .22s ease-in-out; }
|
|
17
|
+
.widget-overlay[data-state="closing"] { opacity:0; pointer-events:none; }
|
|
18
|
+
.widget-modal { position:relative; width:calc(100% - calc(var(--widget-unit, 1px) * 32)); height:65%; border-radius:calc(var(--widget-unit, 1px) * 24); background:#fff; color:#000; overflow:hidden; display:flex; flex-direction:column; padding-bottom:calc(var(--widget-unit, 1px) * 12); }
|
|
19
|
+
.widget-modal-header { display:flex; gap:0; align-items:center; min-height:calc(var(--widget-unit, 1px) * 52); padding:calc(var(--widget-unit, 1px) * 4) calc(var(--widget-unit, 1px) * 4) 0; flex:none; }
|
|
20
|
+
.widget-modal-header button,.widget-more-slot { width:calc(var(--widget-unit, 1px) * 44); height:calc(var(--widget-unit, 1px) * 44); flex:none; padding:0; background:transparent; color:#000; font-size:calc(var(--widget-unit, 1px) * 22); font-weight:400; }
|
|
21
|
+
.widget-modal-header h3 { flex:1; min-width:0; margin:0; text-align:center; font-size:calc(var(--widget-unit, 1px) * 14); font-weight:500; line-height:1.4; display:-webkit-box; -webkit-line-clamp:2; -webkit-box-orient:vertical; overflow:hidden; }
|
|
22
|
+
.widget-modal-body { flex:1; position:relative; min-height:0; overflow:hidden; font-size:calc(var(--widget-unit, 1px) * 14); }
|
|
23
|
+
.widget-document { position:absolute; top:0; left:0; border:0; display:block; background:#fff; }
|
|
24
|
+
.widget-loading,.widget-failure { position:absolute; inset:0; display:flex; flex-direction:column; align-items:center; justify-content:center; gap:calc(var(--widget-unit, 1px) * 12); padding:calc(var(--widget-unit, 1px) * 24); text-align:center; color:#0009; }
|
|
25
|
+
.widget-loading p,.widget-failure p { margin:0; font-size:calc(var(--widget-unit, 1px) * 14); line-height:1.5; }
|
|
26
|
+
.widget-failure h4 { margin:0; font-size:calc(var(--widget-unit, 1px) * 17); font-weight:600; color:#000; }
|
|
27
|
+
.widget-spinner { width:calc(var(--widget-unit, 1px) * 20); height:calc(var(--widget-unit, 1px) * 20); border:calc(var(--widget-unit, 1px) * 2) solid #0002; border-top-color:#0009; border-radius:50%; animation:widget-spin .85s linear infinite; }
|
|
28
|
+
.widget-failure-mark { display:grid; place-items:center; width:calc(var(--widget-unit, 1px) * 32); height:calc(var(--widget-unit, 1px) * 32); border:calc(var(--widget-unit, 1px) * 1.5) solid #0006; border-radius:50%; font-size:calc(var(--widget-unit, 1px) * 22); }
|
|
29
|
+
.widget-failure button { margin-top:calc(var(--widget-unit, 1px) * 4); padding:calc(var(--widget-unit, 1px) * 10) calc(var(--widget-unit, 1px) * 16); border-radius:calc(var(--widget-unit, 1px) * 8); background:#f1f3f4; color:#000; font-size:calc(var(--widget-unit, 1px) * 14); }
|
|
30
|
+
.widget-options { top:calc(var(--widget-unit, 1px) * 52); right:calc(var(--widget-unit, 1px) * 8); width:calc(var(--widget-unit, 1px) * 232); padding:calc(var(--widget-unit, 1px) * 8) 0; }
|
|
31
|
+
/* Menu rhythm and monochrome icons match the native action menu. */
|
|
32
|
+
.widget-options button { position:relative; display:flex; align-items:center; gap:calc(var(--widget-unit, 1px) * 6); width:100%; min-height:calc(var(--widget-unit, 1px) * 36); padding:calc(var(--widget-unit, 1px) * 6) calc(var(--widget-unit, 1px) * 16); text-align:left; background:transparent; color:#000; font-size:calc(var(--widget-unit, 1px) * 14); font-weight:400; }
|
|
33
|
+
.widget-options button+button { margin-top:calc(var(--widget-unit, 1px) * 8); }
|
|
34
|
+
.widget-options button+button::before { content:""; position:absolute; top:calc(var(--widget-unit, 1px) * -4); left:calc(var(--widget-unit, 1px) * 16); right:calc(var(--widget-unit, 1px) * 16); height:calc(var(--widget-unit, 1px) * .3); background:#00000026; }
|
|
35
|
+
.widget-options svg { width:calc(var(--widget-unit, 1px) * 20); height:calc(var(--widget-unit, 1px) * 20); flex:none; fill:none; stroke:currentColor; stroke-width:1.5; stroke-linecap:round; stroke-linejoin:round; }
|
|
36
|
+
.widget-options .widget-destructive,.widget-confirm .widget-destructive { color:#d73340; }
|
|
37
|
+
.widget-options button:disabled,.widget-confirm button:disabled { opacity:.45; cursor:wait; }
|
|
38
|
+
.widget-confirm-backdrop { position:absolute; inset:0; z-index:7; display:grid; place-items:center; background:#0003; padding:calc(var(--widget-unit, 1px) * 20); }
|
|
39
|
+
.widget-confirm { width:calc(var(--widget-unit, 1px) * 270); max-width:100%; padding:calc(var(--widget-unit, 1px) * 20) calc(var(--widget-unit, 1px) * 16) calc(var(--widget-unit, 1px) * 10); border-radius:calc(var(--widget-unit, 1px) * 16); background:#fff; text-align:center; }
|
|
40
|
+
.widget-confirm h4 { margin:0 0 calc(var(--widget-unit, 1px) * 8); font-size:calc(var(--widget-unit, 1px) * 17); font-weight:600; }
|
|
41
|
+
.widget-confirm p { margin:0; font-size:calc(var(--widget-unit, 1px) * 13); line-height:1.5; }
|
|
42
|
+
.widget-confirm-actions { display:flex; margin-top:calc(var(--widget-unit, 1px) * 16); border-top:calc(var(--widget-unit, 1px) * .5) solid #0002; }
|
|
43
|
+
.widget-confirm-actions button { flex:1; padding:calc(var(--widget-unit, 1px) * 12); font-size:calc(var(--widget-unit, 1px) * 16); background:transparent; color:#000; }
|
|
44
|
+
.widget-native-share { position:absolute; z-index:6; inset:calc(var(--widget-unit, 1px) * 52) 0 0; padding:calc(var(--widget-unit, 1px) * 28) calc(var(--widget-unit, 1px) * 24); background:#fff; font-size:calc(var(--widget-unit, 1px) * 14); }
|
|
45
|
+
.widget-native-share h4 { margin:0 0 calc(var(--widget-unit, 1px) * 12); font-size:calc(var(--widget-unit, 1px) * 17); }
|
|
46
|
+
.widget-native-share p { line-height:1.6; color:#0009; margin:0 0 calc(var(--widget-unit, 1px) * 24); }
|
|
47
|
+
.widget-native-share button { display:block; width:100%; margin-top:calc(var(--widget-unit, 1px) * 12); padding:calc(var(--widget-unit, 1px) * 12); border-radius:calc(var(--widget-unit, 1px) * 8); color:#000; background:#f1f3f4; font-size:calc(var(--widget-unit, 1px) * 14); }
|
|
48
|
+
.widget-pin-note { position:absolute; z-index:8; top:calc(17.5% + calc(var(--widget-unit, 1px) * 58)); left:50%; transform:translateX(-50%); max-width:90%; border-radius:calc(var(--widget-unit, 1px) * 16); background:#f1f3f4f5; box-shadow:0 calc(var(--widget-unit, 1px) * 4) calc(var(--widget-unit, 1px) * 16) #0001; padding:calc(var(--widget-unit, 1px) * 12) calc(var(--widget-unit, 1px) * 16); font-size:calc(var(--widget-unit, 1px) * 12); text-align:center; }
|
|
49
|
+
.widget-entry { width:calc(var(--widget-unit, 1px) * 297); max-width:100%; padding:calc(var(--widget-unit, 1px) * 12); border:calc(var(--widget-unit, 1px) * .3) solid #00000026; border-radius:calc(var(--widget-unit, 1px) * 16); background:#fff; color:#000; font-size:calc(var(--widget-unit, 1px) * 14); }
|
|
50
|
+
.widget-entry h4 { margin:0 0 calc(var(--widget-unit, 1px) * 12); font:500 calc(var(--widget-unit, 1px) * 14)/1.4 -apple-system,BlinkMacSystemFont,sans-serif; white-space:nowrap; overflow:hidden; text-overflow:ellipsis; }
|
|
51
|
+
.widget-entry p { margin:0; color:#000; font-size:calc(var(--widget-unit, 1px) * 14); line-height:1.4; overflow-wrap:anywhere; display:-webkit-box; -webkit-line-clamp:4; -webkit-box-orient:vertical; overflow:hidden; }
|
|
52
|
+
.widget-entry p.widget-secondary { color:#0009; }
|
|
53
|
+
.widget-entry-details { display:flex; align-items:center; gap:calc(var(--widget-unit, 1px) * 12); }
|
|
54
|
+
.widget-entry-photo { width:calc(var(--widget-unit, 1px) * 64); height:calc(var(--widget-unit, 1px) * 64); border-radius:50%; object-fit:cover; flex:none; }
|
|
55
|
+
.widget-entry-open { width:100%; height:calc(var(--widget-unit, 1px) * 40); min-height:0; padding:0; margin-top:calc(var(--widget-unit, 1px) * 12); background:#f1f3f4; color:#000; border-radius:calc(var(--widget-unit, 1px) * 8); font-size:calc(var(--widget-unit, 1px) * 14); font-weight:500; }
|
|
56
|
+
.widget-entry-metadata { display:flex; min-width:0; margin-top:calc(var(--widget-unit, 1px) * 12); font-size:calc(var(--widget-unit, 1px) * 11); line-height:1.4; color:#0009; }
|
|
57
|
+
.widget-creator { overflow:hidden; text-overflow:ellipsis; white-space:nowrap; }
|
|
58
|
+
.widget-permission { flex:none; }
|
|
29
59
|
.widget-entry button:focus-visible,.widget-shortcut button:focus-visible,.widget-overlay button:focus-visible { outline:2px solid #007a9b; outline-offset:2px; }
|
|
30
60
|
@keyframes widget-fade { from { opacity:0; } to { opacity:1; } }
|
|
31
|
-
@
|
|
61
|
+
@keyframes widget-spin { to { transform:rotate(360deg); } }
|
|
62
|
+
@media(prefers-reduced-motion:reduce) { .widget-overlay { animation-duration:.1s; transition-duration:.1s; } .widget-pill { transition:none; } }
|
package/dist/web/widgets.js
CHANGED
|
@@ -1,126 +1,280 @@
|
|
|
1
|
-
/* Client reference: my-life-buddies-ios dev
|
|
1
|
+
/* Client reference: my-life-buddies-ios dev ff1845b (2026-09-16).
|
|
2
|
+
Native point sizes share the phone's scale; Web content remains in an opaque sandbox. */
|
|
2
3
|
(() => {
|
|
3
|
-
const
|
|
4
|
-
|
|
4
|
+
const el = (tag, className = '', text) => {
|
|
5
|
+
const node = document.createElement(tag); node.className = className;
|
|
6
|
+
if (text !== undefined) node.textContent = text;
|
|
7
|
+
return node;
|
|
8
|
+
};
|
|
9
|
+
const button = (label, action, className = '') => {
|
|
10
|
+
const node = el('button', className, label); node.type = 'button'; node.addEventListener('click', action); return node;
|
|
11
|
+
};
|
|
12
|
+
const menuIcon = name => {
|
|
13
|
+
const paths = {
|
|
14
|
+
share: 'M12 15V3m-4 4 4-4 4 4M7 11H5v10h14V11h-2',
|
|
15
|
+
pin: 'm9 3 6 0-1 6 4 4v2H6v-2l4-4ZM12 15v6',
|
|
16
|
+
unpin: 'm3 3 18 18M9 3h6l-1 6 4 4v2M6 15h6m0 0v6',
|
|
17
|
+
trash: 'M4 7h16M9 7V4h6v3M6 7l1 14h10l1-14M10 11v6m4-6v6',
|
|
18
|
+
};
|
|
19
|
+
const svg = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
|
|
20
|
+
svg.setAttribute('viewBox', '0 0 24 24'); svg.setAttribute('aria-hidden', 'true');
|
|
21
|
+
const path = document.createElementNS(svg.namespaceURI, 'path'); path.setAttribute('d', paths[name]);
|
|
22
|
+
svg.append(path); return svg;
|
|
23
|
+
};
|
|
5
24
|
window.createSimulatorWidgets = ({ api, avatar }) => {
|
|
6
25
|
const shortcut = document.querySelector('#widget-shortcut');
|
|
7
26
|
const overlay = document.querySelector('#widget-overlay');
|
|
8
27
|
const phone = document.querySelector('.phone-screen');
|
|
9
|
-
|
|
10
|
-
let
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
28
|
+
const device = document.querySelector('.phone');
|
|
29
|
+
let reopenId, conversationScope;
|
|
30
|
+
let items = [], scope = '', generation = 0, ready = false, loading = false, lastRefresh = 0;
|
|
31
|
+
let failed = false, signature = '', selected, focusBefore, openRevision = 0, frameObserver;
|
|
32
|
+
let collapseTimer, closeTimer, loadTimer, documentFrame, documentToken, documentReady;
|
|
33
|
+
let phase = 'normal', choices, pill, busy = false, refreshPromise;
|
|
34
|
+
let documentLifecycle = false, canDelete = false;
|
|
35
|
+
const timers = () => { clearTimeout(loadTimer); frameObserver?.disconnect(); frameObserver = undefined; documentFrame = undefined; documentReady = undefined; };
|
|
36
|
+
const scale = () => device.clientWidth / 415;
|
|
37
|
+
const resize = new ResizeObserver(() => { phone.style.setProperty('--widget-unit', `${scale()}px`); });
|
|
38
|
+
resize.observe(device);
|
|
39
|
+
function setPhase(value) { phase = value; shortcut.dataset.phase = value; pill?.setAttribute('aria-expanded', String(value === 'choosing')); }
|
|
40
|
+
function scheduleCollapse() {
|
|
41
|
+
clearTimeout(collapseTimer);
|
|
42
|
+
if (!ready || !items.length || selected || document.hidden || phase !== 'normal') return;
|
|
43
|
+
collapseTimer = setTimeout(() => setPhase('collapsed'), 5000);
|
|
44
|
+
}
|
|
45
|
+
function closeChoices(reset = true) {
|
|
46
|
+
choices?.remove(); choices = undefined;
|
|
47
|
+
if (reset) { setPhase('normal'); scheduleCollapse(); }
|
|
48
|
+
}
|
|
49
|
+
function finishClose() {
|
|
50
|
+
overlay.hidden = true; overlay.replaceChildren(); delete overlay.dataset.state;
|
|
15
51
|
for (const child of phone.children) if (child !== overlay) child.inert = false;
|
|
16
|
-
if (
|
|
52
|
+
if (focusBefore?.isConnected) focusBefore.focus(); focusBefore = undefined; setPhase('normal'); scheduleCollapse();
|
|
53
|
+
}
|
|
54
|
+
function close(immediate = false) {
|
|
55
|
+
if (busy && !immediate) return;
|
|
56
|
+
clearTimeout(closeTimer); timers(); openRevision++; selected = undefined;
|
|
57
|
+
if (immediate || overlay.hidden) { finishClose(); return; }
|
|
58
|
+
overlay.dataset.state = 'closing';
|
|
59
|
+
closeTimer = setTimeout(finishClose, window.matchMedia('(prefers-reduced-motion:reduce)').matches ? 100 : 220);
|
|
17
60
|
}
|
|
18
|
-
function button(label, action, className = '') { const n = el('button', className, label); n.type = 'button'; n.addEventListener('click', action); return n; }
|
|
19
61
|
function render() {
|
|
20
|
-
const
|
|
21
|
-
if (
|
|
22
|
-
|
|
23
|
-
|
|
62
|
+
const next = JSON.stringify([ready, items, failed]);
|
|
63
|
+
if (next === signature) return;
|
|
64
|
+
signature = next;
|
|
65
|
+
const choosing = phase === 'choosing';
|
|
66
|
+
closeChoices(false); shortcut.replaceChildren(); shortcut.hidden = !ready || (!items.length && !failed);
|
|
24
67
|
if (shortcut.hidden) return;
|
|
25
68
|
if (!items.length) { shortcut.append(button('重新加载小挂件', () => refresh(true), 'widget-pill')); return; }
|
|
26
|
-
const
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
if (
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
69
|
+
const title = items.length === 1 ? items[0].title : '全部小挂件';
|
|
70
|
+
pill = button('', () => {
|
|
71
|
+
clearTimeout(collapseTimer);
|
|
72
|
+
if (items.length === 1) { open(items[0]); return; }
|
|
73
|
+
if (choices) { closeChoices(); return; }
|
|
74
|
+
setPhase('choosing');
|
|
75
|
+
choices = el('div', 'widget-choices'); choices.setAttribute('role', 'menu'); choices.setAttribute('aria-label', '会话小挂件列表');
|
|
76
|
+
for (const item of items) {
|
|
77
|
+
const row = button('', () => { closeChoices(false); open(item); }); row.setAttribute('role', 'menuitem');
|
|
78
|
+
row.append(el('span', '', item.title), el('span', 'widget-arrow', '›')); choices.append(row);
|
|
79
|
+
}
|
|
80
|
+
shortcut.append(choices); choices.firstChild?.focus();
|
|
33
81
|
}, 'widget-pill');
|
|
82
|
+
pill.setAttribute('aria-label', title);
|
|
34
83
|
const image = avatar(); image.classList.add('widget-pill-avatar');
|
|
35
|
-
pill.append(image, el('span', 'widget-pill-title',
|
|
36
|
-
if (items.length > 1) { pill.append(el('span', 'widget-chevron', '
|
|
37
|
-
shortcut.append(pill);
|
|
84
|
+
pill.append(image, el('span', 'widget-pill-title', title));
|
|
85
|
+
if (items.length > 1) { pill.append(el('span', 'widget-chevron', '›')); pill.setAttribute('aria-haspopup', 'menu'); }
|
|
86
|
+
shortcut.append(pill); setPhase(choosing ? 'normal' : phase);
|
|
38
87
|
}
|
|
39
88
|
async function refresh(force = false) {
|
|
40
|
-
if (!ready
|
|
89
|
+
if (!ready) return;
|
|
90
|
+
if (loading) return refreshPromise;
|
|
91
|
+
if (!force && Date.now() - lastRefresh < 1500) return;
|
|
41
92
|
loading = true; const mine = generation;
|
|
42
93
|
try {
|
|
43
|
-
|
|
94
|
+
refreshPromise = api('/api/simulator/widgets');
|
|
95
|
+
const value = await refreshPromise;
|
|
44
96
|
if (mine !== generation || !ready) return;
|
|
97
|
+
const previousIDs = items.map(item => item.id).join(',');
|
|
45
98
|
items = value.widgets || []; failed = false;
|
|
46
|
-
|
|
99
|
+
documentLifecycle = value.capabilities?.documentLifecycle === true;
|
|
100
|
+
canDelete = value.capabilities?.deleteWidget === true;
|
|
101
|
+
if (selected && !items.some(item => item.id === selected.id)) close(true);
|
|
102
|
+
if (previousIDs !== items.map(item => item.id).join(',')) { setPhase('normal'); scheduleCollapse(); }
|
|
103
|
+
// Revoked ownership must also remove an already open owner menu.
|
|
104
|
+
if (selected && items.find(item => item.id === selected.id)?.role !== 'owner') {
|
|
105
|
+
overlay.querySelector('.widget-options')?.remove();
|
|
106
|
+
overlay.querySelector('.widget-confirm-backdrop')?.remove();
|
|
107
|
+
overlay.querySelector('.widget-native-share')?.remove();
|
|
108
|
+
const more = overlay.querySelector('.widget-more'); if (more) more.hidden = true;
|
|
109
|
+
}
|
|
47
110
|
} catch { if (mine === generation) failed = true; }
|
|
48
111
|
finally { if (mine === generation) { loading = false; lastRefresh = Date.now(); render(); } }
|
|
49
112
|
}
|
|
113
|
+
function notify(text, error = false) {
|
|
114
|
+
overlay.querySelector('.widget-pin-note')?.remove();
|
|
115
|
+
const note = el('div', `widget-pin-note${error ? ' is-error' : ''}`, text); note.setAttribute('role', 'status'); overlay.append(note);
|
|
116
|
+
setTimeout(() => note.remove(), 4000);
|
|
117
|
+
}
|
|
118
|
+
function showConfirmation(card, item, mine, current) {
|
|
119
|
+
const shade = el('div', 'widget-confirm-backdrop');
|
|
120
|
+
const dialog = el('section', 'widget-confirm'); dialog.setAttribute('role', 'alertdialog'); dialog.setAttribute('aria-label', '删除小挂件?');
|
|
121
|
+
dialog.append(el('h4', '', '删除小挂件?'), el('p', '', '将删除小挂件和其中的数据,所有会话里的引用及首页固定都会失效。此操作不可恢复。'));
|
|
122
|
+
const actions = el('div', 'widget-confirm-actions');
|
|
123
|
+
const cancel = button('取消', () => { if (!busy) { shade.remove(); card.querySelector('.widget-more')?.focus(); } });
|
|
124
|
+
const remove = button('删除', async () => {
|
|
125
|
+
if (busy) return;
|
|
126
|
+
busy = true; cancel.disabled = true; remove.disabled = true; remove.textContent = '正在删除…';
|
|
127
|
+
try {
|
|
128
|
+
await api(`/api/simulator/widgets/${encodeURIComponent(item.id)}`, {method:'DELETE'});
|
|
129
|
+
if (mine !== openRevision || current !== generation) return;
|
|
130
|
+
items = items.filter(value => value.id !== item.id); render(); busy = false; close();
|
|
131
|
+
} catch {
|
|
132
|
+
if (mine !== openRevision || current !== generation) return;
|
|
133
|
+
shade.remove(); notify('操作失败,请稍后重试', true);
|
|
134
|
+
} finally { if (mine === openRevision && current === generation) busy = false; }
|
|
135
|
+
}, 'widget-destructive');
|
|
136
|
+
actions.append(cancel, remove); dialog.append(actions); shade.append(dialog); card.append(shade); cancel.focus();
|
|
137
|
+
}
|
|
138
|
+
function showShare(card) {
|
|
139
|
+
// Sharing chooses real conversations in the App; the simulator never fabricates recipients.
|
|
140
|
+
const notice = el('div', 'widget-native-share');
|
|
141
|
+
notice.append(el('h4', '', '在搭搭 App 中分享'), el('p', '', '使用真机预览打开这个搭子,在小挂件的「分享」中选择聊天。'));
|
|
142
|
+
notice.append(button('打开真机预览', () => { close(true); document.querySelector('#pair-device').click(); }), button('返回小挂件', () => notice.remove()));
|
|
143
|
+
card.append(notice); notice.querySelector('button').focus();
|
|
144
|
+
}
|
|
145
|
+
function ownerMenu(card, item, mine, current) {
|
|
146
|
+
const old = card.querySelector('.widget-options'); if (old) { old.remove(); return; }
|
|
147
|
+
const currentItem = items.find(value => value.id === item.id);
|
|
148
|
+
if (currentItem?.role !== 'owner' || busy) return;
|
|
149
|
+
const menu = el('div', 'widget-options'); menu.setAttribute('role', 'menu');
|
|
150
|
+
const share = button('分享', () => { menu.remove(); showShare(card); });
|
|
151
|
+
const pin = button(currentItem.isPinned ? '从首页取消固定' : '固定到首页', async () => {
|
|
152
|
+
if (busy) return; busy = true; menu.querySelectorAll('button').forEach(node => { node.disabled = true; });
|
|
153
|
+
try {
|
|
154
|
+
await api(`/api/simulator/widgets/${encodeURIComponent(item.id)}/pin`, {method:currentItem.isPinned ? 'DELETE' : 'PUT'});
|
|
155
|
+
if (mine !== openRevision || current !== generation) return;
|
|
156
|
+
currentItem.isPinned = !currentItem.isPinned; menu.remove(); notify(currentItem.isPinned ? '已固定到首页' : '已从首页取消固定');
|
|
157
|
+
} catch {
|
|
158
|
+
if (mine === openRevision && current === generation) { menu.remove(); notify('操作失败,请稍后重试', true); }
|
|
159
|
+
} finally { if (mine === openRevision && current === generation) busy = false; }
|
|
160
|
+
});
|
|
161
|
+
const remove = button('删除小挂件', () => { menu.remove(); showConfirmation(card, item, mine, current); }, 'widget-destructive');
|
|
162
|
+
share.prepend(menuIcon('share')); pin.prepend(menuIcon(currentItem.isPinned ? 'unpin' : 'pin')); remove.prepend(menuIcon('trash'));
|
|
163
|
+
for (const row of [share, pin, ...(canDelete ? [remove] : [])]) { row.setAttribute('role', 'menuitem'); menu.append(row); }
|
|
164
|
+
card.append(menu); share.focus();
|
|
165
|
+
}
|
|
50
166
|
async function open(item) {
|
|
51
167
|
if (!item.type || !ready) return;
|
|
52
168
|
const mine = ++openRevision, current = generation;
|
|
53
169
|
if (overlay.hidden) focusBefore = document.activeElement;
|
|
54
|
-
|
|
170
|
+
clearTimeout(collapseTimer); clearTimeout(closeTimer); closeChoices(false); timers(); busy = false;
|
|
55
171
|
selected = item; document.querySelector('#message-input').blur();
|
|
56
|
-
overlay.replaceChildren(); overlay.hidden = false;
|
|
172
|
+
overlay.replaceChildren(); overlay.hidden = false; overlay.dataset.state = 'opening';
|
|
57
173
|
const card = el('section', 'widget-modal'); card.setAttribute('role', 'dialog'); card.setAttribute('aria-modal', 'true'); card.setAttribute('aria-label', item.title);
|
|
58
174
|
const header = el('header', 'widget-modal-header');
|
|
59
|
-
const dismiss = button('×', close); dismiss.setAttribute('aria-label', '关闭小挂件');
|
|
60
|
-
const more = button('···', () =>
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
await api(`/api/simulator/widgets/${encodeURIComponent(item.id)}/pin`, { method: currentItem.isPinned ? 'DELETE' : 'PUT' });
|
|
68
|
-
if (mine !== openRevision || current !== generation) return;
|
|
69
|
-
currentItem.isPinned = !currentItem.isPinned; menu.remove();
|
|
70
|
-
const note = el('div', 'widget-pin-note', currentItem.isPinned ? '已固定到首页' : '已从首页取消固定'); note.setAttribute('role', 'status'); card.append(note); setTimeout(() => note.remove(), 4000);
|
|
71
|
-
} catch (error) { if (mine === openRevision) { menu.replaceChildren(el('p', '', error.message), button('知道了', () => menu.remove())); } }
|
|
72
|
-
}));
|
|
73
|
-
menu.append(button('刷新', () => open(item))); card.append(menu);
|
|
74
|
-
}); more.setAttribute('aria-label', '小挂件更多');
|
|
75
|
-
header.append(dismiss, el('h3', '', item.title), more);
|
|
76
|
-
const body = el('div', 'widget-modal-body'); body.append(el('p', 'widget-loading', '正在打开小挂件…'));
|
|
77
|
-
card.append(header, body); overlay.append(card);
|
|
175
|
+
const dismiss = button('×', () => close()); dismiss.setAttribute('aria-label', '关闭小挂件');
|
|
176
|
+
const more = button('···', () => ownerMenu(card, item, mine, current), 'widget-more'); more.setAttribute('aria-label', '小挂件更多'); more.hidden = true;
|
|
177
|
+
const slot = el('div', 'widget-more-slot'); slot.append(more);
|
|
178
|
+
header.append(dismiss, el('h3', '', item.title), slot);
|
|
179
|
+
const body = el('div', 'widget-modal-body'); body.setAttribute('aria-busy', 'true');
|
|
180
|
+
const loadingView = el('div', 'widget-loading'); loadingView.setAttribute('role', 'status');
|
|
181
|
+
loadingView.append(el('span', 'widget-spinner'), el('p', '', '正在打开小挂件…'));
|
|
182
|
+
body.append(loadingView); card.append(header, body); overlay.append(card);
|
|
78
183
|
for (const child of phone.children) if (child !== overlay) child.inert = true;
|
|
79
184
|
dismiss.focus();
|
|
80
|
-
|
|
81
|
-
const url = previewUrl(`/api/simulator/widgets/${encodeURIComponent(item.id)}`);
|
|
82
|
-
// Probe errors in the parent; actual HTML executes only inside the opaque sandbox document.
|
|
83
|
-
const response = await fetch(url, { credentials: 'same-origin' });
|
|
84
|
-
if (!response.ok) { const error = await response.json(); throw new Error(error.error?.message || error.error || '小挂件暂时无法打开'); }
|
|
85
|
-
await response.arrayBuffer();
|
|
185
|
+
const failedLoad = () => {
|
|
86
186
|
if (mine !== openRevision || current !== generation) return;
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
187
|
+
timers(); body.setAttribute('aria-busy', 'false');
|
|
188
|
+
const failure = el('div', 'widget-failure'); failure.setAttribute('role', 'status');
|
|
189
|
+
failure.append(el('span', 'widget-failure-mark', '!'), el('h4', '', '小挂件暂时无法打开'), el('p', '', '加载失败,请稍后重试'), button('重新加载', () => open(item)));
|
|
190
|
+
body.replaceChildren(failure);
|
|
191
|
+
};
|
|
192
|
+
const frame = el('iframe', 'widget-document'); frame.title = item.title; frame.setAttribute('sandbox', 'allow-scripts'); frame.referrerPolicy = 'no-referrer'; frame.style.visibility = 'hidden';
|
|
193
|
+
documentFrame = frame; documentToken = crypto.randomUUID();
|
|
194
|
+
const url = new URL(window.previewApiUrl?.(`/api/simulator/widgets/${encodeURIComponent(item.id)}`) ?? `/api/simulator/widgets/${encodeURIComponent(item.id)}`, location.href);
|
|
195
|
+
url.searchParams.set('render', documentToken); frame.src = url.href;
|
|
196
|
+
documentReady = state => {
|
|
97
197
|
if (mine !== openRevision || current !== generation) return;
|
|
98
|
-
|
|
99
|
-
|
|
198
|
+
if (state !== 'ready') { failedLoad(); return; }
|
|
199
|
+
clearTimeout(loadTimer); loadingView.remove(); frame.style.visibility = 'visible'; body.setAttribute('aria-busy', 'false');
|
|
200
|
+
};
|
|
201
|
+
frame.addEventListener('error', failedLoad);
|
|
202
|
+
// Older CLI bridges cannot send lifecycle signals. Keep their document load usable.
|
|
203
|
+
frame.addEventListener('load', () => { if (!documentLifecycle) documentReady?.('ready'); });
|
|
204
|
+
body.append(frame);
|
|
205
|
+
frameObserver = new ResizeObserver(() => {
|
|
206
|
+
const factor = scale(); if (!factor) return;
|
|
207
|
+
frame.style.width = `${body.clientWidth / factor}px`; frame.style.height = `${body.clientHeight / factor}px`;
|
|
208
|
+
frame.style.transform = `scale(${factor})`; frame.style.transformOrigin = 'top left';
|
|
209
|
+
}); frameObserver.observe(body);
|
|
210
|
+
loadTimer = setTimeout(failedLoad, 15000);
|
|
211
|
+
await refresh(true);
|
|
212
|
+
if (mine === openRevision && current === generation) more.hidden = failed || items.find(value => value.id === item.id)?.role !== 'owner';
|
|
100
213
|
}
|
|
214
|
+
window.addEventListener('message', event => {
|
|
215
|
+
if (!documentFrame || event.source !== documentFrame.contentWindow || event.data?.channel !== 'buddy-widget-document' || event.data?.render !== documentToken) return;
|
|
216
|
+
documentReady?.(event.data.state);
|
|
217
|
+
});
|
|
101
218
|
overlay.addEventListener('click', event => { if (event.target === overlay) close(); });
|
|
219
|
+
document.addEventListener('pointerdown', event => {
|
|
220
|
+
if (choices && !shortcut.contains(event.target)) closeChoices();
|
|
221
|
+
const menu = overlay.querySelector('.widget-options');
|
|
222
|
+
if (menu && !menu.contains(event.target) && !event.target.closest('.widget-more')) menu.remove();
|
|
223
|
+
});
|
|
102
224
|
document.addEventListener('keydown', event => {
|
|
103
|
-
if (event.key === 'Escape') {
|
|
225
|
+
if (event.key === 'Escape') {
|
|
226
|
+
if (busy) return;
|
|
227
|
+
const nested = overlay.querySelector('.widget-confirm-backdrop,.widget-native-share,.widget-options');
|
|
228
|
+
if (nested) { nested.remove(); overlay.querySelector('.widget-more')?.focus(); } else if (!overlay.hidden) close(); else closeChoices();
|
|
229
|
+
}
|
|
104
230
|
if (event.key === 'Tab' && !overlay.hidden) {
|
|
105
|
-
const
|
|
231
|
+
const scope = overlay.querySelector('.widget-confirm,.widget-native-share') || overlay;
|
|
232
|
+
const controls = [...scope.querySelectorAll('button:not(:disabled), iframe')].filter(node => !node.hidden && node.getClientRects().length);
|
|
233
|
+
const first = controls[0], last = controls.at(-1);
|
|
106
234
|
if (event.shiftKey && document.activeElement === first) { event.preventDefault(); last?.focus(); }
|
|
107
235
|
else if (!event.shiftKey && document.activeElement === last) { event.preventDefault(); first?.focus(); }
|
|
108
236
|
}
|
|
109
237
|
});
|
|
110
|
-
document.addEventListener('visibilitychange', () => {
|
|
238
|
+
document.addEventListener('visibilitychange', () => {
|
|
239
|
+
clearTimeout(collapseTimer); closeChoices(false);
|
|
240
|
+
if (!document.hidden) { setPhase('normal'); scheduleCollapse(); void refresh(true); }
|
|
241
|
+
});
|
|
242
|
+
window.addEventListener('pagehide', () => { resize.disconnect(); timers(); clearTimeout(collapseTimer); clearTimeout(closeTimer); });
|
|
111
243
|
return {
|
|
112
|
-
update({
|
|
244
|
+
update({active, conversationRevision, devRevision, reloading = false}) {
|
|
113
245
|
const key = `${conversationRevision || 0}:${devRevision || 0}`;
|
|
114
|
-
if (
|
|
115
|
-
|
|
246
|
+
if (conversationScope !== conversationRevision || (!active && !reloading)) reopenId = undefined;
|
|
247
|
+
else if (selected && (key !== scope || reloading)) reopenId = selected.id;
|
|
248
|
+
conversationScope = conversationRevision;
|
|
249
|
+
if (key !== scope || !active) {
|
|
250
|
+
scope = key; generation++; ready = false; items = []; failed = false; loading = false; lastRefresh = 0; busy = false; documentLifecycle = false; canDelete = false;
|
|
251
|
+
clearTimeout(collapseTimer); close(true);
|
|
252
|
+
}
|
|
253
|
+
ready = active; render();
|
|
254
|
+
const mine = generation;
|
|
255
|
+
void refresh().then(() => {
|
|
256
|
+
if (!ready || mine !== generation || !reopenId) return;
|
|
257
|
+
const item = items.find(value => value.id === reopenId);
|
|
258
|
+
reopenId = undefined;
|
|
259
|
+
if (item) void open(item);
|
|
260
|
+
});
|
|
116
261
|
},
|
|
117
|
-
reset() { generation++; ready = false; items = []; loading = false; failed = false; close(); render(); },
|
|
262
|
+
reset() { reopenId = undefined; generation++; ready = false; items = []; loading = false; failed = false; busy = false; clearTimeout(collapseTimer); close(true); render(); },
|
|
118
263
|
card(item) {
|
|
119
264
|
const card = el('div', 'widget-entry'); card.append(el('h4', '', item.title));
|
|
120
265
|
const details = el('div', 'widget-entry-details');
|
|
121
|
-
if (item.type && item.imageMediaId) {
|
|
122
|
-
|
|
266
|
+
if (item.type && item.imageMediaId) {
|
|
267
|
+
const image = el('img', 'widget-entry-photo'); image.alt = `${item.title}的照片`;
|
|
268
|
+
image.src = window.previewApiUrl?.(`/api/simulator/widgets/${encodeURIComponent(item.id)}/image?mediaId=${encodeURIComponent(item.imageMediaId)}`) ?? `/api/simulator/widgets/${encodeURIComponent(item.id)}/image?mediaId=${encodeURIComponent(item.imageMediaId)}`;
|
|
269
|
+
image.addEventListener('error', () => { image.hidden = true; }); details.append(image);
|
|
270
|
+
}
|
|
271
|
+
details.append(el('p', item.type && item.summary?.trim() ? '' : 'widget-secondary', item.type ? item.summary?.trim() || '打开查看最新内容' : '此小挂件已删除或不可用')); card.append(details);
|
|
123
272
|
if (item.type) card.append(button('去查看', () => open(item), 'widget-entry-open'));
|
|
273
|
+
const permission = {owner:'可管理、分享',editor:'可编辑',viewer:'仅支持查看'}[item.role];
|
|
274
|
+
const metadata = el('div', 'widget-entry-metadata');
|
|
275
|
+
if (item.creatorName?.trim()) metadata.append(el('span', 'widget-creator', `${item.creatorName.trim()}创建`));
|
|
276
|
+
if (permission) metadata.append(el('span', 'widget-permission', (metadata.children.length ? '|' : '') + permission));
|
|
277
|
+
if (metadata.children.length) card.append(metadata);
|
|
124
278
|
return card;
|
|
125
279
|
},
|
|
126
280
|
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@my-life-buddies/cli",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.15.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "搭搭 Developer Platform 命令行工具",
|
|
6
6
|
"publishConfig": {
|
|
@@ -39,7 +39,7 @@
|
|
|
39
39
|
"ws": "8.21.3"
|
|
40
40
|
},
|
|
41
41
|
"devDependencies": {
|
|
42
|
-
"@buddy/cli-core": "0.
|
|
43
|
-
"@buddy/preview": "0.
|
|
42
|
+
"@buddy/cli-core": "0.15.0",
|
|
43
|
+
"@buddy/preview": "0.15.0"
|
|
44
44
|
}
|
|
45
45
|
}
|