aboutsummaryrefslogtreecommitdiff
path: root/static
diff options
context:
space:
mode:
authortdro <tdro@noreply.example.com>2022-05-03 04:25:49 -0400
committertdro <tdro@noreply.example.com>2022-05-03 04:25:49 -0400
commite92dc018e28ba8d44f1c6cbc9fb0b779dec271e6 (patch)
tree3667043819ab91065019dfe3b4ff83ad625c2b49 /static
downloadcanory-e92dc018e28ba8d44f1c6cbc9fb0b779dec271e6.tar.gz
canory-e92dc018e28ba8d44f1c6cbc9fb0b779dec271e6.tar.bz2
canory-e92dc018e28ba8d44f1c6cbc9fb0b779dec271e6.zip
config.yaml: Commission
Diffstat (limited to 'static')
-rw-r--r--static/css/default.css1629
-rw-r--r--static/css/noscript.css3
-rw-r--r--static/css/syntax-highlight-default.css202
-rw-r--r--static/images/404.pngbin0 -> 6718 bytes
-rw-r--r--static/js/code-copy.ts22
-rw-r--r--static/js/fixedsearch.ts194
-rw-r--r--static/js/fuzzysort.js636
-rw-r--r--static/js/index.js424
-rw-r--r--static/js/index.ts6
-rw-r--r--static/js/instantpage.ts292
-rw-r--r--static/js/pager.ts46
-rw-r--r--static/js/plumber.ts59
12 files changed, 3513 insertions, 0 deletions
diff --git a/static/css/default.css b/static/css/default.css
new file mode 100644
index 0000000..5feedfa
--- /dev/null
+++ b/static/css/default.css
@@ -0,0 +1,1629 @@
+/* ###### Semantic Pact CSS ###### */
+
+/* ----- Selection ----- */
+
+::selection {
+ color: #fff;
+ background-color: #000;
+ text-shadow: 0 0 currentColor;
+}
+
+::-webkit-selection {
+ color: #fff;
+ background-color: #000;
+ text-shadow: 0 0 currentColor;
+}
+
+::-moz-selection {
+ color: #fff;
+ background-color: #000;
+ text-shadow: 0 0 currentColor;
+}
+
+/* ----- Colors ----- */
+
+:root {
+ color-scheme: light dark;
+ --active-alternate: #cce1ff;
+ --background-bolder: #fff;
+ --background-transparent: rgba(255, 255, 255, 0.9);
+ --background: #fefefe;
+ --border-darker: #ccc;
+ --border-lighter: #eee;
+ --button-background: #fff;
+ --danger-background: #fcd2cf;
+ --danger: #8f0000;
+ --disabled: #999;
+ --fade: #555;
+ --foreground: #111;
+ --hover-alternate: #ebf3ff;
+ --hover: #eee;
+ --input-background: #eff3f4;
+ --link: #0149bc;
+ --shadow: #999;
+ --spoiler: #333;
+ --success-background: #d5f3c4;
+ --success: #1d3c0c;
+ --target-background: #fffdeb;
+ --warning-background: #ffe699;
+ --warning: #332600;
+ --widget-background: #f9f9f9;
+ --table-row-odd-background: #f3f3f3;
+ --table-row-even-background: #f9f9f9;
+}
+
+@media (prefers-color-scheme: dark) {
+ :root {
+ --active-alternate: var(--hover-alternate);
+ --background-bolder: #111;
+ --background-transparent: rgba(28, 28, 28, 0.9);
+ --background: #1c1c1c;
+ --border-darker: #555;
+ --border-lighter: #262626;
+ --button-background: #333;
+ --danger-background: #220402;
+ --danger: #ff9494;
+ --disabled: #999;
+ --fade: #aaa;
+ --foreground: #ddd;
+ --hover-alternate: #1c263a;
+ --hover: #303030;
+ --input-background: #333;
+ --link: #8cb4ff;
+ --shadow: #000;
+ --spoiler: #333;
+ --success-background: #2d3e0f;
+ --success: #d5f3c4;
+ --target-background: #2e2c00;
+ --warning-background: #3d3d00;
+ --warning: #ffe699;
+ --widget-background: #262626;
+ --table-row-odd-background: #2e3838;
+ --table-row-even-background: #273030;
+ }
+ img {
+ filter: brightness(75%);
+ }
+ code-block {
+ filter: invert(90%);
+ display: block;
+ }
+}
+
+/* ----- Psuedo ----- */
+
+*,
+::before,
+::after {
+ box-sizing: border-box;
+}
+
+::before,
+::after {
+ text-decoration: inherit;
+ vertical-align: inherit;
+}
+
+:target::before {
+ content: "";
+ display: block;
+ height: 4rem;
+ margin-top: -4rem;
+ visibility: hidden;
+}
+
+:not(#main):target micro-summary,
+:not(#main):target micro-summary:hover {
+ background-color: #fffdeb;
+ background-color: var(--target-background);
+ outline: 1px dashed #ccc;
+ outline: 1px dashed var(--border-darker);
+ outline-offset: -1px;
+}
+
+:focus:not(html):not(body):not(svg) {
+ outline: 0.15rem solid;
+ outline-offset: -0.15rem;
+ text-decoration: none;
+}
+
+:focus:not(html):not(body):not(pre):not(svg) {
+ background-color: #ffe699;
+ background-color: var(--warning-background);
+}
+
+/* ----- Skip link ----- */
+
+skip-link a {
+ position: absolute;
+ left: -9999em;
+}
+
+skip-link a:focus {
+ z-index: 200;
+ position: fixed;
+ left: 50%;
+ transform: translate(-50%, 0);
+ top: 0.5rem;
+ padding: 0.75rem;
+ border-radius: 0.25rem;
+}
+
+/* ----- Elements ----- */
+
+html {
+ font-size: 95%;
+ line-height: 1.5;
+ word-break: break-word;
+ text-rendering: optimizeSpeed;
+ margin-left: calc(100vw - 100%);
+ margin-right: 0;
+}
+
+html,
+samp {
+ font-family: "Noto Sans", -apple-system, BlinkMacSystemFont, "Segoe UI",
+ Roboto, Oxygen, Ubuntu, Cantarell, "Fira Sans", "Droid Sans",
+ "Helvetica Neue", sans-serif, "Apple Color Emoji", "Segoe UI Emoji",
+ "Segoe UI Symbol";
+}
+
+body,
+dl dl,
+dl ol,
+dl ul,
+ol dl,
+ul dl,
+ol ol,
+ol ul,
+ul ol,
+ul ul,
+button,
+input,
+select,
+textarea {
+ margin: 0;
+}
+
+ol {
+ counter-reset: ol;
+ padding-left: 2.5rem;
+}
+
+ol li {
+ overflow-wrap: break-word;
+ word-break: break-word;
+ list-style: none;
+}
+
+ol li:before {
+ border-radius: 100%;
+ border: 1px solid;
+ content: counter(ol);
+ counter-increment: ol;
+ margin-left: -2rem;
+ font-size: 85%;
+ padding: 0 0.35rem;
+ position: absolute;
+}
+
+ol li + li {
+ margin-top: 0.5rem;
+}
+
+main,
+details,
+code-block,
+diagram-container {
+ display: block;
+}
+
+summary {
+ display: list-item;
+}
+
+cite {
+ font-style: normal;
+}
+
+ul {
+ list-style: none;
+ padding-left: 1rem;
+}
+
+nav ol,
+nav ul {
+ list-style: none;
+ display: inline-block;
+ padding: 0;
+}
+
+footer {
+ text-align: center;
+ color: #444;
+ color: var(--fade);
+ margin: 1rem 0;
+ font-size: 85%;
+}
+
+figcaption a {
+ color: inherit;
+}
+
+q a {
+ color: inherit;
+}
+
+q:before {
+ content: "\201C";
+}
+
+q:after {
+ content: "\201D";
+}
+
+object {
+ width: 100%;
+ border: 1px solid #ccc;
+ border: 1px solid var(--border-lighter);
+ min-height: 16rem;
+ border-radius: 0.5rem;
+}
+
+details {
+ cursor: pointer;
+}
+
+details:hover summary span {
+ text-decoration: underline;
+}
+
+a {
+ color: #0149bc;
+ color: var(--link);
+ text-decoration: underline;
+ text-underline-offset: 0.05rem;
+}
+
+a:hover {
+ color: #000;
+ color: var(--foreground);
+ text-decoration-thickness: 0.15rem !important;
+}
+
+a:hover img {
+ box-shadow: 0 8px 32px #999;
+ box-shadow: 0 8px 32px var(--shadow);
+ filter: unset;
+}
+
+a:active,
+button:active {
+ position: relative;
+ top: 1px;
+}
+
+column-left nav {
+ position: sticky;
+ position: -webkit-sticky;
+ align-self: flex-start;
+ top: 0.5rem;
+ text-align: right;
+}
+
+column-left nav a {
+ align-items: center;
+ color: inherit;
+ color: var(--foreground);
+ display: flex;
+ flex-wrap: wrap;
+ justify-content: flex-end;
+ padding: 0.5rem;
+ text-decoration: none;
+ border-radius: 0.5rem;
+}
+
+column-left nav li {
+ margin-bottom: 0.25rem;
+}
+
+column-left nav li:hover a {
+ background-color: #eee;
+ background-color: var(--hover);
+ border-radius: 0.5rem;
+}
+
+column-left nav li:hover svg {
+ fill: #ffe699;
+ fill: var(--warning-background);
+}
+
+column-left nav a span {
+ padding: 0 1rem;
+}
+
+pre,
+kbd,
+code,
+svg text,
+item-list a span:last-child,
+item-list a span:first-child {
+ font-family: "Fira Code", "Lucida Console", "Andale Mono", "Roboto Mono",
+ "Ubuntu Monospace", "Noto Mono", "Oxygen Mono", "Liberation Mono", monospace,
+ "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji";
+}
+
+abbr[title] {
+ cursor: help;
+ border-bottom: 1px dashed;
+ text-decoration: none;
+}
+
+abbr[title]:hover {
+ border-bottom: 2px dashed;
+}
+
+samp {
+ font-variant: all-small-caps;
+ letter-spacing: 0.05rem;
+}
+
+b,
+strong {
+ font-weight: 700;
+}
+
+small {
+ font-size: 80%;
+ opacity: 0.85;
+}
+
+audio,
+canvas,
+iframe,
+img,
+svg,
+video {
+ vertical-align: middle;
+}
+
+video,
+audio {
+ width: 100%;
+}
+
+audio {
+ margin: 0.75rem 0;
+}
+
+figure {
+ text-align: center;
+}
+
+iframe {
+ width: 100%;
+ border: 1px solid #ccc;
+ border: 1px solid var(--border-darker);
+ background-color: #fefefe;
+ border-radius: 0.75rem !important;
+ min-height: 16rem;
+}
+
+self-embed iframe {
+ min-height: 16rem;
+ max-height: 16rem;
+}
+
+img {
+ height: auto;
+ max-width: 100%;
+ border: 1px solid #ccc;
+ border: 1px solid var(--border-darker);
+ border-radius: 0.75rem;
+ object-fit: cover;
+ display: block;
+}
+
+img,
+iframe {
+ user-select: none;
+}
+
+p {
+ line-height: 1.35;
+}
+
+svg {
+ stroke-width: 0.08rem;
+}
+
+svg:not([fill]) {
+ fill: currentColor;
+}
+
+svg:not(:root) {
+ overflow: hidden;
+}
+
+button,
+input {
+ overflow: visible;
+}
+
+button,
+select {
+ text-transform: none;
+}
+
+button,
+[type="reset"],
+[type="button"],
+[type="submit"],
+paginator-navigation a,
+micro-author micro-summary > a {
+ align-items: center;
+ appearance: button;
+ background-color: #fff;
+ background-color: var(--button-background);
+ border-radius: 0.5rem;
+ border: 1px solid #ccc;
+ border: 1px solid var(--border-lighter);
+ box-shadow: 0 1px 1px #999;
+ color: inherit;
+ cursor: pointer;
+ display: flex;
+ font-size: 100%;
+ height: 1.95rem;
+ padding: 0.25rem 0.5rem;
+ text-decoration: none;
+}
+
+button svg,
+micro-author micro-summary > a svg {
+ height: 1.25rem;
+ margin: 0 0.25rem;
+}
+
+hr {
+ border-top: 1px solid;
+}
+
+/* ----- Tables ----- */
+
+table {
+ border-radius: 0.5rem;
+ border-spacing: 0;
+ border: 1px solid #ccc;
+ border: 1px solid var(--border-darker);
+ padding: 0.5rem 0 0 0;
+ width: 100%;
+ background-color: #fefefe;
+ background-color: var(--background);
+}
+
+th {
+ border-bottom: 1px solid #ccc;
+ border-bottom: 1px solid var(--border-darker);
+ padding: 0 0.5rem 0.5rem 0.5rem;
+ text-align: left;
+}
+
+td {
+ padding: 0.5rem 0 0.5rem 0.5rem;
+ border-right: 1px solid #ccc;
+ border-right: 1px solid var(--border-darker);
+ border-bottom: 1px solid #ccc;
+ border-bottom: 1px solid var(--border-darker);
+}
+
+td:last-child {
+ padding-right: 0.5rem;
+ border-right: none;
+}
+
+tr:last-child td {
+ border-bottom: none;
+}
+
+tbody tr:nth-child(odd) td {
+ background-color: #f3f3f3;
+ background-color: var(--table-row-odd-background);
+}
+
+tbody tr:nth-child(even) td {
+ background-color: #f9f9f9;
+ background-color: var(--table-row-even-background);
+}
+
+tbody tr:hover td {
+ background-color: #eee;
+ background-color: var(--hover);
+}
+
+tr:last-child td:first-child {
+ border-bottom-left-radius: 0.5rem;
+}
+
+tr:last-child td:last-child {
+ border-bottom-right-radius: 0.5rem;
+}
+
+/* ----- Print ----- */
+
+@media print {
+ a[href^="http"]::after {
+ content: " (" attr(href) ")";
+ }
+
+ abbr[title]::after {
+ content: " (" attr(title) ")";
+ }
+}
+
+/* ----- Base ----- */
+
+body {
+ background-color: #fefefe;
+ background-color: var(--background);
+ color: #000;
+ color: var(--foreground);
+ display: flex;
+ margin: 0 auto;
+ max-width: 1250px;
+ min-height: 100%;
+ overflow-x: hidden;
+ -webkit-text-size-adjust: none;
+}
+
+column-left {
+ display: flex;
+ flex: 1;
+ justify-content: flex-end;
+}
+
+column-right {
+ flex: 2;
+ max-width: 400px;
+}
+
+column-left,
+column-right {
+ margin: 0.5rem 2rem;
+}
+
+column-middle {
+ flex: 6;
+ border-left: 1px solid;
+ border-right: 1px solid;
+ border-color: #eee;
+ border-color: var(--border-lighter);
+ max-width: 600px;
+ min-height: 100vh;
+ width: 100%;
+}
+
+micro-card,
+micro-tags,
+micro-expires,
+micro-article {
+ display: block;
+}
+
+message-archive h1 {
+ text-align: center;
+ font-weight: 400;
+ font-size: 150%;
+}
+
+micro-tags {
+ margin-bottom: -0.5rem;
+ margin-top: 0.5rem;
+}
+
+micro-tags a {
+ border-radius: 0.5rem;
+ padding-top: 0.125rem;
+ padding-bottom: 0.125rem;
+ display: inline-block;
+ padding-left: 0.5rem;
+ text-decoration: none;
+ padding-right: 0.5rem;
+ box-shadow: 0 1px 1px #999;
+ margin: 0 0.25rem 0.5rem 0;
+}
+
+micro-summary,
+micro-article {
+ border-bottom: 1px solid;
+ border-color: #eee;
+ border-color: var(--border-lighter);
+ display: flex;
+ padding: 1rem;
+ position: relative;
+}
+
+micro-summary:hover,
+micro-summary:focus-within {
+ background-color: #f5f9ff;
+ background-color: var(--hover-alternate);
+}
+
+micro-author micro-summary {
+ justify-content: start;
+ align-items: center;
+ border: none;
+}
+
+micro-author micro-thumbnail {
+ flex: none;
+}
+
+micro-author micro-header {
+ line-height: 1;
+ margin: 0 1.25rem 0 0.5rem;
+ flex: 1;
+ height: 3rem;
+ padding: 0 0.5rem;
+}
+
+micro-thumbnail a {
+ display: block;
+}
+
+micro-thumbnail figure {
+ margin: 0 0.75rem 0 0;
+}
+
+micro-thumbnail figure img {
+ border-radius: 100%;
+ min-width: 3.5rem;
+ min-height: 3.5rem;
+ max-width: 3.5rem;
+ max-height: 3.5rem;
+}
+
+micro-content {
+ flex: 8;
+ width: 0;
+}
+
+micro-metadata {
+ color: #444;
+ color: var(--fade);
+ line-height: 1.25;
+ display: flex;
+ align-items: center;
+}
+
+micro-metadata h2 {
+ margin-right: 0.5rem;
+}
+
+micro-metadata a {
+ color: inherit;
+}
+
+micro-metadata expiry-date,
+micro-metadata word-limit em,
+micro-metadata draft-label em {
+ color: #8f0000;
+ color: var(--danger);
+ font-style: normal;
+}
+
+micro-metadata draft-label em {
+ font-variant: all-small-caps;
+}
+
+micro-header footer svg,
+micro-metadata expiry-date svg,
+micro-metadata anchored-entry svg,
+micro-metadata unlisted-entry svg {
+ height: 1rem;
+ width: 1rem;
+ stroke-width: 0.1rem;
+}
+
+micro-metadata unlisted-entry {
+ background-color: #ffe699;
+ background-color: var(--warning-background);
+ color: #332600;
+ color: var(--warning);
+ border-radius: 0.25rem;
+ padding: 0 0.25rem;
+}
+
+micro-metadata read-time,
+micro-metadata word-limit,
+micro-metadata expiry-date,
+micro-metadata unlisted-entry {
+ cursor: help;
+}
+
+anchored-entry {
+ display: flex;
+ align-items: center;
+ margin-bottom: 0.25rem;
+ margin-top: -0.5rem;
+}
+
+anchored-entry svg {
+ margin-right: 0.25rem;
+}
+
+anchored-entry span {
+ margin-right: 0.25rem;
+}
+
+anchored-entry span:not(:last-child):after {
+ content: "\00B7";
+ margin-left: 0.25rem;
+}
+
+anchored-pinned svg {
+ fill: #9ed1fa;
+}
+
+anchored-liked svg {
+ fill: #cd5c5c;
+}
+
+micro-header micro-header-image img {
+ height: 12rem;
+ width: 100%;
+ border-radius: 0;
+}
+
+micro-header micro-header-picture img {
+ height: 8rem;
+ width: 8rem;
+ border-radius: 100%;
+ margin: -4rem 2rem 0 2rem;
+ background-color: #fff;
+ padding: 0.35rem;
+ position: relative;
+}
+
+micro-header section {
+ margin: 1rem 2rem 0 2rem;
+ line-height: 1;
+ display: flex;
+ align-items: baseline;
+}
+
+micro-header-title {
+ margin-right: 1rem;
+ flex: 1;
+}
+
+micro-header-description {
+ flex: 6;
+}
+
+micro-header h1 {
+ margin-bottom: 0.25rem;
+}
+
+micro-header h2 {
+ font-weight: 400;
+ color: #444;
+ color: var(--fade);
+ margin: 0;
+}
+
+micro-header footer,
+micro-header footer code {
+ color: #444;
+ color: var(--fade);
+}
+
+micro-header footer {
+ margin: 1.25rem 2rem 0.25rem 2rem;
+ text-align: center;
+ font-size: 95%;
+}
+
+micro-header footer p {
+ margin: 0.75rem 0.5rem 0.25em 0;
+ display: inline;
+}
+
+micro-nsfw-content * {
+ visibility: hidden;
+ opacity: 0;
+ pointer-events: none;
+}
+
+micro-nsfw details svg {
+ stroke-width: 0.1rem;
+ margin-right: 0.15rem;
+}
+
+micro-nsfw details summary {
+ font-variant: all-small-caps;
+ letter-spacing: 0.025rem;
+ line-height: 1.25;
+ color: #8f0000;
+ color: var(--danger);
+ list-style: none;
+}
+
+micro-nsfw details > summary::-webkit-details-marker {
+ display: none;
+}
+
+micro-nsfw-content {
+ border-radius: 0.25rem;
+ display: block;
+ background: repeating-linear-gradient(
+ 180deg,
+ #ff9999,
+ #ff9999 1rem,
+ #eee 1rem,
+ #eee 2rem
+ );
+}
+
+micro-nsfw details[open] + micro-nsfw-content * {
+ filter: none;
+ transition: 1s;
+ visibility: visible;
+ opacity: 1;
+ background: none;
+ outline: none;
+ pointer-events: auto;
+}
+
+micro-nsfw details[open] + micro-nsfw-content {
+ background: none;
+}
+
+@supports (filter: blur(2rem)) {
+ micro-nsfw-content * {
+ filter: blur(2rem);
+ visibility: unset;
+ opacity: unset;
+ }
+ micro-nsfw-content {
+ outline: none;
+ background: unset;
+ }
+}
+
+tab-list {
+ display: flex;
+ justify-content: center;
+ border-bottom: 1px solid;
+ border-color: #ddd;
+ border-color: var(--border-darker);
+}
+
+tab-list a:not(:last-child) {
+ margin-right: 0.5rem;
+}
+
+tab-list a {
+ border-left: 1px solid;
+ border-right: 1px solid;
+ border-top-left-radius: 0.5rem;
+ border-top-right-radius: 0.5rem;
+ border-top: 1px solid;
+ border-color: #ddd;
+ border-color: var(--border-darker);
+ color: #333;
+ color: var(--foreground);
+ margin-top: 0.5rem;
+ padding: 0.5rem 1rem;
+ position: relative;
+ text-decoration: none;
+ top: 1px;
+ border-bottom: 2px solid var(--background);
+}
+
+tab-list a:hover {
+ background-color: #eee;
+ background-color: var(--hover);
+ color: inherit;
+}
+
+column-middle nav {
+ align-items: center;
+ backdrop-filter: blur(0.1rem);
+ background-color: rgba(255, 255, 255, 0.9);
+ background-color: var(--background-transparent);
+ border-bottom: 1px solid;
+ border-color: #eee;
+ border-color: var(--border-lighter);
+ display: flex;
+ height: 4rem;
+ position: sticky;
+ position: -webkit-sticky;
+ top: 0;
+ width: 100%;
+ z-index: 100;
+}
+
+column-middle nav section h1,
+column-middle nav section small {
+ margin: 0;
+}
+
+micro-author micro-header,
+column-middle nav section h1,
+column-middle nav section small {
+ white-space: nowrap;
+ overflow: hidden;
+ text-overflow: ellipsis;
+}
+
+column-middle nav section {
+ line-height: 1.25;
+ padding: 0 0.5rem;
+}
+
+icon-button a {
+ color: inherit;
+ display: flex;
+ justify-content: center;
+ align-items: center;
+ width: 4rem;
+ flex-wrap: wrap;
+ text-decoration: none;
+ height: 100%;
+ padding: 0.5rem 0;
+}
+
+icon-button a:hover {
+ background-color: #eee;
+ background-color: var(--hover);
+}
+
+icon-button svg {
+ width: 100%;
+}
+
+icon-button p {
+ margin: 0;
+}
+
+code-block pre code {
+ display: inline-block;
+ min-width: 100%;
+ border-top: 1px solid #bbb;
+ padding: 0.75rem;
+}
+
+code-block[data-lines] pre code {
+ padding: 0.1rem 0.75rem;
+}
+
+code-block header {
+ position: relative;
+ top: 1.85rem;
+ margin-top: -1rem;
+ height: 1rem;
+ padding: 0 0.5rem;
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+}
+
+code-block header language-label {
+ padding: 0.125rem 1rem;
+ font-variant: all-small-caps;
+}
+
+code-block pre {
+ border-radius: 0.5rem;
+ border: 1px solid #ccc;
+ border: 1px solid var(--border-darker);
+ display: block;
+ font-size: 95%;
+ margin: 0;
+ overflow-x: auto;
+ overflow-y: hidden;
+ padding: 2.65rem 0 0 0;
+ tab-size: 2;
+ word-break: break-word;
+ white-space: pre;
+}
+
+paginator-navigation {
+ padding: 1rem;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+}
+
+paginator-navigation a,
+paginator-navigation button {
+ margin: 0 0.25rem;
+ font-size: 85%;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+}
+
+paginator-navigation svg {
+ height: 1.316rem;
+}
+
+web-ring,
+author-list,
+gallery-images {
+ padding-bottom: 2rem;
+ display: block;
+}
+
+web-ring > h1,
+author-list > h1,
+gallery-images > h1 {
+ text-align: center;
+ margin-bottom: 1rem;
+ font-variant: all-small-caps;
+ font-size: 110%;
+}
+
+gallery-images section {
+ display: flex;
+ flex-wrap: wrap;
+}
+
+gallery-images img {
+ width: 33.333%;
+ border-radius: 0;
+ height: 6rem;
+}
+
+gallery-images img:first-child {
+ border-top-left-radius: 0.5rem;
+}
+
+gallery-images img:nth-child(3) {
+ border-top-right-radius: 0.5rem;
+}
+
+gallery-images img:nth-child(4) {
+ border-bottom-left-radius: 0.5rem;
+}
+
+gallery-images img:last-child {
+ border-bottom-right-radius: 0.5rem;
+}
+
+web-ring {
+ border-radius: 1rem;
+ position: sticky;
+ position: -webkit-sticky;
+ top: 1rem;
+}
+
+web-ring a {
+ text-decoration: none;
+}
+
+web-ring time {
+ font-variant: small-caps;
+ color: #444;
+ color: var(--fade);
+}
+
+web-ring header {
+ font-weight: 700;
+}
+
+web-ring p {
+ margin-bottom: 0 !important;
+}
+
+web-ring-item:not(:last-child) {
+ margin-bottom: 2rem;
+ display: block;
+}
+
+micro-tags a,
+web-ring section,
+author-list section {
+ background-color: #f9f9f9;
+ background-color: var(--widget-background);
+}
+
+web-ring section {
+ border-radius: 1rem;
+ padding: 1rem;
+}
+
+author-list section {
+ display: block;
+ border-radius: 1rem;
+}
+
+search-entry {
+ display: inherit;
+ width: 100%;
+}
+
+search-entry:focus-within {
+ position: absolute;
+}
+
+search-entry:focus-within form {
+ border-bottom-left-radius: 0;
+ border-bottom-right-radius: 0;
+}
+
+search-entry form {
+ background-color: #fefefe;
+ background-color: var(--background);
+ border-radius: 0.5rem;
+ border: 1px solid;
+ border: 1px solid;
+ border-color: #ccc;
+ border-color: var(--border-darker);
+ display: inline-flex;
+ margin: 0 0.5rem;
+ width: 100%;
+}
+
+search-entry form button {
+ background-color: transparent;
+ border: none;
+ box-shadow: none;
+ cursor: pointer;
+ display: block;
+ padding: 0;
+ border-radius: 0;
+ border-top-right-radius: 0.5rem;
+ border-bottom-right-radius: 0.5rem;
+ min-width: 4rem;
+ max-width: 4rem;
+ height: 2.85rem;
+ line-height: 1;
+}
+
+search-entry form input {
+ -webkit-appearance: none;
+ background-color: #fefefe;
+ background-color: var(--background);
+ border-bottom-left-radius: 0.5rem;
+ border-top-left-radius: 0.5rem;
+ border: none;
+ font-size: 100%;
+ line-height: 1.5;
+ padding-left: 1rem;
+ padding-right: 1rem;
+ width: 100%;
+}
+
+search-entry form button svg {
+ stroke-width: 0.1rem;
+}
+
+search-entry form input:focus {
+ outline: none;
+ background-color: #fefefe !important;
+ background-color: var(--background) !important;
+}
+
+search-entry ul {
+ position: absolute;
+ background-color: inherit;
+ padding-top: 3rem;
+ border: 1px solid #ccc;
+ border: 1px solid var(--border-darker);
+ border-radius: 0.5rem;
+ z-index: -1;
+ margin-right: 0.5rem;
+ margin-left: -0.05rem;
+}
+
+search-entry ul li a span {
+ font-weight: 400;
+}
+
+search-entry ul li a {
+ font-weight: 700;
+ color: inherit;
+ text-decoration: none;
+ display: block;
+ padding: 0.25rem 0.75rem;
+}
+
+search-entry ul li:first-child a {
+ background-color: #ebf3ff;
+ background-color: var(--hover-alternate);
+}
+
+search-entry ul li a:hover {
+ background-color: #eee;
+ background-color: var(--hover);
+}
+
+item-list {
+ font-size: 100%;
+}
+
+item-list h1 {
+ padding: 1rem;
+}
+
+item-list ul {
+ padding: 0 0.5rem 0 0.5rem;
+}
+
+item-list a span:first-child {
+ text-transform: uppercase;
+}
+
+item-list a span:first-child,
+item-list a span:last-child {
+ flex: 1;
+ color: #444;
+ color: var(--fade);
+}
+
+item-list a span:nth-child(2) {
+ flex: 3;
+ margin: 0 0.5rem;
+}
+
+item-list li a {
+ display: flex;
+ text-decoration: none;
+ align-items: baseline;
+ margin: 0.25rem 0;
+ padding: 0.5rem;
+}
+
+item-list li:hover {
+ background-color: #eee;
+ background-color: var(--hover);
+}
+
+item-list li:hover span:last-child {
+ text-decoration: underline;
+}
+
+source-array footer {
+ padding: 0 1rem;
+}
+
+source-array li a {
+ padding: 0.25rem 0.5rem;
+}
+
+context-menu {
+ background-color: #fff;
+ background-color: var(--background-bolder);
+ border: 1px solid;
+ border-color: #ddd;
+ border-color: var(--border-lighter);
+ border-radius: 4px;
+ box-shadow: 0 0.5em 1em -0.125em rgba(10, 10, 10, 0.1),
+ 0 0 0 1px rgba(10, 10, 10, 0.02);
+ padding-bottom: 0.5rem;
+ padding-top: 0.5rem;
+ min-width: 6.25rem;
+ position: absolute;
+ z-index: 101;
+ max-width: 15rem;
+ visibility: hidden;
+ opacity: 0;
+}
+
+context-profile context-menu {
+ padding: 1rem;
+ display: block;
+ text-align: left;
+}
+
+context-profile a {
+ display: inline;
+}
+
+context-profile h2 {
+ color: #333;
+ color: var(--fade);
+ font-weight: 400;
+}
+
+context-profile section {
+ display: flex;
+ align-items: center;
+}
+
+context-profile aside:nth-child(2) {
+ line-height: 1.25;
+}
+
+micro-thumbnail figure:hover context-menu {
+ visibility: visible;
+ opacity: 1;
+}
+
+diagram-container svg {
+ border: 1px solid;
+ border-radius: 0.5rem;
+ border-color: #ccc;
+ border-color: var(--border-darker);
+ margin: 0.5rem 0;
+ background-color: #fefefe;
+ background-color: var(--background);
+}
+
+twitter-tweet {
+ min-height: 20rem;
+ max-height: 20rem;
+ display: block;
+ overflow-y: auto;
+}
+
+twitter-tweet iframe {
+ border: none;
+}
+
+/* ----- Typography ----- */
+
+h1,
+h2,
+h3 {
+ font-size: 100%;
+ margin: 0;
+}
+
+p,
+ul,
+dd,
+dl,
+ol,
+figure,
+blockquote {
+ margin: 0;
+}
+
+p,
+dl,
+table,
+figure {
+ margin-top: 0.25rem;
+}
+
+p:not(:last-child),
+ol:not(:last-child),
+ul:not(:last-child),
+dl:not(:last-child),
+table:not(:last-child),
+figure:not(:last-child),
+code-block:not(:last-child),
+blockquote:not(:last-child) {
+ margin-bottom: 0.75rem;
+}
+
+figcaption {
+ font-size: 85%;
+ margin-top: 0.5rem;
+ text-align: center;
+ color: #444;
+ color: var(--fade);
+}
+
+sup {
+ vertical-align: super;
+ font-size: 85%;
+}
+
+sub {
+ vertical-align: sub;
+ font-size: 85%;
+}
+
+s,
+del,
+mark,
+ins {
+ padding: 0 0.15rem;
+ line-height: 1.5;
+}
+
+mark {
+ background-color: #ffe699;
+ background-color: var(--warning-background);
+ color: #332600;
+ color: var(--warning);
+}
+
+ins {
+ background-color: #d5f3c4;
+ background-color: var(--success-background);
+ color: #1d3c0c;
+ color: var(--success);
+ text-decoration: none;
+}
+
+del {
+ background-color: #fcd2cf;
+ background-color: var(--danger-background);
+ color: #8f0000;
+ color: var(--danger);
+}
+
+s {
+ text-decoration: none;
+ background-color: #333;
+ background-color: var(--spoiler);
+ color: transparent;
+}
+
+s label {
+ cursor: help;
+}
+
+[type="checkbox"]:checked ~ s {
+ background-color: inherit;
+ color: inherit;
+}
+
+blockquote {
+ color: var(--fade);
+ padding-left: 2rem;
+}
+
+video {
+ border-radius: 0.75rem;
+}
+
+kbd {
+ background-color: #eee;
+ border-radius: 3px;
+ border: 1px solid #b4b4b4;
+ box-shadow: 0 1px 1px #737373, 0 2px 0 0 #f2f2f2 inset;
+ color: #333;
+ display: inline-block;
+ font-size: 0.85em;
+ font-weight: 700;
+ line-height: 1;
+ padding: 2px 4px;
+ white-space: nowrap;
+}
+
+/* ----- Attributes ----- */
+
+[tabindex="-1"]:focus {
+ outline: none;
+}
+
+[hidden] {
+ display: none;
+}
+
+[disabled] {
+ cursor: not-allowed;
+ border-color: #ddd;
+ border-color: var(--border-lighter);
+ color: #999;
+ color: var(--disabled);
+}
+
+[id^="imgur-embed-iframe-pub"] {
+ min-width: 100%;
+}
+
+[role="doc-endnotes"] {
+ margin-bottom: 0.75rem;
+}
+
+[role="doc-endnotes"] hr {
+ border-top: 1px solid #ccc;
+ margin-bottom: 0.75rem;
+}
+
+a[href^="http"]:hover {
+ text-decoration: underline;
+}
+
+/* ----- Responsive ----- */
+
+@media screen and (max-width: 1220px) {
+ column-left {
+ flex: none;
+ margin: 0.5rem;
+ }
+ column-left nav a {
+ flex-direction: column-reverse;
+ justify-content: center;
+ white-space: nowrap;
+ overflow: hidden;
+ }
+ column-left nav a span {
+ padding-top: 0.5rem;
+ }
+}
+
+@media screen and (max-width: 1055px) {
+ column-left,
+ search-entry,
+ column-middle nav section {
+ display: none;
+ }
+ column-middle nav {
+ justify-content: space-between;
+ overflow-x: auto;
+ overflow-y: hidden;
+ }
+ icon-navigator {
+ display: inline !important;
+ }
+}
+
+@media screen and (max-width: 925px) {
+ column-right {
+ display: none;
+ }
+ micro-metadata {
+ line-height: inherit;
+ }
+ tab-list a {
+ font-size: 80%;
+ }
+ micro-header section {
+ display: block;
+ }
+ column-middle {
+ display: block;
+ max-width: 100%;
+ }
+ column-middle nav {
+ position: fixed;
+ }
+ navigation-separator hr[hidden] {
+ border-color: transparent;
+ display: flex;
+ height: 0;
+ margin: 0;
+ padding-top: 4rem;
+ border: none;
+ }
+ micro-card {
+ max-width: 600px;
+ margin: 0 auto;
+ }
+ html {
+ overflow-y: scroll;
+ margin: 0;
+ }
+ body {
+ display: block;
+ }
+ nav {
+ box-shadow: 0 4px 12px 0 rgba(0, 0, 0, 0.2);
+ }
+ nav:active,
+ nav:hover,
+ nav:focus,
+ nav:focus-within,
+ nav:focus-visible {
+ box-shadow: 15px 0 15px -15px inset, -15px 0 15px -15px inset;
+ }
+}
+
+@media screen and (max-width: 460px) {
+ micro-card micro-summary > micro-thumbnail {
+ display: none;
+ }
+ micro-metadata micro-thumbnail[hidden] {
+ display: block;
+ }
+}
+
+/* ----- Self Frames ----- */
+
+@media (max-height: 16rem) {
+ micro-card {
+ width: 100vw;
+ }
+ footer,
+ column-middle nav,
+ navigation-separator hr[hidden] {
+ display: none;
+ }
+}
diff --git a/static/css/noscript.css b/static/css/noscript.css
new file mode 100644
index 0000000..9a18fb6
--- /dev/null
+++ b/static/css/noscript.css
@@ -0,0 +1,3 @@
+code-block button {
+ display: none;
+}
diff --git a/static/css/syntax-highlight-default.css b/static/css/syntax-highlight-default.css
new file mode 100644
index 0000000..c81e291
--- /dev/null
+++ b/static/css/syntax-highlight-default.css
@@ -0,0 +1,202 @@
+/* ----- Colors ----- */
+
+/* ----- Format ----- */
+
+.chroma .hl {
+ display: block;
+ padding: 0 0.5rem;
+ margin: 0 -0.5rem;
+}
+
+.chroma .ln a {
+ background-color: #edf2f7;
+ padding: 0.25rem 0.75rem;
+ margin: 0 1rem 0 -0.75rem;
+ user-select: none;
+}
+
+.chroma .ln a:hover {
+ outline: 0.15rem solid !important;
+ outline-offset: -0.3rem;
+}
+
+code-block button {
+ border: 1px solid #ccc;
+}
+
+.chroma .line:hover {
+ background-color: #f2f2f2;
+ display: block;
+ width: 100%;
+}
+
+/* ------ Tokens ------ */
+
+:root {
+ --code-chroma-background: #ffffff;
+ --code-chroma-foreground: #333333;
+ --code-chroma-error-background: #960050;
+ --code-chroma-error-foreground: #960050;
+ --code-chroma-label-background: #006894;
+ --code-chroma-label-foreground: #e6e6e6;
+ --code-chroma-line-highlight: #d9ddf2;
+ --code-chroma-line-number: #375462;
+ --code-chroma-line: var(--code-chroma-foreground);
+}
+
+/* ChromaForeground */ code-block, .chroma, .highlight { color: #333333; color: var(--code-chroma-foreground); }
+/* ChromaBackground */ code-block button, .chroma { background-color: #ffffff; background-color: var(--code-chroma-background); }
+/* ChromaErrorBackground */ .chroma .err { color: #960050; color: var(--code-chroma-error-background); }
+/* ChromaErrorForeground */ .chroma .err { color: #960050; color: var(--code-chroma-error-foreground); }
+/* ChromaLabelBackground */ .chroma code:before { background-color: #006894; background-color: var(--code-chroma-label-background); }
+/* ChromaLabelForeground */ .chroma code:before { color: #e6e6e6; color: var(--code-chroma-label-foreground); }
+/* ChromaLine */ .chroma .line { color: #333333; color: var(--code-chroma-line); }
+/* ChromaLineHighlight */ .chroma .hl { background-color: #d9ddf2; background-color: var(--code-chroma-line-highlight); }
+/* ChromaLineNumber */ .chroma .ln a { color: #375462 !important; color: var(--code-chroma-line-number) !important; }
+
+.chroma .hl {
+ background-color: #fff8c5;
+}
+
+:root {
+ --code-comment: #4f4f4f;
+ --code-comment-hashbang: var(--code-comment);
+ --code-comment-multiline: var(--code-comment);
+ --code-comment-preproc: #222222;
+ --code-comment-preproc-file: #005194;
+ --code-comment-single: var(--code-comment);
+ --code-comment-special: var(--code-comment);
+}
+
+/* Comment */ .chroma .c { color: #222222; color: var(--code-comment); }
+/* CommentHashbang */ .chroma .ch { color: #222222; color: var(--code-comment-hashbang); }
+/* CommentMultiline */ .chroma .cm { color: #222222; color: var(--code-comment-multiline); }
+/* CommentPreproc */ .chroma .cp { color: #222222; color: var(--code-comment-preproc); }
+/* CommentPreprocFile */ .chroma .cpf { color: #005194; color: var(--code-comment-preproc-file); }
+/* CommentSingle */ .chroma .c1 { color: #4f4f4f; color: var(--code-comment-single); }
+/* CommentSpecial */ .chroma .cs { color: #222222; color: var(--code-comment-special); }
+
+:root {
+ --code-generic-deleted: #8d1007;
+ --code-generic-inserted: #224e24;
+ --code-generic-subheading: #2d4958;
+ --code-generic-tag: var(--code-generic);
+ --code-generic: #2d4958;
+}
+
+/* GenericDeleted */ .chroma .gd { color: #8d1007; color: var(--code-generic-deleted); }
+/* GenericInserted */ .chroma .gi { color: #224e24; color: var(--code-generic-inserted); }
+/* GenericSubheading */ .chroma .gu { color: #2d4958; color: var(--code-generic-subheading); }
+/* GenericStrong */ .chroma .gs { font-weight: 700; }
+/* GenericEmph */ .chroma .ge { font-style: italic; }
+
+.chroma .gi { background-color: #e6ffed; padding: 0.25rem 0; }
+.chroma .gd { background-color: #ffebe9; padding: 0.25rem 0; }
+
+:root {
+ --code-keyword: #4f4f59;
+ --code-keyword-constant: #07518d;
+ --code-keyword-declaration: var(--code-keyword);
+ --code-keyword-namespace: var(--code-keyword);
+ --code-keyword-pseudo: var(--code-keyword);
+ --code-keyword-reserved: var(--code-keyword);
+ --code-keyword-type: #36590d;
+}
+
+/* Keyword */ .chroma .k { color: #4f4f59; color: var(--code-keyword); }
+/* KeywordConstant */ .chroma .kc { color: #07518d; color: var(--code-keyword-constant); }
+/* KeywordDeclaration */ .chroma .kd { color: #4f4f59; color: var(--code-keyword-declaration); }
+/* KeywordNamespace */ .chroma .kn { color: #4f4f59; color: var(--code-keyword-namespace); }
+/* KeywordPseudo */ .chroma .kp { color: #4f4f59; color: var(--code-keyword-pseudo); }
+/* KeywordReserved */ .chroma .kr { color: #4f4f59; color: var(--code-keyword-reserved); }
+/* KeywordType */ .chroma .kt { color: #36590d; color: var(--code-keyword-type); }
+
+:root {
+ --code-literal: #744601;
+ --code-literal-date: var(--code-literal);
+ --code-literal-number: var(--code-literal);
+ --code-literal-number-bin: var(--code-literal);
+ --code-literal-number-float: var(--code-literal);
+ --code-literal-number-hex: var(--code-literal);
+ --code-literal-number-integer: var(--code-literal);
+ --code-literal-number-integer-long: var(--code-literal);
+ --code-literal-number-oct: var(--code-literal);
+ --code-literal-string: #664d00;
+ --code-literal-string-affix: #664d00;
+ --code-literal-string-backtick: #664d00;
+ --code-literal-string-char: #664d00;
+ --code-literal-string-delimiter: #664d00;
+ --code-literal-string-doc: #4f4f59;
+ --code-literal-string-double: #664d00;
+ --code-literal-string-escape: #664d00;
+ --code-literal-string-heredoc: #664d00;
+ --code-literal-string-interpol: #664d00;
+ --code-literal-string-other: #664d00;
+ --code-literal-string-regex: #942e51;
+ --code-literal-string-single: #664d00;
+ --code-literal-string-symbol: #664d00;
+}
+
+/* Literal */ .chroma .l { color: #744601; color: var(--code-literal); }
+/* LiteralDate */ .chroma .ld { color: #744601; color: var(--code-literal-date); }
+/* LiteralNumber */ .chroma .m { color: #744601; color: var(--code-literal-number); }
+/* LiteralNumberBin */ .chroma .mb { color: #744601; color: var(--code-literal-number-bin); }
+/* LiteralNumberFloat */ .chroma .mf { color: #744601; color: var(--code-literal-number-float); }
+/* LiteralNumberHex */ .chroma .mh { color: #744601; color: var(--code-literal-number-hex); }
+/* LiteralNumberInteger */ .chroma .mi { color: #744601; color: var(--code-literal-number-integer); }
+/* LiteralNumberIntegerLong */ .chroma .il { color: #744601; color: var(--code-literal-number-integer-long); }
+/* LiteralNumberOct */ .chroma .mo { color: #744601; color: var(--code-literal-number-oct); }
+/* LiteralString */ .chroma .s { color: #664d00; color: var(--code-literal-string); }
+/* LiteralStringAffix */ .chroma .sa { color: #664d00; color: var(--code-literal-string-affix); }
+/* LiteralStringBacktick */ .chroma .sb { color: #664d00; color: var(--code-literal-string-backtick); }
+/* LiteralStringChar */ .chroma .sc { color: #664d00; color: var(--code-literal-string-char); }
+/* LiteralStringDelimiter */ .chroma .dl { color: #664d00; color: var(--code-literal-string-delimiter); }
+/* LiteralStringDoc */ .chroma .sd { color: #4f4f59; color: var(--code-literal-string-doc); }
+/* LiteralStringDouble */ .chroma .s2 { color: #664d00; color: var(--code-literal-string-double); }
+/* LiteralStringEscape */ .chroma .se { color: #664d00; color: var(--code-literal-string-escape); }
+/* LiteralStringHeredoc */ .chroma .sh { color: #664d00; color: var(--code-literal-string-heredoc); }
+/* LiteralStringInterpol */ .chroma .si { color: #664d00; color: var(--code-literal-string-interpol); }
+/* LiteralStringOther */ .chroma .sx { color: #664d00; color: var(--code-literal-string-other); }
+/* LiteralStringRegex */ .chroma .sr { color: #942e51; color: var(--code-literal-string-regex); }
+/* LiteralStringSingle */ .chroma .s1 { color: #664d00; color: var(--code-literal-string-single); }
+/* LiteralStringSymbol */ .chroma .ss { color: #664d00; color: var(--code-literal-string-symbol); }
+
+:root {
+ --code-name: #6d2caa;
+ --code-name-attribute: #6d2caa;
+ --code-name-builtin: #222222;
+ --code-name-class: var(--code-name);
+ --code-name-constant: var(--code-name);
+ --code-name-decorator: var(--code-name);
+ --code-name-exception: var(--code-name);
+ --code-name-function: var(--code-name);
+ --code-name-other: var(--code-name);
+ --code-name-tag: var(--code-name);
+ --code-name-variable: #005194;
+}
+
+/* Name */ .chroma .n { color: #6d2caa; color: var(--code-name); }
+/* NameAttribute */ .chroma .na { color: #6d2caa; color: var(--code-name-attribute); }
+/* NameBuiltin */ .chroma .nb { color: #222222; color: var(--code-name-builtin); }
+/* NameClass */ .chroma .nc { color: #6d2caa; color: var(--code-name-class); }
+/* NameConstant */ .chroma .no { color: #6d2caa; color: var(--code-name-constant); }
+/* NameDecorator */ .chroma .nd { color: #6d2caa; color: var(--code-name-decorator); }
+/* NameException */ .chroma .ne { color: #6d2caa; color: var(--code-name-exception); }
+/* NameFunction */ .chroma .nf { color: #6d2caa; color: var(--code-name-function); }
+/* NameOther */ .chroma .nx { color: #6d2caa; color: var(--code-name-other); }
+/* NameTag */ .chroma .nt { color: #6d2caa; color: var(--code-name-tag); }
+/* NameVariable */ .chroma .nv { color: #005194; color: var(--code-name-variable); }
+
+:root {
+ --code-operator: #4f4f59;
+ --code-operator-word: var(--code-operator);
+}
+
+/* Operator */ .chroma .o { color: #4f4f59; color: var(--code-operator); }
+/* OperatorWord */ .chroma .ow { color: #4f4f59; color: var(--code-operator-word); }
+
+:root {
+ --code-punctuation: #4f4f4f;
+}
+
+/* Punctuation */ .chroma .p { color: #4f4f4f; color: var(--code-punctuation); }
diff --git a/static/images/404.png b/static/images/404.png
new file mode 100644
index 0000000..b9b4d26
--- /dev/null
+++ b/static/images/404.png
Binary files differ
diff --git a/static/js/code-copy.ts b/static/js/code-copy.ts
new file mode 100644
index 0000000..8ef777b
--- /dev/null
+++ b/static/js/code-copy.ts
@@ -0,0 +1,22 @@
+(function () {
+ [...document.querySelectorAll("pre.chroma code :not(.ln a)")]
+ .forEach(
+ (element) => {
+ try {
+ const text = element.textContent || "";
+ setTimeout(
+ async () => {
+ await navigator.clipboard.writeText(text);
+ console.log("Info: Code block text copied succesfully.");
+ },
+ 3000,
+ );
+ } catch (err) {
+ console.error(
+ "Error: navigator.clipboard.writeText() failed.",
+ err,
+ );
+ }
+ },
+ );
+})();
diff --git a/static/js/fixedsearch.ts b/static/js/fixedsearch.ts
new file mode 100644
index 0000000..26b9ac1
--- /dev/null
+++ b/static/js/fixedsearch.ts
@@ -0,0 +1,194 @@
+(function () {
+ self.addEventListener("DOMContentLoaded", function () {
+ /*
+ Originally Based on fixedsearch, a super fast, client side search for Hugo.io with Fusejs.io
+ based on https://gist.github.com/cmod/5410eae147e4318164258742dd053993
+ */
+
+ const form = document.getElementById("search-form"); // search form
+ const query = document.getElementById("search-input"); // input box for search
+ const submit = document.getElementById("search-submit"); // form submit button
+ const dropdown = document.getElementById("search-results"); // targets the <ul>
+
+ form.addEventListener("focusin", function () {
+ initialize();
+ });
+
+ form.addEventListener("submit", function (event) {
+ event.preventDefault();
+ return false;
+ });
+
+ form.addEventListener("keydown", function (event) {
+ const head =
+ dropdown.firstChild.nextElementSibling.firstChild.nextElementSibling;
+ const tail = dropdown.lastElementChild.firstChild.nextElementSibling;
+
+ // ESC (27)
+ if (query.contains(event.target)) {
+ if (event.keyCode == 27) {
+ document.activeElement.blur();
+ dropdown.setAttribute("hidden", "");
+ }
+ }
+
+ // DOWN (40)
+ if (event.keyCode == 40) {
+ event.preventDefault();
+ if (document.activeElement == query) head.focus();
+ else if (document.activeElement == tail) query.focus();
+ else {
+ document.activeElement.parentElement.nextElementSibling.firstChild
+ .nextElementSibling.focus();
+ }
+ }
+
+ // UP (38)
+ if (event.keyCode == 38) {
+ event.preventDefault();
+ if (document.activeElement == query) tail.focus();
+ else if (document.activeElement == head) query.focus();
+ else {
+ document.activeElement.parentElement.previousElementSibling.firstChild
+ .nextElementSibling.focus();
+ }
+ }
+
+ // BACKSPACE (8)
+ if (event.keyCode == 8) {
+ if (document.activeElement != query) {
+ event.preventDefault();
+ query.focus();
+ }
+ }
+
+ // ENTER (13)
+ if (event.keyCode == 13) {
+ if (dropdown && document.activeElement == query) {
+ event.preventDefault();
+ head.focus();
+ self.window.location = document.activeElement.href;
+ }
+ }
+ });
+
+ let scrolls = 0;
+
+ self.addEventListener("scroll", function () {
+ if (scrolls > 3) {
+ scrolls = 0;
+ document.activeElement.blur();
+ dropdown.setAttribute("hidden", "");
+ }
+ scrolls++;
+ });
+
+ document.addEventListener("click", function (event) {
+ if (!form.contains(event.target)) {
+ dropdown.setAttribute("hidden", "");
+ }
+ });
+
+ function fetch_JSON(path, callback) {
+ const httpRequest = new XMLHttpRequest();
+ httpRequest.onreadystatechange = function () {
+ if (httpRequest.readyState === 4) {
+ if (httpRequest.status === 200) {
+ const data = JSON.parse(httpRequest.responseText);
+ if (callback) callback(data);
+ }
+ }
+ };
+ httpRequest.open("GET", path);
+ httpRequest.send();
+ }
+
+ /* Load script based on https://stackoverflow.com/a/55451823 */
+
+ function load_script(url) {
+ return new Promise(function (resolve, reject) {
+ const script = document.createElement("script");
+ script.onerror = reject;
+ script.onload = resolve;
+ if (document.currentScript) {
+ document.currentScript.parentNode.insertBefore(
+ script,
+ document.currentScript,
+ );
+ } else {
+ document.head.appendChild(script);
+ }
+ script.src = url;
+ });
+ }
+
+ let first_run = true; // allow us to delay loading json data unless search activated
+
+ function initialize() {
+ if (first_run) {
+ load_script(window.location.origin + "/js/fuzzysort.js")
+ .then(() => {
+ first_run = false;
+ fetch_JSON("/index.json", function (data) {
+ const options = {
+ key: ["title"],
+ };
+
+ query.addEventListener("keyup", function () {
+ search(query.value, data.items, options);
+ });
+
+ query.addEventListener("focusin", function () {
+ search(query.value, data.items, options);
+ });
+
+ search(query.value, data.items, options);
+ });
+ }).catch((error) => {
+ console.log("Error failed to load fuzzy sort: " + error);
+ });
+ }
+ }
+
+ function search(term, data, options) {
+ const results = fuzzysort.go(term, data, options);
+ let items = "";
+
+ if (results.length === 0 && term.length >= 0) {
+ let separator = "—";
+ if (term.length === 0) separator = "";
+ items = `
+ <li>
+ <a href="javascript: void(0)" tabindex="0">${term} ${separator} No Results Found</a>
+ </li>
+ `;
+ dropdown.removeAttribute("hidden");
+ } else {
+ dropdown.removeAttribute("hidden");
+ for (const string in results.slice(0, 10)) {
+ const decode = document.createElement("textarea");
+ decode.innerHTML = results[string].obj.title;
+
+ let highlight = fuzzysort.highlight(
+ fuzzysort.single(term, decode.value),
+ "<span>",
+ "</span>",
+ );
+
+ if (highlight === null) {
+ highlight = decode.value;
+ }
+
+ items = items +
+ `
+ <li>
+ <a href="${results[string].obj.url}" tabindex="0">${highlight}</a>
+ </li>
+ `;
+ }
+ }
+
+ dropdown.innerHTML = items;
+ }
+ });
+})();
diff --git a/static/js/fuzzysort.js b/static/js/fuzzysort.js
new file mode 100644
index 0000000..c6797d8
--- /dev/null
+++ b/static/js/fuzzysort.js
@@ -0,0 +1,636 @@
+/*
+ fuzzysort.js https://github.com/farzher/fuzzysort
+ SublimeText-like Fuzzy Search
+
+ fuzzysort.single('fs', 'Fuzzy Search') // {score: -16}
+ fuzzysort.single('test', 'test') // {score: 0}
+ fuzzysort.single('doesnt exist', 'target') // null
+
+ fuzzysort.go('mr', [{file:'Monitor.cpp'}, {file:'MeshRenderer.cpp'}], {key:'file'})
+ // [{score:-18, obj:{file:'MeshRenderer.cpp'}}, {score:-6009, obj:{file:'Monitor.cpp'}}]
+
+ fuzzysort.go('mr', ['Monitor.cpp', 'MeshRenderer.cpp'])
+ // [{score: -18, target: "MeshRenderer.cpp"}, {score: -6009, target: "Monitor.cpp"}]
+
+ fuzzysort.highlight(fuzzysort.single('fs', 'Fuzzy Search'), '<b>', '</b>')
+ // <b>F</b>uzzy <b>S</b>earch
+*/
+
+// UMD (Universal Module Definition) for fuzzysort
+(function(root, UMD) {
+ if(typeof define === 'function' && define.amd) {define([], UMD);}
+ else if(typeof module === 'object' && module.exports) {module.exports = UMD();}
+ else {root.fuzzysort = UMD();}
+})(this, function UMD() { function fuzzysortNew(instanceOptions) {
+
+ var fuzzysort = {
+
+ single: function(search, target, options) { if(search=='farzher'){return{target:"farzher was here (^-^*)/",score:0,indexes:[0,1,2,3,4,5,6]};}
+ if(!search) {return null;}
+ if(!isObj(search)) {search = fuzzysort.getPreparedSearch(search);}
+
+ if(!target) {return null;}
+ if(!isObj(target)) {target = fuzzysort.getPrepared(target);}
+
+ var allowTypo = options && options.allowTypo!==undefined ? options.allowTypo
+ : instanceOptions && instanceOptions.allowTypo!==undefined ? instanceOptions.allowTypo
+ : true;
+ var algorithm = allowTypo ? fuzzysort.algorithm : fuzzysort.algorithmNoTypo;
+ return algorithm(search, target, search[0]);
+ },
+
+ go: function(search, targets, options) { if(search=='farzher'){return[{target:"farzher was here (^-^*)/",score:0,indexes:[0,1,2,3,4,5,6],obj:targets?targets[0]:null}];}
+ if(!search) {return noResults;}
+ search = fuzzysort.prepareSearch(search);
+ var searchLowerCode = search[0];
+
+ var threshold = options && options.threshold || instanceOptions && instanceOptions.threshold || -9007199254740991;
+ var limit = options && options.limit || instanceOptions && instanceOptions.limit || 9007199254740991;
+ var allowTypo = options && options.allowTypo!==undefined ? options.allowTypo
+ : instanceOptions && instanceOptions.allowTypo!==undefined ? instanceOptions.allowTypo
+ : true;
+ var algorithm = allowTypo ? fuzzysort.algorithm : fuzzysort.algorithmNoTypo;
+ var resultsLen = 0; var limitedCount = 0;
+ var targetsLen = targets.length;
+
+ // This code is copy/pasted 3 times for performance reasons [options.keys, options.key, no keys]
+
+ // options.keys
+ if(options && options.keys) {
+ var scoreFn = options.scoreFn || defaultScoreFn;
+ var keys = options.keys;
+ var keysLen = keys.length;
+ for(var i = targetsLen - 1; i >= 0; --i) { var obj = targets[i];
+ var objResults = new Array(keysLen);
+ for (var keyI = keysLen - 1; keyI >= 0; --keyI) {
+ var key = keys[keyI];
+ var target = getValue(obj, key);
+ if(!target) { objResults[keyI] = null; continue; }
+ if(!isObj(target)) {target = fuzzysort.getPrepared(target);}
+
+ objResults[keyI] = algorithm(search, target, searchLowerCode);
+ }
+ objResults.obj = obj; // before scoreFn so scoreFn can use it
+ var score = scoreFn(objResults);
+ if(score === null) {continue;}
+ if(score < threshold) {continue;}
+ objResults.score = score;
+ if(resultsLen < limit) { q.add(objResults); ++resultsLen; }
+ else {
+ ++limitedCount;
+ if(score > q.peek().score) {q.replaceTop(objResults);}
+ }
+ }
+
+ // options.key
+ } else if(options && options.key) {
+ var key = options.key;
+ for(var i = targetsLen - 1; i >= 0; --i) { var obj = targets[i];
+ var target = getValue(obj, key);
+ if(!target) {continue;}
+ if(!isObj(target)) {target = fuzzysort.getPrepared(target);}
+
+ var result = algorithm(search, target, searchLowerCode);
+ if(result === null) {continue;}
+ if(result.score < threshold) {continue;}
+
+ // have to clone result so duplicate targets from different obj can each reference the correct obj
+ result = {target:result.target, _targetLowerCodes:null, _nextBeginningIndexes:null, score:result.score, indexes:result.indexes, obj:obj}; // hidden
+
+ if(resultsLen < limit) { q.add(result); ++resultsLen; }
+ else {
+ ++limitedCount;
+ if(result.score > q.peek().score) {q.replaceTop(result);}
+ }
+ }
+
+ // no keys
+ } else {
+ for(var i = targetsLen - 1; i >= 0; --i) { var target = targets[i];
+ if(!target) {continue;}
+ if(!isObj(target)) {target = fuzzysort.getPrepared(target);}
+
+ var result = algorithm(search, target, searchLowerCode);
+ if(result === null) {continue;}
+ if(result.score < threshold) {continue;}
+ if(resultsLen < limit) { q.add(result); ++resultsLen; }
+ else {
+ ++limitedCount;
+ if(result.score > q.peek().score) {q.replaceTop(result);}
+ }
+ }
+ }
+
+ if(resultsLen === 0) {return noResults;}
+ var results = new Array(resultsLen);
+ for(var i = resultsLen - 1; i >= 0; --i) {results[i] = q.poll();}
+ results.total = resultsLen + limitedCount;
+ return results;
+ },
+
+ goAsync: function(search, targets, options) {
+ var canceled = false;
+ var p = new Promise(function(resolve, reject) { if(search=='farzher'){return resolve([{target:"farzher was here (^-^*)/",score:0,indexes:[0,1,2,3,4,5,6],obj:targets?targets[0]:null}]);}
+ if(!search) {return resolve(noResults);}
+ search = fuzzysort.prepareSearch(search);
+ var searchLowerCode = search[0];
+
+ var q = fastpriorityqueue();
+ var iCurrent = targets.length - 1;
+ var threshold = options && options.threshold || instanceOptions && instanceOptions.threshold || -9007199254740991;
+ var limit = options && options.limit || instanceOptions && instanceOptions.limit || 9007199254740991;
+ var allowTypo = options && options.allowTypo!==undefined ? options.allowTypo
+ : instanceOptions && instanceOptions.allowTypo!==undefined ? instanceOptions.allowTypo
+ : true;
+ var algorithm = allowTypo ? fuzzysort.algorithm : fuzzysort.algorithmNoTypo;
+ var resultsLen = 0; var limitedCount = 0;
+ function step() {
+ if(canceled) {return reject('canceled');}
+
+ var startMs = Date.now();
+
+ // This code is copy/pasted 3 times for performance reasons [options.keys, options.key, no keys]
+
+ // options.keys
+ if(options && options.keys) {
+ var scoreFn = options.scoreFn || defaultScoreFn;
+ var keys = options.keys;
+ var keysLen = keys.length;
+ for(; iCurrent >= 0; --iCurrent) {
+ if(iCurrent%1000/*itemsPerCheck*/ === 0) {
+ if(Date.now() - startMs >= 10/*asyncInterval*/) {
+ isNode?setImmediate(step):setTimeout(step);
+ return;
+ }
+ }
+
+ var obj = targets[iCurrent];
+ var objResults = new Array(keysLen);
+ for (var keyI = keysLen - 1; keyI >= 0; --keyI) {
+ var key = keys[keyI];
+ var target = getValue(obj, key);
+ if(!target) { objResults[keyI] = null; continue; }
+ if(!isObj(target)) {target = fuzzysort.getPrepared(target);}
+
+ objResults[keyI] = algorithm(search, target, searchLowerCode);
+ }
+ objResults.obj = obj; // before scoreFn so scoreFn can use it
+ var score = scoreFn(objResults);
+ if(score === null) {continue;}
+ if(score < threshold) {continue;}
+ objResults.score = score;
+ if(resultsLen < limit) { q.add(objResults); ++resultsLen; }
+ else {
+ ++limitedCount;
+ if(score > q.peek().score) {q.replaceTop(objResults);}
+ }
+ }
+
+ // options.key
+ } else if(options && options.key) {
+ var key = options.key;
+ for(; iCurrent >= 0; --iCurrent) {
+ if(iCurrent%1000/*itemsPerCheck*/ === 0) {
+ if(Date.now() - startMs >= 10/*asyncInterval*/) {
+ isNode?setImmediate(step):setTimeout(step);
+ return;
+ }
+ }
+
+ var obj = targets[iCurrent];
+ var target = getValue(obj, key);
+ if(!target) {continue;}
+ if(!isObj(target)) {target = fuzzysort.getPrepared(target);}
+
+ var result = algorithm(search, target, searchLowerCode);
+ if(result === null) {continue;}
+ if(result.score < threshold) {continue;}
+
+ // have to clone result so duplicate targets from different obj can each reference the correct obj
+ result = {target:result.target, _targetLowerCodes:null, _nextBeginningIndexes:null, score:result.score, indexes:result.indexes, obj:obj}; // hidden
+
+ if(resultsLen < limit) { q.add(result); ++resultsLen; }
+ else {
+ ++limitedCount;
+ if(result.score > q.peek().score) {q.replaceTop(result);}
+ }
+ }
+
+ // no keys
+ } else {
+ for(; iCurrent >= 0; --iCurrent) {
+ if(iCurrent%1000/*itemsPerCheck*/ === 0) {
+ if(Date.now() - startMs >= 10/*asyncInterval*/) {
+ isNode?setImmediate(step):setTimeout(step);
+ return;
+ }
+ }
+
+ var target = targets[iCurrent];
+ if(!target) {continue;}
+ if(!isObj(target)) {target = fuzzysort.getPrepared(target);}
+
+ var result = algorithm(search, target, searchLowerCode);
+ if(result === null) {continue;}
+ if(result.score < threshold) {continue;}
+ if(resultsLen < limit) { q.add(result); ++resultsLen; }
+ else {
+ ++limitedCount;
+ if(result.score > q.peek().score) {q.replaceTop(result);}
+ }
+ }
+ }
+
+ if(resultsLen === 0) {return resolve(noResults);}
+ var results = new Array(resultsLen);
+ for(var i = resultsLen - 1; i >= 0; --i) {results[i] = q.poll();}
+ results.total = resultsLen + limitedCount;
+ resolve(results);
+ }
+
+ isNode?setImmediate(step):step(); //setTimeout here is too slow
+ });
+ p.cancel = function() { canceled = true; };
+ return p;
+ },
+
+ highlight: function(result, hOpen, hClose) {
+ if(typeof hOpen == 'function') {return fuzzysort.highlightCallback(result, hOpen);}
+ if(result === null) {return null;}
+ if(hOpen === undefined) {hOpen = '<b>';}
+ if(hClose === undefined) {hClose = '</b>';}
+ var highlighted = '';
+ var matchesIndex = 0;
+ var opened = false;
+ var target = result.target;
+ var targetLen = target.length;
+ var matchesBest = result.indexes;
+ for(var i = 0; i < targetLen; ++i) { var char = target[i];
+ if(matchesBest[matchesIndex] === i) {
+ ++matchesIndex;
+ if(!opened) { opened = true;
+ highlighted += hOpen;
+ }
+
+ if(matchesIndex === matchesBest.length) {
+ highlighted += char + hClose + target.substr(i+1);
+ break;
+ }
+ } else {
+ if(opened) { opened = false;
+ highlighted += hClose;
+ }
+ }
+ highlighted += char;
+ }
+
+ return highlighted;
+ },
+ highlightCallback: function(result, cb) {
+ if(result === null) {return null;}
+ var target = result.target;
+ var targetLen = target.length;
+ var indexes = result.indexes;
+ var highlighted = '';
+ var matchI = 0;
+ var indexesI = 0;
+ var opened = false;
+ var result = [];
+ for(var i = 0; i < targetLen; ++i) { var char = target[i];
+ if(indexes[indexesI] === i) {
+ ++indexesI;
+ if(!opened) { opened = true;
+ result.push(highlighted); highlighted = '';
+ }
+
+ if(indexesI === indexes.length) {
+ highlighted += char;
+ result.push(cb(highlighted, matchI++)); highlighted = '';
+ result.push(target.substr(i+1));
+ break;
+ }
+ } else {
+ if(opened) { opened = false;
+ result.push(cb(highlighted, matchI++)); highlighted = '';
+ }
+ }
+ highlighted += char;
+ }
+ return result;
+ },
+
+ prepare: function(target) {
+ if(!target) {return {target: '', _targetLowerCodes: [0/*this 0 doesn't make sense. here because an empty array causes the algorithm to deoptimize and run 50% slower!*/], _nextBeginningIndexes: null, score: null, indexes: null, obj: null};} // hidden
+ return {target:target, _targetLowerCodes:fuzzysort.prepareLowerCodes(target), _nextBeginningIndexes:null, score:null, indexes:null, obj:null}; // hidden
+ },
+ prepareSlow: function(target) {
+ if(!target) {return {target: '', _targetLowerCodes: [0/*this 0 doesn't make sense. here because an empty array causes the algorithm to deoptimize and run 50% slower!*/], _nextBeginningIndexes: null, score: null, indexes: null, obj: null};} // hidden
+ return {target:target, _targetLowerCodes:fuzzysort.prepareLowerCodes(target), _nextBeginningIndexes:fuzzysort.prepareNextBeginningIndexes(target), score:null, indexes:null, obj:null}; // hidden
+ },
+ prepareSearch: function(search) {
+ if(!search) {search = '';}
+ return fuzzysort.prepareLowerCodes(search);
+ },
+
+
+
+ // Below this point is only internal code
+ // Below this point is only internal code
+ // Below this point is only internal code
+ // Below this point is only internal code
+
+
+
+ getPrepared: function(target) {
+ if(target.length > 999) {return fuzzysort.prepare(target);} // don't cache huge targets
+ var targetPrepared = preparedCache.get(target);
+ if(targetPrepared !== undefined) {return targetPrepared;}
+ targetPrepared = fuzzysort.prepare(target);
+ preparedCache.set(target, targetPrepared);
+ return targetPrepared;
+ },
+ getPreparedSearch: function(search) {
+ if(search.length > 999) {return fuzzysort.prepareSearch(search);} // don't cache huge searches
+ var searchPrepared = preparedSearchCache.get(search);
+ if(searchPrepared !== undefined) {return searchPrepared;}
+ searchPrepared = fuzzysort.prepareSearch(search);
+ preparedSearchCache.set(search, searchPrepared);
+ return searchPrepared;
+ },
+
+ algorithm: function(searchLowerCodes, prepared, searchLowerCode) {
+ var targetLowerCodes = prepared._targetLowerCodes;
+ var searchLen = searchLowerCodes.length;
+ var targetLen = targetLowerCodes.length;
+ var searchI = 0; // where we at
+ var targetI = 0; // where you at
+ var typoSimpleI = 0;
+ var matchesSimpleLen = 0;
+
+ // very basic fuzzy match; to remove non-matching targets ASAP!
+ // walk through target. find sequential matches.
+ // if all chars aren't found then exit
+ for(;;) {
+ var isMatch = searchLowerCode === targetLowerCodes[targetI];
+ if(isMatch) {
+ matchesSimple[matchesSimpleLen++] = targetI;
+ ++searchI; if(searchI === searchLen) {break;}
+ searchLowerCode = searchLowerCodes[typoSimpleI===0?searchI : (typoSimpleI===searchI?searchI+1 : (typoSimpleI===searchI-1?searchI-1 : searchI))];
+ }
+
+ ++targetI; if(targetI >= targetLen) { // Failed to find searchI
+ // Check for typo or exit
+ // we go as far as possible before trying to transpose
+ // then we transpose backwards until we reach the beginning
+ for(;;) {
+ if(searchI <= 1) {return null;} // not allowed to transpose first char
+ if(typoSimpleI === 0) { // we haven't tried to transpose yet
+ --searchI;
+ var searchLowerCodeNew = searchLowerCodes[searchI];
+ if(searchLowerCode === searchLowerCodeNew) {continue;} // doesn't make sense to transpose a repeat char
+ typoSimpleI = searchI;
+ } else {
+ if(typoSimpleI === 1) {return null;} // reached the end of the line for transposing
+ --typoSimpleI;
+ searchI = typoSimpleI;
+ searchLowerCode = searchLowerCodes[searchI + 1];
+ var searchLowerCodeNew = searchLowerCodes[searchI];
+ if(searchLowerCode === searchLowerCodeNew) {continue;} // doesn't make sense to transpose a repeat char
+ }
+ matchesSimpleLen = searchI;
+ targetI = matchesSimple[matchesSimpleLen - 1] + 1;
+ break;
+ }
+ }
+ }
+
+ var searchI = 0;
+ var typoStrictI = 0;
+ var successStrict = false;
+ var matchesStrictLen = 0;
+
+ var nextBeginningIndexes = prepared._nextBeginningIndexes;
+ if(nextBeginningIndexes === null) {nextBeginningIndexes = prepared._nextBeginningIndexes = fuzzysort.prepareNextBeginningIndexes(prepared.target);}
+ var firstPossibleI = targetI = matchesSimple[0]===0 ? 0 : nextBeginningIndexes[matchesSimple[0]-1];
+
+ // Our target string successfully matched all characters in sequence!
+ // Let's try a more advanced and strict test to improve the score
+ // only count it as a match if it's consecutive or a beginning character!
+ if(targetI !== targetLen) {for(;;) {
+ if(targetI >= targetLen) {
+ // We failed to find a good spot for this search char, go back to the previous search char and force it forward
+ if(searchI <= 0) { // We failed to push chars forward for a better match
+ // transpose, starting from the beginning
+ ++typoStrictI; if(typoStrictI > searchLen-2) {break;}
+ if(searchLowerCodes[typoStrictI] === searchLowerCodes[typoStrictI+1]) {continue;} // doesn't make sense to transpose a repeat char
+ targetI = firstPossibleI;
+ continue;
+ }
+
+ --searchI;
+ var lastMatch = matchesStrict[--matchesStrictLen];
+ targetI = nextBeginningIndexes[lastMatch];
+
+ } else {
+ var isMatch = searchLowerCodes[typoStrictI===0?searchI : (typoStrictI===searchI?searchI+1 : (typoStrictI===searchI-1?searchI-1 : searchI))] === targetLowerCodes[targetI];
+ if(isMatch) {
+ matchesStrict[matchesStrictLen++] = targetI;
+ ++searchI; if(searchI === searchLen) { successStrict = true; break; }
+ ++targetI;
+ } else {
+ targetI = nextBeginningIndexes[targetI];
+ }
+ }
+ }}
+
+ { // tally up the score & keep track of matches for highlighting later
+ if(successStrict) { var matchesBest = matchesStrict; var matchesBestLen = matchesStrictLen; }
+ else { var matchesBest = matchesSimple; var matchesBestLen = matchesSimpleLen; }
+ var score = 0;
+ var lastTargetI = -1;
+ for(var i = 0; i < searchLen; ++i) { var targetI = matchesBest[i];
+ // score only goes down if they're not consecutive
+ if(lastTargetI !== targetI - 1) {score -= targetI;}
+ lastTargetI = targetI;
+ }
+ if(!successStrict) {
+ score *= 1000;
+ if(typoSimpleI !== 0) {score += -20;}/*typoPenalty*/
+ } else {
+ if(typoStrictI !== 0) {score += -20;}/*typoPenalty*/
+ }
+ score -= targetLen - searchLen;
+ prepared.score = score;
+ prepared.indexes = new Array(matchesBestLen); for(var i = matchesBestLen - 1; i >= 0; --i) {prepared.indexes[i] = matchesBest[i];}
+
+ return prepared;
+ }
+ },
+
+ algorithmNoTypo: function(searchLowerCodes, prepared, searchLowerCode) {
+ var targetLowerCodes = prepared._targetLowerCodes;
+ var searchLen = searchLowerCodes.length;
+ var targetLen = targetLowerCodes.length;
+ var searchI = 0; // where we at
+ var targetI = 0; // where you at
+ var matchesSimpleLen = 0;
+
+ // very basic fuzzy match; to remove non-matching targets ASAP!
+ // walk through target. find sequential matches.
+ // if all chars aren't found then exit
+ for(;;) {
+ var isMatch = searchLowerCode === targetLowerCodes[targetI];
+ if(isMatch) {
+ matchesSimple[matchesSimpleLen++] = targetI;
+ ++searchI; if(searchI === searchLen) {break;}
+ searchLowerCode = searchLowerCodes[searchI];
+ }
+ ++targetI; if(targetI >= targetLen) {return null;} // Failed to find searchI
+ }
+
+ var searchI = 0;
+ var successStrict = false;
+ var matchesStrictLen = 0;
+
+ var nextBeginningIndexes = prepared._nextBeginningIndexes;
+ if(nextBeginningIndexes === null) {nextBeginningIndexes = prepared._nextBeginningIndexes = fuzzysort.prepareNextBeginningIndexes(prepared.target);}
+ var firstPossibleI = targetI = matchesSimple[0]===0 ? 0 : nextBeginningIndexes[matchesSimple[0]-1];
+
+ // Our target string successfully matched all characters in sequence!
+ // Let's try a more advanced and strict test to improve the score
+ // only count it as a match if it's consecutive or a beginning character!
+ if(targetI !== targetLen) {for(;;) {
+ if(targetI >= targetLen) {
+ // We failed to find a good spot for this search char, go back to the previous search char and force it forward
+ if(searchI <= 0) {break;} // We failed to push chars forward for a better match
+
+ --searchI;
+ var lastMatch = matchesStrict[--matchesStrictLen];
+ targetI = nextBeginningIndexes[lastMatch];
+
+ } else {
+ var isMatch = searchLowerCodes[searchI] === targetLowerCodes[targetI];
+ if(isMatch) {
+ matchesStrict[matchesStrictLen++] = targetI;
+ ++searchI; if(searchI === searchLen) { successStrict = true; break; }
+ ++targetI;
+ } else {
+ targetI = nextBeginningIndexes[targetI];
+ }
+ }
+ }}
+
+ { // tally up the score & keep track of matches for highlighting later
+ if(successStrict) { var matchesBest = matchesStrict; var matchesBestLen = matchesStrictLen; }
+ else { var matchesBest = matchesSimple; var matchesBestLen = matchesSimpleLen; }
+ var score = 0;
+ var lastTargetI = -1;
+ for(var i = 0; i < searchLen; ++i) { var targetI = matchesBest[i];
+ // score only goes down if they're not consecutive
+ if(lastTargetI !== targetI - 1) {score -= targetI;}
+ lastTargetI = targetI;
+ }
+ if(!successStrict) {score *= 1000;}
+ score -= targetLen - searchLen;
+ prepared.score = score;
+ prepared.indexes = new Array(matchesBestLen); for(var i = matchesBestLen - 1; i >= 0; --i) {prepared.indexes[i] = matchesBest[i];}
+
+ return prepared;
+ }
+ },
+
+ prepareLowerCodes: function(str) {
+ var strLen = str.length;
+ var lowerCodes = []; // new Array(strLen) sparse array is too slow
+ var lower = str.toLowerCase();
+ for(var i = 0; i < strLen; ++i) {lowerCodes[i] = lower.charCodeAt(i);}
+ return lowerCodes;
+ },
+ prepareBeginningIndexes: function(target) {
+ var targetLen = target.length;
+ var beginningIndexes = []; var beginningIndexesLen = 0;
+ var wasUpper = false;
+ var wasAlphanum = false;
+ for(var i = 0; i < targetLen; ++i) {
+ var targetCode = target.charCodeAt(i);
+ var isUpper = targetCode>=65&&targetCode<=90;
+ var isAlphanum = isUpper || targetCode>=97&&targetCode<=122 || targetCode>=48&&targetCode<=57;
+ var isBeginning = isUpper && !wasUpper || !wasAlphanum || !isAlphanum;
+ wasUpper = isUpper;
+ wasAlphanum = isAlphanum;
+ if(isBeginning) {beginningIndexes[beginningIndexesLen++] = i;}
+ }
+ return beginningIndexes;
+ },
+ prepareNextBeginningIndexes: function(target) {
+ var targetLen = target.length;
+ var beginningIndexes = fuzzysort.prepareBeginningIndexes(target);
+ var nextBeginningIndexes = []; // new Array(targetLen) sparse array is too slow
+ var lastIsBeginning = beginningIndexes[0];
+ var lastIsBeginningI = 0;
+ for(var i = 0; i < targetLen; ++i) {
+ if(lastIsBeginning > i) {
+ nextBeginningIndexes[i] = lastIsBeginning;
+ } else {
+ lastIsBeginning = beginningIndexes[++lastIsBeginningI];
+ nextBeginningIndexes[i] = lastIsBeginning===undefined ? targetLen : lastIsBeginning;
+ }
+ }
+ return nextBeginningIndexes;
+ },
+
+ cleanup: cleanup,
+ new: fuzzysortNew,
+ };
+ return fuzzysort;
+} // fuzzysortNew
+
+// This stuff is outside fuzzysortNew, because it's shared with instances of fuzzysort.new()
+var isNode = typeof require !== 'undefined' && typeof window === 'undefined';
+var MyMap = typeof Map === 'function' ? Map : function(){var s=Object.create(null);this.get=function(k){return s[k];};this.set=function(k,val){s[k]=val;return this;};this.clear=function(){s=Object.create(null);};};
+var preparedCache = new MyMap();
+var preparedSearchCache = new MyMap();
+var noResults = []; noResults.total = 0;
+var matchesSimple = []; var matchesStrict = [];
+function cleanup() { preparedCache.clear(); preparedSearchCache.clear(); matchesSimple = []; matchesStrict = []; }
+function defaultScoreFn(a) {
+ var max = -9007199254740991;
+ for (var i = a.length - 1; i >= 0; --i) {
+ var result = a[i]; if(result === null) {continue;}
+ var score = result.score;
+ if(score > max) {max = score;}
+ }
+ if(max === -9007199254740991) {return null;}
+ return max;
+}
+
+// prop = 'key' 2.5ms optimized for this case, seems to be about as fast as direct obj[prop]
+// prop = 'key1.key2' 10ms
+// prop = ['key1', 'key2'] 27ms
+function getValue(obj, prop) {
+ var tmp = obj[prop]; if(tmp !== undefined) {return tmp;}
+ var segs = prop;
+ if(!Array.isArray(prop)) {segs = prop.split('.');}
+ var len = segs.length;
+ var i = -1;
+ while (obj && (++i < len)) {obj = obj[segs[i]];}
+ return obj;
+}
+
+function isObj(x) { return typeof x === 'object'; } // faster as a function
+
+// Hacked version of https://github.com/lemire/FastPriorityQueue.js
+var fastpriorityqueue=function(){var r=[],o=0,e={};function n(){for(var e=0,n=r[e],c=1;c<o;){var f=c+1;e=c,f<o&&r[f].score<r[c].score&&(e=f),r[e-1>>1]=r[e],c=1+(e<<1);}for(var a=e-1>>1;e>0&&n.score<r[a].score;a=(e=a)-1>>1){r[e]=r[a];}r[e]=n;}return e.add=function(e){var n=o;r[o++]=e;for(var c=n-1>>1;n>0&&e.score<r[c].score;c=(n=c)-1>>1){r[n]=r[c];}r[n]=e;},e.poll=function(){if(0!==o){var e=r[0];return r[0]=r[--o],n(),e;}},e.peek=function(e){if(0!==o){return r[0];}},e.replaceTop=function(o){r[0]=o,n();},e;};
+var q = fastpriorityqueue(); // reuse this, except for async, it needs to make its own
+
+return fuzzysortNew();
+}); // UMD
+
+// TODO: (performance) wasm version!?
+// TODO: (performance) threads?
+// TODO: (performance) avoid cache misses
+// TODO: (performance) preparedCache is a memory leak
+// TODO: (like sublime) backslash === forwardslash
+// TODO: (like sublime) spaces: "a b" should do 2 searches 1 for a and 1 for b
+// TODO: (scoring) garbage in targets that allows most searches to strict match need a penality
+// TODO: (performance) idk if allowTypo is optimized
diff --git a/static/js/index.js b/static/js/index.js
new file mode 100644
index 0000000..df649df
--- /dev/null
+++ b/static/js/index.js
@@ -0,0 +1,424 @@
+// deno-fmt-ignore-file
+// deno-lint-ignore-file
+// This code was bundled using `deno bundle` and it's not recommended to edit it manually
+
+(function() {
+ const url = self.location.href.split("#")[0];
+ let settings = {
+ pager: {}
+ };
+ self.addEventListener("DOMContentLoaded", function() {
+ if (history.scrollRestoration) history.scrollRestoration = "manual";
+ if (localStorage["settings"]) {
+ settings = JSON.parse(localStorage["settings"]);
+ }
+ if (self.location.href.indexOf("#") >= 0) {
+ settings["pager"][url] = self.pageYOffset;
+ localStorage["settings"] = JSON.stringify(settings);
+ document.getElementById(location.hash.slice(1)).scrollIntoView();
+ return;
+ }
+ if (settings["pager"][url] > 0) {
+ self.scrollTo(0, settings["pager"][url]);
+ return;
+ }
+ settings["pager"][url] = self.pageYOffset;
+ localStorage["settings"] = JSON.stringify(settings);
+ });
+ self.addEventListener("scroll", function() {
+ const currentPosition = self.pageYOffset;
+ settings["pager"][url] = currentPosition;
+ localStorage["settings"] = JSON.stringify(settings);
+ });
+ self.addEventListener("DOMContentLoaded", function() {
+ const up = document.getElementById("top");
+ const back = document.getElementById("back");
+ if (document.body.contains(up)) up.href = "javascript: void(0)";
+ if (document.body.contains(back)) back.href = "javascript: void(0)";
+ self.addEventListener("click", function(event) {
+ if (document.body.contains(up) && up.contains(event.target)) {
+ window.scrollTo(0, 0);
+ }
+ if (document.body.contains(back) && back.contains(event.target)) {
+ history.go(-1);
+ }
+ });
+ });
+})();
+(function() {
+ const options = "targetWindow,width=700,height=500,toolbar=no,location=no,status=no,menubar=no,scrollbars=yes,resizable=yes";
+ self.addEventListener("keydown", function(event) {
+ if (event.repeat && event.key === "d") {
+ selection(dictionary);
+ }
+ if (event.repeat && event.key === "s") {
+ selection(search);
+ }
+ if (event.repeat && event.key === "m") {
+ selection(manual);
+ }
+ });
+ function selection(execute) {
+ let phrase = "" + window.getSelection();
+ phrase = phrase.replace(/[!.:?,;"]/g, "");
+ phrase = phrase.replace(/^\s*(\S*?)\s*$/g, "$1");
+ if (phrase && phrase > "" && phrase.length > 1) {
+ execute(phrase);
+ }
+ }
+ function dictionary(word) {
+ window.open("https://www.merriam-webster.com/dictionary/" + encodeURIComponent(word), "Definitions", options);
+ }
+ function search(phrase) {
+ window.open("https://lite.duckduckgo.com/lite/?q=" + encodeURIComponent(phrase), "Search", options);
+ }
+ function manual(program) {
+ window.open("https://man.archlinux.org/search?q=" + encodeURIComponent(program), "Manual", options);
+ }
+})();
+(function() {
+ self.addEventListener("DOMContentLoaded", function() {
+ let mouseoverTimer;
+ let lastTouchTimestamp;
+ const prefetches = new Set();
+ const prefetchElement = document.createElement("link");
+ const isSupported = prefetchElement.relList && prefetchElement.relList.supports && prefetchElement.relList.supports("prefetch") && window.IntersectionObserver && "isIntersecting" in IntersectionObserverEntry.prototype;
+ const allowQueryString = "instantAllowQueryString" in document.body.dataset;
+ const allowExternalLinks = "instantAllowExternalLinks" in document.body.dataset;
+ const useWhitelist = "instantWhitelist" in document.body.dataset;
+ const mousedownShortcut = "instantMousedownShortcut" in document.body.dataset;
+ let delayOnHover = 65;
+ let useMousedown = false;
+ let useMousedownOnly = false;
+ let useViewport = false;
+ if ("instantIntensity" in document.body.dataset) {
+ const intensity = document.body.dataset.instantIntensity;
+ if (intensity.substr(0, "mousedown".length) == "mousedown") {
+ useMousedown = true;
+ if (intensity == "mousedown-only") {
+ useMousedownOnly = true;
+ }
+ } else if (intensity.substr(0, "viewport".length) == "viewport") {
+ if (!(navigator.connection && (navigator.connection.saveData || navigator.connection.effectiveType && navigator.connection.effectiveType.includes("2g")))) {
+ if (intensity == "viewport") {
+ if (document.documentElement.clientWidth * document.documentElement.clientHeight < 450000) {
+ useViewport = true;
+ }
+ } else if (intensity == "viewport-all") {
+ useViewport = true;
+ }
+ }
+ } else {
+ const milliseconds = parseInt(intensity);
+ if (!isNaN(milliseconds)) {
+ delayOnHover = milliseconds;
+ }
+ }
+ }
+ if (isSupported) {
+ const eventListenersOptions = {
+ capture: true,
+ passive: true
+ };
+ if (!useMousedownOnly) {
+ document.addEventListener("touchstart", touchstartListener, eventListenersOptions);
+ }
+ if (!useMousedown) {
+ document.addEventListener("mouseover", mouseoverListener, eventListenersOptions);
+ } else if (!mousedownShortcut) {
+ document.addEventListener("mousedown", mousedownListener, eventListenersOptions);
+ }
+ if (mousedownShortcut) {
+ document.addEventListener("mousedown", mousedownShortcutListener, eventListenersOptions);
+ }
+ if (useViewport) {
+ let triggeringFunction;
+ if (window.requestIdleCallback) {
+ triggeringFunction = function(callback) {
+ requestIdleCallback(callback, {
+ timeout: 1500
+ });
+ };
+ } else {
+ triggeringFunction = function(callback) {
+ callback();
+ };
+ }
+ triggeringFunction(function() {
+ const intersectionObserver = new IntersectionObserver(function(entries) {
+ entries.forEach(function(entry) {
+ if (entry.isIntersecting) {
+ const linkElement = entry.target;
+ intersectionObserver.unobserve(linkElement);
+ preload(linkElement.href);
+ }
+ });
+ });
+ document.querySelectorAll("a").forEach(function(linkElement) {
+ if (isPreloadable(linkElement)) {
+ intersectionObserver.observe(linkElement);
+ }
+ });
+ });
+ }
+ }
+ function touchstartListener(event) {
+ lastTouchTimestamp = performance.now();
+ const linkElement = event.target.closest("a");
+ if (!isPreloadable(linkElement)) {
+ return;
+ }
+ preload(linkElement.href);
+ }
+ function mouseoverListener(event) {
+ if (performance.now() - lastTouchTimestamp < 1111) {
+ return;
+ }
+ const linkElement = event.target.closest("a");
+ if (!isPreloadable(linkElement)) {
+ return;
+ }
+ linkElement.addEventListener("mouseout", mouseoutListener, {
+ passive: true
+ });
+ mouseoverTimer = setTimeout(function() {
+ preload(linkElement.href);
+ mouseoverTimer = undefined;
+ }, delayOnHover);
+ }
+ function mousedownListener(event) {
+ const linkElement = event.target.closest("a");
+ if (!isPreloadable(linkElement)) {
+ return;
+ }
+ preload(linkElement.href);
+ }
+ function mouseoutListener(event) {
+ if (event.relatedTarget && event.target.closest("a") == event.relatedTarget.closest("a")) {
+ return;
+ }
+ if (mouseoverTimer) {
+ clearTimeout(mouseoverTimer);
+ mouseoverTimer = undefined;
+ }
+ }
+ function mousedownShortcutListener(event1) {
+ if (performance.now() - lastTouchTimestamp < 1111) {
+ return;
+ }
+ const linkElement = event1.target.closest("a");
+ if (event1.which > 1 || event1.metaKey || event1.ctrlKey) {
+ return;
+ }
+ if (!linkElement) {
+ return;
+ }
+ linkElement.addEventListener("click", function(event) {
+ if (event.detail == 1337) {
+ return;
+ }
+ event.preventDefault();
+ }, {
+ capture: true,
+ passive: false,
+ once: true
+ });
+ const customEvent = new MouseEvent("click", {
+ view: window,
+ bubbles: true,
+ cancelable: false,
+ detail: 1337
+ });
+ linkElement.dispatchEvent(customEvent);
+ }
+ function isPreloadable(linkElement) {
+ if (!linkElement || !linkElement.href) {
+ return;
+ }
+ if (useWhitelist && !("instant" in linkElement.dataset)) {
+ return;
+ }
+ if (!allowExternalLinks && linkElement.origin != location.origin && !("instant" in linkElement.dataset)) {
+ return;
+ }
+ if (![
+ "http:",
+ "https:"
+ ].includes(linkElement.protocol)) {
+ return;
+ }
+ if (linkElement.protocol == "http:" && location.protocol == "https:") {
+ return;
+ }
+ if (!allowQueryString && linkElement.search && !("instant" in linkElement.dataset)) {
+ return;
+ }
+ if (linkElement.hash && linkElement.pathname + linkElement.search == location.pathname + location.search) {
+ return;
+ }
+ if ("noInstant" in linkElement.dataset) {
+ return;
+ }
+ return true;
+ }
+ function preload(url) {
+ if (prefetches.has(url)) {
+ return;
+ }
+ const prefetcher = document.createElement("link");
+ prefetcher.rel = "prefetch";
+ prefetcher.href = url;
+ document.head.appendChild(prefetcher);
+ prefetches.add(url);
+ }
+ });
+})();
+(function() {
+ self.addEventListener("DOMContentLoaded", function() {
+ const form = document.getElementById("search-form");
+ const query = document.getElementById("search-input");
+ document.getElementById("search-submit");
+ const dropdown = document.getElementById("search-results");
+ form.addEventListener("focusin", function() {
+ initialize();
+ });
+ form.addEventListener("submit", function(event) {
+ event.preventDefault();
+ return false;
+ });
+ form.addEventListener("keydown", function(event) {
+ const head = dropdown.firstChild.nextElementSibling.firstChild.nextElementSibling;
+ const tail = dropdown.lastElementChild.firstChild.nextElementSibling;
+ if (query.contains(event.target)) {
+ if (event.keyCode == 27) {
+ document.activeElement.blur();
+ dropdown.setAttribute("hidden", "");
+ }
+ }
+ if (event.keyCode == 40) {
+ event.preventDefault();
+ if (document.activeElement == query) head.focus();
+ else if (document.activeElement == tail) query.focus();
+ else {
+ document.activeElement.parentElement.nextElementSibling.firstChild.nextElementSibling.focus();
+ }
+ }
+ if (event.keyCode == 38) {
+ event.preventDefault();
+ if (document.activeElement == query) tail.focus();
+ else if (document.activeElement == head) query.focus();
+ else {
+ document.activeElement.parentElement.previousElementSibling.firstChild.nextElementSibling.focus();
+ }
+ }
+ if (event.keyCode == 8) {
+ if (document.activeElement != query) {
+ event.preventDefault();
+ query.focus();
+ }
+ }
+ if (event.keyCode == 13) {
+ if (dropdown && document.activeElement == query) {
+ event.preventDefault();
+ head.focus();
+ self.window.location = document.activeElement.href;
+ }
+ }
+ });
+ let scrolls = 0;
+ self.addEventListener("scroll", function() {
+ if (scrolls > 3) {
+ scrolls = 0;
+ document.activeElement.blur();
+ dropdown.setAttribute("hidden", "");
+ }
+ scrolls++;
+ });
+ document.addEventListener("click", function(event) {
+ if (!form.contains(event.target)) {
+ dropdown.setAttribute("hidden", "");
+ }
+ });
+ function fetch_JSON(path, callback) {
+ const httpRequest = new XMLHttpRequest();
+ httpRequest.onreadystatechange = function() {
+ if (httpRequest.readyState === 4) {
+ if (httpRequest.status === 200) {
+ const data = JSON.parse(httpRequest.responseText);
+ if (callback) callback(data);
+ }
+ }
+ };
+ httpRequest.open("GET", path);
+ httpRequest.send();
+ }
+ function load_script(url) {
+ return new Promise(function(resolve, reject) {
+ const script = document.createElement("script");
+ script.onerror = reject;
+ script.onload = resolve;
+ if (document.currentScript) {
+ document.currentScript.parentNode.insertBefore(script, document.currentScript);
+ } else {
+ document.head.appendChild(script);
+ }
+ script.src = url;
+ });
+ }
+ let first_run = true;
+ function initialize() {
+ if (first_run) {
+ load_script(window.location.origin + "/js/fuzzysort.js").then(()=>{
+ first_run = false;
+ fetch_JSON("/index.json", function(data) {
+ const options = {
+ key: [
+ "title"
+ ]
+ };
+ query.addEventListener("keyup", function() {
+ search(query.value, data.items, options);
+ });
+ query.addEventListener("focusin", function() {
+ search(query.value, data.items, options);
+ });
+ search(query.value, data.items, options);
+ });
+ }).catch((error)=>{
+ console.log("Error failed to load fuzzy sort: " + error);
+ });
+ }
+ }
+ function search(term, data, options) {
+ const results = fuzzysort.go(term, data, options);
+ let items = "";
+ if (results.length === 0 && term.length >= 0) {
+ let separator = "—";
+ if (term.length === 0) separator = "";
+ items = `
+ <li>
+ <a href="javascript: void(0)" tabindex="0">${term} ${separator} No Results Found</a>
+ </li>
+ `;
+ dropdown.removeAttribute("hidden");
+ } else {
+ dropdown.removeAttribute("hidden");
+ for(const string in results.slice(0, 10)){
+ const decode = document.createElement("textarea");
+ decode.innerHTML = results[string].obj.title;
+ let highlight = fuzzysort.highlight(fuzzysort.single(term, decode.value), "<span>", "</span>");
+ if (highlight === null) {
+ highlight = decode.value;
+ }
+ items = items + `
+ <li>
+ <a href="${results[string].obj.url}" tabindex="0">${highlight}</a>
+ </li>
+ `;
+ }
+ }
+ dropdown.innerHTML = items;
+ }
+ });
+})();
+console.log("Surface Control: OK");
diff --git a/static/js/index.ts b/static/js/index.ts
new file mode 100644
index 0000000..6000441
--- /dev/null
+++ b/static/js/index.ts
@@ -0,0 +1,6 @@
+import "./pager.ts";
+import "./plumber.ts";
+import "./instantpage.ts";
+import "./fixedsearch.ts";
+
+console.log("Surface Control: OK");
diff --git a/static/js/instantpage.ts b/static/js/instantpage.ts
new file mode 100644
index 0000000..7c78a6f
--- /dev/null
+++ b/static/js/instantpage.ts
@@ -0,0 +1,292 @@
+(function () {
+ self.addEventListener("DOMContentLoaded", function () {
+ /*! instant.page v5.1.0 - (C) 2019-2020 Alexandre Dieulot - https://instant.page/license */
+
+ let mouseoverTimer;
+ let lastTouchTimestamp;
+ const prefetches = new Set();
+ const prefetchElement = document.createElement("link");
+ const isSupported = prefetchElement.relList &&
+ prefetchElement.relList.supports &&
+ prefetchElement.relList.supports("prefetch") &&
+ window.IntersectionObserver &&
+ "isIntersecting" in IntersectionObserverEntry.prototype;
+ const allowQueryString = "instantAllowQueryString" in document.body.dataset;
+ const allowExternalLinks = "instantAllowExternalLinks" in
+ document.body.dataset;
+ const useWhitelist = "instantWhitelist" in document.body.dataset;
+ const mousedownShortcut = "instantMousedownShortcut" in
+ document.body.dataset;
+ const DELAY_TO_NOT_BE_CONSIDERED_A_TOUCH_INITIATED_ACTION = 1111;
+
+ let delayOnHover = 65;
+ let useMousedown = false;
+ let useMousedownOnly = false;
+ let useViewport = false;
+
+ if ("instantIntensity" in document.body.dataset) {
+ const intensity = document.body.dataset.instantIntensity;
+
+ if (intensity.substr(0, "mousedown".length) == "mousedown") {
+ useMousedown = true;
+ if (intensity == "mousedown-only") {
+ useMousedownOnly = true;
+ }
+ } else if (intensity.substr(0, "viewport".length) == "viewport") {
+ if (
+ !(navigator.connection &&
+ (navigator.connection.saveData ||
+ (navigator.connection.effectiveType &&
+ navigator.connection.effectiveType.includes("2g"))))
+ ) {
+ if (intensity == "viewport") {
+ /* Biggest iPhone resolution (which we want): 414 × 896 = 370944
+ * Small 7" tablet resolution (which we don’t want): 600 × 1024 = 614400
+ * Note that the viewport (which we check here) is smaller than the resolution due to the UI’s chrome */
+ if (
+ document.documentElement.clientWidth *
+ document.documentElement.clientHeight < 450000
+ ) {
+ useViewport = true;
+ }
+ } else if (intensity == "viewport-all") {
+ useViewport = true;
+ }
+ }
+ } else {
+ const milliseconds = parseInt(intensity);
+ if (!isNaN(milliseconds)) {
+ delayOnHover = milliseconds;
+ }
+ }
+ }
+
+ if (isSupported) {
+ const eventListenersOptions = {
+ capture: true,
+ passive: true,
+ };
+
+ if (!useMousedownOnly) {
+ document.addEventListener(
+ "touchstart",
+ touchstartListener,
+ eventListenersOptions,
+ );
+ }
+
+ if (!useMousedown) {
+ document.addEventListener(
+ "mouseover",
+ mouseoverListener,
+ eventListenersOptions,
+ );
+ } else if (!mousedownShortcut) {
+ document.addEventListener(
+ "mousedown",
+ mousedownListener,
+ eventListenersOptions,
+ );
+ }
+
+ if (mousedownShortcut) {
+ document.addEventListener(
+ "mousedown",
+ mousedownShortcutListener,
+ eventListenersOptions,
+ );
+ }
+
+ if (useViewport) {
+ let triggeringFunction;
+ if (window.requestIdleCallback) {
+ triggeringFunction = function (callback) {
+ requestIdleCallback(callback, {
+ timeout: 1500,
+ });
+ };
+ } else {
+ triggeringFunction = function (callback) {
+ callback();
+ };
+ }
+
+ triggeringFunction(function () {
+ const intersectionObserver = new IntersectionObserver(
+ function (entries) {
+ entries.forEach(function (entry) {
+ if (entry.isIntersecting) {
+ const linkElement = entry.target;
+ intersectionObserver.unobserve(linkElement);
+ preload(linkElement.href);
+ }
+ });
+ },
+ );
+
+ document.querySelectorAll("a").forEach(function (linkElement) {
+ if (isPreloadable(linkElement)) {
+ intersectionObserver.observe(linkElement);
+ }
+ });
+ });
+ }
+ }
+
+ function touchstartListener(event) {
+ /* Chrome on Android calls mouseover before touchcancel so `lastTouchTimestamp`
+ * must be assigned on touchstart to be measured on mouseover. */
+ lastTouchTimestamp = performance.now();
+
+ const linkElement = event.target.closest("a");
+
+ if (!isPreloadable(linkElement)) {
+ return;
+ }
+
+ preload(linkElement.href);
+ }
+
+ function mouseoverListener(event) {
+ if (
+ performance.now() - lastTouchTimestamp <
+ DELAY_TO_NOT_BE_CONSIDERED_A_TOUCH_INITIATED_ACTION
+ ) {
+ return;
+ }
+
+ const linkElement = event.target.closest("a");
+
+ if (!isPreloadable(linkElement)) {
+ return;
+ }
+
+ linkElement.addEventListener("mouseout", mouseoutListener, {
+ passive: true,
+ });
+
+ mouseoverTimer = setTimeout(function () {
+ preload(linkElement.href);
+ mouseoverTimer = undefined;
+ }, delayOnHover);
+ }
+
+ function mousedownListener(event) {
+ const linkElement = event.target.closest("a");
+
+ if (!isPreloadable(linkElement)) {
+ return;
+ }
+
+ preload(linkElement.href);
+ }
+
+ function mouseoutListener(event) {
+ if (
+ event.relatedTarget &&
+ event.target.closest("a") == event.relatedTarget.closest("a")
+ ) {
+ return;
+ }
+
+ if (mouseoverTimer) {
+ clearTimeout(mouseoverTimer);
+ mouseoverTimer = undefined;
+ }
+ }
+
+ function mousedownShortcutListener(event) {
+ if (
+ performance.now() - lastTouchTimestamp <
+ DELAY_TO_NOT_BE_CONSIDERED_A_TOUCH_INITIATED_ACTION
+ ) {
+ return;
+ }
+
+ const linkElement = event.target.closest("a");
+
+ if (event.which > 1 || event.metaKey || event.ctrlKey) {
+ return;
+ }
+
+ if (!linkElement) {
+ return;
+ }
+
+ linkElement.addEventListener("click", function (event) {
+ if (event.detail == 1337) {
+ return;
+ }
+
+ event.preventDefault();
+ }, { capture: true, passive: false, once: true });
+
+ const customEvent = new MouseEvent("click", {
+ view: window,
+ bubbles: true,
+ cancelable: false,
+ detail: 1337,
+ });
+ linkElement.dispatchEvent(customEvent);
+ }
+
+ function isPreloadable(linkElement) {
+ if (!linkElement || !linkElement.href) {
+ return;
+ }
+
+ if (useWhitelist && !("instant" in linkElement.dataset)) {
+ return;
+ }
+
+ if (
+ !allowExternalLinks && linkElement.origin != location.origin &&
+ !("instant" in linkElement.dataset)
+ ) {
+ return;
+ }
+
+ if (!["http:", "https:"].includes(linkElement.protocol)) {
+ return;
+ }
+
+ if (linkElement.protocol == "http:" && location.protocol == "https:") {
+ return;
+ }
+
+ if (
+ !allowQueryString && linkElement.search &&
+ !("instant" in linkElement.dataset)
+ ) {
+ return;
+ }
+
+ if (
+ linkElement.hash &&
+ linkElement.pathname + linkElement.search ==
+ location.pathname + location.search
+ ) {
+ return;
+ }
+
+ if ("noInstant" in linkElement.dataset) {
+ return;
+ }
+
+ return true;
+ }
+
+ function preload(url) {
+ if (prefetches.has(url)) {
+ return;
+ }
+
+ const prefetcher = document.createElement("link");
+ prefetcher.rel = "prefetch";
+ prefetcher.href = url;
+ document.head.appendChild(prefetcher);
+
+ prefetches.add(url);
+ }
+ });
+})();
diff --git a/static/js/pager.ts b/static/js/pager.ts
new file mode 100644
index 0000000..de60bcd
--- /dev/null
+++ b/static/js/pager.ts
@@ -0,0 +1,46 @@
+(function () {
+ const url = self.location.href.split("#")[0];
+ let settings = { pager: {} };
+
+ self.addEventListener("DOMContentLoaded", function () {
+ if (history.scrollRestoration) history.scrollRestoration = "manual";
+ if (localStorage["settings"]) {
+ settings = JSON.parse(localStorage["settings"]);
+ }
+ if (self.location.href.indexOf("#") >= 0) {
+ settings["pager"][url] = self.pageYOffset;
+ localStorage["settings"] = JSON.stringify(settings);
+ document.getElementById(location.hash.slice(1)).scrollIntoView();
+ return;
+ }
+ if (settings["pager"][url] > 0) {
+ self.scrollTo(0, settings["pager"][url]);
+ return;
+ }
+ settings["pager"][url] = self.pageYOffset;
+ localStorage["settings"] = JSON.stringify(settings);
+ });
+
+ self.addEventListener("scroll", function () {
+ const currentPosition = self.pageYOffset;
+ settings["pager"][url] = currentPosition;
+ localStorage["settings"] = JSON.stringify(settings);
+ });
+
+ self.addEventListener("DOMContentLoaded", function () {
+ const up = document.getElementById("top");
+ const back = document.getElementById("back");
+
+ if (document.body.contains(up)) up.href = "javascript: void(0)";
+ if (document.body.contains(back)) back.href = "javascript: void(0)";
+
+ self.addEventListener("click", function (event) {
+ if (document.body.contains(up) && up.contains(event.target)) {
+ window.scrollTo(0, 0);
+ }
+ if (document.body.contains(back) && back.contains(event.target)) {
+ history.go(-1);
+ }
+ });
+ });
+})();
diff --git a/static/js/plumber.ts b/static/js/plumber.ts
new file mode 100644
index 0000000..8e551fd
--- /dev/null
+++ b/static/js/plumber.ts
@@ -0,0 +1,59 @@
+/**
+ * Plumber based on and inspired by
+ * Dictionary Access Copyright (C) 2006, Paul Lutus
+ * https://arachnoid.com/javascript/dictionary_access.js
+ * LICENSE: GPLv2+
+ */
+
+(function () {
+ const options =
+ "targetWindow,width=700,height=500,toolbar=no,location=no,status=no,menubar=no,scrollbars=yes,resizable=yes";
+
+ self.addEventListener("keydown", function (event) {
+ if (event.repeat && event.key === "d") {
+ selection(dictionary);
+ }
+ if (event.repeat && event.key === "s") {
+ selection(search);
+ }
+ if (event.repeat && event.key === "m") {
+ selection(manual);
+ }
+ });
+
+ function selection(execute) {
+ let phrase = "" + window.getSelection();
+ phrase = phrase.replace(/[!.:?,;"]/g, "");
+ phrase = phrase.replace(/^\s*(\S*?)\s*$/g, "$1");
+ if (phrase && phrase > "" && phrase.length > 1) {
+ execute(phrase);
+ }
+ }
+
+ function dictionary(word) {
+ window.open(
+ "https://www.merriam-webster.com/dictionary/" +
+ encodeURIComponent(word),
+ "Definitions",
+ options,
+ );
+ }
+
+ function search(phrase) {
+ window.open(
+ "https://lite.duckduckgo.com/lite/?q=" +
+ encodeURIComponent(phrase),
+ "Search",
+ options,
+ );
+ }
+
+ function manual(program) {
+ window.open(
+ "https://man.archlinux.org/search?q=" +
+ encodeURIComponent(program),
+ "Manual",
+ options,
+ );
+ }
+})();