diff --git a/gulp/res/js/watchlist.js b/gulp/res/js/watchlist.js index f81ad91a..de9cbea2 100644 --- a/gulp/res/js/watchlist.js +++ b/gulp/res/js/watchlist.js @@ -1,31 +1,92 @@ 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() { - this.loadMap(); - //todo: here, update the map and set the unread to 0 if isThread === true - //window.addEventListener('storage', () => this.loadMap()); //this complicates things, and would need to re-render list a lot + this.watchListMap = new Map(JSON.parse(localStorage.getItem('watchlist'))); + window.addEventListener('storage', e => this.storageEventHandler(e)); this.settingsInput = document.getElementById('watchlist-setting'); this.clearButton = document.getElementById('watchlist-clear'); this.clearButton.addEventListener('click', () => this.clear(), false) - this.createListHtml(); - this.refreshInterval = setInterval(() => this.refresh(), 3 * 1000); + const threadMatch = window.location.pathname.match(/^\/(?\w+)(?:\/manage)?\/thread\/(?\d+)\.html$/); + 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() { - console.log('refreshing thread watcher'); for (let t of this.watchListMap.entries()) { const [board, postId] = t[0].split('-'); const data = t[1]; - this.fetchThread(board, postId, data); //async fetch and update + this.fetchThread(board, postId, data); } } - //pause() { } - - //resume() { } + async fetchThread(board, postId, data) { + let res, json; + 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() { - this.watchListMap = new Map(JSON.parse(localStorage.getItem('watchlist'))); + storageEventHandler(e) { + 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() { @@ -34,7 +95,7 @@ class ThreadWatcher { this.settingsInput.value = mapSpread; } - createListHtml() { + createList() { const threadWatcherHtml = threadwatcher(); const footer = document.getElementById('bottom'); footer.insertAdjacentHTML('afterend', threadWatcherHtml); @@ -43,71 +104,66 @@ class ThreadWatcher { for (let t of this.watchListMap.entries()) { const [board, postId] = t[0].split('-'); const data = t[1]; - this.add(board, postId, data, true); + this.addRow(board, postId, data); } } - async fetchThread(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}`; - this.watchListMap.set(key, data); - 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) - } + add(board, postId, data) { + const key = `${board}-${postId}`; + if (this.watchListMap.has(key)) { + return; } + this.watchListMap.set(key, data); + this.addRow(board, postId, data); + this.commit(); } - add(board, postId, data, insertOnly=false) { - const key = `${board}-${postId}`; - if (!insertOnly) { - if (this.watchListMap.has(key)) { - return; //already watching - } - console.log('adding', key, 'to watchlist'); - this.watchListMap.set(key, data); + remove(board, postId) { + this.deleteRow(board, postId); + this.watchListMap.delete(`${board}-${postId}`); + this.commit(); + } + + clear() { + for (let t of this.watchListMap.entries()) { + const [board, postId] = t[0].split('-'); + const data = t[1]; + this.deleteRow(board, postId, data); } - //todo: modify watchListItemHtml to highlight/bold, if already in selected thread + this.watchListMap = new Map(); + this.commit(); + } + + addRow(board, postId, data) { const watchListItemHtml = watchedthread({ watchedthread: { board, postId, ...data } }); this.threadWatcher.insertAdjacentHTML('beforeend', watchListItemHtml); const watchedThreadElem = this.threadWatcher.lastChild; const closeButton = watchedThreadElem.querySelector('.close'); - closeButton.addEventListener('click', e => this.remove(e, key)); - //watchedThreadElem.addEventListener('mouseover', () => watchedThreadElem.classList.remove('bold')) - this.commit(); + closeButton.addEventListener('click', e => this.remove(board, postId)); } - remove(e, key) { - console.log('removing', key, 'from watchlist'); - e.target.parentElement.remove(); - this.watchListMap.delete(key); - this.commit(); + deleteRow(board, postId) { + const row = this.threadWatcher.querySelector(`[data-id="${board}-${postId}"]`); + row.remove(); } - clear() { - console.log('clearing watchlist'); - this.watchListMap = new Map(); - Array.from(this.threadWatcher.children) - .forEach((c, i) => i > 0 && c.remove()); //remove all except first child (the draghandle) - setLocalStorage('watchlist', '[]'); - this.commit(); + updateRow(board, postId, data) { + const row = this.threadWatcher.querySelector(`[data-id="${board}-${postId}"]`); + if (data.unread === 0) { + row.removeAttribute('data-unread'); + } else { + row.setAttribute('data-unread', data.unread); + } } } const threadWatcher = new ThreadWatcher(); -window.addEventListener('settingsReady', () => threadWatcher.init()); + +window.addEventListener('settingsReady', () => { + + threadWatcher.init() + + //settings shit goes here + +});