@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.
@@ -215,6 +215,194 @@ var createBranchingListDeleteKeymap = (itemName, wrapperNames) => {
215
215
  });
216
216
  };
217
217
 
218
+ // src/ordered-list/roman.ts
219
+ var ROMAN_NUMERALS = [
220
+ [1e3, "m"],
221
+ [900, "cm"],
222
+ [500, "d"],
223
+ [400, "cd"],
224
+ [100, "c"],
225
+ [90, "xc"],
226
+ [50, "l"],
227
+ [40, "xl"],
228
+ [10, "x"],
229
+ [9, "ix"],
230
+ [5, "v"],
231
+ [4, "iv"],
232
+ [1, "i"]
233
+ ];
234
+ var ALPHA_NUMERALS = "abcdefghijklmnopqrstuvwxyz";
235
+ var ORDERED_LIST_ALPHA_MARKER_PATTERN = "[a-zA-Z]{1,2}";
236
+ var ORDERED_LIST_MARKER_PATTERN = String.raw`\d+|[ivxlcdmIVXLCDM]+|${ORDERED_LIST_ALPHA_MARKER_PATTERN}`;
237
+ function toRoman(num) {
238
+ let remaining = num;
239
+ let result = "";
240
+ for (const [value, numeral] of ROMAN_NUMERALS) {
241
+ while (remaining >= value) {
242
+ result += numeral;
243
+ remaining -= value;
244
+ }
245
+ }
246
+ return result;
247
+ }
248
+ function toRomanUpper(num) {
249
+ return toRoman(num).toUpperCase();
250
+ }
251
+ function fromRoman(roman) {
252
+ const lower = roman.toLowerCase();
253
+ let index = 0;
254
+ let result = 0;
255
+ while (index < lower.length) {
256
+ let matched = false;
257
+ for (const [value, numeral] of ROMAN_NUMERALS) {
258
+ if (lower.startsWith(numeral, index)) {
259
+ result += value;
260
+ index += numeral.length;
261
+ matched = true;
262
+ break;
263
+ }
264
+ }
265
+ if (!matched) {
266
+ return 0;
267
+ }
268
+ }
269
+ return result;
270
+ }
271
+ function isValidRoman(marker) {
272
+ if (!/^[ivxlcdmIVXLCDM]+$/.test(marker)) {
273
+ return false;
274
+ }
275
+ const value = fromRoman(marker);
276
+ if (value <= 0) {
277
+ return false;
278
+ }
279
+ const expected = marker === marker.toLowerCase() ? toRoman(value) : toRomanUpper(value);
280
+ return expected === marker;
281
+ }
282
+ function fromAlpha(marker) {
283
+ const lower = marker.toLowerCase();
284
+ if (lower.length === 1) {
285
+ return lower.charCodeAt(0) - "a".charCodeAt(0) + 1;
286
+ }
287
+ if (lower.length === 2) {
288
+ const first = lower.charCodeAt(0) - "a".charCodeAt(0);
289
+ const second = lower.charCodeAt(1) - "a".charCodeAt(0);
290
+ return (first + 1) * 26 + second + 1;
291
+ }
292
+ return 0;
293
+ }
294
+ function toRomanAlpha(num) {
295
+ if (num <= 26) {
296
+ return ALPHA_NUMERALS[num - 1];
297
+ }
298
+ const first = Math.floor((num - 1) / 26) - 1;
299
+ const second = (num - 1) % 26;
300
+ if (first < 0) {
301
+ return ALPHA_NUMERALS[second];
302
+ }
303
+ return ALPHA_NUMERALS[first] + ALPHA_NUMERALS[second];
304
+ }
305
+ function detectMarkerType(marker) {
306
+ if (!marker || /^\d+$/.test(marker)) {
307
+ return void 0;
308
+ }
309
+ if (isValidRoman(marker)) {
310
+ return marker === marker.toLowerCase() ? "i" : "I";
311
+ }
312
+ if (/^[a-z]{1,2}$/.test(marker)) {
313
+ return "a";
314
+ }
315
+ if (/^[A-Z]{1,2}$/.test(marker)) {
316
+ return "A";
317
+ }
318
+ return void 0;
319
+ }
320
+ function markerToStart(marker) {
321
+ if (/^\d+$/.test(marker)) {
322
+ return parseInt(marker, 10);
323
+ }
324
+ const type = detectMarkerType(marker);
325
+ if (type === "i" || type === "I") {
326
+ return fromRoman(marker);
327
+ }
328
+ if (type === "a" || type === "A") {
329
+ const start = fromAlpha(marker);
330
+ return start > 0 ? start : 1;
331
+ }
332
+ const parsed = parseInt(marker, 10);
333
+ return Number.isNaN(parsed) ? 1 : parsed;
334
+ }
335
+ function startToMarker(type, start) {
336
+ if (type === "numeric") {
337
+ return String(start);
338
+ }
339
+ switch (type) {
340
+ case "a":
341
+ return toRomanAlpha(start);
342
+ case "A":
343
+ return toRomanAlpha(start).toUpperCase();
344
+ case "i":
345
+ return toRoman(start);
346
+ case "I":
347
+ return toRomanUpper(start);
348
+ default:
349
+ return String(start);
350
+ }
351
+ }
352
+ function areOrderedListMarkersSequential(markers) {
353
+ var _a;
354
+ if (markers.length === 0) {
355
+ return false;
356
+ }
357
+ const firstType = (_a = detectMarkerType(markers[0])) != null ? _a : "numeric";
358
+ const firstStart = markerToStart(markers[0]);
359
+ if (firstStart < 1) {
360
+ return false;
361
+ }
362
+ for (let i = 0; i < markers.length; i++) {
363
+ const expected = startToMarker(firstType, firstStart + i);
364
+ if (markers[i] !== expected) {
365
+ return false;
366
+ }
367
+ }
368
+ return true;
369
+ }
370
+ function parseListMarker(marker) {
371
+ return {
372
+ type: detectMarkerType(marker),
373
+ start: markerToStart(marker)
374
+ };
375
+ }
376
+ function buildOrderedListAttrsFromMarker(marker) {
377
+ const { type, start } = parseListMarker(marker);
378
+ const attrs = {};
379
+ if (type) {
380
+ attrs.type = type;
381
+ }
382
+ if (start !== 1) {
383
+ attrs.start = start;
384
+ }
385
+ return attrs;
386
+ }
387
+ function getListMarker(type, index, separator = ". ") {
388
+ const position = index + 1;
389
+ if (!type || type === "1") {
390
+ return `${position}${separator}`;
391
+ }
392
+ switch (type) {
393
+ case "a":
394
+ return `${toRomanAlpha(position)}${separator}`;
395
+ case "A":
396
+ return `${toRomanAlpha(position).toUpperCase()}${separator}`;
397
+ case "i":
398
+ return `${toRoman(position)}${separator}`;
399
+ case "I":
400
+ return `${toRomanUpper(position)}${separator}`;
401
+ default:
402
+ return `${position}${separator}`;
403
+ }
404
+ }
405
+
218
406
  // src/item/list-item.ts
219
407
  function isSameLineOrderedListToken(token) {
220
408
  var _a, _b;
@@ -317,13 +505,15 @@ var ListItem = import_core3.Node.create({
317
505
  node,
318
506
  h,
319
507
  (context) => {
320
- var _a, _b;
508
+ var _a, _b, _c, _d;
321
509
  if (context.parentType === "bulletList") {
322
510
  return "- ";
323
511
  }
324
512
  if (context.parentType === "orderedList") {
325
513
  const start = ((_b = (_a = context.meta) == null ? void 0 : _a.parentAttrs) == null ? void 0 : _b.start) || 1;
326
- return `${start + context.index}. `;
514
+ const type = (_d = (_c = context.meta) == null ? void 0 : _c.parentAttrs) == null ? void 0 : _d.type;
515
+ const index = start - 1 + (context.index || 0);
516
+ return getListMarker(type, index, ". ");
327
517
  }
328
518
  return "- ";
329
519
  },
@@ -614,17 +804,25 @@ var ListKeymap = import_core9.Extension.create({
614
804
  });
615
805
 
616
806
  // src/ordered-list/ordered-list.ts
807
+ var import_state = require("@tiptap/pm/state");
617
808
  var import_core10 = require("@tiptap/core");
618
809
 
619
810
  // src/ordered-list/utils.ts
620
- var ORDERED_LIST_ITEM_REGEX = /^(\s*)(\d+)\.\s+(.*)$/;
811
+ var ORDERED_LIST_ITEM_REGEX = new RegExp(
812
+ `^(\\s*)(${ORDERED_LIST_MARKER_PATTERN})([.)])\\s+(.*)$`
813
+ );
814
+ var ORDERED_LIST_LINE_START_REGEX = new RegExp(
815
+ `^(\\s*)(${ORDERED_LIST_MARKER_PATTERN})([.)])\\s+`
816
+ );
621
817
  var INDENTED_LINE_REGEX = /^\s/;
818
+ function isOrderedListMarkerLine(line) {
819
+ return ORDERED_LIST_ITEM_REGEX.test(line.trimStart());
820
+ }
622
821
  function isBlockContentLine(line) {
623
822
  const trimmedLine = line.trimStart();
624
823
  return (
625
824
  // oxlint-disable-next-line prefer-string-starts-ends-with
626
- /^[-+*]\s+/.test(trimmedLine) || // oxlint-disable-next-line prefer-string-starts-ends-with
627
- /^\d+\.\s+/.test(trimmedLine) || // oxlint-disable-next-line prefer-string-starts-ends-with
825
+ /^[-+*]\s+/.test(trimmedLine) || isOrderedListMarkerLine(trimmedLine) || // oxlint-disable-next-line prefer-string-starts-ends-with
628
826
  /^>\s?/.test(trimmedLine) || // oxlint-disable-next-line prefer-string-starts-ends-with
629
827
  /^```/.test(trimmedLine) || // oxlint-disable-next-line prefer-string-starts-ends-with
630
828
  /^~~~/.test(trimmedLine)
@@ -666,8 +864,11 @@ function collectOrderedListItems(lines) {
666
864
  if (!match) {
667
865
  break;
668
866
  }
669
- const [, indent, number, content] = match;
867
+ const [, indent, marker, _separator, content] = match;
670
868
  const indentLevel = indent.length;
869
+ const number = parseInt(marker, 10);
870
+ const markerType = isNaN(number) ? detectMarkerType(marker) : void 0;
871
+ const itemNumber = isNaN(number) ? markerToStart(marker) : number;
671
872
  const itemContentLines = [content];
672
873
  let nextLineIndex = currentLineIndex + 1;
673
874
  const itemLines = [line];
@@ -684,8 +885,10 @@ function collectOrderedListItems(lines) {
684
885
  sawBlankLine = true;
685
886
  nextLineIndex += 1;
686
887
  } else if (nextLine.match(INDENTED_LINE_REGEX)) {
888
+ const leadingWhitespace = nextLine.length - nextLine.trimStart().length;
889
+ const contentIndent = indentLevel + marker.length + 1;
687
890
  itemLines.push(nextLine);
688
- itemContentLines.push(nextLine.slice(indentLevel + 2));
891
+ itemContentLines.push(nextLine.slice(Math.min(leadingWhitespace, contentIndent)));
689
892
  nextLineIndex += 1;
690
893
  } else {
691
894
  if (sawBlankLine) {
@@ -698,7 +901,8 @@ function collectOrderedListItems(lines) {
698
901
  }
699
902
  listItems.push({
700
903
  indent: indentLevel,
701
- number: parseInt(number, 10),
904
+ number: itemNumber,
905
+ type: markerType,
702
906
  content: itemContentLines.join("\n").trim(),
703
907
  contentLines: itemContentLines,
704
908
  raw: itemLines.join("\n")
@@ -708,6 +912,44 @@ function collectOrderedListItems(lines) {
708
912
  }
709
913
  return [listItems, consumed];
710
914
  }
915
+ var PLAIN_TEXT_ORDERED_LIST_LINE_REGEX = new RegExp(
916
+ `^(${ORDERED_LIST_MARKER_PATTERN})([.)])\\s+(.+)$`
917
+ );
918
+ function parsePlainTextOrderedListPaste(text) {
919
+ const lines = text.split("\n").filter((l) => l.trim().length > 0);
920
+ if (lines.length === 0) {
921
+ return null;
922
+ }
923
+ const parsedItems = [];
924
+ for (const line of lines) {
925
+ const match = line.trim().match(PLAIN_TEXT_ORDERED_LIST_LINE_REGEX);
926
+ if (!match) {
927
+ return null;
928
+ }
929
+ parsedItems.push({
930
+ marker: match[1],
931
+ content: match[3]
932
+ });
933
+ }
934
+ const markers = parsedItems.map((item) => item.marker);
935
+ if (!areOrderedListMarkersSequential(markers)) {
936
+ return null;
937
+ }
938
+ const attrs = buildOrderedListAttrsFromMarker(parsedItems[0].marker);
939
+ return {
940
+ type: "orderedList",
941
+ attrs,
942
+ content: parsedItems.map((item) => ({
943
+ type: "listItem",
944
+ content: [
945
+ {
946
+ type: "paragraph",
947
+ content: [{ type: "text", text: item.content }]
948
+ }
949
+ ]
950
+ }))
951
+ };
952
+ }
711
953
  function buildNestedStructure(items, baseIndent, lexer) {
712
954
  const result = [];
713
955
  let currentIndex = 0;
@@ -742,6 +984,7 @@ function buildNestedStructure(items, baseIndent, lexer) {
742
984
  type: "list",
743
985
  ordered: true,
744
986
  start: nestedItems[0].number,
987
+ typeMarker: nestedItems[0].type,
745
988
  items: nestedListItems,
746
989
  raw: nestedItems.map((nestedItem) => nestedItem.raw).join("\n")
747
990
  });
@@ -793,6 +1036,27 @@ function parseListItems(items, helpers) {
793
1036
  var ListItemName2 = "listItem";
794
1037
  var TextStyleName2 = "textStyle";
795
1038
  var orderedListInputRegex = /^(\d+)\.\s$/;
1039
+ function cssListStyleTypeToHtmlType(style) {
1040
+ const match = style.match(/list-style-type\s*:\s*([^;]+)/i);
1041
+ if (!match) {
1042
+ return null;
1043
+ }
1044
+ const cssValue = match[1].trim().toLowerCase();
1045
+ switch (cssValue) {
1046
+ case "upper-roman":
1047
+ return "I";
1048
+ case "lower-roman":
1049
+ return "i";
1050
+ case "upper-alpha":
1051
+ case "upper-latin":
1052
+ return "A";
1053
+ case "lower-alpha":
1054
+ case "lower-latin":
1055
+ return "a";
1056
+ default:
1057
+ return null;
1058
+ }
1059
+ }
796
1060
  var OrderedList = import_core10.Node.create({
797
1061
  name: "orderedList",
798
1062
  addOptions() {
@@ -817,7 +1081,30 @@ var OrderedList = import_core10.Node.create({
817
1081
  },
818
1082
  type: {
819
1083
  default: null,
820
- parseHTML: (element) => element.getAttribute("type")
1084
+ parseHTML: (element) => {
1085
+ const htmlType = element.getAttribute("type");
1086
+ if (htmlType) {
1087
+ return htmlType;
1088
+ }
1089
+ const style = element.getAttribute("style");
1090
+ if (style) {
1091
+ const mappedFromOl = cssListStyleTypeToHtmlType(style);
1092
+ if (mappedFromOl) {
1093
+ return mappedFromOl;
1094
+ }
1095
+ }
1096
+ const firstLi = element.querySelector("li");
1097
+ if (firstLi) {
1098
+ const liStyle = firstLi.getAttribute("style");
1099
+ if (liStyle) {
1100
+ const mappedFromLi = cssListStyleTypeToHtmlType(liStyle);
1101
+ if (mappedFromLi) {
1102
+ return mappedFromLi;
1103
+ }
1104
+ }
1105
+ }
1106
+ return null;
1107
+ }
821
1108
  }
822
1109
  };
823
1110
  },
@@ -829,8 +1116,15 @@ var OrderedList = import_core10.Node.create({
829
1116
  ];
830
1117
  },
831
1118
  renderHTML({ HTMLAttributes }) {
832
- const { start, ...attributesWithoutStart } = HTMLAttributes;
833
- return start === 1 ? ["ol", (0, import_core10.mergeAttributes)(this.options.HTMLAttributes, attributesWithoutStart), 0] : ["ol", (0, import_core10.mergeAttributes)(this.options.HTMLAttributes, HTMLAttributes), 0];
1119
+ const { start, type, ...attributesWithoutType } = HTMLAttributes;
1120
+ const attrs = (0, import_core10.mergeAttributes)(this.options.HTMLAttributes, attributesWithoutType);
1121
+ if (start !== 1) {
1122
+ attrs.start = start;
1123
+ }
1124
+ if (type && type !== "1") {
1125
+ attrs.type = type;
1126
+ }
1127
+ return ["ol", attrs, 0];
834
1128
  },
835
1129
  markdownTokenName: "list",
836
1130
  parseMarkdown: (token, helpers) => {
@@ -838,11 +1132,19 @@ var OrderedList = import_core10.Node.create({
838
1132
  return [];
839
1133
  }
840
1134
  const startValue = token.start || 1;
1135
+ const typeValue = token.typeMarker;
841
1136
  const content = token.items ? parseListItems(token.items, helpers) : [];
1137
+ const attrs = {};
842
1138
  if (startValue !== 1) {
1139
+ attrs.start = startValue;
1140
+ }
1141
+ if (typeValue) {
1142
+ attrs.type = typeValue;
1143
+ }
1144
+ if (Object.keys(attrs).length > 0) {
843
1145
  return {
844
1146
  type: "orderedList",
845
- attrs: { start: startValue },
1147
+ attrs,
846
1148
  content
847
1149
  };
848
1150
  }
@@ -861,12 +1163,12 @@ var OrderedList = import_core10.Node.create({
861
1163
  name: "orderedList",
862
1164
  level: "block",
863
1165
  start: (src) => {
864
- const match = src.match(/^(\s*)(\d+)\.\s+/);
1166
+ const match = src.match(ORDERED_LIST_LINE_START_REGEX);
865
1167
  const index = match == null ? void 0 : match.index;
866
1168
  return index !== void 0 ? index : -1;
867
1169
  },
868
1170
  tokenize: (src, _tokens, lexer) => {
869
- var _a;
1171
+ var _a, _b;
870
1172
  const lines = src.split("\n");
871
1173
  const [listItems, consumed] = collectOrderedListItems(lines);
872
1174
  if (listItems.length === 0) {
@@ -877,10 +1179,12 @@ var OrderedList = import_core10.Node.create({
877
1179
  return void 0;
878
1180
  }
879
1181
  const startValue = ((_a = listItems[0]) == null ? void 0 : _a.number) || 1;
1182
+ const typeMarker = (_b = listItems[0]) == null ? void 0 : _b.type;
880
1183
  return {
881
1184
  type: "list",
882
1185
  ordered: true,
883
1186
  start: startValue,
1187
+ typeMarker,
884
1188
  items,
885
1189
  raw: lines.slice(0, consumed).join("\n")
886
1190
  };
@@ -904,12 +1208,47 @@ var OrderedList = import_core10.Node.create({
904
1208
  "Mod-Shift-7": () => this.editor.commands.toggleOrderedList()
905
1209
  };
906
1210
  },
1211
+ addProseMirrorPlugins() {
1212
+ return [
1213
+ new import_state.Plugin({
1214
+ props: {
1215
+ handlePaste: (view, event) => {
1216
+ var _a, _b;
1217
+ const html = (_a = event.clipboardData) == null ? void 0 : _a.getData("text/html");
1218
+ if (html == null ? void 0 : html.trim()) {
1219
+ return false;
1220
+ }
1221
+ const text = (_b = event.clipboardData) == null ? void 0 : _b.getData("text/plain");
1222
+ if (!text) {
1223
+ return false;
1224
+ }
1225
+ const orderedListContent = parsePlainTextOrderedListPaste(text);
1226
+ if (!orderedListContent) {
1227
+ return false;
1228
+ }
1229
+ try {
1230
+ const orderedListNode = view.state.schema.nodeFromJSON(orderedListContent);
1231
+ const tr = view.state.tr.replaceSelectionWith(orderedListNode);
1232
+ view.dispatch(tr);
1233
+ return true;
1234
+ } catch {
1235
+ return false;
1236
+ }
1237
+ }
1238
+ }
1239
+ })
1240
+ ];
1241
+ },
907
1242
  addInputRules() {
1243
+ const joinPredicate = (match, node) => {
1244
+ const hasDefaultType = !node.attrs.type || node.attrs.type === "1";
1245
+ return hasDefaultType && node.childCount + node.attrs.start === +match[1];
1246
+ };
908
1247
  let inputRule = (0, import_core10.wrappingInputRule)({
909
1248
  find: orderedListInputRegex,
910
1249
  type: this.type,
911
1250
  getAttributes: (match) => ({ start: +match[1] }),
912
- joinPredicate: (match, node) => node.childCount + node.attrs.start === +match[1]
1251
+ joinPredicate
913
1252
  });
914
1253
  if (this.options.keepMarks || this.options.keepAttributes) {
915
1254
  inputRule = (0, import_core10.wrappingInputRule)({
@@ -918,7 +1257,7 @@ var OrderedList = import_core10.Node.create({
918
1257
  keepMarks: this.options.keepMarks,
919
1258
  keepAttributes: this.options.keepAttributes,
920
1259
  getAttributes: (match) => ({ start: +match[1], ...this.editor.getAttributes(TextStyleName2) }),
921
- joinPredicate: (match, node) => node.childCount + node.attrs.start === +match[1],
1260
+ joinPredicate,
922
1261
  editor: this.editor
923
1262
  });
924
1263
  }