@jianwen-lang/parser 0.1.1 → 0.1.2
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 +16 -1
- package/dist/core/block/rules/attribute-line.d.ts +13 -0
- package/dist/core/block/rules/attribute-line.js +227 -0
- package/dist/core/block/rules/code-block.d.ts +2 -0
- package/dist/core/block/rules/code-block.js +73 -0
- package/dist/core/block/rules/code-fence.d.ts +15 -0
- package/dist/core/block/rules/code-fence.js +37 -0
- package/dist/core/block/rules/content-title.d.ts +12 -0
- package/dist/core/block/rules/content-title.js +70 -0
- package/dist/core/block/rules/footnotes.d.ts +9 -0
- package/dist/core/block/rules/footnotes.js +105 -0
- package/dist/core/block/rules/html.d.ts +7 -0
- package/dist/core/block/rules/html.js +48 -0
- package/dist/core/block/rules/image.d.ts +9 -0
- package/dist/core/block/rules/image.js +78 -0
- package/dist/core/block/rules/list.d.ts +3 -0
- package/dist/core/block/rules/list.js +275 -0
- package/dist/core/block/rules/paragraph.d.ts +6 -0
- package/dist/core/block/rules/paragraph.js +55 -0
- package/dist/core/block/rules/quote.d.ts +13 -0
- package/dist/core/block/rules/quote.js +104 -0
- package/dist/core/block/rules/table.d.ts +2 -0
- package/dist/core/block/rules/table.js +199 -0
- package/dist/core/block/runtime.d.ts +25 -0
- package/dist/core/block/runtime.js +116 -0
- package/dist/core/block-parser.js +183 -1322
- package/dist/html/convert.d.ts +8 -0
- package/dist/html/convert.js +22 -0
- package/dist/html/render/blocks.d.ts +15 -0
- package/dist/html/render/blocks.js +50 -6
- package/dist/html/render/html.d.ts +16 -0
- package/dist/html/render/html.js +48 -14
- package/dist/html/render/utils.d.ts +1 -0
- package/package.json +5 -1
package/README.md
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
# @jianwen-lang/parser `0.1.
|
|
1
|
+
# @jianwen-lang/parser `0.1.2`
|
|
2
2
|
|
|
3
3
|
Jianwen(简文)是一种类似 Markdown 的轻量级标记语言,针对网页博客、公众号等内容发布场景的排版能力进行优化。本包提供 Jianwen 的 **TypeScript 核心解析器**(输出结构化 AST)以及 **HTML 渲染器**,用于在编辑器、渲染服务、静态站点生成等场景复用同一套解析语义。
|
|
4
4
|
|
|
@@ -55,6 +55,19 @@ const { html, ast, errors } = renderJianwenToHtmlDocument(source, {
|
|
|
55
55
|
});
|
|
56
56
|
```
|
|
57
57
|
|
|
58
|
+
如果你在做编辑器并需要“渲染区块 ↔ 源码行范围”映射(例如拖拽区块后回写源码),可以使用:
|
|
59
|
+
|
|
60
|
+
```ts
|
|
61
|
+
import { renderJianwenToHtmlDocumentWithBlockMap } from '@jianwen-lang/parser';
|
|
62
|
+
|
|
63
|
+
const result = renderJianwenToHtmlDocumentWithBlockMap(source, {
|
|
64
|
+
document: { format: true },
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
console.log(result.groups);
|
|
68
|
+
// [{ id, kind, startLine, endLine, readOnly, origin? }, ...]
|
|
69
|
+
```
|
|
70
|
+
|
|
58
71
|
## Include:文件/标签展开(可选)
|
|
59
72
|
|
|
60
73
|
解析阶段支持将 `[@](path)`(文件 include)或 `[@=tag]`(标签 include)按需展开。开启方式:
|
|
@@ -83,7 +96,9 @@ const { ast, errors } = parseJianwenWithErrors(source, {
|
|
|
83
96
|
- `ParseOptions`:`expandInclude` / `includeMaxDepth` / `loadFile`
|
|
84
97
|
- 渲染
|
|
85
98
|
- `renderDocumentToHtml(doc, options?) => string`
|
|
99
|
+
- `renderDocumentToHtmlWithBlockMap(doc, options?) => { html; groups }`
|
|
86
100
|
- `renderJianwenToHtmlDocument(source, options?) => { html; ast; errors }`
|
|
101
|
+
- `renderJianwenToHtmlDocumentWithBlockMap(source, options?) => { html; ast; errors; groups }`
|
|
87
102
|
- `buildHtmlDocument(bodyHtml, options?) => string`
|
|
88
103
|
|
|
89
104
|
## 规范与扩展
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { BlockAttributes } from '../../ast';
|
|
2
|
+
import { ParseError } from '../../errors';
|
|
3
|
+
import { PendingBlockContext } from '../types';
|
|
4
|
+
export interface ConsumeAttributeLineOptions {
|
|
5
|
+
text: string;
|
|
6
|
+
lineNumber: number;
|
|
7
|
+
errors: ParseError[];
|
|
8
|
+
pending: PendingBlockContext;
|
|
9
|
+
fallbackPosition?: BlockAttributes['position'];
|
|
10
|
+
tabCount?: number;
|
|
11
|
+
}
|
|
12
|
+
export declare function isAttributeOnlyLine(text: string): boolean;
|
|
13
|
+
export declare function tryConsumeAttributeOnlyLine(options: ConsumeAttributeLineOptions): boolean;
|
|
@@ -0,0 +1,227 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.isAttributeOnlyLine = isAttributeOnlyLine;
|
|
4
|
+
exports.tryConsumeAttributeOnlyLine = tryConsumeAttributeOnlyLine;
|
|
5
|
+
const diagnostics_1 = require("../../diagnostics");
|
|
6
|
+
const include_1 = require("./include");
|
|
7
|
+
const MULTI_ARROW_WARNING_CODE = 'layout-multi-arrow';
|
|
8
|
+
const UNKNOWN_ATTRIBUTE_WARNING_CODE = 'unknown-attribute-token';
|
|
9
|
+
function isAttributeOnlyLine(text) {
|
|
10
|
+
if (text.length === 0) {
|
|
11
|
+
return false;
|
|
12
|
+
}
|
|
13
|
+
const trimmed = text.trim();
|
|
14
|
+
if ((0, include_1.matchInclude)(trimmed)) {
|
|
15
|
+
return false;
|
|
16
|
+
}
|
|
17
|
+
let i = 0;
|
|
18
|
+
while (i < text.length) {
|
|
19
|
+
const ch = text[i];
|
|
20
|
+
if (ch === '[') {
|
|
21
|
+
const end = text.indexOf(']', i + 1);
|
|
22
|
+
if (end === -1) {
|
|
23
|
+
return false;
|
|
24
|
+
}
|
|
25
|
+
i = end + 1;
|
|
26
|
+
continue;
|
|
27
|
+
}
|
|
28
|
+
if (ch === ' ' || ch === '\t') {
|
|
29
|
+
i += 1;
|
|
30
|
+
continue;
|
|
31
|
+
}
|
|
32
|
+
return false;
|
|
33
|
+
}
|
|
34
|
+
return true;
|
|
35
|
+
}
|
|
36
|
+
function tryConsumeAttributeOnlyLine(options) {
|
|
37
|
+
const result = parseAttributeLine(options.text, options.lineNumber, options.errors, options.pending.attrs, options.fallbackPosition, options.tabCount);
|
|
38
|
+
if (!result.recognizedAny) {
|
|
39
|
+
return false;
|
|
40
|
+
}
|
|
41
|
+
if (result.attrs) {
|
|
42
|
+
options.pending.attrs = mergeBlockAttributes(options.pending.attrs, result.attrs);
|
|
43
|
+
}
|
|
44
|
+
if (result.foldNext) {
|
|
45
|
+
options.pending.foldNext = true;
|
|
46
|
+
}
|
|
47
|
+
if (result.tagName) {
|
|
48
|
+
options.pending.tagName = result.tagName;
|
|
49
|
+
}
|
|
50
|
+
if (result.isComment) {
|
|
51
|
+
options.pending.isComment = true;
|
|
52
|
+
}
|
|
53
|
+
if (result.isDisabled) {
|
|
54
|
+
options.pending.isDisabled = true;
|
|
55
|
+
}
|
|
56
|
+
if (result.isSheet) {
|
|
57
|
+
options.pending.isSheet = true;
|
|
58
|
+
}
|
|
59
|
+
if (result.isHtml) {
|
|
60
|
+
options.pending.isHtml = true;
|
|
61
|
+
}
|
|
62
|
+
return true;
|
|
63
|
+
}
|
|
64
|
+
function parseAttributeLine(text, lineNumber, errors, baseAttrs, fallbackPosition, tabCount) {
|
|
65
|
+
const result = {
|
|
66
|
+
attrs: undefined,
|
|
67
|
+
foldNext: false,
|
|
68
|
+
tagName: undefined,
|
|
69
|
+
isComment: false,
|
|
70
|
+
isDisabled: false,
|
|
71
|
+
isSheet: false,
|
|
72
|
+
isHtml: false,
|
|
73
|
+
recognizedAny: false,
|
|
74
|
+
};
|
|
75
|
+
let attrs = baseAttrs ? { ...baseAttrs } : undefined;
|
|
76
|
+
const unknownTokens = [];
|
|
77
|
+
let arrowCount = 0;
|
|
78
|
+
let i = 0;
|
|
79
|
+
while (i < text.length) {
|
|
80
|
+
const start = text.indexOf('[', i);
|
|
81
|
+
if (start === -1) {
|
|
82
|
+
break;
|
|
83
|
+
}
|
|
84
|
+
const end = text.indexOf(']', start + 1);
|
|
85
|
+
if (end === -1) {
|
|
86
|
+
break;
|
|
87
|
+
}
|
|
88
|
+
const inside = text.slice(start + 1, end).trim();
|
|
89
|
+
if (inside.length === 0) {
|
|
90
|
+
i = end + 1;
|
|
91
|
+
continue;
|
|
92
|
+
}
|
|
93
|
+
const parts = inside.split(',');
|
|
94
|
+
for (const rawPart of parts) {
|
|
95
|
+
const part = rawPart.trim();
|
|
96
|
+
if (part.length === 0) {
|
|
97
|
+
continue;
|
|
98
|
+
}
|
|
99
|
+
if (part === 'c') {
|
|
100
|
+
attrs = ensureBlockAttributes(attrs);
|
|
101
|
+
attrs.align = 'center';
|
|
102
|
+
result.recognizedAny = true;
|
|
103
|
+
continue;
|
|
104
|
+
}
|
|
105
|
+
if (part === 'r') {
|
|
106
|
+
attrs = ensureBlockAttributes(attrs);
|
|
107
|
+
attrs.align = 'right';
|
|
108
|
+
result.recognizedAny = true;
|
|
109
|
+
continue;
|
|
110
|
+
}
|
|
111
|
+
if (part === '->' || part === '<-' || part === '<->') {
|
|
112
|
+
arrowCount += 1;
|
|
113
|
+
result.recognizedAny = true;
|
|
114
|
+
if (part === '->' && arrowCount > 2) {
|
|
115
|
+
(0, diagnostics_1.reportParseWarning)(errors, {
|
|
116
|
+
message: 'More than two [->] attributes in a row; extra [->] will be treated as plain text.',
|
|
117
|
+
line: lineNumber,
|
|
118
|
+
code: MULTI_ARROW_WARNING_CODE,
|
|
119
|
+
});
|
|
120
|
+
continue;
|
|
121
|
+
}
|
|
122
|
+
attrs = ensureBlockAttributes(attrs);
|
|
123
|
+
if (part === '->' || part === '<->') {
|
|
124
|
+
const basePosition = attrs.position ??
|
|
125
|
+
fallbackPosition ??
|
|
126
|
+
(tabCount !== undefined ? mapTabsToPosition(tabCount) : undefined);
|
|
127
|
+
attrs.position = shiftPositionRight(basePosition);
|
|
128
|
+
attrs.sameLine = true;
|
|
129
|
+
}
|
|
130
|
+
if (part === '<-' || part === '<->') {
|
|
131
|
+
attrs.truncateRight = true;
|
|
132
|
+
}
|
|
133
|
+
continue;
|
|
134
|
+
}
|
|
135
|
+
if (part === 'fold') {
|
|
136
|
+
result.foldNext = true;
|
|
137
|
+
result.recognizedAny = true;
|
|
138
|
+
continue;
|
|
139
|
+
}
|
|
140
|
+
if (part === 'sheet') {
|
|
141
|
+
result.isSheet = true;
|
|
142
|
+
result.recognizedAny = true;
|
|
143
|
+
continue;
|
|
144
|
+
}
|
|
145
|
+
if (part === 'html') {
|
|
146
|
+
result.isHtml = true;
|
|
147
|
+
result.recognizedAny = true;
|
|
148
|
+
continue;
|
|
149
|
+
}
|
|
150
|
+
if (part === 'comment') {
|
|
151
|
+
result.isComment = true;
|
|
152
|
+
result.recognizedAny = true;
|
|
153
|
+
continue;
|
|
154
|
+
}
|
|
155
|
+
if (part === 'disable' || part === 'd') {
|
|
156
|
+
result.isDisabled = true;
|
|
157
|
+
result.recognizedAny = true;
|
|
158
|
+
continue;
|
|
159
|
+
}
|
|
160
|
+
if (part.startsWith('tag=')) {
|
|
161
|
+
result.tagName = part.slice('tag='.length);
|
|
162
|
+
result.recognizedAny = true;
|
|
163
|
+
continue;
|
|
164
|
+
}
|
|
165
|
+
if (part.startsWith('t=')) {
|
|
166
|
+
result.tagName = part.slice(2);
|
|
167
|
+
result.recognizedAny = true;
|
|
168
|
+
continue;
|
|
169
|
+
}
|
|
170
|
+
if (part.startsWith('f=')) {
|
|
171
|
+
result.tagName = part.slice(2);
|
|
172
|
+
result.recognizedAny = true;
|
|
173
|
+
continue;
|
|
174
|
+
}
|
|
175
|
+
unknownTokens.push(part);
|
|
176
|
+
continue;
|
|
177
|
+
}
|
|
178
|
+
i = end + 1;
|
|
179
|
+
}
|
|
180
|
+
if (!result.recognizedAny && unknownTokens.length > 0) {
|
|
181
|
+
for (const token of unknownTokens) {
|
|
182
|
+
(0, diagnostics_1.reportParseWarning)(errors, {
|
|
183
|
+
message: `Unknown block attribute token "${token}"`,
|
|
184
|
+
line: lineNumber,
|
|
185
|
+
code: UNKNOWN_ATTRIBUTE_WARNING_CODE,
|
|
186
|
+
});
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
result.attrs = attrs;
|
|
190
|
+
return result;
|
|
191
|
+
}
|
|
192
|
+
function ensureBlockAttributes(attrs) {
|
|
193
|
+
if (attrs) {
|
|
194
|
+
return attrs;
|
|
195
|
+
}
|
|
196
|
+
return {};
|
|
197
|
+
}
|
|
198
|
+
function mergeBlockAttributes(base, extra) {
|
|
199
|
+
if (!base) {
|
|
200
|
+
return { ...extra };
|
|
201
|
+
}
|
|
202
|
+
return { ...base, ...extra };
|
|
203
|
+
}
|
|
204
|
+
function mapTabsToPosition(tabCount) {
|
|
205
|
+
if (tabCount === 0) {
|
|
206
|
+
return 'L';
|
|
207
|
+
}
|
|
208
|
+
if (tabCount === 1) {
|
|
209
|
+
return 'C';
|
|
210
|
+
}
|
|
211
|
+
if (tabCount >= 2) {
|
|
212
|
+
return 'R';
|
|
213
|
+
}
|
|
214
|
+
return undefined;
|
|
215
|
+
}
|
|
216
|
+
function shiftPositionRight(position) {
|
|
217
|
+
if (position === 'L') {
|
|
218
|
+
return 'C';
|
|
219
|
+
}
|
|
220
|
+
if (position === 'C') {
|
|
221
|
+
return 'R';
|
|
222
|
+
}
|
|
223
|
+
if (position === 'R') {
|
|
224
|
+
return 'R';
|
|
225
|
+
}
|
|
226
|
+
return 'C';
|
|
227
|
+
}
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.tryParseCodeBlock = tryParseCodeBlock;
|
|
4
|
+
const diagnostics_1 = require("../../diagnostics");
|
|
5
|
+
const location_1 = require("../../location");
|
|
6
|
+
const lexer_1 = require("../../../lexer/lexer");
|
|
7
|
+
const code_fence_1 = require("./code-fence");
|
|
8
|
+
function tryParseCodeBlock(ctx) {
|
|
9
|
+
const codeFenceStart = (0, code_fence_1.matchCodeFenceStart)(ctx.trimmedContent);
|
|
10
|
+
const attrCodeFenceStart = codeFenceStart ? null : (0, code_fence_1.matchAttributedCodeFence)(ctx.trimmedContent);
|
|
11
|
+
const effectiveCodeFence = codeFenceStart || attrCodeFenceStart;
|
|
12
|
+
if (!effectiveCodeFence) {
|
|
13
|
+
return null;
|
|
14
|
+
}
|
|
15
|
+
const rawLines = [ctx.lineInfo.raw];
|
|
16
|
+
const codeLines = [];
|
|
17
|
+
const fenceIndent = ctx.lineInfo.tabCount;
|
|
18
|
+
let jCode = ctx.index + 1;
|
|
19
|
+
let closed = false;
|
|
20
|
+
while (jCode < ctx.lines.length) {
|
|
21
|
+
const nextRaw = ctx.lines[jCode];
|
|
22
|
+
if (nextRaw === undefined) {
|
|
23
|
+
jCode += 1;
|
|
24
|
+
continue;
|
|
25
|
+
}
|
|
26
|
+
const nextInfo = (0, lexer_1.getLineInfo)(nextRaw);
|
|
27
|
+
const nextTrimmed = nextInfo.content.trim();
|
|
28
|
+
if ((0, code_fence_1.isCodeFenceEnd)(nextTrimmed)) {
|
|
29
|
+
rawLines.push(nextInfo.raw);
|
|
30
|
+
closed = true;
|
|
31
|
+
jCode += 1;
|
|
32
|
+
break;
|
|
33
|
+
}
|
|
34
|
+
rawLines.push(nextInfo.raw);
|
|
35
|
+
let codeLine = nextRaw;
|
|
36
|
+
for (let t = 0; t < fenceIndent && codeLine.startsWith('\t'); t++) {
|
|
37
|
+
codeLine = codeLine.slice(1);
|
|
38
|
+
}
|
|
39
|
+
codeLines.push(codeLine);
|
|
40
|
+
jCode += 1;
|
|
41
|
+
}
|
|
42
|
+
if (!closed) {
|
|
43
|
+
(0, diagnostics_1.reportParseError)(ctx.errors, {
|
|
44
|
+
message: 'Code block is not closed with ```',
|
|
45
|
+
line: ctx.index + 1,
|
|
46
|
+
});
|
|
47
|
+
}
|
|
48
|
+
const blockAttrs = ctx.buildBlockAttrs(ctx.lineInfo.tabCount);
|
|
49
|
+
const isHtmlBlock = ctx.pending.isHtml || (attrCodeFenceStart?.isHtml ?? false);
|
|
50
|
+
let block;
|
|
51
|
+
if (ctx.pending.isDisabled) {
|
|
52
|
+
const disabled = {
|
|
53
|
+
type: 'disabledBlock',
|
|
54
|
+
raw: rawLines.join('\n'),
|
|
55
|
+
blockAttrs,
|
|
56
|
+
};
|
|
57
|
+
(0, location_1.setNodeLocation)(disabled, ctx.lineLocation);
|
|
58
|
+
block = disabled;
|
|
59
|
+
}
|
|
60
|
+
else {
|
|
61
|
+
const codeBlock = {
|
|
62
|
+
type: 'code',
|
|
63
|
+
language: effectiveCodeFence.language,
|
|
64
|
+
value: codeLines.join('\n'),
|
|
65
|
+
htmlLike: isHtmlBlock ? true : undefined,
|
|
66
|
+
blockAttrs,
|
|
67
|
+
};
|
|
68
|
+
(0, location_1.setNodeLocation)(codeBlock, ctx.lineLocation);
|
|
69
|
+
block = codeBlock;
|
|
70
|
+
}
|
|
71
|
+
ctx.commitBlock(block, blockAttrs, { allowTag: !ctx.pending.isDisabled });
|
|
72
|
+
return jCode;
|
|
73
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
interface CodeFenceStartMatchResult {
|
|
2
|
+
language?: string;
|
|
3
|
+
}
|
|
4
|
+
export interface AttributedCodeFenceResult {
|
|
5
|
+
language?: string;
|
|
6
|
+
isHtml: boolean;
|
|
7
|
+
}
|
|
8
|
+
export declare function matchCodeFenceStart(trimmed: string): CodeFenceStartMatchResult | undefined;
|
|
9
|
+
export declare function isCodeFenceEnd(trimmed: string): boolean;
|
|
10
|
+
/**
|
|
11
|
+
* Match code fence with optional leading attributes like [html]```
|
|
12
|
+
* Returns the language and whether it's an HTML block
|
|
13
|
+
*/
|
|
14
|
+
export declare function matchAttributedCodeFence(trimmed: string): AttributedCodeFenceResult | undefined;
|
|
15
|
+
export {};
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.matchCodeFenceStart = matchCodeFenceStart;
|
|
4
|
+
exports.isCodeFenceEnd = isCodeFenceEnd;
|
|
5
|
+
exports.matchAttributedCodeFence = matchAttributedCodeFence;
|
|
6
|
+
function matchCodeFenceStart(trimmed) {
|
|
7
|
+
if (!trimmed.startsWith('```')) {
|
|
8
|
+
return undefined;
|
|
9
|
+
}
|
|
10
|
+
const m = trimmed.match(/^```([^\s`]*)\s*$/);
|
|
11
|
+
if (!m) {
|
|
12
|
+
return undefined;
|
|
13
|
+
}
|
|
14
|
+
const group = m[1];
|
|
15
|
+
if (group === undefined || group.length === 0) {
|
|
16
|
+
return { language: undefined };
|
|
17
|
+
}
|
|
18
|
+
return { language: group };
|
|
19
|
+
}
|
|
20
|
+
function isCodeFenceEnd(trimmed) {
|
|
21
|
+
return /^```\s*$/.test(trimmed);
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* Match code fence with optional leading attributes like [html]```
|
|
25
|
+
* Returns the language and whether it's an HTML block
|
|
26
|
+
*/
|
|
27
|
+
function matchAttributedCodeFence(trimmed) {
|
|
28
|
+
// Match pattern: optional [attr]... followed by ```language?
|
|
29
|
+
const match = trimmed.match(/^(\[.+\])*\s*```([^\s`]*)\s*$/);
|
|
30
|
+
if (!match) {
|
|
31
|
+
return undefined;
|
|
32
|
+
}
|
|
33
|
+
const attrPart = match[1] || '';
|
|
34
|
+
const language = match[2] || undefined;
|
|
35
|
+
const isHtml = /\[html\]/i.test(attrPart);
|
|
36
|
+
return { language, isHtml };
|
|
37
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { BlockNode } from '../../ast';
|
|
2
|
+
import { BlockRuleContext } from './types';
|
|
3
|
+
interface ContentTitleRuleContext extends BlockRuleContext {
|
|
4
|
+
blocks: BlockNode[];
|
|
5
|
+
resetPending: () => void;
|
|
6
|
+
}
|
|
7
|
+
interface ContentTitleMatchResult {
|
|
8
|
+
text: string;
|
|
9
|
+
}
|
|
10
|
+
export declare function tryParseContentTitleBlock(ctx: ContentTitleRuleContext): number | null;
|
|
11
|
+
export declare function matchContentTitle(content: string): ContentTitleMatchResult | undefined;
|
|
12
|
+
export {};
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.tryParseContentTitleBlock = tryParseContentTitleBlock;
|
|
4
|
+
exports.matchContentTitle = matchContentTitle;
|
|
5
|
+
const location_1 = require("../../location");
|
|
6
|
+
function tryParseContentTitleBlock(ctx) {
|
|
7
|
+
const contentTitleMatch = matchContentTitle(ctx.lineInfo.content);
|
|
8
|
+
if (!contentTitleMatch) {
|
|
9
|
+
return null;
|
|
10
|
+
}
|
|
11
|
+
const text = contentTitleMatch.text;
|
|
12
|
+
let consumedByImageTitle = false;
|
|
13
|
+
if (canAttachToPreviousImage(ctx.pending)) {
|
|
14
|
+
const lastBlock = ctx.blocks[ctx.blocks.length - 1];
|
|
15
|
+
if (lastBlock && lastBlock.type === 'image') {
|
|
16
|
+
lastBlock.title = text;
|
|
17
|
+
consumedByImageTitle = true;
|
|
18
|
+
}
|
|
19
|
+
else if (lastBlock && lastBlock.type === 'taggedBlock' && lastBlock.child.type === 'image') {
|
|
20
|
+
lastBlock.child.title = text;
|
|
21
|
+
consumedByImageTitle = true;
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
if (consumedByImageTitle) {
|
|
25
|
+
ctx.resetPending();
|
|
26
|
+
return ctx.index + 1;
|
|
27
|
+
}
|
|
28
|
+
const blockAttrs = ctx.buildBlockAttrs(ctx.lineInfo.tabCount);
|
|
29
|
+
let block;
|
|
30
|
+
if (ctx.pending.isDisabled) {
|
|
31
|
+
const disabled = {
|
|
32
|
+
type: 'disabledBlock',
|
|
33
|
+
raw: ctx.lineInfo.raw,
|
|
34
|
+
blockAttrs,
|
|
35
|
+
};
|
|
36
|
+
(0, location_1.setNodeLocation)(disabled, ctx.lineLocation);
|
|
37
|
+
block = disabled;
|
|
38
|
+
}
|
|
39
|
+
else {
|
|
40
|
+
const contentTitle = {
|
|
41
|
+
type: 'contentTitle',
|
|
42
|
+
children: [{ type: 'text', value: text }],
|
|
43
|
+
};
|
|
44
|
+
(0, location_1.setNodeLocation)(contentTitle, ctx.lineLocation);
|
|
45
|
+
block = contentTitle;
|
|
46
|
+
}
|
|
47
|
+
ctx.commitBlock(block, blockAttrs);
|
|
48
|
+
return ctx.index + 1;
|
|
49
|
+
}
|
|
50
|
+
function matchContentTitle(content) {
|
|
51
|
+
const m = content.match(/^>\s+(.+)$/);
|
|
52
|
+
if (!m) {
|
|
53
|
+
return undefined;
|
|
54
|
+
}
|
|
55
|
+
const group = m[1];
|
|
56
|
+
if (group === undefined) {
|
|
57
|
+
return undefined;
|
|
58
|
+
}
|
|
59
|
+
const text = group.trimEnd();
|
|
60
|
+
return { text };
|
|
61
|
+
}
|
|
62
|
+
function canAttachToPreviousImage(pending) {
|
|
63
|
+
return (!pending.attrs &&
|
|
64
|
+
!pending.foldNext &&
|
|
65
|
+
!pending.tagName &&
|
|
66
|
+
!pending.isComment &&
|
|
67
|
+
!pending.isDisabled &&
|
|
68
|
+
!pending.isSheet &&
|
|
69
|
+
!pending.isHtml);
|
|
70
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { BlockNode } from '../../ast';
|
|
2
|
+
import { ParseError } from '../../errors';
|
|
3
|
+
import { BlockRuleContext } from './types';
|
|
4
|
+
interface FootnotesRuleContext extends BlockRuleContext {
|
|
5
|
+
parseBlocks: (source: string, errors: ParseError[]) => BlockNode[];
|
|
6
|
+
}
|
|
7
|
+
export declare function tryParseFootnotesBlock(ctx: FootnotesRuleContext): number | null;
|
|
8
|
+
export declare function isFootnotesLine(trimmed: string): boolean;
|
|
9
|
+
export {};
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.tryParseFootnotesBlock = tryParseFootnotesBlock;
|
|
4
|
+
exports.isFootnotesLine = isFootnotesLine;
|
|
5
|
+
const location_1 = require("../../location");
|
|
6
|
+
const lexer_1 = require("../../../lexer/lexer");
|
|
7
|
+
function tryParseFootnotesBlock(ctx) {
|
|
8
|
+
if (!isFootnotesLine(ctx.trimmedContent)) {
|
|
9
|
+
return null;
|
|
10
|
+
}
|
|
11
|
+
const rawLines = [ctx.lineInfo.raw];
|
|
12
|
+
const regionLines = [];
|
|
13
|
+
let jFoot = ctx.index + 1;
|
|
14
|
+
while (jFoot < ctx.lines.length) {
|
|
15
|
+
const nextRaw = ctx.lines[jFoot];
|
|
16
|
+
if (nextRaw === undefined) {
|
|
17
|
+
jFoot += 1;
|
|
18
|
+
continue;
|
|
19
|
+
}
|
|
20
|
+
const nextInfo = (0, lexer_1.getLineInfo)(nextRaw);
|
|
21
|
+
const nextTrimmed = nextInfo.content.trim();
|
|
22
|
+
if (nextTrimmed.length === 0) {
|
|
23
|
+
break;
|
|
24
|
+
}
|
|
25
|
+
rawLines.push(nextInfo.raw);
|
|
26
|
+
regionLines.push(nextInfo.content);
|
|
27
|
+
jFoot += 1;
|
|
28
|
+
}
|
|
29
|
+
const blockAttrs = ctx.buildBlockAttrs(ctx.lineInfo.tabCount);
|
|
30
|
+
let block;
|
|
31
|
+
if (ctx.pending.isDisabled) {
|
|
32
|
+
const disabled = {
|
|
33
|
+
type: 'disabledBlock',
|
|
34
|
+
raw: rawLines.join('\n'),
|
|
35
|
+
blockAttrs,
|
|
36
|
+
};
|
|
37
|
+
(0, location_1.setNodeLocation)(disabled, ctx.lineLocation);
|
|
38
|
+
block = disabled;
|
|
39
|
+
}
|
|
40
|
+
else {
|
|
41
|
+
const children = parseFootnoteDefs(regionLines, ctx.errors, ctx.parseBlocks);
|
|
42
|
+
const footnotesBlock = {
|
|
43
|
+
type: 'footnotes',
|
|
44
|
+
children,
|
|
45
|
+
blockAttrs,
|
|
46
|
+
};
|
|
47
|
+
(0, location_1.setNodeLocation)(footnotesBlock, ctx.lineLocation);
|
|
48
|
+
block = footnotesBlock;
|
|
49
|
+
}
|
|
50
|
+
ctx.commitBlock(block, blockAttrs);
|
|
51
|
+
return jFoot;
|
|
52
|
+
}
|
|
53
|
+
function isFootnotesLine(trimmed) {
|
|
54
|
+
return trimmed === '[footnotes]';
|
|
55
|
+
}
|
|
56
|
+
function parseFootnoteDefs(lines, errors, parseBlocks) {
|
|
57
|
+
const defs = [];
|
|
58
|
+
let i = 0;
|
|
59
|
+
while (i < lines.length) {
|
|
60
|
+
const raw = lines[i];
|
|
61
|
+
if (raw === undefined) {
|
|
62
|
+
i += 1;
|
|
63
|
+
continue;
|
|
64
|
+
}
|
|
65
|
+
const trimmed = raw.trim();
|
|
66
|
+
const match = trimmed.match(/^\[fn=([^\]]+)\]\s*$/);
|
|
67
|
+
if (!match) {
|
|
68
|
+
i += 1;
|
|
69
|
+
continue;
|
|
70
|
+
}
|
|
71
|
+
const idGroup = match[1];
|
|
72
|
+
if (!idGroup) {
|
|
73
|
+
i += 1;
|
|
74
|
+
continue;
|
|
75
|
+
}
|
|
76
|
+
const id = idGroup.trim();
|
|
77
|
+
i += 1;
|
|
78
|
+
const contentLines = [];
|
|
79
|
+
while (i < lines.length) {
|
|
80
|
+
const innerRaw = lines[i];
|
|
81
|
+
if (innerRaw === undefined) {
|
|
82
|
+
i += 1;
|
|
83
|
+
continue;
|
|
84
|
+
}
|
|
85
|
+
const innerTrimmed = innerRaw.trim();
|
|
86
|
+
if (innerTrimmed.length === 0) {
|
|
87
|
+
break;
|
|
88
|
+
}
|
|
89
|
+
if (/^\[fn=[^\]]+\]\s*$/.test(innerTrimmed)) {
|
|
90
|
+
break;
|
|
91
|
+
}
|
|
92
|
+
contentLines.push(innerRaw);
|
|
93
|
+
i += 1;
|
|
94
|
+
}
|
|
95
|
+
const contentSource = contentLines.join('\n');
|
|
96
|
+
const children = contentSource.length > 0 ? parseBlocks(contentSource, errors) : [];
|
|
97
|
+
const def = {
|
|
98
|
+
type: 'footnoteDef',
|
|
99
|
+
id,
|
|
100
|
+
children,
|
|
101
|
+
};
|
|
102
|
+
defs.push(def);
|
|
103
|
+
}
|
|
104
|
+
return defs;
|
|
105
|
+
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { BlockRuleContext } from './types';
|
|
2
|
+
interface HtmlBlockMatchResult {
|
|
3
|
+
source: string;
|
|
4
|
+
}
|
|
5
|
+
export declare function tryParseHtmlBlock(ctx: BlockRuleContext): number | null;
|
|
6
|
+
export declare function matchHtmlBlock(trimmed: string): HtmlBlockMatchResult | undefined;
|
|
7
|
+
export {};
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.tryParseHtmlBlock = tryParseHtmlBlock;
|
|
4
|
+
exports.matchHtmlBlock = matchHtmlBlock;
|
|
5
|
+
const location_1 = require("../../location");
|
|
6
|
+
function tryParseHtmlBlock(ctx) {
|
|
7
|
+
const htmlMatch = matchHtmlBlock(ctx.trimmedContent);
|
|
8
|
+
if (!htmlMatch) {
|
|
9
|
+
return null;
|
|
10
|
+
}
|
|
11
|
+
const blockAttrs = ctx.buildBlockAttrs(ctx.lineInfo.tabCount);
|
|
12
|
+
let block;
|
|
13
|
+
if (ctx.pending.isDisabled) {
|
|
14
|
+
const disabled = {
|
|
15
|
+
type: 'disabledBlock',
|
|
16
|
+
raw: ctx.lineInfo.raw,
|
|
17
|
+
blockAttrs,
|
|
18
|
+
};
|
|
19
|
+
(0, location_1.setNodeLocation)(disabled, ctx.lineLocation);
|
|
20
|
+
block = disabled;
|
|
21
|
+
}
|
|
22
|
+
else {
|
|
23
|
+
const htmlBlock = {
|
|
24
|
+
type: 'html',
|
|
25
|
+
source: htmlMatch.source,
|
|
26
|
+
blockAttrs,
|
|
27
|
+
};
|
|
28
|
+
(0, location_1.setNodeLocation)(htmlBlock, ctx.lineLocation);
|
|
29
|
+
block = htmlBlock;
|
|
30
|
+
}
|
|
31
|
+
ctx.commitBlock(block, blockAttrs);
|
|
32
|
+
return ctx.index + 1;
|
|
33
|
+
}
|
|
34
|
+
function matchHtmlBlock(trimmed) {
|
|
35
|
+
const m = trimmed.match(/^\[html\]\(([^)]+)\)\s*$/);
|
|
36
|
+
if (!m) {
|
|
37
|
+
return undefined;
|
|
38
|
+
}
|
|
39
|
+
const urlGroup = m[1];
|
|
40
|
+
if (urlGroup === undefined) {
|
|
41
|
+
return undefined;
|
|
42
|
+
}
|
|
43
|
+
const source = urlGroup.trim();
|
|
44
|
+
if (source.length === 0) {
|
|
45
|
+
return undefined;
|
|
46
|
+
}
|
|
47
|
+
return { source };
|
|
48
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { BlockRuleContext } from './types';
|
|
2
|
+
interface ImageBlockMatchResult {
|
|
3
|
+
url: string;
|
|
4
|
+
shape?: 'square' | 'rounded';
|
|
5
|
+
roundedRadius?: number;
|
|
6
|
+
}
|
|
7
|
+
export declare function tryParseImageBlock(ctx: BlockRuleContext): number | null;
|
|
8
|
+
export declare function matchImageBlock(trimmed: string): ImageBlockMatchResult | undefined;
|
|
9
|
+
export {};
|