diff --git a/src/steps/rewrite-icons.js b/src/steps/rewrite-icons.js index d0220451..923c0117 100644 --- a/src/steps/rewrite-icons.js +++ b/src/steps/rewrite-icons.js @@ -13,8 +13,6 @@ import { h } from 'hastscript'; import { CONTINUE, SKIP, visit } from 'unist-util-visit'; -const REGEXP_ICON = /(? icon element: * @@ -43,6 +41,7 @@ function createIcon(value) { */ export default function rewrite({ content }) { const { hast } = content; + visit(hast, (node, idx, parent) => { if (node.tagName === 'code') { return SKIP; @@ -52,31 +51,97 @@ export default function rewrite({ content }) { } const text = node.value; - let lastIdx = 0; - for (const match of text.matchAll(REGEXP_ICON)) { - const [matched, icon] = match; - const before = text.substring(lastIdx, match.index); - if (before) { - // textNode.parentNode.insertBefore(document.createTextNode(before), textNode); - parent.children.splice(idx, 0, { type: 'text', value: before }); - idx += 1; - } - // textNode.parentNode.insertBefore(createIcon(document, icon), textNode); - parent.children.splice(idx, 0, createIcon(icon)); - idx += 1; - lastIdx = match.index + matched.length; + const tokens = []; + let pos = 0; + + // Find URN and timestamp patterns and mark their ranges + const skipRanges = []; + + // URN patterns + const urnRegex = /urn:[^\s]*/g; + let urnMatch = urnRegex.exec(text); + while (urnMatch !== null) { + skipRanges.push([urnMatch.index, urnMatch.index + urnMatch[0].length]); + urnMatch = urnRegex.exec(text); + } + + // Timestamp patterns (both real and placeholder) + const timeRegex = /(?:\d{4}|\b[A-Z]{4})-(?:\d{2}|[A-Z]{2})-(?:\d{2}|[A-Z]{2})T(?:\d{2}|[A-Z]{2}):(?:\d{2}|[A-Z]{2}):(?:\d{2}|[A-Z]{2})/g; + let timeMatch = timeRegex.exec(text); + while (timeMatch !== null) { + skipRanges.push([timeMatch.index, timeMatch.index + timeMatch[0].length]); + timeMatch = timeRegex.exec(text); } - if (lastIdx && lastIdx <= text.length) { - // there is still some text left - const after = text.substring(lastIdx); - if (after) { - node.value = after; + while (pos < text.length) { + const colonPos = text.indexOf(':', pos); + if (colonPos === -1) { + tokens.push({ type: 'TEXT', value: text.slice(pos) }); + break; + } + + // Add text before the colon + if (colonPos > pos) { + tokens.push({ type: 'TEXT', value: text.slice(pos, colonPos) }); + } + + // Look for the closing colon + const nextColon = text.indexOf(':', colonPos + 1); + if (nextColon === -1) { + tokens.push({ type: 'TEXT', value: text.slice(colonPos) }); + break; + } + + const potentialIcon = text.slice(colonPos, nextColon + 1); + const beforeText = text.slice(Math.max(0, colonPos - 20), colonPos); + + // Check if this colon is part of a skip range + const isInSkipRange = skipRanges.some((range) => colonPos >= range[0] + && nextColon <= range[1]); + + // Additional check for timestamp-like patterns + const isTimestampPattern = /[A-Z]{2}:[A-Z]{2}/.test(potentialIcon) + || beforeText.match(/[A-Z]{2}$/) + || text.slice(nextColon + 1).match(/^[A-Z]{2}/); + + // Skip if this is part of a known pattern + const skipIfFound = [ + /https?/, // URLs + /T\d{2}/, // ISO timestamps + /\d{4}-\d{2}/, // Dates + ]; + + const shouldSkip = isInSkipRange + || isTimestampPattern + || skipIfFound.some((pattern) => pattern.test(beforeText)) + || /\d$/.test(beforeText) // number before first colon + || /^\d/.test(text[nextColon + 1]); // number after second colon + + if (shouldSkip) { + tokens.push({ type: 'TEXT', value: potentialIcon }); } else { - parent.children.splice(idx, 1); - idx -= 1; + const iconName = potentialIcon.slice(1, -1); + if (/^[#a-z0-9][-a-z0-9]*[a-z0-9]$/.test(iconName)) { + tokens.push({ type: 'ICON', value: iconName }); + } else { + tokens.push({ type: 'TEXT', value: potentialIcon }); + } } + + pos = nextColon + 1; + } + + // Only process if we found any icons + if (!tokens.some((t) => t.type === 'ICON')) { + return CONTINUE; } - return idx + 1; + + // Convert tokens to nodes + const newNodes = tokens.map((token) => ( + token.type === 'ICON' ? createIcon(token.value) : { type: 'text', value: token.value } + )); + + parent.children.splice(idx, 1, ...newNodes); + return idx + newNodes.length; }); } diff --git a/test/fixtures/content/icons-ignored.html b/test/fixtures/content/icons-ignored.html index 47f247b3..6f02ed5e 100644 --- a/test/fixtures/content/icons-ignored.html +++ b/test/fixtures/content/icons-ignored.html @@ -5,5 +5,29 @@

Icons

:rocket:

https://example.test/:urn:

urn:aaid:sc:VA6C2:ac6066f3-fd1d-4e00-bed3-fa3aa6d981d8

+

:this is also ignored:

+

08:28:54

+

g) and 1:00-3:00 PM. H

+

6:00AM-6:00 PM MT

+

11:30am-12:30pm CT

+

c HEVC 4:2:2 10 bit

+

色情報が半分の4:2:2素材では、エッ

+

月1日木曜日14:00-19:00

+

Sec4:3-Sec4:6, Sec3:

+

ben Sie :1: ein, ge

+

168.0.52:4501:ssl

+

YYYY-MM-DDTHH:mm:ss.sssZ

+

2024-05-02T06:20:10.123Z

+

1:test: should be ignored

+

test:2: should also be ignored

+

x1:icon: should be ignored

+

:icon:2 should be ignored

+

00:00:00

+

This is just regular text with no icons



+

Empty text:

+

Non-text:

+

Mixed content: before :icon: between :button: after

+

:not-processed

+

Text with :1: number

- \ No newline at end of file + diff --git a/test/fixtures/content/icons-ignored.md b/test/fixtures/content/icons-ignored.md index 30301a79..e3465d27 100644 --- a/test/fixtures/content/icons-ignored.md +++ b/test/fixtures/content/icons-ignored.md @@ -9,3 +9,54 @@ [https://example.test/:urn:](https://example.test/:urn:) urn:aaid:sc:VA6C2:ac6066f3-fd1d-4e00-bed3-fa3aa6d981d8 + +:this is also ignored: + +08:28:54 + +g) and 1:00-3:00 PM. H + +6:00AM-6:00 PM MT + + 11:30am-12:30pm CT + +c HEVC 4:2:2 10 bit + +色情報が半分の4:2:2素材では、エッ + +月1日木曜日14:00-19:00 + +Sec4:3-Sec4:6, Sec3: + +ben Sie :1: ein, ge + +168.0.52:4501:ssl + +YYYY-MM-DDTHH:mm:ss.sssZ + +2024-05-02T06:20:10.123Z + +1:test: should be ignored + +test:2: should also be ignored + +x1:icon: should be ignored + +:icon:2 should be ignored + +00:00:00 + +This is just regular text with no icons + +
+
+ +Empty text: + +Non-text: + +Mixed content: before :icon: between :button: after + +:not-processed + +Text with :1: number diff --git a/test/fixtures/content/icons.html b/test/fixtures/content/icons.html index 9ff77789..21228add 100644 --- a/test/fixtures/content/icons.html +++ b/test/fixtures/content/icons.html @@ -1,9 +1,25 @@

Icons

-

Hello

-

Hello banner.

-

Hello mark.

+

Hello

+

Hello banner.

+

Hello mark.

Teamblasting off again.

+

This is a that should work

+

number check

+

number check

+

number check

+

number check

+

number check

+

number check

+

number check

+

number check

+

number check

+

number check

+

number check

+

number check

+

icon named

+

for :foo:2 press the please.

+

Regular text more text final text

-
\ No newline at end of file + diff --git a/test/fixtures/content/icons.md b/test/fixtures/content/icons.md index b84468f4..3feaab3b 100644 --- a/test/fixtures/content/icons.md +++ b/test/fixtures/content/icons.md @@ -7,3 +7,36 @@ Hello :red: banner. Hello :#check: mark. Team:rocket:blasting off again. + +This is a :a-10-check: that should work + +number check :1-check: + +number check :two-2-check: + +number check :three-3-check: + +number check :four4check: + +number check :5-check: + +number check :six-6000-check: + +number check :seven7000check: + +number check :8000check: + +number check :nine9000: + +number check :ten-10000: + +number check :eleven-11: + +number check :twelve-v1: + +icon named :icon: + +for :foo:2 press the :button: please. + +Regular text :button: more text :rocket: final text +