Files
AFFiNE-Mirror/packages/common/reader/src/doc-parser/delta-to-md/delta-to-md.ts
T
Peng Xiao f4c20056a0 feat(core): moving in affine-reader doc parsers (#12840)
fix AI-191

#### PR Dependency Tree


* **PR #12840** 👈

This tree was auto-generated by
[Charcoal](https://github.com/danerwilliams/charcoal)

<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit

- **New Features**
- Introduced the ability to convert rich text documents into Markdown,
supporting a wide range of content types such as headings, lists,
tables, images, code blocks, attachments, and embedded documents.
- Added support for parsing collaborative document structures and
rendering them as structured Markdown or parsed representations.
- Enhanced handling of database and table blocks, including conversion
to Markdown tables with headers and cell content.

- **Documentation**
  - Added a README noting the use of a forked Markdown converter.

- **Tests**
  - Added new test coverage for document parsing features.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->


#### PR Dependency Tree


* **PR #12840** 👈

This tree was auto-generated by
[Charcoal](https://github.com/danerwilliams/charcoal)
2025-06-17 16:32:11 +08:00

148 lines
3.8 KiB
TypeScript

// eslint-disable
// @ts-nocheck
import { Node } from './utils/node';
export const deltaToMd = (delta, converters) => {
return convert(delta, converters).render().trimEnd() + '\n';
};
function convert(ops, converters) {
let group, line, el, activeInline, beginningOfLine;
let root = new Node();
function newLine() {
el = line = new Node(['', '\n']);
root.append(line);
activeInline = {};
}
newLine();
for (let i = 0; i < ops.length; i++) {
let op = ops[i];
if (typeof op.insert === 'object') {
for (let k in op.insert) {
if (converters.embed[k]) {
applyInlineAttributes(op.attributes);
converters.embed[k].call(el, op.insert[k], op.attributes);
}
}
} else {
let lines = op.insert.split('\n');
if (hasBlockLevelAttribute(op.attributes, converters)) {
// Some line-level styling (ie headings) is applied by inserting a \n
// with the style; the style applies back to the previous \n.
// There *should* only be one style in an insert operation.
for (let j = 1; j < lines.length; j++) {
for (let attr in op.attributes) {
if (converters.block[attr]) {
let fn = converters.block[attr];
if (typeof fn === 'object') {
if (group && group.type !== attr) {
group = null;
}
if (!group && fn.group) {
group = {
el: fn.group(),
type: attr,
value: op.attributes[attr],
distance: 0,
};
root.append(group.el);
}
if (group) {
group.el.append(line);
group.distance = 0;
}
fn = fn.line;
}
fn.call(line, op.attributes, group);
newLine();
break;
}
}
}
beginningOfLine = true;
} else {
for (let l = 0; l < lines.length; l++) {
if ((l > 0 || beginningOfLine) && group && ++group.distance >= 2) {
group = null;
}
applyInlineAttributes(
op.attributes,
ops[i + 1] && ops[i + 1].attributes
);
el.append(lines[l]);
if (l < lines.length - 1) {
newLine();
}
}
beginningOfLine = false;
}
}
}
return root;
function applyInlineAttributes(attrs, next?: any) {
let first: any[] = [];
let then: any[] = [];
attrs = attrs || {};
let tag = el,
seen = {};
while (tag._format) {
seen[tag._format] = true;
if (!attrs[tag._format] || tag.open !== tag.close) {
for (let k in seen) {
delete activeInline[k];
}
el = tag.parent();
}
tag = tag.parent();
}
for (let attr in attrs) {
if (converters.inline[attr] && attrs[attr]) {
if (activeInline[attr] && activeInline[attr] === attrs[attr]) {
continue; // do nothing -- we should already be inside this style's tag
}
if (next && attrs[attr] === next[attr]) {
first.push(attr); // if the next operation has the same style, this should be the outermost tag
} else {
then.push(attr);
}
activeInline[attr] = attrs[attr];
}
}
first.forEach(apply);
then.forEach(apply);
function apply(fmt) {
let newEl = converters.inline[fmt].call(null, attrs[fmt]);
if (Array.isArray(newEl)) {
newEl = new Node(newEl);
}
newEl._format = fmt;
el.append(newEl);
el = newEl;
}
}
}
function hasBlockLevelAttribute(attrs, converters) {
for (let k in attrs) {
if (Object.keys(converters.block).includes(k)) {
return true;
}
}
return false;
}