mirror of
https://github.com/toeverything/AFFiNE.git
synced 2024-12-23 20:11:43 +03:00
feat(core): use new print pdf api (#7932)
This commit is contained in:
parent
cf086e4018
commit
3db95bafa2
@ -49,7 +49,7 @@
|
|||||||
"@radix-ui/react-toolbar": "^1.0.4",
|
"@radix-ui/react-toolbar": "^1.0.4",
|
||||||
"@radix-ui/react-tooltip": "^1.0.7",
|
"@radix-ui/react-tooltip": "^1.0.7",
|
||||||
"@radix-ui/react-visually-hidden": "^1.1.0",
|
"@radix-ui/react-visually-hidden": "^1.1.0",
|
||||||
"@toeverything/theme": "^1.0.5",
|
"@toeverything/theme": "^1.0.7",
|
||||||
"@vanilla-extract/dynamic": "^2.1.0",
|
"@vanilla-extract/dynamic": "^2.1.0",
|
||||||
"bytes": "^3.1.2",
|
"bytes": "^3.1.2",
|
||||||
"check-password-strength": "^2.0.10",
|
"check-password-strength": "^2.0.10",
|
||||||
|
@ -48,7 +48,7 @@
|
|||||||
"@sentry/integrations": "^7.109.0",
|
"@sentry/integrations": "^7.109.0",
|
||||||
"@sentry/react": "^8.0.0",
|
"@sentry/react": "^8.0.0",
|
||||||
"@sgtpooki/file-type": "^1.0.1",
|
"@sgtpooki/file-type": "^1.0.1",
|
||||||
"@toeverything/theme": "^1.0.5",
|
"@toeverything/theme": "^1.0.7",
|
||||||
"@vanilla-extract/dynamic": "^2.1.0",
|
"@vanilla-extract/dynamic": "^2.1.0",
|
||||||
"animejs": "^3.2.2",
|
"animejs": "^3.2.2",
|
||||||
"async-call-rpc": "^6.4.2",
|
"async-call-rpc": "^6.4.2",
|
||||||
|
@ -42,6 +42,11 @@ export const tableHeaderInfoRow = style({
|
|||||||
fontSize: cssVar('fontSm'),
|
fontSize: cssVar('fontSm'),
|
||||||
fontWeight: 500,
|
fontWeight: 500,
|
||||||
minHeight: 34,
|
minHeight: 34,
|
||||||
|
'@media': {
|
||||||
|
print: {
|
||||||
|
display: 'none',
|
||||||
|
},
|
||||||
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
export const tableHeaderSecondaryRow = style({
|
export const tableHeaderSecondaryRow = style({
|
||||||
@ -54,6 +59,11 @@ export const tableHeaderSecondaryRow = style({
|
|||||||
padding: '0 6px',
|
padding: '0 6px',
|
||||||
gap: '8px',
|
gap: '8px',
|
||||||
height: 24,
|
height: 24,
|
||||||
|
'@media': {
|
||||||
|
print: {
|
||||||
|
display: 'none',
|
||||||
|
},
|
||||||
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
export const tableHeaderCollapseButtonWrapper = style({
|
export const tableHeaderCollapseButtonWrapper = style({
|
||||||
@ -101,12 +111,26 @@ export const tableHeaderDivider = style({
|
|||||||
borderTop: `0.5px solid ${cssVar('borderColor')}`,
|
borderTop: `0.5px solid ${cssVar('borderColor')}`,
|
||||||
width: '100%',
|
width: '100%',
|
||||||
margin: '8px 0',
|
margin: '8px 0',
|
||||||
|
'@media': {
|
||||||
|
print: {
|
||||||
|
display: 'none',
|
||||||
|
},
|
||||||
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
export const tableBodyRoot = style({
|
export const tableBodyRoot = style({
|
||||||
display: 'flex',
|
display: 'flex',
|
||||||
flexDirection: 'column',
|
flexDirection: 'column',
|
||||||
gap: 8,
|
gap: 8,
|
||||||
|
'@media': {
|
||||||
|
print: {
|
||||||
|
selectors: {
|
||||||
|
'&[data-state="open"]': {
|
||||||
|
marginBottom: 32,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
export const tableBodySortable = style({
|
export const tableBodySortable = style({
|
||||||
@ -124,6 +148,11 @@ export const addPropertyButton = style({
|
|||||||
height: 36,
|
height: 36,
|
||||||
fontWeight: 400,
|
fontWeight: 400,
|
||||||
gap: 6,
|
gap: 6,
|
||||||
|
'@media': {
|
||||||
|
print: {
|
||||||
|
display: 'none',
|
||||||
|
},
|
||||||
|
},
|
||||||
});
|
});
|
||||||
globalStyle(`${addPropertyButton} svg`, {
|
globalStyle(`${addPropertyButton} svg`, {
|
||||||
fontSize: 16,
|
fontSize: 16,
|
||||||
|
@ -148,6 +148,11 @@ export const rowContainerStyle = style({
|
|||||||
alignItems: 'center',
|
alignItems: 'center',
|
||||||
padding: '4px',
|
padding: '4px',
|
||||||
});
|
});
|
||||||
|
export const exportContainerStyle = style({
|
||||||
|
display: 'flex',
|
||||||
|
flexDirection: 'column',
|
||||||
|
gap: '8px',
|
||||||
|
});
|
||||||
export const labelStyle = style({
|
export const labelStyle = style({
|
||||||
fontSize: cssVar('fontSm'),
|
fontSize: cssVar('fontSm'),
|
||||||
fontWeight: 500,
|
fontWeight: 500,
|
||||||
|
@ -1,20 +1,22 @@
|
|||||||
import { ExportMenuItems } from '@affine/core/components/page-list';
|
import {
|
||||||
|
ExportMenuItems,
|
||||||
|
PrintMenuItems,
|
||||||
|
} from '@affine/core/components/page-list';
|
||||||
import { useExportPage } from '@affine/core/hooks/affine/use-export-page';
|
import { useExportPage } from '@affine/core/hooks/affine/use-export-page';
|
||||||
import { EditorService } from '@affine/core/modules/editor';
|
import { EditorService } from '@affine/core/modules/editor';
|
||||||
import { useI18n } from '@affine/i18n';
|
import { useI18n } from '@affine/i18n';
|
||||||
import { useLiveData, useService } from '@toeverything/infra';
|
import { useLiveData, useService } from '@toeverything/infra';
|
||||||
|
|
||||||
import * as styles from './index.css';
|
import * as styles from './index.css';
|
||||||
import type { ShareMenuProps } from './share-menu';
|
|
||||||
|
|
||||||
export const ShareExport = ({ currentPage }: ShareMenuProps) => {
|
export const ShareExport = () => {
|
||||||
const t = useI18n();
|
const t = useI18n();
|
||||||
const editor = useService(EditorService).editor;
|
const editor = useService(EditorService).editor;
|
||||||
const exportHandler = useExportPage(currentPage);
|
const exportHandler = useExportPage();
|
||||||
const currentMode = useLiveData(editor.mode$);
|
const currentMode = useLiveData(editor.mode$);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<div className={styles.exportContainerStyle}>
|
||||||
<div className={styles.descriptionStyle}>
|
<div className={styles.descriptionStyle}>
|
||||||
{t['com.affine.share-menu.ShareViaExportDescription']()}
|
{t['com.affine.share-menu.ShareViaExportDescription']()}
|
||||||
</div>
|
</div>
|
||||||
@ -25,6 +27,19 @@ export const ShareExport = ({ currentPage }: ShareMenuProps) => {
|
|||||||
pageMode={currentMode}
|
pageMode={currentMode}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</>
|
{currentMode === 'page' && (
|
||||||
|
<>
|
||||||
|
<div className={styles.descriptionStyle}>
|
||||||
|
{t['com.affine.share-menu.ShareViaPrintDescription']()}
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<PrintMenuItems
|
||||||
|
exportHandler={exportHandler}
|
||||||
|
className={styles.exportItemStyle}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
@ -39,7 +39,7 @@ export const ShareMenuContent = (props: ShareMenuProps) => {
|
|||||||
<SharePage {...props} />
|
<SharePage {...props} />
|
||||||
</Tabs.Content>
|
</Tabs.Content>
|
||||||
<Tabs.Content value="export">
|
<Tabs.Content value="export">
|
||||||
<ShareExport {...props} />
|
<ShareExport />
|
||||||
</Tabs.Content>
|
</Tabs.Content>
|
||||||
</Tabs.Root>
|
</Tabs.Root>
|
||||||
</div>
|
</div>
|
||||||
|
@ -14,6 +14,11 @@ export const container = style({
|
|||||||
padding: '0 24px',
|
padding: '0 24px',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
'@media': {
|
||||||
|
print: {
|
||||||
|
display: 'none',
|
||||||
|
},
|
||||||
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
export const dividerContainer = style({
|
export const dividerContainer = style({
|
||||||
|
@ -168,6 +168,9 @@ export const BlocksuiteEditorContainer = forwardRef<
|
|||||||
get mode() {
|
get mode() {
|
||||||
return mode;
|
return mode;
|
||||||
},
|
},
|
||||||
|
get origin() {
|
||||||
|
return rootRef.current;
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
const proxy = new Proxy(api, {
|
const proxy = new Proxy(api, {
|
||||||
|
@ -162,7 +162,7 @@ export const PageHeaderMenuButton = ({
|
|||||||
}
|
}
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
const exportHandler = useExportPage(editorService.editor.doc.blockSuiteDoc);
|
const exportHandler = useExportPage();
|
||||||
|
|
||||||
const handleDuplicate = useCallback(() => {
|
const handleDuplicate = useCallback(() => {
|
||||||
duplicate(pageId);
|
duplicate(pageId);
|
||||||
|
@ -1,15 +1,15 @@
|
|||||||
import { MenuItem, MenuSub } from '@affine/component';
|
import { MenuItem, MenuSeparator, MenuSub } from '@affine/component';
|
||||||
import { track } from '@affine/core/mixpanel';
|
import { track } from '@affine/core/mixpanel';
|
||||||
import { useI18n } from '@affine/i18n';
|
import { useI18n } from '@affine/i18n';
|
||||||
import {
|
import {
|
||||||
ExportIcon,
|
ExportIcon,
|
||||||
ExportToHtmlIcon,
|
ExportToHtmlIcon,
|
||||||
ExportToMarkdownIcon,
|
ExportToMarkdownIcon,
|
||||||
ExportToPdfIcon,
|
|
||||||
ExportToPngIcon,
|
ExportToPngIcon,
|
||||||
|
FileIcon,
|
||||||
} from '@blocksuite/icons/rc';
|
} from '@blocksuite/icons/rc';
|
||||||
import type { ReactNode } from 'react';
|
import type { ReactNode } from 'react';
|
||||||
import { useCallback, useMemo } from 'react';
|
import { useCallback } from 'react';
|
||||||
|
|
||||||
import { transitionStyle } from './index.css';
|
import { transitionStyle } from './index.css';
|
||||||
|
|
||||||
@ -22,7 +22,7 @@ interface ExportMenuItemProps<T> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
interface ExportProps {
|
interface ExportProps {
|
||||||
exportHandler: (type: 'pdf' | 'html' | 'png' | 'markdown') => Promise<void>;
|
exportHandler: (type: 'pdf' | 'html' | 'png' | 'markdown') => void;
|
||||||
pageMode?: 'page' | 'edgeless';
|
pageMode?: 'page' | 'edgeless';
|
||||||
className?: string;
|
className?: string;
|
||||||
}
|
}
|
||||||
@ -47,74 +47,73 @@ export function ExportMenuItem<T>({
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const PrintMenuItems = ({
|
||||||
|
exportHandler,
|
||||||
|
className = transitionStyle,
|
||||||
|
}: ExportProps) => {
|
||||||
|
const t = useI18n();
|
||||||
|
return (
|
||||||
|
<ExportMenuItem
|
||||||
|
onSelect={() => exportHandler('pdf')}
|
||||||
|
className={className}
|
||||||
|
type="pdf"
|
||||||
|
icon={<FileIcon />}
|
||||||
|
label={t['com.affine.export.print']()}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
export const ExportMenuItems = ({
|
export const ExportMenuItems = ({
|
||||||
exportHandler,
|
exportHandler,
|
||||||
className = transitionStyle,
|
className = transitionStyle,
|
||||||
pageMode = 'page',
|
pageMode = 'page',
|
||||||
}: ExportProps) => {
|
}: ExportProps) => {
|
||||||
const t = useI18n();
|
const t = useI18n();
|
||||||
const itemMap = useMemo(
|
return (
|
||||||
() => [
|
<>
|
||||||
{
|
<ExportMenuItem
|
||||||
component: ExportMenuItem,
|
onSelect={() => exportHandler('html')}
|
||||||
props: {
|
className={className}
|
||||||
onSelect: () => exportHandler('pdf'),
|
type="html"
|
||||||
className: className,
|
icon={<ExportToHtmlIcon />}
|
||||||
type: 'pdf',
|
label={t['Export to HTML']()}
|
||||||
icon: <ExportToPdfIcon />,
|
/>
|
||||||
label: t['Export to PDF'](),
|
{pageMode !== 'edgeless' && (
|
||||||
},
|
<ExportMenuItem
|
||||||
},
|
onSelect={() => exportHandler('png')}
|
||||||
{
|
className={className}
|
||||||
component: ExportMenuItem,
|
type="png"
|
||||||
props: {
|
icon={<ExportToPngIcon />}
|
||||||
onSelect: () => exportHandler('html'),
|
label={t['Export to PNG']()}
|
||||||
className: className,
|
/>
|
||||||
type: 'html',
|
)}
|
||||||
icon: <ExportToHtmlIcon />,
|
<ExportMenuItem
|
||||||
label: t['Export to HTML'](),
|
onSelect={() => exportHandler('markdown')}
|
||||||
},
|
className={className}
|
||||||
},
|
type="markdown"
|
||||||
{
|
icon={<ExportToMarkdownIcon />}
|
||||||
component: ExportMenuItem,
|
label={t['Export to Markdown']()}
|
||||||
props: {
|
/>
|
||||||
onSelect: () => exportHandler('png'),
|
</>
|
||||||
className: className,
|
|
||||||
type: 'png',
|
|
||||||
icon: <ExportToPngIcon />,
|
|
||||||
label: t['Export to PNG'](),
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
component: ExportMenuItem,
|
|
||||||
props: {
|
|
||||||
onSelect: () => exportHandler('markdown'),
|
|
||||||
className: className,
|
|
||||||
type: 'markdown',
|
|
||||||
icon: <ExportToMarkdownIcon />,
|
|
||||||
label: t['Export to Markdown'](),
|
|
||||||
},
|
|
||||||
},
|
|
||||||
],
|
|
||||||
[className, exportHandler, t]
|
|
||||||
);
|
);
|
||||||
const items = itemMap.map(({ component: Component, props }) =>
|
|
||||||
pageMode === 'edgeless' &&
|
|
||||||
(props.type === 'pdf' || props.type === 'png') ? null : (
|
|
||||||
<Component key={props.label} {...props} />
|
|
||||||
)
|
|
||||||
);
|
|
||||||
return items;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export const Export = ({ exportHandler, className, pageMode }: ExportProps) => {
|
export const Export = ({ exportHandler, className, pageMode }: ExportProps) => {
|
||||||
const t = useI18n();
|
const t = useI18n();
|
||||||
const items = (
|
const items = (
|
||||||
<ExportMenuItems
|
<>
|
||||||
exportHandler={exportHandler}
|
<ExportMenuItems
|
||||||
className={className}
|
exportHandler={exportHandler}
|
||||||
pageMode={pageMode}
|
className={className}
|
||||||
/>
|
pageMode={pageMode}
|
||||||
|
/>
|
||||||
|
{pageMode !== 'edgeless' && (
|
||||||
|
<>
|
||||||
|
<MenuSeparator />
|
||||||
|
<PrintMenuItems exportHandler={exportHandler} className={className} />
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
);
|
);
|
||||||
const handleExportMenuOpenChange = useCallback((open: boolean) => {
|
const handleExportMenuOpenChange = useCallback((open: boolean) => {
|
||||||
if (open) {
|
if (open) {
|
||||||
|
@ -4,23 +4,35 @@ import {
|
|||||||
resolveGlobalLoadingEventAtom,
|
resolveGlobalLoadingEventAtom,
|
||||||
} from '@affine/component/global-loading';
|
} from '@affine/component/global-loading';
|
||||||
import { track } from '@affine/core/mixpanel';
|
import { track } from '@affine/core/mixpanel';
|
||||||
import { apis } from '@affine/electron-api';
|
import { EditorService } from '@affine/core/modules/editor';
|
||||||
import { useI18n } from '@affine/i18n';
|
import { useI18n } from '@affine/i18n';
|
||||||
import type { PageRootService, RootBlockModel } from '@blocksuite/blocks';
|
import type { PageRootService } from '@blocksuite/blocks';
|
||||||
import { HtmlTransformer, MarkdownTransformer } from '@blocksuite/blocks';
|
import {
|
||||||
|
HtmlTransformer,
|
||||||
|
MarkdownTransformer,
|
||||||
|
printToPdf,
|
||||||
|
} from '@blocksuite/blocks';
|
||||||
|
import type { AffineEditorContainer } from '@blocksuite/presets';
|
||||||
import type { Doc } from '@blocksuite/store';
|
import type { Doc } from '@blocksuite/store';
|
||||||
|
import { useLiveData, useService } from '@toeverything/infra';
|
||||||
import { useSetAtom } from 'jotai';
|
import { useSetAtom } from 'jotai';
|
||||||
import { nanoid } from 'nanoid';
|
import { nanoid } from 'nanoid';
|
||||||
import { useCallback } from 'react';
|
|
||||||
|
import { useAsyncCallback } from '../affine-async-hooks';
|
||||||
|
|
||||||
type ExportType = 'pdf' | 'html' | 'png' | 'markdown';
|
type ExportType = 'pdf' | 'html' | 'png' | 'markdown';
|
||||||
|
|
||||||
interface ExportHandlerOptions {
|
interface ExportHandlerOptions {
|
||||||
page: Doc;
|
page: Doc;
|
||||||
|
editorContainer: AffineEditorContainer;
|
||||||
type: ExportType;
|
type: ExportType;
|
||||||
}
|
}
|
||||||
|
|
||||||
async function exportHandler({ page, type }: ExportHandlerOptions) {
|
async function exportHandler({
|
||||||
|
page,
|
||||||
|
type,
|
||||||
|
editorContainer,
|
||||||
|
}: ExportHandlerOptions) {
|
||||||
const editorRoot = document.querySelector('editor-host');
|
const editorRoot = document.querySelector('editor-host');
|
||||||
let pageService: PageRootService | null = null;
|
let pageService: PageRootService | null = null;
|
||||||
if (editorRoot) {
|
if (editorRoot) {
|
||||||
@ -37,15 +49,8 @@ async function exportHandler({ page, type }: ExportHandlerOptions) {
|
|||||||
await MarkdownTransformer.exportDoc(page);
|
await MarkdownTransformer.exportDoc(page);
|
||||||
break;
|
break;
|
||||||
case 'pdf':
|
case 'pdf':
|
||||||
if (environment.isDesktop && page.meta?.mode === 'page') {
|
await printToPdf(editorContainer);
|
||||||
await apis?.export.savePDFFileAs(
|
return;
|
||||||
(page.root as RootBlockModel).title.toString()
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
if (!pageService) return;
|
|
||||||
await pageService.exportManager.exportPdf();
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case 'png': {
|
case 'png': {
|
||||||
if (!pageService) return;
|
if (!pageService) return;
|
||||||
await pageService.exportManager.exportPng();
|
await pageService.exportManager.exportPng();
|
||||||
@ -54,21 +59,31 @@ async function exportHandler({ page, type }: ExportHandlerOptions) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export const useExportPage = (page: Doc) => {
|
export const useExportPage = () => {
|
||||||
|
const editor = useService(EditorService).editor;
|
||||||
|
const editorContainer = useLiveData(editor.editorContainer$);
|
||||||
|
const blocksuiteDoc = editor.doc.blockSuiteDoc;
|
||||||
const pushGlobalLoadingEvent = useSetAtom(pushGlobalLoadingEventAtom);
|
const pushGlobalLoadingEvent = useSetAtom(pushGlobalLoadingEventAtom);
|
||||||
const resolveGlobalLoadingEvent = useSetAtom(resolveGlobalLoadingEventAtom);
|
const resolveGlobalLoadingEvent = useSetAtom(resolveGlobalLoadingEventAtom);
|
||||||
const t = useI18n();
|
const t = useI18n();
|
||||||
|
|
||||||
const onClickHandler = useCallback(
|
const onClickHandler = useAsyncCallback(
|
||||||
async (type: ExportType) => {
|
async (type: ExportType) => {
|
||||||
|
if (editorContainer === null) return;
|
||||||
|
|
||||||
|
// editor container is wrapped by a proxy, we need to get the origin
|
||||||
|
const originEditorContainer = (editorContainer as any)
|
||||||
|
.origin as AffineEditorContainer;
|
||||||
|
|
||||||
const globalLoadingID = nanoid();
|
const globalLoadingID = nanoid();
|
||||||
pushGlobalLoadingEvent({
|
pushGlobalLoadingEvent({
|
||||||
key: globalLoadingID,
|
key: globalLoadingID,
|
||||||
});
|
});
|
||||||
try {
|
try {
|
||||||
await exportHandler({
|
await exportHandler({
|
||||||
page,
|
page: blocksuiteDoc,
|
||||||
type,
|
type,
|
||||||
|
editorContainer: originEditorContainer,
|
||||||
});
|
});
|
||||||
notify.success({
|
notify.success({
|
||||||
title: t['com.affine.export.success.title'](),
|
title: t['com.affine.export.success.title'](),
|
||||||
@ -84,7 +99,13 @@ export const useExportPage = (page: Doc) => {
|
|||||||
resolveGlobalLoadingEvent(globalLoadingID);
|
resolveGlobalLoadingEvent(globalLoadingID);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
[page, pushGlobalLoadingEvent, resolveGlobalLoadingEvent, t]
|
[
|
||||||
|
blocksuiteDoc,
|
||||||
|
editorContainer,
|
||||||
|
pushGlobalLoadingEvent,
|
||||||
|
resolveGlobalLoadingEvent,
|
||||||
|
t,
|
||||||
|
]
|
||||||
);
|
);
|
||||||
|
|
||||||
return onClickHandler;
|
return onClickHandler;
|
||||||
|
@ -51,7 +51,7 @@ export function useRegisterBlocksuiteEditorCommands(editor: Editor) {
|
|||||||
}, [setInfoModalState]);
|
}, [setInfoModalState]);
|
||||||
|
|
||||||
const { duplicate } = useBlockSuiteMetaHelper(docCollection);
|
const { duplicate } = useBlockSuiteMetaHelper(docCollection);
|
||||||
const exportHandler = useExportPage(doc.blockSuiteDoc);
|
const exportHandler = useExportPage();
|
||||||
const { setTrashModal } = useTrashModalHelper(docCollection);
|
const { setTrashModal } = useTrashModalHelper(docCollection);
|
||||||
const onClickDelete = useCallback(
|
const onClickDelete = useCallback(
|
||||||
(title: string) => {
|
(title: string) => {
|
||||||
@ -189,7 +189,7 @@ export function useRegisterBlocksuiteEditorCommands(editor: Editor) {
|
|||||||
type: 'pdf',
|
type: 'pdf',
|
||||||
});
|
});
|
||||||
|
|
||||||
await exportHandler('pdf');
|
exportHandler('pdf');
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
@ -206,7 +206,7 @@ export function useRegisterBlocksuiteEditorCommands(editor: Editor) {
|
|||||||
type: 'html',
|
type: 'html',
|
||||||
});
|
});
|
||||||
|
|
||||||
await exportHandler('html');
|
exportHandler('html');
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
@ -223,7 +223,7 @@ export function useRegisterBlocksuiteEditorCommands(editor: Editor) {
|
|||||||
type: 'png',
|
type: 'png',
|
||||||
});
|
});
|
||||||
|
|
||||||
await exportHandler('png');
|
exportHandler('png');
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
@ -240,7 +240,7 @@ export function useRegisterBlocksuiteEditorCommands(editor: Editor) {
|
|||||||
type: 'markdown',
|
type: 'markdown',
|
||||||
});
|
});
|
||||||
|
|
||||||
await exportHandler('markdown');
|
exportHandler('markdown');
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
import type { DocMode } from '@blocksuite/blocks';
|
import type { DocMode } from '@blocksuite/blocks';
|
||||||
|
import type { AffineEditorContainer } from '@blocksuite/presets';
|
||||||
import type { DocService, WorkspaceService } from '@toeverything/infra';
|
import type { DocService, WorkspaceService } from '@toeverything/infra';
|
||||||
import { Entity, LiveData } from '@toeverything/infra';
|
import { Entity, LiveData } from '@toeverything/infra';
|
||||||
|
|
||||||
@ -14,6 +15,8 @@ export class Editor extends Entity<{ defaultMode: DocMode }> {
|
|||||||
readonly isSharedMode =
|
readonly isSharedMode =
|
||||||
this.workspaceService.workspace.openOptions.isSharedMode;
|
this.workspaceService.workspace.openOptions.isSharedMode;
|
||||||
|
|
||||||
|
readonly editorContainer$ = new LiveData<AffineEditorContainer | null>(null);
|
||||||
|
|
||||||
toggleMode() {
|
toggleMode() {
|
||||||
this.mode$.next(this.mode$.value === 'edgeless' ? 'page' : 'edgeless');
|
this.mode$.next(this.mode$.value === 'edgeless' ? 'page' : 'edgeless');
|
||||||
}
|
}
|
||||||
@ -22,6 +25,10 @@ export class Editor extends Entity<{ defaultMode: DocMode }> {
|
|||||||
this.mode$.next(mode);
|
this.mode$.next(mode);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
setEditorContainer(editorContainer: AffineEditorContainer | null) {
|
||||||
|
this.editorContainer$.next(editorContainer);
|
||||||
|
}
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
private readonly docService: DocService,
|
private readonly docService: DocService,
|
||||||
private readonly workspaceService: WorkspaceService
|
private readonly workspaceService: WorkspaceService
|
||||||
|
@ -102,8 +102,7 @@ const DetailPageImpl = memo(function DetailPageImpl() {
|
|||||||
|
|
||||||
const isInTrash = useLiveData(doc.meta$.map(meta => meta.trash));
|
const isInTrash = useLiveData(doc.meta$.map(meta => meta.trash));
|
||||||
const { openPage, jumpToPageBlock, jumpToTag } = useNavigateHelper();
|
const { openPage, jumpToPageBlock, jumpToTag } = useNavigateHelper();
|
||||||
const [editorContainer, setEditorContainer] =
|
const editorContainer = useLiveData(editor.editorContainer$);
|
||||||
useState<AffineEditorContainer | null>(null);
|
|
||||||
|
|
||||||
const isSideBarOpen = useLiveData(workbench.sidebarOpen$);
|
const isSideBarOpen = useLiveData(workbench.sidebarOpen$);
|
||||||
const { appSettings } = useAppSettingHelper();
|
const { appSettings } = useAppSettingHelper();
|
||||||
@ -179,7 +178,7 @@ const DetailPageImpl = memo(function DetailPageImpl() {
|
|||||||
usePageDocumentTitle(title);
|
usePageDocumentTitle(title);
|
||||||
|
|
||||||
const onLoad = useCallback(
|
const onLoad = useCallback(
|
||||||
(bsPage: BlockSuiteDoc, editor: AffineEditorContainer) => {
|
(bsPage: BlockSuiteDoc, editorContainer: AffineEditorContainer) => {
|
||||||
try {
|
try {
|
||||||
// todo(joooye34): improve the following migration code
|
// todo(joooye34): improve the following migration code
|
||||||
const surfaceBlock = bsPage.getBlockByFlavour('affine:surface')[0];
|
const surfaceBlock = bsPage.getBlockByFlavour('affine:surface')[0];
|
||||||
@ -201,7 +200,7 @@ const DetailPageImpl = memo(function DetailPageImpl() {
|
|||||||
} catch {}
|
} catch {}
|
||||||
|
|
||||||
// blocksuite editor host
|
// blocksuite editor host
|
||||||
const editorHost = editor.host;
|
const editorHost = editorContainer.host;
|
||||||
|
|
||||||
// provide image proxy endpoint to blocksuite
|
// provide image proxy endpoint to blocksuite
|
||||||
editorHost?.std.clipboard.use(
|
editorHost?.std.clipboard.use(
|
||||||
@ -240,13 +239,20 @@ const DetailPageImpl = memo(function DetailPageImpl() {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
setEditorContainer(editor);
|
editor.setEditorContainer(editorContainer);
|
||||||
|
|
||||||
return () => {
|
return () => {
|
||||||
disposable.dispose();
|
disposable.dispose();
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
[jumpToPageBlock, docCollection.id, openPage, jumpToTag, workspace.id]
|
[
|
||||||
|
editor,
|
||||||
|
jumpToPageBlock,
|
||||||
|
docCollection.id,
|
||||||
|
openPage,
|
||||||
|
jumpToTag,
|
||||||
|
workspace.id,
|
||||||
|
]
|
||||||
);
|
);
|
||||||
|
|
||||||
const [refCallback, hasScrollTop] = useHasScrollTop();
|
const [refCallback, hasScrollTop] = useHasScrollTop();
|
||||||
|
@ -975,6 +975,7 @@
|
|||||||
"com.affine.payment.billing-setting.upgrade": "Upgrade",
|
"com.affine.payment.billing-setting.upgrade": "Upgrade",
|
||||||
"com.affine.payment.billing-setting.view-invoice": "View invoice",
|
"com.affine.payment.billing-setting.view-invoice": "View invoice",
|
||||||
"com.affine.payment.billing-setting.year": "year",
|
"com.affine.payment.billing-setting.year": "year",
|
||||||
|
"com.affine.export.print": "Print",
|
||||||
"com.affine.payment.billing-type-form.description": "Please tell us more about your use case, to make AFFiNE better.",
|
"com.affine.payment.billing-type-form.description": "Please tell us more about your use case, to make AFFiNE better.",
|
||||||
"com.affine.payment.billing-type-form.go": "Go",
|
"com.affine.payment.billing-type-form.go": "Go",
|
||||||
"com.affine.payment.billing-type-form.title": "Tell us your use case",
|
"com.affine.payment.billing-type-form.title": "Tell us your use case",
|
||||||
@ -1276,6 +1277,7 @@
|
|||||||
"com.affine.share-menu.SharePage": "Share doc",
|
"com.affine.share-menu.SharePage": "Share doc",
|
||||||
"com.affine.share-menu.ShareViaExport": "Share via export",
|
"com.affine.share-menu.ShareViaExport": "Share via export",
|
||||||
"com.affine.share-menu.ShareViaExportDescription": "Download a static copy of your doc to share with others.",
|
"com.affine.share-menu.ShareViaExportDescription": "Download a static copy of your doc to share with others.",
|
||||||
|
"com.affine.share-menu.ShareViaPrintDescription": "Print a paper copy.",
|
||||||
"com.affine.share-menu.ShareWithLink": "Share with link",
|
"com.affine.share-menu.ShareWithLink": "Share with link",
|
||||||
"com.affine.share-menu.ShareWithLinkDescription": "Create a link you can easily share with anyone. The visitors will open your doc in the form od a document",
|
"com.affine.share-menu.ShareWithLinkDescription": "Create a link you can easily share with anyone. The visitors will open your doc in the form od a document",
|
||||||
"com.affine.share-menu.SharedPage": "Shared doc",
|
"com.affine.share-menu.SharedPage": "Shared doc",
|
||||||
|
@ -336,19 +336,6 @@ test('assert the recent browse pages are on the recent list', async ({
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
test('can use cmdk to export pdf', async ({ page }) => {
|
|
||||||
await openHomePage(page);
|
|
||||||
await waitForEditorLoad(page);
|
|
||||||
await clickNewPageButton(page);
|
|
||||||
await getBlockSuiteEditorTitle(page).click();
|
|
||||||
await getBlockSuiteEditorTitle(page).fill('this is a new page to export');
|
|
||||||
await openQuickSearchByShortcut(page);
|
|
||||||
const [download] = await Promise.all([
|
|
||||||
page.waitForEvent('download'),
|
|
||||||
keyboardDownAndSelect(page, 'Export to PDF'),
|
|
||||||
]);
|
|
||||||
expect(download.suggestedFilename()).toBe('this is a new page to export.pdf');
|
|
||||||
});
|
|
||||||
test('can use cmdk to export png', async ({ page }) => {
|
test('can use cmdk to export png', async ({ page }) => {
|
||||||
await openHomePage(page);
|
await openHomePage(page);
|
||||||
await waitForEditorLoad(page);
|
await waitForEditorLoad(page);
|
||||||
|
12
yarn.lock
12
yarn.lock
@ -326,7 +326,7 @@ __metadata:
|
|||||||
"@storybook/react": "npm:^8.2.9"
|
"@storybook/react": "npm:^8.2.9"
|
||||||
"@storybook/react-vite": "npm:^8.2.9"
|
"@storybook/react-vite": "npm:^8.2.9"
|
||||||
"@testing-library/react": "npm:^16.0.0"
|
"@testing-library/react": "npm:^16.0.0"
|
||||||
"@toeverything/theme": "npm:^1.0.5"
|
"@toeverything/theme": "npm:^1.0.7"
|
||||||
"@types/bytes": "npm:^3.1.4"
|
"@types/bytes": "npm:^3.1.4"
|
||||||
"@types/react": "npm:^18.2.75"
|
"@types/react": "npm:^18.2.75"
|
||||||
"@types/react-dnd": "npm:^3.0.2"
|
"@types/react-dnd": "npm:^3.0.2"
|
||||||
@ -422,7 +422,7 @@ __metadata:
|
|||||||
"@sgtpooki/file-type": "npm:^1.0.1"
|
"@sgtpooki/file-type": "npm:^1.0.1"
|
||||||
"@swc/core": "npm:^1.4.13"
|
"@swc/core": "npm:^1.4.13"
|
||||||
"@testing-library/react": "npm:^16.0.0"
|
"@testing-library/react": "npm:^16.0.0"
|
||||||
"@toeverything/theme": "npm:^1.0.5"
|
"@toeverything/theme": "npm:^1.0.7"
|
||||||
"@types/animejs": "npm:^3.1.12"
|
"@types/animejs": "npm:^3.1.12"
|
||||||
"@types/bytes": "npm:^3.1.4"
|
"@types/bytes": "npm:^3.1.4"
|
||||||
"@types/image-blob-reduce": "npm:^4.1.4"
|
"@types/image-blob-reduce": "npm:^4.1.4"
|
||||||
@ -13858,10 +13858,10 @@ __metadata:
|
|||||||
languageName: unknown
|
languageName: unknown
|
||||||
linkType: soft
|
linkType: soft
|
||||||
|
|
||||||
"@toeverything/theme@npm:^1.0.2, @toeverything/theme@npm:^1.0.5":
|
"@toeverything/theme@npm:^1.0.2, @toeverything/theme@npm:^1.0.7":
|
||||||
version: 1.0.5
|
version: 1.0.7
|
||||||
resolution: "@toeverything/theme@npm:1.0.5"
|
resolution: "@toeverything/theme@npm:1.0.7"
|
||||||
checksum: 10/26f177192c546b8b6c953a4d75da5520486fa92c872b889052861cd121ef9840b5006e2f1513988117290066812e08015aa99b025d13c01ca50563a9ba26a732
|
checksum: 10/86b46af255450ab7ea0a20faf41c27793129852759d23736e914876a696c40e6daa15b25cde7353cd56c673c6191d04cabe6b77f6131ba0b0862bb8d482d7a01
|
||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user