@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 +41 -0
- package/anyblock.ts +261 -0
- package/index.ts +5 -0
- package/jsdom_init.ts +30 -0
- package/package.json +36 -0
- package/styles.css +1204 -0
- package/tsconfig.json +33 -0
- package/vite.config.ts +38 -0
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
|
+
}
|