mirror of
https://github.com/toeverything/AFFiNE.git
synced 2026-02-13 12:55:00 +00:00
fix(editor): database behavier (#14394)
fix #13459 fix #13707 fix #13924 #### PR Dependency Tree * **PR #14394** 👈 This tree was auto-generated by [Charcoal](https://github.com/danerwilliams/charcoal) <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit * **New Features** * Improved URL paste: text is split into segments, inserted correctly, and single-URL pastes create linked-page references. * **UI Improvements** * Redesigned layout selector with compact dynamic options. * Number-format options are always available in table headers and mobile menus. * **Bug Fixes** * More consistent paste behavior for mixed text+URL content. * Prevented recursive selection updates when exiting edit mode. * **Tests** * Added tests for URL splitting, paste insertion, number formatting, and selection behavior. * **Chores** * Removed number-formatting feature flag; formatting now applied by default. <!-- end of auto-generated comment: release notes by coderabbit.ai -->
This commit is contained in:
@@ -3,8 +3,10 @@ import { describe, expect, it, vi } from 'vitest';
|
||||
|
||||
import type { GroupBy } from '../core/common/types.js';
|
||||
import type { DataSource } from '../core/data-source/base.js';
|
||||
import { DetailSelection } from '../core/detail/selection.js';
|
||||
import { groupByMatchers } from '../core/group-by/define.js';
|
||||
import { t } from '../core/logical/type-presets.js';
|
||||
import type { DataViewCellLifeCycle } from '../core/property/index.js';
|
||||
import { checkboxPropertyModelConfig } from '../property-presets/checkbox/define.js';
|
||||
import { multiSelectPropertyModelConfig } from '../property-presets/multi-select/define.js';
|
||||
import { selectPropertyModelConfig } from '../property-presets/select/define.js';
|
||||
@@ -456,4 +458,60 @@ describe('kanban', () => {
|
||||
expect(next?.hideEmpty).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
describe('detail selection', () => {
|
||||
it('should avoid recursive selection update when exiting select edit mode', () => {
|
||||
vi.stubGlobal('requestAnimationFrame', ((cb: FrameRequestCallback) => {
|
||||
cb(0);
|
||||
return 0;
|
||||
}) as typeof requestAnimationFrame);
|
||||
try {
|
||||
let selection: DetailSelection;
|
||||
let beforeExitCalls = 0;
|
||||
|
||||
const cell = {
|
||||
beforeEnterEditMode: () => true,
|
||||
beforeExitEditingMode: () => {
|
||||
beforeExitCalls += 1;
|
||||
selection.selection = {
|
||||
propertyId: 'status',
|
||||
isEditing: false,
|
||||
};
|
||||
},
|
||||
afterEnterEditingMode: () => {},
|
||||
focusCell: () => true,
|
||||
blurCell: () => true,
|
||||
forceUpdate: () => {},
|
||||
} satisfies DataViewCellLifeCycle;
|
||||
|
||||
const field = {
|
||||
isFocus$: signal(false),
|
||||
isEditing$: signal(false),
|
||||
cell,
|
||||
focus: () => {},
|
||||
blur: () => {},
|
||||
};
|
||||
|
||||
const detail = {
|
||||
querySelector: () => field,
|
||||
};
|
||||
|
||||
selection = new DetailSelection(detail);
|
||||
selection.selection = {
|
||||
propertyId: 'status',
|
||||
isEditing: true,
|
||||
};
|
||||
|
||||
selection.selection = {
|
||||
propertyId: 'status',
|
||||
isEditing: false,
|
||||
};
|
||||
|
||||
expect(beforeExitCalls).toBe(1);
|
||||
expect(field.isEditing$.value).toBe(false);
|
||||
} finally {
|
||||
vi.unstubAllGlobals();
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,36 +0,0 @@
|
||||
import { describe, expect, test } from 'vitest';
|
||||
|
||||
import { mobileEffects } from '../view-presets/table/mobile/effect.js';
|
||||
import type { MobileTableGroup } from '../view-presets/table/mobile/group.js';
|
||||
import { pcEffects } from '../view-presets/table/pc/effect.js';
|
||||
import type { TableGroup } from '../view-presets/table/pc/group.js';
|
||||
|
||||
/** @vitest-environment happy-dom */
|
||||
|
||||
describe('TableGroup', () => {
|
||||
test('toggle collapse on pc', () => {
|
||||
pcEffects();
|
||||
const group = document.createElement(
|
||||
'affine-data-view-table-group'
|
||||
) as TableGroup;
|
||||
|
||||
expect(group.collapsed$.value).toBe(false);
|
||||
(group as any)._toggleCollapse();
|
||||
expect(group.collapsed$.value).toBe(true);
|
||||
(group as any)._toggleCollapse();
|
||||
expect(group.collapsed$.value).toBe(false);
|
||||
});
|
||||
|
||||
test('toggle collapse on mobile', () => {
|
||||
mobileEffects();
|
||||
const group = document.createElement(
|
||||
'mobile-table-group'
|
||||
) as MobileTableGroup;
|
||||
|
||||
expect(group.collapsed$.value).toBe(false);
|
||||
(group as any)._toggleCollapse();
|
||||
expect(group.collapsed$.value).toBe(true);
|
||||
(group as any)._toggleCollapse();
|
||||
expect(group.collapsed$.value).toBe(false);
|
||||
});
|
||||
});
|
||||
101
blocksuite/affine/data-view/src/__tests__/table.unit.spec.ts
Normal file
101
blocksuite/affine/data-view/src/__tests__/table.unit.spec.ts
Normal file
@@ -0,0 +1,101 @@
|
||||
import { describe, expect, test } from 'vitest';
|
||||
|
||||
import { numberFormats } from '../property-presets/number/utils/formats.js';
|
||||
import {
|
||||
formatNumber,
|
||||
NumberFormatSchema,
|
||||
parseNumber,
|
||||
} from '../property-presets/number/utils/formatter.js';
|
||||
import { mobileEffects } from '../view-presets/table/mobile/effect.js';
|
||||
import type { MobileTableGroup } from '../view-presets/table/mobile/group.js';
|
||||
import { pcEffects } from '../view-presets/table/pc/effect.js';
|
||||
import type { TableGroup } from '../view-presets/table/pc/group.js';
|
||||
|
||||
/** @vitest-environment happy-dom */
|
||||
|
||||
describe('TableGroup', () => {
|
||||
test('toggle collapse on pc', () => {
|
||||
pcEffects();
|
||||
const group = document.createElement(
|
||||
'affine-data-view-table-group'
|
||||
) as TableGroup;
|
||||
|
||||
expect(group.collapsed$.value).toBe(false);
|
||||
(group as any)._toggleCollapse();
|
||||
expect(group.collapsed$.value).toBe(true);
|
||||
(group as any)._toggleCollapse();
|
||||
expect(group.collapsed$.value).toBe(false);
|
||||
});
|
||||
|
||||
test('toggle collapse on mobile', () => {
|
||||
mobileEffects();
|
||||
const group = document.createElement(
|
||||
'mobile-table-group'
|
||||
) as MobileTableGroup;
|
||||
|
||||
expect(group.collapsed$.value).toBe(false);
|
||||
(group as any)._toggleCollapse();
|
||||
expect(group.collapsed$.value).toBe(true);
|
||||
(group as any)._toggleCollapse();
|
||||
expect(group.collapsed$.value).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe('number formatter', () => {
|
||||
test('number format menu should expose all schema formats', () => {
|
||||
const menuFormats = numberFormats.map(format => format.type);
|
||||
const schemaFormats = NumberFormatSchema.options;
|
||||
|
||||
expect(new Set(menuFormats)).toEqual(new Set(schemaFormats));
|
||||
expect(menuFormats).toHaveLength(schemaFormats.length);
|
||||
});
|
||||
|
||||
test('formats grouped decimal numbers with Intl grouping rules', () => {
|
||||
const value = 11451.4;
|
||||
const decimals = 1;
|
||||
const expected = new Intl.NumberFormat(navigator.language, {
|
||||
style: 'decimal',
|
||||
useGrouping: true,
|
||||
minimumFractionDigits: decimals,
|
||||
maximumFractionDigits: decimals,
|
||||
}).format(value);
|
||||
|
||||
expect(formatNumber(value, 'numberWithCommas', decimals)).toBe(expected);
|
||||
});
|
||||
|
||||
test('formats percent values with Intl percent rules', () => {
|
||||
const value = 0.1234;
|
||||
const decimals = 2;
|
||||
const expected = new Intl.NumberFormat(navigator.language, {
|
||||
style: 'percent',
|
||||
useGrouping: false,
|
||||
minimumFractionDigits: decimals,
|
||||
maximumFractionDigits: decimals,
|
||||
}).format(value);
|
||||
|
||||
expect(formatNumber(value, 'percent', decimals)).toBe(expected);
|
||||
});
|
||||
|
||||
test('formats currency values with Intl currency rules', () => {
|
||||
const value = 11451.4;
|
||||
const expected = new Intl.NumberFormat(navigator.language, {
|
||||
style: 'currency',
|
||||
currency: 'USD',
|
||||
currencyDisplay: 'symbol',
|
||||
}).format(value);
|
||||
|
||||
expect(formatNumber(value, 'currencyUSD')).toBe(expected);
|
||||
});
|
||||
|
||||
test('parses grouped number string pasted from clipboard', () => {
|
||||
expect(parseNumber('11,451.4')).toBe(11451.4);
|
||||
});
|
||||
|
||||
test('keeps regular decimal parsing', () => {
|
||||
expect(parseNumber('123.45')).toBe(123.45);
|
||||
});
|
||||
|
||||
test('supports comma as decimal separator in locale-specific input', () => {
|
||||
expect(parseNumber('11451,4', ',')).toBe(11451.4);
|
||||
});
|
||||
});
|
||||
@@ -22,7 +22,6 @@ import { html } from 'lit/static-html.js';
|
||||
import { dataViewCommonStyle } from './common/css-variable.js';
|
||||
import type { DataSource } from './data-source/index.js';
|
||||
import type { DataViewSelection } from './types.js';
|
||||
import { cacheComputed } from './utils/cache.js';
|
||||
import { renderUniLit } from './utils/uni-component/index.js';
|
||||
import type { DataViewUILogicBase } from './view/data-view-base.js';
|
||||
import type { SingleView } from './view-manager/single-view.js';
|
||||
@@ -75,12 +74,38 @@ export class DataViewRootUILogic {
|
||||
|
||||
return new (logic(view))(this, view);
|
||||
}
|
||||
private readonly views$ = cacheComputed(this.viewManager.views$, viewId =>
|
||||
this.createDataViewUILogic(viewId)
|
||||
);
|
||||
private readonly _viewsCache = new Map<
|
||||
string,
|
||||
{ mode: string; logic: DataViewUILogicBase }
|
||||
>();
|
||||
|
||||
private readonly views$ = computed(() => {
|
||||
const viewDataList = this.dataSource.viewDataList$.value;
|
||||
const validIds = new Set(viewDataList.map(viewData => viewData.id));
|
||||
|
||||
for (const cachedId of this._viewsCache.keys()) {
|
||||
if (!validIds.has(cachedId)) {
|
||||
this._viewsCache.delete(cachedId);
|
||||
}
|
||||
}
|
||||
|
||||
return viewDataList.map(viewData => {
|
||||
const cached = this._viewsCache.get(viewData.id);
|
||||
if (cached && cached.mode === viewData.mode) {
|
||||
return cached.logic;
|
||||
}
|
||||
const logic = this.createDataViewUILogic(viewData.id);
|
||||
this._viewsCache.set(viewData.id, {
|
||||
mode: viewData.mode,
|
||||
logic,
|
||||
});
|
||||
return logic;
|
||||
});
|
||||
});
|
||||
|
||||
private readonly viewsMap$ = computed(() => {
|
||||
return Object.fromEntries(
|
||||
this.views$.list.value.map(logic => [logic.view.id, logic])
|
||||
this.views$.value.map(logic => [logic.view.id, logic])
|
||||
);
|
||||
});
|
||||
private readonly _uiRef = signal<DataViewRootUI>();
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import type { KanbanCardSelection } from '../../view-presets';
|
||||
import type { KanbanCard } from '../../view-presets/kanban/pc/card.js';
|
||||
import { KanbanCell } from '../../view-presets/kanban/pc/cell.js';
|
||||
import type { RecordDetail } from './detail.js';
|
||||
import { RecordField } from './field.js';
|
||||
|
||||
type DetailViewSelection = {
|
||||
@@ -9,16 +8,39 @@ type DetailViewSelection = {
|
||||
isEditing: boolean;
|
||||
};
|
||||
|
||||
type DetailSelectionHost = {
|
||||
querySelector: (selector: string) => unknown;
|
||||
};
|
||||
|
||||
const isSameDetailSelection = (
|
||||
current?: DetailViewSelection,
|
||||
next?: DetailViewSelection
|
||||
) => {
|
||||
if (!current && !next) {
|
||||
return true;
|
||||
}
|
||||
if (!current || !next) {
|
||||
return false;
|
||||
}
|
||||
return (
|
||||
current.propertyId === next.propertyId &&
|
||||
current.isEditing === next.isEditing
|
||||
);
|
||||
};
|
||||
|
||||
export class DetailSelection {
|
||||
_selection?: DetailViewSelection;
|
||||
|
||||
onSelect = (selection?: DetailViewSelection) => {
|
||||
if (isSameDetailSelection(this._selection, selection)) {
|
||||
return;
|
||||
}
|
||||
const old = this._selection;
|
||||
this._selection = selection;
|
||||
if (old) {
|
||||
this.blur(old);
|
||||
}
|
||||
this._selection = selection;
|
||||
if (selection) {
|
||||
if (selection && isSameDetailSelection(this._selection, selection)) {
|
||||
this.focus(selection);
|
||||
}
|
||||
};
|
||||
@@ -49,7 +71,7 @@ export class DetailSelection {
|
||||
}
|
||||
}
|
||||
|
||||
constructor(private readonly viewEle: RecordDetail) {}
|
||||
constructor(private readonly viewEle: DetailSelectionHost) {}
|
||||
|
||||
blur(selection: DetailViewSelection) {
|
||||
const container = this.getFocusCellContainer(selection);
|
||||
@@ -111,8 +133,10 @@ export class DetailSelection {
|
||||
}
|
||||
|
||||
focusFirstCell() {
|
||||
const firstId = this.viewEle.querySelector('affine-data-view-record-field')
|
||||
?.column.id;
|
||||
const firstField = this.viewEle.querySelector(
|
||||
'affine-data-view-record-field'
|
||||
) as RecordField | undefined;
|
||||
const firstId = firstField?.column.id;
|
||||
if (firstId) {
|
||||
this.selection = {
|
||||
propertyId: firstId,
|
||||
@@ -144,11 +168,12 @@ export class DetailSelection {
|
||||
|
||||
getSelectCard(selection: KanbanCardSelection) {
|
||||
const { groupKey, cardId } = selection.cards[0];
|
||||
const group = this.viewEle.querySelector(
|
||||
`affine-data-view-kanban-group[data-key="${groupKey}"]`
|
||||
) as HTMLElement | undefined;
|
||||
|
||||
return this.viewEle
|
||||
.querySelector(`affine-data-view-kanban-group[data-key="${groupKey}"]`)
|
||||
?.querySelector(
|
||||
`affine-data-view-kanban-card[data-card-id="${cardId}"]`
|
||||
) as KanbanCard | undefined;
|
||||
return group?.querySelector(
|
||||
`affine-data-view-kanban-card[data-card-id="${cardId}"]`
|
||||
) as KanbanCard | undefined;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,6 +12,5 @@ export type PropertyDataUpdater<
|
||||
> = (data: Data) => Partial<Data>;
|
||||
|
||||
export interface DatabaseFlags {
|
||||
enable_number_formatting: boolean;
|
||||
enable_table_virtual_scroll: boolean;
|
||||
}
|
||||
|
||||
@@ -24,17 +24,11 @@ export class NumberCell extends BaseCellRenderer<
|
||||
private accessor _inputEle!: HTMLInputElement;
|
||||
|
||||
private _getFormattedString(value: number | undefined = this.value) {
|
||||
const enableNewFormatting =
|
||||
this.view.featureFlags$.value.enable_number_formatting;
|
||||
const decimals = this.property.data$.value.decimal ?? 0;
|
||||
const formatMode = (this.property.data$.value.format ??
|
||||
'number') as NumberFormat;
|
||||
|
||||
return value != undefined
|
||||
? enableNewFormatting
|
||||
? formatNumber(value, formatMode, decimals)
|
||||
: value.toString()
|
||||
: '';
|
||||
return value != undefined ? formatNumber(value, formatMode, decimals) : '';
|
||||
}
|
||||
|
||||
private readonly _keydown = (e: KeyboardEvent) => {
|
||||
@@ -58,9 +52,7 @@ export class NumberCell extends BaseCellRenderer<
|
||||
return;
|
||||
}
|
||||
|
||||
const enableNewFormatting =
|
||||
this.view.featureFlags$.value.enable_number_formatting;
|
||||
const value = enableNewFormatting ? parseNumber(str) : parseFloat(str);
|
||||
const value = parseNumber(str);
|
||||
if (isNaN(value)) {
|
||||
if (this._inputEle) {
|
||||
this._inputEle.value = this.value
|
||||
|
||||
@@ -3,6 +3,7 @@ import zod from 'zod';
|
||||
import { t } from '../../core/logical/type-presets.js';
|
||||
import { propertyType } from '../../core/property/property-config.js';
|
||||
import { NumberPropertySchema } from './types.js';
|
||||
import { parseNumber } from './utils/formatter.js';
|
||||
export const numberPropertyType = propertyType('number');
|
||||
|
||||
export const numberPropertyModelConfig = numberPropertyType.modelConfig({
|
||||
@@ -21,7 +22,7 @@ export const numberPropertyModelConfig = numberPropertyType.modelConfig({
|
||||
default: () => null,
|
||||
toString: ({ value }) => value?.toString() ?? '',
|
||||
fromString: ({ value }) => {
|
||||
const num = value ? Number(value) : NaN;
|
||||
const num = value ? parseNumber(value) : NaN;
|
||||
return { value: isNaN(num) ? null : num };
|
||||
},
|
||||
toJson: ({ value }) => value ?? null,
|
||||
|
||||
@@ -64,9 +64,6 @@ export class MobileTableColumnHeader extends SignalWatcher(
|
||||
};
|
||||
|
||||
private popMenu(ele?: HTMLElement) {
|
||||
const enableNumberFormatting =
|
||||
this.tableViewManager.featureFlags$.value.enable_number_formatting;
|
||||
|
||||
popMenu(popupTargetFromElement(ele ?? this), {
|
||||
options: {
|
||||
title: {
|
||||
@@ -76,41 +73,36 @@ export class MobileTableColumnHeader extends SignalWatcher(
|
||||
inputConfig(this.column),
|
||||
typeConfig(this.column),
|
||||
// Number format begin
|
||||
...(enableNumberFormatting
|
||||
? [
|
||||
menu.subMenu({
|
||||
name: 'Number Format',
|
||||
hide: () =>
|
||||
!this.column.dataUpdate ||
|
||||
this.column.type$.value !== 'number',
|
||||
options: {
|
||||
title: {
|
||||
text: 'Number Format',
|
||||
menu.subMenu({
|
||||
name: 'Number Format',
|
||||
hide: () =>
|
||||
!this.column.dataUpdate || this.column.type$.value !== 'number',
|
||||
options: {
|
||||
title: {
|
||||
text: 'Number Format',
|
||||
},
|
||||
items: [
|
||||
numberFormatConfig(this.column),
|
||||
...numberFormats.map(format => {
|
||||
const data = this.column.data$.value;
|
||||
return menu.action({
|
||||
isSelected: data.format === format.type,
|
||||
prefix: html`<span
|
||||
style="font-size: var(--affine-font-base); scale: 1.2;"
|
||||
>${format.symbol}</span
|
||||
>`,
|
||||
name: format.label,
|
||||
select: () => {
|
||||
if (data.format === format.type) return;
|
||||
this.column.dataUpdate(() => ({
|
||||
format: format.type,
|
||||
}));
|
||||
},
|
||||
items: [
|
||||
numberFormatConfig(this.column),
|
||||
...numberFormats.map(format => {
|
||||
const data = this.column.data$.value;
|
||||
return menu.action({
|
||||
isSelected: data.format === format.type,
|
||||
prefix: html`<span
|
||||
style="font-size: var(--affine-font-base); scale: 1.2;"
|
||||
>${format.symbol}</span
|
||||
>`,
|
||||
name: format.label,
|
||||
select: () => {
|
||||
if (data.format === format.type) return;
|
||||
this.column.dataUpdate(() => ({
|
||||
format: format.type,
|
||||
}));
|
||||
},
|
||||
});
|
||||
}),
|
||||
],
|
||||
},
|
||||
});
|
||||
}),
|
||||
]
|
||||
: []),
|
||||
],
|
||||
},
|
||||
}),
|
||||
// Number format end
|
||||
menu.group({
|
||||
items: [
|
||||
|
||||
@@ -205,47 +205,39 @@ export class DatabaseHeaderColumn extends SignalWatcher(
|
||||
}
|
||||
|
||||
private popMenu(ele?: HTMLElement) {
|
||||
const enableNumberFormatting =
|
||||
this.tableViewManager.featureFlags$.value.enable_number_formatting;
|
||||
|
||||
popMenu(popupTargetFromElement(ele ?? this), {
|
||||
options: {
|
||||
items: [
|
||||
inputConfig(this.column),
|
||||
typeConfig(this.column),
|
||||
// Number format begin
|
||||
...(enableNumberFormatting
|
||||
? [
|
||||
menu.subMenu({
|
||||
name: 'Number Format',
|
||||
hide: () =>
|
||||
!this.column.dataUpdate ||
|
||||
this.column.type$.value !== 'number',
|
||||
options: {
|
||||
items: [
|
||||
numberFormatConfig(this.column),
|
||||
...numberFormats.map(format => {
|
||||
const data = this.column.data$.value;
|
||||
return menu.action({
|
||||
isSelected: data.format === format.type,
|
||||
prefix: html`<span
|
||||
style="font-size: var(--affine-font-base); scale: 1.2;"
|
||||
>${format.symbol}</span
|
||||
>`,
|
||||
name: format.label,
|
||||
select: () => {
|
||||
if (data.format === format.type) return;
|
||||
this.column.dataUpdate(() => ({
|
||||
format: format.type,
|
||||
}));
|
||||
},
|
||||
});
|
||||
}),
|
||||
],
|
||||
},
|
||||
menu.subMenu({
|
||||
name: 'Number Format',
|
||||
hide: () =>
|
||||
!this.column.dataUpdate || this.column.type$.value !== 'number',
|
||||
options: {
|
||||
items: [
|
||||
numberFormatConfig(this.column),
|
||||
...numberFormats.map(format => {
|
||||
const data = this.column.data$.value;
|
||||
return menu.action({
|
||||
isSelected: data.format === format.type,
|
||||
prefix: html`<span
|
||||
style="font-size: var(--affine-font-base); scale: 1.2;"
|
||||
>${format.symbol}</span
|
||||
>`,
|
||||
name: format.label,
|
||||
select: () => {
|
||||
if (data.format === format.type) return;
|
||||
this.column.dataUpdate(() => ({
|
||||
format: format.type,
|
||||
}));
|
||||
},
|
||||
});
|
||||
}),
|
||||
]
|
||||
: []),
|
||||
],
|
||||
},
|
||||
}),
|
||||
// Number format end
|
||||
menu.group({
|
||||
items: [
|
||||
|
||||
@@ -205,47 +205,39 @@ export class DatabaseHeaderColumn extends SignalWatcher(
|
||||
}
|
||||
|
||||
private popMenu(ele?: HTMLElement) {
|
||||
const enableNumberFormatting =
|
||||
this.tableViewManager.featureFlags$.value.enable_number_formatting;
|
||||
|
||||
popMenu(popupTargetFromElement(ele ?? this), {
|
||||
options: {
|
||||
items: [
|
||||
inputConfig(this.column),
|
||||
typeConfig(this.column),
|
||||
// Number format begin
|
||||
...(enableNumberFormatting
|
||||
? [
|
||||
menu.subMenu({
|
||||
name: 'Number Format',
|
||||
hide: () =>
|
||||
!this.column.dataUpdate ||
|
||||
this.column.type$.value !== 'number',
|
||||
options: {
|
||||
items: [
|
||||
numberFormatConfig(this.column),
|
||||
...numberFormats.map(format => {
|
||||
const data = this.column.data$.value;
|
||||
return menu.action({
|
||||
isSelected: data.format === format.type,
|
||||
prefix: html`<span
|
||||
style="font-size: var(--affine-font-base); scale: 1.2;"
|
||||
>${format.symbol}</span
|
||||
>`,
|
||||
name: format.label,
|
||||
select: () => {
|
||||
if (data.format === format.type) return;
|
||||
this.column.dataUpdate(() => ({
|
||||
format: format.type,
|
||||
}));
|
||||
},
|
||||
});
|
||||
}),
|
||||
],
|
||||
},
|
||||
menu.subMenu({
|
||||
name: 'Number Format',
|
||||
hide: () =>
|
||||
!this.column.dataUpdate || this.column.type$.value !== 'number',
|
||||
options: {
|
||||
items: [
|
||||
numberFormatConfig(this.column),
|
||||
...numberFormats.map(format => {
|
||||
const data = this.column.data$.value;
|
||||
return menu.action({
|
||||
isSelected: data.format === format.type,
|
||||
prefix: html`<span
|
||||
style="font-size: var(--affine-font-base); scale: 1.2;"
|
||||
>${format.symbol}</span
|
||||
>`,
|
||||
name: format.label,
|
||||
select: () => {
|
||||
if (data.format === format.type) return;
|
||||
this.column.dataUpdate(() => ({
|
||||
format: format.type,
|
||||
}));
|
||||
},
|
||||
});
|
||||
}),
|
||||
]
|
||||
: []),
|
||||
],
|
||||
},
|
||||
}),
|
||||
// Number format end
|
||||
menu.group({
|
||||
items: [
|
||||
|
||||
@@ -337,6 +337,7 @@ export const popViewOptions = (
|
||||
const reopen = () => {
|
||||
popViewOptions(target, dataViewLogic);
|
||||
};
|
||||
let handler: ReturnType<typeof popMenu>;
|
||||
const items: MenuConfig[] = [];
|
||||
items.push(
|
||||
menu.input({
|
||||
@@ -350,16 +351,9 @@ export const popViewOptions = (
|
||||
items.push(
|
||||
menu.group({
|
||||
items: [
|
||||
menu.action({
|
||||
name: 'Layout',
|
||||
postfix: html` <div
|
||||
style="font-size: 14px;text-transform: capitalize;"
|
||||
>
|
||||
${view.type}
|
||||
</div>
|
||||
${ArrowRightSmallIcon()}`,
|
||||
select: () => {
|
||||
const viewTypes = view.manager.viewMetas.map<MenuConfig>(meta => {
|
||||
menu => {
|
||||
const viewTypeItems = menu.renderItems(
|
||||
view.manager.viewMetas.map<MenuConfig>(meta => {
|
||||
return menu => {
|
||||
if (!menu.search(meta.model.defaultName)) {
|
||||
return;
|
||||
@@ -379,10 +373,10 @@ export const popViewOptions = (
|
||||
? 'var(--affine-text-emphasis-color)'
|
||||
: 'var(--affine-text-secondary-color)',
|
||||
});
|
||||
const data: MenuButtonData = {
|
||||
const buttonData: MenuButtonData = {
|
||||
content: () => html`
|
||||
<div
|
||||
style="color:var(--affine-text-emphasis-color);width:100%;display: flex;flex-direction: column;align-items: center;justify-content: center;padding: 6px 16px;white-space: nowrap"
|
||||
style="width:100%;display: flex;flex-direction: column;align-items: center;justify-content: center;padding: 6px 16px;white-space: nowrap"
|
||||
>
|
||||
<div style="${iconStyle}">
|
||||
${renderUniLit(meta.renderer.icon)}
|
||||
@@ -392,7 +386,7 @@ export const popViewOptions = (
|
||||
`,
|
||||
select: () => {
|
||||
const id = view.manager.currentViewId$.value;
|
||||
if (!id) {
|
||||
if (!id || meta.type === view.type) {
|
||||
return;
|
||||
}
|
||||
view.manager.viewChangeType(id, meta.type);
|
||||
@@ -403,55 +397,35 @@ export const popViewOptions = (
|
||||
const containerStyle = styleMap({
|
||||
flex: '1',
|
||||
});
|
||||
return html` <affine-menu-button
|
||||
return html`<affine-menu-button
|
||||
style="${containerStyle}"
|
||||
.data="${data}"
|
||||
.data="${buttonData}"
|
||||
.menu="${menu}"
|
||||
></affine-menu-button>`;
|
||||
};
|
||||
});
|
||||
const subHandler = popMenu(target, {
|
||||
options: {
|
||||
title: {
|
||||
onBack: reopen,
|
||||
text: 'Layout',
|
||||
},
|
||||
items: [
|
||||
menu => {
|
||||
const result = menu.renderItems(viewTypes);
|
||||
if (result.length) {
|
||||
return html` <div style="display: flex">${result}</div>`;
|
||||
}
|
||||
return html``;
|
||||
},
|
||||
// menu.toggleSwitch({
|
||||
// name: 'Show block icon',
|
||||
// on: true,
|
||||
// onChange: value => {
|
||||
// console.log(value);
|
||||
// },
|
||||
// }),
|
||||
// menu.toggleSwitch({
|
||||
// name: 'Show Vertical lines',
|
||||
// on: true,
|
||||
// onChange: value => {
|
||||
// console.log(value);
|
||||
// },
|
||||
// }),
|
||||
],
|
||||
},
|
||||
middleware: [
|
||||
autoPlacement({
|
||||
allowedPlacements: ['bottom-start', 'top-start'],
|
||||
}),
|
||||
offset({ mainAxis: 15, crossAxis: -162 }),
|
||||
shift({ crossAxis: true }),
|
||||
],
|
||||
});
|
||||
subHandler.menu.menuElement.style.minHeight = '550px';
|
||||
},
|
||||
prefix: LayoutIcon(),
|
||||
}),
|
||||
})
|
||||
);
|
||||
if (!viewTypeItems.length) {
|
||||
return html``;
|
||||
}
|
||||
return html`
|
||||
<div style="display:flex;align-items:center;gap:8px;padding:0 2px;">
|
||||
<div
|
||||
style="display:flex;align-items:center;color:var(--affine-icon-color);"
|
||||
>
|
||||
${LayoutIcon()}
|
||||
</div>
|
||||
<div
|
||||
style="font-size:14px;line-height:22px;color:var(--affine-text-secondary-color);"
|
||||
>
|
||||
Layout
|
||||
</div>
|
||||
</div>
|
||||
<div style="display:flex;gap:8px;margin-top:8px;">
|
||||
${viewTypeItems}
|
||||
</div>
|
||||
`;
|
||||
},
|
||||
],
|
||||
})
|
||||
);
|
||||
@@ -486,7 +460,6 @@ export const popViewOptions = (
|
||||
],
|
||||
})
|
||||
);
|
||||
let handler: ReturnType<typeof popMenu>;
|
||||
handler = popMenu(target, {
|
||||
options: {
|
||||
title: {
|
||||
|
||||
Reference in New Issue
Block a user