| author | |
| committer | |
| log | fa86e0da8bb2677603cb5bcda64b565a362359a6 |
| tree | de1e68df47cd8f90e57210af655dcb854bf7fcab |
| parent | dad822b38eaec73a2696f012cd4e8a4117dd2e87 |
| signature | Commit is signed but in an unrecognized format. |
20 files changed, 259 insertions(+), 68 deletions(-)
framework/bundle.ts+43-27| ... | @@ -23,7 +23,8 @@ export async function bundleClientJavaScript( | ... | @@ -23,7 +23,8 @@ export async function bundleClientJavaScript( |
| 23 | } | 23 | } |
| 24 | 24 | ||
| 25 | const clientPlugins: esbuild.Plugin[] = [ | 25 | const clientPlugins: esbuild.Plugin[] = [ |
| 26 | // There are currently no plugins needed by 'paperclover.net' | 26 | projectRelativeResolution(), |
| 27 | markoViaBuildCache(incr), | ||
| 27 | ]; | 28 | ]; |
| 28 | 29 | ||
| 29 | const bundle = await esbuild.build({ | 30 | const bundle = await esbuild.build({ |
| ... | @@ -42,6 +43,9 @@ export async function bundleClientJavaScript( | ... | @@ -42,6 +43,9 @@ export async function bundleClientJavaScript( |
| 42 | jsx: "automatic", | 43 | jsx: "automatic", |
| 43 | jsxImportSource: "#ssr", | 44 | jsxImportSource: "#ssr", |
| 44 | jsxDev: dev, | 45 | jsxDev: dev, |
| 46 | define: { | ||
| 47 | "ASSERT": "console.assert", | ||
| 48 | }, | ||
| 45 | }); | 49 | }); |
| 46 | if (bundle.errors.length || bundle.warnings.length) { | 50 | if (bundle.errors.length || bundle.warnings.length) { |
| 47 | throw new AggregateError( | 51 | throw new AggregateError( |
| ... | @@ -50,11 +54,15 @@ export async function bundleClientJavaScript( | ... | @@ -50,11 +54,15 @@ export async function bundleClientJavaScript( |
| 50 | ); | 54 | ); |
| 51 | } | 55 | } |
| 52 | const publicScriptRoutes = extraPublicScripts.map((file) => | 56 | const publicScriptRoutes = extraPublicScripts.map((file) => |
| 53 | path.basename(file).replace(/\.client\.[tj]sx?/, "") | 57 | "/js/" + |
| 58 | path.relative(hot.projectSrc, file).replaceAll("\\", "/").replace( | ||
| 59 | /\.client\.[tj]sx?/, | ||
| 60 | ".js", | ||
| 61 | ) | ||
| 54 | ); | 62 | ); |
| 55 | const { metafile } = bundle; | 63 | const { metafile, outputFiles } = bundle; |
| 56 | const promises: Promise<void>[] = []; | 64 | const promises: Promise<void>[] = []; |
| 57 | for (const file of bundle.outputFiles) { | 65 | for (const file of outputFiles) { |
| 58 | const { text } = file; | 66 | const { text } = file; |
| 59 | let route = file.path.replace(/^.*!/, "").replaceAll("\\", "/"); | 67 | let route = file.path.replace(/^.*!/, "").replaceAll("\\", "/"); |
| 60 | const { inputs } = UNWRAP(metafile.outputs["out!" + route]); | 68 | const { inputs } = UNWRAP(metafile.outputs["out!" + route]); |
| ... | @@ -63,8 +71,8 @@ export async function bundleClientJavaScript( | ... | @@ -63,8 +71,8 @@ export async function bundleClientJavaScript( |
| 63 | // Register non-chunks as script entries. | 71 | // Register non-chunks as script entries. |
| 64 | const chunk = route.startsWith("/js/c."); | 72 | const chunk = route.startsWith("/js/c."); |
| 65 | if (!chunk) { | 73 | if (!chunk) { |
| 66 | const key = hot.getScriptId(path.resolve(sources[0])); | 74 | const key = hot.getScriptId(path.resolve(sources[sources.length - 1])); |
| 67 | route = "/js/" + key + ".js"; | 75 | route = "/js/" + key.replace(/\.client\.tsx?/, ".js"); |
| 68 | incr.put({ | 76 | incr.put({ |
| 69 | sources, | 77 | sources, |
| 70 | kind: "script", | 78 | kind: "script", |
| ... | @@ -123,27 +131,7 @@ export async function bundleServerJavaScript( | ... | @@ -123,27 +131,7 @@ export async function bundleServerJavaScript( |
| 123 | "$views": viewSource, | 131 | "$views": viewSource, |
| 124 | }), | 132 | }), |
| 125 | projectRelativeResolution(), | 133 | projectRelativeResolution(), |
| 126 | { | 134 | markoViaBuildCache(incr), |
| 127 | name: "marko via build cache", | ||
| 128 | setup(b) { | ||
| 129 | b.onLoad( | ||
| 130 | { filter: /\.marko$/ }, | ||
| 131 | async ({ path: file }) => { | ||
| 132 | const key = path.relative(hot.projectRoot, file) | ||
| 133 | .replaceAll("\\", "/"); | ||
| 134 | const cacheEntry = incr.out.serverMarko.get(key); | ||
| 135 | if (!cacheEntry) { | ||
| 136 | throw new Error("Marko file not in cache: " + file); | ||
| 137 | } | ||
| 138 | return ({ | ||
| 139 | loader: "ts", | ||
| 140 | contents: cacheEntry.src, | ||
| 141 | resolveDir: path.dirname(file), | ||
| 142 | }); | ||
| 143 | }, | ||
| 144 | ); | ||
| 145 | }, | ||
| 146 | }, | ||
| 147 | { | 135 | { |
| 148 | name: "replace client references", | 136 | name: "replace client references", |
| 149 | setup(b) { | 137 | setup(b) { |
| ... | @@ -168,6 +156,9 @@ export async function bundleServerJavaScript( | ... | @@ -168,6 +156,9 @@ export async function bundleServerJavaScript( |
| 168 | }, | 156 | }, |
| 169 | }, | 157 | }, |
| 170 | ]; | 158 | ]; |
| 159 | const pkg = await fs.readJson("package.json") as { | ||
| 160 | dependencies: Record<string, string>; | ||
| 161 | }; | ||
| 171 | const { metafile, outputFiles } = await esbuild.build({ | 162 | const { metafile, outputFiles } = await esbuild.build({ |
| 172 | bundle: true, | 163 | bundle: true, |
| 173 | chunkNames: "c.[hash]", | 164 | chunkNames: "c.[hash]", |
| ... | @@ -186,6 +177,8 @@ export async function bundleServerJavaScript( | ... | @@ -186,6 +177,8 @@ export async function bundleServerJavaScript( |
| 186 | jsx: "automatic", | 177 | jsx: "automatic", |
| 187 | jsxImportSource: "#ssr", | 178 | jsxImportSource: "#ssr", |
| 188 | jsxDev: false, | 179 | jsxDev: false, |
| 180 | external: Object.keys(pkg.dependencies) | ||
| 181 | .filter((x) => !x.startsWith("@paperclover")), | ||
| 189 | }); | 182 | }); |
| 190 | 183 | ||
| 191 | const files: Record<string, Buffer> = {}; | 184 | const files: Record<string, Buffer> = {}; |
| ... | @@ -279,6 +272,29 @@ export async function finalizeServerJavaScript( | ... | @@ -279,6 +272,29 @@ export async function finalizeServerJavaScript( |
| 279 | }); | 272 | }); |
| 280 | } | 273 | } |
| 281 | 274 | ||
| 275 | function markoViaBuildCache(incr: Incremental): esbuild.Plugin { | ||
| 276 | return { | ||
| 277 | name: "marko via build cache", | ||
| 278 | setup(b) { | ||
| 279 | b.onLoad( | ||
| 280 | { filter: /\.marko$/ }, | ||
| 281 | async ({ path: file }) => { | ||
| 282 | const key = path.relative(hot.projectRoot, file) | ||
| 283 | .replaceAll("\\", "/"); | ||
| 284 | const cacheEntry = incr.out.serverMarko.get(key); | ||
| 285 | if (!cacheEntry) { | ||
| 286 | throw new Error("Marko file not in cache: " + file); | ||
| 287 | } | ||
| 288 | return ({ | ||
| 289 | loader: "ts", | ||
| 290 | contents: cacheEntry.src, | ||
| 291 | resolveDir: path.dirname(file), | ||
| 292 | }); | ||
| 293 | }, | ||
| 294 | ); | ||
| 295 | }, | ||
| 296 | }; | ||
| 297 | } | ||
| 282 | import * as esbuild from "esbuild"; | 298 | import * as esbuild from "esbuild"; |
| 283 | import * as path from "node:path"; | 299 | import * as path from "node:path"; |
| 284 | import process from "node:process"; | 300 | import process from "node:process"; |
framework/engine/marko-runtime.ts+1-3| ... | @@ -52,7 +52,6 @@ export const dynamicTag = ( | ... | @@ -52,7 +52,6 @@ export const dynamicTag = ( |
| 52 | if (typeof tag === "function") { | 52 | if (typeof tag === "function") { |
| 53 | clover: { | 53 | clover: { |
| 54 | const unwrapped = (tag as any).unwrapped; | 54 | const unwrapped = (tag as any).unwrapped; |
| 55 | console.log({ tag, unwrapped }); | ||
| 56 | if (unwrapped) { | 55 | if (unwrapped) { |
| 57 | tag = unwrapped; | 56 | tag = unwrapped; |
| 58 | break clover; | 57 | break clover; |
| ... | @@ -130,7 +129,7 @@ export function escapeXML(input: unknown) { | ... | @@ -130,7 +129,7 @@ export function escapeXML(input: unknown) { |
| 130 | ) { | 129 | ) { |
| 131 | throw new Error( | 130 | throw new Error( |
| 132 | `Unexpected object in template placeholder: '` + | 131 | `Unexpected object in template placeholder: '` + |
| 133 | util.inspect({ name: "clover" }) + "'. " + | 132 | engine.inspect({ name: "clover" }) + "'. " + |
| 134 | `To emit a literal '[object Object]', use \${String(value)}`, | 133 | `To emit a literal '[object Object]', use \${String(value)}`, |
| 135 | ); | 134 | ); |
| 136 | } | 135 | } |
| ... | @@ -145,4 +144,3 @@ import * as engine from "./ssr.ts"; | ... | @@ -145,4 +144,3 @@ import * as engine from "./ssr.ts"; |
| 145 | import type { ServerRenderer } from "marko/html/template"; | 144 | import type { ServerRenderer } from "marko/html/template"; |
| 146 | import { type Accessor } from "marko/common/types"; | 145 | import { type Accessor } from "marko/common/types"; |
| 147 | import * as marko from "#marko/html"; | 146 | import * as marko from "#marko/html"; |
| 148 | import * as util from "node:util"; |
framework/generate.ts+9-6| ... | @@ -8,9 +8,9 @@ export function main() { | ... | @@ -8,9 +8,9 @@ export function main() { |
| 8 | successText, | 8 | successText, |
| 9 | failureText: () => "sitegen FAIL", | 9 | failureText: () => "sitegen FAIL", |
| 10 | }, async (spinner) => { | 10 | }, async (spinner) => { |
| 11 | const incr = Incremental.fromDisk(); | 11 | // const incr = Incremental.fromDisk(); |
| 12 | await incr.statAllFiles(); | 12 | // await incr.statAllFiles(); |
| 13 | // const incr = new Incremental(); | 13 | const incr = new Incremental(); |
| 14 | const result = await sitegen(spinner, incr); | 14 | const result = await sitegen(spinner, incr); |
| 15 | incr.toDisk(); // Allows picking up this state again | 15 | incr.toDisk(); // Allows picking up this state again |
| 16 | return result; | 16 | return result; |
| ... | @@ -122,7 +122,6 @@ export async function sitegen( | ... | @@ -122,7 +122,6 @@ export async function sitegen( |
| 122 | } | 122 | } |
| 123 | } | 123 | } |
| 124 | } | 124 | } |
| 125 | scripts = scripts.filter(({ file }) => !file.match(/\.client\.[tj]sx?/)); | ||
| 126 | const globalCssPath = join("global.css"); | 125 | const globalCssPath = join("global.css"); |
| 127 | 126 | ||
| 128 | // TODO: make sure that `static` and `pages` does not overlap | 127 | // TODO: make sure that `static` and `pages` does not overlap |
| ... | @@ -311,8 +310,12 @@ export async function sitegen( | ... | @@ -311,8 +310,12 @@ export async function sitegen( |
| 311 | maxJobs: 2, | 310 | maxJobs: 2, |
| 312 | }); | 311 | }); |
| 313 | viewQueue.addMany(neededViews); | 312 | viewQueue.addMany(neededViews); |
| 314 | await pageQueue.done({ method: "stop" }); | 313 | const pageAndViews = [ |
| 315 | await viewQueue.done({ method: "stop" }); | 314 | pageQueue.done({ method: "stop" }), |
| 315 | viewQueue.done({ method: "stop" }), | ||
| 316 | ]; | ||
| 317 | await Promise.allSettled(pageAndViews); | ||
| 318 | await Promise.all(pageAndViews); | ||
| 316 | status.format = spinnerFormat; | 319 | status.format = spinnerFormat; |
| 317 | 320 | ||
| 318 | // -- bundle server javascript (backend and views) -- | 321 | // -- bundle server javascript (backend and views) -- |
framework/hot.ts+5-1| ... | @@ -63,7 +63,7 @@ Module.prototype._compile = function ( | ... | @@ -63,7 +63,7 @@ Module.prototype._compile = function ( |
| 63 | if (shouldTrackPath(filename)) { | 63 | if (shouldTrackPath(filename)) { |
| 64 | const cssImportsMaybe: string[] = []; | 64 | const cssImportsMaybe: string[] = []; |
| 65 | const imports: string[] = []; | 65 | const imports: string[] = []; |
| 66 | for (const { filename: file } of this.children) { | 66 | for (const { filename: file, cloverClientRefs } of this.children) { |
| 67 | if (file.endsWith(".css")) cssImportsMaybe.push(file); | 67 | if (file.endsWith(".css")) cssImportsMaybe.push(file); |
| 68 | else { | 68 | else { |
| 69 | const child = fileStats.get(file); | 69 | const child = fileStats.get(file); |
| ... | @@ -71,6 +71,10 @@ Module.prototype._compile = function ( | ... | @@ -71,6 +71,10 @@ Module.prototype._compile = function ( |
| 71 | const { cssImportsRecursive } = child; | 71 | const { cssImportsRecursive } = child; |
| 72 | if (cssImportsRecursive) cssImportsMaybe.push(...cssImportsRecursive); | 72 | if (cssImportsRecursive) cssImportsMaybe.push(...cssImportsRecursive); |
| 73 | imports.push(file); | 73 | imports.push(file); |
| 74 | if (cloverClientRefs && cloverClientRefs.length > 0) { | ||
| 75 | (this.cloverClientRefs ??= []) | ||
| 76 | .push(...cloverClientRefs); | ||
| 77 | } | ||
| 74 | } | 78 | } |
| 75 | } | 79 | } |
| 76 | fileStats.set(filename, { | 80 | fileStats.set(filename, { |
framework/incremental.ts+1-1| ... | @@ -148,7 +148,7 @@ export class Incremental { | ... | @@ -148,7 +148,7 @@ export class Incremental { |
| 148 | for (const key of map.keys()) { | 148 | for (const key of map.keys()) { |
| 149 | if (!this.round.referenced.has(`${kind}\0${key}`)) { | 149 | if (!this.round.referenced.has(`${kind}\0${key}`)) { |
| 150 | unreferenced.push({ kind: kind as ArtifactKind, key }); | 150 | unreferenced.push({ kind: kind as ArtifactKind, key }); |
| 151 | this.out[kind as ArtifactKind].delete(key); | 151 | // this.out[kind as ArtifactKind].delete(key); |
| 152 | } | 152 | } |
| 153 | } | 153 | } |
| 154 | } | 154 | } |
framework/lib/assets.ts-2| ... | @@ -11,7 +11,6 @@ export async function reload() { | ... | @@ -11,7 +11,6 @@ export async function reload() { |
| 11 | fs.readFile(path.join(import.meta.dirname, "static.json"), "utf8"), | 11 | fs.readFile(path.join(import.meta.dirname, "static.json"), "utf8"), |
| 12 | fs.readFile(path.join(import.meta.dirname, "static.blob")), | 12 | fs.readFile(path.join(import.meta.dirname, "static.blob")), |
| 13 | ]); | 13 | ]); |
| 14 | console.log("new buffer loaded"); | ||
| 15 | assets = { | 14 | assets = { |
| 16 | map: JSON.parse(map), | 15 | map: JSON.parse(map), |
| 17 | buf, | 16 | buf, |
| ... | @@ -110,7 +109,6 @@ function assetInner(c: Context, asset: BuiltAsset, status: StatusCode) { | ... | @@ -110,7 +109,6 @@ function assetInner(c: Context, asset: BuiltAsset, status: StatusCode) { |
| 110 | } | 109 | } |
| 111 | 110 | ||
| 112 | process.on("message", (msg: any) => { | 111 | process.on("message", (msg: any) => { |
| 113 | console.log({ msg }); | ||
| 114 | if (msg?.type === "clover.assets.reload") reload(); | 112 | if (msg?.type === "clover.assets.reload") reload(); |
| 115 | }); | 113 | }); |
| 116 | 114 |
framework/watch.ts+2-2| ... | @@ -58,8 +58,8 @@ export async function main() { | ... | @@ -58,8 +58,8 @@ export async function main() { |
| 58 | successText: generate.successText, | 58 | successText: generate.successText, |
| 59 | failureText: () => "sitegen FAIL", | 59 | failureText: () => "sitegen FAIL", |
| 60 | }, async (spinner) => { | 60 | }, async (spinner) => { |
| 61 | console.log("---"); | 61 | console.info("---"); |
| 62 | console.log( | 62 | console.info( |
| 63 | "Updated" + | 63 | "Updated" + |
| 64 | (changed.length === 1 | 64 | (changed.length === 1 |
| 65 | ? " " + changed[0] | 65 | ? " " + changed[0] |
package-lock.json+152| ... | @@ -8,6 +8,8 @@ | ... | @@ -8,6 +8,8 @@ |
| 8 | "@hono/node-server": "^1.14.3", | 8 | "@hono/node-server": "^1.14.3", |
| 9 | "@mdx-js/mdx": "^3.1.0", | 9 | "@mdx-js/mdx": "^3.1.0", |
| 10 | "@paperclover/console": "git+https://git.paperclover.net/clo/console.git", | 10 | "@paperclover/console": "git+https://git.paperclover.net/clo/console.git", |
| 11 | "codemirror": "^6.0.1", | ||
| 12 | "devalue": "^5.1.1", | ||
| 11 | "esbuild": "^0.25.5", | 13 | "esbuild": "^0.25.5", |
| 12 | "hls.js": "^1.6.5", | 14 | "hls.js": "^1.6.5", |
| 13 | "hono": "^4.7.11", | 15 | "hono": "^4.7.11", |
| ... | @@ -404,6 +406,87 @@ | ... | @@ -404,6 +406,87 @@ |
| 404 | "node": ">=6.9.0" | 406 | "node": ">=6.9.0" |
| 405 | } | 407 | } |
| 406 | }, | 408 | }, |
| 409 | "node_modules/@codemirror/autocomplete": { | ||
| 410 | "version": "6.18.6", | ||
| 411 | "resolved": "https://registry.npmjs.org/@codemirror/autocomplete/-/autocomplete-6.18.6.tgz", | ||
| 412 | "integrity": "sha512-PHHBXFomUs5DF+9tCOM/UoW6XQ4R44lLNNhRaW9PKPTU0D7lIjRg3ElxaJnTwsl/oHiR93WSXDBrekhoUGCPtg==", | ||
| 413 | "license": "MIT", | ||
| 414 | "dependencies": { | ||
| 415 | "@codemirror/language": "^6.0.0", | ||
| 416 | "@codemirror/state": "^6.0.0", | ||
| 417 | "@codemirror/view": "^6.17.0", | ||
| 418 | "@lezer/common": "^1.0.0" | ||
| 419 | } | ||
| 420 | }, | ||
| 421 | "node_modules/@codemirror/commands": { | ||
| 422 | "version": "6.8.1", | ||
| 423 | "resolved": "https://registry.npmjs.org/@codemirror/commands/-/commands-6.8.1.tgz", | ||
| 424 | "integrity": "sha512-KlGVYufHMQzxbdQONiLyGQDUW0itrLZwq3CcY7xpv9ZLRHqzkBSoteocBHtMCoY7/Ci4xhzSrToIeLg7FxHuaw==", | ||
| 425 | "license": "MIT", | ||
| 426 | "dependencies": { | ||
| 427 | "@codemirror/language": "^6.0.0", | ||
| 428 | "@codemirror/state": "^6.4.0", | ||
| 429 | "@codemirror/view": "^6.27.0", | ||
| 430 | "@lezer/common": "^1.1.0" | ||
| 431 | } | ||
| 432 | }, | ||
| 433 | "node_modules/@codemirror/language": { | ||
| 434 | "version": "6.11.1", | ||
| 435 | "resolved": "https://registry.npmjs.org/@codemirror/language/-/language-6.11.1.tgz", | ||
| 436 | "integrity": "sha512-5kS1U7emOGV84vxC+ruBty5sUgcD0te6dyupyRVG2zaSjhTDM73LhVKUtVwiqSe6QwmEoA4SCiU8AKPFyumAWQ==", | ||
| 437 | "license": "MIT", | ||
| 438 | "dependencies": { | ||
| 439 | "@codemirror/state": "^6.0.0", | ||
| 440 | "@codemirror/view": "^6.23.0", | ||
| 441 | "@lezer/common": "^1.1.0", | ||
| 442 | "@lezer/highlight": "^1.0.0", | ||
| 443 | "@lezer/lr": "^1.0.0", | ||
| 444 | "style-mod": "^4.0.0" | ||
| 445 | } | ||
| 446 | }, | ||
| 447 | "node_modules/@codemirror/lint": { | ||
| 448 | "version": "6.8.5", | ||
| 449 | "resolved": "https://registry.npmjs.org/@codemirror/lint/-/lint-6.8.5.tgz", | ||
| 450 | "integrity": "sha512-s3n3KisH7dx3vsoeGMxsbRAgKe4O1vbrnKBClm99PU0fWxmxsx5rR2PfqQgIt+2MMJBHbiJ5rfIdLYfB9NNvsA==", | ||
| 451 | "license": "MIT", | ||
| 452 | "dependencies": { | ||
| 453 | "@codemirror/state": "^6.0.0", | ||
| 454 | "@codemirror/view": "^6.35.0", | ||
| 455 | "crelt": "^1.0.5" | ||
| 456 | } | ||
| 457 | }, | ||
| 458 | "node_modules/@codemirror/search": { | ||
| 459 | "version": "6.5.11", | ||
| 460 | "resolved": "https://registry.npmjs.org/@codemirror/search/-/search-6.5.11.tgz", | ||
| 461 | "integrity": "sha512-KmWepDE6jUdL6n8cAAqIpRmLPBZ5ZKnicE8oGU/s3QrAVID+0VhLFrzUucVKHG5035/BSykhExDL/Xm7dHthiA==", | ||
| 462 | "license": "MIT", | ||
| 463 | "dependencies": { | ||
| 464 | "@codemirror/state": "^6.0.0", | ||
| 465 | "@codemirror/view": "^6.0.0", | ||
| 466 | "crelt": "^1.0.5" | ||
| 467 | } | ||
| 468 | }, | ||
| 469 | "node_modules/@codemirror/state": { | ||
| 470 | "version": "6.5.2", | ||
| 471 | "resolved": "https://registry.npmjs.org/@codemirror/state/-/state-6.5.2.tgz", | ||
| 472 | "integrity": "sha512-FVqsPqtPWKVVL3dPSxy8wEF/ymIEuVzF1PK3VbUgrxXpJUSHQWWZz4JMToquRxnkw+36LTamCZG2iua2Ptq0fA==", | ||
| 473 | "license": "MIT", | ||
| 474 | "dependencies": { | ||
| 475 | "@marijn/find-cluster-break": "^1.0.0" | ||
| 476 | } | ||
| 477 | }, | ||
| 478 | "node_modules/@codemirror/view": { | ||
| 479 | "version": "6.37.2", | ||
| 480 | "resolved": "https://registry.npmjs.org/@codemirror/view/-/view-6.37.2.tgz", | ||
| 481 | "integrity": "sha512-XD3LdgQpxQs5jhOOZ2HRVT+Rj59O4Suc7g2ULvZ+Yi8eCkickrkZ5JFuoDhs2ST1mNI5zSsNYgR3NGa4OUrbnw==", | ||
| 482 | "license": "MIT", | ||
| 483 | "dependencies": { | ||
| 484 | "@codemirror/state": "^6.5.0", | ||
| 485 | "crelt": "^1.0.6", | ||
| 486 | "style-mod": "^4.1.0", | ||
| 487 | "w3c-keyname": "^2.2.4" | ||
| 488 | } | ||
| 489 | }, | ||
| 407 | "node_modules/@esbuild/aix-ppc64": { | 490 | "node_modules/@esbuild/aix-ppc64": { |
| 408 | "version": "0.25.5", | 491 | "version": "0.25.5", |
| 409 | "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.5.tgz", | 492 | "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.5.tgz", |
| ... | @@ -864,6 +947,30 @@ | ... | @@ -864,6 +947,30 @@ |
| 864 | "@jridgewell/sourcemap-codec": "^1.4.14" | 947 | "@jridgewell/sourcemap-codec": "^1.4.14" |
| 865 | } | 948 | } |
| 866 | }, | 949 | }, |
| 950 | "node_modules/@lezer/common": { | ||
| 951 | "version": "1.2.3", | ||
| 952 | "resolved": "https://registry.npmjs.org/@lezer/common/-/common-1.2.3.tgz", | ||
| 953 | "integrity": "sha512-w7ojc8ejBqr2REPsWxJjrMFsA/ysDCFICn8zEOR9mrqzOu2amhITYuLD8ag6XZf0CFXDrhKqw7+tW8cX66NaDA==", | ||
| 954 | "license": "MIT" | ||
| 955 | }, | ||
| 956 | "node_modules/@lezer/highlight": { | ||
| 957 | "version": "1.2.1", | ||
| 958 | "resolved": "https://registry.npmjs.org/@lezer/highlight/-/highlight-1.2.1.tgz", | ||
| 959 | "integrity": "sha512-Z5duk4RN/3zuVO7Jq0pGLJ3qynpxUVsh7IbUbGj88+uV2ApSAn6kWg2au3iJb+0Zi7kKtqffIESgNcRXWZWmSA==", | ||
| 960 | "license": "MIT", | ||
| 961 | "dependencies": { | ||
| 962 | "@lezer/common": "^1.0.0" | ||
| 963 | } | ||
| 964 | }, | ||
| 965 | "node_modules/@lezer/lr": { | ||
| 966 | "version": "1.4.2", | ||
| 967 | "resolved": "https://registry.npmjs.org/@lezer/lr/-/lr-1.4.2.tgz", | ||
| 968 | "integrity": "sha512-pu0K1jCIdnQ12aWNaAVU5bzi7Bd1w54J3ECgANPmYLtQKP0HBj2cE/5coBD66MT10xbtIuUr7tg0Shbsvk0mDA==", | ||
| 969 | "license": "MIT", | ||
| 970 | "dependencies": { | ||
| 971 | "@lezer/common": "^1.0.0" | ||
| 972 | } | ||
| 973 | }, | ||
| 867 | "node_modules/@luxass/strip-json-comments": { | 974 | "node_modules/@luxass/strip-json-comments": { |
| 868 | "version": "1.4.0", | 975 | "version": "1.4.0", |
| 869 | "resolved": "https://registry.npmjs.org/@luxass/strip-json-comments/-/strip-json-comments-1.4.0.tgz", | 976 | "resolved": "https://registry.npmjs.org/@luxass/strip-json-comments/-/strip-json-comments-1.4.0.tgz", |
| ... | @@ -873,6 +980,12 @@ | ... | @@ -873,6 +980,12 @@ |
| 873 | "node": ">=18" | 980 | "node": ">=18" |
| 874 | } | 981 | } |
| 875 | }, | 982 | }, |
| 983 | "node_modules/@marijn/find-cluster-break": { | ||
| 984 | "version": "1.0.2", | ||
| 985 | "resolved": "https://registry.npmjs.org/@marijn/find-cluster-break/-/find-cluster-break-1.0.2.tgz", | ||
| 986 | "integrity": "sha512-l0h88YhZFyKdXIFNfSWpyjStDjGHwZ/U7iobcK1cQQD8sejsONdQtTVU+1wVN1PBw40PiiHB1vA5S7VTfQiP9g==", | ||
| 987 | "license": "MIT" | ||
| 988 | }, | ||
| 876 | "node_modules/@marko/compiler": { | 989 | "node_modules/@marko/compiler": { |
| 877 | "version": "5.39.21", | 990 | "version": "5.39.21", |
| 878 | "resolved": "https://registry.npmjs.org/@marko/compiler/-/compiler-5.39.21.tgz", | 991 | "resolved": "https://registry.npmjs.org/@marko/compiler/-/compiler-5.39.21.tgz", |
| ... | @@ -1458,6 +1571,21 @@ | ... | @@ -1458,6 +1571,21 @@ |
| 1458 | "node": ">=8" | 1571 | "node": ">=8" |
| 1459 | } | 1572 | } |
| 1460 | }, | 1573 | }, |
| 1574 | "node_modules/codemirror": { | ||
| 1575 | "version": "6.0.1", | ||
| 1576 | "resolved": "https://registry.npmjs.org/codemirror/-/codemirror-6.0.1.tgz", | ||
| 1577 | "integrity": "sha512-J8j+nZ+CdWmIeFIGXEFbFPtpiYacFMDR8GlHK3IyHQJMCaVRfGx9NT+Hxivv1ckLWPvNdZqndbr/7lVhrf/Svg==", | ||
| 1578 | "license": "MIT", | ||
| 1579 | "dependencies": { | ||
| 1580 | "@codemirror/autocomplete": "^6.0.0", | ||
| 1581 | "@codemirror/commands": "^6.0.0", | ||
| 1582 | "@codemirror/language": "^6.0.0", | ||
| 1583 | "@codemirror/lint": "^6.0.0", | ||
| 1584 | "@codemirror/search": "^6.0.0", | ||
| 1585 | "@codemirror/state": "^6.0.0", | ||
| 1586 | "@codemirror/view": "^6.0.0" | ||
| 1587 | } | ||
| 1588 | }, | ||
| 1461 | "node_modules/collapse-white-space": { | 1589 | "node_modules/collapse-white-space": { |
| 1462 | "version": "2.1.0", | 1590 | "version": "2.1.0", |
| 1463 | "resolved": "https://registry.npmjs.org/collapse-white-space/-/collapse-white-space-2.1.0.tgz", | 1591 | "resolved": "https://registry.npmjs.org/collapse-white-space/-/collapse-white-space-2.1.0.tgz", |
| ... | @@ -1537,6 +1665,12 @@ | ... | @@ -1537,6 +1665,12 @@ |
| 1537 | } | 1665 | } |
| 1538 | } | 1666 | } |
| 1539 | }, | 1667 | }, |
| 1668 | "node_modules/crelt": { | ||
| 1669 | "version": "1.0.6", | ||
| 1670 | "resolved": "https://registry.npmjs.org/crelt/-/crelt-1.0.6.tgz", | ||
| 1671 | "integrity": "sha512-VQ2MBenTq1fWZUH9DJNGti7kKv6EeAuYr3cLwxUWhIu1baTaXh4Ib5W2CqHVqib4/MqbYGJqiL3Zb8GJZr3l4g==", | ||
| 1672 | "license": "MIT" | ||
| 1673 | }, | ||
| 1540 | "node_modules/csstype": { | 1674 | "node_modules/csstype": { |
| 1541 | "version": "3.1.3", | 1675 | "version": "3.1.3", |
| 1542 | "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz", | 1676 | "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz", |
| ... | @@ -1605,6 +1739,12 @@ | ... | @@ -1605,6 +1739,12 @@ |
| 1605 | "node": ">=6" | 1739 | "node": ">=6" |
| 1606 | } | 1740 | } |
| 1607 | }, | 1741 | }, |
| 1742 | "node_modules/devalue": { | ||
| 1743 | "version": "5.1.1", | ||
| 1744 | "resolved": "https://registry.npmjs.org/devalue/-/devalue-5.1.1.tgz", | ||
| 1745 | "integrity": "sha512-maua5KUiapvEwiEAe+XnlZ3Rh0GD+qI1J/nb9vrJc3muPXvcF/8gXYTWF76+5DAqHyDUtOIImEuo0YKE9mshVw==", | ||
| 1746 | "license": "MIT" | ||
| 1747 | }, | ||
| 1608 | "node_modules/devlop": { | 1748 | "node_modules/devlop": { |
| 1609 | "version": "1.1.0", | 1749 | "version": "1.1.0", |
| 1610 | "resolved": "https://registry.npmjs.org/devlop/-/devlop-1.1.0.tgz", | 1750 | "resolved": "https://registry.npmjs.org/devlop/-/devlop-1.1.0.tgz", |
| ... | @@ -3748,6 +3888,12 @@ | ... | @@ -3748,6 +3888,12 @@ |
| 3748 | "url": "https://github.com/chalk/strip-ansi?sponsor=1" | 3888 | "url": "https://github.com/chalk/strip-ansi?sponsor=1" |
| 3749 | } | 3889 | } |
| 3750 | }, | 3890 | }, |
| 3891 | "node_modules/style-mod": { | ||
| 3892 | "version": "4.1.2", | ||
| 3893 | "resolved": "https://registry.npmjs.org/style-mod/-/style-mod-4.1.2.tgz", | ||
| 3894 | "integrity": "sha512-wnD1HyVqpJUI2+eKZ+eo1UwghftP6yuFheBqqe+bWCotBjC2K1YnteJILRMs3SM4V/0dLEW1SC27MWP5y+mwmw==", | ||
| 3895 | "license": "MIT" | ||
| 3896 | }, | ||
| 3751 | "node_modules/style-to-js": { | 3897 | "node_modules/style-to-js": { |
| 3752 | "version": "1.1.16", | 3898 | "version": "1.1.16", |
| 3753 | "resolved": "https://registry.npmjs.org/style-to-js/-/style-to-js-1.1.16.tgz", | 3899 | "resolved": "https://registry.npmjs.org/style-to-js/-/style-to-js-1.1.16.tgz", |
| ... | @@ -4020,6 +4166,12 @@ | ... | @@ -4020,6 +4166,12 @@ |
| 4020 | "url": "https://opencollective.com/unified" | 4166 | "url": "https://opencollective.com/unified" |
| 4021 | } | 4167 | } |
| 4022 | }, | 4168 | }, |
| 4169 | "node_modules/w3c-keyname": { | ||
| 4170 | "version": "2.2.8", | ||
| 4171 | "resolved": "https://registry.npmjs.org/w3c-keyname/-/w3c-keyname-2.2.8.tgz", | ||
| 4172 | "integrity": "sha512-dpojBhNsCNN7T82Tm7k26A6G9ML3NkhDsnw9n/eoxSRlVBB4CEtIQ/KTCLI2Fwf3ataSXRhYFkQi3SlnFwPvPQ==", | ||
| 4173 | "license": "MIT" | ||
| 4174 | }, | ||
| 4023 | "node_modules/wrap-ansi": { | 4175 | "node_modules/wrap-ansi": { |
| 4024 | "version": "7.0.0", | 4176 | "version": "7.0.0", |
| 4025 | "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", | 4177 | "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", |
package.json+2| ... | @@ -4,6 +4,8 @@ | ... | @@ -4,6 +4,8 @@ |
| 4 | "@hono/node-server": "^1.14.3", | 4 | "@hono/node-server": "^1.14.3", |
| 5 | "@mdx-js/mdx": "^3.1.0", | 5 | "@mdx-js/mdx": "^3.1.0", |
| 6 | "@paperclover/console": "git+https://git.paperclover.net/clo/console.git", | 6 | "@paperclover/console": "git+https://git.paperclover.net/clo/console.git", |
| 7 | "codemirror": "^6.0.1", | ||
| 8 | "devalue": "^5.1.1", | ||
| 7 | "esbuild": "^0.25.5", | 9 | "esbuild": "^0.25.5", |
| 8 | "hls.js": "^1.6.5", | 10 | "hls.js": "^1.6.5", |
| 9 | "hono": "^4.7.11", | 11 | "hono": "^4.7.11", |
src/q+a/backend.ts+4-9| ... | @@ -127,9 +127,9 @@ app.get("/q+a/:id", async (c, next) => { | ... | @@ -127,9 +127,9 @@ app.get("/q+a/:id", async (c, next) => { |
| 127 | const question = Question.getByDate(timestamp); | 127 | const question = Question.getByDate(timestamp); |
| 128 | if (!question) return next(); | 128 | if (!question) return next(); |
| 129 | 129 | ||
| 130 | // if (image) { | 130 | if (image) { |
| 131 | // return getQuestionImage(question, c.req.method === "HEAD"); | 131 | return getQuestionImage(question, c.req.method === "HEAD"); |
| 132 | // } | 132 | } |
| 133 | return renderView(c, "q+a/permalink", { question }); | 133 | return renderView(c, "q+a/permalink", { question }); |
| 134 | }); | 134 | }); |
| 135 | 135 | ||
| ... | @@ -193,11 +193,6 @@ app.get("/q+a/things/random", async (c) => { | ... | @@ -193,11 +193,6 @@ app.get("/q+a/things/random", async (c) => { |
| 193 | c.res = await renderView(c, "q+a/things-random", {}); | 193 | c.res = await renderView(c, "q+a/things-random", {}); |
| 194 | }); | 194 | }); |
| 195 | 195 | ||
| 196 | // 404 | ||
| 197 | app.get("/q+a/*", async (c) => { | ||
| 198 | return serveAsset(c, "/q+a/404", 404); | ||
| 199 | }); | ||
| 200 | |||
| 201 | async function questionFailure( | 196 | async function questionFailure( |
| 202 | c: Context, | 197 | c: Context, |
| 203 | status: ContentfulStatusCode, | 198 | status: ContentfulStatusCode, |
| ... | @@ -229,5 +224,5 @@ import { | ... | @@ -229,5 +224,5 @@ import { |
| 229 | } from "./models/PendingQuestion.ts"; | 224 | } from "./models/PendingQuestion.ts"; |
| 230 | import { Question, QuestionType } from "./models/Question.ts"; | 225 | import { Question, QuestionType } from "./models/Question.ts"; |
| 231 | import { renderView } from "#sitegen/view"; | 226 | import { renderView } from "#sitegen/view"; |
| 232 | // import { getQuestionImage } from "./question_image"; | 227 | import { getQuestionImage } from "./image.tsx"; |
| 233 | import { formatQuestionId, questionIdToTimestamp } from "./format.ts"; | 228 | import { formatQuestionId, questionIdToTimestamp } from "./format.ts"; |
src/q+a/clover-markdown.tsx+1-1| ... | @@ -211,7 +211,7 @@ function renderNode(node: string | ASTNode | ASTNode[]): any { | ... | @@ -211,7 +211,7 @@ function renderNode(node: string | ASTNode | ASTNode[]): any { |
| 211 | if (typeof node === "string") { | 211 | if (typeof node === "string") { |
| 212 | return node; | 212 | return node; |
| 213 | } else if (Array.isArray(node)) { | 213 | } else if (Array.isArray(node)) { |
| 214 | return node.flatMap((item) => renderNode(item)); | 214 | return node.map((item) => renderNode(item)); |
| 215 | } else if (node.type === "text") { | 215 | } else if (node.type === "text") { |
| 216 | return node.content; | 216 | return node.content; |
| 217 | } else { | 217 | } else { |
src/q+a/header.client.ts+8| ... | @@ -1,4 +1,12 @@ | ... | @@ -1,4 +1,12 @@ |
| 1 | const h1 = document.querySelector("h1")!; | 1 | const h1 = document.querySelector("h1")!; |
| 2 | const key = "net.paperclover.q+a.header"; | ||
| 3 | const state = localStorage?.getItem(key); | ||
| 4 | if (state === "detrevni ton") h1.classList.toggle("invert"); | ||
| 2 | h1.addEventListener("click", () => { | 5 | h1.addEventListener("click", () => { |
| 3 | h1.classList.toggle("invert"); | 6 | h1.classList.toggle("invert"); |
| 7 | localStorage?.setItem( | ||
| 8 | key, | ||
| 9 | (localStorage.getItem?.(key) ?? "not inverted") | ||
| 10 | .split("").reverse().join(""), | ||
| 11 | ); | ||
| 4 | }); | 12 | }); |
src/q+a/image.tsx+1-2| ... | @@ -5,14 +5,13 @@ const cacheImageDir = path.resolve(".clover/question_images"); | ... | @@ -5,14 +5,13 @@ const cacheImageDir = path.resolve(".clover/question_images"); |
| 5 | const getBrowser = RefCountedExpirable( | 5 | const getBrowser = RefCountedExpirable( |
| 6 | () => | 6 | () => |
| 7 | puppeteer.launch({ | 7 | puppeteer.launch({ |
| 8 | // headless: false, | ||
| 9 | args: ["--no-sandbox", "--disable-setuid-sandbox"], | 8 | args: ["--no-sandbox", "--disable-setuid-sandbox"], |
| 10 | }), | 9 | }), |
| 11 | (b) => b.close(), | 10 | (b) => b.close(), |
| 12 | ); | 11 | ); |
| 13 | 12 | ||
| 14 | export async function renderQuestionImage(question: Question) { | 13 | export async function renderQuestionImage(question: Question) { |
| 15 | const html = await renderViewToString("q+a/embed-image", { question }); | 14 | const html = await renderViewToString("q+a/image-embed", { question }); |
| 16 | 15 | ||
| 17 | // this browser session will be reused if multiple images are generated | 16 | // this browser session will be reused if multiple images are generated |
| 18 | // either at the same time or within a 5-minute time span. the dispose | 17 | // either at the same time or within a 5-minute time span. the dispose |
src/q+a/pages/admin.q+a.marko+3| ... | @@ -6,6 +6,9 @@ export const meta: Metadata = { | ... | @@ -6,6 +6,9 @@ export const meta: Metadata = { |
| 6 | title: "paper clover q+a", | 6 | title: "paper clover q+a", |
| 7 | description: "ask clover a question", | 7 | description: "ask clover a question", |
| 8 | }; | 8 | }; |
| 9 | export const regenerate = { | ||
| 10 | manual: true, | ||
| 11 | }; | ||
| 9 | 12 | ||
| 10 | <const/inboxSize = PendingQuestion.getAll().length /> | 13 | <const/inboxSize = PendingQuestion.getAll().length /> |
| 11 | <if=(inboxSize > 0)> | 14 | <if=(inboxSize > 0)> |
src/q+a/pages/q+a.404.marko+1| ... | @@ -4,6 +4,7 @@ export const meta: Metadata = { | ... | @@ -4,6 +4,7 @@ export const meta: Metadata = { |
| 4 | }; | 4 | }; |
| 5 | 5 | ||
| 6 | <h2>sound the alarms</h2> | 6 | <h2>sound the alarms</h2> |
| 7 | <p>this page doesn't exist</p> | ||
| 7 | <p> | 8 | <p> |
| 8 | <a href="/q+a">return to the questions list</a> | 9 | <a href="/q+a">return to the questions list</a> |
| 9 | </p> | 10 | </p> |
src/q+a/pages/q+a.marko+4-1| ... | @@ -1,4 +1,7 @@ | ... | @@ -1,4 +1,7 @@ |
| 1 | export * as layout from "../layout.tsx"; | 1 | export * as layout from "../layout.tsx"; |
| 2 | export const regenerate = { | ||
| 3 | manual: true, | ||
| 4 | }; | ||
| 2 | 5 | ||
| 3 | export interface Input { | 6 | export interface Input { |
| 4 | admin?: boolean; | 7 | admin?: boolean; |
| ... | @@ -11,7 +14,7 @@ export const meta: Metadata = { | ... | @@ -11,7 +14,7 @@ export const meta: Metadata = { |
| 11 | <const/{ admin = false } = input /> | 14 | <const/{ admin = false } = input /> |
| 12 | <const/questions = [...Question.getAll()] /> | 15 | <const/questions = [...Question.getAll()] /> |
| 13 | 16 | ||
| 14 | <if=!admin> | 17 | <if=true> |
| 15 | <question-form /> | 18 | <question-form /> |
| 16 | </> | 19 | </> |
| 17 | <for|question| of=questions> | 20 | <for|question| of=questions> |
src/q+a/scripts/editor.client.tsx+6-5| ... | @@ -1,9 +1,8 @@ | ... | @@ -1,9 +1,8 @@ |
| 1 | import { basicSetup, EditorState, EditorView } from "codemirror"; | 1 | import { EditorState } from "@codemirror/state"; |
| 2 | import { basicSetup, EditorView } from "codemirror"; | ||
| 2 | import { ssrSync } from "#ssr"; | 3 | import { ssrSync } from "#ssr"; |
| 3 | // @ts-ignore | 4 | import { ScriptPayload } from "@/q+a/view/editor.marko"; |
| 4 | import type { ScriptPayload } from "../views/editor.marko"; | 5 | import QuestionRender from "@/q+a/tags/question.marko"; |
| 5 | // @ts-ignore | ||
| 6 | import QuestionRender from "@/q+a/components/Question.marko"; | ||
| 7 | 6 | ||
| 8 | declare const payload: ScriptPayload; | 7 | declare const payload: ScriptPayload; |
| 9 | const date = new Date(payload.date); | 8 | const date = new Date(payload.date); |
| ... | @@ -21,9 +20,11 @@ function updatePreview(text: string) { | ... | @@ -21,9 +20,11 @@ function updatePreview(text: string) { |
| 21 | type: payload.type, | 20 | type: payload.type, |
| 22 | date, | 21 | date, |
| 23 | }} | 22 | }} |
| 23 | editor | ||
| 24 | />, | 24 | />, |
| 25 | ).text; | 25 | ).text; |
| 26 | } | 26 | } |
| 27 | updatePreview(payload.text); | ||
| 27 | 28 | ||
| 28 | const startState = EditorState.create({ | 29 | const startState = EditorState.create({ |
| 29 | doc: payload.text, | 30 | doc: payload.text, |
src/q+a/tags/question.marko+6-2| ... | @@ -2,12 +2,13 @@ | ... | @@ -2,12 +2,13 @@ |
| 2 | export interface Input { | 2 | export interface Input { |
| 3 | question: Question; | 3 | question: Question; |
| 4 | admin?: boolean; | 4 | admin?: boolean; |
| 5 | editor?: boolean; | ||
| 5 | } | 6 | } |
| 6 | 7 | ||
| 7 | // 2024-12-31 05:00:00 EST | 8 | // 2024-12-31 05:00:00 EST |
| 8 | export const transitionDate = 1735639200000; | 9 | export const transitionDate = 1735639200000; |
| 9 | 10 | ||
| 10 | <const/{ question, admin } = input /> | 11 | <const/{ question, admin = false, editor = false } = input /> |
| 11 | <const/{ id, date } = question/> | 12 | <const/{ id, date } = question/> |
| 12 | 13 | ||
| 13 | <${"e-"} | 14 | <${"e-"} |
| ... | @@ -30,8 +31,11 @@ export const transitionDate = 1735639200000; | ... | @@ -30,8 +31,11 @@ export const transitionDate = 1735639200000; |
| 30 | </> | 31 | </> |
| 31 | 32 | ||
| 32 | // this singleton script will make all the '<time>' tags clickable. | 33 | // this singleton script will make all the '<time>' tags clickable. |
| 33 | client import "./clickable-links.client.ts"; | 34 | <if=!editor> |
| 35 | client import "./clickable-links.client.ts"; | ||
| 36 | </> | ||
| 34 | 37 | ||
| 35 | import type { Question } from "@/q+a/models/Question.ts"; | 38 | import type { Question } from "@/q+a/models/Question.ts"; |
| 36 | import { formatQuestionTimestamp, formatQuestionISOTimestamp } from "@/q+a/format.ts"; | 39 | import { formatQuestionTimestamp, formatQuestionISOTimestamp } from "@/q+a/format.ts"; |
| 37 | import { CloverMarkdown } from "@/q+a/clover-markdown.tsx"; | 40 | import { CloverMarkdown } from "@/q+a/clover-markdown.tsx"; |
| 41 | import { addScript as Script } from "#sitegen" |
src/q+a/views/editor.marko+4-2| ... | @@ -56,10 +56,12 @@ export interface ScriptPayload { | ... | @@ -56,10 +56,12 @@ export interface ScriptPayload { |
| 56 | </div> | 56 | </div> |
| 57 | <div#editor /> | 57 | <div#editor /> |
| 58 | <main#preview> | 58 | <main#preview> |
| 59 | <question ...{question} /> | 59 | <question editor ...{ question } /> |
| 60 | </main> | 60 | </main> |
| 61 | </div> | 61 | </div> |
| 62 | <html-script src="/js/edit_frontend.js" type="module" /> | 62 | <html-script>self.payload=${devalue.uneval(payload)}</html-script> |
| 63 | <html-script src="/js/q+a/scripts/editor.js" type="module" /> | ||
| 63 | 64 | ||
| 65 | import * as devalue from 'devalue'; | ||
| 64 | import { type PendingQuestion } from "@/q+a/models/PendingQuestion.ts"; | 66 | import { type PendingQuestion } from "@/q+a/models/PendingQuestion.ts"; |
| 65 | import { Question, QuestionType } from "@/q+a/models/Question.ts"; | 67 | import { Question, QuestionType } from "@/q+a/models/Question.ts"; |
src/q+a/views/permalink.marko+6-4| ... | @@ -1,23 +1,25 @@ | ... | @@ -1,23 +1,25 @@ |
| 1 | export * as layout from "@/q+a/layout.tsx"; | ||
| 1 | export interface Input { | 2 | export interface Input { |
| 2 | question: Question; | 3 | question: Question; |
| 3 | } | 4 | } |
| 4 | 5 | ||
| 5 | server export function meta({ context: { req }, question }) { | 6 | server export function meta({ context, question }) { |
| 6 | const isDiscord = req.get("user-agent") | 7 | const isDiscord = context.get("user-agent") |
| 7 | ?.toLowerCase() | 8 | ?.toLowerCase() |
| 8 | .includes("discordbot"); | 9 | .includes("discordbot"); |
| 9 | if (question.type === QuestionType.normal) { | 10 | if (question.type === QuestionType.normal) { |
| 10 | return { | 11 | return { |
| 11 | title: "question permalink", | 12 | title: "question permalink", |
| 12 | openGraph: { | 13 | openGraph: { |
| 13 | images: [{ url: `https://paperclover.net/q+a/${q.id}.png` }], | 14 | images: [{ url: `https://paperclover.net/q+a/${question.id}.png` }], |
| 14 | }, | 15 | }, |
| 15 | twitter: { card: "summary_large_image" }, | 16 | twitter: { card: "summary_large_image" }, |
| 16 | themeColor: isDiscord | 17 | themeColor: isDiscord |
| 17 | ? q.date.getTime() > transitionDate ? "#8c78ff" : "#58ff71" | 18 | ? question.date.getTime() > transitionDate ? "#8c78ff" : "#58ff71" |
| 18 | : undefined, | 19 | : undefined, |
| 19 | }; | 20 | }; |
| 20 | } | 21 | } |
| 22 | return { title: 'question permalink' }; | ||
| 21 | } | 23 | } |
| 22 | 24 | ||
| 23 | <const/{ question }=input/> | 25 | <const/{ question }=input/> |