aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--Makefile2
-rw-r--r--assets/icons.svg0
-rw-r--r--assets/main.js40
-rw-r--r--assets/style.css2
-rw-r--r--css/main.scss203
-rw-r--r--js/KeyValueStore.ts89
-rw-r--r--js/main.ts191
-rw-r--r--template.go68
8 files changed, 473 insertions, 122 deletions
diff --git a/Makefile b/Makefile
index da35555..fa3998e 100644
--- a/Makefile
+++ b/Makefile
@@ -7,7 +7,7 @@ dev: build
7 7
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 --outFile assets/main.js js/main.ts 10 tsc --strict --module none --outFile /dev/stdout js/KeyValueStore.ts js/main.ts | uglifyjs -c -m -o assets/main.js --
11 pyftsubset fonts/iosevka-term-ss03-regular.ttf --name-IDs+=0,4,6 --text-file=glyphs.txt --flavor='woff' --output-file='assets/iosevka-term-ss03-regular.woff' 11 pyftsubset fonts/iosevka-term-ss03-regular.ttf --name-IDs+=0,4,6 --text-file=glyphs.txt --flavor='woff' --output-file='assets/iosevka-term-ss03-regular.woff'
12 pyftsubset fonts/iosevka-term-ss03-regular.ttf --name-IDs+=0,4,6 --text-file=glyphs.txt --flavor='woff2' --output-file='assets/iosevka-term-ss03-regular.woff2' 12 pyftsubset fonts/iosevka-term-ss03-regular.ttf --name-IDs+=0,4,6 --text-file=glyphs.txt --flavor='woff2' --output-file='assets/iosevka-term-ss03-regular.woff2'
13 go build -o ./gopherproxy ./cmd/gopherproxy/main.go 13 go build -o ./gopherproxy ./cmd/gopherproxy/main.go
diff --git a/assets/icons.svg b/assets/icons.svg
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/assets/icons.svg
diff --git a/assets/main.js b/assets/main.js
index 46cb7ee..cbabeef 100644
--- a/assets/main.js
+++ b/assets/main.js
@@ -1,39 +1 @@
1"use strict"; "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({imagePreviews:{value:"1"===ensureSetting("image-previews","1"),valueRange:[!1,!0]}});function generateImageThumbnails(){for(var i=document.querySelectorAll(".link--IMG, .link--GIF"),s=i.length,e=function(){var e=i[s],t=e.href.replace(/^(.*?)\/I/,"$1/T"),n=document.createTextNode("\n"),a=document.createElement("span");a.classList.add("type-annotation"),a.textContent=" -> ";var l=document.createElement("img");l.src=t,l.addEventListener("load",function(e){l.classList.remove("faded")});var r=document.createElement("a");r.classList.add("img-preview"),r.href=e.href,r.addEventListener("click",function(e){return e.preventDefault(),l.classList.add("faded"),l.classList.contains("expanded")?(l.classList.remove("expanded"),l.src=t):(l.classList.add("expanded"),l.src=r.href),!1}),r.append(l),e.parentNode.insertBefore(r,e.nextSibling),e.parentNode.insertBefore(a,r),e.parentNode.insertBefore(n,a)};s--;)e()}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(){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})}(),document.getElementsByClassName("location__prefix")[0].addEventListener("click",function(e){e.preventDefault();var t=prompt("Please enter new location: ","");return null!==t&&""!==t&&(window.location.href=window.location.origin+"/"+t),!1}),function(){function e(e,t){void 0===t&&(t=!1),e?generateImageThumbnails():t||removeImageThumbnails(),localStorage.setItem("image-previews",e?"1":"0"),n.textContent=e?"[yes]":"[no]"}var n=document.getElementsByClassName("setting--image-previews")[0].getElementsByClassName("setting__value")[0];n.addEventListener("click",function(e){return e.preventDefault(),settings.cycleValue("imagePreviews"),!1}),e(settings.getValue("imagePreviews"),!0),settings.addCallback("imagePreviews",e)}(),function(){for(var a=document.getElementsByClassName("modal"),l=a.length,e=function(){var t=a[l],n=t.getElementsByClassName("modal__content")[0],e=t.getElementsByClassName("modal__close-btn")[0];document.addEventListener("click",function(e){t.classList.contains("modal--visible")&&(e.target===n||n.contains(e.target)||(t.classList.remove("modal--visible"),e.preventDefault(),e.stopPropagation()))},!0),document.addEventListener("keydown",function(e){t.classList.contains("modal--visible")&&27===e.keyCode&&t.classList.remove("modal--visible")}),e.addEventListener("click",function(e){return e.preventDefault(),t.classList.remove("modal--visible"),!1})};l--;)e();var t=document.getElementsByClassName("settings-btn")[0],n=document.getElementsByClassName("modal--settings")[0];t.addEventListener("click",function(e){return e.preventDefault(),n.classList.add("modal--visible"),!1})}(); \ No newline at end of file
2var linkQryEls = document.getElementsByClassName('link--QRY');
3var i = linkQryEls.length;
4while (i--) {
5 linkQryEls[i].addEventListener('click', function (e) {
6 e.preventDefault();
7 var resp = prompt('Please enter required input: ', '');
8 if ((resp !== null) && (resp !== "")) {
9 window.location.href = e.target.href + '?' + resp;
10 }
11 return false;
12 });
13}
14var imgPreviewEls = document.getElementsByClassName('img-preview');
15i = imgPreviewEls.length;
16var _loop_1 = function () {
17 var imgPreviewEl = imgPreviewEls[i];
18 var child = imgPreviewEl.children[0];
19 var thumbnailUrl = child.src;
20 child.addEventListener('load', function (e) {
21 child.classList.remove('faded');
22 });
23 imgPreviewEls[i].addEventListener('click', function (e) {
24 e.preventDefault();
25 child.classList.add('faded');
26 if (child.classList.contains('expanded')) {
27 child.classList.remove('expanded');
28 child.src = thumbnailUrl;
29 }
30 else {
31 child.classList.add('expanded');
32 child.src = imgPreviewEl.href;
33 }
34 return false;
35 });
36};
37while (i--) {
38 _loop_1();
39}
diff --git a/assets/style.css b/assets/style.css
index 46ad8cb..3ab3b36 100644
--- a/assets/style.css
+++ b/assets/style.css
@@ -1 +1 @@
@font-face{font-family:'Iosevka Term SS03';font-style:normal;font-weight:normal;src:url("/iosevka-term-ss03-regular.woff2") format("woff2"),url("/iosevka-term-ss03-regular.woff") format("woff")}body{margin:0;padding:0;background-color:#14171a;color:#cad1d8}button{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;background:none;border:0;padding:0;color:#fff}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}::selection{color:#000;background-color:rgba(239,198,138,0.996)}:link{color:#fff}:visited{color:#cad1d8}.header{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;padding:.9em 1em;border-bottom:1px solid #353a3f;line-height:1.3;color:#686f76}.header__uripart{color:#929ba3}.header__uripart--last{color:#fff}.wrap{text-align:center}.content{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;display:inline-block;min-width:50em;min-width:85ch;margin:0;padding:2em 1em;text-align:left}.type-annotation{color:#929ba3} @font-face{font-family:'Iosevka Term SS03';font-style:normal;font-weight:normal;src:url("/iosevka-term-ss03-regular.woff2") format("woff2"),url("/iosevka-term-ss03-regular.woff") format("woff")}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}.novisit{color:#fff}.header-base{display:flex;flex-flow:row wrap;align-items:center;justify-content:space-between}.header-base::after{display:block;width:100%;white-space:nowrap;overflow:hidden;color:#5d646a;content:'----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------'}.header{padding:.9em 1em 0;color:#929ba3}.location{color:#5d646a}.location__prefix{color:#5d646a}.location__prefix:hover{color:#fff}.location__uripart{color:#fff}.location__uripart+.location__slash+.location__uripart{color:#cad1d8}.location__uripart+.location__slash+.location__uripart:hover{color:#fff}.wrap{text-align:center}.content{display:inline-block;min-width:50em;min-width:85ch;margin:0;padding:2em 1em;text-align:left;font:inherit}.type-annotation{color:#929ba3}.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{margin-bottom:1.5em}.modal__title{padding-right:1em;white-space:nowrap;text-overflow:ellipsis;overflow:hidden}.setting{display:flex;flex-direction:row;align-items:center;justify-content:space-between}.setting__label{white-space:nowrap;overflow:hidden;margin-right:.5em}.setting__label::after{color:#5d646a;content:' . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .'}#none{position:fixed;top:0;left:0;width:0;height:0}@media screen and (max-width: 800px){.location__uripart ~ *{display:none}.modal{padding:1em}.modal__content{padding:1em 1.3em}}
diff --git a/css/main.scss b/css/main.scss
index 0d5a5d9..6334f42 100644
--- a/css/main.scss
+++ b/css/main.scss
@@ -2,7 +2,7 @@ $accent: scale-color(#e8ad58, $lightness: 30%);
2$background: hsl(210, 14%, 9%); 2$background: hsl(210, 14%, 9%);
3$text: mix(hsl(210, 60%, 95%), $background, 85%); 3$text: mix(hsl(210, 60%, 95%), $background, 85%);
4$text-minus: mix(hsl(210, 100%, 95%), $background, 60%); 4$text-minus: mix(hsl(210, 100%, 95%), $background, 60%);
5$text-faint: mix(hsl(210, 100%, 95%), $background, 40%); 5$text-faint: mix(hsl(210, 100%, 95%), $background, 35%);
6$text-plus: #fff; 6$text-plus: #fff;
7$link-idle: $text-plus; 7$link-idle: $text-plus;
8$link-visited: $text; 8$link-visited: $text;
@@ -10,11 +10,7 @@ $border: mix(hsl(210, 100%, 95%), $background, 16%);
10$sel-background: rgba($accent, .996); 10$sel-background: rgba($accent, .996);
11$sel-text: #000; 11$sel-text: #000;
12 12
13@mixin monospace-font { 13$font-monospace: 'Iosevka Term SS03', 'IBM Plex Mono', 'Fira Code', 'Fira Mono', 'Roboto Mono', 'Droid Sans Mono', Monaco, Consolas, Courier, monospace;
14 font-family: 'Iosevka Term SS03', 'IBM Plex Mono', 'Fira Code', 'Fira Mono', 'Roboto Mono', 'Droid Sans Mono', Monaco, Consolas, Courier, monospace;
15 font-size: 1 / 16 * 17em;
16 line-height: 1.5;
17}
18 14
19@font-face { 15@font-face {
20 font-family: 'Iosevka Term SS03'; 16 font-family: 'Iosevka Term SS03';
@@ -35,15 +31,29 @@ body {
35 // hsla(210, 100%, 95%, .025) 2px 31 // hsla(210, 100%, 95%, .025) 2px
36 // ); 32 // );
37 color: $text; 33 color: $text;
34 font-family: $font-monospace;
35 font-size: 1 / 16 * 17em;
36 line-height: 1.5;
37}
38
39h1, h2, h3, h4, h5, h6 {
40 font: inherit;
41 color: $text-plus;
42 margin: 0;
38} 43}
39 44
40button { 45button {
41 @include monospace-font; 46 background: none;
47 border: 0;
48 padding: 0;
49 color: $text-plus;
50 font: inherit;
51 text-decoration: underline;
52 cursor: pointer;
42 53
43 background: none; 54 &:focus {
44 border: 0; 55 outline: 1px dotted currentColor;
45 padding: 0; 56 }
46 color: $text-plus;
47} 57}
48 58
49img { 59img {
@@ -66,6 +76,10 @@ img {
66 } 76 }
67} 77}
68 78
79strong {
80 font-weight: normal;
81}
82
69::selection { 83::selection {
70 color: $sel-text; 84 color: $sel-text;
71 background-color: $sel-background; 85 background-color: $sel-background;
@@ -79,44 +93,175 @@ img {
79 color: $link-visited; 93 color: $link-visited;
80} 94}
81 95
96:link,
97:visited {
98 &:hover {
99 color: $text-plus;
100 }
101}
102
103.novisit {
104 color: $link-idle;
105}
106
82// :link, :visited { 107// :link, :visited {
83// &:hover { 108// &:hover {
84// color: $text-plus; 109// color: $text-plus;
85// } 110// }
86// } 111// }
87 112
88.header { 113.header-base {
89 @include monospace-font; 114 display: flex;
115 flex-flow: row wrap;
116 align-items: center;
117 justify-content: space-between;
90 118
91 padding: .9em 1em; 119 &::after {
92 border-bottom: 1px solid $border; 120 display: block;
93 line-height: 1.3; 121 width: 100%;
94 color: $text-faint; 122 white-space: nowrap;
123 overflow: hidden;
124 color: $text-faint;
125 content: '----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------';
126 }
95} 127}
96 128
97.header__uripart { 129//
98 color: $text-minus; 130
131.header {
132 padding: .9em 1em 0;
133 color: $text-minus;
99} 134}
100 135
101.header__uripart--last { 136//
102 color: $text-plus; 137
138.location {
139 color: $text-faint;
140
141 &__prefix {
142 color: $text-faint;
143
144 &:hover {
145 color: $text-plus;
146 }
147 }
148
149 &__uripart {
150 color: $text-plus;
151
152 + .location__slash + .location__uripart {
153 color: $text;
154
155 &:hover {
156 color: $text-plus;
157 }
158 }
159 }
103} 160}
104 161
162//
163
105.wrap { 164.wrap {
106 text-align: center; 165 text-align: center;
107} 166}
108 167
109.content { 168.content {
110 @include monospace-font; 169 display: inline-block;
111 170 min-width: 50em;
112 display: inline-block; 171 min-width: 5ch + 80;
113 min-width: 50em; 172 margin: 0;
114 min-width: 5ch + 80; 173 padding: 2em 1em;
115 margin: 0; 174 text-align: left;
116 padding: 2em 1em; 175 font: inherit;
117 text-align: left;
118} 176}
119 177
178//
179
120.type-annotation { 180.type-annotation {
121 color: $text-minus; 181 color: $text-minus;
122} 182}
183
184//
185
186.modal {
187 position: fixed;
188 top: 0;
189 left: 0;
190 z-index: 100;
191 display: none;
192 width: 100%;
193 height: 100%;
194 box-sizing: border-box;
195 padding: 2em;
196 background-color: rgba(#000, .75);
197
198 &--visible {
199 display: block;
200 }
201
202 &__content {
203 max-width: 30em;
204 padding: 1.5em 1.8em;
205 margin: 0 auto;
206 background-color: $background;
207 box-shadow: 0 .3em 2em #000;
208 text-align: left;
209 }
210
211 &__head {
212 margin-bottom: 1.5em;
213 }
214
215 &__title {
216 padding-right: 1em;
217 white-space: nowrap;
218 text-overflow: ellipsis;
219 overflow: hidden;
220 }
221}
222
223//
224
225.setting {
226 display: flex;
227 flex-direction: row;
228 align-items: center;
229 justify-content: space-between;
230
231 &__label {
232 white-space: nowrap;
233 overflow: hidden;
234 margin-right: .5em;
235
236 &::after {
237 color: $text-faint;
238 content: ' . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .';
239 }
240 }
241}
242
243//
244
245#none {
246 position: fixed;
247 top: 0;
248 left: 0;
249 width: 0;
250 height: 0;
251}
252
253//
254
255@media screen and (max-width: 800px) {
256 .location__uripart ~ * {
257 display: none;
258 }
259
260 .modal {
261 padding: 1em;
262 }
263
264 .modal__content {
265 padding: 1em 1.3em;
266 }
267}
diff --git a/js/KeyValueStore.ts b/js/KeyValueStore.ts
new file mode 100644
index 0000000..fd8f39e
--- /dev/null
+++ b/js/KeyValueStore.ts
@@ -0,0 +1,89 @@
1type KeyValueStoreCallback<T> = (value: T) => void;
2
3interface IKeyValueStoreParam<T> {
4 value: T;
5 valueRange?: T[];
6 callbacks?: KeyValueStoreCallback<T>[];
7}
8
9type KeyValueStoreData<T> = { [K in keyof T]: IKeyValueStoreParam<T[K]> };
10
11class KeyValueStore<T> {
12 constructor(private data: KeyValueStoreData<T>) {
13 for (const id of Object.keys(data)) {
14 const props = data[id as keyof typeof data];
15
16 if (props.valueRange && props.valueRange.indexOf(props.value) === -1) {
17 throw new Error(`Invalid value "${props.value}" for ID "${id}"`);
18 }
19 }
20 }
21
22 public getValue(id: keyof T) {
23 return this.data[id].value;
24 }
25
26 public setValue<K extends keyof T>(id: K, value: T[K]) {
27 const props = this.data[id];
28
29 if (props.valueRange) {
30 const valueIndex = props.valueRange.indexOf(value);
31 if (valueIndex === -1) {
32 throw new Error(`Invalid value "${value}" for ID "${id}"`);
33 }
34 }
35
36 props.value = value;
37
38 if (props.callbacks) {
39 props.callbacks.forEach(callback => {
40 callback(value);
41 });
42 }
43 }
44
45 public cycleValue<K extends keyof T>(id: K, dir: -1 | 1 = 1) {
46 const props = this.data[id];
47
48 if (!props) {
49 throw new Error(`Invalid ID "${id}"`);
50 }
51
52 let value = props.value;
53
54 if (props.valueRange) {
55 let valueIndex = props.valueRange.indexOf(value) + dir;
56
57 if (valueIndex >= props.valueRange.length) {
58 valueIndex = 0;
59 } else if (valueIndex < 0) {
60 valueIndex = props.valueRange.length - 1;
61 }
62
63 value = props.value = props.valueRange[valueIndex];
64 } else if (typeof value === "number") {
65 (value as number) += dir;
66 props.value = value;
67 } else {
68 throw new Error(`Can't cycle "${id}"`);
69 }
70
71 if (props.callbacks) {
72 props.callbacks.forEach(callback => {
73 callback(value);
74 });
75 }
76
77 return value;
78 }
79
80 public addCallback<K extends keyof T>(id: K, callback: KeyValueStoreCallback<T[K]>) {
81 const props = this.data[id];
82
83 if (!props.callbacks) {
84 props.callbacks = [];
85 }
86
87 props.callbacks.push(callback);
88 }
89}
diff --git a/js/main.ts b/js/main.ts
index 21e589d..7ef7020 100644
--- a/js/main.ts
+++ b/js/main.ts
@@ -1,42 +1,181 @@
1let linkQryEls = document.getElementsByClassName('link--QRY'); 1/// <reference path="./KeyValueStore.ts" />
2let i = linkQryEls.length; 2
3while (i--) { 3//
4 linkQryEls[i].addEventListener('click', e => { 4
5function ensureSetting(key: string, defaultValue: string): string {
6 let strValue = localStorage.getItem(key);
7 if (strValue === null) {
8 strValue = defaultValue;
9 localStorage.setItem(key, strValue);
10 }
11 return strValue;
12}
13
14const settings = new KeyValueStore({
15 imagePreviews: {
16 value: ensureSetting('image-previews', '1') === '1',
17 valueRange: [false, true]
18 }
19});
20
21//
22
23(() => {
24 const linkQryEls = document.getElementsByClassName('link--QRY');
25 let i = linkQryEls.length;
26 while (i--) {
27 linkQryEls[i].addEventListener('click', e => {
28 e.preventDefault();
29
30 const resp = prompt('Please enter required input: ', '');
31 if ((resp !== null) && (resp !== "")) {
32 window.location.href = (e.target as HTMLAnchorElement).href + '?' + resp;
33 }
34
35 return false;
36 });
37 }
38})();
39
40(() => {
41 const locationPrefixEl = document.getElementsByClassName('location__prefix')[0];
42 locationPrefixEl.addEventListener('click', e => {
5 e.preventDefault(); 43 e.preventDefault();
6 44
7 const resp = prompt('Please enter required input: ', ''); 45 const resp = prompt('Please enter new location: ', '');
8 if ((resp !== null) && (resp !== "")) { 46 if ((resp !== null) && (resp !== "")) {
9 window.location.href = (e.target as HTMLAnchorElement).href + '?' + resp; 47 window.location.href = window.location.origin + '/' + resp;
10 } 48 }
11 49
12 return false; 50 return false;
13 }); 51 });
14} 52})();
15 53
16let imgPreviewEls = document.getElementsByClassName('img-preview'); 54(() => {
17i = imgPreviewEls.length; 55 const settingImagePreviewEl = document.getElementsByClassName('setting--image-previews')[0];
18while (i--) { 56 const settingImagePreviewValueEl = settingImagePreviewEl.getElementsByClassName('setting__value')[0];
19 const imgPreviewEl = imgPreviewEls[i] as HTMLAnchorElement; 57 const settingImagePreviewCallback = (value: boolean, init = false) => {
20 const child = imgPreviewEl.children[0] as HTMLImageElement; 58 if (value) {
21 const thumbnailUrl = child.src; 59 generateImageThumbnails();
60 } else if (!init) {
61 removeImageThumbnails();
62 }
22 63
23 child.addEventListener('load', e => { 64 localStorage.setItem('image-previews', value ? '1' : '0');
24 child.classList.remove('faded'); 65 settingImagePreviewValueEl.textContent = value ? '[yes]' : '[no]';
25 }); 66 }
26 67
27 imgPreviewEls[i].addEventListener('click', e => { 68 settingImagePreviewValueEl.addEventListener('click', e => {
28 e.preventDefault(); 69 e.preventDefault();
70 settings.cycleValue('imagePreviews');
71 return false;
72 });
29 73
30 child.classList.add('faded'); 74 settingImagePreviewCallback(settings.getValue('imagePreviews'), true);
75 settings.addCallback('imagePreviews', settingImagePreviewCallback);
76})();
31 77
32 if (child.classList.contains('expanded')) { 78(() => {
33 child.classList.remove('expanded'); 79 const modalEls = document.getElementsByClassName('modal');
34 child.src = thumbnailUrl; 80 let i = modalEls.length;
35 } else { 81 while (i--) {
36 child.classList.add('expanded'); 82 const modalEl = modalEls[i];
37 child.src = imgPreviewEl.href; 83 const modalContentEl = modalEl.getElementsByClassName('modal__content')[0];
38 } 84 const modalCloseBtnEl = modalEl.getElementsByClassName('modal__close-btn')[0];
39 85
86 document.addEventListener('click', e => {
87 if (!modalEl.classList.contains('modal--visible')) {
88 return;
89 }
90 if (e.target !== modalContentEl && !modalContentEl.contains(e.target as Element)) {
91 modalEl.classList.remove('modal--visible');
92 e.preventDefault();
93 e.stopPropagation();
94 }
95 }, true);
96
97 document.addEventListener('keydown', e => {
98 if (!modalEl.classList.contains('modal--visible')) {
99 return;
100 }
101 if (e.keyCode === 27) {
102 modalEl.classList.remove('modal--visible');
103 }
104 });
105
106 modalCloseBtnEl.addEventListener('click', e => {
107 e.preventDefault();
108 modalEl.classList.remove('modal--visible');
109 return false;
110 });
111 }
112
113 //
114
115 const settingsBtnEl = document.getElementsByClassName('settings-btn')[0];
116 const settingsModalEl = document.getElementsByClassName('modal--settings')[0];
117
118 settingsBtnEl.addEventListener('click', e => {
119 e.preventDefault();
120 settingsModalEl.classList.add('modal--visible');
40 return false; 121 return false;
41 }); 122 });
123})();
124
125function generateImageThumbnails() {
126 const linkImgEls = document.querySelectorAll('.link--IMG, .link--GIF');
127 let i = linkImgEls.length;
128 while (i--) {
129 const linkImgEl = linkImgEls[i] as HTMLAnchorElement;
130 const thumbnailUrl = linkImgEl.href.replace(/^(.*?)\/I/, '$1/T');
131
132 const lineBreakEl = document.createTextNode('\n');
133
134 const typeAnnotEl = document.createElement('span');
135 typeAnnotEl.classList.add('type-annotation');
136 typeAnnotEl.textContent = ' -> ';
137
138 const thumbnailEl = document.createElement('img');
139 thumbnailEl.src = thumbnailUrl;
140 thumbnailEl.addEventListener('load', e => {
141 thumbnailEl.classList.remove('faded');
142 });
143
144 const thumbnailAnchorEl = document.createElement('a');
145 thumbnailAnchorEl.classList.add('img-preview');
146 thumbnailAnchorEl.href = linkImgEl.href;
147 thumbnailAnchorEl.addEventListener('click', e => {
148 e.preventDefault();
149
150 thumbnailEl.classList.add('faded');
151
152 if (thumbnailEl.classList.contains('expanded')) {
153 thumbnailEl.classList.remove('expanded');
154 thumbnailEl.src = thumbnailUrl;
155 } else {
156 thumbnailEl.classList.add('expanded');
157 thumbnailEl.src = thumbnailAnchorEl.href;
158 }
159
160 return false;
161 });
162
163 thumbnailAnchorEl.append(thumbnailEl);
164 linkImgEl.parentNode!.insertBefore(thumbnailAnchorEl, linkImgEl.nextSibling);
165 linkImgEl.parentNode!.insertBefore(typeAnnotEl, thumbnailAnchorEl);
166 linkImgEl.parentNode!.insertBefore(lineBreakEl, typeAnnotEl);
167 }
168}
169
170function removeImageThumbnails() {
171 const linkImgEls = document.querySelectorAll('.link--IMG, .link--GIF');
172 let i = linkImgEls.length;
173 while (i--) {
174 const linkImgEl = linkImgEls[i];
175 let j = 3;
176
177 while (j-- && linkImgEl.nextSibling) {
178 linkImgEl.nextSibling.remove();
179 }
180 }
42} 181}
diff --git a/template.go b/template.go
index f5f4f2b..dfda36d 100644
--- a/template.go
+++ b/template.go
@@ -12,31 +12,38 @@ var tpltext = `<!doctype html>
12 </style> 12 </style>
13 </head> 13 </head>
14 <body> 14 <body>
15 <header class="header"> 15 <header class="header header-base">
16 {{- $href := "" -}} 16 <div class="location">
17 {{- $uriParts := split .URI "/" -}} 17 {{- $href := "" -}}
18 {{- $uriLast := $uriParts | last -}} 18 {{- $uriParts := split .URI "/" -}}
19 {{- $uriParts = $uriParts | pop -}} 19 {{- $uriLast := $uriParts | last -}}
20 {{- if eq $uriLast "" -}} 20 {{- $uriParts = $uriParts | pop -}}
21 {{- $uriLast = $uriParts | last -}} 21 {{- if eq $uriLast "" -}}
22 {{- $uriParts = $uriParts | pop -}} 22 {{- $uriLast = $uriParts | last -}}
23 {{- end -}} 23 {{- $uriParts = $uriParts | pop -}}
24 {{- if eq (len $uriParts) 1 -}} 24 {{- end -}}
25 {{- $uriLast = $uriParts | last -}} 25 {{- if eq (len $uriParts) 1 -}}
26 {{- $uriParts = $uriParts | pop -}} 26 {{- $uriLast = $uriParts | last -}}
27 {{- end -}} 27 {{- $uriParts = $uriParts | pop -}}
28 {{- end -}}
28 29
29 {{- range $i, $part := $uriParts -}} 30 <button class="location__prefix">gopher://</button> {{ range $i, $part := $uriParts -}}
30 {{- if ne $i 1 -}} 31 {{- if ne $i 1 -}}
31 {{- $href = printf "%s/%s" $href . -}} 32 {{- $href = printf "%s/%s" $href . -}}
32 {{- if ne $i 0 }} / {{ end -}} 33 {{- if ne $i 0 -}}
33 <a href="{{ $href }}" class="header__uripart">{{ . }}</a> 34 <span class="location__slash"> / </span>
34 {{- else -}} 35 {{- end -}}
35 {{- $href = printf "%s/1" $href -}} 36 <a href="{{ $href }}" class="location__uripart">{{ . }}</a>
37 {{- else -}}
38 {{- $href = printf "%s/1" $href -}}
39 {{- end -}}
40 {{- end -}}
41 {{- if ne (len $uriParts) 0 -}}
42 <span class="location__slash"> / </span>
36 {{- end -}} 43 {{- end -}}
37 {{- end -}} 44 <span class="location__uripart">{{ $uriLast -}}</span>
38 {{- if ne (len $uriParts) 0 }} / {{ end -}} 45 </div>
39 <span class="header__uripart header__uripart--last">{{ $uriLast -}}</span> 46 <button class="settings-btn">Settings</button>
40 </header> 47 </header>
41 <main class="wrap"> 48 <main class="wrap">
42 <pre class="content"> 49 <pre class="content">
@@ -48,9 +55,6 @@ var tpltext = `<!doctype html>
48 {{- end -}} 55 {{- end -}}
49 {{- if .Link -}} 56 {{- if .Link -}}
50 {{- $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)) -}} 57 {{- $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)) -}}
51 {{- if or (eq .Type "IMG") (eq .Type "GIF") -}}
52 {{- $content = printf "%s\n%s" $content (printf "<span class=\"type-annotation\"> -> </span><a class=\"img-preview\" href=\"%s\"><img src=\"%s\" /></a>" .Link (.Link | replace "^/(.*?)/I" "/$1/T")) -}}
53 {{- end -}}
54 {{- else -}} 58 {{- else -}}
55 {{- $content = printf "%s%s" $content (printf " %s" (.Text | HTMLEscape)) -}} 59 {{- $content = printf "%s%s" $content (printf " %s" (.Text | HTMLEscape)) -}}
56 {{- end -}} 60 {{- end -}}
@@ -61,6 +65,18 @@ var tpltext = `<!doctype html>
61 {{- end -}} 65 {{- end -}}
62 </pre> 66 </pre>
63 </main> 67 </main>
68 <aside class="modal modal--settings">
69 <div class="modal__content">
70 <header class="modal__head header-base">
71 <h1 class="modal__title">Settings</h1>
72 <button class="modal__close-btn">Close</button>
73 </header>
74 <div class="setting setting--image-previews">
75 <strong class="setting__label">Image thumbnails</strong>
76 <button class="setting__value">[N/A]</button>
77 </div>
78 </div>
79 </aside>
64 <script type="text/javascript"> 80 <script type="text/javascript">
65 {{ .Script | safeJs }} 81 {{ .Script | safeJs }}
66 </script> 82 </script>