chore(core): remove snapshot import export feature flag (#8865)

This commit is contained in:
Chen
2024-11-20 15:06:20 +08:00
committed by GitHub
parent 47243247b9
commit b87c3840f3
19 changed files with 120 additions and 331 deletions

View File

@@ -206,16 +206,6 @@ export const AFFINE_FLAGS = {
configurable: false,
defaultState: isMobile,
},
enable_snapshot_import_export: {
category: 'affine',
displayName:
'com.affine.settings.workspace.experimental-features.enable-snapshot-import-export.name',
description:
'com.affine.settings.workspace.experimental-features.enable-snapshot-import-export.description',
hide: true,
configurable: true,
defaultState: false,
},
} satisfies { [key in string]: FlagInfo };
export type AFFINE_FLAGS = typeof AFFINE_FLAGS;

View File

@@ -14,7 +14,7 @@
"@affine/core": "workspace:*",
"@affine/i18n": "workspace:*",
"@blocksuite/affine": "0.17.32",
"@blocksuite/icons": "^2.1.69",
"@blocksuite/icons": "^2.1.70",
"@capacitor/android": "^6.1.2",
"@capacitor/core": "^6.1.2",
"@sentry/react": "^8.0.0",

View File

@@ -16,7 +16,7 @@
"@affine/core": "workspace:*",
"@affine/i18n": "workspace:*",
"@blocksuite/affine": "0.17.32",
"@blocksuite/icons": "^2.1.69",
"@blocksuite/icons": "^2.1.70",
"@capacitor/app": "^6.0.1",
"@capacitor/browser": "^6.0.3",
"@capacitor/core": "^6.1.2",

View File

@@ -14,7 +14,7 @@
"@affine/core": "workspace:*",
"@affine/i18n": "workspace:*",
"@blocksuite/affine": "0.17.32",
"@blocksuite/icons": "^2.1.69",
"@blocksuite/icons": "^2.1.70",
"@sentry/react": "^8.0.0",
"react": "^18.2.0",
"react-dom": "^18.2.0",

View File

@@ -14,7 +14,7 @@
},
"peerDependencies": {
"@blocksuite/affine": "*",
"@blocksuite/icons": "2.1.67"
"@blocksuite/icons": "2.1.68"
},
"dependencies": {
"@affine/cli": "workspace:*",
@@ -24,7 +24,7 @@
"@affine/i18n": "workspace:*",
"@atlaskit/pragmatic-drag-and-drop": "^1.2.1",
"@atlaskit/pragmatic-drag-and-drop-hitbox": "^1.0.3",
"@blocksuite/icons": "2.1.69",
"@blocksuite/icons": "2.1.70",
"@emotion/react": "^11.11.4",
"@emotion/styled": "^11.11.5",
"@radix-ui/react-avatar": "^1.0.4",
@@ -64,7 +64,7 @@
},
"devDependencies": {
"@blocksuite/affine": "0.17.32",
"@blocksuite/icons": "2.1.69",
"@blocksuite/icons": "2.1.70",
"@chromatic-com/storybook": "^3.0.0",
"@storybook/addon-essentials": "^8.2.9",
"@storybook/addon-interactions": "^8.2.9",

View File

@@ -17,7 +17,7 @@
"@affine/templates": "workspace:*",
"@affine/track": "workspace:*",
"@blocksuite/affine": "0.17.32",
"@blocksuite/icons": "2.1.69",
"@blocksuite/icons": "2.1.70",
"@capacitor/app": "^6.0.1",
"@capacitor/browser": "^6.0.3",
"@dnd-kit/core": "^6.1.0",

View File

@@ -11,11 +11,7 @@ import { useBlockSuiteMetaHelper } from '@affine/core/components/hooks/affine/us
import { useEnableCloud } from '@affine/core/components/hooks/affine/use-enable-cloud';
import { useExportPage } from '@affine/core/components/hooks/affine/use-export-page';
import { useDocMetaHelper } from '@affine/core/components/hooks/use-block-suite-page-meta';
import {
Export,
MoveToTrash,
Snapshot,
} from '@affine/core/components/page-list';
import { Export, MoveToTrash } from '@affine/core/components/page-list';
import { IsFavoriteIcon } from '@affine/core/components/pure/icons';
import { useDetailPageHeaderResponsive } from '@affine/core/desktop/pages/workspace/detail-page/use-header-responsive';
import { WorkspaceDialogService } from '@affine/core/modules/dialogs';
@@ -44,7 +40,6 @@ import {
TocIcon,
} from '@blocksuite/icons/rc';
import {
FeatureFlagService,
useLiveData,
useService,
useServiceOptional,
@@ -84,10 +79,6 @@ export const PageHeaderMenuButton = ({
const primaryMode = useLiveData(editorService.editor.doc.primaryMode$);
const workbench = useService(WorkbenchService).workbench;
const featureFlagService = useService(FeatureFlagService);
const enableSnapshotImportExport = useLiveData(
featureFlagService.flags.enable_snapshot_import_export.$
);
const openInAppService = useServiceOptional(OpenInAppService);
const { favorite, toggleFavorite } = useFavorite(pageId);
@@ -402,7 +393,6 @@ export const PageHeaderMenuButton = ({
{t['Import']()}
</MenuItem>
<Export exportHandler={exportHandler} pageMode={currentMode} />
{enableSnapshotImportExport && <Snapshot />}
<MenuSeparator />
<MoveToTrash
data-testid="editor-option-menu-delete"

View File

@@ -260,6 +260,23 @@ export function useRegisterBlocksuiteEditorCommands(editor: Editor) {
})
);
unsubs.push(
registerAffineCommand({
id: `editor:${mode}-export-to-snapshot`,
preconditionStrategy,
category: `editor:${mode}`,
icon: mode === 'page' ? <PageIcon /> : <EdgelessIcon />,
label: t['Export to Snapshot'](),
async run() {
track.$.cmdk.editor.export({
type: 'snapshot',
});
exportHandler('snapshot');
},
})
);
unsubs.push(
registerAffineCommand({
id: `editor:${mode}-move-to-trash`,

View File

@@ -6,6 +6,7 @@ import {
ExportToHtmlIcon,
ExportToMarkdownIcon,
ExportToPngIcon,
PageIcon,
PrinterIcon,
} from '@blocksuite/icons/rc';
import type { ReactNode } from 'react';
@@ -22,7 +23,9 @@ interface ExportMenuItemProps<T> {
}
interface ExportProps {
exportHandler: (type: 'pdf' | 'html' | 'png' | 'markdown') => void;
exportHandler: (
type: 'pdf' | 'html' | 'png' | 'markdown' | 'snapshot'
) => void;
pageMode?: 'page' | 'edgeless';
className?: string;
}
@@ -94,6 +97,13 @@ export const ExportMenuItems = ({
icon={<ExportToMarkdownIcon />}
label={t['Export to Markdown']()}
/>
<ExportMenuItem
onSelect={() => exportHandler('snapshot')}
className={className}
type="snapshot"
icon={<PageIcon />}
label={t['Export to Snapshot']()}
/>
</>
);
};

View File

@@ -2,4 +2,3 @@ export * from './disable-public-sharing';
export * from './export';
// export * from './MoveTo';
export * from './move-to-trash';
export * from './snapshot';

View File

@@ -1,223 +0,0 @@
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;
track.$.header.snapshot.import({
type: 'snapshot',
status: 'importing',
});
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)`,
});
track.$.header.snapshot.import({
type: 'snapshot',
status: 'success',
result: {
docCount: importedDocs.length,
},
});
return importedDocs;
} catch (error) {
console.error('Error importing snapshot:', error);
notify.error({
title: 'Import Snapshot Failed',
message: 'Failed to import snapshot. Please try again.',
});
track.$.header.snapshot.import({
type: 'snapshot',
status: 'failed',
error: error instanceof Error ? error.message : undefined,
});
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':
track.$.header.snapshot.export({
type: 'snapshot',
});
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': 'snapshot-menu',
}}
subOptions={{}}
>
{t['Snapshot']()}
</MenuSub>
);
};

View File

@@ -8,6 +8,7 @@ import { UrlService } from '@affine/core/modules/url';
import { useI18n } from '@affine/i18n';
import track from '@affine/track';
import {
HtmlTransformer,
MarkdownTransformer,
NotionHtmlTransformer,
openFileOrFiles,
@@ -15,24 +16,22 @@ import {
} from '@blocksuite/affine/blocks';
import type { DocCollection } from '@blocksuite/affine/store';
import {
ExportToHtmlIcon,
ExportToMarkdownIcon,
HelpIcon,
NotionIcon,
PageIcon,
ZipIcon,
} from '@blocksuite/icons/rc';
import {
FeatureFlagService,
useLiveData,
useService,
WorkspaceService,
} from '@toeverything/infra';
import { useService, WorkspaceService } from '@toeverything/infra';
import { cssVar } from '@toeverything/theme';
import { cssVarV2 } from '@toeverything/theme/v2';
import { type ReactElement, useCallback, useState } from 'react';
import * as style from './styles.css';
type ImportType = 'markdown' | 'markdownZip' | 'notion' | 'snapshot';
type AcceptType = 'Markdown' | 'Zip';
type ImportType = 'markdown' | 'markdownZip' | 'notion' | 'snapshot' | 'html';
type AcceptType = 'Markdown' | 'Zip' | 'Html';
type Status = 'idle' | 'importing' | 'success' | 'error';
type ImportResult = {
docIds: string[];
@@ -68,14 +67,31 @@ const importOptions = [
key: 'markdownZip',
label: 'com.affine.import.markdown-with-media-files',
prefixIcon: (
<ExportToMarkdownIcon
<ZipIcon color={cssVarV2('icon/primary')} width={20} height={20} />
),
suffixIcon: (
<HelpIcon color={cssVarV2('icon/primary')} width={20} height={20} />
),
suffixTooltip: 'com.affine.import.markdown-with-media-files.tooltip',
testId: 'editor-option-menu-import-markdown-with-media',
type: 'markdownZip' as ImportType,
},
{
key: 'html',
label: 'com.affine.import.html-files',
prefixIcon: (
<ExportToHtmlIcon
color={cssVarV2('icon/primary')}
width={20}
height={20}
/>
),
testId: 'editor-option-menu-import-markdown-with-media',
type: 'markdownZip' as ImportType,
suffixIcon: (
<HelpIcon color={cssVarV2('icon/primary')} width={20} height={20} />
),
suffixTooltip: 'com.affine.import.html-files.tooltip',
testId: 'editor-option-menu-import-html',
type: 'html' as ImportType,
},
{
key: 'notion',
@@ -88,6 +104,15 @@ const importOptions = [
testId: 'editor-option-menu-import-notion',
type: 'notion' as ImportType,
},
{
key: 'snapshot',
label: 'com.affine.import.snapshot',
prefixIcon: (
<PageIcon color={cssVarV2('icon/primary')} width={20} height={20} />
),
testId: 'editor-option-menu-import-snapshot',
type: 'snapshot' as ImportType,
},
];
const importConfigs: Record<ImportType, ImportConfig> = {
@@ -128,6 +153,28 @@ const importConfigs: Record<ImportType, ImportConfig> = {
};
},
},
html: {
fileOptions: { acceptType: 'Html', multiple: true },
importFunction: async (docCollection, files) => {
if (!Array.isArray(files)) {
throw new Error('Expected an array of files for html files import');
}
const docIds: string[] = [];
for (const file of files) {
const text = await file.text();
const fileName = file.name.split('.').slice(0, -1).join('.');
const docId = await HtmlTransformer.importHTMLToDoc({
collection: docCollection,
html: text,
fileName,
});
if (docId) docIds.push(docId);
}
return {
docIds,
};
},
},
notion: {
fileOptions: { acceptType: 'Zip', multiple: false },
importFunction: async (docCollection, file) => {
@@ -200,10 +247,6 @@ const ImportOptions = ({
onImport: (type: ImportType) => void;
}) => {
const t = useI18n();
const featureFlagService = useService(FeatureFlagService);
const enableSnapshotImportExport = useLiveData(
featureFlagService.flags.enable_snapshot_import_export.$
);
return (
<>
@@ -232,14 +275,6 @@ const ImportOptions = ({
)
)}
</div>
{enableSnapshotImportExport && (
<div className={style.importModalTip}>
{t['Import']()}{' '}
<span className={style.link} onClick={() => onImport('snapshot')}>
{t['Snapshot']()}.
</span>
</div>
)}
<div className={style.importModalTip}>
{t['com.affine.import.modal.tip']()}{' '}
<a
@@ -373,6 +408,9 @@ export const ImportDialog = ({
docCount: docIds.length,
},
});
track.$.importModal.$.createDoc({
control: 'import',
});
} catch (error) {
const errorMessage =
error instanceof Error ? error.message : 'Unknown error occurred';

View File

@@ -10,11 +10,7 @@ import { appIconMap, appNames } from '@affine/core/utils/channel';
import { useI18n } from '@affine/i18n';
import { mixpanel } from '@affine/track';
import { ArrowRightSmallIcon, OpenInNewIcon } from '@blocksuite/icons/rc';
import {
FeatureFlagService,
useLiveData,
useServices,
} from '@toeverything/infra';
import { useServices } from '@toeverything/infra';
import { useCallback } from 'react';
import { useAppSettingHelper } from '../../../../../components/hooks/affine/use-app-setting-helper';
@@ -29,13 +25,9 @@ export const AboutAffine = () => {
const channel = BUILD_CONFIG.appBuildType;
const appIcon = appIconMap[channel];
const appName = appNames[channel];
const { urlService, featureFlagService } = useServices({
const { urlService } = useServices({
UrlService,
FeatureFlagService,
});
const enableSnapshotImportExport = useLiveData(
featureFlagService.flags.enable_snapshot_import_export.$
);
const onSwitchAutoCheck = useCallback(
(checked: boolean) => {
@@ -65,13 +57,6 @@ export const AboutAffine = () => {
[updateSettings]
);
const onSwitchSnapshotImportExport = useCallback(
(checked: boolean) => {
featureFlagService.flags.enable_snapshot_import_export.set(checked);
},
[featureFlagService]
);
return (
<>
<SettingHeader
@@ -155,16 +140,6 @@ export const AboutAffine = () => {
{t['com.affine.aboutAFFiNE.contact.community']()}
<OpenInNewIcon className="icon" />
</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 title={t['com.affine.aboutAFFiNE.community.title']()}>
<div className={styles.communityWrapper}>

View File

@@ -1,21 +1,21 @@
{
"ar": 76,
"ar": 75,
"ca": 5,
"da": 6,
"de": 28,
"el-GR": 0,
"en": 100,
"es-AR": 14,
"es-CL": 16,
"es-CL": 15,
"es": 14,
"fr": 67,
"hi": 2,
"it-IT": 1,
"it": 1,
"ja": 100,
"ko": 80,
"ko": 79,
"pl": 0,
"pt-BR": 87,
"pt-BR": 86,
"ru": 74,
"sv-SE": 4,
"ur": 3,

View File

@@ -526,8 +526,11 @@
"com.affine.import-template.dialog.errorLoad": "Failed to load template, please try again.",
"com.affine.import_file": "Support Markdown/Notion",
"com.affine.import.affine-workspace-data": "AFFiNE workspace data",
"com.affine.import.html-files": "HTML",
"com.affine.import.html-files.tooltip": "This is an experimental feature that is not perfect and may cause your data to be missing after import.",
"com.affine.import.markdown-files": "Markdown files (.md)",
"com.affine.import.markdown-with-media-files": "Markdown with media files (.zip)",
"com.affine.import.markdown-with-media-files.tooltip": "Please upload a markdown zip file with attachments, experimental function, there may be data loss.",
"com.affine.import.modal.tip": "If you'd like to request support for additional file types, feel free to let us know on",
"com.affine.import.notion": "Notion",
"com.affine.import.notion.tooltip": "Import your Notion data. Supported import formats: HTML with subpages.",

View File

@@ -526,8 +526,11 @@
"com.affine.import-template.dialog.errorLoad": "读取模版失败,请重试。",
"com.affine.import_file": "支持 Markdown/Notion",
"com.affine.import.affine-workspace-data": "AFFiNE 工作区数据",
"com.affine.import.html-files": "HTML",
"com.affine.import.html-files.tooltip": "这是一个实验性功能,可能不完全完美,导入后可能会导致数据丢失。",
"com.affine.import.markdown-files": "Markdown 文件 (.md)",
"com.affine.import.markdown-with-media-files": "Markdown 文件(带媒体文件) (.zip)",
"com.affine.import.markdown-with-media-files.tooltip": "请上传带有附件的 Markdown 压缩包,这是一个实验性功能,可能不完全完美,导入后可能会导致数据丢失。",
"com.affine.import.modal.tip": "如果您希望请求支持其他文件类型,请随时告诉我们",
"com.affine.import.notion": "Notion",
"com.affine.import.notion.tooltip": "导入您的 Notion 数据。支持导入格式HTML 带子页面。",

View File

@@ -526,8 +526,11 @@
"com.affine.import-template.dialog.errorLoad": "加載模板失敗,請重試。",
"com.affine.import_file": "支援 Markdown/Notion",
"com.affine.import.affine-workspace-data": "AFFiNE 工作區數據",
"com.affine.import.html-files": "HTML",
"com.affine.import.html-files.tooltip": "這是一個實驗性功能,可能不完全完美,導入後可能會導致數據丟失。",
"com.affine.import.markdown-files": "Markdown 文件 (.md)",
"com.affine.import.markdown-with-media-files": "Markdown 文件(帶媒體文件) (.zip)",
"com.affine.import.markdown-with-media-files.tooltip": "請上傳帶有附件的 Markdown 壓縮包,這是一個實驗性功能,可能不完全完美,導入後可能會導致數據丟失。",
"com.affine.import.modal.tip": "如果您希望請求支持其他文件類型,請隨時告訴我們",
"com.affine.import.notion": "Notion",
"com.affine.import.notion.tooltip": "導入您的 Notion 數據。支持導入格式HTML 帶子頁面。",

View File

@@ -236,7 +236,7 @@ const PageEvents = {
$: ['open', 'close', 'switchPageMode', 'viewPlans'],
},
importModal: {
$: ['open', 'import'],
$: ['open', 'import', 'createDoc'],
},
paywall: {
storage: ['viewPlans'],