@linloir/limr 0.1.0 → 1.0.0
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.en.md +123 -0
- package/README.md +63 -138
- package/dist/base.css +10 -7
- package/dist/{chunk-65V5OZ4G.js → chunk-3ILUZKDB.js} +77 -17
- package/dist/chunk-3ILUZKDB.js.map +1 -0
- package/dist/{chunk-ETHLPBKQ.js → chunk-N77VD2YR.js} +96 -51
- package/dist/chunk-N77VD2YR.js.map +1 -0
- package/dist/{chunk-7JF5HHVF.js → chunk-TTISGBST.js} +14 -1
- package/dist/{chunk-7JF5HHVF.js.map → chunk-TTISGBST.js.map} +1 -1
- package/dist/core/index.d.ts +6 -3
- package/dist/core/index.js +3 -1
- package/dist/extensions/index.d.ts +2 -2
- package/dist/extensions/index.js +2 -2
- package/dist/index.d.ts +2 -2
- package/dist/index.js +7 -3
- package/dist/react/index.d.ts +4 -3
- package/dist/react/index.js +5 -3
- package/dist/styles.css +10 -7
- package/dist/{translations-BliabrPd.d.ts → translations-DHnYMg4h.d.ts} +11 -3
- package/docs/README.md +13 -0
- package/docs/api.md +5 -158
- package/docs/assets/rendered.svg +9 -0
- package/docs/assets/source.svg +9 -0
- package/docs/en/README.md +26 -0
- package/docs/en/ai-skills.md +57 -0
- package/docs/en/annotations.md +84 -0
- package/docs/en/api.md +107 -0
- package/docs/en/architecture.md +33 -0
- package/docs/en/extensions.md +159 -0
- package/docs/en/getting-started.md +67 -0
- package/docs/en/integration.md +130 -0
- package/docs/en/modules.md +80 -0
- package/docs/en/performance.md +53 -0
- package/docs/en/plugin-packages.md +68 -0
- package/docs/en/styles.md +80 -0
- package/docs/en/syntax.md +352 -0
- package/docs/extensions.md +5 -306
- package/docs/implementation.md +5 -76
- package/docs/performance.md +5 -81
- package/docs/screenshots/blocks.png +0 -0
- package/docs/screenshots/dark.png +0 -0
- package/docs/screenshots/desktop.png +0 -0
- package/docs/screenshots/plugin-mobile.png +0 -0
- package/docs/styles.md +5 -100
- package/docs/visual-upgrade.md +5 -145
- package/docs/zh-CN/README.md +26 -0
- package/docs/zh-CN/ai-skills.md +57 -0
- package/docs/zh-CN/annotations.md +84 -0
- package/docs/zh-CN/api.md +107 -0
- package/docs/zh-CN/architecture.md +33 -0
- package/docs/zh-CN/extensions.md +159 -0
- package/docs/zh-CN/getting-started.md +67 -0
- package/docs/zh-CN/integration.md +130 -0
- package/docs/zh-CN/modules.md +80 -0
- package/docs/zh-CN/performance.md +53 -0
- package/docs/zh-CN/plugin-packages.md +68 -0
- package/docs/zh-CN/styles.md +80 -0
- package/docs/zh-CN/syntax.md +352 -0
- package/examples/notes.tsx +18 -11
- package/examples/packages/paper-theme/index.css +25 -0
- package/examples/packages/paper-theme/package.json +7 -0
- package/examples/packages/reading-tools/index.d.ts +2 -0
- package/examples/packages/reading-tools/index.js +25 -0
- package/examples/packages/reading-tools/package.json +16 -0
- package/package.json +9 -5
- package/skills/limr-plugin-development/SKILL.md +22 -0
- package/skills/limr-plugin-development/agents/openai.yaml +4 -0
- package/skills/limr-plugin-development/assets/plugin-template/README.md +30 -0
- package/skills/limr-plugin-development/assets/plugin-template/package.json +36 -0
- package/skills/limr-plugin-development/assets/plugin-template/src/index.tsx +50 -0
- package/skills/limr-plugin-development/assets/plugin-template/src/styles.css +9 -0
- package/skills/limr-plugin-development/assets/plugin-template/tsconfig.json +20 -0
- package/skills/limr-plugin-development/assets/plugin-template/tsup.config.ts +8 -0
- package/skills/limr-plugin-development/references/contract.md +45 -0
- package/skills/limr-use/SKILL.md +23 -0
- package/skills/limr-use/agents/openai.yaml +4 -0
- package/skills/limr-use/references/integration.md +41 -0
- package/skills/limr-use/references/syntax.md +17 -0
- package/dist/chunk-65V5OZ4G.js.map +0 -1
- package/dist/chunk-ETHLPBKQ.js.map +0 -1
|
@@ -0,0 +1,352 @@
|
|
|
1
|
+
# 默认语法
|
|
2
|
+
|
|
3
|
+
> 阅读提示:本文包含 LiMR 扩展语法和交互示例。部分内容在 GitHub 等普通 Markdown 阅读器中无法完整展示;前往 [GitHub Pages 文档](https://linloir.github.io/LiMR/#/docs/zh-CN/syntax) 可获得最佳阅读体验。
|
|
4
|
+
|
|
5
|
+
[文档目录](README.md) · [English](../en/syntax.md)
|
|
6
|
+
|
|
7
|
+
**模块**是为 LiMR 增加一组能力的配置对象,例如公式、视频或引用。下面的“创建函数”在应用代码中调用,返回模块对象;“模块名”是对象的 `name` 字段,用于替换和移除;“标签 / 指令”才是写在 Markdown 正文中的语法。完整概念和配置方式见[内置模块](modules.md)。
|
|
8
|
+
|
|
9
|
+
`@linloir/limr` 随包提供以下十个模块。`Reader`、独立 `Markdown` 和 `DocumentProvider` 默认通过 `standard()` 全部启用;纯核心 `createDocument()` 默认只有 CommonMark。下面每节给出源码和实际效果,交互示例由 LiMR 自己渲染。
|
|
10
|
+
|
|
11
|
+
## 模块清单
|
|
12
|
+
|
|
13
|
+
| 创建函数(应用代码) | 模块名(name) | 默认能力 | 注册的特殊标签 / 指令 |
|
|
14
|
+
| --- | --- | --- | --- |
|
|
15
|
+
| `gfm()` | `gfm` | 表格、删除线、任务列表、自动链接、标准脚注 | 无 |
|
|
16
|
+
| `math()` | `math` | 行内与块级公式 | `$…$`、`$$…$$` |
|
|
17
|
+
| `codeHighlight()` | `code-highlight` | Shiki 围栏代码高亮 | 标准代码围栏 |
|
|
18
|
+
| `definition()` | `definition` | 释义 | 标签 `definition`、`title`、`content`;指令 `definition` |
|
|
19
|
+
| `question()` | `question` | 练习题与答案 | 标签 `question` 及下文子标签;指令 `question` |
|
|
20
|
+
| `extras()` | `extras` | 拓展阅读 | 标签 `extras`、`extra`;指令 `extras` |
|
|
21
|
+
| `video()` | `video` | 原生视频、YouTube、Bilibili | 标签 `video`、`md-video`;指令 `video` |
|
|
22
|
+
| `figures()` | `figures` | 图组与图注 | 标签 `figures`、`figure`、`figcaption`、`caption`、`img`;指令 `figures`、`figure`、`figcaption` |
|
|
23
|
+
| `citation()` | `citation` | 引用定义与角标 | 标签 `citation`、`ref`;不注册指令 |
|
|
24
|
+
| `translations()` | `translations` | 原文、译文与对照 | 标签及指令 `translations`、`origin`、`translation` |
|
|
25
|
+
|
|
26
|
+
这些是 Markdown 中允许的静态标签,不是任意 MDX/HTML 执行环境。使用字符串属性,如 `multi="true"`、`answer="[0,2]"`;不要使用 `answer={[0,2]}`、事件函数或展开属性。未注册 HTML 按文本显示;表达式不执行并可能产生诊断。嵌套块的标签和正文之间保留空行。
|
|
27
|
+
|
|
28
|
+
## CommonMark 与 GFM
|
|
29
|
+
|
|
30
|
+
````markdown
|
|
31
|
+
# 标题
|
|
32
|
+
|
|
33
|
+
**粗体**、*斜体*、~~删除线~~、[链接](https://example.com)、`行内代码`。
|
|
34
|
+
|
|
35
|
+
> 引用块,默认界面标题为 Note / 注释。
|
|
36
|
+
|
|
37
|
+
- 普通列表
|
|
38
|
+
- [x] 已完成
|
|
39
|
+
- [ ] 未完成
|
|
40
|
+
|
|
41
|
+
| 名称 | 说明 |
|
|
42
|
+
| --- | --- |
|
|
43
|
+
| LiMR | Markdown renderer |
|
|
44
|
+
|
|
45
|
+
```typescript
|
|
46
|
+
const value = 42;
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
标准脚注[^note]。
|
|
50
|
+
|
|
51
|
+
[^note]: 出现在文末的脚注内容。
|
|
52
|
+
````
|
|
53
|
+
|
|
54
|
+
实际效果(可交互):
|
|
55
|
+
|
|
56
|
+
#### 标题示例
|
|
57
|
+
|
|
58
|
+
**粗体**、*斜体*、~~删除线~~、[链接](https://example.com)、`行内代码`。
|
|
59
|
+
|
|
60
|
+
> 引用块,默认界面标题为 Note / 注释。
|
|
61
|
+
|
|
62
|
+
- 普通列表
|
|
63
|
+
- [x] 已完成
|
|
64
|
+
- [ ] 未完成
|
|
65
|
+
|
|
66
|
+
| 名称 | 说明 |
|
|
67
|
+
| --- | --- |
|
|
68
|
+
| LiMR | Markdown renderer |
|
|
69
|
+
|
|
70
|
+
```typescript
|
|
71
|
+
const value = 42;
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
标准脚注[^syntax-note]。
|
|
75
|
+
|
|
76
|
+
[^syntax-note]: 出现在文末的脚注内容。
|
|
77
|
+
|
|
78
|
+
标准 `[^note]` 是文末脚注,`<ref>` 是标注卡片,两者不同。图片 `` 由普通 Markdown 支持。原始 HTML 不透传到 DOM;需要自定义外观时注册扩展或覆盖 `components`。
|
|
79
|
+
|
|
80
|
+
## 公式与代码
|
|
81
|
+
|
|
82
|
+
```markdown
|
|
83
|
+
行内公式:$E = mc^2$。
|
|
84
|
+
|
|
85
|
+
$$
|
|
86
|
+
\int_0^1 x^2\,dx = \frac{1}{3}
|
|
87
|
+
$$
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
实际效果(可交互):
|
|
91
|
+
|
|
92
|
+
行内公式:$E = mc^2$。
|
|
93
|
+
|
|
94
|
+
$$
|
|
95
|
+
\int_0^1 x^2\,dx = \frac{1}{3}
|
|
96
|
+
$$
|
|
97
|
+
|
|
98
|
+
公式需要导入 `@linloir/limr/math.css`;支持通过 `math({ macros, strict })` 配置。代码围栏语言用于 Shiki 高亮。默认接近视口才加载高亮,流式代码暂停更新 180ms 后尝试高亮;超过默认 100,000 字符、未支持或未在允许列表中的语言保留可读源码。`codeHighlight({ languages, lightTheme, darkTheme, eager, maxLength })` 可配置。
|
|
99
|
+
|
|
100
|
+
## 释义
|
|
101
|
+
|
|
102
|
+
```markdown
|
|
103
|
+
<definition title="熵">
|
|
104
|
+
|
|
105
|
+
表示不确定性的量:$H(X)$。
|
|
106
|
+
|
|
107
|
+
</definition>
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
实际效果(可交互):
|
|
111
|
+
|
|
112
|
+
<definition title="熵">
|
|
113
|
+
|
|
114
|
+
表示不确定性的量:$H(X)$。
|
|
115
|
+
|
|
116
|
+
</definition>
|
|
117
|
+
|
|
118
|
+
富文本标题和指定正文可以使用子标签:
|
|
119
|
+
|
|
120
|
+
```markdown
|
|
121
|
+
<definition>
|
|
122
|
+
|
|
123
|
+
<title>**Entropy** / 熵</title>
|
|
124
|
+
|
|
125
|
+
<content>
|
|
126
|
+
|
|
127
|
+
正文支持 Markdown。
|
|
128
|
+
|
|
129
|
+
</content>
|
|
130
|
+
|
|
131
|
+
</definition>
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
`title` 属性按文字渲染;富文本用子标签。也可以写 `:::definition{title="熵"}`,以 `:::` 结束。
|
|
135
|
+
|
|
136
|
+
## 练习题
|
|
137
|
+
|
|
138
|
+
```markdown
|
|
139
|
+
<question title="哪些是偶数?">
|
|
140
|
+
|
|
141
|
+
<question-content>请选择所有正确选项。</question-content>
|
|
142
|
+
|
|
143
|
+
<question-sel-grp multi="true" answer="[0,2]">
|
|
144
|
+
|
|
145
|
+
<question-sel>2</question-sel>
|
|
146
|
+
|
|
147
|
+
<question-sel>3</question-sel>
|
|
148
|
+
|
|
149
|
+
<question-sel>4</question-sel>
|
|
150
|
+
|
|
151
|
+
</question-sel-grp>
|
|
152
|
+
|
|
153
|
+
<question-hint>考虑能否被 2 整除。</question-hint>
|
|
154
|
+
|
|
155
|
+
<question-answer>2 和 4 是偶数。</question-answer>
|
|
156
|
+
|
|
157
|
+
</question>
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
实际效果(可交互):
|
|
161
|
+
|
|
162
|
+
<question title="哪些是偶数?">
|
|
163
|
+
|
|
164
|
+
<question-content>请选择所有正确选项。</question-content>
|
|
165
|
+
|
|
166
|
+
<question-sel-grp multi="true" answer="[0,2]">
|
|
167
|
+
|
|
168
|
+
<question-sel>2</question-sel>
|
|
169
|
+
|
|
170
|
+
<question-sel>3</question-sel>
|
|
171
|
+
|
|
172
|
+
<question-sel>4</question-sel>
|
|
173
|
+
|
|
174
|
+
</question-sel-grp>
|
|
175
|
+
|
|
176
|
+
<question-hint>考虑能否被 2 整除。</question-hint>
|
|
177
|
+
|
|
178
|
+
<question-answer>2 和 4 是偶数。</question-answer>
|
|
179
|
+
|
|
180
|
+
</question>
|
|
181
|
+
|
|
182
|
+
答案索引从 **0** 开始。`multi` 默认关闭;`answer` 可以是 JSON 数组或单个索引的字符串。属性优先读取选择组,其次读取 `question`。提示和答案可以单独展开,有答案索引时支持提交、判分和重试。它不连接 AI,也不创建问答标注。
|
|
183
|
+
|
|
184
|
+
## 拓展阅读
|
|
185
|
+
|
|
186
|
+
```markdown
|
|
187
|
+
<extras>
|
|
188
|
+
|
|
189
|
+
<extra title="继续阅读" url="https://example.com/article" desc="背景和补充材料。" />
|
|
190
|
+
|
|
191
|
+
</extras>
|
|
192
|
+
```
|
|
193
|
+
|
|
194
|
+
实际效果(可交互):
|
|
195
|
+
|
|
196
|
+
<extras>
|
|
197
|
+
|
|
198
|
+
<extra title="内置模块" url="https://linloir.github.io/LiMR/#/docs/zh-CN/modules" desc="背景和补充材料。" />
|
|
199
|
+
|
|
200
|
+
</extras>
|
|
201
|
+
|
|
202
|
+
`extra` 使用 `title`、`url`、`desc`。链接经过内置 URL 过滤。
|
|
203
|
+
|
|
204
|
+
## 引用卡片
|
|
205
|
+
|
|
206
|
+
```markdown
|
|
207
|
+
一个来源<ref id="a" />,或一组来源<ref ids='["a","b"]' />。
|
|
208
|
+
|
|
209
|
+
<ref id="a">带引用的 **原文**</ref>
|
|
210
|
+
|
|
211
|
+
<citation id="a" title="资料 A" url="https://example.com/a" description="完整的 **Markdown** 说明。" summary="收起时的简短摘要。" />
|
|
212
|
+
|
|
213
|
+
<citation cite-id="b" title="资料 B" url="https://example.com/b" desc="兼容属性名。" />
|
|
214
|
+
```
|
|
215
|
+
|
|
216
|
+
实际效果(可交互):
|
|
217
|
+
|
|
218
|
+
一个来源<ref id="a" />,或一组来源<ref ids='["a","b"]' />。
|
|
219
|
+
|
|
220
|
+
<ref id="a">带引用的 **原文**</ref>
|
|
221
|
+
|
|
222
|
+
<citation id="a" title="资料 A" url="https://example.com/a" description="完整的 **Markdown** 说明。" summary="收起时的简短摘要。" />
|
|
223
|
+
|
|
224
|
+
<citation cite-id="b" title="资料 B" url="https://example.com/b" desc="兼容属性名。" />
|
|
225
|
+
|
|
226
|
+
`citation` 的 ID 读取 `cite-id`,其次 `id`;说明读取 `desc`,其次 `description`。`ref` 读取 `cite-from-ids`、`ids`、`id`,支持 JSON 数组或逗号分隔字符串。定义本身不占正文位置,同一个定义可以被多个角标引用。
|
|
227
|
+
|
|
228
|
+
Citation 默认不提供展开大卡片,点击固定预览;摘要按 `summary → description → 安全 URL → title` 回退,去掉 Markdown 标记。正文显示 `description`,没有时显示 `summary`。悬浮或激活时临时高亮引用所在的最近文本块(段落、标题、列表项或表格单元格),不写入已保存高亮;外部范围标注使用绑定范围。配置与存储见[标注与选区](annotations.md)。
|
|
229
|
+
|
|
230
|
+
## 图片、图组与图注
|
|
231
|
+
|
|
232
|
+
```markdown
|
|
233
|
+
<figures columns="2" min-width="220" max-width="560" max-height="480">
|
|
234
|
+
|
|
235
|
+
<figure src="/figures/a.png" alt="实验 A" caption="(a) 实验 A" />
|
|
236
|
+
|
|
237
|
+
<figure id="experiment-b">
|
|
238
|
+
|
|
239
|
+

|
|
240
|
+
|
|
241
|
+
<figcaption>**(b)** 支持 Markdown 的图注。</figcaption>
|
|
242
|
+
|
|
243
|
+
</figure>
|
|
244
|
+
|
|
245
|
+
<figcaption>图 1 · 两个实验。</figcaption>
|
|
246
|
+
|
|
247
|
+
</figures>
|
|
248
|
+
```
|
|
249
|
+
|
|
250
|
+
实际效果(可交互):
|
|
251
|
+
|
|
252
|
+
<figures columns="2" min-width="220" max-width="560" max-height="480">
|
|
253
|
+
|
|
254
|
+
<figure src="../assets/source.svg" alt="实验 A" caption="(a) 实验 A" />
|
|
255
|
+
|
|
256
|
+
<figure id="experiment-b">
|
|
257
|
+
|
|
258
|
+

|
|
259
|
+
|
|
260
|
+
<figcaption>**(b)** 支持 Markdown 的图注。</figcaption>
|
|
261
|
+
|
|
262
|
+
</figure>
|
|
263
|
+
|
|
264
|
+
<figcaption>图 1 · 两个实验。</figcaption>
|
|
265
|
+
|
|
266
|
+
</figures>
|
|
267
|
+
|
|
268
|
+
`figure` 可独立使用。图片可以来自 `src` / `url` 属性、内部 Markdown 图片或 `<img src="…" alt="…" />`。每个 figure 放一张图;多张图用 figures。图注可用 `caption` 属性或 `figcaption` / `caption` 子标签,富文本使用子标签。`id` 可作为页内链接目标。
|
|
269
|
+
|
|
270
|
+
| 属性 | 默认值 | 说明 |
|
|
271
|
+
| --- | --- | --- |
|
|
272
|
+
| `min-width` / `min-height` | 220 / 120 | 决定何时增加列,窄容器优先适应宽度 |
|
|
273
|
+
| `max-width` / `max-height` | 560 / 480 | 保持比例的图片上限;`none` / `false` 取消上限 |
|
|
274
|
+
| `columns` / `cols` | 自动 | 指定列数,最多 24 |
|
|
275
|
+
| `rows` | 自动 | 未指定列数时由图数推导列数 |
|
|
276
|
+
| `responsive` | true | 容器不足时降低显式列数;false 保留列数 |
|
|
277
|
+
| `gap` | 16 | 图组间距,像素 |
|
|
278
|
+
|
|
279
|
+
尺寸接受非负数字或 `px`。图组限制由子图继承,子图可以覆盖。对应工厂参数为驼峰命名,例如 `figures({ maxHeight: 360, maxWidth: false })`。图片不变形、不裁切图注。单图复制按钮复制图片 URL;图组复制按钮复制 Markdown 源码,不下载图片二进制。
|
|
280
|
+
|
|
281
|
+
指令示例:
|
|
282
|
+
|
|
283
|
+
```markdown
|
|
284
|
+
:::figures{columns="2"}
|
|
285
|
+
::figure{src="/a.png" caption="A"}
|
|
286
|
+
::figure{src="/b.png" caption="B"}
|
|
287
|
+
:::
|
|
288
|
+
```
|
|
289
|
+
|
|
290
|
+
## 视频
|
|
291
|
+
|
|
292
|
+
```markdown
|
|
293
|
+
<video src="/demo.mp4" provider="native" title="演示" />
|
|
294
|
+
|
|
295
|
+
<md-video url="https://www.youtube.com/watch?v=dQw4w9WgXcQ" loading="click" />
|
|
296
|
+
|
|
297
|
+
<video url="https://www.bilibili.com/video/BV1xx411c7mD/?p=2" />
|
|
298
|
+
```
|
|
299
|
+
|
|
300
|
+
`url` 优先于 `src`;`provider` 默认 `auto`,还可以是 `native`、`youtube`、`bilibili`。YouTube 支持 watch、短链接、embed、shorts、live 和起始时间;Bilibili 支持 BV/av 与分 P。支持 `title` 和 `loading`。
|
|
301
|
+
|
|
302
|
+
**默认 `loading="visible"`**:在视口附近 200px 挂载播放器,不自动播放。`click` 等待点击,`eager` 立即挂载。可以用 `video({ loading: 'click' })` 设置模块默认值。平台、网络和嵌入权限仍会影响真实播放;不能把本地 iframe 测试当作远程播放保证。自定义平台通过 `video({ resolveUrl })` 接入。
|
|
303
|
+
|
|
304
|
+
点击加载的视频示例(点击前不创建播放器):
|
|
305
|
+
|
|
306
|
+
<video url="https://www.youtube.com/watch?v=dQw4w9WgXcQ" loading="click" title="视频示例" />
|
|
307
|
+
|
|
308
|
+
## 分段译文
|
|
309
|
+
|
|
310
|
+
```markdown
|
|
311
|
+
<translations default="both">
|
|
312
|
+
|
|
313
|
+
<origin>
|
|
314
|
+
|
|
315
|
+
Original **Markdown**.
|
|
316
|
+
|
|
317
|
+
</origin>
|
|
318
|
+
|
|
319
|
+
<translation>
|
|
320
|
+
|
|
321
|
+
翻译后的 **Markdown**。
|
|
322
|
+
|
|
323
|
+
</translation>
|
|
324
|
+
|
|
325
|
+
</translations>
|
|
326
|
+
```
|
|
327
|
+
|
|
328
|
+
实际效果(可交互):
|
|
329
|
+
|
|
330
|
+
<translations default="both">
|
|
331
|
+
|
|
332
|
+
<origin>
|
|
333
|
+
|
|
334
|
+
Original **Markdown**.
|
|
335
|
+
|
|
336
|
+
</origin>
|
|
337
|
+
|
|
338
|
+
<translation>
|
|
339
|
+
|
|
340
|
+
翻译后的 **Markdown**。
|
|
341
|
+
|
|
342
|
+
</translation>
|
|
343
|
+
|
|
344
|
+
</translations>
|
|
345
|
+
|
|
346
|
+
`default` 为 `origin`、`translation` 或 `both`,未指定时默认 `origin`(可用工厂 `defaultMode` 改写)。角标依次切换原文、译文、对照;对照时译文在前。流式输入只有一侧时先显示已有内容。模块不提供自动翻译服务。
|
|
347
|
+
|
|
348
|
+
全局 `parameters.translationMode` 覆盖各块;传 `onTranslationModeChange` 接受角标发出的更改请求,否则全局模式锁定。设回 `undefined` 恢复各块的本地选择。见[项目集成](integration.md)。
|
|
349
|
+
|
|
350
|
+
## 选择与替换
|
|
351
|
+
|
|
352
|
+
默认高亮存为 annotation,没有 `==高亮==` 内置语法。自定义标签需注册插件;不是所有子标签都有同名指令。使用完整清单配置或替换默认模块,见[扩展开发](extensions.md)。
|
package/examples/notes.tsx
CHANGED
|
@@ -1,16 +1,18 @@
|
|
|
1
1
|
import { useEffect, useLayoutEffect, useRef } from 'react';
|
|
2
|
-
import { AnnotationMarkdown, ToolIcon, defineExtension, useLiMR, type AnnotationContentProps, type RendererExtension } from '@linloir/limr';
|
|
2
|
+
import { AnnotationMarkdown, ToolIcon, defineExtension, useLiMR, useLocale, type AnnotationContentProps, type RendererExtension } from '@linloir/limr';
|
|
3
3
|
|
|
4
4
|
interface NoteData { text: string; draft?: string }
|
|
5
5
|
const kinds = [
|
|
6
|
-
{ type: 'margin-note', label: '旁注', icon: 'comment', color: '#81B29A', hint: '在段落旁留下一个想法' },
|
|
7
|
-
{ type: 'reading-note', label: '笔记', icon: 'note', color: '#6B9BD2', hint: '整理这一段的阅读笔记' },
|
|
8
|
-
{ type: 'reader-question', label: '提问', icon: 'question', color: '#E5A06E', hint: '写下还想进一步理解的问题' },
|
|
9
|
-
{ type: 'reader-footnote', label: '脚注', icon: 'footnote', color: '#B39DDB', hint: '补充解释或参考来源' },
|
|
6
|
+
{ type: 'margin-note', label: '旁注', icon: 'comment', color: '#81B29A', hint: '在段落旁留下一个想法', labelEn: 'Margin note', hintEn: 'Leave a thought beside this passage' },
|
|
7
|
+
{ type: 'reading-note', label: '笔记', icon: 'note', color: '#6B9BD2', hint: '整理这一段的阅读笔记', labelEn: 'Note', hintEn: 'Write a reading note' },
|
|
8
|
+
{ type: 'reader-question', label: '提问', icon: 'question', color: '#E5A06E', hint: '写下还想进一步理解的问题', labelEn: 'Question', hintEn: 'What would you like to understand?' },
|
|
9
|
+
{ type: 'reader-footnote', label: '脚注', icon: 'footnote', color: '#B39DDB', hint: '补充解释或参考来源', labelEn: 'Footnote', hintEn: 'Add an explanation or a source' },
|
|
10
10
|
] as const;
|
|
11
11
|
|
|
12
12
|
function NoteContent({ annotation, data, editing, presentation }: AnnotationContentProps<NoteData>) {
|
|
13
13
|
const commands = useLiMR();
|
|
14
|
+
const locale = useLocale();
|
|
15
|
+
const zh = locale.language?.startsWith('zh') ?? false;
|
|
14
16
|
const text = data?.draft ?? data?.text ?? '';
|
|
15
17
|
const isEditing = editing || data?.draft !== undefined;
|
|
16
18
|
const input = useRef<HTMLTextAreaElement>(null);
|
|
@@ -45,25 +47,30 @@ function NoteContent({ annotation, data, editing, presentation }: AnnotationCont
|
|
|
45
47
|
};
|
|
46
48
|
return <div className="demo-note-content" data-limr-card-interactive="true">
|
|
47
49
|
{isEditing ? <form onSubmit={event => { event.preventDefault(); save(); }}>
|
|
48
|
-
<textarea className="demo-note-editor" ref={input} aria-label={`${kind.label}内容`} rows={1} value={text} onChange={event => commands.updateAnnotation(annotation.id, { data: { text: data?.text ?? '', draft: event.target.value } })} placeholder={kind.hint} onKeyDown={event => {
|
|
50
|
+
<textarea className="demo-note-editor" ref={input} aria-label={zh ? `${kind.label}内容` : `${kind.labelEn} content`} rows={1} value={text} onChange={event => commands.updateAnnotation(annotation.id, { data: { text: data?.text ?? '', draft: event.target.value } })} placeholder={zh ? kind.hint : kind.hintEn} onKeyDown={event => {
|
|
49
51
|
if (event.nativeEvent.isComposing) return;
|
|
50
52
|
if (event.key === 'Escape') { event.preventDefault(); event.stopPropagation(); cancel(); }
|
|
51
53
|
else if (event.key === 'Enter' && (event.metaKey || event.ctrlKey)) { event.preventDefault(); save(); }
|
|
52
54
|
}} />
|
|
53
|
-
<div className="demo-note-actions"><button type="button" onClick={cancel}><ToolIcon name="close" /><span className="limr-underline"
|
|
54
|
-
</form> : <><AnnotationMarkdown source={data?.text || '还没有内容。'} /><div className="demo-note-actions"><button type="button" onClick={() => { commands.updateAnnotation(annotation.id, { data: { text: data?.text ?? '', draft: data?.text ?? '' } }); commands.editAnnotation(annotation.id); }}><ToolIcon name="edit" /><span className="limr-underline"
|
|
55
|
+
<div className="demo-note-actions"><button type="button" onClick={cancel}><ToolIcon name="close" /><span className="limr-underline">{zh ? '取消' : 'Cancel'}</span></button><button type="submit" className="demo-note-submit" disabled={!text.trim()}><ToolIcon name="check" /><span className="limr-underline">{locale.submit}</span></button></div>
|
|
56
|
+
</form> : <><AnnotationMarkdown source={data?.text || (zh ? '还没有内容。' : 'No content yet.')} /><div className="demo-note-actions"><button type="button" onClick={() => { commands.updateAnnotation(annotation.id, { data: { text: data?.text ?? '', draft: data?.text ?? '' } }); commands.editAnnotation(annotation.id); }}><ToolIcon name="edit" /><span className="limr-underline">{zh ? '修改' : 'Edit'}</span></button><button type="button" className="demo-note-delete" onClick={() => commands.removeAnnotation(annotation.id)}><ToolIcon name="trash" /><span className="limr-underline">{locale.deleteAnnotations}</span></button></div></>}
|
|
55
57
|
</div>;
|
|
56
58
|
}
|
|
57
59
|
|
|
60
|
+
function NotePreview({ data }: { data?: NoteData }) {
|
|
61
|
+
const locale = useLocale();
|
|
62
|
+
return <span>{data?.text || (locale.language?.startsWith('zh') ? '正在编辑…' : 'Editing…')}</span>;
|
|
63
|
+
}
|
|
64
|
+
|
|
58
65
|
/** Modules bind every footnote/note/question through the same annotation API. */
|
|
59
66
|
export function readingNotes(): RendererExtension {
|
|
60
67
|
return defineExtension({
|
|
61
68
|
name: 'reading-notes',
|
|
62
|
-
annotationTypes: kinds.map(kind => ({ type: kind.type, label: kind.label, color: kind.color, icon: <ToolIcon name={kind.icon} />, Content: NoteContent,
|
|
63
|
-
sidebar: { Preview:
|
|
69
|
+
annotationTypes: kinds.map(kind => ({ type: kind.type, label: locale => locale.language?.startsWith('zh') ? kind.label : kind.labelEn, color: kind.color, icon: <ToolIcon name={kind.icon} />, Content: NoteContent,
|
|
70
|
+
sidebar: { Preview: NotePreview },
|
|
64
71
|
overlay: { previewWidth: 300, expand: { placement: 'center' as const, width: 400, maxHeight: '75dvh' } },
|
|
65
72
|
})),
|
|
66
|
-
actions: kinds.map(kind => ({ id: `reading-notes:${kind.type}`, label: kind.label, icon: <ToolIcon name={kind.icon} />, run: context => {
|
|
73
|
+
actions: kinds.map(kind => ({ id: `reading-notes:${kind.type}`, label: context => context.locale.language?.startsWith('zh') ? kind.label : kind.labelEn, icon: <ToolIcon name={kind.icon} />, run: context => {
|
|
67
74
|
context.addAnnotation({ id: `note-${crypto.randomUUID()}`, type: kind.type, data: { text: '', draft: '' } satisfies NoteData,
|
|
68
75
|
anchors: context.selection.syntax.ranges.map(range => ({ range, documentId: context.document.id, quote: context.document.source.slice(range.start, range.end) })),
|
|
69
76
|
}, { edit: true });
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
/* Unlayered application/theme CSS overrides the built-in @layer limr. */
|
|
2
|
+
[data-limr-appearance='paper'] {
|
|
3
|
+
--limr-accent: #185145;
|
|
4
|
+
--limr-link: #185145;
|
|
5
|
+
--limr-selection: #548b7440;
|
|
6
|
+
--limr-background: #fcfaf4;
|
|
7
|
+
--limr-heading: #283b34;
|
|
8
|
+
}
|
|
9
|
+
[data-limr-appearance='paper'][data-limr-theme='dark'] {
|
|
10
|
+
--limr-background: #1e2421;
|
|
11
|
+
--limr-heading: #d8e4db;
|
|
12
|
+
--limr-link: #a8cbb8;
|
|
13
|
+
}
|
|
14
|
+
[data-limr-appearance='paper'] .reading-tip {
|
|
15
|
+
border-inline-start: 3px solid var(--limr-accent);
|
|
16
|
+
padding: .8rem 1rem;
|
|
17
|
+
background: #548b7410;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/* The same appearance selector is applied to portalled toolbars. */
|
|
21
|
+
.limr-selection-toolbar[data-limr-appearance='paper'] {
|
|
22
|
+
--limr-toolbar-radius: 3px;
|
|
23
|
+
--limr-toolbar-panel-max-height: 190px;
|
|
24
|
+
--limr-toolbar-accent: var(--limr-accent);
|
|
25
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { createElement as h } from 'react';
|
|
2
|
+
import { AnnotationCard, AnnotationMarkdown, defineBlock, defineExtension } from '@linloir/limr';
|
|
3
|
+
|
|
4
|
+
// No renderer internals or application services are needed by an npm plugin.
|
|
5
|
+
export function readingTools() {
|
|
6
|
+
const tip = defineBlock({ name: 'reading-tip', component: ({ children }) => h('aside', { className: 'reading-tip' }, children) });
|
|
7
|
+
return defineExtension({
|
|
8
|
+
...tip,
|
|
9
|
+
name: 'reading-tools',
|
|
10
|
+
annotationTypes: [{
|
|
11
|
+
type: 'reading-note', label: 'Reading note', icon: '◇', color: '#548b74',
|
|
12
|
+
overlay: { Card: props => h(AnnotationCard, { ...props, title: 'Reading note' }), expand: { placement: 'anchor' } },
|
|
13
|
+
Content: ({ data }) => h(AnnotationMarkdown, { source: data?.text ?? '' }),
|
|
14
|
+
}],
|
|
15
|
+
actions: [{
|
|
16
|
+
id: 'reading-note:add', label: 'Save passage', icon: h('svg', { viewBox: '0 0 24 24', fill: 'none', stroke: 'currentColor', strokeWidth: 1.7, strokeLinecap: 'round', strokeLinejoin: 'round' }, h('rect', { x: 5, y: 3, width: 14, height: 18, rx: 3 }), h('path', { d: 'M9 8h6M9 12h6M9 16h4' })),
|
|
17
|
+
run(context) {
|
|
18
|
+
const id = `reading-note-${crypto.randomUUID()}`;
|
|
19
|
+
context.addAnnotation({ id, type: 'reading-note', range: context.selection.syntax.ranges[0], data: { text: `**Saved passage**\n\n${context.selection.text}` } });
|
|
20
|
+
context.activateAnnotation(id);
|
|
21
|
+
context.clearSelection();
|
|
22
|
+
},
|
|
23
|
+
}],
|
|
24
|
+
});
|
|
25
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@limr-example/reading-tools",
|
|
3
|
+
"version": "0.0.0",
|
|
4
|
+
"private": true,
|
|
5
|
+
"type": "module",
|
|
6
|
+
"exports": {
|
|
7
|
+
".": {
|
|
8
|
+
"types": "./index.d.ts",
|
|
9
|
+
"import": "./index.js"
|
|
10
|
+
}
|
|
11
|
+
},
|
|
12
|
+
"peerDependencies": {
|
|
13
|
+
"@linloir/limr": "^1.0.0",
|
|
14
|
+
"react": "^18.3.0 || ^19.0.0"
|
|
15
|
+
}
|
|
16
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@linloir/limr",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "1.0.0",
|
|
4
4
|
"description": "Linloir's limr Is a Markdown Renderer — extensible reading and streaming components for React",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -23,9 +23,11 @@
|
|
|
23
23
|
"files": [
|
|
24
24
|
"dist",
|
|
25
25
|
"README.md",
|
|
26
|
+
"README.en.md",
|
|
26
27
|
"LICENSE",
|
|
27
|
-
"docs
|
|
28
|
-
"
|
|
28
|
+
"docs",
|
|
29
|
+
"skills",
|
|
30
|
+
"examples",
|
|
29
31
|
"benchmarks/results/*.json"
|
|
30
32
|
],
|
|
31
33
|
"exports": {
|
|
@@ -97,14 +99,16 @@
|
|
|
97
99
|
"vitest": "^3.2.7"
|
|
98
100
|
},
|
|
99
101
|
"scripts": {
|
|
100
|
-
"dev": "vite --host 127.0.0.1",
|
|
102
|
+
"dev": "pnpm pack:skills && vite --host 127.0.0.1",
|
|
101
103
|
"build": "tsup && node scripts/build-assets.mjs",
|
|
102
|
-
"build:playground": "vite build --base=/LiMR/",
|
|
104
|
+
"build:playground": "pnpm pack:skills && vite build --base=/LiMR/",
|
|
103
105
|
"preview:playground": "vite preview --host 127.0.0.1 --base=/LiMR/",
|
|
104
106
|
"typecheck": "tsc --noEmit",
|
|
105
107
|
"test": "vitest run",
|
|
106
108
|
"test:browser": "playwright test",
|
|
107
109
|
"test:package": "node scripts/package-smoke.mjs",
|
|
110
|
+
"test:docs": "node scripts/check-docs.mjs",
|
|
111
|
+
"pack:skills": "python3 scripts/package-skills.py",
|
|
108
112
|
"bench": "tsx benchmarks/run.ts",
|
|
109
113
|
"bench:browser": "tsx benchmarks/browser.ts",
|
|
110
114
|
"bench:selection": "tsx benchmarks/selection.ts",
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: limr-plugin-development
|
|
3
|
+
description: Develop, package, and integrate new @linloir/limr extension modules with custom syntax, annotation cards, selection actions, parameters, or CSS themes. Use for reusable LiMR plugin packages; ordinary application integration belongs to limr-use.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Develop a LiMR plugin
|
|
7
|
+
|
|
8
|
+
Deliver the requested extension as a package plus a working host import. Inspect the installed LiMR version and public declarations before choosing APIs. This bundle describes 1.0.0, including its locale APIs.
|
|
9
|
+
|
|
10
|
+
Read [the contract](references/contract.md) for source mapping, annotation presentation, actions, resources, and localization. Start from [plugin-template](assets/plugin-template/README.md) when a standalone TypeScript/ESM package is useful. Copy the complete template to the requested project destination, rename its example package and IDs, and remove capabilities not needed by the task. Do not edit the installed skill as the project implementation.
|
|
11
|
+
|
|
12
|
+
Choose the smallest public contract: defineBlock for a block; defineAnnotation for a type; defineExtension to combine capabilities. Keep React/LiMR as peers and build externals, export declarations, and expose CSS separately with sideEffects. Import no renderer internals and bundle no second React or LiMR runtime.
|
|
13
|
+
|
|
14
|
+
Preserve original source positions. Reuse children/renderChildren, renderValue, and renderAttribute. Store every selected range with documentId and its original quote. A transform defaults to document scope; scope:local is valid only for subtree-local, prefix-independent work.
|
|
15
|
+
|
|
16
|
+
Let the module own card meaning and controls through Content, overlay.Card, and sidebar.Preview. Reuse AnnotationCard and AnnotationMarkdown where suitable. LiMR owns shared positioning, portals, focus, and motion. Preview must not fetch; long tasks honor context.signal. The application owns services and credentials.
|
|
17
|
+
|
|
18
|
+
Support host localization when requested: on 1.0.0 use useLocale() in components, locale.language for plugin dictionaries, label(locale) for annotation names, and context.locale in actions. Preserve stored user text across language changes. Older hosts require explicit plugin options instead; reflect the actual minimum in peerDependencies.
|
|
19
|
+
|
|
20
|
+
Integrate with a stable plugins array and explicit CSS imports. Use extensions + replaceExtension for default replacements; duplicate names/tags/directives are errors. Do not silently discard persisted unknown annotations.
|
|
21
|
+
|
|
22
|
+
Validate the built package in an independent consumer, not just a source alias. Check TypeScript, SSR, retained built-ins, syntax/source mapping/finalized streaming where relevant, and desktop/mobile card interactions for UI features. Exercise supported React major versions and both languages/themes when claimed. Report the package path and verified limits. Publishing remains a separate user action unless explicitly requested.
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
# LiMR reading-tools starter / 插件模板
|
|
2
|
+
|
|
3
|
+
Copy this entire folder into your project, rename the example package/extension/type/action IDs, and adapt the implementation. It requires LiMR 1.0.0 for useLocale and localized annotation labels. The @example package is not on npm. private: true permits local packing while preventing accidental publication under the sample name.
|
|
4
|
+
|
|
5
|
+
复制完整目录到项目,替换示例包名和各 ID。模板需要 LiMR 1.0.0 的语言接口;未发布版本可先安装维护者提供的本地 LiMR tgz。
|
|
6
|
+
|
|
7
|
+
```sh
|
|
8
|
+
npm install
|
|
9
|
+
npm run typecheck
|
|
10
|
+
npm run build
|
|
11
|
+
npm pack
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
Install the generated tgz in a separate host application, then:
|
|
15
|
+
|
|
16
|
+
```tsx
|
|
17
|
+
import { Reader, chinese } from '@linloir/limr';
|
|
18
|
+
import { readingTools } from '@example/limr-reading-tools';
|
|
19
|
+
import '@linloir/limr/styles.css';
|
|
20
|
+
import '@example/limr-reading-tools/styles.css';
|
|
21
|
+
|
|
22
|
+
const plugins = [readingTools()];
|
|
23
|
+
export function Article({ source }: { source: string }) {
|
|
24
|
+
return <Reader source={source} plugins={plugins} locale={chinese} />;
|
|
25
|
+
}
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
Supports <reading-tip title="Tip"> with Markdown inside, plus a selection action creating an example:note annotation with original-source anchors. English/Chinese labels follow the reader locale; options.label explicitly overrides both. User note text is never translated. Persist annotations in the host with annotations/onAnnotationsChange. The plugin includes no service connection or storage.
|
|
29
|
+
|
|
30
|
+
Test the actual archive in each supported React major, SSR, desktop/mobile cards, and both locales before claiming compatibility. Keep React and LiMR external and declare them as peers. The CSS import is explicit.
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@example/limr-reading-tools",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"private": true,
|
|
5
|
+
"type": "module",
|
|
6
|
+
"license": "MIT",
|
|
7
|
+
"files": [
|
|
8
|
+
"dist",
|
|
9
|
+
"README.md"
|
|
10
|
+
],
|
|
11
|
+
"sideEffects": [
|
|
12
|
+
"**/*.css"
|
|
13
|
+
],
|
|
14
|
+
"exports": {
|
|
15
|
+
".": {
|
|
16
|
+
"types": "./dist/index.d.ts",
|
|
17
|
+
"import": "./dist/index.js"
|
|
18
|
+
},
|
|
19
|
+
"./styles.css": "./dist/styles.css"
|
|
20
|
+
},
|
|
21
|
+
"scripts": {
|
|
22
|
+
"typecheck": "tsc --noEmit",
|
|
23
|
+
"build": "tsup",
|
|
24
|
+
"prepack": "npm run build"
|
|
25
|
+
},
|
|
26
|
+
"peerDependencies": {
|
|
27
|
+
"@linloir/limr": "^1.0.0",
|
|
28
|
+
"react": "^18.3.0 || ^19.0.0"
|
|
29
|
+
},
|
|
30
|
+
"devDependencies": {
|
|
31
|
+
"@types/node": "^22.0.0",
|
|
32
|
+
"@types/react": "^19.0.0",
|
|
33
|
+
"tsup": "^8.5.0",
|
|
34
|
+
"typescript": "^5.9.0"
|
|
35
|
+
}
|
|
36
|
+
}
|