Compare commits

15 Commits

Author SHA1 Message Date
3f0d36015b update readme 2026-03-31 23:27:55 +08:00
Codex
3a3fd97055 Split TUI from default watch mode 2026-03-30 11:05:44 +08:00
aa05d73c9c Clean up CSS and update default styles
Normalize formatting across CSS files and fix selector whitespace
and quote usage. Change default link color to browser blue and
enable smooth scrolling; hover underlines links. Revamp table
styles (borders, caption, zebra rows, padding, footer alignment).
Adjust proof/theorem header spacing and minor spacing/padding fixes.
2026-03-30 10:42:25 +08:00
3652459503 Add Unix and enable stdin-driven quit in watch TUI 2026-03-29 13:48:43 +08:00
fc4cac00d5 format code 2026-03-29 13:38:58 +08:00
55719f3444 fix a tui bug 2026-03-26 15:57:32 +08:00
d4629ec8e7 update readme 2026-03-26 15:57:02 +08:00
e419366615 better tui for watch 2026-03-24 21:05:23 +08:00
6c59abb9cc Introduce CLI, watch server, and bundled assets
Add data-files and bundled templates/fonts/css to the package and
rename the executable to hakysidian (autogen Paths_hakysidian).
Refactor site.hs to parse CLI commands (build/clean/rebuild/watch),
start a preview server, snapshot inputs and run an incremental watch
loop, and move rules into a siteRules function. Update ChaoDoc and
filters to accept math-macros, and add favicon links to templates.
2026-03-24 20:47:46 +08:00
6a3b4c5f88 Remove hakyll-blog.cabal and update .Proof CSS 2026-03-24 20:06:48 +08:00
720a19e24d Remove .notes-list custom styles 2026-03-24 20:01:35 +08:00
1789c75f18 Revert "Rename package to hakysidian; add CLI and assets"
This reverts commit 3d2c5a8852.
2026-03-24 19:58:26 +08:00
3d2c5a8852 Rename package to hakysidian; add CLI and assets 2026-03-24 18:59:06 +08:00
614de591ba Add notes list and integrate into templates
New partial templates/notes-list.html to render the notes list.
Add .notes-list CSS for styling and ensure contents areas use it.
Refactor site.hs: add loadNoteLinks and provide a "notes" listField
used by index and note pages.
2026-03-24 14:56:45 +08:00
71611b0641 Increase body line-height and add theorem padding 2026-03-23 14:54:09 +08:00
13 changed files with 1203 additions and 417 deletions

View File

@@ -1,6 +1,7 @@
.theorem-environment {
font-style: italic;
margin-top: 1em;
padding: 0.5em;
background-color: whitesmoke;
}
@@ -10,22 +11,22 @@
}
.theorem-header .index:before {
content: ' ';
content: " ";
}
.theorem-header .name:before {
content: ' (';
content: " (";
}
.theorem-header .name:after {
content: ')';
content: ")";
}
.theorem-header:after {
content: '.\2002\2002';
content: ".\2002\2002";
}
.theorem-header+p {
.theorem-header + p {
display: inline;
}
@@ -35,17 +36,31 @@
}
.Proof {
background: none;
font-style: normal;
position: relative;
}
.Proof:after {
content: '∎';
content: "∎";
position: absolute;
right: 0px;
bottom: 0px;
}
.Proof span.theorem-header span.name {
font-weight: normal;
font-style: italic;
}
.Proof span.theorem-header span.name:before {
content: " ";
}
.Proof span.theorem-header span.name:after {
content: " ";
}
table.postindex {
width: 100%;
}
@@ -64,7 +79,7 @@ table.postindex td.right {
}
.header-section-number:after {
content: '.';
content: ".";
}
.csl-entry {
@@ -83,6 +98,6 @@ table.postindex td.right {
.csl-right-inline {
display: table-cell;
}
.csl-right-inline a{
.csl-right-inline a {
word-break: break-all;
}

View File

@@ -3,10 +3,7 @@
--color-tag1: gray;
--color-tag2: darkolivegreen;
--color-bg: white;
--color-link: #337ab7;
--color-linkhbg: #e6f0ff;
--color-linkh: #002266;
--color-bq: olivedrab;
--color-link: #0000ee;
--color-notice: #fb4f4f;
}
@@ -19,43 +16,48 @@
html {
scrollbar-gutter: stable;
scroll-behavior: smooth;
font-size: 14pt;
}
body {
font-family: 'Lato', -apple-system, BlinkMacSystemFont, 'PingFang SC', 'Microsoft YaHei', sans-serif;
font-family:
"Lato",
-apple-system,
BlinkMacSystemFont,
"PingFang SC",
"Microsoft YaHei",
sans-serif;
font-optical-sizing: auto;
font-weight: 400;
font-style: normal;
font-size: 1rem;
line-height: 125%;
line-height: 140%;
color: var(--color-text);
background-color: var(--color-bg);
text-rendering: optimizeLegibility;
}
body.lang-zh {
text-align: left;
text-autospace: no-autospace; /*using pangu.hs*/
}
body a {
color: var(--color-link);
text-decoration: none;
}
.text-space a:hover {
background-color: var(--color-linkhbg);
color: var(--color-linkh);
text-decoration: none;
body a:hover {
text-decoration: underline;
}
details {
background-color: var(--color-linkhbg);
padding-left: 1em;
border: 2px solid var(--color-text);
}
summary:hover {
cursor: pointer;
}
/*mathML*/
.htmlmathparagraph, mtext,math {
.htmlmathparagraph,
mtext,
math {
font-family: Lete Sans Math;
}
.math-container,
@@ -63,7 +65,7 @@ summary:hover {
display: block;
overflow-x: auto;
overflow-y: hidden;
padding: .5em;
padding: 0.5em;
}
.math-container.math-container-tagged {
@@ -72,7 +74,7 @@ summary:hover {
align-items: center;
column-gap: 1rem;
overflow: visible;
padding: .5em 0;
padding: 0.5em 0;
}
.math-container.math-container-tagged .math-tag-spacer {
@@ -83,7 +85,7 @@ summary:hover {
min-width: 0;
overflow-x: auto;
overflow-y: hidden;
padding: .5em 0;
padding: 0.5em 0;
}
.math-container.math-container-tagged .math-tag {
@@ -104,66 +106,32 @@ summary:hover {
font-variant-caps: small-caps;
}
p {
hyphens: auto;
}
a.url {
word-break: break-all;
}
html body div.text-space main ul.post-list {
list-style-type: none;
padding-left: 1em;
}
/* top bar */
header {
font-weight: 400;
font-family: "IosevkaC", sans-serif;
}
/* top bar*/
.navbar {
display: flex;
justify-content: space-between;
align-items: center;
}
.navright a {
margin: 0 0 0 1em;
}
/* Links inside the navbar */
.navbar a {
text-decoration: none;
color: var(--color-text);
}
.navbar a:visited {
color: var(--color-text);
}
nav {
text-align: right;
border-bottom: solid 1px var(--color-text);
}
nav a {
font-size: 1.2rem;
/*margin-left: 0.5em;*/
display: inline-block;
vertical-align: middle;
text-decoration: none;
}
.uri {
word-wrap: break-word;
/* Legacy support */
overflow-wrap: break-word;
/* Modern property */
word-break: break-all;
/* Break long words if necessary */
white-space: normal;
/* Allow wrapping */
}
footer {
color: var(--color-text);
font-size: 0.8rem;
@@ -172,15 +140,6 @@ footer {
padding-right: 1em;
}
h1,
h2,
h3,
h4,
h5,
h6 {
text-align: left;
}
.pagetitle {
font-size: 2rem;
font-weight: normal;
@@ -194,22 +153,20 @@ h1 {
font-size: 1.44rem;
font-weight: bold;
font-style: normal;
text-align: left;
line-height: 100%;
}
h2 {
margin-top: 1em;
font-size: 1.2rem;
font-weight: bold;
font-style: normal
font-style: normal;
}
h3 {
margin-top: 1em;
font-size: 1rem;
font-weight: bold;
font-style: normal
font-style: normal;
}
article .header {
@@ -219,14 +176,7 @@ article .header {
text-align: left;
}
.info {
color: var(--color-tag2);
font-size: 1rem;
font-style: normal;
text-align: left;
}
.info,
.info a {
color: var(--color-tag2);
font-size: 1rem;
@@ -247,33 +197,45 @@ section.body {
line-height: normal;
}
blockquote {
margin: 1rem 0;
padding: 0 0 0 1.5em;
border-left: 3px solid var(--color-bq);
/* table. copied from https://developer.mozilla.org/en-US/docs/Web/HTML/Reference/Elements/table */
table {
border-collapse: collapse;
border: 2px solid rgb(140 140 140);
font-size: 0.8rem;
letter-spacing: 1px;
}
blockquote p {
margin: 0;
caption {
caption-side: bottom;
padding: 10px;
font-weight: bold;
}
ol {
padding-left: 2em;
}
ul {
list-style-type: square;
padding-left: 2em;
}
li {
margin-bottom: 0.15em;
thead,
tfoot {
background-color: rgb(228 240 245);
}
table,
th,
td {
border: 1px solid darkolivegreen;
border-collapse: collapse;
text-align: left;
border: 1px solid rgb(160 160 160);
padding: 8px 10px;
}
td:last-of-type {
text-align: center;
}
tbody > tr:nth-of-type(even) {
background-color: rgb(237 238 242);
}
tfoot th {
text-align: right;
}
tfoot td {
font-weight: bold;
}
figure {
@@ -284,23 +246,11 @@ figure {
max-width: 80%;
}
figcaption {
/* font: italic smaller sans-serif; */
padding: 3px;
text-align: center;
}
.caption {
display: none
}
.centerimg img {
margin: 0 auto 0 auto;
display: block;
}
div.highlight,
pre code {
margin: auto;
@@ -320,18 +270,16 @@ code {
text-rendering: optimizeSpeed;
}
.draft-notice {
color: var(--color-notice);
margin: 1em auto;
text-align: center
text-align: center;
}
.subtitle {
text-align: left;
font-size: 1.2rem;
margin-top: 0
margin-top: 0;
}
.gallery {
margin-top: 2em;
@@ -401,15 +349,21 @@ code {
padding-left: 1em;
line-height: 1.2;
list-style-type: decimal;
margin-left: 0
margin-left: 0;
}
div#contents ul.notes-list,
div#contents-big ul.notes-list {
list-style: none;
padding-left: 0;
}
div#contents-big ul ul {
list-style-type: none;
}
div#contents-big li+li {
margin-top: 0.5em
div#contents-big li + li {
margin-top: 0.5em;
}
div#contents-big {
@@ -422,7 +376,7 @@ code {
margin-right: 4em;
position: sticky;
top: 5rem;
left: 100%
left: 100%;
}
div#contents-big .mini-header {
@@ -446,7 +400,6 @@ code {
}
@media print {
.no-print,
.no-print * {
display: none !important;

View File

@@ -1,4 +1,3 @@
/* fonts */
@font-face {

View File

@@ -1,13 +1,22 @@
name: hakyll-blog
cabal-version: 2.4
name: hakysidian
version: 0.1.0.0
build-type: Simple
cabal-version: >= 1.10
data-files:
bib_style.csl
favicon.ico
css/*.css
fonts/*.otf
fonts/*.ttf
fonts/*.woff2
templates/*.html
executable site
executable hakysidian
hs-source-dirs: src
main-is: site.hs
other-modules: ChaoDoc, SideNoteHTML, Pangu
other-modules: ChaoDoc, SideNoteHTML, Pangu, Paths_hakysidian
autogen-modules: Paths_hakysidian
build-depends: base >= 4.18
, hakyll >= 4.15
, mtl >= 2.2.2
@@ -20,7 +29,13 @@ executable site
-- , process
-- , regex-compat
, array
, directory
, filepath
, process
, time
, unix
, wai-app-static
, warp
-- , ghc-syntax-highlighter
-- , blaze-html >= 0.9
, megaparsec

View File

@@ -1,5 +1,6 @@
COMMANDS := build watch rebuild clean
BIN := hakysidian
.PHONY: $(COMMANDS), publish
# Set the default goal, so running 'make' without arguments will run 'make build'.
@@ -7,18 +8,18 @@ COMMANDS := build watch rebuild clean
# ---
$(COMMANDS): site
@echo "Running command: ./site $@"
-@./site $@
$(COMMANDS): $(BIN)
@echo "Running command: ./$(BIN) $@"
-@./$(BIN) $@
# --- Rules ---
# using relative symlinks should be fine since everything only works at ./
site: src/site.hs src/ChaoDoc.hs
cabal build
ln -sf "$(shell cabal list-bin exe:site)" site
$(BIN): src/site.hs src/ChaoDoc.hs
cabal build exe:hakysidian
ln -sf "$(shell cabal list-bin exe:hakysidian)" $(BIN)
# move from katex to mathjax
# katex_cli:

134
readme.md
View File

@@ -1,6 +1,130 @@
things don't work:
1. equation labels & paragraph labels
2. pandoc does not support mathtools: <https://github.com/jgm/texmath/issues/249>
3. cross document refs
4.
# Drawbacks
- currently all shared files (css, templates, csl files...) are stored in `~/.cabal/store/`. there will be a copy for every compile
- web preview needs a port. if you don't set port manually, you cannot preview two projects at the same time.
--------
# hakysidian
`hakysidian` is a static site generator for note projects.
It is built on Hakyll, but packaged as a reusable CLI so you can run the same site generator across multiple note repositories without copying shared assets around. The executable bundles its shared `css/`, `fonts/`, `templates/`, `favicon.ico`, and `bib_style.csl` files with Cabal, then reads project-specific content from the current working directory.
## What It Expects
Run `hakysidian` inside a project directory with this layout:
```text
your-project/
├── notes/
│ ├── first-note.md
│ └── another-note.md
├── reference.bib
├── math-macros.md
└── images/ # optional
```
Required inputs:
- `notes/`: markdown notes to compile.
- `reference.bib`: bibliography used by Pandoc citeproc.
- `math-macros.md`: math macro definitions prepended before note parsing.
Optional inputs:
- `images/`: copied into the generated site as-is.
Shared assets are not required in each project. They come from the installed `hakysidian` package.
## Output
By default, `hakysidian` writes:
- `_site/`: generated site output.
- `_cache/`: Hakyll cache and temporary files.
Note pages use clean URLs. For example:
```text
notes/graph.md -> _site/notes/graph/index.html
```
## Install
From this repository:
```bash
cabal build exe:hakysidian
cabal install exe:hakysidian
```
## Commands
The default CLI mirrors the common Hakyll workflow:
```bash
hakysidian build
hakysidian clean
hakysidian rebuild
hakysidian watch
```
`watch` also supports:
```bash
hakysidian watch --host 127.0.0.1 --port 8000
hakysidian watch --no-server
```
The dashboard is now an explicit TUI mode:
```bash
hakysidian -tui
hakysidian -tui --host 127.0.0.1 --port 8000
hakysidian -tui --no-server
```
What each command does:
- `build`: incremental site build.
- `clean`: removes generated output and cache.
- `rebuild`: clears output/cache and builds from scratch.
- `watch`: runs Hakyll's normal watch workflow, prints build logs directly to the terminal, and rebuilds automatically on change.
- `-tui`: starts the interactive dashboard with explicit controls for watching and cleaning.
## Watch And TUI
Both `watch` and `-tui` work against the same project inputs:
- `notes/**`
- `reference.bib`
- `math-macros.md`
- `images/**`
Normal `watch` behaves like a standard Hakyll watch command: it stays in the terminal, rebuilds when inputs change, and can start a preview server unless `--no-server` is passed.
`-tui` uses an alternate-screen dashboard that:
- uses the terminals current size to keep the dashboard within the visible screen,
- keeps recent build output in a bounded activity pane,
- can start a local preview server unless `--no-server` is passed,
- supports `w` to start watching, `s` to stop watching, `c` to clean, and `q` to quit.
The TUI requires an interactive terminal.
## Notes Format
This generator is opinionated toward the current note pipeline in this repository:
- Markdown is parsed with Pandoc and custom theorem/callout handling.
- math is rendered with MathML. looks good in firefox
- sidenotes are supported
- spacing between CJK chars and ascii is automatically handled by a filter.
- Citations are processed through `reference.bib` and the bundled `bib_style.csl`.
- `math-macros.md` is injected before parsing so note content and theorem titles can use the same macros.
- Notes are rendered with the bundled templates and stylesheet set.

View File

@@ -124,9 +124,9 @@ preprocessTheorems (Div attr xs)
attr' = addAttr attr "type" theoremType
preprocessTheorems x = return x
theoremFilter :: Pandoc -> Pandoc
theoremFilter doc =
walk makeTheorem $
theoremFilter :: Text -> Pandoc -> Pandoc
theoremFilter mathMacros doc =
walk (makeTheorem mathMacros) $
autorefFilter $
evalState (walkM preprocessTheorems normalizedDoc) 1
where
@@ -171,24 +171,13 @@ autorefFilter x = walk (autoref links) x
where
links = query theoremIndex x
-- processCitations works on AST. If you want to use citations in theorem name,
-- then you need to convert citations there to AST as well and then use processCitations\
-- Thus one need to apply the theorem filter first.
-- autoref still does not work.
mathMacros :: Text
mathMacros = unsafePerformIO (pack <$> readFile "math-macros.md")
{-# NOINLINE mathMacros #-}
prependMacros :: Text -> Text -> Text
prependMacros macros body = macros <> "\n\n" <> body
prependMathMacros :: Text -> Text
prependMathMacros = prependMacros mathMacros
thmNamePandoc :: Text -> Pandoc
thmNamePandoc x =
thmNamePandoc :: Text -> Text -> Pandoc
thmNamePandoc mathMacros x =
fromRight (Pandoc nullMeta []) . runPure $
readMarkdown chaoDocRead (prependMathMacros x)
readMarkdown chaoDocRead (prependMacros mathMacros x)
obsidianTheoremFilter :: Pandoc -> Pandoc
obsidianTheoremFilter = attachStandaloneLabels . walk rewriteObsidianBlockQuote
@@ -390,8 +379,8 @@ unsnoc (x : xs) = do
(prefix, lastElem) <- unsnoc xs
return (x : prefix, lastElem)
makeTheorem :: Block -> Block
makeTheorem (Div attr xs)
makeTheorem :: Text -> Block -> Block
makeTheorem mathMacros (Div attr xs)
| isNothing t = Div attr xs
| otherwise = Div (addClass attr "theorem-environment") (Plain [header] : xs)
where
@@ -408,26 +397,23 @@ makeTheorem (Div attr xs)
nametext =
if isNothing name
then Str ""
else Span (addClass nullAttr "name") (pandocToInline $ thmNamePandoc $ fromJust name)
makeTheorem x = x
else Span (addClass nullAttr "name") (pandocToInline $ thmNamePandoc mathMacros $ fromJust name)
makeTheorem _ x = x
-- bib from https://github.com/chaoxu/chaoxu.github.io/tree/develop
cslFile :: String
cslFile = "bib_style.csl"
bibFile :: String
bibFile :: T.Text
bibFile = "reference.bib"
chaoDocPandocCompiler :: Compiler (Item Pandoc)
chaoDocPandocCompiler = do
chaoDocPandocCompiler :: FilePath -> Compiler (Item Pandoc)
chaoDocPandocCompiler cslPath = do
macros <- T.pack <$> loadBody "math-macros.md"
void (loadBody "reference.bib" :: Compiler String)
body <- getResourceBody
let bodyWithMacros =
fmap (T.unpack . prependMacros macros . T.pack) body
myReadPandocBiblio chaoDocRead (T.pack cslFile) (T.pack bibFile) myFilter bodyWithMacros
myReadPandocBiblio chaoDocRead (T.pack cslPath) bibFile (myFilter macros) bodyWithMacros
chaoDocCompiler :: Compiler (Item String)
chaoDocCompiler = chaoDocPandocCompiler <&> writePandocWith chaoDocWrite
chaoDocCompiler :: FilePath -> Compiler (Item String)
chaoDocCompiler cslPath = chaoDocPandocCompiler cslPath <&> writePandocWith chaoDocWrite
addMeta :: T.Text -> MetaValue -> Pandoc -> Pandoc
addMeta name value (Pandoc meta a) =
@@ -465,8 +451,9 @@ myReadPandocBiblio ropt csl biblio pdfilter item = do
-- let a x = itemSetBody (pandoc' x)
return $ fmap (const pandoc') item
myFilter :: Pandoc -> Pandoc
myFilter = usingSideNotesHTML chaoDocWrite . theoremFilter . panguFilter . displayMathFilter
myFilter :: Text -> Pandoc -> Pandoc
myFilter mathMacros =
usingSideNotesHTML chaoDocWrite . theoremFilter mathMacros . panguFilter . displayMathFilter
-- pangu filter
lastChar :: Inline -> Maybe Char

File diff suppressed because it is too large Load Diff

View File

@@ -3,6 +3,7 @@
<meta name="viewport" content="width=device-width, initial-scale=1" />
<meta name="googlebot" content="noindex" />
<title>$title$</title>
<link rel="icon" href="/favicon.ico" />
<link rel="stylesheet" href="/css/fonts.css" />
<link rel="stylesheet" href="/css/default.css" />
<link rel="stylesheet" href="/css/pygentize.css" />

View File

@@ -7,6 +7,7 @@
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta name="googlebot" content="noindex">
<title></title>
<link rel="icon" href="/favicon.ico" />
<link rel="stylesheet" href="/css/fonts.css" />
<link rel="stylesheet" href="/css/default.css" />
<link rel="stylesheet" href="/css/pygentize.css" />

View File

@@ -11,6 +11,7 @@ $partial("templates/head.html")$
that are big enough -->
<div id="contents-big">
<p class="mini-header">Notes <a id="up-arrow" href="/"></a></p>
$partial("templates/notes-list.html")$
<p class="mini-header">Contents <a id="up-arrow" href="#"></a></p>
$toc$
</div>

View File

@@ -0,0 +1,7 @@
<ul class="notes-list">
$for(notes)$
<li>
<a href="$url$">$title$</a>
</li>
$endfor$
</ul>

View File

@@ -1,8 +1,2 @@
<h1 class="pagetitle">$title$</h1>
<ul>
$for(posts)$
<li>
<a href="$url$">$title$</a>
</li>
$endfor$
</ul>
$partial("templates/notes-list.html")$