mirror of
http://101.35.51.105:3000/congyu/work_with_codex.git
synced 2026-04-27 22:30:50 +08:00
Add Hakyll site generator and assets
Add site executable and Haskell modules (site.hs, ChaoDoc.hs, SideNoteHTML.hs, Pangu.hs) to handle Pandoc/Hakyll compilation, theorem/sidenote processing and CJK spacing. Add CSS, font files, favicon, templates, Makefile, and a CSL bibliographic style. Update .gitignore to ignore build artifacts.
This commit is contained in:
+2
-1
@@ -1,5 +1,6 @@
|
||||
_*/
|
||||
|
||||
dist-newstyle/
|
||||
site
|
||||
.ds_store
|
||||
.vscode/
|
||||
*.sage.py
|
||||
|
||||
+130
@@ -0,0 +1,130 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<style xmlns="http://purl.org/net/xbiblio/csl" class="in-text" version="1.0" demote-non-dropping-particle="sort-only" default-locale="en-US">
|
||||
<!-- This style was edited with the Visual CSL Editor (http://editor.citationstyles.org/visualEditor/) -->
|
||||
<info>
|
||||
<title>Elsevier (numeric, with titles)</title>
|
||||
<id>http://www.zotero.org/styles/elsevier-numeric-with-titles</id>
|
||||
<link href="http://www.zotero.org/styles/elsevier-numeric-with-titles" rel="self"/>
|
||||
<link href="http://www.zotero.org/styles/elsevier-without-titles" rel="template"/>
|
||||
<link href="http://www.elsevier.com/journals/solid-state-electronics/0038-1101/guide-for-authors#68000" rel="documentation"/>
|
||||
<author>
|
||||
<name>Richard Karnesky</name>
|
||||
<email>karnesky+zotero@gmail.com</email>
|
||||
<uri>http://arc.nucapt.northwestern.edu/Richard_Karnesky</uri>
|
||||
</author>
|
||||
<contributor>
|
||||
<name>Rintze Zelle</name>
|
||||
<uri>http://twitter.com/rintzezelle</uri>
|
||||
</contributor>
|
||||
<category citation-format="numeric"/>
|
||||
<category field="generic-base"/>
|
||||
<summary>A style for many of Elsevier's journals that includes article titles in the reference list</summary>
|
||||
<updated>2025-06-05T07:59:51+00:00</updated>
|
||||
<rights license="http://creativecommons.org/licenses/by-sa/3.0/">This work is licensed under a Creative Commons Attribution-ShareAlike 3.0 License</rights>
|
||||
</info>
|
||||
<macro name="author">
|
||||
<names variable="author">
|
||||
<name initialize-with="." delimiter=", " delimiter-precedes-last="always"/>
|
||||
<label form="short" prefix=", "/>
|
||||
<substitute>
|
||||
<names variable="editor"/>
|
||||
<names variable="translator"/>
|
||||
</substitute>
|
||||
</names>
|
||||
</macro>
|
||||
<macro name="editor">
|
||||
<names variable="editor">
|
||||
<name initialize-with="." delimiter=", " delimiter-precedes-last="always"/>
|
||||
<label form="short" prefix=" (" text-case="capitalize-first" suffix=".)" strip-periods="true"/>
|
||||
</names>
|
||||
</macro>
|
||||
<macro name="year-date">
|
||||
<choose>
|
||||
<if variable="issued">
|
||||
<date variable="issued">
|
||||
<date-part name="year"/>
|
||||
</date>
|
||||
</if>
|
||||
<else>
|
||||
<text term="no date" form="short"/>
|
||||
</else>
|
||||
</choose>
|
||||
</macro>
|
||||
<macro name="publisher">
|
||||
<text variable="publisher" suffix=", "/>
|
||||
<text variable="publisher-place" suffix=", "/>
|
||||
<text macro="year-date"/>
|
||||
</macro>
|
||||
<macro name="edition">
|
||||
<choose>
|
||||
<if is-numeric="edition">
|
||||
<group delimiter=" ">
|
||||
<number variable="edition" form="ordinal"/>
|
||||
<text term="edition" form="short"/>
|
||||
</group>
|
||||
</if>
|
||||
<else>
|
||||
<text variable="edition"/>
|
||||
</else>
|
||||
</choose>
|
||||
</macro>
|
||||
<citation collapse="citation-number">
|
||||
<sort>
|
||||
<key variable="citation-number"/>
|
||||
</sort>
|
||||
<layout prefix="[" suffix="]" delimiter=",">
|
||||
<text variable="citation-number"/>
|
||||
</layout>
|
||||
</citation>
|
||||
<bibliography entry-spacing="0" second-field-align="flush" et-al-min="7" et-al-use-first="6">
|
||||
<layout suffix=".">
|
||||
<text variable="citation-number" prefix="[" suffix="]"/>
|
||||
<text macro="author" prefix=" " suffix=", "/>
|
||||
<choose>
|
||||
<if type="bill book graphic legal_case legislation motion_picture report song" match="any">
|
||||
<group delimiter=", ">
|
||||
<text variable="title" font-style="normal" font-weight="normal"/>
|
||||
<text macro="edition"/>
|
||||
<text macro="publisher"/>
|
||||
</group>
|
||||
</if>
|
||||
<else-if type="chapter paper-conference" match="any">
|
||||
<text variable="title" suffix=", "/>
|
||||
<text term="in" suffix=": "/>
|
||||
<text variable="container-title" form="short" text-case="title" font-style="italic" suffix=", "/>
|
||||
<text macro="edition" suffix=", "/>
|
||||
<text macro="publisher"/>
|
||||
<group delimiter=" ">
|
||||
<label variable="page" form="short" prefix=": "/>
|
||||
<text variable="page"/>
|
||||
</group>
|
||||
</else-if>
|
||||
<else-if type="patent">
|
||||
<group delimiter=", ">
|
||||
<text variable="title"/>
|
||||
<text variable="number"/>
|
||||
<text macro="year-date"/>
|
||||
</group>
|
||||
</else-if>
|
||||
<else-if type="thesis">
|
||||
<group delimiter=", ">
|
||||
<text variable="title"/>
|
||||
<text variable="genre"/>
|
||||
<text variable="publisher"/>
|
||||
<text macro="year-date"/>
|
||||
</group>
|
||||
</else-if>
|
||||
<else>
|
||||
<group delimiter=" ">
|
||||
<text variable="title" font-style="normal" font-weight="normal" suffix=","/>
|
||||
<text variable="container-title" form="short" text-case="title" font-style="italic" suffix="."/>
|
||||
<text variable="volume"/>
|
||||
<text macro="year-date" prefix="(" suffix=")"/>
|
||||
<text variable="page" form="short"/>
|
||||
</group>
|
||||
</else>
|
||||
</choose>
|
||||
<text variable="DOI" prefix=" "/>
|
||||
</layout>
|
||||
</bibliography>
|
||||
</style>
|
||||
@@ -0,0 +1,100 @@
|
||||
.theorem-environment {
|
||||
font-style: italic;
|
||||
margin-top: 1em;
|
||||
}
|
||||
|
||||
.theorem-header {
|
||||
font-weight: bold;
|
||||
font-style: normal;
|
||||
}
|
||||
|
||||
.theorem-header .index:before {
|
||||
content: ' ';
|
||||
}
|
||||
|
||||
.theorem-header .name:before {
|
||||
content: ' (';
|
||||
}
|
||||
|
||||
.theorem-header .name:after {
|
||||
content: ')';
|
||||
}
|
||||
|
||||
.theorem-header:after {
|
||||
content: '.\2002\2002';
|
||||
}
|
||||
|
||||
.theorem-header+p {
|
||||
display: inline;
|
||||
}
|
||||
|
||||
.Proof .type {
|
||||
font-style: italic;
|
||||
font-weight: normal;
|
||||
}
|
||||
|
||||
.Proof {
|
||||
font-style: normal;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.Proof:after {
|
||||
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%;
|
||||
}
|
||||
|
||||
table.postindex cite {
|
||||
font-style: normal;
|
||||
}
|
||||
|
||||
table.postindex td.right {
|
||||
text-align: right;
|
||||
width: 11ex;
|
||||
}
|
||||
|
||||
.header-section-number {
|
||||
margin-right: 10px;
|
||||
}
|
||||
|
||||
.header-section-number:after {
|
||||
content: '.';
|
||||
}
|
||||
|
||||
.csl-entry {
|
||||
display: table;
|
||||
width: 100%;
|
||||
table-layout: auto;
|
||||
}
|
||||
|
||||
.csl-left-margin {
|
||||
display: table-cell;
|
||||
padding-right: 0.5em;
|
||||
white-space: nowrap;
|
||||
width: 1px;
|
||||
}
|
||||
|
||||
.csl-right-inline {
|
||||
display: table-cell;
|
||||
}
|
||||
.csl-right-inline a{
|
||||
word-break: break-all;
|
||||
}
|
||||
+362
@@ -0,0 +1,362 @@
|
||||
:root {
|
||||
--color-text: black;
|
||||
--color-tag1: gray;
|
||||
--color-tag2: darkolivegreen;
|
||||
--color-bg: white;
|
||||
--color-notice: #fb4f4f;
|
||||
}
|
||||
|
||||
html {
|
||||
scrollbar-gutter: stable;
|
||||
scroll-behavior: smooth;
|
||||
font-size: 110%;
|
||||
}
|
||||
|
||||
body {
|
||||
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: 140%;
|
||||
color: var(--color-text);
|
||||
background-color: var(--color-bg);
|
||||
text-rendering: optimizeLegibility;
|
||||
}
|
||||
|
||||
body a {
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
body a:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
details {
|
||||
padding-left: 1em;
|
||||
border: 2px solid var(--color-text);
|
||||
}
|
||||
summary:hover {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
/*mathML*/
|
||||
.htmlmathparagraph, mtext,math {
|
||||
font-family: Lete Sans Math;
|
||||
}
|
||||
#math-container {
|
||||
display: block;
|
||||
overflow-x: auto;
|
||||
overflow-y: hidden;
|
||||
padding: .5em;
|
||||
}
|
||||
|
||||
.text-space .langtag {
|
||||
color: var(--color-tag1);
|
||||
}
|
||||
|
||||
.sc {
|
||||
font-variant-caps: small-caps;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
nav a {
|
||||
display: inline-block;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.uri {
|
||||
word-wrap: break-word;
|
||||
overflow-wrap: break-word;
|
||||
word-break: break-all;
|
||||
white-space: normal;
|
||||
}
|
||||
|
||||
footer {
|
||||
color: var(--color-text);
|
||||
font-size: 0.8rem;
|
||||
margin-top: 2em;
|
||||
text-align: right;
|
||||
padding-right: 1em;
|
||||
}
|
||||
|
||||
.pagetitle {
|
||||
font-size: 2rem;
|
||||
font-weight: normal;
|
||||
font-style: normal;
|
||||
text-align: left;
|
||||
line-height: 100%;
|
||||
}
|
||||
|
||||
h1 {
|
||||
margin-top: 1em;
|
||||
font-size: 1.44rem;
|
||||
font-weight: bold;
|
||||
font-style: normal;
|
||||
}
|
||||
|
||||
h2 {
|
||||
margin-top: 1em;
|
||||
font-size: 1.2rem;
|
||||
font-weight: bold;
|
||||
font-style: normal
|
||||
}
|
||||
|
||||
h3 {
|
||||
margin-top: 1em;
|
||||
font-size: 1rem;
|
||||
font-weight: bold;
|
||||
font-style: normal
|
||||
}
|
||||
|
||||
article .header {
|
||||
font-size: 1rem;
|
||||
font-style: normal;
|
||||
color: var(--color-tag1);
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
|
||||
.info,.info a {
|
||||
color: var(--color-tag2);
|
||||
font-size: 1rem;
|
||||
font-style: normal;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.info a:visited {
|
||||
color: var(--color-tag2);
|
||||
}
|
||||
|
||||
section.body {
|
||||
margin-top: 2rem;
|
||||
}
|
||||
|
||||
.ascii-art {
|
||||
font-family: monospace;
|
||||
line-height: normal;
|
||||
}
|
||||
|
||||
/* 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;
|
||||
}
|
||||
|
||||
caption {
|
||||
caption-side: bottom;
|
||||
padding: 10px;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
thead,
|
||||
tfoot {
|
||||
background-color: rgb(228 240 245);
|
||||
}
|
||||
|
||||
th,
|
||||
td {
|
||||
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 {
|
||||
display: flex;
|
||||
flex-flow: column;
|
||||
padding: 5px;
|
||||
margin: auto;
|
||||
max-width: 80%;
|
||||
}
|
||||
|
||||
.centerimg img {
|
||||
margin: 0 auto 0 auto;
|
||||
display: block;
|
||||
}
|
||||
|
||||
|
||||
div.highlight,
|
||||
pre code {
|
||||
margin: auto;
|
||||
padding: 10px;
|
||||
overflow: auto;
|
||||
display: block;
|
||||
}
|
||||
|
||||
code {
|
||||
font-family: "IosevkaC", monospace;
|
||||
margin: 0 auto;
|
||||
display: inline-block;
|
||||
padding: 0px 2px;
|
||||
border-radius: 2px;
|
||||
font-variant-ligatures: none;
|
||||
font-kerning: none;
|
||||
text-rendering: optimizeSpeed;
|
||||
}
|
||||
|
||||
|
||||
.draft-notice {
|
||||
color: var(--color-notice);
|
||||
margin: 1em auto;
|
||||
text-align: center
|
||||
}
|
||||
|
||||
|
||||
.subtitle {
|
||||
text-align: left;
|
||||
font-size: 1.2rem;
|
||||
margin-top: 0
|
||||
}
|
||||
.gallery {
|
||||
margin-top: 2em;
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.gallery img {
|
||||
width: 100%;
|
||||
max-width: 320px;
|
||||
display: block;
|
||||
}
|
||||
|
||||
/* phones -- no sidebar no sidenotes*/
|
||||
@media (max-width: 768px) {
|
||||
body {
|
||||
/* width: 90%; */
|
||||
margin: auto;
|
||||
padding: 0 5%;
|
||||
text-align: left;
|
||||
max-width: 876px;
|
||||
}
|
||||
mjx-container[display="true"]
|
||||
/*, .katex-display */ {
|
||||
overflow-x: auto;
|
||||
overflow-y: hidden;
|
||||
}
|
||||
|
||||
/* .katex-display>.katex>.katex-html>.tag {
|
||||
display: inline-block;
|
||||
position: relative;
|
||||
padding-left: 10pt;
|
||||
} */
|
||||
}
|
||||
|
||||
.toc {
|
||||
display: none;
|
||||
}
|
||||
|
||||
/* sidebar. no sidenotes */
|
||||
@media (min-width: 769px) {
|
||||
body {
|
||||
max-width: 1350px;
|
||||
display: -webkit-flex;
|
||||
-webkit-flex-flow: row wrap;
|
||||
display: -ms-flexbox;
|
||||
-ms-flex-flow: row wrap;
|
||||
flex-flow: row wrap;
|
||||
width: 95%;
|
||||
padding-right: 5%;
|
||||
margin: auto;
|
||||
}
|
||||
|
||||
.toc {
|
||||
margin-top: 5rem;
|
||||
margin-left: 0;
|
||||
margin-right: 0;
|
||||
width: 33%;
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
div#contents ul,
|
||||
div#contents-big ul {
|
||||
margin-top: 0.5em;
|
||||
margin-bottom: 0.5em;
|
||||
padding-left: 1em;
|
||||
line-height: 1.2;
|
||||
list-style-type: decimal;
|
||||
margin-left: 0
|
||||
}
|
||||
|
||||
div#contents-big ul ul {
|
||||
list-style-type: none;
|
||||
}
|
||||
|
||||
div#contents-big li+li {
|
||||
margin-top: 0.5em
|
||||
}
|
||||
|
||||
div#contents-big {
|
||||
font-size: 80%;
|
||||
padding-top: 0;
|
||||
padding-left: 1rem;
|
||||
text-align: left;
|
||||
max-width: 60%;
|
||||
clear: both;
|
||||
margin-right: 4em;
|
||||
position: sticky;
|
||||
top: 5rem;
|
||||
left: 100%
|
||||
}
|
||||
|
||||
div#contents-big .mini-header {
|
||||
font-weight: bold;
|
||||
margin: 0;
|
||||
font-variant: small-caps;
|
||||
}
|
||||
|
||||
.text-space {
|
||||
display: inline-block;
|
||||
width: 66%;
|
||||
max-width: 800px;
|
||||
}
|
||||
}
|
||||
/* sidebar+sidenotes */
|
||||
@media (min-width: 1200px) {
|
||||
body {
|
||||
width: 75%;
|
||||
padding-right: 25%;
|
||||
}
|
||||
}
|
||||
|
||||
@media print {
|
||||
|
||||
.no-print,
|
||||
.no-print * {
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
body {
|
||||
margin: auto;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
|
||||
/* fonts */
|
||||
|
||||
@font-face {
|
||||
font-family: "Lato";
|
||||
src: url("/fonts/Lato-Regular.woff2") format("woff2");
|
||||
font-weight: normal;
|
||||
font-style: normal;
|
||||
}
|
||||
@font-face {
|
||||
font-family: "Lato";
|
||||
src: url("/fonts/Lato-Bold.woff2") format("woff2");
|
||||
font-weight: bold;
|
||||
font-style: normal;
|
||||
}
|
||||
@font-face {
|
||||
font-family: "Lato";
|
||||
src: url("/fonts/Lato-Italic.woff2") format("woff2");
|
||||
font-weight: normal;
|
||||
font-style: italic;
|
||||
}
|
||||
@font-face {
|
||||
font-family: "Lato";
|
||||
src: url("/fonts/Lato-BoldItalic.woff2") format("woff2");
|
||||
font-weight: bold;
|
||||
font-style: italic;
|
||||
}
|
||||
@font-face {
|
||||
font-family: "Lete Sans Math";
|
||||
src: url("/fonts/LeteSansMath.woff2") format("woff2");
|
||||
font-weight: normal;
|
||||
font-style: normal;
|
||||
}
|
||||
@font-face {
|
||||
font-family: "Lete Sans Math";
|
||||
src: url("/fonts/LeteSansMath-Bold.woff2") format("woff2");
|
||||
font-weight: bold;
|
||||
font-style: normal;
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: "IosevkaC";
|
||||
src: url("/fonts/IosevkaCustom-Regular.woff2") format("woff2");
|
||||
font-weight: normal;
|
||||
font-style: normal;
|
||||
}
|
||||
@font-face {
|
||||
font-family: "IosevkaC";
|
||||
src: url("/fonts/IosevkaCustom-Bold.woff2") format("woff2");
|
||||
font-weight: bold;
|
||||
font-style: normal;
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
|
||||
code.sourceCode
|
||||
{
|
||||
background: inherit
|
||||
}
|
||||
pre > code.sourceCode > span > a:first-child::before { text-decoration: underline; }
|
||||
code span.al { color: #CB4B16; font-weight: bold; } /* Alert */
|
||||
code span.an { color: #60a0b0; font-weight: bold; font-style: italic; } /* Annotation */
|
||||
code span.at { color: #7d9029; } /* Attribute */
|
||||
code span.bn { color: #D33682; } /* BaseN */
|
||||
code span.bu { } /* BuiltIn */
|
||||
code span.cf { color: #5F8700; font-weight: bold; } /* ControlFlow */
|
||||
code span.ch { color: #16801a; } /* Char */
|
||||
code span.cn { color: #880000; } /* Constant */
|
||||
code span.co { color: #93A1A1; font-style: italic; } /* Comment */
|
||||
code span.cv { color: #60a0b0; font-weight: bold; font-style: italic; } /* CommentVar */
|
||||
code span.do { color: #ba2121; font-style: italic; } /* Documentation */
|
||||
code span.dt { background-color: #f8edff; } /* DataType */
|
||||
code span.dv { color: #D33682; } /* DecVal */
|
||||
code span.er { color: #D30102; font-weight: bold; } /* Error */
|
||||
code span.ex { } /* Extension */
|
||||
code span.fl { color: #D33682; } /* Float */
|
||||
code span.fu { } /* Function */
|
||||
code span.im { color: #D70000} /* Import */
|
||||
code span.in { color: #60a0b0; font-weight: bold; font-style: italic; } /* Information */
|
||||
code span.kw { font-weight: bold; } /* Keyword */
|
||||
code span.op { font-weight: bold; } /* Operator */
|
||||
code span.ot { font-weight: bold; } /* Other */
|
||||
code span.pp { color: #bc7a00; } /* Preprocessor */
|
||||
code span.sc { color: #4070a0; } /* SpecialChar */
|
||||
code span.ss { color: #bb6688; } /* SpecialString */
|
||||
code span.st { color: #16801a; } /* String */
|
||||
code span.va { color: #19177c; } /* Variable */
|
||||
code span.vs { color: #4070a0; } /* VerbatimString */
|
||||
code span.wa { color: #60a0b0; font-weight: bold; font-style: italic; } /* Warning */
|
||||
@@ -0,0 +1,179 @@
|
||||
/*
|
||||
This file is copied from
|
||||
https://github.com/slotThe/slotThe.github.io/blob/main/css/sidenotes.css
|
||||
with minor modifications made by Yu Cong.
|
||||
|
||||
The original author is Tony Zorman.
|
||||
|
||||
Extracted from:
|
||||
|
||||
https://github.com/edwardtufte/tufte-css
|
||||
|
||||
and modified to fit my website's theme.
|
||||
*/
|
||||
|
||||
body {
|
||||
counter-reset: sidenote-counter;
|
||||
}
|
||||
|
||||
.sidenote,
|
||||
.marginnote,
|
||||
.marginnote-left {
|
||||
float: right;
|
||||
clear: right;
|
||||
margin-right: -45%;
|
||||
width: 40%;
|
||||
margin-top: 0.3rem;
|
||||
margin-bottom: 0;
|
||||
font-size: 0.8em;
|
||||
line-height: 1.2;
|
||||
vertical-align: baseline;
|
||||
position: relative;
|
||||
text-align: left;
|
||||
}
|
||||
@media (max-width: 1200px) {
|
||||
.sidenote,
|
||||
.marginnote,
|
||||
.marginnote-left {
|
||||
margin-right: -40%;
|
||||
width: 33%;
|
||||
}
|
||||
}
|
||||
|
||||
.marginnote-left {
|
||||
float: left;
|
||||
clear: left;
|
||||
margin-left: -32%;
|
||||
width: 25%;
|
||||
position: relative;
|
||||
text-align: right;
|
||||
}
|
||||
/* The first condition is for the case of a left-aligned layout (on a
|
||||
smaller screen), and the second condition for a more centered layout
|
||||
on a larger screen. It's a bit awkward, sadly :/ */
|
||||
@media (max-width: 1349px) or ((min-width: 1367px) and (max-width: 1620px)) {
|
||||
.marginnote-left {
|
||||
margin-left: -33%;
|
||||
width: 30%;
|
||||
}
|
||||
}
|
||||
|
||||
.sidenote code {
|
||||
font-size: 0.94em;
|
||||
}
|
||||
|
||||
/* For some reason, although only `overflow-x` is set in `default.css`,
|
||||
block code in side and marginnotes gets a vertical (!) scrollbar no
|
||||
matter what; disable that.
|
||||
*/
|
||||
div .marginnote pre,
|
||||
div .sidenote pre {
|
||||
overflow-y: hidden;
|
||||
}
|
||||
|
||||
.sidenote-number {
|
||||
counter-increment: sidenote-counter;
|
||||
color: var(--color-link);
|
||||
}
|
||||
|
||||
.sidenote-number:after,
|
||||
.sidenote:before {
|
||||
position: relative;
|
||||
vertical-align: baseline;
|
||||
}
|
||||
|
||||
.sidenote-number:after {
|
||||
content: counter(sidenote-counter);
|
||||
font-size: 0.8rem;
|
||||
top: -0.5rem;
|
||||
}
|
||||
|
||||
/* Properly position siednote number and adjust position of sidenote
|
||||
paragraphs:
|
||||
https://github.com/edwardtufte/tufte-css/issues/93#issuecomment-670695382
|
||||
*/
|
||||
.sidenote::before {
|
||||
content: counter(sidenote-counter) " ";
|
||||
font-size: 0.8rem;
|
||||
top: -0.55rem;
|
||||
position: absolute;
|
||||
right: calc(100% + 0.5em);
|
||||
}
|
||||
|
||||
.sidenote p {
|
||||
margin: 1em 0;
|
||||
}
|
||||
|
||||
.sidenote p:first-child {
|
||||
margin-top: 0;
|
||||
}
|
||||
|
||||
.sidenote p:last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
/* */
|
||||
|
||||
input.margin-toggle {
|
||||
display: none;
|
||||
}
|
||||
|
||||
label.sidenote-number {
|
||||
display: inline-block;
|
||||
max-height: 2rem; /* should be less than or equal to paragraph line-height */
|
||||
}
|
||||
|
||||
label.margin-toggle:not(.sidenote-number) {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.iframe-wrapper {
|
||||
position: relative;
|
||||
padding-bottom: 56.25%; /* 16:9 */
|
||||
padding-top: 25px;
|
||||
height: 0;
|
||||
}
|
||||
|
||||
.iframe-wrapper iframe {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
@media (max-width: 1200px) {
|
||||
label.margin-toggle:not(.sidenote-number) {
|
||||
display: inline;
|
||||
}
|
||||
|
||||
.sidenote,
|
||||
.marginnote,
|
||||
.marginnote-left {
|
||||
display: none;
|
||||
}
|
||||
|
||||
/* Linkify sidenotes iff they are clickable */
|
||||
.margin-toggle,
|
||||
.sidenote-number:after {
|
||||
color: var(--color-link);
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.margin-toggle:checked + .sidenote,
|
||||
.margin-toggle:checked + .marginnote,
|
||||
.margin-toggle:checked + .marginnote-left {
|
||||
display: block;
|
||||
float: left;
|
||||
left: 1rem;
|
||||
clear: both;
|
||||
width: 95%;
|
||||
margin: 1rem 2.5%;
|
||||
position: relative;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
label {
|
||||
cursor: pointer;
|
||||
}
|
||||
}
|
||||
BIN
Binary file not shown.
|
After Width: | Height: | Size: 2.6 KiB |
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -0,0 +1,25 @@
|
||||
|
||||
COMMANDS := build watch rebuild clean
|
||||
.PHONY: $(COMMANDS), publish
|
||||
|
||||
# Set the default goal, so running 'make' without arguments will run 'make build'.
|
||||
.DEFAULT_GOAL := build
|
||||
|
||||
# ---
|
||||
$(COMMANDS): site
|
||||
@echo "Running command: ./site $@"
|
||||
-@./site $@
|
||||
|
||||
|
||||
# --- 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
|
||||
|
||||
# move from katex to mathjax
|
||||
# katex_cli:
|
||||
# cd katex_rust_fork && cargo build --release
|
||||
# ln -sf ./katex_rust_fork/target/release/katex_cli katex_cli
|
||||
+278
@@ -0,0 +1,278 @@
|
||||
{-# LANGUAGE BlockArguments #-}
|
||||
{-# LANGUAGE OverloadedStrings #-}
|
||||
|
||||
module ChaoDoc (chaoDocRead, chaoDocWrite, chaoDocPandocCompiler, chaoDocCompiler) where
|
||||
|
||||
import Control.Monad.State
|
||||
import Data.Either
|
||||
import Data.Functor
|
||||
import Data.List (intersect)
|
||||
import qualified Data.Map as M
|
||||
import Data.Maybe
|
||||
import Data.Text (Text, pack)
|
||||
import qualified Data.Text as T
|
||||
import Hakyll
|
||||
import Pangu (isCJK, pangu)
|
||||
import SideNoteHTML (usingSideNotesHTML)
|
||||
import System.IO.Unsafe
|
||||
import Text.Pandoc
|
||||
-- import Text.Pandoc.Builder
|
||||
import Text.Pandoc.Walk (query, walk, walkM)
|
||||
|
||||
-- setMeta key val (Pandoc (Meta ms) bs) = Pandoc (Meta $ M.insert key val ms) bs
|
||||
|
||||
-- On mac, please do `export LANG=C` before using this thing
|
||||
chaoDocRead :: ReaderOptions
|
||||
chaoDocRead =
|
||||
def
|
||||
{ readerExtensions =
|
||||
enableExtension Ext_tex_math_double_backslash $
|
||||
enableExtension Ext_tex_math_single_backslash $
|
||||
enableExtension Ext_latex_macros $
|
||||
enableExtension Ext_raw_tex pandocExtensions
|
||||
}
|
||||
|
||||
chaoDocWrite :: WriterOptions
|
||||
chaoDocWrite =
|
||||
def
|
||||
{ writerHTMLMathMethod = MathML,
|
||||
-- writerHtml5 = True,
|
||||
-- writerHighlightStyle = Just syntaxHighlightingStyle,
|
||||
writerNumberSections = True,
|
||||
writerTableOfContents = True,
|
||||
writerTOCDepth = 2
|
||||
}
|
||||
|
||||
cslFile :: String
|
||||
cslFile = "bib_style.csl"
|
||||
|
||||
bibFile :: String
|
||||
bibFile = "reference.bib"
|
||||
|
||||
chaoDocPandocCompiler :: Compiler (Item Pandoc)
|
||||
chaoDocPandocCompiler = do
|
||||
macros <- T.pack <$> loadBody "math-macros.tex"
|
||||
csl <- load $ fromFilePath cslFile
|
||||
bib <- load $ fromFilePath bibFile
|
||||
body <- getResourceBody
|
||||
let bodyWithMacros =
|
||||
fmap (T.unpack . prependMacros macros . T.pack) body
|
||||
prepare =
|
||||
addMeta "link-citations" (MetaBool True)
|
||||
. addMeta "reference-section-title" (MetaInlines [Str "References"])
|
||||
. myFilter
|
||||
readPandocWith chaoDocRead bodyWithMacros
|
||||
>>= processPandocBiblio csl bib . fmap prepare
|
||||
|
||||
chaoDocCompiler :: Compiler (Item String)
|
||||
chaoDocCompiler = chaoDocPandocCompiler <&> writePandocWith chaoDocWrite
|
||||
|
||||
addMeta :: T.Text -> MetaValue -> Pandoc -> Pandoc
|
||||
addMeta name value (Pandoc meta a) =
|
||||
let prevMap = unMeta meta
|
||||
newMap = M.insert name value prevMap
|
||||
newMeta = Meta newMap
|
||||
in Pandoc newMeta a
|
||||
|
||||
myFilter :: Pandoc -> Pandoc
|
||||
myFilter = usingSideNotesHTML chaoDocWrite . theoremFilter . panguFilter . displayMathFilter
|
||||
|
||||
pandocToInline :: Pandoc -> [Inline]
|
||||
pandocToInline (Pandoc _ blocks) = go (reverse blocks)
|
||||
where
|
||||
go (Plain inlines : _) = inlines
|
||||
go (Para inlines : _) = inlines
|
||||
go (_ : xs) = go xs
|
||||
go [] = []
|
||||
|
||||
incrementalBlock :: [Text]
|
||||
incrementalBlock =
|
||||
[ "Theorem",
|
||||
"Conjecture",
|
||||
"Definition",
|
||||
"Example",
|
||||
"Lemma",
|
||||
"Problem",
|
||||
"Proposition",
|
||||
"Corollary",
|
||||
"Observation",
|
||||
"定理",
|
||||
"猜想",
|
||||
"定义",
|
||||
"例",
|
||||
"引理",
|
||||
"问题",
|
||||
"命题",
|
||||
"推论",
|
||||
"观察"
|
||||
]
|
||||
|
||||
otherBlock :: [Text]
|
||||
otherBlock = ["Proof", "Remark", "证明", "备注"]
|
||||
|
||||
theoremClasses :: [Text]
|
||||
theoremClasses = incrementalBlock ++ otherBlock
|
||||
|
||||
-- create a filter for theorems
|
||||
getClass :: Attr -> [Text]
|
||||
getClass (_, c, _) = c
|
||||
|
||||
addClass :: Attr -> Text -> Attr
|
||||
addClass (a, b, c) d = (a, d : b, c)
|
||||
|
||||
addAttr :: Attr -> Text -> Text -> Attr
|
||||
addAttr (a, b, c) x y = (a, b, (x, y) : c)
|
||||
|
||||
-- For each theorem, add a number, and also add add class theorem
|
||||
preprocessTheorems :: Block -> State Int Block
|
||||
preprocessTheorems (Div attr xs)
|
||||
| isIncremental = do
|
||||
curId <- get
|
||||
put (curId + 1)
|
||||
return $ Div (addAttr attr' "index" (pack $ show curId)) xs
|
||||
| isOtherBlock = return $ Div attr' xs
|
||||
| otherwise = return (Div attr xs)
|
||||
where
|
||||
isIncremental = getClass attr `intersect` incrementalBlock /= []
|
||||
isOtherBlock = getClass attr `intersect` otherBlock /= []
|
||||
theoremType = head (getClass attr `intersect` theoremClasses)
|
||||
attr' = addAttr attr "type" theoremType
|
||||
preprocessTheorems x = return x
|
||||
|
||||
theoremFilter :: Pandoc -> Pandoc
|
||||
theoremFilter doc = walk makeTheorem $ autorefFilter $ evalState (walkM preprocessTheorems doc) 1
|
||||
|
||||
-- [index, type, idx]
|
||||
theoremIndex :: Block -> [(Text, (Text, Text))]
|
||||
theoremIndex (Div attr _)
|
||||
| isNothing t = []
|
||||
| isIncremental = [(idx, (fromJust t, fromJust index))]
|
||||
| otherwise = []
|
||||
where
|
||||
(idx, _, parm) = attr
|
||||
t = lookup "type" parm
|
||||
index = lookup "index" parm
|
||||
isIncremental = fromJust t `elem` incrementalBlock
|
||||
theoremIndex _ = []
|
||||
|
||||
autoref :: [(Text, (Text, Text))] -> Inline -> Inline
|
||||
autoref x (Cite citations inlines)
|
||||
| valid = Link nullAttr [Str linkTitle] ("#" <> citeid, linkTitle)
|
||||
| otherwise = Cite citations inlines
|
||||
where
|
||||
citeid = citationId $ head citations
|
||||
valid = citeid `elem` map fst x
|
||||
(theoremType, num) = fromJust $ lookup citeid x
|
||||
linkTitle = theoremType <> " " <> num
|
||||
autoref _ y = y
|
||||
|
||||
autorefFilter :: Pandoc -> Pandoc
|
||||
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.tex")
|
||||
{-# NOINLINE mathMacros #-}
|
||||
|
||||
prependMacros :: Text -> Text -> Text
|
||||
prependMacros macros body = macros <> "\n\n" <> body
|
||||
|
||||
prependMathMacros :: Text -> Text
|
||||
prependMathMacros = prependMacros mathMacros
|
||||
|
||||
thmNamePandoc :: Text -> Pandoc
|
||||
thmNamePandoc x =
|
||||
fromRight (Pandoc nullMeta []) . runPure $
|
||||
readMarkdown chaoDocRead (prependMathMacros x)
|
||||
|
||||
makeTheorem :: Block -> Block
|
||||
makeTheorem (Div attr xs)
|
||||
| isNothing t = Div attr xs
|
||||
| otherwise = Div (addClass attr "theorem-environment") (Plain [header] : xs)
|
||||
where
|
||||
(_, _, parm) = attr
|
||||
t = lookup "type" parm
|
||||
name = lookup "title" parm
|
||||
index = lookup "index" parm
|
||||
header = Span (addClass nullAttr "theorem-header") [typetext, indextext, nametext]
|
||||
typetext = Span (addClass nullAttr "type") [Str $ fromJust t]
|
||||
indextext =
|
||||
if isNothing index
|
||||
then Str ""
|
||||
else Span (addClass nullAttr "index") [Str $ fromJust index]
|
||||
nametext =
|
||||
if isNothing name
|
||||
then Str ""
|
||||
else Span (addClass nullAttr "name") (pandocToInline $ thmNamePandoc $ fromJust name)
|
||||
makeTheorem x = x
|
||||
|
||||
-- pangu filter
|
||||
lastChar :: Inline -> Maybe Char
|
||||
lastChar e = case e of
|
||||
Str s -> if null (T.unpack s) then Nothing else Just (last (T.unpack s))
|
||||
Emph is -> lastCharList is
|
||||
Strong is -> lastCharList is
|
||||
Strikeout is -> lastCharList is
|
||||
Link _ is _ -> lastCharList is
|
||||
Span _ is -> lastCharList is
|
||||
Quoted _ is -> lastCharList is
|
||||
_ -> Nothing
|
||||
where
|
||||
lastCharList [] = Nothing
|
||||
lastCharList is = lastChar (last is)
|
||||
|
||||
firstChar :: Inline -> Maybe Char
|
||||
firstChar e = case e of
|
||||
Str s -> if null (T.unpack s) then Nothing else Just (head (T.unpack s))
|
||||
Emph is -> firstCharList is
|
||||
Strong is -> firstCharList is
|
||||
Strikeout is -> firstCharList is
|
||||
Link _ is _ -> firstCharList is
|
||||
Span _ is -> firstCharList is
|
||||
Quoted _ is -> firstCharList is
|
||||
_ -> Nothing
|
||||
where
|
||||
firstCharList [] = Nothing
|
||||
firstCharList is = firstChar (head is)
|
||||
|
||||
panguInline :: Inline -> Inline
|
||||
panguInline e = case e of
|
||||
Str s -> Str (pangu s)
|
||||
Emph is -> Emph (panguInlines is)
|
||||
Strong is -> Strong (panguInlines is)
|
||||
Strikeout is -> Strikeout (panguInlines is)
|
||||
Link at is tg -> Link at (panguInlines is) tg
|
||||
Span at is -> Span at (panguInlines is)
|
||||
Quoted qt is -> Quoted qt (panguInlines is)
|
||||
_ -> e
|
||||
|
||||
panguInlines :: [Inline] -> [Inline]
|
||||
panguInlines = foldr (addSpace . panguInline) []
|
||||
where
|
||||
addSpace x [] = [x]
|
||||
addSpace x (y : ys)
|
||||
| shouldSpace x y = x : Space : y : ys
|
||||
| otherwise = x : y : ys
|
||||
shouldSpace x y = case (lastChar x, firstChar y) of
|
||||
(Just lc, Just fc) -> isCJK lc /= isCJK fc
|
||||
_ -> False
|
||||
|
||||
panguFilter :: Pandoc -> Pandoc
|
||||
panguFilter = walk transformBlocks
|
||||
where
|
||||
transformBlocks :: Block -> Block
|
||||
transformBlocks (Para inlines) = Para (panguInlines inlines)
|
||||
transformBlocks x = x
|
||||
|
||||
-- display math wrapper for MathML
|
||||
displayMathFilter :: Pandoc -> Pandoc
|
||||
displayMathFilter = walk wrapDisplayMath
|
||||
where
|
||||
wrapDisplayMath m@(Math DisplayMath _) =
|
||||
Span ("math-container", [], []) [m]
|
||||
wrapDisplayMath x = x
|
||||
+227
@@ -0,0 +1,227 @@
|
||||
{-# LANGUAGE OverloadedStrings #-}
|
||||
|
||||
module Pangu (pangu, isCJK) where
|
||||
|
||||
import Data.Function (fix)
|
||||
import Data.Text (Text)
|
||||
import qualified Data.Text as T
|
||||
import Data.Void (Void)
|
||||
import Replace.Megaparsec (streamEdit)
|
||||
import Text.Megaparsec
|
||||
import Text.Megaparsec.Char
|
||||
|
||||
-------------------------------------------------------------------------------
|
||||
type Parser = Parsec Void Text
|
||||
|
||||
type Rule = Parser Text
|
||||
|
||||
type RuleSet = [Rule]
|
||||
|
||||
applyUntilFixed :: Rule -> Text -> Text
|
||||
applyUntilFixed rule =
|
||||
fix
|
||||
( \loop current ->
|
||||
let next = streamEdit (try rule) id current
|
||||
in if next == current then next else loop next
|
||||
)
|
||||
|
||||
applyRulesRecursively :: RuleSet -> Text -> Text
|
||||
applyRulesRecursively rules input = foldl (flip applyUntilFixed) input rules
|
||||
|
||||
applyRules :: RuleSet -> Text -> Text
|
||||
applyRules rules input = foldl (flip applyOnce) input rules
|
||||
where
|
||||
applyOnce rule = streamEdit (try rule) id
|
||||
|
||||
-------------------------------------------------------------------------------
|
||||
-- rules for pangu
|
||||
|
||||
-- alphaNumChar from megaparsec matches CJK chars...
|
||||
-- need to implement a new one
|
||||
alphanumericChar :: Parser Char
|
||||
alphanumericChar = satisfy $ \c ->
|
||||
(c >= 'a' && c <= 'z')
|
||||
|| (c >= 'A' && c <= 'Z')
|
||||
|| (c >= '0' && c <= '9')
|
||||
|
||||
-- | Check if a character falls within the CJK ranges provided
|
||||
isCJK :: Char -> Bool
|
||||
isCJK c = any (\(start, end) -> c >= start && c <= end) cjkRanges
|
||||
where
|
||||
cjkRanges =
|
||||
[ ('\x2e80', '\x2eff'),
|
||||
('\x2f00', '\x2fdf'),
|
||||
('\x3040', '\x309f'),
|
||||
('\x30a0', '\x30fa'),
|
||||
('\x30fc', '\x30ff'),
|
||||
('\x3100', '\x312f'),
|
||||
('\x3200', '\x32ff'),
|
||||
('\x3400', '\x4dbf'),
|
||||
('\x4e00', '\x9fff'),
|
||||
('\xf900', '\xfaff')
|
||||
]
|
||||
|
||||
convertToFullwidth :: Char -> Char
|
||||
convertToFullwidth c =
|
||||
case c of
|
||||
':' -> ':'
|
||||
'.' -> '。'
|
||||
'~' -> '~'
|
||||
'!' -> '!'
|
||||
'?' -> '?'
|
||||
',' -> ','
|
||||
';' -> ';'
|
||||
'\"' -> '”'
|
||||
'\'' -> '’'
|
||||
_ -> c
|
||||
|
||||
-- A parser that matches a single CJK character
|
||||
cjkChar :: Parser Char
|
||||
cjkChar = satisfy isCJK
|
||||
|
||||
-- use python.py as reference for these rules
|
||||
|
||||
fullwidthCJKsymCJK :: Rule
|
||||
fullwidthCJKsymCJK = do
|
||||
lcjk <- cjkChar
|
||||
_ <- many (char ' ')
|
||||
sym <- try (some (char ':')) <|> count 1 (char '.')
|
||||
_ <- many (char ' ')
|
||||
rcjk <- cjkChar
|
||||
let transformedsym = map convertToFullwidth sym
|
||||
return $ T.pack $ [lcjk] ++ transformedsym ++ [rcjk]
|
||||
|
||||
fullwidthCJKsym :: Rule
|
||||
fullwidthCJKsym = do
|
||||
cjk <- cjkChar
|
||||
_ <- many (char ' ')
|
||||
sym <- some $ oneOf ("~!?,;" :: [Char])
|
||||
_ <- many (char ' ')
|
||||
let transformedsym = T.pack $ map convertToFullwidth sym
|
||||
return $ T.pack [cjk] <> transformedsym
|
||||
|
||||
dotsCJK :: Rule
|
||||
dotsCJK = do
|
||||
dots <- chunk "..." <|> chunk "…"
|
||||
cjk <- cjkChar
|
||||
return $ dots <> T.pack (" " ++ [cjk])
|
||||
|
||||
fixCJKcolAN :: Rule
|
||||
fixCJKcolAN = do
|
||||
cjk <- cjkChar
|
||||
_ <- char ':'
|
||||
an <- alphanumericChar
|
||||
return $ T.pack $ [cjk] ++ ":" ++ [an]
|
||||
|
||||
-- quotes
|
||||
-- seems confusing ...
|
||||
quotesym :: [Char]
|
||||
quotesym = "'`\x05f4\""
|
||||
|
||||
cjkquote :: Rule
|
||||
cjkquote = do
|
||||
cjk <- cjkChar
|
||||
quote <- oneOf quotesym
|
||||
return $ T.pack $ [cjk] ++ " " ++ [quote]
|
||||
|
||||
quoteCJK :: Rule
|
||||
quoteCJK = do
|
||||
quote <- oneOf quotesym
|
||||
cjk <- cjkChar
|
||||
return $ T.pack $ [quote] ++ " " ++ [cjk]
|
||||
|
||||
fixQuote :: Rule
|
||||
fixQuote = do
|
||||
openQuotes <- T.pack <$> some (oneOf quotesym)
|
||||
_ <- many spaceChar
|
||||
content <- T.pack <$> someTill anySingle (lookAhead $ some (oneOf quotesym))
|
||||
closeQuotes <- T.pack <$> some (oneOf quotesym)
|
||||
return $ openQuotes <> T.strip content <> closeQuotes
|
||||
|
||||
cjkpossessivequote :: Rule
|
||||
cjkpossessivequote = do
|
||||
cjk <- cjkChar
|
||||
_ <- char '\''
|
||||
_ <- lookAhead $ anySingleBut 's'
|
||||
return $ T.pack $ cjk : " '"
|
||||
|
||||
-- This singlequoteCJK rule will turn '你好' into ' 你好'
|
||||
-- which seems not desirable...
|
||||
-- however, the behavior is aligned with python version
|
||||
singlequoteCJK :: Rule
|
||||
singlequoteCJK = do
|
||||
_ <- char '\''
|
||||
cjk <- cjkChar
|
||||
return $ T.pack $ "' " ++ [cjk]
|
||||
|
||||
fixPossessivequote :: Rule
|
||||
fixPossessivequote = do
|
||||
pre <- cjkChar <|> alphanumericChar
|
||||
_ <- some spaceChar
|
||||
_ <- chunk "'s"
|
||||
return $ T.pack $ pre : "'s"
|
||||
|
||||
-- hash
|
||||
hashANSCJKhash :: Rule
|
||||
hashANSCJKhash = do
|
||||
cjk1 <- cjkChar
|
||||
_ <- char '#'
|
||||
mid <- some cjkChar
|
||||
_ <- char '#'
|
||||
cjk2 <- cjkChar
|
||||
return $ T.pack $ [cjk1] ++ " #" ++ mid ++ "# " ++ [cjk2]
|
||||
|
||||
cjkhash :: Rule
|
||||
cjkhash = do
|
||||
cjk <- cjkChar
|
||||
_ <- char '#'
|
||||
_ <- lookAhead $ anySingleBut ' '
|
||||
return $ T.pack $ cjk : " #"
|
||||
|
||||
hashcjk :: Rule
|
||||
hashcjk = do
|
||||
_ <- char '#'
|
||||
_ <- lookAhead $ anySingleBut ' '
|
||||
cjk <- cjkChar
|
||||
return $ T.pack $ "# " ++ [cjk]
|
||||
|
||||
-- operators
|
||||
cjkOPTan :: Rule
|
||||
cjkOPTan = do
|
||||
cjk <- cjkChar
|
||||
opt <- oneOf ("+-=*/&|<>%" :: [Char])
|
||||
an <- alphanumericChar
|
||||
return $ T.pack [cjk, ' ', opt, ' ', an]
|
||||
|
||||
anOPTcjk :: Rule
|
||||
anOPTcjk = do
|
||||
an <- alphanumericChar
|
||||
opt <- oneOf ("+-=*/&|<>%" :: [Char])
|
||||
cjk <- cjkChar
|
||||
return $ T.pack [an, ' ', opt, ' ', cjk]
|
||||
|
||||
-- slash/bracket rules are not implemented
|
||||
|
||||
-- CJK and alphanumeric without space
|
||||
|
||||
cjkans :: Rule
|
||||
cjkans = do
|
||||
cjk <- cjkChar
|
||||
_ <- lookAhead (alphanumericChar <|> oneOf ("@$%^&*-+\\=|/" :: [Char]))
|
||||
return $ T.pack [cjk, ' ']
|
||||
|
||||
anscjk :: Rule
|
||||
anscjk = do
|
||||
an <- alphanumericChar <|> oneOf ("~!$%^&*-+\\=|;:,./?" :: [Char])
|
||||
_ <- lookAhead cjkChar
|
||||
return $ T.pack [an, ' ']
|
||||
|
||||
-- rule set, the order matters
|
||||
recursiveRules :: RuleSet
|
||||
recursiveRules = [fullwidthCJKsymCJK, fullwidthCJKsym]
|
||||
|
||||
onepassRules :: RuleSet
|
||||
onepassRules = [anscjk, cjkans]
|
||||
|
||||
pangu :: Text -> Text
|
||||
pangu input = applyRules onepassRules $ applyRulesRecursively recursiveRules input
|
||||
@@ -0,0 +1,161 @@
|
||||
{-# LANGUAGE BangPatterns #-}
|
||||
{-# LANGUAGE DerivingStrategies #-}
|
||||
{-# LANGUAGE LambdaCase #-}
|
||||
{-# LANGUAGE OverloadedStrings #-}
|
||||
{-# LANGUAGE ScopedTypeVariables #-}
|
||||
{- |
|
||||
Module : Text.Pandoc.SideNoteHTML
|
||||
Description : Convert pandoc footnotes to sidenotes
|
||||
Copyright : (c) Tony Zorman 2023
|
||||
License : MIT
|
||||
Maintainer : Tony Zorman <soliditsallgood@mailbox.org>
|
||||
Stability : experimental
|
||||
Portability : non-portable
|
||||
-}
|
||||
module SideNoteHTML (usingSideNotesHTML) where
|
||||
|
||||
import Control.Monad (foldM)
|
||||
import Control.Monad.State (State, get, modify', runState)
|
||||
import Data.Text (Text)
|
||||
import Text.Pandoc (runPure, writeHtml5String)
|
||||
import Text.Pandoc.Definition (Block (..), Inline (..), Pandoc (..))
|
||||
import Text.Pandoc.Options (WriterOptions)
|
||||
import Text.Pandoc.Shared (tshow)
|
||||
import Text.Pandoc.Walk (walkM)
|
||||
import qualified Data.Text as T
|
||||
|
||||
-- type NoteType :: Type
|
||||
data NoteType = Sidenote | Marginnote
|
||||
deriving stock (Show, Eq)
|
||||
|
||||
-- type SidenoteState :: Type
|
||||
data SidenoteState = SNS
|
||||
{ _writer :: !WriterOptions
|
||||
, counter :: !Int
|
||||
}
|
||||
|
||||
-- type Sidenote :: Type -> Type
|
||||
type Sidenote = State SidenoteState
|
||||
|
||||
-- | Like 'Text.Pandoc.SideNote.usingSideNotes', but immediately
|
||||
-- pre-render the sidenotes. This has the advantage that sidenotes may
|
||||
-- be wrapped in a @<div>@ (instead of a 'Span'), which allows arbitrary
|
||||
-- blocks to be nested in them. The disadvantage is that one now has to
|
||||
-- specify the 'WriterOptions' for the current document, meaning this is
|
||||
-- meant to be used as a module and is unlikely to be useful as a
|
||||
-- standalone application.
|
||||
--
|
||||
-- ==== __Example__
|
||||
--
|
||||
-- Using this function with <https://jaspervdj.be/hakyll/ hakyll> could
|
||||
-- look something like the following, defining an equivalent to the
|
||||
-- default @pandocCompiler@.
|
||||
--
|
||||
-- > myPandocCompiler :: Compiler (Item String)
|
||||
-- > myPandocCompiler =
|
||||
-- > pandocCompilerWithTransformM
|
||||
-- > defaultHakyllReaderOptions
|
||||
-- > defaultHakyllWriterOptions
|
||||
-- > (usingSideNotesHTML defaultHakyllWriterOptions)
|
||||
--
|
||||
usingSideNotesHTML :: WriterOptions -> Pandoc -> Pandoc
|
||||
usingSideNotesHTML writer (Pandoc meta blocks) =
|
||||
-- Drop a superfluous paragraph at the start of the document.
|
||||
Pandoc meta . someStart . walkBlocks (SNS writer 0) $ blocks
|
||||
where
|
||||
someStart :: [Block] -> [Block]
|
||||
someStart = \case
|
||||
(Para [Str ""] : bs) -> bs
|
||||
bs -> bs
|
||||
|
||||
walkBlocks :: SidenoteState -> [Block] -> [Block]
|
||||
walkBlocks sns = \case
|
||||
[] -> []
|
||||
(b : bs) -> b' <> walkBlocks s' bs
|
||||
where (b', s') = walkM mkSidenote [b] `runState` sns
|
||||
|
||||
-- Sidenotes can probably appear in more places; this should be
|
||||
-- filled-in at some point.
|
||||
mkSidenote :: [Block] -> Sidenote [Block]
|
||||
mkSidenote = foldM (\acc b -> (acc <>) <$> single b) []
|
||||
where
|
||||
-- Try to find and render a sidenote in a single block.
|
||||
single :: Block -> Sidenote [Block]
|
||||
single = \case
|
||||
-- Simulate a paragraph by inserting a dummy block; this is needed
|
||||
-- in case two consecutive paragraphs have sidenotes, or a paragraph
|
||||
-- doesn't have one at all.
|
||||
Para inlines -> (Para [Str ""] :) <$> renderSidenote [] inlines
|
||||
Plain inlines -> renderSidenote [] inlines
|
||||
OrderedList attrs bs -> (:[]) . OrderedList attrs <$> traverse mkSidenote bs
|
||||
BulletList bs -> (:[]) . BulletList <$> traverse mkSidenote bs
|
||||
block -> pure [block]
|
||||
|
||||
renderSidenote :: [Inline] -> [Inline] -> Sidenote [Block]
|
||||
renderSidenote !inlines = \case
|
||||
[] -> pure [plain inlines]
|
||||
Note bs : xs -> do block <- go bs
|
||||
mappend [ -- Start gluing before, see [Note Comment].
|
||||
plain (RawInline "html" commentStart : inlines)
|
||||
, block
|
||||
]
|
||||
<$> renderSidenote
|
||||
[RawInline "html" commentEnd] -- End gluing after
|
||||
xs
|
||||
b : xs -> renderSidenote (b : inlines) xs
|
||||
where
|
||||
go :: [Block] -> Sidenote Block
|
||||
go blocks = do
|
||||
SNS w i <- get <* modify' (\sns -> sns{ counter = 1 + counter sns })
|
||||
let (typ, noteText) = getNoteType (render w blocks)
|
||||
pure . RawBlock "html" $
|
||||
mconcat [ commentEnd -- End gluing before
|
||||
, label typ i <> input i <> note typ noteText
|
||||
, commentStart -- Start gluing after
|
||||
]
|
||||
|
||||
-- The '{-}' symbol differentiates between margin note and side note.
|
||||
getNoteType :: Text -> (NoteType, Text)
|
||||
getNoteType t
|
||||
| "{-} " `T.isPrefixOf` t = (Marginnote, T.drop 4 t)
|
||||
| otherwise = (Sidenote , t)
|
||||
|
||||
render :: WriterOptions -> [Block] -> Text
|
||||
render w bs = case runPure (writeHtml5String w (Pandoc mempty bs)) of
|
||||
Left err -> error $ "Text.Pandoc.SideNoteHTML.writePandocWith: " ++ show err
|
||||
Right txt -> T.drop 1 (T.dropWhile (/= '\n') txt)
|
||||
|
||||
commentEnd :: T.Text
|
||||
commentEnd = "-->"
|
||||
|
||||
commentStart :: T.Text
|
||||
commentStart = "<!--"
|
||||
|
||||
plain :: [Inline] -> Block
|
||||
plain = Plain . reverse
|
||||
|
||||
label :: NoteType -> Int -> Text
|
||||
label nt i = "<label for=\"sn-" <> tshow i <> "\" class=\"margin-toggle" <> sidenoteNumber <> "\">" <> altSymbol <> "</label>"
|
||||
where
|
||||
sidenoteNumber :: Text = case nt of
|
||||
Sidenote -> " sidenote-number"
|
||||
Marginnote -> ""
|
||||
altSymbol :: Text = case nt of
|
||||
Sidenote -> ""
|
||||
Marginnote -> "⊕"
|
||||
|
||||
input :: Int -> Text
|
||||
input i = "<input type=\"checkbox\" id=\"sn-" <> tshow i <> "\" class=\"margin-toggle\"/>"
|
||||
|
||||
note :: NoteType -> Text -> Text
|
||||
note nt body = "<div class=\"" <> T.toLower (tshow nt) <> "\">" <> body <> "</div>"
|
||||
|
||||
{- [Note Comment]
|
||||
|
||||
This is obviously horrible, but we have to do this in order for the
|
||||
blocks (which are now not inline elements anymore!) immediately before
|
||||
and after the sidenote to be "glued" to the sidenote itself. In this
|
||||
way, the number indicating the sidenote does not have an extra space
|
||||
associated to it on either side, which otherwise would be the case.
|
||||
|
||||
-}
|
||||
+118
@@ -0,0 +1,118 @@
|
||||
{-# LANGUAGE BlockArguments #-}
|
||||
{-# LANGUAGE LambdaCase #-}
|
||||
{-# LANGUAGE OverloadedStrings #-}
|
||||
{-# LANGUAGE ScopedTypeVariables #-}
|
||||
|
||||
import ChaoDoc
|
||||
import qualified Data.Text as T
|
||||
import Hakyll
|
||||
import System.FilePath
|
||||
import Text.Pandoc
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
-- https://www.rohanjain.in/hakyll-clean-urls/
|
||||
cleanRoute :: Routes
|
||||
cleanRoute = customRoute createIndexRoute
|
||||
where
|
||||
createIndexRoute ident = takeDirectory p </> takeBaseName p </> "index.html"
|
||||
where
|
||||
p = toFilePath ident
|
||||
--------------------------------------------------------------------------------
|
||||
|
||||
config :: Configuration
|
||||
config =
|
||||
defaultConfiguration
|
||||
{ ignoreFile = \path ->
|
||||
ignoreFile defaultConfiguration path
|
||||
|| ".git" `elem` splitDirectories (normalise path)
|
||||
}
|
||||
|
||||
main :: IO ()
|
||||
main = hakyllWith config $ do
|
||||
match "images/**" $ do
|
||||
route idRoute
|
||||
compile copyFileCompiler
|
||||
|
||||
match "math-macros.tex" $ compile getResourceBody
|
||||
|
||||
match "bib_style.csl" $ compile cslCompiler
|
||||
|
||||
match "reference.bib" $ compile biblioCompiler
|
||||
|
||||
match "fonts/*.woff2" $ do
|
||||
route idRoute
|
||||
compile copyFileCompiler
|
||||
|
||||
match "favicon.ico" $ do
|
||||
route idRoute
|
||||
compile copyFileCompiler
|
||||
|
||||
match "css/*" $ do
|
||||
route idRoute
|
||||
compile compressCssCompiler
|
||||
|
||||
match "notes/*" $ do
|
||||
route cleanRoute
|
||||
compile $ do
|
||||
tocCtx <- getTocCtx defaultContext
|
||||
chaoDocCompiler
|
||||
>>= loadAndApplyTemplate "templates/post.html" tocCtx
|
||||
>>= loadAndApplyTemplate "templates/default.html" tocCtx
|
||||
>>= relativizeUrls
|
||||
|
||||
create ["index.html"] $ do
|
||||
route idRoute
|
||||
compile $ do
|
||||
posts <- loadAll "notes/*"
|
||||
let indexCtx =
|
||||
constField "title" "Notes"
|
||||
`mappend` constField "toc" ""
|
||||
`mappend` listField "posts" postCtx (return posts)
|
||||
`mappend` defaultContext
|
||||
makeItem ""
|
||||
>>= loadAndApplyTemplate "templates/post-list.html" indexCtx
|
||||
>>= loadAndApplyTemplate "templates/default.html" indexCtx
|
||||
>>= relativizeUrls
|
||||
|
||||
match "templates/*" $ compile templateBodyCompiler
|
||||
|
||||
postCtx :: Context String
|
||||
postCtx =
|
||||
dateField "date" "%B %e, %Y"
|
||||
<> dateField "date" "%Y-%m-%d"
|
||||
<> defaultContext
|
||||
|
||||
-- toc from https://github.com/slotThe/slotThe.github.io
|
||||
getTocCtx :: Context a -> Compiler (Context a)
|
||||
getTocCtx ctx = do
|
||||
noToc <- (Just "true" ==) <$> (getUnderlying >>= (`getMetadataField` "no-toc"))
|
||||
writerOpts <- mkTocWriter defaultHakyllWriterOptions
|
||||
toc <- writePandocWith writerOpts <$> chaoDocPandocCompiler
|
||||
pure $
|
||||
mconcat
|
||||
[ ctx,
|
||||
constField "toc" $ killLinkIds (itemBody toc),
|
||||
if noToc then boolField "no-toc" (pure noToc) else mempty
|
||||
]
|
||||
where
|
||||
mkTocWriter :: WriterOptions -> Compiler WriterOptions
|
||||
mkTocWriter writerOpts = do
|
||||
tmpl <- either (const Nothing) Just <$> unsafeCompiler (compileTemplate "" "$toc$")
|
||||
pure $
|
||||
writerOpts
|
||||
{ writerTableOfContents = True,
|
||||
writerTOCDepth = 2,
|
||||
writerTemplate = tmpl,
|
||||
writerHTMLMathMethod = MathML
|
||||
}
|
||||
|
||||
asTxt :: (T.Text -> T.Text) -> String -> String
|
||||
asTxt f = T.unpack . f . T.pack
|
||||
|
||||
killLinkIds :: String -> String
|
||||
killLinkIds = asTxt (mconcat . go . T.splitOn "id=\"toc-")
|
||||
where
|
||||
go :: [T.Text] -> [T.Text]
|
||||
go = \case
|
||||
[] -> []
|
||||
x : xs -> x : map (T.drop 1 . T.dropWhile (/= '\"')) xs
|
||||
@@ -0,0 +1,23 @@
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
$partial("templates/head.html")$
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div class="toc">
|
||||
<div id="contents-big">
|
||||
<p class="mini-header">Contents</p>
|
||||
<a id="up-arrow" href="#">#</a>
|
||||
$toc$
|
||||
</div>
|
||||
</div>
|
||||
<div class="text-space">
|
||||
$partial("templates/navbar.html")$
|
||||
<main role="main">
|
||||
<h1 class="pagetitle">$title$</h1>
|
||||
$body$
|
||||
</main>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,10 @@
|
||||
<meta charset="utf-8" />
|
||||
<meta http-equiv="x-ua-compatible" content="ie=edge" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<meta name="googlebot" content="noindex" />
|
||||
<title>$title$</title>
|
||||
<link rel="stylesheet" href="/css/fonts.css" />
|
||||
<link rel="stylesheet" href="/css/default.css" />
|
||||
<link rel="stylesheet" href="/css/pygentize.css" />
|
||||
<link rel="stylesheet" href="/css/chao-theorems.css" />
|
||||
<link rel="stylesheet" href="/css/sidenotes.css" />
|
||||
@@ -0,0 +1,5 @@
|
||||
<header class="no-print">
|
||||
<nav>
|
||||
<a href="/">Home</a>
|
||||
</nav>
|
||||
</header>
|
||||
@@ -0,0 +1,5 @@
|
||||
<ul class="post-list">
|
||||
$for(posts)$
|
||||
<li><a href="$url$">$title$</a></li>
|
||||
$endfor$
|
||||
</ul>
|
||||
@@ -0,0 +1,5 @@
|
||||
<article>
|
||||
<section class="body">
|
||||
$body$
|
||||
</section>
|
||||
</article>
|
||||
@@ -0,0 +1,37 @@
|
||||
name: zzz
|
||||
version: 0.1.0.0
|
||||
build-type: Simple
|
||||
cabal-version: >= 1.10
|
||||
|
||||
|
||||
executable site
|
||||
hs-source-dirs: src
|
||||
main-is: site.hs
|
||||
other-modules: ChaoDoc, SideNoteHTML, Pangu
|
||||
build-depends: base >= 4.18
|
||||
, hakyll >= 4.15
|
||||
, mtl >= 2.2.2
|
||||
, pandoc
|
||||
, pandoc-types >= 1.22.2.1
|
||||
, pandoc-sidenote
|
||||
, tagsoup
|
||||
, text
|
||||
, containers
|
||||
-- , process
|
||||
-- , regex-compat
|
||||
, array
|
||||
, filepath
|
||||
-- , ghc-syntax-highlighter
|
||||
-- , blaze-html >= 0.9
|
||||
, megaparsec
|
||||
, replace-megaparsec
|
||||
ghc-options: -Weverything
|
||||
-Wno-implicit-prelude
|
||||
-Wno-missing-import-lists
|
||||
-Wno-unused-packages
|
||||
-Wno-missing-safe-haskell-mode
|
||||
-Wno-all-missed-specialisations
|
||||
-Wno-unsafe
|
||||
-Wno-prepositive-qualified-module
|
||||
-O2 -threaded -rtsopts -with-rtsopts=-N
|
||||
default-language: Haskell2010
|
||||
Reference in New Issue
Block a user