cm-md-editor 1.0.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/README.md +15 -0
- package/index.html +54 -0
- package/package.json +17 -0
- package/src/MdEditor.js +106 -0
package/README.md
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
# cm-md-editor
|
|
2
|
+
|
|
3
|
+
This is a minimal **markdown editor** which is used in chessmail.de.
|
|
4
|
+
|
|
5
|
+
## Key features
|
|
6
|
+
|
|
7
|
+
- Vanilla JavaScript module, without dependencies
|
|
8
|
+
- Supports outlines with tab and shift-tab to indent and outdent
|
|
9
|
+
- Supports the **bold** syntax with command-b/ctrl-b
|
|
10
|
+
- Supports the _italic_ syntax with command-i/ctrl-i
|
|
11
|
+
- Supports undo and redo with command-z/ctrl-z and command-shift-z/ctrl-shift-z
|
|
12
|
+
- It is…
|
|
13
|
+
- lightweight
|
|
14
|
+
- easy to use
|
|
15
|
+
- fast
|
package/index.html
ADDED
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
<!doctype html>
|
|
2
|
+
<html lang="en" data-bs-theme="dark">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="UTF-8">
|
|
5
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
6
|
+
<title>extreme-minimal-md-editor</title>
|
|
7
|
+
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/css/bootstrap.min.css" rel="stylesheet"
|
|
8
|
+
integrity="sha384-T3c6CoIi6uLrA9TneNEoa7RxnatzjcDSCmG1MXxSR1GAsXEV/Dwwykc2MPK8M2HN" crossorigin="anonymous">
|
|
9
|
+
<style>
|
|
10
|
+
#editor {
|
|
11
|
+
width: 100%;
|
|
12
|
+
height: calc(100vh - 4rem);
|
|
13
|
+
padding: 0.7rem;
|
|
14
|
+
font-family: monospace;
|
|
15
|
+
white-space: pre-wrap;
|
|
16
|
+
overflow-wrap: break-word;
|
|
17
|
+
tab-size: 4;
|
|
18
|
+
line-height: 1.3;
|
|
19
|
+
}
|
|
20
|
+
</style>
|
|
21
|
+
</head>
|
|
22
|
+
<body class="p-2">
|
|
23
|
+
<div class="container-fluid">
|
|
24
|
+
<label for="editor">cm-md-editor</label>
|
|
25
|
+
<textarea id="editor">
|
|
26
|
+
# cm-md-editor
|
|
27
|
+
|
|
28
|
+
This is a minimal **markdown editor** which is used in chessmail.de.
|
|
29
|
+
|
|
30
|
+
## Key features
|
|
31
|
+
|
|
32
|
+
- Vanilla JavaScript module, without dependencies
|
|
33
|
+
- Supports outlines with tab and shift-tab to indent and outdent
|
|
34
|
+
- Supports the **bold** syntax with command-b/ctrl-b
|
|
35
|
+
- Supports the _italic_ syntax with command-i/ctrl-i
|
|
36
|
+
- Supports undo and redo with command-z/ctrl-z and command-shift-z/ctrl-shift-z
|
|
37
|
+
- It is…
|
|
38
|
+
- lightweight
|
|
39
|
+
- easy to use
|
|
40
|
+
- fast
|
|
41
|
+
|
|
42
|
+
☝️ [Check out the Demo page](https://shaack.com/projekte/cm-md-editor/)
|
|
43
|
+
</textarea>
|
|
44
|
+
</div>
|
|
45
|
+
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/js/bootstrap.bundle.min.js"
|
|
46
|
+
integrity="sha384-C6RzsynM9kWDrMNeT87bh95OGNyZPhcTNXj1NW7RuBCsyN/o0jlpcV8Qyq46cDfL"
|
|
47
|
+
crossorigin="anonymous"></script>
|
|
48
|
+
<script type="module">
|
|
49
|
+
import {MdEditor} from "./src/MdEditor.js"
|
|
50
|
+
|
|
51
|
+
new MdEditor(document.getElementById('editor'))
|
|
52
|
+
</script>
|
|
53
|
+
</body>
|
|
54
|
+
</html>
|
package/package.json
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "cm-md-editor",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "a simple markdown editor",
|
|
5
|
+
"main": "index.js",
|
|
6
|
+
"scripts": {
|
|
7
|
+
"test": "echo \"Error: no test specified\" && exit 1"
|
|
8
|
+
},
|
|
9
|
+
"keywords": [
|
|
10
|
+
"markdown",
|
|
11
|
+
"js",
|
|
12
|
+
"es6",
|
|
13
|
+
"web"
|
|
14
|
+
],
|
|
15
|
+
"author": "shaack.com",
|
|
16
|
+
"license": "MIT"
|
|
17
|
+
}
|
package/src/MdEditor.js
ADDED
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Author: Stefan Haack (https://shaack.com)
|
|
3
|
+
* Date: 2023-11-12
|
|
4
|
+
*/
|
|
5
|
+
export class MdEditor {
|
|
6
|
+
|
|
7
|
+
constructor(element) {
|
|
8
|
+
this.element = element
|
|
9
|
+
|
|
10
|
+
// listen to typing
|
|
11
|
+
this.element.addEventListener('keydown', (e) => this.handleKeyDown(e))
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
insertTextAtCursor(text) {
|
|
15
|
+
document.execCommand("insertText", false, text)
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
handleKeyDown(e) {
|
|
19
|
+
const start = this.element.selectionStart
|
|
20
|
+
const end = this.element.selectionEnd
|
|
21
|
+
const before = this.element.value.substring(0, start)
|
|
22
|
+
const selected = this.element.value.substring(start, end)
|
|
23
|
+
const currentLine = before.substring(before.lastIndexOf('\n') + 1)
|
|
24
|
+
const isListMode = currentLine.match(/^\t*- /)
|
|
25
|
+
if (e.key === 'Tab') {
|
|
26
|
+
e.preventDefault()
|
|
27
|
+
if (isListMode) {
|
|
28
|
+
if (!e.shiftKey) {
|
|
29
|
+
this.insertTabAtLineStart()
|
|
30
|
+
} else {
|
|
31
|
+
this.removeTab()
|
|
32
|
+
}
|
|
33
|
+
} else {
|
|
34
|
+
this.insertTabAtCursorPosition()
|
|
35
|
+
}
|
|
36
|
+
} else if (e.key === 'Enter') {
|
|
37
|
+
this.handleEnterKey(e)
|
|
38
|
+
} else if (e.ctrlKey || e.metaKey) {
|
|
39
|
+
if (e.key === 'b') { // bold
|
|
40
|
+
e.preventDefault()
|
|
41
|
+
if (selected) {
|
|
42
|
+
this.insertTextAtCursor('**' + selected + '**')
|
|
43
|
+
} else {
|
|
44
|
+
this.insertTextAtCursor('**')
|
|
45
|
+
}
|
|
46
|
+
} else if (e.key === 'i') { // italic
|
|
47
|
+
e.preventDefault()
|
|
48
|
+
if (selected) {
|
|
49
|
+
this.insertTextAtCursor('_' + selected + '_')
|
|
50
|
+
} else {
|
|
51
|
+
this.insertTextAtCursor('_')
|
|
52
|
+
}
|
|
53
|
+
} else if (e.key === 'e') { // game todo this could be an extension
|
|
54
|
+
e.preventDefault()
|
|
55
|
+
this.insertTextAtCursor('[game id="' + selected + '"]')
|
|
56
|
+
this.element.selectionStart = start + 10
|
|
57
|
+
this.element.selectionEnd = start + 10 + selected.length
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
handleEnterKey(e) {
|
|
63
|
+
const start = this.element.selectionStart
|
|
64
|
+
const before = this.element.value.substring(0, start)
|
|
65
|
+
const currentLine = before.substring(before.lastIndexOf('\n') + 1)
|
|
66
|
+
const matchEmpty = currentLine.match(/^(\s*- )$/)
|
|
67
|
+
const match = currentLine.match(/^(\s*- )/)
|
|
68
|
+
if(matchEmpty) {
|
|
69
|
+
e.preventDefault()
|
|
70
|
+
const pre = matchEmpty[1]
|
|
71
|
+
this.element.selectionStart = before.lastIndexOf('\n') + 1
|
|
72
|
+
this.element.selectionEnd = this.element.selectionStart + pre.length
|
|
73
|
+
this.insertTextAtCursor('')
|
|
74
|
+
} else if (match) {
|
|
75
|
+
e.preventDefault()
|
|
76
|
+
const pre = match[1]
|
|
77
|
+
this.insertTextAtCursor('\n' + pre)
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
insertTabAtCursorPosition() {
|
|
82
|
+
this.insertTextAtCursor('\t')
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
insertTabAtLineStart() {
|
|
86
|
+
const start = this.element.selectionStart
|
|
87
|
+
const before = this.element.value.substring(0, start)
|
|
88
|
+
const lineStart = before.lastIndexOf('\n') + 1
|
|
89
|
+
this.element.selectionStart = this.element.selectionEnd = lineStart
|
|
90
|
+
this.insertTextAtCursor('\t')
|
|
91
|
+
this.element.selectionStart = this.element.selectionEnd = start + 1
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
removeTab() {
|
|
95
|
+
const start = this.element.selectionStart
|
|
96
|
+
const before = this.element.value.substring(0, start)
|
|
97
|
+
const lineStart = before.lastIndexOf('\n') + 1
|
|
98
|
+
const currentLine = before.substring(lineStart)
|
|
99
|
+
if (currentLine.startsWith('\t')) {
|
|
100
|
+
this.element.selectionStart = lineStart
|
|
101
|
+
this.element.selectionEnd = lineStart + 1
|
|
102
|
+
this.insertTextAtCursor("")
|
|
103
|
+
this.element.selectionStart = this.element.selectionEnd = start - 1
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
}
|