From 1caf56dc0a2bbf4bb484fe4f312b5229a63aace3 Mon Sep 17 00:00:00 2001 From: Hsieh Chin Fan Date: Wed, 16 Oct 2024 16:57:47 +0800 Subject: feat: add simple script to test mapclay * add more options for generateMaps for general purposes * add rollup config for adding script into addon --- src/dumbymap.mjs | 128 +++++++++++++++++++++++++++++++++---------------------- 1 file changed, 78 insertions(+), 50 deletions(-) (limited to 'src') diff --git a/src/dumbymap.mjs b/src/dumbymap.mjs index 8e26ee6..ee61838 100644 --- a/src/dumbymap.mjs +++ b/src/dumbymap.mjs @@ -3,7 +3,7 @@ import MarkdownItAnchor from 'markdown-it-anchor' import MarkdownItFootnote from 'markdown-it-footnote' import MarkdownItFrontMatter from 'markdown-it-front-matter' import MarkdownItInjectLinenumbers from 'markdown-it-inject-linenumbers' -import { renderWith, defaultAliases, parseConfigsFromYaml } from 'mapclay' +import * as mapclay from 'mapclay' import { replaceTextNodes, onRemove, animateRectTransition, throttle, shiftByWindow } from './utils' import { Layout, SideBySide, Overlay } from './Layout' import * as utils from './dumbyUtils' @@ -85,6 +85,68 @@ export const markdown2HTML = (container, mdContent) => { return container } +/** + * defaultBlocks. + * @description Default way to get blocks from Semantic HTML + * @param {HTMLElement} root + */ +const defaultBlocks = root => { + const articles = root.querySelectorAll('article') + if (articles.length > 2) return articles + + const others = Array.from( + root.querySelectorAll(':has(>p, >blockquote, >pre, >ul, >ol, >table, >details)'), + ) + .map(e => { + e.classList.add('dumby-block') + return e + }) + .filter(e => { + const isContained = e.parentElement.closest('.dumby-block') + if (isContained) e.classList.remove('dumby-block') + return !isContained + }) + + return others +} + +/** + * updateAttributeByStep. + * @description Update data attribute by steps of map render + * @param {Object} - renderer which is running steps + */ +const updateAttributeByStep = ({ results, target, steps }) => { + let passNum = results.filter( + r => r.type === 'render' && r.state.match(/success|skip/), + ).length + const total = steps.length + passNum += `/${total}` + + const final = results.filter(r => r.type === 'render').length === total + + // FIXME HACK use MutationObserver for animation + if (!target.animations) target.animations = Promise.resolve() + target.animations = target.animations.then(async () => { + await new Promise(resolve => setTimeout(resolve, 100)) + if (final) passNum += '\x20' + target.dataset.report = passNum + + if (final) setTimeout(() => delete target.dataset.report, 100) + }) +} + +/** Get default render method by converter */ +const defaultRender = mapclay.renderWith(config => ({ + use: config.use ?? 'Leaflet', + width: '100%', + ...config, + aliases: { + ...mapclay.defaultAliases, + ...(config.aliases ?? {}), + }, + stepCallback: updateAttributeByStep, +})) + /** * Generates maps based on the provided configuration * @@ -96,16 +158,19 @@ export const markdown2HTML = (container, mdContent) => { */ export const generateMaps = (container, { crs = 'EPSG:4326', + initialLayout, layouts = [], delay, renderCallback, - addBlocks = htmlHolder => Array.from(htmlHolder.querySelectorAll('article')), + addBlocks = defaultBlocks, + render = defaultRender, } = {}) => { - /** Prepare Contaner/HTML-Holder/Showcase */ + /** Prepare Contaner */ container.classList.add('Dumby') delete container.dataset.layout - container.dataset.layout = defaultLayouts[0].name + container.dataset.layout = initialLayout ?? defaultLayouts[0].name + /** Prepare Semantic HTML part and blocks of contents inside */ const htmlHolder = container.querySelector('.SemanticHtml, :has(article, section)') ?? container.firstElementChild htmlHolder.classList.add('.SemanticHtml') const blocks = addBlocks(htmlHolder) @@ -114,12 +179,13 @@ export const generateMaps = (container, { b.dataset.total = blocks.length }) + /** Prepare Showcase */ const showcase = document.createElement('div') container.appendChild(showcase) showcase.classList.add('Showcase') - const renderPromises = [] - /** Prepare Modal */ + /** Prepare Other Variables */ + const renderPromises = [] const modalContent = document.createElement('div') container.appendChild(modalContent) const modal = new PlainModal(modalContent) @@ -392,63 +458,25 @@ export const generateMaps = (container, { const elementsWithMapConfig = Array.from( container.querySelectorAll(mapBlockSelector) ?? [], ) - /** - * updateAttributeByStep. - * - * @param {Object} - renderer which is running steps - */ - const updateAttributeByStep = ({ results, target, steps }) => { - let passNum = results.filter( - r => r.type === 'render' && r.state.match(/success|skip/), - ).length - const total = steps.length - passNum += `/${total}` - - const final = results.filter(r => r.type === 'render').length === total - - // FIXME HACK use MutationObserver for animation - if (!target.animations) target.animations = Promise.resolve() - target.animations = target.animations.then(async () => { - await new Promise(resolve => setTimeout(resolve, 100)) - if (final) passNum += '\x20' - target.dataset.report = passNum - - if (final) setTimeout(() => delete target.dataset.report, 100) - }) + if (elementsWithMapConfig.length === 0) { + const map = document.createElement('pre') + map.textContent = '#Created by DumbyMap' + htmlHolder.insertBefore(map, htmlHolder.firstElementChild) + elementsWithMapConfig.push(map) } - /** - * config converter for mapclay.renderWith() - * - * @param {Object} config - * @return {Object} - converted config - */ - const configConverter = config => ({ - use: config.use ?? 'Leaflet', - width: '100%', - ...config, - aliases: { - ...defaultAliases, - ...(config.aliases ?? {}), - }, - stepCallback: updateAttributeByStep, - }) - - /** Get render method by converter */ - const render = renderWith(configConverter) /** Render each taget element for maps */ let order = 0 elementsWithMapConfig.forEach(target => { // Get text in code block starts with markdown text '```map' const configText = target - .querySelector('.language-map') .textContent // BE CAREFUL!!! 0xa0 char is "non-breaking spaces" in HTML text content // replace it by normal space .replace(/\u00A0/g, '\u0020') let configList = [] try { - configList = parseConfigsFromYaml(configText).map(assignMapId) + configList = mapclay.parseConfigsFromYaml(configText).map(assignMapId) } catch (_) { console.warn('Fail to parse yaml config for element', target) return -- cgit v1.2.3-70-g09d2 From a6f5b8634cd0681cea9468e4ee725b5b125ddfe7 Mon Sep 17 00:00:00 2001 From: Hsieh Chin Fan Date: Wed, 16 Oct 2024 16:58:12 +0800 Subject: feat(CSS): dont't modify origin CSS unless layout is specified --- src/css/dumbymap.css | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src') diff --git a/src/css/dumbymap.css b/src/css/dumbymap.css index dc1f59b..fdf3bdd 100644 --- a/src/css/dumbymap.css +++ b/src/css/dumbymap.css @@ -166,7 +166,7 @@ root { } } -.dumby-block { +.Dumby:not([data-layout=""]) .dumby-block { padding: 1rem 1rem 1rem 2rem; position: relative; -- cgit v1.2.3-70-g09d2 From 847872c5ed3cdcec572e1cc5cfd43498f828e1cd Mon Sep 17 00:00:00 2001 From: Hsieh Chin Fan Date: Wed, 16 Oct 2024 16:59:38 +0800 Subject: fix: position of contextmenu --- src/dumbymap.mjs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src') diff --git a/src/dumbymap.mjs b/src/dumbymap.mjs index ee61838..c03680d 100644 --- a/src/dumbymap.mjs +++ b/src/dumbymap.mjs @@ -538,7 +538,7 @@ export const generateMaps = (container, { container.oncontextmenu = e => { menu.replaceChildren() menu.style.display = 'block' - menu.style.cssText = `left: ${e.x - menu.offsetParent.offsetLeft + 10}px; top: ${e.y - menu.offsetParent.offsetTop + 5}px;` + menu.style.cssText = `left: ${e.clientX - menu.offsetParent.offsetLeft + 10}px; top: ${e.clientY - menu.offsetParent.offsetTop + 5}px;` e.preventDefault() // Menu Items for map -- cgit v1.2.3-70-g09d2 From d805347e492d571b5ac5f4d310b6e39466ca87a9 Mon Sep 17 00:00:00 2001 From: Hsieh Chin Fan Date: Wed, 16 Oct 2024 18:56:28 +0800 Subject: fix: prevent define web components twice --- src/MenuItem.mjs | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) (limited to 'src') 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 { } } } -window.customElements.define('dumby-menu-item', Item, { extends: 'div' }) +if (!window.customElements.get('dumby-menu-item')) { + window.customElements.define('dumby-menu-item', Item, { extends: 'div' }) +} /** * Basic Element for menu item that generates a submenu on hover @@ -80,7 +82,9 @@ export class Folder extends window.HTMLDivElement { } } } -window.customElements.define('menu-folder', Folder, { extends: 'div' }) +if (!window.customElements.get('menu-folder')) { + window.customElements.define('menu-folder', Folder, { extends: 'div' }) +} /** * Creates a menu item for picking a map @@ -232,7 +236,9 @@ export class Suggestion extends Item { } } } -window.customElements.define('menu-item-suggestion', Suggestion, { extends: 'div' }) +if (!window.customElements.get('menu-item-suggestion')) { + window.customElements.define('menu-item-suggestion', Suggestion, { extends: 'div' }) +} /** * renderResults. return a menu item for reporting render results -- cgit v1.2.3-70-g09d2 From fa4dfcbfa6361417e325f90d59e86ae8d4765807 Mon Sep 17 00:00:00 2001 From: Hsieh Chin Fan Date: Wed, 16 Oct 2024 18:57:01 +0800 Subject: feat(CSS): do not make GeoLink bolder while hovering --- src/css/dumbymap.css | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src') diff --git a/src/css/dumbymap.css b/src/css/dumbymap.css index fdf3bdd..c0bb1a9 100644 --- a/src/css/dumbymap.css +++ b/src/css/dumbymap.css @@ -161,7 +161,7 @@ root { &:hover, &.drag { background-image: none; - font-weight: bolder; + /* font-weight: bolder; */ text-decoration: none; } } -- cgit v1.2.3-70-g09d2 From 6ad14a3daf3292048de1a4c72cabca243d03b103 Mon Sep 17 00:00:00 2001 From: Hsieh Chin Fan Date: Wed, 16 Oct 2024 20:45:41 +0800 Subject: fix: misused for result of match --- src/dumbymap.mjs | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) (limited to 'src') diff --git a/src/dumbymap.mjs b/src/dumbymap.mjs index c03680d..55d29d3 100644 --- a/src/dumbymap.mjs +++ b/src/dumbymap.mjs @@ -242,11 +242,8 @@ export const generateMaps = (container, { fromEPSGCode(crs).then(() => resolve()) }) const addGeoSchemeByText = new Promise(resolve => { - const coordPatterns = [ - /[\x28\x5B\uFF08]\D*(-?\d+\.?\d*)([\x2F\s])(-?\d+\.?\d*)\D*[\x29\x5D\uFF09]/, - /(-?\d+\.?\d*)([,\uFF0C])(-?\d+\.?\d*)/, - ] - const re = new RegExp(coordPatterns.map(p => p.source).join('|'), 'g') + const coordPatterns = /(-?\d+\.?\d*)([,\x2F\uFF0C])(-?\d+\.?\d*)/ + const re = new RegExp(coordPatterns, 'g') htmlHolder.querySelectorAll('p') .forEach(p => { replaceTextNodes(p, re, match => { -- cgit v1.2.3-70-g09d2 From 5502bf29bdbaa054a96794ddf93ab8cf0bcc2f77 Mon Sep 17 00:00:00 2001 From: Hsieh Chin Fan Date: Thu, 17 Oct 2024 10:19:46 +0800 Subject: fix: CRS for addon --- addon/index.mjs | 1 + addon/manifest.json | 5 +++-- src/dumbymap.mjs | 7 +++---- 3 files changed, 7 insertions(+), 6 deletions(-) (limited to 'src') diff --git a/addon/index.mjs b/addon/index.mjs index 323edf1..2494ec4 100644 --- a/addon/index.mjs +++ b/addon/index.mjs @@ -12,6 +12,7 @@ const simpleRender = window.mapclay.renderWith(config => ({ })) window.generateMaps(document.querySelector('main') ?? document.body, { + crs: url.searchParams.get('crs') ?? 'EPSG:4326', initialLayout: '', render: simpleRender, }) diff --git a/addon/manifest.json b/addon/manifest.json index e97ae50..2d7b1ed 100644 --- a/addon/manifest.json +++ b/addon/manifest.json @@ -12,7 +12,7 @@ "content_scripts": [ { "matches": [ - "*://*.mozilla.org/*", + "*://developer.mozilla.org/*", "*://hackmd.io/*", "*://*.ptt.cc/*" ], @@ -29,6 +29,7 @@ "permissions": [ "activeTab", "tabs", - "scripting" + "scripting", + "https://epsg.io/*" ] } diff --git a/src/dumbymap.mjs b/src/dumbymap.mjs index 55d29d3..cbd44b2 100644 --- a/src/dumbymap.mjs +++ b/src/dumbymap.mjs @@ -241,10 +241,10 @@ export const generateMaps = (container, { register(proj4) fromEPSGCode(crs).then(() => resolve()) }) - const addGeoSchemeByText = new Promise(resolve => { + const addGeoSchemeByText = (async () => { const coordPatterns = /(-?\d+\.?\d*)([,\x2F\uFF0C])(-?\d+\.?\d*)/ const re = new RegExp(coordPatterns, 'g') - htmlHolder.querySelectorAll('p') + htmlHolder.querySelectorAll('.dumby-block') .forEach(p => { replaceTextNodes(p, re, match => { const a = document.createElement('a') @@ -253,8 +253,7 @@ export const generateMaps = (container, { return a }) }) - resolve() - }) + })() Promise.all([setCRS, addGeoSchemeByText]).then(() => { Array.from(container.querySelectorAll(geoLinkSelector)) -- cgit v1.2.3-70-g09d2 From 62b7af8ce0ccbad26cd00be24cc2cf1e817b8581 Mon Sep 17 00:00:00 2001 From: Hsieh Chin Fan Date: Thu, 17 Oct 2024 10:26:52 +0800 Subject: feat: more precise way to get htmlHolder and blocks --- addon/index.mjs | 25 ++++++++++++++++++++++++- src/dumbymap.mjs | 5 ++++- 2 files changed, 28 insertions(+), 2 deletions(-) (limited to 'src') diff --git a/addon/index.mjs b/addon/index.mjs index 2494ec4..7f33fc5 100644 --- a/addon/index.mjs +++ b/addon/index.mjs @@ -1,4 +1,26 @@ -const { Leaflet } = window.mapclay.renderers +const url = new URL(window.location) +if (url.host === 'www.ptt.cc') { + const content = document.querySelector('#main-content') + Array.from(content.childNodes) + .filter(n => !(n instanceof window.HTMLElement)) + .forEach(text => { + const span = document.createElement('span') + span.innerText = text.textContent + text.replaceWith(span) + }) +} + +const blockSelectors = { + 'developer.mozilla': '.section-content', + 'hackmd.io': '#doc > *', + 'www.ptt.cc': '#main-content > span', +} +const blockSelector = blockSelectors[url.host] + +const addBlocks = blockSelector + ? root => Array.from(root.querySelectorAll(blockSelector)) + : undefined + const simpleRender = window.mapclay.renderWith(config => ({ use: 'Leaflet', width: '100%', @@ -13,6 +35,7 @@ const simpleRender = window.mapclay.renderWith(config => ({ window.generateMaps(document.querySelector('main') ?? document.body, { crs: url.searchParams.get('crs') ?? 'EPSG:4326', + addBlocks, initialLayout: '', render: simpleRender, }) diff --git a/src/dumbymap.mjs b/src/dumbymap.mjs index cbd44b2..94fcc1d 100644 --- a/src/dumbymap.mjs +++ b/src/dumbymap.mjs @@ -171,8 +171,11 @@ export const generateMaps = (container, { container.dataset.layout = initialLayout ?? defaultLayouts[0].name /** Prepare Semantic HTML part and blocks of contents inside */ - const htmlHolder = container.querySelector('.SemanticHtml, :has(article, section)') ?? container.firstElementChild + const htmlHolder = container.querySelector('.SemanticHtml') ?? + Array.from(container.children).find(e => e.id?.includes('main') || e.className.includes('main')) ?? + Array.from(container.children).sort((a, b) => a.textContent.length < b.textContent.length).at(0) htmlHolder.classList.add('.SemanticHtml') + const blocks = addBlocks(htmlHolder) blocks.forEach(b => { b.classList.add('dumby-block') -- cgit v1.2.3-70-g09d2 From 337a562ee8c9e2531a1a6799acaa66567ad7ef12 Mon Sep 17 00:00:00 2001 From: Hsieh Chin Fan Date: Thu, 17 Oct 2024 10:28:32 +0800 Subject: feat: option "autoMap" in case no valid render target in Semantic HTML --- addon/index.mjs | 1 + src/dumbymap.mjs | 14 ++++++++------ 2 files changed, 9 insertions(+), 6 deletions(-) (limited to 'src') diff --git a/addon/index.mjs b/addon/index.mjs index 092adc4..b5d71ba 100644 --- a/addon/index.mjs +++ b/addon/index.mjs @@ -38,4 +38,5 @@ window.generateMaps(document.querySelector('main') ?? document.body, { addBlocks, initialLayout: '', render: simpleRender, + autoMap: true, }) diff --git a/src/dumbymap.mjs b/src/dumbymap.mjs index 94fcc1d..706e874 100644 --- a/src/dumbymap.mjs +++ b/src/dumbymap.mjs @@ -14,7 +14,7 @@ import { register, fromEPSGCode } from 'ol/proj/proj4' import LeaderLine from 'leader-line' /** CSS Selector for main components */ -const mapBlockSelector = 'pre:has(.language-map)' +const mapBlockSelector = 'pre:has(.language-map), .mapclay-container' const docLinkSelector = 'a[href^="#"][title^="=>"]' const geoLinkSelector = 'a[href^="geo:"]' @@ -163,6 +163,7 @@ export const generateMaps = (container, { delay, renderCallback, addBlocks = defaultBlocks, + autoMap = false, render = defaultRender, } = {}) => { /** Prepare Contaner */ @@ -457,11 +458,12 @@ export const generateMaps = (container, { const elementsWithMapConfig = Array.from( container.querySelectorAll(mapBlockSelector) ?? [], ) - if (elementsWithMapConfig.length === 0) { - const map = document.createElement('pre') - map.textContent = '#Created by DumbyMap' - htmlHolder.insertBefore(map, htmlHolder.firstElementChild) - elementsWithMapConfig.push(map) + if (autoMap && elementsWithMapConfig.length === 0) { + const mapContainer = document.createElement('pre') + mapContainer.className = 'mapclay-container' + mapContainer.textContent = '#Created by DumbyMap' + htmlHolder.insertBefore(mapContainer, htmlHolder.firstElementChild) + elementsWithMapConfig.push(mapContainer) } /** Render each taget element for maps */ -- cgit v1.2.3-70-g09d2 From 9a66411258781a57d5c953b7113403fdf0d218cf Mon Sep 17 00:00:00 2001 From: Hsieh Chin Fan Date: Thu, 17 Oct 2024 10:31:22 +0800 Subject: feat: add utils for finding common ancestor --- src/utils.mjs | 32 ++++++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) (limited to 'src') 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 = ( node = nodeIterator.nextNode() } } + +/** + * Get the common ancestor of two or more elements + * {@link https://gist.github.com/kieranbarker/cd86310d0782b7c52ce90cd7f45bb3eb} + * @param {String} selector A valid CSS selector + * @returns {Element} The common ancestor + */ +export function getCommonAncestor (selector) { + // Get the elements matching the selector + const elems = document.querySelectorAll(selector) + + // If there are no elements, return null + if (elems.length < 1) return null + + // If there's only one element, return it + if (elems.length < 2) return elems[0] + + // Otherwise, create a new Range + const range = document.createRange() + + // Start at the beginning of the first element + range.setStart(elems[0], 0) + + // Stop at the end of the last element + range.setEnd( + elems[elems.length - 1], + elems[elems.length - 1].childNodes.length, + ) + + // Return the common ancestor + return range.commonAncestorContainer +} -- cgit v1.2.3-70-g09d2