mirror of
https://github.com/toeverything/AFFiNE.git
synced 2026-08-06 03:49:56 +08:00
chore: merge blocksuite source code (#9213)
This commit is contained in:
@@ -0,0 +1,74 @@
|
||||
import {
|
||||
getNextContentBlock,
|
||||
matchFlavours,
|
||||
} from '@blocksuite/affine-shared/utils';
|
||||
import type { BlockStdScope } from '@blocksuite/block-std';
|
||||
import type { Text } from '@blocksuite/store';
|
||||
|
||||
// When deleting at line end of a list block,
|
||||
// check current block's children and siblings
|
||||
/**
|
||||
* Example:
|
||||
- Line1 <-(cursor here)
|
||||
- Line2
|
||||
- Line3
|
||||
- Line4
|
||||
- Line5
|
||||
- Line6
|
||||
- Line7
|
||||
- Line8
|
||||
- Line9
|
||||
*/
|
||||
export function forwardDelete(std: BlockStdScope): true | undefined {
|
||||
const text = std.selection.find('text');
|
||||
if (!text) return;
|
||||
const isCollapsed = text.isCollapsed();
|
||||
const doc = std.doc;
|
||||
const model = doc.getBlock(text.from.blockId)?.model;
|
||||
if (!model || !matchFlavours(model, ['affine:list'])) return;
|
||||
const isEnd = isCollapsed && text.from.index === model.text.length;
|
||||
if (!isEnd) return;
|
||||
// Has children in list
|
||||
const firstChild = model.firstChild();
|
||||
if (firstChild) {
|
||||
model.text.join(firstChild.text as Text);
|
||||
const grandChildren = firstChild.children;
|
||||
if (grandChildren) {
|
||||
doc.moveBlocks(grandChildren, model);
|
||||
doc.deleteBlock(firstChild);
|
||||
return true;
|
||||
}
|
||||
|
||||
doc.deleteBlock(firstChild);
|
||||
return true;
|
||||
}
|
||||
|
||||
const parent = doc.getParent(model);
|
||||
// Has text sibling
|
||||
const nextSibling = doc.getNext(model);
|
||||
const nextText = nextSibling?.text;
|
||||
if (nextSibling && nextText) {
|
||||
model.text.join(nextText);
|
||||
if (nextSibling.children) {
|
||||
if (!parent) return;
|
||||
doc.moveBlocks(nextSibling.children, parent, model, false);
|
||||
}
|
||||
|
||||
doc.deleteBlock(nextSibling);
|
||||
return true;
|
||||
}
|
||||
|
||||
// Has next text block in other note block
|
||||
const nextBlock = getNextContentBlock(std.host, model);
|
||||
const nextBlockText = nextBlock?.text;
|
||||
if (nextBlock && nextBlockText) {
|
||||
model.text.join(nextBlock.text as Text);
|
||||
if (nextBlock.children) {
|
||||
const nextBlockParent = doc.getParent(nextBlock);
|
||||
if (!nextBlockParent) return;
|
||||
doc.moveBlocks(nextBlock.children, nextBlockParent, parent, false);
|
||||
}
|
||||
doc.deleteBlock(nextBlock);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
@@ -0,0 +1,66 @@
|
||||
import {
|
||||
BulletIcons,
|
||||
checkboxChecked,
|
||||
checkboxUnchecked,
|
||||
toggleDown,
|
||||
toggleRight,
|
||||
} from '@blocksuite/affine-components/icons';
|
||||
import type { ListBlockModel } from '@blocksuite/affine-model';
|
||||
import { html } from 'lit';
|
||||
|
||||
import { getNumberPrefix } from './get-number-prefix.js';
|
||||
|
||||
const getListDeep = (model: ListBlockModel): number => {
|
||||
let deep = 0;
|
||||
let parent = model.doc.getParent(model);
|
||||
while (parent?.flavour === model.flavour) {
|
||||
deep++;
|
||||
parent = model.doc.getParent(parent);
|
||||
}
|
||||
return deep;
|
||||
};
|
||||
|
||||
export function getListIcon(
|
||||
model: ListBlockModel,
|
||||
showChildren: boolean,
|
||||
onClick: (e: MouseEvent) => void
|
||||
) {
|
||||
const deep = getListDeep(model);
|
||||
switch (model.type) {
|
||||
case 'bulleted':
|
||||
return html`<div
|
||||
contenteditable="false"
|
||||
class="affine-list-block__prefix"
|
||||
@click=${onClick}
|
||||
>
|
||||
${BulletIcons[deep % BulletIcons.length]}
|
||||
</div>`;
|
||||
case 'numbered':
|
||||
return html`<div
|
||||
contenteditable="false"
|
||||
class="affine-list-block__prefix affine-list-block__numbered"
|
||||
@click=${onClick}
|
||||
>
|
||||
${model.order ? getNumberPrefix(model.order, deep) : '1.'}
|
||||
</div>`;
|
||||
case 'todo':
|
||||
return html`<div
|
||||
contenteditable="false"
|
||||
class=${`affine-list-block__prefix affine-list-block__todo-prefix ${model.doc.readonly ? 'readonly' : ''}`}
|
||||
@click=${onClick}
|
||||
>
|
||||
${model.checked ? checkboxChecked() : checkboxUnchecked()}
|
||||
</div>`;
|
||||
case 'toggle':
|
||||
return html`<div
|
||||
contenteditable="false"
|
||||
class="affine-list-block__prefix"
|
||||
@click=${onClick}
|
||||
>
|
||||
${showChildren ? toggleDown : toggleRight}
|
||||
</div>`;
|
||||
default:
|
||||
console.error('Unknown list type', model.type, model);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
function number2letter(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;
|
||||
}
|
||||
|
||||
// Derive from https://gist.github.com/imilu/00f32c61e50b7ca296f91e9d96d8e976
|
||||
export function number2roman(num: number) {
|
||||
const lookup: Record<string, number> = {
|
||||
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]) {
|
||||
romanStr += i;
|
||||
num -= lookup[i];
|
||||
}
|
||||
}
|
||||
return romanStr;
|
||||
}
|
||||
|
||||
function getPrefix(depth: number, index: number) {
|
||||
const map = [
|
||||
() => index,
|
||||
() => number2letter(index - 1),
|
||||
() => number2roman(index),
|
||||
];
|
||||
return map[depth % map.length]();
|
||||
}
|
||||
|
||||
export function getNumberPrefix(index: number, depth: number) {
|
||||
const prefix = getPrefix(depth, index);
|
||||
return `${prefix}.`;
|
||||
}
|
||||
Reference in New Issue
Block a user