Files
AFFiNE-Mirror/libs/components/editor-blocks/src/utils/indent.ts
T
2022-08-16 13:06:14 +08:00

203 lines
4.9 KiB
TypeScript

import {
type BlockEditor,
supportChildren,
} from '@toeverything/components/editor-core';
import { Protocol } from '@toeverything/datasource/db-service';
import { AsyncBlock } from '@toeverything/framework/virgo';
import type { TodoAsyncBlock } from '../blocks/todo/types';
/**
* Is the block in top level
*/
export const isTopLevelBlock = (
editor: BlockEditor,
block: AsyncBlock
): boolean => {
return (
editor.getRootBlockId() === block.id ||
block.type === Protocol.Block.Type.group ||
block.type === Protocol.Block.Type.page
);
};
/**
* Move down
* @returns true if indent is success
* @example
* ```
* [ ]
* └─ [ ]
* [x] <- tab
* └─ [ ]
*
* ↓
*
* [ ]
* ├─ [ ]
* ├─ [x] <-
* └─ [ ]
* ```
*/
const indentBlock = async (block: TodoAsyncBlock) => {
const previousBlock = await block.previousSibling();
if (!previousBlock || !supportChildren(previousBlock)) {
// Bottom, can not indent, do nothing
return false;
}
// Indent current node, but preserve child node hierarchy
const previousTodo = previousBlock as TodoAsyncBlock;
await previousTodo.setProperties({
collapsed: { value: false },
});
// 1. save target block children
const children = await block.children();
// 2. remove current block and children
await block.remove();
await block.removeChildren();
// 3. append target block and children to previous node
await previousTodo.append(block, ...children);
return true;
};
/**
* Move up
* @returns true if dedent is success
* @example
* ```
* [ ]
* ├─ [ ]
* ├─ [x] <- shift + tab
* └─ [ ]
*
* ↓
*
* [ ]
* └─ [ ]
* [x] <-
* └─ [ ]
* ```
*/
const dedentBlock = async (editor: BlockEditor, block: AsyncBlock) => {
if (editor.getRootBlockId() === block.id) {
return false;
}
let parentBlock = await block.parent();
if (!parentBlock) {
throw new Error('Failed to dedent block! Parent block not found!');
}
if (isTopLevelBlock(editor, parentBlock)) {
// Top, do nothing
return false;
}
// 1. save child blocks of the parent block
const previousSiblings = await block.previousSiblings();
const nextSiblings = await block.nextSiblings();
// const children = await parentBlock.children();
// 2. remove all child blocks after the target block from the parent block
await parentBlock.removeChildren();
// TODO fix block sync with db-service
// Need update block from service now
parentBlock = await block.parent();
if (!parentBlock) {
throw new Error('Failed to dedent block! Parent block not found!');
}
await parentBlock.append(...previousSiblings);
parentBlock = await block.parent();
if (!parentBlock) {
throw new Error('Failed to dedent block! Parent block not found!');
}
// 3. remove current block
await block.remove();
// 4. append parent children to target block
await block.append(...nextSiblings);
// 5. append block to parent
await parentBlock.after(block);
return true;
};
export const tabBlock = async (
editor: BlockEditor,
block: AsyncBlock,
isShiftKey: boolean
) => {
if (isShiftKey) {
return await dedentBlock(editor, block);
} else {
return await indentBlock(block);
}
};
const type2Map: Record<number, string> = {};
const type3Map: Record<number, string> = {};
export function getNumber(type: string, index: number) {
if (type === 'type2') {
if (type2Map[index]) {
return type2Map[index];
}
type2Map[index] = getType2(index - 1);
return type2Map[index];
}
if (type === 'type3') {
if (type3Map[index]) {
return type3Map[index];
}
type3Map[index] = getType3(index);
return type3Map[index];
}
return index;
}
const getType2 = (n: number) => {
const ordA = 'a'.charCodeAt(0);
const ordZ = 'z'.charCodeAt(0);
const len = ordZ - ordA + 1;
let s = '';
while (n >= 0) {
s = String.fromCharCode((n % len) + ordA) + s;
n = Math.floor(n / len) - 1;
}
return s;
};
const getType3 = (num: number) => {
const lookup = {
m: 1000,
cm: 900,
d: 500,
cd: 400,
c: 100,
xc: 90,
l: 50,
xl: 40,
x: 10,
ix: 9,
v: 5,
iv: 4,
i: 1,
};
let romanStr = '';
for (const i in lookup) {
while (num >= lookup[i as keyof typeof lookup]) {
romanStr += i;
num -= lookup[i as keyof typeof lookup];
}
}
return romanStr;
};
export function getChildrenType(type: string) {
const typeMap: Record<string, string> = {
type1: 'type2',
type2: 'type3',
type3: 'type1',
};
return typeMap[type];
}