diff options
Diffstat (limited to 'src/editor.mjs')
-rw-r--r-- | src/editor.mjs | 188 |
1 files changed, 94 insertions, 94 deletions
diff --git a/src/editor.mjs b/src/editor.mjs index 73177b6..f80ff07 100644 --- a/src/editor.mjs +++ b/src/editor.mjs | |||
@@ -1,22 +1,22 @@ | |||
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 | const toggleEditing = () => { | 13 | const toggleEditing = () => { |
14 | if (document.body.getAttribute("data-mode") === "editing") { | 14 | if (document.body.getAttribute('data-mode') === 'editing') { |
15 | document.body.removeAttribute("data-mode"); | 15 | document.body.removeAttribute('data-mode'); |
16 | } else { | 16 | } else { |
17 | document.body.setAttribute("data-mode", "editing"); | 17 | document.body.setAttribute('data-mode', 'editing'); |
18 | } | 18 | } |
19 | HtmlContainer.setAttribute("data-layout", "normal"); | 19 | HtmlContainer.setAttribute('data-layout', 'normal'); |
20 | }; | 20 | }; |
21 | // }}} | 21 | // }}} |
22 | // Set up EasyMDE {{{ | 22 | // Set up EasyMDE {{{ |
@@ -30,37 +30,37 @@ const editor = new EasyMDE({ | |||
30 | initialValue: defaultContent, | 30 | initialValue: defaultContent, |
31 | autosave: { | 31 | autosave: { |
32 | enabled: true, | 32 | enabled: true, |
33 | uniqueId: "dumbymap", | 33 | uniqueId: 'dumbymap', |
34 | }, | 34 | }, |
35 | indentWithTabs: false, | 35 | indentWithTabs: false, |
36 | lineNumbers: true, | 36 | lineNumbers: true, |
37 | promptURLs: true, | 37 | promptURLs: true, |
38 | uploadImage: true, | 38 | uploadImage: true, |
39 | spellChecker: false, | 39 | spellChecker: false, |
40 | toolbarButtonClassPrefix: "mde", | 40 | toolbarButtonClassPrefix: 'mde', |
41 | status: false, | 41 | status: false, |
42 | shortcuts: { | 42 | shortcuts: { |
43 | map: "Ctrl-Alt-M", | 43 | map: 'Ctrl-Alt-M', |
44 | debug: "Ctrl-Alt-D", | 44 | debug: 'Ctrl-Alt-D', |
45 | toggleUnorderedList: null, | 45 | toggleUnorderedList: null, |
46 | toggleOrderedList: null, | 46 | toggleOrderedList: null, |
47 | }, | 47 | }, |
48 | toolbar: [ | 48 | toolbar: [ |
49 | { | 49 | { |
50 | name: "map", | 50 | name: 'map', |
51 | title: "Toggle Map Generation", | 51 | title: 'Toggle Map Generation', |
52 | text: "🌏", | 52 | text: '🌏', |
53 | action: () => toggleEditing(), | 53 | action: () => toggleEditing(), |
54 | }, | 54 | }, |
55 | { | 55 | { |
56 | name: "debug", | 56 | name: 'debug', |
57 | title: "Save content as URL", | 57 | title: 'Save content as URL', |
58 | text: "🤔", | 58 | text: '🤔', |
59 | action: () => { | 59 | action: () => { |
60 | const state = { content: editor.value() }; | 60 | const state = { content: editor.value() }; |
61 | window.location.hash = encodeURIComponent(JSON.stringify(state)); | 61 | window.location.hash = encodeURIComponent(JSON.stringify(state)); |
62 | navigator.clipboard.writeText(window.location.href); | 62 | navigator.clipboard.writeText(window.location.href); |
63 | alert("URL copied to clipboard"); | 63 | alert('URL copied to clipboard'); |
64 | }, | 64 | }, |
65 | }, | 65 | }, |
66 | "undo", | 66 | "undo", |
@@ -103,7 +103,7 @@ const getContentFromHash = hash => { | |||
103 | }; | 103 | }; |
104 | 104 | ||
105 | const initialState = getStateFromHash(window.location.hash); | 105 | const initialState = getStateFromHash(window.location.hash); |
106 | window.location.hash = ""; | 106 | window.location.hash = ''; |
107 | const contentFromHash = initialState.content; | 107 | const contentFromHash = initialState.content; |
108 | 108 | ||
109 | // Seems like autosave would overwrite initialValue, set content from hash here | 109 | // Seems like autosave would overwrite initialValue, set content from hash here |
@@ -131,9 +131,9 @@ const addClassToCodeLines = () => { | |||
131 | if (line.text.match(/^[\u0060]{3}/)) { | 131 | if (line.text.match(/^[\u0060]{3}/)) { |
132 | insideCodeBlock = !insideCodeBlock; | 132 | insideCodeBlock = !insideCodeBlock; |
133 | } else if (insideCodeBlock) { | 133 | } else if (insideCodeBlock) { |
134 | cm.addLineClass(index, "text", "inside-code-block"); | 134 | cm.addLineClass(index, 'text', 'inside-code-block'); |
135 | } else { | 135 | } else { |
136 | cm.removeLineClass(index, "text", "inside-code-block"); | 136 | cm.removeLineClass(index, 'text', 'inside-code-block'); |
137 | } | 137 | } |
138 | }); | 138 | }); |
139 | }; | 139 | }; |
@@ -141,29 +141,29 @@ addClassToCodeLines(); | |||
141 | 141 | ||
142 | const completeForCodeBlock = change => { | 142 | const completeForCodeBlock = change => { |
143 | const line = change.to.line; | 143 | const line = change.to.line; |
144 | if (change.origin === "+input") { | 144 | if (change.origin === '+input') { |
145 | const text = change.text[0]; | 145 | const text = change.text[0]; |
146 | 146 | ||
147 | // Completion for YAML doc separator | 147 | // Completion for YAML doc separator |
148 | if ( | 148 | if ( |
149 | text === "-" && | 149 | text === '-' && |
150 | change.to.ch === 0 && | 150 | change.to.ch === 0 && |
151 | insideCodeblockForMap(cm.getCursor()) | 151 | insideCodeblockForMap(cm.getCursor()) |
152 | ) { | 152 | ) { |
153 | cm.setSelection({ line: line, ch: 0 }, { line: line, ch: 1 }); | 153 | cm.setSelection({ line: line, ch: 0 }, { line: line, ch: 1 }); |
154 | cm.replaceSelection(text.repeat(3) + "\n"); | 154 | cm.replaceSelection(text.repeat(3) + '\n'); |
155 | } | 155 | } |
156 | 156 | ||
157 | // Completion for Code fence | 157 | // Completion for Code fence |
158 | if (text === "`" && change.to.ch === 0) { | 158 | if (text === '`' && change.to.ch === 0) { |
159 | cm.setSelection({ line: line, ch: 0 }, { line: line, ch: 1 }); | 159 | cm.setSelection({ line: line, ch: 0 }, { line: line, ch: 1 }); |
160 | cm.replaceSelection(text.repeat(3)); | 160 | cm.replaceSelection(text.repeat(3)); |
161 | const numberOfFences = cm | 161 | const numberOfFences = cm |
162 | .getValue() | 162 | .getValue() |
163 | .split("\n") | 163 | .split('\n') |
164 | .filter(line => line.match(/[\u0060]{3}/)).length; | 164 | .filter(line => line.match(/[\u0060]{3}/)).length; |
165 | if (numberOfFences % 2 === 1) { | 165 | if (numberOfFences % 2 === 1) { |
166 | cm.replaceSelection("map\n\n```"); | 166 | cm.replaceSelection('map\n\n```'); |
167 | cm.setCursor({ line: line + 1 }); | 167 | cm.setCursor({ line: line + 1 }); |
168 | } | 168 | } |
169 | } | 169 | } |
@@ -171,11 +171,11 @@ const completeForCodeBlock = change => { | |||
171 | 171 | ||
172 | // For YAML doc separator, <hr> and code fence | 172 | // For YAML doc separator, <hr> and code fence |
173 | // Auto delete to start of line | 173 | // Auto delete to start of line |
174 | if (change.origin === "+delete") { | 174 | if (change.origin === '+delete') { |
175 | const match = change.removed[0].match(/^[-\u0060]$/)?.at(0); | 175 | const match = change.removed[0].match(/^[-\u0060]$/)?.at(0); |
176 | if (match && cm.getLine(line) === match.repeat(2) && match) { | 176 | if (match && cm.getLine(line) === match.repeat(2) && match) { |
177 | cm.setSelection({ line: line, ch: 0 }, { line: line, ch: 2 }); | 177 | cm.setSelection({ line: line, ch: 0 }, { line: line, ch: 2 }); |
178 | cm.replaceSelection(""); | 178 | cm.replaceSelection(''); |
179 | } | 179 | } |
180 | } | 180 | } |
181 | }; | 181 | }; |
@@ -202,23 +202,23 @@ const updateDumbyMap = () => { | |||
202 | updateDumbyMap(); | 202 | updateDumbyMap(); |
203 | 203 | ||
204 | // Re-render HTML by editor content | 204 | // Re-render HTML by editor content |
205 | cm.on("change", (_, change) => { | 205 | cm.on('change', (_, change) => { |
206 | updateDumbyMap(); | 206 | updateDumbyMap(); |
207 | addClassToCodeLines(); | 207 | addClassToCodeLines(); |
208 | completeForCodeBlock(change); | 208 | completeForCodeBlock(change); |
209 | }); | 209 | }); |
210 | 210 | ||
211 | // Set class for focus | 211 | // Set class for focus |
212 | cm.on("focus", () => { | 212 | cm.on('focus', () => { |
213 | cm.getWrapperElement().classList.add("focus"); | 213 | cm.getWrapperElement().classList.add('focus'); |
214 | HtmlContainer.classList.remove("focus"); | 214 | HtmlContainer.classList.remove('focus'); |
215 | }); | 215 | }); |
216 | 216 | ||
217 | cm.on("beforeChange", (_, change) => { | 217 | cm.on('beforeChange', (_, change) => { |
218 | const line = change.to.line; | 218 | const line = change.to.line; |
219 | // Don't allow more content after YAML doc separator | 219 | // Don't allow more content after YAML doc separator |
220 | if (change.origin.match(/^(\+input|paste)$/)) { | 220 | if (change.origin.match(/^(\+input|paste)$/)) { |
221 | if (cm.getLine(line) === "---" && change.text[0] !== "") { | 221 | if (cm.getLine(line) === '---' && change.text[0] !== '') { |
222 | change.cancel(); | 222 | change.cancel(); |
223 | } | 223 | } |
224 | } | 224 | } |
@@ -239,9 +239,9 @@ window.onhashchange = () => { | |||
239 | // }}} | 239 | // }}} |
240 | // Completion in Code Blok {{{ | 240 | // Completion in Code Blok {{{ |
241 | // Elements about suggestions {{{ | 241 | // Elements about suggestions {{{ |
242 | const menu = document.createElement("div"); | 242 | const menu = document.createElement('div'); |
243 | menu.id = "menu"; | 243 | menu.id = 'menu'; |
244 | menu.onclick = () => (menu.style.display = "none"); | 244 | menu.onclick = () => (menu.style.display = 'none'); |
245 | document.body.append(menu); | 245 | document.body.append(menu); |
246 | 246 | ||
247 | const rendererOptions = {}; | 247 | const rendererOptions = {}; |
@@ -249,7 +249,7 @@ const rendererOptions = {}; | |||
249 | // }}} | 249 | // }}} |
250 | // Aliases for map options {{{ | 250 | // Aliases for map options {{{ |
251 | const aliasesForMapOptions = {}; | 251 | const aliasesForMapOptions = {}; |
252 | const defaultApply = "./dist/default.yml"; | 252 | const defaultApply = './dist/default.yml'; |
253 | fetch(defaultApply) | 253 | fetch(defaultApply) |
254 | .then(res => res.text()) | 254 | .then(res => res.text()) |
255 | .then(rawText => { | 255 | .then(rawText => { |
@@ -269,9 +269,9 @@ const insideCodeblockForMap = anchor => { | |||
269 | let line = anchor.line - 1; | 269 | let line = anchor.line - 1; |
270 | while (line >= 0) { | 270 | while (line >= 0) { |
271 | const content = cm.getLine(line); | 271 | const content = cm.getLine(line); |
272 | if (content === "```map") { | 272 | if (content === '```map') { |
273 | return true; | 273 | return true; |
274 | } else if (content === "```") { | 274 | } else if (content === '```') { |
275 | return false; | 275 | return false; |
276 | } | 276 | } |
277 | line = line - 1; | 277 | line = line - 1; |
@@ -331,7 +331,7 @@ const getSuggestionsForOptions = (optionTyped, validOptions) => { | |||
331 | return suggestOptions.map( | 331 | return suggestOptions.map( |
332 | o => | 332 | o => |
333 | new menuItem.Suggestion({ | 333 | new menuItem.Suggestion({ |
334 | text: `<span>${o.valueOf()}</span><span class='info' title="${o.desc ?? ""}">ⓘ</span>`, | 334 | text: `<span>${o.valueOf()}</span><span class='info' title="${o.desc ?? ''}">ⓘ</span>`, |
335 | replace: `${o.valueOf()}: `, | 335 | replace: `${o.valueOf()}: `, |
336 | }), | 336 | }), |
337 | ); | 337 | ); |
@@ -347,7 +347,7 @@ const getSuggestionFromMapOption = option => { | |||
347 | 347 | ||
348 | return new menuItem.Suggestion({ | 348 | return new menuItem.Suggestion({ |
349 | text: text, | 349 | text: text, |
350 | replace: `${option.valueOf()}: ${option.example ?? ""}`, | 350 | replace: `${option.valueOf()}: ${option.example ?? ''}`, |
351 | }); | 351 | }); |
352 | }; | 352 | }; |
353 | // }}} | 353 | // }}} |
@@ -355,7 +355,7 @@ const getSuggestionFromMapOption = option => { | |||
355 | const getSuggestionsFromAliases = option => | 355 | const getSuggestionsFromAliases = option => |
356 | Object.entries(aliasesForMapOptions[option.valueOf()] ?? {})?.map(record => { | 356 | Object.entries(aliasesForMapOptions[option.valueOf()] ?? {})?.map(record => { |
357 | const [alias, value] = record; | 357 | const [alias, value] = record; |
358 | const valueString = JSON.stringify(value).replaceAll('"', ""); | 358 | const valueString = JSON.stringify(value).replaceAll('"', ''); |
359 | return new menuItem.Suggestion({ | 359 | return new menuItem.Suggestion({ |
360 | text: `<span>${alias}</span><span class="truncate" style="color: gray">${valueString}</span>`, | 360 | text: `<span>${alias}</span><span class="truncate" style="color: gray">${valueString}</span>`, |
361 | replace: `${option.valueOf()}: ${valueString}`, | 361 | replace: `${option.valueOf()}: ${valueString}`, |
@@ -391,17 +391,17 @@ const getSuggestions = anchor => { | |||
391 | .markText( | 391 | .markText( |
392 | { ...anchor, ch: 0 }, | 392 | { ...anchor, ch: 0 }, |
393 | { ...anchor, ch: text.length }, | 393 | { ...anchor, ch: text.length }, |
394 | { className: "invalid-input" }, | 394 | { className: 'invalid-input' }, |
395 | ); | 395 | ); |
396 | 396 | ||
397 | // Check if "use: <renderer>" is set | 397 | // Check if "use: <renderer>" is set |
398 | const lineWithRenderer = getLineWithRenderer(anchor); | 398 | const lineWithRenderer = getLineWithRenderer(anchor); |
399 | const renderer = lineWithRenderer | 399 | const renderer = lineWithRenderer |
400 | ? cm.getLine(lineWithRenderer).split(" ")[1] | 400 | ? cm.getLine(lineWithRenderer).split(' ')[1] |
401 | : null; | 401 | : null; |
402 | if (renderer && anchor.line !== lineWithRenderer) { | 402 | if (renderer && anchor.line !== lineWithRenderer) { |
403 | // Do not check properties | 403 | // Do not check properties |
404 | if (text.startsWith(" ")) return []; | 404 | if (text.startsWith(' ')) return []; |
405 | 405 | ||
406 | // If no valid options for current used renderer, go get it! | 406 | // If no valid options for current used renderer, go get it! |
407 | const validOptions = rendererOptions[renderer]; | 407 | const validOptions = rendererOptions[renderer]; |
@@ -426,7 +426,7 @@ const getSuggestions = anchor => { | |||
426 | } | 426 | } |
427 | 427 | ||
428 | // If input is "key:value" (no space left after colon), then it is invalid | 428 | // If input is "key:value" (no space left after colon), then it is invalid |
429 | const isKeyFinished = text.includes(":"); | 429 | const isKeyFinished = text.includes(':'); |
430 | const isValidKeyValue = text.match(/^[^:]+:\s+/); | 430 | const isValidKeyValue = text.match(/^[^:]+:\s+/); |
431 | if (isKeyFinished && !isValidKeyValue) { | 431 | if (isKeyFinished && !isValidKeyValue) { |
432 | markInputIsInvalid(); | 432 | markInputIsInvalid(); |
@@ -434,7 +434,7 @@ const getSuggestions = anchor => { | |||
434 | } | 434 | } |
435 | 435 | ||
436 | // If user is typing option | 436 | // If user is typing option |
437 | const keyTyped = text.split(":")[0].trim(); | 437 | const keyTyped = text.split(':')[0].trim(); |
438 | if (!isKeyFinished) { | 438 | if (!isKeyFinished) { |
439 | markInputIsInvalid(); | 439 | markInputIsInvalid(); |
440 | return getSuggestionsForOptions(keyTyped, validOptions); | 440 | return getSuggestionsForOptions(keyTyped, validOptions); |
@@ -447,7 +447,7 @@ const getSuggestions = anchor => { | |||
447 | } | 447 | } |
448 | 448 | ||
449 | if (isKeyFinished && matchedOption) { | 449 | if (isKeyFinished && matchedOption) { |
450 | const valueTyped = text.substring(text.indexOf(":") + 1).trim(); | 450 | const valueTyped = text.substring(text.indexOf(':') + 1).trim(); |
451 | const isValidValue = matchedOption.isValid(valueTyped); | 451 | const isValidValue = matchedOption.isValid(valueTyped); |
452 | if (!valueTyped) { | 452 | if (!valueTyped) { |
453 | return [ | 453 | return [ |
@@ -465,8 +465,8 @@ const getSuggestions = anchor => { | |||
465 | const rendererSuggestions = Object.entries(defaultAliases.use) | 465 | const rendererSuggestions = Object.entries(defaultAliases.use) |
466 | .filter(([renderer]) => { | 466 | .filter(([renderer]) => { |
467 | const suggestion = `use: ${renderer}`; | 467 | const suggestion = `use: ${renderer}`; |
468 | const suggestionPattern = suggestion.replace(" ", "").toLowerCase(); | 468 | const suggestionPattern = suggestion.replace(' ', '').toLowerCase(); |
469 | const textPattern = text.replace(" ", "").toLowerCase(); | 469 | const textPattern = text.replace(' ', '').toLowerCase(); |
470 | return suggestion !== text && suggestionPattern.includes(textPattern); | 470 | return suggestion !== text && suggestionPattern.includes(textPattern); |
471 | }) | 471 | }) |
472 | .map( | 472 | .map( |
@@ -484,55 +484,55 @@ const getSuggestions = anchor => { | |||
484 | // {{{ FUNCTION: Show element about suggestions | 484 | // {{{ FUNCTION: Show element about suggestions |
485 | const addSuggestions = (anchor, suggestions) => { | 485 | const addSuggestions = (anchor, suggestions) => { |
486 | if (suggestions.length === 0) { | 486 | if (suggestions.length === 0) { |
487 | menu.style.display = "none"; | 487 | menu.style.display = 'none'; |
488 | return; | 488 | return; |
489 | } else { | 489 | } else { |
490 | menu.style.display = "block"; | 490 | menu.style.display = 'block'; |
491 | } | 491 | } |
492 | 492 | ||
493 | menu.innerHTML = ""; | 493 | menu.innerHTML = ''; |
494 | suggestions | 494 | suggestions |
495 | .map(s => s.createElement(cm)) | 495 | .map(s => s.createElement(cm)) |
496 | .forEach(option => menu.appendChild(option)); | 496 | .forEach(option => menu.appendChild(option)); |
497 | 497 | ||
498 | const widgetAnchor = document.createElement("div"); | 498 | const widgetAnchor = document.createElement('div'); |
499 | cm.addWidget(anchor, widgetAnchor, true); | 499 | cm.addWidget(anchor, widgetAnchor, true); |
500 | const rect = widgetAnchor.getBoundingClientRect(); | 500 | const rect = widgetAnchor.getBoundingClientRect(); |
501 | menu.style.left = `calc(${rect.left}px + 2rem)`; | 501 | menu.style.left = `calc(${rect.left}px + 2rem)`; |
502 | menu.style.top = `calc(${rect.bottom}px + 1rem)`; | 502 | menu.style.top = `calc(${rect.bottom}px + 1rem)`; |
503 | menu.style.maxWidth = `calc(${window.innerWidth}px - ${rect.x}px - 3rem)`; | 503 | menu.style.maxWidth = `calc(${window.innerWidth}px - ${rect.x}px - 3rem)`; |
504 | menu.style.display = "block"; | 504 | menu.style.display = 'block'; |
505 | }; | 505 | }; |
506 | // }}} | 506 | // }}} |
507 | // EVENT: Suggests for current selection {{{ | 507 | // EVENT: Suggests for current selection {{{ |
508 | // FIXME Dont show suggestion when selecting multiple chars | 508 | // FIXME Dont show suggestion when selecting multiple chars |
509 | cm.on("cursorActivity", _ => { | 509 | cm.on('cursorActivity', _ => { |
510 | menu.style.display = "none"; | 510 | menu.style.display = 'none'; |
511 | const anchor = cm.getCursor(); | 511 | const anchor = cm.getCursor(); |
512 | 512 | ||
513 | if (insideCodeblockForMap(anchor)) { | 513 | if (insideCodeblockForMap(anchor)) { |
514 | handleTypingInCodeBlock(anchor); | 514 | handleTypingInCodeBlock(anchor); |
515 | } | 515 | } |
516 | }); | 516 | }); |
517 | cm.on("blur", () => { | 517 | cm.on('blur', () => { |
518 | menu.style.display = "none"; | 518 | menu.style.display = 'none'; |
519 | cm.getWrapperElement().classList.remove("focus"); | 519 | cm.getWrapperElement().classList.remove('focus'); |
520 | HtmlContainer.classList.add("focus"); | 520 | HtmlContainer.classList.add('focus'); |
521 | }); | 521 | }); |
522 | // }}} | 522 | // }}} |
523 | // EVENT: keydown for suggestions {{{ | 523 | // EVENT: keydown for suggestions {{{ |
524 | const keyForSuggestions = ["Tab", "Enter", "Escape"]; | 524 | const keyForSuggestions = ['Tab', 'Enter', 'Escape']; |
525 | cm.on("keydown", (_, e) => { | 525 | cm.on('keydown', (_, e) => { |
526 | if ( | 526 | if ( |
527 | !cm.hasFocus || | 527 | !cm.hasFocus || |
528 | !keyForSuggestions.includes(e.key) || | 528 | !keyForSuggestions.includes(e.key) || |
529 | menu.style.display === "none" | 529 | menu.style.display === 'none' |
530 | ) | 530 | ) |
531 | return; | 531 | return; |
532 | 532 | ||
533 | // Directly add a newline when no suggestion is selected | 533 | // Directly add a newline when no suggestion is selected |
534 | const currentSuggestion = menu.querySelector(".container__suggestion.focus"); | 534 | const currentSuggestion = menu.querySelector('.container__suggestion.focus'); |
535 | if (!currentSuggestion && e.key === "Enter") return; | 535 | if (!currentSuggestion && e.key === 'Enter') return; |
536 | 536 | ||
537 | // Override default behavior | 537 | // Override default behavior |
538 | e.preventDefault(); | 538 | e.preventDefault(); |
@@ -540,25 +540,25 @@ cm.on("keydown", (_, e) => { | |||
540 | // Suggestion when pressing Tab or Shift + Tab | 540 | // Suggestion when pressing Tab or Shift + Tab |
541 | const nextSuggestion = | 541 | const nextSuggestion = |
542 | currentSuggestion?.nextSibling ?? | 542 | currentSuggestion?.nextSibling ?? |
543 | menu.querySelector(".container__suggestion:first-child"); | 543 | menu.querySelector('.container__suggestion:first-child'); |
544 | const previousSuggestion = | 544 | const previousSuggestion = |
545 | currentSuggestion?.previousSibling ?? | 545 | currentSuggestion?.previousSibling ?? |
546 | menu.querySelector(".container__suggestion:last-child"); | 546 | menu.querySelector('.container__suggestion:last-child'); |
547 | const focusSuggestion = e.shiftKey ? previousSuggestion : nextSuggestion; | 547 | const focusSuggestion = e.shiftKey ? previousSuggestion : nextSuggestion; |
548 | 548 | ||
549 | // Current editor selection state | 549 | // Current editor selection state |
550 | const anchor = cm.getCursor(); | 550 | const anchor = cm.getCursor(); |
551 | switch (e.key) { | 551 | switch (e.key) { |
552 | case "Tab": | 552 | case 'Tab': |
553 | Array.from(menu.children).forEach(s => s.classList.remove("focus")); | 553 | Array.from(menu.children).forEach(s => s.classList.remove('focus')); |
554 | focusSuggestion.classList.add("focus"); | 554 | focusSuggestion.classList.add('focus'); |
555 | focusSuggestion.scrollIntoView({ behavior: "smooth", block: "nearest" }); | 555 | focusSuggestion.scrollIntoView({ behavior: 'smooth', block: 'nearest' }); |
556 | break; | 556 | break; |
557 | case "Enter": | 557 | case 'Enter': |
558 | currentSuggestion.onclick(); | 558 | currentSuggestion.onclick(); |
559 | break; | 559 | break; |
560 | case "Escape": | 560 | case 'Escape': |
561 | menu.style.display = "none"; | 561 | menu.style.display = 'none'; |
562 | // Focus editor again | 562 | // Focus editor again |
563 | setTimeout(() => cm.focus() && cm.setCursor(anchor), 100); | 563 | setTimeout(() => cm.focus() && cm.setCursor(anchor), 100); |
564 | break; | 564 | break; |
@@ -566,34 +566,34 @@ cm.on("keydown", (_, e) => { | |||
566 | }); | 566 | }); |
567 | 567 | ||
568 | document.onkeydown = e => { | 568 | document.onkeydown = e => { |
569 | if (e.altKey && e.ctrlKey && e.key === "m") { | 569 | if (e.altKey && e.ctrlKey && e.key === 'm') { |
570 | toggleEditing(); | 570 | toggleEditing(); |
571 | e.preventDefault(); | 571 | e.preventDefault(); |
572 | return null; | 572 | return null; |
573 | } | 573 | } |
574 | 574 | ||
575 | if (!cm.hasFocus()) { | 575 | if (!cm.hasFocus()) { |
576 | if (e.key === "F1") { | 576 | if (e.key === 'F1') { |
577 | e.preventDefault(); | 577 | e.preventDefault(); |
578 | cm.focus(); | 578 | cm.focus(); |
579 | } | 579 | } |
580 | if (e.key === "Tab") { | 580 | if (e.key === 'Tab') { |
581 | e.preventDefault(); | 581 | e.preventDefault(); |
582 | dumbymap.utils.focusNextMap(e.shiftKey); | 582 | dumbymap.utils.focusNextMap(e.shiftKey); |
583 | } | 583 | } |
584 | if (e.key === "x" || e.key === "X") { | 584 | if (e.key === 'x' || e.key === 'X') { |
585 | e.preventDefault(); | 585 | e.preventDefault(); |
586 | dumbymap.utils.switchToNextLayout(e.shiftKey); | 586 | dumbymap.utils.switchToNextLayout(e.shiftKey); |
587 | } | 587 | } |
588 | if (e.key === "n") { | 588 | if (e.key === 'n') { |
589 | e.preventDefault(); | 589 | e.preventDefault(); |
590 | dumbymap.utils.focusNextBlock(); | 590 | dumbymap.utils.focusNextBlock(); |
591 | } | 591 | } |
592 | if (e.key === "p") { | 592 | if (e.key === 'p') { |
593 | e.preventDefault(); | 593 | e.preventDefault(); |
594 | dumbymap.utils.focusNextBlock(true); | 594 | dumbymap.utils.focusNextBlock(true); |
595 | } | 595 | } |
596 | if (e.key === "Escape") { | 596 | if (e.key === 'Escape') { |
597 | e.preventDefault(); | 597 | e.preventDefault(); |
598 | dumbymap.utils.removeBlockFocus(); | 598 | dumbymap.utils.removeBlockFocus(); |
599 | } | 599 | } |
@@ -604,15 +604,15 @@ document.onkeydown = e => { | |||
604 | // }}} | 604 | // }}} |
605 | // Layout Switch {{{ | 605 | // Layout Switch {{{ |
606 | const layoutObserver = new MutationObserver(() => { | 606 | const layoutObserver = new MutationObserver(() => { |
607 | const layout = HtmlContainer.getAttribute("data-layout"); | 607 | const layout = HtmlContainer.getAttribute('data-layout'); |
608 | if (layout !== "normal") { | 608 | if (layout !== 'normal') { |
609 | document.body.removeAttribute("data-mode"); | 609 | document.body.removeAttribute('data-mode'); |
610 | } | 610 | } |
611 | }); | 611 | }); |
612 | 612 | ||
613 | layoutObserver.observe(HtmlContainer, { | 613 | layoutObserver.observe(HtmlContainer, { |
614 | attributes: true, | 614 | attributes: true, |
615 | attributeFilter: ["data-layout"], | 615 | attributeFilter: ['data-layout'], |
616 | attributeOldValue: true, | 616 | attributeOldValue: true, |
617 | }); | 617 | }); |
618 | // }}} | 618 | // }}} |
@@ -624,7 +624,7 @@ document.oncontextmenu = e => { | |||
624 | const range = selection.getRangeAt(0); | 624 | const range = selection.getRangeAt(0); |
625 | if (selection) { | 625 | if (selection) { |
626 | e.preventDefault(); | 626 | e.preventDefault(); |
627 | menu.innerHTML = ""; | 627 | menu.innerHTML = ''; |
628 | menu.style.cssText = `display: block; left: ${e.clientX + 10}px; top: ${e.clientY + 5}px;`; | 628 | menu.style.cssText = `display: block; left: ${e.clientX + 10}px; top: ${e.clientY + 5}px;`; |
629 | const addGeoLink = new menuItem.GeoLink({ range }); | 629 | const addGeoLink = new menuItem.GeoLink({ range }); |
630 | menu.appendChild(addGeoLink.createElement()); | 630 | menu.appendChild(addGeoLink.createElement()); |
@@ -635,7 +635,7 @@ document.oncontextmenu = e => { | |||
635 | }; | 635 | }; |
636 | 636 | ||
637 | const actionOutsideMenu = e => { | 637 | const actionOutsideMenu = e => { |
638 | if (menu.style.display === "none" || cm.hasFocus()) return; | 638 | if (menu.style.display === 'none' || cm.hasFocus()) return; |
639 | const rect = menu.getBoundingClientRect(); | 639 | const rect = menu.getBoundingClientRect(); |
640 | if ( | 640 | if ( |
641 | e.clientX < rect.left || | 641 | e.clientX < rect.left || |
@@ -643,11 +643,11 @@ const actionOutsideMenu = e => { | |||
643 | e.clientY < rect.top || | 643 | e.clientY < rect.top || |
644 | e.clientY > rect.top + rect.height | 644 | e.clientY > rect.top + rect.height |
645 | ) { | 645 | ) { |
646 | menu.style.display = "none"; | 646 | menu.style.display = 'none'; |
647 | } | 647 | } |
648 | }; | 648 | }; |
649 | 649 | ||
650 | document.addEventListener("click", actionOutsideMenu); | 650 | document.addEventListener('click', actionOutsideMenu); |
651 | 651 | ||
652 | // }}} | 652 | // }}} |
653 | 653 | ||