mirror of
https://github.com/GoogleChrome/chrome-extensions-samples.git
synced 2026-03-26 13:19:49 +07:00
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:
3
.eslintignore
Normal file
3
.eslintignore
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
_archive
|
||||||
|
third-party
|
||||||
|
node_modules
|
||||||
27
.eslintrc.js
Normal file
27
.eslintrc.js
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
/* eslint-env node */
|
||||||
|
module.exports = {
|
||||||
|
extends: ['prettier', 'eslint:recommended'],
|
||||||
|
plugins: ['prettier'],
|
||||||
|
rules: {
|
||||||
|
'prettier/prettier': ['error'],
|
||||||
|
'no-unused-vars': [
|
||||||
|
'warn',
|
||||||
|
{
|
||||||
|
argsIgnorePattern: '^_',
|
||||||
|
varsIgnorePattern: '^_'
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
env: {
|
||||||
|
browser: true,
|
||||||
|
webextensions: true,
|
||||||
|
es2021: true,
|
||||||
|
jquery: true,
|
||||||
|
worker: true
|
||||||
|
},
|
||||||
|
overrides: [],
|
||||||
|
parserOptions: {
|
||||||
|
ecmaVersion: 'latest',
|
||||||
|
sourceType: 'module'
|
||||||
|
}
|
||||||
|
};
|
||||||
2
.github/ISSUE_TEMPLATE/bug_report.md
vendored
2
.github/ISSUE_TEMPLATE/bug_report.md
vendored
@@ -4,7 +4,6 @@ about: Create a report to help us improve
|
|||||||
title: ''
|
title: ''
|
||||||
labels: ''
|
labels: ''
|
||||||
assignees: ''
|
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).
|
⚠️ 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**
|
**To Reproduce**
|
||||||
Steps to reproduce the behavior, or file the issue is found in:
|
Steps to reproduce the behavior, or file the issue is found in:
|
||||||
|
|
||||||
1. Go to '...'
|
1. Go to '...'
|
||||||
2. Click on '....'
|
2. Click on '....'
|
||||||
3. Scroll down to '....'
|
3. Scroll down to '....'
|
||||||
|
|||||||
2
.gitignore
vendored
2
.gitignore
vendored
@@ -1,5 +1,5 @@
|
|||||||
*~
|
*~
|
||||||
*.DS_store
|
*.DS_store
|
||||||
|
node_modules
|
||||||
# Temporary directory for debugging extension samples
|
# Temporary directory for debugging extension samples
|
||||||
_debug
|
_debug
|
||||||
|
|||||||
4
.husky/pre-commit
Executable file
4
.husky/pre-commit
Executable file
@@ -0,0 +1,4 @@
|
|||||||
|
#!/usr/bin/env sh
|
||||||
|
. "$(dirname "$0")/_/husky.sh"
|
||||||
|
|
||||||
|
npx lint-staged
|
||||||
3
.prettierignore
Normal file
3
.prettierignore
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
_archive
|
||||||
|
third-party
|
||||||
|
node_modules
|
||||||
9
.prettierrc.json
Normal file
9
.prettierrc.json
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
{
|
||||||
|
"printWidth": 80,
|
||||||
|
"tabWidth": 2,
|
||||||
|
"semi": true,
|
||||||
|
"singleQuote": true,
|
||||||
|
"trailingComma": "none",
|
||||||
|
"bracketSpacing": true,
|
||||||
|
"arrowParens": "always"
|
||||||
|
}
|
||||||
@@ -1,17 +1,48 @@
|
|||||||
# Contributing to this Repository
|
# How to Contribute
|
||||||
|
|
||||||
Thank you for your interest in contributing!
|
We'd love to accept your patches and contributions to this project.
|
||||||
|
|
||||||
Send us your patches early and often and in whatever shape or form.
|
## Before you begin
|
||||||
|
|
||||||
## Legal
|
### Sign our Contributor License Agreement
|
||||||
|
|
||||||
Unfortunately there are some legal hurdles. Sorry about that.
|
Contributions to this project must be accompanied by a
|
||||||
|
[Contributor License Agreement](https://cla.developers.google.com/about) (CLA).
|
||||||
|
You (or your employer) retain the copyright to your contribution; this simply
|
||||||
|
gives us permission to use and redistribute your contributions as part of the
|
||||||
|
project.
|
||||||
|
|
||||||
This repository is a Google open source project, and so we require contributors to sign Google's open source Contributor License Agreement.
|
If you or your current employer have already signed the Google CLA (even if it
|
||||||
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).
|
was for a different project), you probably don't need to do it again.
|
||||||
Individuals can sign electronically in seconds (see the bottom of the page); corporations will need to email a PDF, or mail.
|
|
||||||
|
|
||||||
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).
|
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
|
## Samples
|
||||||
|
|
||||||
The directory structure is as follows:
|
The directory structure is as follows:
|
||||||
|
|
||||||
* [api-samples/](api-samples/) - extensions focused on a single API package
|
- [api-samples/](api-samples/) - extensions focused on a single API package
|
||||||
* [functional-samples/](functional-samples/) - full featured extensions spanning multiple API packages
|
- [functional-samples/](functional-samples/) - full featured extensions spanning multiple API packages
|
||||||
* [_archive/apps/](_archive/apps/) - deprecated Chrome Apps platform (not listed below)
|
- [\_archive/apps/](_archive/apps/) - deprecated Chrome Apps platform (not listed below)
|
||||||
* [_archive/mv2/](_archive/mv2/) - resources for manifest version 2
|
- [\_archive/mv2/](_archive/mv2/) - resources for manifest version 2
|
||||||
|
|
||||||
To experiment with these samples, please clone this repo and use 'Load Unpacked Extension'.
|
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).
|
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>
|
</tr>
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</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 {
|
.flex {
|
||||||
display: flex;
|
display: flex;
|
||||||
gap: .25em;
|
gap: 0.25em;
|
||||||
margin: .5em 0;
|
margin: 0.5em 0;
|
||||||
align-items: flex-end;
|
align-items: flex-end;
|
||||||
}
|
}
|
||||||
|
|
||||||
.spaced {
|
.spaced {
|
||||||
margin: .5em 0;
|
margin: 0.5em 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.full-width {
|
.full-width {
|
||||||
|
|||||||
@@ -1,167 +1,213 @@
|
|||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
<html lang="en">
|
<html lang="en">
|
||||||
<head>
|
<head>
|
||||||
<meta charset="UTF-8">
|
<meta charset="UTF-8" />
|
||||||
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
<title>Document</title>
|
<title>Document</title>
|
||||||
<script defer src="index.js"></script>
|
<script defer src="index.js"></script>
|
||||||
<link rel="stylesheet" href="../third-party/awsm/awsm.css">
|
<link rel="stylesheet" href="../third-party/awsm/awsm.css" />
|
||||||
<link rel="stylesheet" href="index.css">
|
<link rel="stylesheet" href="index.css" />
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<main>
|
<main>
|
||||||
<section>
|
<section>
|
||||||
<h1>Action API Demo</h1>
|
<h1>Action API Demo</h1>
|
||||||
<p>Before experimenting with these APIs, we recommend you pin the extension's action button to your
|
<p>
|
||||||
toolbar in order to make it easier to see the changes. </p>
|
Before experimenting with these APIs, we recommend you pin the
|
||||||
<img src="../images/pin-action.png">
|
extension's action button to your toolbar in order to make it easier
|
||||||
</section>
|
to see the changes.
|
||||||
|
</p>
|
||||||
|
<img src="../images/pin-action.png" />
|
||||||
|
</section>
|
||||||
|
|
||||||
<section id="toggle-state">
|
<section id="toggle-state">
|
||||||
<h2>enable / disable</h2>
|
<h2>enable / disable</h2>
|
||||||
|
|
||||||
<p>Clicking the below <em>toggle enabled state</em> button will enable or disable the extensions'
|
<p>
|
||||||
action button in Chrome's toolbar and extensions menu.</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
|
<p>
|
||||||
href="https://developer.chrome.com/docs/extensions/reference/action/#event-onClicked"><code>action.onClicked</code></a>
|
When disabled, clicking the action will not open a popup or trigger
|
||||||
events.</p>
|
<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">
|
<div class="flex">
|
||||||
<figure>
|
<figure>
|
||||||
<img src="../images/action-enabled.png">
|
<img src="../images/action-enabled.png" />
|
||||||
<figcaption>Action enabled</figcaption>
|
<figcaption>Action enabled</figcaption>
|
||||||
</figure>
|
</figure>
|
||||||
<figure>
|
<figure>
|
||||||
<img src="../images/action-disabled.png">
|
<img src="../images/action-disabled.png" />
|
||||||
<figcaption>Action disabled</figcaption>
|
<figcaption>Action disabled</figcaption>
|
||||||
</figure>
|
</figure>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
<section id="popup">
|
<section id="popup">
|
||||||
<h2>Popup</h2>
|
<h2>Popup</h2>
|
||||||
|
|
||||||
<p>This demo's <a href="manifest.json">manifest.json</a> file sets the value of
|
<p>
|
||||||
<code>action.default_popup</code> to <code>popups/popup.html</code>. We can change that behavior at runtime using <a
|
This demo's <a href="manifest.json">manifest.json</a> file sets the
|
||||||
href="https://developer.chrome.com/docs/extensions/reference/action/#method-setPopup"><code>action.setPopup</code></a>.</p>
|
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>
|
<label>
|
||||||
Current popup value
|
Change popup page<br />
|
||||||
<input type="text" id="current-popup-value" disabled>
|
<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>
|
</label>
|
||||||
</div>
|
|
||||||
|
|
||||||
<p>Register a handler to change how the action button behaves. Once changed, clicking the
|
<div class="spaced">
|
||||||
action will open your new favorite website.</p>
|
<label>
|
||||||
<button id="onclicked-button">Change action click behavior</button>
|
Current popup value
|
||||||
<button id="onclicked-reset-button">reset</button>
|
<input type="text" id="current-popup-value" disabled />
|
||||||
</section>
|
</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">
|
<!-- badge -->
|
||||||
<h2>Badge Text</h2>
|
|
||||||
|
|
||||||
<p>The action's badge text is a text overlay with a solid background color. This provides a
|
<section id="badge-text">
|
||||||
passive UI surface to share information with the user. It is most commonly used to show a
|
<h2>Badge Text</h2>
|
||||||
notification count or number of actions taken on the current page.</p>
|
|
||||||
|
|
||||||
<div class="spaced">
|
<p>
|
||||||
<label>
|
The action's badge text is a text overlay with a solid background
|
||||||
Enter badge text (live update)<br>
|
color. This provides a passive UI surface to share information with
|
||||||
<input type="text" id="badge-text-input">
|
the user. It is most commonly used to show a notification count or
|
||||||
</label>
|
number of actions taken on the current page.
|
||||||
</div>
|
</p>
|
||||||
|
|
||||||
<div class="flex">
|
<div class="spaced">
|
||||||
<label class="full-width">
|
<label>
|
||||||
Current badge text
|
Enter badge text (live update)<br />
|
||||||
<input type="text" id="current-badge-text" disabled>
|
<input type="text" id="badge-text-input" />
|
||||||
</label>
|
</label>
|
||||||
<button id="clear-badge-button">clear badge text</button>
|
</div>
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="spaced">
|
<div class="flex">
|
||||||
<button id="set-badge-background-color-button">Randomize badge background color</button>
|
<label class="full-width">
|
||||||
</div>
|
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">
|
<div class="spaced">
|
||||||
<label class="full-width">
|
<button id="set-badge-background-color-button">
|
||||||
Current badge color
|
Randomize badge background color
|
||||||
<input type="text" id="current-badge-bg-color" disabled>
|
</button>
|
||||||
</label>
|
</div>
|
||||||
<button id="reset-badge-background-color-button">reset badge 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">
|
<section id="setIcon">
|
||||||
<h2>Icon</h2>
|
<h2>Icon</h2>
|
||||||
|
|
||||||
<p>The <a
|
<p>
|
||||||
href="https://developer.chrome.com/docs/extensions/reference/action/#method-setIcon"><code>action.setIcon</code></a>
|
The
|
||||||
method allows you to change the action button's icon by either providing the path of an image
|
<a
|
||||||
or the raw <a href="https://developer.mozilla.org/en-US/docs/Web/API/ImageData">ImageData</a>.</p>
|
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="set-icon-button">set a new action icon</button>
|
||||||
<button id="reset-icon-button">reset action icon</button>
|
<button id="reset-icon-button">reset action icon</button>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
<!-- badge - hover text (title) -->
|
<!-- badge - hover text (title) -->
|
||||||
|
|
||||||
<section id="title">
|
<section id="title">
|
||||||
<h2>Hover Text</h2>
|
<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
|
<p>
|
||||||
href="https://developer.chrome.com/docs/extensions/reference/action/#method-getTitle"><code>action.getTitle</code></a>
|
This value can be read and changed at runtime using the
|
||||||
and <a
|
<a
|
||||||
href="https://developer.chrome.com/docs/extensions/reference/action/#method-setTitle"><code>action.setTitle</code></a>
|
href="https://developer.chrome.com/docs/extensions/reference/action/#method-getTitle"
|
||||||
methods, respectively.</p>
|
><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">
|
<div class="spaced">
|
||||||
<label>
|
<label>
|
||||||
Enter a new title (debounced)<br>
|
Enter a new title (debounced)<br />
|
||||||
<input type="text" id="title-input">
|
<input type="text" id="title-input" />
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="flex">
|
<div class="flex">
|
||||||
<label class="full-width">
|
<label class="full-width">
|
||||||
Current title
|
Current title
|
||||||
<input type="text" id="current-title" disabled>
|
<input type="text" id="current-title" disabled />
|
||||||
</label>
|
</label>
|
||||||
<button id="reset-title-button">reset title</button>
|
<button id="reset-title-button">reset title</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="flex">
|
<div class="flex">
|
||||||
<figure>
|
<figure>
|
||||||
<img src="../images/title-no-hover.png">
|
<img src="../images/title-no-hover.png" />
|
||||||
<figcaption>Default appearance</figcaption>
|
<figcaption>Default appearance</figcaption>
|
||||||
</figure>
|
</figure>
|
||||||
<figure>
|
<figure>
|
||||||
<img src="../images/title-hover.png">
|
<img src="../images/title-hover.png" />
|
||||||
<figcaption>Title appears on hover</figcaption>
|
<figcaption>Title appears on hover</figcaption>
|
||||||
</figure>
|
</figure>
|
||||||
</section>
|
</div>
|
||||||
</main>
|
</section>
|
||||||
</body>
|
</main>
|
||||||
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|||||||
@@ -25,39 +25,43 @@ function debounce(timeout, callback) {
|
|||||||
// have to track it ourselves.
|
// have to track it ourselves.
|
||||||
// Relevant feature request: https://bugs.chromium.org/p/chromium/issues/detail?id=1189295
|
// Relevant feature request: https://bugs.chromium.org/p/chromium/issues/detail?id=1189295
|
||||||
let actionEnabled = true;
|
let actionEnabled = true;
|
||||||
let showToggleState = document.getElementById('show-toggle-state');
|
const showToggleState = document.getElementById('show-toggle-state');
|
||||||
document.getElementById('toggle-state-button').addEventListener('click', (_event) => {
|
document
|
||||||
if (actionEnabled) {
|
.getElementById('toggle-state-button')
|
||||||
chrome.action.disable();
|
.addEventListener('click', (_event) => {
|
||||||
} else {
|
if (actionEnabled) {
|
||||||
chrome.action.enable();
|
chrome.action.disable();
|
||||||
}
|
} else {
|
||||||
actionEnabled = !actionEnabled;
|
chrome.action.enable();
|
||||||
});
|
}
|
||||||
|
actionEnabled = !actionEnabled;
|
||||||
|
});
|
||||||
|
|
||||||
document.getElementById('popup-options').addEventListener('change', async (event) => {
|
document
|
||||||
let popup = event.target.value;
|
.getElementById('popup-options')
|
||||||
await chrome.action.setPopup({ popup });
|
.addEventListener('change', async (event) => {
|
||||||
|
const popup = event.target.value;
|
||||||
|
await chrome.action.setPopup({ popup });
|
||||||
|
|
||||||
// Show the updated popup path
|
// Show the updated popup path
|
||||||
await getCurrentPopup();
|
await getCurrentPopup();
|
||||||
});
|
});
|
||||||
|
|
||||||
async function getCurrentPopup() {
|
async function getCurrentPopup() {
|
||||||
let popup = await chrome.action.getPopup({});
|
const popup = await chrome.action.getPopup({});
|
||||||
document.getElementById('current-popup-value').value = popup;
|
document.getElementById('current-popup-value').value = popup;
|
||||||
return popup;
|
return popup;
|
||||||
};
|
}
|
||||||
|
|
||||||
async function showCurrentPage() {
|
async function showCurrentPage() {
|
||||||
let popup = await getCurrentPopup();
|
const popup = await getCurrentPopup();
|
||||||
let pathname = '';
|
let pathname = '';
|
||||||
if (popup) {
|
if (popup) {
|
||||||
pathname = new URL(popup).pathname;
|
pathname = new URL(popup).pathname;
|
||||||
}
|
}
|
||||||
|
|
||||||
let options = document.getElementById('popup-options');
|
const options = document.getElementById('popup-options');
|
||||||
let option = options.querySelector(`option[value="${pathname}"]`);
|
const option = options.querySelector(`option[value="${pathname}"]`);
|
||||||
option.selected = true;
|
option.selected = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -75,129 +79,139 @@ chrome.action.onClicked.addListener((tab) => {
|
|||||||
chrome.tabs.create({ url: 'https://html5zombo.com/' });
|
chrome.tabs.create({ url: 'https://html5zombo.com/' });
|
||||||
});
|
});
|
||||||
|
|
||||||
document.getElementById('onclicked-button').addEventListener('click', async () => {
|
document
|
||||||
// Our listener will only receive the action's click event after clear out the popup URL
|
.getElementById('onclicked-button')
|
||||||
await chrome.action.setPopup({ popup: '' });
|
.addEventListener('click', async () => {
|
||||||
await showCurrentPage();
|
// 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 () => {
|
document
|
||||||
await chrome.action.setPopup({ popup: 'popups/popup.html' });
|
.getElementById('onclicked-reset-button')
|
||||||
await showCurrentPage();
|
.addEventListener('click', async () => {
|
||||||
});
|
await chrome.action.setPopup({ popup: 'popups/popup.html' });
|
||||||
|
await showCurrentPage();
|
||||||
|
});
|
||||||
|
|
||||||
// ----------
|
// ----------
|
||||||
// badge text
|
// badge text
|
||||||
// ----------
|
// ----------
|
||||||
|
|
||||||
async function showBadgeText() {
|
async function showBadgeText() {
|
||||||
let text = await chrome.action.getBadgeText({});
|
const text = await chrome.action.getBadgeText({});
|
||||||
document.getElementById('current-badge-text').value = text;
|
document.getElementById('current-badge-text').value = text;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Populate badge text inputs on on page load
|
// Populate badge text inputs on on page load
|
||||||
showBadgeText();
|
showBadgeText();
|
||||||
|
|
||||||
document.getElementById('badge-text-input').addEventListener('input', async (event) => {
|
document
|
||||||
let text = event.target.value;
|
.getElementById('badge-text-input')
|
||||||
await chrome.action.setBadgeText({ text });
|
.addEventListener('input', async (event) => {
|
||||||
|
const text = event.target.value;
|
||||||
|
await chrome.action.setBadgeText({ text });
|
||||||
|
|
||||||
showBadgeText();
|
showBadgeText();
|
||||||
});
|
});
|
||||||
|
|
||||||
document.getElementById('clear-badge-button').addEventListener('click', async () => {
|
document
|
||||||
await chrome.action.setBadgeText({ text: '' });
|
.getElementById('clear-badge-button')
|
||||||
|
.addEventListener('click', async () => {
|
||||||
|
await chrome.action.setBadgeText({ text: '' });
|
||||||
|
|
||||||
showBadgeText();
|
showBadgeText();
|
||||||
});
|
});
|
||||||
|
|
||||||
// ----------------------
|
// ----------------------
|
||||||
// badge background color
|
// badge background color
|
||||||
// ----------------------
|
// ----------------------
|
||||||
|
|
||||||
async function showBadgeColor() {
|
async function showBadgeColor() {
|
||||||
let color = await chrome.action.getBadgeBackgroundColor({});
|
const color = await chrome.action.getBadgeBackgroundColor({});
|
||||||
document.getElementById('current-badge-bg-color').value = JSON.stringify(color, null, 0);
|
document.getElementById('current-badge-bg-color').value = JSON.stringify(
|
||||||
|
color,
|
||||||
|
null,
|
||||||
|
0
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Populate badge background color inputs on on page load
|
// Populate badge background color inputs on on page load
|
||||||
showBadgeColor();
|
showBadgeColor();
|
||||||
|
|
||||||
document.getElementById('set-badge-background-color-button').addEventListener('click', async () => {
|
document
|
||||||
// To show off this method, we must first make sure the badge has text
|
.getElementById('set-badge-background-color-button')
|
||||||
let currentText = await chrome.action.getBadgeText({});
|
.addEventListener('click', async () => {
|
||||||
if (!currentText) {
|
// To show off this method, we must first make sure the badge has text
|
||||||
chrome.action.setBadgeText({ text: 'hi :)' });
|
let currentText = await chrome.action.getBadgeText({});
|
||||||
showBadgeText();
|
if (!currentText) {
|
||||||
}
|
chrome.action.setBadgeText({ text: 'hi :)' });
|
||||||
|
showBadgeText();
|
||||||
|
}
|
||||||
|
|
||||||
// Next, generate a random RGBA color
|
// Next, generate a random RGBA color
|
||||||
let color = [0, 0, 0].map(() => Math.floor(Math.random() * 255));
|
const color = [0, 0, 0].map(() => Math.floor(Math.random() * 255));
|
||||||
|
|
||||||
// Use the default background color ~10% of the time.
|
// 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),
|
// 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
|
// an alpha value of 0 sets the default color while a value of 1-255 will make the RGB color
|
||||||
// fully opaque.
|
// fully opaque.
|
||||||
if (Math.random() < 0.1) {
|
if (Math.random() < 0.1) {
|
||||||
color.push(0);
|
color.push(0);
|
||||||
} else {
|
} else {
|
||||||
color.push(255);
|
color.push(255);
|
||||||
}
|
}
|
||||||
|
|
||||||
chrome.action.setBadgeBackgroundColor({ color });
|
chrome.action.setBadgeBackgroundColor({ color });
|
||||||
showBadgeColor();
|
showBadgeColor();
|
||||||
});
|
});
|
||||||
|
|
||||||
document.getElementById('reset-badge-background-color-button').addEventListener('click', async () => {
|
document
|
||||||
chrome.action.setBadgeBackgroundColor({ color: [0, 0, 0, 0] });
|
.getElementById('reset-badge-background-color-button')
|
||||||
showBadgeColor();
|
.addEventListener('click', async () => {
|
||||||
});
|
chrome.action.setBadgeBackgroundColor({ color: [0, 0, 0, 0] });
|
||||||
|
showBadgeColor();
|
||||||
|
});
|
||||||
|
|
||||||
// -----------
|
// -----------
|
||||||
// action icon
|
// action icon
|
||||||
// -----------
|
// -----------
|
||||||
|
|
||||||
const EMOJI = [
|
const EMOJI = ['confetti', 'suit', 'bow', 'dog', 'skull', 'yoyo', 'cat'];
|
||||||
'confetti',
|
|
||||||
'suit',
|
|
||||||
'bow',
|
|
||||||
'dog',
|
|
||||||
'skull',
|
|
||||||
'yoyo',
|
|
||||||
'cat',
|
|
||||||
];
|
|
||||||
|
|
||||||
let lastIconIndex = 0;
|
let lastIconIndex = 0;
|
||||||
document.getElementById('set-icon-button').addEventListener('click', async () => {
|
document
|
||||||
// Clear out the badge text in order to make the icon change easier to see
|
.getElementById('set-icon-button')
|
||||||
chrome.action.setBadgeText({ text: '' });
|
.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
|
// Randomly pick a new icon
|
||||||
let index = lastIconIndex;
|
let index = lastIconIndex;
|
||||||
index = Math.floor(Math.random() * (EMOJI.length));
|
index = Math.floor(Math.random() * EMOJI.length);
|
||||||
if (index === lastIconIndex) {
|
if (index === lastIconIndex) {
|
||||||
// Dupe detected! Increment the index & modulo to make sure we don't go out of bounds
|
// Dupe detected! Increment the index & modulo to make sure we don't go out of bounds
|
||||||
index = (index + 1) % EMOJI.length;
|
index = (index + 1) % EMOJI.length;
|
||||||
}
|
}
|
||||||
let emojiFile = `images/emoji-${EMOJI[index]}.png`;
|
const emojiFile = `images/emoji-${EMOJI[index]}.png`;
|
||||||
lastIconIndex = index;
|
lastIconIndex = index;
|
||||||
|
|
||||||
// There are easier ways for a page to extract an image's imageData, but the approach used here
|
// 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.
|
// works in both extension pages and service workers.
|
||||||
let response = await fetch(chrome.runtime.getURL(emojiFile));
|
const response = await fetch(chrome.runtime.getURL(emojiFile));
|
||||||
let blob = await response.blob();
|
const blob = await response.blob();
|
||||||
let imageBitmap = await createImageBitmap(blob);
|
const imageBitmap = await createImageBitmap(blob);
|
||||||
let osc = new OffscreenCanvas(imageBitmap.width, imageBitmap.height);
|
const osc = new OffscreenCanvas(imageBitmap.width, imageBitmap.height);
|
||||||
let ctx = osc.getContext('2d');
|
let ctx = osc.getContext('2d');
|
||||||
ctx.drawImage(imageBitmap, 0, 0);
|
ctx.drawImage(imageBitmap, 0, 0);
|
||||||
let imageData = ctx.getImageData(0, 0, osc.width, osc.height);
|
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', () => {
|
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 });
|
chrome.action.setIcon({ path: manifest.action.default_icon });
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -205,23 +219,28 @@ document.getElementById('reset-icon-button').addEventListener('click', () => {
|
|||||||
// get/set title
|
// get/set title
|
||||||
// -------------
|
// -------------
|
||||||
|
|
||||||
let titleInput = document.getElementById('title-input');
|
const titleInput = document.getElementById('title-input');
|
||||||
let titleInputDebounce = Number.parseInt(titleInput.dataset.debounce || 100);
|
const titleInputDebounce = Number.parseInt(titleInput.dataset.debounce || 100);
|
||||||
titleInput.addEventListener('input', debounce(200, async (event) => {
|
titleInput.addEventListener(
|
||||||
let title = event.target.value;
|
'input',
|
||||||
chrome.action.setTitle({ title });
|
debounce(200, async (event) => {
|
||||||
|
const title = event.target.value;
|
||||||
|
chrome.action.setTitle({ title });
|
||||||
|
|
||||||
showActionTitle();
|
showActionTitle();
|
||||||
}));
|
})
|
||||||
|
);
|
||||||
|
|
||||||
document.getElementById('reset-title-button').addEventListener('click', async (event) => {
|
document
|
||||||
let manifest = chrome.runtime.getManifest();
|
.getElementById('reset-title-button')
|
||||||
let title = manifest.action.default_title;
|
.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() {
|
async function showActionTitle() {
|
||||||
let title = await chrome.action.getTitle({});
|
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 empty, the title falls back to the name of the extension
|
||||||
if (title === '') {
|
if (title === '') {
|
||||||
// … which we can get from the extension's manifest
|
// … which we can get from the extension's manifest
|
||||||
let manifest = chrome.runtime.getManifest();
|
const manifest = chrome.runtime.getManifest();
|
||||||
title = manifest.name;
|
title = manifest.name;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,32 +1,32 @@
|
|||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
<html lang="en">
|
<html lang="en">
|
||||||
<head>
|
<head>
|
||||||
<meta charset="UTF-8">
|
<meta charset="UTF-8" />
|
||||||
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
<title>Document</title>
|
<title>Document</title>
|
||||||
<style>
|
<style>
|
||||||
.center {
|
.center {
|
||||||
min-height: 100px;
|
min-height: 100px;
|
||||||
min-width: 200px;
|
min-width: 200px;
|
||||||
display: grid;
|
display: grid;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
gap: 1ch;
|
gap: 1ch;
|
||||||
background-color: salmon;
|
background-color: salmon;
|
||||||
}
|
}
|
||||||
.text {
|
.text {
|
||||||
font-size: 2rem;
|
font-size: 2rem;
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
color: white;
|
color: white;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<h2>Action API Demo</h2>
|
<h2>Action API Demo</h2>
|
||||||
<div class="center">
|
<div class="center">
|
||||||
<span class="text">A</span>
|
<span class="text">A</span>
|
||||||
</div>
|
</div>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|||||||
@@ -1,32 +1,32 @@
|
|||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
<html lang="en">
|
<html lang="en">
|
||||||
<head>
|
<head>
|
||||||
<meta charset="UTF-8">
|
<meta charset="UTF-8" />
|
||||||
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
<title>Document</title>
|
<title>Document</title>
|
||||||
<style>
|
<style>
|
||||||
.center {
|
.center {
|
||||||
min-height: 100px;
|
min-height: 100px;
|
||||||
min-width: 200px;
|
min-width: 200px;
|
||||||
display: grid;
|
display: grid;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
gap: 1ch;
|
gap: 1ch;
|
||||||
background-color: royalblue;
|
background-color: royalblue;
|
||||||
}
|
}
|
||||||
.text {
|
.text {
|
||||||
font-size: 2rem;
|
font-size: 2rem;
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
color: white;
|
color: white;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<h2>Action API Demo</h2>
|
<h2>Action API Demo</h2>
|
||||||
<div class="center">
|
<div class="center">
|
||||||
<span class="text">B</span>
|
<span class="text">B</span>
|
||||||
</div>
|
</div>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|||||||
@@ -1,32 +1,32 @@
|
|||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
<html lang="en">
|
<html lang="en">
|
||||||
<head>
|
<head>
|
||||||
<meta charset="UTF-8">
|
<meta charset="UTF-8" />
|
||||||
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
<title>Document</title>
|
<title>Document</title>
|
||||||
<style>
|
<style>
|
||||||
.center {
|
.center {
|
||||||
min-height: 100px;
|
min-height: 100px;
|
||||||
min-width: 200px;
|
min-width: 200px;
|
||||||
display: grid;
|
display: grid;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
gap: 1ch;
|
gap: 1ch;
|
||||||
background-color: lightseagreen;
|
background-color: lightseagreen;
|
||||||
}
|
}
|
||||||
.text {
|
.text {
|
||||||
font-size: 2rem;
|
font-size: 2rem;
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
color: white;
|
color: white;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<h2>Action API Demo</h2>
|
<h2>Action API Demo</h2>
|
||||||
<div class="center">
|
<div class="center">
|
||||||
<span class="text">Hello, world!</span>
|
<span class="text">Hello, world!</span>
|
||||||
</div>
|
</div>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|||||||
@@ -6,14 +6,16 @@
|
|||||||
|
|
||||||
// Initialize the demo on install
|
// Initialize the demo on install
|
||||||
chrome.runtime.onInstalled.addListener((reason) => {
|
chrome.runtime.onInstalled.addListener((reason) => {
|
||||||
if (reason !== chrome.runtime.OnInstalledReason.INSTALL) { return }
|
if (reason !== chrome.runtime.OnInstalledReason.INSTALL) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
openDemoTab();
|
openDemoTab();
|
||||||
|
|
||||||
// Create an alarm so we have something to look at in the demo
|
// Create an alarm so we have something to look at in the demo
|
||||||
chrome.alarms.create('demo-default-alarm', {
|
chrome.alarms.create('demo-default-alarm', {
|
||||||
delayInMinutes: 1,
|
delayInMinutes: 1,
|
||||||
periodInMinutes: 1,
|
periodInMinutes: 1
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -43,7 +43,7 @@ body {
|
|||||||
.alarm-display,
|
.alarm-display,
|
||||||
.alarm-log {
|
.alarm-log {
|
||||||
min-height: 2em;
|
min-height: 2em;
|
||||||
padding: .5em;
|
padding: 0.5em;
|
||||||
background-color: hsl(0, 0%, 95%);
|
background-color: hsl(0, 0%, 95%);
|
||||||
border: 1px solid hsl(0, 0%, 80%);
|
border: 1px solid hsl(0, 0%, 80%);
|
||||||
border-radius: 4px;
|
border-radius: 4px;
|
||||||
@@ -55,7 +55,7 @@ body {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.alarm-log > * {
|
.alarm-log > * {
|
||||||
padding: .5em;
|
padding: 0.5em;
|
||||||
}
|
}
|
||||||
|
|
||||||
.alarm-log > *:not(:first-child) {
|
.alarm-log > *:not(:first-child) {
|
||||||
@@ -67,7 +67,7 @@ body {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.alarm-row {
|
.alarm-row {
|
||||||
padding: .5em;
|
padding: 0.5em;
|
||||||
position: relative;
|
position: relative;
|
||||||
border-top: 1px solid transparent;
|
border-top: 1px solid transparent;
|
||||||
border-bottom: 1px solid transparent;
|
border-bottom: 1px solid transparent;
|
||||||
@@ -75,7 +75,7 @@ body {
|
|||||||
|
|
||||||
.alarm-row:hover,
|
.alarm-row:hover,
|
||||||
.alarm-log > *:hover {
|
.alarm-log > *:hover {
|
||||||
background: hsl(190, 20%, 90%)
|
background: hsl(190, 20%, 90%);
|
||||||
}
|
}
|
||||||
|
|
||||||
.alarm-row:not(:first-child) {
|
.alarm-row:not(:first-child) {
|
||||||
@@ -92,6 +92,6 @@ body {
|
|||||||
|
|
||||||
.alarm-row__cancel-button {
|
.alarm-row__cancel-button {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
top: .5em;
|
top: 0.5em;
|
||||||
right: .5em;
|
right: 0.5em;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,66 +1,75 @@
|
|||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
<html lang="en">
|
<html lang="en">
|
||||||
<head>
|
<head>
|
||||||
<meta charset="UTF-8">
|
<meta charset="UTF-8" />
|
||||||
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
<title>Document</title>
|
<title>Document</title>
|
||||||
<link rel="stylesheet" href="index.css">
|
<link rel="stylesheet" href="index.css" />
|
||||||
<script defer src="index.js"></script>
|
<script defer src="index.js"></script>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<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>
|
|
||||||
|
|
||||||
<section>
|
<section>
|
||||||
<h2>Alarm log</h2>
|
<h2>Create Alarm</h2>
|
||||||
<pre class="alarm-log"></pre>
|
<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>
|
||||||
</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>
|
</html>
|
||||||
|
|||||||
@@ -13,26 +13,26 @@ const pad = (val, len = 2) => val.toString().padStart(len, '0');
|
|||||||
|
|
||||||
// DOM event bindings
|
// DOM event bindings
|
||||||
|
|
||||||
//// Alarm display buttons
|
// Alarm display buttons
|
||||||
|
|
||||||
clearButton.addEventListener('click', () => manager.cancelAllAlarms());
|
clearButton.addEventListener('click', () => manager.cancelAllAlarms());
|
||||||
refreshButton.addEventListener('click', () => manager.refreshDisplay());
|
refreshButton.addEventListener('click', () => manager.refreshDisplay());
|
||||||
|
|
||||||
//// New alarm form
|
// New alarm form
|
||||||
|
|
||||||
form.addEventListener('submit', (event) => {
|
form.addEventListener('submit', (event) => {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
let formData = new FormData(form);
|
const formData = new FormData(form);
|
||||||
let data = Object.fromEntries(formData);
|
const data = Object.fromEntries(formData);
|
||||||
|
|
||||||
// Extract form values
|
// Extract form values
|
||||||
let name = data['alarm-name'];
|
const name = data['alarm-name'];
|
||||||
let delay = Number.parseFloat(data['time-value']);
|
const delay = Number.parseFloat(data['time-value']);
|
||||||
let delayFormat = data['time-format'];
|
const delayFormat = data['time-format'];
|
||||||
let period = Number.parseFloat(data['period']);
|
const period = Number.parseFloat(data['period']);
|
||||||
|
|
||||||
// Prepare alarm info for creation call
|
// Prepare alarm info for creation call
|
||||||
let alarmInfo = {};
|
const alarmInfo = {};
|
||||||
|
|
||||||
if (delayFormat === 'ms') {
|
if (delayFormat === 'ms') {
|
||||||
// Specified in milliseconds, use `when` property
|
// Specified in milliseconds, use `when` property
|
||||||
@@ -62,15 +62,15 @@ class AlarmManager {
|
|||||||
}
|
}
|
||||||
|
|
||||||
logMessage(message) {
|
logMessage(message) {
|
||||||
let date = new Date();
|
const date = new Date();
|
||||||
let pad = (val, len = 2) => val.toString().padStart(len, '0');
|
const pad = (val, len = 2) => val.toString().padStart(len, '0');
|
||||||
let h = pad(date.getHours());
|
const h = pad(date.getHours());
|
||||||
let m = pad(date.getMinutes());
|
const m = pad(date.getMinutes());
|
||||||
let s = pad(date.getSeconds());
|
const s = pad(date.getSeconds());
|
||||||
let ms = pad(date.getMilliseconds(), 3);
|
const ms = pad(date.getMilliseconds(), 3);
|
||||||
let time = `${h}:${m}:${s}.${ms}`;
|
const time = `${h}:${m}:${s}.${ms}`;
|
||||||
|
|
||||||
let logLine = document.createElement('div');
|
const logLine = document.createElement('div');
|
||||||
logLine.textContent = `[${time}] ${message}`;
|
logLine.textContent = `[${time}] ${message}`;
|
||||||
|
|
||||||
// Log events in reverse chronological order
|
// Log events in reverse chronological order
|
||||||
@@ -78,20 +78,20 @@ class AlarmManager {
|
|||||||
}
|
}
|
||||||
|
|
||||||
handleAlarm = async (alarm) => {
|
handleAlarm = async (alarm) => {
|
||||||
let json = JSON.stringify(alarm);
|
const json = JSON.stringify(alarm);
|
||||||
this.logMessage(`Alarm "${alarm.name}" fired\n${json}}`);
|
this.logMessage(`Alarm "${alarm.name}" fired\n${json}}`);
|
||||||
await this.refreshDisplay();
|
await this.refreshDisplay();
|
||||||
}
|
};
|
||||||
|
|
||||||
handleCancelAlarm = async (event) => {
|
handleCancelAlarm = async (event) => {
|
||||||
if (!event.target.classList.contains('alarm-row__cancel-button')) {
|
if (!event.target.classList.contains('alarm-row__cancel-button')) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
let name = event.target.parentElement.dataset.name;
|
const name = event.target.parentElement.dataset.name;
|
||||||
await this.cancelAlarm(name);
|
await this.cancelAlarm(name);
|
||||||
await this.refreshDisplay();
|
await this.refreshDisplay();
|
||||||
}
|
};
|
||||||
|
|
||||||
async cancelAlarm(name) {
|
async cancelAlarm(name) {
|
||||||
// TODO: Remove custom promise wrapper once the Alarms API supports promises
|
// 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
|
// Thin wrapper around alarms.create to log creation event
|
||||||
createAlarm(name, alarmInfo) {
|
createAlarm(name, alarmInfo) {
|
||||||
chrome.alarms.create(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.logMessage(`Created "${name}"\n${json}`);
|
||||||
this.refreshDisplay();
|
this.refreshDisplay();
|
||||||
}
|
}
|
||||||
|
|
||||||
renderAlarm(alarm, isLast) {
|
renderAlarm(alarm, isLast) {
|
||||||
let alarmEl = document.createElement('div');
|
const alarmEl = document.createElement('div');
|
||||||
alarmEl.classList.add('alarm-row');
|
alarmEl.classList.add('alarm-row');
|
||||||
alarmEl.dataset.name = alarm.name;
|
alarmEl.dataset.name = alarm.name;
|
||||||
alarmEl.textContent = JSON.stringify(alarm, 0, 2) + (isLast ? '' : ',');
|
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.classList.add('alarm-row__cancel-button');
|
||||||
cancelButton.textContent = 'cancel';
|
cancelButton.textContent = 'cancel';
|
||||||
alarmEl.appendChild(cancelButton);
|
alarmEl.appendChild(cancelButton);
|
||||||
@@ -142,15 +142,15 @@ class AlarmManager {
|
|||||||
|
|
||||||
resolve(wasCleared);
|
resolve(wasCleared);
|
||||||
});
|
});
|
||||||
})
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
async populateDisplay() {
|
async populateDisplay() {
|
||||||
// TODO: Remove custom promise wrapper once the Alarms API supports promises
|
// TODO: Remove custom promise wrapper once the Alarms API supports promises
|
||||||
return new Promise((resolve) => {
|
return new Promise((resolve) => {
|
||||||
chrome.alarms.getAll((alarms) => {
|
chrome.alarms.getAll((alarms) => {
|
||||||
for (let [index, alarm] of alarms.entries()) {
|
for (const [index, alarm] of alarms.entries()) {
|
||||||
let isLast = index === alarms.length - 1;
|
const isLast = index === alarms.length - 1;
|
||||||
this.renderAlarm(alarm, isLast);
|
this.renderAlarm(alarm, isLast);
|
||||||
}
|
}
|
||||||
resolve();
|
resolve();
|
||||||
@@ -163,16 +163,15 @@ class AlarmManager {
|
|||||||
#refreshing = false;
|
#refreshing = false;
|
||||||
|
|
||||||
async refreshDisplay() {
|
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 {
|
try {
|
||||||
await Promise.all([
|
await Promise.all([this.clearDisplay(), this.populateDisplay()]);
|
||||||
this.clearDisplay(),
|
|
||||||
this.populateDisplay(),
|
|
||||||
]);
|
|
||||||
} finally {
|
} 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();
|
manager.refreshDisplay();
|
||||||
|
|||||||
@@ -5,8 +5,6 @@
|
|||||||
"background": {
|
"background": {
|
||||||
"service_worker": "bg-wrapper.js"
|
"service_worker": "bg-wrapper.js"
|
||||||
},
|
},
|
||||||
"permissions": [
|
"permissions": ["alarms"],
|
||||||
"alarms"
|
|
||||||
],
|
|
||||||
"action": {}
|
"action": {}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,56 +3,54 @@
|
|||||||
// found in the LICENSE file.
|
// found in the LICENSE file.
|
||||||
|
|
||||||
// When you specify "type": "module" in the manifest background,
|
// When you specify "type": "module" in the manifest background,
|
||||||
// you can include the service worker as an ES Module,
|
// you can include the service worker as an ES Module,
|
||||||
import { tldLocales } from './locales.js'
|
import { tldLocales } from './locales.js';
|
||||||
|
|
||||||
// Add a listener to create the initial context menu items,
|
// Add a listener to create the initial context menu items,
|
||||||
// context menu items only need to be created at runtime.onInstalled
|
// context menu items only need to be created at runtime.onInstalled
|
||||||
chrome.runtime.onInstalled.addListener(async () => {
|
chrome.runtime.onInstalled.addListener(async () => {
|
||||||
for (let [tld, locale] of Object.entries(tldLocales)) {
|
for (const [tld, locale] of Object.entries(tldLocales)) {
|
||||||
chrome.contextMenus.create({
|
chrome.contextMenus.create({
|
||||||
id: tld,
|
id: tld,
|
||||||
title: locale,
|
title: locale,
|
||||||
type: 'normal',
|
type: 'normal',
|
||||||
contexts: ['selection'],
|
contexts: ['selection']
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// Open a new search tab when the user clicks a context menu
|
// Open a new search tab when the user clicks a context menu
|
||||||
chrome.contextMenus.onClicked.addListener((item, tab) => {
|
chrome.contextMenus.onClicked.addListener((item, tab) => {
|
||||||
const tld = item.menuItemId
|
const tld = item.menuItemId;
|
||||||
let url = new URL(`https://google.${tld}/search`)
|
const url = new URL(`https://google.${tld}/search`);
|
||||||
url.searchParams.set('q', item.selectionText)
|
url.searchParams.set('q', item.selectionText);
|
||||||
chrome.tabs.create({ url: url.href, index: tab.index + 1 });
|
chrome.tabs.create({ url: url.href, index: tab.index + 1 });
|
||||||
});
|
});
|
||||||
|
|
||||||
// Add or removes the locale from context menu
|
// Add or removes the locale from context menu
|
||||||
// when the user checks or unchecks the locale in the popup
|
// when the user checks or unchecks the locale in the popup
|
||||||
chrome.storage.onChanged.addListener(({ enabledTlds }) => {
|
chrome.storage.onChanged.addListener(({ enabledTlds }) => {
|
||||||
if (typeof enabledTlds === 'undefined') return
|
if (typeof enabledTlds === 'undefined') return;
|
||||||
|
|
||||||
let allTlds = Object.keys(tldLocales)
|
const allTlds = Object.keys(tldLocales);
|
||||||
let currentTlds = new Set(enabledTlds.newValue);
|
const currentTlds = new Set(enabledTlds.newValue);
|
||||||
let oldTlds = new Set(enabledTlds.oldValue ?? allTlds);
|
const oldTlds = new Set(enabledTlds.oldValue ?? allTlds);
|
||||||
let changes = allTlds.map((tld) => ({
|
const changes = allTlds.map((tld) => ({
|
||||||
tld,
|
tld,
|
||||||
added: currentTlds.has(tld) && !oldTlds.has(tld),
|
added: currentTlds.has(tld) && !oldTlds.has(tld),
|
||||||
removed: !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) {
|
if (added) {
|
||||||
chrome.contextMenus.create({
|
chrome.contextMenus.create({
|
||||||
id: tld,
|
id: tld,
|
||||||
title: tldLocales[tld],
|
title: tldLocales[tld],
|
||||||
type: 'normal',
|
type: 'normal',
|
||||||
contexts: ['selection'],
|
contexts: ['selection']
|
||||||
});
|
});
|
||||||
}
|
} else if (removed) {
|
||||||
else if (removed) {
|
|
||||||
chrome.contextMenus.remove(tld);
|
chrome.contextMenus.remove(tld);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -6,14 +6,14 @@
|
|||||||
export const tldLocales = {
|
export const tldLocales = {
|
||||||
'com.au': 'Australia',
|
'com.au': 'Australia',
|
||||||
'com.br': 'Brazil',
|
'com.br': 'Brazil',
|
||||||
'ca': 'Canada',
|
ca: 'Canada',
|
||||||
'cn': 'China',
|
cn: 'China',
|
||||||
'fr': 'France',
|
fr: 'France',
|
||||||
'it': 'Italy',
|
it: 'Italy',
|
||||||
'co.in': 'India',
|
'co.in': 'India',
|
||||||
'co.jp': 'Japan',
|
'co.jp': 'Japan',
|
||||||
'com.ms': 'Mexico',
|
'com.ms': 'Mexico',
|
||||||
'ru': 'Russia',
|
ru: 'Russia',
|
||||||
'co.za': 'South Africa',
|
'co.za': 'South Africa',
|
||||||
'co.uk': 'United Kingdom'
|
'co.uk': 'United Kingdom'
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -12,8 +12,8 @@
|
|||||||
"default_popup": "popup.html"
|
"default_popup": "popup.html"
|
||||||
},
|
},
|
||||||
"icons": {
|
"icons": {
|
||||||
"16": "globalGoogle16.png",
|
"16": "globalGoogle16.png",
|
||||||
"48": "globalGoogle48.png",
|
"48": "globalGoogle48.png",
|
||||||
"128": "globalGoogle128.png"
|
"128": "globalGoogle128.png"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,26 +1,24 @@
|
|||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
<html>
|
<html>
|
||||||
|
<head>
|
||||||
|
<title>Global Context Search</title>
|
||||||
|
<style>
|
||||||
|
body {
|
||||||
|
min-width: 300px;
|
||||||
|
font-size: 15px;
|
||||||
|
}
|
||||||
|
|
||||||
<head>
|
input {
|
||||||
<title>Global Context Search</title>
|
margin: 5px;
|
||||||
<style>
|
outline: none;
|
||||||
body {
|
}
|
||||||
min-width: 300px;
|
</style>
|
||||||
font-size: 15px;
|
</head>
|
||||||
}
|
|
||||||
|
|
||||||
input {
|
<body>
|
||||||
margin: 5px;
|
<h2>Global Google Search</h2>
|
||||||
outline: none;
|
<h3>Countries</h3>
|
||||||
}
|
<form id="form"></form>
|
||||||
</style>
|
<script src="popup.js" type="module"></script>
|
||||||
</head>
|
</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.
|
// found in the LICENSE file.
|
||||||
|
|
||||||
// TLD: top level domain; the "com" in "google.com"
|
// TLD: top level domain; the "com" in "google.com"
|
||||||
import { tldLocales } from './locales.js'
|
import { tldLocales } from './locales.js';
|
||||||
|
|
||||||
createForm().catch(console.error);
|
createForm().catch(console.error);
|
||||||
|
|
||||||
async function createForm() {
|
async function createForm() {
|
||||||
let { enabledTlds = Object.keys(tldLocales) } = await chrome.storage.sync.get('enabledTlds');
|
const { enabledTlds = Object.keys(tldLocales) } =
|
||||||
let checked = new Set(enabledTlds)
|
await chrome.storage.sync.get('enabledTlds');
|
||||||
|
const checked = new Set(enabledTlds);
|
||||||
|
|
||||||
let form = document.getElementById('form');
|
const form = document.getElementById('form');
|
||||||
for (let [tld, locale] of Object.entries(tldLocales)) {
|
for (const [tld, locale] of Object.entries(tldLocales)) {
|
||||||
let checkbox = document.createElement('input');
|
const checkbox = document.createElement('input');
|
||||||
checkbox.type = 'checkbox';
|
checkbox.type = 'checkbox';
|
||||||
checkbox.checked = checked.has(tld);
|
checkbox.checked = checked.has(tld);
|
||||||
checkbox.name = tld;
|
checkbox.name = tld;
|
||||||
checkbox.addEventListener('click', (event) => {
|
checkbox.addEventListener('click', (event) => {
|
||||||
handleCheckboxClick(event).catch(console.error)
|
handleCheckboxClick(event).catch(console.error);
|
||||||
})
|
});
|
||||||
|
|
||||||
let span = document.createElement('span');
|
const span = document.createElement('span');
|
||||||
span.textContent = locale;
|
span.textContent = locale;
|
||||||
|
|
||||||
let div = document.createElement('div');
|
const div = document.createElement('div');
|
||||||
div.appendChild(checkbox);
|
div.appendChild(checkbox);
|
||||||
div.appendChild(span);
|
div.appendChild(span);
|
||||||
|
|
||||||
form.appendChild(div);
|
form.appendChild(div);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async function handleCheckboxClick(event) {
|
async function handleCheckboxClick(event) {
|
||||||
let checkbox = event.target
|
const checkbox = event.target;
|
||||||
let tld = checkbox.name
|
const tld = checkbox.name;
|
||||||
let enabled = checkbox.checked
|
const enabled = checkbox.checked;
|
||||||
|
|
||||||
let { enabledTlds = Object.keys(tldLocales) } = await chrome.storage.sync.get('enabledTlds');
|
const { enabledTlds = Object.keys(tldLocales) } =
|
||||||
let tldSet = new Set(enabledTlds)
|
await chrome.storage.sync.get('enabledTlds');
|
||||||
|
const tldSet = new Set(enabledTlds);
|
||||||
if (enabled) tldSet.add(tld)
|
|
||||||
else tldSet.delete(tld)
|
if (enabled) tldSet.add(tld);
|
||||||
|
else tldSet.delete(tld);
|
||||||
await chrome.storage.sync.set({ enabledTlds: [...tldSet] })
|
|
||||||
|
await chrome.storage.sync.set({ enabledTlds: [...tldSet] });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -7,4 +7,4 @@
|
|||||||
"action": {
|
"action": {
|
||||||
"default_popup": "popup.html"
|
"default_popup": "popup.html"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,15 +1,15 @@
|
|||||||
<!doctype html>
|
<!DOCTYPE html>
|
||||||
<html>
|
<html>
|
||||||
<head>
|
<head>
|
||||||
<script src="popup.js" type="module"></script>
|
<script src="popup.js" type="module"></script>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<form id="control-row">
|
<form id="control-row">
|
||||||
<label for="input">Domain:</label>
|
<label for="input">Domain:</label>
|
||||||
<input type="text" id="input">
|
<input type="text" id="input" />
|
||||||
<br>
|
<br />
|
||||||
<button id="go">Clear Cookies</button>
|
<button id="go">Clear Cookies</button>
|
||||||
</form>
|
</form>
|
||||||
<span id="message" hidden></span>
|
<span id="message" hidden></span>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
const form = document.getElementById("control-row");
|
const form = document.getElementById('control-row');
|
||||||
const go = document.getElementById("go");
|
const go = document.getElementById('go');
|
||||||
const input = document.getElementById("input");
|
const input = document.getElementById('input');
|
||||||
const message = document.getElementById("message");
|
const message = document.getElementById('message');
|
||||||
|
|
||||||
// The async IIFE is necessary because Chrome <89 does not support top level await.
|
// The async IIFE is necessary because Chrome <89 does not support top level await.
|
||||||
(async function initPopupWindow() {
|
(async function initPopupWindow() {
|
||||||
@@ -11,13 +11,15 @@ const message = document.getElementById("message");
|
|||||||
try {
|
try {
|
||||||
let url = new URL(tab.url);
|
let url = new URL(tab.url);
|
||||||
input.value = url.hostname;
|
input.value = url.hostname;
|
||||||
} catch {}
|
} catch {
|
||||||
|
// ignore
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
input.focus();
|
input.focus();
|
||||||
})();
|
})();
|
||||||
|
|
||||||
form.addEventListener("submit", handleFormSubmit);
|
form.addEventListener('submit', handleFormSubmit);
|
||||||
|
|
||||||
async function handleFormSubmit(event) {
|
async function handleFormSubmit(event) {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
@@ -26,7 +28,7 @@ async function handleFormSubmit(event) {
|
|||||||
|
|
||||||
let url = stringToUrl(input.value);
|
let url = stringToUrl(input.value);
|
||||||
if (!url) {
|
if (!url) {
|
||||||
setMessage("Invalid URL");
|
setMessage('Invalid URL');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -38,11 +40,15 @@ function stringToUrl(input) {
|
|||||||
// Start with treating the provided value as a URL
|
// Start with treating the provided value as a URL
|
||||||
try {
|
try {
|
||||||
return new URL(input);
|
return new URL(input);
|
||||||
} catch {}
|
} catch {
|
||||||
|
// ignore
|
||||||
|
}
|
||||||
// If that fails, try assuming the provided input is an HTTP host
|
// If that fails, try assuming the provided input is an HTTP host
|
||||||
try {
|
try {
|
||||||
return new URL("http://" + input);
|
return new URL('http://' + input);
|
||||||
} catch {}
|
} catch {
|
||||||
|
// ignore
|
||||||
|
}
|
||||||
// If that fails ¯\_(ツ)_/¯
|
// If that fails ¯\_(ツ)_/¯
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
@@ -53,7 +59,7 @@ async function deleteDomainCookies(domain) {
|
|||||||
const cookies = await chrome.cookies.getAll({ domain });
|
const cookies = await chrome.cookies.getAll({ domain });
|
||||||
|
|
||||||
if (cookies.length === 0) {
|
if (cookies.length === 0) {
|
||||||
return "No cookies found";
|
return 'No cookies found';
|
||||||
}
|
}
|
||||||
|
|
||||||
let pending = cookies.map(deleteCookie);
|
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
|
// To remove cookies set with a Secure attribute, we must provide the correct protocol in the
|
||||||
// details object's `url` property.
|
// details object's `url` property.
|
||||||
// https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Set-Cookie#Secure
|
// 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
|
// 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
|
// 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({
|
return chrome.cookies.remove({
|
||||||
url: cookieUrl,
|
url: cookieUrl,
|
||||||
name: cookie.name,
|
name: cookie.name,
|
||||||
storeId: cookie.storeId,
|
storeId: cookie.storeId
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -98,5 +104,5 @@ function setMessage(str) {
|
|||||||
|
|
||||||
function clearMessage() {
|
function clearMessage() {
|
||||||
message.hidden = true;
|
message.hidden = true;
|
||||||
message.textContent = "";
|
message.textContent = '';
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,13 +4,18 @@
|
|||||||
chrome.commands.onCommand.addListener(async (command) => {
|
chrome.commands.onCommand.addListener(async (command) => {
|
||||||
const tabs = await chrome.tabs.query({ currentWindow: true });
|
const tabs = await chrome.tabs.query({ currentWindow: true });
|
||||||
// Sort tabs according to their index in the window.
|
// Sort tabs according to their index in the window.
|
||||||
tabs.sort((a, b) => { return a.index < b.index; });
|
tabs.sort((a, b) => {
|
||||||
let activeIndex = tabs.findIndex((tab) => { return tab.active; });
|
return a.index < b.index;
|
||||||
let lastTab = tabs.length - 1;
|
});
|
||||||
|
const activeIndex = tabs.findIndex((tab) => {
|
||||||
|
return tab.active;
|
||||||
|
});
|
||||||
|
const lastTab = tabs.length - 1;
|
||||||
let newIndex = -1;
|
let newIndex = -1;
|
||||||
if (command === 'flip-tabs-forward')
|
if (command === 'flip-tabs-forward') {
|
||||||
newIndex = activeIndex === 0 ? lastTab : activeIndex - 1;
|
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 });
|
chrome.tabs.update(tabs[newIndex].id, { active: true, highlighted: true });
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -32,4 +32,4 @@
|
|||||||
"48": "images/tabFlipper48.png",
|
"48": "images/tabFlipper48.png",
|
||||||
"128": "images/tabFlipper128.png"
|
"128": "images/tabFlipper128.png"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
<html lang="en">
|
<html lang="en">
|
||||||
<body>
|
<body>
|
||||||
<script src="popup.js"></script>
|
<script src="popup.js"></script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|||||||
@@ -1,11 +1,11 @@
|
|||||||
function faviconURL(u) {
|
function faviconURL(u) {
|
||||||
const url = new URL(chrome.runtime.getURL("/_favicon/"));
|
const url = new URL(chrome.runtime.getURL('/_favicon/'));
|
||||||
url.searchParams.set("pageUrl", u); // this encodes the URL as well
|
url.searchParams.set('pageUrl', u); // this encodes the URL as well
|
||||||
url.searchParams.set("size", "32");
|
url.searchParams.set('size', '32');
|
||||||
return url.toString();
|
return url.toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
const img = document.createElement('img');
|
const img = document.createElement('img');
|
||||||
// chrome-extension://EXTENSION_ID/_favicon/?pageUrl=https%3A%2F%2Fwww.google.com&size=32
|
// 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);
|
document.body.appendChild(img);
|
||||||
|
|||||||
@@ -5,6 +5,6 @@
|
|||||||
// This event is fired with the user accepts the input in the omnibox.
|
// This event is fired with the user accepts the input in the omnibox.
|
||||||
chrome.omnibox.onInputEntered.addListener((text) => {
|
chrome.omnibox.onInputEntered.addListener((text) => {
|
||||||
// Encode user input for special characters , / ? : @ & = + $ #
|
// 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 });
|
chrome.tabs.create({ url: newURL });
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -6,12 +6,12 @@
|
|||||||
"background": {
|
"background": {
|
||||||
"service_worker": "background.js"
|
"service_worker": "background.js"
|
||||||
},
|
},
|
||||||
"omnibox": { "keyword" : "nt" },
|
"omnibox": { "keyword": "nt" },
|
||||||
"action": {
|
"action": {
|
||||||
"default_icon": {
|
"default_icon": {
|
||||||
"16": "newtab_search16.png",
|
"16": "newtab_search16.png",
|
||||||
"32": "newtab_search32.png"
|
"32": "newtab_search32.png"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"icons": {
|
"icons": {
|
||||||
"16": "newtab_search16.png",
|
"16": "newtab_search16.png",
|
||||||
|
|||||||
@@ -3,11 +3,11 @@
|
|||||||
* source code is governed by a BSD-style license that can be found in the
|
* source code is governed by a BSD-style license that can be found in the
|
||||||
* LICENSE file.
|
* LICENSE file.
|
||||||
-->
|
-->
|
||||||
<!DOCTYPE HTML>
|
<!DOCTYPE html>
|
||||||
<html>
|
<html>
|
||||||
<head>
|
<head>
|
||||||
<title>Printers</title>
|
<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>
|
<script src="printers.js"></script>
|
||||||
</head>
|
</head>
|
||||||
|
|
||||||
|
|||||||
@@ -6,42 +6,46 @@ function onPrintButtonClicked(printerId, dpi) {
|
|||||||
var ticket = {
|
var ticket = {
|
||||||
version: '1.0',
|
version: '1.0',
|
||||||
print: {
|
print: {
|
||||||
color: {type: 'STANDARD_MONOCHROME'},
|
color: { type: 'STANDARD_MONOCHROME' },
|
||||||
duplex: {type: 'NO_DUPLEX'},
|
duplex: { type: 'NO_DUPLEX' },
|
||||||
page_orientation: {type: 'LANDSCAPE'},
|
page_orientation: { type: 'LANDSCAPE' },
|
||||||
copies: {copies: 1},
|
copies: { copies: 1 },
|
||||||
dpi: {horizontal_dpi: dpi.horizontal_dpi, vertical_dpi: dpi.vertical_dpi},
|
dpi: {
|
||||||
|
horizontal_dpi: dpi.horizontal_dpi,
|
||||||
|
vertical_dpi: dpi.vertical_dpi
|
||||||
|
},
|
||||||
media_size: {
|
media_size: {
|
||||||
width_microns: 210000,
|
width_microns: 210000,
|
||||||
height_microns: 297000,
|
height_microns: 297000,
|
||||||
vendor_id: 'iso_a4_210x297mm'
|
vendor_id: 'iso_a4_210x297mm'
|
||||||
},
|
},
|
||||||
collate: {collate: false}
|
collate: { collate: false }
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
fetch('test.pdf')
|
fetch('test.pdf')
|
||||||
.then(response => response.arrayBuffer())
|
.then((response) => response.arrayBuffer())
|
||||||
.then(arrayBuffer => {
|
.then((arrayBuffer) => {
|
||||||
const request = {
|
const request = {
|
||||||
job: {
|
job: {
|
||||||
printerId: printerId,
|
printerId: printerId,
|
||||||
title: 'test job',
|
title: 'test job',
|
||||||
ticket: ticket,
|
ticket: ticket,
|
||||||
contentType: 'application/pdf',
|
contentType: 'application/pdf',
|
||||||
document: new Blob(
|
document: new Blob([new Uint8Array(arrayBuffer)], {
|
||||||
[new Uint8Array(arrayBuffer)], {type: 'application/pdf'})
|
type: 'application/pdf'
|
||||||
}
|
})
|
||||||
};
|
}
|
||||||
chrome.printing.submitJob(request, (response) => {
|
};
|
||||||
if (response !== undefined) {
|
chrome.printing.submitJob(request, (response) => {
|
||||||
console.log(response.status);
|
if (response !== undefined) {
|
||||||
}
|
console.log(response.status);
|
||||||
if (chrome.runtime.lastError !== undefined) {
|
}
|
||||||
console.log(chrome.runtime.lastError.message);
|
if (chrome.runtime.lastError !== undefined) {
|
||||||
}
|
console.log(chrome.runtime.lastError.message);
|
||||||
});
|
}
|
||||||
});
|
});
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function createPrintButton(onClicked) {
|
function createPrintButton(onClicked) {
|
||||||
@@ -52,12 +56,12 @@ function createPrintButton(onClicked) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function createPrintersTable() {
|
function createPrintersTable() {
|
||||||
chrome.printing.getPrinters(function(printers) {
|
chrome.printing.getPrinters(function (printers) {
|
||||||
const tbody = document.createElement('tbody');
|
const tbody = document.createElement('tbody');
|
||||||
|
|
||||||
for (let i = 0; i < printers.length; ++i) {
|
for (let i = 0; i < printers.length; ++i) {
|
||||||
const printer = printers[i];
|
const printer = printers[i];
|
||||||
chrome.printing.getPrinterInfo(printer.id, function(response) {
|
chrome.printing.getPrinterInfo(printer.id, function (response) {
|
||||||
const columnValues = [
|
const columnValues = [
|
||||||
printer.id,
|
printer.id,
|
||||||
printer.name,
|
printer.name,
|
||||||
@@ -67,7 +71,7 @@ function createPrintersTable() {
|
|||||||
printer.isDefault,
|
printer.isDefault,
|
||||||
printer.recentlyUsedRank,
|
printer.recentlyUsedRank,
|
||||||
JSON.stringify(response.capabilities),
|
JSON.stringify(response.capabilities),
|
||||||
response.status,
|
response.status
|
||||||
];
|
];
|
||||||
|
|
||||||
let tr = document.createElement('tr');
|
let tr = document.createElement('tr');
|
||||||
@@ -79,10 +83,14 @@ function createPrintersTable() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const printTd = document.createElement('td');
|
const printTd = document.createElement('td');
|
||||||
printTd.appendChild(createPrintButton(function() {
|
printTd.appendChild(
|
||||||
onPrintButtonClicked(
|
createPrintButton(function () {
|
||||||
printer.id, response.capabilities.printer.dpi.option[0]);
|
onPrintButtonClicked(
|
||||||
}));
|
printer.id,
|
||||||
|
response.capabilities.printer.dpi.option[0]
|
||||||
|
);
|
||||||
|
})
|
||||||
|
);
|
||||||
tr.appendChild(printTd);
|
tr.appendChild(printTd);
|
||||||
|
|
||||||
tbody.appendChild(tr);
|
tbody.appendChild(tr);
|
||||||
@@ -94,6 +102,6 @@ function createPrintersTable() {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
document.addEventListener('DOMContentLoaded', function() {
|
document.addEventListener('DOMContentLoaded', function () {
|
||||||
createPrintersTable();
|
createPrintersTable();
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -9,6 +9,6 @@ chrome.action.onClicked.addListener((tab) => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
function showReadme(info, tab) {
|
function showReadme(info, tab) {
|
||||||
let url = chrome.runtime.getURL("readme.html");
|
const url = chrome.runtime.getURL('readme.html');
|
||||||
chrome.tabs.create({ url });
|
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.innerText = 'Load images';
|
||||||
loadButton.addEventListener('click', handleLoadRequest);
|
loadButton.addEventListener('click', handleLoadRequest);
|
||||||
|
|
||||||
document.querySelector('body').append(loadButton);
|
document.querySelector('body').append(loadButton);
|
||||||
|
|
||||||
function handleLoadRequest() {
|
function handleLoadRequest() {
|
||||||
for (let id of imageIds) {
|
for (const id of imageIds) {
|
||||||
let element = document.getElementById(id);
|
const element = document.getElementById(id);
|
||||||
element.src = chrome.runtime.getURL(`${id}.png`);
|
element.src = chrome.runtime.getURL(`${id}.png`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -18,13 +18,14 @@
|
|||||||
],
|
],
|
||||||
"web_accessible_resources": [
|
"web_accessible_resources": [
|
||||||
{
|
{
|
||||||
"resources": [ "test1.png", "test2.png" ],
|
"resources": ["test1.png", "test2.png"],
|
||||||
"matches": [ "https://web-accessible-resources-1.glitch.me/*" ]
|
"matches": ["https://web-accessible-resources-1.glitch.me/*"]
|
||||||
}, {
|
},
|
||||||
"resources": [ "test3.png", "test4.png" ],
|
{
|
||||||
"matches": [ "https://web-accessible-resources-2.glitch.me/*" ],
|
"resources": ["test3.png", "test4.png"],
|
||||||
|
"matches": ["https://web-accessible-resources-2.glitch.me/*"],
|
||||||
"use_dynamic_url": true
|
"use_dynamic_url": true
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"key": "AAAAB3NzaC1yc2EAAAADAQABAAABAQCnCTnUK8jgYTxnQLdtE6QzkZgn3rZv0U1naCx4csdSDqYEBXgW2pR2m/uUIAU1HzAUfkDckqTezyIG1bPw8l5X8FyWfgMQANFgTPXGRNXTmDSqHcqvS7zvuEr0xF12oGLBKa7cdEsaQzdfDWsm5BlwFIPfPXUokaHEGvxPBjrXHQmx+Z4xAyhzNh+v5bFr63lsL0ysS8z4KVKc1G1lcUZnp7Oz9n0pZP9QW0Oei2KCumDqGpqVd249232a0E9TUeQ+lqAxiN4ybzBgUT5al7Yh1nIhGHxPyRnihtHmx+hxupCuhzXeaoKjWiADp+FEK/aPAzvP5ynLDQHelez/eGdF"
|
"key": "AAAAB3NzaC1yc2EAAAADAQABAAABAQCnCTnUK8jgYTxnQLdtE6QzkZgn3rZv0U1naCx4csdSDqYEBXgW2pR2m/uUIAU1HzAUfkDckqTezyIG1bPw8l5X8FyWfgMQANFgTPXGRNXTmDSqHcqvS7zvuEr0xF12oGLBKa7cdEsaQzdfDWsm5BlwFIPfPXUokaHEGvxPBjrXHQmx+Z4xAyhzNh+v5bFr63lsL0ysS8z4KVKc1G1lcUZnp7Oz9n0pZP9QW0Oei2KCumDqGpqVd249232a0E9TUeQ+lqAxiN4ybzBgUT5al7Yh1nIhGHxPyRnihtHmx+hxupCuhzXeaoKjWiADp+FEK/aPAzvP5ynLDQHelez/eGdF"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,46 +3,127 @@
|
|||||||
License: none (public domain)
|
License: none (public domain)
|
||||||
*/
|
*/
|
||||||
|
|
||||||
html, body, div, span, applet, object, iframe,
|
html,
|
||||||
h1, h2, h3, h4, h5, h6, p, blockquote, pre,
|
body,
|
||||||
a, abbr, acronym, address, big, cite, code,
|
div,
|
||||||
del, dfn, em, img, ins, kbd, q, s, samp,
|
span,
|
||||||
small, strike, strong, sub, sup, tt, var,
|
applet,
|
||||||
b, u, i, center,
|
object,
|
||||||
dl, dt, dd, ol, ul, li,
|
iframe,
|
||||||
fieldset, form, label, legend,
|
h1,
|
||||||
table, caption, tbody, tfoot, thead, tr, th, td,
|
h2,
|
||||||
article, aside, canvas, details, embed,
|
h3,
|
||||||
figure, figcaption, footer, header, hgroup,
|
h4,
|
||||||
menu, nav, output, ruby, section, summary,
|
h5,
|
||||||
time, mark, audio, video {
|
h6,
|
||||||
margin: 0;
|
p,
|
||||||
padding: 0;
|
blockquote,
|
||||||
border: 0;
|
pre,
|
||||||
font-size: 100%;
|
a,
|
||||||
font: inherit;
|
abbr,
|
||||||
vertical-align: baseline;
|
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 */
|
/* HTML5 display-role reset for older browsers */
|
||||||
article, aside, details, figcaption, figure,
|
article,
|
||||||
footer, header, hgroup, menu, nav, section {
|
aside,
|
||||||
display: block;
|
details,
|
||||||
|
figcaption,
|
||||||
|
figure,
|
||||||
|
footer,
|
||||||
|
header,
|
||||||
|
hgroup,
|
||||||
|
menu,
|
||||||
|
nav,
|
||||||
|
section {
|
||||||
|
display: block;
|
||||||
}
|
}
|
||||||
body {
|
body {
|
||||||
line-height: 1;
|
line-height: 1;
|
||||||
}
|
}
|
||||||
ol, ul {
|
ol,
|
||||||
list-style: none;
|
ul {
|
||||||
|
list-style: none;
|
||||||
}
|
}
|
||||||
blockquote, q {
|
blockquote,
|
||||||
quotes: none;
|
q {
|
||||||
|
quotes: none;
|
||||||
}
|
}
|
||||||
blockquote:before, blockquote:after,
|
blockquote:before,
|
||||||
q:before, q:after {
|
blockquote:after,
|
||||||
content: '';
|
q:before,
|
||||||
content: none;
|
q:after {
|
||||||
|
content: "";
|
||||||
|
content: none;
|
||||||
}
|
}
|
||||||
table {
|
table {
|
||||||
border-collapse: collapse;
|
border-collapse: collapse;
|
||||||
border-spacing: 0;
|
border-spacing: 0;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,91 +1,123 @@
|
|||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
<html lang="en">
|
<html lang="en">
|
||||||
<head>
|
<head>
|
||||||
<meta charset="UTF-8">
|
<meta charset="UTF-8" />
|
||||||
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
<title>Web Accessible Resources - Readme</title>
|
<title>Web Accessible Resources - Readme</title>
|
||||||
<style>
|
<style>
|
||||||
body {
|
body {
|
||||||
font-family: Helvetica, Arial, sans-serif;
|
font-family: Helvetica, Arial, sans-serif;
|
||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
}
|
}
|
||||||
table {
|
table {
|
||||||
padding: 0;
|
padding: 0;
|
||||||
border-collapse: collapse;
|
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>
|
||||||
|
|
||||||
}
|
<p>
|
||||||
th {
|
The first image on each site is statically referenced by the site using a
|
||||||
background: hsl(0,0%,90%);
|
URL in the following format:
|
||||||
padding: .25em .5em;
|
<code>chrome-extension://<extension-id>/<image-path></code>. The
|
||||||
text-align: left;
|
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
|
||||||
td {
|
by using
|
||||||
padding: .25em .5em;
|
<a
|
||||||
border-top: 1px solid hsl(0,0%,50%);
|
href="https://developer.chrome.com/docs/extensions/reference/runtime/#method-getURL"
|
||||||
}
|
>chrome.runtime.getURL()</a
|
||||||
</style>
|
>
|
||||||
</head>
|
to build the image's URL at runtime.
|
||||||
<body>
|
</p>
|
||||||
<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
|
<table>
|
||||||
format: <code>chrome-extension://<extension-id>/<image-path></code>. The second image on
|
<thead>
|
||||||
each site will only be injected into the page when you click the "Load images" button for that
|
<tr>
|
||||||
page. This injection is performed by using <a
|
<th>File</th>
|
||||||
href="https://developer.chrome.com/docs/extensions/reference/runtime/#method-getURL">chrome.runtime.getURL()</a>
|
<th>Target domain</th>
|
||||||
to build the image's URL at runtime.</p>
|
<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>
|
<figure>
|
||||||
<thead>
|
<figcaption>
|
||||||
<tr>
|
https://web-accessible-resources-1.glitch.me/ can access images
|
||||||
<th>File</th>
|
<a href="test1.png"><code>test1.png</code></a> and
|
||||||
<th>Target domain</th>
|
<a href="test2.png"><code>test2.png</code></a>
|
||||||
<th>Injection method</th>
|
</figcaption>
|
||||||
</tr>
|
<iframe
|
||||||
</thead>
|
src="https://web-accessible-resources-1.glitch.me/"
|
||||||
<tbody>
|
width="100%"
|
||||||
<tr>
|
height="200"
|
||||||
<td><code><a href="test1.png">test1.png</a></code></td>
|
></iframe>
|
||||||
<td>web-accessible-resources-1.glitch.me</td>
|
</figure>
|
||||||
<td>Statically referenced</td>
|
<figure>
|
||||||
</tr>
|
<figcaption>
|
||||||
<tr>
|
https://web-accessible-resources-2.glitch.me/ can access images
|
||||||
<td><code><a href="test2.png">test2.png</a></code></td>
|
<a href="test3.png"><code>test3.png</code></a> and
|
||||||
<td>web-accessible-resources-1.glitch.me</td>
|
<a href="test4.png"><code>test4.png</code></a>
|
||||||
<td>Dynamically injected</td>
|
</figcaption>
|
||||||
</tr>
|
<iframe
|
||||||
<tr>
|
src="https://web-accessible-resources-2.glitch.me/"
|
||||||
<td><code><a href="test3.png">test3.png</a></code></td>
|
width="100%"
|
||||||
<td>web-accessible-resources-2.glitch.me</td>
|
height="200"
|
||||||
<td>Statically referenced</td>
|
></iframe>
|
||||||
</tr>
|
</figure>
|
||||||
<tr>
|
</body>
|
||||||
<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>
|
|
||||||
</html>
|
</html>
|
||||||
|
|||||||
@@ -29,7 +29,7 @@ async function addToClipboard(value) {
|
|||||||
await chrome.offscreen.createDocument({
|
await chrome.offscreen.createDocument({
|
||||||
url: 'offscreen.html',
|
url: 'offscreen.html',
|
||||||
reasons: [chrome.offscreen.Reason.CLIPBOARD],
|
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
|
// Now that we have an offscreen document, we can dispatch the
|
||||||
@@ -37,11 +37,11 @@ async function addToClipboard(value) {
|
|||||||
chrome.runtime.sendMessage({
|
chrome.runtime.sendMessage({
|
||||||
type: 'copy-data-to-clipboard',
|
type: 'copy-data-to-clipboard',
|
||||||
target: 'offscreen-doc',
|
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.
|
// replace the offscreen document based implementation with something like this.
|
||||||
async function addToClipboardV2(value) {
|
async function addToClipboardV2(value) {
|
||||||
navigator.clipboard.writeText(value);
|
navigator.clipboard.writeText(value);
|
||||||
|
|||||||
@@ -6,8 +6,5 @@
|
|||||||
"service_worker": "background.js"
|
"service_worker": "background.js"
|
||||||
},
|
},
|
||||||
"action": {},
|
"action": {},
|
||||||
"permissions": [
|
"permissions": ["offscreen", "clipboardWrite"]
|
||||||
"offscreen",
|
|
||||||
"clipboardWrite"
|
|
||||||
]
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,3 +1,3 @@
|
|||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
<textarea id="text"></textarea>
|
<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:
|
// We use a <textarea> element for two main reasons:
|
||||||
// 1. preserve the formatting of multiline text,
|
// 1. preserve the formatting of multiline text,
|
||||||
// 2. select the node's content using this element's `.select()` method.
|
// 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
|
// Use the offscreen document's `document` interface to write a new value to the
|
||||||
// system clipboard.
|
// system clipboard.
|
||||||
@@ -56,7 +55,9 @@ let textEl = document.querySelector('#text');
|
|||||||
async function handleClipboardWrite(data) {
|
async function handleClipboardWrite(data) {
|
||||||
// Error if we received the wrong kind of data.
|
// Error if we received the wrong kind of data.
|
||||||
if (typeof data !== 'string') {
|
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
|
// `document.execCommand('copy')` works against the user's selection in a web
|
||||||
@@ -66,6 +67,6 @@ async function handleClipboardWrite(data) {
|
|||||||
textEl.select();
|
textEl.select();
|
||||||
document.execCommand('copy');
|
document.execCommand('copy');
|
||||||
|
|
||||||
//Job's done! Close the offscreen document.
|
// Job's done! Close the offscreen document.
|
||||||
window.close();
|
window.close();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,10 +5,7 @@
|
|||||||
"background": {
|
"background": {
|
||||||
"service_worker": "background.js"
|
"service_worker": "background.js"
|
||||||
},
|
},
|
||||||
"permissions": [
|
"permissions": ["scripting", "activeTab"],
|
||||||
"scripting",
|
|
||||||
"activeTab"
|
|
||||||
],
|
|
||||||
"action": {
|
"action": {
|
||||||
"default_popup": "popup.html"
|
"default_popup": "popup.html"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ body {
|
|||||||
min-height: 10em;
|
min-height: 10em;
|
||||||
}
|
}
|
||||||
main {
|
main {
|
||||||
padding: 1em .5em;
|
padding: 1em 0.5em;
|
||||||
display: grid;
|
display: grid;
|
||||||
place-items: center;
|
place-items: center;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,21 +1,21 @@
|
|||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
<html lang="en">
|
<html lang="en">
|
||||||
<head>
|
<head>
|
||||||
<meta charset="UTF-8">
|
<meta charset="UTF-8" />
|
||||||
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
<title>Document</title>
|
<title>Document</title>
|
||||||
<link rel="stylesheet" href="popup.css">
|
<link rel="stylesheet" href="popup.css" />
|
||||||
<script src="popup.js" defer></script>
|
<script src="popup.js" defer></script>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<main>
|
<main>
|
||||||
<div>
|
<div>
|
||||||
<button id="inject-file">Inject file</button>
|
<button id="inject-file">Inject file</button>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<button id="inject-function">Inject function</button>
|
<button id="inject-function">Inject function</button>
|
||||||
</div>
|
</div>
|
||||||
</main>
|
</main>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|||||||
@@ -1,17 +1,17 @@
|
|||||||
let injectFile = document.getElementById('inject-file');
|
const injectFile = document.getElementById('inject-file');
|
||||||
let injectFunction = document.getElementById('inject-function');
|
const injectFunction = document.getElementById('inject-function');
|
||||||
|
|
||||||
async function getCurrentTab() {
|
async function getCurrentTab() {
|
||||||
let queryOptions = { active: true, currentWindow: true };
|
const queryOptions = { active: true, currentWindow: true };
|
||||||
let [tab] = await chrome.tabs.query(queryOptions);
|
const [tab] = await chrome.tabs.query(queryOptions);
|
||||||
return tab;
|
return tab;
|
||||||
}
|
}
|
||||||
|
|
||||||
injectFile.addEventListener('click', async () => {
|
injectFile.addEventListener('click', async () => {
|
||||||
let tab = await getCurrentTab();
|
const tab = await getCurrentTab();
|
||||||
|
|
||||||
chrome.scripting.executeScript({
|
chrome.scripting.executeScript({
|
||||||
target: {tabId: tab.id},
|
target: { tabId: tab.id },
|
||||||
files: ['content-script.js']
|
files: ['content-script.js']
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@@ -21,11 +21,11 @@ function showAlert(givenName) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
injectFunction.addEventListener('click', async () => {
|
injectFunction.addEventListener('click', async () => {
|
||||||
let tab = await getCurrentTab();
|
const tab = await getCurrentTab();
|
||||||
|
|
||||||
let name = 'World';
|
const name = 'World';
|
||||||
chrome.scripting.executeScript({
|
chrome.scripting.executeScript({
|
||||||
target: {tabId: tab.id},
|
target: { tabId: tab.id },
|
||||||
func: showAlert,
|
func: showAlert,
|
||||||
args: [name]
|
args: [name]
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -3,9 +3,7 @@
|
|||||||
"description": "A browser action with a popup dump of all bookmarks, including search, add, edit and delete.",
|
"description": "A browser action with a popup dump of all bookmarks, including search, add, edit and delete.",
|
||||||
"version": "1.1",
|
"version": "1.1",
|
||||||
"manifest_version": 3,
|
"manifest_version": 3,
|
||||||
"permissions": [
|
"permissions": ["bookmarks"],
|
||||||
"bookmarks"
|
|
||||||
],
|
|
||||||
"action": {
|
"action": {
|
||||||
"default_title": "My Bookmarks",
|
"default_title": "My Bookmarks",
|
||||||
"default_icon": "icon.png",
|
"default_icon": "icon.png",
|
||||||
|
|||||||
@@ -1,3 +1,3 @@
|
|||||||
#editdialog input {
|
#editdialog input {
|
||||||
width: 100%
|
width: 100%;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,19 +1,19 @@
|
|||||||
<html>
|
<html>
|
||||||
<head>
|
<head>
|
||||||
<link rel="stylesheet" href="third-party/jquery-ui.css">
|
<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.structure.css" />
|
||||||
<link rel="stylesheet" href="third-party/jquery-ui.theme.css">
|
<link rel="stylesheet" href="third-party/jquery-ui.theme.css" />
|
||||||
<link rel="stylesheet" href="popup.css">
|
<link rel="stylesheet" href="popup.css" />
|
||||||
<script src="third-party/jquery-1.12.4.js"></script>
|
<script src="third-party/jquery-1.12.4.js"></script>
|
||||||
<script src="third-party/jquery-ui-1.12.1.js"></script>
|
<script src="third-party/jquery-ui-1.12.1.js"></script>
|
||||||
</head>
|
</head>
|
||||||
<body style="width: 400px">
|
<body style="width: 400px">
|
||||||
<div>Search Bookmarks: <input id="search"></div>
|
<div>Search Bookmarks: <input id="search" /></div>
|
||||||
<div id="bookmarks"></div>
|
<div id="bookmarks"></div>
|
||||||
<div id="editdialog"></div>
|
<div id="editdialog"></div>
|
||||||
<div id="deletedialog"></div>
|
<div id="deletedialog"></div>
|
||||||
<div id="adddialog"></div>
|
<div id="adddialog"></div>
|
||||||
<div id="test-frame"></div>
|
<div id="test-frame"></div>
|
||||||
<script src="popup.js"></script>
|
<script src="popup.js"></script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|||||||
@@ -12,14 +12,16 @@ $('#search').change(function () {
|
|||||||
|
|
||||||
// Traverse the bookmark tree, and print the folder and nodes.
|
// Traverse the bookmark tree, and print the folder and nodes.
|
||||||
function dumpBookmarks(query) {
|
function dumpBookmarks(query) {
|
||||||
var bookmarkTreeNodes = chrome.bookmarks.getTree(function (bookmarkTreeNodes) {
|
const bookmarkTreeNodes = chrome.bookmarks.getTree(function (
|
||||||
|
bookmarkTreeNodes
|
||||||
|
) {
|
||||||
$('#bookmarks').append(dumpTreeNodes(bookmarkTreeNodes, query));
|
$('#bookmarks').append(dumpTreeNodes(bookmarkTreeNodes, query));
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function dumpTreeNodes(bookmarkNodes, query) {
|
function dumpTreeNodes(bookmarkNodes, query) {
|
||||||
var list = $('<ul>');
|
const list = $('<ul>');
|
||||||
for (var i = 0; i < bookmarkNodes.length; i++) {
|
for (let i = 0; i < bookmarkNodes.length; i++) {
|
||||||
list.append(dumpNode(bookmarkNodes[i], query));
|
list.append(dumpNode(bookmarkNodes[i], query));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -29,12 +31,15 @@ function dumpTreeNodes(bookmarkNodes, query) {
|
|||||||
function dumpNode(bookmarkNode, query) {
|
function dumpNode(bookmarkNode, query) {
|
||||||
if (bookmarkNode.title) {
|
if (bookmarkNode.title) {
|
||||||
if (query && !bookmarkNode.children) {
|
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>');
|
return $('<span></span>');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var anchor = $('<a>');
|
const anchor = $('<a>');
|
||||||
anchor.attr('href', bookmarkNode.url);
|
anchor.attr('href', bookmarkNode.url);
|
||||||
anchor.text(bookmarkNode.title);
|
anchor.text(bookmarkNode.title);
|
||||||
|
|
||||||
@@ -47,114 +52,136 @@ function dumpNode(bookmarkNode, query) {
|
|||||||
});
|
});
|
||||||
|
|
||||||
var span = $('<span>');
|
var span = $('<span>');
|
||||||
var options = bookmarkNode.children ?
|
const options = bookmarkNode.children
|
||||||
$('<span>[<a href="#" id="addlink">Add</a>]</span>') :
|
? $('<span>[<a href="#" id="addlink">Add</a>]</span>')
|
||||||
$('<span>[<a id="editlink" href="#">Edit</a> <a id="deletelink" ' +
|
: $(
|
||||||
'href="#">Delete</a>]</span>');
|
'<span>[<a id="editlink" href="#">Edit</a> <a id="deletelink" ' +
|
||||||
var edit = bookmarkNode.children ? $('<table><tr><td>Name</td><td>' +
|
'href="#">Delete</a>]</span>'
|
||||||
'<input id="title"></td></tr><tr><td>URL</td><td><input id="url">' +
|
);
|
||||||
'</td></tr></table>') : $('<input>');
|
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.
|
// Show add and edit links when hover over.
|
||||||
span.hover(function () {
|
span
|
||||||
span.append(options);
|
.hover(
|
||||||
$('#deletelink').click(function (event) {
|
function () {
|
||||||
console.log(event)
|
span.append(options);
|
||||||
$('#deletedialog').empty().dialog({
|
$('#deletelink').click(function (event) {
|
||||||
autoOpen: false,
|
console.log(event);
|
||||||
closeOnEscape: true,
|
$('#deletedialog')
|
||||||
title: 'Confirm Deletion',
|
.empty()
|
||||||
modal: true,
|
.dialog({
|
||||||
show: 'slide',
|
autoOpen: false,
|
||||||
position: {
|
closeOnEscape: true,
|
||||||
my: "left",
|
title: 'Confirm Deletion',
|
||||||
at: "center",
|
modal: true,
|
||||||
of: event.target.parentElement.parentElement
|
show: 'slide',
|
||||||
},
|
position: {
|
||||||
buttons: {
|
my: 'left',
|
||||||
'Yes, Delete It!': function () {
|
at: 'center',
|
||||||
chrome.bookmarks.remove(String(bookmarkNode.id));
|
of: event.target.parentElement.parentElement
|
||||||
span.parent().remove();
|
},
|
||||||
$(this).dialog('destroy');
|
buttons: {
|
||||||
},
|
'Yes, Delete It!': function () {
|
||||||
Cancel: function () {
|
chrome.bookmarks.remove(String(bookmarkNode.id));
|
||||||
$(this).dialog('destroy');
|
span.parent().remove();
|
||||||
}
|
$(this).dialog('destroy');
|
||||||
}
|
},
|
||||||
}).dialog('open');
|
Cancel: function () {
|
||||||
});
|
$(this).dialog('destroy');
|
||||||
$('#addlink').click(function (event) {
|
}
|
||||||
edit.show();
|
}
|
||||||
$('#adddialog').empty().append(edit).dialog({
|
})
|
||||||
autoOpen: false,
|
.dialog('open');
|
||||||
closeOnEscape: true,
|
});
|
||||||
title: 'Add New Bookmark',
|
$('#addlink').click(function (event) {
|
||||||
modal: true,
|
edit.show();
|
||||||
show: 'slide',
|
$('#adddialog')
|
||||||
position: {
|
.empty()
|
||||||
my: "left",
|
.append(edit)
|
||||||
at: "center",
|
.dialog({
|
||||||
of: event.target.parentElement.parentElement
|
autoOpen: false,
|
||||||
},
|
closeOnEscape: true,
|
||||||
buttons: {
|
title: 'Add New Bookmark',
|
||||||
'Add': function () {
|
modal: true,
|
||||||
edit.hide();
|
show: 'slide',
|
||||||
chrome.bookmarks.create({
|
position: {
|
||||||
parentId: bookmarkNode.id,
|
my: 'left',
|
||||||
title: $('#title').val(), url: $('#url').val()
|
at: 'center',
|
||||||
});
|
of: event.target.parentElement.parentElement
|
||||||
$('#bookmarks').empty();
|
},
|
||||||
$(this).dialog('destroy');
|
buttons: {
|
||||||
window.dumpBookmarks();
|
Add: function () {
|
||||||
},
|
edit.hide();
|
||||||
'Cancel': function () {
|
chrome.bookmarks.create({
|
||||||
edit.hide();
|
parentId: bookmarkNode.id,
|
||||||
$(this).dialog('destroy');
|
title: $('#title').val(),
|
||||||
}
|
url: $('#url').val()
|
||||||
}
|
});
|
||||||
}).dialog('open');
|
$('#bookmarks').empty();
|
||||||
});
|
$(this).dialog('destroy');
|
||||||
$('#editlink').click(function (event) {
|
window.dumpBookmarks();
|
||||||
edit.show();
|
},
|
||||||
edit.val(anchor.text());
|
Cancel: function () {
|
||||||
$('#editdialog').empty().append(edit).dialog({
|
edit.hide();
|
||||||
autoOpen: false,
|
$(this).dialog('destroy');
|
||||||
closeOnEscape: true,
|
}
|
||||||
title: 'Edit Title',
|
}
|
||||||
modal: true,
|
})
|
||||||
show: 'fade',
|
.dialog('open');
|
||||||
position: {
|
});
|
||||||
my: "left",
|
$('#editlink').click(function (event) {
|
||||||
at: "center",
|
edit.show();
|
||||||
of: event.target.parentElement.parentElement
|
edit.val(anchor.text());
|
||||||
},
|
$('#editdialog')
|
||||||
buttons: {
|
.empty()
|
||||||
'Save': function () {
|
.append(edit)
|
||||||
edit.hide();
|
.dialog({
|
||||||
chrome.bookmarks.update(String(bookmarkNode.id), {
|
autoOpen: false,
|
||||||
title: edit.val()
|
closeOnEscape: true,
|
||||||
});
|
title: 'Edit Title',
|
||||||
anchor.text(edit.val());
|
modal: true,
|
||||||
options.show();
|
show: 'fade',
|
||||||
$(this).dialog('destroy');
|
position: {
|
||||||
},
|
my: 'left',
|
||||||
'Cancel': function () {
|
at: 'center',
|
||||||
edit.hide();
|
of: event.target.parentElement.parentElement
|
||||||
$(this).dialog('destroy');
|
},
|
||||||
}
|
buttons: {
|
||||||
}
|
Save: function () {
|
||||||
}).dialog('open');
|
edit.hide();
|
||||||
});
|
chrome.bookmarks.update(String(bookmarkNode.id), {
|
||||||
options.fadeIn();
|
title: edit.val()
|
||||||
},
|
});
|
||||||
|
anchor.text(edit.val());
|
||||||
|
options.show();
|
||||||
|
$(this).dialog('destroy');
|
||||||
|
},
|
||||||
|
Cancel: function () {
|
||||||
|
edit.hide();
|
||||||
|
$(this).dialog('destroy');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.dialog('open');
|
||||||
|
});
|
||||||
|
options.fadeIn();
|
||||||
|
},
|
||||||
|
|
||||||
// unhover
|
// unhover
|
||||||
function () {
|
function () {
|
||||||
options.remove();
|
options.remove();
|
||||||
}).append(anchor);
|
}
|
||||||
|
)
|
||||||
|
.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) {
|
if (bookmarkNode.children && bookmarkNode.children.length > 0) {
|
||||||
li.append(dumpTreeNodes(bookmarkNode.children, query));
|
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`.
|
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
|
## Testing the extension
|
||||||
|
|
||||||
|
|||||||
@@ -1,12 +1,12 @@
|
|||||||
function faviconURL(u) {
|
function faviconURL(u) {
|
||||||
const url = new URL(chrome.runtime.getURL("/_favicon/"));
|
const url = new URL(chrome.runtime.getURL('/_favicon/'));
|
||||||
url.searchParams.set("pageUrl", u); // this encodes the URL as well
|
url.searchParams.set('pageUrl', u); // this encodes the URL as well
|
||||||
url.searchParams.set("size", "32");
|
url.searchParams.set('size', '32');
|
||||||
return url.toString();
|
return url.toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
const imageOverlay = document.createElement('img');
|
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.alt = "Google's favicon";
|
||||||
imageOverlay.classList.add('favicon-overlay');
|
imageOverlay.classList.add('favicon-overlay');
|
||||||
document.body.appendChild(imageOverlay);
|
document.body.appendChild(imageOverlay);
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
.favicon-overlay {
|
.favicon-overlay {
|
||||||
all: initial !important;
|
all: initial !important;
|
||||||
position: fixed !important;
|
position: fixed !important;
|
||||||
top: 0 !important;
|
top: 0 !important;
|
||||||
left: 0 !important;
|
left: 0 !important;
|
||||||
z-index: 9999 !important;
|
z-index: 9999 !important;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,8 +2,8 @@
|
|||||||
"name": "Chromium Milestones",
|
"name": "Chromium Milestones",
|
||||||
"version": "1.0",
|
"version": "1.0",
|
||||||
"manifest_version": 3,
|
"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.",
|
"description": "Shows the Chromium release milestone a given code review was merged into.",
|
||||||
"host_permissions": [ "https://crrie.com/" ],
|
"host_permissions": ["https://crrie.com/"],
|
||||||
"permissions": [ "activeTab" ]
|
"permissions": ["activeTab"]
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,19 +12,19 @@
|
|||||||
// See the License for the specific language governing permissions and
|
// See the License for the specific language governing permissions and
|
||||||
// limitations under the License.
|
// 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) {
|
function getMilestone(tabs) {
|
||||||
const div = document.createElement("div");
|
const div = document.createElement('div');
|
||||||
document.body.appendChild(div);
|
document.body.appendChild(div);
|
||||||
const url = tabs[0].url;
|
const url = tabs[0].url;
|
||||||
const origin = 'https://chromium-review.googlesource.com';
|
const origin = 'https://chromium-review.googlesource.com';
|
||||||
const search = `^${origin}/c/chromium/src/\\+/(\\d+)`;
|
const search = `^${origin}/c/chromium/src/\\+/(\\d+)`;
|
||||||
const match = url.match(search);
|
const match = url.match(search);
|
||||||
if (match != undefined && match.length == 2) {
|
if (match != undefined && match.length == 2) {
|
||||||
getMilestoneForRevId(match[1]).then(
|
getMilestoneForRevId(match[1]).then((milestone) =>
|
||||||
(milestone) => milestone != '' ? (div.innerText = `m${milestone}`)
|
milestone != '' ? (div.innerText = `m${milestone}`) : window.close()
|
||||||
: window.close());
|
);
|
||||||
} else {
|
} else {
|
||||||
window.close();
|
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.
|
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"/>
|
<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",
|
"version": "1.0.0",
|
||||||
"description": "Demonstrates optional permissions in extensions",
|
"description": "Demonstrates optional permissions in extensions",
|
||||||
"permissions": ["storage"],
|
"permissions": ["storage"],
|
||||||
"optional_permissions": [
|
"optional_permissions": ["topSites"],
|
||||||
"topSites"
|
|
||||||
],
|
|
||||||
"icons": {
|
"icons": {
|
||||||
"16": "images/icon16.png",
|
"16": "images/icon16.png",
|
||||||
"32": "images/icon32.png",
|
"32": "images/icon32.png",
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
<html>
|
<html>
|
||||||
<head>
|
<head>
|
||||||
<meta charset="UTF-8">
|
<meta charset="UTF-8" />
|
||||||
<title>New Tab - Optional Permissions</title>
|
<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>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<div id="todo_div" class="center colorFun">
|
<div id="todo_div" class="center colorFun">
|
||||||
@@ -12,7 +12,7 @@
|
|||||||
<div id="display_top"></div>
|
<div id="display_top"></div>
|
||||||
<form class="center">
|
<form class="center">
|
||||||
<input id="todo_value" placeholder="My focus today is..." />
|
<input id="todo_value" placeholder="My focus today is..." />
|
||||||
<input type="submit" value="Submit">
|
<input type="submit" value="Submit" />
|
||||||
</form>
|
</form>
|
||||||
<footer></footer>
|
<footer></footer>
|
||||||
<script src="newtab.js"></script>
|
<script src="newtab.js"></script>
|
||||||
|
|||||||
@@ -23,35 +23,35 @@ const todo = document.getElementById('display_todo');
|
|||||||
const form = document.querySelector('form');
|
const form = document.querySelector('form');
|
||||||
const footer = document.querySelector('footer');
|
const footer = document.querySelector('footer');
|
||||||
|
|
||||||
const createTop = () => { chrome.topSites.get((topSites) => {
|
const createTop = () => {
|
||||||
topSites.forEach((site) => {
|
chrome.topSites.get((topSites) => {
|
||||||
let div = document.createElement('div');
|
topSites.forEach((site) => {
|
||||||
div.className = 'colorFun';
|
const div = document.createElement('div');
|
||||||
let tooltip = document.createElement('span');
|
div.className = 'colorFun';
|
||||||
tooltip.innerText = site.title;
|
const tooltip = document.createElement('span');
|
||||||
tooltip.className = 'tooltip';
|
tooltip.innerText = site.title;
|
||||||
let url = document.createElement('a');
|
tooltip.className = 'tooltip';
|
||||||
url.href = site.url;
|
const url = document.createElement('a');
|
||||||
let hostname = (new URL(site.url)).hostname;
|
url.href = site.url;
|
||||||
let image = document.createElement('img');
|
const hostname = new URL(site.url).hostname;
|
||||||
image.title = site.title;
|
const image = document.createElement('img');
|
||||||
image.src = 'https://logo.clearbit.com/' + hostname;
|
image.title = site.title;
|
||||||
url.appendChild(image);
|
image.src = 'https://logo.clearbit.com/' + hostname;
|
||||||
div.appendChild(url);
|
url.appendChild(image);
|
||||||
div.appendChild(tooltip);
|
div.appendChild(url);
|
||||||
sites_div.appendChild(div);
|
div.appendChild(tooltip);
|
||||||
})
|
sites_div.appendChild(div);
|
||||||
})};
|
});
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
chrome.permissions.contains({ permissions: ['topSites'] }).then((result) => {
|
||||||
|
|
||||||
chrome.permissions.contains({permissions: ['topSites']}).then((result)=>{
|
|
||||||
if (result) {
|
if (result) {
|
||||||
// The extension has the permissions.
|
// The extension has the permissions.
|
||||||
createTop();
|
createTop();
|
||||||
} else {
|
} else {
|
||||||
// The extension doesn't have the permissions.
|
// 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.innerText = 'Allow Extension to Access Top Sites';
|
||||||
button.addEventListener('click', (event) => {
|
button.addEventListener('click', (event) => {
|
||||||
chrome.permissions.request(newPerms).then((granted) => {
|
chrome.permissions.request(newPerms).then((granted) => {
|
||||||
@@ -66,21 +66,21 @@ chrome.permissions.contains({permissions: ['topSites']}).then((result)=>{
|
|||||||
});
|
});
|
||||||
footer.appendChild(button);
|
footer.appendChild(button);
|
||||||
}
|
}
|
||||||
})
|
});
|
||||||
|
|
||||||
form.addEventListener('submit', () => {
|
form.addEventListener('submit', () => {
|
||||||
let todo_value = document.getElementById('todo_value');
|
const todo_value = document.getElementById('todo_value');
|
||||||
chrome.storage.sync.set({todo: todo_value.value});
|
chrome.storage.sync.set({ todo: todo_value.value });
|
||||||
});
|
});
|
||||||
|
|
||||||
function setToDo() {
|
function setToDo() {
|
||||||
chrome.storage.sync.get(['todo']).then((value)=>{
|
chrome.storage.sync.get(['todo']).then((value) => {
|
||||||
if (!value.todo) {
|
if (!value.todo) {
|
||||||
todo.innerText = '';
|
todo.innerText = '';
|
||||||
} else {
|
} else {
|
||||||
todo.innerText = value.todo;
|
todo.innerText = value.todo;
|
||||||
}
|
}
|
||||||
})
|
});
|
||||||
};
|
}
|
||||||
|
|
||||||
setToDo();
|
setToDo();
|
||||||
|
|||||||
@@ -37,11 +37,21 @@ h1 {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@keyframes color-extravaganza {
|
@keyframes color-extravaganza {
|
||||||
0% {background-color: #4285F4;}
|
0% {
|
||||||
10% {background-color: #4285F4;}
|
background-color: #4285f4;
|
||||||
25% {background-color: #EA4335;}
|
}
|
||||||
50% {background-color: #FBBC04;}
|
10% {
|
||||||
100% {background-color: #34A853;}
|
background-color: #4285f4;
|
||||||
|
}
|
||||||
|
25% {
|
||||||
|
background-color: #ea4335;
|
||||||
|
}
|
||||||
|
50% {
|
||||||
|
background-color: #fbbc04;
|
||||||
|
}
|
||||||
|
100% {
|
||||||
|
background-color: #34a853;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.colorFun {
|
.colorFun {
|
||||||
@@ -69,8 +79,8 @@ h1 {
|
|||||||
transition: opacity 0.3s;
|
transition: opacity 0.3s;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#todo_div,
|
||||||
#todo_div, .colorFun:hover .tooltip {
|
.colorFun:hover .tooltip {
|
||||||
visibility: visible;
|
visibility: visible;
|
||||||
opacity: 1;
|
opacity: 1;
|
||||||
animation-name: color-extravaganza;
|
animation-name: color-extravaganza;
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ function reddenPage() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
chrome.action.onClicked.addListener((tab) => {
|
chrome.action.onClicked.addListener((tab) => {
|
||||||
if(!tab.url.includes("chrome://")) {
|
if (!tab.url.includes('chrome://')) {
|
||||||
chrome.scripting.executeScript({
|
chrome.scripting.executeScript({
|
||||||
target: { tabId: tab.id },
|
target: { tabId: tab.id },
|
||||||
function: reddenPage
|
function: reddenPage
|
||||||
|
|||||||
@@ -4,10 +4,7 @@
|
|||||||
"manifest_version": 3,
|
"manifest_version": 3,
|
||||||
"version": "0.1",
|
"version": "0.1",
|
||||||
"description": "Turns the page red when you click the icon",
|
"description": "Turns the page red when you click the icon",
|
||||||
"permissions": [
|
"permissions": ["activeTab", "scripting"],
|
||||||
"activeTab",
|
|
||||||
"scripting"
|
|
||||||
],
|
|
||||||
"background": {
|
"background": {
|
||||||
"service_worker": "background.js"
|
"service_worker": "background.js"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,10 +9,8 @@ chrome.alarms.onAlarm.addListener(() => {
|
|||||||
type: 'basic',
|
type: 'basic',
|
||||||
iconUrl: 'stay_hydrated.png',
|
iconUrl: 'stay_hydrated.png',
|
||||||
title: 'Time to Hydrate',
|
title: 'Time to Hydrate',
|
||||||
message: 'Everyday I\'m Guzzlin\'!',
|
message: "Everyday I'm Guzzlin'!",
|
||||||
buttons: [
|
buttons: [{ title: 'Keep it Flowing.' }],
|
||||||
{ title: 'Keep it Flowing.' }
|
|
||||||
],
|
|
||||||
priority: 0
|
priority: 0
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -3,11 +3,7 @@
|
|||||||
"description": "Demonstrates usage and features of the event page by reminding user to drink water",
|
"description": "Demonstrates usage and features of the event page by reminding user to drink water",
|
||||||
"version": "1.0",
|
"version": "1.0",
|
||||||
"manifest_version": 3,
|
"manifest_version": 3,
|
||||||
"permissions": [
|
"permissions": ["alarms", "notifications", "storage"],
|
||||||
"alarms",
|
|
||||||
"notifications",
|
|
||||||
"storage"
|
|
||||||
],
|
|
||||||
"background": {
|
"background": {
|
||||||
"service_worker": "background.js"
|
"service_worker": "background.js"
|
||||||
},
|
},
|
||||||
@@ -21,4 +17,4 @@
|
|||||||
"48": "drink_water48.png",
|
"48": "drink_water48.png",
|
||||||
"128": "drink_water128.png"
|
"128": "drink_water128.png"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ found in the LICENSE file. -->
|
|||||||
}
|
}
|
||||||
|
|
||||||
button:hover {
|
button:hover {
|
||||||
outline: #80DEEA dotted thick;
|
outline: #80deea dotted thick;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
<!--
|
<!--
|
||||||
@@ -29,13 +29,13 @@ found in the LICENSE file. -->
|
|||||||
-->
|
-->
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<img src='./stay_hydrated.png' id='hydrateImage'>
|
<img src="./stay_hydrated.png" id="hydrateImage" />
|
||||||
<!-- 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 -->
|
in approximately 1 minute increments if released -->
|
||||||
<button id="sampleMinute" value="1">Sample minute</button>
|
<button id="sampleMinute" value="1">Sample minute</button>
|
||||||
<button id="min15" value="15">15 Minutes</button>
|
<button id="min15" value="15">15 Minutes</button>
|
||||||
<button id="min30" value="30">30 Minutes</button>
|
<button id="min30" value="30">30 Minutes</button>
|
||||||
<button id="cancelAlarm">Cancel Alarm</button>
|
<button id="cancelAlarm">Cancel Alarm</button>
|
||||||
<script src="popup.js"></script>
|
<script src="popup.js"></script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|||||||
@@ -4,20 +4,20 @@
|
|||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
function setAlarm(event) {
|
function setAlarm(event) {
|
||||||
let minutes = parseFloat(event.target.value);
|
const minutes = parseFloat(event.target.value);
|
||||||
chrome.action.setBadgeText({text: 'ON'});
|
chrome.action.setBadgeText({ text: 'ON' });
|
||||||
chrome.alarms.create({delayInMinutes: minutes});
|
chrome.alarms.create({ delayInMinutes: minutes });
|
||||||
chrome.storage.sync.set({minutes: minutes});
|
chrome.storage.sync.set({ minutes: minutes });
|
||||||
window.close();
|
window.close();
|
||||||
}
|
}
|
||||||
|
|
||||||
function clearAlarm() {
|
function clearAlarm() {
|
||||||
chrome.action.setBadgeText({text: ''});
|
chrome.action.setBadgeText({ text: '' });
|
||||||
chrome.alarms.clearAll();
|
chrome.alarms.clearAll();
|
||||||
window.close();
|
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
|
// in approximately 1 minute increments if released
|
||||||
document.getElementById('sampleMinute').addEventListener('click', setAlarm);
|
document.getElementById('sampleMinute').addEventListener('click', setAlarm);
|
||||||
document.getElementById('min15').addEventListener('click', setAlarm);
|
document.getElementById('min15').addEventListener('click', setAlarm);
|
||||||
|
|||||||
@@ -14,12 +14,12 @@
|
|||||||
|
|
||||||
chrome.runtime.onInstalled.addListener(() => {
|
chrome.runtime.onInstalled.addListener(() => {
|
||||||
chrome.action.setBadgeText({
|
chrome.action.setBadgeText({
|
||||||
text: "OFF",
|
text: 'OFF'
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
const extensions = 'https://developer.chrome.com/docs/extensions'
|
const extensions = 'https://developer.chrome.com/docs/extensions';
|
||||||
const webstore = 'https://developer.chrome.com/docs/webstore'
|
const webstore = 'https://developer.chrome.com/docs/webstore';
|
||||||
|
|
||||||
// When the user clicks on the extension action
|
// When the user clicks on the extension action
|
||||||
chrome.action.onClicked.addListener(async (tab) => {
|
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'
|
// We retrieve the action badge to check if the extension is 'ON' or 'OFF'
|
||||||
const prevState = await chrome.action.getBadgeText({ tabId: tab.id });
|
const prevState = await chrome.action.getBadgeText({ tabId: tab.id });
|
||||||
// Next state will always be the opposite
|
// 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
|
// Set the action badge to the next state
|
||||||
await chrome.action.setBadgeText({
|
await chrome.action.setBadgeText({
|
||||||
tabId: tab.id,
|
tabId: tab.id,
|
||||||
text: nextState,
|
text: nextState
|
||||||
});
|
});
|
||||||
|
|
||||||
if (nextState === "ON") {
|
if (nextState === 'ON') {
|
||||||
// Insert the CSS file when the user turns the extension on
|
// Insert the CSS file when the user turns the extension on
|
||||||
await chrome.scripting.insertCSS({
|
await chrome.scripting.insertCSS({
|
||||||
files: ["focus-mode.css"],
|
files: ['focus-mode.css'],
|
||||||
target: { tabId: tab.id },
|
target: { tabId: tab.id }
|
||||||
});
|
});
|
||||||
} else if (nextState === "OFF") {
|
} else if (nextState === 'OFF') {
|
||||||
// Remove the CSS file when the user turns the extension off
|
// Remove the CSS file when the user turns the extension off
|
||||||
await chrome.scripting.removeCSS({
|
await chrome.scripting.removeCSS({
|
||||||
files: ["focus-mode.css"],
|
files: ['focus-mode.css'],
|
||||||
target: { tabId: tab.id },
|
target: { tabId: tab.id }
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
let color = '#3aa757';
|
const color = '#3aa757';
|
||||||
|
|
||||||
chrome.runtime.onInstalled.addListener(() => {
|
chrome.runtime.onInstalled.addListener(() => {
|
||||||
chrome.storage.sync.set({ color });
|
chrome.storage.sync.set({ color });
|
||||||
|
|||||||
@@ -8,6 +8,5 @@ button {
|
|||||||
}
|
}
|
||||||
|
|
||||||
button.current {
|
button.current {
|
||||||
box-shadow: 0 0 0 2px white,
|
box-shadow: 0 0 0 2px white, 0 0 0 4px black;
|
||||||
0 0 0 4px black;
|
}
|
||||||
}
|
|
||||||
|
|||||||
@@ -1,11 +1,10 @@
|
|||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
<html>
|
<html>
|
||||||
<head>
|
<head>
|
||||||
<link rel="stylesheet" href="button.css">
|
<link rel="stylesheet" href="button.css" />
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<div id="buttonDiv">
|
<div id="buttonDiv"></div>
|
||||||
</div>
|
|
||||||
<div>
|
<div>
|
||||||
<p>Choose a different background color!</p>
|
<p>Choose a different background color!</p>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,12 +1,12 @@
|
|||||||
let page = document.getElementById("buttonDiv");
|
const page = document.getElementById('buttonDiv');
|
||||||
let selectedClassName = "current";
|
const selectedClassName = 'current';
|
||||||
const presetButtonColors = ["#3aa757", "#e8453c", "#f9bb2d", "#4688f1"];
|
const presetButtonColors = ['#3aa757', '#e8453c', '#f9bb2d', '#4688f1'];
|
||||||
|
|
||||||
// Reacts to a button click by marking the selected button and saving
|
// Reacts to a button click by marking the selected button and saving
|
||||||
// the selection
|
// the selection
|
||||||
function handleButtonClick(event) {
|
function handleButtonClick(event) {
|
||||||
// Remove styling from the previously selected color
|
// Remove styling from the previously selected color
|
||||||
let current = event.target.parentElement.querySelector(
|
const current = event.target.parentElement.querySelector(
|
||||||
`.${selectedClassName}`
|
`.${selectedClassName}`
|
||||||
);
|
);
|
||||||
if (current && current !== event.target) {
|
if (current && current !== event.target) {
|
||||||
@@ -14,20 +14,20 @@ function handleButtonClick(event) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Mark the button as selected
|
// Mark the button as selected
|
||||||
let color = event.target.dataset.color;
|
const color = event.target.dataset.color;
|
||||||
event.target.classList.add(selectedClassName);
|
event.target.classList.add(selectedClassName);
|
||||||
chrome.storage.sync.set({ color });
|
chrome.storage.sync.set({ color });
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add a button to the page for each supplied color
|
// Add a button to the page for each supplied color
|
||||||
function constructOptions(buttonColors) {
|
function constructOptions(buttonColors) {
|
||||||
chrome.storage.sync.get("color", (data) => {
|
chrome.storage.sync.get('color', (data) => {
|
||||||
let currentColor = data.color;
|
const currentColor = data.color;
|
||||||
|
|
||||||
// For each color we were provided…
|
// For each color we were provided…
|
||||||
for (let buttonColor of buttonColors) {
|
for (const buttonColor of buttonColors) {
|
||||||
// …create a button with that color…
|
// …create a button with that color…
|
||||||
let button = document.createElement("button");
|
const button = document.createElement('button');
|
||||||
button.dataset.color = buttonColor;
|
button.dataset.color = buttonColor;
|
||||||
button.style.backgroundColor = buttonColor;
|
button.style.backgroundColor = buttonColor;
|
||||||
|
|
||||||
@@ -37,7 +37,7 @@ function constructOptions(buttonColors) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// …and register a listener for when that button is clicked
|
// …and register a listener for when that button is clicked
|
||||||
button.addEventListener("click", handleButtonClick);
|
button.addEventListener('click', handleButtonClick);
|
||||||
page.appendChild(button);
|
page.appendChild(button);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
<html>
|
<html>
|
||||||
<head>
|
<head>
|
||||||
<link rel="stylesheet" href="button.css">
|
<link rel="stylesheet" href="button.css" />
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<button id="changeColor"></button>
|
<button id="changeColor"></button>
|
||||||
|
|||||||
@@ -1,24 +1,24 @@
|
|||||||
// Initialize button with users' preferred color
|
// 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;
|
changeColor.style.backgroundColor = color;
|
||||||
});
|
});
|
||||||
|
|
||||||
// When the button is clicked, inject setPageBackgroundColor into current page
|
// When the button is clicked, inject setPageBackgroundColor into current page
|
||||||
changeColor.addEventListener("click", async () => {
|
changeColor.addEventListener('click', async () => {
|
||||||
let [tab] = await chrome.tabs.query({ active: true, currentWindow: true });
|
const [tab] = await chrome.tabs.query({ active: true, currentWindow: true });
|
||||||
|
|
||||||
chrome.scripting.executeScript({
|
chrome.scripting.executeScript({
|
||||||
target: { tabId: tab.id },
|
target: { tabId: tab.id },
|
||||||
func: setPageBackgroundColor,
|
func: setPageBackgroundColor
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
// The body of this function will be executed as a content script inside the
|
// The body of this function will be executed as a content script inside the
|
||||||
// current page
|
// current page
|
||||||
function setPageBackgroundColor() {
|
function setPageBackgroundColor() {
|
||||||
chrome.storage.sync.get("color", ({ color }) => {
|
chrome.storage.sync.get('color', ({ color }) => {
|
||||||
document.body.style.backgroundColor = 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": [
|
"content_scripts": [
|
||||||
{
|
{
|
||||||
"js": [
|
"js": ["scripts/content.js"],
|
||||||
"scripts/content.js"
|
|
||||||
],
|
|
||||||
"matches": [
|
"matches": [
|
||||||
"https://developer.chrome.com/docs/extensions/*",
|
"https://developer.chrome.com/docs/extensions/*",
|
||||||
"https://developer.chrome.com/docs/webstore/*"
|
"https://developer.chrome.com/docs/webstore/*"
|
||||||
|
|||||||
@@ -12,7 +12,7 @@
|
|||||||
// See the License for the specific language governing permissions and
|
// See the License for the specific language governing permissions and
|
||||||
// limitations under the License.
|
// 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.
|
// `document.querySelector` may return null if the selector doesn't match anything.
|
||||||
if (article) {
|
if (article) {
|
||||||
@@ -24,7 +24,7 @@ if (article) {
|
|||||||
* regular expression character class "\w" to match against "word characters" because it only
|
* 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
|
* 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.
|
* *are not* a whitespace characters. See the below link for more information.
|
||||||
*
|
*
|
||||||
* https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Regular_Expressions
|
* https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Regular_Expressions
|
||||||
*/
|
*/
|
||||||
const wordMatchRegExp = /[^\s]+/g;
|
const wordMatchRegExp = /[^\s]+/g;
|
||||||
@@ -32,16 +32,16 @@ if (article) {
|
|||||||
// matchAll returns an iterator, convert to array to get word count
|
// matchAll returns an iterator, convert to array to get word count
|
||||||
const wordCount = [...words].length;
|
const wordCount = [...words].length;
|
||||||
const readingTime = Math.round(wordCount / 200);
|
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
|
// 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`;
|
badge.textContent = `⏱️ ${readingTime} min read`;
|
||||||
|
|
||||||
// Support for API reference docs
|
// Support for API reference docs
|
||||||
const heading = article.querySelector("h1");
|
const heading = article.querySelector('h1');
|
||||||
// Support for article docs with date
|
// 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
|
// 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": {
|
"action": {
|
||||||
"default_popup": "popup.html"
|
"default_popup": "popup.html"
|
||||||
},
|
},
|
||||||
"host_permissions": [
|
"host_permissions": ["https://developer.chrome.com/*"],
|
||||||
"https://developer.chrome.com/*"
|
"permissions": ["tabGroups"]
|
||||||
],
|
|
||||||
"permissions": [
|
|
||||||
"tabGroups"
|
|
||||||
]
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,26 +14,26 @@
|
|||||||
|
|
||||||
const tabs = await chrome.tabs.query({
|
const tabs = await chrome.tabs.query({
|
||||||
url: [
|
url: [
|
||||||
"https://developer.chrome.com/docs/webstore/*",
|
'https://developer.chrome.com/docs/webstore/*',
|
||||||
"https://developer.chrome.com/docs/extensions/*",
|
'https://developer.chrome.com/docs/extensions/*'
|
||||||
],
|
]
|
||||||
});
|
});
|
||||||
|
|
||||||
// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl/Collator
|
// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl/Collator
|
||||||
const collator = new Intl.Collator();
|
const collator = new Intl.Collator();
|
||||||
tabs.sort((a, b) => collator.compare(a.title, b.title));
|
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();
|
const elements = new Set();
|
||||||
for (const tab of tabs) {
|
for (const tab of tabs) {
|
||||||
const element = template.content.firstElementChild.cloneNode(true);
|
const element = template.content.firstElementChild.cloneNode(true);
|
||||||
|
|
||||||
const title = tab.title.split("-")[0].trim();
|
const title = tab.title.split('-')[0].trim();
|
||||||
const pathname = new URL(tab.url).pathname.slice("/docs".length);
|
const pathname = new URL(tab.url).pathname.slice('/docs'.length);
|
||||||
|
|
||||||
element.querySelector(".title").textContent = title;
|
element.querySelector('.title').textContent = title;
|
||||||
element.querySelector(".pathname").textContent = pathname;
|
element.querySelector('.pathname').textContent = pathname;
|
||||||
element.querySelector("a").addEventListener("click", async () => {
|
element.querySelector('a').addEventListener('click', async () => {
|
||||||
// need to focus window as well as the active tab
|
// need to focus window as well as the active tab
|
||||||
await chrome.tabs.update(tab.id, { active: true });
|
await chrome.tabs.update(tab.id, { active: true });
|
||||||
await chrome.windows.update(tab.windowId, { focused: true });
|
await chrome.windows.update(tab.windowId, { focused: true });
|
||||||
@@ -41,11 +41,11 @@ for (const tab of tabs) {
|
|||||||
|
|
||||||
elements.add(element);
|
elements.add(element);
|
||||||
}
|
}
|
||||||
document.querySelector("ul").append(...elements);
|
document.querySelector('ul').append(...elements);
|
||||||
|
|
||||||
const button = document.querySelector("button");
|
const button = document.querySelector('button');
|
||||||
button.addEventListener("click", async () => {
|
button.addEventListener('click', async () => {
|
||||||
const tabIds = tabs.map(({ id }) => id);
|
const tabIds = tabs.map(({ id }) => id);
|
||||||
const group = await chrome.tabs.group({ tabIds });
|
const group = await chrome.tabs.group({ tabIds });
|
||||||
await chrome.tabGroups.update(group, { title: "DOCS" });
|
await chrome.tabGroups.update(group, { title: 'DOCS' });
|
||||||
});
|
});
|
||||||
|
|||||||
3389
package-lock.json
generated
Normal file
3389
package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
39
package.json
Normal file
39
package.json
Normal file
@@ -0,0 +1,39 @@
|
|||||||
|
{
|
||||||
|
"name": "chrome-extensions-samples",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"private": true,
|
||||||
|
"description": "Official samples for Chrome Extensions and the Chrome Apps platform.",
|
||||||
|
"scripts": {
|
||||||
|
"prettier": "npx prettier **/*.{md,html} -w",
|
||||||
|
"lint": "eslint **/*.js",
|
||||||
|
"lint:fix": "npm run lint -- --fix",
|
||||||
|
"prepare": "husky install"
|
||||||
|
},
|
||||||
|
"repository": {
|
||||||
|
"type": "git",
|
||||||
|
"url": "git+https://github.com/GoogleChrome/chrome-extensions-samples.git"
|
||||||
|
},
|
||||||
|
"keywords": [],
|
||||||
|
"author": "The Chrome Team",
|
||||||
|
"license": "Apache 2.0",
|
||||||
|
"bugs": {
|
||||||
|
"url": "https://github.com/GoogleChrome/chrome-extensions-samples/issues"
|
||||||
|
},
|
||||||
|
"homepage": "https://github.com/GoogleChrome/chrome-extensions-samples#readme",
|
||||||
|
"devDependencies": {
|
||||||
|
"eslint": "^8.34.0",
|
||||||
|
"eslint-config-prettier": "8.6.0",
|
||||||
|
"eslint-plugin-prettier": "4.2.1",
|
||||||
|
"husky": "^8.0.0",
|
||||||
|
"lint-staged": "^13.1.2",
|
||||||
|
"prettier": "2.8.4"
|
||||||
|
},
|
||||||
|
"lint-staged":{
|
||||||
|
"**/*.js":[
|
||||||
|
"npx eslint --fix"
|
||||||
|
],
|
||||||
|
"**/*.{md,html}":[
|
||||||
|
"npx prettier --write"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user