mirror of
https://github.com/logseq/logseq.git
synced 2026-06-01 19:01:22 +00:00
Adds a hex/HSV picker pane to the icon-picker color popover, plus a per-graph recents lane and cross-theme contrast adjustment. Hex input supports named-color autocomplete (148 CSS + 949 XKCD) with a ghost suffix that Tab/Right-arrow accepts. Picked colors that fail WCAG 3:1 against either light or dark background are auto-shifted in OKLCH per mode; recents and the trigger swatch render as half-pie splits when the two modes diverge. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
130 lines
4.2 KiB
Clojure
Executable File
130 lines
4.2 KiB
Clojure
Executable File
#!/usr/bin/env bb
|
|
;; build_color_dicts.bb
|
|
;;
|
|
;; Downloads the XKCD color survey (`https://xkcd.com/color/rgb.txt`) and
|
|
;; emits `src/main/frontend/colors/named.cljs` as a deterministic, hermetic
|
|
;; CLJS source file containing the dataset as a `def` map literal.
|
|
;;
|
|
;; The XKCD dataset is licensed CC0 (public domain). It hasn't changed
|
|
;; since 2010. We embed it at build time so the bundle works fully offline.
|
|
;;
|
|
;; Usage: bb scripts/build_color_dicts.bb
|
|
;;
|
|
;; Idempotent: rerunning produces byte-identical output.
|
|
|
|
(ns build-color-dicts
|
|
(:require [babashka.curl :as curl]
|
|
[babashka.fs :as fs]
|
|
[clojure.java.io :as io]
|
|
[clojure.string :as str]))
|
|
|
|
(def ^:private xkcd-url "https://xkcd.com/color/rgb.txt")
|
|
(def ^:private cache-path "/tmp/xkcd_rgb.txt")
|
|
|
|
(def ^:private repo-root
|
|
(-> *file* fs/parent fs/parent str))
|
|
|
|
(def ^:private out-path
|
|
(str repo-root "/src/main/frontend/colors/named.cljs"))
|
|
|
|
(defn- fetch!
|
|
"Return the raw rgb.txt body. Caches at /tmp/xkcd_rgb.txt to avoid hammering xkcd.com."
|
|
[]
|
|
(if (fs/exists? cache-path)
|
|
(slurp cache-path)
|
|
(let [body (:body (curl/get xkcd-url))]
|
|
(spit cache-path body)
|
|
body)))
|
|
|
|
(defn- parse-line
|
|
"Parse one entry line. Returns [name hex] or nil for the license comment."
|
|
[line]
|
|
(when-not (or (str/blank? line)
|
|
(str/starts-with? line "#"))
|
|
(let [[raw-name raw-hex] (str/split line #"\t")]
|
|
(when (and raw-name raw-hex)
|
|
(let [nm (str/lower-case (str/trim raw-name))
|
|
hx (-> raw-hex str/trim (str/replace #"^#" "") str/lower-case)]
|
|
(when (and (re-matches #"[0-9a-f]{6}" hx)
|
|
(seq nm))
|
|
[nm hx]))))))
|
|
|
|
(defn- parse-all [body]
|
|
(->> (str/split-lines body)
|
|
(keep parse-line)
|
|
(into (sorted-map))))
|
|
|
|
(defn- format-pairs
|
|
"Format the sorted map into one `key value` pair per line, indented to
|
|
align with the open brace at column 3 (`(def^:export named-colors{`)."
|
|
[pairs]
|
|
(->> pairs
|
|
(map (fn [[nm hx]]
|
|
(str " " (pr-str nm) " " (pr-str hx))))
|
|
(str/join "\n")))
|
|
|
|
(def ^:private file-template
|
|
"(ns frontend.colors.named
|
|
\"XKCD color survey dataset (~949 colors).
|
|
Public domain (CC0): https://xkcd.com/color/rgb.txt
|
|
Generated by scripts/build_color_dicts.bb -- do not edit by hand.\")
|
|
|
|
(def ^:export named-colors
|
|
\"Map of normalized lowercase color name -> 6-char hex (no leading '#').
|
|
Names with spaces are stored as-is; the resolver normalizes input the
|
|
same way before lookup.\"
|
|
{%PAIRS%})
|
|
|
|
(def ^:private by-hex
|
|
(into {} (map (fn [[n h]] [h n])) named-colors))
|
|
|
|
(defn name->hex
|
|
\"Look up a normalized color name (lowercase, single-spaced). Returns
|
|
6-char hex without '#' or nil.\"
|
|
[s]
|
|
(get named-colors s))
|
|
|
|
(defn hex->name
|
|
\"Reverse lookup: 6-char lowercase hex (no '#') -> canonical name or nil.\"
|
|
[h]
|
|
(get by-hex h))
|
|
")
|
|
|
|
(defn- render [pairs]
|
|
;; The opening `{` sits flush at column 3 (after ` `). We render the
|
|
;; first pair on the same line as the brace and subsequent pairs at the
|
|
;; same column, matching the spec example formatting.
|
|
(let [lines (->> pairs
|
|
(map (fn [[nm hx]]
|
|
(str (pr-str nm) " " (pr-str hx)))))
|
|
indent " "
|
|
joined (->> (rest lines)
|
|
(map #(str indent %))
|
|
(cons (first lines))
|
|
(str/join "\n"))]
|
|
(str/replace file-template "%PAIRS%" joined)))
|
|
|
|
(defn- gzip-size
|
|
"Estimate gzipped size of a string in bytes."
|
|
[s]
|
|
(let [bs (.getBytes ^String s "UTF-8")
|
|
baos (java.io.ByteArrayOutputStream.)]
|
|
(with-open [gz (java.util.zip.GZIPOutputStream. baos)]
|
|
(.write gz bs))
|
|
(count (.toByteArray baos))))
|
|
|
|
(defn -main [& _]
|
|
(let [body (fetch!)
|
|
pairs (parse-all body)
|
|
out (render pairs)]
|
|
(fs/create-dirs (fs/parent out-path))
|
|
(spit out-path out)
|
|
(let [raw-bytes (count (.getBytes ^String out "UTF-8"))]
|
|
(println "wrote" out-path)
|
|
(println " entries: " (count pairs))
|
|
(println " raw size: " raw-bytes "bytes")
|
|
(println " gzipped est:" (gzip-size out) "bytes"))))
|
|
|
|
(when (= *file* (System/getProperty "babashka.file"))
|
|
(apply -main *command-line-args*))
|