@limetech/lime-elements 40.5.4 → 40.6.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.
Files changed (35) hide show
  1. package/CHANGELOG.md +11 -0
  2. package/dist/cjs/{index-ETPz91V5.js → index-zZvs6xK-.js} +9382 -9504
  3. package/dist/cjs/lime-elements.cjs.js +1 -1
  4. package/dist/cjs/limel-file-viewer.cjs.entry.js +1 -1
  5. package/dist/cjs/limel-markdown.cjs.entry.js +28 -4
  6. package/dist/cjs/limel-prosemirror-adapter.cjs.entry.js +2 -2
  7. package/dist/cjs/loader.cjs.js +1 -1
  8. package/dist/cjs/markdown-exporter-9p7d3Q76.js +6210 -0
  9. package/dist/cjs/{markdown-parser-YH5HSSgF.js → markdown-parser-Cgv5Y9Z4.js} +579 -848
  10. package/dist/collection/components/markdown/markdown-exporter.js +186 -0
  11. package/dist/collection/components/markdown/markdown-parser.js +2 -3
  12. package/dist/collection/components/markdown/markdown.js +54 -2
  13. package/dist/esm/{index-CbciMfiU.js → index-BN64nlbO.js} +9369 -9505
  14. package/dist/esm/lime-elements.js +1 -1
  15. package/dist/esm/limel-file-viewer.entry.js +1 -1
  16. package/dist/esm/limel-markdown.entry.js +28 -4
  17. package/dist/esm/limel-prosemirror-adapter.entry.js +2 -2
  18. package/dist/esm/loader.js +1 -1
  19. package/dist/esm/markdown-exporter--3_MaqwD.js +6208 -0
  20. package/dist/esm/{markdown-parser-DrxxGLF5.js → markdown-parser-DCf-3dKZ.js} +506 -791
  21. package/dist/lime-elements/lime-elements.esm.js +1 -1
  22. package/dist/lime-elements/{p-05afb316.entry.js → p-20658306.entry.js} +1 -1
  23. package/dist/lime-elements/p-BPzhuHcl.js +1 -0
  24. package/dist/lime-elements/p-CC4Usa-Q.js +7 -0
  25. package/dist/lime-elements/p-_UXAljNc.js +1 -0
  26. package/dist/lime-elements/p-dc9cd793.entry.js +1 -0
  27. package/dist/lime-elements/{p-10594468.entry.js → p-dd62042a.entry.js} +1 -1
  28. package/dist/types/components/markdown/markdown-exporter.d.ts +15 -0
  29. package/dist/types/components/markdown/markdown.d.ts +19 -0
  30. package/dist/types/components.d.ts +10 -0
  31. package/dist/types/global/shared-types/custom-element.types.d.ts +25 -0
  32. package/package.json +7 -1
  33. package/dist/lime-elements/p-6d5b23ad.entry.js +0 -1
  34. package/dist/lime-elements/p-C9xHOCE_.js +0 -7
  35. package/dist/lime-elements/p-DMNtoJ8I.js +0 -1
@@ -0,0 +1,186 @@
1
+ import { fromDom } from "hast-util-from-dom";
2
+ import { defaultHandlers } from "hast-util-to-mdast";
3
+ import { defaultHandlers as defaultMarkdownHandlers, } from "mdast-util-to-markdown";
4
+ import rehypeRemark from "rehype-remark";
5
+ import remarkGfm from "remark-gfm";
6
+ import remarkParse from "remark-parse";
7
+ import remarkStringify from "remark-stringify";
8
+ import { unified } from "unified";
9
+ /**
10
+ * Convert rendered markdown content back to markdown, replacing each
11
+ * whitelisted custom element with the markdown it describes itself as.
12
+ *
13
+ * Elements that do not implement {@link MarkdownDescribable} are
14
+ * converted like any unknown element: their light DOM children are kept,
15
+ * and an element without children disappears.
16
+ *
17
+ * @param container - The element holding the rendered content.
18
+ * @param whitelist - The custom elements allowed in the content.
19
+ * @returns The content as GitHub Flavored Markdown.
20
+ */
21
+ export async function exportMarkdown(container, whitelist) {
22
+ const tagNames = new Set(whitelist
23
+ .map((definition) => definition.tagName)
24
+ .filter(isDescribableTag));
25
+ // The tree is captured before any description is awaited, so that
26
+ // content rendered in the meantime cannot end up in the export without
27
+ // its description.
28
+ const describableElements = new Map();
29
+ const tree = fromDom(container, {
30
+ afterTransform: (domNode, hastNode) => {
31
+ if (hastNode.type !== 'element') {
32
+ return;
33
+ }
34
+ restoreDeferredImage(hastNode);
35
+ hideFootnoteBackReference(hastNode);
36
+ if (domNode instanceof Element && tagNames.has(domNode.localName)) {
37
+ describableElements.set(hastNode, domNode);
38
+ }
39
+ },
40
+ });
41
+ await describeElements(describableElements);
42
+ const handlers = {
43
+ sup: handleSup,
44
+ section: handleSection,
45
+ };
46
+ for (const tagName of tagNames) {
47
+ handlers[tagName] = handleCustomElement;
48
+ }
49
+ const processor = unified()
50
+ .use(rehypeRemark, { handlers })
51
+ .use(remarkGfm)
52
+ .use(remarkStringify, {
53
+ bullet: '-',
54
+ handlers: { break: handleBreak },
55
+ });
56
+ const mdast = await processor.run({
57
+ type: 'root',
58
+ children: tree.children,
59
+ });
60
+ return processor.stringify(mdast).trimEnd();
61
+ }
62
+ /**
63
+ * The description is kept on the node itself, since `rehype-remark`
64
+ * works on a clone of the tree. It also becomes the node's only child, so
65
+ * that whitespace minification treats the element as content and keeps
66
+ * the spaces around it. An element whose description fails is converted
67
+ * as if it did not describe itself.
68
+ * @param elements
69
+ */
70
+ async function describeElements(elements) {
71
+ await Promise.all([...elements].map(async ([node, element]) => {
72
+ try {
73
+ const description = await element.toMarkdown();
74
+ node.properties.dataMarkdownDescription = description;
75
+ node.children = [{ type: 'text', value: description }];
76
+ }
77
+ catch (error) {
78
+ console.error('Failed to describe element as markdown', error);
79
+ }
80
+ }));
81
+ }
82
+ const handleCustomElement = (state, node) => {
83
+ const description = node.properties.dataMarkdownDescription;
84
+ if (typeof description === 'string') {
85
+ return parseMarkdown(description);
86
+ }
87
+ return state.all(node);
88
+ };
89
+ /**
90
+ * Write hard line breaks as two trailing spaces rather than a backslash,
91
+ * since the export is also read as plain text, where the spaces are
92
+ * invisible.
93
+ * @param args
94
+ */
95
+ const handleBreak = (...args) => {
96
+ const markdown = defaultMarkdownHandlers.break(...args);
97
+ return markdown === '\\\n' ? ' \n' : markdown;
98
+ };
99
+ /**
100
+ * Parse a description into mdast nodes. A description that is a single
101
+ * paragraph gives phrasing content, so that an element inside a
102
+ * paragraph or list item stays inline.
103
+ * @param markdown
104
+ */
105
+ function parseMarkdown(markdown) {
106
+ const { children } = unified()
107
+ .use(remarkParse)
108
+ .use(remarkGfm)
109
+ .parse(markdown);
110
+ if (children.length === 1 && children[0].type === 'paragraph') {
111
+ return children[0].children;
112
+ }
113
+ return children;
114
+ }
115
+ /**
116
+ * An image that has not been scrolled into view yet keeps its URL in
117
+ * `data-src`, until the lazy loading moves it to `src`.
118
+ * @param node
119
+ */
120
+ function restoreDeferredImage(node) {
121
+ const deferredSrc = node.properties.dataSrc;
122
+ if (node.tagName === 'img' && !node.properties.src && deferredSrc) {
123
+ node.properties.src = deferredSrc;
124
+ }
125
+ }
126
+ /**
127
+ * The links from a footnote back to its references are recreated when the
128
+ * markdown is rendered again.
129
+ * @param node
130
+ */
131
+ function hideFootnoteBackReference(node) {
132
+ if (node.tagName === 'a' &&
133
+ node.properties.dataFootnoteBackref !== undefined) {
134
+ node.properties.dataMdast = 'ignore';
135
+ }
136
+ }
137
+ /**
138
+ * Footnotes are labelled with their rendered numbers. The ids only keep an
139
+ * encoded, lowercased form of the original labels, which cannot be turned
140
+ * back into them reliably. A reference shows its footnote's number, and
141
+ * the definitions are listed in the same order.
142
+ * @param state
143
+ * @param node
144
+ */
145
+ const handleSup = (state, node) => {
146
+ const link = node.children.find((child) => isElement(child, 'a') &&
147
+ child.properties.dataFootnoteRef !== undefined);
148
+ if (!link) {
149
+ return defaultHandlers.sup(state, node);
150
+ }
151
+ const identifier = link.children
152
+ .map((child) => (child.type === 'text' ? child.value : ''))
153
+ .join('');
154
+ return { type: 'footnoteReference', identifier, label: identifier };
155
+ };
156
+ const handleSection = (state, node) => {
157
+ if (node.properties.dataFootnotes === undefined) {
158
+ return defaultHandlers.section(state, node);
159
+ }
160
+ const list = node.children.find((child) => isElement(child, 'ol'));
161
+ const items = list === null || list === void 0 ? void 0 : list.children.filter((child) => isElement(child, 'li'));
162
+ return (items !== null && items !== void 0 ? items : []).map((item, index) => {
163
+ const identifier = String(index + 1);
164
+ return {
165
+ type: 'footnoteDefinition',
166
+ identifier,
167
+ label: identifier,
168
+ children: state.toFlow(state.all(item)),
169
+ };
170
+ });
171
+ };
172
+ function isElement(node, tagName) {
173
+ return node.type === 'element' && node.tagName === tagName;
174
+ }
175
+ /**
176
+ * Whether the custom element registered for the tag implements
177
+ * {@link MarkdownDescribable}. Stencil defines a component's methods on
178
+ * the element's prototype when the tag is registered, before the
179
+ * component itself has loaded.
180
+ * @param tagName
181
+ */
182
+ function isDescribableTag(tagName) {
183
+ var _a;
184
+ const prototype = (_a = customElements.get(tagName)) === null || _a === void 0 ? void 0 : _a.prototype;
185
+ return typeof (prototype === null || prototype === void 0 ? void 0 : prototype.toMarkdown) === 'function';
186
+ }
@@ -2,6 +2,7 @@ import { unified } from "unified";
2
2
  import remarkParse from "remark-parse";
3
3
  import remarkRehype from "remark-rehype";
4
4
  import remarkGfm from "remark-gfm";
5
+ import remarkBreaks from "remark-breaks";
5
6
  import rehypeParse from "rehype-parse";
6
7
  import rehypeSanitize, { defaultSchema } from "rehype-sanitize";
7
8
  import rehypeStringify from "rehype-stringify";
@@ -27,12 +28,10 @@ import { createRemoveEmptyParagraphsPlugin } from "./remove-empty-paragraphs-plu
27
28
  */
28
29
  export async function markdownToHTML(text, options) {
29
30
  var _a;
30
- if (options === null || options === void 0 ? void 0 : options.forceHardLineBreaks) {
31
- text = text.replaceAll(/(?<!\\)([\n\r])/g, ' $1');
32
- }
33
31
  const file = await unified()
34
32
  .use(remarkParse)
35
33
  .use(remarkGfm)
34
+ .use((options === null || options === void 0 ? void 0 : options.forceHardLineBreaks) ? [remarkBreaks] : [])
36
35
  .use(remarkRehype, { allowDangerousHtml: true })
37
36
  .use(rehypeRaw)
38
37
  .use(createLinksPlugin())
@@ -39,6 +39,7 @@ import { adaptColorContrast } from "../../util/adapt-color-contrast";
39
39
  * @exampleComponent limel-example-markdown-custom-component-with-json-props
40
40
  * @exampleComponent limel-example-markdown-remove-empty-paragraphs
41
41
  * @exampleComponent limel-example-markdown-adapt-color-contrast
42
+ * @exampleComponent limel-example-markdown-to-markdown
42
43
  * @exampleComponent limel-example-markdown-composite
43
44
  */
44
45
  export class Markdown {
@@ -97,7 +98,31 @@ export class Markdown {
97
98
  this.adaptColorContrast = false;
98
99
  this.imageIntersectionObserver = null;
99
100
  }
100
- async textChanged() {
101
+ /**
102
+ * Returns the content as markdown, with every whitelisted custom
103
+ * element replaced by the markdown it describes itself as.
104
+ *
105
+ * Use this to hand the content to a target that cannot render custom
106
+ * elements, such as the clipboard. An element describes itself by
107
+ * implementing `MarkdownDescribable`; elements that do not keep
108
+ * their child content, or are removed if they have none.
109
+ *
110
+ * The markdown is generated from the rendered content, so it may be
111
+ * formatted differently from `value`.
112
+ *
113
+ * @alpha
114
+ * @returns The content as markdown.
115
+ */
116
+ async toMarkdown() {
117
+ await this.rendering;
118
+ const { exportMarkdown } = await import('./markdown-exporter');
119
+ return exportMarkdown(this.rootElement, this.cachedCombinedWhitelist);
120
+ }
121
+ textChanged() {
122
+ this.rendering = this.renderMarkdown();
123
+ return this.rendering;
124
+ }
125
+ async renderMarkdown() {
101
126
  try {
102
127
  this.cleanupImageIntersectionObserver();
103
128
  // The whitelist merge and default import live here (not in
@@ -145,7 +170,7 @@ export class Markdown {
145
170
  this.cleanupImageIntersectionObserver();
146
171
  }
147
172
  render() {
148
- return (h(Host, { key: 'd10bcceddc2844e43817d406965155f282e21549' }, h("div", { key: '4253c517b72dbd21081e5fe26aaa0e75a91cb7db', id: "markdown", ref: (el) => (this.rootElement = el) })));
173
+ return (h(Host, { key: '86a21b8636af14891a657e136a9dceae4acb3b23' }, h("div", { key: '5f53c998000e2f197953232a63b2deb8ee1878bf', id: "markdown", ref: (el) => (this.rootElement = el) })));
149
174
  }
150
175
  setupImageIntersectionObserver() {
151
176
  if (this.lazyLoadImages) {
@@ -282,6 +307,33 @@ export class Markdown {
282
307
  }
283
308
  };
284
309
  }
310
+ static get methods() {
311
+ return {
312
+ "toMarkdown": {
313
+ "complexType": {
314
+ "signature": "() => Promise<string>",
315
+ "parameters": [],
316
+ "references": {
317
+ "Promise": {
318
+ "location": "global",
319
+ "id": "global::Promise"
320
+ }
321
+ },
322
+ "return": "Promise<string>"
323
+ },
324
+ "docs": {
325
+ "text": "Returns the content as markdown, with every whitelisted custom\nelement replaced by the markdown it describes itself as.\n\nUse this to hand the content to a target that cannot render custom\nelements, such as the clipboard. An element describes itself by\nimplementing `MarkdownDescribable`; elements that do not keep\ntheir child content, or are removed if they have none.\n\nThe markdown is generated from the rendered content, so it may be\nformatted differently from `value`.",
326
+ "tags": [{
327
+ "name": "alpha",
328
+ "text": undefined
329
+ }, {
330
+ "name": "returns",
331
+ "text": "The content as markdown."
332
+ }]
333
+ }
334
+ }
335
+ };
336
+ }
285
337
  static get watchers() {
286
338
  return [{
287
339
  "propName": "value",