dsh-figmark 1.2.3 → 1.2.5

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 CHANGED
@@ -73,15 +73,17 @@ window.__ModuleLoader__.load({
73
73
  reveal_hidden: "元素目前不可见,请先展开或翻到对应页面",
74
74
  reveal_wrong_surface: "无法切换到标注所在的页面",
75
75
  reveal_timeout: "页面没有响应定位请求",
76
- serviceKicker: "LOCAL RUNTIME",
77
76
  serviceTitle: "本机网页",
78
- serviceHint: "只标注已经在 http:// 上运行、可交互的页面。静态 HTML 只读预览,不进入标注。",
79
- serviceSearch: "输入 localhost:5173 或运行中的地址",
77
+ serviceHint: "打开正在运行的本机网页,直接圈画与批注。",
78
+ serviceSearch: "搜索页面,或输入 localhost:5173",
80
79
  serviceOpen: "打开",
81
80
  serviceOpening: "正在连接",
82
81
  serviceLocal: "正在运行",
83
82
  serviceScanning: "检索中",
84
83
  serviceRescan: "重新检索",
84
+ serviceNoMatch: "没有匹配的页面",
85
+ serviceChooseMatch: "找到多个页面,请选择下方的一项。",
86
+ serviceSearchHint: "换个关键词,或输入完整的本机地址。",
85
87
  serviceEmpty: "没有发现本机服务",
86
88
  serviceEmptyHint: "先启动开发服务器,或在上方直接输入地址。",
87
89
  serviceBadAddress: "请输入本机 HTTP 地址",
@@ -169,18 +171,25 @@ window.__ModuleLoader__.load({
169
171
  detailHint: "当前选中",
170
172
  detailUnset: "未选中",
171
173
  detailIdle: "在图上点一下、框一块或画一笔,就能为那处写说明。",
172
- cardOpen: "打开完整标注",
174
+ cardOpen: "打开原文件",
173
175
  cardOpenLive: "回到本机网页",
174
176
  cardCaptureOmitted: "位置标注",
175
177
  cardCaptureFailed: "截图失败",
176
- cardMore: "还有 {n} 处",
178
+ cardMore: "查看其余 {n} 处",
179
+ cardExpand: "查看完整批注",
180
+ cardCollapse: "收起详情",
181
+ cardOpenFailed: "未能打开原内容,请重试。",
177
182
  cardGoal: "目标",
178
183
  cardPage: "第 {n} 页",
184
+ cardPages: "共 {n} 页",
179
185
  cardCount: "{n} 处标注",
180
186
  pendingCount: "{n}",
181
187
  pendingRemove: "移除附图标注",
182
- pendingSendHint: "发送当前对话框中的全部标注",
183
- pendingSendNow: "发送全部标注",
188
+ pendingSendHint: "发送当前消息及全部待发送标注",
189
+ pendingSendMessage: "发送消息",
190
+ pendingSending: "发送中…",
191
+ pendingUnavailable: "当前输入框暂时无法发送。",
192
+ pendingSendNow: "发送这份标注",
184
193
  pendingFailed: "没能发送:"
185
194
  };
186
195
  /** English copy. */
@@ -223,15 +232,17 @@ window.__ModuleLoader__.load({
223
232
  reveal_hidden: "The element is not visible yet",
224
233
  reveal_wrong_surface: "Could not switch to the mark’s page",
225
234
  reveal_timeout: "The page did not answer the locate request",
226
- serviceKicker: "LOCAL RUNTIME",
227
235
  serviceTitle: "Local pages",
228
- serviceHint: "Annotate interactive pages already served over http://. Static HTML is preview-only.",
229
- serviceSearch: "localhost:5173 or another running address",
236
+ serviceHint: "Open a running local page to draw and comment.",
237
+ serviceSearch: "Find a page or enter localhost:5173",
230
238
  serviceOpen: "Open",
231
239
  serviceOpening: "Connecting",
232
240
  serviceLocal: "Running",
233
241
  serviceScanning: "Scanning",
234
242
  serviceRescan: "Scan again",
243
+ serviceNoMatch: "No matching pages",
244
+ serviceChooseMatch: "Several pages match. Choose one below.",
245
+ serviceSearchHint: "Try another keyword or enter the full local address.",
235
246
  serviceEmpty: "No local services found",
236
247
  serviceEmptyHint: "Start a development server, or type its address above.",
237
248
  serviceBadAddress: "Enter a local HTTP address",
@@ -319,18 +330,25 @@ window.__ModuleLoader__.load({
319
330
  detailHint: "selected",
320
331
  detailUnset: "nothing selected",
321
332
  detailIdle: "Click the figure, or box a region or draw a stroke, to write about that spot.",
322
- cardOpen: "Open the full annotation",
333
+ cardOpen: "Open source file",
323
334
  cardOpenLive: "Return to the local page",
324
335
  cardCaptureOmitted: "location only",
325
336
  cardCaptureFailed: "screenshot failed",
326
- cardMore: "and {n} more",
337
+ cardMore: "Show {n} more",
338
+ cardExpand: "Review all annotations",
339
+ cardCollapse: "Collapse details",
340
+ cardOpenFailed: "Could not open the source. Try again.",
327
341
  cardGoal: "Goal",
328
342
  cardPage: "page {n}",
343
+ cardPages: "{n} pages",
329
344
  cardCount: "{n} annotations",
330
345
  pendingCount: "{n}",
331
346
  pendingRemove: "Remove the annotation",
332
- pendingSendHint: "Send every annotation in this composer",
333
- pendingSendNow: "Send every annotation",
347
+ pendingSendHint: "Send the message and all staged reviews",
348
+ pendingSendMessage: "Send message",
349
+ pendingSending: "Sending…",
350
+ pendingUnavailable: "This composer cannot send right now.",
351
+ pendingSendNow: "Send this review",
334
352
  pendingFailed: "Could not send: "
335
353
  };
336
354
  /** Locale namespace this dictionary registers under. */
@@ -3970,6 +3988,7 @@ window.__ModuleLoader__.load({
3970
3988
  style: { pointerEvents: "none" },
3971
3989
  children: [boxed ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("rect", {
3972
3990
  "data-da-element-box": mark.id,
3991
+ onPointerDown: props.onPress,
3973
3992
  x: box.x,
3974
3993
  y: box.y,
3975
3994
  width: box.width,
@@ -4001,7 +4020,8 @@ window.__ModuleLoader__.load({
4001
4020
  const code = elideCode(anchorCodeName(mark.anchor));
4002
4021
  const pad = 4 * unit;
4003
4022
  const fontSize = CODE_FONT_SIZE * unit;
4004
- const width = code.length * fontSize * .62 + pad * 2;
4023
+ const advance = Array.from(code).reduce((sum, char) => sum + (char.charCodeAt(0) > 255 ? 1 : .62), 0);
4024
+ const width = Math.min(advance * fontSize + pad * 2, Math.max(pad * 2, paper.right - paper.left));
4005
4025
  const height = fontSize + pad * 1.6;
4006
4026
  const gap = 4 * unit;
4007
4027
  const limit = Math.max(paper.left, paper.right - width);
@@ -4019,17 +4039,27 @@ window.__ModuleLoader__.load({
4019
4039
  fillOpacity: .94,
4020
4040
  stroke: mark.color,
4021
4041
  strokeWidth: 1.2 * unit
4022
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("text", {
4023
- "data-da-element-code": mark.id,
4042
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("foreignObject", {
4024
4043
  x: x + pad,
4025
- y: y + height / 2,
4026
- fontFamily: CODE_FONT_FAMILY,
4027
- fontSize,
4028
- fontWeight: 600,
4029
- fill: CODE_TEXT_COLOR,
4030
- stroke: "none",
4031
- dominantBaseline: "central",
4032
- children: code
4044
+ y,
4045
+ width: Math.max(0, width - pad * 2),
4046
+ height,
4047
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
4048
+ "data-da-element-code": mark.id,
4049
+ title: anchorCodeName(mark.anchor),
4050
+ style: {
4051
+ display: "block",
4052
+ overflow: "hidden",
4053
+ textOverflow: "ellipsis",
4054
+ whiteSpace: "nowrap",
4055
+ fontFamily: CODE_FONT_FAMILY,
4056
+ fontSize,
4057
+ fontWeight: 600,
4058
+ color: CODE_TEXT_COLOR,
4059
+ lineHeight: `${height}px`
4060
+ },
4061
+ children: code
4062
+ })
4033
4063
  })] });
4034
4064
  }
4035
4065
  /** One mark: its shape, its hit area, and whatever its selection asks for. */
@@ -4120,7 +4150,10 @@ window.__ModuleLoader__.load({
4120
4150
  bottom: -props.originY + props.height - 2
4121
4151
  },
4122
4152
  boxed: props.elementExtent === "box",
4123
- named: selected
4153
+ named: selected,
4154
+ onPress: (event) => {
4155
+ onPressMark(mark, event);
4156
+ }
4124
4157
  }),
4125
4158
  selected && !isDraft && isPoint ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(FixedFrame, {
4126
4159
  box: pointMarkBox(shown, unit),
@@ -4815,14 +4848,14 @@ window.__ModuleLoader__.load({
4815
4848
  * Upload one file as a draft attachment without putting it in the rail yet.
4816
4849
  * @param conversation - the conversation service.
4817
4850
  * @param sessionId - session the draft belongs to.
4818
- * @param file - the file to hold, when there is one.
4851
+ * @param files - the rendered review images to hold.
4819
4852
  * @returns the ids the send will add, empty when the file could not be made into one.
4820
4853
  */
4821
- function holdFile(conversation, sessionId, file) {
4854
+ function holdFiles(conversation, sessionId, files) {
4822
4855
  const create = conversation.createDrafts?.bind(conversation);
4823
- if (file === void 0 || create === void 0) return [];
4856
+ if (files.length === 0 || create === void 0) return [];
4824
4857
  try {
4825
- return create(sessionId, [file]).map((draft) => draft.id);
4858
+ return create(sessionId, files);
4826
4859
  } catch {
4827
4860
  return [];
4828
4861
  }
@@ -4833,9 +4866,10 @@ window.__ModuleLoader__.load({
4833
4866
  * @param sessionId - session the draft belongs to.
4834
4867
  * @param message - the message, verbatim.
4835
4868
  * @param file - the review picture, when the surface has one to attach.
4869
+ * @param additionalFiles - marked page images of a multi-page review.
4836
4870
  * @returns what was armed, or undefined when this session cannot be armed.
4837
4871
  */
4838
- function armReview(conversation, sessionId, message, file) {
4872
+ function armReview(conversation, sessionId, message, file, additionalFiles = []) {
4839
4873
  let shell;
4840
4874
  try {
4841
4875
  shell = conversation.input.shell(sessionId);
@@ -4843,10 +4877,12 @@ window.__ModuleLoader__.load({
4843
4877
  return;
4844
4878
  }
4845
4879
  if (typeof shell.submit !== "function") return void 0;
4880
+ const drafts = holdFiles(conversation, sessionId, file === void 0 ? additionalFiles : [file, ...additionalFiles]);
4846
4881
  return {
4847
4882
  shell,
4848
4883
  text: message,
4849
- attachmentIds: holdFile(conversation, sessionId, file)
4884
+ attachmentIds: drafts.map((draft) => draft.id),
4885
+ previewUrls: drafts.flatMap((draft) => draft.previewUrl === void 0 ? [] : [draft.previewUrl])
4850
4886
  };
4851
4887
  }
4852
4888
  /**
@@ -4909,12 +4945,13 @@ window.__ModuleLoader__.load({
4909
4945
  for (const claim of claims) {
4910
4946
  const { text, attachmentIds } = claim.armed;
4911
4947
  if (draft.includes(text)) continue;
4912
- if (attachmentIds.length > 0) {
4913
- if (!attach(shell, attachmentIds)) {
4948
+ const missing = attachmentIds.filter((id) => !live.attachmentIds?.includes(id));
4949
+ if (missing.length > 0) {
4950
+ if (!attach(shell, missing)) {
4914
4951
  unwind(shell, typed, added);
4915
4952
  return;
4916
4953
  }
4917
- added.push(...attachmentIds);
4954
+ added.push(...missing);
4918
4955
  }
4919
4956
  draft = draft.trim() === "" ? text : `${draft}\n\n${text}`;
4920
4957
  }
@@ -4959,22 +4996,34 @@ window.__ModuleLoader__.load({
4959
4996
  try {
4960
4997
  live = shell.snapshot;
4961
4998
  } catch {
4962
- for (const claim of claims) claim.done();
4999
+ for (const claim of claims) {
5000
+ claim.armed.admission?.release();
5001
+ claim.done();
5002
+ }
4963
5003
  return;
4964
5004
  }
4965
5005
  if (live.phase !== void 0 && live.phase !== "plain") {
4966
- for (const claim of claims) claim.done();
5006
+ for (const claim of claims) {
5007
+ claim.armed.admission?.release();
5008
+ claim.done();
5009
+ }
4967
5010
  return;
4968
5011
  }
4969
5012
  const draft = live.draft;
4970
5013
  if (claims.every((claim) => !draft.includes(claim.armed.text))) {
4971
- for (const claim of claims) claim.done();
5014
+ for (const claim of claims) {
5015
+ claim.armed.admission?.release();
5016
+ claim.done();
5017
+ }
4972
5018
  return;
4973
5019
  }
4974
5020
  try {
4975
5021
  shell.setDraft(merge.typed);
4976
5022
  } catch {}
4977
- for (const claim of claims) for (const id of claim.armed.attachmentIds) shell.removeAttachment?.(id);
5023
+ for (const claim of claims) {
5024
+ if (claim.armed.admission !== void 0) attach(shell, claim.armed.admission.ids.filter((id) => !live.attachmentIds?.includes(id)));
5025
+ if (claim.armed.staged !== true) for (const id of claim.armed.attachmentIds) shell.removeAttachment?.(id);
5026
+ }
4978
5027
  }
4979
5028
  /**
4980
5029
  * Take one card off whatever patch its shell is currently carrying.
@@ -5046,10 +5095,11 @@ window.__ModuleLoader__.load({
5046
5095
  }
5047
5096
  const send = () => {
5048
5097
  try {
5098
+ for (const claim of claims) for (const id of claim.armed.admission?.ids ?? []) if (shell.removeAttachment?.(id) !== true) return;
5049
5099
  original.apply(shell, args);
5050
- settleClaims(shell, claims, merge);
5051
5100
  } finally {
5052
5101
  waiting = false;
5102
+ settleClaims(shell, claims, merge);
5053
5103
  }
5054
5104
  };
5055
5105
  if (wroteThrough(shell, merge)) {
@@ -5102,13 +5152,39 @@ window.__ModuleLoader__.load({
5102
5152
  */
5103
5153
  function parkInComposer(input) {
5104
5154
  const { document: document_, locale, paths, sessionId } = input;
5105
- const message = buildAnnotationMessage(document_, paths, locale, input.file !== void 0, input.pageImages ?? 0, input.pageCapture);
5155
+ let armed = input.conversation === void 0 ? void 0 : armReview(input.conversation, sessionId, "", input.file, input.files);
5156
+ const planned = (input.file === void 0 ? 0 : 1) + (input.files?.length ?? 0);
5157
+ const attached = armed?.attachmentIds.length ?? planned;
5158
+ const pageCapture = input.pageCapture === "attached" && attached === 0 ? "failed" : input.pageCapture;
5159
+ const message = buildAnnotationMessage(document_, paths, locale, attached > 0, Math.min(input.pageImages ?? 0, attached), pageCapture);
5160
+ if (armed !== void 0 && input.conversation !== void 0) {
5161
+ const admission = armed.attachmentIds.length === 0;
5162
+ const drafts = admission && armed.shell.removeAttachment !== void 0 ? holdFiles(input.conversation, sessionId, [new File([Uint8Array.from(atob("iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mNk+A8AAQUBAScY42YAAAAASUVORK5CYII="), (char) => char.charCodeAt(0))], "figmark-draft.png", { type: "image/png" })]) : [];
5163
+ const ids = admission ? drafts.map((draft) => draft.id) : armed.attachmentIds;
5164
+ const conversation = input.conversation;
5165
+ if (ids.length > 0 && attach(armed.shell, ids)) armed = {
5166
+ ...armed,
5167
+ attachmentIds: ids,
5168
+ staged: true,
5169
+ ...admission ? {
5170
+ previewUrls: drafts.flatMap((draft) => draft.previewUrl === void 0 ? [] : [draft.previewUrl]),
5171
+ admission: {
5172
+ ids,
5173
+ release: () => {
5174
+ for (const id of ids) conversation.releaseDraftAttachment?.(id);
5175
+ }
5176
+ }
5177
+ } : {}
5178
+ };
5179
+ else {
5180
+ for (const id of ids) conversation.releaseDraftAttachment?.(id);
5181
+ armed = void 0;
5182
+ }
5183
+ }
5106
5184
  rememberDelivered(message, input.thumb);
5107
- const block = matchAnnotation(message, 0);
5108
- const armed = input.conversation === void 0 ? void 0 : armReview(input.conversation, sessionId, message, input.file);
5109
5185
  parkAnnotation({
5110
5186
  sessionId,
5111
- data: block?.data ?? {
5187
+ data: matchAnnotation(message, 0)?.data ?? {
5112
5188
  file: document_.figure.path,
5113
5189
  scope: "",
5114
5190
  target: "",
@@ -5119,7 +5195,10 @@ window.__ModuleLoader__.load({
5119
5195
  },
5120
5196
  raw: message,
5121
5197
  conversation: input.conversation,
5122
- armed,
5198
+ armed: armed === void 0 ? void 0 : {
5199
+ ...armed,
5200
+ text: message
5201
+ },
5123
5202
  send: input.send
5124
5203
  });
5125
5204
  }
@@ -8563,12 +8642,13 @@ body[data-ds-dark-theme] .da-root{--da-quiet:#9aa0a8}
8563
8642
  /* The hand-off appears only once the field says something: an empty pill is a
8564
8643
  place to type, not a decision to make. */
8565
8644
  .da-comment-send{display:flex;align-items:center;gap:4px;flex:0 0 auto;padding-bottom:3px;padding-right:3px}
8645
+ .da-comment-remove{flex:0 0 30px;margin-bottom:3px}
8566
8646
  .da-comment-send-btn{display:inline-flex;align-items:center;justify-content:center;width:30px;height:30px;
8567
8647
  border:0;border-radius:50%;background:var(--dsw-alias-interactive-bg-hover,rgba(127,127,127,.12));
8568
8648
  color:var(--dsw-alias-label-secondary,currentColor);cursor:pointer;padding:0}
8569
8649
  .da-comment-send-btn:hover{background:var(--dsw-alias-interactive-bg-hover,rgba(127,127,127,.2));
8570
8650
  color:var(--dsw-alias-label-primary,currentColor)}
8571
- .da-comment-send-btn:disabled{opacity:.45;cursor:default}
8651
+ .da-comment-send-btn[aria-disabled="true"],.da-comment-send-btn:disabled{opacity:.45;cursor:default}
8572
8652
  .da-comment-send-btn svg{width:15px;height:15px}
8573
8653
  /* Both ways out of the pill are the same neutral chip. Which hand-off is "primary"
8574
8654
  is the reader's judgement at the moment of sending, not something the panel gets to
@@ -8792,11 +8872,11 @@ body[data-ds-dark-theme] .da-root{--da-quiet:#9aa0a8}
8792
8872
  border:0.5px solid var(--dsw-alias-border-l2,rgba(128,128,128,.3));border-radius:12px;
8793
8873
  background:var(--dsw-alias-bg-base,rgba(255,255,255,.72));
8794
8874
  box-shadow:0 1px 2px rgba(20,40,80,.04);white-space:normal}
8795
- button.da-card{appearance:none;font-family:inherit;cursor:pointer;transition:border-color .15s ease,box-shadow .15s ease,background .15s ease}
8796
- button.da-card:hover{border-color:var(--dsw-alias-border-l3,rgba(128,128,128,.46));background:var(--dsw-alias-bg-layer-2,rgba(128,128,128,.08));
8797
- box-shadow:0 3px 10px rgba(20,40,80,.07)}
8798
- button.da-card:focus-visible{outline:2px solid var(--da-accent,#176df2);outline-offset:1px}
8799
- .da-card-head{display:flex;align-items:center;gap:8px;min-width:0;padding:7px 9px;
8875
+ /* Review, source navigation and delivery are separate native buttons. */
8876
+ .da-card button,.da-staged-send{appearance:none;font:inherit;color:inherit;cursor:pointer}
8877
+ .da-card button:focus-visible,.da-staged-send:focus-visible{outline:2px solid var(--da-accent,#176df2);outline-offset:-2px}
8878
+ .da-card button:hover,.da-staged-send:hover{background:var(--dsw-alias-bg-layer-2,rgba(128,128,128,.08));color:var(--da-accent,#176df2)}
8879
+ .da-card-head{width:100%;box-sizing:border-box;border:0;text-align:left;display:flex;align-items:center;gap:8px;min-width:0;padding:7px 9px;
8800
8880
  border-bottom:0.5px solid var(--dsw-alias-border-l2,rgba(128,128,128,.2));
8801
8881
  background:var(--dsw-alias-bg-layer-1,rgba(128,128,128,.025))}
8802
8882
  .da-card-shot{position:relative;display:flex;flex:0 0 auto;align-items:center;justify-content:center;width:34px;height:34px;overflow:hidden;border-radius:7px;
@@ -8804,7 +8884,7 @@ button.da-card:focus-visible{outline:2px solid var(--da-accent,#176df2);outline-
8804
8884
  background:var(--dsw-alias-bg-layer-3,rgba(128,128,128,.1));color:var(--da-accent,#176df2)}
8805
8885
  /* The picture covers the glyph; a picture that fails hides itself, and the glyph
8806
8886
  underneath it all along is what is left. A miss is never an empty box. */
8807
- .da-card-shot img{position:absolute;inset:0;width:100%;height:100%;object-fit:cover}
8887
+ .da-card-shot img{position:absolute;inset:0;width:100%;height:100%;object-fit:contain}
8808
8888
  .da-card-shot img[hidden]{display:none}
8809
8889
  .da-card-glyph{width:18px;height:18px}
8810
8890
  .da-card-text{display:flex;flex:1 1 auto;flex-direction:column;gap:2px;min-width:0}
@@ -8827,10 +8907,11 @@ button.da-card:focus-visible{outline:2px solid var(--da-accent,#176df2);outline-
8827
8907
  border-radius:50%;background:var(--dsw-alias-bg-layer-3,rgba(128,128,128,.1));
8828
8908
  color:var(--dsw-alias-label-tertiary,inherit)}
8829
8909
  .da-card-go svg{width:14px;height:14px;fill:none;stroke:currentColor;stroke-width:1.5;stroke-linecap:round;stroke-linejoin:round}
8830
- button.da-card:hover .da-card-go{background:var(--da-accent-soft,rgba(23,109,242,.14));color:var(--da-accent,#176df2)}
8910
+ .da-card-head:hover .da-card-go{background:var(--da-accent-soft,rgba(23,109,242,.14));color:var(--da-accent,#176df2)}
8831
8911
  /* Each mark is a compact record, not a line of debug output. The row head answers
8832
8912
  "which mark and where"; the element name and selector follow only when the message
8833
8913
  carried them; the reader's sentence is the strongest text. */
8914
+ .da-card[data-expanded="true"] .da-card-go{transform:rotate(90deg)}
8834
8915
  .da-card-body{display:flex;flex-direction:column;min-width:0;padding:0 9px 2px}
8835
8916
  .da-card-row{display:flex;flex-direction:column;gap:2px;min-width:0;padding:6px 0}
8836
8917
  .da-card-row+.da-card-row{border-top:0.5px solid var(--dsw-alias-border-l2,rgba(128,128,128,.14))}
@@ -8932,21 +9013,33 @@ button.da-card:hover .da-card-go{background:var(--da-accent-soft,rgba(23,109,242
8932
9013
  background:none;font:inherit;font-size:13px;line-height:1;cursor:pointer;opacity:.65}
8933
9014
  .da-staged-result-x:hover{opacity:1;background:var(--dsw-alias-bg-layer-2,rgba(255,255,255,.72))}
8934
9015
  .da-staged-result-x:focus-visible{outline:2px solid var(--da-accent,#176df2);outline-offset:1px}
8935
- /* One figure, one picture. The message that carries an annotation also attaches the
8936
- review picture, and the shell draws that attachment full width above the bubble —
8937
- so the same figure appeared twice, once huge and once as the card's 38px copy. The
8938
- card can open that figure itself, so the large copy is the one to drop: it is the
8939
- duplicate, and it was the reason the card sat under a picture the reader had
8940
- already seen. Selected from the attachment row forward rather than from the card
8941
- back, because CSS has no parent selector to reach; :has does have a following
8942
- sibling test, which is exactly this relationship.
8943
-
8944
- Two guards keep it narrow. :not([data-static]) means a card that cannot open
8945
- anything (the queue dock's, drawn before sending) hides nothing — that copy is the
8946
- only picture in the dock. And if a shell ever stops marking its attachment row
8947
- with data-message-attachments, this rule stops matching rather than breaking
8948
- anything: the large picture simply returns and the card keeps working. */
8949
- [data-message-attachments]:has(~ * .da-card:not([data-static])){display:none}
9016
+ /* Attachments belong to the message. A review may carry several PDF pages,
9017
+ and a message may also carry unrelated files; its source is not that snapshot. */
9018
+ .da-card-actions{display:flex;flex-wrap:wrap;align-items:center;justify-content:space-between;gap:4px;padding:3px 5px;
9019
+ border-top:0.5px solid var(--dsw-alias-border-l2,rgba(128,128,128,.2))}
9020
+ .da-card-toggle,.da-card-source{border:0;border-radius:6px;background:none;padding:6px 4px;min-height:30px;font-size:11px!important}
9021
+ .da-card-source{color:var(--da-accent,#176df2)!important}
9022
+ .da-card-error{padding:6px 9px;color:var(--dsw-alias-label-error,#c0392b);font-size:11px}
9023
+ .da-card-locator{margin-left:22px;overflow-wrap:anywhere;white-space:pre-wrap;color:var(--dsw-alias-label-tertiary,inherit);
9024
+ font:10px/1.5 var(--dsw-font-family-mono,ui-monospace,SFMono-Regular,Menlo,monospace)}
9025
+ .da-card[data-expanded="true"] .da-card-said{display:block;white-space:pre-wrap;-webkit-line-clamp:unset}
9026
+ .da-card[data-expanded="true"] .da-card-name{white-space:normal;overflow-wrap:anywhere}
9027
+ .da-card-target{overflow-wrap:anywhere}
9028
+ .da-staged-type{font-size:9px;font-weight:650;letter-spacing:.04em;color:var(--dsw-alias-label-tertiary,inherit)}
9029
+ .da-staged-card[data-expanded="true"]{width:min(440px,100%)}
9030
+ .da-staged-card[data-expanded="true"] .da-staged-line{display:none}
9031
+ .da-staged-chevron{display:flex;flex:0 0 18px;margin-left:auto;color:var(--dsw-alias-label-tertiary,#858991)}
9032
+ .da-staged-chevron svg{width:14px;height:14px;fill:none;stroke:currentColor;stroke-width:1.5}
9033
+ .da-staged-open[aria-expanded="true"] .da-staged-chevron{transform:rotate(90deg)}
9034
+ .da-staged-text{flex:1 1 auto;padding-right:0}
9035
+ .da-staged-open{padding-right:18px}
9036
+ .da-staged-details{display:block;width:100%;margin-top:8px;border-top:1px solid var(--dsw-alias-border-main,rgba(128,128,128,.18))}
9037
+ .da-staged-details .da-card{width:100%;max-width:none;margin:0;border:0;border-radius:0;box-shadow:none;background:transparent}
9038
+ .da-staged-details .da-card-body{padding:8px 0 0}
9039
+ .da-overlay[data-tool="select"] [data-da-element-box]{pointer-events:all;cursor:pointer}
9040
+ .da-staged-details[hidden]{display:none}
9041
+ .da-staged-send{border:0;border-radius:6px;background:none;min-height:28px;padding:4px 8px;font-size:11px;color:var(--da-accent,#176df2)}
9042
+ .da-staged-send[disabled]{opacity:.6;cursor:default}
8950
9043
 
8951
9044
  /* --- local service browser ------------------------------------------------ */
8952
9045
  .da-service-shell{display:flex;flex-direction:column;height:100%;min-height:0}
@@ -8998,7 +9091,7 @@ button.da-card:hover .da-card-go{background:var(--da-accent-soft,rgba(23,109,242
8998
9091
  .da-services{--da-service-ink:var(--dsw-alias-label-primary,#1b1d20);
8999
9092
  --da-service-muted:var(--dsw-alias-label-tertiary,#787d85);
9000
9093
  --da-service-line:color-mix(in srgb,var(--dsw-alias-border-l2,#c9cbd0) 66%,transparent);
9001
- box-sizing:border-box;height:100%;overflow:auto;padding:clamp(26px,7vh,64px) 22px 40px;
9094
+ box-sizing:border-box;height:100%;overflow:auto;padding:24px 20px 32px;width:100%;
9002
9095
  color:var(--da-service-ink);
9003
9096
  background:
9004
9097
  radial-gradient(circle at 18% 0%,color-mix(in srgb,#4c8a63 8%,transparent),transparent 36%),
@@ -9006,10 +9099,7 @@ button.da-card:hover .da-card-go{background:var(--da-accent-soft,rgba(23,109,242
9006
9099
  .da-services-head,.da-service-search,.da-services-toolbar,.da-service-list,.da-services-error{
9007
9100
  width:min(480px,100%);margin-left:auto;margin-right:auto}
9008
9101
  .da-services-head{padding:0 4px 18px}
9009
- .da-services-kicker{display:block;margin-bottom:8px;color:var(--da-service-muted);
9010
- font:9px/1.2 var(--dsw-font-family-mono,ui-monospace,SFMono-Regular,Menlo,monospace);
9011
- letter-spacing:.16em}
9012
- .da-services-head h1{margin:0;font-size:clamp(25px,4.5vw,34px);font-weight:680;line-height:1.08;letter-spacing:-.035em}
9102
+ .da-services-head h1{margin:0;font-size:20px;font-weight:680;line-height:1.08;letter-spacing:-.035em}
9013
9103
  .da-services-head p{max-width:410px;margin:9px 0 0;color:var(--da-service-muted);font-size:12px;line-height:1.65}
9014
9104
  .da-service-search{display:flex;align-items:center;gap:9px;box-sizing:border-box;height:48px;padding:0 7px 0 14px;
9015
9105
  border:1px solid var(--da-service-line);border-radius:15px;
@@ -9913,46 +10003,60 @@ button.da-card:hover .da-card-go{background:var(--da-accent-soft,rgba(23,109,242
9913
10003
  left: props.at.left,
9914
10004
  top: props.at.top
9915
10005
  },
9916
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("textarea", {
9917
- ref: props.inputRef,
9918
- className: "da-comment",
9919
- value: mark.detail ?? "",
9920
- placeholder: hasDetail(mark) ? t("detailPlaceholder") : t("addComment"),
9921
- rows: 1,
9922
- maxLength: MAX_DETAIL_CHARS,
9923
- onChange: (event) => {
9924
- props.onWrite(event.target.value);
9925
- },
9926
- onKeyDown: (event) => {
9927
- if (event.key !== "Escape") return;
9928
- event.preventDefault();
9929
- event.stopPropagation();
9930
- event.currentTarget.blur();
9931
- }
9932
- }), hasDetail(mark) ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
9933
- className: "da-comment-send",
9934
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
9935
- type: "button",
9936
- className: "da-comment-send-btn da-comment-send-primary",
9937
- disabled: props.busy,
9938
- "aria-label": t("sendToAgent"),
9939
- title: t("sendToAgent"),
9940
- onClick: () => {
9941
- props.onSendToAgent();
10006
+ children: [
10007
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("textarea", {
10008
+ ref: props.inputRef,
10009
+ className: "da-comment",
10010
+ value: mark.detail ?? "",
10011
+ placeholder: hasDetail(mark) ? t("detailPlaceholder") : t("addComment"),
10012
+ rows: 1,
10013
+ maxLength: MAX_DETAIL_CHARS,
10014
+ onChange: (event) => {
10015
+ props.onWrite(event.target.value);
9942
10016
  },
9943
- children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(SendIcon, {})
9944
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
10017
+ onKeyDown: (event) => {
10018
+ if (event.key !== "Escape") return;
10019
+ event.preventDefault();
10020
+ event.stopPropagation();
10021
+ event.currentTarget.blur();
10022
+ }
10023
+ }),
10024
+ hasDetail(mark) ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
10025
+ className: "da-comment-send",
10026
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
10027
+ type: "button",
10028
+ className: "da-comment-send-btn da-comment-send-primary",
10029
+ disabled: props.busy,
10030
+ "aria-label": t("sendToAgent"),
10031
+ title: t("sendToAgent"),
10032
+ onClick: () => {
10033
+ props.onSendToAgent();
10034
+ },
10035
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(SendIcon, {})
10036
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
10037
+ type: "button",
10038
+ className: "da-comment-send-btn",
10039
+ disabled: props.busy,
10040
+ "aria-label": t("sendToComposer"),
10041
+ title: t("sendToComposer"),
10042
+ onClick: () => {
10043
+ props.onSendToComposer();
10044
+ },
10045
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ChatIcon, {})
10046
+ })]
10047
+ }) : null,
10048
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
9945
10049
  type: "button",
9946
- className: "da-comment-send-btn",
9947
- disabled: props.busy,
9948
- "aria-label": t("sendToComposer"),
9949
- title: t("sendToComposer"),
10050
+ className: "da-comment-send-btn da-comment-remove",
10051
+ "aria-label": t("delete"),
10052
+ title: t("delete"),
10053
+ "aria-disabled": props.busy,
9950
10054
  onClick: () => {
9951
- props.onSendToComposer();
10055
+ if (!props.busy) props.onRemove();
9952
10056
  },
9953
- children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ChatIcon, {})
9954
- })]
9955
- }) : null]
10057
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(TrashIcon, {})
10058
+ })
10059
+ ]
9956
10060
  });
9957
10061
  }
9958
10062
  //#endregion
@@ -15538,6 +15642,9 @@ button.da-card:hover .da-card-go{background:var(--da-accent-soft,rgba(23,109,242
15538
15642
  onWrite: (value) => {
15539
15643
  setMarkDetail(selected.id, value);
15540
15644
  },
15645
+ onRemove: () => {
15646
+ removeMark(selected.id);
15647
+ },
15541
15648
  onSendToAgent: () => {
15542
15649
  sendOne(selected, "agent");
15543
15650
  },
@@ -15559,6 +15666,32 @@ button.da-card:hover .da-card-go{background:var(--da-accent-soft,rgba(23,109,242
15559
15666
  });
15560
15667
  }
15561
15668
  //#endregion
15669
+ //#region src/client/annotation-subject.ts
15670
+ /** Shared display identity for a review, before and after sending. */
15671
+ /**
15672
+ * Identify the reviewed source, independently of its mark tools or screenshot.
15673
+ * @param data - the immutable message facts.
15674
+ * @returns a readable name, media badge, and whether navigation belongs to the service tab.
15675
+ */
15676
+ function annotationSubject(data) {
15677
+ const address = data.html ? loopbackAddressOf(data.file) : void 0;
15678
+ if (address !== void 0) {
15679
+ const url = new URL(address);
15680
+ return {
15681
+ name: `${url.host}${url.pathname === "/" ? "" : url.pathname}`,
15682
+ badge: "WEB",
15683
+ live: true
15684
+ };
15685
+ }
15686
+ const suffix = suffixOf(data.file);
15687
+ const badge = data.html ? "HTML" : /^(?:pdf|svg|png|jpe?g|webp|gif|bmp|ico)$/u.test(suffix) ? suffix.toUpperCase() : "FIG";
15688
+ return {
15689
+ name: baseNameOf(data.file),
15690
+ badge,
15691
+ live: false
15692
+ };
15693
+ }
15694
+ //#endregion
15562
15695
  //#region src/client/annotation-card.tsx
15563
15696
  /**
15564
15697
  * The annotation a reader sent, as a card inside their own bubble.
@@ -15566,8 +15699,8 @@ button.da-card:hover .da-card-go{background:var(--da-accent-soft,rgba(23,109,242
15566
15699
  * The message still says everything it said — the model reads the same text,
15567
15700
  * copying it copies the same text — but in the transcript the same block is easier
15568
15701
  * to scan as a small sheet: which figure, how large, how many marks, and what each
15569
- * of them asked for. Clicking it opens the figure in the Sidebar, where the full
15570
- * annotator (and every mark, at full size) already is.
15702
+ * of them asked for. Clicking its header expands the delivered review; a separate action opens the
15703
+ * current source in the Sidebar, which may have changed since this message was sent.
15571
15704
  *
15572
15705
  * Two rules keep this a view and nothing more. It reads the block it was dispatched
15573
15706
  * for and never edits it; and it draws from the parsed data alone, so a block whose
@@ -15693,9 +15826,10 @@ button.da-card:hover .da-card-go{background:var(--da-accent-soft,rgba(23,109,242
15693
15826
  * @returns the row.
15694
15827
  */
15695
15828
  function CardRow(props) {
15696
- const { mark, ordinal, page } = props;
15829
+ const { mark, ordinal, page, expanded } = props;
15697
15830
  const said = mark.comment === "" ? void 0 : mark.comment;
15698
15831
  const parts = readCardMarkLabel(mark.label);
15832
+ const number = /\d+$/u.exec(parts.name)?.[0] ?? String(ordinal);
15699
15833
  const kind = parts.name.replace(/\s*\d+$/u, "");
15700
15834
  const label = parts.element === "" ? kind : parts.element;
15701
15835
  return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
@@ -15708,7 +15842,7 @@ button.da-card:hover .da-card-go{background:var(--da-accent-soft,rgba(23,109,242
15708
15842
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
15709
15843
  className: "da-card-kind",
15710
15844
  "aria-hidden": "true",
15711
- children: ordinal
15845
+ children: number
15712
15846
  }),
15713
15847
  /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
15714
15848
  className: "da-card-mark",
@@ -15737,6 +15871,10 @@ button.da-card:hover .da-card-go{background:var(--da-accent-soft,rgba(23,109,242
15737
15871
  className: "da-card-said",
15738
15872
  children: said
15739
15873
  }),
15874
+ expanded ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
15875
+ className: "da-card-locator",
15876
+ children: mark.label
15877
+ }) : null,
15740
15878
  parts.aside === "" ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
15741
15879
  className: "da-card-warning",
15742
15880
  children: parts.aside
@@ -15750,7 +15888,6 @@ button.da-card:hover .da-card-go{background:var(--da-accent-soft,rgba(23,109,242
15750
15888
  * @returns the card, or the block's own text when its data is not one it can draw.
15751
15889
  */
15752
15890
  function AnnotationCard(props) {
15753
- const [shot, setShot] = (0, react.useState)(true);
15754
15891
  (0, react.useEffect)(() => {
15755
15892
  ensureStyles(document);
15756
15893
  }, []);
@@ -15760,85 +15897,110 @@ button.da-card:hover .da-card-go{background:var(--da-accent-soft,rgba(23,109,242
15760
15897
  className: "da-card-fallback",
15761
15898
  children: raw
15762
15899
  });
15900
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ReviewCard, {
15901
+ ...props,
15902
+ data,
15903
+ raw
15904
+ }, raw || JSON.stringify(data));
15905
+ }
15906
+ /** The review snapshot; navigation to the current source is a separate action. */
15907
+ function ReviewCard(props) {
15908
+ const { data, raw, embedded = false } = props;
15909
+ const [failedThumb, setFailedThumb] = (0, react.useState)();
15910
+ const [expanded, setExpanded] = (0, react.useState)(embedded);
15911
+ const [trouble, setTrouble] = (0, react.useState)(false);
15912
+ const bodyId = (0, react.useId)();
15763
15913
  const interactive = props.interactive !== false;
15764
15914
  const open = props.open;
15765
- const thumb = shot ? thumbFor(raw) : void 0;
15766
- const shown = data.marks.slice(0, MAX_ROWS);
15915
+ const candidate = thumbFor(raw);
15916
+ const thumb = candidate === failedThumb ? void 0 : candidate;
15917
+ const shown = embedded || expanded ? data.marks : data.marks.slice(0, MAX_ROWS);
15767
15918
  const rest = data.marks.length - shown.length;
15768
- const liveFile = data.html && loopbackAddressOf(data.file) !== void 0;
15769
- const displayFile = liveFile ? data.file.replace(/[?#].*$/u, "") : data.file;
15919
+ const subject = annotationSubject(data);
15920
+ const liveFile = subject.live;
15770
15921
  const captureNotice = liveFile && thumb === void 0 ? data.pageCapture === "omitted" ? resolveTranslate(props, "cardCaptureOmitted") : data.pageCapture === "failed" ? resolveTranslate(props, "cardCaptureFailed") : "" : "";
15771
- const canOpen = interactive && open !== void 0 && (props.canOpen?.(data.file, data.html) ?? true);
15772
- const cut = Math.max(displayFile.lastIndexOf("/"), displayFile.lastIndexOf("\\"));
15773
- const name = displayFile.slice(cut + 1);
15922
+ const sourceOpener = interactive && (props.canOpen?.(data.file, data.html) ?? true) ? open : void 0;
15774
15923
  const dimensions = /(\d+)\s*[×x]\s*(\d+)/u.exec(data.scope);
15775
15924
  const size = dimensions === null ? "" : `${dimensions[1]}×${dimensions[2]}`;
15776
- const head = /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
15777
- className: "da-card-head",
15778
- children: [
15779
- /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
15780
- className: "da-card-shot",
15781
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(SheetIcon, {}), thumb === void 0 ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("img", {
15782
- alt: "",
15783
- src: thumb,
15784
- onError: () => {
15785
- setShot(false);
15786
- }
15787
- })]
15788
- }),
15789
- /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
15790
- className: "da-card-text",
15791
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
15792
- className: "da-card-title-row",
15793
- children: [
15794
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
15795
- className: "da-card-title",
15796
- children: name
15797
- }),
15798
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
15799
- className: "da-card-type",
15800
- children: data.html ? "HTML" : "FIG"
15801
- }),
15802
- captureNotice === "" ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
15803
- className: "da-card-capture",
15804
- children: captureNotice
15805
- })
15806
- ]
15807
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
15808
- className: "da-card-scope",
15809
- children: [size === "" ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
15810
- className: "da-card-meta",
15811
- title: data.scope,
15812
- children: size
15813
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
15814
- className: "da-card-tally",
15815
- "aria-label": resolveTranslate(props, "cardCount", { n: data.marks.length }),
15816
- title: data.scope,
15817
- children: data.marks.length
15818
- })]
15925
+ const pages = /^(?:共 (\d+) 页|(\d+) pages)(?:,|,|$)/u.exec(data.scope);
15926
+ const scope = pages === null ? size : resolveTranslate(props, "cardPages", { n: Number(pages[1] ?? pages[2]) });
15927
+ const headContent = /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [
15928
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
15929
+ className: "da-card-shot",
15930
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(SheetIcon, {}), thumb === void 0 ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("img", {
15931
+ alt: "",
15932
+ src: thumb,
15933
+ onError: () => {
15934
+ setFailedThumb(thumb);
15935
+ }
15936
+ })]
15937
+ }),
15938
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
15939
+ className: "da-card-text",
15940
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
15941
+ className: "da-card-title-row",
15942
+ children: [
15943
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
15944
+ className: "da-card-title",
15945
+ title: data.file,
15946
+ children: subject.name
15947
+ }),
15948
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
15949
+ className: "da-card-type",
15950
+ children: subject.badge
15951
+ }),
15952
+ captureNotice === "" ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
15953
+ className: "da-card-capture",
15954
+ children: captureNotice
15955
+ })
15956
+ ]
15957
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
15958
+ className: "da-card-scope",
15959
+ children: [scope === "" ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
15960
+ className: "da-card-meta",
15961
+ title: data.scope,
15962
+ children: scope
15963
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
15964
+ className: "da-card-tally",
15965
+ "aria-label": resolveTranslate(props, "cardCount", { n: data.marks.length }),
15966
+ title: data.scope,
15967
+ children: data.marks.length
15819
15968
  })]
15820
- }),
15821
- canOpen ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
15822
- className: "da-card-go",
15823
- "aria-hidden": "true",
15824
- children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("svg", {
15825
- viewBox: "0 0 16 16",
15826
- focusable: "false",
15827
- children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("path", { d: "m6 3.5 4.5 4.5L6 12.5" })
15828
- })
15829
- }) : null
15830
- ]
15969
+ })]
15970
+ }),
15971
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
15972
+ className: "da-card-go",
15973
+ "aria-hidden": "true",
15974
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("svg", {
15975
+ viewBox: "0 0 16 16",
15976
+ focusable: "false",
15977
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("path", { d: "m6 3.5 4.5 4.5L6 12.5" })
15978
+ })
15979
+ })
15980
+ ] });
15981
+ const head = /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
15982
+ type: "button",
15983
+ className: "da-card-head",
15984
+ "aria-expanded": expanded,
15985
+ "aria-controls": bodyId,
15986
+ title: resolveTranslate(props, expanded ? "cardCollapse" : "cardExpand"),
15987
+ onClick: () => {
15988
+ setExpanded((value) => !value);
15989
+ },
15990
+ children: headContent
15831
15991
  });
15832
15992
  const body = /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
15833
15993
  className: "da-card-body",
15994
+ id: bodyId,
15834
15995
  children: [
15835
15996
  shown.map((mark, index) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(CardRow, {
15836
15997
  mark,
15837
15998
  ordinal: index + 1,
15999
+ expanded,
15838
16000
  page: mark.page === void 0 ? void 0 : resolveTranslate(props, "cardPage", { n: mark.page })
15839
16001
  }, `${String(mark.page ?? "")}-${String(index)}`)),
15840
16002
  rest > 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
15841
- className: "da-card-row da-card-rest",
16003
+ className: "da-card-rest",
15842
16004
  children: resolveTranslate(props, "cardMore", { n: rest })
15843
16005
  }) : null,
15844
16006
  data.target === "" ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
@@ -15850,20 +16012,39 @@ button.da-card:hover .da-card-go{background:var(--da-accent-soft,rgba(23,109,242
15850
16012
  })
15851
16013
  ]
15852
16014
  });
15853
- const onClick = canOpen && open !== void 0 ? () => {
15854
- open(data.file, data.html);
15855
- } : void 0;
15856
- if (onClick === void 0) return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
15857
- className: "da-card",
15858
- "data-static": "",
15859
- children: [head, body]
15860
- });
15861
- return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
15862
- type: "button",
16015
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
15863
16016
  className: "da-card",
15864
- title: `${resolveTranslate(props, liveFile ? "cardOpenLive" : "cardOpen")} — ${displayFile}`,
15865
- onClick,
15866
- children: [head, body]
16017
+ "data-expanded": expanded,
16018
+ children: [
16019
+ embedded ? null : head,
16020
+ body,
16021
+ embedded ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
16022
+ className: "da-card-actions",
16023
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
16024
+ type: "button",
16025
+ className: "da-card-toggle",
16026
+ "aria-expanded": expanded,
16027
+ "aria-controls": bodyId,
16028
+ onClick: () => {
16029
+ setExpanded((value) => !value);
16030
+ },
16031
+ children: resolveTranslate(props, expanded ? "cardCollapse" : "cardExpand")
16032
+ }), sourceOpener === void 0 ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
16033
+ type: "button",
16034
+ className: "da-card-source",
16035
+ title: data.file,
16036
+ onClick: () => {
16037
+ setTrouble(sourceOpener(data.file, data.html) === false);
16038
+ },
16039
+ children: resolveTranslate(props, liveFile ? "cardOpenLive" : "cardOpen")
16040
+ })]
16041
+ }),
16042
+ trouble ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
16043
+ className: "da-card-error",
16044
+ role: "status",
16045
+ children: resolveTranslate(props, "cardOpenFailed")
16046
+ }) : null
16047
+ ]
15867
16048
  });
15868
16049
  }
15869
16050
  //#endregion
@@ -15948,7 +16129,8 @@ button.da-card:hover .da-card-go{background:var(--da-accent-soft,rgba(23,109,242
15948
16129
  paths: saved,
15949
16130
  thumb,
15950
16131
  file: void 0,
15951
- pageImages: images.length,
16132
+ files: images.slice(0, 8).map(({ page, image }) => new File([image], `${baseNameOf(document.figure.path)}${pageFileSuffix(page)}.annotated.png`, { type: "image/png" })),
16133
+ pageImages: Math.min(images.length, 8),
15952
16134
  locale: options.locale,
15953
16135
  send: async () => {
15954
16136
  if (sessions === void 0) throw new Error("the sessions service is unavailable");
@@ -16167,31 +16349,9 @@ button.da-card:hover .da-card-go{background:var(--da-accent-soft,rgba(23,109,242
16167
16349
  //#endregion
16168
16350
  //#region src/client/pending-card.tsx
16169
16351
  /**
16170
- * The parked annotation, drawn inside the composer as an attachment card.
16171
- *
16172
- * The composer is a lexical editor owned by the conversation package: nothing can be
16173
- * written into it that stays a card, and the whole annotation block sitting in the
16174
- * input box is exactly what a reader cannot scan. So the block is armed beside the box
16175
- * — its message held, its picture uploaded — and the chip stands beside the reader's
16176
- * own attachments as the face of what that send will carry: the figure's thumbnail, how
16177
- * many marks it carries, the file's name, and the first mark's note.
16178
- * It is portalled into the composer card's own flow (see `composer-seat`), so it
16179
- * sits where an attached picture sits; the dock entry that owns it holds only the
16180
- * anchor, and falls back to drawing in the dock itself when there is no card.
16181
- *
16182
- * What the card shows and what it hides is the point. A dimension or a pair of
16183
- * coordinates is not something to read at a glance, so it stays in the block the model
16184
- * reads and in the hover title, and never on the face: the face says the figure and the
16185
- * count first, the note second, and truncates both rather than wrapping into a wall of
16186
- * text.
16187
- *
16188
- * One press is the point, and the box keeps the reader's own words while it is not
16189
- * pressed: nothing is written into the draft, so the sentence being typed stays the only
16190
- * text in it. The box's own send carries the armed message — typing above the card and
16191
- * pressing Send sends the sentence and the review as one message, with the annotation
16192
- * behind the sentence — and the card's own action runs that same send for a reader who
16193
- * wants to fire it from the card. The card goes when that send takes it, or when the
16194
- * reader drops it, because that is when it stops standing for anything.
16352
+ * Reviews staged beside the composer. The chip previews its immutable payload;
16353
+ * the native composer Send carries armed reviews, with or without typed text.
16354
+ * Older hosts without draft admission retain a direct-delivery fallback.
16195
16355
  * @module dsh-figmark/client/pending-card
16196
16356
  */
16197
16357
  /**
@@ -16229,28 +16389,6 @@ button.da-card:hover .da-card-go{background:var(--da-accent-soft,rgba(23,109,242
16229
16389
  });
16230
16390
  }
16231
16391
  /**
16232
- * The small outline that stands for a mark's shape on the card's one line.
16233
- * @returns the glyph.
16234
- */
16235
- function MarkIcon() {
16236
- return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("svg", {
16237
- className: "da-staged-mark-icon",
16238
- viewBox: "0 0 12 12",
16239
- "aria-hidden": "true",
16240
- focusable: "false",
16241
- children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("rect", {
16242
- x: "1",
16243
- y: "2",
16244
- width: "10",
16245
- height: "8",
16246
- rx: "1.5",
16247
- fill: "none",
16248
- stroke: "currentColor",
16249
- strokeWidth: "1.1"
16250
- })
16251
- });
16252
- }
16253
- /**
16254
16392
  * A mark's name without its coordinates: the kind and its number, which is what a
16255
16393
  * reader recognises, with a space between the two the way the product writes it.
16256
16394
  * @param label - the mark's own name, coordinates and all.
@@ -16269,25 +16407,15 @@ button.da-card:hover .da-card-go{background:var(--da-accent-soft,rgba(23,109,242
16269
16407
  const [busy, setBusy] = (0, react.useState)(false);
16270
16408
  const [trouble, setTrouble] = (0, react.useState)("");
16271
16409
  const [shot, setShot] = (0, react.useState)(true);
16410
+ const [expanded, setExpanded] = (0, react.useState)(false);
16411
+ const detailId = (0, react.useId)();
16272
16412
  const sending = (0, react.useRef)(false);
16273
16413
  const armed = entry.armed;
16274
16414
  const data = entry.data;
16275
16415
  const thumb = shot ? thumbFor(entry.raw) : void 0;
16276
16416
  const send = async () => {
16277
16417
  if (sending.current) return;
16278
- if (armed !== void 0) {
16279
- sending.current = true;
16280
- setBusy(true);
16281
- try {
16282
- armed.shell.submit?.();
16283
- } finally {
16284
- queueMicrotask(() => {
16285
- sending.current = false;
16286
- setBusy(false);
16287
- });
16288
- }
16289
- return;
16290
- }
16418
+ setTrouble("");
16291
16419
  sending.current = true;
16292
16420
  setBusy(true);
16293
16421
  try {
@@ -16311,88 +16439,142 @@ button.da-card:hover .da-card-go{background:var(--da-accent-soft,rgba(23,109,242
16311
16439
  entry.id,
16312
16440
  sessionId
16313
16441
  ]);
16314
- const shownFile = data.file.replace(/[?#].*$/u, "");
16315
- const cut = Math.max(shownFile.lastIndexOf("/"), shownFile.lastIndexOf("\\"));
16316
- const name = shownFile.slice(cut + 1);
16442
+ const subject = annotationSubject(data);
16443
+ const name = subject.name;
16317
16444
  const first = data.marks[0];
16318
- const captureNotice = data.html && thumb === void 0 ? data.pageCapture === "omitted" ? resolveTranslate(props, "cardCaptureOmitted") : data.pageCapture === "failed" ? resolveTranslate(props, "cardCaptureFailed") : "" : "";
16445
+ const captureNotice = subject.live && thumb === void 0 ? data.pageCapture === "omitted" ? resolveTranslate(props, "cardCaptureOmitted") : data.pageCapture === "failed" ? resolveTranslate(props, "cardCaptureFailed") : "" : "";
16319
16446
  const detail = first === void 0 ? name : first.comment === "" ? first.label : `${first.label} · ${first.comment}`;
16320
16447
  const hover = data.scope === "" ? data.file : `${data.file} · ${data.scope}`;
16321
16448
  const removeLabel = resolveTranslate(props, "pendingRemove");
16449
+ const hint = resolveTranslate(props, "pendingSendNow");
16322
16450
  return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
16323
16451
  className: "da-staged",
16324
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
16325
- className: "da-staged-card",
16326
- title: hover,
16327
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
16328
- type: "button",
16329
- className: "da-staged-open",
16330
- disabled: busy,
16331
- title: resolveTranslate(props, armed === void 0 ? "pendingSendNow" : "pendingSendHint"),
16332
- onClick: () => {
16333
- send();
16334
- },
16335
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
16336
- className: "da-staged-shot",
16337
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(FigureIcon, {}), thumb === void 0 ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("img", {
16338
- alt: "",
16339
- src: thumb,
16340
- onError: () => {
16341
- setShot(false);
16342
- }
16343
- })]
16344
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
16345
- className: "da-staged-text",
16346
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
16347
- className: "da-staged-head",
16452
+ children: [
16453
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
16454
+ className: "da-staged-card",
16455
+ "data-expanded": expanded,
16456
+ title: hover,
16457
+ children: [
16458
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
16459
+ type: "button",
16460
+ className: "da-staged-open",
16461
+ "aria-expanded": expanded,
16462
+ "aria-controls": detailId,
16463
+ title: resolveTranslate(props, expanded ? "cardCollapse" : "cardExpand"),
16464
+ onClick: () => {
16465
+ setExpanded((value) => !value);
16466
+ },
16348
16467
  children: [
16349
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
16350
- className: "da-staged-title",
16351
- title: name,
16352
- children: name
16468
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
16469
+ className: "da-staged-shot",
16470
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(FigureIcon, {}), thumb === void 0 ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("img", {
16471
+ alt: "",
16472
+ src: thumb,
16473
+ onError: () => {
16474
+ setShot(false);
16475
+ }
16476
+ })]
16353
16477
  }),
16354
- captureNotice === "" ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
16355
- className: "da-staged-capture",
16356
- children: captureNotice
16478
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
16479
+ className: "da-staged-text",
16480
+ children: [
16481
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
16482
+ className: "da-staged-head",
16483
+ children: [
16484
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
16485
+ className: "da-staged-title",
16486
+ title: name,
16487
+ children: name
16488
+ }),
16489
+ captureNotice === "" ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
16490
+ className: "da-staged-capture",
16491
+ children: captureNotice
16492
+ }),
16493
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
16494
+ className: "da-staged-count",
16495
+ "aria-label": resolveTranslate(props, "cardCount", { n: data.marks.length }),
16496
+ children: resolveTranslate(props, "pendingCount", { n: data.marks.length })
16497
+ })
16498
+ ]
16499
+ }),
16500
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
16501
+ className: "da-staged-type",
16502
+ children: subject.badge
16503
+ }),
16504
+ first === void 0 ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
16505
+ className: "da-staged-line",
16506
+ title: detail,
16507
+ children: [
16508
+ first.page === void 0 ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
16509
+ className: "da-card-page",
16510
+ children: resolveTranslate(props, "cardPage", { n: first.page })
16511
+ }),
16512
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
16513
+ className: "da-staged-shape",
16514
+ children: shortLabel(first.label)
16515
+ }),
16516
+ first.comment === "" ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
16517
+ className: "da-staged-said",
16518
+ children: first.comment
16519
+ })
16520
+ ]
16521
+ })
16522
+ ]
16357
16523
  }),
16358
16524
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
16359
- className: "da-staged-count",
16360
- "aria-label": resolveTranslate(props, "cardCount", { n: data.marks.length }),
16361
- children: resolveTranslate(props, "pendingCount", { n: data.marks.length })
16362
- })
16363
- ]
16364
- }), first === void 0 ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
16365
- className: "da-staged-line",
16366
- title: detail,
16367
- children: [
16368
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)(MarkIcon, {}),
16369
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
16370
- className: "da-staged-shape",
16371
- children: shortLabel(first.label)
16372
- }),
16373
- first.comment === "" ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
16374
- className: "da-staged-said",
16375
- children: first.comment
16525
+ className: "da-staged-chevron",
16526
+ "aria-hidden": "true",
16527
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("svg", {
16528
+ viewBox: "0 0 16 16",
16529
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("path", { d: "m6 3.5 4.5 4.5L6 12.5" })
16530
+ })
16376
16531
  })
16377
16532
  ]
16378
- })]
16379
- })]
16380
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
16533
+ }),
16534
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
16535
+ type: "button",
16536
+ className: "da-staged-x",
16537
+ "aria-label": removeLabel,
16538
+ title: removeLabel,
16539
+ disabled: busy,
16540
+ onClick: () => {
16541
+ if (sending.current) return;
16542
+ if (armed !== void 0) disarmReview(armed, entry.conversation);
16543
+ unparkAnnotation(sessionId, entry.id);
16544
+ },
16545
+ children: "×"
16546
+ }),
16547
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
16548
+ id: detailId,
16549
+ className: "da-staged-details",
16550
+ hidden: !expanded,
16551
+ children: expanded ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(AnnotationCard, {
16552
+ node: { data },
16553
+ raw: entry.raw,
16554
+ interactive: false,
16555
+ embedded: true,
16556
+ t: props.t,
16557
+ localeId: props.localeId
16558
+ }) : null
16559
+ })
16560
+ ]
16561
+ }),
16562
+ armed === void 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
16381
16563
  type: "button",
16382
- className: "da-staged-x",
16383
- "aria-label": removeLabel,
16384
- title: removeLabel,
16564
+ className: "da-staged-send",
16385
16565
  disabled: busy,
16566
+ title: hint,
16386
16567
  onClick: () => {
16387
- if (armed !== void 0) disarmReview(armed, entry.conversation);
16388
- unparkAnnotation(sessionId, entry.id);
16568
+ send();
16389
16569
  },
16390
- children: "×"
16391
- })]
16392
- }), trouble === "" ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
16393
- className: "da-staged-trouble",
16394
- children: trouble
16395
- })]
16570
+ children: resolveTranslate(props, busy ? "pendingSending" : "pendingSendNow")
16571
+ }) : null,
16572
+ trouble === "" ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
16573
+ className: "da-staged-trouble",
16574
+ role: "alert",
16575
+ children: trouble
16576
+ })
16577
+ ]
16396
16578
  });
16397
16579
  }
16398
16580
  /**
@@ -16427,6 +16609,11 @@ button.da-card:hover .da-card-go{background:var(--da-accent-soft,rgba(23,109,242
16427
16609
  return mountSeat(node, setSeat);
16428
16610
  }, [sessionId, showing]);
16429
16611
  if (!showing) return null;
16612
+ const owned = entries.flatMap((entry) => entry.armed?.previewUrls ?? []).map((url) => `img[src=${JSON.stringify(url)}]`).join(",");
16613
+ const thumbnailStyle = owned === "" ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("style", { children: `
16614
+ [data-composer-card] [role="group"] > div:has(${owned}){display:none}
16615
+ [data-composer-card] > div:has(> div > [role="group"]):not(:has([role="group"] > div:not(:has(${owned})))){display:none}
16616
+ ` });
16430
16617
  const cards = /* @__PURE__ */ (0, react_jsx_runtime.jsx)(react_jsx_runtime.Fragment, { children: entries.map((entry) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ParkedChip, {
16431
16618
  entry,
16432
16619
  sessionId,
@@ -16453,11 +16640,15 @@ button.da-card:hover .da-card-go{background:var(--da-accent-soft,rgba(23,109,242
16453
16640
  children: "×"
16454
16641
  })]
16455
16642
  });
16456
- return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
16457
- className: "da-staged-anchor",
16458
- ref: anchor,
16459
- hidden: true
16460
- }), seat === null ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [cards, report] }) : (0, react_dom.createPortal)(/* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [cards, report] }), seat)] });
16643
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [
16644
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
16645
+ className: "da-staged-anchor",
16646
+ ref: anchor,
16647
+ hidden: true
16648
+ }),
16649
+ thumbnailStyle,
16650
+ seat === null ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [cards, report] }) : (0, react_dom.createPortal)(/* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [cards, report] }), seat)
16651
+ ] });
16461
16652
  }
16462
16653
  //#endregion
16463
16654
  //#region src/client/live-drafts.ts
@@ -17041,13 +17232,13 @@ button.da-card:hover .da-card-go{background:var(--da-accent-soft,rgba(23,109,242
17041
17232
  return;
17042
17233
  }
17043
17234
  const needle = query.trim().toLowerCase();
17044
- const titleMatch = visible.find((service) => service.title?.toLowerCase().includes(needle) === true);
17045
- if (titleMatch !== void 0) {
17046
- openWorkspace(titleMatch.url, titleMatch.title);
17047
- return;
17048
- }
17049
- const first = visible[0];
17050
- openWorkspace(looksLikeAddress(query) || first === void 0 ? query : first.url, looksLikeAddress(query) || first === void 0 ? void 0 : first.title);
17235
+ if (needle === "") return;
17236
+ const titleMatch = visible.some((service) => service.title?.toLowerCase().includes(needle) === true);
17237
+ if (looksLikeAddress(query) && !titleMatch) openWorkspace(query);
17238
+ else if (visible.length === 1) {
17239
+ const only = visible[0];
17240
+ openWorkspace(only.url, only.title);
17241
+ } else setError(t(visible.length === 0 ? "serviceNoMatch" : "serviceChooseMatch"));
17051
17242
  };
17052
17243
  const visible = (0, react.useMemo)(() => {
17053
17244
  const needle = query.trim().toLowerCase();
@@ -17146,14 +17337,7 @@ button.da-card:hover .da-card-go{background:var(--da-accent-soft,rgba(23,109,242
17146
17337
  children: [
17147
17338
  /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("header", {
17148
17339
  className: "da-services-head",
17149
- children: [
17150
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
17151
- className: "da-services-kicker",
17152
- children: t("serviceKicker")
17153
- }),
17154
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("h1", { children: t("serviceTitle") }),
17155
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", { children: t("serviceHint") })
17156
- ]
17340
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("h1", { children: t("serviceTitle") }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", { children: t("serviceHint") })]
17157
17341
  }),
17158
17342
  /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("form", {
17159
17343
  className: "da-service-search",
@@ -17211,7 +17395,7 @@ button.da-card:hover .da-card-go{background:var(--da-accent-soft,rgba(23,109,242
17211
17395
  children: t("serviceScanning")
17212
17396
  }) : visible.length === 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
17213
17397
  className: "da-services-empty",
17214
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("strong", { children: t("serviceEmpty") }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: t("serviceEmptyHint") })]
17398
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("strong", { children: t(query.trim() === "" ? "serviceEmpty" : "serviceNoMatch") }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: t(query.trim() === "" ? "serviceEmptyHint" : "serviceSearchHint") })]
17215
17399
  }) : visible.map((service) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
17216
17400
  type: "button",
17217
17401
  className: "da-service-row",
@@ -18445,6 +18629,7 @@ button.da-card:hover .da-card-go{background:var(--da-accent-soft,rgba(23,109,242
18445
18629
  */
18446
18630
  function canOpenFigure(ctx, sessionId, path, html) {
18447
18631
  if (sessionId === "" || path === "") return false;
18632
+ if (/^[a-z][a-z\d+.-]*:\/\//iu.test(path) && !isLivePage(path, html)) return false;
18448
18633
  const sidebar = ctx.get("sidebarRight");
18449
18634
  if (sidebar === void 0) return false;
18450
18635
  return !isLivePage(path, html) || sidebar.openTab !== void 0;
@@ -18521,6 +18706,7 @@ button.da-card:hover .da-card-go{background:var(--da-accent-soft,rgba(23,109,242
18521
18706
  ctx.effect(() => sidebarTabs.register({
18522
18707
  id: SERVICE_BODY_ID,
18523
18708
  kind: SERVICE_KIND,
18709
+ keepMounted: true,
18524
18710
  priority: "extension",
18525
18711
  title: () => locale.bind(NAMESPACE)("serviceTitle"),
18526
18712
  guide: [{