@limetech/lime-elements 38.12.5 → 38.13.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.
Files changed (52) hide show
  1. package/CHANGELOG.md +16 -0
  2. package/dist/cjs/lime-elements.cjs.js +1 -1
  3. package/dist/cjs/limel-markdown.cjs.entry.js +49 -1
  4. package/dist/cjs/limel-markdown.cjs.entry.js.map +1 -1
  5. package/dist/cjs/limel-prosemirror-adapter.cjs.entry.js +100 -29
  6. package/dist/cjs/limel-prosemirror-adapter.cjs.entry.js.map +1 -1
  7. package/dist/cjs/loader.cjs.js +1 -1
  8. package/dist/cjs/{markdown-parser-5b5ed6c4.js → markdown-parser-564adb69.js} +32 -1
  9. package/dist/cjs/{markdown-parser-5b5ed6c4.js.map → markdown-parser-564adb69.js.map} +1 -1
  10. package/dist/collection/components/markdown/allowed-css-properties.js +3 -0
  11. package/dist/collection/components/markdown/allowed-css-properties.js.map +1 -1
  12. package/dist/collection/components/markdown/image-intersection-observer.js +29 -0
  13. package/dist/collection/components/markdown/image-intersection-observer.js.map +1 -0
  14. package/dist/collection/components/markdown/image-markdown-plugin.js +28 -0
  15. package/dist/collection/components/markdown/image-markdown-plugin.js.map +1 -0
  16. package/dist/collection/components/markdown/markdown-parser.js +2 -0
  17. package/dist/collection/components/markdown/markdown-parser.js.map +1 -1
  18. package/dist/collection/components/markdown/markdown.js +38 -0
  19. package/dist/collection/components/markdown/markdown.js.map +1 -1
  20. package/dist/collection/components/text-editor/prosemirror-adapter/plugins/image/inserter.js +59 -9
  21. package/dist/collection/components/text-editor/prosemirror-adapter/plugins/image/inserter.js.map +1 -1
  22. package/dist/collection/components/text-editor/prosemirror-adapter/plugins/image/node.js +31 -14
  23. package/dist/collection/components/text-editor/prosemirror-adapter/plugins/image/node.js.map +1 -1
  24. package/dist/collection/components/text-editor/prosemirror-adapter/plugins/image/view.js +10 -5
  25. package/dist/collection/components/text-editor/prosemirror-adapter/plugins/image/view.js.map +1 -1
  26. package/dist/esm/lime-elements.js +1 -1
  27. package/dist/esm/limel-markdown.entry.js +49 -1
  28. package/dist/esm/limel-markdown.entry.js.map +1 -1
  29. package/dist/esm/limel-prosemirror-adapter.entry.js +100 -29
  30. package/dist/esm/limel-prosemirror-adapter.entry.js.map +1 -1
  31. package/dist/esm/loader.js +1 -1
  32. package/dist/esm/{markdown-parser-ecdce41c.js → markdown-parser-1c1fdedc.js} +32 -1
  33. package/dist/esm/{markdown-parser-ecdce41c.js.map → markdown-parser-1c1fdedc.js.map} +1 -1
  34. package/dist/lime-elements/lime-elements.esm.js +1 -1
  35. package/dist/lime-elements/lime-elements.esm.js.map +1 -1
  36. package/dist/lime-elements/p-ce152b39.entry.js +2 -0
  37. package/dist/lime-elements/p-ce152b39.entry.js.map +1 -0
  38. package/dist/lime-elements/{p-e5c8cf08.js → p-cf87519f.js} +3 -3
  39. package/dist/lime-elements/p-cf87519f.js.map +1 -0
  40. package/dist/lime-elements/{p-8f4c55fa.entry.js → p-d5ac8f59.entry.js} +2 -2
  41. package/dist/lime-elements/p-d5ac8f59.entry.js.map +1 -0
  42. package/dist/types/components/markdown/image-intersection-observer.d.ts +10 -0
  43. package/dist/types/components/markdown/image-markdown-plugin.d.ts +9 -0
  44. package/dist/types/components/markdown/markdown-parser.d.ts +1 -0
  45. package/dist/types/components/markdown/markdown.d.ts +9 -1
  46. package/dist/types/components/text-editor/prosemirror-adapter/plugins/image/node.d.ts +4 -0
  47. package/dist/types/components.d.ts +8 -0
  48. package/package.json +1 -1
  49. package/dist/lime-elements/p-8f4c55fa.entry.js.map +0 -1
  50. package/dist/lime-elements/p-e5c8cf08.js.map +0 -1
  51. package/dist/lime-elements/p-eadff599.entry.js +0 -2
  52. package/dist/lime-elements/p-eadff599.entry.js.map +0 -1
@@ -10,5 +10,8 @@ export const allowedCssProperties = [
10
10
  'text-decoration-thickness',
11
11
  'text-decoration',
12
12
  'width',
13
+ 'height',
14
+ 'min-width',
15
+ 'min-height',
13
16
  ];
14
17
  //# sourceMappingURL=allowed-css-properties.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"allowed-css-properties.js","sourceRoot":"","sources":["../../../src/components/markdown/allowed-css-properties.ts"],"names":[],"mappings":"AAAA,MAAM,CAAC,MAAM,oBAAoB,GAAG;EAChC,kBAAkB;EAClB,OAAO;EACP,YAAY;EACZ,aAAa;EACb,uBAAuB;EACvB,sBAAsB;EACtB,0BAA0B;EAC1B,uBAAuB;EACvB,2BAA2B;EAC3B,iBAAiB;EACjB,OAAO;CACV,CAAC","sourcesContent":["export const allowedCssProperties = [\n 'background-color',\n 'color',\n 'font-style',\n 'font-weight',\n 'text-decoration-color',\n 'text-decoration-line',\n 'text-decoration-skip-ink',\n 'text-decoration-style',\n 'text-decoration-thickness',\n 'text-decoration',\n 'width',\n];\n"]}
1
+ {"version":3,"file":"allowed-css-properties.js","sourceRoot":"","sources":["../../../src/components/markdown/allowed-css-properties.ts"],"names":[],"mappings":"AAAA,MAAM,CAAC,MAAM,oBAAoB,GAAG;EAChC,kBAAkB;EAClB,OAAO;EACP,YAAY;EACZ,aAAa;EACb,uBAAuB;EACvB,sBAAsB;EACtB,0BAA0B;EAC1B,uBAAuB;EACvB,2BAA2B;EAC3B,iBAAiB;EACjB,OAAO;EACP,QAAQ;EACR,WAAW;EACX,YAAY;CACf,CAAC","sourcesContent":["export const allowedCssProperties = [\n 'background-color',\n 'color',\n 'font-style',\n 'font-weight',\n 'text-decoration-color',\n 'text-decoration-line',\n 'text-decoration-skip-ink',\n 'text-decoration-style',\n 'text-decoration-thickness',\n 'text-decoration',\n 'width',\n 'height',\n 'min-width',\n 'min-height',\n];\n"]}
@@ -0,0 +1,29 @@
1
+ export class ImageIntersectionObserver {
2
+ /**
3
+ * @param containerElement - The element containing images to observe.
4
+ */
5
+ constructor(containerElement) {
6
+ this.handleIntersection = (entries) => {
7
+ entries.forEach((entry) => {
8
+ if (entry.isIntersecting) {
9
+ const img = entry.target;
10
+ const dataSrc = img.getAttribute('data-src');
11
+ if (dataSrc) {
12
+ img.setAttribute('src', dataSrc);
13
+ img.removeAttribute('data-src');
14
+ }
15
+ this.observer.unobserve(img);
16
+ }
17
+ });
18
+ };
19
+ this.observer = new IntersectionObserver(this.handleIntersection);
20
+ const images = containerElement.querySelectorAll('img');
21
+ images.forEach((img) => {
22
+ this.observer.observe(img);
23
+ });
24
+ }
25
+ disconnect() {
26
+ this.observer.disconnect();
27
+ }
28
+ }
29
+ //# sourceMappingURL=image-intersection-observer.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"image-intersection-observer.js","sourceRoot":"","sources":["../../../src/components/markdown/image-intersection-observer.ts"],"names":[],"mappings":"AAAA,MAAM,OAAO,yBAAyB;EAGlC;;KAEG;EACH,YAAmB,gBAA6B;IAa/B,uBAAkB,GAAG,CAClC,OAAoC,EACtC,EAAE;MACA,OAAO,CAAC,OAAO,CAAC,CAAC,KAAK,EAAE,EAAE;QACtB,IAAI,KAAK,CAAC,cAAc,EAAE;UACtB,MAAM,GAAG,GAAG,KAAK,CAAC,MAA0B,CAAC;UAC7C,MAAM,OAAO,GAAG,GAAG,CAAC,YAAY,CAAC,UAAU,CAAC,CAAC;UAE7C,IAAI,OAAO,EAAE;YACT,GAAG,CAAC,YAAY,CAAC,KAAK,EAAE,OAAO,CAAC,CAAC;YACjC,GAAG,CAAC,eAAe,CAAC,UAAU,CAAC,CAAC;WACnC;UAED,IAAI,CAAC,QAAQ,CAAC,SAAS,CAAC,GAAG,CAAC,CAAC;SAChC;MACL,CAAC,CAAC,CAAC;IACP,CAAC,CAAC;IA5BE,IAAI,CAAC,QAAQ,GAAG,IAAI,oBAAoB,CAAC,IAAI,CAAC,kBAAkB,CAAC,CAAC;IAElE,MAAM,MAAM,GAAG,gBAAgB,CAAC,gBAAgB,CAAC,KAAK,CAAC,CAAC;IACxD,MAAM,CAAC,OAAO,CAAC,CAAC,GAAG,EAAE,EAAE;MACnB,IAAI,CAAC,QAAQ,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC;IAC/B,CAAC,CAAC,CAAC;EACP,CAAC;EAEM,UAAU;IACb,IAAI,CAAC,QAAQ,CAAC,UAAU,EAAE,CAAC;EAC/B,CAAC;CAmBJ","sourcesContent":["export class ImageIntersectionObserver {\n private observer: IntersectionObserver;\n\n /**\n * @param containerElement - The element containing images to observe.\n */\n public constructor(containerElement: HTMLElement) {\n this.observer = new IntersectionObserver(this.handleIntersection);\n\n const images = containerElement.querySelectorAll('img');\n images.forEach((img) => {\n this.observer.observe(img);\n });\n }\n\n public disconnect() {\n this.observer.disconnect();\n }\n\n private readonly handleIntersection = (\n entries: IntersectionObserverEntry[],\n ) => {\n entries.forEach((entry) => {\n if (entry.isIntersecting) {\n const img = entry.target as HTMLImageElement;\n const dataSrc = img.getAttribute('data-src');\n\n if (dataSrc) {\n img.setAttribute('src', dataSrc);\n img.removeAttribute('data-src');\n }\n\n this.observer.unobserve(img);\n }\n });\n };\n}\n"]}
@@ -0,0 +1,28 @@
1
+ import { visit } from 'unist-util-visit';
2
+ /**
3
+ * Creates a unified.js plugin that transforms image elements for lazy loading
4
+ *
5
+ * @param lazyLoadImages - Whether to enable lazy loading for images
6
+ * @returns A unified.js plugin function
7
+ */
8
+ export function createLazyLoadImagesPlugin(lazyLoadImages = false) {
9
+ return () => {
10
+ if (!lazyLoadImages) {
11
+ return (tree) => tree;
12
+ }
13
+ return (tree) => {
14
+ visit(tree, 'element', (node) => {
15
+ if (node.tagName === 'img') {
16
+ node.properties = node.properties || {};
17
+ node.properties.loading = 'lazy';
18
+ if (node.properties.src) {
19
+ node.properties['data-src'] = node.properties.src;
20
+ node.properties.src = undefined;
21
+ }
22
+ }
23
+ });
24
+ return tree;
25
+ };
26
+ };
27
+ }
28
+ //# sourceMappingURL=image-markdown-plugin.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"image-markdown-plugin.js","sourceRoot":"","sources":["../../../src/components/markdown/image-markdown-plugin.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,EAAE,MAAM,kBAAkB,CAAC;AAIzC;;;;;GAKG;AACH,MAAM,UAAU,0BAA0B,CAAC,cAAc,GAAG,KAAK;EAC7D,OAAO,GAAgB,EAAE;IACrB,IAAI,CAAC,cAAc,EAAE;MACjB,OAAO,CAAC,IAAU,EAAE,EAAE,CAAC,IAAI,CAAC;KAC/B;IAED,OAAO,CAAC,IAAU,EAAE,EAAE;MAClB,KAAK,CAAC,IAAI,EAAE,SAAS,EAAE,CAAC,IAAS,EAAE,EAAE;QACjC,IAAI,IAAI,CAAC,OAAO,KAAK,KAAK,EAAE;UACxB,IAAI,CAAC,UAAU,GAAG,IAAI,CAAC,UAAU,IAAI,EAAE,CAAC;UACxC,IAAI,CAAC,UAAU,CAAC,OAAO,GAAG,MAAM,CAAC;UAEjC,IAAI,IAAI,CAAC,UAAU,CAAC,GAAG,EAAE;YACrB,IAAI,CAAC,UAAU,CAAC,UAAU,CAAC,GAAG,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC;YAClD,IAAI,CAAC,UAAU,CAAC,GAAG,GAAG,SAAS,CAAC;WACnC;SACJ;MACL,CAAC,CAAC,CAAC;MAEH,OAAO,IAAI,CAAC;IAChB,CAAC,CAAC;EACN,CAAC,CAAC;AACN,CAAC","sourcesContent":["import { visit } from 'unist-util-visit';\nimport { Node } from 'unist';\nimport { Plugin, Transformer } from 'unified';\n\n/**\n * Creates a unified.js plugin that transforms image elements for lazy loading\n *\n * @param lazyLoadImages - Whether to enable lazy loading for images\n * @returns A unified.js plugin function\n */\nexport function createLazyLoadImagesPlugin(lazyLoadImages = false): Plugin {\n return (): Transformer => {\n if (!lazyLoadImages) {\n return (tree: Node) => tree;\n }\n\n return (tree: Node) => {\n visit(tree, 'element', (node: any) => {\n if (node.tagName === 'img') {\n node.properties = node.properties || {};\n node.properties.loading = 'lazy';\n\n if (node.properties.src) {\n node.properties['data-src'] = node.properties.src;\n node.properties.src = undefined;\n }\n }\n });\n\n return tree;\n };\n };\n}\n"]}
@@ -9,6 +9,7 @@ import rehypeStringify from 'rehype-stringify';
9
9
  import rehypeRaw from 'rehype-raw';
10
10
  import { visit } from 'unist-util-visit';
11
11
  import { sanitizeStyle } from './sanitize-style';
12
+ import { createLazyLoadImagesPlugin } from './image-markdown-plugin';
12
13
  /**
13
14
  * Takes a string as input and returns a new string
14
15
  * where the text has been converted to HTML.
@@ -42,6 +43,7 @@ export async function markdownToHTML(text, options) {
42
43
  visit(tree, 'element', sanitizeStyle);
43
44
  };
44
45
  })
46
+ .use(createLazyLoadImagesPlugin(options === null || options === void 0 ? void 0 : options.lazyLoadImages))
45
47
  .use(rehypeStringify)
46
48
  .process(text);
47
49
  return file.toString();
@@ -1 +1 @@
1
- {"version":3,"file":"markdown-parser.js","sourceRoot":"","sources":["../../../src/components/markdown/markdown-parser.ts"],"names":[],"mappings":"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","sourcesContent":["import { unified } from 'unified';\nimport remarkParse from 'remark-parse';\nimport remarkRehype from 'remark-rehype';\nimport remarkGfm from 'remark-gfm';\nimport rehypeParse from 'rehype-parse';\nimport rehypeExternalLinks from 'rehype-external-links';\nimport rehypeSanitize, { defaultSchema } from 'rehype-sanitize';\nimport rehypeStringify from 'rehype-stringify';\nimport rehypeRaw from 'rehype-raw';\nimport { visit } from 'unist-util-visit';\nimport { sanitizeStyle } from './sanitize-style';\nimport { Node } from 'unist';\nimport { Schema } from 'rehype-sanitize/lib';\nimport { CustomElementDefinition } from '../../global/shared-types/custom-element.types';\n\n/**\n * Takes a string as input and returns a new string\n * where the text has been converted to HTML.\n *\n * If the text is formatted with .md markdown, it will\n * be transformed to HTML.\n *\n * If the text already is in HTML it will be sanitized and\n * \"dangerous\" tags such as <script> will be removed.\n *\n * @param text - The string to convert.\n * @param options - Options for the conversions.\n * @returns The resulting HTML.\n */\nexport async function markdownToHTML(\n text: string,\n options?: MarkdownToHTMLOptions,\n): Promise<string> {\n if (options?.forceHardLineBreaks) {\n text = text.replace(/(?<!\\\\)([\\n\\r])/g, ' $1');\n }\n\n const file = await unified()\n .use(remarkParse)\n .use(remarkGfm)\n .use(remarkRehype, { allowDangerousHtml: true })\n .use(rehypeExternalLinks, { target: '_blank' })\n .use(rehypeRaw)\n .use(rehypeSanitize, {\n ...getWhiteList(options?.whitelist ?? []),\n })\n .use(() => {\n return (tree: Node) => {\n // Run the sanitizeStyle function on all elements, to sanitize\n // the value of the `style` attribute, if there is one.\n visit(tree, 'element', sanitizeStyle);\n };\n })\n .use(rehypeStringify)\n .process(text);\n\n return file.toString();\n}\n\n/**\n * Sanitizes a given HTML string by removing dangerous tags and attributes.\n *\n * @param html - The string containing HTML to sanitize.\n * @param whitelist - Optional whitelist of custom components.\n * @returns The sanitized HTML string.\n */\nexport async function sanitizeHTML(\n html: string,\n whitelist?: CustomElementDefinition[],\n): Promise<string> {\n const file = await unified()\n .use(rehypeParse)\n .use(rehypeSanitize, {\n ...getWhiteList(whitelist ?? []),\n })\n .use(() => {\n return (tree: Node) => {\n // Run the sanitizeStyle function on all elements, to sanitize\n // the value of the `style` attribute, if there is one.\n visit(tree, 'element', sanitizeStyle);\n };\n })\n .use(rehypeStringify)\n .process(html);\n\n return file.toString();\n}\n\nfunction getWhiteList(allowedComponents: CustomElementDefinition[]): Schema {\n const defaultSchemaClone = [...(defaultSchema.attributes['*'] ?? [])];\n const asteriskAttributeWhitelist = defaultSchemaClone.filter((attr) => {\n return attr !== 'height';\n });\n asteriskAttributeWhitelist.push('style');\n\n const whitelist: Schema = {\n ...defaultSchema,\n tagNames: [\n ...(defaultSchema.tagNames || []),\n ...allowedComponents.map((component) => component.tagName),\n ],\n attributes: {\n ...defaultSchema.attributes,\n p: [\n ...(defaultSchema.attributes.p ?? []),\n ['className', 'MsoNormal'],\n ], // Allow the class 'MsoNormal' on <p> elements\n '*': asteriskAttributeWhitelist,\n },\n };\n\n for (const component of allowedComponents) {\n whitelist.attributes[component.tagName] = component.attributes;\n }\n\n return whitelist;\n}\n\n/**\n * Options for markdownToHTML.\n */\nexport interface MarkdownToHTMLOptions {\n /**\n * Set to `true` to convert all soft line breaks to hard line breaks.\n */\n forceHardLineBreaks?: boolean;\n whitelist?: CustomElementDefinition[];\n}\n"]}
1
+ {"version":3,"file":"markdown-parser.js","sourceRoot":"","sources":["../../../src/components/markdown/markdown-parser.ts"],"names":[],"mappings":"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","sourcesContent":["import { unified } from 'unified';\nimport remarkParse from 'remark-parse';\nimport remarkRehype from 'remark-rehype';\nimport remarkGfm from 'remark-gfm';\nimport rehypeParse from 'rehype-parse';\nimport rehypeExternalLinks from 'rehype-external-links';\nimport rehypeSanitize, { defaultSchema } from 'rehype-sanitize';\nimport rehypeStringify from 'rehype-stringify';\nimport rehypeRaw from 'rehype-raw';\nimport { visit } from 'unist-util-visit';\nimport { sanitizeStyle } from './sanitize-style';\nimport { Node } from 'unist';\nimport { Schema } from 'rehype-sanitize/lib';\nimport { createLazyLoadImagesPlugin } from './image-markdown-plugin';\nimport { CustomElementDefinition } from '../../global/shared-types/custom-element.types';\n\n/**\n * Takes a string as input and returns a new string\n * where the text has been converted to HTML.\n *\n * If the text is formatted with .md markdown, it will\n * be transformed to HTML.\n *\n * If the text already is in HTML it will be sanitized and\n * \"dangerous\" tags such as <script> will be removed.\n *\n * @param text - The string to convert.\n * @param options - Options for the conversions.\n * @returns The resulting HTML.\n */\nexport async function markdownToHTML(\n text: string,\n options?: MarkdownToHTMLOptions,\n): Promise<string> {\n if (options?.forceHardLineBreaks) {\n text = text.replace(/(?<!\\\\)([\\n\\r])/g, ' $1');\n }\n\n const file = await unified()\n .use(remarkParse)\n .use(remarkGfm)\n .use(remarkRehype, { allowDangerousHtml: true })\n .use(rehypeExternalLinks, { target: '_blank' })\n .use(rehypeRaw)\n .use(rehypeSanitize, {\n ...getWhiteList(options?.whitelist ?? []),\n })\n .use(() => {\n return (tree: Node) => {\n // Run the sanitizeStyle function on all elements, to sanitize\n // the value of the `style` attribute, if there is one.\n visit(tree, 'element', sanitizeStyle);\n };\n })\n .use(createLazyLoadImagesPlugin(options?.lazyLoadImages))\n .use(rehypeStringify)\n .process(text);\n\n return file.toString();\n}\n\n/**\n * Sanitizes a given HTML string by removing dangerous tags and attributes.\n *\n * @param html - The string containing HTML to sanitize.\n * @param whitelist - Optional whitelist of custom components.\n * @returns The sanitized HTML string.\n */\nexport async function sanitizeHTML(\n html: string,\n whitelist?: CustomElementDefinition[],\n): Promise<string> {\n const file = await unified()\n .use(rehypeParse)\n .use(rehypeSanitize, {\n ...getWhiteList(whitelist ?? []),\n })\n .use(() => {\n return (tree: Node) => {\n // Run the sanitizeStyle function on all elements, to sanitize\n // the value of the `style` attribute, if there is one.\n visit(tree, 'element', sanitizeStyle);\n };\n })\n .use(rehypeStringify)\n .process(html);\n\n return file.toString();\n}\n\nfunction getWhiteList(allowedComponents: CustomElementDefinition[]): Schema {\n const defaultSchemaClone = [...(defaultSchema.attributes['*'] ?? [])];\n const asteriskAttributeWhitelist = defaultSchemaClone.filter((attr) => {\n return attr !== 'height';\n });\n asteriskAttributeWhitelist.push('style');\n\n const whitelist: Schema = {\n ...defaultSchema,\n tagNames: [\n ...(defaultSchema.tagNames || []),\n ...allowedComponents.map((component) => component.tagName),\n ],\n attributes: {\n ...defaultSchema.attributes,\n p: [\n ...(defaultSchema.attributes.p ?? []),\n ['className', 'MsoNormal'],\n ], // Allow the class 'MsoNormal' on <p> elements\n '*': asteriskAttributeWhitelist,\n },\n };\n\n for (const component of allowedComponents) {\n whitelist.attributes[component.tagName] = component.attributes;\n }\n\n return whitelist;\n}\n\n/**\n * Options for markdownToHTML.\n */\nexport interface MarkdownToHTMLOptions {\n /**\n * Set to `true` to convert all soft line breaks to hard line breaks.\n */\n forceHardLineBreaks?: boolean;\n whitelist?: CustomElementDefinition[];\n lazyLoadImages?: boolean;\n}\n"]}
@@ -1,6 +1,7 @@
1
1
  import { h } from '@stencil/core';
2
2
  import { markdownToHTML } from './markdown-parser';
3
3
  import { globalConfig } from '../../global/config';
4
+ import { ImageIntersectionObserver } from './image-intersection-observer';
4
5
  /**
5
6
  * The Markdown component receives markdown syntax
6
7
  * and renders it as HTML.
@@ -22,17 +23,22 @@ import { globalConfig } from '../../global/config';
22
23
  */
23
24
  export class Markdown {
24
25
  constructor() {
26
+ this.imageIntersectionObserver = null;
25
27
  this.value = undefined;
26
28
  this.whitelist = globalConfig.markdownWhitelist;
29
+ this.lazyLoadImages = false;
27
30
  }
28
31
  async textChanged() {
29
32
  var _a;
30
33
  try {
34
+ this.cleanupImageIntersectionObserver();
31
35
  const html = await markdownToHTML(this.value, {
32
36
  forceHardLineBreaks: true,
33
37
  whitelist: (_a = this.whitelist) !== null && _a !== void 0 ? _a : [],
38
+ lazyLoadImages: this.lazyLoadImages,
34
39
  });
35
40
  this.rootElement.innerHTML = html;
41
+ this.setupImageIntersectionObserver();
36
42
  }
37
43
  catch (error) {
38
44
  // eslint-disable-next-line no-console
@@ -42,11 +48,25 @@ export class Markdown {
42
48
  async componentDidLoad() {
43
49
  this.textChanged();
44
50
  }
51
+ disconnectedCallback() {
52
+ this.cleanupImageIntersectionObserver();
53
+ }
45
54
  render() {
46
55
  return [
47
56
  h("div", { id: "markdown", ref: (el) => (this.rootElement = el) }),
48
57
  ];
49
58
  }
59
+ setupImageIntersectionObserver() {
60
+ if (this.lazyLoadImages) {
61
+ this.imageIntersectionObserver = new ImageIntersectionObserver(this.rootElement);
62
+ }
63
+ }
64
+ cleanupImageIntersectionObserver() {
65
+ if (this.imageIntersectionObserver) {
66
+ this.imageIntersectionObserver.disconnect();
67
+ this.imageIntersectionObserver = null;
68
+ }
69
+ }
50
70
  static get is() { return "limel-markdown"; }
51
71
  static get encapsulation() { return "shadow"; }
52
72
  static get originalStyleUrls() {
@@ -101,6 +121,24 @@ export class Markdown {
101
121
  "text": "Whitelisted html elements.\n\nAny custom element added here will not be sanitized and thus rendered.\nCan also be set via `limel-config`. Setting this property will override\nthe global config."
102
122
  },
103
123
  "defaultValue": "globalConfig.markdownWhitelist"
124
+ },
125
+ "lazyLoadImages": {
126
+ "type": "boolean",
127
+ "mutable": false,
128
+ "complexType": {
129
+ "original": "boolean",
130
+ "resolved": "boolean",
131
+ "references": {}
132
+ },
133
+ "required": false,
134
+ "optional": false,
135
+ "docs": {
136
+ "tags": [],
137
+ "text": "Enable lazy loading for images"
138
+ },
139
+ "attribute": "lazy-load-images",
140
+ "reflect": false,
141
+ "defaultValue": "false"
104
142
  }
105
143
  };
106
144
  }
@@ -1 +1 @@
1
- {"version":3,"file":"markdown.js","sourceRoot":"","sources":["../../../src/components/markdown/markdown.tsx"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,CAAC,EAAE,IAAI,EAAE,KAAK,EAAE,MAAM,eAAe,CAAC;AAC1D,OAAO,EAAE,cAAc,EAAE,MAAM,mBAAmB,CAAC;AACnD,OAAO,EAAE,YAAY,EAAE,MAAM,qBAAqB,CAAC;AAGnD;;;;;;;;;;;;;;;;;;GAkBG;AAMH,MAAM,OAAO,QAAQ;;;qBAmBb,YAAY,CAAC,iBAAiB;;EAG3B,KAAK,CAAC,WAAW;;IACpB,IAAI;MACA,MAAM,IAAI,GAAG,MAAM,cAAc,CAAC,IAAI,CAAC,KAAK,EAAE;QAC1C,mBAAmB,EAAE,IAAI;QACzB,SAAS,EAAE,MAAA,IAAI,CAAC,SAAS,mCAAI,EAAE;OAClC,CAAC,CAAC;MACH,IAAI,CAAC,WAAW,CAAC,SAAS,GAAG,IAAI,CAAC;KACrC;IAAC,OAAO,KAAK,EAAE;MACZ,sCAAsC;MACtC,OAAO,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC;KACxB;EACL,CAAC;EAEM,KAAK,CAAC,gBAAgB;IACzB,IAAI,CAAC,WAAW,EAAE,CAAC;EACvB,CAAC;EAIM,MAAM;IACT,OAAO;MACH,WACI,EAAE,EAAC,UAAU,EACb,GAAG,EAAE,CAAC,EAAE,EAAE,EAAE,CAAC,CAAC,IAAI,CAAC,WAAW,GAAG,EAAoB,CAAC,GACxD;KACL,CAAC;EACN,CAAC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CACJ","sourcesContent":["import { Component, h, Prop, Watch } from '@stencil/core';\nimport { markdownToHTML } from './markdown-parser';\nimport { globalConfig } from '../../global/config';\nimport { CustomElementDefinition } from '../../global/shared-types/custom-element.types';\n\n/**\n * The Markdown component receives markdown syntax\n * and renders it as HTML.\n *\n * @exampleComponent limel-example-markdown-headings\n * @exampleComponent limel-example-markdown-emphasis\n * @exampleComponent limel-example-markdown-lists\n * @exampleComponent limel-example-markdown-links\n * @exampleComponent limel-example-markdown-images\n * @exampleComponent limel-example-markdown-code\n * @exampleComponent limel-example-markdown-footnotes\n * @exampleComponent limel-example-markdown-tables\n * @exampleComponent limel-example-markdown-html\n * @exampleComponent limel-example-markdown-keys\n * @exampleComponent limel-example-markdown-blockquotes\n * @exampleComponent limel-example-markdown-horizontal-rule\n * @exampleComponent limel-example-markdown-composite\n * @exampleComponent limel-example-markdown-custom-component\n */\n@Component({\n tag: 'limel-markdown',\n styleUrl: 'markdown.scss',\n shadow: true,\n})\nexport class Markdown {\n /**\n * The input text. Treated as GitHub Flavored Markdown, with the addition\n * that any included HTML will be parsed and rendered as HTML, rather than\n * as text.\n */\n @Prop()\n public value: string;\n\n /**\n * Whitelisted html elements.\n *\n * Any custom element added here will not be sanitized and thus rendered.\n * Can also be set via `limel-config`. Setting this property will override\n * the global config.\n * @alpha\n */\n @Prop()\n public whitelist?: CustomElementDefinition[] =\n globalConfig.markdownWhitelist;\n\n @Watch('value')\n public async textChanged() {\n try {\n const html = await markdownToHTML(this.value, {\n forceHardLineBreaks: true,\n whitelist: this.whitelist ?? [],\n });\n this.rootElement.innerHTML = html;\n } catch (error) {\n // eslint-disable-next-line no-console\n console.error(error);\n }\n }\n\n public async componentDidLoad() {\n this.textChanged();\n }\n\n private rootElement: HTMLDivElement;\n\n public render() {\n return [\n <div\n id=\"markdown\"\n ref={(el) => (this.rootElement = el as HTMLDivElement)}\n />,\n ];\n }\n}\n"]}
1
+ {"version":3,"file":"markdown.js","sourceRoot":"","sources":["../../../src/components/markdown/markdown.tsx"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,CAAC,EAAE,IAAI,EAAE,KAAK,EAAE,MAAM,eAAe,CAAC;AAC1D,OAAO,EAAE,cAAc,EAAE,MAAM,mBAAmB,CAAC;AACnD,OAAO,EAAE,YAAY,EAAE,MAAM,qBAAqB,CAAC;AAEnD,OAAO,EAAE,yBAAyB,EAAE,MAAM,+BAA+B,CAAC;AAE1E;;;;;;;;;;;;;;;;;;GAkBG;AAMH,MAAM,OAAO,QAAQ;;IAgDT,8BAAyB,GAAqC,IAAI,CAAC;;qBA7BvE,YAAY,CAAC,iBAAiB;0BAMV,KAAK;;EAGtB,KAAK,CAAC,WAAW;;IACpB,IAAI;MACA,IAAI,CAAC,gCAAgC,EAAE,CAAC;MAExC,MAAM,IAAI,GAAG,MAAM,cAAc,CAAC,IAAI,CAAC,KAAK,EAAE;QAC1C,mBAAmB,EAAE,IAAI;QACzB,SAAS,EAAE,MAAA,IAAI,CAAC,SAAS,mCAAI,EAAE;QAC/B,cAAc,EAAE,IAAI,CAAC,cAAc;OACtC,CAAC,CAAC;MAEH,IAAI,CAAC,WAAW,CAAC,SAAS,GAAG,IAAI,CAAC;MAElC,IAAI,CAAC,8BAA8B,EAAE,CAAC;KACzC;IAAC,OAAO,KAAK,EAAE;MACZ,sCAAsC;MACtC,OAAO,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC;KACxB;EACL,CAAC;EAKM,KAAK,CAAC,gBAAgB;IACzB,IAAI,CAAC,WAAW,EAAE,CAAC;EACvB,CAAC;EAEM,oBAAoB;IACvB,IAAI,CAAC,gCAAgC,EAAE,CAAC;EAC5C,CAAC;EAEM,MAAM;IACT,OAAO;MACH,WACI,EAAE,EAAC,UAAU,EACb,GAAG,EAAE,CAAC,EAAE,EAAE,EAAE,CAAC,CAAC,IAAI,CAAC,WAAW,GAAG,EAAoB,CAAC,GACxD;KACL,CAAC;EACN,CAAC;EAEO,8BAA8B;IAClC,IAAI,IAAI,CAAC,cAAc,EAAE;MACrB,IAAI,CAAC,yBAAyB,GAAG,IAAI,yBAAyB,CAC1D,IAAI,CAAC,WAAW,CACnB,CAAC;KACL;EACL,CAAC;EAEO,gCAAgC;IACpC,IAAI,IAAI,CAAC,yBAAyB,EAAE;MAChC,IAAI,CAAC,yBAAyB,CAAC,UAAU,EAAE,CAAC;MAC5C,IAAI,CAAC,yBAAyB,GAAG,IAAI,CAAC;KACzC;EACL,CAAC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CACJ","sourcesContent":["import { Component, h, Prop, Watch } from '@stencil/core';\nimport { markdownToHTML } from './markdown-parser';\nimport { globalConfig } from '../../global/config';\nimport { CustomElementDefinition } from '../../global/shared-types/custom-element.types';\nimport { ImageIntersectionObserver } from './image-intersection-observer';\n\n/**\n * The Markdown component receives markdown syntax\n * and renders it as HTML.\n *\n * @exampleComponent limel-example-markdown-headings\n * @exampleComponent limel-example-markdown-emphasis\n * @exampleComponent limel-example-markdown-lists\n * @exampleComponent limel-example-markdown-links\n * @exampleComponent limel-example-markdown-images\n * @exampleComponent limel-example-markdown-code\n * @exampleComponent limel-example-markdown-footnotes\n * @exampleComponent limel-example-markdown-tables\n * @exampleComponent limel-example-markdown-html\n * @exampleComponent limel-example-markdown-keys\n * @exampleComponent limel-example-markdown-blockquotes\n * @exampleComponent limel-example-markdown-horizontal-rule\n * @exampleComponent limel-example-markdown-composite\n * @exampleComponent limel-example-markdown-custom-component\n */\n@Component({\n tag: 'limel-markdown',\n styleUrl: 'markdown.scss',\n shadow: true,\n})\nexport class Markdown {\n /**\n * The input text. Treated as GitHub Flavored Markdown, with the addition\n * that any included HTML will be parsed and rendered as HTML, rather than\n * as text.\n */\n @Prop()\n public value: string;\n\n /**\n * Whitelisted html elements.\n *\n * Any custom element added here will not be sanitized and thus rendered.\n * Can also be set via `limel-config`. Setting this property will override\n * the global config.\n * @alpha\n */\n @Prop()\n public whitelist?: CustomElementDefinition[] =\n globalConfig.markdownWhitelist;\n\n /**\n * Enable lazy loading for images\n */\n @Prop()\n public lazyLoadImages = false;\n\n @Watch('value')\n public async textChanged() {\n try {\n this.cleanupImageIntersectionObserver();\n\n const html = await markdownToHTML(this.value, {\n forceHardLineBreaks: true,\n whitelist: this.whitelist ?? [],\n lazyLoadImages: this.lazyLoadImages,\n });\n\n this.rootElement.innerHTML = html;\n\n this.setupImageIntersectionObserver();\n } catch (error) {\n // eslint-disable-next-line no-console\n console.error(error);\n }\n }\n\n private rootElement: HTMLDivElement;\n private imageIntersectionObserver: ImageIntersectionObserver | null = null;\n\n public async componentDidLoad() {\n this.textChanged();\n }\n\n public disconnectedCallback() {\n this.cleanupImageIntersectionObserver();\n }\n\n public render() {\n return [\n <div\n id=\"markdown\"\n ref={(el) => (this.rootElement = el as HTMLDivElement)}\n />,\n ];\n }\n\n private setupImageIntersectionObserver() {\n if (this.lazyLoadImages) {\n this.imageIntersectionObserver = new ImageIntersectionObserver(\n this.rootElement,\n );\n }\n }\n\n private cleanupImageIntersectionObserver() {\n if (this.imageIntersectionObserver) {\n this.imageIntersectionObserver.disconnect();\n this.imageIntersectionObserver = null;\n }\n }\n}\n"]}
@@ -132,9 +132,29 @@ const processPasteEvent = (view, event, slice) => {
132
132
  if (!clipboardData) {
133
133
  return false;
134
134
  }
135
+ const isImageFilePasted = handlePastedImages(view, clipboardData);
136
+ const filteredSlice = new Slice(filterImageNodes(slice.content), slice.openStart, slice.openEnd);
137
+ if (filteredSlice.content.childCount < slice.content.childCount) {
138
+ const { state, dispatch } = view;
139
+ const tr = state.tr.replaceSelection(filteredSlice);
140
+ dispatch(tr);
141
+ return true;
142
+ }
143
+ return isImageFilePasted;
144
+ };
145
+ /**
146
+ * Processes any image files found in the clipboard data and dispatches an imagePasted event.
147
+ *
148
+ * @param view - The ProseMirror editor view
149
+ * @param clipboardData - The clipboard data transfer object containing potential image files
150
+ * @returns True if at least one valid image file was found and processed, false otherwise
151
+ */
152
+ function handlePastedImages(view, clipboardData) {
153
+ let isImageFilePasted = false;
135
154
  const files = Array.from(clipboardData.files || []);
136
155
  for (const file of files) {
137
- if (file.type.startsWith('image/')) {
156
+ if (isImageFile(file, clipboardData)) {
157
+ isImageFilePasted = true;
138
158
  const reader = new FileReader();
139
159
  reader.onloadend = () => {
140
160
  view.dom.dispatchEvent(new CustomEvent('imagePasted', {
@@ -144,13 +164,43 @@ const processPasteEvent = (view, event, slice) => {
144
164
  reader.readAsDataURL(file);
145
165
  }
146
166
  }
147
- const filteredSlice = new Slice(filterImageNodes(slice.content), slice.openStart, slice.openEnd);
148
- if (filteredSlice.content.childCount < slice.content.childCount) {
149
- const { state, dispatch } = view;
150
- const tr = state.tr.replaceSelection(filteredSlice);
151
- dispatch(tr);
152
- return true;
167
+ return isImageFilePasted;
168
+ }
169
+ /**
170
+ * Determines if a file is an image that should be processed by the image handler.
171
+ *
172
+ * This function checks both the file's MIME type and the clipboard HTML content.
173
+ * It filters out HTML content from Excel and HTML tables, as they are not relevant for image processing.
174
+ *
175
+ * @param file - The file object to check
176
+ * @param clipboardData - The full clipboard data transfer object to examine for context
177
+ * @returns True if the file is an image that should be processed, false otherwise
178
+ */
179
+ function isImageFile(file, clipboardData) {
180
+ var _a, _b;
181
+ if (!isContentTypeImage(file)) {
182
+ return false;
153
183
  }
154
- return files.length > 0;
155
- };
184
+ 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 : '';
185
+ return !isHtmlFromExcel(html) && !isHtmlTable(html);
186
+ }
187
+ function isContentTypeImage(file) {
188
+ if (!(file === null || file === void 0 ? void 0 : file.type)) {
189
+ return false;
190
+ }
191
+ return file.type.startsWith('image/');
192
+ }
193
+ function isHtmlFromExcel(html) {
194
+ if (!html) {
195
+ return false;
196
+ }
197
+ return (html.includes('name=generator content="microsoft excel"') ||
198
+ html.includes('xmlns:x="urn:schemas-microsoft-com:office:excel"'));
199
+ }
200
+ function isHtmlTable(html) {
201
+ if (!html) {
202
+ return false;
203
+ }
204
+ return html.includes('<table');
205
+ }
156
206
  //# sourceMappingURL=inserter.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"inserter.js","sourceRoot":"","sources":["../../../../../../src/components/text-editor/prosemirror-adapter/plugins/image/inserter.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,mBAAmB,CAAC;AAEtD,OAAO,EAAE,cAAc,EAAE,MAAM,2BAA2B,CAAC;AAG3D,OAAO,EAAE,IAAI,EAAE,KAAK,EAAE,QAAQ,EAAE,MAAM,mBAAmB,CAAC;AAG1D,MAAM,CAAC,MAAM,SAAS,GAAG,IAAI,SAAS,CAAC,qBAAqB,CAAC,CAAC;AAI9D,MAAM,CAAC,MAAM,yBAAyB,GAAG,CACrC,mBAAwC,EAC1C,EAAE;EACA,OAAO,IAAI,MAAM,CAAC;IACd,GAAG,EAAE,SAAS;IACd,KAAK,EAAE;MACH,WAAW,EAAE,CAAC,IAAI,EAAE,KAAK,EAAE,KAAK,EAAE,EAAE;QAChC,OAAO,iBAAiB,CAAC,IAAI,EAAE,KAAK,EAAE,KAAK,CAAC,CAAC;MACjD,CAAC;MACD,eAAe,EAAE;QACb,WAAW,EAAE,CAAC,CAAC,EAAE,KAAK,EAAE,EAAE;UACtB,mBAAmB,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC;QACtC,CAAC;OACJ;KACJ;GACJ,CAAC,CAAC;AACP,CAAC,CAAC;AAEF,MAAM,CAAC,MAAM,oBAAoB,GAAG,CAChC,IAAgB,EAChB,UAAkB,EAClB,QAAkB,EACL,EAAE;EACf,OAAO;IACH,QAAQ,EAAE,QAAQ;IAClB,eAAe,EAAE,uBAAuB,CAAC,IAAI,EAAE,UAAU,EAAE,QAAQ,CAAC;IACpE,WAAW,EAAE,mBAAmB,CAAC,IAAI,EAAE,QAAQ,CAAC;IAChD,qBAAqB,EAAE,6BAA6B,CAAC,IAAI,EAAE,QAAQ,CAAC;GACvE,CAAC;AACN,CAAC,CAAC;AAEF,MAAM,uBAAuB,GACzB,CAAC,IAAgB,EAAE,UAAkB,EAAE,QAAkB,EAAE,EAAE,CAAC,GAAG,EAAE;EAC/D,MAAM,EAAE,KAAK,EAAE,QAAQ,EAAE,GAAG,IAAI,CAAC;EACjC,MAAM,EAAE,MAAM,EAAE,GAAG,KAAK,CAAC;EAEzB,MAAM,cAAc,GAAG,oBAAoB,CACvC,UAAU,EACV,QAAQ,EACR,SAAS,CACZ,CAAC;EACF,MAAM,eAAe,GAAG,MAAM,CAAC,KAAK,CAAC,KAAK,CAAC,MAAM,CAAC,cAAc,CAAC,CAAC;EAElE,MAAM,WAAW,GAAG,KAAK,CAAC,EAAE,CAAC,oBAAoB,CAAC,eAAe,CAAC,CAAC;EAEnE,QAAQ,CAAC,WAAW,CAAC,CAAC;AAC1B,CAAC,CAAC;AAEN,MAAM,mBAAmB,GACrB,CAAC,IAAgB,EAAE,QAAkB,EAAE,EAAE,CAAC,CAAC,GAAY,EAAE,EAAE;EACvD,MAAM,EAAE,KAAK,EAAE,QAAQ,EAAE,GAAG,IAAI,CAAC;EACjC,MAAM,EAAE,MAAM,EAAE,GAAG,KAAK,CAAC;EAEzB,MAAM,EAAE,GAAG,KAAK,CAAC,EAAE,CAAC;EACpB,KAAK,CAAC,GAAG,CAAC,WAAW,CAAC,CAAC,IAAI,EAAE,GAAG,EAAE,EAAE;IAChC,IAAI,IAAI,CAAC,KAAK,CAAC,UAAU,KAAK,QAAQ,CAAC,EAAE,EAAE;MACvC,MAAM,cAAc,GAAG,oBAAoB,CACvC,GAAG,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,GAAG,EAC1B,QAAQ,EACR,SAAS,CACZ,CAAC;MACF,MAAM,SAAS,GAAG,MAAM,CAAC,KAAK,CAAC,KAAK,CAAC,MAAM,CAAC,cAAc,CAAC,CAAC;MAE5D,EAAE,CAAC,WAAW,CAAC,GAAG,EAAE,GAAG,GAAG,IAAI,CAAC,QAAQ,EAAE,SAAS,CAAC,CAAC;MAEpD,OAAO,KAAK,CAAC;KAChB;EACL,CAAC,CAAC,CAAC;EAEH,QAAQ,CAAC,EAAE,CAAC,CAAC;AACjB,CAAC,CAAC;AAEN,MAAM,6BAA6B,GAC/B,CAAC,IAAgB,EAAE,QAAkB,EAAE,EAAE,CAAC,GAAG,EAAE;EAC3C,MAAM,EAAE,KAAK,EAAE,QAAQ,EAAE,GAAG,IAAI,CAAC;EACjC,MAAM,EAAE,MAAM,EAAE,GAAG,KAAK,CAAC;EAEzB,MAAM,EAAE,GAAG,KAAK,CAAC,EAAE,CAAC;EACpB,KAAK,CAAC,GAAG,CAAC,WAAW,CAAC,CAAC,IAAI,EAAE,GAAG,EAAE,EAAE;IAChC,IAAI,IAAI,CAAC,KAAK,CAAC,UAAU,KAAK,QAAQ,CAAC,EAAE,EAAE;MACvC,MAAM,cAAc,GAAG,oBAAoB,CACvC,IAAI,CAAC,KAAK,CAAC,GAAG,EACd,QAAQ,EACR,QAAQ,CACX,CAAC;MACF,MAAM,oBAAoB,GACtB,MAAM,CAAC,KAAK,CAAC,KAAK,CAAC,MAAM,CAAC,cAAc,CAAC,CAAC;MAE9C,EAAE,CAAC,WAAW,CAAC,GAAG,EAAE,GAAG,GAAG,IAAI,CAAC,QAAQ,EAAE,oBAAoB,CAAC,CAAC;MAE/D,OAAO,KAAK,CAAC;KAChB;EACL,CAAC,CAAC,CAAC;EAEH,QAAQ,CAAC,EAAE,CAAC,CAAC;AACjB,CAAC,CAAC;AAEN,SAAS,oBAAoB,CACzB,GAAW,EACX,QAAkB,EAClB,KAAuB;EAEvB,OAAO;IACH,GAAG,EAAE,GAAG;IACR,GAAG,EAAE,QAAQ,CAAC,QAAQ;IACtB,UAAU,EAAE,QAAQ,CAAC,EAAE;IACvB,KAAK,EAAE,KAAK;GACf,CAAC;AACN,CAAC;AAED;;;;GAIG;AACH,MAAM,WAAW,GAAG,CAAC,IAAqB,EAAW,EAAE;EACnD,IAAI,IAAI,YAAY,IAAI,EAAE;IACtB,IAAI,IAAI,CAAC,IAAI,CAAC,IAAI,KAAK,OAAO,EAAE;MAC5B,OAAO,IAAI,CAAC;KACf;IAED,IAAI,KAAK,GAAG,KAAK,CAAC;IAClB,IAAI,CAAC,OAAO,CAAC,OAAO,CAAC,CAAC,KAAK,EAAE,EAAE;MAC3B,IAAI,WAAW,CAAC,KAAK,CAAC,EAAE;QACpB,KAAK,GAAG,IAAI,CAAC;OAChB;IACL,CAAC,CAAC,CAAC;IAEH,OAAO,KAAK,CAAC;GAChB;OAAM,IAAI,IAAI,YAAY,QAAQ,EAAE;IACjC,IAAI,KAAK,GAAG,KAAK,CAAC;IAClB,IAAI,CAAC,OAAO,CAAC,CAAC,KAAK,EAAE,EAAE;MACnB,IAAI,WAAW,CAAC,KAAK,CAAC,EAAE;QACpB,KAAK,GAAG,IAAI,CAAC;OAChB;IACL,CAAC,CAAC,CAAC;IAEH,OAAO,KAAK,CAAC;GAChB;EAED,OAAO,KAAK,CAAC;AACjB,CAAC,CAAC;AAEF;;;;GAIG;AACH,MAAM,gBAAgB,GAAG,CAAC,QAAkB,EAAY,EAAE;EACtD,MAAM,gBAAgB,GAAW,EAAE,CAAC;EAEpC,QAAQ,CAAC,OAAO,CAAC,CAAC,KAAK,EAAE,EAAE;IACvB,IAAI,CAAC,WAAW,CAAC,KAAK,CAAC,EAAE;MACrB,IAAI,KAAK,CAAC,OAAO,CAAC,IAAI,GAAG,CAAC,EAAE;QACxB,MAAM,eAAe,GAAG,gBAAgB,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC;QACxD,MAAM,OAAO,GAAG,KAAK,CAAC,IAAI,CAAC,eAAe,CAAC,CAAC;QAC5C,gBAAgB,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;OAClC;WAAM;QACH,gBAAgB,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;OAChC;KACJ;EACL,CAAC,CAAC,CAAC;EAEH,OAAO,QAAQ,CAAC,SAAS,CAAC,gBAAgB,CAAC,CAAC;AAChD,CAAC,CAAC;AAEF;;;;;;;GAOG;AACH,MAAM,iBAAiB,GAAG,CACtB,IAAgB,EAChB,KAAqB,EACrB,KAAY,EACL,EAAE;EACT,MAAM,aAAa,GAAG,KAAK,CAAC,aAAa,CAAC;EAC1C,IAAI,CAAC,aAAa,EAAE;IAChB,OAAO,KAAK,CAAC;GAChB;EAED,MAAM,KAAK,GAAG,KAAK,CAAC,IAAI,CAAC,aAAa,CAAC,KAAK,IAAI,EAAE,CAAC,CAAC;EACpD,KAAK,MAAM,IAAI,IAAI,KAAK,EAAE;IACtB,IAAI,IAAI,CAAC,IAAI,CAAC,UAAU,CAAC,QAAQ,CAAC,EAAE;MAChC,MAAM,MAAM,GAAG,IAAI,UAAU,EAAE,CAAC;MAChC,MAAM,CAAC,SAAS,GAAG,GAAG,EAAE;QACpB,IAAI,CAAC,GAAG,CAAC,aAAa,CAClB,IAAI,WAAW,CAAC,aAAa,EAAE;UAC3B,MAAM,EAAE,oBAAoB,CACxB,IAAI,EACJ,MAAM,CAAC,MAAgB,EACvB,cAAc,CAAC,IAAI,CAAC,CACvB;SACJ,CAAC,CACL,CAAC;MACN,CAAC,CAAC;MAEF,MAAM,CAAC,aAAa,CAAC,IAAI,CAAC,CAAC;KAC9B;GACJ;EAED,MAAM,aAAa,GAAG,IAAI,KAAK,CAC3B,gBAAgB,CAAC,KAAK,CAAC,OAAO,CAAC,EAC/B,KAAK,CAAC,SAAS,EACf,KAAK,CAAC,OAAO,CAChB,CAAC;EAEF,IAAI,aAAa,CAAC,OAAO,CAAC,UAAU,GAAG,KAAK,CAAC,OAAO,CAAC,UAAU,EAAE;IAC7D,MAAM,EAAE,KAAK,EAAE,QAAQ,EAAE,GAAG,IAAI,CAAC;IACjC,MAAM,EAAE,GAAG,KAAK,CAAC,EAAE,CAAC,gBAAgB,CAAC,aAAa,CAAC,CAAC;IACpD,QAAQ,CAAC,EAAE,CAAC,CAAC;IAEb,OAAO,IAAI,CAAC;GACf;EAED,OAAO,KAAK,CAAC,MAAM,GAAG,CAAC,CAAC;AAC5B,CAAC,CAAC","sourcesContent":["import { Plugin, PluginKey } from 'prosemirror-state';\nimport { EditorView } from 'prosemirror-view';\nimport { createFileInfo } from '../../../../../util/files';\nimport { FileInfo } from '../../../../../global/shared-types/file.types';\nimport { ImageInserter, EditorImageState } from '../../../text-editor.types';\nimport { Node, Slice, Fragment } from 'prosemirror-model';\nimport { ImageNodeAttrs } from './node';\n\nexport const pluginKey = new PluginKey('imageInserterPlugin');\n\ntype ImagePastedCallback = (data: ImageInserter) => CustomEvent<ImageInserter>;\n\nexport const createImageInserterPlugin = (\n imagePastedCallback: ImagePastedCallback,\n) => {\n return new Plugin({\n key: pluginKey,\n props: {\n handlePaste: (view, event, slice) => {\n return processPasteEvent(view, event, slice);\n },\n handleDOMEvents: {\n imagePasted: (_, event) => {\n imagePastedCallback(event.detail);\n },\n },\n },\n });\n};\n\nexport const imageInserterFactory = (\n view: EditorView,\n base64Data: string,\n fileInfo: FileInfo,\n): ImageInserter => {\n return {\n fileInfo: fileInfo,\n insertThumbnail: createThumbnailInserter(view, base64Data, fileInfo),\n insertImage: createImageInserter(view, fileInfo),\n insertFailedThumbnail: createFailedThumbnailInserter(view, fileInfo),\n };\n};\n\nconst createThumbnailInserter =\n (view: EditorView, base64Data: string, fileInfo: FileInfo) => () => {\n const { state, dispatch } = view;\n const { schema } = state;\n\n const imageNodeAttrs = createImageNodeAttrs(\n base64Data,\n fileInfo,\n 'loading',\n );\n const placeholderNode = schema.nodes.image.create(imageNodeAttrs);\n\n const transaction = state.tr.replaceSelectionWith(placeholderNode);\n\n dispatch(transaction);\n };\n\nconst createImageInserter =\n (view: EditorView, fileInfo: FileInfo) => (src?: string) => {\n const { state, dispatch } = view;\n const { schema } = state;\n\n const tr = state.tr;\n state.doc.descendants((node, pos) => {\n if (node.attrs.fileInfoId === fileInfo.id) {\n const imageNodeAttrs = createImageNodeAttrs(\n src ? src : node.attrs.src,\n fileInfo,\n 'success',\n );\n const imageNode = schema.nodes.image.create(imageNodeAttrs);\n\n tr.replaceWith(pos, pos + node.nodeSize, imageNode);\n\n return false;\n }\n });\n\n dispatch(tr);\n };\n\nconst createFailedThumbnailInserter =\n (view: EditorView, fileInfo: FileInfo) => () => {\n const { state, dispatch } = view;\n const { schema } = state;\n\n const tr = state.tr;\n state.doc.descendants((node, pos) => {\n if (node.attrs.fileInfoId === fileInfo.id) {\n const imageNodeAttrs = createImageNodeAttrs(\n node.attrs.src,\n fileInfo,\n 'failed',\n );\n const errorPlaceholderNode =\n schema.nodes.image.create(imageNodeAttrs);\n\n tr.replaceWith(pos, pos + node.nodeSize, errorPlaceholderNode);\n\n return false;\n }\n });\n\n dispatch(tr);\n };\n\nfunction createImageNodeAttrs(\n src: string,\n fileInfo: FileInfo,\n state: EditorImageState,\n): ImageNodeAttrs {\n return {\n src: src,\n alt: fileInfo.filename,\n fileInfoId: fileInfo.id,\n state: state,\n };\n}\n\n/**\n * Check if a given ProseMirror node or fragment contains any image nodes.\n * @param node - The ProseMirror node or fragment to check.\n * @returns A boolean indicating whether the node contains any image nodes.\n */\nconst isImageNode = (node: Node | Fragment): boolean => {\n if (node instanceof Node) {\n if (node.type.name === 'image') {\n return true;\n }\n\n let found = false;\n node.content.forEach((child) => {\n if (isImageNode(child)) {\n found = true;\n }\n });\n\n return found;\n } else if (node instanceof Fragment) {\n let found = false;\n node.forEach((child) => {\n if (isImageNode(child)) {\n found = true;\n }\n });\n\n return found;\n }\n\n return false;\n};\n\n/**\n * Filter out image nodes from a ProseMirror fragment.\n * @param fragment - The ProseMirror fragment to filter.\n * @returns A new fragment with image nodes removed.\n */\nconst filterImageNodes = (fragment: Fragment): Fragment => {\n const filteredChildren: Node[] = [];\n\n fragment.forEach((child) => {\n if (!isImageNode(child)) {\n if (child.content.size > 0) {\n const filteredContent = filterImageNodes(child.content);\n const newNode = child.copy(filteredContent);\n filteredChildren.push(newNode);\n } else {\n filteredChildren.push(child);\n }\n }\n });\n\n return Fragment.fromArray(filteredChildren);\n};\n\n/**\n * Process a paste event and trigger an imagePasted event if an image file is pasted.\n * If an HTML image element is pasted, this image is filtered out from the slice content.\n *\n * @param view - The ProseMirror editor view.\n * @param event - The paste event.\n * @returns A boolean; True if an image file was pasted to prevent default paste behavior, otherwise false.\n */\nconst processPasteEvent = (\n view: EditorView,\n event: ClipboardEvent,\n slice: Slice,\n): boolean => {\n const clipboardData = event.clipboardData;\n if (!clipboardData) {\n return false;\n }\n\n const files = Array.from(clipboardData.files || []);\n for (const file of files) {\n if (file.type.startsWith('image/')) {\n const reader = new FileReader();\n reader.onloadend = () => {\n view.dom.dispatchEvent(\n new CustomEvent('imagePasted', {\n detail: imageInserterFactory(\n view,\n reader.result as string,\n createFileInfo(file),\n ),\n }),\n );\n };\n\n reader.readAsDataURL(file);\n }\n }\n\n const filteredSlice = new Slice(\n filterImageNodes(slice.content),\n slice.openStart,\n slice.openEnd,\n );\n\n if (filteredSlice.content.childCount < slice.content.childCount) {\n const { state, dispatch } = view;\n const tr = state.tr.replaceSelection(filteredSlice);\n dispatch(tr);\n\n return true;\n }\n\n return files.length > 0;\n};\n"]}
1
+ {"version":3,"file":"inserter.js","sourceRoot":"","sources":["../../../../../../src/components/text-editor/prosemirror-adapter/plugins/image/inserter.ts"],"names":[],"mappings":"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","sourcesContent":["import { Plugin, PluginKey } from 'prosemirror-state';\nimport { EditorView } from 'prosemirror-view';\nimport { createFileInfo } from '../../../../../util/files';\nimport { FileInfo } from '../../../../../global/shared-types/file.types';\nimport { ImageInserter, EditorImageState } from '../../../text-editor.types';\nimport { Node, Slice, Fragment } from 'prosemirror-model';\nimport { ImageNodeAttrs } from './node';\n\nexport const pluginKey = new PluginKey('imageInserterPlugin');\n\ntype ImagePastedCallback = (data: ImageInserter) => CustomEvent<ImageInserter>;\n\nexport const createImageInserterPlugin = (\n imagePastedCallback: ImagePastedCallback,\n) => {\n return new Plugin({\n key: pluginKey,\n props: {\n handlePaste: (view, event, slice) => {\n return processPasteEvent(view, event, slice);\n },\n handleDOMEvents: {\n imagePasted: (_, event) => {\n imagePastedCallback(event.detail);\n },\n },\n },\n });\n};\n\nexport const imageInserterFactory = (\n view: EditorView,\n base64Data: string,\n fileInfo: FileInfo,\n): ImageInserter => {\n return {\n fileInfo: fileInfo,\n insertThumbnail: createThumbnailInserter(view, base64Data, fileInfo),\n insertImage: createImageInserter(view, fileInfo),\n insertFailedThumbnail: createFailedThumbnailInserter(view, fileInfo),\n };\n};\n\nconst createThumbnailInserter =\n (view: EditorView, base64Data: string, fileInfo: FileInfo) => () => {\n const { state, dispatch } = view;\n const { schema } = state;\n\n const imageNodeAttrs = createImageNodeAttrs(\n base64Data,\n fileInfo,\n 'loading',\n );\n const placeholderNode = schema.nodes.image.create(imageNodeAttrs);\n\n const transaction = state.tr.replaceSelectionWith(placeholderNode);\n\n dispatch(transaction);\n };\n\nconst createImageInserter =\n (view: EditorView, fileInfo: FileInfo) => (src?: string) => {\n const { state, dispatch } = view;\n const { schema } = state;\n\n const tr = state.tr;\n state.doc.descendants((node, pos) => {\n if (node.attrs.fileInfoId === fileInfo.id) {\n const imageNodeAttrs = createImageNodeAttrs(\n src ? src : node.attrs.src,\n fileInfo,\n 'success',\n );\n const imageNode = schema.nodes.image.create(imageNodeAttrs);\n\n tr.replaceWith(pos, pos + node.nodeSize, imageNode);\n\n return false;\n }\n });\n\n dispatch(tr);\n };\n\nconst createFailedThumbnailInserter =\n (view: EditorView, fileInfo: FileInfo) => () => {\n const { state, dispatch } = view;\n const { schema } = state;\n\n const tr = state.tr;\n state.doc.descendants((node, pos) => {\n if (node.attrs.fileInfoId === fileInfo.id) {\n const imageNodeAttrs = createImageNodeAttrs(\n node.attrs.src,\n fileInfo,\n 'failed',\n );\n const errorPlaceholderNode =\n schema.nodes.image.create(imageNodeAttrs);\n\n tr.replaceWith(pos, pos + node.nodeSize, errorPlaceholderNode);\n\n return false;\n }\n });\n\n dispatch(tr);\n };\n\nfunction createImageNodeAttrs(\n src: string,\n fileInfo: FileInfo,\n state: EditorImageState,\n): ImageNodeAttrs {\n return {\n src: src,\n alt: fileInfo.filename,\n fileInfoId: fileInfo.id,\n state: state,\n };\n}\n\n/**\n * Check if a given ProseMirror node or fragment contains any image nodes.\n * @param node - The ProseMirror node or fragment to check.\n * @returns A boolean indicating whether the node contains any image nodes.\n */\nconst isImageNode = (node: Node | Fragment): boolean => {\n if (node instanceof Node) {\n if (node.type.name === 'image') {\n return true;\n }\n\n let found = false;\n node.content.forEach((child) => {\n if (isImageNode(child)) {\n found = true;\n }\n });\n\n return found;\n } else if (node instanceof Fragment) {\n let found = false;\n node.forEach((child) => {\n if (isImageNode(child)) {\n found = true;\n }\n });\n\n return found;\n }\n\n return false;\n};\n\n/**\n * Filter out image nodes from a ProseMirror fragment.\n * @param fragment - The ProseMirror fragment to filter.\n * @returns A new fragment with image nodes removed.\n */\nconst filterImageNodes = (fragment: Fragment): Fragment => {\n const filteredChildren: Node[] = [];\n\n fragment.forEach((child) => {\n if (!isImageNode(child)) {\n if (child.content.size > 0) {\n const filteredContent = filterImageNodes(child.content);\n const newNode = child.copy(filteredContent);\n filteredChildren.push(newNode);\n } else {\n filteredChildren.push(child);\n }\n }\n });\n\n return Fragment.fromArray(filteredChildren);\n};\n\n/**\n * Process a paste event and trigger an imagePasted event if an image file is pasted.\n * If an HTML image element is pasted, this image is filtered out from the slice content.\n *\n * @param view - The ProseMirror editor view.\n * @param event - The paste event.\n * @returns A boolean; True if an image file was pasted to prevent default paste behavior, otherwise false.\n */\nconst processPasteEvent = (\n view: EditorView,\n event: ClipboardEvent,\n slice: Slice,\n): boolean => {\n const clipboardData = event.clipboardData;\n if (!clipboardData) {\n return false;\n }\n\n const isImageFilePasted = handlePastedImages(view, clipboardData);\n\n const filteredSlice = new Slice(\n filterImageNodes(slice.content),\n slice.openStart,\n slice.openEnd,\n );\n\n if (filteredSlice.content.childCount < slice.content.childCount) {\n const { state, dispatch } = view;\n const tr = state.tr.replaceSelection(filteredSlice);\n dispatch(tr);\n\n return true;\n }\n\n return isImageFilePasted;\n};\n\n/**\n * Processes any image files found in the clipboard data and dispatches an imagePasted event.\n *\n * @param view - The ProseMirror editor view\n * @param clipboardData - The clipboard data transfer object containing potential image files\n * @returns True if at least one valid image file was found and processed, false otherwise\n */\nfunction handlePastedImages(\n view: EditorView,\n clipboardData: DataTransfer,\n): boolean {\n let isImageFilePasted = false;\n const files = Array.from(clipboardData.files || []);\n\n for (const file of files) {\n if (isImageFile(file, clipboardData)) {\n isImageFilePasted = true;\n\n const reader = new FileReader();\n reader.onloadend = () => {\n view.dom.dispatchEvent(\n new CustomEvent('imagePasted', {\n detail: imageInserterFactory(\n view,\n reader.result as string,\n createFileInfo(file),\n ),\n }),\n );\n };\n\n reader.readAsDataURL(file);\n }\n }\n\n return isImageFilePasted;\n}\n\n/**\n * Determines if a file is an image that should be processed by the image handler.\n *\n * This function checks both the file's MIME type and the clipboard HTML content.\n * It filters out HTML content from Excel and HTML tables, as they are not relevant for image processing.\n *\n * @param file - The file object to check\n * @param clipboardData - The full clipboard data transfer object to examine for context\n * @returns True if the file is an image that should be processed, false otherwise\n */\nfunction isImageFile(file: File, clipboardData: DataTransfer): boolean {\n if (!isContentTypeImage(file)) {\n return false;\n }\n\n const html = clipboardData?.getData('text/html')?.toLowerCase() ?? '';\n\n return !isHtmlFromExcel(html) && !isHtmlTable(html);\n}\n\nfunction isContentTypeImage(file: File): boolean {\n if (!file?.type) {\n return false;\n }\n\n return file.type.startsWith('image/');\n}\n\nfunction isHtmlFromExcel(html: string): boolean {\n if (!html) {\n return false;\n }\n\n return (\n html.includes('name=generator content=\"microsoft excel\"') ||\n html.includes('xmlns:x=\"urn:schemas-microsoft-com:office:excel\"')\n );\n}\n\nfunction isHtmlTable(html: string): boolean {\n if (!html) {\n return false;\n }\n\n return html.includes('<table');\n}\n"]}
@@ -6,6 +6,13 @@ export function getImageNode(language) {
6
6
  export function getImageNodeMarkdownSerializer(language) {
7
7
  return { image: createImageNodeMarkdownSerializer(language) };
8
8
  }
9
+ export function applyImageStyles(img, node) {
10
+ img.style.height = node.attrs.height;
11
+ img.style.width = node.attrs.width;
12
+ img.style.minHeight = node.attrs.minHeight;
13
+ img.style.minWidth = node.attrs.minWidth;
14
+ img.style.maxWidth = node.attrs.maxWidth;
15
+ }
9
16
  /**
10
17
  * Recursively checks if a ProseMirror node or
11
18
  * any of its child nodes is an image node.
@@ -24,16 +31,16 @@ export function hasImageNode(node) {
24
31
  }
25
32
  function createImageNodeMarkdownSerializer(language) {
26
33
  return (markdownSerializerState, node) => {
27
- const { state, alt, src, width, maxWidth } = node.attrs;
34
+ const state = node.attrs.state;
28
35
  if (!isEditorImageState(state)) {
29
36
  return;
30
37
  }
31
38
  if (state === 'success') {
32
- const imageHTML = getImageHTML(src, alt, width, maxWidth);
39
+ const imageHTML = getImageHTML(node.attrs);
33
40
  markdownSerializerState.write(imageHTML);
34
41
  return;
35
42
  }
36
- const statusHTML = getStatusHTML(state, alt, language);
43
+ const statusHTML = getStatusHTML(state, node.attrs.alt, language);
37
44
  markdownSerializerState.write(statusHTML);
38
45
  };
39
46
  }
@@ -44,16 +51,25 @@ function getStatusHTML(state, alt, language) {
44
51
  });
45
52
  return `<span>${text}</span>`;
46
53
  }
47
- function getImageHTML(src, alt, width, maxWidth) {
54
+ function getImageHTML(attrs) {
48
55
  const style = [];
49
- if (width) {
50
- style.push(`width: ${width};`);
56
+ if (attrs.height) {
57
+ style.push(`height: ${attrs.height};`);
58
+ }
59
+ if (attrs.width) {
60
+ style.push(`width: ${attrs.width};`);
61
+ }
62
+ if (attrs.minHeight) {
63
+ style.push(`min-height: ${attrs.minHeight};`);
51
64
  }
52
- if (maxWidth) {
53
- style.push(`max-width: ${maxWidth};`);
65
+ if (attrs.minWidth) {
66
+ style.push(`min-width: ${attrs.minWidth};`);
54
67
  }
55
- const styleAttribute = style.length > 0 ? ` style="${style.join(' ')}"` : '';
56
- return `<img src="${src}" alt="${alt}"${styleAttribute} />`;
68
+ if (attrs.maxWidth) {
69
+ style.push(`max-width: ${attrs.maxWidth};`);
70
+ }
71
+ const styleAttribute = style.length > 0 ? ` style="${style.join('')}"` : '';
72
+ return `<img src="${attrs.src}" alt="${attrs.alt}"${styleAttribute} />`;
57
73
  }
58
74
  function createImageNodeSpec(language) {
59
75
  return {
@@ -63,7 +79,10 @@ function createImageNodeSpec(language) {
63
79
  src: { default: '' },
64
80
  alt: { default: '' },
65
81
  fileInfoId: { default: '' },
82
+ height: { default: '' },
66
83
  width: { default: '' },
84
+ minHeight: { default: '' },
85
+ minWidth: { default: '' },
67
86
  maxWidth: { default: '100%' },
68
87
  state: { default: 'success' },
69
88
  },
@@ -121,16 +140,14 @@ function createStatusSpan(key, node, language) {
121
140
  }
122
141
  function updateImageElement(img, node) {
123
142
  img.alt = node.attrs.alt;
124
- img.style.maxWidth = node.attrs.maxWidth;
125
- img.style.width = node.attrs.width;
143
+ applyImageStyles(img, node);
126
144
  return img;
127
145
  }
128
146
  function createImageElement(node) {
129
147
  const img = document.createElement('img');
130
148
  img.src = node.attrs.src;
131
149
  img.alt = node.attrs.alt;
132
- img.style.maxWidth = node.attrs.maxWidth;
133
- img.style.width = node.attrs.width;
150
+ applyImageStyles(img, node);
134
151
  return img;
135
152
  }
136
153
  //# sourceMappingURL=node.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"node.js","sourceRoot":"","sources":["../../../../../../src/components/text-editor/prosemirror-adapter/plugins/image/node.ts"],"names":[],"mappings":"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","sourcesContent":["import { NodeSpec, Node, DOMOutputSpec } from 'prosemirror-model';\nimport { EditorImageState } from '../../../text-editor.types';\nimport { MarkdownSerializerState } from 'prosemirror-markdown';\nimport { Languages } from '../../../../date-picker/date.types';\nimport translate from '../../../../../global/translations';\n\nexport const imageCache = new Map<string, HTMLImageElement>();\n\ntype MarkdownSerializerFunction = (\n state: MarkdownSerializerState,\n node: Node,\n) => void;\n\nexport function getImageNode(language: Languages): Record<string, NodeSpec> {\n return { image: createImageNodeSpec(language) };\n}\n\nexport function getImageNodeMarkdownSerializer(\n language: Languages,\n): Record<string, MarkdownSerializerFunction> {\n return { image: createImageNodeMarkdownSerializer(language) };\n}\n\n/**\n * Recursively checks if a ProseMirror node or\n * any of its child nodes is an image node.\n */\nexport function hasImageNode(node: Node): boolean {\n if (node.type.name === 'image') {\n return true;\n }\n\n for (let i = 0; i < node.childCount; i++) {\n const childNode = node.child(i);\n if (hasImageNode(childNode)) {\n return true;\n }\n }\n\n return false;\n}\n\nfunction createImageNodeMarkdownSerializer(\n language: Languages,\n): MarkdownSerializerFunction {\n return (markdownSerializerState: MarkdownSerializerState, node: Node) => {\n const { state, alt, src, width, maxWidth } = node.attrs;\n\n if (!isEditorImageState(state)) {\n return;\n }\n\n if (state === 'success') {\n const imageHTML = getImageHTML(src, alt, width, maxWidth);\n markdownSerializerState.write(imageHTML);\n\n return;\n }\n\n const statusHTML = getStatusHTML(state, alt, language);\n markdownSerializerState.write(statusHTML);\n };\n}\n\nfunction getStatusHTML(\n state: EditorImageState,\n alt: string,\n language: Languages,\n): string {\n const key = state === 'failed' ? 'failed' : 'loading';\n const text = translate.get(`editor-image-view.${key}`, language, {\n filename: alt || 'file',\n });\n\n return `<span>${text}</span>`;\n}\n\nfunction getImageHTML(\n src: string,\n alt: string,\n width: string,\n maxWidth: string,\n): string {\n const style = [];\n\n if (width) {\n style.push(`width: ${width};`);\n }\n\n if (maxWidth) {\n style.push(`max-width: ${maxWidth};`);\n }\n\n const styleAttribute =\n style.length > 0 ? ` style=\"${style.join(' ')}\"` : '';\n\n return `<img src=\"${src}\" alt=\"${alt}\"${styleAttribute} />`;\n}\n\nexport interface ImageNodeAttrs {\n src: string;\n alt: string;\n state: EditorImageState;\n fileInfoId: string | number;\n width?: string;\n maxWidth?: string;\n}\n\nfunction createImageNodeSpec(language: Languages): NodeSpec {\n return {\n group: 'inline',\n inline: true,\n attrs: {\n src: { default: '' },\n alt: { default: '' },\n fileInfoId: { default: '' },\n width: { default: '' },\n maxWidth: { default: '100%' },\n state: { default: 'success' },\n },\n toDOM: (node): DOMOutputSpec => {\n if (!isEditorImageState(node.attrs.state)) {\n return;\n }\n\n if (node.attrs.state === 'success') {\n return getOrCreateImageElement(node.attrs.fileInfoId, node);\n }\n\n return createStatusSpanForState(node.attrs.state, node, language);\n },\n parseDOM: [\n {\n tag: 'img',\n getAttrs: (dom: HTMLElement): ImageNodeAttrs => {\n return {\n src: dom.getAttribute('src') || '',\n alt: dom.getAttribute('alt') || 'file',\n width: dom.style.width || '',\n maxWidth: '100%',\n state: 'success',\n fileInfoId: crypto.randomUUID(),\n };\n },\n },\n ],\n };\n}\n\nfunction isEditorImageState(state: unknown): state is EditorImageState {\n return state === 'loading' || state === 'failed' || state === 'success';\n}\n\nfunction getOrCreateImageElement(\n fileInfoId: string,\n node: Node,\n): HTMLImageElement {\n let img = imageCache.get(fileInfoId);\n\n if (img) {\n updateImageElement(img, node);\n } else {\n img = createImageElement(node);\n imageCache.set(fileInfoId, img);\n }\n\n return img;\n}\n\nfunction createStatusSpanForState(\n state: EditorImageState,\n node: Node,\n language: Languages,\n): HTMLSpanElement {\n const statusKey = state === 'failed' ? 'failed' : 'loading';\n\n return createStatusSpan(statusKey, node, language);\n}\n\nfunction createStatusSpan(\n key: string,\n node: Node,\n language: Languages,\n): HTMLSpanElement {\n const text = translate.get(`editor-image-view.${key}`, language, {\n filename: node.attrs.alt || 'file',\n });\n const span = document.createElement('span');\n span.textContent = text;\n\n return span;\n}\n\nfunction updateImageElement(\n img: HTMLImageElement,\n node: Node,\n): HTMLImageElement {\n img.alt = node.attrs.alt;\n img.style.maxWidth = node.attrs.maxWidth;\n img.style.width = node.attrs.width;\n\n return img;\n}\n\nfunction createImageElement(node: Node): HTMLImageElement {\n const img = document.createElement('img');\n img.src = node.attrs.src;\n img.alt = node.attrs.alt;\n img.style.maxWidth = node.attrs.maxWidth;\n img.style.width = node.attrs.width;\n\n return img;\n}\n"]}
1
+ {"version":3,"file":"node.js","sourceRoot":"","sources":["../../../../../../src/components/text-editor/prosemirror-adapter/plugins/image/node.ts"],"names":[],"mappings":"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","sourcesContent":["import { NodeSpec, Node, DOMOutputSpec } from 'prosemirror-model';\nimport { EditorImageState } from '../../../text-editor.types';\nimport { MarkdownSerializerState } from 'prosemirror-markdown';\nimport { Languages } from '../../../../date-picker/date.types';\nimport translate from '../../../../../global/translations';\n\nexport const imageCache = new Map<string, HTMLImageElement>();\n\ntype MarkdownSerializerFunction = (\n state: MarkdownSerializerState,\n node: Node,\n) => void;\n\nexport function getImageNode(language: Languages): Record<string, NodeSpec> {\n return { image: createImageNodeSpec(language) };\n}\n\nexport function getImageNodeMarkdownSerializer(\n language: Languages,\n): Record<string, MarkdownSerializerFunction> {\n return { image: createImageNodeMarkdownSerializer(language) };\n}\n\nexport function applyImageStyles(img: HTMLImageElement, node: Node) {\n img.style.height = node.attrs.height;\n img.style.width = node.attrs.width;\n img.style.minHeight = node.attrs.minHeight;\n img.style.minWidth = node.attrs.minWidth;\n img.style.maxWidth = node.attrs.maxWidth;\n}\n\n/**\n * Recursively checks if a ProseMirror node or\n * any of its child nodes is an image node.\n */\nexport function hasImageNode(node: Node): boolean {\n if (node.type.name === 'image') {\n return true;\n }\n\n for (let i = 0; i < node.childCount; i++) {\n const childNode = node.child(i);\n if (hasImageNode(childNode)) {\n return true;\n }\n }\n\n return false;\n}\n\nfunction createImageNodeMarkdownSerializer(\n language: Languages,\n): MarkdownSerializerFunction {\n return (markdownSerializerState: MarkdownSerializerState, node: Node) => {\n const state = node.attrs.state;\n if (!isEditorImageState(state)) {\n return;\n }\n\n if (state === 'success') {\n const imageHTML = getImageHTML(node.attrs as ImageNodeAttrs);\n markdownSerializerState.write(imageHTML);\n\n return;\n }\n\n const statusHTML = getStatusHTML(state, node.attrs.alt, language);\n markdownSerializerState.write(statusHTML);\n };\n}\n\nfunction getStatusHTML(\n state: EditorImageState,\n alt: string,\n language: Languages,\n): string {\n const key = state === 'failed' ? 'failed' : 'loading';\n const text = translate.get(`editor-image-view.${key}`, language, {\n filename: alt || 'file',\n });\n\n return `<span>${text}</span>`;\n}\n\nfunction getImageHTML(attrs: ImageNodeAttrs): string {\n const style = [];\n\n if (attrs.height) {\n style.push(`height: ${attrs.height};`);\n }\n\n if (attrs.width) {\n style.push(`width: ${attrs.width};`);\n }\n\n if (attrs.minHeight) {\n style.push(`min-height: ${attrs.minHeight};`);\n }\n\n if (attrs.minWidth) {\n style.push(`min-width: ${attrs.minWidth};`);\n }\n\n if (attrs.maxWidth) {\n style.push(`max-width: ${attrs.maxWidth};`);\n }\n\n const styleAttribute = style.length > 0 ? ` style=\"${style.join('')}\"` : '';\n\n return `<img src=\"${attrs.src}\" alt=\"${attrs.alt}\"${styleAttribute} />`;\n}\n\nexport interface ImageNodeAttrs {\n src: string;\n alt: string;\n state: EditorImageState;\n fileInfoId: string | number;\n height?: string;\n width?: string;\n minHeight?: string;\n minWidth?: string;\n maxWidth?: string;\n}\n\nfunction createImageNodeSpec(language: Languages): NodeSpec {\n return {\n group: 'inline',\n inline: true,\n attrs: {\n src: { default: '' },\n alt: { default: '' },\n fileInfoId: { default: '' },\n height: { default: '' },\n width: { default: '' },\n minHeight: { default: '' },\n minWidth: { default: '' },\n maxWidth: { default: '100%' },\n state: { default: 'success' },\n },\n toDOM: (node): DOMOutputSpec => {\n if (!isEditorImageState(node.attrs.state)) {\n return;\n }\n\n if (node.attrs.state === 'success') {\n return getOrCreateImageElement(node.attrs.fileInfoId, node);\n }\n\n return createStatusSpanForState(node.attrs.state, node, language);\n },\n parseDOM: [\n {\n tag: 'img',\n getAttrs: (dom: HTMLElement): ImageNodeAttrs => {\n return {\n src: dom.getAttribute('src') || '',\n alt: dom.getAttribute('alt') || 'file',\n width: dom.style.width || '',\n maxWidth: '100%',\n state: 'success',\n fileInfoId: crypto.randomUUID(),\n };\n },\n },\n ],\n };\n}\n\nfunction isEditorImageState(state: unknown): state is EditorImageState {\n return state === 'loading' || state === 'failed' || state === 'success';\n}\n\nfunction getOrCreateImageElement(\n fileInfoId: string,\n node: Node,\n): HTMLImageElement {\n let img = imageCache.get(fileInfoId);\n\n if (img) {\n updateImageElement(img, node);\n } else {\n img = createImageElement(node);\n imageCache.set(fileInfoId, img);\n }\n\n return img;\n}\n\nfunction createStatusSpanForState(\n state: EditorImageState,\n node: Node,\n language: Languages,\n): HTMLSpanElement {\n const statusKey = state === 'failed' ? 'failed' : 'loading';\n\n return createStatusSpan(statusKey, node, language);\n}\n\nfunction createStatusSpan(\n key: string,\n node: Node,\n language: Languages,\n): HTMLSpanElement {\n const text = translate.get(`editor-image-view.${key}`, language, {\n filename: node.attrs.alt || 'file',\n });\n const span = document.createElement('span');\n span.textContent = text;\n\n return span;\n}\n\nfunction updateImageElement(\n img: HTMLImageElement,\n node: Node,\n): HTMLImageElement {\n img.alt = node.attrs.alt;\n applyImageStyles(img, node);\n\n return img;\n}\n\nfunction createImageElement(node: Node): HTMLImageElement {\n const img = document.createElement('img');\n img.src = node.attrs.src;\n img.alt = node.attrs.alt;\n applyImageStyles(img, node);\n\n return img;\n}\n"]}
@@ -1,5 +1,6 @@
1
1
  import { Plugin } from 'prosemirror-state';
2
2
  import translate from '../../../../../global/translations';
3
+ import { applyImageStyles } from './node';
3
4
  const MIN_WIDTH = 10;
4
5
  export const createImageViewPlugin = (language) => {
5
6
  return new Plugin({
@@ -50,7 +51,7 @@ class ImageView {
50
51
  window.removeEventListener('pointermove', onPointerMove);
51
52
  window.removeEventListener('pointerup', onPointerUp);
52
53
  handle.setAttribute('aria-grabbed', 'false');
53
- this.view.dispatch(this.view.state.tr.setNodeMarkup(this.getPos(), undefined, Object.assign(Object.assign({}, this.node.attrs), { width: this.img.style.width, height: this.node.attrs.height })));
54
+ this.persistDimensions();
54
55
  };
55
56
  window.addEventListener('pointermove', onPointerMove);
56
57
  window.addEventListener('pointerup', onPointerUp);
@@ -115,19 +116,23 @@ class ImageView {
115
116
  this.img = document.createElement('img');
116
117
  this.img.src = node.attrs.src;
117
118
  this.img.alt = node.attrs.alt;
118
- this.img.style.maxWidth = node.attrs.maxWidth;
119
- this.img.style.width = node.attrs.width;
119
+ applyImageStyles(this.img, node);
120
+ this.img.onload = () => {
121
+ this.persistDimensions();
122
+ };
120
123
  this.dom.appendChild(this.img);
121
124
  this.transitionBetweenStates();
122
125
  }
126
+ persistDimensions() {
127
+ this.view.dispatch(this.view.state.tr.setNodeMarkup(this.getPos(), undefined, Object.assign(Object.assign({}, this.node.attrs), { height: `${this.img.offsetHeight}px`, width: `${this.img.offsetWidth}px`, minHeight: `${this.img.offsetHeight}px`, minWidth: `${this.img.offsetWidth}px` })));
128
+ }
123
129
  // Ensure that the existing NodeView is reused rather than recreated.
124
130
  // Recreating the NodeView will cause flickering between states.
125
131
  update(node) {
126
132
  if (!this.transitioningBetweenSuccessStates(node)) {
127
133
  this.img.src = node.attrs.src;
128
134
  this.img.alt = node.attrs.alt;
129
- this.img.style.maxWidth = node.attrs.maxWidth;
130
- this.img.style.width = node.attrs.width;
135
+ applyImageStyles(this.img, node);
131
136
  }
132
137
  this.node = node;
133
138
  this.transitionBetweenStates();