diff --git a/blocksuite/affine/block-root/src/edgeless/clipboard/clipboard.ts b/blocksuite/affine/block-root/src/edgeless/clipboard/clipboard.ts index 59cfc7bbf6..b80746ac90 100644 --- a/blocksuite/affine/block-root/src/edgeless/clipboard/clipboard.ts +++ b/blocksuite/affine/block-root/src/edgeless/clipboard/clipboard.ts @@ -57,7 +57,7 @@ import { type SerializedXYWH, Vec, } from '@blocksuite/global/gfx'; -import { assertType, DisposableGroup, nToLast } from '@blocksuite/global/utils'; +import { assertType, DisposableGroup } from '@blocksuite/global/utils'; import { type BlockSnapshot, BlockSnapshotSchema, @@ -1168,13 +1168,13 @@ export class EdgelessClipboardController extends PageClipboard { const bGroups = b.groups as SurfaceGroupLikeModel[]; let i = 1; - let aGroup: GfxModel | undefined = nToLast(aGroups, i); - let bGroup: GfxModel | undefined = nToLast(bGroups, i); + let aGroup: GfxModel | undefined = aGroups.at(-i); + let bGroup: GfxModel | 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; diff --git a/blocksuite/affine/block-root/src/edgeless/components/rects/edgeless-selected-rect.ts b/blocksuite/affine/block-root/src/edgeless/components/rects/edgeless-selected-rect.ts index a931df65c0..68f92cfa24 100644 --- a/blocksuite/affine/block-root/src/edgeless/components/rects/edgeless-selected-rect.ts +++ b/blocksuite/affine/block-root/src/edgeless/components/rects/edgeless-selected-rect.ts @@ -56,7 +56,7 @@ import { normalizeDegAngle, } from '@blocksuite/global/gfx'; import type { Disposable } from '@blocksuite/global/utils'; -import { assertType, pickValues, Slot } from '@blocksuite/global/utils'; +import { assertType, Slot } from '@blocksuite/global/utils'; import { css, html, nothing } from 'lit'; import { state } from 'lit/decorators.js'; import { ifDefined } from 'lit/directives/if-defined.js'; @@ -1310,13 +1310,17 @@ export class EdgelessSelectedRectWidget extends WidgetComponent< gfx.viewport.viewportUpdated.on(this._updateOnViewportChange) ); - pickValues(gfx.surface!, [ - 'elementAdded', - 'elementRemoved', - 'elementUpdated', - ]).forEach(slot => { - _disposables.add(slot.on(this._updateOnElementChange)); - }); + if (gfx.surface) { + _disposables.add( + gfx.surface.elementAdded.on(this._updateOnElementChange) + ); + _disposables.add( + gfx.surface.elementRemoved.on(this._updateOnElementChange) + ); + _disposables.add( + gfx.surface.elementUpdated.on(this._updateOnElementChange) + ); + } _disposables.add( this.doc.slots.blockUpdated.on(this._updateOnElementChange) diff --git a/blocksuite/affine/block-root/src/edgeless/components/toolbar/template/builtin-templates.ts b/blocksuite/affine/block-root/src/edgeless/components/toolbar/template/builtin-templates.ts index 8697368d15..30f555ac03 100644 --- a/blocksuite/affine/block-root/src/edgeless/components/toolbar/template/builtin-templates.ts +++ b/blocksuite/affine/block-root/src/edgeless/components/toolbar/template/builtin-templates.ts @@ -1,5 +1,3 @@ -import { keys } from '@blocksuite/global/utils'; - import type { Template, TemplateCategory, @@ -40,7 +38,7 @@ const flat = (arr: T[][]) => }, []); export const builtInTemplates = { - list: async (category: string) => { + list: async (category: string): Promise => { const extendTemplates = flat( await Promise.all(extendTemplate.map(manager => manager.list(category))) ); @@ -52,15 +50,12 @@ export const builtInTemplates = { const result: Template[] = cate.templates instanceof Function ? await cate.templates() - : await Promise.all( - // @ts-expect-error FIXME: ts error - keys(cate.templates).map(key => cate.templates[key]()) - ); + : await Promise.all(Object.values(cate.templates)); return result.concat(extendTemplates); }, - categories: async () => { + categories: async (): Promise => { const extendCates = flat( await Promise.all(extendTemplate.map(manager => manager.categories())) ); @@ -69,7 +64,7 @@ export const builtInTemplates = { return templates.map(cate => cate.name).concat(extendCates); }, - search: async (keyword: string, cateName?: string) => { + search: async (keyword: string, cateName?: string): Promise => { const candidates: Template[] = flat( await Promise.all( extendTemplate.map(manager => manager.search(keyword, cateName)) @@ -86,18 +81,15 @@ export const builtInTemplates = { } if (categroy.templates instanceof Function) { - return; + return categroy.templates(); } return Promise.all( - keys(categroy.templates).map(async name => { + Object.entries(categroy.templates).map(async ([name, template]) => { if ( lcs(keyword, (name as string).toLocaleLowerCase()) === keyword.length ) { - // @ts-expect-error FIXME: ts error - const template = await categroy.templates[name](); - candidates.push(template); } }) diff --git a/blocksuite/affine/block-root/src/edgeless/gfx-tool/default-tool-ext/event-ext.ts b/blocksuite/affine/block-root/src/edgeless/gfx-tool/default-tool-ext/event-ext.ts index 93edd78a0a..256047108c 100644 --- a/blocksuite/affine/block-root/src/edgeless/gfx-tool/default-tool-ext/event-ext.ts +++ b/blocksuite/affine/block-root/src/edgeless/gfx-tool/default-tool-ext/event-ext.ts @@ -1,7 +1,7 @@ import type { PointerEventState } from '@blocksuite/block-std'; import type { GfxElementModelView } from '@blocksuite/block-std/gfx'; import { Bound } from '@blocksuite/global/gfx'; -import { last } from '@blocksuite/global/utils'; +import last from 'lodash-es/last'; import { DefaultModeDragType, DefaultToolExt } from './ext.js'; diff --git a/blocksuite/affine/block-root/src/edgeless/gfx-tool/default-tool-ext/mind-map-ext/drag-utils.ts b/blocksuite/affine/block-root/src/edgeless/gfx-tool/default-tool-ext/mind-map-ext/drag-utils.ts index 29e4250d45..9adbcc9768 100644 --- a/blocksuite/affine/block-root/src/edgeless/gfx-tool/default-tool-ext/mind-map-ext/drag-utils.ts +++ b/blocksuite/affine/block-root/src/edgeless/gfx-tool/default-tool-ext/mind-map-ext/drag-utils.ts @@ -9,7 +9,7 @@ import { type MindmapRoot, } from '@blocksuite/affine-model'; import { Bound } from '@blocksuite/global/gfx'; -import { last } from '@blocksuite/global/utils'; +import last from 'lodash-es/last'; const isOnEdge = (node: MindmapNode, direction: 'tail' | 'head') => { let current = node; diff --git a/blocksuite/affine/block-root/src/edgeless/gfx-tool/default-tool-ext/mind-map-ext/indicator-overlay.ts b/blocksuite/affine/block-root/src/edgeless/gfx-tool/default-tool-ext/mind-map-ext/indicator-overlay.ts index 0a97547429..b39db72974 100644 --- a/blocksuite/affine/block-root/src/edgeless/gfx-tool/default-tool-ext/mind-map-ext/indicator-overlay.ts +++ b/blocksuite/affine/block-root/src/edgeless/gfx-tool/default-tool-ext/mind-map-ext/indicator-overlay.ts @@ -19,7 +19,7 @@ import { toRadian, Vec, } from '@blocksuite/global/gfx'; -import { last } from '@blocksuite/global/utils'; +import last from 'lodash-es/last'; export class MindMapIndicatorOverlay extends Overlay { static INDICATOR_SIZE = [48, 22]; diff --git a/blocksuite/affine/block-root/src/edgeless/gfx-tool/default-tool.ts b/blocksuite/affine/block-root/src/edgeless/gfx-tool/default-tool.ts index f17720b16b..fcef083b07 100644 --- a/blocksuite/affine/block-root/src/edgeless/gfx-tool/default-tool.ts +++ b/blocksuite/affine/block-root/src/edgeless/gfx-tool/default-tool.ts @@ -43,9 +43,10 @@ import { } from '@blocksuite/block-std/gfx'; import type { IVec } from '@blocksuite/global/gfx'; import { Bound, getCommonBoundWithRotation, Vec } from '@blocksuite/global/gfx'; -import { DisposableGroup, last, noop } from '@blocksuite/global/utils'; +import { DisposableGroup, noop } from '@blocksuite/global/utils'; import { effect } from '@preact/signals-core'; import clamp from 'lodash-es/clamp'; +import last from 'lodash-es/last'; import type { EdgelessRootBlockComponent } from '../edgeless-root-block.js'; import { prepareCloneData } from '../utils/clone-utils.js'; diff --git a/blocksuite/affine/block-root/src/edgeless/utils/clipboard-utils.ts b/blocksuite/affine/block-root/src/edgeless/utils/clipboard-utils.ts index 1eab8d3fdc..19d3bbf8c9 100644 --- a/blocksuite/affine/block-root/src/edgeless/utils/clipboard-utils.ts +++ b/blocksuite/affine/block-root/src/edgeless/utils/clipboard-utils.ts @@ -19,8 +19,8 @@ import { type SerializedElement, } from '@blocksuite/block-std/gfx'; import { getCommonBoundWithRotation } from '@blocksuite/global/gfx'; -import { groupBy } from '@blocksuite/global/utils'; import { type BlockSnapshot, BlockSnapshotSchema } from '@blocksuite/store'; +import groupBy from 'lodash-es/groupBy'; import type { EdgelessRootBlockComponent } from '../edgeless-root-block.js'; import { getSortedCloneElements, prepareCloneData } from './clone-utils.js'; diff --git a/blocksuite/affine/block-root/src/widgets/element-toolbar/change-brush-button.ts b/blocksuite/affine/block-root/src/widgets/element-toolbar/change-brush-button.ts index 5a85b054d5..aa9ba1b1a8 100644 --- a/blocksuite/affine/block-root/src/widgets/element-toolbar/change-brush-button.ts +++ b/blocksuite/affine/block-root/src/widgets/element-toolbar/change-brush-button.ts @@ -19,10 +19,12 @@ import { } from '@blocksuite/affine-model'; import { FeatureFlagService } from '@blocksuite/affine-shared/services'; import type { ColorEvent } from '@blocksuite/affine-shared/utils'; -import { countBy, maxBy, WithDisposable } from '@blocksuite/global/utils'; +import { WithDisposable } from '@blocksuite/global/utils'; import { html, LitElement, nothing } from 'lit'; import { property, query } from 'lit/decorators.js'; import { when } from 'lit/directives/when.js'; +import countBy from 'lodash-es/countBy'; +import maxBy from 'lodash-es/maxBy'; import type { LineWidthEvent } from '../../edgeless/components/panel/line-width-panel.js'; import type { EdgelessRootBlockComponent } from '../../edgeless/edgeless-root-block.js'; diff --git a/blocksuite/affine/block-root/src/widgets/element-toolbar/change-connector-button.ts b/blocksuite/affine/block-root/src/widgets/element-toolbar/change-connector-button.ts index 0edd8ffaf5..9922b8d60a 100644 --- a/blocksuite/affine/block-root/src/widgets/element-toolbar/change-connector-button.ts +++ b/blocksuite/affine/block-root/src/widgets/element-toolbar/change-connector-button.ts @@ -25,7 +25,7 @@ import { } from '@blocksuite/affine-model'; import { FeatureFlagService } from '@blocksuite/affine-shared/services'; import type { ColorEvent } from '@blocksuite/affine-shared/utils'; -import { countBy, maxBy, WithDisposable } from '@blocksuite/global/utils'; +import { WithDisposable } from '@blocksuite/global/utils'; import { AddTextIcon, ConnectorCIcon, @@ -51,6 +51,8 @@ import { join } from 'lit/directives/join.js'; import { repeat } from 'lit/directives/repeat.js'; import { styleMap } from 'lit/directives/style-map.js'; import { when } from 'lit/directives/when.js'; +import countBy from 'lodash-es/countBy'; +import maxBy from 'lodash-es/maxBy'; import { type LineStyleEvent, diff --git a/blocksuite/affine/block-root/src/widgets/element-toolbar/change-frame-button.ts b/blocksuite/affine/block-root/src/widgets/element-toolbar/change-frame-button.ts index cc8d1aba1f..fa21c2b61c 100644 --- a/blocksuite/affine/block-root/src/widgets/element-toolbar/change-frame-button.ts +++ b/blocksuite/affine/block-root/src/widgets/element-toolbar/change-frame-button.ts @@ -23,12 +23,14 @@ import type { ColorEvent } from '@blocksuite/affine-shared/utils'; import { matchModels } from '@blocksuite/affine-shared/utils'; import { GfxExtensionIdentifier } from '@blocksuite/block-std/gfx'; import { deserializeXYWH, serializeXYWH } from '@blocksuite/global/gfx'; -import { countBy, maxBy, WithDisposable } from '@blocksuite/global/utils'; +import { WithDisposable } from '@blocksuite/global/utils'; import { EditIcon, PageIcon, UngroupIcon } from '@blocksuite/icons/lit'; import { html, LitElement, nothing } from 'lit'; import { property, query } from 'lit/decorators.js'; import { join } from 'lit/directives/join.js'; import { when } from 'lit/directives/when.js'; +import countBy from 'lodash-es/countBy'; +import maxBy from 'lodash-es/maxBy'; import type { EdgelessRootBlockComponent } from '../../edgeless/edgeless-root-block.js'; import { mountFrameTitleEditor } from '../../edgeless/utils/text.js'; diff --git a/blocksuite/affine/block-root/src/widgets/element-toolbar/change-mindmap-button.ts b/blocksuite/affine/block-root/src/widgets/element-toolbar/change-mindmap-button.ts index 93db2e51d6..bd2487aec6 100644 --- a/blocksuite/affine/block-root/src/widgets/element-toolbar/change-mindmap-button.ts +++ b/blocksuite/affine/block-root/src/widgets/element-toolbar/change-mindmap-button.ts @@ -11,12 +11,14 @@ import type { } from '@blocksuite/affine-model'; import { LayoutType, MindmapStyle } from '@blocksuite/affine-model'; import { EditPropsStore } from '@blocksuite/affine-shared/services'; -import { countBy, maxBy, WithDisposable } from '@blocksuite/global/utils'; +import { WithDisposable } from '@blocksuite/global/utils'; import { RadiantIcon, RightLayoutIcon, StyleIcon } from '@blocksuite/icons/lit'; import { css, html, LitElement, nothing, type TemplateResult } from 'lit'; import { property, state } from 'lit/decorators.js'; import { join } from 'lit/directives/join.js'; import { repeat } from 'lit/directives/repeat.js'; +import countBy from 'lodash-es/countBy'; +import maxBy from 'lodash-es/maxBy'; import type { EdgelessRootBlockComponent } from '../../edgeless/edgeless-root-block.js'; import { SmallArrowDownIcon } from './icons.js'; diff --git a/blocksuite/affine/block-root/src/widgets/element-toolbar/change-note-button.ts b/blocksuite/affine/block-root/src/widgets/element-toolbar/change-note-button.ts index ae7dd6bb1d..3fda891ec9 100644 --- a/blocksuite/affine/block-root/src/widgets/element-toolbar/change-note-button.ts +++ b/blocksuite/affine/block-root/src/widgets/element-toolbar/change-note-button.ts @@ -31,7 +31,7 @@ import { ThemeProvider, } from '@blocksuite/affine-shared/services'; import { Bound } from '@blocksuite/global/gfx'; -import { countBy, maxBy, WithDisposable } from '@blocksuite/global/utils'; +import { WithDisposable } from '@blocksuite/global/utils'; import { AutoHeightIcon, CornerIcon, @@ -46,6 +46,8 @@ import { property, query } from 'lit/decorators.js'; import { join } from 'lit/directives/join.js'; import { createRef, type Ref, ref } from 'lit/directives/ref.js'; import { when } from 'lit/directives/when.js'; +import countBy from 'lodash-es/countBy'; +import maxBy from 'lodash-es/maxBy'; import { type LineStyleEvent, diff --git a/blocksuite/affine/block-root/src/widgets/element-toolbar/change-shape-button.ts b/blocksuite/affine/block-root/src/widgets/element-toolbar/change-shape-button.ts index 45601eb0d1..b06ebd8857 100644 --- a/blocksuite/affine/block-root/src/widgets/element-toolbar/change-shape-button.ts +++ b/blocksuite/affine/block-root/src/widgets/element-toolbar/change-shape-button.ts @@ -29,7 +29,7 @@ import { } from '@blocksuite/affine-model'; import { FeatureFlagService } from '@blocksuite/affine-shared/services'; import type { ColorEvent } from '@blocksuite/affine-shared/utils'; -import { countBy, maxBy, WithDisposable } from '@blocksuite/global/utils'; +import { WithDisposable } from '@blocksuite/global/utils'; import { AddTextIcon, ShapeIcon, @@ -43,7 +43,9 @@ import { choose } from 'lit/directives/choose.js'; import { join } from 'lit/directives/join.js'; import { styleMap } from 'lit/directives/style-map.js'; import { when } from 'lit/directives/when.js'; -import { isEqual } from 'lodash-es'; +import countBy from 'lodash-es/countBy'; +import isEqual from 'lodash-es/isEqual'; +import maxBy from 'lodash-es/maxBy'; import { type LineStyleEvent, diff --git a/blocksuite/affine/block-root/src/widgets/element-toolbar/change-text-menu.ts b/blocksuite/affine/block-root/src/widgets/element-toolbar/change-text-menu.ts index 0333bacdd8..2906c7c381 100644 --- a/blocksuite/affine/block-root/src/widgets/element-toolbar/change-text-menu.ts +++ b/blocksuite/affine/block-root/src/widgets/element-toolbar/change-text-menu.ts @@ -32,7 +32,7 @@ import { import { FeatureFlagService } from '@blocksuite/affine-shared/services'; import type { ColorEvent } from '@blocksuite/affine-shared/utils'; import { Bound } from '@blocksuite/global/gfx'; -import { countBy, maxBy, WithDisposable } from '@blocksuite/global/utils'; +import { WithDisposable } from '@blocksuite/global/utils'; import { TextAlignCenterIcon, TextAlignLeftIcon, @@ -43,6 +43,8 @@ import { property, query } from 'lit/decorators.js'; import { choose } from 'lit/directives/choose.js'; import { join } from 'lit/directives/join.js'; import { when } from 'lit/directives/when.js'; +import countBy from 'lodash-es/countBy'; +import maxBy from 'lodash-es/maxBy'; import type { EdgelessRootBlockComponent } from '../../edgeless/edgeless-root-block.js'; import { SmallArrowDownIcon } from './icons.js'; diff --git a/blocksuite/affine/block-root/src/widgets/element-toolbar/index.ts b/blocksuite/affine/block-root/src/widgets/element-toolbar/index.ts index e9ddc6aee9..5f730ce707 100644 --- a/blocksuite/affine/block-root/src/widgets/element-toolbar/index.ts +++ b/blocksuite/affine/block-root/src/widgets/element-toolbar/index.ts @@ -31,11 +31,11 @@ import { requestConnectedFrame } from '@blocksuite/affine-shared/utils'; import { WidgetComponent } from '@blocksuite/block-std'; import type { GfxModel } from '@blocksuite/block-std/gfx'; import { clamp, getCommonBoundWithRotation } from '@blocksuite/global/gfx'; -import { atLeastNMatches, groupBy, pickValues } from '@blocksuite/global/utils'; import { ConnectorCIcon } from '@blocksuite/icons/lit'; import { css, html, nothing, type TemplateResult, unsafeCSS } from 'lit'; import { property, state } from 'lit/decorators.js'; import { join } from 'lit/directives/join.js'; +import groupBy from 'lodash-es/groupBy'; import type { EdgelessRootBlockComponent } from '../../edgeless/edgeless-root-block.js'; import { @@ -270,11 +270,8 @@ export class EdgelessElementToolbarWidget extends WidgetComponent< edgelessText, mindmap: mindmaps, } = groupedSelected; - const selectedAtLeastTwoTypes = atLeastNMatches( - Object.values(groupedSelected), - e => !!e.length, - 2 - ); + const selectedAtLeastTwoTypes = + Object.values(groupedSelected).filter(e => !!e.length).length >= 2; const quickConnectButton = selectedElements.length === 1 && !connector?.length @@ -385,10 +382,16 @@ export class EdgelessElementToolbarWidget extends WidgetComponent< }) ); - pickValues(this.edgeless.service.surface, [ - 'elementAdded', - 'elementUpdated', - ]).forEach(slot => _disposables.add(slot.on(this._updateOnSelectedChange))); + _disposables.add( + this.edgeless.service.surface.elementAdded.on( + this._updateOnSelectedChange + ) + ); + _disposables.add( + this.edgeless.service.surface.elementUpdated.on( + this._updateOnSelectedChange + ) + ); _disposables.add( this.doc.slots.blockUpdated.on(this._updateOnSelectedChange) diff --git a/blocksuite/affine/block-surface-ref/package.json b/blocksuite/affine/block-surface-ref/package.json index 9b98120490..49aaca7d17 100644 --- a/blocksuite/affine/block-surface-ref/package.json +++ b/blocksuite/affine/block-surface-ref/package.json @@ -25,15 +25,13 @@ "@lit/context": "^1.1.2", "@preact/signals-core": "^1.8.0", "@toeverything/theme": "^1.1.12", + "@types/lodash-es": "^4.17.12", "fractional-indexing": "^3.2.0", "lit": "^3.2.0", - "lodash.chunk": "^4.2.0", + "lodash-es": "^4.17.21", "nanoid": "^5.0.7", "zod": "^3.23.8" }, - "devDependencies": { - "@types/lodash.chunk": "^4.2.9" - }, "exports": { ".": "./src/index.ts", "./effects": "./src/effects.ts" diff --git a/blocksuite/affine/block-surface/package.json b/blocksuite/affine/block-surface/package.json index 16badc78af..9106ada08a 100644 --- a/blocksuite/affine/block-surface/package.json +++ b/blocksuite/affine/block-surface/package.json @@ -23,17 +23,17 @@ "@lit/context": "^1.1.2", "@preact/signals-core": "^1.8.0", "@toeverything/theme": "^1.1.12", + "@types/lodash-es": "^4.17.12", "fractional-indexing": "^3.2.0", "html2canvas": "^1.4.1", "lit": "^3.2.0", - "lodash.chunk": "^4.2.0", + "lodash-es": "^4.17.21", "nanoid": "^5.0.7", "pdf-lib": "^1.17.1", "yjs": "^13.6.21", "zod": "^3.23.8" }, "devDependencies": { - "@types/lodash.chunk": "^4.2.9", "vitest": "3.0.7" }, "exports": { diff --git a/blocksuite/affine/block-surface/src/commands/auto-align.ts b/blocksuite/affine/block-surface/src/commands/auto-align.ts index 5c76c2e0a4..22af56ca98 100644 --- a/blocksuite/affine/block-surface/src/commands/auto-align.ts +++ b/blocksuite/affine/block-surface/src/commands/auto-align.ts @@ -10,7 +10,7 @@ import { type GfxModel, } from '@blocksuite/block-std/gfx'; import { Bound } from '@blocksuite/global/gfx'; -import chunk from 'lodash.chunk'; +import chunk from 'lodash-es/chunk'; const ALIGN_HEIGHT = 200; const ALIGN_PADDING = 20; diff --git a/blocksuite/affine/block-surface/src/managers/connector-manager.ts b/blocksuite/affine/block-surface/src/managers/connector-manager.ts index 91adad08a3..6cd97ba23b 100644 --- a/blocksuite/affine/block-surface/src/managers/connector-manager.ts +++ b/blocksuite/affine/block-surface/src/managers/connector-manager.ts @@ -32,8 +32,9 @@ import { toRadian, Vec, } from '@blocksuite/global/gfx'; -import { assertType, last } from '@blocksuite/global/utils'; +import { assertType } from '@blocksuite/global/utils'; import { effect } from '@preact/signals-core'; +import last from 'lodash-es/last'; import { Overlay } from '../renderer/overlay.js'; import { AStarRunner } from '../utils/a-star.js'; @@ -602,7 +603,7 @@ function mergePath(points: IVec[] | IVec3[]) { continue; result.push([cur[0], cur[1]]); } - result.push(last(points) as IVec); + result.push(last(points as IVec[]) as IVec); for (let i = 0; i < result.length - 1; i++) { const cur = result[i]; const next = result[i + 1]; diff --git a/blocksuite/affine/block-surface/src/renderer/canvas-renderer.ts b/blocksuite/affine/block-surface/src/renderer/canvas-renderer.ts index e503c19227..1d0051a0e9 100644 --- a/blocksuite/affine/block-surface/src/renderer/canvas-renderer.ts +++ b/blocksuite/affine/block-surface/src/renderer/canvas-renderer.ts @@ -8,7 +8,8 @@ import type { } from '@blocksuite/block-std/gfx'; import type { IBound } from '@blocksuite/global/gfx'; import { getBoundWithRotation, intersects } from '@blocksuite/global/gfx'; -import { DisposableGroup, last, Slot } from '@blocksuite/global/utils'; +import { DisposableGroup, Slot } from '@blocksuite/global/utils'; +import last from 'lodash-es/last'; import type { SurfaceElementModel } from '../element-model/base.js'; import { RoughCanvas } from '../utils/rough/canvas.js'; diff --git a/blocksuite/affine/block-surface/src/utils/mindmap/utils.ts b/blocksuite/affine/block-surface/src/utils/mindmap/utils.ts index 8e6b349105..0ba73d0dd6 100644 --- a/blocksuite/affine/block-surface/src/utils/mindmap/utils.ts +++ b/blocksuite/affine/block-surface/src/utils/mindmap/utils.ts @@ -14,7 +14,9 @@ import { type SurfaceBlockModel, } from '@blocksuite/block-std/gfx'; import type { IVec } from '@blocksuite/global/gfx'; -import { assertType, isEqual, last } from '@blocksuite/global/utils'; +import { assertType } from '@blocksuite/global/utils'; +import isEqual from 'lodash-es/isEqual'; +import last from 'lodash-es/last'; import * as Y from 'yjs'; import { fitContent } from '../../renderer/elements/shape/utils.js'; diff --git a/blocksuite/affine/data-view/package.json b/blocksuite/affine/data-view/package.json index 33a4620fb3..3cb80ec634 100644 --- a/blocksuite/affine/data-view/package.json +++ b/blocksuite/affine/data-view/package.json @@ -24,8 +24,10 @@ "@lit/context": "^1.1.2", "@preact/signals-core": "^1.8.0", "@toeverything/theme": "^1.1.12", + "@types/lodash-es": "^4.17.12", "date-fns": "^4.0.0", "lit": "^3.2.0", + "lodash-es": "^4.17.21", "yjs": "^13.6.21", "zod": "^3.23.8" }, diff --git a/blocksuite/affine/data-view/src/view-presets/table/pc/controller/drag-to-fill.ts b/blocksuite/affine/data-view/src/view-presets/table/pc/controller/drag-to-fill.ts index 3cc21fffc4..fa0612104c 100644 --- a/blocksuite/affine/data-view/src/view-presets/table/pc/controller/drag-to-fill.ts +++ b/blocksuite/affine/data-view/src/view-presets/table/pc/controller/drag-to-fill.ts @@ -1,9 +1,9 @@ import { ShadowlessElement } from '@blocksuite/block-std'; -import { isEqual } from '@blocksuite/global/utils'; import { type Text } from '@blocksuite/store'; import { css, html } from 'lit'; import { state } from 'lit/decorators.js'; import { createRef, ref } from 'lit/directives/ref.js'; +import isEqual from 'lodash-es/isEqual'; import * as Y from 'yjs'; import { t } from '../../../../core/index.js'; diff --git a/blocksuite/affine/model/package.json b/blocksuite/affine/model/package.json index 7cf00da52c..3e4988a8b9 100644 --- a/blocksuite/affine/model/package.json +++ b/blocksuite/affine/model/package.json @@ -18,7 +18,9 @@ "@blocksuite/inline": "workspace:*", "@blocksuite/store": "workspace:*", "@toeverything/theme": "^1.1.12", + "@types/lodash-es": "^4.17.12", "fractional-indexing": "^3.2.0", + "lodash-es": "^4.17.21", "yjs": "^13.6.21", "zod": "^3.23.8" }, diff --git a/blocksuite/affine/model/src/elements/group/group.ts b/blocksuite/affine/model/src/elements/group/group.ts index c3bcfccc4f..5f53b11437 100644 --- a/blocksuite/affine/model/src/elements/group/group.ts +++ b/blocksuite/affine/model/src/elements/group/group.ts @@ -12,7 +12,6 @@ import { } from '@blocksuite/block-std/gfx'; import type { IVec, PointLocation } from '@blocksuite/global/gfx'; import { Bound, linePolygonIntersects } from '@blocksuite/global/gfx'; -import { keys } from '@blocksuite/global/utils'; import * as Y from 'yjs'; type GroupElementProps = BaseElementProps & { @@ -44,7 +43,7 @@ export class GroupElementModel extends GfxGroupLikeElementModel; - keys(props.children).forEach(key => { + Object.keys(props.children).forEach(key => { children.set(key as string, true); }); diff --git a/blocksuite/affine/model/src/elements/mindmap/mindmap.ts b/blocksuite/affine/model/src/elements/mindmap/mindmap.ts index 3765c4014d..5a64321164 100644 --- a/blocksuite/affine/model/src/elements/mindmap/mindmap.ts +++ b/blocksuite/affine/model/src/elements/mindmap/mindmap.ts @@ -14,8 +14,10 @@ import { } from '@blocksuite/block-std/gfx'; import type { Bound, SerializedXYWH, XYWH } from '@blocksuite/global/gfx'; import { deserializeXYWH } from '@blocksuite/global/gfx'; -import { assertType, keys, last, noop, pick } from '@blocksuite/global/utils'; +import { assertType, noop } from '@blocksuite/global/utils'; import { generateKeyBetween } from 'fractional-indexing'; +import last from 'lodash-es/last'; +import pick from 'lodash-es/pick'; import * as Y from 'yjs'; import { z } from 'zod'; @@ -182,11 +184,11 @@ export class MindmapElementModel extends GfxGroupLikeElementModel = new Y.Map(); - keys(props.children).forEach(key => { - const detail = pick, keyof NodeDetail>( - props.children![key], - ['index', 'parent'] - ); + Object.entries(props.children).forEach(([key, value]) => { + const detail = pick, keyof NodeDetail>(value, [ + 'index', + 'parent', + ]); children.set(key as string, detail as NodeDetail); }); diff --git a/blocksuite/affine/model/src/elements/mindmap/style.ts b/blocksuite/affine/model/src/elements/mindmap/style.ts index 4806bd951a..e442a72da8 100644 --- a/blocksuite/affine/model/src/elements/mindmap/style.ts +++ b/blocksuite/affine/model/src/elements/mindmap/style.ts @@ -1,4 +1,5 @@ -import { isEqual, last } from '@blocksuite/global/utils'; +import isEqual from 'lodash-es/isEqual'; +import last from 'lodash-es/last'; import { ConnectorMode } from '../../consts/connector.js'; import { MindmapStyle } from '../../consts/mindmap.js'; diff --git a/blocksuite/affine/shared/src/__tests__/utils/iterable.unit.spec.ts b/blocksuite/affine/shared/src/__tests__/utils/iterable.unit.spec.ts deleted file mode 100644 index ab5d7101ce..0000000000 --- a/blocksuite/affine/shared/src/__tests__/utils/iterable.unit.spec.ts +++ /dev/null @@ -1,86 +0,0 @@ -import { - atLeastNMatches, - countBy, - groupBy, - maxBy, -} from '@blocksuite/global/utils'; -import { describe, expect, it } from 'vitest'; - -describe('countBy', () => { - it('basic', () => { - const items = [ - { name: 'a', classroom: 'c1' }, - { name: 'b', classroom: 'c2' }, - { name: 'a', classroom: 'c2' }, - ]; - const counted = countBy(items, i => i.name); - expect(counted).toEqual({ a: 2, b: 1 }); - }); - - it('empty items', () => { - const counted = countBy([], i => i); - expect(Object.keys(counted).length).toBe(0); - }); -}); - -describe('maxBy', () => { - it('basic', () => { - const items = [{ n: 1 }, { n: 2 }]; - const max = maxBy(items, i => i.n); - expect(max).toBe(items[1]); - }); - - it('empty items', () => { - expect(maxBy([], i => i)).toBeNull(); - }); -}); - -describe('atLeastNMatches', () => { - it('basic', () => { - const arr = [1, 2, 3, 4, 5, 6, 7, 8, 9]; - const isEven = (num: number): boolean => num % 2 === 0; - const isGreaterThan5 = (num: number): boolean => num > 5; - const isNegative = (num: number): boolean => num < 0; - - expect(atLeastNMatches(arr, isEven, 3)).toBe(true); - expect(atLeastNMatches(arr, isGreaterThan5, 5)).toBe(false); - expect(atLeastNMatches(arr, isNegative, 1)).toBe(false); - - const strArr = ['apple', 'banana', 'orange', 'kiwi', 'mango']; - const startsWithA = (str: string): boolean => str[0].toLowerCase() === 'a'; - const longerThan5 = (str: string): boolean => str.length > 5; - - expect(atLeastNMatches(strArr, startsWithA, 1)).toBe(true); - expect(atLeastNMatches(strArr, longerThan5, 3)).toBe(false); - }); -}); - -describe('groupBy', () => { - it('basic', () => { - const students = [ - { name: 'Alice', age: 25 }, - { name: 'Bob', age: 23 }, - { name: 'Cathy', age: 25 }, - { name: 'David', age: 23 }, - ]; - - const groupedByAge = groupBy(students, student => student.age.toString()); - const expectedGroupedByAge = { - '23': [ - { name: 'Bob', age: 23 }, - { name: 'David', age: 23 }, - ], - '25': [ - { name: 'Alice', age: 25 }, - { name: 'Cathy', age: 25 }, - ], - }; - expect(groupedByAge).toMatchObject(expectedGroupedByAge); - }); - - it('empty', () => { - const emptyArray: string[] = []; - const groupedEmptyArray = groupBy(emptyArray, item => item); - expect(Object.keys(groupedEmptyArray).length).toBe(0); - }); -}); diff --git a/blocksuite/affine/shared/src/adapters/notion-html/delta-converter.ts b/blocksuite/affine/shared/src/adapters/notion-html/delta-converter.ts index 45d8ad28a7..4683e46bfe 100644 --- a/blocksuite/affine/shared/src/adapters/notion-html/delta-converter.ts +++ b/blocksuite/affine/shared/src/adapters/notion-html/delta-converter.ts @@ -2,9 +2,9 @@ import { createIdentifier, type ServiceIdentifier, } from '@blocksuite/global/di'; -import { isEqual } from '@blocksuite/global/utils'; import type { DeltaInsert } from '@blocksuite/inline'; import type { ExtensionType } from '@blocksuite/store'; +import isEqual from 'lodash-es/isEqual'; import type { AffineTextAttributes } from '../../types/index.js'; import { diff --git a/blocksuite/affine/shared/src/adapters/utils/text.ts b/blocksuite/affine/shared/src/adapters/utils/text.ts index 7ee92da5bb..6c3bb19d54 100644 --- a/blocksuite/affine/shared/src/adapters/utils/text.ts +++ b/blocksuite/affine/shared/src/adapters/utils/text.ts @@ -1,6 +1,6 @@ import type { ReferenceParams } from '@blocksuite/affine-model'; -import { isEqual } from '@blocksuite/global/utils'; import type { DeltaInsert } from '@blocksuite/inline'; +import isEqual from 'lodash-es/isEqual'; const mergeDeltas = ( acc: DeltaInsert[], diff --git a/blocksuite/affine/widget-drag-handle/src/watchers/drag-event-watcher.ts b/blocksuite/affine/widget-drag-handle/src/watchers/drag-event-watcher.ts index 5472845dfc..9accdfd9ab 100644 --- a/blocksuite/affine/widget-drag-handle/src/watchers/drag-event-watcher.ts +++ b/blocksuite/affine/widget-drag-handle/src/watchers/drag-event-watcher.ts @@ -54,7 +54,7 @@ import { Rect, type SerializedXYWH, } from '@blocksuite/global/gfx'; -import { assertType, groupBy, last } from '@blocksuite/global/utils'; +import { assertType } from '@blocksuite/global/utils'; import { type BlockModel, type BlockSnapshot, @@ -63,6 +63,8 @@ import { type SliceSnapshot, toDraftModel, } from '@blocksuite/store'; +import groupBy from 'lodash-es/groupBy'; +import last from 'lodash-es/last'; import type { AffineDragHandleWidget } from '../drag-handle.js'; import { PreviewHelper } from '../helpers/preview-helper.js'; diff --git a/blocksuite/affine/widget-remote-selection/src/edgeless/index.ts b/blocksuite/affine/widget-remote-selection/src/edgeless/index.ts index 3acfe0faa9..04a71f0c93 100644 --- a/blocksuite/affine/widget-remote-selection/src/edgeless/index.ts +++ b/blocksuite/affine/widget-remote-selection/src/edgeless/index.ts @@ -10,7 +10,6 @@ import { GfxControllerIdentifier, type GfxModel, } from '@blocksuite/block-std/gfx'; -import { pickValues } from '@blocksuite/global/utils'; import { MultiCursorDuotoneIcon } from '@blocksuite/icons/lit'; import type { UserInfo } from '@blocksuite/store'; import { css, html, nothing } from 'lit'; @@ -183,13 +182,17 @@ export class EdgelessRemoteSelectionWidget extends WidgetComponent { - _disposables.add(slot.on(this._updateOnElementChange)); - }); + if (this.surface) { + _disposables.add( + this.surface.elementAdded.on(this._updateOnElementChange) + ); + _disposables.add( + this.surface.elementRemoved.on(this._updateOnElementChange) + ); + _disposables.add( + this.surface.elementUpdated.on(this._updateOnElementChange) + ); + } _disposables.add(doc.slots.blockUpdated.on(this._updateOnElementChange)); diff --git a/blocksuite/framework/block-std/src/gfx/controller.ts b/blocksuite/framework/block-std/src/gfx/controller.ts index 5634767a7e..8101b01cf8 100644 --- a/blocksuite/framework/block-std/src/gfx/controller.ts +++ b/blocksuite/framework/block-std/src/gfx/controller.ts @@ -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'; diff --git a/blocksuite/framework/block-std/src/gfx/layer.ts b/blocksuite/framework/block-std/src/gfx/layer.ts index 15bd9bff13..40aa682fa3 100644 --- a/blocksuite/framework/block-std/src/gfx/layer.ts +++ b/blocksuite/framework/block-std/src/gfx/layer.ts @@ -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); diff --git a/blocksuite/framework/block-std/src/gfx/model/surface/element-model.ts b/blocksuite/framework/block-std/src/gfx/model/surface/element-model.ts index 43a7d575ba..25c41846d0 100644 --- a/blocksuite/framework/block-std/src/gfx/model/surface/element-model.ts +++ b/blocksuite/framework/block-std/src/gfx/model/surface/element-model.ts @@ -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 { diff --git a/blocksuite/framework/block-std/src/gfx/selection.ts b/blocksuite/framework/block-std/src/gfx/selection.ts index acfbfc2fd5..1121b82048 100644 --- a/blocksuite/framework/block-std/src/gfx/selection.ts +++ b/blocksuite/framework/block-std/src/gfx/selection.ts @@ -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, diff --git a/blocksuite/framework/block-std/src/utils/layer.ts b/blocksuite/framework/block-std/src/utils/layer.ts index 0b71b31e1b..397036653a 100644 --- a/blocksuite/framework/block-std/src/utils/layer.ts +++ b/blocksuite/framework/block-std/src/utils/layer.ts @@ -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; diff --git a/blocksuite/framework/global/src/__tests__/utils.unit.spec.ts b/blocksuite/framework/global/src/__tests__/utils.unit.spec.ts deleted file mode 100644 index 655ba0e706..0000000000 --- a/blocksuite/framework/global/src/__tests__/utils.unit.spec.ts +++ /dev/null @@ -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); - }); -}); diff --git a/blocksuite/framework/global/src/utils/index.ts b/blocksuite/framework/global/src/utils/index.ts index 004cfaa694..de21f56879 100644 --- a/blocksuite/framework/global/src/utils/index.ts +++ b/blocksuite/framework/global/src/utils/index.ts @@ -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'; diff --git a/blocksuite/framework/global/src/utils/is-equal.ts b/blocksuite/framework/global/src/utils/is-equal.ts deleted file mode 100644 index ef61a8de9b..0000000000 --- a/blocksuite/framework/global/src/utils/is-equal.ts +++ /dev/null @@ -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(_: unknown): asserts _ is T {} - -export type Equals = - /// - (() => T extends X ? 1 : 2) extends () => T extends Y ? 1 : 2 - ? true - : false; - -type Allowed = - | unknown - | void - | null - | undefined - | boolean - | number - | string - | unknown[] - | object; -export function isEqual( - val: T, - expected: U -): Equals { - const a = isPrimitive(val); - const b = isPrimitive(expected); - if (a && b) { - if (!Object.is(val, expected)) { - return false as Equals; - } - } else if (a !== b) { - return false as Equals; - } else { - if (Array.isArray(val) && Array.isArray(expected)) { - if (val.length !== expected.length) { - return false as Equals; - } - return val.every((x, i) => isEqual(x, expected[i])) as Equals; - } else if (typeof val === 'object' && typeof expected === 'object') { - const obj1 = Object.entries(val as Record); - const obj2 = Object.entries(expected as Record); - if (obj1.length !== obj2.length) { - return false as Equals; - } - return obj1.every((x, i) => isEqual(x, obj2[i])) as Equals; - } - } - return true as Equals; -} diff --git a/blocksuite/framework/global/src/utils/iterable.ts b/blocksuite/framework/global/src/utils/iterable.ts deleted file mode 100644 index 724d09b7f3..0000000000 --- a/blocksuite/framework/global/src/utils/iterable.ts +++ /dev/null @@ -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( - items: T[], - key: (item: T) => string | number | null -): Record { - const count: Record = {}; - 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(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( - 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( - arr: T[], - key: K | ((item: T) => K) -): Record { - const result = {} as Record; - - 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(target: Array, keys: number[]): Array { - return keys.reduce((pre, key) => { - pre.push(target[key]); - return pre; - }, [] as T[]); -} - -export function pick( - target: T, - keys: K[] -): Record { - return keys.reduce( - (pre, key) => { - pre[key] = target[key]; - return pre; - }, - {} as Record - ); -} - -export function pickValues( - target: T, - keys: K[] -): Array { - return keys.reduce( - (pre, key) => { - pre.push(target[key]); - return pre; - }, - [] as Array - ); -} - -export function lastN(target: Array, 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).length === 0; - } - - return false; -} - -export function keys(obj: T): (keyof T)[] { - return Object.keys(obj as object) as (keyof T)[]; -} - -export function values(obj: T): T[keyof T][] { - return Object.values(obj as object); -} - -type IterableType = T extends Array ? U : T; - -export function last>( - iterable: T -): IterableType | 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; -} - -export function nToLast>( - iterable: T, - n: number -): IterableType | undefined { - if (Array.isArray(iterable)) { - return iterable[iterable.length - n]; - } - - const arr = [...iterable]; - - return arr[arr.length - n] as IterableType; -} diff --git a/blocksuite/framework/global/src/utils/types.ts b/blocksuite/framework/global/src/utils/types.ts index 6617133661..7af7881635 100644 --- a/blocksuite/framework/global/src/utils/types.ts +++ b/blocksuite/framework/global/src/utils/types.ts @@ -10,3 +10,5 @@ export type DeepPartial = { : DeepPartial : T[P]; }; + +export function assertType(_: unknown): asserts _ is T {} diff --git a/blocksuite/framework/sync/package.json b/blocksuite/framework/sync/package.json index 4ddcc10228..21581a6601 100644 --- a/blocksuite/framework/sync/package.json +++ b/blocksuite/framework/sync/package.json @@ -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": { diff --git a/blocksuite/framework/sync/src/doc/peer.ts b/blocksuite/framework/sync/src/doc/peer.ts index 005f4ffb3b..7c21b84d12 100644 --- a/blocksuite/framework/sync/src/doc/peer.ts +++ b/blocksuite/framework/sync/src/doc/peer.ts @@ -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, diff --git a/yarn.lock b/yarn.lock index 0dab003146..48bd64538a 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2638,10 +2638,10 @@ __metadata: "@lit/context": "npm:^1.1.2" "@preact/signals-core": "npm:^1.8.0" "@toeverything/theme": "npm:^1.1.12" - "@types/lodash.chunk": "npm:^4.2.9" + "@types/lodash-es": "npm:^4.17.12" fractional-indexing: "npm:^3.2.0" lit: "npm:^3.2.0" - lodash.chunk: "npm:^4.2.0" + lodash-es: "npm:^4.17.21" nanoid: "npm:^5.0.7" zod: "npm:^3.23.8" languageName: unknown @@ -2661,11 +2661,11 @@ __metadata: "@lit/context": "npm:^1.1.2" "@preact/signals-core": "npm:^1.8.0" "@toeverything/theme": "npm:^1.1.12" - "@types/lodash.chunk": "npm:^4.2.9" + "@types/lodash-es": "npm:^4.17.12" fractional-indexing: "npm:^3.2.0" html2canvas: "npm:^1.4.1" lit: "npm:^3.2.0" - lodash.chunk: "npm:^4.2.0" + lodash-es: "npm:^4.17.21" nanoid: "npm:^5.0.7" pdf-lib: "npm:^1.17.1" vitest: "npm:3.0.7" @@ -2786,7 +2786,9 @@ __metadata: "@blocksuite/inline": "workspace:*" "@blocksuite/store": "workspace:*" "@toeverything/theme": "npm:^1.1.12" + "@types/lodash-es": "npm:^4.17.12" fractional-indexing: "npm:^3.2.0" + lodash-es: "npm:^4.17.21" yjs: "npm:^13.6.21" zod: "npm:^3.23.8" languageName: unknown @@ -3062,8 +3064,10 @@ __metadata: "@lit/context": "npm:^1.1.2" "@preact/signals-core": "npm:^1.8.0" "@toeverything/theme": "npm:^1.1.12" + "@types/lodash-es": "npm:^4.17.12" date-fns: "npm:^4.0.0" lit: "npm:^3.2.0" + lodash-es: "npm:^4.17.21" yjs: "npm:^13.6.21" zod: "npm:^3.23.8" languageName: unknown @@ -3233,8 +3237,10 @@ __metadata: resolution: "@blocksuite/sync@workspace:blocksuite/framework/sync" dependencies: "@blocksuite/global": "workspace:*" + "@types/lodash-es": "npm:^4.17.12" idb: "npm:^8.0.0" idb-keyval: "npm:^6.2.1" + lodash-es: "npm:^4.17.21" vitest: "npm:3.0.7" y-protocols: "npm:^1.0.6" peerDependencies: @@ -14145,15 +14151,6 @@ __metadata: languageName: node linkType: hard -"@types/lodash.chunk@npm:^4.2.9": - version: 4.2.9 - resolution: "@types/lodash.chunk@npm:4.2.9" - dependencies: - "@types/lodash": "npm:*" - checksum: 10/ccffe7273a0941655d5b988baeffa8f7d4d19a8b43ed728ff4e616013506efe85914ba99d4ec299e0106506e1bca3923b065eabb0aa5f1e4b18f68e790ae6b88 - languageName: node - linkType: hard - "@types/lodash.clonedeep@npm:^4.5.9": version: 4.5.9 resolution: "@types/lodash.clonedeep@npm:4.5.9" @@ -24222,13 +24219,6 @@ __metadata: languageName: node linkType: hard -"lodash.chunk@npm:^4.2.0": - version: 4.2.0 - resolution: "lodash.chunk@npm:4.2.0" - checksum: 10/3b6639fda107a0d8c3996090eeb7ad0f05063e6a1f4692a01335e7c1990030d3274c3164d78159f24928d86eb0943ad326b0a4a2da706c250c4d3166ddd8ed0e - languageName: node - linkType: hard - "lodash.clonedeep@npm:^4.5.0": version: 4.5.0 resolution: "lodash.clonedeep@npm:4.5.0"