diff options
Diffstat (limited to 'src/editor.mjs')
-rw-r--r-- | src/editor.mjs | 548 |
1 files changed, 274 insertions, 274 deletions
diff --git a/src/editor.mjs b/src/editor.mjs index 6f529ed..4c50e79 100644 --- a/src/editor.mjs +++ b/src/editor.mjs | |||
@@ -1,44 +1,44 @@ | |||
1 | /*global EasyMDE*/ | 1 | /* global EasyMDE */ |
2 | /*eslint no-undef: "error"*/ | 2 | /* eslint no-undef: "error" */ |
3 | import { markdown2HTML, generateMaps } from './dumbymap'; | 3 | import { markdown2HTML, generateMaps } from './dumbymap' |
4 | import { defaultAliases, parseConfigsFromYaml } from 'mapclay'; | 4 | import { defaultAliases, parseConfigsFromYaml } from 'mapclay' |
5 | import * as menuItem from './MenuItem'; | 5 | import * as menuItem from './MenuItem' |
6 | 6 | ||
7 | // Set up Containers {{{ | 7 | // Set up Containers {{{ |
8 | 8 | ||
9 | const HtmlContainer = document.querySelector('.DumbyMap'); | 9 | const HtmlContainer = document.querySelector('.DumbyMap') |
10 | const textArea = document.querySelector('.editor textarea'); | 10 | const textArea = document.querySelector('.editor textarea') |
11 | let dumbymap; | 11 | let dumbymap |
12 | 12 | ||
13 | new MutationObserver(mutations => { | 13 | new window.MutationObserver(mutations => { |
14 | const mutation = mutations.at(-1); | 14 | const mutation = mutations.at(-1) |
15 | const mode = mutation.target.getAttribute('data-mode'); | 15 | const mode = mutation.target.getAttribute('data-mode') |
16 | const layout = HtmlContainer.getAttribute('data-layout'); | 16 | const layout = HtmlContainer.getAttribute('data-layout') |
17 | if (mode === 'editing' && layout !== 'normal') { | 17 | if (mode === 'editing' && layout !== 'normal') { |
18 | HtmlContainer.setAttribute('data-layout', 'normal'); | 18 | HtmlContainer.setAttribute('data-layout', 'normal') |
19 | } | 19 | } |
20 | }).observe(document.querySelector('[data-mode]'), { | 20 | }).observe(document.querySelector('[data-mode]'), { |
21 | attributes: true, | 21 | attributes: true, |
22 | attributeFilter: ['data-mode'], | 22 | attributeFilter: ['data-mode'], |
23 | attributeOldValue: true, | 23 | attributeOldValue: true |
24 | }); | 24 | }) |
25 | const toggleEditing = () => { | 25 | const toggleEditing = () => { |
26 | const mode = document.body.getAttribute('data-mode'); | 26 | const mode = document.body.getAttribute('data-mode') |
27 | document.body.setAttribute('data-mode', mode === 'editing' ? '' : 'editing'); | 27 | document.body.setAttribute('data-mode', mode === 'editing' ? '' : 'editing') |
28 | }; | 28 | } |
29 | // }}} | 29 | // }}} |
30 | // Set up EasyMDE {{{ | 30 | // Set up EasyMDE {{{ |
31 | 31 | ||
32 | // Content values for editor | 32 | // Content values for editor |
33 | 33 | ||
34 | const defaultContent = | 34 | const defaultContent = |
35 | '## Links\n\n- [Go to marker](geo:24,121?id=foo,leaflet&text=normal "Link Test")\n\n```map\nid: foo\nuse: Maplibre\n```\n'; | 35 | '## Links\n\n- [Go to marker](geo:24,121?id=foo,leaflet&text=normal "Link Test")\n\n```map\nid: foo\nuse: Maplibre\n```\n' |
36 | const editor = new EasyMDE({ | 36 | const editor = new EasyMDE({ |
37 | element: textArea, | 37 | element: textArea, |
38 | initialValue: defaultContent, | 38 | initialValue: defaultContent, |
39 | autosave: { | 39 | autosave: { |
40 | enabled: true, | 40 | enabled: true, |
41 | uniqueId: 'dumbymap', | 41 | uniqueId: 'dumbymap' |
42 | }, | 42 | }, |
43 | indentWithTabs: false, | 43 | indentWithTabs: false, |
44 | lineNumbers: true, | 44 | lineNumbers: true, |
@@ -51,105 +51,105 @@ const editor = new EasyMDE({ | |||
51 | map: 'Ctrl-Alt-M', | 51 | map: 'Ctrl-Alt-M', |
52 | debug: 'Ctrl-Alt-D', | 52 | debug: 'Ctrl-Alt-D', |
53 | toggleUnorderedList: null, | 53 | toggleUnorderedList: null, |
54 | toggleOrderedList: null, | 54 | toggleOrderedList: null |
55 | }, | 55 | }, |
56 | toolbar: [ | 56 | toolbar: [ |
57 | { | 57 | { |
58 | name: 'map', | 58 | name: 'map', |
59 | title: 'Toggle Map Generation', | 59 | title: 'Toggle Map Generation', |
60 | text: '🌏', | 60 | text: '🌏', |
61 | action: () => toggleEditing(), | 61 | action: () => toggleEditing() |
62 | }, | 62 | }, |
63 | { | 63 | { |
64 | name: 'debug', | 64 | name: 'debug', |
65 | title: 'Save content as URL', | 65 | title: 'Save content as URL', |
66 | text: '🤔', | 66 | text: '🤔', |
67 | action: () => { | 67 | action: () => { |
68 | const state = { content: editor.value() }; | 68 | const state = { content: editor.value() } |
69 | window.location.hash = encodeURIComponent(JSON.stringify(state)); | 69 | window.location.hash = encodeURIComponent(JSON.stringify(state)) |
70 | navigator.clipboard.writeText(window.location.href); | 70 | navigator.clipboard.writeText(window.location.href) |
71 | alert('URL copied to clipboard'); | 71 | window.alert('URL copied to clipboard') |
72 | }, | 72 | } |
73 | }, | 73 | }, |
74 | { | 74 | { |
75 | name: 'undo', | 75 | name: 'undo', |
76 | title: 'Undo last editing', | 76 | title: 'Undo last editing', |
77 | text: '\u27F2', | 77 | text: '\u27F2', |
78 | action: EasyMDE.undo, | 78 | action: EasyMDE.undo |
79 | }, | 79 | }, |
80 | { | 80 | { |
81 | name: 'redo', | 81 | name: 'redo', |
82 | text: '\u27F3', | 82 | text: '\u27F3', |
83 | title: 'Redo editing', | 83 | title: 'Redo editing', |
84 | action: EasyMDE.redo, | 84 | action: EasyMDE.redo |
85 | }, | 85 | }, |
86 | '|', | 86 | '|', |
87 | { | 87 | { |
88 | name: 'heading-1', | 88 | name: 'heading-1', |
89 | text: 'H1', | 89 | text: 'H1', |
90 | title: 'Big Heading', | 90 | title: 'Big Heading', |
91 | action: EasyMDE['heading-1'], | 91 | action: EasyMDE['heading-1'] |
92 | }, | 92 | }, |
93 | { | 93 | { |
94 | name: 'heading-2', | 94 | name: 'heading-2', |
95 | text: 'H2', | 95 | text: 'H2', |
96 | title: 'Medium Heading', | 96 | title: 'Medium Heading', |
97 | action: EasyMDE['heading-2'], | 97 | action: EasyMDE['heading-2'] |
98 | }, | 98 | }, |
99 | '|', | 99 | '|', |
100 | { | 100 | { |
101 | name: 'link', | 101 | name: 'link', |
102 | text: '\u{1F517}', | 102 | text: '\u{1F517}', |
103 | title: 'Create Link', | 103 | title: 'Create Link', |
104 | action: EasyMDE.drawLink, | 104 | action: EasyMDE.drawLink |
105 | }, | 105 | }, |
106 | { | 106 | { |
107 | name: 'image', | 107 | name: 'image', |
108 | text: '\u{1F5BC}', | 108 | text: '\u{1F5BC}', |
109 | title: 'Create Image', | 109 | title: 'Create Image', |
110 | action: EasyMDE.drawImage, | 110 | action: EasyMDE.drawImage |
111 | }, | 111 | }, |
112 | '|', | 112 | '|', |
113 | { | 113 | { |
114 | name: 'Bold', | 114 | name: 'Bold', |
115 | text: '\u{1D401}', | 115 | text: '\u{1D401}', |
116 | title: 'Bold', | 116 | title: 'Bold', |
117 | action: EasyMDE.toggleBold, | 117 | action: EasyMDE.toggleBold |
118 | }, | 118 | }, |
119 | { | 119 | { |
120 | name: 'Italic', | 120 | name: 'Italic', |
121 | text: '\u{1D43C}', | 121 | text: '\u{1D43C}', |
122 | title: 'Italic', | 122 | title: 'Italic', |
123 | action: EasyMDE.toggleItalic, | 123 | action: EasyMDE.toggleItalic |
124 | }, | 124 | } |
125 | ], | 125 | ] |
126 | }); | 126 | }) |
127 | 127 | ||
128 | const cm = editor.codemirror; | 128 | const cm = editor.codemirror |
129 | 129 | ||
130 | const getStateFromHash = hash => { | 130 | const getStateFromHash = hash => { |
131 | const hashValue = hash.substring(1); | 131 | const hashValue = hash.substring(1) |
132 | const stateString = decodeURIComponent(hashValue); | 132 | const stateString = decodeURIComponent(hashValue) |
133 | try { | 133 | try { |
134 | return JSON.parse(stateString) ?? {}; | 134 | return JSON.parse(stateString) ?? {} |
135 | } catch (_) { | 135 | } catch (_) { |
136 | return {}; | 136 | return {} |
137 | } | 137 | } |
138 | }; | 138 | } |
139 | 139 | ||
140 | const getContentFromHash = hash => { | 140 | const getContentFromHash = hash => { |
141 | const state = getStateFromHash(hash); | 141 | const state = getStateFromHash(hash) |
142 | return state.content; | 142 | return state.content |
143 | }; | 143 | } |
144 | 144 | ||
145 | const initialState = getStateFromHash(window.location.hash); | 145 | const initialState = getStateFromHash(window.location.hash) |
146 | window.location.hash = ''; | 146 | window.location.hash = '' |
147 | const contentFromHash = initialState.content; | 147 | const contentFromHash = initialState.content |
148 | 148 | ||
149 | // Seems like autosave would overwrite initialValue, set content from hash here | 149 | // Seems like autosave would overwrite initialValue, set content from hash here |
150 | if (contentFromHash) { | 150 | if (contentFromHash) { |
151 | editor.cleanup(); | 151 | editor.cleanup() |
152 | editor.value(contentFromHash); | 152 | editor.value(contentFromHash) |
153 | } | 153 | } |
154 | // }}} | 154 | // }}} |
155 | // Set up logic about editor content {{{ | 155 | // Set up logic about editor content {{{ |
@@ -159,30 +159,30 @@ const afterMapRendered = _ => { | |||
159 | // const lonLat = mapHolder.renderer.unproject([event.x, event.y]) | 159 | // const lonLat = mapHolder.renderer.unproject([event.x, event.y]) |
160 | // // TODO... | 160 | // // TODO... |
161 | // } | 161 | // } |
162 | }; | 162 | } |
163 | markdown2HTML(HtmlContainer, editor.value()); | 163 | markdown2HTML(HtmlContainer, editor.value()) |
164 | dumbymap = generateMaps(HtmlContainer, afterMapRendered); | 164 | dumbymap = generateMaps(HtmlContainer, afterMapRendered) |
165 | 165 | ||
166 | // Quick hack to style lines inside code block | 166 | // Quick hack to style lines inside code block |
167 | const addClassToCodeLines = () => { | 167 | const addClassToCodeLines = () => { |
168 | const lines = cm.getLineHandle(0).parent.lines; | 168 | const lines = cm.getLineHandle(0).parent.lines |
169 | let insideCodeBlock = false; | 169 | let insideCodeBlock = false |
170 | lines.forEach((line, index) => { | 170 | lines.forEach((line, index) => { |
171 | if (line.text.match(/^[\u0060]{3}/)) { | 171 | if (line.text.match(/^[\u0060]{3}/)) { |
172 | insideCodeBlock = !insideCodeBlock; | 172 | insideCodeBlock = !insideCodeBlock |
173 | } else if (insideCodeBlock) { | 173 | } else if (insideCodeBlock) { |
174 | cm.addLineClass(index, 'text', 'inside-code-block'); | 174 | cm.addLineClass(index, 'text', 'inside-code-block') |
175 | } else { | 175 | } else { |
176 | cm.removeLineClass(index, 'text', 'inside-code-block'); | 176 | cm.removeLineClass(index, 'text', 'inside-code-block') |
177 | } | 177 | } |
178 | }); | 178 | }) |
179 | }; | 179 | } |
180 | addClassToCodeLines(); | 180 | addClassToCodeLines() |
181 | 181 | ||
182 | const completeForCodeBlock = change => { | 182 | const completeForCodeBlock = change => { |
183 | const line = change.to.line; | 183 | const line = change.to.line |
184 | if (change.origin === '+input') { | 184 | if (change.origin === '+input') { |
185 | const text = change.text[0]; | 185 | const text = change.text[0] |
186 | 186 | ||
187 | // Completion for YAML doc separator | 187 | // Completion for YAML doc separator |
188 | if ( | 188 | if ( |
@@ -190,21 +190,21 @@ const completeForCodeBlock = change => { | |||
190 | change.to.ch === 0 && | 190 | change.to.ch === 0 && |
191 | insideCodeblockForMap(cm.getCursor()) | 191 | insideCodeblockForMap(cm.getCursor()) |
192 | ) { | 192 | ) { |
193 | cm.setSelection({ line: line, ch: 0 }, { line: line, ch: 1 }); | 193 | cm.setSelection({ line, ch: 0 }, { line, ch: 1 }) |
194 | cm.replaceSelection(text.repeat(3) + '\n'); | 194 | cm.replaceSelection(text.repeat(3) + '\n') |
195 | } | 195 | } |
196 | 196 | ||
197 | // Completion for Code fence | 197 | // Completion for Code fence |
198 | if (text === '`' && change.to.ch === 0) { | 198 | if (text === '`' && change.to.ch === 0) { |
199 | cm.setSelection({ line: line, ch: 0 }, { line: line, ch: 1 }); | 199 | cm.setSelection({ line, ch: 0 }, { line, ch: 1 }) |
200 | cm.replaceSelection(text.repeat(3)); | 200 | cm.replaceSelection(text.repeat(3)) |
201 | const numberOfFences = cm | 201 | const numberOfFences = cm |
202 | .getValue() | 202 | .getValue() |
203 | .split('\n') | 203 | .split('\n') |
204 | .filter(line => line.match(/[\u0060]{3}/)).length; | 204 | .filter(line => line.match(/[\u0060]{3}/)).length |
205 | if (numberOfFences % 2 === 1) { | 205 | if (numberOfFences % 2 === 1) { |
206 | cm.replaceSelection('map\n\n```'); | 206 | cm.replaceSelection('map\n\n```') |
207 | cm.setCursor({ line: line + 1 }); | 207 | cm.setCursor({ line: line + 1 }) |
208 | } | 208 | } |
209 | } | 209 | } |
210 | } | 210 | } |
@@ -212,63 +212,64 @@ const completeForCodeBlock = change => { | |||
212 | // For YAML doc separator, <hr> and code fence | 212 | // For YAML doc separator, <hr> and code fence |
213 | // Auto delete to start of line | 213 | // Auto delete to start of line |
214 | if (change.origin === '+delete') { | 214 | if (change.origin === '+delete') { |
215 | const match = change.removed[0].match(/^[-\u0060]$/)?.at(0); | 215 | const match = change.removed[0].match(/^[-\u0060]$/)?.at(0) |
216 | if (match && cm.getLine(line) === match.repeat(2) && match) { | 216 | if (match && cm.getLine(line) === match.repeat(2) && match) { |
217 | cm.setSelection({ line: line, ch: 0 }, { line: line, ch: 2 }); | 217 | cm.setSelection({ line, ch: 0 }, { line, ch: 2 }) |
218 | cm.replaceSelection(''); | 218 | cm.replaceSelection('') |
219 | } | 219 | } |
220 | } | 220 | } |
221 | }; | 221 | } |
222 | |||
223 | const debounceForMap = (() => { | ||
224 | let timer = null; | ||
225 | 222 | ||
226 | return function (...args) { | 223 | /* Disable debounce temporarily */ |
227 | dumbymap = generateMaps.apply(this, args); | 224 | // const debounceForMap = (() => { |
228 | // clearTimeout(timer); | 225 | // const timer = null |
229 | // timer = setTimeout(() => { | 226 | // |
230 | // dumbymap = generateMaps.apply(this, args) | 227 | // return function (...args) { |
231 | // }, 10); | 228 | // dumbymap = generateMaps.apply(this, args) |
232 | }; | 229 | // clearTimeout(timer); |
233 | })(); | 230 | // timer = setTimeout(() => { |
231 | // dumbymap = generateMaps.apply(this, args) | ||
232 | // }, 10); | ||
233 | // } | ||
234 | // })() | ||
234 | 235 | ||
235 | const updateDumbyMap = () => { | 236 | const updateDumbyMap = () => { |
236 | markdown2HTML(HtmlContainer, editor.value()); | 237 | markdown2HTML(HtmlContainer, editor.value()) |
237 | // TODO Test if generate maps intantly is OK with map cache | 238 | // TODO Test if generate maps intantly is OK with map cache |
238 | // debounceForMap(HtmlContainer, afterMapRendered) | 239 | // debounceForMap(HtmlContainer, afterMapRendered) |
239 | dumbymap = generateMaps(HtmlContainer, afterMapRendered); | 240 | dumbymap = generateMaps(HtmlContainer, afterMapRendered) |
240 | }; | 241 | } |
241 | 242 | ||
242 | updateDumbyMap(); | 243 | updateDumbyMap() |
243 | 244 | ||
244 | // Re-render HTML by editor content | 245 | // Re-render HTML by editor content |
245 | cm.on('change', (_, change) => { | 246 | cm.on('change', (_, change) => { |
246 | updateDumbyMap(); | 247 | updateDumbyMap() |
247 | addClassToCodeLines(); | 248 | addClassToCodeLines() |
248 | completeForCodeBlock(change); | 249 | completeForCodeBlock(change) |
249 | }); | 250 | }) |
250 | 251 | ||
251 | // Set class for focus | 252 | // Set class for focus |
252 | cm.on('focus', () => { | 253 | cm.on('focus', () => { |
253 | cm.getWrapperElement().classList.add('focus'); | 254 | cm.getWrapperElement().classList.add('focus') |
254 | HtmlContainer.classList.remove('focus'); | 255 | HtmlContainer.classList.remove('focus') |
255 | }); | 256 | }) |
256 | 257 | ||
257 | cm.on('beforeChange', (_, change) => { | 258 | cm.on('beforeChange', (_, change) => { |
258 | const line = change.to.line; | 259 | const line = change.to.line |
259 | // Don't allow more content after YAML doc separator | 260 | // Don't allow more content after YAML doc separator |
260 | if (change.origin.match(/^(\+input|paste)$/)) { | 261 | if (change.origin.match(/^(\+input|paste)$/)) { |
261 | if (cm.getLine(line) === '---' && change.text[0] !== '') { | 262 | if (cm.getLine(line) === '---' && change.text[0] !== '') { |
262 | change.cancel(); | 263 | change.cancel() |
263 | } | 264 | } |
264 | } | 265 | } |
265 | }); | 266 | }) |
266 | 267 | ||
267 | // Reload editor content by hash value | 268 | // Reload editor content by hash value |
268 | window.onhashchange = () => { | 269 | window.onhashchange = () => { |
269 | const content = getContentFromHash(window.location.hash); | 270 | const content = getContentFromHash(window.location.hash) |
270 | if (content) editor.value(content); | 271 | if (content) editor.value(content) |
271 | }; | 272 | } |
272 | 273 | ||
273 | // FIXME DEBUGONLY | 274 | // FIXME DEBUGONLY |
274 | // generateMaps(HtmlContainer) | 275 | // generateMaps(HtmlContainer) |
@@ -279,159 +280,159 @@ window.onhashchange = () => { | |||
279 | // }}} | 280 | // }}} |
280 | // Completion in Code Blok {{{ | 281 | // Completion in Code Blok {{{ |
281 | // Elements about suggestions {{{ | 282 | // Elements about suggestions {{{ |
282 | const menu = document.createElement('div'); | 283 | const menu = document.createElement('div') |
283 | menu.className = 'menu editor-menu'; | 284 | menu.className = 'menu editor-menu' |
284 | menu.style.display = 'none'; | 285 | menu.style.display = 'none' |
285 | menu.onclick = () => (menu.style.display = 'none'); | 286 | menu.onclick = () => (menu.style.display = 'none') |
286 | new MutationObserver(() => { | 287 | new window.MutationObserver(() => { |
287 | if (menu.style.display === 'none') { | 288 | if (menu.style.display === 'none') { |
288 | menu.replaceChildren(); | 289 | menu.replaceChildren() |
289 | } | 290 | } |
290 | }).observe(menu, { | 291 | }).observe(menu, { |
291 | attributes: true, | 292 | attributes: true, |
292 | attributeFilter: ['style'], | 293 | attributeFilter: ['style'] |
293 | }); | 294 | }) |
294 | document.body.append(menu); | 295 | document.body.append(menu) |
295 | 296 | ||
296 | const rendererOptions = {}; | 297 | const rendererOptions = {} |
297 | 298 | ||
298 | // }}} | 299 | // }}} |
299 | // Aliases for map options {{{ | 300 | // Aliases for map options {{{ |
300 | const aliasesForMapOptions = {}; | 301 | const aliasesForMapOptions = {} |
301 | const defaultApply = './dist/default.yml'; | 302 | const defaultApply = './dist/default.yml' |
302 | fetch(defaultApply) | 303 | fetch(defaultApply) |
303 | .then(res => res.text()) | 304 | .then(res => res.text()) |
304 | .then(rawText => { | 305 | .then(rawText => { |
305 | const config = parseConfigsFromYaml(rawText)?.at(0); | 306 | const config = parseConfigsFromYaml(rawText)?.at(0) |
306 | Object.assign(aliasesForMapOptions, config.aliases ?? {}); | 307 | Object.assign(aliasesForMapOptions, config.aliases ?? {}) |
307 | }) | 308 | }) |
308 | .catch(err => console.warn(`Fail to get aliases from ${defaultApply}`, err)); | 309 | .catch(err => console.warn(`Fail to get aliases from ${defaultApply}`, err)) |
309 | // }}} | 310 | // }}} |
310 | // FUNCTION: Check if current token is inside code block {{{ | 311 | // FUNCTION: Check if current token is inside code block {{{ |
311 | const insideCodeblockForMap = anchor => { | 312 | const insideCodeblockForMap = anchor => { |
312 | const token = cm.getTokenAt(anchor); | 313 | const token = cm.getTokenAt(anchor) |
313 | const insideCodeBlock = | 314 | const insideCodeBlock = |
314 | token.state.overlay.codeBlock && | 315 | token.state.overlay.codeBlock && |
315 | !cm.getLine(anchor.line).match(/^[\u0060]{3}/); | 316 | !cm.getLine(anchor.line).match(/^[\u0060]{3}/) |
316 | if (!insideCodeBlock) return false; | 317 | if (!insideCodeBlock) return false |
317 | 318 | ||
318 | let line = anchor.line - 1; | 319 | let line = anchor.line - 1 |
319 | while (line >= 0) { | 320 | while (line >= 0) { |
320 | const content = cm.getLine(line); | 321 | const content = cm.getLine(line) |
321 | if (content === '```map') { | 322 | if (content === '```map') { |
322 | return true; | 323 | return true |
323 | } else if (content === '```') { | 324 | } else if (content === '```') { |
324 | return false; | 325 | return false |
325 | } | 326 | } |
326 | line = line - 1; | 327 | line = line - 1 |
327 | } | 328 | } |
328 | return false; | 329 | return false |
329 | }; | 330 | } |
330 | // }}} | 331 | // }}} |
331 | // FUNCTION: Get Renderer by cursor position in code block {{{ | 332 | // FUNCTION: Get Renderer by cursor position in code block {{{ |
332 | const getLineWithRenderer = anchor => { | 333 | const getLineWithRenderer = anchor => { |
333 | const currentLine = anchor.line; | 334 | const currentLine = anchor.line |
334 | if (!cm.getLine) return null; | 335 | if (!cm.getLine) return null |
335 | 336 | ||
336 | const match = line => cm.getLine(line).match(/^use: /); | 337 | const match = line => cm.getLine(line).match(/^use: /) |
337 | 338 | ||
338 | if (match(currentLine)) return currentLine; | 339 | if (match(currentLine)) return currentLine |
339 | 340 | ||
340 | // Look backward/forward for pattern of used renderer: /use: .+/ | 341 | // Look backward/forward for pattern of used renderer: /use: .+/ |
341 | let pl = currentLine - 1; | 342 | let pl = currentLine - 1 |
342 | while (pl > 0 && insideCodeblockForMap(anchor)) { | 343 | while (pl > 0 && insideCodeblockForMap(anchor)) { |
343 | const text = cm.getLine(pl); | 344 | const text = cm.getLine(pl) |
344 | if (match(pl)) { | 345 | if (match(pl)) { |
345 | return pl; | 346 | return pl |
346 | } else if (text.match(/^---|^[\u0060]{3}/)) { | 347 | } else if (text.match(/^---|^[\u0060]{3}/)) { |
347 | break; | 348 | break |
348 | } | 349 | } |
349 | pl = pl - 1; | 350 | pl = pl - 1 |
350 | } | 351 | } |
351 | 352 | ||
352 | let nl = currentLine + 1; | 353 | let nl = currentLine + 1 |
353 | while (insideCodeblockForMap(anchor)) { | 354 | while (insideCodeblockForMap(anchor)) { |
354 | const text = cm.getLine(nl); | 355 | const text = cm.getLine(nl) |
355 | if (match(nl)) { | 356 | if (match(nl)) { |
356 | return nl; | 357 | return nl |
357 | } else if (text.match(/^---|^[\u0060]{3}/)) { | 358 | } else if (text.match(/^---|^[\u0060]{3}/)) { |
358 | return null; | 359 | return null |
359 | } | 360 | } |
360 | nl = nl + 1; | 361 | nl = nl + 1 |
361 | } | 362 | } |
362 | 363 | ||
363 | return null; | 364 | return null |
364 | }; | 365 | } |
365 | // }}} | 366 | // }}} |
366 | // FUNCTION: Return suggestions for valid options {{{ | 367 | // FUNCTION: Return suggestions for valid options {{{ |
367 | const getSuggestionsForOptions = (optionTyped, validOptions) => { | 368 | const getSuggestionsForOptions = (optionTyped, validOptions) => { |
368 | let suggestOptions = []; | 369 | let suggestOptions = [] |
369 | 370 | ||
370 | const matchedOptions = validOptions.filter(o => | 371 | const matchedOptions = validOptions.filter(o => |
371 | o.valueOf().toLowerCase().includes(optionTyped.toLowerCase()), | 372 | o.valueOf().toLowerCase().includes(optionTyped.toLowerCase()) |
372 | ); | 373 | ) |
373 | 374 | ||
374 | if (matchedOptions.length > 0) { | 375 | if (matchedOptions.length > 0) { |
375 | suggestOptions = matchedOptions; | 376 | suggestOptions = matchedOptions |
376 | } else { | 377 | } else { |
377 | suggestOptions = validOptions; | 378 | suggestOptions = validOptions |
378 | } | 379 | } |
379 | 380 | ||
380 | return suggestOptions.map( | 381 | return suggestOptions.map( |
381 | o => | 382 | o => |
382 | new menuItem.Suggestion({ | 383 | new menuItem.Suggestion({ |
383 | text: `<span>${o.valueOf()}</span><span class='info' title="${o.desc ?? ''}">ⓘ</span>`, | 384 | text: `<span>${o.valueOf()}</span><span class='info' title="${o.desc ?? ''}">ⓘ</span>`, |
384 | replace: `${o.valueOf()}: `, | 385 | replace: `${o.valueOf()}: ` |
385 | }), | 386 | }) |
386 | ); | 387 | ) |
387 | }; | 388 | } |
388 | // }}} | 389 | // }}} |
389 | // FUNCTION: Return suggestion for example of option value {{{ | 390 | // FUNCTION: Return suggestion for example of option value {{{ |
390 | const getSuggestionFromMapOption = option => { | 391 | const getSuggestionFromMapOption = option => { |
391 | if (!option.example) return null; | 392 | if (!option.example) return null |
392 | 393 | ||
393 | const text = option.example_desc | 394 | const text = option.example_desc |
394 | ? `<span>${option.example_desc}</span><span class="truncate"style="color: gray">${option.example}</span>` | 395 | ? `<span>${option.example_desc}</span><span class="truncate"style="color: gray">${option.example}</span>` |
395 | : `<span>${option.example}</span>`; | 396 | : `<span>${option.example}</span>` |
396 | 397 | ||
397 | return new menuItem.Suggestion({ | 398 | return new menuItem.Suggestion({ |
398 | text: text, | 399 | text, |
399 | replace: `${option.valueOf()}: ${option.example ?? ''}`, | 400 | replace: `${option.valueOf()}: ${option.example ?? ''}` |
400 | }); | 401 | }) |
401 | }; | 402 | } |
402 | // }}} | 403 | // }}} |
403 | // FUNCTION: Return suggestions from aliases {{{ | 404 | // FUNCTION: Return suggestions from aliases {{{ |
404 | const getSuggestionsFromAliases = option => | 405 | const getSuggestionsFromAliases = option => |
405 | Object.entries(aliasesForMapOptions[option.valueOf()] ?? {})?.map(record => { | 406 | Object.entries(aliasesForMapOptions[option.valueOf()] ?? {})?.map(record => { |
406 | const [alias, value] = record; | 407 | const [alias, value] = record |
407 | const valueString = JSON.stringify(value).replaceAll('"', ''); | 408 | const valueString = JSON.stringify(value).replaceAll('"', '') |
408 | return new menuItem.Suggestion({ | 409 | return new menuItem.Suggestion({ |
409 | text: `<span>${alias}</span><span class="truncate" style="color: gray">${valueString}</span>`, | 410 | text: `<span>${alias}</span><span class="truncate" style="color: gray">${valueString}</span>`, |
410 | replace: `${option.valueOf()}: ${valueString}`, | 411 | replace: `${option.valueOf()}: ${valueString}` |
411 | }); | 412 | }) |
412 | }) ?? []; | 413 | }) ?? [] |
413 | // }}} | 414 | // }}} |
414 | // FUCNTION: Handler for map codeblock {{{ | 415 | // FUCNTION: Handler for map codeblock {{{ |
415 | const handleTypingInCodeBlock = anchor => { | 416 | const handleTypingInCodeBlock = anchor => { |
416 | const text = cm.getLine(anchor.line); | 417 | const text = cm.getLine(anchor.line) |
417 | if (text.match(/^\s\+$/) && text.length % 2 !== 0) { | 418 | if (text.match(/^\s\+$/) && text.length % 2 !== 0) { |
418 | // TODO Completion for even number of spaces | 419 | // TODO Completion for even number of spaces |
419 | } else if (text.match(/^-/)) { | 420 | } else if (text.match(/^-/)) { |
420 | // TODO Completion for YAML doc separator | 421 | // TODO Completion for YAML doc separator |
421 | } else { | 422 | } else { |
422 | const suggestions = getSuggestions(anchor); | 423 | const suggestions = getSuggestions(anchor) |
423 | addSuggestions(anchor, suggestions); | 424 | addSuggestions(anchor, suggestions) |
424 | } | 425 | } |
425 | }; | 426 | } |
426 | // }}} | 427 | // }}} |
427 | // FUNCTION: get suggestions by current input {{{ | 428 | // FUNCTION: get suggestions by current input {{{ |
428 | const getSuggestions = anchor => { | 429 | const getSuggestions = anchor => { |
429 | const text = cm.getLine(anchor.line); | 430 | const text = cm.getLine(anchor.line) |
430 | 431 | ||
431 | // Clear marks on text | 432 | // Clear marks on text |
432 | cm.findMarks({ ...anchor, ch: 0 }, { ...anchor, ch: text.length }).forEach( | 433 | cm.findMarks({ ...anchor, ch: 0 }, { ...anchor, ch: text.length }).forEach( |
433 | m => m.clear(), | 434 | m => m.clear() |
434 | ); | 435 | ) |
435 | 436 | ||
436 | // Mark user input invalid by case | 437 | // Mark user input invalid by case |
437 | const markInputIsInvalid = () => | 438 | const markInputIsInvalid = () => |
@@ -440,231 +441,230 @@ const getSuggestions = anchor => { | |||
440 | .markText( | 441 | .markText( |
441 | { ...anchor, ch: 0 }, | 442 | { ...anchor, ch: 0 }, |
442 | { ...anchor, ch: text.length }, | 443 | { ...anchor, ch: text.length }, |
443 | { className: 'invalid-input' }, | 444 | { className: 'invalid-input' } |
444 | ); | 445 | ) |
445 | 446 | ||
446 | // Check if "use: <renderer>" is set | 447 | // Check if "use: <renderer>" is set |
447 | const lineWithRenderer = getLineWithRenderer(anchor); | 448 | const lineWithRenderer = getLineWithRenderer(anchor) |
448 | const renderer = lineWithRenderer | 449 | const renderer = lineWithRenderer |
449 | ? cm.getLine(lineWithRenderer).split(' ')[1] | 450 | ? cm.getLine(lineWithRenderer).split(' ')[1] |
450 | : null; | 451 | : null |
451 | if (renderer && anchor.line !== lineWithRenderer) { | 452 | if (renderer && anchor.line !== lineWithRenderer) { |
452 | // Do not check properties | 453 | // Do not check properties |
453 | if (text.startsWith(' ')) return []; | 454 | if (text.startsWith(' ')) return [] |
454 | 455 | ||
455 | // If no valid options for current used renderer, go get it! | 456 | // If no valid options for current used renderer, go get it! |
456 | const validOptions = rendererOptions[renderer]; | 457 | const validOptions = rendererOptions[renderer] |
457 | if (!validOptions) { | 458 | if (!validOptions) { |
458 | // Get list of valid options for current renderer | 459 | // Get list of valid options for current renderer |
459 | const rendererUrl = defaultAliases.use[renderer]?.value; | 460 | const rendererUrl = defaultAliases.use[renderer]?.value |
460 | import(rendererUrl) | 461 | import(rendererUrl) |
461 | .then(rendererModule => { | 462 | .then(rendererModule => { |
462 | rendererOptions[renderer] = rendererModule.default.validOptions; | 463 | rendererOptions[renderer] = rendererModule.default.validOptions |
463 | const currentAnchor = cm.getCursor(); | 464 | const currentAnchor = cm.getCursor() |
464 | if (insideCodeblockForMap(currentAnchor)) { | 465 | if (insideCodeblockForMap(currentAnchor)) { |
465 | handleTypingInCodeBlock(currentAnchor); | 466 | handleTypingInCodeBlock(currentAnchor) |
466 | } | 467 | } |
467 | }) | 468 | }) |
468 | .catch(_ => { | 469 | .catch(_ => { |
469 | markInputIsInvalid(lineWithRenderer); | 470 | markInputIsInvalid(lineWithRenderer) |
470 | console.warn( | 471 | console.warn( |
471 | `Fail to get valid options from Renderer typed: ${renderer}`, | 472 | `Fail to get valid options from Renderer typed: ${renderer}` |
472 | ); | 473 | ) |
473 | }); | 474 | }) |
474 | return []; | 475 | return [] |
475 | } | 476 | } |
476 | 477 | ||
477 | // If input is "key:value" (no space left after colon), then it is invalid | 478 | // If input is "key:value" (no space left after colon), then it is invalid |
478 | const isKeyFinished = text.includes(':'); | 479 | const isKeyFinished = text.includes(':') |
479 | const isValidKeyValue = text.match(/^[^:]+:\s+/); | 480 | const isValidKeyValue = text.match(/^[^:]+:\s+/) |
480 | if (isKeyFinished && !isValidKeyValue) { | 481 | if (isKeyFinished && !isValidKeyValue) { |
481 | markInputIsInvalid(); | 482 | markInputIsInvalid() |
482 | return []; | 483 | return [] |
483 | } | 484 | } |
484 | 485 | ||
485 | // If user is typing option | 486 | // If user is typing option |
486 | const keyTyped = text.split(':')[0].trim(); | 487 | const keyTyped = text.split(':')[0].trim() |
487 | if (!isKeyFinished) { | 488 | if (!isKeyFinished) { |
488 | markInputIsInvalid(); | 489 | markInputIsInvalid() |
489 | return getSuggestionsForOptions(keyTyped, validOptions); | 490 | return getSuggestionsForOptions(keyTyped, validOptions) |
490 | } | 491 | } |
491 | 492 | ||
492 | // If user is typing value | 493 | // If user is typing value |
493 | const matchedOption = validOptions.find(o => o.name === keyTyped); | 494 | const matchedOption = validOptions.find(o => o.name === keyTyped) |
494 | if (isKeyFinished && !matchedOption) { | 495 | if (isKeyFinished && !matchedOption) { |
495 | markInputIsInvalid(); | 496 | markInputIsInvalid() |
496 | } | 497 | } |
497 | 498 | ||
498 | if (isKeyFinished && matchedOption) { | 499 | if (isKeyFinished && matchedOption) { |
499 | const valueTyped = text.substring(text.indexOf(':') + 1).trim(); | 500 | const valueTyped = text.substring(text.indexOf(':') + 1).trim() |
500 | const isValidValue = matchedOption.isValid(valueTyped); | 501 | const isValidValue = matchedOption.isValid(valueTyped) |
501 | if (!valueTyped) { | 502 | if (!valueTyped) { |
502 | return [ | 503 | return [ |
503 | getSuggestionFromMapOption(matchedOption), | 504 | getSuggestionFromMapOption(matchedOption), |
504 | ...getSuggestionsFromAliases(matchedOption), | 505 | ...getSuggestionsFromAliases(matchedOption) |
505 | ].filter(s => s instanceof menuItem.Suggestion); | 506 | ].filter(s => s instanceof menuItem.Suggestion) |
506 | } | 507 | } |
507 | if (valueTyped && !isValidValue) { | 508 | if (valueTyped && !isValidValue) { |
508 | markInputIsInvalid(); | 509 | markInputIsInvalid() |
509 | return []; | 510 | return [] |
510 | } | 511 | } |
511 | } | 512 | } |
512 | } else { | 513 | } else { |
513 | // Suggestion for "use" | 514 | // Suggestion for "use" |
514 | const rendererSuggestions = Object.entries(defaultAliases.use) | 515 | const rendererSuggestions = Object.entries(defaultAliases.use) |
515 | .filter(([renderer]) => { | 516 | .filter(([renderer]) => { |
516 | const suggestion = `use: ${renderer}`; | 517 | const suggestion = `use: ${renderer}` |
517 | const suggestionPattern = suggestion.replace(' ', '').toLowerCase(); | 518 | const suggestionPattern = suggestion.replace(' ', '').toLowerCase() |
518 | const textPattern = text.replace(' ', '').toLowerCase(); | 519 | const textPattern = text.replace(' ', '').toLowerCase() |
519 | return suggestion !== text && suggestionPattern.includes(textPattern); | 520 | return suggestion !== text && suggestionPattern.includes(textPattern) |
520 | }) | 521 | }) |
521 | .map( | 522 | .map( |
522 | ([renderer, info]) => | 523 | ([renderer, info]) => |
523 | new menuItem.Suggestion({ | 524 | new menuItem.Suggestion({ |
524 | text: `<span>use: ${renderer}</span><span class='info' title="${info.desc}">ⓘ</span>`, | 525 | text: `<span>use: ${renderer}</span><span class='info' title="${info.desc}">ⓘ</span>`, |
525 | replace: `use: ${renderer}`, | 526 | replace: `use: ${renderer}` |
526 | }), | 527 | }) |
527 | ); | 528 | ) |
528 | return rendererSuggestions.length > 0 ? rendererSuggestions : []; | 529 | return rendererSuggestions.length > 0 ? rendererSuggestions : [] |
529 | } | 530 | } |
530 | return []; | 531 | return [] |
531 | }; | 532 | } |
532 | // }}} | 533 | // }}} |
533 | // {{{ FUNCTION: Show element about suggestions | 534 | // {{{ FUNCTION: Show element about suggestions |
534 | const addSuggestions = (anchor, suggestions) => { | 535 | const addSuggestions = (anchor, suggestions) => { |
535 | if (suggestions.length === 0) { | 536 | if (suggestions.length === 0) { |
536 | menu.style.display = 'none'; | 537 | menu.style.display = 'none' |
537 | return; | 538 | return |
538 | } else { | 539 | } else { |
539 | menu.style.display = 'block'; | 540 | menu.style.display = 'block' |
540 | } | 541 | } |
541 | 542 | ||
542 | menu.innerHTML = ''; | 543 | menu.innerHTML = '' |
543 | suggestions | 544 | suggestions |
544 | .map(s => s.createElement(cm)) | 545 | .map(s => s.createElement(cm)) |
545 | .forEach(option => menu.appendChild(option)); | 546 | .forEach(option => menu.appendChild(option)) |
546 | 547 | ||
547 | const widgetAnchor = document.createElement('div'); | 548 | const widgetAnchor = document.createElement('div') |
548 | cm.addWidget(anchor, widgetAnchor, true); | 549 | cm.addWidget(anchor, widgetAnchor, true) |
549 | const rect = widgetAnchor.getBoundingClientRect(); | 550 | const rect = widgetAnchor.getBoundingClientRect() |
550 | menu.style.left = `calc(${rect.left}px + 2rem)`; | 551 | menu.style.left = `calc(${rect.left}px + 2rem)` |
551 | menu.style.top = `calc(${rect.bottom}px + 1rem)`; | 552 | menu.style.top = `calc(${rect.bottom}px + 1rem)` |
552 | menu.style.maxWidth = `calc(${window.innerWidth}px - ${rect.x}px - 3rem)`; | 553 | menu.style.maxWidth = `calc(${window.innerWidth}px - ${rect.x}px - 3rem)` |
553 | menu.style.display = 'block'; | 554 | menu.style.display = 'block' |
554 | }; | 555 | } |
555 | // }}} | 556 | // }}} |
556 | // EVENT: Suggests for current selection {{{ | 557 | // EVENT: Suggests for current selection {{{ |
557 | // FIXME Dont show suggestion when selecting multiple chars | 558 | // FIXME Dont show suggestion when selecting multiple chars |
558 | cm.on('cursorActivity', _ => { | 559 | cm.on('cursorActivity', _ => { |
559 | menu.style.display = 'none'; | 560 | menu.style.display = 'none' |
560 | const anchor = cm.getCursor(); | 561 | const anchor = cm.getCursor() |
561 | 562 | ||
562 | if (insideCodeblockForMap(anchor)) { | 563 | if (insideCodeblockForMap(anchor)) { |
563 | handleTypingInCodeBlock(anchor); | 564 | handleTypingInCodeBlock(anchor) |
564 | } | 565 | } |
565 | }); | 566 | }) |
566 | cm.on('blur', () => { | 567 | cm.on('blur', () => { |
567 | if (menu.checkVisibility()) { | 568 | if (menu.checkVisibility()) { |
568 | cm.focus() | 569 | cm.focus() |
569 | } else { | 570 | } else { |
570 | cm.getWrapperElement().classList.remove('focus'); | 571 | cm.getWrapperElement().classList.remove('focus') |
571 | HtmlContainer.classList.add('focus'); | 572 | HtmlContainer.classList.add('focus') |
572 | } | 573 | } |
573 | }); | 574 | }) |
574 | // }}} | 575 | // }}} |
575 | // EVENT: keydown for suggestions {{{ | 576 | // EVENT: keydown for suggestions {{{ |
576 | const keyForSuggestions = ['Tab', 'Enter', 'Escape']; | 577 | const keyForSuggestions = ['Tab', 'Enter', 'Escape'] |
577 | cm.on('keydown', (_, e) => { | 578 | cm.on('keydown', (_, e) => { |
578 | if ( | 579 | if ( |
579 | !cm.hasFocus || | 580 | !cm.hasFocus || |
580 | !keyForSuggestions.includes(e.key) || | 581 | !keyForSuggestions.includes(e.key) || |
581 | menu.style.display === 'none' | 582 | menu.style.display === 'none' |
582 | ) | 583 | ) { return } |
583 | return; | ||
584 | 584 | ||
585 | // Directly add a newline when no suggestion is selected | 585 | // Directly add a newline when no suggestion is selected |
586 | const currentSuggestion = menu.querySelector('.container__suggestion.focus'); | 586 | const currentSuggestion = menu.querySelector('.container__suggestion.focus') |
587 | if (!currentSuggestion && e.key === 'Enter') return; | 587 | if (!currentSuggestion && e.key === 'Enter') return |
588 | 588 | ||
589 | // Override default behavior | 589 | // Override default behavior |
590 | e.preventDefault(); | 590 | e.preventDefault() |
591 | 591 | ||
592 | // Suggestion when pressing Tab or Shift + Tab | 592 | // Suggestion when pressing Tab or Shift + Tab |
593 | const nextSuggestion = | 593 | const nextSuggestion = |
594 | currentSuggestion?.nextSibling ?? | 594 | currentSuggestion?.nextSibling ?? |
595 | menu.querySelector('.container__suggestion:first-child'); | 595 | menu.querySelector('.container__suggestion:first-child') |
596 | const previousSuggestion = | 596 | const previousSuggestion = |
597 | currentSuggestion?.previousSibling ?? | 597 | currentSuggestion?.previousSibling ?? |
598 | menu.querySelector('.container__suggestion:last-child'); | 598 | menu.querySelector('.container__suggestion:last-child') |
599 | const focusSuggestion = e.shiftKey ? previousSuggestion : nextSuggestion; | 599 | const focusSuggestion = e.shiftKey ? previousSuggestion : nextSuggestion |
600 | 600 | ||
601 | // Current editor selection state | 601 | // Current editor selection state |
602 | switch (e.key) { | 602 | switch (e.key) { |
603 | case 'Tab': | 603 | case 'Tab': |
604 | Array.from(menu.children).forEach(s => s.classList.remove('focus')); | 604 | Array.from(menu.children).forEach(s => s.classList.remove('focus')) |
605 | focusSuggestion.classList.add('focus'); | 605 | focusSuggestion.classList.add('focus') |
606 | focusSuggestion.scrollIntoView({ behavior: 'smooth', block: 'nearest' }); | 606 | focusSuggestion.scrollIntoView({ behavior: 'smooth', block: 'nearest' }) |
607 | break; | 607 | break |
608 | case 'Enter': | 608 | case 'Enter': |
609 | currentSuggestion.onclick(); | 609 | currentSuggestion.onclick() |
610 | break; | 610 | break |
611 | case 'Escape': | 611 | case 'Escape': |
612 | if (!menu.checkVisibility()) break; | 612 | if (!menu.checkVisibility()) break |
613 | // HACK delay menu display change for blur event, mark cm focus should keep | 613 | // HACK delay menu display change for blur event, mark cm focus should keep |
614 | setTimeout(() => (menu.style.display = 'none'), 50); | 614 | setTimeout(() => (menu.style.display = 'none'), 50) |
615 | break; | 615 | break |
616 | } | 616 | } |
617 | }); | 617 | }) |
618 | 618 | ||
619 | document.onkeydown = e => { | 619 | document.onkeydown = e => { |
620 | if (e.altKey && e.ctrlKey && e.key === 'm') { | 620 | if (e.altKey && e.ctrlKey && e.key === 'm') { |
621 | toggleEditing(); | 621 | toggleEditing() |
622 | e.preventDefault(); | 622 | e.preventDefault() |
623 | return null; | 623 | return null |
624 | } | 624 | } |
625 | 625 | ||
626 | if (!cm.hasFocus()) { | 626 | if (!cm.hasFocus()) { |
627 | if (e.key === 'F1') { | 627 | if (e.key === 'F1') { |
628 | e.preventDefault(); | 628 | e.preventDefault() |
629 | cm.focus(); | 629 | cm.focus() |
630 | } | 630 | } |
631 | if (e.key === 'Tab') { | 631 | if (e.key === 'Tab') { |
632 | e.preventDefault(); | 632 | e.preventDefault() |
633 | dumbymap.utils.focusNextMap(e.shiftKey); | 633 | dumbymap.utils.focusNextMap(e.shiftKey) |
634 | } | 634 | } |
635 | if (e.key === 'x' || e.key === 'X') { | 635 | if (e.key === 'x' || e.key === 'X') { |
636 | e.preventDefault(); | 636 | e.preventDefault() |
637 | dumbymap.utils.switchToNextLayout(e.shiftKey); | 637 | dumbymap.utils.switchToNextLayout(e.shiftKey) |
638 | } | 638 | } |
639 | if (e.key === 'n') { | 639 | if (e.key === 'n') { |
640 | e.preventDefault(); | 640 | e.preventDefault() |
641 | dumbymap.utils.focusNextBlock(); | 641 | dumbymap.utils.focusNextBlock() |
642 | } | 642 | } |
643 | if (e.key === 'p') { | 643 | if (e.key === 'p') { |
644 | e.preventDefault(); | 644 | e.preventDefault() |
645 | dumbymap.utils.focusNextBlock(true); | 645 | dumbymap.utils.focusNextBlock(true) |
646 | } | 646 | } |
647 | if (e.key === 'Escape') { | 647 | if (e.key === 'Escape') { |
648 | e.preventDefault(); | 648 | e.preventDefault() |
649 | dumbymap.utils.removeBlockFocus(); | 649 | dumbymap.utils.removeBlockFocus() |
650 | } | 650 | } |
651 | } | 651 | } |
652 | }; | 652 | } |
653 | 653 | ||
654 | // }}} | 654 | // }}} |
655 | // }}} | 655 | // }}} |
656 | // Layout Switch {{{ | 656 | // Layout Switch {{{ |
657 | new MutationObserver(mutaions => { | 657 | new window.MutationObserver(mutaions => { |
658 | const mutation = mutaions.at(-1); | 658 | const mutation = mutaions.at(-1) |
659 | const layout = HtmlContainer.getAttribute('data-layout'); | 659 | const layout = HtmlContainer.getAttribute('data-layout') |
660 | if (layout !== 'normal' || mutation.oldValue === 'normal') { | 660 | if (layout !== 'normal' || mutation.oldValue === 'normal') { |
661 | document.body.setAttribute('data-mode', ''); | 661 | document.body.setAttribute('data-mode', '') |
662 | } | 662 | } |
663 | }).observe(HtmlContainer, { | 663 | }).observe(HtmlContainer, { |
664 | attributes: true, | 664 | attributes: true, |
665 | attributeFilter: ['data-layout'], | 665 | attributeFilter: ['data-layout'], |
666 | attributeOldValue: true, | 666 | attributeOldValue: true |
667 | }); | 667 | }) |
668 | // }}} | 668 | // }}} |
669 | 669 | ||
670 | // vim: sw=2 ts=2 foldmethod=marker foldmarker={{{,}}} | 670 | // vim: sw=2 ts=2 foldmethod=marker foldmarker={{{,}}} |