cm-md-editor 2.1.0 → 2.2.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/index.html +16 -4
- package/package.json +1 -1
- package/src/MdEditor.js +136 -40
package/index.html
CHANGED
|
@@ -3,13 +3,16 @@
|
|
|
3
3
|
<head>
|
|
4
4
|
<meta charset="UTF-8">
|
|
5
5
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
6
|
-
<title>
|
|
6
|
+
<title>cm-md-editor</title>
|
|
7
7
|
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/css/bootstrap.min.css" rel="stylesheet"
|
|
8
8
|
integrity="sha384-T3c6CoIi6uLrA9TneNEoa7RxnatzjcDSCmG1MXxSR1GAsXEV/Dwwykc2MPK8M2HN" crossorigin="anonymous">
|
|
9
9
|
<style>
|
|
10
|
+
body {
|
|
11
|
+
padding: 1rem; margin-top: 0rem;
|
|
12
|
+
}
|
|
10
13
|
#editor {
|
|
11
14
|
width: 100%;
|
|
12
|
-
height: calc(100vh -
|
|
15
|
+
height: calc(100vh - 5rem);
|
|
13
16
|
padding: 0.7rem;
|
|
14
17
|
font-family: monospace;
|
|
15
18
|
tab-size: 4;
|
|
@@ -17,10 +20,15 @@
|
|
|
17
20
|
}
|
|
18
21
|
</style>
|
|
19
22
|
</head>
|
|
20
|
-
<body
|
|
23
|
+
<body>
|
|
21
24
|
<div class="container-fluid">
|
|
22
|
-
<label for="editor">cm-md-editor</label>
|
|
25
|
+
<label for="editor" style="display: none">cm-md-editor</label>
|
|
23
26
|
<textarea id="editor">
|
|
27
|
+
---
|
|
28
|
+
title: cm-md-editor
|
|
29
|
+
description: Some example text for the editor
|
|
30
|
+
---
|
|
31
|
+
|
|
24
32
|
# Heading 1
|
|
25
33
|
|
|
26
34
|
## Heading 2
|
|
@@ -29,6 +37,10 @@
|
|
|
29
37
|
|
|
30
38
|
This is a minimal **markdown editor** with _italic_ and **_bold italic_** text. You can also use ~~strikethrough~~ for deleted text.
|
|
31
39
|
|
|
40
|
+
<!-- This is a comment -->
|
|
41
|
+
|
|
42
|
+
<span>Some HTML</span>
|
|
43
|
+
|
|
32
44
|
Inline `code spans` are highlighted and protect their content: `**not bold**`.
|
|
33
45
|
|
|
34
46
|
> Blockquotes are highlighted with a green prefix.
|
package/package.json
CHANGED
package/src/MdEditor.js
CHANGED
|
@@ -4,8 +4,26 @@
|
|
|
4
4
|
*/
|
|
5
5
|
export class MdEditor {
|
|
6
6
|
|
|
7
|
-
constructor(element) {
|
|
7
|
+
constructor(element, props) {
|
|
8
8
|
this.element = element
|
|
9
|
+
this.props = {
|
|
10
|
+
syntaxHighlight: 1, // opacity of the highlighting, set 0 to disable
|
|
11
|
+
colorHeading: "100,160,255",
|
|
12
|
+
colorCode: "130,170,200",
|
|
13
|
+
colorComment: "128,128,128",
|
|
14
|
+
colorLink: "100,180,220",
|
|
15
|
+
colorBlockquote: "100,200,150",
|
|
16
|
+
colorList: "100,200,150",
|
|
17
|
+
colorStrikethrough: "255,100,100",
|
|
18
|
+
colorBold: "255,180,80",
|
|
19
|
+
colorItalic: "180,130,255",
|
|
20
|
+
colorHtmlTag: "200,120,120",
|
|
21
|
+
colorHorizontalRule: "128,128,200",
|
|
22
|
+
colorEscape: "128,128,128",
|
|
23
|
+
colorFrontMatter: "128,128,200",
|
|
24
|
+
toolbarButtons: ["h1", "h2", "h3", "bold", "italic", "ul", "ol", "link", "image"],
|
|
25
|
+
...props
|
|
26
|
+
}
|
|
9
27
|
this.element.addEventListener('keydown', (e) => this.handleKeyDown(e))
|
|
10
28
|
this.createToolbar()
|
|
11
29
|
this.createHighlightBackdrop()
|
|
@@ -19,21 +37,24 @@ export class MdEditor {
|
|
|
19
37
|
toolbar.style.cssText = 'display:flex;gap:1px;padding:2px;flex-wrap:wrap;background:rgba(128,128,128,0.15);border:1px solid rgba(128,128,128,0.3);border-bottom:none;border-radius:4px 4px 0 0;box-sizing:border-box;width:100%;'
|
|
20
38
|
wrapper.insertBefore(toolbar, this.element)
|
|
21
39
|
this.element.style.borderRadius = '0 0 4px 4px'
|
|
22
|
-
const
|
|
23
|
-
{title: 'Heading 1', icon: '<
|
|
24
|
-
{title: 'Heading 2', icon: '<
|
|
25
|
-
{title: 'Heading 3', icon: '<
|
|
26
|
-
{title: 'Bold', icon: '<
|
|
27
|
-
{title: 'Italic', icon: '<
|
|
28
|
-
{title: 'Unordered List', icon: '<
|
|
29
|
-
{title: 'Ordered List', icon: '<
|
|
40
|
+
const allButtons = [
|
|
41
|
+
{name: 'h1', title: 'Heading 1', icon: '<path d="M7.648 13V3H6.3v4.234H1.348V3H0v10h1.348V8.421H6.3V13zM14 13V3h-1.333l-2.381 1.766V6.12L12.6 4.443h.066V13z"/>', action: () => this.toggleHeading(1)},
|
|
42
|
+
{name: 'h2', title: 'Heading 2', icon: '<path d="M7.495 13V3.201H6.174v4.15H1.32V3.2H0V13h1.32V8.513h4.854V13zm3.174-7.071v-.05c0-.934.66-1.752 1.801-1.752 1.005 0 1.76.639 1.76 1.651 0 .898-.582 1.58-1.12 2.19l-3.69 4.2V13h6.331v-1.149h-4.458v-.079L13.9 8.786c.919-1.048 1.666-1.874 1.666-3.101C15.565 4.149 14.35 3 12.499 3 10.46 3 9.384 4.393 9.384 5.879v.05z"/>', action: () => this.toggleHeading(2)},
|
|
43
|
+
{name: 'h3', title: 'Heading 3', icon: '<path d="M11.07 8.4h1.049c1.174 0 1.99.69 2.004 1.724s-.802 1.786-2.068 1.779c-1.11-.007-1.905-.605-1.99-1.357h-1.21C8.926 11.91 10.116 13 12.028 13c1.99 0 3.439-1.188 3.404-2.87-.028-1.553-1.287-2.221-2.096-2.313v-.07c.724-.127 1.814-.935 1.772-2.293-.035-1.392-1.21-2.468-3.038-2.454-1.927.007-2.94 1.196-2.981 2.426h1.23c.064-.71.732-1.336 1.744-1.336 1.027 0 1.744.64 1.744 1.568.007.95-.738 1.639-1.744 1.639h-.991V8.4ZM7.495 13V3.201H6.174v4.15H1.32V3.2H0V13h1.32V8.513h4.854V13z"/>', action: () => this.toggleHeading(3)},
|
|
44
|
+
{name: 'bold', title: 'Bold', icon: '<path d="M8.21 13c2.106 0 3.412-1.087 3.412-2.823 0-1.306-.984-2.283-2.324-2.386v-.055a2.176 2.176 0 0 0 1.852-2.14c0-1.51-1.162-2.46-3.014-2.46H3.843V13zM5.908 4.674h1.696c.963 0 1.517.451 1.517 1.244 0 .834-.629 1.32-1.73 1.32H5.908V4.673zm0 6.788V8.598h1.73c1.217 0 1.88.492 1.88 1.415 0 .943-.643 1.449-1.832 1.449H5.907z"/>', action: () => this.toggleBold()},
|
|
45
|
+
{name: 'italic', title: 'Italic', icon: '<path d="M7.991 11.674 9.53 4.455c.123-.595.246-.71 1.347-.807l.11-.52H7.211l-.11.52c1.06.096 1.128.212 1.005.807L6.57 11.674c-.123.595-.246.71-1.346.806l-.11.52h3.774l.11-.52c-1.06-.095-1.129-.211-1.006-.806z"/>', action: () => this.toggleItalic()},
|
|
46
|
+
{name: 'ul', title: 'Unordered List', icon: '<path fill-rule="evenodd" d="M5 11.5a.5.5 0 0 1 .5-.5h9a.5.5 0 0 1 0 1h-9a.5.5 0 0 1-.5-.5m0-4a.5.5 0 0 1 .5-.5h9a.5.5 0 0 1 0 1h-9a.5.5 0 0 1-.5-.5m0-4a.5.5 0 0 1 .5-.5h9a.5.5 0 0 1 0 1h-9a.5.5 0 0 1-.5-.5m-3 1a1 1 0 1 0 0-2 1 1 0 0 0 0 2m0 4a1 1 0 1 0 0-2 1 1 0 0 0 0 2m0 4a1 1 0 1 0 0-2 1 1 0 0 0 0 2"/>', action: () => this.insertUnorderedList()},
|
|
47
|
+
{name: 'ol', title: 'Ordered List', icon: '<path fill-rule="evenodd" d="M5 11.5a.5.5 0 0 1 .5-.5h9a.5.5 0 0 1 0 1h-9a.5.5 0 0 1-.5-.5m0-4a.5.5 0 0 1 .5-.5h9a.5.5 0 0 1 0 1h-9a.5.5 0 0 1-.5-.5m0-4a.5.5 0 0 1 .5-.5h9a.5.5 0 0 1 0 1h-9a.5.5 0 0 1-.5-.5"/><path d="M1.713 11.865v-.474H2c.217 0 .363-.137.363-.317 0-.185-.158-.31-.361-.31-.223 0-.367.152-.373.31h-.59c.016-.467.373-.787.986-.787.588-.002.954.291.957.703a.595.595 0 0 1-.492.594v.033a.615.615 0 0 1 .569.631c.003.533-.502.8-1.051.8-.656 0-1-.37-1.008-.794h.582c.008.178.186.306.422.309.254 0 .424-.145.422-.35-.002-.195-.155-.348-.414-.348h-.3zm-.004-4.699h-.604v-.035c0-.408.295-.844.958-.844.583 0 .96.326.96.756 0 .389-.257.617-.476.848l-.537.572v.03h1.054V9H1.143v-.395l.957-.99c.138-.142.293-.304.293-.508 0-.18-.147-.32-.342-.32a.33.33 0 0 0-.342.338zM2.564 5h-.635V2.924h-.031l-.598.42v-.567l.629-.443h.635z"/>', action: () => this.insertOrderedList()},
|
|
48
|
+
{name: 'link', title: 'Insert Link', icon: '<path d="M4.715 6.542 3.343 7.914a3 3 0 1 0 4.243 4.243l1.828-1.829A3 3 0 0 0 8.586 5.5L8 6.086a1 1 0 0 0-.154.199 2 2 0 0 1 .861 3.337L6.88 11.45a2 2 0 1 1-2.83-2.83l.793-.792a4 4 0 0 1-.128-1.287z"/><path d="M6.586 4.672A3 3 0 0 0 7.414 9.5l.775-.776a2 2 0 0 1-.896-3.346L9.12 3.55a2 2 0 1 1 2.83 2.83l-.793.792c.112.42.155.855.128 1.287l1.372-1.372a3 3 0 1 0-4.243-4.243z"/>', action: () => this.insertLink()},
|
|
49
|
+
{name: 'image', title: 'Insert Image', icon: '<path d="M6.002 5.5a1.5 1.5 0 1 1-3 0 1.5 1.5 0 0 1 3 0"/><path d="M1.5 2A1.5 1.5 0 0 0 0 3.5v9A1.5 1.5 0 0 0 1.5 14h13a1.5 1.5 0 0 0 1.5-1.5v-9A1.5 1.5 0 0 0 14.5 2zm13 1a.5.5 0 0 1 .5.5v6l-3.775-1.947a.5.5 0 0 0-.577.093l-3.71 3.71-2.66-1.772a.5.5 0 0 0-.63.062L1.002 12v.54L1 12.5v-9a.5.5 0 0 1 .5-.5z"/>', action: () => this.insertImage()},
|
|
30
50
|
]
|
|
51
|
+
const buttons = allButtons.filter(btn => this.props.toolbarButtons.includes(btn.name))
|
|
31
52
|
buttons.forEach(btn => {
|
|
32
53
|
const button = document.createElement('button')
|
|
33
54
|
button.type = 'button'
|
|
34
55
|
button.title = btn.title
|
|
35
56
|
button.style.cssText = 'background:none;border:none;border-radius:3px;cursor:pointer;padding:4px 6px;display:flex;align-items:center;justify-content:center;color:inherit;opacity:0.6;transition:opacity 0.15s,background 0.15s;'
|
|
36
|
-
button.innerHTML = `<svg xmlns="http://www.w3.org/2000/svg" width="
|
|
57
|
+
button.innerHTML = `<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 16 16" fill="currentColor">${btn.icon}</svg>`
|
|
37
58
|
button.addEventListener('mouseenter', () => { button.style.opacity = '1'; button.style.background = 'rgba(128,128,128,0.2)' })
|
|
38
59
|
button.addEventListener('mouseleave', () => { button.style.opacity = '0.6'; button.style.background = 'none' })
|
|
39
60
|
button.addEventListener('mousedown', (e) => {
|
|
@@ -57,7 +78,7 @@ export class MdEditor {
|
|
|
57
78
|
this.wrapButton.type = 'button'
|
|
58
79
|
this.wrapButton.title = 'Toggle word wrap'
|
|
59
80
|
this.wrapButton.style.cssText = 'background:none;border:none;border-radius:3px;cursor:pointer;padding:4px 6px;display:flex;align-items:center;justify-content:center;color:inherit;opacity:0.6;transition:opacity 0.15s,background 0.15s;'
|
|
60
|
-
this.wrapButton.innerHTML = `<svg xmlns="http://www.w3.org/2000/svg" width="
|
|
81
|
+
this.wrapButton.innerHTML = `<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 16 16" fill="currentColor"><path fill-rule="evenodd" d="M2 3.5a.5.5 0 0 1 .5-.5h11a.5.5 0 0 1 0 1h-11a.5.5 0 0 1-.5-.5m0 4a.5.5 0 0 1 .5-.5h9a2.5 2.5 0 0 1 0 5h-1.293l.647.646a.5.5 0 0 1-.708.708l-1.5-1.5a.5.5 0 0 1 0-.708l1.5-1.5a.5.5 0 0 1 .708.708l-.647.646H11.5a1.5 1.5 0 0 0 0-3h-9a.5.5 0 0 1-.5-.5m0 4a.5.5 0 0 1 .5-.5H7a.5.5 0 0 1 0 1H2.5a.5.5 0 0 1-.5-.5"/></svg>`
|
|
61
82
|
this.wrapButton.addEventListener('mouseenter', () => { this.wrapButton.style.opacity = '1'; this.wrapButton.style.background = 'rgba(128,128,128,0.2)' })
|
|
62
83
|
this.wrapButton.addEventListener('mouseleave', () => { this.wrapButton.style.opacity = this.wrapEnabled ? '0.9' : '0.4'; this.wrapButton.style.background = 'none' })
|
|
63
84
|
this.wrapButton.addEventListener('mousedown', (e) => e.preventDefault())
|
|
@@ -70,6 +91,7 @@ export class MdEditor {
|
|
|
70
91
|
}
|
|
71
92
|
|
|
72
93
|
createHighlightBackdrop() {
|
|
94
|
+
if (!this.props.syntaxHighlight) return
|
|
73
95
|
const container = this.element.parentNode
|
|
74
96
|
container.style.position = 'relative'
|
|
75
97
|
this.backdrop = document.createElement('div')
|
|
@@ -79,7 +101,7 @@ export class MdEditor {
|
|
|
79
101
|
|
|
80
102
|
// Copy textarea computed styles to backdrop
|
|
81
103
|
const cs = window.getComputedStyle(this.element)
|
|
82
|
-
this.backdrop.style.cssText = `position:absolute;overflow:hidden;pointer-events:none;z-index:1;`
|
|
104
|
+
this.backdrop.style.cssText = `position:absolute;overflow:hidden;pointer-events:none;z-index:1;opacity:${this.props.syntaxHighlight};`
|
|
83
105
|
this.highlightLayer.style.cssText = `white-space:pre-wrap;word-wrap:break-word;overflow-wrap:break-word;color:transparent;`
|
|
84
106
|
|
|
85
107
|
const syncStyles = () => {
|
|
@@ -117,48 +139,92 @@ export class MdEditor {
|
|
|
117
139
|
this.updateHighlight()
|
|
118
140
|
}
|
|
119
141
|
|
|
142
|
+
colorSpan(colorProp, content) {
|
|
143
|
+
return '<span style="color:rgba(' + this.props[colorProp] + ',1)">' + content + '</span>'
|
|
144
|
+
}
|
|
145
|
+
|
|
120
146
|
updateHighlight() {
|
|
121
147
|
const text = this.element.value
|
|
122
148
|
const lines = text.split('\n')
|
|
123
149
|
let html = ''
|
|
124
150
|
let inCodeBlock = false
|
|
151
|
+
let inHtmlComment = false
|
|
152
|
+
let inFrontMatter = false
|
|
153
|
+
// YAML front matter must start at the very first line
|
|
154
|
+
if (lines.length > 0 && lines[0].trim() === '---') {
|
|
155
|
+
inFrontMatter = true
|
|
156
|
+
}
|
|
125
157
|
|
|
126
158
|
for (let i = 0; i < lines.length; i++) {
|
|
127
159
|
if (i > 0) html += '\n'
|
|
128
160
|
const line = lines[i]
|
|
129
161
|
|
|
162
|
+
// YAML front matter
|
|
163
|
+
if (inFrontMatter) {
|
|
164
|
+
html += this.colorSpan('colorFrontMatter', this.escapeHtml(line))
|
|
165
|
+
if (i > 0 && line.trim() === '---') {
|
|
166
|
+
inFrontMatter = false
|
|
167
|
+
}
|
|
168
|
+
continue
|
|
169
|
+
}
|
|
170
|
+
|
|
130
171
|
// Fenced code block delimiter
|
|
131
172
|
if (/^`{3,}/.test(line)) {
|
|
132
173
|
inCodeBlock = !inCodeBlock
|
|
133
|
-
html += '
|
|
174
|
+
html += this.colorSpan('colorCode', this.escapeHtml(line))
|
|
134
175
|
continue
|
|
135
176
|
}
|
|
136
177
|
if (inCodeBlock) {
|
|
137
|
-
html += '
|
|
178
|
+
html += this.colorSpan('colorCode', this.escapeHtml(line))
|
|
179
|
+
continue
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
// HTML comment handling (can span multiple lines)
|
|
183
|
+
if (inHtmlComment) {
|
|
184
|
+
const endIdx = line.indexOf('-->')
|
|
185
|
+
if (endIdx !== -1) {
|
|
186
|
+
inHtmlComment = false
|
|
187
|
+
html += this.colorSpan('colorComment', this.escapeHtml(line.substring(0, endIdx + 3)))
|
|
188
|
+
html += this.highlightInline(line.substring(endIdx + 3))
|
|
189
|
+
} else {
|
|
190
|
+
html += this.colorSpan('colorComment', this.escapeHtml(line))
|
|
191
|
+
}
|
|
192
|
+
continue
|
|
193
|
+
}
|
|
194
|
+
if (line.trimStart().startsWith('<!--')) {
|
|
195
|
+
const endIdx = line.indexOf('-->', line.indexOf('<!--') + 4)
|
|
196
|
+
if (endIdx !== -1) {
|
|
197
|
+
// Single-line comment
|
|
198
|
+
html += this.colorSpan('colorComment', this.escapeHtml(line))
|
|
199
|
+
} else {
|
|
200
|
+
// Multi-line comment starts
|
|
201
|
+
inHtmlComment = true
|
|
202
|
+
html += this.colorSpan('colorComment', this.escapeHtml(line))
|
|
203
|
+
}
|
|
138
204
|
continue
|
|
139
205
|
}
|
|
140
206
|
|
|
141
207
|
// Horizontal rule (3+ of same -, *, or _ with optional spaces)
|
|
142
208
|
if (/^\s{0,3}([-*_])\s*(\1\s*){2,}$/.test(line)) {
|
|
143
|
-
html += '
|
|
209
|
+
html += this.colorSpan('colorHorizontalRule', this.escapeHtml(line))
|
|
144
210
|
continue
|
|
145
211
|
}
|
|
146
212
|
|
|
147
213
|
// Headings
|
|
148
214
|
const headingMatch = line.match(/^(#{1,6}) /)
|
|
149
215
|
if (headingMatch) {
|
|
150
|
-
const opacity = Math.max(0.3,
|
|
151
|
-
html += '<span style="color:rgba(
|
|
216
|
+
const opacity = Math.max(0.3, 1 - (headingMatch[1].length - 1) * 0.1)
|
|
217
|
+
html += '<span style="color:rgba(' + this.props.colorHeading + ',' + opacity + ')">' + this.escapeHtml(line) + '</span>'
|
|
152
218
|
continue
|
|
153
219
|
}
|
|
154
220
|
|
|
155
221
|
// Reference link definition [ref]: url
|
|
156
222
|
const refMatch = line.match(/^(\s{0,3}\[)([^\]]+)(\]:\s+)(.+)$/)
|
|
157
223
|
if (refMatch) {
|
|
158
|
-
html += '
|
|
159
|
-
+ '
|
|
160
|
-
+ '
|
|
161
|
-
+ '
|
|
224
|
+
html += this.colorSpan('colorLink', this.escapeHtml(refMatch[1]))
|
|
225
|
+
+ this.colorSpan('colorLink', this.escapeHtml(refMatch[2]))
|
|
226
|
+
+ this.colorSpan('colorLink', this.escapeHtml(refMatch[3]))
|
|
227
|
+
+ this.colorSpan('colorLink', this.escapeHtml(refMatch[4]))
|
|
162
228
|
continue
|
|
163
229
|
}
|
|
164
230
|
|
|
@@ -167,7 +233,7 @@ export class MdEditor {
|
|
|
167
233
|
let rest = line
|
|
168
234
|
const bqMatch = line.match(/^(\s*>+\s?)/)
|
|
169
235
|
if (bqMatch) {
|
|
170
|
-
prefix = '
|
|
236
|
+
prefix = this.colorSpan('colorBlockquote', this.escapeHtml(bqMatch[0]))
|
|
171
237
|
rest = line.substring(bqMatch[0].length)
|
|
172
238
|
}
|
|
173
239
|
|
|
@@ -201,7 +267,7 @@ export class MdEditor {
|
|
|
201
267
|
let result = ''
|
|
202
268
|
for (const seg of segments) {
|
|
203
269
|
if (seg.type === 'code') {
|
|
204
|
-
result += '
|
|
270
|
+
result += this.colorSpan('colorCode', this.escapeHtml(seg.content))
|
|
205
271
|
} else {
|
|
206
272
|
result += this.highlightTextSegment(this.escapeHtml(seg.content))
|
|
207
273
|
}
|
|
@@ -211,47 +277,48 @@ export class MdEditor {
|
|
|
211
277
|
|
|
212
278
|
highlightTextSegment(escaped) {
|
|
213
279
|
let result = escaped
|
|
280
|
+
const c = (prop) => this.props[prop]
|
|
214
281
|
|
|
215
282
|
// Escape sequences: dim the backslash before markdown punctuation
|
|
216
283
|
result = result.replace(/\\([\\`*_{}[\]()#+\-.!~|])/g,
|
|
217
|
-
'<span style="color:rgba(
|
|
284
|
+
'<span style="color:rgba(' + c('colorEscape') + ',1)">\\</span>$1')
|
|
218
285
|
|
|
219
286
|
// Unordered list markers with optional task list checkbox
|
|
220
287
|
result = result.replace(/^(\t*)(- )(\[[ xX]\] )?/, (_, tabs, marker, task) => {
|
|
221
|
-
let r = tabs + '
|
|
288
|
+
let r = tabs + this.colorSpan('colorList', marker)
|
|
222
289
|
if (task) {
|
|
223
|
-
r += '
|
|
290
|
+
r += this.colorSpan('colorList', task)
|
|
224
291
|
}
|
|
225
292
|
return r
|
|
226
293
|
})
|
|
227
294
|
|
|
228
295
|
// Ordered list markers
|
|
229
296
|
result = result.replace(/^(\t*)(\d+\. )/, (_, tabs, marker) =>
|
|
230
|
-
tabs + '
|
|
297
|
+
tabs + this.colorSpan('colorList', marker))
|
|
231
298
|
|
|
232
299
|
// Images  and Links [text](url)
|
|
233
|
-
result = result.replace(/(!?\[)(.*?)(\]\()(.+?)(\))/g,
|
|
234
|
-
'
|
|
300
|
+
result = result.replace(/(!?\[)(.*?)(\]\()(.+?)(\))/g, (_, p1, p2, p3, p4, p5) =>
|
|
301
|
+
this.colorSpan('colorLink', p1) + this.colorSpan('colorLink', p2) + this.colorSpan('colorLink', p3) + this.colorSpan('colorLink', p4) + this.colorSpan('colorLink', p5))
|
|
235
302
|
|
|
236
303
|
// Reference links [text][ref]
|
|
237
|
-
result = result.replace(/(\[)(.*?)(\]\[)(.*?)(\])/g,
|
|
238
|
-
'
|
|
304
|
+
result = result.replace(/(\[)(.*?)(\]\[)(.*?)(\])/g, (_, p1, p2, p3, p4, p5) =>
|
|
305
|
+
this.colorSpan('colorLink', p1) + this.colorSpan('colorLink', p2) + this.colorSpan('colorLink', p3) + this.colorSpan('colorLink', p4) + this.colorSpan('colorLink', p5))
|
|
239
306
|
|
|
240
307
|
// Strikethrough ~~text~~
|
|
241
|
-
result = result.replace(/(~~)(.*?)(~~)/g,
|
|
242
|
-
'
|
|
308
|
+
result = result.replace(/(~~)(.*?)(~~)/g, (_, p1, p2, p3) =>
|
|
309
|
+
this.colorSpan('colorStrikethrough', p1) + this.colorSpan('colorStrikethrough', p2) + this.colorSpan('colorStrikethrough', p3))
|
|
243
310
|
|
|
244
311
|
// Bold **text**
|
|
245
|
-
result = result.replace(/(\*\*)(.*?)(\*\*)/g,
|
|
246
|
-
'
|
|
312
|
+
result = result.replace(/(\*\*)(.*?)(\*\*)/g, (_, p1, p2, p3) =>
|
|
313
|
+
this.colorSpan('colorBold', p1) + this.colorSpan('colorBold', p2) + this.colorSpan('colorBold', p3))
|
|
247
314
|
|
|
248
315
|
// Italic _text_
|
|
249
|
-
result = result.replace(/((?:^|[^\\]))(\_)(.*?[^\\])(\_)/g,
|
|
250
|
-
|
|
316
|
+
result = result.replace(/((?:^|[^\\]))(\_)(.*?[^\\])(\_)/g, (_, pre, p1, p2, p3) =>
|
|
317
|
+
pre + this.colorSpan('colorItalic', p1) + this.colorSpan('colorItalic', p2) + this.colorSpan('colorItalic', p3))
|
|
251
318
|
|
|
252
319
|
// HTML tags
|
|
253
|
-
result = result.replace(/(<)(\/?[a-zA-Z]\w*)(.*?)(>)/g,
|
|
254
|
-
'
|
|
320
|
+
result = result.replace(/(<)(\/?[a-zA-Z]\w*)(.*?)(>)/g, (_, p1, p2, p3, p4) =>
|
|
321
|
+
this.colorSpan('colorHtmlTag', p1 + p2 + p3 + p4))
|
|
255
322
|
|
|
256
323
|
return result
|
|
257
324
|
}
|
|
@@ -274,7 +341,7 @@ export class MdEditor {
|
|
|
274
341
|
getCurrentLineInfo() {
|
|
275
342
|
const start = this.element.selectionStart
|
|
276
343
|
const text = this.element.value
|
|
277
|
-
const lineStart = text.lastIndexOf('\n', start - 1) + 1
|
|
344
|
+
const lineStart = start === 0 ? 0 : text.lastIndexOf('\n', start - 1) + 1
|
|
278
345
|
let lineEnd = text.indexOf('\n', start)
|
|
279
346
|
if (lineEnd === -1) lineEnd = text.length
|
|
280
347
|
const line = text.substring(lineStart, lineEnd)
|
|
@@ -287,6 +354,7 @@ export class MdEditor {
|
|
|
287
354
|
}
|
|
288
355
|
|
|
289
356
|
toggleHeading(level) {
|
|
357
|
+
this.element.focus()
|
|
290
358
|
const {lineStart, lineEnd, line} = this.getCurrentLineInfo()
|
|
291
359
|
const prefix = '#'.repeat(level) + ' '
|
|
292
360
|
const headingMatch = line.match(/^(#{1,6}) /)
|
|
@@ -355,6 +423,34 @@ export class MdEditor {
|
|
|
355
423
|
}
|
|
356
424
|
}
|
|
357
425
|
|
|
426
|
+
insertLink() {
|
|
427
|
+
const start = this.element.selectionStart
|
|
428
|
+
const end = this.element.selectionEnd
|
|
429
|
+
const selected = this.element.value.substring(start, end)
|
|
430
|
+
const url = prompt('Enter URL:')
|
|
431
|
+
if (url === null) return
|
|
432
|
+
const linkText = selected || 'link'
|
|
433
|
+
this.element.focus()
|
|
434
|
+
this.selectLineRange(start, end)
|
|
435
|
+
this.insertTextAtCursor('[' + linkText + '](' + url + ')')
|
|
436
|
+
this.element.selectionStart = start + 1
|
|
437
|
+
this.element.selectionEnd = start + 1 + linkText.length
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
insertImage() {
|
|
441
|
+
const start = this.element.selectionStart
|
|
442
|
+
const end = this.element.selectionEnd
|
|
443
|
+
const selected = this.element.value.substring(start, end)
|
|
444
|
+
const url = prompt('Enter image URL:')
|
|
445
|
+
if (url === null) return
|
|
446
|
+
const altText = selected || 'image'
|
|
447
|
+
this.element.focus()
|
|
448
|
+
this.selectLineRange(start, end)
|
|
449
|
+
this.insertTextAtCursor('')
|
|
450
|
+
this.element.selectionStart = start + 2
|
|
451
|
+
this.element.selectionEnd = start + 2 + altText.length
|
|
452
|
+
}
|
|
453
|
+
|
|
358
454
|
insertTextAtCursor(text) {
|
|
359
455
|
// execCommand is deprecated, but without alternative to insert text and preserve the correct undo/redo stack
|
|
360
456
|
document.execCommand("insertText", false, text)
|