mirror of
https://github.com/toeverything/AFFiNE.git
synced 2025-01-02 11:27:26 +03:00
parent
4977055a2e
commit
ef82b9d3e7
@ -64,7 +64,7 @@ export class DocPropertiesStore extends Store {
|
||||
createDocPropertyInfo(
|
||||
config: Omit<DocCustomPropertyInfo, 'id'> & { id?: string }
|
||||
) {
|
||||
return this.dbService.db.docCustomPropertyInfo.create(config).id;
|
||||
return this.dbService.db.docCustomPropertyInfo.create(config);
|
||||
}
|
||||
|
||||
removeDocPropertyInfo(id: string) {
|
||||
|
@ -3,11 +3,16 @@ import {
|
||||
useConfirmModal,
|
||||
useLitPortalFactory,
|
||||
} from '@affine/component';
|
||||
import type {
|
||||
DatabaseRow,
|
||||
DatabaseValueCell,
|
||||
} from '@affine/core/modules/doc-info/types';
|
||||
import { EditorService } from '@affine/core/modules/editor';
|
||||
import { EditorSettingService } from '@affine/core/modules/editor-setting';
|
||||
import { JournalService } from '@affine/core/modules/journal';
|
||||
import { toURLSearchParams } from '@affine/core/modules/navigation';
|
||||
import { PeekViewService } from '@affine/core/modules/peek-view/services/peek-view';
|
||||
import track from '@affine/track';
|
||||
import type { DocMode } from '@blocksuite/affine/blocks';
|
||||
import {
|
||||
DocTitle,
|
||||
@ -16,6 +21,7 @@ import {
|
||||
} from '@blocksuite/affine/presets';
|
||||
import type { Doc } from '@blocksuite/affine/store';
|
||||
import {
|
||||
type DocCustomPropertyInfo,
|
||||
DocService,
|
||||
DocsService,
|
||||
FeatureFlagService,
|
||||
@ -239,6 +245,28 @@ export const BlocksuiteDocEditor = forwardRef<
|
||||
editorSettingService.editorSetting.settings$.selector(s => s.displayDocInfo)
|
||||
);
|
||||
|
||||
const onPropertyChange = useCallback((property: DocCustomPropertyInfo) => {
|
||||
track.doc.inlineDocInfo.property.editProperty({
|
||||
type: property.type,
|
||||
});
|
||||
}, []);
|
||||
|
||||
const onPropertyAdded = useCallback((property: DocCustomPropertyInfo) => {
|
||||
track.doc.inlineDocInfo.property.addProperty({
|
||||
type: property.type,
|
||||
control: 'at menu',
|
||||
});
|
||||
}, []);
|
||||
|
||||
const onDatabasePropertyChange = useCallback(
|
||||
(_row: DatabaseRow, cell: DatabaseValueCell) => {
|
||||
track.doc.inlineDocInfo.databaseProperty.editProperty({
|
||||
type: cell.property.type$.value,
|
||||
});
|
||||
},
|
||||
[]
|
||||
);
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className={styles.affineDocViewport} style={{ height: '100%' }}>
|
||||
@ -248,7 +276,12 @@ export const BlocksuiteDocEditor = forwardRef<
|
||||
<BlocksuiteEditorJournalDocTitle page={page} />
|
||||
)}
|
||||
{!shared && displayDocInfo ? (
|
||||
<DocPropertiesTable defaultOpenProperty={defaultOpenProperty} />
|
||||
<DocPropertiesTable
|
||||
onDatabasePropertyChange={onDatabasePropertyChange}
|
||||
onPropertyChange={onPropertyChange}
|
||||
onPropertyAdded={onPropertyAdded}
|
||||
defaultOpenProperty={defaultOpenProperty}
|
||||
/>
|
||||
) : null}
|
||||
<adapted.DocEditor
|
||||
className={styles.docContainer}
|
||||
|
@ -1,7 +1,12 @@
|
||||
import { MenuItem, MenuSeparator } from '@affine/component';
|
||||
import { generateUniqueNameInSequence } from '@affine/core/utils/unique-name';
|
||||
import { useI18n } from '@affine/i18n';
|
||||
import { DocsService, useLiveData, useService } from '@toeverything/infra';
|
||||
import {
|
||||
type DocCustomPropertyInfo,
|
||||
DocsService,
|
||||
useLiveData,
|
||||
useService,
|
||||
} from '@toeverything/infra';
|
||||
import { useCallback } from 'react';
|
||||
|
||||
import {
|
||||
@ -15,7 +20,7 @@ export const CreatePropertyMenuItems = ({
|
||||
onCreated,
|
||||
}: {
|
||||
at?: 'before' | 'after';
|
||||
onCreated?: (propertyId: string) => void;
|
||||
onCreated?: (property: DocCustomPropertyInfo) => void;
|
||||
}) => {
|
||||
const t = useI18n();
|
||||
const docsService = useService(DocsService);
|
||||
@ -36,13 +41,13 @@ export const CreatePropertyMenuItems = ({
|
||||
? generateUniqueNameInSequence(option.name, allNames)
|
||||
: option.name;
|
||||
const uniqueId = typeDefined.uniqueId;
|
||||
const newPropertyId = propertyList.createProperty({
|
||||
const newProperty = propertyList.createProperty({
|
||||
id: uniqueId,
|
||||
name,
|
||||
type: option.type,
|
||||
index: propertyList.indexAt(at),
|
||||
});
|
||||
onCreated?.(newPropertyId);
|
||||
onCreated?.(newProperty);
|
||||
},
|
||||
[at, onCreated, propertyList, properties]
|
||||
);
|
||||
|
@ -1,6 +1,7 @@
|
||||
import { Divider, IconButton, Tooltip } from '@affine/component';
|
||||
import { generateUniqueNameInSequence } from '@affine/core/utils/unique-name';
|
||||
import { useI18n } from '@affine/i18n';
|
||||
import track from '@affine/track';
|
||||
import { PlusIcon } from '@blocksuite/icons/rc';
|
||||
import {
|
||||
Content as CollapsibleContent,
|
||||
@ -41,13 +42,17 @@ export const DocPropertySidebar = () => {
|
||||
const name = nameExists
|
||||
? generateUniqueNameInSequence(option.name, allNames)
|
||||
: option.name;
|
||||
const newPropertyId = propertyList.createProperty({
|
||||
const newProperty = propertyList.createProperty({
|
||||
id: typeDefined.uniqueId,
|
||||
name,
|
||||
type: option.type,
|
||||
index: propertyList.indexAt('after'),
|
||||
});
|
||||
setNewPropertyId(newPropertyId);
|
||||
setNewPropertyId(newProperty.id);
|
||||
track.doc.sidepanel.property.addProperty({
|
||||
control: 'property list',
|
||||
type: option.type,
|
||||
});
|
||||
},
|
||||
[propertyList, properties]
|
||||
);
|
||||
|
@ -9,6 +9,10 @@ import {
|
||||
useDropTarget,
|
||||
} from '@affine/component';
|
||||
import { DocDatabaseBacklinkInfo } from '@affine/core/modules/doc-info';
|
||||
import type {
|
||||
DatabaseRow,
|
||||
DatabaseValueCell,
|
||||
} from '@affine/core/modules/doc-info/types';
|
||||
import { WorkbenchService } from '@affine/core/modules/workbench';
|
||||
import { ViewService } from '@affine/core/modules/workbench/services/view';
|
||||
import type { AffineDNDData } from '@affine/core/types/dnd';
|
||||
@ -26,7 +30,6 @@ import {
|
||||
} from '@toeverything/infra';
|
||||
import clsx from 'clsx';
|
||||
import type React from 'react';
|
||||
import type { HTMLProps } from 'react';
|
||||
import { forwardRef, useCallback, useState } from 'react';
|
||||
|
||||
import { DocPropertyIcon } from './icons/doc-property-icon';
|
||||
@ -47,6 +50,13 @@ export type DefaultOpenProperty =
|
||||
|
||||
export interface DocPropertiesTableProps {
|
||||
defaultOpenProperty?: DefaultOpenProperty;
|
||||
onPropertyAdded?: (property: DocCustomPropertyInfo) => void;
|
||||
onPropertyChange?: (property: DocCustomPropertyInfo, value: unknown) => void;
|
||||
onDatabasePropertyChange?: (
|
||||
row: DatabaseRow,
|
||||
cell: DatabaseValueCell,
|
||||
value: unknown
|
||||
) => void;
|
||||
}
|
||||
|
||||
interface DocPropertiesTableHeaderProps {
|
||||
@ -95,11 +105,13 @@ interface DocPropertyRowProps {
|
||||
propertyInfo: DocCustomPropertyInfo;
|
||||
showAll?: boolean;
|
||||
defaultOpenEditMenu?: boolean;
|
||||
onChange?: (value: unknown) => void;
|
||||
}
|
||||
|
||||
export const DocPropertyRow = ({
|
||||
propertyInfo,
|
||||
defaultOpenEditMenu,
|
||||
onChange,
|
||||
}: DocPropertyRowProps) => {
|
||||
const t = useI18n();
|
||||
const docService = useService(DocService);
|
||||
@ -123,8 +135,9 @@ export const DocPropertyRow = ({
|
||||
throw new Error('only allow string value');
|
||||
}
|
||||
docService.doc.record.setCustomProperty(propertyInfo.id, value);
|
||||
onChange?.(value);
|
||||
},
|
||||
[docService, propertyInfo]
|
||||
[docService, onChange, propertyInfo]
|
||||
);
|
||||
|
||||
const docId = docService.doc.id;
|
||||
@ -214,6 +227,8 @@ interface DocWorkspacePropertiesTableBodyProps {
|
||||
className?: string;
|
||||
style?: React.CSSProperties;
|
||||
defaultOpen?: boolean;
|
||||
onChange?: (property: DocCustomPropertyInfo, value: unknown) => void;
|
||||
onPropertyAdded?: (property: DocCustomPropertyInfo) => void;
|
||||
}
|
||||
|
||||
// 🏷️ Tags (⋅ xxx) (⋅ yyy)
|
||||
@ -221,104 +236,121 @@ interface DocWorkspacePropertiesTableBodyProps {
|
||||
// + Add a property
|
||||
const DocWorkspacePropertiesTableBody = forwardRef<
|
||||
HTMLDivElement,
|
||||
DocWorkspacePropertiesTableBodyProps & HTMLProps<HTMLDivElement>
|
||||
>(({ className, style, defaultOpen, ...props }, ref) => {
|
||||
const t = useI18n();
|
||||
const docsService = useService(DocsService);
|
||||
const workbenchService = useService(WorkbenchService);
|
||||
const viewService = useServiceOptional(ViewService);
|
||||
const properties = useLiveData(docsService.propertyList.sortedProperties$);
|
||||
const [propertyCollapsed, setPropertyCollapsed] = useState(true);
|
||||
DocWorkspacePropertiesTableBodyProps
|
||||
>(
|
||||
(
|
||||
{ className, style, defaultOpen, onChange, onPropertyAdded, ...props },
|
||||
ref
|
||||
) => {
|
||||
const t = useI18n();
|
||||
const docsService = useService(DocsService);
|
||||
const workbenchService = useService(WorkbenchService);
|
||||
const viewService = useServiceOptional(ViewService);
|
||||
const properties = useLiveData(docsService.propertyList.sortedProperties$);
|
||||
const [propertyCollapsed, setPropertyCollapsed] = useState(true);
|
||||
|
||||
const [newPropertyId, setNewPropertyId] = useState<string | null>(null);
|
||||
const [newPropertyId, setNewPropertyId] = useState<string | null>(null);
|
||||
|
||||
return (
|
||||
<PropertyCollapsibleSection
|
||||
ref={ref}
|
||||
className={clsx(styles.tableBodyRoot, className)}
|
||||
style={style}
|
||||
title={t.t('com.affine.workspace.properties')}
|
||||
defaultCollapsed={!defaultOpen}
|
||||
{...props}
|
||||
>
|
||||
<PropertyCollapsibleContent
|
||||
collapsible
|
||||
collapsed={propertyCollapsed}
|
||||
onCollapseChange={setPropertyCollapsed}
|
||||
className={styles.tableBodySortable}
|
||||
collapseButtonText={({ hide, isCollapsed }) =>
|
||||
isCollapsed
|
||||
? hide === 1
|
||||
? t['com.affine.page-properties.more-property.one']({
|
||||
count: hide.toString(),
|
||||
})
|
||||
: t['com.affine.page-properties.more-property.more']({
|
||||
count: hide.toString(),
|
||||
})
|
||||
: hide === 1
|
||||
? t['com.affine.page-properties.hide-property.one']({
|
||||
count: hide.toString(),
|
||||
})
|
||||
: t['com.affine.page-properties.hide-property.more']({
|
||||
count: hide.toString(),
|
||||
})
|
||||
}
|
||||
const handlePropertyAdded = useCallback(
|
||||
(property: DocCustomPropertyInfo) => {
|
||||
setNewPropertyId(property.id);
|
||||
onPropertyAdded?.(property);
|
||||
},
|
||||
[onPropertyAdded]
|
||||
);
|
||||
|
||||
return (
|
||||
<PropertyCollapsibleSection
|
||||
ref={ref}
|
||||
className={clsx(styles.tableBodyRoot, className)}
|
||||
style={style}
|
||||
title={t.t('com.affine.workspace.properties')}
|
||||
defaultCollapsed={!defaultOpen}
|
||||
{...props}
|
||||
>
|
||||
{properties.map(property => (
|
||||
<DocPropertyRow
|
||||
key={property.id}
|
||||
propertyInfo={property}
|
||||
defaultOpenEditMenu={newPropertyId === property.id}
|
||||
/>
|
||||
))}
|
||||
<div className={styles.actionContainer}>
|
||||
<Menu
|
||||
items={
|
||||
<CreatePropertyMenuItems
|
||||
at="after"
|
||||
onCreated={setNewPropertyId}
|
||||
/>
|
||||
}
|
||||
contentOptions={{
|
||||
onClick(e) {
|
||||
e.stopPropagation();
|
||||
},
|
||||
}}
|
||||
>
|
||||
<Button
|
||||
variant="plain"
|
||||
prefix={<PlusIcon />}
|
||||
className={styles.propertyActionButton}
|
||||
data-testid="add-property-button"
|
||||
>
|
||||
{t['com.affine.page-properties.add-property']()}
|
||||
</Button>
|
||||
</Menu>
|
||||
{viewService ? (
|
||||
<Button
|
||||
variant="plain"
|
||||
prefix={<PropertyIcon />}
|
||||
className={clsx(
|
||||
styles.propertyActionButton,
|
||||
styles.propertyConfigButton
|
||||
)}
|
||||
onClick={() => {
|
||||
viewService.view.activeSidebarTab('properties');
|
||||
workbenchService.workbench.openSidebar();
|
||||
<PropertyCollapsibleContent
|
||||
collapsible
|
||||
collapsed={propertyCollapsed}
|
||||
onCollapseChange={setPropertyCollapsed}
|
||||
className={styles.tableBodySortable}
|
||||
collapseButtonText={({ hide, isCollapsed }) =>
|
||||
isCollapsed
|
||||
? hide === 1
|
||||
? t['com.affine.page-properties.more-property.one']({
|
||||
count: hide.toString(),
|
||||
})
|
||||
: t['com.affine.page-properties.more-property.more']({
|
||||
count: hide.toString(),
|
||||
})
|
||||
: hide === 1
|
||||
? t['com.affine.page-properties.hide-property.one']({
|
||||
count: hide.toString(),
|
||||
})
|
||||
: t['com.affine.page-properties.hide-property.more']({
|
||||
count: hide.toString(),
|
||||
})
|
||||
}
|
||||
>
|
||||
{properties.map(property => (
|
||||
<DocPropertyRow
|
||||
key={property.id}
|
||||
propertyInfo={property}
|
||||
defaultOpenEditMenu={newPropertyId === property.id}
|
||||
onChange={value => onChange?.(property, value)}
|
||||
/>
|
||||
))}
|
||||
<div className={styles.actionContainer}>
|
||||
<Menu
|
||||
items={
|
||||
<CreatePropertyMenuItems
|
||||
at="after"
|
||||
onCreated={handlePropertyAdded}
|
||||
/>
|
||||
}
|
||||
contentOptions={{
|
||||
onClick(e) {
|
||||
e.stopPropagation();
|
||||
},
|
||||
}}
|
||||
>
|
||||
{t['com.affine.page-properties.config-properties']()}
|
||||
</Button>
|
||||
) : null}
|
||||
</div>
|
||||
</PropertyCollapsibleContent>
|
||||
</PropertyCollapsibleSection>
|
||||
);
|
||||
});
|
||||
<Button
|
||||
variant="plain"
|
||||
prefix={<PlusIcon />}
|
||||
className={styles.propertyActionButton}
|
||||
data-testid="add-property-button"
|
||||
>
|
||||
{t['com.affine.page-properties.add-property']()}
|
||||
</Button>
|
||||
</Menu>
|
||||
{viewService ? (
|
||||
<Button
|
||||
variant="plain"
|
||||
prefix={<PropertyIcon />}
|
||||
className={clsx(
|
||||
styles.propertyActionButton,
|
||||
styles.propertyConfigButton
|
||||
)}
|
||||
onClick={() => {
|
||||
viewService.view.activeSidebarTab('properties');
|
||||
workbenchService.workbench.openSidebar();
|
||||
}}
|
||||
>
|
||||
{t['com.affine.page-properties.config-properties']()}
|
||||
</Button>
|
||||
) : null}
|
||||
</div>
|
||||
</PropertyCollapsibleContent>
|
||||
</PropertyCollapsibleSection>
|
||||
);
|
||||
}
|
||||
);
|
||||
DocWorkspacePropertiesTableBody.displayName = 'PagePropertiesTableBody';
|
||||
|
||||
const DocPropertiesTableInner = ({
|
||||
defaultOpenProperty,
|
||||
onPropertyAdded,
|
||||
onPropertyChange,
|
||||
onDatabasePropertyChange,
|
||||
}: DocPropertiesTableProps) => {
|
||||
const [expanded, setExpanded] = useState(!!defaultOpenProperty);
|
||||
return (
|
||||
@ -334,9 +366,12 @@ const DocPropertiesTableInner = ({
|
||||
defaultOpen={
|
||||
!defaultOpenProperty || defaultOpenProperty.type === 'workspace'
|
||||
}
|
||||
onPropertyAdded={onPropertyAdded}
|
||||
onChange={onPropertyChange}
|
||||
/>
|
||||
<div className={styles.tableHeaderDivider} />
|
||||
<DocDatabaseBacklinkInfo
|
||||
onChange={onDatabasePropertyChange}
|
||||
defaultOpen={
|
||||
defaultOpenProperty?.type === 'database'
|
||||
? [
|
||||
@ -356,8 +391,6 @@ const DocPropertiesTableInner = ({
|
||||
|
||||
// this is the main component that renders the page properties table at the top of the page below
|
||||
// the page title
|
||||
export const DocPropertiesTable = ({
|
||||
defaultOpenProperty,
|
||||
}: DocPropertiesTableProps) => {
|
||||
return <DocPropertiesTableInner defaultOpenProperty={defaultOpenProperty} />;
|
||||
export const DocPropertiesTable = (props: DocPropertiesTableProps) => {
|
||||
return <DocPropertiesTableInner {...props} />;
|
||||
};
|
||||
|
@ -1,6 +1,7 @@
|
||||
import type { TagLike } from '@affine/component/ui/tags';
|
||||
import { TagsInlineEditor as TagsInlineEditorComponent } from '@affine/component/ui/tags';
|
||||
import { TagService, useDeleteTagConfirmModal } from '@affine/core/modules/tag';
|
||||
import track from '@affine/track';
|
||||
import {
|
||||
LiveData,
|
||||
useLiveData,
|
||||
@ -38,6 +39,9 @@ export const TagsInlineEditor = ({
|
||||
const onCreateTag = useCallback(
|
||||
(name: string, color: string) => {
|
||||
const newTag = tagService.tagList.createTag(name, color);
|
||||
track.doc.inlineDocInfo.property.editProperty({
|
||||
type: 'tags',
|
||||
});
|
||||
return {
|
||||
id: newTag.id,
|
||||
value: newTag.value$.value,
|
||||
@ -50,6 +54,9 @@ export const TagsInlineEditor = ({
|
||||
const onSelectTag = useCallback(
|
||||
(tagId: string) => {
|
||||
tagService.tagList.tagByTagId$(tagId).value?.tag(pageId);
|
||||
track.doc.inlineDocInfo.property.editProperty({
|
||||
type: 'tags',
|
||||
});
|
||||
},
|
||||
[pageId, tagService.tagList]
|
||||
);
|
||||
@ -57,6 +64,9 @@ export const TagsInlineEditor = ({
|
||||
const onDeselectTag = useCallback(
|
||||
(tagId: string) => {
|
||||
tagService.tagList.tagByTagId$(tagId).value?.untag(pageId);
|
||||
track.doc.inlineDocInfo.property.editProperty({
|
||||
type: 'tags',
|
||||
});
|
||||
},
|
||||
[pageId, tagService.tagList]
|
||||
);
|
||||
@ -68,6 +78,9 @@ export const TagsInlineEditor = ({
|
||||
} else if (property === 'color') {
|
||||
tagService.tagList.tagByTagId$(id).value?.changeColor(value);
|
||||
}
|
||||
track.doc.inlineDocInfo.property.editProperty({
|
||||
type: 'tags',
|
||||
});
|
||||
},
|
||||
[tagService.tagList]
|
||||
);
|
||||
@ -77,6 +90,9 @@ export const TagsInlineEditor = ({
|
||||
const onTagDelete = useAsyncCallback(
|
||||
async (id: string) => {
|
||||
await deleteTags([id]);
|
||||
track.doc.inlineDocInfo.property.editProperty({
|
||||
type: 'tags',
|
||||
});
|
||||
},
|
||||
[deleteTags]
|
||||
);
|
||||
|
@ -8,16 +8,22 @@ import {
|
||||
import { CreatePropertyMenuItems } from '@affine/core/components/doc-properties/menu/create-doc-property';
|
||||
import { DocPropertyRow } from '@affine/core/components/doc-properties/table';
|
||||
import { DocDatabaseBacklinkInfo } from '@affine/core/modules/doc-info';
|
||||
import type {
|
||||
DatabaseRow,
|
||||
DatabaseValueCell,
|
||||
} from '@affine/core/modules/doc-info/types';
|
||||
import { DocsSearchService } from '@affine/core/modules/docs-search';
|
||||
import { useI18n } from '@affine/i18n';
|
||||
import track from '@affine/track';
|
||||
import { PlusIcon } from '@blocksuite/icons/rc';
|
||||
import {
|
||||
type DocCustomPropertyInfo,
|
||||
DocsService,
|
||||
LiveData,
|
||||
useLiveData,
|
||||
useServices,
|
||||
} from '@toeverything/infra';
|
||||
import { useMemo, useState } from 'react';
|
||||
import { useCallback, useMemo, useState } from 'react';
|
||||
|
||||
import * as styles from './info-modal.css';
|
||||
import { LinksRow } from './links-row';
|
||||
@ -49,6 +55,23 @@ export const InfoTable = ({
|
||||
)
|
||||
);
|
||||
|
||||
const onBacklinkPropertyChange = useCallback(
|
||||
(_row: DatabaseRow, cell: DatabaseValueCell, _value: unknown) => {
|
||||
track.$.docInfoPanel.databaseProperty.editProperty({
|
||||
type: cell.property.type$.value,
|
||||
});
|
||||
},
|
||||
[]
|
||||
);
|
||||
|
||||
const onPropertyAdded = useCallback((property: DocCustomPropertyInfo) => {
|
||||
setNewPropertyId(property.id);
|
||||
track.$.docInfoPanel.property.addProperty({
|
||||
type: property.type,
|
||||
control: 'at menu',
|
||||
});
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<>
|
||||
{backlinks && backlinks.length > 0 ? (
|
||||
@ -102,7 +125,7 @@ export const InfoTable = ({
|
||||
/>
|
||||
))}
|
||||
<Menu
|
||||
items={<CreatePropertyMenuItems onCreated={setNewPropertyId} />}
|
||||
items={<CreatePropertyMenuItems onCreated={onPropertyAdded} />}
|
||||
contentOptions={{
|
||||
onClick(e) {
|
||||
e.stopPropagation();
|
||||
@ -120,7 +143,7 @@ export const InfoTable = ({
|
||||
</PropertyCollapsibleContent>
|
||||
</PropertyCollapsibleSection>
|
||||
<Divider size="thinner" />
|
||||
<DocDatabaseBacklinkInfo />
|
||||
<DocDatabaseBacklinkInfo onChange={onBacklinkPropertyChange} />
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
@ -4,7 +4,13 @@ import { DocPropertyManager } from '@affine/core/components/doc-properties/manag
|
||||
import { CreatePropertyMenuItems } from '@affine/core/components/doc-properties/menu/create-doc-property';
|
||||
import { useWorkspaceInfo } from '@affine/core/components/hooks/use-workspace-info';
|
||||
import { Trans, useI18n } from '@affine/i18n';
|
||||
import { FrameworkScope, type WorkspaceMetadata } from '@toeverything/infra';
|
||||
import track from '@affine/track';
|
||||
import {
|
||||
type DocCustomPropertyInfo,
|
||||
FrameworkScope,
|
||||
type WorkspaceMetadata,
|
||||
} from '@toeverything/infra';
|
||||
import { useCallback } from 'react';
|
||||
|
||||
import { useWorkspace } from '../../../../../components/hooks/use-workspace';
|
||||
import * as styles from './styles.css';
|
||||
@ -12,10 +18,17 @@ import * as styles from './styles.css';
|
||||
const WorkspaceSettingPropertiesMain = () => {
|
||||
const t = useI18n();
|
||||
|
||||
const onCreated = useCallback((property: DocCustomPropertyInfo) => {
|
||||
track.$.settingsPanel.workspace.addProperty({
|
||||
type: property.type,
|
||||
control: 'at menu',
|
||||
});
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<div className={styles.main}>
|
||||
<div className={styles.listHeader}>
|
||||
<Menu items={<CreatePropertyMenuItems />}>
|
||||
<Menu items={<CreatePropertyMenuItems onCreated={onCreated} />}>
|
||||
<Button variant="primary">
|
||||
{t['com.affine.settings.workspace.properties.add_property']()}
|
||||
</Button>
|
||||
|
@ -32,4 +32,5 @@ export interface DatabaseCellRendererProps {
|
||||
rowId: string;
|
||||
cell: DatabaseValueCell;
|
||||
dataSource: DatabaseBlockDataSource;
|
||||
onChange: (value: unknown) => void;
|
||||
}
|
||||
|
@ -8,6 +8,7 @@ export const CheckboxCell = ({
|
||||
cell,
|
||||
rowId,
|
||||
dataSource,
|
||||
onChange,
|
||||
}: DatabaseCellRendererProps) => {
|
||||
const value = useLiveData(cell.value$ as LiveData<boolean>);
|
||||
return (
|
||||
@ -16,6 +17,7 @@ export const CheckboxCell = ({
|
||||
value={value ? 'true' : 'false'}
|
||||
onChange={v => {
|
||||
dataSource.cellValueChange(rowId, cell.property.id, v === 'true');
|
||||
onChange?.(v === 'true');
|
||||
}}
|
||||
/>
|
||||
);
|
||||
|
@ -20,6 +20,7 @@ export const DateCell = ({
|
||||
cell,
|
||||
rowId,
|
||||
dataSource,
|
||||
onChange,
|
||||
}: DatabaseCellRendererProps) => {
|
||||
const value = useLiveData(
|
||||
cell.value$ as LiveData<number | string | undefined>
|
||||
@ -34,6 +35,7 @@ export const DateCell = ({
|
||||
cell.property.id,
|
||||
fromInternalDateString(v)
|
||||
);
|
||||
onChange?.(fromInternalDateString(v));
|
||||
}}
|
||||
/>
|
||||
);
|
||||
|
@ -21,6 +21,7 @@ export const LinkCell = ({
|
||||
cell,
|
||||
dataSource,
|
||||
rowId,
|
||||
onChange,
|
||||
}: DatabaseCellRendererProps) => {
|
||||
const isEmpty = useLiveData(
|
||||
cell.value$.map(value => typeof value !== 'string' || !value)
|
||||
@ -35,7 +36,8 @@ export const LinkCell = ({
|
||||
dataSource.cellValueChange(rowId, cell.id, tempValue.trim());
|
||||
setEditing(false);
|
||||
setTempValue(tempValue.trim());
|
||||
}, [dataSource, rowId, cell.id, tempValue]);
|
||||
onChange?.(tempValue.trim());
|
||||
}, [dataSource, rowId, cell.id, onChange, tempValue]);
|
||||
|
||||
const handleOnChange: ChangeEventHandler<HTMLTextAreaElement> = useCallback(
|
||||
e => {
|
||||
|
@ -7,6 +7,7 @@ export const NumberCell = ({
|
||||
cell,
|
||||
rowId,
|
||||
dataSource,
|
||||
onChange,
|
||||
}: DatabaseCellRendererProps) => {
|
||||
const value = useLiveData(cell.value$);
|
||||
return (
|
||||
@ -14,6 +15,7 @@ export const NumberCell = ({
|
||||
value={value}
|
||||
onChange={v => {
|
||||
dataSource.cellValueChange(rowId, cell.property.id, v);
|
||||
onChange?.(v);
|
||||
}}
|
||||
/>
|
||||
);
|
||||
|
@ -9,6 +9,7 @@ export const ProgressCell = ({
|
||||
cell,
|
||||
dataSource,
|
||||
rowId,
|
||||
onChange,
|
||||
}: DatabaseCellRendererProps) => {
|
||||
const value = useLiveData(cell.value$ as LiveData<number>);
|
||||
const isEmpty = value === undefined;
|
||||
@ -27,6 +28,7 @@ export const ProgressCell = ({
|
||||
}}
|
||||
onBlur={() => {
|
||||
dataSource.cellValueChange(rowId, cell.id, localValue);
|
||||
onChange?.(localValue);
|
||||
}}
|
||||
/>
|
||||
</PropertyValue>
|
||||
|
@ -40,6 +40,7 @@ const renderRichText = ({
|
||||
export const RichTextCell = ({
|
||||
cell,
|
||||
dataSource,
|
||||
onChange,
|
||||
}: DatabaseCellRendererProps) => {
|
||||
const std = useBlockStdScope(dataSource.doc);
|
||||
const text = useLiveData(cell.value$ as LiveData<Y.Text>);
|
||||
@ -49,14 +50,19 @@ export const RichTextCell = ({
|
||||
if (ref.current) {
|
||||
ref.current.innerHTML = '';
|
||||
const richText = renderRichText({ doc: dataSource.doc, std, text });
|
||||
const listener = () => {
|
||||
onChange(text);
|
||||
};
|
||||
if (richText) {
|
||||
richText.addEventListener('change', listener);
|
||||
ref.current.append(richText);
|
||||
return () => {
|
||||
richText.removeEventListener('change', listener);
|
||||
richText.remove();
|
||||
};
|
||||
}
|
||||
}
|
||||
return () => {};
|
||||
}, [dataSource.doc, std, text]);
|
||||
}, [dataSource.doc, onChange, std, text]);
|
||||
return <PropertyValue ref={ref}></PropertyValue>;
|
||||
};
|
||||
|
@ -147,6 +147,7 @@ const BlocksuiteDatabaseSelector = ({
|
||||
dataSource,
|
||||
rowId,
|
||||
multiple,
|
||||
onChange,
|
||||
}: DatabaseCellRendererProps & { multiple: boolean }) => {
|
||||
const tagService = useService(TagService);
|
||||
const selectCell = cell as any as SingleSelectCell | MultiSelectCell;
|
||||
@ -177,21 +178,24 @@ const BlocksuiteDatabaseSelector = ({
|
||||
const onDeleteTag = useCallback(
|
||||
(tagId: string) => {
|
||||
adapter.deleteTag(selectCell, dataSource, tagId);
|
||||
onChange?.(selectCell.value$.value);
|
||||
},
|
||||
[dataSource, selectCell]
|
||||
[dataSource, selectCell, onChange]
|
||||
);
|
||||
const onDeselectTag = useCallback(
|
||||
(tagId: string) => {
|
||||
adapter.deselectTag(rowId, selectCell, dataSource, tagId, multiple);
|
||||
onChange?.(selectCell.value$.value);
|
||||
},
|
||||
[selectCell, dataSource, rowId, multiple]
|
||||
[rowId, selectCell, dataSource, multiple, onChange]
|
||||
);
|
||||
|
||||
const onSelectTag = useCallback(
|
||||
(tagId: string) => {
|
||||
adapter.selectTag(rowId, selectCell, dataSource, tagId, multiple);
|
||||
onChange?.(selectCell.value$.value);
|
||||
},
|
||||
[rowId, selectCell, dataSource, multiple]
|
||||
[rowId, selectCell, dataSource, multiple, onChange]
|
||||
);
|
||||
|
||||
const tagColors = useMemo(() => {
|
||||
@ -237,6 +241,7 @@ export const SelectCell = ({
|
||||
cell,
|
||||
dataSource,
|
||||
rowId,
|
||||
onChange,
|
||||
}: DatabaseCellRendererProps) => {
|
||||
const isEmpty = useLiveData(
|
||||
cell.value$.map(value => Array.isArray(value) && value.length === 0)
|
||||
@ -248,6 +253,7 @@ export const SelectCell = ({
|
||||
dataSource={dataSource}
|
||||
rowId={rowId}
|
||||
multiple={false}
|
||||
onChange={onChange}
|
||||
/>
|
||||
</PropertyValue>
|
||||
);
|
||||
@ -257,6 +263,7 @@ export const MultiSelectCell = ({
|
||||
cell,
|
||||
dataSource,
|
||||
rowId,
|
||||
onChange,
|
||||
}: DatabaseCellRendererProps) => {
|
||||
const isEmpty = useLiveData(
|
||||
cell.value$.map(value => Array.isArray(value) && value.length === 0)
|
||||
@ -268,6 +275,7 @@ export const MultiSelectCell = ({
|
||||
dataSource={dataSource}
|
||||
rowId={rowId}
|
||||
multiple={true}
|
||||
onChange={onChange}
|
||||
/>
|
||||
</PropertyValue>
|
||||
);
|
||||
|
@ -46,10 +46,12 @@ const DatabaseBacklinkCell = ({
|
||||
cell,
|
||||
dataSource,
|
||||
rowId,
|
||||
onChange,
|
||||
}: {
|
||||
cell: DatabaseValueCell;
|
||||
dataSource: DatabaseBlockDataSource;
|
||||
rowId: string;
|
||||
onChange: (value: unknown) => void;
|
||||
}) => {
|
||||
const cellType = useLiveData(cell.property.type$);
|
||||
|
||||
@ -67,7 +69,12 @@ const DatabaseBacklinkCell = ({
|
||||
data-testid="database-backlink-cell"
|
||||
>
|
||||
<DatabaseBacklinkCellName cell={cell} config={config} />
|
||||
<config.Renderer cell={cell} dataSource={dataSource} rowId={rowId} />
|
||||
<config.Renderer
|
||||
cell={cell}
|
||||
dataSource={dataSource}
|
||||
rowId={rowId}
|
||||
onChange={onChange}
|
||||
/>
|
||||
</li>
|
||||
);
|
||||
};
|
||||
@ -79,9 +86,15 @@ const DatabaseBacklinkCell = ({
|
||||
const DatabaseBacklinkRow = ({
|
||||
defaultOpen = false,
|
||||
row$,
|
||||
onChange,
|
||||
}: {
|
||||
defaultOpen: boolean;
|
||||
row$: Observable<DatabaseRow | undefined>;
|
||||
onChange?: (
|
||||
row: DatabaseRow,
|
||||
cell: DatabaseValueCell,
|
||||
value: unknown
|
||||
) => void;
|
||||
}) => {
|
||||
const row = useLiveData(
|
||||
useMemo(() => LiveData.from(row$, undefined), [row$])
|
||||
@ -132,6 +145,7 @@ const DatabaseBacklinkRow = ({
|
||||
cell={cell}
|
||||
dataSource={row.dataSource}
|
||||
rowId={row.id}
|
||||
onChange={value => onChange?.(row, cell, value)}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
@ -142,11 +156,17 @@ const DatabaseBacklinkRow = ({
|
||||
|
||||
export const DocDatabaseBacklinkInfo = ({
|
||||
defaultOpen = [],
|
||||
onChange,
|
||||
}: {
|
||||
defaultOpen?: {
|
||||
databaseId: string;
|
||||
rowId: string;
|
||||
}[];
|
||||
onChange?: (
|
||||
row: DatabaseRow,
|
||||
cell: DatabaseValueCell,
|
||||
value: unknown
|
||||
) => void;
|
||||
}) => {
|
||||
const doc = useService(DocService).doc;
|
||||
const docDatabaseBacklinks = useService(DocDatabaseBacklinksService);
|
||||
@ -173,6 +193,7 @@ export const DocDatabaseBacklinkInfo = ({
|
||||
backlink.rowId === rowId
|
||||
)}
|
||||
row$={row$}
|
||||
onChange={onChange}
|
||||
/>
|
||||
<Divider size="thinner" className={styles.divider} />
|
||||
</Fragment>
|
||||
|
@ -46,7 +46,9 @@ type DocEvents =
|
||||
| 'openDocOptionsMenu'
|
||||
| 'openDocInfo'
|
||||
| 'copyBlockToLink'
|
||||
| 'bookmark';
|
||||
| 'bookmark'
|
||||
| 'editProperty'
|
||||
| 'addProperty';
|
||||
type EditorEvents = 'bold' | 'italic' | 'underline' | 'strikeThrough';
|
||||
// END SECTION
|
||||
|
||||
@ -148,10 +150,12 @@ const PageEvents = {
|
||||
},
|
||||
docInfoPanel: {
|
||||
$: ['open'],
|
||||
property: ['editProperty', 'addProperty'],
|
||||
databaseProperty: ['editProperty'],
|
||||
},
|
||||
settingsPanel: {
|
||||
menu: ['openSettings'],
|
||||
workspace: ['viewPlans', 'export'],
|
||||
workspace: ['viewPlans', 'export', 'addProperty'],
|
||||
profileAndBadge: ['viewPlans'],
|
||||
accountUsage: ['viewPlans'],
|
||||
accountSettings: ['uploadAvatar', 'removeAvatar', 'updateUserName'],
|
||||
@ -277,6 +281,11 @@ const PageEvents = {
|
||||
},
|
||||
inlineDocInfo: {
|
||||
$: ['toggle'],
|
||||
property: ['editProperty', 'addProperty'],
|
||||
databaseProperty: ['editProperty'],
|
||||
},
|
||||
sidepanel: {
|
||||
property: ['addProperty'],
|
||||
},
|
||||
},
|
||||
// remove when type added
|
||||
@ -413,6 +422,8 @@ export type EventArgs = {
|
||||
copyBlockToLink: {
|
||||
type: string;
|
||||
};
|
||||
editProperty: { type: string };
|
||||
addProperty: { type: string; control: 'at menu' | 'property list' };
|
||||
};
|
||||
|
||||
// for type checking
|
||||
|
Loading…
Reference in New Issue
Block a user