diff --git a/.github/workflows/workers.yml b/.github/workflows/workers.yml new file mode 100644 index 0000000000..b0e5a79981 --- /dev/null +++ b/.github/workflows/workers.yml @@ -0,0 +1,21 @@ +name: Deploy Cloudflare Worker + +on: + push: + branches: + - master + paths: + - packages/workers/** + +jobs: + deploy: + runs-on: ubuntu-latest + name: Deploy + environment: production + steps: + - uses: actions/checkout@v2 + - name: Publish + uses: cloudflare/wrangler-action@2.0.0 + with: + apiToken: ${{ secrets.CF_API_TOKEN }} + workingDirectory: 'packages/workers' diff --git a/apps/web/preset.config.mjs b/apps/web/preset.config.mjs index 8964a26511..b690cf6307 100644 --- a/apps/web/preset.config.mjs +++ b/apps/web/preset.config.mjs @@ -24,6 +24,7 @@ const buildPreset = { enableBroadcastChannelProvider: true, enableDebugPage: true, changelogUrl: 'https://affine.pro/blog/whats-new-affine-0630', + imageProxyUrl: 'https://workers.toeverything.workers.dev/proxy/image', enablePreloading: true, enableNewSettingModal: true, enableNewSettingUnstableApi: false, @@ -41,6 +42,7 @@ const buildPreset = { enableBroadcastChannelProvider: true, enableDebugPage: true, changelogUrl: 'https://affine.pro/blog/whats-new-affine-0630', + imageProxyUrl: 'https://workers.toeverything.workers.dev/proxy/image', enablePreloading: true, enableNewSettingModal: true, enableNewSettingUnstableApi: false, diff --git a/packages/component/src/components/page-list/operation-menu-items/export.tsx b/packages/component/src/components/page-list/operation-menu-items/export.tsx index 0107ae1c00..0faeb055bb 100644 --- a/packages/component/src/components/page-list/operation-menu-items/export.tsx +++ b/packages/component/src/components/page-list/operation-menu-items/export.tsx @@ -1,7 +1,6 @@ import { pushNotificationAtom } from '@affine/component/notification-center'; import { useAFFiNEI18N } from '@affine/i18n/hooks'; import type { PageBlockModel } from '@blocksuite/blocks'; -import { ContentParser } from '@blocksuite/blocks/content-parser'; import { ArrowRightSmallIcon, ExportIcon, @@ -11,16 +10,16 @@ import { ExportToPngIcon, } from '@blocksuite/icons'; import { useSetAtom } from 'jotai'; -import { useCallback, useRef } from 'react'; +import { useCallback } from 'react'; import { Menu, MenuItem } from '../../..'; +import { getContentParser } from './get-content-parser'; import type { CommonMenuItemProps } from './types'; export const ExportToPdfMenuItem = ({ onSelect, }: CommonMenuItemProps<{ type: 'pdf' }>) => { const t = useAFFiNEI18N(); - const contentParserRef = useRef(); const { currentEditor } = globalThis; const setPushNotification = useSetAtom(pushNotificationAtom); @@ -53,9 +52,7 @@ export const ExportToPdfMenuItem = ({ }); }); } else { - const contentParser = - contentParserRef.current ?? - (contentParserRef.current = new ContentParser(currentEditor.page)); + const contentParser = getContentParser(currentEditor.page); contentParser .exportPdf() @@ -95,17 +92,14 @@ export const ExportToHtmlMenuItem = ({ onSelect, }: CommonMenuItemProps<{ type: 'html' }>) => { const t = useAFFiNEI18N(); - const contentParserRef = useRef(); const { currentEditor } = globalThis; const setPushNotification = useSetAtom(pushNotificationAtom); const onClickExportHtml = useCallback(() => { if (!currentEditor) { return; } - if (!contentParserRef.current) { - contentParserRef.current = new ContentParser(currentEditor.page); - } - contentParserRef.current + const contentParser = getContentParser(currentEditor.page); + contentParser .exportHtml() .then(() => { onSelect?.({ type: 'html' }); @@ -138,7 +132,6 @@ export const ExportToPngMenuItem = ({ onSelect, }: CommonMenuItemProps<{ type: 'png' }>) => { const t = useAFFiNEI18N(); - const contentParserRef = useRef(); const { currentEditor } = globalThis; const setPushNotification = useSetAtom(pushNotificationAtom); @@ -146,9 +139,7 @@ export const ExportToPngMenuItem = ({ if (!currentEditor) { return; } - const contentParser = - contentParserRef.current ?? - (contentParserRef.current = new ContentParser(currentEditor.page)); + const contentParser = getContentParser(currentEditor.page); contentParser .exportPng() @@ -189,17 +180,14 @@ export const ExportToMarkdownMenuItem = ({ onSelect, }: CommonMenuItemProps<{ type: 'markdown' }>) => { const t = useAFFiNEI18N(); - const contentParserRef = useRef(); const { currentEditor } = globalThis; const setPushNotification = useSetAtom(pushNotificationAtom); const onClickExportMarkdown = useCallback(() => { if (!currentEditor) { return; } - if (!contentParserRef.current) { - contentParserRef.current = new ContentParser(currentEditor.page); - } - contentParserRef.current + const contentParser = getContentParser(currentEditor.page); + contentParser .exportMarkdown() .then(() => { onSelect?.({ type: 'markdown' }); diff --git a/packages/component/src/components/page-list/operation-menu-items/get-content-parser.ts b/packages/component/src/components/page-list/operation-menu-items/get-content-parser.ts new file mode 100644 index 0000000000..17106025ff --- /dev/null +++ b/packages/component/src/components/page-list/operation-menu-items/get-content-parser.ts @@ -0,0 +1,18 @@ +import { ContentParser } from '@blocksuite/blocks/content-parser'; +import type { Page } from '@blocksuite/store'; + +const contentParserWeakMap = new WeakMap(); + +export function getContentParser(page: Page) { + if (!contentParserWeakMap.has(page)) { + contentParserWeakMap.set( + page, + new ContentParser(page, { + imageProxyEndpoint: !environment.isDesktop + ? runtimeConfig.imageProxyUrl + : undefined, + }) + ); + } + return contentParserWeakMap.get(page) as ContentParser; +} diff --git a/packages/env/src/global.ts b/packages/env/src/global.ts index 775d826488..1b3d09ce4a 100644 --- a/packages/env/src/global.ts +++ b/packages/env/src/global.ts @@ -63,6 +63,8 @@ export const buildFlagsSchema = z.object({ enableBroadcastChannelProvider: z.boolean(), enableDebugPage: z.boolean(), changelogUrl: z.string(), + // see: packages/workers + imageProxyUrl: z.string(), enablePreloading: z.boolean(), enableNewSettingModal: z.boolean(), enableNewSettingUnstableApi: z.boolean(), diff --git a/packages/workers/src/index.ts b/packages/workers/src/index.ts new file mode 100644 index 0000000000..7ec5596fb9 --- /dev/null +++ b/packages/workers/src/index.ts @@ -0,0 +1,63 @@ +const ALLOW_ORIGIN = ['https://affine.pro', 'https://affine.fail']; + +function isString(s: any): boolean { + return typeof s === 'string' || s instanceof String; +} + +function isOriginAllowed( + origin: string, + allowedOrigin: string | RegExp | Array +): boolean { + if (Array.isArray(allowedOrigin)) { + for (let i = 0; i < allowedOrigin.length; ++i) { + if (isOriginAllowed(origin, allowedOrigin[i])) { + return true; + } + } + return false; + } else if (isString(allowedOrigin)) { + return origin === allowedOrigin; + } else if (allowedOrigin instanceof RegExp) { + return allowedOrigin.test(origin); + } else { + return !!allowedOrigin; + } +} + +async function proxyImage(request: Request): Promise { + const url = new URL(request.url); + const imageURL = url.searchParams.get('url'); + + if (!imageURL) { + return new Response('Missing "url" parameter', { status: 400 }); + } + + const imageRequest = new Request(imageURL, { + method: 'GET', + headers: request.headers, + }); + + const response = await fetch(imageRequest); + const modifiedResponse = new Response(response.body); + modifiedResponse.headers.set('Access-Control-Allow-Origin', '*'); + modifiedResponse.headers.set('Access-Control-Allow-Methods', 'GET'); + + return modifiedResponse; +} + +const handler = { + async fetch(request: Request) { + if (!isOriginAllowed(request.headers.get('Origin') || '', ALLOW_ORIGIN)) { + return new Response('unauthorized', { status: 401 }); + } + + const url = new URL(request.url); + if (url.pathname.startsWith('/proxy/image')) { + return await proxyImage(request); + } + + return new Response('not found', { status: 404 }); + }, +}; + +export default handler; diff --git a/packages/workers/wrangler.toml b/packages/workers/wrangler.toml new file mode 100644 index 0000000000..09bd635ecf --- /dev/null +++ b/packages/workers/wrangler.toml @@ -0,0 +1,3 @@ +name = "workers" +main = "./src/index.ts" +compatibility_date = "2023-07-11"