@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/index.cjs CHANGED
@@ -24,13 +24,23 @@ __export(index_exports, {
24
24
  ListItem: () => ListItem,
25
25
  ListKeymap: () => ListKeymap,
26
26
  ListKit: () => ListKit,
27
+ ORDERED_LIST_MARKER_PATTERN: () => ORDERED_LIST_MARKER_PATTERN,
27
28
  OrderedList: () => OrderedList,
28
29
  TaskItem: () => TaskItem,
29
30
  TaskList: () => TaskList,
31
+ areOrderedListMarkersSequential: () => areOrderedListMarkersSequential,
32
+ buildOrderedListAttrsFromMarker: () => buildOrderedListAttrsFromMarker,
30
33
  bulletListInputRegex: () => bulletListInputRegex,
34
+ detectMarkerType: () => detectMarkerType,
35
+ getListMarker: () => getListMarker,
31
36
  inputRegex: () => inputRegex,
32
37
  listHelpers: () => listHelpers_exports,
33
- orderedListInputRegex: () => orderedListInputRegex
38
+ markerToStart: () => markerToStart,
39
+ orderedListInputRegex: () => orderedListInputRegex,
40
+ parseListMarker: () => parseListMarker,
41
+ parsePlainTextOrderedListPaste: () => parsePlainTextOrderedListPaste,
42
+ toRoman: () => toRoman,
43
+ toRomanUpper: () => toRomanUpper
34
44
  });
35
45
  module.exports = __toCommonJS(index_exports);
36
46
 
@@ -224,6 +234,194 @@ var createBranchingListDeleteKeymap = (itemName, wrapperNames) => {
224
234
  });
225
235
  };
226
236
 
237
+ // src/ordered-list/roman.ts
238
+ var ROMAN_NUMERALS = [
239
+ [1e3, "m"],
240
+ [900, "cm"],
241
+ [500, "d"],
242
+ [400, "cd"],
243
+ [100, "c"],
244
+ [90, "xc"],
245
+ [50, "l"],
246
+ [40, "xl"],
247
+ [10, "x"],
248
+ [9, "ix"],
249
+ [5, "v"],
250
+ [4, "iv"],
251
+ [1, "i"]
252
+ ];
253
+ var ALPHA_NUMERALS = "abcdefghijklmnopqrstuvwxyz";
254
+ var ORDERED_LIST_ALPHA_MARKER_PATTERN = "[a-zA-Z]{1,2}";
255
+ var ORDERED_LIST_MARKER_PATTERN = String.raw`\d+|[ivxlcdmIVXLCDM]+|${ORDERED_LIST_ALPHA_MARKER_PATTERN}`;
256
+ function toRoman(num) {
257
+ let remaining = num;
258
+ let result = "";
259
+ for (const [value, numeral] of ROMAN_NUMERALS) {
260
+ while (remaining >= value) {
261
+ result += numeral;
262
+ remaining -= value;
263
+ }
264
+ }
265
+ return result;
266
+ }
267
+ function toRomanUpper(num) {
268
+ return toRoman(num).toUpperCase();
269
+ }
270
+ function fromRoman(roman) {
271
+ const lower = roman.toLowerCase();
272
+ let index = 0;
273
+ let result = 0;
274
+ while (index < lower.length) {
275
+ let matched = false;
276
+ for (const [value, numeral] of ROMAN_NUMERALS) {
277
+ if (lower.startsWith(numeral, index)) {
278
+ result += value;
279
+ index += numeral.length;
280
+ matched = true;
281
+ break;
282
+ }
283
+ }
284
+ if (!matched) {
285
+ return 0;
286
+ }
287
+ }
288
+ return result;
289
+ }
290
+ function isValidRoman(marker) {
291
+ if (!/^[ivxlcdmIVXLCDM]+$/.test(marker)) {
292
+ return false;
293
+ }
294
+ const value = fromRoman(marker);
295
+ if (value <= 0) {
296
+ return false;
297
+ }
298
+ const expected = marker === marker.toLowerCase() ? toRoman(value) : toRomanUpper(value);
299
+ return expected === marker;
300
+ }
301
+ function fromAlpha(marker) {
302
+ const lower = marker.toLowerCase();
303
+ if (lower.length === 1) {
304
+ return lower.charCodeAt(0) - "a".charCodeAt(0) + 1;
305
+ }
306
+ if (lower.length === 2) {
307
+ const first = lower.charCodeAt(0) - "a".charCodeAt(0);
308
+ const second = lower.charCodeAt(1) - "a".charCodeAt(0);
309
+ return (first + 1) * 26 + second + 1;
310
+ }
311
+ return 0;
312
+ }
313
+ function toRomanAlpha(num) {
314
+ if (num <= 26) {
315
+ return ALPHA_NUMERALS[num - 1];
316
+ }
317
+ const first = Math.floor((num - 1) / 26) - 1;
318
+ const second = (num - 1) % 26;
319
+ if (first < 0) {
320
+ return ALPHA_NUMERALS[second];
321
+ }
322
+ return ALPHA_NUMERALS[first] + ALPHA_NUMERALS[second];
323
+ }
324
+ function detectMarkerType(marker) {
325
+ if (!marker || /^\d+$/.test(marker)) {
326
+ return void 0;
327
+ }
328
+ if (isValidRoman(marker)) {
329
+ return marker === marker.toLowerCase() ? "i" : "I";
330
+ }
331
+ if (/^[a-z]{1,2}$/.test(marker)) {
332
+ return "a";
333
+ }
334
+ if (/^[A-Z]{1,2}$/.test(marker)) {
335
+ return "A";
336
+ }
337
+ return void 0;
338
+ }
339
+ function markerToStart(marker) {
340
+ if (/^\d+$/.test(marker)) {
341
+ return parseInt(marker, 10);
342
+ }
343
+ const type = detectMarkerType(marker);
344
+ if (type === "i" || type === "I") {
345
+ return fromRoman(marker);
346
+ }
347
+ if (type === "a" || type === "A") {
348
+ const start = fromAlpha(marker);
349
+ return start > 0 ? start : 1;
350
+ }
351
+ const parsed = parseInt(marker, 10);
352
+ return Number.isNaN(parsed) ? 1 : parsed;
353
+ }
354
+ function startToMarker(type, start) {
355
+ if (type === "numeric") {
356
+ return String(start);
357
+ }
358
+ switch (type) {
359
+ case "a":
360
+ return toRomanAlpha(start);
361
+ case "A":
362
+ return toRomanAlpha(start).toUpperCase();
363
+ case "i":
364
+ return toRoman(start);
365
+ case "I":
366
+ return toRomanUpper(start);
367
+ default:
368
+ return String(start);
369
+ }
370
+ }
371
+ function areOrderedListMarkersSequential(markers) {
372
+ var _a;
373
+ if (markers.length === 0) {
374
+ return false;
375
+ }
376
+ const firstType = (_a = detectMarkerType(markers[0])) != null ? _a : "numeric";
377
+ const firstStart = markerToStart(markers[0]);
378
+ if (firstStart < 1) {
379
+ return false;
380
+ }
381
+ for (let i = 0; i < markers.length; i++) {
382
+ const expected = startToMarker(firstType, firstStart + i);
383
+ if (markers[i] !== expected) {
384
+ return false;
385
+ }
386
+ }
387
+ return true;
388
+ }
389
+ function parseListMarker(marker) {
390
+ return {
391
+ type: detectMarkerType(marker),
392
+ start: markerToStart(marker)
393
+ };
394
+ }
395
+ function buildOrderedListAttrsFromMarker(marker) {
396
+ const { type, start } = parseListMarker(marker);
397
+ const attrs = {};
398
+ if (type) {
399
+ attrs.type = type;
400
+ }
401
+ if (start !== 1) {
402
+ attrs.start = start;
403
+ }
404
+ return attrs;
405
+ }
406
+ function getListMarker(type, index, separator = ". ") {
407
+ const position = index + 1;
408
+ if (!type || type === "1") {
409
+ return `${position}${separator}`;
410
+ }
411
+ switch (type) {
412
+ case "a":
413
+ return `${toRomanAlpha(position)}${separator}`;
414
+ case "A":
415
+ return `${toRomanAlpha(position).toUpperCase()}${separator}`;
416
+ case "i":
417
+ return `${toRoman(position)}${separator}`;
418
+ case "I":
419
+ return `${toRomanUpper(position)}${separator}`;
420
+ default:
421
+ return `${position}${separator}`;
422
+ }
423
+ }
424
+
227
425
  // src/item/list-item.ts
228
426
  function isSameLineOrderedListToken(token) {
229
427
  var _a, _b;
@@ -326,13 +524,15 @@ var ListItem = import_core3.Node.create({
326
524
  node,
327
525
  h,
328
526
  (context) => {
329
- var _a, _b;
527
+ var _a, _b, _c, _d;
330
528
  if (context.parentType === "bulletList") {
331
529
  return "- ";
332
530
  }
333
531
  if (context.parentType === "orderedList") {
334
532
  const start = ((_b = (_a = context.meta) == null ? void 0 : _a.parentAttrs) == null ? void 0 : _b.start) || 1;
335
- return `${start + context.index}. `;
533
+ const type = (_d = (_c = context.meta) == null ? void 0 : _c.parentAttrs) == null ? void 0 : _d.type;
534
+ const index = start - 1 + (context.index || 0);
535
+ return getListMarker(type, index, ". ");
336
536
  }
337
537
  return "- ";
338
538
  },
@@ -626,17 +826,25 @@ var ListKeymap = import_core9.Extension.create({
626
826
  var import_core13 = require("@tiptap/core");
627
827
 
628
828
  // src/ordered-list/ordered-list.ts
829
+ var import_state = require("@tiptap/pm/state");
629
830
  var import_core10 = require("@tiptap/core");
630
831
 
631
832
  // src/ordered-list/utils.ts
632
- var ORDERED_LIST_ITEM_REGEX = /^(\s*)(\d+)\.\s+(.*)$/;
833
+ var ORDERED_LIST_ITEM_REGEX = new RegExp(
834
+ `^(\\s*)(${ORDERED_LIST_MARKER_PATTERN})([.)])\\s+(.*)$`
835
+ );
836
+ var ORDERED_LIST_LINE_START_REGEX = new RegExp(
837
+ `^(\\s*)(${ORDERED_LIST_MARKER_PATTERN})([.)])\\s+`
838
+ );
633
839
  var INDENTED_LINE_REGEX = /^\s/;
840
+ function isOrderedListMarkerLine(line) {
841
+ return ORDERED_LIST_ITEM_REGEX.test(line.trimStart());
842
+ }
634
843
  function isBlockContentLine(line) {
635
844
  const trimmedLine = line.trimStart();
636
845
  return (
637
846
  // oxlint-disable-next-line prefer-string-starts-ends-with
638
- /^[-+*]\s+/.test(trimmedLine) || // oxlint-disable-next-line prefer-string-starts-ends-with
639
- /^\d+\.\s+/.test(trimmedLine) || // oxlint-disable-next-line prefer-string-starts-ends-with
847
+ /^[-+*]\s+/.test(trimmedLine) || isOrderedListMarkerLine(trimmedLine) || // oxlint-disable-next-line prefer-string-starts-ends-with
640
848
  /^>\s?/.test(trimmedLine) || // oxlint-disable-next-line prefer-string-starts-ends-with
641
849
  /^```/.test(trimmedLine) || // oxlint-disable-next-line prefer-string-starts-ends-with
642
850
  /^~~~/.test(trimmedLine)
@@ -678,8 +886,11 @@ function collectOrderedListItems(lines) {
678
886
  if (!match) {
679
887
  break;
680
888
  }
681
- const [, indent, number, content] = match;
889
+ const [, indent, marker, _separator, content] = match;
682
890
  const indentLevel = indent.length;
891
+ const number = parseInt(marker, 10);
892
+ const markerType = isNaN(number) ? detectMarkerType(marker) : void 0;
893
+ const itemNumber = isNaN(number) ? markerToStart(marker) : number;
683
894
  const itemContentLines = [content];
684
895
  let nextLineIndex = currentLineIndex + 1;
685
896
  const itemLines = [line];
@@ -696,8 +907,10 @@ function collectOrderedListItems(lines) {
696
907
  sawBlankLine = true;
697
908
  nextLineIndex += 1;
698
909
  } else if (nextLine.match(INDENTED_LINE_REGEX)) {
910
+ const leadingWhitespace = nextLine.length - nextLine.trimStart().length;
911
+ const contentIndent = indentLevel + marker.length + 1;
699
912
  itemLines.push(nextLine);
700
- itemContentLines.push(nextLine.slice(indentLevel + 2));
913
+ itemContentLines.push(nextLine.slice(Math.min(leadingWhitespace, contentIndent)));
701
914
  nextLineIndex += 1;
702
915
  } else {
703
916
  if (sawBlankLine) {
@@ -710,7 +923,8 @@ function collectOrderedListItems(lines) {
710
923
  }
711
924
  listItems.push({
712
925
  indent: indentLevel,
713
- number: parseInt(number, 10),
926
+ number: itemNumber,
927
+ type: markerType,
714
928
  content: itemContentLines.join("\n").trim(),
715
929
  contentLines: itemContentLines,
716
930
  raw: itemLines.join("\n")
@@ -720,6 +934,44 @@ function collectOrderedListItems(lines) {
720
934
  }
721
935
  return [listItems, consumed];
722
936
  }
937
+ var PLAIN_TEXT_ORDERED_LIST_LINE_REGEX = new RegExp(
938
+ `^(${ORDERED_LIST_MARKER_PATTERN})([.)])\\s+(.+)$`
939
+ );
940
+ function parsePlainTextOrderedListPaste(text) {
941
+ const lines = text.split("\n").filter((l) => l.trim().length > 0);
942
+ if (lines.length === 0) {
943
+ return null;
944
+ }
945
+ const parsedItems = [];
946
+ for (const line of lines) {
947
+ const match = line.trim().match(PLAIN_TEXT_ORDERED_LIST_LINE_REGEX);
948
+ if (!match) {
949
+ return null;
950
+ }
951
+ parsedItems.push({
952
+ marker: match[1],
953
+ content: match[3]
954
+ });
955
+ }
956
+ const markers = parsedItems.map((item) => item.marker);
957
+ if (!areOrderedListMarkersSequential(markers)) {
958
+ return null;
959
+ }
960
+ const attrs = buildOrderedListAttrsFromMarker(parsedItems[0].marker);
961
+ return {
962
+ type: "orderedList",
963
+ attrs,
964
+ content: parsedItems.map((item) => ({
965
+ type: "listItem",
966
+ content: [
967
+ {
968
+ type: "paragraph",
969
+ content: [{ type: "text", text: item.content }]
970
+ }
971
+ ]
972
+ }))
973
+ };
974
+ }
723
975
  function buildNestedStructure(items, baseIndent, lexer) {
724
976
  const result = [];
725
977
  let currentIndex = 0;
@@ -754,6 +1006,7 @@ function buildNestedStructure(items, baseIndent, lexer) {
754
1006
  type: "list",
755
1007
  ordered: true,
756
1008
  start: nestedItems[0].number,
1009
+ typeMarker: nestedItems[0].type,
757
1010
  items: nestedListItems,
758
1011
  raw: nestedItems.map((nestedItem) => nestedItem.raw).join("\n")
759
1012
  });
@@ -805,6 +1058,27 @@ function parseListItems(items, helpers) {
805
1058
  var ListItemName2 = "listItem";
806
1059
  var TextStyleName2 = "textStyle";
807
1060
  var orderedListInputRegex = /^(\d+)\.\s$/;
1061
+ function cssListStyleTypeToHtmlType(style) {
1062
+ const match = style.match(/list-style-type\s*:\s*([^;]+)/i);
1063
+ if (!match) {
1064
+ return null;
1065
+ }
1066
+ const cssValue = match[1].trim().toLowerCase();
1067
+ switch (cssValue) {
1068
+ case "upper-roman":
1069
+ return "I";
1070
+ case "lower-roman":
1071
+ return "i";
1072
+ case "upper-alpha":
1073
+ case "upper-latin":
1074
+ return "A";
1075
+ case "lower-alpha":
1076
+ case "lower-latin":
1077
+ return "a";
1078
+ default:
1079
+ return null;
1080
+ }
1081
+ }
808
1082
  var OrderedList = import_core10.Node.create({
809
1083
  name: "orderedList",
810
1084
  addOptions() {
@@ -829,7 +1103,30 @@ var OrderedList = import_core10.Node.create({
829
1103
  },
830
1104
  type: {
831
1105
  default: null,
832
- parseHTML: (element) => element.getAttribute("type")
1106
+ parseHTML: (element) => {
1107
+ const htmlType = element.getAttribute("type");
1108
+ if (htmlType) {
1109
+ return htmlType;
1110
+ }
1111
+ const style = element.getAttribute("style");
1112
+ if (style) {
1113
+ const mappedFromOl = cssListStyleTypeToHtmlType(style);
1114
+ if (mappedFromOl) {
1115
+ return mappedFromOl;
1116
+ }
1117
+ }
1118
+ const firstLi = element.querySelector("li");
1119
+ if (firstLi) {
1120
+ const liStyle = firstLi.getAttribute("style");
1121
+ if (liStyle) {
1122
+ const mappedFromLi = cssListStyleTypeToHtmlType(liStyle);
1123
+ if (mappedFromLi) {
1124
+ return mappedFromLi;
1125
+ }
1126
+ }
1127
+ }
1128
+ return null;
1129
+ }
833
1130
  }
834
1131
  };
835
1132
  },
@@ -841,8 +1138,15 @@ var OrderedList = import_core10.Node.create({
841
1138
  ];
842
1139
  },
843
1140
  renderHTML({ HTMLAttributes }) {
844
- const { start, ...attributesWithoutStart } = HTMLAttributes;
845
- return start === 1 ? ["ol", (0, import_core10.mergeAttributes)(this.options.HTMLAttributes, attributesWithoutStart), 0] : ["ol", (0, import_core10.mergeAttributes)(this.options.HTMLAttributes, HTMLAttributes), 0];
1141
+ const { start, type, ...attributesWithoutType } = HTMLAttributes;
1142
+ const attrs = (0, import_core10.mergeAttributes)(this.options.HTMLAttributes, attributesWithoutType);
1143
+ if (start !== 1) {
1144
+ attrs.start = start;
1145
+ }
1146
+ if (type && type !== "1") {
1147
+ attrs.type = type;
1148
+ }
1149
+ return ["ol", attrs, 0];
846
1150
  },
847
1151
  markdownTokenName: "list",
848
1152
  parseMarkdown: (token, helpers) => {
@@ -850,11 +1154,19 @@ var OrderedList = import_core10.Node.create({
850
1154
  return [];
851
1155
  }
852
1156
  const startValue = token.start || 1;
1157
+ const typeValue = token.typeMarker;
853
1158
  const content = token.items ? parseListItems(token.items, helpers) : [];
1159
+ const attrs = {};
854
1160
  if (startValue !== 1) {
1161
+ attrs.start = startValue;
1162
+ }
1163
+ if (typeValue) {
1164
+ attrs.type = typeValue;
1165
+ }
1166
+ if (Object.keys(attrs).length > 0) {
855
1167
  return {
856
1168
  type: "orderedList",
857
- attrs: { start: startValue },
1169
+ attrs,
858
1170
  content
859
1171
  };
860
1172
  }
@@ -873,12 +1185,12 @@ var OrderedList = import_core10.Node.create({
873
1185
  name: "orderedList",
874
1186
  level: "block",
875
1187
  start: (src) => {
876
- const match = src.match(/^(\s*)(\d+)\.\s+/);
1188
+ const match = src.match(ORDERED_LIST_LINE_START_REGEX);
877
1189
  const index = match == null ? void 0 : match.index;
878
1190
  return index !== void 0 ? index : -1;
879
1191
  },
880
1192
  tokenize: (src, _tokens, lexer) => {
881
- var _a;
1193
+ var _a, _b;
882
1194
  const lines = src.split("\n");
883
1195
  const [listItems, consumed] = collectOrderedListItems(lines);
884
1196
  if (listItems.length === 0) {
@@ -889,10 +1201,12 @@ var OrderedList = import_core10.Node.create({
889
1201
  return void 0;
890
1202
  }
891
1203
  const startValue = ((_a = listItems[0]) == null ? void 0 : _a.number) || 1;
1204
+ const typeMarker = (_b = listItems[0]) == null ? void 0 : _b.type;
892
1205
  return {
893
1206
  type: "list",
894
1207
  ordered: true,
895
1208
  start: startValue,
1209
+ typeMarker,
896
1210
  items,
897
1211
  raw: lines.slice(0, consumed).join("\n")
898
1212
  };
@@ -916,12 +1230,47 @@ var OrderedList = import_core10.Node.create({
916
1230
  "Mod-Shift-7": () => this.editor.commands.toggleOrderedList()
917
1231
  };
918
1232
  },
1233
+ addProseMirrorPlugins() {
1234
+ return [
1235
+ new import_state.Plugin({
1236
+ props: {
1237
+ handlePaste: (view, event) => {
1238
+ var _a, _b;
1239
+ const html = (_a = event.clipboardData) == null ? void 0 : _a.getData("text/html");
1240
+ if (html == null ? void 0 : html.trim()) {
1241
+ return false;
1242
+ }
1243
+ const text = (_b = event.clipboardData) == null ? void 0 : _b.getData("text/plain");
1244
+ if (!text) {
1245
+ return false;
1246
+ }
1247
+ const orderedListContent = parsePlainTextOrderedListPaste(text);
1248
+ if (!orderedListContent) {
1249
+ return false;
1250
+ }
1251
+ try {
1252
+ const orderedListNode = view.state.schema.nodeFromJSON(orderedListContent);
1253
+ const tr = view.state.tr.replaceSelectionWith(orderedListNode);
1254
+ view.dispatch(tr);
1255
+ return true;
1256
+ } catch {
1257
+ return false;
1258
+ }
1259
+ }
1260
+ }
1261
+ })
1262
+ ];
1263
+ },
919
1264
  addInputRules() {
1265
+ const joinPredicate = (match, node) => {
1266
+ const hasDefaultType = !node.attrs.type || node.attrs.type === "1";
1267
+ return hasDefaultType && node.childCount + node.attrs.start === +match[1];
1268
+ };
920
1269
  let inputRule = (0, import_core10.wrappingInputRule)({
921
1270
  find: orderedListInputRegex,
922
1271
  type: this.type,
923
1272
  getAttributes: (match) => ({ start: +match[1] }),
924
- joinPredicate: (match, node) => node.childCount + node.attrs.start === +match[1]
1273
+ joinPredicate
925
1274
  });
926
1275
  if (this.options.keepMarks || this.options.keepAttributes) {
927
1276
  inputRule = (0, import_core10.wrappingInputRule)({
@@ -930,7 +1279,7 @@ var OrderedList = import_core10.Node.create({
930
1279
  keepMarks: this.options.keepMarks,
931
1280
  keepAttributes: this.options.keepAttributes,
932
1281
  getAttributes: (match) => ({ start: +match[1], ...this.editor.getAttributes(TextStyleName2) }),
933
- joinPredicate: (match, node) => node.childCount + node.attrs.start === +match[1],
1282
+ joinPredicate,
934
1283
  editor: this.editor
935
1284
  });
936
1285
  }
@@ -1309,12 +1658,22 @@ var ListKit = import_core13.Extension.create({
1309
1658
  ListItem,
1310
1659
  ListKeymap,
1311
1660
  ListKit,
1661
+ ORDERED_LIST_MARKER_PATTERN,
1312
1662
  OrderedList,
1313
1663
  TaskItem,
1314
1664
  TaskList,
1665
+ areOrderedListMarkersSequential,
1666
+ buildOrderedListAttrsFromMarker,
1315
1667
  bulletListInputRegex,
1668
+ detectMarkerType,
1669
+ getListMarker,
1316
1670
  inputRegex,
1317
1671
  listHelpers,
1318
- orderedListInputRegex
1672
+ markerToStart,
1673
+ orderedListInputRegex,
1674
+ parseListMarker,
1675
+ parsePlainTextOrderedListPaste,
1676
+ toRoman,
1677
+ toRomanUpper
1319
1678
  });
1320
1679
  //# sourceMappingURL=index.cjs.map