mirror of
https://github.com/GoogleChrome/chrome-extensions-samples.git
synced 2026-03-26 13:19:49 +07:00
Add Reading List API sample (#1021)
* Add Reading List API sample * Apply suggestions from code review Co-authored-by: amysteamdev <37001393+AmySteam@users.noreply.github.com> * Add minimum_chrome_version --------- Co-authored-by: amysteamdev <37001393+AmySteam@users.noreply.github.com>
This commit is contained in:
20
api-samples/readingList/README.md
Normal file
20
api-samples/readingList/README.md
Normal file
@@ -0,0 +1,20 @@
|
||||
# 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
|
||||
|
||||
The extension's action icon opens a page that allows you to add new items, as well as update or delete existing ones.
|
||||
|
||||
<img src="screenshot.png" height=300 alt="Screenshot showing the chrome.readingList API demo running in Chrome.">
|
||||
|
||||
## Running this extension
|
||||
|
||||
1. Clone this repository.
|
||||
2. Load this directory in Chrome as an [unpacked extension](https://developer.chrome.com/docs/extensions/mv3/getstarted/development-basics/#load-unpacked).
|
||||
3. Click on the action icon.
|
||||
4. An extension page will open.
|
||||
|
||||
## Implementation Notes
|
||||
|
||||
Listeners are added for all events, so the table automatically updates when data in the reading list changes.
|
||||
77
api-samples/readingList/index.css
Normal file
77
api-samples/readingList/index.css
Normal file
@@ -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%;
|
||||
}
|
||||
63
api-samples/readingList/index.html
Normal file
63
api-samples/readingList/index.html
Normal file
@@ -0,0 +1,63 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>Reading List Demo</title>
|
||||
<link rel="stylesheet" href="index.css" />
|
||||
<script defer src="index.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
<template id="table-item">
|
||||
<tr>
|
||||
<td><a>Title</a></td>
|
||||
<td>
|
||||
<select name="read" value="no">
|
||||
<option value="no">No</option>
|
||||
<option value="yes">Yes</option>
|
||||
</select>
|
||||
</td>
|
||||
<td>1/1/1970, 00:00:00</td>
|
||||
<td>
|
||||
<button class="update-button">Update</button>
|
||||
<button class="delete-button">Delete</button>
|
||||
</td>
|
||||
</tr>
|
||||
</template>
|
||||
<form>
|
||||
<h1>Reading List Demo</h1>
|
||||
<section>
|
||||
<h2>Add new item</h2>
|
||||
<label>
|
||||
<span>Title</span>
|
||||
<input type="text" name="title" value="Example URL" />
|
||||
</label>
|
||||
<label>
|
||||
<span>URL</span>
|
||||
<input type="text" name="url" value="https://example.com/*" />
|
||||
</label>
|
||||
<label>
|
||||
<span>Has been read</span>
|
||||
<select name="read" value="no">
|
||||
<option value="no">No</option>
|
||||
<option value="yes">Yes</option>
|
||||
</select>
|
||||
</label>
|
||||
<button type="button" id="add-item">Add item</button>
|
||||
<p id="error"></p>
|
||||
</section>
|
||||
<section>
|
||||
<h2>Items</h2>
|
||||
<table id="items">
|
||||
<tr>
|
||||
<th>Title</th>
|
||||
<th>Read</th>
|
||||
<th>Created At</th>
|
||||
<th>Actions</th>
|
||||
</tr>
|
||||
</table>
|
||||
</section>
|
||||
</form>
|
||||
</body>
|
||||
</html>
|
||||
151
api-samples/readingList/index.js
Normal file
151
api-samples/readingList/index.js
Normal file
@@ -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);
|
||||
12
api-samples/readingList/manifest.json
Normal file
12
api-samples/readingList/manifest.json
Normal file
@@ -0,0 +1,12 @@
|
||||
{
|
||||
"name": "Reading List API Demo",
|
||||
"version": "1.0",
|
||||
"minimum_chrome_version": "120",
|
||||
"manifest_version": 3,
|
||||
"description": "Uses the chrome.readingList API to display, update and remove reading list entries.",
|
||||
"background": {
|
||||
"service_worker": "sw.js"
|
||||
},
|
||||
"permissions": ["readingList"],
|
||||
"action": {}
|
||||
}
|
||||
BIN
api-samples/readingList/screenshot.png
Normal file
BIN
api-samples/readingList/screenshot.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 87 KiB |
19
api-samples/readingList/sw.js
Normal file
19
api-samples/readingList/sw.js
Normal file
@@ -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' });
|
||||
}
|
||||
Reference in New Issue
Block a user