@anyblock/remark-any-block 1.0.0-beta1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md ADDED
@@ -0,0 +1,41 @@
1
+ # AnyBlock Remark Plugin
2
+
3
+ ## remark-any-block 使用
4
+
5
+ 详见 github *any-block* 组织下的 *Remark Demo* 仓库
6
+
7
+ 暂时不属于完全通用的 Remark 插件 (doing, 或者你自己改改), 当前该插件测试于 Quartz V4 版本
8
+
9
+ ## 构建
10
+
11
+ 构建就简单的 `pnpm build`
12
+
13
+ (仅自用) 构建、上传npm:
14
+
15
+ ```bash
16
+ # $ pnpm build # 设置了prepublishOnly,不需要先手动编译。但是你可以手动执行这一步,来检查编译是否正常
17
+
18
+ $ npm adduser # 先登录,在vscode里他会让我打开浏览器来登录
19
+ Username: ...
20
+ Password: ...
21
+
22
+ $ npm publish # 上传 (注意不要重名、npm账号可能需要邮箱验证)
23
+ # 如果设置了 package.json::script.prepublishOnly,会先执行 (一般是build)
24
+ # 这一步会将当前文件夹内容都上传到npm中名为 `<package.json 里 name@version>` 的包里
25
+ # 如果没有对应包,会自动创建
26
+
27
+ # or 或
28
+ $ npm publish --tag beta # 如果使用测试或beta版本 (包含 `-tagname`),如 `-beta`
29
+ # 需要 添加 `--tag <tagname>`,如 `--tag beta`
30
+ ```
31
+
32
+ > [!warning]
33
+ > 补丁: 2025-12-09 开始不再能使用长期验证方式,得用 `npm login`
34
+ >
35
+ > 否则报错:
36
+ > ```bash
37
+ > npm error code E403
38
+ > npm error 403 403 Forbidden - PUT https://registry.npmjs.org/... - Two-factor authentication or granular access token with bypass 2fa enabled is required to publish packages.
39
+ > ```
40
+ >
41
+ > 详见: https://github.blog/changelog/2025-12-09-npm-classic-tokens-revoked-session-based-auth-and-cli-token-management-now-available/
package/anyblock.ts ADDED
@@ -0,0 +1,261 @@
1
+ // import { unified } from 'unified';
2
+ // import remarkParse from 'remark-parse';
3
+ // import remarkRehype from 'remark-rehype';
4
+ // import rehypeStringify from 'rehype-stringify';
5
+
6
+ // import { remove } from "unist-util-remove"
7
+ import { Plugin } from "unified"
8
+ import { Root, RootContent, Paragraph, Text, Code, Html } from "mdast"
9
+ import type { VFile } from "vfile"
10
+ import { toMarkdown } from "mdast-util-to-markdown"
11
+ import { visit } from "unist-util-visit"
12
+
13
+ // 这里不想去依赖 Quartz 项目,所以用any。但是你可以去看具体的类型定义
14
+ // import { type QuartzTransformerPlugin } from "../types"
15
+ // import { type BuildCtx } from "../../util/ctx"
16
+ type QuartzTransformerPlugin = any
17
+ type BuildCtx = any
18
+
19
+ // 1. Markdown-it
20
+ import MarkdownIt from "markdown-it"
21
+ const md = new MarkdownIt({
22
+ html: true, // 启用 HTML 标签解析
23
+ breaks: true // 将换行符转换为 <br> 标签
24
+ })
25
+
26
+ // 2. AnyBlock
27
+ import { ABConvertManager } from "../ABConverter/ABConvertManager"
28
+ import { ABCSetting, ABReg } from "../ABConverter/ABReg"
29
+ import "../ABConverter/converter/abc_text"
30
+ import "../ABConverter/converter/abc_code"
31
+ import "../ABConverter/converter/abc_list"
32
+ import "../ABConverter/converter/abc_c2list"
33
+ import "../ABConverter/converter/abc_table"
34
+ import "../ABConverter/converter/abc_dir_tree"
35
+ import "../ABConverter/converter/abc_deco"
36
+ import "../ABConverter/converter/abc_ex"
37
+ import "../ABConverter/converter/abc_mdit_container"
38
+ import "../ABConverter/converter/abc_echarts"
39
+ import "../ABConverter/converter/abc_plantuml" // 可选建议:
40
+ import "../ABConverter/converter/abc_mermaid" // 可选建议:新版无额外依赖,旧版非 min 环境下 7.1MB
41
+ import "../ABConverter/converter/abc_markmap" // 可选建议:1.3MB
42
+
43
+ import { jsdom_init } from "./jsdom_init"
44
+ jsdom_init()
45
+
46
+ /**
47
+ * 大部分选项是为了与 markdown-it 版本保持一致而保留的。
48
+ * 目前这些选项未被使用,但已预留用于未来的行为切换。
49
+ */
50
+ export interface AnyBlockOptions {
51
+ // multiline: boolean;
52
+ // rowspan: boolean;
53
+ // headerless: boolean;
54
+ // multibody: boolean;
55
+ // autolabel: boolean;
56
+ }
57
+
58
+ /**
59
+ * 检测 `[header]` 段落
60
+ */
61
+ function matchAbHeader(node: RootContent): string | null {
62
+ if (node.type !== "paragraph") return null;
63
+ const text = (node.children as RootContent[])
64
+ .map((c) => (c.type === "text" ? (c as Text).value : ""))
65
+ .join("");
66
+ const m = text.match(ABReg.reg_header);
67
+ return m && m[1] ? m[1] : null;
68
+ }
69
+
70
+ /**
71
+ * 检测 `:::container` 段落
72
+ * 匹配时返回 `{flag, type}`
73
+ */
74
+ function matchContainerStart(node: RootContent):
75
+ | { flag: string; type: string }
76
+ | null {
77
+ if (node.type !== "paragraph") return null;
78
+ const text = (node.children as RootContent[])
79
+ .map((c) => (c.type === "text" ? (c as Text).value : ""))
80
+ .join("")
81
+ .trim();
82
+ const m = text.match(ABReg.reg_mdit_head);
83
+ if (!m || !m[3] || !m[4]) return null;
84
+ return { flag: m[3], type: m[4] };
85
+ }
86
+
87
+ /**
88
+ * 检测到一个 `:::` 结尾的段落
89
+ */
90
+ function isContainerEnd(node: RootContent, flag: string): boolean {
91
+ if (node.type !== "paragraph") return false;
92
+ const text = (node.children as RootContent[])
93
+ .map((c) => (c.type === "text" ? (c as Text).value : ""))
94
+ .join("")
95
+ .trim();
96
+ return text === flag;
97
+ }
98
+
99
+ /**
100
+ * 将一组 mdast 节点序列化为 markdown 格式
101
+ */
102
+ function nodesToMarkdown(nodes: RootContent[]): string {
103
+ return toMarkdown(
104
+ { type: "root", children: nodes } as unknown as Root,
105
+ { listItemIndent: "one" }
106
+ ).trimEnd();
107
+ }
108
+
109
+ /**
110
+ * remark 插件: 将任何特殊的 AnyBlock 语法转换为带有 `lang = "AnyBlock"` 属性的“代码”节点。
111
+ *
112
+ * - `[header]` + (list|heading|code|blockquote|table...)
113
+ * - `:::type ... :::`
114
+ */
115
+ export const remark_anyblock_to_codeblock: Plugin<[Partial<AnyBlockOptions>?], Root> =
116
+ (_options = {}) =>
117
+ (tree) => {
118
+ const out: RootContent[] = [];
119
+ const children = [...tree.children] as RootContent[];
120
+
121
+ for (let i = 0; i < children.length; i++) {
122
+ const node = children[i];
123
+
124
+ // --- square-inline header flow ---
125
+ const header = matchAbHeader(node);
126
+ if (header) {
127
+ const body: RootContent[] = [];
128
+ let j = i + 1;
129
+ for (; j < children.length; j++) {
130
+ const n = children[j];
131
+ if (
132
+ n.type === "list" ||
133
+ n.type === "heading" ||
134
+ n.type === "code" ||
135
+ n.type === "blockquote" ||
136
+ n.type === "table"
137
+ ) {
138
+ body.push(n);
139
+ continue;
140
+ }
141
+ // stop when first non-matching block is hit
142
+ break;
143
+ }
144
+ if (body.length > 0) {
145
+ const codeValue = `[${header}]\n${nodesToMarkdown(body)}`;
146
+ out.push({
147
+ type: "code",
148
+ lang: "AnyBlock",
149
+ value: codeValue,
150
+ data: { markup: "[]" },
151
+ } as Code);
152
+ i = j - 1;
153
+ continue;
154
+ }
155
+ }
156
+
157
+ // --- ::: container flow ---
158
+ const container = matchContainerStart(node);
159
+ if (container) {
160
+ const body: RootContent[] = [];
161
+ let j = i + 1;
162
+ for (; j < children.length; j++) {
163
+ const n = children[j];
164
+ if (isContainerEnd(n, container.flag)) {
165
+ break;
166
+ }
167
+ body.push(n);
168
+ }
169
+ if (j < children.length) {
170
+ const codeValue = `[${container.type}]\n${nodesToMarkdown(body)}`;
171
+ out.push({
172
+ type: "code",
173
+ lang: "AnyBlock",
174
+ value: codeValue,
175
+ data: { markup: container.flag },
176
+ } as Code);
177
+ i = j; // skip closing line
178
+ continue;
179
+ }
180
+ }
181
+
182
+ // default passthrough
183
+ out.push(node);
184
+ }
185
+
186
+ (tree as Root).children = out;
187
+ }
188
+
189
+ // 渲染 anyblock 代码块
190
+ const remark_anyblock_render_codeblock = () => {
191
+ return (tree: Root, _file: VFile) => {
192
+ visit(tree, "code", (node: Code, index: number|undefined, parent: any|undefined) => { // 遍历所有的 code 类型节点
193
+ console.log("\nanyblock codeblock transformer visit:", node)
194
+ if (node.lang != "anyblock") return
195
+ if (!parent || !index) return
196
+
197
+ const lines = node.value.split("\n")
198
+ const head = lines.shift()
199
+ const headerMatch = head?.match(/\[(.*)\]/)
200
+ if (!headerMatch) return
201
+
202
+ const header = headerMatch[1];
203
+ const content = lines.join("\n").trimStart();
204
+ const markup = (node.data as any)?.markup ?? ""
205
+
206
+ const el: HTMLDivElement = document.createElement("div");
207
+ el.classList.add("ab-note", "drop-shadow");
208
+ ABConvertManager.autoABConvert(el, header, content, markup.startsWith(":::") ? "mdit" : "");
209
+
210
+ // new node
211
+ console.log("\nanyblock codeblock transformer visit2:", header, 'c==', content)
212
+ console.log("\nanyblock codeblock transformer visit3:", el.outerHTML, el)
213
+ const new_node: Html = {
214
+ type: 'html',
215
+ value: el.outerHTML,
216
+ }
217
+ parent.children.splice(index, 1, new_node)
218
+ // return 'skip'
219
+ })
220
+ }
221
+ }
222
+
223
+ {
224
+ // 定义环境条件
225
+ ABCSetting.env = "markdown-it"; // remark
226
+
227
+ // 定义默认渲染行为 // [!code hl] risk 同步适配异步,可能会存在问题
228
+ ABConvertManager.getInstance().redefine_renderMarkdown((markdown: string, el: HTMLElement):void => {
229
+ el.classList.add("markdown-rendered")
230
+ // el.textContent = markdown.replace('\n', '<br>'); return; // TODO 临时
231
+
232
+ // 只能阻塞
233
+ // const file = unified()
234
+ // .use(remarkParse)
235
+ // .use(remarkRehype)
236
+ // .use(rehypeStringify);
237
+ // const file2: VFile = await file.process(markdown);
238
+ // const result: string = String(file);
239
+
240
+ const result = md.render(markdown);
241
+
242
+ // console.log('re-renderMarkdown:', markdown, '===>', result)
243
+ const el_child = document.createElement("div"); el.appendChild(el_child); el_child.innerHTML = result;
244
+ })
245
+ }
246
+
247
+ // 这是 Quartz 的 Transformer 插件定义
248
+ export const transformer_anyblock: QuartzTransformerPlugin = (/*options: any*/) => {
249
+ return {
250
+ name: "AnyBlock",
251
+ markdownPlugins(_ctx: BuildCtx) {
252
+ return [
253
+ // remark_anyblock_to_codeblock,
254
+ remark_anyblock_render_codeblock, // last
255
+ ]
256
+ },
257
+ htmlPlugins(_ctx: BuildCtx) {
258
+ return []
259
+ }
260
+ }
261
+ }
package/index.ts ADDED
@@ -0,0 +1,5 @@
1
+ // JsDom。仅用于提供document对象支持 (如果Ob环境中则不需要,用ob自带document对象的)
2
+ export { jsdom_init } from './jsdom_init'
3
+ export { transformer_anyblock } from './anyblock'
4
+ export { abConvertEvent } from '../ABConverter/ABConvertEvent'
5
+ export { ABConvertManager } from '../ABConverter/ABConvertManager' // for client
package/jsdom_init.ts ADDED
@@ -0,0 +1,30 @@
1
+ /**
2
+ * 初始化JSDOM
3
+ *
4
+ * JsDom。仅用于提供document对象支持 (如果Ob等客户端渲染环境中则不需要,服务端渲染则需要)
5
+ */
6
+
7
+ // import jsdom from "jsdom"
8
+
9
+ export async function jsdom_init() {
10
+ const { default: jsdom } = await import('jsdom') // 废弃,要同步,避免docuemnt初始化不及时
11
+ const { JSDOM } = jsdom
12
+ const dom = new JSDOM(`<!DOCTYPE html><html><body></body></html>`, {
13
+ url: 'http://localhost/', // @warn 若缺少该行,则在mdit+build环境下,编译报错
14
+ });
15
+ global.Storage = dom.window.Storage;
16
+ global.window = dom.window as any
17
+ global.history = dom.window.history // @warn 若缺少该行,则在mdit+build环境下,编译报错:ReferenceError: history is not defined
18
+ global.document = dom.window.document
19
+ global.Node = dom.window.Node
20
+ global.NodeList = dom.window.NodeList
21
+ global.HTMLElement = dom.window.HTMLElement
22
+ global.HTMLDivElement = dom.window.HTMLDivElement
23
+ global.HTMLPreElement = dom.window.HTMLPreElement
24
+ global.HTMLQuoteElement = dom.window.HTMLQuoteElement
25
+ global.HTMLTableElement = dom.window.HTMLTableElement
26
+ global.HTMLUListElement = dom.window.HTMLUListElement
27
+ global.HTMLScriptElement = dom.window.HTMLScriptElement
28
+ dom.window.scrollTo = ()=>{} // @warn 若缺少该行,编译警告:Error: Not implemented: window.scrollTo
29
+ global.MutationObserver = dom.window.MutationObserver
30
+ }
package/package.json ADDED
@@ -0,0 +1,36 @@
1
+ {
2
+ "name": "@anyblock/remark-any-block",
3
+ "version": "1.0.0-beta1",
4
+ "description": "You can flexibility to create a 'Block' by many means. It also provides many useful features, like `list to table`.",
5
+ "main": "index.js",
6
+ "publishConfig": {
7
+ "access": "public"
8
+ },
9
+ "scripts": {
10
+ "test": "echo \"Error: no test specified\" && exit 1",
11
+ "build": "pnpm build_vite",
12
+ "build_vite": "pnpm copy_abc_style && vite build",
13
+ "prepublishOnly": "pnpm build",
14
+ "copy_abc_style": "copyfiles --flat ../../src/ABConverter/style/styles.css ./"
15
+ },
16
+ "keywords": [],
17
+ "author": "",
18
+ "license": "ISC",
19
+ "packageManager": "pnpm@10.10.0",
20
+ "dependencies": {
21
+ "markdown-it": "^14.1.0",
22
+ "mdast": "^3.0.0",
23
+ "mdast-util-to-markdown": "^2.1.2",
24
+ "tslib": "^2.8.1",
25
+ "unified": "^11.0.5",
26
+ "unist-util-visit": "^5.0.0",
27
+ "vfile": "^6.0.3"
28
+ },
29
+ "devDependencies": {
30
+ "@types/jsdom": "^21.1.7",
31
+ "@types/markdown-it": "^14.1.2",
32
+ "copyfiles": "^2.4.1",
33
+ "typescript": "^5.9.3",
34
+ "vite": "^7.2.7"
35
+ }
36
+ }