update
This commit is contained in:
@@ -1,5 +1,5 @@
|
|||||||
import { nextTick, inject, provide, watch, type InjectionKey, onBeforeUnmount, watchEffect, onMounted, type Ref, type CSSProperties, defineComponent, ref } from "vue";
|
import { nextTick, inject, provide, watch, type InjectionKey, onBeforeUnmount, watchEffect, onMounted, type Ref, type CSSProperties, defineComponent, ref } from "vue";
|
||||||
import { h as djh } from "@/util.ts";
|
import { addCSS, css, h as djh } from "@/util.ts";
|
||||||
|
|
||||||
type TooltipContext = {
|
type TooltipContext = {
|
||||||
show: (newText: string, x: number, y: number) => void,
|
show: (newText: string, x: number, y: number) => void,
|
||||||
@@ -8,9 +8,42 @@ type TooltipContext = {
|
|||||||
|
|
||||||
const tooltipContext = Symbol('tooltip') as InjectionKey<TooltipContext>;
|
const tooltipContext = Symbol('tooltip') as InjectionKey<TooltipContext>;
|
||||||
|
|
||||||
|
const tooltipStyles = css`
|
||||||
|
.tooltip-container {
|
||||||
|
display: inline-block;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tooltip-carrier {
|
||||||
|
opacity: 0;
|
||||||
|
display: block;
|
||||||
|
pointer-events: none;
|
||||||
|
background-color: black;
|
||||||
|
border: white solid 1px;
|
||||||
|
color: white;
|
||||||
|
padding: 10px;
|
||||||
|
position: absolute;
|
||||||
|
z-index: 1;
|
||||||
|
overflow: hidden;
|
||||||
|
height: 0;
|
||||||
|
width: 0;
|
||||||
|
position: absolute;
|
||||||
|
transition: opacity 200ms, height 200ms, width 200ms;
|
||||||
|
|
||||||
|
.text-carrier {
|
||||||
|
position: absolute;
|
||||||
|
width: 350px;
|
||||||
|
font-size: 16px;
|
||||||
|
font-family: "Roboto", serif;
|
||||||
|
display: block;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
}`;
|
||||||
|
|
||||||
export function setupTooltip(options: { carrier: Ref<HTMLElement | null> }) {
|
export function setupTooltip(options: { carrier: Ref<HTMLElement | null> }) {
|
||||||
const { carrier } = options;
|
const { carrier } = options;
|
||||||
|
|
||||||
|
addCSS('tooltip-carrier', tooltipStyles);
|
||||||
|
|
||||||
watchEffect(() => {
|
watchEffect(() => {
|
||||||
if (carrier.value) {
|
if (carrier.value) {
|
||||||
carrier.value.classList.add('tooltip-carrier');
|
carrier.value.classList.add('tooltip-carrier');
|
||||||
@@ -82,6 +115,8 @@ export default defineComponent({
|
|||||||
setup(props, { slots, attrs }) {
|
setup(props, { slots, attrs }) {
|
||||||
const tooltip = inject(tooltipContext, () => { throw new Error('No tooltip context'); }, true);
|
const tooltip = inject(tooltipContext, () => { throw new Error('No tooltip context'); }, true);
|
||||||
|
|
||||||
|
onBeforeUnmount(() => tooltip.hide());
|
||||||
|
|
||||||
return () => <>
|
return () => <>
|
||||||
<div class="tooltip-container"
|
<div class="tooltip-container"
|
||||||
{...attrs}
|
{...attrs}
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
export type DJAPIEndpoint = "/rp-articles";
|
export type DJAPIEndpoint = "/rp-articles";
|
||||||
|
|
||||||
export interface DJAPIResultMap extends Record<DJAPIEndpoint, unknown> {
|
export interface DJAPIResultMap extends Record<DJAPIEndpoint, unknown> {
|
||||||
"/rp-articles": { slug: string; name: string }[];
|
"/rp-articles": { slug: string; title: string, tags?: string[] }[];
|
||||||
}
|
}
|
||||||
|
|
||||||
export type DJAPIResult = DJAPIResultMap[DJAPIEndpoint];
|
export type DJAPIResult = DJAPIResultMap[DJAPIEndpoint];
|
||||||
|
|||||||
1
app/devtools-shim.ts
Normal file
1
app/devtools-shim.ts
Normal file
@@ -0,0 +1 @@
|
|||||||
|
export function setupDevtoolsPlugin() {}
|
||||||
@@ -17,17 +17,17 @@ export default defineComponent({
|
|||||||
step: 0.1,
|
step: 0.1,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "Armour, Natural Dessicated Thyroid",
|
name: 'Natural Dessicated Thyroid ("Armour")',
|
||||||
mpg: 60,
|
mpg: 60,
|
||||||
unit: "mg",
|
unit: "mg",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'Liothyronine (Triiodothyronine, "Cytomel/Cynomel", T3)',
|
name: 'Liothyronine (T3 - "Cytomel/Cynomel")',
|
||||||
mpg: MPG_T3_SYN,
|
mpg: MPG_T3_SYN,
|
||||||
unit: "mcg",
|
unit: "mcg",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "Levothyroxine (Thyroxine, T4)",
|
name: "Levothyroxine (T4)",
|
||||||
mpg: MPG_T4_SYN,
|
mpg: MPG_T4_SYN,
|
||||||
unit: "mcg",
|
unit: "mcg",
|
||||||
},
|
},
|
||||||
@@ -67,7 +67,7 @@ export default defineComponent({
|
|||||||
{inputDefs.map((_) => (
|
{inputDefs.map((_) => (
|
||||||
<tr key={_.name}>
|
<tr key={_.name}>
|
||||||
<td>
|
<td>
|
||||||
{_.name}
|
{_.name}{_.unit && (', ' + _.unit)}
|
||||||
</td>
|
</td>
|
||||||
<td class="right">
|
<td class="right">
|
||||||
<div style="display: inline-block;">
|
<div style="display: inline-block;">
|
||||||
@@ -86,20 +86,19 @@ export default defineComponent({
|
|||||||
type="number"
|
type="number"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<span class="breathe">{_.unit}</span>
|
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
))}
|
))}
|
||||||
<tr>
|
<tr>
|
||||||
<td colspan="2">
|
<td colspan="2">
|
||||||
<strong>Compounded (T3 and T4, "Cynoplus")</strong>
|
<strong>Compounded (T3 and T4 - "Cynoplus")</strong>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr class="ratios">
|
<tr class="ratios">
|
||||||
<td>
|
<td>
|
||||||
Desired Ratio (T3:T4)
|
Desired Ratio (T3:T4)
|
||||||
</td>
|
</td>
|
||||||
<td class="right">
|
<td class="right ratio">
|
||||||
<div>
|
<div>
|
||||||
<input
|
<input
|
||||||
value={t3Ratio.value}
|
value={t3Ratio.value}
|
||||||
@@ -110,8 +109,8 @@ export default defineComponent({
|
|||||||
step="1"
|
step="1"
|
||||||
type="number"
|
type="number"
|
||||||
/>
|
/>
|
||||||
</div>{" "}
|
</div>
|
||||||
:{" "}
|
<span class="separator"/>
|
||||||
<div>
|
<div>
|
||||||
<input
|
<input
|
||||||
value={t4Ratio.value}
|
value={t4Ratio.value}
|
||||||
@@ -127,9 +126,9 @@ export default defineComponent({
|
|||||||
</tr>
|
</tr>
|
||||||
<tr class="synthetic">
|
<tr class="synthetic">
|
||||||
<td>
|
<td>
|
||||||
Synthetic T3/T4 Combo
|
T3 mcg : T4 mcg
|
||||||
</td>
|
</td>
|
||||||
<td class="right">
|
<td class="right ratio">
|
||||||
<div>
|
<div>
|
||||||
<input
|
<input
|
||||||
value={compounded.value.t3Syn}
|
value={compounded.value.t3Syn}
|
||||||
@@ -143,8 +142,8 @@ export default defineComponent({
|
|||||||
step="1"
|
step="1"
|
||||||
type="number"
|
type="number"
|
||||||
/>
|
/>
|
||||||
</div>{" "}
|
</div>
|
||||||
:{" "}
|
<span class="separator"/>
|
||||||
<div>
|
<div>
|
||||||
<input
|
<input
|
||||||
value={compounded.value.t4Syn}
|
value={compounded.value.t4Syn}
|
||||||
@@ -169,7 +168,7 @@ export default defineComponent({
|
|||||||
<p>
|
<p>
|
||||||
<ul>
|
<ul>
|
||||||
<li>
|
<li>
|
||||||
1st November 2024: Migrated to new web framework and fixed some buggy input.
|
1st November 2024: Migrated to new web framework and fixed some buggy input/ugly styling.
|
||||||
</li>
|
</li>
|
||||||
<li>
|
<li>
|
||||||
13th March 2024: Removed the synthetic/pure distinction as it was confusing and
|
13th March 2024: Removed the synthetic/pure distinction as it was confusing and
|
||||||
|
|||||||
@@ -64,8 +64,9 @@ export default defineComponent({
|
|||||||
{rpArticles.value && rpArticles.value.map((_) => (
|
{rpArticles.value && rpArticles.value.map((_) => (
|
||||||
<li>
|
<li>
|
||||||
<RouterLink to={{ name: "GEDeutschArticle", params: { articleName: _.slug } }}>
|
<RouterLink to={{ name: "GEDeutschArticle", params: { articleName: _.slug } }}>
|
||||||
{_.name}
|
{_.title}
|
||||||
</RouterLink>
|
</RouterLink>
|
||||||
|
{_.tags?.map(tag => <span class="tag">{tag}</span>)}
|
||||||
</li>
|
</li>
|
||||||
))}
|
))}
|
||||||
</ul>
|
</ul>
|
||||||
|
|||||||
@@ -1,7 +1,9 @@
|
|||||||
import { createTextVNode, defineComponent, h, inject, onServerPrefetch, ref, type VNode, watchEffect } from "vue";
|
import { createTextVNode, computed, defineComponent, h, inject, onServerPrefetch, ref, type VNode, watchEffect } from "vue";
|
||||||
import { RouterLink } from "vue-router";
|
import { RouterLink } from "vue-router";
|
||||||
import useAsyncState from "@/useAsyncState.ts";
|
import useAsyncState from "@/useAsyncState.ts";
|
||||||
import useHead from "@/useHead.ts";
|
import useHead from "@/useHead.ts";
|
||||||
|
import DJEmail from "@/DJEmail.tsx";
|
||||||
|
import getDJAPI from "@/api.ts";
|
||||||
|
|
||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
name: "ge-deutsch-article",
|
name: "ge-deutsch-article",
|
||||||
@@ -23,25 +25,32 @@ export default defineComponent({
|
|||||||
(innerHTML: string) => Object.assign(document.createElement("div"), { innerHTML }),
|
(innerHTML: string) => Object.assign(document.createElement("div"), { innerHTML }),
|
||||||
);
|
);
|
||||||
|
|
||||||
const title = ref("");
|
|
||||||
|
|
||||||
const { result: articleContent, stateIsReady } = useAsyncState(
|
const { result: articleContent, stateIsReady } = useAsyncState(
|
||||||
"ge-deutsch-article-data",
|
"ge-deutsch-article-data",
|
||||||
async ({ hostUrl }) => {
|
async ({ hostUrl }) => {
|
||||||
const articleResponse = await fetch(`${hostUrl}/generative-energy/content/${props.articleName}.html`);
|
const articleResponse = await fetch(`${hostUrl}/generative-energy/content/${props.articleName}.html`);
|
||||||
const result = await articleResponse.text();
|
const result = await articleResponse.text();
|
||||||
title.value = result.split('<h1 lang="de">')[1].split("</h1>")[0];
|
|
||||||
return result;
|
return result;
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const {
|
||||||
|
result: articleData,
|
||||||
|
stateIsReady: articleDataReady,
|
||||||
|
} = useAsyncState('article-data', ({hostUrl}) => getDJAPI(hostUrl, '/rp-articles'));
|
||||||
|
|
||||||
|
const articleMetadata = computed(() => articleData.value?.find(_ => _.slug === props.articleName));
|
||||||
|
|
||||||
|
const title = computed(() => {
|
||||||
|
return articleContent.value?.split('<h1 lang="de">')[1].split("</h1>")[0] ?? 'Artikel';
|
||||||
|
});
|
||||||
|
|
||||||
useHead({ title });
|
useHead({ title });
|
||||||
|
|
||||||
onServerPrefetch(() =>
|
onServerPrefetch(() =>
|
||||||
new Promise<void>((res) => {
|
new Promise<void>((res) => {
|
||||||
watchEffect(() => {
|
watchEffect(() => {
|
||||||
if (title.value !== "") {
|
if (title.value !== "") {
|
||||||
console.log("resolve", title.value);
|
|
||||||
res();
|
res();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -59,7 +68,7 @@ export default defineComponent({
|
|||||||
children.unshift(h("button", {
|
children.unshift(h("button", {
|
||||||
class: "swap",
|
class: "swap",
|
||||||
onClick: (e) => {
|
onClick: (e) => {
|
||||||
e.target.parentElement.classList.toggle("swap");
|
(e.target as HTMLButtonElement).parentElement?.classList.toggle("swap");
|
||||||
},
|
},
|
||||||
}, "↻"));
|
}, "↻"));
|
||||||
}
|
}
|
||||||
@@ -71,7 +80,11 @@ export default defineComponent({
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return h((node as Element).tagName, attrs, children);
|
if (el.tagName === "H1") {
|
||||||
|
return h("header", attrs, h("h1", {}, children));
|
||||||
|
} else {
|
||||||
|
return h((node as Element).tagName, attrs, children);
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
return createTextVNode(node.textContent ?? "");
|
return createTextVNode(node.textContent ?? "");
|
||||||
}
|
}
|
||||||
@@ -85,7 +98,8 @@ export default defineComponent({
|
|||||||
return <div>Artikel lädt...</div>;
|
return <div>Artikel lädt...</div>;
|
||||||
}
|
}
|
||||||
|
|
||||||
await stateIsReady;
|
await Promise.all([stateIsReady, articleDataReady]);
|
||||||
|
console.log(articleMetadata.value);
|
||||||
|
|
||||||
return () => (
|
return () => (
|
||||||
<div class="ge-article">
|
<div class="ge-article">
|
||||||
@@ -95,6 +109,12 @@ export default defineComponent({
|
|||||||
Sprache auf <span>{currentLang.value === "en" ? "Deutsch" : "Englisch"}</span> umschalten
|
Sprache auf <span>{currentLang.value === "en" ? "Deutsch" : "Englisch"}</span> umschalten
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
<p class="text-slab">
|
||||||
|
Bei dem untenstehenden Artikel handelt es sich um eine hobbymäßige, amateurhafte Übersetzung des
|
||||||
|
Artikels „{ title.value }“ von Ray Peat. Bei Ungenauigkeiten oder Fehlübersetzungen freue ich mich über <DJEmail>eine Mail</DJEmail>!
|
||||||
|
</p>
|
||||||
|
{ articleMetadata.value?.tags?.includes('in-arbeit') && <h5 class="baustelle">🚧 Bitte beachte, dass diese Übersetzung noch in Arbeit und darum nicht fertig ist! 🚧</h5> }
|
||||||
|
<hr />
|
||||||
<article class={`lang-${currentLang.value}`}>
|
<article class={`lang-${currentLang.value}`}>
|
||||||
<ArticleContentTransformed />
|
<ArticleContentTransformed />
|
||||||
</article>
|
</article>
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import { RouterLink } from "vue-router";
|
import { RouterLink } from "vue-router";
|
||||||
import useHead from "@/useHead.ts";
|
import useHead from "@/useHead.ts";
|
||||||
|
import DJTooltip from "@/DJTooltip.tsx";
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: "ge-main",
|
name: "ge-main",
|
||||||
@@ -20,16 +21,14 @@ export default {
|
|||||||
<h2>Links</h2>
|
<h2>Links</h2>
|
||||||
<ul>
|
<ul>
|
||||||
<li>
|
<li>
|
||||||
<RouterLink to={{ name: "GECalculator" }}>Thyroid Calculator</RouterLink>
|
<DJTooltip tooltip="Convert to and from grains, set ratios, etc.">
|
||||||
<span style="display: none" class="tooltip">
|
<RouterLink to={{ name: "GECalculator" }}>Thyroid Calculator</RouterLink>
|
||||||
Convert to and from grains, set ratios, etc.
|
</DJTooltip>
|
||||||
</span>
|
|
||||||
</li>
|
</li>
|
||||||
<li>
|
<li>
|
||||||
<RouterLink to={{ name: "GEDeutsch" }}>Ray Peat Articles in German</RouterLink>
|
<DJTooltip tooltip="A selection of articles by Ray that I have translated in my spare time into German.">
|
||||||
<span style="display: none" class="tooltip">
|
<RouterLink to={{ name: "GEDeutsch" }}>Ray Peat Articles in German</RouterLink>
|
||||||
A selection of articles by Ray that I have translated in my spare time into German.
|
</DJTooltip>
|
||||||
</span>
|
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { defineComponent, Suspense, type VNode } from "vue";
|
import { defineComponent, ref, Suspense, type VNode } from "vue";
|
||||||
import { type RouteRecordRaw, RouterLink, RouterView, useRoute } from "vue-router";
|
import { type RouteRecordRaw, RouterLink, RouterView, useRoute } from "vue-router";
|
||||||
import GEMain from "@/generative-energy/GEMain.tsx";
|
import GEMain from "@/generative-energy/GEMain.tsx";
|
||||||
import DJEmail from "@/DJEmail.tsx";
|
import DJEmail from "@/DJEmail.tsx";
|
||||||
@@ -6,6 +6,7 @@ import GEDeutsch from "@/generative-energy/GEDeutsch.tsx";
|
|||||||
import GEDeutschArticle from "@/generative-energy/GEDeutschArticle.tsx";
|
import GEDeutschArticle from "@/generative-energy/GEDeutschArticle.tsx";
|
||||||
import GECalculator from "@/generative-energy/GECalculator.tsx";
|
import GECalculator from "@/generative-energy/GECalculator.tsx";
|
||||||
import DJDonate from "@/DJDonate.tsx";
|
import DJDonate from "@/DJDonate.tsx";
|
||||||
|
import { setupTooltip } from "@/DJTooltip.tsx";
|
||||||
|
|
||||||
export const routes: RouteRecordRaw[] = [
|
export const routes: RouteRecordRaw[] = [
|
||||||
{
|
{
|
||||||
@@ -41,8 +42,11 @@ export default defineComponent({
|
|||||||
name: "ge-root",
|
name: "ge-root",
|
||||||
setup() {
|
setup() {
|
||||||
const route = useRoute();
|
const route = useRoute();
|
||||||
|
const carrier = ref<HTMLDivElement | null>(null);
|
||||||
|
setupTooltip({ carrier });
|
||||||
return () => (
|
return () => (
|
||||||
<>
|
<>
|
||||||
|
<div ref={carrier} class="tooltip-carrier" />
|
||||||
<main>
|
<main>
|
||||||
<RouterLink class={"home-btn" + (route.name === "GEMain" ? " hide" : "")} to={{ name: "GEMain" }}>
|
<RouterLink class={"home-btn" + (route.name === "GEMain" ? " hide" : "")} to={{ name: "GEMain" }}>
|
||||||
Generative Energy Home
|
Generative Energy Home
|
||||||
|
|||||||
@@ -1,8 +1,10 @@
|
|||||||
import { createSSRApp } from "vue";
|
import { createSSRApp } from "vue";
|
||||||
import { createRouter, createWebHistory } from "vue-router";
|
import { createRouter, createWebHistory } from "vue-router";
|
||||||
import GERoot, { routes } from "@/generative-energy/GERoot.tsx";
|
import GERoot, { routes } from "@/generative-energy/GERoot.tsx";
|
||||||
|
import { cssRegistry } from "@/util.ts";
|
||||||
|
|
||||||
createSSRApp(GERoot)
|
createSSRApp(GERoot)
|
||||||
|
.provide(cssRegistry, new Set())
|
||||||
.use(createRouter({
|
.use(createRouter({
|
||||||
routes,
|
routes,
|
||||||
history: createWebHistory("/generative-energy"),
|
history: createWebHistory("/generative-energy"),
|
||||||
|
|||||||
@@ -1,4 +1,7 @@
|
|||||||
import { createSSRApp } from "vue";
|
import { createSSRApp } from "vue";
|
||||||
import DJHomeRoot from "@/home/DJHomeRoot.tsx";
|
import DJHomeRoot from "@/home/DJHomeRoot.tsx";
|
||||||
|
import { cssRegistry } from "@/util.ts";
|
||||||
|
|
||||||
createSSRApp(DJHomeRoot).mount("#app-root");
|
createSSRApp(DJHomeRoot)
|
||||||
|
.provide(cssRegistry, new Set())
|
||||||
|
.mount("#app-root");
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ export type DJSSRContext = {
|
|||||||
title: MaybeRefOrGetter<string>;
|
title: MaybeRefOrGetter<string>;
|
||||||
};
|
};
|
||||||
registry: Record<string, unknown>;
|
registry: Record<string, unknown>;
|
||||||
|
styles: Record<string, string>;
|
||||||
};
|
};
|
||||||
|
|
||||||
export default function useDJSSRContext() {
|
export default function useDJSSRContext() {
|
||||||
|
|||||||
23
app/util.ts
23
app/util.ts
@@ -1,3 +1,6 @@
|
|||||||
|
import { type InjectionKey, inject } from 'vue';
|
||||||
|
import useDJSSRContext from "@/useDJSSRContext.ts";
|
||||||
|
|
||||||
export function gid(id: string, doc: (Document | ShadowRoot) | undefined) {
|
export function gid(id: string, doc: (Document | ShadowRoot) | undefined) {
|
||||||
return ((doc ?? document).getElementById(id));
|
return ((doc ?? document).getElementById(id));
|
||||||
}
|
}
|
||||||
@@ -37,9 +40,23 @@ export function css(strs: TemplateStringsArray, ...vals: string[]) {
|
|||||||
for (let i = 1; i < strs.length; i++) {
|
for (let i = 1; i < strs.length; i++) {
|
||||||
result += vals[i] + strs[i];
|
result += vals[i] + strs[i];
|
||||||
}
|
}
|
||||||
const sheet = new CSSStyleSheet();
|
return result;
|
||||||
sheet.replaceSync(result);
|
}
|
||||||
return sheet;
|
|
||||||
|
|
||||||
|
export const cssRegistry = Symbol('css-registry') as InjectionKey<Set<string>>;
|
||||||
|
export function addCSS(key: string, css: string) {
|
||||||
|
const context = useDJSSRContext();
|
||||||
|
if (context && !context.styles[key]) {
|
||||||
|
context.styles[key] = css;
|
||||||
|
} else {
|
||||||
|
const registry = inject(cssRegistry);
|
||||||
|
if (!registry?.has(key)) {
|
||||||
|
const stylesheet = new CSSStyleSheet();
|
||||||
|
stylesheet.replace(css);
|
||||||
|
document.adoptedStyleSheets.push(stylesheet);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
|||||||
71
main.ts
71
main.ts
@@ -13,20 +13,23 @@ const utf8Decoder = new TextDecoder("utf-8");
|
|||||||
|
|
||||||
const parser = new DOMParser();
|
const parser = new DOMParser();
|
||||||
|
|
||||||
function appHeaderScript(params: { appstate: Record<string, unknown>; entryPath: string }) {
|
function appHeaderScript(params: { ssrContext: DJSSRContext, entryPath: string }) {
|
||||||
return `<script type="importmap">
|
return `<script type="importmap">
|
||||||
{
|
{
|
||||||
"imports": {
|
"imports": {
|
||||||
"vue": "/deps/vue/dist/vue.esm-browser.prod.js",
|
"vue": "/deps/vue/dist/vue.esm-browser.prod.js",
|
||||||
"vue-router": "/deps/vue-router/dist/vue-router.esm-browser.js",
|
"vue-router": "/deps/vue-router/dist/vue-router.esm-browser.js",
|
||||||
"vue/jsx-runtime": "/deps/vue/jsx-runtime/index.mjs",
|
"vue/jsx-runtime": "/deps/vue/jsx-runtime/index.mjs",
|
||||||
"@vue/devtools-api": "/deps/@vue/devtools-api/lib/esm/index.js",
|
"@vue/devtools-api": "/app/devtools-shim.ts",
|
||||||
"@/": "/app/"
|
"@/": "/app/"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
<style>
|
||||||
|
${ Object.values(params.ssrContext.styles).join('\n') }
|
||||||
|
</style>
|
||||||
<script type="module">
|
<script type="module">
|
||||||
window.appstate = ${JSON.stringify(params.appstate)};
|
window.appstate = ${JSON.stringify(params.ssrContext.registry)};
|
||||||
import('${params.entryPath}');
|
import('${params.entryPath}');
|
||||||
</script>`;
|
</script>`;
|
||||||
}
|
}
|
||||||
@@ -47,7 +50,7 @@ for await (const path of publicFiles) {
|
|||||||
sites.push(path);
|
sites.push(path);
|
||||||
}
|
}
|
||||||
|
|
||||||
function getAPIResponse(apiReq: Request): Response {
|
async function getAPIResponse(apiReq: Request): Promise<Response> {
|
||||||
let jsonResponse: DJAPIResult | { error: string } | null = null;
|
let jsonResponse: DJAPIResult | { error: string } | null = null;
|
||||||
let status = 200;
|
let status = 200;
|
||||||
|
|
||||||
@@ -62,22 +65,42 @@ function getAPIResponse(apiReq: Request): Response {
|
|||||||
const apiPath = pathname.split("/api")[1];
|
const apiPath = pathname.split("/api")[1];
|
||||||
|
|
||||||
if (apiPath === "/rp-articles") {
|
if (apiPath === "/rp-articles") {
|
||||||
jsonResponse = [
|
const paths: string[] = [];
|
||||||
{ name: "Koffein: ein vitamin-ähnlicher Nährstoff, oder Adaptogen", slug: "caffeine" },
|
const contentDir = './public/generative-energy/content/';
|
||||||
{
|
for await (const dirEnt of Deno.readDir(contentDir)) {
|
||||||
name: "TSH, Temperatur, Puls, und andere Indikatoren bei einer Schilddrüsenunterfunktion",
|
if (dirEnt.isFile && dirEnt.name.endsWith('.html')) {
|
||||||
slug: "hypothyroidism",
|
paths.push(`${contentDir}${dirEnt.name}`);
|
||||||
},
|
}
|
||||||
] satisfies DJAPIResultMap["/rp-articles"];
|
}
|
||||||
}
|
const result: DJAPIResultMap['/rp-articles'] = [];
|
||||||
|
for (const filePath of paths) {
|
||||||
|
const content = await Deno.readTextFile(filePath);
|
||||||
|
const dom = parser.parseFromString(content, 'text/html');
|
||||||
|
const metadata = { title: '', tags: [] as string[], slug: '' };
|
||||||
|
const metaTags = dom.querySelectorAll('meta') as unknown as NodeListOf<HTMLMetaElement>;
|
||||||
|
for (const metaTag of metaTags) {
|
||||||
|
const name = metaTag.attributes.getNamedItem('name')?.value ?? '';
|
||||||
|
const content = metaTag.attributes.getNamedItem('content')?.value ?? '';
|
||||||
|
if (name === 'title') {
|
||||||
|
metadata.title = content;
|
||||||
|
} else if (name === 'tags') {
|
||||||
|
metadata.tags = content ? content.split(",") : [];
|
||||||
|
} else if (name === 'slug') {
|
||||||
|
metadata.slug = content;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
result.push(metadata);
|
||||||
|
}
|
||||||
|
jsonResponse = result;
|
||||||
|
console.log(result);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (!jsonResponse) {
|
if (!jsonResponse) {
|
||||||
jsonResponse = { error: `API route ${ apiPath } not found.` };
|
jsonResponse = { error: `API route ${ apiPath } not found.` };
|
||||||
status = 404;
|
status = 404;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
const headers = new Headers();
|
const headers = new Headers();
|
||||||
headers.set("Content-Type", "application/json");
|
headers.set("Content-Type", "application/json");
|
||||||
return new Response(JSON.stringify(jsonResponse), {
|
return new Response(JSON.stringify(jsonResponse), {
|
||||||
@@ -93,13 +116,17 @@ Deno.serve({
|
|||||||
console.log(`Listening on port http://${hostname}:${port}/`);
|
console.log(`Listening on port http://${hostname}:${port}/`);
|
||||||
},
|
},
|
||||||
}, async (req, _conn) => {
|
}, async (req, _conn) => {
|
||||||
|
const timeStart = new Date().getTime();
|
||||||
|
|
||||||
let response: Response | null = null;
|
let response: Response | null = null;
|
||||||
|
|
||||||
|
const url = URL.parse(req.url);
|
||||||
|
|
||||||
if (req.method === "GET") {
|
if (req.method === "GET") {
|
||||||
const pathname = URL.parse(req.url)?.pathname ?? "/";
|
const pathname = url?.pathname ?? "/";
|
||||||
|
|
||||||
if (pathname.startsWith("/api/")) {
|
if (pathname.startsWith("/api/")) {
|
||||||
response = getAPIResponse(req);
|
response = await getAPIResponse(req);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Public/static files
|
// Public/static files
|
||||||
@@ -144,7 +171,7 @@ Deno.serve({
|
|||||||
app.provide("dom-parse", (innerHTML: string) => {
|
app.provide("dom-parse", (innerHTML: string) => {
|
||||||
return parser.parseFromString(innerHTML, "text/html").documentElement;
|
return parser.parseFromString(innerHTML, "text/html").documentElement;
|
||||||
});
|
});
|
||||||
const ssrContext: DJSSRContext = { registry: {}, head: { title: "" } };
|
const ssrContext: DJSSRContext = { styles: {}, registry: {}, head: { title: "" } };
|
||||||
if (router) {
|
if (router) {
|
||||||
await router.replace(pathname.split("/generative-energy")[1]);
|
await router.replace(pathname.split("/generative-energy")[1]);
|
||||||
await router.isReady();
|
await router.isReady();
|
||||||
@@ -155,7 +182,7 @@ Deno.serve({
|
|||||||
.replaceAll("%TITLE%", toValue(ssrContext.head?.title) ?? "Site")
|
.replaceAll("%TITLE%", toValue(ssrContext.head?.title) ?? "Site")
|
||||||
.replace(
|
.replace(
|
||||||
`<!-- SSR HEAD OUTLET -->`,
|
`<!-- SSR HEAD OUTLET -->`,
|
||||||
appHeaderScript({ appstate: ssrContext.registry, entryPath: clientEntry }),
|
appHeaderScript({ ssrContext, entryPath: clientEntry }),
|
||||||
);
|
);
|
||||||
response = new Response(content, { headers: { "Content-Type": "text/html" } });
|
response = new Response(content, { headers: { "Content-Type": "text/html" } });
|
||||||
}
|
}
|
||||||
@@ -164,7 +191,13 @@ Deno.serve({
|
|||||||
response = new Response("Only GET allowed.", { status: 500 });
|
response = new Response("Only GET allowed.", { status: 500 });
|
||||||
}
|
}
|
||||||
|
|
||||||
return response ?? new Response("Not found.", { status: 404 });
|
response ??= new Response("Not found.", { status: 404 })
|
||||||
|
|
||||||
|
const timeEnd = new Date().getTime();
|
||||||
|
|
||||||
|
console.log(`Request ${ url?.pathname ?? 'malformed' }\tStatus ${ response.status }, Duration ${ timeEnd - timeStart }ms`);
|
||||||
|
|
||||||
|
return response;
|
||||||
});
|
});
|
||||||
|
|
||||||
Deno.addSignalListener("SIGINT", () => {
|
Deno.addSignalListener("SIGINT", () => {
|
||||||
|
|||||||
@@ -1,3 +1,7 @@
|
|||||||
|
<meta name="slug" content="caffeine">
|
||||||
|
<meta name="title" content="Koffein: ein vitamin-ähnlicher Nährstoff, oder Adaptogen">
|
||||||
|
<meta name="tags" content="in-arbeit">
|
||||||
|
|
||||||
<h1 lang="en">Caffeine: A vitamin-like nutrient, or adaptogen</h1>
|
<h1 lang="en">Caffeine: A vitamin-like nutrient, or adaptogen</h1>
|
||||||
<h1 lang="de">Koffein: ein vitamin-ähnlicher Nährstoff, oder Adaptogen</h1>
|
<h1 lang="de">Koffein: ein vitamin-ähnlicher Nährstoff, oder Adaptogen</h1>
|
||||||
|
|
||||||
|
|||||||
@@ -1,3 +1,7 @@
|
|||||||
|
<meta name="title" content="TSH, Temperatur, Puls, und andere Indikatoren bei einer Schilddrüsenunterfunktion">
|
||||||
|
<meta name="slug" content="hypothyroidism">
|
||||||
|
<meta name="tags" content="">
|
||||||
|
|
||||||
<h1 lang="en">TSH, temperature, pulse rate, and other indicators in hypothyroidism</h1>
|
<h1 lang="en">TSH, temperature, pulse rate, and other indicators in hypothyroidism</h1>
|
||||||
<h1 lang="de">TSH, Temperatur, Puls, und andere Indikatoren bei einer Schilddrüsenunterfunktion</h1>
|
<h1 lang="de">TSH, Temperatur, Puls, und andere Indikatoren bei einer Schilddrüsenunterfunktion</h1>
|
||||||
|
|
||||||
|
|||||||
@@ -14,6 +14,11 @@ h1, h2, h3, h4, h5 {
|
|||||||
font-family: "Roboto Slab", "Segoe UI", Tahoma, Geneva, Verdana, sans-serif;
|
font-family: "Roboto Slab", "Segoe UI", Tahoma, Geneva, Verdana, sans-serif;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.baustelle {
|
||||||
|
text-align: center;
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
a.home-btn {
|
a.home-btn {
|
||||||
display: block;
|
display: block;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
@@ -23,6 +28,15 @@ a.home-btn {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
span.tag {
|
||||||
|
margin-left: 4px;
|
||||||
|
font-style: italic;
|
||||||
|
|
||||||
|
&::before {
|
||||||
|
content: '#';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.ge-article {
|
.ge-article {
|
||||||
.header {
|
.header {
|
||||||
margin-top: 20px;
|
margin-top: 20px;
|
||||||
@@ -78,28 +92,28 @@ a.home-btn {
|
|||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
.lang-en *:lang(de) {
|
.lang-en span:lang(de) {
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
.lang-de *:lang(en) {
|
.lang-de span:lang(en) {
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
main {
|
main {
|
||||||
width: 100%;
|
width: calc(100% - 20px);
|
||||||
|
margin: auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
@media (min-width: 600px) {
|
@media (min-width: 900px) {
|
||||||
main {
|
main {
|
||||||
max-width: 1340px;
|
max-width: 943px;
|
||||||
margin: auto;
|
|
||||||
min-width: 600px;
|
min-width: 600px;
|
||||||
width: 60%;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
header {
|
header {
|
||||||
margin: auto;
|
margin: auto;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
@@ -174,22 +188,45 @@ footer {
|
|||||||
input {
|
input {
|
||||||
width: 70px;
|
width: 70px;
|
||||||
}
|
}
|
||||||
|
|
||||||
tr:last-of-type td {
|
tr:last-of-type td {
|
||||||
border-bottom: none;
|
border-bottom: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
td {
|
td {
|
||||||
border-bottom: 1px solid var(--text-color);
|
border-bottom: 1px solid var(--text-color);
|
||||||
border-right: 1px solid var(--text-color);
|
border-right: 1px solid var(--text-color);
|
||||||
padding: 10px;
|
padding: 10px;
|
||||||
}
|
}
|
||||||
|
|
||||||
td:last-of-type {
|
td:last-of-type {
|
||||||
border-right: none;
|
border-right: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
td.right {
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
.breathe {
|
.breathe {
|
||||||
padding-left: 4px;
|
padding-left: 4px;
|
||||||
padding-right: 4px;
|
padding-right: 4px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.separator::before {
|
||||||
|
content: '—';
|
||||||
|
}
|
||||||
|
|
||||||
@media (min-width: 600px) {
|
@media (min-width: 600px) {
|
||||||
|
.separator::before {
|
||||||
|
content: ':';
|
||||||
|
padding-left: 4px;
|
||||||
|
padding-right: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
td.ratio {
|
||||||
|
text-align: left;
|
||||||
|
}
|
||||||
|
|
||||||
td.right div {
|
td.right div {
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -183,35 +183,3 @@ a {
|
|||||||
text-decoration: none;
|
text-decoration: none;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
}
|
}
|
||||||
|
|
||||||
.tooltip-container {
|
|
||||||
display: inline-block;
|
|
||||||
}
|
|
||||||
|
|
||||||
.tooltip-carrier {
|
|
||||||
opacity: 0;
|
|
||||||
display: block;
|
|
||||||
pointer-events: none;
|
|
||||||
background-color: black;
|
|
||||||
border: white solid 1px;
|
|
||||||
color: white;
|
|
||||||
padding: 10px;
|
|
||||||
position: absolute;
|
|
||||||
z-index: 1;
|
|
||||||
overflow: hidden;
|
|
||||||
height: 0;
|
|
||||||
width: 0;
|
|
||||||
position: absolute;
|
|
||||||
transition: opacity 200ms, height 200ms, width 200ms;
|
|
||||||
|
|
||||||
.text-carrier {
|
|
||||||
position: absolute;
|
|
||||||
width: 350px;
|
|
||||||
font-size: 16px;
|
|
||||||
font-family: "Roboto", serif;
|
|
||||||
display: block;
|
|
||||||
overflow: hidden;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user