Skip to content

add numeric icon feature #762

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 9 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 6 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
35 changes: 29 additions & 6 deletions src/steps/rewrite-icons.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,6 @@
import { h } from 'hastscript';
import { CONTINUE, SKIP, visit } from 'unist-util-visit';

const REGEXP_ICON = /(?<!(?:https?|urn)[^\s]*):(#?[a-z_-]+[a-z\d]*):/gi;

/**
* Create a <span> icon element:
*
Expand All @@ -35,6 +33,11 @@ function createIcon(value) {
return h('span', { className: ['icon', `icon-${name}`] });
}

// Helper function to check if a character is a digit
function isDigit(char) {
return char >= '0' && char <= '9';
}

/**
* Rewrite :icons:
*
Expand All @@ -43,6 +46,7 @@ function createIcon(value) {
*/
export default function rewrite({ content }) {
const { hast } = content;

visit(hast, (node, idx, parent) => {
if (node.tagName === 'code') {
return SKIP;
Expand All @@ -52,23 +56,41 @@ export default function rewrite({ content }) {
}

const text = node.value;

// Process icons with stricter regex
// Only match valid icon patterns that:
// - Must not be part of a timestamp pattern (real or example)
// - Must not be part of a URL/URN pattern
// - Must not be part of a time/ratio pattern
const ICON_REGEX = /(?<!(?:https?|urn)[^\s]*|[\d:T]):((?!mm\b)[#a-z\d][a-z\d_-]*[a-z\d]):/gi;

let lastIdx = 0;
for (const match of text.matchAll(REGEXP_ICON)) {
for (const match of text.matchAll(ICON_REGEX)) {
const [matched, icon] = match;

// Additional validation to prevent matching within patterns
const beforeChar = match.index > 0 ? text[match.index - 1] : '';
const afterChar = match.index + matched.length < text.length
? text[match.index + matched.length]
: '';

// Skip if this looks like part of a pattern
if (beforeChar === ':' || beforeChar === 'T' || isDigit(beforeChar)
|| afterChar === ':' || isDigit(afterChar)) {
return idx + 1;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

what about the rest of the text? eg 00:00:00 and :icon: ?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

should :icon: be ignored?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I added tests for :icon: to work (literal) and 00:00:00 to fail.

Both are working.

Copy link
Contributor

@tripodsan tripodsan Feb 12, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I mean, the return here aborts the for loop over all matches. so for the pattern :foo:2 and :icon: the icon is not replaced. I'll add the test

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

als I don't think that :icon:2 should be ignored should be ignored.

}

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;
}

if (lastIdx && lastIdx <= text.length) {
// there is still some text left
const after = text.substring(lastIdx);
if (after) {
node.value = after;
Expand All @@ -77,6 +99,7 @@ export default function rewrite({ content }) {
idx -= 1;
}
}

return idx + 1;
});
}
19 changes: 18 additions & 1 deletion test/fixtures/content/icons-ignored.html
Original file line number Diff line number Diff line change
Expand Up @@ -5,5 +5,22 @@ <h1 id="icons">Icons</h1>
<pre><code>:rocket:</code></pre>
<p><a href="https://example.test/:urn:">https://example.test/:urn:</a></p>
<p>urn:aaid:sc:VA6C2:ac6066f3-fd1d-4e00-bed3-fa3aa6d981d8</p>
<p>:this is also ignored:</p>
<p>08:28:54</p>
<p>g) and 1:00-3:00 PM. H</p>
<p>6:00AM-6:00 PM MT</p>
<p>11:30am-12:30pm CT</p>
<p>c HEVC 4:2:2 10 bit</p>
<p>色情報が半分の4:2:2素材では、エッ</p>
<p>月1日木曜日14:00-19:00</p>
<p>Sec4:3-Sec4:6, Sec3:</p>
<p>ben Sie :1: ein, ge</p>
<p>168.0.52:4501:ssl</p>
<p>YYYY-MM-DDTHH:mm:ss.sssZ</p>
<p>1:test: should be ignored</p>
<p>test:2: should also be ignored</p>
<p>x1:icon: should be ignored</p>
<p>:icon:2 should be ignored</p>
<p>00:00:00</p>
</div>
</main>
</main>
34 changes: 34 additions & 0 deletions test/fixtures/content/icons-ignored.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,3 +9,37 @@
[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

1:test: should be ignored

test:2: should also be ignored

x1:icon: should be ignored

:icon:2 should be ignored

00:00:00
22 changes: 18 additions & 4 deletions test/fixtures/content/icons.html
Original file line number Diff line number Diff line change
@@ -1,9 +1,23 @@
<main>
<div>
<h1 id="icons">Icons</h1>
<p>Hello <span class="icon icon-button"></span></p>
<p>Hello <span class="icon icon-red"></span> banner.</p>
<p>Hello <span class="icon icon-check"></span> mark.</p>
<p>Hello<span class="icon icon-button"></span></p>
<p>Hello <span class="icon icon-red"></span>banner.</p>
<p>Hello <span class="icon icon-check"></span>mark.</p>
<p>Team<span class="icon icon-rocket"></span>blasting off again.</p>
<p>This is a <span class="icon icon-a-10-check"></span>that should work</p>
<p>number check<span class="icon icon-1-check"></span></p>
<p>number check<span class="icon icon-two-2-check"></span></p>
<p>number check<span class="icon icon-three-3-check"></span></p>
<p>number check<span class="icon icon-four4check"></span></p>
<p>number check<span class="icon icon-5-check"></span></p>
<p>number check<span class="icon icon-six-6000-check"></span></p>
<p>number check<span class="icon icon-seven7000check"></span></p>
<p>number check<span class="icon icon-8000check"></span></p>
<p>number check<span class="icon icon-nine9000"></span></p>
<p>number check<span class="icon icon-ten-10000"></span></p>
<p>number check<span class="icon icon-eleven-11"></span></p>
<p>number check<span class="icon icon-twelve-v1"></span></p>
<p>icon named<span class="icon icon-icon"></span></p>
</div>
</main>
</main>
28 changes: 28 additions & 0 deletions test/fixtures/content/icons.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,3 +7,31 @@ 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:
Loading