Add prettier and eslint (#831)
* 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:
Родитель
299f2134cb
Коммит
dc2174377a
|
@ -0,0 +1,3 @@
|
|||
_archive
|
||||
third-party
|
||||
node_modules
|
|
@ -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'
|
||||
}
|
||||
};
|
|
@ -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 '....'
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
*~
|
||||
*.DS_store
|
||||
|
||||
node_modules
|
||||
# Temporary directory for debugging extension samples
|
||||
_debug
|
||||
|
|
|
@ -0,0 +1,4 @@
|
|||
#!/usr/bin/env sh
|
||||
. "$(dirname "$0")/_/husky.sh"
|
||||
|
||||
npx lint-staged
|
|
@ -0,0 +1,3 @@
|
|||
_archive
|
||||
third-party
|
||||
node_modules
|
|
@ -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
|
||||
```
|
||||
|
|
18
README.md
18
README.md
|
@ -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 < 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 < 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://<extension-id>/<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://<extension-id>/<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' });
|
||||
});
|
||||
|
|
Разница между файлами не показана из-за своего большого размера
Загрузить разницу
|
@ -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"
|
||||
]
|
||||
}
|
||||
}
|
Загрузка…
Ссылка в новой задаче