const DEFAULT_HISTORY_ITEM_COUNT = 20; const EMPTY_HISTORY_MESSAGE = `You haven't visited any sites yet.`; let requestedTop = 0; let lastRequestSize = 0; let itemHeight = 48; const dateStringFormat = new Intl.DateTimeFormat('default', { weekday: 'long', year: 'numeric', month: 'long', day: 'numeric' }); const timeStringFormat = new Intl.DateTimeFormat('default', { hour: '2-digit', minute: '2-digit' }); const messageHandler = event => { var message = event.data.message; var args = event.data.args; switch (message) { case commands.MG_GET_HISTORY: let entriesContainer = document.getElementById('entries-container'); if (args.from == 0 && args.items.length) { entriesContainer.textContent = ''; let clearButton = document.getElementById('btn-clear'); clearButton.classList.remove('hidden'); } loadItems(args.items); if (args.items.length == lastRequestSize) { document.addEventListener('scroll', requestTrigger); } else if (entriesContainer.childElementCount == 0) { loadUIForEmptyHistory(); } break; default: console.log(`Unexpected message: ${JSON.stringify(event.data)}`); break; } }; const requestTrigger = function(event) { let triggerRange = 50; let element = document.body; if (element.scrollTop + element.clientHeight >= element.scrollHeight - triggerRange) { getMoreHistoryItems(); event.target.removeEventListener('scroll', requestTrigger); } }; function requestHistoryItems(from, count) { let message = { message: commands.MG_GET_HISTORY, args: { from: from, count: count || DEFAULT_HISTORY_ITEM_COUNT } }; window.chrome.webview.postMessage(message); } function removeItem(id) { let message = { message: commands.MG_REMOVE_HISTORY_ITEM, args: { id: id } }; window.chrome.webview.postMessage(message); } function createItemElement(item, id, date) { let itemContainer = document.createElement('div'); itemContainer.id = id; itemContainer.className = 'item-container'; let itemElement = document.createElement('div'); itemElement.className = 'item'; // Favicon let faviconElement = document.createElement('div'); faviconElement.className = 'favicon'; let faviconImage = document.createElement('img'); faviconImage.src = item.favicon; faviconElement.append(faviconImage); itemElement.append(faviconElement); // Title let titleLabel = document.createElement('div'); titleLabel.className = 'label-title'; let linkElement = document.createElement('a'); linkElement.href = item.uri; linkElement.title = item.title; linkElement.textContent = item.title; titleLabel.append(linkElement); itemElement.append(titleLabel); // URI let uriLabel = document.createElement('div'); uriLabel.className = 'label-uri'; let textElement = document.createElement('p'); textElement.title = item.uri; textElement.textContent = item.uri; uriLabel.append(textElement); itemElement.append(uriLabel); // Time let timeLabel = document.createElement('div'); timeLabel.className = 'label-time'; let timeText = document.createElement('p'); timeText.textContent = timeStringFormat.format(date); timeLabel.append(timeText); itemElement.append(timeLabel); // Close button let closeButton = document.createElement('div'); closeButton.className = 'btn-close'; closeButton.addEventListener('click', function(e) { if (itemContainer.parentNode.children.length <= 2) { itemContainer.parentNode.remove(); } else { itemContainer.remove(); } let entriesContainer = document.getElementById('entries-container'); if (entriesContainer.childElementCount == 0) { loadUIForEmptyHistory(); } removeItem(parseInt(id.split('-')[1])); }); itemElement.append(closeButton); itemContainer.append(itemElement); return itemContainer; } function createDateContainer(id, date) { let dateContainer = document.createElement('div'); dateContainer.id = id; let dateLabel = document.createElement('h3'); dateLabel.className = 'header-date'; dateLabel.textContent = dateStringFormat.format(date); dateContainer.append(dateLabel); return dateContainer; } function loadItems(items) { let dateContainer; let fragment; items.map((entry) => { let id = entry.id; let item = entry.item; let itemContainerId = `item-${id}`; // Skip the item if already loaded. This could happen if the user // visits an item for the current date again before requesting more // history items. let itemContainer = document.getElementById(itemContainerId); if (itemContainer) { return; } let date = new Date(item.timestamp); let day = date.getDate(); let month = date.getMonth(); let year = date.getFullYear(); let dateContainerId = `entries-${month}-${day}-${year}`; // If entry belongs to a new date, append buffered items for previous // date. if (dateContainer && dateContainer.id != dateContainerId) { dateContainer.append(fragment); } dateContainer = document.getElementById(dateContainerId); if (!dateContainer) { dateContainer = createDateContainer(dateContainerId, date); fragment = document.createDocumentFragment(); let entriesContainer = document.getElementById('entries-container'); entriesContainer.append(dateContainer); } else if (!fragment) { fragment = document.createDocumentFragment(); } itemContainer = createItemElement(item, itemContainerId, date); fragment.append(itemContainer); }); // Append remaining items in buffer if (fragment) { dateContainer.append(fragment); } } function getMoreHistoryItems(n) { n = n ? n : DEFAULT_HISTORY_ITEM_COUNT; requestHistoryItems(requestedTop, n); requestedTop += n; lastRequestSize = n; document.removeEventListener('scroll', requestTrigger); } function addUIListeners() { let confirmButton = document.getElementById('prompt-true'); confirmButton.addEventListener('click', function(event) { clearHistory(); event.stopPropagation(); }); let cancelButton = document.getElementById('prompt-false'); cancelButton.addEventListener('click', function(event) { toggleClearPrompt(); event.stopPropagation(); }); let promptBox = document.getElementById('prompt-box'); promptBox.addEventListener('click', function(event) { event.stopPropagation(); }); let promptOverlay = document.getElementById('overlay'); promptOverlay.addEventListener('click', toggleClearPrompt); let clearButton = document.getElementById('btn-clear'); clearButton.addEventListener('click', toggleClearPrompt); } function toggleClearPrompt() { let promptOverlay = document.getElementById('overlay'); promptOverlay.classList.toggle('hidden'); } function loadUIForEmptyHistory() { let entriesContainer = document.getElementById('entries-container'); entriesContainer.textContent = EMPTY_HISTORY_MESSAGE; let clearButton = document.getElementById('btn-clear'); clearButton.classList.add('hidden'); } function clearHistory() { toggleClearPrompt(); loadUIForEmptyHistory(); let message = { message: commands.MG_CLEAR_HISTORY, args: {} }; window.chrome.webview.postMessage(message); } function init() { window.chrome.webview.addEventListener('message', messageHandler); let viewportItemsCapacity = Math.round(window.innerHeight / itemHeight); addUIListeners(); getMoreHistoryItems(viewportItemsCapacity); } init();