@tiptap/extension-list 3.26.1 → 3.27.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/kit/index.js CHANGED
@@ -197,6 +197,194 @@ var createBranchingListDeleteKeymap = (itemName, wrapperNames) => {
197
197
  });
198
198
  };
199
199
 
200
+ // src/ordered-list/roman.ts
201
+ var ROMAN_NUMERALS = [
202
+ [1e3, "m"],
203
+ [900, "cm"],
204
+ [500, "d"],
205
+ [400, "cd"],
206
+ [100, "c"],
207
+ [90, "xc"],
208
+ [50, "l"],
209
+ [40, "xl"],
210
+ [10, "x"],
211
+ [9, "ix"],
212
+ [5, "v"],
213
+ [4, "iv"],
214
+ [1, "i"]
215
+ ];
216
+ var ALPHA_NUMERALS = "abcdefghijklmnopqrstuvwxyz";
217
+ var ORDERED_LIST_ALPHA_MARKER_PATTERN = "[a-zA-Z]{1,2}";
218
+ var ORDERED_LIST_MARKER_PATTERN = String.raw`\d+|[ivxlcdmIVXLCDM]+|${ORDERED_LIST_ALPHA_MARKER_PATTERN}`;
219
+ function toRoman(num) {
220
+ let remaining = num;
221
+ let result = "";
222
+ for (const [value, numeral] of ROMAN_NUMERALS) {
223
+ while (remaining >= value) {
224
+ result += numeral;
225
+ remaining -= value;
226
+ }
227
+ }
228
+ return result;
229
+ }
230
+ function toRomanUpper(num) {
231
+ return toRoman(num).toUpperCase();
232
+ }
233
+ function fromRoman(roman) {
234
+ const lower = roman.toLowerCase();
235
+ let index = 0;
236
+ let result = 0;
237
+ while (index < lower.length) {
238
+ let matched = false;
239
+ for (const [value, numeral] of ROMAN_NUMERALS) {
240
+ if (lower.startsWith(numeral, index)) {
241
+ result += value;
242
+ index += numeral.length;
243
+ matched = true;
244
+ break;
245
+ }
246
+ }
247
+ if (!matched) {
248
+ return 0;
249
+ }
250
+ }
251
+ return result;
252
+ }
253
+ function isValidRoman(marker) {
254
+ if (!/^[ivxlcdmIVXLCDM]+$/.test(marker)) {
255
+ return false;
256
+ }
257
+ const value = fromRoman(marker);
258
+ if (value <= 0) {
259
+ return false;
260
+ }
261
+ const expected = marker === marker.toLowerCase() ? toRoman(value) : toRomanUpper(value);
262
+ return expected === marker;
263
+ }
264
+ function fromAlpha(marker) {
265
+ const lower = marker.toLowerCase();
266
+ if (lower.length === 1) {
267
+ return lower.charCodeAt(0) - "a".charCodeAt(0) + 1;
268
+ }
269
+ if (lower.length === 2) {
270
+ const first = lower.charCodeAt(0) - "a".charCodeAt(0);
271
+ const second = lower.charCodeAt(1) - "a".charCodeAt(0);
272
+ return (first + 1) * 26 + second + 1;
273
+ }
274
+ return 0;
275
+ }
276
+ function toRomanAlpha(num) {
277
+ if (num <= 26) {
278
+ return ALPHA_NUMERALS[num - 1];
279
+ }
280
+ const first = Math.floor((num - 1) / 26) - 1;
281
+ const second = (num - 1) % 26;
282
+ if (first < 0) {
283
+ return ALPHA_NUMERALS[second];
284
+ }
285
+ return ALPHA_NUMERALS[first] + ALPHA_NUMERALS[second];
286
+ }
287
+ function detectMarkerType(marker) {
288
+ if (!marker || /^\d+$/.test(marker)) {
289
+ return void 0;
290
+ }
291
+ if (isValidRoman(marker)) {
292
+ return marker === marker.toLowerCase() ? "i" : "I";
293
+ }
294
+ if (/^[a-z]{1,2}$/.test(marker)) {
295
+ return "a";
296
+ }
297
+ if (/^[A-Z]{1,2}$/.test(marker)) {
298
+ return "A";
299
+ }
300
+ return void 0;
301
+ }
302
+ function markerToStart(marker) {
303
+ if (/^\d+$/.test(marker)) {
304
+ return parseInt(marker, 10);
305
+ }
306
+ const type = detectMarkerType(marker);
307
+ if (type === "i" || type === "I") {
308
+ return fromRoman(marker);
309
+ }
310
+ if (type === "a" || type === "A") {
311
+ const start = fromAlpha(marker);
312
+ return start > 0 ? start : 1;
313
+ }
314
+ const parsed = parseInt(marker, 10);
315
+ return Number.isNaN(parsed) ? 1 : parsed;
316
+ }
317
+ function startToMarker(type, start) {
318
+ if (type === "numeric") {
319
+ return String(start);
320
+ }
321
+ switch (type) {
322
+ case "a":
323
+ return toRomanAlpha(start);
324
+ case "A":
325
+ return toRomanAlpha(start).toUpperCase();
326
+ case "i":
327
+ return toRoman(start);
328
+ case "I":
329
+ return toRomanUpper(start);
330
+ default:
331
+ return String(start);
332
+ }
333
+ }
334
+ function areOrderedListMarkersSequential(markers) {
335
+ var _a;
336
+ if (markers.length === 0) {
337
+ return false;
338
+ }
339
+ const firstType = (_a = detectMarkerType(markers[0])) != null ? _a : "numeric";
340
+ const firstStart = markerToStart(markers[0]);
341
+ if (firstStart < 1) {
342
+ return false;
343
+ }
344
+ for (let i = 0; i < markers.length; i++) {
345
+ const expected = startToMarker(firstType, firstStart + i);
346
+ if (markers[i] !== expected) {
347
+ return false;
348
+ }
349
+ }
350
+ return true;
351
+ }
352
+ function parseListMarker(marker) {
353
+ return {
354
+ type: detectMarkerType(marker),
355
+ start: markerToStart(marker)
356
+ };
357
+ }
358
+ function buildOrderedListAttrsFromMarker(marker) {
359
+ const { type, start } = parseListMarker(marker);
360
+ const attrs = {};
361
+ if (type) {
362
+ attrs.type = type;
363
+ }
364
+ if (start !== 1) {
365
+ attrs.start = start;
366
+ }
367
+ return attrs;
368
+ }
369
+ function getListMarker(type, index, separator = ". ") {
370
+ const position = index + 1;
371
+ if (!type || type === "1") {
372
+ return `${position}${separator}`;
373
+ }
374
+ switch (type) {
375
+ case "a":
376
+ return `${toRomanAlpha(position)}${separator}`;
377
+ case "A":
378
+ return `${toRomanAlpha(position).toUpperCase()}${separator}`;
379
+ case "i":
380
+ return `${toRoman(position)}${separator}`;
381
+ case "I":
382
+ return `${toRomanUpper(position)}${separator}`;
383
+ default:
384
+ return `${position}${separator}`;
385
+ }
386
+ }
387
+
200
388
  // src/item/list-item.ts
201
389
  function isSameLineOrderedListToken(token) {
202
390
  var _a, _b;
@@ -299,13 +487,15 @@ var ListItem = Node2.create({
299
487
  node,
300
488
  h,
301
489
  (context) => {
302
- var _a, _b;
490
+ var _a, _b, _c, _d;
303
491
  if (context.parentType === "bulletList") {
304
492
  return "- ";
305
493
  }
306
494
  if (context.parentType === "orderedList") {
307
495
  const start = ((_b = (_a = context.meta) == null ? void 0 : _a.parentAttrs) == null ? void 0 : _b.start) || 1;
308
- return `${start + context.index}. `;
496
+ const type = (_d = (_c = context.meta) == null ? void 0 : _c.parentAttrs) == null ? void 0 : _d.type;
497
+ const index = start - 1 + (context.index || 0);
498
+ return getListMarker(type, index, ". ");
309
499
  }
310
500
  return "- ";
311
501
  },
@@ -596,17 +786,25 @@ var ListKeymap = Extension2.create({
596
786
  });
597
787
 
598
788
  // src/ordered-list/ordered-list.ts
789
+ import { Plugin } from "@tiptap/pm/state";
599
790
  import { mergeAttributes as mergeAttributes3, Node as Node3, wrappingInputRule as wrappingInputRule2 } from "@tiptap/core";
600
791
 
601
792
  // src/ordered-list/utils.ts
602
- var ORDERED_LIST_ITEM_REGEX = /^(\s*)(\d+)\.\s+(.*)$/;
793
+ var ORDERED_LIST_ITEM_REGEX = new RegExp(
794
+ `^(\\s*)(${ORDERED_LIST_MARKER_PATTERN})([.)])\\s+(.*)$`
795
+ );
796
+ var ORDERED_LIST_LINE_START_REGEX = new RegExp(
797
+ `^(\\s*)(${ORDERED_LIST_MARKER_PATTERN})([.)])\\s+`
798
+ );
603
799
  var INDENTED_LINE_REGEX = /^\s/;
800
+ function isOrderedListMarkerLine(line) {
801
+ return ORDERED_LIST_ITEM_REGEX.test(line.trimStart());
802
+ }
604
803
  function isBlockContentLine(line) {
605
804
  const trimmedLine = line.trimStart();
606
805
  return (
607
806
  // oxlint-disable-next-line prefer-string-starts-ends-with
608
- /^[-+*]\s+/.test(trimmedLine) || // oxlint-disable-next-line prefer-string-starts-ends-with
609
- /^\d+\.\s+/.test(trimmedLine) || // oxlint-disable-next-line prefer-string-starts-ends-with
807
+ /^[-+*]\s+/.test(trimmedLine) || isOrderedListMarkerLine(trimmedLine) || // oxlint-disable-next-line prefer-string-starts-ends-with
610
808
  /^>\s?/.test(trimmedLine) || // oxlint-disable-next-line prefer-string-starts-ends-with
611
809
  /^```/.test(trimmedLine) || // oxlint-disable-next-line prefer-string-starts-ends-with
612
810
  /^~~~/.test(trimmedLine)
@@ -648,8 +846,11 @@ function collectOrderedListItems(lines) {
648
846
  if (!match) {
649
847
  break;
650
848
  }
651
- const [, indent, number, content] = match;
849
+ const [, indent, marker, _separator, content] = match;
652
850
  const indentLevel = indent.length;
851
+ const number = parseInt(marker, 10);
852
+ const markerType = isNaN(number) ? detectMarkerType(marker) : void 0;
853
+ const itemNumber = isNaN(number) ? markerToStart(marker) : number;
653
854
  const itemContentLines = [content];
654
855
  let nextLineIndex = currentLineIndex + 1;
655
856
  const itemLines = [line];
@@ -666,8 +867,10 @@ function collectOrderedListItems(lines) {
666
867
  sawBlankLine = true;
667
868
  nextLineIndex += 1;
668
869
  } else if (nextLine.match(INDENTED_LINE_REGEX)) {
870
+ const leadingWhitespace = nextLine.length - nextLine.trimStart().length;
871
+ const contentIndent = indentLevel + marker.length + 1;
669
872
  itemLines.push(nextLine);
670
- itemContentLines.push(nextLine.slice(indentLevel + 2));
873
+ itemContentLines.push(nextLine.slice(Math.min(leadingWhitespace, contentIndent)));
671
874
  nextLineIndex += 1;
672
875
  } else {
673
876
  if (sawBlankLine) {
@@ -680,7 +883,8 @@ function collectOrderedListItems(lines) {
680
883
  }
681
884
  listItems.push({
682
885
  indent: indentLevel,
683
- number: parseInt(number, 10),
886
+ number: itemNumber,
887
+ type: markerType,
684
888
  content: itemContentLines.join("\n").trim(),
685
889
  contentLines: itemContentLines,
686
890
  raw: itemLines.join("\n")
@@ -690,6 +894,44 @@ function collectOrderedListItems(lines) {
690
894
  }
691
895
  return [listItems, consumed];
692
896
  }
897
+ var PLAIN_TEXT_ORDERED_LIST_LINE_REGEX = new RegExp(
898
+ `^(${ORDERED_LIST_MARKER_PATTERN})([.)])\\s+(.+)$`
899
+ );
900
+ function parsePlainTextOrderedListPaste(text) {
901
+ const lines = text.split("\n").filter((l) => l.trim().length > 0);
902
+ if (lines.length === 0) {
903
+ return null;
904
+ }
905
+ const parsedItems = [];
906
+ for (const line of lines) {
907
+ const match = line.trim().match(PLAIN_TEXT_ORDERED_LIST_LINE_REGEX);
908
+ if (!match) {
909
+ return null;
910
+ }
911
+ parsedItems.push({
912
+ marker: match[1],
913
+ content: match[3]
914
+ });
915
+ }
916
+ const markers = parsedItems.map((item) => item.marker);
917
+ if (!areOrderedListMarkersSequential(markers)) {
918
+ return null;
919
+ }
920
+ const attrs = buildOrderedListAttrsFromMarker(parsedItems[0].marker);
921
+ return {
922
+ type: "orderedList",
923
+ attrs,
924
+ content: parsedItems.map((item) => ({
925
+ type: "listItem",
926
+ content: [
927
+ {
928
+ type: "paragraph",
929
+ content: [{ type: "text", text: item.content }]
930
+ }
931
+ ]
932
+ }))
933
+ };
934
+ }
693
935
  function buildNestedStructure(items, baseIndent, lexer) {
694
936
  const result = [];
695
937
  let currentIndex = 0;
@@ -724,6 +966,7 @@ function buildNestedStructure(items, baseIndent, lexer) {
724
966
  type: "list",
725
967
  ordered: true,
726
968
  start: nestedItems[0].number,
969
+ typeMarker: nestedItems[0].type,
727
970
  items: nestedListItems,
728
971
  raw: nestedItems.map((nestedItem) => nestedItem.raw).join("\n")
729
972
  });
@@ -775,6 +1018,27 @@ function parseListItems(items, helpers) {
775
1018
  var ListItemName2 = "listItem";
776
1019
  var TextStyleName2 = "textStyle";
777
1020
  var orderedListInputRegex = /^(\d+)\.\s$/;
1021
+ function cssListStyleTypeToHtmlType(style) {
1022
+ const match = style.match(/list-style-type\s*:\s*([^;]+)/i);
1023
+ if (!match) {
1024
+ return null;
1025
+ }
1026
+ const cssValue = match[1].trim().toLowerCase();
1027
+ switch (cssValue) {
1028
+ case "upper-roman":
1029
+ return "I";
1030
+ case "lower-roman":
1031
+ return "i";
1032
+ case "upper-alpha":
1033
+ case "upper-latin":
1034
+ return "A";
1035
+ case "lower-alpha":
1036
+ case "lower-latin":
1037
+ return "a";
1038
+ default:
1039
+ return null;
1040
+ }
1041
+ }
778
1042
  var OrderedList = Node3.create({
779
1043
  name: "orderedList",
780
1044
  addOptions() {
@@ -799,7 +1063,30 @@ var OrderedList = Node3.create({
799
1063
  },
800
1064
  type: {
801
1065
  default: null,
802
- parseHTML: (element) => element.getAttribute("type")
1066
+ parseHTML: (element) => {
1067
+ const htmlType = element.getAttribute("type");
1068
+ if (htmlType) {
1069
+ return htmlType;
1070
+ }
1071
+ const style = element.getAttribute("style");
1072
+ if (style) {
1073
+ const mappedFromOl = cssListStyleTypeToHtmlType(style);
1074
+ if (mappedFromOl) {
1075
+ return mappedFromOl;
1076
+ }
1077
+ }
1078
+ const firstLi = element.querySelector("li");
1079
+ if (firstLi) {
1080
+ const liStyle = firstLi.getAttribute("style");
1081
+ if (liStyle) {
1082
+ const mappedFromLi = cssListStyleTypeToHtmlType(liStyle);
1083
+ if (mappedFromLi) {
1084
+ return mappedFromLi;
1085
+ }
1086
+ }
1087
+ }
1088
+ return null;
1089
+ }
803
1090
  }
804
1091
  };
805
1092
  },
@@ -811,8 +1098,15 @@ var OrderedList = Node3.create({
811
1098
  ];
812
1099
  },
813
1100
  renderHTML({ HTMLAttributes }) {
814
- const { start, ...attributesWithoutStart } = HTMLAttributes;
815
- return start === 1 ? ["ol", mergeAttributes3(this.options.HTMLAttributes, attributesWithoutStart), 0] : ["ol", mergeAttributes3(this.options.HTMLAttributes, HTMLAttributes), 0];
1101
+ const { start, type, ...attributesWithoutType } = HTMLAttributes;
1102
+ const attrs = mergeAttributes3(this.options.HTMLAttributes, attributesWithoutType);
1103
+ if (start !== 1) {
1104
+ attrs.start = start;
1105
+ }
1106
+ if (type && type !== "1") {
1107
+ attrs.type = type;
1108
+ }
1109
+ return ["ol", attrs, 0];
816
1110
  },
817
1111
  markdownTokenName: "list",
818
1112
  parseMarkdown: (token, helpers) => {
@@ -820,11 +1114,19 @@ var OrderedList = Node3.create({
820
1114
  return [];
821
1115
  }
822
1116
  const startValue = token.start || 1;
1117
+ const typeValue = token.typeMarker;
823
1118
  const content = token.items ? parseListItems(token.items, helpers) : [];
1119
+ const attrs = {};
824
1120
  if (startValue !== 1) {
1121
+ attrs.start = startValue;
1122
+ }
1123
+ if (typeValue) {
1124
+ attrs.type = typeValue;
1125
+ }
1126
+ if (Object.keys(attrs).length > 0) {
825
1127
  return {
826
1128
  type: "orderedList",
827
- attrs: { start: startValue },
1129
+ attrs,
828
1130
  content
829
1131
  };
830
1132
  }
@@ -843,12 +1145,12 @@ var OrderedList = Node3.create({
843
1145
  name: "orderedList",
844
1146
  level: "block",
845
1147
  start: (src) => {
846
- const match = src.match(/^(\s*)(\d+)\.\s+/);
1148
+ const match = src.match(ORDERED_LIST_LINE_START_REGEX);
847
1149
  const index = match == null ? void 0 : match.index;
848
1150
  return index !== void 0 ? index : -1;
849
1151
  },
850
1152
  tokenize: (src, _tokens, lexer) => {
851
- var _a;
1153
+ var _a, _b;
852
1154
  const lines = src.split("\n");
853
1155
  const [listItems, consumed] = collectOrderedListItems(lines);
854
1156
  if (listItems.length === 0) {
@@ -859,10 +1161,12 @@ var OrderedList = Node3.create({
859
1161
  return void 0;
860
1162
  }
861
1163
  const startValue = ((_a = listItems[0]) == null ? void 0 : _a.number) || 1;
1164
+ const typeMarker = (_b = listItems[0]) == null ? void 0 : _b.type;
862
1165
  return {
863
1166
  type: "list",
864
1167
  ordered: true,
865
1168
  start: startValue,
1169
+ typeMarker,
866
1170
  items,
867
1171
  raw: lines.slice(0, consumed).join("\n")
868
1172
  };
@@ -886,12 +1190,47 @@ var OrderedList = Node3.create({
886
1190
  "Mod-Shift-7": () => this.editor.commands.toggleOrderedList()
887
1191
  };
888
1192
  },
1193
+ addProseMirrorPlugins() {
1194
+ return [
1195
+ new Plugin({
1196
+ props: {
1197
+ handlePaste: (view, event) => {
1198
+ var _a, _b;
1199
+ const html = (_a = event.clipboardData) == null ? void 0 : _a.getData("text/html");
1200
+ if (html == null ? void 0 : html.trim()) {
1201
+ return false;
1202
+ }
1203
+ const text = (_b = event.clipboardData) == null ? void 0 : _b.getData("text/plain");
1204
+ if (!text) {
1205
+ return false;
1206
+ }
1207
+ const orderedListContent = parsePlainTextOrderedListPaste(text);
1208
+ if (!orderedListContent) {
1209
+ return false;
1210
+ }
1211
+ try {
1212
+ const orderedListNode = view.state.schema.nodeFromJSON(orderedListContent);
1213
+ const tr = view.state.tr.replaceSelectionWith(orderedListNode);
1214
+ view.dispatch(tr);
1215
+ return true;
1216
+ } catch {
1217
+ return false;
1218
+ }
1219
+ }
1220
+ }
1221
+ })
1222
+ ];
1223
+ },
889
1224
  addInputRules() {
1225
+ const joinPredicate = (match, node) => {
1226
+ const hasDefaultType = !node.attrs.type || node.attrs.type === "1";
1227
+ return hasDefaultType && node.childCount + node.attrs.start === +match[1];
1228
+ };
890
1229
  let inputRule = wrappingInputRule2({
891
1230
  find: orderedListInputRegex,
892
1231
  type: this.type,
893
1232
  getAttributes: (match) => ({ start: +match[1] }),
894
- joinPredicate: (match, node) => node.childCount + node.attrs.start === +match[1]
1233
+ joinPredicate
895
1234
  });
896
1235
  if (this.options.keepMarks || this.options.keepAttributes) {
897
1236
  inputRule = wrappingInputRule2({
@@ -900,7 +1239,7 @@ var OrderedList = Node3.create({
900
1239
  keepMarks: this.options.keepMarks,
901
1240
  keepAttributes: this.options.keepAttributes,
902
1241
  getAttributes: (match) => ({ start: +match[1], ...this.editor.getAttributes(TextStyleName2) }),
903
- joinPredicate: (match, node) => node.childCount + node.attrs.start === +match[1],
1242
+ joinPredicate,
904
1243
  editor: this.editor
905
1244
  });
906
1245
  }