on master help sign in
fix merge conflicts
42d7c39ckipp01
links/main.css+56−69
@@ -1,39 +1,39 @@
1* { margin:0;padding:0;border:0;outline:0;text-decoration:none;font-weight:inherit;font-style:inherit;color:inherit;font-size:100%;font-family:inherit;vertical-align:baseline;list-style:none;border-collapse:collapse;border-spacing:0; -webkit-font-smoothing: antialiased;-moz-osx-font-smoothing: grayscale;}
1* { margin:0;padding:0;border:0;outline:0;text-decoration:none;font-weight:inherit;font-style:inherit;color:inherit;font-size:100%;font-family:inherit;vertical-align:baseline;list-style:none;border-collapse:collapse;border-spacing:0; -webkit-font-smoothing: antialiased;-moz-osx-font-smoothing: grayscale;}
2
2
3body { background:#f4f4f4; padding:0px; margin:0px; }
3body { background:#f4f4f4; padding:0px; margin:0px; }
4−body > div { display:block; background: #111;min-height: calc(100vh - 60px);width: calc(100vw - 60px);margin: 15px;color: #333;padding: 15px;font-family:'input_mono_regular', 'Monospaced','Courier New',courier;font-size:11px; position: relative; border-radius: 2px; color:white; overflow: hidden;}
4+body > div { background: #111; min-height: calc(100vh - 60px); width: calc(100vw - 60px); margin: 15px; padding: 15px; font-family:'input_mono_regular', 'Monospaced','Courier New',courier; font-size:11px; color:white; position: relative; }
5−body > div > ul { max-width: 800px; padding-bottom: 15px;margin-bottom:15px; overflow: hidden;}
5+body > div > ul { padding-bottom: 15px; margin-bottom:15px; }
6−body div p { display:block; padding:0px 5px; line-height: 20px; max-width: 600px}
6+body div p { display:block; padding:0px 5px; line-height: 20px;}
7−body div p a { text-decoration: underline; cursor: pointer;}
7+body footer { border-top: 2px solid white; padding-top: 15px; margin-top: 15px;}
8−body div p a:hover { text-decoration: underline; }
8+body footer a { text-decoration: underline; }
9body strong { font-weight: normal; font-family: 'input_mono_medium' }
9body strong { font-weight: normal; font-family: 'input_mono_medium' }
10−body div p.readme { max-width:500px; }
11−body .col3 { columns:3; }
12body .selected { background: white; color:black; }
10body .selected { background: white; color:black; }
13
11
14−body #sidebar { position: absolute;top: 0px;right: 0px;padding: 15px;width: 200px; overflow: hidden;}
12+/* portal */
15−body #sidebar > * { border-bottom: 0px; line-height: 16px; padding-bottom: 0px }
13+body #portal main,
16−body #sidebar ul { margin-bottom: 15px }
14+body #portal footer { max-width: 800px; }
17−body #sidebar > * li { text-transform: lowercase; }
18−body #sidebar > * li:hover { cursor: pointer; }
19−body #sidebar > * li span.right { color:#555; float:right; }
20−body #sidebar > * li span.right:after { content: " msgs"; }
21−body #sidebar #channels li:before, body #sidebar #categories li:before { content:'/'; }
22−body #sidebar #users li:before { content:'@'; }
23−body #footer { padding-left: 60px; background-image: url(../icon.white.svg); background-size: 40px; background-repeat: no-repeat; background-position: 0px 10px; border-top: 2px solid white; padding-top: 15px; margin-top: 15px; max-width: calc(100% - 300px); min-height: 45px; line-height: 15px;}
15+body #portal #icon { display: block; width: 30px; height: 30px; background-image: url(../icon.white.svg); background-size: cover; background-repeat: no-repeat; position: absolute; bottom: 15px; right:15px; background-position: center; padding:0px }
24−
16+body #portal ul { columns:1; }
25−@media only screen and (min-width:200px) and (max-width: 800px) {
17+body #portal ul li { display:block; padding-left:5px; text-overflow: ellipsis; text-transform: lowercase;}
26− body #hallway #sidebar { position: absolute;top: 0px;right: 0px;padding: 15px;width: 200px;display: none;background-color: #000; }
18+body #portal ul li:before { content: attr(id) ")"; }
27− body #hallway #sidebar #channels { margin-top: 30px; }
19+body #portal ul li[data-type]::after { content:"<" attr(data-type) ">"; color:#555; }
28−}
20+body #portal ul li a { line-height: 20px; padding:0px 5px; text-decoration: none;}
29−
21+body #portal ul li a:visited { color:#ccc; text-decoration: none;}
30−/* Hallway */
22+body #portal ul li a:hover { text-decoration: underline; }
31
23
32−body #hallway #entries { width: calc(100vw - 300px) !important; }
24+body aside { min-width: 200px; padding: 0 15px; line-height: 16px; display: none; }
25+body aside ul { margin-bottom: 15px; }
26+body aside #channels li:before, body #sidebar #categories li:before { content:'/'; }
27+body aside #users li:before { content:'@'; }
28+body aside span.right { color: #555; float: right; }
29+body aside li { text-transform: lowercase; cursor: pointer; }
33
30
34−body #hallway #pagination { margin: 10px; text-align: center; }
31+/* hallway */
35−body #hallway #pagination span { padding: 3px; margin: 2px; cursor: pointer; }
36
32
33+body #hallway #entries { display: flex; margin-top: 35px; }
34+body #hallway #entries > div { flex-grow: 1; }
35+body #hallway #showbar { position: absolute;top: 0px;width: 92%;height: 50px; background-image: url("data:image/svg+xml; utf8,<svg xmlns='http://www.w3.org/2000/svg' width='22' height='22'><path d='M11,4 L11,18 M4,4 L 4,18 M18,4 L18,18' stroke='%23555' fill='none' stroke-width='2' stroke-linecap='round'/></svg>");background-repeat: no-repeat;background-position: right;background-size: 25px; }
36+body #hallway #hidebar { position: absolute;top: 0px;width: 100%;height: 50px;background-image: url("data:image/svg+xml; utf8,<svg xmlns='http://www.w3.org/2000/svg' width='22' height='22'><path d='M5,5 L17,17 M17,5 L5,17' stroke='%23555' fill='none' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'/></svg>");background-repeat: no-repeat;background-position: left;background-size: 25px; }
37body #hallway .entry { line-height: 16px; display: block; position: relative; padding-left:100px; }
37body #hallway .entry { line-height: 16px; display: block; position: relative; padding-left:100px; }
38body #hallway .entry .date { color: #555;display: block;float:right; margin-left:20px; padding-bottom: 16px;}
38body #hallway .entry .date { color: #555;display: block;float:right; margin-left:20px; padding-bottom: 16px;}
39body #hallway .entry .author { font-family: 'input_mono_medium'; position: absolute;left: 0px; }
39body #hallway .entry .author { font-family: 'input_mono_medium'; position: absolute;left: 0px; }
@@ -48,50 +48,37 @@ body #hallway .entry.highlight .author { color:#555; }
48body #hallway .entry:hover .author { color:#ccc; }
48body #hallway .entry:hover .author { color:#ccc; }
49body #hallway .entry:hover .date { color:#ccc; }
49body #hallway .entry:hover .date { color:#ccc; }
50body #hallway .entry:hover .body { color:#ccc; border-bottom-color: #555 }
50body #hallway .entry:hover .body { color:#ccc; border-bottom-color: #555 }
51−body #hallway ul { max-width: 100%; }
51+body #hallway #footer { padding-left: 60px; background-image: url(../icon.white.svg); background-size: 40px; background-repeat: no-repeat; background-position: 0px 10px; border-top: 2px solid white; padding-top: 20px; margin-top: 15px; max-width: calc(100% - 300px); min-height: 45px; line-height: 15px;}
52−body #hallway ul li:last-child { border-bottom:0px; }
53−
54−@media only screen and (min-width:200px) and (max-width: 800px) {
55− body #hallway #entries { width: 100% !important;margin-top: 30px; }
56− body #hallway #showbar { position: absolute;top: 0px;width: 92%;height: 50px; background-image: url("data:image/svg+xml; utf8,<svg xmlns='http://www.w3.org/2000/svg' width='22' height='22'><path d='M11,4 L11,18 M4,4 L 4,18 M18,4 L18,18' stroke='%23555' fill='none' stroke-width='2' stroke-linecap='round'/></svg>");background-repeat: no-repeat;background-position: right;background-size: 25px; }
57− body #hallway #hidebar { position: absolute;top: 0px;width: 100%;height: 50px;background-image: url("data:image/svg+xml; utf8,<svg xmlns='http://www.w3.org/2000/svg' width='22' height='22'><path d='M5,5 L17,17 M17,5 L5,17' stroke='%23555' fill='none' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'/></svg>");background-repeat: no-repeat;background-position: left;background-size: 25px; }
58−}
59
52
60−/* Portal */
53+body #hallway #pagination { margin: 10px; text-align: center; }
54+body #hallway #pagination span { padding: 3px; margin: 2px; cursor: pointer; }
61
55
62−body #portal #icon { display: block;width: 30px;height: 30px;background-image: url(../icon.white.svg);background-size: cover;background-repeat: no-repeat;position: absolute;bottom: 15px;right:15px;background-position: center;padding:0px }
56+/* wiki */
63−body #portal ul { columns:3; }
57+body #wiki { background: #ccc; color:#333; }
64−body #portal ul li { display:block; padding-left:5px; text-overflow: ellipsis; text-transform: lowercase;}
58+body #wiki main { display: flex; }
65−body #portal ul li:before { content: attr(id) ")"; }
59+body #wiki main { flex: 1; }
60+body #wiki main { max-width: calc(100% - 230px); }
61+body #wiki main h1 { font-size: 1.3em; margin: 5px 0; font-weight: bold; }
62+body #wiki main div.related { border-bottom: 1px solid white; margin-bottom: 10px; }
63+body #wiki #main { width: 100%; }
64+body #wiki #main ul li { line-height: 15px; }
65+body #wiki aside { min-width: 220px; }
66+body #wiki aside { overflow-y: scroll; height: calc(100% - 15px); position: absolute; right: 0;}
67+body #wiki aside a { display: inline-block; width: 135px; white-space: nowrap; text-overflow: ellipsis; overflow: hidden; padding: 7px; }
68+body #wiki aside li { line-height: 7px; }
69+body #wiki aside li:before { display: none; }
70+body #wiki aside a:before { content: '/'; }
66−body #portal ul li[data-type]::after { content:"<" attr(data-type) ">"; color:#555; }
71+body #wiki aside a:after { content: attr(data-msgs) " msgs"; position: absolute; right: 20px; color:#555; }
67−body #portal ul li a { line-height: 20px; padding:0px 5px; text-decoration: none;}
72+body #wiki ul.term { margin-bottom: 15px; }
68−body #portal ul li a:visited { color:#ccc; text-decoration: none;}
73+body #wiki ul.term li.name { margin-bottom: 5px; }
69−body #portal ul li a:hover { text-decoration: underline; }
74+body #wiki #footer { padding-left: 60px; background-image: url(../icon.white.svg); background-size: 40px; background-repeat: no-repeat; background-position: 0px 10px; border-top: 2px solid white; padding-top: 22px; margin-top: 15px; max-width: calc(100% - 300px); min-height: 45px; line-height: 15px;}
70
75
71−@media (max-width: 730px) {
72− body #portal ul li:before { display: none }
73−}
74−@media (max-width: 630px) {
76+@media (min-width: 630px) {
77+ body aside { display: block; }
75− body #portal ul { columns: 2 !important; }
78+ body #portal ul { columns: 3;}
76− body #portal ul li:before { display: none }
79+ body #hallway #entries { margin-top: 0; }
80+ body #hallway #showbar { display: none; }
81+ body #hallway #hidebar { display: none; }
82+ body #wiki footer { margin-right: 225px; }
77}
83}
78−@media (max-width: 530px) {
79− body #portal ul { columns: 1 !important; }
80− body #portal ul li:before { display: inline }
81−}
82−
83−/* Wiki */
84
84
85−body #wiki { background: #ccc; color:#333; }
86−body #wiki #entry { max-width: calc(100% - 230px); margin-bottom: 30px; line-height: 15px }
87−body #wiki #entry a { text-decoration: underline; }
88−body #wiki #sidebar { overflow-y: scroll; height: calc(100% - 15px); }
89−body #wiki #sidebar a { display: inline-block; width: 120px; white-space: nowrap; text-overflow: ellipsis; overflow: hidden; padding: 7px; }
90−body #wiki #sidebar li { line-height: 7px; }
91−body #wiki #sidebar li:before { display: none; }
92−body #wiki #sidebar a:before { content: '/'; }
93−body #wiki #sidebar a:after { content: attr(data-msgs) " msgs"; position: absolute; right: 20px; color:#555; }
94−body #wiki ul.term { border-bottom: 0px; margin-bottom: 0px; padding-bottom: 0px}
95−body #wiki ul.term li { margin-bottom: 5px }
96−body #wiki ul.term li.name { margin-bottom: 5px }
97−body #wiki ul.term li a.author { color:#555; text-decoration: none !important; }
scripts/hallway.js+31−30
@@ -14,24 +14,21 @@ function Hallway (sites) {
14 this.finder = { filter: filterAndPage[0].trim().replace('#', ''), page: filterAndPage[1] || 1 }
14 this.finder = { filter: filterAndPage[0].trim().replace('#', ''), page: filterAndPage[1] || 1 }
15 this.cache = null
15 this.cache = null
16
16
17− this._entries = document.createElement('div')
17+ this._main = document.createElement('main')
18− this._entries.id = 'entries'
18+ this._main.id = 'entries'
19− this._sidebar = document.createElement('div')
20− this._sidebar.id = 'sidebar'
21− this._footer = document.createElement('p')
19+ this._footer = document.createElement('footer')
22 this._footer.id = 'footer'
20 this._footer.id = 'footer'
23− this._footer.innerHTML = `The <strong>Hallway</strong> is a decentralized forum, to join the conversation, add a <a href="https://github.com/XXIIVV/webring#joining-the-hallway">feed:</a> field to your entry in the <a href="https://github.com/XXIIVV/Webring/">webring</a>.`
21+ this._footer.innerHTML = `<p>The <strong>Hallway</strong> is a decentralized forum, to join the conversation, add a <a href="https://github.com/XXIIVV/webring#joining-the-hallway">feed:</a> field to your entry in the <a href="https://github.com/XXIIVV/Webring/">webring</a>.</p>`
24
22
25 this.install = function (host) {
23 this.install = function (host) {
26− this._el.appendChild(this._entries)
24+ this._el.appendChild(this._main)
27− this._el.appendChild(this._sidebar)
28 this._el.appendChild(this._footer)
25 this._el.appendChild(this._footer)
29 host.appendChild(this._el)
26 host.appendChild(this._el)
30 this.findFeeds()
27 this.findFeeds()
31 }
28 }
32
29
33 this.start = function () {
30 this.start = function () {
34− this._entries.innerHTML = 'Loading..'
31+ this._main.innerHTML = 'Loading..'
35 this.fetchFeeds()
32 this.fetchFeeds()
36 }
33 }
37
34
@@ -43,27 +40,31 @@ function Hallway (sites) {
43 const tags = this.findTags(localEntries)
40 const tags = this.findTags(localEntries)
44 const relevantEntries = localEntries.filter(val => !this.finder.filter || (val.author === this.finder.filter || val.channel === this.finder.filter || val.tags.includes(this.finder.filter)))
41 const relevantEntries = localEntries.filter(val => !this.finder.filter || (val.author === this.finder.filter || val.channel === this.finder.filter || val.tags.includes(this.finder.filter)))
45
42
46− this._entries.innerHTML = `
43+ const mainHtml = `
47− <ul>
44+ <div><ul>
48− ${localEntries.filter((val) => !this.finder.filter || (val.author === this.finder.filter || val.channel === this.finder.filter || val.tags.includes(this.finder.filter))).filter((_, id) => id < Number(this.finder.page) * 20 && id >= (Number(this.finder.page) - 1) * 20).reduce((acc, val) => acc + this.templateEntry(val) + '\n', '')}
45+ ${localEntries.filter((val) => !this.finder.filter || (val.author === this.finder.filter || val.channel === this.finder.filter || val.tags.includes(this.finder.filter))).filter((_, id) => id < Number(this.finder.page) * 20 && id >= (Number(this.finder.page) - 1) * 20).reduce((acc, val) => acc + this.templateEntry(val) + '\n', '')}
49− </ul>
46+ </ul>
50− <div id='pagination'>
47+ <div id='pagination'>
51− ${[...Array(Math.ceil(relevantEntries.length / 20)).keys()].reduce((acc, num) => `${acc}<span class='${Number(hallway.finder.page) === num + 1 ? 'selected' : ''}' onclick='filter("${this.finder.filter}^${num + 1}")'>${num + 1}</span>`, '')}
48+ ${[...Array(Math.ceil(relevantEntries.length / 20)).keys()].reduce((acc, num) => `${acc}<span class='${Number(hallway.finder.page) === num + 1 ? 'selected' : ''}' onclick='filter("${this.finder.filter}^${num + 1}")'>${num + 1}</span>`, '')}
52− </div>
49+ </div>
53− <a id='showbar' onclick="toggleVisibility('sidebar');"></a>`
50+ <a id='showbar' onclick="toggleVisibility('aside');"></a></div>`
54−
51+
55− this._sidebar.innerHTML = `
52+ const aside = `
53+ <aside id='aside'>
56− <a id='hidebar' onclick="toggleVisibility('sidebar');"></a>
54+ <a id='hidebar' onclick="toggleVisibility('aside');"></a>
57− <ul id='channels'>
55+ <ul id='channels'>
58− <li onclick='filter("")' class='${hallway.finder.filter === '' ? 'selected' : ''}'><a href='#'>hallway <span class='right'>${localEntries.length}</span></a></li>
56+ <li onclick='filter("")' class='${hallway.finder.filter === '' ? 'selected' : ''}'><a href='#'>hallway <span class='right'>${localEntries.length}</span></a></li>
59− ${Object.keys(channels).slice(0, 15).reduce((acc, val) => acc + `<li onclick='filter("${val}")' class='${hallway.finder.filter === val ? 'selected' : ''}'><a href='#${val}'>${val} <span class='right'>${channels[val]}</span></a></li>\n`, '')}
57+ ${Object.keys(channels).slice(0, 15).reduce((acc, val) => acc + `<li onclick='filter("${val}")' class='${hallway.finder.filter === val ? 'selected' : ''}'><a href='#${val}'>${val} <span class='right'>${channels[val]}</span></a></li>\n`, '')}
60− </ul>
58+ </ul>
61− <ul id='users'>
59+ <ul id='users'>
62− ${Object.keys(users).slice(0, 15).reduce((acc, val) => acc + `<li onclick='filter("${val}")' class='${hallway.finder.filter === val ? 'selected' : ''}' href='#${val}'>${val} <span class='right'>${users[val]}</span></li>\n`, '')}
60+ ${Object.keys(users).slice(0, 15).reduce((acc, val) => acc + `<li onclick='filter("${val}")' class='${hallway.finder.filter === val ? 'selected' : ''}' href='#${val}'>${val} <span class='right'>${users[val]}</span></li>\n`, '')}
63− </ul>
61+ </ul>
64− <ul id='tags'>
62+ <ul id='tags'>
65− ${Object.keys(tags).slice(0, 15).reduce((acc, val) => acc + `<li onclick='filter("${val}")' class='${hallway.finder.filter === val ? 'selected' : ''}' href='#${val}'>#${val} <span class='right'>${tags[val]}</span></li>\n`, '')}
63+ ${Object.keys(tags).slice(0, 15).reduce((acc, val) => acc + `<li onclick='filter("${val}")' class='${hallway.finder.filter === val ? 'selected' : ''}' href='#${val}'>#${val} <span class='right'>${tags[val]}</span></li>\n`, '')}
66− </ul>`
64+ </ul>
65+ </aside>`
66+
67+ this._main.innerHTML = `${mainHtml}${aside}`
67
68
68 if (feeds) {
69 if (feeds) {
69 this.cache = feeds
70 this.cache = feeds
scripts/portal.js+13−15
@@ -16,19 +16,22 @@ function Portal (sites) {
16 }
16 }
17
17
18 function _directory (sites) {
18 function _directory (sites) {
19− return `
20− <ul>${sites.reduce( (acc, site, id) =>
21− `${acc}<li ${site.type ? `data-type="${site.type}"` : ''} id='${id}'><a href='${site.url}'>${_name(site)}</a></li>`
19+ const listItems= sites.reduce((acc, site, id) => `${acc}<li ${_type(site)} id='${id}'><a href='${site.url}'>${_name(site)}</a></li>`, '')
22− , '')}</ul>\n${_readme()}${_buttons()}`
20+ return `<main><ul>${listItems}</ul></main><footer>${_readme()}${_buttons()}</footer>`
23 }
21 }
24
22
25 function _name (site) {
23 function _name (site) {
26 return site.title || `${site.url.split('//')[1]}`
24 return site.title || `${site.url.split('//')[1]}`
27 }
25 }
28
26
27+ function _type (site) {
28+ return site.type ? `data-type="${site.type}"` : ''
29+ }
30+
29 function _redirect (site) {
31 function _redirect (site) {
30− return `<p id='footer'>Redirecting to <a href="${site.url}">${site.url}</a></p><meta http-equiv="refresh" content="3; url=${site.url}">
32+ return `<main><p>Redirecting to <a href="${site.url}">${site.url}</a></p></main>
33+ <meta http-equiv="refresh" content="3; url=${site.url}">
31− <p class='buttons'><a href='#' onClick="portal.reload('')">Directory</a> | <a href='#${site.url}' onClick="portal.reload('random')">Skip</a> | <a href='#random' onClick="portal.reload('random')">Random</a> | <a href='https://github.com/XXIIVV/webring'>Information</a> <a id='icon' href='#random' onClick="portal.reload('random')"></a></p>`
34+ <footer><p class='buttons'><a href='#' onClick="portal.reload('')">Directory</a> | <a href='#${site.url}' onClick="portal.reload('random')">Skip</a> | <a href='#random' onClick="portal.reload('random')">Random</a> | <a href='https://github.com/XXIIVV/webring'>Information</a> <a id='icon' href='#random' onClick="portal.reload('random')"></a></p>`
32 }
35 }
33
36
34 //
37 //
@@ -52,17 +55,12 @@ function Portal (sites) {
52 this.locate = function () {
55 this.locate = function () {
53 const hash = window.location.hash.replace('#', '').trim()
56 const hash = window.location.hash.replace('#', '').trim()
54
57
55− if (hash == 'random') {
58+ if (hash === 'random') {
56 return Math.floor(Math.random() * this.sites.length)
59 return Math.floor(Math.random() * this.sites.length)
60+ } else {
61+ const id = sites.findIndex(site => site.url.indexOf(hash) > -1)
62+ return id > -1 ? parseInt(id) : -1
57 }
63 }
58−
59− for (const id in this.sites) {
60− const site = this.sites[id]
61− if (site.url.indexOf(hash) > -1) {
62− return parseInt(id)
63− }
64− }
65− return -1
66 }
64 }
67
65
68 this.next = function (loc = this.locate()) {
66 this.next = function (loc = this.locate()) {
scripts/wiki.js+105−123
@@ -1,154 +1,136 @@
1'use strict'
1'use strict'
2
2
3−function Wiki (sites) {
3+const Wiki = sites => {
4− const feeds = {}
4+ const entries = {}
5− this.sites = sites
5+ const main = document.getElementById('main')
6− this._el = document.createElement('div')
6+ const aside = document.getElementById('aside')
7− this._el.id = 'wiki'
7+ const authors = new Set()
8− this._sidebar = document.createElement('div')
8+ const terms = new Set()
9− this._sidebar.id = 'sidebar'
9+
10− this._categories = document.createElement('ul')
10+ const storeEntry = (author, url, parent, cat, entry) => {
11− this._categories.id = 'categories'
11+ if (!(parent in entries)) {
12− this._entry = document.createElement('div')
12+ entries[parent] = {}
13− this._entry.id = 'entry'
13+ entries[parent][cat] = [{ entry, author, url }]
14−
14+ } else if (!(cat in entries[parent])) {
15− this._footer = document.createElement('p')
15+ entries[parent][cat] = [{ entry, author, url }]
16− this._footer.id = 'footer'
17− this._footer.innerHTML = `The <strong>Wiki</strong> is a decentralized encyclopedia, to join the conversation, add a <a href="https://github.com/XXIIVV/webring#joining-the-wiki">wiki:</a> field to your entry in the <a href="https://github.com/XXIIVV/Webring/">webring</a>.`
18−
19− this.loc = ''
20− this.byName = {}
21− this.byCat = {}
22− this.byAuthor = {}
23−
24− this.install = (host) => {
25− this._el.appendChild(this._entry)
26− this._sidebar.appendChild(this._categories)
27− this._el.appendChild(this._sidebar)
28− this._el.appendChild(this._footer)
29− host.appendChild(this._el)
30− this.fetch()
31− }
32−
33− this.start = () => {
34− this._entry.innerHTML = 'Loading..'
35− }
36−
37− this.refresh = () => {
38− // Main
39− if (this.loc) {
40− if (this.byCat[this.loc]) {
41− const formatHtml = (entries, entry) => `${entries} ${this.templateTerm(entry.name, this.byName[entry.name])}<br />`
42− const html = this.byCat[this.loc].reduce(formatHtml, '')
43− this._entry.innerHTML = html
44− } else if (this.byName[this.loc]) {
45− const html = `${this.templateTerm(this.loc, this.byName[this.loc])}<br />${this.templateRelated(this.loc)}`
46− this._entry.innerHTML = html
47− } else {
48− const html = `Unknown: ${this.loc}. Return <a href='#'>home</a>, or try a <a href='${this.random()}'>random page</a>.`
49− this._entry.innerHTML = html
50− }
51 } else {
16 } else {
52− this._entry.innerHTML = `Click a /topic to get started, or try a <a href='#${this.random()}'>random page</a>.<br />The wiki contains ${Object.keys(this.byName).length} terms, in ${Object.keys(this.byCat).length} categories, by ${Object.keys(this.byAuthor).length} authors.`
17+ entries[parent][cat].push({ entry, author, url })
53 }
18 }
54− // Sidebar
19+ terms.add(cat)
55− this._categories.innerHTML = Object.keys(this.byCat).reduce((acc, id) =>
20+ authors.add(author)
56− { return this.byCat[id].length > 5
57− ? `${acc}<li ${wiki.at(id) ? 'class="selected"' : ''}>
58− <a href='#${id}' data-msgs='${this.byCat[id].length}'>${id}</a>
59− </li>`
60− : acc
61− } , `<li ${wiki.at() ? 'class="selected"' : ''}>
62− <a href='#' data-msgs='${Object.keys(this.byName).length}'>home</a>
63− </li>`
64− )
65 }
21 }
66
22
67− this.go = () => {
23+ const storeEntries = (author, url, parent, entries) => {
68− this.loc = decodeURIComponent( window.location.hash.substr(1) ).toUpperCase()
24+ Array.isArray(entries)
69− this.refresh()
25+ ? storeEntry(author, url, parent, 'list-definitions', entries)
26+ : Object.keys(entries).forEach(entry => storeEntry(author, url, parent, entry, entries[entry]))
70 }
27 }
71
28
72− this.at = (q = '') => {
29+ const transform = (author, url, ndtl) => {
73− return this.loc.toUpperCase() === q.toUpperCase() || (this.cat(this.loc).toUpperCase() === q.toUpperCase() && q !== '')
30+ Object.keys(ndtl).forEach(cat => storeEntries(author, url, cat, ndtl[cat]))
74 }
31 }
75
32
76− this.random = () => {
33+ const parse = (site, content) => {
77− const keys = Object.keys(this.byCat)
34+ console.log('Wiki', 'Parsing ' + site.wiki)
78− return keys[Math.floor(Math.random() * keys.length)]
35+ const ndtl = indental(content)
36+ transform(site.author, site.url, ndtl)
79 }
37 }
80
38
81− this.related = (name) => {
39+ const selected = (key, category) => key.toLowerCase() === category.toLowerCase()
82− return this.byCat[this.cat(name)]
40+ ? 'selected'
83− }
41+ : ''
84
42
85− this.cat = (name) => {
43+ const formatSideBarCat = (key, entries) => (currentHtml, cat) => {
86− return this.byName[name] ? this.byName[name][0].cat : ''
44+ const catLength = Object.keys(entries[cat]).length
45+ const newHtml = `<li class='${selected(key, cat)}'}'>
46+ <a href='#${cat}' data-msgs='${catLength}'>${cat.toLowerCase()}</a>
47+ </li>`
48+ return `${currentHtml}${newHtml}`
87 }
49 }
88
50
89− this.templateRelated = (name) => {
51+ const formatEntry = (definitions, definition) => {
90− const relatedWords = this.related(name)
52+ const formatEntryList = entryList => entryList.reduce((items, item) => `${items}<li>${item}</li>`, '')
91− const html = `<ul class='col3'>${relatedWords.reduce( (acc, item, id) =>
92− { return `${acc}<li ${wiki.at(item.name) ? 'class="selected"' : ''}>
93− <a href='#${item.name.toLowerCase()}'>${item.name.toLowerCase()}</a>
94− </li>`
95− }, '') }
96− </ul>`
97− return html
98− }
99
53
100− this.templateTerm = (name, entries) => {
101− const formatEntry = (acc, entry) => {
102− if (typeof entry.value === 'string') {
54+ const newHtml = typeof definition.entry === 'string'
55+ ? `<li>${definition.entry}
103− return `${acc}<li>${entry.value}<a class='author' target='_blank' href='${entry.origin.url}'> — @${entry.origin.author}</a></li>`
56+ <a class='author' target='_blank' href='${definition.url}'> - @${definition.author}</a>
104− } else {
57+ </li>`
105− const listItems = entry.value.reduce((items, item) => `${items}<li>${item}</li>`, '')
58+ : `<li><ul>${formatEntryList(definition.entry)}
106− return `${acc}<li><ul>${listItems}<a class='author' target='_blank' href='${entry.origin.url}'> — @${entry.origin.author}</a></ul></li>`
59+ <a class='author' target='_blank' href='${definition.url}'> — @${definition.author}</a>
107− }
60+ </ul></li>`
108− }
109
61
110− const htmlEntries = entries.reduce(formatEntry, '')
62+ return `${definitions}${newHtml}`
111− return `<ul class="term"><li class="name"><strong>${name.toLowerCase()}</strong></li>${htmlEntries}</ul>`
112 }
63 }
113
64
114− this.fetch = () => {
65+ const formatEntries = entries => (currentHtml, category) => {
115− console.log('Wiki', 'Fetching..')
66+ const definitions = entries[category].reduce(formatEntry, '')
116− for (const site of sites) {
67+ const title = category === 'list-definitions'
117− if (!site.wiki || !site.author) { continue }
68+ ? ''
118− fetch(site.wiki, { cache: 'no-store' }).then(x => x.text()).then((content) => {
69+ : `<li class='name'><strong>${category.toLowerCase()}</strong></li>`
119− this.parse(site, content)
70+ return `${currentHtml}<ul class='term'>${title}${definitions}</ul>`
120− this.refresh(feeds)
121− }).catch((err) => {
122− console.warn(`${site.wiki}`, err)
123− })
124− }
125 }
71 }
126
72
127− this.add = (name, value, cat, origin) => {
73+ const findRelated = key => {
128− this.byName[name] = this.byName[name] || []
74+ const searchOtherCategories = entry => {
129− this.byName[name].push({ name, value, cat, origin })
75+ return Object.keys(entries)
76+ .filter(category => Object.keys(entries[category]).some(otherCat => otherCat === entry))
77+ }
130
78
131− this.byCat[cat] = this.byCat[cat] || []
79+ const foundMatches = Object.keys(entries[key])
132− this.byCat[cat].push({ name, value, cat, origin })
80+ .filter(entry => entry !== 'list-definitions')
81+ .map(searchOtherCategories)
82+ const flattened = [].concat.apply([], foundMatches)
83+ return Array.from(new Set(flattened)).filter(related => related !== key)
84+ }
133
85
134− this.byAuthor[origin.author] = this.byAuthor[origin.author] || []
86+ const formatRelated = (allRelated, relatedCategory) => {
135− this.byAuthor[origin.author].push({ name, value, cat, origin })
87+ const newHtml = `<span><a href='#${relatedCategory}'>/${relatedCategory.toLowerCase()}</a> </span>`
88+ return `${allRelated}${newHtml}`
136 }
89 }
137
90
138− this.parse = (site, content) => {
91+ const refresh = key => {
139− console.log('Wiki', 'Parsing ' + site.wiki)
92+ if (key) {
140− const cats = indental(content)
93+ if (entries[key] === undefined) return
141− for (const cat in cats) {
94+ const items = Object.keys(entries[key]).sort().reduce(formatEntries(entries[key]), '')
142− const terms = cats[cat]
95+ const relatedCategories = findRelated(key)
143− for (const name in terms) {
96+ const relatedHtml = relatedCategories.length > 0
144− this.add(name, terms[name], cat, site)
97+ ? relatedCategories.reduce(formatRelated, 'Related categories: ')
145− }
98+ : ''
99+ main.innerHTML = `<div class='related'><h1>${key}</h1>${relatedHtml}</div>${items}`
100+ } else {
101+ const html = `Click a /topic to get started, or try a random page`
102+ const stats = `The wiki contains ${terms.size} terms in ${Object.keys(entries).length} categories, from ${authors.size} authors.`
103+ main.innerHTML = `${html}<br />${stats}`
146 }
104 }
105+
106+ const homeCat = `<li class='${selected(key, '')}'>
107+ <a href='#' data-msgs='${terms.size}'>home</a>
108+ </li>`
109+ const cats = Object.keys(entries).sort().reduce(formatSideBarCat(key, entries), homeCat)
110+ aside.innerHTML = `<ul>${cats}</ul>`
147 }
111 }
148
112
149− String.prototype.toUrl = function () { return this.toLowerCase().replace(/ /g, '+').replace(/[^0-9a-z+:\-./~]/gi, '').trim() }
113+ return {
114+ initialize: hash => {
115+ console.log('Wiki', 'Fetching...')
116+ sites.filter(site => site.wiki && site.author).forEach(site => {
117+ fetch(site.wiki, { cache: 'no-store' }).then(x => x.text()).then(content => {
118+ parse(site, content)
119+ refresh(stripHash(hash))
120+ }).catch((err) => {
121+ console.warn(`${site.wiki}`, err)
122+ })
123+ })
124+ },
125+ refresh: key => { refresh(key) }
126+ }
127+}
150
128
151− window.onload = this.go
129+const stripHash = hash => {
152− window.onhashchange = this.go
130+ const decoded = decodeURIComponent(hash)
131+ return decoded.charAt(0) === '#' ? decoded.substring(1) : decoded
153}
132}
154
133
134+window.addEventListener('hashchange', () => {
135+ wiki.refresh(stripHash(window.location.hash).toUpperCase())
136+})
wiki.html+11−3
@@ -11,10 +11,18 @@
11 <title>Wiki</title>
11 <title>Wiki</title>
12</head>
12</head>
13<body>
13<body>
14+ <div id="wiki">
15+ <main>
16+ <div id="main"></div>
17+ <aside id="aside"></aside>
18+ </main>
19+ <footer id="footer">
20+ <p>The <strong>Wiki</strong> is a decentralized encyclopedia, to join the conversation, add a <a href="https://github.com/XXIIVV/webring#joining-the-wiki">wiki:</a> field to your entry in the <a href="https://github.com/XXIIVV/Webring/">webring</a>.</p>
21+ </footer>
22+ </div>
14 <script>
23 <script>
15− const wiki = new Wiki(sites);
24+ const wiki = Wiki(sites)
16− wiki.install(document.body);
25+ wiki.initialize(window.location.hash)
17− wiki.start();
18 </script>
26 </script>
19</body>
27</body>
20</html>
28</html>