@tiptap/extension-list 3.26.1 → 3.27.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.
@@ -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];
@@ -698,7 +899,8 @@ function collectOrderedListItems(lines) {
698
899
  }
699
900
  listItems.push({
700
901
  indent: indentLevel,
701
- number: parseInt(number, 10),
902
+ number: itemNumber,
903
+ type: markerType,
702
904
  content: itemContentLines.join("\n").trim(),
703
905
  contentLines: itemContentLines,
704
906
  raw: itemLines.join("\n")
@@ -708,6 +910,44 @@ function collectOrderedListItems(lines) {
708
910
  }
709
911
  return [listItems, consumed];
710
912
  }
913
+ var PLAIN_TEXT_ORDERED_LIST_LINE_REGEX = new RegExp(
914
+ `^(${ORDERED_LIST_MARKER_PATTERN})([.)])\\s+(.+)$`
915
+ );
916
+ function parsePlainTextOrderedListPaste(text) {
917
+ const lines = text.split("\n").filter((l) => l.trim().length > 0);
918
+ if (lines.length === 0) {
919
+ return null;
920
+ }
921
+ const parsedItems = [];
922
+ for (const line of lines) {
923
+ const match = line.trim().match(PLAIN_TEXT_ORDERED_LIST_LINE_REGEX);
924
+ if (!match) {
925
+ return null;
926
+ }
927
+ parsedItems.push({
928
+ marker: match[1],
929
+ content: match[3]
930
+ });
931
+ }
932
+ const markers = parsedItems.map((item) => item.marker);
933
+ if (!areOrderedListMarkersSequential(markers)) {
934
+ return null;
935
+ }
936
+ const attrs = buildOrderedListAttrsFromMarker(parsedItems[0].marker);
937
+ return {
938
+ type: "orderedList",
939
+ attrs,
940
+ content: parsedItems.map((item) => ({
941
+ type: "listItem",
942
+ content: [
943
+ {
944
+ type: "paragraph",
945
+ content: [{ type: "text", text: item.content }]
946
+ }
947
+ ]
948
+ }))
949
+ };
950
+ }
711
951
  function buildNestedStructure(items, baseIndent, lexer) {
712
952
  const result = [];
713
953
  let currentIndex = 0;
@@ -742,6 +982,7 @@ function buildNestedStructure(items, baseIndent, lexer) {
742
982
  type: "list",
743
983
  ordered: true,
744
984
  start: nestedItems[0].number,
985
+ typeMarker: nestedItems[0].type,
745
986
  items: nestedListItems,
746
987
  raw: nestedItems.map((nestedItem) => nestedItem.raw).join("\n")
747
988
  });
@@ -793,6 +1034,27 @@ function parseListItems(items, helpers) {
793
1034
  var ListItemName2 = "listItem";
794
1035
  var TextStyleName2 = "textStyle";
795
1036
  var orderedListInputRegex = /^(\d+)\.\s$/;
1037
+ function cssListStyleTypeToHtmlType(style) {
1038
+ const match = style.match(/list-style-type\s*:\s*([^;]+)/i);
1039
+ if (!match) {
1040
+ return null;
1041
+ }
1042
+ const cssValue = match[1].trim().toLowerCase();
1043
+ switch (cssValue) {
1044
+ case "upper-roman":
1045
+ return "I";
1046
+ case "lower-roman":
1047
+ return "i";
1048
+ case "upper-alpha":
1049
+ case "upper-latin":
1050
+ return "A";
1051
+ case "lower-alpha":
1052
+ case "lower-latin":
1053
+ return "a";
1054
+ default:
1055
+ return null;
1056
+ }
1057
+ }
796
1058
  var OrderedList = import_core10.Node.create({
797
1059
  name: "orderedList",
798
1060
  addOptions() {
@@ -817,7 +1079,30 @@ var OrderedList = import_core10.Node.create({
817
1079
  },
818
1080
  type: {
819
1081
  default: null,
820
- parseHTML: (element) => element.getAttribute("type")
1082
+ parseHTML: (element) => {
1083
+ const htmlType = element.getAttribute("type");
1084
+ if (htmlType) {
1085
+ return htmlType;
1086
+ }
1087
+ const style = element.getAttribute("style");
1088
+ if (style) {
1089
+ const mappedFromOl = cssListStyleTypeToHtmlType(style);
1090
+ if (mappedFromOl) {
1091
+ return mappedFromOl;
1092
+ }
1093
+ }
1094
+ const firstLi = element.querySelector("li");
1095
+ if (firstLi) {
1096
+ const liStyle = firstLi.getAttribute("style");
1097
+ if (liStyle) {
1098
+ const mappedFromLi = cssListStyleTypeToHtmlType(liStyle);
1099
+ if (mappedFromLi) {
1100
+ return mappedFromLi;
1101
+ }
1102
+ }
1103
+ }
1104
+ return null;
1105
+ }
821
1106
  }
822
1107
  };
823
1108
  },
@@ -829,8 +1114,15 @@ var OrderedList = import_core10.Node.create({
829
1114
  ];
830
1115
  },
831
1116
  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];
1117
+ const { start, type, ...attributesWithoutType } = HTMLAttributes;
1118
+ const attrs = (0, import_core10.mergeAttributes)(this.options.HTMLAttributes, attributesWithoutType);
1119
+ if (start !== 1) {
1120
+ attrs.start = start;
1121
+ }
1122
+ if (type && type !== "1") {
1123
+ attrs.type = type;
1124
+ }
1125
+ return ["ol", attrs, 0];
834
1126
  },
835
1127
  markdownTokenName: "list",
836
1128
  parseMarkdown: (token, helpers) => {
@@ -838,11 +1130,19 @@ var OrderedList = import_core10.Node.create({
838
1130
  return [];
839
1131
  }
840
1132
  const startValue = token.start || 1;
1133
+ const typeValue = token.typeMarker;
841
1134
  const content = token.items ? parseListItems(token.items, helpers) : [];
1135
+ const attrs = {};
842
1136
  if (startValue !== 1) {
1137
+ attrs.start = startValue;
1138
+ }
1139
+ if (typeValue) {
1140
+ attrs.type = typeValue;
1141
+ }
1142
+ if (Object.keys(attrs).length > 0) {
843
1143
  return {
844
1144
  type: "orderedList",
845
- attrs: { start: startValue },
1145
+ attrs,
846
1146
  content
847
1147
  };
848
1148
  }
@@ -861,12 +1161,12 @@ var OrderedList = import_core10.Node.create({
861
1161
  name: "orderedList",
862
1162
  level: "block",
863
1163
  start: (src) => {
864
- const match = src.match(/^(\s*)(\d+)\.\s+/);
1164
+ const match = src.match(ORDERED_LIST_LINE_START_REGEX);
865
1165
  const index = match == null ? void 0 : match.index;
866
1166
  return index !== void 0 ? index : -1;
867
1167
  },
868
1168
  tokenize: (src, _tokens, lexer) => {
869
- var _a;
1169
+ var _a, _b;
870
1170
  const lines = src.split("\n");
871
1171
  const [listItems, consumed] = collectOrderedListItems(lines);
872
1172
  if (listItems.length === 0) {
@@ -877,10 +1177,12 @@ var OrderedList = import_core10.Node.create({
877
1177
  return void 0;
878
1178
  }
879
1179
  const startValue = ((_a = listItems[0]) == null ? void 0 : _a.number) || 1;
1180
+ const typeMarker = (_b = listItems[0]) == null ? void 0 : _b.type;
880
1181
  return {
881
1182
  type: "list",
882
1183
  ordered: true,
883
1184
  start: startValue,
1185
+ typeMarker,
884
1186
  items,
885
1187
  raw: lines.slice(0, consumed).join("\n")
886
1188
  };
@@ -904,12 +1206,47 @@ var OrderedList = import_core10.Node.create({
904
1206
  "Mod-Shift-7": () => this.editor.commands.toggleOrderedList()
905
1207
  };
906
1208
  },
1209
+ addProseMirrorPlugins() {
1210
+ return [
1211
+ new import_state.Plugin({
1212
+ props: {
1213
+ handlePaste: (view, event) => {
1214
+ var _a, _b;
1215
+ const html = (_a = event.clipboardData) == null ? void 0 : _a.getData("text/html");
1216
+ if (html == null ? void 0 : html.trim()) {
1217
+ return false;
1218
+ }
1219
+ const text = (_b = event.clipboardData) == null ? void 0 : _b.getData("text/plain");
1220
+ if (!text) {
1221
+ return false;
1222
+ }
1223
+ const orderedListContent = parsePlainTextOrderedListPaste(text);
1224
+ if (!orderedListContent) {
1225
+ return false;
1226
+ }
1227
+ try {
1228
+ const orderedListNode = view.state.schema.nodeFromJSON(orderedListContent);
1229
+ const tr = view.state.tr.replaceSelectionWith(orderedListNode);
1230
+ view.dispatch(tr);
1231
+ return true;
1232
+ } catch {
1233
+ return false;
1234
+ }
1235
+ }
1236
+ }
1237
+ })
1238
+ ];
1239
+ },
907
1240
  addInputRules() {
1241
+ const joinPredicate = (match, node) => {
1242
+ const hasDefaultType = !node.attrs.type || node.attrs.type === "1";
1243
+ return hasDefaultType && node.childCount + node.attrs.start === +match[1];
1244
+ };
908
1245
  let inputRule = (0, import_core10.wrappingInputRule)({
909
1246
  find: orderedListInputRegex,
910
1247
  type: this.type,
911
1248
  getAttributes: (match) => ({ start: +match[1] }),
912
- joinPredicate: (match, node) => node.childCount + node.attrs.start === +match[1]
1249
+ joinPredicate
913
1250
  });
914
1251
  if (this.options.keepMarks || this.options.keepAttributes) {
915
1252
  inputRule = (0, import_core10.wrappingInputRule)({
@@ -918,7 +1255,7 @@ var OrderedList = import_core10.Node.create({
918
1255
  keepMarks: this.options.keepMarks,
919
1256
  keepAttributes: this.options.keepAttributes,
920
1257
  getAttributes: (match) => ({ start: +match[1], ...this.editor.getAttributes(TextStyleName2) }),
921
- joinPredicate: (match, node) => node.childCount + node.attrs.start === +match[1],
1258
+ joinPredicate,
922
1259
  editor: this.editor
923
1260
  });
924
1261
  }