finish q+a
This commit is contained in:
parent
db244583d7
commit
7f5011bace
20 changed files with 259 additions and 68 deletions
|
@ -23,7 +23,8 @@ export async function bundleClientJavaScript(
|
||||||
}
|
}
|
||||||
|
|
||||||
const clientPlugins: esbuild.Plugin[] = [
|
const clientPlugins: esbuild.Plugin[] = [
|
||||||
// There are currently no plugins needed by 'paperclover.net'
|
projectRelativeResolution(),
|
||||||
|
markoViaBuildCache(incr),
|
||||||
];
|
];
|
||||||
|
|
||||||
const bundle = await esbuild.build({
|
const bundle = await esbuild.build({
|
||||||
|
@ -42,6 +43,9 @@ export async function bundleClientJavaScript(
|
||||||
jsx: "automatic",
|
jsx: "automatic",
|
||||||
jsxImportSource: "#ssr",
|
jsxImportSource: "#ssr",
|
||||||
jsxDev: dev,
|
jsxDev: dev,
|
||||||
|
define: {
|
||||||
|
"ASSERT": "console.assert",
|
||||||
|
},
|
||||||
});
|
});
|
||||||
if (bundle.errors.length || bundle.warnings.length) {
|
if (bundle.errors.length || bundle.warnings.length) {
|
||||||
throw new AggregateError(
|
throw new AggregateError(
|
||||||
|
@ -50,11 +54,15 @@ export async function bundleClientJavaScript(
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
const publicScriptRoutes = extraPublicScripts.map((file) =>
|
const publicScriptRoutes = extraPublicScripts.map((file) =>
|
||||||
path.basename(file).replace(/\.client\.[tj]sx?/, "")
|
"/js/" +
|
||||||
|
path.relative(hot.projectSrc, file).replaceAll("\\", "/").replace(
|
||||||
|
/\.client\.[tj]sx?/,
|
||||||
|
".js",
|
||||||
|
)
|
||||||
);
|
);
|
||||||
const { metafile } = bundle;
|
const { metafile, outputFiles } = bundle;
|
||||||
const promises: Promise<void>[] = [];
|
const promises: Promise<void>[] = [];
|
||||||
for (const file of bundle.outputFiles) {
|
for (const file of outputFiles) {
|
||||||
const { text } = file;
|
const { text } = file;
|
||||||
let route = file.path.replace(/^.*!/, "").replaceAll("\\", "/");
|
let route = file.path.replace(/^.*!/, "").replaceAll("\\", "/");
|
||||||
const { inputs } = UNWRAP(metafile.outputs["out!" + route]);
|
const { inputs } = UNWRAP(metafile.outputs["out!" + route]);
|
||||||
|
@ -63,8 +71,8 @@ export async function bundleClientJavaScript(
|
||||||
// Register non-chunks as script entries.
|
// Register non-chunks as script entries.
|
||||||
const chunk = route.startsWith("/js/c.");
|
const chunk = route.startsWith("/js/c.");
|
||||||
if (!chunk) {
|
if (!chunk) {
|
||||||
const key = hot.getScriptId(path.resolve(sources[0]));
|
const key = hot.getScriptId(path.resolve(sources[sources.length - 1]));
|
||||||
route = "/js/" + key + ".js";
|
route = "/js/" + key.replace(/\.client\.tsx?/, ".js");
|
||||||
incr.put({
|
incr.put({
|
||||||
sources,
|
sources,
|
||||||
kind: "script",
|
kind: "script",
|
||||||
|
@ -123,27 +131,7 @@ export async function bundleServerJavaScript(
|
||||||
"$views": viewSource,
|
"$views": viewSource,
|
||||||
}),
|
}),
|
||||||
projectRelativeResolution(),
|
projectRelativeResolution(),
|
||||||
{
|
markoViaBuildCache(incr),
|
||||||
name: "marko via build cache",
|
|
||||||
setup(b) {
|
|
||||||
b.onLoad(
|
|
||||||
{ filter: /\.marko$/ },
|
|
||||||
async ({ path: file }) => {
|
|
||||||
const key = path.relative(hot.projectRoot, file)
|
|
||||||
.replaceAll("\\", "/");
|
|
||||||
const cacheEntry = incr.out.serverMarko.get(key);
|
|
||||||
if (!cacheEntry) {
|
|
||||||
throw new Error("Marko file not in cache: " + file);
|
|
||||||
}
|
|
||||||
return ({
|
|
||||||
loader: "ts",
|
|
||||||
contents: cacheEntry.src,
|
|
||||||
resolveDir: path.dirname(file),
|
|
||||||
});
|
|
||||||
},
|
|
||||||
);
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
name: "replace client references",
|
name: "replace client references",
|
||||||
setup(b) {
|
setup(b) {
|
||||||
|
@ -168,6 +156,9 @@ export async function bundleServerJavaScript(
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
const pkg = await fs.readJson("package.json") as {
|
||||||
|
dependencies: Record<string, string>;
|
||||||
|
};
|
||||||
const { metafile, outputFiles } = await esbuild.build({
|
const { metafile, outputFiles } = await esbuild.build({
|
||||||
bundle: true,
|
bundle: true,
|
||||||
chunkNames: "c.[hash]",
|
chunkNames: "c.[hash]",
|
||||||
|
@ -186,6 +177,8 @@ export async function bundleServerJavaScript(
|
||||||
jsx: "automatic",
|
jsx: "automatic",
|
||||||
jsxImportSource: "#ssr",
|
jsxImportSource: "#ssr",
|
||||||
jsxDev: false,
|
jsxDev: false,
|
||||||
|
external: Object.keys(pkg.dependencies)
|
||||||
|
.filter((x) => !x.startsWith("@paperclover")),
|
||||||
});
|
});
|
||||||
|
|
||||||
const files: Record<string, Buffer> = {};
|
const files: Record<string, Buffer> = {};
|
||||||
|
@ -279,6 +272,29 @@ export async function finalizeServerJavaScript(
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function markoViaBuildCache(incr: Incremental): esbuild.Plugin {
|
||||||
|
return {
|
||||||
|
name: "marko via build cache",
|
||||||
|
setup(b) {
|
||||||
|
b.onLoad(
|
||||||
|
{ filter: /\.marko$/ },
|
||||||
|
async ({ path: file }) => {
|
||||||
|
const key = path.relative(hot.projectRoot, file)
|
||||||
|
.replaceAll("\\", "/");
|
||||||
|
const cacheEntry = incr.out.serverMarko.get(key);
|
||||||
|
if (!cacheEntry) {
|
||||||
|
throw new Error("Marko file not in cache: " + file);
|
||||||
|
}
|
||||||
|
return ({
|
||||||
|
loader: "ts",
|
||||||
|
contents: cacheEntry.src,
|
||||||
|
resolveDir: path.dirname(file),
|
||||||
|
});
|
||||||
|
},
|
||||||
|
);
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
import * as esbuild from "esbuild";
|
import * as esbuild from "esbuild";
|
||||||
import * as path from "node:path";
|
import * as path from "node:path";
|
||||||
import process from "node:process";
|
import process from "node:process";
|
||||||
|
|
|
@ -52,7 +52,6 @@ export const dynamicTag = (
|
||||||
if (typeof tag === "function") {
|
if (typeof tag === "function") {
|
||||||
clover: {
|
clover: {
|
||||||
const unwrapped = (tag as any).unwrapped;
|
const unwrapped = (tag as any).unwrapped;
|
||||||
console.log({ tag, unwrapped });
|
|
||||||
if (unwrapped) {
|
if (unwrapped) {
|
||||||
tag = unwrapped;
|
tag = unwrapped;
|
||||||
break clover;
|
break clover;
|
||||||
|
@ -130,7 +129,7 @@ export function escapeXML(input: unknown) {
|
||||||
) {
|
) {
|
||||||
throw new Error(
|
throw new Error(
|
||||||
`Unexpected object in template placeholder: '` +
|
`Unexpected object in template placeholder: '` +
|
||||||
util.inspect({ name: "clover" }) + "'. " +
|
engine.inspect({ name: "clover" }) + "'. " +
|
||||||
`To emit a literal '[object Object]', use \${String(value)}`,
|
`To emit a literal '[object Object]', use \${String(value)}`,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -145,4 +144,3 @@ import * as engine from "./ssr.ts";
|
||||||
import type { ServerRenderer } from "marko/html/template";
|
import type { ServerRenderer } from "marko/html/template";
|
||||||
import { type Accessor } from "marko/common/types";
|
import { type Accessor } from "marko/common/types";
|
||||||
import * as marko from "#marko/html";
|
import * as marko from "#marko/html";
|
||||||
import * as util from "node:util";
|
|
||||||
|
|
|
@ -8,9 +8,9 @@ export function main() {
|
||||||
successText,
|
successText,
|
||||||
failureText: () => "sitegen FAIL",
|
failureText: () => "sitegen FAIL",
|
||||||
}, async (spinner) => {
|
}, async (spinner) => {
|
||||||
const incr = Incremental.fromDisk();
|
// const incr = Incremental.fromDisk();
|
||||||
await incr.statAllFiles();
|
// await incr.statAllFiles();
|
||||||
// const incr = new Incremental();
|
const incr = new Incremental();
|
||||||
const result = await sitegen(spinner, incr);
|
const result = await sitegen(spinner, incr);
|
||||||
incr.toDisk(); // Allows picking up this state again
|
incr.toDisk(); // Allows picking up this state again
|
||||||
return result;
|
return result;
|
||||||
|
@ -122,7 +122,6 @@ export async function sitegen(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
scripts = scripts.filter(({ file }) => !file.match(/\.client\.[tj]sx?/));
|
|
||||||
const globalCssPath = join("global.css");
|
const globalCssPath = join("global.css");
|
||||||
|
|
||||||
// TODO: make sure that `static` and `pages` does not overlap
|
// TODO: make sure that `static` and `pages` does not overlap
|
||||||
|
@ -311,8 +310,12 @@ export async function sitegen(
|
||||||
maxJobs: 2,
|
maxJobs: 2,
|
||||||
});
|
});
|
||||||
viewQueue.addMany(neededViews);
|
viewQueue.addMany(neededViews);
|
||||||
await pageQueue.done({ method: "stop" });
|
const pageAndViews = [
|
||||||
await viewQueue.done({ method: "stop" });
|
pageQueue.done({ method: "stop" }),
|
||||||
|
viewQueue.done({ method: "stop" }),
|
||||||
|
];
|
||||||
|
await Promise.allSettled(pageAndViews);
|
||||||
|
await Promise.all(pageAndViews);
|
||||||
status.format = spinnerFormat;
|
status.format = spinnerFormat;
|
||||||
|
|
||||||
// -- bundle server javascript (backend and views) --
|
// -- bundle server javascript (backend and views) --
|
||||||
|
|
|
@ -63,7 +63,7 @@ Module.prototype._compile = function (
|
||||||
if (shouldTrackPath(filename)) {
|
if (shouldTrackPath(filename)) {
|
||||||
const cssImportsMaybe: string[] = [];
|
const cssImportsMaybe: string[] = [];
|
||||||
const imports: string[] = [];
|
const imports: string[] = [];
|
||||||
for (const { filename: file } of this.children) {
|
for (const { filename: file, cloverClientRefs } of this.children) {
|
||||||
if (file.endsWith(".css")) cssImportsMaybe.push(file);
|
if (file.endsWith(".css")) cssImportsMaybe.push(file);
|
||||||
else {
|
else {
|
||||||
const child = fileStats.get(file);
|
const child = fileStats.get(file);
|
||||||
|
@ -71,6 +71,10 @@ Module.prototype._compile = function (
|
||||||
const { cssImportsRecursive } = child;
|
const { cssImportsRecursive } = child;
|
||||||
if (cssImportsRecursive) cssImportsMaybe.push(...cssImportsRecursive);
|
if (cssImportsRecursive) cssImportsMaybe.push(...cssImportsRecursive);
|
||||||
imports.push(file);
|
imports.push(file);
|
||||||
|
if (cloverClientRefs && cloverClientRefs.length > 0) {
|
||||||
|
(this.cloverClientRefs ??= [])
|
||||||
|
.push(...cloverClientRefs);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
fileStats.set(filename, {
|
fileStats.set(filename, {
|
||||||
|
|
|
@ -148,7 +148,7 @@ export class Incremental {
|
||||||
for (const key of map.keys()) {
|
for (const key of map.keys()) {
|
||||||
if (!this.round.referenced.has(`${kind}\0${key}`)) {
|
if (!this.round.referenced.has(`${kind}\0${key}`)) {
|
||||||
unreferenced.push({ kind: kind as ArtifactKind, key });
|
unreferenced.push({ kind: kind as ArtifactKind, key });
|
||||||
this.out[kind as ArtifactKind].delete(key);
|
// this.out[kind as ArtifactKind].delete(key);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -11,7 +11,6 @@ export async function reload() {
|
||||||
fs.readFile(path.join(import.meta.dirname, "static.json"), "utf8"),
|
fs.readFile(path.join(import.meta.dirname, "static.json"), "utf8"),
|
||||||
fs.readFile(path.join(import.meta.dirname, "static.blob")),
|
fs.readFile(path.join(import.meta.dirname, "static.blob")),
|
||||||
]);
|
]);
|
||||||
console.log("new buffer loaded");
|
|
||||||
assets = {
|
assets = {
|
||||||
map: JSON.parse(map),
|
map: JSON.parse(map),
|
||||||
buf,
|
buf,
|
||||||
|
@ -110,7 +109,6 @@ function assetInner(c: Context, asset: BuiltAsset, status: StatusCode) {
|
||||||
}
|
}
|
||||||
|
|
||||||
process.on("message", (msg: any) => {
|
process.on("message", (msg: any) => {
|
||||||
console.log({ msg });
|
|
||||||
if (msg?.type === "clover.assets.reload") reload();
|
if (msg?.type === "clover.assets.reload") reload();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -58,8 +58,8 @@ export async function main() {
|
||||||
successText: generate.successText,
|
successText: generate.successText,
|
||||||
failureText: () => "sitegen FAIL",
|
failureText: () => "sitegen FAIL",
|
||||||
}, async (spinner) => {
|
}, async (spinner) => {
|
||||||
console.log("---");
|
console.info("---");
|
||||||
console.log(
|
console.info(
|
||||||
"Updated" +
|
"Updated" +
|
||||||
(changed.length === 1
|
(changed.length === 1
|
||||||
? " " + changed[0]
|
? " " + changed[0]
|
||||||
|
|
152
package-lock.json
generated
152
package-lock.json
generated
|
@ -8,6 +8,8 @@
|
||||||
"@hono/node-server": "^1.14.3",
|
"@hono/node-server": "^1.14.3",
|
||||||
"@mdx-js/mdx": "^3.1.0",
|
"@mdx-js/mdx": "^3.1.0",
|
||||||
"@paperclover/console": "git+https://git.paperclover.net/clo/console.git",
|
"@paperclover/console": "git+https://git.paperclover.net/clo/console.git",
|
||||||
|
"codemirror": "^6.0.1",
|
||||||
|
"devalue": "^5.1.1",
|
||||||
"esbuild": "^0.25.5",
|
"esbuild": "^0.25.5",
|
||||||
"hls.js": "^1.6.5",
|
"hls.js": "^1.6.5",
|
||||||
"hono": "^4.7.11",
|
"hono": "^4.7.11",
|
||||||
|
@ -404,6 +406,87 @@
|
||||||
"node": ">=6.9.0"
|
"node": ">=6.9.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@codemirror/autocomplete": {
|
||||||
|
"version": "6.18.6",
|
||||||
|
"resolved": "https://registry.npmjs.org/@codemirror/autocomplete/-/autocomplete-6.18.6.tgz",
|
||||||
|
"integrity": "sha512-PHHBXFomUs5DF+9tCOM/UoW6XQ4R44lLNNhRaW9PKPTU0D7lIjRg3ElxaJnTwsl/oHiR93WSXDBrekhoUGCPtg==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@codemirror/language": "^6.0.0",
|
||||||
|
"@codemirror/state": "^6.0.0",
|
||||||
|
"@codemirror/view": "^6.17.0",
|
||||||
|
"@lezer/common": "^1.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@codemirror/commands": {
|
||||||
|
"version": "6.8.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/@codemirror/commands/-/commands-6.8.1.tgz",
|
||||||
|
"integrity": "sha512-KlGVYufHMQzxbdQONiLyGQDUW0itrLZwq3CcY7xpv9ZLRHqzkBSoteocBHtMCoY7/Ci4xhzSrToIeLg7FxHuaw==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@codemirror/language": "^6.0.0",
|
||||||
|
"@codemirror/state": "^6.4.0",
|
||||||
|
"@codemirror/view": "^6.27.0",
|
||||||
|
"@lezer/common": "^1.1.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@codemirror/language": {
|
||||||
|
"version": "6.11.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/@codemirror/language/-/language-6.11.1.tgz",
|
||||||
|
"integrity": "sha512-5kS1U7emOGV84vxC+ruBty5sUgcD0te6dyupyRVG2zaSjhTDM73LhVKUtVwiqSe6QwmEoA4SCiU8AKPFyumAWQ==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@codemirror/state": "^6.0.0",
|
||||||
|
"@codemirror/view": "^6.23.0",
|
||||||
|
"@lezer/common": "^1.1.0",
|
||||||
|
"@lezer/highlight": "^1.0.0",
|
||||||
|
"@lezer/lr": "^1.0.0",
|
||||||
|
"style-mod": "^4.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@codemirror/lint": {
|
||||||
|
"version": "6.8.5",
|
||||||
|
"resolved": "https://registry.npmjs.org/@codemirror/lint/-/lint-6.8.5.tgz",
|
||||||
|
"integrity": "sha512-s3n3KisH7dx3vsoeGMxsbRAgKe4O1vbrnKBClm99PU0fWxmxsx5rR2PfqQgIt+2MMJBHbiJ5rfIdLYfB9NNvsA==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@codemirror/state": "^6.0.0",
|
||||||
|
"@codemirror/view": "^6.35.0",
|
||||||
|
"crelt": "^1.0.5"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@codemirror/search": {
|
||||||
|
"version": "6.5.11",
|
||||||
|
"resolved": "https://registry.npmjs.org/@codemirror/search/-/search-6.5.11.tgz",
|
||||||
|
"integrity": "sha512-KmWepDE6jUdL6n8cAAqIpRmLPBZ5ZKnicE8oGU/s3QrAVID+0VhLFrzUucVKHG5035/BSykhExDL/Xm7dHthiA==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@codemirror/state": "^6.0.0",
|
||||||
|
"@codemirror/view": "^6.0.0",
|
||||||
|
"crelt": "^1.0.5"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@codemirror/state": {
|
||||||
|
"version": "6.5.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/@codemirror/state/-/state-6.5.2.tgz",
|
||||||
|
"integrity": "sha512-FVqsPqtPWKVVL3dPSxy8wEF/ymIEuVzF1PK3VbUgrxXpJUSHQWWZz4JMToquRxnkw+36LTamCZG2iua2Ptq0fA==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@marijn/find-cluster-break": "^1.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@codemirror/view": {
|
||||||
|
"version": "6.37.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/@codemirror/view/-/view-6.37.2.tgz",
|
||||||
|
"integrity": "sha512-XD3LdgQpxQs5jhOOZ2HRVT+Rj59O4Suc7g2ULvZ+Yi8eCkickrkZ5JFuoDhs2ST1mNI5zSsNYgR3NGa4OUrbnw==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@codemirror/state": "^6.5.0",
|
||||||
|
"crelt": "^1.0.6",
|
||||||
|
"style-mod": "^4.1.0",
|
||||||
|
"w3c-keyname": "^2.2.4"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@esbuild/aix-ppc64": {
|
"node_modules/@esbuild/aix-ppc64": {
|
||||||
"version": "0.25.5",
|
"version": "0.25.5",
|
||||||
"resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.5.tgz",
|
"resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.5.tgz",
|
||||||
|
@ -864,6 +947,30 @@
|
||||||
"@jridgewell/sourcemap-codec": "^1.4.14"
|
"@jridgewell/sourcemap-codec": "^1.4.14"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@lezer/common": {
|
||||||
|
"version": "1.2.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/@lezer/common/-/common-1.2.3.tgz",
|
||||||
|
"integrity": "sha512-w7ojc8ejBqr2REPsWxJjrMFsA/ysDCFICn8zEOR9mrqzOu2amhITYuLD8ag6XZf0CFXDrhKqw7+tW8cX66NaDA==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
|
"node_modules/@lezer/highlight": {
|
||||||
|
"version": "1.2.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/@lezer/highlight/-/highlight-1.2.1.tgz",
|
||||||
|
"integrity": "sha512-Z5duk4RN/3zuVO7Jq0pGLJ3qynpxUVsh7IbUbGj88+uV2ApSAn6kWg2au3iJb+0Zi7kKtqffIESgNcRXWZWmSA==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@lezer/common": "^1.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@lezer/lr": {
|
||||||
|
"version": "1.4.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/@lezer/lr/-/lr-1.4.2.tgz",
|
||||||
|
"integrity": "sha512-pu0K1jCIdnQ12aWNaAVU5bzi7Bd1w54J3ECgANPmYLtQKP0HBj2cE/5coBD66MT10xbtIuUr7tg0Shbsvk0mDA==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@lezer/common": "^1.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@luxass/strip-json-comments": {
|
"node_modules/@luxass/strip-json-comments": {
|
||||||
"version": "1.4.0",
|
"version": "1.4.0",
|
||||||
"resolved": "https://registry.npmjs.org/@luxass/strip-json-comments/-/strip-json-comments-1.4.0.tgz",
|
"resolved": "https://registry.npmjs.org/@luxass/strip-json-comments/-/strip-json-comments-1.4.0.tgz",
|
||||||
|
@ -873,6 +980,12 @@
|
||||||
"node": ">=18"
|
"node": ">=18"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@marijn/find-cluster-break": {
|
||||||
|
"version": "1.0.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/@marijn/find-cluster-break/-/find-cluster-break-1.0.2.tgz",
|
||||||
|
"integrity": "sha512-l0h88YhZFyKdXIFNfSWpyjStDjGHwZ/U7iobcK1cQQD8sejsONdQtTVU+1wVN1PBw40PiiHB1vA5S7VTfQiP9g==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
"node_modules/@marko/compiler": {
|
"node_modules/@marko/compiler": {
|
||||||
"version": "5.39.21",
|
"version": "5.39.21",
|
||||||
"resolved": "https://registry.npmjs.org/@marko/compiler/-/compiler-5.39.21.tgz",
|
"resolved": "https://registry.npmjs.org/@marko/compiler/-/compiler-5.39.21.tgz",
|
||||||
|
@ -1458,6 +1571,21 @@
|
||||||
"node": ">=8"
|
"node": ">=8"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/codemirror": {
|
||||||
|
"version": "6.0.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/codemirror/-/codemirror-6.0.1.tgz",
|
||||||
|
"integrity": "sha512-J8j+nZ+CdWmIeFIGXEFbFPtpiYacFMDR8GlHK3IyHQJMCaVRfGx9NT+Hxivv1ckLWPvNdZqndbr/7lVhrf/Svg==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@codemirror/autocomplete": "^6.0.0",
|
||||||
|
"@codemirror/commands": "^6.0.0",
|
||||||
|
"@codemirror/language": "^6.0.0",
|
||||||
|
"@codemirror/lint": "^6.0.0",
|
||||||
|
"@codemirror/search": "^6.0.0",
|
||||||
|
"@codemirror/state": "^6.0.0",
|
||||||
|
"@codemirror/view": "^6.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/collapse-white-space": {
|
"node_modules/collapse-white-space": {
|
||||||
"version": "2.1.0",
|
"version": "2.1.0",
|
||||||
"resolved": "https://registry.npmjs.org/collapse-white-space/-/collapse-white-space-2.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/collapse-white-space/-/collapse-white-space-2.1.0.tgz",
|
||||||
|
@ -1537,6 +1665,12 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/crelt": {
|
||||||
|
"version": "1.0.6",
|
||||||
|
"resolved": "https://registry.npmjs.org/crelt/-/crelt-1.0.6.tgz",
|
||||||
|
"integrity": "sha512-VQ2MBenTq1fWZUH9DJNGti7kKv6EeAuYr3cLwxUWhIu1baTaXh4Ib5W2CqHVqib4/MqbYGJqiL3Zb8GJZr3l4g==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
"node_modules/csstype": {
|
"node_modules/csstype": {
|
||||||
"version": "3.1.3",
|
"version": "3.1.3",
|
||||||
"resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz",
|
"resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz",
|
||||||
|
@ -1605,6 +1739,12 @@
|
||||||
"node": ">=6"
|
"node": ">=6"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/devalue": {
|
||||||
|
"version": "5.1.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/devalue/-/devalue-5.1.1.tgz",
|
||||||
|
"integrity": "sha512-maua5KUiapvEwiEAe+XnlZ3Rh0GD+qI1J/nb9vrJc3muPXvcF/8gXYTWF76+5DAqHyDUtOIImEuo0YKE9mshVw==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
"node_modules/devlop": {
|
"node_modules/devlop": {
|
||||||
"version": "1.1.0",
|
"version": "1.1.0",
|
||||||
"resolved": "https://registry.npmjs.org/devlop/-/devlop-1.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/devlop/-/devlop-1.1.0.tgz",
|
||||||
|
@ -3748,6 +3888,12 @@
|
||||||
"url": "https://github.com/chalk/strip-ansi?sponsor=1"
|
"url": "https://github.com/chalk/strip-ansi?sponsor=1"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/style-mod": {
|
||||||
|
"version": "4.1.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/style-mod/-/style-mod-4.1.2.tgz",
|
||||||
|
"integrity": "sha512-wnD1HyVqpJUI2+eKZ+eo1UwghftP6yuFheBqqe+bWCotBjC2K1YnteJILRMs3SM4V/0dLEW1SC27MWP5y+mwmw==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
"node_modules/style-to-js": {
|
"node_modules/style-to-js": {
|
||||||
"version": "1.1.16",
|
"version": "1.1.16",
|
||||||
"resolved": "https://registry.npmjs.org/style-to-js/-/style-to-js-1.1.16.tgz",
|
"resolved": "https://registry.npmjs.org/style-to-js/-/style-to-js-1.1.16.tgz",
|
||||||
|
@ -4020,6 +4166,12 @@
|
||||||
"url": "https://opencollective.com/unified"
|
"url": "https://opencollective.com/unified"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/w3c-keyname": {
|
||||||
|
"version": "2.2.8",
|
||||||
|
"resolved": "https://registry.npmjs.org/w3c-keyname/-/w3c-keyname-2.2.8.tgz",
|
||||||
|
"integrity": "sha512-dpojBhNsCNN7T82Tm7k26A6G9ML3NkhDsnw9n/eoxSRlVBB4CEtIQ/KTCLI2Fwf3ataSXRhYFkQi3SlnFwPvPQ==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
"node_modules/wrap-ansi": {
|
"node_modules/wrap-ansi": {
|
||||||
"version": "7.0.0",
|
"version": "7.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz",
|
||||||
|
|
|
@ -4,6 +4,8 @@
|
||||||
"@hono/node-server": "^1.14.3",
|
"@hono/node-server": "^1.14.3",
|
||||||
"@mdx-js/mdx": "^3.1.0",
|
"@mdx-js/mdx": "^3.1.0",
|
||||||
"@paperclover/console": "git+https://git.paperclover.net/clo/console.git",
|
"@paperclover/console": "git+https://git.paperclover.net/clo/console.git",
|
||||||
|
"codemirror": "^6.0.1",
|
||||||
|
"devalue": "^5.1.1",
|
||||||
"esbuild": "^0.25.5",
|
"esbuild": "^0.25.5",
|
||||||
"hls.js": "^1.6.5",
|
"hls.js": "^1.6.5",
|
||||||
"hono": "^4.7.11",
|
"hono": "^4.7.11",
|
||||||
|
|
|
@ -127,9 +127,9 @@ app.get("/q+a/:id", async (c, next) => {
|
||||||
const question = Question.getByDate(timestamp);
|
const question = Question.getByDate(timestamp);
|
||||||
if (!question) return next();
|
if (!question) return next();
|
||||||
|
|
||||||
// if (image) {
|
if (image) {
|
||||||
// return getQuestionImage(question, c.req.method === "HEAD");
|
return getQuestionImage(question, c.req.method === "HEAD");
|
||||||
// }
|
}
|
||||||
return renderView(c, "q+a/permalink", { question });
|
return renderView(c, "q+a/permalink", { question });
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -193,11 +193,6 @@ app.get("/q+a/things/random", async (c) => {
|
||||||
c.res = await renderView(c, "q+a/things-random", {});
|
c.res = await renderView(c, "q+a/things-random", {});
|
||||||
});
|
});
|
||||||
|
|
||||||
// 404
|
|
||||||
app.get("/q+a/*", async (c) => {
|
|
||||||
return serveAsset(c, "/q+a/404", 404);
|
|
||||||
});
|
|
||||||
|
|
||||||
async function questionFailure(
|
async function questionFailure(
|
||||||
c: Context,
|
c: Context,
|
||||||
status: ContentfulStatusCode,
|
status: ContentfulStatusCode,
|
||||||
|
@ -229,5 +224,5 @@ import {
|
||||||
} from "./models/PendingQuestion.ts";
|
} from "./models/PendingQuestion.ts";
|
||||||
import { Question, QuestionType } from "./models/Question.ts";
|
import { Question, QuestionType } from "./models/Question.ts";
|
||||||
import { renderView } from "#sitegen/view";
|
import { renderView } from "#sitegen/view";
|
||||||
// import { getQuestionImage } from "./question_image";
|
import { getQuestionImage } from "./image.tsx";
|
||||||
import { formatQuestionId, questionIdToTimestamp } from "./format.ts";
|
import { formatQuestionId, questionIdToTimestamp } from "./format.ts";
|
||||||
|
|
|
@ -211,7 +211,7 @@ function renderNode(node: string | ASTNode | ASTNode[]): any {
|
||||||
if (typeof node === "string") {
|
if (typeof node === "string") {
|
||||||
return node;
|
return node;
|
||||||
} else if (Array.isArray(node)) {
|
} else if (Array.isArray(node)) {
|
||||||
return node.flatMap((item) => renderNode(item));
|
return node.map((item) => renderNode(item));
|
||||||
} else if (node.type === "text") {
|
} else if (node.type === "text") {
|
||||||
return node.content;
|
return node.content;
|
||||||
} else {
|
} else {
|
||||||
|
|
|
@ -1,4 +1,12 @@
|
||||||
const h1 = document.querySelector("h1")!;
|
const h1 = document.querySelector("h1")!;
|
||||||
|
const key = "net.paperclover.q+a.header";
|
||||||
|
const state = localStorage?.getItem(key);
|
||||||
|
if (state === "detrevni ton") h1.classList.toggle("invert");
|
||||||
h1.addEventListener("click", () => {
|
h1.addEventListener("click", () => {
|
||||||
h1.classList.toggle("invert");
|
h1.classList.toggle("invert");
|
||||||
|
localStorage?.setItem(
|
||||||
|
key,
|
||||||
|
(localStorage.getItem?.(key) ?? "not inverted")
|
||||||
|
.split("").reverse().join(""),
|
||||||
|
);
|
||||||
});
|
});
|
||||||
|
|
|
@ -5,14 +5,13 @@ const cacheImageDir = path.resolve(".clover/question_images");
|
||||||
const getBrowser = RefCountedExpirable(
|
const getBrowser = RefCountedExpirable(
|
||||||
() =>
|
() =>
|
||||||
puppeteer.launch({
|
puppeteer.launch({
|
||||||
// headless: false,
|
|
||||||
args: ["--no-sandbox", "--disable-setuid-sandbox"],
|
args: ["--no-sandbox", "--disable-setuid-sandbox"],
|
||||||
}),
|
}),
|
||||||
(b) => b.close(),
|
(b) => b.close(),
|
||||||
);
|
);
|
||||||
|
|
||||||
export async function renderQuestionImage(question: Question) {
|
export async function renderQuestionImage(question: Question) {
|
||||||
const html = await renderViewToString("q+a/embed-image", { question });
|
const html = await renderViewToString("q+a/image-embed", { question });
|
||||||
|
|
||||||
// this browser session will be reused if multiple images are generated
|
// this browser session will be reused if multiple images are generated
|
||||||
// either at the same time or within a 5-minute time span. the dispose
|
// either at the same time or within a 5-minute time span. the dispose
|
||||||
|
|
|
@ -6,6 +6,9 @@ export const meta: Metadata = {
|
||||||
title: "paper clover q+a",
|
title: "paper clover q+a",
|
||||||
description: "ask clover a question",
|
description: "ask clover a question",
|
||||||
};
|
};
|
||||||
|
export const regenerate = {
|
||||||
|
manual: true,
|
||||||
|
};
|
||||||
|
|
||||||
<const/inboxSize = PendingQuestion.getAll().length />
|
<const/inboxSize = PendingQuestion.getAll().length />
|
||||||
<if=(inboxSize > 0)>
|
<if=(inboxSize > 0)>
|
||||||
|
|
|
@ -4,6 +4,7 @@ export const meta: Metadata = {
|
||||||
};
|
};
|
||||||
|
|
||||||
<h2>sound the alarms</h2>
|
<h2>sound the alarms</h2>
|
||||||
|
<p>this page doesn't exist</p>
|
||||||
<p>
|
<p>
|
||||||
<a href="/q+a">return to the questions list</a>
|
<a href="/q+a">return to the questions list</a>
|
||||||
</p>
|
</p>
|
||||||
|
|
|
@ -1,4 +1,7 @@
|
||||||
export * as layout from "../layout.tsx";
|
export * as layout from "../layout.tsx";
|
||||||
|
export const regenerate = {
|
||||||
|
manual: true,
|
||||||
|
};
|
||||||
|
|
||||||
export interface Input {
|
export interface Input {
|
||||||
admin?: boolean;
|
admin?: boolean;
|
||||||
|
@ -11,7 +14,7 @@ export const meta: Metadata = {
|
||||||
<const/{ admin = false } = input />
|
<const/{ admin = false } = input />
|
||||||
<const/questions = [...Question.getAll()] />
|
<const/questions = [...Question.getAll()] />
|
||||||
|
|
||||||
<if=!admin>
|
<if=true>
|
||||||
<question-form />
|
<question-form />
|
||||||
</>
|
</>
|
||||||
<for|question| of=questions>
|
<for|question| of=questions>
|
||||||
|
|
|
@ -1,9 +1,8 @@
|
||||||
import { basicSetup, EditorState, EditorView } from "codemirror";
|
import { EditorState } from "@codemirror/state";
|
||||||
|
import { basicSetup, EditorView } from "codemirror";
|
||||||
import { ssrSync } from "#ssr";
|
import { ssrSync } from "#ssr";
|
||||||
// @ts-ignore
|
import { ScriptPayload } from "@/q+a/view/editor.marko";
|
||||||
import type { ScriptPayload } from "../views/editor.marko";
|
import QuestionRender from "@/q+a/tags/question.marko";
|
||||||
// @ts-ignore
|
|
||||||
import QuestionRender from "@/q+a/components/Question.marko";
|
|
||||||
|
|
||||||
declare const payload: ScriptPayload;
|
declare const payload: ScriptPayload;
|
||||||
const date = new Date(payload.date);
|
const date = new Date(payload.date);
|
||||||
|
@ -21,9 +20,11 @@ function updatePreview(text: string) {
|
||||||
type: payload.type,
|
type: payload.type,
|
||||||
date,
|
date,
|
||||||
}}
|
}}
|
||||||
|
editor
|
||||||
/>,
|
/>,
|
||||||
).text;
|
).text;
|
||||||
}
|
}
|
||||||
|
updatePreview(payload.text);
|
||||||
|
|
||||||
const startState = EditorState.create({
|
const startState = EditorState.create({
|
||||||
doc: payload.text,
|
doc: payload.text,
|
||||||
|
|
|
@ -2,12 +2,13 @@
|
||||||
export interface Input {
|
export interface Input {
|
||||||
question: Question;
|
question: Question;
|
||||||
admin?: boolean;
|
admin?: boolean;
|
||||||
|
editor?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 2024-12-31 05:00:00 EST
|
// 2024-12-31 05:00:00 EST
|
||||||
export const transitionDate = 1735639200000;
|
export const transitionDate = 1735639200000;
|
||||||
|
|
||||||
<const/{ question, admin } = input />
|
<const/{ question, admin = false, editor = false } = input />
|
||||||
<const/{ id, date } = question/>
|
<const/{ id, date } = question/>
|
||||||
|
|
||||||
<${"e-"}
|
<${"e-"}
|
||||||
|
@ -30,8 +31,11 @@ export const transitionDate = 1735639200000;
|
||||||
</>
|
</>
|
||||||
|
|
||||||
// this singleton script will make all the '<time>' tags clickable.
|
// this singleton script will make all the '<time>' tags clickable.
|
||||||
client import "./clickable-links.client.ts";
|
<if=!editor>
|
||||||
|
client import "./clickable-links.client.ts";
|
||||||
|
</>
|
||||||
|
|
||||||
import type { Question } from "@/q+a/models/Question.ts";
|
import type { Question } from "@/q+a/models/Question.ts";
|
||||||
import { formatQuestionTimestamp, formatQuestionISOTimestamp } from "@/q+a/format.ts";
|
import { formatQuestionTimestamp, formatQuestionISOTimestamp } from "@/q+a/format.ts";
|
||||||
import { CloverMarkdown } from "@/q+a/clover-markdown.tsx";
|
import { CloverMarkdown } from "@/q+a/clover-markdown.tsx";
|
||||||
|
import { addScript as Script } from "#sitegen"
|
||||||
|
|
|
@ -56,10 +56,12 @@ export interface ScriptPayload {
|
||||||
</div>
|
</div>
|
||||||
<div#editor />
|
<div#editor />
|
||||||
<main#preview>
|
<main#preview>
|
||||||
<question ...{question} />
|
<question editor ...{ question } />
|
||||||
</main>
|
</main>
|
||||||
</div>
|
</div>
|
||||||
<html-script src="/js/edit_frontend.js" type="module" />
|
<html-script>self.payload=${devalue.uneval(payload)}</html-script>
|
||||||
|
<html-script src="/js/q+a/scripts/editor.js" type="module" />
|
||||||
|
|
||||||
|
import * as devalue from 'devalue';
|
||||||
import { type PendingQuestion } from "@/q+a/models/PendingQuestion.ts";
|
import { type PendingQuestion } from "@/q+a/models/PendingQuestion.ts";
|
||||||
import { Question, QuestionType } from "@/q+a/models/Question.ts";
|
import { Question, QuestionType } from "@/q+a/models/Question.ts";
|
||||||
|
|
|
@ -1,23 +1,25 @@
|
||||||
|
export * as layout from "@/q+a/layout.tsx";
|
||||||
export interface Input {
|
export interface Input {
|
||||||
question: Question;
|
question: Question;
|
||||||
}
|
}
|
||||||
|
|
||||||
server export function meta({ context: { req }, question }) {
|
server export function meta({ context, question }) {
|
||||||
const isDiscord = req.get("user-agent")
|
const isDiscord = context.get("user-agent")
|
||||||
?.toLowerCase()
|
?.toLowerCase()
|
||||||
.includes("discordbot");
|
.includes("discordbot");
|
||||||
if (question.type === QuestionType.normal) {
|
if (question.type === QuestionType.normal) {
|
||||||
return {
|
return {
|
||||||
title: "question permalink",
|
title: "question permalink",
|
||||||
openGraph: {
|
openGraph: {
|
||||||
images: [{ url: `https://paperclover.net/q+a/${q.id}.png` }],
|
images: [{ url: `https://paperclover.net/q+a/${question.id}.png` }],
|
||||||
},
|
},
|
||||||
twitter: { card: "summary_large_image" },
|
twitter: { card: "summary_large_image" },
|
||||||
themeColor: isDiscord
|
themeColor: isDiscord
|
||||||
? q.date.getTime() > transitionDate ? "#8c78ff" : "#58ff71"
|
? question.date.getTime() > transitionDate ? "#8c78ff" : "#58ff71"
|
||||||
: undefined,
|
: undefined,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
return { title: 'question permalink' };
|
||||||
}
|
}
|
||||||
|
|
||||||
<const/{ question }=input/>
|
<const/{ question }=input/>
|
||||||
|
|
Loading…
Reference in a new issue