diff options
Diffstat (limited to 'src')
-rw-r--r-- | src/Layout.mjs | 88 | ||||
-rw-r--r-- | src/MenuItem.mjs | 34 | ||||
-rw-r--r-- | src/css/dumbymap.css | 4 | ||||
-rw-r--r-- | src/css/style.css | 2 | ||||
-rw-r--r-- | src/dumbyUtils.mjs | 28 | ||||
-rw-r--r-- | src/dumbymap.mjs | 174 | ||||
-rw-r--r-- | src/editor.mjs | 188 | ||||
-rw-r--r-- | src/utils.mjs | 8 |
8 files changed, 264 insertions, 262 deletions
diff --git a/src/Layout.mjs b/src/Layout.mjs index 9cf2ab9..3a835c9 100644 --- a/src/Layout.mjs +++ b/src/Layout.mjs | |||
@@ -1,9 +1,9 @@ | |||
1 | import PlainDraggable from "plain-draggable"; | 1 | import PlainDraggable from 'plain-draggable'; |
2 | import { onRemove, animateRectTransition } from "./utils"; | 2 | import { onRemove, animateRectTransition } from './utils'; |
3 | 3 | ||
4 | export class Layout { | 4 | export class Layout { |
5 | constructor(options = {}) { | 5 | constructor(options = {}) { |
6 | if (!options.name) throw Error("Layout name is not given"); | 6 | if (!options.name) throw Error('Layout name is not given'); |
7 | this.name = options.name; | 7 | this.name = options.name; |
8 | this.enterHandler = options.enterHandler; | 8 | this.enterHandler = options.enterHandler; |
9 | this.leaveHandler = options.leaveHandler; | 9 | this.leaveHandler = options.leaveHandler; |
@@ -12,63 +12,63 @@ export class Layout { | |||
12 | } | 12 | } |
13 | 13 | ||
14 | export class SideBySide extends Layout { | 14 | export class SideBySide extends Layout { |
15 | name = "side-by-side"; | 15 | name = 'side-by-side'; |
16 | 16 | ||
17 | enterHandler = ({ container, htmlHolder, showcase }) => { | 17 | enterHandler = ({ container, htmlHolder, showcase }) => { |
18 | const bar = document.createElement("div"); | 18 | const bar = document.createElement('div'); |
19 | bar.className = "bar"; | 19 | bar.className = 'bar'; |
20 | bar.innerHTML = '<div class="bar-handle"></div>'; | 20 | bar.innerHTML = '<div class="bar-handle"></div>'; |
21 | const handle = bar.querySelector(".bar-handle"); | 21 | const handle = bar.querySelector('.bar-handle'); |
22 | container.appendChild(bar); | 22 | container.appendChild(bar); |
23 | 23 | ||
24 | // Resize views by value | 24 | // Resize views by value |
25 | const resizeByLeft = left => { | 25 | const resizeByLeft = left => { |
26 | htmlHolder.style.width = left + "px"; | 26 | htmlHolder.style.width = left + 'px'; |
27 | showcase.style.width = | 27 | showcase.style.width = |
28 | parseFloat(getComputedStyle(container).width) - left + "px"; | 28 | parseFloat(getComputedStyle(container).width) - left + 'px'; |
29 | }; | 29 | }; |
30 | 30 | ||
31 | const draggable = new PlainDraggable(bar, { | 31 | const draggable = new PlainDraggable(bar, { |
32 | handle: handle, | 32 | handle: handle, |
33 | containment: { left: "25%", top: 0, right: "75%", height: 0 }, | 33 | containment: { left: '25%', top: 0, right: '75%', height: 0 }, |
34 | }); | 34 | }); |
35 | draggable.draggableCursor = "grab"; | 35 | draggable.draggableCursor = 'grab'; |
36 | 36 | ||
37 | draggable.onDrag = pos => { | 37 | draggable.onDrag = pos => { |
38 | handle.style.transform = "unset"; | 38 | handle.style.transform = 'unset'; |
39 | resizeByLeft(pos.left); | 39 | resizeByLeft(pos.left); |
40 | }; | 40 | }; |
41 | draggable.onDragEnd = _ => { | 41 | draggable.onDragEnd = _ => { |
42 | handle.removeAttribute("style"); | 42 | handle.removeAttribute('style'); |
43 | }; | 43 | }; |
44 | 44 | ||
45 | onRemove(bar, () => draggable.remove()); | 45 | onRemove(bar, () => draggable.remove()); |
46 | }; | 46 | }; |
47 | 47 | ||
48 | leaveHandler = ({ container }) => { | 48 | leaveHandler = ({ container }) => { |
49 | container.querySelector(".bar")?.remove(); | 49 | container.querySelector('.bar')?.remove(); |
50 | }; | 50 | }; |
51 | } | 51 | } |
52 | 52 | ||
53 | export class Overlay extends Layout { | 53 | export class Overlay extends Layout { |
54 | name = "overlay"; | 54 | name = 'overlay'; |
55 | 55 | ||
56 | saveLeftTopAsData = element => { | 56 | saveLeftTopAsData = element => { |
57 | const { left, top } = element.getBoundingClientRect(); | 57 | const { left, top } = element.getBoundingClientRect(); |
58 | element.setAttribute("data-left", left); | 58 | element.setAttribute('data-left', left); |
59 | element.setAttribute("data-top", top); | 59 | element.setAttribute('data-top', top); |
60 | }; | 60 | }; |
61 | 61 | ||
62 | addDraggable = element => { | 62 | addDraggable = element => { |
63 | // Make sure current element always on top | 63 | // Make sure current element always on top |
64 | const siblings = Array.from( | 64 | const siblings = Array.from( |
65 | element.parentElement?.querySelectorAll(":scope > *") ?? [], | 65 | element.parentElement?.querySelectorAll(':scope > *') ?? [], |
66 | ); | 66 | ); |
67 | let popTimer = null; | 67 | let popTimer = null; |
68 | element.onmouseover = () => { | 68 | element.onmouseover = () => { |
69 | popTimer = setTimeout(() => { | 69 | popTimer = setTimeout(() => { |
70 | siblings.forEach(e => e.style.removeProperty("z-index")); | 70 | siblings.forEach(e => e.style.removeProperty('z-index')); |
71 | element.style.zIndex = "9001"; | 71 | element.style.zIndex = '9001'; |
72 | }, 200); | 72 | }, 200); |
73 | }; | 73 | }; |
74 | element.onmouseout = () => { | 74 | element.onmouseout = () => { |
@@ -76,9 +76,9 @@ export class Overlay extends Layout { | |||
76 | }; | 76 | }; |
77 | 77 | ||
78 | // Add draggable part | 78 | // Add draggable part |
79 | const draggablePart = document.createElement("div"); | 79 | const draggablePart = document.createElement('div'); |
80 | element.appendChild(draggablePart); | 80 | element.appendChild(draggablePart); |
81 | draggablePart.className = "draggable-part"; | 81 | draggablePart.className = 'draggable-part'; |
82 | draggablePart.innerHTML = '<div class="handle">\u2630</div>'; | 82 | draggablePart.innerHTML = '<div class="handle">\u2630</div>'; |
83 | 83 | ||
84 | // Add draggable instance | 84 | // Add draggable instance |
@@ -91,16 +91,16 @@ export class Overlay extends Layout { | |||
91 | }); | 91 | }); |
92 | 92 | ||
93 | // FIXME use pure CSS to hide utils | 93 | // FIXME use pure CSS to hide utils |
94 | const utils = element.querySelector(".utils"); | 94 | const utils = element.querySelector('.utils'); |
95 | draggable.onDragStart = () => { | 95 | draggable.onDragStart = () => { |
96 | utils.style.display = "none"; | 96 | utils.style.display = 'none'; |
97 | element.classList.add("drag"); | 97 | element.classList.add('drag'); |
98 | }; | 98 | }; |
99 | 99 | ||
100 | draggable.onDragEnd = () => { | 100 | draggable.onDragEnd = () => { |
101 | utils.style = ""; | 101 | utils.style = ''; |
102 | element.classList.remove("drag"); | 102 | element.classList.remove('drag'); |
103 | element.style.zIndex = "9000"; | 103 | element.style.zIndex = '9000'; |
104 | }; | 104 | }; |
105 | 105 | ||
106 | // Reposition draggable instance when resized | 106 | // Reposition draggable instance when resized |
@@ -125,12 +125,12 @@ export class Overlay extends Layout { | |||
125 | // Create draggable blocks and set each position by previous one | 125 | // Create draggable blocks and set each position by previous one |
126 | let [left, top] = [20, 20]; | 126 | let [left, top] = [20, 20]; |
127 | blocks.forEach(block => { | 127 | blocks.forEach(block => { |
128 | const originLeft = Number(block.getAttribute("data-left")); | 128 | const originLeft = Number(block.getAttribute('data-left')); |
129 | const originTop = Number(block.getAttribute("data-top")); | 129 | const originTop = Number(block.getAttribute('data-top')); |
130 | 130 | ||
131 | // Create draggable block | 131 | // Create draggable block |
132 | const wrapper = document.createElement("div"); | 132 | const wrapper = document.createElement('div'); |
133 | wrapper.classList.add("draggable-block"); | 133 | wrapper.classList.add('draggable-block'); |
134 | wrapper.innerHTML = ` | 134 | wrapper.innerHTML = ` |
135 | <div class="utils"> | 135 | <div class="utils"> |
136 | <div id="close">\u274C</div> | 136 | <div id="close">\u274C</div> |
@@ -138,18 +138,18 @@ export class Overlay extends Layout { | |||
138 | <div id="minus-font-size">\u2796</div> | 138 | <div id="minus-font-size">\u2796</div> |
139 | </div> | 139 | </div> |
140 | `; | 140 | `; |
141 | wrapper.title = "Middle-click to hide block"; | 141 | wrapper.title = 'Middle-click to hide block'; |
142 | wrapper.onmouseup = e => { | 142 | wrapper.onmouseup = e => { |
143 | // Hide block with middle click | 143 | // Hide block with middle click |
144 | if (e.button === 1) { | 144 | if (e.button === 1) { |
145 | wrapper.classList.add("hide"); | 145 | wrapper.classList.add('hide'); |
146 | } | 146 | } |
147 | }; | 147 | }; |
148 | 148 | ||
149 | // Set DOMRect for wrapper | 149 | // Set DOMRect for wrapper |
150 | wrapper.appendChild(block); | 150 | wrapper.appendChild(block); |
151 | wrapper.style.left = left + "px"; | 151 | wrapper.style.left = left + 'px'; |
152 | wrapper.style.top = top + "px"; | 152 | wrapper.style.top = top + 'px'; |
153 | htmlHolder.appendChild(wrapper); | 153 | htmlHolder.appendChild(wrapper); |
154 | const { width } = wrapper.getBoundingClientRect(); | 154 | const { width } = wrapper.getBoundingClientRect(); |
155 | left += width + 30; | 155 | left += width + 30; |
@@ -168,7 +168,7 @@ export class Overlay extends Layout { | |||
168 | // Trivial case: | 168 | // Trivial case: |
169 | // This hack make sure utils remains at the same place even when wrapper resized | 169 | // This hack make sure utils remains at the same place even when wrapper resized |
170 | // Prevent DOMRect changes when user clicking plus/minus button many times | 170 | // Prevent DOMRect changes when user clicking plus/minus button many times |
171 | const utils = wrapper.querySelector(".utils"); | 171 | const utils = wrapper.querySelector('.utils'); |
172 | utils.onmouseover = () => { | 172 | utils.onmouseover = () => { |
173 | const { left, top } = utils.getBoundingClientRect(); | 173 | const { left, top } = utils.getBoundingClientRect(); |
174 | utils.style.cssText = `visibility: visible; z-index: 9000; position: fixed; transition: unset; left: ${left}px; top: ${top}px;`; | 174 | utils.style.cssText = `visibility: visible; z-index: 9000; position: fixed; transition: unset; left: ${left}px; top: ${top}px;`; |
@@ -176,20 +176,20 @@ export class Overlay extends Layout { | |||
176 | }; | 176 | }; |
177 | utils.onmouseout = () => { | 177 | utils.onmouseout = () => { |
178 | wrapper.appendChild(utils); | 178 | wrapper.appendChild(utils); |
179 | utils.removeAttribute("style"); | 179 | utils.removeAttribute('style'); |
180 | }; | 180 | }; |
181 | 181 | ||
182 | // Close button | 182 | // Close button |
183 | wrapper.querySelector("#close").onclick = () => { | 183 | wrapper.querySelector('#close').onclick = () => { |
184 | wrapper.classList.add("hide"); | 184 | wrapper.classList.add('hide'); |
185 | utils.removeAttribute("style"); | 185 | utils.removeAttribute('style'); |
186 | }; | 186 | }; |
187 | // Plus/Minus font-size of content | 187 | // Plus/Minus font-size of content |
188 | wrapper.querySelector("#plus-font-size").onclick = () => { | 188 | wrapper.querySelector('#plus-font-size').onclick = () => { |
189 | const fontSize = parseFloat(getComputedStyle(block).fontSize) / 16; | 189 | const fontSize = parseFloat(getComputedStyle(block).fontSize) / 16; |
190 | block.style.fontSize = `${fontSize + 0.2}rem`; | 190 | block.style.fontSize = `${fontSize + 0.2}rem`; |
191 | }; | 191 | }; |
192 | wrapper.querySelector("#minus-font-size").onclick = () => { | 192 | wrapper.querySelector('#minus-font-size').onclick = () => { |
193 | const fontSize = parseFloat(getComputedStyle(block).fontSize) / 16; | 193 | const fontSize = parseFloat(getComputedStyle(block).fontSize) / 16; |
194 | block.style.fontSize = `${fontSize - 0.2}rem`; | 194 | block.style.fontSize = `${fontSize - 0.2}rem`; |
195 | }; | 195 | }; |
@@ -198,7 +198,7 @@ export class Overlay extends Layout { | |||
198 | 198 | ||
199 | leaveHandler = ({ htmlHolder, blocks }) => { | 199 | leaveHandler = ({ htmlHolder, blocks }) => { |
200 | const resumeFromDraggable = block => { | 200 | const resumeFromDraggable = block => { |
201 | const draggableContainer = block.closest(".draggable-block"); | 201 | const draggableContainer = block.closest('.draggable-block'); |
202 | if (!draggableContainer) return; | 202 | if (!draggableContainer) return; |
203 | htmlHolder.appendChild(block); | 203 | htmlHolder.appendChild(block); |
204 | draggableContainer.remove(); | 204 | draggableContainer.remove(); |
diff --git a/src/MenuItem.mjs b/src/MenuItem.mjs index 2f9aff4..734c313 100644 --- a/src/MenuItem.mjs +++ b/src/MenuItem.mjs | |||
@@ -1,8 +1,8 @@ | |||
1 | import { createGeoLink } from "./dumbymap"; | 1 | import { createGeoLink } from './dumbymap'; |
2 | 2 | ||
3 | export function nextMap() { | 3 | export function nextMap() { |
4 | const element = document.createElement("div"); | 4 | const element = document.createElement('div'); |
5 | element.className = "menu-item"; | 5 | element.className = 'menu-item'; |
6 | element.innerHTML = 'Next Map <span class="info">(Tab)</span>'; | 6 | element.innerHTML = 'Next Map <span class="info">(Tab)</span>'; |
7 | element.onclick = () => this.utils.focusNextMap(); | 7 | element.onclick = () => this.utils.focusNextMap(); |
8 | 8 | ||
@@ -10,8 +10,8 @@ export function nextMap() { | |||
10 | } | 10 | } |
11 | 11 | ||
12 | export function nextBlock() { | 12 | export function nextBlock() { |
13 | const element = document.createElement("div"); | 13 | const element = document.createElement('div'); |
14 | element.className = "menu-item"; | 14 | element.className = 'menu-item'; |
15 | element.innerHTML = 'Next Block <span class="info">(n)</span>'; | 15 | element.innerHTML = 'Next Block <span class="info">(n)</span>'; |
16 | element.onclick = () => this.utils.focusNextBlock(); | 16 | element.onclick = () => this.utils.focusNextBlock(); |
17 | 17 | ||
@@ -19,8 +19,8 @@ export function nextBlock() { | |||
19 | } | 19 | } |
20 | 20 | ||
21 | export function nextLayout() { | 21 | export function nextLayout() { |
22 | const element = document.createElement("div"); | 22 | const element = document.createElement('div'); |
23 | element.className = "menu-item"; | 23 | element.className = 'menu-item'; |
24 | element.innerHTML = 'Next Layout <span class="info">(x)</span>'; | 24 | element.innerHTML = 'Next Layout <span class="info">(x)</span>'; |
25 | element.onclick = () => this.utils.switchToNextLayout(); | 25 | element.onclick = () => this.utils.switchToNextLayout(); |
26 | 26 | ||
@@ -33,9 +33,9 @@ export class GeoLink { | |||
33 | } | 33 | } |
34 | 34 | ||
35 | createElement = () => { | 35 | createElement = () => { |
36 | const element = document.createElement("div"); | 36 | const element = document.createElement('div'); |
37 | element.className = "menu-item"; | 37 | element.className = 'menu-item'; |
38 | element.innerText = "Add GeoLink"; | 38 | element.innerText = 'Add GeoLink'; |
39 | element.onclick = this.addGeoLinkbyRange; | 39 | element.onclick = this.addGeoLinkbyRange; |
40 | 40 | ||
41 | return element; | 41 | return element; |
@@ -49,7 +49,7 @@ export class GeoLink { | |||
49 | if (!match) return false; | 49 | if (!match) return false; |
50 | 50 | ||
51 | const [x, y] = match.slice(1); | 51 | const [x, y] = match.slice(1); |
52 | const anchor = document.createElement("a"); | 52 | const anchor = document.createElement('a'); |
53 | anchor.textContent = content; | 53 | anchor.textContent = content; |
54 | // FIXME apply WGS84 | 54 | // FIXME apply WGS84 |
55 | anchor.href = `geo:${y},${x}?xy=${x},${y}`; | 55 | anchor.href = `geo:${y},${x}?xy=${x},${y}`; |
@@ -67,21 +67,21 @@ export class Suggestion { | |||
67 | } | 67 | } |
68 | 68 | ||
69 | createElement(codemirror) { | 69 | createElement(codemirror) { |
70 | const option = document.createElement("div"); | 70 | const option = document.createElement('div'); |
71 | if (this.text.startsWith("<")) { | 71 | if (this.text.startsWith('<')) { |
72 | option.innerHTML = this.text; | 72 | option.innerHTML = this.text; |
73 | } else { | 73 | } else { |
74 | option.innerText = this.text; | 74 | option.innerText = this.text; |
75 | } | 75 | } |
76 | option.classList.add("container__suggestion"); | 76 | option.classList.add('container__suggestion'); |
77 | option.onmouseover = () => { | 77 | option.onmouseover = () => { |
78 | Array.from(option.parentElement?.children ?? []).forEach(s => | 78 | Array.from(option.parentElement?.children ?? []).forEach(s => |
79 | s.classList.remove("focus"), | 79 | s.classList.remove('focus'), |
80 | ); | 80 | ); |
81 | option.classList.add("focus"); | 81 | option.classList.add('focus'); |
82 | }; | 82 | }; |
83 | option.onmouseout = () => { | 83 | option.onmouseout = () => { |
84 | option.classList.remove("focus"); | 84 | option.classList.remove('focus'); |
85 | }; | 85 | }; |
86 | option.onclick = () => { | 86 | option.onclick = () => { |
87 | const anchor = codemirror.getCursor(); | 87 | const anchor = codemirror.getCursor(); |
diff --git a/src/css/dumbymap.css b/src/css/dumbymap.css index c6782fc..f04b1fb 100644 --- a/src/css/dumbymap.css +++ b/src/css/dumbymap.css | |||
@@ -101,12 +101,12 @@ root { | |||
101 | transform: translate(-50%, -50%); | 101 | transform: translate(-50%, -50%); |
102 | } | 102 | } |
103 | 103 | ||
104 | &[data-render='fulfilled'][data-report$="\20"]::after { | 104 | &[data-render='fulfilled'][data-report$='\20']::after { |
105 | content: '\2714 ' attr(data-report); | 105 | content: '\2714 ' attr(data-report); |
106 | animation: 1.5s forwards fade-out cubic-bezier(0.44, 0.18, 0.86, -0.21); | 106 | animation: 1.5s forwards fade-out cubic-bezier(0.44, 0.18, 0.86, -0.21); |
107 | } | 107 | } |
108 | 108 | ||
109 | &[data-render='unfulfilled'][data-report$="\20"]::after { | 109 | &[data-render='unfulfilled'][data-report$='\20']::after { |
110 | content: '\2716 ' attr(data-report); | 110 | content: '\2716 ' attr(data-report); |
111 | animation: 2.5s forwards fade-out cubic-bezier(0.44, 0.18, 0.86, -0.21); | 111 | animation: 2.5s forwards fade-out cubic-bezier(0.44, 0.18, 0.86, -0.21); |
112 | } | 112 | } |
diff --git a/src/css/style.css b/src/css/style.css index d98e871..ecc7dd8 100644 --- a/src/css/style.css +++ b/src/css/style.css | |||
@@ -84,7 +84,7 @@ a { | |||
84 | color: #007bff; | 84 | color: #007bff; |
85 | text-decoration: none; | 85 | text-decoration: none; |
86 | 86 | ||
87 | &[href^=http]::after { | 87 | &[href^='http']::after { |
88 | content: ''; | 88 | content: ''; |
89 | display: inline-block; | 89 | display: inline-block; |
90 | width: 11px; | 90 | width: 11px; |
diff --git a/src/dumbyUtils.mjs b/src/dumbyUtils.mjs index d30145c..cfac00b 100644 --- a/src/dumbyUtils.mjs +++ b/src/dumbyUtils.mjs | |||
@@ -1,16 +1,16 @@ | |||
1 | export function focusNextMap(reverse = false) { | 1 | export function focusNextMap(reverse = false) { |
2 | const renderedList = Array.from( | 2 | const renderedList = Array.from( |
3 | this.htmlHolder.querySelectorAll("[data-render=fulfilled]"), | 3 | this.htmlHolder.querySelectorAll('[data-render=fulfilled]'), |
4 | ); | 4 | ); |
5 | const mapNum = renderedList.length; | 5 | const mapNum = renderedList.length; |
6 | if (mapNum === 0) return; | 6 | if (mapNum === 0) return; |
7 | 7 | ||
8 | // Get current focused map element | 8 | // Get current focused map element |
9 | const currentFocus = this.container.querySelector(".mapclay.focus"); | 9 | const currentFocus = this.container.querySelector('.mapclay.focus'); |
10 | 10 | ||
11 | // Remove class name of focus for ALL candidates | 11 | // Remove class name of focus for ALL candidates |
12 | // This may trigger animation | 12 | // This may trigger animation |
13 | renderedList.forEach(ele => ele.classList.remove("focus")); | 13 | renderedList.forEach(ele => ele.classList.remove('focus')); |
14 | 14 | ||
15 | // Get next existing map element | 15 | // Get next existing map element |
16 | const padding = reverse ? -1 : 1; | 16 | const padding = reverse ? -1 : 1; |
@@ -19,18 +19,18 @@ export function focusNextMap(reverse = false) { | |||
19 | : 0; | 19 | : 0; |
20 | nextIndex = (nextIndex + mapNum) % mapNum; | 20 | nextIndex = (nextIndex + mapNum) % mapNum; |
21 | const nextFocus = renderedList[nextIndex]; | 21 | const nextFocus = renderedList[nextIndex]; |
22 | nextFocus.classList.add("focus"); | 22 | nextFocus.classList.add('focus'); |
23 | 23 | ||
24 | return nextFocus; | 24 | return nextFocus; |
25 | } | 25 | } |
26 | 26 | ||
27 | export function focusDelay() { | 27 | export function focusDelay() { |
28 | return window.getComputedStyle(this.showcase).display === "none" ? 50 : 300; | 28 | return window.getComputedStyle(this.showcase).display === 'none' ? 50 : 300; |
29 | } | 29 | } |
30 | 30 | ||
31 | export function switchToNextLayout(reverse = false) { | 31 | export function switchToNextLayout(reverse = false) { |
32 | const layouts = this.layouts; | 32 | const layouts = this.layouts; |
33 | const currentLayoutName = this.container.getAttribute("data-layout"); | 33 | const currentLayoutName = this.container.getAttribute('data-layout'); |
34 | const currentIndex = layouts.map(l => l.name).indexOf(currentLayoutName); | 34 | const currentIndex = layouts.map(l => l.name).indexOf(currentLayoutName); |
35 | const padding = reverse ? -1 : 1; | 35 | const padding = reverse ? -1 : 1; |
36 | const nextIndex = | 36 | const nextIndex = |
@@ -38,7 +38,7 @@ export function switchToNextLayout(reverse = false) { | |||
38 | ? 0 | 38 | ? 0 |
39 | : (currentIndex + padding + layouts.length) % layouts.length; | 39 | : (currentIndex + padding + layouts.length) % layouts.length; |
40 | const nextLayout = layouts[nextIndex]; | 40 | const nextLayout = layouts[nextIndex]; |
41 | this.container.setAttribute("data-layout", nextLayout.name); | 41 | this.container.setAttribute('data-layout', nextLayout.name); |
42 | } | 42 | } |
43 | 43 | ||
44 | export function focusNextBlock(reverse = false) { | 44 | export function focusNextBlock(reverse = false) { |
@@ -49,7 +49,7 @@ export function focusNextBlock(reverse = false) { | |||
49 | visibilityProperty: true, | 49 | visibilityProperty: true, |
50 | }), | 50 | }), |
51 | ); | 51 | ); |
52 | const currentBlock = blocks.find(b => b.classList.contains("focus")); | 52 | const currentBlock = blocks.find(b => b.classList.contains('focus')); |
53 | const currentIndex = blocks.indexOf(currentBlock); | 53 | const currentIndex = blocks.indexOf(currentBlock); |
54 | const padding = reverse ? -1 : 1; | 54 | const padding = reverse ? -1 : 1; |
55 | const nextIndex = | 55 | const nextIndex = |
@@ -57,16 +57,16 @@ export function focusNextBlock(reverse = false) { | |||
57 | ? 0 | 57 | ? 0 |
58 | : (currentIndex + padding + blocks.length) % blocks.length; | 58 | : (currentIndex + padding + blocks.length) % blocks.length; |
59 | const nextBlock = blocks[nextIndex]; | 59 | const nextBlock = blocks[nextIndex]; |
60 | blocks.forEach(b => b.classList.remove("focus")); | 60 | blocks.forEach(b => b.classList.remove('focus')); |
61 | nextBlock?.classList?.add("focus"); | 61 | nextBlock?.classList?.add('focus'); |
62 | const scrollBlock = | 62 | const scrollBlock = |
63 | nextBlock.getBoundingClientRect().height > | 63 | nextBlock.getBoundingClientRect().height > |
64 | nextBlock.parentElement.getBoundingClientRect().height * 0.8 | 64 | nextBlock.parentElement.getBoundingClientRect().height * 0.8 |
65 | ? "nearest" | 65 | ? 'nearest' |
66 | : "center"; | 66 | : 'center'; |
67 | nextBlock.scrollIntoView({ behavior: "smooth", block: scrollBlock }); | 67 | nextBlock.scrollIntoView({ behavior: 'smooth', block: scrollBlock }); |
68 | } | 68 | } |
69 | 69 | ||
70 | export function removeBlockFocus() { | 70 | export function removeBlockFocus() { |
71 | this.blocks.forEach(b => b.classList.remove("focus")); | 71 | this.blocks.forEach(b => b.classList.remove('focus')); |
72 | } | 72 | } |
diff --git a/src/dumbymap.mjs b/src/dumbymap.mjs index 241c6b9..dc22021 100644 --- a/src/dumbymap.mjs +++ b/src/dumbymap.mjs | |||
@@ -1,21 +1,21 @@ | |||
1 | import MarkdownIt from "markdown-it"; | 1 | import MarkdownIt from 'markdown-it'; |
2 | import MarkdownItAnchor from "markdown-it-anchor"; | 2 | import MarkdownItAnchor from 'markdown-it-anchor'; |
3 | import MarkdownItFootnote from "markdown-it-footnote"; | 3 | import MarkdownItFootnote from 'markdown-it-footnote'; |
4 | import MarkdownItFrontMatter from "markdown-it-front-matter"; | 4 | import MarkdownItFrontMatter from 'markdown-it-front-matter'; |
5 | import MarkdownItTocDoneRight from "markdown-it-toc-done-right"; | 5 | import MarkdownItTocDoneRight from 'markdown-it-toc-done-right'; |
6 | import LeaderLine from "leader-line"; | 6 | import LeaderLine from 'leader-line'; |
7 | import { renderWith, defaultAliases, parseConfigsFromYaml } from "mapclay"; | 7 | import { renderWith, defaultAliases, parseConfigsFromYaml } from 'mapclay'; |
8 | import { onRemove, animateRectTransition, throttle } from "./utils"; | 8 | import { onRemove, animateRectTransition, throttle } from './utils'; |
9 | import { Layout, SideBySide, Overlay } from "./Layout"; | 9 | import { Layout, SideBySide, Overlay } from './Layout'; |
10 | import * as utils from "./dumbyUtils"; | 10 | import * as utils from './dumbyUtils'; |
11 | 11 | ||
12 | const docLinkSelector = 'a[href^="#"][title^="=>"]'; | 12 | const docLinkSelector = 'a[href^="#"][title^="=>"]'; |
13 | const geoLinkSelector = 'a[href^="geo:"]'; | 13 | const geoLinkSelector = 'a[href^="geo:"]'; |
14 | 14 | ||
15 | const layouts = [ | 15 | const layouts = [ |
16 | new Layout({ name: "normal" }), | 16 | new Layout({ name: 'normal' }), |
17 | new SideBySide({ name: "side-by-side" }), | 17 | new SideBySide({ name: 'side-by-side' }), |
18 | new Overlay({ name: "overlay" }), | 18 | new Overlay({ name: 'overlay' }), |
19 | ]; | 19 | ]; |
20 | const mapCache = {}; | 20 | const mapCache = {}; |
21 | 21 | ||
@@ -26,12 +26,12 @@ const mapCache = {}; | |||
26 | * @param {HTMLElement} Elements contains anchor elements for doclinks | 26 | * @param {HTMLElement} Elements contains anchor elements for doclinks |
27 | */ | 27 | */ |
28 | export const createDocLink = link => { | 28 | export const createDocLink = link => { |
29 | link.classList.add("with-leader-line", "doclink"); | 29 | link.classList.add('with-leader-line', 'doclink'); |
30 | link.lines = []; | 30 | link.lines = []; |
31 | 31 | ||
32 | link.onmouseover = () => { | 32 | link.onmouseover = () => { |
33 | const label = decodeURIComponent(link.href.split("#")[1]); | 33 | const label = decodeURIComponent(link.href.split('#')[1]); |
34 | const selector = link.title.split("=>")[1] ?? "#" + label; | 34 | const selector = link.title.split('=>')[1] ?? '#' + label; |
35 | const target = document.querySelector(selector); | 35 | const target = document.querySelector(selector); |
36 | if (!target?.checkVisibility()) return; | 36 | if (!target?.checkVisibility()) return; |
37 | 37 | ||
@@ -40,13 +40,13 @@ export const createDocLink = link => { | |||
40 | end: target, | 40 | end: target, |
41 | middleLabel: LeaderLine.pathLabel({ | 41 | middleLabel: LeaderLine.pathLabel({ |
42 | text: label, | 42 | text: label, |
43 | fontWeight: "bold", | 43 | fontWeight: 'bold', |
44 | }), | 44 | }), |
45 | hide: true, | 45 | hide: true, |
46 | path: "magnet", | 46 | path: 'magnet', |
47 | }); | 47 | }); |
48 | link.lines.push(line); | 48 | link.lines.push(line); |
49 | line.show("draw", { duration: 300 }); | 49 | line.show('draw', { duration: 300 }); |
50 | }; | 50 | }; |
51 | link.onmouseout = () => { | 51 | link.onmouseout = () => { |
52 | link.lines.forEach(line => line.remove()); | 52 | link.lines.forEach(line => line.remove()); |
@@ -63,13 +63,13 @@ export const createDocLink = link => { | |||
63 | */ | 63 | */ |
64 | export const createGeoLink = (link, callback = null) => { | 64 | export const createGeoLink = (link, callback = null) => { |
65 | const url = new URL(link.href); | 65 | const url = new URL(link.href); |
66 | const xyInParams = url.searchParams.get("xy"); | 66 | const xyInParams = url.searchParams.get('xy'); |
67 | const xy = xyInParams | 67 | const xy = xyInParams |
68 | ? xyInParams.split(",")?.map(Number) | 68 | ? xyInParams.split(',')?.map(Number) |
69 | : url?.href | 69 | : url?.href |
70 | ?.match(/^geo:([0-9.,]+)/) | 70 | ?.match(/^geo:([0-9.,]+)/) |
71 | ?.at(1) | 71 | ?.at(1) |
72 | ?.split(",") | 72 | ?.split(',') |
73 | ?.reverse() | 73 | ?.reverse() |
74 | ?.map(Number); | 74 | ?.map(Number); |
75 | 75 | ||
@@ -78,8 +78,8 @@ export const createGeoLink = (link, callback = null) => { | |||
78 | // Geo information in link | 78 | // Geo information in link |
79 | link.url = url; | 79 | link.url = url; |
80 | link.xy = xy; | 80 | link.xy = xy; |
81 | link.classList.add("with-leader-line", "geolink"); | 81 | link.classList.add('with-leader-line', 'geolink'); |
82 | link.targets = link.url.searchParams.get("id")?.split(",") ?? null; | 82 | link.targets = link.url.searchParams.get('id')?.split(',') ?? null; |
83 | 83 | ||
84 | // LeaderLine | 84 | // LeaderLine |
85 | link.lines = []; | 85 | link.lines = []; |
@@ -94,7 +94,7 @@ export const markdown2HTML = (container, mdContent) => { | |||
94 | Array.from(container.children).map(e => e.remove()); | 94 | Array.from(container.children).map(e => e.remove()); |
95 | 95 | ||
96 | container.innerHTML = '<div class="SemanticHtml"></div>'; | 96 | container.innerHTML = '<div class="SemanticHtml"></div>'; |
97 | const htmlHolder = container.querySelector(".SemanticHtml"); | 97 | const htmlHolder = container.querySelector('.SemanticHtml'); |
98 | 98 | ||
99 | const md = MarkdownIt({ | 99 | const md = MarkdownIt({ |
100 | html: true, | 100 | html: true, |
@@ -102,7 +102,7 @@ export const markdown2HTML = (container, mdContent) => { | |||
102 | }) | 102 | }) |
103 | .use(MarkdownItAnchor, { | 103 | .use(MarkdownItAnchor, { |
104 | permalink: MarkdownItAnchor.permalink.linkInsideHeader({ | 104 | permalink: MarkdownItAnchor.permalink.linkInsideHeader({ |
105 | placement: "before", | 105 | placement: 'before', |
106 | }), | 106 | }), |
107 | }) | 107 | }) |
108 | .use(MarkdownItFootnote) | 108 | .use(MarkdownItFootnote) |
@@ -110,49 +110,49 @@ export const markdown2HTML = (container, mdContent) => { | |||
110 | .use(MarkdownItTocDoneRight); | 110 | .use(MarkdownItTocDoneRight); |
111 | 111 | ||
112 | // FIXME A better way to generate blocks | 112 | // FIXME A better way to generate blocks |
113 | md.renderer.rules.dumby_block_open = () => "<div>"; | 113 | md.renderer.rules.dumby_block_open = () => '<div>'; |
114 | md.renderer.rules.dumby_block_close = () => "</div>"; | 114 | md.renderer.rules.dumby_block_close = () => '</div>'; |
115 | 115 | ||
116 | md.core.ruler.before("block", "dumby_block", state => { | 116 | md.core.ruler.before('block', 'dumby_block', state => { |
117 | state.tokens.push(new state.Token("dumby_block_open", "", 1)); | 117 | state.tokens.push(new state.Token('dumby_block_open', '', 1)); |
118 | }); | 118 | }); |
119 | 119 | ||
120 | // Add close tag for block with more than 2 empty lines | 120 | // Add close tag for block with more than 2 empty lines |
121 | md.block.ruler.before("table", "dumby_block", (state, startLine) => { | 121 | md.block.ruler.before('table', 'dumby_block', (state, startLine) => { |
122 | if ( | 122 | if ( |
123 | state.src[state.bMarks[startLine - 1]] === "\n" && | 123 | state.src[state.bMarks[startLine - 1]] === '\n' && |
124 | state.src[state.bMarks[startLine - 2]] === "\n" && | 124 | state.src[state.bMarks[startLine - 2]] === '\n' && |
125 | state.tokens.at(-1).type !== "list_item_open" // Quick hack for not adding tag after "::marker" for <li> | 125 | state.tokens.at(-1).type !== 'list_item_open' // Quick hack for not adding tag after "::marker" for <li> |
126 | ) { | 126 | ) { |
127 | state.push("dumby_block_close", "", -1); | 127 | state.push('dumby_block_close', '', -1); |
128 | state.push("dumby_block_open", "", 1); | 128 | state.push('dumby_block_open', '', 1); |
129 | } | 129 | } |
130 | }); | 130 | }); |
131 | 131 | ||
132 | md.core.ruler.after("block", "dumby_block", state => { | 132 | md.core.ruler.after('block', 'dumby_block', state => { |
133 | state.tokens.push(new state.Token("dumby_block_close", "", -1)); | 133 | state.tokens.push(new state.Token('dumby_block_close', '', -1)); |
134 | }); | 134 | }); |
135 | 135 | ||
136 | const contentWithToc = "${toc}\n\n\n" + mdContent; | 136 | const contentWithToc = '${toc}\n\n\n' + mdContent; |
137 | htmlHolder.innerHTML = md.render(contentWithToc); | 137 | htmlHolder.innerHTML = md.render(contentWithToc); |
138 | 138 | ||
139 | // TODO Do this in markdown-it | 139 | // TODO Do this in markdown-it |
140 | const blocks = htmlHolder.querySelectorAll(":scope > div:not(:has(nav))"); | 140 | const blocks = htmlHolder.querySelectorAll(':scope > div:not(:has(nav))'); |
141 | blocks.forEach(b => { | 141 | blocks.forEach(b => { |
142 | b.classList.add("dumby-block"); | 142 | b.classList.add('dumby-block'); |
143 | b.setAttribute("data-total", blocks.length); | 143 | b.setAttribute('data-total', blocks.length); |
144 | }); | 144 | }); |
145 | 145 | ||
146 | return container; | 146 | return container; |
147 | //}}} | 147 | //}}} |
148 | }; | 148 | }; |
149 | export const generateMaps = (container, { delay, mapCallback }) => { | 149 | export const generateMaps = (container, { delay, mapCallback }) => { |
150 | container.classList.add("Dumby"); | 150 | container.classList.add('Dumby'); |
151 | const htmlHolder = container.querySelector(".SemanticHtml") ?? container; | 151 | const htmlHolder = container.querySelector('.SemanticHtml') ?? container; |
152 | const blocks = Array.from(htmlHolder.querySelectorAll(".dumby-block")); | 152 | const blocks = Array.from(htmlHolder.querySelectorAll('.dumby-block')); |
153 | const showcase = document.createElement("div"); | 153 | const showcase = document.createElement('div'); |
154 | container.appendChild(showcase); | 154 | container.appendChild(showcase); |
155 | showcase.classList.add("Showcase"); | 155 | showcase.classList.add('Showcase'); |
156 | const renderMaps = []; | 156 | const renderMaps = []; |
157 | 157 | ||
158 | const dumbymap = { | 158 | const dumbymap = { |
@@ -196,13 +196,13 @@ export const generateMaps = (container, { delay, mapCallback }) => { | |||
196 | ).filter(l => createGeoLink(l, geoLinkCallback)); | 196 | ).filter(l => createGeoLink(l, geoLinkCallback)); |
197 | 197 | ||
198 | const isAnchorPointedBy = link => anchor => { | 198 | const isAnchorPointedBy = link => anchor => { |
199 | const mapContainer = anchor.closest(".mapclay"); | 199 | const mapContainer = anchor.closest('.mapclay'); |
200 | const isTarget = !link.targets || link.targets.includes(mapContainer.id); | 200 | const isTarget = !link.targets || link.targets.includes(mapContainer.id); |
201 | return anchor.title === link.url.pathname && isTarget; | 201 | return anchor.title === link.url.pathname && isTarget; |
202 | }; | 202 | }; |
203 | 203 | ||
204 | const isAnchorVisible = anchor => { | 204 | const isAnchorVisible = anchor => { |
205 | const mapContainer = anchor.closest(".mapclay"); | 205 | const mapContainer = anchor.closest('.mapclay'); |
206 | return insideWindow(anchor) && insideParent(anchor, mapContainer); | 206 | return insideWindow(anchor) && insideParent(anchor, mapContainer); |
207 | }; | 207 | }; |
208 | 208 | ||
@@ -211,10 +211,10 @@ export const generateMaps = (container, { delay, mapCallback }) => { | |||
211 | start: link, | 211 | start: link, |
212 | end: anchor, | 212 | end: anchor, |
213 | hide: true, | 213 | hide: true, |
214 | middleLabel: link.url.searchParams.get("text"), | 214 | middleLabel: link.url.searchParams.get('text'), |
215 | path: "magnet", | 215 | path: 'magnet', |
216 | }); | 216 | }); |
217 | line.show("draw", { duration: 300 }); | 217 | line.show('draw', { duration: 300 }); |
218 | return line; | 218 | return line; |
219 | }; | 219 | }; |
220 | 220 | ||
@@ -232,7 +232,7 @@ export const generateMaps = (container, { delay, mapCallback }) => { | |||
232 | }; | 232 | }; |
233 | 233 | ||
234 | const updateMapByMarker = xy => marker => { | 234 | const updateMapByMarker = xy => marker => { |
235 | const renderer = marker.closest(".mapclay")?.renderer; | 235 | const renderer = marker.closest('.mapclay')?.renderer; |
236 | renderer.updateCamera({ center: xy }, true); | 236 | renderer.updateCamera({ center: xy }, true); |
237 | }; | 237 | }; |
238 | 238 | ||
@@ -273,7 +273,7 @@ export const generateMaps = (container, { delay, mapCallback }) => { | |||
273 | const target = mutation.target; | 273 | const target = mutation.target; |
274 | const focus = target | 274 | const focus = target |
275 | .getAttribute(mutation.attributeName) | 275 | .getAttribute(mutation.attributeName) |
276 | .includes("focus"); | 276 | .includes('focus'); |
277 | const shouldBeInShowcase = | 277 | const shouldBeInShowcase = |
278 | focus && | 278 | focus && |
279 | showcase.checkVisibility({ | 279 | showcase.checkVisibility({ |
@@ -287,8 +287,8 @@ export const generateMaps = (container, { delay, mapCallback }) => { | |||
287 | 287 | ||
288 | // Placeholder for map in Showcase, it should has the same DOMRect | 288 | // Placeholder for map in Showcase, it should has the same DOMRect |
289 | const placeholder = target.cloneNode(true); | 289 | const placeholder = target.cloneNode(true); |
290 | placeholder.removeAttribute("id"); | 290 | placeholder.removeAttribute('id'); |
291 | placeholder.classList.remove("mapclay", "focus"); | 291 | placeholder.classList.remove('mapclay', 'focus'); |
292 | target.parentElement.replaceChild(placeholder, target); | 292 | target.parentElement.replaceChild(placeholder, target); |
293 | 293 | ||
294 | // FIXME Maybe use @start-style for CSS | 294 | // FIXME Maybe use @start-style for CSS |
@@ -297,10 +297,10 @@ export const generateMaps = (container, { delay, mapCallback }) => { | |||
297 | // To make sure the original height of placeholder is applied, DOM changes seems needed | 297 | // To make sure the original height of placeholder is applied, DOM changes seems needed |
298 | // then set data-attribute for CSS selector to change height to 0 | 298 | // then set data-attribute for CSS selector to change height to 0 |
299 | placeholder.getBoundingClientRect(); | 299 | placeholder.getBoundingClientRect(); |
300 | placeholder.setAttribute("data-placeholder", target.id); | 300 | placeholder.setAttribute('data-placeholder', target.id); |
301 | 301 | ||
302 | // To fit showcase, remove all inline style | 302 | // To fit showcase, remove all inline style |
303 | target.removeAttribute("style"); | 303 | target.removeAttribute('style'); |
304 | showcase.appendChild(target); | 304 | showcase.appendChild(target); |
305 | 305 | ||
306 | // Resume rect from Semantic HTML to Showcase, with animation | 306 | // Resume rect from Semantic HTML to Showcase, with animation |
@@ -333,7 +333,7 @@ export const generateMaps = (container, { delay, mapCallback }) => { | |||
333 | // Layout {{{ | 333 | // Layout {{{ |
334 | // press key to switch layout | 334 | // press key to switch layout |
335 | const defaultLayout = layouts[0]; | 335 | const defaultLayout = layouts[0]; |
336 | container.setAttribute("data-layout", defaultLayout.name); | 336 | container.setAttribute('data-layout', defaultLayout.name); |
337 | 337 | ||
338 | // observe layout change | 338 | // observe layout change |
339 | const layoutObserver = new MutationObserver(mutations => { | 339 | const layoutObserver = new MutationObserver(mutations => { |
@@ -351,7 +351,7 @@ export const generateMaps = (container, { delay, mapCallback }) => { | |||
351 | Object.values(dumbymap) | 351 | Object.values(dumbymap) |
352 | .flat() | 352 | .flat() |
353 | .filter(ele => ele instanceof HTMLElement) | 353 | .filter(ele => ele instanceof HTMLElement) |
354 | .forEach(ele => ele.removeAttribute("style")); | 354 | .forEach(ele => ele.removeAttribute('style')); |
355 | 355 | ||
356 | if (newLayout) { | 356 | if (newLayout) { |
357 | layouts | 357 | layouts |
@@ -362,13 +362,13 @@ export const generateMaps = (container, { delay, mapCallback }) => { | |||
362 | // Since layout change may show/hide showcase, the current focused map should do something | 362 | // Since layout change may show/hide showcase, the current focused map should do something |
363 | // Reset attribute triggers MutationObserver which is observing it | 363 | // Reset attribute triggers MutationObserver which is observing it |
364 | const focusMap = | 364 | const focusMap = |
365 | container.querySelector(".mapclay.focus") ?? | 365 | container.querySelector('.mapclay.focus') ?? |
366 | container.querySelector(".mapclay"); | 366 | container.querySelector('.mapclay'); |
367 | focusMap?.classList?.add("focus"); | 367 | focusMap?.classList?.add('focus'); |
368 | }); | 368 | }); |
369 | layoutObserver.observe(container, { | 369 | layoutObserver.observe(container, { |
370 | attributes: true, | 370 | attributes: true, |
371 | attributeFilter: ["data-layout"], | 371 | attributeFilter: ['data-layout'], |
372 | attributeOldValue: true, | 372 | attributeOldValue: true, |
373 | characterDataOldValue: true, | 373 | characterDataOldValue: true, |
374 | }); | 374 | }); |
@@ -380,8 +380,10 @@ export const generateMaps = (container, { delay, mapCallback }) => { | |||
380 | 380 | ||
381 | const afterMapRendered = renderer => { | 381 | const afterMapRendered = renderer => { |
382 | const mapElement = renderer.target; | 382 | const mapElement = renderer.target; |
383 | mapElement.setAttribute("tabindex", "-1"); | 383 | //FIXME |
384 | if (mapElement.getAttribute("data-render") === "fulfilled") { | 384 | mapElement.renderer = renderer; |
385 | mapElement.setAttribute('tabindex', '-1'); | ||
386 | if (mapElement.getAttribute('data-render') === 'fulfilled') { | ||
385 | mapCache[mapElement.id] = renderer; | 387 | mapCache[mapElement.id] = renderer; |
386 | } | 388 | } |
387 | 389 | ||
@@ -394,14 +396,14 @@ export const generateMaps = (container, { delay, mapCallback }) => { | |||
394 | // Add markers with Geolinks | 396 | // Add markers with Geolinks |
395 | renderer.addMarkers(markers); | 397 | renderer.addMarkers(markers); |
396 | mapElement | 398 | mapElement |
397 | .querySelectorAll(".marker") | 399 | .querySelectorAll('.marker') |
398 | .forEach(marker => htmlHolder.anchors.push(marker)); | 400 | .forEach(marker => htmlHolder.anchors.push(marker)); |
399 | 401 | ||
400 | // Work with Mutation Observer | 402 | // Work with Mutation Observer |
401 | const observer = mapFocusObserver(); | 403 | const observer = mapFocusObserver(); |
402 | mapFocusObserver().observe(mapElement, { | 404 | mapFocusObserver().observe(mapElement, { |
403 | attributes: true, | 405 | attributes: true, |
404 | attributeFilter: ["class"], | 406 | attributeFilter: ['class'], |
405 | attributeOldValue: true, | 407 | attributeOldValue: true, |
406 | }); | 408 | }); |
407 | onRemove(mapElement, () => observer.disconnect()); | 409 | onRemove(mapElement, () => observer.disconnect()); |
@@ -412,10 +414,10 @@ export const generateMaps = (container, { delay, mapCallback }) => { | |||
412 | const assignMapId = config => { | 414 | const assignMapId = config => { |
413 | let mapId = config.id; | 415 | let mapId = config.id; |
414 | if (!mapId) { | 416 | if (!mapId) { |
415 | mapId = config.use?.split("/")?.at(-1); | 417 | mapId = config.use?.split('/')?.at(-1); |
416 | let counter = 1; | 418 | let counter = 1; |
417 | while (!mapId || mapIdList.includes(mapId)) { | 419 | while (!mapId || mapIdList.includes(mapId)) { |
418 | mapId = `${config.use ?? "unnamed"}-${counter}`; | 420 | mapId = `${config.use ?? 'unnamed'}-${counter}`; |
419 | counter++; | 421 | counter++; |
420 | } | 422 | } |
421 | config.id = mapId; | 423 | config.id = mapId; |
@@ -426,21 +428,21 @@ export const generateMaps = (container, { delay, mapCallback }) => { | |||
426 | 428 | ||
427 | // Render each code block with "language-map" class | 429 | // Render each code block with "language-map" class |
428 | const elementsWithMapConfig = Array.from( | 430 | const elementsWithMapConfig = Array.from( |
429 | container.querySelectorAll('pre:has(.language-map)') ?? [] | 431 | container.querySelectorAll('pre:has(.language-map)') ?? [], |
430 | ) | 432 | ); |
431 | /** | 433 | /** |
432 | * updateAttributeByStep. | 434 | * updateAttributeByStep. |
433 | * | 435 | * |
434 | * @param {Object} -- renderer which is running steps | 436 | * @param {Object} -- renderer which is running steps |
435 | */ | 437 | */ |
436 | const updateAttributeByStep = ({ results, target, steps }) => { | 438 | const updateAttributeByStep = ({ results, target, steps }) => { |
437 | let passNum = results | 439 | let passNum = results.filter( |
438 | .filter(r => r.type === 'step' && r.state.match(/success|skip/)) | 440 | r => r.type === 'step' && r.state.match(/success|skip/), |
439 | .length | 441 | ).length; |
440 | const total = steps.length; | 442 | const total = steps.length; |
441 | passNum += `/${total}`; | 443 | passNum += `/${total}`; |
442 | if (results.filter(r=>r.type === 'step').length === total) { | 444 | if (results.filter(r => r.type === 'step').length === total) { |
443 | passNum += '\u0020' | 445 | passNum += '\u0020'; |
444 | } | 446 | } |
445 | 447 | ||
446 | // FIXME HACK use MutationObserver for animation | 448 | // FIXME HACK use MutationObserver for animation |
@@ -449,7 +451,7 @@ export const generateMaps = (container, { delay, mapCallback }) => { | |||
449 | await new Promise(resolve => setTimeout(resolve, 150)); | 451 | await new Promise(resolve => setTimeout(resolve, 150)); |
450 | target.setAttribute('data-report', passNum); | 452 | target.setAttribute('data-report', passNum); |
451 | }); | 453 | }); |
452 | } | 454 | }; |
453 | /** | 455 | /** |
454 | * config converter for mapclay.renderWith() | 456 | * config converter for mapclay.renderWith() |
455 | * | 457 | * |
@@ -465,21 +467,21 @@ export const generateMaps = (container, { delay, mapCallback }) => { | |||
465 | ...(config.aliases ?? {}), | 467 | ...(config.aliases ?? {}), |
466 | }, | 468 | }, |
467 | stepCallback: updateAttributeByStep, | 469 | stepCallback: updateAttributeByStep, |
468 | }) | 470 | }); |
469 | const render = renderWith(configConverter) | 471 | const render = renderWith(configConverter); |
470 | elementsWithMapConfig.forEach(target => { | 472 | elementsWithMapConfig.forEach(target => { |
471 | // Get text in code block starts with markdown text '```map' | 473 | // Get text in code block starts with markdown text '```map' |
472 | const configText = target | 474 | const configText = target |
473 | .querySelector(".language-map") | 475 | .querySelector('.language-map') |
474 | .textContent // BE CAREFUL!!! 0xa0 char is "non-breaking spaces" in HTML text content | 476 | .textContent // BE CAREFUL!!! 0xa0 char is "non-breaking spaces" in HTML text content |
475 | // replace it by normal space | 477 | // replace it by normal space |
476 | .replace(/\u00A0/g, "\u0020"); | 478 | .replace(/\u00A0/g, '\u0020'); |
477 | 479 | ||
478 | let configList = []; | 480 | let configList = []; |
479 | try { | 481 | try { |
480 | configList = parseConfigsFromYaml(configText).map(assignMapId); | 482 | configList = parseConfigsFromYaml(configText).map(assignMapId); |
481 | } catch (_) { | 483 | } catch (_) { |
482 | console.warn("Fail to parse yaml config for element", target); | 484 | console.warn('Fail to parse yaml config for element', target); |
483 | return; | 485 | return; |
484 | } | 486 | } |
485 | 487 | ||
@@ -494,9 +496,9 @@ export const generateMaps = (container, { delay, mapCallback }) => { | |||
494 | }); | 496 | }); |
495 | 497 | ||
496 | // trivial: if map cache is applied, do not show yaml text | 498 | // trivial: if map cache is applied, do not show yaml text |
497 | if (target.querySelector(".mapclay")) { | 499 | if (target.querySelector('.mapclay')) { |
498 | target | 500 | target |
499 | .querySelectorAll(":scope > :not([data-render=fulfilled])") | 501 | .querySelectorAll(':scope > :not([data-render=fulfilled])') |
500 | .forEach(e => e.remove()); | 502 | .forEach(e => e.remove()); |
501 | } | 503 | } |
502 | 504 | ||
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 | ||
diff --git a/src/utils.mjs b/src/utils.mjs index ad9131e..3824672 100644 --- a/src/utils.mjs +++ b/src/utils.mjs | |||
@@ -6,7 +6,7 @@ | |||
6 | */ | 6 | */ |
7 | export const onRemove = (element, callback) => { | 7 | export const onRemove = (element, callback) => { |
8 | const parent = element.parentNode; | 8 | const parent = element.parentNode; |
9 | if (!parent) throw new Error("The node must already be attached"); | 9 | if (!parent) throw new Error('The node must already be attached'); |
10 | 10 | ||
11 | const obs = new MutationObserver(mutations => { | 11 | const obs = new MutationObserver(mutations => { |
12 | for (const mutation of mutations) { | 12 | for (const mutation of mutations) { |
@@ -33,7 +33,7 @@ export const onRemove = (element, callback) => { | |||
33 | */ | 33 | */ |
34 | export const animateRectTransition = (element, rect, options = {}) => { | 34 | export const animateRectTransition = (element, rect, options = {}) => { |
35 | if (!element.parentElement) | 35 | if (!element.parentElement) |
36 | throw new Error("The node must already be attached"); | 36 | throw new Error('The node must already be attached'); |
37 | 37 | ||
38 | const { width: w1, height: h1, left: x1, top: y1 } = rect; | 38 | const { width: w1, height: h1, left: x1, top: y1 } = rect; |
39 | const { | 39 | const { |
@@ -62,7 +62,7 @@ export const animateRectTransition = (element, rect, options = {}) => { | |||
62 | 62 | ||
63 | return element.animate(keyframes, { | 63 | return element.animate(keyframes, { |
64 | duration: options.duration ?? 500, | 64 | duration: options.duration ?? 500, |
65 | easing: "ease-in-out", | 65 | easing: 'ease-in-out', |
66 | }); | 66 | }); |
67 | }; | 67 | }; |
68 | 68 | ||
@@ -82,7 +82,7 @@ export function throttle(func, delay) { | |||
82 | 82 | ||
83 | timerFlag = setTimeout( | 83 | timerFlag = setTimeout( |
84 | () => (timerFlag = null), | 84 | () => (timerFlag = null), |
85 | typeof delay === "function" ? delay.call(context) : delay, | 85 | typeof delay === 'function' ? delay.call(context) : delay, |
86 | ); | 86 | ); |
87 | 87 | ||
88 | return func.call(context, ...args); | 88 | return func.call(context, ...args); |