@limetech/lime-elements 38.13.0 → 38.13.2

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 (46) hide show
  1. package/CHANGELOG.md +17 -0
  2. package/dist/cjs/limel-markdown.cjs.entry.js +1 -1
  3. package/dist/cjs/limel-prosemirror-adapter.cjs.entry.js +228 -71
  4. package/dist/cjs/limel-prosemirror-adapter.cjs.entry.js.map +1 -1
  5. package/dist/cjs/{markdown-parser-564adb69.js → markdown-parser-563c73db.js} +144 -386
  6. package/dist/cjs/{markdown-parser-564adb69.js.map → markdown-parser-563c73db.js.map} +1 -1
  7. package/dist/collection/components/markdown/link-markdown-plugin.js +30 -0
  8. package/dist/collection/components/markdown/link-markdown-plugin.js.map +1 -0
  9. package/dist/collection/components/markdown/markdown-parser.js +4 -4
  10. package/dist/collection/components/markdown/markdown-parser.js.map +1 -1
  11. package/dist/collection/components/text-editor/prosemirror-adapter/menu/menu-commands.js +3 -42
  12. package/dist/collection/components/text-editor/prosemirror-adapter/menu/menu-commands.js.map +1 -1
  13. package/dist/collection/components/text-editor/prosemirror-adapter/plugins/image/inserter.js +59 -9
  14. package/dist/collection/components/text-editor/prosemirror-adapter/plugins/image/inserter.js.map +1 -1
  15. package/dist/collection/components/text-editor/prosemirror-adapter/plugins/link/link-mark-spec.js +33 -0
  16. package/dist/collection/components/text-editor/prosemirror-adapter/plugins/link/link-mark-spec.js.map +1 -0
  17. package/dist/collection/components/text-editor/prosemirror-adapter/plugins/{link-plugin.js → link/link-plugin.js} +135 -22
  18. package/dist/collection/components/text-editor/prosemirror-adapter/plugins/link/link-plugin.js.map +1 -0
  19. package/dist/collection/components/text-editor/prosemirror-adapter/plugins/link/utils.js +39 -0
  20. package/dist/collection/components/text-editor/prosemirror-adapter/plugins/link/utils.js.map +1 -0
  21. package/dist/collection/components/text-editor/prosemirror-adapter/prosemirror-adapter.js +3 -1
  22. package/dist/collection/components/text-editor/prosemirror-adapter/prosemirror-adapter.js.map +1 -1
  23. package/dist/esm/limel-markdown.entry.js +1 -1
  24. package/dist/esm/limel-prosemirror-adapter.entry.js +228 -71
  25. package/dist/esm/limel-prosemirror-adapter.entry.js.map +1 -1
  26. package/dist/esm/{markdown-parser-1c1fdedc.js → markdown-parser-41d15994.js} +144 -387
  27. package/dist/esm/markdown-parser-41d15994.js.map +1 -0
  28. package/dist/lime-elements/lime-elements.esm.js +1 -1
  29. package/dist/lime-elements/p-6b5d588e.entry.js +2 -0
  30. package/dist/lime-elements/p-6b5d588e.entry.js.map +1 -0
  31. package/dist/lime-elements/{p-ce152b39.entry.js → p-895ae176.entry.js} +2 -2
  32. package/dist/lime-elements/p-95a71e89.js +8 -0
  33. package/dist/lime-elements/p-95a71e89.js.map +1 -0
  34. package/dist/types/components/markdown/link-markdown-plugin.d.ts +9 -0
  35. package/dist/types/components/text-editor/prosemirror-adapter/menu/menu-commands.d.ts +0 -2
  36. package/dist/types/components/text-editor/prosemirror-adapter/plugins/link/link-mark-spec.d.ts +10 -0
  37. package/dist/types/components/text-editor/prosemirror-adapter/plugins/link/utils.d.ts +3 -0
  38. package/package.json +1 -2
  39. package/dist/collection/components/text-editor/prosemirror-adapter/plugins/link-plugin.js.map +0 -1
  40. package/dist/esm/markdown-parser-1c1fdedc.js.map +0 -1
  41. package/dist/lime-elements/p-587f6b6d.entry.js +0 -2
  42. package/dist/lime-elements/p-587f6b6d.entry.js.map +0 -1
  43. package/dist/lime-elements/p-cf87519f.js +0 -8
  44. package/dist/lime-elements/p-cf87519f.js.map +0 -1
  45. /package/dist/lime-elements/{p-ce152b39.entry.js.map → p-895ae176.entry.js.map} +0 -0
  46. /package/dist/types/components/text-editor/prosemirror-adapter/plugins/{link-plugin.d.ts → link/link-plugin.d.ts} +0 -0
package/CHANGELOG.md CHANGED
@@ -1,3 +1,20 @@
1
+ ## [38.13.2](https://github.com/Lundalogik/lime-elements/compare/v38.13.1...v38.13.2) (2025-05-23)
2
+
3
+
4
+ ### Bug Fixes
5
+
6
+
7
+ * **markdown/text-editor:** properly handle internal vs external link targets ([e42123b](https://github.com/Lundalogik/lime-elements/commit/e42123b41fe2f3025c910325e37b2ac12c907cfb))
8
+ * **text-editor:** don't interpret `:` as links when pasting ([9509540](https://github.com/Lundalogik/lime-elements/commit/9509540f3591ab7401a53a5de48ca774a63ef2b2))
9
+
10
+ ## [38.13.1](https://github.com/Lundalogik/lime-elements/compare/v38.13.0...v38.13.1) (2025-05-21)
11
+
12
+
13
+ ### Bug Fixes
14
+
15
+
16
+ * **text-editor:** prevent Excel tables from being processed as images when pasted ([8ec93fb](https://github.com/Lundalogik/lime-elements/commit/8ec93fbeb751ba3c984d572a117a71de40f5c94a))
17
+
1
18
  ## [38.13.0](https://github.com/Lundalogik/lime-elements/compare/v38.12.5...v38.13.0) (2025-05-20)
2
19
 
3
20
 
@@ -3,7 +3,7 @@
3
3
  Object.defineProperty(exports, '__esModule', { value: true });
4
4
 
5
5
  const index = require('./index-174a078a.js');
6
- const markdownParser = require('./markdown-parser-564adb69.js');
6
+ const markdownParser = require('./markdown-parser-563c73db.js');
7
7
  const config = require('./config-e7e1a299.js');
8
8
  require('./_commonjsHelpers-a5111d61.js');
9
9
 
@@ -3,6 +3,7 @@
3
3
  Object.defineProperty(exports, '__esModule', { value: true });
4
4
 
5
5
  const index = require('./index-174a078a.js');
6
+ const markdownParser = require('./markdown-parser-563c73db.js');
6
7
  const _baseIsEqual = require('./_baseIsEqual-244a20fe.js');
7
8
  const _assignValue = require('./_assignValue-7a5440a6.js');
8
9
  const _baseAssignValue = require('./_baseAssignValue-e84dd769.js');
@@ -12,19 +13,18 @@ const _getTag = require('./_getTag-92cb60fe.js');
12
13
  const _getPrototype = require('./_getPrototype-18d2118e.js');
13
14
  const isArray = require('./isArray-d188a04f.js');
14
15
  const isObjectLike = require('./isObjectLike-3e3f0cba.js');
15
- const markdownParser = require('./markdown-parser-564adb69.js');
16
16
  const translations = require('./translations-1d376e48.js');
17
17
  const randomString = require('./random-string-e8ad4419.js');
18
18
  const isItem = require('./isItem-3f8ad629.js');
19
19
  const files = require('./files-c08d24d4.js');
20
20
  const isEqual = require('./isEqual-a4bccf32.js');
21
21
  const debounce = require('./debounce-2e5f4b7e.js');
22
+ require('./_commonjsHelpers-a5111d61.js');
22
23
  require('./eq-9a943b00.js');
23
24
  require('./_getNative-60328036.js');
24
25
  require('./_isIndex-b40f4fc5.js');
25
26
  require('./isArrayLike-e840b044.js');
26
27
  require('./_defineProperty-8f56146d.js');
27
- require('./_commonjsHelpers-a5111d61.js');
28
28
  require('./file-metadata-e309a7a4.js');
29
29
  require('./get-icon-props-65f39e40.js');
30
30
  require('./toNumber-062ea29c.js');
@@ -16902,24 +16902,15 @@ const setActiveMethodForWrap = (command, nodeType) => {
16902
16902
  const createInsertLinkCommand = (schema, _, link) => {
16903
16903
  const command = (state, dispatch) => {
16904
16904
  const { from, to } = state.selection;
16905
+ const linkMark = schema.marks.link.create(markdownParser.getLinkAttributes(link.href, link.href));
16905
16906
  if (from === to) {
16906
16907
  // If no text is selected, insert new text with link
16907
- const linkMark = schema.marks.link.create({
16908
- href: link.href,
16909
- title: link.href,
16910
- target: isExternalLink(link.href) ? '_blank' : null,
16911
- });
16912
16908
  const linkText = link.text || link.href;
16913
16909
  const newLink = schema.text(linkText, [linkMark]);
16914
16910
  dispatch(state.tr.insert(from, newLink));
16915
16911
  }
16916
16912
  else {
16917
16913
  // If text is selected, replace selected text with link text
16918
- const linkMark = schema.marks.link.create({
16919
- href: link.href,
16920
- title: link.href,
16921
- target: isExternalLink(link.href) ? '_blank' : null,
16922
- });
16923
16914
  const selectedText = state.doc.textBetween(from, to, ' ');
16924
16915
  const newLink = schema.text(link.text || selectedText, [linkMark]);
16925
16916
  dispatch(state.tr.replaceWith(from, to, newLink));
@@ -16993,15 +16984,6 @@ const toggleNodeType = (schema, type, attrs = {}, shouldWrap = false) => {
16993
16984
  return false;
16994
16985
  };
16995
16986
  };
16996
- const isValidUrl = (text) => {
16997
- try {
16998
- new URL(text);
16999
- }
17000
- catch (_a) {
17001
- return false;
17002
- }
17003
- return true;
17004
- };
17005
16987
  const createSetNodeTypeCommand = (schema, nodeType, level) => {
17006
16988
  const type = schema.nodes[nodeType];
17007
16989
  if (!type) {
@@ -17074,27 +17056,6 @@ const createListCommand = (schema, listType) => {
17074
17056
  setActiveMethodForWrap(command, type);
17075
17057
  return command;
17076
17058
  };
17077
- const copyPasteLinkCommand = (state, dispatch) => {
17078
- const { from, to } = state.selection;
17079
- if (from === to) {
17080
- return false;
17081
- }
17082
- const clipboardData = window.clipboardData;
17083
- if (!clipboardData) {
17084
- return false;
17085
- }
17086
- const copyPastedText = clipboardData.getData('text');
17087
- if (!isValidUrl(copyPastedText)) {
17088
- return false;
17089
- }
17090
- const linkMark = state.schema.marks.link.create({
17091
- href: copyPastedText,
17092
- target: isExternalLink(copyPastedText) ? '_blank' : null,
17093
- });
17094
- const selectedText = state.doc.textBetween(from, to, ' ');
17095
- const newLink = state.schema.text(selectedText, [linkMark]);
17096
- dispatch(state.tr.replaceWith(from, to, newLink));
17097
- };
17098
17059
  const commandMapping = {
17099
17060
  strong: createToggleMarkCommand,
17100
17061
  em: createToggleMarkCommand,
@@ -17133,7 +17094,6 @@ class MenuCommandFactory {
17133
17094
  'Mod-Shift-X': this.getCommand(EditorMenuTypes.Strikethrough),
17134
17095
  'Mod-`': this.getCommand(EditorMenuTypes.Code),
17135
17096
  'Mod-Shift-C': this.getCommand(EditorMenuTypes.CodeBlock),
17136
- 'Mod-v': copyPasteLinkCommand,
17137
17097
  };
17138
17098
  }
17139
17099
  }
@@ -26260,32 +26220,145 @@ const processClickEvent = (view, event) => {
26260
26220
  }, DOUBLE_CLICK_DELAY);
26261
26221
  return true;
26262
26222
  };
26263
- const processPasteEvent$1 = (view, event) => {
26264
- const clipboardData = event.clipboardData;
26265
- if (!clipboardData) {
26223
+ /**
26224
+ * Regular expression for matching URLs, mailto links, and phone links
26225
+ */
26226
+ const URL_REGEX = /(https?:\/\/[^\s<>"']+|mailto:[^\s<>"']+|tel:[^\s<>"']+)/g;
26227
+ /**
26228
+ * Checks if the text contains any URLs, mailto links, or phone links
26229
+ */
26230
+ const hasUrls = (text) => {
26231
+ // Reset regex before use
26232
+ URL_REGEX.lastIndex = 0;
26233
+ return URL_REGEX.test(text);
26234
+ };
26235
+ /**
26236
+ * Creates a text node with the provided content
26237
+ */
26238
+ const createTextNode = (schema, content) => {
26239
+ return schema.text(content);
26240
+ };
26241
+ /**
26242
+ * Creates a link node with the provided URL
26243
+ */
26244
+ const createLinkNode = (schema, url) => {
26245
+ const linkMark = schema.marks.link.create(markdownParser.getLinkAttributes(url, url));
26246
+ return schema.text(url, [linkMark]);
26247
+ };
26248
+ /**
26249
+ * Finds all link matches in the provided text
26250
+ */
26251
+ const findLinkMatches = (text) => {
26252
+ const matches = [];
26253
+ let match;
26254
+ // Reset regex before use
26255
+ URL_REGEX.lastIndex = 0;
26256
+ while ((match = URL_REGEX.exec(text)) !== null) {
26257
+ matches.push({
26258
+ url: match[0],
26259
+ start: match.index,
26260
+ end: match.index + match[0].length,
26261
+ });
26262
+ }
26263
+ return matches;
26264
+ };
26265
+ /**
26266
+ * Creates text nodes with links for any URLs, mailto links, or phone links found in the text
26267
+ */
26268
+ const createNodesWithLinks = (text, schema) => {
26269
+ const nodes = [];
26270
+ const matches = findLinkMatches(text);
26271
+ if (matches.length === 0) {
26272
+ // No links found, just return the text as a single node
26273
+ return [createTextNode(schema, text)];
26274
+ }
26275
+ let lastIndex = 0;
26276
+ // Process each match
26277
+ for (const match of matches) {
26278
+ // Add text before the current link if any
26279
+ if (match.start > lastIndex) {
26280
+ nodes.push(createTextNode(schema, text.slice(lastIndex, match.start)));
26281
+ }
26282
+ // Add the link node
26283
+ nodes.push(createLinkNode(schema, match.url));
26284
+ lastIndex = match.end;
26285
+ }
26286
+ // Add any remaining text after the last link
26287
+ if (lastIndex < text.length) {
26288
+ nodes.push(createTextNode(schema, text.slice(lastIndex)));
26289
+ }
26290
+ return nodes;
26291
+ };
26292
+ /**
26293
+ * Pastes nodes at the current selection
26294
+ * @param view - The editor view
26295
+ * @param nodes - Array of nodes to paste
26296
+ */
26297
+ const pasteAsLink = (view, nodes) => {
26298
+ if (nodes.length === 0) {
26299
+ return;
26300
+ }
26301
+ if (isSingleLinkNode(nodes)) {
26302
+ insertSingleLink(view, nodes[0]);
26303
+ }
26304
+ else {
26305
+ insertNodeFragment(view, nodes);
26306
+ }
26307
+ };
26308
+ /**
26309
+ * Checks if the nodes array contains just a single link node
26310
+ */
26311
+ const isSingleLinkNode = (nodes) => {
26312
+ if (nodes.length !== 1) {
26266
26313
  return false;
26267
26314
  }
26268
- const text = clipboardData.getData('text/plain');
26269
- // Process as a link if the text is a valid URL
26270
- if (isValidUrl(text)) {
26271
- pasteAsLink(view, text);
26272
- return true;
26315
+ const node = nodes[0];
26316
+ // Must be text with non-empty content
26317
+ if (!node.isText || !node.text || node.text.trim() === '') {
26318
+ return false;
26273
26319
  }
26274
- return false;
26320
+ // Must have a link mark (even if there are other marks, we just care about link presence)
26321
+ return !!node.marks.find((mark) => mark.type.name === 'link');
26275
26322
  };
26276
- const pasteAsLink = (view, href) => {
26323
+ /**
26324
+ * Inserts a single link node, applying it to selected text if present
26325
+ */
26326
+ const insertSingleLink = (view, linkNode) => {
26277
26327
  const { state, dispatch } = view;
26278
26328
  const { from, to } = state.selection;
26279
- const linkMark = schema$1.marks.link.create({
26280
- href: href,
26281
- title: href,
26282
- target: isExternalLink(href) ? '_blank' : null,
26283
- });
26284
- const selectedText = state.doc.textBetween(from, to, ' ') || href;
26285
- const transaction = state.tr
26329
+ const linkMark = linkNode.marks.find((mark) => mark.type.name === 'link');
26330
+ // Use selected text if there's a selection, otherwise use the URL
26331
+ const selectedText = state.doc.textBetween(from, to, ' ') || linkMark.attrs.href;
26332
+ // Insert the text and add the link mark
26333
+ dispatch(state.tr
26286
26334
  .insertText(selectedText, from, to)
26287
- .addMark(from, from + selectedText.length, linkMark);
26288
- dispatch(transaction);
26335
+ .addMark(from, from + selectedText.length, linkMark));
26336
+ };
26337
+ /**
26338
+ * Inserts multiple nodes as a fragment at the current selection
26339
+ * @param view - The editor view
26340
+ * @param nodes - Array of nodes to insert
26341
+ */
26342
+ const insertNodeFragment = (view, nodes) => {
26343
+ const { state, dispatch } = view;
26344
+ const { from, to } = state.selection;
26345
+ // Create a fragment from the array of nodes
26346
+ const fragment = Fragment.fromArray(nodes);
26347
+ // Replace the current selection with the fragment
26348
+ dispatch(state.tr.replaceWith(from, to, fragment));
26349
+ };
26350
+ /**
26351
+ * Handles pasted content, converting URLs to links
26352
+ */
26353
+ const processPasteEvent$1 = (view, event) => {
26354
+ var _a;
26355
+ const text = (_a = event.clipboardData) === null || _a === void 0 ? void 0 : _a.getData('text/plain');
26356
+ if (!text || !hasUrls(text)) {
26357
+ return false;
26358
+ }
26359
+ const nodes = createNodesWithLinks(text, view.state.schema);
26360
+ pasteAsLink(view, nodes);
26361
+ return true;
26289
26362
  };
26290
26363
  const createLinkPlugin = (updateLinkCallback) => {
26291
26364
  return new Plugin({
@@ -26327,6 +26400,39 @@ const createLinkPlugin = (updateLinkCallback) => {
26327
26400
  });
26328
26401
  };
26329
26402
 
26403
+ const linkMarkSpec = {
26404
+ attrs: {
26405
+ href: { default: '' },
26406
+ title: { default: null },
26407
+ target: { default: null },
26408
+ rel: { default: null },
26409
+ referrerpolicy: { default: null },
26410
+ },
26411
+ inclusive: false,
26412
+ parseDOM: [
26413
+ {
26414
+ tag: 'a[href]',
26415
+ getAttrs: (dom) => {
26416
+ return {
26417
+ href: dom.getAttribute('href') || '',
26418
+ title: dom.getAttribute('title'),
26419
+ target: dom.getAttribute('target'),
26420
+ rel: dom.getAttribute('rel'),
26421
+ referrerpolicy: dom.getAttribute('referrerpolicy'),
26422
+ };
26423
+ },
26424
+ },
26425
+ ],
26426
+ toDOM: (mark) => {
26427
+ const target = mark.attrs.target || null;
26428
+ const securityAttrs = {
26429
+ rel: target === '_blank' ? 'noopener noreferrer' : null,
26430
+ referrerpolicy: target === '_blank' ? 'noreferrer' : null,
26431
+ };
26432
+ return ['a', Object.assign(Object.assign({}, mark.attrs), securityAttrs), 0];
26433
+ },
26434
+ };
26435
+
26330
26436
  const pluginKey = new PluginKey('imageInserterPlugin');
26331
26437
  const createImageInserterPlugin = (imagePastedCallback) => {
26332
26438
  return new Plugin({
@@ -26458,9 +26564,29 @@ const processPasteEvent = (view, event, slice) => {
26458
26564
  if (!clipboardData) {
26459
26565
  return false;
26460
26566
  }
26567
+ const isImageFilePasted = handlePastedImages(view, clipboardData);
26568
+ const filteredSlice = new Slice(filterImageNodes(slice.content), slice.openStart, slice.openEnd);
26569
+ if (filteredSlice.content.childCount < slice.content.childCount) {
26570
+ const { state, dispatch } = view;
26571
+ const tr = state.tr.replaceSelection(filteredSlice);
26572
+ dispatch(tr);
26573
+ return true;
26574
+ }
26575
+ return isImageFilePasted;
26576
+ };
26577
+ /**
26578
+ * Processes any image files found in the clipboard data and dispatches an imagePasted event.
26579
+ *
26580
+ * @param view - The ProseMirror editor view
26581
+ * @param clipboardData - The clipboard data transfer object containing potential image files
26582
+ * @returns True if at least one valid image file was found and processed, false otherwise
26583
+ */
26584
+ function handlePastedImages(view, clipboardData) {
26585
+ let isImageFilePasted = false;
26461
26586
  const files$1 = Array.from(clipboardData.files || []);
26462
26587
  for (const file of files$1) {
26463
- if (file.type.startsWith('image/')) {
26588
+ if (isImageFile(file, clipboardData)) {
26589
+ isImageFilePasted = true;
26464
26590
  const reader = new FileReader();
26465
26591
  reader.onloadend = () => {
26466
26592
  view.dom.dispatchEvent(new CustomEvent('imagePasted', {
@@ -26470,15 +26596,45 @@ const processPasteEvent = (view, event, slice) => {
26470
26596
  reader.readAsDataURL(file);
26471
26597
  }
26472
26598
  }
26473
- const filteredSlice = new Slice(filterImageNodes(slice.content), slice.openStart, slice.openEnd);
26474
- if (filteredSlice.content.childCount < slice.content.childCount) {
26475
- const { state, dispatch } = view;
26476
- const tr = state.tr.replaceSelection(filteredSlice);
26477
- dispatch(tr);
26478
- return true;
26599
+ return isImageFilePasted;
26600
+ }
26601
+ /**
26602
+ * Determines if a file is an image that should be processed by the image handler.
26603
+ *
26604
+ * This function checks both the file's MIME type and the clipboard HTML content.
26605
+ * It filters out HTML content from Excel and HTML tables, as they are not relevant for image processing.
26606
+ *
26607
+ * @param file - The file object to check
26608
+ * @param clipboardData - The full clipboard data transfer object to examine for context
26609
+ * @returns True if the file is an image that should be processed, false otherwise
26610
+ */
26611
+ function isImageFile(file, clipboardData) {
26612
+ var _a, _b;
26613
+ if (!isContentTypeImage(file)) {
26614
+ return false;
26479
26615
  }
26480
- return files$1.length > 0;
26481
- };
26616
+ const html = (_b = (_a = clipboardData === null || clipboardData === void 0 ? void 0 : clipboardData.getData('text/html')) === null || _a === void 0 ? void 0 : _a.toLowerCase()) !== null && _b !== void 0 ? _b : '';
26617
+ return !isHtmlFromExcel(html) && !isHtmlTable(html);
26618
+ }
26619
+ function isContentTypeImage(file) {
26620
+ if (!(file === null || file === void 0 ? void 0 : file.type)) {
26621
+ return false;
26622
+ }
26623
+ return file.type.startsWith('image/');
26624
+ }
26625
+ function isHtmlFromExcel(html) {
26626
+ if (!html) {
26627
+ return false;
26628
+ }
26629
+ return (html.includes('name=generator content="microsoft excel"') ||
26630
+ html.includes('xmlns:x="urn:schemas-microsoft-com:office:excel"'));
26631
+ }
26632
+ function isHtmlTable(html) {
26633
+ if (!html) {
26634
+ return false;
26635
+ }
26636
+ return html.includes('<table');
26637
+ }
26482
26638
 
26483
26639
  const MIN_WIDTH = 10;
26484
26640
  const createImageViewPlugin = (language) => {
@@ -28664,6 +28820,7 @@ const ProsemirrorAdapter = class {
28664
28820
  nodes: nodes,
28665
28821
  marks: schema$1.spec.marks.append({
28666
28822
  strikethrough: strikethrough,
28823
+ link: linkMarkSpec,
28667
28824
  }),
28668
28825
  });
28669
28826
  }