mirror of
https://github.com/toeverything/AFFiNE.git
synced 2026-02-14 21:27:20 +00:00
refactor(editor): use lodash (#10657)
This commit is contained in:
@@ -4,9 +4,10 @@ import {
|
||||
getCommonBoundWithRotation,
|
||||
type IBound,
|
||||
} from '@blocksuite/global/gfx';
|
||||
import { assertType, DisposableGroup, last } from '@blocksuite/global/utils';
|
||||
import { assertType, DisposableGroup } from '@blocksuite/global/utils';
|
||||
import type { BlockModel } from '@blocksuite/store';
|
||||
import { Signal } from '@preact/signals-core';
|
||||
import last from 'lodash-es/last';
|
||||
|
||||
import { LifeCycleWatcher } from '../extension/lifecycle-watcher.js';
|
||||
import type { BlockStdScope } from '../scope/block-std-scope.js';
|
||||
|
||||
@@ -1,12 +1,8 @@
|
||||
import { Bound } from '@blocksuite/global/gfx';
|
||||
import {
|
||||
assertType,
|
||||
DisposableGroup,
|
||||
last,
|
||||
Slot,
|
||||
} from '@blocksuite/global/utils';
|
||||
import { assertType, DisposableGroup, Slot } from '@blocksuite/global/utils';
|
||||
import type { Store } from '@blocksuite/store';
|
||||
import { generateKeyBetween } from 'fractional-indexing';
|
||||
import last from 'lodash-es/last';
|
||||
|
||||
import {
|
||||
compare,
|
||||
@@ -162,7 +158,9 @@ export class LayerManager {
|
||||
if (curLayer) {
|
||||
curLayer.indexes = [
|
||||
getElementIndex(curLayer.elements[0]),
|
||||
getElementIndex(last(curLayer.elements)!),
|
||||
getElementIndex(
|
||||
last(curLayer.elements as GfxPrimitiveElementModel[])!
|
||||
),
|
||||
];
|
||||
curLayer.zIndex = currentCSSZindex;
|
||||
layers.push(curLayer as LayerManager['layers'][number]);
|
||||
@@ -342,7 +340,10 @@ export class LayerManager {
|
||||
if (
|
||||
!last(this.layers) ||
|
||||
[SortOrder.AFTER, SortOrder.SAME].includes(
|
||||
compare(target, last(last(this.layers)!.elements)!)
|
||||
compare(
|
||||
target,
|
||||
last(last(this.layers)!.elements as GfxPrimitiveElementModel[])!
|
||||
)
|
||||
)
|
||||
) {
|
||||
const layer = last(this.layers);
|
||||
@@ -364,7 +365,15 @@ export class LayerManager {
|
||||
const layer = layers[cur];
|
||||
const layerElements = layer.elements;
|
||||
|
||||
if (isInRange([layerElements[0], last(layerElements)!], target)) {
|
||||
if (
|
||||
isInRange(
|
||||
[
|
||||
layerElements[0],
|
||||
last(layerElements as GfxPrimitiveElementModel[])!,
|
||||
],
|
||||
target
|
||||
)
|
||||
) {
|
||||
const insertIdx = layerElements.findIndex((_, idx) => {
|
||||
const pre = layerElements[idx - 1];
|
||||
return (
|
||||
@@ -392,7 +401,13 @@ export class LayerManager {
|
||||
} else {
|
||||
const nextLayer = layers[cur - 1];
|
||||
|
||||
if (!nextLayer || compare(target, last(nextLayer.elements)!) >= 0) {
|
||||
if (
|
||||
!nextLayer ||
|
||||
compare(
|
||||
target,
|
||||
last(nextLayer.elements as GfxPrimitiveElementModel[])!
|
||||
) >= 0
|
||||
) {
|
||||
if (layer.type === type) {
|
||||
addToLayer(layer, target, 0);
|
||||
updateLayersZIndex(layers, cur);
|
||||
|
||||
@@ -13,8 +13,9 @@ import {
|
||||
type SerializedXYWH,
|
||||
type XYWH,
|
||||
} from '@blocksuite/global/gfx';
|
||||
import { DisposableGroup, isEqual, Slot } from '@blocksuite/global/utils';
|
||||
import { DisposableGroup, Slot } from '@blocksuite/global/utils';
|
||||
import { createMutex } from 'lib0/mutex';
|
||||
import isEqual from 'lodash-es/isEqual';
|
||||
import * as Y from 'yjs';
|
||||
|
||||
import {
|
||||
|
||||
@@ -2,12 +2,8 @@ import {
|
||||
getCommonBoundWithRotation,
|
||||
type IPoint,
|
||||
} from '@blocksuite/global/gfx';
|
||||
import {
|
||||
assertType,
|
||||
DisposableGroup,
|
||||
groupBy,
|
||||
Slot,
|
||||
} from '@blocksuite/global/utils';
|
||||
import { assertType, DisposableGroup, Slot } from '@blocksuite/global/utils';
|
||||
import groupBy from 'lodash-es/groupBy';
|
||||
|
||||
import {
|
||||
BlockSelection,
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import { nToLast } from '@blocksuite/global/utils';
|
||||
import type { Store } from '@blocksuite/store';
|
||||
|
||||
import type { GfxLocalElementModel } from '../gfx/index.js';
|
||||
@@ -113,17 +112,17 @@ export function compare(
|
||||
| GfxModel
|
||||
| GfxGroupCompatibleInterface
|
||||
| GfxLocalElementModel
|
||||
| undefined = nToLast(aGroups, i);
|
||||
| undefined = aGroups.at(-i);
|
||||
let bGroup:
|
||||
| GfxModel
|
||||
| GfxGroupCompatibleInterface
|
||||
| GfxLocalElementModel
|
||||
| undefined = nToLast(bGroups, i);
|
||||
| undefined = bGroups.at(-i);
|
||||
|
||||
while (aGroup === bGroup && aGroup) {
|
||||
++i;
|
||||
aGroup = nToLast(aGroups, i);
|
||||
bGroup = nToLast(bGroups, i);
|
||||
aGroup = aGroups.at(-i);
|
||||
bGroup = bGroups.at(-i);
|
||||
}
|
||||
|
||||
aGroup = aGroup ?? a;
|
||||
|
||||
@@ -1,69 +0,0 @@
|
||||
import { describe, expect, test } from 'vitest';
|
||||
|
||||
import { isEqual } from '../utils/index.js';
|
||||
|
||||
describe('isEqual', () => {
|
||||
test('number', () => {
|
||||
expect(isEqual(1, 1)).toBe(true);
|
||||
expect(isEqual(1, 114514)).toBe(false);
|
||||
expect(isEqual(NaN, NaN)).toBe(true);
|
||||
expect(isEqual(0, -0)).toBe(false);
|
||||
});
|
||||
|
||||
test('string', () => {
|
||||
expect(isEqual('', '')).toBe(true);
|
||||
expect(isEqual('', ' ')).toBe(false);
|
||||
});
|
||||
|
||||
test('array', () => {
|
||||
expect(isEqual([], [])).toBe(true);
|
||||
expect(isEqual([1, 1, 4, 5, 1, 4], [])).toBe(false);
|
||||
expect(isEqual([1, 1, 4, 5, 1, 4], [1, 1, 4, 5, 1, 4])).toBe(true);
|
||||
});
|
||||
|
||||
test('object', () => {
|
||||
expect(isEqual({}, {})).toBe(true);
|
||||
expect(
|
||||
isEqual(
|
||||
{
|
||||
f: 1,
|
||||
g: {
|
||||
o: '',
|
||||
},
|
||||
},
|
||||
{
|
||||
f: 1,
|
||||
g: {
|
||||
o: '',
|
||||
},
|
||||
}
|
||||
)
|
||||
).toBe(true);
|
||||
expect(isEqual({}, { foo: 1 })).toBe(false);
|
||||
// @ts-expect-error ignore
|
||||
expect(isEqual({ foo: 1 }, {})).toBe(false);
|
||||
});
|
||||
|
||||
test('nested', () => {
|
||||
const nested = {
|
||||
string: 'this is a string',
|
||||
integer: 42,
|
||||
array: [19, 19, 810, 'test', NaN],
|
||||
nestedArray: [
|
||||
[1, 2],
|
||||
[3, 4],
|
||||
],
|
||||
float: 114.514,
|
||||
undefined,
|
||||
object: {
|
||||
'first-child': true,
|
||||
'second-child': false,
|
||||
'last-child': null,
|
||||
},
|
||||
bigint: 110101195306153019n,
|
||||
};
|
||||
expect(isEqual(nested, nested)).toBe(true);
|
||||
// @ts-expect-error ignore
|
||||
expect(isEqual({ foo: [] }, { foo: '' })).toBe(false);
|
||||
});
|
||||
});
|
||||
@@ -1,8 +1,6 @@
|
||||
export * from './crypto.js';
|
||||
export * from './disposable.js';
|
||||
export * from './function.js';
|
||||
export * from './is-equal.js';
|
||||
export * from './iterable.js';
|
||||
export * from './logger.js';
|
||||
export * from './signal-watcher.js';
|
||||
export * from './slot.js';
|
||||
|
||||
@@ -1,54 +0,0 @@
|
||||
// https://stackoverflow.com/questions/31538010/test-if-a-variable-is-a-primitive-rather-than-an-object
|
||||
export function isPrimitive(
|
||||
a: unknown
|
||||
): a is null | undefined | boolean | number | string {
|
||||
return a !== Object(a);
|
||||
}
|
||||
|
||||
export function assertType<T>(_: unknown): asserts _ is T {}
|
||||
|
||||
export type Equals<X, Y> =
|
||||
///
|
||||
(<T>() => T extends X ? 1 : 2) extends <T>() => T extends Y ? 1 : 2
|
||||
? true
|
||||
: false;
|
||||
|
||||
type Allowed =
|
||||
| unknown
|
||||
| void
|
||||
| null
|
||||
| undefined
|
||||
| boolean
|
||||
| number
|
||||
| string
|
||||
| unknown[]
|
||||
| object;
|
||||
export function isEqual<T extends Allowed, U extends T>(
|
||||
val: T,
|
||||
expected: U
|
||||
): Equals<T, U> {
|
||||
const a = isPrimitive(val);
|
||||
const b = isPrimitive(expected);
|
||||
if (a && b) {
|
||||
if (!Object.is(val, expected)) {
|
||||
return false as Equals<T, U>;
|
||||
}
|
||||
} else if (a !== b) {
|
||||
return false as Equals<T, U>;
|
||||
} else {
|
||||
if (Array.isArray(val) && Array.isArray(expected)) {
|
||||
if (val.length !== expected.length) {
|
||||
return false as Equals<T, U>;
|
||||
}
|
||||
return val.every((x, i) => isEqual(x, expected[i])) as Equals<T, U>;
|
||||
} else if (typeof val === 'object' && typeof expected === 'object') {
|
||||
const obj1 = Object.entries(val as Record<string, unknown>);
|
||||
const obj2 = Object.entries(expected as Record<string, unknown>);
|
||||
if (obj1.length !== obj2.length) {
|
||||
return false as Equals<T, U>;
|
||||
}
|
||||
return obj1.every((x, i) => isEqual(x, obj2[i])) as Equals<T, U>;
|
||||
}
|
||||
}
|
||||
return true as Equals<T, U>;
|
||||
}
|
||||
@@ -1,218 +0,0 @@
|
||||
/**
|
||||
*
|
||||
* @example
|
||||
* ```ts
|
||||
* const items = [
|
||||
* {name: 'a', classroom: 'c1'},
|
||||
* {name: 'b', classroom: 'c2'},
|
||||
* {name: 'a', classroom: 't0'}
|
||||
* ]
|
||||
* const counted = countBy(items1, i => i.name);
|
||||
* // counted: { a: 2, b: 1}
|
||||
* ```
|
||||
*/
|
||||
export function countBy<T>(
|
||||
items: T[],
|
||||
key: (item: T) => string | number | null
|
||||
): Record<string, number> {
|
||||
const count: Record<string, number> = {};
|
||||
items.forEach(item => {
|
||||
const k = key(item);
|
||||
if (k === null) return;
|
||||
if (!count[k]) {
|
||||
count[k] = 0;
|
||||
}
|
||||
count[k] += 1;
|
||||
});
|
||||
return count;
|
||||
}
|
||||
|
||||
/**
|
||||
* @example
|
||||
* ```ts
|
||||
* const items = [{n: 1}, {n: 2}]
|
||||
* const max = maxBy(items, i => i.n);
|
||||
* // max: {n: 2}
|
||||
* ```
|
||||
*/
|
||||
export function maxBy<T>(items: T[], value: (item: T) => number): T | null {
|
||||
if (!items.length) {
|
||||
return null;
|
||||
}
|
||||
let maxItem = items[0];
|
||||
let max = value(maxItem);
|
||||
|
||||
for (let i = 1; i < items.length; i++) {
|
||||
const item = items[i];
|
||||
const v = value(item);
|
||||
if (v > max) {
|
||||
max = v;
|
||||
maxItem = item;
|
||||
}
|
||||
}
|
||||
|
||||
return maxItem;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if there are at least `n` elements in the array that match the given condition.
|
||||
*
|
||||
* @param arr - The input array of elements.
|
||||
* @param matchFn - A function that takes an element of the array and returns a boolean value
|
||||
* indicating if the element matches the desired condition.
|
||||
* @param n - The minimum number of matching elements required.
|
||||
* @returns A boolean value indicating if there are at least `n` matching elements in the array.
|
||||
*
|
||||
* @example
|
||||
* const arr = [1, 2, 3, 4, 5];
|
||||
* const isEven = (num: number): boolean => num % 2 === 0;
|
||||
* console.log(atLeastNMatches(arr, isEven, 2)); // Output: true
|
||||
*/
|
||||
export function atLeastNMatches<T>(
|
||||
arr: T[],
|
||||
matchFn: (element: T) => boolean,
|
||||
n: number
|
||||
): boolean {
|
||||
let count = 0;
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/prefer-for-of
|
||||
for (let i = 0; i < arr.length; i++) {
|
||||
if (matchFn(arr[i])) {
|
||||
count++;
|
||||
|
||||
if (count >= n) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Groups an array of elements based on a provided key function.
|
||||
*
|
||||
* @example
|
||||
* interface Student {
|
||||
* name: string;
|
||||
* age: number;
|
||||
* }
|
||||
* const students: Student[] = [
|
||||
* { name: 'Alice', age: 25 },
|
||||
* { name: 'Bob', age: 23 },
|
||||
* { name: 'Cathy', age: 25 },
|
||||
* ];
|
||||
* const groupedByAge = groupBy(students, (student) => student.age.toString());
|
||||
* console.log(groupedByAge);
|
||||
* // Output: {
|
||||
* '23': [ { name: 'Bob', age: 23 } ],
|
||||
* '25': [ { name: 'Alice', age: 25 }, { name: 'Cathy', age: 25 } ]
|
||||
* }
|
||||
*/
|
||||
export function groupBy<T, K extends string>(
|
||||
arr: T[],
|
||||
key: K | ((item: T) => K)
|
||||
): Record<K, T[]> {
|
||||
const result = {} as Record<string, T[]>;
|
||||
|
||||
for (const item of arr) {
|
||||
const groupKey = (
|
||||
typeof key === 'function' ? key(item) : (item as any)[key]
|
||||
) as string;
|
||||
|
||||
if (!result[groupKey]) {
|
||||
result[groupKey] = [];
|
||||
}
|
||||
|
||||
result[groupKey].push(item);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
export function pickArray<T>(target: Array<T>, keys: number[]): Array<T> {
|
||||
return keys.reduce((pre, key) => {
|
||||
pre.push(target[key]);
|
||||
return pre;
|
||||
}, [] as T[]);
|
||||
}
|
||||
|
||||
export function pick<T, K extends keyof T>(
|
||||
target: T,
|
||||
keys: K[]
|
||||
): Record<K, T[K]> {
|
||||
return keys.reduce(
|
||||
(pre, key) => {
|
||||
pre[key] = target[key];
|
||||
return pre;
|
||||
},
|
||||
{} as Record<K, T[K]>
|
||||
);
|
||||
}
|
||||
|
||||
export function pickValues<T, K extends keyof T>(
|
||||
target: T,
|
||||
keys: K[]
|
||||
): Array<T[K]> {
|
||||
return keys.reduce(
|
||||
(pre, key) => {
|
||||
pre.push(target[key]);
|
||||
return pre;
|
||||
},
|
||||
[] as Array<T[K]>
|
||||
);
|
||||
}
|
||||
|
||||
export function lastN<T>(target: Array<T>, n: number) {
|
||||
return target.slice(target.length - n, target.length);
|
||||
}
|
||||
|
||||
export function isEmpty(obj: unknown) {
|
||||
if (Object.getPrototypeOf(obj) === Object.prototype) {
|
||||
return Object.keys(obj as object).length === 0;
|
||||
}
|
||||
|
||||
if (Array.isArray(obj) || typeof obj === 'string') {
|
||||
return (obj as Array<unknown>).length === 0;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
export function keys<T>(obj: T): (keyof T)[] {
|
||||
return Object.keys(obj as object) as (keyof T)[];
|
||||
}
|
||||
|
||||
export function values<T>(obj: T): T[keyof T][] {
|
||||
return Object.values(obj as object);
|
||||
}
|
||||
|
||||
type IterableType<T> = T extends Array<infer U> ? U : T;
|
||||
|
||||
export function last<T extends Iterable<unknown>>(
|
||||
iterable: T
|
||||
): IterableType<T> | undefined {
|
||||
if (Array.isArray(iterable)) {
|
||||
return iterable[iterable.length - 1];
|
||||
}
|
||||
|
||||
let last: unknown | undefined;
|
||||
for (const item of iterable) {
|
||||
last = item;
|
||||
}
|
||||
|
||||
return last as IterableType<T>;
|
||||
}
|
||||
|
||||
export function nToLast<T extends Iterable<unknown>>(
|
||||
iterable: T,
|
||||
n: number
|
||||
): IterableType<T> | undefined {
|
||||
if (Array.isArray(iterable)) {
|
||||
return iterable[iterable.length - n];
|
||||
}
|
||||
|
||||
const arr = [...iterable];
|
||||
|
||||
return arr[arr.length - n] as IterableType<T>;
|
||||
}
|
||||
@@ -10,3 +10,5 @@ export type DeepPartial<T> = {
|
||||
: DeepPartial<T[P]>
|
||||
: T[P];
|
||||
};
|
||||
|
||||
export function assertType<T>(_: unknown): asserts _ is T {}
|
||||
|
||||
@@ -13,8 +13,10 @@
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@blocksuite/global": "workspace:*",
|
||||
"@types/lodash-es": "^4.17.12",
|
||||
"idb": "^8.0.0",
|
||||
"idb-keyval": "^6.2.1",
|
||||
"lodash-es": "^4.17.21",
|
||||
"y-protocols": "^1.0.6"
|
||||
},
|
||||
"devDependencies": {
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { isEqual, type Logger, Slot } from '@blocksuite/global/utils';
|
||||
import { type Logger, Slot } from '@blocksuite/global/utils';
|
||||
import isEqual from 'lodash-es/isEqual';
|
||||
import type { Doc } from 'yjs';
|
||||
import {
|
||||
applyUpdate,
|
||||
|
||||
Reference in New Issue
Block a user