aboutsummaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
-rw-r--r--eslint.config.js49
-rw-r--r--package.json3
-rw-r--r--rollup.config.js27
-rw-r--r--src/Layout.mjs8
-rw-r--r--src/MenuItem.mjs46
-rw-r--r--src/dumbyUtils.mjs36
-rw-r--r--src/dumbymap.mjs44
-rw-r--r--src/editor.mjs140
-rw-r--r--src/utils.mjs8
9 files changed, 154 insertions, 207 deletions
diff --git a/eslint.config.js b/eslint.config.js
deleted file mode 100644
index 3bca729..0000000
--- a/eslint.config.js
+++ /dev/null
@@ -1,49 +0,0 @@
1import globals from 'globals'
2import js from '@eslint/js'
3import importPlugin from 'eslint-plugin-import'
4import promisePlugin from 'eslint-plugin-promise'
5import nodePlugin from 'eslint-plugin-node'
6import jsdoc from 'eslint-plugin-jsdoc'
7
8export default [
9 js.configs.recommended,
10 {
11 languageOptions: {
12 ecmaVersion: 2022,
13 sourceType: 'module',
14 globals: {
15 ...globals.browser,
16 ...globals.node
17 }
18 },
19 plugins: {
20 import: importPlugin,
21 promise: promisePlugin,
22 node: nodePlugin
23 },
24 rules: {
25 'no-unused-vars': ['warn', {
26 varsIgnorePattern: '^_',
27 argsIgnorePattern: '^_',
28 caughtErrorsIgnorePattern: '^ignore|_'
29 }],
30 'import/no-unresolved': 'error',
31 'no-console': ['error', { allow: ['info', 'warn', 'error'] }],
32 eqeqeq: ['error', 'always'],
33 // 'curly': ['warn', 'multi'],
34 'prefer-const': 'error',
35 'no-var': 'error',
36 'array-callback-return': 'error',
37 'no-unexpected-multiline': 'warn'
38 }
39 },
40 {
41 files: ['src/*js'],
42 plugins: {
43 jsdoc
44 },
45 rules: {
46 'jsdoc/require-description': 'warn'
47 }
48 }
49]
diff --git a/package.json b/package.json
index 2c39691..0017e6c 100644
--- a/package.json
+++ b/package.json
@@ -25,7 +25,7 @@
25 "build-resources": "cp node_modules/easymde/dist/easymde.min.js dist; cp node_modules/easymde/dist/easymde.min.css dist/css", 25 "build-resources": "cp node_modules/easymde/dist/easymde.min.js dist; cp node_modules/easymde/dist/easymde.min.css dist/css",
26 "server": "live-server --port=8080 --ignore='**/src/**js' --wait=2000 --no-browser --cors", 26 "server": "live-server --port=8080 --ignore='**/src/**js' --wait=2000 --no-browser --cors",
27 "dev": "npm run server", 27 "dev": "npm run server",
28 "lint": "npx standard", 28 "lint": "standard --fix",
29 "style": "scripts/stylelint.sh", 29 "style": "scripts/stylelint.sh",
30 "prepack": "npm run lint && npm run style && npm run build", 30 "prepack": "npm run lint && npm run style && npm run build",
31 "postpack": "rm -rf dist/css dist/renderers; ln -sf `pwd`/src/css dist; cp node_modules/easymde/dist/easymde.min.css src/css; ln -sf `pwd`/mapclay/dist/renderers dist" 31 "postpack": "rm -rf dist/css dist/renderers; ln -sf `pwd`/src/css dist; cp node_modules/easymde/dist/easymde.min.css src/css; ln -sf `pwd`/mapclay/dist/renderers dist"
@@ -38,7 +38,6 @@
38 "globals": "^15.10.0", 38 "globals": "^15.10.0",
39 "rollup": "^4.24.0", 39 "rollup": "^4.24.0",
40 "rollup-plugin-bundle-stats": "^4.15.1", 40 "rollup-plugin-bundle-stats": "^4.15.1",
41 "standard": "^17.1.2",
42 "stylelint": "^16.9.0", 41 "stylelint": "^16.9.0",
43 "stylelint-config-standard": "^36.0.1", 42 "stylelint-config-standard": "^36.0.1",
44 "stylelint-order": "^6.0.4" 43 "stylelint-order": "^6.0.4"
diff --git a/rollup.config.js b/rollup.config.js
index 145224b..f5a22eb 100644
--- a/rollup.config.js
+++ b/rollup.config.js
@@ -13,12 +13,12 @@ const general = {
13 dir: './dist', 13 dir: './dist',
14 format: 'esm', 14 format: 'esm',
15 entryFileNames: '[name].mjs', 15 entryFileNames: '[name].mjs',
16 sourcemap: 'true' 16 sourcemap: 'true',
17 } 17 },
18 ], 18 ],
19 watch: { 19 watch: {
20 clearScreen: false, 20 clearScreen: false,
21 include: ['src/**', 'mapclay/dist/mapclay.mjs'] 21 include: ['src/**', 'mapclay/dist/mapclay.mjs'],
22 }, 22 },
23 context: 'window', 23 context: 'window',
24 plugins: [ 24 plugins: [
@@ -26,13 +26,12 @@ const general = {
26 name: 'watch-mapclay', 26 name: 'watch-mapclay',
27 buildStart () { 27 buildStart () {
28 const mapclayPath = join(process.cwd(), 'mapclay', 'dist', 'mapclay.mjs') 28 const mapclayPath = join(process.cwd(), 'mapclay', 'dist', 'mapclay.mjs')
29 console.log('Watching:', mapclayPath)
30 if (existsSync(mapclayPath)) { 29 if (existsSync(mapclayPath)) {
31 this.addWatchFile(mapclayPath) 30 this.addWatchFile(mapclayPath)
32 } else { 31 } else {
33 console.log('mapclay.mjs not found at:', mapclayPath) 32 console.warn('mapclay.mjs not found at:', mapclayPath)
34 } 33 }
35 } 34 },
36 }, 35 },
37 { 36 {
38 name: 'leader-line', 37 name: 'leader-line',
@@ -41,7 +40,7 @@ const general = {
41 return `${code}\nexport default LeaderLine;` 40 return `${code}\nexport default LeaderLine;`
42 } 41 }
43 return null 42 return null
44 } 43 },
45 }, 44 },
46 { 45 {
47 name: 'mapclay', 46 name: 'mapclay',
@@ -50,24 +49,24 @@ const general = {
50 return './mapclay/dist/mapclay.mjs' 49 return './mapclay/dist/mapclay.mjs'
51 } 50 }
52 return null 51 return null
53 } 52 },
54 }, 53 },
55 node(), 54 node(),
56 commonjs(), 55 commonjs(),
57 production && terser({ 56 production && terser({
58 keep_fnames: true 57 keep_fnames: true,
59 }), 58 }),
60 production && bundleStats() 59 production && bundleStats(),
61 ] 60 ],
62} 61}
63 62
64export default [ 63export default [
65 { 64 {
66 input: 'src/editor.mjs' 65 input: 'src/editor.mjs',
67 }, 66 },
68 { 67 {
69 input: 'src/dumbymap.mjs' 68 input: 'src/dumbymap.mjs',
70 } 69 },
71] 70]
72 .map(config => ({ ...general, ...config })) 71 .map(config => ({ ...general, ...config }))
73 .filter((config) => production || config.input.match(/editor/)) 72 .filter((config) => production || config.input.match(/editor/))
diff --git a/src/Layout.mjs b/src/Layout.mjs
index 6bb6282..c5276b4 100644
--- a/src/Layout.mjs
+++ b/src/Layout.mjs
@@ -55,7 +55,7 @@ export class SideBySide extends Layout {
55 55
56 const draggable = new PlainDraggable(bar, { 56 const draggable = new PlainDraggable(bar, {
57 handle, 57 handle,
58 containment: { left: '25%', top: 0, right: '75%', height: 0 } 58 containment: { left: '25%', top: 0, right: '75%', height: 0 },
59 }) 59 })
60 draggable.draggableCursor = 'grab' 60 draggable.draggableCursor = 'grab'
61 61
@@ -110,7 +110,7 @@ export class Overlay extends Layout {
110 addDraggable = element => { 110 addDraggable = element => {
111 // Make sure current element always on top 111 // Make sure current element always on top
112 const siblings = Array.from( 112 const siblings = Array.from(
113 element.parentElement?.querySelectorAll(':scope > *') ?? [] 113 element.parentElement?.querySelectorAll(':scope > *') ?? [],
114 ) 114 )
115 let popTimer = null 115 let popTimer = null
116 element.onmouseover = () => { 116 element.onmouseover = () => {
@@ -135,7 +135,7 @@ export class Overlay extends Layout {
135 top, 135 top,
136 left, 136 left,
137 handle: draggablePart, 137 handle: draggablePart,
138 snap: { x: { step: 20 }, y: { step: 20 } } 138 snap: { x: { step: 20 }, y: { step: 20 } },
139 }) 139 })
140 140
141 // FIXME use pure CSS to hide utils 141 // FIXME use pure CSS to hide utils
@@ -215,7 +215,7 @@ export class Overlay extends Layout {
215 animateRectTransition( 215 animateRectTransition(
216 wrapper, 216 wrapper,
217 { left: originLeft, top: originTop }, 217 { left: originLeft, top: originTop },
218 { resume: true, duration: 300 } 218 { resume: true, duration: 300 },
219 ).finished.finally(() => this.addDraggable(wrapper)) 219 ).finished.finally(() => this.addDraggable(wrapper))
220 220
221 // Trivial case: 221 // Trivial case:
diff --git a/src/MenuItem.mjs b/src/MenuItem.mjs
index d580cb3..0fea539 100644
--- a/src/MenuItem.mjs
+++ b/src/MenuItem.mjs
@@ -81,9 +81,9 @@ export const pickMapItem = ({ utils }) =>
81 onclick: () => { 81 onclick: () => {
82 map.classList.add('focus') 82 map.classList.add('focus')
83 map.scrollIntoView({ behavior: 'smooth' }) 83 map.scrollIntoView({ behavior: 'smooth' })
84 } 84 },
85 }) 85 }),
86 ) 86 ),
87 }) 87 })
88 88
89/** 89/**
@@ -118,10 +118,10 @@ export const pickBlockItem = ({ blocks, utils }) =>
118 // UX: remove menu after user select/deselect blocks 118 // UX: remove menu after user select/deselect blocks
119 const submenu = e.target.closest('.sub-menu') 119 const submenu = e.target.closest('.sub-menu')
120 submenu.onmouseleave = () => { submenu.closest('.menu').style.display = 'none' } 120 submenu.onmouseleave = () => { submenu.closest('.menu').style.display = 'none' }
121 } 121 },
122 }) 122 })
123 } 123 },
124 ) 124 ),
125 }) 125 })
126 126
127/** 127/**
@@ -138,14 +138,14 @@ export const pickLayoutItem = ({ container, layouts }) =>
138 layout => 138 layout =>
139 new Item({ 139 new Item({
140 text: layout.name, 140 text: layout.name,
141 onclick: () => container.setAttribute('data-layout', layout.name) 141 onclick: () => container.setAttribute('data-layout', layout.name),
142 }) 142 }),
143 ), 143 ),
144 new Item({ 144 new Item({
145 innerHTML: '<a href="https://github.com/outdoorsafetylab/dumbymap#layouts" class="external" style="display: block; padding: 0.5rem;">More...</a>', 145 innerHTML: '<a href="https://github.com/outdoorsafetylab/dumbymap#layouts" class="external" style="display: block; padding: 0.5rem;">More...</a>',
146 style: 'padding: 0;' 146 style: 'padding: 0;',
147 }) 147 }),
148 ] 148 ],
149 }) 149 })
150 150
151/** 151/**
@@ -174,7 +174,7 @@ export const addGeoLink = ({ utils }, range) =>
174 range.deleteContents() 174 range.deleteContents()
175 range.insertNode(anchor) 175 range.insertNode(anchor)
176 } 176 }
177 } 177 },
178 }) 178 })
179 179
180/** 180/**
@@ -195,7 +195,7 @@ export class Suggestion extends Item {
195 195
196 this.onmouseover = () => { 196 this.onmouseover = () => {
197 Array.from(this.parentElement?.children)?.forEach(s => 197 Array.from(this.parentElement?.children)?.forEach(s =>
198 s.classList.remove('focus') 198 s.classList.remove('focus'),
199 ) 199 )
200 this.classList.add('focus') 200 this.classList.add('focus')
201 } 201 }
@@ -244,12 +244,12 @@ export const renderResults = ({ modal, modalContent }, map) =>
244 success: 'green', 244 success: 'green',
245 fail: 'red', 245 fail: 'red',
246 skip: 'black', 246 skip: 'black',
247 stop: 'chocolate' 247 stop: 'chocolate',
248 }[result.state] ?? 'black' 248 }[result.state] ?? 'black'
249 printObject( 249 printObject(
250 result, 250 result,
251 modalContent, 251 modalContent,
252 `${result.func.name} <span style='float: right;'><span style='display: inline-block; width: 100px; color: ${color};'>${result.state}</span></span>` 252 `${result.func.name} <span style='float: right;'><span style='display: inline-block; width: 100px; color: ${color};'>${result.state}</span></span>`,
253 ) 253 )
254 } 254 }
255 255
@@ -258,7 +258,7 @@ export const renderResults = ({ modal, modalContent }, map) =>
258 prepareHeading.textContent = 'Prepare Steps' 258 prepareHeading.textContent = 'Prepare Steps'
259 modalContent.appendChild(prepareHeading) 259 modalContent.appendChild(prepareHeading)
260 const prepareSteps = map.renderer.results.filter( 260 const prepareSteps = map.renderer.results.filter(
261 r => r.type === 'prepare' 261 r => r.type === 'prepare',
262 ) 262 )
263 prepareSteps.forEach(printDetails) 263 prepareSteps.forEach(printDetails)
264 264
@@ -268,7 +268,7 @@ export const renderResults = ({ modal, modalContent }, map) =>
268 modalContent.appendChild(renderHeading) 268 modalContent.appendChild(renderHeading)
269 const renderSteps = map.renderer.results.filter(r => r.type === 'render') 269 const renderSteps = map.renderer.results.filter(r => r.type === 'render')
270 renderSteps.forEach(printDetails) 270 renderSteps.forEach(printDetails)
271 } 271 },
272 }) 272 })
273 273
274/** 274/**
@@ -326,7 +326,7 @@ function printObject (obj, parentElement, name = null) {
326export const toggleBlockFocus = block => 326export const toggleBlockFocus = block =>
327 new Item({ 327 new Item({
328 text: 'Toggle Focus', 328 text: 'Toggle Focus',
329 onclick: () => block.classList.toggle('focus') 329 onclick: () => block.classList.toggle('focus'),
330 }) 330 })
331 331
332/** 332/**
@@ -337,7 +337,7 @@ export const toggleBlockFocus = block =>
337export const toggleMapFocus = map => 337export const toggleMapFocus = map =>
338 new Item({ 338 new Item({
339 text: 'Toggle Focus', 339 text: 'Toggle Focus',
340 onclick: () => map.classList.toggle('focus') 340 onclick: () => map.classList.toggle('focus'),
341 }) 341 })
342 342
343/** 343/**
@@ -354,7 +354,7 @@ export const getCoordinatesByPixels = (map, xy) =>
354 const xyString = `[${x.toFixed(7)}, ${y.toFixed(7)}]` 354 const xyString = `[${x.toFixed(7)}, ${y.toFixed(7)}]`
355 navigator.clipboard.writeText(xyString) 355 navigator.clipboard.writeText(xyString)
356 window.alert(`${xyString} copied to clipboard`) 356 window.alert(`${xyString} copied to clipboard`)
357 } 357 },
358 }) 358 })
359 359
360/** 360/**
@@ -365,7 +365,7 @@ export const getCoordinatesByPixels = (map, xy) =>
365export const restoreCamera = map => 365export const restoreCamera = map =>
366 new Item({ 366 new Item({
367 text: 'Restore Camera', 367 text: 'Restore Camera',
368 onclick: () => map.renderer.restoreCamera() 368 onclick: () => map.renderer.restoreCamera(),
369 }) 369 })
370 370
371/** 371/**
@@ -387,6 +387,6 @@ export const addRefLink = (cm, refLinks) =>
387 } else { 387 } else {
388 cm.replaceSelection(`[${selection}][${refLink.ref}]`) 388 cm.replaceSelection(`[${selection}][${refLink.ref}]`)
389 } 389 }
390 } 390 },
391 })) 391 })),
392 }) 392 })
diff --git a/src/dumbyUtils.mjs b/src/dumbyUtils.mjs
index 852e4c7..2efc3b1 100644
--- a/src/dumbyUtils.mjs
+++ b/src/dumbyUtils.mjs
@@ -6,7 +6,7 @@ import { insideWindow, insideParent } from './utils'
6 * 6 *
7 * @param {Boolean} reverse -- focus previous map 7 * @param {Boolean} reverse -- focus previous map
8 */ 8 */
9export function focusNextMap(reverse = false) { 9export function focusNextMap (reverse = false) {
10 const renderedList = this.utils.renderedMaps() 10 const renderedList = this.utils.renderedMaps()
11 const index = renderedList.findIndex(e => e.classList.contains('focus')) 11 const index = renderedList.findIndex(e => e.classList.contains('focus'))
12 const nextIndex = (index + (reverse ? -1 : 1)) % renderedList.length 12 const nextIndex = (index + (reverse ? -1 : 1)) % renderedList.length
@@ -21,13 +21,13 @@ export function focusNextMap(reverse = false) {
21 * 21 *
22 * @param {Boolean} reverse -- focus previous block 22 * @param {Boolean} reverse -- focus previous block
23 */ 23 */
24export function focusNextBlock(reverse = false) { 24export function focusNextBlock (reverse = false) {
25 const blocks = this.blocks.filter(b => 25 const blocks = this.blocks.filter(b =>
26 b.checkVisibility({ 26 b.checkVisibility({
27 contentVisibilityAuto: true, 27 contentVisibilityAuto: true,
28 opacityProperty: true, 28 opacityProperty: true,
29 visibilityProperty: true 29 visibilityProperty: true,
30 }) 30 }),
31 ) 31 )
32 const index = blocks.findIndex(e => e.classList.contains('focus')) 32 const index = blocks.findIndex(e => e.classList.contains('focus'))
33 const nextIndex = (index + (reverse ? -1 : 1)) % blocks.length 33 const nextIndex = (index + (reverse ? -1 : 1)) % blocks.length
@@ -56,7 +56,7 @@ export const scrollToBlock = block => {
56/** 56/**
57 * focusDelay. Delay of throttle, value changes by cases 57 * focusDelay. Delay of throttle, value changes by cases
58 */ 58 */
59export function focusDelay() { 59export function focusDelay () {
60 return window.window.getComputedStyle(this.showcase).display === 'none' ? 50 : 300 60 return window.window.getComputedStyle(this.showcase).display === 'none' ? 50 : 300
61} 61}
62 62
@@ -65,7 +65,7 @@ export function focusDelay() {
65 * 65 *
66 * @param {Boolean} reverse -- Switch to previous one 66 * @param {Boolean} reverse -- Switch to previous one
67 */ 67 */
68export function switchToNextLayout(reverse = false) { 68export function switchToNextLayout (reverse = false) {
69 const layouts = this.layouts 69 const layouts = this.layouts
70 const currentLayoutName = this.container.getAttribute('data-layout') 70 const currentLayoutName = this.container.getAttribute('data-layout')
71 const currentIndex = layouts.map(l => l.name).indexOf(currentLayoutName) 71 const currentIndex = layouts.map(l => l.name).indexOf(currentLayoutName)
@@ -81,7 +81,7 @@ export function switchToNextLayout(reverse = false) {
81/** 81/**
82 * removeBlockFocus. 82 * removeBlockFocus.
83 */ 83 */
84export function removeBlockFocus() { 84export function removeBlockFocus () {
85 this.blocks.forEach(b => b.classList.remove('focus')) 85 this.blocks.forEach(b => b.classList.remove('focus'))
86} 86}
87 87
@@ -94,7 +94,7 @@ export function removeBlockFocus() {
94const getMarkersFromMaps = link => { 94const getMarkersFromMaps = link => {
95 const maps = Array.from( 95 const maps = Array.from(
96 link.closest('.Dumby') 96 link.closest('.Dumby')
97 .querySelectorAll('.mapclay[data-render="fulfilled"]') 97 .querySelectorAll('.mapclay[data-render="fulfilled"]'),
98 ) 98 )
99 return maps 99 return maps
100 .filter(map => link.targets ? link.targets.includes(map.id) : true) 100 .filter(map => link.targets ? link.targets.includes(map.id) : true)
@@ -105,7 +105,7 @@ const getMarkersFromMaps = link => {
105 return map.querySelector(`.marker[title="${markerTitle}"]`) ?? 105 return map.querySelector(`.marker[title="${markerTitle}"]`) ??
106 renderer.addMarker({ 106 renderer.addMarker({
107 xy: link.xy, 107 xy: link.xy,
108 title: markerTitle 108 title: markerTitle,
109 }) 109 })
110 }) 110 })
111} 111}
@@ -122,7 +122,7 @@ const addLeaderLine = (link, target) => {
122 end: target, 122 end: target,
123 hide: true, 123 hide: true,
124 middleLabel: link.url.searchParams.get('text'), 124 middleLabel: link.url.searchParams.get('text'),
125 path: 'magnet' 125 path: 'magnet',
126 }) 126 })
127 line.show('draw', { duration: 300 }) 127 line.show('draw', { duration: 300 })
128 128
@@ -196,10 +196,10 @@ export const createDocLink = link => {
196 end: target, 196 end: target,
197 middleLabel: LeaderLine.pathLabel({ 197 middleLabel: LeaderLine.pathLabel({
198 text: label, 198 text: label,
199 fontWeight: 'bold' 199 fontWeight: 'bold',
200 }), 200 }),
201 hide: true, 201 hide: true,
202 path: 'magnet' 202 path: 'magnet',
203 }) 203 })
204 link.lines.push(line) 204 link.lines.push(line)
205 line.show('draw', { duration: 300 }) 205 line.show('draw', { duration: 300 })
@@ -229,7 +229,6 @@ const removeLeaderLines = link => {
229 * @return {Function} function 229 * @return {Function} function
230 */ 230 */
231const updateMapCameraByMarker = xy => marker => { 231const updateMapCameraByMarker = xy => marker => {
232 console.log('update')
233 const renderer = marker.closest('.mapclay')?.renderer 232 const renderer = marker.closest('.mapclay')?.renderer
234 renderer.updateCamera({ center: xy }, true) 233 renderer.updateCamera({ center: xy }, true)
235} 234}
@@ -244,10 +243,17 @@ const isAnchorVisible = anchor => {
244 return insideWindow(anchor) && insideParent(anchor, mapContainer) 243 return insideWindow(anchor) && insideParent(anchor, mapContainer)
245} 244}
246 245
246/**
247 * addAnchorByPoint.
248 *
249 * @param {point} options.point -- object has {x, y} for window coordinates
250 * @param {HTMLElement} options.map
251 * @param {Function} options.validateAnchorName -- validate anchor name is OK to use
252 */
247export const addAnchorByPoint = ({ 253export const addAnchorByPoint = ({
248 point, 254 point,
249 map, 255 map,
250 validateAnchorName = () => true 256 validateAnchorName = () => true,
251}) => { 257}) => {
252 const rect = map.getBoundingClientRect() 258 const rect = map.getBoundingClientRect()
253 const [x, y] = map.renderer 259 const [x, y] = map.renderer
@@ -268,7 +274,7 @@ export const addAnchorByPoint = ({
268 map.renderer.addMarker({ 274 map.renderer.addMarker({
269 xy: [x, y], 275 xy: [x, y],
270 title: `${map.id}@${x},${y}`, 276 title: `${map.id}@${x},${y}`,
271 type: 'circle' 277 type: 'circle',
272 }) 278 })
273 279
274 return { ref: anchorName, link } 280 return { ref: anchorName, link }
diff --git a/src/dumbymap.mjs b/src/dumbymap.mjs
index 1da5bb6..b02e783 100644
--- a/src/dumbymap.mjs
+++ b/src/dumbymap.mjs
@@ -17,7 +17,7 @@ const geoLinkSelector = 'a[href^="geo:"]'
17const layouts = [ 17const layouts = [
18 new Layout({ name: 'normal' }), 18 new Layout({ name: 'normal' }),
19 new SideBySide({ name: 'side-by-side' }), 19 new SideBySide({ name: 'side-by-side' }),
20 new Overlay({ name: 'overlay' }) 20 new Overlay({ name: 'overlay' }),
21] 21]
22const mapCache = {} 22const mapCache = {}
23 23
@@ -37,12 +37,12 @@ export const markdown2HTML = (container, mdContent) => {
37 const md = MarkdownIt({ 37 const md = MarkdownIt({
38 html: true, 38 html: true,
39 breaks: true, 39 breaks: true,
40 linkify: true 40 linkify: true,
41 }) 41 })
42 .use(MarkdownItAnchor, { 42 .use(MarkdownItAnchor, {
43 permalink: MarkdownItAnchor.permalink.linkInsideHeader({ 43 permalink: MarkdownItAnchor.permalink.linkInsideHeader({
44 placement: 'before' 44 placement: 'before',
45 }) 45 }),
46 }) 46 })
47 .use(MarkdownItFootnote) 47 .use(MarkdownItFootnote)
48 .use(MarkdownItFrontMatter) 48 .use(MarkdownItFrontMatter)
@@ -58,11 +58,11 @@ export const markdown2HTML = (container, mdContent) => {
58 match.text = `${x}${sep} ${y}` 58 match.text = `${x}${sep} ${y}`
59 match.index += match.text.indexOf(x) + 1 59 match.index += match.text.indexOf(x) + 1
60 return match 60 return match
61 } 61 },
62 } 62 }
63 const patterns = ['[', '(', '📍', '\uFF08', '@', 'geo:', 'twd'] 63 const patterns = ['[', '(', '📍', '\uFF08', '@', 'geo:', 'twd']
64 patterns.forEach(prefix => 64 patterns.forEach(prefix =>
65 md.linkify.add(prefix, coordinateValue) 65 md.linkify.add(prefix, coordinateValue),
66 ) 66 )
67 67
68 // FIXME A better way to generate blocks 68 // FIXME A better way to generate blocks
@@ -133,7 +133,7 @@ export const generateMaps = (container, { delay, renderCallback } = {}) => {
133 ...utils, 133 ...utils,
134 renderedMaps: () => 134 renderedMaps: () =>
135 Array.from( 135 Array.from(
136 container.querySelectorAll('.mapclay[data-render=fulfilled]') 136 container.querySelectorAll('.mapclay[data-render=fulfilled]'),
137 ).sort((a, b) => a.style.order > b.style.order), 137 ).sort((a, b) => a.style.order > b.style.order),
138 setContextMenu: (menuCallback) => { 138 setContextMenu: (menuCallback) => {
139 const originalCallback = container.oncontextmenu 139 const originalCallback = container.oncontextmenu
@@ -145,8 +145,8 @@ export const generateMaps = (container, { delay, renderCallback } = {}) => {
145 } 145 }
146 }, 146 },
147 focusNextMap: throttle(utils.focusNextMap, utils.focusDelay), 147 focusNextMap: throttle(utils.focusNextMap, utils.focusDelay),
148 switchToNextLayout: throttle(utils.switchToNextLayout, 300) 148 switchToNextLayout: throttle(utils.switchToNextLayout, 300),
149 } 149 },
150 } 150 }
151 Object.entries(dumbymap.utils).forEach(([util, func]) => { 151 Object.entries(dumbymap.utils).forEach(([util, func]) => {
152 dumbymap.utils[util] = func.bind(dumbymap) 152 dumbymap.utils[util] = func.bind(dumbymap)
@@ -155,7 +155,7 @@ export const generateMaps = (container, { delay, renderCallback } = {}) => {
155 // LeaderLine {{{ 155 // LeaderLine {{{
156 156
157 Array.from(container.querySelectorAll(docLinkSelector)).filter( 157 Array.from(container.querySelectorAll(docLinkSelector)).filter(
158 utils.createDocLink 158 utils.createDocLink,
159 ) 159 )
160 160
161 // Add GeoLinks 161 // Add GeoLinks
@@ -177,7 +177,7 @@ export const generateMaps = (container, { delay, renderCallback } = {}) => {
177 showcase.checkVisibility({ 177 showcase.checkVisibility({
178 contentVisibilityAuto: true, 178 contentVisibilityAuto: true,
179 opacityProperty: true, 179 opacityProperty: true,
180 visibilityProperty: true 180 visibilityProperty: true,
181 }) 181 })
182 182
183 if (focus) { 183 if (focus) {
@@ -217,12 +217,12 @@ export const generateMaps = (container, { delay, renderCallback } = {}) => {
217 // Resume rect from Semantic HTML to Showcase, with animation 217 // Resume rect from Semantic HTML to Showcase, with animation
218 animateRectTransition(target, placeholder.getBoundingClientRect(), { 218 animateRectTransition(target, placeholder.getBoundingClientRect(), {
219 duration: 300, 219 duration: 300,
220 resume: true 220 resume: true,
221 }) 221 })
222 } else if (showcase.contains(target)) { 222 } else if (showcase.contains(target)) {
223 // Check placeholder is inside Semantic HTML 223 // Check placeholder is inside Semantic HTML
224 const placeholder = htmlHolder.querySelector( 224 const placeholder = htmlHolder.querySelector(
225 `[data-placeholder="${target.id}"]` 225 `[data-placeholder="${target.id}"]`,
226 ) 226 )
227 if (!placeholder) { throw Error(`Cannot find placeholder for map "${target.id}"`) } 227 if (!placeholder) { throw Error(`Cannot find placeholder for map "${target.id}"`) }
228 228
@@ -234,7 +234,7 @@ export const generateMaps = (container, { delay, renderCallback } = {}) => {
234 234
235 // animation from Showcase to placeholder 235 // animation from Showcase to placeholder
236 animateRectTransition(target, placeholder.getBoundingClientRect(), { 236 animateRectTransition(target, placeholder.getBoundingClientRect(), {
237 duration: 300 237 duration: 300,
238 }).finished.finally(afterAnimation) 238 }).finished.finally(afterAnimation)
239 } 239 }
240 }) 240 })
@@ -277,7 +277,7 @@ export const generateMaps = (container, { delay, renderCallback } = {}) => {
277 attributes: true, 277 attributes: true,
278 attributeFilter: ['data-layout'], 278 attributeFilter: ['data-layout'],
279 attributeOldValue: true, 279 attributeOldValue: true,
280 characterDataOldValue: true 280 characterDataOldValue: true,
281 }) 281 })
282 282
283 onRemove(htmlHolder, () => layoutObserver.disconnect()) 283 onRemove(htmlHolder, () => layoutObserver.disconnect())
@@ -303,7 +303,7 @@ export const generateMaps = (container, { delay, renderCallback } = {}) => {
303 observer.observe(mapElement, { 303 observer.observe(mapElement, {
304 attributes: true, 304 attributes: true,
305 attributeFilter: ['class'], 305 attributeFilter: ['class'],
306 attributeOldValue: true 306 attributeOldValue: true,
307 }) 307 })
308 onRemove(dumbymap.htmlHolder, () => { 308 onRemove(dumbymap.htmlHolder, () => {
309 observer.disconnect() 309 observer.disconnect()
@@ -329,7 +329,7 @@ export const generateMaps = (container, { delay, renderCallback } = {}) => {
329 329
330 // Render each code block with "language-map" class 330 // Render each code block with "language-map" class
331 const elementsWithMapConfig = Array.from( 331 const elementsWithMapConfig = Array.from(
332 container.querySelectorAll(mapBlockSelector) ?? [] 332 container.querySelectorAll(mapBlockSelector) ?? [],
333 ) 333 )
334 /** 334 /**
335 * updateAttributeByStep. 335 * updateAttributeByStep.
@@ -338,7 +338,7 @@ export const generateMaps = (container, { delay, renderCallback } = {}) => {
338 */ 338 */
339 const updateAttributeByStep = ({ results, target, steps }) => { 339 const updateAttributeByStep = ({ results, target, steps }) => {
340 let passNum = results.filter( 340 let passNum = results.filter(
341 r => r.type === 'render' && r.state.match(/success|skip/) 341 r => r.type === 'render' && r.state.match(/success|skip/),
342 ).length 342 ).length
343 const total = steps.length 343 const total = steps.length
344 passNum += `/${total}` 344 passNum += `/${total}`
@@ -367,9 +367,9 @@ export const generateMaps = (container, { delay, renderCallback } = {}) => {
367 ...config, 367 ...config,
368 aliases: { 368 aliases: {
369 ...defaultAliases, 369 ...defaultAliases,
370 ...(config.aliases ?? {}) 370 ...(config.aliases ?? {}),
371 }, 371 },
372 stepCallback: updateAttributeByStep 372 stepCallback: updateAttributeByStep,
373 }) 373 })
374 const render = renderWith(configConverter) 374 const render = renderWith(configConverter)
375 let order = 0 375 let order = 0
@@ -422,7 +422,7 @@ export const generateMaps = (container, { delay, renderCallback } = {}) => {
422 } 422 }
423 }) 423 })
424 }, 424 },
425 delay ?? 1000 425 delay ?? 1000,
426 ) 426 )
427 onRemove(htmlHolder, () => { 427 onRemove(htmlHolder, () => {
428 clearTimeout(timer) 428 clearTimeout(timer)
@@ -497,7 +497,7 @@ export const generateMaps = (container, { delay, renderCallback } = {}) => {
497 } 497 }
498 document.addEventListener('click', actionOutsideMenu) 498 document.addEventListener('click', actionOutsideMenu)
499 onRemove(htmlHolder, () => 499 onRemove(htmlHolder, () =>
500 document.removeEventListener('click', actionOutsideMenu) 500 document.removeEventListener('click', actionOutsideMenu),
501 ) 501 )
502 // }}} 502 // }}}
503 return Object.seal(dumbymap) 503 return Object.seal(dumbymap)
diff --git a/src/editor.mjs b/src/editor.mjs
index 3714327..55c6267 100644
--- a/src/editor.mjs
+++ b/src/editor.mjs
@@ -3,12 +3,12 @@
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'
6import { shiftByWindow } from './utils.mjs'
7import { addAnchorByPoint } from './dumbyUtils.mjs' 6import { addAnchorByPoint } from './dumbyUtils.mjs'
7import { shiftByWindow } from './utils.mjs'
8import LeaderLine from 'leader-line' 8import LeaderLine from 'leader-line'
9 9
10// Set up Containers {{{ 10// Set up Containers {{{
11 11/** Variables about dumbymap and editor **/
12const url = new URL(window.location) 12const url = new URL(window.location)
13const context = document.querySelector('[data-mode]') 13const context = document.querySelector('[data-mode]')
14const dumbyContainer = document.querySelector('.DumbyMap') 14const dumbyContainer = document.querySelector('.DumbyMap')
@@ -27,7 +27,6 @@ const appendRefLink = ({ cm, ref, link }) => {
27 27
28 refLinks.push({ ref, link }) 28 refLinks.push({ ref, link })
29} 29}
30
31/** 30/**
32 * Watch for changes of editing mode 31 * Watch for changes of editing mode
33 * 32 *
@@ -43,11 +42,10 @@ new window.MutationObserver(() => {
43}).observe(context, { 42}).observe(context, {
44 attributes: true, 43 attributes: true,
45 attributeFilter: ['data-mode'], 44 attributeFilter: ['data-mode'],
46 attributeOldValue: true 45 attributeOldValue: true,
47}) 46})
48
49/** 47/**
50 * toggle editing mode 48 * toggleEditing: toggle editing mode
51 */ 49 */
52const toggleEditing = () => { 50const toggleEditing = () => {
53 const mode = context.getAttribute('data-mode') 51 const mode = context.getAttribute('data-mode')
@@ -55,9 +53,7 @@ const toggleEditing = () => {
55} 53}
56// }}} 54// }}}
57// Set up EasyMDE {{{ 55// Set up EasyMDE {{{
58 56/** Contents for tutorial **/
59// Content values for editor
60
61const defaultContent = 57const defaultContent =
62 `<br> 58 `<br>
63 59
@@ -105,12 +101,13 @@ If you want know more, take a look at subjects below:
105[Editor]: #This%20is%20editor! "=>.editor" 101[Editor]: #This%20is%20editor! "=>.editor"
106[subject]: placeholder` 102[subject]: placeholder`
107 103
104/** Editor from EasyMDE **/
108const editor = new EasyMDE({ 105const editor = new EasyMDE({
109 element: textArea, 106 element: textArea,
110 initialValue: defaultContent, 107 initialValue: defaultContent,
111 autosave: { 108 autosave: {
112 enabled: true, 109 enabled: true,
113 uniqueId: 'dumbymap' 110 uniqueId: 'dumbymap',
114 }, 111 },
115 indentWithTabs: false, 112 indentWithTabs: false,
116 lineNumbers: true, 113 lineNumbers: true,
@@ -123,21 +120,21 @@ const editor = new EasyMDE({
123 map: 'Ctrl-Alt-M', 120 map: 'Ctrl-Alt-M',
124 debug: 'Ctrl-Alt-D', 121 debug: 'Ctrl-Alt-D',
125 toggleUnorderedList: null, 122 toggleUnorderedList: null,
126 toggleOrderedList: null 123 toggleOrderedList: null,
127 }, 124 },
128 toolbar: [ 125 toolbar: [
129 { 126 {
130 name: 'roll', 127 name: 'roll',
131 title: 'Roll a Dice', 128 title: 'Roll a Dice',
132 text: '\u{2684}', 129 text: '\u{2684}',
133 action: () => addMapRandomlyByPreset() 130 action: () => addMapRandomlyByPreset(),
134 }, 131 },
135 { 132 {
136 name: 'export', 133 name: 'export',
137 title: 'Export current page', 134 title: 'Export current page',
138 text: '\u{1F4BE}', 135 text: '\u{1F4BE}',
139 action: () => { 136 action: () => {
140 } 137 },
141 }, 138 },
142 { 139 {
143 name: 'hash', 140 name: 'hash',
@@ -150,59 +147,59 @@ const editor = new EasyMDE({
150 window.location.search = '' 147 window.location.search = ''
151 navigator.clipboard.writeText(window.location.href) 148 navigator.clipboard.writeText(window.location.href)
152 window.alert('URL updated in address bar, you can save current page as bookmark') 149 window.alert('URL updated in address bar, you can save current page as bookmark')
153 } 150 },
154 }, 151 },
155 '|', 152 '|',
156 { 153 {
157 name: 'undo', 154 name: 'undo',
158 title: 'Undo last editing', 155 title: 'Undo last editing',
159 text: '\u27F2', 156 text: '\u27F2',
160 action: EasyMDE.undo 157 action: EasyMDE.undo,
161 }, 158 },
162 { 159 {
163 name: 'redo', 160 name: 'redo',
164 text: '\u27F3', 161 text: '\u27F3',
165 title: 'Redo editing', 162 title: 'Redo editing',
166 action: EasyMDE.redo 163 action: EasyMDE.redo,
167 }, 164 },
168 '|', 165 '|',
169 { 166 {
170 name: 'heading-1', 167 name: 'heading-1',
171 text: 'H1', 168 text: 'H1',
172 title: 'Big Heading', 169 title: 'Big Heading',
173 action: EasyMDE['heading-1'] 170 action: EasyMDE['heading-1'],
174 }, 171 },
175 { 172 {
176 name: 'heading-2', 173 name: 'heading-2',
177 text: 'H2', 174 text: 'H2',
178 title: 'Medium Heading', 175 title: 'Medium Heading',
179 action: EasyMDE['heading-2'] 176 action: EasyMDE['heading-2'],
180 }, 177 },
181 '|', 178 '|',
182 { 179 {
183 name: 'link', 180 name: 'link',
184 text: '\u{1F517}', 181 text: '\u{1F517}',
185 title: 'Create Link', 182 title: 'Create Link',
186 action: EasyMDE.drawLink 183 action: EasyMDE.drawLink,
187 }, 184 },
188 { 185 {
189 name: 'image', 186 name: 'image',
190 text: '\u{1F5BC}', 187 text: '\u{1F5BC}',
191 title: 'Create Image', 188 title: 'Create Image',
192 action: EasyMDE.drawImage 189 action: EasyMDE.drawImage,
193 }, 190 },
194 '|', 191 '|',
195 { 192 {
196 name: 'Bold', 193 name: 'Bold',
197 text: '\u{1D401}', 194 text: '\u{1D401}',
198 title: 'Bold', 195 title: 'Bold',
199 action: EasyMDE.toggleBold 196 action: EasyMDE.toggleBold,
200 }, 197 },
201 { 198 {
202 name: 'Italic', 199 name: 'Italic',
203 text: '\u{1D43C}', 200 text: '\u{1D43C}',
204 title: 'Italic', 201 title: 'Italic',
205 action: EasyMDE.toggleItalic 202 action: EasyMDE.toggleItalic,
206 }, 203 },
207 '|', 204 '|',
208 { 205 {
@@ -213,13 +210,14 @@ const editor = new EasyMDE({
213 editor.value(defaultContent) 210 editor.value(defaultContent)
214 refLinks = getRefLinks() 211 refLinks = getRefLinks()
215 updateDumbyMap() 212 updateDumbyMap()
216 } 213 },
217 } 214 },
218 ] 215 ],
219}) 216})
220 217/** CodeMirror Instance **/
221const cm = editor.codemirror 218const cm = editor.codemirror
222 219
220/** Ref Links **/
223const getRefLinks = () => editor.value() 221const getRefLinks = () => editor.value()
224 .split('\n') 222 .split('\n')
225 .map(line => { 223 .map(line => {
@@ -244,7 +242,6 @@ const getStateFromHash = hash => {
244 return {} 242 return {}
245 } 243 }
246} 244}
247
248/** 245/**
249 * get editor content from hash string 246 * get editor content from hash string
250 * 247 *
@@ -254,7 +251,9 @@ const getContentFromHash = hash => {
254 const state = getStateFromHash(hash) 251 const state = getStateFromHash(hash)
255 return state.content 252 return state.content
256} 253}
254/** Hash and Query Parameters in URL **/
257const contentFromHash = getContentFromHash(window.location.hash) 255const contentFromHash = getContentFromHash(window.location.hash)
256window.location.hash = ''
258 257
259if (url.searchParams.get('content') === 'tutorial') { 258if (url.searchParams.get('content') === 'tutorial') {
260 editor.value(defaultContent) 259 editor.value(defaultContent)
@@ -263,12 +262,10 @@ if (url.searchParams.get('content') === 'tutorial') {
263 editor.cleanup() 262 editor.cleanup()
264 editor.value(contentFromHash) 263 editor.value(contentFromHash)
265} 264}
266
267window.location.hash = ''
268
269// }}} 265// }}}
270// Set up logic about editor content {{{ 266// Set up logic about editor content {{{
271 267
268/** Sync scroll from HTML to CodeMirror **/
272const htmlOnScroll = (ele) => () => { 269const htmlOnScroll = (ele) => () => {
273 if (textArea.dataset.scrollLine) return 270 if (textArea.dataset.scrollLine) return
274 271
@@ -288,12 +285,11 @@ const htmlOnScroll = (ele) => () => {
288 } 285 }
289} 286}
290 287
291// Sync CodeMirror LineNumber with HTML Contents
292new window.MutationObserver(() => { 288new window.MutationObserver(() => {
293 clearTimeout(dumbyContainer.timer) 289 clearTimeout(dumbyContainer.timer)
294 dumbyContainer.timer = setTimeout( 290 dumbyContainer.timer = setTimeout(
295 () => delete dumbyContainer.dataset.scrollLine, 291 () => delete dumbyContainer.dataset.scrollLine,
296 50 292 50,
297 ) 293 )
298 294
299 const line = dumbyContainer.dataset.scrollLine 295 const line = dumbyContainer.dataset.scrollLine
@@ -306,7 +302,7 @@ new window.MutationObserver(() => {
306 } 302 }
307}).observe(dumbyContainer, { 303}).observe(dumbyContainer, {
308 attributes: true, 304 attributes: true,
309 attributeFilter: ['data-scroll-line'] 305 attributeFilter: ['data-scroll-line'],
310}) 306})
311 307
312const setScrollLine = () => { 308const setScrollLine = () => {
@@ -318,12 +314,12 @@ const setScrollLine = () => {
318} 314}
319cm.on('scroll', setScrollLine) 315cm.on('scroll', setScrollLine)
320 316
321// Sync HTML Contents with CodeMirror LineNumber 317/** Sync scroll from CodeMirror to HTML **/
322new window.MutationObserver(() => { 318new window.MutationObserver(() => {
323 clearTimeout(textArea.timer) 319 clearTimeout(textArea.timer)
324 textArea.timer = setTimeout( 320 textArea.timer = setTimeout(
325 () => delete textArea.dataset.scrollLine, 321 () => delete textArea.dataset.scrollLine,
326 1000 322 1000,
327 ) 323 )
328 324
329 const line = textArea.dataset.scrollLine 325 const line = textArea.dataset.scrollLine
@@ -345,11 +341,9 @@ new window.MutationObserver(() => {
345 dumbymap.htmlHolder.scrollBy(0, top - coords.top + 30) 341 dumbymap.htmlHolder.scrollBy(0, top - coords.top + 30)
346}).observe(textArea, { 342}).observe(textArea, {
347 attributes: true, 343 attributes: true,
348 attributeFilter: ['data-scroll-line'] 344 attributeFilter: ['data-scroll-line'],
349}) 345})
350 346
351markdown2HTML(dumbyContainer, editor.value())
352
353/** 347/**
354 * addClassToCodeLines. Quick hack to style lines inside code block 348 * addClassToCodeLines. Quick hack to style lines inside code block
355 */ 349 */
@@ -444,7 +438,7 @@ const menuForEditor = (event, menu) => {
444 if (context.dataset.mode !== 'editing') { 438 if (context.dataset.mode !== 'editing') {
445 const switchToEditingMode = new menuItem.Item({ 439 const switchToEditingMode = new menuItem.Item({
446 innerHTML: '<strong>EDIT</strong>', 440 innerHTML: '<strong>EDIT</strong>',
447 onclick: () => (context.dataset.mode = 'editing') 441 onclick: () => (context.dataset.mode = 'editing'),
448 }) 442 })
449 menu.appendChild(switchToEditingMode) 443 menu.appendChild(switchToEditingMode)
450 } 444 }
@@ -456,7 +450,7 @@ const menuForEditor = (event, menu) => {
456 onclick: (event) => { 450 onclick: (event) => {
457 const { ref, link } = addAnchorByPoint({ point: event, map, validateAnchorName }) 451 const { ref, link } = addAnchorByPoint({ point: event, map, validateAnchorName })
458 appendRefLink({ cm, ref, link }) 452 appendRefLink({ cm, ref, link })
459 } 453 },
460 }) 454 })
461 menu.insertBefore(item, menu.firstChild) 455 menu.insertBefore(item, menu.firstChild)
462 } 456 }
@@ -468,7 +462,7 @@ const menuForEditor = (event, menu) => {
468const updateDumbyMap = () => { 462const updateDumbyMap = () => {
469 markdown2HTML(dumbyContainer, editor.value()) 463 markdown2HTML(dumbyContainer, editor.value())
470 // debounceForMap(dumbyContainer, afterMapRendered) 464 // debounceForMap(dumbyContainer, afterMapRendered)
471 dumbymap = generateMaps(dumbyContainer, { renderCallback }) 465 dumbymap = generateMaps(dumbyContainer, {})
472 // Set onscroll callback 466 // Set onscroll callback
473 const htmlHolder = dumbymap.htmlHolder 467 const htmlHolder = dumbymap.htmlHolder
474 htmlHolder.onscroll = htmlOnScroll(htmlHolder) 468 htmlHolder.onscroll = htmlOnScroll(htmlHolder)
@@ -521,7 +515,7 @@ new window.MutationObserver(() => {
521 } 515 }
522}).observe(menu, { 516}).observe(menu, {
523 attributes: true, 517 attributes: true,
524 attributeFilter: ['style'] 518 attributeFilter: ['style'],
525}) 519})
526document.body.append(menu) 520document.body.append(menu)
527 521
@@ -613,7 +607,7 @@ const getSuggestionsForOptions = (optionTyped, validOptions) => {
613 let suggestOptions = [] 607 let suggestOptions = []
614 608
615 const matchedOptions = validOptions.filter(o => 609 const matchedOptions = validOptions.filter(o =>
616 o.valueOf().toLowerCase().includes(optionTyped.toLowerCase()) 610 o.valueOf().toLowerCase().includes(optionTyped.toLowerCase()),
617 ) 611 )
618 612
619 if (matchedOptions.length > 0) { 613 if (matchedOptions.length > 0) {
@@ -627,8 +621,8 @@ const getSuggestionsForOptions = (optionTyped, validOptions) => {
627 new menuItem.Suggestion({ 621 new menuItem.Suggestion({
628 text: `<span>${o.valueOf()}</span><span class='info' title="${o.desc ?? ''}">ⓘ</span>`, 622 text: `<span>${o.valueOf()}</span><span class='info' title="${o.desc ?? ''}">ⓘ</span>`,
629 replace: `${o.valueOf()}: `, 623 replace: `${o.valueOf()}: `,
630 cm 624 cm,
631 }) 625 }),
632 ) 626 )
633} 627}
634// }}} 628// }}}
@@ -647,7 +641,7 @@ const getSuggestionFromMapOption = option => {
647 return new menuItem.Suggestion({ 641 return new menuItem.Suggestion({
648 text, 642 text,
649 replace: `${option.valueOf()}: ${option.example ?? ''}`, 643 replace: `${option.valueOf()}: ${option.example ?? ''}`,
650 cm 644 cm,
651 }) 645 })
652} 646}
653// }}} 647// }}}
@@ -663,7 +657,7 @@ const getSuggestionsFromAliases = option =>
663 return new menuItem.Suggestion({ 657 return new menuItem.Suggestion({
664 text: `<span>${alias}</span><span class="truncate" style="color: gray">${valueString}</span>`, 658 text: `<span>${alias}</span><span class="truncate" style="color: gray">${valueString}</span>`,
665 replace: `${option.valueOf()}: ${valueString}`, 659 replace: `${option.valueOf()}: ${valueString}`,
666 cm 660 cm,
667 }) 661 })
668 }) ?? [] 662 }) ?? []
669// }}} 663// }}}
@@ -694,7 +688,7 @@ const getSuggestions = anchor => {
694 688
695 // Clear marks on text 689 // Clear marks on text
696 cm.findMarks({ ...anchor, ch: 0 }, { ...anchor, ch: text.length }).forEach( 690 cm.findMarks({ ...anchor, ch: 0 }, { ...anchor, ch: text.length }).forEach(
697 m => m.clear() 691 m => m.clear(),
698 ) 692 )
699 693
700 // Mark user input invalid by case 694 // Mark user input invalid by case
@@ -704,7 +698,7 @@ const getSuggestions = anchor => {
704 .markText( 698 .markText(
705 { ...anchor, ch: 0 }, 699 { ...anchor, ch: 0 },
706 { ...anchor, ch: text.length }, 700 { ...anchor, ch: text.length },
707 { className: 'invalid-input' } 701 { className: 'invalid-input' },
708 ) 702 )
709 703
710 // Check if "use: <renderer>" is set 704 // Check if "use: <renderer>" is set
@@ -732,7 +726,7 @@ const getSuggestions = anchor => {
732 .catch(_ => { 726 .catch(_ => {
733 markInputIsInvalid(lineWithRenderer) 727 markInputIsInvalid(lineWithRenderer)
734 console.warn( 728 console.warn(
735 `Fail to get valid options from Renderer typed: ${renderer}` 729 `Fail to get valid options from Renderer typed: ${renderer}`,
736 ) 730 )
737 }) 731 })
738 return [] 732 return []
@@ -765,7 +759,7 @@ const getSuggestions = anchor => {
765 if (!valueTyped) { 759 if (!valueTyped) {
766 return [ 760 return [
767 getSuggestionFromMapOption(matchedOption), 761 getSuggestionFromMapOption(matchedOption),
768 ...getSuggestionsFromAliases(matchedOption) 762 ...getSuggestionsFromAliases(matchedOption),
769 ].filter(s => s instanceof menuItem.Suggestion) 763 ].filter(s => s instanceof menuItem.Suggestion)
770 } 764 }
771 if (valueTyped && !isValidValue) { 765 if (valueTyped && !isValidValue) {
@@ -787,19 +781,19 @@ const getSuggestions = anchor => {
787 new menuItem.Suggestion({ 781 new menuItem.Suggestion({
788 text: `<span>use: ${renderer}</span><span class='info' title="${info.desc}">ⓘ</span>`, 782 text: `<span>use: ${renderer}</span><span class='info' title="${info.desc}">ⓘ</span>`,
789 replace: `use: ${renderer}`, 783 replace: `use: ${renderer}`,
790 cm 784 cm,
791 }) 785 }),
792 ) 786 )
793 return rendererSuggestions.length === 0 787 return rendererSuggestions.length === 0
794 ? [] 788 ? []
795 : [ 789 : [
796 ...rendererSuggestions, 790 ...rendererSuggestions,
797 new menuItem.Item({ 791 new menuItem.Item({
798 innerHTML: '<a href="https://github.com/outdoorsafetylab/mapclay#renderer" class="external" style="display: block;">More...</a>', 792 innerHTML: '<a href="https://github.com/outdoorsafetylab/mapclay#renderer" class="external" style="display: block;">More...</a>',
799 className: ['suggestion'], 793 className: ['suggestion'],
800 onclick: () => window.open('https://github.com/outdoorsafetylab/mapclay#renderer', '_blank') 794 onclick: () => window.open('https://github.com/outdoorsafetylab/mapclay#renderer', '_blank'),
801 }) 795 }),
802 ] 796 ]
803 } 797 }
804 return [] 798 return []
805} 799}
@@ -943,7 +937,7 @@ new window.MutationObserver(mutaions => {
943}).observe(dumbyContainer, { 937}).observe(dumbyContainer, {
944 attributes: true, 938 attributes: true,
945 attributeFilter: ['data-layout'], 939 attributeFilter: ['data-layout'],
946 attributeOldValue: true 940 attributeOldValue: true,
947}) 941})
948// }}} 942// }}}
949 943
@@ -954,7 +948,7 @@ const addMapRandomlyByPreset = () => {
954 const yamlText = [ 948 const yamlText = [
955 'apply: dist/default.yml', 949 'apply: dist/default.yml',
956 'width: 85%', 950 'width: 85%',
957 'height: 200px' 951 'height: 200px',
958 ] 952 ]
959 const order = [ 953 const order = [
960 'id', 954 'id',
@@ -964,12 +958,12 @@ const addMapRandomlyByPreset = () => {
964 'height', 958 'height',
965 'center', 959 'center',
966 'XYZ', 960 'XYZ',
967 'zoom' 961 'zoom',
968 ] 962 ]
969 const aliasesEntries = Object.entries(aliasesForMapOptions) 963 const aliasesEntries = Object.entries(aliasesForMapOptions)
970 .filter(([key, _]) => 964 .filter(([key, _]) =>
971 order.includes(key) && 965 order.includes(key) &&
972 !yamlText.find(text => text.startsWith(key)) 966 !yamlText.find(text => text.startsWith(key)),
973 ) 967 )
974 if (aliasesEntries.length === 0) return 968 if (aliasesEntries.length === 0) return
975 969
@@ -997,12 +991,12 @@ const addMapRandomlyByPreset = () => {
997 }) 991 })
998 992
999 yamlText.sort((a, b) => 993 yamlText.sort((a, b) =>
1000 order.indexOf(a.split(':')[0]) > order.indexOf(b.split(':')[0]) 994 order.indexOf(a.split(':')[0]) > order.indexOf(b.split(':')[0]),
1001 ) 995 )
1002 const anchor = cm.getCursor() 996 const anchor = cm.getCursor()
1003 cm.replaceRange( 997 cm.replaceRange(
1004 '\n```map\n' + yamlText.join('\n') + '\n```\n', 998 '\n```map\n' + yamlText.join('\n') + '\n```\n',
1005 anchor 999 anchor,
1006 ) 1000 )
1007} 1001}
1008 1002
@@ -1068,6 +1062,7 @@ document.addEventListener('selectionchange', () => {
1068 } 1062 }
1069}) 1063})
1070 1064
1065/** Drag/Drop on map for new reference style link */
1071dumbyContainer.onmousedown = (e) => { 1066dumbyContainer.onmousedown = (e) => {
1072 // Check should start drag event for GeoLink 1067 // Check should start drag event for GeoLink
1073 const selection = document.getSelection() 1068 const selection = document.getSelection()
@@ -1087,14 +1082,13 @@ dumbyContainer.onmousedown = (e) => {
1087 lineEnd.style.cssText = `position: absolute; left: ${e.clientX}px; top: ${e.clientY}px;` 1082 lineEnd.style.cssText = `position: absolute; left: ${e.clientX}px; top: ${e.clientY}px;`
1088 document.body.appendChild(lineEnd) 1083 document.body.appendChild(lineEnd)
1089 1084
1090 menu.style.display = 'block'
1091 const line = new LeaderLine({ 1085 const line = new LeaderLine({
1092 start: geoLink, 1086 start: geoLink,
1093 end: lineEnd, 1087 end: lineEnd,
1094 path: 'magnet' 1088 path: 'magnet',
1095 }) 1089 })
1096 1090
1097 function onMouseMove(event) { 1091 function onMouseMove (event) {
1098 lineEnd.style.left = event.clientX + 'px' 1092 lineEnd.style.left = event.clientX + 'px'
1099 lineEnd.style.top = event.clientY + 'px' 1093 lineEnd.style.top = event.clientY + 'px'
1100 line.position() 1094 line.position()
@@ -1102,7 +1096,7 @@ dumbyContainer.onmousedown = (e) => {
1102 } 1096 }
1103 1097
1104 dumbyContainer.onmousemove = onMouseMove 1098 dumbyContainer.onmousemove = onMouseMove
1105 dumbyContainer.onmouseup = function(e) { 1099 dumbyContainer.onmouseup = function (e) {
1106 dumbyContainer.onmousemove = null 1100 dumbyContainer.onmousemove = null
1107 dumbyContainer.onmouseup = null 1101 dumbyContainer.onmouseup = null
1108 line?.remove() 1102 line?.remove()
@@ -1122,16 +1116,14 @@ dumbyContainer.onmousedown = (e) => {
1122 return 1116 return
1123 } 1117 }
1124 1118
1125 const {ref, link} = refLink 1119 const { ref, link } = refLink
1126 appendRefLink({ cm, ref, link }) 1120 appendRefLink({ cm, ref, link })
1127 if (selection === ref) { 1121 if (selection === ref) {
1128 cm.replaceSelection(`[${selection}]`) 1122 cm.replaceSelection(`[${selection}]`)
1129 } else { 1123 } else {
1130 cm.replaceSelection(`[${selection}][${ref}]`) 1124 cm.replaceSelection(`[${selection}][${ref}]`)
1131 } 1125 }
1132 }; 1126 }
1133} 1127}
1134 1128
1135dumbyContainer.ondragstart = () => false 1129dumbyContainer.ondragstart = () => false
1136
1137// vim: sw=2 ts=2 foldmethod=marker foldmarker={{{,}}}
diff --git a/src/utils.mjs b/src/utils.mjs
index dba023a..d408b3d 100644
--- a/src/utils.mjs
+++ b/src/utils.mjs
@@ -39,7 +39,7 @@ export const animateRectTransition = (element, rect, options = {}) => {
39 width: w2, 39 width: w2,
40 height: h2, 40 height: h2,
41 left: x2, 41 left: x2,
42 top: y2 42 top: y2,
43 } = element.getBoundingClientRect() 43 } = element.getBoundingClientRect()
44 44
45 const rw = (w1 ?? w2) / w2 45 const rw = (w1 ?? w2) / w2
@@ -55,13 +55,13 @@ export const animateRectTransition = (element, rect, options = {}) => {
55 const transform2 = `translate(${dx}px, ${dy}px) scale(${rw}, ${rh})` 55 const transform2 = `translate(${dx}px, ${dy}px) scale(${rw}, ${rh})`
56 const keyframes = [ 56 const keyframes = [
57 { transform: transform1, opacity: 1 }, 57 { transform: transform1, opacity: 1 },
58 { transform: transform2, opacity: 0.3 } 58 { transform: transform2, opacity: 0.3 },
59 ] 59 ]
60 if (options.resume === true) keyframes.reverse() 60 if (options.resume === true) keyframes.reverse()
61 61
62 return element.animate(keyframes, { 62 return element.animate(keyframes, {
63 duration: options.duration ?? 500, 63 duration: options.duration ?? 500,
64 easing: 'ease-in-out' 64 easing: 'ease-in-out',
65 }) 65 })
66} 66}
67 67
@@ -81,7 +81,7 @@ export function throttle (func, delay) {
81 81
82 timerFlag = setTimeout( 82 timerFlag = setTimeout(
83 () => (timerFlag = null), 83 () => (timerFlag = null),
84 typeof delay === 'function' ? delay.call(context) : delay 84 typeof delay === 'function' ? delay.call(context) : delay,
85 ) 85 )
86 86
87 return func.call(context, ...args) 87 return func.call(context, ...args)