mirror of
https://github.com/toeverything/AFFiNE.git
synced 2024-12-23 10:42:00 +03:00
refactor: image preview plugin (#3457)
This commit is contained in:
parent
be3909370e
commit
52809a2783
@ -81,6 +81,9 @@ export const createGlobalThis = () => {
|
|||||||
TextDecoder: globalThis.TextDecoder,
|
TextDecoder: globalThis.TextDecoder,
|
||||||
Request: globalThis.Request,
|
Request: globalThis.Request,
|
||||||
Error: globalThis.Error,
|
Error: globalThis.Error,
|
||||||
|
// bookmark uses these
|
||||||
|
Blob: globalThis.Blob,
|
||||||
|
ClipboardItem: globalThis.ClipboardItem,
|
||||||
|
|
||||||
// fixme: use our own db api
|
// fixme: use our own db api
|
||||||
indexedDB: globalThis.indexedDB,
|
indexedDB: globalThis.indexedDB,
|
||||||
|
@ -37,6 +37,7 @@ const builtinPluginUrl = new Set([
|
|||||||
'/plugins/bookmark',
|
'/plugins/bookmark',
|
||||||
'/plugins/copilot',
|
'/plugins/copilot',
|
||||||
'/plugins/hello-world',
|
'/plugins/hello-world',
|
||||||
|
'/plugins/image-preview',
|
||||||
]);
|
]);
|
||||||
|
|
||||||
const logger = new DebugLogger('register-plugins');
|
const logger = new DebugLogger('register-plugins');
|
||||||
|
@ -1,11 +1,13 @@
|
|||||||
import { BlockHubWrapper } from '@affine/component/block-hub';
|
import { BlockHubWrapper } from '@affine/component/block-hub';
|
||||||
import { BlockSuiteEditor } from '@affine/component/block-suite-editor';
|
import { BlockSuiteEditor } from '@affine/component/block-suite-editor';
|
||||||
import { ImagePreviewModal } from '@affine/component/image-preview-modal';
|
|
||||||
import { initEmptyPage } from '@affine/env/blocksuite';
|
import { initEmptyPage } from '@affine/env/blocksuite';
|
||||||
import { WorkspaceFlavour } from '@affine/env/workspace';
|
import { WorkspaceFlavour } from '@affine/env/workspace';
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-restricted-imports
|
||||||
|
import { ImagePreviewModal } from '@affine/image-preview-plugin/src/component';
|
||||||
import { rootBlockHubAtom } from '@affine/workspace/atom';
|
import { rootBlockHubAtom } from '@affine/workspace/atom';
|
||||||
import { getOrCreateWorkspace } from '@affine/workspace/manager';
|
import { getOrCreateWorkspace } from '@affine/workspace/manager';
|
||||||
import type { Meta } from '@storybook/react';
|
import type { Meta } from '@storybook/react';
|
||||||
|
import { createPortal } from 'react-dom';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
title: 'Component/ImagePreviewModal',
|
title: 'Component/ImagePreviewModal',
|
||||||
@ -52,6 +54,10 @@ export const Default = () => {
|
|||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<BlockSuiteEditor mode="page" page={page} onInit={initEmptyPage} />
|
<BlockSuiteEditor mode="page" page={page} onInit={initEmptyPage} />
|
||||||
|
{createPortal(
|
||||||
|
<ImagePreviewModal pageId={page.id} workspace={page.workspace} />,
|
||||||
|
document.body
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
<BlockHubWrapper
|
<BlockHubWrapper
|
||||||
style={{
|
style={{
|
||||||
|
@ -18,6 +18,9 @@
|
|||||||
{
|
{
|
||||||
"path": "../../packages/workspace"
|
"path": "../../packages/workspace"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"path": "../../plugins/image-preview"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"path": "./tsconfig.node.json"
|
"path": "./tsconfig.node.json"
|
||||||
}
|
}
|
||||||
|
@ -45,7 +45,8 @@ const external = [
|
|||||||
/^@toeverything/,
|
/^@toeverything/,
|
||||||
|
|
||||||
// react
|
// react
|
||||||
/^react/,
|
'react',
|
||||||
|
/^react\//,
|
||||||
/^react-dom/,
|
/^react-dom/,
|
||||||
|
|
||||||
// store
|
// store
|
||||||
@ -72,11 +73,11 @@ const json: z.infer<typeof packageJsonInputSchema> = await readFile(
|
|||||||
)
|
)
|
||||||
.then(text => JSON.parse(text))
|
.then(text => JSON.parse(text))
|
||||||
.then(async json => {
|
.then(async json => {
|
||||||
const { success } = await packageJsonInputSchema.safeParseAsync(json);
|
const result = await packageJsonInputSchema.safeParseAsync(json);
|
||||||
if (success) {
|
if (result.success) {
|
||||||
return json;
|
return json;
|
||||||
} else {
|
} else {
|
||||||
throw new Error('invalid package.json');
|
throw new Error('invalid package.json', result.error);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -8,8 +8,7 @@ import { Skeleton } from '@mui/material';
|
|||||||
import { use } from 'foxact/use';
|
import { use } from 'foxact/use';
|
||||||
import { useAtomValue } from 'jotai';
|
import { useAtomValue } from 'jotai';
|
||||||
import type { CSSProperties, ReactElement } from 'react';
|
import type { CSSProperties, ReactElement } from 'react';
|
||||||
import { lazy, memo, Suspense, useCallback, useEffect, useRef } from 'react';
|
import { memo, Suspense, useCallback, useEffect, useRef } from 'react';
|
||||||
import { createPortal } from 'react-dom';
|
|
||||||
import type { FallbackProps } from 'react-error-boundary';
|
import type { FallbackProps } from 'react-error-boundary';
|
||||||
import { ErrorBoundary } from 'react-error-boundary';
|
import { ErrorBoundary } from 'react-error-boundary';
|
||||||
|
|
||||||
@ -41,12 +40,6 @@ declare global {
|
|||||||
var currentEditor: EditorContainer | undefined;
|
var currentEditor: EditorContainer | undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
const ImagePreviewModal = lazy(() =>
|
|
||||||
import('../image-preview-modal').then(module => ({
|
|
||||||
default: module.ImagePreviewModal,
|
|
||||||
}))
|
|
||||||
);
|
|
||||||
|
|
||||||
const BlockSuiteEditorImpl = (props: EditorProps): ReactElement => {
|
const BlockSuiteEditorImpl = (props: EditorProps): ReactElement => {
|
||||||
const { onLoad, page, mode, style } = props;
|
const { onLoad, page, mode, style } = props;
|
||||||
if (!page.loaded) {
|
if (!page.loaded) {
|
||||||
@ -191,17 +184,6 @@ export const BlockSuiteEditor = memo(function BlockSuiteEditor(
|
|||||||
<Suspense fallback={<BlockSuiteFallback />}>
|
<Suspense fallback={<BlockSuiteFallback />}>
|
||||||
<BlockSuiteEditorImpl {...props} />
|
<BlockSuiteEditorImpl {...props} />
|
||||||
</Suspense>
|
</Suspense>
|
||||||
{props.page && (
|
|
||||||
<Suspense fallback={null}>
|
|
||||||
{createPortal(
|
|
||||||
<ImagePreviewModal
|
|
||||||
workspace={props.page.workspace}
|
|
||||||
pageId={props.page.id}
|
|
||||||
/>,
|
|
||||||
document.body
|
|
||||||
)}
|
|
||||||
</Suspense>
|
|
||||||
)}
|
|
||||||
</ErrorBoundary>
|
</ErrorBoundary>
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
20
plugins/image-preview/package.json
Normal file
20
plugins/image-preview/package.json
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
{
|
||||||
|
"name": "@affine/image-preview-plugin",
|
||||||
|
"version": "0.8.0-canary.0",
|
||||||
|
"description": "Image preview plugin",
|
||||||
|
"affinePlugin": {
|
||||||
|
"release": true,
|
||||||
|
"entry": {
|
||||||
|
"core": "./src/index.ts"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"@affine/component": "workspace:*",
|
||||||
|
"@blocksuite/icons": "^2.1.27",
|
||||||
|
"@toeverything/plugin-infra": "workspace:*",
|
||||||
|
"@toeverything/theme": "^0.7.9",
|
||||||
|
"clsx": "^2.0.0",
|
||||||
|
"react-error-boundary": "^4.0.10",
|
||||||
|
"swr": "2.1.5"
|
||||||
|
}
|
||||||
|
}
|
11
plugins/image-preview/src/app.tsx
Normal file
11
plugins/image-preview/src/app.tsx
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
import type { Page } from '@blocksuite/store';
|
||||||
|
|
||||||
|
import { ImagePreviewModal } from './component';
|
||||||
|
|
||||||
|
export type AppProps = {
|
||||||
|
page: Page;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const App = ({ page }: AppProps) => {
|
||||||
|
return <ImagePreviewModal pageId={page.id} workspace={page.workspace} />;
|
||||||
|
};
|
@ -5,8 +5,8 @@ export const hasAnimationPlayedAtom = atom<boolean | null>(true);
|
|||||||
|
|
||||||
previewBlockIdAtom.onMount = set => {
|
previewBlockIdAtom.onMount = set => {
|
||||||
const callback = (event: MouseEvent) => {
|
const callback = (event: MouseEvent) => {
|
||||||
const target = event.target;
|
const target = event.target as HTMLElement | null;
|
||||||
if (target instanceof HTMLImageElement) {
|
if (target?.tagName === 'IMG') {
|
||||||
const imageBlock = target.closest('affine-image');
|
const imageBlock = target.closest('affine-image');
|
||||||
if (imageBlock) {
|
if (imageBlock) {
|
||||||
const blockId = imageBlock.getAttribute('data-block-id');
|
const blockId = imageBlock.getAttribute('data-block-id');
|
@ -1,5 +1,4 @@
|
|||||||
import '@blocksuite/blocks';
|
import { Button, IconButton, Tooltip } from '@affine/component';
|
||||||
|
|
||||||
import type { ImageBlockModel } from '@blocksuite/blocks';
|
import type { ImageBlockModel } from '@blocksuite/blocks';
|
||||||
import { assertExists } from '@blocksuite/global/utils';
|
import { assertExists } from '@blocksuite/global/utils';
|
||||||
import {
|
import {
|
||||||
@ -23,8 +22,6 @@ import type { FallbackProps } from 'react-error-boundary';
|
|||||||
import { ErrorBoundary } from 'react-error-boundary';
|
import { ErrorBoundary } from 'react-error-boundary';
|
||||||
import useSWR from 'swr';
|
import useSWR from 'swr';
|
||||||
|
|
||||||
import { Button, IconButton } from '../../ui/button';
|
|
||||||
import { Tooltip } from '../../ui/tooltip';
|
|
||||||
import { useZoomControls } from './hooks/use-zoom';
|
import { useZoomControls } from './hooks/use-zoom';
|
||||||
import {
|
import {
|
||||||
buttonStyle,
|
buttonStyle,
|
18
plugins/image-preview/src/index.ts
Normal file
18
plugins/image-preview/src/index.ts
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
import type { PluginContext } from '@toeverything/plugin-infra/entry';
|
||||||
|
import { createElement } from 'react';
|
||||||
|
import { createRoot } from 'react-dom/client';
|
||||||
|
|
||||||
|
import { App } from './app';
|
||||||
|
|
||||||
|
export const entry = (context: PluginContext) => {
|
||||||
|
context.register('editor', (div, editor) => {
|
||||||
|
const root = createRoot(div);
|
||||||
|
root.render(createElement(App, { page: editor.page }));
|
||||||
|
return () => {
|
||||||
|
root.unmount();
|
||||||
|
};
|
||||||
|
});
|
||||||
|
return () => {
|
||||||
|
// do nothing
|
||||||
|
};
|
||||||
|
};
|
17
plugins/image-preview/tsconfig.json
Normal file
17
plugins/image-preview/tsconfig.json
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
{
|
||||||
|
"extends": "../../tsconfig.json",
|
||||||
|
"include": ["./src"],
|
||||||
|
"compilerOptions": {
|
||||||
|
"noEmit": false,
|
||||||
|
"outDir": "lib",
|
||||||
|
"jsx": "preserve"
|
||||||
|
},
|
||||||
|
"references": [
|
||||||
|
{
|
||||||
|
"path": "../../packages/plugin-infra"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "../../packages/component"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
@ -1,16 +1,15 @@
|
|||||||
import { spawnSync } from 'node:child_process';
|
import { spawn } from 'node:child_process';
|
||||||
|
|
||||||
spawnSync('yarn', ['-T', 'run', 'dev-plugin', '--plugin', 'bookmark'], {
|
const builtInPlugins = ['bookmark', 'hello-world', 'copilot', 'image-preview'];
|
||||||
stdio: 'inherit',
|
|
||||||
shell: true,
|
|
||||||
});
|
|
||||||
|
|
||||||
spawnSync('yarn', ['-T', 'run', 'dev-plugin', '--plugin', 'hello-world'], {
|
for (const plugin of builtInPlugins) {
|
||||||
stdio: 'inherit',
|
const cp = spawn('yarn', ['-T', 'run', 'dev-plugin', '--plugin', plugin], {
|
||||||
shell: true,
|
stdio: 'inherit',
|
||||||
});
|
shell: true,
|
||||||
|
});
|
||||||
spawnSync('yarn', ['-T', 'run', 'dev-plugin', '--plugin', 'copilot'], {
|
cp.on('exit', code => {
|
||||||
stdio: 'inherit',
|
if (code !== 0) {
|
||||||
shell: true,
|
process.exit(code);
|
||||||
});
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
@ -7,7 +7,7 @@ test('plugin should exist', async ({ page }) => {
|
|||||||
await openHomePage(page);
|
await openHomePage(page);
|
||||||
await waitEditorLoad(page);
|
await waitEditorLoad(page);
|
||||||
await page.route('**/plugins/**/package.json', route => route.fetch(), {
|
await page.route('**/plugins/**/package.json', route => route.fetch(), {
|
||||||
times: 3,
|
times: 4,
|
||||||
});
|
});
|
||||||
await page.waitForTimeout(50);
|
await page.waitForTimeout(50);
|
||||||
const packageJson = await page.evaluate(() => {
|
const packageJson = await page.evaluate(() => {
|
||||||
@ -17,14 +17,26 @@ test('plugin should exist', async ({ page }) => {
|
|||||||
expect(packageJson).toEqual([
|
expect(packageJson).toEqual([
|
||||||
{
|
{
|
||||||
name: '@affine/bookmark-plugin',
|
name: '@affine/bookmark-plugin',
|
||||||
|
version: expect.any(String),
|
||||||
|
description: expect.any(String),
|
||||||
affinePlugin: expect.anything(),
|
affinePlugin: expect.anything(),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: '@affine/copilot-plugin',
|
name: '@affine/copilot-plugin',
|
||||||
|
version: expect.any(String),
|
||||||
|
description: expect.any(String),
|
||||||
affinePlugin: expect.anything(),
|
affinePlugin: expect.anything(),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: '@affine/hello-world-plugin',
|
name: '@affine/hello-world-plugin',
|
||||||
|
version: expect.any(String),
|
||||||
|
description: expect.any(String),
|
||||||
|
affinePlugin: expect.anything(),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: '@affine/image-preview-plugin',
|
||||||
|
version: expect.any(String),
|
||||||
|
description: expect.any(String),
|
||||||
affinePlugin: expect.anything(),
|
affinePlugin: expect.anything(),
|
||||||
},
|
},
|
||||||
]);
|
]);
|
||||||
|
14
yarn.lock
14
yarn.lock
@ -403,6 +403,20 @@ __metadata:
|
|||||||
languageName: unknown
|
languageName: unknown
|
||||||
linkType: soft
|
linkType: soft
|
||||||
|
|
||||||
|
"@affine/image-preview-plugin@workspace:plugins/image-preview":
|
||||||
|
version: 0.0.0-use.local
|
||||||
|
resolution: "@affine/image-preview-plugin@workspace:plugins/image-preview"
|
||||||
|
dependencies:
|
||||||
|
"@affine/component": "workspace:*"
|
||||||
|
"@blocksuite/icons": ^2.1.27
|
||||||
|
"@toeverything/plugin-infra": "workspace:*"
|
||||||
|
"@toeverything/theme": ^0.7.9
|
||||||
|
clsx: ^2.0.0
|
||||||
|
react-error-boundary: ^4.0.10
|
||||||
|
swr: 2.1.5
|
||||||
|
languageName: unknown
|
||||||
|
linkType: soft
|
||||||
|
|
||||||
"@affine/jotai@workspace:*, @affine/jotai@workspace:packages/jotai":
|
"@affine/jotai@workspace:*, @affine/jotai@workspace:packages/jotai":
|
||||||
version: 0.0.0-use.local
|
version: 0.0.0-use.local
|
||||||
resolution: "@affine/jotai@workspace:packages/jotai"
|
resolution: "@affine/jotai@workspace:packages/jotai"
|
||||||
|
Loading…
Reference in New Issue
Block a user