Basking: a place to dump thoughts, jot notes and keep a journal
Plain markdown files, tags by typing #hashtags, a page per day, search, pin and trash with undo, autosave with conflict warnings, API tokens, zip export, and the Lightwell dashboard. Includes install.sh, CI and release workflows. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
commit
b548faa258
23 files changed
+4026
No files matched your search
@@ -0,0 +1,47 @@
|
||||
// Style gate for the dashboard, following the Lightwell design language (see docs/design.md).
|
||||
// Fails on em or en dashes, emoji, exclamation marks in interface copy, and raw colors outside the
|
||||
// token block of the dashboard stylesheet. Run with: node scripts/lint-design.mjs
|
||||
import { readdirSync, readFileSync, statSync } from "node:fs";
|
||||
import { extname, join, relative, sep } from "node:path";
|
||||
|
||||
const root = new URL("..", import.meta.url).pathname.replace(/^\/([A-Za-z]:)/, "$1");
|
||||
const skipDirs = new Set(["node_modules", ".git", "dist", "bin", "data"]);
|
||||
const exts = new Set([".html", ".css", ".js", ".md", ".go", ".sh", ".mjs", ".json", ".yml"]);
|
||||
|
||||
const dash = /[–—−]/;
|
||||
const emoji = /[\u{1F300}-\u{1FAFF}\u{2600}-\u{27BF}\u{1F900}-\u{1F9FF}\u{FE0F}\u{2B50}\u{2705}\u{274C}]/u;
|
||||
const rawColor = /#[0-9a-fA-F]{3,8}\b|\brgba?\(|\bhsla?\(/;
|
||||
|
||||
function* walk(p) {
|
||||
const st = statSync(p, { throwIfNoEntry: false });
|
||||
if (!st) return;
|
||||
if (st.isFile()) return yield p;
|
||||
for (const name of readdirSync(p)) if (!skipDirs.has(name)) yield* walk(join(p, name));
|
||||
}
|
||||
|
||||
const problems = [];
|
||||
for (const file of walk(root)) {
|
||||
if (!exts.has(extname(file))) continue;
|
||||
const rel = relative(root, file).split(sep).join("/");
|
||||
if (rel === "scripts/lint-design.mjs" || rel === "basking.json") continue;
|
||||
const isPage = rel.startsWith("internal/web/static/");
|
||||
let inTokens = false;
|
||||
readFileSync(file, "utf8").split("\n").forEach((line, i) => {
|
||||
const at = (msg) => problems.push(`${rel}:${i + 1}: ${msg}`);
|
||||
if (line.includes("tokens:begin")) inTokens = true;
|
||||
if (dash.test(line)) at("em dash, en dash or minus sign. Use a comma, colon, period or the word to.");
|
||||
if (emoji.test(line)) at("emoji or pictographic symbol. Use an approved outline icon.");
|
||||
if (isPage && !inTokens && rawColor.test(line.replace(/\/\*.*?\*\//g, "")) && !/placeholder|href="#|&#/.test(line)) {
|
||||
at("raw color outside the token block. Use a token.");
|
||||
}
|
||||
if (isPage && /(?:toast|textContent\s*=|class:\s*'(?:empty|notice)')[^;\n]*'[^']*[A-Za-z]![^']*'/.test(line)) at("exclamation mark in interface copy.");
|
||||
if (line.includes("tokens:end")) inTokens = false;
|
||||
});
|
||||
}
|
||||
|
||||
if (problems.length) {
|
||||
console.error(problems.join("\n"));
|
||||
console.error(`\n${problems.length} style problem(s).`);
|
||||
process.exit(1);
|
||||
}
|
||||
console.log("Style checks passed.");
|
||||
Reference in new issue
Block a user