|
| 1 | +// Copyright 2023 Google LLC |
| 2 | +// |
| 3 | +// Licensed under the Apache License, Version 2.0 (the "License"); |
| 4 | +// you may not use this file except in compliance with the License. |
| 5 | +// You may obtain a copy of the License at |
| 6 | +// |
| 7 | +// https://www.apache.org/licenses/LICENSE-2.0 |
| 8 | +// |
| 9 | +// Unless required by applicable law or agreed to in writing, software |
| 10 | +// distributed under the License is distributed on an "AS IS" BASIS, |
| 11 | +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| 12 | +// See the License for the specific language governing permissions and |
| 13 | +// limitations under the License. |
| 14 | + |
| 15 | +const ADD_ITEM_BUTTON_ID = 'add-item'; |
| 16 | +const ITEMS_TABLE_ID = 'items'; |
| 17 | +const TABLE_ITEM_TEMPLATE_ID = 'table-item'; |
| 18 | +const READ_SELECT_YES_VALUE = 'yes'; |
| 19 | +const READ_SELECT_NO_VALUE = 'no'; |
| 20 | + |
| 21 | +/** |
| 22 | + * Removes an entry from the reading list. |
| 23 | + * |
| 24 | + * @param url URL of entry to remove. |
| 25 | + */ |
| 26 | +async function removeEntry(url) { |
| 27 | + await chrome.readingList.removeEntry({ url }); |
| 28 | +} |
| 29 | + |
| 30 | +/** |
| 31 | + * Adds an entry to the reading list. |
| 32 | + * |
| 33 | + * @param title Title of the entry |
| 34 | + * @param url URL of entry to add |
| 35 | + * @param hasBeenRead If the entry has been read |
| 36 | + */ |
| 37 | +async function addEntry(title, url, hasBeenRead) { |
| 38 | + await chrome.readingList.addEntry({ title, url, hasBeenRead }); |
| 39 | +} |
| 40 | + |
| 41 | +/** |
| 42 | + * Updates an entry in the reading list. |
| 43 | + * |
| 44 | + * @param url URL of entry to update |
| 45 | + * @param hasBeenRead If the entry has been read |
| 46 | + */ |
| 47 | +async function updateEntry(url, hasBeenRead) { |
| 48 | + await chrome.readingList.updateEntry({ url, hasBeenRead }); |
| 49 | +} |
| 50 | + |
| 51 | +/** |
| 52 | + * Updates the UI with the current reading list items. |
| 53 | + */ |
| 54 | +async function updateUI() { |
| 55 | + const items = await chrome.readingList.query({}); |
| 56 | + |
| 57 | + const table = document.getElementById(ITEMS_TABLE_ID); |
| 58 | + |
| 59 | + for (const item of items) { |
| 60 | + // Use existing row if possible, otherwise create a new one. |
| 61 | + const row = |
| 62 | + document.querySelector(`[data-url="${item.url}"`) || |
| 63 | + document.getElementById(TABLE_ITEM_TEMPLATE_ID).content.cloneNode(true) |
| 64 | + .children[0]; |
| 65 | + |
| 66 | + updateRow(row, item); |
| 67 | + |
| 68 | + table.appendChild(row); |
| 69 | + } |
| 70 | + |
| 71 | + // Remove any rows that no longer exist |
| 72 | + table.querySelectorAll('tr').forEach((row, i) => { |
| 73 | + // Ignore header row |
| 74 | + if (i === 0) return; |
| 75 | + if (!items.find((i) => i.url === row.getAttribute('data-url'))) { |
| 76 | + row.remove(); |
| 77 | + } |
| 78 | + }); |
| 79 | +} |
| 80 | + |
| 81 | +/** |
| 82 | + * Updates a row with the data from item. |
| 83 | + * |
| 84 | + * @param row Table row element to update. |
| 85 | + * @param item Data from reading list API. |
| 86 | + */ |
| 87 | +function updateRow(row, item) { |
| 88 | + row.setAttribute('data-url', item.url); |
| 89 | + |
| 90 | + const titleField = row.querySelector('td:nth-child(1) a'); |
| 91 | + titleField.href = item.url; |
| 92 | + titleField.innerText = item.title; |
| 93 | + |
| 94 | + const readField = row.querySelector('td:nth-child(2) select'); |
| 95 | + readField.value = item.hasBeenRead |
| 96 | + ? READ_SELECT_YES_VALUE |
| 97 | + : READ_SELECT_NO_VALUE; |
| 98 | + |
| 99 | + const createdAtField = row.querySelector('td:nth-child(3)'); |
| 100 | + createdAtField.innerText = `${new Date(item.creationTime).toLocaleString()}`; |
| 101 | + |
| 102 | + const deleteButton = row.querySelector('.delete-button'); |
| 103 | + deleteButton.addEventListener('click', async (event) => { |
| 104 | + event.preventDefault(); |
| 105 | + await removeEntry(item.url); |
| 106 | + updateUI(); |
| 107 | + }); |
| 108 | + |
| 109 | + const updateButton = row.querySelector('.update-button'); |
| 110 | + updateButton.addEventListener('click', async (event) => { |
| 111 | + event.preventDefault(); |
| 112 | + await updateEntry(item.url, readField.value === READ_SELECT_YES_VALUE); |
| 113 | + }); |
| 114 | +} |
| 115 | + |
| 116 | +const ERROR_ID = 'error'; |
| 117 | + |
| 118 | +const ITEM_TITLE_SELECTOR = '[name="title"]'; |
| 119 | +const ITEM_URL_SELECTOR = '[name="url"]'; |
| 120 | +const ITEM_READ_SELECTOR = '[name="read"]'; |
| 121 | + |
| 122 | +// Add item button click handler |
| 123 | +document |
| 124 | + .getElementById(ADD_ITEM_BUTTON_ID) |
| 125 | + .addEventListener('click', async () => { |
| 126 | + try { |
| 127 | + // Get data from input fields |
| 128 | + const title = document.querySelector(ITEM_TITLE_SELECTOR).value; |
| 129 | + const url = document.querySelector(ITEM_URL_SELECTOR).value; |
| 130 | + const hasBeenRead = |
| 131 | + document.querySelector(ITEM_READ_SELECTOR).value === |
| 132 | + READ_SELECT_YES_VALUE; |
| 133 | + |
| 134 | + // Attempt to add the entry |
| 135 | + await addEntry(title, url, hasBeenRead); |
| 136 | + document.getElementById(ERROR_ID).style.display = 'none'; |
| 137 | + } catch (ex) { |
| 138 | + // Something went wrong, show an error |
| 139 | + document.getElementById(ERROR_ID).innerText = ex.message; |
| 140 | + document.getElementById(ERROR_ID).style.display = 'block'; |
| 141 | + } |
| 142 | + |
| 143 | + updateUI(); |
| 144 | + }); |
| 145 | + |
| 146 | +updateUI(); |
| 147 | + |
| 148 | +// Update the UI whenever data in the reading list changes |
| 149 | +chrome.readingList.onEntryAdded.addListener(updateUI); |
| 150 | +chrome.readingList.onEntryRemoved.addListener(updateUI); |
| 151 | +chrome.readingList.onEntryUpdated.addListener(updateUI); |
0 commit comments