aboutsummaryrefslogtreecommitdiffhomepage
path: root/src/editor.mjs
diff options
context:
space:
mode:
authorHsieh Chin Fan <pham@topo.tw>2024-09-29 15:06:59 +0800
committerHsieh Chin Fan <pham@topo.tw>2024-09-29 15:06:59 +0800
commitf598dcbf6283f2c2b81a63d315548c6bc4e943fb (patch)
tree204a86cf47dd2bd2223502d2daaeb4afc6ecce09 /src/editor.mjs
parent88201b62764931868f0bc3cae978edb3fd307485 (diff)
style: prettier
Diffstat (limited to 'src/editor.mjs')
-rw-r--r--src/editor.mjs188
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"*/
3import { markdown2HTML, generateMaps } from "./dumbymap"; 3import { markdown2HTML, generateMaps } from './dumbymap';
4import { defaultAliases, parseConfigsFromYaml } from "mapclay"; 4import { defaultAliases, parseConfigsFromYaml } from 'mapclay';
5import * as menuItem from "./MenuItem"; 5import * as menuItem from './MenuItem';
6 6
7// Set up Containers {{{ 7// Set up Containers {{{
8 8
9const HtmlContainer = document.querySelector(".DumbyMap"); 9const HtmlContainer = document.querySelector('.DumbyMap');
10const textArea = document.querySelector(".editor textarea"); 10const textArea = document.querySelector('.editor textarea');
11let dumbymap; 11let dumbymap;
12 12
13const toggleEditing = () => { 13const 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
105const initialState = getStateFromHash(window.location.hash); 105const initialState = getStateFromHash(window.location.hash);
106window.location.hash = ""; 106window.location.hash = '';
107const contentFromHash = initialState.content; 107const 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
142const completeForCodeBlock = change => { 142const 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 = () => {
202updateDumbyMap(); 202updateDumbyMap();
203 203
204// Re-render HTML by editor content 204// Re-render HTML by editor content
205cm.on("change", (_, change) => { 205cm.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
212cm.on("focus", () => { 212cm.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
217cm.on("beforeChange", (_, change) => { 217cm.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 {{{
242const menu = document.createElement("div"); 242const menu = document.createElement('div');
243menu.id = "menu"; 243menu.id = 'menu';
244menu.onclick = () => (menu.style.display = "none"); 244menu.onclick = () => (menu.style.display = 'none');
245document.body.append(menu); 245document.body.append(menu);
246 246
247const rendererOptions = {}; 247const rendererOptions = {};
@@ -249,7 +249,7 @@ const rendererOptions = {};
249// }}} 249// }}}
250// Aliases for map options {{{ 250// Aliases for map options {{{
251const aliasesForMapOptions = {}; 251const aliasesForMapOptions = {};
252const defaultApply = "./dist/default.yml"; 252const defaultApply = './dist/default.yml';
253fetch(defaultApply) 253fetch(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 => {
355const getSuggestionsFromAliases = option => 355const 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
485const addSuggestions = (anchor, suggestions) => { 485const 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
509cm.on("cursorActivity", _ => { 509cm.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});
517cm.on("blur", () => { 517cm.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 {{{
524const keyForSuggestions = ["Tab", "Enter", "Escape"]; 524const keyForSuggestions = ['Tab', 'Enter', 'Escape'];
525cm.on("keydown", (_, e) => { 525cm.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
568document.onkeydown = e => { 568document.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 {{{
606const layoutObserver = new MutationObserver(() => { 606const 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
613layoutObserver.observe(HtmlContainer, { 613layoutObserver.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
637const actionOutsideMenu = e => { 637const 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
650document.addEventListener("click", actionOutsideMenu); 650document.addEventListener('click', actionOutsideMenu);
651 651
652// }}} 652// }}}
653 653