mirror of
http://101.35.51.105:3000/congyu/Hakysidian.git
synced 2026-04-28 05:50:49 +08:00
Compare commits
3 Commits
3652459503
...
master
| Author | SHA1 | Date | |
|---|---|---|---|
| 3f0d36015b | |||
| 3a3fd97055 | |||
| aa05d73c9c |
+19
-6
@@ -11,19 +11,19 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.theorem-header .index:before {
|
.theorem-header .index:before {
|
||||||
content: ' ';
|
content: " ";
|
||||||
}
|
}
|
||||||
|
|
||||||
.theorem-header .name:before {
|
.theorem-header .name:before {
|
||||||
content: ' (';
|
content: " (";
|
||||||
}
|
}
|
||||||
|
|
||||||
.theorem-header .name:after {
|
.theorem-header .name:after {
|
||||||
content: ')';
|
content: ")";
|
||||||
}
|
}
|
||||||
|
|
||||||
.theorem-header:after {
|
.theorem-header:after {
|
||||||
content: '.\2002\2002';
|
content: ".\2002\2002";
|
||||||
}
|
}
|
||||||
|
|
||||||
.theorem-header + p {
|
.theorem-header + p {
|
||||||
@@ -42,12 +42,25 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.Proof:after {
|
.Proof:after {
|
||||||
content: '∎';
|
content: "∎";
|
||||||
position: absolute;
|
position: absolute;
|
||||||
right: 0px;
|
right: 0px;
|
||||||
bottom: 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 {
|
table.postindex {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
}
|
}
|
||||||
@@ -66,7 +79,7 @@ table.postindex td.right {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.header-section-number:after {
|
.header-section-number:after {
|
||||||
content: '.';
|
content: ".";
|
||||||
}
|
}
|
||||||
|
|
||||||
.csl-entry {
|
.csl-entry {
|
||||||
|
|||||||
+65
-118
@@ -3,10 +3,7 @@
|
|||||||
--color-tag1: gray;
|
--color-tag1: gray;
|
||||||
--color-tag2: darkolivegreen;
|
--color-tag2: darkolivegreen;
|
||||||
--color-bg: white;
|
--color-bg: white;
|
||||||
--color-link: #337ab7;
|
--color-link: #0000ee;
|
||||||
--color-linkhbg: #e6f0ff;
|
|
||||||
--color-linkh: #002266;
|
|
||||||
--color-bq: olivedrab;
|
|
||||||
--color-notice: #fb4f4f;
|
--color-notice: #fb4f4f;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -19,11 +16,18 @@
|
|||||||
|
|
||||||
html {
|
html {
|
||||||
scrollbar-gutter: stable;
|
scrollbar-gutter: stable;
|
||||||
|
scroll-behavior: smooth;
|
||||||
font-size: 14pt;
|
font-size: 14pt;
|
||||||
}
|
}
|
||||||
|
|
||||||
body {
|
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-optical-sizing: auto;
|
||||||
font-weight: 400;
|
font-weight: 400;
|
||||||
font-style: normal;
|
font-style: normal;
|
||||||
@@ -32,30 +36,28 @@ body {
|
|||||||
color: var(--color-text);
|
color: var(--color-text);
|
||||||
background-color: var(--color-bg);
|
background-color: var(--color-bg);
|
||||||
text-rendering: optimizeLegibility;
|
text-rendering: optimizeLegibility;
|
||||||
}
|
text-autospace: no-autospace; /*using pangu.hs*/
|
||||||
body.lang-zh {
|
|
||||||
text-align: left;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
body a {
|
body a {
|
||||||
color: var(--color-link);
|
|
||||||
text-decoration: none;
|
text-decoration: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
.text-space a:hover {
|
body a:hover {
|
||||||
background-color: var(--color-linkhbg);
|
text-decoration: underline;
|
||||||
color: var(--color-linkh);
|
|
||||||
text-decoration: none;
|
|
||||||
}
|
}
|
||||||
details {
|
details {
|
||||||
background-color: var(--color-linkhbg);
|
padding-left: 1em;
|
||||||
|
border: 2px solid var(--color-text);
|
||||||
}
|
}
|
||||||
summary:hover {
|
summary:hover {
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
}
|
}
|
||||||
|
|
||||||
/*mathML*/
|
/*mathML*/
|
||||||
.htmlmathparagraph, mtext,math {
|
.htmlmathparagraph,
|
||||||
|
mtext,
|
||||||
|
math {
|
||||||
font-family: Lete Sans Math;
|
font-family: Lete Sans Math;
|
||||||
}
|
}
|
||||||
.math-container,
|
.math-container,
|
||||||
@@ -63,7 +65,7 @@ summary:hover {
|
|||||||
display: block;
|
display: block;
|
||||||
overflow-x: auto;
|
overflow-x: auto;
|
||||||
overflow-y: hidden;
|
overflow-y: hidden;
|
||||||
padding: .5em;
|
padding: 0.5em;
|
||||||
}
|
}
|
||||||
|
|
||||||
.math-container.math-container-tagged {
|
.math-container.math-container-tagged {
|
||||||
@@ -72,7 +74,7 @@ summary:hover {
|
|||||||
align-items: center;
|
align-items: center;
|
||||||
column-gap: 1rem;
|
column-gap: 1rem;
|
||||||
overflow: visible;
|
overflow: visible;
|
||||||
padding: .5em 0;
|
padding: 0.5em 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.math-container.math-container-tagged .math-tag-spacer {
|
.math-container.math-container-tagged .math-tag-spacer {
|
||||||
@@ -83,7 +85,7 @@ summary:hover {
|
|||||||
min-width: 0;
|
min-width: 0;
|
||||||
overflow-x: auto;
|
overflow-x: auto;
|
||||||
overflow-y: hidden;
|
overflow-y: hidden;
|
||||||
padding: .5em 0;
|
padding: 0.5em 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.math-container.math-container-tagged .math-tag {
|
.math-container.math-container-tagged .math-tag {
|
||||||
@@ -104,66 +106,32 @@ summary:hover {
|
|||||||
font-variant-caps: small-caps;
|
font-variant-caps: small-caps;
|
||||||
}
|
}
|
||||||
|
|
||||||
p {
|
|
||||||
hyphens: auto;
|
|
||||||
}
|
|
||||||
|
|
||||||
a.url {
|
a.url {
|
||||||
word-break: break-all;
|
word-break: break-all;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
html body div.text-space main ul.post-list {
|
||||||
|
list-style-type: none;
|
||||||
|
padding-left: 1em;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* top bar */
|
||||||
header {
|
header {
|
||||||
font-weight: 400;
|
font-weight: 400;
|
||||||
font-family: "IosevkaC", sans-serif;
|
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 {
|
nav a {
|
||||||
font-size: 1.2rem;
|
|
||||||
/*margin-left: 0.5em;*/
|
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
vertical-align: middle;
|
|
||||||
text-decoration: none;
|
text-decoration: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
.uri {
|
.uri {
|
||||||
word-wrap: break-word;
|
word-wrap: break-word;
|
||||||
/* Legacy support */
|
|
||||||
overflow-wrap: break-word;
|
overflow-wrap: break-word;
|
||||||
/* Modern property */
|
|
||||||
word-break: break-all;
|
word-break: break-all;
|
||||||
/* Break long words if necessary */
|
|
||||||
white-space: normal;
|
white-space: normal;
|
||||||
/* Allow wrapping */
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
footer {
|
footer {
|
||||||
color: var(--color-text);
|
color: var(--color-text);
|
||||||
font-size: 0.8rem;
|
font-size: 0.8rem;
|
||||||
@@ -172,15 +140,6 @@ footer {
|
|||||||
padding-right: 1em;
|
padding-right: 1em;
|
||||||
}
|
}
|
||||||
|
|
||||||
h1,
|
|
||||||
h2,
|
|
||||||
h3,
|
|
||||||
h4,
|
|
||||||
h5,
|
|
||||||
h6 {
|
|
||||||
text-align: left;
|
|
||||||
}
|
|
||||||
|
|
||||||
.pagetitle {
|
.pagetitle {
|
||||||
font-size: 2rem;
|
font-size: 2rem;
|
||||||
font-weight: normal;
|
font-weight: normal;
|
||||||
@@ -194,22 +153,20 @@ h1 {
|
|||||||
font-size: 1.44rem;
|
font-size: 1.44rem;
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
font-style: normal;
|
font-style: normal;
|
||||||
text-align: left;
|
|
||||||
line-height: 100%;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
h2 {
|
h2 {
|
||||||
margin-top: 1em;
|
margin-top: 1em;
|
||||||
font-size: 1.2rem;
|
font-size: 1.2rem;
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
font-style: normal
|
font-style: normal;
|
||||||
}
|
}
|
||||||
|
|
||||||
h3 {
|
h3 {
|
||||||
margin-top: 1em;
|
margin-top: 1em;
|
||||||
font-size: 1rem;
|
font-size: 1rem;
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
font-style: normal
|
font-style: normal;
|
||||||
}
|
}
|
||||||
|
|
||||||
article .header {
|
article .header {
|
||||||
@@ -219,14 +176,7 @@ article .header {
|
|||||||
text-align: left;
|
text-align: left;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.info,
|
||||||
.info {
|
|
||||||
color: var(--color-tag2);
|
|
||||||
font-size: 1rem;
|
|
||||||
font-style: normal;
|
|
||||||
text-align: left;
|
|
||||||
}
|
|
||||||
|
|
||||||
.info a {
|
.info a {
|
||||||
color: var(--color-tag2);
|
color: var(--color-tag2);
|
||||||
font-size: 1rem;
|
font-size: 1rem;
|
||||||
@@ -247,33 +197,45 @@ section.body {
|
|||||||
line-height: normal;
|
line-height: normal;
|
||||||
}
|
}
|
||||||
|
|
||||||
blockquote {
|
/* table. copied from https://developer.mozilla.org/en-US/docs/Web/HTML/Reference/Elements/table */
|
||||||
margin: 1rem 0;
|
table {
|
||||||
padding: 0 0 0 1.5em;
|
border-collapse: collapse;
|
||||||
border-left: 3px solid var(--color-bq);
|
border: 2px solid rgb(140 140 140);
|
||||||
|
font-size: 0.8rem;
|
||||||
|
letter-spacing: 1px;
|
||||||
}
|
}
|
||||||
|
|
||||||
blockquote p {
|
caption {
|
||||||
margin: 0;
|
caption-side: bottom;
|
||||||
|
padding: 10px;
|
||||||
|
font-weight: bold;
|
||||||
}
|
}
|
||||||
|
|
||||||
ol {
|
thead,
|
||||||
padding-left: 2em;
|
tfoot {
|
||||||
}
|
background-color: rgb(228 240 245);
|
||||||
ul {
|
|
||||||
list-style-type: square;
|
|
||||||
padding-left: 2em;
|
|
||||||
}
|
|
||||||
li {
|
|
||||||
margin-bottom: 0.15em;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
table,
|
|
||||||
th,
|
th,
|
||||||
td {
|
td {
|
||||||
border: 1px solid darkolivegreen;
|
border: 1px solid rgb(160 160 160);
|
||||||
border-collapse: collapse;
|
padding: 8px 10px;
|
||||||
text-align: left;
|
}
|
||||||
|
|
||||||
|
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 {
|
figure {
|
||||||
@@ -284,23 +246,11 @@ figure {
|
|||||||
max-width: 80%;
|
max-width: 80%;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
figcaption {
|
|
||||||
/* font: italic smaller sans-serif; */
|
|
||||||
padding: 3px;
|
|
||||||
text-align: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.caption {
|
|
||||||
display: none
|
|
||||||
}
|
|
||||||
|
|
||||||
.centerimg img {
|
.centerimg img {
|
||||||
margin: 0 auto 0 auto;
|
margin: 0 auto 0 auto;
|
||||||
display: block;
|
display: block;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
div.highlight,
|
div.highlight,
|
||||||
pre code {
|
pre code {
|
||||||
margin: auto;
|
margin: auto;
|
||||||
@@ -320,18 +270,16 @@ code {
|
|||||||
text-rendering: optimizeSpeed;
|
text-rendering: optimizeSpeed;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
.draft-notice {
|
.draft-notice {
|
||||||
color: var(--color-notice);
|
color: var(--color-notice);
|
||||||
margin: 1em auto;
|
margin: 1em auto;
|
||||||
text-align: center
|
text-align: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
.subtitle {
|
.subtitle {
|
||||||
text-align: left;
|
text-align: left;
|
||||||
font-size: 1.2rem;
|
font-size: 1.2rem;
|
||||||
margin-top: 0
|
margin-top: 0;
|
||||||
}
|
}
|
||||||
.gallery {
|
.gallery {
|
||||||
margin-top: 2em;
|
margin-top: 2em;
|
||||||
@@ -401,7 +349,7 @@ code {
|
|||||||
padding-left: 1em;
|
padding-left: 1em;
|
||||||
line-height: 1.2;
|
line-height: 1.2;
|
||||||
list-style-type: decimal;
|
list-style-type: decimal;
|
||||||
margin-left: 0
|
margin-left: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
div#contents ul.notes-list,
|
div#contents ul.notes-list,
|
||||||
@@ -415,7 +363,7 @@ code {
|
|||||||
}
|
}
|
||||||
|
|
||||||
div#contents-big li + li {
|
div#contents-big li + li {
|
||||||
margin-top: 0.5em
|
margin-top: 0.5em;
|
||||||
}
|
}
|
||||||
|
|
||||||
div#contents-big {
|
div#contents-big {
|
||||||
@@ -428,7 +376,7 @@ code {
|
|||||||
margin-right: 4em;
|
margin-right: 4em;
|
||||||
position: sticky;
|
position: sticky;
|
||||||
top: 5rem;
|
top: 5rem;
|
||||||
left: 100%
|
left: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
div#contents-big .mini-header {
|
div#contents-big .mini-header {
|
||||||
@@ -452,7 +400,6 @@ code {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@media print {
|
@media print {
|
||||||
|
|
||||||
.no-print,
|
.no-print,
|
||||||
.no-print * {
|
.no-print * {
|
||||||
display: none !important;
|
display: none !important;
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
|
|
||||||
/* fonts */
|
/* fonts */
|
||||||
|
|
||||||
@font-face {
|
@font-face {
|
||||||
|
|||||||
@@ -1,3 +1,14 @@
|
|||||||
|
|
||||||
|
# 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
|
||||||
|
|
||||||
`hakysidian` is a static site generator for note projects.
|
`hakysidian` is a static site generator for note projects.
|
||||||
@@ -54,7 +65,7 @@ cabal install exe:hakysidian
|
|||||||
|
|
||||||
## Commands
|
## Commands
|
||||||
|
|
||||||
The CLI mirrors the common Hakyll workflow:
|
The default CLI mirrors the common Hakyll workflow:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
hakysidian build
|
hakysidian build
|
||||||
@@ -70,28 +81,41 @@ hakysidian watch --host 127.0.0.1 --port 8000
|
|||||||
hakysidian watch --no-server
|
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:
|
What each command does:
|
||||||
|
|
||||||
- `build`: incremental site build.
|
- `build`: incremental site build.
|
||||||
- `clean`: removes generated output and cache.
|
- `clean`: removes generated output and cache.
|
||||||
- `rebuild`: clears output/cache and builds from scratch.
|
- `rebuild`: clears output/cache and builds from scratch.
|
||||||
- `watch`: shows an in-place terminal dashboard, watches project inputs, and rebuilds automatically on change.
|
- `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 Mode
|
## Watch And TUI
|
||||||
|
|
||||||
`watch` tracks:
|
Both `watch` and `-tui` work against the same project inputs:
|
||||||
|
|
||||||
- `notes/**`
|
- `notes/**`
|
||||||
- `reference.bib`
|
- `reference.bib`
|
||||||
- `math-macros.md`
|
- `math-macros.md`
|
||||||
- `images/**`
|
- `images/**`
|
||||||
|
|
||||||
The watch UI:
|
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 terminal’s current size to keep the dashboard within the visible screen,
|
- uses the terminal’s current size to keep the dashboard within the visible screen,
|
||||||
- keeps recent build output in a bounded activity pane,
|
- keeps recent build output in a bounded activity pane,
|
||||||
- avoids scrolling raw Hakyll logs through the terminal,
|
- can start a local preview server unless `--no-server` is passed,
|
||||||
- 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
|
## Notes Format
|
||||||
|
|
||||||
|
|||||||
+159
-69
@@ -9,7 +9,7 @@ import ChaoDoc
|
|||||||
import Control.Concurrent (forkIO, threadDelay)
|
import Control.Concurrent (forkIO, threadDelay)
|
||||||
import Control.Exception (SomeException, bracket_, try)
|
import Control.Exception (SomeException, bracket_, try)
|
||||||
import Control.Monad (filterM, unless, void, when)
|
import Control.Monad (filterM, unless, void, when)
|
||||||
import Data.Char (isSpace)
|
import Data.Char (isSpace, toLower)
|
||||||
import Data.IORef (IORef, newIORef, readIORef, writeIORef)
|
import Data.IORef (IORef, newIORef, readIORef, writeIORef)
|
||||||
import Data.Kind (Type)
|
import Data.Kind (Type)
|
||||||
import Data.List (intercalate, isPrefixOf, sort, sortOn)
|
import Data.List (intercalate, isPrefixOf, sort, sortOn)
|
||||||
@@ -112,11 +112,24 @@ data CliCommand
|
|||||||
| CleanCommand
|
| CleanCommand
|
||||||
| HelpCommand
|
| HelpCommand
|
||||||
| RebuildCommand
|
| RebuildCommand
|
||||||
|
| TuiCommand WatchSettings
|
||||||
| WatchCommand WatchSettings
|
| WatchCommand WatchSettings
|
||||||
|
|
||||||
|
type TuiAction :: Type
|
||||||
|
data TuiAction
|
||||||
|
= TuiClean
|
||||||
|
| TuiQuit
|
||||||
|
| TuiStartWatching
|
||||||
|
| TuiStopWatching
|
||||||
|
|
||||||
type FileSnapshot :: Type
|
type FileSnapshot :: Type
|
||||||
type FileSnapshot = M.Map FilePath UTCTime
|
type FileSnapshot = M.Map FilePath UTCTime
|
||||||
|
|
||||||
|
type TuiWatchState :: Type
|
||||||
|
data TuiWatchState
|
||||||
|
= TuiWatchStopped
|
||||||
|
| TuiWatching FileSnapshot
|
||||||
|
|
||||||
type ServerStatus :: Type
|
type ServerStatus :: Type
|
||||||
data ServerStatus
|
data ServerStatus
|
||||||
= ServerDisabled
|
= ServerDisabled
|
||||||
@@ -203,14 +216,17 @@ main = do
|
|||||||
Right RebuildCommand -> do
|
Right RebuildCommand -> do
|
||||||
validateProject projectRoot
|
validateProject projectRoot
|
||||||
exitWith =<< runSiteCommand config rebuildOptions cslPath
|
exitWith =<< runSiteCommand config rebuildOptions cslPath
|
||||||
|
Right (TuiCommand watchSettings) -> do
|
||||||
|
validateProject projectRoot
|
||||||
|
exitWith =<< runTui projectRoot config cslPath watchSettings
|
||||||
Right (WatchCommand watchSettings) -> do
|
Right (WatchCommand watchSettings) -> do
|
||||||
validateProject projectRoot
|
validateProject projectRoot
|
||||||
exitWith =<< runWatch projectRoot config cslPath watchSettings
|
exitWith =<< runSiteCommand config (watchOptions watchSettings) cslPath
|
||||||
|
|
||||||
usageText :: String
|
usageText :: String
|
||||||
usageText =
|
usageText =
|
||||||
unlines
|
unlines
|
||||||
[ "usage: hakysidian [build|clean|rebuild|watch [--host HOST] [--port PORT] [--no-server]]",
|
[ "usage: hakysidian [build|clean|rebuild|watch [--host HOST] [--port PORT] [--no-server]|-tui [--host HOST] [--port PORT] [--no-server]]",
|
||||||
"",
|
"",
|
||||||
"Run inside a project directory containing notes/, reference.bib, math-macros.md, and optional images/."
|
"Run inside a project directory containing notes/, reference.bib, math-macros.md, and optional images/."
|
||||||
]
|
]
|
||||||
@@ -223,6 +239,8 @@ parseCliCommand config args
|
|||||||
["build"] -> Right BuildCommand
|
["build"] -> Right BuildCommand
|
||||||
["clean"] -> Right CleanCommand
|
["clean"] -> Right CleanCommand
|
||||||
["rebuild"] -> Right RebuildCommand
|
["rebuild"] -> Right RebuildCommand
|
||||||
|
"-tui" : rest -> Right (TuiCommand (parseWatchSettings config rest))
|
||||||
|
"--tui" : rest -> Right (TuiCommand (parseWatchSettings config rest))
|
||||||
"watch" : rest -> Right (WatchCommand (parseWatchSettings config rest))
|
"watch" : rest -> Right (WatchCommand (parseWatchSettings config rest))
|
||||||
command : _ -> Left ("Unknown command: " <> command)
|
command : _ -> Left ("Unknown command: " <> command)
|
||||||
|
|
||||||
@@ -251,12 +269,12 @@ validateProject projectRoot = do
|
|||||||
initialDashboardState :: DashboardState
|
initialDashboardState :: DashboardState
|
||||||
initialDashboardState =
|
initialDashboardState =
|
||||||
DashboardState
|
DashboardState
|
||||||
{ dashboardStatus = "starting",
|
{ dashboardStatus = "idle",
|
||||||
dashboardLastChange = "waiting for first build",
|
dashboardLastChange = "press w to start watching",
|
||||||
dashboardLastBuild = "pending",
|
dashboardLastBuild = "no command run yet",
|
||||||
dashboardLogLines =
|
dashboardLogLines =
|
||||||
[ "watcher ready",
|
[ "tui ready",
|
||||||
"watching notes/, reference.bib, math-macros.md, images/ (optional)"
|
"controls: w watch, s stop, c clean, q quit"
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -353,12 +371,11 @@ watchInputMode inputMode =
|
|||||||
renderWatchDashboard ::
|
renderWatchDashboard ::
|
||||||
IORef (Maybe (TerminalSize, ServerStatus, DashboardState)) ->
|
IORef (Maybe (TerminalSize, ServerStatus, DashboardState)) ->
|
||||||
FilePath ->
|
FilePath ->
|
||||||
Configuration ->
|
|
||||||
WatchSettings ->
|
WatchSettings ->
|
||||||
IORef ServerStatus ->
|
IORef ServerStatus ->
|
||||||
DashboardState ->
|
DashboardState ->
|
||||||
IO ()
|
IO ()
|
||||||
renderWatchDashboard renderStateRef projectRoot config watchSettings serverStatusRef dashboard = do
|
renderWatchDashboard renderStateRef projectRoot watchSettings serverStatusRef dashboard = do
|
||||||
terminalSize <- getTerminalSize
|
terminalSize <- getTerminalSize
|
||||||
serverStatus <- readIORef serverStatusRef
|
serverStatus <- readIORef serverStatusRef
|
||||||
previousRenderState <- readIORef renderStateRef
|
previousRenderState <- readIORef renderStateRef
|
||||||
@@ -369,22 +386,20 @@ renderWatchDashboard renderStateRef projectRoot config watchSettings serverStatu
|
|||||||
border = "+" ++ replicate (cols - 2) '-' ++ "+"
|
border = "+" ++ replicate (cols - 2) '-' ++ "+"
|
||||||
infoRows =
|
infoRows =
|
||||||
[ dashboardRow cols ("Project : " ++ projectRoot),
|
[ dashboardRow cols ("Project : " ++ projectRoot),
|
||||||
-- dashboardRow cols ("Output : " ++ destinationDirectory config),
|
|
||||||
dashboardRow cols ("Preview : " ++ renderServerStatus watchSettings serverStatus),
|
dashboardRow cols ("Preview : " ++ renderServerStatus watchSettings serverStatus),
|
||||||
-- dashboardRow cols "Watch : notes/, reference.bib, math-macros.md, images/ (optional)",
|
dashboardRow cols ("Change : " ++ dashboardLastChange dashboard),
|
||||||
-- dashboardRow cols ("Change : " ++ dashboardLastChange dashboard),
|
dashboardRow cols ("Last op : " ++ dashboardLastBuild dashboard)
|
||||||
dashboardRow cols ("Build : " ++ dashboardLastBuild dashboard)
|
|
||||||
]
|
]
|
||||||
headerRows =
|
headerRows =
|
||||||
[ border,
|
[ border,
|
||||||
dashboardTitleRow cols "hakysidian watch" (dashboardStatus dashboard),
|
dashboardTitleRow cols "hakysidian tui" (dashboardStatus dashboard),
|
||||||
border
|
border
|
||||||
]
|
]
|
||||||
++ infoRows
|
++ infoRows
|
||||||
++ [border, dashboardRow cols "Recent activity", border]
|
++ [border, dashboardRow cols "Recent activity", border]
|
||||||
footerRows =
|
footerRows =
|
||||||
[ border,
|
[ border,
|
||||||
dashboardRow cols "Controls: q quit, Ctrl-C interrupt",
|
dashboardRow cols "Controls: w watch, s stop, c clean, q quit, Ctrl-C interrupt",
|
||||||
border
|
border
|
||||||
]
|
]
|
||||||
availableLogRows = max 1 (rows - length headerRows - length footerRows)
|
availableLogRows = max 1 (rows - length headerRows - length footerRows)
|
||||||
@@ -483,20 +498,20 @@ watchLoopDelayMicros = 1000000
|
|||||||
watchInputPollMicros :: Int
|
watchInputPollMicros :: Int
|
||||||
watchInputPollMicros = 100000
|
watchInputPollMicros = 100000
|
||||||
|
|
||||||
waitForWatchQuit :: Bool -> Int -> IO Bool
|
waitForTuiAction :: Bool -> Int -> IO (Maybe TuiAction)
|
||||||
waitForWatchQuit watchInputEnabled remainingMicros
|
waitForTuiAction watchInputEnabled remainingMicros
|
||||||
| remainingMicros <= 0 = pure False
|
| remainingMicros <= 0 = pure Nothing
|
||||||
| otherwise = do
|
| otherwise = do
|
||||||
shouldQuit <- pollWatchQuit watchInputEnabled
|
nextAction <- pollTuiAction watchInputEnabled
|
||||||
if shouldQuit
|
case nextAction of
|
||||||
then pure True
|
Just action -> pure (Just action)
|
||||||
else do
|
Nothing -> do
|
||||||
threadDelay (min watchInputPollMicros remainingMicros)
|
threadDelay (min watchInputPollMicros remainingMicros)
|
||||||
waitForWatchQuit watchInputEnabled (remainingMicros - watchInputPollMicros)
|
waitForTuiAction watchInputEnabled (remainingMicros - watchInputPollMicros)
|
||||||
|
|
||||||
pollWatchQuit :: Bool -> IO Bool
|
pollTuiAction :: Bool -> IO (Maybe TuiAction)
|
||||||
pollWatchQuit watchInputEnabled
|
pollTuiAction watchInputEnabled
|
||||||
| not watchInputEnabled = pure False
|
| not watchInputEnabled = pure Nothing
|
||||||
| otherwise = drainInput
|
| otherwise = drainInput
|
||||||
where
|
where
|
||||||
drainInput = do
|
drainInput = do
|
||||||
@@ -504,10 +519,18 @@ pollWatchQuit watchInputEnabled
|
|||||||
if hasInput
|
if hasInput
|
||||||
then do
|
then do
|
||||||
inputChar <- hGetChar stdin
|
inputChar <- hGetChar stdin
|
||||||
if inputChar == 'q'
|
case parseTuiAction inputChar of
|
||||||
then pure True
|
Just action -> pure (Just action)
|
||||||
else drainInput
|
Nothing -> drainInput
|
||||||
else pure False
|
else pure Nothing
|
||||||
|
|
||||||
|
parseTuiAction :: Char -> Maybe TuiAction
|
||||||
|
parseTuiAction inputChar = case toLower inputChar of
|
||||||
|
'c' -> Just TuiClean
|
||||||
|
'q' -> Just TuiQuit
|
||||||
|
's' -> Just TuiStopWatching
|
||||||
|
'w' -> Just TuiStartWatching
|
||||||
|
_ -> Nothing
|
||||||
|
|
||||||
trimTrailingSpace :: String -> String
|
trimTrailingSpace :: String -> String
|
||||||
trimTrailingSpace = reverse . dropWhile isSpace . reverse
|
trimTrailingSpace = reverse . dropWhile isSpace . reverse
|
||||||
@@ -546,54 +569,107 @@ cleanOptions = Options {verbosity = False, optCommand = Clean}
|
|||||||
rebuildOptions :: Options
|
rebuildOptions :: Options
|
||||||
rebuildOptions = Options {verbosity = False, optCommand = Rebuild}
|
rebuildOptions = Options {verbosity = False, optCommand = Rebuild}
|
||||||
|
|
||||||
|
watchOptions :: WatchSettings -> Options
|
||||||
|
watchOptions watchSettings =
|
||||||
|
Options
|
||||||
|
{ verbosity = False,
|
||||||
|
optCommand =
|
||||||
|
Watch
|
||||||
|
{ host = watchHost watchSettings,
|
||||||
|
port = watchPort watchSettings,
|
||||||
|
no_server = not (watchServerEnabled watchSettings)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
runSiteCommand :: Configuration -> Options -> FilePath -> IO ExitCode
|
runSiteCommand :: Configuration -> Options -> FilePath -> IO ExitCode
|
||||||
runSiteCommand config options cslPath =
|
runSiteCommand config options cslPath =
|
||||||
hakyllWithExitCodeAndArgs config options (siteRules cslPath)
|
hakyllWithExitCodeAndArgs config options (siteRules cslPath)
|
||||||
|
|
||||||
runWatch :: FilePath -> Configuration -> FilePath -> WatchSettings -> IO ExitCode
|
runTui :: FilePath -> Configuration -> FilePath -> WatchSettings -> IO ExitCode
|
||||||
runWatch projectRoot config _cslPath watchSettings = do
|
runTui projectRoot config _cslPath watchSettings = do
|
||||||
stdoutInteractive <- hIsTerminalDevice stdout
|
stdoutInteractive <- hIsTerminalDevice stdout
|
||||||
stdinInteractive <- hIsTerminalDevice stdin
|
stdinInteractive <- hIsTerminalDevice stdin
|
||||||
let watchInputEnabled = stdoutInteractive && stdinInteractive
|
let watchInputEnabled = stdoutInteractive && stdinInteractive
|
||||||
|
if watchInputEnabled
|
||||||
|
then
|
||||||
withWatchTui do
|
withWatchTui do
|
||||||
serverStatusRef <- newIORef initialServerStatus
|
serverStatusRef <- newIORef initialServerStatus
|
||||||
renderStateRef <- newIORef Nothing
|
renderStateRef <- newIORef Nothing
|
||||||
startPreviewServer config watchSettings serverStatusRef
|
startPreviewServer config watchSettings serverStatusRef
|
||||||
renderWatchDashboard renderStateRef projectRoot config watchSettings serverStatusRef initialDashboardState
|
renderWatchDashboard renderStateRef projectRoot watchSettings serverStatusRef initialDashboardState
|
||||||
(_, initialDashboard) <-
|
tuiLoop watchInputEnabled renderStateRef serverStatusRef TuiWatchStopped initialDashboardState
|
||||||
runWatchBuild
|
else do
|
||||||
"build"
|
putStrLn "hakysidian -tui requires an interactive terminal."
|
||||||
"initial build"
|
pure (ExitFailure 1)
|
||||||
"initial build"
|
|
||||||
projectRoot
|
|
||||||
config
|
|
||||||
watchSettings
|
|
||||||
renderStateRef
|
|
||||||
serverStatusRef
|
|
||||||
initialDashboardState
|
|
||||||
initialSnapshot <- snapshotInputs projectRoot
|
|
||||||
watchLoop watchInputEnabled renderStateRef serverStatusRef initialSnapshot initialDashboard
|
|
||||||
where
|
where
|
||||||
initialServerStatus
|
initialServerStatus
|
||||||
| watchServerEnabled watchSettings = ServerStarting
|
| watchServerEnabled watchSettings = ServerStarting
|
||||||
| otherwise = ServerDisabled
|
| otherwise = ServerDisabled
|
||||||
|
|
||||||
watchLoop ::
|
tuiLoop ::
|
||||||
Bool ->
|
Bool ->
|
||||||
IORef (Maybe (TerminalSize, ServerStatus, DashboardState)) ->
|
IORef (Maybe (TerminalSize, ServerStatus, DashboardState)) ->
|
||||||
IORef ServerStatus ->
|
IORef ServerStatus ->
|
||||||
FileSnapshot ->
|
TuiWatchState ->
|
||||||
DashboardState ->
|
DashboardState ->
|
||||||
IO ExitCode
|
IO ExitCode
|
||||||
watchLoop watchInputEnabled renderStateRef serverStatusRef previousSnapshot dashboard = do
|
tuiLoop watchInputEnabled renderStateRef serverStatusRef watchState dashboard = do
|
||||||
renderWatchDashboard renderStateRef projectRoot config watchSettings serverStatusRef dashboard
|
renderWatchDashboard renderStateRef projectRoot watchSettings serverStatusRef dashboard
|
||||||
shouldQuit <- waitForWatchQuit watchInputEnabled watchLoopDelayMicros
|
nextAction <- waitForTuiAction watchInputEnabled watchLoopDelayMicros
|
||||||
if shouldQuit
|
case nextAction of
|
||||||
then pure ExitSuccess
|
Just TuiQuit -> pure ExitSuccess
|
||||||
else do
|
Just TuiStartWatching -> case watchState of
|
||||||
|
TuiWatching _ ->
|
||||||
|
tuiLoop watchInputEnabled renderStateRef serverStatusRef watchState dashboard
|
||||||
|
TuiWatchStopped -> do
|
||||||
|
(_, nextDashboard) <-
|
||||||
|
runDashboardCommand
|
||||||
|
"rebuild"
|
||||||
|
"watch start"
|
||||||
|
"manual start"
|
||||||
|
"building (watch start)"
|
||||||
|
(watchCommandStatus "watch start")
|
||||||
|
projectRoot
|
||||||
|
watchSettings
|
||||||
|
renderStateRef
|
||||||
|
serverStatusRef
|
||||||
|
dashboard
|
||||||
|
nextSnapshot <- snapshotInputs projectRoot
|
||||||
|
tuiLoop watchInputEnabled renderStateRef serverStatusRef (TuiWatching nextSnapshot) nextDashboard
|
||||||
|
Just TuiStopWatching -> case watchState of
|
||||||
|
TuiWatchStopped ->
|
||||||
|
tuiLoop watchInputEnabled renderStateRef serverStatusRef watchState dashboard
|
||||||
|
TuiWatching _ -> do
|
||||||
|
nextDashboard <-
|
||||||
|
appendDashboardMessage
|
||||||
|
( dashboard
|
||||||
|
{ dashboardStatus = "idle",
|
||||||
|
dashboardLastChange = "watch stopped"
|
||||||
|
}
|
||||||
|
)
|
||||||
|
"watch stopped"
|
||||||
|
tuiLoop watchInputEnabled renderStateRef serverStatusRef TuiWatchStopped nextDashboard
|
||||||
|
Just TuiClean -> do
|
||||||
|
(_, nextDashboard) <-
|
||||||
|
runDashboardCommand
|
||||||
|
"clean"
|
||||||
|
"clean"
|
||||||
|
"manual clean"
|
||||||
|
"cleaning"
|
||||||
|
cleanCommandStatus
|
||||||
|
projectRoot
|
||||||
|
watchSettings
|
||||||
|
renderStateRef
|
||||||
|
serverStatusRef
|
||||||
|
dashboard
|
||||||
|
tuiLoop watchInputEnabled renderStateRef serverStatusRef TuiWatchStopped nextDashboard
|
||||||
|
Nothing -> case watchState of
|
||||||
|
TuiWatchStopped ->
|
||||||
|
tuiLoop watchInputEnabled renderStateRef serverStatusRef watchState dashboard
|
||||||
|
TuiWatching previousSnapshot -> do
|
||||||
nextSnapshot <- snapshotInputs projectRoot
|
nextSnapshot <- snapshotInputs projectRoot
|
||||||
if nextSnapshot == previousSnapshot
|
if nextSnapshot == previousSnapshot
|
||||||
then watchLoop watchInputEnabled renderStateRef serverStatusRef previousSnapshot dashboard
|
then tuiLoop watchInputEnabled renderStateRef serverStatusRef watchState dashboard
|
||||||
else do
|
else do
|
||||||
let changedFiles = diffSnapshots previousSnapshot nextSnapshot
|
let changedFiles = diffSnapshots previousSnapshot nextSnapshot
|
||||||
command :: String
|
command :: String
|
||||||
@@ -603,52 +679,66 @@ runWatch projectRoot config _cslPath watchSettings = do
|
|||||||
else "build"
|
else "build"
|
||||||
changeSummary = intercalate ", " changedFiles
|
changeSummary = intercalate ", " changedFiles
|
||||||
(_, nextDashboard) <-
|
(_, nextDashboard) <-
|
||||||
runWatchBuild
|
runDashboardCommand
|
||||||
command
|
command
|
||||||
command
|
command
|
||||||
changeSummary
|
changeSummary
|
||||||
|
("building (" ++ command ++ ")")
|
||||||
|
(watchCommandStatus command)
|
||||||
projectRoot
|
projectRoot
|
||||||
config
|
|
||||||
watchSettings
|
watchSettings
|
||||||
renderStateRef
|
renderStateRef
|
||||||
serverStatusRef
|
serverStatusRef
|
||||||
dashboard
|
dashboard
|
||||||
watchLoop watchInputEnabled renderStateRef serverStatusRef nextSnapshot nextDashboard
|
tuiLoop watchInputEnabled renderStateRef serverStatusRef (TuiWatching nextSnapshot) nextDashboard
|
||||||
|
|
||||||
|
watchCommandStatus :: String -> ExitCode -> String
|
||||||
|
watchCommandStatus label exitCode
|
||||||
|
| exitCode == ExitSuccess = "watching"
|
||||||
|
| otherwise = "watching after failed " ++ label
|
||||||
|
|
||||||
|
cleanCommandStatus :: ExitCode -> String
|
||||||
|
cleanCommandStatus exitCode
|
||||||
|
| exitCode == ExitSuccess = "idle"
|
||||||
|
| otherwise = "idle after failed clean"
|
||||||
|
|
||||||
renderBuildResult :: ExitCode -> String
|
renderBuildResult :: ExitCode -> String
|
||||||
renderBuildResult ExitSuccess = "success"
|
renderBuildResult ExitSuccess = "success"
|
||||||
renderBuildResult (ExitFailure code) = "failed (" ++ show code ++ ")"
|
renderBuildResult (ExitFailure code) = "failed (" ++ show code ++ ")"
|
||||||
|
|
||||||
runWatchBuild ::
|
appendDashboardMessage :: DashboardState -> String -> IO DashboardState
|
||||||
|
appendDashboardMessage dashboard message = do
|
||||||
|
timestamp <- watchTimestamp
|
||||||
|
pure (appendLogBatch dashboard message timestamp [])
|
||||||
|
|
||||||
|
runDashboardCommand ::
|
||||||
String ->
|
String ->
|
||||||
String ->
|
String ->
|
||||||
String ->
|
String ->
|
||||||
|
String ->
|
||||||
|
(ExitCode -> String) ->
|
||||||
FilePath ->
|
FilePath ->
|
||||||
Configuration ->
|
|
||||||
WatchSettings ->
|
WatchSettings ->
|
||||||
IORef (Maybe (TerminalSize, ServerStatus, DashboardState)) ->
|
IORef (Maybe (TerminalSize, ServerStatus, DashboardState)) ->
|
||||||
IORef ServerStatus ->
|
IORef ServerStatus ->
|
||||||
DashboardState ->
|
DashboardState ->
|
||||||
IO (ExitCode, DashboardState)
|
IO (ExitCode, DashboardState)
|
||||||
runWatchBuild command label changeSummary projectRoot config watchSettings renderStateRef serverStatusRef dashboard = do
|
runDashboardCommand command label changeSummary runningStatus completedStatus projectRoot watchSettings renderStateRef serverStatusRef dashboard = do
|
||||||
startedAt <- watchTimestamp
|
startedAt <- watchTimestamp
|
||||||
let runningDashboard =
|
let runningDashboard =
|
||||||
dashboard
|
dashboard
|
||||||
{ dashboardStatus = "building (" ++ label ++ ")",
|
{ dashboardStatus = runningStatus,
|
||||||
dashboardLastChange = changeSummary,
|
dashboardLastChange = changeSummary,
|
||||||
dashboardLastBuild = "running since " ++ startedAt
|
dashboardLastBuild = "running since " ++ startedAt
|
||||||
}
|
}
|
||||||
renderWatchDashboard renderStateRef projectRoot config watchSettings serverStatusRef runningDashboard
|
renderWatchDashboard renderStateRef projectRoot watchSettings serverStatusRef runningDashboard
|
||||||
(exitCode, buildLines) <- runCapturedSiteCommand projectRoot command
|
(exitCode, buildLines) <- runCapturedSiteCommand projectRoot command
|
||||||
finishedAt <- watchTimestamp
|
finishedAt <- watchTimestamp
|
||||||
let loggedDashboard =
|
let loggedDashboard =
|
||||||
appendLogBatch runningDashboard (label ++ ": " ++ changeSummary) finishedAt buildLines
|
appendLogBatch runningDashboard (label ++ ": " ++ changeSummary) finishedAt buildLines
|
||||||
completedDashboard =
|
completedDashboard =
|
||||||
loggedDashboard
|
loggedDashboard
|
||||||
{ dashboardStatus =
|
{ dashboardStatus = completedStatus exitCode,
|
||||||
if exitCode == ExitSuccess
|
|
||||||
then "watching"
|
|
||||||
else "watching after failed " ++ label,
|
|
||||||
dashboardLastBuild =
|
dashboardLastBuild =
|
||||||
renderBuildResult exitCode
|
renderBuildResult exitCode
|
||||||
++ " at "
|
++ " at "
|
||||||
@@ -656,7 +746,7 @@ runWatchBuild command label changeSummary projectRoot config watchSettings rende
|
|||||||
++ " via "
|
++ " via "
|
||||||
++ label
|
++ label
|
||||||
}
|
}
|
||||||
renderWatchDashboard renderStateRef projectRoot config watchSettings serverStatusRef completedDashboard
|
renderWatchDashboard renderStateRef projectRoot watchSettings serverStatusRef completedDashboard
|
||||||
pure (exitCode, completedDashboard)
|
pure (exitCode, completedDashboard)
|
||||||
|
|
||||||
startPreviewServer :: Configuration -> WatchSettings -> IORef ServerStatus -> IO ()
|
startPreviewServer :: Configuration -> WatchSettings -> IORef ServerStatus -> IO ()
|
||||||
|
|||||||
Reference in New Issue
Block a user