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
+