@mnemosyne_os/affine-reader 0.1.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/LICENSE +21 -0
- package/NOTICE.md +42 -0
- package/README.md +89 -0
- package/dist/cli.d.ts +14 -0
- package/dist/cli.js +79 -0
- package/dist/exportMarkdown.d.ts +46 -0
- package/dist/exportMarkdown.js +104 -0
- package/dist/index.d.ts +17 -0
- package/dist/index.js +34 -0
- package/dist/locate.d.ts +24 -0
- package/dist/locate.js +91 -0
- package/dist/nodeSqlite.d.ts +20 -0
- package/dist/nodeSqlite.js +50 -0
- package/dist/read.d.ts +45 -0
- package/dist/read.js +197 -0
- package/dist/stage.d.ts +27 -0
- package/dist/stage.js +54 -0
- package/dist/types.d.ts +72 -0
- package/dist/types.js +10 -0
- package/dist/vendor/affine/blocksuite-types.d.ts +25 -0
- package/dist/vendor/affine/blocksuite-types.js +14 -0
- package/dist/vendor/affine/delta-to-md/delta-converters.d.ts +39 -0
- package/dist/vendor/affine/delta-to-md/delta-converters.js +81 -0
- package/dist/vendor/affine/delta-to-md/delta-to-md.d.ts +1 -0
- package/dist/vendor/affine/delta-to-md/delta-to-md.js +132 -0
- package/dist/vendor/affine/delta-to-md/index.d.ts +2 -0
- package/dist/vendor/affine/delta-to-md/index.js +7 -0
- package/dist/vendor/affine/delta-to-md/utils/node.d.ts +13 -0
- package/dist/vendor/affine/delta-to-md/utils/node.js +59 -0
- package/dist/vendor/affine/delta-to-md/utils/url.d.ts +1 -0
- package/dist/vendor/affine/delta-to-md/utils/url.js +8 -0
- package/dist/vendor/affine/parser.d.ts +5 -0
- package/dist/vendor/affine/parser.js +386 -0
- package/dist/vendor/affine/types.d.ts +100 -0
- package/dist/vendor/affine/types.js +2 -0
- package/package.json +64 -0
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.deltaToMd = void 0;
|
|
4
|
+
// oxlint-disable
|
|
5
|
+
// @ts-nocheck
|
|
6
|
+
const node_1 = require("./utils/node");
|
|
7
|
+
const deltaToMd = (delta, converters) => {
|
|
8
|
+
return convert(delta, converters).render().trimEnd() + '\n';
|
|
9
|
+
};
|
|
10
|
+
exports.deltaToMd = deltaToMd;
|
|
11
|
+
function convert(ops, converters) {
|
|
12
|
+
let group, line, el, activeInline, beginningOfLine;
|
|
13
|
+
let root = new node_1.Node();
|
|
14
|
+
function newLine() {
|
|
15
|
+
el = line = new node_1.Node(['', '\n']);
|
|
16
|
+
root.append(line);
|
|
17
|
+
activeInline = {};
|
|
18
|
+
}
|
|
19
|
+
newLine();
|
|
20
|
+
for (let i = 0; i < ops.length; i++) {
|
|
21
|
+
let op = ops[i];
|
|
22
|
+
if (typeof op.insert === 'object') {
|
|
23
|
+
for (let k in op.insert) {
|
|
24
|
+
if (converters.embed[k]) {
|
|
25
|
+
applyInlineAttributes(op.attributes);
|
|
26
|
+
converters.embed[k].call(el, op.insert[k], op.attributes);
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
else {
|
|
31
|
+
let lines = op.insert.split('\n');
|
|
32
|
+
if (hasBlockLevelAttribute(op.attributes, converters)) {
|
|
33
|
+
// Some line-level styling (ie headings) is applied by inserting a \n
|
|
34
|
+
// with the style; the style applies back to the previous \n.
|
|
35
|
+
// There *should* only be one style in an insert operation.
|
|
36
|
+
for (let j = 1; j < lines.length; j++) {
|
|
37
|
+
for (let attr in op.attributes) {
|
|
38
|
+
if (converters.block[attr]) {
|
|
39
|
+
let fn = converters.block[attr];
|
|
40
|
+
if (typeof fn === 'object') {
|
|
41
|
+
if (group && group.type !== attr) {
|
|
42
|
+
group = null;
|
|
43
|
+
}
|
|
44
|
+
if (!group && fn.group) {
|
|
45
|
+
group = {
|
|
46
|
+
el: fn.group(),
|
|
47
|
+
type: attr,
|
|
48
|
+
value: op.attributes[attr],
|
|
49
|
+
distance: 0,
|
|
50
|
+
};
|
|
51
|
+
root.append(group.el);
|
|
52
|
+
}
|
|
53
|
+
if (group) {
|
|
54
|
+
group.el.append(line);
|
|
55
|
+
group.distance = 0;
|
|
56
|
+
}
|
|
57
|
+
fn = fn.line;
|
|
58
|
+
}
|
|
59
|
+
fn.call(line, op.attributes, group);
|
|
60
|
+
newLine();
|
|
61
|
+
break;
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
beginningOfLine = true;
|
|
66
|
+
}
|
|
67
|
+
else {
|
|
68
|
+
for (let l = 0; l < lines.length; l++) {
|
|
69
|
+
if ((l > 0 || beginningOfLine) && group && ++group.distance >= 2) {
|
|
70
|
+
group = null;
|
|
71
|
+
}
|
|
72
|
+
applyInlineAttributes(op.attributes, ops[i + 1] && ops[i + 1].attributes);
|
|
73
|
+
el.append(lines[l]);
|
|
74
|
+
if (l < lines.length - 1) {
|
|
75
|
+
newLine();
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
beginningOfLine = false;
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
return root;
|
|
83
|
+
function applyInlineAttributes(attrs, next) {
|
|
84
|
+
let first = [];
|
|
85
|
+
let then = [];
|
|
86
|
+
attrs = attrs || {};
|
|
87
|
+
let tag = el, seen = {};
|
|
88
|
+
while (tag._format) {
|
|
89
|
+
seen[tag._format] = true;
|
|
90
|
+
if (!attrs[tag._format] || tag.open !== tag.close) {
|
|
91
|
+
for (let k in seen) {
|
|
92
|
+
delete activeInline[k];
|
|
93
|
+
}
|
|
94
|
+
el = tag.parent();
|
|
95
|
+
}
|
|
96
|
+
tag = tag.parent();
|
|
97
|
+
}
|
|
98
|
+
for (let attr in attrs) {
|
|
99
|
+
if (converters.inline[attr] && attrs[attr]) {
|
|
100
|
+
if (activeInline[attr] && activeInline[attr] === attrs[attr]) {
|
|
101
|
+
continue; // do nothing -- we should already be inside this style's tag
|
|
102
|
+
}
|
|
103
|
+
if (next && attrs[attr] === next[attr]) {
|
|
104
|
+
first.push(attr); // if the next operation has the same style, this should be the outermost tag
|
|
105
|
+
}
|
|
106
|
+
else {
|
|
107
|
+
then.push(attr);
|
|
108
|
+
}
|
|
109
|
+
activeInline[attr] = attrs[attr];
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
first.forEach(apply);
|
|
113
|
+
then.forEach(apply);
|
|
114
|
+
function apply(fmt) {
|
|
115
|
+
let newEl = converters.inline[fmt].call(null, attrs[fmt]);
|
|
116
|
+
if (Array.isArray(newEl)) {
|
|
117
|
+
newEl = new node_1.Node(newEl);
|
|
118
|
+
}
|
|
119
|
+
newEl._format = fmt;
|
|
120
|
+
el.append(newEl);
|
|
121
|
+
el = newEl;
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
function hasBlockLevelAttribute(attrs, converters) {
|
|
126
|
+
for (let k in attrs) {
|
|
127
|
+
if (Object.keys(converters.block).includes(k)) {
|
|
128
|
+
return true;
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
return false;
|
|
132
|
+
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.deltaToMd = exports.getConverters = void 0;
|
|
4
|
+
var delta_converters_1 = require("./delta-converters");
|
|
5
|
+
Object.defineProperty(exports, "getConverters", { enumerable: true, get: function () { return delta_converters_1.getConverters; } });
|
|
6
|
+
var delta_to_md_1 = require("./delta-to-md");
|
|
7
|
+
Object.defineProperty(exports, "deltaToMd", { enumerable: true, get: function () { return delta_to_md_1.deltaToMd; } });
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
export declare class Node {
|
|
2
|
+
id: number;
|
|
3
|
+
children: Node[];
|
|
4
|
+
open: string;
|
|
5
|
+
close: string;
|
|
6
|
+
text: string;
|
|
7
|
+
_format: string;
|
|
8
|
+
_parent: Node;
|
|
9
|
+
constructor(data?: string[] | string);
|
|
10
|
+
append(e: Node): void;
|
|
11
|
+
render(): any;
|
|
12
|
+
parent(): Node;
|
|
13
|
+
}
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.Node = void 0;
|
|
4
|
+
// oxlint-disable
|
|
5
|
+
// @ts-nocheck
|
|
6
|
+
let id = 0;
|
|
7
|
+
class Node {
|
|
8
|
+
id = ++id;
|
|
9
|
+
children;
|
|
10
|
+
open;
|
|
11
|
+
close;
|
|
12
|
+
text;
|
|
13
|
+
_format;
|
|
14
|
+
_parent;
|
|
15
|
+
constructor(data) {
|
|
16
|
+
if (Array.isArray(data)) {
|
|
17
|
+
this.open = data[0];
|
|
18
|
+
this.close = data[1];
|
|
19
|
+
}
|
|
20
|
+
else if (typeof data === 'string') {
|
|
21
|
+
this.text = data;
|
|
22
|
+
}
|
|
23
|
+
this.children = [];
|
|
24
|
+
}
|
|
25
|
+
append(e) {
|
|
26
|
+
if (!(e instanceof Node)) {
|
|
27
|
+
e = new Node(e);
|
|
28
|
+
}
|
|
29
|
+
if (e._parent) {
|
|
30
|
+
const idx = e._parent.children.indexOf(e);
|
|
31
|
+
e._parent.children.splice(idx, 1);
|
|
32
|
+
}
|
|
33
|
+
e._parent = this;
|
|
34
|
+
this.children = this.children.concat(e);
|
|
35
|
+
}
|
|
36
|
+
render() {
|
|
37
|
+
const inner = (this.text || '') + this.children.map(c => c.render()).join('');
|
|
38
|
+
if (inner.trim() === '' &&
|
|
39
|
+
this.open === this.close &&
|
|
40
|
+
this.open &&
|
|
41
|
+
this.close) {
|
|
42
|
+
return '';
|
|
43
|
+
}
|
|
44
|
+
const wrapped = this.open && this.close;
|
|
45
|
+
const emptyInner = inner.trim() === '';
|
|
46
|
+
const fragments = [
|
|
47
|
+
inner.startsWith(' ') && !emptyInner && wrapped ? ' ' : '',
|
|
48
|
+
this.open,
|
|
49
|
+
wrapped ? inner.trim() : inner,
|
|
50
|
+
this.close,
|
|
51
|
+
inner.endsWith(' ') && !emptyInner && wrapped ? ' ' : '',
|
|
52
|
+
].filter(f => f);
|
|
53
|
+
return fragments.join('');
|
|
54
|
+
}
|
|
55
|
+
parent() {
|
|
56
|
+
return this._parent;
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
exports.Node = Node;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare const encodeLink: (link: string) => string;
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.encodeLink = void 0;
|
|
4
|
+
const encodeLink = (link) => encodeURI(link)
|
|
5
|
+
.replaceAll('(', '%28')
|
|
6
|
+
.replaceAll(')', '%29')
|
|
7
|
+
.replace(/(\?|&)response-content-disposition=attachment.*$/, '');
|
|
8
|
+
exports.encodeLink = encodeLink;
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import type { BaseParsedBlock, ParsedBlock, ParsedDoc, ParserContext, YBlock, YBlocks } from './types';
|
|
2
|
+
export declare const parseBlockToMd: (block: BaseParsedBlock, padding?: string) => string;
|
|
3
|
+
export declare function parseBlock(context: ParserContext, yBlock: YBlock | undefined, yBlocks: YBlocks, // all blocks
|
|
4
|
+
aiEditable?: boolean, blockLevel?: number): ParsedBlock | null;
|
|
5
|
+
export declare const parsePageDoc: (ctx: ParserContext) => ParsedDoc;
|
|
@@ -0,0 +1,386 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.parsePageDoc = exports.parseBlockToMd = void 0;
|
|
4
|
+
exports.parseBlock = parseBlock;
|
|
5
|
+
const yjs_1 = require("yjs");
|
|
6
|
+
const delta_to_md_1 = require("./delta-to-md");
|
|
7
|
+
const parseBlockToMd = (block, padding = '') => {
|
|
8
|
+
if (block.content) {
|
|
9
|
+
return (block.content
|
|
10
|
+
.split('\n')
|
|
11
|
+
.map(line => padding + line)
|
|
12
|
+
.slice(0, -1)
|
|
13
|
+
.join('\n') +
|
|
14
|
+
'\n' +
|
|
15
|
+
block.children.map(b => (0, exports.parseBlockToMd)(b, padding + ' ')).join(''));
|
|
16
|
+
}
|
|
17
|
+
else {
|
|
18
|
+
return block.children.map(b => (0, exports.parseBlockToMd)(b, padding)).join('');
|
|
19
|
+
}
|
|
20
|
+
};
|
|
21
|
+
exports.parseBlockToMd = parseBlockToMd;
|
|
22
|
+
function parseBlock(context, yBlock, yBlocks, // all blocks
|
|
23
|
+
aiEditable = false, blockLevel = 0) {
|
|
24
|
+
if (!yBlock) {
|
|
25
|
+
return null;
|
|
26
|
+
}
|
|
27
|
+
const deltaConverters = (0, delta_to_md_1.getConverters)({
|
|
28
|
+
convertInlineReferenceLink: ref => {
|
|
29
|
+
return {
|
|
30
|
+
title: ref.title || context.renderDocTitle?.(ref.pageId) || '',
|
|
31
|
+
link: context.buildDocUrl(ref.pageId),
|
|
32
|
+
};
|
|
33
|
+
},
|
|
34
|
+
});
|
|
35
|
+
const id = yBlock.get('sys:id');
|
|
36
|
+
const flavour = yBlock.get('sys:flavour');
|
|
37
|
+
const type = yBlock.get('prop:type');
|
|
38
|
+
const toMd = () => (0, delta_to_md_1.deltaToMd)(yBlock.get('prop:text').toDelta(), deltaConverters);
|
|
39
|
+
const hidden = yBlock.get('prop:hidden');
|
|
40
|
+
const displayMode = yBlock.get('prop:displayMode');
|
|
41
|
+
const childrenIds = yBlock.get('sys:children') instanceof yjs_1.Array
|
|
42
|
+
? yBlock.get('sys:children').toJSON()
|
|
43
|
+
: [];
|
|
44
|
+
let result = {
|
|
45
|
+
id,
|
|
46
|
+
flavour,
|
|
47
|
+
content: '',
|
|
48
|
+
children: [],
|
|
49
|
+
type,
|
|
50
|
+
};
|
|
51
|
+
if (hidden || displayMode === 'edgeless') {
|
|
52
|
+
return result;
|
|
53
|
+
}
|
|
54
|
+
let placeholder = false;
|
|
55
|
+
try {
|
|
56
|
+
switch (flavour) {
|
|
57
|
+
case 'affine:paragraph': {
|
|
58
|
+
let initial = '';
|
|
59
|
+
if (type === 'h1') {
|
|
60
|
+
initial = '# ';
|
|
61
|
+
}
|
|
62
|
+
else if (type === 'h2') {
|
|
63
|
+
initial = '## ';
|
|
64
|
+
}
|
|
65
|
+
else if (type === 'h3') {
|
|
66
|
+
initial = '### ';
|
|
67
|
+
}
|
|
68
|
+
else if (type === 'h4') {
|
|
69
|
+
initial = '#### ';
|
|
70
|
+
}
|
|
71
|
+
else if (type === 'h5') {
|
|
72
|
+
initial = '##### ';
|
|
73
|
+
}
|
|
74
|
+
else if (type === 'h6') {
|
|
75
|
+
initial = '###### ';
|
|
76
|
+
}
|
|
77
|
+
else if (type === 'quote') {
|
|
78
|
+
initial = '> ';
|
|
79
|
+
}
|
|
80
|
+
result.content = initial + toMd() + '\n';
|
|
81
|
+
break;
|
|
82
|
+
}
|
|
83
|
+
case 'affine:divider': {
|
|
84
|
+
result.content = '\n---\n\n';
|
|
85
|
+
break;
|
|
86
|
+
}
|
|
87
|
+
case 'affine:list': {
|
|
88
|
+
let prefix = type === 'bulleted' ? '* ' : '1. ';
|
|
89
|
+
if (type === 'todo') {
|
|
90
|
+
const checked = yBlock.get('prop:checked');
|
|
91
|
+
prefix = checked ? '- [x] ' : '- [ ] ';
|
|
92
|
+
}
|
|
93
|
+
result.content = prefix + toMd();
|
|
94
|
+
break;
|
|
95
|
+
}
|
|
96
|
+
case 'affine:code': {
|
|
97
|
+
const lang = yBlock.get('prop:language')?.toLowerCase() || 'txt';
|
|
98
|
+
// do not transform to delta for code block
|
|
99
|
+
const caption = yBlock.get('prop:caption');
|
|
100
|
+
result.content =
|
|
101
|
+
'```' +
|
|
102
|
+
lang +
|
|
103
|
+
(caption ? ` ${caption}` : '') +
|
|
104
|
+
'\n' +
|
|
105
|
+
yBlock.get('prop:text').toJSON() +
|
|
106
|
+
'\n```\n\n';
|
|
107
|
+
break;
|
|
108
|
+
}
|
|
109
|
+
case 'affine:image': {
|
|
110
|
+
const sourceId = yBlock.get('prop:sourceId');
|
|
111
|
+
const width = yBlock.get('prop:width');
|
|
112
|
+
const height = yBlock.get('prop:height');
|
|
113
|
+
// fixme: this may not work if workspace is not public
|
|
114
|
+
const blobUrl = context.buildBlobUrl(sourceId);
|
|
115
|
+
const caption = yBlock.get('prop:caption');
|
|
116
|
+
if (width || height || caption) {
|
|
117
|
+
result.content =
|
|
118
|
+
`<img
|
|
119
|
+
src="${blobUrl}"
|
|
120
|
+
alt="${caption}"
|
|
121
|
+
width="${width || 'auto'}"
|
|
122
|
+
height="${height || 'auto'}"
|
|
123
|
+
/>
|
|
124
|
+
` + '\n\n';
|
|
125
|
+
}
|
|
126
|
+
else {
|
|
127
|
+
result.content = `\n\n\n`;
|
|
128
|
+
}
|
|
129
|
+
Object.assign(result, {
|
|
130
|
+
sourceId,
|
|
131
|
+
width,
|
|
132
|
+
height,
|
|
133
|
+
caption,
|
|
134
|
+
blobUrl,
|
|
135
|
+
});
|
|
136
|
+
break;
|
|
137
|
+
}
|
|
138
|
+
case 'affine:attachment': {
|
|
139
|
+
const sourceId = yBlock.get('prop:sourceId');
|
|
140
|
+
const blobUrl = context.buildBlobUrl(sourceId);
|
|
141
|
+
const caption = yBlock.get('prop:caption');
|
|
142
|
+
if (type.startsWith('video')) {
|
|
143
|
+
result.content =
|
|
144
|
+
`<video muted autoplay loop preload="auto" playsinline>
|
|
145
|
+
<source src="${blobUrl}" type="${type}" />
|
|
146
|
+
</video>
|
|
147
|
+
` + '\n\n';
|
|
148
|
+
}
|
|
149
|
+
else {
|
|
150
|
+
// assume it is an image
|
|
151
|
+
result.content = `\n\n\n`;
|
|
152
|
+
}
|
|
153
|
+
Object.assign(result, {
|
|
154
|
+
sourceId,
|
|
155
|
+
blobUrl,
|
|
156
|
+
caption,
|
|
157
|
+
});
|
|
158
|
+
break;
|
|
159
|
+
}
|
|
160
|
+
case 'affine:embed-youtube': {
|
|
161
|
+
const videoId = yBlock.get('prop:videoId');
|
|
162
|
+
// prettier-ignore
|
|
163
|
+
result.content = `
|
|
164
|
+
<iframe
|
|
165
|
+
type="text/html"
|
|
166
|
+
width="100%"
|
|
167
|
+
height="410px"
|
|
168
|
+
src="https://www.youtube.com/embed/${videoId}"
|
|
169
|
+
frameborder="0"
|
|
170
|
+
allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share"
|
|
171
|
+
allowfullscreen
|
|
172
|
+
credentialless>
|
|
173
|
+
</iframe>` + '\n\n';
|
|
174
|
+
break;
|
|
175
|
+
}
|
|
176
|
+
case 'affine:bookmark': {
|
|
177
|
+
const url = yBlock.get('prop:url');
|
|
178
|
+
const caption = yBlock.get('prop:caption');
|
|
179
|
+
result.content = `\n[](Bookmark,${url})\n\n`;
|
|
180
|
+
Object.assign(result, {
|
|
181
|
+
url,
|
|
182
|
+
caption,
|
|
183
|
+
});
|
|
184
|
+
break;
|
|
185
|
+
}
|
|
186
|
+
case 'affine:embed-linked-doc':
|
|
187
|
+
case 'affine:embed-synced-doc': {
|
|
188
|
+
const pageId = yBlock.get('prop:pageId');
|
|
189
|
+
const caption = yBlock.get('prop:caption');
|
|
190
|
+
result.content = `\n[${caption}](${context.buildDocUrl(pageId)})\n\n`;
|
|
191
|
+
Object.assign(result, {
|
|
192
|
+
pageId,
|
|
193
|
+
caption,
|
|
194
|
+
});
|
|
195
|
+
break;
|
|
196
|
+
}
|
|
197
|
+
case 'affine:surface':
|
|
198
|
+
case 'affine:page':
|
|
199
|
+
case 'affine:note':
|
|
200
|
+
case 'affine:frame': {
|
|
201
|
+
result.content = '';
|
|
202
|
+
break;
|
|
203
|
+
}
|
|
204
|
+
case 'affine:database': {
|
|
205
|
+
const title = yBlock.get('prop:title').toJSON();
|
|
206
|
+
const childrenTitleById = Object.fromEntries(childrenIds.map(cid => {
|
|
207
|
+
const child = parseBlock(context, yBlocks.get(cid), yBlocks, aiEditable, blockLevel + 1);
|
|
208
|
+
if (!child) {
|
|
209
|
+
return [cid, ''];
|
|
210
|
+
}
|
|
211
|
+
return [cid, (0, exports.parseBlockToMd)(child)];
|
|
212
|
+
}));
|
|
213
|
+
const cols = yBlock.get('prop:columns').toJSON();
|
|
214
|
+
const cells = yBlock.get('prop:cells').toJSON();
|
|
215
|
+
const optionToTagHtml = (option) => {
|
|
216
|
+
return `<span data-affine-option data-value="${option.id}" data-option-color="${option.color}">${option.value}</span>`;
|
|
217
|
+
};
|
|
218
|
+
const dbRows = childrenIds
|
|
219
|
+
.map(cid => {
|
|
220
|
+
const row = cells[cid];
|
|
221
|
+
return cols.map(col => {
|
|
222
|
+
const value = row?.[col.id]?.value;
|
|
223
|
+
if (col.type !== 'title' && !value) {
|
|
224
|
+
return '';
|
|
225
|
+
}
|
|
226
|
+
switch (col.type) {
|
|
227
|
+
case 'title':
|
|
228
|
+
return childrenTitleById[cid];
|
|
229
|
+
case 'select':
|
|
230
|
+
return optionToTagHtml(col.data['options'].find((opt) => opt.id === value));
|
|
231
|
+
case 'multi-select':
|
|
232
|
+
return col.data['options']
|
|
233
|
+
.filter((opt) => value.includes(opt.id))
|
|
234
|
+
.map(optionToTagHtml)
|
|
235
|
+
.join('');
|
|
236
|
+
default:
|
|
237
|
+
return value ?? '';
|
|
238
|
+
}
|
|
239
|
+
});
|
|
240
|
+
})
|
|
241
|
+
.filter(row => !row.every(v => !v));
|
|
242
|
+
const header = cols.map(col => {
|
|
243
|
+
return col.name;
|
|
244
|
+
});
|
|
245
|
+
const divider = cols.map(() => {
|
|
246
|
+
return '---';
|
|
247
|
+
});
|
|
248
|
+
// convert to markdown table
|
|
249
|
+
result.content =
|
|
250
|
+
[header, divider, ...dbRows]
|
|
251
|
+
.map(row => {
|
|
252
|
+
return ('|' +
|
|
253
|
+
row
|
|
254
|
+
.map(cell => String(cell || '')?.trim())
|
|
255
|
+
.join('|')
|
|
256
|
+
.replace(/\n+/g, '<br />') +
|
|
257
|
+
'|');
|
|
258
|
+
})
|
|
259
|
+
.join('\n') + '\n\n';
|
|
260
|
+
Object.assign(result, {
|
|
261
|
+
title,
|
|
262
|
+
rows: dbRows.map(row => {
|
|
263
|
+
return Object.fromEntries(row.map((v, i) => [cols[i].name, v]));
|
|
264
|
+
}),
|
|
265
|
+
});
|
|
266
|
+
break;
|
|
267
|
+
}
|
|
268
|
+
case 'affine:table': {
|
|
269
|
+
// Extract row IDs and their order
|
|
270
|
+
const rowEntries = Object.entries(yBlock.toJSON())
|
|
271
|
+
.filter(([key]) => key.startsWith('prop:rows.') && key.endsWith('.rowId'))
|
|
272
|
+
.map(([key, value]) => {
|
|
273
|
+
const rowId = value;
|
|
274
|
+
const orderKey = key.replace('.rowId', '.order');
|
|
275
|
+
const order = yBlock.get(orderKey);
|
|
276
|
+
const backgroundColor = yBlock.get(key.replace('.rowId', '.backgroundColor'));
|
|
277
|
+
return { rowId, order, backgroundColor };
|
|
278
|
+
})
|
|
279
|
+
.sort((a, b) => a.order.localeCompare(b.order));
|
|
280
|
+
// Extract column IDs and their order
|
|
281
|
+
const columnEntries = Object.entries(yBlock.toJSON())
|
|
282
|
+
.filter(([key]) => key.startsWith('prop:columns.') && key.endsWith('.columnId'))
|
|
283
|
+
.map(([key, value]) => {
|
|
284
|
+
const columnId = value;
|
|
285
|
+
const orderKey = key.replace('.columnId', '.order');
|
|
286
|
+
const order = yBlock.get(orderKey);
|
|
287
|
+
return { columnId, order };
|
|
288
|
+
})
|
|
289
|
+
.sort((a, b) => a.order.localeCompare(b.order));
|
|
290
|
+
// Build the table rows with cell data
|
|
291
|
+
const tableRows = rowEntries.map(({ rowId }) => {
|
|
292
|
+
return columnEntries.map(({ columnId }) => {
|
|
293
|
+
const cellKey = `prop:cells.${rowId}:${columnId}.text`;
|
|
294
|
+
const cellText = yBlock.get(cellKey);
|
|
295
|
+
return cellText || '';
|
|
296
|
+
});
|
|
297
|
+
});
|
|
298
|
+
// Store column IDs for reference
|
|
299
|
+
const columnIds = columnEntries.map(({ columnId }) => columnId);
|
|
300
|
+
// Use the first row as header and the rest as data rows
|
|
301
|
+
if (tableRows.length > 0) {
|
|
302
|
+
const headerRow = tableRows[0];
|
|
303
|
+
const dataRows = tableRows.slice(1);
|
|
304
|
+
const separators = headerRow.map(() => '---');
|
|
305
|
+
// Convert to markdown table with first row as header
|
|
306
|
+
result.content =
|
|
307
|
+
[headerRow, separators, ...dataRows]
|
|
308
|
+
.map(row => {
|
|
309
|
+
return ('|' +
|
|
310
|
+
row
|
|
311
|
+
.map(cell => String(cell || '')?.trim())
|
|
312
|
+
.join('|')
|
|
313
|
+
.replace(/\n+/g, '<br />') +
|
|
314
|
+
'|');
|
|
315
|
+
})
|
|
316
|
+
.join('\n') + '\n\n';
|
|
317
|
+
}
|
|
318
|
+
else {
|
|
319
|
+
// Handle empty table case
|
|
320
|
+
result.content = '';
|
|
321
|
+
}
|
|
322
|
+
Object.assign(result, {
|
|
323
|
+
columns: columnIds,
|
|
324
|
+
rows: tableRows,
|
|
325
|
+
});
|
|
326
|
+
break;
|
|
327
|
+
}
|
|
328
|
+
default: {
|
|
329
|
+
// console.warn("Unknown or unsupported flavour", flavour);
|
|
330
|
+
placeholder = true;
|
|
331
|
+
}
|
|
332
|
+
}
|
|
333
|
+
result.children =
|
|
334
|
+
flavour !== 'affine:database'
|
|
335
|
+
? childrenIds
|
|
336
|
+
.map(cid => parseBlock(context, yBlocks.get(cid), yBlocks, aiEditable, blockLevel + 1))
|
|
337
|
+
.filter((block) => !!block &&
|
|
338
|
+
!(block.content === '' && block.children.length === 0))
|
|
339
|
+
: [];
|
|
340
|
+
}
|
|
341
|
+
catch (e) {
|
|
342
|
+
console.warn('Error converting block to md', e);
|
|
343
|
+
}
|
|
344
|
+
if (result.content && aiEditable && blockLevel === 2) {
|
|
345
|
+
// add a placeholder comment for the block level 2
|
|
346
|
+
if (flavour === 'affine:database' || placeholder) {
|
|
347
|
+
result.content = `<!-- block_id=${id} flavour=${flavour} placeholder -->\n`;
|
|
348
|
+
result.children = [];
|
|
349
|
+
}
|
|
350
|
+
else {
|
|
351
|
+
result.content = `<!-- block_id=${id} flavour=${flavour} -->\n${result.content}`;
|
|
352
|
+
}
|
|
353
|
+
}
|
|
354
|
+
return result;
|
|
355
|
+
}
|
|
356
|
+
const parsePageDoc = (ctx) => {
|
|
357
|
+
// we assume that the first block is the page block
|
|
358
|
+
const yBlocks = ctx.doc.getMap('blocks');
|
|
359
|
+
const maybePageBlock = Object.entries(yBlocks.toJSON()).findLast(([_, b]) => b['sys:flavour'] === 'affine:page');
|
|
360
|
+
// there are cases that the page is empty due to some weird issues
|
|
361
|
+
if (!maybePageBlock) {
|
|
362
|
+
return {
|
|
363
|
+
title: '',
|
|
364
|
+
md: '',
|
|
365
|
+
};
|
|
366
|
+
}
|
|
367
|
+
else {
|
|
368
|
+
const yPage = yBlocks.get(maybePageBlock[0]);
|
|
369
|
+
const title = yPage.get('prop:title');
|
|
370
|
+
const rootBlock = parseBlock(ctx, yPage, yBlocks, ctx.aiEditable);
|
|
371
|
+
if (!rootBlock) {
|
|
372
|
+
return {
|
|
373
|
+
title: '',
|
|
374
|
+
md: '',
|
|
375
|
+
};
|
|
376
|
+
}
|
|
377
|
+
rootBlock.children = rootBlock.children.filter((block) => block.flavour === 'affine:note');
|
|
378
|
+
const md = (0, exports.parseBlockToMd)(rootBlock);
|
|
379
|
+
return {
|
|
380
|
+
title: title.toJSON(),
|
|
381
|
+
parsedBlock: rootBlock,
|
|
382
|
+
md,
|
|
383
|
+
};
|
|
384
|
+
}
|
|
385
|
+
};
|
|
386
|
+
exports.parsePageDoc = parsePageDoc;
|