mirror of
https://github.com/toeverything/AFFiNE.git
synced 2026-02-15 05:37:32 +00:00
feat: add snapshot import export config (#8584)
[BS-1470](https://linear.app/affine-design/issue/BS-1470/提供-snapshot-导入导出开关)
This commit is contained in:
@@ -162,6 +162,14 @@ export const AFFINE_FLAGS = {
|
|||||||
configurable: false,
|
configurable: false,
|
||||||
defaultState: isMobile,
|
defaultState: isMobile,
|
||||||
},
|
},
|
||||||
|
enable_snapshot_import_export: {
|
||||||
|
category: 'affine',
|
||||||
|
displayName: 'Enable Snapshot Import Export',
|
||||||
|
description:
|
||||||
|
'Once enabled, users can import and export blocksuite snapshots',
|
||||||
|
configurable: true,
|
||||||
|
defaultState: false,
|
||||||
|
},
|
||||||
} satisfies { [key in string]: FlagInfo };
|
} satisfies { [key in string]: FlagInfo };
|
||||||
|
|
||||||
export type AFFINE_FLAGS = typeof AFFINE_FLAGS;
|
export type AFFINE_FLAGS = typeof AFFINE_FLAGS;
|
||||||
|
|||||||
@@ -13,7 +13,11 @@ import { UrlService } from '@affine/core/modules/url';
|
|||||||
import { useI18n } from '@affine/i18n';
|
import { useI18n } from '@affine/i18n';
|
||||||
import { mixpanel } from '@affine/track';
|
import { mixpanel } from '@affine/track';
|
||||||
import { ArrowRightSmallIcon, OpenInNewIcon } from '@blocksuite/icons/rc';
|
import { ArrowRightSmallIcon, OpenInNewIcon } from '@blocksuite/icons/rc';
|
||||||
import { useService } from '@toeverything/infra';
|
import {
|
||||||
|
FeatureFlagService,
|
||||||
|
useLiveData,
|
||||||
|
useServices,
|
||||||
|
} from '@toeverything/infra';
|
||||||
import { useCallback } from 'react';
|
import { useCallback } from 'react';
|
||||||
|
|
||||||
import { useAppSettingHelper } from '../../../../../components/hooks/affine/use-app-setting-helper';
|
import { useAppSettingHelper } from '../../../../../components/hooks/affine/use-app-setting-helper';
|
||||||
@@ -23,9 +27,15 @@ import { UpdateCheckSection } from './update-check-section';
|
|||||||
|
|
||||||
export const AboutAffine = () => {
|
export const AboutAffine = () => {
|
||||||
const t = useI18n();
|
const t = useI18n();
|
||||||
const urlService = useService(UrlService);
|
const { urlService, featureFlagService } = useServices({
|
||||||
|
UrlService,
|
||||||
|
FeatureFlagService,
|
||||||
|
});
|
||||||
const { appSettings, updateSettings } = useAppSettingHelper();
|
const { appSettings, updateSettings } = useAppSettingHelper();
|
||||||
const { toggleAutoCheck, toggleAutoDownload } = useAppUpdater();
|
const { toggleAutoCheck, toggleAutoDownload } = useAppUpdater();
|
||||||
|
const enableSnapshotImportExport = useLiveData(
|
||||||
|
featureFlagService.flags.enable_snapshot_import_export.$
|
||||||
|
);
|
||||||
const channel = BUILD_CONFIG.appBuildType;
|
const channel = BUILD_CONFIG.appBuildType;
|
||||||
const appIcon = appIconMap[channel];
|
const appIcon = appIconMap[channel];
|
||||||
const appName = appNames[channel];
|
const appName = appNames[channel];
|
||||||
@@ -58,6 +68,13 @@ export const AboutAffine = () => {
|
|||||||
[updateSettings]
|
[updateSettings]
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const onSwitchSnapshotImportExport = useCallback(
|
||||||
|
(checked: boolean) => {
|
||||||
|
featureFlagService.flags.enable_snapshot_import_export.set(checked);
|
||||||
|
},
|
||||||
|
[featureFlagService]
|
||||||
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<SettingHeader
|
<SettingHeader
|
||||||
@@ -141,6 +158,16 @@ export const AboutAffine = () => {
|
|||||||
{t['com.affine.aboutAFFiNE.contact.community']()}
|
{t['com.affine.aboutAFFiNE.contact.community']()}
|
||||||
<OpenInNewIcon className="icon" />
|
<OpenInNewIcon className="icon" />
|
||||||
</a>
|
</a>
|
||||||
|
<SettingRow
|
||||||
|
name={t['com.affine.snapshot.import-export.enable']()}
|
||||||
|
desc={t['com.affine.snapshot.import-export.enable.desc']()}
|
||||||
|
className={styles.snapshotImportExportRow}
|
||||||
|
>
|
||||||
|
<Switch
|
||||||
|
checked={enableSnapshotImportExport}
|
||||||
|
onChange={onSwitchSnapshotImportExport}
|
||||||
|
/>
|
||||||
|
</SettingRow>
|
||||||
</SettingWrapper>
|
</SettingWrapper>
|
||||||
<SettingWrapper title={t['com.affine.aboutAFFiNE.community.title']()}>
|
<SettingWrapper title={t['com.affine.aboutAFFiNE.community.title']()}>
|
||||||
<div className={styles.communityWrapper}>
|
<div className={styles.communityWrapper}>
|
||||||
|
|||||||
@@ -71,3 +71,6 @@ globalStyle(`${appImageRow} .right-col`, {
|
|||||||
paddingLeft: '0',
|
paddingLeft: '0',
|
||||||
paddingRight: '20px',
|
paddingRight: '20px',
|
||||||
});
|
});
|
||||||
|
export const snapshotImportExportRow = style({
|
||||||
|
marginTop: '12px',
|
||||||
|
});
|
||||||
|
|||||||
@@ -16,7 +16,11 @@ import { useEnableCloud } from '@affine/core/components/hooks/affine/use-enable-
|
|||||||
import { useExportPage } from '@affine/core/components/hooks/affine/use-export-page';
|
import { useExportPage } from '@affine/core/components/hooks/affine/use-export-page';
|
||||||
import { useTrashModalHelper } from '@affine/core/components/hooks/affine/use-trash-modal-helper';
|
import { useTrashModalHelper } from '@affine/core/components/hooks/affine/use-trash-modal-helper';
|
||||||
import { useDocMetaHelper } from '@affine/core/components/hooks/use-block-suite-page-meta';
|
import { useDocMetaHelper } from '@affine/core/components/hooks/use-block-suite-page-meta';
|
||||||
import { Export, MoveToTrash } from '@affine/core/components/page-list';
|
import {
|
||||||
|
Export,
|
||||||
|
MoveToTrash,
|
||||||
|
Snapshot,
|
||||||
|
} from '@affine/core/components/page-list';
|
||||||
import { IsFavoriteIcon } from '@affine/core/components/pure/icons';
|
import { IsFavoriteIcon } from '@affine/core/components/pure/icons';
|
||||||
import { useDetailPageHeaderResponsive } from '@affine/core/desktop/pages/workspace/detail-page/use-header-responsive';
|
import { useDetailPageHeaderResponsive } from '@affine/core/desktop/pages/workspace/detail-page/use-header-responsive';
|
||||||
import { DocInfoService } from '@affine/core/modules/doc-info';
|
import { DocInfoService } from '@affine/core/modules/doc-info';
|
||||||
@@ -44,7 +48,12 @@ import {
|
|||||||
SplitViewIcon,
|
SplitViewIcon,
|
||||||
TocIcon,
|
TocIcon,
|
||||||
} from '@blocksuite/icons/rc';
|
} from '@blocksuite/icons/rc';
|
||||||
import { useLiveData, useService, WorkspaceService } from '@toeverything/infra';
|
import {
|
||||||
|
FeatureFlagService,
|
||||||
|
useLiveData,
|
||||||
|
useService,
|
||||||
|
WorkspaceService,
|
||||||
|
} from '@toeverything/infra';
|
||||||
import { useSetAtom } from 'jotai';
|
import { useSetAtom } from 'jotai';
|
||||||
import { useCallback, useState } from 'react';
|
import { useCallback, useState } from 'react';
|
||||||
|
|
||||||
@@ -79,6 +88,10 @@ export const PageHeaderMenuButton = ({
|
|||||||
const primaryMode = useLiveData(editorService.editor.doc.primaryMode$);
|
const primaryMode = useLiveData(editorService.editor.doc.primaryMode$);
|
||||||
|
|
||||||
const workbench = useService(WorkbenchService).workbench;
|
const workbench = useService(WorkbenchService).workbench;
|
||||||
|
const featureFlagService = useService(FeatureFlagService);
|
||||||
|
const enableSnapshotImportExport = useLiveData(
|
||||||
|
featureFlagService.flags.enable_snapshot_import_export.$
|
||||||
|
);
|
||||||
|
|
||||||
const { favorite, toggleFavorite } = useFavorite(pageId);
|
const { favorite, toggleFavorite } = useFavorite(pageId);
|
||||||
|
|
||||||
@@ -357,6 +370,7 @@ export const PageHeaderMenuButton = ({
|
|||||||
{t['Import']()}
|
{t['Import']()}
|
||||||
</MenuItem>
|
</MenuItem>
|
||||||
<Export exportHandler={exportHandler} pageMode={currentMode} />
|
<Export exportHandler={exportHandler} pageMode={currentMode} />
|
||||||
|
{enableSnapshotImportExport && <Snapshot />}
|
||||||
<MenuSeparator />
|
<MenuSeparator />
|
||||||
<MoveToTrash
|
<MoveToTrash
|
||||||
data-testid="editor-option-menu-delete"
|
data-testid="editor-option-menu-delete"
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import { toast } from '@affine/component';
|
|||||||
import { AppSidebarService } from '@affine/core/modules/app-sidebar';
|
import { AppSidebarService } from '@affine/core/modules/app-sidebar';
|
||||||
import { EditorSettingService } from '@affine/core/modules/editor-setting';
|
import { EditorSettingService } from '@affine/core/modules/editor-setting';
|
||||||
import { WorkbenchService } from '@affine/core/modules/workbench';
|
import { WorkbenchService } from '@affine/core/modules/workbench';
|
||||||
import type { DocMode } from '@blocksuite/affine/blocks';
|
import { type DocMode } from '@blocksuite/affine/blocks';
|
||||||
import type { DocCollection } from '@blocksuite/affine/store';
|
import type { DocCollection } from '@blocksuite/affine/store';
|
||||||
import { type DocProps, DocsService, useServices } from '@toeverything/infra';
|
import { type DocProps, DocsService, useServices } from '@toeverything/infra';
|
||||||
import { useCallback, useMemo } from 'react';
|
import { useCallback, useMemo } from 'react';
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ import {
|
|||||||
HtmlTransformer,
|
HtmlTransformer,
|
||||||
MarkdownTransformer,
|
MarkdownTransformer,
|
||||||
printToPdf,
|
printToPdf,
|
||||||
|
ZipTransformer,
|
||||||
} from '@blocksuite/affine/blocks';
|
} from '@blocksuite/affine/blocks';
|
||||||
import type { AffineEditorContainer } from '@blocksuite/affine/presets';
|
import type { AffineEditorContainer } from '@blocksuite/affine/presets';
|
||||||
import type { Doc } from '@blocksuite/affine/store';
|
import type { Doc } from '@blocksuite/affine/store';
|
||||||
@@ -20,7 +21,7 @@ import { nanoid } from 'nanoid';
|
|||||||
|
|
||||||
import { useAsyncCallback } from '../affine-async-hooks';
|
import { useAsyncCallback } from '../affine-async-hooks';
|
||||||
|
|
||||||
type ExportType = 'pdf' | 'html' | 'png' | 'markdown';
|
type ExportType = 'pdf' | 'html' | 'png' | 'markdown' | 'snapshot';
|
||||||
|
|
||||||
interface ExportHandlerOptions {
|
interface ExportHandlerOptions {
|
||||||
page: Doc;
|
page: Doc;
|
||||||
@@ -44,6 +45,9 @@ async function exportHandler({
|
|||||||
case 'markdown':
|
case 'markdown':
|
||||||
await MarkdownTransformer.exportDoc(page);
|
await MarkdownTransformer.exportDoc(page);
|
||||||
return;
|
return;
|
||||||
|
case 'snapshot':
|
||||||
|
await ZipTransformer.exportDocs(page.collection, [page]);
|
||||||
|
return;
|
||||||
case 'pdf':
|
case 'pdf':
|
||||||
await printToPdf(editorContainer);
|
await printToPdf(editorContainer);
|
||||||
return;
|
return;
|
||||||
|
|||||||
@@ -2,3 +2,4 @@ export * from './disable-public-sharing';
|
|||||||
export * from './export';
|
export * from './export';
|
||||||
// export * from './MoveTo';
|
// export * from './MoveTo';
|
||||||
export * from './move-to-trash';
|
export * from './move-to-trash';
|
||||||
|
export * from './snapshot';
|
||||||
|
|||||||
@@ -0,0 +1,203 @@
|
|||||||
|
import { MenuItem, MenuSeparator, MenuSub, notify } from '@affine/component';
|
||||||
|
import { WorkbenchService } from '@affine/core/modules/workbench';
|
||||||
|
import { useI18n } from '@affine/i18n';
|
||||||
|
import track from '@affine/track';
|
||||||
|
import { openFileOrFiles, ZipTransformer } from '@blocksuite/affine/blocks';
|
||||||
|
import type { Doc } from '@blocksuite/affine/store';
|
||||||
|
import { ExportIcon, ImportIcon, ToneIcon } from '@blocksuite/icons/rc';
|
||||||
|
import {
|
||||||
|
FeatureFlagService,
|
||||||
|
useService,
|
||||||
|
WorkspaceService,
|
||||||
|
} from '@toeverything/infra';
|
||||||
|
import { type ReactNode, useCallback } from 'react';
|
||||||
|
|
||||||
|
import { useExportPage } from '../../hooks/affine/use-export-page';
|
||||||
|
import { useAsyncCallback } from '../../hooks/affine-async-hooks';
|
||||||
|
import { transitionStyle } from './index.css';
|
||||||
|
|
||||||
|
interface SnapshotMenuItemsProps {
|
||||||
|
snapshotActionHandler: (action: 'import' | 'export' | 'disable') => void;
|
||||||
|
className?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface SnapshotMenuItemProps<T> {
|
||||||
|
onSelect: () => void;
|
||||||
|
className?: string;
|
||||||
|
type: T;
|
||||||
|
icon: ReactNode;
|
||||||
|
label: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface SnapshotProps {
|
||||||
|
className?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function SnapshotMenuItem<T>({
|
||||||
|
onSelect,
|
||||||
|
className,
|
||||||
|
type,
|
||||||
|
icon,
|
||||||
|
label,
|
||||||
|
}: SnapshotMenuItemProps<T>) {
|
||||||
|
return (
|
||||||
|
<MenuItem
|
||||||
|
className={className}
|
||||||
|
data-testid={`snapshot-${type}`}
|
||||||
|
onSelect={onSelect}
|
||||||
|
block
|
||||||
|
prefixIcon={icon}
|
||||||
|
>
|
||||||
|
{label}
|
||||||
|
</MenuItem>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export const DisableSnapshotMenuItems = ({
|
||||||
|
snapshotActionHandler,
|
||||||
|
className = transitionStyle,
|
||||||
|
}: SnapshotMenuItemsProps) => {
|
||||||
|
const t = useI18n();
|
||||||
|
return (
|
||||||
|
<SnapshotMenuItem
|
||||||
|
onSelect={() => snapshotActionHandler('disable')}
|
||||||
|
className={className}
|
||||||
|
type="disable"
|
||||||
|
icon={<ToneIcon />}
|
||||||
|
label={t['Disable Snapshot']()}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const SnapshotMenuItems = ({
|
||||||
|
snapshotActionHandler,
|
||||||
|
className = transitionStyle,
|
||||||
|
}: SnapshotMenuItemsProps) => {
|
||||||
|
const t = useI18n();
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<SnapshotMenuItem
|
||||||
|
onSelect={() => snapshotActionHandler('import')}
|
||||||
|
className={className}
|
||||||
|
type="import"
|
||||||
|
icon={<ImportIcon />}
|
||||||
|
label={t['Import']()}
|
||||||
|
/>
|
||||||
|
<SnapshotMenuItem
|
||||||
|
onSelect={() => snapshotActionHandler('export')}
|
||||||
|
className={className}
|
||||||
|
type="export"
|
||||||
|
icon={<ExportIcon />}
|
||||||
|
label={t['Export']()}
|
||||||
|
/>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const Snapshot = ({ className }: SnapshotProps) => {
|
||||||
|
const t = useI18n();
|
||||||
|
const workspace = useService(WorkspaceService).workspace;
|
||||||
|
const docCollection = workspace.docCollection;
|
||||||
|
const workbench = useService(WorkbenchService).workbench;
|
||||||
|
const exportHandler = useExportPage();
|
||||||
|
const featureFlagService = useService(FeatureFlagService);
|
||||||
|
|
||||||
|
const importSnapshot = useCallback(async () => {
|
||||||
|
try {
|
||||||
|
const file = await openFileOrFiles({ acceptType: 'Zip' });
|
||||||
|
if (!file) return null;
|
||||||
|
|
||||||
|
const importedDocs = (
|
||||||
|
await ZipTransformer.importDocs(docCollection, file)
|
||||||
|
).filter(doc => doc !== undefined);
|
||||||
|
if (importedDocs.length === 0) {
|
||||||
|
notify.error({
|
||||||
|
title: 'Import Snapshot Failed',
|
||||||
|
message: 'No valid documents found in the imported file.',
|
||||||
|
});
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
notify.success({
|
||||||
|
title: 'Imported Snapshot Successfully',
|
||||||
|
message: `Imported ${importedDocs.length} doc(s)`,
|
||||||
|
});
|
||||||
|
return importedDocs;
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error importing snapshot:', error);
|
||||||
|
notify.error({
|
||||||
|
title: 'Import Snapshot Failed',
|
||||||
|
message: 'Failed to import snapshot. Please try again.',
|
||||||
|
});
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}, [docCollection]);
|
||||||
|
|
||||||
|
const openImportedDocs = useCallback(
|
||||||
|
(importedDocs: Doc[]) => {
|
||||||
|
if (importedDocs.length > 1) {
|
||||||
|
workbench.openAll();
|
||||||
|
} else if (importedDocs[0]?.id) {
|
||||||
|
workbench.openDoc(importedDocs[0].id);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
[workbench]
|
||||||
|
);
|
||||||
|
|
||||||
|
const handleImportSnapshot = useAsyncCallback(async () => {
|
||||||
|
const importedDocs = await importSnapshot();
|
||||||
|
if (importedDocs) {
|
||||||
|
openImportedDocs(importedDocs);
|
||||||
|
track.$.header.docOptions.import();
|
||||||
|
track.$.header.actions.createDoc({
|
||||||
|
control: 'import',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}, [importSnapshot, openImportedDocs]);
|
||||||
|
|
||||||
|
const disableSnapshotActionOption = useCallback(() => {
|
||||||
|
featureFlagService.flags.enable_snapshot_import_export.set(false);
|
||||||
|
}, [featureFlagService]);
|
||||||
|
|
||||||
|
const snapshotActionHandler = useCallback(
|
||||||
|
(action: 'import' | 'export' | 'disable') => {
|
||||||
|
switch (action) {
|
||||||
|
case 'import':
|
||||||
|
return handleImportSnapshot();
|
||||||
|
case 'export':
|
||||||
|
return exportHandler('snapshot');
|
||||||
|
case 'disable':
|
||||||
|
return disableSnapshotActionOption();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
[handleImportSnapshot, exportHandler, disableSnapshotActionOption]
|
||||||
|
);
|
||||||
|
|
||||||
|
const items = (
|
||||||
|
<>
|
||||||
|
<SnapshotMenuItems
|
||||||
|
snapshotActionHandler={snapshotActionHandler}
|
||||||
|
className={className}
|
||||||
|
/>
|
||||||
|
<MenuSeparator />
|
||||||
|
<DisableSnapshotMenuItems
|
||||||
|
snapshotActionHandler={snapshotActionHandler}
|
||||||
|
className={className}
|
||||||
|
/>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<MenuSub
|
||||||
|
items={items}
|
||||||
|
triggerOptions={{
|
||||||
|
className: transitionStyle,
|
||||||
|
prefixIcon: <ToneIcon />,
|
||||||
|
['data-testid' as string]: 'snapshot-menu',
|
||||||
|
}}
|
||||||
|
subOptions={{}}
|
||||||
|
>
|
||||||
|
{t['Snapshot']()}
|
||||||
|
</MenuSub>
|
||||||
|
);
|
||||||
|
};
|
||||||
@@ -13,7 +13,7 @@
|
|||||||
"ja": 100,
|
"ja": 100,
|
||||||
"ko": 89,
|
"ko": 89,
|
||||||
"pl": 0,
|
"pl": 0,
|
||||||
"pt-BR": 97,
|
"pt-BR": 96,
|
||||||
"ru": 82,
|
"ru": 82,
|
||||||
"sv-SE": 5,
|
"sv-SE": 5,
|
||||||
"ur": 3,
|
"ur": 3,
|
||||||
|
|||||||
@@ -27,6 +27,7 @@
|
|||||||
"Delete": "Delete",
|
"Delete": "Delete",
|
||||||
"Disable": "Disable",
|
"Disable": "Disable",
|
||||||
"Disable Public Sharing": "Disable public sharing",
|
"Disable Public Sharing": "Disable public sharing",
|
||||||
|
"Disable Snapshot": "Disable snapshot",
|
||||||
"Divider": "Divider",
|
"Divider": "Divider",
|
||||||
"Edgeless": "Edgeless",
|
"Edgeless": "Edgeless",
|
||||||
"Edit": "Edit",
|
"Edit": "Edit",
|
||||||
@@ -90,6 +91,7 @@
|
|||||||
"Sign in": "Sign in AFFiNE Cloud",
|
"Sign in": "Sign in AFFiNE Cloud",
|
||||||
"Sign in and Enable": "Sign in and enable",
|
"Sign in and Enable": "Sign in and enable",
|
||||||
"Sign out": "Sign out",
|
"Sign out": "Sign out",
|
||||||
|
"Snapshot": "Snapshot",
|
||||||
"Storage": "Storage",
|
"Storage": "Storage",
|
||||||
"Storage and Export": "Storage and export",
|
"Storage and Export": "Storage and export",
|
||||||
"Successfully deleted": "Successfully deleted",
|
"Successfully deleted": "Successfully deleted",
|
||||||
@@ -1218,6 +1220,8 @@
|
|||||||
"com.affine.shortcutsTitle.page": "Page",
|
"com.affine.shortcutsTitle.page": "Page",
|
||||||
"com.affine.sidebarSwitch.collapse": "Collapse sidebar",
|
"com.affine.sidebarSwitch.collapse": "Collapse sidebar",
|
||||||
"com.affine.sidebarSwitch.expand": "Expand sidebar",
|
"com.affine.sidebarSwitch.expand": "Expand sidebar",
|
||||||
|
"com.affine.snapshot.import-export.enable": "Snapshot Imp. & Exp.",
|
||||||
|
"com.affine.snapshot.import-export.enable.desc": "Snapshot import and export support. When your document has a data error, turn this on and ask the AFFiNE team for help. Once enabled you can find the Snapshot Export Import option in the document's More menu.",
|
||||||
"com.affine.star-affine.cancel": "Maybe later",
|
"com.affine.star-affine.cancel": "Maybe later",
|
||||||
"com.affine.star-affine.confirm": "Star on GitHub",
|
"com.affine.star-affine.confirm": "Star on GitHub",
|
||||||
"com.affine.star-affine.description": "Are you finding our app useful and enjoyable? We'd love your support to keep improving! A great way to help us out is by giving us a star on GitHub. This simple action can make a big difference and helps us continue to deliver the best experience for you.",
|
"com.affine.star-affine.description": "Are you finding our app useful and enjoyable? We'd love your support to keep improving! A great way to help us out is by giving us a star on GitHub. This simple action can make a big difference and helps us continue to deliver the best experience for you.",
|
||||||
|
|||||||
@@ -27,6 +27,7 @@
|
|||||||
"Delete": "删除",
|
"Delete": "删除",
|
||||||
"Disable": "禁用",
|
"Disable": "禁用",
|
||||||
"Disable Public Sharing": "禁用公开分享",
|
"Disable Public Sharing": "禁用公开分享",
|
||||||
|
"Disable Snapshot": "禁用快照",
|
||||||
"Divider": "分割线",
|
"Divider": "分割线",
|
||||||
"Edgeless": "无界",
|
"Edgeless": "无界",
|
||||||
"Edit": "编辑",
|
"Edit": "编辑",
|
||||||
@@ -90,6 +91,7 @@
|
|||||||
"Sign in": "登录 AFFiNE Cloud",
|
"Sign in": "登录 AFFiNE Cloud",
|
||||||
"Sign in and Enable": "登录并启用",
|
"Sign in and Enable": "登录并启用",
|
||||||
"Sign out": "登出 AFFiNE 云",
|
"Sign out": "登出 AFFiNE 云",
|
||||||
|
"Snapshot": "快照",
|
||||||
"Storage": "储存",
|
"Storage": "储存",
|
||||||
"Storage and Export": "储存与导出",
|
"Storage and Export": "储存与导出",
|
||||||
"Successfully deleted": "成功删除。",
|
"Successfully deleted": "成功删除。",
|
||||||
@@ -1195,6 +1197,8 @@
|
|||||||
"com.affine.shortcutsTitle.page": "页面",
|
"com.affine.shortcutsTitle.page": "页面",
|
||||||
"com.affine.sidebarSwitch.collapse": "折叠侧边栏",
|
"com.affine.sidebarSwitch.collapse": "折叠侧边栏",
|
||||||
"com.affine.sidebarSwitch.expand": "展开侧边栏",
|
"com.affine.sidebarSwitch.expand": "展开侧边栏",
|
||||||
|
"com.affine.snapshot.import-export.enable": "启用快照导入导出",
|
||||||
|
"com.affine.snapshot.import-export.enable.desc": "支持快照导入和导出。当您的文档出现数据错误时,请启用此功能并寻求 AFFiNE 团队的帮助。启用后,您可以在文档的更多菜单中找到快照导出导入选项。",
|
||||||
"com.affine.star-affine.cancel": "稍后",
|
"com.affine.star-affine.cancel": "稍后",
|
||||||
"com.affine.star-affine.confirm": "在 GitHub 点亮星标",
|
"com.affine.star-affine.confirm": "在 GitHub 点亮星标",
|
||||||
"com.affine.star-affine.description": "您觉得我们的应用程序有用且有趣吗? 我们希望得到您的支持,以不断进步! 帮助我们的一个好方法是在 GitHub 给我们一颗星。 这个简单的行动可以给我们很大的激励,并帮助我们继续为您提供最佳体验。",
|
"com.affine.star-affine.description": "您觉得我们的应用程序有用且有趣吗? 我们希望得到您的支持,以不断进步! 帮助我们的一个好方法是在 GitHub 给我们一颗星。 这个简单的行动可以给我们很大的激励,并帮助我们继续为您提供最佳体验。",
|
||||||
|
|||||||
@@ -27,6 +27,7 @@
|
|||||||
"Delete": "刪除",
|
"Delete": "刪除",
|
||||||
"Disable": "停用",
|
"Disable": "停用",
|
||||||
"Disable Public Sharing": "停用公開分享",
|
"Disable Public Sharing": "停用公開分享",
|
||||||
|
"Disable Snapshot": "停用快照",
|
||||||
"Divider": "分割線",
|
"Divider": "分割線",
|
||||||
"Edgeless": "無界",
|
"Edgeless": "無界",
|
||||||
"Edit": "編輯",
|
"Edit": "編輯",
|
||||||
@@ -90,6 +91,7 @@
|
|||||||
"Sign in": "登入 AFFiNE Cloud",
|
"Sign in": "登入 AFFiNE Cloud",
|
||||||
"Sign in and Enable": "登入並啟用",
|
"Sign in and Enable": "登入並啟用",
|
||||||
"Sign out": "登出",
|
"Sign out": "登出",
|
||||||
|
"Snapshot": "快照",
|
||||||
"Storage": "儲存",
|
"Storage": "儲存",
|
||||||
"Storage and Export": "存儲和匯出",
|
"Storage and Export": "存儲和匯出",
|
||||||
"Successfully deleted": "已成功刪除",
|
"Successfully deleted": "已成功刪除",
|
||||||
@@ -1176,6 +1178,8 @@
|
|||||||
"com.affine.shortcutsTitle.page": "頁面",
|
"com.affine.shortcutsTitle.page": "頁面",
|
||||||
"com.affine.sidebarSwitch.collapse": "收起側欄",
|
"com.affine.sidebarSwitch.collapse": "收起側欄",
|
||||||
"com.affine.sidebarSwitch.expand": "展開側欄",
|
"com.affine.sidebarSwitch.expand": "展開側欄",
|
||||||
|
"com.affine.snapshot.import-export.enable": "啟用快照匯入匯出",
|
||||||
|
"com.affine.snapshot.import-export.enable.desc": "支援快照匯入和匯出。當您的文件出現資料錯誤時,請啟用此功能並尋求 AFFiNE 團隊的協助。啟用後,您可以在文件的更多選單中找到快照匯出匯入選項。",
|
||||||
"com.affine.star-affine.cancel": "稍後",
|
"com.affine.star-affine.cancel": "稍後",
|
||||||
"com.affine.star-affine.confirm": "在 GitHub 點亮星標",
|
"com.affine.star-affine.confirm": "在 GitHub 點亮星標",
|
||||||
"com.affine.star-affine.description": "您覺得我們的應用程序有用且有趣嗎?我們希望得到您的支持,以不斷進步!幫助我們的一個好方法是在 GitHub 給我們一顆星。這個簡單的行動可以給我們很大的激勵,並幫助我們繼續為您提供最佳體驗。",
|
"com.affine.star-affine.description": "您覺得我們的應用程序有用且有趣嗎?我們希望得到您的支持,以不斷進步!幫助我們的一個好方法是在 GitHub 給我們一顆星。這個簡單的行動可以給我們很大的激勵,並幫助我們繼續為您提供最佳體驗。",
|
||||||
|
|||||||
Reference in New Issue
Block a user