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

@@ -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}>