@pixerate/editor 0.21.0 → 0.22.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (70) hide show
  1. package/README.md +20 -11
  2. package/dist/agent/index.cjs +8 -8
  3. package/dist/agent/index.d.cts +2 -2
  4. package/dist/agent/index.d.ts +2 -2
  5. package/dist/agent/index.js +2 -2
  6. package/dist/canvas/index.cjs +1269 -13
  7. package/dist/canvas/index.cjs.map +1 -1
  8. package/dist/canvas/index.d.cts +432 -6
  9. package/dist/canvas/index.d.ts +432 -6
  10. package/dist/canvas/index.js +1264 -8
  11. package/dist/canvas/index.js.map +1 -1
  12. package/dist/{chunk-U6NDQFOK.cjs → chunk-6M3BCTBD.cjs} +427 -138
  13. package/dist/chunk-6M3BCTBD.cjs.map +1 -0
  14. package/dist/{chunk-TVRC4KVT.cjs → chunk-AA4ZEVCC.cjs} +845 -90
  15. package/dist/chunk-AA4ZEVCC.cjs.map +1 -0
  16. package/dist/{chunk-LA7PEGU3.cjs → chunk-CL37JGFW.cjs} +4 -4
  17. package/dist/{chunk-LA7PEGU3.cjs.map → chunk-CL37JGFW.cjs.map} +1 -1
  18. package/dist/{chunk-A7DKIA72.js → chunk-DENSCKFC.js} +642 -69
  19. package/dist/chunk-DENSCKFC.js.map +1 -0
  20. package/dist/{chunk-4QPLEUL2.cjs → chunk-GVSMRPHB.cjs} +1046 -54
  21. package/dist/chunk-GVSMRPHB.cjs.map +1 -0
  22. package/dist/{chunk-KYRZDWM4.js → chunk-JJDLPEWL.js} +2 -2
  23. package/dist/{chunk-3TYMNULT.js → chunk-MJZBNWG7.js} +807 -52
  24. package/dist/chunk-MJZBNWG7.js.map +1 -0
  25. package/dist/{chunk-CYFJCKC4.js → chunk-QGHGJ56O.js} +997 -5
  26. package/dist/chunk-QGHGJ56O.js.map +1 -0
  27. package/dist/{chunk-GVW4P7BG.js → chunk-RU52I7QO.js} +305 -16
  28. package/dist/chunk-RU52I7QO.js.map +1 -0
  29. package/dist/{chunk-JYRIOFEN.cjs → chunk-SAZRENEZ.cjs} +647 -74
  30. package/dist/chunk-SAZRENEZ.cjs.map +1 -0
  31. package/dist/{controller-OUdxPY-X.d.ts → controller-B7GIybc4.d.cts} +139 -7
  32. package/dist/{controller-OUdxPY-X.d.cts → controller-B7GIybc4.d.ts} +139 -7
  33. package/dist/extensions/index.cjs +14 -4
  34. package/dist/extensions/index.cjs.map +1 -1
  35. package/dist/extensions/index.d.cts +3 -3
  36. package/dist/extensions/index.d.ts +3 -3
  37. package/dist/extensions/index.js +13 -3
  38. package/dist/{index-k3mFbPx_.d.cts → index-CCr_z9kF.d.cts} +3 -3
  39. package/dist/{index-Ng1pG7JE.d.ts → index-Ck6ajwXh.d.ts} +3 -3
  40. package/dist/index.cjs +250 -16
  41. package/dist/index.cjs.map +1 -1
  42. package/dist/index.d.cts +294 -8
  43. package/dist/index.d.ts +294 -8
  44. package/dist/index.js +241 -7
  45. package/dist/index.js.map +1 -1
  46. package/dist/{registry-iAHbqkYt.d.cts → registry-GJI4gToU.d.cts} +15 -4
  47. package/dist/{registry-iAHbqkYt.d.ts → registry-GJI4gToU.d.ts} +15 -4
  48. package/dist/serializers/index.cjs +3 -3
  49. package/dist/serializers/index.d.cts +1 -1
  50. package/dist/serializers/index.d.ts +1 -1
  51. package/dist/serializers/index.js +2 -2
  52. package/dist/spreadsheet/index.cjs +7 -3
  53. package/dist/spreadsheet/index.cjs.map +1 -1
  54. package/dist/spreadsheet/index.d.cts +33 -4
  55. package/dist/spreadsheet/index.d.ts +33 -4
  56. package/dist/spreadsheet/index.js +6 -2
  57. package/dist/{track-changes-G8JcWB8b.d.cts → track-changes-BSF58RK0.d.cts} +92 -9
  58. package/dist/{track-changes-G8JcWB8b.d.ts → track-changes-BSF58RK0.d.ts} +92 -9
  59. package/dist/{types-DMe0vFB1.d.cts → types-CJ4U-HHI.d.cts} +1 -1
  60. package/dist/{types-DMe0vFB1.d.ts → types-CJ4U-HHI.d.ts} +1 -1
  61. package/package.json +1 -1
  62. package/dist/chunk-3TYMNULT.js.map +0 -1
  63. package/dist/chunk-4QPLEUL2.cjs.map +0 -1
  64. package/dist/chunk-A7DKIA72.js.map +0 -1
  65. package/dist/chunk-CYFJCKC4.js.map +0 -1
  66. package/dist/chunk-GVW4P7BG.js.map +0 -1
  67. package/dist/chunk-JYRIOFEN.cjs.map +0 -1
  68. package/dist/chunk-TVRC4KVT.cjs.map +0 -1
  69. package/dist/chunk-U6NDQFOK.cjs.map +0 -1
  70. /package/dist/{chunk-KYRZDWM4.js.map → chunk-JJDLPEWL.js.map} +0 -0
package/dist/index.js CHANGED
@@ -29,6 +29,10 @@ import {
29
29
  Comments,
30
30
  CrossReference,
31
31
  DEFAULT_AUTOCORRECT_EXCEPTIONS,
32
+ DEFAULT_DOCX_MAX_IMAGE_SIZE,
33
+ DEFAULT_ZIP_MAX_ENTRIES,
34
+ DEFAULT_ZIP_MAX_TOTAL_SIZE,
35
+ DOCX_MIME_TYPES,
32
36
  FileImport,
33
37
  FontSize,
34
38
  Footnote,
@@ -48,11 +52,14 @@ import {
48
52
  SmilieReplacer,
49
53
  TableOfContents,
50
54
  TemplateSuggestions,
55
+ ZipArchive,
56
+ ZipError,
51
57
  autocorrectPluginKey,
52
58
  buildOutlineTree,
53
59
  capitalizeSentenceRules,
54
60
  commentsPluginKey,
55
61
  computeOutline,
62
+ createDocxImportAdapter,
56
63
  createImportAdapterRegistry,
57
64
  createImportContext,
58
65
  createMentionExtension,
@@ -62,6 +69,8 @@ import {
62
69
  defaultAutocorrectRules,
63
70
  defaultImportRegistry,
64
71
  documentStructureCommandItems,
72
+ docxImportAdapter,
73
+ docxToDocumentImport,
65
74
  fileImportPluginKey,
66
75
  findAutocorrection,
67
76
  findCommentRanges,
@@ -91,10 +100,12 @@ import {
91
100
  insertImageNode,
92
101
  insertImportedDocument,
93
102
  isInsidePromptToken,
103
+ isUnsafeZipPath,
94
104
  markdownImportAdapter,
95
105
  navigateToHeading,
96
106
  outlinePluginKey,
97
107
  plainTextImportAdapter,
108
+ readZip,
98
109
  registerImportAdapter,
99
110
  removeImageNode,
100
111
  richClipboardPluginKey,
@@ -108,8 +119,9 @@ import {
108
119
  twoInitialCapitalsRules,
109
120
  unregisterImportAdapter,
110
121
  updateImageSrc,
111
- writeRichClipboard
112
- } from "./chunk-CYFJCKC4.js";
122
+ writeRichClipboard,
123
+ zipCrc32
124
+ } from "./chunk-QGHGJ56O.js";
113
125
  import {
114
126
  INSTRUCTION_REGEX,
115
127
  TEMPLATE_REGEX,
@@ -136,7 +148,7 @@ import {
136
148
  rtfToPlainText,
137
149
  sanitizeImportedHtml,
138
150
  serializeRichClipboardPayload
139
- } from "./chunk-KYRZDWM4.js";
151
+ } from "./chunk-JJDLPEWL.js";
140
152
  import {
141
153
  DEFAULT_COLUMN_WIDTH,
142
154
  DEFAULT_ROW_HEIGHT,
@@ -150,6 +162,7 @@ import {
150
162
  applyFormulaSuggestion,
151
163
  bindSpreadsheetTableActions,
152
164
  buildSpreadsheetView,
165
+ clampMenuPosition,
153
166
  columnDependencyKey,
154
167
  compareCellValues,
155
168
  computeFillTarget,
@@ -190,14 +203,16 @@ import {
190
203
  shiftFormulaReferences,
191
204
  toNumber,
192
205
  tsvImportAdapter
193
- } from "./chunk-GVW4P7BG.js";
206
+ } from "./chunk-RU52I7QO.js";
194
207
  import {
195
208
  HistoryManager,
196
209
  createHistoryManager
197
210
  } from "./chunk-KAADNAPF.js";
198
211
  import {
212
+ BLOCK_CHANGE_ATTRIBUTE,
199
213
  CommentStore,
200
214
  DELETION_MARK_NAME,
215
+ FORMAT_CHANGE_MARK_NAME,
201
216
  INSERTION_MARK_NAME,
202
217
  MARKDOWN_MENTION_SPLIT_REGEX,
203
218
  MENTION_REGEX,
@@ -205,9 +220,12 @@ import {
205
220
  TrackChanges,
206
221
  TrackChangesStore,
207
222
  TrackedDeletion,
223
+ TrackedFormatChange,
208
224
  TrackedInsertion,
209
225
  createCommentStore,
210
226
  createReviewId,
227
+ describeBlockChange,
228
+ describeFormatChange,
211
229
  escapeHtml,
212
230
  extractMentions,
213
231
  extractMentionsFromDoc,
@@ -229,11 +247,12 @@ import {
229
247
  sanitizeHtml,
230
248
  stripMarkdownCode,
231
249
  trackChangesPluginKey
232
- } from "./chunk-3TYMNULT.js";
250
+ } from "./chunk-MJZBNWG7.js";
233
251
  import {
234
252
  FORMULA_ERRORS,
235
253
  FormulaEvaluator,
236
254
  FormulaParser,
255
+ MAX_FORMULA_ARRAY_SIZE,
237
256
  WHOLE_COLUMN_END_ROW,
238
257
  checkFormulaArgumentCount,
239
258
  colNameToIndex,
@@ -255,7 +274,7 @@ import {
255
274
  tokenizeFormula,
256
275
  unregisterFormulaFunction,
257
276
  wildcardToRegExp
258
- } from "./chunk-A7DKIA72.js";
277
+ } from "./chunk-DENSCKFC.js";
259
278
 
260
279
  // src/controller.ts
261
280
  import { Editor } from "@tiptap/core";
@@ -560,8 +579,199 @@ var FileImportController = class {
560
579
  function createFileImportController(getTarget, options) {
561
580
  return new FileImportController(getTarget, options);
562
581
  }
582
+
583
+ // src/import/pdf.ts
584
+ var isTextItem = (item) => typeof item.str === "string";
585
+ function median(values) {
586
+ if (!values.length) return 0;
587
+ const sorted = [...values].sort((a, b) => a - b);
588
+ const mid = sorted.length >> 1;
589
+ return sorted.length % 2 ? sorted[mid] : (sorted[mid - 1] + sorted[mid]) / 2;
590
+ }
591
+ function groupLines(items) {
592
+ const lines = [];
593
+ let current = null;
594
+ let breakNext = false;
595
+ for (const item of items) {
596
+ if (!isTextItem(item)) continue;
597
+ const x = item.transform?.[4] ?? 0;
598
+ const y = item.transform?.[5] ?? (current?.y ?? 0);
599
+ const height = item.height || Math.abs(item.transform?.[3] ?? 0) || current?.height || 10;
600
+ const tolerance = Math.max(1, height * 0.5);
601
+ if (!item.str) {
602
+ if (item.hasEOL) {
603
+ if (breakNext && current) current.blankAfter = true;
604
+ breakNext = true;
605
+ }
606
+ continue;
607
+ }
608
+ if (!current || breakNext || Math.abs(y - current.y) > tolerance) {
609
+ current = { text: "", y, height, blankAfter: false };
610
+ lines.push(current);
611
+ } else if (current.endX !== void 0 && x - current.endX > height * 0.15) {
612
+ if (!/\s$/.test(current.text) && !/^\s/.test(item.str)) current.text += " ";
613
+ }
614
+ current.text += item.str;
615
+ current.height = Math.max(current.height, height);
616
+ current.endX = item.width !== void 0 ? x + item.width : void 0;
617
+ breakNext = !!item.hasEOL;
618
+ }
619
+ return lines;
620
+ }
621
+ function pdfTextItemsToParagraphs(items, options = {}) {
622
+ const lines = groupLines(items).filter((line) => line.text.trim() || line.blankAfter);
623
+ const gaps = [];
624
+ for (let i = 1; i < lines.length; i++) {
625
+ const gap = lines[i - 1].y - lines[i].y;
626
+ if (gap > 0) gaps.push(gap);
627
+ }
628
+ const typical = median(gaps);
629
+ const factor = options.paragraphGap ?? 1.4;
630
+ const paragraphs = [];
631
+ let current = [];
632
+ const flush = () => {
633
+ const text = current.join(" ").replace(/\s+/g, " ").trim();
634
+ if (text) paragraphs.push(text);
635
+ current = [];
636
+ };
637
+ lines.forEach((line, i) => {
638
+ if (i > 0) {
639
+ const prev = lines[i - 1];
640
+ const gap = prev.y - line.y;
641
+ const bigGap = typical > 0 ? gap > typical * factor : gap > prev.height * 1.8;
642
+ if (prev.blankAfter || bigGap || gap < -prev.height * 0.5) flush();
643
+ }
644
+ const text = line.text.trim();
645
+ if (!text) return;
646
+ if (current.length && /\w-$/.test(current[current.length - 1]) && /^[a-z]/.test(text)) {
647
+ current[current.length - 1] = current[current.length - 1].slice(0, -1) + text;
648
+ } else {
649
+ current.push(text);
650
+ }
651
+ });
652
+ flush();
653
+ return paragraphs;
654
+ }
655
+ function createPdfImportAdapter(pdfjs, options = {}) {
656
+ return {
657
+ id: options.id ?? "pdf",
658
+ label: "PDF",
659
+ mimeTypes: ["application/pdf"],
660
+ extensions: [".pdf"],
661
+ target: "document",
662
+ async import(_input, context) {
663
+ const data = new Uint8Array((await context.readArrayBuffer()).slice(0));
664
+ const doc = await pdfjs.getDocument({ ...options.documentParams, data }).promise;
665
+ const paragraphs = [];
666
+ try {
667
+ const pages = Math.min(doc.numPages, options.maxPages ?? Infinity);
668
+ for (let n = 1; n <= pages; n++) {
669
+ const page = await doc.getPage(n);
670
+ const content = await page.getTextContent();
671
+ paragraphs.push(...pdfTextItemsToParagraphs(content.items, options));
672
+ page.cleanup?.();
673
+ }
674
+ } finally {
675
+ await doc.destroy?.();
676
+ }
677
+ const text = paragraphs.join("\n");
678
+ return { html: plainTextToParagraphHtml(text), text };
679
+ }
680
+ };
681
+ }
682
+
683
+ // src/import/zip-export.ts
684
+ var INDEX_NAMES = /^(index|readme|home|_index|start)$/i;
685
+ var baseName = (path) => path.slice(path.lastIndexOf("/") + 1);
686
+ var stem = (name) => name.replace(/\.[^./]+$/, "");
687
+ function zipEntryTitle(path) {
688
+ const title = stem(baseName(path)).replace(/\s+[0-9a-f]{32}$/i, "").replace(/[_]+/g, " ").trim();
689
+ return title || baseName(path);
690
+ }
691
+ function sortZipEntryNames(names) {
692
+ const isIndex = (name) => INDEX_NAMES.test(stem(baseName(name)));
693
+ const depth = (name) => name.split("/").length;
694
+ return [...names].sort((a, b) => {
695
+ const ai = isIndex(a);
696
+ const bi = isIndex(b);
697
+ if (ai !== bi) return ai ? -1 : 1;
698
+ if (ai && bi && depth(a) !== depth(b)) return depth(a) - depth(b);
699
+ return a.localeCompare(b, void 0, { numeric: true, sensitivity: "base" });
700
+ });
701
+ }
702
+ var isJunk = (name) => name.startsWith("__MACOSX/") || name.split("/").some((segment) => segment.startsWith("."));
703
+ function createZipImportAdapter(options = {}) {
704
+ const id = options.id ?? "zip";
705
+ return {
706
+ id,
707
+ label: "Zipped documents",
708
+ mimeTypes: ["application/zip", "application/x-zip-compressed"],
709
+ extensions: [".zip"],
710
+ target: "document",
711
+ async import(_input, context) {
712
+ const registry = options.registry ?? context.registry ?? defaultImportRegistry;
713
+ const zip = await readZip(await context.readArrayBuffer(), {
714
+ maxTotalSize: 100 * 1024 * 1024,
715
+ maxEntries: 5e3,
716
+ ...options.zip
717
+ });
718
+ const maxFiles = options.maxFiles ?? 500;
719
+ const level = options.headingLevel ?? 2;
720
+ const files = [];
721
+ const skipped = [];
722
+ let html = "";
723
+ let text = "";
724
+ const names = zip.entries.filter((entry) => !entry.isDirectory).map((entry) => entry.name);
725
+ for (const name of sortZipEntryNames(names)) {
726
+ if (isJunk(name)) continue;
727
+ const adapter = registry.find({ fileName: name, target: "document" });
728
+ if (!adapter) {
729
+ skipped.push({ name, reason: "unsupported type" });
730
+ continue;
731
+ }
732
+ if (adapter.id === id || adapter.extensions.some((ext) => ext.toLowerCase() === ".zip")) {
733
+ skipped.push({ name, reason: "nested archive" });
734
+ continue;
735
+ }
736
+ if (files.length >= maxFiles) {
737
+ skipped.push({ name, reason: "file limit reached" });
738
+ continue;
739
+ }
740
+ try {
741
+ const outcome = await registry.importFile(await zip.read(name), {
742
+ fileName: name,
743
+ adapterId: adapter.id
744
+ });
745
+ const title = zipEntryTitle(name);
746
+ if (options.headings !== false) {
747
+ html += `<h${level}>${escapeHtml(title)}</h${level}>`;
748
+ text += `${text ? "\n" : ""}${title}
749
+ `;
750
+ } else if (text) {
751
+ text += "\n";
752
+ }
753
+ html += outcome.result.html.trim();
754
+ text += outcome.result.text;
755
+ files.push({ name, adapterId: adapter.id });
756
+ } catch (error) {
757
+ const code = error?.code;
758
+ if (code === "too_large") throw error;
759
+ skipped.push({ name, reason: error instanceof Error ? error.message : String(error) });
760
+ }
761
+ }
762
+ if (!files.length) {
763
+ throw new ImportError("invalid_file", "The archive contains no importable documents.", {
764
+ fileName: context.fileName
765
+ });
766
+ }
767
+ return { html, text, files, skipped };
768
+ }
769
+ };
770
+ }
771
+ var zipImportAdapter = createZipImportAdapter();
563
772
  export {
564
773
  Autocorrect,
774
+ BLOCK_CHANGE_ATTRIBUTE,
565
775
  BUILT_IN_IMPORT_ADAPTERS,
566
776
  Bookmark,
567
777
  COMMENT_MARK_NAME,
@@ -571,12 +781,17 @@ export {
571
781
  CrossReference,
572
782
  DEFAULT_AUTOCORRECT_EXCEPTIONS,
573
783
  DEFAULT_COLUMN_WIDTH,
784
+ DEFAULT_DOCX_MAX_IMAGE_SIZE,
574
785
  DEFAULT_ROW_HEIGHT,
786
+ DEFAULT_ZIP_MAX_ENTRIES,
787
+ DEFAULT_ZIP_MAX_TOTAL_SIZE,
575
788
  DELETION_MARK_NAME,
789
+ DOCX_MIME_TYPES,
576
790
  DataSourceBinder,
577
791
  DependencyGraph,
578
792
  DirtyTracker,
579
793
  EditorController,
794
+ FORMAT_CHANGE_MARK_NAME,
580
795
  FORMULA_ERRORS,
581
796
  FileImport,
582
797
  FileImportController,
@@ -595,6 +810,7 @@ export {
595
810
  ImportError,
596
811
  LoadingNode,
597
812
  MARKDOWN_MENTION_SPLIT_REGEX,
813
+ MAX_FORMULA_ARRAY_SIZE,
598
814
  MENTION_REGEX,
599
815
  Markdown,
600
816
  Mention,
@@ -617,9 +833,12 @@ export {
617
833
  TrackChanges,
618
834
  TrackChangesStore,
619
835
  TrackedDeletion,
836
+ TrackedFormatChange,
620
837
  TrackedInsertion,
621
838
  VARIABLE_REGEX,
622
839
  WHOLE_COLUMN_END_ROW,
840
+ ZipArchive,
841
+ ZipError,
623
842
  aggregateValues,
624
843
  applyFormulaSuggestion,
625
844
  autocorrectPluginKey,
@@ -628,6 +847,7 @@ export {
628
847
  buildSpreadsheetView,
629
848
  capitalizeSentenceRules,
630
849
  checkFormulaArgumentCount,
850
+ clampMenuPosition,
631
851
  colNameToIndex,
632
852
  columnDependencyKey,
633
853
  commentsPluginKey,
@@ -638,6 +858,7 @@ export {
638
858
  computeRowGroups,
639
859
  createCommentStore,
640
860
  createDirtyTracker,
861
+ createDocxImportAdapter,
641
862
  createEditor,
642
863
  createFileImportController,
643
864
  createHistoryManager,
@@ -645,16 +866,22 @@ export {
645
866
  createImportContext,
646
867
  createMentionExtension,
647
868
  createOutlineStore,
869
+ createPdfImportAdapter,
648
870
  createReviewId,
649
871
  createRichTextPreset,
650
872
  createSpreadsheetClipboardData,
873
+ createZipImportAdapter,
651
874
  crossReferencePluginKey,
652
875
  csvImportAdapter,
653
876
  defaultAutocorrectRules,
654
877
  defaultImportRegistry,
655
878
  defaultIsEqual,
879
+ describeBlockChange,
880
+ describeFormatChange,
656
881
  describeFormulaError,
657
882
  documentStructureCommandItems,
883
+ docxImportAdapter,
884
+ docxToDocumentImport,
658
885
  escapeCsvField,
659
886
  escapeHtml,
660
887
  escapeVariableText,
@@ -730,6 +957,7 @@ export {
730
957
  isPromptValueSet,
731
958
  isSingleCellRange,
732
959
  isTrackChangesEnabled,
960
+ isUnsafeZipPath,
733
961
  listFormulaFunctions,
734
962
  mapFormulaReferences,
735
963
  mapRawOffsetToResolved,
@@ -752,6 +980,7 @@ export {
752
980
  parseRichClipboardPayload,
753
981
  parseTsv,
754
982
  parseVariableBody,
983
+ pdfTextItemsToParagraphs,
755
984
  pickImportFiles,
756
985
  plainTextImportAdapter,
757
986
  plainTextToParagraphHtml,
@@ -764,6 +993,7 @@ export {
764
993
  rangesIntersect,
765
994
  readRichClipboard,
766
995
  readSpreadsheetClipboard,
996
+ readZip,
767
997
  rebaseClipboardRows,
768
998
  registerFormulaFunction,
769
999
  registerImportAdapter,
@@ -790,6 +1020,7 @@ export {
790
1020
  shiftFormulaReferences,
791
1021
  smartQuoteRules,
792
1022
  smilieAutocorrectRules,
1023
+ sortZipEntryNames,
793
1024
  stripMarkdownCode,
794
1025
  symbolRules,
795
1026
  syncFootnotes,
@@ -809,6 +1040,9 @@ export {
809
1040
  updateImageSrc,
810
1041
  variableRegex,
811
1042
  wildcardToRegExp,
812
- writeRichClipboard
1043
+ writeRichClipboard,
1044
+ zipCrc32,
1045
+ zipEntryTitle,
1046
+ zipImportAdapter
813
1047
  };
814
1048
  //# sourceMappingURL=index.js.map
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/controller.ts","../src/dirty.ts","../src/import/controller.ts"],"sourcesContent":["import { Editor, EditorOptions, Content, Extensions } from \"@tiptap/core\";\nimport { Slice } from \"@tiptap/pm/model\";\nimport { Token, tokenizePrompt } from \"./grammar\";\nimport {\n plainTextToTipTapHtml,\n plainTextToSlice,\n getEditorText,\n markdownToTipTapHtml,\n getEditorMarkdown,\n} from \"./serializers\";\n\nexport interface EditorControllerOptions extends Partial<\n Omit<EditorOptions, \"element\" | \"content\">\n> {\n element?: Element | HTMLElement | null;\n content?: string | Content;\n plainTextMode?: boolean;\n markdownMode?: boolean;\n extensions?: Extensions;\n onPlainTextChange?: (text: string) => void;\n onMarkdownChange?: (markdown: string) => void;\n onTokensChange?: (tokens: Token[]) => void;\n onSelectionChange?: (range: { from: number; to: number } | null) => void;\n}\n\n/**\n * UI-agnostic controller managing the lifecycle, commands, and plain-text/token\n * synchronization of a rich text or prompt editor.\n */\nexport class EditorController {\n public readonly editor: Editor;\n private readonly plainTextMode: boolean;\n private readonly markdownMode: boolean;\n private readonly onPlainTextChange?: (text: string) => void;\n private readonly onMarkdownChange?: (markdown: string) => void;\n private readonly onTokensChange?: (tokens: Token[]) => void;\n private readonly onSelectionChange?: (\n range: { from: number; to: number } | null,\n ) => void;\n private baselineContent: string;\n\n constructor(options: EditorControllerOptions = {}) {\n const {\n element,\n content,\n plainTextMode = false,\n markdownMode = false,\n extensions = [],\n onPlainTextChange,\n onMarkdownChange,\n onTokensChange,\n onSelectionChange,\n ...editorOptions\n } = options;\n\n this.plainTextMode = plainTextMode;\n this.markdownMode = markdownMode;\n this.onPlainTextChange = onPlainTextChange;\n this.onMarkdownChange = onMarkdownChange;\n this.onTokensChange = onTokensChange;\n this.onSelectionChange = onSelectionChange;\n\n const initialContent =\n plainTextMode && typeof content === \"string\"\n ? plainTextToTipTapHtml(content)\n : markdownMode && typeof content === \"string\"\n ? markdownToTipTapHtml(content)\n : content;\n\n this.editor = new Editor({\n element: element || undefined,\n content: initialContent,\n extensions,\n ...editorOptions,\n onUpdate: (props) => {\n editorOptions.onUpdate?.(props);\n this.handleUpdate();\n },\n onSelectionUpdate: (props) => {\n editorOptions.onSelectionUpdate?.(props);\n this.handleSelectionUpdate();\n },\n });\n\n if (plainTextMode) {\n this.enforcePlainTextClipboard();\n }\n\n this.baselineContent = this.getCurrentContentString();\n }\n\n private enforcePlainTextClipboard() {\n // Opt-in rich clipboard / file import must not bypass the plain-text invariant.\n const richClipboard = this.editor.storage?.richClipboard;\n if (richClipboard) richClipboard.plainTextMode = true;\n const fileImport = this.editor.extensionManager?.extensions.find(\n (ext) => ext.name === \"fileImport\",\n );\n if (fileImport) fileImport.options.asPlainText = true;\n\n this.editor.setOptions({\n editorProps: {\n ...this.editor.options.editorProps,\n clipboardTextParser: (\n text: string,\n context: any,\n _plain: boolean,\n view?: any,\n ) => {\n const schema =\n context?.doc?.type?.schema ||\n context?.schema ||\n view?.state?.schema ||\n this.editor.schema;\n\n if (!schema) return Slice.empty;\n\n return plainTextToSlice(text, schema);\n },\n },\n });\n }\n\n private handleUpdate() {\n if (this.plainTextMode || this.onPlainTextChange || this.onTokensChange) {\n const text = this.getPlainText();\n this.onPlainTextChange?.(text);\n\n if (this.onTokensChange) {\n const tokens = tokenizePrompt(text);\n this.onTokensChange(tokens);\n }\n }\n\n if (this.markdownMode || this.onMarkdownChange) {\n const markdown = this.getMarkdown();\n this.onMarkdownChange?.(markdown);\n }\n }\n\n private handleSelectionUpdate() {\n if (this.onSelectionChange) {\n const { from, to } = this.editor.state.selection;\n this.onSelectionChange({ from, to });\n }\n }\n\n public getPlainText(): string {\n return getEditorText(this.editor);\n }\n\n public setPlainText(text: string, emitUpdate = true) {\n const html = plainTextToTipTapHtml(text);\n this.editor.commands.setContent(html, emitUpdate);\n }\n\n public getMarkdown(): string {\n return getEditorMarkdown(this.editor);\n }\n\n public setMarkdown(markdown: string, emitUpdate = true) {\n if (this.editor.storage?.markdown?.parser) {\n this.editor.commands.setContent(markdown, emitUpdate);\n } else {\n const html = markdownToTipTapHtml(markdown);\n this.editor.commands.setContent(html, emitUpdate);\n }\n }\n\n /**\n * Replaces `from`..`to` with literal text: HTML in `text` is not parsed, and\n * newlines split paragraphs.\n */\n private insertPlainText(from: number, to: number, text: string) {\n const { state, view } = this.editor;\n const tr = text.includes(\"\\n\") || text.includes(\"\\r\")\n ? state.tr.replace(from, to, plainTextToSlice(text, state.schema))\n : text\n ? state.tr.insertText(text, from, to)\n : state.tr.delete(from, to);\n view.dispatch(tr.scrollIntoView());\n }\n\n /** Inserts text literally at the selection; HTML in `text` is not parsed. */\n public insertText(text: string) {\n const { from, to } = this.editor.state.selection;\n this.insertPlainText(from, to, text);\n }\n\n /** Inserts text literally at `pos`; HTML in `text` is not parsed. */\n public insertTextAt(pos: number, text: string) {\n this.insertPlainText(pos, pos, text);\n this.editor.commands.focus();\n }\n\n /** Replaces a range with literal text; HTML in `text` is not parsed. */\n public replaceRange(from: number, to: number, text: string) {\n this.insertPlainText(from, to, text);\n this.editor.commands.focus();\n }\n\n public getCurrentContentString(): string {\n if (this.plainTextMode) return this.getPlainText();\n if (this.markdownMode) return this.getMarkdown();\n return this.editor.getHTML();\n }\n\n public isDirty(): boolean {\n return this.getCurrentContentString() !== this.baselineContent;\n }\n\n public setCheckpoint(content?: string): void {\n this.baselineContent =\n content !== undefined ? content : this.getCurrentContentString();\n }\n\n public resetDirty(): void {\n this.setCheckpoint();\n }\n\n public getBaselineContent(): string {\n return this.baselineContent;\n }\n\n public destroy() {\n this.editor.destroy();\n }\n}\n\n/**\n * Creates and initializes an EditorController instance.\n */\nexport function createEditor(\n options: EditorControllerOptions = {},\n): EditorController {\n return new EditorController(options);\n}\n","/**\n * Compares two values for structural equality.\n * Handles primitives, objects, arrays, and null/undefined.\n */\nexport function defaultIsEqual<T>(a: T, b: T): boolean {\n if (Object.is(a, b)) return true;\n if (\n typeof a !== \"object\" ||\n a === null ||\n typeof b !== \"object\" ||\n b === null\n ) {\n return false;\n }\n\n // Fast path for arrays\n if (Array.isArray(a) && Array.isArray(b)) {\n if (a.length !== b.length) return false;\n for (let i = 0; i < a.length; i++) {\n if (!defaultIsEqual(a[i], b[i])) return false;\n }\n return true;\n }\n if (Array.isArray(a) !== Array.isArray(b)) {\n return false;\n }\n\n const keysA = Object.keys(a) as (keyof T)[];\n const keysB = Object.keys(b) as (keyof T)[];\n\n if (keysA.length !== keysB.length) return false;\n\n for (const key of keysA) {\n if (!Object.prototype.hasOwnProperty.call(b, key)) return false;\n if (!defaultIsEqual(a[key], b[key])) return false;\n }\n\n return true;\n}\n\nexport interface DirtyTrackerOptions<T> {\n isEqual?: (a: T, b: T) => boolean;\n}\n\n/**\n * Tracks a baseline value and determines if the current state is dirty (modified).\n */\nexport class DirtyTracker<T = unknown> {\n private baseline: T;\n private readonly isEqual: (a: T, b: T) => boolean;\n\n constructor(\n initialBaseline: T,\n options?: DirtyTrackerOptions<T> | ((a: T, b: T) => boolean),\n ) {\n this.baseline = initialBaseline;\n if (typeof options === \"function\") {\n this.isEqual = options;\n } else {\n this.isEqual = options?.isEqual ?? defaultIsEqual;\n }\n }\n\n /**\n * Returns current baseline value.\n */\n public getBaseline(): T {\n return this.baseline;\n }\n\n /**\n * Sets a new baseline value without triggering any dirty state.\n */\n public setBaseline(value: T): void {\n this.baseline = value;\n }\n\n /**\n * Checks whether the given value differs from the baseline.\n */\n public isDirty(current: T): boolean {\n return !this.isEqual(this.baseline, current);\n }\n\n /**\n * Resets the baseline to the provided current value (or clears dirty status).\n */\n public reset(current: T): void {\n this.baseline = current;\n }\n}\n\n/**\n * Creates a DirtyTracker instance.\n */\nexport function createDirtyTracker<T>(\n initialBaseline: T,\n options?: DirtyTrackerOptions<T> | ((a: T, b: T) => boolean),\n): DirtyTracker<T> {\n return new DirtyTracker(initialBaseline, options);\n}\n","import { defaultImportRegistry, type DocumentImport } from \"./adapters\";\nimport {\n importFilesIntoEditor,\n type ImportEditorTarget,\n type ImportFilesIntoEditorOptions,\n} from \"./editor\";\nimport type { FileLike, ImportOutcome } from \"./registry\";\n\n/**\n * Opens the browser file picker and resolves with the chosen files (an empty\n * array when the dialog is cancelled). `accept` defaults to every extension\n * and MIME type the default registry can import as a document.\n */\nexport function pickImportFiles(options: { accept?: string; multiple?: boolean } = {}): Promise<File[]> {\n if (typeof document === \"undefined\") return Promise.resolve([]);\n return new Promise((resolve) => {\n const input = document.createElement(\"input\");\n input.type = \"file\";\n input.accept = options.accept ?? defaultImportRegistry.accept(\"document\");\n input.multiple = options.multiple ?? true;\n input.style.display = \"none\";\n const finish = (files: File[]) => {\n input.remove();\n resolve(files);\n };\n input.addEventListener(\"change\", () => finish(Array.from(input.files ?? [])), { once: true });\n input.addEventListener(\"cancel\", () => finish([]), { once: true });\n document.body.appendChild(input);\n input.click();\n });\n}\n\nexport interface FileImportState {\n isImporting: boolean;\n /** Last import error (cleared when a new import starts). */\n error: Error | null;\n /** Outcomes of the most recent import. */\n lastOutcomes: ImportOutcome<DocumentImport>[];\n}\n\nexport interface FileImportControllerOptions extends Omit<ImportFilesIntoEditorOptions, \"at\"> {\n /** Allow choosing several files in the picker (default `true`). */\n multiple?: boolean;\n}\n\n/**\n * Framework-agnostic state for a file-import button: tracks `isImporting` /\n * `error`, opens the file picker and inserts the imported content into the\n * editor returned by `getTarget`. React's `useFileImport` and Svelte's\n * `createFileImport` wrap it.\n */\nexport class FileImportController {\n private state: FileImportState = { isImporting: false, error: null, lastOutcomes: [] };\n private readonly listeners = new Set<(state: FileImportState) => void>();\n private pending = 0;\n\n constructor(\n private readonly getTarget: () => ImportEditorTarget | null | undefined,\n private options: FileImportControllerOptions = {},\n ) {}\n\n setOptions(options: FileImportControllerOptions): void {\n this.options = options;\n }\n\n /** `accept` attribute value for document imports. */\n get accept(): string {\n return (this.options.registry ?? defaultImportRegistry).accept(\"document\");\n }\n\n getState = (): FileImportState => this.state;\n\n subscribe = (listener: (state: FileImportState) => void): (() => void) => {\n this.listeners.add(listener);\n return () => {\n this.listeners.delete(listener);\n };\n };\n\n private setState(patch: Partial<FileImportState>) {\n this.state = { ...this.state, ...patch };\n this.listeners.forEach((listener) => listener(this.state));\n }\n\n /** Imports files into the editor at `at` (default: the selection). */\n importFiles = async (\n files: Iterable<FileLike> | ArrayLike<FileLike>,\n at?: number,\n ): Promise<ImportOutcome<DocumentImport>[]> => {\n const target = this.getTarget();\n const list = Array.from(files as ArrayLike<FileLike>);\n if (!target || list.length === 0) return [];\n this.pending++;\n this.setState({ isImporting: true, error: null });\n let error: Error | null = null;\n try {\n const outcomes = await importFilesIntoEditor(target, list, {\n ...this.options,\n at,\n onError: (err, file) => {\n error = err;\n this.options.onError?.(err, file);\n },\n });\n this.pending--;\n this.setState({ isImporting: this.pending > 0, error, lastOutcomes: outcomes });\n return outcomes;\n } catch (err) {\n this.pending--;\n this.setState({ isImporting: this.pending > 0, error: err instanceof Error ? err : new Error(String(err)) });\n return [];\n }\n };\n\n /** Opens the file picker and imports the chosen files at the selection. */\n openFilePicker = async (): Promise<ImportOutcome<DocumentImport>[]> => {\n const files = await pickImportFiles({ accept: this.accept, multiple: this.options.multiple });\n return this.importFiles(files);\n };\n}\n\nexport function createFileImportController(\n getTarget: () => ImportEditorTarget | null | undefined,\n options?: FileImportControllerOptions,\n): FileImportController {\n return new FileImportController(getTarget, options);\n}\n"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../src/controller.ts","../src/dirty.ts","../src/import/controller.ts","../src/import/pdf.ts","../src/import/zip-export.ts"],"sourcesContent":["import { Editor, EditorOptions, Content, Extensions } from \"@tiptap/core\";\nimport { Slice } from \"@tiptap/pm/model\";\nimport { Token, tokenizePrompt } from \"./grammar\";\nimport {\n plainTextToTipTapHtml,\n plainTextToSlice,\n getEditorText,\n markdownToTipTapHtml,\n getEditorMarkdown,\n} from \"./serializers\";\n\nexport interface EditorControllerOptions extends Partial<\n Omit<EditorOptions, \"element\" | \"content\">\n> {\n element?: Element | HTMLElement | null;\n content?: string | Content;\n plainTextMode?: boolean;\n markdownMode?: boolean;\n extensions?: Extensions;\n onPlainTextChange?: (text: string) => void;\n onMarkdownChange?: (markdown: string) => void;\n onTokensChange?: (tokens: Token[]) => void;\n onSelectionChange?: (range: { from: number; to: number } | null) => void;\n}\n\n/**\n * UI-agnostic controller managing the lifecycle, commands, and plain-text/token\n * synchronization of a rich text or prompt editor.\n */\nexport class EditorController {\n public readonly editor: Editor;\n private readonly plainTextMode: boolean;\n private readonly markdownMode: boolean;\n private readonly onPlainTextChange?: (text: string) => void;\n private readonly onMarkdownChange?: (markdown: string) => void;\n private readonly onTokensChange?: (tokens: Token[]) => void;\n private readonly onSelectionChange?: (\n range: { from: number; to: number } | null,\n ) => void;\n private baselineContent: string;\n\n constructor(options: EditorControllerOptions = {}) {\n const {\n element,\n content,\n plainTextMode = false,\n markdownMode = false,\n extensions = [],\n onPlainTextChange,\n onMarkdownChange,\n onTokensChange,\n onSelectionChange,\n ...editorOptions\n } = options;\n\n this.plainTextMode = plainTextMode;\n this.markdownMode = markdownMode;\n this.onPlainTextChange = onPlainTextChange;\n this.onMarkdownChange = onMarkdownChange;\n this.onTokensChange = onTokensChange;\n this.onSelectionChange = onSelectionChange;\n\n const initialContent =\n plainTextMode && typeof content === \"string\"\n ? plainTextToTipTapHtml(content)\n : markdownMode && typeof content === \"string\"\n ? markdownToTipTapHtml(content)\n : content;\n\n this.editor = new Editor({\n element: element || undefined,\n content: initialContent,\n extensions,\n ...editorOptions,\n onUpdate: (props) => {\n editorOptions.onUpdate?.(props);\n this.handleUpdate();\n },\n onSelectionUpdate: (props) => {\n editorOptions.onSelectionUpdate?.(props);\n this.handleSelectionUpdate();\n },\n });\n\n if (plainTextMode) {\n this.enforcePlainTextClipboard();\n }\n\n this.baselineContent = this.getCurrentContentString();\n }\n\n private enforcePlainTextClipboard() {\n // Opt-in rich clipboard / file import must not bypass the plain-text invariant.\n const richClipboard = this.editor.storage?.richClipboard;\n if (richClipboard) richClipboard.plainTextMode = true;\n const fileImport = this.editor.extensionManager?.extensions.find(\n (ext) => ext.name === \"fileImport\",\n );\n if (fileImport) fileImport.options.asPlainText = true;\n\n this.editor.setOptions({\n editorProps: {\n ...this.editor.options.editorProps,\n clipboardTextParser: (\n text: string,\n context: any,\n _plain: boolean,\n view?: any,\n ) => {\n const schema =\n context?.doc?.type?.schema ||\n context?.schema ||\n view?.state?.schema ||\n this.editor.schema;\n\n if (!schema) return Slice.empty;\n\n return plainTextToSlice(text, schema);\n },\n },\n });\n }\n\n private handleUpdate() {\n if (this.plainTextMode || this.onPlainTextChange || this.onTokensChange) {\n const text = this.getPlainText();\n this.onPlainTextChange?.(text);\n\n if (this.onTokensChange) {\n const tokens = tokenizePrompt(text);\n this.onTokensChange(tokens);\n }\n }\n\n if (this.markdownMode || this.onMarkdownChange) {\n const markdown = this.getMarkdown();\n this.onMarkdownChange?.(markdown);\n }\n }\n\n private handleSelectionUpdate() {\n if (this.onSelectionChange) {\n const { from, to } = this.editor.state.selection;\n this.onSelectionChange({ from, to });\n }\n }\n\n public getPlainText(): string {\n return getEditorText(this.editor);\n }\n\n public setPlainText(text: string, emitUpdate = true) {\n const html = plainTextToTipTapHtml(text);\n this.editor.commands.setContent(html, emitUpdate);\n }\n\n public getMarkdown(): string {\n return getEditorMarkdown(this.editor);\n }\n\n public setMarkdown(markdown: string, emitUpdate = true) {\n if (this.editor.storage?.markdown?.parser) {\n this.editor.commands.setContent(markdown, emitUpdate);\n } else {\n const html = markdownToTipTapHtml(markdown);\n this.editor.commands.setContent(html, emitUpdate);\n }\n }\n\n /**\n * Replaces `from`..`to` with literal text: HTML in `text` is not parsed, and\n * newlines split paragraphs.\n */\n private insertPlainText(from: number, to: number, text: string) {\n const { state, view } = this.editor;\n const tr = text.includes(\"\\n\") || text.includes(\"\\r\")\n ? state.tr.replace(from, to, plainTextToSlice(text, state.schema))\n : text\n ? state.tr.insertText(text, from, to)\n : state.tr.delete(from, to);\n view.dispatch(tr.scrollIntoView());\n }\n\n /** Inserts text literally at the selection; HTML in `text` is not parsed. */\n public insertText(text: string) {\n const { from, to } = this.editor.state.selection;\n this.insertPlainText(from, to, text);\n }\n\n /** Inserts text literally at `pos`; HTML in `text` is not parsed. */\n public insertTextAt(pos: number, text: string) {\n this.insertPlainText(pos, pos, text);\n this.editor.commands.focus();\n }\n\n /** Replaces a range with literal text; HTML in `text` is not parsed. */\n public replaceRange(from: number, to: number, text: string) {\n this.insertPlainText(from, to, text);\n this.editor.commands.focus();\n }\n\n public getCurrentContentString(): string {\n if (this.plainTextMode) return this.getPlainText();\n if (this.markdownMode) return this.getMarkdown();\n return this.editor.getHTML();\n }\n\n public isDirty(): boolean {\n return this.getCurrentContentString() !== this.baselineContent;\n }\n\n public setCheckpoint(content?: string): void {\n this.baselineContent =\n content !== undefined ? content : this.getCurrentContentString();\n }\n\n public resetDirty(): void {\n this.setCheckpoint();\n }\n\n public getBaselineContent(): string {\n return this.baselineContent;\n }\n\n public destroy() {\n this.editor.destroy();\n }\n}\n\n/**\n * Creates and initializes an EditorController instance.\n */\nexport function createEditor(\n options: EditorControllerOptions = {},\n): EditorController {\n return new EditorController(options);\n}\n","/**\n * Compares two values for structural equality.\n * Handles primitives, objects, arrays, and null/undefined.\n */\nexport function defaultIsEqual<T>(a: T, b: T): boolean {\n if (Object.is(a, b)) return true;\n if (\n typeof a !== \"object\" ||\n a === null ||\n typeof b !== \"object\" ||\n b === null\n ) {\n return false;\n }\n\n // Fast path for arrays\n if (Array.isArray(a) && Array.isArray(b)) {\n if (a.length !== b.length) return false;\n for (let i = 0; i < a.length; i++) {\n if (!defaultIsEqual(a[i], b[i])) return false;\n }\n return true;\n }\n if (Array.isArray(a) !== Array.isArray(b)) {\n return false;\n }\n\n const keysA = Object.keys(a) as (keyof T)[];\n const keysB = Object.keys(b) as (keyof T)[];\n\n if (keysA.length !== keysB.length) return false;\n\n for (const key of keysA) {\n if (!Object.prototype.hasOwnProperty.call(b, key)) return false;\n if (!defaultIsEqual(a[key], b[key])) return false;\n }\n\n return true;\n}\n\nexport interface DirtyTrackerOptions<T> {\n isEqual?: (a: T, b: T) => boolean;\n}\n\n/**\n * Tracks a baseline value and determines if the current state is dirty (modified).\n */\nexport class DirtyTracker<T = unknown> {\n private baseline: T;\n private readonly isEqual: (a: T, b: T) => boolean;\n\n constructor(\n initialBaseline: T,\n options?: DirtyTrackerOptions<T> | ((a: T, b: T) => boolean),\n ) {\n this.baseline = initialBaseline;\n if (typeof options === \"function\") {\n this.isEqual = options;\n } else {\n this.isEqual = options?.isEqual ?? defaultIsEqual;\n }\n }\n\n /**\n * Returns current baseline value.\n */\n public getBaseline(): T {\n return this.baseline;\n }\n\n /**\n * Sets a new baseline value without triggering any dirty state.\n */\n public setBaseline(value: T): void {\n this.baseline = value;\n }\n\n /**\n * Checks whether the given value differs from the baseline.\n */\n public isDirty(current: T): boolean {\n return !this.isEqual(this.baseline, current);\n }\n\n /**\n * Resets the baseline to the provided current value (or clears dirty status).\n */\n public reset(current: T): void {\n this.baseline = current;\n }\n}\n\n/**\n * Creates a DirtyTracker instance.\n */\nexport function createDirtyTracker<T>(\n initialBaseline: T,\n options?: DirtyTrackerOptions<T> | ((a: T, b: T) => boolean),\n): DirtyTracker<T> {\n return new DirtyTracker(initialBaseline, options);\n}\n","import { defaultImportRegistry, type DocumentImport } from \"./adapters\";\nimport {\n importFilesIntoEditor,\n type ImportEditorTarget,\n type ImportFilesIntoEditorOptions,\n} from \"./editor\";\nimport type { FileLike, ImportOutcome } from \"./registry\";\n\n/**\n * Opens the browser file picker and resolves with the chosen files (an empty\n * array when the dialog is cancelled). `accept` defaults to every extension\n * and MIME type the default registry can import as a document.\n */\nexport function pickImportFiles(options: { accept?: string; multiple?: boolean } = {}): Promise<File[]> {\n if (typeof document === \"undefined\") return Promise.resolve([]);\n return new Promise((resolve) => {\n const input = document.createElement(\"input\");\n input.type = \"file\";\n input.accept = options.accept ?? defaultImportRegistry.accept(\"document\");\n input.multiple = options.multiple ?? true;\n input.style.display = \"none\";\n const finish = (files: File[]) => {\n input.remove();\n resolve(files);\n };\n input.addEventListener(\"change\", () => finish(Array.from(input.files ?? [])), { once: true });\n input.addEventListener(\"cancel\", () => finish([]), { once: true });\n document.body.appendChild(input);\n input.click();\n });\n}\n\nexport interface FileImportState {\n isImporting: boolean;\n /** Last import error (cleared when a new import starts). */\n error: Error | null;\n /** Outcomes of the most recent import. */\n lastOutcomes: ImportOutcome<DocumentImport>[];\n}\n\nexport interface FileImportControllerOptions extends Omit<ImportFilesIntoEditorOptions, \"at\"> {\n /** Allow choosing several files in the picker (default `true`). */\n multiple?: boolean;\n}\n\n/**\n * Framework-agnostic state for a file-import button: tracks `isImporting` /\n * `error`, opens the file picker and inserts the imported content into the\n * editor returned by `getTarget`. React's `useFileImport` and Svelte's\n * `createFileImport` wrap it.\n */\nexport class FileImportController {\n private state: FileImportState = { isImporting: false, error: null, lastOutcomes: [] };\n private readonly listeners = new Set<(state: FileImportState) => void>();\n private pending = 0;\n\n constructor(\n private readonly getTarget: () => ImportEditorTarget | null | undefined,\n private options: FileImportControllerOptions = {},\n ) {}\n\n setOptions(options: FileImportControllerOptions): void {\n this.options = options;\n }\n\n /** `accept` attribute value for document imports. */\n get accept(): string {\n return (this.options.registry ?? defaultImportRegistry).accept(\"document\");\n }\n\n getState = (): FileImportState => this.state;\n\n subscribe = (listener: (state: FileImportState) => void): (() => void) => {\n this.listeners.add(listener);\n return () => {\n this.listeners.delete(listener);\n };\n };\n\n private setState(patch: Partial<FileImportState>) {\n this.state = { ...this.state, ...patch };\n this.listeners.forEach((listener) => listener(this.state));\n }\n\n /** Imports files into the editor at `at` (default: the selection). */\n importFiles = async (\n files: Iterable<FileLike> | ArrayLike<FileLike>,\n at?: number,\n ): Promise<ImportOutcome<DocumentImport>[]> => {\n const target = this.getTarget();\n const list = Array.from(files as ArrayLike<FileLike>);\n if (!target || list.length === 0) return [];\n this.pending++;\n this.setState({ isImporting: true, error: null });\n let error: Error | null = null;\n try {\n const outcomes = await importFilesIntoEditor(target, list, {\n ...this.options,\n at,\n onError: (err, file) => {\n error = err;\n this.options.onError?.(err, file);\n },\n });\n this.pending--;\n this.setState({ isImporting: this.pending > 0, error, lastOutcomes: outcomes });\n return outcomes;\n } catch (err) {\n this.pending--;\n this.setState({ isImporting: this.pending > 0, error: err instanceof Error ? err : new Error(String(err)) });\n return [];\n }\n };\n\n /** Opens the file picker and imports the chosen files at the selection. */\n openFilePicker = async (): Promise<ImportOutcome<DocumentImport>[]> => {\n const files = await pickImportFiles({ accept: this.accept, multiple: this.options.multiple });\n return this.importFiles(files);\n };\n}\n\nexport function createFileImportController(\n getTarget: () => ImportEditorTarget | null | undefined,\n options?: FileImportControllerOptions,\n): FileImportController {\n return new FileImportController(getTarget, options);\n}\n","import { plainTextToParagraphHtml } from \"../serializers/html\";\nimport type { DocumentImportAdapter } from \"./adapters\";\n\n/**\n * PDF text import through an injected pdf.js-compatible library. The editor\n * ships no PDF parser; consumers that already bundle `pdfjs-dist` register\n * the adapter themselves:\n *\n * ```ts\n * import * as pdfjs from \"pdfjs-dist\";\n * registerImportAdapter(createPdfImportAdapter(pdfjs));\n * ```\n *\n * Text items are grouped into lines by baseline (`transform[5]`) and lines\n * into paragraphs by vertical gaps larger than the page's typical line\n * spacing (or blank `hasEOL` lines). Only text is imported: no images,\n * tables, links or styling.\n */\n\n/** A text item from `page.getTextContent()` (marked-content items have no `str`). */\nexport interface PdfJsTextItem {\n str: string;\n /** `[a, b, c, d, x, y]` transform; `x` / `y` are the baseline origin. */\n transform?: readonly number[];\n width?: number;\n height?: number;\n /** pdf.js sets this when a line break follows the item. */\n hasEOL?: boolean;\n}\n\nexport interface PdfJsTextContent {\n items: ReadonlyArray<PdfJsTextItem | { type: string }>;\n}\n\nexport interface PdfJsPage {\n getTextContent(): Promise<PdfJsTextContent>;\n cleanup?(): unknown;\n}\n\nexport interface PdfJsDocument {\n numPages: number;\n getPage(pageNumber: number): Promise<PdfJsPage>;\n destroy?(): unknown;\n}\n\n/** The subset of the pdf.js API the adapter uses (`pdfjs-dist` satisfies it). */\nexport interface PdfJsLike {\n getDocument(source: { data: Uint8Array; [option: string]: unknown }): { promise: Promise<PdfJsDocument> };\n}\n\nexport interface PdfTextOptions {\n /**\n * A line gap larger than this multiple of the page's typical line spacing\n * starts a new paragraph (default `1.4`).\n */\n paragraphGap?: number;\n}\n\nexport interface PdfImportOptions extends PdfTextOptions {\n /** Adapter id (default `\"pdf\"`). */\n id?: string;\n /** Import at most this many pages (default: all). */\n maxPages?: number;\n /** Extra `getDocument` parameters, e.g. `{ disableFontFace: true }`. */\n documentParams?: Record<string, unknown>;\n}\n\ninterface Line {\n text: string;\n y: number;\n height: number;\n /** A blank line (empty `hasEOL` item) follows this one. */\n blankAfter: boolean;\n}\n\nconst isTextItem = (item: PdfJsTextItem | { type: string }): item is PdfJsTextItem =>\n typeof (item as PdfJsTextItem).str === \"string\";\n\nfunction median(values: number[]): number {\n if (!values.length) return 0;\n const sorted = [...values].sort((a, b) => a - b);\n const mid = sorted.length >> 1;\n return sorted.length % 2 ? sorted[mid] : (sorted[mid - 1] + sorted[mid]) / 2;\n}\n\nfunction groupLines(items: ReadonlyArray<PdfJsTextItem | { type: string }>): Line[] {\n const lines: Line[] = [];\n let current: (Line & { endX?: number }) | null = null;\n let breakNext = false;\n for (const item of items) {\n if (!isTextItem(item)) continue;\n const x = item.transform?.[4] ?? 0;\n const y: number = item.transform?.[5] ?? (current?.y ?? 0);\n const height: number = item.height || Math.abs(item.transform?.[3] ?? 0) || current?.height || 10;\n const tolerance = Math.max(1, height * 0.5);\n if (!item.str) {\n // Empty items mark line ends; two in a row mean a blank line.\n if (item.hasEOL) {\n if (breakNext && current) current.blankAfter = true;\n breakNext = true;\n }\n continue;\n }\n if (!current || breakNext || Math.abs(y - current.y) > tolerance) {\n current = { text: \"\", y, height, blankAfter: false };\n lines.push(current);\n } else if (current.endX !== undefined && x - current.endX > height * 0.15) {\n // A horizontal gap without an explicit space item.\n if (!/\\s$/.test(current.text) && !/^\\s/.test(item.str)) current.text += \" \";\n }\n current.text += item.str;\n current.height = Math.max(current.height, height);\n current.endX = item.width !== undefined ? x + item.width : undefined;\n breakNext = !!item.hasEOL;\n }\n return lines;\n}\n\n/**\n * Groups one page's pdf.js text items into paragraphs. Exposed for custom\n * adapters and tests.\n */\nexport function pdfTextItemsToParagraphs(\n items: ReadonlyArray<PdfJsTextItem | { type: string }>,\n options: PdfTextOptions = {},\n): string[] {\n const lines = groupLines(items).filter((line) => line.text.trim() || line.blankAfter);\n const gaps: number[] = [];\n for (let i = 1; i < lines.length; i++) {\n const gap = lines[i - 1].y - lines[i].y;\n if (gap > 0) gaps.push(gap);\n }\n const typical = median(gaps);\n const factor = options.paragraphGap ?? 1.4;\n\n const paragraphs: string[] = [];\n let current: string[] = [];\n const flush = () => {\n const text = current.join(\" \").replace(/\\s+/g, \" \").trim();\n if (text) paragraphs.push(text);\n current = [];\n };\n lines.forEach((line, i) => {\n if (i > 0) {\n const prev = lines[i - 1];\n const gap = prev.y - line.y;\n const bigGap = typical > 0 ? gap > typical * factor : gap > prev.height * 1.8;\n // A line above the previous one (gap < 0) starts a new column or block.\n if (prev.blankAfter || bigGap || gap < -prev.height * 0.5) flush();\n }\n const text = line.text.trim();\n if (!text) return;\n if (current.length && /\\w-$/.test(current[current.length - 1]) && /^[a-z]/.test(text)) {\n // Re-join words hyphenated across lines.\n current[current.length - 1] = current[current.length - 1].slice(0, -1) + text;\n } else {\n current.push(text);\n }\n });\n flush();\n return paragraphs;\n}\n\n/** Creates a PDF `document` adapter backed by an injected pdf.js-compatible library. */\nexport function createPdfImportAdapter(pdfjs: PdfJsLike, options: PdfImportOptions = {}): DocumentImportAdapter {\n return {\n id: options.id ?? \"pdf\",\n label: \"PDF\",\n mimeTypes: [\"application/pdf\"],\n extensions: [\".pdf\"],\n target: \"document\",\n async import(_input, context) {\n // pdf.js may transfer (detach) the buffer it receives, so pass a copy.\n const data = new Uint8Array((await context.readArrayBuffer()).slice(0));\n const doc = await pdfjs.getDocument({ ...options.documentParams, data }).promise;\n const paragraphs: string[] = [];\n try {\n const pages = Math.min(doc.numPages, options.maxPages ?? Infinity);\n for (let n = 1; n <= pages; n++) {\n const page = await doc.getPage(n);\n const content = await page.getTextContent();\n paragraphs.push(...pdfTextItemsToParagraphs(content.items, options));\n page.cleanup?.();\n }\n } finally {\n await doc.destroy?.();\n }\n const text = paragraphs.join(\"\\n\");\n return { html: plainTextToParagraphHtml(text), text };\n },\n };\n}\n","import { escapeHtml } from \"../serializers/plain-text\";\nimport { defaultImportRegistry, type DocumentImport, type DocumentImportAdapter } from \"./adapters\";\nimport { ImportError, type ImportAdapterRegistry } from \"./registry\";\nimport { readZip, type ZipReadOptions } from \"./zip\";\n\n/**\n * Zipped document exports (Notion, Confluence, Obsidian, static-site dumps):\n * a `.zip` of markdown / HTML / text / DOCX files. Each entry is imported\n * with the registry's other `document` adapters and the results are\n * concatenated, with a heading per file.\n *\n * Not registered by default: `.zip` is a generic container, and claiming\n * every dropped archive as a document would steal drops from other handlers\n * (e.g. attachment uploads). Opt in with\n * `registerImportAdapter(zipImportAdapter)`.\n */\n\nexport interface ZipDocumentImport extends DocumentImport {\n /** Imported entries in document order, with the adapter that handled each. */\n files: { name: string; adapterId: string }[];\n /** Entries that were left out, with the reason. */\n skipped: { name: string; reason: string }[];\n}\n\nexport interface ZipImportOptions {\n /** Adapter id (default `\"zip\"`). */\n id?: string;\n /**\n * Registry used for the entries. Defaults to the registry running the\n * import (`context.registry`), then the default registry.\n */\n registry?: ImportAdapterRegistry;\n /** Add a heading with each file's title before its content (default `true`). */\n headings?: boolean;\n /** Heading level for file titles (default `2`). */\n headingLevel?: 1 | 2 | 3 | 4 | 5 | 6;\n /** Import at most this many files (default 500); the rest are reported as skipped. */\n maxFiles?: number;\n /** Archive limits (default: 100 MiB extracted, 5 000 entries). */\n zip?: ZipReadOptions;\n}\n\nconst INDEX_NAMES = /^(index|readme|home|_index|start)$/i;\n\nconst baseName = (path: string) => path.slice(path.lastIndexOf(\"/\") + 1);\nconst stem = (name: string) => name.replace(/\\.[^./]+$/, \"\");\n\n/** File title: the name without extension, path or a trailing Notion-style id. */\nexport function zipEntryTitle(path: string): string {\n const title = stem(baseName(path))\n .replace(/\\s+[0-9a-f]{32}$/i, \"\")\n .replace(/[_]+/g, \" \")\n .trim();\n return title || baseName(path);\n}\n\n/**\n * Orders entries for import: index / README files first (shallowest first),\n * then by path with natural number ordering (`2` before `10`).\n */\nexport function sortZipEntryNames(names: readonly string[]): string[] {\n const isIndex = (name: string) => INDEX_NAMES.test(stem(baseName(name)));\n const depth = (name: string) => name.split(\"/\").length;\n return [...names].sort((a, b) => {\n const ai = isIndex(a);\n const bi = isIndex(b);\n if (ai !== bi) return ai ? -1 : 1;\n if (ai && bi && depth(a) !== depth(b)) return depth(a) - depth(b);\n return a.localeCompare(b, undefined, { numeric: true, sensitivity: \"base\" });\n });\n}\n\nconst isJunk = (name: string) =>\n name.startsWith(\"__MACOSX/\") || name.split(\"/\").some((segment) => segment.startsWith(\".\"));\n\n/** Creates a `document` adapter for zipped exports. */\nexport function createZipImportAdapter(options: ZipImportOptions = {}): DocumentImportAdapter {\n const id = options.id ?? \"zip\";\n return {\n id,\n label: \"Zipped documents\",\n mimeTypes: [\"application/zip\", \"application/x-zip-compressed\"],\n extensions: [\".zip\"],\n target: \"document\",\n async import(_input, context): Promise<ZipDocumentImport> {\n const registry = options.registry ?? context.registry ?? defaultImportRegistry;\n const zip = await readZip(await context.readArrayBuffer(), {\n maxTotalSize: 100 * 1024 * 1024,\n maxEntries: 5_000,\n ...options.zip,\n });\n const maxFiles = options.maxFiles ?? 500;\n const level = options.headingLevel ?? 2;\n const files: ZipDocumentImport[\"files\"] = [];\n const skipped: ZipDocumentImport[\"skipped\"] = [];\n let html = \"\";\n let text = \"\";\n\n const names = zip.entries.filter((entry) => !entry.isDirectory).map((entry) => entry.name);\n for (const name of sortZipEntryNames(names)) {\n if (isJunk(name)) continue;\n const adapter = registry.find({ fileName: name, target: \"document\" });\n if (!adapter) {\n skipped.push({ name, reason: \"unsupported type\" });\n continue;\n }\n if (adapter.id === id || adapter.extensions.some((ext) => ext.toLowerCase() === \".zip\")) {\n skipped.push({ name, reason: \"nested archive\" });\n continue;\n }\n if (files.length >= maxFiles) {\n skipped.push({ name, reason: \"file limit reached\" });\n continue;\n }\n try {\n const outcome = await registry.importFile<DocumentImport>(await zip.read(name), {\n fileName: name,\n adapterId: adapter.id,\n });\n const title = zipEntryTitle(name);\n if (options.headings !== false) {\n html += `<h${level}>${escapeHtml(title)}</h${level}>`;\n text += `${text ? \"\\n\" : \"\"}${title}\\n`;\n } else if (text) {\n text += \"\\n\";\n }\n html += outcome.result.html.trim();\n text += outcome.result.text;\n files.push({ name, adapterId: adapter.id });\n } catch (error) {\n // Archive-wide limits stop the whole import; other failures skip the entry.\n const code = (error as { code?: string })?.code;\n if (code === \"too_large\") throw error;\n skipped.push({ name, reason: error instanceof Error ? error.message : String(error) });\n }\n }\n if (!files.length) {\n throw new ImportError(\"invalid_file\", \"The archive contains no importable documents.\", {\n fileName: context.fileName,\n });\n }\n return { html, text, files, skipped };\n },\n };\n}\n\n/** Zipped-export adapter with default options. Opt in with `registerImportAdapter(zipImportAdapter)`. */\nexport const zipImportAdapter: DocumentImportAdapter = createZipImportAdapter();\n"],"mappings":"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","names":[]}
@@ -4,9 +4,10 @@
4
4
  * import pipelines of document apps, where each source format declares the
5
5
  * MIME types and extensions it accepts.
6
6
  *
7
- * Built-in adapters cover markdown, HTML, plain text and RTF for the
8
- * `document` target. Binary formats (DOCX, PDF, zipped exports) and other
9
- * targets (e.g. CSV → `spreadsheet`) plug in with `registerImportAdapter`.
7
+ * Built-in adapters cover markdown, HTML, plain text, RTF and DOCX for the
8
+ * `document` target. PDF (`createPdfImportAdapter`), zipped exports
9
+ * (`zipImportAdapter`) and other targets (e.g. CSV → `spreadsheet`) plug in
10
+ * with `registerImportAdapter`.
10
11
  */
11
12
  /** What an import produces content for. Custom targets are plain strings. */
12
13
  type ImportTarget = "document" | "spreadsheet" | (string & {});
@@ -29,6 +30,11 @@ interface ImportContext {
29
30
  readText(): Promise<string>;
30
31
  /** Reads the input as binary data (strings are UTF-8 encoded). */
31
32
  readArrayBuffer(): Promise<ArrayBuffer>;
33
+ /**
34
+ * The registry running the import, so container adapters (e.g. zipped
35
+ * exports) can import nested files with the same adapters.
36
+ */
37
+ registry?: ImportAdapterRegistry;
32
38
  }
33
39
  interface ImportAdapter<TResult = unknown> {
34
40
  /** Unique id, e.g. `markdown`, `html`, `csv`. */
@@ -67,7 +73,11 @@ interface ImportOutcome<TResult = unknown> {
67
73
  mimeType?: string;
68
74
  result: TResult;
69
75
  }
70
- type ImportErrorCode = "unsupported_type" | "unknown_adapter" | "adapter_failed";
76
+ type ImportErrorCode = "unsupported_type" | "unknown_adapter" | "adapter_failed"
77
+ /** The file is damaged or not the format its name/type claims (e.g. a `.docx` without `word/document.xml`). */
78
+ | "invalid_file"
79
+ /** A platform API the adapter needs (e.g. `DOMParser`) is missing. */
80
+ | "unsupported_environment";
71
81
  declare class ImportError extends Error {
72
82
  readonly code: ImportErrorCode;
73
83
  readonly fileName?: string;
@@ -85,6 +95,7 @@ declare function createImportContext(input: ImportInput, details: {
85
95
  fileName?: string;
86
96
  mimeType?: string;
87
97
  target: ImportTarget;
98
+ registry?: ImportAdapterRegistry;
88
99
  }): ImportContext;
89
100
  /**
90
101
  * A set of import adapters with type matching. Use the default registry via