feat: add calendar docs with inline markdown rendering
Add src/ui/calendar.md with full documentation for both calendar namespaces (picker props, event grid, ticker, agenda, event data format, date utilities, CSS classes). Add a minimal markdown-to-hiccup renderer (ui.markdown) that handles headings, fenced code blocks, tables, lists, inline code, and bold. Styled with ui/markdown.css using theme tokens. Each dev target renders the docs inline on the Calendar page: - Hiccup: slurps the .md file at render time - Replicant: embeds via compile-time macro (ui.macros/inline-file) - Squint: fetches from /calendar.md served by Vite Also fixes calendar event grid day cells to be square (aspect-ratio: 1 with overflow: hidden instead of min-height).
This commit is contained in:
7
bb.edn
7
bb.edn
@@ -20,7 +20,12 @@
|
|||||||
(let [lr (slurp "dev/css-live-reload.js")]
|
(let [lr (slurp "dev/css-live-reload.js")]
|
||||||
(spit "dev/replicant/public/css-live-reload.js" lr)
|
(spit "dev/replicant/public/css-live-reload.js" lr)
|
||||||
(spit "dev/squint/public/css-live-reload.js" lr)
|
(spit "dev/squint/public/css-live-reload.js" lr)
|
||||||
(println "Copied css-live-reload.js to dev targets")))}
|
(println "Copied css-live-reload.js to dev targets"))
|
||||||
|
(doseq [md (.listFiles (io/file "src/ui")
|
||||||
|
(reify java.io.FileFilter
|
||||||
|
(accept [_ f] (.endsWith (.getName f) ".md"))))]
|
||||||
|
(spit (str "dev/squint/public/" (.getName md)) (slurp md)))
|
||||||
|
(println "Copied component docs to dev/squint"))}
|
||||||
|
|
||||||
watch-theme
|
watch-theme
|
||||||
{:doc "Watch src/ui/*.css and tokens.edn, rebuild theme on changes"
|
{:doc "Watch src/ui/*.css and tokens.edn, rebuild theme on changes"
|
||||||
|
|||||||
@@ -23,7 +23,8 @@
|
|||||||
[ui.icon :as icon]
|
[ui.icon :as icon]
|
||||||
[ui.separator :as separator]
|
[ui.separator :as separator]
|
||||||
[ui.calendar :as calendar]
|
[ui.calendar :as calendar]
|
||||||
[ui.calendar-events :as cal-events]))
|
[ui.calendar-events :as cal-events]
|
||||||
|
[ui.markdown :as markdown]))
|
||||||
|
|
||||||
;; ── Query Params ────────────────────────────────────────────────────
|
;; ── Query Params ────────────────────────────────────────────────────
|
||||||
|
|
||||||
@@ -412,6 +413,8 @@
|
|||||||
(defn calendar-page []
|
(defn calendar-page []
|
||||||
[:div
|
[:div
|
||||||
(page-header "Calendar" "Date picker, event grid, ticker strip, and agenda list.")
|
(page-header "Calendar" "Date picker, event grid, ticker strip, and agenda list.")
|
||||||
|
(into [:div {:class "md-docs"}]
|
||||||
|
(markdown/markdown->hiccup (slurp "src/ui/calendar.md")))
|
||||||
(calendar-demo)])
|
(calendar-demo)])
|
||||||
|
|
||||||
(defn icons-page []
|
(defn icons-page []
|
||||||
|
|||||||
@@ -20,7 +20,9 @@
|
|||||||
[ui.icon :as icon]
|
[ui.icon :as icon]
|
||||||
[ui.separator :as separator]
|
[ui.separator :as separator]
|
||||||
[ui.calendar :as calendar]
|
[ui.calendar :as calendar]
|
||||||
[ui.calendar-events :as cal-events]))
|
[ui.calendar-events :as cal-events]
|
||||||
|
[ui.markdown :as markdown])
|
||||||
|
(:require-macros [ui.macros :refer [inline-file]]))
|
||||||
|
|
||||||
;; ── State ───────────────────────────────────────────────────────────
|
;; ── State ───────────────────────────────────────────────────────────
|
||||||
|
|
||||||
@@ -390,9 +392,13 @@
|
|||||||
["Dev & Technical"
|
["Dev & Technical"
|
||||||
[:code :terminal :database :globe :shield :zap :book-open :map-pin]]])
|
[:code :terminal :database :globe :shield :zap :book-open :map-pin]]])
|
||||||
|
|
||||||
|
(def calendar-docs-md (inline-file "../../src/ui/calendar.md"))
|
||||||
|
|
||||||
(defn calendar-page []
|
(defn calendar-page []
|
||||||
[:div
|
[:div
|
||||||
(page-header "Calendar" "Date picker, event grid, ticker strip, and agenda list.")
|
(page-header "Calendar" "Date picker, event grid, ticker strip, and agenda list.")
|
||||||
|
(into [:div {:class ["md-docs"]}]
|
||||||
|
(markdown/markdown->hiccup calendar-docs-md))
|
||||||
(calendar-demo)])
|
(calendar-demo)])
|
||||||
|
|
||||||
(defn icons-page []
|
(defn icons-page []
|
||||||
|
|||||||
@@ -19,7 +19,8 @@
|
|||||||
[ui.icon :as icon]
|
[ui.icon :as icon]
|
||||||
[ui.separator :as separator]
|
[ui.separator :as separator]
|
||||||
[ui.calendar :as calendar]
|
[ui.calendar :as calendar]
|
||||||
[ui.calendar-events :as cal-events]))
|
[ui.calendar-events :as cal-events]
|
||||||
|
[ui.markdown :as markdown]))
|
||||||
|
|
||||||
;; ── State ───────────────────────────────────────────────────────────
|
;; ── State ───────────────────────────────────────────────────────────
|
||||||
|
|
||||||
@@ -433,9 +434,23 @@
|
|||||||
(into [:div {:style {"display" "grid" "grid-template-columns" "repeat(auto-fill, minmax(5rem, 1fr))" "gap" "var(--size-4)"}}]
|
(into [:div {:style {"display" "grid" "grid-template-columns" "repeat(auto-fill, minmax(5rem, 1fr))" "gap" "var(--size-4)"}}]
|
||||||
(map icon-card icons)))))
|
(map icon-card icons)))))
|
||||||
|
|
||||||
|
(def !calendar-docs (atom nil))
|
||||||
|
|
||||||
|
(defn load-calendar-docs! []
|
||||||
|
(when-not @!calendar-docs
|
||||||
|
(-> (js/fetch "/calendar.md")
|
||||||
|
(.then (fn [r] (.text r)))
|
||||||
|
(.then (fn [text]
|
||||||
|
(reset! !calendar-docs text)
|
||||||
|
(render!))))))
|
||||||
|
|
||||||
(defn calendar-page []
|
(defn calendar-page []
|
||||||
|
(load-calendar-docs!)
|
||||||
[:div
|
[:div
|
||||||
(page-header "Calendar" "Date picker, event grid, ticker strip, and agenda list.")
|
(page-header "Calendar" "Date picker, event grid, ticker strip, and agenda list.")
|
||||||
|
(when-let [md @!calendar-docs]
|
||||||
|
(into [:div {:class "md-docs"}]
|
||||||
|
(markdown/markdown->hiccup md)))
|
||||||
(calendar-demo)])
|
(calendar-demo)])
|
||||||
|
|
||||||
(defn icons-page []
|
(defn icons-page []
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
(ns ui.calendar
|
(ns ui.calendar
|
||||||
|
"Month-grid date picker. See src/ui/calendar.md for full documentation."
|
||||||
(:require [clojure.string :as str]
|
(:require [clojure.string :as str]
|
||||||
[ui.button :as button]
|
[ui.button :as button]
|
||||||
[ui.icon :as icon]))
|
[ui.icon :as icon]))
|
||||||
|
|||||||
115
src/ui/calendar.md
Normal file
115
src/ui/calendar.md
Normal file
@@ -0,0 +1,115 @@
|
|||||||
|
# Calendar
|
||||||
|
|
||||||
|
A month-grid date picker and event calendar inspired by [shadcn/radix Calendar](https://ui.shadcn.com/docs/components/radix/calendar) and the org-mode-agenda-cli.
|
||||||
|
|
||||||
|
Two namespaces: `ui.calendar` for the core date picker, `ui.calendar-events` for event grid, ticker strip, and agenda list.
|
||||||
|
|
||||||
|
## Date Picker
|
||||||
|
|
||||||
|
A compact month grid with navigation, today highlighting, and date selection.
|
||||||
|
|
||||||
|
```clojure
|
||||||
|
;; Minimal static calendar
|
||||||
|
(calendar/calendar {:year 2026 :month 3 :today-str "2026-03-29"})
|
||||||
|
|
||||||
|
;; Interactive with selection
|
||||||
|
(calendar/calendar {:year 2026 :month 3
|
||||||
|
:today-str "2026-03-29"
|
||||||
|
:selected-date "2026-03-15"
|
||||||
|
:on-select (fn [date-str] (println date-str))
|
||||||
|
:on-prev-month (fn [_] ...)
|
||||||
|
:on-next-month (fn [_] ...)})
|
||||||
|
```
|
||||||
|
|
||||||
|
### Props
|
||||||
|
|
||||||
|
| Prop | Type | Description |
|
||||||
|
|------------------|--------|-------------------------------------------|
|
||||||
|
| `:year` | int | Displayed year (e.g. 2026) |
|
||||||
|
| `:month` | int | Displayed month (1–12) |
|
||||||
|
| `:today-str` | string | Today's date as `"YYYY-MM-DD"` |
|
||||||
|
| `:selected-date` | string | Selected date as `"YYYY-MM-DD"` or nil |
|
||||||
|
| `:on-select` | fn | `(fn [date-str] ...)` — on day click |
|
||||||
|
| `:on-prev-month` | fn | Called on prev-month button click |
|
||||||
|
| `:on-next-month` | fn | Called on next-month button click |
|
||||||
|
| `:class` | string | Additional CSS classes |
|
||||||
|
| `:attrs` | map | Additional HTML attributes |
|
||||||
|
|
||||||
|
## Event Grid
|
||||||
|
|
||||||
|
A full month grid with colored event pills inside day cells.
|
||||||
|
|
||||||
|
```clojure
|
||||||
|
(cal-events/calendar-event-grid
|
||||||
|
{:year 2026 :month 3
|
||||||
|
:today-str "2026-03-29"
|
||||||
|
:events [{:title "Meeting" :date "2026-03-29" :time-start "10:00" :color :accent}]
|
||||||
|
:on-select (fn [date-str] ...)
|
||||||
|
:on-prev-month (fn [_] ...)
|
||||||
|
:on-next-month (fn [_] ...)
|
||||||
|
:on-event-click (fn [event-map] ...)
|
||||||
|
:max-visible 3})
|
||||||
|
```
|
||||||
|
|
||||||
|
## Day Ticker
|
||||||
|
|
||||||
|
Horizontal scrollable strip showing days with event dot indicators.
|
||||||
|
|
||||||
|
```clojure
|
||||||
|
(cal-events/ticker-strip
|
||||||
|
{:days [{:date "2026-03-29" :day-num 29 :day-label "Su"} ...]
|
||||||
|
:today-str "2026-03-29"
|
||||||
|
:selected "2026-03-29"
|
||||||
|
:events events
|
||||||
|
:on-select (fn [date-str] ...)})
|
||||||
|
```
|
||||||
|
|
||||||
|
## Agenda List
|
||||||
|
|
||||||
|
Vertical list of events grouped by day.
|
||||||
|
|
||||||
|
```clojure
|
||||||
|
(cal-events/agenda-list
|
||||||
|
{:days [{:date "2026-03-29" :label "Today"} ...]
|
||||||
|
:events events
|
||||||
|
:on-event-click (fn [event-map] ...)})
|
||||||
|
```
|
||||||
|
|
||||||
|
## Event Data Format
|
||||||
|
|
||||||
|
Events are plain maps:
|
||||||
|
|
||||||
|
```clojure
|
||||||
|
{:title "Team standup"
|
||||||
|
:date "2026-03-29" ;; YYYY-MM-DD
|
||||||
|
:time-start "09:00" ;; HH:MM or nil
|
||||||
|
:time-end "09:30" ;; HH:MM or nil
|
||||||
|
:color :accent ;; :accent :danger :success :warning or nil
|
||||||
|
:done? false}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Event Colors
|
||||||
|
|
||||||
|
Colors map to the theme's semantic tokens and support dark mode automatically: `:accent`, `:danger`, `:success`, `:warning`. Pass `nil` for the default gray.
|
||||||
|
|
||||||
|
## Date Utilities
|
||||||
|
|
||||||
|
All date math is pure (no JS Date dependency) and works on all targets:
|
||||||
|
|
||||||
|
- `(days-in-month year month)` — days in a month (handles leap years)
|
||||||
|
- `(day-of-week year month day)` — 0=Mon..6=Sun
|
||||||
|
- `(first-day-of-week year month)` — weekday of the 1st
|
||||||
|
- `(calendar-days year month)` — full grid including prev/next month padding
|
||||||
|
- `(prev-month year month)` / `(next-month year month)` — returns `[year month]`
|
||||||
|
- `(date-str year month day)` — formats as `"YYYY-MM-DD"`
|
||||||
|
|
||||||
|
## CSS Classes
|
||||||
|
|
||||||
|
The calendar uses `cal-` prefixed classes. Key states on day cells:
|
||||||
|
|
||||||
|
- `.cal-day-today` — today's date
|
||||||
|
- `.cal-day-selected` — currently selected date
|
||||||
|
- `.cal-day-outside` — days from adjacent months
|
||||||
|
- `.cal-day-disabled` — unselectable days
|
||||||
|
|
||||||
|
Event pills use color classes: `.cal-event-accent`, `.cal-event-danger`, `.cal-event-success`, `.cal-event-warning`, `.cal-event-default`.
|
||||||
@@ -1,4 +1,5 @@
|
|||||||
(ns ui.calendar-events
|
(ns ui.calendar-events
|
||||||
|
"Event-aware calendar components. See src/ui/calendar.md for full documentation."
|
||||||
(:require [clojure.string :as str]
|
(:require [clojure.string :as str]
|
||||||
[ui.calendar :as cal]))
|
[ui.calendar :as cal]))
|
||||||
|
|
||||||
|
|||||||
@@ -15,7 +15,7 @@
|
|||||||
.cal-event-day {
|
.cal-event-day {
|
||||||
width: auto;
|
width: auto;
|
||||||
height: auto;
|
height: auto;
|
||||||
min-height: var(--size-16);
|
aspect-ratio: 1;
|
||||||
align-items: flex-start;
|
align-items: flex-start;
|
||||||
justify-content: flex-start;
|
justify-content: flex-start;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
@@ -24,6 +24,7 @@
|
|||||||
border-right: var(--border-0);
|
border-right: var(--border-0);
|
||||||
border-bottom: var(--border-0);
|
border-bottom: var(--border-0);
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
|
overflow: hidden;
|
||||||
}
|
}
|
||||||
|
|
||||||
.cal-grid-events .cal-day:nth-child(7n) {
|
.cal-grid-events .cal-day:nth-child(7n) {
|
||||||
@@ -442,7 +443,6 @@
|
|||||||
|
|
||||||
@media (max-width: 768px) {
|
@media (max-width: 768px) {
|
||||||
.cal-event-day {
|
.cal-event-day {
|
||||||
min-height: var(--size-12);
|
|
||||||
padding: var(--size-1);
|
padding: var(--size-1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
7
src/ui/macros.clj
Normal file
7
src/ui/macros.clj
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
(ns ui.macros)
|
||||||
|
|
||||||
|
(defmacro inline-file
|
||||||
|
"Read a file at compile time and return its contents as a string.
|
||||||
|
For use in ClojureScript (shadow-cljs) to embed file content."
|
||||||
|
[path]
|
||||||
|
(slurp path))
|
||||||
183
src/ui/markdown.cljc
Normal file
183
src/ui/markdown.cljc
Normal file
@@ -0,0 +1,183 @@
|
|||||||
|
(ns ui.markdown
|
||||||
|
"Minimal markdown-to-hiccup renderer for dev documentation pages.
|
||||||
|
Supports: headings, fenced code blocks, tables, unordered lists,
|
||||||
|
inline code, bold, paragraphs."
|
||||||
|
(:require [clojure.string :as str]))
|
||||||
|
|
||||||
|
(defn- parse-inline
|
||||||
|
"Replace inline markdown (backticks, bold) with hiccup fragments.
|
||||||
|
Returns a vector of strings and hiccup nodes."
|
||||||
|
[text]
|
||||||
|
(if (str/blank? text)
|
||||||
|
[text]
|
||||||
|
(loop [remaining text
|
||||||
|
result []]
|
||||||
|
(if (str/blank? remaining)
|
||||||
|
result
|
||||||
|
(let [;; Find the next special marker
|
||||||
|
code-idx (str/index-of remaining "`")
|
||||||
|
bold-idx (str/index-of remaining "**")]
|
||||||
|
(cond
|
||||||
|
;; Inline code is nearest
|
||||||
|
(and code-idx (or (nil? bold-idx) (<= code-idx bold-idx)))
|
||||||
|
(let [before (subs remaining 0 code-idx)
|
||||||
|
after (subs remaining (inc code-idx))
|
||||||
|
end-idx (str/index-of after "`")]
|
||||||
|
(if end-idx
|
||||||
|
(let [code (subs after 0 end-idx)
|
||||||
|
rest (subs after (inc end-idx))]
|
||||||
|
(recur rest (cond-> result
|
||||||
|
(seq before) (conj before)
|
||||||
|
true (conj [:code {:class "md-inline-code"} code]))))
|
||||||
|
;; No closing backtick — treat as literal
|
||||||
|
(conj result remaining)))
|
||||||
|
|
||||||
|
;; Bold is nearest
|
||||||
|
(and bold-idx (or (nil? code-idx) (< bold-idx code-idx)))
|
||||||
|
(let [before (subs remaining 0 bold-idx)
|
||||||
|
after (subs remaining (+ bold-idx 2))
|
||||||
|
end-idx (str/index-of after "**")]
|
||||||
|
(if end-idx
|
||||||
|
(let [bold (subs after 0 end-idx)
|
||||||
|
rest (subs after (+ end-idx 2))]
|
||||||
|
(recur rest (cond-> result
|
||||||
|
(seq before) (conj before)
|
||||||
|
true (conj [:strong bold]))))
|
||||||
|
(conj result remaining)))
|
||||||
|
|
||||||
|
:else
|
||||||
|
(conj result remaining)))))))
|
||||||
|
|
||||||
|
(defn- heading-level [line]
|
||||||
|
(let [trimmed (str/triml line)]
|
||||||
|
(when (str/starts-with? trimmed "#")
|
||||||
|
(let [hashes (re-find #"^#{1,6}" trimmed)]
|
||||||
|
(when hashes
|
||||||
|
(let [n (count hashes)
|
||||||
|
text (str/trim (subs trimmed n))]
|
||||||
|
[n text]))))))
|
||||||
|
|
||||||
|
(defn- table-row? [line]
|
||||||
|
(and (str/starts-with? (str/trim line) "|")
|
||||||
|
(str/ends-with? (str/trim line) "|")))
|
||||||
|
|
||||||
|
(defn- separator-row? [line]
|
||||||
|
(and (table-row? line)
|
||||||
|
(every? #(re-matches #"\s*:?-+:?\s*" %)
|
||||||
|
(-> (str/trim line)
|
||||||
|
(subs 1)
|
||||||
|
(str/replace #"\|$" "")
|
||||||
|
(str/split #"\|")))))
|
||||||
|
|
||||||
|
(defn- parse-table-cells [line]
|
||||||
|
(->> (-> (str/trim line)
|
||||||
|
(subs 1)
|
||||||
|
(str/replace #"\|$" "")
|
||||||
|
(str/split #"\|"))
|
||||||
|
(mapv str/trim)))
|
||||||
|
|
||||||
|
(defn- list-item? [line]
|
||||||
|
(re-matches #"^\s*[-*]\s+.+" line))
|
||||||
|
|
||||||
|
(defn- list-item-text [line]
|
||||||
|
(str/replace line #"^\s*[-*]\s+" ""))
|
||||||
|
|
||||||
|
(defn markdown->hiccup
|
||||||
|
"Convert a markdown string to a hiccup data structure.
|
||||||
|
Returns a vector of hiccup elements."
|
||||||
|
[md-str]
|
||||||
|
(let [lines (str/split-lines md-str)]
|
||||||
|
(loop [i 0
|
||||||
|
result []]
|
||||||
|
(if (>= i (count lines))
|
||||||
|
result
|
||||||
|
(let [line (nth lines i)]
|
||||||
|
(cond
|
||||||
|
;; Blank line — skip
|
||||||
|
(str/blank? line)
|
||||||
|
(recur (inc i) result)
|
||||||
|
|
||||||
|
;; Fenced code block
|
||||||
|
(str/starts-with? (str/trim line) "```")
|
||||||
|
(let [lang (str/replace (str/trim (subs (str/trim line) 3)) #"\"" "")
|
||||||
|
;; Collect lines until closing ```
|
||||||
|
code-lines (loop [j (inc i)
|
||||||
|
acc []]
|
||||||
|
(if (>= j (count lines))
|
||||||
|
[acc j]
|
||||||
|
(let [l (nth lines j)]
|
||||||
|
(if (str/starts-with? (str/trim l) "```")
|
||||||
|
[acc (inc j)]
|
||||||
|
(recur (inc j) (conj acc l))))))]
|
||||||
|
(recur (second code-lines)
|
||||||
|
(conj result
|
||||||
|
[:pre {:class "md-code-block"}
|
||||||
|
[:code {:class (when (seq lang) (str "language-" lang))}
|
||||||
|
(str/join "\n" (first code-lines))]])))
|
||||||
|
|
||||||
|
;; Heading
|
||||||
|
(heading-level line)
|
||||||
|
(let [[n text] (heading-level line)
|
||||||
|
tag (keyword (str "h" n))]
|
||||||
|
(recur (inc i)
|
||||||
|
(conj result (into [tag {:class "md-heading"}] (parse-inline text)))))
|
||||||
|
|
||||||
|
;; Table
|
||||||
|
(table-row? line)
|
||||||
|
(let [header-cells (parse-table-cells line)
|
||||||
|
;; Skip separator row, collect data rows
|
||||||
|
body-start (if (and (< (inc i) (count lines))
|
||||||
|
(separator-row? (nth lines (inc i))))
|
||||||
|
(+ i 2)
|
||||||
|
(inc i))
|
||||||
|
body-rows (loop [j body-start
|
||||||
|
acc []]
|
||||||
|
(if (and (< j (count lines))
|
||||||
|
(table-row? (nth lines j)))
|
||||||
|
(recur (inc j) (conj acc (parse-table-cells (nth lines j))))
|
||||||
|
[acc j]))]
|
||||||
|
(recur (second body-rows)
|
||||||
|
(conj result
|
||||||
|
[:table {:class "md-table"}
|
||||||
|
[:thead
|
||||||
|
(into [:tr]
|
||||||
|
(map (fn [c] (into [:th] (parse-inline c)))
|
||||||
|
header-cells))]
|
||||||
|
(into [:tbody]
|
||||||
|
(map (fn [row]
|
||||||
|
(into [:tr]
|
||||||
|
(map (fn [c] (into [:td] (parse-inline c)))
|
||||||
|
row)))
|
||||||
|
(first body-rows)))])))
|
||||||
|
|
||||||
|
;; Unordered list — collect consecutive items
|
||||||
|
(list-item? line)
|
||||||
|
(let [items (loop [j i
|
||||||
|
acc []]
|
||||||
|
(if (and (< j (count lines))
|
||||||
|
(list-item? (nth lines j)))
|
||||||
|
(recur (inc j) (conj acc (list-item-text (nth lines j))))
|
||||||
|
[acc j]))]
|
||||||
|
(recur (second items)
|
||||||
|
(conj result
|
||||||
|
(into [:ul {:class "md-list"}]
|
||||||
|
(map (fn [txt] (into [:li] (parse-inline txt)))
|
||||||
|
(first items))))))
|
||||||
|
|
||||||
|
;; Paragraph (default)
|
||||||
|
:else
|
||||||
|
(let [;; Collect consecutive non-blank, non-special lines
|
||||||
|
para (loop [j i
|
||||||
|
acc []]
|
||||||
|
(if (and (< j (count lines))
|
||||||
|
(not (str/blank? (nth lines j)))
|
||||||
|
(not (str/starts-with? (str/trim (nth lines j)) "```"))
|
||||||
|
(not (str/starts-with? (str/trim (nth lines j)) "#"))
|
||||||
|
(not (table-row? (nth lines j)))
|
||||||
|
(not (list-item? (nth lines j))))
|
||||||
|
(recur (inc j) (conj acc (nth lines j)))
|
||||||
|
[acc j]))]
|
||||||
|
(recur (second para)
|
||||||
|
(conj result
|
||||||
|
(into [:p {:class "md-paragraph"}]
|
||||||
|
(parse-inline (str/join " " (first para)))))))))))))
|
||||||
79
src/ui/markdown.css
Normal file
79
src/ui/markdown.css
Normal file
@@ -0,0 +1,79 @@
|
|||||||
|
/* ── Markdown Docs ────────────────────────────────────────────────── */
|
||||||
|
|
||||||
|
.md-docs {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: var(--size-3);
|
||||||
|
font-size: var(--font-sm);
|
||||||
|
line-height: 1.6;
|
||||||
|
color: var(--fg-0);
|
||||||
|
}
|
||||||
|
|
||||||
|
.md-heading {
|
||||||
|
margin: 0;
|
||||||
|
color: var(--fg-0);
|
||||||
|
line-height: 1.3;
|
||||||
|
}
|
||||||
|
|
||||||
|
.md-docs h1.md-heading { font-size: var(--font-xl); margin-top: var(--size-2); }
|
||||||
|
.md-docs h2.md-heading { font-size: var(--font-lg); margin-top: var(--size-4); padding-bottom: var(--size-1); border-bottom: var(--border-0); }
|
||||||
|
.md-docs h3.md-heading { font-size: var(--font-md); margin-top: var(--size-2); }
|
||||||
|
|
||||||
|
.md-paragraph {
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.md-inline-code {
|
||||||
|
font-family: ui-monospace, SFMono-Regular, "SF Mono", Menlo, monospace;
|
||||||
|
font-size: 0.85em;
|
||||||
|
padding: var(--size-1);
|
||||||
|
background: var(--bg-2);
|
||||||
|
border-radius: var(--radius-sm);
|
||||||
|
}
|
||||||
|
|
||||||
|
.md-code-block {
|
||||||
|
margin: 0;
|
||||||
|
padding: var(--size-3);
|
||||||
|
background: var(--bg-2);
|
||||||
|
border-radius: var(--radius-md);
|
||||||
|
overflow-x: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.md-code-block code {
|
||||||
|
font-family: ui-monospace, SFMono-Regular, "SF Mono", Menlo, monospace;
|
||||||
|
font-size: var(--font-xs);
|
||||||
|
line-height: 1.5;
|
||||||
|
white-space: pre;
|
||||||
|
}
|
||||||
|
|
||||||
|
.md-table {
|
||||||
|
width: 100%;
|
||||||
|
border-collapse: collapse;
|
||||||
|
font-size: var(--font-xs);
|
||||||
|
}
|
||||||
|
|
||||||
|
.md-table th,
|
||||||
|
.md-table td {
|
||||||
|
padding: var(--size-2) var(--size-3);
|
||||||
|
text-align: left;
|
||||||
|
border-bottom: var(--border-0);
|
||||||
|
}
|
||||||
|
|
||||||
|
.md-table th {
|
||||||
|
font-weight: 600;
|
||||||
|
color: var(--fg-1);
|
||||||
|
background: var(--bg-2);
|
||||||
|
}
|
||||||
|
|
||||||
|
.md-table td {
|
||||||
|
color: var(--fg-0);
|
||||||
|
}
|
||||||
|
|
||||||
|
.md-list {
|
||||||
|
margin: 0;
|
||||||
|
padding-left: var(--size-5);
|
||||||
|
}
|
||||||
|
|
||||||
|
.md-list li {
|
||||||
|
margin-bottom: var(--size-1);
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user