99 lines
3.1 KiB
TypeScript
99 lines
3.1 KiB
TypeScript
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."
|
|
}
|
|
</>;
|
|
}
|
|
});
|
|
|