|
|
@ -1,31 +1,92 @@ |
|
|
|
class ThreadWatcher { |
|
|
|
class ThreadWatcher { |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
//todo:
|
|
|
|
|
|
|
|
//- dont increase unread count when refreshing if already have a thread opened
|
|
|
|
|
|
|
|
//- option to hide/minimise/disable thread watcher, or not show unless there is at least 1 thread in watchlist
|
|
|
|
|
|
|
|
//- update settings export to include watched threads
|
|
|
|
|
|
|
|
//- notifications, probably in fetchthread()
|
|
|
|
|
|
|
|
//- try and deal with having more tabs open running refresh more often than necessary. maybe for this it has to
|
|
|
|
|
|
|
|
// use a serviceworker so it only has 1 thread background fetching (problems: needs https so maybe wont work on
|
|
|
|
|
|
|
|
// tor, lokinet, etc). with service worker needs to be some mechanism that only 1 client/tab send message to do
|
|
|
|
|
|
|
|
// a fetch, or maybe it only responds at most every 60 seconds since the client can share with eachother.
|
|
|
|
|
|
|
|
|
|
|
|
init() { |
|
|
|
init() { |
|
|
|
this.loadMap(); |
|
|
|
this.watchListMap = new Map(JSON.parse(localStorage.getItem('watchlist'))); |
|
|
|
//todo: here, update the map and set the unread to 0 if isThread === true
|
|
|
|
window.addEventListener('storage', e => this.storageEventHandler(e));
|
|
|
|
//window.addEventListener('storage', () => this.loadMap()); //this complicates things, and would need to re-render list a lot
|
|
|
|
|
|
|
|
this.settingsInput = document.getElementById('watchlist-setting'); |
|
|
|
this.settingsInput = document.getElementById('watchlist-setting'); |
|
|
|
this.clearButton = document.getElementById('watchlist-clear'); |
|
|
|
this.clearButton = document.getElementById('watchlist-clear'); |
|
|
|
this.clearButton.addEventListener('click', () => this.clear(), false) |
|
|
|
this.clearButton.addEventListener('click', () => this.clear(), false) |
|
|
|
this.createListHtml(); |
|
|
|
const threadMatch = window.location.pathname.match(/^\/(?<threadBoard>\w+)(?:\/manage)?\/thread\/(?<threadId>\d+)\.html$/); |
|
|
|
this.refreshInterval = setInterval(() => this.refresh(), 3 * 1000); |
|
|
|
if (threadMatch && threadMatch.groups) { |
|
|
|
|
|
|
|
const key = `${threadMatch.groups.threadBoard}-${threadMatch.groups.threadId}`; |
|
|
|
|
|
|
|
const data = this.watchListMap.get(key); |
|
|
|
|
|
|
|
if (data) { |
|
|
|
|
|
|
|
data.unread = 0; |
|
|
|
|
|
|
|
data.updatedDate = new Date(); |
|
|
|
|
|
|
|
this.watchListMap.set(key, data); |
|
|
|
|
|
|
|
this.commit(); |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
this.createList(); |
|
|
|
|
|
|
|
this.refreshInterval = setInterval(() => this.refresh(), 60 * 1000); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
refresh() { |
|
|
|
refresh() { |
|
|
|
console.log('refreshing thread watcher'); |
|
|
|
|
|
|
|
for (let t of this.watchListMap.entries()) { |
|
|
|
for (let t of this.watchListMap.entries()) { |
|
|
|
const [board, postId] = t[0].split('-'); |
|
|
|
const [board, postId] = t[0].split('-'); |
|
|
|
const data = t[1]; |
|
|
|
const data = t[1]; |
|
|
|
this.fetchThread(board, postId, data); //async fetch and update
|
|
|
|
this.fetchThread(board, postId, data); |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
//pause() { }
|
|
|
|
async fetchThread(board, postId, data) { |
|
|
|
|
|
|
|
let res, json; |
|
|
|
//resume() { }
|
|
|
|
try { |
|
|
|
|
|
|
|
res = await fetch(`/${board}/thread/${postId}.json`); |
|
|
|
|
|
|
|
json = await res.json(); |
|
|
|
|
|
|
|
} catch (e) { /* ignore */ } |
|
|
|
|
|
|
|
if (json && json.replies && json.replies.length > 0) { |
|
|
|
|
|
|
|
const updatedDate = new Date(data.updatedDate); |
|
|
|
|
|
|
|
const newPosts = json.replies.filter(r => new Date(r.date) > updatedDate); |
|
|
|
|
|
|
|
if (newPosts.length > 0) { |
|
|
|
|
|
|
|
data.subject = json.subject; |
|
|
|
|
|
|
|
data.updatedDate = new Date(); |
|
|
|
|
|
|
|
data.unread += newPosts.length; |
|
|
|
|
|
|
|
const key = `${board}-${postId}`; |
|
|
|
|
|
|
|
this.watchListMap.set(key, data); |
|
|
|
|
|
|
|
this.updateRow(board, postId, data); |
|
|
|
|
|
|
|
this.commit(); |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
} else if (res && res.status === 404) { |
|
|
|
|
|
|
|
console.log('removing 404 thread from watchlist'); |
|
|
|
|
|
|
|
this.remove(board, postId); |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
loadMap() { |
|
|
|
storageEventHandler(e) { |
|
|
|
this.watchListMap = new Map(JSON.parse(localStorage.getItem('watchlist'))); |
|
|
|
if (e.storageArea === localStorage |
|
|
|
|
|
|
|
&& e.key === 'watchlist') { |
|
|
|
|
|
|
|
console.log('updating watchlist from another context'); |
|
|
|
|
|
|
|
const newMap = new Map(JSON.parse(e.newValue)); |
|
|
|
|
|
|
|
const deleted = []; |
|
|
|
|
|
|
|
this.watchListMap.forEach((data, key) => { |
|
|
|
|
|
|
|
if (!newMap.has(key)) { |
|
|
|
|
|
|
|
const [board, postId] = key.split('-'); |
|
|
|
|
|
|
|
this.deleteRow(board, postId); |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
}); |
|
|
|
|
|
|
|
newMap.forEach((data, key) => { |
|
|
|
|
|
|
|
const [board, postId] = key.split('-'); |
|
|
|
|
|
|
|
const oldData = this.watchListMap.get(key); |
|
|
|
|
|
|
|
if (!oldData) { |
|
|
|
|
|
|
|
this.addRow(board, postId, data); |
|
|
|
|
|
|
|
} else if (oldData && (oldData.unread !== data.unread |
|
|
|
|
|
|
|
|| oldData.subject !== data.subject)) { |
|
|
|
|
|
|
|
this.updateRow(board, postId, data); |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
}); |
|
|
|
|
|
|
|
this.watchListMap = new Map(JSON.parse(e.newValue)); |
|
|
|
|
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
commit() { |
|
|
|
commit() { |
|
|
@ -34,7 +95,7 @@ class ThreadWatcher { |
|
|
|
this.settingsInput.value = mapSpread; |
|
|
|
this.settingsInput.value = mapSpread; |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
createListHtml() { |
|
|
|
createList() { |
|
|
|
const threadWatcherHtml = threadwatcher(); |
|
|
|
const threadWatcherHtml = threadwatcher(); |
|
|
|
const footer = document.getElementById('bottom'); |
|
|
|
const footer = document.getElementById('bottom'); |
|
|
|
footer.insertAdjacentHTML('afterend', threadWatcherHtml); |
|
|
|
footer.insertAdjacentHTML('afterend', threadWatcherHtml); |
|
|
@ -43,71 +104,66 @@ class ThreadWatcher { |
|
|
|
for (let t of this.watchListMap.entries()) { |
|
|
|
for (let t of this.watchListMap.entries()) { |
|
|
|
const [board, postId] = t[0].split('-'); |
|
|
|
const [board, postId] = t[0].split('-'); |
|
|
|
const data = t[1]; |
|
|
|
const data = t[1]; |
|
|
|
this.add(board, postId, data, true); |
|
|
|
this.addRow(board, postId, data); |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
async fetchThread(board, postId, data) { |
|
|
|
add(board, postId, data) { |
|
|
|
console.log('thread watcher fetching', board, postId); |
|
|
|
|
|
|
|
let json; |
|
|
|
|
|
|
|
try { |
|
|
|
|
|
|
|
json = await fetch(`/${board}/thread/${postId}.json`).then(res => res.json()); |
|
|
|
|
|
|
|
} catch (e) { |
|
|
|
|
|
|
|
console.error(e); |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
if (json.replies.length > 0) { |
|
|
|
|
|
|
|
const newPosts = json.replies.filter(r => new Date(r.date) > data.updatedDate); |
|
|
|
|
|
|
|
if (newPosts.length > 0) { |
|
|
|
|
|
|
|
console.log('new posts in watched thread', board, postId); |
|
|
|
|
|
|
|
data.updatedDate = new Date(); |
|
|
|
|
|
|
|
data.unread += newPosts.length; |
|
|
|
|
|
|
|
const key = `${board}-${postId}`; |
|
|
|
const key = `${board}-${postId}`; |
|
|
|
|
|
|
|
if (this.watchListMap.has(key)) { |
|
|
|
|
|
|
|
return; |
|
|
|
|
|
|
|
} |
|
|
|
this.watchListMap.set(key, data); |
|
|
|
this.watchListMap.set(key, data); |
|
|
|
|
|
|
|
this.addRow(board, postId, data); |
|
|
|
this.commit(); |
|
|
|
this.commit(); |
|
|
|
const updateRow = this.threadWatcher.querySelector(`[data-id=${key}]`); |
|
|
|
|
|
|
|
updateRow.setAttribute('data-unread', data.unread); |
|
|
|
|
|
|
|
//updateRow.classList.add('bold');
|
|
|
|
|
|
|
|
//todo: notification (and extra setting for if watchlist notifs)
|
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
remove(board, postId) { |
|
|
|
|
|
|
|
this.deleteRow(board, postId); |
|
|
|
|
|
|
|
this.watchListMap.delete(`${board}-${postId}`); |
|
|
|
|
|
|
|
this.commit(); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
add(board, postId, data, insertOnly=false) { |
|
|
|
clear() { |
|
|
|
const key = `${board}-${postId}`; |
|
|
|
for (let t of this.watchListMap.entries()) { |
|
|
|
if (!insertOnly) { |
|
|
|
const [board, postId] = t[0].split('-'); |
|
|
|
if (this.watchListMap.has(key)) { |
|
|
|
const data = t[1]; |
|
|
|
return; //already watching
|
|
|
|
this.deleteRow(board, postId, data); |
|
|
|
} |
|
|
|
} |
|
|
|
console.log('adding', key, 'to watchlist'); |
|
|
|
this.watchListMap = new Map(); |
|
|
|
this.watchListMap.set(key, data); |
|
|
|
this.commit(); |
|
|
|
} |
|
|
|
} |
|
|
|
//todo: modify watchListItemHtml to highlight/bold, if already in selected thread
|
|
|
|
|
|
|
|
|
|
|
|
addRow(board, postId, data) { |
|
|
|
const watchListItemHtml = watchedthread({ watchedthread: { board, postId, ...data } }); |
|
|
|
const watchListItemHtml = watchedthread({ watchedthread: { board, postId, ...data } }); |
|
|
|
this.threadWatcher.insertAdjacentHTML('beforeend', watchListItemHtml); |
|
|
|
this.threadWatcher.insertAdjacentHTML('beforeend', watchListItemHtml); |
|
|
|
const watchedThreadElem = this.threadWatcher.lastChild; |
|
|
|
const watchedThreadElem = this.threadWatcher.lastChild; |
|
|
|
const closeButton = watchedThreadElem.querySelector('.close'); |
|
|
|
const closeButton = watchedThreadElem.querySelector('.close'); |
|
|
|
closeButton.addEventListener('click', e => this.remove(e, key)); |
|
|
|
closeButton.addEventListener('click', e => this.remove(board, postId)); |
|
|
|
//watchedThreadElem.addEventListener('mouseover', () => watchedThreadElem.classList.remove('bold'))
|
|
|
|
|
|
|
|
this.commit(); |
|
|
|
|
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
remove(e, key) { |
|
|
|
deleteRow(board, postId) { |
|
|
|
console.log('removing', key, 'from watchlist'); |
|
|
|
const row = this.threadWatcher.querySelector(`[data-id="${board}-${postId}"]`); |
|
|
|
e.target.parentElement.remove(); |
|
|
|
row.remove(); |
|
|
|
this.watchListMap.delete(key); |
|
|
|
|
|
|
|
this.commit(); |
|
|
|
|
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
clear() { |
|
|
|
updateRow(board, postId, data) { |
|
|
|
console.log('clearing watchlist'); |
|
|
|
const row = this.threadWatcher.querySelector(`[data-id="${board}-${postId}"]`); |
|
|
|
this.watchListMap = new Map(); |
|
|
|
if (data.unread === 0) { |
|
|
|
Array.from(this.threadWatcher.children) |
|
|
|
row.removeAttribute('data-unread'); |
|
|
|
.forEach((c, i) => i > 0 && c.remove()); //remove all except first child (the draghandle)
|
|
|
|
} else { |
|
|
|
setLocalStorage('watchlist', '[]'); |
|
|
|
row.setAttribute('data-unread', data.unread); |
|
|
|
this.commit(); |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
const threadWatcher = new ThreadWatcher(); |
|
|
|
const threadWatcher = new ThreadWatcher(); |
|
|
|
window.addEventListener('settingsReady', () => threadWatcher.init()); |
|
|
|
|
|
|
|
|
|
|
|
window.addEventListener('settingsReady', () => { |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
threadWatcher.init() |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
//settings shit goes here
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
}); |
|
|
|