zentao-api 0.6.2 → 0.6.4
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/dist/browser/zentao-api.global.js +25 -2
- package/dist/modules/generated.d.ts +552 -70
- package/dist/modules/generated.js +723 -129
- package/dist/modules/override.js +35 -0
- package/dist/modules/registry.d.ts +579 -130
- package/dist/modules/resolve.d.ts +3 -3
- package/dist/modules/resolve.js +4 -4
- package/dist/request/index.js +2 -2
- package/dist/types/module.d.ts +3 -1
- package/dist/types/options.d.ts +1 -0
- package/dist/utils/doc-helper/html.d.ts +3 -0
- package/dist/utils/doc-helper/html.js +442 -0
- package/dist/utils/doc-helper/index.d.ts +3 -0
- package/dist/utils/doc-helper/index.js +2 -0
- package/dist/utils/doc-helper/inline.d.ts +3 -0
- package/dist/utils/doc-helper/inline.js +168 -0
- package/dist/utils/doc-helper/markdown.d.ts +3 -0
- package/dist/utils/doc-helper/markdown.js +402 -0
- package/dist/utils/doc-helper/shared.d.ts +37 -0
- package/dist/utils/doc-helper/shared.js +305 -0
- package/dist/utils/doc-helper/types.d.ts +86 -0
- package/dist/utils/doc-helper/types.js +1 -0
- package/dist/version.js +2 -2
- package/package.json +1 -1
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import type { ListPagerInfo, ModuleAction, ModuleActionRequest, ModuleDefinition } from '../types/index.js';
|
|
1
|
+
import type { ListPagerInfo, ModuleAction, ModuleActionRequest, ModuleDefinition, RequestProcessOptions } from '../types/index.js';
|
|
2
2
|
/** 将模块名、动作名和调用参数解析为实际 API 请求路径、查询参数和请求体。 */
|
|
3
3
|
export declare function resolveActionRequest(module: ModuleDefinition, actionName: string, params?: Record<string, unknown>): ModuleActionRequest;
|
|
4
4
|
/** 根据动作定义中的 resultGetter,从原始响应里提取业务数据。 */
|
|
5
|
-
export declare function extractResult(action: ModuleAction, response: Record<string, unknown>, params?: Record<string, unknown
|
|
5
|
+
export declare function extractResult(action: ModuleAction, response: Record<string, unknown>, params?: Record<string, unknown>, options?: RequestProcessOptions): unknown;
|
|
6
6
|
/** 根据动作定义中的 pagerGetter,从原始响应里提取分页信息。 */
|
|
7
|
-
export declare function extractPager(action: ModuleAction, response: Record<string, unknown>, params?: Record<string, unknown
|
|
7
|
+
export declare function extractPager(action: ModuleAction, response: Record<string, unknown>, params?: Record<string, unknown>, options?: RequestProcessOptions): ListPagerInfo | undefined;
|
package/dist/modules/resolve.js
CHANGED
|
@@ -214,12 +214,12 @@ export function resolveActionRequest(module, actionName, params = {}) {
|
|
|
214
214
|
};
|
|
215
215
|
}
|
|
216
216
|
/** 根据动作定义中的 resultGetter,从原始响应里提取业务数据。 */
|
|
217
|
-
export function extractResult(action, response, params = {}) {
|
|
217
|
+
export function extractResult(action, response, params = {}, options = {}) {
|
|
218
218
|
const getter = action.resultGetter;
|
|
219
219
|
if (!getter)
|
|
220
220
|
return response.data ?? response;
|
|
221
221
|
if (typeof getter === 'function')
|
|
222
|
-
return getter(response, params);
|
|
222
|
+
return getter(response, params, options);
|
|
223
223
|
if (typeof getter === 'string')
|
|
224
224
|
return getNestedValue(response, getter);
|
|
225
225
|
const result = {};
|
|
@@ -229,12 +229,12 @@ export function extractResult(action, response, params = {}) {
|
|
|
229
229
|
return result;
|
|
230
230
|
}
|
|
231
231
|
/** 根据动作定义中的 pagerGetter,从原始响应里提取分页信息。 */
|
|
232
|
-
export function extractPager(action, response, params = {}) {
|
|
232
|
+
export function extractPager(action, response, params = {}, options = {}) {
|
|
233
233
|
const getter = action.pagerGetter;
|
|
234
234
|
if (!getter)
|
|
235
235
|
return response.pager;
|
|
236
236
|
if (typeof getter === 'function')
|
|
237
|
-
return getter(response, params);
|
|
237
|
+
return getter(response, params, options);
|
|
238
238
|
if (typeof getter === 'string')
|
|
239
239
|
return getNestedValue(response, getter);
|
|
240
240
|
const page = getNestedValue(response, getter.pageID);
|
package/dist/request/index.js
CHANGED
|
@@ -148,9 +148,9 @@ function normalizeResponse(command, raw, options) {
|
|
|
148
148
|
}
|
|
149
149
|
const record = raw;
|
|
150
150
|
const status = record.status === 'fail' ? 'fail' : 'success';
|
|
151
|
-
const data = applyProcessing(extractResult(command.action, record, command.params), options);
|
|
151
|
+
const data = applyProcessing(extractResult(command.action, record, command.params, options), options);
|
|
152
152
|
const rawMessage = record.message;
|
|
153
|
-
const pager = extractPager(command.action, record, command.params);
|
|
153
|
+
const pager = extractPager(command.action, record, command.params, options);
|
|
154
154
|
const response = {
|
|
155
155
|
status,
|
|
156
156
|
message: stringifyMessage(rawMessage),
|
package/dist/types/module.d.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { HttpMethod } from './client.js';
|
|
2
|
+
import type { RequestProcessOptions } from './options.js';
|
|
2
3
|
import type { Pager } from './response.js';
|
|
3
4
|
/** 模块动作类型:基础 CRUD 或自定义动作。 */
|
|
4
5
|
export type ModuleActionType = 'list' | 'get' | 'create' | 'update' | 'delete' | 'action';
|
|
@@ -78,8 +79,9 @@ export type ModuleActionResultFieldMap = Readonly<Record<string, string>>;
|
|
|
78
79
|
* 从原始响应中提取数据时使用的函数形态。
|
|
79
80
|
* @param data 原始响应对象。
|
|
80
81
|
* @param params 触发本次请求的原始调用参数。
|
|
82
|
+
* @param options 本次请求的数据处理选项;直接调用 getter 时可以省略。
|
|
81
83
|
*/
|
|
82
|
-
export type ModuleActionGetterFn<T> = (data: unknown, params: Record<string, unknown
|
|
84
|
+
export type ModuleActionGetterFn<T, O = RequestProcessOptions> = (data: unknown, params: Record<string, unknown>, options?: O) => T;
|
|
83
85
|
/** 禅道模块中的单个 API 动作定义。 */
|
|
84
86
|
export interface ModuleAction {
|
|
85
87
|
/** 动作名称,例如 `list`、`get`、`close`。 */
|
package/dist/types/options.d.ts
CHANGED
|
@@ -0,0 +1,442 @@
|
|
|
1
|
+
import { deltaToHtml } from './inline.js';
|
|
2
|
+
import { blockChildren, escapeHtml, escapeHtmlAttribute, formatFileSize, isEdgelessOnly, isRecord, normalizeSnapshot, pageTitleText, plainText, resolveAssetUrl, resolveDocumentUrl, resolveZuiImageUrl, safeLanguage, sanitizeUrl, toFiniteNumber, toRecord, toStringValue, validateSnapshotTree, } from './shared.js';
|
|
3
|
+
const TRANSPARENT_FLAVOURS = new Set([
|
|
4
|
+
'affine:surface',
|
|
5
|
+
'affine:zui-layout-cell',
|
|
6
|
+
]);
|
|
7
|
+
const EMBED_LINK_FLAVOURS = new Set([
|
|
8
|
+
'affine:bookmark',
|
|
9
|
+
'affine:embed-figma',
|
|
10
|
+
'affine:embed-github',
|
|
11
|
+
'affine:embed-loom',
|
|
12
|
+
'affine:embed-youtube',
|
|
13
|
+
]);
|
|
14
|
+
function joinHtml(parts) {
|
|
15
|
+
return parts.filter(Boolean).join('\n');
|
|
16
|
+
}
|
|
17
|
+
function attribute(name, value) {
|
|
18
|
+
return value === undefined || value === null || value === ''
|
|
19
|
+
? ''
|
|
20
|
+
: ` ${name}="${escapeHtmlAttribute(value)}"`;
|
|
21
|
+
}
|
|
22
|
+
function dataAttribute(name, value) {
|
|
23
|
+
return isRecord(value)
|
|
24
|
+
? attribute(name, JSON.stringify(value))
|
|
25
|
+
: attribute(name, value);
|
|
26
|
+
}
|
|
27
|
+
function positiveInteger(value) {
|
|
28
|
+
const number = toFiniteNumber(value);
|
|
29
|
+
return number !== undefined && number > 0
|
|
30
|
+
? Math.min(10000, Math.round(number))
|
|
31
|
+
: undefined;
|
|
32
|
+
}
|
|
33
|
+
function safeDimension(value) {
|
|
34
|
+
if (typeof value === 'number' && Number.isFinite(value) && value > 0) {
|
|
35
|
+
return String(value);
|
|
36
|
+
}
|
|
37
|
+
if (typeof value !== 'string')
|
|
38
|
+
return '';
|
|
39
|
+
const dimension = value.trim();
|
|
40
|
+
return /^(?:\d+(?:\.\d+)?(?:px|%|em|rem|vh|vw)?|auto)$/i.test(dimension)
|
|
41
|
+
? dimension
|
|
42
|
+
: '';
|
|
43
|
+
}
|
|
44
|
+
function safeAlign(value) {
|
|
45
|
+
return ['left', 'center', 'right'].includes(String(value))
|
|
46
|
+
? String(value)
|
|
47
|
+
: '';
|
|
48
|
+
}
|
|
49
|
+
function renderListGroup(items, state) {
|
|
50
|
+
const type = toStringValue(toRecord(items[0]?.props).type, 'bulleted');
|
|
51
|
+
const tag = type === 'numbered' ? 'ol' : 'ul';
|
|
52
|
+
const className = `${escapeHtmlAttribute(type)}-list`;
|
|
53
|
+
const listItems = items.map(item => {
|
|
54
|
+
const props = toRecord(item.props);
|
|
55
|
+
const checkbox = type === 'todo'
|
|
56
|
+
? `<input type="checkbox" disabled${props.checked ? ' checked' : ''}> `
|
|
57
|
+
: '';
|
|
58
|
+
return `<li class="affine-list-block-container">${checkbox}${deltaToHtml(props.text, state.options, item)}${renderBlocks(blockChildren(item), state)}</li>`;
|
|
59
|
+
});
|
|
60
|
+
return `<${tag} class="${className}">${listItems.join('')}</${tag}>`;
|
|
61
|
+
}
|
|
62
|
+
function optionLabel(column, value) {
|
|
63
|
+
const data = toRecord(column.data);
|
|
64
|
+
const options = Array.isArray(data.options)
|
|
65
|
+
? data.options.filter(isRecord)
|
|
66
|
+
: [];
|
|
67
|
+
return toStringValue(options.find(option => option.id === value)?.value);
|
|
68
|
+
}
|
|
69
|
+
function databaseCell(row, column, cells, state) {
|
|
70
|
+
const columnId = toStringValue(column.id);
|
|
71
|
+
const rowCells = toRecord(cells[toStringValue(row.id)]);
|
|
72
|
+
const cell = toRecord(rowCells[columnId]);
|
|
73
|
+
const value = cell.value;
|
|
74
|
+
switch (column.type) {
|
|
75
|
+
case 'title':
|
|
76
|
+
return deltaToHtml(toRecord(row.props).text, state.options, row);
|
|
77
|
+
case 'rich-text':
|
|
78
|
+
return deltaToHtml(value, state.options, row);
|
|
79
|
+
case 'date': {
|
|
80
|
+
const date = new Date(typeof value === 'number' ? value : Number.NaN);
|
|
81
|
+
if (Number.isNaN(date.getTime()))
|
|
82
|
+
return '';
|
|
83
|
+
const year = date.getFullYear();
|
|
84
|
+
const month = String(date.getMonth() + 1).padStart(2, '0');
|
|
85
|
+
const day = String(date.getDate()).padStart(2, '0');
|
|
86
|
+
return `${year}-${month}-${day}`;
|
|
87
|
+
}
|
|
88
|
+
case 'select':
|
|
89
|
+
return escapeHtml(optionLabel(column, value));
|
|
90
|
+
case 'multi-select':
|
|
91
|
+
return escapeHtml(Array.isArray(value)
|
|
92
|
+
? value.map(item => optionLabel(column, item)).filter(Boolean).join(',')
|
|
93
|
+
: '');
|
|
94
|
+
case 'checkbox':
|
|
95
|
+
return value === undefined ? '' : escapeHtml(String(value));
|
|
96
|
+
default:
|
|
97
|
+
return value === undefined || value === null
|
|
98
|
+
? ''
|
|
99
|
+
: escapeHtml(String(value));
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
function renderDatabase(block, state) {
|
|
103
|
+
const props = toRecord(block.props);
|
|
104
|
+
const columns = Array.isArray(props.columns)
|
|
105
|
+
? props.columns.filter(isRecord)
|
|
106
|
+
: [];
|
|
107
|
+
const cells = toRecord(props.cells);
|
|
108
|
+
const header = `<tr>${columns
|
|
109
|
+
.map(column => `<th>${escapeHtml(toStringValue(column.name))}</th>`)
|
|
110
|
+
.join('')}</tr>`;
|
|
111
|
+
const body = blockChildren(block)
|
|
112
|
+
.map(row => `<tr>${columns
|
|
113
|
+
.map(column => `<td>${databaseCell(row, column, cells, state)}</td>`)
|
|
114
|
+
.join('')}</tr>`)
|
|
115
|
+
.join('');
|
|
116
|
+
const title = deltaToHtml(props.title, state.options, block);
|
|
117
|
+
return joinHtml([
|
|
118
|
+
title ? `<h5>${title}</h5>` : '',
|
|
119
|
+
`<table class="affine-database-block-container"><thead>${header}</thead><tbody>${body}</tbody></table>`,
|
|
120
|
+
]);
|
|
121
|
+
}
|
|
122
|
+
function renderZuiTable(block, state) {
|
|
123
|
+
const props = toRecord(block.props);
|
|
124
|
+
const rows = Array.isArray(props.rows) ? props.rows.filter(isRecord) : [];
|
|
125
|
+
const firstRowHead = props.firstRowHead !== false;
|
|
126
|
+
const body = rows
|
|
127
|
+
.map((row, rowIndex) => {
|
|
128
|
+
const cells = Array.isArray(row.cells) ? row.cells.filter(isRecord) : [];
|
|
129
|
+
return `<tr>${cells
|
|
130
|
+
.map(cell => {
|
|
131
|
+
const tag = cell.head ?? (rowIndex === 0 && firstRowHead) ? 'th' : 'td';
|
|
132
|
+
const colspan = positiveInteger(cell.colspan);
|
|
133
|
+
const rowspan = positiveInteger(cell.rowspan);
|
|
134
|
+
const width = safeDimension(cell.width);
|
|
135
|
+
const align = safeAlign(cell.align);
|
|
136
|
+
const style = align ? `text-align:${align}` : '';
|
|
137
|
+
return `<${tag}${attribute('colspan', colspan)}${attribute('rowspan', rowspan)}${attribute('width', width)}${attribute('style', style)}>${deltaToHtml(cell.text, state.options, block)}</${tag}>`;
|
|
138
|
+
})
|
|
139
|
+
.join('')}</tr>`;
|
|
140
|
+
})
|
|
141
|
+
.join('');
|
|
142
|
+
return `<table class="affine-table-block-container"${attribute('data-block-id', block.id)}><tbody>${body}</tbody></table>`;
|
|
143
|
+
}
|
|
144
|
+
function renderCustomExportNodes(nodes, state) {
|
|
145
|
+
return nodes
|
|
146
|
+
.filter(isRecord)
|
|
147
|
+
.map(node => {
|
|
148
|
+
const props = toRecord(node.props);
|
|
149
|
+
if (node.type === 'heading') {
|
|
150
|
+
const depth = Math.min(6, Math.max(1, typeof props.depth === 'number' ? props.depth : 2));
|
|
151
|
+
return `<h${depth}>${escapeHtml(toStringValue(props.text))}</h${depth}>`;
|
|
152
|
+
}
|
|
153
|
+
if (node.type === 'table') {
|
|
154
|
+
const columns = Array.isArray(props.cols)
|
|
155
|
+
? props.cols.filter(isRecord)
|
|
156
|
+
: [];
|
|
157
|
+
const data = Array.isArray(props.data) ? props.data.filter(isRecord) : [];
|
|
158
|
+
const header = `<tr>${columns
|
|
159
|
+
.map(column => `<th>${escapeHtml(toStringValue(column.text) || toStringValue(column.name))}</th>`)
|
|
160
|
+
.join('')}</tr>`;
|
|
161
|
+
const body = data
|
|
162
|
+
.map(row => `<tr>${columns
|
|
163
|
+
.map(column => {
|
|
164
|
+
const cell = toRecord(row[toStringValue(column.name)]);
|
|
165
|
+
return `<td>${escapeHtml(toStringValue(cell.text))}</td>`;
|
|
166
|
+
})
|
|
167
|
+
.join('')}</tr>`)
|
|
168
|
+
.join('');
|
|
169
|
+
return `<table class="affine-zui-custom-block-table"><thead>${header}</thead><tbody>${body}</tbody></table>`;
|
|
170
|
+
}
|
|
171
|
+
if (node.type === 'link') {
|
|
172
|
+
const url = sanitizeUrl(props.href);
|
|
173
|
+
const label = toStringValue(props.text) || url;
|
|
174
|
+
return url
|
|
175
|
+
? `<a href="${escapeHtmlAttribute(url)}" target="_blank" rel="noopener noreferrer">${escapeHtml(label)}</a>`
|
|
176
|
+
: escapeHtml(label);
|
|
177
|
+
}
|
|
178
|
+
return `<div>${escapeHtml(toStringValue(props.text))}</div>`;
|
|
179
|
+
})
|
|
180
|
+
.join('');
|
|
181
|
+
}
|
|
182
|
+
function renderCustomBlock(block, state) {
|
|
183
|
+
const contentValue = toRecord(block.props).content;
|
|
184
|
+
if (typeof contentValue === 'string') {
|
|
185
|
+
return `<div class="affine-zui-custom-block">${escapeHtml(contentValue)}</div>`;
|
|
186
|
+
}
|
|
187
|
+
const content = toRecord(contentValue);
|
|
188
|
+
const html = toStringValue(content.html);
|
|
189
|
+
if (html) {
|
|
190
|
+
return state.options.allowUnsafeHtml
|
|
191
|
+
? html
|
|
192
|
+
: `<pre class="affine-zui-html-source"><code>${escapeHtml(html)}</code></pre>`;
|
|
193
|
+
}
|
|
194
|
+
const exported = Array.isArray(content.export) ? content.export : [];
|
|
195
|
+
if (exported.length) {
|
|
196
|
+
return `<div class="affine-zui-custom-block">${renderCustomExportNodes(exported, state)}</div>`;
|
|
197
|
+
}
|
|
198
|
+
const title = toStringValue(content.title);
|
|
199
|
+
const fetcher = content.fetcher;
|
|
200
|
+
const fetcherUrl = typeof fetcher === 'string' ? fetcher : toRecord(fetcher).url;
|
|
201
|
+
const url = sanitizeUrl(content.exportUrl) || sanitizeUrl(fetcherUrl);
|
|
202
|
+
const component = toStringValue(content.component);
|
|
203
|
+
return `<div class="affine-zui-custom-block">${title ? `<h5>${escapeHtml(title)}</h5>` : ''}${component
|
|
204
|
+
? `<div class="affine-zui-component-placeholder" data-component="${escapeHtmlAttribute(component)}">[Component: ${escapeHtml(component)}]</div>`
|
|
205
|
+
: ''}${url
|
|
206
|
+
? `<a href="${escapeHtmlAttribute(url)}" target="_blank" rel="noopener noreferrer">${escapeHtml(url)}</a>`
|
|
207
|
+
: ''}</div>`;
|
|
208
|
+
}
|
|
209
|
+
function renderEmbedLink(block) {
|
|
210
|
+
const props = toRecord(block.props);
|
|
211
|
+
const url = sanitizeUrl(props.url);
|
|
212
|
+
if (!url)
|
|
213
|
+
return '';
|
|
214
|
+
const title = toStringValue(props.title) || url;
|
|
215
|
+
return `<div class="affine-paragraph-block-container"><a href="${escapeHtmlAttribute(url)}">${escapeHtml(title)}</a></div>`;
|
|
216
|
+
}
|
|
217
|
+
function renderHolder(block) {
|
|
218
|
+
const props = toRecord(block.props);
|
|
219
|
+
const text = toStringValue(props.text) || toStringValue(props.name);
|
|
220
|
+
return `<pre class="affine-zui-holder-block-container"${attribute('data-holder-name', props.name)}${dataAttribute('data-holder-data', props.data)}><code class="affine-zui-holder-block-content">${escapeHtml(text)}</code></pre>`;
|
|
221
|
+
}
|
|
222
|
+
function customRenderer(block, state) {
|
|
223
|
+
const renderer = state.options.renderBlock;
|
|
224
|
+
if (!renderer)
|
|
225
|
+
return undefined;
|
|
226
|
+
const context = {
|
|
227
|
+
format: 'html',
|
|
228
|
+
renderChildren: children => renderBlocks(children ?? blockChildren(block), state),
|
|
229
|
+
renderInline: text => deltaToHtml(text, state.options, block),
|
|
230
|
+
};
|
|
231
|
+
return renderer(block, context);
|
|
232
|
+
}
|
|
233
|
+
function renderUnknown(block, state) {
|
|
234
|
+
const strategy = state.options.unknownBlock ?? 'children';
|
|
235
|
+
if (strategy === 'throw') {
|
|
236
|
+
throw new TypeError(`Unsupported BlockSuite block flavour: ${block.flavour}`);
|
|
237
|
+
}
|
|
238
|
+
return strategy === 'children' ? renderBlocks(blockChildren(block), state) : '';
|
|
239
|
+
}
|
|
240
|
+
function renderBlock(block, state) {
|
|
241
|
+
const overridden = customRenderer(block, state);
|
|
242
|
+
if (overridden !== undefined)
|
|
243
|
+
return overridden;
|
|
244
|
+
const props = toRecord(block.props);
|
|
245
|
+
if (block.flavour === 'affine:page') {
|
|
246
|
+
return renderBlocks(blockChildren(block), state);
|
|
247
|
+
}
|
|
248
|
+
if (block.flavour === 'affine:note') {
|
|
249
|
+
return isEdgelessOnly(block) ? '' : renderBlocks(blockChildren(block), state);
|
|
250
|
+
}
|
|
251
|
+
if (TRANSPARENT_FLAVOURS.has(block.flavour)) {
|
|
252
|
+
return renderBlocks(blockChildren(block), state);
|
|
253
|
+
}
|
|
254
|
+
switch (block.flavour) {
|
|
255
|
+
case 'affine:paragraph': {
|
|
256
|
+
const text = deltaToHtml(props.text, state.options, block);
|
|
257
|
+
const type = toStringValue(props.type, 'text');
|
|
258
|
+
const align = safeAlign(props.align);
|
|
259
|
+
const style = align ? attribute('style', `text-align:${align}`) : '';
|
|
260
|
+
let own;
|
|
261
|
+
if (/^h[1-6]$/.test(type)) {
|
|
262
|
+
own = `<${type}${style}>${text}</${type}>`;
|
|
263
|
+
}
|
|
264
|
+
else if (type === 'quote') {
|
|
265
|
+
own = `<blockquote class="quote"><p${style}>${text}</p></blockquote>`;
|
|
266
|
+
}
|
|
267
|
+
else {
|
|
268
|
+
own = `<p${style}>${text}</p>`;
|
|
269
|
+
}
|
|
270
|
+
const children = renderBlocks(blockChildren(block), state);
|
|
271
|
+
return `<div class="affine-paragraph-block-container">${own}${children
|
|
272
|
+
? `<div class="affine-block-children-container">${children}</div>`
|
|
273
|
+
: ''}</div>`;
|
|
274
|
+
}
|
|
275
|
+
case 'affine:code': {
|
|
276
|
+
const text = plainText(props.text);
|
|
277
|
+
const language = safeLanguage(props.language);
|
|
278
|
+
return `<pre><code${language ? ` class="language-${escapeHtmlAttribute(language)}"` : ''}>${escapeHtml(text)}</code></pre>`;
|
|
279
|
+
}
|
|
280
|
+
case 'affine:divider':
|
|
281
|
+
return '<hr>';
|
|
282
|
+
case 'affine:latex':
|
|
283
|
+
return `<div class="affine-latex-block" data-latex="${escapeHtmlAttribute(toStringValue(props.latex))}">${escapeHtml(toStringValue(props.latex))}</div>`;
|
|
284
|
+
case 'affine:image': {
|
|
285
|
+
const url = resolveAssetUrl(state.options, block, 'image');
|
|
286
|
+
if (!url)
|
|
287
|
+
return '';
|
|
288
|
+
const width = positiveInteger(props.width);
|
|
289
|
+
const height = positiveInteger(props.height);
|
|
290
|
+
const caption = toStringValue(props.caption);
|
|
291
|
+
return `<figure class="affine-image-block-container"><img src="${escapeHtmlAttribute(url)}" alt="${escapeHtmlAttribute(caption)}"${attribute('title', caption)}${attribute('width', width)}${attribute('height', height)}></figure>`;
|
|
292
|
+
}
|
|
293
|
+
case 'affine:zui-image': {
|
|
294
|
+
const url = resolveZuiImageUrl(state.options, block);
|
|
295
|
+
if (!url)
|
|
296
|
+
return '';
|
|
297
|
+
return `<figure class="affine-image-block-container"${safeAlign(props.align)
|
|
298
|
+
? attribute('style', `text-align:${safeAlign(props.align)}`)
|
|
299
|
+
: ''}><img src="${escapeHtmlAttribute(url)}" alt="${escapeHtmlAttribute(toStringValue(props.caption))}"${attribute('width', positiveInteger(props.width))}${attribute('height', positiveInteger(props.height))}></figure>`;
|
|
300
|
+
}
|
|
301
|
+
case 'affine:attachment': {
|
|
302
|
+
const name = toStringValue(props.name, 'Attachment');
|
|
303
|
+
const url = resolveAssetUrl(state.options, block, 'attachment');
|
|
304
|
+
const caption = toStringValue(props.caption);
|
|
305
|
+
const label = url
|
|
306
|
+
? `<a href="${escapeHtmlAttribute(url)}" target="_blank" rel="noopener noreferrer">${escapeHtml(name)}</a>`
|
|
307
|
+
: escapeHtml(name);
|
|
308
|
+
return `<div class="affine-attachment-block-container"><p><strong>${label}</strong> <em>(${escapeHtml(formatFileSize(props.size))}${caption ? `, ${escapeHtml(caption)}` : ''})</em></p></div>`;
|
|
309
|
+
}
|
|
310
|
+
case 'affine:database':
|
|
311
|
+
return renderDatabase(block, state);
|
|
312
|
+
case 'affine:zui-table':
|
|
313
|
+
return renderZuiTable(block, state);
|
|
314
|
+
case 'affine:zui-holder':
|
|
315
|
+
return joinHtml([
|
|
316
|
+
renderHolder(block),
|
|
317
|
+
renderBlocks(blockChildren(block), state),
|
|
318
|
+
]);
|
|
319
|
+
case 'affine:zui-layout':
|
|
320
|
+
return `<div class="affine-zui-layout"${attribute('data-layout', props.type)}>${renderBlocks(blockChildren(block), state)}</div>`;
|
|
321
|
+
case 'affine:zui-panel':
|
|
322
|
+
return `<aside class="affine-zui-panel"${attribute('data-type', props.type)}>${renderBlocks(blockChildren(block), state)}</aside>`;
|
|
323
|
+
case 'affine:zui-expand': {
|
|
324
|
+
const title = deltaToHtml(props.title, state.options, block);
|
|
325
|
+
return `<details class="affine-zui-expand"${props.open === false ? '' : ' open'}><summary>${title}</summary>${renderBlocks(blockChildren(block), state)}</details>`;
|
|
326
|
+
}
|
|
327
|
+
case 'affine:embed-zui-whiteboard': {
|
|
328
|
+
const image = sanitizeUrl(props.sceneImage, 'image');
|
|
329
|
+
if (image) {
|
|
330
|
+
return `<img class="affine-whiteboard-block-image" src="${escapeHtmlAttribute(image)}" alt="${escapeHtmlAttribute(toStringValue(props.caption, 'whiteboard'))}">`;
|
|
331
|
+
}
|
|
332
|
+
return `<div class="affine-whiteboard-block-container">${props.sceneData ? '[Whiteboard]' : '[Empty Whiteboard]'}</div>`;
|
|
333
|
+
}
|
|
334
|
+
case 'affine:embed-zui-custom':
|
|
335
|
+
return renderCustomBlock(block, state);
|
|
336
|
+
case 'affine:embed-zui-html': {
|
|
337
|
+
const html = toStringValue(props.html);
|
|
338
|
+
if (!html)
|
|
339
|
+
return '';
|
|
340
|
+
return state.options.allowUnsafeHtml
|
|
341
|
+
? html
|
|
342
|
+
: `<pre class="affine-zui-html-source"><code>${escapeHtml(html)}</code></pre>`;
|
|
343
|
+
}
|
|
344
|
+
case 'affine:embed-zui-iframe': {
|
|
345
|
+
const url = sanitizeUrl(props.src);
|
|
346
|
+
if (!url)
|
|
347
|
+
return '';
|
|
348
|
+
const sandbox = toStringValue(props.sandbox);
|
|
349
|
+
let sandboxTokens = sandbox
|
|
350
|
+
.split(/\s+/)
|
|
351
|
+
.filter(token => [
|
|
352
|
+
'allow-forms',
|
|
353
|
+
'allow-modals',
|
|
354
|
+
'allow-pointer-lock',
|
|
355
|
+
'allow-popups',
|
|
356
|
+
'allow-same-origin',
|
|
357
|
+
'allow-scripts',
|
|
358
|
+
].includes(token));
|
|
359
|
+
// A same-origin scripted frame can remove its own sandbox attribute.
|
|
360
|
+
if (sandboxTokens.includes('allow-scripts') &&
|
|
361
|
+
sandboxTokens.includes('allow-same-origin')) {
|
|
362
|
+
sandboxTokens = sandboxTokens.filter(token => token !== 'allow-same-origin');
|
|
363
|
+
}
|
|
364
|
+
const sandboxAttribute = sandboxTokens.length
|
|
365
|
+
? attribute('sandbox', sandboxTokens.join(' '))
|
|
366
|
+
: ' sandbox';
|
|
367
|
+
return `<iframe src="${escapeHtmlAttribute(url)}"${sandboxAttribute}${attribute('width', safeDimension(props.width))}${attribute('height', safeDimension(props.height))}${props.allowfullscreen ? ' allowfullscreen' : ''}></iframe>`;
|
|
368
|
+
}
|
|
369
|
+
case 'affine:embed-zui-component': {
|
|
370
|
+
const name = toStringValue(props.name);
|
|
371
|
+
return name
|
|
372
|
+
? `<div class="affine-zui-component-placeholder" data-component="${escapeHtmlAttribute(name)}">[Component: ${escapeHtml(name)}]</div>`
|
|
373
|
+
: '';
|
|
374
|
+
}
|
|
375
|
+
case 'affine:embed-linked-doc': {
|
|
376
|
+
const pageId = toStringValue(props.pageId);
|
|
377
|
+
if (!pageId)
|
|
378
|
+
return '';
|
|
379
|
+
const title = state.options.resolveDocTitle?.(pageId) ?? 'untitled';
|
|
380
|
+
const url = resolveDocumentUrl(state.options, { pageId, params: toRecord(props.params), title }, block);
|
|
381
|
+
return url
|
|
382
|
+
? `<a href="${escapeHtmlAttribute(url)}">${escapeHtml(title)}</a>`
|
|
383
|
+
: escapeHtml(title);
|
|
384
|
+
}
|
|
385
|
+
case 'affine:embed-synced-doc': {
|
|
386
|
+
const children = blockChildren(block);
|
|
387
|
+
if (children.length)
|
|
388
|
+
return renderBlocks(children, state);
|
|
389
|
+
const pageId = toStringValue(props.pageId);
|
|
390
|
+
if (!pageId)
|
|
391
|
+
return '';
|
|
392
|
+
const title = state.options.resolveDocTitle?.(pageId) ?? pageId;
|
|
393
|
+
const url = resolveDocumentUrl(state.options, { pageId, params: toRecord(props.params), title }, block);
|
|
394
|
+
return url
|
|
395
|
+
? `<a class="affine-synced-doc-link" href="${escapeHtmlAttribute(url)}">${escapeHtml(title)}</a>`
|
|
396
|
+
: `<span class="affine-synced-doc-link">${escapeHtml(title)}</span>`;
|
|
397
|
+
}
|
|
398
|
+
default:
|
|
399
|
+
return EMBED_LINK_FLAVOURS.has(block.flavour)
|
|
400
|
+
? renderEmbedLink(block)
|
|
401
|
+
: renderUnknown(block, state);
|
|
402
|
+
}
|
|
403
|
+
}
|
|
404
|
+
function renderBlocks(blocks, state) {
|
|
405
|
+
const rendered = [];
|
|
406
|
+
for (let index = 0; index < blocks.length; index += 1) {
|
|
407
|
+
const block = blocks[index];
|
|
408
|
+
if (!block)
|
|
409
|
+
continue;
|
|
410
|
+
if (block.flavour === 'affine:list') {
|
|
411
|
+
const type = toStringValue(toRecord(block.props).type, 'bulleted');
|
|
412
|
+
const group = [block];
|
|
413
|
+
while (index + 1 < blocks.length) {
|
|
414
|
+
const next = blocks[index + 1];
|
|
415
|
+
if (next?.flavour !== 'affine:list' ||
|
|
416
|
+
toStringValue(toRecord(next.props).type, 'bulleted') !== type) {
|
|
417
|
+
break;
|
|
418
|
+
}
|
|
419
|
+
group.push(next);
|
|
420
|
+
index += 1;
|
|
421
|
+
}
|
|
422
|
+
rendered.push(renderListGroup(group, state));
|
|
423
|
+
}
|
|
424
|
+
else {
|
|
425
|
+
rendered.push(renderBlock(block, state));
|
|
426
|
+
}
|
|
427
|
+
}
|
|
428
|
+
return joinHtml(rendered);
|
|
429
|
+
}
|
|
430
|
+
/** Convert a BlockSuite 0.19.x snapshot to semantic, escaped HTML. */
|
|
431
|
+
export function snapshotToHtml(input, options = {}) {
|
|
432
|
+
const normalized = normalizeSnapshot(input);
|
|
433
|
+
validateSnapshotTree(normalized.blocks, options);
|
|
434
|
+
const state = { options };
|
|
435
|
+
const title = pageTitleText(normalized);
|
|
436
|
+
const heading = options.includeTitle === false || !title ? '' : `<h1>${escapeHtml(title)}</h1>`;
|
|
437
|
+
const body = joinHtml([heading, renderBlocks(normalized.blocks, state)]);
|
|
438
|
+
const fullDocument = options.fullDocument ?? normalized.documentLike;
|
|
439
|
+
if (!fullDocument)
|
|
440
|
+
return body;
|
|
441
|
+
return `<!doctype html><html><head><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1"><title>${escapeHtml(title)}</title></head><body><main class="blocksuite-document">${body}</main></body></html>`;
|
|
442
|
+
}
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
export { snapshotToHtml } from './html.js';
|
|
2
|
+
export { snapshotToMarkdown } from './markdown.js';
|
|
3
|
+
export type { BlockRenderer, BlockSnapshot, ConvertOptions, DeltaInsert, DocSnapshot, DocSnapshotMeta, DocumentReference, HtmlOptions, JsonRecord, MarkdownOptions, OutputFormat, RenderBlockContext, SliceSnapshot, Snapshot, SnapshotEnvelope, SnapshotInput, TextSnapshot, UnknownBlockStrategy, } from './types.js';
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
import type { BlockSnapshot, ConvertOptions } from './types.js';
|
|
2
|
+
export declare function deltaToMarkdown(value: unknown, options: ConvertOptions, block?: BlockSnapshot): string;
|
|
3
|
+
export declare function deltaToHtml(value: unknown, options: ConvertOptions, block?: BlockSnapshot): string;
|