on 569c815 help sign in
webring/scripts/hallway.js
194 lines 5.6 kB history raw
1'use strict'
3function Hallway (sites) {
4 const feeds = {}
5 this.sites = sites
6 this.el = document.createElement('div')
7 this.el.id = 'hallway'
9 this.install = function (host) {
10 host.appendChild(this.el)
11 this.findFeeds()
12 }
14 this.start = function () {
15 this.el.innerHTML = 'hello'
16 this.fetchFeeds()
17 }
19 this.refresh = function (feeds) {
20 const entries = this.findEntries(feeds)
21 const channels = this.findChannels(entries)
22 const users = this.findUsers(entries)
24 let _entries = ''
25 for (const id in entries) {
26 const entry = entries[id]
27 _entries += `${this.templateEntry(entry)}\n`
28 }
30 let _channels = ''
31 for (const id in channels) {
32 _channels += `<li>${id} ${channels[id]}</li>\n`
33 }
35 let _users = ''
36 for (const id in users) {
37 _users += `<li>${id} ${users[id]}</li>\n`
38 }
40 this.el.innerHTML = `
41 <ul id='entries'>
42 ${_entries}
43 </ul>
44 <div id='sidebar'>
45 <ul id='channels'>
46 ${_channels}
47 </ul>
48 <ul id='users'>
49 ${_users}
50 </ul>
51 </div>
52 ${this._footer()}`
53 }
55 // Entries
57 this.findChannels = function (entries) {
58 const channels = {}
59 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
63 }
64 return channels
65 }
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 }
76 this.findEntries = function (feeds) {
77 const a = []
78 for (const id in feeds) {
79 const feed = feeds[id]
80 for (const i in feed.content) {
81 const entry = feed.content[i]
82 a.push(entry)
83 }
84 }
85 return a.sort(compare)
86 }
88 this.templateEntry = function (entry) {
89 // Find mention
90 if (entry.body.indexOf('@<') > -1) {
91 const data = entry.body.split('@<').pop().split('>')[0]
92 const mention = data.split(' ')
93 const name = mention[0]
94 const path = mention[1]
95 entry.body = entry.body.replace(`@<${data}>`, `<a href='${path}'>@${name}</a>`)
96 }
98 if (entry.body.substr(0, 1) === '/') {
99 const channel = entry.body.split(' ')[0]
100 entry.body = entry.body.replace(channel, `<span class='channel'>${channel}</span>`)
101 }
103 const filter = window.location.hash.substr(1).replace(/\+/g, ' ').toLowerCase()
104 const highlight = filter == entry.author.toLowerCase()
106 return `<li class='entry ${highlight ? 'highlight' : ''}'><span class='date'>${timeAgo(Date.parse(entry.date))}</span> <a class='author' href=''>${entry.author}</a> <span class='body'>${entry.body}</span></li>`
107 }
109 // Feeds
111 this.findFeeds = function () {
112 console.log('Finding feeds..')
113 for (const id in sites) {
114 const site = sites[id]
115 if (site.feed && site.author) {
116 feeds[site.author] = { path: site.feed }
117 }
118 }
119 console.log(`Found ${Object.keys(feeds).length} feeds.`)
120 }
122 this.fetchFeeds = function () {
123 console.log(`Fetching ${Object.keys(feeds).length} feeds..`)
124 for (const id in feeds) {
125 this.fetchFeed(id, feeds[id])
126 }
127 }
129 this.fetchFeed = function (id, feed) {
130 console.log(`Fetching ${id}(${feed.path})..`)
132 fetch(feed.path, { cache: 'no-store' }).then(x => x.text()).then((content) => {
133 feeds[id].content = parseFeed(id, content)
134 this.refresh(feeds)
135 }).catch((err) => {
136 console.warn(`${id}`, err)
137 })
138 }
140 // Extras
142 this._footer = function () {
143 return '<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>'
144 }
146 // Utils
148 function compare (a, b) {
149 return a.offset < b.offset ? -1 : a.offset > b.offset ? 1 : 0
150 }
152 function parseFeed (author, feed) {
153 const lines = feed.split('\n')
154 const entries = []
155 for (const id in lines) {
156 const line = lines[id].trim()
157 if (line === '') { continue }
158 const parts = line.replace(' ', '\t').split('\t')
159 const date = parts[0].trim()
160 const body = parts[1].trim()
161 const channel = body.substr(0, 1) === '/' ? body.split(' ')[0].substr(1).toLowerCase() : null
162 const offset = new Date() - new Date(date)
163 entries.push({ date, body, author, offset, channel })
164 }
165 return entries
166 }
168 function timeAgo (dateParam) {
169 const date = typeof dateParam === 'object' ? dateParam : new Date(dateParam)
170 const DAY_IN_MS = 86400000 // 24 * 60 * 60 * 1000
171 const today = new Date()
172 const yesterday = new Date(today - DAY_IN_MS)
173 const seconds = Math.round((today - date) / 1000)
174 const minutes = Math.round(seconds / 60)
175 const isToday = today.toDateString() === date.toDateString()
176 const isYesterday = yesterday.toDateString() === date.toDateString()
178 if (seconds < 5) {
179 return 'now'
180 } else if (seconds < 60) {
181 return `${seconds} seconds ago`
182 } else if (seconds < 90) {
183 return 'about a minute ago'
184 } else if (minutes < 60) {
185 return `${minutes} minutes ago`
186 } else if (isToday) {
187 return `${Math.floor(minutes / 60)} hours ago`
188 } else if (isYesterday) {
189 return 'yesterday'
190 }
192 return `${Math.floor(minutes / 1440)} days ago`
193 }