refactor(editor): rename block-std to std (#11250)

Closes: BS-2946
This commit is contained in:
Saul-Mirone
2025-03-28 07:20:34 +00:00
parent 4498676a96
commit 205cd7a86d
1029 changed files with 1580 additions and 1698 deletions
@@ -0,0 +1,66 @@
import { generateKeyBetween } from 'fractional-indexing';
function hasSamePrefix(a: string, b: string) {
return a.startsWith(b) || b.startsWith(a);
}
/**
* generate a key between a and b, the result key is always satisfied with a < result < b.
* the key always has a random suffix, so there is no need to worry about collision.
*
* make sure a and b are generated by this function.
*
* @param customPostfix custom postfix for the key, only letters and numbers are allowed
*/
export function generateKeyBetweenV2(a: string | null, b: string | null) {
const randomSize = 32;
function postfix(length: number = randomSize) {
const chars =
'123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz';
const values = new Uint8Array(length);
crypto.getRandomValues(values);
let result = '';
for (let i = 0; i < length; i++) {
result += chars.charAt(values[i] % chars.length);
}
return result;
}
if (a !== null && b !== null && a >= b) {
throw new Error('a should be smaller than b');
}
// get the subkey in full key
// e.g.
// a0xxxx -> a
// a0x0xxxx -> a0x
function subkey(key: string | null) {
if (key === null) {
return null;
}
if (key.length <= randomSize + 1) {
// no subkey
return key;
}
const splitAt = key.substring(0, key.length - randomSize - 1);
return splitAt;
}
const aSubkey = subkey(a);
const bSubkey = subkey(b);
if (aSubkey === null && bSubkey === null) {
// generate a new key
return generateKeyBetween(null, null) + '0' + postfix();
} else if (aSubkey === null && bSubkey !== null) {
// generate a key before b
return generateKeyBetween(null, bSubkey) + '0' + postfix();
} else if (bSubkey === null && aSubkey !== null) {
// generate a key after a
return generateKeyBetween(aSubkey, null) + '0' + postfix();
} else if (aSubkey !== null && bSubkey !== null) {
// generate a key between a and b
if (hasSamePrefix(aSubkey, bSubkey) && a !== null && b !== null) {
// conflict, if the subkeys are the same, generate a key between fullkeys
return generateKeyBetween(a, b) + '0' + postfix();
} else {
return generateKeyBetween(aSubkey, bSubkey) + '0' + postfix();
}
}
throw new Error('Never reach here');
}
+32
View File
@@ -0,0 +1,32 @@
import type { Store } from '@blocksuite/store';
import { effect } from '@preact/signals-core';
import { SurfaceBlockModel } from '../gfx/model/surface/surface-model.js';
export function onSurfaceAdded(
doc: Store,
callback: (model: SurfaceBlockModel | null) => void
) {
let found = false;
let foundId = '';
const dispose = effect(() => {
// if the surface is already found, no need to search again
if (found && doc.getBlock(foundId)) {
return;
}
for (const block of Object.values(doc.blocks.value)) {
if (block.model instanceof SurfaceBlockModel) {
callback(block.model);
found = true;
foundId = block.id;
return;
}
}
callback(null);
});
return dispose;
}
+137
View File
@@ -0,0 +1,137 @@
import type { Store } from '@blocksuite/store';
import type { GfxLocalElementModel } from '../gfx/index.js';
import type { Layer } from '../gfx/layer.js';
import {
type GfxGroupCompatibleInterface,
isGfxGroupCompatibleModel,
} from '../gfx/model/base.js';
import type { GfxBlockElementModel } from '../gfx/model/gfx-block-model.js';
import type { GfxModel } from '../gfx/model/model.js';
import type { SurfaceBlockModel } from '../gfx/model/surface/surface-model.js';
export function getLayerEndZIndex(layers: Layer[], layerIndex: number) {
const layer = layers[layerIndex];
return layer
? layer.type === 'block'
? layer.zIndex + layer.elements.length - 1
: layer.zIndex
: 0;
}
export function updateLayersZIndex(layers: Layer[], startIdx: number) {
const startLayer = layers[startIdx];
let curIndex = startLayer.zIndex;
for (let i = startIdx; i < layers.length; ++i) {
const curLayer = layers[i];
curLayer.zIndex = curIndex;
curIndex += curLayer.type === 'block' ? curLayer.elements.length : 1;
}
}
export function getElementIndex(indexable: GfxModel) {
const groups = indexable.groups as GfxGroupCompatibleInterface[];
if (groups.length) {
const groupIndexes = groups
.map(group => group.index)
.reverse()
.join('-');
return `${groupIndexes}-${indexable.index}`;
}
return indexable.index;
}
export function ungroupIndex(index: string) {
return index.split('-')[0];
}
export function insertToOrderedArray(array: GfxModel[], element: GfxModel) {
let idx = 0;
while (
idx < array.length &&
[SortOrder.BEFORE, SortOrder.SAME].includes(compare(array[idx], element))
) {
++idx;
}
array.splice(idx, 0, element);
}
export function removeFromOrderedArray(array: GfxModel[], element: GfxModel) {
const idx = array.indexOf(element);
if (idx !== -1) {
array.splice(idx, 1);
}
}
export enum SortOrder {
AFTER = 1,
BEFORE = -1,
SAME = 0,
}
export function isInRange(edges: [GfxModel, GfxModel], target: GfxModel) {
return compare(target, edges[0]) >= 0 && compare(target, edges[1]) < 0;
}
export function renderableInEdgeless(
doc: Store,
surface: SurfaceBlockModel,
block: GfxBlockElementModel
) {
const parent = doc.getParent(block);
return parent === doc.root || parent === surface;
}
/**
* A comparator function for sorting elements in the surface.
* SortOrder.AFTER means a should be rendered after b and so on.
* @returns
*/
export function compare(
a: GfxModel | GfxLocalElementModel,
b: GfxModel | GfxLocalElementModel
) {
if (isGfxGroupCompatibleModel(a) && b.groups.includes(a)) {
return SortOrder.BEFORE;
} else if (isGfxGroupCompatibleModel(b) && a.groups.includes(b)) {
return SortOrder.AFTER;
} else {
const aGroups = a.groups as GfxGroupCompatibleInterface[];
const bGroups = b.groups as GfxGroupCompatibleInterface[];
let i = 1;
let aGroup:
| GfxModel
| GfxGroupCompatibleInterface
| GfxLocalElementModel
| undefined = aGroups.at(-i);
let bGroup:
| GfxModel
| GfxGroupCompatibleInterface
| GfxLocalElementModel
| undefined = bGroups.at(-i);
while (aGroup === bGroup && aGroup) {
++i;
aGroup = aGroups.at(-i);
bGroup = bGroups.at(-i);
}
aGroup = aGroup ?? a;
bGroup = bGroup ?? b;
return aGroup.index === bGroup.index
? SortOrder.SAME
: aGroup.index < bGroup.index
? SortOrder.BEFORE
: SortOrder.AFTER;
}
}
+137
View File
@@ -0,0 +1,137 @@
import type { Store } from '@blocksuite/store';
import {
type GfxCompatibleInterface,
type GfxGroupCompatibleInterface,
isGfxGroupCompatibleModel,
} from '../gfx/model/base.js';
import type { GfxGroupModel, GfxModel } from '../gfx/model/model.js';
/**
* Get the top elements from the list of elements, which are in some tree structures.
*
* For example: a list `[G1, E1, G2, E2, E3, E4, G4, E5, E6]`,
* and they are in the elements tree like:
* ```
* G1 G4 E6
* / \ |
* E1 G2 E5
* / \
* E2 G3*
* / \
* E3 E4
* ```
* where the star symbol `*` denote it is not in the list.
*
* The result should be `[G1, G4, E6]`
*/
export function getTopElements(elements: GfxModel[]): GfxModel[] {
const results = new Set(elements);
elements = [...new Set(elements)];
elements.forEach(e1 => {
elements.forEach(e2 => {
if (isGfxGroupCompatibleModel(e1) && e1.hasDescendant(e2)) {
results.delete(e2);
}
});
});
return [...results];
}
function traverse(
element: GfxModel,
preCallback?: (element: GfxModel) => void | boolean,
postCallBack?: (element: GfxModel) => void
) {
// avoid infinite loop caused by circular reference
const visited = new Set<GfxModel>();
const innerTraverse = (element: GfxModel) => {
if (visited.has(element)) return;
visited.add(element);
if (preCallback) {
const interrupt = preCallback(element);
if (interrupt) return;
}
if (isGfxGroupCompatibleModel(element)) {
element.childElements.forEach(child => {
innerTraverse(child);
});
}
postCallBack && postCallBack(element);
};
innerTraverse(element);
}
export function descendantElementsImpl(
container: GfxGroupCompatibleInterface
): GfxModel[] {
const results: GfxModel[] = [];
container.childElements.forEach(child => {
traverse(child, element => {
results.push(element);
});
});
return results;
}
export function hasDescendantElementImpl(
container: GfxGroupCompatibleInterface,
element: GfxCompatibleInterface
): boolean {
let _container = element.group;
while (_container) {
if (_container === container) return true;
_container = _container.group;
}
return false;
}
/**
* This checker is used to prevent circular reference, when adding a child element to a container.
*/
export function canSafeAddToContainer(
container: GfxGroupModel,
element: GfxCompatibleInterface
) {
if (
element === container ||
(isGfxGroupCompatibleModel(element) && element.hasDescendant(container))
) {
return false;
}
return true;
}
export function isLockedByAncestorImpl(
element: GfxCompatibleInterface
): boolean {
return element.groups.some(isLockedBySelfImpl);
}
export function isLockedBySelfImpl(element: GfxCompatibleInterface): boolean {
return element.lockedBySelf ?? false;
}
export function isLockedImpl(element: GfxCompatibleInterface): boolean {
return isLockedBySelfImpl(element) || isLockedByAncestorImpl(element);
}
export function lockElementImpl(doc: Store, element: GfxCompatibleInterface) {
doc.transact(() => {
element.lockedBySelf = true;
});
}
export function unlockElementImpl(doc: Store, element: GfxCompatibleInterface) {
doc.transact(() => {
element.lockedBySelf = false;
});
}