diff --git a/api-samples/readingList/README.md b/api-samples/readingList/README.md new file mode 100644 index 0000000000..09ffa3018a --- /dev/null +++ b/api-samples/readingList/README.md @@ -0,0 +1,13 @@ +# chrome.readingList API + +This sample demonstrates using the [chrome.readingList](https://developer.chrome.com/docs/extensions/reference/readingList/) API to view items in the reading list. + +## Overview + +Once this extension is installed, clicking this extension's action icon will open an extension page. + +Screenshot showing the chrome.readingList API demo running in Chrome. + +## Implementation Notes + +Listeners are added for all events, so the table automatically updates when data in the reading list changes. diff --git a/api-samples/readingList/index.css b/api-samples/readingList/index.css new file mode 100644 index 0000000000..97242d2753 --- /dev/null +++ b/api-samples/readingList/index.css @@ -0,0 +1,77 @@ +/* Copyright 2023 Google LLC + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + https://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. */ + +form { + display: inline-block; + border: 1px solid #dadce0; + min-width: 500px; +} + +h1 { + background: #f6f9fe; + margin: 0; + padding: 15px; + font-size: 20px; + text-align: center; +} + +label { + margin: 20px; + display: flex; + justify-content: space-between; +} + +section { + margin: 10px; + border: 2px solid grey; + padding: 10px; +} + +section h2 { + margin: 0; +} + +#error { + display: none; + color: rgb(136, 0, 0); +} + +table { + margin-top: 10px; + border-collapse: collapse; + width: 500px; +} + +tr { + border: 1px solid black; +} + +th:first-child, +td:first-child { + width: 40%; +} + +th, +td { + border: 1px solid black; + padding: 5px; + text-align: center; + width: 20%; +} + +button { + display: block; + margin: 5px 0; + width: 100%; +} diff --git a/api-samples/readingList/index.html b/api-samples/readingList/index.html new file mode 100644 index 0000000000..a792cce51e --- /dev/null +++ b/api-samples/readingList/index.html @@ -0,0 +1,63 @@ + + + + + + + Reading List Demo + + + + + +
+

Reading List Demo

+
+

Add new item

+ + + + +

+
+
+

Items

+ + + + + + + +
TitleReadCreated AtActions
+
+
+ + diff --git a/api-samples/readingList/index.js b/api-samples/readingList/index.js new file mode 100644 index 0000000000..6a44bdb488 --- /dev/null +++ b/api-samples/readingList/index.js @@ -0,0 +1,151 @@ +// Copyright 2023 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +const ADD_ITEM_BUTTON_ID = 'add-item'; +const ITEMS_TABLE_ID = 'items'; +const TABLE_ITEM_TEMPLATE_ID = 'table-item'; +const READ_SELECT_YES_VALUE = 'yes'; +const READ_SELECT_NO_VALUE = 'no'; + +/** + * Removes an entry from the reading list. + * + * @param url URL of entry to remove. + */ +async function removeEntry(url) { + await chrome.readingList.removeEntry({ url }); +} + +/** + * Adds an entry to the reading list. + * + * @param title Title of the entry + * @param url URL of entry to add + * @param hasBeenRead If the entry has been read + */ +async function addEntry(title, url, hasBeenRead) { + await chrome.readingList.addEntry({ title, url, hasBeenRead }); +} + +/** + * Updates an entry in the reading list. + * + * @param url URL of entry to update + * @param hasBeenRead If the entry has been read + */ +async function updateEntry(url, hasBeenRead) { + await chrome.readingList.updateEntry({ url, hasBeenRead }); +} + +/** + * Updates the UI with the current reading list items. + */ +async function updateUI() { + const items = await chrome.readingList.query({}); + + const table = document.getElementById(ITEMS_TABLE_ID); + + for (const item of items) { + // Use existing row if possible, otherwise create a new one. + const row = + document.querySelector(`[data-url="${item.url}"`) || + document.getElementById(TABLE_ITEM_TEMPLATE_ID).content.cloneNode(true) + .children[0]; + + updateRow(row, item); + + table.appendChild(row); + } + + // Remove any rows that no longer exist + table.querySelectorAll('tr').forEach((row, i) => { + // Ignore header row + if (i === 0) return; + if (!items.find((i) => i.url === row.getAttribute('data-url'))) { + row.remove(); + } + }); +} + +/** + * Updates a row with the data from item. + * + * @param row Table row element to update. + * @param item Data from reading list API. + */ +function updateRow(row, item) { + row.setAttribute('data-url', item.url); + + const titleField = row.querySelector('td:nth-child(1) a'); + titleField.href = item.url; + titleField.innerText = item.title; + + const readField = row.querySelector('td:nth-child(2) select'); + readField.value = item.hasBeenRead + ? READ_SELECT_YES_VALUE + : READ_SELECT_NO_VALUE; + + const createdAtField = row.querySelector('td:nth-child(3)'); + createdAtField.innerText = `${new Date(item.creationTime).toLocaleString()}`; + + const deleteButton = row.querySelector('.delete-button'); + deleteButton.addEventListener('click', async (event) => { + event.preventDefault(); + await removeEntry(item.url); + updateUI(); + }); + + const updateButton = row.querySelector('.update-button'); + updateButton.addEventListener('click', async (event) => { + event.preventDefault(); + await updateEntry(item.url, readField.value === READ_SELECT_YES_VALUE); + }); +} + +const ERROR_ID = 'error'; + +const ITEM_TITLE_SELECTOR = '[name="title"]'; +const ITEM_URL_SELECTOR = '[name="url"]'; +const ITEM_READ_SELECTOR = '[name="read"]'; + +// Add item button click handler +document + .getElementById(ADD_ITEM_BUTTON_ID) + .addEventListener('click', async () => { + try { + // Get data from input fields + const title = document.querySelector(ITEM_TITLE_SELECTOR).value; + const url = document.querySelector(ITEM_URL_SELECTOR).value; + const hasBeenRead = + document.querySelector(ITEM_READ_SELECTOR).value === + READ_SELECT_YES_VALUE; + + // Attempt to add the entry + await addEntry(title, url, hasBeenRead); + document.getElementById(ERROR_ID).style.display = 'none'; + } catch (ex) { + // Something went wrong, show an error + document.getElementById(ERROR_ID).innerText = ex.message; + document.getElementById(ERROR_ID).style.display = 'block'; + } + + updateUI(); + }); + +updateUI(); + +// Update the UI whenever data in the reading list changes +chrome.readingList.onEntryAdded.addListener(updateUI); +chrome.readingList.onEntryRemoved.addListener(updateUI); +chrome.readingList.onEntryUpdated.addListener(updateUI); diff --git a/api-samples/readingList/manifest.json b/api-samples/readingList/manifest.json new file mode 100644 index 0000000000..b5bd715a5f --- /dev/null +++ b/api-samples/readingList/manifest.json @@ -0,0 +1,11 @@ +{ + "name": "Reading List API Demo", + "version": "1.0", + "manifest_version": 3, + "description": "Uses the chrome.readingList API to display reading list entries.", + "background": { + "service_worker": "sw.js" + }, + "permissions": ["readingList"], + "action": {} +} diff --git a/api-samples/readingList/screenshot.png b/api-samples/readingList/screenshot.png new file mode 100644 index 0000000000..8bfdf943ac Binary files /dev/null and b/api-samples/readingList/screenshot.png differ diff --git a/api-samples/readingList/sw.js b/api-samples/readingList/sw.js new file mode 100644 index 0000000000..218cfc0fe5 --- /dev/null +++ b/api-samples/readingList/sw.js @@ -0,0 +1,19 @@ +// Copyright 2023 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +chrome.action.onClicked.addListener(openDemoTab); + +function openDemoTab() { + chrome.tabs.create({ url: 'index.html' }); +}