@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,159 @@
|
|
|
1
|
+
# 扩展开发
|
|
2
|
+
|
|
3
|
+
> 阅读提示:本文包含 LiMR 扩展语法和交互示例。部分内容在 GitHub 等普通 Markdown 阅读器中无法完整展示;前往 [GitHub Pages 文档](https://linloir.github.io/LiMR/#/docs/zh-CN/extensions) 可获得最佳阅读体验。
|
|
4
|
+
|
|
5
|
+
[文档目录](README.md) · [English](../en/extensions.md)
|
|
6
|
+
|
|
7
|
+
第一次接触模块时,先阅读[内置模块](modules.md):那里解释模块对象、创建函数、模块名、Markdown 标签,以及 `extensions` 和 `plugins` 的区别。
|
|
8
|
+
|
|
9
|
+
## 选择接口
|
|
10
|
+
|
|
11
|
+
| 需求 | 接口 |
|
|
12
|
+
| --- | --- |
|
|
13
|
+
| 一个带 Markdown 正文的新区块 | defineBlock |
|
|
14
|
+
| 一种独立标注类型 | defineAnnotation |
|
|
15
|
+
| 组合语法、标注、动作和参数 | defineExtension / RendererExtension |
|
|
16
|
+
| 改写某种已有节点的外观 | components |
|
|
17
|
+
| 新的解析规则 | micromark + mdast 扩展,必要时 transform |
|
|
18
|
+
| 仅修改视觉 | CSS 主题包 + appearance |
|
|
19
|
+
|
|
20
|
+
所有接口从 `@linloir/limr` 公开导出。第三方包不要导入 src、dist 内部路径或 useRuntime 等未公开接口。完整独立模板见[插件模块包](plugin-packages.md)。
|
|
21
|
+
|
|
22
|
+
## 追加、删除与替换默认模块
|
|
23
|
+
|
|
24
|
+
```tsx
|
|
25
|
+
import { Reader, standard, replaceExtension, video, defineBlock } from '@linloir/limr';
|
|
26
|
+
|
|
27
|
+
const extensions = replaceExtension(standard(), video({ loading: 'click' }));
|
|
28
|
+
const plugins = [defineBlock({
|
|
29
|
+
name: 'reading-tip',
|
|
30
|
+
component: ({ children }) => <aside className="reading-tip">{children}</aside>,
|
|
31
|
+
})];
|
|
32
|
+
|
|
33
|
+
export function Article({ source }: { source: string }) {
|
|
34
|
+
return <Reader source={source} extensions={extensions} plugins={plugins} />;
|
|
35
|
+
}
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
只移除模块可 filter 它的 name。扩展名、注册标签名和指令名各自必须唯一,重复会报错;不要用 plugins 再追加一份 citation 或 video。`replaceExtension` 替换同名项,不存在时追加。保留配置引用,避免流式渲染期间反复重建解析器。
|
|
39
|
+
|
|
40
|
+
## 自定义正文区块
|
|
41
|
+
|
|
42
|
+
```tsx
|
|
43
|
+
import { defineBlock } from '@linloir/limr';
|
|
44
|
+
|
|
45
|
+
export const notice = defineBlock({
|
|
46
|
+
name: 'notice',
|
|
47
|
+
component: ({ children, renderAttribute }) => <aside className="notice">
|
|
48
|
+
<header>{renderAttribute('title')}</header>
|
|
49
|
+
{children}
|
|
50
|
+
</aside>,
|
|
51
|
+
});
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
支持 `<notice title="提示">…</notice>` 和 `:::notice{title="提示"}`。name 默认同时成为标签名和指令名,可通过 tags / directives 自定义。defineBlock 默认 display: block、selection: content、scope: local。
|
|
55
|
+
|
|
56
|
+
| NodeRendererProps | 用法 |
|
|
57
|
+
| --- | --- |
|
|
58
|
+
| node | 当前 AST 节点和静态属性 |
|
|
59
|
+
| children | 已经渲染好的子树;优先复用 |
|
|
60
|
+
| source | **当前节点**对应的源码片段,不是全文 |
|
|
61
|
+
| pending | 所属尾块是否仍在流式生成 |
|
|
62
|
+
| renderChildren(nodes, { trailing? }) | 重排已有子树,可在末尾注入纯操作角标 |
|
|
63
|
+
| renderValue(tokens?) | 渲染字面量及高亮 token,保留原文选择和标注插入 |
|
|
64
|
+
| renderAttribute(name) | 显示属性文本并保留原文坐标 |
|
|
65
|
+
|
|
66
|
+
不要把 children 转字符串再解析,也不要仅输出 node.value 导致源码映射丢失。无法逐字对应的图表/媒体用 selection: atomic;需要指定可视边界时使用 data-limr-selection-surface。纯操作角标可以使用公开 InlineMarker,不必建立 Annotation。
|
|
67
|
+
|
|
68
|
+
## 标注卡片由插件拥有
|
|
69
|
+
|
|
70
|
+
```tsx
|
|
71
|
+
import { AnnotationCard, AnnotationMarkdown, defineAnnotation, ToolIcon } from '@linloir/limr';
|
|
72
|
+
|
|
73
|
+
export const note = defineAnnotation<{ text: string }>({
|
|
74
|
+
type: 'example:note',
|
|
75
|
+
label: 'Note',
|
|
76
|
+
icon: <ToolIcon name="note" />,
|
|
77
|
+
color: '#6B9BD2',
|
|
78
|
+
Content: ({ data, loading, error, retry }) => loading ? <p>Loading…</p>
|
|
79
|
+
: error ? <button onClick={retry}>Retry</button>
|
|
80
|
+
: <AnnotationMarkdown source={data?.text ?? ''} />,
|
|
81
|
+
sidebar: {
|
|
82
|
+
Preview: ({ data }) => data?.text ? <span>{data.text}</span> : null,
|
|
83
|
+
previewLines: 2,
|
|
84
|
+
},
|
|
85
|
+
overlay: {
|
|
86
|
+
Card: props => <AnnotationCard {...props} title="Note" />,
|
|
87
|
+
previewWidth: 320,
|
|
88
|
+
expand: { placement: 'center', width: 640, maxHeight: '80dvh' },
|
|
89
|
+
},
|
|
90
|
+
});
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
Content 处理 presentation(preview / expanded / sidebar)、editing、data 和资源状态。overlay.Card 拥有标题、正文和操作,可复用 AnnotationCard 或完全自绘;children 是共享加载状态的内容。LiMR 管理定位、portal、焦点、动画和退出生命周期。不要用通用标题覆盖插件自己的语义。
|
|
94
|
+
|
|
95
|
+
expand 必须显式配置;默认不展开。placement 支持 anchor / center,尺寸为像素或 CSS 长度,也可按 annotation 返回策略。sidebar.Preview 只读本地 data;不要在摘要触发网络请求。Preview: false 或返回 null 隐藏摘要。同一 occurrence 的首个类型负责卡片外观和分组摘要,可通过 annotations 获取组内数据。
|
|
96
|
+
|
|
97
|
+
sidebar 支持 previewLines、previewColor、color、borderColor、titleColor、textColor、excerptColor 和 collapseOnContentClick。内容根节点无需重复添加卡片 padding;复用共享 `--limr-sidebar-padding-inline`,处理内部段落间距即可。
|
|
98
|
+
|
|
99
|
+
## 选区动作
|
|
100
|
+
|
|
101
|
+
```tsx
|
|
102
|
+
import { defineExtension, ToolIcon } from '@linloir/limr';
|
|
103
|
+
|
|
104
|
+
export const savePassage = defineExtension({
|
|
105
|
+
name: 'example:save-passage',
|
|
106
|
+
actions: [{
|
|
107
|
+
id: 'example:save-passage', label: 'Save passage', icon: <ToolIcon name="note" />,
|
|
108
|
+
visible: ({ selection }) => selection.settled && selection.quality !== 'unmapped',
|
|
109
|
+
run(context) {
|
|
110
|
+
const id = `example-note-${crypto.randomUUID()}`;
|
|
111
|
+
context.addAnnotation({
|
|
112
|
+
id, type: 'example:note', data: { text: context.selection.text },
|
|
113
|
+
anchors: context.selection.syntax.ranges.map(range => ({
|
|
114
|
+
range, documentId: context.document.id,
|
|
115
|
+
quote: context.document.source.slice(range.start, range.end),
|
|
116
|
+
})),
|
|
117
|
+
});
|
|
118
|
+
context.activateAnnotation(id);
|
|
119
|
+
context.clearSelection();
|
|
120
|
+
},
|
|
121
|
+
}],
|
|
122
|
+
});
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
与上面的 note 扩展一起安装。SelectionAction 必须有 id、label、icon,可提供 visible、disabled、run、onHover、renderExpanded 或 render。面板操作通过 context.run 使用工具栏的 busy/error 处理。上下文包含选区、完整文档快照、同片段 annotations/highlight、locale、取消信号与命令。动作 ID 冲突时后者覆盖;宿主显式 actions 的同 ID 项最终优先。
|
|
126
|
+
|
|
127
|
+
## 参数
|
|
128
|
+
|
|
129
|
+
```tsx
|
|
130
|
+
import { defineExtension, useRendererParameter } from '@linloir/limr';
|
|
131
|
+
|
|
132
|
+
declare module '@linloir/limr' {
|
|
133
|
+
interface RendererParameters { exampleDetail?: 'short' | 'full' }
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
export const glossary = defineExtension({
|
|
137
|
+
name: 'example:glossary', tags: ['glossary'], scope: 'local',
|
|
138
|
+
parameters: { exampleDetail: 'short' },
|
|
139
|
+
components: {
|
|
140
|
+
glossary: ({ children }) => <aside data-detail={useRendererParameter('exampleDetail')}>
|
|
141
|
+
{children}
|
|
142
|
+
</aside>,
|
|
143
|
+
},
|
|
144
|
+
});
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
宿主传 `parameters={{ exampleDetail: 'full' }}` 覆盖默认值。参数变更只更新视图,不解析源文;相同参数后注册默认值优先。独立包需要让声明合并出现在导出的 .d.ts 中。
|
|
148
|
+
|
|
149
|
+
## 解析与流式约束
|
|
150
|
+
|
|
151
|
+
SyntaxExtension 支持 micromark、mdast、tags、directives、display、selection、transform 和 scope。transform 默认全文作用域;仅依赖当前局部子树、不会回写前文或读取全局文档语义时才声明 scope: local。context.source 仍是原始全文,position 必须保持原始 UTF-16 坐标。不要先用正则替换 source 再解析。
|
|
152
|
+
|
|
153
|
+
未知特殊标签安全显示整段原文,内部 Markdown 也作为原文显示。默认配置的未知指令则渲染内部 Markdown,丢弃指令外层标记;未安装任何指令扩展时按基础 Markdown 解析。不完整标签会回退并记录 diagnostics,之后追加再尝试恢复;结束必须 finish 或 streaming=false。扩展 transform 的异常记录为 diagnostic;客户端渲染边界可回退源码,但 React 错误边界不负责捕获服务端渲染异常。
|
|
154
|
+
|
|
155
|
+
验证应覆盖标签/指令、中文与 emoji、源坐标、流式结束与一次性解析等价、SSR,以及新增交互的浏览器行为。独立分发还需验证真实打包消费,见[插件模块包](plugin-packages.md)。
|
|
156
|
+
|
|
157
|
+
## 插件本地化
|
|
158
|
+
|
|
159
|
+
1.0.0 起,组件可用 useLocale() 读取合并后的 locale,以 locale.language 选择自己的文案。AnnotationType.label 接受字符串或 `(locale) => string`;选区动作使用 context.locale。Citation 默认标题随语言切换为 Citation / 引用。保持插件实例稳定即可更新文案,无需重写源文;用户保存的笔记不会被翻译。
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
# 快速开始
|
|
2
|
+
|
|
3
|
+
> 阅读提示:本文包含 LiMR 扩展语法和交互示例。部分内容在 GitHub 等普通 Markdown 阅读器中无法完整展示;前往 [GitHub Pages 文档](https://linloir.github.io/LiMR/#/docs/zh-CN/getting-started) 可获得最佳阅读体验。
|
|
4
|
+
|
|
5
|
+
[文档目录](README.md) · [English](../en/getting-started.md)
|
|
6
|
+
|
|
7
|
+
## 安装
|
|
8
|
+
|
|
9
|
+
```sh
|
|
10
|
+
npm install @linloir/limr
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
宿主项目需要 React 和 React DOM 18.3 或 19。包是 ESM,Node.js 要求 20+;支持 Vite 等 ESM 构建工具。CSS 必须由应用显式导入。
|
|
14
|
+
|
|
15
|
+
## 完整阅读器
|
|
16
|
+
|
|
17
|
+
```tsx
|
|
18
|
+
import { Reader, chinese } from '@linloir/limr';
|
|
19
|
+
import '@linloir/limr/styles.css';
|
|
20
|
+
import '@linloir/limr/math.css';
|
|
21
|
+
|
|
22
|
+
const source = `# LiMR
|
|
23
|
+
|
|
24
|
+
正文支持 **强调**、表格、代码和 $x^2$。
|
|
25
|
+
|
|
26
|
+
## 参考来源
|
|
27
|
+
|
|
28
|
+
阅读这段话<ref id="guide" />。
|
|
29
|
+
|
|
30
|
+
<citation id="guide" title="Guide" url="https://example.com/guide" />`;
|
|
31
|
+
|
|
32
|
+
export function Article() {
|
|
33
|
+
return <Reader source={source} locale={chinese} theme="auto" />;
|
|
34
|
+
}
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
宽屏提供目录、正文和侧栏;容器宽度小于 `sidebarBreakpoint`(默认 1120px)时标注改用浮层。目录在窄屏以可折叠列表展示。
|
|
38
|
+
|
|
39
|
+
## 只需要正文
|
|
40
|
+
|
|
41
|
+
```tsx
|
|
42
|
+
import { Markdown } from '@linloir/limr';
|
|
43
|
+
import '@linloir/limr/styles/base.css';
|
|
44
|
+
|
|
45
|
+
export function Excerpt() {
|
|
46
|
+
return <Markdown source="一段 **Markdown**。" />;
|
|
47
|
+
}
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
`Markdown` 可以独立使用,保留解析和选区能力,但不自动挂载工具栏。静态内容可设 `interactive={false}` 关闭选区监听。已经有 `DocumentProvider` 时,用不带 `source` 的 `<Markdown />` 共享文档。
|
|
51
|
+
|
|
52
|
+
## 选用样式
|
|
53
|
+
|
|
54
|
+
| 导入 | 内容 |
|
|
55
|
+
| --- | --- |
|
|
56
|
+
| `@linloir/limr/styles.css` | 默认主题、布局、交互和 Maple 代码字体 |
|
|
57
|
+
| `@linloir/limr/styles/base.css` | 相同样式,不引入 Maple 字体 CSS |
|
|
58
|
+
| `@linloir/limr/fonts.css` | 只引入 Maple,可与 base.css 分开使用 |
|
|
59
|
+
| `@linloir/limr/math.css` | KaTeX 样式与本地公式字体 |
|
|
60
|
+
|
|
61
|
+
`styles.css` 与 `styles/base.css` 选一个。选择 base 不会减少 npm 安装时包含的字体文件。中文正文使用宿主/系统字体,Playground 的 Noto Serif SC 并非组件默认加载项。
|
|
62
|
+
|
|
63
|
+
## 下一步
|
|
64
|
+
|
|
65
|
+
- 查看[默认语法](syntax.md),区分内置正文模块与应用标注插件。
|
|
66
|
+
- 查看[项目集成](integration.md),接入流式输出、滚动容器和 SSR。
|
|
67
|
+
- 需要 AI 协助接入时安装 [limr-use Skill](ai-skills.md)。
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
# 项目集成
|
|
2
|
+
|
|
3
|
+
> 阅读提示:本文包含 LiMR 扩展语法和交互示例。部分内容在 GitHub 等普通 Markdown 阅读器中无法完整展示;前往 [GitHub Pages 文档](https://linloir.github.io/LiMR/#/docs/zh-CN/integration) 可获得最佳阅读体验。
|
|
4
|
+
|
|
5
|
+
[文档目录](README.md) · [English](../en/integration.md)
|
|
6
|
+
|
|
7
|
+
## 选择组件
|
|
8
|
+
|
|
9
|
+
用 `Reader` 获取完整阅读体验;用 `Markdown` 渲染独立正文;自己设计布局时让一个 `DocumentProvider` 管理一个主 `Markdown` 及其目录、卡片和工具栏。Provider 内再次传入 `source` 的 Markdown 会创建独立文档,不再共享父文档状态。
|
|
10
|
+
|
|
11
|
+
## 流式内容
|
|
12
|
+
|
|
13
|
+
```tsx
|
|
14
|
+
import { Markdown } from '@linloir/limr';
|
|
15
|
+
|
|
16
|
+
export function StreamingAnswer({ answer, generating }: {
|
|
17
|
+
answer: string;
|
|
18
|
+
generating: boolean;
|
|
19
|
+
}) {
|
|
20
|
+
return <Markdown source={answer} streaming={generating} followOutput />;
|
|
21
|
+
}
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
应用负责传输层,把每个片段追加到完整 `answer`;不要只把最后一个片段传给 source。最后一次更新必须传 `streaming={false}`,触发完整语义校正。followOutput 只在仍靠近底部时跟随,不会强行拉回正在阅读上文的用户。纯核心支持 `append(chunk)` / `finish()`,React props 使用累计字符串。
|
|
25
|
+
|
|
26
|
+
## 内部滚动容器
|
|
27
|
+
|
|
28
|
+
```tsx
|
|
29
|
+
import { useState } from 'react';
|
|
30
|
+
import { Reader } from '@linloir/limr';
|
|
31
|
+
|
|
32
|
+
export function ScrollPanel({ source }: { source: string }) {
|
|
33
|
+
const [scrollRoot, setScrollRoot] = useState<HTMLDivElement | null>(null);
|
|
34
|
+
return <div ref={setScrollRoot} style={{ height: '80vh', overflowY: 'auto' }}>
|
|
35
|
+
<Reader source={source} scrollRoot={scrollRoot} scrollOffset={32} />
|
|
36
|
+
</div>;
|
|
37
|
+
}
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
窗口滚动无需指定 scrollRoot。固定顶栏通过 scrollOffset 留出距离;目录布局可用 `--limr-sticky-top` 调整。切换整篇独立文档时,给 Reader 新的 React key,并提供稳定、页面内唯一的 documentId,以重新建立任务与交互状态。
|
|
41
|
+
|
|
42
|
+
## 自定义组合
|
|
43
|
+
|
|
44
|
+
下面是固定三栏的桌面组合。窄屏响应式切换、目录折叠和自动高度优先使用 Reader;需要自己的断点策略时,由应用决定挂载 sidebar 还是 overlay。
|
|
45
|
+
|
|
46
|
+
```tsx
|
|
47
|
+
import { DocumentProvider, Markdown, TableOfContents, AnnotationSidebar,
|
|
48
|
+
SelectionToolbar } from '@linloir/limr';
|
|
49
|
+
|
|
50
|
+
export function DesktopReader({ source }: { source: string }) {
|
|
51
|
+
return <DocumentProvider source={source}>
|
|
52
|
+
<div style={{ display: 'grid', gridTemplateColumns: '180px minmax(0, 1fr) 280px', gap: 24 }}>
|
|
53
|
+
<TableOfContents style={{ position: 'sticky', top: 24, alignSelf: 'start', maxHeight: '80vh' }} />
|
|
54
|
+
<main className="limr-reader-content" style={{ minHeight: '70vh', overflow: 'clip' }}>
|
|
55
|
+
<Markdown />
|
|
56
|
+
</main>
|
|
57
|
+
<AnnotationSidebar />
|
|
58
|
+
</div>
|
|
59
|
+
<SelectionToolbar />
|
|
60
|
+
</DocumentProvider>;
|
|
61
|
+
}
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
正文与同步侧栏应处于同一滚动容器、同一未缩放坐标系;侧栏由网格行或宿主给定高度,不按所有卡片总高度撑大页面,也不要给同步列表增加独立滚动。卡片内的长内容可以自行滚动。抽屉等普通列表用 `<AnnotationSidebar sync="none" />`,宿主提供高度与 overflow。
|
|
65
|
+
|
|
66
|
+
浮层组合是在同一个 Provider 中放置 `<Markdown />`、`<AnnotationOverlay />`、`<SelectionToolbar />`。两个浮层组件支持 portalRoot,Reader 的组合 props 不直接暴露 portalRoot;需要定制时拆开组合。
|
|
67
|
+
|
|
68
|
+
## 受控数据与文档切换
|
|
69
|
+
|
|
70
|
+
```tsx
|
|
71
|
+
import { useState } from 'react';
|
|
72
|
+
import { Reader, type Annotation } from '@linloir/limr';
|
|
73
|
+
|
|
74
|
+
export function SavedArticle({ id, source }: { id: string; source: string }) {
|
|
75
|
+
const [annotations, setAnnotations] = useState<readonly Annotation[]>([]);
|
|
76
|
+
return <Reader key={id} documentId={id} source={source}
|
|
77
|
+
annotations={annotations} onAnnotationsChange={setAnnotations} />;
|
|
78
|
+
}
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
该片段展示单篇文章的状态。真实应用按文章 ID 加载/保存 annotations,并在切换文章时更换这一数组;React key 不会自动清空宿主状态。不传 annotations 时使用组件内部状态;传入后必须通过回调接收并回传编辑,否则编辑操作会报错。不可原地修改数组或 data。持久化范围与迁移规则见[标注与选区](annotations.md)。
|
|
82
|
+
|
|
83
|
+
## 全局译文模式
|
|
84
|
+
|
|
85
|
+
```tsx
|
|
86
|
+
import { useState } from 'react';
|
|
87
|
+
import { Reader, type TranslationMode } from '@linloir/limr';
|
|
88
|
+
|
|
89
|
+
export function BilingualArticle({ source }: { source: string }) {
|
|
90
|
+
const [mode, setMode] = useState<TranslationMode | undefined>();
|
|
91
|
+
return <Reader source={source}
|
|
92
|
+
parameters={{ translationMode: mode, onTranslationModeChange: setMode }} />;
|
|
93
|
+
}
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
参数仅影响视图,不触发重解析。其他插件也可以注册自己的参数,见[扩展开发](extensions.md)。
|
|
97
|
+
|
|
98
|
+
## SSR 与客户端边界
|
|
99
|
+
|
|
100
|
+
交互组件放在宿主框架的客户端组件边界内(例如需要时在文件顶部使用 `'use client'`),由框架安排 SSR 和 hydration。不要在模块初始化时访问 window/document;保持服务端与客户端的 source、documentId 和初始配置一致。
|
|
101
|
+
|
|
102
|
+
同步 SSR 中,未加载公式输出源码回退。需要首次 HTML 包含完整 KaTeX 内容时,在服务器渲染前调用:
|
|
103
|
+
|
|
104
|
+
```tsx
|
|
105
|
+
import { renderToString } from 'react-dom/server';
|
|
106
|
+
import { Markdown, preloadMath } from '@linloir/limr';
|
|
107
|
+
|
|
108
|
+
export async function renderArticle(source: string) {
|
|
109
|
+
await preloadMath();
|
|
110
|
+
return renderToString(<Markdown source={source} documentId="server-article" />);
|
|
111
|
+
}
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
也可以使用 React 流式 SSR 处理 Suspense。Shiki 在客户端补充高亮,SSR HTML 仍包含代码文本。`load` 标注请求在客户端显示卡片时发起;如需服务端已有内容,可预先放进 annotation.data。宿主仍需把 CSS 和字体资源放到页面能访问的位置。
|
|
115
|
+
|
|
116
|
+
## 已有插件与主题
|
|
117
|
+
|
|
118
|
+
从真实插件包导入工厂,在模块外或 useMemo 中创建稳定 plugins 数组,再传给 Reader。CSS 需要按插件说明显式导入;主题通过 appearance 选择,不是通过 plugins 传入。具体安装与本地 tgz 流程见[插件模块包](plugin-packages.md)。
|
|
119
|
+
|
|
120
|
+
## 常见问题
|
|
121
|
+
|
|
122
|
+
| 现象 | 检查 |
|
|
123
|
+
| --- | --- |
|
|
124
|
+
| 布局没有样式 / 公式错位 | 是否导入 styles 或 base,以及 math.css |
|
|
125
|
+
| 加入插件后默认模块消失 | 是否把追加插件误传给 extensions |
|
|
126
|
+
| 编辑标注后数据不更新 | 是否在受控回调中回传新数组 |
|
|
127
|
+
| 正文、目录不同步 | Markdown 是否意外带 source 创建了第二个 Provider |
|
|
128
|
+
| 内部滚动定位错误 | scrollRoot 是否是真正滚动的 DOM 元素 |
|
|
129
|
+
| 浮层主题不一致 | 是否使用 data-limr-appearance 作用域覆盖 portal |
|
|
130
|
+
| 流式渲染反复全文解析 | 是否每次 render 重建 extensions/plugins,或存在全文 transform |
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
# 内置模块
|
|
2
|
+
|
|
3
|
+
> 阅读提示:本文包含 LiMR 扩展语法和交互示例。部分内容在 GitHub 等普通 Markdown 阅读器中无法完整展示;前往 [GitHub Pages 文档](https://linloir.github.io/LiMR/#/docs/zh-CN/modules) 可获得最佳阅读体验。
|
|
4
|
+
|
|
5
|
+
[文档目录](README.md) · [English](../en/modules.md)
|
|
6
|
+
|
|
7
|
+
## 模块是什么
|
|
8
|
+
|
|
9
|
+
LiMR 的基础解析器认识 CommonMark:标题、段落、列表、链接、图片和代码围栏等。**模块**是在这个基础上增加一组能力的配置对象,例如识别公式、渲染视频、显示引用卡片。开发时这个对象的公开类型叫 `RendererExtension`,所以 API 中也称它为“扩展”。
|
|
10
|
+
|
|
11
|
+
模块可以同时包含语法规则、React 渲染组件、标注类型、工具栏动作和默认参数;并非每个模块都必须有这些部分。例如 `math` 增加公式语法和渲染,`code-highlight` 为现有代码围栏添加高亮,`citation` 同时提供引用语法和标注卡片。
|
|
12
|
+
|
|
13
|
+
**插件**通常指应用自行添加的模块,或第三方包提供的模块。它与内置模块使用相同的接口;npm 包只是分发代码的方式,一个包可以导出多个模块。把模块放进 `plugins` 后,就会追加到已有配置中。
|
|
14
|
+
|
|
15
|
+
## 函数、模块名和标签名的区别
|
|
16
|
+
|
|
17
|
+
以视频为例:
|
|
18
|
+
|
|
19
|
+
```tsx
|
|
20
|
+
import { Reader, standard, replaceExtension, video } from '@linloir/limr';
|
|
21
|
+
|
|
22
|
+
const videoModule = video({ loading: 'click' });
|
|
23
|
+
// videoModule.name === 'video'
|
|
24
|
+
const extensions = replaceExtension(standard(), videoModule);
|
|
25
|
+
|
|
26
|
+
export function Article() {
|
|
27
|
+
return <Reader extensions={extensions} source={'<video src="/lesson.mp4" />'} />;
|
|
28
|
+
}
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
| 写法 | 在哪里使用 | 含义 |
|
|
32
|
+
| --- | --- | --- |
|
|
33
|
+
| `video` / `video(...)` | 应用的 JavaScript / TypeScript | 导入的函数 / 调用这个函数,创建一个模块配置对象 |
|
|
34
|
+
| `videoModule` | 应用代码 | 自己给对象起的变量名,可以改成其他名字 |
|
|
35
|
+
| `'video'` | 模块对象的 `name` 字段 | LiMR 区分、替换和移除模块时使用的稳定标识 |
|
|
36
|
+
| `<video … />` / `<md-video … />` | Markdown 正文 | 模块注册的两种标签,触发同一个视频渲染器 |
|
|
37
|
+
| `::video{…}` | Markdown 正文 | 模块注册的指令写法,也可以使用 `:::video` 容器 |
|
|
38
|
+
|
|
39
|
+
这些名称不要求一致。例如创建代码高亮模块的函数是 `codeHighlight()`,对象的模块名却是 `'code-highlight'`,正文只需要普通代码围栏,不需要 `<code-highlight>` 标签。不要把 `video()` 或字符串 `'video'` 直接写进 Markdown 来启用模块,也不要把字符串放进 `plugins`;传入的是函数返回的对象。
|
|
40
|
+
|
|
41
|
+
`video({ loading: 'click' })` 的参数设置这个 Reader 中视频模块的默认行为;正文的 `<video loading="eager" … />` 则可以覆盖某一个视频的设置。不同模块支持不同参数,见[默认语法](syntax.md)。
|
|
42
|
+
|
|
43
|
+
## 随 LiMR 提供的十个默认模块
|
|
44
|
+
|
|
45
|
+
这些模块都随 `@linloir/limr` 安装,不需要再安装十个 npm 包。`standard()` 返回由下面十个模块对象组成的数组,使用各自的默认配置。
|
|
46
|
+
|
|
47
|
+
| 创建模块的函数 | 对象的 `name` | 增加的能力与正文写法 |
|
|
48
|
+
| --- | --- | --- |
|
|
49
|
+
| `gfm()` | `gfm` | 表格、`~~删除线~~`、任务列表、自动链接、`[^id]` 文末脚注 |
|
|
50
|
+
| `math()` | `math` | `$…$` 行内公式、`$$…$$` 块级公式,需要额外导入 math.css |
|
|
51
|
+
| `codeHighlight()` | `code-highlight` | 使用 Shiki 为带语言标记的代码围栏着色 |
|
|
52
|
+
| `definition()` | `definition` | `<definition>` 释义卡片,可带标题与 Markdown 正文 |
|
|
53
|
+
| `question()` | `question` | `<question>` 练习题,选项、提示、答案与本地判分 |
|
|
54
|
+
| `extras()` | `extras` | `<extras>` / `<extra>` 拓展阅读链接 |
|
|
55
|
+
| `video()` | `video` | `<video>` / `<md-video>` 原生视频、YouTube、Bilibili 嵌入 |
|
|
56
|
+
| `figures()` | `figures` | `<figures>` / `<figure>` 响应式图组、单图和图注 |
|
|
57
|
+
| `citation()` | `citation` | `<citation>` 定义来源,`<ref>` 插入引用角标和卡片 |
|
|
58
|
+
| `translations()` | `translations` | `<translations>` 包含原文与译文,切换原文、译文、对照 |
|
|
59
|
+
|
|
60
|
+
所有函数都可以从 `@linloir/limr` 或 `@linloir/limr/extensions` 导入。`standard()` 是便捷组合函数,自身不是第十一个模块;它的返回值是数组。
|
|
61
|
+
|
|
62
|
+
`Reader`、独立使用的 `Markdown`、`DocumentProvider` 在未传 `extensions` 时自动使用 `standard()`,因此最初的 `<Reader source={source} />` 就支持这些能力。共享 Provider 内的组件使用 Provider 的配置。纯核心 `createDocument()` 默认只启用 CommonMark;需要时显式传入扩展,它不负责显示 React UI。
|
|
63
|
+
|
|
64
|
+
高亮、复制、删除选区标注是工具栏自带动作,高亮以 annotation 数据保存,不是另外一种默认 Markdown 标签。Playground 的旁注、笔记、提问、脚注和模拟 AI 问答来自示例插件,**没有自动装进每个 Reader**。`question()` 的练习题与“提问”标注也不是同一功能。
|
|
65
|
+
|
|
66
|
+
## 按需要组合
|
|
67
|
+
|
|
68
|
+
默认配置足够时不必写 `standard()`。需要定制时,记住这三个操作:
|
|
69
|
+
|
|
70
|
+
| 目的 | 写法 | 结果 |
|
|
71
|
+
| --- | --- | --- |
|
|
72
|
+
| 加自己的功能 | `plugins={[myPlugin]}` | 保留默认模块,追加插件对象 |
|
|
73
|
+
| 只保留指定能力 | `extensions={[gfm(), math()]}` | 替换完整默认模块清单;CommonMark 仍可用 |
|
|
74
|
+
| 调整一个内置模块 | `extensions={replaceExtension(standard(), video({ loading: 'click' }))}` | 按模块 `name` 替换视频,保留其他模块 |
|
|
75
|
+
|
|
76
|
+
移除一个模块也可以过滤数组:`standard().filter(module => module.name !== 'video')`。不需要扩展语法时传 `extensions={[]}`。
|
|
77
|
+
|
|
78
|
+
不要通过 `plugins={[video()]}` 再次追加已默认存在的视频模块:重复模块名、标签或指令会报错。模块没有启用时,未知标签按文本显示;未知指令可能仅留下内部 Markdown;已有的未知标注数据保留并显示通用回退。完整边界见[架构与行为](architecture.md)。
|
|
79
|
+
|
|
80
|
+
接下来可以直接试读[语法示例](syntax.md),或参考[扩展开发](extensions.md)创建模块、[插件模块包](plugin-packages.md)打包分发。
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
# 性能与限制
|
|
2
|
+
|
|
3
|
+
> 阅读提示:本文包含 LiMR 扩展语法和交互示例。部分内容在 GitHub 等普通 Markdown 阅读器中无法完整展示;前往 [GitHub Pages 文档](https://linloir.github.io/LiMR/#/docs/zh-CN/performance) 可获得最佳阅读体验。
|
|
4
|
+
|
|
5
|
+
[文档目录](README.md) · [English](../en/performance.md)
|
|
6
|
+
|
|
7
|
+
以下保留仓库 **2026-10-12** 的历史基准,便于对照。它们不是本次文档/Playground 更新后重新测得的数据,也不是性能保证。
|
|
8
|
+
|
|
9
|
+
环境:Intel Core i7-13700、WSL2/Linux、Node 20.19.5、Chromium 156.0.8078.4、React 19 production。对照为 react-markdown 10.1.0 + remark-gfm 4.0.1。
|
|
10
|
+
|
|
11
|
+
## 浏览器
|
|
12
|
+
|
|
13
|
+
双方渲染同一份 GFM,使用相同排版,只比较正文,不含 Reader 目录/侧栏。每组取三个独立页面样本的中位数,预热并等待字体,交替测量;计时包括同步 React 提交和强制布局,不包括网络、异步重资源和帧间空闲时间。
|
|
14
|
+
|
|
15
|
+
| 场景 | LiMR | react-markdown |
|
|
16
|
+
| --- | ---: | ---: |
|
|
17
|
+
| 87,381 字符,200 节,首次挂载 | 126.1 ms | 134.0 ms |
|
|
18
|
+
| 877,782 字符,2,000 节,首次挂载 | 1,345.4 ms | 1,437.6 ms |
|
|
19
|
+
| 34,860 字符,138 次流式提交,累计同步时间 | 306.1 ms | 1,864.1 ms |
|
|
20
|
+
| 同一流式场景,单次提交 P95 | 2.9 ms | 23.7 ms |
|
|
21
|
+
|
|
22
|
+
最后一次完整解析包含在统计中;P95 不代表每帧都低于 16ms。原始数据:[browser.json](../../benchmarks/results/browser.json)。
|
|
23
|
+
|
|
24
|
+
## 选区与 SSR
|
|
25
|
+
|
|
26
|
+
历史长文拖选绘制延迟 P95 为 25.2–28.5ms,代码为 18.9–19.4ms。这是 selectionchange 到覆盖层变更的延迟,不是硬件输入到屏幕的延迟。同一自动化拖动手势总耗时 LiMR 3.23s,对照 1.52s;不能因为 rAF 接近就声称成本相同。数据:[selection.json](../../benchmarks/results/selection.json)。
|
|
27
|
+
|
|
28
|
+
| Node 场景 | LiMR | 对照 |
|
|
29
|
+
| --- | ---: | ---: |
|
|
30
|
+
| 87,381 字符,SSR | 123.85 ms | 135.80 ms |
|
|
31
|
+
| 877,782 字符,SSR | 5,508.29 ms | 2,720.70 ms |
|
|
32
|
+
| 34,860 字符,137 次核心追加及收尾 | 164.04 ms | 2,673.74 ms |
|
|
33
|
+
|
|
34
|
+
大文档 SSR 波动明显,LiMR 的样本为 2.64–9.10s,对照为 2.70–5.03s;这组数据不显示 SSR 优势。核心追加对照每次重新解析完整前缀。数据:[node.json](../../benchmarks/results/node.json)。
|
|
35
|
+
|
|
36
|
+
## 使用限制
|
|
37
|
+
|
|
38
|
+
保留全部 DOM 支持搜索、选择和打印,因此首次完整长文渲染仍有成本。增量解析复用稳定前缀;长尾块、全文 transform、定义变化、中间编辑和收尾仍可昂贵。保持 extensions/plugins 引用稳定,按需要限制高亮语言和长度。
|
|
39
|
+
|
|
40
|
+
这些数据不覆盖大型公式集、Shiki 冷启动、密集标注、第三方插件、完整 Reader 布局或实体移动设备。字体安装体积与浏览器请求量也不同,见[样式与字体](styles.md)。
|
|
41
|
+
|
|
42
|
+
## 在仓库复现
|
|
43
|
+
|
|
44
|
+
```sh
|
|
45
|
+
NODE_ENV=production pnpm bench
|
|
46
|
+
pnpm exec vite build
|
|
47
|
+
pnpm exec vite preview --host 127.0.0.1 --port 4174
|
|
48
|
+
# 在另一个终端执行:
|
|
49
|
+
pnpm bench:browser
|
|
50
|
+
pnpm bench:selection
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
运行时避免同时构建或跑其他基准;脚本写入 benchmarks/results。每次使用自己的硬件、内容和已构建版本重新判断,不把历史测量当作最新包验收结果。
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
# 插件模块包
|
|
2
|
+
|
|
3
|
+
> 阅读提示:本文包含 LiMR 扩展语法和交互示例。部分内容在 GitHub 等普通 Markdown 阅读器中无法完整展示;前往 [GitHub Pages 文档](https://linloir.github.io/LiMR/#/docs/zh-CN/plugin-packages) 可获得最佳阅读体验。
|
|
4
|
+
|
|
5
|
+
[文档目录](README.md) · [English](../en/plugin-packages.md)
|
|
6
|
+
|
|
7
|
+
一个插件包导出返回 RendererExtension 的工厂,消费者安装后通过 plugins 追加。插件可同时提供语法、标注、动作和参数,CSS 独立导出。它不需要修改 LiMR 源码。
|
|
8
|
+
|
|
9
|
+
## 可复制模板
|
|
10
|
+
|
|
11
|
+
完整模板位于 [plugin-template](../../skills/limr-plugin-development/assets/plugin-template/README.md),包含 TypeScript 源码、类型构建、CSS、package.json 和 tsup 配置。它演示 reading-tip 区块、example:note 卡片和保存选区动作。
|
|
12
|
+
|
|
13
|
+
安装 LiMR 后,在应用目录执行:
|
|
14
|
+
|
|
15
|
+
```sh
|
|
16
|
+
mkdir -p packages/limr-reading-tools
|
|
17
|
+
cp -R node_modules/@linloir/limr/skills/limr-plugin-development/assets/plugin-template/. packages/limr-reading-tools/
|
|
18
|
+
cd packages/limr-reading-tools
|
|
19
|
+
npm install
|
|
20
|
+
npm run typecheck
|
|
21
|
+
npm run build
|
|
22
|
+
npm pack
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
源码仓库中的对应路径为 skills/limr-plugin-development/assets/plugin-template。替换模板的 @example 包名、扩展名、类型名和动作 ID,再实现业务数据与 UI;模板的 private: true 用于防止示例名被误发布,不影响本地打包。
|
|
26
|
+
|
|
27
|
+
## 包契约
|
|
28
|
+
|
|
29
|
+
| 字段 | 要求 |
|
|
30
|
+
| --- | --- |
|
|
31
|
+
| type | module |
|
|
32
|
+
| exports["."] | import 指向 ESM,types 指向 .d.ts |
|
|
33
|
+
| exports["./styles.css"] | 插件需要 CSS 时显式导出 |
|
|
34
|
+
| files | 包含构建输出与使用说明 |
|
|
35
|
+
| sideEffects | CSS 使用 ["**/*.css"],避免被构建工具移除 |
|
|
36
|
+
| peerDependencies | @linloir/limr、react;直接依赖 react-dom API 时再声明 react-dom |
|
|
37
|
+
| 构建 external | LiMR、React、react/jsx-runtime,以及使用到的 React DOM 入口 |
|
|
38
|
+
|
|
39
|
+
LiMR 或 React 的第二份内置副本会破坏上下文/Hook 共享。不要打包宿主依赖,不要深度导入 LiMR 内部文件。参数声明合并必须随导出的类型分发。
|
|
40
|
+
|
|
41
|
+
## 在应用中引入
|
|
42
|
+
|
|
43
|
+
下方 @example 包来自模板,并未发布到 npm。先安装本地打出的归档;生成文件名以 npm pack 输出为准:
|
|
44
|
+
|
|
45
|
+
```sh
|
|
46
|
+
npm install ./packages/limr-reading-tools/example-limr-reading-tools-0.1.0.tgz
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
```tsx
|
|
50
|
+
import { Reader } from '@linloir/limr';
|
|
51
|
+
import { readingTools } from '@example/limr-reading-tools';
|
|
52
|
+
import '@linloir/limr/styles.css';
|
|
53
|
+
import '@example/limr-reading-tools/styles.css';
|
|
54
|
+
|
|
55
|
+
const plugins = [readingTools({ label: '阅读笔记' })];
|
|
56
|
+
|
|
57
|
+
export function Article({ source }: { source: string }) {
|
|
58
|
+
return <Reader source={source} plugins={plugins} />;
|
|
59
|
+
}
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
真实第三方包使用它的包名和 peer 版本约束。改写已存在的默认模块使用 extensions + replaceExtension,不要重复注册同一标签。字体、公式样式、持久化和应用服务仍由宿主配置。
|
|
63
|
+
|
|
64
|
+
## 验证可分发性
|
|
65
|
+
|
|
66
|
+
本地类型检查通过后,还需把真实 tgz 安装到独立消费者中:检查公开导入、SSR、CSS 资源、默认模块仍可用和新增交互。支持 React 18/19 的包应分别验证两种宿主。语法插件检查流式结束与一次性解析等价;交互插件检查桌面侧栏、窄屏浮层、键盘、异步取消和两种主题。
|
|
67
|
+
|
|
68
|
+
LiMR 仓库的 [reading-tools](../../examples/packages/reading-tools/index.js) 和 [paper-theme](../../examples/packages/paper-theme/index.css) 是另一组可打包示例。AI 可使用[插件开发 Skill](ai-skills.md),其中的模板可独立复制,引用文件无需依赖本仓库路径。
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
# 样式与字体
|
|
2
|
+
|
|
3
|
+
> 阅读提示:本文包含 LiMR 扩展语法和交互示例。部分内容在 GitHub 等普通 Markdown 阅读器中无法完整展示;前往 [GitHub Pages 文档](https://linloir.github.io/LiMR/#/docs/zh-CN/styles) 可获得最佳阅读体验。
|
|
4
|
+
|
|
5
|
+
[文档目录](README.md) · [English](../en/styles.md)
|
|
6
|
+
|
|
7
|
+
## 基础样式和主题
|
|
8
|
+
|
|
9
|
+
默认 CSS 在 `@layer limr` 中,宿主普通 CSS 可覆盖。选择 styles.css(带 Maple)或 styles/base.css(不加载 Maple);公式另加 math.css。
|
|
10
|
+
|
|
11
|
+
```tsx
|
|
12
|
+
import { Reader } from '@linloir/limr';
|
|
13
|
+
import '@linloir/limr/styles/base.css';
|
|
14
|
+
import './paper.css';
|
|
15
|
+
|
|
16
|
+
export function Paper({ source }: { source: string }) {
|
|
17
|
+
return <Reader source={source} appearance="paper" theme="auto" />;
|
|
18
|
+
}
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
paper.css 示例:
|
|
22
|
+
|
|
23
|
+
```css
|
|
24
|
+
[data-limr-appearance='paper'] {
|
|
25
|
+
--limr-font-text: Georgia, 'Noto Serif SC', serif;
|
|
26
|
+
--limr-content-width: 820px;
|
|
27
|
+
--limr-accent: #8b6548;
|
|
28
|
+
}
|
|
29
|
+
[data-limr-appearance='paper'][data-limr-theme='dark'] {
|
|
30
|
+
--limr-accent: #d1ab84;
|
|
31
|
+
}
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
appearance 会传给 portal 中的浮层/工具栏;不要只用 `.limr-reader .your-selector` 匹配这些元素。theme: auto 通过系统偏好选择内置主题;自定义 auto 深色覆盖还需对应 prefers-color-scheme 媒体规则。独立 CSS 包示例见 [paper-theme](../../examples/packages/paper-theme/index.css)。
|
|
35
|
+
|
|
36
|
+
## 常用变量
|
|
37
|
+
|
|
38
|
+
| 变量 | 用途 |
|
|
39
|
+
| --- | --- |
|
|
40
|
+
| --limr-font-text / --limr-font-code | 正文 / 代码字体 |
|
|
41
|
+
| --limr-font-size / --limr-line-height | 基础排版 |
|
|
42
|
+
| --limr-font-features | 正文默认 'palt' 1,字体支持时采用比例标点 |
|
|
43
|
+
| --limr-emphasis-spacing / --limr-list-item-gap | 斜体末尾 / 列表间距 |
|
|
44
|
+
| --limr-text / --limr-heading / --limr-secondary / --limr-muted | 文字层次 |
|
|
45
|
+
| --limr-background / --limr-surface / --limr-border | 背景和边框 |
|
|
46
|
+
| --limr-link / --limr-accent / --limr-selection | 链接、激活、选区 |
|
|
47
|
+
| --limr-content-width / --limr-reader-width | 正文 / 总宽,默认 900px / 1800px |
|
|
48
|
+
| --limr-toc-width / --limr-sidebar-width / --limr-column-gap | 侧栏宽度与列间隔 |
|
|
49
|
+
| --limr-sticky-top | 吸顶偏移 |
|
|
50
|
+
| --limr-reader-bottom-space / --limr-reader-min-height | 短正文高度与底部预留 |
|
|
51
|
+
| --limr-sidebar-padding-inline / --limr-sidebar-reference-gap | 侧栏内容统一留白(10px)和关联区间隔(1px) |
|
|
52
|
+
| --limr-overlay-duration / --limr-overlay-morph-duration | 浮层进退(560ms)和预览/展开转换(480ms) |
|
|
53
|
+
| --limr-overlay-z / --limr-toolbar-z | 浮层层级 |
|
|
54
|
+
| --limr-overflow-fade-size | 溢出渐隐,默认 24px,0px 关闭 |
|
|
55
|
+
| --limr-toolbar-panel-max-height | 工具栏面板高度上限 |
|
|
56
|
+
| --limr-tool-icon-size / --limr-mark-size | 工具图标 / 正文角标尺寸 |
|
|
57
|
+
|
|
58
|
+
runtime 测量的 --limr-toolbar-available-height、--limr-toolbar-panel-height、--limr-toc-available-height 是布局输出,不应由主题覆盖。sidebar 的局部颜色通常由 AnnotationType.sidebar 配置提供,见[扩展开发](extensions.md)。
|
|
59
|
+
|
|
60
|
+
## 字体和资源体积
|
|
61
|
+
|
|
62
|
+
Maple Mono NF CN v7.9 的本地 WOFF2 子集随包分发,衍生字体名为 LiMR Maple Mono NF CN,OFL 许可证随资源提供。unicode-range 和 font-display: swap 限制运行时按需请求,但完整字体资产仍约 31MB,base.css 只避免浏览器加载,不减少 npm 安装体积。
|
|
63
|
+
|
|
64
|
+
正文不自动加载中文字体;可使用系统回退、自托管或宿主选定的字体服务。Playground 的 Noto Serif SC 是演示项目依赖。不要假定任何字体覆盖所有文字。
|
|
65
|
+
|
|
66
|
+
默认字体/公式样式不依赖外部 CDN。带哈希的字体资源可由宿主设置长期 immutable 缓存;库无法替服务器设置 HTTP 缓存头。远程图片、视频和插件请求由内容和宿主决定。
|
|
67
|
+
|
|
68
|
+
## 布局与交互约定
|
|
69
|
+
|
|
70
|
+
同步侧栏的高度由阅读区域决定,不能用所有卡片总高撑大页面。模块只控制自己的内容,骨架负责灰色关联区、摘要与卡片外壳;避免重复 padding 或空段落补偿。大卡片从预览位置过渡,减少动态效果设置会关闭动画。
|
|
71
|
+
|
|
72
|
+
密集的文末引用在静止时向上借用空余空间。悬浮或激活时优先对齐当前条目,允许邻项在边缘裁切;角标悬浮时可压缩可选摘要,为关联区和标题留出空间。展开内容受可见阅读区域高度限制,并可在卡片内滚动。侧栏条目即使被向上收拢,也会反向对齐正文。取消激活后按当前卡片高度平滑回位,使用 `--limr-motion-slow` 和 `--limr-ease`,保持卡片间距与页面高度。
|
|
73
|
+
|
|
74
|
+
选区保留原生浏览器范围与键盘行为,绘制层只计算可见片段。代码支持局部高亮,公式整体选择,图片高亮使用边框。打印隐藏绘制层并保留正文。移动端自动化检查不等于 iOS/Android 原生拖动手柄和真机验收。
|
|
75
|
+
|
|
76
|
+
## 仅 CSS 的独立主题
|
|
77
|
+
|
|
78
|
+
包可导出 CSS,files 包含 CSS,sideEffects 为 `["**/*.css"]`;不需要 React 或解析器运行时依赖。规则用独立 appearance 名隔离,覆盖浅色、深色、auto 和 portal,并在同页两个不同主题 Reader 中检查。
|
|
79
|
+
|
|
80
|
+
展开工具栏面板使用工具栏的全部可用宽度。宽度由当前操作按钮行与可用视口决定,展开内容不参与工具栏的固有宽度计算,不会反向撑大工具栏。高亮样式按钮按文字自然宽度排列,空间不足时整项换行并左对齐。多行下划线需要保持内部 span 为 inline,不把它直接作为 flex 子项。
|