feat: update some shit
This commit is contained in:
parent
799a736f36
commit
03d17569da
154
components/PageHero.tsx
Normal file
154
components/PageHero.tsx
Normal file
@ -0,0 +1,154 @@
|
|||||||
|
import { withSubComponents } from "@components/helpers/withSubComponents.ts";
|
||||||
|
import Image from "@components/Image.tsx";
|
||||||
|
import { IconExternalLink } from "@components/icons.tsx";
|
||||||
|
import { type ComponentChildren, createContext } from "preact";
|
||||||
|
import { IconArrowNarrowLeft } from "@components/icons.tsx";
|
||||||
|
import { IconEdit } from "@components/icons.tsx";
|
||||||
|
import { useContext } from "preact/hooks";
|
||||||
|
import { GenericResource } from "@lib/types.ts";
|
||||||
|
|
||||||
|
const HeroContext = createContext<{ image?: string; thumbnail?: string }>({
|
||||||
|
image: undefined,
|
||||||
|
thumbnail: undefined,
|
||||||
|
});
|
||||||
|
|
||||||
|
function Wrapper(
|
||||||
|
{ children, image, thumbnail }: {
|
||||||
|
children: ComponentChildren;
|
||||||
|
image?: string;
|
||||||
|
thumbnail?: string;
|
||||||
|
},
|
||||||
|
) {
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
class={`flex justify-between flex-col relative w-full min-h-[${
|
||||||
|
image ? 400 : 200
|
||||||
|
}px] rounded-3xl overflow-hidden`}
|
||||||
|
>
|
||||||
|
<HeroContext.Provider value={{ image }}>
|
||||||
|
{image &&
|
||||||
|
(
|
||||||
|
<Image
|
||||||
|
fill
|
||||||
|
src={image}
|
||||||
|
thumbnail={thumbnail}
|
||||||
|
alt="Recipe Banner"
|
||||||
|
// style={{ objectPosition: "0% 25%" }}
|
||||||
|
class="absolute object-cover w-full h-full -z-10"
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
{children}
|
||||||
|
</HeroContext.Provider>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function Title(
|
||||||
|
{ children, link }: { children: ComponentChildren; link?: string },
|
||||||
|
) {
|
||||||
|
const ctx = useContext(HeroContext);
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
class={`${
|
||||||
|
ctx.image ? "noisy-gradient" : ""
|
||||||
|
} after:opacity-90 flex gap-4 items-center ${ctx.image ? "pt-12" : ""}`}
|
||||||
|
>
|
||||||
|
<h2
|
||||||
|
class="relative text-4xl font-bold z-10"
|
||||||
|
style={{ color: ctx.image ? "#1F1F1F" : "white" }}
|
||||||
|
>
|
||||||
|
{children}
|
||||||
|
{link &&
|
||||||
|
(
|
||||||
|
<a
|
||||||
|
href={link}
|
||||||
|
target="__blank"
|
||||||
|
class="p-2 inline-flex"
|
||||||
|
>
|
||||||
|
<IconExternalLink />
|
||||||
|
</a>
|
||||||
|
)}
|
||||||
|
</h2>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function BackLink({ href }: { href: string }) {
|
||||||
|
return (
|
||||||
|
<a
|
||||||
|
class="px-4 py-2 bg-gray-300 text-gray-800 rounded-lg flex gap-1 items-center"
|
||||||
|
href={href}
|
||||||
|
>
|
||||||
|
<IconArrowNarrowLeft class="w-5 h-5" /> Back
|
||||||
|
</a>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function EditLink({ href }: { href: string }) {
|
||||||
|
const ctx = useContext(HeroContext);
|
||||||
|
return (
|
||||||
|
<a
|
||||||
|
class={`px-4 py-2 ${
|
||||||
|
ctx.image ? "bg-gray-300 text-gray-800" : "text-gray-200"
|
||||||
|
} rounded-lg flex gap-1 items-center`}
|
||||||
|
href={href}
|
||||||
|
>
|
||||||
|
<IconEdit class="w-5 h-5" />
|
||||||
|
</a>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function Header({ children }: { children: ComponentChildren }) {
|
||||||
|
return (
|
||||||
|
<div class="flex justify-between mx-8 mt-8">
|
||||||
|
{children}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function Subline(
|
||||||
|
{ entries, children }: {
|
||||||
|
children?: ComponentChildren;
|
||||||
|
entries: (string | { href: string; title: string })[];
|
||||||
|
},
|
||||||
|
) {
|
||||||
|
const ctx = useContext(HeroContext);
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
class={`relative flex items-center z-50 flex gap-5 font-sm text-light mt-3`}
|
||||||
|
style={{ color: ctx.image ? "#1F1F1F" : "white" }}
|
||||||
|
>
|
||||||
|
{children}
|
||||||
|
{entries.filter((s) =>
|
||||||
|
s && (typeof s === "string" ? s?.length > 1 : true)
|
||||||
|
).map((s) => {
|
||||||
|
if (typeof s === "string") {
|
||||||
|
return <span>{s}</span>;
|
||||||
|
} else {
|
||||||
|
return <a href={s.href}>{s.title}</a>;
|
||||||
|
}
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function Footer({ children }: { children: ComponentChildren }) {
|
||||||
|
const ctx = useContext(HeroContext);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
class={`relative inset-x-0 py-4 px-8 ${ctx.image ? "py-8" : ""} `}
|
||||||
|
>
|
||||||
|
{children}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default withSubComponents(Wrapper, {
|
||||||
|
EditLink,
|
||||||
|
BackLink,
|
||||||
|
Footer,
|
||||||
|
Subline,
|
||||||
|
Header,
|
||||||
|
Title,
|
||||||
|
});
|
@ -1,106 +0,0 @@
|
|||||||
import { Star } from "@components/Stars.tsx";
|
|
||||||
import {
|
|
||||||
IconArrowNarrowLeft,
|
|
||||||
IconEdit,
|
|
||||||
IconExternalLink,
|
|
||||||
} from "@components/icons.tsx";
|
|
||||||
import Image from "@components/Image.tsx";
|
|
||||||
import { GenericResource } from "@lib/types.ts";
|
|
||||||
|
|
||||||
export function RecipeHero(
|
|
||||||
{ data, subline, backlink, editLink }: {
|
|
||||||
backlink: string;
|
|
||||||
subline?: (string | { title: string; href: string })[];
|
|
||||||
editLink?: string;
|
|
||||||
data: GenericResource;
|
|
||||||
},
|
|
||||||
) {
|
|
||||||
const { meta: { image } = {} } = data;
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div
|
|
||||||
class={`flex justify-between flex-col relative w-full min-h-[${
|
|
||||||
image ? 400 : 200
|
|
||||||
}px] rounded-3xl overflow-hidden`}
|
|
||||||
>
|
|
||||||
{image &&
|
|
||||||
(
|
|
||||||
<Image
|
|
||||||
fill
|
|
||||||
src={image}
|
|
||||||
thumbnail={data.meta?.thumbnail}
|
|
||||||
alt="Recipe Banner"
|
|
||||||
style={{ objectPosition: "0% 25%" }}
|
|
||||||
class="absolute object-cover w-full h-full -z-10"
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
|
|
||||||
<div class="flex justify-between mx-8 mt-8">
|
|
||||||
<a
|
|
||||||
class="px-4 py-2 bg-gray-300 text-gray-800 rounded-lg flex gap-1 items-center"
|
|
||||||
href={backlink}
|
|
||||||
>
|
|
||||||
<IconArrowNarrowLeft class="w-5 h-5" /> Back
|
|
||||||
</a>
|
|
||||||
{editLink &&
|
|
||||||
(
|
|
||||||
<a
|
|
||||||
class={`px-4 py-2 ${
|
|
||||||
image ? "bg-gray-300 text-gray-800" : "text-gray-200"
|
|
||||||
} rounded-lg flex gap-1 items-center`}
|
|
||||||
href={editLink}
|
|
||||||
>
|
|
||||||
<IconEdit class="w-5 h-5" />
|
|
||||||
</a>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div
|
|
||||||
class={`relative inset-x-0 py-4 px-8 ${image ? "py-8" : ""} `}
|
|
||||||
>
|
|
||||||
<div
|
|
||||||
class={`${
|
|
||||||
image ? "noisy-gradient" : ""
|
|
||||||
} after:opacity-90 flex gap-4 items-center ${image ? "pt-12" : ""}`}
|
|
||||||
>
|
|
||||||
<h2
|
|
||||||
class="relative text-4xl font-bold z-10"
|
|
||||||
style={{ color: image ? "#1F1F1F" : "white" }}
|
|
||||||
>
|
|
||||||
{data.name}
|
|
||||||
{data.meta?.link &&
|
|
||||||
(
|
|
||||||
<a
|
|
||||||
href={data.meta.link}
|
|
||||||
target="__blank"
|
|
||||||
class="p-2 inline-flex"
|
|
||||||
name="Link to Original recipe"
|
|
||||||
>
|
|
||||||
<IconExternalLink />
|
|
||||||
</a>
|
|
||||||
)}
|
|
||||||
</h2>
|
|
||||||
|
|
||||||
{data.meta?.rating && <Star rating={data.meta.rating} />}
|
|
||||||
</div>
|
|
||||||
{subline?.length &&
|
|
||||||
(
|
|
||||||
<div
|
|
||||||
class={`relative z-50 flex gap-5 font-sm text-light mt-3`}
|
|
||||||
style={{ color: image ? "#1F1F1F" : "white" }}
|
|
||||||
>
|
|
||||||
{subline.filter((s) =>
|
|
||||||
s && (typeof s === "string" ? s?.length > 1 : true)
|
|
||||||
).map((s) => {
|
|
||||||
if (typeof s === "string") {
|
|
||||||
return <span>{s}</span>;
|
|
||||||
} else {
|
|
||||||
return <a href={s.href}>{s.title}</a>;
|
|
||||||
}
|
|
||||||
})}
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
11
components/helpers/withSubComponents.ts
Normal file
11
components/helpers/withSubComponents.ts
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
// deno-lint-ignore no-explicit-any
|
||||||
|
export function withSubComponents<A, B extends Record<string, any>>(
|
||||||
|
component: A,
|
||||||
|
subcomponents: B,
|
||||||
|
): A & B {
|
||||||
|
Object.keys(subcomponents).forEach((key) => {
|
||||||
|
// deno-lint-ignore no-explicit-any
|
||||||
|
(component as any)[key] = (subcomponents as any)[key];
|
||||||
|
});
|
||||||
|
return component as A & B;
|
||||||
|
}
|
@ -16,3 +16,4 @@ export { default as IconLogout } from "https://deno.land/x/tabler_icons_tsx@0.0.
|
|||||||
export { default as IconSearch } from "https://deno.land/x/tabler_icons_tsx@0.0.3/tsx/search.tsx";
|
export { default as IconSearch } from "https://deno.land/x/tabler_icons_tsx@0.0.3/tsx/search.tsx";
|
||||||
export { default as IconGhost } from "https://deno.land/x/tabler_icons_tsx@0.0.3/tsx/ghost.tsx";
|
export { default as IconGhost } from "https://deno.land/x/tabler_icons_tsx@0.0.3/tsx/ghost.tsx";
|
||||||
export { default as IconBrandYoutube } from "https://deno.land/x/tabler_icons_tsx@0.0.3/tsx/brand-youtube.tsx";
|
export { default as IconBrandYoutube } from "https://deno.land/x/tabler_icons_tsx@0.0.3/tsx/brand-youtube.tsx";
|
||||||
|
export { default as IconWand } from "https://deno.land/x/tabler_icons_tsx@0.0.5/tsx/wand.tsx";
|
||||||
|
@ -1,9 +1,16 @@
|
|||||||
import { useCallback, useState } from "preact/hooks";
|
import { useCallback, useState } from "preact/hooks";
|
||||||
|
import { IconWand } from "@components/icons.tsx";
|
||||||
|
|
||||||
export function Recommendations({ id, type }: { id: string; type: string }) {
|
type RecommendationState = "disabled" | "loading";
|
||||||
const [state, setState] = useState<"disabled" | "loading">(
|
|
||||||
"disabled",
|
export function Recommendations(
|
||||||
);
|
{ id, type, load = false }: {
|
||||||
|
id: string;
|
||||||
|
type: string;
|
||||||
|
load?: boolean;
|
||||||
|
},
|
||||||
|
) {
|
||||||
|
const [state, setState] = useState<RecommendationState>("disabled");
|
||||||
const [results, setResults] = useState();
|
const [results, setResults] = useState();
|
||||||
|
|
||||||
const startFetch = useCallback(
|
const startFetch = useCallback(
|
||||||
@ -17,27 +24,54 @@ export function Recommendations({ id, type }: { id: string; type: string }) {
|
|||||||
[id, type],
|
[id, type],
|
||||||
);
|
);
|
||||||
|
|
||||||
if (results) {
|
if (load) {
|
||||||
return (
|
startFetch();
|
||||||
<ul class="gap-5">
|
|
||||||
{results.map((res) => {
|
|
||||||
return (
|
|
||||||
<div class="flex gap-5 items-center mb-4">
|
|
||||||
<img
|
|
||||||
class="w-12 h-12 rounded-full object-cover"
|
|
||||||
src={`https://image.tmdb.org/t/p/original${res.poster_path}`}
|
|
||||||
/>
|
|
||||||
<p>{res.title}</p>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
})}
|
|
||||||
</ul>
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (state === "loading") {
|
return (
|
||||||
return <p>Loading...</p>;
|
<span>
|
||||||
}
|
{results && (
|
||||||
|
<div
|
||||||
|
style={{ boxShadow: "0px 49px 33px #1412183b inset" }}
|
||||||
|
class="relative w-full bg-white -mt-10 pt-10 -z-50 rounded-2xl p-5"
|
||||||
|
>
|
||||||
|
<h3 class="text-2xl my-5 flex items-center gap-2">
|
||||||
|
Similar Movies
|
||||||
|
</h3>
|
||||||
|
<ul class="gap-5">
|
||||||
|
{results.map((res) => {
|
||||||
|
return (
|
||||||
|
<div class="flex gap-5 items-center mb-4">
|
||||||
|
<img
|
||||||
|
class="w-12 h-12 rounded-full object-cover"
|
||||||
|
src={`https://image.tmdb.org/t/p/original${res.poster_path}`}
|
||||||
|
/>
|
||||||
|
<p>{res.title}</p>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
return <button onClick={startFetch}>load recommendations</button>;
|
{state === "loading" && !results && (
|
||||||
|
<div
|
||||||
|
style={{ boxShadow: "0px 49px 33px #1412183b inset" }}
|
||||||
|
class="relative w-full bg-white -mt-10 pt-10 -z-50 rounded-2xl p-5"
|
||||||
|
>
|
||||||
|
<p class="mt-5">Loading...</p>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{!results && state === "disabled" &&
|
||||||
|
(
|
||||||
|
<button
|
||||||
|
onClick={startFetch}
|
||||||
|
class="ml-8 mt-2 font-thin flex items-center gap-2 text-white my-2"
|
||||||
|
>
|
||||||
|
<IconWand class="w-4" /> Similar
|
||||||
|
</button>
|
||||||
|
)}
|
||||||
|
</span>
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
@ -4,7 +4,7 @@ import { CSS, KATEX_CSS } from "https://deno.land/x/gfm@0.2.5/mod.ts";
|
|||||||
import { Head, Partial } from "$fresh/runtime.ts";
|
import { Head, Partial } from "$fresh/runtime.ts";
|
||||||
import { Emoji } from "@components/Emoji.tsx";
|
import { Emoji } from "@components/Emoji.tsx";
|
||||||
|
|
||||||
export default function MyLayout({ Component, url }: LayoutProps) {
|
export default function MyLayout({ Component }: LayoutProps) {
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
class="md:grid mx-auto"
|
class="md:grid mx-auto"
|
||||||
@ -20,7 +20,7 @@ export default function MyLayout({ Component, url }: LayoutProps) {
|
|||||||
return (
|
return (
|
||||||
<a
|
<a
|
||||||
href={m.link}
|
href={m.link}
|
||||||
class={`flex items-center gap-2 text-white [data-ancestor]:bg-white [data-ancestor]:text-black p-3 text-xl w-full rounded-2xl`}
|
class={`flex items-center gap-2 text-white [data-current]:bg-white [data-current]:text-black p-3 text-xl w-full rounded-2xl`}
|
||||||
>
|
>
|
||||||
{<Emoji class="w-6 h-6" name={m.emoji} />} {m.name}
|
{<Emoji class="w-6 h-6" name={m.emoji} />} {m.name}
|
||||||
</a>
|
</a>
|
||||||
|
@ -1,15 +1,16 @@
|
|||||||
import { Handlers, PageProps } from "$fresh/server.ts";
|
import { Handlers, PageProps } from "$fresh/server.ts";
|
||||||
import { MainLayout } from "@components/layouts/main.tsx";
|
import { MainLayout } from "@components/layouts/main.tsx";
|
||||||
import { Article, getArticle } from "@lib/resource/articles.ts";
|
import { Article, getArticle } from "@lib/resource/articles.ts";
|
||||||
import { RecipeHero } from "@components/RecipeHero.tsx";
|
|
||||||
import { KMenu } from "@islands/KMenu.tsx";
|
import { KMenu } from "@islands/KMenu.tsx";
|
||||||
import { YoutubePlayer } from "@components/Youtube.tsx";
|
import { YoutubePlayer } from "@components/Youtube.tsx";
|
||||||
import { HashTags } from "@components/HashTags.tsx";
|
import { HashTags } from "@components/HashTags.tsx";
|
||||||
import { isYoutubeLink } from "@lib/string.ts";
|
import { isYoutubeLink } from "@lib/string.ts";
|
||||||
import { renderMarkdown } from "@lib/documents.ts";
|
import { renderMarkdown } from "@lib/documents.ts";
|
||||||
import { RedirectSearchHandler } from "@islands/Search.tsx";
|
import { RedirectSearchHandler } from "@islands/Search.tsx";
|
||||||
|
import PageHero from "@components/PageHero.tsx";
|
||||||
|
import { Star } from "@components/Stars.tsx";
|
||||||
|
|
||||||
export const handler: Handlers<Article | null> = {
|
export const handler: Handlers<{ article: Article; session: unknown }> = {
|
||||||
async GET(_, ctx) {
|
async GET(_, ctx) {
|
||||||
const article = await getArticle(ctx.params.name);
|
const article = await getArticle(ctx.params.name);
|
||||||
return ctx.render({ article, session: ctx.state.session });
|
return ctx.render({ article, session: ctx.state.session });
|
||||||
@ -33,20 +34,33 @@ export default function Greet(
|
|||||||
>
|
>
|
||||||
<RedirectSearchHandler />
|
<RedirectSearchHandler />
|
||||||
<KMenu type="main" context={{ type: "article" }} />
|
<KMenu type="main" context={{ type: "article" }} />
|
||||||
<RecipeHero
|
|
||||||
data={article}
|
<PageHero image={article.meta.image} thumbnail={article.meta.thumbnail}>
|
||||||
subline={[
|
<PageHero.Header>
|
||||||
author && {
|
<PageHero.BackLink href="/articles" />
|
||||||
title: author,
|
{session && (
|
||||||
href: `/?q=${encodeURIComponent(author)}`,
|
<PageHero.EditLink
|
||||||
},
|
href={`https://notes.max-richter.dev/Media/articles/${article.id}`}
|
||||||
date.toString(),
|
/>
|
||||||
]}
|
)}
|
||||||
editLink={session
|
</PageHero.Header>
|
||||||
? `https://notes.max-richter.dev/Media/articles/${article.id}`
|
<PageHero.Footer>
|
||||||
: ""}
|
<PageHero.Title link={article.meta.link}>
|
||||||
backlink="/articles"
|
{article.name}
|
||||||
/>
|
</PageHero.Title>
|
||||||
|
<PageHero.Subline
|
||||||
|
entries={[
|
||||||
|
author && {
|
||||||
|
title: author,
|
||||||
|
href: `/?q=${encodeURIComponent(author)}`,
|
||||||
|
},
|
||||||
|
date.toString(),
|
||||||
|
]}
|
||||||
|
>
|
||||||
|
{article.meta.rating && <Star rating={article.meta.rating} />}
|
||||||
|
</PageHero.Subline>
|
||||||
|
</PageHero.Footer>
|
||||||
|
</PageHero>
|
||||||
{article.tags.length > 0 && (
|
{article.tags.length > 0 && (
|
||||||
<>
|
<>
|
||||||
<br />
|
<br />
|
||||||
|
@ -1,12 +1,13 @@
|
|||||||
import { Handlers, PageProps, RouteContext } from "$fresh/server.ts";
|
import { PageProps, RouteContext } from "$fresh/server.ts";
|
||||||
import { MainLayout } from "@components/layouts/main.tsx";
|
import { MainLayout } from "@components/layouts/main.tsx";
|
||||||
import { getMovie, Movie } from "@lib/resource/movies.ts";
|
import { getMovie, Movie } from "@lib/resource/movies.ts";
|
||||||
import { RecipeHero } from "@components/RecipeHero.tsx";
|
|
||||||
import { HashTags } from "@components/HashTags.tsx";
|
import { HashTags } from "@components/HashTags.tsx";
|
||||||
import { renderMarkdown } from "@lib/documents.ts";
|
import { renderMarkdown } from "@lib/documents.ts";
|
||||||
import { KMenu } from "@islands/KMenu.tsx";
|
import { KMenu } from "@islands/KMenu.tsx";
|
||||||
import { RedirectSearchHandler } from "@islands/Search.tsx";
|
import { RedirectSearchHandler } from "@islands/Search.tsx";
|
||||||
import { Recommendations } from "@islands/Recommendations.tsx";
|
import { Recommendations } from "@islands/Recommendations.tsx";
|
||||||
|
import PageHero from "@components/PageHero.tsx";
|
||||||
|
import { Star } from "@components/Stars.tsx";
|
||||||
|
|
||||||
export default async function Greet(
|
export default async function Greet(
|
||||||
props: PageProps<{ movie: Movie; session: Record<string, string> }>,
|
props: PageProps<{ movie: Movie; session: Record<string, string> }>,
|
||||||
@ -23,19 +24,33 @@ export default async function Greet(
|
|||||||
<MainLayout url={props.url} title={`Movie > ${movie.name}`} context={movie}>
|
<MainLayout url={props.url} title={`Movie > ${movie.name}`} context={movie}>
|
||||||
<RedirectSearchHandler />
|
<RedirectSearchHandler />
|
||||||
<KMenu type="main" context={movie} />
|
<KMenu type="main" context={movie} />
|
||||||
<RecipeHero
|
<PageHero image={movie.meta.image} thumbnail={movie.meta.thumbnail}>
|
||||||
data={movie}
|
<PageHero.Header>
|
||||||
subline={[
|
<PageHero.BackLink href="/movies" />
|
||||||
author && {
|
{session && (
|
||||||
title: author,
|
<PageHero.EditLink
|
||||||
href: `/?q=${encodeURIComponent(author)}`,
|
href={`https://notes.max-richter.dev/Media/movies/${movie.id}`}
|
||||||
},
|
/>
|
||||||
date.toString(),
|
)}
|
||||||
]}
|
</PageHero.Header>
|
||||||
editLink={session
|
<PageHero.Footer>
|
||||||
? `https://notes.max-richter.dev/Media/movies/${movie.id}`
|
<PageHero.Title>{movie.name}</PageHero.Title>
|
||||||
: ""}
|
<PageHero.Subline
|
||||||
backlink="/movies"
|
entries={[
|
||||||
|
author && {
|
||||||
|
title: author,
|
||||||
|
href: `/?q=${encodeURIComponent(author)}`,
|
||||||
|
},
|
||||||
|
date.toString(),
|
||||||
|
]}
|
||||||
|
>
|
||||||
|
{movie.meta.rating && <Star rating={movie.meta.rating} />}
|
||||||
|
</PageHero.Subline>
|
||||||
|
</PageHero.Footer>
|
||||||
|
</PageHero>
|
||||||
|
<Recommendations
|
||||||
|
id={movie.id}
|
||||||
|
type="movie"
|
||||||
/>
|
/>
|
||||||
{movie.tags.length > 0 && (
|
{movie.tags.length > 0 && (
|
||||||
<>
|
<>
|
||||||
@ -53,8 +68,6 @@ export default async function Greet(
|
|||||||
>
|
>
|
||||||
{content}
|
{content}
|
||||||
</pre>
|
</pre>
|
||||||
|
|
||||||
<Recommendations id={movie.id} type="movie"></Recommendations>
|
|
||||||
</div>
|
</div>
|
||||||
</MainLayout>
|
</MainLayout>
|
||||||
);
|
);
|
||||||
|
@ -1,14 +1,15 @@
|
|||||||
import { Handlers, PageProps } from "$fresh/server.ts";
|
import { Handlers, PageProps } from "$fresh/server.ts";
|
||||||
import { IngredientsList } from "@islands/IngredientsList.tsx";
|
import { IngredientsList } from "@islands/IngredientsList.tsx";
|
||||||
import { RecipeHero } from "@components/RecipeHero.tsx";
|
|
||||||
import { MainLayout } from "@components/layouts/main.tsx";
|
import { MainLayout } from "@components/layouts/main.tsx";
|
||||||
import Counter from "@islands/Counter.tsx";
|
import Counter from "@islands/Counter.tsx";
|
||||||
import { useSignal } from "@preact/signals";
|
import { useSignal } from "@preact/signals";
|
||||||
import { getRecipe, Recipe } from "@lib/resource/recipes.ts";
|
import { getRecipe, Recipe } from "@lib/resource/recipes.ts";
|
||||||
import { RedirectSearchHandler } from "@islands/Search.tsx";
|
import { RedirectSearchHandler } from "@islands/Search.tsx";
|
||||||
import { KMenu } from "@islands/KMenu.tsx";
|
import { KMenu } from "@islands/KMenu.tsx";
|
||||||
|
import PageHero from "@components/PageHero.tsx";
|
||||||
|
import { Star } from "@components/Stars.tsx";
|
||||||
|
|
||||||
export const handler: Handlers<Recipe | null> = {
|
export const handler: Handlers<{ recipe: Recipe; session: unknown } | null> = {
|
||||||
async GET(_, ctx) {
|
async GET(_, ctx) {
|
||||||
const recipe = await getRecipe(ctx.params.name);
|
const recipe = await getRecipe(ctx.params.name);
|
||||||
return ctx.render({ recipe, session: ctx.state.session });
|
return ctx.render({ recipe, session: ctx.state.session });
|
||||||
@ -25,7 +26,7 @@ export default function Greet(
|
|||||||
|
|
||||||
const subline = [
|
const subline = [
|
||||||
recipe?.meta?.time && `Duration ${recipe.meta.time}`,
|
recipe?.meta?.time && `Duration ${recipe.meta.time}`,
|
||||||
].filter(Boolean);
|
].filter(Boolean) as string[];
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<MainLayout
|
<MainLayout
|
||||||
@ -35,14 +36,25 @@ export default function Greet(
|
|||||||
>
|
>
|
||||||
<RedirectSearchHandler />
|
<RedirectSearchHandler />
|
||||||
<KMenu type="main" context={recipe} />
|
<KMenu type="main" context={recipe} />
|
||||||
<RecipeHero
|
|
||||||
data={recipe}
|
<PageHero image={recipe.meta?.image} thumbnail={recipe.meta?.thumbnail}>
|
||||||
backlink="/recipes"
|
<PageHero.Header>
|
||||||
editLink={session
|
<PageHero.BackLink href="/articles" />
|
||||||
? `https://notes.max-richter.dev/Recipes/${recipe.id}`
|
{session && (
|
||||||
: ""}
|
<PageHero.EditLink
|
||||||
subline={subline}
|
href={`https://notes.max-richter.dev/Recipes/${recipe.id}`}
|
||||||
/>
|
/>
|
||||||
|
)}
|
||||||
|
</PageHero.Header>
|
||||||
|
<PageHero.Footer>
|
||||||
|
<PageHero.Title>{recipe.name}</PageHero.Title>
|
||||||
|
<PageHero.Subline
|
||||||
|
entries={subline}
|
||||||
|
>
|
||||||
|
{recipe.meta?.rating && <Star rating={recipe.meta?.rating} />}
|
||||||
|
</PageHero.Subline>
|
||||||
|
</PageHero.Footer>
|
||||||
|
</PageHero>
|
||||||
<div class="px-8 text-white mt-10">
|
<div class="px-8 text-white mt-10">
|
||||||
<div class="flex items-center gap-8">
|
<div class="flex items-center gap-8">
|
||||||
<h3 class="text-3xl my-5">Ingredients</h3>
|
<h3 class="text-3xl my-5">Ingredients</h3>
|
||||||
|
@ -1,11 +1,12 @@
|
|||||||
import { Handlers, PageProps } from "$fresh/server.ts";
|
import { Handlers, PageProps } from "$fresh/server.ts";
|
||||||
import { MainLayout } from "@components/layouts/main.tsx";
|
import { MainLayout } from "@components/layouts/main.tsx";
|
||||||
import { RecipeHero } from "@components/RecipeHero.tsx";
|
|
||||||
import { HashTags } from "@components/HashTags.tsx";
|
import { HashTags } from "@components/HashTags.tsx";
|
||||||
import { renderMarkdown } from "@lib/documents.ts";
|
import { renderMarkdown } from "@lib/documents.ts";
|
||||||
import { getSeries, Series } from "@lib/resource/series.ts";
|
import { getSeries, Series } from "@lib/resource/series.ts";
|
||||||
import { RedirectSearchHandler } from "@islands/Search.tsx";
|
import { RedirectSearchHandler } from "@islands/Search.tsx";
|
||||||
import { KMenu } from "@islands/KMenu.tsx";
|
import { KMenu } from "@islands/KMenu.tsx";
|
||||||
|
import PageHero from "@components/PageHero.tsx";
|
||||||
|
import { Star } from "@components/Stars.tsx";
|
||||||
|
|
||||||
export const handler: Handlers<{ serie: Series; session: unknown }> = {
|
export const handler: Handlers<{ serie: Series; session: unknown }> = {
|
||||||
async GET(_, ctx) {
|
async GET(_, ctx) {
|
||||||
@ -27,20 +28,31 @@ export default function Greet(
|
|||||||
<MainLayout url={props.url} title={`Serie > ${serie.name}`} context={serie}>
|
<MainLayout url={props.url} title={`Serie > ${serie.name}`} context={serie}>
|
||||||
<RedirectSearchHandler />
|
<RedirectSearchHandler />
|
||||||
<KMenu type="main" context={serie} />
|
<KMenu type="main" context={serie} />
|
||||||
<RecipeHero
|
|
||||||
data={serie}
|
<PageHero image={serie.meta.image} thumbnail={serie.meta.thumbnail}>
|
||||||
subline={[
|
<PageHero.Header>
|
||||||
author && {
|
<PageHero.BackLink href="/series" />
|
||||||
title: author,
|
{session && (
|
||||||
href: `/?q=${encodeURIComponent(author)}`,
|
<PageHero.EditLink
|
||||||
},
|
href={`https://notes.max-richter.dev/Media/series/${serie.id}`}
|
||||||
date.toString(),
|
/>
|
||||||
]}
|
)}
|
||||||
editLink={session
|
</PageHero.Header>
|
||||||
? `https://notes.max-richter.dev/Media/series/${serie.id}`
|
<PageHero.Footer>
|
||||||
: ""}
|
<PageHero.Title>{serie.name}</PageHero.Title>
|
||||||
backlink="/series"
|
<PageHero.Subline
|
||||||
/>
|
entries={[
|
||||||
|
author && {
|
||||||
|
title: author,
|
||||||
|
href: `/?q=${encodeURIComponent(author)}`,
|
||||||
|
},
|
||||||
|
date.toString(),
|
||||||
|
]}
|
||||||
|
>
|
||||||
|
{serie.meta.rating && <Star rating={serie.meta.rating} />}
|
||||||
|
</PageHero.Subline>
|
||||||
|
</PageHero.Footer>
|
||||||
|
</PageHero>
|
||||||
{serie.tags.length > 0 && (
|
{serie.tags.length > 0 && (
|
||||||
<>
|
<>
|
||||||
<br />
|
<br />
|
||||||
|
Loading…
x
Reference in New Issue
Block a user