@bendyline/squisq-editor-react 2.13.6 → 2.13.7

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.
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  MARKDOWN_ESCAPE_ATTR
3
- } from "./chunk-V7HUUMMB.js";
3
+ } from "./chunk-ZLKINE4Y.js";
4
4
 
5
5
  // src/MarkdownEscape.ts
6
6
  import { Mark, mergeAttributes } from "@tiptap/core";
@@ -3,11 +3,11 @@ import {
3
3
  AlignedTableHeader,
4
4
  MarkdownEscape,
5
5
  PlainTextClipboard
6
- } from "./chunk-SINLDQ63.js";
6
+ } from "./chunk-4MBH7MMU.js";
7
7
  import {
8
8
  markdownToTiptap,
9
9
  tiptapToMarkdown
10
- } from "./chunk-V7HUUMMB.js";
10
+ } from "./chunk-ZLKINE4Y.js";
11
11
 
12
12
  // src/jsonEditor/JsonEditorContext.tsx
13
13
  import { createContext, useContext, useEffect, useMemo } from "react";
@@ -5,7 +5,7 @@ import {
5
5
  import {
6
6
  markdownToTiptap,
7
7
  tiptapToMarkdown
8
- } from "./chunk-V7HUUMMB.js";
8
+ } from "./chunk-ZLKINE4Y.js";
9
9
 
10
10
  // src/InsertMenuItems.tsx
11
11
  import { createContext, useContext, useEffect, useMemo, useRef } from "react";
@@ -19,7 +19,7 @@ import {
19
19
  useDictationState,
20
20
  useEditorContext,
21
21
  useEditorContextOptional
22
- } from "./chunk-OMSRCHQ4.js";
22
+ } from "./chunk-PCIVTLQG.js";
23
23
  import {
24
24
  monacoLanguageRequestKey,
25
25
  monacoLanguagesForDocument,
@@ -30,7 +30,7 @@ import {
30
30
  AlignedTableHeader,
31
31
  MarkdownEscape,
32
32
  PlainTextClipboard
33
- } from "./chunk-SINLDQ63.js";
33
+ } from "./chunk-4MBH7MMU.js";
34
34
  import {
35
35
  escapeLinkLabel,
36
36
  formatLinkDestination,
@@ -39,7 +39,7 @@ import {
39
39
  mediaEditNodeAttributeName,
40
40
  mediaEditNodeAttributes,
41
41
  tiptapToMarkdown
42
- } from "./chunk-V7HUUMMB.js";
42
+ } from "./chunk-ZLKINE4Y.js";
43
43
  import {
44
44
  ImageEditor,
45
45
  ImageViewer,
@@ -21814,6 +21814,10 @@ ${TASK_LIST_MARKDOWN}
21814
21814
  const showBlockSectionInOverflow = showTemplateInOverflow || showTransitionInOverflow;
21815
21815
  const overflowBlockLabel = currentTemplate ? templateLabel(currentTemplate) : "Heading";
21816
21816
  const showToolbarOverflow = actionsLaneShown && (collapsedFormatButtons.length > 0 || showFormattingControls && clippedContextual.size > 0 || showInsertInOverflow || collapsedChrome.length > 0);
21817
+ const portalDialog = (dialog) => createPortal13(
21818
+ /* @__PURE__ */ jsx50("div", { className: "squisq-dialog-portal", "data-theme": colorScheme, children: dialog }),
21819
+ document.body
21820
+ );
21817
21821
  return /* @__PURE__ */ jsxs39(
21818
21822
  "div",
21819
21823
  {
@@ -22440,35 +22444,39 @@ ${TASK_LIST_MARKDOWN}
22440
22444
  }
22441
22445
  ),
22442
22446
  slotRight,
22443
- showDocumentChrome && showDocSettings && /* @__PURE__ */ jsx50(
22444
- DocumentSettingsDialog,
22445
- {
22446
- markdownSource,
22447
- onSave: (next) => {
22448
- setMarkdownSource(next);
22449
- setShowDocSettings(false);
22450
- },
22451
- onClose: () => setShowDocSettings(false),
22452
- defaultThemeId: previewSettings?.inheritedThemeId,
22453
- defaultThemeLabel: previewSettings?.inheritedThemeLabel
22454
- }
22447
+ showDocumentChrome && showDocSettings && portalDialog(
22448
+ /* @__PURE__ */ jsx50(
22449
+ DocumentSettingsDialog,
22450
+ {
22451
+ markdownSource,
22452
+ onSave: (next) => {
22453
+ setMarkdownSource(next);
22454
+ setShowDocSettings(false);
22455
+ },
22456
+ onClose: () => setShowDocSettings(false),
22457
+ defaultThemeId: previewSettings?.inheritedThemeId,
22458
+ defaultThemeLabel: previewSettings?.inheritedThemeLabel
22459
+ }
22460
+ )
22455
22461
  ),
22456
22462
  showDocumentChrome && showLayoutManager && /* @__PURE__ */ jsx50(CustomLayoutManager, { onClose: () => setShowLayoutManager(false) }),
22457
22463
  contextMenuAvailable && /* @__PURE__ */ jsx50(WikiLinkMenuItems, { onEdit: () => handleAction("link") }),
22458
- linkDialog && /* @__PURE__ */ jsx50(
22459
- LinkDialog,
22460
- {
22461
- mode: linkDialog.mode,
22462
- kind: linkDialog.wiki ? "wiki" : "url",
22463
- initialText: linkDialog.initialText,
22464
- initialUrl: linkDialog.initialUrl,
22465
- onConfirm: handleLinkConfirm,
22466
- onClose: () => setLinkDialog(null),
22467
- documentLinkProvider,
22468
- linkSchemes
22469
- }
22464
+ linkDialog && portalDialog(
22465
+ /* @__PURE__ */ jsx50(
22466
+ LinkDialog,
22467
+ {
22468
+ mode: linkDialog.mode,
22469
+ kind: linkDialog.wiki ? "wiki" : "url",
22470
+ initialText: linkDialog.initialText,
22471
+ initialUrl: linkDialog.initialUrl,
22472
+ onConfirm: handleLinkConfirm,
22473
+ onClose: () => setLinkDialog(null),
22474
+ documentLinkProvider,
22475
+ linkSchemes
22476
+ }
22477
+ )
22470
22478
  ),
22471
- videoEmbedDialog,
22479
+ videoEmbedDialog && portalDialog(videoEmbedDialog),
22472
22480
  insertMenuAnchor && createPortal13(
22473
22481
  /* @__PURE__ */ jsxs39(
22474
22482
  "div",
@@ -33903,10 +33911,41 @@ function persistFromWrite(bodyMd, state) {
33903
33911
  import { useCallback as useCallback43, useEffect as useEffect54, useMemo as useMemo41, useRef as useRef50, useState as useState67 } from "react";
33904
33912
  import { useEditor as useEditor2, EditorContent as EditorContent2 } from "@tiptap/react";
33905
33913
  import { Selection as Selection2 } from "@tiptap/pm/state";
33906
- import { Extension as Extension16 } from "@tiptap/core";
33914
+ import { Extension as Extension17, getHTMLFromFragment } from "@tiptap/core";
33907
33915
  import StarterKit2 from "@tiptap/starter-kit";
33908
33916
  import Table from "@tiptap/extension-table";
33909
33917
  import TableRow from "@tiptap/extension-table-row";
33918
+
33919
+ // src/tiptap/codeFenceSource.ts
33920
+ import { Extension as Extension16 } from "@tiptap/core";
33921
+ var FENCE_MARKER = /^(`{3,}|~{3,})$/;
33922
+ var CodeFenceSource = Extension16.create({
33923
+ name: "codeFenceSource",
33924
+ addGlobalAttributes() {
33925
+ return [
33926
+ {
33927
+ types: ["codeBlock"],
33928
+ attributes: {
33929
+ fenceMarker: {
33930
+ default: null,
33931
+ parseHTML: (element) => {
33932
+ const value = element.getAttribute("data-squisq-fence");
33933
+ return value && FENCE_MARKER.test(value) ? value : null;
33934
+ },
33935
+ renderHTML: (attributes) => typeof attributes.fenceMarker === "string" && FENCE_MARKER.test(attributes.fenceMarker) ? { "data-squisq-fence": attributes.fenceMarker } : {}
33936
+ },
33937
+ fenceMeta: {
33938
+ default: null,
33939
+ parseHTML: (element) => element.getAttribute("data-squisq-fence-meta") || null,
33940
+ renderHTML: (attributes) => typeof attributes.fenceMeta === "string" && attributes.fenceMeta ? { "data-squisq-fence-meta": attributes.fenceMeta } : {}
33941
+ }
33942
+ }
33943
+ }
33944
+ ];
33945
+ }
33946
+ });
33947
+
33948
+ // src/WysiwygEditor.tsx
33910
33949
  import TaskList from "@tiptap/extension-task-list";
33911
33950
  import TaskItem from "@tiptap/extension-task-item";
33912
33951
  import Link2 from "@tiptap/extension-link";
@@ -34549,6 +34588,15 @@ var ImageWithMediaProvider = Image.extend({
34549
34588
  return Number.isFinite(n) && n > 0 ? n : null;
34550
34589
  },
34551
34590
  renderHTML: (attrs) => attrs.height ? { height: String(attrs.height) } : {}
34591
+ },
34592
+ // A linked image (`[![alt](src)](url)`, a README badge). This image is
34593
+ // a block node, which a link mark cannot wrap, so the parser lifts it
34594
+ // out of its `<a>` and the link was lost on every save. Keep the target
34595
+ // as an attribute the Markdown bridge writes back around the image.
34596
+ href: {
34597
+ default: null,
34598
+ parseHTML: (element) => element.getAttribute("data-squisq-image-href") ?? element.closest("a[href]")?.getAttribute("href") ?? null,
34599
+ renderHTML: (attrs) => attrs.href ? { "data-squisq-image-href": attrs.href } : {}
34552
34600
  }
34553
34601
  };
34554
34602
  },
@@ -35127,6 +35175,316 @@ function DocumentTray({ editor }) {
35127
35175
  // src/WysiwygEditor.tsx
35128
35176
  import { profileBlockContents as profileBlockContents2, recommendTemplatesForBlock as recommendTemplatesForBlock2 } from "@bendyline/squisq/recommend";
35129
35177
 
35178
+ // src/sourcePreservation.ts
35179
+ function pairLines(a, b) {
35180
+ const aToB = new Int32Array(a.length).fill(-1);
35181
+ const work = [[0, a.length, 0, b.length]];
35182
+ while (work.length > 0) {
35183
+ let [aLo, aHi, bLo, bHi] = work.pop();
35184
+ while (aLo < aHi && bLo < bHi && a[aLo] === b[bLo]) aToB[aLo++] = bLo++;
35185
+ while (aHi > aLo && bHi > bLo && a[aHi - 1] === b[bHi - 1]) aToB[--aHi] = --bHi;
35186
+ if (aLo >= aHi || bLo >= bHi) continue;
35187
+ const countA = /* @__PURE__ */ new Map();
35188
+ const indexA = /* @__PURE__ */ new Map();
35189
+ for (let i = aLo; i < aHi; i++) {
35190
+ const line = a[i];
35191
+ if (line.trim() === "") continue;
35192
+ countA.set(line, (countA.get(line) ?? 0) + 1);
35193
+ indexA.set(line, i);
35194
+ }
35195
+ const countB = /* @__PURE__ */ new Map();
35196
+ for (let j = bLo; j < bHi; j++) {
35197
+ const line = b[j];
35198
+ if (countA.has(line)) countB.set(line, (countB.get(line) ?? 0) + 1);
35199
+ }
35200
+ const candidates = [];
35201
+ for (let j = bLo; j < bHi; j++) {
35202
+ const line = b[j];
35203
+ if (countB.get(line) === 1 && countA.get(line) === 1) {
35204
+ candidates.push([indexA.get(line), j]);
35205
+ }
35206
+ }
35207
+ const anchors = longestIncreasingByA(candidates);
35208
+ if (anchors.length === 0) continue;
35209
+ let prevA = aLo;
35210
+ let prevB = bLo;
35211
+ for (const [i, j] of anchors) {
35212
+ aToB[i] = j;
35213
+ work.push([prevA, i, prevB, j]);
35214
+ prevA = i + 1;
35215
+ prevB = j + 1;
35216
+ }
35217
+ work.push([prevA, aHi, prevB, bHi]);
35218
+ }
35219
+ return aToB;
35220
+ }
35221
+ function longestIncreasingByA(pairs) {
35222
+ if (pairs.length === 0) return [];
35223
+ const tails = [];
35224
+ const previous = new Int32Array(pairs.length).fill(-1);
35225
+ for (let p = 0; p < pairs.length; p++) {
35226
+ const value = pairs[p][0];
35227
+ let lo = 0;
35228
+ let hi = tails.length;
35229
+ while (lo < hi) {
35230
+ const mid = lo + hi >> 1;
35231
+ if (pairs[tails[mid]][0] < value) lo = mid + 1;
35232
+ else hi = mid;
35233
+ }
35234
+ if (lo > 0) previous[p] = tails[lo - 1];
35235
+ tails[lo] = p;
35236
+ }
35237
+ const result = [];
35238
+ for (let p = tails[tails.length - 1]; p >= 0; p = previous[p]) result.push(pairs[p]);
35239
+ return result.reverse();
35240
+ }
35241
+ function lineKeys(lines2) {
35242
+ return lines2.map((line) => line.endsWith("\r") ? line.slice(0, -1) : line);
35243
+ }
35244
+ function detectLineEnding(text) {
35245
+ const breaks = text.split("\n").length - 1;
35246
+ if (breaks === 0) return "\n";
35247
+ const crlf = text.split("\r\n").length - 1;
35248
+ return crlf * 2 > breaks ? "\r\n" : "\n";
35249
+ }
35250
+ function blocksIn(lines2, roles, start, end) {
35251
+ const blocks = [];
35252
+ let blockStart = -1;
35253
+ for (let i = start; i < end; i++) {
35254
+ const separator = lines2[i].trim() === "" && roles[i] === "out";
35255
+ if (separator) {
35256
+ if (blockStart >= 0) blocks.push([blockStart, i]);
35257
+ blockStart = -1;
35258
+ } else if (blockStart < 0) {
35259
+ blockStart = i;
35260
+ }
35261
+ }
35262
+ if (blockStart >= 0) blocks.push([blockStart, end]);
35263
+ return blocks;
35264
+ }
35265
+ function blockText(lines2, [start, end]) {
35266
+ return lines2.slice(start, end).join("\n").toLowerCase().replace(/[^\p{L}\p{N}]+/gu, "");
35267
+ }
35268
+ function refineGap(regions, s, n, sStart, sEnd, nStart, nEnd) {
35269
+ const sBlocks = blocksIn(s.lines, s.roles, sStart, sEnd);
35270
+ const nBlocks = blocksIn(n.lines, n.roles, nStart, nEnd);
35271
+ const same = (a, b) => {
35272
+ const text = blockText(s.lines, a);
35273
+ return text !== "" && text === blockText(n.lines, b);
35274
+ };
35275
+ let head = 0;
35276
+ while (head < sBlocks.length && head < nBlocks.length && same(sBlocks[head], nBlocks[head])) {
35277
+ head++;
35278
+ }
35279
+ let tail = 0;
35280
+ while (tail < sBlocks.length - head && tail < nBlocks.length - head && same(sBlocks[sBlocks.length - 1 - tail], nBlocks[nBlocks.length - 1 - tail])) {
35281
+ tail++;
35282
+ }
35283
+ let sAt = sStart;
35284
+ let nAt = nStart;
35285
+ const push = (sTo, nTo) => {
35286
+ if (sTo > sAt || nTo > nAt) regions.push({ sStart: sAt, sEnd: sTo, nStart: nAt, nEnd: nTo });
35287
+ sAt = sTo;
35288
+ nAt = nTo;
35289
+ };
35290
+ const pairs = [];
35291
+ for (let k2 = 0; k2 < head; k2++) pairs.push([sBlocks[k2], nBlocks[k2]]);
35292
+ const middleS = head < sBlocks.length - tail ? sBlocks[head][0] : null;
35293
+ const middleN = head < nBlocks.length - tail ? nBlocks[head][0] : null;
35294
+ for (const [sBlock, nBlock] of pairs) {
35295
+ push(sBlock[0], nBlock[0]);
35296
+ push(sBlock[1], nBlock[1]);
35297
+ }
35298
+ if (middleS !== null || middleN !== null) {
35299
+ const sTo = tail > 0 ? sBlocks[sBlocks.length - tail][0] : sEnd;
35300
+ const nTo = tail > 0 ? nBlocks[nBlocks.length - tail][0] : nEnd;
35301
+ push(sTo, nTo);
35302
+ }
35303
+ for (let k2 = tail; k2 > 0; k2--) {
35304
+ const sBlock = sBlocks[sBlocks.length - k2];
35305
+ const nBlock = nBlocks[nBlocks.length - k2];
35306
+ push(sBlock[0], nBlock[0]);
35307
+ push(sBlock[1], nBlock[1]);
35308
+ }
35309
+ push(sEnd, nEnd);
35310
+ }
35311
+ function createSourceBaseline(original, normalized) {
35312
+ const originalLines = original.split("\n");
35313
+ const normalizedLines = normalized.split("\n");
35314
+ const originalRoles = literalRoles(originalLines);
35315
+ const sides = {
35316
+ s: { lines: originalLines, roles: originalRoles },
35317
+ n: { lines: normalizedLines, roles: literalRoles(normalizedLines) }
35318
+ };
35319
+ const sToN = pairLines(lineKeys(originalLines), lineKeys(normalizedLines));
35320
+ const regions = [];
35321
+ let s = 0;
35322
+ let n = 0;
35323
+ const pushGap = (sEnd, nEnd) => {
35324
+ if (sEnd > s || nEnd > n) refineGap(regions, sides.s, sides.n, s, sEnd, n, nEnd);
35325
+ };
35326
+ for (let i = 0; i < originalLines.length; i++) {
35327
+ const j = sToN[i];
35328
+ if (j < 0) continue;
35329
+ pushGap(i, j);
35330
+ regions.push({ sStart: i, sEnd: i + 1, nStart: j, nEnd: j + 1 });
35331
+ s = i + 1;
35332
+ n = j + 1;
35333
+ }
35334
+ pushGap(originalLines.length, normalizedLines.length);
35335
+ return {
35336
+ original,
35337
+ normalized,
35338
+ originalLines,
35339
+ normalizedLines,
35340
+ regions,
35341
+ originalRoles,
35342
+ lineEnding: detectLineEnding(original)
35343
+ };
35344
+ }
35345
+ function literalRoles(lines2) {
35346
+ const roles = [];
35347
+ let fence = null;
35348
+ let math = false;
35349
+ let comment = false;
35350
+ for (const raw of lines2) {
35351
+ const line = raw.endsWith("\r") ? raw.slice(0, -1) : raw;
35352
+ if (fence) {
35353
+ const close = /^ {0,3}(`{3,}|~{3,})[ \t]*$/.exec(line);
35354
+ if (close && close[1][0] === fence.char && close[1].length >= fence.length) {
35355
+ fence = null;
35356
+ roles.push("close");
35357
+ } else {
35358
+ roles.push("in");
35359
+ }
35360
+ continue;
35361
+ }
35362
+ if (math) {
35363
+ if (line.trim() === "$$") {
35364
+ math = false;
35365
+ roles.push("close");
35366
+ } else {
35367
+ roles.push("in");
35368
+ }
35369
+ continue;
35370
+ }
35371
+ if (comment) {
35372
+ if (line.includes("-->")) {
35373
+ comment = false;
35374
+ roles.push("close");
35375
+ } else {
35376
+ roles.push("in");
35377
+ }
35378
+ continue;
35379
+ }
35380
+ const open = /^ {0,3}(`{3,}|~{3,})(.*)$/.exec(line);
35381
+ if (open && !(open[1][0] === "`" && open[2].includes("`"))) {
35382
+ fence = { char: open[1][0], length: open[1].length };
35383
+ roles.push("open");
35384
+ continue;
35385
+ }
35386
+ if (line.trim() === "$$") {
35387
+ math = true;
35388
+ roles.push("open");
35389
+ continue;
35390
+ }
35391
+ const commentStart = line.indexOf("<!--");
35392
+ if (commentStart >= 0 && !line.includes("-->", commentStart + 4)) {
35393
+ comment = true;
35394
+ roles.push("open");
35395
+ continue;
35396
+ }
35397
+ roles.push("out");
35398
+ }
35399
+ return roles;
35400
+ }
35401
+ function preserveSource(baseline, edited) {
35402
+ if (baseline.original === baseline.normalized) return edited;
35403
+ if (edited === baseline.normalized) return baseline.original;
35404
+ const editedLines = edited.split("\n");
35405
+ const nToM = pairLines(lineKeys(baseline.normalizedLines), lineKeys(editedLines));
35406
+ const { regions } = baseline;
35407
+ const clean = new Array(regions.length);
35408
+ const mStart = new Int32Array(regions.length);
35409
+ const mEnd = new Int32Array(regions.length);
35410
+ for (let r = 0; r < regions.length; r++) {
35411
+ const region = regions[r];
35412
+ if (region.nEnd === region.nStart) {
35413
+ clean[r] = false;
35414
+ continue;
35415
+ }
35416
+ let ok = true;
35417
+ const first = nToM[region.nStart];
35418
+ for (let k2 = region.nStart; k2 < region.nEnd && ok; k2++) {
35419
+ ok = nToM[k2] === first + (k2 - region.nStart) && first >= 0;
35420
+ }
35421
+ clean[r] = ok;
35422
+ mStart[r] = first;
35423
+ mEnd[r] = first + (region.nEnd - region.nStart);
35424
+ }
35425
+ const isBlank2 = (lines2, start, end) => {
35426
+ for (let k2 = start; k2 < end; k2++) if (lines2[k2].trim() !== "") return false;
35427
+ return true;
35428
+ };
35429
+ const separatorIsSafe = regions.map((region, r) => {
35430
+ if (region.nEnd === region.nStart) return true;
35431
+ if (!isBlank2(baseline.normalizedLines, region.nStart, region.nEnd)) return true;
35432
+ if (!isBlank2(baseline.originalLines, region.sStart, region.sEnd)) return true;
35433
+ const neighbourClean = (k2) => k2 < 0 || k2 >= regions.length || regions[k2].nEnd === regions[k2].nStart || clean[k2];
35434
+ return neighbourClean(r - 1) && neighbourClean(r + 1);
35435
+ });
35436
+ for (let r = 0; r < regions.length; r++) if (!separatorIsSafe[r]) clean[r] = false;
35437
+ for (let r = 0; r < regions.length; r++) {
35438
+ const region = regions[r];
35439
+ if (region.nEnd !== region.nStart) continue;
35440
+ const before = r - 1;
35441
+ const after = r + 1;
35442
+ let position = -1;
35443
+ if (before < 0) position = 0;
35444
+ else if (clean[before]) position = mEnd[before];
35445
+ else if (after >= regions.length) position = editedLines.length;
35446
+ else if (clean[after]) position = mStart[after];
35447
+ clean[r] = position >= 0;
35448
+ mStart[r] = mEnd[r] = position;
35449
+ }
35450
+ const out = [];
35451
+ const needsLineEnding = [];
35452
+ const sourceRole = [];
35453
+ const lastOriginal = baseline.originalLines.length - 1;
35454
+ const editedRoles = literalRoles(editedLines);
35455
+ let cursor = 0;
35456
+ const emitEdited = (end) => {
35457
+ for (; cursor < end; cursor++) {
35458
+ out.push(editedLines[cursor]);
35459
+ needsLineEnding.push(true);
35460
+ sourceRole.push(editedRoles[cursor]);
35461
+ }
35462
+ };
35463
+ for (let r = 0; r < regions.length; r++) {
35464
+ if (!clean[r]) continue;
35465
+ const region = regions[r];
35466
+ if (mStart[r] < cursor) return edited;
35467
+ emitEdited(mStart[r]);
35468
+ for (let k2 = region.sStart; k2 < region.sEnd; k2++) {
35469
+ out.push(baseline.originalLines[k2]);
35470
+ needsLineEnding.push(k2 === lastOriginal);
35471
+ sourceRole.push(baseline.originalRoles[k2]);
35472
+ }
35473
+ cursor = mEnd[r];
35474
+ }
35475
+ emitEdited(editedLines.length);
35476
+ const assembledRoles = literalRoles(out);
35477
+ for (let k2 = 0; k2 < out.length; k2++) {
35478
+ if (assembledRoles[k2] !== sourceRole[k2]) return edited;
35479
+ }
35480
+ if (baseline.lineEnding === "\r\n") {
35481
+ for (let k2 = 0; k2 < out.length - 1; k2++) {
35482
+ if (needsLineEnding[k2] && !out[k2].endsWith("\r")) out[k2] += "\r";
35483
+ }
35484
+ }
35485
+ return out.join("\n");
35486
+ }
35487
+
35130
35488
  // src/MentionExtension.tsx
35131
35489
  import Mention from "@tiptap/extension-mention";
35132
35490
  import { PluginKey as PluginKey17 } from "@tiptap/pm/state";
@@ -35621,7 +35979,7 @@ var BLOCK_TAG_DATA_VALUES = {
35621
35979
  function pickEmptyPrompt() {
35622
35980
  return EMPTY_PROMPTS[Math.floor(Math.random() * EMPTY_PROMPTS.length)];
35623
35981
  }
35624
- var SourceFrontmatter = Extension16.create({
35982
+ var SourceFrontmatter = Extension17.create({
35625
35983
  name: "sourceFrontmatter",
35626
35984
  addOptions() {
35627
35985
  return { initial: "" };
@@ -35781,6 +36139,10 @@ function WysiwygEditor({
35781
36139
  initialDisplayBodyRef.current = body;
35782
36140
  }
35783
36141
  }
36142
+ const loadedSourceRef = useRef50(null);
36143
+ if (loadedSourceRef.current === null) {
36144
+ loadedSourceRef.current = { body: stripFrontmatter(editorSource).body, baseline: null };
36145
+ }
35784
36146
  const initialContentRef = useRef50(void 0);
35785
36147
  if (initialContentRef.current === void 0) {
35786
36148
  initialContentRef.current = markdownToTiptap(initialDisplayBodyRef.current);
@@ -35807,6 +36169,7 @@ function WysiwygEditor({
35807
36169
  }
35808
36170
  }),
35809
36171
  HeadingWithTemplate.configure({ levels: [1, 2, 3, 4, 5, 6] }),
36172
+ CodeFenceSource,
35810
36173
  Superscript,
35811
36174
  Subscript,
35812
36175
  MarkdownEscape,
@@ -35865,7 +36228,16 @@ function WysiwygEditor({
35865
36228
  onUpdate: ({ editor: ed, transaction }) => {
35866
36229
  if (!transaction.docChanged) return;
35867
36230
  if (isExternalUpdate.current || externalApplyPendingRef.current) return;
35868
- const bodyMd = persistFromWrite(tiptapToMarkdown(ed.getHTML()), wrapStateRef.current);
36231
+ const serialize = (html) => persistFromWrite(tiptapToMarkdown(html), wrapStateRef.current);
36232
+ let bodyMd = serialize(ed.getHTML());
36233
+ const loaded = loadedSourceRef.current;
36234
+ if (loaded) {
36235
+ loaded.baseline ?? (loaded.baseline = createSourceBaseline(
36236
+ loaded.body,
36237
+ serialize(getHTMLFromFragment(transaction.before.content, ed.schema))
36238
+ ));
36239
+ bodyMd = preserveSource(loaded.baseline, bodyMd);
36240
+ }
35869
36241
  const frontmatter = sourceLayoutRef.current === "document" ? String(ed.state.doc.attrs.sourceFrontmatter ?? "") : frontmatterRef.current;
35870
36242
  const newSource = frontmatter + bodyMd;
35871
36243
  pendingLocalSourcesRef.current.push(newSource);
@@ -36121,6 +36493,7 @@ function WysiwygEditor({
36121
36493
  const { body, frontmatter } = stripFrontmatter(editorSource);
36122
36494
  pendingLocalSourcesRef.current = [];
36123
36495
  frontmatterRef.current = frontmatter;
36496
+ loadedSourceRef.current = { body, baseline: null };
36124
36497
  let displayBody = body;
36125
36498
  if (wrapPolicyEnabled) {
36126
36499
  const ingest = ingestForWrite(body);
@@ -65,6 +65,57 @@ function escapeLinkLabel(text) {
65
65
  return text.replace(/([\\[\]])/g, "\\$1");
66
66
  }
67
67
 
68
+ // src/htmlEntities.ts
69
+ function escapeHtml(text) {
70
+ return text.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;");
71
+ }
72
+ function unescapeHtml(text) {
73
+ return text.replace(/&lt;/g, "<").replace(/&gt;/g, ">").replace(/&quot;/g, '"').replace(/&nbsp;|&#160;|&#xA0;/gi, "\xA0").replace(/&amp;/g, "&");
74
+ }
75
+
76
+ // src/markdownFence.ts
77
+ var BACKTICK = "`";
78
+ function matchFenceOpen(line) {
79
+ const match = /^( {0,3})(`{3,}|~{3,})(.*)$/.exec(line);
80
+ if (!match) return null;
81
+ const marker = match[2];
82
+ const info = match[3];
83
+ if (marker[0] === BACKTICK && info.includes(BACKTICK)) return null;
84
+ return { indent: match[1].length, marker, info: info.trim() };
85
+ }
86
+ function isFenceClose(line, fence) {
87
+ const match = /^ {0,3}(`{3,}|~{3,})[ \t]*$/.exec(line);
88
+ return !!match && match[1][0] === fence.marker[0] && match[1].length >= fence.marker.length;
89
+ }
90
+ function fenceContentLine(line, fence) {
91
+ const indent = /^ */.exec(line)[0].length;
92
+ return line.slice(Math.min(indent, fence.indent));
93
+ }
94
+ function fencedCodeToHtml(fence, codeLines) {
95
+ const [lang = "", ...rest] = fence.info.split(/\s+/);
96
+ const meta = rest.join(" ");
97
+ const langAttr = lang ? ` class="language-${escapeHtml(lang)}"` : "";
98
+ const markerAttr = fence.marker === "```" ? "" : ` data-squisq-fence="${fence.marker}"`;
99
+ const metaAttr = meta ? ` data-squisq-fence-meta="${escapeHtml(meta)}"` : "";
100
+ return `<pre${markerAttr}${metaAttr}><code${langAttr}>${escapeHtml(codeLines.join("\n"))}</code></pre>`;
101
+ }
102
+ function fenceForCode(code, info, preferred) {
103
+ const char = preferred[0] === "~" || info.includes(BACKTICK) ? "~" : BACKTICK;
104
+ let length = preferred[0] === char ? Math.max(3, preferred.length) : 3;
105
+ const runs = char === "~" ? /^ {0,3}(~{3,})/gm : /^ {0,3}(`{3,})/gm;
106
+ for (const run of code.matchAll(runs)) length = Math.max(length, run[1].length + 1);
107
+ return char.repeat(length);
108
+ }
109
+ function fencedCodeToMarkdown(preAttrs, codeAttrs, codeHtml) {
110
+ const lang = unescapeHtml(/\bclass="language-([^"]*)"/.exec(codeAttrs)?.[1] ?? "");
111
+ const marker = /\bdata-squisq-fence="([`~]{3,})"/.exec(preAttrs)?.[1] ?? "```";
112
+ const meta = unescapeHtml(/\bdata-squisq-fence-meta="([^"]*)"/.exec(preAttrs)?.[1] ?? "");
113
+ const info = meta ? `${lang} ${meta}`.trim() : lang;
114
+ const code = unescapeHtml(codeHtml);
115
+ const fence = fenceForCode(code, info, marker);
116
+ return [fence + info, code, fence];
117
+ }
118
+
68
119
  // src/markdownQuote.ts
69
120
  function isQuoteLine(line) {
70
121
  return line.startsWith("> ") || line.trimEnd() === ">";
@@ -210,8 +261,7 @@ var RE_ESCAPE_SPAN = new RegExp(
210
261
  "gi"
211
262
  );
212
263
  var RE_INLINE_CODE = /`(.+?)`/g;
213
- var BACKTICK = "`";
214
- var FENCE = "```";
264
+ var BACKTICK2 = "`";
215
265
  var RE_IMAGE = /!\[(.*?)\]\((.+?)\)/g;
216
266
  var RE_MENTION = /@\[([^\]]+?)\]\(([a-z][a-z0-9+.-]*)\\?:([^)\s]+)\)/gi;
217
267
  var RE_MENTION_TAG = /<span\b[^>]*?\bdata-mention\b[^>]*?>(?:<[^>]+>)*([^<]*)<\/span>/gi;
@@ -224,17 +274,23 @@ var RE_A_TAG = /<a\b([^>]*)>(.*?)<\/a>/g;
224
274
  var RE_IMG_TAG = /<img\b([^>]*)>/g;
225
275
  var RE_STRIP_TAGS = /<[^>]+>/g;
226
276
  function joinWrappedCodeSpans(lines) {
227
- if (!lines.some((line) => line.includes(BACKTICK))) return lines;
277
+ if (!lines.some((line) => line.includes(BACKTICK2))) return lines;
228
278
  const joined = [];
229
- let inFence = false;
279
+ let fence = null;
230
280
  for (let i = 0; i < lines.length; i++) {
231
281
  const line = lines[i];
232
- if (line.startsWith(FENCE)) {
233
- inFence = !inFence;
282
+ if (fence) {
283
+ if (isFenceClose(line, fence)) fence = null;
234
284
  joined.push(line);
235
285
  continue;
236
286
  }
237
- if (inFence || !leavesCodeSpanOpen(line)) {
287
+ const opener = matchFenceOpen(line);
288
+ if (opener) {
289
+ fence = opener;
290
+ joined.push(line);
291
+ continue;
292
+ }
293
+ if (!leavesCodeSpanOpen(line)) {
238
294
  joined.push(line);
239
295
  continue;
240
296
  }
@@ -243,7 +299,7 @@ function joinWrappedCodeSpans(lines) {
243
299
  let closed = false;
244
300
  while (next < lines.length) {
245
301
  const candidate = lines[next];
246
- if (candidate.trim() === "" || candidate.startsWith(FENCE)) break;
302
+ if (candidate.trim() === "" || matchFenceOpen(candidate)) break;
247
303
  merged = `${merged} ${candidate.replace(/^[ \t]+/, "")}`;
248
304
  next++;
249
305
  if (!leavesCodeSpanOpen(merged)) {
@@ -261,7 +317,7 @@ function joinWrappedCodeSpans(lines) {
261
317
  return joined;
262
318
  }
263
319
  function leavesCodeSpanOpen(text) {
264
- return text.replace(RE_INLINE_CODE, "").includes(BACKTICK);
320
+ return text.replace(RE_INLINE_CODE, "").includes(BACKTICK2);
265
321
  }
266
322
  var QUOTE_CONVERTERS = {
267
323
  markdownToHtml: (markdown) => markdownToTiptap(markdown),
@@ -276,8 +332,7 @@ function markdownToTiptap(markdown) {
276
332
  const lines = joinWrappedCodeSpans(html.split("\n"));
277
333
  const frontmatterEnd = findFrontmatterEnd(lines);
278
334
  const outputBlocks = [];
279
- let inCodeBlock = false;
280
- let codeBlockLang = "";
335
+ let codeFence = null;
281
336
  let codeBlockLines = [];
282
337
  const listStack = [];
283
338
  let inTable = false;
@@ -415,26 +470,23 @@ function markdownToTiptap(markdown) {
415
470
  };
416
471
  for (let i = 0; i < lines.length; i++) {
417
472
  const line = lines[i];
418
- if (line.startsWith("```")) {
419
- if (!inCodeBlock) {
420
- flushList();
421
- flushTable();
422
- flushPendingBlankParagraphs();
423
- inCodeBlock = true;
424
- codeBlockLang = line.slice(3).trim();
473
+ if (codeFence) {
474
+ if (isFenceClose(line, codeFence)) {
475
+ pushBlock(fencedCodeToHtml(codeFence, codeBlockLines));
476
+ codeFence = null;
425
477
  codeBlockLines = [];
426
- continue;
427
478
  } else {
428
- const langAttr = codeBlockLang ? ` class="language-${escapeHtml(codeBlockLang)}"` : "";
429
- pushBlock(`<pre><code${langAttr}>${escapeHtml(codeBlockLines.join("\n"))}</code></pre>`);
430
- inCodeBlock = false;
431
- codeBlockLang = "";
432
- codeBlockLines = [];
433
- continue;
479
+ codeBlockLines.push(fenceContentLine(line, codeFence));
434
480
  }
481
+ continue;
435
482
  }
436
- if (inCodeBlock) {
437
- codeBlockLines.push(line);
483
+ const fenceOpen = matchFenceOpen(line);
484
+ if (fenceOpen && (fenceOpen.indent === 0 || listStack.length === 0)) {
485
+ flushList();
486
+ flushTable();
487
+ flushPendingBlankParagraphs();
488
+ codeFence = fenceOpen;
489
+ codeBlockLines = [];
438
490
  continue;
439
491
  }
440
492
  if (inTable && !/^\|.*\|$/.test(line.trim())) {
@@ -524,6 +576,15 @@ function markdownToTiptap(markdown) {
524
576
  tableLines.push(line);
525
577
  continue;
526
578
  }
579
+ const linkedImageMatch = line.trim().match(/^\[!\[([^\]]*)\]\(([^()\s]+)\)\]\(([^()\s]+)\)$/);
580
+ if (linkedImageMatch) {
581
+ flushList();
582
+ const alt = escapeHtml(linkedImageMatch[1] ?? "");
583
+ const src = escapeHtml(unwrapLinkDestination(linkedImageMatch[2] ?? ""));
584
+ const href = escapeHtml(unwrapLinkDestination(linkedImageMatch[3] ?? ""));
585
+ pushBlock(`<img alt="${alt}" src="${src}" data-squisq-image-href="${href}">`);
586
+ continue;
587
+ }
527
588
  const standaloneImageMatch = line.trim().match(/^!\[(.*?)\]\((.+?)\)$/);
528
589
  if (standaloneImageMatch) {
529
590
  flushList();
@@ -563,9 +624,8 @@ function markdownToTiptap(markdown) {
563
624
  ).join("<br>");
564
625
  pushBlock(`<p>${paragraphHtml}</p>`);
565
626
  }
566
- if (inCodeBlock) {
567
- const langAttr = codeBlockLang ? ` class="language-${escapeHtml(codeBlockLang)}"` : "";
568
- pushBlock(`<pre><code${langAttr}>${escapeHtml(codeBlockLines.join("\n"))}</code></pre>`);
627
+ if (codeFence) {
628
+ pushBlock(fencedCodeToHtml(codeFence, codeBlockLines));
569
629
  }
570
630
  flushList();
571
631
  flushTable();
@@ -608,13 +668,9 @@ function tiptapToMarkdown(html) {
608
668
  remaining = remaining.slice(headingMatch[0].length);
609
669
  continue;
610
670
  }
611
- const codeMatch = remaining.match(/^<pre\b[^>]*><code\b([^>]*)>(.*?)<\/code><\/pre>/s);
671
+ const codeMatch = remaining.match(/^<pre\b([^>]*)><code\b([^>]*)>(.*?)<\/code><\/pre>/s);
612
672
  if (codeMatch) {
613
- const lang = /\bclass="language-([^"]*)"/.exec(codeMatch[1] ?? "")?.[1] ?? "";
614
- const code = unescapeHtml(codeMatch[2]);
615
- lines.push("```" + lang);
616
- lines.push(code);
617
- lines.push("```");
673
+ lines.push(...fencedCodeToMarkdown(codeMatch[1] ?? "", codeMatch[2] ?? "", codeMatch[3]));
618
674
  lines.push("");
619
675
  remaining = remaining.slice(codeMatch[0].length);
620
676
  continue;
@@ -718,7 +774,7 @@ function tiptapToMarkdown(html) {
718
774
  if (src) {
719
775
  const alt = /\balt="([^"]*)"/i.exec(attrs)?.[1] ?? "";
720
776
  const serialized = serializeImage(src, alt, attrs);
721
- lines.push(serialized.startsWith("<") ? serialized : unescapeHtml(serialized));
777
+ lines.push(serialized.includes("<img") ? serialized : unescapeHtml(serialized));
722
778
  lines.push("");
723
779
  }
724
780
  remaining = remaining.slice(imgMatch[0].length);
@@ -1037,26 +1093,22 @@ function serializeImage(src, alt, attrs) {
1037
1093
  const width = /\bwidth="([^"]*)"/i.exec(attrs)?.[1];
1038
1094
  const height = /\bheight="([^"]*)"/i.exec(attrs)?.[1];
1039
1095
  const title = /\btitle="([^"]*)"/i.exec(attrs)?.[1];
1096
+ const href = /\bdata-squisq-image-href="([^"]*)"/i.exec(attrs)?.[1];
1097
+ const linked = (image) => href ? `[${image}](${escapedDestination(href)})` : image;
1040
1098
  if (title?.startsWith("squisq-upload:")) return "";
1041
1099
  if (!width && !height) {
1042
- return `![${alt}](${escapedDestination(src)})`;
1100
+ return linked(`![${alt}](${escapedDestination(src)})`);
1043
1101
  }
1044
1102
  const parts = [`<img alt="${alt}" src="${src}"`];
1045
1103
  if (width) parts.push(` width="${width}"`);
1046
1104
  if (height) parts.push(` height="${height}"`);
1047
1105
  if (title) parts.push(` title="${title}"`);
1048
1106
  parts.push(">");
1049
- return parts.join("");
1107
+ return linked(parts.join(""));
1050
1108
  }
1051
1109
  function escapedDestination(attrValue) {
1052
1110
  return needsAngleDestination(attrValue) ? `&lt;${attrValue}&gt;` : attrValue;
1053
1111
  }
1054
- function escapeHtml(text) {
1055
- return text.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;");
1056
- }
1057
- function unescapeHtml(text) {
1058
- return text.replace(/&lt;/g, "<").replace(/&gt;/g, ">").replace(/&quot;/g, '"').replace(/&nbsp;|&#160;|&#xA0;/gi, "\xA0").replace(/&amp;/g, "&");
1059
- }
1060
1112
  function inlineToHtml(text) {
1061
1113
  const placeholders = [];
1062
1114
  const stash = (html) => {
package/dist/index.js CHANGED
@@ -243,7 +243,7 @@ import {
243
243
  videoEmbedParagraphJson,
244
244
  viewStateFromBinding,
245
245
  writeHeadingViewState
246
- } from "./chunk-WEEHXZUY.js";
246
+ } from "./chunk-WZONLKTC.js";
247
247
  import {
248
248
  EditorProvider,
249
249
  applySourceEditsToText,
@@ -264,7 +264,7 @@ import {
264
264
  useBlockNavigator,
265
265
  useEditorContext,
266
266
  useEditorInsertMenuItems
267
- } from "./chunk-OMSRCHQ4.js";
267
+ } from "./chunk-PCIVTLQG.js";
268
268
  import {
269
269
  SPEECH_INPUT_SAMPLE_RATE,
270
270
  isSpeechInputProviderFactory,
@@ -273,14 +273,14 @@ import {
273
273
  import "./chunk-A2H3JOA5.js";
274
274
  import {
275
275
  JsonEditor
276
- } from "./chunk-JMZ7RALZ.js";
276
+ } from "./chunk-LDU3AWER.js";
277
277
  import {
278
278
  MarkdownEscape
279
- } from "./chunk-SINLDQ63.js";
279
+ } from "./chunk-4MBH7MMU.js";
280
280
  import {
281
281
  markdownToTiptap,
282
282
  tiptapToMarkdown
283
- } from "./chunk-V7HUUMMB.js";
283
+ } from "./chunk-ZLKINE4Y.js";
284
284
  import {
285
285
  ImageEditor,
286
286
  ImageViewer,
@@ -1,8 +1,8 @@
1
1
  import {
2
2
  JsonEditor
3
- } from "../chunk-JMZ7RALZ.js";
4
- import "../chunk-SINLDQ63.js";
5
- import "../chunk-V7HUUMMB.js";
3
+ } from "../chunk-LDU3AWER.js";
4
+ import "../chunk-4MBH7MMU.js";
5
+ import "../chunk-ZLKINE4Y.js";
6
6
  export {
7
7
  JsonEditor
8
8
  };
@@ -19,9 +19,9 @@ import {
19
19
  updateReviewDecorations,
20
20
  useReview,
21
21
  useReviewState
22
- } from "../chunk-OMSRCHQ4.js";
22
+ } from "../chunk-PCIVTLQG.js";
23
23
  import "../chunk-DNEVUOBZ.js";
24
- import "../chunk-V7HUUMMB.js";
24
+ import "../chunk-ZLKINE4Y.js";
25
25
 
26
26
  // src/review/proofingAdapter.ts
27
27
  import {
@@ -5,16 +5,16 @@ import {
5
5
  RawEditor,
6
6
  WysiwygEditor,
7
7
  useEditorContextMenuItems
8
- } from "../chunk-WEEHXZUY.js";
8
+ } from "../chunk-WZONLKTC.js";
9
9
  import {
10
10
  EditorProvider,
11
11
  useEditorContext,
12
12
  useEditorInsertMenuItems
13
- } from "../chunk-OMSRCHQ4.js";
13
+ } from "../chunk-PCIVTLQG.js";
14
14
  import "../chunk-DNEVUOBZ.js";
15
15
  import "../chunk-A2H3JOA5.js";
16
- import "../chunk-SINLDQ63.js";
17
- import "../chunk-V7HUUMMB.js";
16
+ import "../chunk-4MBH7MMU.js";
17
+ import "../chunk-ZLKINE4Y.js";
18
18
  import "../chunk-HLU6ZBS7.js";
19
19
  import "../chunk-LXGQA2SY.js";
20
20
  import "../chunk-LRZJD5YD.js";
@@ -7939,6 +7939,10 @@
7939
7939
  container-type: inline-size;
7940
7940
  container-name: squisq-toolbar;
7941
7941
  }
7942
+ .squisq-dialog-portal {
7943
+ font-family: var( --squisq-ux-font, -apple-system, BlinkMacSystemFont, "Segoe UI", "Noto Sans", Helvetica, Arial, sans-serif );
7944
+ color: var(--squisq-text);
7945
+ }
7942
7946
  .squisq-toolbar-view-tabs {
7943
7947
  display: flex;
7944
7948
  gap: 0;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bendyline/squisq-editor-react",
3
- "version": "2.13.6",
3
+ "version": "2.13.7",
4
4
  "description": "React editor shell with raw/WYSIWYG/preview modes for Squisq documents",
5
5
  "license": "MIT",
6
6
  "author": "Bendyline",
@@ -102,12 +102,12 @@
102
102
  }
103
103
  },
104
104
  "dependencies": {
105
- "@bendyline/squisq": "2.13.4",
105
+ "@bendyline/squisq": "2.13.5",
106
106
  "@bendyline/squisq-calc": "2.11.2",
107
- "@bendyline/squisq-formats": "2.8.5",
108
- "@bendyline/squisq-grid-react": "2.11.33",
109
- "@bendyline/squisq-react": "2.12.13",
110
- "@bendyline/squisq-video-react": "2.5.14",
107
+ "@bendyline/squisq-formats": "2.8.6",
108
+ "@bendyline/squisq-grid-react": "2.11.34",
109
+ "@bendyline/squisq-react": "2.12.14",
110
+ "@bendyline/squisq-video-react": "2.5.15",
111
111
  "@fortawesome/fontawesome-free": "7.3.1",
112
112
  "@tiptap/core": "2.27.3",
113
113
  "@tiptap/extension-heading": "2.27.3",