Skip to content

Commit e64bb10

Browse files
committed
Add Reading List API sample
1 parent b25a56b commit e64bb10

File tree

7 files changed

+334
-0
lines changed

7 files changed

+334
-0
lines changed

api-samples/readingList/README.md

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
# chrome.readingList API
2+
3+
This sample demonstrates using the [chrome.readingList](https://developer.chrome.com/docs/extensions/reference/readingList/) API to view items in the reading list.
4+
5+
## Overview
6+
7+
Once this extension is installed, clicking this extension's action icon will open an extension page.
8+
9+
<img src="screenshot.png" height=300 alt="Screenshot showing the chrome.readingList API demo running in Chrome.">
10+
11+
## Implementation Notes
12+
13+
Listeners are added for all events, so the table automatically updates when data in the reading list changes.

api-samples/readingList/index.css

Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
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+
form {
16+
display: inline-block;
17+
border: 1px solid #dadce0;
18+
min-width: 500px;
19+
}
20+
21+
h1 {
22+
background: #f6f9fe;
23+
margin: 0;
24+
padding: 15px;
25+
font-size: 20px;
26+
text-align: center;
27+
}
28+
29+
label {
30+
margin: 20px;
31+
display: flex;
32+
justify-content: space-between;
33+
}
34+
35+
section {
36+
margin: 10px;
37+
border: 2px solid grey;
38+
padding: 10px;
39+
}
40+
41+
section h2 {
42+
margin: 0;
43+
}
44+
45+
#error {
46+
display: none;
47+
color: rgb(136, 0, 0);
48+
}
49+
50+
table {
51+
margin-top: 10px;
52+
border-collapse: collapse;
53+
width: 500px;
54+
}
55+
56+
tr {
57+
border: 1px solid black;
58+
}
59+
60+
th:first-child,
61+
td:first-child {
62+
width: 40%;
63+
}
64+
65+
th,
66+
td {
67+
border: 1px solid black;
68+
padding: 5px;
69+
text-align: center;
70+
width: 20%;
71+
}
72+
73+
button {
74+
display: block;
75+
margin: 5px 0;
76+
width: 100%;
77+
}

api-samples/readingList/index.html

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
<!DOCTYPE html>
2+
<html lang="en">
3+
<head>
4+
<meta charset="UTF-8" />
5+
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
6+
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
7+
<title>Reading List Demo</title>
8+
<link rel="stylesheet" href="index.css" />
9+
<script defer src="index.js"></script>
10+
</head>
11+
<body>
12+
<template id="table-item">
13+
<tr>
14+
<td><a>Title</a></td>
15+
<td>
16+
<select name="read" value="no">
17+
<option value="no">No</option>
18+
<option value="yes">Yes</option>
19+
</select>
20+
</td>
21+
<td>1/1/1970, 00:00:00</td>
22+
<td>
23+
<button class="update-button">Update</button>
24+
<button class="delete-button">Delete</button>
25+
</td>
26+
</tr>
27+
</template>
28+
<form>
29+
<h1>Reading List Demo</h1>
30+
<section>
31+
<h2>Add new item</h2>
32+
<label>
33+
<span>Title</span>
34+
<input type="text" name="title" value="Example URL" />
35+
</label>
36+
<label>
37+
<span>URL</span>
38+
<input type="text" name="url" value="https://example.com/*" />
39+
</label>
40+
<label>
41+
<span>Has been read</span>
42+
<select name="read" value="no">
43+
<option value="no">No</option>
44+
<option value="yes">Yes</option>
45+
</select>
46+
</label>
47+
<button type="button" id="add-item">Add item</button>
48+
<p id="error"></p>
49+
</section>
50+
<section>
51+
<h2>Items</h2>
52+
<table id="items">
53+
<tr>
54+
<th>Title</th>
55+
<th>Read</th>
56+
<th>Created At</th>
57+
<th>Actions</th>
58+
</tr>
59+
</table>
60+
</section>
61+
</form>
62+
</body>
63+
</html>

api-samples/readingList/index.js

Lines changed: 151 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,151 @@
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);
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
{
2+
"name": "Reading List API Demo",
3+
"version": "1.0",
4+
"manifest_version": 3,
5+
"description": "Uses the chrome.readingList API to display reading list entries.",
6+
"background": {
7+
"service_worker": "sw.js"
8+
},
9+
"permissions": ["readingList"],
10+
"action": {}
11+
}
87 KB
Loading

api-samples/readingList/sw.js

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
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+
chrome.action.onClicked.addListener(openDemoTab);
16+
17+
function openDemoTab() {
18+
chrome.tabs.create({ url: 'index.html' });
19+
}

0 commit comments

Comments
 (0)