diff --git a/packages/frontend/component/src/ui/modal/modal.tsx b/packages/frontend/component/src/ui/modal/modal.tsx index bf14329fce..b8a23631c3 100644 --- a/packages/frontend/component/src/ui/modal/modal.tsx +++ b/packages/frontend/component/src/ui/modal/modal.tsx @@ -17,8 +17,8 @@ export interface ModalProps extends DialogProps { width?: CSSProperties['width']; height?: CSSProperties['height']; minHeight?: CSSProperties['minHeight']; - title?: string; - description?: string; + title?: React.ReactNode; + description?: React.ReactNode; withoutCloseButton?: boolean; portalOptions?: DialogPortalProps; diff --git a/packages/frontend/core/src/components/affine/page-properties/common.ts b/packages/frontend/core/src/components/affine/page-properties/common.ts index 6d168f3c85..298220150b 100644 --- a/packages/frontend/core/src/components/affine/page-properties/common.ts +++ b/packages/frontend/core/src/components/affine/page-properties/common.ts @@ -1,3 +1,4 @@ +import { cssVar } from '@toeverything/theme'; import { atom } from 'jotai'; import { createContext } from 'react'; @@ -6,3 +7,22 @@ import type { PagePropertiesManager } from './page-properties-manager'; // @ts-expect-error this should always be set export const managerContext = createContext(); export const pageInfoCollapsedAtom = atom(false); + +type TagColorHelper = T extends `paletteLine${infer Color}` ? Color : never; +type TagColorName = TagColorHelper[0]>; + +const tagColorIds: TagColorName[] = [ + 'Red', + 'Magenta', + 'Orange', + 'Yellow', + 'Green', + 'Teal', + 'Blue', + 'Purple', + 'Grey', +]; + +export const tagColors = tagColorIds.map( + color => [color, cssVar(`paletteLine${color}`)] as const +); diff --git a/packages/frontend/core/src/components/affine/page-properties/icons-mapping.tsx b/packages/frontend/core/src/components/affine/page-properties/icons-mapping.tsx index a0258476c7..c245f7ba83 100644 --- a/packages/frontend/core/src/components/affine/page-properties/icons-mapping.tsx +++ b/packages/frontend/core/src/components/affine/page-properties/icons-mapping.tsx @@ -4,67 +4,139 @@ import type { SVGProps } from 'react'; type IconType = (props: SVGProps) => JSX.Element; -// todo: this breaks tree-shaking, and we should fix it (using dynamic imports?) -const IconsMapping = icons; -export type PagePropertyIcon = keyof typeof IconsMapping; +// assume all exports in icons are icon Components +type LibIconComponentName = keyof typeof icons; -const excludedIcons: PagePropertyIcon[] = [ - 'YoutubeDuotoneIcon', - 'LinearLogoIcon', - 'RedditDuotoneIcon', - 'Logo2Icon', - 'Logo3Icon', - 'Logo4Icon', - 'InstagramDuotoneIcon', - 'TelegramDuotoneIcon', - 'TextBackgroundDuotoneIcon', -]; +type fromLibIconName = T extends `${infer N}Icon` + ? Uncapitalize + : never; -export const iconNames = Object.keys(IconsMapping).filter( - icon => !excludedIcons.includes(icon as PagePropertyIcon) -) as PagePropertyIcon[]; +export const iconNames = [ + 'ai', + 'email', + 'text', + 'dateTime', + 'keyboard', + 'pen', + 'account', + 'embedWeb', + 'layer', + 'pin', + 'appearance', + 'eraser', + 'layout', + 'presentation', + 'bookmark', + 'exportToHtml', + 'lightMode', + 'progress', + 'bulletedList', + 'exportToMarkdown', + 'link', + 'publish', + 'camera', + 'exportToPdf', + 'linkedEdgeless', + 'quote', + 'checkBoxCheckLinear', + 'exportToPng', + 'linkedPage', + 'save', + 'cloudWorkspace', + 'exportToSvg', + 'localData', + 'shape', + 'code', + 'favorite', + 'localWorkspace', + 'style', + 'codeBlock', + 'file', + 'lock', + 'tag', + 'collaboration', + 'folder', + 'multiSelect', + 'tags', + 'colorPicker', + 'frame', + 'new', + 'today', + 'contactWithUs', + 'grid', + 'now', + 'upgrade', + 'darkMode', + 'grouping', + 'number', + 'userGuide', + 'databaseKanbanView', + 'image', + 'numberedList', + 'view', + 'databaseListView', + 'inbox', + 'other', + 'viewLayers', + 'databaseTableView', + 'info', + 'page', + 'attachment', + 'delete', + 'issue', + 'paste', + 'heartbreak', + 'edgeless', + 'journal', + 'payment', +] as const satisfies fromLibIconName[]; + +export type PagePropertyIcon = (typeof iconNames)[number]; export const getDefaultIconName = ( type: PagePropertyType ): PagePropertyIcon => { switch (type) { case 'text': - return 'TextIcon'; + return 'text'; case 'tags': - return 'TagIcon'; + return 'tag'; case 'date': - return 'DateTimeIcon'; + return 'dateTime'; case 'progress': - return 'ProgressIcon'; + return 'progress'; case 'checkbox': - return 'CheckBoxCheckLinearIcon'; + return 'checkBoxCheckLinear'; case 'number': - return 'NumberIcon'; + return 'number'; default: - return 'TextIcon'; + return 'text'; } }; -// fixme: this function may break if icons are imported twice -export const IconToIconName = (icon: IconType) => { - const iconKey = Object.entries(IconsMapping).find(([_, candidate]) => { - return candidate === icon; - })?.[0]; - return iconKey; -}; - export const getSafeIconName = ( iconName: string, type?: PagePropertyType ): PagePropertyIcon => { - return Object.hasOwn(IconsMapping, iconName) + return iconNames.includes(iconName as any) ? (iconName as PagePropertyIcon) : getDefaultIconName(type || PagePropertyType.Text); }; +const nameToComponentName = ( + iconName: PagePropertyIcon +): LibIconComponentName => { + const capitalize = (s: string) => s.charAt(0).toUpperCase() + s.slice(1); + return `${capitalize(iconName)}Icon` as LibIconComponentName; +}; + export const nameToIcon = ( iconName: string, type?: PagePropertyType ): IconType => { - return IconsMapping[getSafeIconName(iconName, type)]; + const Icon = icons[nameToComponentName(getSafeIconName(iconName, type))]; + if (!Icon) { + throw new Error(`Icon ${iconName} not found`); + } + return Icon; }; diff --git a/packages/frontend/core/src/components/affine/page-properties/icons-selector.tsx b/packages/frontend/core/src/components/affine/page-properties/icons-selector.tsx index bdc231ab85..a0ba47d48c 100644 --- a/packages/frontend/core/src/components/affine/page-properties/icons-selector.tsx +++ b/packages/frontend/core/src/components/affine/page-properties/icons-selector.tsx @@ -6,7 +6,7 @@ import { useEffect, useRef } from 'react'; import { iconNames, nameToIcon, type PagePropertyIcon } from './icons-mapping'; import * as styles from './icons-selector.css'; -const iconsPerRow = 10; +const iconsPerRow = 6; const iconRows = chunk(iconNames, iconsPerRow); @@ -46,15 +46,13 @@ export const IconsSelectorPanel = ({ const Icon = nameToIcon(iconName); return (
onSelectedChange(iconName)} key={iconName} className={styles.iconButton} data-name={iconName} data-active={selected === iconName} > - onSelectedChange(iconName)} - /> +
); })} diff --git a/packages/frontend/core/src/components/affine/page-properties/menu-items.tsx b/packages/frontend/core/src/components/affine/page-properties/menu-items.tsx index 97cef77c95..e26b0a5288 100644 --- a/packages/frontend/core/src/components/affine/page-properties/menu-items.tsx +++ b/packages/frontend/core/src/components/affine/page-properties/menu-items.tsx @@ -9,10 +9,11 @@ import { import type { PageInfoCustomPropertyMeta } from '@affine/core/modules/workspace/properties/schema'; import { useAFFiNEI18N } from '@affine/i18n/hooks'; import { - type ChangeEventHandler, cloneElement, isValidElement, + type KeyboardEventHandler, type MouseEventHandler, + useCallback, } from 'react'; import { @@ -81,12 +82,29 @@ export const EditPropertyNameMenuItem = ({ onNameChange, onIconChange, }: { - onNameBlur: ChangeEventHandler; - onNameChange: (name: string) => void; + onNameBlur: (e: string) => void; + onNameChange: (e: string) => void; onIconChange: (icon: PagePropertyIcon) => void; property: PageInfoCustomPropertyMeta; }) => { const iconName = getSafeIconName(property.icon, property.type); + const onKeyDown: KeyboardEventHandler = useCallback( + e => { + e.stopPropagation(); + if (e.key === 'Enter') { + e.preventDefault(); + onBlur(e.currentTarget.value); + } + }, + [onBlur] + ); + const handleBlur = useCallback( + (e: React.FocusEvent) => { + onBlur(e.target.value); + }, + [onBlur] + ); + const t = useAFFiNEI18N(); return (
@@ -96,11 +114,10 @@ export const EditPropertyNameMenuItem = ({ />
); diff --git a/packages/frontend/core/src/components/affine/page-properties/page-properties-manager.ts b/packages/frontend/core/src/components/affine/page-properties/page-properties-manager.ts index da2f0b17d4..26e598ca4e 100644 --- a/packages/frontend/core/src/components/affine/page-properties/page-properties-manager.ts +++ b/packages/frontend/core/src/components/affine/page-properties/page-properties-manager.ts @@ -56,7 +56,7 @@ export class PagePropertiesMetaManager { return this.adapter.schema.pageProperties.custom; } - getOrderedCustomPropertiesSchema() { + getOrderedPropertiesSchema() { return Object.values(this.customPropertiesSchema).sort( (a, b) => a.order - b.order ); @@ -66,7 +66,7 @@ export class PagePropertiesMetaManager { return !!this.customPropertiesSchema[id]; } - validateCustomPropertyValue(id: string, value?: any) { + validatePropertyValue(id: string, value?: any) { if (!value) { // value is optional in all cases? return true; @@ -79,7 +79,7 @@ export class PagePropertiesMetaManager { return validatePropertyValue(type, value); } - addCustomPropertyMeta(schema: { + addPropertyMeta(schema: { name: string; type: PagePropertyType; icon?: string; @@ -103,10 +103,7 @@ export class PagePropertiesMetaManager { return property; } - updateCustomPropertyMeta( - id: string, - opt: Partial - ) { + updatePropertyMeta(id: string, opt: Partial) { if (!this.checkPropertyExists(id)) { logger.warn(`property ${id} not found`); return; @@ -118,13 +115,13 @@ export class PagePropertiesMetaManager { return this.customPropertiesSchema[id]?.required; } - removeCustomPropertyMeta(id: string) { + removePropertyMeta(id: string) { // should warn if the property is in use delete this.customPropertiesSchema[id]; } // returns page schema properties -> related page - getCustomPropertyStatistics() { + getPropertyStatistics() { const mapping = new Map>(); for (const page of this.adapter.workspace.blockSuiteWorkspace.pages.values()) { const properties = this.adapter.getPageProperties(page.id); @@ -147,12 +144,13 @@ export class PagePropertiesManager { this.metaManager = new PagePropertiesMetaManager(this.adapter); } + // prevent infinite loop private ensuring = false; ensureRequiredProperties() { if (this.ensuring) return; this.ensuring = true; this.transact(() => { - this.metaManager.getOrderedCustomPropertiesSchema().forEach(property => { + this.metaManager.getOrderedPropertiesSchema().forEach(property => { if (property.required && !this.hasCustomProperty(property.id)) { this.addCustomProperty(property.id); } @@ -240,7 +238,7 @@ export class PagePropertiesManager { return; } - if (!this.metaManager.validateCustomPropertyValue(id, value)) { + if (!this.metaManager.validatePropertyValue(id, value)) { logger.warn(`property ${id} value ${value} is invalid`); return; } @@ -273,7 +271,7 @@ export class PagePropertiesManager { } if ( opt.value !== undefined && - !this.metaManager.validateCustomPropertyValue(id, opt.value) + !this.metaManager.validatePropertyValue(id, opt.value) ) { logger.warn(`property ${id} value ${opt.value} is invalid`); return; @@ -282,7 +280,7 @@ export class PagePropertiesManager { } get updateCustomPropertyMeta() { - return this.metaManager.updateCustomPropertyMeta.bind(this.metaManager); + return this.metaManager.updatePropertyMeta.bind(this.metaManager); } get isPropertyRequired() { diff --git a/packages/frontend/core/src/components/affine/page-properties/property-row-value-renderer.tsx b/packages/frontend/core/src/components/affine/page-properties/property-row-value-renderer.tsx index 902d295f5f..24dd60e5d5 100644 --- a/packages/frontend/core/src/components/affine/page-properties/property-row-value-renderer.tsx +++ b/packages/frontend/core/src/components/affine/page-properties/property-row-value-renderer.tsx @@ -1,4 +1,6 @@ import { Checkbox, DatePicker, Menu } from '@affine/component'; +import { useBlockSuitePageMeta } from '@affine/core/hooks/use-block-suite-page-meta'; +import { WorkspaceLegacyProperties } from '@affine/core/modules/workspace'; import type { PageInfoCustomProperty, PageInfoCustomPropertyMeta, @@ -6,11 +8,14 @@ import type { } from '@affine/core/modules/workspace/properties/schema'; import { timestampToLocalDate } from '@affine/core/utils'; import { useAFFiNEI18N } from '@affine/i18n/hooks'; +import { assertExists } from '@blocksuite/global/utils'; +import { Page, useLiveData, useService, Workspace } from '@toeverything/infra'; import { noop } from 'lodash-es'; import { type ChangeEventHandler, useCallback, useContext } from 'react'; import { managerContext } from './common'; import * as styles from './styles.css'; +import { TagsInlineEditor } from './tags-inline-editor'; interface PropertyRowValueProps { property: PageInfoCustomProperty; @@ -108,6 +113,38 @@ export const TextValue = ({ property, meta }: PropertyRowValueProps) => { ); }; +export const TagsValue = () => { + const workspace = useService(Workspace); + const page = useService(Page); + const blockSuiteWorkspace = workspace.blockSuiteWorkspace; + const pageMetas = useBlockSuitePageMeta(blockSuiteWorkspace); + const legacyProperties = useService(WorkspaceLegacyProperties); + const options = useLiveData(legacyProperties.tagOptions$); + + const pageMeta = pageMetas.find(x => x.id === page.id); + assertExists(pageMeta, 'pageMeta should exist'); + const tagIds = pageMeta.tags; + const t = useAFFiNEI18N(); + const onChange = useCallback( + (tags: string[]) => { + legacyProperties.updatePageTags(page.id, tags); + }, + [legacyProperties, page.id] + ); + + return ( + + ); +}; + export const propertyValueRenderers: Record< PagePropertyType, typeof DateValue @@ -117,6 +154,6 @@ export const propertyValueRenderers: Record< text: TextValue, number: TextValue, // todo: fix following - tags: TextValue, + tags: TagsValue, progress: TextValue, }; diff --git a/packages/frontend/core/src/components/affine/page-properties/styles.css.ts b/packages/frontend/core/src/components/affine/page-properties/styles.css.ts index c62c70ceec..3c2b49a6e1 100644 --- a/packages/frontend/core/src/components/affine/page-properties/styles.css.ts +++ b/packages/frontend/core/src/components/affine/page-properties/styles.css.ts @@ -99,10 +99,11 @@ export const tableBodyRoot = style({ gap: 8, }); -export const tableBody = style({ +export const tableBodySortable = style({ display: 'flex', flexDirection: 'column', gap: 4, + position: 'relative', }); export const addPropertyButton = style({ @@ -143,7 +144,14 @@ export const propertyRow = style({ }, }); -export const draggableRow = style({ +export const tagsPropertyRow = style([ + propertyRow, + { + marginBottom: -4, + }, +]); + +export const draggableItem = style({ cursor: 'pointer', selectors: { '&:before': { @@ -184,7 +192,7 @@ export const draggableRow = style({ }); export const draggableRowSetting = style([ - draggableRow, + draggableItem, { selectors: { '&:active:before': { @@ -200,31 +208,43 @@ export const draggableRowSetting = style([ export const propertyRowCell = style({ display: 'flex', flexDirection: 'row', - alignItems: 'center', + alignItems: 'flex-start', position: 'relative', - padding: 6, borderRadius: 4, - cursor: 'pointer', fontSize: cssVar('fontSm'), + lineHeight: '20px', userSelect: 'none', ':focus-visible': { outline: 'none', }, - ':hover': { - backgroundColor: cssVar('hoverColor'), - }, }); +export const editablePropertyRowCell = style([ + propertyRowCell, + { + cursor: 'pointer', + ':hover': { + backgroundColor: cssVar('hoverColor'), + }, + }, +]); + export const propertyRowNameCell = style([ propertyRowCell, - draggableRow, { + padding: 6, color: cssVar('textSecondaryColor'), width: propertyNameCellWidth, gap: 6, }, ]); +export const sortablePropertyRowNameCell = style([ + propertyRowNameCell, + draggableItem, + editablePropertyRowCell, +]); + export const propertyRowIconContainer = style({ display: 'flex', alignItems: 'center', @@ -234,6 +254,14 @@ export const propertyRowIconContainer = style({ color: 'inherit', }); +export const propertyRowNameContainer = style({ + display: 'flex', + flexDirection: 'row', + alignItems: 'center', + gap: 6, + flexGrow: 1, +}); + export const propertyRowName = style({ flexGrow: 1, overflow: 'hidden', @@ -244,7 +272,9 @@ export const propertyRowName = style({ export const propertyRowValueCell = style([ propertyRowCell, + editablePropertyRowCell, { + padding: '6px 8px', border: `1px solid transparent`, color: cssVar('textPrimaryColor'), ':focus': { @@ -257,6 +287,9 @@ export const propertyRowValueCell = style([ '&[data-empty="true"]': { color: cssVar('placeholderColor'), }, + '&[data-readonly=true]': { + pointerEvents: 'none', + }, }, flex: 1, }, diff --git a/packages/frontend/core/src/components/affine/page-properties/table.tsx b/packages/frontend/core/src/components/affine/page-properties/table.tsx index 2d550f3ddd..84dee7f325 100644 --- a/packages/frontend/core/src/components/affine/page-properties/table.tsx +++ b/packages/frontend/core/src/components/affine/page-properties/table.tsx @@ -4,6 +4,7 @@ import { Menu, MenuIcon, MenuItem, + type MenuProps, Tooltip, } from '@affine/component'; import { useCurrentWorkspacePropertiesAdapter } from '@affine/core/hooks/use-affine-adapter'; @@ -22,6 +23,7 @@ import { InvisibleIcon, MoreHorizontalIcon, PlusIcon, + TagsIcon, ToggleExpandIcon, ViewIcon, } from '@blocksuite/icons'; @@ -42,10 +44,9 @@ import { arrayMove, SortableContext, useSortable } from '@dnd-kit/sortable'; import * as Collapsible from '@radix-ui/react-collapsible'; import clsx from 'clsx'; import { use } from 'foxact/use'; -import { useAtom, useAtomValue } from 'jotai'; +import { atom, useAtom, useAtomValue, useSetAtom } from 'jotai'; import type React from 'react'; import { - type ChangeEventHandler, type CSSProperties, type MouseEvent, type MouseEventHandler, @@ -75,7 +76,10 @@ import { newPropertyTypes, PagePropertiesManager, } from './page-properties-manager'; -import { propertyValueRenderers } from './property-row-value-renderer'; +import { + propertyValueRenderers, + TagsValue, +} from './property-row-value-renderer'; import * as styles from './styles.css'; type PagePropertiesSettingsPopupProps = PropsWithChildren<{ @@ -87,10 +91,17 @@ const Divider = () =>
; type PropertyVisibility = PageInfoCustomProperty['visibility']; +const editingPropertyAtom = atom(null); + +const modifiers = [restrictToParentElement, restrictToVerticalAxis]; const SortableProperties = ({ children }: PropsWithChildren) => { const manager = useContext(managerContext); - const properties = manager.getOrderedCustomProperties(); - const readonly = manager.readonly; + const properties = useMemo( + () => manager.getOrderedCustomProperties(), + [manager] + ); + const editingItem = useAtomValue(editingPropertyAtom); + const draggable = !manager.readonly && !editingItem; const sensors = useSensors( useSensor(PointerSensor, { activationConstraint: { @@ -100,7 +111,7 @@ const SortableProperties = ({ children }: PropsWithChildren) => { ); const onDragEnd = useCallback( (event: DragEndEvent) => { - if (readonly) { + if (!draggable) { return; } const { active, over } = event; @@ -118,11 +129,13 @@ const SortableProperties = ({ children }: PropsWithChildren) => { }); } }, - [manager, properties, readonly] + [manager, properties, draggable] ); return ( - {children} + + {children} + ); }; @@ -262,6 +275,11 @@ const VisibilityModeSelector = ({ rootOptions={{ open: required ? false : undefined, }} + contentOptions={{ + onClick(e) { + e.stopPropagation(); + }, + }} >
{required ? ( @@ -287,7 +305,7 @@ export const PagePropertiesSettingsPopup = ({ const menuItems = useMemo(() => { const options: MenuItemOption[] = []; options.push( -
+
{t['com.affine.page-properties.settings.title']()}
); @@ -319,7 +337,18 @@ export const PagePropertiesSettingsPopup = ({ return renderMenuItemOptions(options); }, [manager, properties, t]); - return {children}; + return ( + + {children} + + ); }; type PageBacklinksPopupProps = PropsWithChildren<{ @@ -334,6 +363,11 @@ export const PageBacklinksPopup = ({ return ( {backlinks.map(pageId => ( @@ -359,7 +393,7 @@ interface PagePropertyRowNameProps { onFinishEditing: () => void; } -export const PagePropertyRowName = ({ +export const PagePropertyRowNameMenu = ({ editing, meta, property, @@ -386,11 +420,10 @@ export const PagePropertyRowName = ({ property.id, ]); const t = useAFFiNEI18N(); - const handleNameBlur: ChangeEventHandler = useCallback( - e => { - e.stopPropagation(); + const handleNameBlur = useCallback( + (v: string) => { manager.updateCustomPropertyMeta(meta.id, { - name: e.target.value, + name: v, }); }, [manager, meta.id] @@ -486,6 +519,9 @@ export const PagePropertyRowName = ({ }} contentOptions={{ onInteractOutside: handleFinishEditing, + onClick(e) { + e.stopPropagation(); + }, }} items={menuItems} > @@ -569,7 +605,7 @@ export const PagePropertiesTableHeader = ({
{t['com.affine.page-properties.page-info']()}
- {(collapsed && properties.length === 0) || manager.readonly ? null : ( + {properties.length === 0 || manager.readonly ? null : ( } /> @@ -591,17 +627,6 @@ export const PagePropertiesTableHeader = ({ ); }; -const usePagePropertiesManager = (page: Page) => { - // the workspace properties adapter adapter is reactive, - // which means it's reference will change when any of the properties change - // also it will trigger a re-render of the component - const adapter = useCurrentWorkspacePropertiesAdapter(); - const manager = useMemo(() => { - return new PagePropertiesManager(adapter, page.id); - }, [adapter, page.id]); - return manager; -}; - interface PagePropertyRowProps { property: PageInfoCustomProperty; style?: React.CSSProperties; @@ -617,25 +642,22 @@ const PagePropertyRow = ({ property }: PagePropertyRowProps) => { const name = meta.name; const ValueRenderer = propertyValueRenderers[meta.type]; const [editingMeta, setEditingMeta] = useState(false); + const setEditingItem = useSetAtom(editingPropertyAtom); const handleEditMeta = useCallback(() => { if (!manager.readonly) { setEditingMeta(true); } - }, [manager.readonly]); + setEditingItem(property.id); + }, [manager.readonly, property.id, setEditingItem]); const handleFinishEditingMeta = useCallback(() => { setEditingMeta(false); - }, []); + setEditingItem(null); + }, [setEditingItem]); return ( - + {({ attributes, listeners }) => ( <> - {
-
- +
+
+ +
+
{name}
-
{name}
- + )} @@ -660,13 +685,35 @@ const PagePropertyRow = ({ property }: PagePropertyRowProps) => { ); }; +const PageTagsRow = () => { + const t = useAFFiNEI18N(); + return ( +
+
+
+
+ +
+
{t['Tags']()}
+
+
+ +
+ ); +}; + interface PagePropertiesTableBodyProps { className?: string; style?: React.CSSProperties; } -const modifiers = [restrictToParentElement, restrictToVerticalAxis]; - // 🏷️ Tags (⋅ xxx) (⋅ yyy) // #️⃣ Number 123456 // + Add a property @@ -684,7 +731,8 @@ export const PagePropertiesTableBody = ({ className={clsx(styles.tableBodyRoot, className)} style={style} > -
+ +
{properties .filter( @@ -735,13 +783,13 @@ export const PagePropertiesCreatePropertyMenuItems = ({ e: React.MouseEvent, option: { type: PagePropertyType; name: string; icon: string } ) => { - const schemaList = metaManager.getOrderedCustomPropertiesSchema(); + const schemaList = metaManager.getOrderedPropertiesSchema(); const nameExists = schemaList.some(meta => meta.name === option.name); const allNames = schemaList.map(meta => meta.name); const name = nameExists ? findNextDefaultName(option.name, allNames) : option.name; - const { id } = metaManager.addCustomPropertyMeta({ + const { id } = metaManager.addPropertyMeta({ name, icon: option.icon, type: option.type, @@ -791,7 +839,7 @@ const PagePropertiesAddPropertyMenuItems = ({ const manager = useContext(managerContext); const t = useAFFiNEI18N(); - const metaList = manager.metaManager.getOrderedCustomPropertiesSchema(); + const metaList = manager.metaManager.getOrderedPropertiesSchema(); const nonRequiredMetaList = metaList.filter(meta => !meta.required); const isChecked = useCallback( (m: string) => { @@ -859,23 +907,36 @@ export const PagePropertiesAddProperty = () => { e.preventDefault(); setAdding(prev => !prev); }, []); - const handleCreated = useCallback( - (e: React.MouseEvent, id: string) => { + + const menuOptions = useMemo(() => { + const handleCreated = (e: React.MouseEvent, id: string) => { toggleAdding(e); manager.addCustomProperty(id); - }, - [manager, toggleAdding] - ); - const items = adding ? ( - - ) : ( - - ); + }; + const items = adding ? ( + + ) : ( + + ); + + return { + contentOptions: { + onClick(e) { + e.stopPropagation(); + }, + }, + rootOptions: { + onOpenChange: () => setAdding(true), + }, + items, + } satisfies Partial; + }, [adding, manager, toggleAdding]); + return ( - setAdding(true) }} items={items}> +