@cueloop/client 0.1.0-alpha.43 → 0.1.0-alpha.45

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/src/view-plan.ts CHANGED
@@ -8,6 +8,7 @@
8
8
  */
9
9
 
10
10
  import {
11
+ inlineRuns,
11
12
  isAddressed,
12
13
  lcsDiff,
13
14
  parseBlocks,
@@ -141,7 +142,18 @@ function numberOrderedListItems(display: DisplayBlock[]): void {
141
142
 
142
143
  // ── marks (annotation highlights + keyboard span) ──
143
144
 
144
- export type RunRole = "plain" | "ins" | "del" | "mark-comment" | "kspan" | "mark-focus";
145
+ export type RunRole =
146
+ | "plain"
147
+ | "ins"
148
+ | "del"
149
+ | "mark-comment"
150
+ | "kspan"
151
+ | "mark-focus"
152
+ | "strong"
153
+ | "em"
154
+ | "code"
155
+ | "strike"
156
+ | "link";
145
157
 
146
158
  export interface StyleRun {
147
159
  text: string;
@@ -149,6 +161,8 @@ export interface StyleRun {
149
161
  /** Offset of this run in the block's working text; null for del runs. */
150
162
  start: number | null;
151
163
  annotationId?: string;
164
+ /** Link target for `link` runs. */
165
+ href?: string;
152
166
  }
153
167
 
154
168
  export interface Mark {
@@ -195,6 +209,7 @@ export function marksByDisplay(
195
209
 
196
210
  /** Base runs for a display block: plain text, or word-diff for mod blocks. */
197
211
  export function blockRuns(block: DisplayBlock, markup: boolean): StyleRun[] {
212
+ const literal = block.kind === "code" || block.kind === "hr";
198
213
  if (block.type === "mod" && markup) {
199
214
  const changes = wordLevelChanges(block.base!.text, block.work!.text);
200
215
  const runs: StyleRun[] = [];
@@ -202,18 +217,57 @@ export function blockRuns(block: DisplayBlock, markup: boolean): StyleRun[] {
202
217
  for (const change of changes) {
203
218
  if (change.kind === "removed") {
204
219
  runs.push({ text: change.text, role: "del", start: null });
205
- } else {
206
- runs.push({
207
- text: change.text,
208
- role: change.kind === "added" ? "ins" : "plain",
209
- start: workOffset,
210
- });
211
- workOffset += change.text.length;
220
+ continue;
212
221
  }
222
+ // A code/rule block stays literal; prose segments inline-tokenize so their
223
+ // markers conceal while their work offsets stay intact. Added text keeps
224
+ // the green diff role (it wins over emphasis); context text shows emphasis.
225
+ const segment = literal
226
+ ? [{ text: change.text, role: "plain" as const, start: workOffset }]
227
+ : inlineStyleRuns(change.text, workOffset);
228
+ for (const run of segment) {
229
+ runs.push(change.kind === "added" ? { ...run, role: "ins" } : run);
230
+ }
231
+ workOffset += change.text.length;
213
232
  }
214
233
  return runs;
215
234
  }
216
- return [{ text: displayText(block), role: "plain", start: 0 }];
235
+ const text = displayText(block);
236
+ // Prose carries inline markup; code fences and rules are literal. Concealed
237
+ // markers are dropped, so every rendered cell still maps to a work offset.
238
+ if (markup && !literal) {
239
+ return inlineStyleRuns(text, 0);
240
+ }
241
+ return [{ text, role: "plain", start: 0 }];
242
+ }
243
+
244
+ /** A link href safe to hand a terminal as an OSC 8 target, or undefined. Only
245
+ * http(s) and mailto are trusted; a scheme like javascript: is dropped. The
246
+ * target must stay printable ASCII: a control byte (ESC, BEL) in a reviewed
247
+ * document could otherwise splice into the terminal escape stream. */
248
+ export function safeLinkHref(href: string | undefined): string | undefined {
249
+ if (href === undefined) return undefined;
250
+ if (!/^(https?:|mailto:)/i.test(href)) return undefined;
251
+ return /^[\x21-\x7e]+$/.test(href) ? href : undefined;
252
+ }
253
+
254
+ /**
255
+ * Map the schema inline tokens to positioned StyleRuns, dropping the concealed
256
+ * markers. `base` is the run's offset within the block's working text (0 for a
257
+ * whole prose block; a segment's start for a word-diff piece).
258
+ */
259
+ export function inlineStyleRuns(text: string, base: number): StyleRun[] {
260
+ const runs: StyleRun[] = [];
261
+ for (const run of inlineRuns(text)) {
262
+ if (run.role === "marker" || run.start === null) continue;
263
+ runs.push({
264
+ text: run.text,
265
+ role: run.role === "text" ? "plain" : run.role,
266
+ start: base + run.start,
267
+ ...(run.href !== undefined ? { href: run.href } : {}),
268
+ });
269
+ }
270
+ return runs;
217
271
  }
218
272
 
219
273
  /** Split runs at mark boundaries; marks only bind to runs with offsets. */
@@ -274,6 +328,39 @@ export function renderedOffsetFor(runs: StyleRun[], workOffset: number): number
274
328
  return null;
275
329
  }
276
330
 
331
+ /**
332
+ * Rendered offset of the first positioned character at or after `workOffset`.
333
+ * A word span may start on a concealed marker (the `**` of an emphasized
334
+ * word), which has no rendered cell; the span visuals snap to the first
335
+ * character that does. Positioned runs appear in ascending work order.
336
+ */
337
+ export function renderedOffsetAtOrAfter(runs: StyleRun[], workOffset: number): number | null {
338
+ let rendered = 0;
339
+ for (const run of runs) {
340
+ if (run.start !== null && run.start + run.text.length > workOffset) {
341
+ return rendered + Math.max(0, workOffset - run.start);
342
+ }
343
+ rendered += run.text.length;
344
+ }
345
+ return null;
346
+ }
347
+
348
+ /**
349
+ * Rendered offset of the last positioned character at or before `workOffset` -
350
+ * the closing-marker counterpart of `renderedOffsetAtOrAfter`.
351
+ */
352
+ export function renderedOffsetAtOrBefore(runs: StyleRun[], workOffset: number): number | null {
353
+ let rendered = 0;
354
+ let best: number | null = null;
355
+ for (const run of runs) {
356
+ if (run.start !== null && run.start <= workOffset) {
357
+ best = rendered + Math.min(workOffset - run.start, run.text.length - 1);
358
+ }
359
+ rendered += run.text.length;
360
+ }
361
+ return best;
362
+ }
363
+
277
364
  /**
278
365
  * Work-text range covered by a rendered-text selection [start, end). Del runs
279
366
  * and other unpositioned text inside the selection contribute nothing; the