markdown-text-editor 0.0.24-beta.3 → 0.1.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.
- package/LICENSE +20 -20
- package/README.md +277 -268
- package/dist/index.css.map +1 -1
- package/dist/index.js.map +1 -1
- package/dist/markdown-text-editor.css.map +1 -1
- package/dist/markdown-text-editor.js +1 -1
- package/dist/markdown-text-editor.js.map +1 -1
- package/package.json +67 -65
- package/src/plugins/index.js +2 -2
- package/src/plugins/markdown/Toolbar/MakeTool.js +63 -0
- package/src/plugins/markdown/Toolbar/index.js +42 -36
- package/src/plugins/markdown/Toolbar/tools/BoldTool.js +14 -14
- package/src/plugins/markdown/Toolbar/tools/CheckListTool.js +18 -0
- package/src/plugins/markdown/Toolbar/tools/ItalicTool.js +16 -16
- package/src/plugins/markdown/Toolbar/tools/OLTool.js +20 -0
- package/src/plugins/markdown/Toolbar/tools/PreviewTool.js +98 -98
- package/src/plugins/markdown/Toolbar/tools/StrikethroughTool.js +15 -15
- package/src/plugins/markdown/Toolbar/tools/ULTool.js +18 -0
- package/src/plugins/markdown/editor.js +141 -141
- package/src/plugins/markdown/preview.js +16 -16
- package/src/plugins/markdown/styles/main.css +2 -2
- package/src/plugins/markdown/utils/markdownUtils.js +7 -7
- package/src/plugins/markdown/Toolbar/tools/MarkdownTool.js +0 -40
|
@@ -1,142 +1,142 @@
|
|
|
1
|
-
// #markdown\editor.js
|
|
2
|
-
import './styles/main.css';
|
|
3
|
-
import { marked } from 'marked';
|
|
4
|
-
import Toolbar from './Toolbar/index.js';
|
|
5
|
-
import Preview from './preview.js';
|
|
6
|
-
|
|
7
|
-
marked.setOptions({
|
|
8
|
-
breaks: true
|
|
9
|
-
});
|
|
10
|
-
|
|
11
|
-
class MarkdownEditor {
|
|
12
|
-
constructor(selector, options = {}) {
|
|
13
|
-
this.usertextarea = typeof selector === 'string' ? document.querySelector(selector) : selector;
|
|
14
|
-
this.options = options;
|
|
15
|
-
this.preview = this.options.toolbar.includes('preview');
|
|
16
|
-
this.init();
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
init() {
|
|
20
|
-
this.createEditor();
|
|
21
|
-
if (this.options.toolbar) this.addToolbar();
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
createEditor() {
|
|
25
|
-
if (!this.isTextareaValid()) return;
|
|
26
|
-
|
|
27
|
-
this.applyDefaultAttributes();
|
|
28
|
-
this.buildEditorLayout();
|
|
29
|
-
this.addInputListener();
|
|
30
|
-
this.render();
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
isTextareaValid() {
|
|
34
|
-
return this.usertextarea.tagName === 'TEXTAREA';
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
applyDefaultAttributes() {
|
|
38
|
-
this.usertextarea.classList.add(
|
|
39
|
-
"focus:outline-0",
|
|
40
|
-
"border-0",
|
|
41
|
-
"p-1.5",
|
|
42
|
-
"max-w-full",
|
|
43
|
-
"size-full",
|
|
44
|
-
"bg-transparent",
|
|
45
|
-
"outline-0",
|
|
46
|
-
"appearance-none",
|
|
47
|
-
"prose",
|
|
48
|
-
"prose-sm",
|
|
49
|
-
"md:prose-base",
|
|
50
|
-
"dark:prose-invert",
|
|
51
|
-
"text-stone-700",
|
|
52
|
-
"dark:text-stone-200",
|
|
53
|
-
"overflow-y-auto"
|
|
54
|
-
);
|
|
55
|
-
if (!this.usertextarea.hasAttribute('placeholder')) {
|
|
56
|
-
this.usertextarea.placeholder = this.options.placeholder || 'Write your markdown...';
|
|
57
|
-
}
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
buildEditorLayout() {
|
|
61
|
-
this.editorContainer = document.createElement('div');
|
|
62
|
-
this.editorContainer.className = `
|
|
63
|
-
markdown-editor-wrapper
|
|
64
|
-
border border-stone-200
|
|
65
|
-
dark:border-stone-700
|
|
66
|
-
rounded-md
|
|
67
|
-
overflow-hidden
|
|
68
|
-
`;
|
|
69
|
-
this.usertextarea.parentNode.insertBefore(this.editorContainer, this.usertextarea);
|
|
70
|
-
|
|
71
|
-
this.markdownEditorDiv = document.createElement('div');
|
|
72
|
-
this.markdownEditorDiv.className = `editor-layout`;
|
|
73
|
-
this.editorContainer.appendChild(this.markdownEditorDiv);
|
|
74
|
-
|
|
75
|
-
this.addTextareaWrapper();
|
|
76
|
-
if (this.preview) this.addPreviewWrapper();
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
addTextareaWrapper() {
|
|
80
|
-
const textareaContainer = document.createElement('div');
|
|
81
|
-
textareaContainer.className = `
|
|
82
|
-
textarea-wrapper
|
|
83
|
-
p-2
|
|
84
|
-
bg-white
|
|
85
|
-
dark:bg-stone-800
|
|
86
|
-
grid
|
|
87
|
-
after:px-3.5
|
|
88
|
-
after:py-2.5
|
|
89
|
-
after:text-inherit
|
|
90
|
-
[&>textarea]:resize-none
|
|
91
|
-
[&>textarea]:[grid-area:1/1/2/2]
|
|
92
|
-
after:[grid-area:1/1/2/2]
|
|
93
|
-
after:whitespace-pre-wrap
|
|
94
|
-
after:invisible
|
|
95
|
-
after:content-[attr(data-cloned-val)_'_']
|
|
96
|
-
after:border
|
|
97
|
-
`;
|
|
98
|
-
textareaContainer.appendChild(this.usertextarea);
|
|
99
|
-
this.markdownEditorDiv.appendChild(textareaContainer);
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
addPreviewWrapper() {
|
|
105
|
-
const preview = new Preview(this.markdownEditorDiv);
|
|
106
|
-
this.previewContent = preview.getPreviewContent();
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
addInputListener() {
|
|
110
|
-
this.usertextarea.addEventListener('input', () => this.render());
|
|
111
|
-
this.usertextarea.addEventListener('scroll', () => {
|
|
112
|
-
const textarea = this.usertextarea;
|
|
113
|
-
const previewPane = this.previewContent;
|
|
114
|
-
|
|
115
|
-
// Calculate the proportion of the textarea that has been scrolled
|
|
116
|
-
const textareaScrollRatio = textarea.scrollTop / (textarea.scrollHeight - textarea.clientHeight);
|
|
117
|
-
|
|
118
|
-
// Apply the same scroll ratio to the preview pane
|
|
119
|
-
previewPane.scrollTop = textareaScrollRatio * (previewPane.scrollHeight - previewPane.clientHeight);
|
|
120
|
-
});
|
|
121
|
-
}
|
|
122
|
-
|
|
123
|
-
addToolbar() {
|
|
124
|
-
new Toolbar(this, this.options.toolbar || ['bold', 'italic', 'strikethrough']);
|
|
125
|
-
}
|
|
126
|
-
|
|
127
|
-
insertText(text) {
|
|
128
|
-
const { selectionStart, selectionEnd } = this.usertextarea;
|
|
129
|
-
const value = this.usertextarea.value;
|
|
130
|
-
this.usertextarea.value = `${value.substring(0, selectionStart)}${text}${value.substring(selectionEnd)}`;
|
|
131
|
-
this.usertextarea.focus();
|
|
132
|
-
this.usertextarea.setSelectionRange(selectionStart, selectionStart + text.length);
|
|
133
|
-
this.render();
|
|
134
|
-
}
|
|
135
|
-
|
|
136
|
-
render() {
|
|
137
|
-
const html = marked(this.usertextarea.value);
|
|
138
|
-
this.previewContent.innerHTML = html;
|
|
139
|
-
}
|
|
140
|
-
}
|
|
141
|
-
|
|
1
|
+
// #markdown\editor.js
|
|
2
|
+
import './styles/main.css';
|
|
3
|
+
import { marked } from 'marked';
|
|
4
|
+
import Toolbar from './Toolbar/index.js';
|
|
5
|
+
import Preview from './preview.js';
|
|
6
|
+
|
|
7
|
+
marked.setOptions({
|
|
8
|
+
breaks: true
|
|
9
|
+
});
|
|
10
|
+
|
|
11
|
+
class MarkdownEditor {
|
|
12
|
+
constructor(selector, options = {}) {
|
|
13
|
+
this.usertextarea = typeof selector === 'string' ? document.querySelector(selector) : selector;
|
|
14
|
+
this.options = options;
|
|
15
|
+
this.preview = this.options.toolbar.includes('preview');
|
|
16
|
+
this.init();
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
init() {
|
|
20
|
+
this.createEditor();
|
|
21
|
+
if (this.options.toolbar) this.addToolbar();
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
createEditor() {
|
|
25
|
+
if (!this.isTextareaValid()) return;
|
|
26
|
+
|
|
27
|
+
this.applyDefaultAttributes();
|
|
28
|
+
this.buildEditorLayout();
|
|
29
|
+
this.addInputListener();
|
|
30
|
+
this.render();
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
isTextareaValid() {
|
|
34
|
+
return this.usertextarea.tagName === 'TEXTAREA';
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
applyDefaultAttributes() {
|
|
38
|
+
this.usertextarea.classList.add(
|
|
39
|
+
"focus:outline-0",
|
|
40
|
+
"border-0",
|
|
41
|
+
"p-1.5",
|
|
42
|
+
"max-w-full",
|
|
43
|
+
"size-full",
|
|
44
|
+
"bg-transparent",
|
|
45
|
+
"outline-0",
|
|
46
|
+
"appearance-none",
|
|
47
|
+
"prose",
|
|
48
|
+
"prose-sm",
|
|
49
|
+
"md:prose-base",
|
|
50
|
+
"dark:prose-invert",
|
|
51
|
+
"text-stone-700",
|
|
52
|
+
"dark:text-stone-200",
|
|
53
|
+
"overflow-y-auto"
|
|
54
|
+
);
|
|
55
|
+
if (!this.usertextarea.hasAttribute('placeholder')) {
|
|
56
|
+
this.usertextarea.placeholder = this.options.placeholder || 'Write your markdown...';
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
buildEditorLayout() {
|
|
61
|
+
this.editorContainer = document.createElement('div');
|
|
62
|
+
this.editorContainer.className = `
|
|
63
|
+
markdown-editor-wrapper
|
|
64
|
+
border border-stone-200
|
|
65
|
+
dark:border-stone-700
|
|
66
|
+
rounded-md
|
|
67
|
+
overflow-hidden
|
|
68
|
+
`;
|
|
69
|
+
this.usertextarea.parentNode.insertBefore(this.editorContainer, this.usertextarea);
|
|
70
|
+
|
|
71
|
+
this.markdownEditorDiv = document.createElement('div');
|
|
72
|
+
this.markdownEditorDiv.className = `editor-layout`;
|
|
73
|
+
this.editorContainer.appendChild(this.markdownEditorDiv);
|
|
74
|
+
|
|
75
|
+
this.addTextareaWrapper();
|
|
76
|
+
if (this.preview) this.addPreviewWrapper();
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
addTextareaWrapper() {
|
|
80
|
+
const textareaContainer = document.createElement('div');
|
|
81
|
+
textareaContainer.className = `
|
|
82
|
+
textarea-wrapper
|
|
83
|
+
p-2
|
|
84
|
+
bg-white
|
|
85
|
+
dark:bg-stone-800
|
|
86
|
+
grid
|
|
87
|
+
after:px-3.5
|
|
88
|
+
after:py-2.5
|
|
89
|
+
after:text-inherit
|
|
90
|
+
[&>textarea]:resize-none
|
|
91
|
+
[&>textarea]:[grid-area:1/1/2/2]
|
|
92
|
+
after:[grid-area:1/1/2/2]
|
|
93
|
+
after:whitespace-pre-wrap
|
|
94
|
+
after:invisible
|
|
95
|
+
after:content-[attr(data-cloned-val)_'_']
|
|
96
|
+
after:border
|
|
97
|
+
`;
|
|
98
|
+
textareaContainer.appendChild(this.usertextarea);
|
|
99
|
+
this.markdownEditorDiv.appendChild(textareaContainer);
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
addPreviewWrapper() {
|
|
105
|
+
const preview = new Preview(this.markdownEditorDiv);
|
|
106
|
+
this.previewContent = preview.getPreviewContent();
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
addInputListener() {
|
|
110
|
+
this.usertextarea.addEventListener('input', () => this.render());
|
|
111
|
+
this.usertextarea.addEventListener('scroll', () => {
|
|
112
|
+
const textarea = this.usertextarea;
|
|
113
|
+
const previewPane = this.previewContent;
|
|
114
|
+
|
|
115
|
+
// Calculate the proportion of the textarea that has been scrolled
|
|
116
|
+
const textareaScrollRatio = textarea.scrollTop / (textarea.scrollHeight - textarea.clientHeight);
|
|
117
|
+
|
|
118
|
+
// Apply the same scroll ratio to the preview pane
|
|
119
|
+
previewPane.scrollTop = textareaScrollRatio * (previewPane.scrollHeight - previewPane.clientHeight);
|
|
120
|
+
});
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
addToolbar() {
|
|
124
|
+
new Toolbar(this, this.options.toolbar || ['bold', 'italic', 'strikethrough']);
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
insertText(text) {
|
|
128
|
+
const { selectionStart, selectionEnd } = this.usertextarea;
|
|
129
|
+
const value = this.usertextarea.value;
|
|
130
|
+
this.usertextarea.value = `${value.substring(0, selectionStart)}${text}${value.substring(selectionEnd)}`;
|
|
131
|
+
this.usertextarea.focus();
|
|
132
|
+
this.usertextarea.setSelectionRange(selectionStart, selectionStart + text.length);
|
|
133
|
+
this.render();
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
render() {
|
|
137
|
+
const html = marked(this.usertextarea.value);
|
|
138
|
+
this.previewContent.innerHTML = html;
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
|
|
142
142
|
export default MarkdownEditor;
|
|
@@ -1,17 +1,17 @@
|
|
|
1
|
-
// markdown\preview.js
|
|
2
|
-
class Preview {
|
|
3
|
-
constructor(container) {
|
|
4
|
-
this.previewContainer = document.createElement('div');
|
|
5
|
-
this.previewContainer.className = 'preview-wrapper bg-white dark:bg-stone-800 p-2 hidden';
|
|
6
|
-
this.previewContent = document.createElement('div');
|
|
7
|
-
this.previewContent.className = 'preview-content prose prose-sm md:prose-base dark:prose-invert p-1.5 overflow-y-auto h-[90lvh] max-w-max';
|
|
8
|
-
this.previewContainer.appendChild(this.previewContent);
|
|
9
|
-
container.appendChild(this.previewContainer);
|
|
10
|
-
}
|
|
11
|
-
|
|
12
|
-
getPreviewContent() {
|
|
13
|
-
return this.previewContent;
|
|
14
|
-
}
|
|
15
|
-
}
|
|
16
|
-
|
|
1
|
+
// markdown\preview.js
|
|
2
|
+
class Preview {
|
|
3
|
+
constructor(container) {
|
|
4
|
+
this.previewContainer = document.createElement('div');
|
|
5
|
+
this.previewContainer.className = 'preview-wrapper bg-white dark:bg-stone-800 p-2 hidden';
|
|
6
|
+
this.previewContent = document.createElement('div');
|
|
7
|
+
this.previewContent.className = 'preview-content prose prose-sm md:prose-base dark:prose-invert p-1.5 overflow-y-auto h-[90lvh] max-w-max';
|
|
8
|
+
this.previewContainer.appendChild(this.previewContent);
|
|
9
|
+
container.appendChild(this.previewContainer);
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
getPreviewContent() {
|
|
13
|
+
return this.previewContent;
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
|
|
17
17
|
export default Preview;
|
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
@tailwind base;
|
|
2
|
-
@tailwind components;
|
|
1
|
+
@tailwind base;
|
|
2
|
+
@tailwind components;
|
|
3
3
|
@tailwind utilities;
|
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
// utils/markdownUtils.js
|
|
2
|
-
|
|
3
|
-
import { marked } from 'marked';
|
|
4
|
-
|
|
5
|
-
export function renderMarkdown(content) {
|
|
6
|
-
return marked(content);
|
|
7
|
-
}
|
|
1
|
+
// utils/markdownUtils.js
|
|
2
|
+
|
|
3
|
+
import { marked } from 'marked';
|
|
4
|
+
|
|
5
|
+
export function renderMarkdown(content) {
|
|
6
|
+
return marked(content);
|
|
7
|
+
}
|
|
@@ -1,40 +0,0 @@
|
|
|
1
|
-
// #components\Toolbar\tools\MarkdownTool.js
|
|
2
|
-
class MarkdownTool {
|
|
3
|
-
constructor(editor, syntax, defaultText) {
|
|
4
|
-
this.editor = editor;
|
|
5
|
-
this.syntax = syntax; // Markdown syntax (e.g., ** for bold, * for italic)
|
|
6
|
-
this.defaultText = defaultText; // Default text if nothing is selected
|
|
7
|
-
this.button = this.createButton();
|
|
8
|
-
}
|
|
9
|
-
|
|
10
|
-
// Create a button element (can be overridden in child classes)
|
|
11
|
-
createButton(iconHtml) {
|
|
12
|
-
const button = document.createElement('button');
|
|
13
|
-
button.innerHTML = iconHtml; // Pass icon HTML from child classes
|
|
14
|
-
button.type='button';
|
|
15
|
-
button.className = 'markdown-btn p-2 hover:bg-stone-200 dark:hover:bg-stone-600 rounded duration-300';
|
|
16
|
-
button.addEventListener('click', () => this.applySyntax());
|
|
17
|
-
return button;
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
// Toggle markdown syntax at the current cursor position
|
|
21
|
-
applySyntax() {
|
|
22
|
-
const textarea = this.editor.usertextarea;
|
|
23
|
-
const { selectionStart, selectionEnd } = textarea;
|
|
24
|
-
const selectedText = textarea.value.substring(selectionStart, selectionEnd);
|
|
25
|
-
|
|
26
|
-
const syntaxLength = this.syntax.length;
|
|
27
|
-
|
|
28
|
-
if (selectedText.startsWith(this.syntax) && selectedText.endsWith(this.syntax)) {
|
|
29
|
-
// If text is already wrapped with the markdown syntax, remove it
|
|
30
|
-
const unformattedText = selectedText.slice(syntaxLength, -syntaxLength);
|
|
31
|
-
this.editor.insertText(unformattedText);
|
|
32
|
-
} else {
|
|
33
|
-
// If no text is selected or it's not wrapped, add the markdown syntax
|
|
34
|
-
const newText = `${this.syntax}${selectedText || this.defaultText}${this.syntax}`;
|
|
35
|
-
this.editor.insertText(newText);
|
|
36
|
-
}
|
|
37
|
-
}
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
export default MarkdownTool;
|