big style update
This commit is contained in:
98
app/blog/DjBlogEntry.tsx
Normal file
98
app/blog/DjBlogEntry.tsx
Normal file
@@ -0,0 +1,98 @@
|
||||
import { computed, createTextVNode, defineComponent, h, type VNode } from "vue";
|
||||
import useHead from "@/useHead.ts";
|
||||
import useAsyncState from "@/useAsyncState.ts";
|
||||
import getDjAPI from "@/api.ts";
|
||||
import getDomParser from "@/domParse.ts";
|
||||
import { addCSS, css } from "../util.ts";
|
||||
|
||||
const style = css`
|
||||
.byline {
|
||||
font-style: italic;
|
||||
color: gray;
|
||||
}
|
||||
|
||||
h1 {
|
||||
color: var(--dj-palette1);
|
||||
}
|
||||
|
||||
p {
|
||||
margin-bottom: 30px;
|
||||
}
|
||||
`;
|
||||
|
||||
export default defineComponent({
|
||||
name: "DjBlogEntry",
|
||||
props: {
|
||||
slug: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
async setup(props) {
|
||||
addCSS('DjBlogEntry', style);
|
||||
|
||||
const parseDom = getDomParser();
|
||||
|
||||
const blogpostContent = useAsyncState(
|
||||
`dj-blog-article-content-${ props.slug }`,
|
||||
async ({ hostUrl }) => {
|
||||
const blogpostResponse = await fetch(`${hostUrl}/blog/content/${ props.slug }.html`);
|
||||
const result = await blogpostResponse.text();
|
||||
return result;
|
||||
},
|
||||
);
|
||||
|
||||
const blogpostsMetadata = useAsyncState('article-metadata', ({ hostUrl }) => getDjAPI(hostUrl, '/blog-entries'));
|
||||
|
||||
const blogpostMetadata = computed(() => blogpostsMetadata.result.value?.find(_ => _.slug === props.slug));
|
||||
|
||||
useHead({
|
||||
title: () => blogpostMetadata.value?.title ?? '',
|
||||
metatags: () => blogpostMetadata.value ? [
|
||||
{ name: 'title', content: blogpostMetadata.value.title },
|
||||
{ name: 'author', content: 'Daniel Ledda' },
|
||||
] : [],
|
||||
});
|
||||
|
||||
function transformPostNode(node: Node): VNode | string {
|
||||
if (node.nodeType === node.ELEMENT_NODE) {
|
||||
const el = node as Element;
|
||||
const attrs: Record<string, string> = {};
|
||||
const children = [...node.childNodes].map((_) => transformPostNode(_));
|
||||
|
||||
for (let i = 0; i < el.attributes.length; i++) {
|
||||
const item = el.attributes.item(i);
|
||||
if (item) {
|
||||
attrs[item.name] = item.value;
|
||||
}
|
||||
}
|
||||
|
||||
return h((node as Element).tagName, attrs, children);
|
||||
} else {
|
||||
return createTextVNode(node.textContent ?? "");
|
||||
}
|
||||
}
|
||||
|
||||
function PostContentTransformed() {
|
||||
if (blogpostContent.result.value) {
|
||||
const dom = parseDom(blogpostContent.result.value);
|
||||
return h("div", {}, [...dom.children].map((_) => transformPostNode(_)));
|
||||
}
|
||||
return <div>Blog post loading...</div>;
|
||||
}
|
||||
|
||||
await Promise.allSettled([ blogpostContent.done, blogpostsMetadata.done ]);
|
||||
|
||||
return () => <>
|
||||
{ blogpostMetadata.value
|
||||
? <>
|
||||
<h1>{ blogpostMetadata.value.title }</h1>
|
||||
<div class="byline">by Daniel Ledda, first published { new Date(blogpostMetadata.value.createdAt).toLocaleDateString() }</div>
|
||||
<PostContentTransformed />
|
||||
</>
|
||||
: "Sorry, this blog post doesn't seem to exist."
|
||||
}
|
||||
</>;
|
||||
}
|
||||
});
|
||||
|
||||
42
app/blog/DjBlogMain.tsx
Normal file
42
app/blog/DjBlogMain.tsx
Normal file
@@ -0,0 +1,42 @@
|
||||
import { defineComponent } from "vue";
|
||||
import useAsyncState from "@/useAsyncState.ts";
|
||||
import getDjAPI from "@/api.ts";
|
||||
import { RouterLink } from "vue-router";
|
||||
import { addCSS, css } from "@/util.ts";
|
||||
|
||||
const style = css`
|
||||
.entry {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
gap: 4px;
|
||||
}
|
||||
`;
|
||||
|
||||
export default defineComponent({
|
||||
name: "DjBlogMain",
|
||||
async setup() {
|
||||
addCSS('DjBlogMain', style);
|
||||
|
||||
const blogEntries = useAsyncState('blog-entries-meta', ({ hostUrl }) => getDjAPI(hostUrl, "/blog-entries"));
|
||||
|
||||
await blogEntries.done;
|
||||
|
||||
return () => <>
|
||||
<main>
|
||||
<h2>Entries</h2>
|
||||
<ul>
|
||||
{blogEntries.result.value?.map(_ => (
|
||||
<li key={_.slug}>
|
||||
<div class="entry">
|
||||
<RouterLink to={{ name: 'DjBlogEntry', params: { slug: _.slug }}}>{ _.title }</RouterLink>
|
||||
<span>-</span>
|
||||
<time datetime={ _.createdAt }>{ new Date(_.createdAt).toLocaleDateString() }</time>
|
||||
</div>
|
||||
</li>
|
||||
)) ?? <li>Blog posts loading...</li>}
|
||||
</ul>
|
||||
</main>
|
||||
</>;
|
||||
},
|
||||
});
|
||||
|
||||
@@ -1,7 +1,31 @@
|
||||
import { defineComponent, ref } from "vue";
|
||||
import useHead from "@/useHead.ts";
|
||||
import { defineComponent, ref, type VNode, Suspense } from "vue";
|
||||
import { type RouteRecordRaw, RouterLink, RouterView } from "vue-router";
|
||||
import DjTooltip, { setupTooltip } from "@/DjTooltip.tsx";
|
||||
import DjBlogEntry from "@/blog/DjBlogEntry.tsx";
|
||||
import DjBlogMain from "@/blog/DjBlogMain.tsx";
|
||||
import DjEmail from "@/DjEmail.tsx";
|
||||
import { addCSS, css } from "@/util.ts";
|
||||
import useHead from "@/useHead.ts";
|
||||
|
||||
export const routes: RouteRecordRaw[] = [
|
||||
{
|
||||
path: "/",
|
||||
name: "DjBlogMain",
|
||||
component: DjBlogMain,
|
||||
},
|
||||
{
|
||||
path: "/post/:slug",
|
||||
name: "DjBlogEntry",
|
||||
component: DjBlogEntry,
|
||||
props: ({ params }) => {
|
||||
if ("slug" in params) {
|
||||
return { slug: params.slug };
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
const styles = css`
|
||||
.supercontainer {
|
||||
@@ -9,31 +33,85 @@ const styles = css`
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
margin-bottom: 100px;
|
||||
}
|
||||
|
||||
.container {
|
||||
width: 800px;
|
||||
}
|
||||
|
||||
footer {
|
||||
color: gray;
|
||||
font-style: italic;
|
||||
margin-top: 40px;
|
||||
}
|
||||
|
||||
a {
|
||||
color: var(--dj-palette3);
|
||||
text-decoration: solid line;
|
||||
|
||||
&:visited {
|
||||
color: var(--dj-visited);
|
||||
}
|
||||
}
|
||||
|
||||
nav {
|
||||
font-size: 40px;
|
||||
margin-bottom: 40px;
|
||||
text-decoration: none;
|
||||
text-align: right;
|
||||
width: 100%;
|
||||
|
||||
a, a:visited {
|
||||
color: var(--dj-palette3);
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
export default defineComponent({
|
||||
name: "app-root",
|
||||
name: "DjBlogRoot",
|
||||
setup() {
|
||||
addCSS('dj-blog-root', styles);
|
||||
const carrier = ref<HTMLDivElement | null>(null);
|
||||
setupTooltip({ carrier });
|
||||
|
||||
addCSS('dj-blog-root', styles);
|
||||
useHead({ title: "djblog Home" });
|
||||
|
||||
const tooltipCarrier = ref<HTMLDivElement | null>(null);
|
||||
setupTooltip({ carrier: tooltipCarrier });
|
||||
|
||||
return () => <>
|
||||
<div ref={tooltipCarrier} class="tooltip-carrier" />
|
||||
<div class="supercontainer">
|
||||
<div class="container">
|
||||
<DjTooltip tooltip="come in and find out...">
|
||||
<h1>dj blog</h1>
|
||||
</DjTooltip>
|
||||
return () => (
|
||||
<>
|
||||
<div ref={carrier} class="tooltip-carrier" />
|
||||
<div class="supercontainer">
|
||||
<div class="container">
|
||||
<nav>
|
||||
<DjTooltip tooltip="flog, clog, bog, frog, cog, log, grog, fog, snog...">
|
||||
<RouterLink to={{ name: 'DjBlogMain' }}>
|
||||
dj blog
|
||||
</RouterLink>
|
||||
</DjTooltip>
|
||||
</nav>
|
||||
<RouterView>
|
||||
{{
|
||||
default: ({ Component }: { Component: VNode }) => (Component &&
|
||||
(
|
||||
<Suspense>
|
||||
{{
|
||||
default: () => Component,
|
||||
fallback: () => <div>Page loading...</div>,
|
||||
}}
|
||||
</Suspense>
|
||||
)),
|
||||
}}
|
||||
</RouterView>
|
||||
<footer>
|
||||
<div class="bottom">
|
||||
<div>
|
||||
<a href="/">djledda.net</a> {new Date().getFullYear()} - <DjEmail>{() => "Contact"}</DjEmail>
|
||||
</div>
|
||||
</div>
|
||||
</footer>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</>;
|
||||
</>
|
||||
);
|
||||
},
|
||||
});
|
||||
|
||||
@@ -1,7 +1,12 @@
|
||||
import { createSSRApp } from "vue";
|
||||
import DjBlogRoot from "@/blog//DjBlogRoot.tsx";
|
||||
import { createRouter, createWebHistory } from "vue-router";
|
||||
import DjBlogRoot, { routes } from "@/blog//DjBlogRoot.tsx";
|
||||
import { cssRegistry } from "@/util.ts";
|
||||
|
||||
createSSRApp(DjBlogRoot)
|
||||
.provide(cssRegistry, new Set())
|
||||
.use(createRouter({
|
||||
routes,
|
||||
history: createWebHistory("/blog"),
|
||||
}))
|
||||
.mount("#app-root");
|
||||
|
||||
@@ -1,7 +1,12 @@
|
||||
import { createSSRApp } from "vue";
|
||||
import DjBlogRoot from "@/blog/DjBlogRoot.tsx";
|
||||
import DjBlogRoot, { routes } from "@/blog/DjBlogRoot.tsx";
|
||||
import { createMemoryHistory, createRouter } from "vue-router";
|
||||
|
||||
export default function createApp() {
|
||||
const app = createSSRApp(DjBlogRoot);
|
||||
return { app, router: null };
|
||||
const router = createRouter({
|
||||
routes: routes,
|
||||
history: createMemoryHistory("/blog"),
|
||||
});
|
||||
const app = createSSRApp(DjBlogRoot).use(router);
|
||||
return { app, router };
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user