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 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
+ }
@@ -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
+ }