@gravity-ui/markdown-editor 14.4.0 → 14.5.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.
- package/build/cjs/bundle/Editor.js +1 -0
- package/build/cjs/bundle/config/markup.d.ts +41 -17
- package/build/cjs/bundle/config/markup.js +413 -308
- package/build/cjs/bundle/config/wysiwyg.d.ts +29 -18
- package/build/cjs/bundle/config/wysiwyg.js +526 -310
- package/build/cjs/bundle/sticky/sticky.css +1 -1
- package/build/cjs/bundle/types.d.ts +2 -0
- package/build/cjs/extensions/behavior/Clipboard/utils.d.ts +1 -0
- package/build/cjs/extensions/behavior/Clipboard/utils.js +1 -0
- package/build/cjs/extensions/markdown/CodeBlock/handle-paste.js +5 -17
- package/build/cjs/extensions/yfm/Checkbox/CheckboxSpecs/const.d.ts +7 -0
- package/build/cjs/extensions/yfm/Checkbox/CheckboxSpecs/const.js +8 -1
- package/build/cjs/extensions/yfm/Checkbox/CheckboxSpecs/index.d.ts +1 -1
- package/build/cjs/extensions/yfm/Checkbox/CheckboxSpecs/index.js +2 -1
- package/build/cjs/extensions/yfm/Checkbox/CheckboxSpecs/schema.d.ts +1 -1
- package/build/cjs/extensions/yfm/Checkbox/CheckboxSpecs/schema.js +7 -7
- package/build/cjs/extensions/yfm/Checkbox/CheckboxSpecs/serializer.d.ts +1 -1
- package/build/cjs/extensions/yfm/Checkbox/CheckboxSpecs/serializer.js +2 -2
- package/build/cjs/extensions/yfm/Checkbox/const.d.ts +1 -1
- package/build/cjs/extensions/yfm/Checkbox/const.js +2 -1
- package/build/cjs/extensions/yfm/Checkbox/index.d.ts +2 -2
- package/build/cjs/extensions/yfm/Checkbox/index.js +4 -38
- package/build/cjs/extensions/yfm/Checkbox/nodeviews.d.ts +16 -0
- package/build/cjs/extensions/yfm/Checkbox/nodeviews.js +56 -0
- package/build/cjs/extensions/yfm/YfmFile/YfmFileSpecs/const.d.ts +12 -0
- package/build/cjs/extensions/yfm/YfmFile/YfmFileSpecs/const.js +21 -2
- package/build/cjs/extensions/yfm/YfmFile/YfmFileSpecs/index.d.ts +8 -1
- package/build/cjs/extensions/yfm/YfmFile/YfmFileSpecs/index.js +29 -5
- package/build/cjs/markup/codemirror/create.d.ts +1 -0
- package/build/cjs/markup/codemirror/create.js +41 -4
- package/build/cjs/markup/codemirror/html-to-markdown/converters.d.ts +111 -0
- package/build/cjs/markup/codemirror/html-to-markdown/converters.js +214 -0
- package/build/cjs/markup/codemirror/html-to-markdown/handlers.d.ts +104 -0
- package/build/cjs/markup/codemirror/html-to-markdown/handlers.js +233 -0
- package/build/cjs/markup/codemirror/html-to-markdown/helpers.d.ts +1 -0
- package/build/cjs/markup/codemirror/html-to-markdown/helpers.js +21 -0
- package/build/cjs/markup/commands/inline.js +18 -8
- package/build/cjs/utils/clipboard.d.ts +14 -0
- package/build/cjs/utils/clipboard.js +36 -1
- package/build/cjs/version.js +1 -1
- package/build/esm/bundle/Editor.js +1 -0
- package/build/esm/bundle/config/markup.d.ts +41 -17
- package/build/esm/bundle/config/markup.js +411 -307
- package/build/esm/bundle/config/wysiwyg.d.ts +29 -18
- package/build/esm/bundle/config/wysiwyg.js +499 -284
- package/build/esm/bundle/sticky/sticky.css +1 -1
- package/build/esm/bundle/types.d.ts +2 -0
- package/build/esm/extensions/behavior/Clipboard/utils.d.ts +1 -0
- package/build/esm/extensions/behavior/Clipboard/utils.js +1 -0
- package/build/esm/extensions/markdown/CodeBlock/handle-paste.js +2 -14
- package/build/esm/extensions/yfm/Checkbox/CheckboxSpecs/const.d.ts +7 -0
- package/build/esm/extensions/yfm/Checkbox/CheckboxSpecs/const.js +7 -0
- package/build/esm/extensions/yfm/Checkbox/CheckboxSpecs/index.d.ts +1 -1
- package/build/esm/extensions/yfm/Checkbox/CheckboxSpecs/index.js +1 -1
- package/build/esm/extensions/yfm/Checkbox/CheckboxSpecs/schema.d.ts +1 -1
- package/build/esm/extensions/yfm/Checkbox/CheckboxSpecs/schema.js +7 -7
- package/build/esm/extensions/yfm/Checkbox/CheckboxSpecs/serializer.d.ts +1 -1
- package/build/esm/extensions/yfm/Checkbox/CheckboxSpecs/serializer.js +2 -2
- package/build/esm/extensions/yfm/Checkbox/const.d.ts +1 -1
- package/build/esm/extensions/yfm/Checkbox/const.js +1 -1
- package/build/esm/extensions/yfm/Checkbox/index.d.ts +2 -2
- package/build/esm/extensions/yfm/Checkbox/index.js +3 -38
- package/build/esm/extensions/yfm/Checkbox/nodeviews.d.ts +16 -0
- package/build/esm/extensions/yfm/Checkbox/nodeviews.js +52 -0
- package/build/esm/extensions/yfm/YfmFile/YfmFileSpecs/const.d.ts +12 -0
- package/build/esm/extensions/yfm/YfmFile/YfmFileSpecs/const.js +21 -2
- package/build/esm/extensions/yfm/YfmFile/YfmFileSpecs/index.d.ts +8 -1
- package/build/esm/extensions/yfm/YfmFile/YfmFileSpecs/index.js +29 -6
- package/build/esm/markup/codemirror/create.d.ts +1 -0
- package/build/esm/markup/codemirror/create.js +40 -3
- package/build/esm/markup/codemirror/html-to-markdown/converters.d.ts +111 -0
- package/build/esm/markup/codemirror/html-to-markdown/converters.js +210 -0
- package/build/esm/markup/codemirror/html-to-markdown/handlers.d.ts +104 -0
- package/build/esm/markup/codemirror/html-to-markdown/handlers.js +215 -0
- package/build/esm/markup/codemirror/html-to-markdown/helpers.d.ts +1 -0
- package/build/esm/markup/codemirror/html-to-markdown/helpers.js +17 -0
- package/build/esm/markup/commands/inline.js +18 -8
- package/build/esm/utils/clipboard.d.ts +14 -0
- package/build/esm/utils/clipboard.js +32 -0
- package/build/esm/version.js +1 -1
- package/build/styles.css +1 -1
- package/package.json +9 -7
|
@@ -0,0 +1,210 @@
|
|
|
1
|
+
import { BrHandler, CodeHandler, DivHandler, FormattingHandler, GenericHandler, HeaderHandler, ImageHandler, LinkHandler, OrderedListHandler, ParagraphHandler, TableHandler, TableRowHandler, TextNodeHandler, UnorderedListHandler, } from './handlers';
|
|
2
|
+
import { applyFormatting } from './helpers';
|
|
3
|
+
/**
|
|
4
|
+
* Main converter class that implements the visitor interface to convert HTML to Markdown.
|
|
5
|
+
* Uses the Chain of Responsibility pattern for handling different node types.
|
|
6
|
+
*/
|
|
7
|
+
export class MarkdownConverter {
|
|
8
|
+
constructor() {
|
|
9
|
+
// Set up the chain of responsibility for handling different node types
|
|
10
|
+
this.handler = this.setupHandlerChain();
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* Converts a text node to Markdown, escaping special characters.
|
|
14
|
+
*/
|
|
15
|
+
visitText(node) {
|
|
16
|
+
return (node.textContent || '').replace(/\n+/g, '').replace(/([<>])/g, '\\$1');
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* Converts an HTML anchor element to Markdown link syntax.
|
|
20
|
+
*/
|
|
21
|
+
visitLink(node) {
|
|
22
|
+
var _a;
|
|
23
|
+
const linkText = this.collectTextContent(node);
|
|
24
|
+
const url = node.href || '';
|
|
25
|
+
// Handle links with formatted content vs plain text differently
|
|
26
|
+
const formattedText = node.childNodes.length === 1 && ((_a = node.firstChild) === null || _a === void 0 ? void 0 : _a.nodeType) === Node.TEXT_NODE
|
|
27
|
+
? applyFormatting(linkText, node) // Plain text link
|
|
28
|
+
: Array.from(node.childNodes)
|
|
29
|
+
.map((child) => {
|
|
30
|
+
if (child.nodeType === Node.ELEMENT_NODE) {
|
|
31
|
+
return applyFormatting(child.textContent || '', child);
|
|
32
|
+
}
|
|
33
|
+
return child.textContent || '';
|
|
34
|
+
})
|
|
35
|
+
.join(''); // Apply formatting for each formatted child node
|
|
36
|
+
return `[${formattedText}](${url} "${linkText.replace(/"/g, '\\"')}")`;
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* Converts an HTML heading element to Markdown heading syntax.
|
|
40
|
+
*/
|
|
41
|
+
visitHeader(node, level) {
|
|
42
|
+
const headerContent = this.collectTextContent(node);
|
|
43
|
+
return '#'.repeat(level) + ' ' + headerContent + '\n';
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* Converts an HTML paragraph to Markdown format.
|
|
47
|
+
*/
|
|
48
|
+
visitParagraph(node) {
|
|
49
|
+
const content = this.processChildren(node);
|
|
50
|
+
return content.trim() + '\n\n';
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* Applies Markdown formatting (bold, italic, etc.) to text content.
|
|
54
|
+
*/
|
|
55
|
+
visitFormatting(node) {
|
|
56
|
+
var _a;
|
|
57
|
+
if (node.childNodes.length === 1 && ((_a = node.firstChild) === null || _a === void 0 ? void 0 : _a.nodeType) === Node.TEXT_NODE) {
|
|
58
|
+
const text = this.collectTextContent(node);
|
|
59
|
+
return applyFormatting(text, node);
|
|
60
|
+
}
|
|
61
|
+
return applyFormatting(this.visitGeneric(node), node);
|
|
62
|
+
}
|
|
63
|
+
/**
|
|
64
|
+
* Converts HTML code elements to Markdown inline code syntax.
|
|
65
|
+
*/
|
|
66
|
+
visitCode(node) {
|
|
67
|
+
const codeContent = this.collectCodeContent(node);
|
|
68
|
+
if (codeContent.includes('\n')) {
|
|
69
|
+
return '```\n' + codeContent + '\n```\n';
|
|
70
|
+
}
|
|
71
|
+
else if (codeContent.includes('`')) {
|
|
72
|
+
return '`` ' + codeContent + ' ``';
|
|
73
|
+
}
|
|
74
|
+
else {
|
|
75
|
+
return `\`${codeContent}\``;
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
/**
|
|
79
|
+
* Handles generic HTML elements by processing their children.
|
|
80
|
+
*/
|
|
81
|
+
visitGeneric(node) {
|
|
82
|
+
return this.processChildren(node);
|
|
83
|
+
}
|
|
84
|
+
/**
|
|
85
|
+
* Converts an HTML div element to Markdown format, adding a single newline.
|
|
86
|
+
*/
|
|
87
|
+
visitDiv(node) {
|
|
88
|
+
const content = this.processChildren(node);
|
|
89
|
+
return content + '\n'; // Add a single newline for <div>
|
|
90
|
+
}
|
|
91
|
+
/**
|
|
92
|
+
* Converts an HTML br element to a newline in Markdown.
|
|
93
|
+
*/
|
|
94
|
+
visitBr() {
|
|
95
|
+
return '\n'; // Single newline for <br>
|
|
96
|
+
}
|
|
97
|
+
/**
|
|
98
|
+
* Converts an HTML table row element to Markdown table row format.
|
|
99
|
+
*/
|
|
100
|
+
visitTableRow(node) {
|
|
101
|
+
const cells = Array.from(node.children).map((cell) => {
|
|
102
|
+
return this.visitGeneric(cell).trim() || '';
|
|
103
|
+
});
|
|
104
|
+
return '||\n' + cells.join('\n|\n') + '\n||';
|
|
105
|
+
}
|
|
106
|
+
/**
|
|
107
|
+
* Converts an HTML table element to Markdown table format.
|
|
108
|
+
*/
|
|
109
|
+
visitTable(node) {
|
|
110
|
+
const rows = [];
|
|
111
|
+
const tableRows = Array.from(node.querySelectorAll('tr'));
|
|
112
|
+
tableRows.forEach((row) => {
|
|
113
|
+
rows.push(this.visitTableRow(row));
|
|
114
|
+
});
|
|
115
|
+
return '\n\n#|\n' + rows.join('\n') + '\n|#\n\n';
|
|
116
|
+
}
|
|
117
|
+
/**
|
|
118
|
+
* Converts img tag to Markdown image format
|
|
119
|
+
*/
|
|
120
|
+
visitImage(node) {
|
|
121
|
+
const imgElement = node;
|
|
122
|
+
const altText = imgElement.alt || '';
|
|
123
|
+
const src = imgElement.src || '';
|
|
124
|
+
return ``;
|
|
125
|
+
}
|
|
126
|
+
/**
|
|
127
|
+
* Processes a single node using the handler chain.
|
|
128
|
+
*/
|
|
129
|
+
processNode(node) {
|
|
130
|
+
const result = this.getHandler().handle(node, this);
|
|
131
|
+
return result;
|
|
132
|
+
}
|
|
133
|
+
/**
|
|
134
|
+
* Creates and links together handlers in a specific order implementing the Chain of Responsibility pattern.
|
|
135
|
+
* @returns The first handler in the chain
|
|
136
|
+
*/
|
|
137
|
+
setupHandlerChain() {
|
|
138
|
+
// Create handlers for each type of node
|
|
139
|
+
const textHandler = new TextNodeHandler();
|
|
140
|
+
const linkHandler = new LinkHandler();
|
|
141
|
+
const headerHandler = new HeaderHandler();
|
|
142
|
+
const paragraphHandler = new ParagraphHandler();
|
|
143
|
+
const formattingHandler = new FormattingHandler();
|
|
144
|
+
const codeHandler = new CodeHandler();
|
|
145
|
+
const genericHandler = new GenericHandler();
|
|
146
|
+
const orderedListHandler = new OrderedListHandler();
|
|
147
|
+
const unorderedListHandler = new UnorderedListHandler();
|
|
148
|
+
const divHandler = new DivHandler();
|
|
149
|
+
const brHandler = new BrHandler();
|
|
150
|
+
const tableRowHandler = new TableRowHandler();
|
|
151
|
+
const tableHandler = new TableHandler();
|
|
152
|
+
const imageHandler = new ImageHandler(); // New handler for <img>
|
|
153
|
+
// Chain handlers together in priority order
|
|
154
|
+
textHandler
|
|
155
|
+
.setNext(linkHandler)
|
|
156
|
+
.setNext(headerHandler)
|
|
157
|
+
.setNext(paragraphHandler)
|
|
158
|
+
.setNext(divHandler)
|
|
159
|
+
.setNext(brHandler)
|
|
160
|
+
.setNext(orderedListHandler)
|
|
161
|
+
.setNext(unorderedListHandler)
|
|
162
|
+
.setNext(formattingHandler)
|
|
163
|
+
.setNext(codeHandler)
|
|
164
|
+
.setNext(imageHandler) // Add image handler
|
|
165
|
+
.setNext(tableHandler)
|
|
166
|
+
.setNext(tableRowHandler)
|
|
167
|
+
.setNext(genericHandler);
|
|
168
|
+
return textHandler;
|
|
169
|
+
}
|
|
170
|
+
/**
|
|
171
|
+
* Recursively collects and processes text content from a node and its children.
|
|
172
|
+
*/
|
|
173
|
+
collectTextContent(node) {
|
|
174
|
+
// handle seo elements (hide it's content)
|
|
175
|
+
if (node.className === 'visually-hidden') {
|
|
176
|
+
return '';
|
|
177
|
+
}
|
|
178
|
+
if (node.nodeType === Node.TEXT_NODE) {
|
|
179
|
+
return this.visitText(node);
|
|
180
|
+
}
|
|
181
|
+
return Array.from(node.childNodes)
|
|
182
|
+
.map((child) => this.collectTextContent(child))
|
|
183
|
+
.join('');
|
|
184
|
+
}
|
|
185
|
+
/**
|
|
186
|
+
* Collects raw text content from code elements.
|
|
187
|
+
*/
|
|
188
|
+
collectCodeContent(node) {
|
|
189
|
+
if (node.nodeType === Node.TEXT_NODE) {
|
|
190
|
+
return node.textContent || '';
|
|
191
|
+
}
|
|
192
|
+
return Array.from(node.childNodes)
|
|
193
|
+
.map((child) => this.collectCodeContent(child))
|
|
194
|
+
.join('');
|
|
195
|
+
}
|
|
196
|
+
/**
|
|
197
|
+
* Processes all child nodes of a given node.
|
|
198
|
+
*/
|
|
199
|
+
processChildren(node) {
|
|
200
|
+
return Array.from(node.childNodes)
|
|
201
|
+
.map((child) => this.processNode(child))
|
|
202
|
+
.join('');
|
|
203
|
+
}
|
|
204
|
+
/**
|
|
205
|
+
* Gets the first handler in the chain.
|
|
206
|
+
*/
|
|
207
|
+
getHandler() {
|
|
208
|
+
return this.handler;
|
|
209
|
+
}
|
|
210
|
+
}
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
import { HTMLNodeVisitor } from './converters';
|
|
2
|
+
/**
|
|
3
|
+
* Base handler class implementing the Chain of Responsibility pattern for HTML node processing.
|
|
4
|
+
* Each concrete handler decides whether it can process a node or should pass it to the next handler.
|
|
5
|
+
*/
|
|
6
|
+
export declare abstract class NodeHandler {
|
|
7
|
+
protected next: NodeHandler | null;
|
|
8
|
+
/**
|
|
9
|
+
* Sets up the next handler in the chain
|
|
10
|
+
*/
|
|
11
|
+
setNext(handler: NodeHandler): NodeHandler;
|
|
12
|
+
/**
|
|
13
|
+
* Process the given node or delegate to the next handler
|
|
14
|
+
*/
|
|
15
|
+
abstract handle(node: Node, visitor: HTMLNodeVisitor): string;
|
|
16
|
+
/**
|
|
17
|
+
* Delegates processing to the next handler in the chain
|
|
18
|
+
*/
|
|
19
|
+
protected handleNext(node: Node, visitor: HTMLNodeVisitor): string;
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Handles text nodes, converting them to markdown text
|
|
23
|
+
*/
|
|
24
|
+
export declare class TextNodeHandler extends NodeHandler {
|
|
25
|
+
handle(node: Node, visitor: HTMLNodeVisitor): string;
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* Handles anchor elements, converting them to markdown links
|
|
29
|
+
*/
|
|
30
|
+
export declare class LinkHandler extends NodeHandler {
|
|
31
|
+
handle(node: Node, visitor: HTMLNodeVisitor): string;
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Handles header elements (h1-h6), converting them to markdown headers
|
|
35
|
+
*/
|
|
36
|
+
export declare class HeaderHandler extends NodeHandler {
|
|
37
|
+
handle(node: Node, visitor: HTMLNodeVisitor): string;
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* Handles paragraph elements, converting them to markdown paragraphs
|
|
41
|
+
*/
|
|
42
|
+
export declare class ParagraphHandler extends NodeHandler {
|
|
43
|
+
handle(node: Node, visitor: HTMLNodeVisitor): string;
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* Handles text formatting elements (b, strong, i, em), converting them to markdown formatting
|
|
47
|
+
*/
|
|
48
|
+
export declare class FormattingHandler extends NodeHandler {
|
|
49
|
+
handle(node: Node, visitor: HTMLNodeVisitor): string;
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* Handles code elements, converting them to markdown code blocks or inline code
|
|
53
|
+
*/
|
|
54
|
+
export declare class CodeHandler extends NodeHandler {
|
|
55
|
+
handle(node: Node, visitor: HTMLNodeVisitor): string;
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* Fallback handler for any HTML elements not handled by other specific handlers
|
|
59
|
+
*/
|
|
60
|
+
export declare class GenericHandler extends NodeHandler {
|
|
61
|
+
handle(node: Node, visitor: HTMLNodeVisitor): string;
|
|
62
|
+
}
|
|
63
|
+
/**
|
|
64
|
+
* Handles ordered list elements, converting them to markdown ordered lists
|
|
65
|
+
*/
|
|
66
|
+
export declare class OrderedListHandler extends NodeHandler {
|
|
67
|
+
handle(node: Node, visitor: HTMLNodeVisitor): string;
|
|
68
|
+
}
|
|
69
|
+
/**
|
|
70
|
+
* Handles unordered list elements, converting them to markdown unordered lists
|
|
71
|
+
*/
|
|
72
|
+
export declare class UnorderedListHandler extends NodeHandler {
|
|
73
|
+
handle(node: Node, visitor: HTMLNodeVisitor): string;
|
|
74
|
+
}
|
|
75
|
+
/**
|
|
76
|
+
* Handles div elements, converting them to markdown paragraphs
|
|
77
|
+
*/
|
|
78
|
+
export declare class DivHandler extends NodeHandler {
|
|
79
|
+
handle(node: Node, visitor: HTMLNodeVisitor): string;
|
|
80
|
+
}
|
|
81
|
+
/**
|
|
82
|
+
* Handles br elements, converting them to markdown newlines
|
|
83
|
+
*/
|
|
84
|
+
export declare class BrHandler extends NodeHandler {
|
|
85
|
+
handle(node: Node, visitor: HTMLNodeVisitor): string;
|
|
86
|
+
}
|
|
87
|
+
/**
|
|
88
|
+
* Handles table row elements, converting them to markdown table rows
|
|
89
|
+
*/
|
|
90
|
+
export declare class TableRowHandler extends NodeHandler {
|
|
91
|
+
handle(node: Node, visitor: HTMLNodeVisitor): string;
|
|
92
|
+
}
|
|
93
|
+
/**
|
|
94
|
+
* Handles table elements, converting them to markdown tables
|
|
95
|
+
*/
|
|
96
|
+
export declare class TableHandler extends NodeHandler {
|
|
97
|
+
handle(node: Node, visitor: HTMLNodeVisitor): string;
|
|
98
|
+
}
|
|
99
|
+
/**
|
|
100
|
+
* Handles image elements, converting them to markdown images
|
|
101
|
+
*/
|
|
102
|
+
export declare class ImageHandler extends NodeHandler {
|
|
103
|
+
handle(node: Node, visitor: HTMLNodeVisitor): string;
|
|
104
|
+
}
|
|
@@ -0,0 +1,215 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Base handler class implementing the Chain of Responsibility pattern for HTML node processing.
|
|
3
|
+
* Each concrete handler decides whether it can process a node or should pass it to the next handler.
|
|
4
|
+
*/
|
|
5
|
+
export class NodeHandler {
|
|
6
|
+
constructor() {
|
|
7
|
+
this.next = null;
|
|
8
|
+
}
|
|
9
|
+
/**
|
|
10
|
+
* Sets up the next handler in the chain
|
|
11
|
+
*/
|
|
12
|
+
setNext(handler) {
|
|
13
|
+
this.next = handler;
|
|
14
|
+
return handler;
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* Delegates processing to the next handler in the chain
|
|
18
|
+
*/
|
|
19
|
+
handleNext(node, visitor) {
|
|
20
|
+
if (this.next) {
|
|
21
|
+
return this.next.handle(node, visitor);
|
|
22
|
+
}
|
|
23
|
+
return '';
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* Handles text nodes, converting them to markdown text
|
|
28
|
+
*/
|
|
29
|
+
export class TextNodeHandler extends NodeHandler {
|
|
30
|
+
handle(node, visitor) {
|
|
31
|
+
if (node.nodeType === Node.TEXT_NODE) {
|
|
32
|
+
return visitor.visitText(node);
|
|
33
|
+
}
|
|
34
|
+
return this.handleNext(node, visitor);
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* Handles anchor elements, converting them to markdown links
|
|
39
|
+
*/
|
|
40
|
+
export class LinkHandler extends NodeHandler {
|
|
41
|
+
handle(node, visitor) {
|
|
42
|
+
if (node.nodeType === Node.ELEMENT_NODE &&
|
|
43
|
+
node.tagName.toLowerCase() === 'a') {
|
|
44
|
+
return visitor.visitLink(node);
|
|
45
|
+
}
|
|
46
|
+
return this.handleNext(node, visitor);
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* Handles header elements (h1-h6), converting them to markdown headers
|
|
51
|
+
*/
|
|
52
|
+
export class HeaderHandler extends NodeHandler {
|
|
53
|
+
handle(node, visitor) {
|
|
54
|
+
if (node.nodeType !== Node.ELEMENT_NODE) {
|
|
55
|
+
return this.handleNext(node, visitor);
|
|
56
|
+
}
|
|
57
|
+
const tagName = node.tagName.toLowerCase();
|
|
58
|
+
const headerMatch = tagName.match(/^h([1-6])$/);
|
|
59
|
+
if (headerMatch) {
|
|
60
|
+
const headerLevel = parseInt(headerMatch[1], 10);
|
|
61
|
+
return visitor.visitHeader(node, headerLevel);
|
|
62
|
+
}
|
|
63
|
+
return this.handleNext(node, visitor);
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* Handles paragraph elements, converting them to markdown paragraphs
|
|
68
|
+
*/
|
|
69
|
+
export class ParagraphHandler extends NodeHandler {
|
|
70
|
+
handle(node, visitor) {
|
|
71
|
+
if (node.nodeType !== Node.ELEMENT_NODE) {
|
|
72
|
+
return this.handleNext(node, visitor);
|
|
73
|
+
}
|
|
74
|
+
if (node.tagName.toLowerCase() === 'p') {
|
|
75
|
+
return visitor.visitParagraph(node);
|
|
76
|
+
}
|
|
77
|
+
return this.handleNext(node, visitor);
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
/**
|
|
81
|
+
* Handles text formatting elements (b, strong, i, em), converting them to markdown formatting
|
|
82
|
+
*/
|
|
83
|
+
export class FormattingHandler extends NodeHandler {
|
|
84
|
+
handle(node, visitor) {
|
|
85
|
+
var _a;
|
|
86
|
+
if (node.nodeType !== Node.ELEMENT_NODE) {
|
|
87
|
+
return this.handleNext(node, visitor);
|
|
88
|
+
}
|
|
89
|
+
const element = node;
|
|
90
|
+
const tagName = element.tagName.toLowerCase();
|
|
91
|
+
const formattingTags = ['b', 'strong', 'i', 'em', 'span'];
|
|
92
|
+
if (formattingTags.includes(tagName) &&
|
|
93
|
+
!['a', 'code', 'pre'].includes(((_a = element.parentElement) === null || _a === void 0 ? void 0 : _a.tagName.toLowerCase()) || '')) {
|
|
94
|
+
return visitor.visitFormatting(element);
|
|
95
|
+
}
|
|
96
|
+
return this.handleNext(node, visitor);
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
/**
|
|
100
|
+
* Handles code elements, converting them to markdown code blocks or inline code
|
|
101
|
+
*/
|
|
102
|
+
export class CodeHandler extends NodeHandler {
|
|
103
|
+
handle(node, visitor) {
|
|
104
|
+
if (node.nodeType !== Node.ELEMENT_NODE) {
|
|
105
|
+
return this.handleNext(node, visitor);
|
|
106
|
+
}
|
|
107
|
+
if (node.tagName.toLowerCase() === 'code' ||
|
|
108
|
+
node.tagName.toLowerCase() === 'pre') {
|
|
109
|
+
return visitor.visitCode(node);
|
|
110
|
+
}
|
|
111
|
+
return this.handleNext(node, visitor);
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
/**
|
|
115
|
+
* Fallback handler for any HTML elements not handled by other specific handlers
|
|
116
|
+
*/
|
|
117
|
+
export class GenericHandler extends NodeHandler {
|
|
118
|
+
handle(node, visitor) {
|
|
119
|
+
return visitor.visitGeneric(node);
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
/**
|
|
123
|
+
* Handles ordered list elements, converting them to markdown ordered lists
|
|
124
|
+
*/
|
|
125
|
+
export class OrderedListHandler extends NodeHandler {
|
|
126
|
+
handle(node, visitor) {
|
|
127
|
+
if (node.nodeType === Node.ELEMENT_NODE &&
|
|
128
|
+
node.tagName.toLowerCase() === 'ol') {
|
|
129
|
+
const items = Array.from(node.childNodes)
|
|
130
|
+
.filter((child) => child.nodeType === Node.ELEMENT_NODE &&
|
|
131
|
+
child.tagName.toLowerCase() === 'li')
|
|
132
|
+
.map((item, index) => `${index + 1}. ${visitor.visitGeneric(item)}`)
|
|
133
|
+
.join('\n');
|
|
134
|
+
return items + '\n';
|
|
135
|
+
}
|
|
136
|
+
return this.handleNext(node, visitor);
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
/**
|
|
140
|
+
* Handles unordered list elements, converting them to markdown unordered lists
|
|
141
|
+
*/
|
|
142
|
+
export class UnorderedListHandler extends NodeHandler {
|
|
143
|
+
handle(node, visitor) {
|
|
144
|
+
if (node.nodeType === Node.ELEMENT_NODE &&
|
|
145
|
+
node.tagName.toLowerCase() === 'ul') {
|
|
146
|
+
const items = Array.from(node.childNodes)
|
|
147
|
+
.filter((child) => child.nodeType === Node.ELEMENT_NODE &&
|
|
148
|
+
child.tagName.toLowerCase() === 'li')
|
|
149
|
+
.map((item) => `- ${visitor.visitGeneric(item)}`)
|
|
150
|
+
.join('\n');
|
|
151
|
+
return items + '\n';
|
|
152
|
+
}
|
|
153
|
+
return this.handleNext(node, visitor);
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
/**
|
|
157
|
+
* Handles div elements, converting them to markdown paragraphs
|
|
158
|
+
*/
|
|
159
|
+
export class DivHandler extends NodeHandler {
|
|
160
|
+
handle(node, visitor) {
|
|
161
|
+
if (node.nodeType === Node.ELEMENT_NODE &&
|
|
162
|
+
node.tagName.toLowerCase() === 'div') {
|
|
163
|
+
return visitor.visitDiv(node);
|
|
164
|
+
}
|
|
165
|
+
return this.handleNext(node, visitor);
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
/**
|
|
169
|
+
* Handles br elements, converting them to markdown newlines
|
|
170
|
+
*/
|
|
171
|
+
export class BrHandler extends NodeHandler {
|
|
172
|
+
handle(node, visitor) {
|
|
173
|
+
if (node.nodeType === Node.ELEMENT_NODE &&
|
|
174
|
+
node.tagName.toLowerCase() === 'br') {
|
|
175
|
+
return visitor.visitBr();
|
|
176
|
+
}
|
|
177
|
+
return this.handleNext(node, visitor);
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
/**
|
|
181
|
+
* Handles table row elements, converting them to markdown table rows
|
|
182
|
+
*/
|
|
183
|
+
export class TableRowHandler extends NodeHandler {
|
|
184
|
+
handle(node, visitor) {
|
|
185
|
+
if (node.nodeType === Node.ELEMENT_NODE &&
|
|
186
|
+
node.tagName.toLowerCase() === 'tr') {
|
|
187
|
+
return visitor.visitTableRow(node);
|
|
188
|
+
}
|
|
189
|
+
return this.handleNext(node, visitor);
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
/**
|
|
193
|
+
* Handles table elements, converting them to markdown tables
|
|
194
|
+
*/
|
|
195
|
+
export class TableHandler extends NodeHandler {
|
|
196
|
+
handle(node, visitor) {
|
|
197
|
+
if (node.nodeType === Node.ELEMENT_NODE &&
|
|
198
|
+
node.tagName.toLowerCase() === 'table') {
|
|
199
|
+
return visitor.visitTable(node);
|
|
200
|
+
}
|
|
201
|
+
return this.handleNext(node, visitor);
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
/**
|
|
205
|
+
* Handles image elements, converting them to markdown images
|
|
206
|
+
*/
|
|
207
|
+
export class ImageHandler extends NodeHandler {
|
|
208
|
+
handle(node, visitor) {
|
|
209
|
+
if (node.nodeType === Node.ELEMENT_NODE &&
|
|
210
|
+
node.tagName.toLowerCase() === 'img') {
|
|
211
|
+
return visitor.visitImage(node);
|
|
212
|
+
}
|
|
213
|
+
return this.handleNext(node, visitor);
|
|
214
|
+
}
|
|
215
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function applyFormatting(text: string, element: HTMLElement): string;
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
export function applyFormatting(text, element) {
|
|
2
|
+
// Check for italic formatting (either through tags or CSS)
|
|
3
|
+
const hasItalic = element.tagName.toLowerCase() === 'i' ||
|
|
4
|
+
element.tagName.toLowerCase() === 'em' ||
|
|
5
|
+
element.style.fontStyle === 'italic';
|
|
6
|
+
// Check for bold formatting (either through tags or CSS font-weight)
|
|
7
|
+
const hasBold = element.tagName.toLowerCase() === 'b' ||
|
|
8
|
+
element.tagName.toLowerCase() === 'strong' ||
|
|
9
|
+
parseInt(element.style.fontWeight, 10) >= 600;
|
|
10
|
+
// Apply markdown formatting in specific order
|
|
11
|
+
let formatted = text;
|
|
12
|
+
if (hasItalic)
|
|
13
|
+
formatted = `*${formatted}*`; // Wrap in single asterisks for italic
|
|
14
|
+
if (hasBold)
|
|
15
|
+
formatted = `**${formatted}**`; // Wrap in double asterisks for bold
|
|
16
|
+
return formatted;
|
|
17
|
+
}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { snippet } from '@codemirror/autocomplete';
|
|
2
|
+
import { DirectiveSyntaxFacet } from '../codemirror';
|
|
2
3
|
const defaultLinkSnippet = snippet(`[#{2:link}](#{1:url} "#{3:title}")`);
|
|
3
4
|
export const insertLink = ({ state, dispatch }) => {
|
|
4
5
|
const { from, to, empty } = state.selection.main;
|
|
@@ -41,16 +42,25 @@ export function insertImages(images) {
|
|
|
41
42
|
return true;
|
|
42
43
|
};
|
|
43
44
|
}
|
|
45
|
+
const fileToCurlySyntax = (file) => {
|
|
46
|
+
const attrsStr = Object.entries(file)
|
|
47
|
+
.map(([key, value]) => `${key}="${value.replace('"', '')}"`)
|
|
48
|
+
.join(' ');
|
|
49
|
+
return `{% file ${attrsStr} %}`;
|
|
50
|
+
};
|
|
51
|
+
const fileToDirectiveSyntax = (file) => {
|
|
52
|
+
const { src, name, type } = file;
|
|
53
|
+
let markup = `:file[${name}](${src})`;
|
|
54
|
+
if (type)
|
|
55
|
+
markup += `{type="${type}"}`;
|
|
56
|
+
return markup;
|
|
57
|
+
};
|
|
44
58
|
export const insertFiles = (files) => {
|
|
45
59
|
return ({ state, dispatch }) => {
|
|
46
|
-
const
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
.join(' ');
|
|
51
|
-
return `{% file ${attrsStr} %}`;
|
|
52
|
-
})
|
|
53
|
-
.join(' ');
|
|
60
|
+
const serializer = state.facet(DirectiveSyntaxFacet).shouldInsertDirectiveMarkup('yfmFile')
|
|
61
|
+
? fileToDirectiveSyntax
|
|
62
|
+
: fileToCurlySyntax;
|
|
63
|
+
const markup = files.map(serializer).join(' ');
|
|
54
64
|
const tr = state.changeByRange((range) => {
|
|
55
65
|
const changes = state.changes({ from: range.from, to: range.to, insert: markup });
|
|
56
66
|
return { changes, range: range.map(changes) };
|
|
@@ -3,6 +3,7 @@ export declare enum DataTransferType {
|
|
|
3
3
|
Text = "text/plain",
|
|
4
4
|
Html = "text/html",
|
|
5
5
|
Yfm = "text/yfm",
|
|
6
|
+
Rtf = "text/rtf",
|
|
6
7
|
UriList = "text/uri-list",
|
|
7
8
|
VSCodeData = "vscode-editor-data",
|
|
8
9
|
Files = "Files"
|
|
@@ -10,3 +11,16 @@ export declare enum DataTransferType {
|
|
|
10
11
|
export declare function isFilesOnly({ types }: DataTransfer): boolean;
|
|
11
12
|
export declare function isFilesFromHtml({ types }: DataTransfer): boolean;
|
|
12
13
|
export declare function isImageFile(file: File): boolean;
|
|
14
|
+
export declare function isVSCode(data: DataTransfer): boolean;
|
|
15
|
+
export declare type VSCodeData = {
|
|
16
|
+
version: number;
|
|
17
|
+
isFromEmptySelection: boolean;
|
|
18
|
+
multicursorText: null | string;
|
|
19
|
+
mode: string;
|
|
20
|
+
[key: string]: unknown;
|
|
21
|
+
};
|
|
22
|
+
export declare function tryParseVSCodeData(data: DataTransfer): VSCodeData | undefined;
|
|
23
|
+
/**
|
|
24
|
+
* Checks if HTML conversion should be skipped based on clipboard contents.
|
|
25
|
+
*/
|
|
26
|
+
export declare function shouldSkipHtmlConversion(clipboardData: DataTransfer): boolean;
|
|
@@ -4,6 +4,7 @@ export var DataTransferType;
|
|
|
4
4
|
DataTransferType["Text"] = "text/plain";
|
|
5
5
|
DataTransferType["Html"] = "text/html";
|
|
6
6
|
DataTransferType["Yfm"] = "text/yfm";
|
|
7
|
+
DataTransferType["Rtf"] = "text/rtf";
|
|
7
8
|
DataTransferType["UriList"] = "text/uri-list";
|
|
8
9
|
DataTransferType["VSCodeData"] = "vscode-editor-data";
|
|
9
10
|
DataTransferType["Files"] = "Files";
|
|
@@ -19,3 +20,34 @@ export function isFilesFromHtml({ types }) {
|
|
|
19
20
|
export function isImageFile(file) {
|
|
20
21
|
return file.type.startsWith('image/');
|
|
21
22
|
}
|
|
23
|
+
export function isVSCode(data) {
|
|
24
|
+
return data.types.includes(DataTransferType.VSCodeData);
|
|
25
|
+
}
|
|
26
|
+
export function tryParseVSCodeData(data) {
|
|
27
|
+
try {
|
|
28
|
+
return JSON.parse(data.getData(DataTransferType.VSCodeData));
|
|
29
|
+
}
|
|
30
|
+
catch (e) {
|
|
31
|
+
console.error(e);
|
|
32
|
+
return undefined;
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* Checks if HTML conversion should be skipped based on clipboard contents.
|
|
37
|
+
*/
|
|
38
|
+
export function shouldSkipHtmlConversion(clipboardData) {
|
|
39
|
+
const hasHtml = clipboardData.types.includes(DataTransferType.Html);
|
|
40
|
+
// If there's no HTML content, skip conversion
|
|
41
|
+
if (!hasHtml)
|
|
42
|
+
return true;
|
|
43
|
+
// Check for standard HTML clipboard (text/plain + text/html)
|
|
44
|
+
if (clipboardData.types.length === 2)
|
|
45
|
+
return false;
|
|
46
|
+
// Check for WebStorm/Safari case (includes RTF)
|
|
47
|
+
if (clipboardData.types.length === 3) {
|
|
48
|
+
const rtf = clipboardData.getData(DataTransferType.Rtf);
|
|
49
|
+
return rtf.indexOf('\fmodern JetBrains') > 0;
|
|
50
|
+
}
|
|
51
|
+
// Skip conversion for any other cases
|
|
52
|
+
return true;
|
|
53
|
+
}
|
package/build/esm/version.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
/** During build process, the current version will be injected here */
|
|
2
|
-
export const VERSION = typeof '14.
|
|
2
|
+
export const VERSION = typeof '14.5.1' !== 'undefined' ? '14.5.1' : 'unknown';
|
package/build/styles.css
CHANGED
|
@@ -455,7 +455,7 @@
|
|
|
455
455
|
position: absolute;
|
|
456
456
|
inset: var(--g-md-toolbar-sticky-inset, -4px);
|
|
457
457
|
content: "";
|
|
458
|
-
border: 1px solid var(--g-color-line-generic-solid);
|
|
458
|
+
border: var(--g-md-toolbar-sticky-border, 1px solid var(--g-color-line-generic-solid));
|
|
459
459
|
border-radius: 4px;
|
|
460
460
|
background-color: var(--g-color-base-background);
|
|
461
461
|
}
|