diff --git a/packages/plugin/assets/locales/en/translation.json b/packages/plugin/assets/locales/en/translation.json index 1763228..3cf9ed5 100644 --- a/packages/plugin/assets/locales/en/translation.json +++ b/packages/plugin/assets/locales/en/translation.json @@ -52,5 +52,7 @@ "open-showcase-page": "Open Showcase Page", "open-home-page": "Open Home Page", "save-page-not-available": "Save Page (Not Available)", - "language": "Language:" + "language": "Language:", + "loading-archived-list": "Loading Archived List", + "no-archived-pages": "No Archived Pages" } diff --git a/packages/plugin/assets/locales/zh-CN/translation.json b/packages/plugin/assets/locales/zh-CN/translation.json index d56c77b..185dcdb 100644 --- a/packages/plugin/assets/locales/zh-CN/translation.json +++ b/packages/plugin/assets/locales/zh-CN/translation.json @@ -52,5 +52,7 @@ "open-showcase-page": "打开 Showcase 页面", "open-home-page": "打开首页", "save-page-not-available": "保存页面(不可用)", - "language": "语言: " + "language": "语言: ", + "loading-archived-list": "加载已存档列表中", + "no-archived-pages": "没有存档页面" } diff --git a/packages/plugin/background/background.ts b/packages/plugin/background/background.ts index cdb2630..532722f 100644 --- a/packages/plugin/background/background.ts +++ b/packages/plugin/background/background.ts @@ -206,3 +206,16 @@ onMessage('generate-tag', async ({ data: { title, pageDesc, tagLanguage, preferr tags, } }) + +onMessage('query-by-url', async ({ data: { pageUrl } }) => { + const pages = await request('/pages/query_by_url', { + method: 'POST', + body: JSON.stringify({ pageUrl }), + headers: { + 'Content-Type': 'application/json', + }, + }) + return { + pages, + } +}) diff --git a/packages/plugin/popup/components/PluginHomePage.tsx b/packages/plugin/popup/components/PluginHomePage.tsx index 2097c9e..c76cda5 100644 --- a/packages/plugin/popup/components/PluginHomePage.tsx +++ b/packages/plugin/popup/components/PluginHomePage.tsx @@ -6,6 +6,7 @@ import { isNil } from '@web-archive/shared/utils' import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from '@web-archive/shared/components/tooltip' import { useTranslation } from 'react-i18next' import { getCurrentTab } from '../utils/tab' +import SavedPageList from './SavedPageList' import { ThemeToggle } from '~/popup/components/ThemeToggle' import type { PageType } from '~/popup/PopupPage' @@ -124,6 +125,13 @@ function PluginHomePage({ setActivePage }: PluginHomePageProps) { !saveAvailabel ? t('save-page-not-available') : t('save-page') } + { + saveAvailabel && ( + + + ) + } + ) } diff --git a/packages/plugin/popup/components/SavedPageList.tsx b/packages/plugin/popup/components/SavedPageList.tsx new file mode 100644 index 0000000..eacd12d --- /dev/null +++ b/packages/plugin/popup/components/SavedPageList.tsx @@ -0,0 +1,75 @@ +import { isNil } from '@web-archive/shared/utils' +import { sendMessage } from 'webext-bridge/popup' +import { useRequest } from 'ahooks' +import { LoaderCircle } from 'lucide-react' +import { useTranslation } from 'react-i18next' +import { getCurrentTab } from '../utils/tab' + +async function getSavedPages() { + const currentTab = await getCurrentTab() + if (isNil(currentTab?.id) || isNil(currentTab.url)) { + return [] + } + + const { pages } = await sendMessage('query-by-url', { + pageUrl: currentTab.url, + }) + + return pages +} + +function formatTime(date: string) { + const timestamp = Date.parse(`${date}Z`) + const dateObj = new Date(timestamp) + return dateObj.toLocaleString() +} + +function SavedPageList() { + const { t } = useTranslation() + const { data: savedPages, loading } = useRequest(getSavedPages) + + async function handlePageClick(pageId: number) { + const { serverUrl } = await sendMessage('get-server-url', {}) + window.open(`${serverUrl}/#/page/${pageId}`, '_blank') + } + + if (loading) { + return ( +
+ +
{t('loading-archived-list')}
+
+ ) + } + + if (!savedPages?.length) { + return ( +
+
{t('no-archived-pages')}
+
+ ) + } + + return ( +
+
+ { + savedPages?.map((page) => { + return ( +
handlePageClick(page.id)} + > +
{page.title}
+
{formatTime(page.createdAt)}
+
+ ) + }) + } +
+
+ ) +} + +export default SavedPageList diff --git a/packages/plugin/shim.d.ts b/packages/plugin/shim.d.ts index 2f15346..f162177 100644 --- a/packages/plugin/shim.d.ts +++ b/packages/plugin/shim.d.ts @@ -1,5 +1,5 @@ import type { ProtocolWithReturn } from 'webext-bridge' -import type { AITagConfig, Tag } from '@web-archive/shared/types' +import type { AITagConfig, Page, Tag } from '@web-archive/shared/types' import type { SeriableSingleFileTask } from './background/processor' import type { LoadStage, SingleFileSetting } from '~/utils/singleFile' @@ -41,5 +41,12 @@ declare module 'webext-bridge' { 'scrape-available': ProtocolWithReturn<{ tabId: number }, { available: boolean }> 'get-ai-tag-config': ProtocolWithReturn<{}, { aiTagConfig: AITagConfig }> 'generate-tag': ProtocolWithReturn + 'query-by-url': ProtocolWithReturn<{ pageUrl: string }, { + pages: Array<{ + id: number + title: string + createdAt: string + }> + }> } } diff --git a/packages/server/src/api/pages.ts b/packages/server/src/api/pages.ts index 1f8bd53..baf630f 100644 --- a/packages/server/src/api/pages.ts +++ b/packages/server/src/api/pages.ts @@ -1,9 +1,10 @@ import { Hono } from 'hono' import { validator } from 'hono/validator' import { isNil, isNotNil, isNumberString } from '@web-archive/shared/utils' +import { z } from 'zod' import type { HonoTypeUserInformation } from '~/constants/binding' import result from '~/utils/result' -import { clearDeletedPage, deletePageById, getPageById, insertPage, queryDeletedPage, queryPage, queryRecentSavePage, restorePage, selectPageTotalCount, updatePage } from '~/model/page' +import { clearDeletedPage, deletePageById, getPageById, insertPage, queryDeletedPage, queryPage, queryPageByUrl, queryRecentSavePage, restorePage, selectPageTotalCount, updatePage } from '~/model/page' import { getFolderById, restoreFolder } from '~/model/folder' import { getFileFromBucket, saveFileToBucket } from '~/utils/file' import { updateShowcase } from '~/model/showcase' @@ -130,6 +131,33 @@ app.post( }, ) +app.post( + '/query_by_url', + validator('json', (value, c) => { + const errorMsg = { + message: 'Page URL is required', + } + const schema = z.object({ + pageUrl: z.string(errorMsg).min(1, errorMsg), + }) + + const parsed = schema.safeParse(value) + if (!parsed.success) { + if (parsed.error.errors.length > 0) { + return c.json(result.error(400, parsed.error.errors[0].message)) + } + return c.json(result.error(400, 'Invalid request')) + } + + return parsed.data + }), + async (c) => { + const { pageUrl } = c.req.valid('json') + const pages = await queryPageByUrl(c.env.DB, pageUrl) + return c.json(result.success(pages)) + }, +) + app.get('/recent_save', async (c) => { const pages = await queryRecentSavePage(c.env.DB) return c.json(result.success(pages)) diff --git a/packages/server/src/model/page.ts b/packages/server/src/model/page.ts index 50c4573..cea9ea5 100644 --- a/packages/server/src/model/page.ts +++ b/packages/server/src/model/page.ts @@ -90,6 +90,12 @@ async function queryPage(DB: D1Database, options: { folderId?: number, pageNumbe return sqlResult.results } +async function queryPageByUrl(DB: D1Database, pageUrl: string) { + const sql = `SELECT * FROM pages WHERE pageUrl = ? AND isDeleted = 0` + const result = await DB.prepare(sql).bind(pageUrl).all() + return result.results +} + async function selectDeletedPageTotalCount(DB: D1Database) { const sql = ` SELECT COUNT(*) as count FROM pages @@ -240,6 +246,7 @@ async function updatePage(DB: D1Database, options: UpdatePageOptions) { export { selectPageTotalCount, queryPage, + queryPageByUrl, selectDeletedPageTotalCount, queryDeletedPage, deletePageById,