feat(component): shortcut style for tooltip (#7721)
 - New `shortcut` prop for `<Tooltip />` - single key ```tsx <Tooltip shortcut="T" /> ``` - multiple ```tsx <Tooltip shortcut={["⌘", "K"]} /> ``` - Round tooltip's arrow - Use new design system colors - Replace some usage - App sidebar switch - Editor mode switch - New tab (new)
This commit is contained in:
@@ -64,7 +64,8 @@ export interface ButtonProps
|
|||||||
suffixStyle?: CSSProperties;
|
suffixStyle?: CSSProperties;
|
||||||
|
|
||||||
tooltip?: TooltipProps['content'];
|
tooltip?: TooltipProps['content'];
|
||||||
tooltipOptions?: Partial<Omit<TooltipProps, 'content'>>;
|
tooltipShortcut?: TooltipProps['shortcut'];
|
||||||
|
tooltipOptions?: Partial<Omit<TooltipProps, 'content' | 'shortcut'>>;
|
||||||
}
|
}
|
||||||
|
|
||||||
const IconSlot = ({
|
const IconSlot = ({
|
||||||
@@ -113,6 +114,7 @@ export const Button = forwardRef<HTMLButtonElement, ButtonProps>(
|
|||||||
contentStyle,
|
contentStyle,
|
||||||
|
|
||||||
tooltip,
|
tooltip,
|
||||||
|
tooltipShortcut,
|
||||||
tooltipOptions,
|
tooltipOptions,
|
||||||
onClick,
|
onClick,
|
||||||
|
|
||||||
@@ -129,7 +131,7 @@ export const Button = forwardRef<HTMLButtonElement, ButtonProps>(
|
|||||||
);
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Tooltip content={tooltip} {...tooltipOptions}>
|
<Tooltip content={tooltip} shortcut={tooltipShortcut} {...tooltipOptions}>
|
||||||
<button
|
<button
|
||||||
{...otherProps}
|
{...otherProps}
|
||||||
ref={ref}
|
ref={ref}
|
||||||
|
|||||||
@@ -1,11 +1,46 @@
|
|||||||
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 tooltipContent = style({
|
export const tooltipContent = style({
|
||||||
backgroundColor: cssVar('tooltip'),
|
backgroundColor: cssVarV2('tooltips/background'),
|
||||||
color: cssVar('white'),
|
color: cssVarV2('tooltips/foreground'),
|
||||||
padding: '5px 12px',
|
padding: '5px 12px',
|
||||||
fontSize: cssVar('fontSm'),
|
fontSize: cssVar('fontSm'),
|
||||||
lineHeight: '22px',
|
lineHeight: '22px',
|
||||||
borderRadius: '4px',
|
borderRadius: '4px',
|
||||||
maxWidth: '280px',
|
maxWidth: '280px',
|
||||||
});
|
});
|
||||||
|
|
||||||
|
export const withShortcut = style({
|
||||||
|
display: 'flex',
|
||||||
|
gap: 10,
|
||||||
|
});
|
||||||
|
export const withShortcutContent = style({
|
||||||
|
whiteSpace: 'nowrap',
|
||||||
|
textOverflow: 'ellipsis',
|
||||||
|
overflow: 'hidden',
|
||||||
|
});
|
||||||
|
|
||||||
|
export const shortcut = style({
|
||||||
|
display: 'flex',
|
||||||
|
alignItems: 'center',
|
||||||
|
gap: 2,
|
||||||
|
});
|
||||||
|
export const command = style({
|
||||||
|
background: cssVarV2('tooltips/secondaryBackground'),
|
||||||
|
fontSize: cssVar('fontXs'),
|
||||||
|
fontWeight: 400,
|
||||||
|
lineHeight: '20px',
|
||||||
|
height: 16,
|
||||||
|
minWidth: 16,
|
||||||
|
display: 'flex',
|
||||||
|
alignItems: 'center',
|
||||||
|
justifyContent: 'center',
|
||||||
|
padding: '0 4px',
|
||||||
|
borderRadius: 4,
|
||||||
|
selectors: {
|
||||||
|
'&[data-length="1"]': {
|
||||||
|
width: 16,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
import type { Meta, StoryFn } from '@storybook/react';
|
import type { Meta, StoryFn } from '@storybook/react';
|
||||||
|
import { useState } from 'react';
|
||||||
|
|
||||||
import { Button } from '../button';
|
import { Button } from '../button';
|
||||||
|
import { RadioGroup } from '../radio';
|
||||||
import type { TooltipProps } from './index';
|
import type { TooltipProps } from './index';
|
||||||
import Tooltip from './index';
|
import Tooltip from './index';
|
||||||
|
|
||||||
@@ -18,6 +20,77 @@ const Template: StoryFn<TooltipProps> = args => (
|
|||||||
export const Default: StoryFn<TooltipProps> = Template.bind(undefined);
|
export const Default: StoryFn<TooltipProps> = Template.bind(undefined);
|
||||||
Default.args = {};
|
Default.args = {};
|
||||||
|
|
||||||
|
export const WithShortCut = () => {
|
||||||
|
const shortCuts = [
|
||||||
|
['Text', 'T'],
|
||||||
|
['Bold', ['⌘', 'B']],
|
||||||
|
['Quick Search', ['⌘', 'K']],
|
||||||
|
['Share', ['⌘', 'Shift', 'S']],
|
||||||
|
['Copy', ['$mod', '$shift', 'C']],
|
||||||
|
] as Array<[string, string | string[]]>;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div style={{ display: 'flex', gap: 4 }}>
|
||||||
|
{shortCuts.map(([name, shortcut]) => (
|
||||||
|
<Tooltip shortcut={shortcut} content={name} key={name}>
|
||||||
|
<Button>{name}</Button>
|
||||||
|
</Tooltip>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const CustomAlign = () => {
|
||||||
|
const [align, setAlign] = useState('center' as const);
|
||||||
|
const _ = undefined;
|
||||||
|
const positions = [
|
||||||
|
// [top, left, right, bottom, translateX, translateY]
|
||||||
|
[0, 0, _, _, _, _],
|
||||||
|
[0, '50%', _, _, '-50%', _],
|
||||||
|
[0, _, 0, _, _, _],
|
||||||
|
['50%', 0, _, _, _, '-50%'],
|
||||||
|
['50%', _, 0, _, _, '-50%'],
|
||||||
|
[_, 0, _, 0, _, _],
|
||||||
|
[_, '50%', _, 0, '-50%', _],
|
||||||
|
[_, _, 0, 0, _, _],
|
||||||
|
];
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<RadioGroup
|
||||||
|
items={['start', 'center', 'end']}
|
||||||
|
value={align}
|
||||||
|
onChange={setAlign}
|
||||||
|
/>
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
width: '100%',
|
||||||
|
height: 200,
|
||||||
|
position: 'relative',
|
||||||
|
border: '1px solid rgba(100,100,100,0.2)',
|
||||||
|
marginTop: 40,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{positions.map(pos => {
|
||||||
|
const key = pos.join('-');
|
||||||
|
const style = {
|
||||||
|
position: 'absolute',
|
||||||
|
top: pos[0],
|
||||||
|
left: pos[1],
|
||||||
|
right: pos[2],
|
||||||
|
bottom: pos[3],
|
||||||
|
transform: `translate(${pos[4] ?? 0}, ${pos[5] ?? 0})`,
|
||||||
|
} as const;
|
||||||
|
return (
|
||||||
|
<Tooltip align={align} content="This is a tooltip" key={key}>
|
||||||
|
<Button style={style}>Show tooltip</Button>
|
||||||
|
</Tooltip>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
export const WithCustomContent: StoryFn<TooltipProps> = args => (
|
export const WithCustomContent: StoryFn<TooltipProps> = args => (
|
||||||
<Tooltip
|
<Tooltip
|
||||||
content={
|
content={
|
||||||
|
|||||||
@@ -4,15 +4,36 @@ import type {
|
|||||||
TooltipProps as RootProps,
|
TooltipProps as RootProps,
|
||||||
} from '@radix-ui/react-tooltip';
|
} from '@radix-ui/react-tooltip';
|
||||||
import * as TooltipPrimitive from '@radix-ui/react-tooltip';
|
import * as TooltipPrimitive from '@radix-ui/react-tooltip';
|
||||||
|
import { cssVar } from '@toeverything/theme';
|
||||||
|
import { cssVarV2 } from '@toeverything/theme/v2';
|
||||||
import clsx from 'clsx';
|
import clsx from 'clsx';
|
||||||
import type { ReactElement, ReactNode } from 'react';
|
import { type ReactElement, type ReactNode } from 'react';
|
||||||
|
|
||||||
|
import { getCommand } from '../../utils/keyboard-mapping';
|
||||||
import * as styles from './styles.css';
|
import * as styles from './styles.css';
|
||||||
|
|
||||||
export interface TooltipProps {
|
export interface TooltipProps {
|
||||||
// `children` can not be string, number or even undefined
|
// `children` can not be string, number or even undefined
|
||||||
children: ReactElement;
|
children: ReactElement;
|
||||||
content?: ReactNode;
|
content?: ReactNode;
|
||||||
|
/**
|
||||||
|
* When shortcut is provided, will use a single line layout
|
||||||
|
*
|
||||||
|
* ```tsx
|
||||||
|
* <Tooltip shortcut="T" /> // [T]
|
||||||
|
* <Tooltip shortcut="⌘ + K" /> // [⌘ + K]
|
||||||
|
* <Tooltip shortcut={['⌘', 'K']} /> // [⌘] [K]
|
||||||
|
* <Tooltip shortcut={['$mod', 'K']} /> // [⌘] [K] or [Ctrl] [K]
|
||||||
|
* ```
|
||||||
|
*
|
||||||
|
* Mapping:
|
||||||
|
* | Shortcut | macOS | Windows |
|
||||||
|
* |----------|-------|---------|
|
||||||
|
* | `$mod` | `⌘` | `Ctrl` |
|
||||||
|
* | `$alt` | `⌥` | `Alt` |
|
||||||
|
* | `$shift` | `⇧` | `Shift` |
|
||||||
|
*/
|
||||||
|
shortcut?: string | string[];
|
||||||
side?: TooltipContentProps['side'];
|
side?: TooltipContentProps['side'];
|
||||||
align?: TooltipContentProps['align'];
|
align?: TooltipContentProps['align'];
|
||||||
|
|
||||||
@@ -21,11 +42,32 @@ export interface TooltipProps {
|
|||||||
options?: Omit<TooltipContentProps, 'side' | 'align'>;
|
options?: Omit<TooltipContentProps, 'side' | 'align'>;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const TooltipShortcut = ({ shortcut }: { shortcut: string | string[] }) => {
|
||||||
|
const commands = (Array.isArray(shortcut) ? shortcut : [shortcut])
|
||||||
|
.map(cmd => cmd.trim())
|
||||||
|
.map(cmd => getCommand(cmd));
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className={styles.shortcut}>
|
||||||
|
{commands.map((cmd, index) => (
|
||||||
|
<div
|
||||||
|
key={`${index}-${cmd}`}
|
||||||
|
className={styles.command}
|
||||||
|
data-length={cmd.length}
|
||||||
|
>
|
||||||
|
{cmd}
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
export const Tooltip = ({
|
export const Tooltip = ({
|
||||||
children,
|
children,
|
||||||
content,
|
content,
|
||||||
side = 'top',
|
side = 'top',
|
||||||
align = 'center',
|
align = 'center',
|
||||||
|
shortcut,
|
||||||
options,
|
options,
|
||||||
rootOptions,
|
rootOptions,
|
||||||
portalOptions,
|
portalOptions,
|
||||||
@@ -34,6 +76,7 @@ export const Tooltip = ({
|
|||||||
return children;
|
return children;
|
||||||
}
|
}
|
||||||
const { className, ...contentOptions } = options || {};
|
const { className, ...contentOptions } = options || {};
|
||||||
|
const { style: contentStyle, ...restContentOptions } = contentOptions;
|
||||||
return (
|
return (
|
||||||
<TooltipPrimitive.Provider>
|
<TooltipPrimitive.Provider>
|
||||||
<TooltipPrimitive.Root delayDuration={500} {...rootOptions}>
|
<TooltipPrimitive.Root delayDuration={500} {...rootOptions}>
|
||||||
@@ -45,15 +88,31 @@ export const Tooltip = ({
|
|||||||
side={side}
|
side={side}
|
||||||
align={align}
|
align={align}
|
||||||
sideOffset={5}
|
sideOffset={5}
|
||||||
style={{ zIndex: 'var(--affine-z-index-popover)' }}
|
style={{ zIndex: cssVar('zIndexPopover'), ...contentStyle }}
|
||||||
{...contentOptions}
|
{...restContentOptions}
|
||||||
>
|
>
|
||||||
{content}
|
{shortcut ? (
|
||||||
<TooltipPrimitive.Arrow
|
<div className={styles.withShortcut}>
|
||||||
height={6}
|
<div className={styles.withShortcutContent}>{content}</div>
|
||||||
width={10}
|
<TooltipShortcut shortcut={shortcut} />
|
||||||
fill="var(--affine-tooltip)"
|
</div>
|
||||||
/>
|
) : (
|
||||||
|
content
|
||||||
|
)}
|
||||||
|
<TooltipPrimitive.Arrow asChild>
|
||||||
|
<svg
|
||||||
|
width="10"
|
||||||
|
height="6"
|
||||||
|
viewBox="0 0 10 6"
|
||||||
|
fill="none"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
d="M4.11111 5.55C4.55556 6.15 5.44445 6.15 5.88889 5.55L10 0H0L4.11111 5.55Z"
|
||||||
|
fill={cssVarV2('tooltips/background')}
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
</TooltipPrimitive.Arrow>
|
||||||
</TooltipPrimitive.Content>
|
</TooltipPrimitive.Content>
|
||||||
</TooltipPrimitive.Portal>
|
</TooltipPrimitive.Portal>
|
||||||
</TooltipPrimitive.Root>
|
</TooltipPrimitive.Root>
|
||||||
|
|||||||
10
packages/frontend/component/src/utils/keyboard-mapping.ts
Normal file
10
packages/frontend/component/src/utils/keyboard-mapping.ts
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
import { isMacOS } from './platform';
|
||||||
|
|
||||||
|
const macOS = isMacOS();
|
||||||
|
|
||||||
|
export const getCommand = (cmd: '$mod' | '$shift' | '$alt' | string) => {
|
||||||
|
if (cmd === '$mod') return macOS ? '⌘' : 'Ctrl';
|
||||||
|
if (cmd === '$alt') return macOS ? '⌥' : 'Alt';
|
||||||
|
if (cmd === '$shift') return macOS ? '⇧' : 'Shift';
|
||||||
|
return cmd;
|
||||||
|
};
|
||||||
4
packages/frontend/component/src/utils/platform.ts
Normal file
4
packages/frontend/component/src/utils/platform.ts
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
export function isMacOS() {
|
||||||
|
if (typeof navigator === 'undefined') return false;
|
||||||
|
return navigator.userAgent.indexOf('Mac') !== -1;
|
||||||
|
}
|
||||||
@@ -18,9 +18,6 @@ export const SidebarSwitch = ({
|
|||||||
const tooltipContent = open
|
const tooltipContent = open
|
||||||
? t['com.affine.sidebarSwitch.collapse']()
|
? t['com.affine.sidebarSwitch.collapse']()
|
||||||
: t['com.affine.sidebarSwitch.expand']();
|
: t['com.affine.sidebarSwitch.expand']();
|
||||||
// TODO(@CatsJuice): Tooltip shortcut style
|
|
||||||
const collapseKeyboardShortcuts =
|
|
||||||
environment.isBrowser && environment.isMacOs ? ' ⌘+/' : ' Ctrl+/';
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
@@ -29,7 +26,8 @@ export const SidebarSwitch = ({
|
|||||||
data-testid={`app-sidebar-arrow-button-${open ? 'collapse' : 'expand'}`}
|
data-testid={`app-sidebar-arrow-button-${open ? 'collapse' : 'expand'}`}
|
||||||
>
|
>
|
||||||
<IconButton
|
<IconButton
|
||||||
tooltip={tooltipContent + ' ' + collapseKeyboardShortcuts}
|
tooltip={tooltipContent}
|
||||||
|
tooltipShortcut={['$mod', '/']}
|
||||||
tooltipOptions={{ side: open ? 'bottom' : 'right' }}
|
tooltipOptions={{ side: open ? 'bottom' : 'right' }}
|
||||||
className={className}
|
className={className}
|
||||||
size="24"
|
size="24"
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ import { useCallback, useEffect } from 'react';
|
|||||||
|
|
||||||
import type { DocCollection } from '../../../shared';
|
import type { DocCollection } from '../../../shared';
|
||||||
import { toast } from '../../../utils';
|
import { toast } from '../../../utils';
|
||||||
import { StyledEditorModeSwitch, StyledKeyboardItem } from './style';
|
import { StyledEditorModeSwitch } from './style';
|
||||||
import { EdgelessSwitchItem, PageSwitchItem } from './switch-items';
|
import { EdgelessSwitchItem, PageSwitchItem } from './switch-items';
|
||||||
|
|
||||||
export type EditorModeSwitchProps = {
|
export type EditorModeSwitchProps = {
|
||||||
@@ -24,17 +24,7 @@ export type EditorModeSwitchProps = {
|
|||||||
isPublic?: boolean;
|
isPublic?: boolean;
|
||||||
publicMode?: DocMode;
|
publicMode?: DocMode;
|
||||||
};
|
};
|
||||||
const TooltipContent = () => {
|
|
||||||
const t = useI18n();
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
{t['Switch']()}
|
|
||||||
<StyledKeyboardItem>
|
|
||||||
{!environment.isServer && environment.isMacOs ? '⌥ + S' : 'Alt + S'}
|
|
||||||
</StyledKeyboardItem>
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
export const EditorModeSwitch = ({
|
export const EditorModeSwitch = ({
|
||||||
style,
|
style,
|
||||||
docCollection,
|
docCollection,
|
||||||
@@ -106,7 +96,9 @@ export const EditorModeSwitch = ({
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<Tooltip
|
<Tooltip
|
||||||
content={<TooltipContent />}
|
content={t['Switch']()}
|
||||||
|
shortcut={['$alt', 'S']}
|
||||||
|
side="bottom"
|
||||||
options={{
|
options={{
|
||||||
hidden: isPublic || trash,
|
hidden: isPublic || trash,
|
||||||
}}
|
}}
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import { displayFlex, styled } from '@affine/component';
|
import { displayFlex, styled } from '@affine/component';
|
||||||
|
|
||||||
|
// TODO(@CatsJuice): refactor this component
|
||||||
export const StyledEditorModeSwitch = styled('div')<{
|
export const StyledEditorModeSwitch = styled('div')<{
|
||||||
switchLeft: boolean;
|
switchLeft: boolean;
|
||||||
showAlone?: boolean;
|
showAlone?: boolean;
|
||||||
@@ -59,14 +60,3 @@ export const StyledSwitchItem = styled('button')<{
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
export const StyledKeyboardItem = styled('span')(() => {
|
|
||||||
return {
|
|
||||||
marginLeft: '10px',
|
|
||||||
fontSize: 'var(--affine-font-xs)',
|
|
||||||
paddingLeft: '5px',
|
|
||||||
paddingRight: '5px',
|
|
||||||
backgroundColor: 'var(--affine-white-10)',
|
|
||||||
borderRadius: '4px',
|
|
||||||
};
|
|
||||||
});
|
|
||||||
|
|||||||
@@ -16,6 +16,7 @@ import { WindowsAppControls } from '@affine/core/components/pure/header/windows-
|
|||||||
import { useAsyncCallback } from '@affine/core/hooks/affine-async-hooks';
|
import { useAsyncCallback } from '@affine/core/hooks/affine-async-hooks';
|
||||||
import type { AffineDNDData } from '@affine/core/types/dnd';
|
import type { AffineDNDData } from '@affine/core/types/dnd';
|
||||||
import { apis, events } from '@affine/electron-api';
|
import { apis, events } from '@affine/electron-api';
|
||||||
|
import { useI18n } from '@affine/i18n';
|
||||||
import { CloseIcon, PlusIcon, RightSidebarIcon } from '@blocksuite/icons/rc';
|
import { CloseIcon, PlusIcon, RightSidebarIcon } from '@blocksuite/icons/rc';
|
||||||
import {
|
import {
|
||||||
useLiveData,
|
useLiveData,
|
||||||
@@ -223,6 +224,7 @@ export const AppTabsHeader = ({
|
|||||||
className?: string;
|
className?: string;
|
||||||
left?: ReactNode;
|
left?: ReactNode;
|
||||||
}) => {
|
}) => {
|
||||||
|
const t = useI18n();
|
||||||
const sidebarWidth = useAtomValue(appSidebarWidthAtom);
|
const sidebarWidth = useAtomValue(appSidebarWidthAtom);
|
||||||
const sidebarOpen = useAtomValue(appSidebarOpenAtom);
|
const sidebarOpen = useAtomValue(appSidebarOpenAtom);
|
||||||
const sidebarFloating = useAtomValue(appSidebarFloatingAtom);
|
const sidebarFloating = useAtomValue(appSidebarFloatingAtom);
|
||||||
@@ -365,6 +367,8 @@ export const AppTabsHeader = ({
|
|||||||
<IconButton
|
<IconButton
|
||||||
size={22.86}
|
size={22.86}
|
||||||
onClick={onAddTab}
|
onClick={onAddTab}
|
||||||
|
tooltip={t['com.affine.multi-tab.new-tab']()}
|
||||||
|
tooltipShortcut={['$mod', 'T']}
|
||||||
data-testid="add-tab-view-button"
|
data-testid="add-tab-view-button"
|
||||||
style={{ width: 32, height: 32 }}
|
style={{ width: 32, height: 32 }}
|
||||||
icon={<PlusIcon />}
|
icon={<PlusIcon />}
|
||||||
|
|||||||
@@ -36,7 +36,7 @@ export const tabs = style({
|
|||||||
display: 'flex',
|
display: 'flex',
|
||||||
flexDirection: 'row',
|
flexDirection: 'row',
|
||||||
alignItems: 'center',
|
alignItems: 'center',
|
||||||
padding: '0 8px',
|
paddingLeft: 8,
|
||||||
overflow: 'clip',
|
overflow: 'clip',
|
||||||
height: '100%',
|
height: '100%',
|
||||||
selectors: {
|
selectors: {
|
||||||
|
|||||||
@@ -1417,5 +1417,6 @@
|
|||||||
"com.affine.ai.template-insert.failed": "Failed to insert template, please try again.",
|
"com.affine.ai.template-insert.failed": "Failed to insert template, please try again.",
|
||||||
"com.affine.selector-doc.search-placeholder": "Search docs...",
|
"com.affine.selector-doc.search-placeholder": "Search docs...",
|
||||||
"com.affine.selector-collection.search.placeholder": "Search collections...",
|
"com.affine.selector-collection.search.placeholder": "Search collections...",
|
||||||
"com.affine.selector-tag.search.placeholder": "Search tags..."
|
"com.affine.selector-tag.search.placeholder": "Search tags...",
|
||||||
|
"com.affine.multi-tab.new-tab": "New Tab"
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user