├── .husky └── pre-commit ├── packages ├── plugin │ ├── styles │ │ ├── index.ts │ │ └── style.css │ ├── lib │ │ ├── content-script-lib.js │ │ ├── content-script-all-frames-lib.js │ │ ├── single-file-extension-editor-init.js │ │ ├── single-file-extension-frames.js │ │ └── single-file-extension-core.js │ ├── assets │ │ ├── icon.png │ │ ├── icon.svg │ │ └── locales │ │ │ ├── zh-CN │ │ │ └── translation.json │ │ │ └── en │ │ │ └── translation.json │ ├── postcss.config.cjs │ ├── popup │ │ ├── utils │ │ │ ├── tab.ts │ │ │ ├── singleFile.ts │ │ │ └── screenshot.ts │ │ ├── index.html │ │ ├── components │ │ │ ├── LoadingPage.tsx │ │ │ ├── ThemeToggle.tsx │ │ │ ├── LoginPage.tsx │ │ │ ├── NewFolderDialog.tsx │ │ │ ├── SavedPageList.tsx │ │ │ └── FolderCombobox.tsx │ │ ├── main.tsx │ │ ├── composable │ │ │ └── server.ts │ │ └── PopupPage.tsx │ ├── contentScripts │ │ ├── main.ts │ │ └── content.ts │ ├── privacy.md │ ├── background │ │ ├── keepAlive.ts │ │ └── login.ts │ ├── tsconfig.json │ ├── vite.config.ts │ ├── package.json │ ├── utils │ │ ├── file.ts │ │ └── singleFile.ts │ ├── i18n.ts │ ├── vite.production.config.ts │ ├── manifest.dev.json │ ├── manifest.json │ ├── manifest.firefox.json │ ├── shim.d.ts │ └── tailwind.config.cjs ├── server │ ├── scripts │ │ └── init_local.sh │ ├── src │ │ ├── global.d.ts │ │ ├── utils │ │ │ ├── sleep.ts │ │ │ ├── cookie.ts │ │ │ ├── result.ts │ │ │ └── file.ts │ │ ├── sql │ │ │ ├── temp.sql │ │ │ ├── migrations │ │ │ │ ├── 0003_showcase.sql │ │ │ │ ├── 0002_store.sql │ │ │ │ ├── 0004_tag.sql │ │ │ │ └── 0001_init.sql │ │ │ └── types.ts │ │ ├── constants │ │ │ └── binding.ts │ │ ├── api │ │ │ ├── auth.ts │ │ │ ├── data.ts │ │ │ └── showcase.ts │ │ ├── model │ │ │ ├── data.ts │ │ │ ├── showcase.ts │ │ │ └── store.ts │ │ ├── middleware │ │ │ └── token.ts │ │ └── server.ts │ ├── tsconfig.json │ ├── wrangler.raw.toml │ ├── wrangler.toml │ ├── package.json │ └── vite.config.ts ├── shared │ ├── utils │ │ ├── index.ts │ │ ├── helper.ts │ │ ├── validate.ts │ │ └── ai.ts │ ├── types │ │ ├── index.ts │ │ ├── shortcuts.ts │ │ ├── config.ts │ │ └── model.ts │ ├── components │ │ ├── skeleton.tsx │ │ ├── collapsible.tsx │ │ ├── hooks │ │ │ └── use-mobile.ts │ │ ├── label.tsx │ │ ├── textarea.tsx │ │ ├── separator.tsx │ │ ├── input.tsx │ │ ├── checkbox.tsx │ │ ├── switch.tsx │ │ ├── tooltip.tsx │ │ ├── popover.tsx │ │ ├── theme-toggle.tsx │ │ ├── badge.tsx │ │ ├── theme-provider.tsx │ │ ├── button.tsx │ │ ├── card.tsx │ │ ├── language-combobox.tsx │ │ ├── scroll-area.tsx │ │ ├── auto-complete-tag-input.tsx │ │ └── table.tsx │ ├── package.json │ ├── tsconfig.json │ └── global.css └── web │ ├── postcss.config.js │ ├── src │ ├── utils │ │ ├── emitter.ts │ │ ├── router.ts │ │ ├── drag.ts │ │ └── fetcher.ts │ ├── components │ │ ├── common │ │ │ └── chart-card-skeleton.tsx │ │ ├── empty-wrapper.tsx │ │ ├── iframe-page-content.tsx │ │ ├── loading-more.tsx │ │ ├── loading-wrapper.tsx │ │ ├── hamburger.tsx │ │ ├── powerd-by.tsx │ │ ├── empty.tsx │ │ ├── readability-page-content.tsx │ │ ├── r2-usage-card.tsx │ │ ├── not-found.tsx │ │ ├── view-toggle.tsx │ │ ├── screenshot-view.tsx │ │ ├── card-view.tsx │ │ ├── header.tsx │ │ ├── new-folder-dialog.tsx │ │ ├── folder.tsx │ │ ├── github.tsx │ │ ├── list-view.tsx │ │ ├── edit-folder-dialog.tsx │ │ ├── edit-tag-dialog.tsx │ │ └── setting-dialog.tsx │ ├── pages │ │ ├── error.[slug].tsx │ │ ├── showcase │ │ │ └── folder.tsx │ │ ├── (layout) │ │ │ ├── _layout.tsx │ │ │ └── trash.tsx │ │ └── login.tsx │ ├── store │ │ ├── tag.ts │ │ └── app.ts │ ├── data │ │ ├── data.ts │ │ ├── showcase.ts │ │ ├── config.ts │ │ ├── tag.ts │ │ ├── folder.ts │ │ └── page.ts │ ├── router.ts │ ├── hooks │ │ ├── useShouldShowRecent.ts │ │ ├── useObjectUrl.ts │ │ └── useMediaQuery.ts │ ├── i18n.ts │ └── index.tsx │ ├── public │ ├── logo.svg │ └── static │ │ └── logo.svg │ ├── index.html │ ├── tsconfig.json │ ├── package.json │ ├── vite.config.ts │ └── tailwind.config.js ├── pnpm-workspace.yaml ├── docs ├── imgs │ ├── perm.png │ ├── homepage.png │ ├── perm_zh.png │ └── logo.svg ├── feat.md ├── en │ ├── feat.md │ ├── index.md │ ├── usage.md │ ├── contribute.md │ └── about.md ├── index.md ├── usage.md ├── .vitepress │ ├── theme │ │ └── index.ts │ └── config.mts ├── contribute.md ├── README_zh.md └── about.md ├── tsconfig.root.json ├── scripts └── build_clear.mjs ├── .gitignore ├── eslint.config.mjs ├── .github └── workflows │ ├── preview-plugin.yml │ ├── release.yml │ ├── deploy.yml │ └── preview-service.yml ├── README.md └── package.json /.husky/pre-commit: -------------------------------------------------------------------------------- 1 | pnpm run lint:fix 2 | -------------------------------------------------------------------------------- /packages/plugin/styles/index.ts: -------------------------------------------------------------------------------- 1 | import './style.css' 2 | -------------------------------------------------------------------------------- /pnpm-workspace.yaml: -------------------------------------------------------------------------------- 1 | packages: 2 | - 'packages/*' 3 | -------------------------------------------------------------------------------- /packages/server/scripts/init_local.sh: -------------------------------------------------------------------------------- 1 | npx wrangler d1 migrations apply DB --local -------------------------------------------------------------------------------- /docs/imgs/perm.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Ray-D-Song/web-archive/HEAD/docs/imgs/perm.png -------------------------------------------------------------------------------- /docs/imgs/homepage.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Ray-D-Song/web-archive/HEAD/docs/imgs/homepage.png -------------------------------------------------------------------------------- /docs/imgs/perm_zh.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Ray-D-Song/web-archive/HEAD/docs/imgs/perm_zh.png -------------------------------------------------------------------------------- /packages/server/src/global.d.ts: -------------------------------------------------------------------------------- 1 | import {} from 'hono' 2 | 3 | declare module 'hono' { 4 | 5 | } 6 | -------------------------------------------------------------------------------- /packages/plugin/lib/content-script-lib.js: -------------------------------------------------------------------------------- 1 | import './browser-polyfill.min.js' 2 | import './single-file-bootstrap.js' -------------------------------------------------------------------------------- /packages/shared/utils/index.ts: -------------------------------------------------------------------------------- 1 | export * from './validate' 2 | export * from './helper' 3 | export * from './ai' 4 | -------------------------------------------------------------------------------- /packages/plugin/assets/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Ray-D-Song/web-archive/HEAD/packages/plugin/assets/icon.png -------------------------------------------------------------------------------- /packages/shared/types/index.ts: -------------------------------------------------------------------------------- 1 | export * from './shortcuts' 2 | export * from './model' 3 | export * from './config' 4 | -------------------------------------------------------------------------------- /docs/feat.md: -------------------------------------------------------------------------------- 1 | # 功能 2 | 3 | - 文件夹分类 4 | - 页面预览图 5 | - 标题关键字查询 6 | - 橱窗,可以分享自己抓取的页面 7 | - 移动端适配 8 | - tag 分类 9 | - 阅读模式 10 | -------------------------------------------------------------------------------- /packages/web/postcss.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | plugins: { 3 | tailwindcss: {}, 4 | autoprefixer: {}, 5 | }, 6 | } 7 | -------------------------------------------------------------------------------- /packages/plugin/postcss.config.cjs: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | plugins: { 3 | tailwindcss: {}, 4 | autoprefixer: {}, 5 | }, 6 | } 7 | -------------------------------------------------------------------------------- /packages/server/src/utils/sleep.ts: -------------------------------------------------------------------------------- 1 | export async function sleep(ms: number) { 2 | return new Promise(resolve => setTimeout(resolve, ms)) 3 | } 4 | -------------------------------------------------------------------------------- /packages/plugin/lib/content-script-all-frames-lib.js: -------------------------------------------------------------------------------- 1 | import './browser-polyfill.min.js' 2 | import './single-file-frames.js' 3 | import './single-file-extension-frames.js' -------------------------------------------------------------------------------- /packages/server/src/sql/temp.sql: -------------------------------------------------------------------------------- 1 | SELECT 2 | id 3 | FROM pages 4 | WHERE id IN ( 5 | SELECT value FROM json_each((SELECT pageIds FROM tags WHERE id = 1)) 6 | ); 7 | -------------------------------------------------------------------------------- /packages/server/src/utils/cookie.ts: -------------------------------------------------------------------------------- 1 | export function deleteCookie(name: string) { 2 | document.cookie = `${name}=; expires=Thu, 01 Jan 1970 00:00:00 UTC; path=/;` 3 | } 4 | -------------------------------------------------------------------------------- /packages/shared/types/shortcuts.ts: -------------------------------------------------------------------------------- 1 | import type { ChangeEvent } from 'react' 2 | 3 | type CE = ChangeEvent 4 | 5 | export type { 6 | CE, 7 | } 8 | -------------------------------------------------------------------------------- /packages/web/src/utils/emitter.ts: -------------------------------------------------------------------------------- 1 | import mitt from 'mitt' 2 | 3 | type Events = { 4 | movePage: { pageId: number, folderId: number } 5 | refreshSideBar: void 6 | } 7 | 8 | export default mitt() 9 | -------------------------------------------------------------------------------- /packages/shared/utils/helper.ts: -------------------------------------------------------------------------------- 1 | import { type ClassValue, clsx } from 'clsx' 2 | import { twMerge } from 'tailwind-merge' 3 | 4 | export function cn(...inputs: ClassValue[]) { 5 | return twMerge(clsx(inputs)) 6 | } 7 | -------------------------------------------------------------------------------- /docs/en/feat.md: -------------------------------------------------------------------------------- 1 | # Feature 2 | 3 | - Folder classification 4 | - Page preview image 5 | - Title keyword search 6 | - Showcases, you can share your captured pages 7 | - Mobile adaptation 8 | - Tag classification 9 | - Reading mode 10 | -------------------------------------------------------------------------------- /packages/plugin/popup/utils/tab.ts: -------------------------------------------------------------------------------- 1 | import Browser from 'webextension-polyfill' 2 | 3 | export async function getCurrentTab() { 4 | const tabs = await Browser.tabs.query({ active: true, currentWindow: true }) 5 | return tabs[0] 6 | } 7 | -------------------------------------------------------------------------------- /packages/server/src/sql/migrations/0003_showcase.sql: -------------------------------------------------------------------------------- 1 | -- Migration number: 0003 2024-10-23T03:18:17.818Z 2 | ALTER TABLE pages ADD COLUMN isShowcased INTEGER NOT NULL DEFAULT 0; 3 | 4 | CREATE INDEX idx_pages_isShowcased ON pages (isShowcased); 5 | -------------------------------------------------------------------------------- /packages/server/src/constants/binding.ts: -------------------------------------------------------------------------------- 1 | export type Bindings = { 2 | JWT_SECRET: string 3 | DB: D1Database 4 | KV: KVNamespace 5 | BUCKET: R2Bucket 6 | AI: Ai 7 | } 8 | 9 | export type HonoTypeUserInformation = { 10 | Bindings: Bindings 11 | } 12 | -------------------------------------------------------------------------------- /packages/server/src/sql/types.ts: -------------------------------------------------------------------------------- 1 | type Tag = { 2 | id: number 3 | name: string 4 | pageIdDict: string 5 | createdAt: Date 6 | updatedAt: Date 7 | } 8 | 9 | export type { Page, Folder } from '@web-archive/shared/types/model' 10 | export type { Tag } 11 | -------------------------------------------------------------------------------- /packages/web/src/components/common/chart-card-skeleton.tsx: -------------------------------------------------------------------------------- 1 | import { memo } from 'react' 2 | 3 | const ChartCardSkeleton = memo(() => { 4 | return
5 | }) 6 | 7 | export default ChartCardSkeleton 8 | -------------------------------------------------------------------------------- /packages/web/src/components/empty-wrapper.tsx: -------------------------------------------------------------------------------- 1 | import Empty from './empty' 2 | 3 | function EmptyWrapper({ children, empty }: { children: React.ReactNode, empty: boolean }) { 4 | return empty ? : children 5 | } 6 | 7 | export default EmptyWrapper 8 | -------------------------------------------------------------------------------- /packages/web/src/pages/error.[slug].tsx: -------------------------------------------------------------------------------- 1 | import { useParams } from '~/router' 2 | 3 | function ErrorPage() { 4 | const { slug } = useParams('/error/:slug') 5 | 6 | return ( 7 |
8 |

{slug}

9 |
10 | ) 11 | } 12 | 13 | export default ErrorPage 14 | -------------------------------------------------------------------------------- /packages/server/src/sql/migrations/0002_store.sql: -------------------------------------------------------------------------------- 1 | -- Migration number: 0002 2024-10-22T14:31:46.361Z 2 | DROP TABLE IF EXISTS stores; 3 | CREATE TABLE IF NOT EXISTS stores ( 4 | key TEXT PRIMARY KEY, 5 | value TEXT NOT NULL 6 | ); 7 | 8 | CREATE INDEX IF NOT EXISTS idx_stores_key ON stores(key); -------------------------------------------------------------------------------- /packages/plugin/popup/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Popup 7 | 8 | 9 |
10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /docs/imgs/logo.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /packages/plugin/assets/icon.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /packages/web/public/logo.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /packages/web/public/static/logo.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /packages/server/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "baseUrl": ".", 4 | "paths": { 5 | "~/*": ["src/*"] 6 | }, 7 | "types": [ 8 | "@cloudflare/workers-types", 9 | "vite/client" 10 | ], 11 | "strict": true, 12 | "outDir": "./dist/server" 13 | }, 14 | "include": ["*.ts", "./src/**/*.ts"] 15 | } 16 | -------------------------------------------------------------------------------- /packages/server/src/utils/result.ts: -------------------------------------------------------------------------------- 1 | const result = { 2 | success(data: T) { 3 | return { 4 | code: 200, 5 | data, 6 | message: 'success', 7 | } 8 | }, 9 | error: (code: number, message: string) => { 10 | return { 11 | code, 12 | message, 13 | } 14 | }, 15 | } 16 | 17 | export default result 18 | -------------------------------------------------------------------------------- /packages/web/src/store/tag.ts: -------------------------------------------------------------------------------- 1 | import type { Tag } from '@web-archive/shared/types' 2 | import { createContext } from 'react' 3 | 4 | const TagContext = createContext<{ 5 | tagCache: Tag[] 6 | refreshTagCache: () => Promise 7 | }>({ 8 | tagCache: [], 9 | refreshTagCache: async () => [], 10 | }) 11 | 12 | export default TagContext 13 | -------------------------------------------------------------------------------- /docs/index.md: -------------------------------------------------------------------------------- 1 | --- 2 | # https://vitepress.dev/reference/default-theme-home-page 3 | layout: home 4 | 5 | hero: 6 | name: "Web Archive" 7 | text: "" 8 | tagline: 免费网页归档和分享工具,基于 Cloudflare。 9 | actions: 10 | - theme: brand 11 | text: 快速开始 12 | link: /deploy 13 | - theme: alt 14 | text: 关于 15 | link: /about 16 | --- 17 | 18 | -------------------------------------------------------------------------------- /packages/shared/components/skeleton.tsx: -------------------------------------------------------------------------------- 1 | import { cn } from '../utils/helper' 2 | 3 | function Skeleton({ 4 | className, 5 | ...props 6 | }: React.HTMLAttributes) { 7 | return ( 8 |
12 | ) 13 | } 14 | 15 | export { Skeleton } -------------------------------------------------------------------------------- /packages/plugin/lib/single-file-extension-editor-init.js: -------------------------------------------------------------------------------- 1 | !function(){"use strict";document.currentScript.remove(),function e(t){t.querySelectorAll("template[shadowrootmode]").forEach((t=>{let o=t.parentElement.shadowRoot;if(!o){try{o=t.parentElement.attachShadow({mode:t.getAttribute("shadowrootmode")}),o.innerHTML=t.innerHTML,t.remove()}catch(e){}o&&e(o)}}))}(document)}(); 2 | -------------------------------------------------------------------------------- /packages/server/src/api/auth.ts: -------------------------------------------------------------------------------- 1 | import { Hono } from 'hono' 2 | import type { HonoTypeUserInformation } from '~/constants/binding' 3 | import result from '~/utils/result' 4 | 5 | const app = new Hono() 6 | 7 | app.post( 8 | '', 9 | async (c) => { 10 | return c.json(result.success(true)) 11 | }, 12 | ) 13 | 14 | export default app 15 | -------------------------------------------------------------------------------- /packages/web/src/utils/router.ts: -------------------------------------------------------------------------------- 1 | import { createHashRouter } from 'react-router-dom' 2 | import { routes } from '@generouted/react-router' 3 | 4 | const router = createHashRouter(routes) 5 | 6 | function logOut() { 7 | localStorage.removeItem('token') 8 | router.navigate('/login') 9 | } 10 | 11 | export { 12 | logOut, 13 | } 14 | 15 | export default router 16 | -------------------------------------------------------------------------------- /docs/usage.md: -------------------------------------------------------------------------------- 1 | # 使用指南 2 | 3 | 在浏览器插件商店下载插件: 4 | - [Chrome](https://chromewebstore.google.com/detail/web-archive/dfigobdhnhkkdniegjdagofhhhopjajb?hl=zh-CN&utm_source=ext_sidebar) 5 | - [Firefox](https://addons.mozilla.org/zh-CN/firefox/addon/web-archive-ray-banzhe/) 6 | 7 | 首次安装后,需要输入 API 地址和密钥,API 地址是服务地址,密钥就是首个用户(管理员)的密码。 8 | 9 | 在文件夹页面,你可以设置某个页面是否在橱窗中展示。 10 | 橱窗地址:/#/showcase/folder -------------------------------------------------------------------------------- /packages/web/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | Web Archive 9 | 10 | 11 |
12 | 13 | -------------------------------------------------------------------------------- /tsconfig.root.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ESNext", 4 | "lib": [ 5 | "ESNext", 6 | "DOM" 7 | ], 8 | "module": "ESNext", 9 | "moduleResolution": "Bundler", 10 | "allowJs": true, 11 | "strict": true, 12 | "esModuleInterop": true, 13 | "forceConsistentCasingInFileNames": true, 14 | "skipLibCheck": true 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /docs/en/index.md: -------------------------------------------------------------------------------- 1 | --- 2 | # https://vitepress.dev/reference/default-theme-home-page 3 | layout: home 4 | 5 | hero: 6 | name: "Web Archive" 7 | text: "" 8 | tagline: Free web archiving and sharing service based on Cloudflare. 9 | actions: 10 | - theme: brand 11 | text: Quick Start 12 | link: /deploy 13 | - theme: alt 14 | text: About 15 | link: /about 16 | --- 17 | 18 | -------------------------------------------------------------------------------- /scripts/build_clear.mjs: -------------------------------------------------------------------------------- 1 | // delete /dist/service/src/static/static and noto.svg 2 | import fs from 'node:fs' 3 | import path from 'node:path' 4 | 5 | const __dirname = path.dirname(new URL(import.meta.url).pathname) 6 | 7 | fs.rmSync(path.join(__dirname, '../dist/service/src/static/static'), { recursive: true, force: true }) 8 | fs.rmSync(path.join(__dirname, '../dist/service/src/static/noto.svg'), { force: true }) 9 | -------------------------------------------------------------------------------- /packages/plugin/contentScripts/main.ts: -------------------------------------------------------------------------------- 1 | import type { Browser } from 'webextension-polyfill' 2 | 3 | declare const browser: Browser 4 | 5 | (async () => { 6 | // We have to provide the resource as web_accessible in manifest.json 7 | // So that its available when we request using chrome.runtime.getURL 8 | const src = browser.runtime.getURL('contentScripts/content.js') 9 | await import(src) 10 | })() 11 | -------------------------------------------------------------------------------- /packages/shared/components/collapsible.tsx: -------------------------------------------------------------------------------- 1 | "use client" 2 | 3 | import * as CollapsiblePrimitive from "@radix-ui/react-collapsible" 4 | 5 | const Collapsible = CollapsiblePrimitive.Root 6 | 7 | const CollapsibleTrigger = CollapsiblePrimitive.CollapsibleTrigger 8 | 9 | const CollapsibleContent = CollapsiblePrimitive.CollapsibleContent 10 | 11 | export { Collapsible, CollapsibleTrigger, CollapsibleContent } 12 | -------------------------------------------------------------------------------- /packages/shared/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@web-archive/shared", 3 | "type": "module", 4 | "version": "0.1.1-alpha.1", 5 | "description": "", 6 | "author": "", 7 | "license": "ISC", 8 | "keywords": [], 9 | "exports": { 10 | "./types": "./types/index.ts", 11 | "./utils": "./utils/index.ts", 12 | "./components/*": "./components/*.tsx", 13 | "./global.css": "./global.css" 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /packages/web/src/store/app.ts: -------------------------------------------------------------------------------- 1 | import { createContext } from 'react' 2 | 3 | const AppContext = createContext<{ 4 | view: 'card' | 'list' 5 | setView: (view: 'card' | 'list') => void 6 | readMode: boolean 7 | setReadMode: (readMode: boolean) => void 8 | }>({ 9 | view: 'card', 10 | setView: () => { }, 11 | readMode: false, 12 | setReadMode: (value: boolean) => { }, 13 | }) 14 | 15 | export default AppContext 16 | -------------------------------------------------------------------------------- /packages/web/src/components/iframe-page-content.tsx: -------------------------------------------------------------------------------- 1 | interface IframePageContentProps { 2 | pageContentUrl: string 3 | } 4 | 5 | function IframePageContent({ pageContentUrl }: IframePageContentProps) { 6 | return ( 7 |