diff --git a/packages/server/src/bookmark/bookmark.ts b/packages/server/src/bookmark/bookmark.ts index a0c59c0..43c9cd9 100644 --- a/packages/server/src/bookmark/bookmark.ts +++ b/packages/server/src/bookmark/bookmark.ts @@ -1,8 +1,17 @@ import type { User } from "@markone/core/user" -import type { Bookmark } from "@markone/core/bookmark" +import { Readability } from "@mozilla/readability" +import { JSDOM } from "jsdom" import { db } from "~/database.ts" import { DEMO_BOOKMARKS } from "./demo-bookmarks.ts" +class LinkUnreachable {} +class UnsupportedLink {} + +interface CachedPage { + title: string + readableHtml: string +} + function insertDemoBookmarks(user: User) { const query = db.query(` INSERT OR IGNORE INTO bookmarks (id, user_id, kind, title, url) @@ -32,4 +41,49 @@ function findBookmarkHtml(id: string, user: User): string | null { return content_html } -export { insertDemoBookmarks, findBookmarkHtml } +async function cacheWebsite(url: string): Promise { + const websiteText = await fetch(url) + .catch(() => { + throw new LinkUnreachable() + }) + .then((res) => res.text()) + .catch(() => { + throw new UnsupportedLink() + }) + + const dom = new JSDOM(websiteText, { url }) + const reader = new Readability(dom.window.document) + const article = reader.parse() + + if (!article) { + return null + } + + if (article.content) { + const newDom = new JSDOM(article.content, { url }) + const doc = newDom.window.document + + const lightStyleLink = doc.createElement("link") + lightStyleLink.rel = "stylesheet" + lightStyleLink.href = "/reader-styles/sakura.css" + lightStyleLink.media = "screen" + + const darkStyleLink = doc.createElement("link") + darkStyleLink.rel = "stylesheet" + darkStyleLink.href = "/reader-styles/sakura-dark.css" + darkStyleLink.media = "screen and (prefers-color-scheme: dark)" + + doc.head.appendChild(lightStyleLink) + doc.head.appendChild(darkStyleLink) + + article.content = newDom.serialize() + } + + return { + title: article.title || "Untitled", + readableHtml: article.content || "", + } +} + +export { insertDemoBookmarks, findBookmarkHtml, cacheWebsite } +export { LinkUnreachable, UnsupportedLink } diff --git a/packages/server/src/bookmark/handlers.ts b/packages/server/src/bookmark/handlers.ts index dd243da..8832917 100644 --- a/packages/server/src/bookmark/handlers.ts +++ b/packages/server/src/bookmark/handlers.ts @@ -5,9 +5,7 @@ import { ulid } from "ulid" import { db } from "~/database.ts" import { HttpError } from "~/error.ts" import type { User } from "~/user/user.ts" -import { JSDOM } from "jsdom" -import { Readability } from "@mozilla/readability" -import { findBookmarkHtml } from "./bookmark.ts" +import { LinkUnreachable, UnsupportedLink, findBookmarkHtml, cacheWebsite } from "./bookmark.ts" const BOOKMARK_PAGINATION_LIMIT = 100 @@ -66,17 +64,19 @@ async function addBookmark(request: Bun.BunRequest<"/api/bookmarks">, user: User throw new HttpError(400) } - const websiteResponse = await fetch(body.url).catch(() => { - if (body.force) { - return null + const cachedWebsite = await cacheWebsite(body.url).catch((error) => { + if (error instanceof LinkUnreachable) { + if (body.force) { + return null + } + throw new HttpError(400, "LinkUnreachable") } - throw new HttpError(400, "WebsiteUnreachable") + if (error instanceof UnsupportedLink) { + throw new HttpError(400, "UnsupportedLink") + } + console.error(error) + throw new HttpError(500) }) - const websiteText = websiteResponse - ? await websiteResponse.text().catch(() => { - throw new HttpError(400, "UnsupportedWebsite") - }) - : null const bookmark: LinkBookmark = { kind: "link", @@ -88,24 +88,8 @@ async function addBookmark(request: Bun.BunRequest<"/api/bookmarks">, user: User if (body.title) { bookmark.title = body.title - } - - let contentHtml: string - if (websiteText) { - const dom = new JSDOM(websiteText, { - url: body.url, - }) - const reader = new Readability(dom.window.document) - const article = reader.parse() - if (!bookmark.title) { - bookmark.title = article?.title || "Untitled" - } - contentHtml = article?.content || "" - } else { - contentHtml = "" - if (!bookmark.title) { - bookmark.title = "Untitled" - } + } else if (cachedWebsite?.title) { + bookmark.title = cachedWebsite.title } const query = db.query(` @@ -118,7 +102,7 @@ VALUES ($id, $userId, $kind, $title, $url, $html) kind: bookmark.kind, title: bookmark.title, url: bookmark.url, - html: contentHtml, + html: cachedWebsite?.readableHtml ?? "", }) const insertTagQuery = db.query(` diff --git a/packages/web/src/reader-styles/sakura-dark.css b/packages/web/public/reader-styles/sakura-dark.css similarity index 98% rename from packages/web/src/reader-styles/sakura-dark.css rename to packages/web/public/reader-styles/sakura-dark.css index 0fe3c28..c8f31e8 100644 --- a/packages/web/src/reader-styles/sakura-dark.css +++ b/packages/web/public/reader-styles/sakura-dark.css @@ -16,8 +16,8 @@ body { line-height: 1.618; max-width: 38em; margin: auto; - color: #c9c9c9; - background-color: #222222; + color: #e7e5e4; + background-color: #1c1917; padding: 13px; } diff --git a/packages/web/src/reader-styles/sakura.css b/packages/web/public/reader-styles/sakura.css similarity index 91% rename from packages/web/src/reader-styles/sakura.css rename to packages/web/public/reader-styles/sakura.css index fa77d52..45fd9a1 100644 --- a/packages/web/src/reader-styles/sakura.css +++ b/packages/web/public/reader-styles/sakura.css @@ -1,14 +1,15 @@ /* Sakura.css v1.5.0 * ================ * Minimal css theme. + * Colors have been slightly modified to match the rest of the website. * Project: https://github.com/oxalorg/sakura/ */ + /* Body */ html { font-size: 62.5%; - font-family: - -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", - Arial, "Noto Sans", sans-serif; + font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, + "Helvetica Neue", Arial, "Noto Sans", sans-serif; } body { @@ -16,8 +17,8 @@ body { line-height: 1.618; max-width: 38em; margin: auto; - color: #4a4a4a; - background-color: #f9f9f9; + color: #292524; + background-color: #f5f5f4; padding: 13px; } @@ -38,9 +39,8 @@ h4, h5, h6 { line-height: 1.1; - font-family: - -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", - Arial, "Noto Sans", sans-serif; + font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, + "Helvetica Neue", Arial, "Noto Sans", sans-serif; font-weight: 700; margin-top: 3rem; margin-bottom: 1.5rem; diff --git a/packages/web/src/app/bookmarks.tsx b/packages/web/src/app/bookmarks.tsx index 52aaed4..ce036fe 100644 --- a/packages/web/src/app/bookmarks.tsx +++ b/packages/web/src/app/bookmarks.tsx @@ -12,8 +12,6 @@ import { FormField } from "~/components/form-field" import { LoadingSpinner } from "~/components/loading-spinner" import { useMnemonics } from "~/hooks/use-mnemonics" import { useLogOut } from "~/auth" -import sakuraCssSrc from "~/reader-styles/sakura.css?url" -import sakuraDarkCssSrc from "~/reader-styles/sakura-dark.css?url" const LAYOUT_MODE = { popup: "popup", @@ -153,8 +151,8 @@ function Main({ children }: React.PropsWithChildren) { return (
{children} @@ -166,7 +164,12 @@ function BookmarkListPane() { const isBookmarkPreviewOpened = useBookmarkPageStore((state) => state.isBookmarkPreviewOpened) return ( -
+

- +
+ +
) } @@ -393,7 +398,7 @@ function BookmarkListSection() { switch (status) { case "pending": return ( -

+

Loading 

@@ -468,7 +473,7 @@ function BookmarkList({ bookmarks }: { bookmarks: LinkBookmark[] }) { } return ( -
+
{bookmarks.length === 0 ? (

You have not saved any bookmark!

) : ( @@ -495,15 +500,15 @@ function BookmarkPreview() { }, )} > - +
) } -function BookmarkPreviewFrame() { +function BookmarkPreviewContent() { const selectedBookmarkId = useBookmarkPageStore((state) => state.selectedBookmarkId) const actionBarHeight = useBookmarkPageStore((state) => state.actionBarHeight) - const { data, status } = useAuthenticatedQuery(["bookmarks", selectedBookmarkId], () => + const { data, status } = useAuthenticatedQuery(["bookmarks", `${selectedBookmarkId}.html`], () => fetchApi(`/bookmark/${selectedBookmarkId}`, { headers: { Accept: "text/html", @@ -511,23 +516,6 @@ function BookmarkPreviewFrame() { }).then((res) => res.text()), ) - function injectCss(event: React.SyntheticEvent) { - const lightCssLink = document.createElement("link") - lightCssLink.rel = "stylesheet" - lightCssLink.href = sakuraCssSrc - lightCssLink.media = "screen" - - const darkCssLink = document.createElement("link") - darkCssLink.rel = "stylesheet" - darkCssLink.href = sakuraDarkCssSrc - darkCssLink.media = "screen and (prefers-color-scheme: dark)" - - if (event.currentTarget.contentDocument) { - event.currentTarget.contentDocument.head.appendChild(lightCssLink) - event.currentTarget.contentDocument.head.appendChild(darkCssLink) - } - } - switch (status) { case "pending": return ( @@ -537,7 +525,13 @@ function BookmarkPreviewFrame() { ) case "success": return ( -