Skip to content

Commit

Permalink
Adapt the Flexoki color scheme (#29)
Browse files Browse the repository at this point in the history
* Fix no 'saved ✅' when deleting api key

* (Mostly) adapt the flexoki pallette

* Create input component

* Adjust <select> style

* Fix input bottom border

* Apply flexoki to tailwind typography

* Tweak button styles

* Rebuild css in dev mode

* Add back the header and footer border

* Tweak more settings

* Tweak article stylings

* Fix article header

* Fix ai summary styling

* Adjust ai summary placeholder size

* Remove recent articles api endpoint

* tweak ai summary swap

* Include response status in failed to fetch error

* Add custom error page
  • Loading branch information
carterworks authored Jan 21, 2025
1 parent 2ffbd4c commit 1296c1c
Show file tree
Hide file tree
Showing 19 changed files with 432 additions and 89 deletions.
31 changes: 22 additions & 9 deletions src/components/AISummary.tsx
Original file line number Diff line number Diff line change
@@ -1,19 +1,32 @@
import type { FC, PropsWithChildren } from "hono/jsx";
import type { ReadablePage } from "../types";
import type { FC } from "hono/jsx";

const AISummary: FC<{ url: string; summary?: string }> = ({ url, summary }) => {
const proseClasses =
"prose dark:prose-invert font-humanist mt-2 prose-p:mt-0 prose-headings:font-transitional prose-headings:my-0 prose-h2:text-lg";
if (summary) {
return (
<aside className={proseClasses}>
<h2>AI-generated summary</h2>
{/* biome-ignore lint/security/noDangerouslySetInnerHtml: <explanation> */}
<div dangerouslySetInnerHTML={{ __html: summary }} />
</aside>
);
}

const AISummary: FC<{ url: string }> = ({ url }) => {
return (
<aside
hx-trigger="load"
hx-get="/api/summary"
hx-vals={JSON.stringify({ url })}
className="prose dark:prose-invert font-humanist mt-2"
hx-swap="outerHTML"
className={proseClasses}
>
<div class="flex flex-col gap-1">
<div className="w-[30ch] bg-slate-200 dark:bg-gray-600 rounded px-4 py-2 h-4 animate-pulse" />
<div className="w-[33ch] lg:w-[45ch] bg-slate-200 dark:bg-gray-600 rounded px-4 py-2 h-4 animate-pulse" />
<div className="w-[33ch] lg:w-[40ch] bg-slate-200 dark:bg-gray-600 rounded px-4 py-2 h-4 animate-pulse" />
<div className="w-[39ch] lg:w-[42ch] bg-slate-200 dark:bg-gray-600 rounded px-4 py-2 h-4 animate-pulse" />
<div class="flex flex-col gap-1 @container htmx-indicator">
<div className="w-[40cqi] bg-base-50 dark:bg-base-950 px-4 py-2 h-4 animate-pulse" />
<div className="w-[87cqi] bg-base-50 dark:bg-base-950 px-4 py-2 h-4 animate-pulse" />
<div className="w-[93cqi] bg-base-50 dark:bg-base-950 px-4 py-2 h-4 animate-pulse" />
<div className="w-[74cqi] bg-base-50 dark:bg-base-950 px-4 py-2 h-4 animate-pulse" />
<div className="w-[90cqi] bg-base-50 dark:bg-base-950 px-4 py-2 h-4 animate-pulse" />
</div>
</aside>
);
Expand Down
2 changes: 1 addition & 1 deletion src/components/AISummaryError.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import type { FC, PropsWithChildren } from "hono/jsx";

const AISummaryError: FC<PropsWithChildren> = ({ children }) => (
<div className="bg-slate-200 dark:bg-gray-600 rounded px-4 py-2">
<div className="bg-base-50 dark:bg-base-950 rounded px-4 py-2">
<h3 className="font-bold mt-0">Failed to retrieve AI summary</h3>
<p>{children}</p>
</div>
Expand Down
14 changes: 11 additions & 3 deletions src/components/ArticleHeader.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,14 +19,22 @@ const ArticleHeader: FC<{ article: ReadablePage; classes?: string }> = ({
}) => {
const metadata = generateMetadataNuggets(article);
return (
<header className={[classes, "space-y-2"].join(" ")}>
<h2 class="text-3xl font-transitional">
<header
className={[
"space-y-2",
"mb-4",
"pb-4",
"border-b border-base-100 dark:border-base-900",
classes,
].join(" ")}
>
<h2 class="text-2xl font-transitional">
<a href={article.url} class="hover:underline">
{article.title}
</a>
</h2>
<p class="text-sm">{metadata.join(" ・ ")}</p>
<AISummary url={article.url} />
<AISummary url={article.url} summary={article.summary} />
</header>
);
};
Expand Down
2 changes: 1 addition & 1 deletion src/components/ArticleMinimap.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ const ArticleMinimap: FC<{ classes?: string; selector: string }> = ({
classes = "",
selector,
}) => {
return <wc-minimap class={classes} data-article-selector={selector} />;
return <wc-minimap className={classes} data-article-selector={selector} />;
};

export default ArticleMinimap;
14 changes: 6 additions & 8 deletions src/components/Button.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,17 +18,15 @@ function isLink(props: Props): props is LinkProps & CommonProps {
}

const classes = [
"border",
"py-1",
"px-2",
"px-2 py-1",
"rounded",
"transition",
"text-center",
"bg-canvas",
"hover:brightness-95",
"dark:hover:brightness-125",
"active:brightness-105",
"dark:active:brightness-90",
"border",
"drop-shadow",
"cursor-pointer",
"bg-paper hover:bg-base-150 active:bg-base-100 border-base-100",
"dark:bg-base-900 hover:dark:bg-base-800 active:dark:bg-base-850 dark:border-base-900",
] as const;

const Button: FC<PropsWithChildren<Props>> = (props) => {
Expand Down
14 changes: 14 additions & 0 deletions src/components/Input.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import type { FC } from "hono/jsx";

const Input: FC<{
classes?: string;
[k: string]: string | boolean | undefined;
}> = ({ classes = "", ...props }) => {
const className = [
"border border-base-100 dark:border-base-900 rounded block w-full py-1 px-4 transition",
classes,
].join(" ");
return <input className={className} {...props} />;
};

export default Input;
8 changes: 3 additions & 5 deletions src/components/RecentArticles.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,15 +9,13 @@ const RecentArticle: FC<{ article: ReadablePage }> = ({ article }) => {
"md:w-60",
"flex-auto",
"p-2",
"bg-canvas",
"overflow-hidden",
"transition",
"rounded",
"border",
"hover:brightness-95",
"dark:hover:brightness-125",
"active:brightness-105",
"dark:active:brightness-90",
"border-base-100 dark:border-base-900",
"bg-paper hover:bg-base-50 ",
"dark:bg-black hover:dark:bg-base-950 ",
].join(" ")}
>
<a href={`/${article.url}`}>
Expand Down
33 changes: 15 additions & 18 deletions src/components/Settings.tsx
Original file line number Diff line number Diff line change
@@ -1,38 +1,35 @@
import type { FC, PropsWithChildren } from "hono/jsx";
import type { FC } from "hono/jsx";
import Button from "../components/Button";
import Input from "./Input";

const Settings: FC = () => (
<form className="flex flex-col gap-4 items-start" id="settings-form">
<form
className="flex flex-col gap-4 items-start text-black dark:text-base-200"
id="settings-form"
>
<label className="flex flex-col items-start">
<span>AI model</span>
<select name="model" className="border rounded-md py-1 px-4">
<select
name="model"
className="border border-base-100 rounded dark:border-base-900 p-1 bg-paper hover:bg-base-150 active:bg-base-100 dark:bg-base-900 hover:dark:bg-base-800 active:dark:bg-base-850"
>
<option value="gpt-4o-mini">GPT-4o mini</option>
</select>
</label>
{/* biome-ignore lint/a11y/noLabelWithoutControl: <explanation> */}
<label className="flex flex-col items-start">
<span>API key</span>
<input
type="text"
name="apiKey"
className="border focus:border-transparent block w-full rounded-md focus:ring-0 py-1 px-4 transition"
/>
<Input type="text" name="apiKey" />
</label>
<div
id="status"
className="h-4 -mt-3 transition-opacity opacity-0 dark:text-green-400 text-green-700"
className="h-4 -mt-3 transition-opacity opacity-0 text-green-600 dark:text-green-400"
>
✔︎ Saved!
</div>
<div className="flex items-center gap-2">
<Button type="submit" classes="py-1 px-6">
Save
</Button>
<Button
type="reset"
classes="py-1 px-6 text-red-700 border-red-700 dark:text-red-400 dark:border-red-400"
>
Reset
</Button>
<Button type="submit">Save</Button>
<Button type="reset">Reset</Button>
</div>
</form>
);
Expand Down
12 changes: 6 additions & 6 deletions src/layouts/BasePage.tsx
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
import type { FC, PropsWithChildren } from "hono/jsx";
import type { Child, FC, PropsWithChildren } from "hono/jsx";
import Button from "../components/Button";
import Settings from "../components/Settings";

interface BasePageProps {
title?: string;
className?: string;
head?: FC;
head?: Child;
}

const BasePage: FC<PropsWithChildren<BasePageProps>> = ({
Expand All @@ -22,7 +22,7 @@ const BasePage: FC<PropsWithChildren<BasePageProps>> = ({
<title>{title}</title>
<link rel="manifest" href="/manifest.webmanifest" />
<link rel="icon" href="/scissors.svg" type="image/svg+xml" />
{Head && <Head />}
{Head}
<link rel="stylesheet" href="/styles.css" />
<script
src="https://unpkg.com/[email protected]/dist/htmx.min.js"
Expand All @@ -45,7 +45,7 @@ const BasePage: FC<PropsWithChildren<BasePageProps>> = ({
/>
</head>

<body className="text-base px-4 lg:px-0">
<body className="text-base bg-paper font-humanist dark:bg-black text-black dark:text-base-200 px-4 lg:px-0">
<div className="m-auto max-w-prose space-y-2">
<header
className="flex items-center mb-4 gap-4 print:hidden"
Expand All @@ -63,13 +63,13 @@ const BasePage: FC<PropsWithChildren<BasePageProps>> = ({
<div
popover="auto"
id="settings-dialog"
className="border p-2 rounded m-auto"
className="p-4 rounded m-auto bg-paper dark:bg-black drop-shadow-lg border border-base-100 dark:border-base-900"
>
<Settings />
</div>
</header>
<div className={className}>{children}</div>
<footer className="border-t-2 py-2 border-neutral-400 flex justify-between items-start">
<footer className="flex justify-between py-4 my-4 items-start border-t border-base-100 dark:border-base-900">
<ul>
<li>
<a
Expand Down
5 changes: 4 additions & 1 deletion src/middleware/logger.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,10 @@ import type { MiddlewareHandler } from "hono";
*/
export function getColorEnabled(): boolean {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const { process, Deno } = globalThis as { process?: { env: { NO_COLOR?: unknown } }; Deno?: { noColor?: boolean } };
const { process, Deno } = globalThis as {
process?: { env: { NO_COLOR?: unknown } };
Deno?: { noColor?: boolean };
};

const isNoColor =
typeof Deno?.noColor === "boolean"
Expand Down
57 changes: 47 additions & 10 deletions src/pages/ClippedUrl.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import type { FC } from "hono/jsx";
import type { FC, PropsWithChildren } from "hono/jsx";
import ArticleHeader from "../components/ArticleHeader";
import ArticleMinimap from "../components/ArticleMinimap";
import Button from "../components/Button";
Expand Down Expand Up @@ -69,19 +69,48 @@ function generateObsidianUri(
return `obsidian://new?file=${encodeURIComponent(folder + fileName)}&content=${encodeURIComponent(fileContent)}${vaultName}`;
}

const ClippedPageHead: FC<{ article: ReadablePage }> = ({ article }) => <></>;
const ClippedPageHead: FC<{ article: ReadablePage }> = ({ article }) => {
const plainTextSummary = article.summary
? article.summary.replace(/<[^>]*>/g, "")
: `${article.textContent.trim().substring(0, 300)}…`;

const articleHostname = new URL(article.url).hostname;
return (
<>
<meta name="description" content={plainTextSummary} />
<meta
property="og:title"
content={`${article.title} | ${articleHostname} | yazzy`}
/>
<meta property="og:description" content={plainTextSummary} />
<meta property="og:url" content={article.url} />
<meta property="og:type" content="article" />
{article.published && (
<meta
property="og:article:published_time"
content={formatDate(article.published)}
/>
)}
{article.author && (
<meta property="og:article:author" content={article.author} />
)}
</>
);
};

const ClippedUrlPage: FC<{ article: ReadablePage }> = ({ article }) => {
const plainTextSummary = article.summary
? article.summary.replace(/<[^>]*>/g, "")
: `${article.textContent.substring(0, 300)}…`;
const articleHostname = new URL(article.url).hostname;
const markdownContent = generateObsidianContents(article);
const obsidianUri = generateObsidianUri(markdownContent, article.title);
const plainTextContent = `${article.title}\n---\nSummary\n\n${plainTextSummary}\n---\n${article.textContent}`;

return (
<BasePage className="grid grid-cols-1 lg:grid-cols-[auto_1fr] gap-2 lg:gap-4">
<BasePage
className="grid grid-cols-1 lg:grid-cols-[auto_1fr] gap-2 lg:gap-4"
head={<ClippedPageHead article={article} />}
>
<aside className="flex lg:flex-col gap-3 items-center lg:col-start-1 lg:row-span-2 print:hidden">
<Button href={obsidianUri} title="Save to Obsidian" type="link">
<Obsidian className="h-4" />
Expand All @@ -102,18 +131,26 @@ const ClippedUrlPage: FC<{ article: ReadablePage }> = ({ article }) => {
</DownloadAs>
<ArticleMinimap
selector="article"
classes="border fixed bottom-3 mx-auto px-2 rounded lg:m-0 lg:top-2 lg:sticky bg-white dark:bg-neutral-800"
classes="fixed bottom-3 mx-auto px-2 rounded lg:m-0 lg:top-2 lg:sticky bg-paper dark:bg-black"
/>
</aside>
<div className="lg:col-start-2 max-w-prose space-x-2">
<main>
<article>
<ArticleHeader
classes="border-b-2 border-neutral-400 pb-2"
article={article}
/>
<ArticleHeader article={article} />
<div
className="prose dark:prose-invert lg:prose-xl font-humanist prose-headings:font-transitional prose-a:break-words prose-hr:my-4 prose-headings:mt-10 prose-headings:mb-0 !prose-img:max-w-lg prose-img:mx-auto prose-img:rounded"
className={[
"prose dark:prose-invert",
"font-humanist",
"prose-headings:font-transitional",
"prose-a:break-words",
"prose-hr:my-4",
"prose-headings:mt-6",
"prose-headings:mb-0",
"!prose-img:max-w-lg",
"prose-img:mx-auto",
"prose-img:rounded",
].join(" ")}
hx-disable
// biome-ignore lint/security/noDangerouslySetInnerHtml: <explanation>
dangerouslySetInnerHTML={{ __html: article.htmlContent }}
Expand Down
13 changes: 13 additions & 0 deletions src/pages/ErrorPage.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import type { FC } from "hono/jsx";
import BasePage from "../layouts/BasePage";

const ErrorPage: FC<{ message: string }> = ({ message }) => {
return (
<BasePage title="Failure to clip">
<h1 className="text-2xl font-transitional">Failure to clip</h1>
<p>{message}</p>
</BasePage>
);
};

export default ErrorPage;
Loading

0 comments on commit 1296c1c

Please sign in to comment.