aboutsummaryrefslogtreecommitdiffhomepage
path: root/src/dumbymap.mjs
diff options
context:
space:
mode:
Diffstat (limited to 'src/dumbymap.mjs')
-rw-r--r--src/dumbymap.mjs101
1 files changed, 57 insertions, 44 deletions
diff --git a/src/dumbymap.mjs b/src/dumbymap.mjs
index b95e7a9..cd3b8ff 100644
--- a/src/dumbymap.mjs
+++ b/src/dumbymap.mjs
@@ -1,4 +1,3 @@
1// vim:foldmethod
2import MarkdownIt from 'markdown-it' 1import MarkdownIt from 'markdown-it'
3import MarkdownItAnchor from 'markdown-it-anchor' 2import MarkdownItAnchor from 'markdown-it-anchor'
4import MarkdownItFootnote from 'markdown-it-footnote' 3import MarkdownItFootnote from 'markdown-it-footnote'
@@ -8,15 +7,30 @@ import LeaderLine from 'leader-line'
8import PlainDraggable from 'plain-draggable' 7import PlainDraggable from 'plain-draggable'
9import { render, parseConfigsFromYaml } from 'mapclay' 8import { render, parseConfigsFromYaml } from 'mapclay'
10 9
11const observers = new Map() 10function onRemove(element, callback) {
11 const parent = element.parentNode;
12 if (!parent) throw new Error("The node must already be attached");
12 13
13export const markdown2HTML = async (container, mdContent) => { 14 const obs = new MutationObserver(mutations => {
14 // Render: Markdown -> HTML {{{ 15 for (const mutation of mutations) {
16 for (const el of mutation.removedNodes) {
17 if (el === element) {
18 obs.disconnect();
19 callback();
20 }
21 }
22 }
23 });
24 obs.observe(parent, { childList: true, });
25}
26
27// Render: Markdown -> HTML {{{
28export const markdown2HTML = (container, mdContent) => {
29
30 Array.from(container.children).map(e => e.remove())
15 31
16 container.innerHTML = ` 32 container.innerHTML = '<div class="SemanticHtml"></div>'
17 <div id="map"></div> 33 const htmlHolder = container.querySelector('.SemanticHtml')
18 <div id="markdown"></div>
19 `
20 34
21 const md = MarkdownIt({ html: true }) 35 const md = MarkdownIt({ html: true })
22 .use(MarkdownItAnchor, { 36 .use(MarkdownItAnchor, {
@@ -46,15 +60,15 @@ export const markdown2HTML = async (container, mdContent) => {
46 state.tokens.push(new state.Token('draggable_block_close', '', -1)) 60 state.tokens.push(new state.Token('draggable_block_close', '', -1))
47 }) 61 })
48 62
49 const markdown = container.querySelector('#markdown')
50 const contentWithToc = '${toc}\n\n\n' + mdContent 63 const contentWithToc = '${toc}\n\n\n' + mdContent
51 markdown.innerHTML = md.render(contentWithToc); 64 htmlHolder.innerHTML = md.render(contentWithToc);
52 markdown.querySelectorAll('*> div:not(:has(nav))') 65 // TODO Do this in markdown-it
66 htmlHolder.querySelectorAll('*> div:not(:has(nav))')
53 .forEach(b => b.classList.add('draggable-block')) 67 .forEach(b => b.classList.add('draggable-block'))
54 68
55 69
56 // TODO Improve it! 70 // TODO Improve it!
57 const docLinks = Array.from(container.querySelectorAll('#markdown a[href^="#"][title^="doc"]')) 71 const docLinks = Array.from(container.querySelectorAll('a[href^="#"][title^="doc"]'))
58 docLinks.forEach(link => { 72 docLinks.forEach(link => {
59 link.classList.add('with-leader-line', 'doclink') 73 link.classList.add('with-leader-line', 'doclink')
60 link.lines = [] 74 link.lines = []
@@ -77,24 +91,29 @@ export const markdown2HTML = async (container, mdContent) => {
77 link.lines.length = 0 91 link.lines.length = 0
78 } 92 }
79 }) 93 })
94
95 return container
80 //}}} 96 //}}}
81} 97}
82 98
99// FIXME Don't use hard-coded CSS selector
83export const generateMaps = async (container) => { 100export const generateMaps = async (container) => {
84 // LeaderLine {{{ 101 // LeaderLine {{{
85 102
86 // Get anchors with "geo:" scheme 103 // Get anchors with "geo:" scheme
87 const markdown = container.querySelector('#markdown') 104 const htmlHolder = container.querySelector('.SemanticHtml') ?? container
88 markdown.anchors = [] 105 htmlHolder.anchors = []
89 106
90 // Set focusArea 107 // Set focusArea
91 const focusArea = container.querySelector('#map') 108 const showcase = document.createElement('div')
109 container.appendChild(showcase)
110 showcase.classList.add('Showcase')
92 const mapPlaceholder = document.createElement('div') 111 const mapPlaceholder = document.createElement('div')
93 mapPlaceholder.id = 'mapPlaceholder' 112 mapPlaceholder.id = 'mapPlaceholder'
94 focusArea.appendChild(mapPlaceholder) 113 showcase.appendChild(mapPlaceholder)
95 114
96 // Links points to map by geo schema and id 115 // Links points to map by geo schema and id
97 const geoLinks = Array.from(container.querySelectorAll('#markdown a[href^="geo:"]')) 116 const geoLinks = Array.from(htmlHolder.querySelectorAll('a[href^="geo:"]'))
98 .filter(link => { 117 .filter(link => {
99 const url = new URL(link.href) 118 const url = new URL(link.href)
100 const xy = url?.href?.match(/^geo:([0-9.,]+)/)?.at(1)?.split(',')?.reverse()?.map(Number) 119 const xy = url?.href?.match(/^geo:([0-9.,]+)/)?.at(1)?.split(',')?.reverse()?.map(Number)
@@ -113,7 +132,7 @@ export const generateMaps = async (container) => {
113 link.onmouseout = () => removeLeaderLines(link) 132 link.onmouseout = () => removeLeaderLines(link)
114 link.onclick = (event) => { 133 link.onclick = (event) => {
115 event.preventDefault() 134 event.preventDefault()
116 markdown.anchors 135 htmlHolder.anchors
117 .filter(isAnchorPointedBy(link)) 136 .filter(isAnchorPointedBy(link))
118 .forEach(updateMapByMarker(xy)) 137 .forEach(updateMapByMarker(xy))
119 // TODO Just hide leader line and show it again 138 // TODO Just hide leader line and show it again
@@ -147,7 +166,7 @@ export const generateMaps = async (container) => {
147 } 166 }
148 167
149 const addLeaderLines = (link) => { 168 const addLeaderLines = (link) => {
150 link.lines = markdown.anchors 169 link.lines = htmlHolder.anchors
151 .filter(isAnchorPointedBy(link)) 170 .filter(isAnchorPointedBy(link))
152 .filter(isAnchorVisible) 171 .filter(isAnchorVisible)
153 .map(drawLeaderLine(link)) 172 .map(drawLeaderLine(link))
@@ -187,7 +206,7 @@ export const generateMaps = async (container) => {
187 206
188 const afterEachMapLoaded = (mapContainer) => { 207 const afterEachMapLoaded = (mapContainer) => {
189 mapContainer.querySelectorAll('.marker') 208 mapContainer.querySelectorAll('.marker')
190 .forEach(marker => markdown.anchors.push(marker)) 209 .forEach(marker => htmlHolder.anchors.push(marker))
191 210
192 const focusClickedMap = () => { 211 const focusClickedMap = () => {
193 if (container.getAttribute('data-layout') !== 'none') return 212 if (container.getAttribute('data-layout') !== 'none') return
@@ -215,6 +234,7 @@ export const generateMaps = async (container) => {
215 mapIdList.push(mapId) 234 mapIdList.push(mapId)
216 } 235 }
217 236
237 // FIXME Create markers after maps are created
218 const markerOptions = geoLinks.map(link => ({ 238 const markerOptions = geoLinks.map(link => ({
219 targets: link.targets, 239 targets: link.targets,
220 xy: link.xy, 240 xy: link.xy,
@@ -263,20 +283,16 @@ export const generateMaps = async (container) => {
263 283
264 //}}} 284 //}}}
265 // CSS observer {{{ 285 // CSS observer {{{
266 if (!observers.get(container)) {
267 observers.set(container, [])
268 }
269 const obs = observers.get(container)
270 if (obs.length) {
271 obs.forEach(o => o.disconnect())
272 obs.length = 0
273 }
274 // Layout{{{ 286 // Layout{{{
275 287
276 // press key to switch layout 288 // press key to switch layout
277 const layouts = ['none', 'side', 'overlay'] 289 const layouts = ['none', 'side', 'overlay']
278 container.setAttribute("data-layout", layouts[0]) 290 container.setAttribute("data-layout", layouts[0])
291
292 // FIXME Use UI to switch layouts
293 const originalKeyDown = document.onkeydown
279 document.onkeydown = (event) => { 294 document.onkeydown = (event) => {
295 originalKeyDown(event)
280 if (event.key === 'x' && container.querySelector('.map-container')) { 296 if (event.key === 'x' && container.querySelector('.map-container')) {
281 let currentLayout = container.getAttribute('data-layout') 297 let currentLayout = container.getAttribute('data-layout')
282 currentLayout = currentLayout ? currentLayout : 'none' 298 currentLayout = currentLayout ? currentLayout : 'none'
@@ -287,8 +303,8 @@ export const generateMaps = async (container) => {
287 } 303 }
288 304
289 // Add draggable part for blocks 305 // Add draggable part for blocks
290 markdown.blocks = Array.from(markdown.querySelectorAll('.draggable-block')) 306 htmlHolder.blocks = Array.from(htmlHolder.querySelectorAll('.draggable-block'))
291 markdown.blocks.forEach(block => { 307 htmlHolder.blocks.forEach(block => {
292 const draggablePart = document.createElement('div'); 308 const draggablePart = document.createElement('div');
293 draggablePart.classList.add('draggable') 309 draggablePart.classList.add('draggable')
294 draggablePart.textContent = '☰' 310 draggablePart.textContent = '☰'
@@ -303,30 +319,30 @@ export const generateMaps = async (container) => {
303 // observe layout change 319 // observe layout change
304 const layoutObserver = new MutationObserver(() => { 320 const layoutObserver = new MutationObserver(() => {
305 const layout = container.getAttribute('data-layout') 321 const layout = container.getAttribute('data-layout')
306 markdown.blocks.forEach(b => b.style.display = "block") 322 htmlHolder.blocks.forEach(b => b.style.display = "block")
307 323
308 if (layout === 'none') { 324 if (layout === 'none') {
309 mapPlaceholder.innerHTML = "" 325 mapPlaceholder.innerHTML = ""
310 const map = focusArea.querySelector('.map-container') 326 const map = showcase.querySelector('.map-container')
311 // Swap focused map and palceholder in markdown 327 // Swap focused map and palceholder in markdown
312 if (map) { 328 if (map) {
313 mapPlaceholder.parentElement?.replaceChild(map, mapPlaceholder) 329 mapPlaceholder.parentElement?.replaceChild(map, mapPlaceholder)
314 focusArea.append(mapPlaceholder) 330 showcase.append(mapPlaceholder)
315 } 331 }
316 } else { 332 } else {
317 // If paceholder is not set, create one and put map into focusArea 333 // If paceholder is not set, create one and put map into focusArea
318 if (focusArea.contains(mapPlaceholder)) { 334 if (showcase.contains(mapPlaceholder)) {
319 const mapContainer = container.querySelector('.map-container.focus') ?? container.querySelector('.map-container') 335 const mapContainer = container.querySelector('.map-container.focus') ?? container.querySelector('.map-container')
320 mapPlaceholder.innerHTML = `<div>Placeholder</div>` 336 mapPlaceholder.innerHTML = `<div>Placeholder</div>`
321 // TODO Get snapshot image 337 // TODO Get snapshot image
322 // mapPlaceholder.src = map.map.getCanvas().toDataURL() 338 // mapPlaceholder.src = map.map.getCanvas().toDataURL()
323 mapContainer.parentElement?.replaceChild(mapPlaceholder, mapContainer) 339 mapContainer.parentElement?.replaceChild(mapPlaceholder, mapContainer)
324 focusArea.appendChild(mapContainer) 340 showcase.appendChild(mapContainer)
325 } 341 }
326 } 342 }
327 343
328 if (layout === 'overlay') { 344 if (layout === 'overlay') {
329 markdown.blocks.forEach(block => { 345 htmlHolder.blocks.forEach(block => {
330 block.draggableInstance = new PlainDraggable(block, { handle: block.querySelector('.draggable') }) 346 block.draggableInstance = new PlainDraggable(block, { handle: block.querySelector('.draggable') })
331 block.draggableInstance.snap = { x: { step: 20 }, y: { step: 20 } } 347 block.draggableInstance.snap = { x: { step: 20 }, y: { step: 20 } }
332 // block.draggableInstance.onDragEnd = () => { 348 // block.draggableInstance.onDragEnd = () => {
@@ -334,13 +350,9 @@ export const generateMaps = async (container) => {
334 // } 350 // }
335 }) 351 })
336 } else { 352 } else {
337 markdown.blocks.forEach(block => { 353 htmlHolder.blocks.forEach(block => {
338 try { 354 block.style.transform = 'none'
339 block.style.transform = 'none' 355 block.draggableInstance?.remove()
340 block.draggableInstance.remove()
341 } catch (err) {
342 console.warn('Fail to remove draggable instance', err)
343 }
344 }) 356 })
345 } 357 }
346 }); 358 });
@@ -349,7 +361,8 @@ export const generateMaps = async (container) => {
349 attributeFilter: ["data-layout"], 361 attributeFilter: ["data-layout"],
350 attributeOldValue: true 362 attributeOldValue: true
351 }); 363 });
352 obs.push(layoutObserver) 364
365 onRemove(htmlHolder, () => layoutObserver.disconnect())
353 //}}} 366 //}}}
354 //}}} 367 //}}}
355 return container 368 return container