mirror of
https://github.com/toeverything/AFFiNE.git
synced 2026-02-25 10:22:55 +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:
@@ -0,0 +1,56 @@
|
||||
import { focusTextModel } from '@blocksuite/affine-rich-text';
|
||||
import { type Command, TextSelection } from '@blocksuite/std';
|
||||
|
||||
/**
|
||||
* Add a paragraph next to the current block.
|
||||
*/
|
||||
export const addParagraphCommand: Command<
|
||||
{
|
||||
blockId?: string;
|
||||
},
|
||||
{
|
||||
paragraphConvertedId: string;
|
||||
}
|
||||
> = (ctx, next) => {
|
||||
const { std } = ctx;
|
||||
const { store, selection } = std;
|
||||
store.captureSync();
|
||||
|
||||
let blockId = ctx.blockId;
|
||||
if (!blockId) {
|
||||
const text = selection.find(TextSelection);
|
||||
blockId = text?.blockId;
|
||||
}
|
||||
if (!blockId) return;
|
||||
|
||||
const model = store.getBlock(blockId)?.model;
|
||||
if (!model) return;
|
||||
|
||||
let id: string;
|
||||
if (model.children.length > 0) {
|
||||
// before:
|
||||
// aaa|
|
||||
// bbb
|
||||
//
|
||||
// after:
|
||||
// aaa
|
||||
// |
|
||||
// bbb
|
||||
id = store.addBlock('affine:paragraph', {}, model, 0);
|
||||
} else {
|
||||
const parent = store.getParent(model);
|
||||
if (!parent) return;
|
||||
const index = parent.children.indexOf(model);
|
||||
if (index < 0) return;
|
||||
// before:
|
||||
// aaa|
|
||||
//
|
||||
// after:
|
||||
// aaa
|
||||
// |
|
||||
id = store.addBlock('affine:paragraph', {}, parent, index + 1);
|
||||
}
|
||||
|
||||
focusTextModel(std, id);
|
||||
return next({ paragraphConvertedId: id });
|
||||
};
|
||||
@@ -0,0 +1,30 @@
|
||||
import { focusTextModel } from '@blocksuite/affine-rich-text';
|
||||
import { getLastNoteBlock } from '@blocksuite/affine-shared/utils';
|
||||
import type { Command } from '@blocksuite/std';
|
||||
import { Text } from '@blocksuite/store';
|
||||
|
||||
/**
|
||||
* Append a paragraph block at the end of the whole page.
|
||||
*/
|
||||
export const appendParagraphCommand: Command<{ text?: string }> = (
|
||||
ctx,
|
||||
next
|
||||
) => {
|
||||
const { std, text = '' } = ctx;
|
||||
const { store } = std;
|
||||
if (!store.root) return;
|
||||
|
||||
const note = getLastNoteBlock(store);
|
||||
let noteId = note?.id;
|
||||
if (!noteId) {
|
||||
noteId = store.addBlock('affine:note', {}, store.root.id);
|
||||
}
|
||||
const id = store.addBlock(
|
||||
'affine:paragraph',
|
||||
{ text: new Text(text) },
|
||||
noteId
|
||||
);
|
||||
|
||||
focusTextModel(std, id, text.length);
|
||||
next();
|
||||
};
|
||||
@@ -0,0 +1,114 @@
|
||||
import { ParagraphBlockModel } from '@blocksuite/affine-model';
|
||||
import type { IndentContext } from '@blocksuite/affine-shared/types';
|
||||
import {
|
||||
calculateCollapsedSiblings,
|
||||
matchModels,
|
||||
} from '@blocksuite/affine-shared/utils';
|
||||
import { type Command, TextSelection } from '@blocksuite/std';
|
||||
|
||||
export const canDedentParagraphCommand: Command<
|
||||
Partial<Omit<IndentContext, 'flavour' | 'type'>>,
|
||||
{
|
||||
indentContext: IndentContext;
|
||||
}
|
||||
> = (ctx, next) => {
|
||||
let { blockId, inlineIndex } = ctx;
|
||||
const { std } = ctx;
|
||||
const { selection, store } = std;
|
||||
const text = selection.find(TextSelection);
|
||||
|
||||
if (!blockId) {
|
||||
/**
|
||||
* Do nothing if the selection:
|
||||
* - is not a text selection
|
||||
* - or spans multiple blocks
|
||||
*/
|
||||
if (!text || (text.to && text.from.blockId !== text.to.blockId)) {
|
||||
return;
|
||||
}
|
||||
|
||||
blockId = text.from.blockId;
|
||||
inlineIndex = text.from.index;
|
||||
}
|
||||
if (blockId == null || inlineIndex == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
const model = store.getBlock(blockId)?.model;
|
||||
if (!model || !matchModels(model, [ParagraphBlockModel])) {
|
||||
return;
|
||||
}
|
||||
|
||||
const parent = store.getParent(model);
|
||||
if (store.readonly || !parent || parent.role !== 'content') {
|
||||
// Top most, can not unindent, do nothing
|
||||
return;
|
||||
}
|
||||
|
||||
const grandParent = store.getParent(parent);
|
||||
if (!grandParent) return;
|
||||
|
||||
return next({
|
||||
indentContext: {
|
||||
blockId,
|
||||
inlineIndex,
|
||||
type: 'dedent',
|
||||
flavour: 'affine:paragraph',
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
export const dedentParagraphCommand: Command<{
|
||||
indentContext: IndentContext;
|
||||
}> = (ctx, next) => {
|
||||
const { indentContext: dedentContext, std } = ctx;
|
||||
const { store, selection, range, host } = std;
|
||||
|
||||
if (
|
||||
!dedentContext ||
|
||||
dedentContext.type !== 'dedent' ||
|
||||
dedentContext.flavour !== 'affine:paragraph'
|
||||
) {
|
||||
console.warn(
|
||||
'you need to use `canDedentParagraph` command before running `dedentParagraph` command'
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
const { blockId } = dedentContext;
|
||||
|
||||
const model = store.getBlock(blockId)?.model;
|
||||
if (!model) return;
|
||||
|
||||
const parent = store.getParent(model);
|
||||
if (!parent) return;
|
||||
|
||||
const grandParent = store.getParent(parent);
|
||||
if (!grandParent) return;
|
||||
|
||||
store.captureSync();
|
||||
|
||||
if (
|
||||
matchModels(model, [ParagraphBlockModel]) &&
|
||||
model.props.type.startsWith('h') &&
|
||||
model.props.collapsed
|
||||
) {
|
||||
const collapsedSiblings = calculateCollapsedSiblings(model);
|
||||
store.moveBlocks([model, ...collapsedSiblings], grandParent, parent, false);
|
||||
} else {
|
||||
const nextSiblings = store.getNexts(model);
|
||||
store.moveBlocks(nextSiblings, model);
|
||||
store.moveBlocks([model], grandParent, parent, false);
|
||||
}
|
||||
|
||||
const textSelection = selection.find(TextSelection);
|
||||
if (textSelection) {
|
||||
host.updateComplete
|
||||
.then(() => {
|
||||
range.syncTextSelectionToRange(textSelection);
|
||||
})
|
||||
.catch(console.error);
|
||||
}
|
||||
|
||||
return next();
|
||||
};
|
||||
@@ -0,0 +1,156 @@
|
||||
import { ListBlockModel, ParagraphBlockModel } from '@blocksuite/affine-model';
|
||||
import type { IndentContext } from '@blocksuite/affine-shared/types';
|
||||
import {
|
||||
calculateCollapsedSiblings,
|
||||
getNearestHeadingBefore,
|
||||
matchModels,
|
||||
} from '@blocksuite/affine-shared/utils';
|
||||
import { type Command, TextSelection } from '@blocksuite/std';
|
||||
|
||||
export const canIndentParagraphCommand: Command<
|
||||
Partial<Omit<IndentContext, 'flavour' | 'type'>>,
|
||||
{
|
||||
indentContext: IndentContext;
|
||||
}
|
||||
> = (cxt, next) => {
|
||||
let { blockId, inlineIndex } = cxt;
|
||||
const { std } = cxt;
|
||||
const { selection, store } = std;
|
||||
const { schema } = store;
|
||||
|
||||
if (!blockId) {
|
||||
const text = selection.find(TextSelection);
|
||||
/**
|
||||
* Do nothing if the selection:
|
||||
* - is not a text selection
|
||||
* - or spans multiple blocks
|
||||
*/
|
||||
if (!text || (text.to && text.from.blockId !== text.to.blockId)) {
|
||||
return;
|
||||
}
|
||||
|
||||
blockId = text.from.blockId;
|
||||
inlineIndex = text.from.index;
|
||||
}
|
||||
if (blockId == null || inlineIndex == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
const model = std.store.getBlock(blockId)?.model;
|
||||
if (!model || !matchModels(model, [ParagraphBlockModel])) {
|
||||
return;
|
||||
}
|
||||
|
||||
const previousSibling = store.getPrev(model);
|
||||
if (
|
||||
store.readonly ||
|
||||
!previousSibling ||
|
||||
!schema.isValid(model.flavour, previousSibling.flavour)
|
||||
) {
|
||||
// Bottom, can not indent, do nothing
|
||||
return;
|
||||
}
|
||||
|
||||
return next({
|
||||
indentContext: {
|
||||
blockId,
|
||||
inlineIndex,
|
||||
type: 'indent',
|
||||
flavour: 'affine:paragraph',
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
export const indentParagraphCommand: Command<{
|
||||
indentContext: IndentContext;
|
||||
}> = (ctx, next) => {
|
||||
const { indentContext, std } = ctx;
|
||||
const { store, selection, host, range } = std;
|
||||
|
||||
if (
|
||||
!indentContext ||
|
||||
indentContext.type !== 'indent' ||
|
||||
indentContext.flavour !== 'affine:paragraph'
|
||||
) {
|
||||
console.warn(
|
||||
'you need to use `canIndentParagraph` command before running `indentParagraph` command'
|
||||
);
|
||||
return;
|
||||
}
|
||||
const { blockId } = indentContext;
|
||||
|
||||
const model = store.getBlock(blockId)?.model;
|
||||
if (!model) return;
|
||||
|
||||
const previousSibling = store.getPrev(model);
|
||||
if (!previousSibling) return;
|
||||
|
||||
store.captureSync();
|
||||
|
||||
{
|
||||
// > # 123
|
||||
// > # 456
|
||||
//
|
||||
// we need to update 123 collapsed state to false when indent 456
|
||||
const nearestHeading = getNearestHeadingBefore(model);
|
||||
if (
|
||||
nearestHeading &&
|
||||
matchModels(nearestHeading, [ParagraphBlockModel]) &&
|
||||
nearestHeading.props.collapsed
|
||||
) {
|
||||
store.updateBlock(nearestHeading, {
|
||||
collapsed: false,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
if (
|
||||
matchModels(model, [ParagraphBlockModel]) &&
|
||||
model.props.type.startsWith('h') &&
|
||||
model.props.collapsed
|
||||
) {
|
||||
const collapsedSiblings = calculateCollapsedSiblings(model);
|
||||
store.moveBlocks([model, ...collapsedSiblings], previousSibling);
|
||||
} else {
|
||||
store.moveBlocks([model], previousSibling);
|
||||
}
|
||||
|
||||
{
|
||||
// 123
|
||||
// > # 456
|
||||
// 789
|
||||
//
|
||||
// we need to update 456 collapsed state to false when indent 789
|
||||
const nearestHeading = getNearestHeadingBefore(model);
|
||||
if (
|
||||
nearestHeading &&
|
||||
matchModels(nearestHeading, [ParagraphBlockModel]) &&
|
||||
nearestHeading.props.collapsed
|
||||
) {
|
||||
store.updateBlock(nearestHeading, {
|
||||
collapsed: false,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// update collapsed state of affine list
|
||||
if (
|
||||
matchModels(previousSibling, [ListBlockModel]) &&
|
||||
previousSibling.props.collapsed
|
||||
) {
|
||||
store.updateBlock(previousSibling, {
|
||||
collapsed: false,
|
||||
});
|
||||
}
|
||||
|
||||
const textSelection = selection.find(TextSelection);
|
||||
if (textSelection) {
|
||||
host.updateComplete
|
||||
.then(() => {
|
||||
range.syncTextSelectionToRange(textSelection);
|
||||
})
|
||||
.catch(console.error);
|
||||
}
|
||||
|
||||
return next();
|
||||
};
|
||||
11
blocksuite/affine/blocks/paragraph/src/commands/index.ts
Normal file
11
blocksuite/affine/blocks/paragraph/src/commands/index.ts
Normal file
@@ -0,0 +1,11 @@
|
||||
export { addParagraphCommand } from './add-paragraph.js';
|
||||
export { appendParagraphCommand } from './append-paragraph.js';
|
||||
export {
|
||||
canDedentParagraphCommand,
|
||||
dedentParagraphCommand,
|
||||
} from './dedent-paragraph.js';
|
||||
export {
|
||||
canIndentParagraphCommand,
|
||||
indentParagraphCommand,
|
||||
} from './indent-paragraph.js';
|
||||
export { splitParagraphCommand } from './split-paragraph.js';
|
||||
@@ -0,0 +1,81 @@
|
||||
import { ParagraphBlockModel } from '@blocksuite/affine-model';
|
||||
import {
|
||||
focusTextModel,
|
||||
getInlineEditorByModel,
|
||||
} from '@blocksuite/affine-rich-text';
|
||||
import { matchModels } from '@blocksuite/affine-shared/utils';
|
||||
import { type Command, TextSelection } from '@blocksuite/std';
|
||||
|
||||
export const splitParagraphCommand: Command<
|
||||
{
|
||||
blockId?: string;
|
||||
},
|
||||
{
|
||||
paragraphConvertedId: string;
|
||||
}
|
||||
> = (ctx, next) => {
|
||||
const { std } = ctx;
|
||||
const { store, selection } = std;
|
||||
let blockId = ctx.blockId;
|
||||
if (!blockId) {
|
||||
const text = selection.find(TextSelection);
|
||||
blockId = text?.blockId;
|
||||
}
|
||||
if (!blockId) return;
|
||||
|
||||
const model = store.getBlock(blockId)?.model;
|
||||
if (!model || !matchModels(model, [ParagraphBlockModel])) return;
|
||||
|
||||
const inlineEditor = getInlineEditorByModel(std, model);
|
||||
const range = inlineEditor?.getInlineRange();
|
||||
if (!range) return;
|
||||
|
||||
const splitIndex = range.index;
|
||||
const splitLength = range.length;
|
||||
// On press enter, it may convert symbols from yjs ContentString
|
||||
// to yjs ContentFormat. Once it happens, the converted symbol will
|
||||
// be deleted and not counted as model.text.yText.length.
|
||||
// Example: "`a`[enter]" -> yText[<ContentFormat: Code>, "a", <ContentFormat: Code>]
|
||||
// In this case, we should not split the block.
|
||||
if (model.props.text.yText.length < splitIndex + splitLength) return;
|
||||
|
||||
if (model.children.length > 0 && splitIndex > 0) {
|
||||
store.captureSync();
|
||||
const right = model.props.text.split(splitIndex, splitLength);
|
||||
const id = store.addBlock(
|
||||
model.flavour,
|
||||
{
|
||||
text: right,
|
||||
type: model.props.type,
|
||||
},
|
||||
model,
|
||||
0
|
||||
);
|
||||
focusTextModel(std, id);
|
||||
return next({ paragraphConvertedId: id });
|
||||
}
|
||||
|
||||
const parent = store.getParent(model);
|
||||
if (!parent) return;
|
||||
const index = parent.children.indexOf(model);
|
||||
if (index < 0) return;
|
||||
store.captureSync();
|
||||
const right = model.props.text.split(splitIndex, splitLength);
|
||||
const id = store.addBlock(
|
||||
model.flavour,
|
||||
{
|
||||
text: right,
|
||||
type: model.props.type,
|
||||
},
|
||||
parent,
|
||||
index + 1
|
||||
);
|
||||
const newModel = store.getBlock(id)?.model;
|
||||
if (newModel) {
|
||||
store.moveBlocks(model.children, newModel);
|
||||
} else {
|
||||
console.error('Failed to find the new model split from the paragraph');
|
||||
}
|
||||
focusTextModel(std, id);
|
||||
return next({ paragraphConvertedId: id });
|
||||
};
|
||||
Reference in New Issue
Block a user