mirror of
https://github.com/toeverything/AFFiNE.git
synced 2026-02-27 10:52:40 +08:00
refactor(editor): unify directories naming (#11516)
**Directory Structure Changes** - Renamed multiple block-related directories by removing the "block-" prefix: - `block-attachment` → `attachment` - `block-bookmark` → `bookmark` - `block-callout` → `callout` - `block-code` → `code` - `block-data-view` → `data-view` - `block-database` → `database` - `block-divider` → `divider` - `block-edgeless-text` → `edgeless-text` - `block-embed` → `embed`
This commit is contained in:
13
blocksuite/affine/blocks/list/src/adapters/extension.ts
Normal file
13
blocksuite/affine/blocks/list/src/adapters/extension.ts
Normal file
@@ -0,0 +1,13 @@
|
||||
import type { ExtensionType } from '@blocksuite/store';
|
||||
|
||||
import { ListBlockHtmlAdapterExtension } from './html.js';
|
||||
import { ListBlockMarkdownAdapterExtension } from './markdown.js';
|
||||
import { ListBlockNotionHtmlAdapterExtension } from './notion-html.js';
|
||||
import { ListBlockPlainTextAdapterExtension } from './plain-text.js';
|
||||
|
||||
export const ListBlockAdapterExtensions: ExtensionType[] = [
|
||||
ListBlockHtmlAdapterExtension,
|
||||
ListBlockMarkdownAdapterExtension,
|
||||
ListBlockPlainTextAdapterExtension,
|
||||
ListBlockNotionHtmlAdapterExtension,
|
||||
];
|
||||
203
blocksuite/affine/blocks/list/src/adapters/html.ts
Normal file
203
blocksuite/affine/blocks/list/src/adapters/html.ts
Normal file
@@ -0,0 +1,203 @@
|
||||
import { ListBlockSchema } from '@blocksuite/affine-model';
|
||||
import {
|
||||
AdapterTextUtils,
|
||||
BlockHtmlAdapterExtension,
|
||||
type BlockHtmlAdapterMatcher,
|
||||
HastUtils,
|
||||
} from '@blocksuite/affine-shared/adapters';
|
||||
import type { DeltaInsert } from '@blocksuite/store';
|
||||
import { nanoid } from '@blocksuite/store';
|
||||
import type { Element } from 'hast';
|
||||
|
||||
export const listBlockHtmlAdapterMatcher: BlockHtmlAdapterMatcher = {
|
||||
flavour: ListBlockSchema.model.flavour,
|
||||
toMatch: o => HastUtils.isElement(o.node) && o.node.tagName === 'li',
|
||||
fromMatch: o => o.node.flavour === ListBlockSchema.model.flavour,
|
||||
toBlockSnapshot: {
|
||||
enter: (o, context) => {
|
||||
if (!HastUtils.isElement(o.node)) {
|
||||
return;
|
||||
}
|
||||
|
||||
const parentList = o.parent?.node as Element;
|
||||
let listType = 'bulleted';
|
||||
if (parentList.tagName === 'ol') {
|
||||
listType = 'numbered';
|
||||
} else if (Array.isArray(parentList.properties?.className)) {
|
||||
if (parentList.properties.className.includes('to-do-list')) {
|
||||
listType = 'todo';
|
||||
} else if (parentList.properties.className.includes('toggle')) {
|
||||
listType = 'toggle';
|
||||
} else if (parentList.properties.className.includes('bulleted-list')) {
|
||||
listType = 'bulleted';
|
||||
}
|
||||
}
|
||||
|
||||
const listNumber =
|
||||
typeof parentList.properties.start === 'number'
|
||||
? parentList.properties.start + parentList.children.indexOf(o.node)
|
||||
: null;
|
||||
const firstElementChild = HastUtils.getElementChildren(o.node)[0];
|
||||
o.node = HastUtils.flatNodes(
|
||||
o.node,
|
||||
tagName => tagName === 'div' || tagName === 'p'
|
||||
) as Element;
|
||||
|
||||
const { walkerContext, deltaConverter } = context;
|
||||
walkerContext.openNode(
|
||||
{
|
||||
type: 'block',
|
||||
id: nanoid(),
|
||||
flavour: 'affine:list',
|
||||
props: {
|
||||
type: listType,
|
||||
text: {
|
||||
'$blocksuite:internal:text$': true,
|
||||
delta:
|
||||
listType !== 'toggle'
|
||||
? deltaConverter.astToDelta(
|
||||
HastUtils.getInlineOnlyElementAST(o.node)
|
||||
)
|
||||
: deltaConverter.astToDelta(
|
||||
HastUtils.querySelector(o.node, 'summary') ?? o.node
|
||||
),
|
||||
},
|
||||
checked:
|
||||
listType === 'todo'
|
||||
? firstElementChild &&
|
||||
Array.isArray(firstElementChild.properties?.className) &&
|
||||
firstElementChild.properties.className.includes('checkbox-on')
|
||||
: false,
|
||||
collapsed:
|
||||
listType === 'toggle'
|
||||
? firstElementChild &&
|
||||
firstElementChild.tagName === 'details' &&
|
||||
firstElementChild.properties.open === undefined
|
||||
: false,
|
||||
order: listNumber,
|
||||
},
|
||||
children: [],
|
||||
},
|
||||
'children'
|
||||
);
|
||||
},
|
||||
leave: (_, context) => {
|
||||
const { walkerContext } = context;
|
||||
walkerContext.closeNode();
|
||||
},
|
||||
},
|
||||
fromBlockSnapshot: {
|
||||
enter: (o, context) => {
|
||||
const text = (o.node.props.text ?? { delta: [] }) as {
|
||||
delta: DeltaInsert[];
|
||||
};
|
||||
const { deltaConverter, walkerContext } = context;
|
||||
const currentTNode = walkerContext.currentNode();
|
||||
const liChildren = deltaConverter.deltaToAST(text.delta);
|
||||
if (o.node.props.type === 'todo') {
|
||||
liChildren.unshift({
|
||||
type: 'element',
|
||||
tagName: 'input',
|
||||
properties: {
|
||||
type: 'checkbox',
|
||||
checked: o.node.props.checked as boolean,
|
||||
},
|
||||
children: [
|
||||
{
|
||||
type: 'element',
|
||||
tagName: 'label',
|
||||
properties: {
|
||||
style: 'margin-right: 3px;',
|
||||
},
|
||||
children: [],
|
||||
},
|
||||
],
|
||||
});
|
||||
}
|
||||
// check if the list is of the same type
|
||||
if (
|
||||
walkerContext.getNodeContext('affine:list:parent') === o.parent &&
|
||||
currentTNode.type === 'element' &&
|
||||
currentTNode.tagName ===
|
||||
(o.node.props.type === 'numbered' ? 'ol' : 'ul') &&
|
||||
!(
|
||||
Array.isArray(currentTNode.properties.className) &&
|
||||
currentTNode.properties.className.includes('todo-list')
|
||||
) ===
|
||||
AdapterTextUtils.isNullish(
|
||||
o.node.props.type === 'todo'
|
||||
? (o.node.props.checked as boolean)
|
||||
: undefined
|
||||
)
|
||||
) {
|
||||
// if true, add the list item to the list
|
||||
} else {
|
||||
// if false, create a new list
|
||||
walkerContext.openNode(
|
||||
{
|
||||
type: 'element',
|
||||
tagName: o.node.props.type === 'numbered' ? 'ol' : 'ul',
|
||||
properties: {
|
||||
style:
|
||||
o.node.props.type === 'todo'
|
||||
? 'list-style-type: none; padding-inline-start: 18px;'
|
||||
: null,
|
||||
className: [o.node.props.type + '-list'],
|
||||
},
|
||||
children: [],
|
||||
},
|
||||
'children'
|
||||
);
|
||||
walkerContext.setNodeContext('affine:list:parent', o.parent);
|
||||
}
|
||||
|
||||
walkerContext.openNode(
|
||||
{
|
||||
type: 'element',
|
||||
tagName: 'li',
|
||||
properties: {
|
||||
className: ['affine-list-block-container'],
|
||||
},
|
||||
children: liChildren,
|
||||
},
|
||||
'children'
|
||||
);
|
||||
},
|
||||
leave: (o, context) => {
|
||||
const { walkerContext } = context;
|
||||
const currentTNode = walkerContext.currentNode() as Element;
|
||||
const previousTNode = walkerContext.previousNode() as Element;
|
||||
if (
|
||||
walkerContext.getPreviousNodeContext('affine:list:parent') ===
|
||||
o.parent &&
|
||||
currentTNode.tagName === 'li' &&
|
||||
previousTNode.tagName ===
|
||||
(o.node.props.type === 'numbered' ? 'ol' : 'ul') &&
|
||||
!(
|
||||
Array.isArray(previousTNode.properties.className) &&
|
||||
previousTNode.properties.className.includes('todo-list')
|
||||
) ===
|
||||
AdapterTextUtils.isNullish(
|
||||
o.node.props.type === 'todo'
|
||||
? (o.node.props.checked as boolean)
|
||||
: undefined
|
||||
)
|
||||
) {
|
||||
walkerContext.closeNode();
|
||||
if (
|
||||
o.next?.flavour !== 'affine:list' ||
|
||||
o.next.props.type !== o.node.props.type
|
||||
) {
|
||||
// If the next node is not a list or different type of list, close the list
|
||||
walkerContext.closeNode();
|
||||
}
|
||||
} else {
|
||||
walkerContext.closeNode().closeNode();
|
||||
}
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
export const ListBlockHtmlAdapterExtension = BlockHtmlAdapterExtension(
|
||||
listBlockHtmlAdapterMatcher
|
||||
);
|
||||
4
blocksuite/affine/blocks/list/src/adapters/index.ts
Normal file
4
blocksuite/affine/blocks/list/src/adapters/index.ts
Normal file
@@ -0,0 +1,4 @@
|
||||
export * from './html.js';
|
||||
export * from './markdown.js';
|
||||
export * from './notion-html.js';
|
||||
export * from './plain-text.js';
|
||||
156
blocksuite/affine/blocks/list/src/adapters/markdown.ts
Normal file
156
blocksuite/affine/blocks/list/src/adapters/markdown.ts
Normal file
@@ -0,0 +1,156 @@
|
||||
import { ListBlockSchema } from '@blocksuite/affine-model';
|
||||
import {
|
||||
AdapterTextUtils,
|
||||
BlockMarkdownAdapterExtension,
|
||||
type BlockMarkdownAdapterMatcher,
|
||||
type MarkdownAST,
|
||||
} from '@blocksuite/affine-shared/adapters';
|
||||
import type { DeltaInsert } from '@blocksuite/store';
|
||||
import { nanoid } from '@blocksuite/store';
|
||||
import type { List } from 'mdast';
|
||||
|
||||
const LIST_MDAST_TYPE = new Set(['list', 'listItem']);
|
||||
const isListMDASTType = (node: MarkdownAST) => LIST_MDAST_TYPE.has(node.type);
|
||||
|
||||
export const listBlockMarkdownAdapterMatcher: BlockMarkdownAdapterMatcher = {
|
||||
flavour: ListBlockSchema.model.flavour,
|
||||
toMatch: o => isListMDASTType(o.node),
|
||||
fromMatch: o => o.node.flavour === ListBlockSchema.model.flavour,
|
||||
toBlockSnapshot: {
|
||||
enter: (o, context) => {
|
||||
const { walkerContext, deltaConverter } = context;
|
||||
if (o.node.type === 'listItem') {
|
||||
const parentList = o.parent?.node as List;
|
||||
const listNumber = parentList.start
|
||||
? parentList.start + parentList.children.indexOf(o.node)
|
||||
: null;
|
||||
walkerContext.openNode(
|
||||
{
|
||||
type: 'block',
|
||||
id: nanoid(),
|
||||
flavour: 'affine:list',
|
||||
props: {
|
||||
type:
|
||||
o.node.checked !== null
|
||||
? 'todo'
|
||||
: parentList.ordered
|
||||
? 'numbered'
|
||||
: 'bulleted',
|
||||
text: {
|
||||
'$blocksuite:internal:text$': true,
|
||||
delta:
|
||||
o.node.children[0] && o.node.children[0].type === 'paragraph'
|
||||
? deltaConverter.astToDelta(o.node.children[0])
|
||||
: [],
|
||||
},
|
||||
checked: o.node.checked ?? false,
|
||||
collapsed: false,
|
||||
order: listNumber,
|
||||
},
|
||||
children: [],
|
||||
},
|
||||
'children'
|
||||
);
|
||||
if (o.node.children[0] && o.node.children[0].type === 'paragraph') {
|
||||
walkerContext.skipChildren(1);
|
||||
}
|
||||
}
|
||||
},
|
||||
leave: (o, context) => {
|
||||
const { walkerContext } = context;
|
||||
if (o.node.type === 'listItem') {
|
||||
walkerContext.closeNode();
|
||||
}
|
||||
},
|
||||
},
|
||||
fromBlockSnapshot: {
|
||||
enter: (o, context) => {
|
||||
const { walkerContext, deltaConverter } = context;
|
||||
const text = (o.node.props.text ?? { delta: [] }) as {
|
||||
delta: DeltaInsert[];
|
||||
};
|
||||
const currentTNode = walkerContext.currentNode();
|
||||
// check if the list is of the same type
|
||||
if (
|
||||
walkerContext.getNodeContext('affine:list:parent') === o.parent &&
|
||||
currentTNode.type === 'list' &&
|
||||
currentTNode.ordered === (o.node.props.type === 'numbered') &&
|
||||
AdapterTextUtils.isNullish(currentTNode.children[0].checked) ===
|
||||
AdapterTextUtils.isNullish(
|
||||
o.node.props.type === 'todo'
|
||||
? (o.node.props.checked as boolean)
|
||||
: undefined
|
||||
)
|
||||
) {
|
||||
// if true, add the list item to the list
|
||||
} else {
|
||||
// if false, create a new list
|
||||
walkerContext
|
||||
.openNode(
|
||||
{
|
||||
type: 'list',
|
||||
ordered: o.node.props.type === 'numbered',
|
||||
spread: false,
|
||||
children: [],
|
||||
},
|
||||
'children'
|
||||
)
|
||||
.setNodeContext('affine:list:parent', o.parent);
|
||||
}
|
||||
walkerContext
|
||||
.openNode(
|
||||
{
|
||||
type: 'listItem',
|
||||
checked:
|
||||
o.node.props.type === 'todo'
|
||||
? (o.node.props.checked as boolean)
|
||||
: undefined,
|
||||
spread: false,
|
||||
children: [],
|
||||
},
|
||||
'children'
|
||||
)
|
||||
.openNode(
|
||||
{
|
||||
type: 'paragraph',
|
||||
children: deltaConverter.deltaToAST(text.delta),
|
||||
},
|
||||
'children'
|
||||
)
|
||||
.closeNode();
|
||||
},
|
||||
leave: (o, context) => {
|
||||
const { walkerContext } = context;
|
||||
const currentTNode = walkerContext.currentNode();
|
||||
const previousTNode = walkerContext.previousNode();
|
||||
if (
|
||||
walkerContext.getPreviousNodeContext('affine:list:parent') ===
|
||||
o.parent &&
|
||||
currentTNode.type === 'listItem' &&
|
||||
previousTNode?.type === 'list' &&
|
||||
previousTNode.ordered === (o.node.props.type === 'numbered') &&
|
||||
AdapterTextUtils.isNullish(currentTNode.checked) ===
|
||||
AdapterTextUtils.isNullish(
|
||||
o.node.props.type === 'todo'
|
||||
? (o.node.props.checked as boolean)
|
||||
: undefined
|
||||
)
|
||||
) {
|
||||
walkerContext.closeNode();
|
||||
if (
|
||||
o.next?.flavour !== 'affine:list' ||
|
||||
o.next.props.type !== o.node.props.type
|
||||
) {
|
||||
// If the next node is not a list or different type of list, close the list
|
||||
walkerContext.closeNode();
|
||||
}
|
||||
} else {
|
||||
walkerContext.closeNode().closeNode();
|
||||
}
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
export const ListBlockMarkdownAdapterExtension = BlockMarkdownAdapterExtension(
|
||||
listBlockMarkdownAdapterMatcher
|
||||
);
|
||||
116
blocksuite/affine/blocks/list/src/adapters/notion-html.ts
Normal file
116
blocksuite/affine/blocks/list/src/adapters/notion-html.ts
Normal file
@@ -0,0 +1,116 @@
|
||||
import { ListBlockSchema } from '@blocksuite/affine-model';
|
||||
import {
|
||||
BlockNotionHtmlAdapterExtension,
|
||||
type BlockNotionHtmlAdapterMatcher,
|
||||
HastUtils,
|
||||
} from '@blocksuite/affine-shared/adapters';
|
||||
import type { DeltaInsert } from '@blocksuite/store';
|
||||
import { nanoid } from '@blocksuite/store';
|
||||
|
||||
const listBlockMatchTags = new Set(['ul', 'ol', 'li']);
|
||||
|
||||
export const listBlockNotionHtmlAdapterMatcher: BlockNotionHtmlAdapterMatcher =
|
||||
{
|
||||
flavour: ListBlockSchema.model.flavour,
|
||||
toMatch: o =>
|
||||
HastUtils.isElement(o.node) && listBlockMatchTags.has(o.node.tagName),
|
||||
fromMatch: () => false,
|
||||
toBlockSnapshot: {
|
||||
enter: (o, context) => {
|
||||
if (!HastUtils.isElement(o.node)) {
|
||||
return;
|
||||
}
|
||||
|
||||
const { walkerContext, pageMap, deltaConverter } = context;
|
||||
switch (o.node.tagName) {
|
||||
case 'ul':
|
||||
case 'ol': {
|
||||
walkerContext.setNodeContext('hast:list:type', 'bulleted');
|
||||
if (o.node.tagName === 'ol') {
|
||||
walkerContext.setNodeContext('hast:list:type', 'numbered');
|
||||
} else if (Array.isArray(o.node.properties?.className)) {
|
||||
if (o.node.properties.className.includes('to-do-list')) {
|
||||
walkerContext.setNodeContext('hast:list:type', 'todo');
|
||||
} else if (o.node.properties.className.includes('toggle')) {
|
||||
walkerContext.setNodeContext('hast:list:type', 'toggle');
|
||||
} else if (
|
||||
o.node.properties.className.includes('bulleted-list')
|
||||
) {
|
||||
walkerContext.setNodeContext('hast:list:type', 'bulleted');
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
case 'li': {
|
||||
const firstElementChild = HastUtils.getElementChildren(o.node)[0];
|
||||
const notionListType =
|
||||
walkerContext.getNodeContext('hast:list:type');
|
||||
const listType =
|
||||
notionListType === 'toggle' ? 'bulleted' : notionListType;
|
||||
let delta: DeltaInsert[] = [];
|
||||
if (notionListType === 'toggle') {
|
||||
delta = deltaConverter.astToDelta(
|
||||
HastUtils.querySelector(o.node, 'summary') ?? o.node,
|
||||
{ pageMap }
|
||||
);
|
||||
} else if (notionListType === 'todo') {
|
||||
delta = deltaConverter.astToDelta(o.node, { pageMap });
|
||||
} else {
|
||||
delta = deltaConverter.astToDelta(
|
||||
HastUtils.getInlineOnlyElementAST(o.node),
|
||||
{
|
||||
pageMap,
|
||||
}
|
||||
);
|
||||
}
|
||||
walkerContext.openNode(
|
||||
{
|
||||
type: 'block',
|
||||
id: nanoid(),
|
||||
flavour: 'affine:list',
|
||||
props: {
|
||||
type: listType,
|
||||
text: {
|
||||
'$blocksuite:internal:text$': true,
|
||||
delta,
|
||||
},
|
||||
checked:
|
||||
notionListType === 'todo'
|
||||
? firstElementChild &&
|
||||
Array.isArray(
|
||||
firstElementChild.properties?.className
|
||||
) &&
|
||||
firstElementChild.properties.className.includes(
|
||||
'checkbox-on'
|
||||
)
|
||||
: false,
|
||||
collapsed:
|
||||
notionListType === 'toggle'
|
||||
? firstElementChild &&
|
||||
firstElementChild.tagName === 'details' &&
|
||||
firstElementChild.properties.open === undefined
|
||||
: false,
|
||||
},
|
||||
children: [],
|
||||
},
|
||||
'children'
|
||||
);
|
||||
break;
|
||||
}
|
||||
}
|
||||
},
|
||||
leave: (o, context) => {
|
||||
const { walkerContext } = context;
|
||||
if (!HastUtils.isElement(o.node)) {
|
||||
return;
|
||||
}
|
||||
if (o.node.tagName === 'li') {
|
||||
walkerContext.closeNode();
|
||||
}
|
||||
},
|
||||
},
|
||||
fromBlockSnapshot: {},
|
||||
};
|
||||
|
||||
export const ListBlockNotionHtmlAdapterExtension =
|
||||
BlockNotionHtmlAdapterExtension(listBlockNotionHtmlAdapterMatcher);
|
||||
27
blocksuite/affine/blocks/list/src/adapters/plain-text.ts
Normal file
27
blocksuite/affine/blocks/list/src/adapters/plain-text.ts
Normal file
@@ -0,0 +1,27 @@
|
||||
import { ListBlockSchema } from '@blocksuite/affine-model';
|
||||
import {
|
||||
BlockPlainTextAdapterExtension,
|
||||
type BlockPlainTextAdapterMatcher,
|
||||
} from '@blocksuite/affine-shared/adapters';
|
||||
import type { DeltaInsert } from '@blocksuite/store';
|
||||
|
||||
export const listBlockPlainTextAdapterMatcher: BlockPlainTextAdapterMatcher = {
|
||||
flavour: ListBlockSchema.model.flavour,
|
||||
toMatch: () => false,
|
||||
fromMatch: o => o.node.flavour === ListBlockSchema.model.flavour,
|
||||
toBlockSnapshot: {},
|
||||
fromBlockSnapshot: {
|
||||
enter: (o, context) => {
|
||||
const text = (o.node.props.text ?? { delta: [] }) as {
|
||||
delta: DeltaInsert[];
|
||||
};
|
||||
const { deltaConverter } = context;
|
||||
const buffer = deltaConverter.deltaToAST(text.delta).join('');
|
||||
context.textBuffer.content += buffer;
|
||||
context.textBuffer.content += '\n';
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
export const ListBlockPlainTextAdapterExtension =
|
||||
BlockPlainTextAdapterExtension(listBlockPlainTextAdapterMatcher);
|
||||
Reference in New Issue
Block a user