From c78d6b81c6b1f0dc64bf0155c09f4e2fc66d0ef0 Mon Sep 17 00:00:00 2001 From: zzj3720 <17165520+zzj3720@users.noreply.github.com> Date: Mon, 10 Feb 2025 14:14:53 +0000 Subject: [PATCH] feat(editor): table block supports drag-and-drop sorting (#10065) close: BS-2477 --- .../affine/block-table/src/add-button.ts | 3 +- blocksuite/affine/block-table/src/effects.ts | 16 +- .../block-table/src/selection-controller.ts | 203 +++++++++++++++++- .../affine/block-table/src/selection-layer.ts | 3 +- .../affine/block-table/src/table-block.ts | 4 +- .../affine/block-table/src/table-cell.css.ts | 51 ++++- .../affine/block-table/src/table-cell.ts | 199 +++++++++++++++-- .../block-table/src/table-data-manager.ts | 7 +- blocksuite/affine/block-table/src/utils.ts | 6 + .../utils/{ => drag-helper}/cell-select.ts | 3 +- .../shared/src/utils/drag-helper/index.ts | 3 + .../src/utils/drag-helper/linear-move.ts | 38 ++++ .../shared/src/utils/drag-helper/types.ts | 1 + .../shared/src/utils/fractional-indexing.ts | 6 +- blocksuite/affine/shared/src/utils/index.ts | 2 +- 15 files changed, 496 insertions(+), 49 deletions(-) create mode 100644 blocksuite/affine/block-table/src/utils.ts rename blocksuite/affine/shared/src/utils/{ => drag-helper}/cell-select.ts (97%) create mode 100644 blocksuite/affine/shared/src/utils/drag-helper/index.ts create mode 100644 blocksuite/affine/shared/src/utils/drag-helper/linear-move.ts create mode 100644 blocksuite/affine/shared/src/utils/drag-helper/types.ts diff --git a/blocksuite/affine/block-table/src/add-button.ts b/blocksuite/affine/block-table/src/add-button.ts index eb3fc579c0..6fbbde780d 100644 --- a/blocksuite/affine/block-table/src/add-button.ts +++ b/blocksuite/affine/block-table/src/add-button.ts @@ -24,6 +24,7 @@ import { import { DefaultColumnWidth, DefaultRowHeight } from './consts'; import type { TableDataManager } from './table-data-manager'; +export const AddButtonComponentName = 'affine-table-add-button'; export class AddButton extends SignalWatcher( WithDisposable(ShadowlessElement) ) { @@ -322,6 +323,6 @@ export class AddButton extends SignalWatcher( } declare global { interface HTMLElementTagNameMap { - 'affine-table-add-button': AddButton; + [AddButtonComponentName]: AddButton; } } diff --git a/blocksuite/affine/block-table/src/effects.ts b/blocksuite/affine/block-table/src/effects.ts index 60b6268250..fcd08675d3 100644 --- a/blocksuite/affine/block-table/src/effects.ts +++ b/blocksuite/affine/block-table/src/effects.ts @@ -1,11 +1,11 @@ -import { AddButton } from './add-button'; -import { SelectionLayer } from './selection-layer'; -import { TableBlockComponent } from './table-block'; -import { TableCell } from './table-cell'; +import { AddButton, AddButtonComponentName } from './add-button'; +import { SelectionLayer, SelectionLayerComponentName } from './selection-layer'; +import { TableBlockComponent, TableBlockComponentName } from './table-block'; +import { TableCell, TableCellComponentName } from './table-cell'; export function effects() { - customElements.define('affine-table', TableBlockComponent); - customElements.define('affine-table-cell', TableCell); - customElements.define('affine-table-add-button', AddButton); - customElements.define('affine-table-selection-layer', SelectionLayer); + customElements.define(TableBlockComponentName, TableBlockComponent); + customElements.define(TableCellComponentName, TableCell); + customElements.define(AddButtonComponentName, AddButton); + customElements.define(SelectionLayerComponentName, SelectionLayer); } diff --git a/blocksuite/affine/block-table/src/selection-controller.ts b/blocksuite/affine/block-table/src/selection-controller.ts index 59b91ee1bc..dcf6bc9691 100644 --- a/blocksuite/affine/block-table/src/selection-controller.ts +++ b/blocksuite/affine/block-table/src/selection-controller.ts @@ -1,6 +1,7 @@ import { domToOffsets, getAreaByOffsets, + getTargetIndexByDraggingOffset, } from '@blocksuite/affine-shared/utils'; import type { UIEventStateContext } from '@blocksuite/block-std'; import { IS_MOBILE } from '@blocksuite/global/env'; @@ -14,6 +15,13 @@ import { TableSelectionData, } from './selection-schema'; import type { TableBlockComponent } from './table-block'; +import { + createColumnDragPreview, + createRowDragPreview, + type TableCell, + TableCellComponentName, +} from './table-cell'; +import { cleanSelection } from './utils'; type Cells = string[][]; const TEXT = 'text/plain'; export class SelectionController implements ReactiveController { @@ -44,7 +52,7 @@ export class SelectionController implements ReactiveController { return; } const onMove = (event: MouseEvent) => { - this.dataManager.draggingColumnId$.value = columnId; + this.dataManager.widthAdjustColumnId$.value = columnId; this.dataManager.virtualWidth$.value = { columnId, width: Math.max( @@ -55,7 +63,7 @@ export class SelectionController implements ReactiveController { }; const onUp = () => { const width = this.dataManager.virtualWidth$.value?.width; - this.dataManager.draggingColumnId$.value = undefined; + this.dataManager.widthAdjustColumnId$.value = undefined; this.dataManager.virtualWidth$.value = undefined; if (width) { this.dataManager.setColumnWidth(columnId, width); @@ -76,14 +84,199 @@ export class SelectionController implements ReactiveController { if (!(target instanceof HTMLElement)) { return; } - const dragHandle = target.closest('[data-width-adjust-column-id]'); - if (dragHandle instanceof HTMLElement) { - this.widthAdjust(dragHandle, event); + const widthAdjustColumn = target.closest('[data-width-adjust-column-id]'); + if (widthAdjustColumn instanceof HTMLElement) { + this.widthAdjust(widthAdjustColumn, event); + return; + } + const columnDragHandle = target.closest('[data-drag-column-id]'); + if (columnDragHandle instanceof HTMLElement) { + this.columnDrag(columnDragHandle, event); + return; + } + const rowDragHandle = target.closest('[data-drag-row-id]'); + if (rowDragHandle instanceof HTMLElement) { + this.rowDrag(rowDragHandle, event); return; } this.onDragStart(event); }); } + startColumnDrag(x: number, columnDragHandle: HTMLElement) { + const columnId = columnDragHandle.dataset['dragColumnId']; + if (!columnId) { + return; + } + const cellRect = columnDragHandle.closest('td')?.getBoundingClientRect(); + const containerRect = this.host.getBoundingClientRect(); + if (!cellRect) { + return; + } + const initialDiffX = x - cellRect.left; + const cells = Array.from( + this.host.querySelectorAll(`td[data-column-id="${columnId}"]`) + ).map(td => td.closest(TableCellComponentName) as TableCell); + const firstCell = cells[0]; + if (!firstCell) { + return; + } + const draggingIndex = firstCell.columnIndex; + const columns = Array.from( + this.host.querySelectorAll(`td[data-row-id="${firstCell?.row?.rowId}"]`) + ).map(td => td.getBoundingClientRect()); + const columnOffsets = columns.flatMap((column, index) => + index === columns.length - 1 ? [column.left, column.right] : [column.left] + ); + const columnDragPreview = createColumnDragPreview(cells); + columnDragPreview.style.top = `${cellRect.top - containerRect.top - 0.5}px`; + columnDragPreview.style.left = `${cellRect.left - containerRect.left}px`; + columnDragPreview.style.width = `${cellRect.width}px`; + this.host.append(columnDragPreview); + document.body.style.pointerEvents = 'none'; + const onMove = (x: number) => { + const { targetIndex, isForward } = getTargetIndexByDraggingOffset( + columnOffsets, + draggingIndex, + x - initialDiffX + ); + if (targetIndex != null) { + this.dataManager.ui.columnIndicatorIndex$.value = isForward + ? targetIndex + 1 + : targetIndex; + } else { + this.dataManager.ui.columnIndicatorIndex$.value = undefined; + } + columnDragPreview.style.left = `${x - initialDiffX - containerRect.left}px`; + }; + const onEnd = () => { + const targetIndex = this.dataManager.ui.columnIndicatorIndex$.value; + this.dataManager.ui.columnIndicatorIndex$.value = undefined; + document.body.style.pointerEvents = 'auto'; + columnDragPreview.remove(); + if (targetIndex != null) { + this.dataManager.moveColumn( + draggingIndex, + targetIndex === 0 ? undefined : targetIndex - 1 + ); + } + }; + return { + onMove, + onEnd, + }; + } + columnDrag(columnDragHandle: HTMLElement, event: MouseEvent) { + let drag: { onMove: (x: number) => void; onEnd: () => void } | undefined = + undefined; + const initialX = event.clientX; + const onMove = (event: MouseEvent) => { + const diffX = event.clientX - initialX; + if (!drag && Math.abs(diffX) > 10) { + event.preventDefault(); + event.stopPropagation(); + cleanSelection(); + this.setSelected(undefined); + drag = this.startColumnDrag(initialX, columnDragHandle); + } + drag?.onMove(event.clientX); + }; + const onUp = () => { + drag?.onEnd(); + window.removeEventListener('mousemove', onMove); + window.removeEventListener('mouseup', onUp); + }; + window.addEventListener('mousemove', onMove); + window.addEventListener('mouseup', onUp); + } + startRowDrag(y: number, rowDragHandle: HTMLElement) { + const rowId = rowDragHandle.dataset['dragRowId']; + if (!rowId) { + return; + } + const cellRect = rowDragHandle.closest('td')?.getBoundingClientRect(); + const containerRect = this.host.getBoundingClientRect(); + if (!cellRect) { + return; + } + const initialDiffY = y - cellRect.top; + const cells = Array.from( + this.host.querySelectorAll(`td[data-row-id="${rowId}"]`) + ).map(td => td.closest(TableCellComponentName) as TableCell); + const firstCell = cells[0]; + if (!firstCell) { + return; + } + const draggingIndex = firstCell.rowIndex; + const rows = Array.from( + this.host.querySelectorAll( + `td[data-column-id="${firstCell?.column?.columnId}"]` + ) + ).map(td => td.getBoundingClientRect()); + const rowOffsets = rows.flatMap((row, index) => + index === rows.length - 1 ? [row.top, row.bottom] : [row.top] + ); + const rowDragPreview = createRowDragPreview(cells); + rowDragPreview.style.left = `${cellRect.left - containerRect.left}px`; + rowDragPreview.style.top = `${cellRect.top - containerRect.top - 0.5}px`; + rowDragPreview.style.height = `${cellRect.height}px`; + this.host.append(rowDragPreview); + document.body.style.pointerEvents = 'none'; + const onMove = (y: number) => { + const { targetIndex, isForward } = getTargetIndexByDraggingOffset( + rowOffsets, + draggingIndex, + y - initialDiffY + ); + if (targetIndex != null) { + this.dataManager.ui.rowIndicatorIndex$.value = isForward + ? targetIndex + 1 + : targetIndex; + } else { + this.dataManager.ui.rowIndicatorIndex$.value = undefined; + } + rowDragPreview.style.top = `${y - initialDiffY - containerRect.top}px`; + }; + const onEnd = () => { + const targetIndex = this.dataManager.ui.rowIndicatorIndex$.value; + this.dataManager.ui.rowIndicatorIndex$.value = undefined; + document.body.style.pointerEvents = 'auto'; + rowDragPreview.remove(); + if (targetIndex != null) { + this.dataManager.moveRow( + draggingIndex, + targetIndex === 0 ? undefined : targetIndex - 1 + ); + } + }; + return { + onMove, + onEnd, + }; + } + rowDrag(rowDragHandle: HTMLElement, event: MouseEvent) { + let drag: { onMove: (x: number) => void; onEnd: () => void } | undefined = + undefined; + const initialY = event.clientY; + const onMove = (event: MouseEvent) => { + const diffY = event.clientY - initialY; + if (!drag && Math.abs(diffY) > 10) { + event.preventDefault(); + event.stopPropagation(); + cleanSelection(); + this.setSelected(undefined); + drag = this.startRowDrag(initialY, rowDragHandle); + } + drag?.onMove(event.clientY); + }; + // eslint-disable-next-line sonarjs/no-identical-functions + const onUp = () => { + drag?.onEnd(); + window.removeEventListener('mousemove', onMove); + window.removeEventListener('mouseup', onUp); + }; + window.addEventListener('mousemove', onMove); + window.addEventListener('mouseup', onUp); + } readonly doCopyOrCut = (selection: TableAreaSelection, isCut: boolean) => { const columns = this.dataManager.uiColumns$.value; const rows = this.dataManager.uiRows$.value; diff --git a/blocksuite/affine/block-table/src/selection-layer.ts b/blocksuite/affine/block-table/src/selection-layer.ts index a93f0badf3..7233e0f2a3 100644 --- a/blocksuite/affine/block-table/src/selection-layer.ts +++ b/blocksuite/affine/block-table/src/selection-layer.ts @@ -14,6 +14,7 @@ type Rect = { height: number; }; +export const SelectionLayerComponentName = 'affine-table-selection-layer'; export class SelectionLayer extends SignalWatcher( WithDisposable(ShadowlessElement) ) { @@ -105,6 +106,6 @@ export class SelectionLayer extends SignalWatcher( declare global { interface HTMLElementTagNameMap { - 'affine-table-selection-layer': SelectionLayer; + [SelectionLayerComponentName]: SelectionLayer; } } diff --git a/blocksuite/affine/block-table/src/table-block.ts b/blocksuite/affine/block-table/src/table-block.ts index 33a0f8bb8c..cab2f9898d 100644 --- a/blocksuite/affine/block-table/src/table-block.ts +++ b/blocksuite/affine/block-table/src/table-block.ts @@ -23,6 +23,7 @@ import { } from './table-block.css'; import { TableDataManager } from './table-data-manager'; +export const TableBlockComponentName = 'affine-table'; export class TableBlockComponent extends CaptionedBlockComponent { private _dataManager: TableDataManager | null = null; @@ -38,6 +39,7 @@ export class TableBlockComponent extends CaptionedBlockComponent
`; } renderOptionsButton() { - if (!this.row || !this.column) { + if (this.readonly || !this.row || !this.column) { return nothing; } return html` @@ -525,33 +533,115 @@ export class TableCell extends SignalWatcher( }); } - renderWidthDragHandle() { + showColumnIndicator$ = computed(() => { + const indicatorIndex = + this.dataManager.ui.columnIndicatorIndex$.value ?? -1; + if (indicatorIndex === 0 && this.columnIndex === 0) { + return 'left'; + } + if (indicatorIndex - 1 === this.columnIndex) { + return 'right'; + } + return; + }); + showRowIndicator$ = computed(() => { + const indicatorIndex = this.dataManager.ui.rowIndicatorIndex$.value ?? -1; + if (indicatorIndex === 0 && this.rowIndex === 0) { + return 'top'; + } + if (indicatorIndex - 1 === this.rowIndex) { + return 'bottom'; + } + return; + }); + renderRowIndicator() { + if (this.readonly) { + return nothing; + } + const columnIndex = this.columnIndex; + const isFirstColumn = columnIndex === 0; + const isLastColumn = + columnIndex === this.dataManager.uiColumns$.value.length - 1; + const showIndicator = this.showRowIndicator$.value; + const style = (show: boolean) => + styleMap({ + opacity: show ? 1 : 0, + borderRadius: isFirstColumn + ? '3px 0 0 3px' + : isLastColumn + ? '0 3px 3px 0' + : '0', + }); + const indicator0 = + this.rowIndex === 0 + ? html` +
+ ` + : nothing; + return html` + ${indicator0} +
+ `; + } + renderColumnIndicator() { + if (this.readonly) { + return nothing; + } const hoverColumnId$ = this.dataManager.hoverDragHandleColumnId$; - const draggingColumnId$ = this.dataManager.draggingColumnId$; + const draggingColumnId$ = this.dataManager.widthAdjustColumnId$; const rowIndex = this.rowIndex; const isFirstRow = rowIndex === 0; const isLastRow = rowIndex === this.dataManager.uiRows$.value.length - 1; - const show = + const showWidthAdjustIndicator = draggingColumnId$.value === this.column?.columnId || hoverColumnId$.value === this.column?.columnId; - return html`
{ - hoverColumnId$.value = this.column?.columnId; - }} - @mouseleave=${() => { - hoverColumnId$.value = undefined; - }} - style=${styleMap({ + const showIndicator = this.showColumnIndicator$.value; + const style = (show: boolean) => + styleMap({ opacity: show ? 1 : 0, borderRadius: isFirstRow ? '3px 3px 0 0' : isLastRow ? '0 0 3px 3px' : '0', - })} - data-width-adjust-column-id=${this.column?.columnId} - class=${widthDragHandleStyle} - >
`; + }); + const indicator0 = + this.columnIndex === 0 + ? html` +
+ ` + : nothing; + const mouseEnter = () => { + hoverColumnId$.value = this.column?.columnId; + }; + const mouseLeave = () => { + hoverColumnId$.value = undefined; + }; + return html` ${indicator0} +
`; } richText$ = signal(); @@ -666,12 +756,79 @@ export class TableCell extends SignalWatcher( : null}" data-parent-flavour="affine:table" > - ${this.renderOptionsButton()} ${this.renderWidthDragHandle()} + ${this.renderOptionsButton()} ${this.renderColumnIndicator()} + ${this.renderRowIndicator()} `; } } +export const createColumnDragPreview = (cells: TableCell[]) => { + const container = document.createElement('div'); + container.style.position = 'absolute'; + container.style.opacity = '0.8'; + container.style.display = 'flex'; + container.style.flexDirection = 'column'; + container.style.zIndex = '1000'; + container.style.boxShadow = '0 0 10px 0 rgba(0, 0, 0, 0.1)'; + container.style.backgroundColor = cssVarV2.layer.background.primary; + cells.forEach((cell, index) => { + const div = document.createElement('div'); + const td = cell.querySelector('td'); + if (index !== 0) { + div.style.borderTop = `1px solid ${cssVarV2.layer.insideBorder.border}`; + } + if (td) { + div.style.height = `${td.getBoundingClientRect().height}px`; + } + if (cell.text) { + const text = new RichText(); + text.style.padding = '8px 12px'; + text.yText = cell.text; + text.readonly = true; + text.attributesSchema = cell.inlineManager?.getSchema(); + text.attributeRenderer = cell.inlineManager?.getRenderer(); + text.embedChecker = cell.inlineManager?.embedChecker ?? (() => false); + div.append(text); + } + container.append(div); + }); + return container; +}; + +export const createRowDragPreview = (cells: TableCell[]) => { + const container = document.createElement('div'); + container.style.position = 'absolute'; + container.style.opacity = '0.8'; + container.style.display = 'flex'; + container.style.flexDirection = 'row'; + container.style.zIndex = '1000'; + container.style.boxShadow = '0 0 10px 0 rgba(0, 0, 0, 0.1)'; + container.style.backgroundColor = cssVarV2.layer.background.primary; + cells.forEach((cell, index) => { + const div = document.createElement('div'); + const td = cell.querySelector('td'); + if (index !== 0) { + div.style.borderLeft = `1px solid ${cssVarV2.layer.insideBorder.border}`; + } + if (td) { + div.style.width = `${td.getBoundingClientRect().width}px`; + } + if (cell.text) { + const text = new RichText(); + text.style.padding = '8px 12px'; + text.yText = cell.text; + text.readonly = true; + text.attributesSchema = cell.inlineManager?.getSchema(); + text.attributeRenderer = cell.inlineManager?.getRenderer(); + text.embedChecker = cell.inlineManager?.embedChecker ?? (() => false); + div.append(text); + } + container.append(div); + }); + return container; +}; + const threePointerIcon = (vertical: boolean = false) => { return html`
{ }; declare global { interface HTMLElementTagNameMap { - 'affine-table-cell': TableCell; + [TableCellComponentName]: TableCell; } } diff --git a/blocksuite/affine/block-table/src/table-data-manager.ts b/blocksuite/affine/block-table/src/table-data-manager.ts index 34b0a71c04..256c4c00b5 100644 --- a/blocksuite/affine/block-table/src/table-data-manager.ts +++ b/blocksuite/affine/block-table/src/table-data-manager.ts @@ -7,11 +7,14 @@ import type { TableAreaSelection } from './selection-schema'; export class TableDataManager { constructor(private readonly model: TableBlockModel) {} - + ui = { + columnIndicatorIndex$: signal(), + rowIndicatorIndex$: signal(), + }; hoverColumnIndex$ = signal(); hoverRowIndex$ = signal(); hoverDragHandleColumnId$ = signal(); - draggingColumnId$ = signal(); + widthAdjustColumnId$ = signal(); virtualColumnCount$ = signal(0); virtualRowCount$ = signal(0); virtualWidth$ = signal<{ columnId: string; width: number } | undefined>(); diff --git a/blocksuite/affine/block-table/src/utils.ts b/blocksuite/affine/block-table/src/utils.ts new file mode 100644 index 0000000000..526bbdf1c0 --- /dev/null +++ b/blocksuite/affine/block-table/src/utils.ts @@ -0,0 +1,6 @@ +export const cleanSelection = () => { + const selection = window.getSelection(); + if (selection) { + selection.removeAllRanges(); + } +}; diff --git a/blocksuite/affine/shared/src/utils/cell-select.ts b/blocksuite/affine/shared/src/utils/drag-helper/cell-select.ts similarity index 97% rename from blocksuite/affine/shared/src/utils/cell-select.ts rename to blocksuite/affine/shared/src/utils/drag-helper/cell-select.ts index 57bb895ac4..f8ad8b910c 100644 --- a/blocksuite/affine/shared/src/utils/cell-select.ts +++ b/blocksuite/affine/shared/src/utils/drag-helper/cell-select.ts @@ -1,4 +1,5 @@ -type OffsetList = number[]; +import type { OffsetList } from './types'; + type CellOffsets = { rows: OffsetList; columns: OffsetList; diff --git a/blocksuite/affine/shared/src/utils/drag-helper/index.ts b/blocksuite/affine/shared/src/utils/drag-helper/index.ts new file mode 100644 index 0000000000..bf3204b921 --- /dev/null +++ b/blocksuite/affine/shared/src/utils/drag-helper/index.ts @@ -0,0 +1,3 @@ +export * from './cell-select'; +export * from './linear-move'; +export * from './types'; diff --git a/blocksuite/affine/shared/src/utils/drag-helper/linear-move.ts b/blocksuite/affine/shared/src/utils/drag-helper/linear-move.ts new file mode 100644 index 0000000000..080886d6c5 --- /dev/null +++ b/blocksuite/affine/shared/src/utils/drag-helper/linear-move.ts @@ -0,0 +1,38 @@ +import type { OffsetList } from './types'; +export const getTargetIndexByDraggingOffset = ( + offsets: OffsetList, + draggingIndex: number, + indicatorLeft: number +) => { + const originalStart = offsets[draggingIndex]; + const originalWidth = offsets[draggingIndex + 1] - originalStart; + const indicatorRight = indicatorLeft + originalWidth; + const isForward = indicatorLeft > originalStart; + const startIndex = isForward ? draggingIndex + 1 : 0; + const endIndex = isForward ? offsets.length - 1 : draggingIndex - 1; + if (isForward) { + for (let i = endIndex; i >= startIndex; i--) { + const blockCenter = (offsets[i] + offsets[i + 1]) / 2; + if (indicatorRight > blockCenter) { + return { + targetIndex: i, + isForward, + }; + } + } + } else { + for (let i = startIndex; i <= endIndex; i++) { + const blockCenter = (offsets[i] + offsets[i + 1]) / 2; + if (indicatorLeft < blockCenter) { + return { + targetIndex: i, + isForward, + }; + } + } + } + return { + targetIndex: undefined, + isForward, + }; +}; diff --git a/blocksuite/affine/shared/src/utils/drag-helper/types.ts b/blocksuite/affine/shared/src/utils/drag-helper/types.ts new file mode 100644 index 0000000000..e1a0a29fe3 --- /dev/null +++ b/blocksuite/affine/shared/src/utils/drag-helper/types.ts @@ -0,0 +1 @@ +export type OffsetList = number[]; diff --git a/blocksuite/affine/shared/src/utils/fractional-indexing.ts b/blocksuite/affine/shared/src/utils/fractional-indexing.ts index 37f74d4a35..c577a00bd4 100644 --- a/blocksuite/affine/shared/src/utils/fractional-indexing.ts +++ b/blocksuite/affine/shared/src/utils/fractional-indexing.ts @@ -1,5 +1,9 @@ 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. @@ -59,7 +63,7 @@ export function generateFractionalIndexingKeyBetween( return generateKeyBetween(aSubkey, null) + '0' + postfix(); } else if (aSubkey !== null && bSubkey !== null) { // generate a key between a and b - if (aSubkey === bSubkey && a !== null && b !== null) { + 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 { diff --git a/blocksuite/affine/shared/src/utils/index.ts b/blocksuite/affine/shared/src/utils/index.ts index 7bdda68948..5cde1bb805 100644 --- a/blocksuite/affine/shared/src/utils/index.ts +++ b/blocksuite/affine/shared/src/utils/index.ts @@ -1,9 +1,9 @@ export * from './auto-scroll'; export * from './button-popper'; -export * from './cell-select'; export * from './collapsed'; export * from './dnd'; export * from './dom'; +export * from './drag-helper'; export * from './edgeless'; export * from './event'; export * from './file';