Compare commits
32 Commits
master
...
narco-one-
Author | SHA1 | Date | |
---|---|---|---|
|
2d63d931ac | ||
|
63a6098c2a | ||
|
686c099dbc | ||
|
2753461017 | ||
|
09496304ee | ||
|
fff1d8d057 | ||
|
7a8ef95a30 | ||
|
5ecba47e7f | ||
|
be1d6fa45d | ||
|
e1af6ee60c | ||
|
d2171dc061 | ||
|
d6104c87f7 | ||
|
5df1380426 | ||
|
3c1ee1cd9e | ||
|
2d28a7520d | ||
|
d1bc80282f | ||
|
5d73cbf240 | ||
|
d051bd3477 | ||
|
0766dd7f0a | ||
|
f76512e513 | ||
|
f52fd0d124 | ||
|
407e70b20c | ||
|
9dfafe368c | ||
|
e4aaa9e94f | ||
|
97108767e7 | ||
|
e83ff94ef7 | ||
|
4f45e633ac | ||
|
6474eb178a | ||
|
525030c15d | ||
|
b2af8770ee | ||
|
586ad8aad7 | ||
|
dbc26c2901 |
|
@ -1,2 +1,4 @@
|
||||||
vendor/
|
vendor/
|
||||||
vendor-overwrites/
|
vendor-overwrites/*
|
||||||
|
!vendor-overwrites/colorpicker
|
||||||
|
!vendor-overwrites/csslint
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
# https://github.com/eslint/eslint/blob/master/docs/rules/README.md
|
# https://github.com/eslint/eslint/blob/master/docs/rules/README.md
|
||||||
|
|
||||||
parserOptions:
|
parserOptions:
|
||||||
ecmaVersion: 2017
|
ecmaVersion: 2015
|
||||||
|
|
||||||
env:
|
env:
|
||||||
browser: true
|
browser: true
|
||||||
|
@ -9,7 +9,62 @@ env:
|
||||||
webextensions: true
|
webextensions: true
|
||||||
|
|
||||||
globals:
|
globals:
|
||||||
require: readonly # in polyfill.js
|
# messaging.js
|
||||||
|
KEEP_CHANNEL_OPEN: false
|
||||||
|
CHROME: false
|
||||||
|
FIREFOX: false
|
||||||
|
VIVALDI: false
|
||||||
|
OPERA: false
|
||||||
|
URLS: false
|
||||||
|
BG: false
|
||||||
|
API: false
|
||||||
|
notifyAllTabs: false
|
||||||
|
sendMessage: false
|
||||||
|
queryTabs: false
|
||||||
|
getTab: false
|
||||||
|
getOwnTab: false
|
||||||
|
getActiveTab: false
|
||||||
|
getActiveTabRealURL: false
|
||||||
|
getTabRealURL: false
|
||||||
|
openURL: false
|
||||||
|
activateTab: false
|
||||||
|
stringAsRegExp: false
|
||||||
|
ignoreChromeError: false
|
||||||
|
tryCatch: false
|
||||||
|
tryRegExp: false
|
||||||
|
tryJSONparse: false
|
||||||
|
debounce: false
|
||||||
|
deepCopy: false
|
||||||
|
sessionStorageHash: false
|
||||||
|
download: false
|
||||||
|
invokeOrPostpone: false
|
||||||
|
# localization.js
|
||||||
|
template: false
|
||||||
|
t: false
|
||||||
|
o: false
|
||||||
|
tE: false
|
||||||
|
tHTML: false
|
||||||
|
tNodeList: false
|
||||||
|
tDocLoader: false
|
||||||
|
tWordBreak: false
|
||||||
|
formatDate: false
|
||||||
|
# dom.js
|
||||||
|
onDOMready: false
|
||||||
|
onDOMscriptReady: false
|
||||||
|
scrollElementIntoView: false
|
||||||
|
enforceInputRange: false
|
||||||
|
animateElement: false
|
||||||
|
$: false
|
||||||
|
$$: false
|
||||||
|
$create: false
|
||||||
|
$createLink: false
|
||||||
|
# prefs.js
|
||||||
|
prefs: false
|
||||||
|
setupLivePrefs: false
|
||||||
|
# storage-util.js
|
||||||
|
chromeLocal: false
|
||||||
|
chromeSync: false
|
||||||
|
LZString: false
|
||||||
|
|
||||||
rules:
|
rules:
|
||||||
accessor-pairs: [2]
|
accessor-pairs: [2]
|
||||||
|
@ -22,7 +77,7 @@ rules:
|
||||||
brace-style: [2, 1tbs, {allowSingleLine: false}]
|
brace-style: [2, 1tbs, {allowSingleLine: false}]
|
||||||
camelcase: [2, {properties: never}]
|
camelcase: [2, {properties: never}]
|
||||||
class-methods-use-this: [2]
|
class-methods-use-this: [2]
|
||||||
comma-dangle: [2, {arrays: always-multiline, objects: always-multiline}]
|
comma-dangle: [0]
|
||||||
comma-spacing: [2, {before: false, after: true}]
|
comma-spacing: [2, {before: false, after: true}]
|
||||||
comma-style: [2, last]
|
comma-style: [2, last]
|
||||||
complexity: [0]
|
complexity: [0]
|
||||||
|
@ -34,7 +89,7 @@ rules:
|
||||||
dot-location: [2, property]
|
dot-location: [2, property]
|
||||||
dot-notation: [0]
|
dot-notation: [0]
|
||||||
eol-last: [2]
|
eol-last: [2]
|
||||||
eqeqeq: [1, smart]
|
eqeqeq: [1, always]
|
||||||
func-call-spacing: [2, never]
|
func-call-spacing: [2, never]
|
||||||
func-name-matching: [0]
|
func-name-matching: [0]
|
||||||
func-names: [0]
|
func-names: [0]
|
||||||
|
@ -45,15 +100,7 @@ rules:
|
||||||
id-blacklist: [0]
|
id-blacklist: [0]
|
||||||
id-length: [0]
|
id-length: [0]
|
||||||
id-match: [0]
|
id-match: [0]
|
||||||
indent: [2, 2, {
|
indent-legacy: [2, 2, {VariableDeclarator: 0, SwitchCase: 1}]
|
||||||
SwitchCase: 1,
|
|
||||||
ignoreComments: true,
|
|
||||||
ignoredNodes: [
|
|
||||||
"TemplateLiteral > *",
|
|
||||||
"ConditionalExpression",
|
|
||||||
"ForStatement"
|
|
||||||
]
|
|
||||||
}]
|
|
||||||
jsx-quotes: [0]
|
jsx-quotes: [0]
|
||||||
key-spacing: [0]
|
key-spacing: [0]
|
||||||
keyword-spacing: [2]
|
keyword-spacing: [2]
|
||||||
|
@ -95,9 +142,9 @@ rules:
|
||||||
no-empty-function: [0]
|
no-empty-function: [0]
|
||||||
no-empty-pattern: [2]
|
no-empty-pattern: [2]
|
||||||
no-empty: [2, {allowEmptyCatch: true}]
|
no-empty: [2, {allowEmptyCatch: true}]
|
||||||
no-eq-null: [0]
|
no-eq-null: [2]
|
||||||
no-eval: [2]
|
no-eval: [2]
|
||||||
no-ex-assign: [0]
|
no-ex-assign: [2]
|
||||||
no-extend-native: [2]
|
no-extend-native: [2]
|
||||||
no-extra-bind: [2]
|
no-extra-bind: [2]
|
||||||
no-extra-boolean-cast: [2]
|
no-extra-boolean-cast: [2]
|
||||||
|
@ -147,9 +194,6 @@ rules:
|
||||||
no-proto: [2]
|
no-proto: [2]
|
||||||
no-redeclare: [2]
|
no-redeclare: [2]
|
||||||
no-regex-spaces: [2]
|
no-regex-spaces: [2]
|
||||||
no-restricted-globals: [2, name, event]
|
|
||||||
# `name` and `event` (in Chrome) are built-in globals
|
|
||||||
# but we don't use these globals so it's most likely a mistake/typo
|
|
||||||
no-restricted-imports: [0]
|
no-restricted-imports: [0]
|
||||||
no-restricted-modules: [2, domain, freelist, smalloc, sys]
|
no-restricted-modules: [2, domain, freelist, smalloc, sys]
|
||||||
no-restricted-syntax: [2, WithStatement]
|
no-restricted-syntax: [2, WithStatement]
|
||||||
|
@ -170,6 +214,7 @@ rules:
|
||||||
no-trailing-spaces: [2]
|
no-trailing-spaces: [2]
|
||||||
no-undef-init: [2]
|
no-undef-init: [2]
|
||||||
no-undef: [2]
|
no-undef: [2]
|
||||||
|
no-undefined: [0]
|
||||||
no-underscore-dangle: [0]
|
no-underscore-dangle: [0]
|
||||||
no-unexpected-multiline: [2]
|
no-unexpected-multiline: [2]
|
||||||
no-unmodified-loop-condition: [0]
|
no-unmodified-loop-condition: [0]
|
||||||
|
@ -177,9 +222,9 @@ rules:
|
||||||
no-unreachable: [2]
|
no-unreachable: [2]
|
||||||
no-unsafe-finally: [2]
|
no-unsafe-finally: [2]
|
||||||
no-unsafe-negation: [2]
|
no-unsafe-negation: [2]
|
||||||
no-unused-expressions: [2]
|
no-unused-expressions: [1]
|
||||||
no-unused-labels: [0]
|
no-unused-labels: [0]
|
||||||
no-unused-vars: [2, {args: after-used}]
|
no-unused-vars: [1, {args: after-used, vars: local, argsIgnorePattern: ^_}]
|
||||||
no-use-before-define: [2, nofunc]
|
no-use-before-define: [2, nofunc]
|
||||||
no-useless-call: [2]
|
no-useless-call: [2]
|
||||||
no-useless-computed-key: [2]
|
no-useless-computed-key: [2]
|
||||||
|
@ -203,7 +248,7 @@ rules:
|
||||||
prefer-const: [1, {destructuring: all, ignoreReadBeforeAssign: true}]
|
prefer-const: [1, {destructuring: all, ignoreReadBeforeAssign: true}]
|
||||||
quote-props: [0]
|
quote-props: [0]
|
||||||
quotes: [1, single, avoid-escape]
|
quotes: [1, single, avoid-escape]
|
||||||
radix: [2, always]
|
radix: [2, as-needed]
|
||||||
require-jsdoc: [0]
|
require-jsdoc: [0]
|
||||||
require-yield: [2]
|
require-yield: [2]
|
||||||
semi-spacing: [2, {before: false, after: true}]
|
semi-spacing: [2, {before: false, after: true}]
|
||||||
|
@ -225,16 +270,3 @@ rules:
|
||||||
wrap-iife: [2, inside]
|
wrap-iife: [2, inside]
|
||||||
yield-star-spacing: [2, {before: true, after: false}]
|
yield-star-spacing: [2, {before: true, after: false}]
|
||||||
yoda: [2, never]
|
yoda: [2, never]
|
||||||
|
|
||||||
overrides:
|
|
||||||
- files: [tools/*]
|
|
||||||
env:
|
|
||||||
node: true
|
|
||||||
browser: false
|
|
||||||
webextensions: false
|
|
||||||
parserOptions:
|
|
||||||
ecmaVersion: 2017
|
|
||||||
|
|
||||||
- files: ["**/*worker*.js"]
|
|
||||||
env:
|
|
||||||
worker: true
|
|
2
.gitattributes
vendored
2
.gitattributes
vendored
|
@ -1,2 +0,0 @@
|
||||||
# Auto detect text files and perform LF normalization
|
|
||||||
* text=auto
|
|
11
.github/CONTRIBUTING.md
vendored
11
.github/CONTRIBUTING.md
vendored
|
@ -4,9 +4,7 @@
|
||||||
2. [How to report issues](#how-to-report-issues)
|
2. [How to report issues](#how-to-report-issues)
|
||||||
3. [Adding translations](#adding-translations)
|
3. [Adding translations](#adding-translations)
|
||||||
4. [Pull requests](#pull-requests)
|
4. [Pull requests](#pull-requests)
|
||||||
5. [Scripts](#scripts)
|
5. [Contact us](#contact-us)
|
||||||
6. [Updating locale files](#updating-locale-files-admin-only)
|
|
||||||
7. [Contact us](#contact-us)
|
|
||||||
|
|
||||||
## Getting involved
|
## Getting involved
|
||||||
|
|
||||||
|
@ -24,9 +22,6 @@ If not, then provide details describing which page the feature will effect, e.g.
|
||||||
## Adding translations
|
## Adding translations
|
||||||
|
|
||||||
You can help us translate the extension on [Transifex](https://www.transifex.com/github-7/Stylus).
|
You can help us translate the extension on [Transifex](https://www.transifex.com/github-7/Stylus).
|
||||||
Only the languages supported by the web store are allowed:
|
|
||||||
https://developer.chrome.com/docs/webstore/i18n/#localeTable
|
|
||||||
|
|
||||||
|
|
||||||
## Pull requests
|
## Pull requests
|
||||||
|
|
||||||
|
@ -37,10 +32,6 @@ https://developer.chrome.com/docs/webstore/i18n/#localeTable
|
||||||
* Make any changes within a branch of this repository (not the `master` branch).
|
* Make any changes within a branch of this repository (not the `master` branch).
|
||||||
* Submit a pull request and include a reference to the initial issue with the discussion.
|
* Submit a pull request and include a reference to the initial issue with the discussion.
|
||||||
|
|
||||||
## Build scripts
|
|
||||||
|
|
||||||
See [BUILD.md](../BUILD.md) for more information.
|
|
||||||
|
|
||||||
## Contact us
|
## Contact us
|
||||||
|
|
||||||
If you prefer a more informal method of getting in touch or starting a conversation, please [join us on Discord](https://discordapp.com/widget?id=379521691774353408) or leave a comment in the [discussion section](https://add0n.com/stylus.html#reviews). We will monitor any discussions there and join in, and it may be a more appropriate venue for opinions and less urgent suggestions.
|
If you prefer a more informal method of getting in touch or starting a conversation, please [join us on Discord](https://discordapp.com/widget?id=379521691774353408) or leave a comment in the [discussion section](https://add0n.com/stylus.html#reviews). We will monitor any discussions there and join in, and it may be a more appropriate venue for opinions and less urgent suggestions.
|
||||||
|
|
8
.github/ISSUE_TEMPLATE.md
vendored
Normal file
8
.github/ISSUE_TEMPLATE.md
vendored
Normal file
|
@ -0,0 +1,8 @@
|
||||||
|
* **Browser**:
|
||||||
|
* **Operating System**:
|
||||||
|
* **Screenshot**:
|
||||||
|
|
||||||
|
<!--
|
||||||
|
Please make sure you checked that your issue wasn't already addressed.
|
||||||
|
If the issue persists, please help us identifying the cause by providing the above details.
|
||||||
|
-->
|
48
.github/ISSUE_TEMPLATE/bug_report.md
vendored
48
.github/ISSUE_TEMPLATE/bug_report.md
vendored
|
@ -1,48 +0,0 @@
|
||||||
---
|
|
||||||
name: Bug Report
|
|
||||||
about: Create a report about a bug you experienced while using Stylus.
|
|
||||||
title: "[Bug] Replace with title"
|
|
||||||
assignees: ''
|
|
||||||
---
|
|
||||||
|
|
||||||
<!--
|
|
||||||
⚠⚠ Do not delete this issue template! ⚠⚠
|
|
||||||
Reported issues must use this template and have all the necessary information provided.
|
|
||||||
Incomplete reports are likely to be ignored and closed.
|
|
||||||
-->
|
|
||||||
|
|
||||||
<!--
|
|
||||||
Thank you for taking the time to create a report about a bug.
|
|
||||||
Ensure that there are no other existing reports for this bug.
|
|
||||||
Please check if the issue is resolved after a restart of the browser.
|
|
||||||
Additionally, you should check if the issue persists in a new browser profile.
|
|
||||||
Remember to fill out every section on this report and remove any that are not needed.
|
|
||||||
Finally, place a brief description in the title of this report.
|
|
||||||
-->
|
|
||||||
|
|
||||||
# Bug Report
|
|
||||||
|
|
||||||
### Bug Description
|
|
||||||
<!-- Provide a clear and concise description, which will allow us to properly troubleshoot this bug. -->
|
|
||||||
|
|
||||||
### Screenshots
|
|
||||||
<!-- If applicable, add screenshots to help explain this bug. -->
|
|
||||||
|
|
||||||
### CSS Code
|
|
||||||
<!--
|
|
||||||
If the bug is related to (user)CSS or the editor,
|
|
||||||
please post the code (with a service like pastebin) in this bug report.
|
|
||||||
-->
|
|
||||||
|
|
||||||
### System Information
|
|
||||||
<!--
|
|
||||||
Specify the browser name and version as well as the Stylus version you are using.
|
|
||||||
Please do an online search for help if you are not familiar with how to get this information.
|
|
||||||
-->
|
|
||||||
|
|
||||||
- OS: <!-- e.g. Windows, macOS, Linux -->
|
|
||||||
- Browser: <!-- e.g. Chrome 91, Firefox 90, Edge 91, Safari 14 -->
|
|
||||||
- Stylus Version: <!-- e.g. 1.5.21 -->
|
|
||||||
|
|
||||||
### Additional Context
|
|
||||||
<!-- Provide any additional information about this bug. -->
|
|
14
.github/workflows/ci.yml
vendored
14
.github/workflows/ci.yml
vendored
|
@ -1,14 +0,0 @@
|
||||||
name: ci
|
|
||||||
on: [push, pull_request]
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
test:
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@v2
|
|
||||||
- uses: actions/setup-node@v2
|
|
||||||
with:
|
|
||||||
node-version: '14'
|
|
||||||
- run: npm install
|
|
||||||
- run: npm test
|
|
8
.gitignore
vendored
8
.gitignore
vendored
|
@ -1,8 +1,8 @@
|
||||||
*.zip
|
|
||||||
.DS_Store
|
.DS_Store
|
||||||
.eslintcache
|
pull_locales_login.rb
|
||||||
.transifexrc
|
|
||||||
.vscode
|
.vscode
|
||||||
desktop.ini
|
|
||||||
node_modules/
|
node_modules/
|
||||||
|
package-lock.json
|
||||||
yarn.lock
|
yarn.lock
|
||||||
|
*.zip
|
||||||
|
.eslintcache
|
||||||
|
|
10
.tx/config
10
.tx/config
|
@ -1,10 +0,0 @@
|
||||||
[main]
|
|
||||||
host = https://www.transifex.com
|
|
||||||
|
|
||||||
[Stylus.messages]
|
|
||||||
file_filter = _locales/<lang>/messages.json
|
|
||||||
minimum_perc = 0
|
|
||||||
source_file = _locales/en/messages.json
|
|
||||||
source_lang = en_US
|
|
||||||
type = CHROME
|
|
||||||
|
|
70
BUILD.md
70
BUILD.md
|
@ -1,70 +0,0 @@
|
||||||
# Build this project
|
|
||||||
|
|
||||||
## Preparation
|
|
||||||
|
|
||||||
1. Install [Node.js](https://nodejs.org/en/).
|
|
||||||
2. Go to the project root, run `npm install`. This will install all required dependencies.
|
|
||||||
|
|
||||||
Extra preparations are needed if you want to pull locale files from Transifex:
|
|
||||||
|
|
||||||
1. Install Transifex client. Follow the instructions on [this page](https://docs.transifex.com/client/installing-the-client).
|
|
||||||
2. You need a `.transifexrc` file in the root folder. Contact another admin if you need one. It includes the API key to use Transifex's API.
|
|
||||||
|
|
||||||
## Generate the ZIP release
|
|
||||||
|
|
||||||
Use the following command to generate a ZIP file that can be submitted to AMO or CWS:
|
|
||||||
|
|
||||||
```
|
|
||||||
npm run zip
|
|
||||||
```
|
|
||||||
|
|
||||||
The zip file includes all the files from the repository **except**:
|
|
||||||
|
|
||||||
* All dot files (e.g. `.eslintrc` & `.gitignore`).
|
|
||||||
* `node_modules` folder.
|
|
||||||
* `tools` folder.
|
|
||||||
* `package.json` file.
|
|
||||||
* `package-lock.json` and/or `yarn.lock` file(s).
|
|
||||||
|
|
||||||
<!-- FIXME: is this statement still true?
|
|
||||||
* `vendor/codemirror/lib` files. This path is excluded because it contains a file modified for development purposes only. Instead, the CodeMirror files are copied directly from `node_modules/codemirror/lib`.
|
|
||||||
-->
|
|
||||||
|
|
||||||
## Tag a release/Bump the version
|
|
||||||
|
|
||||||
Use the `npm version (major | minor | patch)` command to tag a release.
|
|
||||||
|
|
||||||
There are some scripts that will run automatically before/after tagging a version. Includes:
|
|
||||||
|
|
||||||
1. Test.
|
|
||||||
2. Update version number in `manifest.json`.
|
|
||||||
3. Generate the ZIP file.
|
|
||||||
4. Push the tag to github.
|
|
||||||
|
|
||||||
## Translation
|
|
||||||
|
|
||||||
We host locale files (`message.json`) on Transifex. All the files exist in our GitHub repository, but if you need to update the locale files, you will need to install the [Transifex client](https://docs.transifex.com/client/installing-the-client)
|
|
||||||
|
|
||||||
To pull files from Transifex, run
|
|
||||||
|
|
||||||
```
|
|
||||||
npm run update-locales
|
|
||||||
```
|
|
||||||
|
|
||||||
To push files to Transifex:
|
|
||||||
|
|
||||||
```
|
|
||||||
npm run update-transifex
|
|
||||||
```
|
|
||||||
|
|
||||||
## 3rd-party libraries
|
|
||||||
|
|
||||||
3rd-party libraries are managed by `npm`. Since Stylus is built with vanilla JS, we only use libraries that can run in the browser.
|
|
||||||
|
|
||||||
We keep a copy of these libraries inside the `vendor` directory so users can side-load this repository without executing the build script. These files are downloaded from CDN or pulled from npm (`node_modules`).
|
|
||||||
|
|
||||||
To add/update a library to the latest version, run `npm install PACKAGE_NAME@latest`.
|
|
||||||
|
|
||||||
To remove a library, run `npm uninstall PACKAGE_NAME`.
|
|
||||||
|
|
||||||
After the (un)installation, specify files which should be copied in `tools/build-vendor.js` and run `npm run build-vendor` to rebuild the vendor folder.
|
|
28
README.md
28
README.md
|
@ -1,5 +1,3 @@
|
||||||
# Stylus
|
|
||||||
|
|
||||||
Stylus is a fork of Stylish for Chrome, also compatible with Firefox as a WebExtension
|
Stylus is a fork of Stylish for Chrome, also compatible with Firefox as a WebExtension
|
||||||
|
|
||||||
## Highlights
|
## Highlights
|
||||||
|
@ -21,15 +19,18 @@ Stylus is a fork of Stylish for Chrome, also compatible with Firefox as a WebExt
|
||||||
|
|
||||||
## Screenshots
|
## Screenshots
|
||||||
|
|
||||||
Manager | Editor | Popup search | Popup config | Manager config | Options
|

|
||||||
-|-|-|-|-|-
|

|
||||||
 |  |  |  |  | 
|

|
||||||
|

|
||||||
|

|
||||||
|

|
||||||
|
|
||||||
## Help
|
## Help
|
||||||
|
|
||||||
* [Stylus help and FAQ in our Wiki](https://github.com/openstyles/stylus/wiki)
|
- [Stylus help and FAQ in our Wiki](https://github.com/openstyles/stylus/wiki)
|
||||||
* [Discussion section](https://add0n.com/stylus.html#reviews) of our representation on add0n.com
|
- [Discussion section](https://add0n.com/stylus.html#reviews) of our representation on add0n.com
|
||||||
* Discord: [![Discord][chat-image]][chat-link]
|
- Discord: [![Discord][chat-image]][chat-link]
|
||||||
|
|
||||||
[chat-image]: https://img.shields.io/discord/379521691774353408.svg
|
[chat-image]: https://img.shields.io/discord/379521691774353408.svg
|
||||||
[chat-link]: https://discordapp.com/widget?id=379521691774353408
|
[chat-link]: https://discordapp.com/widget?id=379521691774353408
|
||||||
|
@ -44,16 +45,13 @@ See our [contributing](./.github/CONTRIBUTING.md) page for more details.
|
||||||
|
|
||||||
## License
|
## License
|
||||||
|
|
||||||
Inherited code from the original [Stylish](https://github.com/stylish-userstyles/stylish/):
|
Inherited code from the original [Stylish](https://github.com/stylish-userstyles/stylish/):<br>
|
||||||
|
|
||||||
Copyright © 2005-2014 [Jason Barnabe](jason.barnabe@gmail.com)
|
Copyright © 2005-2014 [Jason Barnabe](jason.barnabe@gmail.com)
|
||||||
|
|
||||||
Current Stylus:
|
Current Stylus:<br>
|
||||||
|
Copyright © 2017-2018 [Stylus Team](https://github.com/openstyles/stylus/graphs/contributors)
|
||||||
Copyright © 2017-2022 [Stylus Team](https://github.com/openstyles/stylus/graphs/contributors)
|
|
||||||
|
|
||||||
**[GNU GPLv3](./LICENSE)**
|
|
||||||
|
|
||||||
|
**[GNU GPLv3](./LICENSE)**<br>
|
||||||
This program is free software: you can redistribute it and/or modify
|
This program is free software: you can redistribute it and/or modify
|
||||||
it under the terms of the GNU General Public License as published by
|
it under the terms of the GNU General Public License as published by
|
||||||
the Free Software Foundation, either version 3 of the License, or
|
the Free Software Foundation, either version 3 of the License, or
|
||||||
|
|
|
@ -1,7 +1,4 @@
|
||||||
{
|
{
|
||||||
"InaccessibleFileHint": {
|
|
||||||
"message": "Stylus لا يستطيع الوصول الى بعض انواع الملفات ( ملفات pdf و json )"
|
|
||||||
},
|
|
||||||
"addStyleLabel": {
|
"addStyleLabel": {
|
||||||
"message": "كتابة نمط جديد"
|
"message": "كتابة نمط جديد"
|
||||||
},
|
},
|
||||||
|
@ -20,7 +17,7 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"appliesDisplayTruncatedSuffix": {
|
"appliesDisplayTruncatedSuffix": {
|
||||||
"message": "و المزيد"
|
"message": "والمزيد"
|
||||||
},
|
},
|
||||||
"appliesDomainOption": {
|
"appliesDomainOption": {
|
||||||
"message": "عناوين URL في النطاق"
|
"message": "عناوين URL في النطاق"
|
||||||
|
@ -58,12 +55,6 @@
|
||||||
"checkingForUpdate": {
|
"checkingForUpdate": {
|
||||||
"message": "جارٍ البحث..."
|
"message": "جارٍ البحث..."
|
||||||
},
|
},
|
||||||
"confirmDelete": {
|
|
||||||
"message": "حذف"
|
|
||||||
},
|
|
||||||
"confirmSave": {
|
|
||||||
"message": "حفظ"
|
|
||||||
},
|
|
||||||
"deleteStyleConfirm": {
|
"deleteStyleConfirm": {
|
||||||
"message": "هل تريد بالتأكيد حذف هذا النمط؟"
|
"message": "هل تريد بالتأكيد حذف هذا النمط؟"
|
||||||
},
|
},
|
||||||
|
@ -76,9 +67,6 @@
|
||||||
"disableStyleLabel": {
|
"disableStyleLabel": {
|
||||||
"message": "تعطيل"
|
"message": "تعطيل"
|
||||||
},
|
},
|
||||||
"editDeleteText": {
|
|
||||||
"message": "حذف"
|
|
||||||
},
|
|
||||||
"editStyleHeading": {
|
"editStyleHeading": {
|
||||||
"message": "تعديل النمط"
|
"message": "تعديل النمط"
|
||||||
},
|
},
|
||||||
|
@ -96,11 +84,8 @@
|
||||||
"enableStyleLabel": {
|
"enableStyleLabel": {
|
||||||
"message": "تمكين"
|
"message": "تمكين"
|
||||||
},
|
},
|
||||||
"genericAdd": {
|
"findStylesForSite": {
|
||||||
"message": "إضافة"
|
"message": "البحث عن المزيد من الأنماط لموقع الويب هذا"
|
||||||
},
|
|
||||||
"genericEnabledLabel": {
|
|
||||||
"message": "ممكّن"
|
|
||||||
},
|
},
|
||||||
"helpAlt": {
|
"helpAlt": {
|
||||||
"message": "مساعدة"
|
"message": "مساعدة"
|
||||||
|
@ -117,21 +102,18 @@
|
||||||
"openManage": {
|
"openManage": {
|
||||||
"message": "إدارة الأنماط المثبتة"
|
"message": "إدارة الأنماط المثبتة"
|
||||||
},
|
},
|
||||||
"optionsSyncUrl": {
|
|
||||||
"message": "عنوان URL"
|
|
||||||
},
|
|
||||||
"sectionAdd": {
|
"sectionAdd": {
|
||||||
"message": "إضافة قسم آخر"
|
"message": "إضافة قسم آخر"
|
||||||
},
|
},
|
||||||
"sectionCode": {
|
"sectionCode": {
|
||||||
"message": "الرمز"
|
"message": "الرمز"
|
||||||
},
|
},
|
||||||
|
"sectionHelp": {
|
||||||
|
"message": "تتيح لك الأقسام تحديد أجزاء مختلفة من الرمز لتطبيقها على مجموعات مختلفة من عناوين URL بالنمط نفسه. فعلى سبيل المثال، يمكن لنمط مفرد تغيير الصفحة الرئيسية لموقع ويب بطريقة، مع تغيير بقية أجزاء موقع الويب بطريقة أخرى."
|
||||||
|
},
|
||||||
"sectionRemove": {
|
"sectionRemove": {
|
||||||
"message": "إزالة القسم"
|
"message": "إزالة القسم"
|
||||||
},
|
},
|
||||||
"sections": {
|
|
||||||
"message": "الأقسام"
|
|
||||||
},
|
|
||||||
"styleCancelEditLabel": {
|
"styleCancelEditLabel": {
|
||||||
"message": "رجوع للإدارة"
|
"message": "رجوع للإدارة"
|
||||||
},
|
},
|
||||||
|
@ -155,6 +137,9 @@
|
||||||
"styleSaveLabel": {
|
"styleSaveLabel": {
|
||||||
"message": "حفظ"
|
"message": "حفظ"
|
||||||
},
|
},
|
||||||
|
"styleSectionsTitle": {
|
||||||
|
"message": "الأقسام"
|
||||||
|
},
|
||||||
"styleToMozillaFormatHelp": {
|
"styleToMozillaFormatHelp": {
|
||||||
"message": "يمكن استخدام تنسيق موزيلا للرمز باستخدام Stylus للمتصفح فايرفوكس ويمكن إرساله إلى userstyles.org."
|
"message": "يمكن استخدام تنسيق موزيلا للرمز باستخدام Stylus للمتصفح فايرفوكس ويمكن إرساله إلى userstyles.org."
|
||||||
},
|
},
|
||||||
|
|
|
@ -52,6 +52,9 @@
|
||||||
"backupButtons": {
|
"backupButtons": {
|
||||||
"message": "Резервни копия"
|
"message": "Резервни копия"
|
||||||
},
|
},
|
||||||
|
"backupMessage": {
|
||||||
|
"message": "Изберете файл или го влачете до страницата."
|
||||||
|
},
|
||||||
"bckpInstStyles": {
|
"bckpInstStyles": {
|
||||||
"message": "Изнасяне на стилове"
|
"message": "Изнасяне на стилове"
|
||||||
},
|
},
|
||||||
|
@ -112,9 +115,6 @@
|
||||||
"confirmOK": {
|
"confirmOK": {
|
||||||
"message": "Добре"
|
"message": "Добре"
|
||||||
},
|
},
|
||||||
"confirmSave": {
|
|
||||||
"message": "Запазване"
|
|
||||||
},
|
|
||||||
"confirmStop": {
|
"confirmStop": {
|
||||||
"message": "Спиране"
|
"message": "Спиране"
|
||||||
},
|
},
|
||||||
|
@ -165,21 +165,21 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"editorStylesButton": {
|
||||||
|
"message": "Стилове за редактора"
|
||||||
|
},
|
||||||
"enableStyleLabel": {
|
"enableStyleLabel": {
|
||||||
"message": "Включване"
|
"message": "Включване"
|
||||||
},
|
},
|
||||||
"exportLabel": {
|
"exportLabel": {
|
||||||
"message": "Изнасяне"
|
"message": "Изнасяне"
|
||||||
},
|
},
|
||||||
"genericAdd": {
|
"findStylesForSite": {
|
||||||
"message": "Добавяне"
|
"message": "Още стилове за този сайт"
|
||||||
},
|
},
|
||||||
"genericDisabledLabel": {
|
"genericDisabledLabel": {
|
||||||
"message": "Изключено"
|
"message": "Изключено"
|
||||||
},
|
},
|
||||||
"genericEnabledLabel": {
|
|
||||||
"message": "Включено"
|
|
||||||
},
|
|
||||||
"genericHistoryLabel": {
|
"genericHistoryLabel": {
|
||||||
"message": "Хронология"
|
"message": "Хронология"
|
||||||
},
|
},
|
||||||
|
@ -263,6 +263,9 @@
|
||||||
"manageFaviconsGray": {
|
"manageFaviconsGray": {
|
||||||
"message": "Сиви"
|
"message": "Сиви"
|
||||||
},
|
},
|
||||||
|
"manageFaviconsHelp": {
|
||||||
|
"message": "Разширението използва външна услуга https://www.google.com/s2/favicons"
|
||||||
|
},
|
||||||
"manageFilters": {
|
"manageFilters": {
|
||||||
"message": "Филтри"
|
"message": "Филтри"
|
||||||
},
|
},
|
||||||
|
@ -299,7 +302,10 @@
|
||||||
"openManage": {
|
"openManage": {
|
||||||
"message": "Управление"
|
"message": "Управление"
|
||||||
},
|
},
|
||||||
"openOptions": {
|
"openOptionsManage": {
|
||||||
|
"message": "Прозорец за настройките"
|
||||||
|
},
|
||||||
|
"openOptionsPopup": {
|
||||||
"message": "Настройки"
|
"message": "Настройки"
|
||||||
},
|
},
|
||||||
"openStylesManager": {
|
"openStylesManager": {
|
||||||
|
@ -368,9 +374,6 @@
|
||||||
"optionsSubheading": {
|
"optionsSubheading": {
|
||||||
"message": "Още настройки"
|
"message": "Още настройки"
|
||||||
},
|
},
|
||||||
"optionsSyncUrl": {
|
|
||||||
"message": "Адрес"
|
|
||||||
},
|
|
||||||
"optionsUpdateImportNote": {
|
"optionsUpdateImportNote": {
|
||||||
"message": "При внасянето на резервни копия от стари версии или от Стайлиш направете ръчна проверка за обновления, за да сте сигурни, че стиловете са актуални."
|
"message": "При внасянето на резервни копия от стари версии или от Стайлиш направете ръчна проверка за обновления, за да сте сигурни, че стиловете са актуални."
|
||||||
},
|
},
|
||||||
|
@ -398,18 +401,21 @@
|
||||||
"searchRegexp": {
|
"searchRegexp": {
|
||||||
"message": "Използвайте синтаксиса /re/ за търсене с регулярни изрази"
|
"message": "Използвайте синтаксиса /re/ за търсене с регулярни изрази"
|
||||||
},
|
},
|
||||||
|
"searchStyles": {
|
||||||
|
"message": "Търсене на съдържанието"
|
||||||
|
},
|
||||||
"sectionAdd": {
|
"sectionAdd": {
|
||||||
"message": "Добавяне на друг отдел"
|
"message": "Добавяне на друг отдел"
|
||||||
},
|
},
|
||||||
"sectionCode": {
|
"sectionCode": {
|
||||||
"message": "Код"
|
"message": "Код"
|
||||||
},
|
},
|
||||||
|
"sectionHelp": {
|
||||||
|
"message": "Отделите ви позволяват в един и същи стил да зададете различни части от кода да се прилагат за различни адреси. Например, един стил може да промени началната страница на даден сайт по един начин, а останалата част на сайта по друг начин."
|
||||||
|
},
|
||||||
"sectionRemove": {
|
"sectionRemove": {
|
||||||
"message": "Премахване на отдела"
|
"message": "Премахване на отдела"
|
||||||
},
|
},
|
||||||
"sections": {
|
|
||||||
"message": "Отдели"
|
|
||||||
},
|
|
||||||
"shortcuts": {
|
"shortcuts": {
|
||||||
"message": "Клавишни комбинации"
|
"message": "Клавишни комбинации"
|
||||||
},
|
},
|
||||||
|
@ -463,6 +469,9 @@
|
||||||
"styleRegexpProblemTooltip": {
|
"styleRegexpProblemTooltip": {
|
||||||
"message": "Брой на неприложените отдели поради неправилно използване на регулярни изрази"
|
"message": "Брой на неприложените отдели поради неправилно използване на регулярни изрази"
|
||||||
},
|
},
|
||||||
|
"styleRegexpTestButton": {
|
||||||
|
"message": "Тест на регулярния израз"
|
||||||
|
},
|
||||||
"styleRegexpTestFull": {
|
"styleRegexpTestFull": {
|
||||||
"message": "Съвпадащи подпрозорци"
|
"message": "Съвпадащи подпрозорци"
|
||||||
},
|
},
|
||||||
|
@ -481,6 +490,9 @@
|
||||||
"styleSaveLabel": {
|
"styleSaveLabel": {
|
||||||
"message": "Запазване"
|
"message": "Запазване"
|
||||||
},
|
},
|
||||||
|
"styleSectionsTitle": {
|
||||||
|
"message": "Отдели"
|
||||||
|
},
|
||||||
"styleToMozillaFormatHelp": {
|
"styleToMozillaFormatHelp": {
|
||||||
"message": "Форматът на Мозила може да се подаде в userstyles.org и да се използва със Стайлиш (Stylish)"
|
"message": "Форматът на Мозила може да се подаде в userstyles.org и да се използва със Стайлиш (Stylish)"
|
||||||
},
|
},
|
||||||
|
|
|
@ -176,6 +176,9 @@
|
||||||
"installUpdateFromLabel": {
|
"installUpdateFromLabel": {
|
||||||
"message": "Провери за обновления"
|
"message": "Провери за обновления"
|
||||||
},
|
},
|
||||||
|
"installUpdateUnavailable": {
|
||||||
|
"message": "За да разрешите проверка за обновления, пуснете файла върху лентата с табове, или в метаданните на стила укажете @updateURL."
|
||||||
|
},
|
||||||
"license": {
|
"license": {
|
||||||
"message": "Лиценз"
|
"message": "Лиценз"
|
||||||
},
|
},
|
||||||
|
@ -236,9 +239,15 @@
|
||||||
"liveReloadError": {
|
"liveReloadError": {
|
||||||
"message": "Получи се грешка докато наблюдавахме файла"
|
"message": "Получи се грешка докато наблюдавахме файла"
|
||||||
},
|
},
|
||||||
|
"liveReloadInstallHint": {
|
||||||
|
"message": "Преглед на живо е разрешен, така че инсталирания стил ще бъде обновен автоматично при външни промени докато двата прозореца с кода и оригинала са отворени."
|
||||||
|
},
|
||||||
"liveReloadLabel": {
|
"liveReloadLabel": {
|
||||||
"message": "Преглед на живо"
|
"message": "Преглед на живо"
|
||||||
},
|
},
|
||||||
|
"liveReloadUnavailable": {
|
||||||
|
"message": "За да разрешите презареждане в реално време, пуснете файла върху лентата с табове (областта, където са показани заглавията на табовете)."
|
||||||
|
},
|
||||||
"manageFilters": {
|
"manageFilters": {
|
||||||
"message": "Филтри"
|
"message": "Филтри"
|
||||||
},
|
},
|
||||||
|
|
|
@ -67,6 +67,9 @@
|
||||||
"backupButtons": {
|
"backupButtons": {
|
||||||
"message": "Zálohovat"
|
"message": "Zálohovat"
|
||||||
},
|
},
|
||||||
|
"backupMessage": {
|
||||||
|
"message": "Vyberte soubor nebo ho přetáhněte na tuto stránku."
|
||||||
|
},
|
||||||
"bckpInstStyles": {
|
"bckpInstStyles": {
|
||||||
"message": "Exportovat styly"
|
"message": "Exportovat styly"
|
||||||
},
|
},
|
||||||
|
@ -178,9 +181,6 @@
|
||||||
"confirmYes": {
|
"confirmYes": {
|
||||||
"message": "Ano"
|
"message": "Ano"
|
||||||
},
|
},
|
||||||
"connectingDropbox": {
|
|
||||||
"message": "Připojování k Dropboxu…"
|
|
||||||
},
|
|
||||||
"dateInstalled": {
|
"dateInstalled": {
|
||||||
"message": "Datum instalace"
|
"message": "Datum instalace"
|
||||||
},
|
},
|
||||||
|
@ -231,15 +231,15 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"editorStylesButton": {
|
||||||
|
"message": "Najít styly pro editor"
|
||||||
|
},
|
||||||
"enableStyleLabel": {
|
"enableStyleLabel": {
|
||||||
"message": "Povolit"
|
"message": "Povolit"
|
||||||
},
|
},
|
||||||
"exportLabel": {
|
"exportLabel": {
|
||||||
"message": "Exportovat"
|
"message": "Exportovat"
|
||||||
},
|
},
|
||||||
"exportSavedSuccess": {
|
|
||||||
"message": "Soubor úspěšně uložen"
|
|
||||||
},
|
|
||||||
"externalFeedback": {
|
"externalFeedback": {
|
||||||
"message": "Zpětná vazba"
|
"message": "Zpětná vazba"
|
||||||
},
|
},
|
||||||
|
@ -272,6 +272,15 @@
|
||||||
"findStyles": {
|
"findStyles": {
|
||||||
"message": "Najít styly"
|
"message": "Najít styly"
|
||||||
},
|
},
|
||||||
|
"findStylesForSite": {
|
||||||
|
"message": "Najít styly pro tento web"
|
||||||
|
},
|
||||||
|
"findStylesInline": {
|
||||||
|
"message": "Zobrazit zde"
|
||||||
|
},
|
||||||
|
"findStylesInlineTooltip": {
|
||||||
|
"message": "Zobrazit výsledky vyhledávání v tomto okně."
|
||||||
|
},
|
||||||
"genericAdd": {
|
"genericAdd": {
|
||||||
"message": "Přidat"
|
"message": "Přidat"
|
||||||
},
|
},
|
||||||
|
@ -308,9 +317,6 @@
|
||||||
"genericUnknown": {
|
"genericUnknown": {
|
||||||
"message": "Neznámé"
|
"message": "Neznámé"
|
||||||
},
|
},
|
||||||
"gettingStyles": {
|
|
||||||
"message": "Získávání všech stylů…"
|
|
||||||
},
|
|
||||||
"helpAlt": {
|
"helpAlt": {
|
||||||
"message": "Nápověda"
|
"message": "Nápověda"
|
||||||
},
|
},
|
||||||
|
@ -451,6 +457,9 @@
|
||||||
"liveReloadError": {
|
"liveReloadError": {
|
||||||
"message": "Při sledování souboru došlo k chybě"
|
"message": "Při sledování souboru došlo k chybě"
|
||||||
},
|
},
|
||||||
|
"liveReloadInstallHint": {
|
||||||
|
"message": "Živá aktualizace je povolena, takže nainstalovaný styl bude automaticky aktualizován při externích změnách, dokud budou tento list a list zdrojového souboru otevřeny."
|
||||||
|
},
|
||||||
"liveReloadLabel": {
|
"liveReloadLabel": {
|
||||||
"message": "Živá aktualizace"
|
"message": "Živá aktualizace"
|
||||||
},
|
},
|
||||||
|
@ -461,7 +470,7 @@
|
||||||
"message": "Zešednutí"
|
"message": "Zešednutí"
|
||||||
},
|
},
|
||||||
"manageFaviconsHelp": {
|
"manageFaviconsHelp": {
|
||||||
"message": "Stylus používá externí službu https://icons.duckduckgo.com"
|
"message": "Stylus používá externí službu https://www.google.com/s2/favicons"
|
||||||
},
|
},
|
||||||
"manageFilters": {
|
"manageFilters": {
|
||||||
"message": "Filtry"
|
"message": "Filtry"
|
||||||
|
@ -505,55 +514,16 @@
|
||||||
"menuShowBadge": {
|
"menuShowBadge": {
|
||||||
"message": "Zobrazit počet aktivních stylů"
|
"message": "Zobrazit počet aktivních stylů"
|
||||||
},
|
},
|
||||||
"meta_invalidCheckboxDefault": {
|
|
||||||
"message": "Neplatný @var checkbox: hodnota musí být 0 nebo 1"
|
|
||||||
},
|
|
||||||
"meta_invalidNumber": {
|
|
||||||
"message": "Očekáváno číslo"
|
|
||||||
},
|
|
||||||
"meta_invalidString": {
|
|
||||||
"message": "Očekáván řetězec v uvozovkách"
|
|
||||||
},
|
|
||||||
"meta_invalidWord": {
|
|
||||||
"message": "Očekáván text"
|
|
||||||
},
|
|
||||||
"meta_missingChar": {
|
|
||||||
"message": "Očekávané znaky: $chars$",
|
|
||||||
"placeholders": {
|
|
||||||
"chars": {
|
|
||||||
"content": "$1"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"meta_unknownMeta": {
|
|
||||||
"message": "Neznámá metadata: $key$",
|
|
||||||
"placeholders": {
|
|
||||||
"key": {
|
|
||||||
"content": "$1"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"meta_unknownVarType": {
|
|
||||||
"message": "Neznámý typ @$varkey$: $vartype$",
|
|
||||||
"placeholders": {
|
|
||||||
"varkey": {
|
|
||||||
"content": "$1"
|
|
||||||
},
|
|
||||||
"vartype": {
|
|
||||||
"content": "$2"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"noFileToImport": {
|
|
||||||
"message": "Před importem stylů byste je měli nejprve exportovat."
|
|
||||||
},
|
|
||||||
"noStylesForSite": {
|
"noStylesForSite": {
|
||||||
"message": "Pro tento web není nainstalován žádný styl."
|
"message": "Pro tento web není nainstalován žádný styl."
|
||||||
},
|
},
|
||||||
"openManage": {
|
"openManage": {
|
||||||
"message": "Spravovat"
|
"message": "Spravovat"
|
||||||
},
|
},
|
||||||
"openOptions": {
|
"openOptionsManage": {
|
||||||
|
"message": "Možnosti rozhraní"
|
||||||
|
},
|
||||||
|
"openOptionsPopup": {
|
||||||
"message": "Možnosti"
|
"message": "Možnosti"
|
||||||
},
|
},
|
||||||
"openStylesManager": {
|
"openStylesManager": {
|
||||||
|
@ -625,18 +595,12 @@
|
||||||
"optionsSubheading": {
|
"optionsSubheading": {
|
||||||
"message": "Další možnosti"
|
"message": "Další možnosti"
|
||||||
},
|
},
|
||||||
"optionsSyncUrl": {
|
|
||||||
"message": "URL adresa"
|
|
||||||
},
|
|
||||||
"optionsUpdateImportNote": {
|
"optionsUpdateImportNote": {
|
||||||
"message": "Importujete-li zálohy stylů ze starší verze nebo z rozšíření Stylish, proveďte jednorázovou ruční kontrolu aktualizací ve správci stylů, aby byly všechny styly aktuální."
|
"message": "Importujete-li zálohy stylů ze starší verze nebo z rozšíření Stylish, proveďte jednorázovou ruční kontrolu aktualizací ve správci stylů, aby byly všechny styly aktuální."
|
||||||
},
|
},
|
||||||
"optionsUpdateInterval": {
|
"optionsUpdateInterval": {
|
||||||
"message": "Interval automatické aktualizace stylů v hodinách (0 = vypnuto)"
|
"message": "Interval automatické aktualizace stylů v hodinách (0 = vypnuto)"
|
||||||
},
|
},
|
||||||
"overwriteFileExport": {
|
|
||||||
"message": "Chcete přepsat stávající soubor?"
|
|
||||||
},
|
|
||||||
"paginationCurrent": {
|
"paginationCurrent": {
|
||||||
"message": "Aktuální stránka"
|
"message": "Aktuální stránka"
|
||||||
},
|
},
|
||||||
|
@ -685,9 +649,6 @@
|
||||||
"previewTooltip": {
|
"previewTooltip": {
|
||||||
"message": "Dočasně použije změny bez uložení.\nUložte styl pro trvalé zachování změn."
|
"message": "Dočasně použije změny bez uložení.\nUložte styl pro trvalé zachování změn."
|
||||||
},
|
},
|
||||||
"readingStyles": {
|
|
||||||
"message": "Čtení stylů…"
|
|
||||||
},
|
|
||||||
"replace": {
|
"replace": {
|
||||||
"message": "Nahradit"
|
"message": "Nahradit"
|
||||||
},
|
},
|
||||||
|
@ -730,21 +691,24 @@
|
||||||
"searchResultWeeklyCount": {
|
"searchResultWeeklyCount": {
|
||||||
"message": "Týdenní počet instalací"
|
"message": "Týdenní počet instalací"
|
||||||
},
|
},
|
||||||
|
"searchStyles": {
|
||||||
|
"message": "Prohledat obsah"
|
||||||
|
},
|
||||||
"sectionAdd": {
|
"sectionAdd": {
|
||||||
"message": "Přidat další sekci"
|
"message": "Přidat další sekci"
|
||||||
},
|
},
|
||||||
"sectionCode": {
|
"sectionCode": {
|
||||||
"message": "Kód"
|
"message": "Kód"
|
||||||
},
|
},
|
||||||
|
"sectionHelp": {
|
||||||
|
"message": "Pomocí sekcí můžete v jednom stylu nadefinovat více částí kódu pro různé skupiny URL adres. Jeden styl pak může např. změnit hlavní stránku webu jinak, než jeho zbytek."
|
||||||
|
},
|
||||||
"sectionRemove": {
|
"sectionRemove": {
|
||||||
"message": "Odstranit sekci"
|
"message": "Odstranit sekci"
|
||||||
},
|
},
|
||||||
"sectionRestore": {
|
"sectionRestore": {
|
||||||
"message": "Obnovit odstraněnou sekci"
|
"message": "Obnovit odstraněnou sekci"
|
||||||
},
|
},
|
||||||
"sections": {
|
|
||||||
"message": "Sekce"
|
|
||||||
},
|
|
||||||
"shortcuts": {
|
"shortcuts": {
|
||||||
"message": "Zkratky"
|
"message": "Zkratky"
|
||||||
},
|
},
|
||||||
|
@ -778,9 +742,6 @@
|
||||||
"styleBeautifyIndentConditional": {
|
"styleBeautifyIndentConditional": {
|
||||||
"message": "Odsadit @media, @supports"
|
"message": "Odsadit @media, @supports"
|
||||||
},
|
},
|
||||||
"styleBeautifyPreserveNewlines": {
|
|
||||||
"message": "Zachovat nové řádky"
|
|
||||||
},
|
|
||||||
"styleCancelEditLabel": {
|
"styleCancelEditLabel": {
|
||||||
"message": "Zpět ke správě"
|
"message": "Zpět ke správě"
|
||||||
},
|
},
|
||||||
|
@ -826,6 +787,36 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"styleMetaErrorCheckbox": {
|
||||||
|
"message": "Neplatný @var checkbox: hodnota musí být 0 nebo 1"
|
||||||
|
},
|
||||||
|
"styleMetaErrorColor": {
|
||||||
|
"message": "$color$je neplatná barva",
|
||||||
|
"placeholders": {
|
||||||
|
"color": {
|
||||||
|
"content": "$1"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"styleMetaErrorPreprocessor": {
|
||||||
|
"message": "Nepodporovaný @preprocessor: $preprocessor$",
|
||||||
|
"placeholders": {
|
||||||
|
"preprocessor": {
|
||||||
|
"content": "$1"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"styleMetaErrorSelectValueMismatch": {
|
||||||
|
"message": "Neplatný @select: hodnota v seznamu neexistuje"
|
||||||
|
},
|
||||||
|
"styleMissingMeta": {
|
||||||
|
"message": "Chybějící metadata @$key$",
|
||||||
|
"placeholders": {
|
||||||
|
"key": {
|
||||||
|
"content": "$1"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"styleMissingName": {
|
"styleMissingName": {
|
||||||
"message": "Zadejte název"
|
"message": "Zadejte název"
|
||||||
},
|
},
|
||||||
|
@ -844,6 +835,9 @@
|
||||||
"styleRegexpProblemTooltip": {
|
"styleRegexpProblemTooltip": {
|
||||||
"message": "Počet sekcí nepoužitých kvůli nesprávnému použití „regexp()“"
|
"message": "Počet sekcí nepoužitých kvůli nesprávnému použití „regexp()“"
|
||||||
},
|
},
|
||||||
|
"styleRegexpTestButton": {
|
||||||
|
"message": "Otestovat RegExp"
|
||||||
|
},
|
||||||
"styleRegexpTestFull": {
|
"styleRegexpTestFull": {
|
||||||
"message": "Odpovídající listy"
|
"message": "Odpovídající listy"
|
||||||
},
|
},
|
||||||
|
@ -865,6 +859,9 @@
|
||||||
"styleSaveLabel": {
|
"styleSaveLabel": {
|
||||||
"message": "Uložit"
|
"message": "Uložit"
|
||||||
},
|
},
|
||||||
|
"styleSectionsTitle": {
|
||||||
|
"message": "Sekce"
|
||||||
|
},
|
||||||
"styleToMozillaFormatHelp": {
|
"styleToMozillaFormatHelp": {
|
||||||
"message": "Kód v Mozilla formátu může být odeslán na userstyles.org a použit v aplikaci Stylish pro Firefox"
|
"message": "Kód v Mozilla formátu může být odeslán na userstyles.org a použit v aplikaci Stylish pro Firefox"
|
||||||
},
|
},
|
||||||
|
@ -906,18 +903,15 @@
|
||||||
"unreachableAMOHint": {
|
"unreachableAMOHint": {
|
||||||
"message": "Povolení přístupu: přejděte na <about:config>, pravým myšidlem zvolte „Nový“, dále „Boolean“, zadejte kód <privacy.resistFingerprinting.block_mozAddonManager> a potvrďte, <true>, OK, načtěte stránku <addons.mozilla.org>."
|
"message": "Povolení přístupu: přejděte na <about:config>, pravým myšidlem zvolte „Nový“, dále „Boolean“, zadejte kód <privacy.resistFingerprinting.block_mozAddonManager> a potvrďte, <true>, OK, načtěte stránku <addons.mozilla.org>."
|
||||||
},
|
},
|
||||||
|
"unreachableAMOHintOldFF": {
|
||||||
|
"message": "Pouze Firefox 59 a novější může být nakonfigurován tak, aby rozšíření typu WebExtensions mohla přidávat styly na stránky chráněné CSP (Content Security Policy) – jako je tato."
|
||||||
|
},
|
||||||
"unreachableContentScript": {
|
"unreachableContentScript": {
|
||||||
"message": "Se stránkou nelze komunikovat. Zkuste znovu načíst list."
|
"message": "Se stránkou nelze komunikovat. Zkuste znovu načíst list."
|
||||||
},
|
},
|
||||||
"unreachableFileHint": {
|
"unreachableFileHint": {
|
||||||
"message": "Stylus může přistupovat k file:// URL pouze při povolení odpovídající možnosti pro rozšíření Stylus ve správci chrome://extensions."
|
"message": "Stylus může přistupovat k file:// URL pouze při povolení odpovídající možnosti pro rozšíření Stylus ve správci chrome://extensions."
|
||||||
},
|
},
|
||||||
"unreachableMozSiteHintOldFF": {
|
|
||||||
"message": "Pouze Firefox 59 a novější může být nakonfigurován tak, aby rozšíření typu WebExtensions mohla přidávat styly na stránky chráněné CSP (Content Security Policy) – jako je tato."
|
|
||||||
},
|
|
||||||
"unzipStyles": {
|
|
||||||
"message": "Rozbalování stylů…"
|
|
||||||
},
|
|
||||||
"updateAllCheckSucceededNoUpdate": {
|
"updateAllCheckSucceededNoUpdate": {
|
||||||
"message": "Aktualizace nenalezeny."
|
"message": "Aktualizace nenalezeny."
|
||||||
},
|
},
|
||||||
|
@ -959,12 +953,6 @@
|
||||||
"updatesCurrentlyInstalled": {
|
"updatesCurrentlyInstalled": {
|
||||||
"message": "Instalované aktualizace:"
|
"message": "Instalované aktualizace:"
|
||||||
},
|
},
|
||||||
"uploadingFile": {
|
|
||||||
"message": "Nahrávání souboru…"
|
|
||||||
},
|
|
||||||
"usercssAvoidOverwriting": {
|
|
||||||
"message": "Prosím, změňte hodnotu @name nebo @namespace ať nedojde k přepsání existujícího stylu."
|
|
||||||
},
|
|
||||||
"usercssConfigIncomplete": {
|
"usercssConfigIncomplete": {
|
||||||
"message": "Styl byl aktualizován nebo smazán po zobrazení dialogu konfigurace. Tyto proměnné nebyly uloženy aby se předešlo poškození metadat stylu."
|
"message": "Styl byl aktualizován nebo smazán po zobrazení dialogu konfigurace. Tyto proměnné nebyly uloženy aby se předešlo poškození metadat stylu."
|
||||||
},
|
},
|
||||||
|
@ -974,6 +962,9 @@
|
||||||
"usercssReplaceTemplateConfirmation": {
|
"usercssReplaceTemplateConfirmation": {
|
||||||
"message": "Nahradit výchozí šablonu pro nové Usercss styly aktuálním kódem?"
|
"message": "Nahradit výchozí šablonu pro nové Usercss styly aktuálním kódem?"
|
||||||
},
|
},
|
||||||
|
"usercssReplaceTemplateName": {
|
||||||
|
"message": "Prázdné @name nahrazuje výchozí šablonu"
|
||||||
|
},
|
||||||
"usercssReplaceTemplateSectionBody": {
|
"usercssReplaceTemplateSectionBody": {
|
||||||
"message": "Sem vložte kód…"
|
"message": "Sem vložte kód…"
|
||||||
},
|
},
|
||||||
|
@ -985,8 +976,5 @@
|
||||||
},
|
},
|
||||||
"writeStyleForURL": {
|
"writeStyleForURL": {
|
||||||
"message": "tuto URL"
|
"message": "tuto URL"
|
||||||
},
|
|
||||||
"zipStyles": {
|
|
||||||
"message": "Balení stylů…"
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,100 +0,0 @@
|
||||||
{
|
|
||||||
"addStyleLabel": {
|
|
||||||
"message": "Skriv ny stil"
|
|
||||||
},
|
|
||||||
"addStyleTitle": {
|
|
||||||
"message": "Tilføj stil"
|
|
||||||
},
|
|
||||||
"alphaChannel": {
|
|
||||||
"message": "Gennemsigtighed"
|
|
||||||
},
|
|
||||||
"appliesAdd": {
|
|
||||||
"message": "Tilføj"
|
|
||||||
},
|
|
||||||
"appliesDisplay": {
|
|
||||||
"message": "Anvendes på: $applies$",
|
|
||||||
"placeholders": {
|
|
||||||
"applies": {
|
|
||||||
"content": "$1"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"appliesDisplayTruncatedSuffix": {
|
|
||||||
"message": "og mere"
|
|
||||||
},
|
|
||||||
"appliesDomainOption": {
|
|
||||||
"message": "URL'er på domænet"
|
|
||||||
},
|
|
||||||
"appliesHelp": {
|
|
||||||
"message": "Brug 'Anvendt på'-styring til at begrænse hvilke URL'er koden i denne sektion anvendes på."
|
|
||||||
},
|
|
||||||
"appliesLabel": {
|
|
||||||
"message": "Anvendes på"
|
|
||||||
},
|
|
||||||
"appliesLineWidgetLabel": {
|
|
||||||
"message": "Vis 'Anvendes på'-info"
|
|
||||||
},
|
|
||||||
"appliesRegexpOption": {
|
|
||||||
"message": "URL'er der matcher regexp'en"
|
|
||||||
},
|
|
||||||
"appliesRemove": {
|
|
||||||
"message": "Fjern"
|
|
||||||
},
|
|
||||||
"appliesRemoveError": {
|
|
||||||
"message": "Kan ikke fjerne sidste 'Anvendes på'-optegnelse"
|
|
||||||
},
|
|
||||||
"appliesSpecify": {
|
|
||||||
"message": "Specificér"
|
|
||||||
},
|
|
||||||
"appliesToEverything": {
|
|
||||||
"message": "Alt"
|
|
||||||
},
|
|
||||||
"appliesUrlPrefixOption": {
|
|
||||||
"message": "URL'er der starter med"
|
|
||||||
},
|
|
||||||
"applyAllUpdates": {
|
|
||||||
"message": "Anvend alle opdateringer"
|
|
||||||
},
|
|
||||||
"author": {
|
|
||||||
"message": "Forfatter"
|
|
||||||
},
|
|
||||||
"bckpInstStyles": {
|
|
||||||
"message": "Eksportér stil"
|
|
||||||
},
|
|
||||||
"checkAllUpdates": {
|
|
||||||
"message": "Tjek alle stiler for opdateringer"
|
|
||||||
},
|
|
||||||
"checkAllUpdatesForce": {
|
|
||||||
"message": "Tjek igen, jeg redigerede ikke nogen stil!"
|
|
||||||
},
|
|
||||||
"checkForUpdate": {
|
|
||||||
"message": "Tjek efter opdatering"
|
|
||||||
},
|
|
||||||
"checkingForUpdate": {
|
|
||||||
"message": "Tjekker..."
|
|
||||||
},
|
|
||||||
"clickToUninstall": {
|
|
||||||
"message": "Klik for at afinstallere"
|
|
||||||
},
|
|
||||||
"cm_autoCloseBrackets": {
|
|
||||||
"message": "Luk automatisk paranteser og citationstegn"
|
|
||||||
},
|
|
||||||
"cm_autoCloseBracketsTooltip": {
|
|
||||||
"message": "Tilføj automatisk et lukket par når man åbner en af ()[]{}''\"\""
|
|
||||||
},
|
|
||||||
"cm_autocompleteOnTyping": {
|
|
||||||
"message": "Autoudfyld på indtastning"
|
|
||||||
},
|
|
||||||
"cm_colorpicker": {
|
|
||||||
"message": "Farvevælgere for CSS-farver"
|
|
||||||
},
|
|
||||||
"cm_indentWithTabs": {
|
|
||||||
"message": "Brug tabs med smart indrykning"
|
|
||||||
},
|
|
||||||
"cm_keyMap": {
|
|
||||||
"message": "Tastegenveje"
|
|
||||||
},
|
|
||||||
"genericAdd": {
|
|
||||||
"message": "Tilføj"
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,7 +1,4 @@
|
||||||
{
|
{
|
||||||
"InaccessibleFileHint": {
|
|
||||||
"message": "Stylus kann auf einige Dateitypen nicht zugreifen (z.B. pdf oder json)"
|
|
||||||
},
|
|
||||||
"addStyleLabel": {
|
"addStyleLabel": {
|
||||||
"message": "Neuen Style erstellen"
|
"message": "Neuen Style erstellen"
|
||||||
},
|
},
|
||||||
|
@ -68,7 +65,7 @@
|
||||||
"message": "Datensicherung"
|
"message": "Datensicherung"
|
||||||
},
|
},
|
||||||
"backupMessage": {
|
"backupMessage": {
|
||||||
"message": "Um die Backupdatei zu importieren, ziehe sie in diese Seite oder klicke auf die Import-Schaltfläche.\n\nZum Exportieren einer mit Stylus 1.5.18 (und älter) kompatiblen Backupdatei, rechtsklicke oder Shift-klicke auf die Export-Schaltfläche."
|
"message": "Wähle eine Datei aus oder ziehe die Datei auf diese Seite. (Drag and Drop)"
|
||||||
},
|
},
|
||||||
"bckpInstStyles": {
|
"bckpInstStyles": {
|
||||||
"message": "Styles exportieren"
|
"message": "Styles exportieren"
|
||||||
|
@ -133,9 +130,6 @@
|
||||||
"cm_tabSize": {
|
"cm_tabSize": {
|
||||||
"message": "Tab-Größe"
|
"message": "Tab-Größe"
|
||||||
},
|
},
|
||||||
"colorpickerPaletteHint": {
|
|
||||||
"message": "Rechtsklicke auf ein Farbmuster, um durch die entsprechenden Codezeilen zu springen"
|
|
||||||
},
|
|
||||||
"colorpickerSwitchFormatTooltip": {
|
"colorpickerSwitchFormatTooltip": {
|
||||||
"message": "Formate wechseln: HEX -> RGB ->HSL.\nShift-Klick, um Richtung umzukehren.\nKürzel: Bild auf- und Bild ab-Tasten."
|
"message": "Formate wechseln: HEX -> RGB ->HSL.\nShift-Klick, um Richtung umzukehren.\nKürzel: Bild auf- und Bild ab-Tasten."
|
||||||
},
|
},
|
||||||
|
@ -181,24 +175,6 @@
|
||||||
"confirmYes": {
|
"confirmYes": {
|
||||||
"message": "Ja"
|
"message": "Ja"
|
||||||
},
|
},
|
||||||
"connectingDropbox": {
|
|
||||||
"message": "Verbinde mit Dropbox..."
|
|
||||||
},
|
|
||||||
"connectingDropboxNotAllowed": {
|
|
||||||
"message": "Verbindung zur Dropbox ist nur in Apps verfügbar, die direkt vom Webstore installiert wurden"
|
|
||||||
},
|
|
||||||
"copied": {
|
|
||||||
"message": "In die Zwischenablage kopiert"
|
|
||||||
},
|
|
||||||
"copy": {
|
|
||||||
"message": "In die Zwischenablage kopieren"
|
|
||||||
},
|
|
||||||
"customNameHint": {
|
|
||||||
"message": "Neuen Anzeigenamen des Styles hier eingeben, um trotzdem weiterhin Updates zu erhalten"
|
|
||||||
},
|
|
||||||
"customNameResetHint": {
|
|
||||||
"message": "Eigenen Anzeigenamen nicht mehr benutzen, wechsle wieder zum Standardnamen des Styles"
|
|
||||||
},
|
|
||||||
"dateInstalled": {
|
"dateInstalled": {
|
||||||
"message": "Installationsdatum"
|
"message": "Installationsdatum"
|
||||||
},
|
},
|
||||||
|
@ -223,29 +199,12 @@
|
||||||
"disableAllStyles": {
|
"disableAllStyles": {
|
||||||
"message": "Alle Styles deaktivieren"
|
"message": "Alle Styles deaktivieren"
|
||||||
},
|
},
|
||||||
"disableAllStylesOff": {
|
|
||||||
"message": "Styles sind ausgeschaltet"
|
|
||||||
},
|
|
||||||
"disableStyleLabel": {
|
"disableStyleLabel": {
|
||||||
"message": "Deaktivieren"
|
"message": "Deaktivieren"
|
||||||
},
|
},
|
||||||
"draftAction": {
|
|
||||||
"message": "Wähle \"Ja\", um diesen Entwurf zu laden oder \"Nein\", um ihn zu verwerfen."
|
|
||||||
},
|
|
||||||
"draftTitle": {
|
|
||||||
"message": "Wiederherstellung ungespeicherter Entwürfe, erstellt vor $date$",
|
|
||||||
"placeholders": {
|
|
||||||
"date": {
|
|
||||||
"content": "$1"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"dragDropMessage": {
|
"dragDropMessage": {
|
||||||
"message": "Ziehe die Backup Datei zum importieren an irgendeinen Ort auf dieser Seite."
|
"message": "Ziehe die Backup Datei zum importieren an irgendeinen Ort auf dieser Seite."
|
||||||
},
|
},
|
||||||
"dragDropUsercssTabstrip": {
|
|
||||||
"message": "Ziehe die Datei auf die Tableiste, um sie zu installieren."
|
|
||||||
},
|
|
||||||
"editDeleteText": {
|
"editDeleteText": {
|
||||||
"message": "Löschen"
|
"message": "Löschen"
|
||||||
},
|
},
|
||||||
|
@ -266,27 +225,15 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"editorSettings": {
|
"editorStylesButton": {
|
||||||
"message": "Editor Einstellungen"
|
"message": "Editor Styles finden"
|
||||||
},
|
},
|
||||||
"enableStyleLabel": {
|
"enableStyleLabel": {
|
||||||
"message": "Aktivieren"
|
"message": "Aktivieren"
|
||||||
},
|
},
|
||||||
"excludeStyleByDomainLabel": {
|
|
||||||
"message": "Aktuelle Domain ausschließen"
|
|
||||||
},
|
|
||||||
"excludeStyleByUrlLabel": {
|
|
||||||
"message": "Aktuelle URL ausschließen"
|
|
||||||
},
|
|
||||||
"exportCompatible": {
|
|
||||||
"message": "Exportieren (Kompatibilitätsmodus)"
|
|
||||||
},
|
|
||||||
"exportLabel": {
|
"exportLabel": {
|
||||||
"message": "Exportieren"
|
"message": "Exportieren"
|
||||||
},
|
},
|
||||||
"exportSavedSuccess": {
|
|
||||||
"message": "Datei erfolgreich gespeichert"
|
|
||||||
},
|
|
||||||
"externalLink": {
|
"externalLink": {
|
||||||
"message": "Externer Link"
|
"message": "Externer Link"
|
||||||
},
|
},
|
||||||
|
@ -313,15 +260,21 @@
|
||||||
"findStyles": {
|
"findStyles": {
|
||||||
"message": "Styles finden"
|
"message": "Styles finden"
|
||||||
},
|
},
|
||||||
|
"findStylesForSite": {
|
||||||
|
"message": "Weitere Styles für diese Seite finden"
|
||||||
|
},
|
||||||
|
"findStylesInline": {
|
||||||
|
"message": "Ergebnisse hier anzeigen"
|
||||||
|
},
|
||||||
|
"findStylesInlineTooltip": {
|
||||||
|
"message": "Suchergebnisse in diesem Fenster anzeigen."
|
||||||
|
},
|
||||||
"genericAdd": {
|
"genericAdd": {
|
||||||
"message": "Hinzufügen"
|
"message": "Hinzufügen"
|
||||||
},
|
},
|
||||||
"genericClone": {
|
"genericClone": {
|
||||||
"message": "Kopieren"
|
"message": "Kopieren"
|
||||||
},
|
},
|
||||||
"genericDescription": {
|
|
||||||
"message": "Beschreibung"
|
|
||||||
},
|
|
||||||
"genericDisabledLabel": {
|
"genericDisabledLabel": {
|
||||||
"message": "Deaktiviert"
|
"message": "Deaktiviert"
|
||||||
},
|
},
|
||||||
|
@ -346,21 +299,12 @@
|
||||||
"genericSavedMessage": {
|
"genericSavedMessage": {
|
||||||
"message": "Gespeichert"
|
"message": "Gespeichert"
|
||||||
},
|
},
|
||||||
"genericSize": {
|
|
||||||
"message": "Größe"
|
|
||||||
},
|
|
||||||
"genericTitle": {
|
"genericTitle": {
|
||||||
"message": "Name"
|
"message": "Name"
|
||||||
},
|
},
|
||||||
"genericUnknown": {
|
"genericUnknown": {
|
||||||
"message": "Unbekannt"
|
"message": "Unbekannt"
|
||||||
},
|
},
|
||||||
"gettingStyles": {
|
|
||||||
"message": "Empfange alle Styles..."
|
|
||||||
},
|
|
||||||
"headerResizerHint": {
|
|
||||||
"message": "Halte Shift gedrückt, um nur diese Art der Benutzeroberfläche (z.B. Editor, Manager, Installer) zu verändern"
|
|
||||||
},
|
|
||||||
"helpAlt": {
|
"helpAlt": {
|
||||||
"message": "Hilfe"
|
"message": "Hilfe"
|
||||||
},
|
},
|
||||||
|
@ -370,9 +314,6 @@
|
||||||
"helpKeyMapHotkey": {
|
"helpKeyMapHotkey": {
|
||||||
"message": "Hotkey drücken"
|
"message": "Hotkey drücken"
|
||||||
},
|
},
|
||||||
"hostDisabled": {
|
|
||||||
"message": "Dieser Host wurde deaktiviert, weil die aktuell genutzte Version deines Browsers einen Fehler enthält."
|
|
||||||
},
|
|
||||||
"importAppendLabel": {
|
"importAppendLabel": {
|
||||||
"message": "Zum Style anfügen"
|
"message": "Zum Style anfügen"
|
||||||
},
|
},
|
||||||
|
@ -382,12 +323,6 @@
|
||||||
"importLabel": {
|
"importLabel": {
|
||||||
"message": "Importieren"
|
"message": "Importieren"
|
||||||
},
|
},
|
||||||
"importPreprocessor": {
|
|
||||||
"message": "Ein Style mit einem <code>@preprocessor</code>funktioniert nicht im klassischen Modus. Du kannst den Editor auf UserCSS umstellen: 1) Öffne den Style-Manager, 2) Aktiviere das Kästchen \"Als UserCSS\", 3) Klicke auf \"Neuen Style erstellen\"\n\nTrotzdem jetzt importieren?"
|
|
||||||
},
|
|
||||||
"importPreprocessorTitle": {
|
|
||||||
"message": "Mögliches Problem wegen @preprocessor"
|
|
||||||
},
|
|
||||||
"importReplaceLabel": {
|
"importReplaceLabel": {
|
||||||
"message": "Style überschreiben"
|
"message": "Style überschreiben"
|
||||||
},
|
},
|
||||||
|
@ -428,7 +363,7 @@
|
||||||
"message": "Style installieren"
|
"message": "Style installieren"
|
||||||
},
|
},
|
||||||
"installButtonInstalled": {
|
"installButtonInstalled": {
|
||||||
"message": "Style erfolgreich installiert"
|
"message": "Style installiert"
|
||||||
},
|
},
|
||||||
"installButtonReinstall": {
|
"installButtonReinstall": {
|
||||||
"message": "Style neu installieren"
|
"message": "Style neu installieren"
|
||||||
|
@ -450,24 +385,18 @@
|
||||||
"installUpdateFromLabel": {
|
"installUpdateFromLabel": {
|
||||||
"message": "Nach Updates suchen"
|
"message": "Nach Updates suchen"
|
||||||
},
|
},
|
||||||
|
"installUpdateUnavailable": {
|
||||||
|
"message": "Ziehe die Datei auf die Tableiste oder definiere @updateURL in den Metadaten des Styles, um automatisch nach Updates zu suchen."
|
||||||
|
},
|
||||||
"license": {
|
"license": {
|
||||||
"message": "Lizenz"
|
"message": "Lizenz"
|
||||||
},
|
},
|
||||||
"linkGetHelp": {
|
"linkGetHelp": {
|
||||||
"message": "Hilfeseite anzeigen"
|
"message": "Hilfeseite anzeigen"
|
||||||
},
|
},
|
||||||
"linkGetShareStyles": {
|
|
||||||
"message": "Styles finden und teilen"
|
|
||||||
},
|
|
||||||
"linkGetShareStylesInfo": {
|
|
||||||
"message": "Die neue, von der Community betriebene Website userstyles.world wurde von Userstyle-Autoren erstellt, um userstyles.org zu ersetzen. Dieses war im letzten Jahr so langsam und unzuverlässig geworden, dass viele Autoren ihre Styles nicht mehr aktualisierten."
|
|
||||||
},
|
|
||||||
"linkGetStyles": {
|
"linkGetStyles": {
|
||||||
"message": "Styles beziehen"
|
"message": "Styles beziehen"
|
||||||
},
|
},
|
||||||
"linkGetStylesInfo": {
|
|
||||||
"message": "Diese Archivseite wurde von einem Nutzer der Userstyle Community erstellt, um eine Sicherungskopie des langsamen und unzuverlässigen userstyles.org bereitzustellen. Die Inhalte werden ungefähr einmal am Tag aktualisiert."
|
|
||||||
},
|
|
||||||
"linkTranslate": {
|
"linkTranslate": {
|
||||||
"message": "Übersetzen"
|
"message": "Übersetzen"
|
||||||
},
|
},
|
||||||
|
@ -520,14 +449,14 @@
|
||||||
"message": "Bei der Echtzeitaktualisierung der Datei ist ein Fehler aufgetreten"
|
"message": "Bei der Echtzeitaktualisierung der Datei ist ein Fehler aufgetreten"
|
||||||
},
|
},
|
||||||
"liveReloadInstallHint": {
|
"liveReloadInstallHint": {
|
||||||
"message": "Lasse diesen Tab geöffnet, um den Style bei externen Änderungen automatisch zu aktualisieren."
|
"message": "Echtzeitaktualisierung ist aktiviert, sodass die Darstellung des jeweiligen Styles automatisch aktualisiert wird, wenn externe Änderungen erfolgen."
|
||||||
},
|
|
||||||
"liveReloadInstallHintFF": {
|
|
||||||
"message": "Lasse diesen und den Originaltab offen, um den Style bei externen Änderungen automatisch zu aktualisieren."
|
|
||||||
},
|
},
|
||||||
"liveReloadLabel": {
|
"liveReloadLabel": {
|
||||||
"message": "Echtzeitaktualisierung"
|
"message": "Echtzeitaktualisierung"
|
||||||
},
|
},
|
||||||
|
"liveReloadUnavailable": {
|
||||||
|
"message": "Ziehe die Datei auf die Tableiste, um die Echtzeitaktualisierung nutzen zu können."
|
||||||
|
},
|
||||||
"manageFavicons": {
|
"manageFavicons": {
|
||||||
"message": "Favicons in der \"Gilt für\" Spalte anzeigen"
|
"message": "Favicons in der \"Gilt für\" Spalte anzeigen"
|
||||||
},
|
},
|
||||||
|
@ -535,7 +464,7 @@
|
||||||
"message": "Ausgegraut"
|
"message": "Ausgegraut"
|
||||||
},
|
},
|
||||||
"manageFaviconsHelp": {
|
"manageFaviconsHelp": {
|
||||||
"message": "Stylus nutzt hierzu den externen Dienst https://icons.duckduckgo.com"
|
"message": "Stylus nutzt hierzu den externen Dienst https://www.google.com/s2/favicons"
|
||||||
},
|
},
|
||||||
"manageFilters": {
|
"manageFilters": {
|
||||||
"message": "Filter"
|
"message": "Filter"
|
||||||
|
@ -546,9 +475,6 @@
|
||||||
"manageMaxTargets": {
|
"manageMaxTargets": {
|
||||||
"message": "Anzahl der \"Gilt für\" Elemente"
|
"message": "Anzahl der \"Gilt für\" Elemente"
|
||||||
},
|
},
|
||||||
"manageMinColumnWidth": {
|
|
||||||
"message": "Minimale Spaltenbreite (in Pixeln. 9999 deaktiviert den Mehrspalten-Modus)"
|
|
||||||
},
|
|
||||||
"manageNewStyleAsUsercss": {
|
"manageNewStyleAsUsercss": {
|
||||||
"message": "als UserCSS"
|
"message": "als UserCSS"
|
||||||
},
|
},
|
||||||
|
@ -582,211 +508,16 @@
|
||||||
"menuShowBadge": {
|
"menuShowBadge": {
|
||||||
"message": "Anzahl der aktiven Styles anzeigen"
|
"message": "Anzahl der aktiven Styles anzeigen"
|
||||||
},
|
},
|
||||||
"meta_invalidCheckboxDefault": {
|
|
||||||
"message": "Ungültige @var Checkbox: Wert muss 0 oder 1 sein"
|
|
||||||
},
|
|
||||||
"meta_invalidColor": {
|
|
||||||
"message": "Ungültige @var Farbe: $color$ ist keine Farbe",
|
|
||||||
"placeholders": {
|
|
||||||
"color": {
|
|
||||||
"content": "$1"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"meta_invalidNumber": {
|
|
||||||
"message": "Erwarte Zahl"
|
|
||||||
},
|
|
||||||
"meta_invalidRange": {
|
|
||||||
"message": "Ungültige @var $type$: Wert muss eine Zahl oder ein Array sein",
|
|
||||||
"placeholders": {
|
|
||||||
"type": {
|
|
||||||
"content": "$1"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"meta_invalidRangeDefault": {
|
|
||||||
"message": "Ungültige @var $type$: Standardwert ist null",
|
|
||||||
"placeholders": {
|
|
||||||
"type": {
|
|
||||||
"content": "$1"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"meta_invalidRangeMax": {
|
|
||||||
"message": "Ungültige @var $type$: Standardwert ist größer als das Maximum",
|
|
||||||
"placeholders": {
|
|
||||||
"type": {
|
|
||||||
"content": "$1"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"meta_invalidRangeMin": {
|
|
||||||
"message": "Ungültige @var $type$: Standardwert ist geringer als das Minimum",
|
|
||||||
"placeholders": {
|
|
||||||
"type": {
|
|
||||||
"content": "$1"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"meta_invalidRangeMultipleUnits": {
|
|
||||||
"message": "Ungültige @var $type$: Mehrere Einheiten sind definiert",
|
|
||||||
"placeholders": {
|
|
||||||
"type": {
|
|
||||||
"content": "$1"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"meta_invalidRangeStep": {
|
|
||||||
"message": "Ungültige @var $type$: Standardwert ist kein Vielfaches der Schrittweite",
|
|
||||||
"placeholders": {
|
|
||||||
"type": {
|
|
||||||
"content": "$1"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"meta_invalidRangeTooManyValues": {
|
|
||||||
"message": "Ungültige @var $type$: Der Array enthält zu viele Elemente",
|
|
||||||
"placeholders": {
|
|
||||||
"type": {
|
|
||||||
"content": "$1"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"meta_invalidRangeUnits": {
|
|
||||||
"message": "Ungültige @var $type$: '$units$' ist keine gültige Einheit",
|
|
||||||
"placeholders": {
|
|
||||||
"type": {
|
|
||||||
"content": "$1"
|
|
||||||
},
|
|
||||||
"units": {
|
|
||||||
"content": "$2"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"meta_invalidRangeValue": {
|
|
||||||
"message": "Ungültige @var $type$: Elemente im Array müssen Zahlen, Strings oder null sein",
|
|
||||||
"placeholders": {
|
|
||||||
"type": {
|
|
||||||
"content": "$1"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"meta_invalidSelect": {
|
|
||||||
"message": "Ungültiges @var select: Der Standardwert muss eine Array oder Objekt sein"
|
|
||||||
},
|
|
||||||
"meta_invalidSelectEmptyOptions": {
|
|
||||||
"message": "Ungültiges @var select: Die Auswahlliste ist leer"
|
|
||||||
},
|
|
||||||
"meta_invalidSelectLabel": {
|
|
||||||
"message": "Ungültiges @var select: Beschriftung der Auswahlmöglichkeiten ist leer"
|
|
||||||
},
|
|
||||||
"meta_invalidSelectMultipleDefaults": {
|
|
||||||
"message": "Ungültiges @var select: Mehrere Standardauswahlen sind definiert"
|
|
||||||
},
|
|
||||||
"meta_invalidSelectNameDuplicated": {
|
|
||||||
"message": "Ungültiges @var select: Doppelte Optionsnamen"
|
|
||||||
},
|
|
||||||
"meta_invalidSelectValue": {
|
|
||||||
"message": "Ungültiges @var select: Werte innerhalb des Arrays/Objekts müssen Strings sein"
|
|
||||||
},
|
|
||||||
"meta_invalidSelectValueMismatch": {
|
|
||||||
"message": "Ungültiges @var select: Wert existiert nicht in der Auswahlliste"
|
|
||||||
},
|
|
||||||
"meta_invalidString": {
|
|
||||||
"message": "Erwarte String in Anführungszeichen"
|
|
||||||
},
|
|
||||||
"meta_invalidURLProtocol": {
|
|
||||||
"message": "Ungültiges URL Protokoll. Nur http und https sind erlaubt: $protocol$",
|
|
||||||
"placeholders": {
|
|
||||||
"protocol": {
|
|
||||||
"content": "$1"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"meta_invalidVersion": {
|
|
||||||
"message": "Ungültige Versionsnummer"
|
|
||||||
},
|
|
||||||
"meta_invalidWord": {
|
|
||||||
"message": "Erwarte ein Wort"
|
|
||||||
},
|
|
||||||
"meta_missingChar": {
|
|
||||||
"message": "Erwarte Zeichen: $chars$",
|
|
||||||
"placeholders": {
|
|
||||||
"chars": {
|
|
||||||
"content": "$1"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"meta_missingEOT": {
|
|
||||||
"message": "Erwarte EOT Daten"
|
|
||||||
},
|
|
||||||
"meta_missingMandatory": {
|
|
||||||
"message": "Erforderliche Metadaten fehlen: $keys$",
|
|
||||||
"placeholders": {
|
|
||||||
"keys": {
|
|
||||||
"content": "$1"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"meta_unknownJSONLiteral": {
|
|
||||||
"message": "Ungültiges JSON: $literal$ ist kein gültiges JSON Symbol",
|
|
||||||
"placeholders": {
|
|
||||||
"literal": {
|
|
||||||
"content": "$1"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"meta_unknownMeta": {
|
|
||||||
"message": "Unbekannte Metadaten: $key$",
|
|
||||||
"placeholders": {
|
|
||||||
"key": {
|
|
||||||
"content": "$1"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"meta_unknownMetaTypo": {
|
|
||||||
"message": "Vielleicht @$keyOk$? Unbekannte Metadaten: @$keyErr$",
|
|
||||||
"placeholders": {
|
|
||||||
"keyErr": {
|
|
||||||
"content": "$1"
|
|
||||||
},
|
|
||||||
"keyOk": {
|
|
||||||
"content": "$2"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"meta_unknownPreprocessor": {
|
|
||||||
"message": "Unbekannter @preprocessor: $preprocessor$",
|
|
||||||
"placeholders": {
|
|
||||||
"preprocessor": {
|
|
||||||
"content": "$1"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"meta_unknownVarType": {
|
|
||||||
"message": "Unbekannter @$varkey$ Typ: $vartype$",
|
|
||||||
"placeholders": {
|
|
||||||
"varkey": {
|
|
||||||
"content": "$1"
|
|
||||||
},
|
|
||||||
"vartype": {
|
|
||||||
"content": "$2"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"noFileToImport": {
|
|
||||||
"message": "Keine Importdatei vorhanden. Um Styles zu importieren, solltest du sie zuerst exportieren."
|
|
||||||
},
|
|
||||||
"noStylesForSite": {
|
"noStylesForSite": {
|
||||||
"message": "Für diese Webseite sind keine Styles installiert."
|
"message": "Für diese Webseite sind keine Styles installiert."
|
||||||
},
|
},
|
||||||
"numberedLine": {
|
|
||||||
"message": "Zeile:"
|
|
||||||
},
|
|
||||||
"openManage": {
|
"openManage": {
|
||||||
"message": "Verwalten"
|
"message": "Verwalten"
|
||||||
},
|
},
|
||||||
"openOptions": {
|
"openOptionsManage": {
|
||||||
|
"message": "Optionen"
|
||||||
|
},
|
||||||
|
"openOptionsPopup": {
|
||||||
"message": "Optionen"
|
"message": "Optionen"
|
||||||
},
|
},
|
||||||
"openStylesManager": {
|
"openStylesManager": {
|
||||||
|
@ -798,15 +529,6 @@
|
||||||
"optionsAdvanced": {
|
"optionsAdvanced": {
|
||||||
"message": "Erweitert"
|
"message": "Erweitert"
|
||||||
},
|
},
|
||||||
"optionsAdvancedAutoSwitchSchemeBySystem": {
|
|
||||||
"message": "Nach Systemeinstellung"
|
|
||||||
},
|
|
||||||
"optionsAdvancedAutoSwitchSchemeByTime": {
|
|
||||||
"message": "Bei Nacht:"
|
|
||||||
},
|
|
||||||
"optionsAdvancedAutoSwitchSchemeNever": {
|
|
||||||
"message": "Deaktiviert. Die hell / dunkel Einstellung in Styles wird ignoriert."
|
|
||||||
},
|
|
||||||
"optionsAdvancedContextDelete": {
|
"optionsAdvancedContextDelete": {
|
||||||
"message": "\"Löschen\" im Editor-Kontextmenü hinzufügen"
|
"message": "\"Löschen\" im Editor-Kontextmenü hinzufügen"
|
||||||
},
|
},
|
||||||
|
@ -814,29 +536,11 @@
|
||||||
"message": "Ermögliche Iframes via HTML[stylus-iframe]"
|
"message": "Ermögliche Iframes via HTML[stylus-iframe]"
|
||||||
},
|
},
|
||||||
"optionsAdvancedExposeIframesNote": {
|
"optionsAdvancedExposeIframesNote": {
|
||||||
"message": "Style wirkt sich auch auf iframes der anvisierten (obersten) Domain aus.\nIframe-spezifisches CSS ist dann wie folgt möglich:\nhtml[stylus-iframe$$=\"twitter.com\"] h1 { display:none }"
|
"message": "Style wirkt sich auch auf iframes der anvisierten Domain aus.\nIframe-spezifisches CSS ist dann möglich wie folgt:\nhtml[stylus-iframe$=\"twitter.com\"] h1 { display:none }"
|
||||||
},
|
|
||||||
"optionsAdvancedExposeStyleName": {
|
|
||||||
"message": "Stylename anzeigen"
|
|
||||||
},
|
|
||||||
"optionsAdvancedExposeStyleNameNote": {
|
|
||||||
"message": "Zeigt den Stylenamen auf der Seite an, um das Debuggen von Styles in den Devtools zu erleichtern. Bitte lade die Tabs neu, um die neue Einstellung zu übernehmen."
|
|
||||||
},
|
},
|
||||||
"optionsAdvancedNewStyleAsUsercss": {
|
"optionsAdvancedNewStyleAsUsercss": {
|
||||||
"message": "Schreibe neuen Style als UserCSS"
|
"message": "Schreibe neuen Style als UserCSS"
|
||||||
},
|
},
|
||||||
"optionsAdvancedPatchCsp": {
|
|
||||||
"message": "<code>CSP</code> abändern, um externe Ressourcen zu erlauben"
|
|
||||||
},
|
|
||||||
"optionsAdvancedPatchCspNote": {
|
|
||||||
"message": "Aktivieren, wenn Styles Bilder oder Schriftarten enthalten, die aufgrund strenger <code>CSP</code> (<code>Content-Security-Policy</code>) Regeln mancher Seiten nicht laden.\n\nDas Aktivieren wird <code>CSP</code>Beschränkungen lockern, um für den Style erforderliche Ressourcen zu laden. Diese Option ist für fortgeschrittene Benutzer gedacht, die sich den möglichen Sicherheitsrisiken bewusst sind und die die Verantwortung dafür tragen, die nachgeladenen Inhalte selbst zu überwachen. Informiere dich über \"CSS-basierte Angriffe\" um mehr zu erfahren.\n\nBeachte außerdem, dass diese Option nicht garantiert funktioniert, falls eine andere installierte Erweiterung die Netzwerkantwort (CSP-header) zuerst abändert."
|
|
||||||
},
|
|
||||||
"optionsAdvancedStyleViaXhr": {
|
|
||||||
"message": "Style unverzüglich einfügen"
|
|
||||||
},
|
|
||||||
"optionsAdvancedStyleViaXhrNote": {
|
|
||||||
"message": "Aktivieren, falls während dem Browsen ungestylter Inhalt aufblitzt (FOUC). Besonders auffällig bei dunklen Styles.\n\nDer technische Grund ist, dass Chrome/Chromium die asynchrone Kommunikation von Erweiterungen zeitlich nach hinten verschiebt, um vermeintlich die Seitenladegeschwindigkeit zu erhöhen. Dies kann das Einfügen von Styles spürbar verzögern. Da für WebExtensions keine synchrone API bereitgestellt wird, versucht Stylus als Notbehelf, Styles über die veraltete XMLHttpRequest web API zu laden. Es sollten keine nachteiligen Effekte eintreten, weil die Anfrage innerhalb weniger Millisekunden abgearbeitet wird, während die Seite noch vom Server geladen wird.\n\nTrotzdem wird Chromium eine Warnung in der Entwicklerkonsole ausgeben. Über Rechtsklick auf die Warnung können zukünftige Meldungen versteckt werden."
|
|
||||||
},
|
|
||||||
"optionsBadgeDisabled": {
|
"optionsBadgeDisabled": {
|
||||||
"message": "Hintergrundfarbe wenn deaktiviert"
|
"message": "Hintergrundfarbe wenn deaktiviert"
|
||||||
},
|
},
|
||||||
|
@ -855,15 +559,9 @@
|
||||||
"optionsCustomizeIcon": {
|
"optionsCustomizeIcon": {
|
||||||
"message": "Symbolleisten-Icon"
|
"message": "Symbolleisten-Icon"
|
||||||
},
|
},
|
||||||
"optionsCustomizeSync": {
|
|
||||||
"message": "Mit Cloud synchronisieren"
|
|
||||||
},
|
|
||||||
"optionsHeading": {
|
"optionsHeading": {
|
||||||
"message": "Optionen"
|
"message": "Optionen"
|
||||||
},
|
},
|
||||||
"optionsIconAuto": {
|
|
||||||
"message": "An Hell- / Dunkelmodus angleichen"
|
|
||||||
},
|
|
||||||
"optionsIconDark": {
|
"optionsIconDark": {
|
||||||
"message": "Dunkle Browser-Themes"
|
"message": "Dunkle Browser-Themes"
|
||||||
},
|
},
|
||||||
|
@ -885,82 +583,15 @@
|
||||||
"optionsResetButton": {
|
"optionsResetButton": {
|
||||||
"message": "Optionen zurücksetzen"
|
"message": "Optionen zurücksetzen"
|
||||||
},
|
},
|
||||||
"optionsStylusThemes": {
|
|
||||||
"message": "Klicke auf einer beliebigen Stylus Seite auf das Stylus-Symbol in der Werkzeugleiste des Browsers, dann klicke auf \"Styles finden\""
|
|
||||||
},
|
|
||||||
"optionsSubheading": {
|
"optionsSubheading": {
|
||||||
"message": "Mehr Optionen"
|
"message": "Mehr Optionen"
|
||||||
},
|
},
|
||||||
"optionsSyncConnect": {
|
|
||||||
"message": "Verbinden"
|
|
||||||
},
|
|
||||||
"optionsSyncDisconnect": {
|
|
||||||
"message": "Trennen"
|
|
||||||
},
|
|
||||||
"optionsSyncLogin": {
|
|
||||||
"message": "Anmelden"
|
|
||||||
},
|
|
||||||
"optionsSyncNone": {
|
|
||||||
"message": "Nichts"
|
|
||||||
},
|
|
||||||
"optionsSyncPassword": {
|
|
||||||
"message": "Passwort"
|
|
||||||
},
|
|
||||||
"optionsSyncStatusConnected": {
|
|
||||||
"message": "Verbunden"
|
|
||||||
},
|
|
||||||
"optionsSyncStatusConnecting": {
|
|
||||||
"message": "Verbinde..."
|
|
||||||
},
|
|
||||||
"optionsSyncStatusDisconnected": {
|
|
||||||
"message": "Verbindung getrennt"
|
|
||||||
},
|
|
||||||
"optionsSyncStatusDisconnecting": {
|
|
||||||
"message": "Trenne Verbindung..."
|
|
||||||
},
|
|
||||||
"optionsSyncStatusPull": {
|
|
||||||
"message": "Hole Style $loaded$ von $total$",
|
|
||||||
"placeholders": {
|
|
||||||
"loaded": {
|
|
||||||
"content": "$1"
|
|
||||||
},
|
|
||||||
"total": {
|
|
||||||
"content": "$2"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"optionsSyncStatusPush": {
|
|
||||||
"message": "Lade Style $loaded$ von $total$ hoch",
|
|
||||||
"placeholders": {
|
|
||||||
"loaded": {
|
|
||||||
"content": "$1"
|
|
||||||
},
|
|
||||||
"total": {
|
|
||||||
"content": "$2"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"optionsSyncStatusRelogin": {
|
|
||||||
"message": "Session abgelaufen, bitte neu einloggen."
|
|
||||||
},
|
|
||||||
"optionsSyncStatusSyncing": {
|
|
||||||
"message": "Synchronisiere..."
|
|
||||||
},
|
|
||||||
"optionsSyncSyncNow": {
|
|
||||||
"message": "Jetzt synchronisieren"
|
|
||||||
},
|
|
||||||
"optionsSyncUsername": {
|
|
||||||
"message": "Benutzername"
|
|
||||||
},
|
|
||||||
"optionsUpdateImportNote": {
|
"optionsUpdateImportNote": {
|
||||||
"message": "Nach dem Importieren von Styles aus einer alten Version oder von Stylish ist eine einmalige manuelle Updatesuche in der Verwaltung nötig. Dies stellt sicher, dass alle Styles auf dem aktuellsten Stand sind."
|
"message": "Nach dem Importieren von Styles aus einer alten Version oder von Stylish ist eine einmalige manuelle Updatesuche in der Verwaltung nötig. Dies stellt sicher, dass alle Styles auf dem aktuellsten Stand sind."
|
||||||
},
|
},
|
||||||
"optionsUpdateInterval": {
|
"optionsUpdateInterval": {
|
||||||
"message": "Style-Updateintervall in Stunden (0 zum Deaktivieren)"
|
"message": "Style-Updateintervall in Stunden (0 zum Deaktivieren)"
|
||||||
},
|
},
|
||||||
"overwriteFileExport": {
|
|
||||||
"message": "Willst du die existierende Datei überschreiben?"
|
|
||||||
},
|
|
||||||
"paginationCurrent": {
|
"paginationCurrent": {
|
||||||
"message": "Aktuelle Seite"
|
"message": "Aktuelle Seite"
|
||||||
},
|
},
|
||||||
|
@ -979,9 +610,6 @@
|
||||||
"parseUsercssError": {
|
"parseUsercssError": {
|
||||||
"message": "UserCSS parsen fehlgeschlagen:"
|
"message": "UserCSS parsen fehlgeschlagen:"
|
||||||
},
|
},
|
||||||
"popupAutoResort": {
|
|
||||||
"message": "Styles im Popup sofort neu sortieren"
|
|
||||||
},
|
|
||||||
"popupBorders": {
|
"popupBorders": {
|
||||||
"message": "Weiße Rahmen an den Seiten hinzufügen"
|
"message": "Weiße Rahmen an den Seiten hinzufügen"
|
||||||
},
|
},
|
||||||
|
@ -994,18 +622,9 @@
|
||||||
"popupHotkeysTooltip": {
|
"popupHotkeysTooltip": {
|
||||||
"message": "Klicke, um Tastenkürzel zu sehen"
|
"message": "Klicke, um Tastenkürzel zu sehen"
|
||||||
},
|
},
|
||||||
"popupManageSiteStyles": {
|
|
||||||
"message": "Styles dieser Seite verwalten"
|
|
||||||
},
|
|
||||||
"popupManageTooltip": {
|
"popupManageTooltip": {
|
||||||
"message": "Shift+Klick oder Rechtsklick öffnet den Stylemanager mit Filter für Styles der aktuellen Seite"
|
"message": "Shift+Klick oder Rechtsklick öffnet den Stylemanager mit Filter für Styles der aktuellen Seite"
|
||||||
},
|
},
|
||||||
"popupMenuButtonTooltip": {
|
|
||||||
"message": "Aktionsmenü"
|
|
||||||
},
|
|
||||||
"popupOpenEditInPopup": {
|
|
||||||
"message": "Einfaches Fenster verwenden (keine Omnibox)"
|
|
||||||
},
|
|
||||||
"popupOpenEditInWindow": {
|
"popupOpenEditInWindow": {
|
||||||
"message": "Editor in neuem Fenster öffnen"
|
"message": "Editor in neuem Fenster öffnen"
|
||||||
},
|
},
|
||||||
|
@ -1018,51 +637,12 @@
|
||||||
"prefShowBadge": {
|
"prefShowBadge": {
|
||||||
"message": "Anzahl der aktiven Styles auf der aktuellen Seite"
|
"message": "Anzahl der aktiven Styles auf der aktuellen Seite"
|
||||||
},
|
},
|
||||||
"preferScheme": {
|
|
||||||
"message": "Hell- / Dunkelmodus Vorrang"
|
|
||||||
},
|
|
||||||
"preferSchemeAlways": {
|
|
||||||
"message": "Derzeit ignoriert (Style wird immer angewendet), weil der allgemeine Hell- / Dunkelmodus nicht aktiv ist"
|
|
||||||
},
|
|
||||||
"preferSchemeDark": {
|
|
||||||
"message": "Dunkel"
|
|
||||||
},
|
|
||||||
"preferSchemeLight": {
|
|
||||||
"message": "Hell"
|
|
||||||
},
|
|
||||||
"preferSchemeNone": {
|
|
||||||
"message": "Keines (immer aktiv)"
|
|
||||||
},
|
|
||||||
"previewLabel": {
|
"previewLabel": {
|
||||||
"message": "Echtzeitvorschau"
|
"message": "Echtzeitvorschau"
|
||||||
},
|
},
|
||||||
"previewTooltip": {
|
"previewTooltip": {
|
||||||
"message": "Übernimmt die Änderungen nur vorübergehend.\nSpeichere den Style, um die Änderungen dauerhaft anzuwenden."
|
"message": "Übernimmt die Änderungen nur vorübergehend.\nSpeichere den Style, um die Änderungen dauerhaft anzuwenden."
|
||||||
},
|
},
|
||||||
"publish": {
|
|
||||||
"message": "Veröffentlichen"
|
|
||||||
},
|
|
||||||
"publishPush": {
|
|
||||||
"message": "Erneut veröffentlichen"
|
|
||||||
},
|
|
||||||
"publishReconnect": {
|
|
||||||
"message": "Versuche, die Verbindung zu trennen; dann erneut veröffentlichen"
|
|
||||||
},
|
|
||||||
"publishRetry": {
|
|
||||||
"message": "Stylus versucht noch, den Style zu veröffentlichen, aber du kannst es erneut versuchen, wenn du keine Aktivität oder Popups siehst. Jetzt neu versuchen?"
|
|
||||||
},
|
|
||||||
"publishStyle": {
|
|
||||||
"message": "Style veröffentlichen"
|
|
||||||
},
|
|
||||||
"publishUsw": {
|
|
||||||
"message": "Via <userstyles.world>"
|
|
||||||
},
|
|
||||||
"readingStyles": {
|
|
||||||
"message": "Lese Styles..."
|
|
||||||
},
|
|
||||||
"reload": {
|
|
||||||
"message": "Neu laden"
|
|
||||||
},
|
|
||||||
"replace": {
|
"replace": {
|
||||||
"message": "Ersetzen"
|
"message": "Ersetzen"
|
||||||
},
|
},
|
||||||
|
@ -1072,24 +652,15 @@
|
||||||
"replaceWith": {
|
"replaceWith": {
|
||||||
"message": "Ersetzen durch"
|
"message": "Ersetzen durch"
|
||||||
},
|
},
|
||||||
"restoreTemplate": {
|
|
||||||
"message": "Standard-Template wiederherstellen.\n\n(Die gerade geöffneten Editorseiten werden nicht verändert.)"
|
|
||||||
},
|
|
||||||
"retrieveBckp": {
|
"retrieveBckp": {
|
||||||
"message": "Styles importieren"
|
"message": "Styles importieren"
|
||||||
},
|
},
|
||||||
"saveAsTemplate": {
|
|
||||||
"message": "Als Template speichern"
|
|
||||||
},
|
|
||||||
"search": {
|
"search": {
|
||||||
"message": "Suche"
|
"message": "Suche"
|
||||||
},
|
},
|
||||||
"searchCaseSensitive": {
|
"searchCaseSensitive": {
|
||||||
"message": "Groß- / Kleinschreibung beachten"
|
"message": "Groß- / Kleinschreibung beachten"
|
||||||
},
|
},
|
||||||
"searchGlobalStyles": {
|
|
||||||
"message": "Auch globale Styles suchen"
|
|
||||||
},
|
|
||||||
"searchNumberOfResults": {
|
"searchNumberOfResults": {
|
||||||
"message": "Trefferanzahl"
|
"message": "Trefferanzahl"
|
||||||
},
|
},
|
||||||
|
@ -1105,12 +676,6 @@
|
||||||
"searchResultNoneFound": {
|
"searchResultNoneFound": {
|
||||||
"message": "Keine Styles für diese Seite gefunden."
|
"message": "Keine Styles für diese Seite gefunden."
|
||||||
},
|
},
|
||||||
"searchResultNotMatching": {
|
|
||||||
"message": "Der Style wurde installiert, gilt aber nicht für die aktuelle URL."
|
|
||||||
},
|
|
||||||
"searchResultNotMatchingNote": {
|
|
||||||
"message": "Frage den Autor des Styles, ob er die URL hinzufügt.\n\nDu kannst den Style auch selbst bearbeiten,\naber dann werden automatische Updates für diesen Style deaktiviert."
|
|
||||||
},
|
|
||||||
"searchResultRating": {
|
"searchResultRating": {
|
||||||
"message": "Bewertung"
|
"message": "Bewertung"
|
||||||
},
|
},
|
||||||
|
@ -1120,48 +685,30 @@
|
||||||
"searchResultWeeklyCount": {
|
"searchResultWeeklyCount": {
|
||||||
"message": "Wöchentliche Installationen"
|
"message": "Wöchentliche Installationen"
|
||||||
},
|
},
|
||||||
"searchStyleQueryHint": {
|
"searchStyles": {
|
||||||
"message": "Stylenamen durchsuchen (Groß-Kleinschreibung wird beachtet, sobald ein Großbuchstabe benutzt wird):\nHaus Baum Sonne - sucht nach allen Wörtern in beliebiger Reihenfolge\n\"Baum Haus\" - sucht exakt diesen Ausdruck (ohne Anführungszeichen)\n/foo.*bar/i - Regulärer Ausdruck ohne Leerzeichen (nutze stattdessen \\s)"
|
"message": "Inhalte durchsuchen"
|
||||||
},
|
|
||||||
"searchStylesAll": {
|
|
||||||
"message": "Alles"
|
|
||||||
},
|
|
||||||
"searchStylesCode": {
|
|
||||||
"message": "CSS Code"
|
|
||||||
},
|
},
|
||||||
"searchStylesHelp": {
|
"searchStylesHelp": {
|
||||||
"message": "Die </>-Taste (Numpad) oder <Ctrl-F> fokussieren das Suchfeld.\nEinfacher Text: Sucht mit Leerzeichen getrennte Wörter in beliebiger Reihenfolge.\nSuche nach genauem Ausdruck: Anführungszeichen verwenden, z.B. <\".header ~ div\">\nRegExp: Nutze Schrägstrich und Flags, z.B. </body.*?\\ba\\b/i>\nStyles, die auf eine URL passen: Aktiviere \"Nach URL\" und suche nach einer vollständigen URL, z.B. https://www.example.org/\nMetadaten: Aktiviere das Kontrollkästchen für Metadaten, um in Namen, \"Gilt für\" Feldern, Installations-URL, Update-URL und dem gesamten Metadatenblock in UserCSS Styles zu suchen."
|
"message": "Die </>-Taste (Numpad) fokussiert das Suchfeld.\nEinfacher Text: Sucht im Namen, Quelltext, Homepage und anvisierten URLs. Wörter mit weniger als 3 Buchstaben werden ignoriert.\nStyles, die auf eine URL passen: Stelle der Suche <url:> voran, z.B. <url:https://github.com/openstyles/stylus>\nRegExp: Nutze Slash und Flags, z.B. </body.*?\\ba\\b/simguy>\nSuche nach genauem Ausdruck: Anführungszeichen verwenden, z.B. <\".header ~ div\"> "
|
||||||
},
|
|
||||||
"searchStylesMatchUrl": {
|
|
||||||
"message": "Nach URL"
|
|
||||||
},
|
|
||||||
"searchStylesMeta": {
|
|
||||||
"message": "Metadaten"
|
|
||||||
},
|
},
|
||||||
"sectionAdd": {
|
"sectionAdd": {
|
||||||
"message": "Weiteren Bereich hinzufügen"
|
"message": "Weiteren Bereich hinzufügen"
|
||||||
},
|
},
|
||||||
|
"sectionHelp": {
|
||||||
|
"message": "Mit Bereichen kannst Du unterschiedliche Code-Teile auf unterschiedliche URLs in dem gleichen Style anwenden. Beispielsweise kann über einen einzigen Style die Startseite der Website geändert werden, während für den Rest der Website andere Änderungen gelten."
|
||||||
|
},
|
||||||
"sectionRemove": {
|
"sectionRemove": {
|
||||||
"message": "Bereich entfernen"
|
"message": "Bereich entfernen"
|
||||||
},
|
},
|
||||||
"sectionRestore": {
|
"sectionRestore": {
|
||||||
"message": "Gelöschten Bereich wiederherstellen"
|
"message": "Gelöschten Bereich wiederherstellen"
|
||||||
},
|
},
|
||||||
"sections": {
|
|
||||||
"message": "Bereiche"
|
|
||||||
},
|
|
||||||
"settings": {
|
|
||||||
"message": "Einstellungen"
|
|
||||||
},
|
|
||||||
"shortcuts": {
|
"shortcuts": {
|
||||||
"message": "Tastenkürzel"
|
"message": "Tastenkürzel"
|
||||||
},
|
},
|
||||||
"shortcutsNote": {
|
"shortcutsNote": {
|
||||||
"message": "Eine Tastenkombination definieren"
|
"message": "Eine Tastenkombination definieren"
|
||||||
},
|
},
|
||||||
"shortcutsNoteFF": {
|
|
||||||
"message": "In Firefox 66+ kannst du die eingebaute Tastenkürzelverwaltung selbst öffnen:\n1) Rechtsklicke das Stylus-Symbol in der Werkzeugleiste und wähle \"Erweiterung verwalten\". Alternativ kannst du about:addons über das Hauptmenü oder per Tastenkürzel Strg-Umschalt-A öffnen.\n2) Klicke auf der geöffneten Seite (about:addons) auf das Zahnrad oben rechts.\n3) Wähle \"Tastenkombinationen von Erweiterungen verwalten\".\n\nHier kannst du alle Tastenkürzel individuell anpassen."
|
|
||||||
},
|
|
||||||
"sortDateNewestFirst": {
|
"sortDateNewestFirst": {
|
||||||
"message": "neueste zuerst"
|
"message": "neueste zuerst"
|
||||||
},
|
},
|
||||||
|
@ -1189,9 +736,6 @@
|
||||||
"styleBeautify": {
|
"styleBeautify": {
|
||||||
"message": "Code formatieren"
|
"message": "Code formatieren"
|
||||||
},
|
},
|
||||||
"styleBeautifyHint": {
|
|
||||||
"message": "Hinweis: Mache einen Rechtsklick auf \"Code formatieren\" oder nutze das weiter unten definierte Tastenkürzel, um den Code zu formatieren ohne dieses Fenster anzuzeigen."
|
|
||||||
},
|
|
||||||
"styleBeautifyIndentConditional": {
|
"styleBeautifyIndentConditional": {
|
||||||
"message": "Rücke @media / @supports ein"
|
"message": "Rücke @media / @supports ein"
|
||||||
},
|
},
|
||||||
|
@ -1207,30 +751,12 @@
|
||||||
"styleEnabledLabel": {
|
"styleEnabledLabel": {
|
||||||
"message": "Aktiviert"
|
"message": "Aktiviert"
|
||||||
},
|
},
|
||||||
"styleExcludeLabel": {
|
|
||||||
"message": "Benutzerdefinierte ausgeschlossene URLs"
|
|
||||||
},
|
|
||||||
"styleFromMozillaFormatError": {
|
"styleFromMozillaFormatError": {
|
||||||
"message": "Import vom Mozilla Format fehlgeschlagen"
|
"message": "Import vom Mozilla Format fehlgeschlagen"
|
||||||
},
|
},
|
||||||
"styleFromMozillaFormatPrompt": {
|
"styleFromMozillaFormatPrompt": {
|
||||||
"message": "Mozilla-Format Code einfügen"
|
"message": "Mozilla-Format Code einfügen"
|
||||||
},
|
},
|
||||||
"styleIncludeLabel": {
|
|
||||||
"message": "Benutzerdefinierte Ziel-URLs"
|
|
||||||
},
|
|
||||||
"styleInjectionImportance": {
|
|
||||||
"message": "Style als wichtig markieren"
|
|
||||||
},
|
|
||||||
"styleInjectionOrder": {
|
|
||||||
"message": "Applikationsreihenfolge der Styles"
|
|
||||||
},
|
|
||||||
"styleInjectionOrderHint": {
|
|
||||||
"message": "Ziehe den Style mit der Maus an die gewünschte Stelle, um die Applikationsreihenfolge zu ändern. Styles weiter unten können die oberen überschreiben."
|
|
||||||
},
|
|
||||||
"styleInjectionOrderHint_prio": {
|
|
||||||
"message": "Die unten als wichtig gekennzeichneten Styles werden immer zuletzt angewendet, sodass sie auch neu installierte Styles überschreiben können. Klicke auf das Zeichen des Styles, um die Wichtigkeit umzuschalten."
|
|
||||||
},
|
|
||||||
"styleInstall": {
|
"styleInstall": {
|
||||||
"message": "\"$stylename$\" mit Stylus installieren?",
|
"message": "\"$stylename$\" mit Stylus installieren?",
|
||||||
"placeholders": {
|
"placeholders": {
|
||||||
|
@ -1261,24 +787,42 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"styleMetaErrorCheckbox": {
|
||||||
|
"message": "Ungültige @var Checkbox: Wert muss 0 oder 1 sein"
|
||||||
|
},
|
||||||
|
"styleMetaErrorColor": {
|
||||||
|
"message": "$color$ist kein gültiger Farbcode",
|
||||||
|
"placeholders": {
|
||||||
|
"color": {
|
||||||
|
"content": "$1"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"styleMetaErrorPreprocessor": {
|
||||||
|
"message": "Nicht unterstützter @preprocessor: $preprocessor$",
|
||||||
|
"placeholders": {
|
||||||
|
"preprocessor": {
|
||||||
|
"content": "$1"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"styleMetaErrorSelectValueMismatch": {
|
||||||
|
"message": "Ungültiges @select: Wert existiert nicht in der Liste"
|
||||||
|
},
|
||||||
|
"styleMissingMeta": {
|
||||||
|
"message": "Erforderliche Metadaten fehlen: @$key$",
|
||||||
|
"placeholders": {
|
||||||
|
"key": {
|
||||||
|
"content": "$1"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"styleMissingName": {
|
"styleMissingName": {
|
||||||
"message": "Bitte Namen eingeben"
|
"message": "Bitte Namen eingeben"
|
||||||
},
|
},
|
||||||
"styleName": {
|
|
||||||
"message": "Stylename"
|
|
||||||
},
|
|
||||||
"styleNotAppliedRegexpProblemTooltip": {
|
"styleNotAppliedRegexpProblemTooltip": {
|
||||||
"message": "Der Style wurde aufgrund ungültiger RegExp nicht angewandt."
|
"message": "Der Style wurde aufgrund ungültiger RegExp nicht angewandt."
|
||||||
},
|
},
|
||||||
"styleNotAppliedSchemeDark": {
|
|
||||||
"message": "Der Style wird nur im dunklen Modus angewendet"
|
|
||||||
},
|
|
||||||
"styleNotAppliedSchemeLight": {
|
|
||||||
"message": "Der Style wird nur im hellen Modus angewendet"
|
|
||||||
},
|
|
||||||
"stylePreferSchemeLabel": {
|
|
||||||
"message": "Dunkler/Heller Modus"
|
|
||||||
},
|
|
||||||
"styleRegexpInvalidExplanation": {
|
"styleRegexpInvalidExplanation": {
|
||||||
"message": "Einige RegExp konnten nicht kompiliert werden."
|
"message": "Einige RegExp konnten nicht kompiliert werden."
|
||||||
},
|
},
|
||||||
|
@ -1288,6 +832,9 @@
|
||||||
"styleRegexpProblemTooltip": {
|
"styleRegexpProblemTooltip": {
|
||||||
"message": "Anzahl der Bereiche, welche aufgrund nicht korrekt verwendeter RegExp nicht angewendet wurden"
|
"message": "Anzahl der Bereiche, welche aufgrund nicht korrekt verwendeter RegExp nicht angewendet wurden"
|
||||||
},
|
},
|
||||||
|
"styleRegexpTestButton": {
|
||||||
|
"message": "RegExp testen"
|
||||||
|
},
|
||||||
"styleRegexpTestFull": {
|
"styleRegexpTestFull": {
|
||||||
"message": "Zutreffende Tabs"
|
"message": "Zutreffende Tabs"
|
||||||
},
|
},
|
||||||
|
@ -1309,8 +856,8 @@
|
||||||
"styleSaveLabel": {
|
"styleSaveLabel": {
|
||||||
"message": "Speichern"
|
"message": "Speichern"
|
||||||
},
|
},
|
||||||
"styleSettings": {
|
"styleSectionsTitle": {
|
||||||
"message": "Style Einstellungen"
|
"message": "Bereiche"
|
||||||
},
|
},
|
||||||
"styleToMozillaFormatHelp": {
|
"styleToMozillaFormatHelp": {
|
||||||
"message": "Das Mozilla-Format des Codes kann mit Stylish für Firefox verwendet werden und bei userstyles.org eingereicht werden."
|
"message": "Das Mozilla-Format des Codes kann mit Stylish für Firefox verwendet werden und bei userstyles.org eingereicht werden."
|
||||||
|
@ -1333,24 +880,7 @@
|
||||||
"message": "Stylus funktioniert nicht auf Seiten wie diesen."
|
"message": "Stylus funktioniert nicht auf Seiten wie diesen."
|
||||||
},
|
},
|
||||||
"stylusUnavailableForURLdetails": {
|
"stylusUnavailableForURLdetails": {
|
||||||
"message": "Als Sicherheitsvorkehrung verbietet der Browser es Erweiterungen, seine eigenen Seiten (wie z.B. chrome://version oder about:addons) und eigene Seiten anderer Erweiterungen zu verändern. Auch die jeweils browser-eigene Erweiterungsgalerie (wie Chrome Web Store oder addons.mozilla.org) kann nicht verändert werden."
|
"message": "Als Sicherheitsvorkehrung verbietet der Browser es Erweiterungen, seine eingebauten Seiten (wie chrome://version, dem neuen Standard-Tab ab Chrome 61, about:addons, usw.), sowie Seiten anderer Erweiterungen zu beeinflussen. Jeder Browser beschränkt auch den Zugriff auf seine eigene Erweiterungsgalerie (wie Chrome Web Store oder addons.mozilla.org)."
|
||||||
},
|
|
||||||
"syncDropboxDeprecated": {
|
|
||||||
"message": "Dropbox Import / Export wurde durch einen fortschrittlicheren Mechanismus auf der Optionsseite ersetzt."
|
|
||||||
},
|
|
||||||
"syncError": {
|
|
||||||
"message": "Synchronisation fehlgeschlagen"
|
|
||||||
},
|
|
||||||
"syncErrorLock": {
|
|
||||||
"message": "Die Datenbank wird bereits verwendet. Die Sperre wird um $TIME$ aufgehoben.",
|
|
||||||
"placeholders": {
|
|
||||||
"time": {
|
|
||||||
"content": "$1"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"syncErrorRelogin": {
|
|
||||||
"message": "Synchronisation erfolglos. Du wurdest ausgeloggt.\nVersuche, dich in den Stylus Einstellungen wieder einzuloggen."
|
|
||||||
},
|
},
|
||||||
"syncStorageErrorSaving": {
|
"syncStorageErrorSaving": {
|
||||||
"message": "Der Wert kann nicht gespeichert werden. Versuche, die Textmenge zu reduzieren."
|
"message": "Der Wert kann nicht gespeichert werden. Versuche, die Textmenge zu reduzieren."
|
||||||
|
@ -1370,21 +900,18 @@
|
||||||
"unreachableAMOHint": {
|
"unreachableAMOHint": {
|
||||||
"message": "Um den Zugriff zu erlauben, öffne <about:config>, rechtsklicke in die Liste, klicke \"Neu\", dann \"Boolean\", füge <privacy.resistFingerprinting.block_mozAddonManager> ein und klicke OK, <true>, OK, lade <addons.mozilla.org> neu."
|
"message": "Um den Zugriff zu erlauben, öffne <about:config>, rechtsklicke in die Liste, klicke \"Neu\", dann \"Boolean\", füge <privacy.resistFingerprinting.block_mozAddonManager> ein und klicke OK, <true>, OK, lade <addons.mozilla.org> neu."
|
||||||
},
|
},
|
||||||
|
"unreachableAMOHintNewFF": {
|
||||||
|
"message": "In Firefox 60 und neuer muss auch die addons.mozilla.org domain von <extensions.webextensions.restrictedDomains> in <about:config> entfernt werden."
|
||||||
|
},
|
||||||
|
"unreachableAMOHintOldFF": {
|
||||||
|
"message": "Nur in Firefox 59 und neuer ist es möglich, WebExtenstions den Zugriff auf CSP-geschützte Seiten wie dieser zu erlauben."
|
||||||
|
},
|
||||||
"unreachableContentScript": {
|
"unreachableContentScript": {
|
||||||
"message": "Konnte nicht mit der Seite kommunizieren. Bitte versuchen Sie, die Seite erneut zu laden."
|
"message": "Konnte nicht mit der Seite kommunizieren. Bitte versuchen Sie, die Seite erneut zu laden."
|
||||||
},
|
},
|
||||||
"unreachableFileHint": {
|
"unreachableFileHint": {
|
||||||
"message": "Stylus kann nur auf das file:// Protokoll in der URL zugreifen, wenn dies in den Einstellungen der Erweiterung unter chrome://extensions festgelegt wurde."
|
"message": "Stylus kann nur auf das file:// Protokoll in der URL zugreifen, wenn dies in den Einstellungen der Erweiterung unter chrome://extensions festgelegt wurde."
|
||||||
},
|
},
|
||||||
"unreachableMozSiteHint": {
|
|
||||||
"message": "In Firefox 60 oder neuer muss diese Domain von <extensions.webextensions.restrictedDomains> in <about:config> entfernt werden."
|
|
||||||
},
|
|
||||||
"unreachableMozSiteHintOldFF": {
|
|
||||||
"message": "Erst ab Firefox 59 können WebExtensions Style-Elemente auf CSP-geschützten Seiten wie dieser hinzufügen."
|
|
||||||
},
|
|
||||||
"unzipStyles": {
|
|
||||||
"message": "Entpacke Styles..."
|
|
||||||
},
|
|
||||||
"updateAllCheckSucceededNoUpdate": {
|
"updateAllCheckSucceededNoUpdate": {
|
||||||
"message": "Keine Updates gefunden."
|
"message": "Keine Updates gefunden."
|
||||||
},
|
},
|
||||||
|
@ -1426,9 +953,6 @@
|
||||||
"updatesCurrentlyInstalled": {
|
"updatesCurrentlyInstalled": {
|
||||||
"message": "Installierte Updates:"
|
"message": "Installierte Updates:"
|
||||||
},
|
},
|
||||||
"uploadingFile": {
|
|
||||||
"message": "Lade Styles hoch..."
|
|
||||||
},
|
|
||||||
"usercssAvoidOverwriting": {
|
"usercssAvoidOverwriting": {
|
||||||
"message": "Bitte ändere @name oder @namespace, um das Überschreiben eines bereits existierenden Styles zu verhindern."
|
"message": "Bitte ändere @name oder @namespace, um das Überschreiben eines bereits existierenden Styles zu verhindern."
|
||||||
},
|
},
|
||||||
|
@ -1441,6 +965,9 @@
|
||||||
"usercssReplaceTemplateConfirmation": {
|
"usercssReplaceTemplateConfirmation": {
|
||||||
"message": "Ersetze das vorgegebene Template für neue UserCSS styles mit dem vorliegenden Code?"
|
"message": "Ersetze das vorgegebene Template für neue UserCSS styles mit dem vorliegenden Code?"
|
||||||
},
|
},
|
||||||
|
"usercssReplaceTemplateName": {
|
||||||
|
"message": "Ein leeres @name ersetzt das vorgegebene Template"
|
||||||
|
},
|
||||||
"usercssReplaceTemplateSectionBody": {
|
"usercssReplaceTemplateSectionBody": {
|
||||||
"message": "Quelltext hier eingeben..."
|
"message": "Quelltext hier eingeben..."
|
||||||
},
|
},
|
||||||
|
@ -1452,8 +979,5 @@
|
||||||
},
|
},
|
||||||
"writeStyleForURL": {
|
"writeStyleForURL": {
|
||||||
"message": "diese URL"
|
"message": "diese URL"
|
||||||
},
|
|
||||||
"zipStyles": {
|
|
||||||
"message": "Packe Styles..."
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,16 +1,10 @@
|
||||||
{
|
{
|
||||||
"InaccessibleFileHint": {
|
|
||||||
"message": "Το Stylus δεν έχει πρόσβαση σε κάποια αρχεία (π.χ. τα αρχεία PDF και JSON)"
|
|
||||||
},
|
|
||||||
"addStyleLabel": {
|
"addStyleLabel": {
|
||||||
"message": "Γράψτε νέο στυλ"
|
"message": "Γράψτε νέο στυλ"
|
||||||
},
|
},
|
||||||
"addStyleTitle": {
|
"addStyleTitle": {
|
||||||
"message": "Προσθήκη στυλ"
|
"message": "Προσθήκη στυλ"
|
||||||
},
|
},
|
||||||
"alphaChannel": {
|
|
||||||
"message": "Αδιαφάνεια"
|
|
||||||
},
|
|
||||||
"appliesAdd": {
|
"appliesAdd": {
|
||||||
"message": "Προσθήκη"
|
"message": "Προσθήκη"
|
||||||
},
|
},
|
||||||
|
@ -34,9 +28,6 @@
|
||||||
"appliesLabel": {
|
"appliesLabel": {
|
||||||
"message": "Ισχύει για"
|
"message": "Ισχύει για"
|
||||||
},
|
},
|
||||||
"appliesLineWidgetWarning": {
|
|
||||||
"message": "Δε λειτουργεί με minified CSS."
|
|
||||||
},
|
|
||||||
"appliesRegexpOption": {
|
"appliesRegexpOption": {
|
||||||
"message": "Διευθύνσεις URL που ταιριάζουν με την κανονική έκφραση"
|
"message": "Διευθύνσεις URL που ταιριάζουν με την κανονική έκφραση"
|
||||||
},
|
},
|
||||||
|
@ -49,167 +40,36 @@
|
||||||
"appliesToEverything": {
|
"appliesToEverything": {
|
||||||
"message": "Τα πάντα"
|
"message": "Τα πάντα"
|
||||||
},
|
},
|
||||||
"appliesUrlOption": {
|
|
||||||
"message": "διεύθυνση URL"
|
|
||||||
},
|
|
||||||
"appliesUrlPrefixOption": {
|
"appliesUrlPrefixOption": {
|
||||||
"message": "Διευθύνσεις URL που αρχίζουν με"
|
"message": "Διευθύνσεις URL που αρχίζουν με"
|
||||||
},
|
},
|
||||||
"applyAllUpdates": {
|
"applyAllUpdates": {
|
||||||
"message": "Εφαρμογή όλων των ενημερώσεων"
|
"message": "Εφαρμογή όλων των ενημερώσεων"
|
||||||
},
|
},
|
||||||
"author": {
|
|
||||||
"message": "Συντάκτης"
|
|
||||||
},
|
|
||||||
"backupButtons": {
|
|
||||||
"message": "Δημιουργήστε αντίγραφο ασφαλείας"
|
|
||||||
},
|
|
||||||
"bckpInstStyles": {
|
|
||||||
"message": "Εξαγωγή στυλ"
|
|
||||||
},
|
|
||||||
"checkAllUpdates": {
|
"checkAllUpdates": {
|
||||||
"message": "Έλεγχος όλων των στυλ για ενημερώσεις"
|
"message": "Έλεγχος όλων των στυλ για ενημερώσεις"
|
||||||
},
|
},
|
||||||
"checkAllUpdatesForce": {
|
|
||||||
"message": "Ελέγξτε πάλι, δεν επεξεργάστηκα κανένα στυλ!"
|
|
||||||
},
|
|
||||||
"checkForUpdate": {
|
"checkForUpdate": {
|
||||||
"message": "Έλεγχος για ενημερώσεις"
|
"message": "Έλεγχος για ενημερώσεις"
|
||||||
},
|
},
|
||||||
"checkingForUpdate": {
|
"checkingForUpdate": {
|
||||||
"message": "Έλεγχος..."
|
"message": "Έλεγχος..."
|
||||||
},
|
},
|
||||||
"clickToUninstall": {
|
|
||||||
"message": "Πατήστε για απεγκατάσταση"
|
|
||||||
},
|
|
||||||
"cm_autoCloseBrackets": {
|
|
||||||
"message": "Αυτόματο κλείσιμο παρενθέσεων και εισαγωγικών"
|
|
||||||
},
|
|
||||||
"cm_autocompleteOnTyping": {
|
|
||||||
"message": "Αυτόματη συμπλήρωση καθώς πληκτρολογείτε"
|
|
||||||
},
|
|
||||||
"cm_indentWithTabs": {
|
"cm_indentWithTabs": {
|
||||||
"message": "Χρήση καρτελών με έξυπνη εσοχή"
|
"message": "Χρήση καρτελών με έξυπνη εσοχή"
|
||||||
},
|
},
|
||||||
"cm_keyMap": {
|
|
||||||
"message": "Συντομεύσεις πληκτρολογίου"
|
|
||||||
},
|
|
||||||
"cm_lineWrapping": {
|
"cm_lineWrapping": {
|
||||||
"message": "Αναδίπλωση λέξεων"
|
"message": "Αναδίπλωση λέξεων"
|
||||||
},
|
},
|
||||||
"cm_matchHighlight": {
|
|
||||||
"message": "Υπογράμμιση"
|
|
||||||
},
|
|
||||||
"cm_matchHighlightSelection": {
|
|
||||||
"message": "Μόνο επιλογή"
|
|
||||||
},
|
|
||||||
"cm_resizeGripHint": {
|
|
||||||
"message": "Διπλό κλικ για μεγιστοποίηση/επαναφορά ύψους"
|
|
||||||
},
|
|
||||||
"cm_smartIndent": {
|
"cm_smartIndent": {
|
||||||
"message": "Χρήση έξυπνης εσοχής"
|
"message": "Χρήση έξυπνης εσοχής"
|
||||||
},
|
},
|
||||||
"cm_tabSize": {
|
"cm_tabSize": {
|
||||||
"message": "Μέγεθος καρτέλας"
|
"message": "Μέγεθος καρτέλας"
|
||||||
},
|
},
|
||||||
"cm_theme": {
|
|
||||||
"message": "Θέμα"
|
|
||||||
},
|
|
||||||
"configOnChange": {
|
|
||||||
"message": "στην αλλαγή"
|
|
||||||
},
|
|
||||||
"configOnChangeTooltip": {
|
|
||||||
"message": "Αυτόματη αποθήκευση και εφαρμογή αλλαγών"
|
|
||||||
},
|
|
||||||
"configureStyle": {
|
|
||||||
"message": "Ρυθμίσεις"
|
|
||||||
},
|
|
||||||
"configureStyleOnHomepage": {
|
|
||||||
"message": "Ρυθμίσεις στην ιστοσελίδα"
|
|
||||||
},
|
|
||||||
"confirmCancel": {
|
|
||||||
"message": "Άκυρο"
|
|
||||||
},
|
|
||||||
"confirmClose": {
|
|
||||||
"message": "Κλείσιμο"
|
|
||||||
},
|
|
||||||
"confirmDefault": {
|
|
||||||
"message": "Χρήση προεπιλογής"
|
|
||||||
},
|
|
||||||
"confirmDelete": {
|
|
||||||
"message": "Διαγραφή"
|
|
||||||
},
|
|
||||||
"confirmDiscardChanges": {
|
|
||||||
"message": "Απόρριψη αλλαγών;"
|
|
||||||
},
|
|
||||||
"confirmNo": {
|
|
||||||
"message": "Όχι"
|
|
||||||
},
|
|
||||||
"confirmOK": {
|
|
||||||
"message": "ΟΚ"
|
|
||||||
},
|
|
||||||
"confirmSave": {
|
|
||||||
"message": "Αποθήκευση"
|
|
||||||
},
|
|
||||||
"confirmYes": {
|
|
||||||
"message": "Ναι"
|
|
||||||
},
|
|
||||||
"connectingDropbox": {
|
|
||||||
"message": "Σύνδεση με το Dropbox..."
|
|
||||||
},
|
|
||||||
"connectingDropboxNotAllowed": {
|
|
||||||
"message": "Η σύνδεση με το Dropbox είναι διαθέσιμη μόνο σε εφαρμογές εγκατεστημένες απευθείας από το κατάστημα ιστού webstore"
|
|
||||||
},
|
|
||||||
"copied": {
|
|
||||||
"message": "Αντιγράφηκε στο πρόχειρο"
|
|
||||||
},
|
|
||||||
"copy": {
|
|
||||||
"message": "Αντιγραφή στο πρόχειρο"
|
|
||||||
},
|
|
||||||
"dateAbbrDay": {
|
|
||||||
"message": "$value$μ",
|
|
||||||
"placeholders": {
|
|
||||||
"value": {
|
|
||||||
"content": "$1"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"dateAbbrHour": {
|
|
||||||
"message": "$value$ω",
|
|
||||||
"placeholders": {
|
|
||||||
"value": {
|
|
||||||
"content": "$1"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"dateAbbrMonth": {
|
|
||||||
"message": "$value$λ",
|
|
||||||
"placeholders": {
|
|
||||||
"value": {
|
|
||||||
"content": "$1"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"dateAbbrYear": {
|
|
||||||
"message": "$value$χ",
|
|
||||||
"placeholders": {
|
|
||||||
"value": {
|
|
||||||
"content": "$1"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"dateInstalled": {
|
|
||||||
"message": "Ημερομηνία εγκατάστασης"
|
|
||||||
},
|
|
||||||
"dateUpdated": {
|
|
||||||
"message": "Ημερομηνία ενημέρωσης"
|
|
||||||
},
|
|
||||||
"dbError": {
|
"dbError": {
|
||||||
"message": "Παρουσιάστηκε σφάλμα χρησιμοποιώντας την κομψή βάση δεδομένων. Θα θέλατε να επισκεφθείτε μια ιστοσελίδα με πιθανές λύσεις;"
|
"message": "Παρουσιάστηκε σφάλμα χρησιμοποιώντας την κομψή βάση δεδομένων. Θα θέλατε να επισκεφθείτε μια ιστοσελίδα με πιθανές λύσεις;"
|
||||||
},
|
},
|
||||||
"defaultTheme": {
|
|
||||||
"message": "προεπιλογή"
|
|
||||||
},
|
|
||||||
"deleteStyleConfirm": {
|
"deleteStyleConfirm": {
|
||||||
"message": "Είστε σίγουροι ότι θέλετε να διαγράψετε αυτό το στυλ;"
|
"message": "Είστε σίγουροι ότι θέλετε να διαγράψετε αυτό το στυλ;"
|
||||||
},
|
},
|
||||||
|
@ -225,15 +85,6 @@
|
||||||
"disableStyleLabel": {
|
"disableStyleLabel": {
|
||||||
"message": "Απενεργοποίηση"
|
"message": "Απενεργοποίηση"
|
||||||
},
|
},
|
||||||
"dragDropMessage": {
|
|
||||||
"message": "Αποθέστε το αντίγραφο ασφαλείας σας οπουδήποτε σε αυτήν τη σελίδα για εισαγωγή."
|
|
||||||
},
|
|
||||||
"dragDropUsercssTabstrip": {
|
|
||||||
"message": "Για να εγκαταστήσετε το αρχείο, αποθέστε το στη λωρίδα καρτελών (την περιοχή όπου εμφανίζονται οι τίτλοι καρτελών)."
|
|
||||||
},
|
|
||||||
"editDeleteText": {
|
|
||||||
"message": "Διαγραφή"
|
|
||||||
},
|
|
||||||
"editGotoLine": {
|
"editGotoLine": {
|
||||||
"message": "Μετάβαση στη γραμμή (ή line:col)"
|
"message": "Μετάβαση στη γραμμή (ή line:col)"
|
||||||
},
|
},
|
||||||
|
@ -254,380 +105,60 @@
|
||||||
"enableStyleLabel": {
|
"enableStyleLabel": {
|
||||||
"message": "Ενεργοποίηση"
|
"message": "Ενεργοποίηση"
|
||||||
},
|
},
|
||||||
"excludeStyleByDomainLabel": {
|
"findStylesForSite": {
|
||||||
"message": "Εξαίρεση του τρέχοντος τομέα"
|
"message": "Αναζήτηση περισσότερων στυλ για αυτή την ιστοσελίδα"
|
||||||
},
|
|
||||||
"excludeStyleByUrlLabel": {
|
|
||||||
"message": "Εξαίρεση του τρέχοντος URL"
|
|
||||||
},
|
|
||||||
"exportLabel": {
|
|
||||||
"message": "Εξαγωγή"
|
|
||||||
},
|
|
||||||
"exportSavedSuccess": {
|
|
||||||
"message": "Το αρχείο αποθηκεύτηκε επιτυχώς."
|
|
||||||
},
|
|
||||||
"externalFeedback": {
|
|
||||||
"message": "Σχόλια"
|
|
||||||
},
|
|
||||||
"externalHomepage": {
|
|
||||||
"message": "Αρχική σελίδα"
|
|
||||||
},
|
|
||||||
"externalLink": {
|
|
||||||
"message": "Εξωτερική σύνδεση"
|
|
||||||
},
|
|
||||||
"externalSupport": {
|
|
||||||
"message": "Υποστήριξη"
|
|
||||||
},
|
|
||||||
"externalUsercssDocument": {
|
|
||||||
"message": "Τεκμηρίωση για Usercss"
|
|
||||||
},
|
|
||||||
"filteredStyles": {
|
|
||||||
"message": "Βλέπετε $numShown$ από 2$numTotal$ συνολικά",
|
|
||||||
"placeholders": {
|
|
||||||
"numShown": {
|
|
||||||
"content": "$1"
|
|
||||||
},
|
|
||||||
"numTotal": {
|
|
||||||
"content": "$2"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"filteredStylesAllHidden": {
|
|
||||||
"message": "Τα φίλτρα που εφαρμόζονται αυτήν τη στιγμή δεν ταιριάζουν με κανένα στυλ"
|
|
||||||
},
|
|
||||||
"findStyles": {
|
|
||||||
"message": "Εύρεση στυλ"
|
|
||||||
},
|
|
||||||
"genericAdd": {
|
|
||||||
"message": "Προσθήκη"
|
|
||||||
},
|
|
||||||
"genericClone": {
|
|
||||||
"message": "Δημιουργία αντιγράφου"
|
|
||||||
},
|
|
||||||
"genericDisabledLabel": {
|
|
||||||
"message": "Απενεργοποιημένο"
|
|
||||||
},
|
|
||||||
"genericEnabledLabel": {
|
|
||||||
"message": "Ενεργοποιημένο"
|
|
||||||
},
|
|
||||||
"genericError": {
|
|
||||||
"message": "Σφάλμα"
|
|
||||||
},
|
|
||||||
"genericHistoryLabel": {
|
|
||||||
"message": "Ιστορικό"
|
|
||||||
},
|
|
||||||
"genericNext": {
|
|
||||||
"message": "Επόμενο"
|
|
||||||
},
|
|
||||||
"genericPrevious": {
|
|
||||||
"message": "Προηγούμενο"
|
|
||||||
},
|
|
||||||
"genericResetLabel": {
|
|
||||||
"message": "Επαναφορά"
|
|
||||||
},
|
|
||||||
"genericSavedMessage": {
|
|
||||||
"message": "Αποθηκεύτηκε"
|
|
||||||
},
|
|
||||||
"genericTitle": {
|
|
||||||
"message": "Τίτλος"
|
|
||||||
},
|
|
||||||
"genericUnknown": {
|
|
||||||
"message": "Άγνωστο"
|
|
||||||
},
|
|
||||||
"gettingStyles": {
|
|
||||||
"message": "Λήψη όλων των στυλ..."
|
|
||||||
},
|
},
|
||||||
"helpAlt": {
|
"helpAlt": {
|
||||||
"message": "Βοήθεια"
|
"message": "Βοήθεια"
|
||||||
},
|
},
|
||||||
"helpKeyMapCommand": {
|
|
||||||
"message": "Πληκτρολογήστε μια εντολή"
|
|
||||||
},
|
|
||||||
"helpKeyMapHotkey": {
|
|
||||||
"message": "Πληκτρολογήστε ένα hotkey"
|
|
||||||
},
|
|
||||||
"importLabel": {
|
|
||||||
"message": "Εισαγωγή"
|
|
||||||
},
|
|
||||||
"importReplaceLabel": {
|
|
||||||
"message": "Αντικατάσταση στυλ"
|
|
||||||
},
|
|
||||||
"importReportLegendAdded": {
|
|
||||||
"message": "προστέθηκαν"
|
|
||||||
},
|
|
||||||
"importReportLegendUpdatedCode": {
|
|
||||||
"message": "ενημερωμένος κώδικας"
|
|
||||||
},
|
|
||||||
"importReportTitle": {
|
|
||||||
"message": "Η εισαγωγή στυλ τελείωσε"
|
|
||||||
},
|
|
||||||
"importReportUnchanged": {
|
|
||||||
"message": "Τίποτα δεν άλλαξε"
|
|
||||||
},
|
|
||||||
"importReportUndoneTitle": {
|
|
||||||
"message": "Η εισαγωγή έχει αναιρεθεί"
|
|
||||||
},
|
|
||||||
"installButton": {
|
|
||||||
"message": "Εγκατάσταση στυλ"
|
|
||||||
},
|
|
||||||
"installButtonInstalled": {
|
|
||||||
"message": "Το στυλ έχει εγκατασταθεί."
|
|
||||||
},
|
|
||||||
"installButtonReinstall": {
|
|
||||||
"message": "Επανεγκατάσταση στυλ"
|
|
||||||
},
|
|
||||||
"installButtonUpdate": {
|
|
||||||
"message": "Ενημέρωση στυλ"
|
|
||||||
},
|
|
||||||
"installUpdate": {
|
"installUpdate": {
|
||||||
"message": "Εγκατάσταση ενημέρωσης"
|
"message": "Εγκατάσταση ενημέρωσης"
|
||||||
},
|
},
|
||||||
"installUpdateFromLabel": {
|
|
||||||
"message": "Έλεγχος για ενημερώσεις"
|
|
||||||
},
|
|
||||||
"license": {
|
|
||||||
"message": "Άδεια χρήσης"
|
|
||||||
},
|
|
||||||
"linkGetHelp": {
|
|
||||||
"message": "Βοήθεια"
|
|
||||||
},
|
|
||||||
"linkGetStyles": {
|
|
||||||
"message": "Λήψη στυλ"
|
|
||||||
},
|
|
||||||
"linkTranslate": {
|
|
||||||
"message": "Μετάφραση"
|
|
||||||
},
|
|
||||||
"linterConfigTooltip": {
|
|
||||||
"message": "Πατήστε εδώ για να ρυθμίσετε το linter"
|
|
||||||
},
|
|
||||||
"linterIssues": {
|
|
||||||
"message": "Ζητήματα"
|
|
||||||
},
|
|
||||||
"linterJSONError": {
|
|
||||||
"message": "Μη έγκυρη μορφή JSON"
|
|
||||||
},
|
|
||||||
"linterResetMessage": {
|
|
||||||
"message": "Για αναίρεση μιας κατά λάθος επαναφοράς, πατήστε Ctrl-Z (ή Cmd-Z) στο πλαίσιο κειμένου"
|
|
||||||
},
|
|
||||||
"manageFilters": {
|
"manageFilters": {
|
||||||
"message": "Φίλτρα"
|
"message": "Φίλτρα"
|
||||||
},
|
},
|
||||||
"manageHeading": {
|
"manageHeading": {
|
||||||
"message": "Εγκατεστημένα Στυλ"
|
"message": "Εγκατεστημένα Στυλ"
|
||||||
},
|
},
|
||||||
"manageNewUI": {
|
|
||||||
"message": "Νέα διαχείριση διάταξης UI"
|
|
||||||
},
|
|
||||||
"manageOnlyDisabled": {
|
|
||||||
"message": "Μόνο απενεργοποιημένα στυλ"
|
|
||||||
},
|
|
||||||
"manageOnlyEnabled": {
|
"manageOnlyEnabled": {
|
||||||
"message": "Μόνο ενεργοποιημένα στυλ"
|
"message": "Μόνο ενεργοποιημένα στυλ"
|
||||||
},
|
},
|
||||||
"manageOnlyExternal": {
|
|
||||||
"message": "Μόνο στυλ από άλλες ιστοσελίδες"
|
|
||||||
},
|
|
||||||
"manageOnlyLocal": {
|
|
||||||
"message": "Μόνο στυλ δημιουργημένα τοπικά"
|
|
||||||
},
|
|
||||||
"manageTitle": {
|
"manageTitle": {
|
||||||
"message": "Κομψή"
|
"message": "Κομψή"
|
||||||
},
|
},
|
||||||
"menuShowBadge": {
|
"menuShowBadge": {
|
||||||
"message": "Εμφάνιση ενεργους καταμέτρησης στυλ"
|
"message": "Εμφάνιση ενεργους καταμέτρησης στυλ"
|
||||||
},
|
},
|
||||||
"noFileToImport": {
|
|
||||||
"message": "Για να εισάγετε τα στυλ σας, πρέπει πρώτα να τα εξάγετε."
|
|
||||||
},
|
|
||||||
"noStylesForSite": {
|
"noStylesForSite": {
|
||||||
"message": "Δεν υπάρχουν εγκατεστημένα στυλ για αυτή την ιστοσελίδα."
|
"message": "Δεν υπάρχουν εγκατεστημένα στυλ για αυτή την ιστοσελίδα."
|
||||||
},
|
},
|
||||||
"openManage": {
|
"openManage": {
|
||||||
"message": "Διαχείριση εγκατεστημένων στυλ"
|
"message": "Διαχείριση εγκατεστημένων στυλ"
|
||||||
},
|
},
|
||||||
"openOptions": {
|
|
||||||
"message": "Επιλογές"
|
|
||||||
},
|
|
||||||
"openStylesManager": {
|
|
||||||
"message": "Άνοιγμα διαχείρισης στυλ"
|
|
||||||
},
|
|
||||||
"optionsActions": {
|
|
||||||
"message": "Ενέργειες"
|
|
||||||
},
|
|
||||||
"optionsAdvanced": {
|
|
||||||
"message": "Για προχωρημένους"
|
|
||||||
},
|
|
||||||
"optionsAdvancedContextDelete": {
|
|
||||||
"message": "Προσθήκη του 'Delete' στο μενού περιβάλλοντος του προγράμματος επεξεργασίας"
|
|
||||||
},
|
|
||||||
"optionsBadgeDisabled": {
|
|
||||||
"message": "Χρώμα φόντου όταν είναι απενεργοποιημένο"
|
|
||||||
},
|
|
||||||
"optionsBadgeNormal": {
|
|
||||||
"message": "Χρώμα υποβάθρου"
|
|
||||||
},
|
|
||||||
"optionsCheck": {
|
|
||||||
"message": "Ενημέρωση στυλ"
|
|
||||||
},
|
|
||||||
"optionsCheckUpdate": {
|
|
||||||
"message": "Έλεγχος και εγκατάσταση διαθέσιμων ενημερώσεων"
|
|
||||||
},
|
|
||||||
"optionsCustomizeBadge": {
|
|
||||||
"message": "Σήμα στο εικονίδιο της γραμμής εργαλείων"
|
|
||||||
},
|
|
||||||
"optionsCustomizePopup": {
|
|
||||||
"message": "Αναδυόμενο παράθυρο"
|
|
||||||
},
|
|
||||||
"optionsCustomizeUpdate": {
|
|
||||||
"message": "Ενημερώσεις"
|
|
||||||
},
|
|
||||||
"optionsHeading": {
|
"optionsHeading": {
|
||||||
"message": "Επιλογές"
|
"message": "Επιλογές"
|
||||||
},
|
},
|
||||||
"optionsIconDark": {
|
|
||||||
"message": "Σκούρο θέμα φυλλομετρητή"
|
|
||||||
},
|
|
||||||
"optionsOpen": {
|
|
||||||
"message": "Άνοιγμα"
|
|
||||||
},
|
|
||||||
"optionsOpenManager": {
|
|
||||||
"message": "Διαχείριση στυλ"
|
|
||||||
},
|
|
||||||
"optionsPopupWidth": {
|
|
||||||
"message": "Πλάτος αναδυόμενου παραθύρου (σε pixels)"
|
|
||||||
},
|
|
||||||
"optionsReset": {
|
|
||||||
"message": "Επαναφορά ρυθμίσεων στις προεπιλεγμένες"
|
|
||||||
},
|
|
||||||
"optionsResetButton": {
|
|
||||||
"message": "Επαναφορά επιλογών"
|
|
||||||
},
|
|
||||||
"optionsSubheading": {
|
|
||||||
"message": "Περισσότερες επιλογές"
|
|
||||||
},
|
|
||||||
"optionsSyncConnect": {
|
|
||||||
"message": "Σύνδεση"
|
|
||||||
},
|
|
||||||
"optionsSyncDisconnect": {
|
|
||||||
"message": "Αποσύνδεση"
|
|
||||||
},
|
|
||||||
"optionsSyncStatusConnected": {
|
|
||||||
"message": "Συνδεδεμένο"
|
|
||||||
},
|
|
||||||
"optionsSyncStatusConnecting": {
|
|
||||||
"message": "Σύνδεση..."
|
|
||||||
},
|
|
||||||
"optionsSyncStatusDisconnected": {
|
|
||||||
"message": "Αποσυνδέθηκε"
|
|
||||||
},
|
|
||||||
"optionsSyncStatusDisconnecting": {
|
|
||||||
"message": "Αποσύνδεση..."
|
|
||||||
},
|
|
||||||
"optionsSyncStatusSyncing": {
|
|
||||||
"message": "Συγχρονισμός ..."
|
|
||||||
},
|
|
||||||
"optionsSyncSyncNow": {
|
|
||||||
"message": "Συγχρονισμός τώρα"
|
|
||||||
},
|
|
||||||
"optionsSyncUrl": {
|
|
||||||
"message": "διεύθυνση URL"
|
|
||||||
},
|
|
||||||
"optionsUpdateInterval": {
|
|
||||||
"message": "Διάστημα αυτόματης ενημέρωσης των στυλ σε ώρες (0 για απενεργοποίηση)"
|
|
||||||
},
|
|
||||||
"paginationNext": {
|
|
||||||
"message": "Επόμενη σελίδα"
|
|
||||||
},
|
|
||||||
"paginationPrevious": {
|
|
||||||
"message": "Προηγούμενη σελίδα"
|
|
||||||
},
|
|
||||||
"popupBordersTooltip": {
|
|
||||||
"message": "Χρήσιμο για σκούρα θέματα στο καινούριο Chrome, καθώς δε βάφει πλέον τα ακριανά περιθώρια."
|
|
||||||
},
|
|
||||||
"popupOpenEditInPopup": {
|
|
||||||
"message": "Χρήση ενός απλού παραθύρου (χωρίς omnibox)"
|
|
||||||
},
|
|
||||||
"popupOpenEditInWindow": {
|
|
||||||
"message": "Άνοιγμα επεξαργαστή σε νέο παράθυρο"
|
|
||||||
},
|
|
||||||
"popupStylesFirst": {
|
"popupStylesFirst": {
|
||||||
"message": "Στυλ λίστας πριν των εντολών στο μενού του κουμπιού γραμμής εργαλείων"
|
"message": "Στυλ λίστας πριν των εντολών στο μενού του κουμπιού γραμμής εργαλείων"
|
||||||
},
|
},
|
||||||
"prefShowBadge": {
|
"prefShowBadge": {
|
||||||
"message": "Εμφάνιση αριθμού των στυλ που δραστηριοποιούνται για την τρέχουσα τοποθεσία στην μπάρα εργαλείων"
|
"message": "Εμφάνιση αριθμού των στυλ που δραστηριοποιούνται για την τρέχουσα τοποθεσία στην μπάρα εργαλείων"
|
||||||
},
|
},
|
||||||
"readingStyles": {
|
|
||||||
"message": "Ανάγνωση στυλ..."
|
|
||||||
},
|
|
||||||
"replace": {
|
|
||||||
"message": "Αντικατάσταση"
|
|
||||||
},
|
|
||||||
"replaceAll": {
|
|
||||||
"message": "Αντικατάσταση όλων"
|
|
||||||
},
|
|
||||||
"replaceWith": {
|
|
||||||
"message": "Αντικατάσταση με"
|
|
||||||
},
|
|
||||||
"retrieveBckp": {
|
|
||||||
"message": "Εισαγωγή στυλ"
|
|
||||||
},
|
|
||||||
"retrieveDropboxSync": {
|
|
||||||
"message": "Εισαγωγή από το Dropbox"
|
|
||||||
},
|
|
||||||
"search": {
|
|
||||||
"message": "Αναζήτηση"
|
|
||||||
},
|
|
||||||
"searchGlobalStyles": {
|
|
||||||
"message": "Επίσης, αναζητήστε καθολικά στυλ"
|
|
||||||
},
|
|
||||||
"searchRegexp": {
|
|
||||||
"message": "Χρησιμοποιήστε τη σύνταξη /re/ για αναζήτηση με regexp."
|
|
||||||
},
|
|
||||||
"searchResultInstallCount": {
|
|
||||||
"message": "Συνολικός αριθμός εγκαταστάσεων"
|
|
||||||
},
|
|
||||||
"searchResultUpdated": {
|
|
||||||
"message": "Ενημερωμένο"
|
|
||||||
},
|
|
||||||
"searchResultWeeklyCount": {
|
|
||||||
"message": "Εβδομαδιαίος αριθμός εγκαταστάσεων"
|
|
||||||
},
|
|
||||||
"searchStylesName": {
|
|
||||||
"message": "Όνομα"
|
|
||||||
},
|
|
||||||
"sectionAdd": {
|
"sectionAdd": {
|
||||||
"message": "Προσθήκη ένος άλλου τμήματος"
|
"message": "Προσθήκη ένος άλλου τμήματος"
|
||||||
},
|
},
|
||||||
"sectionCode": {
|
"sectionCode": {
|
||||||
"message": "Κώδικας"
|
"message": "Κώδικας"
|
||||||
},
|
},
|
||||||
|
"sectionHelp": {
|
||||||
|
"message": "Ενότητες σας επιτρέπουν να ορίσετε διαφορετικά κομμάτια του κώδικα για να εφαρμόζονται σε διαφορετικά σύνολα των διευθύνσεων URL στο ίδιο στυλ. Για παράδειγμα, ένα ενιαίο ύφος θα μπορούσε να αλλάξει την αρχική σελίδα ενός ιστότοπου με έναν τρόπο, ενώ αλλάζει το υπόλοιπο μιας τοποθεσίας ένας άλλος τρόπος."
|
||||||
|
},
|
||||||
"sectionRemove": {
|
"sectionRemove": {
|
||||||
"message": "Αφαίρεση ενότητας"
|
"message": "Αφαίρεση ενότητας"
|
||||||
},
|
},
|
||||||
"sections": {
|
|
||||||
"message": "Ενότητες"
|
|
||||||
},
|
|
||||||
"shortcuts": {
|
|
||||||
"message": "Συντομεύσεις"
|
|
||||||
},
|
|
||||||
"sortDateNewestFirst": {
|
|
||||||
"message": "πιο πρόσφατα πρώτα"
|
|
||||||
},
|
|
||||||
"sortDateOldestFirst": {
|
|
||||||
"message": "πιο παλιά πρώτα"
|
|
||||||
},
|
|
||||||
"styleBadRegexp": {
|
"styleBadRegexp": {
|
||||||
"message": "Το Regexp δεν είναι έγκυρο."
|
"message": "Το Regexp δεν είναι έγκυρο."
|
||||||
},
|
},
|
||||||
"styleBeautify": {
|
|
||||||
"message": "Ωραιοποίηση"
|
|
||||||
},
|
|
||||||
"styleBeautifyIndentConditional": {
|
|
||||||
"message": "Διόρθωση εσοχής για @media και @supports"
|
|
||||||
},
|
|
||||||
"styleBeautifyPreserveNewlines": {
|
|
||||||
"message": "Διατήρηση νέων γραμμών (newlines)"
|
|
||||||
},
|
|
||||||
"styleCancelEditLabel": {
|
"styleCancelEditLabel": {
|
||||||
"message": "Πίσω στη διαχείριση"
|
"message": "Πίσω στη διαχείριση"
|
||||||
},
|
},
|
||||||
|
@ -648,12 +179,12 @@
|
||||||
"styleMissingName": {
|
"styleMissingName": {
|
||||||
"message": "Εισάγετε ένα όνομα"
|
"message": "Εισάγετε ένα όνομα"
|
||||||
},
|
},
|
||||||
"styleRegexpTestNone": {
|
|
||||||
"message": "Δε βρέθηκαν καρτέλες που αντιστοιχούν."
|
|
||||||
},
|
|
||||||
"styleSaveLabel": {
|
"styleSaveLabel": {
|
||||||
"message": "Αποθήκευση"
|
"message": "Αποθήκευση"
|
||||||
},
|
},
|
||||||
|
"styleSectionsTitle": {
|
||||||
|
"message": "Ενότητες"
|
||||||
|
},
|
||||||
"styleToMozillaFormatHelp": {
|
"styleToMozillaFormatHelp": {
|
||||||
"message": "Η μορφή του Mozilla κώδικα μπορεί να χρησιμοποιηθεί με το Stylish για το Firefox και μπορεί να υποβληθεί στο userstyles.org."
|
"message": "Η μορφή του Mozilla κώδικα μπορεί να χρησιμοποιηθεί με το Stylish για το Firefox και μπορεί να υποβληθεί στο userstyles.org."
|
||||||
},
|
},
|
||||||
|
@ -668,36 +199,9 @@
|
||||||
"stylusUnavailableForURL": {
|
"stylusUnavailableForURL": {
|
||||||
"message": "To Stylus δεν λειτουργεί σε σελίδες όπως αυτή."
|
"message": "To Stylus δεν λειτουργεί σε σελίδες όπως αυτή."
|
||||||
},
|
},
|
||||||
"stylusUnavailableForURLdetails": {
|
|
||||||
"message": "Ως μέτρο ασφαλείας, ο φυλλομετρητής απαγορεύει στα πρόσθετα να επέμβουν στις built-in σελίδες του (όπως π.χ. chrome://version, η σελίδα νέας καρτέλας από το Chrome 61 και μετά, about:addons, κλπ.), καθώς και τις σελίδες άλλωων προσθέτων. Επιπλέον, κάθε φυλλομετρητής περιορίζει την πρόσβαση στο κατάστημα προσθέτων (όπως το Chrome Web Store ή το AMO)."
|
|
||||||
},
|
|
||||||
"syncDropboxStyles": {
|
|
||||||
"message": "Εξαγωγή από το Dropbox"
|
|
||||||
},
|
|
||||||
"syncError": {
|
|
||||||
"message": "Ο συγχρονισμός απέτυχε"
|
|
||||||
},
|
|
||||||
"toggleStyle": {
|
|
||||||
"message": "Αλλαγή στυλ"
|
|
||||||
},
|
|
||||||
"undo": {
|
|
||||||
"message": "Αναίρεση"
|
|
||||||
},
|
|
||||||
"undoGlobal": {
|
|
||||||
"message": "Αναίρεση όλων των ενεργειών"
|
|
||||||
},
|
|
||||||
"unreachableFileHint": {
|
|
||||||
"message": "Το Stylus έχει πρόσβαση στις file:// διευθύνσεις URL μόνο αν έχετε επιλέξει το αντίστοιχο πλαίσιο ελέγχου για το πρόσθετο Stylus στη σελίδα chrome://extensions."
|
|
||||||
},
|
|
||||||
"unzipStyles": {
|
|
||||||
"message": "Αποσυμπίεση στυλ..."
|
|
||||||
},
|
|
||||||
"updateAllCheckSucceededNoUpdate": {
|
"updateAllCheckSucceededNoUpdate": {
|
||||||
"message": "Όλα τα στυλ είναι ενημερωμένα."
|
"message": "Όλα τα στυλ είναι ενημερωμένα."
|
||||||
},
|
},
|
||||||
"updateAllCheckSucceededSomeEdited": {
|
|
||||||
"message": "Δεν έχει γίνει έλεγχος ενημερώσεων για κάποια στυλ, για να αποφευχθεί η πιθανότητα απώλειας τοπικών επεξεργασιών. Οι ενημερώσεις μπορούν να εξαναγκαστούν ελέγχοντας το κάθε στυλ ξεχωριστά ή ελέγχοντας πάλι όλα τα στυλ (τοπικές επεξεργασίες θα αντικατασταθούν)"
|
|
||||||
},
|
|
||||||
"updateCheckFailBadResponseCode": {
|
"updateCheckFailBadResponseCode": {
|
||||||
"message": "Αποτυχία ενημέρωσης: ο διακομιστής ανταποκρίθηκε με κωδικό $code$.",
|
"message": "Αποτυχία ενημέρωσης: ο διακομιστής ανταποκρίθηκε με κωδικό $code$.",
|
||||||
"placeholders": {
|
"placeholders": {
|
||||||
|
@ -709,37 +213,16 @@
|
||||||
"updateCheckFailServerUnreachable": {
|
"updateCheckFailServerUnreachable": {
|
||||||
"message": "Αποτυχία ενημέρωσης: απρόσιτος διακομιστής."
|
"message": "Αποτυχία ενημέρωσης: απρόσιτος διακομιστής."
|
||||||
},
|
},
|
||||||
"updateCheckSkippedLocallyEdited": {
|
|
||||||
"message": "Το στυλ επεξεργάστηκε τοπικά στον υπολογιστή σας."
|
|
||||||
},
|
|
||||||
"updateCheckSkippedMaybeLocallyEdited": {
|
|
||||||
"message": "Το στυλ αυτό μπορεί να έχει επεξεργαστεί τοπικά στον υπολογιστή σας."
|
|
||||||
},
|
|
||||||
"updateCheckSucceededNoUpdate": {
|
"updateCheckSucceededNoUpdate": {
|
||||||
"message": "Το στυλ είναι ενημερωμένο."
|
"message": "Το στυλ είναι ενημερωμένο."
|
||||||
},
|
},
|
||||||
"updateCompleted": {
|
"updateCompleted": {
|
||||||
"message": "Η ενημέρωση ολοκληρώθηκε."
|
"message": "Η ενημέρωση ολοκληρώθηκε."
|
||||||
},
|
},
|
||||||
"updatesCurrentlyInstalled": {
|
|
||||||
"message": "Ενημερώσεις που εγκαταστάθηκαν"
|
|
||||||
},
|
|
||||||
"uploadingFile": {
|
|
||||||
"message": "Μεταφόρτωση αρχείου..."
|
|
||||||
},
|
|
||||||
"usercssEditorNamePlaceholder": {
|
|
||||||
"message": "Καθορίστε το @name στον κώδικα"
|
|
||||||
},
|
|
||||||
"versionInvalidOlder": {
|
|
||||||
"message": "Η έκδοση αυτή είναι παλαιότερη από αυτήν που είναι ήδη εγκατεστημένη."
|
|
||||||
},
|
|
||||||
"writeStyleFor": {
|
"writeStyleFor": {
|
||||||
"message": "Γράψτε νέο στυλ για:"
|
"message": "Γράψτε νέο στυλ για:"
|
||||||
},
|
},
|
||||||
"writeStyleForURL": {
|
"writeStyleForURL": {
|
||||||
"message": "αυτή την διεύθυνση URL"
|
"message": "αυτή την διεύθυνση URL"
|
||||||
},
|
|
||||||
"zipStyles": {
|
|
||||||
"message": "Συμπίεση στυλ..."
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -26,6 +26,9 @@
|
||||||
"editStyleHeading": {
|
"editStyleHeading": {
|
||||||
"message": "Edit style"
|
"message": "Edit style"
|
||||||
},
|
},
|
||||||
|
"installUpdateUnavailable": {
|
||||||
|
"message": "To enable checking for updates, drop the file on the tab strip or specify @updateURL in the style metadata."
|
||||||
|
},
|
||||||
"license": {
|
"license": {
|
||||||
"message": "Licence"
|
"message": "Licence"
|
||||||
},
|
},
|
||||||
|
@ -52,6 +55,14 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"styleMetaErrorColor": {
|
||||||
|
"message": "$color$ is not a valid colour",
|
||||||
|
"placeholders": {
|
||||||
|
"color": {
|
||||||
|
"content": "$1"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"styleRegexpPartialExplanation": {
|
"styleRegexpPartialExplanation": {
|
||||||
"message": "This style uses partially matching regexps in violation of <a href='https://developer.mozilla.org/docs/Web/CSS/@document'>CSS4 @document specification</a> which requires a full URL match. The affected CSS sections were not applied to the page. This style was probably created in Stylish-for-Chrome, which incorrectly checks 'regexp()' rules since the very first version (known bug)."
|
"message": "This style uses partially matching regexps in violation of <a href='https://developer.mozilla.org/docs/Web/CSS/@document'>CSS4 @document specification</a> which requires a full URL match. The affected CSS sections were not applied to the page. This style was probably created in Stylish-for-Chrome, which incorrectly checks 'regexp()' rules since the very first version (known bug)."
|
||||||
},
|
},
|
||||||
|
|
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
|
@ -1,7 +1,4 @@
|
||||||
{
|
{
|
||||||
"InaccessibleFileHint": {
|
|
||||||
"message": "Stylus ei saa osadele failitüüpidele ligi pääseda (nt. pdf & json failid)."
|
|
||||||
},
|
|
||||||
"addStyleLabel": {
|
"addStyleLabel": {
|
||||||
"message": "Kirjuta uus stiil"
|
"message": "Kirjuta uus stiil"
|
||||||
},
|
},
|
||||||
|
@ -67,6 +64,9 @@
|
||||||
"backupButtons": {
|
"backupButtons": {
|
||||||
"message": "Varunda"
|
"message": "Varunda"
|
||||||
},
|
},
|
||||||
|
"backupMessage": {
|
||||||
|
"message": "Vali fail või lohista see siia lehele."
|
||||||
|
},
|
||||||
"bckpInstStyles": {
|
"bckpInstStyles": {
|
||||||
"message": "Ekspordi stiilid"
|
"message": "Ekspordi stiilid"
|
||||||
},
|
},
|
||||||
|
@ -178,18 +178,6 @@
|
||||||
"confirmYes": {
|
"confirmYes": {
|
||||||
"message": "Jah"
|
"message": "Jah"
|
||||||
},
|
},
|
||||||
"connectingDropbox": {
|
|
||||||
"message": "Dropboxiga ühendumine..."
|
|
||||||
},
|
|
||||||
"connectingDropboxNotAllowed": {
|
|
||||||
"message": "Dropboxiga ühendumine on saadaval ainult veebipoe kaudu installitud rakendustes"
|
|
||||||
},
|
|
||||||
"copied": {
|
|
||||||
"message": "Kopeeritud lõikelauale"
|
|
||||||
},
|
|
||||||
"copy": {
|
|
||||||
"message": "Kopeeri lõikelauale"
|
|
||||||
},
|
|
||||||
"dateInstalled": {
|
"dateInstalled": {
|
||||||
"message": "Paigaldamise kuupäev"
|
"message": "Paigaldamise kuupäev"
|
||||||
},
|
},
|
||||||
|
@ -220,9 +208,6 @@
|
||||||
"dragDropMessage": {
|
"dragDropMessage": {
|
||||||
"message": "Lohista importimiseks oma varundusfail kuskile siia lehele."
|
"message": "Lohista importimiseks oma varundusfail kuskile siia lehele."
|
||||||
},
|
},
|
||||||
"dragDropUsercssTabstrip": {
|
|
||||||
"message": "Faili installimiseks lohista see kaartide ribale (ala, kus näidatakse kaartide pealkirju)."
|
|
||||||
},
|
|
||||||
"editDeleteText": {
|
"editDeleteText": {
|
||||||
"message": "Kustuta"
|
"message": "Kustuta"
|
||||||
},
|
},
|
||||||
|
@ -243,21 +228,15 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"editorStylesButton": {
|
||||||
|
"message": "Leia redaktori stiile"
|
||||||
|
},
|
||||||
"enableStyleLabel": {
|
"enableStyleLabel": {
|
||||||
"message": "Luba"
|
"message": "Luba"
|
||||||
},
|
},
|
||||||
"excludeStyleByDomainLabel": {
|
|
||||||
"message": "Välista praegune domeen"
|
|
||||||
},
|
|
||||||
"excludeStyleByUrlLabel": {
|
|
||||||
"message": "Välista praegune URL"
|
|
||||||
},
|
|
||||||
"exportLabel": {
|
"exportLabel": {
|
||||||
"message": "Ekspordi"
|
"message": "Ekspordi"
|
||||||
},
|
},
|
||||||
"exportSavedSuccess": {
|
|
||||||
"message": "Fail edukalt salvestatud"
|
|
||||||
},
|
|
||||||
"externalFeedback": {
|
"externalFeedback": {
|
||||||
"message": "Tagasiside"
|
"message": "Tagasiside"
|
||||||
},
|
},
|
||||||
|
@ -290,6 +269,15 @@
|
||||||
"findStyles": {
|
"findStyles": {
|
||||||
"message": "Leia stiile"
|
"message": "Leia stiile"
|
||||||
},
|
},
|
||||||
|
"findStylesForSite": {
|
||||||
|
"message": "Leia sellele saidile veel stiile"
|
||||||
|
},
|
||||||
|
"findStylesInline": {
|
||||||
|
"message": "Aknasisene"
|
||||||
|
},
|
||||||
|
"findStylesInlineTooltip": {
|
||||||
|
"message": "Näita otsingu tulemusi selles aknas."
|
||||||
|
},
|
||||||
"genericAdd": {
|
"genericAdd": {
|
||||||
"message": "Lisa"
|
"message": "Lisa"
|
||||||
},
|
},
|
||||||
|
@ -326,9 +314,6 @@
|
||||||
"genericUnknown": {
|
"genericUnknown": {
|
||||||
"message": "Tundmatu"
|
"message": "Tundmatu"
|
||||||
},
|
},
|
||||||
"gettingStyles": {
|
|
||||||
"message": "Kõigi stiilide hankimine..."
|
|
||||||
},
|
|
||||||
"helpAlt": {
|
"helpAlt": {
|
||||||
"message": "Abi"
|
"message": "Abi"
|
||||||
},
|
},
|
||||||
|
@ -338,9 +323,6 @@
|
||||||
"helpKeyMapHotkey": {
|
"helpKeyMapHotkey": {
|
||||||
"message": "Vajuta kiirklahvi"
|
"message": "Vajuta kiirklahvi"
|
||||||
},
|
},
|
||||||
"hostDisabled": {
|
|
||||||
"message": "See host on keelatud hetkel kasutatavas brauseri praeguses versioonis oleva vea tõttu"
|
|
||||||
},
|
|
||||||
"importAppendLabel": {
|
"importAppendLabel": {
|
||||||
"message": "Lisa stiilile"
|
"message": "Lisa stiilile"
|
||||||
},
|
},
|
||||||
|
@ -412,6 +394,9 @@
|
||||||
"installUpdateFromLabel": {
|
"installUpdateFromLabel": {
|
||||||
"message": "Kontrolli uuendusi"
|
"message": "Kontrolli uuendusi"
|
||||||
},
|
},
|
||||||
|
"installUpdateUnavailable": {
|
||||||
|
"message": "Uuenduste kontrollimise lubamiseks lohista failid kaartide ribale või määratle stiili metaandmetes @updateURL."
|
||||||
|
},
|
||||||
"license": {
|
"license": {
|
||||||
"message": "Litsents"
|
"message": "Litsents"
|
||||||
},
|
},
|
||||||
|
@ -475,6 +460,9 @@
|
||||||
"liveReloadError": {
|
"liveReloadError": {
|
||||||
"message": "Faili vaatamisel esines viga"
|
"message": "Faili vaatamisel esines viga"
|
||||||
},
|
},
|
||||||
|
"liveReloadInstallHint": {
|
||||||
|
"message": "Reaalajas uuestilaadimine on lubatud, seega paigaldatud stiili uuendatakse väliste muudatuste korral automaatselt, kuniks see kaart ja lähtefaili kaart mõlemad lahti on."
|
||||||
|
},
|
||||||
"liveReloadLabel": {
|
"liveReloadLabel": {
|
||||||
"message": "Reaalajas uuestilaadimine"
|
"message": "Reaalajas uuestilaadimine"
|
||||||
},
|
},
|
||||||
|
@ -485,7 +473,7 @@
|
||||||
"message": "Tee halliks"
|
"message": "Tee halliks"
|
||||||
},
|
},
|
||||||
"manageFaviconsHelp": {
|
"manageFaviconsHelp": {
|
||||||
"message": "Stylus kasutab välist teenust https://icons.duckduckgo.com"
|
"message": "Stylus kasutab välist teenust https://www.google.com/s2/favicons"
|
||||||
},
|
},
|
||||||
"manageFilters": {
|
"manageFilters": {
|
||||||
"message": "Filtrid"
|
"message": "Filtrid"
|
||||||
|
@ -521,7 +509,7 @@
|
||||||
"message": "Ainult mitte-Usercss stiilid"
|
"message": "Ainult mitte-Usercss stiilid"
|
||||||
},
|
},
|
||||||
"manageOnlyUpdates": {
|
"manageOnlyUpdates": {
|
||||||
"message": "Ainult uuenduste või vigadega"
|
"message": "Ainult uuenduste ja vigadega"
|
||||||
},
|
},
|
||||||
"manageOnlyUsercss": {
|
"manageOnlyUsercss": {
|
||||||
"message": "Ainult Usercss stiilid"
|
"message": "Ainult Usercss stiilid"
|
||||||
|
@ -529,121 +517,16 @@
|
||||||
"menuShowBadge": {
|
"menuShowBadge": {
|
||||||
"message": "Kuva aktiivsete stiilide hulka"
|
"message": "Kuva aktiivsete stiilide hulka"
|
||||||
},
|
},
|
||||||
"meta_invalidCheckboxDefault": {
|
|
||||||
"message": "Vigane @var märkeruut: väärtus peab olema 0 või 1"
|
|
||||||
},
|
|
||||||
"meta_invalidColor": {
|
|
||||||
"message": "Vigane @var värv: $color$ pole värv",
|
|
||||||
"placeholders": {
|
|
||||||
"color": {
|
|
||||||
"content": "$1"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"meta_invalidRange": {
|
|
||||||
"message": "Vigane @var $type$: väärtus peab olema arv või massiiv",
|
|
||||||
"placeholders": {
|
|
||||||
"type": {
|
|
||||||
"content": "$1"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"meta_invalidRangeMax": {
|
|
||||||
"message": "Vigane @var $type$: vaikimisi väärtus on maksimumist suurem",
|
|
||||||
"placeholders": {
|
|
||||||
"type": {
|
|
||||||
"content": "$1"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"meta_invalidRangeMin": {
|
|
||||||
"message": "Vigane @var $type$: vaikimisi väärtus on miinimumist madalam",
|
|
||||||
"placeholders": {
|
|
||||||
"type": {
|
|
||||||
"content": "$1"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"meta_invalidRangeMultipleUnits": {
|
|
||||||
"message": "Vigane @var $type$: määratletud on mitu ühikut",
|
|
||||||
"placeholders": {
|
|
||||||
"type": {
|
|
||||||
"content": "$1"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"meta_invalidRangeTooManyValues": {
|
|
||||||
"message": "Vigane @var $type$: massiiv sisaldab liiga palju elemente",
|
|
||||||
"placeholders": {
|
|
||||||
"type": {
|
|
||||||
"content": "$1"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"meta_invalidRangeUnits": {
|
|
||||||
"message": "Vigane @var $type$: '$units$' pole lubatud ühik",
|
|
||||||
"placeholders": {
|
|
||||||
"type": {
|
|
||||||
"content": "$1"
|
|
||||||
},
|
|
||||||
"units": {
|
|
||||||
"content": "$2"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"meta_invalidURLProtocol": {
|
|
||||||
"message": "Vigane URL protokoll. Lubatud on ainult http ja https: $protocol$",
|
|
||||||
"placeholders": {
|
|
||||||
"protocol": {
|
|
||||||
"content": "$1"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"meta_missingMandatory": {
|
|
||||||
"message": "Kohustuslikud metaandmed puudu: $keys$",
|
|
||||||
"placeholders": {
|
|
||||||
"keys": {
|
|
||||||
"content": "$1"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"meta_unknownMeta": {
|
|
||||||
"message": "Tundmatud metaandmed: $key$",
|
|
||||||
"placeholders": {
|
|
||||||
"key": {
|
|
||||||
"content": "$1"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"meta_unknownPreprocessor": {
|
|
||||||
"message": "Tundmatu @preprocessor: $preprocessor$",
|
|
||||||
"placeholders": {
|
|
||||||
"preprocessor": {
|
|
||||||
"content": "$1"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"meta_unknownVarType": {
|
|
||||||
"message": "Tundmatu @$varkey$ tüüp: $vartype$",
|
|
||||||
"placeholders": {
|
|
||||||
"varkey": {
|
|
||||||
"content": "$1"
|
|
||||||
},
|
|
||||||
"vartype": {
|
|
||||||
"content": "$2"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"noFileToImport": {
|
|
||||||
"message": "Oma stiilide importimiseks peaksid esmalt selle eksportima."
|
|
||||||
},
|
|
||||||
"noStylesForSite": {
|
"noStylesForSite": {
|
||||||
"message": "Sellele saidile pole stiile paigaldatud."
|
"message": "Sellele saidile pole stiile paigaldatud."
|
||||||
},
|
},
|
||||||
"openManage": {
|
"openManage": {
|
||||||
"message": "Halda"
|
"message": "Halda"
|
||||||
},
|
},
|
||||||
"openOptions": {
|
"openOptionsManage": {
|
||||||
|
"message": "Valikute liides"
|
||||||
|
},
|
||||||
|
"openOptionsPopup": {
|
||||||
"message": "Valikud"
|
"message": "Valikud"
|
||||||
},
|
},
|
||||||
"openStylesManager": {
|
"openStylesManager": {
|
||||||
|
@ -662,7 +545,7 @@
|
||||||
"message": "Paljasta iframe-id HTML[stylus-iframe] kaudu"
|
"message": "Paljasta iframe-id HTML[stylus-iframe] kaudu"
|
||||||
},
|
},
|
||||||
"optionsAdvancedExposeIframesNote": {
|
"optionsAdvancedExposeIframesNote": {
|
||||||
"message": "Paljastab tipp-domeeni igas iframe'is.\nVõimaldab iframe'i-põhise CSS-koodi kirjutamise näiteks selliselt:\nhtml[stylus-iframe$$=\"twitter.com\"] h1 { display:none }"
|
"message": "Paljastab tipp-domeeni igas iframe'is.\nVõimaldab iframe'i-põhise CSS-koodi kirjutamise näiteks selliselt:\nhtml[stylus-iframe$=\"twitter.com\"] h1 { display:none }"
|
||||||
},
|
},
|
||||||
"optionsAdvancedNewStyleAsUsercss": {
|
"optionsAdvancedNewStyleAsUsercss": {
|
||||||
"message": "Kirjuta uus stiil usercss-ina"
|
"message": "Kirjuta uus stiil usercss-ina"
|
||||||
|
@ -688,9 +571,6 @@
|
||||||
"optionsCustomizePopup": {
|
"optionsCustomizePopup": {
|
||||||
"message": "Hüpikaken"
|
"message": "Hüpikaken"
|
||||||
},
|
},
|
||||||
"optionsCustomizeSync": {
|
|
||||||
"message": "Sünkrooni pilve"
|
|
||||||
},
|
|
||||||
"optionsCustomizeUpdate": {
|
"optionsCustomizeUpdate": {
|
||||||
"message": "Uuendused"
|
"message": "Uuendused"
|
||||||
},
|
},
|
||||||
|
@ -721,36 +601,12 @@
|
||||||
"optionsSubheading": {
|
"optionsSubheading": {
|
||||||
"message": "Rohkem valikuid"
|
"message": "Rohkem valikuid"
|
||||||
},
|
},
|
||||||
"optionsSyncNone": {
|
|
||||||
"message": "Pole"
|
|
||||||
},
|
|
||||||
"optionsSyncStatusConnected": {
|
|
||||||
"message": "Ühendatud"
|
|
||||||
},
|
|
||||||
"optionsSyncStatusConnecting": {
|
|
||||||
"message": "Ühendumine..."
|
|
||||||
},
|
|
||||||
"optionsSyncStatusDisconnected": {
|
|
||||||
"message": "Lahti ühendatud"
|
|
||||||
},
|
|
||||||
"optionsSyncStatusDisconnecting": {
|
|
||||||
"message": "Lahtiühendamine..."
|
|
||||||
},
|
|
||||||
"optionsSyncStatusSyncing": {
|
|
||||||
"message": "Sünkroonimine..."
|
|
||||||
},
|
|
||||||
"optionsSyncSyncNow": {
|
|
||||||
"message": "Sünkrooni kohe"
|
|
||||||
},
|
|
||||||
"optionsUpdateImportNote": {
|
"optionsUpdateImportNote": {
|
||||||
"message": "Kui impordid stiilide varundusi vanast versioonist või Stylish'ist, tee ühekordne käsitsi uuenduste kontroll stiilide halduris, et veenduda kõikide stiilide ajakohasuses."
|
"message": "Kui impordid stiilide varundusi vanast versioonist või Stylish'ist, tee ühekordne käsitsi uuenduste kontroll stiilide halduris, et veenduda kõikide stiilide ajakohasuses."
|
||||||
},
|
},
|
||||||
"optionsUpdateInterval": {
|
"optionsUpdateInterval": {
|
||||||
"message": "Kasutajastiilide automaatse uuendamise ajavahe (keelamiseks pane 0)"
|
"message": "Kasutajastiilide automaatse uuendamise ajavahe (keelamiseks pane 0)"
|
||||||
},
|
},
|
||||||
"overwriteFileExport": {
|
|
||||||
"message": "Kas tahad olemasoleva faili üle kirjutada?"
|
|
||||||
},
|
|
||||||
"paginationCurrent": {
|
"paginationCurrent": {
|
||||||
"message": "Praegune leht"
|
"message": "Praegune leht"
|
||||||
},
|
},
|
||||||
|
@ -784,9 +640,6 @@
|
||||||
"popupManageTooltip": {
|
"popupManageTooltip": {
|
||||||
"message": "Shift+klõps või paremklõps avab halduri praeguse saidi kohta käivate stiilidega"
|
"message": "Shift+klõps või paremklõps avab halduri praeguse saidi kohta käivate stiilidega"
|
||||||
},
|
},
|
||||||
"popupMenuButtonTooltip": {
|
|
||||||
"message": "Tegevuste menüü"
|
|
||||||
},
|
|
||||||
"popupOpenEditInWindow": {
|
"popupOpenEditInWindow": {
|
||||||
"message": "Ava redaktor uues aknas"
|
"message": "Ava redaktor uues aknas"
|
||||||
},
|
},
|
||||||
|
@ -805,9 +658,6 @@
|
||||||
"previewTooltip": {
|
"previewTooltip": {
|
||||||
"message": "Ajutiselt rakendab muudatused ilma salvestamata.\nMuudatuste püsivaks tegemiseks salvesta stiil."
|
"message": "Ajutiselt rakendab muudatused ilma salvestamata.\nMuudatuste püsivaks tegemiseks salvesta stiil."
|
||||||
},
|
},
|
||||||
"readingStyles": {
|
|
||||||
"message": "Stiilide lugemine..."
|
|
||||||
},
|
|
||||||
"replace": {
|
"replace": {
|
||||||
"message": "Asenda"
|
"message": "Asenda"
|
||||||
},
|
},
|
||||||
|
@ -820,9 +670,6 @@
|
||||||
"retrieveBckp": {
|
"retrieveBckp": {
|
||||||
"message": "Impordi stiilid"
|
"message": "Impordi stiilid"
|
||||||
},
|
},
|
||||||
"retrieveDropboxSync": {
|
|
||||||
"message": "Dropboxi importimine"
|
|
||||||
},
|
|
||||||
"search": {
|
"search": {
|
||||||
"message": "Otsi"
|
"message": "Otsi"
|
||||||
},
|
},
|
||||||
|
@ -853,21 +700,27 @@
|
||||||
"searchResultWeeklyCount": {
|
"searchResultWeeklyCount": {
|
||||||
"message": "Paigaldus nädalas"
|
"message": "Paigaldus nädalas"
|
||||||
},
|
},
|
||||||
|
"searchStyles": {
|
||||||
|
"message": "Otsi sisu"
|
||||||
|
},
|
||||||
|
"searchStylesHelp": {
|
||||||
|
"message": "</> klahv fokuseerib otsinguvälja.\nLihttekst: otsi nimest, koodist, kodulehe URList ja rakendatavatest saitidest. Sõnu alla 3 tähemärgi ignoreeritakse.\nVastav URL: kasuta otsingus eesliidet <url:>, nt <url:https://github.com/openstyles/stylus>\nRegulaaravaldised sisaldavad kaldkriipse ja lippe, nt </body.*?\\ba\\b/simguy>\nTäpsed sõnad: kirjuta väljendi ümber jutumärgid, nt <\".header ~ div\">"
|
||||||
|
},
|
||||||
"sectionAdd": {
|
"sectionAdd": {
|
||||||
"message": "Lisa uus jaotis"
|
"message": "Lisa uus jaotis"
|
||||||
},
|
},
|
||||||
"sectionCode": {
|
"sectionCode": {
|
||||||
"message": "Kood"
|
"message": "Kood"
|
||||||
},
|
},
|
||||||
|
"sectionHelp": {
|
||||||
|
"message": "Jaotised võimaldavad sul määrata erinevaid koodiosi, mida rakendada mitmetele URLide kogumikele samas stiilis. Näiteks võib üks stiil muuta saidi kodulehte ühtemoodi, seejuures muutes ülejäänud saiti teistmoodi."
|
||||||
|
},
|
||||||
"sectionRemove": {
|
"sectionRemove": {
|
||||||
"message": "Eemalda jaotis"
|
"message": "Eemalda jaotis"
|
||||||
},
|
},
|
||||||
"sectionRestore": {
|
"sectionRestore": {
|
||||||
"message": "Taasta eemaldatud jaotis"
|
"message": "Taasta eemaldatud jaotis"
|
||||||
},
|
},
|
||||||
"sections": {
|
|
||||||
"message": "Jaotised"
|
|
||||||
},
|
|
||||||
"shortcuts": {
|
"shortcuts": {
|
||||||
"message": "Otseteed"
|
"message": "Otseteed"
|
||||||
},
|
},
|
||||||
|
@ -952,6 +805,36 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"styleMetaErrorCheckbox": {
|
||||||
|
"message": "Vigane @var märkeruut: väärtus peab olema 0 või 1"
|
||||||
|
},
|
||||||
|
"styleMetaErrorColor": {
|
||||||
|
"message": "$color$ ei ole sobiv värv",
|
||||||
|
"placeholders": {
|
||||||
|
"color": {
|
||||||
|
"content": "$1"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"styleMetaErrorPreprocessor": {
|
||||||
|
"message": "Mittetoetatud eeltöötleja: $preprocessor$",
|
||||||
|
"placeholders": {
|
||||||
|
"preprocessor": {
|
||||||
|
"content": "$1"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"styleMetaErrorSelectValueMismatch": {
|
||||||
|
"message": "Vigane @select: väärtust pole loendis olemas"
|
||||||
|
},
|
||||||
|
"styleMissingMeta": {
|
||||||
|
"message": "@$key$ metaandmed puuduvad",
|
||||||
|
"placeholders": {
|
||||||
|
"key": {
|
||||||
|
"content": "$1"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"styleMissingName": {
|
"styleMissingName": {
|
||||||
"message": "Sisesta nimi"
|
"message": "Sisesta nimi"
|
||||||
},
|
},
|
||||||
|
@ -970,6 +853,9 @@
|
||||||
"styleRegexpProblemTooltip": {
|
"styleRegexpProblemTooltip": {
|
||||||
"message": "\"regexp()\" vale kasutuse tõttu rakendamata jaotiste arv"
|
"message": "\"regexp()\" vale kasutuse tõttu rakendamata jaotiste arv"
|
||||||
},
|
},
|
||||||
|
"styleRegexpTestButton": {
|
||||||
|
"message": "Regulaaravaldise katsetus"
|
||||||
|
},
|
||||||
"styleRegexpTestFull": {
|
"styleRegexpTestFull": {
|
||||||
"message": "Vastavad kaardid"
|
"message": "Vastavad kaardid"
|
||||||
},
|
},
|
||||||
|
@ -991,6 +877,9 @@
|
||||||
"styleSaveLabel": {
|
"styleSaveLabel": {
|
||||||
"message": "Salvesta"
|
"message": "Salvesta"
|
||||||
},
|
},
|
||||||
|
"styleSectionsTitle": {
|
||||||
|
"message": "Jaotised"
|
||||||
|
},
|
||||||
"styleToMozillaFormatHelp": {
|
"styleToMozillaFormatHelp": {
|
||||||
"message": "Koodi Mozilla-vormingut saab üles laadida saidile userstyles.org ja kasutada klassikalise Firefoxi Stylish'iga"
|
"message": "Koodi Mozilla-vormingut saab üles laadida saidile userstyles.org ja kasutada klassikalise Firefoxi Stylish'iga"
|
||||||
},
|
},
|
||||||
|
@ -1014,12 +903,6 @@
|
||||||
"stylusUnavailableForURLdetails": {
|
"stylusUnavailableForURLdetails": {
|
||||||
"message": "Turvakaalutlustel keelab brauser laiendustel sisseehitatud lehtede (nagu chrome://version, standardne uue kaardi leht alates versioonist Chrome 61, about:addons jne) ning ka teiste laienduste lehtede muutmise. Iga brauser piirab lisaks juurdepääsu oma laienduste galeriile (nagu Chrome'i veebipood või AMO)."
|
"message": "Turvakaalutlustel keelab brauser laiendustel sisseehitatud lehtede (nagu chrome://version, standardne uue kaardi leht alates versioonist Chrome 61, about:addons jne) ning ka teiste laienduste lehtede muutmise. Iga brauser piirab lisaks juurdepääsu oma laienduste galeriile (nagu Chrome'i veebipood või AMO)."
|
||||||
},
|
},
|
||||||
"syncDropboxDeprecated": {
|
|
||||||
"message": "Dropboxi importimine/eksportimine on asendatud edasijõudnuma stiilide sünkroonijaga valikute lehel."
|
|
||||||
},
|
|
||||||
"syncDropboxStyles": {
|
|
||||||
"message": "Dropboxi eksportimine"
|
|
||||||
},
|
|
||||||
"syncStorageErrorSaving": {
|
"syncStorageErrorSaving": {
|
||||||
"message": "Väärtust ei saa salvestada. Proovi teksti hulka vähendada."
|
"message": "Väärtust ei saa salvestada. Proovi teksti hulka vähendada."
|
||||||
},
|
},
|
||||||
|
@ -1038,18 +921,18 @@
|
||||||
"unreachableAMOHint": {
|
"unreachableAMOHint": {
|
||||||
"message": "Ligipääsu lubamiseks ava <about:config>, parem-klõpsa nimekirjal, vali 'Uus', seejärel 'Tõeväärtus', kleebi <privacy.resistFingerprinting.block_mozAddonManager> ja klõpsa Sobib, <true>, Sobib, laadi leht <addons.mozilla.org> uuesti."
|
"message": "Ligipääsu lubamiseks ava <about:config>, parem-klõpsa nimekirjal, vali 'Uus', seejärel 'Tõeväärtus', kleebi <privacy.resistFingerprinting.block_mozAddonManager> ja klõpsa Sobib, <true>, Sobib, laadi leht <addons.mozilla.org> uuesti."
|
||||||
},
|
},
|
||||||
|
"unreachableAMOHintNewFF": {
|
||||||
|
"message": "Firefox 60-s ja uuemas pead ka lehel <about:config> eelistusest <extensions.webextensions.restrictedDomains> eemaldama AMO domeeni."
|
||||||
|
},
|
||||||
|
"unreachableAMOHintOldFF": {
|
||||||
|
"message": "Ainult Firefox 59 ja uuem on võimalik seadistada lubama WebExtensionsil lisada stiilielemente CSP-kaitstud saitidele nagu see."
|
||||||
|
},
|
||||||
"unreachableContentScript": {
|
"unreachableContentScript": {
|
||||||
"message": "Lehega ei saanud ühendust. Proovi kaart uuesti laadida."
|
"message": "Lehega ei saanud ühendust. Proovi kaart uuesti laadida."
|
||||||
},
|
},
|
||||||
"unreachableFileHint": {
|
"unreachableFileHint": {
|
||||||
"message": "Stylus saab ligi pääseda file:// URLidele ainult siis, kui märgistad vastava kasti Stylus laiendusel chrome://extensions lehel"
|
"message": "Stylus saab ligi pääseda file:// URLidele ainult siis, kui märgistad vastava kasti Stylus laiendusel chrome://extensions lehel"
|
||||||
},
|
},
|
||||||
"unreachableMozSiteHintOldFF": {
|
|
||||||
"message": "Ainult Firefox 59 ja uuem on võimalik seadistada lubama WebExtensionsil lisada stiilielemente CSP-kaitstud saitidele nagu see."
|
|
||||||
},
|
|
||||||
"unzipStyles": {
|
|
||||||
"message": "Stiilide lahtipakkimine..."
|
|
||||||
},
|
|
||||||
"updateAllCheckSucceededNoUpdate": {
|
"updateAllCheckSucceededNoUpdate": {
|
||||||
"message": "Uuendusi ei leitud."
|
"message": "Uuendusi ei leitud."
|
||||||
},
|
},
|
||||||
|
@ -1091,9 +974,6 @@
|
||||||
"updatesCurrentlyInstalled": {
|
"updatesCurrentlyInstalled": {
|
||||||
"message": "Uuendused paigaldatud:"
|
"message": "Uuendused paigaldatud:"
|
||||||
},
|
},
|
||||||
"uploadingFile": {
|
|
||||||
"message": "Faili üleslaadimine..."
|
|
||||||
},
|
|
||||||
"usercssAvoidOverwriting": {
|
"usercssAvoidOverwriting": {
|
||||||
"message": "Olemasoleva stiili ülekirjutamise vältimiseks palun muuda @name või @namespace väärtused."
|
"message": "Olemasoleva stiili ülekirjutamise vältimiseks palun muuda @name või @namespace väärtused."
|
||||||
},
|
},
|
||||||
|
@ -1106,6 +986,9 @@
|
||||||
"usercssReplaceTemplateConfirmation": {
|
"usercssReplaceTemplateConfirmation": {
|
||||||
"message": "Asendad vaikimisi malli uutes kasutajacss stiilides praeguse koodiga?"
|
"message": "Asendad vaikimisi malli uutes kasutajacss stiilides praeguse koodiga?"
|
||||||
},
|
},
|
||||||
|
"usercssReplaceTemplateName": {
|
||||||
|
"message": "Tühi @name asendab vaikimisi malli"
|
||||||
|
},
|
||||||
"usercssReplaceTemplateSectionBody": {
|
"usercssReplaceTemplateSectionBody": {
|
||||||
"message": "Sisesta kood siia..."
|
"message": "Sisesta kood siia..."
|
||||||
},
|
},
|
||||||
|
@ -1117,8 +1000,5 @@
|
||||||
},
|
},
|
||||||
"writeStyleForURL": {
|
"writeStyleForURL": {
|
||||||
"message": "see URL"
|
"message": "see URL"
|
||||||
},
|
|
||||||
"zipStyles": {
|
|
||||||
"message": "Stiilide kokkupakkimine..."
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -52,12 +52,6 @@
|
||||||
"checkingForUpdate": {
|
"checkingForUpdate": {
|
||||||
"message": "Tarkistetaan..."
|
"message": "Tarkistetaan..."
|
||||||
},
|
},
|
||||||
"confirmDelete": {
|
|
||||||
"message": "Poista"
|
|
||||||
},
|
|
||||||
"confirmSave": {
|
|
||||||
"message": "Tallenna"
|
|
||||||
},
|
|
||||||
"deleteStyleConfirm": {
|
"deleteStyleConfirm": {
|
||||||
"message": "Oletko varma että haluat poistaa tämän tyylin?"
|
"message": "Oletko varma että haluat poistaa tämän tyylin?"
|
||||||
},
|
},
|
||||||
|
@ -70,9 +64,6 @@
|
||||||
"disableStyleLabel": {
|
"disableStyleLabel": {
|
||||||
"message": "Poista Käytöstä"
|
"message": "Poista Käytöstä"
|
||||||
},
|
},
|
||||||
"editDeleteText": {
|
|
||||||
"message": "Poista"
|
|
||||||
},
|
|
||||||
"editStyleHeading": {
|
"editStyleHeading": {
|
||||||
"message": "Muokkaa Tyyliä"
|
"message": "Muokkaa Tyyliä"
|
||||||
},
|
},
|
||||||
|
@ -90,11 +81,8 @@
|
||||||
"enableStyleLabel": {
|
"enableStyleLabel": {
|
||||||
"message": "Aktivoi"
|
"message": "Aktivoi"
|
||||||
},
|
},
|
||||||
"genericAdd": {
|
"findStylesForSite": {
|
||||||
"message": "Lisää"
|
"message": "Hae lisää tyylejä tälle sivustolle"
|
||||||
},
|
|
||||||
"genericEnabledLabel": {
|
|
||||||
"message": "Aktivoitu"
|
|
||||||
},
|
},
|
||||||
"helpAlt": {
|
"helpAlt": {
|
||||||
"message": "Apu"
|
"message": "Apu"
|
||||||
|
@ -126,12 +114,12 @@
|
||||||
"sectionCode": {
|
"sectionCode": {
|
||||||
"message": "Koodi"
|
"message": "Koodi"
|
||||||
},
|
},
|
||||||
|
"sectionHelp": {
|
||||||
|
"message": "Osiot antavat sinun tarkentaa koodin eri osia niin että ne koskevat eri URL osoitteita samassa tyylissä. Esimerkiksi, yksi tyyli voi muokata kotisivua yhdellä tavalla kun se muokkaa koko muuta sivustoa toisella tavalla."
|
||||||
|
},
|
||||||
"sectionRemove": {
|
"sectionRemove": {
|
||||||
"message": "Poista osio"
|
"message": "Poista osio"
|
||||||
},
|
},
|
||||||
"sections": {
|
|
||||||
"message": "Osiot"
|
|
||||||
},
|
|
||||||
"styleBadRegexp": {
|
"styleBadRegexp": {
|
||||||
"message": "Regexp ei kelpaa."
|
"message": "Regexp ei kelpaa."
|
||||||
},
|
},
|
||||||
|
@ -158,6 +146,9 @@
|
||||||
"styleSaveLabel": {
|
"styleSaveLabel": {
|
||||||
"message": "Tallenna"
|
"message": "Tallenna"
|
||||||
},
|
},
|
||||||
|
"styleSectionsTitle": {
|
||||||
|
"message": "Osiot"
|
||||||
|
},
|
||||||
"styleToMozillaFormatHelp": {
|
"styleToMozillaFormatHelp": {
|
||||||
"message": "Mozilla formaattia koodista voidaan käyttää Stylish Firefoxille ohjelmassa ja voidaan lähettää userstyles.orgiin."
|
"message": "Mozilla formaattia koodista voidaan käyttää Stylish Firefoxille ohjelmassa ja voidaan lähettää userstyles.orgiin."
|
||||||
},
|
},
|
||||||
|
|
|
@ -1,7 +1,4 @@
|
||||||
{
|
{
|
||||||
"InaccessibleFileHint": {
|
|
||||||
"message": "Stylus ne peut pas accéder à certains types de fichiers (ex: fichiers .pdf et .json)."
|
|
||||||
},
|
|
||||||
"addStyleLabel": {
|
"addStyleLabel": {
|
||||||
"message": "Créer un nouveau style"
|
"message": "Créer un nouveau style"
|
||||||
},
|
},
|
||||||
|
@ -68,19 +65,19 @@
|
||||||
"message": "Sauvegarde"
|
"message": "Sauvegarde"
|
||||||
},
|
},
|
||||||
"backupMessage": {
|
"backupMessage": {
|
||||||
"message": "Pour importer le fichier de sauvegarder, glissez-déposez-le sur dans page ou cliquez sur le bouton Importer.\n\nPour export une sauvegarde compatible avec une version de Stylus plus ancienne que 1.5.18, effectuez un clic droit ou un appuie sur la touche Maj + clic sur le bouton Exporter."
|
"message": "Sélectionner un fichier ou le glisser-déposer sur cette page"
|
||||||
},
|
},
|
||||||
"bckpInstStyles": {
|
"bckpInstStyles": {
|
||||||
"message": "Exporter des styles"
|
"message": "Exporter des styles"
|
||||||
},
|
},
|
||||||
"checkAllUpdates": {
|
"checkAllUpdates": {
|
||||||
"message": "Rechercher des mises à jour"
|
"message": "Rechercher des mises à jour pour tous les styles"
|
||||||
},
|
},
|
||||||
"checkAllUpdatesForce": {
|
"checkAllUpdatesForce": {
|
||||||
"message": "Vérifiez à nouveau, je n’ai modifié aucun style !"
|
"message": "Vérifiez à nouveau, je n’ai modifié aucun style !"
|
||||||
},
|
},
|
||||||
"checkForUpdate": {
|
"checkForUpdate": {
|
||||||
"message": "Rechercher une mise à jour"
|
"message": "Rechercher des mises à jour"
|
||||||
},
|
},
|
||||||
"checkingForUpdate": {
|
"checkingForUpdate": {
|
||||||
"message": "Vérification en cours…"
|
"message": "Vérification en cours…"
|
||||||
|
@ -187,40 +184,6 @@
|
||||||
"confirmYes": {
|
"confirmYes": {
|
||||||
"message": "Oui"
|
"message": "Oui"
|
||||||
},
|
},
|
||||||
"connectingDropbox": {
|
|
||||||
"message": "Connexion à Dropbox…"
|
|
||||||
},
|
|
||||||
"connectingDropboxNotAllowed": {
|
|
||||||
"message": "La connexion à Dropbox est disponible uniquement dans les applications installées directement depuis le Web Store."
|
|
||||||
},
|
|
||||||
"copied": {
|
|
||||||
"message": "Copié dans le presse-papier"
|
|
||||||
},
|
|
||||||
"copy": {
|
|
||||||
"message": "Copier dans le presse-papier"
|
|
||||||
},
|
|
||||||
"customNameHint": {
|
|
||||||
"message": "Entrez un nom personnalisé ici pour renommer le style dans l'IU sans casser les mises à jour"
|
|
||||||
},
|
|
||||||
"customNameResetHint": {
|
|
||||||
"message": "Arrête d'utiliser le nom personnalisé, change vers le propre nom du style"
|
|
||||||
},
|
|
||||||
"dateAbbrDay": {
|
|
||||||
"message": "$value$j",
|
|
||||||
"placeholders": {
|
|
||||||
"value": {
|
|
||||||
"content": "$1"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"dateAbbrYear": {
|
|
||||||
"message": "$value$a",
|
|
||||||
"placeholders": {
|
|
||||||
"value": {
|
|
||||||
"content": "$1"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"dateInstalled": {
|
"dateInstalled": {
|
||||||
"message": "Date d'installation"
|
"message": "Date d'installation"
|
||||||
},
|
},
|
||||||
|
@ -245,29 +208,12 @@
|
||||||
"disableAllStyles": {
|
"disableAllStyles": {
|
||||||
"message": "Désactiver tous les styles"
|
"message": "Désactiver tous les styles"
|
||||||
},
|
},
|
||||||
"disableAllStylesOff": {
|
|
||||||
"message": "Les styles sont désactivés"
|
|
||||||
},
|
|
||||||
"disableStyleLabel": {
|
"disableStyleLabel": {
|
||||||
"message": "Désactiver"
|
"message": "Désactiver"
|
||||||
},
|
},
|
||||||
"draftAction": {
|
|
||||||
"message": "Choisissez « Oui » pour charger ce brouillon ou « Non » pour l'abandonner."
|
|
||||||
},
|
|
||||||
"draftTitle": {
|
|
||||||
"message": "Sauvegarde du brouillon, créée $date$",
|
|
||||||
"placeholders": {
|
|
||||||
"date": {
|
|
||||||
"content": "$1"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"dragDropMessage": {
|
"dragDropMessage": {
|
||||||
"message": "Glisser votre fichier de sauvegarde n’importe où sur cette page pour l’importer."
|
"message": "Glisser votre fichier de sauvegarde n’importe où sur cette page pour l’importer."
|
||||||
},
|
},
|
||||||
"dragDropUsercssTabstrip": {
|
|
||||||
"message": "Pour installer le fichier, glisser le fichier sur la barre des onglets (la zone où les titres des onglets sont affichés)."
|
|
||||||
},
|
|
||||||
"editDeleteText": {
|
"editDeleteText": {
|
||||||
"message": "Supprimer"
|
"message": "Supprimer"
|
||||||
},
|
},
|
||||||
|
@ -288,27 +234,15 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"editorSettings": {
|
"editorStylesButton": {
|
||||||
"message": "Paramètres de l’éditeur"
|
"message": "Trouver des styles pour l’éditeur"
|
||||||
},
|
},
|
||||||
"enableStyleLabel": {
|
"enableStyleLabel": {
|
||||||
"message": "Activer"
|
"message": "Activer"
|
||||||
},
|
},
|
||||||
"excludeStyleByDomainLabel": {
|
|
||||||
"message": "Exclure le domaine actuel"
|
|
||||||
},
|
|
||||||
"excludeStyleByUrlLabel": {
|
|
||||||
"message": "Exclure l’URL actuelle"
|
|
||||||
},
|
|
||||||
"exportCompatible": {
|
|
||||||
"message": "Export (mode de compatibilité)"
|
|
||||||
},
|
|
||||||
"exportLabel": {
|
"exportLabel": {
|
||||||
"message": "Exporter"
|
"message": "Exporter"
|
||||||
},
|
},
|
||||||
"exportSavedSuccess": {
|
|
||||||
"message": "Fichier sauvegardé avec succès"
|
|
||||||
},
|
|
||||||
"externalFeedback": {
|
"externalFeedback": {
|
||||||
"message": "Commentaires"
|
"message": "Commentaires"
|
||||||
},
|
},
|
||||||
|
@ -338,6 +272,15 @@
|
||||||
"findStyles": {
|
"findStyles": {
|
||||||
"message": "Trouver des styles"
|
"message": "Trouver des styles"
|
||||||
},
|
},
|
||||||
|
"findStylesForSite": {
|
||||||
|
"message": "Rechercher d'autres styles pour ce site"
|
||||||
|
},
|
||||||
|
"findStylesInline": {
|
||||||
|
"message": "Dans la popup"
|
||||||
|
},
|
||||||
|
"findStylesInlineTooltip": {
|
||||||
|
"message": "Affiche les résultats de recherche dans cette fenêtre"
|
||||||
|
},
|
||||||
"genericAdd": {
|
"genericAdd": {
|
||||||
"message": "Ajouter"
|
"message": "Ajouter"
|
||||||
},
|
},
|
||||||
|
@ -374,12 +317,6 @@
|
||||||
"genericUnknown": {
|
"genericUnknown": {
|
||||||
"message": "Inconnu(e)"
|
"message": "Inconnu(e)"
|
||||||
},
|
},
|
||||||
"gettingStyles": {
|
|
||||||
"message": "Récupération de tous les styles…"
|
|
||||||
},
|
|
||||||
"headerResizerHint": {
|
|
||||||
"message": "Maintenir la touche Maj pour redimensionner uniquement dans ce type d'interface, par exemple, éditeur, gestionnaire, installateur"
|
|
||||||
},
|
|
||||||
"helpAlt": {
|
"helpAlt": {
|
||||||
"message": "Aide"
|
"message": "Aide"
|
||||||
},
|
},
|
||||||
|
@ -389,9 +326,6 @@
|
||||||
"helpKeyMapHotkey": {
|
"helpKeyMapHotkey": {
|
||||||
"message": "Pressez un raccourci clavier"
|
"message": "Pressez un raccourci clavier"
|
||||||
},
|
},
|
||||||
"hostDisabled": {
|
|
||||||
"message": "Cet hôte a été désactivé en raison d'un bogue dans la version actuelle du navigateur utilisé"
|
|
||||||
},
|
|
||||||
"importAppendLabel": {
|
"importAppendLabel": {
|
||||||
"message": "Ajouter au style"
|
"message": "Ajouter au style"
|
||||||
},
|
},
|
||||||
|
@ -401,12 +335,6 @@
|
||||||
"importLabel": {
|
"importLabel": {
|
||||||
"message": "Importer"
|
"message": "Importer"
|
||||||
},
|
},
|
||||||
"importPreprocessor": {
|
|
||||||
"message": "Les styles avec un <code>@preprocessor</code> ne fonctionnera pas en mode classique. Vous pouvez changer l'éditeur en mode Usercss : 1) Ouvrez le gestionnaire de styles, 2) Activer l'option \"en tant que Usercss\", 3) Cliquez \"écrire un nouveau style\"\n\nImporter de toute façon ?"
|
|
||||||
},
|
|
||||||
"importPreprocessorTitle": {
|
|
||||||
"message": "Problème potentiel à cause de @preprocessor"
|
|
||||||
},
|
|
||||||
"importReplaceLabel": {
|
"importReplaceLabel": {
|
||||||
"message": "Remplacer le style"
|
"message": "Remplacer le style"
|
||||||
},
|
},
|
||||||
|
@ -469,24 +397,18 @@
|
||||||
"installUpdateFromLabel": {
|
"installUpdateFromLabel": {
|
||||||
"message": "Rechercher les mises à jour"
|
"message": "Rechercher les mises à jour"
|
||||||
},
|
},
|
||||||
|
"installUpdateUnavailable": {
|
||||||
|
"message": "Pour activer la vérification des mises à jour, glissez le fichier sur la barre des onglets ou spécifiez @updateURL dans les métadonnées du style."
|
||||||
|
},
|
||||||
"license": {
|
"license": {
|
||||||
"message": "Licence"
|
"message": "Licence"
|
||||||
},
|
},
|
||||||
"linkGetHelp": {
|
"linkGetHelp": {
|
||||||
"message": "Consulter l'aide"
|
"message": "Consulter l'aide"
|
||||||
},
|
},
|
||||||
"linkGetShareStyles": {
|
|
||||||
"message": "Obtenir et partager les styles"
|
|
||||||
},
|
|
||||||
"linkGetShareStylesInfo": {
|
|
||||||
"message": "Le nouveau userstyles.world géré par la communauté est créé par les auteurs d'userstyles dans le but de remplacer userstyles.org, qui a été tellement lent et inconscient durant l'année dernière que beaucoup d'auteurs ont arrêté de mettre à jour leurs styles."
|
|
||||||
},
|
|
||||||
"linkGetStyles": {
|
"linkGetStyles": {
|
||||||
"message": "Obtenir des styles"
|
"message": "Obtenir des styles"
|
||||||
},
|
},
|
||||||
"linkGetStylesInfo": {
|
|
||||||
"message": "Ce site d'archive à été créé par un membre de la communauté userstyle pour archiver le lent et inconscient site userstyles.org. Cette archive est mise à jour approximativement chaque jour."
|
|
||||||
},
|
|
||||||
"linkTranslate": {
|
"linkTranslate": {
|
||||||
"message": "Traduire"
|
"message": "Traduire"
|
||||||
},
|
},
|
||||||
|
@ -539,14 +461,14 @@
|
||||||
"message": "Une erreur est survenue durant la surveillance du fichier"
|
"message": "Une erreur est survenue durant la surveillance du fichier"
|
||||||
},
|
},
|
||||||
"liveReloadInstallHint": {
|
"liveReloadInstallHint": {
|
||||||
"message": "Gardez cet onglet ouvert pour mettre à jour le style automatiquement basé sur les modifications extérieures."
|
"message": "Le rechargement automatique est activé, donc le style installé sera mis à jour automatiquement après une modification externe quand à la fois cet onglet et l’onglet du fichier source sont ouverts."
|
||||||
},
|
|
||||||
"liveReloadInstallHintFF": {
|
|
||||||
"message": "Gardez cet onglet ouvert ainsi que l´onglet original pour mettre à jour le style automatiquement basé sur les modifications extérieures."
|
|
||||||
},
|
},
|
||||||
"liveReloadLabel": {
|
"liveReloadLabel": {
|
||||||
"message": "Rechargement immédiat"
|
"message": "Rechargement immédiat"
|
||||||
},
|
},
|
||||||
|
"liveReloadUnavailable": {
|
||||||
|
"message": "Pour activer le rechargement automatique, glisser le fichier sur la barre des onglets (la zone où les titres des onglets sont affichés)."
|
||||||
|
},
|
||||||
"manageFavicons": {
|
"manageFavicons": {
|
||||||
"message": "Favicons dans la colonne « s’applique à »"
|
"message": "Favicons dans la colonne « s’applique à »"
|
||||||
},
|
},
|
||||||
|
@ -554,7 +476,7 @@
|
||||||
"message": "Grisés"
|
"message": "Grisés"
|
||||||
},
|
},
|
||||||
"manageFaviconsHelp": {
|
"manageFaviconsHelp": {
|
||||||
"message": "Stylus utilise le service externe https://icons.duckduckgo.com"
|
"message": "Stylus utilise le service externe https://www.google.com/s2/favicons"
|
||||||
},
|
},
|
||||||
"manageFilters": {
|
"manageFilters": {
|
||||||
"message": "Filtres"
|
"message": "Filtres"
|
||||||
|
@ -587,236 +509,35 @@
|
||||||
"message": "(les styles non installés via une page userstyles.org)"
|
"message": "(les styles non installés via une page userstyles.org)"
|
||||||
},
|
},
|
||||||
"manageOnlyNonUsercss": {
|
"manageOnlyNonUsercss": {
|
||||||
"message": "Styles non Usercss uniquement"
|
"message": "Uniquement les styles non Usercss"
|
||||||
},
|
},
|
||||||
"manageOnlyUpdates": {
|
"manageOnlyUpdates": {
|
||||||
"message": "Avec mises à jour ou problèmes uniquement"
|
"message": "Uniquement avec mises à jour ou problèmes"
|
||||||
},
|
},
|
||||||
"manageOnlyUsercss": {
|
"manageOnlyUsercss": {
|
||||||
"message": "Styles Usercss uniquement"
|
"message": "Uniquement les styles Usercss"
|
||||||
},
|
},
|
||||||
"menuShowBadge": {
|
"menuShowBadge": {
|
||||||
"message": "Montrer le nombre de styles actifs"
|
"message": "Montrer le nombre de styles actifs"
|
||||||
},
|
},
|
||||||
"meta_invalidCheckboxDefault": {
|
|
||||||
"message": "Case à cocher @var invalide : la valeur doit être 0 ou 1"
|
|
||||||
},
|
|
||||||
"meta_invalidColor": {
|
|
||||||
"message": "Couleur @var invalide : $color$ n’est pas une couleur",
|
|
||||||
"placeholders": {
|
|
||||||
"color": {
|
|
||||||
"content": "$1"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"meta_invalidNumber": {
|
|
||||||
"message": "Nombre attendu"
|
|
||||||
},
|
|
||||||
"meta_invalidRange": {
|
|
||||||
"message": "@var $type$ invalide : la valeur doit être un nombre ou un tableau",
|
|
||||||
"placeholders": {
|
|
||||||
"type": {
|
|
||||||
"content": "$1"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"meta_invalidRangeDefault": {
|
|
||||||
"message": "@var $type$ invalide : la valeur par défaut est null",
|
|
||||||
"placeholders": {
|
|
||||||
"type": {
|
|
||||||
"content": "$1"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"meta_invalidRangeMax": {
|
|
||||||
"message": "@var $type$ invalide : la valeur par défaut est au-dessus du maximum",
|
|
||||||
"placeholders": {
|
|
||||||
"type": {
|
|
||||||
"content": "$1"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"meta_invalidRangeMin": {
|
|
||||||
"message": "@var $type$ invalide : la valeur par défaut est en dessous du minimum",
|
|
||||||
"placeholders": {
|
|
||||||
"type": {
|
|
||||||
"content": "$1"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"meta_invalidRangeMultipleUnits": {
|
|
||||||
"message": "@var $type$ invalide : plusieurs unités sont définies",
|
|
||||||
"placeholders": {
|
|
||||||
"type": {
|
|
||||||
"content": "$1"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"meta_invalidRangeStep": {
|
|
||||||
"message": "@var $type$ invalide : la valeur par défaut n’est pas un multiple du pas",
|
|
||||||
"placeholders": {
|
|
||||||
"type": {
|
|
||||||
"content": "$1"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"meta_invalidRangeTooManyValues": {
|
|
||||||
"message": "@var $type$ invalide : le tableau contient trop d’items",
|
|
||||||
"placeholders": {
|
|
||||||
"type": {
|
|
||||||
"content": "$1"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"meta_invalidRangeUnits": {
|
|
||||||
"message": "@var $type$ invalide : '$units$' n’est pas une unité valide",
|
|
||||||
"placeholders": {
|
|
||||||
"type": {
|
|
||||||
"content": "$1"
|
|
||||||
},
|
|
||||||
"units": {
|
|
||||||
"content": "$2"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"meta_invalidRangeValue": {
|
|
||||||
"message": "@var $type$ invalide : les items dans le tableau doivent être des nombres, chaines de caractères ou null",
|
|
||||||
"placeholders": {
|
|
||||||
"type": {
|
|
||||||
"content": "$1"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"meta_invalidSelect": {
|
|
||||||
"message": "@var select invalide : la valeur par défaut doit être un tableau ou un objet"
|
|
||||||
},
|
|
||||||
"meta_invalidSelectEmptyOptions": {
|
|
||||||
"message": "@var select invalide : la liste d’options est vide"
|
|
||||||
},
|
|
||||||
"meta_invalidSelectLabel": {
|
|
||||||
"message": "@var select invalide : le label de l’option est vide"
|
|
||||||
},
|
|
||||||
"meta_invalidSelectMultipleDefaults": {
|
|
||||||
"message": "@var select invalide : plusieurs options par défaut sont définies"
|
|
||||||
},
|
|
||||||
"meta_invalidSelectNameDuplicated": {
|
|
||||||
"message": "@var select invalide : nom d’option dupliqué"
|
|
||||||
},
|
|
||||||
"meta_invalidSelectValue": {
|
|
||||||
"message": "@var select invalide : les valeurs dans le tableau/objet doivent être des chaines de caractères"
|
|
||||||
},
|
|
||||||
"meta_invalidSelectValueMismatch": {
|
|
||||||
"message": "@var select invalide : la valeur n’existe pas dans la liste d’options"
|
|
||||||
},
|
|
||||||
"meta_invalidString": {
|
|
||||||
"message": "Chaine entre guillemets attendue"
|
|
||||||
},
|
|
||||||
"meta_invalidURLProtocol": {
|
|
||||||
"message": "Protocole de l’URL invalide. Seuls http et https sont autorisés : $protocol$",
|
|
||||||
"placeholders": {
|
|
||||||
"protocol": {
|
|
||||||
"content": "$1"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"meta_invalidVersion": {
|
|
||||||
"message": "Numéro de version invalide"
|
|
||||||
},
|
|
||||||
"meta_invalidWord": {
|
|
||||||
"message": "Mot attendu"
|
|
||||||
},
|
|
||||||
"meta_missingChar": {
|
|
||||||
"message": "Caractères attendus : $chars$",
|
|
||||||
"placeholders": {
|
|
||||||
"chars": {
|
|
||||||
"content": "$1"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"meta_missingEOT": {
|
|
||||||
"message": "Données EOT attendues"
|
|
||||||
},
|
|
||||||
"meta_missingMandatory": {
|
|
||||||
"message": "Métadonnée obligatoire manquante: $keys$",
|
|
||||||
"placeholders": {
|
|
||||||
"keys": {
|
|
||||||
"content": "$1"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"meta_unknownJSONLiteral": {
|
|
||||||
"message": "JSON invalide : $literal$ n’est pas une valeur JSON valide",
|
|
||||||
"placeholders": {
|
|
||||||
"literal": {
|
|
||||||
"content": "$1"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"meta_unknownMeta": {
|
|
||||||
"message": "Métadonnée inconnue: $key$",
|
|
||||||
"placeholders": {
|
|
||||||
"key": {
|
|
||||||
"content": "$1"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"meta_unknownMetaTypo": {
|
|
||||||
"message": "Peut-être @$keyOk$? Métadonnée inconnue: @$keyErr$",
|
|
||||||
"placeholders": {
|
|
||||||
"keyErr": {
|
|
||||||
"content": "$1"
|
|
||||||
},
|
|
||||||
"keyOk": {
|
|
||||||
"content": "$2"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"meta_unknownPreprocessor": {
|
|
||||||
"message": "@preprocessor inconnu: $preprocessor$",
|
|
||||||
"placeholders": {
|
|
||||||
"preprocessor": {
|
|
||||||
"content": "$1"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"meta_unknownVarType": {
|
|
||||||
"message": "Type @$varkey$ inconnu : $vartype$",
|
|
||||||
"placeholders": {
|
|
||||||
"varkey": {
|
|
||||||
"content": "$1"
|
|
||||||
},
|
|
||||||
"vartype": {
|
|
||||||
"content": "$2"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"noFileToImport": {
|
|
||||||
"message": "Pour importer vos styles, vous devez d’abord les exporter."
|
|
||||||
},
|
|
||||||
"noStylesForSite": {
|
"noStylesForSite": {
|
||||||
"message": "Aucun style n'est installé pour ce site."
|
"message": "Aucun style n'est installé pour ce site."
|
||||||
},
|
},
|
||||||
"numberedLine": {
|
|
||||||
"message": "Ligne :"
|
|
||||||
},
|
|
||||||
"openManage": {
|
"openManage": {
|
||||||
"message": "Gestion"
|
"message": "Gestion"
|
||||||
},
|
},
|
||||||
|
"openOptionsManage": {
|
||||||
|
"message": "Paramètres d'interface graphique"
|
||||||
|
},
|
||||||
|
"openOptionsPopup": {
|
||||||
|
"message": "Paramètres"
|
||||||
|
},
|
||||||
"openStylesManager": {
|
"openStylesManager": {
|
||||||
"message": "Ouvrir le gestionnaire de styles"
|
"message": "Ouvrir le gestionnaire de styles"
|
||||||
},
|
},
|
||||||
"optionsAdvanced": {
|
"optionsAdvanced": {
|
||||||
"message": "Avancé"
|
"message": "Avancé"
|
||||||
},
|
},
|
||||||
"optionsAdvancedAutoSwitchSchemeBySystem": {
|
|
||||||
"message": "Par préférence système"
|
|
||||||
},
|
|
||||||
"optionsAdvancedAutoSwitchSchemeByTime": {
|
|
||||||
"message": "Par horaire nocturne :"
|
|
||||||
},
|
|
||||||
"optionsAdvancedAutoSwitchSchemeNever": {
|
|
||||||
"message": "Désactivé. Le paramètre clair/obscur dans les styles est ignoré."
|
|
||||||
},
|
|
||||||
"optionsAdvancedContextDelete": {
|
"optionsAdvancedContextDelete": {
|
||||||
"message": "Ajouter « Supprimer » dans le menu contextuel de l’extension"
|
"message": "Ajouter « Supprimer » dans le menu contextuel de l’extension"
|
||||||
},
|
},
|
||||||
|
@ -824,26 +545,11 @@
|
||||||
"message": "Exposer les iframes via HTML[stylus-iframe]"
|
"message": "Exposer les iframes via HTML[stylus-iframe]"
|
||||||
},
|
},
|
||||||
"optionsAdvancedExposeIframesNote": {
|
"optionsAdvancedExposeIframesNote": {
|
||||||
"message": "Expose le domaine principal dans chaque iframe.\nPermet d’écrire du CSS spécifique aux iframe tel que :\nhtml[stylus-iframe$$=\"twitter.com\"] h1 { display:none }"
|
"message": "Expose le domaine principal de chaque iframe.\nPermet d’écrire du CSS spécifique à une iframe tel que :\nhtml[stylus-iframe$=\"twitter.com\"] h1 { display:none }"
|
||||||
},
|
|
||||||
"optionsAdvancedExposeStyleName": {
|
|
||||||
"message": "Afficher le nom du style"
|
|
||||||
},
|
|
||||||
"optionsAdvancedExposeStyleNameNote": {
|
|
||||||
"message": "Afficher le nom du style dans la page pour faciliter le débogage des styles dans les outils de développement. Veuillez recharger le ou les onglets pour appliquer ce nouveau paramètre."
|
|
||||||
},
|
},
|
||||||
"optionsAdvancedNewStyleAsUsercss": {
|
"optionsAdvancedNewStyleAsUsercss": {
|
||||||
"message": "Écrire un nouveau style en tant que usercss"
|
"message": "Écrire un nouveau style en tant que usercss"
|
||||||
},
|
},
|
||||||
"optionsAdvancedPatchCsp": {
|
|
||||||
"message": "Corrige <code>CSP</code> pour permettre les ressources de style"
|
|
||||||
},
|
|
||||||
"optionsAdvancedPatchCspNote": {
|
|
||||||
"message": "Activez cela si les styles comportent des images ou des polices qui échoue à charger sur les sites avec une politique de sécurité de contenus stricte <code>CSP</code> (<code>Content-Security-Policy</code>).\n\nActiver cette option permettra d'alléger les restrictions du <code>CSP</code>, permettant les styles essentiels de charger. Cette option n'est entendue que pour les utilisateurs expérimentés qui comprennent les potentielles implications de sécurité que cela introduit, et accepte la responsabilité de surveiller le contenu qu'ils autorisent. Veuillez lire les attaques basé sur le CSS pour plus d’information.\n\nAussi sachez, que cette option en particulier n'est pas garanti de fonctionner si une autre extension modifie la réponse réseau avant."
|
|
||||||
},
|
|
||||||
"optionsAdvancedStyleViaXhr": {
|
|
||||||
"message": "Mode d'injection instantanée"
|
|
||||||
},
|
|
||||||
"optionsBadgeDisabled": {
|
"optionsBadgeDisabled": {
|
||||||
"message": "Couleur d'arrière plan si désactivé"
|
"message": "Couleur d'arrière plan si désactivé"
|
||||||
},
|
},
|
||||||
|
@ -862,9 +568,6 @@
|
||||||
"optionsCustomizeIcon": {
|
"optionsCustomizeIcon": {
|
||||||
"message": "Icône de la barre d’outils"
|
"message": "Icône de la barre d’outils"
|
||||||
},
|
},
|
||||||
"optionsCustomizeSync": {
|
|
||||||
"message": "Synchroniser dans le nuage"
|
|
||||||
},
|
|
||||||
"optionsCustomizeUpdate": {
|
"optionsCustomizeUpdate": {
|
||||||
"message": "Mises à jour"
|
"message": "Mises à jour"
|
||||||
},
|
},
|
||||||
|
@ -892,76 +595,12 @@
|
||||||
"optionsSubheading": {
|
"optionsSubheading": {
|
||||||
"message": "Plus de paramètres"
|
"message": "Plus de paramètres"
|
||||||
},
|
},
|
||||||
"optionsSyncConnect": {
|
|
||||||
"message": "Connecter"
|
|
||||||
},
|
|
||||||
"optionsSyncDisconnect": {
|
|
||||||
"message": "Déconnecter"
|
|
||||||
},
|
|
||||||
"optionsSyncLogin": {
|
|
||||||
"message": "Se connecter"
|
|
||||||
},
|
|
||||||
"optionsSyncNone": {
|
|
||||||
"message": "Aucun"
|
|
||||||
},
|
|
||||||
"optionsSyncPassword": {
|
|
||||||
"message": "Mot de passe"
|
|
||||||
},
|
|
||||||
"optionsSyncStatusConnected": {
|
|
||||||
"message": "Connecté"
|
|
||||||
},
|
|
||||||
"optionsSyncStatusConnecting": {
|
|
||||||
"message": "Connection..."
|
|
||||||
},
|
|
||||||
"optionsSyncStatusDisconnected": {
|
|
||||||
"message": "Déconnecté"
|
|
||||||
},
|
|
||||||
"optionsSyncStatusDisconnecting": {
|
|
||||||
"message": "Déconnexion..."
|
|
||||||
},
|
|
||||||
"optionsSyncStatusPull": {
|
|
||||||
"message": "Récupération du style $loaded$de $total$",
|
|
||||||
"placeholders": {
|
|
||||||
"loaded": {
|
|
||||||
"content": "$1"
|
|
||||||
},
|
|
||||||
"total": {
|
|
||||||
"content": "$2"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"optionsSyncStatusPush": {
|
|
||||||
"message": "Application du style $loaded$ de $total$",
|
|
||||||
"placeholders": {
|
|
||||||
"loaded": {
|
|
||||||
"content": "$1"
|
|
||||||
},
|
|
||||||
"total": {
|
|
||||||
"content": "$2"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"optionsSyncStatusRelogin": {
|
|
||||||
"message": "Session expirée, veuillez vous reconnecter à nouveau."
|
|
||||||
},
|
|
||||||
"optionsSyncStatusSyncing": {
|
|
||||||
"message": "Synchronisation..."
|
|
||||||
},
|
|
||||||
"optionsSyncSyncNow": {
|
|
||||||
"message": "Synchroniser maintenant"
|
|
||||||
},
|
|
||||||
"optionsSyncUsername": {
|
|
||||||
"message": "Nom d’utilisateur"
|
|
||||||
},
|
|
||||||
"optionsUpdateImportNote": {
|
"optionsUpdateImportNote": {
|
||||||
"message": "Quand vous importez des sauvegardes de style d’une ancienne version ou de Stylish, faites une vérification manuellement pour vous assurez que tous les styles sont à jour."
|
"message": "Quand vous importez des sauvegardes de style d’une ancienne version ou de Stylish, faites une vérification manuellement pour vous assurez que tous les styles sont à jour."
|
||||||
},
|
},
|
||||||
"optionsUpdateInterval": {
|
"optionsUpdateInterval": {
|
||||||
"message": "Intervalle de mise à jour automatique des styles utilisateur en heures (spécifier 0 pour désactiver)"
|
"message": "Intervalle de mise à jour automatique des styles utilisateur en heures (spécifier 0 pour désactiver)"
|
||||||
},
|
},
|
||||||
"overwriteFileExport": {
|
|
||||||
"message": "Voulez-vous écraser un fichier existant ?"
|
|
||||||
},
|
|
||||||
"paginationCurrent": {
|
"paginationCurrent": {
|
||||||
"message": "Page courante"
|
"message": "Page courante"
|
||||||
},
|
},
|
||||||
|
@ -980,9 +619,6 @@
|
||||||
"parseUsercssError": {
|
"parseUsercssError": {
|
||||||
"message": "Stylus a échoué à parser le usercss :"
|
"message": "Stylus a échoué à parser le usercss :"
|
||||||
},
|
},
|
||||||
"popupAutoResort": {
|
|
||||||
"message": "Retrier les styles dans la popup à chaque (dés)activation"
|
|
||||||
},
|
|
||||||
"popupBorders": {
|
"popupBorders": {
|
||||||
"message": "Ajouter des bordures blanches sur les côtés"
|
"message": "Ajouter des bordures blanches sur les côtés"
|
||||||
},
|
},
|
||||||
|
@ -998,12 +634,6 @@
|
||||||
"popupManageTooltip": {
|
"popupManageTooltip": {
|
||||||
"message": "Maj-clic ou clic droit ouvre le gestionnaire avec les styles applicables au site courant."
|
"message": "Maj-clic ou clic droit ouvre le gestionnaire avec les styles applicables au site courant."
|
||||||
},
|
},
|
||||||
"popupMenuButtonTooltip": {
|
|
||||||
"message": "Actions"
|
|
||||||
},
|
|
||||||
"popupOpenEditInPopup": {
|
|
||||||
"message": "Utiliser une simple fenêtre (pas d'omnibox)"
|
|
||||||
},
|
|
||||||
"popupOpenEditInWindow": {
|
"popupOpenEditInWindow": {
|
||||||
"message": "Ouvrir l'éditeur dans une nouvelle fenêtre"
|
"message": "Ouvrir l'éditeur dans une nouvelle fenêtre"
|
||||||
},
|
},
|
||||||
|
@ -1016,42 +646,12 @@
|
||||||
"prefShowBadge": {
|
"prefShowBadge": {
|
||||||
"message": "Afficher le nombre de styles actifs pour le site actuel sur le boutton Stylus"
|
"message": "Afficher le nombre de styles actifs pour le site actuel sur le boutton Stylus"
|
||||||
},
|
},
|
||||||
"preferSchemeDark": {
|
|
||||||
"message": "Obscur"
|
|
||||||
},
|
|
||||||
"preferSchemeLight": {
|
|
||||||
"message": "Clair"
|
|
||||||
},
|
|
||||||
"preferSchemeNone": {
|
|
||||||
"message": "Aucun (quand appliqué)"
|
|
||||||
},
|
|
||||||
"previewLabel": {
|
"previewLabel": {
|
||||||
"message": "Prévisualiser en direct"
|
"message": "Prévisualiser en direct"
|
||||||
},
|
},
|
||||||
"previewTooltip": {
|
"previewTooltip": {
|
||||||
"message": "Applique temporairement les changements sans sauvegarder.\nSauvegarde le style pour rendre les changements permanents."
|
"message": "Applique temporairement les changements sans sauvegarder.\nSauvegarde le style pour rendre les changements permanents."
|
||||||
},
|
},
|
||||||
"publish": {
|
|
||||||
"message": "Publier"
|
|
||||||
},
|
|
||||||
"publishPush": {
|
|
||||||
"message": "Pousser la mise à jour"
|
|
||||||
},
|
|
||||||
"publishReconnect": {
|
|
||||||
"message": "Essayez de vous déconnecter et de publier à nouveau"
|
|
||||||
},
|
|
||||||
"publishStyle": {
|
|
||||||
"message": "Publier le style"
|
|
||||||
},
|
|
||||||
"publishUsw": {
|
|
||||||
"message": "Utiliser <userstyles.world>"
|
|
||||||
},
|
|
||||||
"readingStyles": {
|
|
||||||
"message": "Lecture des styles…"
|
|
||||||
},
|
|
||||||
"reload": {
|
|
||||||
"message": "Recharger"
|
|
||||||
},
|
|
||||||
"replace": {
|
"replace": {
|
||||||
"message": "Remplacer"
|
"message": "Remplacer"
|
||||||
},
|
},
|
||||||
|
@ -1064,21 +664,12 @@
|
||||||
"retrieveBckp": {
|
"retrieveBckp": {
|
||||||
"message": "Importer des styles"
|
"message": "Importer des styles"
|
||||||
},
|
},
|
||||||
"retrieveDropboxSync": {
|
|
||||||
"message": "Importer depuis Dropbox"
|
|
||||||
},
|
|
||||||
"saveAsTemplate": {
|
|
||||||
"message": "Sauvegarder comme modèle"
|
|
||||||
},
|
|
||||||
"search": {
|
"search": {
|
||||||
"message": "Rechercher"
|
"message": "Rechercher"
|
||||||
},
|
},
|
||||||
"searchCaseSensitive": {
|
"searchCaseSensitive": {
|
||||||
"message": "Sensible à la casse"
|
"message": "Sensible à la casse"
|
||||||
},
|
},
|
||||||
"searchGlobalStyles": {
|
|
||||||
"message": "Chercher aussi des styles globaux."
|
|
||||||
},
|
|
||||||
"searchNumberOfResults": {
|
"searchNumberOfResults": {
|
||||||
"message": "Nombre de correspondances"
|
"message": "Nombre de correspondances"
|
||||||
},
|
},
|
||||||
|
@ -1094,12 +685,6 @@
|
||||||
"searchResultNoneFound": {
|
"searchResultNoneFound": {
|
||||||
"message": "Aucun style trouvé pour ce site"
|
"message": "Aucun style trouvé pour ce site"
|
||||||
},
|
},
|
||||||
"searchResultNotMatching": {
|
|
||||||
"message": "Ce style est installé mais il ne s'applique pas au lien du site actuel."
|
|
||||||
},
|
|
||||||
"searchResultNotMatchingNote": {
|
|
||||||
"message": "Essaye de demander l’auteur pour ajouter le lien l'URL.\n\nVous pouvez aussi ouvrir le style dans le gestionnaire et l'éditer soit-même,\nmais soyez avertis que cela désactive les mises à jour automatiques pour ce style."
|
|
||||||
},
|
|
||||||
"searchResultRating": {
|
"searchResultRating": {
|
||||||
"message": "Note"
|
"message": "Note"
|
||||||
},
|
},
|
||||||
|
@ -1109,33 +694,24 @@
|
||||||
"searchResultWeeklyCount": {
|
"searchResultWeeklyCount": {
|
||||||
"message": "Installations hebdomadaires"
|
"message": "Installations hebdomadaires"
|
||||||
},
|
},
|
||||||
"searchStylesAll": {
|
"searchStyles": {
|
||||||
"message": "Tout"
|
"message": "Rechercher dans le contenu"
|
||||||
},
|
},
|
||||||
"searchStylesCode": {
|
"searchStylesHelp": {
|
||||||
"message": "Code CSS"
|
"message": "La touche </> place le curseur dans le champ de recherche.\nText brut : cherche parmi le nom, code, URL de la page d’accueil et les sites auquel il s’applique. Les mots avec moins de 3 lettres sont ignorés.\nStyles correspondant à une URL complète : préfixez la recherche avec <url:>, par ex. <url:https://github.com/openstyles/stylus>\nExpressions rationnelles : incluez les slash et les chevrons, par ex. </body.*?\\ba\\b/simguy>\nMots exacts : entourez la requête de guillemets droits, par ex. <\".header ~ div\">"
|
||||||
},
|
|
||||||
"searchStylesMatchUrl": {
|
|
||||||
"message": "Par lien"
|
|
||||||
},
|
|
||||||
"searchStylesMeta": {
|
|
||||||
"message": "Meta-données"
|
|
||||||
},
|
|
||||||
"searchStylesName": {
|
|
||||||
"message": "Nom"
|
|
||||||
},
|
},
|
||||||
"sectionAdd": {
|
"sectionAdd": {
|
||||||
"message": "Ajouter une section"
|
"message": "Ajouter une section"
|
||||||
},
|
},
|
||||||
|
"sectionHelp": {
|
||||||
|
"message": "Les sections vous permettent de définir différentes portions de code correspondant à un même style que vous pouvez appliquer à des ensembles d'URL distincts. Par exemple, un même style appliqué à la page d'accueil peut modifier celle-ci d'une certaine manière et modifier le reste du site Web d'une autre manière."
|
||||||
|
},
|
||||||
"sectionRemove": {
|
"sectionRemove": {
|
||||||
"message": "Supprimer la section"
|
"message": "Supprimer la section"
|
||||||
},
|
},
|
||||||
"sectionRestore": {
|
"sectionRestore": {
|
||||||
"message": "Restaurer la section supprimée"
|
"message": "Restaurer la section supprimée"
|
||||||
},
|
},
|
||||||
"settings": {
|
|
||||||
"message": "Paramètres"
|
|
||||||
},
|
|
||||||
"shortcuts": {
|
"shortcuts": {
|
||||||
"message": "Raccourcis"
|
"message": "Raccourcis"
|
||||||
},
|
},
|
||||||
|
@ -1169,9 +745,6 @@
|
||||||
"styleBeautify": {
|
"styleBeautify": {
|
||||||
"message": "Embellir "
|
"message": "Embellir "
|
||||||
},
|
},
|
||||||
"styleBeautifyHint": {
|
|
||||||
"message": "Astuce : Cliquez-droit le bouton \"Beautify\" ou utilisez le raccourci clavier définit ci-dessous pour beautify sans montrer le panneau."
|
|
||||||
},
|
|
||||||
"styleBeautifyIndentConditional": {
|
"styleBeautifyIndentConditional": {
|
||||||
"message": "Indenter @media, @supports"
|
"message": "Indenter @media, @supports"
|
||||||
},
|
},
|
||||||
|
@ -1187,9 +760,6 @@
|
||||||
"styleEnabledLabel": {
|
"styleEnabledLabel": {
|
||||||
"message": "Activé"
|
"message": "Activé"
|
||||||
},
|
},
|
||||||
"styleExcludeLabel": {
|
|
||||||
"message": "Personnalisation des sites exclus"
|
|
||||||
},
|
|
||||||
"styleFromMozillaFormatError": {
|
"styleFromMozillaFormatError": {
|
||||||
"message": "Échec de l'importation depuis le format Mozilla"
|
"message": "Échec de l'importation depuis le format Mozilla"
|
||||||
},
|
},
|
||||||
|
@ -1226,27 +796,45 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"styleMetaErrorCheckbox": {
|
||||||
|
"message": "Boite à cocher @var invalide : elle doit valoir 0 ou 1"
|
||||||
|
},
|
||||||
|
"styleMetaErrorColor": {
|
||||||
|
"message": "$color$ n'est pas une couleur valide",
|
||||||
|
"placeholders": {
|
||||||
|
"color": {
|
||||||
|
"content": "$1"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"styleMetaErrorPreprocessor": {
|
||||||
|
"message": "@preprocessor non pris en charge : $preprocessor$",
|
||||||
|
"placeholders": {
|
||||||
|
"preprocessor": {
|
||||||
|
"content": "$1"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"styleMetaErrorSelectValueMismatch": {
|
||||||
|
"message": "@select invalide : la valeur n’existe pas dans la liste"
|
||||||
|
},
|
||||||
|
"styleMissingMeta": {
|
||||||
|
"message": "Metadonnée $key$ manquante",
|
||||||
|
"placeholders": {
|
||||||
|
"key": {
|
||||||
|
"content": "$1"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"styleMissingName": {
|
"styleMissingName": {
|
||||||
"message": "Veuillez saisir un nom"
|
"message": "Veuillez saisir un nom"
|
||||||
},
|
},
|
||||||
"styleMozillaFormatHeading": {
|
"styleMozillaFormatHeading": {
|
||||||
"message": "Format Mozilla"
|
"message": "Format Mozilla"
|
||||||
},
|
},
|
||||||
"styleName": {
|
|
||||||
"message": "Nom du style"
|
|
||||||
},
|
|
||||||
"styleNotAppliedRegexpProblemTooltip": {
|
"styleNotAppliedRegexpProblemTooltip": {
|
||||||
"message": "Le style n'a pu s'appliquer en raison d'une utilisation erronée de 'regexp()'"
|
"message": "Le style n'a pu s'appliquer en raison d'une utilisation erronée de 'regexp()'"
|
||||||
},
|
},
|
||||||
"styleNotAppliedSchemeDark": {
|
|
||||||
"message": "Ce style est uniquement appliqué en mode sombre"
|
|
||||||
},
|
|
||||||
"styleNotAppliedSchemeLight": {
|
|
||||||
"message": "Ce style est uniquement appliqué en mode clair"
|
|
||||||
},
|
|
||||||
"stylePreferSchemeLabel": {
|
|
||||||
"message": "Mode clair/sombre"
|
|
||||||
},
|
|
||||||
"styleRegexpInvalidExplanation": {
|
"styleRegexpInvalidExplanation": {
|
||||||
"message": "Quelques règles 'regexp()' qui n’ont pas pu être compilées"
|
"message": "Quelques règles 'regexp()' qui n’ont pas pu être compilées"
|
||||||
},
|
},
|
||||||
|
@ -1256,6 +844,9 @@
|
||||||
"styleRegexpProblemTooltip": {
|
"styleRegexpProblemTooltip": {
|
||||||
"message": "Nombre de sections non appliquées à cause d’une utilisation incorrecte de 'regexp()'"
|
"message": "Nombre de sections non appliquées à cause d’une utilisation incorrecte de 'regexp()'"
|
||||||
},
|
},
|
||||||
|
"styleRegexpTestButton": {
|
||||||
|
"message": "Test d’expression rationnelle"
|
||||||
|
},
|
||||||
"styleRegexpTestFull": {
|
"styleRegexpTestFull": {
|
||||||
"message": "Onglets correspondants"
|
"message": "Onglets correspondants"
|
||||||
},
|
},
|
||||||
|
@ -1277,9 +868,6 @@
|
||||||
"styleSaveLabel": {
|
"styleSaveLabel": {
|
||||||
"message": "Enregistrer"
|
"message": "Enregistrer"
|
||||||
},
|
},
|
||||||
"styleSettings": {
|
|
||||||
"message": "Paramètres du style"
|
|
||||||
},
|
|
||||||
"styleToMozillaFormatHelp": {
|
"styleToMozillaFormatHelp": {
|
||||||
"message": "Le code au format Mozilla peut être utilisé dans Stylish for Firefox et envoyé à userstyles.org."
|
"message": "Le code au format Mozilla peut être utilisé dans Stylish for Firefox et envoyé à userstyles.org."
|
||||||
},
|
},
|
||||||
|
@ -1297,32 +885,12 @@
|
||||||
"styleUpdateDiscardChanges": {
|
"styleUpdateDiscardChanges": {
|
||||||
"message": "Le style a été changé en dehors de l’éditeur. Voulez-vous recharger le style ?"
|
"message": "Le style a été changé en dehors de l’éditeur. Voulez-vous recharger le style ?"
|
||||||
},
|
},
|
||||||
"styleUpdateUrlLabel": {
|
|
||||||
"message": "URL de mise à jour"
|
|
||||||
},
|
|
||||||
"stylusUnavailableForURL": {
|
"stylusUnavailableForURL": {
|
||||||
"message": "Stylus ne fonctionne pas sur les pages de ce genre"
|
"message": "Stylus ne fonctionne pas sur les pages de ce genre"
|
||||||
},
|
},
|
||||||
"stylusUnavailableForURLdetails": {
|
"stylusUnavailableForURLdetails": {
|
||||||
"message": "Par mesure de sécurité, le navigateur interdit aux extensions d’affecter ses pages internes (comme chrome://version, la page nouvel onglet standard de Chrome 61, about:addons, etc.) ainsi que les pages d’autres extensions. Chaque navigateur restreint également l’accès à sa propre galerie d’extensions (tel que le Chrome Web Store ou AMO)."
|
"message": "Par mesure de sécurité, le navigateur interdit aux extensions d’affecter ses pages internes (comme chrome://version, la page nouvel onglet standard de Chrome 61, about:addons, etc.) ainsi que les pages d’autres extensions. Chaque navigateur restreint également l’accès à sa propre galerie d’extensions (tel que le Chrome Web Store ou AMO)."
|
||||||
},
|
},
|
||||||
"syncDropboxDeprecated": {
|
|
||||||
"message": "L´import/export Dropbox est remplacé par une méthode de synchronisation de styles plus avancés dans la page des options"
|
|
||||||
},
|
|
||||||
"syncDropboxStyles": {
|
|
||||||
"message": "Exporter vers Dropbox"
|
|
||||||
},
|
|
||||||
"syncError": {
|
|
||||||
"message": "Synchronisation échouée"
|
|
||||||
},
|
|
||||||
"syncErrorLock": {
|
|
||||||
"message": "Cette base de données est déjà utilisée. Le verrouillage expirera le $TIME$",
|
|
||||||
"placeholders": {
|
|
||||||
"time": {
|
|
||||||
"content": "$1"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"syncStorageErrorSaving": {
|
"syncStorageErrorSaving": {
|
||||||
"message": "La valeur ne peut pas être sauvegardée. Essayez de réduire la quantité de texte."
|
"message": "La valeur ne peut pas être sauvegardée. Essayez de réduire la quantité de texte."
|
||||||
},
|
},
|
||||||
|
@ -1341,23 +909,20 @@
|
||||||
"unreachableAMOHint": {
|
"unreachableAMOHint": {
|
||||||
"message": "Pour autoriser l’accès, ouvrir <about:config>, clic-droit sur la liste, cliquez sur « Nouvelle » puis « Valeur booléenne », coller <privacy.resistFingerprinting.block_mozAddonManager> et cliquer sur OK, <true>, OK, et rechargez la page <addons.mozilla.org>."
|
"message": "Pour autoriser l’accès, ouvrir <about:config>, clic-droit sur la liste, cliquez sur « Nouvelle » puis « Valeur booléenne », coller <privacy.resistFingerprinting.block_mozAddonManager> et cliquer sur OK, <true>, OK, et rechargez la page <addons.mozilla.org>."
|
||||||
},
|
},
|
||||||
|
"unreachableAMOHintNewFF": {
|
||||||
|
"message": "Pour Firefox 60 et suivants, vous devez également supprimer le domaine AMO de <extensions.webextensions.restrictedDomains> dans <about:config>."
|
||||||
|
},
|
||||||
|
"unreachableAMOHintOldFF": {
|
||||||
|
"message": "Seul Firefox 59 et supérieur peut être configuré pour autoriser les WebExtensions à ajouter des éléments de style sur des sites protégés par CSP comme celui-ci."
|
||||||
|
},
|
||||||
"unreachableContentScript": {
|
"unreachableContentScript": {
|
||||||
"message": "Impossible de communiquer avec la page. Essayez de recharger l’onglet."
|
"message": "Impossible de communiquer avec la page. Essayez de recharger l’onglet."
|
||||||
},
|
},
|
||||||
"unreachableFileHint": {
|
"unreachableFileHint": {
|
||||||
"message": "Stylus peut accéder aux URL file:// uniquement si vous cochez la case correspondante pour l’extension Stylus sur la page chrome://extensions."
|
"message": "Stylus peut accéder aux URL file:// uniquement si vous cochez la case correspondante pour l’extension Stylus sur la page chrome://extensions."
|
||||||
},
|
},
|
||||||
"unreachableMozSiteHint": {
|
|
||||||
"message": "Dans Firefox 60 et plus récent vous devez retirer ce domaine de <extensions.webextensions.restrictedDomains> dans <about:config>."
|
|
||||||
},
|
|
||||||
"unreachableMozSiteHintOldFF": {
|
|
||||||
"message": "Seulement dans Firefox 59 et plus récent que les extensions-web peuvent être configurés pour autoriser de rajouter des éléments de style sur les sites protégés CSP comme celui-ci."
|
|
||||||
},
|
|
||||||
"unzipStyles": {
|
|
||||||
"message": "Décompression des styles…"
|
|
||||||
},
|
|
||||||
"updateAllCheckSucceededNoUpdate": {
|
"updateAllCheckSucceededNoUpdate": {
|
||||||
"message": "Aucune mise à jour trouvée."
|
"message": "All styles are up to date."
|
||||||
},
|
},
|
||||||
"updateAllCheckSucceededSomeEdited": {
|
"updateAllCheckSucceededSomeEdited": {
|
||||||
"message": "Certains styles pouvant être mis à jour n’ont pas été vérifiés pour éviter de perdre de potentiels modifications locales. Les mises à jour peuvent être forcées en vérifiant individuellement, ou en lançant une autre vérification pour tous les styles (les modifications locales seront perdues)."
|
"message": "Certains styles pouvant être mis à jour n’ont pas été vérifiés pour éviter de perdre de potentiels modifications locales. Les mises à jour peuvent être forcées en vérifiant individuellement, ou en lançant une autre vérification pour tous les styles (les modifications locales seront perdues)."
|
||||||
|
@ -1397,9 +962,6 @@
|
||||||
"updatesCurrentlyInstalled": {
|
"updatesCurrentlyInstalled": {
|
||||||
"message": "Mises à jour installées :"
|
"message": "Mises à jour installées :"
|
||||||
},
|
},
|
||||||
"uploadingFile": {
|
|
||||||
"message": "Envoi du fichier…"
|
|
||||||
},
|
|
||||||
"usercssAvoidOverwriting": {
|
"usercssAvoidOverwriting": {
|
||||||
"message": "Veuillez changer la valeur de @name ou @namespace afin d'éviter d'écraser un style pré-existant."
|
"message": "Veuillez changer la valeur de @name ou @namespace afin d'éviter d'écraser un style pré-existant."
|
||||||
},
|
},
|
||||||
|
@ -1412,6 +974,9 @@
|
||||||
"usercssReplaceTemplateConfirmation": {
|
"usercssReplaceTemplateConfirmation": {
|
||||||
"message": "Remplacer le modèle par défaut pour les nouveaux styles Usercss par le code actuel ?"
|
"message": "Remplacer le modèle par défaut pour les nouveaux styles Usercss par le code actuel ?"
|
||||||
},
|
},
|
||||||
|
"usercssReplaceTemplateName": {
|
||||||
|
"message": "Un @name vide remplace le modèle par défaut"
|
||||||
|
},
|
||||||
"usercssReplaceTemplateSectionBody": {
|
"usercssReplaceTemplateSectionBody": {
|
||||||
"message": "Insérer le code ici..."
|
"message": "Insérer le code ici..."
|
||||||
},
|
},
|
||||||
|
@ -1423,8 +988,5 @@
|
||||||
},
|
},
|
||||||
"writeStyleForURL": {
|
"writeStyleForURL": {
|
||||||
"message": "cette URL"
|
"message": "cette URL"
|
||||||
},
|
|
||||||
"zipStyles": {
|
|
||||||
"message": "Compression des styles…"
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,52 +0,0 @@
|
||||||
{
|
|
||||||
"addStyleTitle": {
|
|
||||||
"message": "Engadir Estilo"
|
|
||||||
},
|
|
||||||
"alphaChannel": {
|
|
||||||
"message": "Opacidade"
|
|
||||||
},
|
|
||||||
"appliesAdd": {
|
|
||||||
"message": "Engadir"
|
|
||||||
},
|
|
||||||
"appliesDisplay": {
|
|
||||||
"message": "Aplica a: $applies$",
|
|
||||||
"placeholders": {
|
|
||||||
"applies": {
|
|
||||||
"content": "$1"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"appliesDisplayTruncatedSuffix": {
|
|
||||||
"message": "e mais"
|
|
||||||
},
|
|
||||||
"appliesDomainOption": {
|
|
||||||
"message": "URLs no dominio"
|
|
||||||
},
|
|
||||||
"appliesLabel": {
|
|
||||||
"message": "Aplica para"
|
|
||||||
},
|
|
||||||
"appliesLineWidgetWarning": {
|
|
||||||
"message": "Non funciona con CSS minificado"
|
|
||||||
},
|
|
||||||
"appliesRegexpOption": {
|
|
||||||
"message": "URLs que concorden co regexp"
|
|
||||||
},
|
|
||||||
"appliesRemove": {
|
|
||||||
"message": "Suprimir"
|
|
||||||
},
|
|
||||||
"appliesSpecify": {
|
|
||||||
"message": "Especificar"
|
|
||||||
},
|
|
||||||
"appliesToEverything": {
|
|
||||||
"message": "Todo"
|
|
||||||
},
|
|
||||||
"appliesUrlPrefixOption": {
|
|
||||||
"message": "URLs que comecen por"
|
|
||||||
},
|
|
||||||
"applyAllUpdates": {
|
|
||||||
"message": "Aplicar tódalas actualizacións"
|
|
||||||
},
|
|
||||||
"author": {
|
|
||||||
"message": "Autor"
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -3,16 +3,16 @@
|
||||||
"message": "כתוב עיצוב חדש"
|
"message": "כתוב עיצוב חדש"
|
||||||
},
|
},
|
||||||
"addStyleTitle": {
|
"addStyleTitle": {
|
||||||
"message": "הוסף עיצוב"
|
"message": "הוספת עיצוב"
|
||||||
},
|
},
|
||||||
"alphaChannel": {
|
"alphaChannel": {
|
||||||
"message": "שקיפות"
|
"message": "שקיפות"
|
||||||
},
|
},
|
||||||
"appliesAdd": {
|
"appliesAdd": {
|
||||||
"message": "הוסף"
|
"message": "הוספה"
|
||||||
},
|
},
|
||||||
"appliesDisplay": {
|
"appliesDisplay": {
|
||||||
"message": "חל על: $applies$",
|
"message": "מוחל על: $applies$",
|
||||||
"placeholders": {
|
"placeholders": {
|
||||||
"applies": {
|
"applies": {
|
||||||
"content": "$1"
|
"content": "$1"
|
||||||
|
@ -25,11 +25,8 @@
|
||||||
"appliesDomainOption": {
|
"appliesDomainOption": {
|
||||||
"message": "קישורים תחת הדומיין"
|
"message": "קישורים תחת הדומיין"
|
||||||
},
|
},
|
||||||
"appliesHelp": {
|
|
||||||
"message": "השתמש בהגדרות 'חל על' כדי להגביל את כתובות האתרים שהקוד בסעיף זה חל עליהם."
|
|
||||||
},
|
|
||||||
"appliesLabel": {
|
"appliesLabel": {
|
||||||
"message": "חל על"
|
"message": "מוחל על"
|
||||||
},
|
},
|
||||||
"appliesLineWidgetLabel": {
|
"appliesLineWidgetLabel": {
|
||||||
"message": "הצג אינפורמציית 'חל על'"
|
"message": "הצג אינפורמציית 'חל על'"
|
||||||
|
@ -38,10 +35,10 @@
|
||||||
"message": "לא עובד עם CSS מוקטן (minified)"
|
"message": "לא עובד עם CSS מוקטן (minified)"
|
||||||
},
|
},
|
||||||
"appliesRegexpOption": {
|
"appliesRegexpOption": {
|
||||||
"message": "קישורים התואמים את ה־regexp"
|
"message": "קישורים התואמים regexp"
|
||||||
},
|
},
|
||||||
"appliesRemove": {
|
"appliesRemove": {
|
||||||
"message": "הסר"
|
"message": "הסרה"
|
||||||
},
|
},
|
||||||
"appliesRemoveError": {
|
"appliesRemoveError": {
|
||||||
"message": "לא ניתן להסיר את הערך 'חל על' האחרון"
|
"message": "לא ניתן להסיר את הערך 'חל על' האחרון"
|
||||||
|
@ -53,7 +50,7 @@
|
||||||
"message": "כל האתרים"
|
"message": "כל האתרים"
|
||||||
},
|
},
|
||||||
"appliesUrlOption": {
|
"appliesUrlOption": {
|
||||||
"message": "כתובת אתר"
|
"message": "קישור (URL)"
|
||||||
},
|
},
|
||||||
"appliesUrlPrefixOption": {
|
"appliesUrlPrefixOption": {
|
||||||
"message": "קישורים המתחילים ב"
|
"message": "קישורים המתחילים ב"
|
||||||
|
@ -62,11 +59,14 @@
|
||||||
"message": "החל את כל העדכונים"
|
"message": "החל את כל העדכונים"
|
||||||
},
|
},
|
||||||
"author": {
|
"author": {
|
||||||
"message": "מחבר"
|
"message": "כותב"
|
||||||
},
|
},
|
||||||
"backupButtons": {
|
"backupButtons": {
|
||||||
"message": "גיבוי"
|
"message": "גיבוי"
|
||||||
},
|
},
|
||||||
|
"backupMessage": {
|
||||||
|
"message": "בחר קובץ או גרור ושחרר אותו בדף זה."
|
||||||
|
},
|
||||||
"bckpInstStyles": {
|
"bckpInstStyles": {
|
||||||
"message": "ייצא עיצובים"
|
"message": "ייצא עיצובים"
|
||||||
},
|
},
|
||||||
|
@ -106,26 +106,17 @@
|
||||||
"cm_lineWrapping": {
|
"cm_lineWrapping": {
|
||||||
"message": "עטיפת מילים"
|
"message": "עטיפת מילים"
|
||||||
},
|
},
|
||||||
"cm_linter": {
|
|
||||||
"message": "עורך שגיאות CSS"
|
|
||||||
},
|
|
||||||
"cm_matchHighlight": {
|
"cm_matchHighlight": {
|
||||||
"message": "הדגש"
|
"message": "הדגש"
|
||||||
},
|
},
|
||||||
"cm_matchHighlightSelection": {
|
"cm_matchHighlightSelection": {
|
||||||
"message": "בחירה בלבד"
|
"message": "בחירה בלבד"
|
||||||
},
|
},
|
||||||
"cm_matchHighlightToken": {
|
|
||||||
"message": "תג תחת הסמן"
|
|
||||||
},
|
|
||||||
"cm_resizeGripHint": {
|
"cm_resizeGripHint": {
|
||||||
"message": "דאבל קליק להגדלה מירבית/איפוס הגובה"
|
"message": "דאבל קליק להגדלה מירבית/איפוס הגובה"
|
||||||
},
|
},
|
||||||
"cm_selectByTokens": {
|
"cm_selectByTokens": {
|
||||||
"message": "דאבל קליק בוחר תגים"
|
"message": "דאבל קליק בוחר tokens"
|
||||||
},
|
|
||||||
"cm_selectByTokensTooltip": {
|
|
||||||
"message": "דוגמאות לתגים: .foo-bar-2 #aabbcc 0.32 !important\nבמצב מושבת: נבחרות מילים המופרדות בין פיסוק."
|
|
||||||
},
|
},
|
||||||
"cm_smartIndent": {
|
"cm_smartIndent": {
|
||||||
"message": "השתמש בהזחה חכמה"
|
"message": "השתמש בהזחה חכמה"
|
||||||
|
@ -136,9 +127,6 @@
|
||||||
"cm_theme": {
|
"cm_theme": {
|
||||||
"message": "ערכת נושא"
|
"message": "ערכת נושא"
|
||||||
},
|
},
|
||||||
"colorpickerSwitchFormatTooltip": {
|
|
||||||
"message": "החלף פורמטים: HEX -> RGB -> HSL.\nלחץ Shift כדי להפוך את הכיוון.\nגם באמצעות מקשי PgUp (PageUp), PgDn (PageDown)."
|
|
||||||
},
|
|
||||||
"colorpickerTooltip": {
|
"colorpickerTooltip": {
|
||||||
"message": "פתח את פלטת בחירת הצבעים"
|
"message": "פתח את פלטת בחירת הצבעים"
|
||||||
},
|
},
|
||||||
|
@ -184,27 +172,12 @@
|
||||||
"confirmYes": {
|
"confirmYes": {
|
||||||
"message": "כן"
|
"message": "כן"
|
||||||
},
|
},
|
||||||
"connectingDropbox": {
|
|
||||||
"message": "מתחבר אל Dropbox..."
|
|
||||||
},
|
|
||||||
"connectingDropboxNotAllowed": {
|
|
||||||
"message": "התחברות אל Dropbox זמינה רק בהרחבות המותקנות ישירות מחנות האינטרנט"
|
|
||||||
},
|
|
||||||
"copied": {
|
|
||||||
"message": "הועתק אל לוח ההעתקה"
|
|
||||||
},
|
|
||||||
"copy": {
|
|
||||||
"message": "העתק אל לוח ההעתקה"
|
|
||||||
},
|
|
||||||
"dateInstalled": {
|
"dateInstalled": {
|
||||||
"message": "תאריך התקנה"
|
"message": "תאריך התקנה"
|
||||||
},
|
},
|
||||||
"dateUpdated": {
|
"dateUpdated": {
|
||||||
"message": "תאריך עדכון"
|
"message": "תאריך עדכון"
|
||||||
},
|
},
|
||||||
"dbError": {
|
|
||||||
"message": "אירעה שגיאה בשימוש במסד הנתונים של Stylus. האם ברצונך לבקר בדף אינטרנט עם פתרונות אפשריים?"
|
|
||||||
},
|
|
||||||
"defaultTheme": {
|
"defaultTheme": {
|
||||||
"message": "ברירת מחדל"
|
"message": "ברירת מחדל"
|
||||||
},
|
},
|
||||||
|
@ -214,9 +187,6 @@
|
||||||
"deleteStyleLabel": {
|
"deleteStyleLabel": {
|
||||||
"message": "מחק"
|
"message": "מחק"
|
||||||
},
|
},
|
||||||
"description": {
|
|
||||||
"message": "עיצוב מחדש של האינטרנט באמצעות Stylus, מנהל סגנונות משתמש. Stylus מאפשר לך להתקין בקלות עיצובים וערכות נושא עבור אתרים פופולריים רבים."
|
|
||||||
},
|
|
||||||
"disableAllStyles": {
|
"disableAllStyles": {
|
||||||
"message": "השבת את כל העיצובים"
|
"message": "השבת את כל העיצובים"
|
||||||
},
|
},
|
||||||
|
@ -230,13 +200,13 @@
|
||||||
"message": "מחק"
|
"message": "מחק"
|
||||||
},
|
},
|
||||||
"editGotoLine": {
|
"editGotoLine": {
|
||||||
"message": "גש לשורה (או line:col)"
|
"message": "Goto לשורה (או line:col)"
|
||||||
},
|
},
|
||||||
"editStyleHeading": {
|
"editStyleHeading": {
|
||||||
"message": "ערוך עיצוב"
|
"message": "עריכת עיצוב"
|
||||||
},
|
},
|
||||||
"editStyleLabel": {
|
"editStyleLabel": {
|
||||||
"message": "ערוך"
|
"message": "עריכה"
|
||||||
},
|
},
|
||||||
"editStyleTitle": {
|
"editStyleTitle": {
|
||||||
"message": "עריכת העיצוב $stylename$",
|
"message": "עריכת העיצוב $stylename$",
|
||||||
|
@ -246,21 +216,15 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"editorStylesButton": {
|
||||||
|
"message": "מצא עיצובים לעורך"
|
||||||
|
},
|
||||||
"enableStyleLabel": {
|
"enableStyleLabel": {
|
||||||
"message": "אפשר"
|
"message": "אפשר"
|
||||||
},
|
},
|
||||||
"excludeStyleByDomainLabel": {
|
|
||||||
"message": "אל תכלול את הדומיין הנוכחי"
|
|
||||||
},
|
|
||||||
"excludeStyleByUrlLabel": {
|
|
||||||
"message": "אל תכלול את כתובת האתר הנוכחית"
|
|
||||||
},
|
|
||||||
"exportLabel": {
|
"exportLabel": {
|
||||||
"message": "ייצא"
|
"message": "ייצא"
|
||||||
},
|
},
|
||||||
"exportSavedSuccess": {
|
|
||||||
"message": "קובץ נשמר בהצלחה"
|
|
||||||
},
|
|
||||||
"externalFeedback": {
|
"externalFeedback": {
|
||||||
"message": "חוות דעת"
|
"message": "חוות דעת"
|
||||||
},
|
},
|
||||||
|
@ -273,26 +237,12 @@
|
||||||
"externalSupport": {
|
"externalSupport": {
|
||||||
"message": "תמיכה"
|
"message": "תמיכה"
|
||||||
},
|
},
|
||||||
"externalUsercssDocument": {
|
|
||||||
"message": "תיעוד ל־Usercss"
|
|
||||||
},
|
|
||||||
"filteredStyles": {
|
|
||||||
"message": "$numShown$ מתוך $numTotal$ סך־הכל",
|
|
||||||
"placeholders": {
|
|
||||||
"numShown": {
|
|
||||||
"content": "$1"
|
|
||||||
},
|
|
||||||
"numTotal": {
|
|
||||||
"content": "$2"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"filteredStylesAllHidden": {
|
|
||||||
"message": "מסננים שהוחלו כעת אינם תואמים עיצובים"
|
|
||||||
},
|
|
||||||
"findStyles": {
|
"findStyles": {
|
||||||
"message": "מצא עיצובים"
|
"message": "מצא עיצובים"
|
||||||
},
|
},
|
||||||
|
"findStylesInline": {
|
||||||
|
"message": "מוטבע"
|
||||||
|
},
|
||||||
"genericAdd": {
|
"genericAdd": {
|
||||||
"message": "הוספה"
|
"message": "הוספה"
|
||||||
},
|
},
|
||||||
|
@ -329,9 +279,6 @@
|
||||||
"genericUnknown": {
|
"genericUnknown": {
|
||||||
"message": "לא ידוע"
|
"message": "לא ידוע"
|
||||||
},
|
},
|
||||||
"gettingStyles": {
|
|
||||||
"message": "טוען את כל העיצובים..."
|
|
||||||
},
|
|
||||||
"helpAlt": {
|
"helpAlt": {
|
||||||
"message": "עזרה"
|
"message": "עזרה"
|
||||||
},
|
},
|
||||||
|
@ -341,9 +288,6 @@
|
||||||
"helpKeyMapHotkey": {
|
"helpKeyMapHotkey": {
|
||||||
"message": "לחץ על המקש החם"
|
"message": "לחץ על המקש החם"
|
||||||
},
|
},
|
||||||
"hostDisabled": {
|
|
||||||
"message": "מארח זה הושבת בגלל באג בגרסה הנוכחית של הדפדפן בו נעשה שימוש"
|
|
||||||
},
|
|
||||||
"importAppendLabel": {
|
"importAppendLabel": {
|
||||||
"message": "צרף לעיצוב"
|
"message": "צרף לעיצוב"
|
||||||
},
|
},
|
||||||
|
@ -356,26 +300,14 @@
|
||||||
"importReplaceLabel": {
|
"importReplaceLabel": {
|
||||||
"message": "דרוס עיצוב"
|
"message": "דרוס עיצוב"
|
||||||
},
|
},
|
||||||
"importReplaceTooltip": {
|
|
||||||
"message": "הסר את התוכן של הסגנון הנוכחי והחלף אותו עם העיצוב המיובא"
|
|
||||||
},
|
|
||||||
"importReportLegendAdded": {
|
"importReportLegendAdded": {
|
||||||
"message": "נוספו"
|
"message": "נוספו"
|
||||||
},
|
},
|
||||||
"importReportLegendIdentical": {
|
|
||||||
"message": "זהה דולג"
|
|
||||||
},
|
|
||||||
"importReportLegendInvalid": {
|
|
||||||
"message": "לא חוקי דולג"
|
|
||||||
},
|
|
||||||
"importReportLegendUpdatedBoth": {
|
|
||||||
"message": "מטא דאטה והקוד עודכנו"
|
|
||||||
},
|
|
||||||
"importReportLegendUpdatedCode": {
|
"importReportLegendUpdatedCode": {
|
||||||
"message": "קודים עודכנו"
|
"message": "קודים עודכנו"
|
||||||
},
|
},
|
||||||
"importReportLegendUpdatedMeta": {
|
"importReportLegendUpdatedMeta": {
|
||||||
"message": "מטא דאטה עודכן"
|
"message": "מידע meta עודכנו"
|
||||||
},
|
},
|
||||||
"importReportTitle": {
|
"importReportTitle": {
|
||||||
"message": "סיום ייבוא עיצובים"
|
"message": "סיום ייבוא עיצובים"
|
||||||
|
@ -404,17 +336,12 @@
|
||||||
"installUpdate": {
|
"installUpdate": {
|
||||||
"message": "התקן עדכון"
|
"message": "התקן עדכון"
|
||||||
},
|
},
|
||||||
"installUpdateFrom": {
|
|
||||||
"message": "נכון לעכשיו הסגנון מעודכן מ־ $url$",
|
|
||||||
"placeholders": {
|
|
||||||
"url": {
|
|
||||||
"content": "$1"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"installUpdateFromLabel": {
|
"installUpdateFromLabel": {
|
||||||
"message": "בדוק עדכונים"
|
"message": "בדוק עדכונים"
|
||||||
},
|
},
|
||||||
|
"installUpdateUnavailable": {
|
||||||
|
"message": "על־מנת לאפשר בדיקת עדכונים, אנא שחרר את הקובץ על רצועת הכרטיסיות או ציין @updateURL ב־metadata של העיצוב."
|
||||||
|
},
|
||||||
"license": {
|
"license": {
|
||||||
"message": "רישיון"
|
"message": "רישיון"
|
||||||
},
|
},
|
||||||
|
@ -427,14 +354,6 @@
|
||||||
"linkTranslate": {
|
"linkTranslate": {
|
||||||
"message": "תרגום"
|
"message": "תרגום"
|
||||||
},
|
},
|
||||||
"linterCSSLintIncompatible": {
|
|
||||||
"message": "עורך שגיאות ה־CSS אינו תומך ב־ $preprocessorname$ כמעבד מקדים",
|
|
||||||
"placeholders": {
|
|
||||||
"preprocessorname": {
|
|
||||||
"content": "$1"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"linterCSSLintSettings": {
|
"linterCSSLintSettings": {
|
||||||
"message": "(הגדר כלל כ: 0 = מושבת; 1 = אזהרה; 2 = שגיאה)"
|
"message": "(הגדר כלל כ: 0 = מושבת; 1 = אזהרה; 2 = שגיאה)"
|
||||||
},
|
},
|
||||||
|
@ -449,9 +368,6 @@
|
||||||
"linterConfigTooltip": {
|
"linterConfigTooltip": {
|
||||||
"message": "לחץ להגדרת linter זה"
|
"message": "לחץ להגדרת linter זה"
|
||||||
},
|
},
|
||||||
"linterInvalidConfigError": {
|
|
||||||
"message": "לא נשמר עקב הגדרות תצורה לא חוקיות אלה:"
|
|
||||||
},
|
|
||||||
"linterIssues": {
|
"linterIssues": {
|
||||||
"message": "תקלות"
|
"message": "תקלות"
|
||||||
},
|
},
|
||||||
|
@ -478,12 +394,18 @@
|
||||||
"liveReloadLabel": {
|
"liveReloadLabel": {
|
||||||
"message": "רענון לייב (live)"
|
"message": "רענון לייב (live)"
|
||||||
},
|
},
|
||||||
|
"liveReloadUnavailable": {
|
||||||
|
"message": "על־מנת לאפשר רענון לייב (live), אנא שחרר את הקובץ על רצועת הכרטיסיות (האזור בו כותרות הכרטיסיות מוצגות)."
|
||||||
|
},
|
||||||
"manageFavicons": {
|
"manageFavicons": {
|
||||||
"message": "הצגת אייקונים בעמודת 'חל על'"
|
"message": "הצגת אייקונים בעמודת 'חל על'"
|
||||||
},
|
},
|
||||||
"manageFaviconsGray": {
|
"manageFaviconsGray": {
|
||||||
"message": "האפרת האייקונים"
|
"message": "האפרת האייקונים"
|
||||||
},
|
},
|
||||||
|
"manageFaviconsHelp": {
|
||||||
|
"message": "Stylus משתמש בשירות חיצוני https://www.google.com/s2/favicons"
|
||||||
|
},
|
||||||
"manageFilters": {
|
"manageFilters": {
|
||||||
"message": "מסננים"
|
"message": "מסננים"
|
||||||
},
|
},
|
||||||
|
@ -496,9 +418,6 @@
|
||||||
"manageNewStyleAsUsercss": {
|
"manageNewStyleAsUsercss": {
|
||||||
"message": "כ־Usercss"
|
"message": "כ־Usercss"
|
||||||
},
|
},
|
||||||
"manageNewUI": {
|
|
||||||
"message": "פריסת ממשק משתמש ניהול חדשה"
|
|
||||||
},
|
|
||||||
"manageOnlyDisabled": {
|
"manageOnlyDisabled": {
|
||||||
"message": "רק עיצובים מושבתים"
|
"message": "רק עיצובים מושבתים"
|
||||||
},
|
},
|
||||||
|
@ -511,9 +430,6 @@
|
||||||
"manageOnlyLocal": {
|
"manageOnlyLocal": {
|
||||||
"message": "רק עיצובים שנוצרו באופן מקומי"
|
"message": "רק עיצובים שנוצרו באופן מקומי"
|
||||||
},
|
},
|
||||||
"manageOnlyLocalTooltip": {
|
|
||||||
"message": "(הסגנונות שלא הותקנו דרך דף userstyles.org)"
|
|
||||||
},
|
|
||||||
"manageOnlyNonUsercss": {
|
"manageOnlyNonUsercss": {
|
||||||
"message": "רק לא עיצובי Usercss"
|
"message": "רק לא עיצובי Usercss"
|
||||||
},
|
},
|
||||||
|
@ -526,39 +442,16 @@
|
||||||
"menuShowBadge": {
|
"menuShowBadge": {
|
||||||
"message": "הצג כמות עיצובים מאופשרים"
|
"message": "הצג כמות עיצובים מאופשרים"
|
||||||
},
|
},
|
||||||
"meta_invalidCheckboxDefault": {
|
|
||||||
"message": "תיבת סימון @var לא חוקית: הערך חייב להיות 0 או 1"
|
|
||||||
},
|
|
||||||
"meta_invalidNumber": {
|
|
||||||
"message": "נדרש מספר"
|
|
||||||
},
|
|
||||||
"meta_invalidString": {
|
|
||||||
"message": "נדרשת מחרוזת בתוך גרשיים"
|
|
||||||
},
|
|
||||||
"meta_invalidWord": {
|
|
||||||
"message": "נדרשת מילה"
|
|
||||||
},
|
|
||||||
"meta_missingChar": {
|
|
||||||
"message": "נדרשים תווים: $chars$",
|
|
||||||
"placeholders": {
|
|
||||||
"chars": {
|
|
||||||
"content": "$1"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"meta_missingEOT": {
|
|
||||||
"message": "נדרש מידע EOT"
|
|
||||||
},
|
|
||||||
"noFileToImport": {
|
|
||||||
"message": "כדי לייבא את הסגנונות שלך, עליך לייצא אותם תחילה."
|
|
||||||
},
|
|
||||||
"noStylesForSite": {
|
"noStylesForSite": {
|
||||||
"message": "לא הותקנו עיצובים עבור אתר זה."
|
"message": "לא הותקנו עיצובים עבור אתר זה."
|
||||||
},
|
},
|
||||||
"openManage": {
|
"openManage": {
|
||||||
"message": "ניהול"
|
"message": "ניהול"
|
||||||
},
|
},
|
||||||
"openOptions": {
|
"openOptionsManage": {
|
||||||
|
"message": "אפשרויות UI"
|
||||||
|
},
|
||||||
|
"openOptionsPopup": {
|
||||||
"message": "אפשרויות"
|
"message": "אפשרויות"
|
||||||
},
|
},
|
||||||
"openStylesManager": {
|
"openStylesManager": {
|
||||||
|
@ -571,16 +464,7 @@
|
||||||
"message": "מתקדם"
|
"message": "מתקדם"
|
||||||
},
|
},
|
||||||
"optionsAdvancedContextDelete": {
|
"optionsAdvancedContextDelete": {
|
||||||
"message": "הוספת 'מחק' בתפריט העורך"
|
"message": "הוספת 'מחיקה' בתפריט העורך"
|
||||||
},
|
|
||||||
"optionsAdvancedExposeIframes": {
|
|
||||||
"message": "חשוף iframes באמצעות HTML [stylus-iframe]"
|
|
||||||
},
|
|
||||||
"optionsAdvancedExposeIframesNote": {
|
|
||||||
"message": "חושף את תחום האתר העליון בכל iframe.\nמאפשר כתיבת CSS ספציפית ל־iframe כך:\nhtml[stylus-iframe$$=\"twitter.com\"] h1 { display:none }"
|
|
||||||
},
|
|
||||||
"optionsAdvancedNewStyleAsUsercss": {
|
|
||||||
"message": "כתוב עיצוב חדש בתור usercss"
|
|
||||||
},
|
},
|
||||||
"optionsBadgeDisabled": {
|
"optionsBadgeDisabled": {
|
||||||
"message": "צבע רקע בעת השבתה"
|
"message": "צבע רקע בעת השבתה"
|
||||||
|
@ -603,9 +487,6 @@
|
||||||
"optionsCustomizePopup": {
|
"optionsCustomizePopup": {
|
||||||
"message": "חלון קופץ"
|
"message": "חלון קופץ"
|
||||||
},
|
},
|
||||||
"optionsCustomizeSync": {
|
|
||||||
"message": "סנכרון לענן"
|
|
||||||
},
|
|
||||||
"optionsCustomizeUpdate": {
|
"optionsCustomizeUpdate": {
|
||||||
"message": "עדכונים"
|
"message": "עדכונים"
|
||||||
},
|
},
|
||||||
|
@ -631,75 +512,14 @@
|
||||||
"message": "איפוס האפשרויות לערכי ברירת המחדל"
|
"message": "איפוס האפשרויות לערכי ברירת המחדל"
|
||||||
},
|
},
|
||||||
"optionsResetButton": {
|
"optionsResetButton": {
|
||||||
"message": "אפס אפשרויות"
|
"message": "איפוס האפשרויות"
|
||||||
},
|
},
|
||||||
"optionsSubheading": {
|
"optionsSubheading": {
|
||||||
"message": "אפשרויות נוספות"
|
"message": "אפשרויות נוספות"
|
||||||
},
|
},
|
||||||
"optionsSyncConnect": {
|
|
||||||
"message": "התחבר"
|
|
||||||
},
|
|
||||||
"optionsSyncDisconnect": {
|
|
||||||
"message": "התנתק"
|
|
||||||
},
|
|
||||||
"optionsSyncLogin": {
|
|
||||||
"message": "היכנס"
|
|
||||||
},
|
|
||||||
"optionsSyncNone": {
|
|
||||||
"message": "ללא"
|
|
||||||
},
|
|
||||||
"optionsSyncStatusConnected": {
|
|
||||||
"message": "מחובר"
|
|
||||||
},
|
|
||||||
"optionsSyncStatusConnecting": {
|
|
||||||
"message": "מתחבר..."
|
|
||||||
},
|
|
||||||
"optionsSyncStatusDisconnected": {
|
|
||||||
"message": "מנותק"
|
|
||||||
},
|
|
||||||
"optionsSyncStatusDisconnecting": {
|
|
||||||
"message": "מתנתק..."
|
|
||||||
},
|
|
||||||
"optionsSyncStatusPull": {
|
|
||||||
"message": "מושך סגנון $loaded$ מתוך $total$",
|
|
||||||
"placeholders": {
|
|
||||||
"loaded": {
|
|
||||||
"content": "$1"
|
|
||||||
},
|
|
||||||
"total": {
|
|
||||||
"content": "$2"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"optionsSyncStatusPush": {
|
|
||||||
"message": "מפרסם סגנון $loaded$ מתוך $total$",
|
|
||||||
"placeholders": {
|
|
||||||
"loaded": {
|
|
||||||
"content": "$1"
|
|
||||||
},
|
|
||||||
"total": {
|
|
||||||
"content": "$2"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"optionsSyncStatusSyncing": {
|
|
||||||
"message": "מסנכרן..."
|
|
||||||
},
|
|
||||||
"optionsSyncSyncNow": {
|
|
||||||
"message": "סנכרן כעת"
|
|
||||||
},
|
|
||||||
"optionsSyncUrl": {
|
|
||||||
"message": "כתובת אתר"
|
|
||||||
},
|
|
||||||
"optionsUpdateImportNote": {
|
|
||||||
"message": "בעת ייבוא גיבויים לסגנון מגרסה ישנה או מ־Stylish, בדוק באופן חד פעמי אם יש עדכונים באופן ידני במנהל הסגנונות כדי לוודא שכל הסגנונות מעודכנים."
|
|
||||||
},
|
|
||||||
"optionsUpdateInterval": {
|
"optionsUpdateInterval": {
|
||||||
"message": "עדכון אוטומטי של Userstyle בשעות (הגדר 0 להשבתה)"
|
"message": "עדכון אוטומטי של Userstyle בשעות (הגדר 0 להשבתה)"
|
||||||
},
|
},
|
||||||
"overwriteFileExport": {
|
|
||||||
"message": "האם ברצונך להחליף קובץ קיים?"
|
|
||||||
},
|
|
||||||
"paginationCurrent": {
|
"paginationCurrent": {
|
||||||
"message": "הדף הנוכחי"
|
"message": "הדף הנוכחי"
|
||||||
},
|
},
|
||||||
|
@ -715,35 +535,20 @@
|
||||||
"paginationTotal": {
|
"paginationTotal": {
|
||||||
"message": "סה״כ דפים"
|
"message": "סה״כ דפים"
|
||||||
},
|
},
|
||||||
"parseUsercssError": {
|
|
||||||
"message": "Stylus נכשל בניתוח usercss:"
|
|
||||||
},
|
|
||||||
"popupBorders": {
|
"popupBorders": {
|
||||||
"message": "הוספת שוליים לבנים בצדדים"
|
"message": "הוספת שוליים לבנים בצדדים"
|
||||||
},
|
},
|
||||||
"popupBordersTooltip": {
|
|
||||||
"message": "שימושי לעיצובים כהים ב־Chrome החדש מכיוון שהוא כבר לא מצייר את גבולות הצד"
|
|
||||||
},
|
|
||||||
"popupHotkeysTooltip": {
|
"popupHotkeysTooltip": {
|
||||||
"message": "לחץ על־מנת לצפות במקשים החמים הזמינים"
|
"message": "לחץ על־מנת לצפות במקשים החמים הזמינים"
|
||||||
},
|
},
|
||||||
"popupManageTooltip": {
|
|
||||||
"message": "לחיצה על Shift או מקש ימני בעכבר פותח את מנהל העיצובים"
|
|
||||||
},
|
|
||||||
"popupMenuButtonTooltip": {
|
|
||||||
"message": "תפריט פעולות"
|
|
||||||
},
|
|
||||||
"popupOpenEditInWindow": {
|
"popupOpenEditInWindow": {
|
||||||
"message": "פתח את העורך בחלון חדש"
|
"message": "פתח את העורך בחלון חדש"
|
||||||
},
|
},
|
||||||
"popupOpenEditInWindowTooltip": {
|
|
||||||
"message": "מופעל גם על ידי ניתוק לשונית העורך מחלון הדפדפן,\nומושבת על ידי חיבור לשונית עורך יחידה לחלון אחר."
|
|
||||||
},
|
|
||||||
"popupStylesFirst": {
|
"popupStylesFirst": {
|
||||||
"message": "עיצובים לפני הפקודות"
|
"message": "עיצובים לפני הפקודות"
|
||||||
},
|
},
|
||||||
"prefShowBadge": {
|
"prefShowBadge": {
|
||||||
"message": "מספר העיצובים המאופשרים באתר הנוכחי"
|
"message": "מֿמספר העיצובים המאופשרים באתר הנוכחי"
|
||||||
},
|
},
|
||||||
"previewLabel": {
|
"previewLabel": {
|
||||||
"message": "תצוגת לייב (live)"
|
"message": "תצוגת לייב (live)"
|
||||||
|
@ -751,9 +556,6 @@
|
||||||
"previewTooltip": {
|
"previewTooltip": {
|
||||||
"message": "החלת השינויים באופן זמני ללא שמירה.\nשמור את העיצוב על־מנת להפוך את השינויים לקבועים."
|
"message": "החלת השינויים באופן זמני ללא שמירה.\nשמור את העיצוב על־מנת להפוך את השינויים לקבועים."
|
||||||
},
|
},
|
||||||
"readingStyles": {
|
|
||||||
"message": "קורא עיצובים..."
|
|
||||||
},
|
|
||||||
"replace": {
|
"replace": {
|
||||||
"message": "החלף"
|
"message": "החלף"
|
||||||
},
|
},
|
||||||
|
@ -766,9 +568,6 @@
|
||||||
"retrieveBckp": {
|
"retrieveBckp": {
|
||||||
"message": "ייבוא עיצובים"
|
"message": "ייבוא עיצובים"
|
||||||
},
|
},
|
||||||
"retrieveDropboxSync": {
|
|
||||||
"message": "Dropbox ייבוא"
|
|
||||||
},
|
|
||||||
"search": {
|
"search": {
|
||||||
"message": "חיפוש"
|
"message": "חיפוש"
|
||||||
},
|
},
|
||||||
|
@ -799,21 +598,12 @@
|
||||||
"searchResultWeeklyCount": {
|
"searchResultWeeklyCount": {
|
||||||
"message": "התקנות שבועיות"
|
"message": "התקנות שבועיות"
|
||||||
},
|
},
|
||||||
"sectionAdd": {
|
|
||||||
"message": "הוסף מקטע נוסף"
|
|
||||||
},
|
|
||||||
"sectionCode": {
|
|
||||||
"message": "קוד"
|
|
||||||
},
|
|
||||||
"sectionRemove": {
|
"sectionRemove": {
|
||||||
"message": "הסר סעיף"
|
"message": "הסר סעיף"
|
||||||
},
|
},
|
||||||
"sectionRestore": {
|
"sectionRestore": {
|
||||||
"message": "שחזר סעיף שהוסר"
|
"message": "שחזר סעיף שהוסר"
|
||||||
},
|
},
|
||||||
"sections": {
|
|
||||||
"message": "סעיפים"
|
|
||||||
},
|
|
||||||
"shortcuts": {
|
"shortcuts": {
|
||||||
"message": "קיצורי מקשים"
|
"message": "קיצורי מקשים"
|
||||||
},
|
},
|
||||||
|
@ -829,83 +619,53 @@
|
||||||
"sortLabel": {
|
"sortLabel": {
|
||||||
"message": "בחר שיטת מיון להחלה על העיצובים המותקנים"
|
"message": "בחר שיטת מיון להחלה על העיצובים המותקנים"
|
||||||
},
|
},
|
||||||
"sortStylesHelpTitle": {
|
|
||||||
"message": "סידור תכנים"
|
|
||||||
},
|
|
||||||
"styleBadRegexp": {
|
"styleBadRegexp": {
|
||||||
"message": "ביטוי ה־regexp לא תקין."
|
"message": "ביטוי ה־Regexp לא תקין."
|
||||||
},
|
},
|
||||||
"styleBeautify": {
|
"styleBeautify": {
|
||||||
"message": "ייפה CSS"
|
"message": "ייפה CSS"
|
||||||
},
|
},
|
||||||
"styleBeautifyIndentConditional": {
|
|
||||||
"message": "הזחת @media, @supports"
|
|
||||||
},
|
|
||||||
"styleBeautifyPreserveNewlines": {
|
|
||||||
"message": "שמור שורות חדשות"
|
|
||||||
},
|
|
||||||
"styleCancelEditLabel": {
|
"styleCancelEditLabel": {
|
||||||
"message": "חזרה לניהול"
|
"message": "חזרה לניהול"
|
||||||
},
|
},
|
||||||
"styleChangesNotSaved": {
|
|
||||||
"message": "ביצעת שינויים בסגנון זה מבלי לשמור."
|
|
||||||
},
|
|
||||||
"styleEnabledLabel": {
|
"styleEnabledLabel": {
|
||||||
"message": "מאופשר"
|
"message": "מאופשר"
|
||||||
},
|
},
|
||||||
"styleFromMozillaFormatError": {
|
|
||||||
"message": "הייבוא נכשל מ־Mozilla format"
|
|
||||||
},
|
|
||||||
"styleFromMozillaFormatPrompt": {
|
"styleFromMozillaFormatPrompt": {
|
||||||
"message": "הדבק את הקוד ב־Mozilla-format"
|
"message": "הדבק את הקוד ב־Mozilla-format"
|
||||||
},
|
},
|
||||||
"styleInstall": {
|
"styleMetaErrorColor": {
|
||||||
"message": "להתקין ';$stylename$' אל Stylus?",
|
"message": "$color$ הוא צבע לא תקין",
|
||||||
"placeholders": {
|
"placeholders": {
|
||||||
"stylename": {
|
"color": {
|
||||||
"content": "$1"
|
"content": "$1"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"styleInstallFailed": {
|
"styleMetaErrorPreprocessor": {
|
||||||
"message": "נכשל בהתקנת העיצוב!\n$error$",
|
"message": "@preprocessor לא נתמך: $preprocessor$",
|
||||||
"placeholders": {
|
"placeholders": {
|
||||||
"error": {
|
"preprocessor": {
|
||||||
"content": "$1"
|
"content": "$1"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"styleInstallOverwrite": {
|
"styleMetaErrorSelectValueMismatch": {
|
||||||
"message": "';$stylename$' כבר מותקן. האם לדרוס?\nגרסה: $oldVersion$ -> $newVersion$",
|
"message": "הערך @select: לא קיים ברשימה"
|
||||||
|
},
|
||||||
|
"styleMissingMeta": {
|
||||||
|
"message": "@$key$ metadata חסרים",
|
||||||
"placeholders": {
|
"placeholders": {
|
||||||
"newVersion": {
|
"key": {
|
||||||
"content": "$3"
|
|
||||||
},
|
|
||||||
"oldVersion": {
|
|
||||||
"content": "$2"
|
|
||||||
},
|
|
||||||
"stylename": {
|
|
||||||
"content": "$1"
|
"content": "$1"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"styleMissingName": {
|
"styleMissingName": {
|
||||||
"message": "הזן שם"
|
"message": "אנא הזן שם"
|
||||||
},
|
},
|
||||||
"styleMozillaFormatHeading": {
|
"styleRegexpTestButton": {
|
||||||
"message": "Mozilla-format"
|
"message": "בדוק RegExp"
|
||||||
},
|
|
||||||
"styleNotAppliedRegexpProblemTooltip": {
|
|
||||||
"message": "הסגנון לא הוחל בגלל השימוש בו שגוי ב־'regexp()'"
|
|
||||||
},
|
|
||||||
"styleRegexpInvalidExplanation": {
|
|
||||||
"message": "לא היה ניתן להחיל מספר כללי 'regexp()' כלל."
|
|
||||||
},
|
|
||||||
"styleRegexpPartialExplanation": {
|
|
||||||
"message": "סגנון זה משתמש ב- regexps תואם חלקית בניגוד למפרט <a href='https://developer.mozilla.org/docs/Web/CSS/@document'>CSS4 @document</a> המצריך התאמה מלאה של כתובת אתר. קטעי ה־CSS המושפעים לא הוחלו על הדף. סגנון זה נוצר ככל הנראה ב־Stylish-for-Chrome שבודק באופן שגוי את כללי 'regexp()' מאז הגרסה הראשונה (באג ידוע)."
|
|
||||||
},
|
|
||||||
"styleRegexpProblemTooltip": {
|
|
||||||
"message": "מספר המקטעים שלא הוחלו עקב שימוש שגוי ב־'regexp()'"
|
|
||||||
},
|
},
|
||||||
"styleRegexpTestFull": {
|
"styleRegexpTestFull": {
|
||||||
"message": "כרטיסיות תואמות"
|
"message": "כרטיסיות תואמות"
|
||||||
|
@ -916,20 +676,14 @@
|
||||||
"styleRegexpTestNone": {
|
"styleRegexpTestNone": {
|
||||||
"message": "לא תואם אף כרטיסייה"
|
"message": "לא תואם אף כרטיסייה"
|
||||||
},
|
},
|
||||||
"styleRegexpTestPartial": {
|
|
||||||
"message": "לא תואם לחלוטין, לכן דולג"
|
|
||||||
},
|
|
||||||
"styleRegexpTestTitle": {
|
|
||||||
"message": "רשימת לשוניות פתוחות בהתאמה (לחץ על כתובת אתר למיקוד הלשונית שלה)"
|
|
||||||
},
|
|
||||||
"styleSaveLabel": {
|
"styleSaveLabel": {
|
||||||
"message": "שמור"
|
"message": "שמור"
|
||||||
},
|
},
|
||||||
"styleToMozillaFormatHelp": {
|
"styleSectionsTitle": {
|
||||||
"message": "ניתן לפרסם את ה־Mozilla format של הקוד לאתר userstyles.org ולהשתמש בו עם Stylish הקלאסי עבור Firefox"
|
"message": "סעיפים"
|
||||||
},
|
},
|
||||||
"styleToMozillaFormatTitle": {
|
"styleToMozillaFormatTitle": {
|
||||||
"message": "עיצוב ב־Mozilla-format"
|
"message": "עיצוב ב־Mozilla format"
|
||||||
},
|
},
|
||||||
"styleUpdate": {
|
"styleUpdate": {
|
||||||
"message": "האם אתה בטוח שברצונך לעדכן את '$stylename$'?",
|
"message": "האם אתה בטוח שברצונך לעדכן את '$stylename$'?",
|
||||||
|
@ -942,12 +696,6 @@
|
||||||
"stylusUnavailableForURL": {
|
"stylusUnavailableForURL": {
|
||||||
"message": "Stylus לא עובד על דפים כמו זה."
|
"message": "Stylus לא עובד על דפים כמו זה."
|
||||||
},
|
},
|
||||||
"stylusUnavailableForURLdetails": {
|
|
||||||
"message": "כאמצעי אבטחה, הדפדפן אוסר על הרחבות להשפיע על הדפים המובנים שלו (כמו chrome://version, הכרטיסייה החדשה הרגילה החל מ־Chrome 61, about:addons וכן הלאה), וכן על דפי הרחבות אחרים. כל דפדפן גם מגביל את הגישה לגלריית התוספים שלו (כמו חנות האינטרנט של Chrome או AMO)."
|
|
||||||
},
|
|
||||||
"syncDropboxStyles": {
|
|
||||||
"message": "ייצוא Dropbox"
|
|
||||||
},
|
|
||||||
"syncStorageErrorSaving": {
|
"syncStorageErrorSaving": {
|
||||||
"message": "הערך לא יכול להשמר. אנא נסה להקטין את גודל הטקסט."
|
"message": "הערך לא יכול להשמר. אנא נסה להקטין את גודל הטקסט."
|
||||||
},
|
},
|
||||||
|
@ -966,18 +714,9 @@
|
||||||
"unreachableContentScript": {
|
"unreachableContentScript": {
|
||||||
"message": "לא ניתן לתקשר עם הדף. אנא טען מחדש את הכרטיסייה."
|
"message": "לא ניתן לתקשר עם הדף. אנא טען מחדש את הכרטיסייה."
|
||||||
},
|
},
|
||||||
"unreachableFileHint": {
|
|
||||||
"message": "Stylus יכול לגשת לכתובות אתר file:// רק אם תיבת הסימון המתאימה עבור סיומת Stylus פעילה בדף chrome://extensions."
|
|
||||||
},
|
|
||||||
"unzipStyles": {
|
|
||||||
"message": "מחלץ עיצובים..."
|
|
||||||
},
|
|
||||||
"updateAllCheckSucceededNoUpdate": {
|
"updateAllCheckSucceededNoUpdate": {
|
||||||
"message": "לא נמצאו עדכונים."
|
"message": "לא נמצאו עדכונים."
|
||||||
},
|
},
|
||||||
"updateAllCheckSucceededSomeEdited": {
|
|
||||||
"message": "כמה סגנונות הניתנים לעדכון לא נבדקו כדי להימנע מאבדן עריכות מקומיות אפשריות. ניתן לאלץ עדכונים על ידי בדיקה באופן פרטני, או על ידי הפעלת בדיקה אחרת לכל הסגנונות (עריכות מקומיות יידרסו)."
|
|
||||||
},
|
|
||||||
"updateCheckFailBadResponseCode": {
|
"updateCheckFailBadResponseCode": {
|
||||||
"message": "העדכון נכשל: השרת החזיר תגובה עם הקוד $code$.",
|
"message": "העדכון נכשל: השרת החזיר תגובה עם הקוד $code$.",
|
||||||
"placeholders": {
|
"placeholders": {
|
||||||
|
@ -992,12 +731,6 @@
|
||||||
"updateCheckHistory": {
|
"updateCheckHistory": {
|
||||||
"message": "היסטוריה של בדיקת עדכונים"
|
"message": "היסטוריה של בדיקת עדכונים"
|
||||||
},
|
},
|
||||||
"updateCheckManualUpdateForce": {
|
|
||||||
"message": "התקן עדכון (עריכות מקומיות יידרסו)"
|
|
||||||
},
|
|
||||||
"updateCheckManualUpdateHint": {
|
|
||||||
"message": "אילוץ עדכון ידרוס כל עריכה מקומית."
|
|
||||||
},
|
|
||||||
"updateCheckSkippedLocallyEdited": {
|
"updateCheckSkippedLocallyEdited": {
|
||||||
"message": "עיצוב זה נערך באופן מקומי."
|
"message": "עיצוב זה נערך באופן מקומי."
|
||||||
},
|
},
|
||||||
|
@ -1013,9 +746,6 @@
|
||||||
"updatesCurrentlyInstalled": {
|
"updatesCurrentlyInstalled": {
|
||||||
"message": "העדכונים הותקנו."
|
"message": "העדכונים הותקנו."
|
||||||
},
|
},
|
||||||
"uploadingFile": {
|
|
||||||
"message": "מעלה קובץ..."
|
|
||||||
},
|
|
||||||
"usercssAvoidOverwriting": {
|
"usercssAvoidOverwriting": {
|
||||||
"message": "אנא שנה את הערך של @name or @namespace על־מנת להמנע מדריסה של עיצוב קיים."
|
"message": "אנא שנה את הערך של @name or @namespace על־מנת להמנע מדריסה של עיצוב קיים."
|
||||||
},
|
},
|
||||||
|
@ -1033,8 +763,5 @@
|
||||||
},
|
},
|
||||||
"writeStyleForURL": {
|
"writeStyleForURL": {
|
||||||
"message": "הקישור הנוכחי"
|
"message": "הקישור הנוכחי"
|
||||||
},
|
|
||||||
"zipStyles": {
|
|
||||||
"message": "מקבץ עיצובים..."
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,7 +1,4 @@
|
||||||
{
|
{
|
||||||
"InaccessibleFileHint": {
|
|
||||||
"message": "A Stylus nem tud bizonyos fájltípusokhoz hozzáférni (pl. PDF- & JSON-fájlokhoz)"
|
|
||||||
},
|
|
||||||
"addStyleLabel": {
|
"addStyleLabel": {
|
||||||
"message": "Új stílus írása"
|
"message": "Új stílus írása"
|
||||||
},
|
},
|
||||||
|
@ -15,7 +12,7 @@
|
||||||
"message": "Hozzáadás"
|
"message": "Hozzáadás"
|
||||||
},
|
},
|
||||||
"appliesDisplay": {
|
"appliesDisplay": {
|
||||||
"message": "Érvényes erre:$applies$",
|
"message": "A következőre érvényesül:$applies$",
|
||||||
"placeholders": {
|
"placeholders": {
|
||||||
"applies": {
|
"applies": {
|
||||||
"content": "$1"
|
"content": "$1"
|
||||||
|
@ -26,28 +23,28 @@
|
||||||
"message": "és ennél is több"
|
"message": "és ennél is több"
|
||||||
},
|
},
|
||||||
"appliesDomainOption": {
|
"appliesDomainOption": {
|
||||||
"message": "URL-ek a tartományban"
|
"message": "URL-ek a doménon"
|
||||||
},
|
},
|
||||||
"appliesHelp": {
|
"appliesHelp": {
|
||||||
"message": "Az \"Érvényes erre\" beállítással korlártozható, hogy milyen URL-ekre vonatkozik az itt lévő kód."
|
"message": "Használd az \"A következőre érvényesül\" részt, hogy korlátozd, milyen URL-ekre vonatkozzon az itt lévő kód!"
|
||||||
},
|
},
|
||||||
"appliesLabel": {
|
"appliesLabel": {
|
||||||
"message": "Érvényes erre"
|
"message": "Amire érvényesül"
|
||||||
},
|
},
|
||||||
"appliesLineWidgetLabel": {
|
"appliesLineWidgetLabel": {
|
||||||
"message": "Információ arról, hogy mire érvényes"
|
"message": "Információ megjelenítése arról, hogy mire van alkalmazva"
|
||||||
},
|
},
|
||||||
"appliesLineWidgetWarning": {
|
"appliesLineWidgetWarning": {
|
||||||
"message": "Nem működik minimalizált CSS-sel"
|
"message": "Nem működik minimalizált CSS-szel"
|
||||||
},
|
},
|
||||||
"appliesRegexpOption": {
|
"appliesRegexpOption": {
|
||||||
"message": "Reguláris kifejezésre (regexp) illeszkedő URL-ek"
|
"message": "Reguláris kifejezésekre (regexp) illeszkedő URL-ek"
|
||||||
},
|
},
|
||||||
"appliesRemove": {
|
"appliesRemove": {
|
||||||
"message": "Eltávolítás"
|
"message": "Eltávolítás"
|
||||||
},
|
},
|
||||||
"appliesRemoveError": {
|
"appliesRemoveError": {
|
||||||
"message": "Nem lehet eltávolítani az utolsó 'érvényes erre' bejegyzést"
|
"message": "Nem lehet eltávolítani az utolsó alkalmazási szabályt"
|
||||||
},
|
},
|
||||||
"appliesSpecify": {
|
"appliesSpecify": {
|
||||||
"message": "Szűkítés"
|
"message": "Szűkítés"
|
||||||
|
@ -67,11 +64,14 @@
|
||||||
"backupButtons": {
|
"backupButtons": {
|
||||||
"message": "Biztonsági mentés"
|
"message": "Biztonsági mentés"
|
||||||
},
|
},
|
||||||
|
"backupMessage": {
|
||||||
|
"message": "Válassz ki egy fájlt vagy húzd erre az oldalra!"
|
||||||
|
},
|
||||||
"bckpInstStyles": {
|
"bckpInstStyles": {
|
||||||
"message": "Stílusok exportálása"
|
"message": "Stílusok exportálása"
|
||||||
},
|
},
|
||||||
"checkAllUpdates": {
|
"checkAllUpdates": {
|
||||||
"message": "Összes stílus frissítésének ellenőrzése"
|
"message": "Az összes stílus frissítésének ellenőrzése"
|
||||||
},
|
},
|
||||||
"checkAllUpdatesForce": {
|
"checkAllUpdatesForce": {
|
||||||
"message": "Ellenőrizd újra, nem módosítottam egy stílust sem!"
|
"message": "Ellenőrizd újra, nem módosítottam egy stílust sem!"
|
||||||
|
@ -89,13 +89,13 @@
|
||||||
"message": "Zárójelek és idézőjelek automatikus bezárása"
|
"message": "Zárójelek és idézőjelek automatikus bezárása"
|
||||||
},
|
},
|
||||||
"cm_autoCloseBracketsTooltip": {
|
"cm_autoCloseBracketsTooltip": {
|
||||||
"message": "Nyitó '([{'zárójel gépelésekor lezáró ')]}' zárójel automatikus hozzáadása"
|
"message": "Automatikusan legyen hozzáadva záró jelpár a következők gépelésekor: ()[]{}''\"\""
|
||||||
},
|
},
|
||||||
"cm_autocompleteOnTyping": {
|
"cm_autocompleteOnTyping": {
|
||||||
"message": "Automatikus kiegészítés gépeléskor"
|
"message": "Automatikus kiegészítés gépeléskor"
|
||||||
},
|
},
|
||||||
"cm_colorpicker": {
|
"cm_colorpicker": {
|
||||||
"message": "Színválasztó CSS-színekhez"
|
"message": "Színválasztó CSS színekhez"
|
||||||
},
|
},
|
||||||
"cm_indentWithTabs": {
|
"cm_indentWithTabs": {
|
||||||
"message": "Tabulátorok használata intelligens behúzásra"
|
"message": "Tabulátorok használata intelligens behúzásra"
|
||||||
|
@ -107,7 +107,7 @@
|
||||||
"message": "Automatikus sortörés"
|
"message": "Automatikus sortörés"
|
||||||
},
|
},
|
||||||
"cm_matchHighlight": {
|
"cm_matchHighlight": {
|
||||||
"message": "Kijelölés"
|
"message": "Kijelöl"
|
||||||
},
|
},
|
||||||
"cm_matchHighlightSelection": {
|
"cm_matchHighlightSelection": {
|
||||||
"message": "Csak kiválasztás"
|
"message": "Csak kiválasztás"
|
||||||
|
@ -125,7 +125,7 @@
|
||||||
"message": "Példák kifejezésekre: .valami-2 #aabbcc 0.32 !important\nAmikor ki van kapcsolva: a központosítással elválasztott szavak ki lesznek jelölve."
|
"message": "Példák kifejezésekre: .valami-2 #aabbcc 0.32 !important\nAmikor ki van kapcsolva: a központosítással elválasztott szavak ki lesznek jelölve."
|
||||||
},
|
},
|
||||||
"cm_smartIndent": {
|
"cm_smartIndent": {
|
||||||
"message": "Intelligens behúzás"
|
"message": "Intelligens behúzás használata"
|
||||||
},
|
},
|
||||||
"cm_tabSize": {
|
"cm_tabSize": {
|
||||||
"message": "Tabulátorméret"
|
"message": "Tabulátorméret"
|
||||||
|
@ -178,18 +178,6 @@
|
||||||
"confirmYes": {
|
"confirmYes": {
|
||||||
"message": "Igen"
|
"message": "Igen"
|
||||||
},
|
},
|
||||||
"connectingDropbox": {
|
|
||||||
"message": "Kapcsolódás Dropboxhoz..."
|
|
||||||
},
|
|
||||||
"connectingDropboxNotAllowed": {
|
|
||||||
"message": "Dropboxhoz csak közvetlenül a web-boltból telepített alkalmazásból lehet kapcsolódni."
|
|
||||||
},
|
|
||||||
"copied": {
|
|
||||||
"message": "Vágólapra másolva"
|
|
||||||
},
|
|
||||||
"copy": {
|
|
||||||
"message": "Másolás vágólapra"
|
|
||||||
},
|
|
||||||
"dateInstalled": {
|
"dateInstalled": {
|
||||||
"message": "Telepítés dátuma"
|
"message": "Telepítés dátuma"
|
||||||
},
|
},
|
||||||
|
@ -212,7 +200,7 @@
|
||||||
"message": "Tervezd újra a webet a Stylus stíluskezelővel. A Stylus lehetővé teszi a témák és egyéni külsők egyszerű telepítését sok népszerű oldalhoz."
|
"message": "Tervezd újra a webet a Stylus stíluskezelővel. A Stylus lehetővé teszi a témák és egyéni külsők egyszerű telepítését sok népszerű oldalhoz."
|
||||||
},
|
},
|
||||||
"disableAllStyles": {
|
"disableAllStyles": {
|
||||||
"message": "Összes stílus kikapcsolása"
|
"message": "Az összes stílus kikapcsolása"
|
||||||
},
|
},
|
||||||
"disableStyleLabel": {
|
"disableStyleLabel": {
|
||||||
"message": "Letiltás"
|
"message": "Letiltás"
|
||||||
|
@ -240,21 +228,15 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"editorStylesButton": {
|
||||||
|
"message": "A szerkesztő stílusainak keresése"
|
||||||
|
},
|
||||||
"enableStyleLabel": {
|
"enableStyleLabel": {
|
||||||
"message": "Engedélyezés"
|
"message": "Engedélyezés"
|
||||||
},
|
},
|
||||||
"excludeStyleByDomainLabel": {
|
|
||||||
"message": "Aktuális tartományt kivéve"
|
|
||||||
},
|
|
||||||
"excludeStyleByUrlLabel": {
|
|
||||||
"message": "Aktuális URL-t kivéve"
|
|
||||||
},
|
|
||||||
"exportLabel": {
|
"exportLabel": {
|
||||||
"message": "Exportálás"
|
"message": "Exportálás"
|
||||||
},
|
},
|
||||||
"exportSavedSuccess": {
|
|
||||||
"message": "A fájl sikeresen elmentve."
|
|
||||||
},
|
|
||||||
"externalFeedback": {
|
"externalFeedback": {
|
||||||
"message": "Visszajelzés"
|
"message": "Visszajelzés"
|
||||||
},
|
},
|
||||||
|
@ -287,6 +269,15 @@
|
||||||
"findStyles": {
|
"findStyles": {
|
||||||
"message": "Stílusok keresése"
|
"message": "Stílusok keresése"
|
||||||
},
|
},
|
||||||
|
"findStylesForSite": {
|
||||||
|
"message": "További stílusok keresése ehhez az oldalhoz"
|
||||||
|
},
|
||||||
|
"findStylesInline": {
|
||||||
|
"message": "Helyben"
|
||||||
|
},
|
||||||
|
"findStylesInlineTooltip": {
|
||||||
|
"message": "Keresési eredmények megjelenítése ebben az ablakban."
|
||||||
|
},
|
||||||
"genericAdd": {
|
"genericAdd": {
|
||||||
"message": "Hozzáadás"
|
"message": "Hozzáadás"
|
||||||
},
|
},
|
||||||
|
@ -323,9 +314,6 @@
|
||||||
"genericUnknown": {
|
"genericUnknown": {
|
||||||
"message": "Ismeretlen"
|
"message": "Ismeretlen"
|
||||||
},
|
},
|
||||||
"gettingStyles": {
|
|
||||||
"message": "Minden stílus fogadása..."
|
|
||||||
},
|
|
||||||
"helpAlt": {
|
"helpAlt": {
|
||||||
"message": "Segítség"
|
"message": "Segítség"
|
||||||
},
|
},
|
||||||
|
@ -333,10 +321,10 @@
|
||||||
"message": "Írj be egy parancsot"
|
"message": "Írj be egy parancsot"
|
||||||
},
|
},
|
||||||
"helpKeyMapHotkey": {
|
"helpKeyMapHotkey": {
|
||||||
"message": "Gyorsbillentyű"
|
"message": "Gyorsgomb"
|
||||||
},
|
},
|
||||||
"importAppendLabel": {
|
"importAppendLabel": {
|
||||||
"message": "Hozzáfűzés stílushoz"
|
"message": "Hozzáadás stílushoz"
|
||||||
},
|
},
|
||||||
"importAppendTooltip": {
|
"importAppendTooltip": {
|
||||||
"message": "Az importált stílus hozzáadása a jelenlegi stílushoz"
|
"message": "Az importált stílus hozzáadása a jelenlegi stílushoz"
|
||||||
|
@ -369,10 +357,10 @@
|
||||||
"message": "metainfó frissítve"
|
"message": "metainfó frissítve"
|
||||||
},
|
},
|
||||||
"importReportTitle": {
|
"importReportTitle": {
|
||||||
"message": "A stílusok importálása befejeződött."
|
"message": "A stílusok importálása befejeződött"
|
||||||
},
|
},
|
||||||
"importReportUnchanged": {
|
"importReportUnchanged": {
|
||||||
"message": "Nincs változás."
|
"message": "Semmi sem változott."
|
||||||
},
|
},
|
||||||
"importReportUndone": {
|
"importReportUndone": {
|
||||||
"message": "stílusok visszavonva"
|
"message": "stílusok visszavonva"
|
||||||
|
@ -396,7 +384,7 @@
|
||||||
"message": "Frissítés telepítése"
|
"message": "Frissítés telepítése"
|
||||||
},
|
},
|
||||||
"installUpdateFrom": {
|
"installUpdateFrom": {
|
||||||
"message": "A stílus jelenleg innen frissül: $url$",
|
"message": "A stílus most a következő helyről frissül: $url$",
|
||||||
"placeholders": {
|
"placeholders": {
|
||||||
"url": {
|
"url": {
|
||||||
"content": "$1"
|
"content": "$1"
|
||||||
|
@ -410,7 +398,7 @@
|
||||||
"message": "Licenc"
|
"message": "Licenc"
|
||||||
},
|
},
|
||||||
"linkGetHelp": {
|
"linkGetHelp": {
|
||||||
"message": "Segítség kérése"
|
"message": "Kérj segítséget"
|
||||||
},
|
},
|
||||||
"linkGetStyles": {
|
"linkGetStyles": {
|
||||||
"message": "Szerezz be stílusokat"
|
"message": "Szerezz be stílusokat"
|
||||||
|
@ -438,10 +426,10 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"linterConfigTooltip": {
|
"linterConfigTooltip": {
|
||||||
"message": "Linter beállítása"
|
"message": "Kattints ennek a linternek a beállításához"
|
||||||
},
|
},
|
||||||
"linterInvalidConfigError": {
|
"linterInvalidConfigError": {
|
||||||
"message": "Nincs mentve a következő érvénytelen beállítások miatt:"
|
"message": "Nincs mentve ezek miatt az érvénytelen beállítások miatt:"
|
||||||
},
|
},
|
||||||
"linterIssues": {
|
"linterIssues": {
|
||||||
"message": "Problémák"
|
"message": "Problémák"
|
||||||
|
@ -455,28 +443,31 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"linterJSONError": {
|
"linterJSONError": {
|
||||||
"message": "Érvénytelen JSON-formátum"
|
"message": "Érvénytelen JSON formátum"
|
||||||
},
|
},
|
||||||
"linterResetMessage": {
|
"linterResetMessage": {
|
||||||
"message": "Egy véletlen visszaállítás visszavonásához nyomj Ctrl-Z-t (vagy Cmd-Z) a szövegdobozon belül)"
|
"message": "Egy véletlen visszaállítás visszavonásához nyomj Ctrl-Z-t (vagy Cmd-Z) a szövegdobozon belül)"
|
||||||
},
|
},
|
||||||
"linterRulesLink": {
|
"linterRulesLink": {
|
||||||
"message": "Összes szabály listája"
|
"message": "Lista az összes stílusról"
|
||||||
},
|
},
|
||||||
"liveReloadError": {
|
"liveReloadError": {
|
||||||
"message": "Hiba történt a fájl figyelése közben"
|
"message": "Hiba történt a fájl figyelése közben"
|
||||||
},
|
},
|
||||||
|
"liveReloadInstallHint": {
|
||||||
|
"message": "A valós idejű újratöltés engedélyezve van, így a telepített stílus automatikusan frissítve lesz kűlső változtatások során amíg ez a fül és a forrásfájlt tartalmazó fül nyitva van."
|
||||||
|
},
|
||||||
"liveReloadLabel": {
|
"liveReloadLabel": {
|
||||||
"message": "Valós idejű újratöltés"
|
"message": "Valós idejű újratöltés"
|
||||||
},
|
},
|
||||||
"manageFavicons": {
|
"manageFavicons": {
|
||||||
"message": "Faviconok az 'Érvényes erre' oszlopban"
|
"message": "Faviconok az alkalmazási oszlopban"
|
||||||
},
|
},
|
||||||
"manageFaviconsGray": {
|
"manageFaviconsGray": {
|
||||||
"message": "Megjelenítés szürkítve"
|
"message": "Szürke mód"
|
||||||
},
|
},
|
||||||
"manageFaviconsHelp": {
|
"manageFaviconsHelp": {
|
||||||
"message": "A Stylus külső szolgáltatást használ (https://icons.duckduckgo.com)"
|
"message": "A Stylus egy külső szolgáltatást használ (https://www.google.com/s2/favicons)"
|
||||||
},
|
},
|
||||||
"manageFilters": {
|
"manageFilters": {
|
||||||
"message": "Szűrők"
|
"message": "Szűrők"
|
||||||
|
@ -485,16 +476,16 @@
|
||||||
"message": "Telepített stílusok"
|
"message": "Telepített stílusok"
|
||||||
},
|
},
|
||||||
"manageMaxTargets": {
|
"manageMaxTargets": {
|
||||||
"message": "'Érvényes erre' elemek kijelzendő száma"
|
"message": "Megjelenítendő célok száma"
|
||||||
},
|
},
|
||||||
"manageNewStyleAsUsercss": {
|
"manageNewStyleAsUsercss": {
|
||||||
"message": "Usercss-ként"
|
"message": "Usercss-ként"
|
||||||
},
|
},
|
||||||
"manageNewUI": {
|
"manageNewUI": {
|
||||||
"message": "Új kezelői felület"
|
"message": "Az új kezelési felületkiosztás"
|
||||||
},
|
},
|
||||||
"manageOnlyDisabled": {
|
"manageOnlyDisabled": {
|
||||||
"message": "Csak a letiltott stílusok"
|
"message": "Csak letiltott stílusok"
|
||||||
},
|
},
|
||||||
"manageOnlyEnabled": {
|
"manageOnlyEnabled": {
|
||||||
"message": "Csak a telepített stílusok"
|
"message": "Csak a telepített stílusok"
|
||||||
|
@ -506,13 +497,13 @@
|
||||||
"message": "Csak helyileg létrehozott stílusok"
|
"message": "Csak helyileg létrehozott stílusok"
|
||||||
},
|
},
|
||||||
"manageOnlyLocalTooltip": {
|
"manageOnlyLocalTooltip": {
|
||||||
"message": "(a stílusok nem a userstyles.org egyik oldaláról települtek)"
|
"message": "(a stílusok nem egy userstyles.org oldalon lettek telepítve)"
|
||||||
},
|
},
|
||||||
"manageOnlyNonUsercss": {
|
"manageOnlyNonUsercss": {
|
||||||
"message": "Csak nem Usercss stílusok"
|
"message": "Csak nem Usercss stílusok"
|
||||||
},
|
},
|
||||||
"manageOnlyUpdates": {
|
"manageOnlyUpdates": {
|
||||||
"message": "Csak a frissíthetők vagy problémásak"
|
"message": "Csak frissíthetőek vagy problémásak"
|
||||||
},
|
},
|
||||||
"manageOnlyUsercss": {
|
"manageOnlyUsercss": {
|
||||||
"message": "Csak Usercss stílusok"
|
"message": "Csak Usercss stílusok"
|
||||||
|
@ -520,201 +511,20 @@
|
||||||
"menuShowBadge": {
|
"menuShowBadge": {
|
||||||
"message": "Aktív stílusok számlálójának mutatása"
|
"message": "Aktív stílusok számlálójának mutatása"
|
||||||
},
|
},
|
||||||
"meta_invalidCheckboxDefault": {
|
|
||||||
"message": "Érvénytelen jelölő négyzet @var: az érték csak 0 vagy 1 lehet"
|
|
||||||
},
|
|
||||||
"meta_invalidColor": {
|
|
||||||
"message": "Érvénytelen szín @var: a(z) $color$ nem szín",
|
|
||||||
"placeholders": {
|
|
||||||
"color": {
|
|
||||||
"content": "$1"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"meta_invalidNumber": {
|
|
||||||
"message": "Várt elem: szám"
|
|
||||||
},
|
|
||||||
"meta_invalidRange": {
|
|
||||||
"message": "Érvénytelen változó @var (típus: $type$): az érték csak szám vagy tömb lehet",
|
|
||||||
"placeholders": {
|
|
||||||
"type": {
|
|
||||||
"content": "$1"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"meta_invalidRangeDefault": {
|
|
||||||
"message": "Érvénytelen változó @var (típus: $type$): az alapértelmezett értéke null",
|
|
||||||
"placeholders": {
|
|
||||||
"type": {
|
|
||||||
"content": "$1"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"meta_invalidRangeMax": {
|
|
||||||
"message": "Érvénytelen változó @var (típus: $type$): az alapértelmezett érték nagyobb, mint a maximum",
|
|
||||||
"placeholders": {
|
|
||||||
"type": {
|
|
||||||
"content": "$1"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"meta_invalidRangeMin": {
|
|
||||||
"message": "Érvénytelen változó @var (típus: $type$): az alapértelmezett érték kisebb, mint a minimum",
|
|
||||||
"placeholders": {
|
|
||||||
"type": {
|
|
||||||
"content": "$1"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"meta_invalidRangeMultipleUnits": {
|
|
||||||
"message": "Érvénytelen változó @var (típus: $type$): többféle egység van megadva",
|
|
||||||
"placeholders": {
|
|
||||||
"type": {
|
|
||||||
"content": "$1"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"meta_invalidRangeStep": {
|
|
||||||
"message": "Érvénytelen változó @var (típus: $type$): az alapértelmezett érték nem a lépésköz többszöröse",
|
|
||||||
"placeholders": {
|
|
||||||
"type": {
|
|
||||||
"content": "$1"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"meta_invalidRangeTooManyValues": {
|
|
||||||
"message": "Érvénytelen változó @var (típus: $type$): a tömb túl sok elemet tartalmaz",
|
|
||||||
"placeholders": {
|
|
||||||
"type": {
|
|
||||||
"content": "$1"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"meta_invalidRangeUnits": {
|
|
||||||
"message": "Érvénytelen változó @var (típus: $type$): a(z) '$units$' nem érvényes egység",
|
|
||||||
"placeholders": {
|
|
||||||
"type": {
|
|
||||||
"content": "$1"
|
|
||||||
},
|
|
||||||
"units": {
|
|
||||||
"content": "$2"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"meta_invalidRangeValue": {
|
|
||||||
"message": "Érvénytelen változó @var (típus: $type$): csak szám, karakterlánc vagy null típusú elem lehet a tömbben",
|
|
||||||
"placeholders": {
|
|
||||||
"type": {
|
|
||||||
"content": "$1"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"meta_invalidSelect": {
|
|
||||||
"message": "Érvénytelen kiválasztó lista @var: az alapértelmezett érték tömb vagy objektum lehet csak"
|
|
||||||
},
|
|
||||||
"meta_invalidSelectEmptyOptions": {
|
|
||||||
"message": "Érvénytelen kiválasztó lista @var: a lista üres"
|
|
||||||
},
|
|
||||||
"meta_invalidSelectLabel": {
|
|
||||||
"message": "Érvénytelen kiválasztó lista @var: a listaelem címkéje üres"
|
|
||||||
},
|
|
||||||
"meta_invalidSelectMultipleDefaults": {
|
|
||||||
"message": "Érvénytelen kiválasztó lista @var: egynél több alapértelmezett elem van megadva"
|
|
||||||
},
|
|
||||||
"meta_invalidSelectNameDuplicated": {
|
|
||||||
"message": "Érvénytelen kiválasztó lista @var: egy listaelem kétszer szerepel"
|
|
||||||
},
|
|
||||||
"meta_invalidSelectValue": {
|
|
||||||
"message": "Érvénytelen kiválasztó lista @var: a tömbön/objektumon belüli érték csak karakterlánc lehet"
|
|
||||||
},
|
|
||||||
"meta_invalidSelectValueMismatch": {
|
|
||||||
"message": "Érvénytelen kiválasztó lista @var: az érték nem található a listaelemek között"
|
|
||||||
},
|
|
||||||
"meta_invalidString": {
|
|
||||||
"message": "Várt elem: idézőjelek közti karakterlánc"
|
|
||||||
},
|
|
||||||
"meta_invalidURLProtocol": {
|
|
||||||
"message": "Érvénytelen URL-protokoll. Csak 'http' vagy 'https' engedett: $protocol$",
|
|
||||||
"placeholders": {
|
|
||||||
"protocol": {
|
|
||||||
"content": "$1"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"meta_invalidVersion": {
|
|
||||||
"message": "Érvénytelen verziószám"
|
|
||||||
},
|
|
||||||
"meta_invalidWord": {
|
|
||||||
"message": "Várt elem: szó"
|
|
||||||
},
|
|
||||||
"meta_missingChar": {
|
|
||||||
"message": "Várt karakterek: $chars$",
|
|
||||||
"placeholders": {
|
|
||||||
"chars": {
|
|
||||||
"content": "$1"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"meta_missingEOT": {
|
|
||||||
"message": "Várt elem: EOT-adat"
|
|
||||||
},
|
|
||||||
"meta_missingMandatory": {
|
|
||||||
"message": "Kötelező metaadat hiányzik: $keys$",
|
|
||||||
"placeholders": {
|
|
||||||
"keys": {
|
|
||||||
"content": "$1"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"meta_unknownJSONLiteral": {
|
|
||||||
"message": "Érvénytelen JSON: a(z) $literal$ nem érvényes JSON-karakterlánc (literális)",
|
|
||||||
"placeholders": {
|
|
||||||
"literal": {
|
|
||||||
"content": "$1"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"meta_unknownMeta": {
|
|
||||||
"message": "Ismeretlen metaadat: $key$",
|
|
||||||
"placeholders": {
|
|
||||||
"key": {
|
|
||||||
"content": "$1"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"meta_unknownPreprocessor": {
|
|
||||||
"message": "Ismeretlen @preprocesszor (előfeldolgozó): $preprocessor$",
|
|
||||||
"placeholders": {
|
|
||||||
"preprocessor": {
|
|
||||||
"content": "$1"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"meta_unknownVarType": {
|
|
||||||
"message": "Ismeretlen @$varkey$ változó, típus: $vartype$",
|
|
||||||
"placeholders": {
|
|
||||||
"varkey": {
|
|
||||||
"content": "$1"
|
|
||||||
},
|
|
||||||
"vartype": {
|
|
||||||
"content": "$2"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"noFileToImport": {
|
|
||||||
"message": "A stílusok importálásához előbb exportálni kell őket."
|
|
||||||
},
|
|
||||||
"noStylesForSite": {
|
"noStylesForSite": {
|
||||||
"message": "Nincs telepítve stílus ehhez az oldalhoz."
|
"message": "Nincs telepítve stílus ehhez az oldalhoz."
|
||||||
},
|
},
|
||||||
"openManage": {
|
"openManage": {
|
||||||
"message": "Kezelés"
|
"message": "Kezelés"
|
||||||
},
|
},
|
||||||
"openOptions": {
|
"openOptionsManage": {
|
||||||
|
"message": "A beállítások felülete"
|
||||||
|
},
|
||||||
|
"openOptionsPopup": {
|
||||||
"message": "Beállítások"
|
"message": "Beállítások"
|
||||||
},
|
},
|
||||||
"openStylesManager": {
|
"openStylesManager": {
|
||||||
"message": "Stíluskezelő megnyitása"
|
"message": "A frissítéskezelő megnyitása"
|
||||||
},
|
},
|
||||||
"optionsActions": {
|
"optionsActions": {
|
||||||
"message": "Műveletek"
|
"message": "Műveletek"
|
||||||
|
@ -723,13 +533,13 @@
|
||||||
"message": "Haladó"
|
"message": "Haladó"
|
||||||
},
|
},
|
||||||
"optionsAdvancedContextDelete": {
|
"optionsAdvancedContextDelete": {
|
||||||
"message": "'Törlés' parancs a szerkesztő helyi menüjében"
|
"message": "Delete hozzáadása a gyorsmenühöz"
|
||||||
},
|
},
|
||||||
"optionsAdvancedExposeIframes": {
|
"optionsAdvancedExposeIframes": {
|
||||||
"message": "iframe-ek kitevése HTML[stylus-iframe]-en keresztül"
|
"message": "iframe-ek kitevése HTML[stylus-iframe]-en keresztül"
|
||||||
},
|
},
|
||||||
"optionsAdvancedExposeIframesNote": {
|
"optionsAdvancedExposeIframesNote": {
|
||||||
"message": "Az oldal felső tartományának jelölése minden iframe-ben.\nLehetővé teszi az iframe-specifikus CSS írását, mint pl.:\nhtml[stylus-iframe$$=\"twitter.com\"] h1 { display:none }"
|
"message": "Engedélyezi a legfelsőbb szintű domain elérését\nmindegyik iframe-ben. Lehetővé teszi az olyan\niframe-specifikus CSS írását, mint:\nhtml[stylus-iframe$=\"twitter.com\"] h1 { display:none }"
|
||||||
},
|
},
|
||||||
"optionsAdvancedNewStyleAsUsercss": {
|
"optionsAdvancedNewStyleAsUsercss": {
|
||||||
"message": "Új stílus írása usercss-ként"
|
"message": "Új stílus írása usercss-ként"
|
||||||
|
@ -747,16 +557,13 @@
|
||||||
"message": "Az összes frissítés ellenőrzése és telepítése"
|
"message": "Az összes frissítés ellenőrzése és telepítése"
|
||||||
},
|
},
|
||||||
"optionsCustomizeBadge": {
|
"optionsCustomizeBadge": {
|
||||||
"message": "Ikon képe az eszköztáron"
|
"message": "Jelvény az eszköztárikonon"
|
||||||
},
|
},
|
||||||
"optionsCustomizeIcon": {
|
"optionsCustomizeIcon": {
|
||||||
"message": "Eszköztárikon"
|
"message": "Eszköztárikon"
|
||||||
},
|
},
|
||||||
"optionsCustomizePopup": {
|
"optionsCustomizePopup": {
|
||||||
"message": "Felugró ablak"
|
"message": "Felugró"
|
||||||
},
|
|
||||||
"optionsCustomizeSync": {
|
|
||||||
"message": "Szinkronizálás felhővel"
|
|
||||||
},
|
},
|
||||||
"optionsCustomizeUpdate": {
|
"optionsCustomizeUpdate": {
|
||||||
"message": "Frissítések"
|
"message": "Frissítések"
|
||||||
|
@ -780,74 +587,19 @@
|
||||||
"message": "Felugró ablak szélessége (pixelben)"
|
"message": "Felugró ablak szélessége (pixelben)"
|
||||||
},
|
},
|
||||||
"optionsReset": {
|
"optionsReset": {
|
||||||
"message": "Beállítások visszaállítása alapértelmezett értékre"
|
"message": "Beállítások visszaállítása alapértelmezett értékekre."
|
||||||
},
|
},
|
||||||
"optionsResetButton": {
|
"optionsResetButton": {
|
||||||
"message": "Alapértelmezés visszaállítása"
|
"message": "Beállítások visszaállítása alapra"
|
||||||
},
|
},
|
||||||
"optionsSubheading": {
|
"optionsSubheading": {
|
||||||
"message": "További beállítások"
|
"message": "Még több beállítás"
|
||||||
},
|
|
||||||
"optionsSyncConnect": {
|
|
||||||
"message": "Kapcsolódás"
|
|
||||||
},
|
|
||||||
"optionsSyncDisconnect": {
|
|
||||||
"message": "Kapcsolat bontása"
|
|
||||||
},
|
|
||||||
"optionsSyncLogin": {
|
|
||||||
"message": "Bejelentkezés"
|
|
||||||
},
|
|
||||||
"optionsSyncNone": {
|
|
||||||
"message": "Nincs"
|
|
||||||
},
|
|
||||||
"optionsSyncStatusConnected": {
|
|
||||||
"message": "Kapcsolódva"
|
|
||||||
},
|
|
||||||
"optionsSyncStatusConnecting": {
|
|
||||||
"message": "Kapcsolódás..."
|
|
||||||
},
|
|
||||||
"optionsSyncStatusDisconnected": {
|
|
||||||
"message": "Nincs kapcsolat"
|
|
||||||
},
|
|
||||||
"optionsSyncStatusDisconnecting": {
|
|
||||||
"message": "Kapcsolat bontása..."
|
|
||||||
},
|
|
||||||
"optionsSyncStatusPull": {
|
|
||||||
"message": "Stílusok helyi frissítése (pull): $loaded$/$total$",
|
|
||||||
"placeholders": {
|
|
||||||
"loaded": {
|
|
||||||
"content": "$1"
|
|
||||||
},
|
|
||||||
"total": {
|
|
||||||
"content": "$2"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"optionsSyncStatusPush": {
|
|
||||||
"message": "Stílusok távoli frissítése (push): $loaded$/$total$",
|
|
||||||
"placeholders": {
|
|
||||||
"loaded": {
|
|
||||||
"content": "$1"
|
|
||||||
},
|
|
||||||
"total": {
|
|
||||||
"content": "$2"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"optionsSyncStatusSyncing": {
|
|
||||||
"message": "Szinkronizálás..."
|
|
||||||
},
|
|
||||||
"optionsSyncSyncNow": {
|
|
||||||
"message": "Szinkronizálás most"
|
|
||||||
},
|
},
|
||||||
"optionsUpdateImportNote": {
|
"optionsUpdateImportNote": {
|
||||||
"message": "Amikor régebbi verzióból vagy a Stylishból importálsz stílusokat, egyszer manuálisan frissítsd a stílusokat a stíluskezelőben, hogy megbizonyosodj afelől, hogy mindegyik frissítve van!"
|
"message": "Amikor régebbi verzióból vagy a Stylishból importálsz stílusokat, egyszer manuálisan frissítsd a stílusokat a stíluskezelőben, hogy megbizonyosodj afelől, hogy mindegyik frissítve van!"
|
||||||
},
|
},
|
||||||
"optionsUpdateInterval": {
|
"optionsUpdateInterval": {
|
||||||
"message": "Felhasználói stílus automatikus frissítési időköze órában (0=kikapcsolva)"
|
"message": "Stílus automatikus frissítési intervalluma órában megadva (álltsd 0-ra a kikapcsoláshoz)"
|
||||||
},
|
|
||||||
"overwriteFileExport": {
|
|
||||||
"message": "Felülírjuk a létező fájlt?"
|
|
||||||
},
|
},
|
||||||
"paginationCurrent": {
|
"paginationCurrent": {
|
||||||
"message": "Jelenlegi oldal"
|
"message": "Jelenlegi oldal"
|
||||||
|
@ -867,26 +619,20 @@
|
||||||
"parseUsercssError": {
|
"parseUsercssError": {
|
||||||
"message": "A Stylus nem tudta elemezni a usercss-t"
|
"message": "A Stylus nem tudta elemezni a usercss-t"
|
||||||
},
|
},
|
||||||
"popupAutoResort": {
|
|
||||||
"message": "Stílusok újrarendezése a felugró ablakban átkapcsoláskor"
|
|
||||||
},
|
|
||||||
"popupBorders": {
|
"popupBorders": {
|
||||||
"message": "Fehér szegély az oldalakon"
|
"message": "Fehér szegélyek használata két oldalt"
|
||||||
},
|
},
|
||||||
"popupBordersTooltip": {
|
"popupBordersTooltip": {
|
||||||
"message": "Hasznos az új Chrome sötét témáinál, mert nem színezi át az oldalszegélyeket"
|
"message": "Hasznos az új Chromehoz létrehozott sötét témákhoz, mert nem színezi át az oldalszegélyeket"
|
||||||
},
|
},
|
||||||
"popupHotkeysInfo": {
|
"popupHotkeysInfo": {
|
||||||
"message": "<1>-<9>, <0>, a numpaden is - be-/kikapcsolja az n-nedik stílust (a 0 10-et jelent)\n<A>-<Z> az adott betűvel kezdődő első stílust kapcsolja be/ki \n<Shift> kapcsolgatás helyett megnyitja a szerkesztőt\n<Numpad +> listázott stílusokat engedélyez\n<Numpad –> listázott stílusokat tilt le\n<Numpad *> és <`> (fordított félidézőjel) - kezdeti állapotban engedélyezett stílusokat kapcsol; nem vonatkozik azokra a stílusokra, amiket később engedélyeztél, mialatt a felugró ablakocska nyitva volt, hogy vissza tudd állítani a kezdeti kiválasztást tesztelés után: egyszerűen tilts le mindent, majd pl. <Numpad –> vagy <Numpad *>\nTovábbi információ a wikiben"
|
"message": "<1>-<9>, <0>, a numpaden is - be-/kikapcsolja az n-nedik stílust (a 0 10-et jelent)\n<A>-<Z> az adott betűvel kezdődő első stílust kapcsolja be/ki \n<Shift> kapcsolgatás helyett megnyitja a szerkesztőt\n<Numpad +> listázott stílusokat engedélyez\n<Numpad –> listázott stílusokat tilt le\n<Numpad *> és <`> (fordított félidézőjel) - kezdeti állapotban engedélyezett stílusokat kapcsol; nem vonatkozik azokra a stílusokra, amiket később engedélyeztél, mialatt a felugró ablakocska nyitva volt, hogy vissza tudd állítani a kezdeti kiválasztást tesztelés után: egyszerűen tilts le mindent, majd pl. <Numpad –> vagy <Numpad *>\nTovábbi információ a wikiben"
|
||||||
},
|
},
|
||||||
"popupHotkeysTooltip": {
|
"popupHotkeysTooltip": {
|
||||||
"message": "Elérhető gyorsbillentyűk megtekintése"
|
"message": "Kattints az elérhető gyorsbillentyűk megtekintéséhez"
|
||||||
},
|
},
|
||||||
"popupManageTooltip": {
|
"popupManageTooltip": {
|
||||||
"message": "Shift+kattintás vagy jobb kattintás: a stíluskezelő megnyitása a jelenlegi oldalra érvényes stílusokkal"
|
"message": "Shift-kattintás vagy jobb kattintás megnyitja a stíluskezelőt a jelenlegi oldalra érvényesülő stílusokkal"
|
||||||
},
|
|
||||||
"popupMenuButtonTooltip": {
|
|
||||||
"message": "Műveletmenü"
|
|
||||||
},
|
},
|
||||||
"popupOpenEditInWindow": {
|
"popupOpenEditInWindow": {
|
||||||
"message": "Szerkesztő megnyitása új ablakban"
|
"message": "Szerkesztő megnyitása új ablakban"
|
||||||
|
@ -895,7 +641,7 @@
|
||||||
"message": "A fül a böngésző ablakától történő leválasztásával is engedélyezhető, és letiltható a fül egy másik ablakhoz való hozzácsatolásával."
|
"message": "A fül a böngésző ablakától történő leválasztásával is engedélyezhető, és letiltható a fül egy másik ablakhoz való hozzácsatolásával."
|
||||||
},
|
},
|
||||||
"popupStylesFirst": {
|
"popupStylesFirst": {
|
||||||
"message": "Stílusnevek a parancsok előtt"
|
"message": "Parancsok előtti stílusok"
|
||||||
},
|
},
|
||||||
"prefShowBadge": {
|
"prefShowBadge": {
|
||||||
"message": "A jelenlegi oldalon aktív stílusok száma"
|
"message": "A jelenlegi oldalon aktív stílusok száma"
|
||||||
|
@ -906,9 +652,6 @@
|
||||||
"previewTooltip": {
|
"previewTooltip": {
|
||||||
"message": "Átmenetileg alkalmazza a változtatásokat mentés nélkül.\nMentsd a stílust, hogy a változtatások véglegesek legyenek!"
|
"message": "Átmenetileg alkalmazza a változtatásokat mentés nélkül.\nMentsd a stílust, hogy a változtatások véglegesek legyenek!"
|
||||||
},
|
},
|
||||||
"readingStyles": {
|
|
||||||
"message": "Stílusok olvasása..."
|
|
||||||
},
|
|
||||||
"replace": {
|
"replace": {
|
||||||
"message": "Csere"
|
"message": "Csere"
|
||||||
},
|
},
|
||||||
|
@ -921,9 +664,6 @@
|
||||||
"retrieveBckp": {
|
"retrieveBckp": {
|
||||||
"message": "Stílusok importálása"
|
"message": "Stílusok importálása"
|
||||||
},
|
},
|
||||||
"retrieveDropboxSync": {
|
|
||||||
"message": "Importálás Dropboxból"
|
|
||||||
},
|
|
||||||
"search": {
|
"search": {
|
||||||
"message": "Keresés"
|
"message": "Keresés"
|
||||||
},
|
},
|
||||||
|
@ -954,21 +694,27 @@
|
||||||
"searchResultWeeklyCount": {
|
"searchResultWeeklyCount": {
|
||||||
"message": "Heti telepítések"
|
"message": "Heti telepítések"
|
||||||
},
|
},
|
||||||
|
"searchStyles": {
|
||||||
|
"message": "Tartalom keresése"
|
||||||
|
},
|
||||||
|
"searchStylesHelp": {
|
||||||
|
"message": "A </> billentyűvel a keresési mezőre ugrasz.\nSima szöveg: keresés a névben, kódban, honlap URL-ben és érvényes oldalakban. A 3 betűnél kevesebb szavak figyelmen kívül vannak hagyva.\nIlleszkedő URL: kezdd a keresést „<url:>”-lel, pl. <url:https://github.com/openstyles/stylus>\nReguláris kifejezések: ide tartoznak a per-jelek és a flagek, pl. </body.*?\\ba\\b/simguy>\nPontos szavak: írj idézőjeleket a keresési kifejezés köré, pl. <\".header ~ div\">"
|
||||||
|
},
|
||||||
"sectionAdd": {
|
"sectionAdd": {
|
||||||
"message": "Új szakasz hozzáadása"
|
"message": "Egy újabb szekció hozzáadása"
|
||||||
},
|
},
|
||||||
"sectionCode": {
|
"sectionCode": {
|
||||||
"message": "Kód"
|
"message": "Kód"
|
||||||
},
|
},
|
||||||
|
"sectionHelp": {
|
||||||
|
"message": "A szekciók segítenek egy stílus kódrészleteit különböző URL-ekre alkalmazni. Például egyetlen stílus megváltoztathatja egy oldal honlapját egy bizonyos módon, míg az oldal többi részét máshogyan."
|
||||||
|
},
|
||||||
"sectionRemove": {
|
"sectionRemove": {
|
||||||
"message": "Szekció eltávolítása"
|
"message": "Szekció eltávolítása"
|
||||||
},
|
},
|
||||||
"sectionRestore": {
|
"sectionRestore": {
|
||||||
"message": "Eltávolított szekció visszaállítása"
|
"message": "Eltávolított szekció visszaállítása"
|
||||||
},
|
},
|
||||||
"sections": {
|
|
||||||
"message": "Szekciók"
|
|
||||||
},
|
|
||||||
"shortcuts": {
|
"shortcuts": {
|
||||||
"message": "Gyorsbillentyűk"
|
"message": "Gyorsbillentyűk"
|
||||||
},
|
},
|
||||||
|
@ -976,28 +722,28 @@
|
||||||
"message": "Gyorsbillentyűk megadása"
|
"message": "Gyorsbillentyűk megadása"
|
||||||
},
|
},
|
||||||
"sortDateNewestFirst": {
|
"sortDateNewestFirst": {
|
||||||
"message": "újabb elöl"
|
"message": "a legújabb elöl"
|
||||||
},
|
},
|
||||||
"sortDateOldestFirst": {
|
"sortDateOldestFirst": {
|
||||||
"message": "régebbi elöl"
|
"message": "a legrégebbi elöl"
|
||||||
},
|
},
|
||||||
"sortLabel": {
|
"sortLabel": {
|
||||||
"message": "Telepített stílusok sorbarendezésének módja"
|
"message": "Válaszd ki, hogyan szeretnéd sorba rendezni a telepített stílusokat"
|
||||||
},
|
},
|
||||||
"sortLabelTitleAsc": {
|
"sortLabelTitleAsc": {
|
||||||
"message": "Cím - növekvő sorrend"
|
"message": "Cím növekvő sorrendben"
|
||||||
},
|
},
|
||||||
"sortLabelTitleDesc": {
|
"sortLabelTitleDesc": {
|
||||||
"message": "Cím - csökkenő sorrend"
|
"message": "Cím csökkenő sorrendben"
|
||||||
},
|
},
|
||||||
"sortStylesHelp": {
|
"sortStylesHelp": {
|
||||||
"message": "A legördülő menüben válaszhatjuk ki, hogyan szeretnénk sorbarendezni a telepített stílusok bejegyzéseit. Az alapértelmezett beállítás a bejegyzések címei szerinti növekvő (A-tól Z-ig) sorrend. A „Cím - csökkenő sorrend” csoportban megadott rendezési módok csökkenő (Z-től A-ig) sorrendben rendezik a címeket.\nEgyéb beállításokkal több szempont alapján is rendezhetők a bejegyzések. Tekintsünk erre úgy, mint egy többoszlopos, rendezhető táblázatra, amelyben minden egyes (a plusz jelek között) kiválasztott kategória egy oszlopot vagy egy csoportot jelképez.\nPéldául, ha az van beállítva, hogy „Engedélyezve (első) + Cím”, az engedélyezett bejegyzések kerülnek a lista tetejére, majd az engedélyezett és a letiltott bejegyzések címei külön-külön, növekvő (A-tól Z-ig) sorrendben jelennek meg."
|
"message": "A legördülő menüben válaszd ki, hogyan szeretnéd sorba rendezni a telepített bejegyzéseket. Az alapértelmezett beállítás növekvő sorrend (A-tól Z-ig) a bejegyzések címei szerint. A „Cím csökkenő sorrendben” csoportban megadott rendezési módok csökkenő sorrendet (Z-től A-ig) alkalmaznak a címre.\nMás beállítások azt is lehetővé teszik, hogy több szempont alapján rendezd a bejegyzéseket. Elképzelheted ezt úgy, mint egy többoszlopos rendezési táblázatot, amelyben minden egyes (a plusz jelek között) kiválasztott kategória egy oszlopot vagy egy csoportot jelképez.\nPéldául ha az van beállítva, hogy „Engedélyezve (első) + Cím”, az engedélyezett bejegyzések kerülnek a lista tetejére, aztán az engedélyezett és a letiltott bejegyzések címei külön-külön növekvő sorrendbe A-tól Z-ig) vannak rendezve."
|
||||||
},
|
},
|
||||||
"sortStylesHelpTitle": {
|
"sortStylesHelpTitle": {
|
||||||
"message": "Tartalom rendezése"
|
"message": "Tartalom rendezése"
|
||||||
},
|
},
|
||||||
"styleBadRegexp": {
|
"styleBadRegexp": {
|
||||||
"message": "Érvénytelen reguláris kifejezés."
|
"message": "Érvénytelen regexp."
|
||||||
},
|
},
|
||||||
"styleBeautify": {
|
"styleBeautify": {
|
||||||
"message": "Csinosít"
|
"message": "Csinosít"
|
||||||
|
@ -1053,29 +799,62 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"styleMetaErrorCheckbox": {
|
||||||
|
"message": "Érvénytelen @var jelölődoboz: az értéknek 0-nak vagy 1-nek kell lennie"
|
||||||
|
},
|
||||||
|
"styleMetaErrorColor": {
|
||||||
|
"message": "A(z) $color$ nem valódi szín",
|
||||||
|
"placeholders": {
|
||||||
|
"color": {
|
||||||
|
"content": "$1"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"styleMetaErrorPreprocessor": {
|
||||||
|
"message": "Nem támogatott @preprocessor: $preprocessor$",
|
||||||
|
"placeholders": {
|
||||||
|
"preprocessor": {
|
||||||
|
"content": "$1"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"styleMetaErrorSelectValueMismatch": {
|
||||||
|
"message": "Érvénytelen @select: az érték nem létezik a listában"
|
||||||
|
},
|
||||||
|
"styleMissingMeta": {
|
||||||
|
"message": "Hiányzó metaadat: @$key$",
|
||||||
|
"placeholders": {
|
||||||
|
"key": {
|
||||||
|
"content": "$1"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"styleMissingName": {
|
"styleMissingName": {
|
||||||
"message": "Írj be egy nevet!"
|
"message": "Írj be egy nevet!"
|
||||||
},
|
},
|
||||||
"styleMozillaFormatHeading": {
|
"styleMozillaFormatHeading": {
|
||||||
"message": "Mozilla-formátum"
|
"message": "Mozilla formátum"
|
||||||
},
|
},
|
||||||
"styleNotAppliedRegexpProblemTooltip": {
|
"styleNotAppliedRegexpProblemTooltip": {
|
||||||
"message": "A stílus nem lett alkalmazva a „regexp()” helytelen használata miatt"
|
"message": "A stílus nem lett alkalmazva a „regexp()” helytelen használata miatt"
|
||||||
},
|
},
|
||||||
"styleRegexpInvalidExplanation": {
|
"styleRegexpInvalidExplanation": {
|
||||||
"message": "Néhány „regexp()” szabály nem lefordítható."
|
"message": "Egyes „regexp()” szabályokat nem lehetett lefordítani."
|
||||||
},
|
},
|
||||||
"styleRegexpPartialExplanation": {
|
"styleRegexpPartialExplanation": {
|
||||||
"message": "A stílus olyan részlegesen illeszkedő reguláris kifejezéseket használ, amelyek sértik a<a href='https://developer.mozilla.org/docs/Web/CSS/@document'>CSS4@dokumentumspecifikációt</a>, amely megköveteli az URL teljes illeszkedését. Az érintett CSS-szekciók nem kerültek alkalmazásra az oldalon. A stílus valószínűleg a Stylish-for-Chrome kiegészítőben készült, amely helytelenül ellenőrzi a „regexp()” szabályokat a legelső verziótól fogva (ismert hiba)."
|
"message": "Ez a stílus olyan részlegesen illeszkedő reguláris kifejezéseket használ, melyek sértik a<a href='https://developer.mozilla.org/docs/Web/CSS/@document'>CSS4@dokumentumspecifikációt</a>, mely szerint egy teljes URL-illeszkedésre van szükség. Az érintett CSS-szekciók nem kerültek alkalmazásra az oldalon. Ez a stílus valószínűleg a Stylish Chrome-kiegészítőben lett létrehozva, amely helytelenül ellenőrzi a „regexp()” szabályokat az első verziótól fogva (ismert hiba)."
|
||||||
},
|
},
|
||||||
"styleRegexpProblemTooltip": {
|
"styleRegexpProblemTooltip": {
|
||||||
"message": "A nem alkalmazott szeckiók száma helytelen 'regexp()' használat miatt"
|
"message": "A nem alkalmazott szeckiók száma helytelen 'regexp()' használat miatt"
|
||||||
},
|
},
|
||||||
|
"styleRegexpTestButton": {
|
||||||
|
"message": "RegExp teszt"
|
||||||
|
},
|
||||||
"styleRegexpTestFull": {
|
"styleRegexpTestFull": {
|
||||||
"message": "Illeszkedő fülek"
|
"message": "Illeszkedő fülek"
|
||||||
},
|
},
|
||||||
"styleRegexpTestInvalid": {
|
"styleRegexpTestInvalid": {
|
||||||
"message": "Kihagyott, érvénytelen reguláris kifejezések"
|
"message": "Kihagyott érvénytelen reguláris kifejezések"
|
||||||
},
|
},
|
||||||
"styleRegexpTestNone": {
|
"styleRegexpTestNone": {
|
||||||
"message": "Nincs illeszkedő fül"
|
"message": "Nincs illeszkedő fül"
|
||||||
|
@ -1092,6 +871,9 @@
|
||||||
"styleSaveLabel": {
|
"styleSaveLabel": {
|
||||||
"message": "Mentés"
|
"message": "Mentés"
|
||||||
},
|
},
|
||||||
|
"styleSectionsTitle": {
|
||||||
|
"message": "Szekciók"
|
||||||
|
},
|
||||||
"styleToMozillaFormatHelp": {
|
"styleToMozillaFormatHelp": {
|
||||||
"message": "A Mozilla formátumú kódot beküldheted a userstyles.org-ra és használhatod a klasszikus Stylish Firefoxkiegészítővel."
|
"message": "A Mozilla formátumú kódot beküldheted a userstyles.org-ra és használhatod a klasszikus Stylish Firefoxkiegészítővel."
|
||||||
},
|
},
|
||||||
|
@ -1115,9 +897,6 @@
|
||||||
"stylusUnavailableForURLdetails": {
|
"stylusUnavailableForURLdetails": {
|
||||||
"message": "Biztonsági okokból a böngésző megtiltja, hogy a kiegészítők változtatásokat tegyenek a beépített oldalain (pl. chrome://verzió, a Chrome 61 alapértelmezett „új lap” oldala, about:addons és így tovább) valamint más kiterjesztések oldalain. Ezen kívül mindegyik böngésző korlátozza a saját kiegészítőgalériájának elérését (pl. Chrome webáruház vagy a Mozilla kiegészítők oldala)"
|
"message": "Biztonsági okokból a böngésző megtiltja, hogy a kiegészítők változtatásokat tegyenek a beépített oldalain (pl. chrome://verzió, a Chrome 61 alapértelmezett „új lap” oldala, about:addons és így tovább) valamint más kiterjesztések oldalain. Ezen kívül mindegyik böngésző korlátozza a saját kiegészítőgalériájának elérését (pl. Chrome webáruház vagy a Mozilla kiegészítők oldala)"
|
||||||
},
|
},
|
||||||
"syncDropboxStyles": {
|
|
||||||
"message": "Exportálás Dropboxba"
|
|
||||||
},
|
|
||||||
"syncStorageErrorSaving": {
|
"syncStorageErrorSaving": {
|
||||||
"message": "Az értéket nem lehet menteni. Próbáld meg csökkenteni a szövegmennyiséget!"
|
"message": "Az értéket nem lehet menteni. Próbáld meg csökkenteni a szövegmennyiséget!"
|
||||||
},
|
},
|
||||||
|
@ -1136,18 +915,18 @@
|
||||||
"unreachableAMOHint": {
|
"unreachableAMOHint": {
|
||||||
"message": "Az elérés engedélyezéséhez nyisd meg az <about:config>-ot, kattints jobb egérgombbal, majd „Új”, „Logikai”, és illeszd be, hogy <privacy.resistFingerprinting.block_mozAddonManager>, és kattints az OK-ra,<true> OK, és töltsd újra az oldalt!"
|
"message": "Az elérés engedélyezéséhez nyisd meg az <about:config>-ot, kattints jobb egérgombbal, majd „Új”, „Logikai”, és illeszd be, hogy <privacy.resistFingerprinting.block_mozAddonManager>, és kattints az OK-ra,<true> OK, és töltsd újra az oldalt!"
|
||||||
},
|
},
|
||||||
|
"unreachableAMOHintNewFF": {
|
||||||
|
"message": "A Firefox 60-ban vagy annál újabb verzióban az AMO doménjét is el kell távolítanod a <about:config>-ban levő <extensions.webextensions.restrictedDomains>-ból."
|
||||||
|
},
|
||||||
|
"unreachableAMOHintOldFF": {
|
||||||
|
"message": "Csak a Firefox 59 vagy annál újabb állítható be az ilyen CSP-védett oldalak stílusának átszabása WebExtensions kiegészítőkön keresztül."
|
||||||
|
},
|
||||||
"unreachableContentScript": {
|
"unreachableContentScript": {
|
||||||
"message": "Nem sikerült az oldallal történő kommunikáció. Próbáld meg újratölteni az oldalt!"
|
"message": "Nem sikerült az oldallal történő kommunikáció. Próbáld meg újratölteni az oldalt!"
|
||||||
},
|
},
|
||||||
"unreachableFileHint": {
|
"unreachableFileHint": {
|
||||||
"message": "A Stylus csak akkor képes hozzáférni a file:// URL-ekhez, ha engedélyezed az erre vonatkozó beállítást a Stylus kiegészítőre a chrome://extensions oldalon."
|
"message": "A Stylus csak akkor képes hozzáférni a file:// URL-ekhez, ha engedélyezed az erre vonatkozó beállítást a Stylus kiegészítőre a chrome://extensions oldalon."
|
||||||
},
|
},
|
||||||
"unreachableMozSiteHintOldFF": {
|
|
||||||
"message": "Csak a Firefox 59 vagy annál újabb állítható be az ilyen CSP-védett oldalak stílusának átszabása WebExtensions kiegészítőkön keresztül."
|
|
||||||
},
|
|
||||||
"unzipStyles": {
|
|
||||||
"message": "Stílusok kibontása..."
|
|
||||||
},
|
|
||||||
"updateAllCheckSucceededNoUpdate": {
|
"updateAllCheckSucceededNoUpdate": {
|
||||||
"message": "Nem találhatók frissítések."
|
"message": "Nem találhatók frissítések."
|
||||||
},
|
},
|
||||||
|
@ -1189,9 +968,6 @@
|
||||||
"updatesCurrentlyInstalled": {
|
"updatesCurrentlyInstalled": {
|
||||||
"message": "Frissítve:"
|
"message": "Frissítve:"
|
||||||
},
|
},
|
||||||
"uploadingFile": {
|
|
||||||
"message": "Fájl feltöltése..."
|
|
||||||
},
|
|
||||||
"usercssAvoidOverwriting": {
|
"usercssAvoidOverwriting": {
|
||||||
"message": "Kérlek, módosítsd a @name vagy a @namespace értékét, nehogy felül legyen írva az egyik létező stílus!"
|
"message": "Kérlek, módosítsd a @name vagy a @namespace értékét, nehogy felül legyen írva az egyik létező stílus!"
|
||||||
},
|
},
|
||||||
|
@ -1204,6 +980,9 @@
|
||||||
"usercssReplaceTemplateConfirmation": {
|
"usercssReplaceTemplateConfirmation": {
|
||||||
"message": "Le legyen cserélve az alapértelmezett sablon az új Usercss stílusokhoz a jelenlegi kóddal?"
|
"message": "Le legyen cserélve az alapértelmezett sablon az új Usercss stílusokhoz a jelenlegi kóddal?"
|
||||||
},
|
},
|
||||||
|
"usercssReplaceTemplateName": {
|
||||||
|
"message": "Az üres @name lecseréli az alapértelmezett sablont"
|
||||||
|
},
|
||||||
"usercssReplaceTemplateSectionBody": {
|
"usercssReplaceTemplateSectionBody": {
|
||||||
"message": "Írj kódot ide…"
|
"message": "Írj kódot ide…"
|
||||||
},
|
},
|
||||||
|
@ -1215,8 +994,5 @@
|
||||||
},
|
},
|
||||||
"writeStyleForURL": {
|
"writeStyleForURL": {
|
||||||
"message": "ehhez az URL-hez"
|
"message": "ehhez az URL-hez"
|
||||||
},
|
|
||||||
"zipStyles": {
|
|
||||||
"message": "Stílusok tömörítése..."
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,9 +1,6 @@
|
||||||
{
|
{
|
||||||
"InaccessibleFileHint": {
|
|
||||||
"message": "Stylus non può accedere ad alcuni tipi di file (ad es. pdf & JSON)"
|
|
||||||
},
|
|
||||||
"addStyleLabel": {
|
"addStyleLabel": {
|
||||||
"message": "Scrivi un nuovo stile"
|
"message": "Scrivi nuovo stile"
|
||||||
},
|
},
|
||||||
"addStyleTitle": {
|
"addStyleTitle": {
|
||||||
"message": "Aggiungi stili"
|
"message": "Aggiungi stili"
|
||||||
|
@ -32,7 +29,7 @@
|
||||||
"message": "Utilizza i controlli \"Applica a\" per limitare gli URL a cui viene applicato il codice in questa sezione."
|
"message": "Utilizza i controlli \"Applica a\" per limitare gli URL a cui viene applicato il codice in questa sezione."
|
||||||
},
|
},
|
||||||
"appliesLabel": {
|
"appliesLabel": {
|
||||||
"message": "Applica a"
|
"message": "Vale per"
|
||||||
},
|
},
|
||||||
"appliesLineWidgetLabel": {
|
"appliesLineWidgetLabel": {
|
||||||
"message": "Visualizza info 'Applica a'"
|
"message": "Visualizza info 'Applica a'"
|
||||||
|
@ -62,7 +59,7 @@
|
||||||
"message": "Autore"
|
"message": "Autore"
|
||||||
},
|
},
|
||||||
"backupMessage": {
|
"backupMessage": {
|
||||||
"message": "Per importare il file di backup, selezionalo e rilascialo in questa pagina oppure utilizza il bottone Importa\n\nPer esportare un backup compatibile con Stylus versione 1.5.18 e oltre, selezionalo col tasto destro oppure shift-click il pulsante Esporta"
|
"message": "Seleziona un file o trascinalo in questa pagina."
|
||||||
},
|
},
|
||||||
"bckpInstStyles": {
|
"bckpInstStyles": {
|
||||||
"message": "Esporta stili"
|
"message": "Esporta stili"
|
||||||
|
@ -80,13 +77,7 @@
|
||||||
"message": "Verifica in corso..."
|
"message": "Verifica in corso..."
|
||||||
},
|
},
|
||||||
"clickToUninstall": {
|
"clickToUninstall": {
|
||||||
"message": "Clicca per disinstallare"
|
"message": "Clica per disinstallare"
|
||||||
},
|
|
||||||
"cm_autoCloseBrackets": {
|
|
||||||
"message": "Chiudi automaticamente parentesi e virgolette"
|
|
||||||
},
|
|
||||||
"cm_autoCloseBracketsTooltip": {
|
|
||||||
"message": "Aggiungi automaticamente il simbolo di chiusura quando apri ()[]{}''\"\""
|
|
||||||
},
|
},
|
||||||
"cm_autocompleteOnTyping": {
|
"cm_autocompleteOnTyping": {
|
||||||
"message": "Completamento automatico durante digitazione"
|
"message": "Completamento automatico durante digitazione"
|
||||||
|
@ -95,7 +86,7 @@
|
||||||
"message": "Selezionatore colore per colori CSS"
|
"message": "Selezionatore colore per colori CSS"
|
||||||
},
|
},
|
||||||
"cm_indentWithTabs": {
|
"cm_indentWithTabs": {
|
||||||
"message": "Usa tabulazioni con indentazione intellingente"
|
"message": "Usa schede con indentazione intellingente"
|
||||||
},
|
},
|
||||||
"cm_keyMap": {
|
"cm_keyMap": {
|
||||||
"message": "Mappa caratteri"
|
"message": "Mappa caratteri"
|
||||||
|
@ -106,30 +97,15 @@
|
||||||
"cm_matchHighlightSelection": {
|
"cm_matchHighlightSelection": {
|
||||||
"message": "Solo selezione"
|
"message": "Solo selezione"
|
||||||
},
|
},
|
||||||
"cm_matchHighlightToken": {
|
|
||||||
"message": "Token sotto il puntatore"
|
|
||||||
},
|
|
||||||
"cm_resizeGripHint": {
|
|
||||||
"message": "Doppio click per massimizzare/ripristinare l'altezza"
|
|
||||||
},
|
|
||||||
"cm_selectByTokens": {
|
|
||||||
"message": "Doppio click seleziona i token"
|
|
||||||
},
|
|
||||||
"cm_selectByTokensTooltip": {
|
|
||||||
"message": "Esempi di token: foo-bar-2 #aabbcc 0.32 !important\nQuando disabilitato: vengono selezionate le parole delimitate dalla punteggiatura."
|
|
||||||
},
|
|
||||||
"cm_smartIndent": {
|
"cm_smartIndent": {
|
||||||
"message": "Usa indentazione intelligente"
|
"message": "Usa indentazione intelligente"
|
||||||
},
|
},
|
||||||
"cm_tabSize": {
|
"cm_tabSize": {
|
||||||
"message": "Dimensione tabulazione"
|
"message": "Dimensione scheda"
|
||||||
},
|
},
|
||||||
"cm_theme": {
|
"cm_theme": {
|
||||||
"message": "Tema"
|
"message": "Tema"
|
||||||
},
|
},
|
||||||
"colorpickerSwitchFormatTooltip": {
|
|
||||||
"message": "Cambia formato: HEX -> RGB -> HSL.\nShift-click per invertire la direzione.\nAnche con i tasti PgUp (PaginaSu), PgDn (PaginaGiù)."
|
|
||||||
},
|
|
||||||
"colorpickerTooltip": {
|
"colorpickerTooltip": {
|
||||||
"message": "Apri selettore colore"
|
"message": "Apri selettore colore"
|
||||||
},
|
},
|
||||||
|
@ -166,18 +142,6 @@
|
||||||
"confirmYes": {
|
"confirmYes": {
|
||||||
"message": "Si"
|
"message": "Si"
|
||||||
},
|
},
|
||||||
"connectingDropboxNotAllowed": {
|
|
||||||
"message": "La connessione a Dropbox è disponibile soltanto nelle app installate direttamente dal webstore"
|
|
||||||
},
|
|
||||||
"copied": {
|
|
||||||
"message": "Copiato negli appunti"
|
|
||||||
},
|
|
||||||
"copy": {
|
|
||||||
"message": "Copia negli appunti"
|
|
||||||
},
|
|
||||||
"customNameHint": {
|
|
||||||
"message": "Inserisci qui un nome personalizzato per rinominare lo stile nell'interfaccia utente (UI) senza rompere i suoi aggiornamenti"
|
|
||||||
},
|
|
||||||
"dateInstalled": {
|
"dateInstalled": {
|
||||||
"message": "Data installazione"
|
"message": "Data installazione"
|
||||||
},
|
},
|
||||||
|
@ -208,9 +172,6 @@
|
||||||
"editDeleteText": {
|
"editDeleteText": {
|
||||||
"message": "Elimina"
|
"message": "Elimina"
|
||||||
},
|
},
|
||||||
"editGotoLine": {
|
|
||||||
"message": "Vai alla riga (oppure riga:col)"
|
|
||||||
},
|
|
||||||
"editStyleHeading": {
|
"editStyleHeading": {
|
||||||
"message": "Modifica di stili"
|
"message": "Modifica di stili"
|
||||||
},
|
},
|
||||||
|
@ -225,6 +186,9 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"editorStylesButton": {
|
||||||
|
"message": "Cerca stili editor"
|
||||||
|
},
|
||||||
"enableStyleLabel": {
|
"enableStyleLabel": {
|
||||||
"message": "Attiva"
|
"message": "Attiva"
|
||||||
},
|
},
|
||||||
|
@ -251,12 +215,15 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"filteredStylesAllHidden": {
|
|
||||||
"message": "Nessuno stile corrispondente ai filtri applicati"
|
|
||||||
},
|
|
||||||
"findStyles": {
|
"findStyles": {
|
||||||
"message": "Trova stili"
|
"message": "Trova stili"
|
||||||
},
|
},
|
||||||
|
"findStylesForSite": {
|
||||||
|
"message": "Trova più stili per questo sito"
|
||||||
|
},
|
||||||
|
"findStylesInlineTooltip": {
|
||||||
|
"message": "Visualizza risultati in questa finestra."
|
||||||
|
},
|
||||||
"genericAdd": {
|
"genericAdd": {
|
||||||
"message": "Aggiungi"
|
"message": "Aggiungi"
|
||||||
},
|
},
|
||||||
|
@ -299,9 +266,6 @@
|
||||||
"helpKeyMapCommand": {
|
"helpKeyMapCommand": {
|
||||||
"message": "Inserisci il nome di un comando"
|
"message": "Inserisci il nome di un comando"
|
||||||
},
|
},
|
||||||
"helpKeyMapHotkey": {
|
|
||||||
"message": "Digita una scorciatoia"
|
|
||||||
},
|
|
||||||
"importAppendLabel": {
|
"importAppendLabel": {
|
||||||
"message": "Aggiungi ad uno stile"
|
"message": "Aggiungi ad uno stile"
|
||||||
},
|
},
|
||||||
|
@ -385,17 +349,6 @@
|
||||||
"linkTranslate": {
|
"linkTranslate": {
|
||||||
"message": "Traduci"
|
"message": "Traduci"
|
||||||
},
|
},
|
||||||
"linterCSSLintSettings": {
|
|
||||||
"message": "(Imposta regola: 0 = disabilitata; 1 = warning; 2 = errore)"
|
|
||||||
},
|
|
||||||
"linterConfigPopupTitle": {
|
|
||||||
"message": "Imposta configurazione delle regole di $linter$",
|
|
||||||
"placeholders": {
|
|
||||||
"linter": {
|
|
||||||
"content": "$1"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"linterInvalidConfigError": {
|
"linterInvalidConfigError": {
|
||||||
"message": "Non salvato a causa di queste impostazioni di configurazione non valide:"
|
"message": "Non salvato a causa di queste impostazioni di configurazione non valide:"
|
||||||
},
|
},
|
||||||
|
@ -411,6 +364,9 @@
|
||||||
"liveReloadLabel": {
|
"liveReloadLabel": {
|
||||||
"message": "Ricaricamento live"
|
"message": "Ricaricamento live"
|
||||||
},
|
},
|
||||||
|
"manageFaviconsHelp": {
|
||||||
|
"message": "Stylus utilizza un servizio esterno https://www.google.com/s2/favicons"
|
||||||
|
},
|
||||||
"manageFilters": {
|
"manageFilters": {
|
||||||
"message": "Filtri"
|
"message": "Filtri"
|
||||||
},
|
},
|
||||||
|
@ -447,57 +403,16 @@
|
||||||
"menuShowBadge": {
|
"menuShowBadge": {
|
||||||
"message": "Mostra contatore stili attivi"
|
"message": "Mostra contatore stili attivi"
|
||||||
},
|
},
|
||||||
"meta_invalidNumber": {
|
|
||||||
"message": "Atteso un numero"
|
|
||||||
},
|
|
||||||
"meta_invalidString": {
|
|
||||||
"message": "Attesa una stringa tra apici"
|
|
||||||
},
|
|
||||||
"meta_invalidWord": {
|
|
||||||
"message": "Attesa una parola"
|
|
||||||
},
|
|
||||||
"meta_missingChar": {
|
|
||||||
"message": "Attesi caratteri: $chars$",
|
|
||||||
"placeholders": {
|
|
||||||
"chars": {
|
|
||||||
"content": "$1"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"meta_missingEOT": {
|
|
||||||
"message": "Atteso EOT"
|
|
||||||
},
|
|
||||||
"meta_missingMandatory": {
|
|
||||||
"message": "Manca metadata obbligatorio: $keys$",
|
|
||||||
"placeholders": {
|
|
||||||
"keys": {
|
|
||||||
"content": "$1"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"meta_unknownJSONLiteral": {
|
|
||||||
"message": "JSON non valido: $literal$ non è un letterale JSON valido",
|
|
||||||
"placeholders": {
|
|
||||||
"literal": {
|
|
||||||
"content": "$1"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"meta_unknownMeta": {
|
|
||||||
"message": "metadata sconosciuto: $key$",
|
|
||||||
"placeholders": {
|
|
||||||
"key": {
|
|
||||||
"content": "$1"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"noStylesForSite": {
|
"noStylesForSite": {
|
||||||
"message": "Nessuno stile installato per questo sito."
|
"message": "Nessuno stile installato per questo sito."
|
||||||
},
|
},
|
||||||
"openManage": {
|
"openManage": {
|
||||||
"message": "Gestisci gli stili installati"
|
"message": "Gestisci gli stili installati"
|
||||||
},
|
},
|
||||||
"openOptions": {
|
"openOptionsManage": {
|
||||||
|
"message": "Opzioni UI"
|
||||||
|
},
|
||||||
|
"openOptionsPopup": {
|
||||||
"message": "Opzioni"
|
"message": "Opzioni"
|
||||||
},
|
},
|
||||||
"openStylesManager": {
|
"openStylesManager": {
|
||||||
|
@ -578,9 +493,6 @@
|
||||||
"popupBordersTooltip": {
|
"popupBordersTooltip": {
|
||||||
"message": "Utile per temi scuri nelle nuove versioni di Chrome dato che non colora più i bordi laterali"
|
"message": "Utile per temi scuri nelle nuove versioni di Chrome dato che non colora più i bordi laterali"
|
||||||
},
|
},
|
||||||
"popupHotkeysTooltip": {
|
|
||||||
"message": "Click per visualizzare le scorciatoie disponibili"
|
|
||||||
},
|
|
||||||
"popupManageTooltip": {
|
"popupManageTooltip": {
|
||||||
"message": "Shift-click o click destro apre il gestore con gli stili applicabili per il sito corrente"
|
"message": "Shift-click o click destro apre il gestore con gli stili applicabili per il sito corrente"
|
||||||
},
|
},
|
||||||
|
@ -611,9 +523,6 @@
|
||||||
"search": {
|
"search": {
|
||||||
"message": "Cerca"
|
"message": "Cerca"
|
||||||
},
|
},
|
||||||
"searchNumberOfResults": {
|
|
||||||
"message": "Numero di occorrenze"
|
|
||||||
},
|
|
||||||
"searchResultInstallCount": {
|
"searchResultInstallCount": {
|
||||||
"message": "Installazioni totali"
|
"message": "Installazioni totali"
|
||||||
},
|
},
|
||||||
|
@ -629,18 +538,21 @@
|
||||||
"searchResultWeeklyCount": {
|
"searchResultWeeklyCount": {
|
||||||
"message": "Installazioni settimanali"
|
"message": "Installazioni settimanali"
|
||||||
},
|
},
|
||||||
|
"searchStyles": {
|
||||||
|
"message": "Contenuti ricerca"
|
||||||
|
},
|
||||||
"sectionAdd": {
|
"sectionAdd": {
|
||||||
"message": "Aggiungi un'altra sezione"
|
"message": "Aggiungi un'altra sezione"
|
||||||
},
|
},
|
||||||
"sectionCode": {
|
"sectionCode": {
|
||||||
"message": "Codice"
|
"message": "Codice"
|
||||||
},
|
},
|
||||||
|
"sectionHelp": {
|
||||||
|
"message": "Le sezioni consentono di definire diverse parti di codice da applicare a diversi insiemi di URL dello stesso stile. Ad esempio, un unico stile potrebbe modificare la home page di un sito diversamente da come modificherebbe il resto del sito."
|
||||||
|
},
|
||||||
"sectionRemove": {
|
"sectionRemove": {
|
||||||
"message": "Rimuovi sezione"
|
"message": "Rimuovi sezione"
|
||||||
},
|
},
|
||||||
"sections": {
|
|
||||||
"message": "Sezioni"
|
|
||||||
},
|
|
||||||
"shortcuts": {
|
"shortcuts": {
|
||||||
"message": "Scorciatoie"
|
"message": "Scorciatoie"
|
||||||
},
|
},
|
||||||
|
@ -665,9 +577,6 @@
|
||||||
"styleBadRegexp": {
|
"styleBadRegexp": {
|
||||||
"message": "Regexp non valida."
|
"message": "Regexp non valida."
|
||||||
},
|
},
|
||||||
"styleBeautifyPreserveNewlines": {
|
|
||||||
"message": "Mantieni gli \"a capo\""
|
|
||||||
},
|
|
||||||
"styleCancelEditLabel": {
|
"styleCancelEditLabel": {
|
||||||
"message": "Torna a gestione"
|
"message": "Torna a gestione"
|
||||||
},
|
},
|
||||||
|
@ -713,27 +622,29 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"styleMetaErrorColor": {
|
||||||
|
"message": "$color$ non è un colore valido",
|
||||||
|
"placeholders": {
|
||||||
|
"color": {
|
||||||
|
"content": "$1"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"styleMissingName": {
|
"styleMissingName": {
|
||||||
"message": "Inserisci un nome"
|
"message": "Inserisci un nome"
|
||||||
},
|
},
|
||||||
"styleMozillaFormatHeading": {
|
"styleMozillaFormatHeading": {
|
||||||
"message": "Formato Mozilla"
|
"message": "Formato Mozilla"
|
||||||
},
|
},
|
||||||
"styleNotAppliedRegexpProblemTooltip": {
|
"styleRegexpTestButton": {
|
||||||
"message": "Lo stile non è stato applicato a causa dell'uso errato di 'regexp()'"
|
"message": "Test RegExp"
|
||||||
},
|
|
||||||
"styleRegexpInvalidExplanation": {
|
|
||||||
"message": "Qualche regola 'regexp()' che non può essere compilata."
|
|
||||||
},
|
|
||||||
"styleRegexpProblemTooltip": {
|
|
||||||
"message": "Numero di sezioni non applicato a causa dell'uso errato di 'regexp()'"
|
|
||||||
},
|
|
||||||
"styleRegexpTestInvalid": {
|
|
||||||
"message": "Regexp invalida ignorata"
|
|
||||||
},
|
},
|
||||||
"styleSaveLabel": {
|
"styleSaveLabel": {
|
||||||
"message": "Salva"
|
"message": "Salva"
|
||||||
},
|
},
|
||||||
|
"styleSectionsTitle": {
|
||||||
|
"message": "Sezioni"
|
||||||
|
},
|
||||||
"styleToMozillaFormatHelp": {
|
"styleToMozillaFormatHelp": {
|
||||||
"message": "Il formato Mozilla del codice può essere utilizzato con Stylish per Firefox e può essere inviato a userstyles.org."
|
"message": "Il formato Mozilla del codice può essere utilizzato con Stylish per Firefox e può essere inviato a userstyles.org."
|
||||||
},
|
},
|
||||||
|
@ -760,15 +671,12 @@
|
||||||
"undoGlobal": {
|
"undoGlobal": {
|
||||||
"message": "Annulla in tutte le sezioni"
|
"message": "Annulla in tutte le sezioni"
|
||||||
},
|
},
|
||||||
"unreachableAMO": {
|
"unreachableAMOHintOldFF": {
|
||||||
"message": "Firefox proibisce l'accesso al sito."
|
"message": "Solo Firefox 59 e successivi possono essere configurati per consentire alle WebExtensions di aggiungere elementi degli stili su siti CSP-protected come questo."
|
||||||
},
|
},
|
||||||
"unreachableContentScript": {
|
"unreachableContentScript": {
|
||||||
"message": "Impossibile comunicare con la pagina. Prova a ricaricare la scheda."
|
"message": "Impossibile comunicare con la pagina. Prova a ricaricare la scheda."
|
||||||
},
|
},
|
||||||
"unreachableMozSiteHintOldFF": {
|
|
||||||
"message": "Solo Firefox 59 e successivi possono essere configurati per consentire alle WebExtensions di aggiungere elementi degli stili su siti CSP-protected come questo."
|
|
||||||
},
|
|
||||||
"updateAllCheckSucceededNoUpdate": {
|
"updateAllCheckSucceededNoUpdate": {
|
||||||
"message": "Nessun aggiornamento trovato"
|
"message": "Nessun aggiornamento trovato"
|
||||||
},
|
},
|
||||||
|
|
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
|
@ -1,7 +1,4 @@
|
||||||
{
|
{
|
||||||
"InaccessibleFileHint": {
|
|
||||||
"message": "Stylus não pode acessar alguns tipos de arquivos (ex: arquivos pdf e json)."
|
|
||||||
},
|
|
||||||
"addStyleLabel": {
|
"addStyleLabel": {
|
||||||
"message": "Escrever novo estilo"
|
"message": "Escrever novo estilo"
|
||||||
},
|
},
|
||||||
|
@ -67,6 +64,9 @@
|
||||||
"backupButtons": {
|
"backupButtons": {
|
||||||
"message": "Cópia de segurança"
|
"message": "Cópia de segurança"
|
||||||
},
|
},
|
||||||
|
"backupMessage": {
|
||||||
|
"message": "Selecione um ficheiro ou arraste e solte-o nesta página."
|
||||||
|
},
|
||||||
"bckpInstStyles": {
|
"bckpInstStyles": {
|
||||||
"message": "Exportar estilos"
|
"message": "Exportar estilos"
|
||||||
},
|
},
|
||||||
|
@ -133,9 +133,6 @@
|
||||||
"cm_theme": {
|
"cm_theme": {
|
||||||
"message": "Tema"
|
"message": "Tema"
|
||||||
},
|
},
|
||||||
"colorpickerPaletteHint": {
|
|
||||||
"message": "Clique com o botão direito em uma amostra para percorrer suas linhas de código"
|
|
||||||
},
|
|
||||||
"colorpickerSwitchFormatTooltip": {
|
"colorpickerSwitchFormatTooltip": {
|
||||||
"message": "Mudar formatos: HEX -> RGB -> HSL.\nShift-clique para inverter a direção.\nTambém através das teclas PgUp (PageUp), PgDn (PageDown)."
|
"message": "Mudar formatos: HEX -> RGB -> HSL.\nShift-clique para inverter a direção.\nTambém através das teclas PgUp (PageUp), PgDn (PageDown)."
|
||||||
},
|
},
|
||||||
|
@ -181,32 +178,6 @@
|
||||||
"confirmYes": {
|
"confirmYes": {
|
||||||
"message": "Sim"
|
"message": "Sim"
|
||||||
},
|
},
|
||||||
"connectingDropbox": {
|
|
||||||
"message": "Conectando ao Dropbox..."
|
|
||||||
},
|
|
||||||
"connectingDropboxNotAllowed": {
|
|
||||||
"message": "Conectar ao Dropbox somente é disponível em apps instalados diretamente da loja web"
|
|
||||||
},
|
|
||||||
"copied": {
|
|
||||||
"message": "Copiado para a área de transferência"
|
|
||||||
},
|
|
||||||
"copy": {
|
|
||||||
"message": "Copiar para a área de transferência"
|
|
||||||
},
|
|
||||||
"customNameHint": {
|
|
||||||
"message": "Digite um nome personalizado para renomear o estilo na UI sem quebrar suas atualizações"
|
|
||||||
},
|
|
||||||
"customNameResetHint": {
|
|
||||||
"message": "Deixar de usar o nome personalizado, usar o próprio nome do estilo"
|
|
||||||
},
|
|
||||||
"dateAbbrYear": {
|
|
||||||
"message": "$value$a",
|
|
||||||
"placeholders": {
|
|
||||||
"value": {
|
|
||||||
"content": "$1"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"dateInstalled": {
|
"dateInstalled": {
|
||||||
"message": "Data de instalação"
|
"message": "Data de instalação"
|
||||||
},
|
},
|
||||||
|
@ -237,9 +208,6 @@
|
||||||
"dragDropMessage": {
|
"dragDropMessage": {
|
||||||
"message": "Solte o ficheiro da sua cópia de segurança em qualquer sítio nesta página para importar."
|
"message": "Solte o ficheiro da sua cópia de segurança em qualquer sítio nesta página para importar."
|
||||||
},
|
},
|
||||||
"dragDropUsercssTabstrip": {
|
|
||||||
"message": "Para instalar o arquivo, solte-o na linha das abas (a área onde os títulos das abas são mostrados)."
|
|
||||||
},
|
|
||||||
"editDeleteText": {
|
"editDeleteText": {
|
||||||
"message": "Eliminar"
|
"message": "Eliminar"
|
||||||
},
|
},
|
||||||
|
@ -260,21 +228,15 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"editorStylesButton": {
|
||||||
|
"message": "Encontrar estilos para o editor"
|
||||||
|
},
|
||||||
"enableStyleLabel": {
|
"enableStyleLabel": {
|
||||||
"message": "Ativar"
|
"message": "Ativar"
|
||||||
},
|
},
|
||||||
"excludeStyleByDomainLabel": {
|
|
||||||
"message": "Excluir o domínio atual"
|
|
||||||
},
|
|
||||||
"excludeStyleByUrlLabel": {
|
|
||||||
"message": "Excluir a URL atual"
|
|
||||||
},
|
|
||||||
"exportLabel": {
|
"exportLabel": {
|
||||||
"message": "Exportar"
|
"message": "Exportar"
|
||||||
},
|
},
|
||||||
"exportSavedSuccess": {
|
|
||||||
"message": "Arquivo salvo com sucesso"
|
|
||||||
},
|
|
||||||
"externalLink": {
|
"externalLink": {
|
||||||
"message": "Hiperligação externa"
|
"message": "Hiperligação externa"
|
||||||
},
|
},
|
||||||
|
@ -301,6 +263,15 @@
|
||||||
"findStyles": {
|
"findStyles": {
|
||||||
"message": "Localizar estilos"
|
"message": "Localizar estilos"
|
||||||
},
|
},
|
||||||
|
"findStylesForSite": {
|
||||||
|
"message": "Encontrar mais estilos para este site"
|
||||||
|
},
|
||||||
|
"findStylesInline": {
|
||||||
|
"message": "inline"
|
||||||
|
},
|
||||||
|
"findStylesInlineTooltip": {
|
||||||
|
"message": "Exibir os resultados da pesquisa dentro desta janela."
|
||||||
|
},
|
||||||
"genericAdd": {
|
"genericAdd": {
|
||||||
"message": "Adicionar"
|
"message": "Adicionar"
|
||||||
},
|
},
|
||||||
|
@ -334,9 +305,6 @@
|
||||||
"genericUnknown": {
|
"genericUnknown": {
|
||||||
"message": "Desconhecido"
|
"message": "Desconhecido"
|
||||||
},
|
},
|
||||||
"gettingStyles": {
|
|
||||||
"message": "Obtendo todos os estilos..."
|
|
||||||
},
|
|
||||||
"helpAlt": {
|
"helpAlt": {
|
||||||
"message": "Ajuda"
|
"message": "Ajuda"
|
||||||
},
|
},
|
||||||
|
@ -346,9 +314,6 @@
|
||||||
"helpKeyMapHotkey": {
|
"helpKeyMapHotkey": {
|
||||||
"message": "Prima uma tecla de atalho"
|
"message": "Prima uma tecla de atalho"
|
||||||
},
|
},
|
||||||
"hostDisabled": {
|
|
||||||
"message": "Este hospedeiro foi desabilitado devido a um bug na versão atual que o navegador utilizado se encontra"
|
|
||||||
},
|
|
||||||
"importAppendLabel": {
|
"importAppendLabel": {
|
||||||
"message": "Acrescentar ao estilo"
|
"message": "Acrescentar ao estilo"
|
||||||
},
|
},
|
||||||
|
@ -358,12 +323,6 @@
|
||||||
"importLabel": {
|
"importLabel": {
|
||||||
"message": "Importar"
|
"message": "Importar"
|
||||||
},
|
},
|
||||||
"importPreprocessor": {
|
|
||||||
"message": "Estilos com <code>@preprocessor</code> não irão funcionar no modo clássico. Você pode trocar o editor para o modo UserCSS: 1) abre o gerenciador de estilos, 2) ative a caixa \"como UserCSS\", 3) clique \"Escrever novo estilo\"\n\nDeseja importar mesmo assim?"
|
|
||||||
},
|
|
||||||
"importPreprocessorTitle": {
|
|
||||||
"message": "Possível problema causado pelo @preprocessor"
|
|
||||||
},
|
|
||||||
"importReplaceLabel": {
|
"importReplaceLabel": {
|
||||||
"message": "Sobrescrever estilo"
|
"message": "Sobrescrever estilo"
|
||||||
},
|
},
|
||||||
|
@ -426,6 +385,9 @@
|
||||||
"installUpdateFromLabel": {
|
"installUpdateFromLabel": {
|
||||||
"message": "Procurar atualizações"
|
"message": "Procurar atualizações"
|
||||||
},
|
},
|
||||||
|
"installUpdateUnavailable": {
|
||||||
|
"message": "Para ativar a verificação de atualizações, solte o ficheiro na faixa de separadores ou especifique @updateURL nos metadados de estilo."
|
||||||
|
},
|
||||||
"license": {
|
"license": {
|
||||||
"message": "Licença"
|
"message": "Licença"
|
||||||
},
|
},
|
||||||
|
@ -487,14 +449,14 @@
|
||||||
"message": "Ocorreu um erro ao vigiar o arquivo"
|
"message": "Ocorreu um erro ao vigiar o arquivo"
|
||||||
},
|
},
|
||||||
"liveReloadInstallHint": {
|
"liveReloadInstallHint": {
|
||||||
"message": "Mantenha esta guia aberta para atualizar automaticamente o estilo sob mudanças externas."
|
"message": "O recarregamento dinâmico está ativado para que o estilo instalado seja atualizado automaticamente em alterações externas enquanto esse separador e o separador do arquivo de origem estiverem abertos."
|
||||||
},
|
|
||||||
"liveReloadInstallHintFF": {
|
|
||||||
"message": "Mantenha juntamente esta guia e a guia original abertas para atualizar automaticamente o estilo sob mudanças externas."
|
|
||||||
},
|
},
|
||||||
"liveReloadLabel": {
|
"liveReloadLabel": {
|
||||||
"message": "Recarregamento dinâmico"
|
"message": "Recarregamento dinâmico"
|
||||||
},
|
},
|
||||||
|
"liveReloadUnavailable": {
|
||||||
|
"message": "Para ativar recarregamento dinâmico, solte o ficheiro na faixa de separadores (a área onde os títulos dos separadores são mostrados)."
|
||||||
|
},
|
||||||
"manageFavicons": {
|
"manageFavicons": {
|
||||||
"message": "Favicons em colunas de aplica-se a"
|
"message": "Favicons em colunas de aplica-se a"
|
||||||
},
|
},
|
||||||
|
@ -502,7 +464,7 @@
|
||||||
"message": "Acinzentado(s)"
|
"message": "Acinzentado(s)"
|
||||||
},
|
},
|
||||||
"manageFaviconsHelp": {
|
"manageFaviconsHelp": {
|
||||||
"message": "O Stylus usa um serviço externo https://icons.duckduckgo.com"
|
"message": "O Stylus usa um serviço externo https://www.google.com/s2/favicons"
|
||||||
},
|
},
|
||||||
"manageFilters": {
|
"manageFilters": {
|
||||||
"message": "Filtros"
|
"message": "Filtros"
|
||||||
|
@ -546,71 +508,16 @@
|
||||||
"menuShowBadge": {
|
"menuShowBadge": {
|
||||||
"message": "Mostrar a contagem de estilos ativados"
|
"message": "Mostrar a contagem de estilos ativados"
|
||||||
},
|
},
|
||||||
"meta_invalidCheckboxDefault": {
|
|
||||||
"message": "Inválida @var checkbox: o valor deve ser 0 ou 1"
|
|
||||||
},
|
|
||||||
"meta_invalidNumber": {
|
|
||||||
"message": "Espera-se um número"
|
|
||||||
},
|
|
||||||
"meta_invalidRange": {
|
|
||||||
"message": "@var inválido $type$: valor deve ser um número ou um vetor",
|
|
||||||
"placeholders": {
|
|
||||||
"type": {
|
|
||||||
"content": "$1"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"meta_invalidString": {
|
|
||||||
"message": "Espera-se um texto entre aspas"
|
|
||||||
},
|
|
||||||
"meta_invalidWord": {
|
|
||||||
"message": "Espera-se uma palavra"
|
|
||||||
},
|
|
||||||
"meta_missingChar": {
|
|
||||||
"message": "Caracteres esperados: $chars$",
|
|
||||||
"placeholders": {
|
|
||||||
"chars": {
|
|
||||||
"content": "$1"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"meta_missingMandatory": {
|
|
||||||
"message": "Metadata obrigatório não encontrado: $keys$",
|
|
||||||
"placeholders": {
|
|
||||||
"keys": {
|
|
||||||
"content": "$1"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"meta_unknownMeta": {
|
|
||||||
"message": "Metadata desconhecido: $key$",
|
|
||||||
"placeholders": {
|
|
||||||
"key": {
|
|
||||||
"content": "$1"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"meta_unknownVarType": {
|
|
||||||
"message": "Tipo desconhecido da variável @$varkey$: $vartype$",
|
|
||||||
"placeholders": {
|
|
||||||
"varkey": {
|
|
||||||
"content": "$1"
|
|
||||||
},
|
|
||||||
"vartype": {
|
|
||||||
"content": "$2"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"noFileToImport": {
|
|
||||||
"message": "Para importar seus estilos, você deve exportar primeiro."
|
|
||||||
},
|
|
||||||
"noStylesForSite": {
|
"noStylesForSite": {
|
||||||
"message": "Nenhum estilo instalado para este site."
|
"message": "Nenhum estilo instalado para este site."
|
||||||
},
|
},
|
||||||
"openManage": {
|
"openManage": {
|
||||||
"message": "Gerir"
|
"message": "Gerir"
|
||||||
},
|
},
|
||||||
"openOptions": {
|
"openOptionsManage": {
|
||||||
|
"message": "interface de Opções"
|
||||||
|
},
|
||||||
|
"openOptionsPopup": {
|
||||||
"message": "Opções"
|
"message": "Opções"
|
||||||
},
|
},
|
||||||
"openStylesManager": {
|
"openStylesManager": {
|
||||||
|
@ -628,6 +535,9 @@
|
||||||
"optionsAdvancedExposeIframes": {
|
"optionsAdvancedExposeIframes": {
|
||||||
"message": "Expor iframes via HTML[stylus-iframe]"
|
"message": "Expor iframes via HTML[stylus-iframe]"
|
||||||
},
|
},
|
||||||
|
"optionsAdvancedExposeIframesNote": {
|
||||||
|
"message": "Expõe o domínio do site principal em cada iframe.\nPermite escrever CSS específico para iframe assim:\nhtml [stylus-iframe $ = \"twitter.com\"] h1 {display: none}"
|
||||||
|
},
|
||||||
"optionsAdvancedNewStyleAsUsercss": {
|
"optionsAdvancedNewStyleAsUsercss": {
|
||||||
"message": "Escrever novo estilo como usercss"
|
"message": "Escrever novo estilo como usercss"
|
||||||
},
|
},
|
||||||
|
@ -649,9 +559,6 @@
|
||||||
"optionsCustomizeIcon": {
|
"optionsCustomizeIcon": {
|
||||||
"message": "Ícone da barra de ferramentas"
|
"message": "Ícone da barra de ferramentas"
|
||||||
},
|
},
|
||||||
"optionsCustomizeSync": {
|
|
||||||
"message": "Sincronizar para a nuvem"
|
|
||||||
},
|
|
||||||
"optionsCustomizeUpdate": {
|
"optionsCustomizeUpdate": {
|
||||||
"message": "Atualizações"
|
"message": "Atualizações"
|
||||||
},
|
},
|
||||||
|
@ -682,42 +589,12 @@
|
||||||
"optionsSubheading": {
|
"optionsSubheading": {
|
||||||
"message": "Mais Opções"
|
"message": "Mais Opções"
|
||||||
},
|
},
|
||||||
"optionsSyncConnect": {
|
|
||||||
"message": "Conectar"
|
|
||||||
},
|
|
||||||
"optionsSyncDisconnect": {
|
|
||||||
"message": "Desconectar"
|
|
||||||
},
|
|
||||||
"optionsSyncNone": {
|
|
||||||
"message": "Nenhum"
|
|
||||||
},
|
|
||||||
"optionsSyncStatusConnected": {
|
|
||||||
"message": "Conectado"
|
|
||||||
},
|
|
||||||
"optionsSyncStatusConnecting": {
|
|
||||||
"message": "Conectando..."
|
|
||||||
},
|
|
||||||
"optionsSyncStatusDisconnected": {
|
|
||||||
"message": "Desconectado"
|
|
||||||
},
|
|
||||||
"optionsSyncStatusDisconnecting": {
|
|
||||||
"message": "Desconectando..."
|
|
||||||
},
|
|
||||||
"optionsSyncStatusSyncing": {
|
|
||||||
"message": "Sincronizando..."
|
|
||||||
},
|
|
||||||
"optionsSyncSyncNow": {
|
|
||||||
"message": "Sincronizar agora"
|
|
||||||
},
|
|
||||||
"optionsUpdateImportNote": {
|
"optionsUpdateImportNote": {
|
||||||
"message": "Ao importar uma cópia de segurança de estilo da versão antiga ou do Stylish, faça uma verificação única de atualizações manualmente no gestor de estilos para garantir que todos os estilos sejam atualizados."
|
"message": "Ao importar uma cópia de segurança de estilo da versão antiga ou do Stylish, faça uma verificação única de atualizações manualmente no gestor de estilos para garantir que todos os estilos sejam atualizados."
|
||||||
},
|
},
|
||||||
"optionsUpdateInterval": {
|
"optionsUpdateInterval": {
|
||||||
"message": "Intervalo de atualização automática do estilo de usuário em horas (especifique 0 para desativar)"
|
"message": "Intervalo de atualização automática do estilo de usuário em horas (especifique 0 para desativar)"
|
||||||
},
|
},
|
||||||
"overwriteFileExport": {
|
|
||||||
"message": "Você gostaria de substituir um arquivo existente?"
|
|
||||||
},
|
|
||||||
"paginationCurrent": {
|
"paginationCurrent": {
|
||||||
"message": "Pagina atual"
|
"message": "Pagina atual"
|
||||||
},
|
},
|
||||||
|
@ -769,9 +646,6 @@
|
||||||
"previewTooltip": {
|
"previewTooltip": {
|
||||||
"message": "Temporariamente aplica as alterações sem guardar.\nGuarde o estilo para tornar as alterações permanentes."
|
"message": "Temporariamente aplica as alterações sem guardar.\nGuarde o estilo para tornar as alterações permanentes."
|
||||||
},
|
},
|
||||||
"readingStyles": {
|
|
||||||
"message": "Lendo estilos..."
|
|
||||||
},
|
|
||||||
"replace": {
|
"replace": {
|
||||||
"message": "Substituir"
|
"message": "Substituir"
|
||||||
},
|
},
|
||||||
|
@ -784,9 +658,6 @@
|
||||||
"retrieveBckp": {
|
"retrieveBckp": {
|
||||||
"message": "Importar estilos"
|
"message": "Importar estilos"
|
||||||
},
|
},
|
||||||
"retrieveDropboxSync": {
|
|
||||||
"message": "Importar do Dropbox"
|
|
||||||
},
|
|
||||||
"search": {
|
"search": {
|
||||||
"message": "Pesquisar"
|
"message": "Pesquisar"
|
||||||
},
|
},
|
||||||
|
@ -817,21 +688,27 @@
|
||||||
"searchResultWeeklyCount": {
|
"searchResultWeeklyCount": {
|
||||||
"message": "Instalações semanais"
|
"message": "Instalações semanais"
|
||||||
},
|
},
|
||||||
|
"searchStyles": {
|
||||||
|
"message": "Pesquisar conteúdos"
|
||||||
|
},
|
||||||
|
"searchStylesHelp": {
|
||||||
|
"message": "A tecla </> foca o campo de pesquisa.\nTexto simples: pesquisa dentro do nome, código, URL da homepage e aplica-se a sites . Palavras com menos de 3 letras são ignoradas.\nEstilos que correspondem a um URL completo: prefixe a pesquisa com<url:>, e.g. <url:https://github.com/openstyles/stylus>\nExpressões regulares: inclua barras e sinalizadores, e.g. </body.*?\\ba\\b/simguy>\nPalavras exatas: envolva a consulta entre aspas duplas, e.g. <\". header ~ div\">"
|
||||||
|
},
|
||||||
"sectionAdd": {
|
"sectionAdd": {
|
||||||
"message": "Adicionar outra secção"
|
"message": "Adicionar outra secção"
|
||||||
},
|
},
|
||||||
"sectionCode": {
|
"sectionCode": {
|
||||||
"message": "Código"
|
"message": "Código"
|
||||||
},
|
},
|
||||||
|
"sectionHelp": {
|
||||||
|
"message": "As secções deixam-lhe definir diferentes peças de código a aplicar em diferentes conjuntos de URLs no mesmo estilo. Por exemplo, um só estilo pode mudar a homepage de um site de uma maneira, enquanto muda o resto do site de outra maneira."
|
||||||
|
},
|
||||||
"sectionRemove": {
|
"sectionRemove": {
|
||||||
"message": "Remover secção"
|
"message": "Remover secção"
|
||||||
},
|
},
|
||||||
"sectionRestore": {
|
"sectionRestore": {
|
||||||
"message": "Restaurar secção removida"
|
"message": "Restaurar secção removida"
|
||||||
},
|
},
|
||||||
"sections": {
|
|
||||||
"message": "Secções"
|
|
||||||
},
|
|
||||||
"shortcuts": {
|
"shortcuts": {
|
||||||
"message": "Atalhos"
|
"message": "Atalhos"
|
||||||
},
|
},
|
||||||
|
@ -865,9 +742,6 @@
|
||||||
"styleBeautify": {
|
"styleBeautify": {
|
||||||
"message": "Embelezar"
|
"message": "Embelezar"
|
||||||
},
|
},
|
||||||
"styleBeautifyHint": {
|
|
||||||
"message": "Dica: clique com o botão direito no botão \"Embelezar\" ou use o atalho de teclado definido para embelezar sem mostrar esse painel"
|
|
||||||
},
|
|
||||||
"styleBeautifyIndentConditional": {
|
"styleBeautifyIndentConditional": {
|
||||||
"message": "Indentar @media, @supports"
|
"message": "Indentar @media, @supports"
|
||||||
},
|
},
|
||||||
|
@ -919,6 +793,36 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"styleMetaErrorCheckbox": {
|
||||||
|
"message": "Inválida @var checkbox: o valor deve ser 0 ou 1"
|
||||||
|
},
|
||||||
|
"styleMetaErrorColor": {
|
||||||
|
"message": "$color$não é uma cor válida",
|
||||||
|
"placeholders": {
|
||||||
|
"color": {
|
||||||
|
"content": "$1"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"styleMetaErrorPreprocessor": {
|
||||||
|
"message": "Não e suportado o @preprocessador:$preprocessor$",
|
||||||
|
"placeholders": {
|
||||||
|
"preprocessor": {
|
||||||
|
"content": "$1"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"styleMetaErrorSelectValueMismatch": {
|
||||||
|
"message": "Inválido @select: o valor não existe na lista"
|
||||||
|
},
|
||||||
|
"styleMissingMeta": {
|
||||||
|
"message": "Metadados em falta @$key$",
|
||||||
|
"placeholders": {
|
||||||
|
"key": {
|
||||||
|
"content": "$1"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"styleMissingName": {
|
"styleMissingName": {
|
||||||
"message": "Introduzir um nome"
|
"message": "Introduzir um nome"
|
||||||
},
|
},
|
||||||
|
@ -937,6 +841,9 @@
|
||||||
"styleRegexpProblemTooltip": {
|
"styleRegexpProblemTooltip": {
|
||||||
"message": "Número de secções não aplicadas devido ao uso incorreto de 'regexp ()'"
|
"message": "Número de secções não aplicadas devido ao uso incorreto de 'regexp ()'"
|
||||||
},
|
},
|
||||||
|
"styleRegexpTestButton": {
|
||||||
|
"message": "Testar RegExp"
|
||||||
|
},
|
||||||
"styleRegexpTestFull": {
|
"styleRegexpTestFull": {
|
||||||
"message": "Separadores correspondentes"
|
"message": "Separadores correspondentes"
|
||||||
},
|
},
|
||||||
|
@ -958,6 +865,9 @@
|
||||||
"styleSaveLabel": {
|
"styleSaveLabel": {
|
||||||
"message": "Guardar"
|
"message": "Guardar"
|
||||||
},
|
},
|
||||||
|
"styleSectionsTitle": {
|
||||||
|
"message": "Secções"
|
||||||
|
},
|
||||||
"styleToMozillaFormatHelp": {
|
"styleToMozillaFormatHelp": {
|
||||||
"message": "O formato Mozilla do código pode ser submetido aouserstyles.org e usado com o clássico Stylish para o Firefox"
|
"message": "O formato Mozilla do código pode ser submetido aouserstyles.org e usado com o clássico Stylish para o Firefox"
|
||||||
},
|
},
|
||||||
|
@ -981,9 +891,6 @@
|
||||||
"stylusUnavailableForURLdetails": {
|
"stylusUnavailableForURLdetails": {
|
||||||
"message": "Como precaução de segurança, o browser proíbe extensões de afetar as páginas embutidas (como chrome://version, a página de novo separador predefinido no Chrome 61, about:addons, e assim sucessivamente) tal como páginas de outras extensões. Cada browser também restringe acesso à sua própria galeria de extensões (como Chrome Web Store ou AMO)"
|
"message": "Como precaução de segurança, o browser proíbe extensões de afetar as páginas embutidas (como chrome://version, a página de novo separador predefinido no Chrome 61, about:addons, e assim sucessivamente) tal como páginas de outras extensões. Cada browser também restringe acesso à sua própria galeria de extensões (como Chrome Web Store ou AMO)"
|
||||||
},
|
},
|
||||||
"syncDropboxStyles": {
|
|
||||||
"message": "Exportar para Dropbox"
|
|
||||||
},
|
|
||||||
"syncStorageErrorSaving": {
|
"syncStorageErrorSaving": {
|
||||||
"message": "O valor não pode ser guardado. Tente reduzir a quantidade de texto."
|
"message": "O valor não pode ser guardado. Tente reduzir a quantidade de texto."
|
||||||
},
|
},
|
||||||
|
@ -1002,18 +909,18 @@
|
||||||
"unreachableAMOHint": {
|
"unreachableAMOHint": {
|
||||||
"message": "Para permitir o acesso abra<about:config>,clique com o botão direito na lista, clique em \"Novo\", depois em \"Booleano\", cole <privacy.resistFingerprinting.block_mozAddonManager> e clique em OK,<true>,OK, recarregue a página <addons.mozilla.org>."
|
"message": "Para permitir o acesso abra<about:config>,clique com o botão direito na lista, clique em \"Novo\", depois em \"Booleano\", cole <privacy.resistFingerprinting.block_mozAddonManager> e clique em OK,<true>,OK, recarregue a página <addons.mozilla.org>."
|
||||||
},
|
},
|
||||||
|
"unreachableAMOHintNewFF": {
|
||||||
|
"message": "No Firefox 60 e mais recente, você também terá que remover o domínio AMO de <extensions.webextensions.restrictedDomains> em <about:config>."
|
||||||
|
},
|
||||||
|
"unreachableAMOHintOldFF": {
|
||||||
|
"message": "Somente o Firefox 59 e o mais recente podem ser configurados para permitir que WebExtensions incluam elementos de estilo em sites protegidos por CSP como este."
|
||||||
|
},
|
||||||
"unreachableContentScript": {
|
"unreachableContentScript": {
|
||||||
"message": "Não foi possível comunicar com a página. Tente recarregar o separador."
|
"message": "Não foi possível comunicar com a página. Tente recarregar o separador."
|
||||||
},
|
},
|
||||||
"unreachableFileHint": {
|
"unreachableFileHint": {
|
||||||
"message": "O Stylus pode aceder URLs file:// apenas se ativar a checkbox correspondente para a extensão Stylus na página chrome://extensions"
|
"message": "O Stylus pode aceder URLs file:// apenas se ativar a checkbox correspondente para a extensão Stylus na página chrome://extensions"
|
||||||
},
|
},
|
||||||
"unreachableMozSiteHintOldFF": {
|
|
||||||
"message": "Somente o Firefox 59 e o mais recente podem ser configurados para permitir que WebExtensions incluam elementos de estilo em sites protegidos por CSP como este."
|
|
||||||
},
|
|
||||||
"unzipStyles": {
|
|
||||||
"message": "Descompactando estilos..."
|
|
||||||
},
|
|
||||||
"updateAllCheckSucceededNoUpdate": {
|
"updateAllCheckSucceededNoUpdate": {
|
||||||
"message": "Nenhuma atualização encontrada."
|
"message": "Nenhuma atualização encontrada."
|
||||||
},
|
},
|
||||||
|
@ -1055,9 +962,6 @@
|
||||||
"updatesCurrentlyInstalled": {
|
"updatesCurrentlyInstalled": {
|
||||||
"message": "Atualizações instaladas:"
|
"message": "Atualizações instaladas:"
|
||||||
},
|
},
|
||||||
"uploadingFile": {
|
|
||||||
"message": "Enviando arquivo..."
|
|
||||||
},
|
|
||||||
"usercssAvoidOverwriting": {
|
"usercssAvoidOverwriting": {
|
||||||
"message": "Por favor modifique o valor de @name ou @namespace para evitar sobrescrever um estilo existente."
|
"message": "Por favor modifique o valor de @name ou @namespace para evitar sobrescrever um estilo existente."
|
||||||
},
|
},
|
||||||
|
@ -1070,6 +974,9 @@
|
||||||
"usercssReplaceTemplateConfirmation": {
|
"usercssReplaceTemplateConfirmation": {
|
||||||
"message": "Substituir o modelo predefinido para novos estilos de Usercss com o código atual?"
|
"message": "Substituir o modelo predefinido para novos estilos de Usercss com o código atual?"
|
||||||
},
|
},
|
||||||
|
"usercssReplaceTemplateName": {
|
||||||
|
"message": "@name vazio substitui o modelo predefinido"
|
||||||
|
},
|
||||||
"usercssReplaceTemplateSectionBody": {
|
"usercssReplaceTemplateSectionBody": {
|
||||||
"message": "Insira o código aqui..."
|
"message": "Insira o código aqui..."
|
||||||
},
|
},
|
||||||
|
@ -1081,8 +988,5 @@
|
||||||
},
|
},
|
||||||
"writeStyleForURL": {
|
"writeStyleForURL": {
|
||||||
"message": "este URL"
|
"message": "este URL"
|
||||||
},
|
|
||||||
"zipStyles": {
|
|
||||||
"message": "Compactando estilos..."
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -61,6 +61,9 @@
|
||||||
"author": {
|
"author": {
|
||||||
"message": "Autor"
|
"message": "Autor"
|
||||||
},
|
},
|
||||||
|
"backupMessage": {
|
||||||
|
"message": "Selectați un fișier sau drag-and-drop aici"
|
||||||
|
},
|
||||||
"bckpInstStyles": {
|
"bckpInstStyles": {
|
||||||
"message": "Exportați teme"
|
"message": "Exportați teme"
|
||||||
},
|
},
|
||||||
|
@ -201,6 +204,9 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"editorStylesButton": {
|
||||||
|
"message": "Găsiți teme pentru editor"
|
||||||
|
},
|
||||||
"enableStyleLabel": {
|
"enableStyleLabel": {
|
||||||
"message": "Activați"
|
"message": "Activați"
|
||||||
},
|
},
|
||||||
|
@ -230,6 +236,12 @@
|
||||||
"findStyles": {
|
"findStyles": {
|
||||||
"message": "Găsiți teme"
|
"message": "Găsiți teme"
|
||||||
},
|
},
|
||||||
|
"findStylesForSite": {
|
||||||
|
"message": "Gasiți mai multe teme pentru acest site."
|
||||||
|
},
|
||||||
|
"findStylesInlineTooltip": {
|
||||||
|
"message": "Arătați rezultatele căutării în această pagină."
|
||||||
|
},
|
||||||
"genericAdd": {
|
"genericAdd": {
|
||||||
"message": "Adaugă"
|
"message": "Adaugă"
|
||||||
},
|
},
|
||||||
|
@ -340,6 +352,9 @@
|
||||||
"installUpdateFromLabel": {
|
"installUpdateFromLabel": {
|
||||||
"message": "Verificați update-urile"
|
"message": "Verificați update-urile"
|
||||||
},
|
},
|
||||||
|
"installUpdateUnavailable": {
|
||||||
|
"message": "Pentru a activa verificarea de updates. trage fișierul pe taburi (zona cu titluri) sau specifica @updateURL în metadata temei."
|
||||||
|
},
|
||||||
"license": {
|
"license": {
|
||||||
"message": "Licență"
|
"message": "Licență"
|
||||||
},
|
},
|
||||||
|
@ -397,6 +412,12 @@
|
||||||
"liveReloadError": {
|
"liveReloadError": {
|
||||||
"message": "A avut loc o eroare în timpul monitorizării acestui fișier"
|
"message": "A avut loc o eroare în timpul monitorizării acestui fișier"
|
||||||
},
|
},
|
||||||
|
"liveReloadInstallHint": {
|
||||||
|
"message": "Reload automat este activat deci tema instalată va fi updatată automat când acest tab si fișierul surca sunt deschise."
|
||||||
|
},
|
||||||
|
"liveReloadUnavailable": {
|
||||||
|
"message": "Pentru a activa live reload (refresh automat), trage fișierul pe taburi (zona unde titlurile temelor sunt afișate) "
|
||||||
|
},
|
||||||
"manageFavicons": {
|
"manageFavicons": {
|
||||||
"message": "Favicons în coloana 'se aplică la'"
|
"message": "Favicons în coloana 'se aplică la'"
|
||||||
},
|
},
|
||||||
|
@ -404,7 +425,7 @@
|
||||||
"message": "Hașurat"
|
"message": "Hașurat"
|
||||||
},
|
},
|
||||||
"manageFaviconsHelp": {
|
"manageFaviconsHelp": {
|
||||||
"message": "Stylus folosește un serviciu extern https://icons.duckduckgo.com"
|
"message": "Stylus folosește un serviciu extern https://www.google.com/s2/favicons"
|
||||||
},
|
},
|
||||||
"manageFilters": {
|
"manageFilters": {
|
||||||
"message": "Filtre"
|
"message": "Filtre"
|
||||||
|
@ -445,16 +466,16 @@
|
||||||
"menuShowBadge": {
|
"menuShowBadge": {
|
||||||
"message": "Afișați numărul temelor active"
|
"message": "Afișați numărul temelor active"
|
||||||
},
|
},
|
||||||
"meta_invalidCheckboxDefault": {
|
|
||||||
"message": "@var checkbox invalidă: valuarea trebuie să fie 0 sau 1"
|
|
||||||
},
|
|
||||||
"noStylesForSite": {
|
"noStylesForSite": {
|
||||||
"message": "Nicio temă instalată pentru acest site."
|
"message": "Nicio temă instalată pentru acest site."
|
||||||
},
|
},
|
||||||
"openManage": {
|
"openManage": {
|
||||||
"message": "Managerul"
|
"message": "Managerul"
|
||||||
},
|
},
|
||||||
"openOptions": {
|
"openOptionsManage": {
|
||||||
|
"message": "UI cu opțiuni"
|
||||||
|
},
|
||||||
|
"openOptionsPopup": {
|
||||||
"message": "Opțiuni"
|
"message": "Opțiuni"
|
||||||
},
|
},
|
||||||
"openStylesManager": {
|
"openStylesManager": {
|
||||||
|
@ -472,6 +493,9 @@
|
||||||
"optionsAdvancedExposeIframes": {
|
"optionsAdvancedExposeIframes": {
|
||||||
"message": "Expuneți iframes via HTML[stylus-iframe]"
|
"message": "Expuneți iframes via HTML[stylus-iframe]"
|
||||||
},
|
},
|
||||||
|
"optionsAdvancedExposeIframesNote": {
|
||||||
|
"message": "Expune domain-ul site-ului in fiecare iframe.\nActivează scrierea de CSS specific pentru iframe precum:\nhtml[stylus-iframe$=\"twitter.com\"] h1 { display:none }"
|
||||||
|
},
|
||||||
"optionsAdvancedNewStyleAsUsercss": {
|
"optionsAdvancedNewStyleAsUsercss": {
|
||||||
"message": "Scrieți temă nouă în formatul usercss"
|
"message": "Scrieți temă nouă în formatul usercss"
|
||||||
},
|
},
|
||||||
|
@ -610,21 +634,27 @@
|
||||||
"searchResultWeeklyCount": {
|
"searchResultWeeklyCount": {
|
||||||
"message": "Instalări săptămânale"
|
"message": "Instalări săptămânale"
|
||||||
},
|
},
|
||||||
|
"searchStyles": {
|
||||||
|
"message": "Conținutul căutării"
|
||||||
|
},
|
||||||
|
"searchStylesHelp": {
|
||||||
|
"message": "</> aduce în focus bara de căutare.\nText simplu: caută în nume, cod, homepage URL și site-uri la care se aplică tema. Cuvinte cu mai puțin de 3 litere sunt ignorate.\nURL găsit: adaugă căutarea ca prefix <url:>, ex. <url:https://github.com/openstyles/stylus>\nExpresii obișnuite: include slashes și flags, ex. </body.*?\\ba\\b/simguy>\nCuvinte exacte: introduceți între ghilimele, ex. <\".header ~ div\">"
|
||||||
|
},
|
||||||
"sectionAdd": {
|
"sectionAdd": {
|
||||||
"message": "Adăugați o altă secțiune"
|
"message": "Adăugați o altă secțiune"
|
||||||
},
|
},
|
||||||
"sectionCode": {
|
"sectionCode": {
|
||||||
"message": "Cod"
|
"message": "Cod"
|
||||||
},
|
},
|
||||||
|
"sectionHelp": {
|
||||||
|
"message": "Secțiunile permit definirea de bucăți de cod aplicabile la URL diferite, în cadrul aceleași teme. Spre exemplu, o singură temă poate modifica pagina de pornire a unui site într-un mod diferit de restul site-ului."
|
||||||
|
},
|
||||||
"sectionRemove": {
|
"sectionRemove": {
|
||||||
"message": "Ștergeți secțiunea"
|
"message": "Ștergeți secțiunea"
|
||||||
},
|
},
|
||||||
"sectionRestore": {
|
"sectionRestore": {
|
||||||
"message": "Restaurează o secțiune ștearsă"
|
"message": "Restaurează o secțiune ștearsă"
|
||||||
},
|
},
|
||||||
"sections": {
|
|
||||||
"message": "Secțiuni"
|
|
||||||
},
|
|
||||||
"shortcutsNote": {
|
"shortcutsNote": {
|
||||||
"message": "Creeați keyboard shortcuts"
|
"message": "Creeați keyboard shortcuts"
|
||||||
},
|
},
|
||||||
|
@ -706,6 +736,36 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"styleMetaErrorCheckbox": {
|
||||||
|
"message": "@var checkbox invalidă: valuarea trebuie să fie 0 sau 1"
|
||||||
|
},
|
||||||
|
"styleMetaErrorColor": {
|
||||||
|
"message": "$color$ nu este o culoare validă",
|
||||||
|
"placeholders": {
|
||||||
|
"color": {
|
||||||
|
"content": "$1"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"styleMetaErrorPreprocessor": {
|
||||||
|
"message": "@preprocessor nesuportat: $preprocessor$",
|
||||||
|
"placeholders": {
|
||||||
|
"preprocessor": {
|
||||||
|
"content": "$1"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"styleMetaErrorSelectValueMismatch": {
|
||||||
|
"message": "Valoare @select invalidă: valoarea nu există în listă"
|
||||||
|
},
|
||||||
|
"styleMissingMeta": {
|
||||||
|
"message": "Metadata nu a fost găsită @$key$",
|
||||||
|
"placeholders": {
|
||||||
|
"key": {
|
||||||
|
"content": "$1"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"styleMissingName": {
|
"styleMissingName": {
|
||||||
"message": "Introduceți un nume"
|
"message": "Introduceți un nume"
|
||||||
},
|
},
|
||||||
|
@ -745,6 +805,9 @@
|
||||||
"styleSaveLabel": {
|
"styleSaveLabel": {
|
||||||
"message": "Salvați"
|
"message": "Salvați"
|
||||||
},
|
},
|
||||||
|
"styleSectionsTitle": {
|
||||||
|
"message": "Secțiuni"
|
||||||
|
},
|
||||||
"styleToMozillaFormatHelp": {
|
"styleToMozillaFormatHelp": {
|
||||||
"message": "Formatul Mozilla al codului poate fi uploadat pe userstyles.org și folosit de clasicul Stylish pentru Firefox."
|
"message": "Formatul Mozilla al codului poate fi uploadat pe userstyles.org și folosit de clasicul Stylish pentru Firefox."
|
||||||
},
|
},
|
||||||
|
@ -783,15 +846,18 @@
|
||||||
"unreachableAMOHint": {
|
"unreachableAMOHint": {
|
||||||
"message": "Pentru a permite accesul deschideți <about:config>, right-click pe listă, click 'New', apoi 'Boolean', paste <privacy.resistFingerprinting.block_mozAddonManager> și click OK, <true>, OK, reâncărcați pagina <addons.mozilla.org>."
|
"message": "Pentru a permite accesul deschideți <about:config>, right-click pe listă, click 'New', apoi 'Boolean', paste <privacy.resistFingerprinting.block_mozAddonManager> și click OK, <true>, OK, reâncărcați pagina <addons.mozilla.org>."
|
||||||
},
|
},
|
||||||
|
"unreachableAMOHintNewFF": {
|
||||||
|
"message": "În Firefox 60+ va trebui sa fie șters domain-ul AMO din <extensions.webextensions.restrictedDomains> din <about:config>."
|
||||||
|
},
|
||||||
|
"unreachableAMOHintOldFF": {
|
||||||
|
"message": "Doar Firefox 59 sau mai nou poate fi configurat să permită WebExtension-urilor să adauge elemente la site-uri CSP-protected precum acesta."
|
||||||
|
},
|
||||||
"unreachableContentScript": {
|
"unreachableContentScript": {
|
||||||
"message": "Nu s-a putut comunica cu pagina. Reîncărcați tabul."
|
"message": "Nu s-a putut comunica cu pagina. Reîncărcați tabul."
|
||||||
},
|
},
|
||||||
"unreachableFileHint": {
|
"unreachableFileHint": {
|
||||||
"message": "Stylus poate accesa file:// URLs doar când este activată opțiunea respectivă din pagina cu setări chrome://extensions"
|
"message": "Stylus poate accesa file:// URLs doar când este activată opțiunea respectivă din pagina cu setări chrome://extensions"
|
||||||
},
|
},
|
||||||
"unreachableMozSiteHintOldFF": {
|
|
||||||
"message": "Doar Firefox 59 sau mai nou poate fi configurat să permită WebExtension-urilor să adauge elemente la site-uri CSP-protected precum acesta."
|
|
||||||
},
|
|
||||||
"updateAllCheckSucceededNoUpdate": {
|
"updateAllCheckSucceededNoUpdate": {
|
||||||
"message": "Niciun update găsit."
|
"message": "Niciun update găsit."
|
||||||
},
|
},
|
||||||
|
@ -845,6 +911,9 @@
|
||||||
"usercssReplaceTemplateConfirmation": {
|
"usercssReplaceTemplateConfirmation": {
|
||||||
"message": "Înlocuiți tema de bază a formatului Usercss cu acest cod?"
|
"message": "Înlocuiți tema de bază a formatului Usercss cu acest cod?"
|
||||||
},
|
},
|
||||||
|
"usercssReplaceTemplateName": {
|
||||||
|
"message": "@name este gol și înlocuiețte valoarea de bază"
|
||||||
|
},
|
||||||
"usercssReplaceTemplateSectionBody": {
|
"usercssReplaceTemplateSectionBody": {
|
||||||
"message": "Introduce cod aici..."
|
"message": "Introduce cod aici..."
|
||||||
},
|
},
|
||||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -76,15 +76,9 @@
|
||||||
"cm_theme": {
|
"cm_theme": {
|
||||||
"message": "Тема"
|
"message": "Тема"
|
||||||
},
|
},
|
||||||
"confirmDelete": {
|
|
||||||
"message": "Избриши"
|
|
||||||
},
|
|
||||||
"confirmNo": {
|
"confirmNo": {
|
||||||
"message": "Не"
|
"message": "Не"
|
||||||
},
|
},
|
||||||
"confirmSave": {
|
|
||||||
"message": "Сачувај"
|
|
||||||
},
|
|
||||||
"confirmStop": {
|
"confirmStop": {
|
||||||
"message": "Заустави"
|
"message": "Заустави"
|
||||||
},
|
},
|
||||||
|
@ -112,9 +106,6 @@
|
||||||
"disableStyleLabel": {
|
"disableStyleLabel": {
|
||||||
"message": "Онемогући"
|
"message": "Онемогући"
|
||||||
},
|
},
|
||||||
"editDeleteText": {
|
|
||||||
"message": "Избриши"
|
|
||||||
},
|
|
||||||
"editGotoLine": {
|
"editGotoLine": {
|
||||||
"message": "Иди на ред (или line:col)"
|
"message": "Иди на ред (или line:col)"
|
||||||
},
|
},
|
||||||
|
@ -138,11 +129,8 @@
|
||||||
"exportLabel": {
|
"exportLabel": {
|
||||||
"message": "Извези"
|
"message": "Извези"
|
||||||
},
|
},
|
||||||
"genericAdd": {
|
"findStylesForSite": {
|
||||||
"message": "Додај"
|
"message": "Пронађи још стилова за овај сајт"
|
||||||
},
|
|
||||||
"genericEnabledLabel": {
|
|
||||||
"message": "Омогућено"
|
|
||||||
},
|
},
|
||||||
"helpAlt": {
|
"helpAlt": {
|
||||||
"message": "Помоћ"
|
"message": "Помоћ"
|
||||||
|
@ -206,15 +194,9 @@
|
||||||
"openManage": {
|
"openManage": {
|
||||||
"message": "Управљај инсталираним стиловима"
|
"message": "Управљај инсталираним стиловима"
|
||||||
},
|
},
|
||||||
"openOptions": {
|
|
||||||
"message": "Опције"
|
|
||||||
},
|
|
||||||
"optionsHeading": {
|
"optionsHeading": {
|
||||||
"message": "Опције"
|
"message": "Опције"
|
||||||
},
|
},
|
||||||
"optionsSyncUrl": {
|
|
||||||
"message": "УРЛ"
|
|
||||||
},
|
|
||||||
"popupStylesFirst": {
|
"popupStylesFirst": {
|
||||||
"message": "Излистај стилове пре команди у менију дугмета на алатној траци"
|
"message": "Излистај стилове пре команди у менију дугмета на алатној траци"
|
||||||
},
|
},
|
||||||
|
@ -236,18 +218,21 @@
|
||||||
"searchRegexp": {
|
"searchRegexp": {
|
||||||
"message": "Користи /re/ синтаксу за претрагу регуларним изразом"
|
"message": "Користи /re/ синтаксу за претрагу регуларним изразом"
|
||||||
},
|
},
|
||||||
|
"searchStyles": {
|
||||||
|
"message": "Претражи садржај"
|
||||||
|
},
|
||||||
"sectionAdd": {
|
"sectionAdd": {
|
||||||
"message": "Додај нови одељак"
|
"message": "Додај нови одељак"
|
||||||
},
|
},
|
||||||
"sectionCode": {
|
"sectionCode": {
|
||||||
"message": "Код"
|
"message": "Код"
|
||||||
},
|
},
|
||||||
|
"sectionHelp": {
|
||||||
|
"message": "Одељци вам омогућавају да дефинишете различите делове кода који се примењују на раличите скупове УРЛ-ова у истом стилу. На пример, један исти стил може променити почетну страницу једног сајта на један начин а остатак сајта на други начин."
|
||||||
|
},
|
||||||
"sectionRemove": {
|
"sectionRemove": {
|
||||||
"message": "Уклони одељак"
|
"message": "Уклони одељак"
|
||||||
},
|
},
|
||||||
"sections": {
|
|
||||||
"message": "Одељци"
|
|
||||||
},
|
|
||||||
"styleBadRegexp": {
|
"styleBadRegexp": {
|
||||||
"message": "Регуларни израз је неисправан."
|
"message": "Регуларни израз је неисправан."
|
||||||
},
|
},
|
||||||
|
@ -283,6 +268,9 @@
|
||||||
"styleSaveLabel": {
|
"styleSaveLabel": {
|
||||||
"message": "Сачувај"
|
"message": "Сачувај"
|
||||||
},
|
},
|
||||||
|
"styleSectionsTitle": {
|
||||||
|
"message": "Одељци"
|
||||||
|
},
|
||||||
"styleToMozillaFormatHelp": {
|
"styleToMozillaFormatHelp": {
|
||||||
"message": "Mozilla формат кода се може користити у Stylish за Firefox и може се послати на userstyles.org."
|
"message": "Mozilla формат кода се може користити у Stylish за Firefox и може се послати на userstyles.org."
|
||||||
},
|
},
|
||||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -22,12 +22,6 @@
|
||||||
"appliesToEverything": {
|
"appliesToEverything": {
|
||||||
"message": "అన్నిటికీ"
|
"message": "అన్నిటికీ"
|
||||||
},
|
},
|
||||||
"confirmDelete": {
|
|
||||||
"message": "తొలగించు"
|
|
||||||
},
|
|
||||||
"confirmSave": {
|
|
||||||
"message": "భద్రపరచు"
|
|
||||||
},
|
|
||||||
"deleteStyleConfirm": {
|
"deleteStyleConfirm": {
|
||||||
"message": "మీరు నజంగానే ఈ శైలిని తొలగించాలనుకుంటున్నారా?"
|
"message": "మీరు నజంగానే ఈ శైలిని తొలగించాలనుకుంటున్నారా?"
|
||||||
},
|
},
|
||||||
|
@ -37,18 +31,12 @@
|
||||||
"disableStyleLabel": {
|
"disableStyleLabel": {
|
||||||
"message": "అచేతనించు"
|
"message": "అచేతనించు"
|
||||||
},
|
},
|
||||||
"editDeleteText": {
|
|
||||||
"message": "తొలగించు"
|
|
||||||
},
|
|
||||||
"editStyleLabel": {
|
"editStyleLabel": {
|
||||||
"message": "మార్చు"
|
"message": "మార్చు"
|
||||||
},
|
},
|
||||||
"enableStyleLabel": {
|
"enableStyleLabel": {
|
||||||
"message": "చేతనించు"
|
"message": "చేతనించు"
|
||||||
},
|
},
|
||||||
"genericAdd": {
|
|
||||||
"message": "చేర్చు"
|
|
||||||
},
|
|
||||||
"helpAlt": {
|
"helpAlt": {
|
||||||
"message": "సహాయం"
|
"message": "సహాయం"
|
||||||
},
|
},
|
||||||
|
@ -58,10 +46,10 @@
|
||||||
"manageTitle": {
|
"manageTitle": {
|
||||||
"message": "స్టైలిష్"
|
"message": "స్టైలిష్"
|
||||||
},
|
},
|
||||||
"sections": {
|
|
||||||
"message": "విభాగాలు"
|
|
||||||
},
|
|
||||||
"styleSaveLabel": {
|
"styleSaveLabel": {
|
||||||
"message": "భద్రపరచు"
|
"message": "భద్రపరచు"
|
||||||
|
},
|
||||||
|
"styleSectionsTitle": {
|
||||||
|
"message": "విభాగాలు"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,9 +5,6 @@
|
||||||
"addStyleTitle": {
|
"addStyleTitle": {
|
||||||
"message": "Stil Ekleyin"
|
"message": "Stil Ekleyin"
|
||||||
},
|
},
|
||||||
"alphaChannel": {
|
|
||||||
"message": "Opaklık"
|
|
||||||
},
|
|
||||||
"appliesAdd": {
|
"appliesAdd": {
|
||||||
"message": "Ekleyin"
|
"message": "Ekleyin"
|
||||||
},
|
},
|
||||||
|
@ -31,9 +28,6 @@
|
||||||
"appliesLabel": {
|
"appliesLabel": {
|
||||||
"message": "Şuraya uygulanır"
|
"message": "Şuraya uygulanır"
|
||||||
},
|
},
|
||||||
"appliesLineWidgetWarning": {
|
|
||||||
"message": "Küçültülmüş CSS ile çalışmaz"
|
|
||||||
},
|
|
||||||
"appliesRegexpOption": {
|
"appliesRegexpOption": {
|
||||||
"message": "regexp ile eşleşen URL'ler"
|
"message": "regexp ile eşleşen URL'ler"
|
||||||
},
|
},
|
||||||
|
@ -49,153 +43,15 @@
|
||||||
"appliesUrlPrefixOption": {
|
"appliesUrlPrefixOption": {
|
||||||
"message": "Şununla başlayan URL'ler:"
|
"message": "Şununla başlayan URL'ler:"
|
||||||
},
|
},
|
||||||
"applyAllUpdates": {
|
|
||||||
"message": "Tüm güncellemeleri uygula"
|
|
||||||
},
|
|
||||||
"author": {
|
|
||||||
"message": "Yazar"
|
|
||||||
},
|
|
||||||
"backupButtons": {
|
|
||||||
"message": "Yedek"
|
|
||||||
},
|
|
||||||
"bckpInstStyles": {
|
|
||||||
"message": "Dışa aktar"
|
|
||||||
},
|
|
||||||
"checkAllUpdates": {
|
"checkAllUpdates": {
|
||||||
"message": "Tüm stiller için güncellemeleri denetle"
|
"message": "Tüm stiller için güncellemeleri denetle"
|
||||||
},
|
},
|
||||||
"checkAllUpdatesForce": {
|
|
||||||
"message": "Tekrar kontrol et, herhangi bir stil düzenlemedim!"
|
|
||||||
},
|
|
||||||
"checkForUpdate": {
|
"checkForUpdate": {
|
||||||
"message": "Güncellemeleri denetle"
|
"message": "Güncellemeleri denetle"
|
||||||
},
|
},
|
||||||
"checkingForUpdate": {
|
"checkingForUpdate": {
|
||||||
"message": "Kontrol ediliyor..."
|
"message": "Kontrol ediliyor..."
|
||||||
},
|
},
|
||||||
"clickToUninstall": {
|
|
||||||
"message": "Kaldırmak için tıklayın"
|
|
||||||
},
|
|
||||||
"cm_autoCloseBrackets": {
|
|
||||||
"message": "Parantezleri ve tırnak işaretlerini otomatik olarak kapat"
|
|
||||||
},
|
|
||||||
"cm_autoCloseBracketsTooltip": {
|
|
||||||
"message": "()[]{}''\"\" Açılışlarından birini yazarken otomatik olarak bir kapanış çifti ekleyin"
|
|
||||||
},
|
|
||||||
"cm_autocompleteOnTyping": {
|
|
||||||
"message": "Yazarken tamamla"
|
|
||||||
},
|
|
||||||
"cm_colorpicker": {
|
|
||||||
"message": "CSS renk seçicileri"
|
|
||||||
},
|
|
||||||
"cm_indentWithTabs": {
|
|
||||||
"message": "Akıllı girintili tab kullan"
|
|
||||||
},
|
|
||||||
"cm_keyMap": {
|
|
||||||
"message": "Tuşeşlem"
|
|
||||||
},
|
|
||||||
"cm_lineWrapping": {
|
|
||||||
"message": "Kelime kaydırma"
|
|
||||||
},
|
|
||||||
"cm_matchHighlight": {
|
|
||||||
"message": "Vurgulama"
|
|
||||||
},
|
|
||||||
"cm_matchHighlightSelection": {
|
|
||||||
"message": "Yalnızca seçim"
|
|
||||||
},
|
|
||||||
"cm_matchHighlightToken": {
|
|
||||||
"message": "İmleç altındaki token"
|
|
||||||
},
|
|
||||||
"cm_resizeGripHint": {
|
|
||||||
"message": "Yüksekliği en üst düzeye çıkarmak/geri yüklemek için çift tıklayın"
|
|
||||||
},
|
|
||||||
"cm_selectByTokens": {
|
|
||||||
"message": "Çift tıklamak tokenleri seçer"
|
|
||||||
},
|
|
||||||
"cm_selectByTokensTooltip": {
|
|
||||||
"message": "Token örnekleri: .foo-bar-2 #aabbcc 0.32 !important\nDevre dışı bırakıldığında: noktalama işareti ile ayrılmış kelimeler seçilir."
|
|
||||||
},
|
|
||||||
"cm_smartIndent": {
|
|
||||||
"message": "Akıllı girinti kullanma"
|
|
||||||
},
|
|
||||||
"cm_tabSize": {
|
|
||||||
"message": "Tab büyüklüğü"
|
|
||||||
},
|
|
||||||
"cm_theme": {
|
|
||||||
"message": "Tema"
|
|
||||||
},
|
|
||||||
"colorpickerSwitchFormatTooltip": {
|
|
||||||
"message": "Format değişimleri: HEX -> RGB -> HSL.\nYönü ters çevirmek için Shift tuşunu basılı tutup tıklatın.\nAyrıca PgUp (PageUp), PgDn (PageDown) tuşları ile."
|
|
||||||
},
|
|
||||||
"colorpickerTooltip": {
|
|
||||||
"message": "Renk seçiciyi aç"
|
|
||||||
},
|
|
||||||
"configOnChange": {
|
|
||||||
"message": "değişiklikte"
|
|
||||||
},
|
|
||||||
"configOnChangeTooltip": {
|
|
||||||
"message": "Otomatik kaydetme ve değişiklikleri otomatik olarak uygulama"
|
|
||||||
},
|
|
||||||
"configureStyle": {
|
|
||||||
"message": "Yapılandır"
|
|
||||||
},
|
|
||||||
"configureStyleOnHomepage": {
|
|
||||||
"message": "Ana sayfada yapılandır"
|
|
||||||
},
|
|
||||||
"confirmCancel": {
|
|
||||||
"message": "İptal"
|
|
||||||
},
|
|
||||||
"confirmClose": {
|
|
||||||
"message": "Kapat"
|
|
||||||
},
|
|
||||||
"confirmDefault": {
|
|
||||||
"message": "Öntanımlıyı kullan"
|
|
||||||
},
|
|
||||||
"confirmDelete": {
|
|
||||||
"message": "Sil"
|
|
||||||
},
|
|
||||||
"confirmDiscardChanges": {
|
|
||||||
"message": "Değişiklikleri iptal et?"
|
|
||||||
},
|
|
||||||
"confirmNo": {
|
|
||||||
"message": "Hayır"
|
|
||||||
},
|
|
||||||
"confirmOK": {
|
|
||||||
"message": "Tamam"
|
|
||||||
},
|
|
||||||
"confirmSave": {
|
|
||||||
"message": "Kaydet"
|
|
||||||
},
|
|
||||||
"confirmStop": {
|
|
||||||
"message": "Dur"
|
|
||||||
},
|
|
||||||
"confirmYes": {
|
|
||||||
"message": "Evet"
|
|
||||||
},
|
|
||||||
"connectingDropbox": {
|
|
||||||
"message": "Dropbox'a bağlanıyor..."
|
|
||||||
},
|
|
||||||
"connectingDropboxNotAllowed": {
|
|
||||||
"message": "Dropbox'a bağlanmak yalnızca doğrudan web mağazasından yüklenen uygulamalarda kullanılabilir"
|
|
||||||
},
|
|
||||||
"copied": {
|
|
||||||
"message": "Kopyalandı"
|
|
||||||
},
|
|
||||||
"copy": {
|
|
||||||
"message": "Kopyala"
|
|
||||||
},
|
|
||||||
"dateInstalled": {
|
|
||||||
"message": "Kurulum tarihi"
|
|
||||||
},
|
|
||||||
"dateUpdated": {
|
|
||||||
"message": "Güncelleme tarihi"
|
|
||||||
},
|
|
||||||
"dbError": {
|
|
||||||
"message": "Stylus veritabanı kullanılırken bir hata oluştu. Olası çözümler içeren bir web sayfasını ziyaret etmek ister misiniz?"
|
|
||||||
},
|
|
||||||
"defaultTheme": {
|
|
||||||
"message": "öntanımlı"
|
|
||||||
},
|
|
||||||
"deleteStyleConfirm": {
|
"deleteStyleConfirm": {
|
||||||
"message": "Bu stili silmek istediğinizden emin misiniz?"
|
"message": "Bu stili silmek istediğinizden emin misiniz?"
|
||||||
},
|
},
|
||||||
|
@ -205,24 +61,9 @@
|
||||||
"description": {
|
"description": {
|
||||||
"message": "Kullanıcı stil yöneticisi Stylus ile Web'in stilini yenileyin. Stylus Google, Facebook, YouTube, Orkut ve diğer pek çok site için temalar ve görünüm yüklemenize imkân tanır."
|
"message": "Kullanıcı stil yöneticisi Stylus ile Web'in stilini yenileyin. Stylus Google, Facebook, YouTube, Orkut ve diğer pek çok site için temalar ve görünüm yüklemenize imkân tanır."
|
||||||
},
|
},
|
||||||
"disableAllStyles": {
|
|
||||||
"message": "Tüm stilleri kapat"
|
|
||||||
},
|
|
||||||
"disableStyleLabel": {
|
"disableStyleLabel": {
|
||||||
"message": "Devre dışı bırak"
|
"message": "Devre dışı bırak"
|
||||||
},
|
},
|
||||||
"dragDropMessage": {
|
|
||||||
"message": "İçe aktarmak için yedek dosyanızı bu sayfada herhangi bir yere bırakın."
|
|
||||||
},
|
|
||||||
"dragDropUsercssTabstrip": {
|
|
||||||
"message": "Dosyayı yüklemek için sekme şeridine (sekme başlıklarının gösterildiği alan) bırakın."
|
|
||||||
},
|
|
||||||
"editDeleteText": {
|
|
||||||
"message": "Sil"
|
|
||||||
},
|
|
||||||
"editGotoLine": {
|
|
||||||
"message": "Satıra git (veya satır:sütun)"
|
|
||||||
},
|
|
||||||
"editStyleHeading": {
|
"editStyleHeading": {
|
||||||
"message": "Stili Düzenle"
|
"message": "Stili Düzenle"
|
||||||
},
|
},
|
||||||
|
@ -240,392 +81,36 @@
|
||||||
"enableStyleLabel": {
|
"enableStyleLabel": {
|
||||||
"message": "Etkinleştir"
|
"message": "Etkinleştir"
|
||||||
},
|
},
|
||||||
"excludeStyleByDomainLabel": {
|
"findStylesForSite": {
|
||||||
"message": "Mevcut alan adını hariç tut"
|
"message": "Bu site için başka stiller bul"
|
||||||
},
|
|
||||||
"excludeStyleByUrlLabel": {
|
|
||||||
"message": "Mevcut URL'yi hariç tut"
|
|
||||||
},
|
|
||||||
"exportLabel": {
|
|
||||||
"message": "Dışa aktar"
|
|
||||||
},
|
|
||||||
"exportSavedSuccess": {
|
|
||||||
"message": "Dosya başarıyla kaydedildi"
|
|
||||||
},
|
|
||||||
"externalFeedback": {
|
|
||||||
"message": "Geribildirim"
|
|
||||||
},
|
|
||||||
"externalHomepage": {
|
|
||||||
"message": "Anasayfa"
|
|
||||||
},
|
|
||||||
"externalLink": {
|
|
||||||
"message": "Harici bağlantı"
|
|
||||||
},
|
|
||||||
"externalSupport": {
|
|
||||||
"message": "Destek"
|
|
||||||
},
|
|
||||||
"externalUsercssDocument": {
|
|
||||||
"message": "Usercss belgeleri"
|
|
||||||
},
|
|
||||||
"filteredStyles": {
|
|
||||||
"message": "Toplam $numTotal$ gösterilen $numShown$",
|
|
||||||
"placeholders": {
|
|
||||||
"numShown": {
|
|
||||||
"content": "$1"
|
|
||||||
},
|
|
||||||
"numTotal": {
|
|
||||||
"content": "$2"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"filteredStylesAllHidden": {
|
|
||||||
"message": "Şu anda uygulanan filtreler hiçbir stille eşleşmiyor"
|
|
||||||
},
|
|
||||||
"findStyles": {
|
|
||||||
"message": "Stil bul"
|
|
||||||
},
|
|
||||||
"genericAdd": {
|
|
||||||
"message": "Ekle"
|
|
||||||
},
|
|
||||||
"genericClone": {
|
|
||||||
"message": "Klon"
|
|
||||||
},
|
|
||||||
"genericDisabledLabel": {
|
|
||||||
"message": "Devre dışı"
|
|
||||||
},
|
|
||||||
"genericEnabledLabel": {
|
|
||||||
"message": "Etkin"
|
|
||||||
},
|
|
||||||
"genericError": {
|
|
||||||
"message": "Hata"
|
|
||||||
},
|
|
||||||
"genericHistoryLabel": {
|
|
||||||
"message": "Geçmiş"
|
|
||||||
},
|
|
||||||
"genericNext": {
|
|
||||||
"message": "Sonraki"
|
|
||||||
},
|
|
||||||
"genericPrevious": {
|
|
||||||
"message": "Önceki"
|
|
||||||
},
|
|
||||||
"genericResetLabel": {
|
|
||||||
"message": "Yenile"
|
|
||||||
},
|
|
||||||
"genericSavedMessage": {
|
|
||||||
"message": "Kaydedildi"
|
|
||||||
},
|
|
||||||
"genericTitle": {
|
|
||||||
"message": "Başlık"
|
|
||||||
},
|
|
||||||
"genericUnknown": {
|
|
||||||
"message": "Bilinmiyor"
|
|
||||||
},
|
|
||||||
"gettingStyles": {
|
|
||||||
"message": "Tüm stiller alınıyor..."
|
|
||||||
},
|
},
|
||||||
"helpAlt": {
|
"helpAlt": {
|
||||||
"message": "Yardım"
|
"message": "Yardım"
|
||||||
},
|
},
|
||||||
"helpKeyMapCommand": {
|
|
||||||
"message": "Bir komut adı yazın"
|
|
||||||
},
|
|
||||||
"helpKeyMapHotkey": {
|
|
||||||
"message": "Bir kısayol tuşuna basın"
|
|
||||||
},
|
|
||||||
"hostDisabled": {
|
|
||||||
"message": "Bu ana bilgisayar, kullanılan tarayıcının mevcut sürümündeki bir hata nedeniyle devre dışı bırakıldı"
|
|
||||||
},
|
|
||||||
"importAppendLabel": {
|
|
||||||
"message": "Stile ekle"
|
|
||||||
},
|
|
||||||
"importAppendTooltip": {
|
|
||||||
"message": "İçe aktarılan stili geçerli stile ekleme"
|
|
||||||
},
|
|
||||||
"importLabel": {
|
|
||||||
"message": "İçe aktar"
|
|
||||||
},
|
|
||||||
"importReplaceLabel": {
|
|
||||||
"message": "Stilin üzerine yaz"
|
|
||||||
},
|
|
||||||
"importReplaceTooltip": {
|
|
||||||
"message": "Geçerli stilin içeriğini atın ve içe aktarılan stil ile üzerine yazın"
|
|
||||||
},
|
|
||||||
"importReportLegendAdded": {
|
|
||||||
"message": "eklendi"
|
|
||||||
},
|
|
||||||
"importReportLegendIdentical": {
|
|
||||||
"message": "Özdeş olan atlandı"
|
|
||||||
},
|
|
||||||
"importReportLegendInvalid": {
|
|
||||||
"message": "geçersiz olan atlandı"
|
|
||||||
},
|
|
||||||
"importReportLegendUpdatedBoth": {
|
|
||||||
"message": "hem meta bilgi hem de kod güncellendi"
|
|
||||||
},
|
|
||||||
"importReportLegendUpdatedCode": {
|
|
||||||
"message": "güncellenmiş kod"
|
|
||||||
},
|
|
||||||
"importReportTitle": {
|
|
||||||
"message": "Stillerin içeri aktarılması tamamlandı."
|
|
||||||
},
|
|
||||||
"importReportUnchanged": {
|
|
||||||
"message": "Hiçbir şey değiştirilmedi."
|
|
||||||
},
|
|
||||||
"importReportUndone": {
|
|
||||||
"message": "stiller geri çevrildi"
|
|
||||||
},
|
|
||||||
"importReportUndoneTitle": {
|
|
||||||
"message": "İçe aktarma geri alındı"
|
|
||||||
},
|
|
||||||
"installButton": {
|
|
||||||
"message": "Stil kur"
|
|
||||||
},
|
|
||||||
"installButtonInstalled": {
|
|
||||||
"message": "Stil kuruldu"
|
|
||||||
},
|
|
||||||
"installButtonReinstall": {
|
|
||||||
"message": "Stili yeniden kur"
|
|
||||||
},
|
|
||||||
"installButtonUpdate": {
|
|
||||||
"message": "Stili güncelle"
|
|
||||||
},
|
|
||||||
"installUpdate": {
|
"installUpdate": {
|
||||||
"message": "Güncellemeyi yükle"
|
"message": "Güncellemeyi yükle"
|
||||||
},
|
},
|
||||||
"installUpdateFromLabel": {
|
|
||||||
"message": "Güncellemeleri denetle"
|
|
||||||
},
|
|
||||||
"license": {
|
|
||||||
"message": "Lisans"
|
|
||||||
},
|
|
||||||
"linkGetHelp": {
|
|
||||||
"message": "Destek al"
|
|
||||||
},
|
|
||||||
"linkStylusWiki": {
|
|
||||||
"message": "Viki"
|
|
||||||
},
|
|
||||||
"linkTranslate": {
|
|
||||||
"message": "Çevir"
|
|
||||||
},
|
|
||||||
"linterIssues": {
|
|
||||||
"message": "Sorunlar"
|
|
||||||
},
|
|
||||||
"manageFaviconsGray": {
|
|
||||||
"message": "Gri renkte"
|
|
||||||
},
|
|
||||||
"manageFaviconsHelp": {
|
|
||||||
"message": "Stylus harici bir servis kullanır https://icons.duckduckgo.com"
|
|
||||||
},
|
|
||||||
"manageFilters": {
|
|
||||||
"message": "Filtreler"
|
|
||||||
},
|
|
||||||
"manageHeading": {
|
"manageHeading": {
|
||||||
"message": "Yüklü Stiller"
|
"message": "Yüklü Stiller"
|
||||||
},
|
},
|
||||||
"manageMaxTargets": {
|
|
||||||
"message": "Uygulanan ögelerin sayısı"
|
|
||||||
},
|
|
||||||
"manageNewUI": {
|
|
||||||
"message": "Yeni yönetim ara yüzü düzeni"
|
|
||||||
},
|
|
||||||
"manageOnlyEnabled": {
|
|
||||||
"message": "Yalnızca etkin stiller"
|
|
||||||
},
|
|
||||||
"manageOnlyLocal": {
|
|
||||||
"message": "Yalnızca yerelde oluşturulmuş stiller"
|
|
||||||
},
|
|
||||||
"manageOnlyLocalTooltip": {
|
|
||||||
"message": "Stiller, bir userstyles.org sayfası üzerinden yülenmemiş."
|
|
||||||
},
|
|
||||||
"manageOnlyUpdates": {
|
|
||||||
"message": "Sadece güncellemeler ya da sorunlar ile"
|
|
||||||
},
|
|
||||||
"menuShowBadge": {
|
|
||||||
"message": "Etkin stil sayısını göster"
|
|
||||||
},
|
|
||||||
"noFileToImport": {
|
|
||||||
"message": "Stillerinizi içe aktarmak için önce dışa aktarmanız gerekir."
|
|
||||||
},
|
|
||||||
"noStylesForSite": {
|
"noStylesForSite": {
|
||||||
"message": "Bu site için hiçbir stil yüklenmedi."
|
"message": "Bu site için hiçbir stil yüklenmedi."
|
||||||
},
|
},
|
||||||
"openManage": {
|
"openManage": {
|
||||||
"message": "Yüklü stilleri yönet"
|
"message": "Yüklü stilleri yönet"
|
||||||
},
|
},
|
||||||
"openOptions": {
|
|
||||||
"message": "Seçenekler"
|
|
||||||
},
|
|
||||||
"openStylesManager": {
|
|
||||||
"message": "Stil yöneticisini aç"
|
|
||||||
},
|
|
||||||
"optionsActions": {
|
|
||||||
"message": "İşlemler"
|
|
||||||
},
|
|
||||||
"optionsAdvanced": {
|
|
||||||
"message": "Gelişmiş"
|
|
||||||
},
|
|
||||||
"optionsAdvancedContextDelete": {
|
|
||||||
"message": "Editör bağlam menüsüne 'Sil' seçeneği ekle"
|
|
||||||
},
|
|
||||||
"optionsAdvancedExposeIframes": {
|
|
||||||
"message": "HTML[stylus-iframe] vasıtasıyla iframe'leri gösterin"
|
|
||||||
},
|
|
||||||
"optionsBadgeDisabled": {
|
|
||||||
"message": "Etkin değilken arkaplan rengi"
|
|
||||||
},
|
|
||||||
"optionsBadgeNormal": {
|
|
||||||
"message": "Arkaplan rengi"
|
|
||||||
},
|
|
||||||
"optionsCheck": {
|
|
||||||
"message": "Stilleri güncelle"
|
|
||||||
},
|
|
||||||
"optionsCheckUpdate": {
|
|
||||||
"message": "Uygun tüm güncellemeleri kontrol et ve yükle."
|
|
||||||
},
|
|
||||||
"optionsCustomizePopup": {
|
|
||||||
"message": "Açılır pencere"
|
|
||||||
},
|
|
||||||
"optionsCustomizeUpdate": {
|
|
||||||
"message": "Güncellemeler"
|
|
||||||
},
|
|
||||||
"optionsHeading": {
|
|
||||||
"message": "Seçenekler"
|
|
||||||
},
|
|
||||||
"optionsOpen": {
|
|
||||||
"message": "Aç"
|
|
||||||
},
|
|
||||||
"optionsOpenManager": {
|
|
||||||
"message": "Stilleri yönet"
|
|
||||||
},
|
|
||||||
"optionsPopupWidth": {
|
|
||||||
"message": "Popup genişliği (piksel cinsinden)"
|
|
||||||
},
|
|
||||||
"optionsReset": {
|
|
||||||
"message": "Ayarları varsayılanlara sıfırla."
|
|
||||||
},
|
|
||||||
"optionsResetButton": {
|
|
||||||
"message": "Sıfırlama seçenekleri"
|
|
||||||
},
|
|
||||||
"optionsSubheading": {
|
|
||||||
"message": "Daha Fazla Seçenek"
|
|
||||||
},
|
|
||||||
"optionsSyncConnect": {
|
|
||||||
"message": "Bağlan"
|
|
||||||
},
|
|
||||||
"optionsSyncDisconnect": {
|
|
||||||
"message": "Bağlantıyı kes"
|
|
||||||
},
|
|
||||||
"optionsSyncLogin": {
|
|
||||||
"message": "Giriş yap"
|
|
||||||
},
|
|
||||||
"optionsSyncStatusConnected": {
|
|
||||||
"message": "Bağlandı"
|
|
||||||
},
|
|
||||||
"optionsSyncStatusConnecting": {
|
|
||||||
"message": "Bağlanılıyor..."
|
|
||||||
},
|
|
||||||
"optionsSyncStatusDisconnected": {
|
|
||||||
"message": "Bağlantı kesildi"
|
|
||||||
},
|
|
||||||
"optionsSyncStatusDisconnecting": {
|
|
||||||
"message": "Bağlantı kesiliyor..."
|
|
||||||
},
|
|
||||||
"optionsUpdateImportNote": {
|
|
||||||
"message": "Eski versiyonlardan ya da Stylish'ten stil yedeklerini içeri aktarırken, hepsinin güncel olduğundan emin olmak için stil yöneticisinden bir defa elle güncelleme kontrolü yapın"
|
|
||||||
},
|
|
||||||
"optionsUpdateInterval": {
|
|
||||||
"message": "Saat cinsinden kullanıcı stili otomatik güncelleme aralığı (devre dışı bırakmak için 0 yazın) "
|
|
||||||
},
|
|
||||||
"overwriteFileExport": {
|
|
||||||
"message": "Mevcut bir dosyanın üzerine yazmak istiyor musunuz?"
|
|
||||||
},
|
|
||||||
"paginationCurrent": {
|
|
||||||
"message": "Şimdiki sayfa"
|
|
||||||
},
|
|
||||||
"paginationEstimated": {
|
|
||||||
"message": "Tahmini sayfa sayısı"
|
|
||||||
},
|
|
||||||
"paginationNext": {
|
|
||||||
"message": "Sonraki sayfa"
|
|
||||||
},
|
|
||||||
"paginationPrevious": {
|
|
||||||
"message": "Önceki sayfa"
|
|
||||||
},
|
|
||||||
"paginationTotal": {
|
|
||||||
"message": "Toplam sayfa"
|
|
||||||
},
|
|
||||||
"prefShowBadge": {
|
|
||||||
"message": "Şu an açık olan site için aktif olan stil sayısı"
|
|
||||||
},
|
|
||||||
"previewLabel": {
|
|
||||||
"message": "Canlı önizleme"
|
|
||||||
},
|
|
||||||
"readingStyles": {
|
|
||||||
"message": "Stiller okunuyor..."
|
|
||||||
},
|
|
||||||
"replace": {
|
|
||||||
"message": "Değiştir"
|
|
||||||
},
|
|
||||||
"replaceAll": {
|
|
||||||
"message": "Hepsini değiştir"
|
|
||||||
},
|
|
||||||
"replaceWith": {
|
|
||||||
"message": "Şununla değiştir"
|
|
||||||
},
|
|
||||||
"retrieveBckp": {
|
|
||||||
"message": "Stilleri içe aktar"
|
|
||||||
},
|
|
||||||
"retrieveDropboxSync": {
|
|
||||||
"message": "Dropbox'a aktar"
|
|
||||||
},
|
|
||||||
"search": {
|
|
||||||
"message": "Ara"
|
|
||||||
},
|
|
||||||
"searchRegexp": {
|
|
||||||
"message": "Regexp araması için /re/ sözdizimini (syntax) kullanın."
|
|
||||||
},
|
|
||||||
"searchResultInstallCount": {
|
|
||||||
"message": "Toplam kurulum sayısı"
|
|
||||||
},
|
|
||||||
"searchResultNoneFound": {
|
|
||||||
"message": "Bu site için stil bulunamadı."
|
|
||||||
},
|
|
||||||
"searchResultWeeklyCount": {
|
|
||||||
"message": "Haftalık kurulum sayısı"
|
|
||||||
},
|
|
||||||
"sectionAdd": {
|
"sectionAdd": {
|
||||||
"message": "Başka bölüm ekle"
|
"message": "Başka bölüm ekle"
|
||||||
},
|
},
|
||||||
"sectionCode": {
|
"sectionCode": {
|
||||||
"message": "Kod"
|
"message": "Kod"
|
||||||
},
|
},
|
||||||
|
"sectionHelp": {
|
||||||
|
"message": "Bölümler, aynı stil içindeki farklı URL kümelerine uygulanacak farklı kod parçaları tanımlamanıza olanak sağlar. Örneğin, tek bir stil bir sitenin ana sayfasını belirli bir şekilde değiştirirken, sayfanın kalan kısmını farklı bir şekilde değiştirebilir."
|
||||||
|
},
|
||||||
"sectionRemove": {
|
"sectionRemove": {
|
||||||
"message": "Bölümü kaldır"
|
"message": "Bölümü kaldır"
|
||||||
},
|
},
|
||||||
"sections": {
|
|
||||||
"message": "Bölümler"
|
|
||||||
},
|
|
||||||
"shortcuts": {
|
|
||||||
"message": "Kısayollar"
|
|
||||||
},
|
|
||||||
"shortcutsNote": {
|
|
||||||
"message": "Klavye kısayolları tanımla"
|
|
||||||
},
|
|
||||||
"sortDateNewestFirst": {
|
|
||||||
"message": "önce en yeniler"
|
|
||||||
},
|
|
||||||
"sortDateOldestFirst": {
|
|
||||||
"message": "önce en eskiler"
|
|
||||||
},
|
|
||||||
"sortStylesHelpTitle": {
|
|
||||||
"message": "İçeriği sırala"
|
|
||||||
},
|
|
||||||
"styleBadRegexp": {
|
|
||||||
"message": "Regexp geçerli değil."
|
|
||||||
},
|
|
||||||
"styleBeautify": {
|
|
||||||
"message": "Güzelleştir"
|
|
||||||
},
|
|
||||||
"styleCancelEditLabel": {
|
"styleCancelEditLabel": {
|
||||||
"message": "Yönetim sayfasına dön"
|
"message": "Yönetim sayfasına dön"
|
||||||
},
|
},
|
||||||
|
@ -635,9 +120,6 @@
|
||||||
"styleEnabledLabel": {
|
"styleEnabledLabel": {
|
||||||
"message": "Etkin"
|
"message": "Etkin"
|
||||||
},
|
},
|
||||||
"styleFromMozillaFormatPrompt": {
|
|
||||||
"message": "Mozilla biçemiyle yazılmış kodu yapıştır"
|
|
||||||
},
|
|
||||||
"styleInstall": {
|
"styleInstall": {
|
||||||
"message": "'$stylename$' Stylus'e yüklensin mi?",
|
"message": "'$stylename$' Stylus'e yüklensin mi?",
|
||||||
"placeholders": {
|
"placeholders": {
|
||||||
|
@ -649,80 +131,15 @@
|
||||||
"styleMissingName": {
|
"styleMissingName": {
|
||||||
"message": "Bir ad girin"
|
"message": "Bir ad girin"
|
||||||
},
|
},
|
||||||
"styleMozillaFormatHeading": {
|
|
||||||
"message": "Mozilla Biçemi"
|
|
||||||
},
|
|
||||||
"styleRegexpInvalidExplanation": {
|
|
||||||
"message": "Hiç derlenemeyen bazı 'regexp ()' kuralları."
|
|
||||||
},
|
|
||||||
"styleRegexpProblemTooltip": {
|
|
||||||
"message": "'Regexp ()' yanlış kullanımı nedeniyle uygulanmayan bölüm sayısı"
|
|
||||||
},
|
|
||||||
"styleRegexpTestFull": {
|
|
||||||
"message": "Eşleşen sekmeler"
|
|
||||||
},
|
|
||||||
"styleRegexpTestInvalid": {
|
|
||||||
"message": "Geçersiz regexp'ler atlandı"
|
|
||||||
},
|
|
||||||
"styleRegexpTestNone": {
|
|
||||||
"message": "Eşleşen sekme yok"
|
|
||||||
},
|
|
||||||
"styleRegexpTestPartial": {
|
|
||||||
"message": "Tam olarak eşleşmediğinden atlandı"
|
|
||||||
},
|
|
||||||
"styleRegexpTestTitle": {
|
|
||||||
"message": "Eşleşen açık sekmelerin listesi (sekmesine gitmek için URL'ye tıklayın)"
|
|
||||||
},
|
|
||||||
"styleSaveLabel": {
|
"styleSaveLabel": {
|
||||||
"message": "Kaydet"
|
"message": "Kaydet"
|
||||||
},
|
},
|
||||||
|
"styleSectionsTitle": {
|
||||||
|
"message": "Bölümler"
|
||||||
|
},
|
||||||
"styleToMozillaFormatHelp": {
|
"styleToMozillaFormatHelp": {
|
||||||
"message": "Kodun Mozilla biçimi, Firefox için Stylish ile kullanılabilir ve userstyles.org sitesine gönderilebilir."
|
"message": "Kodun Mozilla biçimi, Firefox için Stylish ile kullanılabilir ve userstyles.org sitesine gönderilebilir."
|
||||||
},
|
},
|
||||||
"styleToMozillaFormatTitle": {
|
|
||||||
"message": "Mozilla biçeminde bir stil"
|
|
||||||
},
|
|
||||||
"styleUpdate": {
|
|
||||||
"message": "\";$stylename$\" stilini güncelleştirmek istiyor musunuz?",
|
|
||||||
"placeholders": {
|
|
||||||
"stylename": {
|
|
||||||
"content": "$1"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"stylusUnavailableForURL": {
|
|
||||||
"message": "Stylus bunun gibi sayfalarda çalışmaz."
|
|
||||||
},
|
|
||||||
"stylusUnavailableForURLdetails": {
|
|
||||||
"message": "Bir güvenlik önlemi olarak tarayıcı eklentilerin, kendinin yerleşik sayfalarını (chrome://version, Chrome 61'den itibaren standart yeni sekme sayfası, about: addons vb.) ve diğer uzantı sayfalarını etkilemesini engeller. Ayrıca her tarayıcı kendi uzantı galerisine (Chrome Web Mağazası veya AMO gibi) erişimi de kısıtlar. "
|
|
||||||
},
|
|
||||||
"syncDropboxDeprecated": {
|
|
||||||
"message": "Dropbox içe/dışa aktarma, seçenekler sayfasında daha gelişmiş bir stil senkronizasyonu ile değiştirilir."
|
|
||||||
},
|
|
||||||
"syncDropboxStyles": {
|
|
||||||
"message": "Dropbox'tan aktar"
|
|
||||||
},
|
|
||||||
"toggleStyle": {
|
|
||||||
"message": "Stili değiştir"
|
|
||||||
},
|
|
||||||
"undo": {
|
|
||||||
"message": "Geri al"
|
|
||||||
},
|
|
||||||
"undoGlobal": {
|
|
||||||
"message": "Tüm bölümlerde geri alma işlemi uygula."
|
|
||||||
},
|
|
||||||
"unreachableContentScript": {
|
|
||||||
"message": "Sayfayla iletişim kurulamadı. Sekmeyi yeniden yüklemeyi deneyin."
|
|
||||||
},
|
|
||||||
"unreachableFileHint": {
|
|
||||||
"message": "Stylus, eğer siz Stylus chrome://extensions sayfasından ilgili onay kutusunu işaretlerseniz file:// adreslerine erişebilir."
|
|
||||||
},
|
|
||||||
"unzipStyles": {
|
|
||||||
"message": "Stiller açılıyor..."
|
|
||||||
},
|
|
||||||
"updateAllCheckSucceededNoUpdate": {
|
|
||||||
"message": "Güncelleme bulunamadı."
|
|
||||||
},
|
|
||||||
"updateCheckFailBadResponseCode": {
|
"updateCheckFailBadResponseCode": {
|
||||||
"message": "Güncellenemedi: sunucu yanıt olarak $code$ kodunu gönderdi.",
|
"message": "Güncellenemedi: sunucu yanıt olarak $code$ kodunu gönderdi.",
|
||||||
"placeholders": {
|
"placeholders": {
|
||||||
|
@ -734,58 +151,10 @@
|
||||||
"updateCheckFailServerUnreachable": {
|
"updateCheckFailServerUnreachable": {
|
||||||
"message": "Güncellenemedi: sunucuya erişilemiyor."
|
"message": "Güncellenemedi: sunucuya erişilemiyor."
|
||||||
},
|
},
|
||||||
"updateCheckHistory": {
|
|
||||||
"message": "Güncelleme kontrolü geçmişi"
|
|
||||||
},
|
|
||||||
"updateCheckManualUpdateForce": {
|
|
||||||
"message": "Güncellemeyi kur (yerel düzenlemelerin üzerine yazılacak)"
|
|
||||||
},
|
|
||||||
"updateCheckManualUpdateHint": {
|
|
||||||
"message": "Bir güncellemeyi zorla yapmak yerel düzenlemelerin üstüne yazacaktır."
|
|
||||||
},
|
|
||||||
"updateCheckSkippedLocallyEdited": {
|
|
||||||
"message": "Bu stil yerel olarak düzenlendi."
|
|
||||||
},
|
|
||||||
"updateCheckSkippedMaybeLocallyEdited": {
|
|
||||||
"message": "Bu stil yerel olarak düzenlenmiş olabilir."
|
|
||||||
},
|
|
||||||
"updateCheckSucceededNoUpdate": {
|
"updateCheckSucceededNoUpdate": {
|
||||||
"message": "Stil güncel."
|
"message": "Stil güncel."
|
||||||
},
|
},
|
||||||
"updateCompleted": {
|
"updateCompleted": {
|
||||||
"message": "Güncelleme tamamlandı."
|
"message": "Güncelleme tamamlandı."
|
||||||
},
|
|
||||||
"updatesCurrentlyInstalled": {
|
|
||||||
"message": "Kurulan güncellemeler:"
|
|
||||||
},
|
|
||||||
"uploadingFile": {
|
|
||||||
"message": "Dosya Yükleniyor..."
|
|
||||||
},
|
|
||||||
"usercssAvoidOverwriting": {
|
|
||||||
"message": "Mevcut bir stilin üzerine yazmaktan kaçınmak için lütfen @name veya @ ad alanının değerini değiştirin."
|
|
||||||
},
|
|
||||||
"usercssConfigIncomplete": {
|
|
||||||
"message": "Stil, yapılandırma iletişim kutusu gösterildikten sonra güncellendi veya silindi. Bu değişkenler, stilin meta verilerini bozmamak için kaydedilmedi:"
|
|
||||||
},
|
|
||||||
"usercssEditorNamePlaceholder": {
|
|
||||||
"message": "Kodda @name belirtin"
|
|
||||||
},
|
|
||||||
"usercssReplaceTemplateConfirmation": {
|
|
||||||
"message": "Yeni Usercss stilleri için varsayılan şablonu geçerli kodla değiştir?"
|
|
||||||
},
|
|
||||||
"usercssReplaceTemplateSectionBody": {
|
|
||||||
"message": "Kodu buraya ekle..."
|
|
||||||
},
|
|
||||||
"versionInvalidOlder": {
|
|
||||||
"message": "Sürüm, kurulu stillerden daha eski."
|
|
||||||
},
|
|
||||||
"writeStyleFor": {
|
|
||||||
"message": "Şunun için stil yaz:"
|
|
||||||
},
|
|
||||||
"writeStyleForURL": {
|
|
||||||
"message": "bu URL"
|
|
||||||
},
|
|
||||||
"zipStyles": {
|
|
||||||
"message": "Stiller sıkıştırılıyor..."
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,285 +0,0 @@
|
||||||
{
|
|
||||||
"InaccessibleFileHint": {
|
|
||||||
"message": "Stylus не може отримати доступ до деяких типів файлів (наприклад, файли PDF і JSON)."
|
|
||||||
},
|
|
||||||
"addStyleLabel": {
|
|
||||||
"message": "Написати новий стиль"
|
|
||||||
},
|
|
||||||
"addStyleTitle": {
|
|
||||||
"message": "Додати стиль"
|
|
||||||
},
|
|
||||||
"alphaChannel": {
|
|
||||||
"message": "Прозорість"
|
|
||||||
},
|
|
||||||
"appliesAdd": {
|
|
||||||
"message": "Додати"
|
|
||||||
},
|
|
||||||
"appliesDisplay": {
|
|
||||||
"message": "Застосувати до $applies$",
|
|
||||||
"placeholders": {
|
|
||||||
"applies": {
|
|
||||||
"content": "$1"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"appliesDisplayTruncatedSuffix": {
|
|
||||||
"message": "та інші"
|
|
||||||
},
|
|
||||||
"appliesDomainOption": {
|
|
||||||
"message": "URL в домені"
|
|
||||||
},
|
|
||||||
"appliesHelp": {
|
|
||||||
"message": "Щоб вказати, до яких URL відноситься код в цьому розділі, скористайтеся параметром \"Застосувати до\"."
|
|
||||||
},
|
|
||||||
"appliesLabel": {
|
|
||||||
"message": "Застосувати до"
|
|
||||||
},
|
|
||||||
"appliesLineWidgetLabel": {
|
|
||||||
"message": "Показати цільові сайти секцій"
|
|
||||||
},
|
|
||||||
"appliesRemove": {
|
|
||||||
"message": "Видалити"
|
|
||||||
},
|
|
||||||
"appliesRemoveError": {
|
|
||||||
"message": "Не можна видалити останній елемент"
|
|
||||||
},
|
|
||||||
"appliesSpecify": {
|
|
||||||
"message": "Вказати"
|
|
||||||
},
|
|
||||||
"appliesToEverything": {
|
|
||||||
"message": "Все"
|
|
||||||
},
|
|
||||||
"appliesUrlPrefixOption": {
|
|
||||||
"message": "URL, що починаються з "
|
|
||||||
},
|
|
||||||
"applyAllUpdates": {
|
|
||||||
"message": "Застосувати всі оновлення"
|
|
||||||
},
|
|
||||||
"author": {
|
|
||||||
"message": "Автор"
|
|
||||||
},
|
|
||||||
"backupButtons": {
|
|
||||||
"message": "Резервне копіювання"
|
|
||||||
},
|
|
||||||
"bckpInstStyles": {
|
|
||||||
"message": "Експорт стилів"
|
|
||||||
},
|
|
||||||
"checkingForUpdate": {
|
|
||||||
"message": "Перевірка ... "
|
|
||||||
},
|
|
||||||
"clickToUninstall": {
|
|
||||||
"message": "Натисніть, щоб видалити "
|
|
||||||
},
|
|
||||||
"cm_selectByTokensTooltip": {
|
|
||||||
"message": "Приклади токенов: .foo-бар-2 #aabbcc 0,32 !important\nЯкщо вимкнено: вибираються слова, розділені розділовими знаками."
|
|
||||||
},
|
|
||||||
"confirmCancel": {
|
|
||||||
"message": "Скасувати "
|
|
||||||
},
|
|
||||||
"confirmDelete": {
|
|
||||||
"message": "Видалити "
|
|
||||||
},
|
|
||||||
"confirmNo": {
|
|
||||||
"message": "Ні"
|
|
||||||
},
|
|
||||||
"confirmSave": {
|
|
||||||
"message": "Зберегти"
|
|
||||||
},
|
|
||||||
"confirmStop": {
|
|
||||||
"message": "Зупинити"
|
|
||||||
},
|
|
||||||
"confirmYes": {
|
|
||||||
"message": "Так"
|
|
||||||
},
|
|
||||||
"deleteStyleLabel": {
|
|
||||||
"message": "Видалити"
|
|
||||||
},
|
|
||||||
"disableStyleLabel": {
|
|
||||||
"message": "Вимкнути"
|
|
||||||
},
|
|
||||||
"editDeleteText": {
|
|
||||||
"message": "Видалити"
|
|
||||||
},
|
|
||||||
"findStyles": {
|
|
||||||
"message": "Знайти стилі"
|
|
||||||
},
|
|
||||||
"genericAdd": {
|
|
||||||
"message": "Додати"
|
|
||||||
},
|
|
||||||
"genericDescription": {
|
|
||||||
"message": "Опис"
|
|
||||||
},
|
|
||||||
"genericDisabledLabel": {
|
|
||||||
"message": "Відключено"
|
|
||||||
},
|
|
||||||
"genericEnabledLabel": {
|
|
||||||
"message": "Увімкнено"
|
|
||||||
},
|
|
||||||
"genericError": {
|
|
||||||
"message": "Помилка"
|
|
||||||
},
|
|
||||||
"genericHistoryLabel": {
|
|
||||||
"message": "Історія"
|
|
||||||
},
|
|
||||||
"genericNext": {
|
|
||||||
"message": "Наступний "
|
|
||||||
},
|
|
||||||
"genericPrevious": {
|
|
||||||
"message": "Попередній"
|
|
||||||
},
|
|
||||||
"genericResetLabel": {
|
|
||||||
"message": "Скинути"
|
|
||||||
},
|
|
||||||
"genericSavedMessage": {
|
|
||||||
"message": "Збережено"
|
|
||||||
},
|
|
||||||
"genericTitle": {
|
|
||||||
"message": "Ім'я"
|
|
||||||
},
|
|
||||||
"genericUnknown": {
|
|
||||||
"message": "Невідомо"
|
|
||||||
},
|
|
||||||
"gettingStyles": {
|
|
||||||
"message": "Отримання всіх стилів ..."
|
|
||||||
},
|
|
||||||
"helpAlt": {
|
|
||||||
"message": "Довідка"
|
|
||||||
},
|
|
||||||
"helpKeyMapCommand": {
|
|
||||||
"message": "Введіть ім'я команди"
|
|
||||||
},
|
|
||||||
"helpKeyMapHotkey": {
|
|
||||||
"message": "Натисніть клавішу"
|
|
||||||
},
|
|
||||||
"hostDisabled": {
|
|
||||||
"message": "Цей хост вимкнено через помилку в поточній версії браузера, що використовується"
|
|
||||||
},
|
|
||||||
"importLabel": {
|
|
||||||
"message": "Імпорт"
|
|
||||||
},
|
|
||||||
"importReplaceLabel": {
|
|
||||||
"message": "Замінити стиль"
|
|
||||||
},
|
|
||||||
"importReportLegendIdentical": {
|
|
||||||
"message": "ідентичні пропущені"
|
|
||||||
},
|
|
||||||
"importReportLegendInvalid": {
|
|
||||||
"message": "недісних пропущено"
|
|
||||||
},
|
|
||||||
"importReportTitle": {
|
|
||||||
"message": "Імпорт стилів закінчено"
|
|
||||||
},
|
|
||||||
"installUpdate": {
|
|
||||||
"message": "Встановити оновлення"
|
|
||||||
},
|
|
||||||
"manageFavicons": {
|
|
||||||
"message": "Піктограми для цільових сайтів"
|
|
||||||
},
|
|
||||||
"manageFilters": {
|
|
||||||
"message": "Фільтри"
|
|
||||||
},
|
|
||||||
"manageHeading": {
|
|
||||||
"message": "Встановити Styles"
|
|
||||||
},
|
|
||||||
"manageNewUI": {
|
|
||||||
"message": "Новий макет інтерфейсу управління користувача"
|
|
||||||
},
|
|
||||||
"meta_unknownJSONLiteral": {
|
|
||||||
"message": "Невірний JSON: $literal$не є дійсним літералом JSON",
|
|
||||||
"placeholders": {
|
|
||||||
"literal": {
|
|
||||||
"content": "$1"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"openManage": {
|
|
||||||
"message": "Керування"
|
|
||||||
},
|
|
||||||
"openOptions": {
|
|
||||||
"message": "Налаштування"
|
|
||||||
},
|
|
||||||
"optionsHeading": {
|
|
||||||
"message": "Налаштування"
|
|
||||||
},
|
|
||||||
"optionsReset": {
|
|
||||||
"message": "Скидання налаштувань до значень за замовчуванням"
|
|
||||||
},
|
|
||||||
"optionsResetButton": {
|
|
||||||
"message": "Скинути параметри"
|
|
||||||
},
|
|
||||||
"optionsSubheading": {
|
|
||||||
"message": "Додатково"
|
|
||||||
},
|
|
||||||
"optionsSyncLogin": {
|
|
||||||
"message": "Логін"
|
|
||||||
},
|
|
||||||
"optionsSyncStatusRelogin": {
|
|
||||||
"message": "Сеанс закінчився, будь ласка, увійдіть ще раз."
|
|
||||||
},
|
|
||||||
"paginationNext": {
|
|
||||||
"message": "Наступна сторінка"
|
|
||||||
},
|
|
||||||
"paginationPrevious": {
|
|
||||||
"message": "Попередня сторінка"
|
|
||||||
},
|
|
||||||
"replace": {
|
|
||||||
"message": "Замінити"
|
|
||||||
},
|
|
||||||
"replaceAll": {
|
|
||||||
"message": "Замінити все"
|
|
||||||
},
|
|
||||||
"retrieveBckp": {
|
|
||||||
"message": "Імпорт стилів"
|
|
||||||
},
|
|
||||||
"search": {
|
|
||||||
"message": "Пошук"
|
|
||||||
},
|
|
||||||
"searchStylesAll": {
|
|
||||||
"message": "Усі"
|
|
||||||
},
|
|
||||||
"searchStylesCode": {
|
|
||||||
"message": "CSS код"
|
|
||||||
},
|
|
||||||
"searchStylesHelp": {
|
|
||||||
"message": "</> або <Ctrl-F>клавіша фокусує поле пошуку.\nРежим за замовчуванням — це пошук у звичайному тексті для всіх термінів, розділених пробілами, у будь-якому порядку.\nТочні слова: оберніть запит у подвійні лапки, напр. <.header ~ div\">\nРегулярні вирази: включають косі риски та прапорці, напр.</body.*?\\ba\\b/i>\n«By URL» в селекторі області: знаходить стилі, які застосовуються до повністю вказаної URL-адреси, напр. https://www.example.org/\n\"Metadata\" в селектрі області: шукає в іменах, \"applies to\" специфікаторів, URL-адреси встановлення, URL-адресі оновлення та всьому блоку метаданих для стилів CSS користувача."
|
|
||||||
},
|
|
||||||
"searchStylesName": {
|
|
||||||
"message": "Назва"
|
|
||||||
},
|
|
||||||
"sectionCode": {
|
|
||||||
"message": "Код"
|
|
||||||
},
|
|
||||||
"sections": {
|
|
||||||
"message": "Розділи"
|
|
||||||
},
|
|
||||||
"styleBeautify": {
|
|
||||||
"message": "Облагородити"
|
|
||||||
},
|
|
||||||
"styleCancelEditLabel": {
|
|
||||||
"message": "Повернутися до керування"
|
|
||||||
},
|
|
||||||
"styleEnabledLabel": {
|
|
||||||
"message": "Увімкнено"
|
|
||||||
},
|
|
||||||
"stylePreferSchemeLabel": {
|
|
||||||
"message": "Темна/Світла тема"
|
|
||||||
},
|
|
||||||
"styleSaveLabel": {
|
|
||||||
"message": "Зберегти"
|
|
||||||
},
|
|
||||||
"syncErrorRelogin": {
|
|
||||||
"message": "Помилка синхронізації. Ви вийшли із системи. \nСпробуйте повторно увійти до системи в налаштуваннях Stylus."
|
|
||||||
},
|
|
||||||
"toggleStyle": {
|
|
||||||
"message": "Включити/виключити стиль"
|
|
||||||
},
|
|
||||||
"writeStyleFor": {
|
|
||||||
"message": "Створити стиль для:"
|
|
||||||
},
|
|
||||||
"writeStyleForURL": {
|
|
||||||
"message": "цей URL"
|
|
||||||
},
|
|
||||||
"zipStyles": {
|
|
||||||
"message": "Запаковування стилів ... "
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,379 +0,0 @@
|
||||||
{
|
|
||||||
"InaccessibleFileHint": {
|
|
||||||
"message": "Stylus không thể truy cập một số kiểu tập tin (chẳng hạn như PDF và JSON)."
|
|
||||||
},
|
|
||||||
"addStyleLabel": {
|
|
||||||
"message": "Viết bảng định kiểu mới"
|
|
||||||
},
|
|
||||||
"addStyleTitle": {
|
|
||||||
"message": "Thêm bảng định kiểu"
|
|
||||||
},
|
|
||||||
"alphaChannel": {
|
|
||||||
"message": "Độ mờ"
|
|
||||||
},
|
|
||||||
"appliesAdd": {
|
|
||||||
"message": "Thêm"
|
|
||||||
},
|
|
||||||
"appliesDisplay": {
|
|
||||||
"message": "Áp dụng với: $applies$",
|
|
||||||
"placeholders": {
|
|
||||||
"applies": {
|
|
||||||
"content": "$1"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"appliesDisplayTruncatedSuffix": {
|
|
||||||
"message": "và một số khác"
|
|
||||||
},
|
|
||||||
"appliesDomainOption": {
|
|
||||||
"message": "Các địa chỉ thuộc tên miền này"
|
|
||||||
},
|
|
||||||
"appliesHelp": {
|
|
||||||
"message": "Dùng tuỳ chọn \"Áp dụng với\" để giới hạn các địa chỉ cho đoạn mã này"
|
|
||||||
},
|
|
||||||
"appliesLabel": {
|
|
||||||
"message": "Áp dụng với"
|
|
||||||
},
|
|
||||||
"appliesLineWidgetLabel": {
|
|
||||||
"message": "Hiển thị thông tin \"Áp dụng với\""
|
|
||||||
},
|
|
||||||
"appliesLineWidgetWarning": {
|
|
||||||
"message": "Không hoạt động với CSS tối giản"
|
|
||||||
},
|
|
||||||
"appliesRegexpOption": {
|
|
||||||
"message": "URL khớp với biểu thức chính quy"
|
|
||||||
},
|
|
||||||
"appliesRemove": {
|
|
||||||
"message": "Xoá"
|
|
||||||
},
|
|
||||||
"appliesRemoveError": {
|
|
||||||
"message": "Không thể xoá mục \"Áp dụng với\" cuối cùng"
|
|
||||||
},
|
|
||||||
"appliesSpecify": {
|
|
||||||
"message": "Chỉ định"
|
|
||||||
},
|
|
||||||
"appliesToEverything": {
|
|
||||||
"message": "Tất cả"
|
|
||||||
},
|
|
||||||
"appliesUrlPrefixOption": {
|
|
||||||
"message": "URL bắt đầu bằng"
|
|
||||||
},
|
|
||||||
"author": {
|
|
||||||
"message": "Tác giả"
|
|
||||||
},
|
|
||||||
"backupButtons": {
|
|
||||||
"message": "Sao lưu"
|
|
||||||
},
|
|
||||||
"backupMessage": {
|
|
||||||
"message": "Để nhập tập tin sao lưu, kéo và thả nó vào trang này hoặc nhấp vào nút Nhập.\n\nĐể xuất một bản sao lưu tương thích với Stylus trước phiên bản 1.5.18, nhấp chuột phải hoặc nhấn giữ Shift khi nhấp chuột trái vào nút Xuất."
|
|
||||||
},
|
|
||||||
"bckpInstStyles": {
|
|
||||||
"message": "Xuất bảng định kiểu"
|
|
||||||
},
|
|
||||||
"checkForUpdate": {
|
|
||||||
"message": "Kiểm tra bản cập nhật mới"
|
|
||||||
},
|
|
||||||
"checkingForUpdate": {
|
|
||||||
"message": "Đang kiểm tra..."
|
|
||||||
},
|
|
||||||
"clickToUninstall": {
|
|
||||||
"message": "Nhấp để huỷ kích hoạt"
|
|
||||||
},
|
|
||||||
"cm_autoCloseBrackets": {
|
|
||||||
"message": "Tự động đóng ngoặc và nháy"
|
|
||||||
},
|
|
||||||
"cm_autoCloseBracketsTooltip": {
|
|
||||||
"message": "Tự động thêm dấu đóng tương ứng khi nhập một trong các dấu (, [, {, ' và \"."
|
|
||||||
},
|
|
||||||
"cm_autocompleteOnTyping": {
|
|
||||||
"message": "Tự động hoàn thành"
|
|
||||||
},
|
|
||||||
"cm_colorpicker": {
|
|
||||||
"message": "Bộ chọn màu cho màu CSS"
|
|
||||||
},
|
|
||||||
"cm_indentWithTabs": {
|
|
||||||
"message": "Lùi đầu dòng thông minh bằng tab"
|
|
||||||
},
|
|
||||||
"cm_lineWrapping": {
|
|
||||||
"message": "Gập dòng dài"
|
|
||||||
},
|
|
||||||
"cm_linter": {
|
|
||||||
"message": "Trình phân tích cú pháp"
|
|
||||||
},
|
|
||||||
"cm_matchHighlight": {
|
|
||||||
"message": "Làm nổi"
|
|
||||||
},
|
|
||||||
"cm_matchHighlightSelection": {
|
|
||||||
"message": "Chỉ vùng được chọn"
|
|
||||||
},
|
|
||||||
"cm_matchHighlightToken": {
|
|
||||||
"message": "Token nằm dưới con trỏ văn bản"
|
|
||||||
},
|
|
||||||
"cm_selectByTokens": {
|
|
||||||
"message": "Nhấp đúp để chọn token"
|
|
||||||
},
|
|
||||||
"cm_selectByTokensTooltip": {
|
|
||||||
"message": "Ví dụ về token: .foo-bar-2 #aabbcc 0.32 !important\nKhi tắt: Chọn từ (phân tách bằng dấu câu)."
|
|
||||||
},
|
|
||||||
"cm_smartIndent": {
|
|
||||||
"message": "Lùi đầu dòng thông minh"
|
|
||||||
},
|
|
||||||
"cm_tabSize": {
|
|
||||||
"message": "Chiều rộng tab"
|
|
||||||
},
|
|
||||||
"cm_theme": {
|
|
||||||
"message": "Chủ đề"
|
|
||||||
},
|
|
||||||
"colorpickerSwitchFormatTooltip": {
|
|
||||||
"message": "Đổi định dạng: HEX → RGB → HSL.\nNhấn giữ phím Shift khi nhấp để đảo thứ tự.\nPhím tắt: PgUp và PgDn."
|
|
||||||
},
|
|
||||||
"colorpickerTooltip": {
|
|
||||||
"message": "Mở bộ chọn màu"
|
|
||||||
},
|
|
||||||
"configOnChange": {
|
|
||||||
"message": "khi thay đổi"
|
|
||||||
},
|
|
||||||
"configOnChangeTooltip": {
|
|
||||||
"message": "Tự động lưu và áp dụng"
|
|
||||||
},
|
|
||||||
"configureStyle": {
|
|
||||||
"message": "Thiết lập"
|
|
||||||
},
|
|
||||||
"configureStyleOnHomepage": {
|
|
||||||
"message": "Thiết lập trên trang chủ"
|
|
||||||
},
|
|
||||||
"confirmCancel": {
|
|
||||||
"message": "Huỷ"
|
|
||||||
},
|
|
||||||
"confirmClose": {
|
|
||||||
"message": "Đóng"
|
|
||||||
},
|
|
||||||
"confirmDefault": {
|
|
||||||
"message": "Dùng mặc định"
|
|
||||||
},
|
|
||||||
"confirmDelete": {
|
|
||||||
"message": "Xoá"
|
|
||||||
},
|
|
||||||
"confirmDiscardChanges": {
|
|
||||||
"message": "Huỷ thay đổi?"
|
|
||||||
},
|
|
||||||
"confirmNo": {
|
|
||||||
"message": "Không"
|
|
||||||
},
|
|
||||||
"confirmSave": {
|
|
||||||
"message": "Lưu"
|
|
||||||
},
|
|
||||||
"confirmStop": {
|
|
||||||
"message": "Dừng"
|
|
||||||
},
|
|
||||||
"confirmYes": {
|
|
||||||
"message": "Có"
|
|
||||||
},
|
|
||||||
"connectingDropbox": {
|
|
||||||
"message": "Đang kết nối với Dropbox..."
|
|
||||||
},
|
|
||||||
"connectingDropboxNotAllowed": {
|
|
||||||
"message": "Tính năng kết nối với Dropbox chỉ khả dụng khi cài ứng dụng trực tiếp từ cửa hàng web"
|
|
||||||
},
|
|
||||||
"copied": {
|
|
||||||
"message": "Đã chép vào khay nhớ tạm"
|
|
||||||
},
|
|
||||||
"copy": {
|
|
||||||
"message": "Chép vào khay nhớ tạm"
|
|
||||||
},
|
|
||||||
"dateAbbrDay": {
|
|
||||||
"message": "$value$ ngày",
|
|
||||||
"placeholders": {
|
|
||||||
"value": {
|
|
||||||
"content": "$1"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"dateAbbrHour": {
|
|
||||||
"message": "$value$ giờ",
|
|
||||||
"placeholders": {
|
|
||||||
"value": {
|
|
||||||
"content": "$1"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"dateAbbrMonth": {
|
|
||||||
"message": "$value$ tháng",
|
|
||||||
"placeholders": {
|
|
||||||
"value": {
|
|
||||||
"content": "$1"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"dateAbbrYear": {
|
|
||||||
"message": "$value$ năm",
|
|
||||||
"placeholders": {
|
|
||||||
"value": {
|
|
||||||
"content": "$1"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"dateInstalled": {
|
|
||||||
"message": "Ngày cài đặt"
|
|
||||||
},
|
|
||||||
"dateUpdated": {
|
|
||||||
"message": "Ngày cập nhật"
|
|
||||||
},
|
|
||||||
"defaultTheme": {
|
|
||||||
"message": "mặc định"
|
|
||||||
},
|
|
||||||
"deleteStyleConfirm": {
|
|
||||||
"message": "Bạn có chắc chắn muốn xoá bảng định kiểu này không?"
|
|
||||||
},
|
|
||||||
"deleteStyleLabel": {
|
|
||||||
"message": "Xoá"
|
|
||||||
},
|
|
||||||
"disableAllStyles": {
|
|
||||||
"message": "Tắt tất cả bảng định kiểu"
|
|
||||||
},
|
|
||||||
"disableAllStylesOff": {
|
|
||||||
"message": "Bật tất cả bảng định kiểu"
|
|
||||||
},
|
|
||||||
"disableStyleLabel": {
|
|
||||||
"message": "Vô hiệu hoá"
|
|
||||||
},
|
|
||||||
"draftAction": {
|
|
||||||
"message": "Chọn \"Có\" để tải bản nháp hoặc \"Không\" để xoá nó đi."
|
|
||||||
},
|
|
||||||
"editDeleteText": {
|
|
||||||
"message": "Xoá"
|
|
||||||
},
|
|
||||||
"editGotoLine": {
|
|
||||||
"message": "Đi đến dòng (hoặc dòng:cột)"
|
|
||||||
},
|
|
||||||
"editStyleHeading": {
|
|
||||||
"message": "Sửa bảng định kiểu"
|
|
||||||
},
|
|
||||||
"editStyleLabel": {
|
|
||||||
"message": "Sửa"
|
|
||||||
},
|
|
||||||
"editStyleTitle": {
|
|
||||||
"message": "Sửa bảng định kiểu $stylename$",
|
|
||||||
"placeholders": {
|
|
||||||
"stylename": {
|
|
||||||
"content": "$1"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"editorSettings": {
|
|
||||||
"message": "Cài đặt trình soạn thảo"
|
|
||||||
},
|
|
||||||
"enableStyleLabel": {
|
|
||||||
"message": "Kích hoạt"
|
|
||||||
},
|
|
||||||
"exportCompatible": {
|
|
||||||
"message": "Xuất (chế độ tương thích)"
|
|
||||||
},
|
|
||||||
"exportLabel": {
|
|
||||||
"message": "Xuất"
|
|
||||||
},
|
|
||||||
"exportSavedSuccess": {
|
|
||||||
"message": "Lưu thành công"
|
|
||||||
},
|
|
||||||
"externalFeedback": {
|
|
||||||
"message": "Phản hồi"
|
|
||||||
},
|
|
||||||
"externalHomepage": {
|
|
||||||
"message": "Trang chủ"
|
|
||||||
},
|
|
||||||
"externalLink": {
|
|
||||||
"message": "Liên kết ngoài"
|
|
||||||
},
|
|
||||||
"externalSupport": {
|
|
||||||
"message": "Ủng hộ"
|
|
||||||
},
|
|
||||||
"genericAdd": {
|
|
||||||
"message": "Thêm"
|
|
||||||
},
|
|
||||||
"genericDescription": {
|
|
||||||
"message": "Mô tả"
|
|
||||||
},
|
|
||||||
"genericDisabledLabel": {
|
|
||||||
"message": "Vô hiệu hoá"
|
|
||||||
},
|
|
||||||
"genericEnabledLabel": {
|
|
||||||
"message": "Kích hoạt"
|
|
||||||
},
|
|
||||||
"genericError": {
|
|
||||||
"message": "Lỗi"
|
|
||||||
},
|
|
||||||
"genericHistoryLabel": {
|
|
||||||
"message": "Lịch sử"
|
|
||||||
},
|
|
||||||
"genericNext": {
|
|
||||||
"message": "Sau"
|
|
||||||
},
|
|
||||||
"genericPrevious": {
|
|
||||||
"message": "Trước"
|
|
||||||
},
|
|
||||||
"genericResetLabel": {
|
|
||||||
"message": "Đặt lại"
|
|
||||||
},
|
|
||||||
"genericSavedMessage": {
|
|
||||||
"message": "Đã lưu"
|
|
||||||
},
|
|
||||||
"genericTest": {
|
|
||||||
"message": "Thứ"
|
|
||||||
},
|
|
||||||
"genericTitle": {
|
|
||||||
"message": "Tiêu đề"
|
|
||||||
},
|
|
||||||
"genericUnknown": {
|
|
||||||
"message": "Không xác định"
|
|
||||||
},
|
|
||||||
"helpAlt": {
|
|
||||||
"message": "Trợ giúp"
|
|
||||||
},
|
|
||||||
"importLabel": {
|
|
||||||
"message": "Nhập"
|
|
||||||
},
|
|
||||||
"importReportLegendAdded": {
|
|
||||||
"message": "đã thêm"
|
|
||||||
},
|
|
||||||
"importReportLegendIdentical": {
|
|
||||||
"message": "Đã bỏ qua các tệp trùng lặp"
|
|
||||||
},
|
|
||||||
"importReportLegendInvalid": {
|
|
||||||
"message": "Đã bỏ qua các tệp không hợp lệ"
|
|
||||||
},
|
|
||||||
"importReportLegendUpdatedBoth": {
|
|
||||||
"message": "đã cập nhật siêu thông tin và mã"
|
|
||||||
},
|
|
||||||
"importReportLegendUpdatedCode": {
|
|
||||||
"message": "đã cập nhật mã"
|
|
||||||
},
|
|
||||||
"importReportLegendUpdatedMeta": {
|
|
||||||
"message": "đã cập nhật siêu thông tin"
|
|
||||||
},
|
|
||||||
"importReportTitle": {
|
|
||||||
"message": "Đã nhập xong"
|
|
||||||
},
|
|
||||||
"installUpdateFromLabel": {
|
|
||||||
"message": "Kiểm tra bản cập nhật mới"
|
|
||||||
},
|
|
||||||
"license": {
|
|
||||||
"message": "Giấy phép"
|
|
||||||
},
|
|
||||||
"linterCSSLintIncompatible": {
|
|
||||||
"message": "CSSLint không hỗ trợ bộ tiền xử lý $preprocessorname$",
|
|
||||||
"placeholders": {
|
|
||||||
"preprocessorname": {
|
|
||||||
"content": "$1"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"linterJSONError": {
|
|
||||||
"message": "Định dạng JSON không hợp lệ"
|
|
||||||
},
|
|
||||||
"styleEnabledLabel": {
|
|
||||||
"message": "Kích hoạt"
|
|
||||||
},
|
|
||||||
"styleSaveLabel": {
|
|
||||||
"message": "Lưu"
|
|
||||||
}
|
|
||||||
}
|
|
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
|
@ -1,26 +0,0 @@
|
||||||
/* global createWorkerApi */// worker-util.js
|
|
||||||
'use strict';
|
|
||||||
|
|
||||||
/** @namespace BackgroundWorker */
|
|
||||||
createWorkerApi({
|
|
||||||
|
|
||||||
async compileUsercss(...args) {
|
|
||||||
require(['/js/usercss-compiler']); /* global compileUsercss */
|
|
||||||
return compileUsercss(...args);
|
|
||||||
},
|
|
||||||
|
|
||||||
nullifyInvalidVars(vars) {
|
|
||||||
require(['/js/meta-parser']); /* global metaParser */
|
|
||||||
return metaParser.nullifyInvalidVars(vars);
|
|
||||||
},
|
|
||||||
|
|
||||||
parseMozFormat(...args) {
|
|
||||||
require(['/js/moz-parser']); /* global extractSections */
|
|
||||||
return extractSections(...args);
|
|
||||||
},
|
|
||||||
|
|
||||||
parseUsercssMeta(text) {
|
|
||||||
require(['/js/meta-parser']);
|
|
||||||
return metaParser.parse(text);
|
|
||||||
},
|
|
||||||
});
|
|
|
@ -1,213 +1,513 @@
|
||||||
/* global API msg */// msg.js
|
/*
|
||||||
/* global addAPI bgReady */// common.js
|
global dbExec getStyles saveStyle deleteStyle
|
||||||
/* global createWorker */// worker-util.js
|
global handleCssTransitionBug detectSloppyRegexps
|
||||||
/* global prefs */
|
global openEditor
|
||||||
/* global styleMan */
|
global styleViaAPI
|
||||||
/* global syncMan */
|
global loadScript
|
||||||
/* global updateMan */
|
global usercss
|
||||||
/* global usercssMan */
|
*/
|
||||||
/* global usoApi */
|
|
||||||
/* global uswApi */
|
|
||||||
/* global FIREFOX UA activateTab openURL */ // toolbox.js
|
|
||||||
/* global colorScheme */ // color-scheme.js
|
|
||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
//#region API
|
window.API_METHODS = Object.assign(window.API_METHODS || {}, {
|
||||||
|
|
||||||
addAPI(/** @namespace API */ {
|
getStyles,
|
||||||
|
saveStyle,
|
||||||
|
deleteStyle,
|
||||||
|
|
||||||
/** Temporary storage for data needed elsewhere e.g. in a content script */
|
getStyleFromDB: id =>
|
||||||
data: ((data = {}) => ({
|
dbExec('get', id).then(event => event.target.result),
|
||||||
del: key => delete data[key],
|
|
||||||
get: key => data[key],
|
|
||||||
has: key => key in data,
|
|
||||||
pop: key => {
|
|
||||||
const val = data[key];
|
|
||||||
delete data[key];
|
|
||||||
return val;
|
|
||||||
},
|
|
||||||
set: (key, val) => {
|
|
||||||
data[key] = val;
|
|
||||||
},
|
|
||||||
}))(),
|
|
||||||
|
|
||||||
styles: styleMan,
|
download(msg) {
|
||||||
sync: syncMan,
|
delete msg.method;
|
||||||
updater: updateMan,
|
return download(msg.url, msg);
|
||||||
usercss: usercssMan,
|
|
||||||
uso: usoApi,
|
|
||||||
usw: uswApi,
|
|
||||||
colorScheme,
|
|
||||||
/** @type {BackgroundWorker} */
|
|
||||||
worker: createWorker({url: '/background/background-worker'}),
|
|
||||||
|
|
||||||
/** @returns {string} */
|
|
||||||
getTabUrlPrefix() {
|
|
||||||
return this.sender.tab.url.match(/^([\w-]+:\/+[^/#]+)/)[1];
|
|
||||||
},
|
},
|
||||||
|
parseCss({code}) {
|
||||||
|
return usercss.invokeWorker({action: 'parse', code});
|
||||||
|
},
|
||||||
|
getPrefs: prefs.getAll,
|
||||||
|
healthCheck: () => dbExec().then(() => true),
|
||||||
|
|
||||||
/**
|
detectSloppyRegexps,
|
||||||
* Opens the editor or activates an existing tab
|
openEditor,
|
||||||
* @param {{
|
updateIcon,
|
||||||
id?: number
|
|
||||||
domain?: string
|
// exposed for stuff that requires followup sendMessage() like popup::openSettings
|
||||||
'url-prefix'?: string
|
// that would fail otherwise if another extension forced the tab to open
|
||||||
}} params
|
// in the foreground thus auto-closing the popup (in Chrome)
|
||||||
* @returns {Promise<chrome.tabs.Tab>}
|
openURL,
|
||||||
*/
|
|
||||||
async openEditor(params) {
|
closeTab: (msg, sender, respond) => {
|
||||||
const u = new URL(chrome.runtime.getURL('edit.html'));
|
chrome.tabs.remove(msg.tabId || sender.tab.id, () => {
|
||||||
u.search = new URLSearchParams(params);
|
if (chrome.runtime.lastError && msg.tabId !== sender.tab.id) {
|
||||||
const wnd = chrome.windows && prefs.get('openEditInWindow');
|
respond(new Error(chrome.runtime.lastError.message));
|
||||||
const wndPos = wnd && prefs.get('windowPosition');
|
|
||||||
const wndBase = wnd && prefs.get('openEditInWindow.popup') ? {type: 'popup'} : {};
|
|
||||||
const ffBug = wnd && FIREFOX; // https://bugzil.la/1271047
|
|
||||||
if (wndPos) {
|
|
||||||
const {left, top, width, height} = wndPos;
|
|
||||||
const r = left + width;
|
|
||||||
const b = top + height;
|
|
||||||
const peek = 32;
|
|
||||||
if (isNaN(r) || r < peek || left > screen.availWidth - peek || width < 100) {
|
|
||||||
delete wndPos.left;
|
|
||||||
delete wndPos.width;
|
|
||||||
}
|
}
|
||||||
if (isNaN(b) || b < peek || top > screen.availHeight - peek || height < 100) {
|
|
||||||
delete wndPos.top;
|
|
||||||
delete wndPos.height;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
const tab = await openURL({
|
|
||||||
url: `${u}`,
|
|
||||||
currentWindow: null,
|
|
||||||
newWindow: wnd && Object.assign(wndBase, !ffBug && wndPos),
|
|
||||||
});
|
});
|
||||||
if (ffBug) await browser.windows.update(tab.windowId, wndPos);
|
return KEEP_CHANNEL_OPEN;
|
||||||
return tab;
|
|
||||||
},
|
},
|
||||||
|
|
||||||
/** @returns {Promise<chrome.tabs.Tab>} */
|
optionsCustomizeHotkeys() {
|
||||||
async openManage({options = false, search, searchMode} = {}) {
|
return browser.runtime.openOptionsPage()
|
||||||
const setUrlParams = url => {
|
.then(() => new Promise(resolve => setTimeout(resolve, 100)))
|
||||||
const u = new URL(url);
|
.then(() => sendMessage({method: 'optionsCustomizeHotkeys'}));
|
||||||
if (search) u.searchParams.set('search', search);
|
|
||||||
if (searchMode) u.searchParams.set('searchMode', searchMode);
|
|
||||||
if (options) u.hash = '#stylus-options';
|
|
||||||
return u.href;
|
|
||||||
};
|
|
||||||
const base = chrome.runtime.getURL('manage.html');
|
|
||||||
const url = setUrlParams(base);
|
|
||||||
const tabs = await browser.tabs.query({url: base + '*'});
|
|
||||||
const same = tabs.find(t => t.url === url);
|
|
||||||
let tab = same || tabs[0];
|
|
||||||
if (!tab) {
|
|
||||||
API.prefsDb.get('badFavs'); // prime the cache to avoid flicker/delay when opening the page
|
|
||||||
tab = await openURL({url, newTab: true});
|
|
||||||
} else if (!same) {
|
|
||||||
msg.sendTab(tab.id, {method: 'pushState', url: setUrlParams(tab.url)});
|
|
||||||
}
|
|
||||||
return activateTab(tab); // activateTab unminimizes the window
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Same as openURL, the only extra prop in `opts` is `message` - it'll be sent
|
|
||||||
* when the tab is ready, which is needed in the popup, otherwise another
|
|
||||||
* extension could force the tab to open in foreground thus auto-closing the
|
|
||||||
* popup (in Chrome at least) and preventing the sendMessage code from running
|
|
||||||
* @returns {Promise<chrome.tabs.Tab>}
|
|
||||||
*/
|
|
||||||
async openURL(opts) {
|
|
||||||
const tab = await openURL(opts);
|
|
||||||
if (opts.message) {
|
|
||||||
await onTabReady(tab);
|
|
||||||
await msg.sendTab(tab.id, opts.message);
|
|
||||||
}
|
|
||||||
return tab;
|
|
||||||
function onTabReady(tab) {
|
|
||||||
return new Promise((resolve, reject) =>
|
|
||||||
setTimeout(function ping(numTries = 10, delay = 100) {
|
|
||||||
msg.sendTab(tab.id, {method: 'ping'})
|
|
||||||
.catch(() => false)
|
|
||||||
.then(pong => pong
|
|
||||||
? resolve(tab)
|
|
||||||
: numTries && setTimeout(ping, delay, numTries - 1, delay * 1.5) ||
|
|
||||||
reject('timeout'));
|
|
||||||
}));
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
prefs: {
|
|
||||||
getValues: () => prefs.__values, // will be deepCopy'd by apiHandler
|
|
||||||
set: prefs.set,
|
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
//#endregion
|
// eslint-disable-next-line no-var
|
||||||
//#region Events
|
var browserCommands, contextMenus;
|
||||||
|
|
||||||
const browserCommands = {
|
// *************************************************************************
|
||||||
openManage: () => API.openManage(),
|
// register all listeners
|
||||||
openOptions: () => API.openManage({options: true}),
|
chrome.runtime.onMessage.addListener(onRuntimeMessage);
|
||||||
reload: () => chrome.runtime.reload(),
|
|
||||||
|
if (FIREFOX) {
|
||||||
|
// see notes in apply.js for getStylesFallback
|
||||||
|
const MSG_GET_STYLES = 'getStyles:';
|
||||||
|
const MSG_GET_STYLES_LEN = MSG_GET_STYLES.length;
|
||||||
|
chrome.runtime.onConnect.addListener(port => {
|
||||||
|
if (!port.name.startsWith(MSG_GET_STYLES)) return;
|
||||||
|
const tabId = port.sender.tab.id;
|
||||||
|
const frameId = port.sender.frameId;
|
||||||
|
const options = tryJSONparse(port.name.slice(MSG_GET_STYLES_LEN));
|
||||||
|
port.disconnect();
|
||||||
|
getStyles(options).then(styles => {
|
||||||
|
if (!styles.length) return;
|
||||||
|
chrome.tabs.executeScript(tabId, {
|
||||||
|
code: `
|
||||||
|
applyOnMessage({
|
||||||
|
method: 'styleApply',
|
||||||
|
styles: ${JSON.stringify(styles)},
|
||||||
|
})
|
||||||
|
`,
|
||||||
|
runAt: 'document_start',
|
||||||
|
frameId,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
const listener =
|
||||||
|
URLS.chromeProtectsNTP
|
||||||
|
? webNavigationListenerChrome
|
||||||
|
: webNavigationListener;
|
||||||
|
|
||||||
|
chrome.webNavigation.onBeforeNavigate.addListener(data =>
|
||||||
|
listener(null, data));
|
||||||
|
|
||||||
|
chrome.webNavigation.onCommitted.addListener(data =>
|
||||||
|
listener('styleApply', data));
|
||||||
|
|
||||||
|
chrome.webNavigation.onHistoryStateUpdated.addListener(data =>
|
||||||
|
listener('styleReplaceAll', data));
|
||||||
|
|
||||||
|
chrome.webNavigation.onReferenceFragmentUpdated.addListener(data =>
|
||||||
|
listener('styleReplaceAll', data));
|
||||||
|
|
||||||
|
if (FIREFOX) {
|
||||||
|
// FF applies page CSP even to content scripts, https://bugzil.la/1267027
|
||||||
|
chrome.webNavigation.onCommitted.addListener(webNavUsercssInstallerFF, {
|
||||||
|
url: [
|
||||||
|
{hostSuffix: '.githubusercontent.com', urlSuffix: '.user.css'},
|
||||||
|
{hostSuffix: '.githubusercontent.com', urlSuffix: '.user.styl'},
|
||||||
|
]
|
||||||
|
});
|
||||||
|
// FF misses some about:blank iframes so we inject our content script explicitly
|
||||||
|
chrome.webNavigation.onDOMContentLoaded.addListener(webNavIframeHelperFF, {
|
||||||
|
url: [
|
||||||
|
{urlEquals: 'about:blank'},
|
||||||
|
]
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (chrome.contextMenus) {
|
||||||
|
chrome.contextMenus.onClicked.addListener((info, tab) =>
|
||||||
|
contextMenus[info.menuItemId].click(info, tab));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (chrome.commands) {
|
||||||
|
// Not available in Firefox - https://bugzilla.mozilla.org/show_bug.cgi?id=1240350
|
||||||
|
chrome.commands.onCommand.addListener(command => browserCommands[command]());
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!chrome.browserAction ||
|
||||||
|
!['setIcon', 'setBadgeBackgroundColor', 'setBadgeText'].every(name => chrome.browserAction[name])) {
|
||||||
|
window.updateIcon = () => {};
|
||||||
|
}
|
||||||
|
|
||||||
|
const tabIcons = new Map();
|
||||||
|
chrome.tabs.onRemoved.addListener(tabId => tabIcons.delete(tabId));
|
||||||
|
chrome.tabs.onReplaced.addListener((added, removed) => tabIcons.delete(removed));
|
||||||
|
|
||||||
|
// *************************************************************************
|
||||||
|
// set the default icon displayed after a tab is created until webNavigation kicks in
|
||||||
|
prefs.subscribe(['iconset'], () =>
|
||||||
|
updateIcon({
|
||||||
|
tab: {id: undefined},
|
||||||
|
styles: {},
|
||||||
|
}));
|
||||||
|
|
||||||
|
// *************************************************************************
|
||||||
|
chrome.runtime.onInstalled.addListener(({reason}) => {
|
||||||
|
if (reason !== 'update') return;
|
||||||
|
// translations may change
|
||||||
|
localStorage.L10N = JSON.stringify({
|
||||||
|
browserUIlanguage: chrome.i18n.getUILanguage(),
|
||||||
|
});
|
||||||
|
// themes may change
|
||||||
|
delete localStorage.codeMirrorThemes;
|
||||||
|
});
|
||||||
|
|
||||||
|
// *************************************************************************
|
||||||
|
// browser commands
|
||||||
|
browserCommands = {
|
||||||
|
openManage() {
|
||||||
|
openURL({url: 'manage.html'});
|
||||||
|
},
|
||||||
styleDisableAll(info) {
|
styleDisableAll(info) {
|
||||||
prefs.set('disableAll', info ? info.checked : !prefs.get('disableAll'));
|
prefs.set('disableAll', info ? info.checked : !prefs.get('disableAll'));
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
if (chrome.commands) {
|
// *************************************************************************
|
||||||
chrome.commands.onCommand.addListener(id => browserCommands[id]());
|
// context menus
|
||||||
|
contextMenus = {
|
||||||
|
'show-badge': {
|
||||||
|
title: 'menuShowBadge',
|
||||||
|
click: info => prefs.set(info.menuItemId, info.checked),
|
||||||
|
},
|
||||||
|
'disableAll': {
|
||||||
|
title: 'disableAllStyles',
|
||||||
|
click: browserCommands.styleDisableAll,
|
||||||
|
},
|
||||||
|
'open-manager': {
|
||||||
|
title: 'openStylesManager',
|
||||||
|
click: browserCommands.openManage,
|
||||||
|
},
|
||||||
|
'editor.contextDelete': {
|
||||||
|
presentIf: () => !FIREFOX && prefs.get('editor.contextDelete'),
|
||||||
|
title: 'editDeleteText',
|
||||||
|
type: 'normal',
|
||||||
|
contexts: ['editable'],
|
||||||
|
documentUrlPatterns: [URLS.ownOrigin + 'edit*'],
|
||||||
|
click: (info, tab) => {
|
||||||
|
sendMessage({tabId: tab.id, method: 'editDeleteText'});
|
||||||
|
},
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
if (chrome.contextMenus) {
|
||||||
|
const createContextMenus = ids => {
|
||||||
|
for (const id of ids) {
|
||||||
|
let item = contextMenus[id];
|
||||||
|
if (item.presentIf && !item.presentIf()) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
item = Object.assign({id}, item);
|
||||||
|
delete item.presentIf;
|
||||||
|
const prefValue = prefs.readOnlyValues[id];
|
||||||
|
item.title = chrome.i18n.getMessage(item.title);
|
||||||
|
if (!item.type && typeof prefValue === 'boolean') {
|
||||||
|
item.type = 'checkbox';
|
||||||
|
item.checked = prefValue;
|
||||||
|
}
|
||||||
|
if (!item.contexts) {
|
||||||
|
item.contexts = ['browser_action'];
|
||||||
|
}
|
||||||
|
delete item.click;
|
||||||
|
chrome.contextMenus.create(item, ignoreChromeError);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// circumvent the bug with disabling check marks in Chrome 62-64
|
||||||
|
const toggleCheckmark = CHROME >= 3172 && CHROME <= 3288 ?
|
||||||
|
(id => chrome.contextMenus.remove(id, () => createContextMenus([id]) + ignoreChromeError())) :
|
||||||
|
((id, checked) => chrome.contextMenus.update(id, {checked}, ignoreChromeError));
|
||||||
|
|
||||||
|
const togglePresence = (id, checked) => {
|
||||||
|
if (checked) {
|
||||||
|
createContextMenus([id]);
|
||||||
|
} else {
|
||||||
|
chrome.contextMenus.remove(id, ignoreChromeError);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const keys = Object.keys(contextMenus);
|
||||||
|
prefs.subscribe(keys.filter(id => typeof prefs.readOnlyValues[id] === 'boolean'), toggleCheckmark);
|
||||||
|
prefs.subscribe(keys.filter(id => contextMenus[id].presentIf), togglePresence);
|
||||||
|
createContextMenus(keys);
|
||||||
}
|
}
|
||||||
|
|
||||||
chrome.runtime.onInstalled.addListener(({reason, previousVersion}) => {
|
// *************************************************************************
|
||||||
if (reason === 'install') {
|
// [re]inject content scripts
|
||||||
if (UA.mobile) prefs.set('manage.newUI', false);
|
window.addEventListener('storageReady', function _() {
|
||||||
if (UA.windows) prefs.set('editor.keyMap', 'sublime');
|
window.removeEventListener('storageReady', _);
|
||||||
|
|
||||||
|
updateIcon({
|
||||||
|
tab: {id: undefined},
|
||||||
|
styles: {},
|
||||||
|
});
|
||||||
|
|
||||||
|
// Firefox injects content script automatically
|
||||||
|
if (FIREFOX) return;
|
||||||
|
|
||||||
|
const NTP = 'chrome://newtab/';
|
||||||
|
const ALL_URLS = '<all_urls>';
|
||||||
|
const contentScripts = chrome.runtime.getManifest().content_scripts;
|
||||||
|
// expand * as .*?
|
||||||
|
const wildcardAsRegExp = (s, flags) => new RegExp(
|
||||||
|
s.replace(/[{}()[\]/\\.+?^$:=!|]/g, '\\$&')
|
||||||
|
.replace(/\*/g, '.*?'), flags);
|
||||||
|
for (const cs of contentScripts) {
|
||||||
|
cs.matches = cs.matches.map(m => (
|
||||||
|
m === ALL_URLS ? m : wildcardAsRegExp(m)
|
||||||
|
));
|
||||||
}
|
}
|
||||||
// TODO: remove this before 1.5.23 as it's only for a few users who installed git 26b75e77
|
|
||||||
if (reason === 'update' && previousVersion === '1.5.22') {
|
const injectCS = (cs, tabId) => {
|
||||||
for (const dbName of ['drafts', prefs.STORAGE_KEY]) {
|
ignoreChromeError();
|
||||||
try {
|
chrome.tabs.executeScript(tabId, {
|
||||||
indexedDB.open(dbName).onsuccess = async e => {
|
file: cs.js[0],
|
||||||
const idb = /** @type IDBDatabase */ e.target.result;
|
runAt: cs.run_at,
|
||||||
const ta = idb.objectStoreNames[0] === 'data' && idb.transaction(['data']);
|
allFrames: cs.all_frames,
|
||||||
if (ta && ta.objectStore('data').autoIncrement) {
|
matchAboutBlank: cs.match_about_blank,
|
||||||
ta.abort();
|
}, ignoreChromeError);
|
||||||
idb.close();
|
};
|
||||||
await new Promise(setTimeout);
|
|
||||||
indexedDB.deleteDatabase(dbName);
|
const pingCS = (cs, {id, url}) => {
|
||||||
|
const maybeInject = pong => !pong && injectCS(cs, id);
|
||||||
|
cs.matches.some(match => {
|
||||||
|
if ((match === ALL_URLS || url.match(match)) &&
|
||||||
|
(!url.startsWith('chrome') || url === NTP)) {
|
||||||
|
sendMessage({method: 'ping', tabId: id}, maybeInject);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
queryTabs().then(tabs =>
|
||||||
|
tabs.forEach(tab => {
|
||||||
|
// skip lazy-loaded aka unloaded tabs that seem to start loading on message in FF
|
||||||
|
if (tab.width) {
|
||||||
|
contentScripts.forEach(cs =>
|
||||||
|
setTimeout(pingCS, 0, cs, tab));
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
});
|
||||||
|
|
||||||
|
// *************************************************************************
|
||||||
|
{
|
||||||
|
const getStylesForFrame = (msg, sender) => {
|
||||||
|
const stylesTask = getStyles(msg);
|
||||||
|
if (!sender || !sender.frameId) return stylesTask;
|
||||||
|
return Promise.all([
|
||||||
|
stylesTask,
|
||||||
|
getTab(sender.tab.id),
|
||||||
|
]).then(([styles, tab]) => {
|
||||||
|
if (tab) styles.exposeIframes = tab.url.replace(/(\/\/[^/]*).*/, '$1');
|
||||||
|
return styles;
|
||||||
|
});
|
||||||
|
};
|
||||||
|
const updateAPI = (_, enabled) => {
|
||||||
|
window.API_METHODS.getStylesForFrame = enabled ? getStylesForFrame : getStyles;
|
||||||
|
};
|
||||||
|
prefs.subscribe(['exposeIframes'], updateAPI);
|
||||||
|
updateAPI(null, prefs.readOnlyValues.exposeIframes);
|
||||||
|
}
|
||||||
|
|
||||||
|
// *************************************************************************
|
||||||
|
|
||||||
|
function webNavigationListener(method, {url, tabId, frameId}) {
|
||||||
|
Promise.all([
|
||||||
|
getStyles({matchUrl: url, asHash: true}),
|
||||||
|
frameId && prefs.readOnlyValues.exposeIframes && getTab(tabId),
|
||||||
|
]).then(([styles, tab]) => {
|
||||||
|
if (method && URLS.supported(url) && tabId >= 0) {
|
||||||
|
if (method === 'styleApply') {
|
||||||
|
handleCssTransitionBug({tabId, frameId, url, styles});
|
||||||
|
}
|
||||||
|
if (tab) styles.exposeIframes = tab.url.replace(/(\/\/[^/]*).*/, '$1');
|
||||||
|
sendMessage({
|
||||||
|
tabId,
|
||||||
|
frameId,
|
||||||
|
method,
|
||||||
|
// ping own page so it retrieves the styles directly
|
||||||
|
styles: url.startsWith(URLS.ownOrigin) ? 'DIY' : styles,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
// main page frame id is 0
|
||||||
|
if (frameId === 0) {
|
||||||
|
tabIcons.delete(tabId);
|
||||||
|
updateIcon({tab: {id: tabId, url}, styles});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
function webNavigationListenerChrome(method, data) {
|
||||||
|
// Chrome 61.0.3161+ doesn't run content scripts on NTP
|
||||||
|
if (
|
||||||
|
!data.url.startsWith('https://www.google.') ||
|
||||||
|
!data.url.includes('/_/chrome/newtab?')
|
||||||
|
) {
|
||||||
|
webNavigationListener(method, data);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
getTab(data.tabId).then(tab => {
|
||||||
|
if (tab.url === 'chrome://newtab/') {
|
||||||
|
data.url = tab.url;
|
||||||
|
}
|
||||||
|
webNavigationListener(method, data);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
function webNavUsercssInstallerFF(data) {
|
||||||
|
const {tabId} = data;
|
||||||
|
Promise.all([
|
||||||
|
sendMessage({tabId, method: 'ping'}),
|
||||||
|
// we need tab index to open the installer next to the original one
|
||||||
|
// and also to skip the double-invocation in FF which assigns tab url later
|
||||||
|
getTab(tabId),
|
||||||
|
]).then(([pong, tab]) => {
|
||||||
|
if (pong !== true && tab.url !== 'about:blank') {
|
||||||
|
window.API_METHODS.installUsercss({direct: true}, {tab});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
function webNavIframeHelperFF({tabId, frameId}) {
|
||||||
|
if (!frameId) return;
|
||||||
|
sendMessage({method: 'ping', tabId, frameId}, pong => {
|
||||||
|
ignoreChromeError();
|
||||||
|
if (pong) return;
|
||||||
|
chrome.tabs.executeScript(tabId, {
|
||||||
|
frameId,
|
||||||
|
file: '/content/apply.js',
|
||||||
|
matchAboutBlank: true,
|
||||||
|
}, ignoreChromeError);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
function updateIcon({tab, styles}) {
|
||||||
|
if (tab.id < 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (URLS.chromeProtectsNTP && tab.url === 'chrome://newtab/') {
|
||||||
|
styles = {};
|
||||||
|
}
|
||||||
|
if (styles) {
|
||||||
|
stylesReceived(styles);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
getTabRealURL(tab)
|
||||||
|
.then(url => getStyles({matchUrl: url, asHash: true}))
|
||||||
|
.then(stylesReceived);
|
||||||
|
|
||||||
|
function stylesReceived(styles) {
|
||||||
|
const disableAll = 'disableAll' in styles ? styles.disableAll : prefs.get('disableAll');
|
||||||
|
const postfix = disableAll ? 'x' : !styles.length ? 'w' : '';
|
||||||
|
const color = prefs.get(disableAll ? 'badgeDisabled' : 'badgeNormal');
|
||||||
|
const text = prefs.get('show-badge') && styles.length ? String(styles.length) : '';
|
||||||
|
const iconset = ['', 'light/'][prefs.get('iconset')] || '';
|
||||||
|
|
||||||
|
let tabIcon = tabIcons.get(tab.id);
|
||||||
|
if (!tabIcon) tabIcons.set(tab.id, (tabIcon = {}));
|
||||||
|
|
||||||
|
if (tabIcon.iconType !== iconset + postfix) {
|
||||||
|
tabIcon.iconType = iconset + postfix;
|
||||||
|
const sizes = FIREFOX || CHROME >= 2883 && !VIVALDI ? [16, 32] : [19, 38];
|
||||||
|
const usePath = tabIcons.get('usePath');
|
||||||
|
Promise.all(sizes.map(size => {
|
||||||
|
const src = `/images/icon/${iconset}${size}${postfix}.png`;
|
||||||
|
return usePath ? src : tabIcons.get(src) || loadIcon(src);
|
||||||
|
})).then(data => {
|
||||||
|
const imageKey = typeof data[0] === 'string' ? 'path' : 'imageData';
|
||||||
|
const imageData = {};
|
||||||
|
sizes.forEach((size, i) => (imageData[size] = data[i]));
|
||||||
|
chrome.browserAction.setIcon({
|
||||||
|
tabId: tab.id,
|
||||||
|
[imageKey]: imageData,
|
||||||
|
}, ignoreChromeError);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
if (tab.id === undefined) return;
|
||||||
|
|
||||||
|
let defaultIcon = tabIcons.get(undefined);
|
||||||
|
if (!defaultIcon) tabIcons.set(undefined, (defaultIcon = {}));
|
||||||
|
if (defaultIcon.color !== color) {
|
||||||
|
defaultIcon.color = color;
|
||||||
|
chrome.browserAction.setBadgeBackgroundColor({color});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (tabIcon.text === text) return;
|
||||||
|
tabIcon.text = text;
|
||||||
|
try {
|
||||||
|
// Chrome supports the callback since 67.0.3381.0, see https://crbug.com/451320
|
||||||
|
chrome.browserAction.setBadgeText({text, tabId: tab.id}, ignoreChromeError);
|
||||||
|
} catch (e) {
|
||||||
|
setTimeout(() => {
|
||||||
|
getTab(tab.id).then(realTab => {
|
||||||
|
// skip pre-rendered tabs
|
||||||
|
if (realTab.index >= 0) {
|
||||||
|
chrome.browserAction.setBadgeText({text, tabId: tab.id});
|
||||||
}
|
}
|
||||||
};
|
});
|
||||||
} catch (e) {}
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
|
||||||
|
|
||||||
msg.on((msg, sender) => {
|
function loadIcon(src, resolve) {
|
||||||
if (msg.method === 'invokeAPI') {
|
if (!resolve) return new Promise(resolve => loadIcon(src, resolve));
|
||||||
let res = msg.path.reduce((res, name) => res && res[name], API);
|
const canvas = document.createElement('canvas');
|
||||||
if (!res) throw new Error(`Unknown API.${msg.path.join('.')}`);
|
const ctx = canvas.getContext('2d');
|
||||||
res = res.apply({msg, sender}, msg.args);
|
const img = new Image();
|
||||||
return res === undefined ? null : res;
|
img.src = src;
|
||||||
|
img.onload = () => {
|
||||||
|
const w = canvas.width = img.width;
|
||||||
|
const h = canvas.height = img.height;
|
||||||
|
ctx.clearRect(0, 0, w, h);
|
||||||
|
ctx.drawImage(img, 0, 0, w, h);
|
||||||
|
const data = ctx.getImageData(0, 0, w, h);
|
||||||
|
// Firefox breaks Canvas when privacy.resistFingerprinting=true, https://bugzil.la/1412961
|
||||||
|
let usePath = tabIcons.get('usePath');
|
||||||
|
if (usePath === undefined) {
|
||||||
|
usePath = data.data.every(b => b === 255);
|
||||||
|
tabIcons.set('usePath', usePath);
|
||||||
|
}
|
||||||
|
if (usePath) {
|
||||||
|
resolve(src);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
tabIcons.set(src, data);
|
||||||
|
resolve(data);
|
||||||
|
};
|
||||||
}
|
}
|
||||||
});
|
}
|
||||||
|
|
||||||
//#endregion
|
|
||||||
|
|
||||||
Promise.all([
|
function onRuntimeMessage(msg, sender, sendResponse) {
|
||||||
browser.extension.isAllowedFileSchemeAccess()
|
const fn = window.API_METHODS[msg.method];
|
||||||
.then(res => API.data.set('hasFileAccess', res)),
|
if (!fn) return;
|
||||||
bgReady.styles,
|
|
||||||
/* These are loaded conditionally.
|
// wrap 'Error' object instance as {__ERROR__: message},
|
||||||
Each item uses `require` individually so IDE can jump to the source and track usage. */
|
// which will be unwrapped by sendMessage,
|
||||||
FIREFOX &&
|
// and prevent exceptions on sending to a closed tab
|
||||||
require(['/background/style-via-api']),
|
const respond = data =>
|
||||||
FIREFOX && ((browser.commands || {}).update) &&
|
tryCatch(sendResponse,
|
||||||
require(['/background/browser-cmd-hotkeys']),
|
data instanceof Error ? {__ERROR__: data.message} : data);
|
||||||
!FIREFOX &&
|
|
||||||
require(['/background/content-scripts']),
|
const result = fn(msg, sender, respond);
|
||||||
chrome.contextMenus &&
|
if (result instanceof Promise) {
|
||||||
require(['/background/context-menus']),
|
result
|
||||||
]).then(() => {
|
.catch(e => ({__ERROR__: e instanceof Error ? e.message : e}))
|
||||||
bgReady._resolveAll();
|
.then(respond);
|
||||||
msg.ready = true;
|
return KEEP_CHANNEL_OPEN;
|
||||||
msg.broadcast({method: 'backgroundReady'});
|
} else if (result === KEEP_CHANNEL_OPEN) {
|
||||||
});
|
return KEEP_CHANNEL_OPEN;
|
||||||
|
} else if (result !== undefined) {
|
||||||
|
respond(result);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -1,22 +0,0 @@
|
||||||
/* global prefs */
|
|
||||||
'use strict';
|
|
||||||
|
|
||||||
/*
|
|
||||||
Registers hotkeys in FF
|
|
||||||
*/
|
|
||||||
|
|
||||||
(() => {
|
|
||||||
const hotkeyPrefs = prefs.knownKeys.filter(k => k.startsWith('hotkey.'));
|
|
||||||
prefs.subscribe(hotkeyPrefs, updateHotkey, {runNow: true});
|
|
||||||
|
|
||||||
async function updateHotkey(name, value) {
|
|
||||||
try {
|
|
||||||
name = name.split('.')[1];
|
|
||||||
if (value.trim()) {
|
|
||||||
await browser.commands.update({name, shortcut: value});
|
|
||||||
} else {
|
|
||||||
await browser.commands.reset(name);
|
|
||||||
}
|
|
||||||
} catch (e) {}
|
|
||||||
}
|
|
||||||
})();
|
|
|
@ -1,91 +0,0 @@
|
||||||
/* global prefs */
|
|
||||||
/* exported colorScheme */
|
|
||||||
|
|
||||||
'use strict';
|
|
||||||
|
|
||||||
const colorScheme = (() => {
|
|
||||||
const changeListeners = new Set();
|
|
||||||
const kSTATE = 'schemeSwitcher.enabled';
|
|
||||||
const kSTART = 'schemeSwitcher.nightStart';
|
|
||||||
const kEND = 'schemeSwitcher.nightEnd';
|
|
||||||
const SCHEMES = ['dark', 'light'];
|
|
||||||
const isDark = {
|
|
||||||
never: null,
|
|
||||||
dark: true,
|
|
||||||
light: false,
|
|
||||||
system: false,
|
|
||||||
time: false,
|
|
||||||
};
|
|
||||||
let isDarkNow = false;
|
|
||||||
|
|
||||||
prefs.subscribe(kSTATE, () => update());
|
|
||||||
prefs.subscribe([kSTART, kEND], (key, value) => {
|
|
||||||
updateTimePreferDark();
|
|
||||||
createAlarm(key, value);
|
|
||||||
}, {runNow: true});
|
|
||||||
chrome.alarms.onAlarm.addListener(({name}) => {
|
|
||||||
if (name === kSTART || name === kEND) {
|
|
||||||
updateTimePreferDark();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
return {
|
|
||||||
SCHEMES,
|
|
||||||
onChange(listener) {
|
|
||||||
changeListeners.add(listener);
|
|
||||||
},
|
|
||||||
isDark: () => isDarkNow,
|
|
||||||
/** @param {StyleObj} _ */
|
|
||||||
shouldIncludeStyle({preferScheme: ps}) {
|
|
||||||
return prefs.get(kSTATE) === 'never' ||
|
|
||||||
!SCHEMES.includes(ps) ||
|
|
||||||
isDarkNow === (ps === 'dark');
|
|
||||||
},
|
|
||||||
updateSystemPreferDark(val) {
|
|
||||||
update('system', val);
|
|
||||||
return true;
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
function calcTime(key) {
|
|
||||||
const [h, m] = prefs.get(key).split(':');
|
|
||||||
return (h * 3600 + m * 60) * 1000;
|
|
||||||
}
|
|
||||||
|
|
||||||
function createAlarm(key, value) {
|
|
||||||
const date = new Date();
|
|
||||||
const [h, m] = value.split(':');
|
|
||||||
date.setHours(h, m, 0, 0);
|
|
||||||
if (date.getTime() < Date.now()) {
|
|
||||||
date.setDate(date.getDate() + 1);
|
|
||||||
}
|
|
||||||
chrome.alarms.create(key, {
|
|
||||||
when: date.getTime(),
|
|
||||||
periodInMinutes: 24 * 60,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function updateTimePreferDark() {
|
|
||||||
const now = Date.now() - new Date().setHours(0, 0, 0, 0);
|
|
||||||
const start = calcTime(kSTART);
|
|
||||||
const end = calcTime(kEND);
|
|
||||||
const val = start > end ?
|
|
||||||
now >= start || now < end :
|
|
||||||
now >= start && now < end;
|
|
||||||
update('time', val);
|
|
||||||
}
|
|
||||||
|
|
||||||
function update(type, val) {
|
|
||||||
if (type) {
|
|
||||||
if (isDark[type] === val) return;
|
|
||||||
isDark[type] = val;
|
|
||||||
}
|
|
||||||
val = isDark[prefs.get(kSTATE)];
|
|
||||||
if (isDarkNow !== val) {
|
|
||||||
isDarkNow = val;
|
|
||||||
for (const listener of changeListeners) {
|
|
||||||
listener(isDarkNow);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})();
|
|
|
@ -1,92 +0,0 @@
|
||||||
/* global API */// msg.js
|
|
||||||
'use strict';
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Common stuff that's loaded first so it's immediately available to all background scripts
|
|
||||||
*/
|
|
||||||
|
|
||||||
window.bgReady = {}; /* global bgReady */
|
|
||||||
bgReady.styles = new Promise(r => (bgReady._resolveStyles = r));
|
|
||||||
bgReady.all = new Promise(r => (bgReady._resolveAll = r));
|
|
||||||
|
|
||||||
const uuidIndex = Object.assign(new Map(), {
|
|
||||||
custom: {},
|
|
||||||
/** `obj` must have a unique `id`, a UUIDv4 `_id`, and Date.now() for `_rev`. */
|
|
||||||
addCustomId(obj, {get = () => obj, set}) {
|
|
||||||
Object.defineProperty(uuidIndex.custom, obj.id, {get, set});
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
/* exported addAPI */
|
|
||||||
function addAPI(methods) {
|
|
||||||
for (const [key, val] of Object.entries(methods)) {
|
|
||||||
const old = API[key];
|
|
||||||
if (old && Object.prototype.toString.call(old) === '[object Object]') {
|
|
||||||
Object.assign(old, val);
|
|
||||||
} else {
|
|
||||||
API[key] = val;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/* exported createCache */
|
|
||||||
/** Creates a FIFO limit-size map. */
|
|
||||||
function createCache({size = 1000, onDeleted} = {}) {
|
|
||||||
const map = new Map();
|
|
||||||
const buffer = Array(size);
|
|
||||||
let index = 0;
|
|
||||||
let lastIndex = 0;
|
|
||||||
return {
|
|
||||||
get(id) {
|
|
||||||
const item = map.get(id);
|
|
||||||
return item && item.data;
|
|
||||||
},
|
|
||||||
set(id, data) {
|
|
||||||
if (map.size === size) {
|
|
||||||
// full
|
|
||||||
map.delete(buffer[lastIndex].id);
|
|
||||||
if (onDeleted) {
|
|
||||||
onDeleted(buffer[lastIndex].id, buffer[lastIndex].data);
|
|
||||||
}
|
|
||||||
lastIndex = (lastIndex + 1) % size;
|
|
||||||
}
|
|
||||||
const item = {id, data, index};
|
|
||||||
map.set(id, item);
|
|
||||||
buffer[index] = item;
|
|
||||||
index = (index + 1) % size;
|
|
||||||
},
|
|
||||||
delete(id) {
|
|
||||||
const item = map.get(id);
|
|
||||||
if (!item) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
map.delete(item.id);
|
|
||||||
const lastItem = buffer[lastIndex];
|
|
||||||
lastItem.index = item.index;
|
|
||||||
buffer[item.index] = lastItem;
|
|
||||||
lastIndex = (lastIndex + 1) % size;
|
|
||||||
if (onDeleted) {
|
|
||||||
onDeleted(item.id, item.data);
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
},
|
|
||||||
clear() {
|
|
||||||
map.clear();
|
|
||||||
index = lastIndex = 0;
|
|
||||||
},
|
|
||||||
has: id => map.has(id),
|
|
||||||
*entries() {
|
|
||||||
for (const [id, item] of map) {
|
|
||||||
yield [id, item.data];
|
|
||||||
}
|
|
||||||
},
|
|
||||||
*values() {
|
|
||||||
for (const item of map.values()) {
|
|
||||||
yield item.data;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
get size() {
|
|
||||||
return map.size;
|
|
||||||
},
|
|
||||||
};
|
|
||||||
}
|
|
|
@ -1,117 +0,0 @@
|
||||||
/* global bgReady */// common.js
|
|
||||||
/* global msg */
|
|
||||||
/* global URLS ignoreChromeError */// toolbox.js
|
|
||||||
'use strict';
|
|
||||||
|
|
||||||
/*
|
|
||||||
Reinject content scripts when the extension is reloaded/updated.
|
|
||||||
Not used in Firefox as it reinjects automatically.
|
|
||||||
*/
|
|
||||||
|
|
||||||
bgReady.all.then(() => {
|
|
||||||
const NTP = 'chrome://newtab/';
|
|
||||||
const ALL_URLS = '<all_urls>';
|
|
||||||
const SCRIPTS = chrome.runtime.getManifest().content_scripts;
|
|
||||||
// expand * as .*?
|
|
||||||
const wildcardAsRegExp = (s, flags) => new RegExp(
|
|
||||||
s.replace(/[{}()[\]/\\.+?^$:=!|]/g, '\\$&')
|
|
||||||
.replace(/\*/g, '.*?'), flags);
|
|
||||||
for (const cs of SCRIPTS) {
|
|
||||||
cs.matches = cs.matches.map(m => (
|
|
||||||
m === ALL_URLS ? m : wildcardAsRegExp(m)
|
|
||||||
));
|
|
||||||
}
|
|
||||||
const busyTabs = new Set();
|
|
||||||
let busyTabsTimer;
|
|
||||||
|
|
||||||
setTimeout(injectToAllTabs);
|
|
||||||
|
|
||||||
function injectToTab({url, tabId, frameId = null}) {
|
|
||||||
for (const script of SCRIPTS) {
|
|
||||||
if (
|
|
||||||
script.matches.some(match =>
|
|
||||||
(match === ALL_URLS || url.match(match)) &&
|
|
||||||
(!url.startsWith('chrome') || url === NTP))
|
|
||||||
) {
|
|
||||||
doInject(tabId, frameId, script);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function doInject(tabId, frameId, script) {
|
|
||||||
const options = frameId === null ? {} : {frameId};
|
|
||||||
msg.sendTab(tabId, {method: 'ping'}, options)
|
|
||||||
.catch(() => false)
|
|
||||||
.then(pong => {
|
|
||||||
if (pong) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const options = {
|
|
||||||
runAt: script.run_at,
|
|
||||||
allFrames: script.all_frames,
|
|
||||||
matchAboutBlank: script.match_about_blank,
|
|
||||||
};
|
|
||||||
if (frameId !== null) {
|
|
||||||
options.allFrames = false;
|
|
||||||
options.frameId = frameId;
|
|
||||||
}
|
|
||||||
for (const file of script.js) {
|
|
||||||
chrome.tabs.executeScript(tabId, Object.assign({file}, options), ignoreChromeError);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function injectToAllTabs() {
|
|
||||||
return browser.tabs.query({}).then(tabs => {
|
|
||||||
for (const tab of tabs) {
|
|
||||||
// skip unloaded/discarded/chrome tabs
|
|
||||||
if (!tab.width || tab.discarded || !URLS.supported(tab.pendingUrl || tab.url)) continue;
|
|
||||||
// our content scripts may still be pending injection at browser start so it's too early to ping them
|
|
||||||
if (tab.status === 'loading') {
|
|
||||||
trackBusyTab(tab.id, true);
|
|
||||||
} else {
|
|
||||||
injectToTab({
|
|
||||||
url: tab.pendingUrl || tab.url,
|
|
||||||
tabId: tab.id,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function toggleBusyTabListeners(state) {
|
|
||||||
const toggle = state ? 'addListener' : 'removeListener';
|
|
||||||
chrome.webNavigation.onCompleted[toggle](onBusyTabUpdated);
|
|
||||||
chrome.webNavigation.onErrorOccurred[toggle](onBusyTabUpdated);
|
|
||||||
chrome.webNavigation.onTabReplaced[toggle](onBusyTabReplaced);
|
|
||||||
chrome.tabs.onRemoved[toggle](onBusyTabRemoved);
|
|
||||||
if (state) {
|
|
||||||
busyTabsTimer = setTimeout(toggleBusyTabListeners, 15e3, false);
|
|
||||||
} else {
|
|
||||||
clearTimeout(busyTabsTimer);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function trackBusyTab(tabId, state) {
|
|
||||||
busyTabs[state ? 'add' : 'delete'](tabId);
|
|
||||||
if (state && busyTabs.size === 1) toggleBusyTabListeners(true);
|
|
||||||
if (!state && !busyTabs.size) toggleBusyTabListeners(false);
|
|
||||||
}
|
|
||||||
|
|
||||||
function onBusyTabUpdated({error, frameId, tabId, url}) {
|
|
||||||
if (!frameId && busyTabs.has(tabId)) {
|
|
||||||
trackBusyTab(tabId, false);
|
|
||||||
if (url && !error) {
|
|
||||||
injectToTab({tabId, url});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function onBusyTabReplaced({replacedTabId}) {
|
|
||||||
trackBusyTab(replacedTabId, false);
|
|
||||||
}
|
|
||||||
|
|
||||||
function onBusyTabRemoved(tabId) {
|
|
||||||
trackBusyTab(tabId, false);
|
|
||||||
}
|
|
||||||
});
|
|
|
@ -1,87 +0,0 @@
|
||||||
/* global browserCommands */// background.js
|
|
||||||
/* global msg */
|
|
||||||
/* global prefs */
|
|
||||||
/* global CHROME URLS ignoreChromeError */// toolbox.js
|
|
||||||
'use strict';
|
|
||||||
|
|
||||||
chrome.management.getSelf(ext => {
|
|
||||||
const contextMenus = Object.assign({
|
|
||||||
'show-badge': {
|
|
||||||
title: 'menuShowBadge',
|
|
||||||
click: togglePref,
|
|
||||||
},
|
|
||||||
'disableAll': {
|
|
||||||
title: 'disableAllStyles',
|
|
||||||
click: browserCommands.styleDisableAll,
|
|
||||||
},
|
|
||||||
'open-manager': {
|
|
||||||
title: 'optionsOpenManager',
|
|
||||||
click: browserCommands.openManage,
|
|
||||||
},
|
|
||||||
'open-options': {
|
|
||||||
title: 'openOptions',
|
|
||||||
click: browserCommands.openOptions,
|
|
||||||
},
|
|
||||||
}, ext.installType === 'development' && {
|
|
||||||
'reload': {
|
|
||||||
title: 'reload',
|
|
||||||
click: browserCommands.reload,
|
|
||||||
},
|
|
||||||
}, CHROME && {
|
|
||||||
'editor.contextDelete': {
|
|
||||||
title: 'editDeleteText',
|
|
||||||
type: 'normal',
|
|
||||||
contexts: ['editable'],
|
|
||||||
documentUrlPatterns: [URLS.ownOrigin + '*'],
|
|
||||||
click: (info, tab) => {
|
|
||||||
msg.sendTab(tab.id, {method: 'editDeleteText'}, undefined, 'extension')
|
|
||||||
.catch(msg.ignoreError);
|
|
||||||
},
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
createContextMenus(Object.keys(contextMenus));
|
|
||||||
chrome.contextMenus.onClicked.addListener((info, tab) =>
|
|
||||||
contextMenus[info.menuItemId].click(info, tab));
|
|
||||||
|
|
||||||
function createContextMenus(ids) {
|
|
||||||
for (const id of ids) {
|
|
||||||
const item = Object.assign({id, contexts: ['browser_action']}, contextMenus[id]);
|
|
||||||
item.title = chrome.i18n.getMessage(item.title);
|
|
||||||
if (typeof prefs.defaults[id] === 'boolean') {
|
|
||||||
if (item.type) {
|
|
||||||
prefs.subscribe(id, togglePresence);
|
|
||||||
} else {
|
|
||||||
item.type = 'checkbox';
|
|
||||||
item.checked = prefs.get(id);
|
|
||||||
prefs.subscribe(id, CHROME >= 62 && CHROME <= 64 ? toggleCheckmarkBugged : toggleCheckmark);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
delete item.click;
|
|
||||||
chrome.contextMenus.create(item, ignoreChromeError);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function toggleCheckmark(id, checked) {
|
|
||||||
chrome.contextMenus.update(id, {checked}, ignoreChromeError);
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Circumvents the bug with disabling check marks in Chrome 62-64 */
|
|
||||||
async function toggleCheckmarkBugged(id) {
|
|
||||||
await browser.contextMenus.remove(id).catch(ignoreChromeError);
|
|
||||||
createContextMenus([id]);
|
|
||||||
}
|
|
||||||
|
|
||||||
/** @param {chrome.contextMenus.OnClickData} info */
|
|
||||||
function togglePref(info) {
|
|
||||||
prefs.set(info.menuItemId, info.checked);
|
|
||||||
}
|
|
||||||
|
|
||||||
function togglePresence(id, checked) {
|
|
||||||
if (checked) {
|
|
||||||
createContextMenus([id]);
|
|
||||||
} else {
|
|
||||||
chrome.contextMenus.remove(id, ignoreChromeError);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
|
@ -1,64 +0,0 @@
|
||||||
/* global chromeLocal */// storage-util.js
|
|
||||||
'use strict';
|
|
||||||
|
|
||||||
/* exported createChromeStorageDB */
|
|
||||||
function createChromeStorageDB(PREFIX) {
|
|
||||||
let INC;
|
|
||||||
const isMain = !PREFIX;
|
|
||||||
if (!PREFIX) PREFIX = 'style-';
|
|
||||||
|
|
||||||
return {
|
|
||||||
|
|
||||||
delete(id) {
|
|
||||||
return chromeLocal.remove(PREFIX + id);
|
|
||||||
},
|
|
||||||
|
|
||||||
get(id) {
|
|
||||||
return chromeLocal.getValue(PREFIX + id);
|
|
||||||
},
|
|
||||||
|
|
||||||
async getAll() {
|
|
||||||
const all = await chromeLocal.get();
|
|
||||||
if (!INC) prepareInc(all);
|
|
||||||
return Object.entries(all)
|
|
||||||
.map(([key, val]) => key.startsWith(PREFIX) &&
|
|
||||||
(!isMain || Number(key.slice(PREFIX.length))) &&
|
|
||||||
val)
|
|
||||||
.filter(Boolean);
|
|
||||||
},
|
|
||||||
|
|
||||||
async put(item) {
|
|
||||||
if (!item.id) {
|
|
||||||
if (!INC) await prepareInc();
|
|
||||||
item.id = INC++;
|
|
||||||
}
|
|
||||||
await chromeLocal.setValue(PREFIX + item.id, item);
|
|
||||||
return item.id;
|
|
||||||
},
|
|
||||||
|
|
||||||
async putMany(items) {
|
|
||||||
const data = {};
|
|
||||||
for (const item of items) {
|
|
||||||
if (!item.id) {
|
|
||||||
if (!INC) await prepareInc();
|
|
||||||
item.id = INC++;
|
|
||||||
}
|
|
||||||
data[PREFIX + item.id] = item;
|
|
||||||
}
|
|
||||||
await chromeLocal.set(data);
|
|
||||||
return items.map(_ => _.id);
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
async function prepareInc(data) {
|
|
||||||
INC = 1;
|
|
||||||
for (const key in data || await chromeLocal.get()) {
|
|
||||||
if (key.startsWith(PREFIX)) {
|
|
||||||
const id = Number(key.slice(PREFIX.length));
|
|
||||||
if (id >= INC) {
|
|
||||||
INC = id + 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
150
background/db.js
150
background/db.js
|
@ -1,150 +0,0 @@
|
||||||
/* global addAPI */// common.js
|
|
||||||
/* global chromeLocal */// storage-util.js
|
|
||||||
/* global cloneError */// worker-util.js
|
|
||||||
/* global deepCopy */// toolbox.js
|
|
||||||
/* global prefs */
|
|
||||||
'use strict';
|
|
||||||
|
|
||||||
/*
|
|
||||||
Initialize a database. There are some problems using IndexedDB in Firefox:
|
|
||||||
https://www.reddit.com/r/firefox/comments/74wttb/note_to_firefox_webextension_developers_who_use/
|
|
||||||
Some of them are fixed in FF59:
|
|
||||||
https://www.reddit.com/r/firefox/comments/7ijuaq/firefox_59_webextensions_can_use_indexeddb_when/
|
|
||||||
*/
|
|
||||||
|
|
||||||
/* exported db */
|
|
||||||
const db = (() => {
|
|
||||||
let exec = async (...args) => (
|
|
||||||
exec = await tryUsingIndexedDB().catch(useChromeStorage)
|
|
||||||
)(...args);
|
|
||||||
const DB = 'stylish';
|
|
||||||
const FALLBACK = 'dbInChromeStorage';
|
|
||||||
const ID_AS_KEY = {[DB]: true};
|
|
||||||
const getStoreName = dbName => dbName === DB ? 'styles' : 'data';
|
|
||||||
const cache = {};
|
|
||||||
const proxies = {};
|
|
||||||
const proxyHandler = {
|
|
||||||
get: ({dbName}, cmd) =>
|
|
||||||
(...args) =>
|
|
||||||
(dbName === DB ? exec : cachedExec)(dbName, cmd, ...args),
|
|
||||||
};
|
|
||||||
/**
|
|
||||||
* @param {string} dbName
|
|
||||||
* @return {IDBObjectStore | {putMany: function(items:?[]):Promise<?[]>}}
|
|
||||||
*/
|
|
||||||
const getProxy = dbName => proxies[dbName] || (
|
|
||||||
(proxies[dbName] = new Proxy({dbName}, proxyHandler))
|
|
||||||
);
|
|
||||||
addAPI(/** @namespace API */ {
|
|
||||||
drafts: getProxy('drafts'),
|
|
||||||
/** Storage for big items that may exceed 8kB limit of chrome.storage.sync.
|
|
||||||
* To make an item syncable register it with uuidIndex.addCustomId. */
|
|
||||||
prefsDb: getProxy(prefs.STORAGE_KEY),
|
|
||||||
});
|
|
||||||
return {
|
|
||||||
styles: getProxy(DB),
|
|
||||||
};
|
|
||||||
|
|
||||||
async function cachedExec(dbName, cmd, a, b) {
|
|
||||||
const hub = cache[dbName] || (cache[dbName] = {});
|
|
||||||
const res = cmd === 'get' && a in hub ? hub[a] : await exec(...arguments);
|
|
||||||
if (cmd === 'get') {
|
|
||||||
hub[a] = deepCopy(res);
|
|
||||||
} else if (cmd === 'put') {
|
|
||||||
hub[ID_AS_KEY[dbName] ? a.id : b] = deepCopy(a);
|
|
||||||
} else if (cmd === 'delete') {
|
|
||||||
delete hub[a];
|
|
||||||
}
|
|
||||||
return res;
|
|
||||||
}
|
|
||||||
|
|
||||||
async function tryUsingIndexedDB() {
|
|
||||||
// we use chrome.storage.local fallback if IndexedDB doesn't save data,
|
|
||||||
// which, once detected on the first run, is remembered in chrome.storage.local
|
|
||||||
// note that accessing indexedDB may throw, https://github.com/openstyles/stylus/issues/615
|
|
||||||
if (typeof indexedDB === 'undefined') {
|
|
||||||
throw new Error('indexedDB is undefined');
|
|
||||||
}
|
|
||||||
switch (await chromeLocal.getValue(FALLBACK)) {
|
|
||||||
case true: throw null;
|
|
||||||
case false: break;
|
|
||||||
default: await testDB();
|
|
||||||
}
|
|
||||||
chromeLocal.setValue(FALLBACK, false);
|
|
||||||
return dbExecIndexedDB;
|
|
||||||
}
|
|
||||||
|
|
||||||
async function testDB() {
|
|
||||||
const id = `${performance.now()}.${Math.random()}.${Date.now()}`;
|
|
||||||
await dbExecIndexedDB(DB, 'put', {id});
|
|
||||||
const e = await dbExecIndexedDB(DB, 'get', id);
|
|
||||||
await dbExecIndexedDB(DB, 'delete', e.id); // throws if `e` or id is null
|
|
||||||
}
|
|
||||||
|
|
||||||
async function useChromeStorage(err) {
|
|
||||||
chromeLocal.setValue(FALLBACK, true);
|
|
||||||
if (err) {
|
|
||||||
chromeLocal.setValue(FALLBACK + 'Reason', cloneError(err));
|
|
||||||
console.warn('Failed to access indexedDB. Switched to storage API.', err);
|
|
||||||
}
|
|
||||||
await require(['/background/db-chrome-storage']); /* global createChromeStorageDB */
|
|
||||||
const BASES = {};
|
|
||||||
return (dbName, method, ...args) => (
|
|
||||||
BASES[dbName] || (
|
|
||||||
BASES[dbName] = createChromeStorageDB(dbName !== DB && `${dbName}-`)
|
|
||||||
)
|
|
||||||
)[method](...args);
|
|
||||||
}
|
|
||||||
|
|
||||||
async function dbExecIndexedDB(dbName, method, ...args) {
|
|
||||||
const mode = method.startsWith('get') ? 'readonly' : 'readwrite';
|
|
||||||
const storeName = getStoreName(dbName);
|
|
||||||
const store = (await open(dbName)).transaction([storeName], mode).objectStore(storeName);
|
|
||||||
const fn = method === 'putMany' ? putMany : storeRequest;
|
|
||||||
return fn(store, method, ...args);
|
|
||||||
}
|
|
||||||
|
|
||||||
function storeRequest(store, method, ...args) {
|
|
||||||
return new Promise((resolve, reject) => {
|
|
||||||
/** @type {IDBRequest} */
|
|
||||||
const request = store[method](...args);
|
|
||||||
request.onsuccess = () => resolve(request.result);
|
|
||||||
request.onerror = reject;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function putMany(store, _method, items) {
|
|
||||||
return Promise.all(items.map(item => storeRequest(store, 'put', item)));
|
|
||||||
}
|
|
||||||
|
|
||||||
function open(name) {
|
|
||||||
return new Promise((resolve, reject) => {
|
|
||||||
const request = indexedDB.open(name, 2);
|
|
||||||
request.onsuccess = e => resolve(create(e));
|
|
||||||
request.onerror = reject;
|
|
||||||
request.onupgradeneeded = create;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function create(event) {
|
|
||||||
/** @type IDBDatabase */
|
|
||||||
const idb = event.target.result;
|
|
||||||
const dbName = idb.name;
|
|
||||||
const sn = getStoreName(dbName);
|
|
||||||
if (!idb.objectStoreNames.contains(sn)) {
|
|
||||||
if (event.type === 'success') {
|
|
||||||
idb.close();
|
|
||||||
return new Promise(resolve => {
|
|
||||||
indexedDB.deleteDatabase(dbName).onsuccess = () => {
|
|
||||||
resolve(open(dbName));
|
|
||||||
};
|
|
||||||
});
|
|
||||||
}
|
|
||||||
idb.createObjectStore(sn, ID_AS_KEY[dbName] ? {
|
|
||||||
keyPath: 'id',
|
|
||||||
autoIncrement: true,
|
|
||||||
} : undefined);
|
|
||||||
}
|
|
||||||
return idb;
|
|
||||||
}
|
|
||||||
})();
|
|
|
@ -1,232 +0,0 @@
|
||||||
/* global API */// msg.js
|
|
||||||
/* global addAPI bgReady */// common.js
|
|
||||||
/* global colorScheme */
|
|
||||||
/* global prefs */
|
|
||||||
/* global tabMan */
|
|
||||||
/* global CHROME FIREFOX UA debounce ignoreChromeError */// toolbox.js
|
|
||||||
'use strict';
|
|
||||||
|
|
||||||
/* exported iconMan */
|
|
||||||
const iconMan = (() => {
|
|
||||||
const ICON_SIZES = FIREFOX || CHROME && !UA.vivaldi ? [16, 32] : [19, 38];
|
|
||||||
const staleBadges = new Set();
|
|
||||||
const imageDataCache = new Map();
|
|
||||||
const badgeOvr = {color: '', text: ''};
|
|
||||||
// https://github.com/openstyles/stylus/issues/1287 Fenix can't use custom ImageData
|
|
||||||
const FIREFOX_ANDROID = FIREFOX && UA.mobile;
|
|
||||||
let isDark;
|
|
||||||
// https://github.com/openstyles/stylus/issues/335
|
|
||||||
let hasCanvas = FIREFOX_ANDROID ? false : loadImage(`/images/icon/${ICON_SIZES[0]}.png`)
|
|
||||||
.then(({data}) => (hasCanvas = data.some(b => b !== 255)));
|
|
||||||
|
|
||||||
addAPI(/** @namespace API */ {
|
|
||||||
/**
|
|
||||||
* @param {(number|string)[]} styleIds
|
|
||||||
* @param {boolean} [lazyBadge=false] preventing flicker during page load
|
|
||||||
*/
|
|
||||||
updateIconBadge(styleIds, {lazyBadge} = {}) {
|
|
||||||
// FIXME: in some cases, we only have to redraw the badge. is it worth a optimization?
|
|
||||||
const {frameId, tab: {id: tabId}} = this.sender;
|
|
||||||
const value = styleIds.length ? styleIds.map(Number) : undefined;
|
|
||||||
tabMan.set(tabId, 'styleIds', frameId, value);
|
|
||||||
debounce(refreshStaleBadges, frameId && lazyBadge ? 250 : 0);
|
|
||||||
staleBadges.add(tabId);
|
|
||||||
if (!frameId) refreshIcon(tabId, true);
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
chrome.webNavigation.onCommitted.addListener(({tabId, frameId}) => {
|
|
||||||
if (!frameId) tabMan.set(tabId, 'styleIds', undefined);
|
|
||||||
});
|
|
||||||
chrome.runtime.onConnect.addListener(port => {
|
|
||||||
if (port.name === 'iframe') {
|
|
||||||
port.onDisconnect.addListener(onPortDisconnected);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
colorScheme.onChange(val => {
|
|
||||||
isDark = val;
|
|
||||||
if (prefs.get('iconset') === -1) {
|
|
||||||
debounce(refreshAllIcons);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
bgReady.all.then(() => {
|
|
||||||
prefs.subscribe([
|
|
||||||
'disableAll',
|
|
||||||
'badgeDisabled',
|
|
||||||
'badgeNormal',
|
|
||||||
], () => debounce(refreshIconBadgeColor), {runNow: true});
|
|
||||||
prefs.subscribe([
|
|
||||||
'show-badge',
|
|
||||||
], () => debounce(refreshAllIconsBadgeText), {runNow: true});
|
|
||||||
prefs.subscribe([
|
|
||||||
'disableAll',
|
|
||||||
'iconset',
|
|
||||||
], () => debounce(refreshAllIcons), {runNow: true});
|
|
||||||
});
|
|
||||||
|
|
||||||
return {
|
|
||||||
/** Calling with no params clears the override */
|
|
||||||
overrideBadge({text = '', color = '', title = ''} = {}) {
|
|
||||||
if (badgeOvr.text === text) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
badgeOvr.text = text;
|
|
||||||
badgeOvr.color = color;
|
|
||||||
refreshIconBadgeColor();
|
|
||||||
setBadgeText({text});
|
|
||||||
for (const tabId of tabMan.list()) {
|
|
||||||
if (text) {
|
|
||||||
setBadgeText({tabId, text});
|
|
||||||
} else {
|
|
||||||
refreshIconBadgeText(tabId);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
chrome.browserAction.setTitle({
|
|
||||||
title: title && chrome.i18n.getMessage(title) || title || '',
|
|
||||||
});
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
function onPortDisconnected({sender}) {
|
|
||||||
if (tabMan.get(sender.tab.id, 'styleIds')) {
|
|
||||||
API.updateIconBadge.call({sender}, [], {lazyBadge: true});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function refreshIconBadgeText(tabId) {
|
|
||||||
if (badgeOvr.text) return;
|
|
||||||
const text = prefs.get('show-badge') ? `${getStyleCount(tabId)}` : '';
|
|
||||||
setBadgeText({tabId, text});
|
|
||||||
}
|
|
||||||
|
|
||||||
function getIconName(hasStyles = false) {
|
|
||||||
const i = prefs.get('iconset');
|
|
||||||
const prefix = i === 0 || i === -1 && isDark ? '' : 'light/';
|
|
||||||
const postfix = prefs.get('disableAll') ? 'x' : !hasStyles ? 'w' : '';
|
|
||||||
return `${prefix}$SIZE$${postfix}`;
|
|
||||||
}
|
|
||||||
|
|
||||||
function refreshIcon(tabId, force = false) {
|
|
||||||
const oldIcon = tabMan.get(tabId, 'icon');
|
|
||||||
const newIcon = getIconName(tabMan.get(tabId, 'styleIds', 0));
|
|
||||||
// (changing the icon only for the main page, frameId = 0)
|
|
||||||
|
|
||||||
if (!force && oldIcon === newIcon) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
tabMan.set(tabId, 'icon', newIcon);
|
|
||||||
setIcon({
|
|
||||||
path: getIconPath(newIcon),
|
|
||||||
tabId,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function getIconPath(icon) {
|
|
||||||
return ICON_SIZES.reduce(
|
|
||||||
(obj, size) => {
|
|
||||||
obj[size] = `/images/icon/${icon.replace('$SIZE$', size)}.png`;
|
|
||||||
return obj;
|
|
||||||
},
|
|
||||||
{}
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
/** @return {number | ''} */
|
|
||||||
function getStyleCount(tabId) {
|
|
||||||
const allIds = new Set();
|
|
||||||
const data = tabMan.get(tabId, 'styleIds') || {};
|
|
||||||
Object.values(data).forEach(frameIds => frameIds.forEach(id => allIds.add(id)));
|
|
||||||
return allIds.size || '';
|
|
||||||
}
|
|
||||||
|
|
||||||
// Caches imageData for icon paths
|
|
||||||
async function loadImage(url) {
|
|
||||||
const {OffscreenCanvas} = !FIREFOX && self.createImageBitmap && self || {};
|
|
||||||
const img = OffscreenCanvas
|
|
||||||
? await createImageBitmap(await (await fetch(url)).blob())
|
|
||||||
: await new Promise((resolve, reject) =>
|
|
||||||
Object.assign(new Image(), {
|
|
||||||
src: url,
|
|
||||||
onload: e => resolve(e.target),
|
|
||||||
onerror: reject,
|
|
||||||
}));
|
|
||||||
const {width: w, height: h} = img;
|
|
||||||
const canvas = OffscreenCanvas
|
|
||||||
? new OffscreenCanvas(w, h)
|
|
||||||
: Object.assign(document.createElement('canvas'), {width: w, height: h});
|
|
||||||
const ctx = canvas.getContext('2d');
|
|
||||||
ctx.drawImage(img, 0, 0, w, h);
|
|
||||||
const result = ctx.getImageData(0, 0, w, h);
|
|
||||||
imageDataCache.set(url, result);
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
function refreshGlobalIcon() {
|
|
||||||
setIcon({
|
|
||||||
path: getIconPath(getIconName()),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function refreshIconBadgeColor() {
|
|
||||||
setBadgeBackgroundColor({
|
|
||||||
color: badgeOvr.color ||
|
|
||||||
prefs.get(prefs.get('disableAll') ? 'badgeDisabled' : 'badgeNormal'),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function refreshAllIcons() {
|
|
||||||
for (const tabId of tabMan.list()) {
|
|
||||||
refreshIcon(tabId);
|
|
||||||
}
|
|
||||||
refreshGlobalIcon();
|
|
||||||
}
|
|
||||||
|
|
||||||
function refreshAllIconsBadgeText() {
|
|
||||||
for (const tabId of tabMan.list()) {
|
|
||||||
refreshIconBadgeText(tabId);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function refreshStaleBadges() {
|
|
||||||
for (const tabId of staleBadges) {
|
|
||||||
refreshIconBadgeText(tabId);
|
|
||||||
}
|
|
||||||
staleBadges.clear();
|
|
||||||
}
|
|
||||||
|
|
||||||
function safeCall(method, data) {
|
|
||||||
const {browserAction = {}} = chrome;
|
|
||||||
const fn = browserAction[method];
|
|
||||||
if (fn) {
|
|
||||||
try {
|
|
||||||
// Chrome supports the callback since 67.0.3381.0, see https://crbug.com/451320
|
|
||||||
fn.call(browserAction, data, ignoreChromeError);
|
|
||||||
} catch (e) {
|
|
||||||
// FIXME: skip pre-rendered tabs?
|
|
||||||
fn.call(browserAction, data);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/** @param {chrome.browserAction.TabIconDetails} data */
|
|
||||||
async function setIcon(data) {
|
|
||||||
if (hasCanvas === true || await hasCanvas) {
|
|
||||||
data.imageData = {};
|
|
||||||
for (const [key, url] of Object.entries(data.path)) {
|
|
||||||
data.imageData[key] = imageDataCache.get(url) || await loadImage(url);
|
|
||||||
}
|
|
||||||
delete data.path;
|
|
||||||
}
|
|
||||||
safeCall('setIcon', data);
|
|
||||||
}
|
|
||||||
|
|
||||||
/** @param {chrome.browserAction.BadgeTextDetails} data */
|
|
||||||
function setBadgeText(data) {
|
|
||||||
safeCall('setBadgeText', data);
|
|
||||||
}
|
|
||||||
|
|
||||||
/** @param {chrome.browserAction.BadgeBackgroundColorDetails} data */
|
|
||||||
function setBadgeBackgroundColor(data) {
|
|
||||||
safeCall('setBadgeBackgroundColor', data);
|
|
||||||
}
|
|
||||||
})();
|
|
|
@ -1,103 +0,0 @@
|
||||||
/* global CHROME FIREFOX URLS deepEqual ignoreChromeError */// toolbox.js
|
|
||||||
/* global bgReady */// common.js
|
|
||||||
/* global msg */
|
|
||||||
'use strict';
|
|
||||||
|
|
||||||
/* exported navMan */
|
|
||||||
const navMan = (() => {
|
|
||||||
const listeners = new Set();
|
|
||||||
let prevData = {};
|
|
||||||
|
|
||||||
chrome.webNavigation.onCommitted.addListener(onNavigation.bind('committed'));
|
|
||||||
chrome.webNavigation.onHistoryStateUpdated.addListener(onFakeNavigation.bind('history'));
|
|
||||||
chrome.webNavigation.onReferenceFragmentUpdated.addListener(onFakeNavigation.bind('hash'));
|
|
||||||
|
|
||||||
return {
|
|
||||||
/** @param {function(data: Object, type: ('committed'|'history'|'hash'))} fn */
|
|
||||||
onUrlChange(fn) {
|
|
||||||
listeners.add(fn);
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
/** @this {string} type */
|
|
||||||
async function onNavigation(data) {
|
|
||||||
if (CHROME && data.timeStamp === prevData.timeStamp && deepEqual(data, prevData)) {
|
|
||||||
return; // Chrome bug: listener is called twice with identical data
|
|
||||||
}
|
|
||||||
prevData = data;
|
|
||||||
if (CHROME &&
|
|
||||||
URLS.chromeProtectsNTP &&
|
|
||||||
data.url.startsWith('https://www.google.') &&
|
|
||||||
data.url.includes('/_/chrome/newtab?')) {
|
|
||||||
// Modern Chrome switched to WebUI NTP so this is obsolete, but there may be exceptions
|
|
||||||
// TODO: investigate, and maybe use a separate listener for CHROME <= ver
|
|
||||||
const tab = await browser.tabs.get(data.tabId);
|
|
||||||
const url = tab.pendingUrl || tab.url;
|
|
||||||
if (url === 'chrome://newtab/') {
|
|
||||||
data.url = url;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
listeners.forEach(fn => fn(data, this));
|
|
||||||
}
|
|
||||||
|
|
||||||
/** @this {string} type */
|
|
||||||
function onFakeNavigation(data) {
|
|
||||||
const {url, frameId} = data;
|
|
||||||
onNavigation.call(this, data);
|
|
||||||
msg.sendTab(data.tabId, {method: 'urlChanged', url}, {frameId})
|
|
||||||
.catch(msg.ignoreError);
|
|
||||||
}
|
|
||||||
})();
|
|
||||||
|
|
||||||
bgReady.all.then(() => {
|
|
||||||
/*
|
|
||||||
* Expose style version on greasyfork/sleazyfork 1) info page and 2) code page
|
|
||||||
* Not using manifest.json as adding a content script disables the extension on update.
|
|
||||||
*/
|
|
||||||
const urlMatches = '/scripts/\\d+[^/]*(/code)?([?#].*)?$';
|
|
||||||
chrome.webNavigation.onCommitted.addListener(({tabId}) => {
|
|
||||||
chrome.tabs.executeScript(tabId, {
|
|
||||||
file: '/content/install-hook-greasyfork.js',
|
|
||||||
runAt: 'document_start',
|
|
||||||
});
|
|
||||||
}, {
|
|
||||||
url: [
|
|
||||||
{hostEquals: 'greasyfork.org', urlMatches},
|
|
||||||
{hostEquals: 'sleazyfork.org', urlMatches},
|
|
||||||
],
|
|
||||||
});
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Removes the Get Stylus button on style pages.
|
|
||||||
* Not using manifest.json as adding a content script disables the extension on update.
|
|
||||||
*/
|
|
||||||
chrome.webNavigation.onCommitted.addListener(({tabId}) => {
|
|
||||||
chrome.tabs.executeScript(tabId, {
|
|
||||||
file: '/content/install-hook-userstylesworld.js',
|
|
||||||
runAt: 'document_start',
|
|
||||||
});
|
|
||||||
}, {
|
|
||||||
url: [
|
|
||||||
{hostEquals: 'userstyles.world'},
|
|
||||||
],
|
|
||||||
});
|
|
||||||
/*
|
|
||||||
* FF misses some about:blank iframes so we inject our content script explicitly
|
|
||||||
*/
|
|
||||||
if (FIREFOX) {
|
|
||||||
chrome.webNavigation.onDOMContentLoaded.addListener(async ({tabId, frameId}) => {
|
|
||||||
if (frameId &&
|
|
||||||
!await msg.sendTab(tabId, {method: 'ping'}, {frameId}).catch(ignoreChromeError)) {
|
|
||||||
for (const file of chrome.runtime.getManifest().content_scripts[0].js) {
|
|
||||||
chrome.tabs.executeScript(tabId, {
|
|
||||||
frameId,
|
|
||||||
file,
|
|
||||||
matchAboutBlank: true,
|
|
||||||
}, ignoreChromeError);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}, {
|
|
||||||
url: [{urlEquals: 'about:blank'}],
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
102
background/openusercss-api.js
Normal file
102
background/openusercss-api.js
Normal file
|
@ -0,0 +1,102 @@
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
(() => {
|
||||||
|
// begin:nanographql - Tiny graphQL client library
|
||||||
|
// Author: yoshuawuyts (https://github.com/yoshuawuyts)
|
||||||
|
// License: MIT
|
||||||
|
// Modified by DecentM to fit project standards
|
||||||
|
|
||||||
|
const getOpname = /(query|mutation) ?([\w\d-_]+)? ?\(.*?\)? \{/;
|
||||||
|
const gql = str => {
|
||||||
|
str = Array.isArray(str) ? str.join('') : str;
|
||||||
|
const name = getOpname.exec(str);
|
||||||
|
|
||||||
|
return variables => {
|
||||||
|
const data = {query: str};
|
||||||
|
if (variables) data.variables = JSON.stringify(variables);
|
||||||
|
if (name && name.length) {
|
||||||
|
const operationName = name[2];
|
||||||
|
if (operationName) data.operationName = name[2];
|
||||||
|
}
|
||||||
|
return JSON.stringify(data);
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
// end:nanographql
|
||||||
|
|
||||||
|
const api = 'https://api.openusercss.org';
|
||||||
|
const doQuery = ({id}, queryString) => {
|
||||||
|
const query = gql(queryString);
|
||||||
|
|
||||||
|
return fetch(api, {
|
||||||
|
method: 'POST',
|
||||||
|
headers: new Headers({
|
||||||
|
'Content-Type': 'application/json'
|
||||||
|
}),
|
||||||
|
body: query({
|
||||||
|
id
|
||||||
|
})
|
||||||
|
})
|
||||||
|
.then(res => res.json());
|
||||||
|
};
|
||||||
|
|
||||||
|
window.API_METHODS = Object.assign(window.API_METHODS || {}, {
|
||||||
|
/**
|
||||||
|
* This function can be used to retrieve a theme object from the
|
||||||
|
* GraphQL API, set above
|
||||||
|
*
|
||||||
|
* Example:
|
||||||
|
* chrome.runtime.sendMessage({
|
||||||
|
* 'method': 'oucThemeById',
|
||||||
|
* 'id': '5a2f819f7c57c751001b49df'
|
||||||
|
* }, console.log);
|
||||||
|
*
|
||||||
|
* @param {ID} $0.id MongoDB style ID
|
||||||
|
* @returns {Promise.<{data: object}>} The GraphQL result with the `theme` object
|
||||||
|
*/
|
||||||
|
|
||||||
|
oucThemeById: params => doQuery(params, `
|
||||||
|
query($id: ID!) {
|
||||||
|
theme(id: $id) {
|
||||||
|
_id
|
||||||
|
title
|
||||||
|
description
|
||||||
|
createdAt
|
||||||
|
lastUpdate
|
||||||
|
version
|
||||||
|
screenshots
|
||||||
|
user {
|
||||||
|
_id
|
||||||
|
displayname
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`),
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This function can be used to retrieve a user object from the
|
||||||
|
* GraphQL API, set above
|
||||||
|
*
|
||||||
|
* Example:
|
||||||
|
* chrome.runtime.sendMessage({
|
||||||
|
* 'method': 'oucUserById',
|
||||||
|
* 'id': '5a2f0361ba666f0b00b9c827'
|
||||||
|
* }, console.log);
|
||||||
|
*
|
||||||
|
* @param {ID} $0.id MongoDB style ID
|
||||||
|
* @returns {Promise.<{data: object}>} The GraphQL result with the `user` object
|
||||||
|
*/
|
||||||
|
|
||||||
|
oucUserById: params => doQuery(params, `
|
||||||
|
query($id: ID!) {
|
||||||
|
user(id: $id) {
|
||||||
|
_id
|
||||||
|
displayname
|
||||||
|
avatarUrl
|
||||||
|
smallAvatarUrl
|
||||||
|
bio
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`),
|
||||||
|
});
|
||||||
|
})();
|
9
background/parserlib-loader.js
Normal file
9
background/parserlib-loader.js
Normal file
|
@ -0,0 +1,9 @@
|
||||||
|
/* global importScripts parserlib CSSLint parseMozFormat */
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
importScripts('/vendor-overwrites/csslint/parserlib.js', '/js/moz-parser.js');
|
||||||
|
parserlib.css.Tokens[parserlib.css.Tokens.COMMENT].hide = false;
|
||||||
|
|
||||||
|
self.onmessage = ({data}) => {
|
||||||
|
self.postMessage(parseMozFormat(data));
|
||||||
|
};
|
226
background/refresh-all-tabs.js
Normal file
226
background/refresh-all-tabs.js
Normal file
|
@ -0,0 +1,226 @@
|
||||||
|
/*
|
||||||
|
global API_METHODS cachedStyles
|
||||||
|
global getStyles filterStyles invalidateCache normalizeStyleSections
|
||||||
|
global updateIcon
|
||||||
|
*/
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
(() => {
|
||||||
|
const previewFromTabs = new Map();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* When style id and state is provided, only that style is propagated.
|
||||||
|
* Otherwise all styles are replaced and the toolbar icon is updated.
|
||||||
|
* @param {Object} [msg]
|
||||||
|
* @param {{id:Number, enabled?:Boolean, sections?: (Array|String)}} [msg.style] -
|
||||||
|
* style to propagate
|
||||||
|
* @param {Boolean} [msg.codeIsUpdated]
|
||||||
|
* @returns {Promise<void>}
|
||||||
|
*/
|
||||||
|
API_METHODS.refreshAllTabs = (msg = {}) =>
|
||||||
|
Promise.all([
|
||||||
|
queryTabs(),
|
||||||
|
maybeParseUsercss(msg),
|
||||||
|
getStyles(),
|
||||||
|
]).then(([tabs, style]) =>
|
||||||
|
new Promise(resolve => {
|
||||||
|
if (style) msg.style.sections = normalizeStyleSections(style);
|
||||||
|
run(tabs, msg, resolve);
|
||||||
|
}));
|
||||||
|
|
||||||
|
|
||||||
|
function run(tabs, msg, resolve) {
|
||||||
|
const {style, codeIsUpdated, refreshOwnTabs} = msg;
|
||||||
|
|
||||||
|
// the style was updated/saved so we need to remove the old copy of the original style
|
||||||
|
if (msg.method === 'styleUpdated' && msg.reason !== 'editPreview') {
|
||||||
|
for (const [tabId, original] of previewFromTabs.entries()) {
|
||||||
|
if (style.id === original.id) {
|
||||||
|
previewFromTabs.delete(tabId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!previewFromTabs.size) {
|
||||||
|
unregisterTabListeners();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!style) {
|
||||||
|
msg = {method: 'styleReplaceAll'};
|
||||||
|
|
||||||
|
// live preview puts the code in cachedStyles, saves the original in previewFromTabs,
|
||||||
|
// and if preview is being disabled, but the style is already deleted, we bail out
|
||||||
|
} else if (msg.reason === 'editPreview' && !updateCache(msg)) {
|
||||||
|
return;
|
||||||
|
|
||||||
|
// simple style update:
|
||||||
|
// * if disabled, apply.js will remove the element
|
||||||
|
// * if toggled and code is unchanged, apply.js will toggle the element
|
||||||
|
} else if (!style.enabled || codeIsUpdated === false) {
|
||||||
|
msg = {
|
||||||
|
method: 'styleUpdated',
|
||||||
|
reason: msg.reason,
|
||||||
|
style: {
|
||||||
|
id: style.id,
|
||||||
|
enabled: style.enabled,
|
||||||
|
},
|
||||||
|
codeIsUpdated,
|
||||||
|
};
|
||||||
|
|
||||||
|
// live preview normal operation, the new code is already in cachedStyles
|
||||||
|
} else {
|
||||||
|
msg.method = 'styleApply';
|
||||||
|
msg.style = {id: msg.style.id};
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!tabs || !tabs.length) {
|
||||||
|
resolve();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const last = tabs[tabs.length - 1];
|
||||||
|
for (const tab of tabs) {
|
||||||
|
if (FIREFOX && !tab.width) continue;
|
||||||
|
if (refreshOwnTabs === false && tab.url.startsWith(URLS.ownOrigin)) continue;
|
||||||
|
chrome.webNavigation.getAllFrames({tabId: tab.id}, frames =>
|
||||||
|
refreshFrame(tab, frames, msg, tab === last && resolve));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function refreshFrame(tab, frames, msg, resolve) {
|
||||||
|
ignoreChromeError();
|
||||||
|
if (!frames || !frames.length) {
|
||||||
|
frames = [{
|
||||||
|
frameId: 0,
|
||||||
|
url: tab.url,
|
||||||
|
}];
|
||||||
|
}
|
||||||
|
msg.tabId = tab.id;
|
||||||
|
const styleId = msg.style && msg.style.id;
|
||||||
|
|
||||||
|
for (const frame of frames) {
|
||||||
|
|
||||||
|
const styles = filterStyles({
|
||||||
|
matchUrl: getFrameUrl(frame, frames),
|
||||||
|
asHash: true,
|
||||||
|
id: styleId,
|
||||||
|
});
|
||||||
|
|
||||||
|
msg = Object.assign({}, msg);
|
||||||
|
msg.frameId = frame.frameId;
|
||||||
|
|
||||||
|
if (msg.method !== 'styleUpdated') {
|
||||||
|
msg.styles = styles;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (msg.method === 'styleApply' && !styles.length) {
|
||||||
|
// remove the style from a previously matching frame
|
||||||
|
invokeOrPostpone(tab.active, sendMessage, {
|
||||||
|
method: 'styleUpdated',
|
||||||
|
reason: 'editPreview',
|
||||||
|
style: {
|
||||||
|
id: styleId,
|
||||||
|
enabled: false,
|
||||||
|
},
|
||||||
|
tabId: tab.id,
|
||||||
|
frameId: frame.frameId,
|
||||||
|
}, ignoreChromeError);
|
||||||
|
} else {
|
||||||
|
invokeOrPostpone(tab.active, sendMessage, msg, ignoreChromeError);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!frame.frameId) {
|
||||||
|
setTimeout(updateIcon, 0, {
|
||||||
|
tab,
|
||||||
|
styles: msg.method === 'styleReplaceAll' ? styles : undefined,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (resolve) resolve();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
function getFrameUrl(frame, frames) {
|
||||||
|
while (frame.url === 'about:blank' && frame.frameId > 0) {
|
||||||
|
const parent = frames.find(f => f.frameId === frame.parentFrameId);
|
||||||
|
if (!parent) break;
|
||||||
|
frame.url = parent.url;
|
||||||
|
frame = parent;
|
||||||
|
}
|
||||||
|
return (frame || frames[0]).url;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
function maybeParseUsercss({style}) {
|
||||||
|
if (style && typeof style.sections === 'string') {
|
||||||
|
return API_METHODS.parseUsercss({sourceCode: style.sections});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
function updateCache(msg) {
|
||||||
|
const {style, tabId, restoring} = msg;
|
||||||
|
const spoofed = !restoring && previewFromTabs.get(tabId);
|
||||||
|
const original = cachedStyles.byId.get(style.id);
|
||||||
|
|
||||||
|
if (style.sections && !restoring) {
|
||||||
|
if (!previewFromTabs.size) {
|
||||||
|
registerTabListeners();
|
||||||
|
}
|
||||||
|
if (!spoofed) {
|
||||||
|
previewFromTabs.set(tabId, Object.assign({}, original));
|
||||||
|
}
|
||||||
|
|
||||||
|
} else {
|
||||||
|
previewFromTabs.delete(tabId);
|
||||||
|
if (!previewFromTabs.size) {
|
||||||
|
unregisterTabListeners();
|
||||||
|
}
|
||||||
|
if (!original) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!restoring) {
|
||||||
|
msg.style = spoofed || original;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
invalidateCache({updated: msg.style});
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
function registerTabListeners() {
|
||||||
|
chrome.tabs.onRemoved.addListener(onTabRemoved);
|
||||||
|
chrome.tabs.onReplaced.addListener(onTabReplaced);
|
||||||
|
chrome.webNavigation.onCommitted.addListener(onTabNavigated);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
function unregisterTabListeners() {
|
||||||
|
chrome.tabs.onRemoved.removeListener(onTabRemoved);
|
||||||
|
chrome.tabs.onReplaced.removeListener(onTabReplaced);
|
||||||
|
chrome.webNavigation.onCommitted.removeListener(onTabNavigated);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
function onTabRemoved(tabId) {
|
||||||
|
const style = previewFromTabs.get(tabId);
|
||||||
|
if (style) {
|
||||||
|
API_METHODS.refreshAllTabs({
|
||||||
|
style,
|
||||||
|
tabId,
|
||||||
|
reason: 'editPreview',
|
||||||
|
restoring: true,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
function onTabReplaced(addedTabId, removedTabId) {
|
||||||
|
onTabRemoved(removedTabId);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
function onTabNavigated({tabId}) {
|
||||||
|
onTabRemoved(tabId);
|
||||||
|
}
|
||||||
|
})();
|
100
background/search-db.js
Normal file
100
background/search-db.js
Normal file
|
@ -0,0 +1,100 @@
|
||||||
|
/* global API_METHODS filterStyles cachedStyles */
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
(() => {
|
||||||
|
// toLocaleLowerCase cache, autocleared after 1 minute
|
||||||
|
const cache = new Map();
|
||||||
|
// top-level style properties to be searched
|
||||||
|
const PARTS = {
|
||||||
|
name: searchText,
|
||||||
|
url: searchText,
|
||||||
|
sourceCode: searchText,
|
||||||
|
sections: searchSections,
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param params
|
||||||
|
* @param {string} params.query - 1. url:someurl 2. text (may contain quoted parts like "qUot Ed")
|
||||||
|
* @param {number[]} [params.ids] - if not specified, all styles are searched
|
||||||
|
* @returns {number[]} - array of matched styles ids
|
||||||
|
*/
|
||||||
|
API_METHODS.searchDB = ({query, ids}) => {
|
||||||
|
let rx, words, icase, matchUrl;
|
||||||
|
query = query.trim();
|
||||||
|
|
||||||
|
if (/^url:/i.test(query)) {
|
||||||
|
matchUrl = query.slice(query.indexOf(':') + 1).trim();
|
||||||
|
if (matchUrl) {
|
||||||
|
return filterStyles({matchUrl}).map(style => style.id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (query.startsWith('/') && /^\/(.+?)\/([gimsuy]*)$/.test(query)) {
|
||||||
|
rx = tryRegExp(RegExp.$1, RegExp.$2);
|
||||||
|
}
|
||||||
|
if (!rx) {
|
||||||
|
words = query
|
||||||
|
.split(/(".*?")|\s+/)
|
||||||
|
.filter(Boolean)
|
||||||
|
.map(w => w.startsWith('"') && w.endsWith('"')
|
||||||
|
? w.slice(1, -1)
|
||||||
|
: w)
|
||||||
|
.filter(w => w.length > 1);
|
||||||
|
words = words.length ? words : [query];
|
||||||
|
icase = words.some(w => w === lower(w));
|
||||||
|
}
|
||||||
|
|
||||||
|
const results = [];
|
||||||
|
for (const item of ids || cachedStyles.list) {
|
||||||
|
const id = isNaN(item) ? item.id : item;
|
||||||
|
if (!query || words && !words.length) {
|
||||||
|
results.push(id);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
const style = isNaN(item) ? item : cachedStyles.byId.get(item);
|
||||||
|
if (!style) continue;
|
||||||
|
for (const part in PARTS) {
|
||||||
|
const text = style[part];
|
||||||
|
if (text && PARTS[part](text, rx, words, icase)) {
|
||||||
|
results.push(id);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (cache.size) debounce(clearCache, 60e3);
|
||||||
|
return results;
|
||||||
|
};
|
||||||
|
|
||||||
|
function searchText(text, rx, words, icase) {
|
||||||
|
if (rx) return rx.test(text);
|
||||||
|
for (let pass = 1; pass <= (icase ? 2 : 1); pass++) {
|
||||||
|
if (words.every(w => text.includes(w))) return true;
|
||||||
|
text = lower(text);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function searchSections(sections, rx, words, icase) {
|
||||||
|
for (const section of sections) {
|
||||||
|
for (const prop in section) {
|
||||||
|
const value = section[prop];
|
||||||
|
if (typeof value === 'string') {
|
||||||
|
if (searchText(value, rx, words, icase)) return true;
|
||||||
|
} else if (Array.isArray(value)) {
|
||||||
|
if (value.some(str => searchText(str, rx, words, icase))) return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function lower(text) {
|
||||||
|
let result = cache.get(text);
|
||||||
|
if (result) return result;
|
||||||
|
result = text.toLocaleLowerCase();
|
||||||
|
cache.set(text, result);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
function clearCache() {
|
||||||
|
cache.clear();
|
||||||
|
}
|
||||||
|
})();
|
78
background/storage-dummy.js
Normal file
78
background/storage-dummy.js
Normal file
|
@ -0,0 +1,78 @@
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
// eslint-disable-next-line no-unused-expressions
|
||||||
|
(chrome.runtime.id.includes('@temporary') || !('sync' in chrome.storage)) && (() => {
|
||||||
|
|
||||||
|
const listeners = new Set();
|
||||||
|
Object.assign(chrome.storage.onChanged, {
|
||||||
|
addListener: fn => listeners.add(fn),
|
||||||
|
hasListener: fn => listeners.has(fn),
|
||||||
|
removeListener: fn => listeners.delete(fn),
|
||||||
|
});
|
||||||
|
|
||||||
|
for (const name of ['local', 'sync']) {
|
||||||
|
const dummy = tryJSONparse(localStorage['dummyStorage.' + name]) || {};
|
||||||
|
chrome.storage[name] = {
|
||||||
|
get(data, cb) {
|
||||||
|
let result = {};
|
||||||
|
if (data === null) {
|
||||||
|
result = deepCopy(dummy);
|
||||||
|
} else if (Array.isArray(data)) {
|
||||||
|
for (const key of data) {
|
||||||
|
result[key] = dummy[key];
|
||||||
|
}
|
||||||
|
} else if (typeof data === 'object') {
|
||||||
|
const hasOwnProperty = Object.prototype.hasOwnProperty;
|
||||||
|
for (const key in data) {
|
||||||
|
if (hasOwnProperty.call(data, key)) {
|
||||||
|
const value = dummy[key];
|
||||||
|
result[key] = value === undefined ? data[key] : value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
result[data] = dummy[data];
|
||||||
|
}
|
||||||
|
if (typeof cb === 'function') cb(result);
|
||||||
|
},
|
||||||
|
set(data, cb) {
|
||||||
|
const hasOwnProperty = Object.prototype.hasOwnProperty;
|
||||||
|
const changes = {};
|
||||||
|
for (const key in data) {
|
||||||
|
if (!hasOwnProperty.call(data, key)) continue;
|
||||||
|
const newValue = data[key];
|
||||||
|
changes[key] = {newValue, oldValue: dummy[key]};
|
||||||
|
dummy[key] = newValue;
|
||||||
|
}
|
||||||
|
localStorage['dummyStorage.' + name] = JSON.stringify(dummy);
|
||||||
|
if (typeof cb === 'function') cb();
|
||||||
|
notify(changes);
|
||||||
|
},
|
||||||
|
remove(keyOrKeys, cb) {
|
||||||
|
const changes = {};
|
||||||
|
for (const key of Array.isArray(keyOrKeys) ? keyOrKeys : [keyOrKeys]) {
|
||||||
|
changes[key] = {oldValue: dummy[key]};
|
||||||
|
delete dummy[key];
|
||||||
|
}
|
||||||
|
localStorage['dummyStorage.' + name] = JSON.stringify(dummy);
|
||||||
|
if (typeof cb === 'function') cb();
|
||||||
|
notify(changes);
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
window.API_METHODS = Object.assign(window.API_METHODS || {}, {
|
||||||
|
dummyStorageGet: ({data, name}) => new Promise(resolve => chrome.storage[name].get(data, resolve)),
|
||||||
|
dummyStorageSet: ({data, name}) => new Promise(resolve => chrome.storage[name].set(data, resolve)),
|
||||||
|
dummyStorageRemove: ({data, name}) => new Promise(resolve => chrome.storage[name].remove(data, resolve)),
|
||||||
|
});
|
||||||
|
|
||||||
|
function notify(changes, name) {
|
||||||
|
for (const fn of listeners.values()) {
|
||||||
|
fn(changes, name);
|
||||||
|
}
|
||||||
|
sendMessage({
|
||||||
|
dummyStorageChanges: changes,
|
||||||
|
dummyStorageName: name,
|
||||||
|
}, ignoreChromeError);
|
||||||
|
}
|
||||||
|
})();
|
836
background/storage.js
Normal file
836
background/storage.js
Normal file
|
@ -0,0 +1,836 @@
|
||||||
|
/* global getStyleWithNoCode styleSectionsEqual */
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
const RX_NAMESPACE = /\s*(@namespace\s+(?:\S+\s+)?url\(http:\/\/.*?\);)\s*/g;
|
||||||
|
const RX_CHARSET = /\s*@charset\s+(['"]).*?\1\s*;\s*/g;
|
||||||
|
const RX_CSS_COMMENTS = /\/\*[\s\S]*?(?:\*\/|$)/g;
|
||||||
|
// eslint-disable-next-line no-var
|
||||||
|
var SLOPPY_REGEXP_PREFIX = '\0';
|
||||||
|
|
||||||
|
// CSS transition bug workaround: since we insert styles asynchronously,
|
||||||
|
// the browsers, especially Firefox, may apply all transitions on page load
|
||||||
|
const CSS_TRANSITION_SUPPRESSOR = '* { transition: none !important; }';
|
||||||
|
const RX_CSS_TRANSITION_DETECTOR = /([\s\n;/{]|-webkit-|-moz-)transition[\s\n]*:[\s\n]*(?!none)/;
|
||||||
|
|
||||||
|
// Note, only 'var'-declared variables are visible from another extension page
|
||||||
|
// eslint-disable-next-line no-var
|
||||||
|
var cachedStyles = {
|
||||||
|
list: null, // array of all styles
|
||||||
|
byId: new Map(), // all styles indexed by id
|
||||||
|
filters: new Map(), // filterStyles() parameters mapped to the returned results, 10k max
|
||||||
|
regexps: new Map(), // compiled style regexps
|
||||||
|
urlDomains: new Map(), // getDomain() results for 100 last checked urls
|
||||||
|
needTransitionPatch: new Map(), // FF bug workaround
|
||||||
|
mutex: {
|
||||||
|
inProgress: true, // while getStyles() is reading IndexedDB all subsequent calls
|
||||||
|
// (initially 'true' to prevent rogue getStyles before dbExec.initialized)
|
||||||
|
onDone: [], // to getStyles() are queued and resolved when the first one finishes
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
// eslint-disable-next-line no-var
|
||||||
|
var dbExec = dbExecIndexedDB;
|
||||||
|
dbExec.initialized = false;
|
||||||
|
|
||||||
|
// we use chrome.storage.local fallback if IndexedDB doesn't save data,
|
||||||
|
// which, once detected on the first run, is remembered in chrome.storage.local
|
||||||
|
// for reliablility and in localStorage for fast synchronous access
|
||||||
|
// (FF may block localStorage depending on its privacy options)
|
||||||
|
do {
|
||||||
|
const done = () => {
|
||||||
|
cachedStyles.mutex.inProgress = false;
|
||||||
|
getStyles().then(() => {
|
||||||
|
dbExec.initialized = true;
|
||||||
|
window.dispatchEvent(new Event('storageReady'));
|
||||||
|
});
|
||||||
|
};
|
||||||
|
const fallback = () => {
|
||||||
|
dbExec = dbExecChromeStorage;
|
||||||
|
chromeLocal.set({dbInChromeStorage: true});
|
||||||
|
localStorage.dbInChromeStorage = 'true';
|
||||||
|
ignoreChromeError();
|
||||||
|
done();
|
||||||
|
};
|
||||||
|
const fallbackSet = localStorage.dbInChromeStorage;
|
||||||
|
if (fallbackSet === 'true' || !tryCatch(() => indexedDB)) {
|
||||||
|
fallback();
|
||||||
|
break;
|
||||||
|
} else if (fallbackSet === 'false') {
|
||||||
|
done();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
chromeLocal.get('dbInChromeStorage')
|
||||||
|
.then(data =>
|
||||||
|
data && data.dbInChromeStorage && Promise.reject())
|
||||||
|
.then(() =>
|
||||||
|
tryCatch(dbExecIndexedDB, 'getAllKeys', IDBKeyRange.lowerBound(1), 1) ||
|
||||||
|
Promise.reject())
|
||||||
|
.then(({target}) => (
|
||||||
|
(target.result || [])[0] ?
|
||||||
|
Promise.reject('ok') :
|
||||||
|
dbExecIndexedDB('put', {id: -1})))
|
||||||
|
.then(() =>
|
||||||
|
dbExecIndexedDB('get', -1))
|
||||||
|
.then(({target}) => (
|
||||||
|
(target.result || {}).id === -1 ?
|
||||||
|
dbExecIndexedDB('delete', -1) :
|
||||||
|
Promise.reject()))
|
||||||
|
.then(() =>
|
||||||
|
Promise.reject('ok'))
|
||||||
|
.catch(result => {
|
||||||
|
if (result === 'ok') {
|
||||||
|
chromeLocal.set({dbInChromeStorage: false});
|
||||||
|
localStorage.dbInChromeStorage = 'false';
|
||||||
|
done();
|
||||||
|
} else {
|
||||||
|
fallback();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} while (0);
|
||||||
|
|
||||||
|
|
||||||
|
function dbExecIndexedDB(method, ...args) {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
Object.assign(indexedDB.open('stylish', 2), {
|
||||||
|
onsuccess(event) {
|
||||||
|
const database = event.target.result;
|
||||||
|
if (!method) {
|
||||||
|
resolve(database);
|
||||||
|
} else {
|
||||||
|
const transaction = database.transaction(['styles'], 'readwrite');
|
||||||
|
const store = transaction.objectStore('styles');
|
||||||
|
Object.assign(store[method](...args), {
|
||||||
|
onsuccess: event => resolve(event, store, transaction, database),
|
||||||
|
onerror: reject,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onerror(event) {
|
||||||
|
console.warn(event.target.error || event.target.errorCode);
|
||||||
|
reject(event);
|
||||||
|
},
|
||||||
|
onupgradeneeded(event) {
|
||||||
|
if (event.oldVersion === 0) {
|
||||||
|
event.target.result.createObjectStore('styles', {
|
||||||
|
keyPath: 'id',
|
||||||
|
autoIncrement: true,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
function dbExecChromeStorage(method, data) {
|
||||||
|
const STYLE_KEY_PREFIX = 'style-';
|
||||||
|
switch (method) {
|
||||||
|
case 'get':
|
||||||
|
return chromeLocal.getValue(STYLE_KEY_PREFIX + data)
|
||||||
|
.then(result => ({target: {result}}));
|
||||||
|
|
||||||
|
case 'put':
|
||||||
|
if (!data.id) {
|
||||||
|
return getStyles().then(() => {
|
||||||
|
data.id = 1;
|
||||||
|
for (const style of cachedStyles.list) {
|
||||||
|
data.id = Math.max(data.id, style.id + 1);
|
||||||
|
}
|
||||||
|
return dbExecChromeStorage('put', data);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return chromeLocal.setValue(STYLE_KEY_PREFIX + data.id, data)
|
||||||
|
.then(() => (chrome.runtime.lastError ? Promise.reject() : data.id));
|
||||||
|
|
||||||
|
case 'delete':
|
||||||
|
return chromeLocal.remove(STYLE_KEY_PREFIX + data);
|
||||||
|
|
||||||
|
case 'getAll':
|
||||||
|
return chromeLocal.get(null).then(storage => {
|
||||||
|
const styles = [];
|
||||||
|
for (const key in storage) {
|
||||||
|
if (key.startsWith(STYLE_KEY_PREFIX) &&
|
||||||
|
Number(key.substr(STYLE_KEY_PREFIX.length))) {
|
||||||
|
styles.push(storage[key]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return {target: {result: styles}};
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return Promise.reject();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
function getStyles(options) {
|
||||||
|
if (cachedStyles.list) {
|
||||||
|
return Promise.resolve(filterStyles(options));
|
||||||
|
}
|
||||||
|
if (cachedStyles.mutex.inProgress) {
|
||||||
|
return new Promise(resolve => {
|
||||||
|
cachedStyles.mutex.onDone.push({options, resolve});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
cachedStyles.mutex.inProgress = true;
|
||||||
|
|
||||||
|
return dbExec('getAll').then(event => {
|
||||||
|
cachedStyles.list = event.target.result || [];
|
||||||
|
cachedStyles.byId.clear();
|
||||||
|
for (const style of cachedStyles.list) {
|
||||||
|
cachedStyles.byId.set(style.id, style);
|
||||||
|
if (!style.name) {
|
||||||
|
style.name = 'ID: ' + style.id;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
cachedStyles.mutex.inProgress = false;
|
||||||
|
for (const {options, resolve} of cachedStyles.mutex.onDone) {
|
||||||
|
resolve(filterStyles(options));
|
||||||
|
}
|
||||||
|
cachedStyles.mutex.onDone = [];
|
||||||
|
return filterStyles(options);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
function filterStyles({
|
||||||
|
enabled = null,
|
||||||
|
id = null,
|
||||||
|
matchUrl = null,
|
||||||
|
md5Url = null,
|
||||||
|
asHash = null,
|
||||||
|
omitCode,
|
||||||
|
strictRegexp = true, // used by the popup to detect bad regexps
|
||||||
|
} = {}) {
|
||||||
|
if (id) id = Number(id);
|
||||||
|
if (asHash) enabled = true;
|
||||||
|
|
||||||
|
if (
|
||||||
|
enabled === null &&
|
||||||
|
id === null &&
|
||||||
|
matchUrl === null &&
|
||||||
|
md5Url === null &&
|
||||||
|
asHash !== true
|
||||||
|
) {
|
||||||
|
return cachedStyles.list;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (matchUrl && !URLS.supported(matchUrl)) {
|
||||||
|
return asHash ? {length: 0} : [];
|
||||||
|
}
|
||||||
|
|
||||||
|
const blankHash = asHash && {
|
||||||
|
length: 0,
|
||||||
|
disableAll: prefs.get('disableAll'),
|
||||||
|
exposeIframes: prefs.get('exposeIframes'),
|
||||||
|
};
|
||||||
|
|
||||||
|
// make sure to use the same order in updateFiltersCache()
|
||||||
|
const cacheKey =
|
||||||
|
enabled + '\t' +
|
||||||
|
id + '\t' +
|
||||||
|
matchUrl + '\t' +
|
||||||
|
md5Url + '\t' +
|
||||||
|
asHash + '\t' +
|
||||||
|
strictRegexp;
|
||||||
|
const cached = cachedStyles.filters.get(cacheKey);
|
||||||
|
let styles;
|
||||||
|
if (cached) {
|
||||||
|
cached.hits++;
|
||||||
|
cached.lastHit = Date.now();
|
||||||
|
styles = asHash
|
||||||
|
? Object.assign(blankHash, cached.styles)
|
||||||
|
: cached.styles.slice();
|
||||||
|
} else {
|
||||||
|
styles = filterStylesInternal({
|
||||||
|
enabled,
|
||||||
|
id,
|
||||||
|
matchUrl,
|
||||||
|
md5Url,
|
||||||
|
asHash,
|
||||||
|
strictRegexp,
|
||||||
|
blankHash,
|
||||||
|
cacheKey,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
if (!omitCode) return styles;
|
||||||
|
if (!asHash) return styles.map(getStyleWithNoCode);
|
||||||
|
for (const id in styles) {
|
||||||
|
const sections = styles[id];
|
||||||
|
if (Array.isArray(sections)) {
|
||||||
|
styles[id] = getStyleWithNoCode({sections}).sections;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return styles;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
function filterStylesInternal({
|
||||||
|
// js engines don't like big functions (V8 often deoptimized the original filterStyles)
|
||||||
|
// it also makes sense to extract the less frequently executed code
|
||||||
|
enabled,
|
||||||
|
id,
|
||||||
|
matchUrl,
|
||||||
|
md5Url,
|
||||||
|
asHash,
|
||||||
|
strictRegexp,
|
||||||
|
blankHash,
|
||||||
|
cacheKey,
|
||||||
|
}) {
|
||||||
|
if (matchUrl && !cachedStyles.urlDomains.has(matchUrl)) {
|
||||||
|
cachedStyles.urlDomains.set(matchUrl, getDomains(matchUrl));
|
||||||
|
for (let i = cachedStyles.urlDomains.size - 100; i > 0; i--) {
|
||||||
|
const firstKey = cachedStyles.urlDomains.keys().next().value;
|
||||||
|
cachedStyles.urlDomains.delete(firstKey);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const styles = id === null
|
||||||
|
? cachedStyles.list
|
||||||
|
: [cachedStyles.byId.get(id)];
|
||||||
|
if (!styles[0]) {
|
||||||
|
// may happen when users [accidentally] reopen an old URL
|
||||||
|
// of edit.html with a non-existent style id parameter
|
||||||
|
return asHash ? blankHash : [];
|
||||||
|
}
|
||||||
|
const filtered = asHash ? {length: 0} : [];
|
||||||
|
const needSections = asHash || matchUrl !== null;
|
||||||
|
const matchUrlBase = matchUrl && matchUrl.includes('#') && matchUrl.split('#', 1)[0];
|
||||||
|
|
||||||
|
let style;
|
||||||
|
for (let i = 0; (style = styles[i]); i++) {
|
||||||
|
if ((enabled === null || style.enabled === enabled)
|
||||||
|
&& (md5Url === null || style.md5Url === md5Url)
|
||||||
|
&& (id === null || style.id === id)) {
|
||||||
|
const sections = needSections &&
|
||||||
|
getApplicableSections({
|
||||||
|
style,
|
||||||
|
matchUrl,
|
||||||
|
strictRegexp,
|
||||||
|
stopOnFirst: !asHash,
|
||||||
|
skipUrlCheck: true,
|
||||||
|
matchUrlBase,
|
||||||
|
});
|
||||||
|
if (asHash) {
|
||||||
|
if (sections.length) {
|
||||||
|
filtered[style.id] = sections;
|
||||||
|
filtered.length++;
|
||||||
|
}
|
||||||
|
} else if (matchUrl === null || sections.length) {
|
||||||
|
filtered.push(style);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
cachedStyles.filters.set(cacheKey, {
|
||||||
|
styles: filtered,
|
||||||
|
lastHit: Date.now(),
|
||||||
|
hits: 1,
|
||||||
|
});
|
||||||
|
if (cachedStyles.filters.size > 10000) {
|
||||||
|
cleanupCachedFilters();
|
||||||
|
}
|
||||||
|
|
||||||
|
// a shallow copy is needed because the cache doesn't store options like disableAll
|
||||||
|
return asHash
|
||||||
|
? Object.assign(blankHash, filtered)
|
||||||
|
: filtered;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
function saveStyle(style) {
|
||||||
|
const id = Number(style.id) || null;
|
||||||
|
const reason = style.reason;
|
||||||
|
const notify = style.notify !== false;
|
||||||
|
delete style.method;
|
||||||
|
delete style.reason;
|
||||||
|
delete style.notify;
|
||||||
|
if (!style.name) {
|
||||||
|
delete style.name;
|
||||||
|
}
|
||||||
|
let existed;
|
||||||
|
let codeIsUpdated;
|
||||||
|
|
||||||
|
return maybeCalcDigest()
|
||||||
|
.then(maybeImportFix)
|
||||||
|
.then(decide);
|
||||||
|
|
||||||
|
function maybeCalcDigest() {
|
||||||
|
if (['install', 'update', 'update-digest'].includes(reason)) {
|
||||||
|
return calcStyleDigest(style).then(digest => {
|
||||||
|
style.originalDigest = digest;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return Promise.resolve();
|
||||||
|
}
|
||||||
|
|
||||||
|
function maybeImportFix() {
|
||||||
|
if (reason === 'import') {
|
||||||
|
style.originalDigest = style.originalDigest || style.styleDigest; // TODO: remove in the future
|
||||||
|
delete style.styleDigest; // TODO: remove in the future
|
||||||
|
if (typeof style.originalDigest !== 'string' || style.originalDigest.length !== 40) {
|
||||||
|
delete style.originalDigest;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function decide() {
|
||||||
|
if (id !== null) {
|
||||||
|
// Update or create
|
||||||
|
style.id = id;
|
||||||
|
return dbExec('get', id).then((event, store) => {
|
||||||
|
const oldStyle = event.target.result;
|
||||||
|
existed = Boolean(oldStyle);
|
||||||
|
if (reason === 'update-digest' && oldStyle.originalDigest === style.originalDigest) {
|
||||||
|
return style;
|
||||||
|
}
|
||||||
|
codeIsUpdated = !existed || 'sections' in style && !styleSectionsEqual(style, oldStyle);
|
||||||
|
style = Object.assign({installDate: Date.now()}, oldStyle, style);
|
||||||
|
return write(style, store);
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
// Create
|
||||||
|
delete style.id;
|
||||||
|
style = Object.assign({
|
||||||
|
// Set optional things if they're undefined
|
||||||
|
enabled: true,
|
||||||
|
updateUrl: null,
|
||||||
|
md5Url: null,
|
||||||
|
url: null,
|
||||||
|
originalMd5: null,
|
||||||
|
installDate: Date.now(),
|
||||||
|
}, style);
|
||||||
|
return write(style);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function write(style, store) {
|
||||||
|
style.sections = normalizeStyleSections(style);
|
||||||
|
if (store) {
|
||||||
|
return new Promise(resolve => {
|
||||||
|
store.put(style).onsuccess = event => resolve(done(event));
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
return dbExec('put', style).then(done);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function done(event) {
|
||||||
|
if (reason === 'update-digest') {
|
||||||
|
return style;
|
||||||
|
}
|
||||||
|
style.id = style.id || event.target.result;
|
||||||
|
invalidateCache(existed ? {updated: style} : {added: style});
|
||||||
|
if (notify) {
|
||||||
|
notifyAllTabs({
|
||||||
|
method: existed ? 'styleUpdated' : 'styleAdded',
|
||||||
|
style, codeIsUpdated, reason,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return style;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
function deleteStyle({id, notify = true}) {
|
||||||
|
id = Number(id);
|
||||||
|
return dbExec('delete', id).then(() => {
|
||||||
|
invalidateCache({deletedId: id});
|
||||||
|
if (notify) {
|
||||||
|
notifyAllTabs({method: 'styleDeleted', id});
|
||||||
|
}
|
||||||
|
return id;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
function getApplicableSections({
|
||||||
|
style,
|
||||||
|
matchUrl,
|
||||||
|
strictRegexp = true,
|
||||||
|
// filterStylesInternal() sets the following to avoid recalc on each style:
|
||||||
|
stopOnFirst,
|
||||||
|
skipUrlCheck,
|
||||||
|
matchUrlBase = matchUrl.includes('#') && matchUrl.split('#', 1)[0],
|
||||||
|
// as per spec the fragment portion is ignored in @-moz-document:
|
||||||
|
// https://www.w3.org/TR/2012/WD-css3-conditional-20120911/#url-of-doc
|
||||||
|
// but the spec is outdated and doesn't account for SPA sites
|
||||||
|
// so we only respect it in case of url("http://exact.url/without/hash")
|
||||||
|
}) {
|
||||||
|
if (!skipUrlCheck && !URLS.supported(matchUrl)) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
const sections = [];
|
||||||
|
for (const section of style.sections) {
|
||||||
|
const {urls, domains, urlPrefixes, regexps, code} = section;
|
||||||
|
const isGlobal = !urls.length && !urlPrefixes.length && !domains.length && !regexps.length;
|
||||||
|
const isMatching = !isGlobal && (
|
||||||
|
urls.length
|
||||||
|
&& (urls.includes(matchUrl) || matchUrlBase && urls.includes(matchUrlBase))
|
||||||
|
|| urlPrefixes.length
|
||||||
|
&& arraySomeIsPrefix(urlPrefixes, matchUrl)
|
||||||
|
|| domains.length
|
||||||
|
&& arraySomeIn(cachedStyles.urlDomains.get(matchUrl) || getDomains(matchUrl), domains)
|
||||||
|
|| regexps.length
|
||||||
|
&& arraySomeMatches(regexps, matchUrl, strictRegexp));
|
||||||
|
if (isGlobal && !styleCodeEmpty(code) || isMatching) {
|
||||||
|
sections.push(section);
|
||||||
|
if (stopOnFirst) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return sections;
|
||||||
|
|
||||||
|
function arraySomeIsPrefix(array, string) {
|
||||||
|
for (const prefix of array) {
|
||||||
|
if (string.startsWith(prefix)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
function arraySomeIn(array, haystack) {
|
||||||
|
for (const el of array) {
|
||||||
|
if (haystack.indexOf(el) >= 0) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
function arraySomeMatches(array, matchUrl, strictRegexp) {
|
||||||
|
for (const regexp of array) {
|
||||||
|
for (let pass = 1; pass <= (strictRegexp ? 1 : 2); pass++) {
|
||||||
|
const cacheKey = pass === 1 ? regexp : SLOPPY_REGEXP_PREFIX + regexp;
|
||||||
|
let rx = cachedStyles.regexps.get(cacheKey);
|
||||||
|
if (rx === false) {
|
||||||
|
// invalid regexp
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if (!rx) {
|
||||||
|
const anchored = pass === 1 ? '^(?:' + regexp + ')$' : '^' + regexp + '$';
|
||||||
|
rx = tryRegExp(anchored);
|
||||||
|
cachedStyles.regexps.set(cacheKey, rx || false);
|
||||||
|
if (!rx) {
|
||||||
|
// invalid regexp
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (rx.test(matchUrl)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
function styleCodeEmpty(code) {
|
||||||
|
// Collect the global section if it's not empty, not comment-only, not namespace-only.
|
||||||
|
const cmtOpen = code && code.indexOf('/*');
|
||||||
|
if (cmtOpen >= 0) {
|
||||||
|
const cmtCloseLast = code.lastIndexOf('*/');
|
||||||
|
if (cmtCloseLast < 0) {
|
||||||
|
code = code.substr(0, cmtOpen);
|
||||||
|
} else {
|
||||||
|
code = code.substr(0, cmtOpen) +
|
||||||
|
code.substring(cmtOpen, cmtCloseLast + 2).replace(RX_CSS_COMMENTS, '') +
|
||||||
|
code.substr(cmtCloseLast + 2);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!code || !code.trim()) return true;
|
||||||
|
if (code.includes('@namespace')) code = code.replace(RX_NAMESPACE, '').trim();
|
||||||
|
if (code.includes('@charset')) code = code.replace(RX_CHARSET, '').trim();
|
||||||
|
return !code;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
function invalidateCache({added, updated, deletedId} = {}) {
|
||||||
|
if (!cachedStyles.list) return;
|
||||||
|
const id = added ? added.id : updated ? updated.id : deletedId;
|
||||||
|
const cached = cachedStyles.byId.get(id);
|
||||||
|
|
||||||
|
if (updated) {
|
||||||
|
if (cached) {
|
||||||
|
const isSectionGlobal = section =>
|
||||||
|
!section.urls.length &&
|
||||||
|
!section.urlPrefixes.length &&
|
||||||
|
!section.domains.length &&
|
||||||
|
!section.regexps.length;
|
||||||
|
const hadOrHasGlobals = cached.sections.some(isSectionGlobal) ||
|
||||||
|
updated.sections.some(isSectionGlobal);
|
||||||
|
const reenabled = !cached.enabled && updated.enabled;
|
||||||
|
const equal = !hadOrHasGlobals &&
|
||||||
|
!reenabled &&
|
||||||
|
styleSectionsEqual(updated, cached, {ignoreCode: true});
|
||||||
|
Object.assign(cached, updated);
|
||||||
|
if (equal) {
|
||||||
|
updateFiltersCache(cached);
|
||||||
|
} else {
|
||||||
|
cachedStyles.filters.clear();
|
||||||
|
}
|
||||||
|
cachedStyles.needTransitionPatch.delete(id);
|
||||||
|
return;
|
||||||
|
} else {
|
||||||
|
added = updated;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (added) {
|
||||||
|
if (!cached) {
|
||||||
|
cachedStyles.list.push(added);
|
||||||
|
cachedStyles.byId.set(added.id, added);
|
||||||
|
cachedStyles.filters.clear();
|
||||||
|
cachedStyles.needTransitionPatch.delete(id);
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (deletedId !== undefined) {
|
||||||
|
if (cached) {
|
||||||
|
const cachedIndex = cachedStyles.list.indexOf(cached);
|
||||||
|
cachedStyles.list.splice(cachedIndex, 1);
|
||||||
|
cachedStyles.byId.delete(deletedId);
|
||||||
|
for (const {styles} of cachedStyles.filters.values()) {
|
||||||
|
if (Array.isArray(styles)) {
|
||||||
|
const index = styles.findIndex(({id}) => id === deletedId);
|
||||||
|
if (index >= 0) styles.splice(index, 1);
|
||||||
|
} else if (deletedId in styles) {
|
||||||
|
delete styles[deletedId];
|
||||||
|
styles.length--;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
cachedStyles.needTransitionPatch.delete(id);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
cachedStyles.list = null;
|
||||||
|
cachedStyles.filters.clear();
|
||||||
|
cachedStyles.needTransitionPatch.clear(id);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
function updateFiltersCache(style) {
|
||||||
|
const {id} = style;
|
||||||
|
for (const [key, {styles}] of cachedStyles.filters.entries()) {
|
||||||
|
if (Array.isArray(styles)) {
|
||||||
|
const index = styles.findIndex(style => style.id === id);
|
||||||
|
if (index >= 0) styles[index] = Object.assign({}, style);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (id in styles) {
|
||||||
|
const [, , matchUrl, , , strictRegexp] = key.split('\t');
|
||||||
|
if (!style.enabled) {
|
||||||
|
delete styles[id];
|
||||||
|
styles.length--;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
const matchUrlBase = matchUrl && matchUrl.includes('#') && matchUrl.split('#', 1)[0];
|
||||||
|
const sections = getApplicableSections({
|
||||||
|
style,
|
||||||
|
matchUrl,
|
||||||
|
matchUrlBase,
|
||||||
|
strictRegexp,
|
||||||
|
skipUrlCheck: true,
|
||||||
|
});
|
||||||
|
if (sections.length) {
|
||||||
|
styles[id] = sections;
|
||||||
|
} else {
|
||||||
|
delete styles[id];
|
||||||
|
styles.length--;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
function cleanupCachedFilters({force = false} = {}) {
|
||||||
|
if (!force) {
|
||||||
|
debounce(cleanupCachedFilters, 1000, {force: true});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const size = cachedStyles.filters.size;
|
||||||
|
const oldestHit = cachedStyles.filters.values().next().value.lastHit;
|
||||||
|
const now = Date.now();
|
||||||
|
const timeSpan = now - oldestHit;
|
||||||
|
const recencyWeight = 5 / size;
|
||||||
|
const hitWeight = 1 / 4; // we make ~4 hits per URL
|
||||||
|
const lastHitWeight = 10;
|
||||||
|
// delete the oldest 10%
|
||||||
|
[...cachedStyles.filters.entries()]
|
||||||
|
.map(([id, v], index) => ({
|
||||||
|
id,
|
||||||
|
weight:
|
||||||
|
index * recencyWeight +
|
||||||
|
v.hits * hitWeight +
|
||||||
|
(v.lastHit - oldestHit) / timeSpan * lastHitWeight,
|
||||||
|
}))
|
||||||
|
.sort((a, b) => a.weight - b.weight)
|
||||||
|
.slice(0, size / 10 + 1)
|
||||||
|
.forEach(({id}) => cachedStyles.filters.delete(id));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
function getDomains(url) {
|
||||||
|
let d = /.*?:\/*([^/:]+)|$/.exec(url)[1];
|
||||||
|
if (!d || url.startsWith('file:')) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
const domains = [d];
|
||||||
|
while (d.indexOf('.') !== -1) {
|
||||||
|
d = d.substring(d.indexOf('.') + 1);
|
||||||
|
domains.push(d);
|
||||||
|
}
|
||||||
|
return domains;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
function normalizeStyleSections({sections}) {
|
||||||
|
// retain known properties in an arbitrarily predefined order
|
||||||
|
return (sections || []).map(section => ({
|
||||||
|
code: section.code || '',
|
||||||
|
urls: section.urls || [],
|
||||||
|
urlPrefixes: section.urlPrefixes || [],
|
||||||
|
domains: section.domains || [],
|
||||||
|
regexps: section.regexps || [],
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
function calcStyleDigest(style) {
|
||||||
|
const jsonString = style.usercssData ?
|
||||||
|
style.sourceCode : JSON.stringify(normalizeStyleSections(style));
|
||||||
|
const text = new TextEncoder('utf-8').encode(jsonString);
|
||||||
|
return crypto.subtle.digest('SHA-1', text).then(hex);
|
||||||
|
|
||||||
|
function hex(buffer) {
|
||||||
|
const parts = [];
|
||||||
|
const PAD8 = '00000000';
|
||||||
|
const view = new DataView(buffer);
|
||||||
|
for (let i = 0; i < view.byteLength; i += 4) {
|
||||||
|
parts.push((PAD8 + view.getUint32(i).toString(16)).slice(-8));
|
||||||
|
}
|
||||||
|
return parts.join('');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
function handleCssTransitionBug({tabId, frameId, url, styles}) {
|
||||||
|
for (let id in styles) {
|
||||||
|
id |= 0;
|
||||||
|
if (!id) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
let need = cachedStyles.needTransitionPatch.get(id);
|
||||||
|
if (need === false) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (need !== true) {
|
||||||
|
need = styles[id].some(sectionContainsTransitions);
|
||||||
|
cachedStyles.needTransitionPatch.set(id, need);
|
||||||
|
if (!need) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (FIREFOX && !url.startsWith(URLS.ownOrigin)) {
|
||||||
|
patchFirefox();
|
||||||
|
} else {
|
||||||
|
styles.needTransitionPatch = true;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
function patchFirefox() {
|
||||||
|
const options = {
|
||||||
|
frameId,
|
||||||
|
code: CSS_TRANSITION_SUPPRESSOR,
|
||||||
|
matchAboutBlank: true,
|
||||||
|
};
|
||||||
|
if (FIREFOX >= 53) {
|
||||||
|
options.cssOrigin = 'user';
|
||||||
|
}
|
||||||
|
browser.tabs.insertCSS(tabId, Object.assign(options, {
|
||||||
|
runAt: 'document_start',
|
||||||
|
})).then(() => setTimeout(() => {
|
||||||
|
browser.tabs.removeCSS(tabId, options).catch(ignoreChromeError);
|
||||||
|
})).catch(ignoreChromeError);
|
||||||
|
}
|
||||||
|
|
||||||
|
function sectionContainsTransitions(section) {
|
||||||
|
let code = section.code;
|
||||||
|
const firstTransition = code.indexOf('transition');
|
||||||
|
if (firstTransition < 0) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
const firstCmt = code.indexOf('/*');
|
||||||
|
// check the part before the first comment
|
||||||
|
if (firstCmt < 0 || firstTransition < firstCmt) {
|
||||||
|
if (quickCheckAround(code, firstTransition)) {
|
||||||
|
return true;
|
||||||
|
} else if (firstCmt < 0) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// check the rest
|
||||||
|
const lastCmt = code.lastIndexOf('*/');
|
||||||
|
if (lastCmt < firstCmt) {
|
||||||
|
// the comment is unclosed and we already checked the preceding part
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
let mid = code.slice(firstCmt, lastCmt + 2);
|
||||||
|
mid = mid.indexOf('*/') === mid.length - 2 ? '' : mid.replace(RX_CSS_COMMENTS, '');
|
||||||
|
code = mid + code.slice(lastCmt + 2);
|
||||||
|
return quickCheckAround(code) || RX_CSS_TRANSITION_DETECTOR.test(code);
|
||||||
|
}
|
||||||
|
|
||||||
|
function quickCheckAround(code, pos = code.indexOf('transition')) {
|
||||||
|
return RX_CSS_TRANSITION_DETECTOR.test(code.substr(Math.max(0, pos - 10), 50));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
According to CSS4 @document specification the entire URL must match.
|
||||||
|
Stylish-for-Chrome implemented it incorrectly since the very beginning.
|
||||||
|
We'll detect styles that abuse the bug by finding the sections that
|
||||||
|
would have been applied by Stylish but not by us as we follow the spec.
|
||||||
|
Additionally we'll check for invalid regexps.
|
||||||
|
*/
|
||||||
|
function detectSloppyRegexps({matchUrl, ids}) {
|
||||||
|
const results = [];
|
||||||
|
for (const id of ids) {
|
||||||
|
const style = cachedStyles.byId.get(id);
|
||||||
|
if (!style) continue;
|
||||||
|
// make sure all regexps are compiled
|
||||||
|
const rxCache = cachedStyles.regexps;
|
||||||
|
let hasRegExp = false;
|
||||||
|
for (const section of style.sections) {
|
||||||
|
for (const regexp of section.regexps) {
|
||||||
|
hasRegExp = true;
|
||||||
|
for (let pass = 1; pass <= 2; pass++) {
|
||||||
|
const cacheKey = pass === 1 ? regexp : SLOPPY_REGEXP_PREFIX + regexp;
|
||||||
|
if (!rxCache.has(cacheKey)) {
|
||||||
|
// according to CSS4 @document specification the entire URL must match
|
||||||
|
const anchored = pass === 1 ? '^(?:' + regexp + ')$' : '^' + regexp + '$';
|
||||||
|
// create in the bg context to avoid leaking of "dead objects"
|
||||||
|
const rx = tryRegExp(anchored);
|
||||||
|
rxCache.set(cacheKey, rx || false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!hasRegExp) continue;
|
||||||
|
const applied = getApplicableSections({style, matchUrl});
|
||||||
|
const wannabe = getApplicableSections({style, matchUrl, strictRegexp: false});
|
||||||
|
results.push({
|
||||||
|
id,
|
||||||
|
applied,
|
||||||
|
skipped: wannabe.length - applied.length,
|
||||||
|
hasInvalidRegexps: wannabe.some(({regexps}) => regexps.some(rx => !rxCache.has(rx))),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return results;
|
||||||
|
}
|
|
@ -1,791 +0,0 @@
|
||||||
/* global API msg */// msg.js
|
|
||||||
/* global CHROME URLS deepEqual isEmptyObj mapObj stringAsRegExp tryRegExp tryURL */// toolbox.js
|
|
||||||
/* global bgReady createCache uuidIndex */// common.js
|
|
||||||
/* global calcStyleDigest styleCodeEmpty */// sections-util.js
|
|
||||||
/* global db */
|
|
||||||
/* global prefs */
|
|
||||||
/* global tabMan */
|
|
||||||
/* global usercssMan */
|
|
||||||
/* global colorScheme */
|
|
||||||
'use strict';
|
|
||||||
|
|
||||||
/*
|
|
||||||
This style manager is a layer between content script and the DB. When a style
|
|
||||||
is added/updated, it broadcast a message to content script and the content
|
|
||||||
script would try to fetch the new code.
|
|
||||||
|
|
||||||
The live preview feature relies on `runtime.connect` and `port.onDisconnect`
|
|
||||||
to cleanup the temporary code. See livePreview in /edit.
|
|
||||||
*/
|
|
||||||
|
|
||||||
const styleUtil = {};
|
|
||||||
|
|
||||||
/* exported styleMan */
|
|
||||||
const styleMan = (() => {
|
|
||||||
|
|
||||||
Object.assign(styleUtil, {
|
|
||||||
id2style,
|
|
||||||
handleSave,
|
|
||||||
uuid2style,
|
|
||||||
});
|
|
||||||
|
|
||||||
//#region Declarations
|
|
||||||
|
|
||||||
/** @typedef {{
|
|
||||||
style: StyleObj,
|
|
||||||
preview?: StyleObj,
|
|
||||||
appliesTo: Set<string>,
|
|
||||||
}} StyleMapData */
|
|
||||||
/** @type {Map<number,StyleMapData>} */
|
|
||||||
const dataMap = new Map();
|
|
||||||
/** @typedef {Object<styleId,{id: number, code: string[]}>} StyleSectionsToApply */
|
|
||||||
/** @type {Map<string,{maybeMatch: Set<styleId>, sections: StyleSectionsToApply}>} */
|
|
||||||
const cachedStyleForUrl = createCache({
|
|
||||||
onDeleted(url, cache) {
|
|
||||||
for (const section of Object.values(cache.sections)) {
|
|
||||||
const data = id2data(section.id);
|
|
||||||
if (data) data.appliesTo.delete(url);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
});
|
|
||||||
const BAD_MATCHER = {test: () => false};
|
|
||||||
const compileRe = createCompiler(text => `^(${text})$`);
|
|
||||||
const compileSloppyRe = createCompiler(text => `^${text}$`);
|
|
||||||
const compileExclusion = createCompiler(buildExclusion);
|
|
||||||
const uuidv4 = crypto.randomUUID ? crypto.randomUUID.bind(crypto) : (() => {
|
|
||||||
const seeds = crypto.getRandomValues(new Uint16Array(8));
|
|
||||||
// 00001111-2222-M333-N444-555566667777
|
|
||||||
seeds[3] = seeds[3] & 0x0FFF | 0x4000; // UUID version 4, M = 4
|
|
||||||
seeds[4] = seeds[4] & 0x3FFF | 0x8000; // UUID variant 1, N = 8..0xB
|
|
||||||
return Array.from(seeds, hex4dashed).join('');
|
|
||||||
});
|
|
||||||
const MISSING_PROPS = {
|
|
||||||
name: style => `ID: ${style.id}`,
|
|
||||||
_id: () => uuidv4(),
|
|
||||||
_rev: () => Date.now(),
|
|
||||||
};
|
|
||||||
const DELETE_IF_NULL = ['id', 'customName', 'md5Url', 'originalMd5'];
|
|
||||||
const INJ_ORDER = 'injectionOrder';
|
|
||||||
const order = {main: {}, prio: {}};
|
|
||||||
const orderWrap = {
|
|
||||||
id: INJ_ORDER,
|
|
||||||
value: mapObj(order, () => []),
|
|
||||||
_id: `${chrome.runtime.id}-${INJ_ORDER}`,
|
|
||||||
_rev: 0,
|
|
||||||
};
|
|
||||||
uuidIndex.addCustomId(orderWrap, {set: setOrder});
|
|
||||||
|
|
||||||
class MatchQuery {
|
|
||||||
constructor(url) {
|
|
||||||
this.url = url;
|
|
||||||
}
|
|
||||||
get urlWithoutHash() {
|
|
||||||
return this._set('urlWithoutHash', this.url.split('#', 1)[0]);
|
|
||||||
}
|
|
||||||
get urlWithoutParams() {
|
|
||||||
return this._set('urlWithoutParams', this.url.split(/[?#]/, 1)[0]);
|
|
||||||
}
|
|
||||||
get domain() {
|
|
||||||
return this._set('domain', tryURL(this.url).hostname);
|
|
||||||
}
|
|
||||||
get isOwnPage() {
|
|
||||||
return this._set('isOwnPage', this.url.startsWith(URLS.ownOrigin));
|
|
||||||
}
|
|
||||||
_set(name, value) {
|
|
||||||
Object.defineProperty(this, name, {value});
|
|
||||||
return value;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/** @type {Promise|boolean} will be `true` to avoid wasting a microtask tick on each `await` */
|
|
||||||
let ready = Promise.all([init(), prefs.ready]);
|
|
||||||
|
|
||||||
chrome.runtime.onConnect.addListener(port => {
|
|
||||||
if (port.name === 'livePreview') {
|
|
||||||
handleLivePreview(port);
|
|
||||||
} else if (port.name.startsWith('draft:')) {
|
|
||||||
handleDraft(port);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
colorScheme.onChange(value => {
|
|
||||||
msg.broadcastExtension({method: 'colorScheme', value});
|
|
||||||
for (const {style} of dataMap.values()) {
|
|
||||||
if (colorScheme.SCHEMES.includes(style.preferScheme)) {
|
|
||||||
broadcastStyleUpdated(style, 'colorScheme');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
//#endregion
|
|
||||||
//#region Exports
|
|
||||||
|
|
||||||
return {
|
|
||||||
|
|
||||||
/** @returns {Promise<number>} style id */
|
|
||||||
async delete(id, reason) {
|
|
||||||
if (ready.then) await ready;
|
|
||||||
const {style, appliesTo} = dataMap.get(id);
|
|
||||||
const sync = reason !== 'sync';
|
|
||||||
const uuid = style._id;
|
|
||||||
db.styles.delete(id);
|
|
||||||
if (sync) API.sync.delete(uuid, Date.now());
|
|
||||||
for (const url of appliesTo) {
|
|
||||||
const cache = cachedStyleForUrl.get(url);
|
|
||||||
if (cache) delete cache.sections[id];
|
|
||||||
}
|
|
||||||
dataMap.delete(id);
|
|
||||||
uuidIndex.delete(uuid);
|
|
||||||
mapObj(orderWrap.value, (group, type) => {
|
|
||||||
delete order[type][id];
|
|
||||||
const i = group.indexOf(uuid);
|
|
||||||
if (i >= 0) group.splice(i, 1);
|
|
||||||
});
|
|
||||||
setOrder(orderWrap, {calc: false});
|
|
||||||
if (style._usw && style._usw.token) {
|
|
||||||
// Must be called after the style is deleted from dataMap
|
|
||||||
API.usw.revoke(id);
|
|
||||||
}
|
|
||||||
API.drafts.delete(id);
|
|
||||||
await msg.broadcast({
|
|
||||||
method: 'styleDeleted',
|
|
||||||
style: {id},
|
|
||||||
});
|
|
||||||
return id;
|
|
||||||
},
|
|
||||||
|
|
||||||
/** @returns {Promise<StyleObj>} */
|
|
||||||
async editSave(style) {
|
|
||||||
if (ready.then) await ready;
|
|
||||||
style = mergeWithMapped(style);
|
|
||||||
style.updateDate = Date.now();
|
|
||||||
return saveStyle(style, {reason: 'editSave'});
|
|
||||||
},
|
|
||||||
|
|
||||||
/** @returns {Promise<?StyleObj>} */
|
|
||||||
async find(...filters) {
|
|
||||||
if (ready.then) await ready;
|
|
||||||
for (const filter of filters) {
|
|
||||||
const filterEntries = Object.entries(filter);
|
|
||||||
for (const {style} of dataMap.values()) {
|
|
||||||
if (filterEntries.every(([key, val]) => style[key] === val)) {
|
|
||||||
return style;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
},
|
|
||||||
|
|
||||||
/** @returns {Promise<StyleObj[]>} */
|
|
||||||
async getAll() {
|
|
||||||
if (ready.then) await ready;
|
|
||||||
return getAllAsArray();
|
|
||||||
},
|
|
||||||
|
|
||||||
/** @returns {Promise<Object<string,StyleObj[]>>}>} */
|
|
||||||
async getAllOrdered(keys) {
|
|
||||||
if (ready.then) await ready;
|
|
||||||
const res = mapObj(orderWrap.value, group => group.map(uuid2style).filter(Boolean));
|
|
||||||
if (res.main.length + res.prio.length < dataMap.size) {
|
|
||||||
for (const {style} of dataMap.values()) {
|
|
||||||
if (!(style.id in order.main) && !(style.id in order.prio)) {
|
|
||||||
res.main.push(style);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return keys
|
|
||||||
? mapObj(res, group => group.map(style => mapObj(style, null, keys)))
|
|
||||||
: res;
|
|
||||||
},
|
|
||||||
|
|
||||||
getOrder: () => orderWrap.value,
|
|
||||||
|
|
||||||
/** @returns {Promise<string | {[remoteId:string]: styleId}>}>} */
|
|
||||||
async getRemoteInfo(id) {
|
|
||||||
if (ready.then) await ready;
|
|
||||||
if (id) return calcRemoteId(id2style(id));
|
|
||||||
const res = {};
|
|
||||||
for (const {style} of dataMap.values()) {
|
|
||||||
const [rid, vars] = calcRemoteId(style);
|
|
||||||
if (rid) res[rid] = [style.id, vars];
|
|
||||||
}
|
|
||||||
return res;
|
|
||||||
},
|
|
||||||
|
|
||||||
/** @returns {Promise<StyleSectionsToApply>} */
|
|
||||||
async getSectionsByUrl(url, id, isInitialApply) {
|
|
||||||
if (ready.then) await ready;
|
|
||||||
if (isInitialApply && prefs.get('disableAll')) {
|
|
||||||
return {
|
|
||||||
cfg: {
|
|
||||||
disableAll: true,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
}
|
|
||||||
// TODO: enable in FF when it supports sourceURL comment in style elements (also options.html)
|
|
||||||
const {exposeStyleName} = CHROME && prefs.__values;
|
|
||||||
const sender = CHROME && this && this.sender || {};
|
|
||||||
if (sender.frameId === 0) {
|
|
||||||
/* Chrome hides text frament from location.href of the page e.g. #:~:text=foo
|
|
||||||
so we'll use the real URL reported by webNavigation API.
|
|
||||||
TODO: if FF will do the same, this won't work as is: FF reports onCommitted too late */
|
|
||||||
url = tabMan.get(sender.tab.id, 'url', 0) || url;
|
|
||||||
}
|
|
||||||
let cache = cachedStyleForUrl.get(url);
|
|
||||||
if (!cache) {
|
|
||||||
cache = {
|
|
||||||
sections: {},
|
|
||||||
maybeMatch: new Set(),
|
|
||||||
};
|
|
||||||
buildCache(cache, url, dataMap.values());
|
|
||||||
cachedStyleForUrl.set(url, cache);
|
|
||||||
} else if (cache.maybeMatch.size) {
|
|
||||||
buildCache(cache, url, Array.from(cache.maybeMatch, id2data).filter(Boolean));
|
|
||||||
}
|
|
||||||
return Object.assign({cfg: {exposeStyleName, order}},
|
|
||||||
id ? mapObj(cache.sections, null, [id])
|
|
||||||
: cache.sections);
|
|
||||||
},
|
|
||||||
|
|
||||||
/** @returns {Promise<StyleObj>} */
|
|
||||||
async get(id) {
|
|
||||||
if (ready.then) await ready;
|
|
||||||
return id2style(id);
|
|
||||||
},
|
|
||||||
|
|
||||||
/** @returns {Promise<StylesByUrlResult[]>} */
|
|
||||||
async getByUrl(url, id = null) {
|
|
||||||
if (ready.then) await ready;
|
|
||||||
// FIXME: do we want to cache this? Who would like to open popup rapidly
|
|
||||||
// or search the DB with the same URL?
|
|
||||||
const result = [];
|
|
||||||
const styles = id
|
|
||||||
? [id2style(id)].filter(Boolean)
|
|
||||||
: getAllAsArray();
|
|
||||||
const query = new MatchQuery(url);
|
|
||||||
for (const style of styles) {
|
|
||||||
let excluded = false;
|
|
||||||
let excludedScheme = false;
|
|
||||||
let included = false;
|
|
||||||
let sloppy = false;
|
|
||||||
let sectionMatched = false;
|
|
||||||
const match = urlMatchStyle(query, style);
|
|
||||||
// TODO: enable this when the function starts returning false
|
|
||||||
// if (match === false) {
|
|
||||||
// continue;
|
|
||||||
// }
|
|
||||||
if (match === 'included') {
|
|
||||||
included = true;
|
|
||||||
}
|
|
||||||
if (match === 'excluded') {
|
|
||||||
excluded = true;
|
|
||||||
}
|
|
||||||
if (match === 'excludedScheme') {
|
|
||||||
excludedScheme = true;
|
|
||||||
}
|
|
||||||
for (const section of style.sections) {
|
|
||||||
const match = urlMatchSection(query, section, true);
|
|
||||||
if (match) {
|
|
||||||
if (match === 'sloppy') {
|
|
||||||
sloppy = true;
|
|
||||||
}
|
|
||||||
sectionMatched = true;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (sectionMatched || included) {
|
|
||||||
result.push(/** @namespace StylesByUrlResult */ {
|
|
||||||
style, excluded, sloppy, excludedScheme, sectionMatched, included});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return result;
|
|
||||||
},
|
|
||||||
|
|
||||||
/** @returns {Promise<StyleObj[]>} */
|
|
||||||
async importMany(items) {
|
|
||||||
if (ready.then) await ready;
|
|
||||||
for (const style of items) {
|
|
||||||
beforeSave(style);
|
|
||||||
if (style.sourceCode && style.usercssData) {
|
|
||||||
await usercssMan.buildCode(style);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
const events = await db.styles.putMany(items);
|
|
||||||
return Promise.all(items.map((item, i) =>
|
|
||||||
handleSave(item, {reason: 'import'}, events[i])
|
|
||||||
));
|
|
||||||
},
|
|
||||||
|
|
||||||
/** @returns {Promise<StyleObj>} */
|
|
||||||
async install(style, reason = null) {
|
|
||||||
if (ready.then) await ready;
|
|
||||||
reason = reason || dataMap.has(style.id) ? 'update' : 'install';
|
|
||||||
style = mergeWithMapped(style);
|
|
||||||
style.originalDigest = await calcStyleDigest(style);
|
|
||||||
// FIXME: update updateDate? what about usercss config?
|
|
||||||
return saveStyle(style, {reason});
|
|
||||||
},
|
|
||||||
|
|
||||||
save: saveStyle,
|
|
||||||
|
|
||||||
async setOrder(value) {
|
|
||||||
if (ready.then) await ready;
|
|
||||||
return setOrder({value}, {broadcast: true, sync: true});
|
|
||||||
},
|
|
||||||
|
|
||||||
/** @returns {Promise<number>} style id */
|
|
||||||
async toggle(id, enabled) {
|
|
||||||
if (ready.then) await ready;
|
|
||||||
const style = Object.assign({}, id2style(id), {enabled});
|
|
||||||
await saveStyle(style, {reason: 'toggle'});
|
|
||||||
return id;
|
|
||||||
},
|
|
||||||
|
|
||||||
// using bind() to skip step-into when debugging
|
|
||||||
|
|
||||||
/** @returns {Promise<StyleObj>} */
|
|
||||||
addExclusion: addIncludeExclude.bind(null, 'exclusions'),
|
|
||||||
/** @returns {Promise<StyleObj>} */
|
|
||||||
addInclusion: addIncludeExclude.bind(null, 'inclusions'),
|
|
||||||
/** @returns {Promise<?StyleObj>} */
|
|
||||||
removeExclusion: removeIncludeExclude.bind(null, 'exclusions'),
|
|
||||||
/** @returns {Promise<?StyleObj>} */
|
|
||||||
removeInclusion: removeIncludeExclude.bind(null, 'inclusions'),
|
|
||||||
|
|
||||||
async config(id, prop, value) {
|
|
||||||
if (ready.then) await ready;
|
|
||||||
const style = Object.assign({}, id2style(id));
|
|
||||||
const {preview = {}} = dataMap.get(id);
|
|
||||||
style[prop] = preview[prop] = value;
|
|
||||||
return saveStyle(style, {reason: 'config'});
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
//#endregion
|
|
||||||
//#region Implementation
|
|
||||||
|
|
||||||
/** @returns {StyleMapData} */
|
|
||||||
function id2data(id) {
|
|
||||||
return dataMap.get(id);
|
|
||||||
}
|
|
||||||
|
|
||||||
/** @returns {?StyleObj} */
|
|
||||||
function id2style(id) {
|
|
||||||
return (dataMap.get(Number(id)) || {}).style;
|
|
||||||
}
|
|
||||||
|
|
||||||
/** @returns {?StyleObj} */
|
|
||||||
function uuid2style(uuid) {
|
|
||||||
return id2style(uuidIndex.get(uuid));
|
|
||||||
}
|
|
||||||
|
|
||||||
function calcRemoteId({md5Url, updateUrl, usercssData: ucd} = {}) {
|
|
||||||
let id;
|
|
||||||
id = (id = /\d+/.test(md5Url) || URLS.extractUsoArchiveId(updateUrl)) && `uso-${id}`
|
|
||||||
|| (id = URLS.extractUSwId(updateUrl)) && `usw-${id}`
|
|
||||||
|| '';
|
|
||||||
return id && [
|
|
||||||
id,
|
|
||||||
ucd && !isEmptyObj(ucd.vars),
|
|
||||||
];
|
|
||||||
}
|
|
||||||
|
|
||||||
/** @returns {StyleObj} */
|
|
||||||
function createNewStyle() {
|
|
||||||
return /** @namespace StyleObj */ {
|
|
||||||
enabled: true,
|
|
||||||
updateUrl: null,
|
|
||||||
md5Url: null,
|
|
||||||
url: null,
|
|
||||||
originalMd5: null,
|
|
||||||
installDate: Date.now(),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
/** @returns {void} */
|
|
||||||
function storeInMap(style) {
|
|
||||||
dataMap.set(style.id, {
|
|
||||||
style,
|
|
||||||
appliesTo: new Set(),
|
|
||||||
});
|
|
||||||
uuidIndex.set(style._id, style.id);
|
|
||||||
}
|
|
||||||
|
|
||||||
/** @returns {StyleObj} */
|
|
||||||
function mergeWithMapped(style) {
|
|
||||||
return Object.assign({},
|
|
||||||
id2style(style.id) || createNewStyle(),
|
|
||||||
style);
|
|
||||||
}
|
|
||||||
|
|
||||||
function handleDraft(port) {
|
|
||||||
const id = port.name.split(':').pop();
|
|
||||||
port.onDisconnect.addListener(() => API.drafts.delete(Number(id) || id));
|
|
||||||
}
|
|
||||||
|
|
||||||
function handleLivePreview(port) {
|
|
||||||
let id;
|
|
||||||
port.onMessage.addListener(style => {
|
|
||||||
if (!id) id = style.id;
|
|
||||||
const data = id2data(id);
|
|
||||||
data.preview = style;
|
|
||||||
broadcastStyleUpdated(style, 'editPreview');
|
|
||||||
});
|
|
||||||
port.onDisconnect.addListener(() => {
|
|
||||||
port = null;
|
|
||||||
if (id) {
|
|
||||||
const data = id2data(id);
|
|
||||||
if (data) {
|
|
||||||
data.preview = null;
|
|
||||||
broadcastStyleUpdated(data.style, 'editPreviewEnd');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
async function addIncludeExclude(type, id, rule) {
|
|
||||||
if (ready.then) await ready;
|
|
||||||
const style = Object.assign({}, id2style(id));
|
|
||||||
const list = style[type] || (style[type] = []);
|
|
||||||
if (list.includes(rule)) {
|
|
||||||
throw new Error('The rule already exists');
|
|
||||||
}
|
|
||||||
style[type] = list.concat([rule]);
|
|
||||||
return saveStyle(style, {reason: 'config'});
|
|
||||||
}
|
|
||||||
|
|
||||||
async function removeIncludeExclude(type, id, rule) {
|
|
||||||
if (ready.then) await ready;
|
|
||||||
const style = Object.assign({}, id2style(id));
|
|
||||||
const list = style[type];
|
|
||||||
if (!list || !list.includes(rule)) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
style[type] = list.filter(r => r !== rule);
|
|
||||||
return saveStyle(style, {reason: 'config'});
|
|
||||||
}
|
|
||||||
|
|
||||||
function broadcastStyleUpdated(style, reason, method = 'styleUpdated') {
|
|
||||||
const {id} = style;
|
|
||||||
const data = id2data(id);
|
|
||||||
const excluded = new Set();
|
|
||||||
const updated = new Set();
|
|
||||||
for (const [url, cache] of cachedStyleForUrl.entries()) {
|
|
||||||
if (!data.appliesTo.has(url)) {
|
|
||||||
cache.maybeMatch.add(id);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
const code = getAppliedCode(new MatchQuery(url), style);
|
|
||||||
if (code) {
|
|
||||||
updated.add(url);
|
|
||||||
buildCacheEntry(cache, style, code);
|
|
||||||
} else {
|
|
||||||
excluded.add(url);
|
|
||||||
delete cache.sections[id];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
data.appliesTo = updated;
|
|
||||||
return msg.broadcast({
|
|
||||||
method,
|
|
||||||
reason,
|
|
||||||
style: {
|
|
||||||
id,
|
|
||||||
md5Url: style.md5Url,
|
|
||||||
enabled: style.enabled,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function beforeSave(style) {
|
|
||||||
if (!style.name) {
|
|
||||||
throw new Error('Style name is empty');
|
|
||||||
}
|
|
||||||
for (const key of DELETE_IF_NULL) {
|
|
||||||
if (style[key] == null) {
|
|
||||||
delete style[key];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (!style._id) {
|
|
||||||
style._id = uuidv4();
|
|
||||||
}
|
|
||||||
style._rev = Date.now();
|
|
||||||
fixKnownProblems(style);
|
|
||||||
}
|
|
||||||
|
|
||||||
async function saveStyle(style, handlingOptions) {
|
|
||||||
beforeSave(style);
|
|
||||||
const newId = await db.styles.put(style);
|
|
||||||
return handleSave(style, handlingOptions, newId);
|
|
||||||
}
|
|
||||||
|
|
||||||
function handleSave(style, {reason, broadcast = true}, id = style.id) {
|
|
||||||
if (style.id == null) style.id = id;
|
|
||||||
const data = id2data(id);
|
|
||||||
const method = data ? 'styleUpdated' : 'styleAdded';
|
|
||||||
if (!data) {
|
|
||||||
storeInMap(style);
|
|
||||||
} else {
|
|
||||||
data.style = style;
|
|
||||||
}
|
|
||||||
if (reason !== 'sync') {
|
|
||||||
API.sync.putDoc(style);
|
|
||||||
}
|
|
||||||
if (broadcast) broadcastStyleUpdated(style, reason, method);
|
|
||||||
return style;
|
|
||||||
}
|
|
||||||
|
|
||||||
// get styles matching a URL, including sloppy regexps and excluded items.
|
|
||||||
function getAppliedCode(query, data) {
|
|
||||||
const result = urlMatchStyle(query, data);
|
|
||||||
if (result === 'included') {
|
|
||||||
// return all sections
|
|
||||||
return data.sections.map(s => s.code);
|
|
||||||
}
|
|
||||||
if (result !== true) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const code = [];
|
|
||||||
for (const section of data.sections) {
|
|
||||||
if (urlMatchSection(query, section) === true && !styleCodeEmpty(section.code)) {
|
|
||||||
code.push(section.code);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return code.length && code;
|
|
||||||
}
|
|
||||||
|
|
||||||
async function init() {
|
|
||||||
const orderPromise = API.prefsDb.get(orderWrap.id);
|
|
||||||
const styles = await db.styles.getAll() || [];
|
|
||||||
const updated = await Promise.all(styles.map(fixKnownProblems).filter(Boolean));
|
|
||||||
if (updated.length) {
|
|
||||||
await db.styles.putMany(updated);
|
|
||||||
}
|
|
||||||
setOrder(await orderPromise, {store: false});
|
|
||||||
styles.forEach(storeInMap);
|
|
||||||
ready = true;
|
|
||||||
bgReady._resolveStyles();
|
|
||||||
}
|
|
||||||
|
|
||||||
function fixKnownProblems(style, initIndex, initArray) {
|
|
||||||
let res = 0;
|
|
||||||
for (const key in MISSING_PROPS) {
|
|
||||||
if (!style[key]) {
|
|
||||||
style[key] = MISSING_PROPS[key](style);
|
|
||||||
res = 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
/* Upgrade the old way of customizing local names */
|
|
||||||
const {originalName} = style;
|
|
||||||
if (originalName) {
|
|
||||||
if (originalName !== style.name) {
|
|
||||||
style.customName = style.name;
|
|
||||||
style.name = originalName;
|
|
||||||
}
|
|
||||||
delete style.originalName;
|
|
||||||
res = 1;
|
|
||||||
}
|
|
||||||
/* wrong homepage url in 1.5.20-1.5.21 due to commit 1e5f118d */
|
|
||||||
for (const key of ['url', 'installationUrl']) {
|
|
||||||
const url = style[key];
|
|
||||||
const fixedUrl = url && url.replace(/([^:]\/)\//, '$1');
|
|
||||||
if (fixedUrl !== url) {
|
|
||||||
res = 1;
|
|
||||||
style[key] = fixedUrl;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
let url;
|
|
||||||
/* USO bug, duplicate "update" subdomain, see #523 */
|
|
||||||
if ((url = style.md5Url) && url.includes('update.update.userstyles')) {
|
|
||||||
res = style.md5Url = url.replace('update.update.userstyles', 'update.userstyles');
|
|
||||||
}
|
|
||||||
/* Default homepage URL for external styles installed from a known distro */
|
|
||||||
if (
|
|
||||||
(!style.url || !style.installationUrl) &&
|
|
||||||
(url = style.updateUrl) &&
|
|
||||||
(url = URLS.extractGreasyForkInstallUrl(url) ||
|
|
||||||
URLS.extractUsoArchiveInstallUrl(url) ||
|
|
||||||
URLS.extractUSwInstallUrl(url)
|
|
||||||
)
|
|
||||||
) {
|
|
||||||
if (!style.url) res = style.url = url;
|
|
||||||
if (!style.installationUrl) res = style.installationUrl = url;
|
|
||||||
}
|
|
||||||
/* @import must precede `vars` that we add at beginning */
|
|
||||||
if (
|
|
||||||
initArray &&
|
|
||||||
!isEmptyObj((style.usercssData || {}).vars) &&
|
|
||||||
style.sections.some(({code}) =>
|
|
||||||
code.startsWith(':root {\n --') &&
|
|
||||||
/@import\s/i.test(code))
|
|
||||||
) {
|
|
||||||
return usercssMan.buildCode(style);
|
|
||||||
}
|
|
||||||
return res && style;
|
|
||||||
}
|
|
||||||
|
|
||||||
function urlMatchStyle(query, style) {
|
|
||||||
if (
|
|
||||||
style.exclusions &&
|
|
||||||
style.exclusions.some(e => compileExclusion(e).test(query.urlWithoutParams))
|
|
||||||
) {
|
|
||||||
return 'excluded';
|
|
||||||
}
|
|
||||||
if (!style.enabled) {
|
|
||||||
return 'disabled';
|
|
||||||
}
|
|
||||||
if (!colorScheme.shouldIncludeStyle(style)) {
|
|
||||||
return 'excludedScheme';
|
|
||||||
}
|
|
||||||
if (
|
|
||||||
style.inclusions &&
|
|
||||||
style.inclusions.some(r => compileExclusion(r).test(query.urlWithoutParams))
|
|
||||||
) {
|
|
||||||
return 'included';
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
function urlMatchSection(query, section, skipEmptyGlobal) {
|
|
||||||
let dd, ddL, pp, ppL, rr, rrL, uu, uuL;
|
|
||||||
if (
|
|
||||||
(dd = section.domains) && (ddL = dd.length) && dd.some(urlMatchDomain, query) ||
|
|
||||||
(pp = section.urlPrefixes) && (ppL = pp.length) && pp.some(urlMatchPrefix, query) ||
|
|
||||||
/* Per the specification the fragment portion is ignored in @-moz-document:
|
|
||||||
https://www.w3.org/TR/2012/WD-css3-conditional-20120911/#url-of-doc
|
|
||||||
but the spec is outdated and doesn't account for SPA sites,
|
|
||||||
so we only respect it for `url()` function */
|
|
||||||
(uu = section.urls) && (uuL = uu.length) && (
|
|
||||||
uu.includes(query.url) ||
|
|
||||||
uu.includes(query.urlWithoutHash)
|
|
||||||
) ||
|
|
||||||
(rr = section.regexps) && (rrL = rr.length) && rr.some(urlMatchRegexp, query)
|
|
||||||
) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
/*
|
|
||||||
According to CSS4 @document specification the entire URL must match.
|
|
||||||
Stylish-for-Chrome implemented it incorrectly since the very beginning.
|
|
||||||
We'll detect styles that abuse the bug by finding the sections that
|
|
||||||
would have been applied by Stylish but not by us as we follow the spec.
|
|
||||||
*/
|
|
||||||
if (rrL && rr.some(urlMatchRegexpSloppy, query)) {
|
|
||||||
return 'sloppy';
|
|
||||||
}
|
|
||||||
// TODO: check for invalid regexps?
|
|
||||||
return !rrL && !ppL && !uuL && !ddL &&
|
|
||||||
!query.isOwnPage && // We allow only intentionally targeted sections for own pages
|
|
||||||
(!skipEmptyGlobal || !styleCodeEmpty(section.code));
|
|
||||||
}
|
|
||||||
/** @this {MatchQuery} */
|
|
||||||
function urlMatchDomain(d) {
|
|
||||||
const _d = this.domain;
|
|
||||||
return d === _d ||
|
|
||||||
_d[_d.length - d.length - 1] === '.' && _d.endsWith(d);
|
|
||||||
}
|
|
||||||
/** @this {MatchQuery} */
|
|
||||||
function urlMatchPrefix(p) {
|
|
||||||
return p && this.url.startsWith(p);
|
|
||||||
}
|
|
||||||
/** @this {MatchQuery} */
|
|
||||||
function urlMatchRegexp(r) {
|
|
||||||
return (!this.isOwnPage || /\bextension\b/.test(r)) &&
|
|
||||||
compileRe(r).test(this.url);
|
|
||||||
}
|
|
||||||
/** @this {MatchQuery} */
|
|
||||||
function urlMatchRegexpSloppy(r) {
|
|
||||||
return (!this.isOwnPage || /\bextension\b/.test(r)) &&
|
|
||||||
compileSloppyRe(r).test(this.url);
|
|
||||||
}
|
|
||||||
|
|
||||||
function createCompiler(compile) {
|
|
||||||
// FIXME: FIFO cache doesn't work well here, if we want to match many
|
|
||||||
// regexps more than the cache size, we will never hit the cache because
|
|
||||||
// the first cache is deleted. So we use a simple map but it leaks memory.
|
|
||||||
const cache = new Map();
|
|
||||||
return text => {
|
|
||||||
let re = cache.get(text);
|
|
||||||
if (!re) {
|
|
||||||
re = tryRegExp(compile(text));
|
|
||||||
if (!re) {
|
|
||||||
re = BAD_MATCHER;
|
|
||||||
}
|
|
||||||
cache.set(text, re);
|
|
||||||
}
|
|
||||||
return re;
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
function compileGlob(text) {
|
|
||||||
return stringAsRegExp(text, '', true)
|
|
||||||
.replace(/\\\\\\\*|\\\*/g, m => m.length > 2 ? m : '.*');
|
|
||||||
}
|
|
||||||
|
|
||||||
function buildExclusion(text) {
|
|
||||||
// match pattern
|
|
||||||
const match = text.match(/^(\*|[\w-]+):\/\/(\*\.)?([\w.]+\/.*)/);
|
|
||||||
if (!match) {
|
|
||||||
return '^' + compileGlob(text) + '$';
|
|
||||||
}
|
|
||||||
return '^' +
|
|
||||||
(match[1] === '*' ? '[\\w-]+' : match[1]) +
|
|
||||||
'://' +
|
|
||||||
(match[2] ? '(?:[\\w.]+\\.)?' : '') +
|
|
||||||
compileGlob(match[3]) +
|
|
||||||
'$';
|
|
||||||
}
|
|
||||||
|
|
||||||
function buildCache(cache, url, styleList) {
|
|
||||||
const query = new MatchQuery(url);
|
|
||||||
for (const {style, appliesTo, preview} of styleList) {
|
|
||||||
const code = getAppliedCode(query, preview || style);
|
|
||||||
if (code) {
|
|
||||||
buildCacheEntry(cache, style, code);
|
|
||||||
appliesTo.add(url);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function buildCacheEntry(cache, style, code = style.code) {
|
|
||||||
cache.sections[style.id] = {
|
|
||||||
code,
|
|
||||||
id: style.id,
|
|
||||||
name: style.customName || style.name,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
/** @returns {StyleObj[]} */
|
|
||||||
function getAllAsArray() {
|
|
||||||
return Array.from(dataMap.values(), v => v.style);
|
|
||||||
}
|
|
||||||
|
|
||||||
/** uuidv4 helper: converts to a 4-digit hex string and adds "-" at required positions */
|
|
||||||
function hex4dashed(num, i) {
|
|
||||||
return (num + 0x10000).toString(16).slice(-4) + (i >= 1 && i <= 4 ? '-' : '');
|
|
||||||
}
|
|
||||||
|
|
||||||
async function setOrder(data, {broadcast, calc = true, store = true, sync} = {}) {
|
|
||||||
if (!data || !data.value || deepEqual(data.value, orderWrap.value)) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
Object.assign(orderWrap, data, sync && {_rev: Date.now()});
|
|
||||||
if (calc) {
|
|
||||||
for (const [type, group] of Object.entries(data.value)) {
|
|
||||||
const dst = order[type] = {};
|
|
||||||
group.forEach((uuid, i) => {
|
|
||||||
const id = uuidIndex.get(uuid);
|
|
||||||
if (id) dst[id] = i;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (broadcast) {
|
|
||||||
msg.broadcast({method: 'styleSort', order});
|
|
||||||
}
|
|
||||||
if (store) {
|
|
||||||
await API.prefsDb.put(orderWrap, orderWrap.id);
|
|
||||||
}
|
|
||||||
if (sync) {
|
|
||||||
API.sync.putDoc(orderWrap);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
//#endregion
|
|
||||||
})();
|
|
|
@ -1,108 +0,0 @@
|
||||||
/* global API */// msg.js
|
|
||||||
/* global RX_META debounce stringAsRegExp tryRegExp */// toolbox.js
|
|
||||||
/* global addAPI */// common.js
|
|
||||||
'use strict';
|
|
||||||
|
|
||||||
(() => {
|
|
||||||
// toLocaleLowerCase cache, autocleared after 1 minute
|
|
||||||
const cache = new Map();
|
|
||||||
const METAKEYS = ['customName', 'name', 'url', 'installationUrl', 'updateUrl'];
|
|
||||||
|
|
||||||
const extractMeta = style =>
|
|
||||||
style.usercssData
|
|
||||||
? (style.sourceCode.match(RX_META) || [''])[0]
|
|
||||||
: null;
|
|
||||||
|
|
||||||
const stripMeta = style =>
|
|
||||||
style.usercssData
|
|
||||||
? style.sourceCode.replace(RX_META, '')
|
|
||||||
: null;
|
|
||||||
|
|
||||||
const MODES = Object.assign(Object.create(null), {
|
|
||||||
code: (style, test) =>
|
|
||||||
style.usercssData
|
|
||||||
? test(stripMeta(style))
|
|
||||||
: searchSections(style, test, 'code'),
|
|
||||||
|
|
||||||
meta: (style, test, part) =>
|
|
||||||
METAKEYS.some(key => test(style[key])) ||
|
|
||||||
test(part === 'all' ? style.sourceCode : extractMeta(style)) ||
|
|
||||||
searchSections(style, test, 'funcs'),
|
|
||||||
|
|
||||||
name: (style, test) =>
|
|
||||||
test(style.customName) ||
|
|
||||||
test(style.name),
|
|
||||||
|
|
||||||
all: (style, test) =>
|
|
||||||
MODES.meta(style, test, 'all') ||
|
|
||||||
!style.usercssData && MODES.code(style, test),
|
|
||||||
});
|
|
||||||
|
|
||||||
addAPI(/** @namespace API */ {
|
|
||||||
styles: {
|
|
||||||
/**
|
|
||||||
* @param params
|
|
||||||
* @param {string} params.query - 1. url:someurl 2. text (may contain quoted parts like "qUot Ed")
|
|
||||||
* @param {'name'|'meta'|'code'|'all'|'url'} [params.mode=all]
|
|
||||||
* @param {number[]} [params.ids] - if not specified, all styles are searched
|
|
||||||
* @returns {number[]} - array of matched styles ids
|
|
||||||
*/
|
|
||||||
async searchDB({query, mode = 'all', ids}) {
|
|
||||||
let res = [];
|
|
||||||
if (mode === 'url' && query) {
|
|
||||||
res = (await API.styles.getByUrl(query)).map(r => r.style.id);
|
|
||||||
} else if (mode in MODES) {
|
|
||||||
const modeHandler = MODES[mode];
|
|
||||||
const m = /^\/(.+?)\/([gimsuy]*)$/.exec(query);
|
|
||||||
const rx = m && tryRegExp(m[1], m[2]);
|
|
||||||
const test = rx ? rx.test.bind(rx) : createTester(query);
|
|
||||||
res = (await API.styles.getAll())
|
|
||||||
.filter(style =>
|
|
||||||
(!ids || ids.includes(style.id)) &&
|
|
||||||
(!query || modeHandler(style, test)))
|
|
||||||
.map(style => style.id);
|
|
||||||
if (cache.size) debounce(clearCache, 60e3);
|
|
||||||
}
|
|
||||||
return res;
|
|
||||||
},
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
function createTester(query) {
|
|
||||||
const flags = `u${lower(query) === query ? 'i' : ''}`;
|
|
||||||
const words = query
|
|
||||||
.split(/(".*?")|\s+/)
|
|
||||||
.filter(Boolean)
|
|
||||||
.map(w => w.startsWith('"') && w.endsWith('"')
|
|
||||||
? w.slice(1, -1)
|
|
||||||
: w)
|
|
||||||
.filter(w => w.length > 1);
|
|
||||||
const rxs = (words.length ? words : [query])
|
|
||||||
.map(w => stringAsRegExp(w, flags));
|
|
||||||
return text => rxs.every(rx => rx.test(text));
|
|
||||||
}
|
|
||||||
|
|
||||||
function searchSections({sections}, test, part) {
|
|
||||||
const inCode = part === 'code' || part === 'all';
|
|
||||||
const inFuncs = part === 'funcs' || part === 'all';
|
|
||||||
for (const section of sections) {
|
|
||||||
for (const prop in section) {
|
|
||||||
const value = section[prop];
|
|
||||||
if (inCode && prop === 'code' && test(value) ||
|
|
||||||
inFuncs && Array.isArray(value) && value.some(str => test(str))) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function lower(text) {
|
|
||||||
let result = cache.get(text);
|
|
||||||
if (!result) cache.set(text, result = text.toLocaleLowerCase());
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
function clearCache() {
|
|
||||||
cache.clear();
|
|
||||||
}
|
|
||||||
})();
|
|
|
@ -1,14 +1,7 @@
|
||||||
/* global API */// msg.js
|
/* global getStyles API_METHODS */
|
||||||
/* global addAPI */// common.js
|
|
||||||
/* global isEmptyObj */// toolbox.js
|
|
||||||
/* global prefs */
|
|
||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
/**
|
API_METHODS.styleViaAPI = !CHROME && (() => {
|
||||||
* Uses chrome.tabs.insertCSS
|
|
||||||
*/
|
|
||||||
|
|
||||||
(() => {
|
|
||||||
const ACTIONS = {
|
const ACTIONS = {
|
||||||
styleApply,
|
styleApply,
|
||||||
styleDeleted,
|
styleDeleted,
|
||||||
|
@ -16,38 +9,28 @@
|
||||||
styleAdded,
|
styleAdded,
|
||||||
styleReplaceAll,
|
styleReplaceAll,
|
||||||
prefChanged,
|
prefChanged,
|
||||||
updateCount,
|
|
||||||
};
|
};
|
||||||
const NOP = new Error('NOP');
|
const NOP = Promise.resolve(new Error('NOP'));
|
||||||
const onError = () => {};
|
const onError = () => {};
|
||||||
|
|
||||||
/* <tabId>: Object
|
/* <tabId>: Object
|
||||||
<frameId>: Object
|
<frameId>: Object
|
||||||
url: String, non-enumerable
|
url: String, non-enumerable
|
||||||
<styleId>: Array of strings
|
<styleId>: Array of strings
|
||||||
section code */
|
section code */
|
||||||
const cache = new Map();
|
const cache = new Map();
|
||||||
|
|
||||||
let observingTabs = false;
|
let observingTabs = false;
|
||||||
|
|
||||||
addAPI(/** @namespace API */ {
|
return (request, sender) => {
|
||||||
async styleViaAPI(request) {
|
const action = ACTIONS[request.action];
|
||||||
try {
|
return !action ? NOP :
|
||||||
const fn = ACTIONS[request.method];
|
action(request, sender)
|
||||||
return fn ? fn(request, this.sender) : NOP;
|
.catch(onError)
|
||||||
} catch (e) {}
|
.then(maybeToggleObserver);
|
||||||
maybeToggleObserver();
|
};
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
function updateCount(request, sender) {
|
function styleApply({id = null, ignoreUrlCheck}, {tab, frameId, url}) {
|
||||||
const {tab, frameId} = sender;
|
|
||||||
if (frameId) {
|
|
||||||
throw new Error('we do not count styles for frames');
|
|
||||||
}
|
|
||||||
const {frameStyles} = getCachedData(tab.id, frameId);
|
|
||||||
API.updateIconBadge.call({sender}, Object.keys(frameStyles));
|
|
||||||
}
|
|
||||||
|
|
||||||
function styleApply({id = null, ignoreUrlCheck = false}, {tab, frameId, url}) {
|
|
||||||
if (prefs.get('disableAll')) {
|
if (prefs.get('disableAll')) {
|
||||||
return NOP;
|
return NOP;
|
||||||
}
|
}
|
||||||
|
@ -55,16 +38,24 @@
|
||||||
if (id === null && !ignoreUrlCheck && frameStyles.url === url) {
|
if (id === null && !ignoreUrlCheck && frameStyles.url === url) {
|
||||||
return NOP;
|
return NOP;
|
||||||
}
|
}
|
||||||
return API.styles.getSectionsByUrl(url, id).then(sections => {
|
return getStyles({id, matchUrl: url, asHash: true}).then(styles => {
|
||||||
delete sections.cfg;
|
|
||||||
const tasks = [];
|
const tasks = [];
|
||||||
for (const section of Object.values(sections)) {
|
for (const styleId in styles) {
|
||||||
const styleId = section.id;
|
if (isNaN(parseInt(styleId))) {
|
||||||
const code = section.code.join('\n');
|
continue;
|
||||||
|
}
|
||||||
|
// shallow-extract code from the sections array in order to reuse references
|
||||||
|
// in other places whereas the combined string gets garbage-collected
|
||||||
|
const styleSections = styles[styleId].map(section => section.code);
|
||||||
|
const code = styleSections.join('\n');
|
||||||
|
if (!code) {
|
||||||
|
delete frameStyles[styleId];
|
||||||
|
continue;
|
||||||
|
}
|
||||||
if (code === (frameStyles[styleId] || []).join('\n')) {
|
if (code === (frameStyles[styleId] || []).join('\n')) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
frameStyles[styleId] = section.code;
|
frameStyles[styleId] = styleSections;
|
||||||
tasks.push(
|
tasks.push(
|
||||||
browser.tabs.insertCSS(tab.id, {
|
browser.tabs.insertCSS(tab.id, {
|
||||||
code,
|
code,
|
||||||
|
@ -79,18 +70,16 @@
|
||||||
cache.set(tab.id, tabFrames);
|
cache.set(tab.id, tabFrames);
|
||||||
}
|
}
|
||||||
return Promise.all(tasks);
|
return Promise.all(tasks);
|
||||||
})
|
});
|
||||||
.then(() => updateCount(null, {tab, frameId}));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function styleDeleted({style: {id}}, {tab, frameId}) {
|
function styleDeleted({id}, {tab, frameId}) {
|
||||||
const {tabFrames, frameStyles, styleSections} = getCachedData(tab.id, frameId, id);
|
const {tabFrames, frameStyles, styleSections} = getCachedData(tab.id, frameId, id);
|
||||||
const code = styleSections.join('\n');
|
const code = styleSections.join('\n');
|
||||||
if (code && !duplicateCodeExists({frameStyles, id, code})) {
|
if (code && !duplicateCodeExists({frameStyles, id, code})) {
|
||||||
delete frameStyles[id];
|
delete frameStyles[id];
|
||||||
removeFrameIfEmpty(tab.id, frameId, tabFrames, frameStyles);
|
removeFrameIfEmpty(tab.id, frameId, tabFrames, frameStyles);
|
||||||
return removeCSS(tab.id, frameId, code)
|
return removeCSS(tab.id, frameId, code);
|
||||||
.then(() => updateCount(null, {tab, frameId}));
|
|
||||||
} else {
|
} else {
|
||||||
return NOP;
|
return NOP;
|
||||||
}
|
}
|
||||||
|
@ -98,7 +87,7 @@
|
||||||
|
|
||||||
function styleUpdated({style}, sender) {
|
function styleUpdated({style}, sender) {
|
||||||
if (!style.enabled) {
|
if (!style.enabled) {
|
||||||
return styleDeleted({style}, sender);
|
return styleDeleted(style, sender);
|
||||||
}
|
}
|
||||||
const {tab, frameId} = sender;
|
const {tab, frameId} = sender;
|
||||||
const {frameStyles, styleSections} = getCachedData(tab.id, frameId, style.id);
|
const {frameStyles, styleSections} = getCachedData(tab.id, frameId, style.id);
|
||||||
|
@ -133,7 +122,7 @@
|
||||||
}
|
}
|
||||||
const {tab, frameId} = sender;
|
const {tab, frameId} = sender;
|
||||||
const {tabFrames, frameStyles} = getCachedData(tab.id, frameId);
|
const {tabFrames, frameStyles} = getCachedData(tab.id, frameId);
|
||||||
if (isEmptyObj(frameStyles)) {
|
if (isEmpty(frameStyles)) {
|
||||||
return NOP;
|
return NOP;
|
||||||
}
|
}
|
||||||
removeFrameIfEmpty(tab.id, frameId, tabFrames, {});
|
removeFrameIfEmpty(tab.id, frameId, tabFrames, {});
|
||||||
|
@ -168,9 +157,9 @@
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const tabFrames = cache.get(tabId);
|
const tabFrames = cache.get(tabId);
|
||||||
if (tabFrames && frameId in tabFrames) {
|
if (frameId in tabFrames) {
|
||||||
delete tabFrames[frameId];
|
delete tabFrames[frameId];
|
||||||
if (isEmptyObj(tabFrames)) {
|
if (isEmpty(tabFrames)) {
|
||||||
onTabRemoved(tabId);
|
onTabRemoved(tabId);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -186,9 +175,9 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
function removeFrameIfEmpty(tabId, frameId, tabFrames, frameStyles) {
|
function removeFrameIfEmpty(tabId, frameId, tabFrames, frameStyles) {
|
||||||
if (isEmptyObj(frameStyles)) {
|
if (isEmpty(frameStyles)) {
|
||||||
delete tabFrames[frameId];
|
delete tabFrames[frameId];
|
||||||
if (isEmptyObj(tabFrames)) {
|
if (isEmpty(tabFrames)) {
|
||||||
cache.delete(tabId);
|
cache.delete(tabId);
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
|
@ -231,4 +220,11 @@
|
||||||
return browser.tabs.removeCSS(tabId, {frameId, code, matchAboutBlank: true})
|
return browser.tabs.removeCSS(tabId, {frameId, code, matchAboutBlank: true})
|
||||||
.catch(onError);
|
.catch(onError);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function isEmpty(obj) {
|
||||||
|
for (const k in obj) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
})();
|
})();
|
||||||
|
|
|
@ -1,166 +0,0 @@
|
||||||
/* global API */// msg.js
|
|
||||||
/* global CHROME URLS ignoreChromeError */// toolbox.js
|
|
||||||
/* global prefs */
|
|
||||||
'use strict';
|
|
||||||
|
|
||||||
(() => {
|
|
||||||
const idCSP = 'patchCsp';
|
|
||||||
const idOFF = 'disableAll';
|
|
||||||
const idXHR = 'styleViaXhr';
|
|
||||||
const rxHOST = /^('none'|(https?:\/\/)?[^']+?[^:'])$/; // strips CSP sources covered by *
|
|
||||||
const blobUrlPrefix = 'blob:' + chrome.runtime.getURL('/');
|
|
||||||
/** @type {Object<string,StylesToPass>} */
|
|
||||||
const stylesToPass = {};
|
|
||||||
const state = {};
|
|
||||||
const injectedCode = CHROME && `${data => {
|
|
||||||
if (self.INJECTED !== 1) { // storing data only if apply.js hasn't run yet
|
|
||||||
window[Symbol.for('styles')] = data;
|
|
||||||
}
|
|
||||||
}}`;
|
|
||||||
|
|
||||||
toggle();
|
|
||||||
prefs.subscribe([idXHR, idOFF, idCSP], toggle);
|
|
||||||
|
|
||||||
function toggle() {
|
|
||||||
const off = prefs.get(idOFF);
|
|
||||||
const csp = prefs.get(idCSP) && !off;
|
|
||||||
const xhr = prefs.get(idXHR) && !off;
|
|
||||||
if (xhr === state.xhr && csp === state.csp && off === state.off) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const reqFilter = {
|
|
||||||
urls: ['*://*/*'],
|
|
||||||
types: ['main_frame', 'sub_frame'],
|
|
||||||
};
|
|
||||||
chrome.webNavigation.onCommitted.removeListener(injectData);
|
|
||||||
chrome.webRequest.onBeforeRequest.removeListener(prepareStyles);
|
|
||||||
chrome.webRequest.onHeadersReceived.removeListener(modifyHeaders);
|
|
||||||
if (xhr || csp) {
|
|
||||||
// We unregistered it above so that the optional EXTRA_HEADERS is properly re-registered
|
|
||||||
chrome.webRequest.onHeadersReceived.addListener(modifyHeaders, reqFilter, [
|
|
||||||
'blocking',
|
|
||||||
'responseHeaders',
|
|
||||||
xhr && chrome.webRequest.OnHeadersReceivedOptions.EXTRA_HEADERS,
|
|
||||||
].filter(Boolean));
|
|
||||||
}
|
|
||||||
if (CHROME ? !off : xhr || csp) {
|
|
||||||
chrome.webRequest.onBeforeRequest.addListener(prepareStyles, reqFilter);
|
|
||||||
}
|
|
||||||
if (CHROME && !off) {
|
|
||||||
chrome.webNavigation.onCommitted.addListener(injectData, {url: [{urlPrefix: 'http'}]});
|
|
||||||
}
|
|
||||||
if (CHROME) {
|
|
||||||
chrome.webRequest.onBeforeRequest.addListener(openNamedStyle, {
|
|
||||||
urls: [URLS.ownOrigin + '*.user.css'],
|
|
||||||
types: ['main_frame'],
|
|
||||||
}, ['blocking']);
|
|
||||||
}
|
|
||||||
state.csp = csp;
|
|
||||||
state.off = off;
|
|
||||||
state.xhr = xhr;
|
|
||||||
}
|
|
||||||
|
|
||||||
/** @param {chrome.webRequest.WebRequestBodyDetails} req */
|
|
||||||
async function prepareStyles(req) {
|
|
||||||
const sections = await API.styles.getSectionsByUrl(req.url);
|
|
||||||
stylesToPass[req2key(req)] = /** @namespace StylesToPass */ {
|
|
||||||
blobId: '',
|
|
||||||
str: JSON.stringify(sections),
|
|
||||||
timer: setTimeout(cleanUp, 600e3, req),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
function injectData(req) {
|
|
||||||
const data = stylesToPass[req2key(req)];
|
|
||||||
if (data && !data.injected) {
|
|
||||||
data.injected = true;
|
|
||||||
chrome.tabs.executeScript(req.tabId, {
|
|
||||||
frameId: req.frameId,
|
|
||||||
runAt: 'document_start',
|
|
||||||
code: `(${injectedCode})(${data.str})`,
|
|
||||||
}, ignoreChromeError);
|
|
||||||
if (!state.xhr) cleanUp(req);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/** @param {chrome.webRequest.WebResponseHeadersDetails} req */
|
|
||||||
function modifyHeaders(req) {
|
|
||||||
const {responseHeaders} = req;
|
|
||||||
const data = stylesToPass[req2key(req)];
|
|
||||||
if (!data || data.str === '{}') {
|
|
||||||
cleanUp(req);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (state.xhr) {
|
|
||||||
data.blobId = URL.createObjectURL(new Blob([data.str])).slice(blobUrlPrefix.length);
|
|
||||||
responseHeaders.push({
|
|
||||||
name: 'Set-Cookie',
|
|
||||||
value: `${chrome.runtime.id}=${data.blobId}`,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
const csp = state.csp &&
|
|
||||||
responseHeaders.find(h => h.name.toLowerCase() === 'content-security-policy');
|
|
||||||
if (csp) {
|
|
||||||
patchCsp(csp);
|
|
||||||
}
|
|
||||||
if (state.xhr || csp) {
|
|
||||||
return {responseHeaders};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/** @param {chrome.webRequest.HttpHeader} csp */
|
|
||||||
function patchCsp(csp) {
|
|
||||||
const src = {};
|
|
||||||
for (let p of csp.value.split(';')) {
|
|
||||||
p = p.trim().split(/\s+/);
|
|
||||||
src[p[0]] = p.slice(1);
|
|
||||||
}
|
|
||||||
// Allow style assets
|
|
||||||
patchCspSrc(src, 'img-src', 'data:', '*');
|
|
||||||
patchCspSrc(src, 'font-src', 'data:', '*');
|
|
||||||
// Allow our DOM styles, allow @import from any URL
|
|
||||||
patchCspSrc(src, 'style-src', "'unsafe-inline'", '*');
|
|
||||||
// Allow our XHR cookies in CSP sandbox (known case: raw github urls)
|
|
||||||
if (src.sandbox && !src.sandbox.includes('allow-same-origin')) {
|
|
||||||
src.sandbox.push('allow-same-origin');
|
|
||||||
}
|
|
||||||
csp.value = Object.entries(src).map(([k, v]) =>
|
|
||||||
`${k}${v.length ? ' ' : ''}${v.join(' ')}`).join('; ');
|
|
||||||
}
|
|
||||||
|
|
||||||
function patchCspSrc(src, name, ...values) {
|
|
||||||
let def = src['default-src'];
|
|
||||||
let list = src[name];
|
|
||||||
if (def || list) {
|
|
||||||
if (!def) def = [];
|
|
||||||
if (!list) list = [...def];
|
|
||||||
if (values.includes('*')) list = src[name] = list.filter(v => !rxHOST.test(v));
|
|
||||||
list.push(...values.filter(v => !list.includes(v) && !def.includes(v)));
|
|
||||||
if (!list.length) delete src[name];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function cleanUp(req) {
|
|
||||||
const key = req2key(req);
|
|
||||||
const data = stylesToPass[key];
|
|
||||||
if (data) {
|
|
||||||
delete stylesToPass[key];
|
|
||||||
clearTimeout(data.timer);
|
|
||||||
if (data.blobId) {
|
|
||||||
URL.revokeObjectURL(blobUrlPrefix + data.blobId);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/** @param {chrome.webRequest.WebRequestBodyDetails} req */
|
|
||||||
function openNamedStyle(req) {
|
|
||||||
if (!req.url.includes('?')) { // skipping our usercss installer
|
|
||||||
chrome.tabs.update(req.tabId, {url: 'edit.html?id=' + req.url.split('#')[1]});
|
|
||||||
return {cancel: true};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function req2key(req) {
|
|
||||||
return req.tabId + ':' + req.frameId;
|
|
||||||
}
|
|
||||||
})();
|
|
|
@ -1,307 +0,0 @@
|
||||||
/* global API msg */// msg.js
|
|
||||||
/* global bgReady uuidIndex */// common.js
|
|
||||||
/* global chromeLocal chromeSync */// storage-util.js
|
|
||||||
/* global db */
|
|
||||||
/* global iconMan */
|
|
||||||
/* global prefs */
|
|
||||||
/* global styleUtil */
|
|
||||||
/* global tokenMan */
|
|
||||||
'use strict';
|
|
||||||
|
|
||||||
const syncMan = (() => {
|
|
||||||
//#region Init
|
|
||||||
|
|
||||||
const SYNC_DELAY = 1; // minutes
|
|
||||||
const SYNC_INTERVAL = 30; // minutes
|
|
||||||
const STATES = Object.freeze({
|
|
||||||
connected: 'connected',
|
|
||||||
connecting: 'connecting',
|
|
||||||
disconnected: 'disconnected',
|
|
||||||
disconnecting: 'disconnecting',
|
|
||||||
});
|
|
||||||
const STORAGE_KEY = 'sync/state/';
|
|
||||||
const NO_LOGIN = ['webdav'];
|
|
||||||
const status = /** @namespace SyncManager.Status */ {
|
|
||||||
STATES,
|
|
||||||
state: STATES.disconnected,
|
|
||||||
syncing: false,
|
|
||||||
progress: null,
|
|
||||||
currentDriveName: null,
|
|
||||||
errorMessage: null,
|
|
||||||
login: false,
|
|
||||||
};
|
|
||||||
const compareRevision = (rev1, rev2) => rev1 - rev2;
|
|
||||||
let lastError = null;
|
|
||||||
let ctrl;
|
|
||||||
let currentDrive;
|
|
||||||
/** @type {Promise|boolean} will be `true` to avoid wasting a microtask tick on each `await` */
|
|
||||||
let ready = bgReady.styles.then(() => {
|
|
||||||
ready = true;
|
|
||||||
prefs.subscribe('sync.enabled',
|
|
||||||
(_, val) => val === 'none'
|
|
||||||
? syncMan.stop()
|
|
||||||
: syncMan.start(val, true),
|
|
||||||
{runNow: true});
|
|
||||||
});
|
|
||||||
|
|
||||||
chrome.alarms.onAlarm.addListener(async ({name}) => {
|
|
||||||
if (name === 'syncNow') {
|
|
||||||
await syncMan.syncNow();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
//#endregion
|
|
||||||
//#region Exports
|
|
||||||
|
|
||||||
return {
|
|
||||||
|
|
||||||
async delete(...args) {
|
|
||||||
if (ready.then) await ready;
|
|
||||||
if (!currentDrive) return;
|
|
||||||
schedule();
|
|
||||||
return ctrl.delete(...args);
|
|
||||||
},
|
|
||||||
|
|
||||||
/** @returns {Promise<SyncManager.Status>} */
|
|
||||||
async getStatus() {
|
|
||||||
return status;
|
|
||||||
},
|
|
||||||
|
|
||||||
async login(name) {
|
|
||||||
if (ready.then) await ready;
|
|
||||||
if (!name) name = prefs.get('sync.enabled');
|
|
||||||
await tokenMan.revokeToken(name);
|
|
||||||
try {
|
|
||||||
await tokenMan.getToken(name, true);
|
|
||||||
status.login = true;
|
|
||||||
} catch (err) {
|
|
||||||
status.login = false;
|
|
||||||
throw err;
|
|
||||||
} finally {
|
|
||||||
emitStatusChange();
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
async putDoc({_id, _rev}) {
|
|
||||||
if (ready.then) await ready;
|
|
||||||
if (!currentDrive) return;
|
|
||||||
schedule();
|
|
||||||
return ctrl.put(_id, _rev);
|
|
||||||
},
|
|
||||||
|
|
||||||
async setDriveOptions(driveName, options) {
|
|
||||||
const key = `secure/sync/driveOptions/${driveName}`;
|
|
||||||
await chromeSync.setValue(key, options);
|
|
||||||
},
|
|
||||||
|
|
||||||
async getDriveOptions(driveName) {
|
|
||||||
const key = `secure/sync/driveOptions/${driveName}`;
|
|
||||||
return await chromeSync.getValue(key) || {};
|
|
||||||
},
|
|
||||||
|
|
||||||
async start(name, fromPref = false) {
|
|
||||||
if (ready.then) await ready;
|
|
||||||
if (!ctrl) await initController();
|
|
||||||
|
|
||||||
if (currentDrive) return;
|
|
||||||
currentDrive = await getDrive(name);
|
|
||||||
ctrl.use(currentDrive);
|
|
||||||
|
|
||||||
status.state = STATES.connecting;
|
|
||||||
status.currentDriveName = currentDrive.name;
|
|
||||||
emitStatusChange();
|
|
||||||
|
|
||||||
if (fromPref || NO_LOGIN.includes(currentDrive.name)) {
|
|
||||||
status.login = true;
|
|
||||||
} else {
|
|
||||||
try {
|
|
||||||
await syncMan.login(name);
|
|
||||||
} catch (err) {
|
|
||||||
console.error(err);
|
|
||||||
status.errorMessage = err.message;
|
|
||||||
lastError = err;
|
|
||||||
emitStatusChange();
|
|
||||||
return syncMan.stop();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
await ctrl.init();
|
|
||||||
|
|
||||||
await syncMan.syncNow(name);
|
|
||||||
prefs.set('sync.enabled', name);
|
|
||||||
status.state = STATES.connected;
|
|
||||||
schedule(SYNC_INTERVAL);
|
|
||||||
emitStatusChange();
|
|
||||||
},
|
|
||||||
|
|
||||||
async stop() {
|
|
||||||
if (ready.then) await ready;
|
|
||||||
if (!currentDrive) return;
|
|
||||||
chrome.alarms.clear('syncNow');
|
|
||||||
status.state = STATES.disconnecting;
|
|
||||||
emitStatusChange();
|
|
||||||
try {
|
|
||||||
await ctrl.uninit();
|
|
||||||
await tokenMan.revokeToken(currentDrive.name);
|
|
||||||
await chromeLocal.remove(STORAGE_KEY + currentDrive.name);
|
|
||||||
} catch (e) {}
|
|
||||||
currentDrive = null;
|
|
||||||
prefs.set('sync.enabled', 'none');
|
|
||||||
status.state = STATES.disconnected;
|
|
||||||
status.currentDriveName = null;
|
|
||||||
status.login = false;
|
|
||||||
emitStatusChange();
|
|
||||||
},
|
|
||||||
|
|
||||||
async syncNow() {
|
|
||||||
if (ready.then) await ready;
|
|
||||||
if (!currentDrive || !status.login) {
|
|
||||||
console.warn('cannot sync when disconnected');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
try {
|
|
||||||
await ctrl.syncNow();
|
|
||||||
status.errorMessage = null;
|
|
||||||
lastError = null;
|
|
||||||
} catch (err) {
|
|
||||||
err.message = translateErrorMessage(err);
|
|
||||||
status.errorMessage = err.message;
|
|
||||||
lastError = err;
|
|
||||||
if (isGrantError(err)) {
|
|
||||||
status.login = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
emitStatusChange();
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
//#endregion
|
|
||||||
//#region Utils
|
|
||||||
|
|
||||||
async function initController() {
|
|
||||||
await require(['/vendor/db-to-cloud/db-to-cloud']); /* global dbToCloud */
|
|
||||||
ctrl = dbToCloud.dbToCloud({
|
|
||||||
onGet: styleUtil.uuid2style,
|
|
||||||
async onPut(doc) {
|
|
||||||
const id = uuidIndex.get(doc._id);
|
|
||||||
const oldCust = uuidIndex.custom[id];
|
|
||||||
const oldDoc = oldCust || styleUtil.id2style(id);
|
|
||||||
const diff = oldDoc ? compareRevision(oldDoc._rev, doc._rev) : -1;
|
|
||||||
if (!diff) return;
|
|
||||||
if (diff > 0) {
|
|
||||||
syncMan.putDoc(oldDoc);
|
|
||||||
} else if (oldCust) {
|
|
||||||
uuidIndex.custom[id] = doc;
|
|
||||||
} else {
|
|
||||||
delete doc.id;
|
|
||||||
if (id) doc.id = id;
|
|
||||||
doc.id = await db.styles.put(doc);
|
|
||||||
await styleUtil.handleSave(doc, {reason: 'sync'});
|
|
||||||
}
|
|
||||||
},
|
|
||||||
onDelete(_id, rev) {
|
|
||||||
const id = uuidIndex.get(_id);
|
|
||||||
const oldDoc = styleUtil.id2style(id);
|
|
||||||
return oldDoc &&
|
|
||||||
compareRevision(oldDoc._rev, rev) <= 0 &&
|
|
||||||
API.styles.delete(id, 'sync');
|
|
||||||
},
|
|
||||||
async onFirstSync() {
|
|
||||||
for (const i of Object.values(uuidIndex.custom).concat(await API.styles.getAll())) {
|
|
||||||
ctrl.put(i._id, i._rev);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
onProgress(e) {
|
|
||||||
if (e.phase === 'start') {
|
|
||||||
status.syncing = true;
|
|
||||||
} else if (e.phase === 'end') {
|
|
||||||
status.syncing = false;
|
|
||||||
status.progress = null;
|
|
||||||
} else {
|
|
||||||
status.progress = e;
|
|
||||||
}
|
|
||||||
emitStatusChange();
|
|
||||||
},
|
|
||||||
compareRevision,
|
|
||||||
getState(drive) {
|
|
||||||
return chromeLocal.getValue(STORAGE_KEY + drive.name);
|
|
||||||
},
|
|
||||||
setState(drive, state) {
|
|
||||||
return chromeLocal.setValue(STORAGE_KEY + drive.name, state);
|
|
||||||
},
|
|
||||||
retryMaxAttempts: 10,
|
|
||||||
retryExp: 1.2,
|
|
||||||
retryDelay: 6,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function emitStatusChange() {
|
|
||||||
msg.broadcastExtension({method: 'syncStatusUpdate', status});
|
|
||||||
iconMan.overrideBadge(getErrorBadge());
|
|
||||||
}
|
|
||||||
|
|
||||||
function isNetworkError(err) {
|
|
||||||
return (
|
|
||||||
err.name === 'TypeError' && /networkerror|failed to fetch/i.test(err.message) ||
|
|
||||||
err.code === 502
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
function isGrantError(err) {
|
|
||||||
if (err.code === 401) return true;
|
|
||||||
if (err.code === 400 && /invalid_grant/.test(err.message)) return true;
|
|
||||||
if (err.name === 'TokenError') return true;
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
function getErrorBadge() {
|
|
||||||
if (status.state === STATES.connected &&
|
|
||||||
(!status.login || lastError && !isNetworkError(lastError))) {
|
|
||||||
return {
|
|
||||||
text: 'x',
|
|
||||||
color: '#F00',
|
|
||||||
title: !status.login ? 'syncErrorRelogin' : `${
|
|
||||||
chrome.i18n.getMessage('syncError')
|
|
||||||
}\n---------------------\n${
|
|
||||||
// splitting to limit each line length
|
|
||||||
lastError.message.replace(/.{60,}?\s(?=.{30,})/g, '$&\n')
|
|
||||||
}`,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async function getDrive(name) {
|
|
||||||
if (name === 'dropbox' || name === 'google' || name === 'onedrive' || name === 'webdav') {
|
|
||||||
const options = await syncMan.getDriveOptions(name);
|
|
||||||
options.getAccessToken = () => tokenMan.getToken(name);
|
|
||||||
options.fetch = name === 'webdav' ? fetchWebDAV.bind(options) : fetch;
|
|
||||||
return dbToCloud.drive[name](options);
|
|
||||||
}
|
|
||||||
throw new Error(`unknown cloud name: ${name}`);
|
|
||||||
}
|
|
||||||
|
|
||||||
/** @this {Object} DriveOptions */
|
|
||||||
function fetchWebDAV(url, init = {}) {
|
|
||||||
init.credentials = 'omit'; // circumventing nextcloud CSRF token error
|
|
||||||
init.headers = Object.assign({}, init.headers, {
|
|
||||||
Authorization: `Basic ${btoa(`${this.username || ''}:${this.password || ''}`)}`,
|
|
||||||
});
|
|
||||||
return fetch(url, init);
|
|
||||||
}
|
|
||||||
|
|
||||||
function schedule(delay = SYNC_DELAY) {
|
|
||||||
chrome.alarms.create('syncNow', {
|
|
||||||
delayInMinutes: delay, // fractional values are supported
|
|
||||||
periodInMinutes: SYNC_INTERVAL,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function translateErrorMessage(err) {
|
|
||||||
if (err.name === 'LockError') {
|
|
||||||
return browser.i18n.getMessage('syncErrorLock', new Date(err.expire).toLocaleString([], {timeStyle: 'short'}));
|
|
||||||
}
|
|
||||||
return err.message || String(err);
|
|
||||||
}
|
|
||||||
|
|
||||||
//#endregion
|
|
||||||
})();
|
|
|
@ -1,62 +0,0 @@
|
||||||
/* global bgReady */// common.js
|
|
||||||
/* global navMan */
|
|
||||||
'use strict';
|
|
||||||
|
|
||||||
const tabMan = (() => {
|
|
||||||
const listeners = new Set();
|
|
||||||
const cache = new Map();
|
|
||||||
chrome.tabs.onRemoved.addListener(tabId => cache.delete(tabId));
|
|
||||||
chrome.tabs.onReplaced.addListener((added, removed) => cache.delete(removed));
|
|
||||||
|
|
||||||
bgReady.all.then(() => {
|
|
||||||
navMan.onUrlChange(({tabId, frameId, url}) => {
|
|
||||||
const oldUrl = !frameId && tabMan.get(tabId, 'url', frameId);
|
|
||||||
tabMan.set(tabId, 'url', frameId, url);
|
|
||||||
if (frameId) return;
|
|
||||||
for (const fn of listeners) {
|
|
||||||
try {
|
|
||||||
fn({tabId, url, oldUrl});
|
|
||||||
} catch (err) {
|
|
||||||
console.error(err);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
return {
|
|
||||||
onUpdate(fn) {
|
|
||||||
listeners.add(fn);
|
|
||||||
},
|
|
||||||
|
|
||||||
get(tabId, ...keys) {
|
|
||||||
return keys.reduce((meta, key) => meta && meta[key], cache.get(tabId));
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* number of keys is arbitrary, last arg is value, `undefined` will delete the last key from meta
|
|
||||||
* (tabId, 'foo', 123) will set tabId's meta to {foo: 123},
|
|
||||||
* (tabId, 'foo', 'bar', 'etc', 123) will set tabId's meta to {foo: {bar: {etc: 123}}}
|
|
||||||
*/
|
|
||||||
set(tabId, ...args) {
|
|
||||||
let meta = cache.get(tabId);
|
|
||||||
if (!meta) {
|
|
||||||
meta = {};
|
|
||||||
cache.set(tabId, meta);
|
|
||||||
}
|
|
||||||
const value = args.pop();
|
|
||||||
const lastKey = args.pop();
|
|
||||||
for (const key of args) meta = meta[key] || (meta[key] = {});
|
|
||||||
if (value === undefined) {
|
|
||||||
delete meta[lastKey];
|
|
||||||
} else {
|
|
||||||
meta[lastKey] = value;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
/** @returns {IterableIterator<number>} */
|
|
||||||
list() {
|
|
||||||
return cache.keys();
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
})();
|
|
|
@ -1,270 +0,0 @@
|
||||||
/* global FIREFOX getActiveTab waitForTabUrl URLS */// toolbox.js
|
|
||||||
/* global chromeLocal */// storage-util.js
|
|
||||||
'use strict';
|
|
||||||
|
|
||||||
/* exported tokenMan */
|
|
||||||
const tokenMan = (() => {
|
|
||||||
const AUTH = {
|
|
||||||
dropbox: {
|
|
||||||
flow: 'token',
|
|
||||||
clientId: 'zg52vphuapvpng9',
|
|
||||||
authURL: 'https://www.dropbox.com/oauth2/authorize',
|
|
||||||
tokenURL: 'https://api.dropboxapi.com/oauth2/token',
|
|
||||||
revoke: token =>
|
|
||||||
fetch('https://api.dropboxapi.com/2/auth/token/revoke', {
|
|
||||||
method: 'POST',
|
|
||||||
headers: {
|
|
||||||
'Authorization': `Bearer ${token}`,
|
|
||||||
},
|
|
||||||
}),
|
|
||||||
},
|
|
||||||
google: {
|
|
||||||
flow: 'code',
|
|
||||||
clientId: '283762574871-d4u58s4arra5jdan2gr00heasjlttt1e.apps.googleusercontent.com',
|
|
||||||
clientSecret: 'J0nc5TlR_0V_ex9-sZk-5faf',
|
|
||||||
authURL: 'https://accounts.google.com/o/oauth2/v2/auth',
|
|
||||||
authQuery: {
|
|
||||||
// NOTE: Google needs 'prompt' parameter to deliver multiple refresh
|
|
||||||
// tokens for multiple machines.
|
|
||||||
// https://stackoverflow.com/q/18519185
|
|
||||||
access_type: 'offline',
|
|
||||||
prompt: 'consent',
|
|
||||||
},
|
|
||||||
tokenURL: 'https://oauth2.googleapis.com/token',
|
|
||||||
scopes: ['https://www.googleapis.com/auth/drive.appdata'],
|
|
||||||
// FIXME: https://github.com/openstyles/stylus/issues/1248
|
|
||||||
// revoke: token => {
|
|
||||||
// const params = {token};
|
|
||||||
// return postQuery(`https://accounts.google.com/o/oauth2/revoke?${new URLSearchParams(params)}`);
|
|
||||||
// },
|
|
||||||
},
|
|
||||||
onedrive: {
|
|
||||||
flow: 'code',
|
|
||||||
clientId: '3864ce03-867c-4ad8-9856-371a097d47b1',
|
|
||||||
clientSecret: '9Pj=TpsrStq8K@1BiwB9PIWLppM:@s=w',
|
|
||||||
authURL: 'https://login.microsoftonline.com/common/oauth2/v2.0/authorize',
|
|
||||||
tokenURL: 'https://login.microsoftonline.com/common/oauth2/v2.0/token',
|
|
||||||
scopes: ['Files.ReadWrite.AppFolder', 'offline_access'],
|
|
||||||
},
|
|
||||||
userstylesworld: {
|
|
||||||
flow: 'code',
|
|
||||||
clientId: 'zeDmKhJIfJqULtcrGMsWaxRtWHEimKgS',
|
|
||||||
clientSecret: 'wqHsvTuThQmXmDiVvOpZxPwSIbyycNFImpAOTxjaIRqDbsXcTOqrymMJKsOMuibFaij' +
|
|
||||||
'ZZAkVYTDbLkQuYFKqgpMsMlFlgwQOYHvHFbgxQHDTwwdOroYhOwFuekCwXUlk',
|
|
||||||
authURL: URLS.usw + 'api/oauth/style/link',
|
|
||||||
tokenURL: URLS.usw + 'api/oauth/token',
|
|
||||||
redirect_uri: 'https://gusted.xyz/callback_helper/',
|
|
||||||
},
|
|
||||||
};
|
|
||||||
const NETWORK_LATENCY = 30; // seconds
|
|
||||||
const DEFAULT_REDIRECT_URI = 'https://clngdbkpkpeebahjckkjfobafhncgmne.chromiumapp.org/';
|
|
||||||
|
|
||||||
let alwaysUseTab = !chrome.windows || (FIREFOX ? false : null);
|
|
||||||
|
|
||||||
class TokenError extends Error {
|
|
||||||
constructor(provider, message) {
|
|
||||||
super(`[${provider}] ${message}`);
|
|
||||||
this.name = 'TokenError';
|
|
||||||
this.provider = provider;
|
|
||||||
if (Error.captureStackTrace) {
|
|
||||||
Error.captureStackTrace(this, TokenError);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
|
|
||||||
buildKeys(name, hooks) {
|
|
||||||
const prefix = `secure/token/${hooks ? hooks.keyName(name) : name}/`;
|
|
||||||
const k = {
|
|
||||||
TOKEN: `${prefix}token`,
|
|
||||||
EXPIRE: `${prefix}expire`,
|
|
||||||
REFRESH: `${prefix}refresh`,
|
|
||||||
};
|
|
||||||
k.LIST = Object.values(k);
|
|
||||||
return k;
|
|
||||||
},
|
|
||||||
|
|
||||||
getClientId(name) {
|
|
||||||
return AUTH[name].clientId;
|
|
||||||
},
|
|
||||||
|
|
||||||
async getToken(name, interactive, hooks) {
|
|
||||||
const k = tokenMan.buildKeys(name, hooks);
|
|
||||||
const obj = await chromeLocal.get(k.LIST);
|
|
||||||
if (obj[k.TOKEN]) {
|
|
||||||
if (!obj[k.EXPIRE] || Date.now() < obj[k.EXPIRE]) {
|
|
||||||
return obj[k.TOKEN];
|
|
||||||
}
|
|
||||||
if (obj[k.REFRESH]) {
|
|
||||||
return refreshToken(name, k, obj);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (!interactive) {
|
|
||||||
throw new TokenError(name, 'Token is missing');
|
|
||||||
}
|
|
||||||
return authUser(k, name, interactive, hooks);
|
|
||||||
},
|
|
||||||
|
|
||||||
async revokeToken(name, hooks) {
|
|
||||||
const provider = AUTH[name];
|
|
||||||
const k = tokenMan.buildKeys(name, hooks);
|
|
||||||
if (provider.revoke) {
|
|
||||||
try {
|
|
||||||
const token = await chromeLocal.getValue(k.TOKEN);
|
|
||||||
if (token) await provider.revoke(token);
|
|
||||||
} catch (e) {
|
|
||||||
console.error(e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
await chromeLocal.remove(k.LIST);
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
async function refreshToken(name, k, obj) {
|
|
||||||
if (!obj[k.REFRESH]) {
|
|
||||||
throw new TokenError(name, 'No refresh token');
|
|
||||||
}
|
|
||||||
const provider = AUTH[name];
|
|
||||||
const body = {
|
|
||||||
client_id: provider.clientId,
|
|
||||||
refresh_token: obj[k.REFRESH],
|
|
||||||
grant_type: 'refresh_token',
|
|
||||||
scope: provider.scopes.join(' '),
|
|
||||||
};
|
|
||||||
if (provider.clientSecret) {
|
|
||||||
body.client_secret = provider.clientSecret;
|
|
||||||
}
|
|
||||||
const result = await postQuery(provider.tokenURL, body);
|
|
||||||
if (!result.refresh_token) {
|
|
||||||
// reuse old refresh token
|
|
||||||
result.refresh_token = obj[k.REFRESH];
|
|
||||||
}
|
|
||||||
return handleTokenResult(result, k);
|
|
||||||
}
|
|
||||||
|
|
||||||
async function authUser(keys, name, interactive = false, hooks = null) {
|
|
||||||
await require(['/vendor/webext-launch-web-auth-flow/webext-launch-web-auth-flow']);
|
|
||||||
/* global webextLaunchWebAuthFlow */
|
|
||||||
const provider = AUTH[name];
|
|
||||||
const state = Math.random().toFixed(8).slice(2);
|
|
||||||
const query = {
|
|
||||||
response_type: provider.flow,
|
|
||||||
client_id: provider.clientId,
|
|
||||||
redirect_uri: provider.redirect_uri || DEFAULT_REDIRECT_URI,
|
|
||||||
state,
|
|
||||||
};
|
|
||||||
if (provider.scopes) {
|
|
||||||
query.scope = provider.scopes.join(' ');
|
|
||||||
}
|
|
||||||
if (provider.authQuery) {
|
|
||||||
Object.assign(query, provider.authQuery);
|
|
||||||
}
|
|
||||||
if (alwaysUseTab == null) {
|
|
||||||
alwaysUseTab = await detectVivaldiWebRequestBug();
|
|
||||||
}
|
|
||||||
if (hooks) hooks.query(query);
|
|
||||||
const url = `${provider.authURL}?${new URLSearchParams(query)}`;
|
|
||||||
const width = Math.min(screen.availWidth - 100, 800);
|
|
||||||
const height = Math.min(screen.availHeight - 100, 800);
|
|
||||||
const wnd = !alwaysUseTab && await browser.windows.getLastFocused();
|
|
||||||
const finalUrl = await webextLaunchWebAuthFlow({
|
|
||||||
url,
|
|
||||||
alwaysUseTab,
|
|
||||||
interactive,
|
|
||||||
redirect_uri: query.redirect_uri,
|
|
||||||
windowOptions: wnd && Object.assign({
|
|
||||||
state: 'normal',
|
|
||||||
width,
|
|
||||||
height,
|
|
||||||
}, wnd.state !== 'minimized' && {
|
|
||||||
// Center the popup to the current window
|
|
||||||
top: Math.ceil(wnd.top + (wnd.height - width) / 2),
|
|
||||||
left: Math.ceil(wnd.left + (wnd.width - width) / 2),
|
|
||||||
}),
|
|
||||||
});
|
|
||||||
const params = new URLSearchParams(
|
|
||||||
provider.flow === 'token' ?
|
|
||||||
new URL(finalUrl).hash.slice(1) :
|
|
||||||
new URL(finalUrl).search.slice(1)
|
|
||||||
);
|
|
||||||
if (params.get('state') !== state) {
|
|
||||||
throw new TokenError(name, `Unexpected state: ${params.get('state')}, expected: ${state}`);
|
|
||||||
}
|
|
||||||
let result;
|
|
||||||
if (provider.flow === 'token') {
|
|
||||||
const obj = {};
|
|
||||||
for (const [key, value] of params) {
|
|
||||||
obj[key] = value;
|
|
||||||
}
|
|
||||||
result = obj;
|
|
||||||
} else {
|
|
||||||
const code = params.get('code');
|
|
||||||
const body = {
|
|
||||||
code,
|
|
||||||
grant_type: 'authorization_code',
|
|
||||||
client_id: provider.clientId,
|
|
||||||
redirect_uri: query.redirect_uri,
|
|
||||||
state,
|
|
||||||
};
|
|
||||||
if (provider.clientSecret) {
|
|
||||||
body.client_secret = provider.clientSecret;
|
|
||||||
}
|
|
||||||
result = await postQuery(provider.tokenURL, body);
|
|
||||||
}
|
|
||||||
return handleTokenResult(result, keys);
|
|
||||||
}
|
|
||||||
|
|
||||||
async function handleTokenResult(result, k) {
|
|
||||||
await chromeLocal.set({
|
|
||||||
[k.TOKEN]: result.access_token,
|
|
||||||
[k.EXPIRE]: result.expires_in
|
|
||||||
? Date.now() + (result.expires_in - NETWORK_LATENCY) * 1000
|
|
||||||
: undefined,
|
|
||||||
[k.REFRESH]: result.refresh_token,
|
|
||||||
});
|
|
||||||
return result.access_token;
|
|
||||||
}
|
|
||||||
|
|
||||||
async function postQuery(url, body) {
|
|
||||||
const options = {
|
|
||||||
method: 'POST',
|
|
||||||
headers: {
|
|
||||||
'Content-Type': 'application/x-www-form-urlencoded',
|
|
||||||
},
|
|
||||||
body: body ? new URLSearchParams(body) : null,
|
|
||||||
};
|
|
||||||
const r = await fetch(url, options);
|
|
||||||
if (r.ok) {
|
|
||||||
return r.json();
|
|
||||||
}
|
|
||||||
const text = await r.text();
|
|
||||||
const err = new Error(`Failed to fetch (${r.status}): ${text}`);
|
|
||||||
err.code = r.status;
|
|
||||||
throw err;
|
|
||||||
}
|
|
||||||
|
|
||||||
async function detectVivaldiWebRequestBug() {
|
|
||||||
// Workaround for https://github.com/openstyles/stylus/issues/1182
|
|
||||||
// Note that modern Vivaldi isn't exposed in `navigator.userAgent` but it adds `extData` to tabs
|
|
||||||
const anyTab = await getActiveTab() || (await browser.tabs.query({}))[0];
|
|
||||||
if (anyTab && !(anyTab.extData || anyTab.vivExtData)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
let bugged = true;
|
|
||||||
const TEST_URL = chrome.runtime.getURL('manifest.json');
|
|
||||||
const check = ({url}) => {
|
|
||||||
bugged = url !== TEST_URL;
|
|
||||||
};
|
|
||||||
chrome.webRequest.onBeforeRequest.addListener(check, {urls: [TEST_URL], types: ['main_frame']});
|
|
||||||
const {tabs: [tab]} = await browser.windows.create({
|
|
||||||
type: 'popup',
|
|
||||||
state: 'minimized',
|
|
||||||
url: TEST_URL,
|
|
||||||
});
|
|
||||||
await waitForTabUrl(tab);
|
|
||||||
chrome.windows.remove(tab.windowId);
|
|
||||||
chrome.webRequest.onBeforeRequest.removeListener(check);
|
|
||||||
return bugged;
|
|
||||||
}
|
|
||||||
})();
|
|
|
@ -1,348 +0,0 @@
|
||||||
/* global API */// msg.js
|
|
||||||
/* global RX_META URLS debounce deepMerge download ignoreChromeError */// toolbox.js
|
|
||||||
/* global calcStyleDigest styleSectionsEqual */ // sections-util.js
|
|
||||||
/* global chromeLocal */// storage-util.js
|
|
||||||
/* global compareVersion */// cmpver.js
|
|
||||||
/* global db */
|
|
||||||
/* global prefs */
|
|
||||||
'use strict';
|
|
||||||
|
|
||||||
/* exported updateMan */
|
|
||||||
const updateMan = (() => {
|
|
||||||
const STATES = /** @namespace UpdaterStates */ {
|
|
||||||
UPDATED: 'updated',
|
|
||||||
SKIPPED: 'skipped',
|
|
||||||
UNREACHABLE: 'server unreachable',
|
|
||||||
// details for SKIPPED status
|
|
||||||
EDITED: 'locally edited',
|
|
||||||
MAYBE_EDITED: 'may be locally edited',
|
|
||||||
SAME_MD5: 'up-to-date: MD5 is unchanged',
|
|
||||||
SAME_CODE: 'up-to-date: code sections are unchanged',
|
|
||||||
SAME_VERSION: 'up-to-date: version is unchanged',
|
|
||||||
ERROR_MD5: 'error: MD5 is invalid',
|
|
||||||
ERROR_JSON: 'error: JSON is invalid',
|
|
||||||
ERROR_VERSION: 'error: version is older than installed style',
|
|
||||||
};
|
|
||||||
const USO_STYLES_API = `${URLS.uso}api/v1/styles/`;
|
|
||||||
const RH_ETAG = {responseHeaders: ['etag']}; // a hashsum of file contents
|
|
||||||
const RX_DATE2VER = new RegExp([
|
|
||||||
/^(\d{4})/,
|
|
||||||
/(0[1-9]|1(?:0|[12](?=\d\d))?|[2-9])/, // in ambiguous cases like yyyy123 the month will be 1
|
|
||||||
/(0[1-9]|[1-2][0-9]?|3[0-1]?|[4-9])/,
|
|
||||||
/\.([01][0-9]?|2[0-3]?|[3-9])/,
|
|
||||||
/\.([0-5][0-9]?|[6-9])$/,
|
|
||||||
].map(rx => rx.source).join(''));
|
|
||||||
const ALARM_NAME = 'scheduledUpdate';
|
|
||||||
const MIN_INTERVAL_MS = 60e3;
|
|
||||||
const RETRY_ERRORS = [
|
|
||||||
503, // service unavailable
|
|
||||||
429, // too many requests
|
|
||||||
];
|
|
||||||
let usoReferers = 0;
|
|
||||||
let lastUpdateTime;
|
|
||||||
let checkingAll = false;
|
|
||||||
let logQueue = [];
|
|
||||||
let logLastWriteTime = 0;
|
|
||||||
|
|
||||||
chromeLocal.getValue('lastUpdateTime').then(val => {
|
|
||||||
lastUpdateTime = val || Date.now();
|
|
||||||
prefs.subscribe('updateInterval', schedule, {runNow: true});
|
|
||||||
chrome.alarms.onAlarm.addListener(onAlarm);
|
|
||||||
});
|
|
||||||
|
|
||||||
return {
|
|
||||||
checkAllStyles,
|
|
||||||
checkStyle,
|
|
||||||
getStates: () => STATES,
|
|
||||||
};
|
|
||||||
|
|
||||||
async function checkAllStyles({
|
|
||||||
save = true,
|
|
||||||
ignoreDigest,
|
|
||||||
observe,
|
|
||||||
} = {}) {
|
|
||||||
resetInterval();
|
|
||||||
checkingAll = true;
|
|
||||||
const port = observe && chrome.runtime.connect({name: 'updater'});
|
|
||||||
const styles = (await API.styles.getAll())
|
|
||||||
.filter(style => style.updateUrl && style.updatable !== false);
|
|
||||||
if (port) port.postMessage({count: styles.length});
|
|
||||||
log('');
|
|
||||||
log(`${save ? 'Scheduled' : 'Manual'} update check for ${styles.length} styles`);
|
|
||||||
await Promise.all(
|
|
||||||
styles.map(style =>
|
|
||||||
checkStyle({style, port, save, ignoreDigest})));
|
|
||||||
if (port) port.postMessage({done: true});
|
|
||||||
if (port) port.disconnect();
|
|
||||||
log('');
|
|
||||||
checkingAll = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param {{
|
|
||||||
id?: number,
|
|
||||||
style?: StyleObj,
|
|
||||||
port?: chrome.runtime.Port,
|
|
||||||
save?: boolean,
|
|
||||||
ignoreDigest?: boolean,
|
|
||||||
}} opts
|
|
||||||
* @returns {{
|
|
||||||
style: StyleObj,
|
|
||||||
updated?: boolean,
|
|
||||||
error?: any,
|
|
||||||
STATES: UpdaterStates,
|
|
||||||
}}
|
|
||||||
|
|
||||||
Original style digests are calculated in these cases:
|
|
||||||
* style is installed or updated from server
|
|
||||||
* non-usercss style is checked for an update and styleSectionsEqual considers it unchanged
|
|
||||||
|
|
||||||
Update check proceeds in these cases:
|
|
||||||
* style has the original digest and it's equal to the current digest
|
|
||||||
* [ignoreDigest: true] style doesn't yet have the original digest but we ignore it
|
|
||||||
* [ignoreDigest: none/false] style doesn't yet have the original digest
|
|
||||||
so we compare the code to the server code and if it's the same we save the digest,
|
|
||||||
otherwise we skip the style and report MAYBE_EDITED status
|
|
||||||
|
|
||||||
'ignoreDigest' option is set on the second manual individual update check on the manage page.
|
|
||||||
*/
|
|
||||||
async function checkStyle(opts) {
|
|
||||||
let {id} = opts;
|
|
||||||
const {
|
|
||||||
style = await API.styles.get(id),
|
|
||||||
ignoreDigest,
|
|
||||||
port,
|
|
||||||
save,
|
|
||||||
} = opts;
|
|
||||||
if (!id) id = style.id;
|
|
||||||
const {md5Url} = style;
|
|
||||||
let {usercssData: ucd, updateUrl} = style;
|
|
||||||
let res, state;
|
|
||||||
try {
|
|
||||||
await checkIfEdited();
|
|
||||||
res = {
|
|
||||||
style: await (ucd && !md5Url ? updateUsercss : updateUSO)().then(maybeSave),
|
|
||||||
updated: true,
|
|
||||||
};
|
|
||||||
state = STATES.UPDATED;
|
|
||||||
} catch (err) {
|
|
||||||
const error = err === 0 && STATES.UNREACHABLE ||
|
|
||||||
err && err.message ||
|
|
||||||
err;
|
|
||||||
res = {error, style, STATES};
|
|
||||||
state = `${STATES.SKIPPED} (${Array.isArray(err) ? err[0].message : error})`;
|
|
||||||
}
|
|
||||||
log(`${state} #${id} ${style.customName || style.name}`);
|
|
||||||
if (port) port.postMessage(res);
|
|
||||||
return res;
|
|
||||||
|
|
||||||
async function checkIfEdited() {
|
|
||||||
if (!ignoreDigest &&
|
|
||||||
style.originalDigest &&
|
|
||||||
style.originalDigest !== await calcStyleDigest(style)) {
|
|
||||||
return Promise.reject(STATES.EDITED);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async function updateUSO() {
|
|
||||||
const md5 = await tryDownload(md5Url);
|
|
||||||
if (!md5 || md5.length !== 32) {
|
|
||||||
return Promise.reject(STATES.ERROR_MD5);
|
|
||||||
}
|
|
||||||
if (md5 === style.originalMd5 && style.originalDigest && !ignoreDigest) {
|
|
||||||
return Promise.reject(STATES.SAME_MD5);
|
|
||||||
}
|
|
||||||
let varsUrl = '';
|
|
||||||
if (!ucd) {
|
|
||||||
ucd = {};
|
|
||||||
varsUrl = updateUrl;
|
|
||||||
updateUrl = style.updateUrl = `${USO_STYLES_API}${md5Url.match(/\/(\d+)/)[1]}`;
|
|
||||||
}
|
|
||||||
usoSpooferStart();
|
|
||||||
let json;
|
|
||||||
try {
|
|
||||||
json = await tryDownload(style.updateUrl, {responseType: 'json'});
|
|
||||||
json = await updateUsercss(json.css) ||
|
|
||||||
(await API.uso.toUsercss(json)).style;
|
|
||||||
if (varsUrl) await API.uso.useVarsUrl(json, varsUrl);
|
|
||||||
} finally {
|
|
||||||
usoSpooferStop();
|
|
||||||
}
|
|
||||||
// USO may not provide a correctly updated originalMd5 (#555)
|
|
||||||
json.originalMd5 = md5;
|
|
||||||
return json;
|
|
||||||
}
|
|
||||||
|
|
||||||
async function updateUsercss(css) {
|
|
||||||
let oldVer = ucd.version;
|
|
||||||
let {etag: oldEtag, updateUrl} = style;
|
|
||||||
const m2 = (css || URLS.extractUsoArchiveId(updateUrl)) &&
|
|
||||||
await getUsoEmbeddedMeta(css);
|
|
||||||
if (m2 && m2.updateUrl) {
|
|
||||||
updateUrl = m2.updateUrl;
|
|
||||||
oldVer = m2.usercssData.version || '0';
|
|
||||||
oldEtag = '';
|
|
||||||
} else if (css) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (oldEtag && oldEtag === await downloadEtag()) {
|
|
||||||
return Promise.reject(STATES.SAME_CODE);
|
|
||||||
}
|
|
||||||
// TODO: when sourceCode is > 100kB use http range request(s) for version check
|
|
||||||
const {headers: {etag}, response} = await tryDownload(updateUrl, RH_ETAG);
|
|
||||||
const json = await API.usercss.buildMeta({sourceCode: response, etag, updateUrl});
|
|
||||||
const delta = compareVersion(json.usercssData.version, oldVer);
|
|
||||||
let err;
|
|
||||||
if (!delta && !ignoreDigest) {
|
|
||||||
// re-install is invalid in a soft upgrade
|
|
||||||
err = response === style.sourceCode
|
|
||||||
? STATES.SAME_CODE
|
|
||||||
: !URLS.isLocalhost(updateUrl) && STATES.SAME_VERSION;
|
|
||||||
}
|
|
||||||
if (delta < 0) {
|
|
||||||
// downgrade is always invalid
|
|
||||||
err = STATES.ERROR_VERSION;
|
|
||||||
}
|
|
||||||
if (err && etag && !style.etag) {
|
|
||||||
// first check of ETAG, gonna write it directly to DB as it's too trivial to sync or announce
|
|
||||||
style.etag = etag;
|
|
||||||
await db.styles.put(style);
|
|
||||||
}
|
|
||||||
return err
|
|
||||||
? Promise.reject(err)
|
|
||||||
: API.usercss.buildCode(json);
|
|
||||||
}
|
|
||||||
|
|
||||||
async function maybeSave(json) {
|
|
||||||
json.id = id;
|
|
||||||
// keep current state
|
|
||||||
delete json.customName;
|
|
||||||
delete json.enabled;
|
|
||||||
const newStyle = Object.assign({}, style, json);
|
|
||||||
newStyle.updateDate = getDateFromVer(newStyle) || Date.now();
|
|
||||||
// update digest even if save === false as there might be just a space added etc.
|
|
||||||
if (!ucd && styleSectionsEqual(json, style)) {
|
|
||||||
style.originalDigest = (await API.styles.install(newStyle)).originalDigest;
|
|
||||||
return Promise.reject(STATES.SAME_CODE);
|
|
||||||
}
|
|
||||||
if (!style.originalDigest && !ignoreDigest) {
|
|
||||||
return Promise.reject(STATES.MAYBE_EDITED);
|
|
||||||
}
|
|
||||||
return !save ? newStyle :
|
|
||||||
(ucd ? API.usercss.install : API.styles.install)(newStyle);
|
|
||||||
}
|
|
||||||
|
|
||||||
async function tryDownload(url, params) {
|
|
||||||
let {retryDelay = 1000} = opts;
|
|
||||||
while (true) {
|
|
||||||
try {
|
|
||||||
params = deepMerge(params || {}, {headers: {'Cache-Control': 'no-cache'}});
|
|
||||||
return await download(url, params);
|
|
||||||
} catch (code) {
|
|
||||||
if (!RETRY_ERRORS.includes(code) ||
|
|
||||||
retryDelay > MIN_INTERVAL_MS) {
|
|
||||||
return Promise.reject(code);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
retryDelay *= 1.25;
|
|
||||||
await new Promise(resolve => setTimeout(resolve, retryDelay));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async function downloadEtag() {
|
|
||||||
const opts = Object.assign({method: 'head'}, RH_ETAG);
|
|
||||||
const req = await tryDownload(style.updateUrl, opts);
|
|
||||||
return req.headers.etag;
|
|
||||||
}
|
|
||||||
|
|
||||||
function getDateFromVer(style) {
|
|
||||||
const m = RX_DATE2VER.exec((style.usercssData || {}).version);
|
|
||||||
if (m) {
|
|
||||||
m[2]--; // month is 0-based in `Date` constructor
|
|
||||||
return new Date(...m.slice(1)).getTime();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/** UserCSS metadata may be embedded in the original USO style so let's use its updateURL */
|
|
||||||
function getUsoEmbeddedMeta(code = style.sourceCode) {
|
|
||||||
const isRaw = arguments[0];
|
|
||||||
const m = code.includes('@updateURL') && (isRaw ? code : code.replace(RX_META, '')).match(RX_META);
|
|
||||||
return m && API.usercss.buildMeta({sourceCode: m[0]}).catch(() => null);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function schedule() {
|
|
||||||
const interval = prefs.get('updateInterval') * 60 * 60 * 1000;
|
|
||||||
if (interval > 0) {
|
|
||||||
const elapsed = Math.max(0, Date.now() - lastUpdateTime);
|
|
||||||
chrome.alarms.create(ALARM_NAME, {
|
|
||||||
when: Date.now() + Math.max(MIN_INTERVAL_MS, interval - elapsed),
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
chrome.alarms.clear(ALARM_NAME, ignoreChromeError);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function onAlarm({name}) {
|
|
||||||
if (name === ALARM_NAME) checkAllStyles();
|
|
||||||
}
|
|
||||||
|
|
||||||
function resetInterval() {
|
|
||||||
chromeLocal.setValue('lastUpdateTime', lastUpdateTime = Date.now());
|
|
||||||
schedule();
|
|
||||||
}
|
|
||||||
|
|
||||||
function log(text) {
|
|
||||||
logQueue.push({text, time: new Date().toLocaleString()});
|
|
||||||
debounce(flushQueue, text && checkingAll ? 1000 : 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
async function flushQueue(lines) {
|
|
||||||
if (!lines) {
|
|
||||||
flushQueue(await chromeLocal.getValue('updateLog') || []);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const time = Date.now() - logLastWriteTime > 11e3 ?
|
|
||||||
logQueue[0].time + ' ' :
|
|
||||||
'';
|
|
||||||
if (logQueue[0] && !logQueue[0].text) {
|
|
||||||
logQueue.shift();
|
|
||||||
if (lines[lines.length - 1]) lines.push('');
|
|
||||||
}
|
|
||||||
lines.splice(0, lines.length - 1000);
|
|
||||||
lines.push(time + (logQueue[0] && logQueue[0].text || ''));
|
|
||||||
lines.push(...logQueue.slice(1).map(item => item.text));
|
|
||||||
|
|
||||||
chromeLocal.setValue('updateLog', lines);
|
|
||||||
logLastWriteTime = Date.now();
|
|
||||||
logQueue = [];
|
|
||||||
}
|
|
||||||
|
|
||||||
function usoSpooferStart() {
|
|
||||||
if (++usoReferers === 1) {
|
|
||||||
chrome.webRequest.onBeforeSendHeaders.addListener(
|
|
||||||
usoSpoofer,
|
|
||||||
{types: ['xmlhttprequest'], urls: [USO_STYLES_API + '*']},
|
|
||||||
['blocking', 'requestHeaders', chrome.webRequest.OnBeforeSendHeadersOptions.EXTRA_HEADERS]
|
|
||||||
.filter(Boolean));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function usoSpooferStop() {
|
|
||||||
if (--usoReferers <= 0) {
|
|
||||||
usoReferers = 0;
|
|
||||||
chrome.webRequest.onBeforeSendHeaders.removeListener(usoSpoofer);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/** @param {chrome.webRequest.WebResponseHeadersDetails | browser.webRequest._OnBeforeSendHeadersDetails} info */
|
|
||||||
function usoSpoofer(info) {
|
|
||||||
if (info.tabId < 0 && URLS.ownOrigin.startsWith(info.initiator || info.originUrl || '')) {
|
|
||||||
const {requestHeaders: hh} = info;
|
|
||||||
const i = (hh.findIndex(h => /^referer$/i.test(h.name)) + 1 || hh.push({})) - 1;
|
|
||||||
hh[i].name = 'referer';
|
|
||||||
hh[i].value = URLS.uso;
|
|
||||||
return {requestHeaders: hh};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})();
|
|
264
background/update.js
Normal file
264
background/update.js
Normal file
|
@ -0,0 +1,264 @@
|
||||||
|
/*
|
||||||
|
global getStyles saveStyle styleSectionsEqual
|
||||||
|
global calcStyleDigest cachedStyles getStyleWithNoCode
|
||||||
|
global usercss semverCompare
|
||||||
|
global API_METHODS
|
||||||
|
*/
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
(() => {
|
||||||
|
|
||||||
|
const STATES = {
|
||||||
|
UPDATED: 'updated',
|
||||||
|
SKIPPED: 'skipped',
|
||||||
|
|
||||||
|
// details for SKIPPED status
|
||||||
|
EDITED: 'locally edited',
|
||||||
|
MAYBE_EDITED: 'may be locally edited',
|
||||||
|
SAME_MD5: 'up-to-date: MD5 is unchanged',
|
||||||
|
SAME_CODE: 'up-to-date: code sections are unchanged',
|
||||||
|
SAME_VERSION: 'up-to-date: version is unchanged',
|
||||||
|
ERROR_MD5: 'error: MD5 is invalid',
|
||||||
|
ERROR_JSON: 'error: JSON is invalid',
|
||||||
|
ERROR_VERSION: 'error: version is older than installed style',
|
||||||
|
};
|
||||||
|
|
||||||
|
const ALARM_NAME = 'scheduledUpdate';
|
||||||
|
const MIN_INTERVAL_MS = 60e3;
|
||||||
|
|
||||||
|
let lastUpdateTime = parseInt(localStorage.lastUpdateTime) || Date.now();
|
||||||
|
let checkingAll = false;
|
||||||
|
let logQueue = [];
|
||||||
|
let logLastWriteTime = 0;
|
||||||
|
|
||||||
|
const retrying = new Set();
|
||||||
|
|
||||||
|
API_METHODS.updateCheckAll = checkAllStyles;
|
||||||
|
API_METHODS.updateCheck = checkStyle;
|
||||||
|
API_METHODS.getUpdaterStates = () => STATES;
|
||||||
|
|
||||||
|
prefs.subscribe(['updateInterval'], schedule);
|
||||||
|
schedule();
|
||||||
|
|
||||||
|
return {checkAllStyles, checkStyle, STATES};
|
||||||
|
|
||||||
|
function checkAllStyles({
|
||||||
|
save = true,
|
||||||
|
ignoreDigest,
|
||||||
|
observe,
|
||||||
|
} = {}) {
|
||||||
|
resetInterval();
|
||||||
|
checkingAll = true;
|
||||||
|
retrying.clear();
|
||||||
|
const port = observe && chrome.runtime.connect({name: 'updater'});
|
||||||
|
return getStyles({}).then(styles => {
|
||||||
|
styles = styles.filter(style => style.updateUrl);
|
||||||
|
if (port) port.postMessage({count: styles.length});
|
||||||
|
log('');
|
||||||
|
log(`${save ? 'Scheduled' : 'Manual'} update check for ${styles.length} styles`);
|
||||||
|
return Promise.all(
|
||||||
|
styles.map(style =>
|
||||||
|
checkStyle({style, port, save, ignoreDigest})));
|
||||||
|
}).then(() => {
|
||||||
|
if (port) port.postMessage({done: true});
|
||||||
|
if (port) port.disconnect();
|
||||||
|
log('');
|
||||||
|
checkingAll = false;
|
||||||
|
retrying.clear();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function checkStyle({
|
||||||
|
id,
|
||||||
|
style = cachedStyles.byId.get(id),
|
||||||
|
port,
|
||||||
|
save = true,
|
||||||
|
ignoreDigest,
|
||||||
|
}) {
|
||||||
|
/*
|
||||||
|
Original style digests are calculated in these cases:
|
||||||
|
* style is installed or updated from server
|
||||||
|
* style is checked for an update and its code is equal to the server code
|
||||||
|
|
||||||
|
Update check proceeds in these cases:
|
||||||
|
* style has the original digest and it's equal to the current digest
|
||||||
|
* [ignoreDigest: true] style doesn't yet have the original digest but we ignore it
|
||||||
|
* [ignoreDigest: none/false] style doesn't yet have the original digest
|
||||||
|
so we compare the code to the server code and if it's the same we save the digest,
|
||||||
|
otherwise we skip the style and report MAYBE_EDITED status
|
||||||
|
|
||||||
|
'ignoreDigest' option is set on the second manual individual update check on the manage page.
|
||||||
|
*/
|
||||||
|
return Promise.resolve(style)
|
||||||
|
.then([calcStyleDigest][!ignoreDigest ? 0 : 'skip'])
|
||||||
|
.then([checkIfEdited][!ignoreDigest ? 0 : 'skip'])
|
||||||
|
.then([maybeUpdateUSO, maybeUpdateUsercss][style.usercssData ? 1 : 0])
|
||||||
|
.then(maybeSave)
|
||||||
|
.then(reportSuccess)
|
||||||
|
.catch(reportFailure);
|
||||||
|
|
||||||
|
function reportSuccess(saved) {
|
||||||
|
log(STATES.UPDATED + ` #${style.id} ${style.name}`);
|
||||||
|
const info = {updated: true, style: saved};
|
||||||
|
if (port) port.postMessage(info);
|
||||||
|
return info;
|
||||||
|
}
|
||||||
|
|
||||||
|
function reportFailure(error) {
|
||||||
|
if ((
|
||||||
|
error === 503 || // Service Unavailable
|
||||||
|
error === 429 // Too Many Requests
|
||||||
|
) && !retrying.has(id)) {
|
||||||
|
retrying.add(id);
|
||||||
|
return new Promise(resolve => {
|
||||||
|
setTimeout(() => {
|
||||||
|
resolve(checkStyle({id, style, port, save, ignoreDigest}));
|
||||||
|
}, 1000);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
error = error === 0 ? 'server unreachable' : error;
|
||||||
|
log(STATES.SKIPPED + ` (${error}) #${style.id} ${style.name}`);
|
||||||
|
const info = {error, STATES, style: getStyleWithNoCode(style)};
|
||||||
|
if (port) port.postMessage(info);
|
||||||
|
return info;
|
||||||
|
}
|
||||||
|
|
||||||
|
function checkIfEdited(digest) {
|
||||||
|
if (style.originalDigest && style.originalDigest !== digest) {
|
||||||
|
return Promise.reject(STATES.EDITED);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function maybeUpdateUSO() {
|
||||||
|
return download(style.md5Url).then(md5 => {
|
||||||
|
if (!md5 || md5.length !== 32) {
|
||||||
|
return Promise.reject(STATES.ERROR_MD5);
|
||||||
|
}
|
||||||
|
if (md5 === style.originalMd5 && style.originalDigest && !ignoreDigest) {
|
||||||
|
return Promise.reject(STATES.SAME_MD5);
|
||||||
|
}
|
||||||
|
// USO can't handle POST requests for style json
|
||||||
|
return download(style.updateUrl, {body: null})
|
||||||
|
.then(text => tryJSONparse(text));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function maybeUpdateUsercss() {
|
||||||
|
// TODO: when sourceCode is > 100kB use http range request(s) for version check
|
||||||
|
return download(style.updateUrl).then(text => {
|
||||||
|
const json = usercss.buildMeta(text);
|
||||||
|
const {usercssData: {version}} = style;
|
||||||
|
const {usercssData: {version: newVersion}} = json;
|
||||||
|
switch (Math.sign(semverCompare(version, newVersion))) {
|
||||||
|
case 0:
|
||||||
|
// re-install is invalid in a soft upgrade
|
||||||
|
if (!ignoreDigest) {
|
||||||
|
const sameCode = text === style.sourceCode;
|
||||||
|
return Promise.reject(sameCode ? STATES.SAME_CODE : STATES.SAME_VERSION);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case 1:
|
||||||
|
// downgrade is always invalid
|
||||||
|
return Promise.reject(STATES.ERROR_VERSION);
|
||||||
|
}
|
||||||
|
return usercss.buildCode(json);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function maybeSave(json = {}) {
|
||||||
|
// usercss is already validated while building
|
||||||
|
if (!json.usercssData && !styleJSONseemsValid(json)) {
|
||||||
|
return Promise.reject(STATES.ERROR_JSON);
|
||||||
|
}
|
||||||
|
|
||||||
|
json.id = style.id;
|
||||||
|
json.updateDate = Date.now();
|
||||||
|
json.reason = 'update';
|
||||||
|
|
||||||
|
// keep current state
|
||||||
|
delete json.enabled;
|
||||||
|
|
||||||
|
// keep local name customizations
|
||||||
|
if (style.originalName !== style.name && style.name !== json.name) {
|
||||||
|
delete json.name;
|
||||||
|
} else {
|
||||||
|
json.originalName = json.name;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (styleSectionsEqual(json, style, {checkSource: true})) {
|
||||||
|
// update digest even if save === false as there might be just a space added etc.
|
||||||
|
json.reason = 'update-digest';
|
||||||
|
return saveStyle(json)
|
||||||
|
.then(saved => {
|
||||||
|
style.originalDigest = saved.originalDigest;
|
||||||
|
return Promise.reject(STATES.SAME_CODE);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!style.originalDigest && !ignoreDigest) {
|
||||||
|
return Promise.reject(STATES.MAYBE_EDITED);
|
||||||
|
}
|
||||||
|
|
||||||
|
return save ?
|
||||||
|
API_METHODS[json.usercssData ? 'saveUsercss' : 'saveStyle'](json) :
|
||||||
|
json;
|
||||||
|
}
|
||||||
|
|
||||||
|
function styleJSONseemsValid(json) {
|
||||||
|
return json
|
||||||
|
&& json.sections
|
||||||
|
&& json.sections.length
|
||||||
|
&& typeof json.sections.every === 'function'
|
||||||
|
&& typeof json.sections[0].code === 'string';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function schedule() {
|
||||||
|
const interval = prefs.get('updateInterval') * 60 * 60 * 1000;
|
||||||
|
if (interval > 0) {
|
||||||
|
const elapsed = Math.max(0, Date.now() - lastUpdateTime);
|
||||||
|
chrome.alarms.create(ALARM_NAME, {
|
||||||
|
when: Date.now() + Math.max(MIN_INTERVAL_MS, interval - elapsed),
|
||||||
|
});
|
||||||
|
chrome.alarms.onAlarm.addListener(onAlarm);
|
||||||
|
} else {
|
||||||
|
chrome.alarms.clear(ALARM_NAME, ignoreChromeError);
|
||||||
|
chrome.alarms.onAlarm.removeListener(onAlarm);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function onAlarm({name}) {
|
||||||
|
if (name === ALARM_NAME) checkAllStyles();
|
||||||
|
}
|
||||||
|
|
||||||
|
function resetInterval() {
|
||||||
|
localStorage.lastUpdateTime = lastUpdateTime = Date.now();
|
||||||
|
schedule();
|
||||||
|
}
|
||||||
|
|
||||||
|
function log(text) {
|
||||||
|
logQueue.push({text, time: new Date().toLocaleString()});
|
||||||
|
debounce(flushQueue, text && checkingAll ? 1000 : 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
function flushQueue(lines) {
|
||||||
|
if (!lines) {
|
||||||
|
chromeLocal.getValue('updateLog', []).then(flushQueue);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const time = Date.now() - logLastWriteTime > 11e3 ?
|
||||||
|
logQueue[0].time + ' ' :
|
||||||
|
'';
|
||||||
|
if (logQueue[0] && !logQueue[0].text) {
|
||||||
|
logQueue.shift();
|
||||||
|
if (lines[lines.length - 1]) lines.push('');
|
||||||
|
}
|
||||||
|
lines.splice(0, lines.length - 1000);
|
||||||
|
lines.push(time + (logQueue[0] && logQueue[0].text || ''));
|
||||||
|
lines.push(...logQueue.slice(1).map(item => item.text));
|
||||||
|
|
||||||
|
chromeLocal.setValue('updateLog', lines);
|
||||||
|
logLastWriteTime = Date.now();
|
||||||
|
logQueue = [];
|
||||||
|
}
|
||||||
|
})();
|
153
background/usercss-helper.js
Normal file
153
background/usercss-helper.js
Normal file
|
@ -0,0 +1,153 @@
|
||||||
|
/* global API_METHODS usercss saveStyle getStyles chromeLocal cachedStyles */
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
(() => {
|
||||||
|
|
||||||
|
API_METHODS.saveUsercss = style => save(style, false);
|
||||||
|
API_METHODS.saveUsercssUnsafe = style => save(style, true);
|
||||||
|
API_METHODS.buildUsercss = build;
|
||||||
|
API_METHODS.installUsercss = install;
|
||||||
|
API_METHODS.parseUsercss = parse;
|
||||||
|
API_METHODS.findUsercss = find;
|
||||||
|
|
||||||
|
const TEMP_CODE_PREFIX = 'tempUsercssCode';
|
||||||
|
const TEMP_CODE_CLEANUP_DELAY = 60e3;
|
||||||
|
let tempCodeLastWriteDate = 0;
|
||||||
|
if (FIREFOX) {
|
||||||
|
// the temp code is created on direct installation of usercss URLs in FF
|
||||||
|
// and can be left behind in case the install page didn't open in time before
|
||||||
|
// the extension was updated/reloaded/disabled or the browser was closed
|
||||||
|
setTimeout(function poll() {
|
||||||
|
if (Date.now() - tempCodeLastWriteDate < TEMP_CODE_CLEANUP_DELAY) {
|
||||||
|
setTimeout(poll, TEMP_CODE_CLEANUP_DELAY);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
chrome.storage.local.get(null, storage => {
|
||||||
|
const leftovers = [];
|
||||||
|
for (const key in storage) {
|
||||||
|
if (key.startsWith(TEMP_CODE_PREFIX)) {
|
||||||
|
leftovers.push(key);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (leftovers.length) {
|
||||||
|
chrome.storage.local.remove(leftovers);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}, TEMP_CODE_CLEANUP_DELAY);
|
||||||
|
}
|
||||||
|
|
||||||
|
function buildMeta(style) {
|
||||||
|
if (style.usercssData) {
|
||||||
|
return Promise.resolve(style);
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
const {sourceCode} = style;
|
||||||
|
// allow sourceCode to be normalized
|
||||||
|
delete style.sourceCode;
|
||||||
|
return Promise.resolve(Object.assign(usercss.buildMeta(sourceCode), style));
|
||||||
|
} catch (e) {
|
||||||
|
return Promise.reject(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function assignVars(style) {
|
||||||
|
if (style.reason === 'config' && style.id) {
|
||||||
|
return style;
|
||||||
|
}
|
||||||
|
const dup = find(style);
|
||||||
|
if (dup) {
|
||||||
|
style.id = dup.id;
|
||||||
|
if (style.reason !== 'config') {
|
||||||
|
// preserve style.vars during update
|
||||||
|
usercss.assignVars(style, dup);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return style;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Parse the source and find the duplication
|
||||||
|
* @param _
|
||||||
|
* @param {String} _.sourceCode
|
||||||
|
* @param {Boolean=} _.checkDup
|
||||||
|
* @param {Boolean=} _.metaOnly
|
||||||
|
* @returns {Promise<{style, dup:Boolean?}>}
|
||||||
|
*/
|
||||||
|
function build({
|
||||||
|
sourceCode,
|
||||||
|
checkDup,
|
||||||
|
metaOnly,
|
||||||
|
}) {
|
||||||
|
const task = buildMeta({sourceCode});
|
||||||
|
return (metaOnly ? task : task.then(usercss.buildCode))
|
||||||
|
.then(style => ({
|
||||||
|
style,
|
||||||
|
dup: checkDup && find(style),
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parse the source, apply customizations, report fatal/syntax errors
|
||||||
|
function parse(style, allowErrors = false) {
|
||||||
|
// restore if stripped by getStyleWithNoCode
|
||||||
|
if (typeof style.sourceCode !== 'string') {
|
||||||
|
style.sourceCode = cachedStyles.byId.get(style.id).sourceCode;
|
||||||
|
}
|
||||||
|
return buildMeta(style)
|
||||||
|
.then(assignVars)
|
||||||
|
.then(style => usercss.buildCode(style, allowErrors));
|
||||||
|
}
|
||||||
|
|
||||||
|
function save(style, allowErrors = false) {
|
||||||
|
return parse(style, allowErrors)
|
||||||
|
.then(result =>
|
||||||
|
allowErrors ?
|
||||||
|
saveStyle(result.style).then(style => ({style, errors: result.errors})) :
|
||||||
|
saveStyle(result));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {Style|{name:string, namespace:string}} styleOrData
|
||||||
|
* @returns {Style}
|
||||||
|
*/
|
||||||
|
function find(styleOrData) {
|
||||||
|
if (styleOrData.id) return cachedStyles.byId.get(styleOrData.id);
|
||||||
|
const {name, namespace} = styleOrData.usercssData || styleOrData;
|
||||||
|
for (const dup of cachedStyles.list) {
|
||||||
|
const data = dup.usercssData;
|
||||||
|
if (!data) continue;
|
||||||
|
if (data.name === name &&
|
||||||
|
data.namespace === namespace) {
|
||||||
|
return dup;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function install({url, direct, downloaded, tab}, sender) {
|
||||||
|
tab = tab !== undefined ? tab : sender.tab;
|
||||||
|
url = url || tab.url;
|
||||||
|
if (direct && !downloaded) {
|
||||||
|
prefetchCodeForInstallation(tab.id, url);
|
||||||
|
}
|
||||||
|
return openURL({
|
||||||
|
url: '/install-usercss.html' +
|
||||||
|
'?updateUrl=' + encodeURIComponent(url) +
|
||||||
|
'&tabId=' + tab.id +
|
||||||
|
(direct ? '&direct=yes' : ''),
|
||||||
|
index: tab.index + 1,
|
||||||
|
openerTabId: tab.id,
|
||||||
|
currentWindow: null,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function prefetchCodeForInstallation(tabId, url) {
|
||||||
|
const key = TEMP_CODE_PREFIX + tabId;
|
||||||
|
tempCodeLastWriteDate = Date.now();
|
||||||
|
Promise.all([
|
||||||
|
download(url),
|
||||||
|
chromeLocal.setValue(key, {loading: true}),
|
||||||
|
]).then(([code]) => {
|
||||||
|
chromeLocal.setValue(key, code);
|
||||||
|
setTimeout(() => chromeLocal.remove(key), TEMP_CODE_CLEANUP_DELAY);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
})();
|
|
@ -1,141 +0,0 @@
|
||||||
/* global RX_META URLS download openURL */// toolbox.js
|
|
||||||
/* global addAPI bgReady */// common.js
|
|
||||||
/* global tabMan */// msg.js
|
|
||||||
'use strict';
|
|
||||||
|
|
||||||
bgReady.all.then(() => {
|
|
||||||
const installCodeCache = {};
|
|
||||||
|
|
||||||
addAPI(/** @namespace API */ {
|
|
||||||
usercss: {
|
|
||||||
getInstallCode(url) {
|
|
||||||
// when the installer tab is reloaded after the cache is expired, this will throw intentionally
|
|
||||||
const {code, timer} = installCodeCache[url];
|
|
||||||
clearInstallCode(url);
|
|
||||||
clearTimeout(timer);
|
|
||||||
return code;
|
|
||||||
},
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
// `glob`: pathname match pattern for webRequest
|
|
||||||
// `rx`: pathname regex to verify the URL really looks like a raw usercss
|
|
||||||
const maybeDistro = {
|
|
||||||
// https://github.com/StylishThemes/GitHub-Dark/raw/master/github-dark.user.css
|
|
||||||
'github.com': {
|
|
||||||
glob: '/*/raw/*',
|
|
||||||
rx: /^\/[^/]+\/[^/]+\/raw\/[^/]+\/[^/]+?\.user\.(css|styl)$/,
|
|
||||||
},
|
|
||||||
// https://raw.githubusercontent.com/StylishThemes/GitHub-Dark/master/github-dark.user.css
|
|
||||||
'raw.githubusercontent.com': {
|
|
||||||
glob: '/*',
|
|
||||||
rx: /^(\/[^/]+?){4}\.user\.(css|styl)$/,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
chrome.webRequest.onBeforeSendHeaders.addListener(maybeInstallFromDistro, {
|
|
||||||
urls: [
|
|
||||||
URLS.usw + 'api/style/*.user.css',
|
|
||||||
...URLS.usoArchiveRaw.map(s => s + 'usercss/*.user.css'),
|
|
||||||
...['greasy', 'sleazy'].map(s => `*://${s}fork.org/scripts/*/code/*.user.css`),
|
|
||||||
...[].concat(
|
|
||||||
...Object.entries(maybeDistro)
|
|
||||||
.map(([host, {glob}]) => makeUsercssGlobs(host, glob))),
|
|
||||||
],
|
|
||||||
types: ['main_frame'],
|
|
||||||
}, ['blocking']);
|
|
||||||
|
|
||||||
chrome.webRequest.onHeadersReceived.addListener(rememberContentType, {
|
|
||||||
urls: makeUsercssGlobs('*', '/*'),
|
|
||||||
types: ['main_frame'],
|
|
||||||
}, ['responseHeaders']);
|
|
||||||
|
|
||||||
tabMan.onUpdate(maybeInstall);
|
|
||||||
|
|
||||||
function clearInstallCode(url) {
|
|
||||||
return delete installCodeCache[url];
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Sites may be using custom types like text/stylus so this coarse filter only excludes html */
|
|
||||||
function isContentTypeText(type) {
|
|
||||||
return /^text\/(?!html)/i.test(type);
|
|
||||||
}
|
|
||||||
|
|
||||||
// in Firefox we have to use a content script to read file://
|
|
||||||
async function loadFromFile(tabId) {
|
|
||||||
return (await browser.tabs.executeScript(tabId, {file: '/content/install-hook-usercss.js'}))[0];
|
|
||||||
}
|
|
||||||
|
|
||||||
async function loadFromUrl(tabId, url) {
|
|
||||||
return (
|
|
||||||
url.startsWith('file:') ||
|
|
||||||
tabMan.get(tabId, isContentTypeText.name) ||
|
|
||||||
isContentTypeText((await fetch(url, {method: 'HEAD'})).headers.get('content-type'))
|
|
||||||
) && download(url);
|
|
||||||
}
|
|
||||||
|
|
||||||
function makeInstallerUrl(url) {
|
|
||||||
return `${URLS.installUsercss}?updateUrl=${encodeURIComponent(url)}`;
|
|
||||||
}
|
|
||||||
|
|
||||||
function makeUsercssGlobs(host, path) {
|
|
||||||
return '%css,%css?*,%styl,%styl?*'.replace(/%/g, `*://${host}${path}.user.`).split(',');
|
|
||||||
}
|
|
||||||
|
|
||||||
async function maybeInstall({tabId, url, oldUrl = ''}) {
|
|
||||||
if (url.includes('.user.') &&
|
|
||||||
/^(https?|file|ftps?):/.test(url) &&
|
|
||||||
/\.user\.(css|styl)$/.test(url.split(/[#?]/, 1)[0]) &&
|
|
||||||
!oldUrl.startsWith(makeInstallerUrl(url))) {
|
|
||||||
const inTab = url.startsWith('file:') && !chrome.app;
|
|
||||||
const code = await (inTab ? loadFromFile : loadFromUrl)(tabId, url);
|
|
||||||
if (!/^\s*</.test(code) && RX_META.test(code)) {
|
|
||||||
await openInstallerPage(tabId, url, {code, inTab});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Faster installation on known distribution sites to avoid flicker of css text */
|
|
||||||
function maybeInstallFromDistro({tabId, url}) {
|
|
||||||
const u = new URL(url);
|
|
||||||
const m = maybeDistro[u.hostname];
|
|
||||||
if (!m || m.rx.test(u.pathname)) {
|
|
||||||
openInstallerPage(tabId, url, {});
|
|
||||||
// Silently suppress navigation.
|
|
||||||
// Don't redirect to the install URL as it'll flash the text!
|
|
||||||
return {cancel: true};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async function openInstallerPage(tabId, url, {code, inTab} = {}) {
|
|
||||||
const newUrl = makeInstallerUrl(url);
|
|
||||||
if (inTab) {
|
|
||||||
const tab = await browser.tabs.get(tabId);
|
|
||||||
return openURL({
|
|
||||||
url: `${newUrl}&tabId=${tabId}`,
|
|
||||||
active: tab.active,
|
|
||||||
index: tab.index + 1,
|
|
||||||
openerTabId: tabId,
|
|
||||||
currentWindow: null,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
const timer = setTimeout(clearInstallCode, 10e3, url);
|
|
||||||
installCodeCache[url] = {code, timer};
|
|
||||||
try {
|
|
||||||
await browser.tabs.update(tabId, {url: newUrl});
|
|
||||||
} catch (err) {
|
|
||||||
// FIXME: remove this when kiwi supports tabs.update
|
|
||||||
// https://github.com/openstyles/stylus/issues/1367
|
|
||||||
if (/Tabs cannot be edited right now/i.test(err.message)) {
|
|
||||||
return browser.tabs.create({url: newUrl});
|
|
||||||
}
|
|
||||||
throw err;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Remember Content-Type to avoid wasting time to re-fetch in loadFromUrl **/
|
|
||||||
function rememberContentType({tabId, responseHeaders}) {
|
|
||||||
const h = responseHeaders.find(h => h.name.toLowerCase() === 'content-type');
|
|
||||||
tabMan.set(tabId, isContentTypeText.name, h && isContentTypeText(h.value) || undefined);
|
|
||||||
}
|
|
||||||
});
|
|
|
@ -1,163 +0,0 @@
|
||||||
/* global API */// msg.js
|
|
||||||
/* global RX_META deepCopy download */// toolbox.js
|
|
||||||
'use strict';
|
|
||||||
|
|
||||||
const usercssMan = {
|
|
||||||
|
|
||||||
GLOBAL_META: Object.entries({
|
|
||||||
author: null,
|
|
||||||
description: null,
|
|
||||||
homepageURL: 'url',
|
|
||||||
updateURL: 'updateUrl',
|
|
||||||
name: null,
|
|
||||||
}),
|
|
||||||
|
|
||||||
/** `src` is a style or vars */
|
|
||||||
async assignVars(style, src) {
|
|
||||||
const meta = style.usercssData;
|
|
||||||
const meta2 = src.usercssData;
|
|
||||||
const {vars} = meta;
|
|
||||||
const oldVars = meta2 ? meta2.vars : src;
|
|
||||||
if (vars && oldVars) {
|
|
||||||
// The type of var might be changed during the update. Set value to null if the value is invalid.
|
|
||||||
for (const [key, v] of Object.entries(vars)) {
|
|
||||||
const old = oldVars[key] && oldVars[key].value;
|
|
||||||
if (old) v.value = old;
|
|
||||||
}
|
|
||||||
meta.vars = await API.worker.nullifyInvalidVars(vars);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
async build({
|
|
||||||
styleId,
|
|
||||||
sourceCode,
|
|
||||||
vars,
|
|
||||||
checkDup,
|
|
||||||
metaOnly,
|
|
||||||
assignVars,
|
|
||||||
initialUrl,
|
|
||||||
}) {
|
|
||||||
// downloading here while install-usercss page is loading to avoid the wait
|
|
||||||
if (initialUrl) sourceCode = await download(initialUrl);
|
|
||||||
const style = await usercssMan.buildMeta({sourceCode});
|
|
||||||
const dup = (checkDup || assignVars) &&
|
|
||||||
await usercssMan.find(styleId ? {id: styleId} : style);
|
|
||||||
let log;
|
|
||||||
if (!metaOnly) {
|
|
||||||
if (vars || assignVars) {
|
|
||||||
await usercssMan.assignVars(style, vars || dup);
|
|
||||||
}
|
|
||||||
await usercssMan.buildCode(style);
|
|
||||||
log = style.log; // extracting the non-enumerable prop, otherwise it won't survive messaging
|
|
||||||
}
|
|
||||||
return {style, dup, log};
|
|
||||||
},
|
|
||||||
|
|
||||||
async buildCode(style) {
|
|
||||||
const {sourceCode: code, usercssData: {vars, preprocessor}} = style;
|
|
||||||
const match = code.match(RX_META);
|
|
||||||
const i = match.index;
|
|
||||||
const j = i + match[0].length;
|
|
||||||
const codeNoMeta = code.slice(0, i) + blankOut(code, i, j) + code.slice(j);
|
|
||||||
const {sections, errors, log} = await API.worker.compileUsercss(preprocessor, codeNoMeta, vars);
|
|
||||||
const recoverable = errors.every(e => e.recoverable);
|
|
||||||
if (!sections.length || !recoverable) {
|
|
||||||
throw !recoverable ? errors : 'Style does not contain any actual CSS to apply.';
|
|
||||||
}
|
|
||||||
style.sections = sections;
|
|
||||||
// adding a non-enumerable prop so it won't be written to storage
|
|
||||||
if (log) Object.defineProperty(style, 'log', {value: log});
|
|
||||||
return style;
|
|
||||||
},
|
|
||||||
|
|
||||||
async buildMeta(style) {
|
|
||||||
if (style.usercssData) {
|
|
||||||
return style;
|
|
||||||
}
|
|
||||||
// remember normalized sourceCode
|
|
||||||
let code = style.sourceCode = style.sourceCode.replace(/\r\n?/g, '\n');
|
|
||||||
style = Object.assign({
|
|
||||||
enabled: true,
|
|
||||||
sections: [],
|
|
||||||
}, style);
|
|
||||||
const match = code.match(RX_META);
|
|
||||||
if (!match) {
|
|
||||||
return Promise.reject(new Error('Could not find metadata.'));
|
|
||||||
}
|
|
||||||
try {
|
|
||||||
code = blankOut(code, 0, match.index) + match[0];
|
|
||||||
const {metadata} = await API.worker.parseUsercssMeta(code);
|
|
||||||
style.usercssData = metadata;
|
|
||||||
// https://github.com/openstyles/stylus/issues/560#issuecomment-440561196
|
|
||||||
for (const [key, globalKey] of usercssMan.GLOBAL_META) {
|
|
||||||
const val = metadata[key];
|
|
||||||
if (val !== undefined) {
|
|
||||||
style[globalKey || key] = val;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return style;
|
|
||||||
} catch (err) {
|
|
||||||
if (err.code) {
|
|
||||||
const args = err.code === 'missingMandatory' || err.code === 'missingChar'
|
|
||||||
? err.args.map(e => e.length === 1 ? JSON.stringify(e) : e).join(', ')
|
|
||||||
: err.args;
|
|
||||||
const msg = chrome.i18n.getMessage(`meta_${(err.code)}`, args);
|
|
||||||
if (msg) err.message = msg;
|
|
||||||
}
|
|
||||||
return Promise.reject(err);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
async configVars(id, vars) {
|
|
||||||
const style = deepCopy(await API.styles.get(id));
|
|
||||||
style.usercssData.vars = vars;
|
|
||||||
await usercssMan.buildCode(style);
|
|
||||||
return (await API.styles.install(style, 'config'))
|
|
||||||
.usercssData.vars;
|
|
||||||
},
|
|
||||||
|
|
||||||
async editSave(style) {
|
|
||||||
style = await usercssMan.parse(style);
|
|
||||||
return {
|
|
||||||
log: style.log, // extracting the non-enumerable prop, otherwise it won't survive messaging
|
|
||||||
style: await API.styles.editSave(style),
|
|
||||||
};
|
|
||||||
},
|
|
||||||
|
|
||||||
async find(styleOrData) {
|
|
||||||
if (styleOrData.id) {
|
|
||||||
return API.styles.get(styleOrData.id);
|
|
||||||
}
|
|
||||||
const {name, namespace} = styleOrData.usercssData || styleOrData;
|
|
||||||
for (const dup of await API.styles.getAll()) {
|
|
||||||
const data = dup.usercssData;
|
|
||||||
if (data &&
|
|
||||||
data.name === name &&
|
|
||||||
data.namespace === namespace) {
|
|
||||||
return dup;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
async install(style, opts) {
|
|
||||||
return API.styles.install(await usercssMan.parse(style, opts));
|
|
||||||
},
|
|
||||||
|
|
||||||
async parse(style, {dup, vars} = {}) {
|
|
||||||
style = await usercssMan.buildMeta(style);
|
|
||||||
// preserve style.vars during update
|
|
||||||
if (dup || (dup = await usercssMan.find(style))) {
|
|
||||||
style.id = dup.id;
|
|
||||||
}
|
|
||||||
if (vars || (vars = dup)) {
|
|
||||||
await usercssMan.assignVars(style, vars);
|
|
||||||
}
|
|
||||||
return usercssMan.buildCode(style);
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
/** Replaces everything with spaces to keep the original length,
|
|
||||||
* but preserves the line breaks to keep the original line/col relation */
|
|
||||||
function blankOut(str, start = 0, end = str.length) {
|
|
||||||
return str.slice(start, end).replace(/[^\r\n]/g, ' ');
|
|
||||||
}
|
|
|
@ -1,158 +0,0 @@
|
||||||
/* global URLS stringAsRegExp */// toolbox.js
|
|
||||||
/* global usercssMan */
|
|
||||||
'use strict';
|
|
||||||
|
|
||||||
const usoApi = {};
|
|
||||||
|
|
||||||
(() => {
|
|
||||||
const pingers = {};
|
|
||||||
|
|
||||||
usoApi.pingback = (usoId, delay) => {
|
|
||||||
clearTimeout(pingers[usoId]);
|
|
||||||
delete pingers[usoId];
|
|
||||||
if (delay > 0) {
|
|
||||||
return new Promise(resolve => (pingers[usoId] = setTimeout(ping, delay, usoId, resolve)));
|
|
||||||
} else if (delay !== false) {
|
|
||||||
return ping(usoId);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Replicating USO-Archive format
|
|
||||||
* https://github.com/33kk/uso-archive/blob/flomaster/lib/uso.js
|
|
||||||
* https://github.com/33kk/uso-archive/blob/flomaster/lib/converters.js
|
|
||||||
*/
|
|
||||||
usoApi.toUsercss = async (data, {metaOnly = true, varsUrl} = {}) => {
|
|
||||||
const badKeys = {};
|
|
||||||
const newKeys = [];
|
|
||||||
const descr = JSON.stringify(data.description.trim());
|
|
||||||
const vars = (data.style_settings || []).map(makeVar, {badKeys, newKeys}).join('');
|
|
||||||
const sourceCode = `\
|
|
||||||
/* ==UserStyle==
|
|
||||||
@name ${data.name}
|
|
||||||
@namespace USO Archive
|
|
||||||
@version ${data.updated.replace(/-/g, '').replace(/[T:]/g, '.').slice(0, 14)}
|
|
||||||
@description ${/^"['`]|\\/.test(descr) ? descr : descr.slice(1, -1)}
|
|
||||||
@author ${(data.user || {}).name || '?'}
|
|
||||||
@license ${makeLicense(data.license)}${vars ? '\n@preprocessor uso' + vars : ''}`
|
|
||||||
.replace(/\*\//g, '*\\/') +
|
|
||||||
`==/UserStyle== */\n${newKeys[0] ? useNewKeys(data.css, badKeys) : data.css}`;
|
|
||||||
const {style} = await usercssMan.build({sourceCode, metaOnly});
|
|
||||||
usoApi.useVarsUrl(style, varsUrl);
|
|
||||||
return {style, badKeys, newKeys};
|
|
||||||
};
|
|
||||||
|
|
||||||
usoApi.useVarsUrl = (style, url) => {
|
|
||||||
if (!/\?ik-/.test(url)) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const cfg = {badKeys: {}, newKeys: []};
|
|
||||||
const {vars} = style.usercssData;
|
|
||||||
if (!vars) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
for (let [key, val] of new URLSearchParams(url.split('?')[1])) {
|
|
||||||
if (!key.startsWith('ik-')) continue;
|
|
||||||
key = makeKey(key.slice(3), cfg);
|
|
||||||
const v = vars[key];
|
|
||||||
if (!v) continue;
|
|
||||||
if (v.options) {
|
|
||||||
let sel = val.startsWith('ik-') && optByName(v, makeKey(val.slice(3), cfg));
|
|
||||||
if (!sel) {
|
|
||||||
key += '-custom';
|
|
||||||
sel = optByName(v, key + '-dropdown');
|
|
||||||
if (sel) vars[key].value = val;
|
|
||||||
}
|
|
||||||
if (sel) v.value = sel.name;
|
|
||||||
} else {
|
|
||||||
v.value = val;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
};
|
|
||||||
|
|
||||||
async function ping(id, resolve) {
|
|
||||||
await fetch(`${URLS.uso}styles/install/${id}?source=stylish-ch`);
|
|
||||||
if (resolve) resolve(true);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
function makeKey(key, {badKeys, newKeys}) {
|
|
||||||
let res = badKeys[key];
|
|
||||||
if (!res) {
|
|
||||||
res = key.replace(/[^-\w]/g, '-');
|
|
||||||
res += newKeys.includes(res) ? '-' : '';
|
|
||||||
if (key !== res) {
|
|
||||||
badKeys[key] = res;
|
|
||||||
newKeys.push(res);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return res;
|
|
||||||
}
|
|
||||||
|
|
||||||
function makeLicense(s) {
|
|
||||||
return !s ? 'NO-REDISTRIBUTION' :
|
|
||||||
s === 'publicdomain' ? 'CC0-1.0' :
|
|
||||||
s.startsWith('ccby') ? `${s.toUpperCase().match(/(..)/g).join('-')}-4.0` :
|
|
||||||
s;
|
|
||||||
}
|
|
||||||
|
|
||||||
function makeVar({
|
|
||||||
label,
|
|
||||||
setting_type: type,
|
|
||||||
install_key: ik,
|
|
||||||
style_setting_options: opts,
|
|
||||||
}) {
|
|
||||||
const cfg = this;
|
|
||||||
let value, suffix;
|
|
||||||
ik = makeKey(ik, cfg);
|
|
||||||
label = JSON.stringify(label);
|
|
||||||
switch (type) {
|
|
||||||
|
|
||||||
case 'color':
|
|
||||||
value = opts[0].value;
|
|
||||||
break;
|
|
||||||
|
|
||||||
case 'text':
|
|
||||||
value = JSON.stringify(opts[0].value);
|
|
||||||
break;
|
|
||||||
|
|
||||||
case 'image': {
|
|
||||||
const ikCust = `${ik}-custom`;
|
|
||||||
opts.push({
|
|
||||||
label: 'Custom',
|
|
||||||
install_key: `${ikCust}-dropdown`,
|
|
||||||
value: `/*[[${ikCust}]]*/`,
|
|
||||||
});
|
|
||||||
suffix = `\n@advanced text ${ikCust} ${label.slice(0, -1)} (Custom)" "https://foo.com/123.jpg"`;
|
|
||||||
type = 'dropdown';
|
|
||||||
} // fallthrough
|
|
||||||
|
|
||||||
case 'dropdown':
|
|
||||||
value = '';
|
|
||||||
for (const o of opts) {
|
|
||||||
const def = o.default ? '*' : '';
|
|
||||||
const val = o.value;
|
|
||||||
const s = ` ${makeKey(o.install_key, cfg)} ${JSON.stringify(o.label + def)} <<<EOT${
|
|
||||||
val.includes('\n') ? '\n' : ' '}${val} EOT;\n`;
|
|
||||||
value = def ? s + value : value + s;
|
|
||||||
}
|
|
||||||
value = `{\n${value}}`;
|
|
||||||
break;
|
|
||||||
|
|
||||||
default:
|
|
||||||
value = '"ERROR: unknown type"';
|
|
||||||
}
|
|
||||||
return `\n@advanced ${type} ${ik} ${label} ${value}${suffix || ''}`;
|
|
||||||
}
|
|
||||||
|
|
||||||
function optByName(v, name) {
|
|
||||||
return v.options.find(o => o.name === name);
|
|
||||||
}
|
|
||||||
|
|
||||||
function useNewKeys(css, badKeys) {
|
|
||||||
const rxsKeys = stringAsRegExp(Object.keys(badKeys).join('\n'), '', true).replace(/\n/g, '|');
|
|
||||||
const rxUsoVars = new RegExp(`(/\\*\\[\\[)(${rxsKeys})(?=]]\\*/)`, 'g');
|
|
||||||
return css.replace(rxUsoVars, (s, a, key) => a + badKeys[key]);
|
|
||||||
}
|
|
||||||
})();
|
|
|
@ -1,120 +0,0 @@
|
||||||
/* global API msg */// msg.js
|
|
||||||
/* global URLS */ // toolbox.js
|
|
||||||
/* global tokenMan */
|
|
||||||
'use strict';
|
|
||||||
|
|
||||||
const uswApi = (() => {
|
|
||||||
|
|
||||||
//#region Internals
|
|
||||||
|
|
||||||
class TokenHooks {
|
|
||||||
constructor(id) {
|
|
||||||
this.id = id;
|
|
||||||
}
|
|
||||||
keyName(name) {
|
|
||||||
return `${name}/${this.id}`;
|
|
||||||
}
|
|
||||||
query(query) {
|
|
||||||
return Object.assign(query, {vendor_data: this.id});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function fakeUsercssHeader(style) {
|
|
||||||
const {name, _usw: u = {}} = style;
|
|
||||||
const meta = Object.entries({
|
|
||||||
'@name': u.name || name || '?',
|
|
||||||
'@version': // Same as USO-archive version: YYYYMMDD.hh.mm
|
|
||||||
new Date().toISOString().replace(/^(\d+)-(\d+)-(\d+)T(\d+):(\d+).+/, '$1$2$3.$4.$5'),
|
|
||||||
'@namespace': u.namespace !== '?' && u.namespace ||
|
|
||||||
u.username && `userstyles.world/user/${u.username}` ||
|
|
||||||
'?',
|
|
||||||
'@description': u.description,
|
|
||||||
'@author': u.username,
|
|
||||||
'@license': u.license,
|
|
||||||
});
|
|
||||||
const maxKeyLen = meta.reduce((res, [k]) => Math.max(res, k.length), 0);
|
|
||||||
return [
|
|
||||||
'/* ==UserStyle==',
|
|
||||||
...meta.map(([k, v]) => v && `${k}${' '.repeat(maxKeyLen - k.length + 2)}${v}`).filter(Boolean),
|
|
||||||
'==/UserStyle== */',
|
|
||||||
].join('\n') + '\n\n';
|
|
||||||
}
|
|
||||||
|
|
||||||
async function linkStyle(style, sourceCode) {
|
|
||||||
const {id} = style;
|
|
||||||
const metadata = await API.worker.parseUsercssMeta(sourceCode).catch(console.warn) || {};
|
|
||||||
const uswData = Object.assign({}, style, {metadata, sourceCode});
|
|
||||||
API.data.set('usw' + id, uswData);
|
|
||||||
const token = await tokenMan.getToken('userstylesworld', true, new TokenHooks(id));
|
|
||||||
const info = await uswFetch('style', token);
|
|
||||||
const data = style._usw = Object.assign({token}, info);
|
|
||||||
style.url = style.url || data.homepage || `${URLS.usw}style/${data.id}`;
|
|
||||||
await uswSave(style);
|
|
||||||
return data;
|
|
||||||
}
|
|
||||||
|
|
||||||
async function uswFetch(path, token, opts) {
|
|
||||||
opts = Object.assign({credentials: 'omit'}, opts);
|
|
||||||
opts.headers = Object.assign({Authorization: `Bearer ${token}`}, opts.headers);
|
|
||||||
return (await (await fetch(`${URLS.usw}api/${path}`, opts)).json()).data;
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Uses a custom method when broadcasting and avoids needlessly sending the entire style */
|
|
||||||
async function uswSave(style) {
|
|
||||||
const {id, _usw} = style;
|
|
||||||
await API.styles.save(style, {broadcast: false});
|
|
||||||
msg.broadcastExtension({method: 'uswData', style: {id, _usw}});
|
|
||||||
}
|
|
||||||
|
|
||||||
//#endregion
|
|
||||||
//#region Exports
|
|
||||||
|
|
||||||
return {
|
|
||||||
/**
|
|
||||||
* @param {number} id
|
|
||||||
* @param {string} sourceCode
|
|
||||||
* @return {Promise<string>}
|
|
||||||
*/
|
|
||||||
async publish(id, sourceCode) {
|
|
||||||
const style = await API.styles.get(id);
|
|
||||||
const code = style.usercssData ? sourceCode
|
|
||||||
: fakeUsercssHeader(style) + sourceCode;
|
|
||||||
const data = (style._usw || {}).token
|
|
||||||
? style._usw
|
|
||||||
: await linkStyle(style, code);
|
|
||||||
return uswFetch(`style/${data.id}`, data.token, {
|
|
||||||
method: 'POST',
|
|
||||||
headers: {'Content-Type': 'application/json'},
|
|
||||||
body: JSON.stringify({code}),
|
|
||||||
});
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param {number} id
|
|
||||||
* @return {Promise<void>}
|
|
||||||
*/
|
|
||||||
async revoke(id) {
|
|
||||||
await tokenMan.revokeToken('userstylesworld', new TokenHooks(id));
|
|
||||||
const style = await API.styles.get(id);
|
|
||||||
if (style) {
|
|
||||||
style._usw = {};
|
|
||||||
await uswSave(style);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
//#endregion
|
|
||||||
})();
|
|
||||||
|
|
||||||
/* Doing this outside so we don't break IDE's recognition of the exported methods in IIFE */
|
|
||||||
for (const [k, fn] of Object.entries(uswApi)) {
|
|
||||||
uswApi[k] = async (id, ...args) => {
|
|
||||||
API.data.set('usw' + id, true);
|
|
||||||
try {
|
|
||||||
/* Awaiting inside `try` so that `finally` runs when done */
|
|
||||||
return await fn(id, ...args);
|
|
||||||
} finally {
|
|
||||||
API.data.del('usw' + id);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
763
content/apply.js
763
content/apply.js
|
@ -1,271 +1,616 @@
|
||||||
/* global API msg */// msg.js
|
/* eslint no-var: 0 */
|
||||||
/* global StyleInjector */
|
|
||||||
/* global prefs */
|
|
||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
(() => {
|
(() => {
|
||||||
if (window.INJECTED === 1) return;
|
if (typeof window.applyOnMessage === 'function') {
|
||||||
window.INJECTED = 1;
|
// some weird bug in new Chrome: the content script gets injected multiple times
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const CHROME = chrome.app ? parseInt(navigator.userAgent.match(/Chrom\w+\/(?:\d+\.){2}(\d+)|$/)[1]) : NaN;
|
||||||
|
var ID_PREFIX = 'stylus-';
|
||||||
|
var ROOT = document.documentElement;
|
||||||
|
var isOwnPage = location.protocol.endsWith('-extension:');
|
||||||
|
var disableAll = false;
|
||||||
|
var exposeIframes = false;
|
||||||
|
var styleElements = new Map();
|
||||||
|
var disabledElements = new Map();
|
||||||
|
var retiredStyleTimers = new Map();
|
||||||
|
var docRewriteObserver;
|
||||||
|
var docRootObserver;
|
||||||
|
|
||||||
/** true -> when the page styles are received,
|
// FF59+ bug workaround
|
||||||
* false -> when disableAll mode is on at start, the styles won't be sent
|
// See https://github.com/openstyles/stylus/issues/461
|
||||||
* so while disableAll lasts we can ignore messages about style updates because
|
// Since it's easy to spoof the browser version in pre-Quantum FF we're checking
|
||||||
* the tab will explicitly ask for all styles in bulk when disableAll mode ends */
|
// for getPreventDefault which got removed in FF59 https://bugzil.la/691151
|
||||||
let hasStyles = false;
|
const FF_BUG461 = !CHROME && !isOwnPage && !Event.prototype.getPreventDefault;
|
||||||
let isDisabled = false;
|
const pageContextQueue = [];
|
||||||
let isTab = !chrome.tabs || location.pathname !== '/popup.html';
|
|
||||||
const order = {main: [], prio: []};
|
|
||||||
const calcOrder = ({id}) =>
|
|
||||||
(order.prio[id] || 0) * 1e6 ||
|
|
||||||
order.main[id] ||
|
|
||||||
id + .5e6; // no order = at the end of `main`
|
|
||||||
const isFrame = window !== parent;
|
|
||||||
const isFrameAboutBlank = isFrame && location.href === 'about:blank';
|
|
||||||
const isUnstylable = !chrome.app && document instanceof XMLDocument;
|
|
||||||
const styleInjector = StyleInjector({
|
|
||||||
compare: (a, b) => calcOrder(a) - calcOrder(b),
|
|
||||||
onUpdate: onInjectorUpdate,
|
|
||||||
});
|
|
||||||
// dynamic iframes don't have a URL yet so we'll use their parent's URL (hash isn't inherited)
|
|
||||||
let matchUrl = isFrameAboutBlank && tryCatch(() => parent.location.href.split('#')[0]) ||
|
|
||||||
location.href;
|
|
||||||
|
|
||||||
// save it now because chrome.runtime will be unavailable in the orphaned script
|
requestStyles();
|
||||||
const orphanEventId = chrome.runtime.id;
|
chrome.runtime.onMessage.addListener(applyOnMessage);
|
||||||
let isOrphaned;
|
window.applyOnMessage = applyOnMessage;
|
||||||
// firefox doesn't orphanize content scripts so the old elements stay
|
|
||||||
if (!chrome.app) styleInjector.clearOrphans();
|
|
||||||
|
|
||||||
/** @type chrome.runtime.Port */
|
if (!isOwnPage) {
|
||||||
let port;
|
window.dispatchEvent(new CustomEvent(chrome.runtime.id));
|
||||||
let lazyBadge = isFrame;
|
window.addEventListener(chrome.runtime.id, orphanCheck, true);
|
||||||
let parentDomain;
|
|
||||||
|
|
||||||
/* about:blank iframes are often used by sites for file upload or background tasks
|
|
||||||
* and they may break if unexpected DOM stuff is present at `load` event
|
|
||||||
* so we'll add the styles only if the iframe becomes visible */
|
|
||||||
const xoEventId = `${Math.random()}`;
|
|
||||||
/** @type IntersectionObserver */
|
|
||||||
let xo;
|
|
||||||
window[Symbol.for('xo')] = (el, cb) => {
|
|
||||||
if (!xo) xo = new IntersectionObserver(onIntersect, {rootMargin: '100%'});
|
|
||||||
el.addEventListener(xoEventId, cb, {once: true});
|
|
||||||
xo.observe(el);
|
|
||||||
};
|
|
||||||
|
|
||||||
// FIXME: move this to background page when following bugs are fixed:
|
|
||||||
// https://bugzil.la/1587723, https://crbug.com/968651
|
|
||||||
const mqDark = matchMedia('(prefers-color-scheme: dark)');
|
|
||||||
mqDark.onchange = e => API.colorScheme.updateSystemPreferDark(e.matches);
|
|
||||||
mqDark.onchange(mqDark);
|
|
||||||
|
|
||||||
// Declare all vars before init() or it'll throw due to "temporal dead zone" of const/let
|
|
||||||
init();
|
|
||||||
|
|
||||||
// the popup needs a check as it's not a tab but can be opened in a tab manually for whatever reason
|
|
||||||
if (!isTab) {
|
|
||||||
chrome.tabs.getCurrent(tab => {
|
|
||||||
isTab = Boolean(tab);
|
|
||||||
if (tab && styleInjector.list.length) updateCount();
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
msg.onTab(applyOnMessage);
|
function requestStyles(options, callback = applyStyles) {
|
||||||
window.addEventListener('pageshow', e => {
|
if (!chrome.app && document instanceof XMLDocument) {
|
||||||
if (e.isTrusted && e.persisted) { // bfcache
|
chrome.runtime.sendMessage({method: 'styleViaAPI', action: 'styleApply'});
|
||||||
updateCount();
|
return;
|
||||||
}
|
}
|
||||||
});
|
var matchUrl = location.href;
|
||||||
|
if (!matchUrl.match(/^(http|file|chrome|ftp)/)) {
|
||||||
if (!chrome.tabs) {
|
// dynamic about: and javascript: iframes don't have an URL yet
|
||||||
window.dispatchEvent(new CustomEvent(orphanEventId));
|
// so we'll try the parent frame which is guaranteed to have a real URL
|
||||||
window.addEventListener(orphanEventId, orphanCheck, true);
|
try {
|
||||||
}
|
if (window !== parent) {
|
||||||
|
matchUrl = parent.location.href;
|
||||||
function onInjectorUpdate() {
|
}
|
||||||
if (!isOrphaned) {
|
} catch (e) {}
|
||||||
updateCount();
|
|
||||||
const onOff = prefs[styleInjector.list.length ? 'subscribe' : 'unsubscribe'];
|
|
||||||
onOff('disableAll', updateDisableAll);
|
|
||||||
if (isFrame) {
|
|
||||||
updateExposeIframes();
|
|
||||||
onOff('exposeIframes', updateExposeIframes);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
const request = Object.assign({
|
||||||
|
method: 'getStylesForFrame',
|
||||||
async function init() {
|
asHash: true,
|
||||||
if (isUnstylable) {
|
matchUrl,
|
||||||
await API.styleViaAPI({method: 'styleApply'});
|
}, options);
|
||||||
|
// On own pages we request the styles directly to minimize delay and flicker
|
||||||
|
if (typeof API === 'function') {
|
||||||
|
API.getStyles(request).then(callback);
|
||||||
|
} else if (!CHROME && getStylesFallback(request)) {
|
||||||
|
// NOP
|
||||||
} else {
|
} else {
|
||||||
const SYM_ID = 'styles';
|
chrome.runtime.sendMessage(request, callback);
|
||||||
const SYM = Symbol.for(SYM_ID);
|
|
||||||
const parentStyles = isFrameAboutBlank &&
|
|
||||||
tryCatch(() => parent[parent.Symbol.for(SYM_ID)]);
|
|
||||||
const styles =
|
|
||||||
window[SYM] ||
|
|
||||||
parentStyles && await new Promise(onFrameElementInView) && parentStyles ||
|
|
||||||
!isFrameAboutBlank && chrome.app && !chrome.tabs && tryCatch(getStylesViaXhr) ||
|
|
||||||
await API.styles.getSectionsByUrl(matchUrl, null, true);
|
|
||||||
if (styles.cfg) {
|
|
||||||
isDisabled = styles.cfg.disableAll;
|
|
||||||
Object.assign(order, styles.cfg.order);
|
|
||||||
}
|
|
||||||
hasStyles = !isDisabled;
|
|
||||||
if (hasStyles) {
|
|
||||||
window[SYM] = styles;
|
|
||||||
await styleInjector.apply(styles);
|
|
||||||
} else {
|
|
||||||
delete window[SYM];
|
|
||||||
prefs.subscribe('disableAll', updateDisableAll);
|
|
||||||
}
|
|
||||||
styleInjector.toggle(hasStyles);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Must be executed inside try/catch */
|
/**
|
||||||
function getStylesViaXhr() {
|
* TODO: remove when FF fixes the bug.
|
||||||
const blobId = document.cookie.split(chrome.runtime.id + '=')[1].split(';')[0];
|
* Firefox borks sendMessage in same-origin iframes that have 'src' with a real path on the site.
|
||||||
const url = 'blob:' + chrome.runtime.getURL(blobId);
|
* We implement a workaround for the initial styleApply case only.
|
||||||
document.cookie = `${chrome.runtime.id}=1; max-age=0`; // remove our cookie
|
* Everything else (like toggling of styles) is still buggy.
|
||||||
const xhr = new XMLHttpRequest();
|
* @param {Object} msg
|
||||||
xhr.open('GET', url, false); // synchronous
|
* @param {Function} callback
|
||||||
xhr.send();
|
* @returns {Boolean|undefined}
|
||||||
URL.revokeObjectURL(url);
|
*/
|
||||||
return JSON.parse(xhr.response);
|
function getStylesFallback(msg) {
|
||||||
|
if (window !== parent &&
|
||||||
|
location.href !== 'about:blank') {
|
||||||
|
try {
|
||||||
|
if (parent.location.origin === location.origin &&
|
||||||
|
parent.location.href !== location.href) {
|
||||||
|
chrome.runtime.connect({name: 'getStyles:' + JSON.stringify(msg)});
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
} catch (e) {}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function applyOnMessage(request) {
|
function applyOnMessage(request, sender, sendResponse) {
|
||||||
const {method} = request;
|
if (request.styles === 'DIY') {
|
||||||
if (isUnstylable) {
|
// Do-It-Yourself tells our built-in pages to fetch the styles directly
|
||||||
if (method === 'urlChanged') {
|
// which is faster because IPC messaging JSON-ifies everything internally
|
||||||
request.method = 'styleReplaceAll';
|
requestStyles({}, styles => {
|
||||||
}
|
request.styles = styles;
|
||||||
if (/^(style|updateCount)/.test(method)) {
|
applyOnMessage(request);
|
||||||
API.styleViaAPI(request);
|
});
|
||||||
return;
|
return;
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const {style} = request;
|
if (!chrome.app && document instanceof XMLDocument && request.method !== 'ping') {
|
||||||
switch (method) {
|
request.action = request.method;
|
||||||
case 'ping':
|
request.method = 'styleViaAPI';
|
||||||
return true;
|
request.styles = null;
|
||||||
|
if (request.style) {
|
||||||
|
request.style.sections = null;
|
||||||
|
}
|
||||||
|
chrome.runtime.sendMessage(request);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (request.method) {
|
||||||
case 'styleDeleted':
|
case 'styleDeleted':
|
||||||
styleInjector.remove(style.id);
|
removeStyle(request);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case 'styleUpdated':
|
case 'styleUpdated':
|
||||||
if (!hasStyles && isDisabled) break;
|
if (request.codeIsUpdated === false) {
|
||||||
if (style.enabled) {
|
applyStyleState(request.style);
|
||||||
API.styles.getSectionsByUrl(matchUrl, style.id).then(sections =>
|
break;
|
||||||
sections[style.id]
|
}
|
||||||
? styleInjector.apply(sections)
|
if (request.style.enabled) {
|
||||||
: styleInjector.remove(style.id));
|
removeStyle({id: request.style.id, retire: true});
|
||||||
|
requestStyles({id: request.style.id});
|
||||||
} else {
|
} else {
|
||||||
styleInjector.remove(style.id);
|
removeStyle(request.style);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case 'styleAdded':
|
case 'styleAdded':
|
||||||
if (!hasStyles && isDisabled) break;
|
if (request.style.enabled) {
|
||||||
if (style.enabled) {
|
requestStyles({id: request.style.id});
|
||||||
API.styles.getSectionsByUrl(matchUrl, style.id)
|
|
||||||
.then(styleInjector.apply);
|
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case 'styleSort':
|
case 'styleApply':
|
||||||
Object.assign(order, request.order);
|
applyStyles(request.styles);
|
||||||
styleInjector.sort();
|
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case 'urlChanged':
|
case 'styleReplaceAll':
|
||||||
if (!hasStyles && isDisabled || matchUrl === request.url) break;
|
replaceAll(request.styles);
|
||||||
matchUrl = request.url;
|
|
||||||
API.styles.getSectionsByUrl(matchUrl).then(sections => {
|
|
||||||
hasStyles = true;
|
|
||||||
styleInjector.replace(sections);
|
|
||||||
});
|
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case 'updateCount':
|
case 'prefChanged':
|
||||||
updateCount();
|
if ('disableAll' in request.prefs) {
|
||||||
|
doDisableAll(request.prefs.disableAll);
|
||||||
|
}
|
||||||
|
if ('exposeIframes' in request.prefs) {
|
||||||
|
doExposeIframes(request.prefs.exposeIframes);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'ping':
|
||||||
|
sendResponse(true);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function updateDisableAll(key, disableAll) {
|
function doDisableAll(disable = disableAll) {
|
||||||
isDisabled = disableAll;
|
if (!disable === !disableAll) {
|
||||||
if (isUnstylable) {
|
return;
|
||||||
API.styleViaAPI({method: 'prefChanged', prefs: {disableAll}});
|
}
|
||||||
} else if (!hasStyles && !disableAll) {
|
disableAll = disable;
|
||||||
init();
|
Array.prototype.forEach.call(document.styleSheets, stylesheet => {
|
||||||
|
if (stylesheet.ownerNode.matches(`style.stylus[id^="${ID_PREFIX}"]`)
|
||||||
|
&& stylesheet.disabled !== disable) {
|
||||||
|
stylesheet.disabled = disable;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function doExposeIframes(state = exposeIframes) {
|
||||||
|
if (state === exposeIframes ||
|
||||||
|
state === true && typeof exposeIframes === 'string' ||
|
||||||
|
window === parent) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
exposeIframes = state;
|
||||||
|
const attr = document.documentElement.getAttribute('stylus-iframe');
|
||||||
|
if (state && state !== attr) {
|
||||||
|
document.documentElement.setAttribute('stylus-iframe', state);
|
||||||
|
} else if (!state && attr !== undefined) {
|
||||||
|
document.documentElement.removeAttribute('stylus-iframe');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function applyStyleState({id, enabled}) {
|
||||||
|
const inCache = disabledElements.get(id) || styleElements.get(id);
|
||||||
|
const inDoc = document.getElementById(ID_PREFIX + id);
|
||||||
|
if (enabled) {
|
||||||
|
if (inDoc) {
|
||||||
|
return;
|
||||||
|
} else if (inCache) {
|
||||||
|
addStyleElement(inCache);
|
||||||
|
disabledElements.delete(id);
|
||||||
|
} else {
|
||||||
|
requestStyles({id});
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
styleInjector.toggle(!disableAll);
|
if (inDoc) {
|
||||||
}
|
disabledElements.set(id, inDoc);
|
||||||
}
|
docRootObserver.evade(() => inDoc.remove());
|
||||||
|
|
||||||
async function updateExposeIframes(key, value = prefs.get('exposeIframes')) {
|
|
||||||
const attr = 'stylus-iframe';
|
|
||||||
const el = document.documentElement;
|
|
||||||
if (!el) return; // got no styles so styleInjector didn't wait for <html>
|
|
||||||
if (!value || !styleInjector.list.length) {
|
|
||||||
el.removeAttribute(attr);
|
|
||||||
} else {
|
|
||||||
if (!parentDomain) parentDomain = await API.getTabUrlPrefix();
|
|
||||||
// Check first to avoid triggering DOM mutation
|
|
||||||
if (el.getAttribute(attr) !== parentDomain) {
|
|
||||||
el.setAttribute(attr, parentDomain);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function updateCount() {
|
function removeStyle({id, retire = false}) {
|
||||||
if (!isTab) return;
|
const el = document.getElementById(ID_PREFIX + id);
|
||||||
if (isFrame) {
|
if (el) {
|
||||||
if (!port && styleInjector.list.length) {
|
if (retire) {
|
||||||
port = chrome.runtime.connect({name: 'iframe'});
|
// to avoid page flicker when the style is updated
|
||||||
} else if (port && !styleInjector.list.length) {
|
// instead of removing it immediately we rename its ID and queue it
|
||||||
port.disconnect();
|
// to be deleted in applyStyles after a new version is fetched and applied
|
||||||
|
const deadID = id + '-ghost';
|
||||||
|
el.id = ID_PREFIX + deadID;
|
||||||
|
// in case something went wrong and new style was never applied
|
||||||
|
retiredStyleTimers.set(deadID, setTimeout(removeStyle, 1000, {id: deadID}));
|
||||||
|
} else {
|
||||||
|
docRootObserver.evade(() => el.remove());
|
||||||
}
|
}
|
||||||
if (lazyBadge && performance.now() > 1000) lazyBadge = false;
|
|
||||||
}
|
}
|
||||||
(isUnstylable ?
|
styleElements.delete(ID_PREFIX + id);
|
||||||
API.styleViaAPI({method: 'updateCount'}) :
|
disabledElements.delete(id);
|
||||||
API.updateIconBadge(styleInjector.list.map(style => style.id), {lazyBadge})
|
retiredStyleTimers.delete(id);
|
||||||
).catch(msg.ignoreError);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function onFrameElementInView(cb) {
|
function applyStyles(styles) {
|
||||||
parent[parent.Symbol.for('xo')](frameElement, cb);
|
if (!styles) {
|
||||||
}
|
// Chrome is starting up
|
||||||
|
requestStyles();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
/** @param {IntersectionObserverEntry[]} entries */
|
if (!document.documentElement) {
|
||||||
function onIntersect(entries) {
|
new MutationObserver((mutations, observer) => {
|
||||||
for (const e of entries) {
|
if (document.documentElement) {
|
||||||
if (e.isIntersecting) {
|
observer.disconnect();
|
||||||
xo.unobserve(e.target);
|
applyStyles(styles);
|
||||||
e.target.dispatchEvent(new Event(xoEventId));
|
}
|
||||||
|
}).observe(document, {childList: true});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ('disableAll' in styles) {
|
||||||
|
doDisableAll(styles.disableAll);
|
||||||
|
}
|
||||||
|
if ('exposeIframes' in styles) {
|
||||||
|
doExposeIframes(styles.exposeIframes);
|
||||||
|
}
|
||||||
|
|
||||||
|
const gotNewStyles = styles.length || styles.needTransitionPatch;
|
||||||
|
if (gotNewStyles) {
|
||||||
|
if (docRootObserver) {
|
||||||
|
docRootObserver.stop();
|
||||||
|
} else {
|
||||||
|
initDocRootObserver();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (styles.needTransitionPatch) {
|
||||||
|
applyTransitionPatch();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (gotNewStyles) {
|
||||||
|
for (const id in styles) {
|
||||||
|
const sections = styles[id];
|
||||||
|
if (!Array.isArray(sections)) continue;
|
||||||
|
applySections(id, sections.map(({code}) => code).join('\n'));
|
||||||
|
}
|
||||||
|
docRootObserver.firstStart();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (FF_BUG461 && (gotNewStyles || styles.needTransitionPatch)) {
|
||||||
|
setContentsInPageContext();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!isOwnPage && !docRewriteObserver && styleElements.size) {
|
||||||
|
initDocRewriteObserver();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (retiredStyleTimers.size) {
|
||||||
|
setTimeout(() => {
|
||||||
|
for (const [id, timer] of retiredStyleTimers.entries()) {
|
||||||
|
removeStyle({id});
|
||||||
|
clearTimeout(timer);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function tryCatch(func, ...args) {
|
function applySections(styleId, code) {
|
||||||
|
const id = ID_PREFIX + styleId;
|
||||||
|
let el = styleElements.get(id) || document.getElementById(id);
|
||||||
|
if (el && el.textContent !== code) {
|
||||||
|
if (CHROME < 3321) {
|
||||||
|
// workaround for Chrome devtools bug fixed in v65
|
||||||
|
el.remove();
|
||||||
|
el = null;
|
||||||
|
} else if (FF_BUG461) {
|
||||||
|
pageContextQueue.push({id: el.id, el, code});
|
||||||
|
} else {
|
||||||
|
el.textContent = code;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!el) {
|
||||||
|
if (document.documentElement instanceof SVGSVGElement) {
|
||||||
|
// SVG document style
|
||||||
|
el = document.createElementNS('http://www.w3.org/2000/svg', 'style');
|
||||||
|
} else if (document instanceof XMLDocument) {
|
||||||
|
// XML document style
|
||||||
|
el = document.createElementNS('http://www.w3.org/1999/xhtml', 'style');
|
||||||
|
} else {
|
||||||
|
// HTML document style; also works on HTML-embedded SVG
|
||||||
|
el = document.createElement('style');
|
||||||
|
}
|
||||||
|
el.id = id;
|
||||||
|
el.type = 'text/css';
|
||||||
|
// SVG className is not a string, but an instance of SVGAnimatedString
|
||||||
|
el.classList.add('stylus');
|
||||||
|
if (FF_BUG461) {
|
||||||
|
pageContextQueue.push({id: el.id, el, code});
|
||||||
|
} else {
|
||||||
|
el.textContent = code;
|
||||||
|
}
|
||||||
|
addStyleElement(el);
|
||||||
|
}
|
||||||
|
styleElements.set(id, el);
|
||||||
|
disabledElements.delete(Number(styleId));
|
||||||
|
return el;
|
||||||
|
}
|
||||||
|
|
||||||
|
function setContentsInPageContext() {
|
||||||
try {
|
try {
|
||||||
return func(...args);
|
(document.head || ROOT).appendChild(document.createElement('script')).text = `(${queue => {
|
||||||
|
document.currentScript.remove();
|
||||||
|
for (const {id, code} of queue) {
|
||||||
|
const el = document.getElementById(id) ||
|
||||||
|
document.querySelector('style.stylus[id="' + id + '"]');
|
||||||
|
if (!el) continue;
|
||||||
|
const {disabled} = el.sheet;
|
||||||
|
el.textContent = code;
|
||||||
|
el.sheet.disabled = disabled;
|
||||||
|
}
|
||||||
|
}})(${JSON.stringify(pageContextQueue)})`;
|
||||||
} catch (e) {}
|
} catch (e) {}
|
||||||
|
let failedSome;
|
||||||
|
for (const {el, code} of pageContextQueue) {
|
||||||
|
if (el.textContent !== code) {
|
||||||
|
el.textContent = code;
|
||||||
|
failedSome = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (failedSome) {
|
||||||
|
console.debug('Could not set code of some styles in page context, ' +
|
||||||
|
'see https://github.com/openstyles/stylus/issues/461');
|
||||||
|
}
|
||||||
|
pageContextQueue.length = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
function addStyleElement(newElement) {
|
||||||
|
if (!ROOT) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
let next;
|
||||||
|
const newStyleId = getStyleId(newElement);
|
||||||
|
for (const el of styleElements.values()) {
|
||||||
|
if (el.parentNode && !el.id.endsWith('-ghost') && getStyleId(el) > newStyleId) {
|
||||||
|
next = el.parentNode === ROOT ? el : null;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (next === newElement.nextElementSibling) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
docRootObserver.evade(() => {
|
||||||
|
ROOT.insertBefore(newElement, next || null);
|
||||||
|
if (disableAll) {
|
||||||
|
newElement.disabled = true;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function replaceAll(newStyles) {
|
||||||
|
if ('disableAll' in newStyles &&
|
||||||
|
disableAll === newStyles.disableAll &&
|
||||||
|
styleElements.size === countStylesInHash(newStyles) &&
|
||||||
|
[...styleElements.values()].every(el =>
|
||||||
|
el.disabled === disableAll &&
|
||||||
|
el.parentNode === ROOT &&
|
||||||
|
el.textContent === (newStyles[getStyleId(el)] || []).map(({code}) => code).join('\n'))) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const oldStyles = Array.prototype.slice.call(
|
||||||
|
document.querySelectorAll(`style.stylus[id^="${ID_PREFIX}"]`));
|
||||||
|
oldStyles.forEach(el => (el.id += '-ghost'));
|
||||||
|
styleElements.clear();
|
||||||
|
disabledElements.clear();
|
||||||
|
[...retiredStyleTimers.values()].forEach(clearTimeout);
|
||||||
|
retiredStyleTimers.clear();
|
||||||
|
applyStyles(newStyles);
|
||||||
|
docRootObserver.evade(() =>
|
||||||
|
oldStyles.forEach(el => el.remove()));
|
||||||
|
}
|
||||||
|
|
||||||
|
function applyTransitionPatch() {
|
||||||
|
// CSS transition bug workaround: since we insert styles asynchronously,
|
||||||
|
// the browsers, especially Firefox, may apply all transitions on page load
|
||||||
|
const className = chrome.runtime.id + '-transition-bug-fix';
|
||||||
|
const docId = document.documentElement.id ? '#' + document.documentElement.id : '';
|
||||||
|
document.documentElement.classList.add(className);
|
||||||
|
applySections(0, `
|
||||||
|
${docId}.${className}:root * {
|
||||||
|
transition: none !important;
|
||||||
|
}
|
||||||
|
`);
|
||||||
|
setTimeout(() => {
|
||||||
|
removeStyle({id: 0});
|
||||||
|
document.documentElement.classList.remove(className);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function getStyleId(el) {
|
||||||
|
return parseInt(el.id.substr(ID_PREFIX.length));
|
||||||
|
}
|
||||||
|
|
||||||
|
function countStylesInHash(styleHash) {
|
||||||
|
let num = 0;
|
||||||
|
for (const k in styleHash) {
|
||||||
|
num += !isNaN(parseInt(k)) ? 1 : 0;
|
||||||
|
}
|
||||||
|
return num;
|
||||||
}
|
}
|
||||||
|
|
||||||
function orphanCheck() {
|
function orphanCheck() {
|
||||||
if (chrome.runtime.id) return;
|
if (chrome.i18n && chrome.i18n.getUILanguage()) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
// In Chrome content script is orphaned on an extension update/reload
|
// In Chrome content script is orphaned on an extension update/reload
|
||||||
// so we need to detach event listeners
|
// so we need to detach event listeners
|
||||||
window.removeEventListener(orphanEventId, orphanCheck, true);
|
[docRewriteObserver, docRootObserver].forEach(ob => ob && ob.disconnect());
|
||||||
mqDark.onchange = null;
|
window.removeEventListener(chrome.runtime.id, orphanCheck, true);
|
||||||
isOrphaned = true;
|
try {
|
||||||
setTimeout(styleInjector.clear, 1000); // avoiding FOUC
|
chrome.runtime.onMessage.removeListener(applyOnMessage);
|
||||||
tryCatch(msg.off, applyOnMessage);
|
} catch (e) {}
|
||||||
|
}
|
||||||
|
|
||||||
|
function initDocRewriteObserver() {
|
||||||
|
// detect documentElement being rewritten from inside the script
|
||||||
|
docRewriteObserver = new MutationObserver(mutations => {
|
||||||
|
for (let m = mutations.length; --m >= 0;) {
|
||||||
|
const added = mutations[m].addedNodes;
|
||||||
|
for (let n = added.length; --n >= 0;) {
|
||||||
|
if (added[n].localName === 'html') {
|
||||||
|
reinjectStyles();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
docRewriteObserver.observe(document, {childList: true});
|
||||||
|
// detect dynamic iframes rewritten after creation by the embedder i.e. externally
|
||||||
|
setTimeout(() => {
|
||||||
|
if (document.documentElement !== ROOT) {
|
||||||
|
reinjectStyles();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
// re-add styles if we detect documentElement being recreated
|
||||||
|
function reinjectStyles() {
|
||||||
|
if (!styleElements) {
|
||||||
|
orphanCheck();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
ROOT = document.documentElement;
|
||||||
|
docRootObserver.stop();
|
||||||
|
const imported = [];
|
||||||
|
for (const [id, el] of styleElements.entries()) {
|
||||||
|
const copy = document.importNode(el, true);
|
||||||
|
el.textContent += ' '; // invalidate CSSOM cache
|
||||||
|
imported.push([id, copy]);
|
||||||
|
addStyleElement(copy);
|
||||||
|
}
|
||||||
|
docRootObserver.start();
|
||||||
|
styleElements = new Map(imported);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function initDocRootObserver() {
|
||||||
|
let lastRestorationTime = 0;
|
||||||
|
let restorationCounter = 0;
|
||||||
|
let observing = false;
|
||||||
|
let sorting = false;
|
||||||
|
let observer;
|
||||||
|
// allow any types of elements between ours, except for the following:
|
||||||
|
const ORDERED_TAGS = ['head', 'body', 'frameset', 'style', 'link'];
|
||||||
|
|
||||||
|
init();
|
||||||
|
return;
|
||||||
|
|
||||||
|
function init() {
|
||||||
|
observer = new MutationObserver(sortStyleElements);
|
||||||
|
docRootObserver = {firstStart, start, stop, evade, disconnect: stop};
|
||||||
|
setTimeout(sortStyleElements);
|
||||||
|
}
|
||||||
|
function firstStart() {
|
||||||
|
if (sortStyleMap()) {
|
||||||
|
sortStyleElements();
|
||||||
|
}
|
||||||
|
start();
|
||||||
|
}
|
||||||
|
function start() {
|
||||||
|
if (!observing && ROOT && observer) {
|
||||||
|
observer.observe(ROOT, {childList: true});
|
||||||
|
observing = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
function stop() {
|
||||||
|
if (observing) {
|
||||||
|
observer.takeRecords();
|
||||||
|
observer.disconnect();
|
||||||
|
observing = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
function evade(fn) {
|
||||||
|
const wasObserving = observing;
|
||||||
|
if (observing) {
|
||||||
|
stop();
|
||||||
|
}
|
||||||
|
fn();
|
||||||
|
if (wasObserving) {
|
||||||
|
start();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
function sortStyleMap() {
|
||||||
|
const list = [];
|
||||||
|
let prevStyleId = 0;
|
||||||
|
let needsSorting = false;
|
||||||
|
for (const entry of styleElements.entries()) {
|
||||||
|
list.push(entry);
|
||||||
|
const el = entry[1];
|
||||||
|
const styleId = getStyleId(el);
|
||||||
|
el.styleId = styleId;
|
||||||
|
needsSorting |= styleId < prevStyleId;
|
||||||
|
prevStyleId = styleId;
|
||||||
|
}
|
||||||
|
if (needsSorting) {
|
||||||
|
styleElements = new Map(list.sort((a, b) => a[1].styleId - b[1].styleId));
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
function sortStyleElements() {
|
||||||
|
if (!observing) return;
|
||||||
|
let prevExpected = document.documentElement.lastElementChild;
|
||||||
|
while (prevExpected && isSkippable(prevExpected, true)) {
|
||||||
|
prevExpected = prevExpected.previousElementSibling;
|
||||||
|
}
|
||||||
|
if (!prevExpected) return;
|
||||||
|
for (const el of styleElements.values()) {
|
||||||
|
if (!isMovable(el)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
while (true) {
|
||||||
|
const next = prevExpected.nextElementSibling;
|
||||||
|
if (next && isSkippable(next)) {
|
||||||
|
prevExpected = next;
|
||||||
|
} else if (
|
||||||
|
next === el ||
|
||||||
|
next === el.previousElementSibling && next ||
|
||||||
|
moveAfter(el, next || prevExpected)) {
|
||||||
|
prevExpected = el;
|
||||||
|
break;
|
||||||
|
} else {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (sorting) {
|
||||||
|
sorting = false;
|
||||||
|
if (observer) observer.takeRecords();
|
||||||
|
if (!restorationLimitExceeded()) {
|
||||||
|
start();
|
||||||
|
} else {
|
||||||
|
setTimeout(start, 1000);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
function isMovable(el) {
|
||||||
|
return el.parentNode || !disabledElements.has(getStyleId(el));
|
||||||
|
}
|
||||||
|
function isSkippable(el, skipOwnStyles) {
|
||||||
|
return !ORDERED_TAGS.includes(el.localName) ||
|
||||||
|
el.id.startsWith(ID_PREFIX) &&
|
||||||
|
(skipOwnStyles || el.id.endsWith('-ghost')) &&
|
||||||
|
el.localName === 'style' &&
|
||||||
|
el.className === 'stylus';
|
||||||
|
}
|
||||||
|
function moveAfter(el, expected) {
|
||||||
|
if (!sorting) {
|
||||||
|
sorting = true;
|
||||||
|
stop();
|
||||||
|
}
|
||||||
|
expected.insertAdjacentElement('afterend', el);
|
||||||
|
if (el.disabled !== disableAll) {
|
||||||
|
// moving an element resets its 'disabled' state
|
||||||
|
el.disabled = disableAll;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
function restorationLimitExceeded() {
|
||||||
|
const t = performance.now();
|
||||||
|
if (t - lastRestorationTime > 1000) {
|
||||||
|
restorationCounter = 0;
|
||||||
|
}
|
||||||
|
lastRestorationTime = t;
|
||||||
|
return ++restorationCounter > 5;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
})();
|
})();
|
||||||
|
|
|
@ -1,21 +0,0 @@
|
||||||
/* global API */// msg.js
|
|
||||||
'use strict';
|
|
||||||
|
|
||||||
// onCommitted may fire twice
|
|
||||||
// Note, we're checking against a literal `1`, not just `if (truthy)`,
|
|
||||||
// because <html id="INJECTED"> is exposed per HTML spec as a global variable and `window.INJECTED`.
|
|
||||||
|
|
||||||
if (window.INJECTED_GREASYFORK !== 1) {
|
|
||||||
window.INJECTED_GREASYFORK = 1;
|
|
||||||
addEventListener('message', async function onMessage(e) {
|
|
||||||
if (e.origin === location.origin &&
|
|
||||||
e.data &&
|
|
||||||
e.data.name &&
|
|
||||||
e.data.type === 'style-version-query') {
|
|
||||||
removeEventListener('message', onMessage);
|
|
||||||
const style = await API.usercss.find(e.data) || {};
|
|
||||||
const {version} = style.usercssData || {};
|
|
||||||
postMessage({type: 'style-version', version}, '*');
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
176
content/install-hook-openusercss.js
Normal file
176
content/install-hook-openusercss.js
Normal file
|
@ -0,0 +1,176 @@
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
(() => {
|
||||||
|
const manifest = chrome.runtime.getManifest();
|
||||||
|
const allowedOrigins = [
|
||||||
|
'https://openusercss.org',
|
||||||
|
'https://openusercss.com'
|
||||||
|
];
|
||||||
|
|
||||||
|
const sendPostMessage = message => {
|
||||||
|
if (allowedOrigins.includes(location.origin)) {
|
||||||
|
window.postMessage(message, location.origin);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const askHandshake = () => {
|
||||||
|
// Tell the page that we exist and that it should send the handshake
|
||||||
|
sendPostMessage({
|
||||||
|
type: 'ouc-begin-handshake'
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
// Listen for queries by the site and respond with a callback object
|
||||||
|
const sendInstalledCallback = styleData => {
|
||||||
|
sendPostMessage({
|
||||||
|
type: 'ouc-is-installed-response',
|
||||||
|
style: styleData
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const installedHandler = event => {
|
||||||
|
if (event.data
|
||||||
|
&& event.data.type === 'ouc-is-installed'
|
||||||
|
&& allowedOrigins.includes(event.origin)
|
||||||
|
) {
|
||||||
|
chrome.runtime.sendMessage({
|
||||||
|
method: 'findUsercss',
|
||||||
|
name: event.data.name,
|
||||||
|
namespace: event.data.namespace
|
||||||
|
}, style => {
|
||||||
|
const data = {event};
|
||||||
|
const callbackObject = {
|
||||||
|
installed: Boolean(style),
|
||||||
|
enabled: style.enabled,
|
||||||
|
name: data.name,
|
||||||
|
namespace: data.namespace
|
||||||
|
};
|
||||||
|
|
||||||
|
sendInstalledCallback(callbackObject);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const attachInstalledListeners = () => {
|
||||||
|
window.addEventListener('message', installedHandler);
|
||||||
|
};
|
||||||
|
|
||||||
|
const doHandshake = () => {
|
||||||
|
// This is a representation of features that Stylus is capable of
|
||||||
|
const implementedFeatures = [
|
||||||
|
'install-usercss',
|
||||||
|
'event:install-usercss',
|
||||||
|
'event:is-installed',
|
||||||
|
'configure-after-install',
|
||||||
|
'builtin-editor',
|
||||||
|
'create-usercss',
|
||||||
|
'edit-usercss',
|
||||||
|
'import-moz-export',
|
||||||
|
'export-moz-export',
|
||||||
|
'update-manual',
|
||||||
|
'update-auto',
|
||||||
|
'export-json-backups',
|
||||||
|
'import-json-backups',
|
||||||
|
'manage-local'
|
||||||
|
];
|
||||||
|
const reportedFeatures = [];
|
||||||
|
|
||||||
|
// The handshake question includes a list of required and optional features
|
||||||
|
// we match them with features we have implemented, and build a union array.
|
||||||
|
event.data.featuresList.required.forEach(feature => {
|
||||||
|
if (implementedFeatures.includes(feature)) {
|
||||||
|
reportedFeatures.push(feature);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
event.data.featuresList.optional.forEach(feature => {
|
||||||
|
if (implementedFeatures.includes(feature)) {
|
||||||
|
reportedFeatures.push(feature);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// We send the handshake response, which includes the key we got, plus some
|
||||||
|
// additional metadata
|
||||||
|
sendPostMessage({
|
||||||
|
type: 'ouc-handshake-response',
|
||||||
|
key: event.data.key,
|
||||||
|
extension: {
|
||||||
|
name: manifest.name,
|
||||||
|
capabilities: reportedFeatures
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const handshakeHandler = event => {
|
||||||
|
if (event.data
|
||||||
|
&& event.data.type === 'ouc-handshake-question'
|
||||||
|
&& allowedOrigins.includes(event.origin)
|
||||||
|
) {
|
||||||
|
doHandshake();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const attachHandshakeListeners = () => {
|
||||||
|
// Wait for the handshake request, then start it
|
||||||
|
window.addEventListener('message', handshakeHandler);
|
||||||
|
};
|
||||||
|
|
||||||
|
const sendInstallCallback = data => {
|
||||||
|
// Send an install callback to the site in order to let it know
|
||||||
|
// we were able to install the theme and it may display a success message
|
||||||
|
sendPostMessage({
|
||||||
|
type: 'ouc-install-callback',
|
||||||
|
key: data.key
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const installHandler = event => {
|
||||||
|
if (event.data
|
||||||
|
&& event.data.type === 'ouc-install-usercss'
|
||||||
|
&& allowedOrigins.includes(event.origin)
|
||||||
|
) {
|
||||||
|
chrome.runtime.sendMessage({
|
||||||
|
method: 'saveUsercss',
|
||||||
|
reason: 'install',
|
||||||
|
name: event.data.title,
|
||||||
|
sourceCode: event.data.code,
|
||||||
|
}, style => {
|
||||||
|
sendInstallCallback({
|
||||||
|
enabled: style.enabled,
|
||||||
|
key: event.data.key
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const attachInstallListeners = () => {
|
||||||
|
// Wait for an install event, then save the theme
|
||||||
|
window.addEventListener('message', installHandler);
|
||||||
|
};
|
||||||
|
|
||||||
|
const orphanCheck = () => {
|
||||||
|
const eventName = chrome.runtime.id + '-install-hook-openusercss';
|
||||||
|
const orphanCheckRequest = () => {
|
||||||
|
// If we can't get the UI language, it means we are orphaned, and should
|
||||||
|
// remove our event handlers
|
||||||
|
if (chrome.i18n && chrome.i18n.getUILanguage()) return true;
|
||||||
|
|
||||||
|
window.removeEventListener('message', installHandler);
|
||||||
|
window.removeEventListener('message', handshakeHandler);
|
||||||
|
window.removeEventListener('message', installedHandler);
|
||||||
|
window.removeEventListener(eventName, orphanCheckRequest, true);
|
||||||
|
};
|
||||||
|
|
||||||
|
// Send the event before we listen for it, for other possible
|
||||||
|
// running instances of the content script.
|
||||||
|
dispatchEvent(new Event(eventName));
|
||||||
|
addEventListener(eventName, orphanCheckRequest, true);
|
||||||
|
};
|
||||||
|
|
||||||
|
orphanCheck();
|
||||||
|
|
||||||
|
attachHandshakeListeners();
|
||||||
|
attachInstallListeners();
|
||||||
|
attachInstalledListeners();
|
||||||
|
askHandshake();
|
||||||
|
})();
|
|
@ -1,26 +1,105 @@
|
||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
// preventing reregistration if reinjected by tabs.executeScript for whatever reason, just in case
|
(() => {
|
||||||
if (typeof window.oldCode !== 'string') {
|
// some weird bug in new Chrome: the content script gets injected multiple times
|
||||||
window.oldCode = (document.querySelector('body > pre') || document.body).textContent;
|
if (typeof window.initUsercssInstall === 'function') return;
|
||||||
chrome.runtime.onConnect.addListener(port => {
|
if (!/text\/(css|plain)/.test(document.contentType) ||
|
||||||
if (port.name !== 'downloadSelf') return;
|
!/==userstyle==/i.test(document.body.textContent)) {
|
||||||
port.onMessage.addListener(async ({id, force}) => {
|
return;
|
||||||
const msg = {id};
|
}
|
||||||
try {
|
window.initUsercssInstall = () => {};
|
||||||
const code = await (await fetch(location.href, {mode: 'same-origin'})).text();
|
|
||||||
if (code !== window.oldCode || force) {
|
|
||||||
msg.code = window.oldCode = code;
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
msg.error = error.message || `${error}`;
|
|
||||||
}
|
|
||||||
port.postMessage(msg);
|
|
||||||
});
|
|
||||||
// FF keeps content scripts connected on navigation https://github.com/openstyles/stylus/issues/864
|
|
||||||
addEventListener('pagehide', () => port.disconnect(), {once: true});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// passing the result to tabs.executeScript
|
orphanCheck();
|
||||||
window.oldCode; // eslint-disable-line no-unused-expressions
|
|
||||||
|
const DELAY = 500;
|
||||||
|
const url = location.href;
|
||||||
|
let sourceCode, port, timer;
|
||||||
|
|
||||||
|
chrome.runtime.onConnect.addListener(onConnected);
|
||||||
|
chrome.runtime.sendMessage({method: 'installUsercss', url}, r =>
|
||||||
|
r && r.__ERROR__ && alert(r.__ERROR__));
|
||||||
|
|
||||||
|
function onConnected(newPort) {
|
||||||
|
port = newPort;
|
||||||
|
port.onDisconnect.addListener(stop);
|
||||||
|
port.onMessage.addListener(onMessage);
|
||||||
|
}
|
||||||
|
|
||||||
|
function onMessage(msg, port) {
|
||||||
|
switch (msg.method) {
|
||||||
|
case 'getSourceCode':
|
||||||
|
fetchText(url)
|
||||||
|
.then(text => {
|
||||||
|
sourceCode = sourceCode || text;
|
||||||
|
port.postMessage({
|
||||||
|
method: msg.method + 'Response',
|
||||||
|
sourceCode,
|
||||||
|
});
|
||||||
|
})
|
||||||
|
.catch(err => port.postMessage({
|
||||||
|
method: msg.method + 'Response',
|
||||||
|
error: err.message || String(err),
|
||||||
|
}));
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'liveReloadStart':
|
||||||
|
start();
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'liveReloadStop':
|
||||||
|
stop();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function fetchText(url) {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
// you can't use fetch in Chrome under 'file:' protocol
|
||||||
|
const xhr = new XMLHttpRequest();
|
||||||
|
xhr.open('GET', url);
|
||||||
|
xhr.addEventListener('load', () => resolve(xhr.responseText));
|
||||||
|
xhr.addEventListener('error', () => reject(xhr));
|
||||||
|
xhr.send();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function start() {
|
||||||
|
timer = timer || setTimeout(check, DELAY);
|
||||||
|
}
|
||||||
|
|
||||||
|
function stop() {
|
||||||
|
clearTimeout(timer);
|
||||||
|
timer = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
function check() {
|
||||||
|
fetchText(url)
|
||||||
|
.then(text => {
|
||||||
|
if (sourceCode === text) return;
|
||||||
|
sourceCode = text;
|
||||||
|
port.postMessage({method: 'sourceCodeChanged', sourceCode});
|
||||||
|
})
|
||||||
|
.catch(error => {
|
||||||
|
console.log(chrome.i18n.getMessage('liveReloadError', error));
|
||||||
|
})
|
||||||
|
.then(() => {
|
||||||
|
timer = null;
|
||||||
|
start();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function orphanCheck() {
|
||||||
|
const eventName = chrome.runtime.id + '-install-hook-usercss';
|
||||||
|
const orphanCheckRequest = () => {
|
||||||
|
if (chrome.i18n && chrome.i18n.getUILanguage()) return true;
|
||||||
|
// In Chrome content script is orphaned on an extension update/reload
|
||||||
|
// so we need to detach event listeners
|
||||||
|
removeEventListener(eventName, orphanCheckRequest, true);
|
||||||
|
try {
|
||||||
|
chrome.runtime.onConnect.removeListener(onConnected);
|
||||||
|
} catch (e) {}
|
||||||
|
};
|
||||||
|
dispatchEvent(new Event(eventName));
|
||||||
|
addEventListener(eventName, orphanCheckRequest, true);
|
||||||
|
}
|
||||||
|
})();
|
||||||
|
|
|
@ -1,324 +1,487 @@
|
||||||
/* global API */// msg.js
|
/* global cloneInto */
|
||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
// eslint-disable-next-line no-unused-expressions
|
(() => {
|
||||||
/^\/styles\/(\d+)(\/([^/]*))?([?#].*)?$/.test(location.pathname) && (async () => {
|
window.dispatchEvent(new CustomEvent(chrome.runtime.id + '-install'));
|
||||||
if (window.INJECTED_USO === 1) return;
|
window.addEventListener(chrome.runtime.id + '-install', orphanCheck, true);
|
||||||
window.INJECTED_USO = 1;
|
|
||||||
|
|
||||||
const usoId = RegExp.$1;
|
document.addEventListener('stylishInstallChrome', onClick);
|
||||||
const USO = 'https://userstyles.org';
|
document.addEventListener('stylishUpdateChrome', onClick);
|
||||||
const apiUrl = `${USO}/api/v1/styles/${usoId}`;
|
|
||||||
const md5Url = `https://update.userstyles.org/${usoId}.md5`;
|
|
||||||
const CLICK = [
|
|
||||||
['#install_stylish_style_button', onInstall],
|
|
||||||
['#update_stylish_style_button', onInstall],
|
|
||||||
['.customize_style_button', onCustomize],
|
|
||||||
['.uninstall_stylish_style_button', onUninstall],
|
|
||||||
];
|
|
||||||
const pageEventId = `${performance.now()}${Math.random()}`;
|
|
||||||
const contentEventId = pageEventId + ':';
|
|
||||||
const orphanEventId = chrome.runtime.id; // id won't be available in the orphaned script
|
|
||||||
const $ = (sel, base = document) => base.querySelector(sel);
|
|
||||||
const toggleListener = (isOn, ...args) => (isOn ? addEventListener : removeEventListener)(...args);
|
|
||||||
const togglePageListener = isOn => toggleListener(isOn, contentEventId, onPageEvent, true);
|
|
||||||
|
|
||||||
const mo = new MutationObserver(onMutation);
|
chrome.runtime.onMessage.addListener(onMessage);
|
||||||
const observeColors = isOn =>
|
|
||||||
isOn ? mo.observe(document.body, {subtree: true, attributes: true, attributeFilter: ['value']})
|
|
||||||
: mo.disconnect();
|
|
||||||
|
|
||||||
let style, dup, md5, pageData, badKeys;
|
onDOMready().then(() => {
|
||||||
|
window.postMessage({
|
||||||
|
direction: 'from-content-script',
|
||||||
|
message: 'StylishInstalled',
|
||||||
|
}, '*');
|
||||||
|
});
|
||||||
|
|
||||||
runInPage(inPageContext, pageEventId, contentEventId, usoId, apiUrl);
|
let gotBody = false;
|
||||||
addEventListener(orphanEventId, orphanCheck, true);
|
new MutationObserver(observeDOM).observe(document.documentElement, {
|
||||||
addEventListener('click', onClick, true);
|
childList: true,
|
||||||
togglePageListener(true);
|
subtree: true,
|
||||||
|
});
|
||||||
|
observeDOM();
|
||||||
|
|
||||||
[md5, dup] = await Promise.all([
|
function observeDOM() {
|
||||||
fetch(md5Url).then(r => r.text()),
|
if (!gotBody) {
|
||||||
API.styles.find({md5Url}, {installationUrl: `https://uso.kkx.one/style/${usoId}`})
|
if (!document.body) return;
|
||||||
.then(sendVarsToPage),
|
gotBody = true;
|
||||||
document.body || new Promise(resolve => addEventListener('load', resolve, {once: true})),
|
// TODO: remove the following statement when USO pagination title is fixed
|
||||||
]);
|
document.title = document.title.replace(/^(\d+)&\w+=/, '#$1: ');
|
||||||
|
chrome.runtime.sendMessage({
|
||||||
if (!dup) {
|
method: 'getStyles',
|
||||||
sendStylishEvent('styleCanBeInstalledChrome');
|
md5Url: getMeta('stylish-md5-url') || location.href
|
||||||
} else if (dup.originalMd5 && dup.originalMd5 !== md5 || !dup.usercssData || !dup.md5Url) {
|
}, checkUpdatability);
|
||||||
// allow update if 1) changed, 2) is a classic USO style, 3) is from USO-archive
|
}
|
||||||
sendStylishEvent('styleCanBeUpdatedChrome');
|
if (document.getElementById('install_button')) {
|
||||||
} else {
|
onDOMready().then(() => {
|
||||||
sendStylishEvent('styleAlreadyInstalledChrome');
|
requestAnimationFrame(() => {
|
||||||
}
|
sendEvent(sendEvent.lastEvent);
|
||||||
|
});
|
||||||
async function onClick(e) {
|
});
|
||||||
for (const [sel, fn] of CLICK) {
|
|
||||||
const el = e.target.closest(sel);
|
|
||||||
if (!el) continue;
|
|
||||||
try {
|
|
||||||
el.disabled = true;
|
|
||||||
await fn(e);
|
|
||||||
} catch (e) {
|
|
||||||
alert(chrome.i18n.getMessage('styleInstallFailed', e.message || e));
|
|
||||||
} finally {
|
|
||||||
el.disabled = false;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function onCustomize() {
|
function onMessage(msg, sender, sendResponse) {
|
||||||
const ss = $('#style-settings');
|
switch (msg.method) {
|
||||||
const willShow = !ss || !ss.offsetHeight;
|
case 'ping':
|
||||||
observeColors(willShow);
|
// orphaned content script check
|
||||||
toggleListener(willShow, 'change', onChange);
|
sendResponse(true);
|
||||||
}
|
break;
|
||||||
|
case 'openSettings':
|
||||||
async function onInstall(e) {
|
openSettings();
|
||||||
const {id} = dup;
|
sendResponse(true);
|
||||||
e.stopPropagation();
|
break;
|
||||||
if (!style) await buildStyle();
|
|
||||||
style = dup = await API.usercss.install(style, {
|
|
||||||
dup: {id},
|
|
||||||
vars: getPageVars(),
|
|
||||||
});
|
|
||||||
sendStylishEvent('styleInstalledChrome');
|
|
||||||
API.uso.pingback(id);
|
|
||||||
}
|
|
||||||
|
|
||||||
function onUninstall() {
|
|
||||||
const {id} = dup;
|
|
||||||
dup = style = false;
|
|
||||||
observeColors(false);
|
|
||||||
removeEventListener('change', onChange);
|
|
||||||
return API.styles.delete(id);
|
|
||||||
}
|
|
||||||
|
|
||||||
function onChange({target: el}) {
|
|
||||||
if (dup && el.matches('[name^="ik-"], [type=file]')) {
|
|
||||||
API.usercss.configVars(dup.id, getPageVars());
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function onMutation(mutations) {
|
/* since we are using "stylish-code-chrome" meta key on all browsers and
|
||||||
for (const {target: el} of mutations) {
|
US.o does not provide "advanced settings" on this url if browser is not Chrome,
|
||||||
if (el.style.display === 'none' &&
|
we need to fix this URL using "stylish-update-url" meta key
|
||||||
/^ik-/.test(el.name) &&
|
*/
|
||||||
/^#[\da-f]{6}$/.test(el.value)) {
|
function getStyleURL() {
|
||||||
onChange({target: el});
|
const textUrl = getMeta('stylish-update-url') || '';
|
||||||
}
|
const jsonUrl = getMeta('stylish-code-chrome') ||
|
||||||
|
textUrl.replace(/styles\/(\d+)\/[^?]*/, 'styles/chrome/$1.json');
|
||||||
|
const paramsMissing = !jsonUrl.includes('?') && textUrl.includes('?');
|
||||||
|
return jsonUrl + (paramsMissing ? textUrl.replace(/^[^?]+/, '') : '');
|
||||||
|
}
|
||||||
|
|
||||||
|
function checkUpdatability([installedStyle]) {
|
||||||
|
// TODO: remove the following statement when USO is fixed
|
||||||
|
document.dispatchEvent(new CustomEvent('stylusFixBuggyUSOsettings', {
|
||||||
|
detail: installedStyle && installedStyle.updateUrl,
|
||||||
|
}));
|
||||||
|
if (!installedStyle) {
|
||||||
|
sendEvent({type: 'styleCanBeInstalledChrome'});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const isCustomizable = /\?/.test(installedStyle.updateUrl);
|
||||||
|
const md5Url = getMeta('stylish-md5-url');
|
||||||
|
if (md5Url && installedStyle.md5Url && installedStyle.originalMd5) {
|
||||||
|
getResource(md5Url).then(md5 => {
|
||||||
|
reportUpdatable(
|
||||||
|
isCustomizable ||
|
||||||
|
md5 !== installedStyle.originalMd5);
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
getStyleJson().then(json => {
|
||||||
|
reportUpdatable(
|
||||||
|
isCustomizable ||
|
||||||
|
!json ||
|
||||||
|
!styleSectionsEqual(json, installedStyle));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function reportUpdatable(isUpdatable) {
|
||||||
|
sendEvent({
|
||||||
|
type: isUpdatable
|
||||||
|
? 'styleCanBeUpdatedChrome'
|
||||||
|
: 'styleAlreadyInstalledChrome',
|
||||||
|
detail: {
|
||||||
|
updateUrl: installedStyle.updateUrl
|
||||||
|
},
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function onPageEvent(e) {
|
|
||||||
pageData = e.detail;
|
|
||||||
togglePageListener(false);
|
|
||||||
}
|
|
||||||
|
|
||||||
async function buildStyle() {
|
function sendEvent(event) {
|
||||||
if (!pageData) pageData = await (await fetch(apiUrl)).json();
|
sendEvent.lastEvent = event;
|
||||||
({style, badKeys} = await API.uso.toUsercss(pageData, {varsUrl: dup.updateUrl}));
|
let {type, detail = null} = event;
|
||||||
Object.assign(style, {
|
if (typeof cloneInto !== 'undefined') {
|
||||||
md5Url,
|
// Firefox requires explicit cloning, however USO can't process our messages anyway
|
||||||
id: dup.id,
|
// because USO tries to use a global "event" variable deprecated in Firefox
|
||||||
originalMd5: md5,
|
detail = cloneInto({detail}, document);
|
||||||
updateUrl: apiUrl,
|
} else {
|
||||||
|
detail = {detail};
|
||||||
|
}
|
||||||
|
onDOMready().then(() => {
|
||||||
|
document.dispatchEvent(new CustomEvent(type, detail));
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function getPageVars() {
|
|
||||||
const {vars} = (style || dup).usercssData;
|
function onClick(event) {
|
||||||
for (const el of document.querySelectorAll('[name^="ik-"]')) {
|
if (onClick.processing || !orphanCheck()) {
|
||||||
const name = el.name.slice(3); // dropping "ik-"
|
return;
|
||||||
const ik = (badKeys || {})[name] || name;
|
}
|
||||||
const v = vars[ik] || false;
|
onClick.processing = true;
|
||||||
const isImage = el.type === 'radio';
|
(event.type.includes('Update') ? onUpdate() : onInstall())
|
||||||
if (v && (!isImage || el.checked)) {
|
.then(done, done);
|
||||||
const val = el.value;
|
function done() {
|
||||||
const isFile = val === 'user-upload';
|
setTimeout(() => {
|
||||||
if (isImage && (isFile || val === 'user-url')) {
|
onClick.processing = false;
|
||||||
const el2 = $(`[type=${isFile ? 'file' : 'url'}]`, el.parentElement);
|
});
|
||||||
const ikCust = `${ik}-custom`;
|
}
|
||||||
v.value = `${ikCust}-dropdown`;
|
}
|
||||||
vars[ikCust].value = isFile ? getFileUriFromPage(el2) : el2.value;
|
|
||||||
} else {
|
|
||||||
v.value = v.type === 'select' ? val.replace(/^ik-/, '') : val;
|
function onInstall() {
|
||||||
|
return getResource(getMeta('stylish-description'))
|
||||||
|
.then(name => saveStyleCode('styleInstall', name))
|
||||||
|
.then(() => getResource(getMeta('stylish-install-ping-url-chrome')));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
function onUpdate() {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
chrome.runtime.sendMessage({
|
||||||
|
method: 'getStyles',
|
||||||
|
md5Url: getMeta('stylish-md5-url') || location.href,
|
||||||
|
}, ([style]) => {
|
||||||
|
saveStyleCode('styleUpdate', style.name, {id: style.id})
|
||||||
|
.then(resolve, reject);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
function saveStyleCode(message, name, addProps) {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
const isNew = message === 'styleInstall';
|
||||||
|
const needsConfirmation = isNew || !saveStyleCode.confirmed;
|
||||||
|
if (needsConfirmation && !confirm(chrome.i18n.getMessage(message, [name]))) {
|
||||||
|
reject();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
saveStyleCode.confirmed = true;
|
||||||
|
enableUpdateButton(false);
|
||||||
|
getStyleJson().then(json => {
|
||||||
|
if (!json) {
|
||||||
|
prompt(chrome.i18n.getMessage('styleInstallFailed', ''),
|
||||||
|
'https://github.com/openstyles/stylus/issues/195');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
chrome.runtime.sendMessage(
|
||||||
|
Object.assign(json, addProps, {
|
||||||
|
method: 'saveStyle',
|
||||||
|
reason: isNew ? 'install' : 'update',
|
||||||
|
}),
|
||||||
|
style => {
|
||||||
|
if (!isNew && style.updateUrl.includes('?')) {
|
||||||
|
enableUpdateButton(true);
|
||||||
|
} else {
|
||||||
|
sendEvent({type: 'styleInstalledChrome'});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
resolve();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
function enableUpdateButton(state) {
|
||||||
|
const important = s => s.replace(/;/g, '!important;');
|
||||||
|
const button = document.getElementById('update_style_button');
|
||||||
|
if (button) {
|
||||||
|
button.style.cssText = state ? '' : important('pointer-events: none; opacity: .35;');
|
||||||
|
const icon = button.querySelector('img[src*=".svg"]');
|
||||||
|
if (icon) {
|
||||||
|
icon.style.cssText = state ? '' : important('transition: transform 5s; transform: rotate(0);');
|
||||||
|
if (state) {
|
||||||
|
setTimeout(() => (icon.style.cssText += important('transform: rotate(10turn);')));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return vars;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function getFileUriFromPage(el) {
|
|
||||||
togglePageListener(true);
|
function getMeta(name) {
|
||||||
sendPageEvent(el);
|
const e = document.querySelector(`link[rel="${name}"]`);
|
||||||
return pageData;
|
return e ? e.getAttribute('href') : null;
|
||||||
}
|
}
|
||||||
|
|
||||||
function runInPage(fn, ...args) {
|
|
||||||
const div = document.createElement('div');
|
function getResource(url, options) {
|
||||||
div.attachShadow({mode: 'closed'})
|
return new Promise(resolve => {
|
||||||
.appendChild(document.createElement('script'))
|
if (url.startsWith('#')) {
|
||||||
.textContent = `(${fn})(${JSON.stringify(args).slice(1, -1)})`;
|
resolve(document.getElementById(url.slice(1)).textContent);
|
||||||
document.documentElement.appendChild(div).remove();
|
} else {
|
||||||
|
chrome.runtime.sendMessage(Object.assign({
|
||||||
|
url,
|
||||||
|
method: 'download',
|
||||||
|
timeout: 60e3,
|
||||||
|
// USO can't handle POST requests for style json
|
||||||
|
body: null,
|
||||||
|
}, options), result => {
|
||||||
|
const error = result && result.__ERROR__;
|
||||||
|
if (error) {
|
||||||
|
alert('Error' + (error ? '\n' + error : ''));
|
||||||
|
} else {
|
||||||
|
resolve(result);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function sendPageEvent(data) {
|
|
||||||
dispatchEvent(data instanceof Node
|
function getStyleJson() {
|
||||||
? new MouseEvent(pageEventId, {relatedTarget: data})
|
return getResource(getStyleURL(), {responseType: 'json'})
|
||||||
: new CustomEvent(pageEventId, {detail: data}));
|
.then(style => {
|
||||||
//* global cloneInto */// WARNING! Firefox requires cloning of an object `detail`
|
if (!style || !Array.isArray(style.sections) || style.sections.length) {
|
||||||
|
return style;
|
||||||
|
}
|
||||||
|
const codeElement = document.getElementById('stylish-code');
|
||||||
|
if (codeElement && !codeElement.textContent.trim()) {
|
||||||
|
return style;
|
||||||
|
}
|
||||||
|
return getResource(getMeta('stylish-update-url')).then(code => new Promise(resolve => {
|
||||||
|
chrome.runtime.sendMessage({method: 'parseCss', code}, ({sections}) => {
|
||||||
|
style.sections = sections;
|
||||||
|
resolve(style);
|
||||||
|
});
|
||||||
|
}));
|
||||||
|
})
|
||||||
|
.catch(() => null);
|
||||||
}
|
}
|
||||||
|
|
||||||
function sendStylishEvent(type) {
|
|
||||||
document.dispatchEvent(new Event(type));
|
|
||||||
}
|
|
||||||
|
|
||||||
function sendVarsToPage(style) {
|
function styleSectionsEqual({sections: a}, {sections: b}) {
|
||||||
if (style) {
|
if (!a || !b) {
|
||||||
const vars = (style.usercssData || {}).vars || `${style.updateUrl}`.split('?')[1];
|
return undefined;
|
||||||
if (vars) sendPageEvent('vars:' + JSON.stringify(vars));
|
}
|
||||||
|
if (a.length !== b.length) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
// order of sections should be identical to account for the case of multiple
|
||||||
|
// sections matching the same URL because the order of rules is part of cascading
|
||||||
|
return a.every((sectionA, index) => propertiesEqual(sectionA, b[index]));
|
||||||
|
|
||||||
|
function propertiesEqual(secA, secB) {
|
||||||
|
for (const name of ['urlPrefixes', 'urls', 'domains', 'regexps']) {
|
||||||
|
if (!equalOrEmpty(secA[name], secB[name], 'every', arrayMirrors)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return equalOrEmpty(secA.code, secB.code, 'substr', (a, b) => a === b);
|
||||||
|
}
|
||||||
|
|
||||||
|
function equalOrEmpty(a, b, telltale, comparator) {
|
||||||
|
const typeA = a && typeof a[telltale] === 'function';
|
||||||
|
const typeB = b && typeof b[telltale] === 'function';
|
||||||
|
return (
|
||||||
|
(a === null || a === undefined || (typeA && !a.length)) &&
|
||||||
|
(b === null || b === undefined || (typeB && !b.length))
|
||||||
|
) || typeA && typeB && a.length === b.length && comparator(a, b);
|
||||||
|
}
|
||||||
|
|
||||||
|
function arrayMirrors(array1, array2) {
|
||||||
|
return (
|
||||||
|
array1.every(el => array2.includes(el)) &&
|
||||||
|
array2.every(el => array1.includes(el))
|
||||||
|
);
|
||||||
}
|
}
|
||||||
return style || false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
function onDOMready() {
|
||||||
|
if (document.readyState !== 'loading') {
|
||||||
|
return Promise.resolve();
|
||||||
|
}
|
||||||
|
return new Promise(resolve => {
|
||||||
|
document.addEventListener('DOMContentLoaded', function _() {
|
||||||
|
document.removeEventListener('DOMContentLoaded', _);
|
||||||
|
resolve();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
function openSettings(countdown = 10e3) {
|
||||||
|
const button = document.querySelector('.customize_button');
|
||||||
|
if (button) {
|
||||||
|
button.dispatchEvent(new MouseEvent('click', {bubbles: true}));
|
||||||
|
setTimeout(function pollArea(countdown = 2000) {
|
||||||
|
const area = document.getElementById('advancedsettings_area');
|
||||||
|
if (area || countdown < 0) {
|
||||||
|
(area || button).scrollIntoView({behavior: 'smooth', block: area ? 'end' : 'center'});
|
||||||
|
} else {
|
||||||
|
setTimeout(pollArea, 100, countdown - 100);
|
||||||
|
}
|
||||||
|
}, 500);
|
||||||
|
} else if (countdown > 0) {
|
||||||
|
setTimeout(openSettings, 100, countdown - 100);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
function orphanCheck() {
|
function orphanCheck() {
|
||||||
if (chrome.runtime.id) return true;
|
// TODO: switch to install-hook-usercss.js impl, and remove explicit orphanCheck() calls
|
||||||
removeEventListener(orphanEventId, orphanCheck, true);
|
if (chrome.i18n && chrome.i18n.getUILanguage()) {
|
||||||
removeEventListener('click', onClick, true);
|
return true;
|
||||||
removeEventListener('change', onChange);
|
}
|
||||||
sendPageEvent('quit');
|
// In Chrome content script is orphaned on an extension update/reload
|
||||||
observeColors(false);
|
// so we need to detach event listeners
|
||||||
togglePageListener(false);
|
window.removeEventListener(chrome.runtime.id + '-install', orphanCheck, true);
|
||||||
|
document.removeEventListener('stylishInstallChrome', onClick);
|
||||||
|
document.removeEventListener('stylishUpdateChrome', onClick);
|
||||||
|
try {
|
||||||
|
chrome.runtime.onMessage.removeListener(onMessage);
|
||||||
|
} catch (e) {}
|
||||||
}
|
}
|
||||||
})();
|
})();
|
||||||
|
|
||||||
function inPageContext(eventId, eventIdHost, styleId, apiUrl) {
|
// run in page context
|
||||||
let done, orphaned, vars;
|
document.documentElement.appendChild(document.createElement('script')).text = '(' + (
|
||||||
// `chrome` may be empty if no extensions use externally_connectable but USO needs it
|
EXTENSION_ORIGIN => {
|
||||||
if (!window.chrome) window.chrome = {runtime: {sendMessage: () => {}}};
|
document.currentScript.remove();
|
||||||
const EXT_ID = 'fjnbnpbmkenffdnngjfgmeleoegfcffe';
|
|
||||||
const {defineProperty} = Object;
|
// spoof Stylish extension presence in Chrome
|
||||||
const {dispatchEvent, CustomEvent, removeEventListener} = window;
|
if (window.chrome && chrome.app) {
|
||||||
const apply = Map.call.bind(Map.apply);
|
const realImage = window.Image;
|
||||||
const OVR = [
|
window.Image = function Image(...args) {
|
||||||
[chrome.runtime, 'sendMessage', (fn, me, args) => {
|
return new Proxy(new realImage(...args), {
|
||||||
const [id, /*msg*/, opts, cb = opts] = args;
|
get(obj, key) {
|
||||||
if (id !== EXT_ID) return apply(fn, me, args);
|
return obj[key];
|
||||||
if (typeof cb !== 'function') return Promise.resolve(true);
|
},
|
||||||
cb(true);
|
set(obj, key, value) {
|
||||||
}],
|
if (key === 'src' && /^chrome-extension:/i.test(value)) {
|
||||||
[Response.prototype, 'json', async (fn, me, args) => {
|
setTimeout(() => typeof obj.onload === 'function' && obj.onload());
|
||||||
const res = await apply(fn, me, args);
|
} else {
|
||||||
try {
|
obj[key] = value;
|
||||||
if (!done && me.url === apiUrl) {
|
}
|
||||||
done = true;
|
return true;
|
||||||
send(res);
|
},
|
||||||
setVars(res);
|
});
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// spoof USO referrer for style search in the popup
|
||||||
|
if (window !== top && location.pathname === '/') {
|
||||||
|
window.addEventListener('message', ({data, origin}) => {
|
||||||
|
if (!data ||
|
||||||
|
!data.xhr ||
|
||||||
|
origin !== EXTENSION_ORIGIN) {
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
} catch (e) {}
|
const xhr = new XMLHttpRequest();
|
||||||
return res;
|
xhr.onloadend = xhr.onerror = () => {
|
||||||
}],
|
top.postMessage({
|
||||||
[window, 'fetch', (fn, me, args) =>
|
id: data.xhr.id,
|
||||||
args[0] === `chrome-extension://${EXT_ID}/index.html`
|
status: xhr.status,
|
||||||
? Promise.resolve(new Response('<!doctype html><html lang="en"></html>'))
|
// [being overcautious] a string response is used instead of relying on responseType=json
|
||||||
: apply(fn, me, args),
|
// because it was invoked in a web page context so another extension may have incorrectly spoofed it
|
||||||
],
|
response: xhr.response,
|
||||||
];
|
}, EXTENSION_ORIGIN);
|
||||||
OVR.forEach(([obj, name, caller], i) => {
|
};
|
||||||
const orig = obj[name];
|
xhr.open('GET', data.xhr.url);
|
||||||
const ovr = new Proxy(orig, {
|
xhr.send();
|
||||||
apply(fn, me, args) {
|
});
|
||||||
if (orphaned) restore(obj, name, ovr, fn);
|
}
|
||||||
return (orphaned ? apply : caller)(fn, me, args);
|
|
||||||
},
|
// USO bug workaround: use the actual style settings in API response
|
||||||
|
let settings;
|
||||||
|
const originalResponseJson = Response.prototype.json;
|
||||||
|
document.addEventListener('stylusFixBuggyUSOsettings', function _({detail}) {
|
||||||
|
document.removeEventListener('stylusFixBuggyUSOsettings', _);
|
||||||
|
// TODO: remove .replace(/^\?/, '') when minimum_chrome_version >= 52 (https://crbug.com/601425)
|
||||||
|
settings = /\?/.test(detail) && new URLSearchParams(new URL(detail).search.replace(/^\?/, ''));
|
||||||
|
if (!settings) {
|
||||||
|
Response.prototype.json = originalResponseJson;
|
||||||
|
}
|
||||||
});
|
});
|
||||||
defineProperty(obj, name, {value: ovr});
|
Response.prototype.json = function (...args) {
|
||||||
OVR[i] = [obj, name, ovr, orig]; // same args as restore()
|
return originalResponseJson.call(this, ...args).then(json => {
|
||||||
});
|
if (!settings || typeof ((json || {}).style_settings || {}).every !== 'function') {
|
||||||
/* We set `isInstalled` at page start intentionally not trying to replicate Stylish login events.
|
return json;
|
||||||
* This difference allows USO site to detect presence of Stylus (or another similar extension). */
|
|
||||||
window.isInstalled = true;
|
|
||||||
addEventListener(eventId, onCommand, true);
|
|
||||||
function onCommand(e) {
|
|
||||||
if (e.detail === 'quit') {
|
|
||||||
removeEventListener(eventId, onCommand, true);
|
|
||||||
OVR.forEach(restore);
|
|
||||||
done = orphaned = true;
|
|
||||||
} else if (/^vars:/.test(e.detail)) {
|
|
||||||
vars = JSON.parse(e.detail.slice(5));
|
|
||||||
} else if (e.relatedTarget) {
|
|
||||||
send(e.relatedTarget.uploadedData);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
function restore(obj, name, ovr, orig) { // same order as OVR after patching
|
|
||||||
if (obj[name] === ovr) {
|
|
||||||
defineProperty(obj, name, {value: orig});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
function send(data) {
|
|
||||||
dispatchEvent(new CustomEvent(eventIdHost, {__proto: null, detail: data}));
|
|
||||||
}
|
|
||||||
function setVars(json) {
|
|
||||||
const images = new Map();
|
|
||||||
const isNew = typeof vars === 'object';
|
|
||||||
const badKeys = {};
|
|
||||||
const newKeys = [];
|
|
||||||
const makeKey = ({install_key: key}) => {
|
|
||||||
let res = isNew ? badKeys[key] : key;
|
|
||||||
if (!res) {
|
|
||||||
res = key.replace(/[^-\w]/g, '-');
|
|
||||||
res += newKeys.includes(res) ? '-' : '';
|
|
||||||
if (key !== res) {
|
|
||||||
badKeys[key] = res;
|
|
||||||
newKeys.push(res);
|
|
||||||
}
|
}
|
||||||
}
|
Response.prototype.json = originalResponseJson;
|
||||||
return res;
|
const images = new Map();
|
||||||
};
|
for (const jsonSetting of json.style_settings) {
|
||||||
if (!isNew) vars = new URLSearchParams(vars);
|
let value = settings.get('ik-' + jsonSetting.install_key);
|
||||||
for (const ss of json.style_settings || []) {
|
if (!value
|
||||||
const ik = makeKey(ss);
|
|| !jsonSetting.style_setting_options
|
||||||
let value = isNew ? (vars[ik] || {}).value : vars.get('ik-' + ik);
|
|| !jsonSetting.style_setting_options[0]) {
|
||||||
if (value == null || !(ss.style_setting_options || [])[0]) {
|
continue;
|
||||||
continue;
|
}
|
||||||
}
|
if (value.startsWith('ik-')) {
|
||||||
if (ss.setting_type === 'image') {
|
value = value.replace(/^ik-/, '');
|
||||||
let isListed;
|
const defaultItem = jsonSetting.style_setting_options.find(item => item.default);
|
||||||
for (const opt of ss.style_setting_options) {
|
if (!defaultItem || defaultItem.install_key !== value) {
|
||||||
isListed |= opt.default = (opt.install_key === value);
|
if (defaultItem) {
|
||||||
}
|
defaultItem.default = false;
|
||||||
images.set(ik, {url: isNew && !isListed ? vars[`${ik}-custom`].value : value, isListed});
|
}
|
||||||
} else if (value.startsWith('ik-') || isNew && vars[ik].type === 'select') {
|
jsonSetting.style_setting_options.some(item => {
|
||||||
value = value.replace(/^ik-/, '');
|
if (item.install_key === value) {
|
||||||
const def = ss.style_setting_options.find(item => item.default);
|
item.default = true;
|
||||||
if (!def || makeKey(def) !== value) {
|
return true;
|
||||||
if (def) def.default = false;
|
}
|
||||||
for (const item of ss.style_setting_options) {
|
});
|
||||||
if (makeKey(item) === value) {
|
}
|
||||||
item.default = true;
|
} else if (jsonSetting.setting_type === 'image') {
|
||||||
break;
|
jsonSetting.style_setting_options.some(item => {
|
||||||
|
if (item.default) {
|
||||||
|
item.default = false;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
images.set(jsonSetting.install_key, value);
|
||||||
|
} else {
|
||||||
|
const item = jsonSetting.style_setting_options[0];
|
||||||
|
if (item.value !== value && item.install_key === 'placeholder') {
|
||||||
|
item.value = value;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
if (images.size) {
|
||||||
const item = ss.style_setting_options[0];
|
new MutationObserver((_, observer) => {
|
||||||
if (item.value !== value && item.install_key === 'placeholder') {
|
if (!document.getElementById('style-settings')) {
|
||||||
item.value = value;
|
return;
|
||||||
|
}
|
||||||
|
observer.disconnect();
|
||||||
|
for (const [name, url] of images.entries()) {
|
||||||
|
const elRadio = document.querySelector(`input[name="ik-${name}"][value="user-url"]`);
|
||||||
|
const elUrl = elRadio && document.getElementById(elRadio.id.replace('url-choice', 'user-url'));
|
||||||
|
if (elUrl) {
|
||||||
|
elUrl.value = url;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}).observe(document, {childList: true, subtree: true});
|
||||||
}
|
}
|
||||||
}
|
return json;
|
||||||
}
|
});
|
||||||
if (!images.size) return;
|
};
|
||||||
|
}
|
||||||
|
) + `)('${chrome.runtime.getURL('').slice(0, -1)}')`;
|
||||||
|
|
||||||
|
// TODO: remove the following statement when USO pagination is fixed
|
||||||
|
if (location.search.includes('category=')) {
|
||||||
|
document.addEventListener('DOMContentLoaded', function _() {
|
||||||
|
document.removeEventListener('DOMContentLoaded', _);
|
||||||
new MutationObserver((_, observer) => {
|
new MutationObserver((_, observer) => {
|
||||||
if (!document.getElementById('style-settings')) return;
|
if (!document.getElementById('pagination')) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
observer.disconnect();
|
observer.disconnect();
|
||||||
for (const [name, {url, isListed}] of images) {
|
const category = '&' + location.search.match(/category=[^&]+/)[0];
|
||||||
const elRadio = document.querySelector(`input[name="ik-${name}"][value="user-url"]`);
|
const links = document.querySelectorAll('#pagination a[href*="page="]:not([href*="category="])');
|
||||||
const elUrl = elRadio && document.getElementById(elRadio.id.replace('url-choice', 'user-url'));
|
for (let i = 0; i < links.length; i++) {
|
||||||
if (elUrl) {
|
links[i].href += category;
|
||||||
elRadio.checked = !isListed;
|
|
||||||
elUrl.value = url;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}).observe(document, {childList: true, subtree: true});
|
}).observe(document, {childList: true, subtree: true});
|
||||||
}
|
});
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,43 +0,0 @@
|
||||||
/* global API */// msg.js
|
|
||||||
'use strict';
|
|
||||||
|
|
||||||
(() => {
|
|
||||||
const ORIGIN = 'https://userstyles.world';
|
|
||||||
const HANDLERS = Object.assign(Object.create(null), {
|
|
||||||
|
|
||||||
async 'usw-ready'() {
|
|
||||||
send({type: 'usw-remove-stylus-button'});
|
|
||||||
if (location.pathname === '/api/oauth/style/new') {
|
|
||||||
const styleId = Number(new URLSearchParams(location.search).get('vendor_data'));
|
|
||||||
const data = await API.data.pop('usw' + styleId);
|
|
||||||
send({type: 'usw-fill-new-style', data});
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
async 'usw-style-info-request'(data) {
|
|
||||||
switch (data.requestType) {
|
|
||||||
case 'installed': {
|
|
||||||
const updateUrl = `${ORIGIN}/api/style/${data.styleID}.user.css`;
|
|
||||||
const style = await API.styles.find({updateUrl});
|
|
||||||
send({
|
|
||||||
type: 'usw-style-info-response',
|
|
||||||
data: {installed: Boolean(style), requestType: 'installed'},
|
|
||||||
});
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
window.addEventListener('message', ({data, source, origin}) => {
|
|
||||||
// Some browsers don't reveal `source` to extensions e.g. Firefox
|
|
||||||
if (data && (source ? source === window : origin === ORIGIN)) {
|
|
||||||
const fn = HANDLERS[data.type];
|
|
||||||
if (fn) fn(data);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
function send(msg) {
|
|
||||||
window.postMessage(msg, ORIGIN);
|
|
||||||
}
|
|
||||||
})();
|
|
|
@ -1,345 +0,0 @@
|
||||||
'use strict';
|
|
||||||
|
|
||||||
/** @type {function(opts):StyleInjector} */
|
|
||||||
window.StyleInjector = window.INJECTED === 1 ? window.StyleInjector : ({
|
|
||||||
compare,
|
|
||||||
onUpdate = () => {},
|
|
||||||
}) => {
|
|
||||||
const PREFIX = 'stylus-';
|
|
||||||
const PATCH_ID = 'transition-patch';
|
|
||||||
// styles are out of order if any of these elements is injected between them
|
|
||||||
const ORDERED_TAGS = new Set(['head', 'body', 'frameset', 'style', 'link']);
|
|
||||||
const docRewriteObserver = RewriteObserver(sort);
|
|
||||||
const docRootObserver = RootObserver(sortIfNeeded);
|
|
||||||
const toSafeChar = c => String.fromCharCode(0xFF00 + c.charCodeAt(0) - 0x20);
|
|
||||||
const list = [];
|
|
||||||
const table = new Map();
|
|
||||||
let isEnabled = true;
|
|
||||||
let isTransitionPatched = chrome.app && CSS.supports('accent-color', 'red'); // Chrome 93
|
|
||||||
let exposeStyleName;
|
|
||||||
// will store the original method refs because the page can override them
|
|
||||||
let creationDoc, createElement, createElementNS;
|
|
||||||
|
|
||||||
return /** @namespace StyleInjector */ {
|
|
||||||
|
|
||||||
list,
|
|
||||||
|
|
||||||
async apply(styleMap) {
|
|
||||||
const styles = styleMapToArray(styleMap);
|
|
||||||
const value = !styles.length
|
|
||||||
? []
|
|
||||||
: await docRootObserver.evade(() => {
|
|
||||||
if (!isTransitionPatched && isEnabled) {
|
|
||||||
applyTransitionPatch(styles);
|
|
||||||
}
|
|
||||||
return styles.map(addUpdate);
|
|
||||||
});
|
|
||||||
emitUpdate();
|
|
||||||
return value;
|
|
||||||
},
|
|
||||||
|
|
||||||
clear() {
|
|
||||||
addRemoveElements(false);
|
|
||||||
list.length = 0;
|
|
||||||
table.clear();
|
|
||||||
emitUpdate();
|
|
||||||
},
|
|
||||||
|
|
||||||
clearOrphans() {
|
|
||||||
for (const el of document.querySelectorAll(`style[id^="${PREFIX}"].stylus`)) {
|
|
||||||
const id = el.id.slice(PREFIX.length);
|
|
||||||
if (/^\d+$/.test(id) || id === PATCH_ID) {
|
|
||||||
el.remove();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
remove(id) {
|
|
||||||
remove(id);
|
|
||||||
emitUpdate();
|
|
||||||
},
|
|
||||||
|
|
||||||
replace(styleMap) {
|
|
||||||
const styles = styleMapToArray(styleMap);
|
|
||||||
const added = new Set(styles.map(s => s.id));
|
|
||||||
const removed = [];
|
|
||||||
for (const style of list) {
|
|
||||||
if (!added.has(style.id)) {
|
|
||||||
removed.push(style.id);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
styles.forEach(addUpdate);
|
|
||||||
removed.forEach(remove);
|
|
||||||
emitUpdate();
|
|
||||||
},
|
|
||||||
|
|
||||||
toggle(enable) {
|
|
||||||
if (isEnabled === enable) return;
|
|
||||||
isEnabled = enable;
|
|
||||||
if (!enable) toggleObservers(false);
|
|
||||||
addRemoveElements(enable);
|
|
||||||
if (enable) toggleObservers(true);
|
|
||||||
},
|
|
||||||
|
|
||||||
sort: sort,
|
|
||||||
};
|
|
||||||
|
|
||||||
function add(style) {
|
|
||||||
const el = style.el = createStyle(style);
|
|
||||||
const i = list.findIndex(item => compare(item, style) > 0);
|
|
||||||
table.set(style.id, style);
|
|
||||||
if (isEnabled) {
|
|
||||||
document.documentElement.insertBefore(el, i < 0 ? null : list[i].el);
|
|
||||||
}
|
|
||||||
list.splice(i < 0 ? list.length : i, 0, style);
|
|
||||||
return el;
|
|
||||||
}
|
|
||||||
|
|
||||||
function addRemoveElements(add) {
|
|
||||||
for (const {el} of list) {
|
|
||||||
if (add) {
|
|
||||||
document.documentElement.appendChild(el);
|
|
||||||
} else {
|
|
||||||
el.remove();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function addUpdate(style) {
|
|
||||||
return table.has(style.id) ? update(style) : add(style);
|
|
||||||
}
|
|
||||||
|
|
||||||
function applyTransitionPatch(styles) {
|
|
||||||
isTransitionPatched = true;
|
|
||||||
// CSS transition bug workaround: since we insert styles asynchronously,
|
|
||||||
// the browsers, especially Firefox, may apply all transitions on page load
|
|
||||||
if (document.readyState === 'complete' ||
|
|
||||||
document.visibilityState === 'hidden' ||
|
|
||||||
!styles.some(s => s.code.includes('transition'))) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const el = createStyle({id: PATCH_ID, code: `
|
|
||||||
:root:not(#\\0):not(#\\0) * {
|
|
||||||
transition: none !important;
|
|
||||||
}
|
|
||||||
`});
|
|
||||||
document.documentElement.appendChild(el);
|
|
||||||
// wait for the next paint to complete
|
|
||||||
// note: requestAnimationFrame won't fire in inactive tabs
|
|
||||||
requestAnimationFrame(() => setTimeout(() => el.remove()));
|
|
||||||
}
|
|
||||||
|
|
||||||
function createStyle(style = {}) {
|
|
||||||
const {id} = style;
|
|
||||||
if (!creationDoc) initCreationDoc();
|
|
||||||
let el;
|
|
||||||
if (document.documentElement instanceof SVGSVGElement) {
|
|
||||||
// SVG document style
|
|
||||||
el = createElementNS.call(creationDoc, 'http://www.w3.org/2000/svg', 'style');
|
|
||||||
} else if (document instanceof XMLDocument) {
|
|
||||||
// XML document style
|
|
||||||
el = createElementNS.call(creationDoc, 'http://www.w3.org/1999/xhtml', 'style');
|
|
||||||
} else {
|
|
||||||
// HTML document style; also works on HTML-embedded SVG
|
|
||||||
el = createElement.call(creationDoc, 'style');
|
|
||||||
}
|
|
||||||
if (id) {
|
|
||||||
el.id = `${PREFIX}${id}`;
|
|
||||||
const oldEl = document.getElementById(el.id);
|
|
||||||
if (oldEl) oldEl.id += '-superseded-by-Stylus';
|
|
||||||
}
|
|
||||||
el.type = 'text/css';
|
|
||||||
// SVG className is not a string, but an instance of SVGAnimatedString
|
|
||||||
el.classList.add('stylus');
|
|
||||||
setTextAndName(el, style);
|
|
||||||
return el;
|
|
||||||
}
|
|
||||||
|
|
||||||
function setTextAndName(el, {id, code = '', name}) {
|
|
||||||
if (exposeStyleName && name) {
|
|
||||||
el.dataset.name = name;
|
|
||||||
name = encodeURIComponent(name.replace(/[?#/']/g, toSafeChar));
|
|
||||||
code += `\n/*# sourceURL=${chrome.runtime.getURL(name)}.user.css#${id} */`;
|
|
||||||
}
|
|
||||||
el.textContent = code;
|
|
||||||
}
|
|
||||||
|
|
||||||
function toggleObservers(shouldStart) {
|
|
||||||
const onOff = shouldStart && isEnabled ? 'start' : 'stop';
|
|
||||||
docRewriteObserver[onOff]();
|
|
||||||
docRootObserver[onOff]();
|
|
||||||
}
|
|
||||||
|
|
||||||
function emitUpdate() {
|
|
||||||
toggleObservers(list.length);
|
|
||||||
onUpdate();
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
FF59+ workaround: allow the page to read our sheets, https://github.com/openstyles/stylus/issues/461
|
|
||||||
First we're trying the page context document where inline styles may be forbidden by CSP
|
|
||||||
https://bugzilla.mozilla.org/show_bug.cgi?id=1579345#c3
|
|
||||||
and since userAgent.navigator can be spoofed via about:config or devtools,
|
|
||||||
we're checking for getPreventDefault that was removed in FF59
|
|
||||||
*/
|
|
||||||
function initCreationDoc() {
|
|
||||||
creationDoc = !Event.prototype.getPreventDefault && document.wrappedJSObject;
|
|
||||||
if (creationDoc) {
|
|
||||||
({createElement, createElementNS} = creationDoc);
|
|
||||||
const el = document.documentElement.appendChild(createStyle());
|
|
||||||
const isApplied = el.sheet;
|
|
||||||
el.remove();
|
|
||||||
if (isApplied) return;
|
|
||||||
}
|
|
||||||
creationDoc = document;
|
|
||||||
({createElement, createElementNS} = document);
|
|
||||||
}
|
|
||||||
|
|
||||||
function remove(id) {
|
|
||||||
const style = table.get(id);
|
|
||||||
if (!style) return;
|
|
||||||
table.delete(id);
|
|
||||||
list.splice(list.indexOf(style), 1);
|
|
||||||
style.el.remove();
|
|
||||||
}
|
|
||||||
|
|
||||||
function sort() {
|
|
||||||
docRootObserver.evade(() => {
|
|
||||||
list.sort(compare);
|
|
||||||
addRemoveElements(true);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function sortIfNeeded() {
|
|
||||||
let needsSort;
|
|
||||||
let el = list.length && list[0].el;
|
|
||||||
if (!el) {
|
|
||||||
needsSort = false;
|
|
||||||
} else if (el.parentNode !== creationDoc.documentElement) {
|
|
||||||
needsSort = true;
|
|
||||||
} else {
|
|
||||||
let i = 0;
|
|
||||||
while (el) {
|
|
||||||
if (i < list.length && el === list[i].el) {
|
|
||||||
i++;
|
|
||||||
} else if (ORDERED_TAGS.has(el.localName)) {
|
|
||||||
needsSort = true;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
el = el.nextElementSibling;
|
|
||||||
}
|
|
||||||
// some styles are not injected to the document
|
|
||||||
if (i < list.length) needsSort = true;
|
|
||||||
}
|
|
||||||
if (needsSort) sort();
|
|
||||||
return needsSort;
|
|
||||||
}
|
|
||||||
|
|
||||||
function styleMapToArray(styleMap) {
|
|
||||||
if (styleMap.cfg) {
|
|
||||||
({exposeStyleName} = styleMap.cfg);
|
|
||||||
delete styleMap.cfg;
|
|
||||||
}
|
|
||||||
return Object.values(styleMap).map(({id, code, name}) => ({
|
|
||||||
id,
|
|
||||||
name,
|
|
||||||
code: code.join(''),
|
|
||||||
}));
|
|
||||||
}
|
|
||||||
|
|
||||||
function update(newStyle) {
|
|
||||||
const {id, code} = newStyle;
|
|
||||||
const style = table.get(id);
|
|
||||||
if (style.code !== code ||
|
|
||||||
style.name !== newStyle.name && exposeStyleName) {
|
|
||||||
style.code = code;
|
|
||||||
setTextAndName(style.el, newStyle);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function RewriteObserver(onChange) {
|
|
||||||
// detect documentElement being rewritten from inside the script
|
|
||||||
let root;
|
|
||||||
let observing = false;
|
|
||||||
let timer;
|
|
||||||
const observer = new MutationObserver(check);
|
|
||||||
return {start, stop};
|
|
||||||
|
|
||||||
function start() {
|
|
||||||
if (observing) return;
|
|
||||||
// detect dynamic iframes rewritten after creation by the embedder i.e. externally
|
|
||||||
root = document.documentElement;
|
|
||||||
timer = setTimeout(check);
|
|
||||||
observer.observe(document, {childList: true});
|
|
||||||
observing = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
function stop() {
|
|
||||||
if (!observing) return;
|
|
||||||
clearTimeout(timer);
|
|
||||||
observer.disconnect();
|
|
||||||
observing = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
function check() {
|
|
||||||
if (root !== document.documentElement) {
|
|
||||||
root = document.documentElement;
|
|
||||||
onChange();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function RootObserver(onChange) {
|
|
||||||
let digest = 0;
|
|
||||||
let lastCalledTime = NaN;
|
|
||||||
let observing = false;
|
|
||||||
const observer = new MutationObserver(() => {
|
|
||||||
if (digest) {
|
|
||||||
if (performance.now() - lastCalledTime > 1000) {
|
|
||||||
digest = 0;
|
|
||||||
} else if (digest > 5) {
|
|
||||||
throw new Error('The page keeps generating mutations. Skip the event.');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (onChange()) {
|
|
||||||
digest++;
|
|
||||||
lastCalledTime = performance.now();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
return {evade, start, stop};
|
|
||||||
|
|
||||||
function evade(fn) {
|
|
||||||
const restore = observing && start;
|
|
||||||
stop();
|
|
||||||
return new Promise(resolve => run(fn, resolve, waitForRoot))
|
|
||||||
.then(restore);
|
|
||||||
}
|
|
||||||
|
|
||||||
function start() {
|
|
||||||
if (observing) return;
|
|
||||||
observer.observe(document.documentElement, {childList: true});
|
|
||||||
observing = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
function stop() {
|
|
||||||
if (!observing) return;
|
|
||||||
// FIXME: do we need this?
|
|
||||||
observer.takeRecords();
|
|
||||||
observer.disconnect();
|
|
||||||
observing = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
function run(fn, resolve, wait) {
|
|
||||||
if (document.documentElement) {
|
|
||||||
resolve(fn());
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
if (wait) wait(fn, resolve);
|
|
||||||
}
|
|
||||||
|
|
||||||
function waitForRoot(...args) {
|
|
||||||
new MutationObserver((_, observer) => run(...args) && observer.disconnect())
|
|
||||||
.observe(document, {childList: true});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
527
edit.html
527
edit.html
|
@ -1,86 +1,122 @@
|
||||||
<!DOCTYPE html>
|
|
||||||
<html id="stylus">
|
<html id="stylus">
|
||||||
<head>
|
<head>
|
||||||
<meta charset="UTF-8">
|
<meta charset="UTF-8">
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
|
||||||
<link href="global.css" rel="stylesheet">
|
<link href="global.css" rel="stylesheet">
|
||||||
<link href="global-dark.css" rel="stylesheet">
|
<link href="edit/edit.css" rel="stylesheet">
|
||||||
<style id="cm-theme"></style>
|
<link rel="stylesheet" href="msgbox/msgbox.css">
|
||||||
|
|
||||||
|
<style id="firefox-transitions-bug-suppressor">
|
||||||
|
/* restrict to FF */
|
||||||
|
@supports (-moz-appearance:none) {
|
||||||
|
/* increased specificity to override sane selectors in user styles */
|
||||||
|
html#stylus.firefox #stylus-edit #header *,
|
||||||
|
html#stylus.firefox #stylus-edit #sections * {
|
||||||
|
transition: none !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
||||||
<script src="js/polyfill.js"></script>
|
|
||||||
<script src="js/toolbox.js"></script>
|
|
||||||
<script src="js/msg.js"></script>
|
|
||||||
<script src="js/prefs.js"></script>
|
|
||||||
<script src="js/dom.js"></script>
|
<script src="js/dom.js"></script>
|
||||||
|
<script src="js/messaging.js"></script>
|
||||||
|
<script src="js/prefs.js"></script>
|
||||||
<script src="js/localization.js"></script>
|
<script src="js/localization.js"></script>
|
||||||
<script src="content/style-injector.js"></script>
|
<script src="js/script-loader.js"></script>
|
||||||
<script src="content/apply.js"></script>
|
|
||||||
|
|
||||||
<script src="js/sections-util.js"></script>
|
|
||||||
<script src="js/storage-util.js"></script>
|
<script src="js/storage-util.js"></script>
|
||||||
<script src="edit/codemirror-themes.js"></script> <!-- must precede base.js -->
|
<script src="content/apply.js"></script>
|
||||||
<script src="edit/base.js"></script>
|
<script src="edit/util.js"></script>
|
||||||
|
<script src="edit/regexp-tester.js"></script>
|
||||||
|
<script src="edit/applies-to-line-widget.js"></script>
|
||||||
|
<script src="edit/source-editor.js"></script>
|
||||||
|
<script src="edit/colorpicker-helper.js"></script>
|
||||||
|
<script src="edit/beautify.js"></script>
|
||||||
|
<script src="edit/sections.js"></script>
|
||||||
|
<script src="edit/show-keymap-help.js"></script>
|
||||||
|
<script src="edit/codemirror-editing-hooks.js"></script>
|
||||||
|
<script src="edit/edit.js"></script>
|
||||||
|
|
||||||
|
<script src="msgbox/msgbox.js" async></script>
|
||||||
|
|
||||||
|
<link href="vendor/codemirror/lib/codemirror.css" rel="stylesheet">
|
||||||
<script src="vendor/codemirror/lib/codemirror.js"></script>
|
<script src="vendor/codemirror/lib/codemirror.js"></script>
|
||||||
|
|
||||||
<script src="vendor/codemirror/mode/css/css.js"></script>
|
<script src="vendor/codemirror/mode/css/css.js"></script>
|
||||||
<script src="vendor/codemirror/mode/stylus/stylus.js"></script>
|
|
||||||
|
<link href="vendor/codemirror/addon/dialog/dialog.css" rel="stylesheet">
|
||||||
<script src="vendor/codemirror/addon/dialog/dialog.js"></script>
|
<script src="vendor/codemirror/addon/dialog/dialog.js"></script>
|
||||||
<script src="vendor/codemirror/addon/edit/closebrackets.js"></script>
|
|
||||||
<script src="vendor/codemirror/addon/scroll/annotatescrollbar.js"></script>
|
<link href="vendor/codemirror/addon/search/matchesonscrollbar.css" rel="stylesheet">
|
||||||
<script src="vendor/codemirror/addon/search/searchcursor.js"></script>
|
|
||||||
<script src="vendor/codemirror/addon/search/matchesonscrollbar.js"></script>
|
<script src="vendor/codemirror/addon/search/matchesonscrollbar.js"></script>
|
||||||
|
<script src="vendor/codemirror/addon/scroll/annotatescrollbar.js"></script>
|
||||||
|
<script src="vendor/codemirror/addon/search/match-highlighter.js"></script>
|
||||||
|
<script src="vendor/codemirror/addon/search/searchcursor.js"></script>
|
||||||
|
|
||||||
<script src="vendor/codemirror/addon/comment/comment.js"></script>
|
<script src="vendor/codemirror/addon/comment/comment.js"></script>
|
||||||
<script src="vendor/codemirror/addon/selection/active-line.js"></script>
|
<script src="vendor/codemirror/addon/selection/active-line.js"></script>
|
||||||
<script src="vendor/codemirror/addon/edit/matchbrackets.js"></script>
|
<script src="vendor/codemirror/addon/edit/matchbrackets.js"></script>
|
||||||
|
|
||||||
|
<link href="vendor/codemirror/addon/fold/foldgutter.css" rel="stylesheet" />
|
||||||
<script src="vendor/codemirror/addon/fold/foldcode.js"></script>
|
<script src="vendor/codemirror/addon/fold/foldcode.js"></script>
|
||||||
<script src="vendor/codemirror/addon/fold/foldgutter.js"></script>
|
<script src="vendor/codemirror/addon/fold/foldgutter.js"></script>
|
||||||
<script src="vendor/codemirror/addon/fold/brace-fold.js"></script>
|
<script src="vendor/codemirror/addon/fold/brace-fold.js"></script>
|
||||||
<script src="vendor/codemirror/addon/fold/indent-fold.js"></script>
|
<script src="vendor/codemirror/addon/fold/indent-fold.js"></script>
|
||||||
<script src="vendor/codemirror/addon/fold/comment-fold.js"></script>
|
<script src="vendor/codemirror/addon/fold/comment-fold.js"></script>
|
||||||
|
|
||||||
|
<link href="vendor/codemirror/addon/lint/lint.css" rel="stylesheet" />
|
||||||
<script src="vendor/codemirror/addon/lint/lint.js"></script>
|
<script src="vendor/codemirror/addon/lint/lint.js"></script>
|
||||||
|
|
||||||
|
|
||||||
|
<link href="vendor/codemirror/addon/hint/show-hint.css" rel="stylesheet" />
|
||||||
<script src="vendor/codemirror/addon/hint/show-hint.js"></script>
|
<script src="vendor/codemirror/addon/hint/show-hint.js"></script>
|
||||||
<script src="vendor/codemirror/addon/hint/css-hint.js"></script>
|
<script src="vendor/codemirror/addon/hint/css-hint.js"></script>
|
||||||
<script src="vendor/codemirror/keymap/emacs.js"></script>
|
|
||||||
<script src="vendor/codemirror/keymap/sublime.js"></script>
|
<script src="vendor/codemirror/keymap/sublime.js"></script>
|
||||||
|
<script src="vendor/codemirror/keymap/emacs.js"></script>
|
||||||
<script src="vendor/codemirror/keymap/vim.js"></script>
|
<script src="vendor/codemirror/keymap/vim.js"></script>
|
||||||
<script src="vendor-overwrites/codemirror-addon/match-highlighter.js"></script>
|
|
||||||
|
|
||||||
<script src="js/color/color-converter.js"></script>
|
<link href="vendor-overwrites/colorpicker/colorpicker.css" rel="stylesheet">
|
||||||
<script src="js/color/color-mimicry.js"></script>
|
<script src="vendor-overwrites/colorpicker/colorconverter.js"></script>
|
||||||
<script src="js/color/color-picker.js"></script>
|
<script src="vendor-overwrites/colorpicker/colorpicker.js"></script>
|
||||||
<script src="js/color/color-view.js"></script>
|
<script src="vendor-overwrites/colorpicker/colorview.js"></script>
|
||||||
<script src="js/worker-util.js"></script>
|
|
||||||
|
|
||||||
<script src="edit/util.js"></script>
|
<link href="edit/global-search.css" rel="stylesheet">
|
||||||
|
<script src="edit/global-search.js"></script>
|
||||||
|
|
||||||
|
<script src="edit/match-highlighter-helper.js"></script>
|
||||||
|
|
||||||
|
<link href="edit/codemirror-default.css" rel="stylesheet">
|
||||||
<script src="edit/codemirror-default.js"></script>
|
<script src="edit/codemirror-default.js"></script>
|
||||||
<script src="edit/codemirror-factory.js"></script>
|
|
||||||
<script src="edit/moz-section-finder.js"></script>
|
<script src="edit/linter.js"></script>
|
||||||
<script src="edit/moz-section-widget.js"></script>
|
<script src="edit/linter-defaults.js"></script>
|
||||||
<script src="edit/linter-manager.js"></script>
|
<script src="edit/linter-engines.js"></script>
|
||||||
<script src="edit/beautify.js"></script>
|
<script src="edit/linter-meta.js"></script>
|
||||||
<script src="edit/source-editor.js"></script>
|
<script src="edit/linter-help-dialog.js"></script>
|
||||||
<script src="edit/sections-editor-section.js"></script>
|
<script src="edit/linter-report.js"></script>
|
||||||
<script src="edit/sections-editor.js"></script>
|
<script src="edit/linter-config-dialog.js"></script>
|
||||||
<script src="edit/usw-integration.js"></script>
|
|
||||||
|
<script src="edit/editor-worker.js"></script>
|
||||||
|
|
||||||
|
<link id="cm-theme" rel="stylesheet">
|
||||||
|
|
||||||
<template data-id="appliesTo">
|
<template data-id="appliesTo">
|
||||||
<li class="applies-to-item">
|
<li class="applies-to-item">
|
||||||
<div class="select-resizer">
|
<div class="select-resizer">
|
||||||
<select name="applies-type" class="applies-type style-contributor">
|
<select name="applies-type" class="applies-type style-contributor">
|
||||||
<option value="url" i18n="appliesUrlOption"></option>
|
<option value="url" i18n-text="appliesUrlOption"></option>
|
||||||
<option value="url-prefix" i18n="appliesUrlPrefixOption"></option>
|
<option value="url-prefix" i18n-text="appliesUrlPrefixOption"></option>
|
||||||
<option value="domain" i18n="appliesDomainOption"></option>
|
<option value="domain" i18n-text="appliesDomainOption"></option>
|
||||||
<option value="regexp" i18n="appliesRegexpOption"></option>
|
<option value="regexp" i18n-text="appliesRegexpOption"></option>
|
||||||
</select>
|
</select>
|
||||||
<svg class="svg-icon select-arrow"><use xlink:href="#svg-icon-select-arrow"/></svg>
|
<svg class="svg-icon select-arrow"><use xlink:href="#svg-icon-select-arrow"/></svg>
|
||||||
</div>
|
</div>
|
||||||
<div class="applies-value-wrapper">
|
<div class="applies-value-wrapper">
|
||||||
<input name="applies-value" class="applies-value style-contributor" spellcheck="false">
|
<input name="applies-value" class="applies-value style-contributor" spellcheck="false">
|
||||||
<a class="remove-applies-to" i18n="appliesRemove, title:appliesRemove" tabindex="0">
|
<a class="remove-applies-to" href="#" i18n-text="appliesRemove" i18n-title="appliesRemove">
|
||||||
<svg class="svg-icon remove"><use xlink:href="#svg-icon-minus"/></svg>
|
<svg class="svg-icon remove"><use xlink:href="#svg-icon-minus"/></svg>
|
||||||
</a>
|
</a>
|
||||||
<a class="add-applies-to" i18n="appliesAdd, title:appliesAdd" tabindex="0">
|
<a class="add-applies-to" href="#" i18n-text="appliesAdd" i18n-title="appliesAdd">
|
||||||
<svg class="svg-icon add"><use xlink:href="#svg-icon-plus"/></svg>
|
<svg class="svg-icon add"><use xlink:href="#svg-icon-plus"/></svg>
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
|
@ -88,8 +124,8 @@
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<template data-id="appliesToEverything">
|
<template data-id="appliesToEverything">
|
||||||
<li class="applies-to-everything" i18n="appliesToEverything">
|
<li class="applies-to-everything" i18n-text="appliesToEverything">
|
||||||
<a class="add-applies-to" i18n="appliesAdd, title:appliesAdd" tabindex="0">
|
<a class="add-applies-to" i18n-text="appliesAdd" i18n-title="appliesAdd" href="#">
|
||||||
<svg class="svg-icon add"><use xlink:href="#svg-icon-plus"/></svg>
|
<svg class="svg-icon add"><use xlink:href="#svg-icon-plus"/></svg>
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
|
@ -97,57 +133,61 @@
|
||||||
|
|
||||||
<template data-id="section">
|
<template data-id="section">
|
||||||
<div class="section">
|
<div class="section">
|
||||||
<!-- not using DIV to make our CSS work for #sections > div:only-of-type .remove-section -->
|
<label i18n-text="sectionCode" class="code-label"></label>
|
||||||
<p class="deleted-section">
|
<br>
|
||||||
<button class="restore-section" i18n="sectionRestore"></button>
|
|
||||||
</p>
|
|
||||||
<label i18n="sectionCode" class="code-label"></label>
|
|
||||||
<div class="applies-to">
|
<div class="applies-to">
|
||||||
<label i18n="appliesLabel, title:appliesHelp" data-cmd="note">
|
<label i18n-text="appliesLabel">
|
||||||
<a class="svg-inline-wrapper applies-to-help" tabindex="0">
|
<a href="#" class="svg-inline-wrapper applies-to-help" tabindex="0">
|
||||||
<svg class="svg-icon info"><use xlink:href="#svg-icon-help"/></svg>
|
<svg class="svg-icon info"><use xlink:href="#svg-icon-help"/></svg>
|
||||||
</a>
|
</a>
|
||||||
</label>
|
</label>
|
||||||
<ul class="applies-to-list"></ul>
|
<ul class="applies-to-list"></ul>
|
||||||
</div>
|
</div>
|
||||||
<div class="edit-actions">
|
<div class="edit-actions">
|
||||||
<button class="remove-section" i18n="sectionRemove"></button>
|
<button class="remove-section" i18n-text="sectionRemove"></button>
|
||||||
<button class="add-section" i18n="long-text:sectionAdd, short-text:genericAdd"></button>
|
<button class="add-section" i18n-long-text="sectionAdd" i18n-short-text="genericAdd"></button>
|
||||||
<button class="clone-section" i18n="genericClone"></button>
|
<button class="clone-section" i18n-text="genericClone"></button>
|
||||||
<button class="move-section-up"></button>
|
<button class="move-section-up"></button>
|
||||||
<button class="move-section-down"></button>
|
<button class="move-section-down"></button>
|
||||||
<button class="beautify-section" i18n="styleBeautify"></button>
|
<button class="beautify-section" i18n-text="styleBeautify"></button>
|
||||||
<button class="test-regexp" i18n="genericTest"></button>
|
<button class="test-regexp" i18n-text="styleRegexpTestButton"></button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
<!-- not using DIV to make our CSS work for #sections > div:only-of-type .remove-section -->
|
||||||
|
<template data-id="deletedSection">
|
||||||
|
<p class="deleted-section">
|
||||||
|
<button class="restore-section" i18n-text="sectionRestore"></button>
|
||||||
|
</p>
|
||||||
|
</template>
|
||||||
|
|
||||||
<template data-id="searchReplaceDialog">
|
<template data-id="searchReplaceDialog">
|
||||||
<div id="search-replace-dialog">
|
<div id="search-replace-dialog">
|
||||||
<div data-type="main">
|
<div data-type="main">
|
||||||
<div data-type="content"></div>
|
<div data-type="content"></div>
|
||||||
<div data-type="actions">
|
<div data-type="actions">
|
||||||
<a data-action="case" i18n="title:searchCaseSensitive" tabindex="0">Aa</a>
|
<a data-action="case" i18n-title="searchCaseSensitive" href="#" tabindex="0">Aa</a>
|
||||||
<a data-action="prev" i18n="title:genericPrevious" data-hotkey-tooltip="findPrev" tabindex="0">
|
<a data-action="prev" i18n-title="genericPrevious" href="#" data-hotkey-tooltip="findPrev" tabindex="0">
|
||||||
<svg class="svg-icon" style="transform: rotate(180deg)"><use xlink:href="#svg-icon-v"/></svg>
|
<svg class="svg-icon" style="transform: rotate(180deg)"><use xlink:href="#svg-icon-v"/></svg>
|
||||||
</a>
|
</a>
|
||||||
<a data-action="next" i18n="title:genericNext" data-hotkey-tooltip="findNext" tabindex="0">
|
<a data-action="next" i18n-title="genericNext" href="#" data-hotkey-tooltip="findNext" tabindex="0">
|
||||||
<svg class="svg-icon"><use xlink:href="#svg-icon-v"/></svg>
|
<svg class="svg-icon"><use xlink:href="#svg-icon-v"/></svg>
|
||||||
</a>
|
</a>
|
||||||
<a data-action="close" i18n="title:confirmClose" data-hotkey-tooltip="=Esc" tabindex="0">
|
<a data-action="close" i18n-title="confirmClose" href="#" data-hotkey-tooltip="=Esc" tabindex="0">
|
||||||
<svg class="svg-icon dismiss"><use xlink:href="#svg-icon-close"/></svg>
|
<svg class="svg-icon dismiss"><use xlink:href="#svg-icon-close"/></svg>
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div data-type="status">
|
<div data-type="status">
|
||||||
<div class="CodeMirror-search-hint" i18n-text="searchRegexp"></div>
|
<div class="CodeMirror-search-hint" i18n-text="searchRegexp"></div>
|
||||||
<div data-type="tally" i18n="title:searchNumberOfResults"></div>
|
<div data-type="tally" i18n-title="searchNumberOfResults"></div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<template data-id="clearSearch">
|
<template data-id="clearSearch">
|
||||||
<div data-type="hover" i18n="title:confirmDelete">
|
<div data-type="hover" i18n-title="confirmDelete">
|
||||||
<svg data-action="clear" class="svg-icon"><use xlink:href="#svg-icon-close"></use></svg>
|
<svg data-action="clear" class="svg-icon"><use xlink:href="#svg-icon-close"></use></svg>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
@ -156,7 +196,7 @@
|
||||||
<div data-type="content">
|
<div data-type="content">
|
||||||
<div data-type="input-wrapper">
|
<div data-type="input-wrapper">
|
||||||
<textarea class="CodeMirror-search-field" rows="1" spellcheck="false" required
|
<textarea class="CodeMirror-search-field" rows="1" spellcheck="false" required
|
||||||
i18n="placeholder:search"></textarea>
|
i18n-placeholder="search"></textarea>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
@ -165,36 +205,36 @@
|
||||||
<div data-type="content">
|
<div data-type="content">
|
||||||
<div data-type="input-wrapper">
|
<div data-type="input-wrapper">
|
||||||
<textarea data-type="replace-from"
|
<textarea data-type="replace-from"
|
||||||
i18n="placeholder:replace"
|
i18n-placeholder="replace"
|
||||||
class="CodeMirror-search-field" rows="1" required
|
class="CodeMirror-search-field" rows="1" required
|
||||||
spellcheck="false"></textarea>
|
spellcheck="false"></textarea>
|
||||||
</div>
|
</div>
|
||||||
<div data-type="input-wrapper">
|
<div data-type="input-wrapper">
|
||||||
<textarea data-type="replace-to"
|
<textarea data-type="replace-to"
|
||||||
i18n="placeholder:replaceWith"
|
i18n-placeholder="replaceWith"
|
||||||
class="CodeMirror-search-field" rows="1" required
|
class="CodeMirror-search-field" rows="1" required
|
||||||
spellcheck="false"></textarea>
|
spellcheck="false"></textarea>
|
||||||
</div>
|
</div>
|
||||||
<button data-action="replace" i18n="replace" disabled></button>
|
<button data-action="replace" i18n-text="replace" disabled></button>
|
||||||
<button data-action="replaceAll" i18n="replaceAll" disabled></button>
|
<button data-action="replaceAll" i18n-text="replaceAll" disabled></button>
|
||||||
<button data-action="undo" i18n="undo" disabled></button>
|
<button data-action="undo" i18n-text="undo" disabled></button>
|
||||||
<!--
|
<!--
|
||||||
Using a separate set of buttons because
|
Using a separate set of buttons because
|
||||||
1. FF can display tooltips only when specified on the <button>, ignores the nested <title> in <svg>
|
1. FF can display tooltips only when specified on the <button>, ignores the nested <title> in <svg>
|
||||||
2. the icon doesn't fill the entire button area so tooltips aren't shown when the edges are hovered
|
2. the icon doesn't fill the entire button area so tooltips aren't shown when the edges are hovered
|
||||||
-->
|
-->
|
||||||
<button class="hidden" data-action="replace" i18n="title:replace" disabled>
|
<button class="hidden" data-action="replace" i18n-title="replace" disabled>
|
||||||
<svg class="svg-icon" viewBox="0 0 20 20">
|
<svg class="svg-icon" viewBox="0 0 20 20">
|
||||||
<polygon points="15.83 4.75 8.76 11.82 5.2 8.26 3.51 9.95 8.76 15.19 17.52 6.43 15.83 4.75"/>
|
<polygon points="15.83 4.75 8.76 11.82 5.2 8.26 3.51 9.95 8.76 15.19 17.52 6.43 15.83 4.75"/>
|
||||||
</svg>
|
</svg>
|
||||||
</button>
|
</button>
|
||||||
<button class="hidden" data-action="replaceAll" i18n="title:replaceAll" disabled>
|
<button class="hidden" data-action="replaceAll" i18n-title="replaceAll" disabled>
|
||||||
<svg class="svg-icon" viewBox="0 0 20 20">
|
<svg class="svg-icon" viewBox="0 0 20 20">
|
||||||
<polygon points="15.8,1.8 8.8,8.8 5.2,5.3 3.5,6.9 8.8,12.2 17.5,3.4 "/>
|
<polygon points="15.8,1.8 8.8,8.8 5.2,5.3 3.5,6.9 8.8,12.2 17.5,3.4 "/>
|
||||||
<polygon points="15.8,7.8 8.8,14.8 5.2,11.3 3.5,12.9 8.8,18.2 17.5,9.4 "/>
|
<polygon points="15.8,7.8 8.8,14.8 5.2,11.3 3.5,12.9 8.8,18.2 17.5,9.4 "/>
|
||||||
</svg>
|
</svg>
|
||||||
</button>
|
</button>
|
||||||
<button class="hidden" data-action="undo" i18n="title:undo" disabled>
|
<button class="hidden" data-action="undo" i18n-title="undo" disabled>
|
||||||
<svg class="svg-icon" viewBox="0 0 20 20">
|
<svg class="svg-icon" viewBox="0 0 20 20">
|
||||||
<path d="M11.3,5.5H8.7V1.4L1.9,6.5l6.8,5.1V7.5h2.6c1.8,0,3.2,1.4,3.2,3.2s-1.4,3.2-3.2,3.2H7.8v2h3.5c2.9,0,5.2-2.3,5.2-5.2S14.2,5.5,11.3,5.5z"/>
|
<path d="M11.3,5.5H8.7V1.4L1.9,6.5l6.8,5.1V7.5h2.6c1.8,0,3.2,1.4,3.2,3.2s-1.4,3.2-3.2,3.2H7.8v2h3.5c2.9,0,5.2-2.3,5.2-5.2S14.2,5.5,11.3,5.5z"/>
|
||||||
</svg>
|
</svg>
|
||||||
|
@ -203,7 +243,7 @@
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<template data-id="jumpToLine">
|
<template data-id="jumpToLine">
|
||||||
<span i18n="editGotoLine">: <input class="CodeMirror-jump-field" type="text"></span>
|
<span i18n-text="editGotoLine">: <input class="CodeMirror-jump-field" type="text"></span>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<template data-id="regexpTestPartial">
|
<template data-id="regexpTestPartial">
|
||||||
|
@ -211,15 +251,15 @@
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<template data-id="resizeGrip">
|
<template data-id="resizeGrip">
|
||||||
<div class="resize-grip" i18n="title:cm_resizeGripHint"></div>
|
<div class="resize-grip" i18n-title="cm_resizeGripHint"></div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<template data-id="keymapHelp">
|
<template data-id="keymapHelp">
|
||||||
<table class="keymap-list">
|
<table class="keymap-list">
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
<th><input i18n="placeholder:helpKeyMapHotkey" type="search"></th>
|
<th><input i18n-placeholder="helpKeyMapHotkey" type="search" class="can-close-on-esc"></th>
|
||||||
<th><input i18n="placeholder:helpKeyMapCommand" type="search"></th>
|
<th><input i18n-placeholder="helpKeyMapCommand" type="search" class="can-close-on-esc" spellcheck="false"></th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
|
@ -230,225 +270,179 @@
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
</template>
|
</template>
|
||||||
|
</head>
|
||||||
|
|
||||||
<link href="vendor/codemirror/lib/codemirror.css" rel="stylesheet">
|
<body id="stylus-edit" class="truegray-alpha-2">
|
||||||
<link href="vendor/codemirror/addon/dialog/dialog.css" rel="stylesheet">
|
|
||||||
<link href="vendor/codemirror/addon/fold/foldgutter.css" rel="stylesheet">
|
|
||||||
<link href="vendor/codemirror/addon/hint/show-hint.css" rel="stylesheet">
|
|
||||||
<link href="vendor/codemirror/addon/lint/lint.css" rel="stylesheet">
|
|
||||||
<link href="vendor/codemirror/addon/search/matchesonscrollbar.css" rel="stylesheet">
|
|
||||||
<link href="js/color/color-picker.css" rel="stylesheet">
|
|
||||||
<link href="edit/codemirror-default.css" rel="stylesheet">
|
|
||||||
|
|
||||||
<template data-id="body"> <!-- https://crbug.com/1288447 -->
|
|
||||||
<div id="header">
|
<div id="header">
|
||||||
<h1 id="heading" i18n="data-edit:editStyleHeading, data-add:addStyleTitle">
|
<h1 id="heading"> </h1> <!-- nbsp allocates the actual height which prevents page shift -->
|
||||||
<a class="usercss-only"
|
|
||||||
href="https://github.com/openstyles/stylus/wiki/Usercss"
|
|
||||||
i18n="title:externalUsercssDocument" target="_blank">
|
|
||||||
<svg class="svg-icon info"><use xlink:href="#svg-icon-help"/></svg>
|
|
||||||
</a>
|
|
||||||
</h1>
|
|
||||||
<section id="basic-info">
|
<section id="basic-info">
|
||||||
<div id="basic-info-name">
|
<div id="basic-info-name">
|
||||||
<input id="name" class="style-contributor" spellcheck="false">
|
<input id="name" class="style-contributor" spellcheck="false">
|
||||||
<a id="reset-name" i18n="title:customNameResetHint" tabindex="0" hidden>
|
|
||||||
<svg class="svg-icon" viewBox="0 0 20 20">
|
|
||||||
<polygon points="16.2,5.5 14.5,3.8 10,8.3 5.5,3.8 3.8,5.5 8.3,10 3.8,14.5
|
|
||||||
5.5,16.2 10,11.7 14.5,16.2 16.2,14.5 11.7,10 "/>
|
|
||||||
</svg>
|
|
||||||
</a>
|
|
||||||
<a id="url" target="_blank"><svg class="svg-icon"><use xlink:href="#svg-icon-external-link"/></svg></a>
|
<a id="url" target="_blank"><svg class="svg-icon"><use xlink:href="#svg-icon-external-link"/></svg></a>
|
||||||
</div>
|
</div>
|
||||||
<div id="basic-info-enabled">
|
<div id="basic-info-enabled">
|
||||||
<label id="enabled-label"
|
<label id="enabled-label"
|
||||||
i18n="styleEnabledLabel, title:toggleStyle"
|
i18n-text="styleEnabledLabel"
|
||||||
|
i18n-title="toggleStyle"
|
||||||
data-hotkey-tooltip="toggleStyle">
|
data-hotkey-tooltip="toggleStyle">
|
||||||
<input type="checkbox" id="enabled" class="style-contributor">
|
<input type="checkbox" id="enabled" class="style-contributor">
|
||||||
<svg class="svg-icon checked"><use xlink:href="#svg-icon-checked"/></svg>
|
<svg class="svg-icon checked"><use xlink:href="#svg-icon-checked"/></svg>
|
||||||
</label>
|
</label>
|
||||||
<label id="preview-label" i18n="previewLabel, title:previewTooltip">
|
<label id="preview-label" i18n-text="previewLabel" i18n-title="previewTooltip" class="hidden">
|
||||||
<input type="checkbox" id="editor.livePreview">
|
<input type="checkbox" id="editor.livePreview">
|
||||||
<svg class="svg-icon checked"><use xlink:href="#svg-icon-checked"/></svg>
|
<svg class="svg-icon checked"><use xlink:href="#svg-icon-checked"/></svg>
|
||||||
</label>
|
</label>
|
||||||
<label id="disableAll-label" i18n="data-on:disableAllStyles, data-off:disableAllStylesOff">
|
<span id="preview-errors" class="hidden">!</span>
|
||||||
<input id="disableAll" type="checkbox">
|
|
||||||
<svg class="svg-icon checked"><use xlink:href="#svg-icon-checked"/></svg>
|
|
||||||
</label>
|
|
||||||
<span id="preview-errors" hidden>!</span>
|
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
<section id="actions">
|
<section id="actions">
|
||||||
<div class="buttons">
|
<div>
|
||||||
<div class="split-btn">
|
<button id="save-button" i18n-text="styleSaveLabel" data-hotkey-tooltip="save"></button>
|
||||||
<button id="save-button" i18n="styleSaveLabel" data-hotkey-tooltip="save" disabled></button
|
<button id="beautify" i18n-text="styleBeautify"></button>
|
||||||
><button class="split-btn-pedal usercss-only" i18n="menu-tpl:saveAsTemplate"></button>
|
<a href="manage.html" tabindex="-1"><button id="cancel-button" i18n-text="styleCancelEditLabel"></button></a>
|
||||||
</div>
|
|
||||||
<button id="beautify" i18n="styleBeautify"></button>
|
|
||||||
<button id="style-settings-btn" i18n="settings"></button>
|
|
||||||
<button id="cancel-button" i18n="title:styleCancelEditLabel">↩</button>
|
|
||||||
</div>
|
</div>
|
||||||
<div id="mozilla-format-buttons" class="buttons sectioned-only">
|
<div id="mozilla-format-container">
|
||||||
<button id="from-mozilla" i18n="importLabel"></button>
|
<h2 id="mozilla-format-heading" i18n-text="styleMozillaFormatHeading">
|
||||||
<button id="to-mozilla" i18n="exportLabel"></button>
|
<a id="to-mozilla-help" class="svg-inline-wrapper" href="#" tabindex="0">
|
||||||
<a id="to-mozilla-help" class="svg-inline-wrapper" tabindex="0"
|
<svg class="svg-icon info"><use xlink:href="#svg-icon-help"/></svg>
|
||||||
i18n="title:styleMozillaFormatHeading">
|
</a>
|
||||||
<svg class="svg-icon info"><use xlink:href="#svg-icon-help"/></svg>
|
</h2>
|
||||||
</a>
|
<div id="mozilla-format-buttons">
|
||||||
|
<button id="from-mozilla" i18n-text="importLabel"></button>
|
||||||
|
<button id="to-mozilla" i18n-text="exportLabel"></button>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
<div id="details-wrapper">
|
<details id="options" data-pref="editor.options.expanded">
|
||||||
<details id="options" data-pref="editor.options.expanded" class="ignore-pref-if-compact">
|
<summary><h2 id="options-heading" i18n-text="optionsHeading"></h2></summary>
|
||||||
<summary><h2 id="options-heading" i18n="editorSettings"></h2></summary>
|
<div id="options-wrapper">
|
||||||
<div id="options-wrapper">
|
<div class="options-column">
|
||||||
<div class="options-column">
|
<div class="option">
|
||||||
<div class="option">
|
<label id="lineWrapping-label" i18n-text="cm_lineWrapping">
|
||||||
<label id="lineWrapping-label" i18n="cm_lineWrapping">
|
<input id="editor.lineWrapping" type="checkbox">
|
||||||
<input id="editor.lineWrapping" type="checkbox">
|
<svg class="svg-icon checked"><use xlink:href="#svg-icon-checked"/></svg>
|
||||||
<svg class="svg-icon checked"><use xlink:href="#svg-icon-checked"/></svg>
|
</label>
|
||||||
</label>
|
</div>
|
||||||
|
<div class="option">
|
||||||
|
<label id="smartIndent-label" i18n-text="cm_smartIndent">
|
||||||
|
<input id="editor.smartIndent" type="checkbox">
|
||||||
|
<svg class="svg-icon checked"><use xlink:href="#svg-icon-checked"/></svg>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
<div class="option">
|
||||||
|
<label id="indentWithTabs-label" i18n-text="cm_indentWithTabs">
|
||||||
|
<input id="editor.indentWithTabs" type="checkbox">
|
||||||
|
<svg class="svg-icon checked"><use xlink:href="#svg-icon-checked"/></svg>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
<div class="option">
|
||||||
|
<label i18n-text="cm_autoCloseBrackets" i18n-title="cm_autoCloseBracketsTooltip">
|
||||||
|
<input id="editor.autoCloseBrackets" type="checkbox">
|
||||||
|
<svg class="svg-icon checked"><use xlink:href="#svg-icon-checked"/></svg>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
<div class="option">
|
||||||
|
<label i18n-text="cm_autocompleteOnTyping">
|
||||||
|
<input id="editor.autocompleteOnTyping" type="checkbox">
|
||||||
|
<svg class="svg-icon checked"><use xlink:href="#svg-icon-checked"/></svg>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
<div class="option">
|
||||||
|
<label i18n-text="cm_selectByTokens"
|
||||||
|
i18n-title="cm_selectByTokensTooltip">
|
||||||
|
<input id="editor.selectByTokens" type="checkbox">
|
||||||
|
<svg class="svg-icon checked"><use xlink:href="#svg-icon-checked"/></svg>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
<div class="option">
|
||||||
|
<label i18n-text="cm_colorpicker">
|
||||||
|
<input id="editor.colorpicker" type="checkbox">
|
||||||
|
<svg class="svg-icon checked"><use xlink:href="#svg-icon-checked"/></svg>
|
||||||
|
</label>
|
||||||
|
<a id="colorpicker-settings" href="#" class="svg-inline-wrapper" i18n-title="shortcutsNote" tabindex="0">
|
||||||
|
<svg class="svg-icon settings"><use xlink:href="#svg-icon-settings"/></svg>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
<div class="option usercss-only">
|
||||||
|
<label i18n-text="appliesLineWidgetLabel" i18n-title="appliesLineWidgetWarning">
|
||||||
|
<input id="editor.appliesToLineWidget" type="checkbox">
|
||||||
|
<svg class="svg-icon checked"><use xlink:href="#svg-icon-checked"/></svg>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="options-column">
|
||||||
|
<div class="option aligned">
|
||||||
|
<label id="tabSize-label" for="editor.tabSize" i18n-text="cm_tabSize"></label>
|
||||||
|
<input id="editor.tabSize" type="number" min="0">
|
||||||
|
</div>
|
||||||
|
<div class="option aligned">
|
||||||
|
<label id="keyMap-label" for="editor.keyMap" i18n-text="cm_keyMap"></label>
|
||||||
|
<div class="select-resizer">
|
||||||
|
<select id="editor.keyMap"></select>
|
||||||
|
<svg class="svg-icon select-arrow"><use xlink:href="#svg-icon-select-arrow"/></svg>
|
||||||
</div>
|
</div>
|
||||||
<div class="option">
|
<a id="keyMap-help" href="#" class="svg-inline-wrapper" tabindex="0">
|
||||||
<label id="smartIndent-label" i18n="cm_smartIndent">
|
<svg class="svg-icon info"><use xlink:href="#svg-icon-help"/></svg>
|
||||||
<input id="editor.smartIndent" type="checkbox">
|
</a>
|
||||||
<svg class="svg-icon checked"><use xlink:href="#svg-icon-checked"/></svg>
|
</div>
|
||||||
</label>
|
<div class="option aligned">
|
||||||
</div>
|
<label id="theme-label" for="editor.theme" i18n-text="cm_theme"></label>
|
||||||
<div class="option">
|
<div class="select-resizer">
|
||||||
<label id="indentWithTabs-label" i18n="cm_indentWithTabs">
|
<select id="editor.theme"></select>
|
||||||
<input id="editor.indentWithTabs" type="checkbox">
|
<svg class="svg-icon select-arrow"><use xlink:href="#svg-icon-select-arrow"/></svg>
|
||||||
<svg class="svg-icon checked"><use xlink:href="#svg-icon-checked"/></svg>
|
|
||||||
</label>
|
|
||||||
</div>
|
|
||||||
<div class="option">
|
|
||||||
<label i18n="cm_autoCloseBrackets, title:cm_autoCloseBracketsTooltip">
|
|
||||||
<input id="editor.autoCloseBrackets" type="checkbox">
|
|
||||||
<svg class="svg-icon checked"><use xlink:href="#svg-icon-checked"/></svg>
|
|
||||||
</label>
|
|
||||||
</div>
|
|
||||||
<div class="option">
|
|
||||||
<label i18n="cm_autocompleteOnTyping">
|
|
||||||
<input id="editor.autocompleteOnTyping" type="checkbox">
|
|
||||||
<svg class="svg-icon checked"><use xlink:href="#svg-icon-checked"/></svg>
|
|
||||||
</label>
|
|
||||||
</div>
|
|
||||||
<div class="option">
|
|
||||||
<label i18n="cm_selectByTokens, title:cm_selectByTokensTooltip">
|
|
||||||
<input id="editor.selectByTokens" type="checkbox">
|
|
||||||
<svg class="svg-icon checked"><use xlink:href="#svg-icon-checked"/></svg>
|
|
||||||
</label>
|
|
||||||
</div>
|
|
||||||
<div class="option sectioned-only">
|
|
||||||
<label i18n="cm_arrowKeysTraverse">
|
|
||||||
<input id="editor.arrowKeysTraverse" type="checkbox">
|
|
||||||
<svg class="svg-icon checked"><use xlink:href="#svg-icon-checked"/></svg>
|
|
||||||
</label>
|
|
||||||
</div>
|
|
||||||
<div class="option">
|
|
||||||
<label i18n="cm_colorpicker">
|
|
||||||
<input id="editor.colorpicker" type="checkbox">
|
|
||||||
<svg class="svg-icon checked"><use xlink:href="#svg-icon-checked"/></svg>
|
|
||||||
</label>
|
|
||||||
<a id="colorpicker-settings" class="svg-inline-wrapper" i18n="title:shortcutsNote" tabindex="0">
|
|
||||||
<svg class="svg-icon settings"><use xlink:href="#svg-icon-config"/></svg>
|
|
||||||
</a>
|
|
||||||
</div>
|
|
||||||
<div class="option usercss-only">
|
|
||||||
<label i18n="appliesLineWidgetLabel, title:appliesLineWidgetWarning">
|
|
||||||
<input id="editor.appliesToLineWidget" type="checkbox">
|
|
||||||
<svg class="svg-icon checked"><use xlink:href="#svg-icon-checked"/></svg>
|
|
||||||
</label>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="options-column">
|
<div class="option aligned">
|
||||||
<div class="option aligned">
|
<label id="highlight-label" for="editor.matchHighlight" i18n-text="cm_matchHighlight"></label>
|
||||||
<label id="tabSize-label" for="editor.tabSize" i18n="cm_tabSize"></label>
|
<div class="select-resizer">
|
||||||
<input id="editor.tabSize" type="number" min="0">
|
<select id="editor.matchHighlight">
|
||||||
|
<option i18n-text="cm_matchHighlightToken" value="token">
|
||||||
|
<option i18n-text="cm_matchHighlightSelection" value="selection">
|
||||||
|
<option i18n-text="genericDisabledLabel" value="">
|
||||||
|
</select>
|
||||||
|
<svg class="svg-icon select-arrow"><use xlink:href="#svg-icon-select-arrow"/></svg>
|
||||||
</div>
|
</div>
|
||||||
<div class="option aligned">
|
</div>
|
||||||
<label id="keyMap-label" for="editor.keyMap" i18n="cm_keyMap"></label>
|
<div class="option aligned">
|
||||||
|
<label id="linter-label" for="editor.linter" i18n-text="cm_linter"></label>
|
||||||
<div class="select-resizer">
|
<div class="select-resizer">
|
||||||
<select id="editor.keyMap"></select>
|
<select id="editor.linter">
|
||||||
<svg class="svg-icon select-arrow"><use xlink:href="#svg-icon-select-arrow"/></svg>
|
<option value="csslint" selected>CSSLint</option>
|
||||||
</div>
|
<option value="stylelint">Stylelint</option>
|
||||||
<a id="keyMap-help" class="svg-inline-wrapper" tabindex="0">
|
<option value="" i18n-text="genericDisabledLabel"></option>
|
||||||
<svg class="svg-icon info"><use xlink:href="#svg-icon-help"/></svg>
|
|
||||||
</a>
|
|
||||||
</div>
|
|
||||||
<div class="option aligned">
|
|
||||||
<label id="theme-label" for="editor.theme" i18n="cm_theme"></label>
|
|
||||||
<div class="select-resizer">
|
|
||||||
<select id="editor.theme"></select>
|
|
||||||
<svg class="svg-icon select-arrow"><use xlink:href="#svg-icon-select-arrow"/></svg>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="option aligned">
|
|
||||||
<label id="highlight-label" for="editor.matchHighlight" i18n="cm_matchHighlight"></label>
|
|
||||||
<div class="select-resizer">
|
|
||||||
<select id="editor.matchHighlight">
|
|
||||||
<option i18n="cm_matchHighlightToken" value="token">
|
|
||||||
<option i18n="cm_matchHighlightSelection" value="selection">
|
|
||||||
<option i18n="genericDisabledLabel" value="">
|
|
||||||
</select>
|
</select>
|
||||||
<svg class="svg-icon select-arrow"><use xlink:href="#svg-icon-select-arrow"/></svg>
|
<svg class="svg-icon select-arrow"><use xlink:href="#svg-icon-select-arrow"/></svg>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
<a id="linter-settings" href="#" class="svg-inline-wrapper" i18n-title="linterConfigTooltip" tabindex="0">
|
||||||
<div class="option aligned">
|
<svg class="svg-icon settings"><use xlink:href="#svg-icon-settings"/></svg>
|
||||||
<label id="linter-label" for="editor.linter" i18n="cm_linter"></label>
|
|
||||||
<div class="select-resizer">
|
|
||||||
<select id="editor.linter">
|
|
||||||
<option value="csslint" selected>CSSLint</option>
|
|
||||||
<option value="stylelint">Stylelint</option>
|
|
||||||
<option value="" i18n="genericDisabledLabel"></option>
|
|
||||||
</select>
|
|
||||||
<svg class="svg-icon select-arrow"><use xlink:href="#svg-icon-select-arrow"/></svg>
|
|
||||||
</div>
|
|
||||||
<a id="linter-settings" class="svg-inline-wrapper" i18n="title:linterConfigTooltip" tabindex="0">
|
|
||||||
<svg class="svg-icon settings"><use xlink:href="#svg-icon-config"/></svg>
|
|
||||||
</a>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</details>
|
|
||||||
<details id="publish" data-pref="editor.publish.expanded" class="ignore-pref-if-compact">
|
|
||||||
<summary><h2 i18n="publish"></h2></summary>
|
|
||||||
<div>
|
|
||||||
<a id="usw-url" href="https://userstyles.world" target="_blank"> </a>
|
|
||||||
<div id="usw-link-info">
|
|
||||||
<dl><dt i18n="styleName"></dt><dd data-usw="name"></dd></dl>
|
|
||||||
<dl><dt i18n="genericDescription"></dt><dd data-usw="description"></dd></dl>
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<button id="usw-publish-style"
|
|
||||||
i18n="data-publish:publishStyle, data-push:publishPush"></button>
|
|
||||||
<button id="usw-disconnect" i18n="optionsSyncDisconnect"></button>
|
|
||||||
<span id="usw-progress"></span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</details>
|
|
||||||
<details id="sections-list" data-pref="editor.toc.expanded" class="ignore-pref-if-compact">
|
|
||||||
<summary><h2 i18n="sections"></h2></summary>
|
|
||||||
<ol id="toc"></ol>
|
|
||||||
</details>
|
|
||||||
<details id="lint" data-pref="editor.lint.expanded" class="ignore-pref-if-compact" hidden>
|
|
||||||
<summary>
|
|
||||||
<h2><span i18n="linterIssues"></span><span id="issue-count"></span>
|
|
||||||
<a id="lint-help" class="svg-inline-wrapper intercepts-click" tabindex="0">
|
|
||||||
<svg class="svg-icon info"><use xlink:href="#svg-icon-help"/></svg>
|
|
||||||
</a>
|
</a>
|
||||||
</h2>
|
</div>
|
||||||
</summary>
|
</div>
|
||||||
<div class="lint-report-container"></div>
|
</div>
|
||||||
</details>
|
</details>
|
||||||
</div>
|
<details id="lint" class="hidden" data-pref="editor.lint.expanded">
|
||||||
<div id="header-resizer" i18n="title:headerResizerHint"></div>
|
<summary>
|
||||||
|
<h2 i18n-text="linterIssues">: <span id="issue-count"></span>
|
||||||
|
<a id="lint-help" href="#" class="svg-inline-wrapper intercepts-click" tabindex="0">
|
||||||
|
<svg class="svg-icon info"><use xlink:href="#svg-icon-help"/></svg>
|
||||||
|
</a>
|
||||||
|
</h2>
|
||||||
|
</summary>
|
||||||
|
<div class="lint-report-container"></div>
|
||||||
|
</details>
|
||||||
<div id="footer" class="hidden">
|
<div id="footer" class="hidden">
|
||||||
<a href="https://github.com/openstyles/stylus/wiki/Usercss"
|
<a href="https://github.com/openstyles/stylus/wiki/Usercss"
|
||||||
i18n="externalUsercssDocument"
|
i18n-text="externalUsercssDocument"
|
||||||
target="_blank"></a>
|
target="_blank"></a>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<section id="sections"></section>
|
<section id="sections">
|
||||||
|
<h2><span id="sections-heading" i18n-text="styleSectionsTitle"></span>
|
||||||
|
<a id="sections-help" href="#" class="svg-inline-wrapper" tabindex="0">
|
||||||
|
<svg class="svg-icon info"><use xlink:href="#svg-icon-help"/></svg>
|
||||||
|
</a>
|
||||||
|
</h2>
|
||||||
|
</section>
|
||||||
<div id="help-popup">
|
<div id="help-popup">
|
||||||
<div class="title"></div><svg id="sections-help" class="svg-icon dismiss"><use xlink:href="#svg-icon-close"/></svg>
|
<div class="title"></div><svg id="sections-help" class="svg-icon dismiss"><use xlink:href="#svg-icon-close"/></svg>
|
||||||
<div class="contents"></div>
|
<div class="contents"></div>
|
||||||
|
@ -460,10 +454,8 @@
|
||||||
<path d="M0 0v8h8v-2h-1v1h-6v-6h1v-1h-2zm4 0l1.5 1.5-2.5 2.5 1 1 2.5-2.5 1.5 1.5v-4h-4z"></path>
|
<path d="M0 0v8h8v-2h-1v1h-6v-6h1v-1h-2zm4 0l1.5 1.5-2.5 2.5 1 1 2.5-2.5 1.5 1.5v-4h-4z"></path>
|
||||||
</symbol>
|
</symbol>
|
||||||
|
|
||||||
<symbol id="svg-icon-help" viewBox="0 0 14 16" i18n="alt:helpAlt">
|
<symbol id="svg-icon-help" viewBox="0 0 14 16" i18n-alt="helpAlt">
|
||||||
<circle cx="7" cy="5" r="1"/>
|
<path fill-rule="evenodd" d="M6.3 5.69a.942.942 0 0 1-.28-.7c0-.28.09-.52.28-.7.19-.18.42-.28.7-.28.28 0 .52.09.7.28.18.19.28.42.28.7 0 .28-.09.52-.28.7a1 1 0 0 1-.7.3c-.28 0-.52-.11-.7-.3zM8 7.99c-.02-.25-.11-.48-.31-.69-.2-.19-.42-.3-.69-.31H6c-.27.02-.48.13-.69.31-.2.2-.3.44-.31.69h1v3c.02.27.11.5.31.69.2.2.42.31.69.31h1c.27 0 .48-.11.69-.31.2-.19.3-.42.31-.69H8V7.98v.01zM7 2.3c-3.14 0-5.7 2.54-5.7 5.68 0 3.14 2.56 5.7 5.7 5.7s5.7-2.55 5.7-5.7c0-3.15-2.56-5.69-5.7-5.69v.01zM7 .98c3.86 0 7 3.14 7 7s-3.14 7-7 7-7-3.12-7-7 3.14-7 7-7z"></path>
|
||||||
<path d="M8,8c0-0.5-0.5-1-1-1H6C5.5,7,5,7.4,5,8h1v3c0,0.5,0.5,1,1,1h1c0.5,0,1-0.4,1-1H8V8z"/>
|
|
||||||
<path d="M7,1c3.9,0,7,3.1,7,7s-3.1,7-7,7s-7-3.1-7-7S3.1,1,7,1z M7,2.3C3.9,2.3,1.3,4.9,1.3,8s2.6,5.7,5.7,5.7s5.7-2.6,5.7-5.7S10.1,2.3,7,2.3C7,2.3,7,2.3,7,2.3z"/>
|
|
||||||
</symbol>
|
</symbol>
|
||||||
|
|
||||||
<symbol id="svg-icon-close" viewBox="0 0 12 16">
|
<symbol id="svg-icon-close" viewBox="0 0 12 16">
|
||||||
|
@ -474,8 +466,8 @@
|
||||||
<path d="M8,11.5L2.8,6.3l1.5-1.5L8,8.6l3.7-3.7l1.5,1.5L8,11.5z"/>
|
<path d="M8,11.5L2.8,6.3l1.5-1.5L8,8.6l3.7-3.7l1.5,1.5L8,11.5z"/>
|
||||||
</symbol>
|
</symbol>
|
||||||
|
|
||||||
<symbol id="svg-icon-config" viewBox="0 0 16 16">
|
<symbol id="svg-icon-settings" viewBox="0 0 16 16">
|
||||||
<path d="M13.3,12.8l1.5-2.6l-2.2-1.5c0-0.2,0.1-0.5,0.1-0.7c0-0.2,0-0.5-0.1-0.7l2.2-1.5l-1.5-2.6l-2.4,1.2 c-0.4-0.3-0.8-0.5-1.2-0.7L9.5,1h-3L6.3,3.7C5.9,3.8,5.5,4.1,5.1,4.4L2.7,3.2L1.2,5.8l2.2,1.5c0,0.2-0.1,0.5-0.1,0.7 c0,0.2,0,0.5,0.1,0.7l-2.2,1.5l1.5,2.6l2.4-1.2c0.4,0.3,0.8,0.5,1.2,0.7L6.5,15h3l0.2-2.7c0.4-0.2,0.8-0.4,1.2-0.7L13.3,12.8z M8,10.3c-1.3,0-2.3-1-2.3-2.3c0-1.3,1-2.3,2.3-2.3c1.3,0,2.3,1,2.3,2.3C10.3,9.3,9.3,10.3,8,10.3z"/>
|
<path d="M8,0C7.6,0,7.3,0,6.9,0.1v2.2C6.1,2.5,5.4,2.8,4.8,3.2L3.2,1.6c-0.6,0.4-1.1,1-1.6,1.6l1.6,1.6C2.8,5.4,2.5,6.1,2.3,6.9H0.1C0,7.3,0,7.6,0,8c0,0.4,0,0.7,0.1,1.1h2.2c0.1,0.8,0.4,1.5,0.9,2.1l-1.6,1.6c0.4,0.6,1,1.1,1.6,1.6l1.6-1.6c0.6,0.4,1.4,0.7,2.1,0.9v2.2C7.3,16,7.6,16,8,16c0.4,0,0.7,0,1.1-0.1v-2.2c0.8-0.1,1.5-0.4,2.1-0.9l1.6,1.6c0.6-0.4,1.1-1,1.6-1.6l-1.6-1.6c0.4-0.6,0.7-1.4,0.9-2.1h2.2C16,8.7,16,8.4,16,8c0-0.4,0-0.7-0.1-1.1h-2.2c-0.1-0.8-0.4-1.5-0.9-2.1l1.6-1.6c-0.4-0.6-1-1.1-1.6-1.6l-1.6,1.6c-0.6-0.4-1.4-0.7-2.1-0.9V0.1C8.7,0,8.4,0,8,0z M8,4.3c2.1,0,3.7,1.7,3.7,3.7c0,0,0,0,0,0c0,2.1-1.7,3.7-3.7,3.7c0,0,0,0,0,0c-2.1,0-3.7-1.7-3.7-3.7c0,0,0,0,0,0C4.3,5.9,5.9,4.3,8,4.3C8,4.3,8,4.3,8,4.3z"/>
|
||||||
</symbol>
|
</symbol>
|
||||||
|
|
||||||
<symbol id="svg-icon-select-arrow" viewBox="0 0 1792 1792">
|
<symbol id="svg-icon-select-arrow" viewBox="0 0 1792 1792">
|
||||||
|
@ -487,21 +479,14 @@
|
||||||
</symbol>
|
</symbol>
|
||||||
|
|
||||||
<symbol id="svg-icon-plus" viewBox="0 0 8 8">
|
<symbol id="svg-icon-plus" viewBox="0 0 8 8">
|
||||||
<path d="M3 0v3h-3v2h3v3h2v-3h3v-2h-3v-3h-2z"/>
|
<path fill-rule="evenodd" d="M3 0v3h-3v2h3v3h2v-3h3v-2h-3v-3h-2z"/>
|
||||||
</symbol>
|
</symbol>
|
||||||
|
|
||||||
<symbol id="svg-icon-minus" viewBox="0 0 8 8">
|
<symbol id="svg-icon-minus" viewBox="0 0 8 8">
|
||||||
<path d="M0 3v2h8v-2h-8z"/>
|
<path fill-rule="evenodd" d="M0 3v2h8v-2h-8z"/>
|
||||||
</symbol>
|
</symbol>
|
||||||
|
|
||||||
</svg>
|
</svg>
|
||||||
</template>
|
|
||||||
|
|
||||||
<link href="edit/edit.css" rel="stylesheet">
|
|
||||||
<script src="js/dark-themer.js"></script> <!-- must be last in HEAD to avoid FOUC -->
|
|
||||||
</head>
|
|
||||||
|
|
||||||
<body id="stylus-edit">
|
|
||||||
<script src="edit/edit.js"></script>
|
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|
545
edit/applies-to-line-widget.js
Normal file
545
edit/applies-to-line-widget.js
Normal file
|
@ -0,0 +1,545 @@
|
||||||
|
/* global regExpTester debounce messageBox CodeMirror template colorMimicry */
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
function createAppliesToLineWidget(cm) {
|
||||||
|
const THROTTLE_DELAY = 400;
|
||||||
|
let TPL, EVENTS, CLICK_ROUTE;
|
||||||
|
let widgets = [];
|
||||||
|
let fromLine, toLine, actualStyle;
|
||||||
|
let initialized = false;
|
||||||
|
return {toggle};
|
||||||
|
|
||||||
|
function toggle(newState = !initialized) {
|
||||||
|
newState = Boolean(newState);
|
||||||
|
if (newState !== initialized) {
|
||||||
|
if (newState) {
|
||||||
|
init();
|
||||||
|
} else {
|
||||||
|
uninit();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function init() {
|
||||||
|
initialized = true;
|
||||||
|
|
||||||
|
TPL = {
|
||||||
|
container:
|
||||||
|
$create('div.applies-to', [
|
||||||
|
$create('label', t('appliesLabel')),
|
||||||
|
$create('ul.applies-to-list'),
|
||||||
|
]),
|
||||||
|
listItem: template.appliesTo.cloneNode(true),
|
||||||
|
appliesToEverything:
|
||||||
|
$create('li.applies-to-everything', t('appliesToEverything')),
|
||||||
|
};
|
||||||
|
|
||||||
|
$('.applies-value', TPL.listItem).insertAdjacentElement('afterend',
|
||||||
|
$create('button.test-regexp', t('styleRegexpTestButton')));
|
||||||
|
|
||||||
|
CLICK_ROUTE = {
|
||||||
|
'.test-regexp': showRegExpTester,
|
||||||
|
|
||||||
|
'.remove-applies-to': (item, apply, event) => {
|
||||||
|
event.preventDefault();
|
||||||
|
const applies = item.closest('.applies-to').__applies;
|
||||||
|
const i = applies.indexOf(apply);
|
||||||
|
let repl;
|
||||||
|
let from;
|
||||||
|
let to;
|
||||||
|
if (applies.length < 2) {
|
||||||
|
messageBox({
|
||||||
|
contents: t('appliesRemoveError'),
|
||||||
|
buttons: [t('confirmClose')]
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (i === 0) {
|
||||||
|
from = apply.mark.find().from;
|
||||||
|
to = applies[i + 1].mark.find().from;
|
||||||
|
repl = '';
|
||||||
|
} else if (i === applies.length - 1) {
|
||||||
|
from = applies[i - 1].mark.find().to;
|
||||||
|
to = apply.mark.find().to;
|
||||||
|
repl = '';
|
||||||
|
} else {
|
||||||
|
from = applies[i - 1].mark.find().to;
|
||||||
|
to = applies[i + 1].mark.find().from;
|
||||||
|
repl = ', ';
|
||||||
|
}
|
||||||
|
cm.replaceRange(repl, from, to, 'appliesTo');
|
||||||
|
clearApply(apply);
|
||||||
|
item.remove();
|
||||||
|
applies.splice(i, 1);
|
||||||
|
},
|
||||||
|
|
||||||
|
'.add-applies-to': (item, apply, event) => {
|
||||||
|
event.preventDefault();
|
||||||
|
const applies = item.closest('.applies-to').__applies;
|
||||||
|
const i = applies.indexOf(apply);
|
||||||
|
const pos = apply.mark.find().to;
|
||||||
|
const text = `, ${apply.type.text}("")`;
|
||||||
|
cm.replaceRange(text, pos, pos, 'appliesTo');
|
||||||
|
const newApply = createApply(
|
||||||
|
cm.indexFromPos(pos) + 2,
|
||||||
|
apply.type.text,
|
||||||
|
'',
|
||||||
|
true
|
||||||
|
);
|
||||||
|
setupApplyMarkers(newApply);
|
||||||
|
applies.splice(i + 1, 0, newApply);
|
||||||
|
item.insertAdjacentElement('afterend', buildChildren(applies, newApply));
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
EVENTS = {
|
||||||
|
onchange({target}) {
|
||||||
|
const typeElement = target.closest('.applies-type');
|
||||||
|
if (typeElement) {
|
||||||
|
const item = target.closest('.applies-to-item');
|
||||||
|
const apply = item.__apply;
|
||||||
|
changeItem(item, apply, 'type', typeElement.value);
|
||||||
|
item.dataset.type = apply.type.text;
|
||||||
|
} else {
|
||||||
|
return EVENTS.oninput.apply(this, arguments);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
oninput({target}) {
|
||||||
|
if (target.matches('.applies-value')) {
|
||||||
|
const item = target.closest('.applies-to-item');
|
||||||
|
const apply = item.__apply;
|
||||||
|
changeItem(item, apply, 'value', target.value);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onclick(event) {
|
||||||
|
const {target} = event;
|
||||||
|
for (const selector in CLICK_ROUTE) {
|
||||||
|
const routed = target.closest(selector);
|
||||||
|
if (routed) {
|
||||||
|
const item = routed.closest('.applies-to-item');
|
||||||
|
CLICK_ROUTE[selector].call(routed, item, item.__apply, event);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
actualStyle = $create('style');
|
||||||
|
fromLine = 0;
|
||||||
|
toLine = cm.doc.size;
|
||||||
|
|
||||||
|
cm.on('change', onChange);
|
||||||
|
cm.on('optionChange', onOptionChange);
|
||||||
|
|
||||||
|
chrome.runtime.onMessage.addListener(onRuntimeMessage);
|
||||||
|
|
||||||
|
requestAnimationFrame(updateWidgetStyle);
|
||||||
|
update();
|
||||||
|
}
|
||||||
|
|
||||||
|
function uninit() {
|
||||||
|
initialized = false;
|
||||||
|
|
||||||
|
widgets.forEach(clearWidget);
|
||||||
|
widgets.length = 0;
|
||||||
|
cm.off('change', onChange);
|
||||||
|
cm.off('optionChange', onOptionChange);
|
||||||
|
chrome.runtime.onMessage.removeListener(onRuntimeMessage);
|
||||||
|
actualStyle.remove();
|
||||||
|
}
|
||||||
|
|
||||||
|
function onChange(cm, event) {
|
||||||
|
const {from, to, origin} = event;
|
||||||
|
if (origin === 'appliesTo') {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const lastChanged = CodeMirror.changeEnd(event).line;
|
||||||
|
fromLine = Math.min(fromLine === null ? from.line : fromLine, from.line);
|
||||||
|
toLine = Math.max(toLine === null ? lastChanged : toLine, to.line);
|
||||||
|
if (origin === 'setValue') {
|
||||||
|
update();
|
||||||
|
} else {
|
||||||
|
debounce(update, THROTTLE_DELAY);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function onOptionChange(cm, option) {
|
||||||
|
if (option === 'theme') {
|
||||||
|
updateWidgetStyle();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function onRuntimeMessage(msg) {
|
||||||
|
if (msg.style || msg.styles ||
|
||||||
|
msg.prefs && 'disableAll' in msg.prefs ||
|
||||||
|
msg.method === 'styleDeleted') {
|
||||||
|
requestAnimationFrame(updateWidgetStyle);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function update() {
|
||||||
|
const changed = {fromLine, toLine};
|
||||||
|
fromLine = Math.max(fromLine || 0, cm.display.viewFrom);
|
||||||
|
toLine = Math.min(toLine === null ? cm.doc.size : toLine, cm.display.viewTo || toLine);
|
||||||
|
const visible = {fromLine, toLine};
|
||||||
|
const {curOp} = cm;
|
||||||
|
if (fromLine >= cm.display.viewFrom && toLine <= (cm.display.viewTo || toLine)) {
|
||||||
|
if (!curOp) cm.startOperation();
|
||||||
|
doUpdate();
|
||||||
|
if (!curOp) cm.endOperation();
|
||||||
|
}
|
||||||
|
if (changed.fromLine !== visible.fromLine || changed.toLine !== visible.toLine) {
|
||||||
|
setTimeout(updateInvisible, 0, changed, visible);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function updateInvisible(changed, visible) {
|
||||||
|
let inOp = false;
|
||||||
|
if (changed.fromLine < visible.fromLine) {
|
||||||
|
fromLine = Math.min(fromLine, changed.fromLine);
|
||||||
|
toLine = Math.min(changed.toLine, visible.fromLine);
|
||||||
|
inOp = true;
|
||||||
|
cm.startOperation();
|
||||||
|
doUpdate();
|
||||||
|
}
|
||||||
|
if (changed.toLine > visible.toLine) {
|
||||||
|
fromLine = Math.max(fromLine, changed.toLine);
|
||||||
|
toLine = Math.max(changed.toLine, visible.toLine);
|
||||||
|
if (!inOp) {
|
||||||
|
inOp = true;
|
||||||
|
cm.startOperation();
|
||||||
|
}
|
||||||
|
doUpdate();
|
||||||
|
}
|
||||||
|
if (inOp) {
|
||||||
|
cm.endOperation();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function updateWidgetStyle() {
|
||||||
|
if (prefs.get('editor.theme') !== 'default' &&
|
||||||
|
!tryCatch(() => $('#cm-theme').sheet.cssRules)) {
|
||||||
|
requestAnimationFrame(updateWidgetStyle);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (prefs.get('editor.theme') !== 'default') {
|
||||||
|
const MIN_LUMA = .05;
|
||||||
|
const MIN_LUMA_DIFF = .4;
|
||||||
|
const color = {
|
||||||
|
wrapper: colorMimicry.get(cm.display.wrapper),
|
||||||
|
gutter: colorMimicry.get(cm.display.gutters, {
|
||||||
|
bg: 'backgroundColor',
|
||||||
|
border: 'borderRightColor',
|
||||||
|
}),
|
||||||
|
line: colorMimicry.get('.CodeMirror-linenumber', null, cm.display.lineDiv),
|
||||||
|
comment: colorMimicry.get('span.cm-comment', null, cm.display.lineDiv),
|
||||||
|
};
|
||||||
|
const hasBorder =
|
||||||
|
color.gutter.style.borderRightWidth !== '0px' &&
|
||||||
|
!/transparent|\b0\)/g.test(color.gutter.style.borderRightColor);
|
||||||
|
const diff = {
|
||||||
|
wrapper: Math.abs(color.gutter.bgLuma - color.wrapper.foreLuma),
|
||||||
|
border: hasBorder ? Math.abs(color.gutter.bgLuma - color.gutter.borderLuma) : 0,
|
||||||
|
line: Math.abs(color.gutter.bgLuma - color.line.foreLuma),
|
||||||
|
};
|
||||||
|
const preferLine = diff.line > diff.wrapper || diff.line > MIN_LUMA_DIFF;
|
||||||
|
const fore = preferLine ? color.line.fore : color.wrapper.fore;
|
||||||
|
|
||||||
|
const border = fore.replace(/[\d.]+(?=\))/, MIN_LUMA_DIFF / 2);
|
||||||
|
const borderStyleForced = `1px ${hasBorder ? color.gutter.style.borderRightStyle : 'solid'} ${border}`;
|
||||||
|
|
||||||
|
actualStyle.textContent = `
|
||||||
|
.applies-to {
|
||||||
|
background-color: ${color.gutter.bg};
|
||||||
|
border-top: ${borderStyleForced};
|
||||||
|
border-bottom: ${borderStyleForced};
|
||||||
|
}
|
||||||
|
.applies-to label {
|
||||||
|
color: ${fore};
|
||||||
|
}
|
||||||
|
.applies-to input,
|
||||||
|
.applies-to select {
|
||||||
|
background-color: rgba(255, 255, 255, ${
|
||||||
|
Math.max(MIN_LUMA, Math.pow(Math.max(0, color.gutter.bgLuma - MIN_LUMA * 2), 2)).toFixed(2)
|
||||||
|
});
|
||||||
|
border: ${borderStyleForced};
|
||||||
|
transition: none;
|
||||||
|
color: ${fore};
|
||||||
|
}
|
||||||
|
.applies-to .svg-icon.select-arrow {
|
||||||
|
fill: ${fore} !important;
|
||||||
|
}
|
||||||
|
.applies-to select option {
|
||||||
|
background-color: ${color.gutter.bg};
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
document.documentElement.appendChild(actualStyle);
|
||||||
|
} else if (prefs.get('editor.theme') === 'default') {
|
||||||
|
actualStyle.textContent = '';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function doUpdate() {
|
||||||
|
// find which widgets needs to be update
|
||||||
|
// some widgets (lines) might be deleted
|
||||||
|
widgets = widgets.filter(w => w.line.lineNo() !== null);
|
||||||
|
let i = widgets.findIndex(w => w.line.lineNo() > fromLine) - 1;
|
||||||
|
let j = widgets.findIndex(w => w.line.lineNo() > toLine);
|
||||||
|
if (i === -2) {
|
||||||
|
i = widgets.length - 1;
|
||||||
|
}
|
||||||
|
if (j < 0) {
|
||||||
|
j = widgets.length;
|
||||||
|
}
|
||||||
|
|
||||||
|
// decide search range
|
||||||
|
const fromPos = {line: widgets[i] ? widgets[i].line.lineNo() : 0, ch: 0};
|
||||||
|
const toPos = {line: widgets[j] ? widgets[j].line.lineNo() : toLine + 1, ch: 0};
|
||||||
|
|
||||||
|
// calc index->pos lookup table
|
||||||
|
let line = 0;
|
||||||
|
let index = 0;
|
||||||
|
let fromIndex, toIndex;
|
||||||
|
const lineIndexes = [index];
|
||||||
|
cm.doc.iter(({text}) => {
|
||||||
|
fromIndex = line === fromPos.line ? index : fromIndex;
|
||||||
|
lineIndexes.push((index += text.length + 1));
|
||||||
|
line++;
|
||||||
|
toIndex = line >= toPos.line ? index : toIndex;
|
||||||
|
return toIndex;
|
||||||
|
});
|
||||||
|
|
||||||
|
// splice
|
||||||
|
i = Math.max(0, i);
|
||||||
|
widgets.splice(i, 0, ...createWidgets(fromIndex, toIndex, widgets.splice(i, j - i), lineIndexes));
|
||||||
|
|
||||||
|
fromLine = null;
|
||||||
|
toLine = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
function *createWidgets(start, end, removed, lineIndexes) {
|
||||||
|
let i = 0;
|
||||||
|
let itemHeight;
|
||||||
|
for (const section of findAppliesTo(start, end)) {
|
||||||
|
let removedWidget = removed[i];
|
||||||
|
while (removedWidget && removedWidget.line.lineNo() < section.pos.line) {
|
||||||
|
clearWidget(removed[i]);
|
||||||
|
removedWidget = removed[++i];
|
||||||
|
}
|
||||||
|
for (const a of section.applies) {
|
||||||
|
setupApplyMarkers(a, lineIndexes);
|
||||||
|
}
|
||||||
|
if (removedWidget && removedWidget.line.lineNo() === section.pos.line) {
|
||||||
|
// reuse old widget
|
||||||
|
removedWidget.section.applies.forEach(apply => {
|
||||||
|
apply.type.mark.clear();
|
||||||
|
apply.value.mark.clear();
|
||||||
|
});
|
||||||
|
removedWidget.section = section;
|
||||||
|
const newNode = buildElement(section);
|
||||||
|
const removedNode = removedWidget.node;
|
||||||
|
if (removedNode.parentNode) {
|
||||||
|
removedNode.parentNode.replaceChild(newNode, removedNode);
|
||||||
|
}
|
||||||
|
removedWidget.node = newNode;
|
||||||
|
removedWidget.changed();
|
||||||
|
yield removedWidget;
|
||||||
|
i++;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
// new widget
|
||||||
|
const widget = cm.addLineWidget(section.pos.line, buildElement(section), {
|
||||||
|
coverGutter: true,
|
||||||
|
noHScroll: true,
|
||||||
|
above: true,
|
||||||
|
height: itemHeight ? section.applies.length * itemHeight : undefined,
|
||||||
|
});
|
||||||
|
widget.section = section;
|
||||||
|
itemHeight = itemHeight || widget.node.offsetHeight / (section.applies.length || 1);
|
||||||
|
yield widget;
|
||||||
|
}
|
||||||
|
removed.slice(i).forEach(clearWidget);
|
||||||
|
}
|
||||||
|
|
||||||
|
function clearWidget(widget) {
|
||||||
|
widget.clear();
|
||||||
|
widget.section.applies.forEach(clearApply);
|
||||||
|
}
|
||||||
|
|
||||||
|
function clearApply(apply) {
|
||||||
|
apply.type.mark.clear();
|
||||||
|
apply.value.mark.clear();
|
||||||
|
apply.mark.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
function setupApplyMarkers(apply, lineIndexes) {
|
||||||
|
apply.type.mark = cm.markText(
|
||||||
|
posFromIndex(cm, apply.type.start, lineIndexes),
|
||||||
|
posFromIndex(cm, apply.type.end, lineIndexes),
|
||||||
|
{clearWhenEmpty: false}
|
||||||
|
);
|
||||||
|
apply.value.mark = cm.markText(
|
||||||
|
posFromIndex(cm, apply.value.start, lineIndexes),
|
||||||
|
posFromIndex(cm, apply.value.end, lineIndexes),
|
||||||
|
{clearWhenEmpty: false}
|
||||||
|
);
|
||||||
|
apply.mark = cm.markText(
|
||||||
|
posFromIndex(cm, apply.start, lineIndexes),
|
||||||
|
posFromIndex(cm, apply.end, lineIndexes),
|
||||||
|
{clearWhenEmpty: false}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function posFromIndex(cm, index, lineIndexes) {
|
||||||
|
if (!lineIndexes) {
|
||||||
|
return cm.posFromIndex(index);
|
||||||
|
}
|
||||||
|
let line = lineIndexes.prev || 0;
|
||||||
|
const prev = lineIndexes[line];
|
||||||
|
const next = lineIndexes[line + 1];
|
||||||
|
if (prev <= index && index < next) {
|
||||||
|
return {line, ch: index - prev};
|
||||||
|
}
|
||||||
|
let a = index < prev ? 0 : line;
|
||||||
|
let b = index < next ? line + 1 : lineIndexes.length - 1;
|
||||||
|
while (a < b - 1) {
|
||||||
|
const mid = (a + b) >> 1;
|
||||||
|
if (lineIndexes[mid] < index) {
|
||||||
|
a = mid;
|
||||||
|
} else {
|
||||||
|
b = mid;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
line = lineIndexes[b] > index ? a : b;
|
||||||
|
Object.defineProperty(lineIndexes, 'prev', {value: line, configurable: true});
|
||||||
|
return {line, ch: index - lineIndexes[line]};
|
||||||
|
}
|
||||||
|
|
||||||
|
function buildElement({applies}) {
|
||||||
|
const container = TPL.container.cloneNode(true);
|
||||||
|
const list = $('.applies-to-list', container);
|
||||||
|
for (const apply of applies) {
|
||||||
|
list.appendChild(buildChildren(applies, apply));
|
||||||
|
}
|
||||||
|
if (!list.children[0]) {
|
||||||
|
list.appendChild(TPL.appliesToEverything.cloneNode(true));
|
||||||
|
}
|
||||||
|
return Object.assign(container, EVENTS, {__applies: applies});
|
||||||
|
}
|
||||||
|
|
||||||
|
function buildChildren(applies, apply) {
|
||||||
|
const el = TPL.listItem.cloneNode(true);
|
||||||
|
el.dataset.type = apply.type.text;
|
||||||
|
el.__apply = apply;
|
||||||
|
$('.applies-type', el).value = apply.type.text;
|
||||||
|
$('.applies-value', el).value = apply.value.text;
|
||||||
|
return el;
|
||||||
|
}
|
||||||
|
|
||||||
|
function changeItem(itemElement, apply, part, newText) {
|
||||||
|
if (!apply) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
part = apply[part];
|
||||||
|
const range = part.mark.find();
|
||||||
|
part.mark.clear();
|
||||||
|
newText = unescapeDoubleslash(newText).replace(/\\/g, '\\\\');
|
||||||
|
cm.replaceRange(newText, range.from, range.to, 'appliesTo');
|
||||||
|
part.mark = cm.markText(
|
||||||
|
range.from,
|
||||||
|
cm.findPosH(range.from, newText.length, 'char'),
|
||||||
|
{clearWhenEmpty: false}
|
||||||
|
);
|
||||||
|
part.text = newText;
|
||||||
|
|
||||||
|
if (part === apply.type) {
|
||||||
|
const range = apply.mark.find();
|
||||||
|
apply.mark.clear();
|
||||||
|
apply.mark = cm.markText(
|
||||||
|
part.mark.find().from,
|
||||||
|
range.to,
|
||||||
|
{clearWhenEmpty: false}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (apply.type.text === 'regexp' && apply.value.text.trim()) {
|
||||||
|
showRegExpTester(itemElement);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function createApply(pos, typeText, valueText, isQuoted = false) {
|
||||||
|
typeText = typeText.toLowerCase();
|
||||||
|
const start = pos;
|
||||||
|
const typeStart = start;
|
||||||
|
const typeEnd = typeStart + typeText.length;
|
||||||
|
const valueStart = typeEnd + 1 + Number(isQuoted);
|
||||||
|
const valueEnd = valueStart + valueText.length;
|
||||||
|
const end = valueEnd + Number(isQuoted) + 1;
|
||||||
|
return {
|
||||||
|
start,
|
||||||
|
type: {
|
||||||
|
text: typeText,
|
||||||
|
start: typeStart,
|
||||||
|
end: typeEnd,
|
||||||
|
},
|
||||||
|
value: {
|
||||||
|
text: unescapeDoubleslash(valueText),
|
||||||
|
start: valueStart,
|
||||||
|
end: valueEnd,
|
||||||
|
},
|
||||||
|
end
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function *findAppliesTo(posStart, posEnd) {
|
||||||
|
const text = cm.getValue();
|
||||||
|
const re = /^[\t ]*@-moz-document[\s\n]+/gm;
|
||||||
|
const applyRe = new RegExp([
|
||||||
|
/(?:\/\*[\s\S]*?(?:\*\/\s*|$))*/,
|
||||||
|
/(url|url-prefix|domain|regexp)/,
|
||||||
|
/\(((['"])(?:\\\\|\\\n|\\\3|[^\n])*?\3|[^)\n]*)\)\s*(,\s*)?/,
|
||||||
|
].map(rx => rx.source).join(''), 'giy');
|
||||||
|
let match;
|
||||||
|
re.lastIndex = posStart;
|
||||||
|
while ((match = re.exec(text))) {
|
||||||
|
if (match.index >= posEnd) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const applies = [];
|
||||||
|
let m;
|
||||||
|
applyRe.lastIndex = re.lastIndex;
|
||||||
|
while ((m = applyRe.exec(text))) {
|
||||||
|
const apply = createApply(
|
||||||
|
m.index,
|
||||||
|
m[1],
|
||||||
|
unquote(m[2]),
|
||||||
|
unquote(m[2]) !== m[2]
|
||||||
|
);
|
||||||
|
applies.push(apply);
|
||||||
|
re.lastIndex = applyRe.lastIndex;
|
||||||
|
}
|
||||||
|
yield {
|
||||||
|
pos: cm.posFromIndex(match.index),
|
||||||
|
applies
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function unquote(s) {
|
||||||
|
const first = s.charAt(0);
|
||||||
|
return (first === '"' || first === "'") && s.endsWith(first) ? s.slice(1, -1) : s;
|
||||||
|
}
|
||||||
|
|
||||||
|
function unescapeDoubleslash(s) {
|
||||||
|
const hasSingleEscapes = /([^\\]|^)\\([^\\]|$)/.test(s);
|
||||||
|
return hasSingleEscapes ? s : s.replace(/\\\\/g, '\\');
|
||||||
|
}
|
||||||
|
|
||||||
|
function showRegExpTester(item) {
|
||||||
|
regExpTester.toggle(true);
|
||||||
|
regExpTester.update(
|
||||||
|
item.closest('.applies-to').__applies
|
||||||
|
.filter(a => a.type.text === 'regexp')
|
||||||
|
.map(a => unescapeDoubleslash(a.value.text)));
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,274 +0,0 @@
|
||||||
/* global CodeMirror */
|
|
||||||
/* global cmFactory */
|
|
||||||
/* global debounce */// toolbox.js
|
|
||||||
/* global editor */
|
|
||||||
/* global linterMan */
|
|
||||||
/* global prefs */
|
|
||||||
'use strict';
|
|
||||||
|
|
||||||
/* Registers 'hint' helper and 'autocompleteOnTyping' option in CodeMirror */
|
|
||||||
|
|
||||||
(() => {
|
|
||||||
const USO_VAR = 'uso-variable';
|
|
||||||
const USO_VALID_VAR = 'variable-3 ' + USO_VAR;
|
|
||||||
const USO_INVALID_VAR = 'error ' + USO_VAR;
|
|
||||||
const rxPROP = /^(prop(erty)?|variable-2|string-2)\b/;
|
|
||||||
const rxVAR = /(^|[^-.\w\u0080-\uFFFF])var\(/iyu;
|
|
||||||
const rxCONSUME = /([-\w]*\s*:\s?)?/yu;
|
|
||||||
const cssMime = CodeMirror.mimeModes['text/css'];
|
|
||||||
const docFuncs = addSuffix(cssMime.documentTypes, '(');
|
|
||||||
const {tokenHooks} = cssMime;
|
|
||||||
const originalCommentHook = tokenHooks['/'];
|
|
||||||
const originalHelper = CodeMirror.hint.css || (() => {});
|
|
||||||
let cssMedia, cssProps, cssValues;
|
|
||||||
|
|
||||||
const AOT_ID = 'autocompleteOnTyping';
|
|
||||||
const AOT_PREF_ID = 'editor.' + AOT_ID;
|
|
||||||
const aot = prefs.get(AOT_PREF_ID);
|
|
||||||
CodeMirror.defineOption(AOT_ID, aot, (cm, value) => {
|
|
||||||
cm[value ? 'on' : 'off']('changes', autocompleteOnTyping);
|
|
||||||
cm[value ? 'on' : 'off']('pick', autocompletePicked);
|
|
||||||
});
|
|
||||||
prefs.subscribe(AOT_PREF_ID, (key, val) => cmFactory.globalSetOption(AOT_ID, val), {runNow: aot});
|
|
||||||
|
|
||||||
CodeMirror.registerHelper('hint', 'css', helper);
|
|
||||||
CodeMirror.registerHelper('hint', 'stylus', helper);
|
|
||||||
|
|
||||||
tokenHooks['/'] = tokenizeUsoVariables;
|
|
||||||
|
|
||||||
async function helper(cm) {
|
|
||||||
const pos = cm.getCursor();
|
|
||||||
const {line, ch} = pos;
|
|
||||||
const {styles, text} = cm.getLineHandle(line);
|
|
||||||
const {style, index} = cm.getStyleAtPos({styles, pos: ch}) || {};
|
|
||||||
const isLessLang = cm.doc.mode.helperType === 'less';
|
|
||||||
const isStylusLang = cm.doc.mode.name === 'stylus';
|
|
||||||
const type = style && style.split(' ', 1)[0] || 'prop?';
|
|
||||||
if (!type || type === 'comment' || type === 'string') {
|
|
||||||
return originalHelper(cm);
|
|
||||||
}
|
|
||||||
// not using getTokenAt until the need is unavoidable because it reparses text
|
|
||||||
// and runs a whole lot of complex calc inside which is slow on long lines
|
|
||||||
// especially if autocomplete is auto-shown on each keystroke
|
|
||||||
let prev, end, state;
|
|
||||||
let i = index;
|
|
||||||
while (
|
|
||||||
(prev == null || `${styles[i - 1]}`.startsWith(type)) &&
|
|
||||||
(prev = i > 2 ? styles[i - 2] : 0) &&
|
|
||||||
isSameToken(text, style, prev)
|
|
||||||
) i -= 2;
|
|
||||||
i = index;
|
|
||||||
while (
|
|
||||||
(end == null || `${styles[i + 1]}`.startsWith(type)) &&
|
|
||||||
(end = styles[i]) &&
|
|
||||||
isSameToken(text, style, end)
|
|
||||||
) i += 2;
|
|
||||||
const getTokenState = () => state || (state = cm.getTokenAt(pos, true).state.state);
|
|
||||||
const str = text.slice(prev, end);
|
|
||||||
const left = text.slice(prev, ch).trim();
|
|
||||||
let leftLC = left.toLowerCase();
|
|
||||||
let list;
|
|
||||||
switch (leftLC[0]) {
|
|
||||||
|
|
||||||
case '!':
|
|
||||||
list = '!important'.startsWith(leftLC) ? ['!important'] : [];
|
|
||||||
break;
|
|
||||||
|
|
||||||
case '@':
|
|
||||||
list = [
|
|
||||||
'@-moz-document',
|
|
||||||
'@charset',
|
|
||||||
'@font-face',
|
|
||||||
'@import',
|
|
||||||
'@keyframes',
|
|
||||||
'@media',
|
|
||||||
'@namespace',
|
|
||||||
'@page',
|
|
||||||
'@supports',
|
|
||||||
'@viewport',
|
|
||||||
];
|
|
||||||
if (isLessLang) list = findAllCssVars(cm, left, '\\s*:').concat(list);
|
|
||||||
break;
|
|
||||||
|
|
||||||
case '#': // prevents autocomplete for #hex colors
|
|
||||||
break;
|
|
||||||
|
|
||||||
case '-': // --variable
|
|
||||||
case '(': // var(
|
|
||||||
list = str.startsWith('--') || testAt(rxVAR, ch - 5, text)
|
|
||||||
? findAllCssVars(cm, left)
|
|
||||||
: [];
|
|
||||||
if (str.startsWith('(')) {
|
|
||||||
prev++;
|
|
||||||
leftLC = left.slice(1);
|
|
||||||
} else {
|
|
||||||
leftLC = left;
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
|
|
||||||
case '/': // USO vars
|
|
||||||
if (str.startsWith('/*[[') && str.endsWith(']]*/')) {
|
|
||||||
prev += 4;
|
|
||||||
end -= 4;
|
|
||||||
end -= text.slice(end - 4, end) === '-rgb' ? 4 : 0;
|
|
||||||
list = Object.keys((editor.style.usercssData || {}).vars || {}).sort();
|
|
||||||
leftLC = left.slice(4);
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
|
|
||||||
case 'u': // url(), url-prefix()
|
|
||||||
case 'd': // domain()
|
|
||||||
case 'r': // regexp()
|
|
||||||
if (/^(variable|tag|error)/.test(type) &&
|
|
||||||
docFuncs.some(s => s.startsWith(leftLC)) &&
|
|
||||||
/^(top|documentTypes|atBlock)/.test(getTokenState())) {
|
|
||||||
end++;
|
|
||||||
list = docFuncs;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
// fallthrough to `default`
|
|
||||||
|
|
||||||
default:
|
|
||||||
// property values
|
|
||||||
if (isStylusLang || getTokenState() === 'prop') {
|
|
||||||
while (i > 0 && !rxPROP.test(styles[i + 1])) i -= 2;
|
|
||||||
const propEnd = styles[i];
|
|
||||||
let prop;
|
|
||||||
if (propEnd > text.lastIndexOf(';', ch - 1)) {
|
|
||||||
while (i > 0 && rxPROP.test(styles[i + 1])) i -= 2;
|
|
||||||
prop = text.slice(styles[i] || 0, propEnd).match(/([-\w]+)?$/u)[1];
|
|
||||||
}
|
|
||||||
if (prop) {
|
|
||||||
if (/[^-\w]/.test(leftLC)) {
|
|
||||||
prev += execAt(/[\s:()]*/y, prev, text)[0].length;
|
|
||||||
leftLC = leftLC.replace(/^[^\w\s]\s*/, '');
|
|
||||||
}
|
|
||||||
if (prop.startsWith('--')) prop = 'color'; // assuming 90% of variables are colors
|
|
||||||
if (!cssProps) await initCssProps();
|
|
||||||
list = [...new Set([...cssValues.all[prop] || [], ...cssValues.global])];
|
|
||||||
end = prev + execAt(/(\s*[-a-z(]+)?/y, prev, text)[0].length;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// properties and media features
|
|
||||||
if (!list &&
|
|
||||||
/^(prop(erty|\?)|atom|error|tag)/.test(type) &&
|
|
||||||
/^(block|atBlock_parens|maybeprop)/.test(getTokenState())) {
|
|
||||||
if (!cssProps) await initCssProps();
|
|
||||||
if (type === 'prop?') {
|
|
||||||
prev += leftLC.length;
|
|
||||||
leftLC = '';
|
|
||||||
}
|
|
||||||
list = state === 'atBlock_parens' ? cssMedia : cssProps;
|
|
||||||
end -= /\W$/u.test(str); // e.g. don't consume ) when inside ()
|
|
||||||
end += execAt(rxCONSUME, end, text)[0].length;
|
|
||||||
|
|
||||||
}
|
|
||||||
if (!list) {
|
|
||||||
return isStylusLang
|
|
||||||
? CodeMirror.hint.fromList(cm, {words: CodeMirror.hintWords.stylus})
|
|
||||||
: originalHelper(cm);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return {
|
|
||||||
list: (list || []).filter(s => s.startsWith(leftLC)),
|
|
||||||
from: {line, ch: prev + str.match(/^\s*/)[0].length},
|
|
||||||
to: {line, ch: end},
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
async function initCssProps() {
|
|
||||||
cssValues = await linterMan.worker.getCssPropsValues();
|
|
||||||
cssProps = addSuffix(cssValues.all);
|
|
||||||
cssMedia = [].concat(...Object.entries(cssMime).map(getMediaKeys).filter(Boolean)).sort();
|
|
||||||
}
|
|
||||||
|
|
||||||
function addSuffix(obj, suffix = ': ') {
|
|
||||||
// Sorting first, otherwise "foo-bar:" would precede "foo:"
|
|
||||||
return Object.keys(obj).sort().map(k => k + suffix);
|
|
||||||
}
|
|
||||||
|
|
||||||
function getMediaKeys([k, v]) {
|
|
||||||
return k === 'mediaFeatures' && addSuffix(v) ||
|
|
||||||
k.startsWith('media') && Object.keys(v);
|
|
||||||
}
|
|
||||||
|
|
||||||
/** makes sure we don't process a different adjacent comment */
|
|
||||||
function isSameToken(text, style, i) {
|
|
||||||
return !style || text[i] !== '/' && text[i + 1] !== '*' ||
|
|
||||||
!style.startsWith(USO_VALID_VAR) && !style.startsWith(USO_INVALID_VAR);
|
|
||||||
}
|
|
||||||
|
|
||||||
function findAllCssVars(cm, leftPart, rightPart = '') {
|
|
||||||
// simplified regex without CSS escapes
|
|
||||||
const [, prefixed, named] = leftPart.match(/^(--|@)?(\S)?/);
|
|
||||||
const rx = new RegExp(
|
|
||||||
'(?:^|[\\s/;{])(' +
|
|
||||||
(prefixed ? leftPart : '--') +
|
|
||||||
(named ? '' : '[a-zA-Z_\u0080-\uFFFF]') +
|
|
||||||
'[-0-9a-zA-Z_\u0080-\uFFFF]*)' +
|
|
||||||
rightPart,
|
|
||||||
'g');
|
|
||||||
const list = new Set();
|
|
||||||
cm.eachLine(({text}) => {
|
|
||||||
for (let m; (m = rx.exec(text));) {
|
|
||||||
list.add(m[1]);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
return [...list].sort();
|
|
||||||
}
|
|
||||||
|
|
||||||
function tokenizeUsoVariables(stream) {
|
|
||||||
const token = originalCommentHook.apply(this, arguments);
|
|
||||||
if (token[1] === 'comment') {
|
|
||||||
const {string, start, pos} = stream;
|
|
||||||
if (testAt(/\/\*\[\[/y, start, string) &&
|
|
||||||
testAt(/]]\*\//y, pos - 4, string)) {
|
|
||||||
const vars = (editor.style.usercssData || {}).vars;
|
|
||||||
token[0] =
|
|
||||||
vars && vars.hasOwnProperty(string.slice(start + 4, pos - 4).replace(/-rgb$/, ''))
|
|
||||||
? USO_VALID_VAR
|
|
||||||
: USO_INVALID_VAR;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return token;
|
|
||||||
}
|
|
||||||
|
|
||||||
function execAt(rx, index, text) {
|
|
||||||
rx.lastIndex = index;
|
|
||||||
return rx.exec(text);
|
|
||||||
}
|
|
||||||
|
|
||||||
function testAt(rx, index, text) {
|
|
||||||
rx.lastIndex = Math.max(0, index);
|
|
||||||
return rx.test(text);
|
|
||||||
}
|
|
||||||
|
|
||||||
function autocompleteOnTyping(cm, [info], debounced) {
|
|
||||||
const lastLine = info.text[info.text.length - 1];
|
|
||||||
if (cm.state.completionActive ||
|
|
||||||
info.origin && !info.origin.includes('input') ||
|
|
||||||
!lastLine) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (cm.state.autocompletePicked) {
|
|
||||||
cm.state.autocompletePicked = false;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (!debounced) {
|
|
||||||
debounce(autocompleteOnTyping, 100, cm, [info], true);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (lastLine.match(/[-a-z!]+$/i)) {
|
|
||||||
cm.state.autocompletePicked = false;
|
|
||||||
cm.options.hintOptions.completeSingle = false;
|
|
||||||
cm.execCommand('autocomplete');
|
|
||||||
setTimeout(() => {
|
|
||||||
cm.options.hintOptions.completeSingle = true;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function autocompletePicked(cm) {
|
|
||||||
cm.state.autocompletePicked = true;
|
|
||||||
}
|
|
||||||
})();
|
|
427
edit/base.js
427
edit/base.js
|
@ -1,427 +0,0 @@
|
||||||
/* global $$ $ $create messageBoxProxy setInputValue setupLivePrefs */// dom.js
|
|
||||||
/* global API */// msg.js
|
|
||||||
/* global CODEMIRROR_THEMES */
|
|
||||||
/* global CodeMirror */
|
|
||||||
/* global MozDocMapper */// sections-util.js
|
|
||||||
/* global chromeSync */// storage-util.js
|
|
||||||
/* global initBeautifyButton */// beautify.js
|
|
||||||
/* global prefs */
|
|
||||||
/* global t */// localization.js
|
|
||||||
/* global FIREFOX getOwnTab sessionStore tryJSONparse tryURL */// toolbox.js
|
|
||||||
'use strict';
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @type Editor
|
|
||||||
* @namespace Editor
|
|
||||||
*/
|
|
||||||
const editor = {
|
|
||||||
style: null,
|
|
||||||
dirty: DirtyReporter(),
|
|
||||||
isUsercss: false,
|
|
||||||
isWindowed: false,
|
|
||||||
livePreview: LivePreview(),
|
|
||||||
/** @type {'customName'|'name'} */
|
|
||||||
nameTarget: 'name',
|
|
||||||
previewDelay: 200, // Chrome devtools uses 200
|
|
||||||
saving: false,
|
|
||||||
scrollInfo: null,
|
|
||||||
|
|
||||||
cancel: () => location.assign('/manage.html'),
|
|
||||||
|
|
||||||
updateClass() {
|
|
||||||
$.rootCL.toggle('is-new-style', !editor.style.id);
|
|
||||||
},
|
|
||||||
|
|
||||||
updateTheme(name) {
|
|
||||||
if (!CODEMIRROR_THEMES[name]) {
|
|
||||||
name = 'default';
|
|
||||||
prefs.set('editor.theme', name);
|
|
||||||
}
|
|
||||||
$('#cm-theme').dataset.theme = name;
|
|
||||||
$('#cm-theme').textContent = CODEMIRROR_THEMES[name] || '';
|
|
||||||
},
|
|
||||||
|
|
||||||
updateTitle(isDirty = editor.dirty.isDirty()) {
|
|
||||||
const {customName, name} = editor.style;
|
|
||||||
document.title = `${
|
|
||||||
isDirty ? '* ' : ''
|
|
||||||
}${
|
|
||||||
customName || name || t('styleMissingName')
|
|
||||||
} - Stylus`; // the suffix enables external utilities to process our windows e.g. pin on top
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
//#region pre-init
|
|
||||||
|
|
||||||
(() => {
|
|
||||||
const mqCompact = matchMedia('(max-width: 850px)');
|
|
||||||
const toggleCompact = mq => $.rootCL.toggle('compact-layout', mq.matches);
|
|
||||||
mqCompact.on('change', toggleCompact);
|
|
||||||
toggleCompact(mqCompact);
|
|
||||||
Object.assign(editor, /** @namespace Editor */ {
|
|
||||||
mqCompact,
|
|
||||||
styleReady: prefs.ready.then(loadStyle),
|
|
||||||
});
|
|
||||||
async function loadStyle() {
|
|
||||||
const params = new URLSearchParams(location.search);
|
|
||||||
let id = Number(params.get('id'));
|
|
||||||
const style = id && await API.styles.get(id) || {
|
|
||||||
id: id = null, // resetting the non-existent id
|
|
||||||
name: params.get('domain') ||
|
|
||||||
tryURL(params.get('url-prefix')).hostname ||
|
|
||||||
'',
|
|
||||||
enabled: true,
|
|
||||||
sections: [
|
|
||||||
MozDocMapper.toSection([...params], {code: ''}),
|
|
||||||
],
|
|
||||||
};
|
|
||||||
// switching the mode here to show the correct page ASAP, usually before DOMContentLoaded
|
|
||||||
const isUC = Boolean(style.usercssData || !id && prefs.get('newStyleAsUsercss'));
|
|
||||||
Object.assign(editor, /** @namespace Editor */ {
|
|
||||||
style,
|
|
||||||
isUsercss: isUC,
|
|
||||||
template: isUC && !id && chromeSync.getLZValue(chromeSync.LZ_KEY.usercssTemplate), // promise
|
|
||||||
});
|
|
||||||
editor.updateClass();
|
|
||||||
editor.updateTheme(prefs.get('editor.theme'));
|
|
||||||
editor.updateTitle(false);
|
|
||||||
$.rootCL.add(isUC ? 'usercss' : 'sectioned');
|
|
||||||
sessionStore.justEditedStyleId = id || '';
|
|
||||||
// no such style so let's clear the invalid URL parameters
|
|
||||||
if (!id) history.replaceState({}, '', location.pathname);
|
|
||||||
}
|
|
||||||
})();
|
|
||||||
|
|
||||||
//#endregion
|
|
||||||
//#region init header
|
|
||||||
|
|
||||||
/* exported EditorHeader */
|
|
||||||
function EditorHeader() {
|
|
||||||
initBeautifyButton($('#beautify'));
|
|
||||||
initKeymapElement();
|
|
||||||
initNameArea();
|
|
||||||
initThemeElement();
|
|
||||||
setupLivePrefs();
|
|
||||||
|
|
||||||
window.on('load', () => {
|
|
||||||
prefs.subscribe('editor.keyMap', showHotkeyInTooltip, {runNow: true});
|
|
||||||
window.on('showHotkeyInTooltip', showHotkeyInTooltip);
|
|
||||||
}, {once: true});
|
|
||||||
|
|
||||||
function findKeyForCommand(command, map) {
|
|
||||||
if (typeof map === 'string') map = CodeMirror.keyMap[map];
|
|
||||||
let key = Object.keys(map).find(k => map[k] === command);
|
|
||||||
if (key) {
|
|
||||||
return key;
|
|
||||||
}
|
|
||||||
for (const ft of Array.isArray(map.fallthrough) ? map.fallthrough : [map.fallthrough]) {
|
|
||||||
key = ft && findKeyForCommand(command, ft);
|
|
||||||
if (key) {
|
|
||||||
return key;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return '';
|
|
||||||
}
|
|
||||||
|
|
||||||
function initNameArea() {
|
|
||||||
const nameEl = $('#name');
|
|
||||||
const resetEl = $('#reset-name');
|
|
||||||
const isCustomName = editor.style.updateUrl || editor.isUsercss;
|
|
||||||
editor.nameTarget = isCustomName ? 'customName' : 'name';
|
|
||||||
nameEl.placeholder = t(editor.isUsercss ? 'usercssEditorNamePlaceholder' : 'styleMissingName');
|
|
||||||
nameEl.title = isCustomName ? t('customNameHint') : '';
|
|
||||||
nameEl.on('input', () => {
|
|
||||||
editor.updateName(true);
|
|
||||||
resetEl.hidden = !editor.style.customName;
|
|
||||||
});
|
|
||||||
resetEl.hidden = !editor.style.customName;
|
|
||||||
resetEl.onclick = () => {
|
|
||||||
editor.style.customName = null; // to delete it from db
|
|
||||||
setInputValue(nameEl, editor.style.name);
|
|
||||||
resetEl.hidden = true;
|
|
||||||
};
|
|
||||||
const enabledEl = $('#enabled');
|
|
||||||
enabledEl.onchange = () => editor.updateEnabledness(enabledEl.checked);
|
|
||||||
}
|
|
||||||
|
|
||||||
function initThemeElement() {
|
|
||||||
$('#editor.theme').append(...[
|
|
||||||
$create('option', {value: 'default'}, t('defaultTheme')),
|
|
||||||
...Object.keys(CODEMIRROR_THEMES).map(s => $create('option', s)),
|
|
||||||
]);
|
|
||||||
// move the theme after built-in CSS so that its same-specificity selectors win
|
|
||||||
document.head.appendChild($('#cm-theme'));
|
|
||||||
}
|
|
||||||
|
|
||||||
function initKeymapElement() {
|
|
||||||
// move 'pc' or 'mac' prefix to the end of the displayed label
|
|
||||||
const maps = Object.keys(CodeMirror.keyMap)
|
|
||||||
.map(name => ({
|
|
||||||
value: name,
|
|
||||||
name: name.replace(/^(pc|mac)(.+)/, (s, arch, baseName) =>
|
|
||||||
baseName.toLowerCase() + '-' + (arch === 'mac' ? 'Mac' : 'PC')),
|
|
||||||
}))
|
|
||||||
.sort((a, b) => a.name < b.name && -1 || a.name > b.name && 1);
|
|
||||||
|
|
||||||
const fragment = document.createDocumentFragment();
|
|
||||||
let bin = fragment;
|
|
||||||
let groupName;
|
|
||||||
// group suffixed maps in <optgroup>
|
|
||||||
maps.forEach(({value, name}, i) => {
|
|
||||||
groupName = !name.includes('-') ? name : groupName;
|
|
||||||
const groupWithNext = maps[i + 1] && maps[i + 1].name.startsWith(groupName);
|
|
||||||
if (groupWithNext) {
|
|
||||||
if (bin === fragment) {
|
|
||||||
bin = fragment.appendChild($create('optgroup', {label: name.split('-')[0]}));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
const el = bin.appendChild($create('option', {value}, name));
|
|
||||||
if (value === prefs.defaults['editor.keyMap']) {
|
|
||||||
el.dataset.default = '';
|
|
||||||
el.title = t('defaultTheme');
|
|
||||||
}
|
|
||||||
if (!groupWithNext) bin = fragment;
|
|
||||||
});
|
|
||||||
const selector = $('#editor.keyMap');
|
|
||||||
selector.textContent = '';
|
|
||||||
selector.appendChild(fragment);
|
|
||||||
selector.value = prefs.get('editor.keyMap');
|
|
||||||
}
|
|
||||||
|
|
||||||
function showHotkeyInTooltip(_, mapName = prefs.get('editor.keyMap')) {
|
|
||||||
const extraKeys = CodeMirror.defaults.extraKeys;
|
|
||||||
for (const el of $$('[data-hotkey-tooltip]')) {
|
|
||||||
if (el._hotkeyTooltipKeyMap !== mapName) {
|
|
||||||
el._hotkeyTooltipKeyMap = mapName;
|
|
||||||
const title = el._hotkeyTooltipTitle = el._hotkeyTooltipTitle || el.title;
|
|
||||||
const cmd = el.dataset.hotkeyTooltip;
|
|
||||||
const key = cmd[0] === '=' ? cmd.slice(1) :
|
|
||||||
findKeyForCommand(cmd, mapName) ||
|
|
||||||
extraKeys && findKeyForCommand(cmd, extraKeys);
|
|
||||||
const newTitle = title + (title && key ? '\n' : '') + (key || '');
|
|
||||||
if (el.title !== newTitle) el.title = newTitle;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
//#endregion
|
|
||||||
//#region init windowed mode
|
|
||||||
|
|
||||||
(() => {
|
|
||||||
let ownTabId;
|
|
||||||
if (chrome.windows) {
|
|
||||||
initWindowedMode();
|
|
||||||
const pos = tryJSONparse(sessionStore.windowPos);
|
|
||||||
delete sessionStore.windowPos;
|
|
||||||
// resize the window on 'undo close'
|
|
||||||
if (pos && pos.left != null) {
|
|
||||||
chrome.windows.update(chrome.windows.WINDOW_ID_CURRENT, pos);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
getOwnTab().then(tab => {
|
|
||||||
ownTabId = tab.id;
|
|
||||||
if (sessionStore['manageStylesHistory' + ownTabId] === location.href) {
|
|
||||||
editor.cancel = () => history.back();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
async function initWindowedMode() {
|
|
||||||
chrome.tabs.onAttached.addListener(onTabAttached);
|
|
||||||
// Chrome 96+ bug: the type is 'app' for a window that was restored via Ctrl-Shift-T
|
|
||||||
const isSimple = ['app', 'popup'].includes((await browser.windows.getCurrent()).type);
|
|
||||||
if (isSimple) require(['/edit/embedded-popup']);
|
|
||||||
editor.isWindowed = isSimple || (
|
|
||||||
history.length === 1 &&
|
|
||||||
await prefs.ready && prefs.get('openEditInWindow') &&
|
|
||||||
(await browser.windows.getAll()).length > 1 &&
|
|
||||||
(await browser.tabs.query({currentWindow: true})).length === 1
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
async function onTabAttached(tabId, info) {
|
|
||||||
if (tabId !== ownTabId) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (info.newPosition !== 0) {
|
|
||||||
prefs.set('openEditInWindow', false);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const win = await browser.windows.get(info.newWindowId, {populate: true});
|
|
||||||
// If there's only one tab in this window, it's been dragged to new window
|
|
||||||
const openEditInWindow = win.tabs.length === 1;
|
|
||||||
// FF-only because Chrome retardedly resets the size during dragging
|
|
||||||
if (openEditInWindow && FIREFOX) {
|
|
||||||
chrome.windows.update(info.newWindowId, prefs.get('windowPosition'));
|
|
||||||
}
|
|
||||||
prefs.set('openEditInWindow', openEditInWindow);
|
|
||||||
}
|
|
||||||
})();
|
|
||||||
|
|
||||||
//#endregion
|
|
||||||
//#region internals
|
|
||||||
|
|
||||||
/** @returns DirtyReporter */
|
|
||||||
function DirtyReporter() {
|
|
||||||
const data = new Map();
|
|
||||||
const listeners = new Set();
|
|
||||||
const dataListeners = new Set();
|
|
||||||
const notifyChange = wasDirty => {
|
|
||||||
const isDirty = data.size > 0;
|
|
||||||
const flipped = isDirty !== wasDirty;
|
|
||||||
if (flipped) {
|
|
||||||
listeners.forEach(cb => cb(isDirty));
|
|
||||||
}
|
|
||||||
if (flipped || isDirty) {
|
|
||||||
dataListeners.forEach(cb => cb(isDirty));
|
|
||||||
}
|
|
||||||
};
|
|
||||||
/** @namespace DirtyReporter */
|
|
||||||
return {
|
|
||||||
add(obj, value) {
|
|
||||||
const wasDirty = data.size > 0;
|
|
||||||
const saved = data.get(obj);
|
|
||||||
if (!saved) {
|
|
||||||
data.set(obj, {type: 'add', newValue: value});
|
|
||||||
} else if (saved.type === 'remove') {
|
|
||||||
if (saved.savedValue === value) {
|
|
||||||
data.delete(obj);
|
|
||||||
} else {
|
|
||||||
saved.newValue = value;
|
|
||||||
saved.type = 'modify';
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
notifyChange(wasDirty);
|
|
||||||
},
|
|
||||||
clear(...objs) {
|
|
||||||
if (data.size && (
|
|
||||||
objs.length
|
|
||||||
? objs.map(data.delete, data).includes(true)
|
|
||||||
: (data.clear(), true)
|
|
||||||
)) {
|
|
||||||
notifyChange(true);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
has(key) {
|
|
||||||
return data.has(key);
|
|
||||||
},
|
|
||||||
isDirty() {
|
|
||||||
return data.size > 0;
|
|
||||||
},
|
|
||||||
modify(obj, oldValue, newValue) {
|
|
||||||
const wasDirty = data.size > 0;
|
|
||||||
const saved = data.get(obj);
|
|
||||||
if (!saved) {
|
|
||||||
if (oldValue !== newValue) {
|
|
||||||
data.set(obj, {type: 'modify', savedValue: oldValue, newValue});
|
|
||||||
} else {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
} else if (saved.type === 'modify') {
|
|
||||||
if (saved.savedValue === newValue) {
|
|
||||||
data.delete(obj);
|
|
||||||
} else {
|
|
||||||
saved.newValue = newValue;
|
|
||||||
}
|
|
||||||
} else if (saved.type === 'add') {
|
|
||||||
saved.newValue = newValue;
|
|
||||||
} else {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
notifyChange(wasDirty);
|
|
||||||
},
|
|
||||||
onChange(cb, add = true) {
|
|
||||||
listeners[add ? 'add' : 'delete'](cb);
|
|
||||||
},
|
|
||||||
onDataChange(cb, add = true) {
|
|
||||||
dataListeners[add ? 'add' : 'delete'](cb);
|
|
||||||
},
|
|
||||||
remove(obj, value) {
|
|
||||||
const wasDirty = data.size > 0;
|
|
||||||
const saved = data.get(obj);
|
|
||||||
if (!saved) {
|
|
||||||
data.set(obj, {type: 'remove', savedValue: value});
|
|
||||||
} else if (saved.type === 'add') {
|
|
||||||
data.delete(obj);
|
|
||||||
} else if (saved.type === 'modify') {
|
|
||||||
saved.type = 'remove';
|
|
||||||
} else {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
notifyChange(wasDirty);
|
|
||||||
},
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
function LivePreview() {
|
|
||||||
let el;
|
|
||||||
let data;
|
|
||||||
let port;
|
|
||||||
let preprocess;
|
|
||||||
let enabled = prefs.get('editor.livePreview');
|
|
||||||
|
|
||||||
prefs.subscribe('editor.livePreview', (key, value) => {
|
|
||||||
if (!value) {
|
|
||||||
if (port) {
|
|
||||||
port.disconnect();
|
|
||||||
port = null;
|
|
||||||
}
|
|
||||||
} else if (data && data.id && (data.enabled || editor.dirty.has('enabled'))) {
|
|
||||||
createPreviewer();
|
|
||||||
updatePreviewer(data);
|
|
||||||
}
|
|
||||||
enabled = value;
|
|
||||||
});
|
|
||||||
|
|
||||||
return {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param {Function} [fn] - preprocessor
|
|
||||||
*/
|
|
||||||
init(fn) {
|
|
||||||
preprocess = fn;
|
|
||||||
},
|
|
||||||
|
|
||||||
update(newData) {
|
|
||||||
data = newData;
|
|
||||||
if (!port) {
|
|
||||||
if (!data.id || !data.enabled || !enabled) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
createPreviewer();
|
|
||||||
}
|
|
||||||
updatePreviewer(data);
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
function createPreviewer() {
|
|
||||||
port = chrome.runtime.connect({name: 'livePreview'});
|
|
||||||
port.onDisconnect.addListener(err => {
|
|
||||||
throw err;
|
|
||||||
});
|
|
||||||
el = $('#preview-errors');
|
|
||||||
el.onclick = () => messageBoxProxy.alert(el.title, 'pre');
|
|
||||||
}
|
|
||||||
|
|
||||||
async function updatePreviewer(data) {
|
|
||||||
try {
|
|
||||||
port.postMessage(preprocess ? await preprocess(data) : data);
|
|
||||||
el.hidden = true;
|
|
||||||
} catch (err) {
|
|
||||||
if (Array.isArray(err)) {
|
|
||||||
err = err.map(e => e.message || e).join('\n');
|
|
||||||
} else if (err && err.index != null) {
|
|
||||||
// FIXME: this would fail if editors[0].getValue() !== data.sourceCode
|
|
||||||
const pos = editor.getEditors()[0].posFromIndex(err.index);
|
|
||||||
err.message = `${pos.line}:${pos.ch} ${err.message || err}`;
|
|
||||||
}
|
|
||||||
el.title = err.message || `${err}`;
|
|
||||||
el.hidden = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
//#endregion
|
|
286
edit/beautify.js
286
edit/beautify.js
|
@ -1,174 +1,140 @@
|
||||||
/* global $ $create moveFocus */// dom.js
|
/*
|
||||||
/* global CodeMirror */
|
global CodeMirror loadScript css_beautify
|
||||||
/* global createHotkeyInput helpPopup */// util.js
|
global editors getSectionForChild showHelp
|
||||||
/* global editor */
|
*/
|
||||||
/* global prefs */
|
|
||||||
/* global t */// localization.js
|
|
||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
CodeMirror.commands.beautify = cm => {
|
function beautify(event) {
|
||||||
// using per-section mode when code editor or applies-to block is focused
|
loadScript('/vendor-overwrites/beautify/beautify-css-mod.js')
|
||||||
const isPerSection = cm.display.wrapper.parentElement.contains(document.activeElement);
|
.then(() => {
|
||||||
beautify(isPerSection ? [cm] : editor.getEditors(), false);
|
if (!window.css_beautify && window.exports) {
|
||||||
};
|
window.css_beautify = window.exports.css_beautify;
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.then(doBeautify);
|
||||||
|
|
||||||
prefs.subscribe('editor.beautify.hotkey', (key, value) => {
|
function doBeautify() {
|
||||||
const {extraKeys} = CodeMirror.defaults;
|
const tabs = prefs.get('editor.indentWithTabs');
|
||||||
for (const [key, cmd] of Object.entries(extraKeys)) {
|
const options = prefs.get('editor.beautify');
|
||||||
if (cmd === 'beautify') {
|
for (const k of Object.keys(prefs.defaults['editor.beautify'])) {
|
||||||
delete extraKeys[key];
|
if (!(k in options)) options[k] = prefs.defaults['editor.beautify'][k];
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
}
|
options.indent_size = tabs ? 1 : prefs.get('editor.tabSize');
|
||||||
if (value) {
|
options.indent_char = tabs ? '\t' : ' ';
|
||||||
extraKeys[value] = 'beautify';
|
|
||||||
}
|
|
||||||
}, {runNow: true});
|
|
||||||
|
|
||||||
/**
|
const section = getSectionForChild(event.target);
|
||||||
* @name beautify
|
const scope = section ? [section.CodeMirror] : editors;
|
||||||
* @param {CodeMirror[]} scope
|
|
||||||
* @param {boolean} [ui=true]
|
|
||||||
*/
|
|
||||||
async function beautify(scope, ui = true) {
|
|
||||||
await require(['/vendor-overwrites/beautify/beautify-css-mod']); /* global css_beautify */
|
|
||||||
const tabs = prefs.get('editor.indentWithTabs');
|
|
||||||
const options = Object.assign(prefs.defaults['editor.beautify'], prefs.get('editor.beautify'));
|
|
||||||
options.indent_size = tabs ? 1 : prefs.get('editor.tabSize');
|
|
||||||
options.indent_char = tabs ? '\t' : ' ';
|
|
||||||
if (ui) {
|
|
||||||
createBeautifyUI(scope, options);
|
|
||||||
}
|
|
||||||
for (const cm of scope) {
|
|
||||||
setTimeout(beautifyEditor, 0, cm, options, ui);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function beautifyEditor(cm, options, ui) {
|
showHelp(t('styleBeautify'),
|
||||||
const pos = options.translate_positions =
|
$create([
|
||||||
[].concat.apply([], cm.doc.sel.ranges.map(r =>
|
$create('.beautify-options', [
|
||||||
[Object.assign({}, r.anchor), Object.assign({}, r.head)]));
|
$createOption('.selector1,', 'selector_separator_newline'),
|
||||||
const text = cm.getValue();
|
$createOption('.selector2', 'newline_before_open_brace'),
|
||||||
const newText = css_beautify(text, options);
|
$createOption('{', 'newline_after_open_brace'),
|
||||||
if (newText !== text) {
|
$createOption('border: none;', 'newline_between_properties', true),
|
||||||
if (!cm.beautifyChange || !cm.beautifyChange[cm.changeGeneration()]) {
|
$createOption('display: block;', 'newline_before_close_brace', true),
|
||||||
// clear the list if last change wasn't a css-beautify
|
$createOption('}', 'newline_between_rules'),
|
||||||
cm.beautifyChange = {};
|
$createLabeledCheckbox('preserve_newlines', 'styleBeautifyPreserveNewlines'),
|
||||||
}
|
$createLabeledCheckbox('indent_conditional', 'styleBeautifyIndentConditional'),
|
||||||
cm.setValue(newText);
|
]),
|
||||||
const selections = [];
|
$create('.buttons', [
|
||||||
for (let i = 0; i < pos.length; i += 2) {
|
$create('button', {
|
||||||
selections.push({anchor: pos[i], head: pos[i + 1]});
|
attributes: {role: 'close'},
|
||||||
}
|
// showHelp.close will be defined after showHelp() is invoked
|
||||||
const {scrollX, scrollY} = window;
|
onclick: () => showHelp.close(),
|
||||||
cm.setSelections(selections);
|
}, t('confirmClose')),
|
||||||
window.scrollTo(scrollX, scrollY);
|
$create('button', {
|
||||||
cm.beautifyChange[cm.changeGeneration()] = true;
|
attributes: {role: 'undo'},
|
||||||
if (ui) {
|
onclick() {
|
||||||
$('button[role="close"]', helpPopup.div).disabled = false;
|
let undoable = false;
|
||||||
}
|
for (const cm of scope) {
|
||||||
}
|
const data = cm.beautifyChange;
|
||||||
}
|
if (!data || !data[cm.changeGeneration()]) continue;
|
||||||
|
delete data[cm.changeGeneration()];
|
||||||
|
const {scrollX, scrollY} = window;
|
||||||
|
cm.undo();
|
||||||
|
cm.scrollIntoView(cm.getCursor());
|
||||||
|
window.scrollTo(scrollX, scrollY);
|
||||||
|
undoable |= data[cm.changeGeneration()];
|
||||||
|
}
|
||||||
|
this.disabled = !undoable;
|
||||||
|
},
|
||||||
|
}, t(scope.length === 1 ? 'undo' : 'undoGlobal')),
|
||||||
|
]),
|
||||||
|
]));
|
||||||
|
|
||||||
function createBeautifyUI(scope, options) {
|
$('#help-popup').className = 'main-bg wide';
|
||||||
helpPopup.show(t('styleBeautify'),
|
|
||||||
$create([
|
scope.forEach(cm => {
|
||||||
$create('.beautify-options', [
|
setTimeout(() => {
|
||||||
$createOption('.selector1,', 'selector_separator_newline'),
|
const pos = options.translate_positions =
|
||||||
$createOption('.selector2', 'newline_before_open_brace'),
|
[].concat.apply([], cm.doc.sel.ranges.map(r =>
|
||||||
$createOption('{', 'newline_after_open_brace'),
|
[Object.assign({}, r.anchor), Object.assign({}, r.head)]));
|
||||||
$createOption('border: none;', 'newline_between_properties', true),
|
const text = cm.getValue();
|
||||||
$createOption('display: block;', 'newline_before_close_brace', true),
|
const newText = css_beautify(text, options);
|
||||||
$createOption('}', 'newline_between_rules'),
|
if (newText !== text) {
|
||||||
$createLabeledCheckbox('preserve_newlines', 'styleBeautifyPreserveNewlines'),
|
if (!cm.beautifyChange || !cm.beautifyChange[cm.changeGeneration()]) {
|
||||||
$createLabeledCheckbox('indent_conditional', 'styleBeautifyIndentConditional'),
|
// clear the list if last change wasn't a css-beautify
|
||||||
editor.isUsercss && $createLabeledCheckbox('indent_mozdoc', '', '... @-moz-document'),
|
cm.beautifyChange = {};
|
||||||
]),
|
}
|
||||||
$create('p.beautify-hint', [
|
cm.setValue(newText);
|
||||||
$create('span', t('styleBeautifyHint') + '\u00A0'),
|
const selections = [];
|
||||||
createHotkeyInput('editor.beautify.hotkey', {
|
for (let i = 0; i < pos.length; i += 2) {
|
||||||
buttons: false,
|
selections.push({anchor: pos[i], head: pos[i + 1]});
|
||||||
onDone: () => moveFocus(helpPopup.div, 0),
|
}
|
||||||
}),
|
const {scrollX, scrollY} = window;
|
||||||
]),
|
cm.setSelections(selections);
|
||||||
$create('.buttons', [
|
window.scrollTo(scrollX, scrollY);
|
||||||
$create('button', {
|
cm.beautifyChange[cm.changeGeneration()] = true;
|
||||||
attributes: {role: 'close'},
|
$('#help-popup button[role="close"]').disabled = false;
|
||||||
onclick: helpPopup.close,
|
}
|
||||||
}, t('confirmClose')),
|
});
|
||||||
$create('button', {
|
|
||||||
attributes: {role: 'undo'},
|
|
||||||
onclick() {
|
|
||||||
let undoable = false;
|
|
||||||
for (const cm of scope) {
|
|
||||||
const data = cm.beautifyChange;
|
|
||||||
if (!data || !data[cm.changeGeneration()]) continue;
|
|
||||||
delete data[cm.changeGeneration()];
|
|
||||||
const {scrollX, scrollY} = window;
|
|
||||||
cm.undo();
|
|
||||||
cm.scrollIntoView(cm.getCursor());
|
|
||||||
window.scrollTo(scrollX, scrollY);
|
|
||||||
undoable |= data[cm.changeGeneration()];
|
|
||||||
}
|
|
||||||
this.disabled = !undoable;
|
|
||||||
},
|
|
||||||
}, t(scope.length === 1 ? 'undo' : 'undoGlobal')),
|
|
||||||
]),
|
|
||||||
]),
|
|
||||||
{
|
|
||||||
className: 'wide',
|
|
||||||
});
|
});
|
||||||
|
|
||||||
$('.beautify-options').onchange = ({target}) => {
|
$('.beautify-options').onchange = ({target}) => {
|
||||||
const value = target.type === 'checkbox' ? target.checked : target.selectedIndex > 0;
|
const value = target.type === 'checkbox' ? target.checked : target.selectedIndex > 0;
|
||||||
const elLine = target.closest('[newline]');
|
prefs.set('editor.beautify', Object.assign(options, {[target.dataset.option]: value}));
|
||||||
if (elLine) elLine.setAttribute('newline', value);
|
if (target.parentNode.hasAttribute('newline')) {
|
||||||
prefs.set('editor.beautify', Object.assign(options, {[target.dataset.option]: value}));
|
target.parentNode.setAttribute('newline', value.toString());
|
||||||
beautify(scope, false);
|
}
|
||||||
};
|
doBeautify();
|
||||||
|
};
|
||||||
|
|
||||||
function $createOption(label, optionName, indent) {
|
function $createOption(label, optionName, indent) {
|
||||||
const value = options[optionName];
|
const value = options[optionName];
|
||||||
return (
|
return (
|
||||||
$create('div', {attributes: {newline: value}}, [
|
$create('div', {attributes: {newline: value}}, [
|
||||||
$create('span', indent ? {attributes: {indent: ''}} : {}, label),
|
$create('span', indent ? {attributes: {indent: ''}} : {}, label),
|
||||||
$create('div.select-resizer', [
|
$create('div.select-resizer', [
|
||||||
$create('select', {dataset: {option: optionName}}, [
|
$create('select', {dataset: {option: optionName}}, [
|
||||||
$create('option', {selected: !value}, '\xA0'),
|
$create('option', {selected: !value}, '\xA0'),
|
||||||
$create('option', {selected: value}, '\\n'),
|
$create('option', {selected: value}, '\\n'),
|
||||||
|
]),
|
||||||
|
$create('SVG:svg.svg-icon.select-arrow', {viewBox: '0 0 1792 1792'}, [
|
||||||
|
$create('SVG:path', {
|
||||||
|
'fill-rule': 'evenodd',
|
||||||
|
'd': 'M1408 704q0 26-19 45l-448 448q-19 19-45 ' +
|
||||||
|
'19t-45-19l-448-448q-19-19-19-45t19-45 45-19h896q26 0 45 19t19 45z'
|
||||||
|
}),
|
||||||
|
]),
|
||||||
]),
|
]),
|
||||||
$create('SVG:svg.svg-icon.select-arrow', {viewBox: '0 0 1792 1792'}, [
|
])
|
||||||
$create('SVG:path', {
|
);
|
||||||
'fill-rule': 'evenodd',
|
}
|
||||||
'd': 'M1408 704q0 26-19 45l-448 448q-19 19-45 ' +
|
|
||||||
'19t-45-19l-448-448q-19-19-19-45t19-45 45-19h896q26 0 45 19t19 45z',
|
|
||||||
}),
|
|
||||||
]),
|
|
||||||
]),
|
|
||||||
])
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
function $createLabeledCheckbox(optionName, i18nKey, text) {
|
function $createLabeledCheckbox(optionName, i18nKey) {
|
||||||
return (
|
return (
|
||||||
$create('label', {style: 'display: block; clear: both;'}, [
|
$create('label', {style: 'display: block; clear: both;'}, [
|
||||||
$create('input', {
|
$create('input', {
|
||||||
type: 'checkbox',
|
type: 'checkbox',
|
||||||
dataset: {option: optionName},
|
dataset: {option: optionName},
|
||||||
checked: options[optionName] !== false,
|
checked: options[optionName] !== false
|
||||||
}),
|
}),
|
||||||
$create('SVG:svg.svg-icon.checked',
|
$create('SVG:svg.svg-icon.checked',
|
||||||
$create('SVG:use', {'xlink:href': '#svg-icon-checked'})),
|
$create('SVG:use', {'xlink:href': '#svg-icon-checked'})),
|
||||||
i18nKey ? t(i18nKey) : text,
|
t(i18nKey),
|
||||||
])
|
])
|
||||||
);
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/* exported initBeautifyButton */
|
|
||||||
function initBeautifyButton(btn, scope) {
|
|
||||||
btn.onclick = btn.oncontextmenu = e => {
|
|
||||||
e.preventDefault();
|
|
||||||
beautify(scope || editor.getEditors(), e.type === 'click');
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
|
@ -1,33 +1,54 @@
|
||||||
/* Built-in CodeMirror and addon customization */
|
:root {
|
||||||
|
--applies-to-pseudo: hsla(214, 100%, 90%, 0.15);
|
||||||
|
}
|
||||||
|
|
||||||
|
/************ CM default ************/
|
||||||
|
.CodeMirror.cm-s-default {
|
||||||
|
background: var(--gray-lightness-93);
|
||||||
|
}
|
||||||
|
|
||||||
|
.CodeMirror {
|
||||||
|
outline-style: solid;
|
||||||
|
outline-color: transparent;
|
||||||
|
outline-width: 1px;
|
||||||
|
outline-offset: -1px;
|
||||||
|
transition: outline-color .25s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.CodeMirror-focused {
|
||||||
|
outline-color: var(--focus-outline);
|
||||||
|
}
|
||||||
|
|
||||||
|
.CodeMirror.cm-s-default .CodeMirror-gutters {
|
||||||
|
background-color: var(--truegray-alpha-1);
|
||||||
|
border-right: 1px solid var(--truegray-alpha-2);
|
||||||
|
}
|
||||||
|
|
||||||
|
.CodeMirror.cm-s-default .CodeMirror-activeline-background {
|
||||||
|
background: var(--cm-activeline-bg);
|
||||||
|
}
|
||||||
|
|
||||||
|
.CodeMirror-hints.default {
|
||||||
|
background-color: var(--main-bg);
|
||||||
|
}
|
||||||
|
|
||||||
.CodeMirror-hints {
|
.CodeMirror-hints {
|
||||||
z-index: 999;
|
z-index: 999;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* match Windows select hover, so no variables */
|
||||||
.CodeMirror-hint:hover {
|
.CodeMirror-hint:hover {
|
||||||
color: var(--bg);
|
color: #fff;
|
||||||
background: #08f;
|
background: #08f;
|
||||||
}
|
}
|
||||||
.CodeMirror {
|
.CodeMirror {
|
||||||
border: solid var(--c80) 1px;
|
border: 1px solid var(--gray-lightness-76);
|
||||||
transition: box-shadow .1s;
|
|
||||||
}
|
}
|
||||||
.CodeMirror {
|
.CodeMirror-lint-mark-warning {
|
||||||
color: inherit;
|
background: none;
|
||||||
background-color: inherit;
|
|
||||||
border: solid var(--c80) 1px;
|
|
||||||
transition: box-shadow .1s;
|
|
||||||
}
|
|
||||||
.CodeMirror-gutters {
|
|
||||||
background-color: var(--c95);
|
|
||||||
border-color: var(--c85);
|
|
||||||
}
|
|
||||||
#stylus#stylus .CodeMirror {
|
|
||||||
/* Using a specificity hack to override userstyles */
|
|
||||||
/* Not using the ring-color hack as it became ugly in new Chrome */
|
|
||||||
outline: none !important;
|
|
||||||
}
|
}
|
||||||
.CodeMirror-dialog {
|
.CodeMirror-dialog {
|
||||||
animation: highlight 3s cubic-bezier(.18, .02, 0, .94);
|
-webkit-animation: highlight 3s cubic-bezier(.18, .02, 0, .94);
|
||||||
}
|
}
|
||||||
.CodeMirror-search-field {
|
.CodeMirror-search-field {
|
||||||
width: 10em;
|
width: 10em;
|
||||||
|
@ -36,10 +57,14 @@
|
||||||
width: 5em;
|
width: 5em;
|
||||||
}
|
}
|
||||||
.CodeMirror-search-hint {
|
.CodeMirror-search-hint {
|
||||||
color: var(--c50);
|
color: var(--truegray);
|
||||||
}
|
}
|
||||||
|
.cm-uso-variable {
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
|
||||||
.CodeMirror-activeline .applies-to:before {
|
.CodeMirror-activeline .applies-to:before {
|
||||||
background-color: hsla(214, 100%, 90%, 0.15);
|
background-color: var(--applies-to-pseudo);
|
||||||
content: "";
|
content: "";
|
||||||
top: 1em;
|
top: 1em;
|
||||||
left: 0;
|
left: 0;
|
||||||
|
@ -48,9 +73,11 @@
|
||||||
position: absolute;
|
position: absolute;
|
||||||
pointer-events: none;
|
pointer-events: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
.CodeMirror-activeline .applies-to ul {
|
.CodeMirror-activeline .applies-to ul {
|
||||||
z-index: 2;
|
z-index: 2;
|
||||||
}
|
}
|
||||||
|
|
||||||
.CodeMirror-foldgutter-open::after,
|
.CodeMirror-foldgutter-open::after,
|
||||||
.CodeMirror-foldgutter-folded::after {
|
.CodeMirror-foldgutter-folded::after {
|
||||||
top: 5px;
|
top: 5px;
|
||||||
|
@ -62,87 +89,15 @@
|
||||||
opacity: .5;
|
opacity: .5;
|
||||||
left: 1px;
|
left: 1px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.CodeMirror-foldgutter-open::after {
|
.CodeMirror-foldgutter-open::after {
|
||||||
border-width: 5px 3px 0 3px;
|
border-width: 5px 3px 0 3px;
|
||||||
border-color: currentColor transparent transparent transparent;
|
border-color: currentColor transparent transparent transparent;
|
||||||
}
|
}
|
||||||
|
|
||||||
.CodeMirror-foldgutter-folded::after {
|
.CodeMirror-foldgutter-folded::after {
|
||||||
margin-top: -2px;
|
margin-top: -2px;
|
||||||
margin-left: 1px;
|
margin-left: 1px;
|
||||||
border-width: 4px 0 4px 5px;
|
border-width: 4px 0 4px 5px;
|
||||||
border-color: transparent transparent transparent currentColor;
|
border-color: transparent transparent transparent currentColor;
|
||||||
}
|
}
|
||||||
.CodeMirror-linenumber {
|
|
||||||
cursor: pointer; /* for bookmarking */
|
|
||||||
}
|
|
||||||
.cm-matchhighlight,
|
|
||||||
.CodeMirror-selection-highlight-scrollbar {
|
|
||||||
background: hsla(200, 100%, 50%, var(--match-hl-opacity, .1));
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Custom stuff we add to CodeMirror */
|
|
||||||
|
|
||||||
.cm-uso-variable {
|
|
||||||
font-weight: bold;
|
|
||||||
}
|
|
||||||
.gutter-bookmark {
|
|
||||||
background: linear-gradient(0deg, hsla(180, 100%, 30%, .75) 2px, hsla(180, 100%, 30%, .2) 2px);
|
|
||||||
}
|
|
||||||
|
|
||||||
@media screen and (prefers-color-scheme: dark), dark {
|
|
||||||
.CodeMirror {
|
|
||||||
--match-hl-opacity: .18;
|
|
||||||
}
|
|
||||||
.CodeMirror-dialog {
|
|
||||||
background-color: #333;
|
|
||||||
}
|
|
||||||
.CodeMirror-dialog-top {
|
|
||||||
border-color: #555;
|
|
||||||
}
|
|
||||||
.CodeMirror-activeline-background {
|
|
||||||
background: hsl(180, 21%, 18%);
|
|
||||||
}
|
|
||||||
.CodeMirror-selected,
|
|
||||||
.CodeMirror-focused .CodeMirror-selected,
|
|
||||||
.CodeMirror-line::selection,
|
|
||||||
.CodeMirror-line > span::selection,
|
|
||||||
.CodeMirror-line > span > span::selection {
|
|
||||||
background: #444;
|
|
||||||
}
|
|
||||||
.CodeMirror-line::-moz-selection,
|
|
||||||
.CodeMirror-line > span::-moz-selection,
|
|
||||||
.CodeMirror-line > span > span::-moz-selection {
|
|
||||||
/* TODO: remove this when strict_min_version >= 62 */
|
|
||||||
background: #444;
|
|
||||||
}
|
|
||||||
.cm-s-default div.CodeMirror-cursor {
|
|
||||||
border-left: 1px solid #fff;
|
|
||||||
}
|
|
||||||
/* Using Chromium's dark devtools colors */
|
|
||||||
.cm-s-default .cm-atom,
|
|
||||||
.cm-s-default .cm-number { color: #a1f7b5 }
|
|
||||||
.cm-s-default .cm-attribute { color: #6194c6 }
|
|
||||||
.cm-s-default .cm-bracket { color: #997 }
|
|
||||||
.cm-s-default .cm-builtin,
|
|
||||||
.cm-s-default .cm-link { color: #9fb4d6 }
|
|
||||||
.cm-s-default .cm-comment { color: #747474 }
|
|
||||||
.cm-s-default .cm-qualifier { color: #ffa34f }
|
|
||||||
.cm-s-default .cm-def,
|
|
||||||
.cm-s-default .cm-header,
|
|
||||||
.cm-s-default .cm-tag,
|
|
||||||
.cm-s-default .cm-type { color: #5db0d7 }
|
|
||||||
.cm-s-default .cm-hr { color: #999 }
|
|
||||||
.cm-s-default .cm-keyword { color: #9a7fd5 }
|
|
||||||
.cm-s-default .cm-meta { color: #ddfb55 }
|
|
||||||
.cm-s-default .cm-operator { color: #d2c057 }
|
|
||||||
.cm-s-default .cm-string { color: #f28b54 }
|
|
||||||
.cm-s-default .cm-variable { color: #d9d9d9 }
|
|
||||||
.cm-s-default .cm-variable-2 { color: #72b9ff }
|
|
||||||
.cm-s-default .cm-variable-3 { color: #9bbbdc }
|
|
||||||
|
|
||||||
@keyframes highlight {
|
|
||||||
from {
|
|
||||||
background-color: #888;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
@ -1,19 +1,14 @@
|
||||||
/* global $ */// dom.js
|
/* global CodeMirror prefs loadScript editor editors */
|
||||||
/* global CodeMirror */
|
|
||||||
/* global UA */// toolbox.js
|
|
||||||
/* global editor */
|
|
||||||
/* global prefs */
|
|
||||||
/* global t */// localization.js
|
|
||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
(() => {
|
(function () {
|
||||||
// CodeMirror miserably fails on keyMap='' so let's ensure it's not
|
// CodeMirror miserably fails on keyMap='' so let's ensure it's not
|
||||||
if (!prefs.get('editor.keyMap')) {
|
if (!prefs.get('editor.keyMap')) {
|
||||||
prefs.reset('editor.keyMap');
|
prefs.reset('editor.keyMap');
|
||||||
}
|
}
|
||||||
|
|
||||||
const defaults = {
|
const defaults = {
|
||||||
autoCloseBrackets: prefs.get('editor.autoCloseBrackets'),
|
|
||||||
mode: 'css',
|
mode: 'css',
|
||||||
lineNumbers: true,
|
lineNumbers: true,
|
||||||
lineWrapping: prefs.get('editor.lineWrapping'),
|
lineWrapping: prefs.get('editor.lineWrapping'),
|
||||||
|
@ -24,13 +19,14 @@
|
||||||
...(prefs.get('editor.linter') ? ['CodeMirror-lint-markers'] : []),
|
...(prefs.get('editor.linter') ? ['CodeMirror-lint-markers'] : []),
|
||||||
],
|
],
|
||||||
matchBrackets: true,
|
matchBrackets: true,
|
||||||
|
highlightSelectionMatches: {showToken: /[#.\-\w]/, annotateScrollbar: true},
|
||||||
hintOptions: {},
|
hintOptions: {},
|
||||||
lintReportDelay: prefs.get('editor.lintReportDelay'),
|
lintReportDelay: prefs.get('editor.lintReportDelay'),
|
||||||
styleActiveLine: {nonEmpty: true},
|
styleActiveLine: true,
|
||||||
theme: prefs.get('editor.theme'),
|
theme: 'default',
|
||||||
keyMap: prefs.get('editor.keyMap'),
|
keyMap: prefs.get('editor.keyMap'),
|
||||||
extraKeys: Object.assign(CodeMirror.defaults.extraKeys || {}, {
|
extraKeys: Object.assign(CodeMirror.defaults.extraKeys || {}, {
|
||||||
// independent of current keyMap; some are implemented only for the edit page
|
// independent of current keyMap
|
||||||
'Alt-Enter': 'toggleStyle',
|
'Alt-Enter': 'toggleStyle',
|
||||||
'Alt-PageDown': 'nextEditor',
|
'Alt-PageDown': 'nextEditor',
|
||||||
'Alt-PageUp': 'prevEditor',
|
'Alt-PageUp': 'prevEditor',
|
||||||
|
@ -41,108 +37,381 @@
|
||||||
|
|
||||||
Object.assign(CodeMirror.defaults, defaults, prefs.get('editor.options'));
|
Object.assign(CodeMirror.defaults, defaults, prefs.get('editor.options'));
|
||||||
|
|
||||||
// Adding hotkeys to some keymaps except 'basic' which is primitive by design
|
// 'basic' keymap only has basic keys by design, so we skip it
|
||||||
{
|
|
||||||
const KM = CodeMirror.keyMap;
|
const extraKeysCommands = {};
|
||||||
const extras = Object.values(CodeMirror.defaults.extraKeys);
|
Object.keys(CodeMirror.defaults.extraKeys).forEach(key => {
|
||||||
if (!extras.includes('jumpToLine')) {
|
extraKeysCommands[CodeMirror.defaults.extraKeys[key]] = true;
|
||||||
KM.sublime['Ctrl-G'] = 'jumpToLine';
|
});
|
||||||
KM.emacsy['Ctrl-G'] = 'jumpToLine';
|
if (!extraKeysCommands.jumpToLine) {
|
||||||
KM.pcDefault['Ctrl-J'] = 'jumpToLine';
|
CodeMirror.keyMap.sublime['Ctrl-G'] = 'jumpToLine';
|
||||||
KM.macDefault['Cmd-J'] = 'jumpToLine';
|
CodeMirror.keyMap.emacsy['Ctrl-G'] = 'jumpToLine';
|
||||||
}
|
CodeMirror.keyMap.pcDefault['Ctrl-J'] = 'jumpToLine';
|
||||||
if (!extras.includes('autocomplete')) {
|
CodeMirror.keyMap.macDefault['Cmd-J'] = 'jumpToLine';
|
||||||
// will be used by 'sublime' on PC via fallthrough
|
}
|
||||||
KM.pcDefault['Ctrl-Space'] = 'autocomplete';
|
if (!extraKeysCommands.autocomplete) {
|
||||||
// OSX uses Ctrl-Space and Cmd-Space for something else
|
// will be used by 'sublime' on PC via fallthrough
|
||||||
KM.macDefault['Alt-Space'] = 'autocomplete';
|
CodeMirror.keyMap.pcDefault['Ctrl-Space'] = 'autocomplete';
|
||||||
// copied from 'emacs' keymap
|
// OSX uses Ctrl-Space and Cmd-Space for something else
|
||||||
KM.emacsy['Alt-/'] = 'autocomplete';
|
CodeMirror.keyMap.macDefault['Alt-Space'] = 'autocomplete';
|
||||||
// 'vim' and 'emacs' define their own autocomplete hotkeys
|
// copied from 'emacs' keymap
|
||||||
}
|
CodeMirror.keyMap.emacsy['Alt-/'] = 'autocomplete';
|
||||||
if (!extras.includes('blockComment')) {
|
// 'vim' and 'emacs' define their own autocomplete hotkeys
|
||||||
KM.sublime['Shift-Ctrl-/'] = 'commentSelection';
|
}
|
||||||
}
|
if (!extraKeysCommands.blockComment) {
|
||||||
if (UA.windows) {
|
CodeMirror.keyMap.sublime['Shift-Ctrl-/'] = 'commentSelection';
|
||||||
// 'pcDefault' keymap on Windows should have F3/Shift-F3/Ctrl-R
|
|
||||||
if (!extras.includes('findNext')) KM.pcDefault['F3'] = 'findNext';
|
|
||||||
if (!extras.includes('findPrev')) KM.pcDefault['Shift-F3'] = 'findPrev';
|
|
||||||
if (!extras.includes('replace')) KM.pcDefault['Ctrl-R'] = 'replace';
|
|
||||||
// try to remap non-interceptable (Shift-)Ctrl-N/T/W hotkeys
|
|
||||||
// Note: modifier order in CodeMirror is S-C-A
|
|
||||||
for (const char of ['N', 'T', 'W']) {
|
|
||||||
for (const remap of [
|
|
||||||
{from: 'Ctrl-', to: ['Alt-', 'Ctrl-Alt-']},
|
|
||||||
{from: 'Shift-Ctrl-', to: ['Ctrl-Alt-', 'Shift-Ctrl-Alt-']},
|
|
||||||
]) {
|
|
||||||
const oldKey = remap.from + char;
|
|
||||||
for (const km of Object.values(KM)) {
|
|
||||||
const command = km[oldKey];
|
|
||||||
if (!command) continue;
|
|
||||||
for (const newMod of remap.to) {
|
|
||||||
const newKey = newMod + char;
|
|
||||||
if (newKey in km) continue;
|
|
||||||
km[newKey] = command;
|
|
||||||
delete km[oldKey];
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Object.assign(CodeMirror.prototype, {
|
if (navigator.appVersion.includes('Windows')) {
|
||||||
/**
|
// 'pcDefault' keymap on Windows should have F3/Shift-F3/Ctrl-R
|
||||||
* @param {'less' | 'stylus' | ?} [pp] - any value besides `less` or `stylus` sets `css` mode
|
if (!extraKeysCommands.findNext) {
|
||||||
* @param {boolean} [force]
|
CodeMirror.keyMap.pcDefault['F3'] = 'findNext';
|
||||||
*/
|
}
|
||||||
setPreprocessor(pp, force) {
|
if (!extraKeysCommands.findPrev) {
|
||||||
const name = pp === 'less' ? 'text/x-less' : pp === 'stylus' ? pp : 'css';
|
CodeMirror.keyMap.pcDefault['Shift-F3'] = 'findPrev';
|
||||||
const m = this.doc.mode;
|
}
|
||||||
if (force || (m.helperType ? m.helperType !== pp : m.name !== name)) {
|
if (!extraKeysCommands.replace) {
|
||||||
this.setOption('mode', name);
|
CodeMirror.keyMap.pcDefault['Ctrl-R'] = 'replace';
|
||||||
this.doc.mode.lineComment = ''; // stylelint chokes on line comments a lot
|
}
|
||||||
}
|
|
||||||
},
|
// try to remap non-interceptable Ctrl-(Shift-)N/T/W hotkeys
|
||||||
/** Superfast GC-friendly check that runs until the first non-space line */
|
['N', 'T', 'W'].forEach(char => {
|
||||||
isBlank() {
|
[
|
||||||
let filled;
|
{from: 'Ctrl-', to: ['Alt-', 'Ctrl-Alt-']},
|
||||||
this.eachLine(({text}) => (filled = text && /\S/.test(text)));
|
// Note: modifier order in CodeMirror is S-C-A
|
||||||
return !filled;
|
{from: 'Shift-Ctrl-', to: ['Ctrl-Alt-', 'Shift-Ctrl-Alt-']}
|
||||||
},
|
].forEach(remap => {
|
||||||
/**
|
const oldKey = remap.from + char;
|
||||||
* Sets cursor and centers it in view if `pos` was out of view
|
Object.keys(CodeMirror.keyMap).forEach(keyMapName => {
|
||||||
* @param {CodeMirror.Pos} pos
|
const keyMap = CodeMirror.keyMap[keyMapName];
|
||||||
* @param {CodeMirror.Pos} [end] - will set a selection from `pos` to `end`
|
const command = keyMap[oldKey];
|
||||||
*/
|
if (!command) {
|
||||||
jumpToPos(pos, end = pos) {
|
return;
|
||||||
const {curOp} = this;
|
}
|
||||||
if (!curOp) this.startOperation();
|
remap.to.some(newMod => {
|
||||||
const y = this.cursorCoords(pos, 'window').top;
|
const newKey = newMod + char;
|
||||||
const rect = this.display.wrapper.getBoundingClientRect();
|
if (!(newKey in keyMap)) {
|
||||||
// case 1) outside of CM viewport or too close to edge so tell CM to render a new viewport
|
delete keyMap[oldKey];
|
||||||
if (y < rect.top + 50 || y > rect.bottom - 100) {
|
keyMap[newKey] = command;
|
||||||
this.scrollIntoView(pos, rect.height / 2);
|
return true;
|
||||||
// case 2) inside CM viewport but outside of window viewport so just scroll the window
|
}
|
||||||
} else if (y < 0 || y > innerHeight) {
|
});
|
||||||
editor.scrollToEditor(this);
|
});
|
||||||
}
|
});
|
||||||
// Using prototype since our bookmark patch sets cm.setSelection to jumpToPos
|
});
|
||||||
CodeMirror.prototype.setSelection.call(this, pos, end);
|
}
|
||||||
if (!curOp) this.endOperation();
|
|
||||||
},
|
Object.assign(CodeMirror.mimeModes['text/css'].propertyKeywords, {
|
||||||
|
// CSS Backgrounds and Borders Module L4
|
||||||
|
'background-position-x': true,
|
||||||
|
'background-position-y': true,
|
||||||
|
|
||||||
|
// CSS Logical Properties and Values L1
|
||||||
|
'block-size': true,
|
||||||
|
'border-block-color': true,
|
||||||
|
'border-block-end': true,
|
||||||
|
'border-block-end-color': true,
|
||||||
|
'border-block-end-style': true,
|
||||||
|
'border-block-end-width': true,
|
||||||
|
'border-block-start': true,
|
||||||
|
'border-block-start-color': true,
|
||||||
|
'border-block-start-style': true,
|
||||||
|
'border-block-start-width': true,
|
||||||
|
'border-block-style': true,
|
||||||
|
'border-block-width': true,
|
||||||
|
'border-inline-color': true,
|
||||||
|
'border-inline-end': true,
|
||||||
|
'border-inline-end-color': true,
|
||||||
|
'border-inline-end-style': true,
|
||||||
|
'border-inline-end-width': true,
|
||||||
|
'border-inline-start': true,
|
||||||
|
'border-inline-start-color': true,
|
||||||
|
'border-inline-start-style': true,
|
||||||
|
'border-inline-start-width': true,
|
||||||
|
'border-inline-style': true,
|
||||||
|
'border-inline-width': true,
|
||||||
|
'inline-size': true,
|
||||||
|
'inset': true,
|
||||||
|
'inset-block': true,
|
||||||
|
'inset-block-end': true,
|
||||||
|
'inset-block-start': true,
|
||||||
|
'inset-inline': true,
|
||||||
|
'inset-inline-end': true,
|
||||||
|
'inset-inline-start': true,
|
||||||
|
'margin-block': true,
|
||||||
|
'margin-block-end': true,
|
||||||
|
'margin-block-start': true,
|
||||||
|
'margin-inline': true,
|
||||||
|
'margin-inline-end': true,
|
||||||
|
'margin-inline-start': true,
|
||||||
|
'max-block-size': true,
|
||||||
|
'max-inline-size': true,
|
||||||
|
'min-block-size': true,
|
||||||
|
'min-inline-size': true,
|
||||||
|
'padding-block': true,
|
||||||
|
'padding-block-end': true,
|
||||||
|
'padding-block-start': true,
|
||||||
|
'padding-inline': true,
|
||||||
|
'padding-inline-end': true,
|
||||||
|
'padding-inline-start': true,
|
||||||
|
'text-align-all': true,
|
||||||
|
|
||||||
|
'contain': true,
|
||||||
|
'mix-blend-mode': true,
|
||||||
|
'isolation': true,
|
||||||
|
'zoom': true,
|
||||||
|
|
||||||
|
// nonstandard https://compat.spec.whatwg.org/
|
||||||
|
'box-reflect': true,
|
||||||
|
'text-fill-color': true,
|
||||||
|
'text-stroke': true,
|
||||||
|
'text-stroke-color': true,
|
||||||
|
'text-stroke-width': true,
|
||||||
|
// end
|
||||||
|
});
|
||||||
|
Object.assign(CodeMirror.mimeModes['text/css'].valueKeywords, {
|
||||||
|
'isolate': true,
|
||||||
|
'recto': true,
|
||||||
|
'verso': true,
|
||||||
|
});
|
||||||
|
Object.assign(CodeMirror.mimeModes['text/css'].colorKeywords, {
|
||||||
|
'darkgrey': true,
|
||||||
|
'darkslategrey': true,
|
||||||
|
'dimgrey': true,
|
||||||
|
'grey': true,
|
||||||
|
'lightgrey': true,
|
||||||
|
'lightslategrey': true,
|
||||||
|
'slategrey': true,
|
||||||
});
|
});
|
||||||
|
|
||||||
Object.assign(CodeMirror.commands, {
|
const MODE = {
|
||||||
jumpToLine(cm) {
|
less: {
|
||||||
const cur = cm.getCursor();
|
family: 'css',
|
||||||
const oldDialog = $('.CodeMirror-dialog', cm.display.wrapper);
|
value: 'text/x-less',
|
||||||
if (oldDialog) cm.focus(); // close the currently opened minidialog
|
isActive: cm =>
|
||||||
cm.openDialog(t.template.jumpToLine.cloneNode(true), str => {
|
cm.doc.mode &&
|
||||||
const [line, ch] = str.match(/^\s*(\d+)(?:\s*:\s*(\d+))?\s*$|$/);
|
cm.doc.mode.name === 'css' &&
|
||||||
if (line) cm.setCursor(line - 1, ch ? ch - 1 : cur.ch);
|
cm.doc.mode.helperType === 'less',
|
||||||
}, {value: cur.line + 1});
|
|
||||||
},
|
},
|
||||||
|
stylus: 'stylus',
|
||||||
|
uso: 'css'
|
||||||
|
};
|
||||||
|
|
||||||
|
CodeMirror.defineExtension('setPreprocessor', function (preprocessor, force = false) {
|
||||||
|
const mode = MODE[preprocessor] || 'css';
|
||||||
|
const isActive = mode.isActive || (
|
||||||
|
cm => cm.doc.mode === mode ||
|
||||||
|
cm.doc.mode && (cm.doc.mode.name + (cm.doc.mode.helperType || '') === mode)
|
||||||
|
);
|
||||||
|
if (!force && isActive(this)) {
|
||||||
|
return Promise.resolve();
|
||||||
|
}
|
||||||
|
if ((mode.family || mode) === 'css') {
|
||||||
|
// css.js is always loaded via html
|
||||||
|
this.setOption('mode', mode.value || mode);
|
||||||
|
return Promise.resolve();
|
||||||
|
}
|
||||||
|
return loadScript(`/vendor/codemirror/mode/${mode}/${mode}.js`).then(() => {
|
||||||
|
this.setOption('mode', mode);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
CodeMirror.defineExtension('isBlank', function () {
|
||||||
|
// superfast checking as it runs only until the first non-blank line
|
||||||
|
let isBlank = true;
|
||||||
|
this.doc.eachLine(line => {
|
||||||
|
if (line.text && line.text.trim()) {
|
||||||
|
isBlank = false;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return isBlank;
|
||||||
|
});
|
||||||
|
|
||||||
|
// doubleclick option
|
||||||
|
if (typeof editors !== 'undefined') {
|
||||||
|
const fn = (cm, repeat) =>
|
||||||
|
repeat === 'double' ?
|
||||||
|
{unit: selectTokenOnDoubleclick} :
|
||||||
|
{};
|
||||||
|
const configure = (_, enabled) => {
|
||||||
|
editors.forEach(cm => cm.setOption('configureMouse', enabled ? fn : null));
|
||||||
|
CodeMirror.defaults.configureMouse = enabled ? fn : null;
|
||||||
|
};
|
||||||
|
configure(null, prefs.get('editor.selectByTokens'));
|
||||||
|
prefs.subscribe(['editor.selectByTokens'], configure);
|
||||||
|
}
|
||||||
|
|
||||||
|
function selectTokenOnDoubleclick(cm, pos) {
|
||||||
|
let {ch} = pos;
|
||||||
|
const {line, sticky} = pos;
|
||||||
|
const {text, styles} = cm.getLineHandle(line);
|
||||||
|
|
||||||
|
const execAt = (rx, i) => (rx.lastIndex = i) && null || rx.exec(text);
|
||||||
|
const at = (rx, i) => (rx.lastIndex = i) && null || rx.test(text);
|
||||||
|
const atWord = ch => at(/\w/y, ch);
|
||||||
|
const atSpace = ch => at(/\s/y, ch);
|
||||||
|
|
||||||
|
const atTokenEnd = styles.indexOf(ch, 1);
|
||||||
|
ch += atTokenEnd < 0 ? 0 : sticky === 'before' && atWord(ch - 1) ? 0 : atSpace(ch + 1) ? 0 : 1;
|
||||||
|
ch = Math.min(text.length, ch);
|
||||||
|
const type = cm.getTokenTypeAt({line, ch: ch + (sticky === 'after' ? 1 : 0)});
|
||||||
|
if (atTokenEnd > 0) ch--;
|
||||||
|
|
||||||
|
const isCss = type && !/^(comment|string)/.test(type);
|
||||||
|
const isNumber = type === 'number';
|
||||||
|
const isSpace = atSpace(ch);
|
||||||
|
let wordChars =
|
||||||
|
isNumber ? /[-+\w.%]/y :
|
||||||
|
isCss ? /[-\w@]/y :
|
||||||
|
isSpace ? /\s/y :
|
||||||
|
atWord(ch) ? /\w/y : /[^\w\s]/y;
|
||||||
|
|
||||||
|
let a = ch;
|
||||||
|
while (a && at(wordChars, a)) a--;
|
||||||
|
a += !a && at(wordChars, a) || isCss && at(/[.!#@]/y, a) ? 0 : at(wordChars, a + 1);
|
||||||
|
|
||||||
|
let b, found;
|
||||||
|
|
||||||
|
if (isNumber) {
|
||||||
|
b = a + execAt(/[+-]?[\d.]+(e\d+)?|$/yi, a)[0].length;
|
||||||
|
found = b >= ch;
|
||||||
|
if (!found) {
|
||||||
|
a = b;
|
||||||
|
ch = a;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!found) {
|
||||||
|
wordChars = isCss ? /[-\w]*/y : new RegExp(wordChars.source + '*', 'uy');
|
||||||
|
b = ch + execAt(wordChars, ch)[0].length;
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
from: {line, ch: a},
|
||||||
|
to: {line, ch: b},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
})();
|
||||||
|
|
||||||
|
// eslint-disable-next-line no-unused-expressions
|
||||||
|
CodeMirror.hint && (() => {
|
||||||
|
const USO_VAR = 'uso-variable';
|
||||||
|
const USO_VALID_VAR = 'variable-3 ' + USO_VAR;
|
||||||
|
const USO_INVALID_VAR = 'error ' + USO_VAR;
|
||||||
|
const RX_IMPORTANT = /(i(m(p(o(r(t(a(nt?)?)?)?)?)?)?)?)?(?=\b|\W|$)/iy;
|
||||||
|
const RX_VAR_KEYWORD = /(^|[^-\w\u0080-\uFFFF])var\(/iy;
|
||||||
|
const RX_END_OF_VAR = /[\s,)]|$/g;
|
||||||
|
|
||||||
|
const originalHelper = CodeMirror.hint.css || (() => {});
|
||||||
|
const helper = cm => {
|
||||||
|
const pos = cm.getCursor();
|
||||||
|
const {line, ch} = pos;
|
||||||
|
const {styles, text} = cm.getLineHandle(line);
|
||||||
|
if (!styles) return originalHelper(cm);
|
||||||
|
const {style, index} = cm.getStyleAtPos({styles, pos: ch}) || {};
|
||||||
|
if (style && (style.startsWith('comment') || style.startsWith('string'))) {
|
||||||
|
return originalHelper(cm);
|
||||||
|
}
|
||||||
|
|
||||||
|
// !important
|
||||||
|
if (text[ch - 1] === '!' && /i|\W|^$/i.test(text[ch] || '')) {
|
||||||
|
RX_IMPORTANT.lastIndex = ch;
|
||||||
|
return {
|
||||||
|
list: ['important'],
|
||||||
|
from: pos,
|
||||||
|
to: {line, ch: ch + RX_IMPORTANT.exec(text)[0].length},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
let prev = index > 2 ? styles[index - 2] : 0;
|
||||||
|
let end = styles[index];
|
||||||
|
|
||||||
|
// #hex colors
|
||||||
|
if (text[prev] === '#') {
|
||||||
|
return {list: [], from: pos, to: pos};
|
||||||
|
}
|
||||||
|
|
||||||
|
// adjust cursor position for /*[[ and ]]*/
|
||||||
|
const adjust = text[prev] === '/' ? 4 : 0;
|
||||||
|
prev += adjust;
|
||||||
|
end -= adjust;
|
||||||
|
const leftPart = text.slice(prev, ch);
|
||||||
|
|
||||||
|
// --css-variables
|
||||||
|
const startsWithDoubleDash = text[prev] === '-' && text[prev + 1] === '-';
|
||||||
|
if (startsWithDoubleDash ||
|
||||||
|
leftPart === '(' && testAt(RX_VAR_KEYWORD, Math.max(0, prev - 4), text)) {
|
||||||
|
// simplified regex without CSS escapes
|
||||||
|
const RX_CSS_VAR = new RegExp(
|
||||||
|
'(?:^|[\\s/;{])(' +
|
||||||
|
(leftPart.startsWith('--') ? leftPart : '--') +
|
||||||
|
(leftPart.length <= 2 ? '[a-zA-Z_\u0080-\uFFFF]' : '') +
|
||||||
|
'[-0-9a-zA-Z_\u0080-\uFFFF]*)',
|
||||||
|
'gm');
|
||||||
|
const cursor = cm.getSearchCursor(RX_CSS_VAR, null, {caseFold: false, multiline: false});
|
||||||
|
const list = new Set();
|
||||||
|
while (cursor.findNext()) {
|
||||||
|
list.add(cursor.pos.match[1]);
|
||||||
|
}
|
||||||
|
if (!startsWithDoubleDash) {
|
||||||
|
prev++;
|
||||||
|
}
|
||||||
|
RX_END_OF_VAR.lastIndex = prev;
|
||||||
|
end = RX_END_OF_VAR.exec(text).index;
|
||||||
|
return {
|
||||||
|
list: [...list.keys()].sort(),
|
||||||
|
from: {line, ch: prev},
|
||||||
|
to: {line, ch: end},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!editor || !style || !style.includes(USO_VAR)) {
|
||||||
|
return originalHelper(cm);
|
||||||
|
}
|
||||||
|
|
||||||
|
// USO vars in usercss mode editor
|
||||||
|
const list = Object.keys(editor.getStyle().usercssData.vars)
|
||||||
|
.filter(name => name.startsWith(leftPart));
|
||||||
|
return {
|
||||||
|
list,
|
||||||
|
from: {line, ch: prev},
|
||||||
|
to: {line, ch: end},
|
||||||
|
};
|
||||||
|
};
|
||||||
|
CodeMirror.registerHelper('hint', 'css', helper);
|
||||||
|
CodeMirror.registerHelper('hint', 'stylus', helper);
|
||||||
|
|
||||||
|
const hooks = CodeMirror.mimeModes['text/css'].tokenHooks;
|
||||||
|
const originalCommentHook = hooks['/'];
|
||||||
|
hooks['/'] = tokenizeUsoVariables;
|
||||||
|
|
||||||
|
function tokenizeUsoVariables(stream) {
|
||||||
|
const token = originalCommentHook.apply(this, arguments);
|
||||||
|
if (token[1] !== 'comment') {
|
||||||
|
return token;
|
||||||
|
}
|
||||||
|
const {string, start, pos} = stream;
|
||||||
|
// /*[[install-key]]*/
|
||||||
|
// 01234 43210
|
||||||
|
if (string[start + 2] === '[' &&
|
||||||
|
string[start + 3] === '[' &&
|
||||||
|
string[pos - 3] === ']' &&
|
||||||
|
string[pos - 4] === ']') {
|
||||||
|
const vars = typeof editor !== 'undefined' && (editor.getStyle().usercssData || {}).vars;
|
||||||
|
const name = vars && string.slice(start + 4, pos - 4);
|
||||||
|
if (vars && Object.hasOwnProperty.call(vars, name.endsWith('-rgb') ? name.slice(0, -4) : name)) {
|
||||||
|
token[0] = USO_VALID_VAR;
|
||||||
|
} else {
|
||||||
|
token[0] = USO_INVALID_VAR;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return token;
|
||||||
|
}
|
||||||
|
|
||||||
|
function testAt(rx, index, text) {
|
||||||
|
if (!rx) return false;
|
||||||
|
rx.lastIndex = index;
|
||||||
|
return rx.test(text);
|
||||||
|
}
|
||||||
})();
|
})();
|
||||||
|
|
699
edit/codemirror-editing-hooks.js
Normal file
699
edit/codemirror-editing-hooks.js
Normal file
|
@ -0,0 +1,699 @@
|
||||||
|
/*
|
||||||
|
global CodeMirror loadScript
|
||||||
|
global editors editor styleId ownTabId
|
||||||
|
global save toggleStyle setupAutocomplete makeSectionVisible getSectionForChild
|
||||||
|
global getSectionsHashes
|
||||||
|
global messageBox
|
||||||
|
*/
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
onDOMscriptReady('/codemirror.js').then(() => {
|
||||||
|
const COMMANDS = {
|
||||||
|
save,
|
||||||
|
toggleStyle,
|
||||||
|
toggleEditorFocus,
|
||||||
|
jumpToLine,
|
||||||
|
nextEditor, prevEditor,
|
||||||
|
commentSelection,
|
||||||
|
};
|
||||||
|
const ORIGINAL_COMMANDS = {
|
||||||
|
insertTab: CodeMirror.commands.insertTab,
|
||||||
|
};
|
||||||
|
// reroute handling to nearest editor when keypress resolves to one of these commands
|
||||||
|
const REROUTED = new Set([
|
||||||
|
'save',
|
||||||
|
'toggleStyle',
|
||||||
|
'jumpToLine',
|
||||||
|
'nextEditor', 'prevEditor',
|
||||||
|
'toggleEditorFocus',
|
||||||
|
'find', 'findNext', 'findPrev', 'replace', 'replaceAll',
|
||||||
|
'colorpicker',
|
||||||
|
]);
|
||||||
|
Object.assign(CodeMirror, {
|
||||||
|
getOption,
|
||||||
|
setOption,
|
||||||
|
closestVisible,
|
||||||
|
});
|
||||||
|
Object.assign(CodeMirror.prototype, {
|
||||||
|
getSection,
|
||||||
|
rerouteHotkeys,
|
||||||
|
});
|
||||||
|
|
||||||
|
CodeMirror.defineInitHook(cm => {
|
||||||
|
if (!cm.display.wrapper.closest('#sections')) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (prefs.get('editor.livePreview') && styleId) {
|
||||||
|
cm.on('changes', updatePreview);
|
||||||
|
}
|
||||||
|
if (prefs.get('editor.autocompleteOnTyping')) {
|
||||||
|
setupAutocomplete(cm);
|
||||||
|
}
|
||||||
|
const wrapper = cm.display.wrapper;
|
||||||
|
cm.on('blur', () => {
|
||||||
|
editors.lastActive = cm;
|
||||||
|
cm.rerouteHotkeys(true);
|
||||||
|
setTimeout(() => {
|
||||||
|
wrapper.classList.toggle('CodeMirror-active', wrapper.contains(document.activeElement));
|
||||||
|
});
|
||||||
|
});
|
||||||
|
cm.on('focus', () => {
|
||||||
|
cm.rerouteHotkeys(false);
|
||||||
|
wrapper.classList.add('CodeMirror-active');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
new MutationObserver((mutations, observer) => {
|
||||||
|
if (!$('#sections')) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
observer.disconnect();
|
||||||
|
|
||||||
|
prefs.subscribe(['editor.keyMap'], showHotkeyInTooltip);
|
||||||
|
addEventListener('showHotkeyInTooltip', showHotkeyInTooltip);
|
||||||
|
showHotkeyInTooltip();
|
||||||
|
|
||||||
|
// N.B. the onchange event listeners should be registered before setupLivePrefs()
|
||||||
|
$('#options').addEventListener('change', onOptionElementChanged);
|
||||||
|
setupLivePreview();
|
||||||
|
buildThemeElement();
|
||||||
|
buildKeymapElement();
|
||||||
|
setupLivePrefs();
|
||||||
|
|
||||||
|
Object.assign(CodeMirror.commands, COMMANDS);
|
||||||
|
rerouteHotkeys(true);
|
||||||
|
}).observe(document, {childList: true, subtree: true});
|
||||||
|
|
||||||
|
return;
|
||||||
|
|
||||||
|
////////////////////////////////////////////////
|
||||||
|
|
||||||
|
function getOption(o) {
|
||||||
|
return CodeMirror.defaults[o];
|
||||||
|
}
|
||||||
|
|
||||||
|
function setOption(o, v) {
|
||||||
|
CodeMirror.defaults[o] = v;
|
||||||
|
if (editors.length > 4 && (o === 'theme' || o === 'lineWrapping')) {
|
||||||
|
throttleSetOption({key: o, value: v, index: 0});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
editors.forEach(editor => {
|
||||||
|
editor.setOption(o, v);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function throttleSetOption({
|
||||||
|
key,
|
||||||
|
value,
|
||||||
|
index,
|
||||||
|
timeStart = performance.now(),
|
||||||
|
cmStart = editors.lastActive || editors[0],
|
||||||
|
editorsCopy = editors.slice(),
|
||||||
|
progress,
|
||||||
|
}) {
|
||||||
|
if (index === 0) {
|
||||||
|
if (!cmStart) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
cmStart.setOption(key, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
const THROTTLE_AFTER_MS = 100;
|
||||||
|
const THROTTLE_SHOW_PROGRESS_AFTER_MS = 100;
|
||||||
|
|
||||||
|
const t0 = performance.now();
|
||||||
|
const total = editorsCopy.length;
|
||||||
|
while (index < total) {
|
||||||
|
const cm = editorsCopy[index++];
|
||||||
|
if (cm === cmStart ||
|
||||||
|
cm !== editors[index] && !editors.includes(cm)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
cm.setOption(key, value);
|
||||||
|
if (performance.now() - t0 > THROTTLE_AFTER_MS) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (index >= total) {
|
||||||
|
$.remove(progress);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!progress &&
|
||||||
|
index < total / 2 &&
|
||||||
|
t0 - timeStart > THROTTLE_SHOW_PROGRESS_AFTER_MS) {
|
||||||
|
let option = $('#editor.' + key);
|
||||||
|
if (option) {
|
||||||
|
if (option.type === 'checkbox') {
|
||||||
|
option = (option.labels || [])[0] || option.nextElementSibling || option;
|
||||||
|
}
|
||||||
|
progress = document.body.appendChild(
|
||||||
|
$create('.set-option-progress', {targetElement: option}));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (progress) {
|
||||||
|
const optionBounds = progress.targetElement.getBoundingClientRect();
|
||||||
|
const bounds = {
|
||||||
|
top: optionBounds.top + window.scrollY + 1,
|
||||||
|
left: optionBounds.left + window.scrollX + 1,
|
||||||
|
width: (optionBounds.width - 2) * index / total | 0,
|
||||||
|
height: optionBounds.height - 2,
|
||||||
|
};
|
||||||
|
const style = progress.style;
|
||||||
|
for (const prop in bounds) {
|
||||||
|
if (bounds[prop] !== parseFloat(style[prop])) {
|
||||||
|
style[prop] = bounds[prop] + 'px';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
setTimeout(throttleSetOption, 0, {
|
||||||
|
key,
|
||||||
|
value,
|
||||||
|
index,
|
||||||
|
timeStart,
|
||||||
|
cmStart,
|
||||||
|
editorsCopy,
|
||||||
|
progress,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function getSection() {
|
||||||
|
return this.display.wrapper.parentNode;
|
||||||
|
}
|
||||||
|
|
||||||
|
function nextEditor(cm) {
|
||||||
|
return nextPrevEditor(cm, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
function prevEditor(cm) {
|
||||||
|
return nextPrevEditor(cm, -1);
|
||||||
|
}
|
||||||
|
|
||||||
|
function nextPrevEditor(cm, direction) {
|
||||||
|
cm = editors[(editors.indexOf(cm) + direction + editors.length) % editors.length];
|
||||||
|
makeSectionVisible(cm);
|
||||||
|
cm.focus();
|
||||||
|
return cm;
|
||||||
|
}
|
||||||
|
|
||||||
|
function jumpToLine(cm) {
|
||||||
|
const cur = cm.getCursor();
|
||||||
|
refocusMinidialog(cm);
|
||||||
|
cm.openDialog(template.jumpToLine.cloneNode(true), str => {
|
||||||
|
const m = str.match(/^\s*(\d+)(?:\s*:\s*(\d+))?\s*$/);
|
||||||
|
if (m) {
|
||||||
|
cm.setCursor(m[1] - 1, m[2] ? m[2] - 1 : cur.ch);
|
||||||
|
}
|
||||||
|
}, {value: cur.line + 1});
|
||||||
|
}
|
||||||
|
|
||||||
|
function commentSelection(cm) {
|
||||||
|
cm.blockComment(cm.getCursor('from'), cm.getCursor('to'), {fullLines: false});
|
||||||
|
}
|
||||||
|
|
||||||
|
function toggleEditorFocus(cm) {
|
||||||
|
if (!cm) return;
|
||||||
|
if (cm.hasFocus()) {
|
||||||
|
setTimeout(() => cm.display.input.blur());
|
||||||
|
} else {
|
||||||
|
cm.focus();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function refocusMinidialog(cm) {
|
||||||
|
const section = cm.getSection();
|
||||||
|
if (!$('.CodeMirror-dialog', section)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// close the currently opened minidialog
|
||||||
|
cm.focus();
|
||||||
|
// make sure to focus the input in newly opened minidialog
|
||||||
|
setTimeout(() => {
|
||||||
|
$('.CodeMirror-dialog', section).focus();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function onOptionElementChanged(event) {
|
||||||
|
const el = event.target;
|
||||||
|
let option = el.id.replace(/^editor\./, '');
|
||||||
|
if (!option) {
|
||||||
|
console.error('no "cm_option"', el);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
let value = el.type === 'checkbox' ? el.checked : el.value;
|
||||||
|
switch (option) {
|
||||||
|
case 'tabSize':
|
||||||
|
value = Number(value);
|
||||||
|
CodeMirror.setOption('indentUnit', value);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'indentWithTabs':
|
||||||
|
CodeMirror.commands.insertTab = value ?
|
||||||
|
ORIGINAL_COMMANDS.insertTab :
|
||||||
|
CodeMirror.commands.insertSoftTab;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'theme': {
|
||||||
|
const themeLink = $('#cm-theme');
|
||||||
|
// use non-localized 'default' internally
|
||||||
|
if (!value || value === 'default' || value === t('defaultTheme')) {
|
||||||
|
value = 'default';
|
||||||
|
if (prefs.get(el.id) !== value) {
|
||||||
|
prefs.set(el.id, value);
|
||||||
|
}
|
||||||
|
themeLink.href = '';
|
||||||
|
el.selectedIndex = 0;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
const url = chrome.runtime.getURL('vendor/codemirror/theme/' + value + '.css');
|
||||||
|
if (themeLink.href === url) {
|
||||||
|
// preloaded in initCodeMirror()
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
// avoid flicker: wait for the second stylesheet to load, then apply the theme
|
||||||
|
document.head.appendChild($create('link#cm-theme2', {rel: 'stylesheet', href: url}));
|
||||||
|
setTimeout(() => {
|
||||||
|
CodeMirror.setOption(option, value);
|
||||||
|
themeLink.remove();
|
||||||
|
$('#cm-theme2').id = 'cm-theme';
|
||||||
|
}, 100);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
case 'autocompleteOnTyping':
|
||||||
|
editors.forEach(cm => setupAutocomplete(cm, el.checked));
|
||||||
|
return;
|
||||||
|
|
||||||
|
case 'autoCloseBrackets':
|
||||||
|
Promise.resolve(value && loadScript('/vendor/codemirror/addon/edit/closebrackets.js')).then(() => {
|
||||||
|
CodeMirror.setOption(option, value);
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
|
||||||
|
case 'matchHighlight':
|
||||||
|
switch (value) {
|
||||||
|
case 'token':
|
||||||
|
case 'selection':
|
||||||
|
document.body.dataset[option] = value;
|
||||||
|
value = {showToken: value === 'token' && /[#.\-\w]/, annotateScrollbar: true};
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
value = null;
|
||||||
|
document.body.removeAttribute('data-match-highlight');
|
||||||
|
}
|
||||||
|
option = 'highlightSelectionMatches';
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'colorpicker':
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
CodeMirror.setOption(option, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
function buildThemeElement() {
|
||||||
|
const themeElement = $('#editor.theme');
|
||||||
|
const themeList = localStorage.codeMirrorThemes;
|
||||||
|
|
||||||
|
const optionsFromArray = options => {
|
||||||
|
const fragment = document.createDocumentFragment();
|
||||||
|
options.forEach(opt => fragment.appendChild($create('option', opt)));
|
||||||
|
themeElement.appendChild(fragment);
|
||||||
|
};
|
||||||
|
|
||||||
|
if (themeList) {
|
||||||
|
optionsFromArray(themeList.split(/\s+/));
|
||||||
|
} else {
|
||||||
|
// Chrome is starting up and shows our edit.html, but the background page isn't loaded yet
|
||||||
|
const theme = prefs.get('editor.theme');
|
||||||
|
optionsFromArray([theme === 'default' ? t('defaultTheme') : theme]);
|
||||||
|
getCodeMirrorThemes().then(() => {
|
||||||
|
const themes = (localStorage.codeMirrorThemes || '').split(/\s+/);
|
||||||
|
optionsFromArray(themes);
|
||||||
|
themeElement.selectedIndex = Math.max(0, themes.indexOf(theme));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function buildKeymapElement() {
|
||||||
|
// move 'pc' or 'mac' prefix to the end of the displayed label
|
||||||
|
const maps = Object.keys(CodeMirror.keyMap)
|
||||||
|
.map(name => ({
|
||||||
|
value: name,
|
||||||
|
name: name.replace(/^(pc|mac)(.+)/, (s, arch, baseName) =>
|
||||||
|
baseName.toLowerCase() + '-' + (arch === 'mac' ? 'Mac' : 'PC')),
|
||||||
|
}))
|
||||||
|
.sort((a, b) => a.name < b.name && -1 || a.name > b.name && 1);
|
||||||
|
|
||||||
|
const fragment = document.createDocumentFragment();
|
||||||
|
let bin = fragment;
|
||||||
|
let groupName;
|
||||||
|
// group suffixed maps in <optgroup>
|
||||||
|
maps.forEach(({value, name}, i) => {
|
||||||
|
groupName = !name.includes('-') ? name : groupName;
|
||||||
|
const groupWithNext = maps[i + 1] && maps[i + 1].name.startsWith(groupName);
|
||||||
|
if (groupWithNext) {
|
||||||
|
if (bin === fragment) {
|
||||||
|
bin = fragment.appendChild($create('optgroup', {label: name.split('-')[0]}));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const el = bin.appendChild($create('option', {value}, name));
|
||||||
|
if (value === prefs.defaults['editor.keyMap']) {
|
||||||
|
el.dataset.default = '';
|
||||||
|
el.title = t('defaultTheme');
|
||||||
|
}
|
||||||
|
if (!groupWithNext) bin = fragment;
|
||||||
|
});
|
||||||
|
$('#editor.keyMap').appendChild(fragment);
|
||||||
|
}
|
||||||
|
|
||||||
|
////////////////////////////////////////////////
|
||||||
|
|
||||||
|
function rerouteHotkeys(enable, immediately) {
|
||||||
|
if (!immediately) {
|
||||||
|
debounce(rerouteHotkeys, 0, enable, true);
|
||||||
|
} else if (enable) {
|
||||||
|
document.addEventListener('keydown', rerouteHandler);
|
||||||
|
} else {
|
||||||
|
document.removeEventListener('keydown', rerouteHandler);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function rerouteHandler(event) {
|
||||||
|
const keyName = CodeMirror.keyName(event);
|
||||||
|
if (!keyName) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const rerouteCommand = name => {
|
||||||
|
if (REROUTED.has(name)) {
|
||||||
|
CodeMirror.commands[name](closestVisible(event.target));
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
if (CodeMirror.lookupKey(keyName, CodeMirror.defaults.keyMap, rerouteCommand) === 'handled' ||
|
||||||
|
CodeMirror.lookupKey(keyName, CodeMirror.defaults.extraKeys, rerouteCommand) === 'handled') {
|
||||||
|
event.preventDefault();
|
||||||
|
event.stopPropagation();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
////////////////////////////////////////////////
|
||||||
|
|
||||||
|
// priority:
|
||||||
|
// 1. associated CM for applies-to element
|
||||||
|
// 2. last active if visible
|
||||||
|
// 3. first visible
|
||||||
|
function closestVisible(nearbyElement) {
|
||||||
|
const cm =
|
||||||
|
nearbyElement instanceof CodeMirror ? nearbyElement :
|
||||||
|
nearbyElement instanceof Node && (getSectionForChild(nearbyElement) || {}).CodeMirror ||
|
||||||
|
editors.lastActive;
|
||||||
|
if (nearbyElement instanceof Node && cm) {
|
||||||
|
const {left, top} = nearbyElement.getBoundingClientRect();
|
||||||
|
const bounds = cm.display.wrapper.getBoundingClientRect();
|
||||||
|
if (top >= 0 && top >= bounds.top &&
|
||||||
|
left >= 0 && left >= bounds.left) {
|
||||||
|
return cm;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// closest editor should have at least 2 lines visible
|
||||||
|
const lineHeight = editors[0].defaultTextHeight();
|
||||||
|
const scrollY = window.scrollY;
|
||||||
|
const windowBottom = scrollY + window.innerHeight - 2 * lineHeight;
|
||||||
|
const allSectionsContainerTop = scrollY + $('#sections').getBoundingClientRect().top;
|
||||||
|
const distances = [];
|
||||||
|
const alreadyInView = cm && offscreenDistance(null, cm) === 0;
|
||||||
|
return alreadyInView ? cm : findClosest();
|
||||||
|
|
||||||
|
function offscreenDistance(index, cm) {
|
||||||
|
if (index >= 0 && distances[index] !== undefined) {
|
||||||
|
return distances[index];
|
||||||
|
}
|
||||||
|
const section = (cm || editors[index]).getSection();
|
||||||
|
if (!section) {
|
||||||
|
return 1e9;
|
||||||
|
}
|
||||||
|
const top = allSectionsContainerTop + section.offsetTop;
|
||||||
|
if (top < scrollY + lineHeight) {
|
||||||
|
return Math.max(0, scrollY - top - lineHeight);
|
||||||
|
}
|
||||||
|
if (top < windowBottom) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
const distance = top - windowBottom + section.offsetHeight;
|
||||||
|
if (index >= 0) {
|
||||||
|
distances[index] = distance;
|
||||||
|
}
|
||||||
|
return distance;
|
||||||
|
}
|
||||||
|
|
||||||
|
function findClosest() {
|
||||||
|
const last = editors.length - 1;
|
||||||
|
let a = 0;
|
||||||
|
let b = last;
|
||||||
|
let c;
|
||||||
|
let distance;
|
||||||
|
while (a < b - 1) {
|
||||||
|
c = (a + b) / 2 | 0;
|
||||||
|
distance = offscreenDistance(c);
|
||||||
|
if (!distance || !c) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
const distancePrev = offscreenDistance(c - 1);
|
||||||
|
const distanceNext = c < last ? offscreenDistance(c + 1) : 1e20;
|
||||||
|
if (distancePrev <= distance && distance <= distanceNext) {
|
||||||
|
b = c;
|
||||||
|
} else {
|
||||||
|
a = c;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
while (b && offscreenDistance(b - 1) <= offscreenDistance(b)) {
|
||||||
|
b--;
|
||||||
|
}
|
||||||
|
const cm = editors[b];
|
||||||
|
if (distances[b] > 0) {
|
||||||
|
makeSectionVisible(cm);
|
||||||
|
}
|
||||||
|
return cm;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
////////////////////////////////////////////////
|
||||||
|
|
||||||
|
function getCodeMirrorThemes() {
|
||||||
|
if (!chrome.runtime.getPackageDirectoryEntry) {
|
||||||
|
const themes = [
|
||||||
|
chrome.i18n.getMessage('defaultTheme'),
|
||||||
|
/* populate-theme-start */
|
||||||
|
'3024-day',
|
||||||
|
'3024-night',
|
||||||
|
'abcdef',
|
||||||
|
'ambiance',
|
||||||
|
'ambiance-mobile',
|
||||||
|
'base16-dark',
|
||||||
|
'base16-light',
|
||||||
|
'bespin',
|
||||||
|
'blackboard',
|
||||||
|
'cobalt',
|
||||||
|
'colorforth',
|
||||||
|
'darcula',
|
||||||
|
'dracula',
|
||||||
|
'duotone-dark',
|
||||||
|
'duotone-light',
|
||||||
|
'eclipse',
|
||||||
|
'elegant',
|
||||||
|
'erlang-dark',
|
||||||
|
'gruvbox-dark',
|
||||||
|
'hopscotch',
|
||||||
|
'icecoder',
|
||||||
|
'idea',
|
||||||
|
'isotope',
|
||||||
|
'lesser-dark',
|
||||||
|
'liquibyte',
|
||||||
|
'lucario',
|
||||||
|
'material',
|
||||||
|
'mbo',
|
||||||
|
'mdn-like',
|
||||||
|
'midnight',
|
||||||
|
'monokai',
|
||||||
|
'neat',
|
||||||
|
'neo',
|
||||||
|
'night',
|
||||||
|
'oceanic-next',
|
||||||
|
'panda-syntax',
|
||||||
|
'paraiso-dark',
|
||||||
|
'paraiso-light',
|
||||||
|
'pastel-on-dark',
|
||||||
|
'railscasts',
|
||||||
|
'rubyblue',
|
||||||
|
'seti',
|
||||||
|
'shadowfox',
|
||||||
|
'solarized',
|
||||||
|
'ssms',
|
||||||
|
'the-matrix',
|
||||||
|
'tomorrow-night-bright',
|
||||||
|
'tomorrow-night-eighties',
|
||||||
|
'ttcn',
|
||||||
|
'twilight',
|
||||||
|
'vibrant-ink',
|
||||||
|
'xq-dark',
|
||||||
|
'xq-light',
|
||||||
|
'yeti',
|
||||||
|
'zenburn',
|
||||||
|
/* populate-theme-end */
|
||||||
|
];
|
||||||
|
localStorage.codeMirrorThemes = themes.join(' ');
|
||||||
|
return Promise.resolve(themes);
|
||||||
|
}
|
||||||
|
return new Promise(resolve => {
|
||||||
|
chrome.runtime.getPackageDirectoryEntry(rootDir => {
|
||||||
|
rootDir.getDirectory('vendor/codemirror/theme', {create: false}, themeDir => {
|
||||||
|
themeDir.createReader().readEntries(entries => {
|
||||||
|
const themes = [
|
||||||
|
chrome.i18n.getMessage('defaultTheme')
|
||||||
|
].concat(
|
||||||
|
entries.filter(entry => entry.isFile)
|
||||||
|
.sort((a, b) => (a.name < b.name ? -1 : 1))
|
||||||
|
.map(entry => entry.name.replace(/\.css$/, ''))
|
||||||
|
);
|
||||||
|
localStorage.codeMirrorThemes = themes.join(' ');
|
||||||
|
resolve(themes);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function showHotkeyInTooltip(_, mapName = prefs.get('editor.keyMap')) {
|
||||||
|
const extraKeys = CodeMirror.defaults.extraKeys;
|
||||||
|
for (const el of $$('[data-hotkey-tooltip]')) {
|
||||||
|
if (el._hotkeyTooltipKeyMap !== mapName) {
|
||||||
|
el._hotkeyTooltipKeyMap = mapName;
|
||||||
|
const title = el._hotkeyTooltipTitle = el._hotkeyTooltipTitle || el.title;
|
||||||
|
const cmd = el.dataset.hotkeyTooltip;
|
||||||
|
const key = cmd[0] === '=' ? cmd.slice(1) :
|
||||||
|
findKeyForCommand(cmd, mapName) ||
|
||||||
|
extraKeys && findKeyForCommand(cmd, extraKeys);
|
||||||
|
const newTitle = title + (title && key ? '\n' : '') + (key || '');
|
||||||
|
if (el.title !== newTitle) el.title = newTitle;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function findKeyForCommand(command, map) {
|
||||||
|
if (typeof map === 'string') map = CodeMirror.keyMap[map];
|
||||||
|
let key = Object.keys(map).find(k => map[k] === command);
|
||||||
|
if (key) {
|
||||||
|
return key;
|
||||||
|
}
|
||||||
|
for (const ft of Array.isArray(map.fallthrough) ? map.fallthrough : [map.fallthrough]) {
|
||||||
|
key = ft && findKeyForCommand(command, ft);
|
||||||
|
if (key) {
|
||||||
|
return key;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
|
||||||
|
function setupLivePreview() {
|
||||||
|
if (!prefs.get('editor.livePreview') && !editors.length) {
|
||||||
|
setTimeout(setupLivePreview);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (styleId) {
|
||||||
|
$('#editor.livePreview').onchange = livePreviewToggled;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// wait for #preview-label's class to lose 'hidden' after the first save
|
||||||
|
new MutationObserver((_, observer) => {
|
||||||
|
if (!styleId) return;
|
||||||
|
observer.disconnect();
|
||||||
|
setupLivePreview();
|
||||||
|
livePreviewToggled();
|
||||||
|
}).observe($('#preview-label'), {
|
||||||
|
attributes: true,
|
||||||
|
attributeFilter: ['class'],
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function livePreviewToggled() {
|
||||||
|
const me = this instanceof Node ? this : $('#editor.livePreview');
|
||||||
|
const previewing = me.checked;
|
||||||
|
editors.forEach(cm => cm[previewing ? 'on' : 'off']('changes', updatePreview));
|
||||||
|
const addRemove = EventTarget.prototype[previewing ? 'addEventListener' : 'removeEventListener'];
|
||||||
|
addRemove.call($('#enabled'), 'change', updatePreview);
|
||||||
|
if (!editor) {
|
||||||
|
for (const el of $$('#sections .applies-to')) {
|
||||||
|
addRemove.call(el, 'input', updatePreview);
|
||||||
|
}
|
||||||
|
toggleLivePreviewSectionsObserver(previewing);
|
||||||
|
}
|
||||||
|
if (!previewing || document.body.classList.contains('dirty')) {
|
||||||
|
updatePreview(null, previewing);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Observes newly added section elements, and sets these event listeners:
|
||||||
|
* 1. 'changes' on CodeMirror inside
|
||||||
|
* 2. 'input' on .applies-to inside
|
||||||
|
* The goal is to avoid listening to 'input' on the entire #sections tree,
|
||||||
|
* which would trigger updatePreview() twice on any keystroke -
|
||||||
|
* both for the synthetic event from CodeMirror and the original event.
|
||||||
|
* Side effects:
|
||||||
|
* two expando properties on #sections
|
||||||
|
* 1. __livePreviewObserver
|
||||||
|
* 2. __livePreviewObserverEnabled
|
||||||
|
* @param {Boolean} enable
|
||||||
|
*/
|
||||||
|
function toggleLivePreviewSectionsObserver(enable) {
|
||||||
|
const sections = $('#sections');
|
||||||
|
const observing = sections.__livePreviewObserverEnabled;
|
||||||
|
let mo = sections.__livePreviewObserver;
|
||||||
|
if (enable && !mo) {
|
||||||
|
sections.__livePreviewObserver = mo = new MutationObserver(mutations => {
|
||||||
|
for (const {addedNodes} of mutations) {
|
||||||
|
for (const node of addedNodes) {
|
||||||
|
const el = node.children && $('.applies-to', node);
|
||||||
|
if (el) el.addEventListener('input', updatePreview);
|
||||||
|
if (node.CodeMirror) node.CodeMirror.on('changes', updatePreview);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
if (enable && !observing) {
|
||||||
|
mo.observe(sections, {childList: true});
|
||||||
|
sections.__livePreviewObserverEnabled = true;
|
||||||
|
} else if (!enable && observing) {
|
||||||
|
mo.disconnect();
|
||||||
|
sections.__livePreviewObserverEnabled = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function updatePreview(data, previewing) {
|
||||||
|
if (previewing !== true && previewing !== false) {
|
||||||
|
if (data instanceof Event && !data.target.matches('.style-contributor')) return;
|
||||||
|
debounce(updatePreview, data && data.id === 'enabled' ? 0 : 400, null, true);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const errors = $('#preview-errors');
|
||||||
|
API.refreshAllTabs({
|
||||||
|
reason: 'editPreview',
|
||||||
|
tabId: ownTabId,
|
||||||
|
style: {
|
||||||
|
id: styleId,
|
||||||
|
enabled: $('#enabled').checked,
|
||||||
|
sections: previewing && (editor ? editors[0].getValue() : getSectionsHashes()),
|
||||||
|
},
|
||||||
|
}).then(() => {
|
||||||
|
errors.classList.add('hidden');
|
||||||
|
}).catch(err => {
|
||||||
|
if (Array.isArray(err)) err = err.join('\n');
|
||||||
|
if (err && editor && !isNaN(err.index)) {
|
||||||
|
const pos = editors[0].posFromIndex(err.index);
|
||||||
|
err = `${pos.line}:${pos.ch} ${err}`;
|
||||||
|
}
|
||||||
|
errors.classList.remove('hidden');
|
||||||
|
errors.onclick = () => messageBox.alert(String(err), 'pre');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
|
@ -1,313 +0,0 @@
|
||||||
/* global CodeMirror */
|
|
||||||
/* global editor */
|
|
||||||
/* global prefs */
|
|
||||||
/* global rerouteHotkeys */// util.js
|
|
||||||
'use strict';
|
|
||||||
|
|
||||||
/*
|
|
||||||
All cm instances created by this module are collected so we can broadcast prefs
|
|
||||||
settings to them. You should `cmFactory.destroy(cm)` to unregister the listener
|
|
||||||
when the instance is not used anymore.
|
|
||||||
*/
|
|
||||||
|
|
||||||
(() => {
|
|
||||||
//#region Factory
|
|
||||||
|
|
||||||
const cms = new Set();
|
|
||||||
let lazyOpt;
|
|
||||||
|
|
||||||
const cmFactory = window.cmFactory = {
|
|
||||||
|
|
||||||
create(place, options) {
|
|
||||||
const cm = CodeMirror(place, options);
|
|
||||||
cm.lastActive = 0;
|
|
||||||
cms.add(cm);
|
|
||||||
return cm;
|
|
||||||
},
|
|
||||||
|
|
||||||
destroy(cm) {
|
|
||||||
cms.delete(cm);
|
|
||||||
},
|
|
||||||
|
|
||||||
globalSetOption(key, value) {
|
|
||||||
CodeMirror.defaults[key] = value;
|
|
||||||
if (cms.size > 4 && lazyOpt.names.includes(key)) {
|
|
||||||
lazyOpt.set(key, value);
|
|
||||||
} else {
|
|
||||||
cms.forEach(cm => cm.setOption(key, value));
|
|
||||||
}
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
// focus and blur
|
|
||||||
|
|
||||||
const onCmFocus = cm => {
|
|
||||||
rerouteHotkeys.toggle(false);
|
|
||||||
cm.display.wrapper.classList.add('CodeMirror-active');
|
|
||||||
cm.lastActive = Date.now();
|
|
||||||
};
|
|
||||||
const onCmBlur = cm => {
|
|
||||||
setTimeout(() => {
|
|
||||||
/* Delaying to next tick to avoid double-processing of the currently processed keyboard event
|
|
||||||
* when it bubbles up from CodeMirror to `document` where the rerouter listens */
|
|
||||||
rerouteHotkeys.toggle(true);
|
|
||||||
const {wrapper} = cm.display;
|
|
||||||
wrapper.classList.toggle('CodeMirror-active', wrapper.contains(document.activeElement));
|
|
||||||
});
|
|
||||||
};
|
|
||||||
CodeMirror.defineInitHook(cm => {
|
|
||||||
cm.on('focus', onCmFocus);
|
|
||||||
cm.on('blur', onCmBlur);
|
|
||||||
});
|
|
||||||
|
|
||||||
// propagated preferences
|
|
||||||
|
|
||||||
const prefToCmOpt = k =>
|
|
||||||
k.startsWith('editor.') &&
|
|
||||||
k.slice('editor.'.length);
|
|
||||||
const prefKeys = prefs.knownKeys.filter(k =>
|
|
||||||
k !== 'editor.colorpicker' && // handled in colorpicker-helper.js
|
|
||||||
k !== 'editor.arrowKeysTraverse' && // handled in sections-editor.js
|
|
||||||
prefToCmOpt(k) in CodeMirror.defaults);
|
|
||||||
const {insertTab, insertSoftTab} = CodeMirror.commands;
|
|
||||||
|
|
||||||
for (const [key, fn] of Object.entries({
|
|
||||||
'editor.tabSize'(cm, value) {
|
|
||||||
cm.setOption('indentUnit', Number(value));
|
|
||||||
},
|
|
||||||
'editor.indentWithTabs'(cm, value) {
|
|
||||||
CodeMirror.commands.insertTab = value ? insertTab : insertSoftTab;
|
|
||||||
},
|
|
||||||
'editor.matchHighlight'(cm, value) {
|
|
||||||
const showToken = value === 'token' && /[#.\-\w]/;
|
|
||||||
const opt = (showToken || value === 'selection') && {
|
|
||||||
showToken,
|
|
||||||
annotateScrollbar: true,
|
|
||||||
delay: 0,
|
|
||||||
onUpdate: updateMatchHighlightCount,
|
|
||||||
};
|
|
||||||
cm.setOption('highlightSelectionMatches', opt || null);
|
|
||||||
},
|
|
||||||
'editor.selectByTokens'(cm, value) {
|
|
||||||
cm.setOption('configureMouse', value ? configureMouseFn : null);
|
|
||||||
},
|
|
||||||
})) {
|
|
||||||
CodeMirror.defineOption(prefToCmOpt(key), prefs.get(key), fn);
|
|
||||||
prefKeys.push(key);
|
|
||||||
}
|
|
||||||
|
|
||||||
prefs.subscribe(prefKeys, (key, val) => {
|
|
||||||
if (key === 'editor.theme') editor.updateTheme(val);
|
|
||||||
cmFactory.globalSetOption(prefToCmOpt(key), val);
|
|
||||||
});
|
|
||||||
|
|
||||||
// lazy propagation
|
|
||||||
|
|
||||||
lazyOpt = {
|
|
||||||
names: ['theme', 'lineWrapping'],
|
|
||||||
set(key, value) {
|
|
||||||
const {observer, queue} = lazyOpt;
|
|
||||||
for (const cm of cms) {
|
|
||||||
let opts = queue.get(cm);
|
|
||||||
if (!opts) queue.set(cm, opts = {});
|
|
||||||
opts[key] = value;
|
|
||||||
observer.observe(cm.display.wrapper);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
setNow({cm, data}) {
|
|
||||||
cm.operation(() => data.forEach(kv => cm.setOption(...kv)));
|
|
||||||
},
|
|
||||||
onView(entries) {
|
|
||||||
const {queue, observer} = lazyOpt;
|
|
||||||
const delayed = [];
|
|
||||||
for (const e of entries) {
|
|
||||||
const r = e.isIntersecting && e.intersectionRect;
|
|
||||||
if (!r) continue;
|
|
||||||
const cm = e.target.CodeMirror;
|
|
||||||
const data = Object.entries(queue.get(cm) || {});
|
|
||||||
queue.delete(cm);
|
|
||||||
observer.unobserve(e.target);
|
|
||||||
if (!data.every(([key, val]) => cm.getOption(key) === val)) {
|
|
||||||
if (r.bottom > 0 && r.top < window.innerHeight) {
|
|
||||||
lazyOpt.setNow({cm, data});
|
|
||||||
} else {
|
|
||||||
delayed.push({cm, data});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (delayed.length) {
|
|
||||||
setTimeout(() => delayed.forEach(lazyOpt.setNow));
|
|
||||||
}
|
|
||||||
},
|
|
||||||
get observer() {
|
|
||||||
if (!lazyOpt._observer) {
|
|
||||||
// must exceed refreshOnView's 100%
|
|
||||||
lazyOpt._observer = new IntersectionObserver(lazyOpt.onView, {rootMargin: '150%'});
|
|
||||||
lazyOpt.queue = new WeakMap();
|
|
||||||
}
|
|
||||||
return lazyOpt._observer;
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
//#endregion
|
|
||||||
//#region Commands
|
|
||||||
|
|
||||||
Object.assign(CodeMirror.commands, {
|
|
||||||
commentSelection(cm) {
|
|
||||||
cm.blockComment(cm.getCursor('from'), cm.getCursor('to'), {fullLines: false});
|
|
||||||
},
|
|
||||||
toggleEditorFocus(cm) {
|
|
||||||
if (!cm) return;
|
|
||||||
if (cm.hasFocus()) {
|
|
||||||
setTimeout(() => cm.display.input.blur());
|
|
||||||
} else {
|
|
||||||
cm.focus();
|
|
||||||
}
|
|
||||||
},
|
|
||||||
});
|
|
||||||
for (const cmd of [
|
|
||||||
'nextEditor',
|
|
||||||
'prevEditor',
|
|
||||||
'save',
|
|
||||||
'toggleStyle',
|
|
||||||
]) {
|
|
||||||
CodeMirror.commands[cmd] = (...args) => editor[cmd](...args);
|
|
||||||
}
|
|
||||||
|
|
||||||
//#endregion
|
|
||||||
//#region CM option handlers
|
|
||||||
|
|
||||||
function updateMatchHighlightCount(cm, state) {
|
|
||||||
cm.display.wrapper.dataset.matchHighlightCount = state.matchesonscroll.matches.length;
|
|
||||||
}
|
|
||||||
|
|
||||||
function configureMouseFn(cm, repeat) {
|
|
||||||
return repeat === 'double' ?
|
|
||||||
{unit: selectTokenOnDoubleclick} :
|
|
||||||
{};
|
|
||||||
}
|
|
||||||
|
|
||||||
function selectTokenOnDoubleclick(cm, pos) {
|
|
||||||
let {ch} = pos;
|
|
||||||
const {line, sticky} = pos;
|
|
||||||
const {text, styles} = cm.getLineHandle(line);
|
|
||||||
|
|
||||||
const execAt = (rx, i) => (rx.lastIndex = i) && null || rx.exec(text);
|
|
||||||
const at = (rx, i) => (rx.lastIndex = i) && null || rx.test(text);
|
|
||||||
const atWord = ch => at(/\w/y, ch);
|
|
||||||
const atSpace = ch => at(/\s/y, ch);
|
|
||||||
|
|
||||||
const atTokenEnd = styles.indexOf(ch, 1);
|
|
||||||
ch += atTokenEnd < 0 ? 0 : sticky === 'before' && atWord(ch - 1) ? 0 : atSpace(ch + 1) ? 0 : 1;
|
|
||||||
ch = Math.min(text.length, ch);
|
|
||||||
const type = cm.getTokenTypeAt({line, ch: ch + (sticky === 'after' ? 1 : 0)});
|
|
||||||
if (atTokenEnd > 0) ch--;
|
|
||||||
|
|
||||||
const isCss = type && !/^(comment|string)/.test(type);
|
|
||||||
const isNumber = type === 'number';
|
|
||||||
const isSpace = atSpace(ch);
|
|
||||||
let wordChars =
|
|
||||||
isNumber ? /[-+\w.%]/y :
|
|
||||||
isCss ? /[-\w@]/y :
|
|
||||||
isSpace ? /\s/y :
|
|
||||||
atWord(ch) ? /\w/y : /[^\w\s]/y;
|
|
||||||
|
|
||||||
let a = ch;
|
|
||||||
while (a && at(wordChars, a)) a--;
|
|
||||||
a += !a && at(wordChars, a) || isCss && at(/[.!#@]/y, a) ? 0 : at(wordChars, a + 1);
|
|
||||||
|
|
||||||
let b, found;
|
|
||||||
|
|
||||||
if (isNumber) {
|
|
||||||
b = a + execAt(/[+-]?[\d.]+(e\d+)?|$/yi, a)[0].length;
|
|
||||||
found = b >= ch;
|
|
||||||
if (!found) {
|
|
||||||
a = b;
|
|
||||||
ch = a;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!found) {
|
|
||||||
wordChars = isCss ? /[-\w]*/y : new RegExp(wordChars.source + '*', 'uy');
|
|
||||||
b = ch + execAt(wordChars, ch)[0].length;
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
from: {line, ch: a},
|
|
||||||
to: {line, ch: b},
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
//#endregion
|
|
||||||
//#region Bookmarks
|
|
||||||
|
|
||||||
const BM_CLS = 'gutter-bookmark';
|
|
||||||
const BM_BRAND = 'sublimeBookmark';
|
|
||||||
const BM_CLICKER = 'CodeMirror-linenumbers';
|
|
||||||
const BM_DATA = Symbol('data');
|
|
||||||
// TODO: revisit when https://github.com/codemirror/CodeMirror/issues/6716 is fixed
|
|
||||||
const tmProto = CodeMirror.TextMarker.prototype;
|
|
||||||
const tmProtoOvr = {};
|
|
||||||
for (const k of ['clear', 'attachLine', 'detachLine']) {
|
|
||||||
tmProtoOvr[k] = function (line) {
|
|
||||||
const {cm} = this.doc;
|
|
||||||
const withOp = !cm.curOp;
|
|
||||||
if (withOp) cm.startOperation();
|
|
||||||
tmProto[k].apply(this, arguments);
|
|
||||||
cm.curOp.ownsGroup.delayedCallbacks.push(toggleMark.bind(this, this.lines[0], line));
|
|
||||||
if (withOp) cm.endOperation();
|
|
||||||
};
|
|
||||||
}
|
|
||||||
for (const name of ['prevBookmark', 'nextBookmark']) {
|
|
||||||
const cmdFn = CodeMirror.commands[name];
|
|
||||||
CodeMirror.commands[name] = cm => {
|
|
||||||
cm.setSelection = cm.jumpToPos;
|
|
||||||
cmdFn(cm);
|
|
||||||
delete cm.setSelection;
|
|
||||||
};
|
|
||||||
}
|
|
||||||
CodeMirror.defineInitHook(cm => {
|
|
||||||
cm.on('gutterClick', onGutterClick);
|
|
||||||
cm.on('gutterContextMenu', onGutterContextMenu);
|
|
||||||
cm.on('markerAdded', onMarkAdded);
|
|
||||||
});
|
|
||||||
// TODO: reimplement bookmarking so next/prev order is decided solely by the line numbers
|
|
||||||
function onGutterClick(cm, line, name, e) {
|
|
||||||
switch (name === BM_CLICKER && e.button) {
|
|
||||||
case 0: {
|
|
||||||
// main button: toggle
|
|
||||||
const [mark] = cm.findMarks({line, ch: 0}, {line, ch: 1e9}, m => m[BM_BRAND]);
|
|
||||||
cm.setCursor(mark ? mark.find(-1) : {line, ch: 0});
|
|
||||||
cm.execCommand('toggleBookmark');
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case 1:
|
|
||||||
// middle button: select all marks
|
|
||||||
cm.execCommand('selectBookmarks');
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
function onGutterContextMenu(cm, line, name, e) {
|
|
||||||
if (name === BM_CLICKER) {
|
|
||||||
cm.execCommand(e.ctrlKey ? 'prevBookmark' : 'nextBookmark');
|
|
||||||
e.preventDefault();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
function onMarkAdded(cm, mark) {
|
|
||||||
if (mark[BM_BRAND]) {
|
|
||||||
// CM bug workaround to keep the mark at line start when the above line is removed
|
|
||||||
mark.inclusiveRight = true;
|
|
||||||
Object.assign(mark, tmProtoOvr);
|
|
||||||
toggleMark.call(mark, true, mark[BM_DATA] = mark.lines[0]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
function toggleMark(state, line = this[BM_DATA]) {
|
|
||||||
this.doc[state ? 'addLineClass' : 'removeLineClass'](line, 'gutter', BM_CLS);
|
|
||||||
if (state) {
|
|
||||||
const bms = this.doc.cm.state.sublimeBookmarks;
|
|
||||||
if (!bms.includes(this)) bms.push(this);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
//#endregion
|
|
||||||
})();
|
|
File diff suppressed because one or more lines are too long
115
edit/colorpicker-helper.js
Normal file
115
edit/colorpicker-helper.js
Normal file
|
@ -0,0 +1,115 @@
|
||||||
|
/* global CodeMirror loadScript editors showHelp */
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
onDOMscriptReady('/colorview.js').then(() => {
|
||||||
|
onDOMready().then(() => {
|
||||||
|
$('#colorpicker-settings').onclick = configureColorpicker;
|
||||||
|
});
|
||||||
|
prefs.subscribe(['editor.colorpicker.hotkey'], registerHotkey);
|
||||||
|
prefs.subscribe(['editor.colorpicker'], setColorpickerOption);
|
||||||
|
setColorpickerOption(null, prefs.get('editor.colorpicker'));
|
||||||
|
|
||||||
|
function setColorpickerOption(id, enabled) {
|
||||||
|
const defaults = CodeMirror.defaults;
|
||||||
|
const keyName = prefs.get('editor.colorpicker.hotkey');
|
||||||
|
defaults.colorpicker = enabled;
|
||||||
|
if (enabled) {
|
||||||
|
if (keyName) {
|
||||||
|
CodeMirror.commands.colorpicker = invokeColorpicker;
|
||||||
|
defaults.extraKeys = defaults.extraKeys || {};
|
||||||
|
defaults.extraKeys[keyName] = 'colorpicker';
|
||||||
|
}
|
||||||
|
defaults.colorpicker = {
|
||||||
|
forceUpdate: editors.length > 0,
|
||||||
|
tooltip: t('colorpickerTooltip'),
|
||||||
|
popup: {
|
||||||
|
tooltipForSwitcher: t('colorpickerSwitchFormatTooltip'),
|
||||||
|
hexUppercase: prefs.get('editor.colorpicker.hexUppercase'),
|
||||||
|
hideDelay: 5000,
|
||||||
|
embedderCallback: state => {
|
||||||
|
['hexUppercase', 'color']
|
||||||
|
.filter(name => state[name] !== prefs.get('editor.colorpicker.' + name))
|
||||||
|
.forEach(name => prefs.set('editor.colorpicker.' + name, state[name]));
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
if (defaults.extraKeys) {
|
||||||
|
delete defaults.extraKeys[keyName];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// on page load runs before CodeMirror.setOption is defined
|
||||||
|
editors.forEach(cm => cm.setOption('colorpicker', defaults.colorpicker));
|
||||||
|
}
|
||||||
|
|
||||||
|
function registerHotkey(id, hotkey) {
|
||||||
|
CodeMirror.commands.colorpicker = invokeColorpicker;
|
||||||
|
const extraKeys = CodeMirror.defaults.extraKeys;
|
||||||
|
for (const key in extraKeys) {
|
||||||
|
if (extraKeys[key] === 'colorpicker') {
|
||||||
|
delete extraKeys[key];
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (hotkey) {
|
||||||
|
extraKeys[hotkey] = 'colorpicker';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function invokeColorpicker(cm) {
|
||||||
|
cm.state.colorpicker.openPopup(prefs.get('editor.colorpicker.color'));
|
||||||
|
}
|
||||||
|
|
||||||
|
function configureColorpicker(event) {
|
||||||
|
event.preventDefault();
|
||||||
|
const input = $create('input', {
|
||||||
|
type: 'search',
|
||||||
|
spellcheck: false,
|
||||||
|
value: prefs.get('editor.colorpicker.hotkey'),
|
||||||
|
onkeydown(event) {
|
||||||
|
event.preventDefault();
|
||||||
|
event.stopPropagation();
|
||||||
|
const key = CodeMirror.keyName(event);
|
||||||
|
switch (key) {
|
||||||
|
case 'Enter':
|
||||||
|
if (this.checkValidity()) {
|
||||||
|
$('#help-popup .dismiss').onclick();
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
case 'Esc':
|
||||||
|
$('#help-popup .dismiss').onclick();
|
||||||
|
return;
|
||||||
|
default:
|
||||||
|
// disallow: [Shift?] characters, modifiers-only, [modifiers?] + Esc, Tab, nav keys
|
||||||
|
if (!key || new RegExp('^(' + [
|
||||||
|
'(Back)?Space',
|
||||||
|
'(Shift-)?.', // a single character
|
||||||
|
'(Shift-?|Ctrl-?|Alt-?|Cmd-?){0,2}(|Esc|Tab|(Page)?(Up|Down)|Left|Right|Home|End|Insert|Delete)',
|
||||||
|
].join('|') + ')$', 'i').test(key)) {
|
||||||
|
this.value = key || this.value;
|
||||||
|
this.setCustomValidity('Not allowed');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
this.value = key;
|
||||||
|
this.setCustomValidity('');
|
||||||
|
prefs.set('editor.colorpicker.hotkey', key);
|
||||||
|
},
|
||||||
|
oninput() {
|
||||||
|
// fired on pressing "x" to clear the field
|
||||||
|
prefs.set('editor.colorpicker.hotkey', '');
|
||||||
|
},
|
||||||
|
onpaste(event) {
|
||||||
|
event.preventDefault();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
const popup = showHelp(t('helpKeyMapHotkey'), input);
|
||||||
|
if (this instanceof Element) {
|
||||||
|
const bounds = this.getBoundingClientRect();
|
||||||
|
popup.style.left = bounds.right + 10 + 'px';
|
||||||
|
popup.style.top = bounds.top - popup.clientHeight / 2 + 'px';
|
||||||
|
popup.style.right = 'auto';
|
||||||
|
}
|
||||||
|
input.focus();
|
||||||
|
}
|
||||||
|
});
|
|
@ -1,68 +0,0 @@
|
||||||
/* global messageBoxProxy */// dom.js
|
|
||||||
/* global API */// msg.js
|
|
||||||
/* global clamp debounce */// toolbox.js
|
|
||||||
/* global editor */
|
|
||||||
/* global prefs */
|
|
||||||
/* global t */// localization.js
|
|
||||||
'use strict';
|
|
||||||
|
|
||||||
(async function AutosaveDrafts() {
|
|
||||||
const makeId = () => editor.style.id || 'new';
|
|
||||||
let delay;
|
|
||||||
let port;
|
|
||||||
connectPort();
|
|
||||||
|
|
||||||
const draft = await API.drafts.get(makeId());
|
|
||||||
if (draft && draft.isUsercss === editor.isUsercss) {
|
|
||||||
const date = makeRelativeDate(draft.date);
|
|
||||||
if (await messageBoxProxy.confirm(t('draftAction'), 'danger', t('draftTitle', date))) {
|
|
||||||
await editor.replaceStyle(draft.style, draft);
|
|
||||||
} else {
|
|
||||||
API.drafts.delete(makeId());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
editor.dirty.onChange(isDirty => isDirty ? connectPort() : port.disconnect());
|
|
||||||
editor.dirty.onDataChange(isDirty => debounce(updateDraft, isDirty ? delay : 0));
|
|
||||||
|
|
||||||
prefs.subscribe('editor.autosaveDraft', (key, val) => {
|
|
||||||
delay = clamp(val * 1000 | 0, 1000, 2 ** 32 - 1);
|
|
||||||
const t = debounce.timers.get(updateDraft);
|
|
||||||
if (t != null) debounce(updateDraft, t ? delay : 0);
|
|
||||||
}, {runNow: true});
|
|
||||||
|
|
||||||
function connectPort() {
|
|
||||||
port = chrome.runtime.connect({name: 'draft:' + makeId()});
|
|
||||||
}
|
|
||||||
|
|
||||||
function makeRelativeDate(date) {
|
|
||||||
let delta = (Date.now() - date) / 1000;
|
|
||||||
if (delta >= 0 && Intl.RelativeTimeFormat) {
|
|
||||||
for (const [span, unit, frac = 1] of [
|
|
||||||
[60, 'second', 0],
|
|
||||||
[60, 'minute', 0],
|
|
||||||
[24, 'hour'],
|
|
||||||
[7, 'day'],
|
|
||||||
[4, 'week'],
|
|
||||||
[12, 'month'],
|
|
||||||
[1e99, 'year'],
|
|
||||||
]) {
|
|
||||||
if (delta < span) {
|
|
||||||
return new Intl.RelativeTimeFormat({style: 'short'}).format(-delta.toFixed(frac), unit);
|
|
||||||
}
|
|
||||||
delta /= span;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return date.toLocaleString();
|
|
||||||
}
|
|
||||||
|
|
||||||
function updateDraft(isDirty = editor.dirty.isDirty()) {
|
|
||||||
if (!isDirty) return;
|
|
||||||
API.drafts.put({
|
|
||||||
date: Date.now(),
|
|
||||||
isUsercss: editor.isUsercss,
|
|
||||||
style: editor.getValue(true),
|
|
||||||
si: editor.makeScrollInfo(),
|
|
||||||
}, makeId());
|
|
||||||
}
|
|
||||||
})();
|
|
877
edit/edit.css
877
edit/edit.css
File diff suppressed because it is too large
Load Diff
970
edit/edit.js
970
edit/edit.js
File diff suppressed because it is too large
Load Diff
118
edit/editor-worker-body.js
Normal file
118
edit/editor-worker-body.js
Normal file
|
@ -0,0 +1,118 @@
|
||||||
|
/* global importScripts parseMozFormat parserlib CSSLint require */
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
createAPI({
|
||||||
|
csslint: (code, config) => {
|
||||||
|
loadParserLib();
|
||||||
|
loadScript(['/vendor-overwrites/csslint/csslint.js']);
|
||||||
|
return CSSLint.verify(code, config).messages
|
||||||
|
.map(m => Object.assign(m, {rule: {id: m.rule.id}}));
|
||||||
|
},
|
||||||
|
stylelint: (code, config) => {
|
||||||
|
loadScript(['/vendor/stylelint-bundle/stylelint-bundle.min.js']);
|
||||||
|
return require('stylelint').lint({code, config});
|
||||||
|
},
|
||||||
|
parseMozFormat: data => {
|
||||||
|
loadParserLib();
|
||||||
|
loadScript(['/js/moz-parser.js']);
|
||||||
|
return parseMozFormat(data);
|
||||||
|
},
|
||||||
|
getStylelintRules,
|
||||||
|
getCsslintRules
|
||||||
|
});
|
||||||
|
|
||||||
|
function getCsslintRules() {
|
||||||
|
loadScript(['/vendor-overwrites/csslint/csslint.js']);
|
||||||
|
return CSSLint.getRules().map(rule => {
|
||||||
|
const output = {};
|
||||||
|
for (const [key, value] of Object.entries(rule)) {
|
||||||
|
if (typeof value !== 'function') {
|
||||||
|
output[key] = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return output;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function getStylelintRules() {
|
||||||
|
loadScript(['/vendor/stylelint-bundle/stylelint-bundle.min.js']);
|
||||||
|
const stylelint = require('stylelint');
|
||||||
|
const options = {};
|
||||||
|
const rxPossible = /\bpossible:("(?:[^"]*?)"|\[(?:[^\]]*?)\]|\{(?:[^}]*?)\})/g;
|
||||||
|
const rxString = /"([-\w\s]{3,}?)"/g;
|
||||||
|
for (const id of Object.keys(stylelint.rules)) {
|
||||||
|
const ruleCode = String(stylelint.rules[id]);
|
||||||
|
const sets = [];
|
||||||
|
let m, mStr;
|
||||||
|
while ((m = rxPossible.exec(ruleCode))) {
|
||||||
|
const possible = m[1];
|
||||||
|
const set = [];
|
||||||
|
while ((mStr = rxString.exec(possible))) {
|
||||||
|
const s = mStr[1];
|
||||||
|
if (s.includes(' ')) {
|
||||||
|
set.push(...s.split(/\s+/));
|
||||||
|
} else {
|
||||||
|
set.push(s);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (possible.includes('ignoreAtRules')) {
|
||||||
|
set.push('ignoreAtRules');
|
||||||
|
}
|
||||||
|
if (possible.includes('ignoreShorthands')) {
|
||||||
|
set.push('ignoreShorthands');
|
||||||
|
}
|
||||||
|
if (set.length) {
|
||||||
|
sets.push(set);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (sets.length) {
|
||||||
|
options[id] = sets;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return options;
|
||||||
|
}
|
||||||
|
|
||||||
|
function loadParserLib() {
|
||||||
|
if (typeof parserlib !== 'undefined') {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
importScripts('/vendor-overwrites/csslint/parserlib.js');
|
||||||
|
parserlib.css.Tokens[parserlib.css.Tokens.COMMENT].hide = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const loadedUrls = new Set();
|
||||||
|
function loadScript(urls) {
|
||||||
|
urls = urls.filter(u => !loadedUrls.has(u));
|
||||||
|
importScripts(...urls);
|
||||||
|
urls.forEach(u => loadedUrls.add(u));
|
||||||
|
}
|
||||||
|
|
||||||
|
function createAPI(methods) {
|
||||||
|
self.onmessage = e => {
|
||||||
|
const message = e.data;
|
||||||
|
Promise.resolve()
|
||||||
|
.then(() => methods[message.action](...message.args))
|
||||||
|
.then(result => ({
|
||||||
|
id: message.id,
|
||||||
|
error: false,
|
||||||
|
data: result
|
||||||
|
}))
|
||||||
|
.catch(err => ({
|
||||||
|
id: message.id,
|
||||||
|
error: true,
|
||||||
|
data: cloneError(err)
|
||||||
|
}))
|
||||||
|
.then(data => self.postMessage(data));
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function cloneError(err) {
|
||||||
|
return Object.assign({
|
||||||
|
name: err.name,
|
||||||
|
stack: err.stack,
|
||||||
|
message: err.message,
|
||||||
|
lineNumber: err.lineNumber,
|
||||||
|
columnNumber: err.columnNumber,
|
||||||
|
fileName: err.fileName
|
||||||
|
}, err);
|
||||||
|
}
|
|
@ -1,176 +1,39 @@
|
||||||
/* global createWorkerApi */// worker-util.js
|
|
||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
(() => {
|
// eslint-disable-next-line no-var
|
||||||
let sugarss = false;
|
var editorWorker = (() => {
|
||||||
|
let worker;
|
||||||
/** @namespace EditorWorker */
|
return new Proxy({}, {
|
||||||
createWorkerApi({
|
get: (target, prop) =>
|
||||||
|
(...args) => {
|
||||||
async csslint(code, config) {
|
if (!worker) {
|
||||||
require(['/js/csslint/parserlib', '/js/csslint/csslint']); /* global CSSLint */
|
worker = createWorker();
|
||||||
return CSSLint
|
|
||||||
.verify(code, config).messages
|
|
||||||
.map(m => Object.assign(m, {rule: {id: m.rule.id}}));
|
|
||||||
},
|
|
||||||
|
|
||||||
getCssPropsValues() {
|
|
||||||
require(['/js/csslint/parserlib']); /* global parserlib */
|
|
||||||
const {
|
|
||||||
css: {Colors, GlobalKeywords, Properties},
|
|
||||||
util: {describeProp},
|
|
||||||
} = parserlib;
|
|
||||||
const namedColors = Object.keys(Colors);
|
|
||||||
const rxNonWord = /(?:<.+?>|[^-\w<(]+\d*)+/g;
|
|
||||||
const res = {};
|
|
||||||
// moving vendor-prefixed props to the end
|
|
||||||
const cmp = (a, b) => a[0] === '-' && b[0] !== '-' ? 1 : a < b ? -1 : a > b;
|
|
||||||
for (const [k, v] of Object.entries(Properties)) {
|
|
||||||
res[k] = false;
|
|
||||||
if (typeof v === 'string') {
|
|
||||||
let last = '';
|
|
||||||
const uniq = [];
|
|
||||||
// strip definitions of function arguments
|
|
||||||
const desc = describeProp(v).replace(/([-\w]+)\(.*?\)/g, 'z-$1');
|
|
||||||
const descNoColors = desc.replace(/<named-color>/g, '');
|
|
||||||
// add a prefix to functions to group them at the end
|
|
||||||
const words = descNoColors.split(rxNonWord).sort(cmp);
|
|
||||||
for (let w of words) {
|
|
||||||
if (w.startsWith('z-')) w = w.slice(2) + '(';
|
|
||||||
if (w !== last) uniq.push(last = w);
|
|
||||||
}
|
|
||||||
if (desc !== descNoColors) uniq.push(...namedColors);
|
|
||||||
if (uniq.length) res[k] = uniq;
|
|
||||||
}
|
}
|
||||||
|
return worker.invoke(prop, args);
|
||||||
}
|
}
|
||||||
return {all: res, global: GlobalKeywords};
|
|
||||||
},
|
|
||||||
|
|
||||||
getRules(linter) {
|
|
||||||
return ruleRetriever[linter](); // eslint-disable-line no-use-before-define
|
|
||||||
},
|
|
||||||
|
|
||||||
metalint(code) {
|
|
||||||
require(['/js/meta-parser']); /* global metaParser */
|
|
||||||
const result = metaParser.lint(code);
|
|
||||||
// extract needed info
|
|
||||||
result.errors = result.errors.map(err => ({
|
|
||||||
code: err.code,
|
|
||||||
args: err.args,
|
|
||||||
message: err.message,
|
|
||||||
index: err.index,
|
|
||||||
}));
|
|
||||||
return result;
|
|
||||||
},
|
|
||||||
|
|
||||||
async stylelint(opts) {
|
|
||||||
require(['/vendor/stylelint-bundle/stylelint-bundle.min']); /* global stylelint */
|
|
||||||
// Stylus-lang allows a trailing ";" but sugarss doesn't, so we monkeypatch it
|
|
||||||
stylelint.SugarSSParser.prototype.checkSemicolon = tt => {
|
|
||||||
while (tt.length && tt[tt.length - 1][0] === ';') tt.pop();
|
|
||||||
};
|
|
||||||
for (const pass of opts.mode === 'stylus' ? [sugarss, !sugarss] : [-1]) {
|
|
||||||
/* We try sugarss (for indented stylus-lang), then css mode, switching them on failure,
|
|
||||||
* so that the succeeding syntax will be used next time first. */
|
|
||||||
opts.config.customSyntax = !pass ? 'sugarss' : '';
|
|
||||||
try {
|
|
||||||
const res = await stylelint.createLinter(opts)._lintSource(opts);
|
|
||||||
if (pass !== -1) sugarss = pass;
|
|
||||||
return collectStylelintResults(res, opts);
|
|
||||||
} catch (e) {
|
|
||||||
const fatal = pass === -1 ||
|
|
||||||
!pass && !/^CssSyntaxError:.+?Unnecessary curly bracket/.test(e) ||
|
|
||||||
pass && !/^CssSyntaxError:.+?Unknown word[\s\S]*?\.decl\s/.test(`${e}${e.stack}`);
|
|
||||||
if (fatal) {
|
|
||||||
return [{
|
|
||||||
from: {line: e.line - 1, ch: e.column - 1},
|
|
||||||
to: {line: e.line - 1, ch: e.column - 1},
|
|
||||||
message: e.reason,
|
|
||||||
severity: 'error',
|
|
||||||
rule: e.name,
|
|
||||||
}];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
});
|
});
|
||||||
|
|
||||||
const ruleRetriever = {
|
function createWorker() {
|
||||||
|
let id = 0;
|
||||||
|
const pendingResponse = new Map();
|
||||||
|
const worker = new Worker('/edit/editor-worker-body.js');
|
||||||
|
worker.onmessage = e => {
|
||||||
|
const message = e.data;
|
||||||
|
pendingResponse.get(message.id)[message.error ? 'reject' : 'resolve'](message.data);
|
||||||
|
pendingResponse.delete(message.id);
|
||||||
|
};
|
||||||
|
return {invoke};
|
||||||
|
|
||||||
csslint() {
|
function invoke(action, args) {
|
||||||
require(['/js/csslint/csslint']);
|
return new Promise((resolve, reject) => {
|
||||||
return CSSLint.getRuleList().map(rule => {
|
pendingResponse.set(id, {resolve, reject});
|
||||||
const output = {};
|
worker.postMessage({
|
||||||
for (const [key, value] of Object.entries(rule)) {
|
id,
|
||||||
if (typeof value !== 'function') {
|
action,
|
||||||
output[key] = value;
|
args
|
||||||
}
|
});
|
||||||
}
|
id++;
|
||||||
return output;
|
|
||||||
});
|
|
||||||
},
|
|
||||||
|
|
||||||
stylelint() {
|
|
||||||
require(['/vendor/stylelint-bundle/stylelint-bundle.min']);
|
|
||||||
const options = {};
|
|
||||||
const rxPossible = /\bpossible:("(?:[^"]*?)"|\[(?:[^\]]*?)\]|\{(?:[^}]*?)\})/g;
|
|
||||||
const rxString = /"([-\w\s]{3,}?)"/g;
|
|
||||||
for (const [id, rule] of Object.entries(stylelint.rules)) {
|
|
||||||
const ruleCode = `${rule()}`;
|
|
||||||
const sets = [];
|
|
||||||
let m, mStr;
|
|
||||||
while ((m = rxPossible.exec(ruleCode))) {
|
|
||||||
const possible = m[1];
|
|
||||||
const set = [];
|
|
||||||
while ((mStr = rxString.exec(possible))) {
|
|
||||||
const s = mStr[1];
|
|
||||||
if (s.includes(' ')) {
|
|
||||||
set.push(...s.split(/\s+/));
|
|
||||||
} else {
|
|
||||||
set.push(s);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (possible.includes('ignoreAtRules')) {
|
|
||||||
set.push('ignoreAtRules');
|
|
||||||
}
|
|
||||||
if (possible.includes('ignoreShorthands')) {
|
|
||||||
set.push('ignoreShorthands');
|
|
||||||
}
|
|
||||||
if (set.length) {
|
|
||||||
sets.push(set);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
options[id] = sets;
|
|
||||||
}
|
|
||||||
return options;
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
function collectStylelintResults({messages}, {mode}) {
|
|
||||||
/* We hide nonfatal "//" warnings since we lint with sugarss without applying @preprocessor.
|
|
||||||
* We can't easily pre-remove "//" comments which may be inside strings, comments, url(), etc.
|
|
||||||
* And even if we did, it'd be wrong to hide potential bugs in stylus-lang like #1460 */
|
|
||||||
const isLess = mode === 'text/x-less';
|
|
||||||
const slashCommentAllowed = isLess || mode === 'stylus';
|
|
||||||
const res = [];
|
|
||||||
for (const m of messages) {
|
|
||||||
if (/deprecation|invalidOption/.test(m.stylelintType)) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
const {rule} = m;
|
|
||||||
const msg = m.text.replace(/^Unexpected\s+/, '').replace(` (${rule})`, '');
|
|
||||||
if (slashCommentAllowed && msg.includes('"//"') ||
|
|
||||||
isLess && /^unknown at-rule "@[-\w]+:"/.test(msg) /* LESS variables */) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
res.push({
|
|
||||||
from: {line: m.line - 1, ch: m.column - 1},
|
|
||||||
to: {line: m.endLine - 1, ch: m.endColumn - 1},
|
|
||||||
message: msg[0].toUpperCase() + msg.slice(1),
|
|
||||||
severity: m.severity,
|
|
||||||
rule,
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
return res;
|
|
||||||
}
|
}
|
||||||
})();
|
})();
|
||||||
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user