on 7c82728 help sign in
webring/scripts/hallway.js
177 lines 5.7 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 this.el.innerHTML = `
25 <ul id='entries'>
26 ${entries.filter((val, id) => { return id < 50 }).reduce((acc, val, id) => { return acc + this.templateEntry(val) + '\n' }, '')}
27 </ul>
28 <div id='sidebar'>
29 <ul id='channels'>
30 ${Object.keys(channels).reduce((acc, val, id) => { return acc + `<li>${val} <span class='right'>${channels[val]}</span></li>\n` }, '')}
31 </ul>
32 <ul id='users'>
33 ${Object.keys(users).reduce((acc, val, id) => { return acc + `<li>${val} <span class='right'>${users[val]}</span></li>\n` }, '')}
34 </ul>
35 </div>
36 <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>`
37 }
39 // Entries
41 this.findChannels = function (entries) {
42 const channels = {}
43 for (const id in entries) {
44 const entry = entries[id]
45 if (!entry.channel) { continue }
46 channels[entry.channel] = channels[entry.channel] ? channels[entry.channel] + 1 : 1
47 }
48 return channels
49 }
51 this.findUsers = function (entries) {
52 const users = {}
53 for (const id in entries) {
54 const entry = entries[id]
55 users[entry.author] = users[entry.author] ? users[entry.author] + 1 : 1
56 }
57 return users
58 }
60 this.findEntries = function (feeds) {
61 const a = []
62 for (const id in feeds) {
63 for (const i in feeds[id].content) {
64 a.push(feeds[id].content[i])
65 }
66 }
67 return a.sort(compare)
68 }
70 this.templateEntry = function (entry) {
71 const words = entry.body.split(' ')
72 const users = []
73 const channels = []
74 const tags = []
76 // Channels
77 for (const id in words) {
78 if (words[id].substr(0, 1) === '@') { users.push(words[id]) }
79 if (words[id].substr(0, 1) === '/') { channels.push(words[id]) }
80 if (words[id].substr(0, 1) === '#') { tags.push(words[id]) }
81 }
83 for (const id in users) {
84 entry.body = entry.body.replace(users[id], `<span class='user'>${users[id]}</span>`)
85 }
86 for (const id in channels) {
87 entry.body = entry.body.replace(channels[id], `<span class='user'>${channels[id]}</span>`)
88 }
89 for (const id in tags) {
90 entry.body = entry.body.replace(tags[id], `<span class='tag'>${tags[id]}</span>`)
91 }
93 const filter = window.location.hash.substr(1).replace(/\+/g, ' ').toLowerCase()
94 const highlight = filter === entry.author.toLowerCase()
96 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>`
97 }
99 // Feeds
101 this.findFeeds = function () {
102 console.log('Finding feeds..')
103 for (const id in sites) {
104 const site = sites[id]
105 if (site.feed && site.author) {
106 feeds[site.author] = { path: site.feed }
107 }
108 }
109 console.log(`Found ${Object.keys(feeds).length} feeds.`)
110 }
112 this.fetchFeeds = function () {
113 console.log(`Fetching ${Object.keys(feeds).length} feeds..`)
114 for (const id in feeds) {
115 this.fetchFeed(id, feeds[id])
116 }
117 }
119 this.fetchFeed = function (id, feed) {
120 console.log(`Fetching ${id}(${feed.path})..`)
121 fetch(feed.path, { cache: 'no-store' }).then(x => x.text()).then((content) => {
122 feeds[id].content = parseFeed(id, content)
123 this.refresh(feeds)
124 }).catch((err) => {
125 console.warn(`${id}`, err)
126 })
127 }
129 // Utils
131 function compare (a, b) {
132 return a.offset < b.offset ? -1 : a.offset > b.offset ? 1 : 0
133 }
135 function parseFeed (author, feed) {
136 const lines = feed.split('\n')
137 const entries = []
138 for (const id in lines) {
139 const line = lines[id].trim()
140 if (line === '') { continue }
141 const parts = line.replace(' ', '\t').split('\t')
142 const date = parts[0].trim()
143 const body = parts[1].trim()
144 const channel = body.substr(0, 1) === '/' ? body.split(' ')[0].substr(1).toLowerCase() : null
145 const offset = new Date() - new Date(date)
146 entries.push({ date, body, author, offset, channel })
147 }
148 return entries
149 }
151 function timeAgo (dateParam) {
152 const date = typeof dateParam === 'object' ? dateParam : new Date(dateParam)
153 const DAY_IN_MS = 86400000 // 24 * 60 * 60 * 1000
154 const today = new Date()
155 const yesterday = new Date(today - DAY_IN_MS)
156 const seconds = Math.round((today - date) / 1000)
157 const minutes = Math.round(seconds / 60)
158 const isToday = today.toDateString() === date.toDateString()
159 const isYesterday = yesterday.toDateString() === date.toDateString()
161 if (seconds < 5) {
162 return 'now'
163 } else if (seconds < 60) {
164 return `${seconds} seconds ago`
165 } else if (seconds < 90) {
166 return 'about a minute ago'
167 } else if (minutes < 60) {
168 return `${minutes} minutes ago`
169 } else if (isToday) {
170 return `${Math.floor(minutes / 60)} hours ago`
171 } else if (isYesterday) {
172 return 'yesterday'
173 }
175 return `${Math.floor(minutes / 1440)} days ago`
176 }