mirror of
https://github.com/toeverything/AFFiNE.git
synced 2026-08-18 02:21:51 +08:00
feat(core): card view drag handle for doc explorer (#12431)
close AF-2624, AF-2628, AF-2581 <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit - **New Features** - Introduced a draggable handle to document cards in the explorer, visible on hover in card view. - Added an option to remove grouping in the display menu. - Added contextual tooltips for user avatars indicating creation or last update. - Enabled optional tooltips on public user labels. - Extended dropdown buttons to accept custom styling classes. - Added a new masonry story showcasing item heights determined by ratios. - **Style** - Enhanced drag handle appearance and visibility for card view items. - Replaced static shadows with theme-aware, smoothly transitioning shadows on card items. - Adjusted spacing between items in the document explorer for improved layout, with increased horizontal and (in card view) vertical gaps. - Reduced top padding in workspace page styles. - Added new button background style for secondary buttons. - **Bug Fixes** - Removed duplicate internal property declarations to eliminate redundancy. - **Refactor** - Simplified layout props by removing fixed height parameters in multiple components. - Updated masonry layout logic to support ratio-based item sizing alongside fixed heights. - Removed randomized skeleton loading placeholders, replacing them with fixed or no placeholders. - Refined masonry component typings and scrollbar placement for improved styling and layout. - Improved selection logic to activate selection mode when selecting all documents. - **Localization** - Added new translation keys for grouping removal and user attribution tooltips. - Updated English locale with new strings for "Remove group" and user-created/updated tooltips. <!-- end of auto-generated comment: release notes by coderabbit.ai -->
This commit is contained in:
@@ -1,4 +1,5 @@
|
|||||||
import { ArrowDownSmallIcon } from '@blocksuite/icons/rc';
|
import { ArrowDownSmallIcon } from '@blocksuite/icons/rc';
|
||||||
|
import clsx from 'clsx';
|
||||||
import type { ButtonHTMLAttributes, MouseEventHandler } from 'react';
|
import type { ButtonHTMLAttributes, MouseEventHandler } from 'react';
|
||||||
import { forwardRef } from 'react';
|
import { forwardRef } from 'react';
|
||||||
|
|
||||||
@@ -12,28 +13,33 @@ type DropdownButtonProps = {
|
|||||||
export const DropdownButton = forwardRef<
|
export const DropdownButton = forwardRef<
|
||||||
HTMLButtonElement,
|
HTMLButtonElement,
|
||||||
DropdownButtonProps
|
DropdownButtonProps
|
||||||
>(({ onClickDropDown, children, size = 'default', ...props }, ref) => {
|
>(
|
||||||
const handleClickDropDown: MouseEventHandler<HTMLElement> = e => {
|
(
|
||||||
e.stopPropagation();
|
{ onClickDropDown, children, size = 'default', className, ...props },
|
||||||
onClickDropDown?.(e);
|
ref
|
||||||
};
|
) => {
|
||||||
return (
|
const handleClickDropDown: MouseEventHandler<HTMLElement> = e => {
|
||||||
<button
|
e.stopPropagation();
|
||||||
ref={ref}
|
onClickDropDown?.(e);
|
||||||
data-size={size}
|
};
|
||||||
className={styles.dropdownBtn}
|
return (
|
||||||
{...props}
|
<button
|
||||||
>
|
ref={ref}
|
||||||
<span>{children}</span>
|
data-size={size}
|
||||||
<span className={styles.divider} />
|
className={clsx(styles.dropdownBtn, className)}
|
||||||
<span className={styles.dropdownWrapper} onClick={handleClickDropDown}>
|
{...props}
|
||||||
<ArrowDownSmallIcon
|
>
|
||||||
className={styles.dropdownIcon}
|
<span>{children}</span>
|
||||||
width={16}
|
<span className={styles.divider} />
|
||||||
height={16}
|
<span className={styles.dropdownWrapper} onClick={handleClickDropDown}>
|
||||||
/>
|
<ArrowDownSmallIcon
|
||||||
</span>
|
className={styles.dropdownIcon}
|
||||||
</button>
|
width={16}
|
||||||
);
|
height={16}
|
||||||
});
|
/>
|
||||||
|
</span>
|
||||||
|
</button>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
);
|
||||||
DropdownButton.displayName = 'DropdownButton';
|
DropdownButton.displayName = 'DropdownButton';
|
||||||
|
|||||||
@@ -279,3 +279,34 @@ export const MultiViewTransition = () => {
|
|||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const availableRatios = [0.8, 1.2, 1.4, 1.5, 1.6, 1.7, 1.8];
|
||||||
|
const ratioItems = Array.from({ length: 10000 }, (_, i) => {
|
||||||
|
const ratio =
|
||||||
|
availableRatios[Math.floor(Math.random() * availableRatios.length)];
|
||||||
|
return {
|
||||||
|
id: i.toString(),
|
||||||
|
ratio,
|
||||||
|
children: (
|
||||||
|
<Card>
|
||||||
|
{i} <br /> ratio: {ratio}
|
||||||
|
</Card>
|
||||||
|
),
|
||||||
|
};
|
||||||
|
});
|
||||||
|
export const HeightByRatio = () => {
|
||||||
|
return (
|
||||||
|
<ResizePanel width={800} height={600}>
|
||||||
|
<Masonry
|
||||||
|
gapX={10}
|
||||||
|
gapY={10}
|
||||||
|
style={{ width: '100%', height: '100%' }}
|
||||||
|
paddingX={12}
|
||||||
|
paddingY={12}
|
||||||
|
items={ratioItems}
|
||||||
|
virtualScroll
|
||||||
|
itemWidthMin={120}
|
||||||
|
/>
|
||||||
|
</ResizePanel>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|||||||
@@ -286,7 +286,6 @@ export const Masonry = ({
|
|||||||
})}
|
})}
|
||||||
<div data-masonry-placeholder style={{ height }} />
|
<div data-masonry-placeholder style={{ height }} />
|
||||||
</Scrollable.Viewport>
|
</Scrollable.Viewport>
|
||||||
<Scrollable.Scrollbar />
|
|
||||||
{stickyGroup ? (
|
{stickyGroup ? (
|
||||||
<div
|
<div
|
||||||
className={clsx(styles.stickyGroupHeader, stickyGroup.className)}
|
className={clsx(styles.stickyGroupHeader, stickyGroup.className)}
|
||||||
@@ -310,16 +309,16 @@ export const Masonry = ({
|
|||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
) : null}
|
) : null}
|
||||||
|
<Scrollable.Scrollbar className={styles.scrollbar} />
|
||||||
</Scrollable.Root>
|
</Scrollable.Root>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
interface MasonryItemProps
|
type MasonryItemProps = MasonryItem &
|
||||||
extends MasonryItem,
|
Omit<React.HTMLAttributes<HTMLDivElement>, 'id' | 'height'> & {
|
||||||
Omit<React.HTMLAttributes<HTMLDivElement>, 'id' | 'height'> {
|
locateMode?: 'transform' | 'leftTop' | 'transform3d';
|
||||||
locateMode?: 'transform' | 'leftTop' | 'transform3d';
|
xywh?: MasonryItemXYWH;
|
||||||
xywh?: MasonryItemXYWH;
|
};
|
||||||
}
|
|
||||||
|
|
||||||
const MasonryGroupHeader = memo(function MasonryGroupHeader({
|
const MasonryGroupHeader = memo(function MasonryGroupHeader({
|
||||||
id,
|
id,
|
||||||
@@ -330,7 +329,6 @@ const MasonryGroupHeader = memo(function MasonryGroupHeader({
|
|||||||
groupId,
|
groupId,
|
||||||
itemCount,
|
itemCount,
|
||||||
collapsed,
|
collapsed,
|
||||||
height,
|
|
||||||
paddingX,
|
paddingX,
|
||||||
...props
|
...props
|
||||||
}: Omit<MasonryItemProps, 'Component'> & {
|
}: Omit<MasonryItemProps, 'Component'> & {
|
||||||
@@ -356,7 +354,6 @@ const MasonryGroupHeader = memo(function MasonryGroupHeader({
|
|||||||
return (
|
return (
|
||||||
<MasonryItem
|
<MasonryItem
|
||||||
id={id}
|
id={id}
|
||||||
height={height}
|
|
||||||
style={{
|
style={{
|
||||||
padding: `0 ${paddingX}px`,
|
padding: `0 ${paddingX}px`,
|
||||||
height: '100%',
|
height: '100%',
|
||||||
@@ -404,7 +401,7 @@ const MasonryItem = memo(function MasonryItem({
|
|||||||
className,
|
className,
|
||||||
style: styleProp,
|
style: styleProp,
|
||||||
...props
|
...props
|
||||||
}: MasonryItemProps) {
|
}: Omit<MasonryItemProps, 'height' | 'ratio'>) {
|
||||||
const style = useMemo(() => {
|
const style = useMemo(() => {
|
||||||
if (!xywh) return { display: 'none' };
|
if (!xywh) return { display: 'none' };
|
||||||
|
|
||||||
|
|||||||
@@ -20,6 +20,9 @@ export const stickyGroupHeader = style({
|
|||||||
top: 0,
|
top: 0,
|
||||||
width: '100%',
|
width: '100%',
|
||||||
});
|
});
|
||||||
|
export const scrollbar = style({
|
||||||
|
zIndex: 1,
|
||||||
|
});
|
||||||
|
|
||||||
export const item = style({
|
export const item = style({
|
||||||
position: 'absolute',
|
position: 'absolute',
|
||||||
|
|||||||
@@ -1,8 +1,7 @@
|
|||||||
export interface MasonryItem extends React.HTMLAttributes<HTMLDivElement> {
|
export type MasonryItem = React.HTMLAttributes<HTMLDivElement> & {
|
||||||
id: string;
|
id: string;
|
||||||
height: number;
|
|
||||||
Component?: React.ComponentType<{ groupId: string; itemId: string }>;
|
Component?: React.ComponentType<{ groupId: string; itemId: string }>;
|
||||||
}
|
} & ({ height: number } | { ratio: number });
|
||||||
|
|
||||||
export interface MasonryGroup extends React.HTMLAttributes<HTMLDivElement> {
|
export interface MasonryGroup extends React.HTMLAttributes<HTMLDivElement> {
|
||||||
id: string;
|
id: string;
|
||||||
|
|||||||
@@ -82,6 +82,7 @@ export const calcLayout = (
|
|||||||
|
|
||||||
groups.forEach((group, index) => {
|
groups.forEach((group, index) => {
|
||||||
const heightStack = Array.from({ length: columns }, () => 0);
|
const heightStack = Array.from({ length: columns }, () => 0);
|
||||||
|
const ratioStack = Array.from({ length: columns }, () => 0);
|
||||||
if (index !== 0) {
|
if (index !== 0) {
|
||||||
finalHeight += groupsGap;
|
finalHeight += groupsGap;
|
||||||
}
|
}
|
||||||
@@ -101,24 +102,52 @@ export const calcLayout = (
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
finalHeight += groupHeaderLayout.h + groupHeaderGapWithItems;
|
finalHeight +=
|
||||||
|
groupHeaderLayout.h +
|
||||||
|
// if group header is empty, don't add gap
|
||||||
|
(groupHeaderLayout.h > 0 ? groupHeaderGapWithItems : 0);
|
||||||
// calculate group items
|
// calculate group items
|
||||||
group.items.forEach(item => {
|
group.items.forEach(item => {
|
||||||
const itemId = group.id ? `${group.id}:${item.id}` : item.id;
|
const itemId = group.id ? `${group.id}:${item.id}` : item.id;
|
||||||
const minHeight = Math.min(...heightStack);
|
const ratioMode = 'ratio' in item;
|
||||||
const minHeightIndex = heightStack.indexOf(minHeight);
|
const height = ratioMode ? item.ratio * width : item.height;
|
||||||
const hasGap = heightStack[minHeightIndex] ? gapY : 0;
|
|
||||||
const x = minHeightIndex * (width + gapX) + paddingX;
|
|
||||||
const y = finalHeight + minHeight + hasGap;
|
|
||||||
|
|
||||||
heightStack[minHeightIndex] += item.height + hasGap;
|
if (ratioMode) {
|
||||||
layout.set(itemId, {
|
const minRatio = Math.min(...ratioStack);
|
||||||
type: 'item',
|
const minRatioIndex = ratioStack.indexOf(minRatio);
|
||||||
x,
|
const minHeight = heightStack[minRatioIndex];
|
||||||
y,
|
const hasGap = heightStack[minRatioIndex] ? gapY : 0;
|
||||||
w: width,
|
const x = minRatioIndex * (width + gapX) + paddingX;
|
||||||
h: item.height,
|
const y = finalHeight + minHeight + hasGap;
|
||||||
});
|
|
||||||
|
ratioStack[minRatioIndex] += item.ratio * 10000;
|
||||||
|
heightStack[minRatioIndex] += height + hasGap;
|
||||||
|
layout.set(itemId, {
|
||||||
|
type: 'item',
|
||||||
|
x,
|
||||||
|
y,
|
||||||
|
w: width,
|
||||||
|
h: height,
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
const minHeight = Math.min(...heightStack);
|
||||||
|
const minHeightIndex = heightStack.indexOf(minHeight);
|
||||||
|
const hasGap = heightStack[minHeightIndex] ? gapY : 0;
|
||||||
|
const x = minHeightIndex * (width + gapX) + paddingX;
|
||||||
|
const y = finalHeight + minHeight + hasGap;
|
||||||
|
|
||||||
|
const ratio = height / width;
|
||||||
|
heightStack[minHeightIndex] += height + hasGap;
|
||||||
|
ratioStack[minHeightIndex] += ratio * 10000;
|
||||||
|
|
||||||
|
layout.set(itemId, {
|
||||||
|
type: 'item',
|
||||||
|
x,
|
||||||
|
y,
|
||||||
|
w: width,
|
||||||
|
h: height,
|
||||||
|
});
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
const groupHeight = Math.max(...heightStack) + paddingY;
|
const groupHeight = Math.max(...heightStack) + paddingY;
|
||||||
|
|||||||
@@ -49,6 +49,9 @@ export const createDocExplorerContext = (
|
|||||||
selectedDocIds$: new LiveData<string[]>([]),
|
selectedDocIds$: new LiveData<string[]>([]),
|
||||||
prevCheckAnchorId$: new LiveData<string | null>(null),
|
prevCheckAnchorId$: new LiveData<string | null>(null),
|
||||||
displayPreference$: displayPreference$,
|
displayPreference$: displayPreference$,
|
||||||
|
showDragHandle$: displayPreference$.selector(
|
||||||
|
displayPreference => displayPreference.showDragHandle
|
||||||
|
),
|
||||||
view$: displayPreference$.selector(
|
view$: displayPreference$.selector(
|
||||||
displayPreference => displayPreference.view
|
displayPreference => displayPreference.view
|
||||||
),
|
),
|
||||||
@@ -85,9 +88,6 @@ export const createDocExplorerContext = (
|
|||||||
showMoreOperation$: displayPreference$.selector(
|
showMoreOperation$: displayPreference$.selector(
|
||||||
displayPreference => displayPreference.showMoreOperation
|
displayPreference => displayPreference.showMoreOperation
|
||||||
),
|
),
|
||||||
showDragHandle$: displayPreference$.selector(
|
|
||||||
displayPreference => displayPreference.showDragHandle
|
|
||||||
),
|
|
||||||
quickDeletePermanently$: displayPreference$.selector(
|
quickDeletePermanently$: displayPreference$.selector(
|
||||||
displayPreference => displayPreference.quickDeletePermanently
|
displayPreference => displayPreference.quickDeletePermanently
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { MenuItem } from '@affine/component';
|
import { Divider, MenuItem } from '@affine/component';
|
||||||
import type { GroupByParams } from '@affine/core/modules/collection-rules/types';
|
import type { GroupByParams } from '@affine/core/modules/collection-rules/types';
|
||||||
import { WorkspacePropertyService } from '@affine/core/modules/workspace-property';
|
import { WorkspacePropertyService } from '@affine/core/modules/workspace-property';
|
||||||
import { useI18n } from '@affine/i18n';
|
import { useI18n } from '@affine/i18n';
|
||||||
@@ -48,8 +48,9 @@ export const GroupByList = ({
|
|||||||
onChange,
|
onChange,
|
||||||
}: {
|
}: {
|
||||||
groupBy?: GroupByParams;
|
groupBy?: GroupByParams;
|
||||||
onChange?: (next: GroupByParams) => void;
|
onChange?: (next: GroupByParams | undefined) => void;
|
||||||
}) => {
|
}) => {
|
||||||
|
const t = useI18n();
|
||||||
const workspacePropertyService = useService(WorkspacePropertyService);
|
const workspacePropertyService = useService(WorkspacePropertyService);
|
||||||
const propertyList = useLiveData(workspacePropertyService.sortedProperties$);
|
const propertyList = useLiveData(workspacePropertyService.sortedProperties$);
|
||||||
const explorerPropertyList = useMemo(() => {
|
const explorerPropertyList = useMemo(() => {
|
||||||
@@ -66,6 +67,10 @@ export const GroupByList = ({
|
|||||||
onChange={onChange}
|
onChange={onChange}
|
||||||
/>
|
/>
|
||||||
))}
|
))}
|
||||||
|
<Divider size="thinner" style={{ margin: '4px 0' }} />
|
||||||
|
<MenuItem onClick={() => onChange?.(undefined)}>
|
||||||
|
{t['com.affine.explorer.display-menu.grouping.remove']()}
|
||||||
|
</MenuItem>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -32,7 +32,7 @@ const ExplorerDisplayMenu = ({
|
|||||||
const t = useI18n();
|
const t = useI18n();
|
||||||
|
|
||||||
const handleGroupByChange = useCallback(
|
const handleGroupByChange = useCallback(
|
||||||
(groupBy: GroupByParams) => {
|
(groupBy: GroupByParams | undefined) => {
|
||||||
onDisplayPreferenceChange({ ...displayPreference, groupBy });
|
onDisplayPreferenceChange({ ...displayPreference, groupBy });
|
||||||
},
|
},
|
||||||
[displayPreference, onDisplayPreferenceChange]
|
[displayPreference, onDisplayPreferenceChange]
|
||||||
|
|||||||
@@ -159,6 +159,14 @@ export const listHide560 = style({
|
|||||||
export const cardViewRoot = style({
|
export const cardViewRoot = style({
|
||||||
vars: {
|
vars: {
|
||||||
'--ring-color': 'transparent',
|
'--ring-color': 'transparent',
|
||||||
|
'--light-shadow':
|
||||||
|
'0px 0px 0px 1px var(--ring-color), 0px 2px 3px rgba(0,0,0,.05)',
|
||||||
|
'--dark-shadow':
|
||||||
|
'0px 0px 0px 1px var(--ring-color), 0px 2px 3px rgba(0,0,0,.05)',
|
||||||
|
'--light-shadow-hover':
|
||||||
|
'0px 0px 0px 1px var(--ring-color), 0px 4px 6px rgba(0,0,0,.1)',
|
||||||
|
'--dark-shadow-hover':
|
||||||
|
'0px 0px 0px 1px var(--ring-color), 0px 4px 6px rgba(0,0,0,.1)',
|
||||||
},
|
},
|
||||||
width: '100%',
|
width: '100%',
|
||||||
height: '100%',
|
height: '100%',
|
||||||
@@ -170,14 +178,30 @@ export const cardViewRoot = style({
|
|||||||
backgroundColor: cssVarV2.layer.background.mobile.secondary,
|
backgroundColor: cssVarV2.layer.background.mobile.secondary,
|
||||||
border: `0.5px solid ${cssVarV2.layer.insideBorder.border}`,
|
border: `0.5px solid ${cssVarV2.layer.insideBorder.border}`,
|
||||||
// TODO: use variable
|
// TODO: use variable
|
||||||
boxShadow: '0px 0px 0px 1px var(--ring-color),0px 0px 3px rgba(0,0,0,.05)',
|
boxShadow: '0px 0px 0px 1px var(--ring-color), 0px 0px 3px rgba(0,0,0,.05)',
|
||||||
overflow: 'hidden',
|
overflow: 'hidden',
|
||||||
|
transition: 'box-shadow 0.23s ease, border-color 0.23s ease',
|
||||||
selectors: {
|
selectors: {
|
||||||
[`${root}[data-selected="true"] &`]: {
|
[`${root}[data-selected="true"] &`]: {
|
||||||
vars: {
|
vars: {
|
||||||
'--ring-color': cssVarV2.layer.insideBorder.primaryBorder,
|
'--ring-color': cssVarV2.layer.insideBorder.primaryBorder,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
'&:hover': {
|
||||||
|
borderColor: cssVarV2.pagelist.hoverBorder,
|
||||||
|
},
|
||||||
|
'[data-theme="light"] &': {
|
||||||
|
boxShadow: 'var(--light-shadow)',
|
||||||
|
},
|
||||||
|
'[data-theme="light"] &:hover': {
|
||||||
|
boxShadow: 'var(--light-shadow-hover)',
|
||||||
|
},
|
||||||
|
'[data-theme="dark"] &': {
|
||||||
|
boxShadow: 'var(--dark-shadow)',
|
||||||
|
},
|
||||||
|
'[data-theme="dark"] &:hover': {
|
||||||
|
boxShadow: 'var(--dark-shadow-hover)',
|
||||||
|
},
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
export const cardViewHeader = style({
|
export const cardViewHeader = style({
|
||||||
@@ -224,3 +248,17 @@ export const cardViewCheckbox = style({
|
|||||||
color: cssVarV2.icon.primary,
|
color: cssVarV2.icon.primary,
|
||||||
pointerEvents: 'none',
|
pointerEvents: 'none',
|
||||||
});
|
});
|
||||||
|
export const cardDragHandle = style([
|
||||||
|
dragHandle,
|
||||||
|
{
|
||||||
|
left: -4,
|
||||||
|
top: 0,
|
||||||
|
transform: 'translateX(-100%)',
|
||||||
|
opacity: 0,
|
||||||
|
selectors: {
|
||||||
|
[`${cardViewRoot}:hover &`]: {
|
||||||
|
opacity: 1,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
import {
|
import {
|
||||||
Checkbox,
|
Checkbox,
|
||||||
DragHandle as DragHandleIcon,
|
DragHandle as DragHandleIcon,
|
||||||
Skeleton,
|
|
||||||
Tooltip,
|
Tooltip,
|
||||||
useDraggable,
|
useDraggable,
|
||||||
|
Wrapper,
|
||||||
} from '@affine/component';
|
} from '@affine/component';
|
||||||
import { DocsService } from '@affine/core/modules/doc';
|
import { DocsService } from '@affine/core/modules/doc';
|
||||||
import { DocDisplayMetaService } from '@affine/core/modules/doc-display-meta';
|
import { DocDisplayMetaService } from '@affine/core/modules/doc-display-meta';
|
||||||
@@ -23,7 +23,6 @@ import {
|
|||||||
type SVGProps,
|
type SVGProps,
|
||||||
useCallback,
|
useCallback,
|
||||||
useContext,
|
useContext,
|
||||||
useState,
|
|
||||||
} from 'react';
|
} from 'react';
|
||||||
|
|
||||||
import { PagePreview } from '../../page-list/page-content-preview';
|
import { PagePreview } from '../../page-list/page-content-preview';
|
||||||
@@ -312,9 +311,6 @@ export const ListViewDoc = ({ docId }: DocListItemProps) => {
|
|||||||
const t = useI18n();
|
const t = useI18n();
|
||||||
const docsService = useService(DocsService);
|
const docsService = useService(DocsService);
|
||||||
const doc = useLiveData(docsService.list.doc$(docId));
|
const doc = useLiveData(docsService.list.doc$(docId));
|
||||||
const [previewSkeletonWidth] = useState(
|
|
||||||
Math.round(100 + Math.random() * 100)
|
|
||||||
);
|
|
||||||
|
|
||||||
if (!doc) {
|
if (!doc) {
|
||||||
return null;
|
return null;
|
||||||
@@ -334,7 +330,7 @@ export const ListViewDoc = ({ docId }: DocListItemProps) => {
|
|||||||
<DocPreview
|
<DocPreview
|
||||||
id={docId}
|
id={docId}
|
||||||
className={styles.listPreview}
|
className={styles.listPreview}
|
||||||
loading={<Skeleton height={20} width={previewSkeletonWidth} />}
|
loading={<Wrapper height={20} width={10} />}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div className={styles.listSpace} />
|
<div className={styles.listSpace} />
|
||||||
@@ -360,19 +356,13 @@ const cardMoreMenuContentOptions = {
|
|||||||
sideOffset: 12,
|
sideOffset: 12,
|
||||||
alignOffset: -4,
|
alignOffset: -4,
|
||||||
} as const;
|
} as const;
|
||||||
const randomPreviewSkeleton = () => {
|
|
||||||
return Array.from({ length: Math.floor(Math.random() * 2) + 1 }, (_, i) => ({
|
|
||||||
id: i,
|
|
||||||
width: Math.round(30 + Math.random() * 70),
|
|
||||||
}));
|
|
||||||
};
|
|
||||||
export const CardViewDoc = ({ docId }: DocListItemProps) => {
|
export const CardViewDoc = ({ docId }: DocListItemProps) => {
|
||||||
const t = useI18n();
|
const t = useI18n();
|
||||||
const contextValue = useContext(DocExplorerContext);
|
const contextValue = useContext(DocExplorerContext);
|
||||||
const selectMode = useLiveData(contextValue.selectMode$);
|
const selectMode = useLiveData(contextValue.selectMode$);
|
||||||
const docsService = useService(DocsService);
|
const docsService = useService(DocsService);
|
||||||
const doc = useLiveData(docsService.list.doc$(docId));
|
const doc = useLiveData(docsService.list.doc$(docId));
|
||||||
const [previewSkeleton] = useState(randomPreviewSkeleton);
|
|
||||||
|
|
||||||
if (!doc) {
|
if (!doc) {
|
||||||
return null;
|
return null;
|
||||||
@@ -380,6 +370,7 @@ export const CardViewDoc = ({ docId }: DocListItemProps) => {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<li className={styles.cardViewRoot}>
|
<li className={styles.cardViewRoot}>
|
||||||
|
<DragHandle id={docId} className={styles.cardDragHandle} />
|
||||||
<header className={styles.cardViewHeader}>
|
<header className={styles.cardViewHeader}>
|
||||||
<DocIcon id={docId} className={styles.cardViewIcon} />
|
<DocIcon id={docId} className={styles.cardViewIcon} />
|
||||||
<DocTitle
|
<DocTitle
|
||||||
@@ -404,17 +395,7 @@ export const CardViewDoc = ({ docId }: DocListItemProps) => {
|
|||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
</header>
|
</header>
|
||||||
<DocPreview
|
<DocPreview id={docId} className={styles.cardPreviewContainer} />
|
||||||
id={docId}
|
|
||||||
className={styles.cardPreviewContainer}
|
|
||||||
loading={
|
|
||||||
<div>
|
|
||||||
{previewSkeleton.map(({ id, width }) => (
|
|
||||||
<Skeleton key={id} height={20} width={`${width}%`} />
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
<CardViewProperties docId={docId} />
|
<CardViewProperties docId={docId} />
|
||||||
</li>
|
</li>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -1,4 +1,9 @@
|
|||||||
import { Masonry, type MasonryGroup, useConfirmModal } from '@affine/component';
|
import {
|
||||||
|
Masonry,
|
||||||
|
type MasonryGroup,
|
||||||
|
type MasonryItem,
|
||||||
|
useConfirmModal,
|
||||||
|
} from '@affine/component';
|
||||||
import { DocsService } from '@affine/core/modules/doc';
|
import { DocsService } from '@affine/core/modules/doc';
|
||||||
import { WorkspacePropertyService } from '@affine/core/modules/workspace-property';
|
import { WorkspacePropertyService } from '@affine/core/modules/workspace-property';
|
||||||
import { Trans, useI18n } from '@affine/i18n';
|
import { Trans, useI18n } from '@affine/i18n';
|
||||||
@@ -69,15 +74,13 @@ const GroupHeader = memo(function GroupHeader({
|
|||||||
return header;
|
return header;
|
||||||
});
|
});
|
||||||
|
|
||||||
const calcCardHeightById = (id: string, base = 250, scale = 10) => {
|
const ratios = [1.26, 1.304, 1.13, 1.391, 1.521];
|
||||||
|
const calcCardRatioById = (id: string) => {
|
||||||
if (!id) {
|
if (!id) {
|
||||||
return base;
|
return ratios[0];
|
||||||
}
|
}
|
||||||
const max = 5;
|
|
||||||
const min = 1;
|
|
||||||
const code = id.charCodeAt(0);
|
const code = id.charCodeAt(0);
|
||||||
const value = Math.floor((code % (max - min)) + min);
|
return ratios[code % ratios.length];
|
||||||
return base + value * scale;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export const DocListItemComponent = memo(function DocListItemComponent({
|
export const DocListItemComponent = memo(function DocListItemComponent({
|
||||||
@@ -94,16 +97,12 @@ export const DocsExplorer = ({
|
|||||||
className,
|
className,
|
||||||
disableMultiDelete,
|
disableMultiDelete,
|
||||||
masonryItemWidthMin,
|
masonryItemWidthMin,
|
||||||
heightBase,
|
|
||||||
heightScale,
|
|
||||||
onRestore,
|
onRestore,
|
||||||
onDelete,
|
onDelete,
|
||||||
}: {
|
}: {
|
||||||
className?: string;
|
className?: string;
|
||||||
disableMultiDelete?: boolean;
|
disableMultiDelete?: boolean;
|
||||||
masonryItemWidthMin?: number;
|
masonryItemWidthMin?: number;
|
||||||
heightBase?: number;
|
|
||||||
heightScale?: number;
|
|
||||||
onRestore?: (ids: string[]) => void;
|
onRestore?: (ids: string[]) => void;
|
||||||
/** Override the default delete action */
|
/** Override the default delete action */
|
||||||
onDelete?: (ids: string[]) => void;
|
onDelete?: (ids: string[]) => void;
|
||||||
@@ -129,23 +128,23 @@ export const DocsExplorer = ({
|
|||||||
height: groupBy ? 24 : 0,
|
height: groupBy ? 24 : 0,
|
||||||
className: styles.groupHeader,
|
className: styles.groupHeader,
|
||||||
items: group.items.map((docId: string) => {
|
items: group.items.map((docId: string) => {
|
||||||
|
if (view === 'list') {
|
||||||
|
return {
|
||||||
|
id: docId,
|
||||||
|
Component: DocListItemComponent,
|
||||||
|
height: 42,
|
||||||
|
} satisfies MasonryItem;
|
||||||
|
}
|
||||||
return {
|
return {
|
||||||
id: docId,
|
id: docId,
|
||||||
Component: DocListItemComponent,
|
Component: DocListItemComponent,
|
||||||
height:
|
ratio: view === 'grid' ? ratios[0] : calcCardRatioById(docId),
|
||||||
view === 'list'
|
} satisfies MasonryItem;
|
||||||
? 42
|
|
||||||
: view === 'grid'
|
|
||||||
? 280
|
|
||||||
: calcCardHeightById(docId, heightBase, heightScale),
|
|
||||||
'data-view': view,
|
|
||||||
className: styles.docItem,
|
|
||||||
};
|
|
||||||
}),
|
}),
|
||||||
} satisfies MasonryGroup;
|
} satisfies MasonryGroup;
|
||||||
});
|
});
|
||||||
return items;
|
return items;
|
||||||
}, [groupBy, groups, heightBase, heightScale, view]);
|
}, [groupBy, groups, view]);
|
||||||
|
|
||||||
const handleCloseFloatingToolbar = useCallback(() => {
|
const handleCloseFloatingToolbar = useCallback(() => {
|
||||||
contextValue.selectMode$?.next(false);
|
contextValue.selectMode$?.next(false);
|
||||||
@@ -222,13 +221,18 @@ export const DocsExplorer = ({
|
|||||||
};
|
};
|
||||||
}, [contextValue]);
|
}, [contextValue]);
|
||||||
|
|
||||||
|
const responsivePaddingX = useCallback(
|
||||||
|
(w: number) => (w > 500 ? 24 : w > 393 ? 20 : 16),
|
||||||
|
[]
|
||||||
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Masonry
|
<Masonry
|
||||||
className={className}
|
className={className}
|
||||||
items={masonryItems}
|
items={masonryItems}
|
||||||
gapY={12}
|
gapY={BUILD_CONFIG.isMobileEdition ? 12 : view === 'list' ? 12 : 24}
|
||||||
gapX={12}
|
gapX={BUILD_CONFIG.isMobileEdition ? 12 : 24}
|
||||||
groupsGap={12}
|
groupsGap={12}
|
||||||
groupHeaderGapWithItems={12}
|
groupHeaderGapWithItems={12}
|
||||||
columns={view === 'list' ? 1 : undefined}
|
columns={view === 'list' ? 1 : undefined}
|
||||||
@@ -237,10 +241,8 @@ export const DocsExplorer = ({
|
|||||||
itemWidth={'stretch'}
|
itemWidth={'stretch'}
|
||||||
virtualScroll
|
virtualScroll
|
||||||
collapsedGroups={collapsedGroups}
|
collapsedGroups={collapsedGroups}
|
||||||
paddingX={useCallback(
|
paddingY={BUILD_CONFIG.isMobileEdition ? 12 : 0}
|
||||||
(w: number) => (w > 500 ? 24 : w > 393 ? 20 : 16),
|
paddingX={BUILD_CONFIG.isMobileEdition ? 16 : responsivePaddingX}
|
||||||
[]
|
|
||||||
)}
|
|
||||||
/>
|
/>
|
||||||
{!disableMultiDelete || onRestore ? (
|
{!disableMultiDelete || onRestore ? (
|
||||||
<ListFloatingToolbar
|
<ListFloatingToolbar
|
||||||
|
|||||||
@@ -43,6 +43,9 @@ export const DocGroupHeader = ({
|
|||||||
}, [groupId, contextValue]);
|
}, [groupId, contextValue]);
|
||||||
|
|
||||||
const handleSelectAll = useCallback(() => {
|
const handleSelectAll = useCallback(() => {
|
||||||
|
if (!contextValue.selectMode$?.value) {
|
||||||
|
contextValue.selectMode$?.next(true);
|
||||||
|
}
|
||||||
const prev = contextValue.selectedDocIds$.value;
|
const prev = contextValue.selectedDocIds$.value;
|
||||||
if (isGroupAllSelected) {
|
if (isGroupAllSelected) {
|
||||||
contextValue.selectedDocIds$.next(
|
contextValue.selectedDocIds$.next(
|
||||||
|
|||||||
@@ -2,7 +2,12 @@ import { IconButton, Menu, MenuItem, MenuSeparator } from '@affine/component';
|
|||||||
import type { FilterParams } from '@affine/core/modules/collection-rules';
|
import type { FilterParams } from '@affine/core/modules/collection-rules';
|
||||||
import { WorkspacePropertyService } from '@affine/core/modules/workspace-property';
|
import { WorkspacePropertyService } from '@affine/core/modules/workspace-property';
|
||||||
import { useI18n } from '@affine/i18n';
|
import { useI18n } from '@affine/i18n';
|
||||||
import { ArrowLeftBigIcon, FavoriteIcon, PlusIcon } from '@blocksuite/icons/rc';
|
import {
|
||||||
|
ArrowLeftBigIcon,
|
||||||
|
CloudWorkspaceIcon,
|
||||||
|
FavoriteIcon,
|
||||||
|
PlusIcon,
|
||||||
|
} from '@blocksuite/icons/rc';
|
||||||
import { useLiveData, useService } from '@toeverything/infra';
|
import { useLiveData, useService } from '@toeverything/infra';
|
||||||
import { useMemo } from 'react';
|
import { useMemo } from 'react';
|
||||||
|
|
||||||
@@ -57,7 +62,9 @@ export const AddFilterMenu = ({
|
|||||||
<span className={styles.filterTypeItemName}>{t['Favorited']()}</span>
|
<span className={styles.filterTypeItemName}>{t['Favorited']()}</span>
|
||||||
</MenuItem>
|
</MenuItem>
|
||||||
<MenuItem
|
<MenuItem
|
||||||
prefixIcon={<FavoriteIcon className={styles.filterTypeItemIcon} />}
|
prefixIcon={
|
||||||
|
<CloudWorkspaceIcon className={styles.filterTypeItemIcon} />
|
||||||
|
}
|
||||||
key={'shared'}
|
key={'shared'}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
onAdd({
|
onAdd({
|
||||||
|
|||||||
@@ -1,5 +1,9 @@
|
|||||||
import { cssVar } from '@toeverything/theme';
|
import { cssVar } from '@toeverything/theme';
|
||||||
|
import { cssVarV2 } from '@toeverything/theme/v2';
|
||||||
import { style } from '@vanilla-extract/css';
|
import { style } from '@vanilla-extract/css';
|
||||||
export const menuContent = style({
|
export const menuContent = style({
|
||||||
backgroundColor: cssVar('backgroundOverlayPanelColor'),
|
backgroundColor: cssVar('backgroundOverlayPanelColor'),
|
||||||
});
|
});
|
||||||
|
export const button = style({
|
||||||
|
backgroundColor: cssVarV2.button.secondary,
|
||||||
|
});
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ import { EdgelessIcon, ImportIcon, PageIcon } from '@blocksuite/icons/rc';
|
|||||||
import type { MouseEvent, PropsWithChildren } from 'react';
|
import type { MouseEvent, PropsWithChildren } from 'react';
|
||||||
import { useCallback, useState } from 'react';
|
import { useCallback, useState } from 'react';
|
||||||
|
|
||||||
import { menuContent } from './new-page-button.css';
|
import * as styles from './new-page-button.css';
|
||||||
|
|
||||||
type NewPageButtonProps = {
|
type NewPageButtonProps = {
|
||||||
createNewDoc: (e?: MouseEvent) => void;
|
createNewDoc: (e?: MouseEvent) => void;
|
||||||
@@ -120,7 +120,7 @@ export const NewPageButton = ({
|
|||||||
open,
|
open,
|
||||||
}}
|
}}
|
||||||
contentOptions={{
|
contentOptions={{
|
||||||
className: menuContent,
|
className: styles.menuContent,
|
||||||
align: 'end',
|
align: 'end',
|
||||||
hideWhenDetached: true,
|
hideWhenDetached: true,
|
||||||
onInteractOutside: useCallback(() => {
|
onInteractOutside: useCallback(() => {
|
||||||
@@ -133,6 +133,7 @@ export const NewPageButton = ({
|
|||||||
onClick={handleCreateNewDoc}
|
onClick={handleCreateNewDoc}
|
||||||
onAuxClick={handleCreateNewPage}
|
onAuxClick={handleCreateNewPage}
|
||||||
onClickDropDown={useCallback(() => setOpen(open => !open), [])}
|
onClickDropDown={useCallback(() => setOpen(open => !open), [])}
|
||||||
|
className={styles.button}
|
||||||
>
|
>
|
||||||
{children}
|
{children}
|
||||||
</DropdownButton>
|
</DropdownButton>
|
||||||
|
|||||||
@@ -13,6 +13,19 @@ import type { GroupHeaderProps } from '../explorer/types';
|
|||||||
import { MemberSelectorInline } from '../member-selector';
|
import { MemberSelectorInline } from '../member-selector';
|
||||||
import * as styles from './created-updated-by.css';
|
import * as styles from './created-updated-by.css';
|
||||||
|
|
||||||
|
const CreatedUsernameTip = ({ userName }: { userName: string }) => {
|
||||||
|
const i18n = useI18n();
|
||||||
|
return i18n.t('com.affine.page-properties.property.createdBy.tip', {
|
||||||
|
userName,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
const UpdatedUsernameTip = ({ userName }: { userName: string }) => {
|
||||||
|
const i18n = useI18n();
|
||||||
|
return i18n.t('com.affine.page-properties.property.updatedBy.tip', {
|
||||||
|
userName,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
const CreatedByUpdatedByAvatar = (props: {
|
const CreatedByUpdatedByAvatar = (props: {
|
||||||
type: 'CreatedBy' | 'UpdatedBy';
|
type: 'CreatedBy' | 'UpdatedBy';
|
||||||
doc: DocRecord;
|
doc: DocRecord;
|
||||||
@@ -33,6 +46,9 @@ const CreatedByUpdatedByAvatar = (props: {
|
|||||||
id={userId}
|
id={userId}
|
||||||
size={props.size}
|
size={props.size}
|
||||||
showName={props.showName}
|
showName={props.showName}
|
||||||
|
tooltip={
|
||||||
|
props.type === 'CreatedBy' ? CreatedUsernameTip : UpdatedUsernameTip
|
||||||
|
}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -33,7 +33,7 @@ export const body = style({
|
|||||||
export const scrollArea = style({
|
export const scrollArea = style({
|
||||||
height: 0,
|
height: 0,
|
||||||
flex: 1,
|
flex: 1,
|
||||||
paddingTop: '24px',
|
paddingTop: '12px',
|
||||||
});
|
});
|
||||||
|
|
||||||
// group
|
// group
|
||||||
@@ -43,7 +43,7 @@ export const pinnedCollection = style({
|
|||||||
flexDirection: 'column',
|
flexDirection: 'column',
|
||||||
gap: 8,
|
gap: 8,
|
||||||
padding: '0 24px',
|
padding: '0 24px',
|
||||||
paddingTop: '24px',
|
paddingTop: '12px',
|
||||||
'@container': {
|
'@container': {
|
||||||
'docs-body (width <= 500px)': {
|
'docs-body (width <= 500px)': {
|
||||||
padding: '0 20px',
|
padding: '0 20px',
|
||||||
|
|||||||
@@ -68,11 +68,7 @@ const AllDocs = () => {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<DocExplorerContext.Provider value={explorerContextValue}>
|
<DocExplorerContext.Provider value={explorerContextValue}>
|
||||||
<DocsExplorer
|
<DocsExplorer masonryItemWidthMin={150} />
|
||||||
masonryItemWidthMin={150}
|
|
||||||
heightBase={180}
|
|
||||||
heightScale={12}
|
|
||||||
/>
|
|
||||||
</DocExplorerContext.Provider>
|
</DocExplorerContext.Provider>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -69,11 +69,7 @@ const CollectionDocs = ({ collection }: { collection: Collection }) => {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<DocExplorerContext.Provider value={explorerContextValue}>
|
<DocExplorerContext.Provider value={explorerContextValue}>
|
||||||
<DocsExplorer
|
<DocsExplorer masonryItemWidthMin={150} />
|
||||||
masonryItemWidthMin={150}
|
|
||||||
heightBase={180}
|
|
||||||
heightScale={12}
|
|
||||||
/>
|
|
||||||
</DocExplorerContext.Provider>
|
</DocExplorerContext.Provider>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -75,11 +75,7 @@ const TagDocs = ({ tag }: { tag: Tag }) => {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<DocExplorerContext.Provider value={explorerContextValue}>
|
<DocExplorerContext.Provider value={explorerContextValue}>
|
||||||
<DocsExplorer
|
<DocsExplorer masonryItemWidthMin={150} />
|
||||||
masonryItemWidthMin={150}
|
|
||||||
heightBase={180}
|
|
||||||
heightScale={12}
|
|
||||||
/>
|
|
||||||
</DocExplorerContext.Provider>
|
</DocExplorerContext.Provider>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
import { Avatar } from '@affine/component';
|
import { Avatar, Tooltip } from '@affine/component';
|
||||||
import { useCurrentServerService } from '@affine/core/components/providers/current-server-scope';
|
import { useCurrentServerService } from '@affine/core/components/providers/current-server-scope';
|
||||||
import { useI18n } from '@affine/i18n';
|
import { useI18n } from '@affine/i18n';
|
||||||
import { useLiveData } from '@toeverything/infra';
|
import { useLiveData } from '@toeverything/infra';
|
||||||
import { useLayoutEffect, useMemo } from 'react';
|
import { type ComponentType, useLayoutEffect, useMemo } from 'react';
|
||||||
|
|
||||||
import { PublicUserService } from '../services/public-user';
|
import { PublicUserService } from '../services/public-user';
|
||||||
import * as styles from './public-user.css';
|
import * as styles from './public-user.css';
|
||||||
@@ -11,10 +11,12 @@ export const PublicUserLabel = ({
|
|||||||
id,
|
id,
|
||||||
size = 20,
|
size = 20,
|
||||||
showName = true,
|
showName = true,
|
||||||
|
tooltip: NameTip,
|
||||||
}: {
|
}: {
|
||||||
id: string;
|
id: string;
|
||||||
size?: number;
|
size?: number;
|
||||||
showName?: boolean;
|
showName?: boolean;
|
||||||
|
tooltip?: ComponentType<{ userName: string }>;
|
||||||
}) => {
|
}) => {
|
||||||
const serverService = useCurrentServerService();
|
const serverService = useCurrentServerService();
|
||||||
const publicUser = useMemo(() => {
|
const publicUser = useMemo(() => {
|
||||||
@@ -50,15 +52,23 @@ export const PublicUserLabel = ({
|
|||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<span className={styles.publicUserLabel}>
|
<Tooltip
|
||||||
<Avatar
|
content={
|
||||||
url={user?.avatar}
|
NameTip ? (
|
||||||
name={user?.name ?? ''}
|
<NameTip userName={user?.name || t['Unknown User']()} />
|
||||||
size={size}
|
) : null
|
||||||
className={styles.publicUserLabelAvatar}
|
}
|
||||||
data-show-name={showName}
|
>
|
||||||
/>
|
<span className={styles.publicUserLabel}>
|
||||||
{showName && user?.name}
|
<Avatar
|
||||||
</span>
|
url={user?.avatar}
|
||||||
|
name={user?.name ?? ''}
|
||||||
|
size={size}
|
||||||
|
className={styles.publicUserLabelAvatar}
|
||||||
|
data-show-name={showName}
|
||||||
|
/>
|
||||||
|
{showName && user?.name}
|
||||||
|
</span>
|
||||||
|
</Tooltip>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -2189,6 +2189,10 @@ export function useAFFiNEI18N(): {
|
|||||||
* `Grouping`
|
* `Grouping`
|
||||||
*/
|
*/
|
||||||
["com.affine.explorer.display-menu.grouping"](): string;
|
["com.affine.explorer.display-menu.grouping"](): string;
|
||||||
|
/**
|
||||||
|
* `Remove group`
|
||||||
|
*/
|
||||||
|
["com.affine.explorer.display-menu.grouping.remove"](): string;
|
||||||
/**
|
/**
|
||||||
* `Ordering`
|
* `Ordering`
|
||||||
*/
|
*/
|
||||||
@@ -3141,6 +3145,18 @@ export function useAFFiNEI18N(): {
|
|||||||
* `Mark this doc as a template, which can be used to create new docs.`
|
* `Mark this doc as a template, which can be used to create new docs.`
|
||||||
*/
|
*/
|
||||||
["com.affine.page-properties.property.template.tooltips"](): string;
|
["com.affine.page-properties.property.template.tooltips"](): string;
|
||||||
|
/**
|
||||||
|
* `Created by {{userName}}`
|
||||||
|
*/
|
||||||
|
["com.affine.page-properties.property.createdBy.tip"](options: {
|
||||||
|
readonly userName: string;
|
||||||
|
}): string;
|
||||||
|
/**
|
||||||
|
* `Last edited by {{userName}}`
|
||||||
|
*/
|
||||||
|
["com.affine.page-properties.property.updatedBy.tip"](options: {
|
||||||
|
readonly userName: string;
|
||||||
|
}): string;
|
||||||
/**
|
/**
|
||||||
* `Properties`
|
* `Properties`
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -546,6 +546,7 @@
|
|||||||
"com.affine.filterList.button.add": "Add filter",
|
"com.affine.filterList.button.add": "Add filter",
|
||||||
"com.affine.explorer.display-menu.button": "Display",
|
"com.affine.explorer.display-menu.button": "Display",
|
||||||
"com.affine.explorer.display-menu.grouping": "Grouping",
|
"com.affine.explorer.display-menu.grouping": "Grouping",
|
||||||
|
"com.affine.explorer.display-menu.grouping.remove": "Remove group",
|
||||||
"com.affine.explorer.display-menu.ordering": "Ordering",
|
"com.affine.explorer.display-menu.ordering": "Ordering",
|
||||||
"com.affine.header.mode-switch.page": "View in Page mode",
|
"com.affine.header.mode-switch.page": "View in Page mode",
|
||||||
"com.affine.header.mode-switch.edgeless": "View in Edgeless Canvas",
|
"com.affine.header.mode-switch.edgeless": "View in Edgeless Canvas",
|
||||||
@@ -780,6 +781,8 @@
|
|||||||
"com.affine.page-properties.property.edgelessTheme.tooltips": "Select the doc theme from Light, Dark, or System. Useful for precise control over content viewing style.",
|
"com.affine.page-properties.property.edgelessTheme.tooltips": "Select the doc theme from Light, Dark, or System. Useful for precise control over content viewing style.",
|
||||||
"com.affine.page-properties.property.pageWidth.tooltips": "Control the width of this page to fit content display needs.",
|
"com.affine.page-properties.property.pageWidth.tooltips": "Control the width of this page to fit content display needs.",
|
||||||
"com.affine.page-properties.property.template.tooltips": "Mark this doc as a template, which can be used to create new docs.",
|
"com.affine.page-properties.property.template.tooltips": "Mark this doc as a template, which can be used to create new docs.",
|
||||||
|
"com.affine.page-properties.property.createdBy.tip": "Created by {{userName}}",
|
||||||
|
"com.affine.page-properties.property.updatedBy.tip": "Last edited by {{userName}}",
|
||||||
"com.affine.propertySidebar.property-list.section": "Properties",
|
"com.affine.propertySidebar.property-list.section": "Properties",
|
||||||
"com.affine.propertySidebar.add-more.section": "Add more properties",
|
"com.affine.propertySidebar.add-more.section": "Add more properties",
|
||||||
"com.affine.page-properties.settings.title": "customize properties",
|
"com.affine.page-properties.settings.title": "customize properties",
|
||||||
|
|||||||
Reference in New Issue
Block a user