* Ignores archived samples
* Uses eslint/recommended rules
* Runs prettier and eslint (including --fix) pre-commit via husky
* Adds new npm scripts: 'lint', 'lint:fix' and 'prettier'
* Does not lint inline js code
* Fix all prettier and eslint errors
* Add custom prettier rules
* Apply custom prettier rules
* Update readme to explain how to setup the repo
* addressed comments
This commit is contained in:
Sebastian Benz 2023-02-22 13:25:39 +01:00 коммит произвёл GitHub
Родитель 299f2134cb
Коммит dc2174377a
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: 4AEE18F83AFDEB23
83 изменённых файлов: 4798 добавлений и 1074 удалений

3
.eslintignore Normal file
Просмотреть файл

@ -0,0 +1,3 @@
_archive
third-party
node_modules

27
.eslintrc.js Normal file
Просмотреть файл

@ -0,0 +1,27 @@
/* eslint-env node */
module.exports = {
extends: ['prettier', 'eslint:recommended'],
plugins: ['prettier'],
rules: {
'prettier/prettier': ['error'],
'no-unused-vars': [
'warn',
{
argsIgnorePattern: '^_',
varsIgnorePattern: '^_'
}
]
},
env: {
browser: true,
webextensions: true,
es2021: true,
jquery: true,
worker: true
},
overrides: [],
parserOptions: {
ecmaVersion: 'latest',
sourceType: 'module'
}
};

2
.github/ISSUE_TEMPLATE/bug_report.md поставляемый
Просмотреть файл

@ -4,7 +4,6 @@ about: Create a report to help us improve
title: ''
labels: ''
assignees: ''
---
⚠️ If you have general Chrome Extensions questions, consider posting to the [Chromium Extensions Group](https://groups.google.com/a/chromium.org/forum/#!forum/chromium-extensions) or [Stack Overflow](https://stackoverflow.com/questions/tagged/google-chrome-extension).
@ -14,6 +13,7 @@ A clear and concise description of what the bug is.
**To Reproduce**
Steps to reproduce the behavior, or file the issue is found in:
1. Go to '...'
2. Click on '....'
3. Scroll down to '....'

2
.gitignore поставляемый
Просмотреть файл

@ -1,5 +1,5 @@
*~
*.DS_store
node_modules
# Temporary directory for debugging extension samples
_debug

4
.husky/pre-commit Executable file
Просмотреть файл

@ -0,0 +1,4 @@
#!/usr/bin/env sh
. "$(dirname "$0")/_/husky.sh"
npx lint-staged

3
.prettierignore Normal file
Просмотреть файл

@ -0,0 +1,3 @@
_archive
third-party
node_modules

9
.prettierrc.json Normal file
Просмотреть файл

@ -0,0 +1,9 @@
{
"printWidth": 80,
"tabWidth": 2,
"semi": true,
"singleQuote": true,
"trailingComma": "none",
"bracketSpacing": true,
"arrowParens": "always"
}

Просмотреть файл

@ -1,17 +1,48 @@
# Contributing to this Repository
# How to Contribute
Thank you for your interest in contributing!
We'd love to accept your patches and contributions to this project.
Send us your patches early and often and in whatever shape or form.
## Before you begin
## Legal
### Sign our Contributor License Agreement
Unfortunately there are some legal hurdles. Sorry about that.
Contributions to this project must be accompanied by a
[Contributor License Agreement](https://cla.developers.google.com/about) (CLA).
You (or your employer) retain the copyright to your contribution; this simply
gives us permission to use and redistribute your contributions as part of the
project.
This repository is a Google open source project, and so we require contributors to sign Google's open source Contributor License Agreement.
It's easy to do, just click here to sign as an [individual](https://developers.google.com/open-source/cla/individual) or [corporation](https://developers.google.com/open-source/cla/corporate).
Individuals can sign electronically in seconds (see the bottom of the page); corporations will need to email a PDF, or mail.
If you or your current employer have already signed the Google CLA (even if it
was for a different project), you probably don't need to do it again.
We cannot accept PRs or patches larger than fixing typos and the like without a signed CLA.
Visit <https://cla.developers.google.com/> to see your current agreements or to
sign a new one.
If your Github account doesn't show the name you used to sign, please mention your name in your PR.
### Review our Community Guidelines
This project follows [Google's Open Source Community
Guidelines](https://opensource.google/conduct/).
## Contribution process
### Code Reviews
All submissions, including submissions by project members, require review. We
use GitHub pull requests for this purpose. Consult
[GitHub Help](https://help.github.com/articles/about-pull-requests/) for more
information on using pull requests.
### Setting up your Environment
If you want to contribute to this repository, you need to first [create your own fork](https://docs.github.com/en/get-started/quickstart/fork-a-repo).
After forking chrome-extensions-samples to your own Github account, run the following steps to get started:
```sh
# clone your fork to your local machine
git clone https://github.com/your-fork/chrome-extensions-samples.git
cd chrome-extensions-samples
# install dependencies
npm install
```

Просмотреть файл

@ -5,16 +5,16 @@ Note that Chrome Apps are deprecated. Learn more [on the Chromium blog](https://
For more information on extensions, see [Chrome Developers](https://developer.chrome.com).
**Note: Samples for Manifest V3 are still being prepared. In the mean time, consider referring to [_archive/mv2/](_archive/mv2/).**
**Note: Samples for Manifest V3 are still being prepared. In the mean time, consider referring to [\_archive/mv2/](_archive/mv2/).**
## Samples
The directory structure is as follows:
* [api-samples/](api-samples/) - extensions focused on a single API package
* [functional-samples/](functional-samples/) - full featured extensions spanning multiple API packages
* [_archive/apps/](_archive/apps/) - deprecated Chrome Apps platform (not listed below)
* [_archive/mv2/](_archive/mv2/) - resources for manifest version 2
- [api-samples/](api-samples/) - extensions focused on a single API package
- [functional-samples/](functional-samples/) - full featured extensions spanning multiple API packages
- [\_archive/apps/](_archive/apps/) - deprecated Chrome Apps platform (not listed below)
- [\_archive/mv2/](_archive/mv2/) - resources for manifest version 2
To experiment with these samples, please clone this repo and use 'Load Unpacked Extension'.
Read more on [Development Basics](https://developer.chrome.com/docs/extensions/mv3/getstarted/development-basics/#load-unpacked).
@ -119,3 +119,11 @@ Read more on [Development Basics](https://developer.chrome.com/docs/extensions/m
</tr>
</tbody>
</table>
## Contributing
Please see [the CONTRIBUTING file](/CONTRIBUTING.md) for information on contributing to the `chrome-extensions-samples` project.
## License
`chrome-extensions-samples` are authored by Google and are licensed under the [Apache License, Version 2.0](/LICENSE).

Просмотреть файл

@ -4,13 +4,13 @@ p {
.flex {
display: flex;
gap: .25em;
margin: .5em 0;
gap: 0.25em;
margin: 0.5em 0;
align-items: flex-end;
}
.spaced {
margin: .5em 0;
margin: 0.5em 0;
}
.full-width {

Просмотреть файл

@ -1,167 +1,213 @@
<!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>Document</title>
<script defer src="index.js"></script>
<link rel="stylesheet" href="../third-party/awsm/awsm.css">
<link rel="stylesheet" href="index.css">
</head>
<body>
<main>
<section>
<h1>Action API Demo</h1>
<p>Before experimenting with these APIs, we recommend you pin the extension's action button to your
toolbar in order to make it easier to see the changes. </p>
<img src="../images/pin-action.png">
</section>
<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>Document</title>
<script defer src="index.js"></script>
<link rel="stylesheet" href="../third-party/awsm/awsm.css" />
<link rel="stylesheet" href="index.css" />
</head>
<body>
<main>
<section>
<h1>Action API Demo</h1>
<p>
Before experimenting with these APIs, we recommend you pin the
extension's action button to your toolbar in order to make it easier
to see the changes.
</p>
<img src="../images/pin-action.png" />
</section>
<section id="toggle-state">
<h2>enable / disable</h2>
<section id="toggle-state">
<h2>enable / disable</h2>
<p>Clicking the below <em>toggle enabled state</em> button will enable or disable the extensions'
action button in Chrome's toolbar and extensions menu.</p>
<p>
Clicking the below <em>toggle enabled state</em> button will enable or
disable the extensions' action button in Chrome's toolbar and
extensions menu.
</p>
<p>When disabled, clicking the action will not open a popup or trigger <a
href="https://developer.chrome.com/docs/extensions/reference/action/#event-onClicked"><code>action.onClicked</code></a>
events.</p>
<p>
When disabled, clicking the action will not open a popup or trigger
<a
href="https://developer.chrome.com/docs/extensions/reference/action/#event-onClicked"
><code>action.onClicked</code></a
>
events.
</p>
<button id="toggle-state-button">toggle enabled state</button>
<button id="toggle-state-button">toggle enabled state</button>
<div class="flex">
<figure>
<img src="../images/action-enabled.png">
<figcaption>Action enabled</figcaption>
</figure>
<figure>
<img src="../images/action-disabled.png">
<figcaption>Action disabled</figcaption>
</figure>
</div>
</section>
<div class="flex">
<figure>
<img src="../images/action-enabled.png" />
<figcaption>Action enabled</figcaption>
</figure>
<figure>
<img src="../images/action-disabled.png" />
<figcaption>Action disabled</figcaption>
</figure>
</div>
</section>
<section id="popup">
<h2>Popup</h2>
<section id="popup">
<h2>Popup</h2>
<p>This demo's <a href="manifest.json">manifest.json</a> file sets the value of
<code>action.default_popup</code> to <code>popups/popup.html</code>. We can change that behavior at runtime using <a
href="https://developer.chrome.com/docs/extensions/reference/action/#method-setPopup"><code>action.setPopup</code></a>.</p>
<p>
This demo's <a href="manifest.json">manifest.json</a> file sets the
value of <code>action.default_popup</code> to
<code>popups/popup.html</code>. We can change that behavior at runtime
using
<a
href="https://developer.chrome.com/docs/extensions/reference/action/#method-setPopup"
><code>action.setPopup</code></a
>.
</p>
<label>
Change popup page<br>
<select id="popup-options">
<option value="/popups/popup.html">Hello world (default)</option>
<option value="/popups/a.html">A</option>
<option value="/popups/b.html">B</option>
<option value="">onClicked handler</option>
</select>
</label>
<div class="spaced">
<label>
Current popup value
<input type="text" id="current-popup-value" disabled>
Change popup page<br />
<select id="popup-options">
<option value="/popups/popup.html">Hello world (default)</option>
<option value="/popups/a.html">A</option>
<option value="/popups/b.html">B</option>
<option value="">onClicked handler</option>
</select>
</label>
</div>
<p>Register a handler to change how the action button behaves. Once changed, clicking the
action will open your new favorite website.</p>
<button id="onclicked-button">Change action click behavior</button>
<button id="onclicked-reset-button">reset</button>
</section>
<div class="spaced">
<label>
Current popup value
<input type="text" id="current-popup-value" disabled />
</label>
</div>
<!-- badge -->
<p>
Register a handler to change how the action button behaves. Once
changed, clicking the action will open your new favorite website.
</p>
<button id="onclicked-button">Change action click behavior</button>
<button id="onclicked-reset-button">reset</button>
</section>
<section id="badge-text">
<h2>Badge Text</h2>
<!-- badge -->
<p>The action's badge text is a text overlay with a solid background color. This provides a
passive UI surface to share information with the user. It is most commonly used to show a
notification count or number of actions taken on the current page.</p>
<section id="badge-text">
<h2>Badge Text</h2>
<div class="spaced">
<label>
Enter badge text (live update)<br>
<input type="text" id="badge-text-input">
</label>
</div>
<p>
The action's badge text is a text overlay with a solid background
color. This provides a passive UI surface to share information with
the user. It is most commonly used to show a notification count or
number of actions taken on the current page.
</p>
<div class="flex">
<label class="full-width">
Current badge text
<input type="text" id="current-badge-text" disabled>
</label>
<button id="clear-badge-button">clear badge text</button>
</div>
<div class="spaced">
<label>
Enter badge text (live update)<br />
<input type="text" id="badge-text-input" />
</label>
</div>
<div class="spaced">
<button id="set-badge-background-color-button">Randomize badge background color</button>
</div>
<div class="flex">
<label class="full-width">
Current badge text
<input type="text" id="current-badge-text" disabled />
</label>
<button id="clear-badge-button">clear badge text</button>
</div>
<div class="flex">
<label class="full-width">
Current badge color
<input type="text" id="current-badge-bg-color" disabled>
</label>
<button id="reset-badge-background-color-button">reset badge color</button>
</div>
<div class="spaced">
<button id="set-badge-background-color-button">
Randomize badge background color
</button>
</div>
</section>
<div class="flex">
<label class="full-width">
Current badge color
<input type="text" id="current-badge-bg-color" disabled />
</label>
<button id="reset-badge-background-color-button">
reset badge color
</button>
</div>
</section>
<!-- badge - icon -->
<!-- badge - icon -->
<section id="setIcon">
<h2>Icon</h2>
<section id="setIcon">
<h2>Icon</h2>
<p>The <a
href="https://developer.chrome.com/docs/extensions/reference/action/#method-setIcon"><code>action.setIcon</code></a>
method allows you to change the action button's icon by either providing the path of an image
or the raw <a href="https://developer.mozilla.org/en-US/docs/Web/API/ImageData">ImageData</a>.</p>
<p>
The
<a
href="https://developer.chrome.com/docs/extensions/reference/action/#method-setIcon"
><code>action.setIcon</code></a
>
method allows you to change the action button's icon by either
providing the path of an image or the raw
<a href="https://developer.mozilla.org/en-US/docs/Web/API/ImageData"
>ImageData</a
>.
</p>
<button id="set-icon-button">set a new action icon</button>
<button id="reset-icon-button">reset action icon</button>
</section>
<button id="set-icon-button">set a new action icon</button>
<button id="reset-icon-button">reset action icon</button>
</section>
<!-- badge - hover text (title) -->
<!-- badge - hover text (title) -->
<section id="title">
<h2>Hover Text</h2>
<section id="title">
<h2>Hover Text</h2>
<p>The action's title is visible when mousing over the extension's action button.</p>
<p>
The action's title is visible when mousing over the extension's action
button.
</p>
<p>This value can be read and changed at runtime using the <a
href="https://developer.chrome.com/docs/extensions/reference/action/#method-getTitle"><code>action.getTitle</code></a>
and <a
href="https://developer.chrome.com/docs/extensions/reference/action/#method-setTitle"><code>action.setTitle</code></a>
methods, respectively.</p>
<p>
This value can be read and changed at runtime using the
<a
href="https://developer.chrome.com/docs/extensions/reference/action/#method-getTitle"
><code>action.getTitle</code></a
>
and
<a
href="https://developer.chrome.com/docs/extensions/reference/action/#method-setTitle"
><code>action.setTitle</code></a
>
methods, respectively.
</p>
<div class="spaced">
<label>
Enter a new title (debounced)<br>
<input type="text" id="title-input">
</label>
</div>
<div class="spaced">
<label>
Enter a new title (debounced)<br />
<input type="text" id="title-input" />
</label>
</div>
<div class="flex">
<label class="full-width">
Current title
<input type="text" id="current-title" disabled>
</label>
<button id="reset-title-button">reset title</button>
</div>
<div class="flex">
<label class="full-width">
Current title
<input type="text" id="current-title" disabled />
</label>
<button id="reset-title-button">reset title</button>
</div>
<div class="flex">
<figure>
<img src="../images/title-no-hover.png">
<figcaption>Default appearance</figcaption>
</figure>
<figure>
<img src="../images/title-hover.png">
<figcaption>Title appears on hover</figcaption>
</figure>
</section>
</main>
</body>
<div class="flex">
<figure>
<img src="../images/title-no-hover.png" />
<figcaption>Default appearance</figcaption>
</figure>
<figure>
<img src="../images/title-hover.png" />
<figcaption>Title appears on hover</figcaption>
</figure>
</div>
</section>
</main>
</body>
</html>

Просмотреть файл

@ -25,39 +25,43 @@ function debounce(timeout, callback) {
// have to track it ourselves.
// Relevant feature request: https://bugs.chromium.org/p/chromium/issues/detail?id=1189295
let actionEnabled = true;
let showToggleState = document.getElementById('show-toggle-state');
document.getElementById('toggle-state-button').addEventListener('click', (_event) => {
if (actionEnabled) {
chrome.action.disable();
} else {
chrome.action.enable();
}
actionEnabled = !actionEnabled;
});
const showToggleState = document.getElementById('show-toggle-state');
document
.getElementById('toggle-state-button')
.addEventListener('click', (_event) => {
if (actionEnabled) {
chrome.action.disable();
} else {
chrome.action.enable();
}
actionEnabled = !actionEnabled;
});
document.getElementById('popup-options').addEventListener('change', async (event) => {
let popup = event.target.value;
await chrome.action.setPopup({ popup });
document
.getElementById('popup-options')
.addEventListener('change', async (event) => {
const popup = event.target.value;
await chrome.action.setPopup({ popup });
// Show the updated popup path
await getCurrentPopup();
});
// Show the updated popup path
await getCurrentPopup();
});
async function getCurrentPopup() {
let popup = await chrome.action.getPopup({});
const popup = await chrome.action.getPopup({});
document.getElementById('current-popup-value').value = popup;
return popup;
};
}
async function showCurrentPage() {
let popup = await getCurrentPopup();
const popup = await getCurrentPopup();
let pathname = '';
if (popup) {
pathname = new URL(popup).pathname;
}
let options = document.getElementById('popup-options');
let option = options.querySelector(`option[value="${pathname}"]`);
const options = document.getElementById('popup-options');
const option = options.querySelector(`option[value="${pathname}"]`);
option.selected = true;
}
@ -75,129 +79,139 @@ chrome.action.onClicked.addListener((tab) => {
chrome.tabs.create({ url: 'https://html5zombo.com/' });
});
document.getElementById('onclicked-button').addEventListener('click', async () => {
// Our listener will only receive the action's click event after clear out the popup URL
await chrome.action.setPopup({ popup: '' });
await showCurrentPage();
});
document
.getElementById('onclicked-button')
.addEventListener('click', async () => {
// Our listener will only receive the action's click event after clear out the popup URL
await chrome.action.setPopup({ popup: '' });
await showCurrentPage();
});
document.getElementById('onclicked-reset-button').addEventListener('click', async () => {
await chrome.action.setPopup({ popup: 'popups/popup.html' });
await showCurrentPage();
});
document
.getElementById('onclicked-reset-button')
.addEventListener('click', async () => {
await chrome.action.setPopup({ popup: 'popups/popup.html' });
await showCurrentPage();
});
// ----------
// badge text
// ----------
async function showBadgeText() {
let text = await chrome.action.getBadgeText({});
const text = await chrome.action.getBadgeText({});
document.getElementById('current-badge-text').value = text;
}
// Populate badge text inputs on on page load
showBadgeText();
document.getElementById('badge-text-input').addEventListener('input', async (event) => {
let text = event.target.value;
await chrome.action.setBadgeText({ text });
document
.getElementById('badge-text-input')
.addEventListener('input', async (event) => {
const text = event.target.value;
await chrome.action.setBadgeText({ text });
showBadgeText();
});
showBadgeText();
});
document.getElementById('clear-badge-button').addEventListener('click', async () => {
await chrome.action.setBadgeText({ text: '' });
document
.getElementById('clear-badge-button')
.addEventListener('click', async () => {
await chrome.action.setBadgeText({ text: '' });
showBadgeText();
});
showBadgeText();
});
// ----------------------
// badge background color
// ----------------------
async function showBadgeColor() {
let color = await chrome.action.getBadgeBackgroundColor({});
document.getElementById('current-badge-bg-color').value = JSON.stringify(color, null, 0);
const color = await chrome.action.getBadgeBackgroundColor({});
document.getElementById('current-badge-bg-color').value = JSON.stringify(
color,
null,
0
);
}
// Populate badge background color inputs on on page load
showBadgeColor();
document.getElementById('set-badge-background-color-button').addEventListener('click', async () => {
// To show off this method, we must first make sure the badge has text
let currentText = await chrome.action.getBadgeText({});
if (!currentText) {
chrome.action.setBadgeText({ text: 'hi :)' });
showBadgeText();
}
document
.getElementById('set-badge-background-color-button')
.addEventListener('click', async () => {
// To show off this method, we must first make sure the badge has text
let currentText = await chrome.action.getBadgeText({});
if (!currentText) {
chrome.action.setBadgeText({ text: 'hi :)' });
showBadgeText();
}
// Next, generate a random RGBA color
let color = [0, 0, 0].map(() => Math.floor(Math.random() * 255));
// Next, generate a random RGBA color
const color = [0, 0, 0].map(() => Math.floor(Math.random() * 255));
// Use the default background color ~10% of the time.
//
// NOTE: Alpha color cannot be set due to crbug.com/1184905. At the time of writing (Chrome 89),
// an alpha value of 0 sets the default color while a value of 1-255 will make the RGB color
// fully opaque.
if (Math.random() < 0.1) {
color.push(0);
} else {
color.push(255);
}
// Use the default background color ~10% of the time.
//
// NOTE: Alpha color cannot be set due to crbug.com/1184905. At the time of writing (Chrome 89),
// an alpha value of 0 sets the default color while a value of 1-255 will make the RGB color
// fully opaque.
if (Math.random() < 0.1) {
color.push(0);
} else {
color.push(255);
}
chrome.action.setBadgeBackgroundColor({ color });
showBadgeColor();
});
chrome.action.setBadgeBackgroundColor({ color });
showBadgeColor();
});
document.getElementById('reset-badge-background-color-button').addEventListener('click', async () => {
chrome.action.setBadgeBackgroundColor({ color: [0, 0, 0, 0] });
showBadgeColor();
});
document
.getElementById('reset-badge-background-color-button')
.addEventListener('click', async () => {
chrome.action.setBadgeBackgroundColor({ color: [0, 0, 0, 0] });
showBadgeColor();
});
// -----------
// action icon
// -----------
const EMOJI = [
'confetti',
'suit',
'bow',
'dog',
'skull',
'yoyo',
'cat',
];
const EMOJI = ['confetti', 'suit', 'bow', 'dog', 'skull', 'yoyo', 'cat'];
let lastIconIndex = 0;
document.getElementById('set-icon-button').addEventListener('click', async () => {
// Clear out the badge text in order to make the icon change easier to see
chrome.action.setBadgeText({ text: '' });
document
.getElementById('set-icon-button')
.addEventListener('click', async () => {
// Clear out the badge text in order to make the icon change easier to see
chrome.action.setBadgeText({ text: '' });
// Randomly pick a new icon
let index = lastIconIndex;
index = Math.floor(Math.random() * (EMOJI.length));
if (index === lastIconIndex) {
// Dupe detected! Increment the index & modulo to make sure we don't go out of bounds
index = (index + 1) % EMOJI.length;
}
let emojiFile = `images/emoji-${EMOJI[index]}.png`;
lastIconIndex = index;
// Randomly pick a new icon
let index = lastIconIndex;
index = Math.floor(Math.random() * EMOJI.length);
if (index === lastIconIndex) {
// Dupe detected! Increment the index & modulo to make sure we don't go out of bounds
index = (index + 1) % EMOJI.length;
}
const emojiFile = `images/emoji-${EMOJI[index]}.png`;
lastIconIndex = index;
// There are easier ways for a page to extract an image's imageData, but the approach used here
// works in both extension pages and service workers.
let response = await fetch(chrome.runtime.getURL(emojiFile));
let blob = await response.blob();
let imageBitmap = await createImageBitmap(blob);
let osc = new OffscreenCanvas(imageBitmap.width, imageBitmap.height);
let ctx = osc.getContext('2d');
ctx.drawImage(imageBitmap, 0, 0);
let imageData = ctx.getImageData(0, 0, osc.width, osc.height);
// There are easier ways for a page to extract an image's imageData, but the approach used here
// works in both extension pages and service workers.
const response = await fetch(chrome.runtime.getURL(emojiFile));
const blob = await response.blob();
const imageBitmap = await createImageBitmap(blob);
const osc = new OffscreenCanvas(imageBitmap.width, imageBitmap.height);
let ctx = osc.getContext('2d');
ctx.drawImage(imageBitmap, 0, 0);
const imageData = ctx.getImageData(0, 0, osc.width, osc.height);
chrome.action.setIcon({ imageData });
});
chrome.action.setIcon({ imageData });
});
document.getElementById('reset-icon-button').addEventListener('click', () => {
let manifest = chrome.runtime.getManifest();
const manifest = chrome.runtime.getManifest();
chrome.action.setIcon({ path: manifest.action.default_icon });
});
@ -205,23 +219,28 @@ document.getElementById('reset-icon-button').addEventListener('click', () => {
// get/set title
// -------------
let titleInput = document.getElementById('title-input');
let titleInputDebounce = Number.parseInt(titleInput.dataset.debounce || 100);
titleInput.addEventListener('input', debounce(200, async (event) => {
let title = event.target.value;
chrome.action.setTitle({ title });
const titleInput = document.getElementById('title-input');
const titleInputDebounce = Number.parseInt(titleInput.dataset.debounce || 100);
titleInput.addEventListener(
'input',
debounce(200, async (event) => {
const title = event.target.value;
chrome.action.setTitle({ title });
showActionTitle();
}));
showActionTitle();
})
);
document.getElementById('reset-title-button').addEventListener('click', async (event) => {
let manifest = chrome.runtime.getManifest();
let title = manifest.action.default_title;
document
.getElementById('reset-title-button')
.addEventListener('click', async (event) => {
const manifest = chrome.runtime.getManifest();
let title = manifest.action.default_title;
chrome.action.setTitle({ title });
chrome.action.setTitle({ title });
showActionTitle();
});
showActionTitle();
});
async function showActionTitle() {
let title = await chrome.action.getTitle({});
@ -229,7 +248,7 @@ async function showActionTitle() {
// If empty, the title falls back to the name of the extension
if (title === '') {
// … which we can get from the extension's manifest
let manifest = chrome.runtime.getManifest();
const manifest = chrome.runtime.getManifest();
title = manifest.name;
}

Просмотреть файл

@ -1,32 +1,32 @@
<!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>Document</title>
<style>
.center {
min-height: 100px;
min-width: 200px;
display: grid;
flex-direction: column;
align-items: center;
justify-content: center;
gap: 1ch;
background-color: salmon;
}
.text {
font-size: 2rem;
font-weight: bold;
color: white;
}
</style>
</head>
<body>
<h2>Action API Demo</h2>
<div class="center">
<span class="text">A</span>
</div>
</body>
<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>Document</title>
<style>
.center {
min-height: 100px;
min-width: 200px;
display: grid;
flex-direction: column;
align-items: center;
justify-content: center;
gap: 1ch;
background-color: salmon;
}
.text {
font-size: 2rem;
font-weight: bold;
color: white;
}
</style>
</head>
<body>
<h2>Action API Demo</h2>
<div class="center">
<span class="text">A</span>
</div>
</body>
</html>

Просмотреть файл

@ -1,32 +1,32 @@
<!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>Document</title>
<style>
.center {
min-height: 100px;
min-width: 200px;
display: grid;
flex-direction: column;
align-items: center;
justify-content: center;
gap: 1ch;
background-color: royalblue;
}
.text {
font-size: 2rem;
font-weight: bold;
color: white;
}
</style>
</head>
<body>
<h2>Action API Demo</h2>
<div class="center">
<span class="text">B</span>
</div>
</body>
<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>Document</title>
<style>
.center {
min-height: 100px;
min-width: 200px;
display: grid;
flex-direction: column;
align-items: center;
justify-content: center;
gap: 1ch;
background-color: royalblue;
}
.text {
font-size: 2rem;
font-weight: bold;
color: white;
}
</style>
</head>
<body>
<h2>Action API Demo</h2>
<div class="center">
<span class="text">B</span>
</div>
</body>
</html>

Просмотреть файл

@ -1,32 +1,32 @@
<!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>Document</title>
<style>
.center {
min-height: 100px;
min-width: 200px;
display: grid;
flex-direction: column;
align-items: center;
justify-content: center;
gap: 1ch;
background-color: lightseagreen;
}
.text {
font-size: 2rem;
font-weight: bold;
color: white;
}
</style>
</head>
<body>
<h2>Action API Demo</h2>
<div class="center">
<span class="text">Hello, world!</span>
</div>
</body>
<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>Document</title>
<style>
.center {
min-height: 100px;
min-width: 200px;
display: grid;
flex-direction: column;
align-items: center;
justify-content: center;
gap: 1ch;
background-color: lightseagreen;
}
.text {
font-size: 2rem;
font-weight: bold;
color: white;
}
</style>
</head>
<body>
<h2>Action API Demo</h2>
<div class="center">
<span class="text">Hello, world!</span>
</div>
</body>
</html>

Просмотреть файл

@ -6,14 +6,16 @@
// Initialize the demo on install
chrome.runtime.onInstalled.addListener((reason) => {
if (reason !== chrome.runtime.OnInstalledReason.INSTALL) { return }
if (reason !== chrome.runtime.OnInstalledReason.INSTALL) {
return;
}
openDemoTab();
// Create an alarm so we have something to look at in the demo
chrome.alarms.create('demo-default-alarm', {
delayInMinutes: 1,
periodInMinutes: 1,
periodInMinutes: 1
});
});

Просмотреть файл

@ -43,7 +43,7 @@ body {
.alarm-display,
.alarm-log {
min-height: 2em;
padding: .5em;
padding: 0.5em;
background-color: hsl(0, 0%, 95%);
border: 1px solid hsl(0, 0%, 80%);
border-radius: 4px;
@ -55,7 +55,7 @@ body {
}
.alarm-log > * {
padding: .5em;
padding: 0.5em;
}
.alarm-log > *:not(:first-child) {
@ -67,7 +67,7 @@ body {
}
.alarm-row {
padding: .5em;
padding: 0.5em;
position: relative;
border-top: 1px solid transparent;
border-bottom: 1px solid transparent;
@ -75,7 +75,7 @@ body {
.alarm-row:hover,
.alarm-log > *:hover {
background: hsl(190, 20%, 90%)
background: hsl(190, 20%, 90%);
}
.alarm-row:not(:first-child) {
@ -92,6 +92,6 @@ body {
.alarm-row__cancel-button {
position: absolute;
top: .5em;
right: .5em;
top: 0.5em;
right: 0.5em;
}

Просмотреть файл

@ -1,66 +1,75 @@
<!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>Document</title>
<link rel="stylesheet" href="index.css">
<script defer src="index.js"></script>
</head>
<body>
<section>
<h2>Create Alarm</h2>
<form class="create-alarm">
<div class="create-alarm__label">Name</div>
<div class="create-alarm__value">
<input type="text" name="alarm-name" value="my-alarm">
</div>
<div class="create-alarm__label">Initial delay *</div>
<div class="create-alarm__value">
<input type="number" step="0.1" name="time-value" min="0" value=1>
<select name="time-format">
<option id="format-minutes" value="min" selected>minutes</option>
<option id="format-ms" value="ms">milliseconds</option>
</select>
</div>
<div class="create-alarm__label">Repetition period *</div>
<div class="create-alarm__value">
<input type="number" step="0.1" min="0" name="period" value="0"> minutes
<br><i>Non-zero values create a repeating alarm that repeats every period.</i>
</div>
<div class="create-alarm__label">
*
</div>
<div class="create-alarm__value">
<i>Can be set to &lt; 1 min in an unpacked extension, but not in a distributed CRX file.</i>
</div>
<button type="submit" class="create-alarm__submit">Submit</button>
</form>
</section>
<section class="col-2">
<section class="">
<h2>Current Alarms
<div class="display-buttons">
<button id="clear-display">Cancel all alarms</button>
<button id="refresh-display" title="Clear display and re-recreate alarm UI">Refresh</button>
</div>
</h2>
<pre class="alarm-display"></pre>
</section>
<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>Document</title>
<link rel="stylesheet" href="index.css" />
<script defer src="index.js"></script>
</head>
<body>
<section>
<h2>Alarm log</h2>
<pre class="alarm-log"></pre>
<h2>Create Alarm</h2>
<form class="create-alarm">
<div class="create-alarm__label">Name</div>
<div class="create-alarm__value">
<input type="text" name="alarm-name" value="my-alarm" />
</div>
<div class="create-alarm__label">Initial delay *</div>
<div class="create-alarm__value">
<input type="number" step="0.1" name="time-value" min="0" value="1" />
<select name="time-format">
<option id="format-minutes" value="min" selected>minutes</option>
<option id="format-ms" value="ms">milliseconds</option>
</select>
</div>
<div class="create-alarm__label">Repetition period *</div>
<div class="create-alarm__value">
<input type="number" step="0.1" min="0" name="period" value="0" />
minutes <br /><i
>Non-zero values create a repeating alarm that repeats every
period.</i
>
</div>
<div class="create-alarm__label">*</div>
<div class="create-alarm__value">
<i
>Can be set to &lt; 1 min in an unpacked extension, but not in a
distributed CRX file.</i
>
</div>
<button type="submit" class="create-alarm__submit">Submit</button>
</form>
</section>
</section>
</body>
<section class="col-2">
<section class="">
<h2>
Current Alarms
<div class="display-buttons">
<button id="clear-display">Cancel all alarms</button>
<button
id="refresh-display"
title="Clear display and re-recreate alarm UI"
>
Refresh
</button>
</div>
</h2>
<pre class="alarm-display"></pre>
</section>
<section>
<h2>Alarm log</h2>
<pre class="alarm-log"></pre>
</section>
</section>
</body>
</html>

Просмотреть файл

@ -13,26 +13,26 @@ const pad = (val, len = 2) => val.toString().padStart(len, '0');
// DOM event bindings
//// Alarm display buttons
// Alarm display buttons
clearButton.addEventListener('click', () => manager.cancelAllAlarms());
refreshButton.addEventListener('click', () => manager.refreshDisplay());
//// New alarm form
// New alarm form
form.addEventListener('submit', (event) => {
event.preventDefault();
let formData = new FormData(form);
let data = Object.fromEntries(formData);
const formData = new FormData(form);
const data = Object.fromEntries(formData);
// Extract form values
let name = data['alarm-name'];
let delay = Number.parseFloat(data['time-value']);
let delayFormat = data['time-format'];
let period = Number.parseFloat(data['period']);
const name = data['alarm-name'];
const delay = Number.parseFloat(data['time-value']);
const delayFormat = data['time-format'];
const period = Number.parseFloat(data['period']);
// Prepare alarm info for creation call
let alarmInfo = {};
const alarmInfo = {};
if (delayFormat === 'ms') {
// Specified in milliseconds, use `when` property
@ -62,15 +62,15 @@ class AlarmManager {
}
logMessage(message) {
let date = new Date();
let pad = (val, len = 2) => val.toString().padStart(len, '0');
let h = pad(date.getHours());
let m = pad(date.getMinutes());
let s = pad(date.getSeconds());
let ms = pad(date.getMilliseconds(), 3);
let time = `${h}:${m}:${s}.${ms}`;
const date = new Date();
const pad = (val, len = 2) => val.toString().padStart(len, '0');
const h = pad(date.getHours());
const m = pad(date.getMinutes());
const s = pad(date.getSeconds());
const ms = pad(date.getMilliseconds(), 3);
const time = `${h}:${m}:${s}.${ms}`;
let logLine = document.createElement('div');
const logLine = document.createElement('div');
logLine.textContent = `[${time}] ${message}`;
// Log events in reverse chronological order
@ -78,20 +78,20 @@ class AlarmManager {
}
handleAlarm = async (alarm) => {
let json = JSON.stringify(alarm);
const json = JSON.stringify(alarm);
this.logMessage(`Alarm "${alarm.name}" fired\n${json}}`);
await this.refreshDisplay();
}
};
handleCancelAlarm = async (event) => {
if (!event.target.classList.contains('alarm-row__cancel-button')) {
return;
}
let name = event.target.parentElement.dataset.name;
const name = event.target.parentElement.dataset.name;
await this.cancelAlarm(name);
await this.refreshDisplay();
}
};
async cancelAlarm(name) {
// TODO: Remove custom promise wrapper once the Alarms API supports promises
@ -111,18 +111,18 @@ class AlarmManager {
// Thin wrapper around alarms.create to log creation event
createAlarm(name, alarmInfo) {
chrome.alarms.create(name, alarmInfo);
let json = JSON.stringify(alarmInfo, null, 2).replace(/\s+/g, ' ');
const json = JSON.stringify(alarmInfo, null, 2).replace(/\s+/g, ' ');
this.logMessage(`Created "${name}"\n${json}`);
this.refreshDisplay();
}
renderAlarm(alarm, isLast) {
let alarmEl = document.createElement('div');
const alarmEl = document.createElement('div');
alarmEl.classList.add('alarm-row');
alarmEl.dataset.name = alarm.name;
alarmEl.textContent = JSON.stringify(alarm, 0, 2) + (isLast ? '' : ',');
let cancelButton = document.createElement('button');
const cancelButton = document.createElement('button');
cancelButton.classList.add('alarm-row__cancel-button');
cancelButton.textContent = 'cancel';
alarmEl.appendChild(cancelButton);
@ -142,15 +142,15 @@ class AlarmManager {
resolve(wasCleared);
});
})
});
}
async populateDisplay() {
// TODO: Remove custom promise wrapper once the Alarms API supports promises
return new Promise((resolve) => {
chrome.alarms.getAll((alarms) => {
for (let [index, alarm] of alarms.entries()) {
let isLast = index === alarms.length - 1;
for (const [index, alarm] of alarms.entries()) {
const isLast = index === alarms.length - 1;
this.renderAlarm(alarm, isLast);
}
resolve();
@ -163,16 +163,15 @@ class AlarmManager {
#refreshing = false;
async refreshDisplay() {
if (this.#refreshing) { return } // refresh in progress, bail
if (this.#refreshing) {
return;
} // refresh in progress, bail
this.#refreshing = true; // acquire lock
this.#refreshing = true; // acquire lock
try {
await Promise.all([
this.clearDisplay(),
this.populateDisplay(),
]);
await Promise.all([this.clearDisplay(), this.populateDisplay()]);
} finally {
this.#refreshing = false; // release lock
this.#refreshing = false; // release lock
}
}
@ -181,5 +180,5 @@ class AlarmManager {
}
}
let manager = new AlarmManager(display, log);
const manager = new AlarmManager(display, log);
manager.refreshDisplay();

Просмотреть файл

@ -5,8 +5,6 @@
"background": {
"service_worker": "bg-wrapper.js"
},
"permissions": [
"alarms"
],
"permissions": ["alarms"],
"action": {}
}

Просмотреть файл

@ -3,56 +3,54 @@
// found in the LICENSE file.
// When you specify "type": "module" in the manifest background,
// you can include the service worker as an ES Module,
import { tldLocales } from './locales.js'
// you can include the service worker as an ES Module,
import { tldLocales } from './locales.js';
// Add a listener to create the initial context menu items,
// context menu items only need to be created at runtime.onInstalled
chrome.runtime.onInstalled.addListener(async () => {
for (let [tld, locale] of Object.entries(tldLocales)) {
for (const [tld, locale] of Object.entries(tldLocales)) {
chrome.contextMenus.create({
id: tld,
title: locale,
type: 'normal',
contexts: ['selection'],
contexts: ['selection']
});
}
});
// Open a new search tab when the user clicks a context menu
chrome.contextMenus.onClicked.addListener((item, tab) => {
const tld = item.menuItemId
let url = new URL(`https://google.${tld}/search`)
url.searchParams.set('q', item.selectionText)
const tld = item.menuItemId;
const url = new URL(`https://google.${tld}/search`);
url.searchParams.set('q', item.selectionText);
chrome.tabs.create({ url: url.href, index: tab.index + 1 });
});
// Add or removes the locale from context menu
// when the user checks or unchecks the locale in the popup
chrome.storage.onChanged.addListener(({ enabledTlds }) => {
if (typeof enabledTlds === 'undefined') return
if (typeof enabledTlds === 'undefined') return;
let allTlds = Object.keys(tldLocales)
let currentTlds = new Set(enabledTlds.newValue);
let oldTlds = new Set(enabledTlds.oldValue ?? allTlds);
let changes = allTlds.map((tld) => ({
const allTlds = Object.keys(tldLocales);
const currentTlds = new Set(enabledTlds.newValue);
const oldTlds = new Set(enabledTlds.oldValue ?? allTlds);
const changes = allTlds.map((tld) => ({
tld,
added: currentTlds.has(tld) && !oldTlds.has(tld),
removed: !currentTlds.has(tld) && oldTlds.has(tld)
}))
}));
for (let { tld, added, removed } of changes) {
for (const { tld, added, removed } of changes) {
if (added) {
chrome.contextMenus.create({
id: tld,
title: tldLocales[tld],
type: 'normal',
contexts: ['selection'],
contexts: ['selection']
});
}
else if (removed) {
} else if (removed) {
chrome.contextMenus.remove(tld);
}
}
});

Просмотреть файл

@ -6,14 +6,14 @@
export const tldLocales = {
'com.au': 'Australia',
'com.br': 'Brazil',
'ca': 'Canada',
'cn': 'China',
'fr': 'France',
'it': 'Italy',
ca: 'Canada',
cn: 'China',
fr: 'France',
it: 'Italy',
'co.in': 'India',
'co.jp': 'Japan',
'com.ms': 'Mexico',
'ru': 'Russia',
ru: 'Russia',
'co.za': 'South Africa',
'co.uk': 'United Kingdom'
};

Просмотреть файл

@ -12,8 +12,8 @@
"default_popup": "popup.html"
},
"icons": {
"16": "globalGoogle16.png",
"48": "globalGoogle48.png",
"128": "globalGoogle128.png"
}
"16": "globalGoogle16.png",
"48": "globalGoogle48.png",
"128": "globalGoogle128.png"
}
}

Просмотреть файл

@ -1,26 +1,24 @@
<!DOCTYPE html>
<html>
<head>
<title>Global Context Search</title>
<style>
body {
min-width: 300px;
font-size: 15px;
}
<head>
<title>Global Context Search</title>
<style>
body {
min-width: 300px;
font-size: 15px;
}
input {
margin: 5px;
outline: none;
}
</style>
</head>
input {
margin: 5px;
outline: none;
}
</style>
</head>
<body>
<h2>Global Google Search</h2>
<h3>Countries</h3>
<form id="form"></form>
<script src="popup.js" type="module"></script>
</body>
</html>
<body>
<h2>Global Google Search</h2>
<h3>Countries</h3>
<form id="form"></form>
<script src="popup.js" type="module"></script>
</body>
</html>

Просмотреть файл

@ -3,48 +3,47 @@
// found in the LICENSE file.
// TLD: top level domain; the "com" in "google.com"
import { tldLocales } from './locales.js'
import { tldLocales } from './locales.js';
createForm().catch(console.error);
async function createForm() {
let { enabledTlds = Object.keys(tldLocales) } = await chrome.storage.sync.get('enabledTlds');
let checked = new Set(enabledTlds)
const { enabledTlds = Object.keys(tldLocales) } =
await chrome.storage.sync.get('enabledTlds');
const checked = new Set(enabledTlds);
let form = document.getElementById('form');
for (let [tld, locale] of Object.entries(tldLocales)) {
let checkbox = document.createElement('input');
const form = document.getElementById('form');
for (const [tld, locale] of Object.entries(tldLocales)) {
const checkbox = document.createElement('input');
checkbox.type = 'checkbox';
checkbox.checked = checked.has(tld);
checkbox.name = tld;
checkbox.addEventListener('click', (event) => {
handleCheckboxClick(event).catch(console.error)
})
let span = document.createElement('span');
handleCheckboxClick(event).catch(console.error);
});
const span = document.createElement('span');
span.textContent = locale;
let div = document.createElement('div');
const div = document.createElement('div');
div.appendChild(checkbox);
div.appendChild(span);
form.appendChild(div);
}
}
async function handleCheckboxClick(event) {
let checkbox = event.target
let tld = checkbox.name
let enabled = checkbox.checked
const checkbox = event.target;
const tld = checkbox.name;
const enabled = checkbox.checked;
let { enabledTlds = Object.keys(tldLocales) } = await chrome.storage.sync.get('enabledTlds');
let tldSet = new Set(enabledTlds)
if (enabled) tldSet.add(tld)
else tldSet.delete(tld)
await chrome.storage.sync.set({ enabledTlds: [...tldSet] })
const { enabledTlds = Object.keys(tldLocales) } =
await chrome.storage.sync.get('enabledTlds');
const tldSet = new Set(enabledTlds);
if (enabled) tldSet.add(tld);
else tldSet.delete(tld);
await chrome.storage.sync.set({ enabledTlds: [...tldSet] });
}

Просмотреть файл

@ -7,4 +7,4 @@
"action": {
"default_popup": "popup.html"
}
}
}

Просмотреть файл

@ -1,15 +1,15 @@
<!doctype html>
<!DOCTYPE html>
<html>
<head>
<script src="popup.js" type="module"></script>
</head>
<body>
<form id="control-row">
<label for="input">Domain:</label>
<input type="text" id="input">
<br>
<button id="go">Clear Cookies</button>
</form>
<span id="message" hidden></span>
</body>
<body>
<form id="control-row">
<label for="input">Domain:</label>
<input type="text" id="input" />
<br />
<button id="go">Clear Cookies</button>
</form>
<span id="message" hidden></span>
</body>
</html>

Просмотреть файл

@ -1,7 +1,7 @@
const form = document.getElementById("control-row");
const go = document.getElementById("go");
const input = document.getElementById("input");
const message = document.getElementById("message");
const form = document.getElementById('control-row');
const go = document.getElementById('go');
const input = document.getElementById('input');
const message = document.getElementById('message');
// The async IIFE is necessary because Chrome <89 does not support top level await.
(async function initPopupWindow() {
@ -11,13 +11,15 @@ const message = document.getElementById("message");
try {
let url = new URL(tab.url);
input.value = url.hostname;
} catch {}
} catch {
// ignore
}
}
input.focus();
})();
form.addEventListener("submit", handleFormSubmit);
form.addEventListener('submit', handleFormSubmit);
async function handleFormSubmit(event) {
event.preventDefault();
@ -26,7 +28,7 @@ async function handleFormSubmit(event) {
let url = stringToUrl(input.value);
if (!url) {
setMessage("Invalid URL");
setMessage('Invalid URL');
return;
}
@ -38,11 +40,15 @@ function stringToUrl(input) {
// Start with treating the provided value as a URL
try {
return new URL(input);
} catch {}
} catch {
// ignore
}
// If that fails, try assuming the provided input is an HTTP host
try {
return new URL("http://" + input);
} catch {}
return new URL('http://' + input);
} catch {
// ignore
}
// If that fails ¯\_(ツ)_/¯
return null;
}
@ -53,7 +59,7 @@ async function deleteDomainCookies(domain) {
const cookies = await chrome.cookies.getAll({ domain });
if (cookies.length === 0) {
return "No cookies found";
return 'No cookies found';
}
let pending = cookies.map(deleteCookie);
@ -76,7 +82,7 @@ function deleteCookie(cookie) {
// To remove cookies set with a Secure attribute, we must provide the correct protocol in the
// details object's `url` property.
// https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Set-Cookie#Secure
const protocol = cookie.secure ? "https:" : "http:";
const protocol = cookie.secure ? 'https:' : 'http:';
// Note that the final URL may not be valid. The domain value for a standard cookie is prefixed
// with a period (invalid) while cookies that are set to `cookie.hostOnly == true` do not have
@ -87,7 +93,7 @@ function deleteCookie(cookie) {
return chrome.cookies.remove({
url: cookieUrl,
name: cookie.name,
storeId: cookie.storeId,
storeId: cookie.storeId
});
}
@ -98,5 +104,5 @@ function setMessage(str) {
function clearMessage() {
message.hidden = true;
message.textContent = "";
message.textContent = '';
}

Просмотреть файл

@ -4,13 +4,18 @@
chrome.commands.onCommand.addListener(async (command) => {
const tabs = await chrome.tabs.query({ currentWindow: true });
// Sort tabs according to their index in the window.
tabs.sort((a, b) => { return a.index < b.index; });
let activeIndex = tabs.findIndex((tab) => { return tab.active; });
let lastTab = tabs.length - 1;
tabs.sort((a, b) => {
return a.index < b.index;
});
const activeIndex = tabs.findIndex((tab) => {
return tab.active;
});
const lastTab = tabs.length - 1;
let newIndex = -1;
if (command === 'flip-tabs-forward')
if (command === 'flip-tabs-forward') {
newIndex = activeIndex === 0 ? lastTab : activeIndex - 1;
else // 'flip-tabs-backwards'
newIndex = activeIndex === lastTab ? 0 : activeIndex + 1;
}
// 'flip-tabs-backwards'
else newIndex = activeIndex === lastTab ? 0 : activeIndex + 1;
chrome.tabs.update(tabs[newIndex].id, { active: true, highlighted: true });
});

Просмотреть файл

@ -32,4 +32,4 @@
"48": "images/tabFlipper48.png",
"128": "images/tabFlipper128.png"
}
}
}

Просмотреть файл

@ -1,6 +1,6 @@
<!DOCTYPE html>
<html lang="en">
<body>
<body>
<script src="popup.js"></script>
</body>
</body>
</html>

Просмотреть файл

@ -1,11 +1,11 @@
function faviconURL(u) {
const url = new URL(chrome.runtime.getURL("/_favicon/"));
url.searchParams.set("pageUrl", u); // this encodes the URL as well
url.searchParams.set("size", "32");
const url = new URL(chrome.runtime.getURL('/_favicon/'));
url.searchParams.set('pageUrl', u); // this encodes the URL as well
url.searchParams.set('size', '32');
return url.toString();
}
const img = document.createElement('img');
// chrome-extension://EXTENSION_ID/_favicon/?pageUrl=https%3A%2F%2Fwww.google.com&size=32
img.src = faviconURL("https://www.google.com")
img.src = faviconURL('https://www.google.com');
document.body.appendChild(img);

Просмотреть файл

@ -5,6 +5,6 @@
// This event is fired with the user accepts the input in the omnibox.
chrome.omnibox.onInputEntered.addListener((text) => {
// Encode user input for special characters , / ? : @ & = + $ #
var newURL = 'https://www.google.com/search?q=' + encodeURIComponent(text);
const newURL = 'https://www.google.com/search?q=' + encodeURIComponent(text);
chrome.tabs.create({ url: newURL });
});

Просмотреть файл

@ -6,12 +6,12 @@
"background": {
"service_worker": "background.js"
},
"omnibox": { "keyword" : "nt" },
"omnibox": { "keyword": "nt" },
"action": {
"default_icon": {
"16": "newtab_search16.png",
"32": "newtab_search32.png"
}
}
},
"icons": {
"16": "newtab_search16.png",

Просмотреть файл

@ -3,11 +3,11 @@
* source code is governed by a BSD-style license that can be found in the
* LICENSE file.
-->
<!DOCTYPE HTML>
<!DOCTYPE html>
<html>
<head>
<title>Printers</title>
<link href="printers.css" rel="stylesheet" type="text/css">
<link href="printers.css" rel="stylesheet" type="text/css" />
<script src="printers.js"></script>
</head>

Просмотреть файл

@ -6,42 +6,46 @@ function onPrintButtonClicked(printerId, dpi) {
var ticket = {
version: '1.0',
print: {
color: {type: 'STANDARD_MONOCHROME'},
duplex: {type: 'NO_DUPLEX'},
page_orientation: {type: 'LANDSCAPE'},
copies: {copies: 1},
dpi: {horizontal_dpi: dpi.horizontal_dpi, vertical_dpi: dpi.vertical_dpi},
color: { type: 'STANDARD_MONOCHROME' },
duplex: { type: 'NO_DUPLEX' },
page_orientation: { type: 'LANDSCAPE' },
copies: { copies: 1 },
dpi: {
horizontal_dpi: dpi.horizontal_dpi,
vertical_dpi: dpi.vertical_dpi
},
media_size: {
width_microns: 210000,
height_microns: 297000,
vendor_id: 'iso_a4_210x297mm'
},
collate: {collate: false}
collate: { collate: false }
}
};
fetch('test.pdf')
.then(response => response.arrayBuffer())
.then(arrayBuffer => {
const request = {
job: {
printerId: printerId,
title: 'test job',
ticket: ticket,
contentType: 'application/pdf',
document: new Blob(
[new Uint8Array(arrayBuffer)], {type: 'application/pdf'})
}
};
chrome.printing.submitJob(request, (response) => {
if (response !== undefined) {
console.log(response.status);
}
if (chrome.runtime.lastError !== undefined) {
console.log(chrome.runtime.lastError.message);
}
});
.then((response) => response.arrayBuffer())
.then((arrayBuffer) => {
const request = {
job: {
printerId: printerId,
title: 'test job',
ticket: ticket,
contentType: 'application/pdf',
document: new Blob([new Uint8Array(arrayBuffer)], {
type: 'application/pdf'
})
}
};
chrome.printing.submitJob(request, (response) => {
if (response !== undefined) {
console.log(response.status);
}
if (chrome.runtime.lastError !== undefined) {
console.log(chrome.runtime.lastError.message);
}
});
});
}
function createPrintButton(onClicked) {
@ -52,12 +56,12 @@ function createPrintButton(onClicked) {
}
function createPrintersTable() {
chrome.printing.getPrinters(function(printers) {
chrome.printing.getPrinters(function (printers) {
const tbody = document.createElement('tbody');
for (let i = 0; i < printers.length; ++i) {
const printer = printers[i];
chrome.printing.getPrinterInfo(printer.id, function(response) {
chrome.printing.getPrinterInfo(printer.id, function (response) {
const columnValues = [
printer.id,
printer.name,
@ -67,7 +71,7 @@ function createPrintersTable() {
printer.isDefault,
printer.recentlyUsedRank,
JSON.stringify(response.capabilities),
response.status,
response.status
];
let tr = document.createElement('tr');
@ -79,10 +83,14 @@ function createPrintersTable() {
}
const printTd = document.createElement('td');
printTd.appendChild(createPrintButton(function() {
onPrintButtonClicked(
printer.id, response.capabilities.printer.dpi.option[0]);
}));
printTd.appendChild(
createPrintButton(function () {
onPrintButtonClicked(
printer.id,
response.capabilities.printer.dpi.option[0]
);
})
);
tr.appendChild(printTd);
tbody.appendChild(tr);
@ -94,6 +102,6 @@ function createPrintersTable() {
});
}
document.addEventListener('DOMContentLoaded', function() {
document.addEventListener('DOMContentLoaded', function () {
createPrintersTable();
});

Просмотреть файл

@ -9,6 +9,6 @@ chrome.action.onClicked.addListener((tab) => {
});
function showReadme(info, tab) {
let url = chrome.runtime.getURL("readme.html");
const url = chrome.runtime.getURL('readme.html');
chrome.tabs.create({ url });
}

Просмотреть файл

@ -1,14 +1,14 @@
let imageIds = ["test2", "test4"];
const imageIds = ['test2', 'test4'];
let loadButton = document.createElement('button');
const loadButton = document.createElement('button');
loadButton.innerText = 'Load images';
loadButton.addEventListener('click', handleLoadRequest);
document.querySelector('body').append(loadButton);
function handleLoadRequest() {
for (let id of imageIds) {
let element = document.getElementById(id);
for (const id of imageIds) {
const element = document.getElementById(id);
element.src = chrome.runtime.getURL(`${id}.png`);
}
}

Просмотреть файл

@ -18,13 +18,14 @@
],
"web_accessible_resources": [
{
"resources": [ "test1.png", "test2.png" ],
"matches": [ "https://web-accessible-resources-1.glitch.me/*" ]
}, {
"resources": [ "test3.png", "test4.png" ],
"matches": [ "https://web-accessible-resources-2.glitch.me/*" ],
"resources": ["test1.png", "test2.png"],
"matches": ["https://web-accessible-resources-1.glitch.me/*"]
},
{
"resources": ["test3.png", "test4.png"],
"matches": ["https://web-accessible-resources-2.glitch.me/*"],
"use_dynamic_url": true
}
],
"key": "AAAAB3NzaC1yc2EAAAADAQABAAABAQCnCTnUK8jgYTxnQLdtE6QzkZgn3rZv0U1naCx4csdSDqYEBXgW2pR2m/uUIAU1HzAUfkDckqTezyIG1bPw8l5X8FyWfgMQANFgTPXGRNXTmDSqHcqvS7zvuEr0xF12oGLBKa7cdEsaQzdfDWsm5BlwFIPfPXUokaHEGvxPBjrXHQmx+Z4xAyhzNh+v5bFr63lsL0ysS8z4KVKc1G1lcUZnp7Oz9n0pZP9QW0Oei2KCumDqGpqVd249232a0E9TUeQ+lqAxiN4ybzBgUT5al7Yh1nIhGHxPyRnihtHmx+hxupCuhzXeaoKjWiADp+FEK/aPAzvP5ynLDQHelez/eGdF"
}
}

Просмотреть файл

@ -3,46 +3,127 @@
License: none (public domain)
*/
html, body, div, span, applet, object, iframe,
h1, h2, h3, h4, h5, h6, p, blockquote, pre,
a, abbr, acronym, address, big, cite, code,
del, dfn, em, img, ins, kbd, q, s, samp,
small, strike, strong, sub, sup, tt, var,
b, u, i, center,
dl, dt, dd, ol, ul, li,
fieldset, form, label, legend,
table, caption, tbody, tfoot, thead, tr, th, td,
article, aside, canvas, details, embed,
figure, figcaption, footer, header, hgroup,
menu, nav, output, ruby, section, summary,
time, mark, audio, video {
margin: 0;
padding: 0;
border: 0;
font-size: 100%;
font: inherit;
vertical-align: baseline;
html,
body,
div,
span,
applet,
object,
iframe,
h1,
h2,
h3,
h4,
h5,
h6,
p,
blockquote,
pre,
a,
abbr,
acronym,
address,
big,
cite,
code,
del,
dfn,
em,
img,
ins,
kbd,
q,
s,
samp,
small,
strike,
strong,
sub,
sup,
tt,
var,
b,
u,
i,
center,
dl,
dt,
dd,
ol,
ul,
li,
fieldset,
form,
label,
legend,
table,
caption,
tbody,
tfoot,
thead,
tr,
th,
td,
article,
aside,
canvas,
details,
embed,
figure,
figcaption,
footer,
header,
hgroup,
menu,
nav,
output,
ruby,
section,
summary,
time,
mark,
audio,
video {
margin: 0;
padding: 0;
border: 0;
font-size: 100%;
font: inherit;
vertical-align: baseline;
}
/* HTML5 display-role reset for older browsers */
article, aside, details, figcaption, figure,
footer, header, hgroup, menu, nav, section {
display: block;
article,
aside,
details,
figcaption,
figure,
footer,
header,
hgroup,
menu,
nav,
section {
display: block;
}
body {
line-height: 1;
line-height: 1;
}
ol, ul {
list-style: none;
ol,
ul {
list-style: none;
}
blockquote, q {
quotes: none;
blockquote,
q {
quotes: none;
}
blockquote:before, blockquote:after,
q:before, q:after {
content: '';
content: none;
blockquote:before,
blockquote:after,
q:before,
q:after {
content: "";
content: none;
}
table {
border-collapse: collapse;
border-spacing: 0;
border-collapse: collapse;
border-spacing: 0;
}

Просмотреть файл

@ -1,91 +1,123 @@
<!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>Web Accessible Resources - Readme</title>
<style>
body {
font-family: Helvetica, Arial, sans-serif;
font-size: 14px;
}
table {
padding: 0;
border-collapse: collapse;
<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>Web Accessible Resources - Readme</title>
<style>
body {
font-family: Helvetica, Arial, sans-serif;
font-size: 14px;
}
table {
padding: 0;
border-collapse: collapse;
}
th {
background: hsl(0, 0%, 90%);
padding: 0.25em 0.5em;
text-align: left;
}
td {
padding: 0.25em 0.5em;
border-top: 1px solid hsl(0, 0%, 50%);
}
</style>
</head>
<body>
<h1>Web Accessible Resources Demo</h1>
<p>This demo shows off the core features of web accessible resources.</p>
<p>
In this demo we have 4 images (test1.png, etc.) that we want to expose on
2 different websites. Each website should only be able to load two
specific images, but both websites will attempt to access all 4 images. To
do this, we define a set of
<a
href="https://developer.chrome.com/docs/extensions/mv3/manifest/web_accessible_resources/"
><code>"web_accessable_resources"</code></a
>
in our <a href="manifest.json">manifest.json</a>. This object specifies
what assets should be accessible to which external resources.
</p>
}
th {
background: hsl(0,0%,90%);
padding: .25em .5em;
text-align: left;
}
td {
padding: .25em .5em;
border-top: 1px solid hsl(0,0%,50%);
}
</style>
</head>
<body>
<h1>Web Accessible Resources Demo</h1>
<p>This demo shows off the core features of web accessible resources.</p>
<p>In this demo we have 4 images (test1.png, etc.) that we want to expose on 2 different websites.
Each website should only be able to load two specific images, but both websites will attempt to
access all 4 images. To do this, we define a set of <a
href="https://developer.chrome.com/docs/extensions/mv3/manifest/web_accessible_resources/"><code>"web_accessable_resources"</code></a>
in our <a href="manifest.json">manifest.json</a>. This object specifies what assets should be
accessible to which external resources.</p>
<p>
The first image on each site is statically referenced by the site using a
URL in the following format:
<code>chrome-extension://&lt;extension-id>/&lt;image-path></code>. The
second image on each site will only be injected into the page when you
click the "Load images" button for that page. This injection is performed
by using
<a
href="https://developer.chrome.com/docs/extensions/reference/runtime/#method-getURL"
>chrome.runtime.getURL()</a
>
to build the image's URL at runtime.
</p>
<p>The first image on each site is statically referenced by the site using a URL in the following
format: <code>chrome-extension://&lt;extension-id>/&lt;image-path></code>. The second image on
each site will only be injected into the page when you click the "Load images" button for that
page. This injection is performed by using <a
href="https://developer.chrome.com/docs/extensions/reference/runtime/#method-getURL">chrome.runtime.getURL()</a>
to build the image's URL at runtime.</p>
<table>
<thead>
<tr>
<th>File</th>
<th>Target domain</th>
<th>Injection method</th>
</tr>
</thead>
<tbody>
<tr>
<td>
<code><a href="test1.png">test1.png</a></code>
</td>
<td>web-accessible-resources-1.glitch.me</td>
<td>Statically referenced</td>
</tr>
<tr>
<td>
<code><a href="test2.png">test2.png</a></code>
</td>
<td>web-accessible-resources-1.glitch.me</td>
<td>Dynamically injected</td>
</tr>
<tr>
<td>
<code><a href="test3.png">test3.png</a></code>
</td>
<td>web-accessible-resources-2.glitch.me</td>
<td>Statically referenced</td>
</tr>
<tr>
<td>
<code><a href="test4.png">test4.png</a></code>
</td>
<td>web-accessible-resources-2.glitch.me</td>
<td>Dynamically injected</td>
</tr>
</tbody>
</table>
<table>
<thead>
<tr>
<th>File</th>
<th>Target domain</th>
<th>Injection method</th>
</tr>
</thead>
<tbody>
<tr>
<td><code><a href="test1.png">test1.png</a></code></td>
<td>web-accessible-resources-1.glitch.me</td>
<td>Statically referenced</td>
</tr>
<tr>
<td><code><a href="test2.png">test2.png</a></code></td>
<td>web-accessible-resources-1.glitch.me</td>
<td>Dynamically injected</td>
</tr>
<tr>
<td><code><a href="test3.png">test3.png</a></code></td>
<td>web-accessible-resources-2.glitch.me</td>
<td>Statically referenced</td>
</tr>
<tr>
<td><code><a href="test4.png">test4.png</a></code></td>
<td>web-accessible-resources-2.glitch.me</td>
<td>Dynamically injected</td>
</tr>
</tbody>
</table>
<figure>
<figcaption>https://web-accessible-resources-1.glitch.me/ can access images
<a href="test1.png"><code>test1.png</code></a> and <a
href="test2.png"><code>test2.png</code></a></figcaption>
<iframe src="https://web-accessible-resources-1.glitch.me/" width=100% height=200></iframe>
</figure>
<figure>
<figcaption>https://web-accessible-resources-2.glitch.me/ can access images
<a href="test3.png"><code>test3.png</code></a> and <a
href="test4.png"><code>test4.png</code></a></figcaption>
<iframe src="https://web-accessible-resources-2.glitch.me/" width=100% height=200></iframe>
</figure>
</body>
<figure>
<figcaption>
https://web-accessible-resources-1.glitch.me/ can access images
<a href="test1.png"><code>test1.png</code></a> and
<a href="test2.png"><code>test2.png</code></a>
</figcaption>
<iframe
src="https://web-accessible-resources-1.glitch.me/"
width="100%"
height="200"
></iframe>
</figure>
<figure>
<figcaption>
https://web-accessible-resources-2.glitch.me/ can access images
<a href="test3.png"><code>test3.png</code></a> and
<a href="test4.png"><code>test4.png</code></a>
</figcaption>
<iframe
src="https://web-accessible-resources-2.glitch.me/"
width="100%"
height="200"
></iframe>
</figure>
</body>
</html>

Просмотреть файл

@ -29,7 +29,7 @@ async function addToClipboard(value) {
await chrome.offscreen.createDocument({
url: 'offscreen.html',
reasons: [chrome.offscreen.Reason.CLIPBOARD],
justification: 'Write text to the clipboard.',
justification: 'Write text to the clipboard.'
});
// Now that we have an offscreen document, we can dispatch the
@ -37,11 +37,11 @@ async function addToClipboard(value) {
chrome.runtime.sendMessage({
type: 'copy-data-to-clipboard',
target: 'offscreen-doc',
data: value,
data: value
});
}
// Solution 2 – Once extension service workers can use the Clipboard API,
// Solution 2 – Once extension service workers can use the Clipboard API,
// replace the offscreen document based implementation with something like this.
async function addToClipboardV2(value) {
navigator.clipboard.writeText(value);

Просмотреть файл

@ -6,8 +6,5 @@
"service_worker": "background.js"
},
"action": {},
"permissions": [
"offscreen",
"clipboardWrite"
]
"permissions": ["offscreen", "clipboardWrite"]
}

Просмотреть файл

@ -1,3 +1,3 @@
<!DOCTYPE html>
<textarea id="text"></textarea>
<script src="offscreen.js""></script>
<script src="offscreen.js"></script>

Просмотреть файл

@ -41,11 +41,10 @@ async function handleMessages(message) {
}
}
// We use a <textarea> element for two main reasons:
// 1. preserve the formatting of multiline text,
// 2. select the node's content using this element's `.select()` method.
let textEl = document.querySelector('#text');
const textEl = document.querySelector('#text');
// Use the offscreen document's `document` interface to write a new value to the
// system clipboard.
@ -56,7 +55,9 @@ let textEl = document.querySelector('#text');
async function handleClipboardWrite(data) {
// Error if we received the wrong kind of data.
if (typeof data !== 'string') {
throw new TypeError(`Value provided must be a 'string', got '${typeof data}'.`);
throw new TypeError(
`Value provided must be a 'string', got '${typeof data}'.`
);
}
// `document.execCommand('copy')` works against the user's selection in a web
@ -66,6 +67,6 @@ async function handleClipboardWrite(data) {
textEl.select();
document.execCommand('copy');
//Job's done! Close the offscreen document.
// Job's done! Close the offscreen document.
window.close();
}

Просмотреть файл

@ -5,10 +5,7 @@
"background": {
"service_worker": "background.js"
},
"permissions": [
"scripting",
"activeTab"
],
"permissions": ["scripting", "activeTab"],
"action": {
"default_popup": "popup.html"
}

Просмотреть файл

@ -13,7 +13,7 @@ body {
min-height: 10em;
}
main {
padding: 1em .5em;
padding: 1em 0.5em;
display: grid;
place-items: center;
}

Просмотреть файл

@ -1,21 +1,21 @@
<!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>Document</title>
<link rel="stylesheet" href="popup.css">
<script src="popup.js" defer></script>
</head>
<body>
<main>
<div>
<button id="inject-file">Inject file</button>
</div>
<div>
<button id="inject-function">Inject function</button>
</div>
</main>
</body>
<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>Document</title>
<link rel="stylesheet" href="popup.css" />
<script src="popup.js" defer></script>
</head>
<body>
<main>
<div>
<button id="inject-file">Inject file</button>
</div>
<div>
<button id="inject-function">Inject function</button>
</div>
</main>
</body>
</html>

Просмотреть файл

@ -1,17 +1,17 @@
let injectFile = document.getElementById('inject-file');
let injectFunction = document.getElementById('inject-function');
const injectFile = document.getElementById('inject-file');
const injectFunction = document.getElementById('inject-function');
async function getCurrentTab() {
let queryOptions = { active: true, currentWindow: true };
let [tab] = await chrome.tabs.query(queryOptions);
const queryOptions = { active: true, currentWindow: true };
const [tab] = await chrome.tabs.query(queryOptions);
return tab;
}
injectFile.addEventListener('click', async () => {
let tab = await getCurrentTab();
const tab = await getCurrentTab();
chrome.scripting.executeScript({
target: {tabId: tab.id},
target: { tabId: tab.id },
files: ['content-script.js']
});
});
@ -21,11 +21,11 @@ function showAlert(givenName) {
}
injectFunction.addEventListener('click', async () => {
let tab = await getCurrentTab();
const tab = await getCurrentTab();
let name = 'World';
const name = 'World';
chrome.scripting.executeScript({
target: {tabId: tab.id},
target: { tabId: tab.id },
func: showAlert,
args: [name]
});

Просмотреть файл

@ -3,9 +3,7 @@
"description": "A browser action with a popup dump of all bookmarks, including search, add, edit and delete.",
"version": "1.1",
"manifest_version": 3,
"permissions": [
"bookmarks"
],
"permissions": ["bookmarks"],
"action": {
"default_title": "My Bookmarks",
"default_icon": "icon.png",

Просмотреть файл

@ -1,3 +1,3 @@
#editdialog input {
width: 100%
width: 100%;
}

Просмотреть файл

@ -1,19 +1,19 @@
<html>
<head>
<link rel="stylesheet" href="third-party/jquery-ui.css">
<link rel="stylesheet" href="third-party/jquery-ui.structure.css">
<link rel="stylesheet" href="third-party/jquery-ui.theme.css">
<link rel="stylesheet" href="popup.css">
<script src="third-party/jquery-1.12.4.js"></script>
<script src="third-party/jquery-ui-1.12.1.js"></script>
</head>
<body style="width: 400px">
<div>Search Bookmarks: <input id="search"></div>
<div id="bookmarks"></div>
<div id="editdialog"></div>
<div id="deletedialog"></div>
<div id="adddialog"></div>
<div id="test-frame"></div>
<script src="popup.js"></script>
</body>
<head>
<link rel="stylesheet" href="third-party/jquery-ui.css" />
<link rel="stylesheet" href="third-party/jquery-ui.structure.css" />
<link rel="stylesheet" href="third-party/jquery-ui.theme.css" />
<link rel="stylesheet" href="popup.css" />
<script src="third-party/jquery-1.12.4.js"></script>
<script src="third-party/jquery-ui-1.12.1.js"></script>
</head>
<body style="width: 400px">
<div>Search Bookmarks: <input id="search" /></div>
<div id="bookmarks"></div>
<div id="editdialog"></div>
<div id="deletedialog"></div>
<div id="adddialog"></div>
<div id="test-frame"></div>
<script src="popup.js"></script>
</body>
</html>

Просмотреть файл

@ -12,14 +12,16 @@ $('#search').change(function () {
// Traverse the bookmark tree, and print the folder and nodes.
function dumpBookmarks(query) {
var bookmarkTreeNodes = chrome.bookmarks.getTree(function (bookmarkTreeNodes) {
const bookmarkTreeNodes = chrome.bookmarks.getTree(function (
bookmarkTreeNodes
) {
$('#bookmarks').append(dumpTreeNodes(bookmarkTreeNodes, query));
});
}
function dumpTreeNodes(bookmarkNodes, query) {
var list = $('<ul>');
for (var i = 0; i < bookmarkNodes.length; i++) {
const list = $('<ul>');
for (let i = 0; i < bookmarkNodes.length; i++) {
list.append(dumpNode(bookmarkNodes[i], query));
}
@ -29,12 +31,15 @@ function dumpTreeNodes(bookmarkNodes, query) {
function dumpNode(bookmarkNode, query) {
if (bookmarkNode.title) {
if (query && !bookmarkNode.children) {
if (String(bookmarkNode.title.toLowerCase()).indexOf(query.toLowerCase()) == -1) {
if (
String(bookmarkNode.title.toLowerCase()).indexOf(query.toLowerCase()) ==
-1
) {
return $('<span></span>');
}
}
var anchor = $('<a>');
const anchor = $('<a>');
anchor.attr('href', bookmarkNode.url);
anchor.text(bookmarkNode.title);
@ -47,114 +52,136 @@ function dumpNode(bookmarkNode, query) {
});
var span = $('<span>');
var options = bookmarkNode.children ?
$('<span>[<a href="#" id="addlink">Add</a>]</span>') :
$('<span>[<a id="editlink" href="#">Edit</a> <a id="deletelink" ' +
'href="#">Delete</a>]</span>');
var edit = bookmarkNode.children ? $('<table><tr><td>Name</td><td>' +
'<input id="title"></td></tr><tr><td>URL</td><td><input id="url">' +
'</td></tr></table>') : $('<input>');
const options = bookmarkNode.children
? $('<span>[<a href="#" id="addlink">Add</a>]</span>')
: $(
'<span>[<a id="editlink" href="#">Edit</a> <a id="deletelink" ' +
'href="#">Delete</a>]</span>'
);
const edit = bookmarkNode.children
? $(
'<table><tr><td>Name</td><td>' +
'<input id="title"></td></tr><tr><td>URL</td><td><input id="url">' +
'</td></tr></table>'
)
: $('<input>');
// Show add and edit links when hover over.
span.hover(function () {
span.append(options);
$('#deletelink').click(function (event) {
console.log(event)
$('#deletedialog').empty().dialog({
autoOpen: false,
closeOnEscape: true,
title: 'Confirm Deletion',
modal: true,
show: 'slide',
position: {
my: "left",
at: "center",
of: event.target.parentElement.parentElement
},
buttons: {
'Yes, Delete It!': function () {
chrome.bookmarks.remove(String(bookmarkNode.id));
span.parent().remove();
$(this).dialog('destroy');
},
Cancel: function () {
$(this).dialog('destroy');
}
}
}).dialog('open');
});
$('#addlink').click(function (event) {
edit.show();
$('#adddialog').empty().append(edit).dialog({
autoOpen: false,
closeOnEscape: true,
title: 'Add New Bookmark',
modal: true,
show: 'slide',
position: {
my: "left",
at: "center",
of: event.target.parentElement.parentElement
},
buttons: {
'Add': function () {
edit.hide();
chrome.bookmarks.create({
parentId: bookmarkNode.id,
title: $('#title').val(), url: $('#url').val()
});
$('#bookmarks').empty();
$(this).dialog('destroy');
window.dumpBookmarks();
},
'Cancel': function () {
edit.hide();
$(this).dialog('destroy');
}
}
}).dialog('open');
});
$('#editlink').click(function (event) {
edit.show();
edit.val(anchor.text());
$('#editdialog').empty().append(edit).dialog({
autoOpen: false,
closeOnEscape: true,
title: 'Edit Title',
modal: true,
show: 'fade',
position: {
my: "left",
at: "center",
of: event.target.parentElement.parentElement
},
buttons: {
'Save': function () {
edit.hide();
chrome.bookmarks.update(String(bookmarkNode.id), {
title: edit.val()
});
anchor.text(edit.val());
options.show();
$(this).dialog('destroy');
},
'Cancel': function () {
edit.hide();
$(this).dialog('destroy');
}
}
}).dialog('open');
});
options.fadeIn();
},
// Show add and edit links when hover over.
span
.hover(
function () {
span.append(options);
$('#deletelink').click(function (event) {
console.log(event);
$('#deletedialog')
.empty()
.dialog({
autoOpen: false,
closeOnEscape: true,
title: 'Confirm Deletion',
modal: true,
show: 'slide',
position: {
my: 'left',
at: 'center',
of: event.target.parentElement.parentElement
},
buttons: {
'Yes, Delete It!': function () {
chrome.bookmarks.remove(String(bookmarkNode.id));
span.parent().remove();
$(this).dialog('destroy');
},
Cancel: function () {
$(this).dialog('destroy');
}
}
})
.dialog('open');
});
$('#addlink').click(function (event) {
edit.show();
$('#adddialog')
.empty()
.append(edit)
.dialog({
autoOpen: false,
closeOnEscape: true,
title: 'Add New Bookmark',
modal: true,
show: 'slide',
position: {
my: 'left',
at: 'center',
of: event.target.parentElement.parentElement
},
buttons: {
Add: function () {
edit.hide();
chrome.bookmarks.create({
parentId: bookmarkNode.id,
title: $('#title').val(),
url: $('#url').val()
});
$('#bookmarks').empty();
$(this).dialog('destroy');
window.dumpBookmarks();
},
Cancel: function () {
edit.hide();
$(this).dialog('destroy');
}
}
})
.dialog('open');
});
$('#editlink').click(function (event) {
edit.show();
edit.val(anchor.text());
$('#editdialog')
.empty()
.append(edit)
.dialog({
autoOpen: false,
closeOnEscape: true,
title: 'Edit Title',
modal: true,
show: 'fade',
position: {
my: 'left',
at: 'center',
of: event.target.parentElement.parentElement
},
buttons: {
Save: function () {
edit.hide();
chrome.bookmarks.update(String(bookmarkNode.id), {
title: edit.val()
});
anchor.text(edit.val());
options.show();
$(this).dialog('destroy');
},
Cancel: function () {
edit.hide();
$(this).dialog('destroy');
}
}
})
.dialog('open');
});
options.fadeIn();
},
// unhover
function () {
options.remove();
}).append(anchor);
// unhover
function () {
options.remove();
}
)
.append(anchor);
}
var li = $(bookmarkNode.title ? '<li>' : '<div>').append(span);
const li = $(bookmarkNode.title ? '<li>' : '<div>').append(span);
if (bookmarkNode.children && bookmarkNode.children.length > 0) {
li.append(dumpTreeNodes(bookmarkNode.children, query));
}

Просмотреть файл

@ -4,7 +4,7 @@ This example fetches the favicon from www.google.com and inserts it at the top l
Note: This extension does not work on `chrome://extensions`.
See [Fetching favicons](https://developer.chrome.com/docs/extensions/mv3/favicon) to learn more.
See [Fetching favicons](https://developer.chrome.com/docs/extensions/mv3/favicon) to learn more.
## Testing the extension

Просмотреть файл

@ -1,12 +1,12 @@
function faviconURL(u) {
const url = new URL(chrome.runtime.getURL("/_favicon/"));
url.searchParams.set("pageUrl", u); // this encodes the URL as well
url.searchParams.set("size", "32");
const url = new URL(chrome.runtime.getURL('/_favicon/'));
url.searchParams.set('pageUrl', u); // this encodes the URL as well
url.searchParams.set('size', '32');
return url.toString();
}
const imageOverlay = document.createElement('img');
imageOverlay.src = faviconURL("https://www.google.com");
imageOverlay.src = faviconURL('https://www.google.com');
imageOverlay.alt = "Google's favicon";
imageOverlay.classList.add('favicon-overlay');
document.body.appendChild(imageOverlay);

Просмотреть файл

@ -1,7 +1,7 @@
.favicon-overlay {
all: initial !important;
position: fixed !important;
top: 0 !important;
left: 0 !important;
z-index: 9999 !important;
}
all: initial !important;
position: fixed !important;
top: 0 !important;
left: 0 !important;
z-index: 9999 !important;
}

Просмотреть файл

@ -2,8 +2,8 @@
"name": "Chromium Milestones",
"version": "1.0",
"manifest_version": 3,
"action": {"default_popup": "popup.html"},
"action": { "default_popup": "popup.html" },
"description": "Shows the Chromium release milestone a given code review was merged into.",
"host_permissions": [ "https://crrie.com/" ],
"permissions": [ "activeTab" ]
"host_permissions": ["https://crrie.com/"],
"permissions": ["activeTab"]
}

Просмотреть файл

@ -12,19 +12,19 @@
// See the License for the specific language governing permissions and
// limitations under the License.
chrome.tabs.query({active : true}).then(tabs => getMilestone(tabs));
chrome.tabs.query({ active: true }).then((tabs) => getMilestone(tabs));
function getMilestone(tabs) {
const div = document.createElement("div");
const div = document.createElement('div');
document.body.appendChild(div);
const url = tabs[0].url;
const origin = 'https://chromium-review.googlesource.com';
const search = `^${origin}/c/chromium/src/\\+/(\\d+)`;
const match = url.match(search);
if (match != undefined && match.length == 2) {
getMilestoneForRevId(match[1]).then(
(milestone) => milestone != '' ? (div.innerText = `m${milestone}`)
: window.close());
getMilestoneForRevId(match[1]).then((milestone) =>
milestone != '' ? (div.innerText = `m${milestone}`) : window.close()
);
} else {
window.close();
}

Просмотреть файл

@ -19,4 +19,3 @@ Then, click on "Allow Extension to Access to top sites". You will see the follow
If you accept, it will display a list of your top sites.
<img src="https://wd.imgix.net/image/BhuKGJaIeLNPW9ehns59NfwqKxF2/ibZ6PqWHsU2v0Y1h0ig2.png" alt="New tab displaying top sites" width="400"/>

Просмотреть файл

@ -3,9 +3,7 @@
"version": "1.0.0",
"description": "Demonstrates optional permissions in extensions",
"permissions": ["storage"],
"optional_permissions": [
"topSites"
],
"optional_permissions": ["topSites"],
"icons": {
"16": "images/icon16.png",
"32": "images/icon32.png",

Просмотреть файл

@ -1,9 +1,9 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<meta charset="UTF-8" />
<title>New Tab - Optional Permissions</title>
<link rel="stylesheet" type="text/css" href="style.css">
<link rel="stylesheet" type="text/css" href="style.css" />
</head>
<body>
<div id="todo_div" class="center colorFun">
@ -12,7 +12,7 @@
<div id="display_top"></div>
<form class="center">
<input id="todo_value" placeholder="My focus today is..." />
<input type="submit" value="Submit">
<input type="submit" value="Submit" />
</form>
<footer></footer>
<script src="newtab.js"></script>

Просмотреть файл

@ -23,35 +23,35 @@ const todo = document.getElementById('display_todo');
const form = document.querySelector('form');
const footer = document.querySelector('footer');
const createTop = () => { chrome.topSites.get((topSites) => {
topSites.forEach((site) => {
let div = document.createElement('div');
div.className = 'colorFun';
let tooltip = document.createElement('span');
tooltip.innerText = site.title;
tooltip.className = 'tooltip';
let url = document.createElement('a');
url.href = site.url;
let hostname = (new URL(site.url)).hostname;
let image = document.createElement('img');
image.title = site.title;
image.src = 'https://logo.clearbit.com/' + hostname;
url.appendChild(image);
div.appendChild(url);
div.appendChild(tooltip);
sites_div.appendChild(div);
})
})};
const createTop = () => {
chrome.topSites.get((topSites) => {
topSites.forEach((site) => {
const div = document.createElement('div');
div.className = 'colorFun';
const tooltip = document.createElement('span');
tooltip.innerText = site.title;
tooltip.className = 'tooltip';
const url = document.createElement('a');
url.href = site.url;
const hostname = new URL(site.url).hostname;
const image = document.createElement('img');
image.title = site.title;
image.src = 'https://logo.clearbit.com/' + hostname;
url.appendChild(image);
div.appendChild(url);
div.appendChild(tooltip);
sites_div.appendChild(div);
});
});
};
chrome.permissions.contains({permissions: ['topSites']}).then((result)=>{
chrome.permissions.contains({ permissions: ['topSites'] }).then((result) => {
if (result) {
// The extension has the permissions.
createTop();
} else {
// The extension doesn't have the permissions.
let button = document.createElement('button');
const button = document.createElement('button');
button.innerText = 'Allow Extension to Access Top Sites';
button.addEventListener('click', (event) => {
chrome.permissions.request(newPerms).then((granted) => {
@ -66,21 +66,21 @@ chrome.permissions.contains({permissions: ['topSites']}).then((result)=>{
});
footer.appendChild(button);
}
})
});
form.addEventListener('submit', () => {
let todo_value = document.getElementById('todo_value');
chrome.storage.sync.set({todo: todo_value.value});
const todo_value = document.getElementById('todo_value');
chrome.storage.sync.set({ todo: todo_value.value });
});
function setToDo() {
chrome.storage.sync.get(['todo']).then((value)=>{
chrome.storage.sync.get(['todo']).then((value) => {
if (!value.todo) {
todo.innerText = '';
} else {
todo.innerText = value.todo;
}
})
};
});
}
setToDo();

Просмотреть файл

@ -37,11 +37,21 @@ h1 {
}
@keyframes color-extravaganza {
0% {background-color: #4285F4;}
10% {background-color: #4285F4;}
25% {background-color: #EA4335;}
50% {background-color: #FBBC04;}
100% {background-color: #34A853;}
0% {
background-color: #4285f4;
}
10% {
background-color: #4285f4;
}
25% {
background-color: #ea4335;
}
50% {
background-color: #fbbc04;
}
100% {
background-color: #34a853;
}
}
.colorFun {
@ -69,8 +79,8 @@ h1 {
transition: opacity 0.3s;
}
#todo_div, .colorFun:hover .tooltip {
#todo_div,
.colorFun:hover .tooltip {
visibility: visible;
opacity: 1;
animation-name: color-extravaganza;

Просмотреть файл

@ -3,7 +3,7 @@ function reddenPage() {
}
chrome.action.onClicked.addListener((tab) => {
if(!tab.url.includes("chrome://")) {
if (!tab.url.includes('chrome://')) {
chrome.scripting.executeScript({
target: { tabId: tab.id },
function: reddenPage

Просмотреть файл

@ -4,10 +4,7 @@
"manifest_version": 3,
"version": "0.1",
"description": "Turns the page red when you click the icon",
"permissions": [
"activeTab",
"scripting"
],
"permissions": ["activeTab", "scripting"],
"background": {
"service_worker": "background.js"
}

Просмотреть файл

@ -9,10 +9,8 @@ chrome.alarms.onAlarm.addListener(() => {
type: 'basic',
iconUrl: 'stay_hydrated.png',
title: 'Time to Hydrate',
message: 'Everyday I\'m Guzzlin\'!',
buttons: [
{ title: 'Keep it Flowing.' }
],
message: "Everyday I'm Guzzlin'!",
buttons: [{ title: 'Keep it Flowing.' }],
priority: 0
});
});

Просмотреть файл

@ -3,11 +3,7 @@
"description": "Demonstrates usage and features of the event page by reminding user to drink water",
"version": "1.0",
"manifest_version": 3,
"permissions": [
"alarms",
"notifications",
"storage"
],
"permissions": ["alarms", "notifications", "storage"],
"background": {
"service_worker": "background.js"
},
@ -21,4 +17,4 @@
"48": "drink_water48.png",
"128": "drink_water128.png"
}
}
}

Просмотреть файл

@ -21,7 +21,7 @@ found in the LICENSE file. -->
}
button:hover {
outline: #80DEEA dotted thick;
outline: #80deea dotted thick;
}
</style>
<!--
@ -29,13 +29,13 @@ found in the LICENSE file. -->
-->
</head>
<body>
<img src='./stay_hydrated.png' id='hydrateImage'>
<!-- An Alarm delay of less than the minimum 1 minute will fire
<img src="./stay_hydrated.png" id="hydrateImage" />
<!-- An Alarm delay of less than the minimum 1 minute will fire
in approximately 1 minute increments if released -->
<button id="sampleMinute" value="1">Sample minute</button>
<button id="min15" value="15">15 Minutes</button>
<button id="min30" value="30">30 Minutes</button>
<button id="cancelAlarm">Cancel Alarm</button>
<script src="popup.js"></script>
<button id="sampleMinute" value="1">Sample minute</button>
<button id="min15" value="15">15 Minutes</button>
<button id="min30" value="30">30 Minutes</button>
<button id="cancelAlarm">Cancel Alarm</button>
<script src="popup.js"></script>
</body>
</html>

Просмотреть файл

@ -4,20 +4,20 @@
'use strict';
function setAlarm(event) {
let minutes = parseFloat(event.target.value);
chrome.action.setBadgeText({text: 'ON'});
chrome.alarms.create({delayInMinutes: minutes});
chrome.storage.sync.set({minutes: minutes});
const minutes = parseFloat(event.target.value);
chrome.action.setBadgeText({ text: 'ON' });
chrome.alarms.create({ delayInMinutes: minutes });
chrome.storage.sync.set({ minutes: minutes });
window.close();
}
function clearAlarm() {
chrome.action.setBadgeText({text: ''});
chrome.action.setBadgeText({ text: '' });
chrome.alarms.clearAll();
window.close();
}
//An Alarm delay of less than the minimum 1 minute will fire
// An Alarm delay of less than the minimum 1 minute will fire
// in approximately 1 minute increments if released
document.getElementById('sampleMinute').addEventListener('click', setAlarm);
document.getElementById('min15').addEventListener('click', setAlarm);

Просмотреть файл

@ -14,12 +14,12 @@
chrome.runtime.onInstalled.addListener(() => {
chrome.action.setBadgeText({
text: "OFF",
text: 'OFF'
});
});
const extensions = 'https://developer.chrome.com/docs/extensions'
const webstore = 'https://developer.chrome.com/docs/webstore'
const extensions = 'https://developer.chrome.com/docs/extensions';
const webstore = 'https://developer.chrome.com/docs/webstore';
// When the user clicks on the extension action
chrome.action.onClicked.addListener(async (tab) => {
@ -27,25 +27,25 @@ chrome.action.onClicked.addListener(async (tab) => {
// We retrieve the action badge to check if the extension is 'ON' or 'OFF'
const prevState = await chrome.action.getBadgeText({ tabId: tab.id });
// Next state will always be the opposite
const nextState = prevState === 'ON' ? 'OFF' : 'ON'
const nextState = prevState === 'ON' ? 'OFF' : 'ON';
// Set the action badge to the next state
await chrome.action.setBadgeText({
tabId: tab.id,
text: nextState,
text: nextState
});
if (nextState === "ON") {
if (nextState === 'ON') {
// Insert the CSS file when the user turns the extension on
await chrome.scripting.insertCSS({
files: ["focus-mode.css"],
target: { tabId: tab.id },
files: ['focus-mode.css'],
target: { tabId: tab.id }
});
} else if (nextState === "OFF") {
} else if (nextState === 'OFF') {
// Remove the CSS file when the user turns the extension off
await chrome.scripting.removeCSS({
files: ["focus-mode.css"],
target: { tabId: tab.id },
files: ['focus-mode.css'],
target: { tabId: tab.id }
});
}
}

Просмотреть файл

@ -1,4 +1,4 @@
let color = '#3aa757';
const color = '#3aa757';
chrome.runtime.onInstalled.addListener(() => {
chrome.storage.sync.set({ color });

Просмотреть файл

@ -8,6 +8,5 @@ button {
}
button.current {
box-shadow: 0 0 0 2px white,
0 0 0 4px black;
}
box-shadow: 0 0 0 2px white, 0 0 0 4px black;
}

Просмотреть файл

@ -1,11 +1,10 @@
<!DOCTYPE html>
<html>
<head>
<link rel="stylesheet" href="button.css">
<link rel="stylesheet" href="button.css" />
</head>
<body>
<div id="buttonDiv">
</div>
<div id="buttonDiv"></div>
<div>
<p>Choose a different background color!</p>
</div>

Просмотреть файл

@ -1,12 +1,12 @@
let page = document.getElementById("buttonDiv");
let selectedClassName = "current";
const presetButtonColors = ["#3aa757", "#e8453c", "#f9bb2d", "#4688f1"];
const page = document.getElementById('buttonDiv');
const selectedClassName = 'current';
const presetButtonColors = ['#3aa757', '#e8453c', '#f9bb2d', '#4688f1'];
// Reacts to a button click by marking the selected button and saving
// the selection
function handleButtonClick(event) {
// Remove styling from the previously selected color
let current = event.target.parentElement.querySelector(
const current = event.target.parentElement.querySelector(
`.${selectedClassName}`
);
if (current && current !== event.target) {
@ -14,20 +14,20 @@ function handleButtonClick(event) {
}
// Mark the button as selected
let color = event.target.dataset.color;
const color = event.target.dataset.color;
event.target.classList.add(selectedClassName);
chrome.storage.sync.set({ color });
}
// Add a button to the page for each supplied color
function constructOptions(buttonColors) {
chrome.storage.sync.get("color", (data) => {
let currentColor = data.color;
chrome.storage.sync.get('color', (data) => {
const currentColor = data.color;
// For each color we were provided…
for (let buttonColor of buttonColors) {
for (const buttonColor of buttonColors) {
// …create a button with that color…
let button = document.createElement("button");
const button = document.createElement('button');
button.dataset.color = buttonColor;
button.style.backgroundColor = buttonColor;
@ -37,7 +37,7 @@ function constructOptions(buttonColors) {
}
// …and register a listener for when that button is clicked
button.addEventListener("click", handleButtonClick);
button.addEventListener('click', handleButtonClick);
page.appendChild(button);
}
});

Просмотреть файл

@ -1,7 +1,7 @@
<!DOCTYPE html>
<html>
<head>
<link rel="stylesheet" href="button.css">
<link rel="stylesheet" href="button.css" />
</head>
<body>
<button id="changeColor"></button>

Просмотреть файл

@ -1,24 +1,24 @@
// Initialize button with users' preferred color
let changeColor = document.getElementById("changeColor");
const changeColor = document.getElementById('changeColor');
chrome.storage.sync.get("color", ({ color }) => {
chrome.storage.sync.get('color', ({ color }) => {
changeColor.style.backgroundColor = color;
});
// When the button is clicked, inject setPageBackgroundColor into current page
changeColor.addEventListener("click", async () => {
let [tab] = await chrome.tabs.query({ active: true, currentWindow: true });
changeColor.addEventListener('click', async () => {
const [tab] = await chrome.tabs.query({ active: true, currentWindow: true });
chrome.scripting.executeScript({
target: { tabId: tab.id },
func: setPageBackgroundColor,
func: setPageBackgroundColor
});
});
// The body of this function will be executed as a content script inside the
// current page
function setPageBackgroundColor() {
chrome.storage.sync.get("color", ({ color }) => {
chrome.storage.sync.get('color', ({ color }) => {
document.body.style.backgroundColor = color;
});
}

Просмотреть файл

@ -1 +1 @@
console.log("This is a popup!");
console.log('This is a popup!');

Просмотреть файл

@ -12,9 +12,7 @@
},
"content_scripts": [
{
"js": [
"scripts/content.js"
],
"js": ["scripts/content.js"],
"matches": [
"https://developer.chrome.com/docs/extensions/*",
"https://developer.chrome.com/docs/webstore/*"

Просмотреть файл

@ -12,7 +12,7 @@
// See the License for the specific language governing permissions and
// limitations under the License.
const article = document.querySelector("article");
const article = document.querySelector('article');
// `document.querySelector` may return null if the selector doesn't match anything.
if (article) {
@ -24,7 +24,7 @@ if (article) {
* regular expression character class "\w" to match against "word characters" because it only
* matches against the Latin alphabet. Instead, we match against any sequence of characters that
* *are not* a whitespace characters. See the below link for more information.
*
*
* https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Regular_Expressions
*/
const wordMatchRegExp = /[^\s]+/g;
@ -32,16 +32,16 @@ if (article) {
// matchAll returns an iterator, convert to array to get word count
const wordCount = [...words].length;
const readingTime = Math.round(wordCount / 200);
const badge = document.createElement("p");
const badge = document.createElement('p');
// Use the same styling as the publish information in an article's header
badge.classList.add("color-secondary-text", "type--caption");
badge.classList.add('color-secondary-text', 'type--caption');
badge.textContent = `⏱️ ${readingTime} min read`;
// Support for API reference docs
const heading = article.querySelector("h1");
const heading = article.querySelector('h1');
// Support for article docs with date
const date = article.querySelector("time")?.parentNode;
const date = article.querySelector('time')?.parentNode;
// https://developer.mozilla.org/en-US/docs/Web/API/Element/insertAdjacentElement
(date ?? heading).insertAdjacentElement("afterend", badge);
(date ?? heading).insertAdjacentElement('afterend', badge);
}

Просмотреть файл

@ -11,10 +11,6 @@
"action": {
"default_popup": "popup.html"
},
"host_permissions": [
"https://developer.chrome.com/*"
],
"permissions": [
"tabGroups"
]
"host_permissions": ["https://developer.chrome.com/*"],
"permissions": ["tabGroups"]
}

Просмотреть файл

@ -14,26 +14,26 @@
const tabs = await chrome.tabs.query({
url: [
"https://developer.chrome.com/docs/webstore/*",
"https://developer.chrome.com/docs/extensions/*",
],
'https://developer.chrome.com/docs/webstore/*',
'https://developer.chrome.com/docs/extensions/*'
]
});
// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl/Collator
const collator = new Intl.Collator();
tabs.sort((a, b) => collator.compare(a.title, b.title));
const template = document.getElementById("li_template");
const template = document.getElementById('li_template');
const elements = new Set();
for (const tab of tabs) {
const element = template.content.firstElementChild.cloneNode(true);
const title = tab.title.split("-")[0].trim();
const pathname = new URL(tab.url).pathname.slice("/docs".length);
const title = tab.title.split('-')[0].trim();
const pathname = new URL(tab.url).pathname.slice('/docs'.length);
element.querySelector(".title").textContent = title;
element.querySelector(".pathname").textContent = pathname;
element.querySelector("a").addEventListener("click", async () => {
element.querySelector('.title').textContent = title;
element.querySelector('.pathname').textContent = pathname;
element.querySelector('a').addEventListener('click', async () => {
// need to focus window as well as the active tab
await chrome.tabs.update(tab.id, { active: true });
await chrome.windows.update(tab.windowId, { focused: true });
@ -41,11 +41,11 @@ for (const tab of tabs) {
elements.add(element);
}
document.querySelector("ul").append(...elements);
document.querySelector('ul').append(...elements);
const button = document.querySelector("button");
button.addEventListener("click", async () => {
const button = document.querySelector('button');
button.addEventListener('click', async () => {
const tabIds = tabs.map(({ id }) => id);
const group = await chrome.tabs.group({ tabIds });
await chrome.tabGroups.update(group, { title: "DOCS" });
await chrome.tabGroups.update(group, { title: 'DOCS' });
});

3389
package-lock.json сгенерированный Normal file

Разница между файлами не показана из-за своего большого размера Загрузить разницу

39
package.json Normal file
Просмотреть файл

@ -0,0 +1,39 @@
{
"name": "chrome-extensions-samples",
"version": "1.0.0",
"private": true,
"description": "Official samples for Chrome Extensions and the Chrome Apps platform.",
"scripts": {
"prettier": "npx prettier **/*.{md,html} -w",
"lint": "eslint **/*.js",
"lint:fix": "npm run lint -- --fix",
"prepare": "husky install"
},
"repository": {
"type": "git",
"url": "git+https://github.com/GoogleChrome/chrome-extensions-samples.git"
},
"keywords": [],
"author": "The Chrome Team",
"license": "Apache 2.0",
"bugs": {
"url": "https://github.com/GoogleChrome/chrome-extensions-samples/issues"
},
"homepage": "https://github.com/GoogleChrome/chrome-extensions-samples#readme",
"devDependencies": {
"eslint": "^8.34.0",
"eslint-config-prettier": "8.6.0",
"eslint-plugin-prettier": "4.2.1",
"husky": "^8.0.0",
"lint-staged": "^13.1.2",
"prettier": "2.8.4"
},
"lint-staged":{
"**/*.js":[
"npx eslint --fix"
],
"**/*.{md,html}":[
"npx prettier --write"
]
}
}