Skip to content

Commit 8554971

Browse files
committed
mdast
1 parent 4829fb1 commit 8554971

File tree

6 files changed

+79
-102
lines changed

6 files changed

+79
-102
lines changed

README.md

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,10 @@
11
# Remark CorePass
22

3-
This Remark plugin, "remark-corepass," is designed to transform CorePass notations into markdown links, enhancing the integration of blockchain-based identifiers and Fediverse handles within markdown content.
3+
This Remark plugin, "remark-corepass," is designed to transform CorePass notations into Markdown links (when positively checked) and negated text (when negatively checked), enhancing the integration of CorePass identifiers (Core ID) within markdown content.
4+
5+
## About Core ID
6+
7+
Core ID is a unique identifier used to reference Core Assets, such as documents, images, videos, and other digital assets, within the [CorePass ecosystem](https://corepass.net). The Core ID is composed of an [ICAN (International Crypto Asset Number)](https://cip.coreblockchain.net/sk-SK/cip/cbc/cip-100) and an checksum, which can be used to validate the identifier.
48

59
## Installation
610

@@ -18,7 +22,7 @@ yarn add remark-corepass
1822

1923
## Usage
2024

21-
Integrate the plugin into your Remark processing pipeline to automatically convert CorePass notations and optionally validate ICAN (International Core Asset Number) identifiers:
25+
Integrate the plugin into your Remark processing pipeline to automatically convert CorePass notations and optionally validate ICAN (International Crypto Asset Number) identifiers:
2226

2327
```typescript
2428
import remark from 'remark';
@@ -36,7 +40,7 @@ import remarkCorepass from 'remark-corepass';
3640
})();
3741
```
3842

39-
The plugin searches for CorePass notations in the format `[domain@coreid]` or `[!cb1234...@coreid]` in your markdown content, converting them into clickable links and validating ICAN identifiers when enabled.
43+
The plugin searches for CorePass notations in the format `[domain@coreid]` or `[!cb1234...@coreid]` in your markdown content, converting them to links and optionally validating ICAN identifiers, displaying invalid items as text.
4044

4145
## Options
4246

dist/index.d.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
1-
import { type Node } from 'unist';
1+
import { Root } from 'mdast';
22
interface CorepassOptions {
33
enableIcanCheck?: boolean;
44
enableSkippingIcanCheck?: boolean;
55
}
6-
export default function remarkCorepass(options?: CorepassOptions): (ast: Node) => void;
6+
export default function remarkCorepass(options?: CorepassOptions): (tree: Root) => void;
77
export {};

dist/index.js

Lines changed: 23 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -12,54 +12,52 @@ const makeTextNode = (text) => ({
1212
});
1313
const shortenId = (hash) => `${hash.slice(0, 4)}${hash.slice(-4)}`;
1414
const corepassPattern = /\[(!?)(((cb|ab|ce)[0-9]{2}[0-9a-f]{40})|((?:[a-z0-9_-]|\p{Emoji})+(?:\.(?:[a-z0-9_-]|\p{Emoji})+)*\.([a-z0-9]+)))@coreid\]/giu;
15-
function isTextNode(node) {
16-
return node.type === 'text';
17-
}
15+
const isTextNode = (node) => node.type === 'text';
1816
export default function remarkCorepass(options = {}) {
1917
const defaults = {
2018
enableIcanCheck: true,
2119
enableSkippingIcanCheck: true,
2220
};
2321
const finalOptions = { ...defaults, ...options };
24-
const transformer = (ast) => {
25-
visit(ast, 'text', (node, index, parent) => {
22+
return (tree) => {
23+
visit(tree, 'text', (node, index, parent) => {
2624
if (!isTextNode(node) || !parent || typeof index !== 'number')
2725
return;
28-
const parentNode = parent;
29-
let newNodes = [];
26+
if (!('children' in parent) || !Array.isArray(parent.children))
27+
return;
28+
const matches = Array.from(node.value.matchAll(corepassPattern));
29+
const newNodes = [];
3030
let lastIndex = 0;
31-
const textNode = node;
32-
textNode.value.replace(corepassPattern, (match, skip, fullId, cpId, net0, sld, tld, offset) => {
33-
if (offset > lastIndex) {
34-
newNodes.push(makeTextNode(textNode.value.slice(lastIndex, offset)));
31+
matches.forEach(match => {
32+
const [fullMatch, skip, fullId, cpId, net0, sld, tld, offset = 0] = match;
33+
const actualOffset = parseInt(offset, 10);
34+
if (actualOffset > lastIndex) {
35+
newNodes.push(makeTextNode(node.value.slice(lastIndex, actualOffset)));
3536
}
36-
let id = fullId;
37-
let willSkip = (finalOptions.enableSkippingIcanCheck) ? ((skip === '!') ? true : false) : false;
38-
let displayName, fullName;
37+
const willSkip = finalOptions.enableSkippingIcanCheck && skip === '!';
38+
const url = `corepass:${fullId.toLowerCase()}`;
39+
let fullName, displayName;
3940
if (cpId !== '' && cpId !== undefined) {
40-
fullName = id.toUpperCase();
41+
fullName = fullId.toUpperCase();
4142
displayName = shortenId(fullName);
42-
if (finalOptions.enableIcanCheck && !willSkip && !Ican.isValid(id, true)) {
43+
if (finalOptions.enableIcanCheck && !willSkip && !Ican.isValid(fullId, true)) {
4344
newNodes.push(makeTextNode(${displayName}@coreid`));
4445
}
4546
else {
46-
newNodes.push(makeLinkNode(`corepass:${id.toLowerCase()}`, `${displayName}@coreid`, fullName));
47+
newNodes.push(makeLinkNode(url, `${displayName}@coreid`, fullName));
4748
}
4849
}
4950
else {
50-
displayName = `${id}`;
51-
newNodes.push(makeLinkNode(`corepass:${id.toLowerCase()}`, `${displayName}@coreid`, displayName));
51+
newNodes.push(makeLinkNode(url, `${fullId}@coreid`, fullId));
5252
}
53-
lastIndex = offset + match.length;
54-
return '';
53+
lastIndex = actualOffset + fullMatch.length;
5554
});
56-
if (lastIndex < textNode.value.length) {
57-
newNodes.push(makeTextNode(textNode.value.slice(lastIndex)));
55+
if (lastIndex < node.value.length) {
56+
newNodes.push(makeTextNode(node.value.slice(lastIndex)));
5857
}
5958
if (newNodes.length > 0) {
60-
parentNode.children.splice(index, 1, ...newNodes);
59+
parent.children.splice(index, 1, ...newNodes);
6160
}
6261
});
6362
};
64-
return transformer;
6563
}

package.json

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "remark-corepass",
3-
"version": "0.1.9",
3+
"version": "0.2.0",
44
"description": "A Remark plugin to transform CorePass notations into markdown links.",
55
"main": "dist/index.js",
66
"types": "dist/index.d.ts",
@@ -42,13 +42,14 @@
4242
"unist-util-visit": "^5.0.0"
4343
},
4444
"devDependencies": {
45+
"@types/mdast": "^4.0.3",
4546
"@types/node": "^20.12.7",
4647
"esm": "^3.2.25",
4748
"remark-parse": "^11.0.0",
4849
"remark-stringify": "^11.0.0",
4950
"ts-node": "^10.9.2",
5051
"typescript": "^5.4.5",
51-
"undici-types": "^6.13.0",
52+
"undici-types": "^6.14.1",
5253
"unified": "^11.0.4",
5354
"uvu": "^0.5.6"
5455
},

src/index.ts

Lines changed: 42 additions & 68 deletions
Original file line numberDiff line numberDiff line change
@@ -1,42 +1,20 @@
1-
import { type Node } from 'unist';
1+
import { Link, Node, Parent, Root, RootContent, Text } from 'mdast';
22
import { visit } from 'unist-util-visit';
33
import Ican from '@blockchainhub/ican';
44

55
interface CorepassOptions {
6-
/**
7-
* Enable ICAN check for CorePass.
8-
*/
96
enableIcanCheck?: boolean;
10-
/**
11-
* Enable skipping ICAN check with sign "!".
12-
*/
137
enableSkippingIcanCheck?: boolean;
148
}
159

16-
interface ParentNode extends Node {
17-
children: Node[];
18-
}
19-
20-
interface LinkNode extends Node {
21-
type: 'link';
22-
url: string;
23-
title: string | null;
24-
children: Array<TextNode>;
25-
}
26-
27-
interface TextNode extends Node {
28-
type: 'text';
29-
value: string;
30-
}
31-
32-
const makeLinkNode = (url: string, text: string, title?: string): LinkNode => ({
10+
const makeLinkNode = (url: string, text: string, title?: string): Link => ({
3311
type: 'link',
3412
url,
3513
title: title || null,
3614
children: [{ type: 'text', value: text }],
3715
});
3816

39-
const makeTextNode = (text: string): TextNode => ({
17+
const makeTextNode = (text: string): Text => ({
4018
type: 'text',
4119
value: text,
4220
});
@@ -45,70 +23,66 @@ const shortenId = (hash: string) => `${hash.slice(0, 4)}…${hash.slice(-4)}`;
4523

4624
const corepassPattern = /\[(!?)(((cb|ab|ce)[0-9]{2}[0-9a-f]{40})|((?:[a-z0-9_-]|\p{Emoji})+(?:\.(?:[a-z0-9_-]|\p{Emoji})+)*\.([a-z0-9]+)))@coreid\]/giu;
4725

48-
function isTextNode(node: Node): node is TextNode {
49-
return node.type === 'text';
50-
}
26+
const isTextNode = (node: Node): node is Text => node.type === 'text';
5127

52-
/**
53-
* A remark plugin to parse CorePass IDs (Core ID) and convert them to links.
54-
* @param options - Options for the CorePass plugin.
55-
* @returns A transformer for the AST.
56-
*/
57-
export default function remarkCorepass(options: CorepassOptions = {}): (ast: Node) => void {
28+
export default function remarkCorepass(options: CorepassOptions = {}): (tree: Root) => void {
5829
const defaults: CorepassOptions = {
59-
enableIcanCheck: true, // Enable Ican check for CorePass
60-
enableSkippingIcanCheck: true, // Enable skipping Ican check with sign "!"
30+
enableIcanCheck: true,
31+
enableSkippingIcanCheck: true,
6132
};
6233
const finalOptions = { ...defaults, ...options };
6334

64-
const transformer = (ast: Node): void => {
65-
visit(ast, 'text', (node, index, parent) => {
35+
return (tree: Node): void => {
36+
visit(tree, 'text', (node: Text, index: number | undefined, parent: Parent | undefined) => {
6637
if (!isTextNode(node) || !parent || typeof index !== 'number') return;
67-
const parentNode: ParentNode = parent as ParentNode;
68-
let newNodes: Node[] = [];
69-
let lastIndex = 0;
38+
if (!('children' in parent) || !Array.isArray((parent as Parent).children)) return;
7039

71-
const textNode: TextNode = node as TextNode;
40+
const matches = Array.from(node.value.matchAll(corepassPattern));
41+
const newNodes: RootContent[] = [];
7242

73-
textNode.value.replace(corepassPattern, (
74-
match: string,
75-
skip: string,
76-
fullId: string,
77-
cpId: string,
78-
net0: string,
79-
sld: string,
80-
tld: string,
81-
offset: number
82-
) => {
83-
if (offset > lastIndex) {
84-
newNodes.push(makeTextNode(textNode.value.slice(lastIndex, offset)));
43+
let lastIndex = 0;
44+
matches.forEach(match => {
45+
const [
46+
fullMatch,
47+
skip,
48+
fullId,
49+
cpId,
50+
net0,
51+
sld,
52+
tld,
53+
offset = 0
54+
] = match;
55+
const actualOffset = parseInt(offset as any, 10);
56+
if (actualOffset > lastIndex) {
57+
newNodes.push(makeTextNode(node.value.slice(lastIndex, actualOffset)));
8558
}
86-
let id = fullId as string;
87-
let willSkip = (finalOptions.enableSkippingIcanCheck) ? ((skip === '!') ? true : false) : false;
88-
let displayName, fullName;
59+
60+
const willSkip = finalOptions.enableSkippingIcanCheck && skip === '!';
61+
const url = `corepass:${fullId.toLowerCase()}`;
62+
let fullName, displayName;
63+
8964
if (cpId !== '' && cpId !== undefined) {
90-
fullName = id.toUpperCase();
65+
fullName = fullId.toUpperCase();
9166
displayName = shortenId(fullName);
92-
if (finalOptions.enableIcanCheck && !willSkip && !Ican.isValid(id, true)) {
67+
if (finalOptions.enableIcanCheck && !willSkip && !Ican.isValid(fullId, true)) {
9368
newNodes.push(makeTextNode(${displayName}@coreid`));
9469
} else {
95-
newNodes.push(makeLinkNode(`corepass:${id.toLowerCase()}`, `${displayName}@coreid`, fullName));
70+
newNodes.push(makeLinkNode(url, `${displayName}@coreid`, fullName));
9671
}
9772
} else {
98-
displayName = `${id}`;
99-
newNodes.push(makeLinkNode(`corepass:${id.toLowerCase()}`, `${displayName}@coreid`, displayName));
73+
newNodes.push(makeLinkNode(url, `${fullId}@coreid`, fullId));
10074
}
101-
lastIndex = offset + match.length;
102-
return '';
75+
76+
lastIndex = actualOffset + fullMatch.length;
10377
});
10478

105-
if (lastIndex < textNode.value.length) {
106-
newNodes.push(makeTextNode(textNode.value.slice(lastIndex)));
79+
if (lastIndex < node.value.length) {
80+
newNodes.push(makeTextNode(node.value.slice(lastIndex)));
10781
}
82+
10883
if (newNodes.length > 0) {
109-
parentNode.children.splice(index, 1, ...newNodes);
84+
parent.children.splice(index, 1, ...newNodes);
11085
}
11186
});
11287
};
113-
return transformer;
11488
}

types/index.d.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
1-
import { Node } from 'unist';
1+
import { Root } from 'mdast';
22

33
declare module 'remark-corepass' {
44
interface CorepassOptions {
55
enableIcanCheck?: boolean;
66
enableSkippingIcanCheck?: boolean;
77
}
88

9-
export default function remarkCorepass(options?: CorepassOptions): (ast: Node) => void;
9+
export default function remarkCorepass(options?: CorepassOptions): (ast: Root) => void;
1010
}

0 commit comments

Comments
 (0)