@@ -1,7 +1,7 @@
11'use strict'
22
33const re_channel = /(\s|^)\/([a-zA-Z0-9]+)/g
4−const re_user = /(\s|^)\@([a-zA-Z0-9]+)/g
4+const re_user = /((\s|^)@<[a-zA-Z0-9./:\-_\+~#= ]*>)/g
55const re_tag = /([^&]|^)\#([a-zA-Z0-9]+)/g
66const re_url = /((https?):\/\/(([-a-zA-Z0-9@:%._\+~#=]{2,256}\.[a-z]{2,6}\b)([-a-zA-Z0-9@:%_\+.~#?&//=]*)))/g
77
@@ -10,7 +10,8 @@ function Hallway (sites) {
1010 this.sites = sites
1111 this.el = document.createElement('div')
1212 this.el.id = 'hallway'
13− this.filter = window.location.hash.replace('#', '').trim()
13+ const filterAndPage = window.location.hash.split('^')
14+ this.finder = { filter: filterAndPage[0].trim().replace('#', ''), page: filterAndPage[1] || 1 }
1415 this.cache = null
1516
1617 this.install = function (host) {
@@ -25,24 +26,30 @@ function Hallway (sites) {
2526
2627 this.refresh = function (feeds = this.cache) {
2728 const entries = this.findEntries(feeds)
28− const channels = this.findChannels(entries)
29− const users = this.findUsers(entries)
29+ const channels = this.find(entries, 'channel')
30+ const users = this.find(entries, 'author')
3031 const tags = this.findTags(entries)
32+ const relevantEntries = entries.filter(val => !this.finder.filter || (val.author === this.finder.filter || val.channel === this.finder.filter || val.tags.includes(this.finder.filter)))
3133
3234 this.el.innerHTML = `
33− <ul id='entries'>
34− ${entries.filter((val, id) => { return !this.filter || (val.author === this.filter || val.channel === this.filter || val.tags.includes(this.filter)) }).filter((val, id) => { return id < 50 }).reduce((acc, val, id) => { return acc + this.templateEntry(val) + '\n' }, '')}
35− </ul>
35+ <div id='entries'>
36+ <ul>
37+ ${entries.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', '')}
38+ </ul>
39+ <div id='pagination'>
40+ ${[...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>`, '')}
41+ </div>
42+ </div>
3643 <div id='sidebar'>
3744 <ul id='channels'>
38− <li onclick='filter("")' class='${hallway.filter === '' ? 'selected' : ''}'><a href='#'>hallway <span class='right'>${entries.length}</span></a></li>
39− ${Object.keys(channels).reduce((acc, val, id) => { return acc + `<li onclick='filter("${val}")' class='${hallway.filter === val ? 'selected' : ''}'><a href='#${val}'>${val} <span class='right'>${channels[val]}</span></a></li>\n` }, '')}
45+ <li onclick='filter("")' class='${hallway.finder.filter === '' ? 'selected' : ''}'><a href='#'>hallway <span class='right'>${entries.length}</span></a></li>
46+ ${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`, '')}
4047 </ul>
4148 <ul id='users'>
42− ${Object.keys(users).reduce((acc, val, id) => { return acc + `<li onclick='filter("${val}")' class='${hallway.filter === val ? 'selected' : ''}' href='#${val}'>${val} <span class='right'>${users[val]}</span></li>\n` }, '')}
49+ ${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`, '')}
4350 </ul>
4451 <ul id='tags'>
45− ${Object.keys(tags).reduce((acc, val, id) => { return acc + `<li onclick='filter("${val}")' class='${hallway.filter === val ? 'selected' : ''}' href='#${val}'>#${val} <span class='right'>${tags[val]}</span></li>\n` }, '')}
52+ ${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`, '')}
4653 </ul>
4754 </div>
4855 <p>The <b>Hallway</b> is a decentralized forum, to join the conversation, simply create yourself a <a href="https://twtxt.readthedocs.io/en/stable/user/twtxtfile.html">twtxt</a> feed and <a href="https://github.com/XXIIVV/Webring/">add it</a> to your entry in the <a href="index.html">webring</a>.</p>`
@@ -54,26 +61,15 @@ function Hallway (sites) {
5461
5562 // Entries
5663
57− this.findChannels = function (entries) {
58− const channels = {}
64+ this.find = function (entries, p) {
65+ const h = {}
5966 for (const id in entries) {
60− const entry = entries[id]
61− if (!entry.channel) { continue }
62− channels[entry.channel] = channels[entry.channel] ? channels[entry.channel] + 1 : 1
67+ if (entries[id] && entries[id][p]) { h[entries[id][p]] = h[entries[id][p]] ? h[entries[id][p]] + 1 : 1 }
6368 }
64− return channels
69+ return h
6570 }
6671
67− this.findUsers = function (entries) {
68− const users = {}
69− for (const id in entries) {
70− const entry = entries[id]
71− users[entry.author] = users[entry.author] ? users[entry.author] + 1 : 1
72− }
73− return users
74− }
75−
76− this.findTags = function (entries){
72+ this.findTags = function (entries) {
7773 const tags = {}
7874 for (const id in entries) {
7975 const entry = entries[id]
@@ -91,29 +87,33 @@ function Hallway (sites) {
9187 a.push(feeds[id].content[i])
9288 }
9389 }
94− return a.sort( (a, b) => a.offset - b.offset )
90+ return a.sort((a, b) => a.offset - b.offset)
91+ }
92+
93+ this.findMention = function (found) {
94+ const mention = Object.keys(feeds).filter(user => found.indexOf(feeds[user].path) > -1)
95+ return mention.length === 1 ? ` <span class='user'>@${mention[0]}</span>` : ` <span class='user'>${found}</span>`
9596 }
9697
9798 this.templateEntry = function (entry) {
9899 entry.html = entry.body
99100 .replace(re_channel, `$1<span class='channel'>/$2</span>`)
100− .replace(re_user, `$1<span class='user'>@$2</span>`)
101+ .replace(re_user, this.findMention)
101102 .replace(re_tag, `$1<span class='tag'>#$2</span>`)
102103 .replace(re_url, `<a target="_blank" href='$1'>$1</a>`)
103−
104+
104105 const filter = window.location.hash.substr(1).replace(/\+/g, ' ').toLowerCase()
105106 const highlight = filter === entry.author.toLowerCase()
106107 const origin = feeds[entry.author].path
107108
108109 return `<li class='entry ${highlight ? 'highlight' : ''}'><span class='date'>${timeAgo(Date.parse(entry.date))}</span> <a class='author' href='${origin}' target='_blank'>${entry.author}</a> <span class='body'>${entry.html}</span></li>`
109110 }
110−
111+
111112 // Feeds
112113
113114 this.findFeeds = function () {
114115 console.log('Finding feeds..')
115− for (const id in sites) {
116− const site = sites[id]
116+ for (const site of sites) {
117117 if (site.feed && site.author) {
118118 feeds[site.author] = { path: site.feed }
119119 }
@@ -141,7 +141,7 @@ function Hallway (sites) {
141141 // Utils
142142
143143 function parseFeed (author, feed) {
144− const lines = feed.split('\n')
144+ const lines = feed.split('\n').filter((line) => line.substr(0, 1) !== '#')
145145 const entries = []
146146 for (const id in lines) {
147147 const line = lines[id].trim()
@@ -150,7 +150,7 @@ function Hallway (sites) {
150150 const date = parts[0].trim()
151151 const body = escapeHtml(parts[1].trim()).trim()
152152 const channel = body.substr(0, 1) === '/' ? body.split(' ')[0].substr(1).toLowerCase() : body.substr(0, 1) === '@' ? 'veranda' : 'lobby'
153− const tags = (body.match(re_tag)||[]).map(a => a.substr(a.indexOf('#')+1).toLowerCase())
153+ const tags = (body.match(re_tag) || []).map(a => a.substr(a.indexOf('#') + 1).toLowerCase())
154154 const offset = new Date() - new Date(date)
155155 entries.push({ date, body, author, offset, channel, tags })
156156 }
@@ -159,9 +159,8 @@ function Hallway (sites) {
159159
160160 function timeAgo (dateParam) {
161161 const date = typeof dateParam === 'object' ? dateParam : new Date(dateParam)
162− const DAY_IN_MS = 86400000 // 24 * 60 * 60 * 1000
163162 const today = new Date()
164− const yesterday = new Date(today - DAY_IN_MS)
163+ const yesterday = new Date(today - 86400000)
165164 const seconds = Math.round((today - date) / 1000)
166165 const minutes = Math.round(seconds / 60)
167166 const isToday = today.toDateString() === date.toDateString()
@@ -183,14 +182,15 @@ function Hallway (sites) {
183182
184183 return `${Math.floor(minutes / 1440)} days ago`
185184 }
185+}
186186
187− function escapeHtml (unsafe) {
188− return unsafe.replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>').replace(/"/g, '"').replace(/'/g, ''')
189− }
187+function escapeHtml (unsafe) {
188+ return unsafe.replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>').replace(/"/g, '"').replace(/'/g, ''')
190189}
191190
192191function filter (name) {
193192 window.location.hash = name
194− hallway.filter = window.location.hash.replace('#', '').trim()
193+ const filterAndPage = window.location.hash.split('^')
194+ hallway.finder = { filter: filterAndPage[0].trim().replace('#', ''), page: filterAndPage[1] || 1 }
195195 hallway.refresh()
196196}