diff options
author | Hsieh Chin Fan <pham@topo.tw> | 2024-10-17 10:38:15 +0800 |
---|---|---|
committer | Hsieh Chin Fan <pham@topo.tw> | 2024-10-17 10:38:15 +0800 |
commit | d91501af0d3860da1022e960199115c33b4a63a6 (patch) | |
tree | 7a9c02421b33dcb36460acceed381d4a116c148d | |
parent | 9c90bd1cbdc5de5b50def0eb4cb3e65e80194af3 (diff) | |
parent | 9a66411258781a57d5c953b7113403fdf0d218cf (diff) |
Merge branch "addon"
-rw-r--r-- | .gitignore | 3 | ||||
-rw-r--r-- | addon/index.mjs | 42 | ||||
-rw-r--r-- | addon/manifest.json | 14 | ||||
-rw-r--r-- | package.json | 3 | ||||
-rw-r--r-- | scripts/rollup.config.js | 56 | ||||
-rw-r--r-- | src/MenuItem.mjs | 12 | ||||
-rw-r--r-- | src/css/dumbymap.css | 4 | ||||
-rw-r--r-- | src/dumbymap.mjs | 153 | ||||
-rw-r--r-- | src/utils.mjs | 32 |
9 files changed, 243 insertions, 76 deletions
@@ -1,5 +1,8 @@ | |||
1 | dist/ | 1 | dist/ |
2 | docs/ | 2 | docs/ |
3 | addon/**js | ||
4 | addon/**css | ||
5 | !addon/index** | ||
3 | doc-coverage/ | 6 | doc-coverage/ |
4 | node_modules/ | 7 | node_modules/ |
5 | package-lock.json | 8 | package-lock.json |
diff --git a/addon/index.mjs b/addon/index.mjs new file mode 100644 index 0000000..b5d71ba --- /dev/null +++ b/addon/index.mjs | |||
@@ -0,0 +1,42 @@ | |||
1 | const url = new URL(window.location) | ||
2 | if (url.host === 'www.ptt.cc') { | ||
3 | const content = document.querySelector('#main-content') | ||
4 | Array.from(content.childNodes) | ||
5 | .filter(n => !(n instanceof window.HTMLElement)) | ||
6 | .forEach(text => { | ||
7 | const span = document.createElement('span') | ||
8 | span.innerText = text.textContent | ||
9 | text.replaceWith(span) | ||
10 | }) | ||
11 | } | ||
12 | |||
13 | const blockSelectors = { | ||
14 | 'developer.mozilla': '.section-content', | ||
15 | 'hackmd.io': '#doc > *', | ||
16 | 'www.ptt.cc': '#main-content > span', | ||
17 | } | ||
18 | const blockSelector = blockSelectors[url.host] | ||
19 | |||
20 | const addBlocks = blockSelector | ||
21 | ? root => Array.from(root.querySelectorAll(blockSelector)) | ||
22 | : undefined | ||
23 | |||
24 | const simpleRender = window.mapclay.renderWith(config => ({ | ||
25 | use: 'Leaflet', | ||
26 | width: '100%', | ||
27 | height: '200px', | ||
28 | XYZ: 'https://tile.openstreetmap.jp/styles/osm-bright/512/{z}/{x}/{y}.png', | ||
29 | ...config, | ||
30 | aliases: { | ||
31 | use: window.mapclay.renderers, | ||
32 | ...(config.aliases ?? {}), | ||
33 | }, | ||
34 | })) | ||
35 | |||
36 | window.generateMaps(document.querySelector('main') ?? document.body, { | ||
37 | crs: url.searchParams.get('crs') ?? 'EPSG:4326', | ||
38 | addBlocks, | ||
39 | initialLayout: '', | ||
40 | render: simpleRender, | ||
41 | autoMap: true, | ||
42 | }) | ||
diff --git a/addon/manifest.json b/addon/manifest.json index 27433b3..2d7b1ed 100644 --- a/addon/manifest.json +++ b/addon/manifest.json | |||
@@ -11,8 +11,15 @@ | |||
11 | 11 | ||
12 | "content_scripts": [ | 12 | "content_scripts": [ |
13 | { | 13 | { |
14 | "matches": ["*://*.mozilla.org/*"], | 14 | "matches": [ |
15 | "js": ["index.mjs"], | 15 | "*://developer.mozilla.org/*", |
16 | "*://hackmd.io/*", | ||
17 | "*://*.ptt.cc/*" | ||
18 | ], | ||
19 | "js": [ | ||
20 | "dumbymap.mjs", | ||
21 | "index.mjs" | ||
22 | ], | ||
16 | "css": [ | 23 | "css": [ |
17 | "css/dumbymap.css" | 24 | "css/dumbymap.css" |
18 | ] | 25 | ] |
@@ -22,6 +29,7 @@ | |||
22 | "permissions": [ | 29 | "permissions": [ |
23 | "activeTab", | 30 | "activeTab", |
24 | "tabs", | 31 | "tabs", |
25 | "scripting" | 32 | "scripting", |
33 | "https://epsg.io/*" | ||
26 | ] | 34 | ] |
27 | } | 35 | } |
diff --git a/package.json b/package.json index d6f8b40..392ca85 100644 --- a/package.json +++ b/package.json | |||
@@ -30,7 +30,8 @@ | |||
30 | "style": "scripts/stylelint.sh", | 30 | "style": "scripts/stylelint.sh", |
31 | "docs": "jsdoc -c scripts/jsdoc.conf src/; xdg-open http://localhost:8080/docs/", | 31 | "docs": "jsdoc -c scripts/jsdoc.conf src/; xdg-open http://localhost:8080/docs/", |
32 | "prepack": "npm run lint && npm run style && npm run build", | 32 | "prepack": "npm run lint && npm run style && npm run build", |
33 | "postpack": "rm -rf dist/css dist/renderers; npm run build-resources; ln -sf `pwd`/src/css dist; cp node_modules/easymde/dist/easymde.min.css src/css; ln -sf `pwd`/node_modules/mapclay/dist/renderers dist" | 33 | "postpack": "rm -rf dist/css dist/renderers; npm run build-resources; ln -sf `pwd`/src/css dist; cp node_modules/easymde/dist/easymde.min.css src/css; ln -sf `pwd`/node_modules/mapclay/dist/renderers dist", |
34 | "addon": "cp src/css/dumbymap.css addon/css; ADDON=true rollup -c scripts/rollup.config.js" | ||
34 | }, | 35 | }, |
35 | "devDependencies": { | 36 | "devDependencies": { |
36 | "@rollup/plugin-alias": "^5.1.1", | 37 | "@rollup/plugin-alias": "^5.1.1", |
diff --git a/scripts/rollup.config.js b/scripts/rollup.config.js index d4af05f..434bd85 100644 --- a/scripts/rollup.config.js +++ b/scripts/rollup.config.js | |||
@@ -5,7 +5,25 @@ import { existsSync } from 'fs' | |||
5 | import { join } from 'path' | 5 | import { join } from 'path' |
6 | import { bundleStats } from 'rollup-plugin-bundle-stats' | 6 | import { bundleStats } from 'rollup-plugin-bundle-stats' |
7 | 7 | ||
8 | const production = !process.env.ROLLUP_WATCH | 8 | const prod = process.env.PRODUCTION |
9 | const addon = process.env.ADDON | ||
10 | |||
11 | function resolve (file, origin) { | ||
12 | // Your way to resolve local include path | ||
13 | } | ||
14 | |||
15 | function pathResolve (options) { | ||
16 | return { | ||
17 | resolveId: function (file, origin) { | ||
18 | // Your local include path must either starts with `./` or `../` | ||
19 | if (file.startsWith('./') || file.startsWith('../')) { | ||
20 | // Return an absolute include path | ||
21 | return resolve(file, origin) | ||
22 | } | ||
23 | return null // Continue to the next plugins! | ||
24 | }, | ||
25 | } | ||
26 | } | ||
9 | 27 | ||
10 | const general = { | 28 | const general = { |
11 | output: [ | 29 | output: [ |
@@ -13,7 +31,6 @@ const general = { | |||
13 | dir: './dist', | 31 | dir: './dist', |
14 | format: 'esm', | 32 | format: 'esm', |
15 | entryFileNames: '[name].mjs', | 33 | entryFileNames: '[name].mjs', |
16 | sourcemap: 'true', | ||
17 | }, | 34 | }, |
18 | ], | 35 | ], |
19 | watch: { | 36 | watch: { |
@@ -42,12 +59,13 @@ const general = { | |||
42 | return null | 59 | return null |
43 | }, | 60 | }, |
44 | }, | 61 | }, |
62 | pathResolve(), | ||
45 | node(), | 63 | node(), |
46 | commonjs(), | 64 | commonjs(), |
47 | production && terser({ | 65 | prod && terser({ |
48 | keep_fnames: true, | 66 | keep_fnames: true, |
49 | }), | 67 | }), |
50 | production && bundleStats(), | 68 | prod && bundleStats(), |
51 | ], | 69 | ], |
52 | } | 70 | } |
53 | 71 | ||
@@ -60,4 +78,32 @@ export default [ | |||
60 | }, | 78 | }, |
61 | ] | 79 | ] |
62 | .map(config => ({ ...general, ...config })) | 80 | .map(config => ({ ...general, ...config })) |
63 | .filter((config) => production || config.input.match(/editor/)) | 81 | .filter(config => { |
82 | if (addon) return config.input.match(/dumbymap/) | ||
83 | if (!prod) return config.input.match(/editor/) | ||
84 | return true | ||
85 | }) | ||
86 | .map(config => { | ||
87 | if (!addon) return config | ||
88 | |||
89 | config.output.forEach(o => { o.dir = './addon' }) | ||
90 | config.plugins.push({ | ||
91 | name: 'remove-exports', | ||
92 | transform (code, id) { | ||
93 | if (id.includes(config.input)) { | ||
94 | // remove export keyword for addon | ||
95 | const transformedCode = code.replace(/\n(\s*)export\s*/g, '$1') | ||
96 | return { | ||
97 | code: [ | ||
98 | transformedCode, | ||
99 | 'window.generateMaps = generateMaps', | ||
100 | 'window.mapclay = mapclay', | ||
101 | ].join('\n'), | ||
102 | } | ||
103 | } | ||
104 | return null | ||
105 | }, | ||
106 | }) | ||
107 | |||
108 | return config | ||
109 | }) | ||
diff --git a/src/MenuItem.mjs b/src/MenuItem.mjs index 7ce75f6..74b01d5 100644 --- a/src/MenuItem.mjs +++ b/src/MenuItem.mjs | |||
@@ -40,7 +40,9 @@ export class Item extends window.HTMLDivElement { | |||
40 | } | 40 | } |
41 | } | 41 | } |
42 | } | 42 | } |
43 | window.customElements.define('dumby-menu-item', Item, { extends: 'div' }) | 43 | if (!window.customElements.get('dumby-menu-item')) { |
44 | window.customElements.define('dumby-menu-item', Item, { extends: 'div' }) | ||
45 | } | ||
44 | 46 | ||
45 | /** | 47 | /** |
46 | * Basic Element for menu item that generates a submenu on hover | 48 | * Basic Element for menu item that generates a submenu on hover |
@@ -80,7 +82,9 @@ export class Folder extends window.HTMLDivElement { | |||
80 | } | 82 | } |
81 | } | 83 | } |
82 | } | 84 | } |
83 | window.customElements.define('menu-folder', Folder, { extends: 'div' }) | 85 | if (!window.customElements.get('menu-folder')) { |
86 | window.customElements.define('menu-folder', Folder, { extends: 'div' }) | ||
87 | } | ||
84 | 88 | ||
85 | /** | 89 | /** |
86 | * Creates a menu item for picking a map | 90 | * Creates a menu item for picking a map |
@@ -232,7 +236,9 @@ export class Suggestion extends Item { | |||
232 | } | 236 | } |
233 | } | 237 | } |
234 | } | 238 | } |
235 | window.customElements.define('menu-item-suggestion', Suggestion, { extends: 'div' }) | 239 | if (!window.customElements.get('menu-item-suggestion')) { |
240 | window.customElements.define('menu-item-suggestion', Suggestion, { extends: 'div' }) | ||
241 | } | ||
236 | 242 | ||
237 | /** | 243 | /** |
238 | * renderResults. return a menu item for reporting render results | 244 | * renderResults. return a menu item for reporting render results |
diff --git a/src/css/dumbymap.css b/src/css/dumbymap.css index dc1f59b..c0bb1a9 100644 --- a/src/css/dumbymap.css +++ b/src/css/dumbymap.css | |||
@@ -161,12 +161,12 @@ root { | |||
161 | &:hover, &.drag { | 161 | &:hover, &.drag { |
162 | background-image: none; | 162 | background-image: none; |
163 | 163 | ||
164 | font-weight: bolder; | 164 | /* font-weight: bolder; */ |
165 | text-decoration: none; | 165 | text-decoration: none; |
166 | } | 166 | } |
167 | } | 167 | } |
168 | 168 | ||
169 | .dumby-block { | 169 | .Dumby:not([data-layout=""]) .dumby-block { |
170 | padding: 1rem 1rem 1rem 2rem; | 170 | padding: 1rem 1rem 1rem 2rem; |
171 | 171 | ||
172 | position: relative; | 172 | position: relative; |
diff --git a/src/dumbymap.mjs b/src/dumbymap.mjs index 8e26ee6..706e874 100644 --- a/src/dumbymap.mjs +++ b/src/dumbymap.mjs | |||
@@ -3,7 +3,7 @@ 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 MarkdownItInjectLinenumbers from 'markdown-it-inject-linenumbers' | 5 | import MarkdownItInjectLinenumbers from 'markdown-it-inject-linenumbers' |
6 | import { renderWith, defaultAliases, parseConfigsFromYaml } from 'mapclay' | 6 | import * as mapclay from 'mapclay' |
7 | import { replaceTextNodes, onRemove, animateRectTransition, throttle, shiftByWindow } from './utils' | 7 | import { replaceTextNodes, onRemove, animateRectTransition, throttle, shiftByWindow } from './utils' |
8 | import { Layout, SideBySide, Overlay } from './Layout' | 8 | import { Layout, SideBySide, Overlay } from './Layout' |
9 | import * as utils from './dumbyUtils' | 9 | import * as utils from './dumbyUtils' |
@@ -14,7 +14,7 @@ import { register, fromEPSGCode } from 'ol/proj/proj4' | |||
14 | import LeaderLine from 'leader-line' | 14 | import LeaderLine from 'leader-line' |
15 | 15 | ||
16 | /** CSS Selector for main components */ | 16 | /** CSS Selector for main components */ |
17 | const mapBlockSelector = 'pre:has(.language-map)' | 17 | const mapBlockSelector = 'pre:has(.language-map), .mapclay-container' |
18 | const docLinkSelector = 'a[href^="#"][title^="=>"]' | 18 | const docLinkSelector = 'a[href^="#"][title^="=>"]' |
19 | const geoLinkSelector = 'a[href^="geo:"]' | 19 | const geoLinkSelector = 'a[href^="geo:"]' |
20 | 20 | ||
@@ -86,6 +86,68 @@ export const markdown2HTML = (container, mdContent) => { | |||
86 | } | 86 | } |
87 | 87 | ||
88 | /** | 88 | /** |
89 | * defaultBlocks. | ||
90 | * @description Default way to get blocks from Semantic HTML | ||
91 | * @param {HTMLElement} root | ||
92 | */ | ||
93 | const defaultBlocks = root => { | ||
94 | const articles = root.querySelectorAll('article') | ||
95 | if (articles.length > 2) return articles | ||
96 | |||
97 | const others = Array.from( | ||
98 | root.querySelectorAll(':has(>p, >blockquote, >pre, >ul, >ol, >table, >details)'), | ||
99 | ) | ||
100 | .map(e => { | ||
101 | e.classList.add('dumby-block') | ||
102 | return e | ||
103 | }) | ||
104 | .filter(e => { | ||
105 | const isContained = e.parentElement.closest('.dumby-block') | ||
106 | if (isContained) e.classList.remove('dumby-block') | ||
107 | return !isContained | ||
108 | }) | ||
109 | |||
110 | return others | ||
111 | } | ||
112 | |||
113 | /** | ||
114 | * updateAttributeByStep. | ||
115 | * @description Update data attribute by steps of map render | ||
116 | * @param {Object} - renderer which is running steps | ||
117 | */ | ||
118 | const updateAttributeByStep = ({ results, target, steps }) => { | ||
119 | let passNum = results.filter( | ||
120 | r => r.type === 'render' && r.state.match(/success|skip/), | ||
121 | ).length | ||
122 | const total = steps.length | ||
123 | passNum += `/${total}` | ||
124 | |||
125 | const final = results.filter(r => r.type === 'render').length === total | ||
126 | |||
127 | // FIXME HACK use MutationObserver for animation | ||
128 | if (!target.animations) target.animations = Promise.resolve() | ||
129 | target.animations = target.animations.then(async () => { | ||
130 | await new Promise(resolve => setTimeout(resolve, 100)) | ||
131 | if (final) passNum += '\x20' | ||
132 | target.dataset.report = passNum | ||
133 | |||
134 | if (final) setTimeout(() => delete target.dataset.report, 100) | ||
135 | }) | ||
136 | } | ||
137 | |||
138 | /** Get default render method by converter */ | ||
139 | const defaultRender = mapclay.renderWith(config => ({ | ||
140 | use: config.use ?? 'Leaflet', | ||
141 | width: '100%', | ||
142 | ...config, | ||
143 | aliases: { | ||
144 | ...mapclay.defaultAliases, | ||
145 | ...(config.aliases ?? {}), | ||
146 | }, | ||
147 | stepCallback: updateAttributeByStep, | ||
148 | })) | ||
149 | |||
150 | /** | ||
89 | * Generates maps based on the provided configuration | 151 | * Generates maps based on the provided configuration |
90 | * | 152 | * |
91 | * @param {HTMLElement} container - The container element for the maps | 153 | * @param {HTMLElement} container - The container element for the maps |
@@ -96,30 +158,38 @@ export const markdown2HTML = (container, mdContent) => { | |||
96 | */ | 158 | */ |
97 | export const generateMaps = (container, { | 159 | export const generateMaps = (container, { |
98 | crs = 'EPSG:4326', | 160 | crs = 'EPSG:4326', |
161 | initialLayout, | ||
99 | layouts = [], | 162 | layouts = [], |
100 | delay, | 163 | delay, |
101 | renderCallback, | 164 | renderCallback, |
102 | addBlocks = htmlHolder => Array.from(htmlHolder.querySelectorAll('article')), | 165 | addBlocks = defaultBlocks, |
166 | autoMap = false, | ||
167 | render = defaultRender, | ||
103 | } = {}) => { | 168 | } = {}) => { |
104 | /** Prepare Contaner/HTML-Holder/Showcase */ | 169 | /** Prepare Contaner */ |
105 | container.classList.add('Dumby') | 170 | container.classList.add('Dumby') |
106 | delete container.dataset.layout | 171 | delete container.dataset.layout |
107 | container.dataset.layout = defaultLayouts[0].name | 172 | container.dataset.layout = initialLayout ?? defaultLayouts[0].name |
108 | 173 | ||
109 | const htmlHolder = container.querySelector('.SemanticHtml, :has(article, section)') ?? container.firstElementChild | 174 | /** Prepare Semantic HTML part and blocks of contents inside */ |
175 | const htmlHolder = container.querySelector('.SemanticHtml') ?? | ||
176 | Array.from(container.children).find(e => e.id?.includes('main') || e.className.includes('main')) ?? | ||
177 | Array.from(container.children).sort((a, b) => a.textContent.length < b.textContent.length).at(0) | ||
110 | htmlHolder.classList.add('.SemanticHtml') | 178 | htmlHolder.classList.add('.SemanticHtml') |
179 | |||
111 | const blocks = addBlocks(htmlHolder) | 180 | const blocks = addBlocks(htmlHolder) |
112 | blocks.forEach(b => { | 181 | blocks.forEach(b => { |
113 | b.classList.add('dumby-block') | 182 | b.classList.add('dumby-block') |
114 | b.dataset.total = blocks.length | 183 | b.dataset.total = blocks.length |
115 | }) | 184 | }) |
116 | 185 | ||
186 | /** Prepare Showcase */ | ||
117 | const showcase = document.createElement('div') | 187 | const showcase = document.createElement('div') |
118 | container.appendChild(showcase) | 188 | container.appendChild(showcase) |
119 | showcase.classList.add('Showcase') | 189 | showcase.classList.add('Showcase') |
120 | const renderPromises = [] | ||
121 | 190 | ||
122 | /** Prepare Modal */ | 191 | /** Prepare Other Variables */ |
192 | const renderPromises = [] | ||
123 | const modalContent = document.createElement('div') | 193 | const modalContent = document.createElement('div') |
124 | container.appendChild(modalContent) | 194 | container.appendChild(modalContent) |
125 | const modal = new PlainModal(modalContent) | 195 | const modal = new PlainModal(modalContent) |
@@ -175,13 +245,10 @@ export const generateMaps = (container, { | |||
175 | register(proj4) | 245 | register(proj4) |
176 | fromEPSGCode(crs).then(() => resolve()) | 246 | fromEPSGCode(crs).then(() => resolve()) |
177 | }) | 247 | }) |
178 | const addGeoSchemeByText = new Promise(resolve => { | 248 | const addGeoSchemeByText = (async () => { |
179 | const coordPatterns = [ | 249 | const coordPatterns = /(-?\d+\.?\d*)([,\x2F\uFF0C])(-?\d+\.?\d*)/ |
180 | /[\x28\x5B\uFF08]\D*(-?\d+\.?\d*)([\x2F\s])(-?\d+\.?\d*)\D*[\x29\x5D\uFF09]/, | 250 | const re = new RegExp(coordPatterns, 'g') |
181 | /(-?\d+\.?\d*)([,\uFF0C])(-?\d+\.?\d*)/, | 251 | htmlHolder.querySelectorAll('.dumby-block') |
182 | ] | ||
183 | const re = new RegExp(coordPatterns.map(p => p.source).join('|'), 'g') | ||
184 | htmlHolder.querySelectorAll('p') | ||
185 | .forEach(p => { | 252 | .forEach(p => { |
186 | replaceTextNodes(p, re, match => { | 253 | replaceTextNodes(p, re, match => { |
187 | const a = document.createElement('a') | 254 | const a = document.createElement('a') |
@@ -190,8 +257,7 @@ export const generateMaps = (container, { | |||
190 | return a | 257 | return a |
191 | }) | 258 | }) |
192 | }) | 259 | }) |
193 | resolve() | 260 | })() |
194 | }) | ||
195 | 261 | ||
196 | Promise.all([setCRS, addGeoSchemeByText]).then(() => { | 262 | Promise.all([setCRS, addGeoSchemeByText]).then(() => { |
197 | Array.from(container.querySelectorAll(geoLinkSelector)) | 263 | Array.from(container.querySelectorAll(geoLinkSelector)) |
@@ -392,63 +458,26 @@ export const generateMaps = (container, { | |||
392 | const elementsWithMapConfig = Array.from( | 458 | const elementsWithMapConfig = Array.from( |
393 | container.querySelectorAll(mapBlockSelector) ?? [], | 459 | container.querySelectorAll(mapBlockSelector) ?? [], |
394 | ) | 460 | ) |
395 | /** | 461 | if (autoMap && elementsWithMapConfig.length === 0) { |
396 | * updateAttributeByStep. | 462 | const mapContainer = document.createElement('pre') |
397 | * | 463 | mapContainer.className = 'mapclay-container' |
398 | * @param {Object} - renderer which is running steps | 464 | mapContainer.textContent = '#Created by DumbyMap' |
399 | */ | 465 | htmlHolder.insertBefore(mapContainer, htmlHolder.firstElementChild) |
400 | const updateAttributeByStep = ({ results, target, steps }) => { | 466 | elementsWithMapConfig.push(mapContainer) |
401 | let passNum = results.filter( | ||
402 | r => r.type === 'render' && r.state.match(/success|skip/), | ||
403 | ).length | ||
404 | const total = steps.length | ||
405 | passNum += `/${total}` | ||
406 | |||
407 | const final = results.filter(r => r.type === 'render').length === total | ||
408 | |||
409 | // FIXME HACK use MutationObserver for animation | ||
410 | if (!target.animations) target.animations = Promise.resolve() | ||
411 | target.animations = target.animations.then(async () => { | ||
412 | await new Promise(resolve => setTimeout(resolve, 100)) | ||
413 | if (final) passNum += '\x20' | ||
414 | target.dataset.report = passNum | ||
415 | |||
416 | if (final) setTimeout(() => delete target.dataset.report, 100) | ||
417 | }) | ||
418 | } | 467 | } |
419 | /** | ||
420 | * config converter for mapclay.renderWith() | ||
421 | * | ||
422 | * @param {Object} config | ||
423 | * @return {Object} - converted config | ||
424 | */ | ||
425 | const configConverter = config => ({ | ||
426 | use: config.use ?? 'Leaflet', | ||
427 | width: '100%', | ||
428 | ...config, | ||
429 | aliases: { | ||
430 | ...defaultAliases, | ||
431 | ...(config.aliases ?? {}), | ||
432 | }, | ||
433 | stepCallback: updateAttributeByStep, | ||
434 | }) | ||
435 | |||
436 | /** Get render method by converter */ | ||
437 | const render = renderWith(configConverter) | ||
438 | 468 | ||
439 | /** Render each taget element for maps */ | 469 | /** Render each taget element for maps */ |
440 | let order = 0 | 470 | let order = 0 |
441 | elementsWithMapConfig.forEach(target => { | 471 | elementsWithMapConfig.forEach(target => { |
442 | // Get text in code block starts with markdown text '```map' | 472 | // Get text in code block starts with markdown text '```map' |
443 | const configText = target | 473 | const configText = target |
444 | .querySelector('.language-map') | ||
445 | .textContent // BE CAREFUL!!! 0xa0 char is "non-breaking spaces" in HTML text content | 474 | .textContent // BE CAREFUL!!! 0xa0 char is "non-breaking spaces" in HTML text content |
446 | // replace it by normal space | 475 | // replace it by normal space |
447 | .replace(/\u00A0/g, '\u0020') | 476 | .replace(/\u00A0/g, '\u0020') |
448 | 477 | ||
449 | let configList = [] | 478 | let configList = [] |
450 | try { | 479 | try { |
451 | configList = parseConfigsFromYaml(configText).map(assignMapId) | 480 | configList = mapclay.parseConfigsFromYaml(configText).map(assignMapId) |
452 | } catch (_) { | 481 | } catch (_) { |
453 | console.warn('Fail to parse yaml config for element', target) | 482 | console.warn('Fail to parse yaml config for element', target) |
454 | return | 483 | return |
@@ -510,7 +539,7 @@ export const generateMaps = (container, { | |||
510 | container.oncontextmenu = e => { | 539 | container.oncontextmenu = e => { |
511 | menu.replaceChildren() | 540 | menu.replaceChildren() |
512 | menu.style.display = 'block' | 541 | menu.style.display = 'block' |
513 | menu.style.cssText = `left: ${e.x - menu.offsetParent.offsetLeft + 10}px; top: ${e.y - menu.offsetParent.offsetTop + 5}px;` | 542 | menu.style.cssText = `left: ${e.clientX - menu.offsetParent.offsetLeft + 10}px; top: ${e.clientY - menu.offsetParent.offsetTop + 5}px;` |
514 | e.preventDefault() | 543 | e.preventDefault() |
515 | 544 | ||
516 | // Menu Items for map | 545 | // Menu Items for map |
diff --git a/src/utils.mjs b/src/utils.mjs index 1fe3ce5..327bee4 100644 --- a/src/utils.mjs +++ b/src/utils.mjs | |||
@@ -177,3 +177,35 @@ export const replaceTextNodes = ( | |||
177 | node = nodeIterator.nextNode() | 177 | node = nodeIterator.nextNode() |
178 | } | 178 | } |
179 | } | 179 | } |
180 | |||
181 | /** | ||
182 | * Get the common ancestor of two or more elements | ||
183 | * {@link https://gist.github.com/kieranbarker/cd86310d0782b7c52ce90cd7f45bb3eb} | ||
184 | * @param {String} selector A valid CSS selector | ||
185 | * @returns {Element} The common ancestor | ||
186 | */ | ||
187 | export function getCommonAncestor (selector) { | ||
188 | // Get the elements matching the selector | ||
189 | const elems = document.querySelectorAll(selector) | ||
190 | |||
191 | // If there are no elements, return null | ||
192 | if (elems.length < 1) return null | ||
193 | |||
194 | // If there's only one element, return it | ||
195 | if (elems.length < 2) return elems[0] | ||
196 | |||
197 | // Otherwise, create a new Range | ||
198 | const range = document.createRange() | ||
199 | |||
200 | // Start at the beginning of the first element | ||
201 | range.setStart(elems[0], 0) | ||
202 | |||
203 | // Stop at the end of the last element | ||
204 | range.setEnd( | ||
205 | elems[elems.length - 1], | ||
206 | elems[elems.length - 1].childNodes.length, | ||
207 | ) | ||
208 | |||
209 | // Return the common ancestor | ||
210 | return range.commonAncestorContainer | ||
211 | } | ||