aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--Makefile8
-rw-r--r--assets/iosevka-aile-regular.woffbin187604 -> 0 bytes
-rw-r--r--assets/iosevka-aile-regular.woff2bin130736 -> 0 bytes
-rw-r--r--assets/iosevka-fixed-ss03-bold.woffbin0 -> 214840 bytes
-rw-r--r--assets/iosevka-fixed-ss03-bold.woff2bin0 -> 146024 bytes
-rw-r--r--assets/iosevka-fixed-ss03-regular.woffbin0 -> 215424 bytes
-rw-r--r--assets/iosevka-fixed-ss03-regular.woff2bin0 -> 145296 bytes
-rw-r--r--assets/iosevka-term-ss03-regular.woffbin155916 -> 0 bytes
-rw-r--r--assets/iosevka-term-ss03-regular.woff2bin111644 -> 0 bytes
-rw-r--r--assets/main.js2
-rw-r--r--assets/style.css2
-rw-r--r--css/main.scss86
-rw-r--r--fonts/iosevka-aile-regular.ttfbin497504 -> 0 bytes
-rw-r--r--fonts/iosevka-term-ss03-bold.ttfbin0 -> 704092 bytes
-rw-r--r--fonts/iosevka-term-ss03-regular.ttfbin410836 -> 699144 bytes
-rw-r--r--internal/port/gemini.go38
-rw-r--r--internal/port/main.go56
-rw-r--r--internal/port/tpl/_fonts.html12
-rw-r--r--internal/port/tpl/_header.html48
-rw-r--r--internal/port/tpl/_modals.html2
-rw-r--r--internal/port/tpl/gemini.html62
-rw-r--r--internal/port/tpl/gopher.html19
-rw-r--r--js/main.ts4
-rw-r--r--pkg/libgemini/libgemini.go64
24 files changed, 214 insertions, 189 deletions
diff --git a/Makefile b/Makefile
index 58ac412..a537070 100644
--- a/Makefile
+++ b/Makefile
@@ -8,10 +8,10 @@ dev: build
8build: clean 8build: clean
9 sassc -t compressed css/main.scss assets/style.css 9 sassc -t compressed css/main.scss assets/style.css
10 tsc --strict --module none --outFile /dev/stdout js/* | terser --compress --mangle -o assets/main.js -- 10 tsc --strict --module none --outFile /dev/stdout js/* | terser --compress --mangle -o assets/main.js --
11 #pyftsubset fonts/iosevka-term-ss03-regular.ttf "*" --name-IDs+=0,4,6 --flavor='woff' --with-zopfli --output-file='assets/iosevka-term-ss03-regular.woff' 11 #pyftsubset fonts/iosevka-fixed-ss03-regular.ttf "*" --name-IDs+=0,4,6 --flavor='woff' --with-zopfli --output-file='assets/iosevka-fixed-ss03-regular.woff'
12 #pyftsubset fonts/iosevka-term-ss03-regular.ttf "*" --name-IDs+=0,4,6 --flavor='woff2' --output-file='assets/iosevka-term-ss03-regular.woff2' 12 #pyftsubset fonts/iosevka-fixed-ss03-regular.ttf "*" --name-IDs+=0,4,6 --flavor='woff2' --output-file='assets/iosevka-fixed-ss03-regular.woff2'
13 #pyftsubset fonts/iosevka-aile-regular.ttf "*" --name-IDs+=0,4,6 --flavor='woff' --with-zopfli --output-file='assets/iosevka-aile-regular.woff' 13 #pyftsubset fonts/iosevka-fixed-ss03-bold.ttf "*" --name-IDs+=0,4,6 --flavor='woff' --with-zopfli --output-file='assets/iosevka-fixed-ss03-bold.woff'
14 #pyftsubset fonts/iosevka-aile-regular.ttf "*" --name-IDs+=0,4,6 --flavor='woff2' --output-file='assets/iosevka-aile-regular.woff2' 14 #pyftsubset fonts/iosevka-fixed-ss03-bold.ttf "*" --name-IDs+=0,4,6 --flavor='woff2' --output-file='assets/iosevka-fixed-ss03-bold.woff2'
15 go build -o ./port.bin ./cmd/port 15 go build -o ./port.bin ./cmd/port
16 16
17profile: 17profile:
diff --git a/assets/iosevka-aile-regular.woff b/assets/iosevka-aile-regular.woff
deleted file mode 100644
index 97b42ea..0000000
--- a/assets/iosevka-aile-regular.woff
+++ /dev/null
Binary files differ
diff --git a/assets/iosevka-aile-regular.woff2 b/assets/iosevka-aile-regular.woff2
deleted file mode 100644
index fea3967..0000000
--- a/assets/iosevka-aile-regular.woff2
+++ /dev/null
Binary files differ
diff --git a/assets/iosevka-fixed-ss03-bold.woff b/assets/iosevka-fixed-ss03-bold.woff
new file mode 100644
index 0000000..0ae2733
--- /dev/null
+++ b/assets/iosevka-fixed-ss03-bold.woff
Binary files differ
diff --git a/assets/iosevka-fixed-ss03-bold.woff2 b/assets/iosevka-fixed-ss03-bold.woff2
new file mode 100644
index 0000000..317ee3c
--- /dev/null
+++ b/assets/iosevka-fixed-ss03-bold.woff2
Binary files differ
diff --git a/assets/iosevka-fixed-ss03-regular.woff b/assets/iosevka-fixed-ss03-regular.woff
new file mode 100644
index 0000000..6168a4a
--- /dev/null
+++ b/assets/iosevka-fixed-ss03-regular.woff
Binary files differ
diff --git a/assets/iosevka-fixed-ss03-regular.woff2 b/assets/iosevka-fixed-ss03-regular.woff2
new file mode 100644
index 0000000..27f81cb
--- /dev/null
+++ b/assets/iosevka-fixed-ss03-regular.woff2
Binary files differ
diff --git a/assets/iosevka-term-ss03-regular.woff b/assets/iosevka-term-ss03-regular.woff
deleted file mode 100644
index eb2568a..0000000
--- a/assets/iosevka-term-ss03-regular.woff
+++ /dev/null
Binary files differ
diff --git a/assets/iosevka-term-ss03-regular.woff2 b/assets/iosevka-term-ss03-regular.woff2
deleted file mode 100644
index 957b020..0000000
--- a/assets/iosevka-term-ss03-regular.woff2
+++ /dev/null
Binary files differ
diff --git a/assets/main.js b/assets/main.js
index eee70d7..1fa4dcf 100644
--- a/assets/main.js
+++ b/assets/main.js
@@ -1 +1 @@
"use strict";var KeyValueStore=function(){function e(e){this.data=e;for(var t=0,n=Object.keys(e);t<n.length;t++){var a=n[t],l=e[a];if(l.valueRange&&-1===l.valueRange.indexOf(l.value))throw new Error('Invalid value "'+l.value+'" for ID "'+a+'"')}}return e.prototype.getValue=function(e){return this.data[e].value},e.prototype.setValue=function(e,t){var n=this.data[e];if(n.valueRange&&-1===n.valueRange.indexOf(t))throw new Error('Invalid value "'+t+'" for ID "'+e+'"');n.value=t,n.callbacks&&n.callbacks.forEach((function(e){e(t)}))},e.prototype.cycleValue=function(e,t){void 0===t&&(t=1);var n=this.data[e];if(!n)throw new Error('Invalid ID "'+e+'"');var a=n.value;if(n.valueRange){var l=n.valueRange.indexOf(a)+t;l>=n.valueRange.length?l=0:l<0&&(l=n.valueRange.length-1),a=n.value=n.valueRange[l]}else{if("number"!=typeof a)throw new Error("Can't cycle \""+e+'"');a+=t,n.value=a}return n.callbacks&&n.callbacks.forEach((function(e){e(a)})),a},e.prototype.addCallback=function(e,t){var n=this.data[e];n.callbacks||(n.callbacks=[]),n.callbacks.push(t)},e}();function ensureSetting(e,t){var n=localStorage.getItem(e);return null===n&&(n=t,localStorage.setItem(e,n)),n}var settings=new KeyValueStore({wordWrap:{value:"1"===ensureSetting("word-wrap","1"),callbacks:[function(e){localStorage.setItem("word-wrap",e?"1":"0")}],valueRange:[!1,!0]},monospaceFont:{value:"1"===ensureSetting("monospace-font","1"),callbacks:[function(e){localStorage.setItem("monospace-font",e?"1":"0")}],valueRange:[!1,!0]},imagePreviews:{value:"1"===ensureSetting("image-previews","1"),callbacks:[function(e){localStorage.setItem("image-previews",e?"1":"0")}],valueRange:[!1,!0]},clickablePlainLinks:{value:"1"===ensureSetting("clickable-plain-links","1"),callbacks:[function(e){localStorage.setItem("clickable-plain-links",e?"1":"0")}],valueRange:[!1,!0]}});function generateImageThumbnails(){for(var e=document.querySelectorAll(".link--IMG, .link--GIF"),t=e.length,n=function(){var n=e[t],a=n.href.replace(/^(.*?)\/I/,"$1/T"),l=document.createTextNode("\n"),i=document.createElement("span");i.classList.add("type-annotation"),i.textContent=" -> ";var s=document.createElement("img");s.src=a,s.addEventListener("load",(function(e){s.classList.remove("faded")}));var r=document.createElement("a");r.classList.add("img-preview"),r.href=n.href,r.addEventListener("click",(function(e){return e.preventDefault(),s.classList.add("faded"),s.classList.contains("expanded")?(s.classList.remove("expanded"),s.src=a):(s.classList.add("expanded"),s.src=r.href),!1})),r.append(s),n.parentNode.insertBefore(r,n.nextSibling),n.parentNode.insertBefore(i,r),n.parentNode.insertBefore(l,i)};t--;)n()}function removeImageThumbnails(){for(var e=document.querySelectorAll(".link--IMG, .link--GIF"),t=e.length;t--;)for(var n=e[t],a=3;a--&&n.nextSibling;)n.nextSibling.remove()}function generateMarkupForPlainLinks(){if(document.body.classList.contains("is-plain")){var e=document.getElementsByClassName("content")[0];e.innerHTML=e.innerHTML.replace(/\b[a-z]*:\/\/[-a-zA-Z0-9@:%._\+~#=]{2,256}\.[a-z]{2,}\b[-a-zA-Z0-9@:%_\+.,~#?&//=]*/g,(function(e){var t=e;return 0===t.indexOf("gopher://")?t=t.replace(/^gopher:\/\/(.*)$/,location.origin+"/gopher/$1"):0===t.indexOf("gemini://")&&(t=t.replace(/^gemini:\/\/(.*)$/,location.origin+"/gemini/$1")),'<a href="'+t+'">'+e+"</a>"})),e.innerHTML=e.innerHTML.replace(/\bmailto:[-a-zA-Z0-9@:%._\+~#=]+@(?:[-a-zA-Z0-9@:%._\+~#=]+\.)+[a-z]{2,}\b/g,(function(e){return'<a href="'+e+'">'+e+"</a>"}))}}function removeMarkupForPlainLinks(){if(document.body.classList.contains("is-plain"))for(var e=document.getElementsByClassName("content")[0],t=e.getElementsByTagName("a"),n=t.length;n--;){var a=t[n],l=document.createTextNode(a.textContent);e.replaceChild(l,a)}}!function(){for(var e=document.getElementsByClassName("link--QRY"),t=e.length;t--;)e[t].addEventListener("click",(function(e){e.preventDefault();var t=prompt("Please enter required input: ","");return null!==t&&""!==t&&(window.location.href=e.target.href+"?"+t),!1}))}(),function(){for(var e=document.getElementsByClassName("location__prefix"),t=e.length;t--;)e[t].addEventListener("click",(function(e){e.preventDefault();var t=prompt("Please enter new location (gopher://... or gemini://...):","");return null!==t&&""!==t.trim()&&(t=0===(t=t.trim()).indexOf("gopher://")?"gopher/"+t.substring(9):0===t.indexOf("gemini://")?"gemini/"+t.substring(9):"gopher/"+t,window.location.href=window.location.origin+"/"+t),!1}))}(),function(){var e=document.getElementsByClassName("wrap")[0],t=e.getElementsByClassName("content")[0],n=document.getElementsByClassName("setting--image-previews")[0].getElementsByClassName("setting__value")[0],a=function(e,t){void 0===t&&(t=!1),e?generateImageThumbnails():t||removeImageThumbnails(),n.textContent=e?"[yes]":"[no]"};n.addEventListener("click",(function(e){return e.preventDefault(),settings.cycleValue("imagePreviews"),!1})),a(settings.getValue("imagePreviews"),!0),settings.addCallback("imagePreviews",a);var l=document.getElementsByClassName("setting--monospace-font")[0].getElementsByClassName("setting__value")[0],i=function(e){e?t.classList.add("content--has-monospace-font"):t.classList.remove("content--has-monospace-font"),l.textContent=e?"[yes]":"[no]"};l.addEventListener("click",(function(e){return e.preventDefault(),settings.cycleValue("monospaceFont"),!1})),i(settings.getValue("monospaceFont")),settings.addCallback("monospaceFont",i);var s=document.getElementsByClassName("setting--word-wrap")[0].getElementsByClassName("setting__value")[0],r=function(t){t?e.classList.add("wrap--word-wrap"):e.classList.remove("wrap--word-wrap"),s.textContent=t?"[yes]":"[no]"};s.addEventListener("click",(function(e){return e.preventDefault(),settings.cycleValue("wordWrap"),!1})),r(settings.getValue("wordWrap")),settings.addCallback("wordWrap",r);var o=document.getElementsByClassName("setting--clickable-plain-links")[0].getElementsByClassName("setting__value")[0],c=function(e){e?generateMarkupForPlainLinks():removeMarkupForPlainLinks(),o.textContent=e?"[yes]":"[no]"};o.addEventListener("click",(function(e){return e.preventDefault(),settings.cycleValue("clickablePlainLinks"),!1})),c(settings.getValue("clickablePlainLinks")),settings.addCallback("clickablePlainLinks",c)}(),function(){for(var e=document.getElementsByClassName("modal"),t=e.length,n=function(){var n=e[t],a=n.getElementsByClassName("modal__content")[0],l=n.getElementsByClassName("modal__close-btn")[0];document.addEventListener("click",(function(e){n.classList.contains("modal--visible")&&(e.target===a||a.contains(e.target)||(n.classList.remove("modal--visible"),e.preventDefault(),e.stopPropagation()))}),!0),document.addEventListener("keydown",(function(e){n.classList.contains("modal--visible")&&27===e.keyCode&&n.classList.remove("modal--visible")})),l.addEventListener("click",(function(e){return e.preventDefault(),n.classList.remove("modal--visible"),!1}))};t--;)n();var a=document.getElementsByClassName("settings-btn")[0],l=document.getElementsByClassName("modal--settings")[0];a.addEventListener("click",(function(e){return e.preventDefault(),l.classList.add("modal--visible"),!1}))}(); \ No newline at end of file "use strict";var KeyValueStore=function(){function e(e){this.data=e;for(var t=0,n=Object.keys(e);t<n.length;t++){var a=n[t],l=e[a];if(l.valueRange&&-1===l.valueRange.indexOf(l.value))throw new Error('Invalid value "'+l.value+'" for ID "'+a+'"')}}return e.prototype.getValue=function(e){return this.data[e].value},e.prototype.setValue=function(e,t){var n=this.data[e];if(n.valueRange&&-1===n.valueRange.indexOf(t))throw new Error('Invalid value "'+t+'" for ID "'+e+'"');n.value=t,n.callbacks&&n.callbacks.forEach((function(e){e(t)}))},e.prototype.cycleValue=function(e,t){void 0===t&&(t=1);var n=this.data[e];if(!n)throw new Error('Invalid ID "'+e+'"');var a=n.value;if(n.valueRange){var l=n.valueRange.indexOf(a)+t;l>=n.valueRange.length?l=0:l<0&&(l=n.valueRange.length-1),a=n.value=n.valueRange[l]}else{if("number"!=typeof a)throw new Error("Can't cycle \""+e+'"');a+=t,n.value=a}return n.callbacks&&n.callbacks.forEach((function(e){e(a)})),a},e.prototype.addCallback=function(e,t){var n=this.data[e];n.callbacks||(n.callbacks=[]),n.callbacks.push(t)},e}();function ensureSetting(e,t){var n=localStorage.getItem(e);return null===n&&(n=t,localStorage.setItem(e,n)),n}var settings=new KeyValueStore({wordWrap:{value:"1"===ensureSetting("word-wrap","1"),callbacks:[function(e){localStorage.setItem("word-wrap",e?"1":"0")}],valueRange:[!1,!0]},monospaceFont:{value:"1"===ensureSetting("monospace-font","1"),callbacks:[function(e){localStorage.setItem("monospace-font",e?"1":"0")}],valueRange:[!1,!0]},imagePreviews:{value:"1"===ensureSetting("image-previews","1"),callbacks:[function(e){localStorage.setItem("image-previews",e?"1":"0")}],valueRange:[!1,!0]},clickablePlainLinks:{value:"1"===ensureSetting("clickable-plain-links","1"),callbacks:[function(e){localStorage.setItem("clickable-plain-links",e?"1":"0")}],valueRange:[!1,!0]}});function generateImageThumbnails(){for(var e=document.querySelectorAll(".link--IMG, .link--GIF"),t=e.length,n=function(){var n=e[t],a=n.href.replace(/^(.*?)\/I/,"$1/T"),l=document.createTextNode("\n"),i=document.createElement("span");i.classList.add("type-annotation"),i.textContent=" -> ";var s=document.createElement("img");s.src=a,s.addEventListener("load",(function(e){s.classList.remove("faded")}));var r=document.createElement("a");r.classList.add("img-preview"),r.href=n.href,r.addEventListener("click",(function(e){return e.preventDefault(),s.classList.add("faded"),s.classList.contains("expanded")?(s.classList.remove("expanded"),s.src=a):(s.classList.add("expanded"),s.src=r.href),!1})),r.append(s),n.parentNode.insertBefore(r,n.nextSibling),n.parentNode.insertBefore(i,r),n.parentNode.insertBefore(l,i)};t--;)n()}function removeImageThumbnails(){for(var e=document.querySelectorAll(".link--IMG, .link--GIF"),t=e.length;t--;)for(var n=e[t],a=3;a--&&n.nextSibling;)n.nextSibling.remove()}function generateMarkupForPlainLinks(){if(document.body.classList.contains("is-plain")){var e=document.getElementsByClassName("content")[0];e.innerHTML=e.innerHTML.replace(/\b[a-z]*:\/\/[-a-zA-Z0-9@:%._\+~#=]{2,256}\.[a-z]{2,}\b[-a-zA-Z0-9@:%_\+.,~#?&//=]*/g,(function(e){var t=e;return 0===t.indexOf("gopher://")?t=t.replace(/^gopher:\/\/(.*)$/,location.origin+"/gopher/$1"):0===t.indexOf("gemini://")&&(t=t.replace(/^gemini:\/\/(.*)$/,location.origin+"/gemini/$1")),'<a href="'+t+'">'+e+"</a>"})),e.innerHTML=e.innerHTML.replace(/\bmailto:[-a-zA-Z0-9@:%._\+~#=]+@(?:[-a-zA-Z0-9@:%._\+~#=]+\.)+[a-z]{2,}\b/g,(function(e){return'<a href="'+e+'">'+e+"</a>"}))}}function removeMarkupForPlainLinks(){if(document.body.classList.contains("is-plain"))for(var e=document.getElementsByClassName("content")[0],t=e.getElementsByTagName("a"),n=t.length;n--;){var a=t[n],l=document.createTextNode(a.textContent);e.replaceChild(l,a)}}!function(){for(var e=document.getElementsByClassName("link--QRY"),t=e.length;t--;)e[t].addEventListener("click",(function(e){e.preventDefault();var t=prompt("Please enter required input: ","");return null!==t&&""!==t&&(window.location.href=e.target.href+"?"+t),!1}))}(),function(){for(var e=document.getElementsByClassName("location__prefix"),t=e.length;t--;)e[t].addEventListener("click",(function(e){e.preventDefault();var t=prompt("Please enter new location (gopher://... or gemini://...):","");return null!==t&&""!==t.trim()&&(t=0===(t=t.trim()).indexOf("gopher://")?"gopher/"+t.substring(9):0===t.indexOf("gemini://")?"gemini/"+t.substring(9):"gopher/"+t,window.location.href=window.location.origin+"/"+t),!1}))}(),function(){var e=document.getElementsByClassName("wrap")[0],t=e.getElementsByClassName("content")[0],n=document.getElementsByClassName("setting--image-previews")[0].getElementsByClassName("setting__value")[0],a=function(e,t){void 0===t&&(t=!1),e?generateImageThumbnails():t||removeImageThumbnails(),n.textContent=e?"[yes]":"[no]"};n.addEventListener("click",(function(e){return e.preventDefault(),settings.cycleValue("imagePreviews"),!1})),a(settings.getValue("imagePreviews"),!0),settings.addCallback("imagePreviews",a);var l=document.getElementsByClassName("setting--monospace-font")[0].getElementsByClassName("setting__value")[0],i=function(e){e?t.classList.add("content--prefer-monospace"):t.classList.remove("content--prefer-monospace"),l.textContent=e?"[yes]":"[no]"};l.addEventListener("click",(function(e){return e.preventDefault(),settings.cycleValue("monospaceFont"),!1})),i(settings.getValue("monospaceFont")),settings.addCallback("monospaceFont",i);var s=document.getElementsByClassName("setting--word-wrap")[0].getElementsByClassName("setting__value")[0],r=function(t){t?e.classList.add("wrap--word-wrap"):e.classList.remove("wrap--word-wrap"),s.textContent=t?"[yes]":"[no]"};s.addEventListener("click",(function(e){return e.preventDefault(),settings.cycleValue("wordWrap"),!1})),r(settings.getValue("wordWrap")),settings.addCallback("wordWrap",r);var o=document.getElementsByClassName("setting--clickable-plain-links")[0].getElementsByClassName("setting__value")[0],c=function(e){e?generateMarkupForPlainLinks():removeMarkupForPlainLinks(),o.textContent=e?"[yes]":"[no]"};o.addEventListener("click",(function(e){return e.preventDefault(),settings.cycleValue("clickablePlainLinks"),!1})),c(settings.getValue("clickablePlainLinks")),settings.addCallback("clickablePlainLinks",c)}(),function(){for(var e=document.getElementsByClassName("modal"),t=e.length,n=function(){var n=e[t],a=n.getElementsByClassName("modal__content")[0],l=n.getElementsByClassName("modal__close-btn")[0];document.addEventListener("click",(function(e){n.classList.contains("modal--visible")&&(e.target===a||a.contains(e.target)||(n.classList.remove("modal--visible"),e.preventDefault(),e.stopPropagation()))}),!0),document.addEventListener("keydown",(function(e){n.classList.contains("modal--visible")&&27===e.keyCode&&n.classList.remove("modal--visible")})),l.addEventListener("click",(function(e){return e.preventDefault(),n.classList.remove("modal--visible"),!1}))};t--;)n();var a=document.getElementsByClassName("settings-btn")[0],l=document.getElementsByClassName("modal--settings")[0];a.addEventListener("click",(function(e){return e.preventDefault(),l.classList.add("modal--visible"),!1}))}(); \ No newline at end of file
diff --git a/assets/style.css b/assets/style.css
index 70f53a7..e662925 100644
--- a/assets/style.css
+++ b/assets/style.css
@@ -1 +1 @@
body{margin:0;padding:0;background-color:#14171a;color:#cad1d8;font-family:"Iosevka Term SS03","IBM Plex Mono","Fira Code","Fira Mono","Roboto Mono","Droid Sans Mono",Monaco,Consolas,Courier,monospace;font-size:1.0625em;line-height:1.5}h1,h2,h3,h4,h5,h6{font:inherit;color:#fff;margin:0}button{background:none;border:0;padding:0;color:#fff;font:inherit;text-decoration:underline;cursor:pointer}button:focus{outline:1px dotted currentColor}img{display:inline-block;vertical-align:top;max-width:8em;margin:.1em 0}img::selection{background-color:rgba(239,198,138,0.35)}img.expanded{max-width:40em;max-width:80ch}img.faded{opacity:.5}strong{font-weight:normal}::selection{color:#000;background-color:rgba(239,198,138,0.996)}:link{color:#fff}:visited{color:#cad1d8}:link:hover,:visited:hover{color:#fff}.header-base{display:flex;flex-direction:row;align-items:center;justify-content:space-between;border-bottom:1px solid #353a3f}.header{padding:.9em 1em;color:#929ba3}.location{flex:0 1 auto;overflow:hidden;white-space:nowrap;text-overflow:ellipsis;text-overflow:ellipsis '|';margin-right:.5em}.location__prefix{margin-right:.3em;color:#929ba3;cursor:pointer}@media (hover: hover){.location__prefix:hover{color:#fff}}.location__prefix--mobile{display:none}.location__slash{margin:0 .3em}.location__uripart{color:#fff}.location__uripart+.location__slash+.location__uripart{color:#cad1d8}.location__uripart:link:hover,.location__uripart:visited:hover{color:#fff}.actions{flex:0 0 auto}.actions :visited{color:#fff}.action{display:inline}.action+.action::before{content:' | '}.wrap{padding:2em 1em;text-align:center}.wrap--word-wrap{max-width:50em;max-width:100ch;margin:0 auto}.wrap--word-wrap .content{white-space:pre-wrap;word-wrap:break-word}.content{box-sizing:border-box;display:inline-block;min-width:0;max-width:100%;margin:0;padding:0;text-align:left;font:inherit;font-family:"Iosevka Aile","Fira Sans","Roboto","Droid Sans",sans-serif}.content--has-type-annotations{padding-left:3em;padding-left:5ch}.content--has-monospace-font{font-family:"Iosevka Term SS03","IBM Plex Mono","Fira Code","Fira Mono","Roboto Mono","Droid Sans Mono",Monaco,Consolas,Courier,monospace}.type-annotation{margin-left:-3em;margin-left:-5ch;color:#929ba3;white-space:pre;font-family:"Iosevka Term SS03","IBM Plex Mono","Fira Code","Fira Mono","Roboto Mono","Droid Sans Mono",Monaco,Consolas,Courier,monospace}.modal{position:fixed;top:0;left:0;z-index:100;display:none;width:100%;height:100%;box-sizing:border-box;padding:2em;background-color:rgba(0,0,0,0.75)}.modal--visible{display:block}.modal__content{max-width:30em;padding:1.5em 1.8em;margin:0 auto;background-color:#14171a;box-shadow:0 .3em 2em #000;text-align:left}.modal__head{padding-bottom:.75em;margin-bottom:1.5em}.modal__title{padding-right:1em;white-space:nowrap;text-overflow:ellipsis;overflow:hidden;text-transform:uppercase}.setting{display:flex;flex-direction:row;align-items:baseline;justify-content:space-between}.setting::after{order:2;flex:1 1 auto;display:block;height:0;margin:0 .5em;border-bottom:2px dotted #353a3f;content:''}.setting__label{order:1}.setting__value{order:3}@media screen and (max-width: 800px){body{font-size:1em}.modal{padding:1em}.modal__content{padding:1em 1.3em}}@media screen and (max-width: 500px){.location__prefix{display:none}.location__prefix--mobile{display:inline}.action{display:block}.action+.action::before{content:''}}@media screen and (max-width: 280px){.location__prefix--mobile{display:none}} body{margin:0;padding:0;background-color:#14171a;color:#cad1d8;font-family:"Iosevka Term SS03","IBM Plex Mono","Fira Code","Fira Mono","Roboto Mono","Droid Sans Mono",Monaco,Consolas,Courier,monospace;font-size:1em;line-height:1.5}pre{margin:0;font:inherit;font-family:"Iosevka Term SS03","IBM Plex Mono","Fira Code","Fira Mono","Roboto Mono","Droid Sans Mono",Monaco,Consolas,Courier,monospace;white-space:pre-wrap}h1,h2,h3,h4,h5,h6{font:inherit;font-weight:bold;color:#fff;margin:0}p{margin:0}ul,ol{margin:0;padding:0;list-style-type:none}ul li{padding-left:4ch}ul li::before{display:inline-block;width:2ch;padding-left:2ch;margin-left:-4ch;content:'–'}button{background:none;border:0;padding:0;color:#fff;font:inherit;text-decoration:underline;cursor:pointer}button:focus{outline:1px dotted currentColor}img{display:inline-block;vertical-align:top;max-width:8em;margin:.1em 0}img::selection{background-color:rgba(239,198,138,0.35)}img.expanded{max-width:40em;max-width:80ch}img.faded{opacity:.5}strong{font-weight:normal}::selection{color:#000;background-color:rgba(239,198,138,0.996)}:link{color:#fff}:visited{color:#cad1d8}:link:hover,:visited:hover{color:#fff}.header-base{display:flex;flex-direction:row;align-items:center;justify-content:space-between;border-bottom:1px solid #353a3f}.header{padding:.9em 1em;color:#929ba3}.location{flex:0 1 auto;overflow:hidden;white-space:nowrap;text-overflow:ellipsis;text-overflow:ellipsis '|';margin-right:.5em}.location__prefix{margin-right:.3em;color:#929ba3;cursor:pointer}@media (hover: hover){.location__prefix:hover{color:#fff}}.location__prefix--mobile{display:none}.location__slash{margin:0 .3em}.location__uripart{color:#fff}.location__uripart+.location__slash+.location__uripart{color:#cad1d8}.location__uripart:link:hover,.location__uripart:visited:hover{color:#fff}.actions{flex:0 0 auto}.actions :visited{color:#fff}.action{display:inline}.action+.action::before{content:' | '}.wrap{padding:2em 1em;text-align:center}.wrap--word-wrap{max-width:45em;max-width:90ch;margin:0 auto}.content{box-sizing:border-box;display:inline-block;min-width:0;max-width:100%;margin:0;padding:0;text-align:left;font:inherit;font-family:"Open Sans","Fira Sans","Roboto","Droid Sans",sans-serif}.content--monospace,.content--prefer-monospace{font-family:"Iosevka Term SS03","IBM Plex Mono","Fira Code","Fira Mono","Roboto Mono","Droid Sans Mono",Monaco,Consolas,Courier,monospace}.section{display:flex;align-items:baseline;flex-direction:row;justify-content:flex-start;min-height:1.5em;padding-left:3em;padding-left:5ch}.section__type{flex:0 0 auto;margin-left:-3em;margin-left:-5ch;padding-right:1.25em;padding-right:2ch;width:1.75em;width:3ch;color:#929ba3;font-family:"Iosevka Term SS03","IBM Plex Mono","Fira Code","Fira Mono","Roboto Mono","Droid Sans Mono",Monaco,Consolas,Courier,monospace;text-align:right;user-select:none}.section__content{min-width:0;word-wrap:break-word}.modal{position:fixed;top:0;left:0;z-index:100;display:none;width:100%;height:100%;box-sizing:border-box;padding:2em;background-color:rgba(0,0,0,0.75)}.modal--visible{display:block}.modal__content{max-width:30em;padding:1.5em 1.8em;margin:0 auto;background-color:#14171a;box-shadow:0 .3em 2em #000;text-align:left}.modal__head{padding-bottom:.75em;margin-bottom:1.5em}.modal__title{padding-right:1em;white-space:nowrap;text-overflow:ellipsis;overflow:hidden;text-transform:uppercase}.setting{display:flex;flex-direction:row;align-items:baseline;justify-content:space-between}.setting::after{order:2;flex:1 1 auto;display:block;height:0;margin:0 .5em;border-bottom:2px dotted #353a3f;content:''}.setting__label{order:1}.setting__value{order:3}@media screen and (max-width: 800px){body{font-size:1em}.modal{padding:1em}.modal__content{padding:1em 1.3em}}@media screen and (max-width: 500px){.location__prefix{display:none}.location__prefix--mobile{display:inline}.action{display:block}.action+.action::before{content:''}}@media screen and (max-width: 280px){.location__prefix--mobile{display:none}}
diff --git a/css/main.scss b/css/main.scss
index 42b0b28..27a62ec 100644
--- a/css/main.scss
+++ b/css/main.scss
@@ -11,7 +11,7 @@ $sel-background: rgba($accent, .996);
11$sel-text: #000; 11$sel-text: #000;
12 12
13$font-monospace: 'Iosevka Term SS03', 'IBM Plex Mono', 'Fira Code', 'Fira Mono', 'Roboto Mono', 'Droid Sans Mono', Monaco, Consolas, Courier, monospace; 13$font-monospace: 'Iosevka Term SS03', 'IBM Plex Mono', 'Fira Code', 'Fira Mono', 'Roboto Mono', 'Droid Sans Mono', Monaco, Consolas, Courier, monospace;
14$font-proportional: 'Iosevka Aile', 'Fira Sans', 'Roboto', 'Droid Sans', sans-serif; 14$font-proportional: 'Open Sans', 'Fira Sans', 'Roboto', 'Droid Sans', sans-serif;
15 15
16// 16//
17// Basic element styles 17// Basic element styles
@@ -28,16 +28,48 @@ body {
28 // ); 28 // );
29 color: $text; 29 color: $text;
30 font-family: $font-monospace; 30 font-family: $font-monospace;
31 font-size: 1 / 16 * 17em; 31 font-size: 1em; //1 / 16 * 17em;
32 line-height: 1.5; 32 line-height: 1.5;
33} 33}
34 34
35pre {
36 margin: 0;
37 font: inherit;
38 font-family: $font-monospace;
39 white-space: pre-wrap;
40}
41
35h1, h2, h3, h4, h5, h6 { 42h1, h2, h3, h4, h5, h6 {
36 font: inherit; 43 font: inherit;
44 font-weight: bold;
37 color: $text-plus; 45 color: $text-plus;
38 margin: 0; 46 margin: 0;
39} 47}
40 48
49p {
50 margin: 0;
51}
52
53ul, ol {
54 margin: 0;
55 padding: 0;
56 list-style-type: none;
57}
58
59ul {
60 li {
61 padding-left: 4ch;
62
63 &::before {
64 display: inline-block;
65 width: 2ch;
66 padding-left: 2ch;
67 margin-left: -4ch;
68 content: '–';
69 }
70 }
71}
72
41button { 73button {
42 background: none; 74 background: none;
43 border: 0; 75 border: 0;
@@ -191,14 +223,9 @@ strong {
191 text-align: center; 223 text-align: center;
192 224
193 &--word-wrap { 225 &--word-wrap {
194 max-width: 50em; 226 max-width: 45em;
195 max-width: 100ch; 227 max-width: 90ch;
196 margin: 0 auto; 228 margin: 0 auto;
197
198 .content {
199 white-space: pre-wrap;
200 word-wrap: break-word;
201 }
202 } 229 }
203} 230}
204 231
@@ -213,22 +240,39 @@ strong {
213 font: inherit; 240 font: inherit;
214 font-family: $font-proportional; 241 font-family: $font-proportional;
215 242
216 &--has-type-annotations { 243 &--monospace,
217 padding-left: 3em; 244 &--prefer-monospace {
218 padding-left: 5ch;
219 }
220
221 &--has-monospace-font {
222 font-family: $font-monospace; 245 font-family: $font-monospace;
223 } 246 }
224} 247}
225 248
226.type-annotation { 249.section {
227 margin-left: -3em; 250 display: flex;
228 margin-left: -5ch; 251 align-items: baseline;
229 color: $text-minus; 252 flex-direction: row;
230 white-space: pre; 253 justify-content: flex-start;
231 font-family: $font-monospace; 254 min-height: 1em * 1.5;
255 padding-left: 3em;
256 padding-left: 5ch;
257
258 &__type {
259 flex: 0 0 auto;
260 margin-left: -3em;
261 margin-left: -5ch;
262 padding-right: 1.25em;
263 padding-right: 2ch;
264 width: 1.75em;
265 width: 3ch;
266 color: $text-minus;
267 font-family: $font-monospace;
268 text-align: right;
269 user-select: none;
270 }
271
272 &__content {
273 min-width: 0;
274 word-wrap: break-word;
275 }
232} 276}
233 277
234// 278//
diff --git a/fonts/iosevka-aile-regular.ttf b/fonts/iosevka-aile-regular.ttf
deleted file mode 100644
index 7cad586..0000000
--- a/fonts/iosevka-aile-regular.ttf
+++ /dev/null
Binary files differ
diff --git a/fonts/iosevka-term-ss03-bold.ttf b/fonts/iosevka-term-ss03-bold.ttf
new file mode 100644
index 0000000..940a28f
--- /dev/null
+++ b/fonts/iosevka-term-ss03-bold.ttf
Binary files differ
diff --git a/fonts/iosevka-term-ss03-regular.ttf b/fonts/iosevka-term-ss03-regular.ttf
index 0cde79e..052416d 100644
--- a/fonts/iosevka-term-ss03-regular.ttf
+++ b/fonts/iosevka-term-ss03-regular.ttf
Binary files differ
diff --git a/internal/port/gemini.go b/internal/port/gemini.go
index 0d8292c..740fccd 100644
--- a/internal/port/gemini.go
+++ b/internal/port/gemini.go
@@ -25,6 +25,7 @@ type GeminiTemplateVariables struct {
25 Assets AssetList 25 Assets AssetList
26 Sections []GeminiSection 26 Sections []GeminiSection
27 Nav []GeminiNavItem 27 Nav []GeminiNavItem
28 IsPlain bool
28} 29}
29 30
30type GeminiNavItem struct { 31type GeminiNavItem struct {
@@ -33,7 +34,7 @@ type GeminiNavItem struct {
33} 34}
34 35
35type GeminiSection struct { 36type GeminiSection struct {
36 Type libgemini.GeminiDocSectionType 37 Type string
37 Text string 38 Text string
38 URL template.URL 39 URL template.URL
39 Items []string 40 Items []string
@@ -83,19 +84,19 @@ func parseGeminiDocument(body *bytes.Buffer, uri string, hostport string) (secti
83 for _, section := range unpreppedSections { 84 for _, section := range unpreppedSections {
84 if section.Type != libgemini.LINK { 85 if section.Type != libgemini.LINK {
85 sections = append(sections, GeminiSection{ 86 sections = append(sections, GeminiSection{
86 Type: section.Type, 87 Type: section.Type.String(),
87 Text: section.Text, 88 Text: section.Text,
88 URL: template.URL(section.URL), 89 URL: template.URL(section.URL),
89 Items: section.Items, 90 Items: section.Items,
90 }) 91 })
92 } else {
93 sections = append(sections, GeminiSection{
94 Type: section.Type.String(),
95 Text: section.Text,
96 URL: template.URL(resolveURL(section.URL, baseURL)),
97 Items: section.Items,
98 })
91 } 99 }
92
93 sections = append(sections, GeminiSection{
94 Type: section.Type,
95 Text: section.Text,
96 URL: template.URL(resolveURL(section.URL, baseURL)),
97 Items: section.Items,
98 })
99 } 100 }
100 101
101 return 102 return
@@ -133,9 +134,10 @@ func GeminiHandler(tpl *template.Template, robotsdata *robotstxt.RobotsData, ass
133 URL: hostport, 134 URL: hostport,
134 Assets: assetList, 135 Assets: assetList,
135 Sections: []GeminiSection{{ 136 Sections: []GeminiSection{{
136 Type: libgemini.RAW_TEXT, 137 Type: libgemini.RAW_TEXT.String(),
137 Text: fmt.Sprintf("Error: %s", err), 138 Text: fmt.Sprintf("Error: %s", err),
138 }}, 139 }},
140 IsPlain: true,
139 }); e != nil { 141 }); e != nil {
140 log.Println("Template error: " + e.Error()) 142 log.Println("Template error: " + e.Error())
141 log.Println(err.Error()) 143 log.Println(err.Error())
@@ -162,9 +164,10 @@ func GeminiHandler(tpl *template.Template, robotsdata *robotstxt.RobotsData, ass
162 URL: fmt.Sprintf("%s/%s", hostport, uri), 164 URL: fmt.Sprintf("%s/%s", hostport, uri),
163 Assets: assetList, 165 Assets: assetList,
164 Sections: []GeminiSection{{ 166 Sections: []GeminiSection{{
165 Type: libgemini.RAW_TEXT, 167 Type: libgemini.RAW_TEXT.String(),
166 Text: fmt.Sprintf("Error: %s", err), 168 Text: fmt.Sprintf("Error: %s", err),
167 }}, 169 }},
170 IsPlain: true,
168 }); e != nil { 171 }); e != nil {
169 log.Println("Template error: " + e.Error()) 172 log.Println("Template error: " + e.Error())
170 log.Println(err.Error()) 173 log.Println(err.Error())
@@ -184,9 +187,10 @@ func GeminiHandler(tpl *template.Template, robotsdata *robotstxt.RobotsData, ass
184 URL: fmt.Sprintf("%s/%s", hostport, uri), 187 URL: fmt.Sprintf("%s/%s", hostport, uri),
185 Assets: assetList, 188 Assets: assetList,
186 Sections: []GeminiSection{{ 189 Sections: []GeminiSection{{
187 Type: libgemini.RAW_TEXT, 190 Type: libgemini.RAW_TEXT.String(),
188 Text: fmt.Sprintf("Error: %s", err), 191 Text: fmt.Sprintf("Error: %s", err),
189 }}, 192 }},
193 IsPlain: true,
190 }); e != nil { 194 }); e != nil {
191 log.Println("Template error: " + e.Error()) 195 log.Println("Template error: " + e.Error())
192 log.Println(err.Error()) 196 log.Println(err.Error())
@@ -204,16 +208,17 @@ func GeminiHandler(tpl *template.Template, robotsdata *robotstxt.RobotsData, ass
204 URL: fmt.Sprintf("%s/%s", hostport, uri), 208 URL: fmt.Sprintf("%s/%s", hostport, uri),
205 Assets: assetList, 209 Assets: assetList,
206 Sections: []GeminiSection{{ 210 Sections: []GeminiSection{{
207 Type: libgemini.RAW_TEXT, 211 Type: libgemini.RAW_TEXT.String(),
208 Text: fmt.Sprintf("Error %d: %s", res.Header.Status, res.Header.Meta), 212 Text: fmt.Sprintf("Error %d: %s", res.Header.Status, res.Header.Meta),
209 }}, 213 }},
214 IsPlain: true,
210 }); err != nil { 215 }); err != nil {
211 log.Println("Template error: " + err.Error()) 216 log.Println("Template error: " + err.Error())
212 } 217 }
213 return 218 return
214 } 219 }
215 220
216 if strings.HasPrefix(res.Header.Meta, "text/") { 221 if strings.HasPrefix(res.Header.Meta, "text/") && !strings.HasPrefix(res.Header.Meta, "text/html") && !strings.HasPrefix(res.Header.Meta, "text/css") {
217 buf := new(bytes.Buffer) 222 buf := new(bytes.Buffer)
218 223
219 _, params, err := mime.ParseMediaType(res.Header.Meta) 224 _, params, err := mime.ParseMediaType(res.Header.Meta)
@@ -230,12 +235,14 @@ func GeminiHandler(tpl *template.Template, robotsdata *robotstxt.RobotsData, ass
230 } 235 }
231 236
232 var sections []GeminiSection 237 var sections []GeminiSection
238 isPlain := true
233 239
234 if strings.HasPrefix(res.Header.Meta, libgemini.MIME_GEMINI) { 240 if strings.HasPrefix(res.Header.Meta, libgemini.MIME_GEMINI) {
235 sections = parseGeminiDocument(buf, uri, hostport) 241 sections = parseGeminiDocument(buf, uri, hostport)
242 isPlain = false
236 } else { 243 } else {
237 sections = append(sections, GeminiSection{ 244 sections = append(sections, GeminiSection{
238 Type: libgemini.RAW_TEXT, 245 Type: libgemini.RAW_TEXT.String(),
239 Text: buf.String(), 246 Text: buf.String(),
240 }) 247 })
241 } 248 }
@@ -245,6 +252,7 @@ func GeminiHandler(tpl *template.Template, robotsdata *robotstxt.RobotsData, ass
245 URL: fmt.Sprintf("%s/%s", hostport, uri), 252 URL: fmt.Sprintf("%s/%s", hostport, uri),
246 Assets: assetList, 253 Assets: assetList,
247 Sections: sections, 254 Sections: sections,
255 IsPlain: isPlain,
248 }); err != nil { 256 }); err != nil {
249 log.Println("Template error: " + err.Error()) 257 log.Println("Template error: " + err.Error())
250 } 258 }
diff --git a/internal/port/main.go b/internal/port/main.go
index 267df44..763057b 100644
--- a/internal/port/main.go
+++ b/internal/port/main.go
@@ -18,12 +18,12 @@ import (
18) 18)
19 19
20type AssetList struct { 20type AssetList struct {
21 Style string 21 Style string
22 JS string 22 JS string
23 FontW string 23 FontRegularW string
24 FontW2 string 24 FontRegularW2 string
25 PropFontW string 25 FontBoldW string
26 PropFontW2 string 26 FontBoldW2 string
27} 27}
28 28
29type startTemplateVariables struct { 29type startTemplateVariables struct {
@@ -136,29 +136,29 @@ func ListenAndServe(bind, startpagefile string, robotsfile string, robotsdebug b
136 // 136 //
137 // Fonts 137 // Fonts
138 138
139 fontdataw, err := box.Find("iosevka-term-ss03-regular.woff") 139 fontRegularWData, err := box.Find("iosevka-fixed-ss03-regular.woff")
140 if err != nil { 140 if err != nil {
141 fontdataw = []byte{} 141 fontRegularWData = []byte{}
142 } 142 }
143 fontwAsset := fmt.Sprintf("/iosevka-term-ss03-regular-%x.woff", md5.Sum(fontdataw)) 143 fontRegularWAsset := fmt.Sprintf("/iosevka-fixed-ss03-regular-%x.woff", md5.Sum(fontRegularWData))
144 144
145 fontdataw2, err := box.Find("iosevka-term-ss03-regular.woff2") 145 fontRegularW2Data, err := box.Find("iosevka-fixed-ss03-regular.woff2")
146 if err != nil { 146 if err != nil {
147 fontdataw2 = []byte{} 147 fontRegularW2Data = []byte{}
148 } 148 }
149 fontw2Asset := fmt.Sprintf("/iosevka-term-ss03-regular-%x.woff2", md5.Sum(fontdataw2)) 149 fontRegularW2Asset := fmt.Sprintf("/iosevka-fixed-ss03-regular-%x.woff2", md5.Sum(fontRegularW2Data))
150 150
151 propfontdataw, err := box.Find("iosevka-aile-regular.woff") 151 fontBoldWData, err := box.Find("iosevka-fixed-ss03-bold.woff")
152 if err != nil { 152 if err != nil {
153 propfontdataw = []byte{} 153 fontBoldWData = []byte{}
154 } 154 }
155 propfontwAsset := fmt.Sprintf("/iosevka-aile-regular-%x.woff", md5.Sum(propfontdataw)) 155 fontBoldWAsset := fmt.Sprintf("/iosevka-fixed-ss03-bold-%x.woff", md5.Sum(fontBoldWData))
156 156
157 propfontdataw2, err := box.Find("iosevka-aile-regular.woff2") 157 fontBoldW2Data, err := box.Find("iosevka-fixed-ss03-bold.woff2")
158 if err != nil { 158 if err != nil {
159 propfontdataw2 = []byte{} 159 fontBoldW2Data = []byte{}
160 } 160 }
161 propfontw2Asset := fmt.Sprintf("/iosevka-aile-regular-%x.woff2", md5.Sum(propfontdataw2)) 161 fontBoldW2Asset := fmt.Sprintf("/iosevka-fixed-ss03-bold-%x.woff2", md5.Sum(fontBoldW2Data))
162 162
163 // 163 //
164 // Stylesheet 164 // Stylesheet
@@ -277,12 +277,12 @@ func ListenAndServe(bind, startpagefile string, robotsfile string, robotsdebug b
277 }) 277 })
278 278
279 assets := AssetList{ 279 assets := AssetList{
280 Style: styleAsset, 280 Style: styleAsset,
281 JS: jsAsset, 281 JS: jsAsset,
282 FontW: fontwAsset, 282 FontRegularW: fontRegularWAsset,
283 FontW2: fontw2Asset, 283 FontRegularW2: fontRegularW2Asset,
284 PropFontW: propfontwAsset, 284 FontBoldW: fontBoldWAsset,
285 PropFontW2: propfontw2Asset, 285 FontBoldW2: fontBoldW2Asset,
286 } 286 }
287 287
288 http.Handle("/", gziphandler.GzipHandler(DefaultHandler(startpageTpl, startpagetext, assets))) 288 http.Handle("/", gziphandler.GzipHandler(DefaultHandler(startpageTpl, startpagetext, assets)))
@@ -292,10 +292,10 @@ func ListenAndServe(bind, startpagefile string, robotsfile string, robotsdebug b
292 http.Handle("/favicon.ico", gziphandler.GzipHandler(FaviconHandler(favicondata))) 292 http.Handle("/favicon.ico", gziphandler.GzipHandler(FaviconHandler(favicondata)))
293 http.Handle(styleAsset, gziphandler.GzipHandler(StyleHandler(styledata))) 293 http.Handle(styleAsset, gziphandler.GzipHandler(StyleHandler(styledata)))
294 http.Handle(jsAsset, gziphandler.GzipHandler(JavaScriptHandler(jsdata))) 294 http.Handle(jsAsset, gziphandler.GzipHandler(JavaScriptHandler(jsdata)))
295 http.HandleFunc(fontwAsset, FontHandler(false, fontdataw)) 295 http.HandleFunc(fontRegularWAsset, FontHandler(false, fontRegularWData))
296 http.HandleFunc(fontw2Asset, FontHandler(true, fontdataw2)) 296 http.HandleFunc(fontRegularW2Asset, FontHandler(true, fontRegularW2Data))
297 http.HandleFunc(propfontwAsset, FontHandler(false, propfontdataw)) 297 http.HandleFunc(fontBoldWAsset, FontHandler(false, fontBoldWData))
298 http.HandleFunc(propfontw2Asset, FontHandler(true, propfontdataw2)) 298 http.HandleFunc(fontBoldW2Asset, FontHandler(true, fontBoldW2Data))
299 //http.Handle("/assets/", http.StripPrefix("/assets/", http.FileServer(http.Dir("assets/")))) 299 //http.Handle("/assets/", http.StripPrefix("/assets/", http.FileServer(http.Dir("assets/"))))
300 300
301 return http.ListenAndServe(bind, nil) 301 return http.ListenAndServe(bind, nil)
diff --git a/internal/port/tpl/_fonts.html b/internal/port/tpl/_fonts.html
index b56aa22..a947222 100644
--- a/internal/port/tpl/_fonts.html
+++ b/internal/port/tpl/_fonts.html
@@ -3,14 +3,14 @@
3 font-family: 'Iosevka Term SS03'; 3 font-family: 'Iosevka Term SS03';
4 font-style: normal; 4 font-style: normal;
5 font-weight: normal; 5 font-weight: normal;
6 src: url('{{ .Assets.FontW2 }}') format('woff2'), 6 src: url('{{ .Assets.FontRegularW2 }}') format('woff2'),
7 url('{{ .Assets.FontW }}') format('woff'); 7 url('{{ .Assets.FontRegularW }}') format('woff');
8 } 8 }
9 @font-face { 9 @font-face {
10 font-family: 'Iosevka Aile'; 10 font-family: 'Iosevka Term SS03';
11 font-style: normal; 11 font-style: normal;
12 font-weight: normal; 12 font-weight: bold;
13 src: url('{{ .Assets.PropFontW2 }}') format('woff2'), 13 src: url('{{ .Assets.FontBoldW2 }}') format('woff2'),
14 url('{{ .Assets.PropFontW }}') format('woff'); 14 url('{{ .Assets.FontBoldW }}') format('woff');
15 } 15 }
16</style> 16</style>
diff --git a/internal/port/tpl/_header.html b/internal/port/tpl/_header.html
deleted file mode 100644
index 5bcd254..0000000
--- a/internal/port/tpl/_header.html
+++ /dev/null
@@ -1,48 +0,0 @@
1<header class="header header-base">
2 <div class="location">
3 <a class="location__prefix">{{ .Protocol }}://</a><a class="location__prefix location__prefix--mobile">://</a>
4
5 {{- if .URL -}}
6 {{- $page := . -}}
7 {{- $href := printf "/%s" .Protocol -}}
8 {{- $uriParts := split .URL "/" -}}
9
10 {{- $uriLast := $uriParts | last -}}
11 {{- $uriParts = $uriParts | pop -}}
12 {{- if eq $uriLast "" -}}
13 {{- $uriLast = $uriParts | last -}}
14 {{- $uriParts = $uriParts | pop -}}
15 {{- end -}}
16
17 {{- range $i, $part := $uriParts -}}
18 {{- if and (eq $page.Protocol "gopher") (eq $i 1) -}}
19 {{- $href = printf "%s/1" $href -}}
20 {{- $part = $part | trimLeftChar -}}
21 {{- if not (eq $part "") -}}
22 {{- $href = printf "%s/%s" $href $part -}}
23 <span class="location__slash">/</span><a href="{{ $href }}/" class="location__uripart">{{ $part }}</a>
24 {{- end -}}
25 {{- else -}}
26 {{- $href = printf "%s/%s" $href . -}}
27 {{- if ne $i 0 -}}
28 <span class="location__slash">/</span>
29 {{- end -}}
30 <a href="{{ $href }}/" class="location__uripart">{{ . }}</a>
31 {{- end -}}
32 {{- end -}}
33 {{- if ne (len $uriParts) 0 -}}
34 <span class="location__slash">/</span>
35 {{- end -}}
36 {{- if and (eq $page.Protocol "gopher") (eq (len $uriParts) 1) -}}
37 {{- $uriLast = $uriLast | trimLeftChar -}}
38 {{- end -}}
39 <span class="location__uripart">{{ $uriLast }}</span>
40 {{- end -}}
41 </div>
42 <div class="actions">
43 {{- if and (not .Lines) (not .Error) (eq .Protocol "gopher") -}}
44 <div class="action"><a href="/gopher/{{ .URL | replace "^([^/]*)/0" "$1/9" }}">View raw</a></div>
45 {{- end -}}
46 <div class="action"><button class="settings-btn">Settings</button></div>
47 </div>
48</header>
diff --git a/internal/port/tpl/_modals.html b/internal/port/tpl/_modals.html
index 3c08d9a..3bbdef2 100644
--- a/internal/port/tpl/_modals.html
+++ b/internal/port/tpl/_modals.html
@@ -9,7 +9,7 @@
9 <button class="setting__value">[N/A]</button> 9 <button class="setting__value">[N/A]</button>
10 </div> 10 </div>
11 <div class="setting setting--monospace-font"> 11 <div class="setting setting--monospace-font">
12 <strong class="setting__label">Monospace font</strong> 12 <strong class="setting__label">Gemini: Monospace font</strong>
13 <button class="setting__value">[N/A]</button> 13 <button class="setting__value">[N/A]</button>
14 </div> 14 </div>
15 <div class="setting setting--image-previews"> 15 <div class="setting setting--image-previews">
diff --git a/internal/port/tpl/gemini.html b/internal/port/tpl/gemini.html
index 08f1b8e..df50d50 100644
--- a/internal/port/tpl/gemini.html
+++ b/internal/port/tpl/gemini.html
@@ -7,28 +7,50 @@
7 <link rel="stylesheet" href="{{ .Assets.Style }}" /> 7 <link rel="stylesheet" href="{{ .Assets.Style }}" />
8 {{- template "_fonts.html" . -}} 8 {{- template "_fonts.html" . -}}
9 </head> 9 </head>
10 <body class="{{ if not .Lines }}is-plain{{ end }}"> 10 <body class="{{ if .IsPlain }}is-plain{{ end }}">
11 {{- template "_header.html" . -}} 11 <header class="header header-base">
12 <div class="location">
13 <a class="location__prefix">gemini://</a><a class="location__prefix location__prefix--mobile">://</a>
14 {{- range $i, $item := .Nav -}}
15 {{- if ne $i 0 -}}
16 <span class="location__slash">/</span>
17 {{- end -}}
18 {{- if .Current -}}
19 <span class="location__uripart">{{ .Label }}</span>
20 {{- else -}}
21 <a href="{{ .URL }}/" class="location__uripart">{{ .Label }}</a>
22 {{- end -}}
23 {{- end -}}
24 </div>
25 <div class="actions">
26 <div class="action"><button class="settings-btn">Settings</button></div>
27 </div>
28 </header>
12 29
13 <main class="wrap"> 30 <main class="wrap">
14 <pre class="content content--has-monospace-font{{ if .Lines }} content--has-type-annotations{{ end }}"> 31 <div class="content{{ if not .IsPlain }} content--has-type-annotations{{ end }}">
15 {{- if .Lines -}} 32 {{- range .Sections -}}
16 {{- $content := "" -}} 33 {{- if eq .Type "RAW_TEXT" -}}
17 {{- range .Lines -}} 34 <div class="section"><span class="section__type">```</span><pre class="section__content">{{- .Text -}}</pre></div>
18 {{- if ne $content "" -}} 35 {{- else if eq .Type "REFLOW_TEXT" -}}
19 {{- $content = printf "%s\n" $content -}} 36 <div class="section"><p class="section__content">{{- .Text -}}</p></div>
20 {{- end -}} 37 {{- else if eq .Type "LINK" -}}
21 {{- if .Link -}} 38 <div class="section"><span class="section__type">=></span><a class="section__content" href="{{ .URL }}">{{- .Text -}}</a></div>
22 {{- $content = printf "%s%s" $content (printf "<span class=\"type-annotation\">%s </span><a class=\"link link--%s\" href=\"%s\">%s</a>" .Type .Type .Link (.Text | HTMLEscape)) -}} 39 {{- else if eq .Type "HEADING_1" -}}
23 {{- else -}} 40 <div class="section"><span class="section__type">#</span><h1 class="section__content">{{- .Text -}}</h1></div>
24 {{- $content = printf "%s%s" $content (printf "<span class=\"type-annotation\"> </span>%s" (.Text | HTMLEscape)) -}} 41 {{- else if eq .Type "HEADING_2" -}}
25 {{- end -}} 42 <div class="section"><span class="section__type">##</span><h2 class="section__content">{{- .Text -}}</h2></div>
26 {{- end -}} 43 {{- else if eq .Type "HEADING_3" -}}
27 {{- $content | safeHtml -}} 44 <div class="section"><span class="section__type">###</span><h3 class="section__content">{{- .Text -}}</h3></div>
28 {{- else -}} 45 {{- else if eq .Type "LIST" -}}
29 {{- .RawText -}} 46 <div class="section"><ul class="section__content">
30 {{- end -}} 47 {{- range .Items -}}
31 </pre> 48 <li>{{- . -}}</li>
49 {{- end -}}
50 </ul></div>
51 {{- end -}}
52 {{- end -}}
53 </div>
32 </main> 54 </main>
33 55
34 {{- template "_modals.html" . -}} 56 {{- template "_modals.html" . -}}
diff --git a/internal/port/tpl/gopher.html b/internal/port/tpl/gopher.html
index c971847..5436123 100644
--- a/internal/port/tpl/gopher.html
+++ b/internal/port/tpl/gopher.html
@@ -31,25 +31,16 @@
31 </header> 31 </header>
32 32
33 <main class="wrap"> 33 <main class="wrap">
34 <pre class="content content--has-monospace-font{{ if not .IsPlain }} content--has-type-annotations{{ end }}"> 34 <div class="content content--monospace{{ if not .IsPlain }} content--has-type-annotations{{ end }}">
35 {{- $content := "" -}}
36 {{- $page := . -}} 35 {{- $page := . -}}
37 {{- range .Lines -}} 36 {{- range .Lines -}}
38 {{- if ne $content "" -}} 37 {{- if .Link -}}
39 {{- $content = printf "%s\n" $content -}} 38 <div class="section"><span class="section__type">{{- .Type -}}</span><a class="section__content" href="{{ .Link }}">{{- .Text -}}</a></div>
40 {{- end -}}
41 {{- if $page.IsPlain -}}
42 {{- $content = printf "%s%s" $content (.Text | HTMLEscape) -}}
43 {{- else -}} 39 {{- else -}}
44 {{- if .Link -}} 40 <div class="section"><span class="section__type"></span><pre class="section__content">{{- .Text -}}</pre></div>
45 {{- $content = printf "%s%s" $content (printf "<span class=\"type-annotation\">%s </span><a class=\"link link--%s\" href=\"%s\">%s</a>" .Type .Type .Link (.Text | HTMLEscape)) -}}
46 {{- else -}}
47 {{- $content = printf "%s%s" $content (printf "<span class=\"type-annotation\"> </span>%s" (.Text | HTMLEscape)) -}}
48 {{- end -}}
49 {{- end -}} 41 {{- end -}}
50 {{- end -}} 42 {{- end -}}
51 {{- $content | safeHtml -}} 43 </div>
52 </pre>
53 </main> 44 </main>
54 45
55 {{- template "_modals.html" . -}} 46 {{- template "_modals.html" . -}}
diff --git a/js/main.ts b/js/main.ts
index 2d11ea4..031e959 100644
--- a/js/main.ts
+++ b/js/main.ts
@@ -128,9 +128,9 @@ const settings = new KeyValueStore({
128 const settingMonospaceFontValueEl = settingMonospaceFontEl.getElementsByClassName('setting__value')[0]; 128 const settingMonospaceFontValueEl = settingMonospaceFontEl.getElementsByClassName('setting__value')[0];
129 const settingMonospaceFontCallback = (value: boolean) => { 129 const settingMonospaceFontCallback = (value: boolean) => {
130 if (value) { 130 if (value) {
131 contentEl.classList.add("content--has-monospace-font"); 131 contentEl.classList.add("content--prefer-monospace");
132 } else { 132 } else {
133 contentEl.classList.remove("content--has-monospace-font"); 133 contentEl.classList.remove("content--prefer-monospace");
134 } 134 }
135 135
136 settingMonospaceFontValueEl.textContent = value ? '[yes]' : '[no]'; 136 settingMonospaceFontValueEl.textContent = value ? '[yes]' : '[no]';
diff --git a/pkg/libgemini/libgemini.go b/pkg/libgemini/libgemini.go
index 48a8ed0..5e37490 100644
--- a/pkg/libgemini/libgemini.go
+++ b/pkg/libgemini/libgemini.go
@@ -53,10 +53,10 @@ var (
53 HeaderPattern = regexp.MustCompile("^(\\d\\d)[ \\t]+(.*)$") 53 HeaderPattern = regexp.MustCompile("^(\\d\\d)[ \\t]+(.*)$")
54 LinkPattern = regexp.MustCompile("^=>[ \\t]*([^ \\t]+)(?:[ \\t]+(.*))?$") 54 LinkPattern = regexp.MustCompile("^=>[ \\t]*([^ \\t]+)(?:[ \\t]+(.*))?$")
55 ReflowModePattern = regexp.MustCompile("^```(.*)$") 55 ReflowModePattern = regexp.MustCompile("^```(.*)$")
56 Heading1Pattern = regexp.MustCompile("^#(.*)$") 56 Heading1Pattern = regexp.MustCompile("^# *(.*)$")
57 Heading2Pattern = regexp.MustCompile("^##(.*)$") 57 Heading2Pattern = regexp.MustCompile("^## *(.*)$")
58 Heading3Pattern = regexp.MustCompile("^###(.*)$") 58 Heading3Pattern = regexp.MustCompile("^### *(.*)$")
59 ListItemPattern = regexp.MustCompile("^\\*(.*)$") 59 ListItemPattern = regexp.MustCompile("^\\* *(.*)$")
60 TermEscapeSGRPattern = regexp.MustCompile("\\[\\d+(;\\d+)*m") 60 TermEscapeSGRPattern = regexp.MustCompile("\\[\\d+(;\\d+)*m")
61) 61)
62 62
@@ -82,6 +82,27 @@ const (
82 LIST = GeminiDocSectionType(6) 82 LIST = GeminiDocSectionType(6)
83) 83)
84 84
85func (it GeminiDocSectionType) String() string {
86 switch it {
87 case RAW_TEXT:
88 return "RAW_TEXT"
89 case REFLOW_TEXT:
90 return "REFLOW_TEXT"
91 case LINK:
92 return "LINK"
93 case HEADING_1:
94 return "HEADING_1"
95 case HEADING_2:
96 return "HEADING_2"
97 case HEADING_3:
98 return "HEADING_3"
99 case LIST:
100 return "LIST"
101 default:
102 return "???"
103 }
104}
105
85type GeminiDocSection struct { 106type GeminiDocSection struct {
86 Type GeminiDocSectionType 107 Type GeminiDocSectionType
87 Text string 108 Text string
@@ -191,20 +212,13 @@ func ParseGeminiDocument(body *bytes.Buffer) (sections []GeminiDocSection) {
191 212
192 if !reflow { 213 if !reflow {
193 if !ignoreSection { 214 if !ignoreSection {
194 if section.Type != RAW_TEXT { 215 sections = append(sections, section)
195 sections = append(sections, section)
196 section = GeminiDocSection{
197 Type: RAW_TEXT,
198 }
199 }
200 } else {
201 ignoreSection = false
202 section = GeminiDocSection{
203 Type: RAW_TEXT,
204 }
205 } 216 }
206 217
207 section.Text = section.Text + "\n" + line 218 section = GeminiDocSection{
219 Type: RAW_TEXT,
220 Text: line,
221 }
208 222
209 continue 223 continue
210 } 224 }
@@ -297,20 +311,14 @@ func ParseGeminiDocument(body *bytes.Buffer) (sections []GeminiDocSection) {
297 } 311 }
298 312
299 if !ignoreSection { 313 if !ignoreSection {
300 if section.Type != REFLOW_TEXT { 314 sections = append(sections, section)
301 sections = append(sections, section)
302 section = GeminiDocSection{
303 Type: REFLOW_TEXT,
304 }
305 }
306 } else {
307 ignoreSection = false
308 section = GeminiDocSection{
309 Type: REFLOW_TEXT,
310 }
311 } 315 }
312 316
313 section.Text = section.Text + "\n" + line 317 ignoreSection = false
318 section = GeminiDocSection{
319 Type: REFLOW_TEXT,
320 Text: line,
321 }
314 } 322 }
315 323
316 if !ignoreSection { 324 if !ignoreSection {