diff --git a/build/build.ts b/build/build.ts index 1ae3f65..d07b70e 100644 --- a/build/build.ts +++ b/build/build.ts @@ -110,12 +110,28 @@ const inlineDepsPlugin = { const deps = [ "parsimmon", "espresso-iisojs", - "codemirror", + "@codemirror", "mithril", "yaml", ]; + const depFiles = new Set(); build.onResolve({ filter: /^[^.]/ }, async (args) => { - if (deps.some((d) => args.path.startsWith(d))) return undefined; + if (args.pluginData === "inlineDeps") return undefined; + if ( + depFiles.has(args.importer) || + deps.some((d) => args.path.startsWith(d)) + ) { + const res = await build.resolve(args.path, { + importer: args.importer, + namespace: args.namespace, + resolveDir: args.resolveDir, + kind: args.kind, + with: args.with, + pluginData: "inlineDeps", + }); + depFiles.add(res.path); + return res; + } return { sideEffects: false, external: true }; }); }, @@ -226,6 +242,16 @@ async function copyStatic(): Promise { } async function generateCss(): Promise { + const tailwindPlugin = { + name: "tailwind", + setup(build) { + build.onLoad({ filter: /\/ui\/css\/app.css$/ }, async (args) => { + const res = await execAsync(`npx @tailwindcss/cli -i ${args.path}`); + return { loader: "css", contents: res.stdout }; + }); + }, + } as esbuild.Plugin; + const res = await esbuild.build({ bundle: true, absWorkingDir: INPUT_DIR, @@ -235,7 +261,11 @@ async function generateCss(): Promise { entryPoints: ["ui/css/app.css"], entryNames: "[dir]/[name]-[hash]", outfile: path.join(OUTPUT_DIR, "public/app.css"), - target: ["chrome109", "safari15.6", "firefox115", "opera102", "edge118"], + plugins: [tailwindPlugin], + loader: { + ".woff2": "dataurl", + }, + target: ["chrome111", "safari16.4", "firefox128"], metafile: true, }); @@ -293,7 +323,7 @@ async function generateFrontendJs(): Promise { sourcesContent: false, platform: "browser", format: "esm", - target: ["chrome109", "safari15.6", "firefox115", "opera102", "edge118"], + target: ["chrome111", "safari16.4", "firefox128"], entryPoints: ["ui/app.ts"], entryNames: "[dir]/[name]-[hash]", outdir: path.join(OUTPUT_DIR, "public"), diff --git a/build/generate-fonts.sh b/build/generate-fonts.sh new file mode 100755 index 0000000..81256ca --- /dev/null +++ b/build/generate-fonts.sh @@ -0,0 +1,18 @@ +#!/bin/bash + +cd "$(dirname "$0")" + +declare -A FONTS=( + [InterVariable]=https://rsms.me/inter/font-files/InterVariable.woff2 + [InterVariable-Italic]=https://rsms.me/inter/font-files/InterVariable-Italic.woff2 + [RobotoMono]=https://raw.githubusercontent.com/googlefonts/RobotoMono/main/fonts/variable/RobotoMono%5Bwght%5D.ttf +) + +for name in "${!FONTS[@]}" +do + url="${FONTS[$name]}" + echo "Downloading $url" + TMP=$(mktemp) + curl "$url" -s --output "$TMP" + pyftsubset "$TMP" --output-file="../ui/css/$name.woff2" --flavor=woff2 --unicodes="U+0000-00FF,U+0131,U+0152-0153,U+02BB-02BC,U+02C6,U+02DA,U+02DC,U+2000-206F,U+2074,U+20AC,U+2122,U+2191,U+2193,U+2212,U+2215,U+FEFF,U+FFFD" --layout-features+="tnum" +done diff --git a/lib/common/expression/pagination.ts b/lib/common/expression/pagination.ts index 462fac8..f232b64 100644 --- a/lib/common/expression/pagination.ts +++ b/lib/common/expression/pagination.ts @@ -1,8 +1,8 @@ import { complement } from "espresso-iisojs"; -import { Expression } from "../../types"; -import normalize from "./normalize"; -import { Clause, SynthContext } from "./synth"; -import { and, or } from "./util"; +import { Expression } from "../../types.ts"; +import normalize from "./normalize.ts"; +import { Clause, SynthContext } from "./synth.ts"; +import { and, or } from "./util.ts"; type Bookmark = Record; diff --git a/lib/ui.ts b/lib/ui.ts index 502fdc9..8a1c444 100644 --- a/lib/ui.ts +++ b/lib/ui.ts @@ -105,6 +105,9 @@ router.post("/login", async (ctx) => { const username = ctx.request.body.username; const password = ctx.request.body.password; + const remember = ctx.request.body.remember; + const TWO_WEEKS_SECS = 1209600; + const ONE_DAY_SECS = 86400; const log = { message: "Log in", @@ -115,8 +118,14 @@ router.post("/login", async (ctx) => { function success(authMethod): void { log.method = authMethod; - const token = jwt.sign({ username, authMethod }, JWT_SECRET); - ctx.cookies.set(JWT_COOKIE, token, { sameSite: "lax" }); + const expiresIn = remember ? TWO_WEEKS_SECS : ONE_DAY_SECS; + const token = jwt.sign({ username, authMethod }, JWT_SECRET, { + expiresIn, + }); + ctx.cookies.set(JWT_COOKIE, token, { + sameSite: "lax", + maxAge: remember ? expiresIn * 1000 : null, + }); ctx.body = JSON.stringify(token); logger.accessInfo(log); } @@ -219,7 +228,7 @@ router.get("/", async (ctx) => { - +