Merge master

This commit is contained in:
eight04 2020-03-31 22:53:15 +08:00
commit 5299e29217
122 changed files with 14066 additions and 13813 deletions

View File

@ -31,7 +31,7 @@ rules:
dot-location: [2, property]
dot-notation: [0]
eol-last: [2]
eqeqeq: [1, always]
eqeqeq: [1, smart]
func-call-spacing: [2, never]
func-name-matching: [0]
func-names: [0]
@ -84,7 +84,7 @@ rules:
no-empty-function: [0]
no-empty-pattern: [2]
no-empty: [2, {allowEmptyCatch: true}]
no-eq-null: [2]
no-eq-null: [0]
no-eval: [2]
no-ex-assign: [2]
no-extend-native: [2]
@ -211,3 +211,12 @@ rules:
wrap-iife: [2, inside]
yield-star-spacing: [2, {before: true, after: false}]
yoda: [2, never]
overrides:
- files: [tools/*]
env:
node: true
browser: false
webextensions: false
parserOptions:
ecmaVersion: 2017

View File

@ -34,25 +34,9 @@ You can help us translate the extension on [Transifex](https://www.transifex.com
* 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.
## Scripts
## Build scripts
* `npm run lint` - Run ESLint on all JavaScript files.
* `npm run update` - Runs update-node & update-main scripts.
* `npm run update-quick` - Updates development dependencies (uses `npm update`; does not include new dependencies).
* `npm run update-locales` (admin only)- Updates locale files from Transifex. See the [updating locale files section](#updating-locale-files-admin-only) for more details.
* `npm run update-main` - Runs update-versions & update-codemirror.
* `npm run update-node` - Update development dependencies, removes & reinstalls `node_modules` folder (slow).
* `npm run update-transifex` (admin only) - Upload `en/messages.json` source to Transifex.
* `npm run update-vendor` - Update codemirror, codemirror themes & other vendor libraries.
* `npm run update-versions` - Update version of `manifest.json` to match `package.json`.
* `npm run zip` - Run update-versions, then compress required files into a zip file.
## Updating locale files (admin only)
* Make sure you have the Transifex client installed. Follow the instructions on [this page](https://docs.transifex.com/client/installing-the-client).
* Contact another admin if you need the `.transifexrc` file in the root folder. It includes the API key to use Transifex's API.
* Use `npm run update-locales` in the command line to [update the language files](https://docs.transifex.com/client/pull) in the repo.
* Use `npm run update-transifex` in the command line to [upload the source](https://docs.transifex.com/client/push) `en/messages.json` file to Transifex.
See [BUILD.md](../BUILD.md) for more information.
## Contact us

1
.gitignore vendored
View File

@ -2,7 +2,6 @@
pull_locales_login.rb
.vscode
node_modules/
package-lock.json
yarn.lock
*.zip
.eslintcache

70
BUILD.md Normal file
View File

@ -0,0 +1,70 @@
# 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.

View File

@ -398,14 +398,6 @@
"message": "Управление",
"description": "Link to open the manage page."
},
"openOptionsManage": {
"message": "Прозорец за настройките",
"description": "Go to Options UI"
},
"openOptionsPopup": {
"message": "Настройки",
"description": "Go to Options UI"
},
"openStylesManager": {
"message": "Управление на стиловете",
"description": "Label for the style maanger opener in the browser action context menu."

View File

@ -230,10 +230,6 @@
"message": "Провери за обновления",
"description": "Label for the checkbox to save current URL for update check"
},
"installUpdateUnavailable": {
"message": "За да разрешите проверка за обновления, пуснете файла върху лентата с табове, или в метаданните на стила укажете @updateURL.",
"description": ""
},
"license": {
"message": "Лиценз",
"description": "Label for the license"
@ -309,18 +305,10 @@
"message": "Получи се грешка докато наблюдавахме файла",
"description": "The label of live-reload error"
},
"liveReloadInstallHint": {
"message": "Преглед на живо е разрешен, така че инсталирания стил ще бъде обновен автоматично при външни промени докато двата прозореца с кода и оригинала са отворени.",
"description": "The label of live-reload feature"
},
"liveReloadLabel": {
"message": "Преглед на живо",
"description": "The label of live-reload feature"
},
"liveReloadUnavailable": {
"message": "За да разрешите презареждане в реално време, пуснете файла върху лентата с табове (областта, където са показани заглавията на табовете).",
"description": ""
},
"manageFilters": {
"message": "Филтри",
"description": "Label for filters container"

View File

@ -597,10 +597,6 @@
"message": "Při sledování souboru došlo k chybě",
"description": "The label of live-reload error"
},
"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.",
"description": "The label of live-reload feature"
},
"liveReloadLabel": {
"message": "Živá aktualizace",
"description": "The label of live-reload feature"
@ -723,14 +719,6 @@
"message": "Spravovat",
"description": "Link to open the manage page."
},
"openOptionsManage": {
"message": "Možnosti rozhraní",
"description": "Go to Options UI"
},
"openOptionsPopup": {
"message": "Možnosti",
"description": "Go to Options UI"
},
"openStylesManager": {
"message": "Otevřít správce stylů",
"description": "Label for the style maanger opener in the browser action context menu."

View File

@ -232,6 +232,14 @@
"message": "Ja",
"description": "'Yes' button in a confirm dialog"
},
"copied": {
"message": "In die Zwischenablage kopiert",
"description": "Message shown when content has been copied to the clipboard"
},
"copy": {
"message": "In die Zwischenablage kopieren",
"description": "Tooltip for elements which can be copied"
},
"dateInstalled": {
"message": "Installationsdatum",
"description": "Option text for the user to sort the style by install date"
@ -272,6 +280,10 @@
"message": "Ziehe die Backup Datei zum importieren an irgendeinen Ort auf dieser Seite.",
"description": "Drag'n'drop message"
},
"dragDropUsercssTabstrip": {
"message": "Ziehe die Datei auf die Tableiste, um sie zu installieren.",
"description": "Message popup shown when erroneously dropping a usercss file into the manager page"
},
"editDeleteText": {
"message": "Löschen",
"description": "Label for the context menu item in the editor to delete selected text"
@ -421,6 +433,10 @@
"message": "Hotkey drücken",
"description": "Placeholder text of inputbox in keymap help popup on the edit style page. Must be very short"
},
"hostDisabled": {
"message": "Dieser Host wurde deaktiviert, weil die aktuell genutzte Version deines Browsers einen Fehler enthält.",
"description": "Tooltip for cloud host disabled"
},
"importAppendLabel": {
"message": "Zum Style anfügen",
"description": "Label for the button to import a style and append to the existing sections"
@ -514,10 +530,6 @@
"message": "Nach Updates suchen",
"description": "Label for the checkbox to save current URL for update check"
},
"installUpdateUnavailable": {
"message": "Ziehe die Datei auf die Tableiste oder definiere @updateURL in den Metadaten des Styles, um automatisch nach Updates zu suchen.",
"description": ""
},
"license": {
"message": "Lizenz",
"description": "Label for the license"
@ -594,17 +606,17 @@
"description": "The label of live-reload error"
},
"liveReloadInstallHint": {
"message": "Echtzeitaktualisierung ist aktiviert, sodass die Darstellung des jeweiligen Styles automatisch aktualisiert wird, wenn externe Änderungen erfolgen.",
"message": "Lasse diesen Tab geöffnet, um den Style bei externen Änderungen automatisch zu aktualisieren.",
"description": "The label of live-reload feature"
},
"liveReloadInstallHintFF": {
"message": "Lasse diesen und den Originaltab offen, um den Style bei externen Änderungen automatisch zu aktualisieren.",
"description": "The extra hint of live-reload feature shown only for file:// URLs in Firefox"
},
"liveReloadLabel": {
"message": "Echtzeitaktualisierung",
"description": "The label of live-reload feature"
},
"liveReloadUnavailable": {
"message": "Ziehe die Datei auf die Tableiste, um die Echtzeitaktualisierung nutzen zu können.",
"description": ""
},
"manageFavicons": {
"message": "Favicons in der \"Gilt für\" Spalte anzeigen",
"description": "Label for the checkbox that toggles applies-to favicons in the new UI on manage page"
@ -897,11 +909,7 @@
"message": "Verwalten",
"description": "Link to open the manage page."
},
"openOptionsManage": {
"message": "Optionen",
"description": "Go to Options UI"
},
"openOptionsPopup": {
"openOptions": {
"message": "Optionen",
"description": "Go to Options UI"
},
@ -957,6 +965,10 @@
"message": "Symbolleisten-Icon",
"description": ""
},
"optionsCustomizeSync": {
"message": "Mit Cloud synchronisieren",
"description": ""
},
"optionsHeading": {
"message": "Optionen",
"description": "Heading for options section on manage page."
@ -1001,6 +1013,70 @@
"message": "Style-Updateintervall in Stunden (0 zum Deaktivieren)",
"description": ""
},
"optionsSyncNone": {
"message": "Nichts",
"description": ""
},
"optionsSyncConnect": {
"message": "Verbinden",
"description": ""
},
"optionsSyncDisconnect": {
"message": "Trennen",
"description": ""
},
"optionsSyncSyncNow": {
"message": "Jetzt synchronisieren",
"description": ""
},
"optionsSyncLogin": {
"message": "Anmelden",
"description": ""
},
"optionsSyncStatusPull": {
"message": "Hole Style $loaded$ von $total$",
"description": "",
"placeholders": {
"loaded": {
"content": "$1"
},
"total": {
"content": "$2"
}
}
},
"optionsSyncStatusPush": {
"message": "Lade Style $loaded$ von $total$ hoch",
"description": "",
"placeholders": {
"loaded": {
"content": "$1"
},
"total": {
"content": "$2"
}
}
},
"optionsSyncStatusSyncing": {
"message": "Synchronisiere...",
"description": ""
},
"optionsSyncStatusConnecting": {
"message": "Verbinde...",
"description": ""
},
"optionsSyncStatusConnected": {
"message": "Verbunden",
"description": ""
},
"optionsSyncStatusDisconnecting": {
"message": "Trenne Verbindung...",
"description": ""
},
"optionsSyncStatusDisconnected": {
"message": "Verbindung getrennt",
"description": ""
},
"paginationCurrent": {
"message": "Aktuelle Seite",
"description": "Tooltip for the current page index in search results"
@ -1077,6 +1153,10 @@
"message": "Übernimmt die Änderungen nur vorübergehend.\nSpeichere den Style, um die Änderungen dauerhaft anzuwenden.",
"description": "Tooltip for the checkbox in style editor to enable live preview while editing."
},
"reload": {
"message": "Stylus Addon neu laden",
"description": "Context menu reload"
},
"replace": {
"message": "Ersetzen",
"description": "Label before the replace input field in the editor shown on Ctrl-H"
@ -1472,6 +1552,10 @@
"message": "diese URL",
"description": "Text for link in toolbar pop-up to write a new style for the current URL"
},
"syncDropboxDeprecated": {
"message": "Dropbox Import / Export wurde durch einen fortschrittlicheren Mechanismus auf der Optionsseite ersetzt.",
"description": ""
},
"overwriteFileExport": {
"message": "Willst du die existierende Datei überschreiben?",
"description": ""

View File

@ -290,6 +290,10 @@
"message": "Drop your backup file anywhere on this page to import.",
"description": "Drag'n'drop message"
},
"dragDropUsercssTabstrip": {
"message": "To install the file, drop it on the tab strip (the area where the tab titles are shown).",
"description": "Message popup shown when erroneously dropping a usercss file into the manager page"
},
"editDeleteText": {
"message": "Delete",
"description": "Label for the context menu item in the editor to delete selected text"
@ -445,6 +449,10 @@
"message": "Press a hotkey",
"description": "Placeholder text of inputbox in keymap help popup on the edit style page. Must be very short"
},
"hostDisabled": {
"message": "This host has been disabled due to a bug in the current version of the browser being used",
"description": "Tooltip for cloud host disabled"
},
"importAppendLabel": {
"message": "Append to style",
"description": "Label for the button to import a style and append to the existing sections"
@ -550,9 +558,6 @@
"message": "Check for updates",
"description": "Label for the checkbox to save current URL for update check"
},
"installUpdateUnavailable": {
"message": "To enable check for updates, drop the file on the tab strip or specify @updateURL in the style metadata."
},
"license": {
"message": "License",
"description": "Label for the license"
@ -633,16 +638,17 @@
"description": "The label of live-reload error"
},
"liveReloadInstallHint": {
"message": "Live reload is enabled so the installed style will be auto-updated on external changes while both this tab and the source file tab are open.",
"message": "Keep this tab open to auto-update the style on external changes.",
"description": "The label of live-reload feature"
},
"liveReloadInstallHintFF": {
"message": "Keep both this tab and the original tab open to auto-update the style on external changes.",
"description": "The extra hint of live-reload feature shown only for file:// URLs in Firefox"
},
"liveReloadLabel": {
"message": "Live reload",
"description": "The label of live-reload feature"
},
"liveReloadUnavailable": {
"message": "To enable live reload, drop the file on the tab strip (the area where the tab titles are shown)."
},
"manageFavicons": {
"message": "Favicons in applies-to column",
"description": "Label for the checkbox that toggles applies-to favicons in the new UI on manage page"
@ -939,11 +945,7 @@
"message": "Manage",
"description": "Link to open the manage page."
},
"openOptionsManage": {
"message": "Options UI",
"description": "Go to Options UI"
},
"openOptionsPopup": {
"openOptions": {
"message": "Options",
"description": "Go to Options UI"
},
@ -1169,6 +1171,10 @@
"message": "Temporarily applies the changes without saving.\nSave the style to make the changes permanent.",
"description": "Tooltip for the checkbox in style editor to enable live preview while editing."
},
"reload": {
"message": "Reload Stylus extension",
"description": "Context menu reload"
},
"replace": {
"message": "Replace",
"description": "Label before the replace input field in the editor shown on Ctrl-H"
@ -1574,6 +1580,9 @@
"syncDropboxStyles": {
"message": "Dropbox Export"
},
"syncDropboxDeprecated": {
"message": "Dropbox import/export is replaced by a more advanced style sync in the options page."
},
"retrieveDropboxSync": {
"message": "Dropbox Import"
},

View File

@ -35,10 +35,6 @@
"message": "Edit style",
"description": "Title of the page for editing styles"
},
"installUpdateUnavailable": {
"message": "To enable checking for updates, drop the file on the tab strip or specify @updateURL in the style metadata.",
"description": ""
},
"license": {
"message": "Licence",
"description": "Label for the license"

View File

@ -49,7 +49,7 @@
"description": "A warning that applies-to information won't show properly with minified CSS"
},
"appliesRegexpOption": {
"message": "URLs coincidentes con la regexp",
"message": "URLs que coinciden con la regexp",
"description": "Option to make the style apply to the entered string as a regular expression"
},
"appliesRemove": {
@ -85,7 +85,7 @@
"description": "Heading for backup"
},
"backupMessage": {
"message": "Seleccione un archivo o arrástrelo y suéltelo en esta página.",
"message": "Al exportar hará una copia de respaldo de TODOS los estilos instalados. Para restaurar una copia de respaldo, importe el archivo o arrástrelo a esta página.",
"description": "Message for backup"
},
"bckpInstStyles": {
@ -109,7 +109,7 @@
"description": "Text to display when checking a style for an update"
},
"clickToUninstall": {
"message": "Pulse para desinstalar",
"message": "Haga clic para desinstalar",
"description": "Label for the overlay on a style thumbnail when installed via inline search in the popup"
},
"cm_autoCloseBrackets": {
@ -140,6 +140,10 @@
"message": "Ajuste de línea",
"description": "Label for the checkbox controlling word wrap option for the style editor."
},
"cm_linter": {
"message": "Validador lint",
"description": "Select the linter to check for CSS issues"
},
"cm_matchHighlight": {
"message": "Resaltar",
"description": "Label for the drop-down list controlling the automatic highlighting of current word/selection occurrences in the style editor."
@ -236,6 +240,14 @@
"message": "Sí",
"description": "'Yes' button in a confirm dialog"
},
"copied": {
"message": "Copiado al portapapeles",
"description": "Message shown when content has been copied to the clipboard"
},
"copy": {
"message": "Copiar al portapapeles",
"description": "Tooltip for elements which can be copied"
},
"dateInstalled": {
"message": "Fecha de instalación",
"description": "Option text for the user to sort the style by install date"
@ -309,6 +321,14 @@
"message": "Activar",
"description": "Label for the button to enable a style"
},
"excludeStyleByDomainLabel": {
"message": "Excluir el dominio Actual",
"description": ""
},
"excludeStyleByUrlLabel": {
"message": "Excluir la URL actual",
"description": ""
},
"exportLabel": {
"message": "Exportar",
"description": "Label for the button to export a style ('edit' page) or all styles ('manage' page)"
@ -514,10 +534,6 @@
"message": "Buscar actualizaciones",
"description": "Label for the checkbox to save current URL for update check"
},
"installUpdateUnavailable": {
"message": "Para habilitar la búsqueda de actualizaciones, suelte el archivo en la pestaña o especifique @updateURL en los metadatos del estilo.",
"description": ""
},
"license": {
"message": "Licencia",
"description": "Label for the license"
@ -593,18 +609,10 @@
"message": "Se ha producido un error al visualizar el archivo",
"description": "The label of live-reload error"
},
"liveReloadInstallHint": {
"message": "La recarga en tiempo real está habilitada, por lo que el estilo instalado se actualizará automáticamente con los cambios externos mientras estén abiertas esta pestaña y la pestaña del archivo de origen.",
"description": "The label of live-reload feature"
},
"liveReloadLabel": {
"message": "Recarga en tiempo real",
"description": "The label of live-reload feature"
},
"liveReloadUnavailable": {
"message": "Para habilitar la recarga en tiempo real, suelte el archivo en la pestaña (el área donde se muestran los títulos de las pestañas).",
"description": ""
},
"manageFavicons": {
"message": "Favicons en la columna 'Se aplica a'",
"description": "Label for the checkbox that toggles applies-to favicons in the new UI on manage page"
@ -897,11 +905,7 @@
"message": "Administrar",
"description": "Link to open the manage page."
},
"openOptionsManage": {
"message": "Interfaz de opciones",
"description": "Go to Options UI"
},
"openOptionsPopup": {
"openOptions": {
"message": "Opciones",
"description": "Go to Options UI"
},
@ -1005,6 +1009,42 @@
"message": "Intervalo de actualización automática en horas (especifique 0 para deshabilitar)",
"description": ""
},
"optionsSyncConnect": {
"message": "Conectar",
"description": ""
},
"optionsSyncDisconnect": {
"message": "Desconectar",
"description": ""
},
"optionsSyncSyncNow": {
"message": "Sincronizar ahora",
"description": ""
},
"optionsSyncLogin": {
"message": "Iniciar Sesión",
"description": ""
},
"optionsSyncStatusSyncing": {
"message": "Sincronizando...",
"description": ""
},
"optionsSyncStatusConnecting": {
"message": "Conectando...",
"description": ""
},
"optionsSyncStatusConnected": {
"message": "Conectado",
"description": ""
},
"optionsSyncStatusDisconnecting": {
"message": "Desconectando...",
"description": ""
},
"optionsSyncStatusDisconnected": {
"message": "Desconectado",
"description": ""
},
"paginationCurrent": {
"message": "Página actual",
"description": "Tooltip for the current page index in search results"
@ -1049,6 +1089,10 @@
"message": "Mayús+clic o clic secundario abre el administrador con estilos aplicables para el sitio actual.",
"description": "Tooltip for the 'Manage' button in the popup."
},
"popupMenuButtonTooltip": {
"message": "Acciones",
"description": "Tooltip for menu button in popup."
},
"popupOpenEditInWindow": {
"message": "Abrir editor en una nueva ventana",
"description": "Label for the checkbox controlling 'edit' action behavior in the popup."

View File

@ -236,6 +236,14 @@
"message": "Jah",
"description": "'Yes' button in a confirm dialog"
},
"copied": {
"message": "Kopeeritud lõikelauale",
"description": "Message shown when content has been copied to the clipboard"
},
"copy": {
"message": "Kopeeri lõikelauale",
"description": "Tooltip for elements which can be copied"
},
"dateInstalled": {
"message": "Paigaldamise kuupäev",
"description": "Option text for the user to sort the style by install date"
@ -276,6 +284,10 @@
"message": "Lohista importimiseks oma varundusfail kuskile siia lehele.",
"description": "Drag'n'drop message"
},
"dragDropUsercssTabstrip": {
"message": "Faili installimiseks lohista see kaartide ribale (ala, kus näidatakse kaartide pealkirju).",
"description": "Message popup shown when erroneously dropping a usercss file into the manager page"
},
"editDeleteText": {
"message": "Kustuta",
"description": "Label for the context menu item in the editor to delete selected text"
@ -433,6 +445,10 @@
"message": "Vajuta kiirklahvi",
"description": "Placeholder text of inputbox in keymap help popup on the edit style page. Must be very short"
},
"hostDisabled": {
"message": "See host on keelatud hetkel kasutatavas brauseri praeguses versioonis oleva vea tõttu",
"description": "Tooltip for cloud host disabled"
},
"importAppendLabel": {
"message": "Lisa stiilile",
"description": "Label for the button to import a style and append to the existing sections"
@ -526,10 +542,6 @@
"message": "Kontrolli uuendusi",
"description": "Label for the checkbox to save current URL for update check"
},
"installUpdateUnavailable": {
"message": "Uuenduste kontrollimise lubamiseks lohista failid kaartide ribale või määratle stiili metaandmetes @updateURL.",
"description": ""
},
"license": {
"message": "Litsents",
"description": "Label for the license"
@ -609,18 +621,10 @@
"message": "Faili vaatamisel esines viga",
"description": "The label of live-reload error"
},
"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.",
"description": "The label of live-reload feature"
},
"liveReloadLabel": {
"message": "Reaalajas uuestilaadimine",
"description": "The label of live-reload feature"
},
"liveReloadUnavailable": {
"message": "Reaalajas uuestilaadimise lubamiseks lohista fail kaartide ribale (ala, kus näidatakse kaartide pealkirju).",
"description": ""
},
"manageFavicons": {
"message": "Lehe ikoonid rakendub-tulbas",
"description": "Label for the checkbox that toggles applies-to favicons in the new UI on manage page"
@ -815,11 +819,7 @@
"message": "Halda",
"description": "Link to open the manage page."
},
"openOptionsManage": {
"message": "Valikute liides",
"description": "Go to Options UI"
},
"openOptionsPopup": {
"openOptions": {
"message": "Valikud",
"description": "Go to Options UI"
},
@ -883,6 +883,10 @@
"message": "Uuendused",
"description": ""
},
"optionsCustomizeSync": {
"message": "Sünkrooni pilve",
"description": ""
},
"optionsHeading": {
"message": "Valikud",
"description": "Heading for options section on manage page."
@ -927,6 +931,34 @@
"message": "Kasutajastiilide automaatse uuendamise ajavahe (keelamiseks pane 0)",
"description": ""
},
"optionsSyncNone": {
"message": "Pole",
"description": ""
},
"optionsSyncSyncNow": {
"message": "Sünkrooni kohe",
"description": ""
},
"optionsSyncStatusSyncing": {
"message": "Sünkroonimine...",
"description": ""
},
"optionsSyncStatusConnecting": {
"message": "Ühendumine...",
"description": ""
},
"optionsSyncStatusConnected": {
"message": "Ühendatud",
"description": ""
},
"optionsSyncStatusDisconnecting": {
"message": "Lahtiühendamine...",
"description": ""
},
"optionsSyncStatusDisconnected": {
"message": "Lahti ühendatud",
"description": ""
},
"paginationCurrent": {
"message": "Praegune leht",
"description": "Tooltip for the current page index in search results"
@ -1402,6 +1434,18 @@
"message": "see URL",
"description": "Text for link in toolbar pop-up to write a new style for the current URL"
},
"syncDropboxStyles": {
"message": "Dropboxi eksportimine",
"description": ""
},
"syncDropboxDeprecated": {
"message": "Dropboxi importimine/eksportimine on asendatud edasijõudnuma stiilide sünkroonijaga valikute lehel.",
"description": ""
},
"retrieveDropboxSync": {
"message": "Dropboxi importimine",
"description": ""
},
"overwriteFileExport": {
"message": "Kas tahad olemasoleva faili üle kirjutada?",
"description": ""
@ -1414,10 +1458,26 @@
"message": "Oma stiilide importimiseks peaksid esmalt selle eksportima.",
"description": ""
},
"connectingDropbox": {
"message": "Dropboxiga ühendumine...",
"description": ""
},
"connectingDropboxNotAllowed": {
"message": "Dropboxiga ühendumine on saadaval ainult veebipoe kaudu installitud rakendustes",
"description": ""
},
"gettingStyles": {
"message": "Kõigi stiilide hankimine...",
"description": ""
},
"zipStyles": {
"message": "Stiilide kokkupakkimine...",
"description": ""
},
"unzipStyles": {
"message": "Stiilide lahtipakkimine...",
"description": ""
},
"readingStyles": {
"message": "Stiilide lugemine...",
"description": ""

View File

@ -244,6 +244,14 @@
"message": "Oui",
"description": "'Yes' button in a confirm dialog"
},
"copied": {
"message": "Copié dans le presse-papier",
"description": "Message shown when content has been copied to the clipboard"
},
"copy": {
"message": "Copier dans le presse-papier",
"description": "Tooltip for elements which can be copied"
},
"dateInstalled": {
"message": "Date d'installation",
"description": "Option text for the user to sort the style by install date"
@ -437,6 +445,10 @@
"message": "Pressez un raccourci clavier",
"description": "Placeholder text of inputbox in keymap help popup on the edit style page. Must be very short"
},
"hostDisabled": {
"message": "Cet hôte a été désactivé en raison d'un bogue dans la version actuelle du navigateur utilisé",
"description": "Tooltip for cloud host disabled"
},
"importAppendLabel": {
"message": "Ajouter au style",
"description": "Label for the button to import a style and append to the existing sections"
@ -530,10 +542,6 @@
"message": "Rechercher les mises à jour",
"description": "Label for the checkbox to save current URL for update check"
},
"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.",
"description": ""
},
"license": {
"message": "Licence",
"description": "Label for the license"
@ -609,18 +617,10 @@
"message": "Une erreur est survenue durant la surveillance du fichier",
"description": "The label of live-reload error"
},
"liveReloadInstallHint": {
"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 longlet du fichier source sont ouverts.",
"description": "The label of live-reload feature"
},
"liveReloadLabel": {
"message": "Rechargement immédiat",
"description": "The label of live-reload feature"
},
"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).",
"description": ""
},
"manageFavicons": {
"message": "Favicons dans la colonne « sapplique à »",
"description": "Label for the checkbox that toggles applies-to favicons in the new UI on manage page"
@ -913,14 +913,6 @@
"message": "Gestion",
"description": "Link to open the manage page."
},
"openOptionsManage": {
"message": "Paramètres d'interface graphique",
"description": "Go to Options UI"
},
"openOptionsPopup": {
"message": "Paramètres",
"description": "Go to Options UI"
},
"openStylesManager": {
"message": "Ouvrir le gestionnaire de styles",
"description": "Label for the style maanger opener in the browser action context menu."
@ -937,6 +929,10 @@
"message": "Exposer les iframes via HTML[stylus-iframe]",
"description": ""
},
"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 }",
"description": "Add attribute to iframe; make sure to include the double $$ in the css example, or the `$=` will be omitted in the displayed text."
},
"optionsAdvancedNewStyleAsUsercss": {
"message": "Écrire un nouveau style en tant que usercss",
"description": ""
@ -969,6 +965,10 @@
"message": "Mises à jour",
"description": ""
},
"optionsCustomizeSync": {
"message": "Synchroniser dans le nuage",
"description": ""
},
"optionsIconDark": {
"message": "Thème foncé",
"description": ""
@ -1009,6 +1009,70 @@
"message": "Intervalle de mise à jour automatique des styles utilisateur en heures (spécifier 0 pour désactiver)",
"description": ""
},
"optionsSyncNone": {
"message": "Aucun",
"description": ""
},
"optionsSyncConnect": {
"message": "Connecter",
"description": ""
},
"optionsSyncDisconnect": {
"message": "Disconnecter",
"description": ""
},
"optionsSyncSyncNow": {
"message": "Synchroniser maintenant",
"description": ""
},
"optionsSyncLogin": {
"message": "Se connecter",
"description": ""
},
"optionsSyncStatusPull": {
"message": "Récupération du style $loaded$de $total$",
"description": "",
"placeholders": {
"loaded": {
"content": "$1"
},
"total": {
"content": "$2"
}
}
},
"optionsSyncStatusPush": {
"message": "Application du style $loaded$ de $total$",
"description": "",
"placeholders": {
"loaded": {
"content": "$1"
},
"total": {
"content": "$2"
}
}
},
"optionsSyncStatusSyncing": {
"message": "Synchronisation...",
"description": ""
},
"optionsSyncStatusConnecting": {
"message": "Connection...",
"description": ""
},
"optionsSyncStatusConnected": {
"message": "Connecté",
"description": ""
},
"optionsSyncStatusDisconnecting": {
"message": "Disconnection...",
"description": ""
},
"optionsSyncStatusDisconnected": {
"message": "Disconnecté",
"description": ""
},
"paginationCurrent": {
"message": "Page courante",
"description": "Tooltip for the current page index in search results"
@ -1033,6 +1097,10 @@
"message": "Stylus a échoué à parser le usercss :",
"description": "The error message to show when stylus failed to parse usercss"
},
"popupAutoResort": {
"message": "Retrier les styles dans la popup à chaque (dés)activation",
"description": "Label for the checkbox controlling popup resorting."
},
"popupBorders": {
"message": "Ajouter des bordures blanches sur les côtés",
"description": ""
@ -1053,6 +1121,10 @@
"message": "Maj-clic ou clic droit ouvre le gestionnaire avec les styles applicables au site courant.",
"description": "Tooltip for the 'Manage' button in the popup."
},
"popupMenuButtonTooltip": {
"message": "Actions",
"description": "Tooltip for menu button in popup."
},
"popupOpenEditInWindow": {
"message": "Ouvrir l'éditeur dans une nouvelle fenêtre",
"description": "Label for the checkbox controlling 'edit' action behavior in the popup."
@ -1077,6 +1149,10 @@
"message": "Applique temporairement les changements sans sauvegarder.\nSauvegarde le style pour rendre les changements permanents.",
"description": "Tooltip for the checkbox in style editor to enable live preview while editing."
},
"reload": {
"message": "Recharger l´extension Stylus",
"description": "Context menu reload"
},
"replace": {
"message": "Remplacer",
"description": "Label before the replace input field in the editor shown on Ctrl-H"
@ -1480,6 +1556,10 @@
"message": "Exporter vers Dropbox",
"description": ""
},
"syncDropboxDeprecated": {
"message": "L´import/export Dropbox est remplacé par une méthode de synchronisation de styles plus avancés dans la page des options",
"description": ""
},
"retrieveDropboxSync": {
"message": "Importer depuis Dropbox",
"description": ""

View File

@ -4,7 +4,7 @@
"description": "Label for the button to go to the add style page"
},
"addStyleTitle": {
"message": "הוספת עיצוב",
"message": "הוסף עיצוב",
"description": "Title of the page for adding styles"
},
"alphaChannel": {
@ -12,11 +12,11 @@
"description": "Label of color's opacity"
},
"appliesAdd": {
"message": "הוספה",
"message": "הוסף",
"description": "Label for the button to add an 'applies' entry"
},
"appliesDisplay": {
"message": "מוחל על: $applies$",
"message": "חל על: $applies$",
"description": "Text on the manage screen to describe what the style applies to",
"placeholders": {
"applies": {
@ -32,8 +32,12 @@
"message": "קישורים תחת הדומיין",
"description": "Option to make the style apply to the entered string as a domain"
},
"appliesHelp": {
"message": "השתמש בהגדרות 'חל על' כדי להגביל את כתובות האתרים שהקוד בסעיף זה חל עליהם.",
"description": "Help text for 'applies to' section"
},
"appliesLabel": {
"message": "מוחל על",
"message": "חל על",
"description": "Label for 'applies to' fields on the edit/add screen"
},
"appliesLineWidgetLabel": {
@ -45,11 +49,11 @@
"description": "A warning that applies-to information won't show properly with minified CSS"
},
"appliesRegexpOption": {
"message": "קישורים התואמים regexp",
"message": "קישורים התואמים את ה־regexp",
"description": "Option to make the style apply to the entered string as a regular expression"
},
"appliesRemove": {
"message": "הסרה",
"message": "הסר",
"description": "Label for the button to remove an 'applies' entry"
},
"appliesRemoveError": {
@ -65,7 +69,7 @@
"description": "Text displayed for styles that apply to all sites"
},
"appliesUrlOption": {
"message": "קישור (URL)",
"message": "כתובת אתר",
"description": "Option to make the style apply to the entered string as a URL"
},
"appliesUrlPrefixOption": {
@ -77,7 +81,7 @@
"description": "Label for the button to apply all detected updates"
},
"author": {
"message": "כותב",
"message": "מחבר",
"description": "Label for the style author"
},
"backupButtons": {
@ -140,6 +144,10 @@
"message": "עטיפת מילים",
"description": "Label for the checkbox controlling word wrap option for the style editor."
},
"cm_linter": {
"message": "עורך שגיאות CSS",
"description": "Select the linter to check for CSS issues"
},
"cm_matchHighlight": {
"message": "הדגש",
"description": "Label for the drop-down list controlling the automatic highlighting of current word/selection occurrences in the style editor."
@ -148,14 +156,22 @@
"message": "בחירה בלבד",
"description": "Style editor's 'highglight' drop-down list option: highlight the occurrences of currently selected text"
},
"cm_matchHighlightToken": {
"message": "תג תחת הסמן",
"description": "Style editor's 'highglight' drop-down list option: highlight the occurrences of the word/token under cursor even if nothing is selected"
},
"cm_resizeGripHint": {
"message": "דאבל קליק להגדלה מירבית/איפוס הגובה",
"description": "Tooltip for the resize grip in style editor"
},
"cm_selectByTokens": {
"message": "דאבל קליק בוחר tokens",
"message": "דאבל קליק בוחר תגים",
"description": "Label for the checkbox in the editor."
},
"cm_selectByTokensTooltip": {
"message": "דוגמאות לתגים: .foo-bar-2 #aabbcc 0.32 !important\nבמצב מושבת: נבחרות מילים המופרדות בין פיסוק.",
"description": ""
},
"cm_smartIndent": {
"message": "השתמש בהזחה חכמה",
"description": "Label for the checkbox controlling smart indentation option for the style editor."
@ -168,6 +184,10 @@
"message": "ערכת נושא",
"description": "Label for the style editor's CSS theme."
},
"colorpickerSwitchFormatTooltip": {
"message": "החלף פורמטים: HEX -> RGB -> HSL.\nלחץ Shift כדי להפוך את הכיוון.\nגם באמצעות מקשי PgUp (PageUp), PgDn (PageDown).",
"description": "Tooltip for the switch button in the color picker popup in the style editor."
},
"colorpickerTooltip": {
"message": "פתח את פלטת בחירת הצבעים",
"description": "Tooltip for the colored squares shown before CSS colors in the style editor."
@ -228,6 +248,14 @@
"message": "כן",
"description": "'Yes' button in a confirm dialog"
},
"copied": {
"message": "הועתק אל לוח ההעתקה",
"description": "Message shown when content has been copied to the clipboard"
},
"copy": {
"message": "העתק אל לוח ההעתקה",
"description": "Tooltip for elements which can be copied"
},
"dateInstalled": {
"message": "תאריך התקנה",
"description": "Option text for the user to sort the style by install date"
@ -236,6 +264,10 @@
"message": "תאריך עדכון",
"description": "Option text for the user to sort the style by last update date"
},
"dbError": {
"message": "אירעה שגיאה בשימוש במסד הנתונים של Stylus. האם ברצונך לבקר בדף אינטרנט עם פתרונות אפשריים?",
"description": "Prompt when a DB error is encountered"
},
"defaultTheme": {
"message": "ברירת מחדל",
"description": "Default CodeMirror CSS theme option on the edit style page"
@ -248,6 +280,10 @@
"message": "מחק",
"description": "Label for the button to delete a style"
},
"description": {
"message": "עיצוב מחדש של האינטרנט באמצעות Stylus, מנהל סגנונות משתמש. Stylus מאפשר לך להתקין בקלות עיצובים וערכות נושא עבור אתרים פופולריים רבים.",
"description": "Extension description"
},
"disableAllStyles": {
"message": "השבת את כל העיצובים",
"description": "Label for the checkbox that turns all enabled styles off."
@ -265,15 +301,15 @@
"description": "Label for the context menu item in the editor to delete selected text"
},
"editGotoLine": {
"message": "Goto לשורה (או line:col)",
"message": "גש לשורה (או line:col)",
"description": "Go to line or line:column on Ctrl-G in style code editor"
},
"editStyleHeading": {
"message": "עריכת עיצוב",
"message": "ערוך עיצוב",
"description": "Title of the page for editing styles"
},
"editStyleLabel": {
"message": "עריכה",
"message": "ערוך",
"description": "Label for the button to go to the edit style page"
},
"editStyleTitle": {
@ -293,6 +329,14 @@
"message": "אפשר",
"description": "Label for the button to enable a style"
},
"excludeStyleByDomainLabel": {
"message": "אל תכלול את הדומיין הנוכחי",
"description": ""
},
"excludeStyleByUrlLabel": {
"message": "אל תכלול את כתובת האתר הנוכחית",
"description": ""
},
"exportLabel": {
"message": "ייצא",
"description": "Label for the button to export a style ('edit' page) or all styles ('manage' page)"
@ -313,14 +357,42 @@
"message": "תמיכה",
"description": "Label for the external link to style's support site"
},
"externalUsercssDocument": {
"message": "תיעוד ל־Usercss",
"description": "Label for the external link to usercss documentation"
},
"filteredStyles": {
"message": "$numShown$ מתוך $numTotal$ סך־הכל",
"description": "TL note - make this message short",
"placeholders": {
"numTotal": {
"content": "$2"
},
"numShown": {
"content": "$1"
}
}
},
"filteredStylesAllHidden": {
"message": "מסננים שהוחלו כעת אינם תואמים עיצובים",
"description": "Text shown when no styles match currently applied filter in the style manager"
},
"findStyles": {
"message": "מצא עיצובים",
"description": "Text for a link that gets a list of styles for the current site"
},
"findStylesForSite": {
"message": "מצא סגנונות נוספים לאתר זה",
"description": "Text for a link that gets a list of styles for the current site"
},
"findStylesInline": {
"message": "מוטבע",
"description": "Text for a checkbox that opens search results 'inline' (within the Stylus popup window)"
},
"findStylesInlineTooltip": {
"message": "הצג תוצאות חיפוש בחלון זה.",
"description": "Text for a checkbox that displays search results within the Stylus popup."
},
"genericAdd": {
"message": "הוספה",
"description": "Used in various places for an action that adds something"
@ -381,6 +453,10 @@
"message": "לחץ על המקש החם",
"description": "Placeholder text of inputbox in keymap help popup on the edit style page. Must be very short"
},
"hostDisabled": {
"message": "מארח זה הושבת בגלל באג בגרסה הנוכחית של הדפדפן בו נעשה שימוש",
"description": "Tooltip for cloud host disabled"
},
"importAppendLabel": {
"message": "צרף לעיצוב",
"description": "Label for the button to import a style and append to the existing sections"
@ -397,16 +473,32 @@
"message": "דרוס עיצוב",
"description": "Label for the button to import and overwrite current style"
},
"importReplaceTooltip": {
"message": "הסר את התוכן של הסגנון הנוכחי והחלף אותו עם העיצוב המיובא",
"description": "Label for the button to import and overwrite current style"
},
"importReportLegendAdded": {
"message": "נוספו",
"description": "Text after the number of styles added in the report shown after importing styles"
},
"importReportLegendIdentical": {
"message": "זהה דולג",
"description": "Text after the number of styles skipped due to being identical to the already installed ones in the report shown after importing styles"
},
"importReportLegendInvalid": {
"message": "לא חוקי דולג",
"description": "Text after the number of styles skipped due to being invalid (not a Stylus/Stylish backup file probably) in the report shown after importing styles"
},
"importReportLegendUpdatedBoth": {
"message": "מטא דאטה והקוד עודכנו",
"description": "Text after the number of styles updated entirely in the report shown after importing styles"
},
"importReportLegendUpdatedCode": {
"message": "קודים עודכנו",
"description": "Text after the number of styles with updated code (meta info is unchanged) in the report shown after importing styles"
},
"importReportLegendUpdatedMeta": {
"message": "מידע meta עודכנו",
"message": טא דאטה עודכן",
"description": "Text after the number of styles with updated meta info like name/url in the report shown after importing styles"
},
"importReportTitle": {
@ -445,14 +537,19 @@
"message": "התקן עדכון",
"description": "Label for the button to install an update for a single style"
},
"installUpdateFrom": {
"message": "נכון לעכשיו הסגנון מעודכן מ־ $url$",
"description": "Label to describe where the style gets update",
"placeholders": {
"url": {
"content": "$1"
}
}
},
"installUpdateFromLabel": {
"message": "בדוק עדכונים",
"description": "Label for the checkbox to save current URL for update check"
},
"installUpdateUnavailable": {
"message": "על־מנת לאפשר בדיקת עדכונים, אנא שחרר את הקובץ על רצועת הכרטיסיות או ציין @updateURL ב־metadata של העיצוב.",
"description": ""
},
"license": {
"message": "רישיון",
"description": "Label for the license"
@ -469,6 +566,15 @@
"message": "תרגום",
"description": "Transifex link text on the manage page"
},
"linterCSSLintIncompatible": {
"message": "עורך שגיאות ה־CSS אינו תומך ב־ $preprocessorname$ כמעבד מקדים",
"description": "The label to display when the preprocessor isn't compatible with CSSLint",
"placeholders": {
"preprocessorname": {
"content": "$1"
}
}
},
"linterCSSLintSettings": {
"message": "(הגדר כלל כ: 0 = מושבת; 1 = אזהרה; 2 = שגיאה)",
"description": "CSSLint rule config values"
@ -486,6 +592,10 @@
"message": "לחץ להגדרת linter זה",
"description": "Icon tooltip to indicate that it opens a popup with the selected linter configuration"
},
"linterInvalidConfigError": {
"message": "לא נשמר עקב הגדרות תצורה לא חוקיות אלה:",
"description": "Invalid linter config will show a message followed by a list of invalid entries"
},
"linterIssues": {
"message": "תקלות",
"description": "Label for the CSS linter issues block on the style edit page"
@ -519,10 +629,6 @@
"message": "רענון לייב (live)",
"description": "The label of live-reload feature"
},
"liveReloadUnavailable": {
"message": "על־מנת לאפשר רענון לייב (live), אנא שחרר את הקובץ על רצועת הכרטיסיות (האזור בו כותרות הכרטיסיות מוצגות).",
"description": ""
},
"manageFavicons": {
"message": "הצגת אייקונים בעמודת 'חל על'",
"description": "Label for the checkbox that toggles applies-to favicons in the new UI on manage page"
@ -551,6 +657,10 @@
"message": "כ־Usercss",
"description": "VERY SHORT label for the checkbox next to the 'Write new style' button in the style manager"
},
"manageNewUI": {
"message": "פריסת ממשק משתמש ניהול חדשה",
"description": "Label for the checkbox that toggles the new UI on manage page"
},
"manageOnlyDisabled": {
"message": "רק עיצובים מושבתים",
"description": "Checkbox to show only disabled styles"
@ -567,6 +677,10 @@
"message": "רק עיצובים שנוצרו באופן מקומי",
"description": "Checkbox to show only locally created styles i.e. non-updatable"
},
"manageOnlyLocalTooltip": {
"message": "(הסגנונות שלא הותקנו דרך דף userstyles.org)",
"description": "Tooltip for the checkbox to show only locally created styles i.e. non-updatable"
},
"manageOnlyNonUsercss": {
"message": "רק לא עיצובי Usercss",
"description": "Checkbox to show only non-Usercss (standard) styles"
@ -583,6 +697,35 @@
"message": "הצג כמות עיצובים מאופשרים",
"description": "Label (must be very short) for the checkbox in the toolbar button context menu controlling toolbar badge text."
},
"meta_invalidCheckboxDefault": {
"message": "תיבת סימון @var לא חוקית: הערך חייב להיות 0 או 1",
"description": "Error displayed when the value of @var checkbox is invalid"
},
"meta_invalidNumber": {
"message": "נדרש מספר",
"description": "Error displayed when the value is expected to be a number"
},
"meta_invalidString": {
"message": "נדרשת מחרוזת בתוך גרשיים",
"description": "Error displayed when the value is expected to be a quoted string"
},
"meta_invalidWord": {
"message": "נדרשת מילה",
"description": "Error displayed when the value is expected to be a word"
},
"meta_missingChar": {
"message": "נדרשים תווים: $chars$",
"description": "Error displayed when the value is expected to be some characters",
"placeholders": {
"chars": {
"content": "$1"
}
}
},
"meta_missingEOT": {
"message": "נדרש מידע EOT",
"description": "Error displayed when the value is expected to be an EOT list"
},
"noStylesForSite": {
"message": "לא הותקנו עיצובים עבור אתר זה.",
"description": "Text displayed when no styles are installed for the current site"
@ -591,11 +734,7 @@
"message": "ניהול",
"description": "Link to open the manage page."
},
"openOptionsManage": {
"message": "אפשרויות UI",
"description": "Go to Options UI"
},
"openOptionsPopup": {
"openOptions": {
"message": "אפשרויות",
"description": "Go to Options UI"
},
@ -612,7 +751,19 @@
"description": ""
},
"optionsAdvancedContextDelete": {
"message": "הוספת 'מחיקה' בתפריט העורך",
"message": "הוספת 'מחק' בתפריט העורך",
"description": ""
},
"optionsAdvancedExposeIframes": {
"message": "חשוף iframes באמצעות HTML [stylus-iframe]",
"description": ""
},
"optionsAdvancedExposeIframesNote": {
"message": "חושף את תחום האתר העליון בכל iframe.\nמאפשר כתיבת CSS ספציפית ל־iframe כך:\nhtml[stylus-iframe$$=\"twitter.com\"] h1 { display:none }",
"description": "Add attribute to iframe; make sure to include the double $$ in the css example, or the `$=` will be omitted in the displayed text."
},
"optionsAdvancedNewStyleAsUsercss": {
"message": "כתוב עיצוב חדש בתור usercss",
"description": ""
},
"optionsBadgeDisabled": {
@ -647,6 +798,10 @@
"message": "עדכונים",
"description": ""
},
"optionsCustomizeSync": {
"message": "סנכרון לענן",
"description": ""
},
"optionsHeading": {
"message": "אפשרויות",
"description": "Heading for options section on manage page."
@ -676,17 +831,85 @@
"description": ""
},
"optionsResetButton": {
"message": יפוס האפשרויות",
"message": פס אפשרויות",
"description": ""
},
"optionsSubheading": {
"message": "אפשרויות נוספות",
"description": "Subheading for options section on manage page."
},
"optionsUpdateImportNote": {
"message": "בעת ייבוא גיבויים לסגנון מגרסה ישנה או מ־Stylish, בדוק באופן חד פעמי אם יש עדכונים באופן ידני במנהל הסגנונות כדי לוודא שכל הסגנונות מעודכנים.",
"description": ""
},
"optionsUpdateInterval": {
"message": "עדכון אוטומטי של Userstyle בשעות (הגדר 0 להשבתה)",
"description": ""
},
"optionsSyncNone": {
"message": "ללא",
"description": ""
},
"optionsSyncConnect": {
"message": "התחבר",
"description": ""
},
"optionsSyncDisconnect": {
"message": "התנתק",
"description": ""
},
"optionsSyncSyncNow": {
"message": "סנכרן כעת",
"description": ""
},
"optionsSyncLogin": {
"message": "היכנס",
"description": ""
},
"optionsSyncStatusPull": {
"message": "מושך סגנון $loaded$ מתוך $total$",
"description": "",
"placeholders": {
"loaded": {
"content": "$1"
},
"total": {
"content": "$2"
}
}
},
"optionsSyncStatusPush": {
"message": "מפרסם סגנון $loaded$ מתוך $total$",
"description": "",
"placeholders": {
"loaded": {
"content": "$1"
},
"total": {
"content": "$2"
}
}
},
"optionsSyncStatusSyncing": {
"message": "מסנכרן...",
"description": ""
},
"optionsSyncStatusConnecting": {
"message": "מתחבר...",
"description": ""
},
"optionsSyncStatusConnected": {
"message": "מחובר",
"description": ""
},
"optionsSyncStatusDisconnecting": {
"message": "מתנתק...",
"description": ""
},
"optionsSyncStatusDisconnected": {
"message": "מנותק",
"description": ""
},
"paginationCurrent": {
"message": "הדף הנוכחי",
"description": "Tooltip for the current page index in search results"
@ -707,24 +930,44 @@
"message": "סה״כ דפים",
"description": ""
},
"parseUsercssError": {
"message": "Stylus נכשל בניתוח usercss:",
"description": "The error message to show when stylus failed to parse usercss"
},
"popupBorders": {
"message": "הוספת שוליים לבנים בצדדים",
"description": ""
},
"popupBordersTooltip": {
"message": "שימושי לעיצובים כהים ב־Chrome החדש מכיוון שהוא כבר לא מצייר את גבולות הצד",
"description": ""
},
"popupHotkeysTooltip": {
"message": "לחץ על־מנת לצפות במקשים החמים הזמינים",
"description": "Tooltip displayed when hovering the right edge of the extension popup"
},
"popupManageTooltip": {
"message": "לחיצה על Shift או מקש ימני בעכבר פותח את מנהל העיצובים",
"description": "Tooltip for the 'Manage' button in the popup."
},
"popupMenuButtonTooltip": {
"message": "תפריט פעולות",
"description": "Tooltip for menu button in popup."
},
"popupOpenEditInWindow": {
"message": "פתח את העורך בחלון חדש",
"description": "Label for the checkbox controlling 'edit' action behavior in the popup."
},
"popupOpenEditInWindowTooltip": {
"message": "מופעל גם על ידי ניתוק לשונית העורך מחלון הדפדפן,\nומושבת על ידי חיבור לשונית עורך יחידה לחלון אחר.",
"description": "Label for the checkbox controlling 'edit' action behavior in the popup."
},
"popupStylesFirst": {
"message": "עיצובים לפני הפקודות",
"description": "Label for the checkbox controlling section order in the popup."
},
"prefShowBadge": {
"message": ֿמספר העיצובים המאופשרים באתר הנוכחי",
"message": ספר העיצובים המאופשרים באתר הנוכחי",
"description": "Label for the checkbox controlling toolbar badge text."
},
"previewLabel": {
@ -735,6 +978,10 @@
"message": "החלת השינויים באופן זמני ללא שמירה.\nשמור את העיצוב על־מנת להפוך את השינויים לקבועים.",
"description": "Tooltip for the checkbox in style editor to enable live preview while editing."
},
"reload": {
"message": "טען מחדש את Stylus",
"description": "Context menu reload"
},
"replace": {
"message": "החלף",
"description": "Label before the replace input field in the editor shown on Ctrl-H"
@ -791,6 +1038,18 @@
"message": "התקנות שבועיות",
"description": "Text for label that shows the number of times a search result was installed during last week"
},
"searchStyles": {
"message": "חיפוש תוכן",
"description": "Label for the search filter textbox on the Manage styles page"
},
"sectionAdd": {
"message": "הוסף מקטע נוסף",
"description": "Label for the button to add a section"
},
"sectionCode": {
"message": "קוד",
"description": "Label for the code for a section"
},
"sectionRemove": {
"message": "הסר סעיף",
"description": "Label for the button to remove a section"
@ -819,30 +1078,103 @@
"message": "בחר שיטת מיון להחלה על העיצובים המותקנים",
"description": "Title on the sort select to indicate it is used for sorting entries"
},
"sortStylesHelpTitle": {
"message": "סידור תכנים",
"description": "Label for the sort info popup on the Manage styles page"
},
"styleBadRegexp": {
"message": "ביטוי ה־Regexp לא תקין.",
"message": "ביטוי ה־regexp לא תקין.",
"description": "Validation message for a bad regexp in a style"
},
"styleBeautify": {
"message": "ייפה CSS",
"description": "Label for the CSS-beautifier button on the edit style page"
},
"styleBeautifyIndentConditional": {
"message": "הזחת @media, @supports",
"description": "CSS-beautifier option"
},
"styleBeautifyPreserveNewlines": {
"message": "שמור שורות חדשות",
"description": "CSS-beautifier option"
},
"styleCancelEditLabel": {
"message": "חזרה לניהול",
"description": "Label for cancel button for style editing"
},
"styleChangesNotSaved": {
"message": "ביצעת שינויים בסגנון זה מבלי לשמור.",
"description": "Text for the prompt when changes are made to a style and the user tries to leave without saving"
},
"styleEnabledLabel": {
"message": "מאופשר",
"description": "Label for the enabled state of styles"
},
"styleFromMozillaFormatError": {
"message": "הייבוא נכשל מ־Mozilla format",
"description": "Label for the import error"
},
"styleFromMozillaFormatPrompt": {
"message": "הדבק את הקוד ב־Mozilla-format",
"description": "Prompt in the dialog displayed after clicking 'Import from Mozilla format' button"
},
"styleInstall": {
"message": "להתקין ';$stylename$' אל Stylus?",
"description": "Confirmation when installing a style",
"placeholders": {
"stylename": {
"content": "$1"
}
}
},
"styleInstallFailed": {
"message": "נכשל בהתקנת העיצוב!\n$error$",
"description": "Warning when installation failed",
"placeholders": {
"error": {
"content": "$1"
}
}
},
"styleInstallOverwrite": {
"message": "';$stylename$' כבר מותקן. האם לדרוס?\nגרסה: $oldVersion$ -> $newVersion$",
"description": "Confirmation when re-installing a style",
"placeholders": {
"stylename": {
"content": "$1"
},
"newVersion": {
"content": "$3"
},
"oldVersion": {
"content": "$2"
}
}
},
"styleMissingName": {
"message": "אנא הזן שם",
"message": "הזן שם",
"description": "Error displayed when user saves without providing a name"
},
"styleMozillaFormatHeading": {
"message": "Mozilla-format",
"description": "Heading for the section with buttons to import/export Mozilla format of the style"
},
"styleNotAppliedRegexpProblemTooltip": {
"message": "הסגנון לא הוחל בגלל השימוש בו שגוי ב־'regexp()'",
"description": "Tooltip in the popup for styles that were not applied at all"
},
"styleRegexpInvalidExplanation": {
"message": "לא היה ניתן להחיל מספר כללי 'regexp()' כלל.",
"description": ""
},
"styleRegexpPartialExplanation": {
"message": "סגנון זה משתמש ב- regexps תואם חלקית בניגוד למפרט <a href='https://developer.mozilla.org/docs/Web/CSS/@document'>CSS4 @document</a> המצריך התאמה מלאה של כתובת אתר. קטעי ה־CSS המושפעים לא הוחלו על הדף. סגנון זה נוצר ככל הנראה ב־Stylish-for-Chrome שבודק באופן שגוי את כללי 'regexp()' מאז הגרסה הראשונה (באג ידוע).",
"description": ""
},
"styleRegexpProblemTooltip": {
"message": "מספר המקטעים שלא הוחלו עקב שימוש שגוי ב־'regexp()'",
"description": "Tooltip in the popup for styles that were applied only partially"
},
"styleRegexpTestButton": {
"message": "בדוק RegExp",
"description": "RegExp test button label in the editor shown when applies-to list has a regexp value"
@ -859,12 +1191,24 @@
"message": "לא תואם אף כרטיסייה",
"description": "RegExp test report: label for expressions that didn't match any tabs"
},
"styleRegexpTestPartial": {
"message": "לא תואם לחלוטין, לכן דולג",
"description": "RegExp test report: label for the partially matching expressions"
},
"styleRegexpTestTitle": {
"message": "רשימת לשוניות פתוחות בהתאמה (לחץ על כתובת אתר למיקוד הלשונית שלה)",
"description": "RegExp test report: title of the report"
},
"styleSaveLabel": {
"message": "שמור",
"description": "Label for save button for style editing"
},
"styleToMozillaFormatHelp": {
"message": "ניתן לפרסם את ה־Mozilla format של הקוד לאתר userstyles.org ולהשתמש בו עם Stylish הקלאסי עבור Firefox",
"description": "Help info for the Mozilla format header section that converts the code to/from Mozilla format"
},
"styleToMozillaFormatTitle": {
"message": "עיצוב ב־Mozilla format",
"message": "עיצוב ב־Mozilla-format",
"description": "Title of the popup with the style code in Mozilla format, shown after pressing the Export button on Edit style page"
},
"styleUpdate": {
@ -880,6 +1224,10 @@
"message": "Stylus לא עובד על דפים כמו זה.",
"description": "Note in the toolbar pop-up when on a URL Stylus can't affect"
},
"stylusUnavailableForURLdetails": {
"message": "כאמצעי אבטחה, הדפדפן אוסר על הרחבות להשפיע על הדפים המובנים שלו (כמו chrome://version, הכרטיסייה החדשה הרגילה החל מ־Chrome 61, about:addons וכן הלאה), וכן על דפי הרחבות אחרים. כל דפדפן גם מגביל את הגישה לגלריית התוספים שלו (כמו חנות האינטרנט של Chrome או AMO).",
"description": "Sub-note in the toolbar pop-up when on a URL Stylus can't affect"
},
"syncStorageErrorSaving": {
"message": "הערך לא יכול להשמר. אנא נסה להקטין את גודל הטקסט.",
"description": "Displayed when trying to save an excessively big value via storage.sync API"
@ -904,10 +1252,18 @@
"message": "לא ניתן לתקשר עם הדף. אנא טען מחדש את הכרטיסייה.",
"description": "Note in the toolbar popup usually on file:// URLs after [re]loading Stylus"
},
"unreachableFileHint": {
"message": "Stylus יכול לגשת לכתובות אתר file:// רק אם תיבת הסימון המתאימה עבור סיומת Stylus פעילה בדף chrome://extensions.",
"description": "Note in the toolbar popup for file:// URLs"
},
"updateAllCheckSucceededNoUpdate": {
"message": "לא נמצאו עדכונים.",
"description": "Text that displays when an update all check completed and no updates are available"
},
"updateAllCheckSucceededSomeEdited": {
"message": "כמה סגנונות הניתנים לעדכון לא נבדקו כדי להימנע מאבדן עריכות מקומיות אפשריות. ניתן לאלץ עדכונים על ידי בדיקה באופן פרטני, או על ידי הפעלת בדיקה אחרת לכל הסגנונות (עריכות מקומיות יידרסו).",
"description": "Text that displays when an update all check completed and no updates are available"
},
"updateCheckFailBadResponseCode": {
"message": "העדכון נכשל: השרת החזיר תגובה עם הקוד $code$.",
"description": "Text that displays when an update check failed because the response code indicates an error",
@ -925,6 +1281,14 @@
"message": "היסטוריה של בדיקת עדכונים",
"description": ""
},
"updateCheckManualUpdateForce": {
"message": "התקן עדכון (עריכות מקומיות יידרסו)",
"description": "Additional text displayed when an update check skipped updating the style to avoid losing local modifications"
},
"updateCheckManualUpdateHint": {
"message": "אילוץ עדכון ידרוס כל עריכה מקומית.",
"description": "Additional text displayed when an update check skipped updating the style to avoid losing local modifications"
},
"updateCheckSkippedLocallyEdited": {
"message": "עיצוב זה נערך באופן מקומי.",
"description": "Text that displays when an update check skipped updating the style to avoid losing local modifications"
@ -968,5 +1332,53 @@
"writeStyleForURL": {
"message": "הקישור הנוכחי",
"description": "Text for link in toolbar pop-up to write a new style for the current URL"
},
"syncDropboxStyles": {
"message": "ייצוא Dropbox",
"description": ""
},
"retrieveDropboxSync": {
"message": "Dropbox ייבוא",
"description": ""
},
"overwriteFileExport": {
"message": "האם ברצונך להחליף קובץ קיים?",
"description": ""
},
"exportSavedSuccess": {
"message": "קובץ נשמר בהצלחה",
"description": ""
},
"noFileToImport": {
"message": "כדי לייבא את הסגנונות שלך, עליך לייצא אותם תחילה.",
"description": ""
},
"connectingDropbox": {
"message": "מתחבר אל Dropbox...",
"description": ""
},
"connectingDropboxNotAllowed": {
"message": "התחברות אל Dropbox זמינה רק בהרחבות המותקנות ישירות מחנות האינטרנט",
"description": ""
},
"gettingStyles": {
"message": "טוען את כל העיצובים...",
"description": ""
},
"zipStyles": {
"message": "מקבץ עיצובים...",
"description": ""
},
"unzipStyles": {
"message": "מחלץ עיצובים...",
"description": ""
},
"readingStyles": {
"message": "קורא עיצובים...",
"description": ""
},
"uploadingFile": {
"message": "מעלה קובץ...",
"description": ""
}
}

View File

@ -41,7 +41,7 @@
"description": "Label for 'applies to' fields on the edit/add screen"
},
"appliesLineWidgetLabel": {
"message": "Információ megjelenítése arról, hogy mire van alkalmazva",
"message": "Információ arról, hogy mire érvényes",
"description": "Label for the checkbox to display applies-to information in the single editor"
},
"appliesLineWidgetWarning": {
@ -57,7 +57,7 @@
"description": "Label for the button to remove an 'applies' entry"
},
"appliesRemoveError": {
"message": "Nem lehet eltávolítani az utolsó alkalmazási szabályt",
"message": "Nem lehet eltávolítani az utolsó 'érvényes erre' bejegyzést",
"description": "Error displayed when the last 'applies' is going to be removed"
},
"appliesSpecify": {
@ -236,6 +236,14 @@
"message": "Igen",
"description": "'Yes' button in a confirm dialog"
},
"copied": {
"message": "Vágólapra másolva",
"description": "Message shown when content has been copied to the clipboard"
},
"copy": {
"message": "Másolás vágólapra",
"description": "Tooltip for elements which can be copied"
},
"dateInstalled": {
"message": "Telepítés dátuma",
"description": "Option text for the user to sort the style by install date"
@ -309,6 +317,14 @@
"message": "Engedélyezés",
"description": "Label for the button to enable a style"
},
"excludeStyleByDomainLabel": {
"message": "Aktuális tartományt kivéve",
"description": ""
},
"excludeStyleByUrlLabel": {
"message": "Aktuális URL-t kivéve",
"description": ""
},
"exportLabel": {
"message": "Exportálás",
"description": "Label for the button to export a style ('edit' page) or all styles ('manage' page)"
@ -593,10 +609,6 @@
"message": "Hiba történt a fájl figyelése közben",
"description": "The label of live-reload error"
},
"liveReloadInstallHint": {
"message": "A valós idejű újratöltés engedélyezve van, így a telepített stílus automatikusan frissül külső változások során, amíg ez a fül és a forrásfájlt tartalmazó fül nyitva van.",
"description": "The label of live-reload feature"
},
"liveReloadLabel": {
"message": "Valós idejű újratöltés",
"description": "The label of live-reload feature"
@ -634,7 +646,7 @@
"description": "Label for the checkbox that toggles the new UI on manage page"
},
"manageOnlyDisabled": {
"message": "Csak letiltott stílusok",
"message": "Csak a letiltott stílusok",
"description": "Checkbox to show only disabled styles"
},
"manageOnlyEnabled": {
@ -669,6 +681,222 @@
"message": "Aktív stílusok számlálójának mutatása",
"description": "Label (must be very short) for the checkbox in the toolbar button context menu controlling toolbar badge text."
},
"meta_invalidCheckboxDefault": {
"message": "Érvénytelen jelölő négyzet @var: az érték csak 0 vagy 1 lehet",
"description": "Error displayed when the value of @var checkbox is invalid"
},
"meta_invalidColor": {
"message": "Érvénytelen szín @var: a(z) $color$ nem szín",
"description": "Error displayed when the value of @var color is invalid",
"placeholders": {
"color": {
"content": "$1"
}
}
},
"meta_invalidRange": {
"message": "Érvénytelen változó @var (típus: $type$): az érték csak szám vagy tömb lehet",
"description": "Error displayed when the value of @var range or @var number is invalid",
"placeholders": {
"type": {
"content": "$1"
}
}
},
"meta_invalidRangeMultipleUnits": {
"message": "Érvénytelen változó @var (típus: $type$): többféle egység van megadva",
"description": "Error displayed when the value of @var range or @var number is invalid",
"placeholders": {
"type": {
"content": "$1"
}
}
},
"meta_invalidRangeTooManyValues": {
"message": "Érvénytelen változó @var (típus: $type$): a tömb túl sok elemet tartalmaz",
"description": "Error displayed when the value of @var range or @var number is invalid",
"placeholders": {
"type": {
"content": "$1"
}
}
},
"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",
"description": "Error displayed when the value of @var range or @var number is invalid",
"placeholders": {
"type": {
"content": "$1"
}
}
},
"meta_invalidRangeDefault": {
"message": "Érvénytelen változó @var (típus: $type$): az alapértelmezett értéke null",
"description": "Error displayed when the value of @var range or @var number is invalid",
"placeholders": {
"type": {
"content": "$1"
}
}
},
"meta_invalidRangeMin": {
"message": "Érvénytelen változó @var (típus: $type$): az alapértelmezett érték kisebb, mint a minimum",
"description": "Error displayed when the value of @var range or @var number is invalid",
"placeholders": {
"type": {
"content": "$1"
}
}
},
"meta_invalidRangeMax": {
"message": "Érvénytelen változó @var (típus: $type$): az alapértelmezett érték nagyobb, mint a maximum",
"description": "Error displayed when the value of @var range or @var number is invalid",
"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",
"description": "Error displayed when the value of @var range or @var number is invalid",
"placeholders": {
"type": {
"content": "$1"
}
}
},
"meta_invalidRangeUnits": {
"message": "Érvénytelen változó @var (típus: $type$): a(z) '$units$' nem érvényes egység",
"description": "Error displayed when the value of @var range or @var number is invalid",
"placeholders": {
"type": {
"content": "$1"
},
"units": {
"content": "$2"
}
}
},
"meta_invalidSelect": {
"message": "Érvénytelen kiválasztó lista @var: az alapértelmezett érték tömb vagy objektum lehet csak",
"description": "Error displayed when the value of @var select is invalid"
},
"meta_invalidSelectValue": {
"message": "Érvénytelen kiválasztó lista @var: a tömbön/objektumon belüli érték csak karakterlánc lehet",
"description": "Error displayed when the value of @var select is invalid"
},
"meta_invalidSelectEmptyOptions": {
"message": "Érvénytelen kiválasztó lista @var: a lista üres",
"description": "Error displayed when the value of @var select is invalid"
},
"meta_invalidSelectLabel": {
"message": "Érvénytelen kiválasztó lista @var: a listaelem címkéje üres",
"description": "Error displayed when the value of @var select is invalid"
},
"meta_invalidSelectMultipleDefaults": {
"message": "Érvénytelen kiválasztó lista @var: egynél több alapértelmezett elem van megadva",
"description": "Error displayed when the value of @var select is invalid"
},
"meta_invalidSelectNameDuplicated": {
"message": "Érvénytelen kiválasztó lista @var: egy listaelem kétszer szerepel",
"description": "Error displayed when the value of @var select is invalid"
},
"meta_invalidSelectValueMismatch": {
"message": "Érvénytelen kiválasztó lista @var: az érték nem található a listaelemek között",
"description": "Error displayed when the value of @var select is invalid"
},
"meta_invalidURLProtocol": {
"message": "Érvénytelen URL-protokoll. Csak 'http' vagy 'https' engedett: $protocol$",
"description": "Error displayed when the protocol of the URL is invalid",
"placeholders": {
"protocol": {
"content": "$1"
}
}
},
"meta_invalidVersion": {
"message": "Érvénytelen verziószám. Az érték nem illeszkedik a SemVer mintájához: $version$",
"description": "Error displayed when @version is invalid",
"placeholders": {
"version": {
"content": "$1"
}
}
},
"meta_invalidNumber": {
"message": "Várt elem: szám",
"description": "Error displayed when the value is expected to be a number"
},
"meta_invalidString": {
"message": "Várt elem: idézőjelek közti karakterlánc",
"description": "Error displayed when the value is expected to be a quoted string"
},
"meta_invalidWord": {
"message": "Várt elem: szó",
"description": "Error displayed when the value is expected to be a word"
},
"meta_missingChar": {
"message": "Várt karakterek: $chars$",
"description": "Error displayed when the value is expected to be some characters",
"placeholders": {
"chars": {
"content": "$1"
}
}
},
"meta_missingEOT": {
"message": "Várt elem: EOT-adat",
"description": "Error displayed when the value is expected to be an EOT list"
},
"meta_missingMandatory": {
"message": "Kötelező metaadat hiányzik: $keys$",
"description": "Error displayed when mandatory keys are missing",
"placeholders": {
"keys": {
"content": "$1"
}
}
},
"meta_unknownJSONLiteral": {
"message": "Érvénytelen JSON: a(z) $literal$ nem érvényes JSON-karakterlánc (literális)",
"description": "Error displayed when JSON value is invalid",
"placeholders": {
"literal": {
"content": "$1"
}
}
},
"meta_unknownMeta": {
"message": "Ismeretlen metaadat: $key$",
"description": "Error displayed when unknown metadata is parsed",
"placeholders": {
"key": {
"content": "$1"
}
}
},
"meta_unknownVarType": {
"message": "Ismeretlen @$varkey$ változó, típus: $vartype$",
"description": "Error displayed when unknown variable type is parsed",
"placeholders": {
"varkey": {
"content": "$1"
},
"vartype": {
"content": "$2"
}
}
},
"meta_unknownPreprocessor": {
"message": "Ismeretlen @preprocesszor (előfeldolgozó): $preprocessor$",
"description": "Error displayed when unknown @preprocessor is parsed",
"placeholders": {
"preprocessor": {
"content": "$1"
}
}
},
"noStylesForSite": {
"message": "Nincs telepítve stílus ehhez az oldalhoz.",
"description": "Text displayed when no styles are installed for the current site"
@ -677,14 +905,6 @@
"message": "Kezelés",
"description": "Link to open the manage page."
},
"openOptionsManage": {
"message": "A beállítások felülete",
"description": "Go to Options UI"
},
"openOptionsPopup": {
"message": "Beállítások",
"description": "Go to Options UI"
},
"openStylesManager": {
"message": "Stíluskezelő megnyitása",
"description": "Label for the style maanger opener in the browser action context menu."
@ -705,6 +925,10 @@
"message": "iframe-ek kitevése HTML[stylus-iframe]-en keresztül",
"description": ""
},
"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 }",
"description": "Add attribute to iframe; make sure to include the double $$ in the css example, or the `$=` will be omitted in the displayed text."
},
"optionsAdvancedNewStyleAsUsercss": {
"message": "Új stílus írása usercss-ként",
"description": ""
@ -741,6 +965,10 @@
"message": "Frissítések",
"description": ""
},
"optionsCustomizeSync": {
"message": "Szinkronizálás felhővel",
"description": ""
},
"optionsHeading": {
"message": "Beállítások",
"description": "Heading for options section on manage page."
@ -782,7 +1010,71 @@
"description": ""
},
"optionsUpdateInterval": {
"message": "Stílus automatikus frissítési intervalluma órában megadva (álltsd 0-ra a kikapcsoláshoz)",
"message": "Felhasználói stílus automatikus frissítési időköze órában (0=kikapcsolva)",
"description": ""
},
"optionsSyncNone": {
"message": "Nincs",
"description": ""
},
"optionsSyncConnect": {
"message": "Kapcsolódás",
"description": ""
},
"optionsSyncDisconnect": {
"message": "Kapcsolat bontása",
"description": ""
},
"optionsSyncSyncNow": {
"message": "Szinkronizálás most",
"description": ""
},
"optionsSyncLogin": {
"message": "Bejelentkezés",
"description": ""
},
"optionsSyncStatusPull": {
"message": "Stílusok helyi frissítése (pull): $loaded$/$total$",
"description": "",
"placeholders": {
"loaded": {
"content": "$1"
},
"total": {
"content": "$2"
}
}
},
"optionsSyncStatusPush": {
"message": "Stílusok távoli frissítése (push): $loaded$/$total$",
"description": "",
"placeholders": {
"loaded": {
"content": "$1"
},
"total": {
"content": "$2"
}
}
},
"optionsSyncStatusSyncing": {
"message": "Szinkronizálás...",
"description": ""
},
"optionsSyncStatusConnecting": {
"message": "Kapcsolódás...",
"description": ""
},
"optionsSyncStatusConnected": {
"message": "Kapcsolódva",
"description": ""
},
"optionsSyncStatusDisconnecting": {
"message": "Kapcsolat bontása...",
"description": ""
},
"optionsSyncStatusDisconnected": {
"message": "Nincs kapcsolat",
"description": ""
},
"paginationCurrent": {
@ -809,6 +1101,10 @@
"message": "A Stylus nem tudta elemezni a usercss-t",
"description": "The error message to show when stylus failed to parse usercss"
},
"popupAutoResort": {
"message": "Stílusok újrarendezése a felugró ablakban átkapcsoláskor",
"description": "Label for the checkbox controlling popup resorting."
},
"popupBorders": {
"message": "Fehér szegély az oldalakon",
"description": ""
@ -829,6 +1125,10 @@
"message": "Shift+kattintás vagy jobb kattintás: a stíluskezelő megnyitása a jelenlegi oldalra érvényes stílusokkal",
"description": "Tooltip for the 'Manage' button in the popup."
},
"popupMenuButtonTooltip": {
"message": "Műveletmenü",
"description": "Tooltip for menu button in popup."
},
"popupOpenEditInWindow": {
"message": "Szerkesztő megnyitása új ablakban",
"description": "Label for the checkbox controlling 'edit' action behavior in the popup."
@ -918,7 +1218,7 @@
"description": "Text in the minihelp displayed when clicking (i) icon to the right of the search input field on the Manage styles page"
},
"sectionAdd": {
"message": "Egy újabb szekció hozzáadása",
"message": "Új szakasz hozzáadása",
"description": "Label for the button to add a section"
},
"sectionCode": {
@ -1163,6 +1463,10 @@
"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.",
"description": "Note in the toolbar popup for file:// URLs"
},
"InaccessibleFileHint": {
"message": "A Stylus nem tud bizonyos fájltípusokhoz hozzáférni (pl. PDF- & JSON-fájlokhoz)",
"description": "Note in the toolbar popup for some file types that cannot be accessed"
},
"updateAllCheckSucceededNoUpdate": {
"message": "Nem találhatók frissítések.",
"description": "Text that displays when an update all check completed and no updates are available"
@ -1251,5 +1555,53 @@
"writeStyleForURL": {
"message": "ehhez az URL-hez",
"description": "Text for link in toolbar pop-up to write a new style for the current URL"
},
"syncDropboxStyles": {
"message": "Exportálás Dropboxba",
"description": ""
},
"retrieveDropboxSync": {
"message": "Importálás Dropboxból",
"description": ""
},
"overwriteFileExport": {
"message": "Felülírjuk a létező fájlt?",
"description": ""
},
"exportSavedSuccess": {
"message": "A fájl sikeresen elmentve.",
"description": ""
},
"noFileToImport": {
"message": "A stílusok importálásához előbb exportálni kell őket.",
"description": ""
},
"connectingDropbox": {
"message": "Kapcsolódás Dropboxhoz...",
"description": ""
},
"connectingDropboxNotAllowed": {
"message": "Dropboxhoz csak közvetlenül a web-boltból telepített alkalmazásból lehet kapcsolódni.",
"description": ""
},
"gettingStyles": {
"message": "Minden stílus fogadása...",
"description": ""
},
"zipStyles": {
"message": "Stílusok tömörítése...",
"description": ""
},
"unzipStyles": {
"message": "Stílusok kibontása...",
"description": ""
},
"readingStyles": {
"message": "Stílusok olvasása...",
"description": ""
},
"uploadingFile": {
"message": "Fájl feltöltése...",
"description": ""
}
}

View File

@ -639,14 +639,6 @@
"message": "Gestisci gli stili installati",
"description": "Link to open the manage page."
},
"openOptionsManage": {
"message": "Opzioni UI",
"description": "Go to Options UI"
},
"openOptionsPopup": {
"message": "Opzioni",
"description": "Go to Options UI"
},
"openStylesManager": {
"message": "Apri gestore stili",
"description": "Label for the style maanger opener in the browser action context menu."

View File

@ -236,6 +236,14 @@
"message": "はい",
"description": "'Yes' button in a confirm dialog"
},
"copied": {
"message": "クリップボードへコピーしました",
"description": "Message shown when content has been copied to the clipboard"
},
"copy": {
"message": "クリップボードへコピー",
"description": "Tooltip for elements which can be copied"
},
"dateInstalled": {
"message": "インストール日",
"description": "Option text for the user to sort the style by install date"
@ -276,6 +284,10 @@
"message": "このページの任意の場所にバックアップファイルをドロップしてインポートします。",
"description": "Drag'n'drop message"
},
"dragDropUsercssTabstrip": {
"message": "ファイルをインストールするときは、タブ(タイトルが表示されている領域)にドロップしてください。",
"description": "Message popup shown when erroneously dropping a usercss file into the manager page"
},
"editDeleteText": {
"message": "削除",
"description": "Label for the context menu item in the editor to delete selected text"
@ -433,6 +445,10 @@
"message": "ホットキーを押す",
"description": "Placeholder text of inputbox in keymap help popup on the edit style page. Must be very short"
},
"hostDisabled": {
"message": "使用中のブラウザの現在のバージョンの不具合のため、このホストは無効にされています",
"description": "Tooltip for cloud host disabled"
},
"importAppendLabel": {
"message": "スタイルに追加",
"description": "Label for the button to import a style and append to the existing sections"
@ -526,10 +542,6 @@
"message": "更新をチェック",
"description": "Label for the checkbox to save current URL for update check"
},
"installUpdateUnavailable": {
"message": "更新のチェックを有効にするには、ファイルをタブストリップにドロップするか、スタイルのメタデータで @updateURL を指定してください。",
"description": ""
},
"license": {
"message": "ライセンス",
"description": "Label for the license"
@ -610,17 +622,17 @@
"description": "The label of live-reload error"
},
"liveReloadInstallHint": {
"message": "自動リロードが有効になっているため、このタブとソースファイルのタブの両方が開いている間に、外部変更によってインストール済みスタイルが自動更新されることがあります。",
"message": "外部の変更時にスタイルを自動更新する場合は、このタブを開いたままにしておいてください。",
"description": "The label of live-reload feature"
},
"liveReloadInstallHintFF": {
"message": "外部の変更時にスタイルを自動更新する場合は、このタブと元のタブを開いたままにしておいてください。",
"description": "The extra hint of live-reload feature shown only for file:// URLs in Firefox"
},
"liveReloadLabel": {
"message": "自動リロード",
"description": "The label of live-reload feature"
},
"liveReloadUnavailable": {
"message": "自動リロードを有効にするには、ファイルをタブストリップ(タブのタイトルが表示されている領域)にドロップしてください。",
"description": ""
},
"manageFavicons": {
"message": "適用先欄のファビコン",
"description": "Label for the checkbox that toggles applies-to favicons in the new UI on manage page"
@ -638,7 +650,7 @@
"description": "Label for filters container"
},
"manageHeading": {
"message": "インストール済みスタイル",
"message": "インストール済みスタイル",
"description": "Heading for the manage page"
},
"manageMaxTargets": {
@ -913,11 +925,7 @@
"message": "管理",
"description": "Link to open the manage page."
},
"openOptionsManage": {
"message": "オプション UI",
"description": "Go to Options UI"
},
"openOptionsPopup": {
"openOptions": {
"message": "オプション",
"description": "Go to Options UI"
},
@ -934,11 +942,11 @@
"description": ""
},
"optionsAdvancedContextDelete": {
"message": "エディタのコンテキストメニューに「削除」を追加します",
"message": "エディタのコンテキストメニューに「削除」を追加",
"description": ""
},
"optionsAdvancedExposeIframes": {
"message": "HTML[stylus-iframe] によってiframeへのアクセスを可能にします",
"message": "HTML[stylus-iframe] によってiframeへのアクセスを可能に",
"description": ""
},
"optionsAdvancedExposeIframesNote": {
@ -958,7 +966,7 @@
"description": ""
},
"optionsCheck": {
"message": "スタイル更新",
"message": "スタイル更新",
"description": ""
},
"optionsCheckUpdate": {
@ -966,7 +974,7 @@
"description": ""
},
"optionsCustomizeBadge": {
"message": "ツールバーアイコンのバッジ",
"message": "アイコンの修飾",
"description": ""
},
"optionsCustomizeIcon": {
@ -978,7 +986,11 @@
"description": ""
},
"optionsCustomizeUpdate": {
"message": "更新",
"message": "更新設定",
"description": ""
},
"optionsCustomizeSync": {
"message": "クラウドとの同期",
"description": ""
},
"optionsHeading": {
@ -998,7 +1010,7 @@
"description": ""
},
"optionsOpenManager": {
"message": "スタイル管理",
"message": "スタイル管理",
"description": ""
},
"optionsPopupWidth": {
@ -1025,6 +1037,70 @@
"message": "ユーザースタイルの自動更新間隔時間単位0を指定すると更新しません",
"description": ""
},
"optionsSyncNone": {
"message": "未選択",
"description": ""
},
"optionsSyncConnect": {
"message": "接続",
"description": ""
},
"optionsSyncDisconnect": {
"message": "切断",
"description": ""
},
"optionsSyncSyncNow": {
"message": "すぐに同期",
"description": ""
},
"optionsSyncLogin": {
"message": "ログイン",
"description": ""
},
"optionsSyncStatusPull": {
"message": "スタイルをダウンロード中 ( $loaded$/ $total$)",
"description": "",
"placeholders": {
"loaded": {
"content": "$1"
},
"total": {
"content": "$2"
}
}
},
"optionsSyncStatusPush": {
"message": "スタイルをアップロード中( $loaded$/ $total$)",
"description": "",
"placeholders": {
"loaded": {
"content": "$1"
},
"total": {
"content": "$2"
}
}
},
"optionsSyncStatusSyncing": {
"message": "同期中...",
"description": ""
},
"optionsSyncStatusConnecting": {
"message": "接続中...",
"description": ""
},
"optionsSyncStatusConnected": {
"message": "接続状態",
"description": ""
},
"optionsSyncStatusDisconnecting": {
"message": "切断中...",
"description": ""
},
"optionsSyncStatusDisconnected": {
"message": "切断状態",
"description": ""
},
"paginationCurrent": {
"message": "現在のページ",
"description": "Tooltip for the current page index in search results"
@ -1050,7 +1126,7 @@
"description": "The error message to show when stylus failed to parse usercss"
},
"popupAutoResort": {
"message": "トグルによってポップアップのスタイルを並べ直します",
"message": "ポップアップのスタイルをソートして表示",
"description": "Label for the checkbox controlling popup resorting."
},
"popupBorders": {
@ -1101,6 +1177,10 @@
"message": "変更を保存しないで一時的に適用します。\n変更を確定する場合には、スタイルを保存してください。",
"description": "Tooltip for the checkbox in style editor to enable live preview while editing."
},
"reload": {
"message": "Stylus拡張をリロードする",
"description": "Context menu reload"
},
"replace": {
"message": "置換",
"description": "Label before the replace input field in the editor shown on Ctrl-H"
@ -1504,6 +1584,10 @@
"message": "Dropboxにエクスポート",
"description": ""
},
"syncDropboxDeprecated": {
"message": "Dropboxへのインポート/エクスポート機能は、オプションページにある より進歩したスタイル同期機能 へ移行しました。",
"description": ""
},
"retrieveDropboxSync": {
"message": "Dropboxからインポート",
"description": ""

View File

@ -117,7 +117,7 @@
"description": "Label for the checkbox in the style editor."
},
"cm_autoCloseBracketsTooltip": {
"message": "Automatisch sluitteken toevoegen by typen van een openingsteken uit ()[]{}''\"\"",
"message": "Automatisch sluitingsteken toevoegen bij typen van een openingsteken uit ()[]{}''\"\"",
"description": "Label for the checkbox in the style editor."
},
"cm_autocompleteOnTyping": {
@ -240,6 +240,14 @@
"message": "Ja",
"description": "'Yes' button in a confirm dialog"
},
"copied": {
"message": "Gekopieerd naar klembord",
"description": "Message shown when content has been copied to the clipboard"
},
"copy": {
"message": "Kopiëren naar klembord",
"description": "Tooltip for elements which can be copied"
},
"dateInstalled": {
"message": "Installatiedatum",
"description": "Option text for the user to sort the style by install date"
@ -280,6 +288,10 @@
"message": "Sleep uw back-upbestand naar deze pagina om het te importeren.",
"description": "Drag'n'drop message"
},
"dragDropUsercssTabstrip": {
"message": "Sleep het bestand naar de tabbladenstrook (het gebied waar de tabbladtitels worden getoond) om het te installeren.",
"description": "Message popup shown when erroneously dropping a usercss file into the manager page"
},
"editDeleteText": {
"message": "Verwijderen",
"description": "Label for the context menu item in the editor to delete selected text"
@ -429,6 +441,10 @@
"message": "Druk op een sneltoets",
"description": "Placeholder text of inputbox in keymap help popup on the edit style page. Must be very short"
},
"hostDisabled": {
"message": "Deze host is uitgeschakeld vanwege een bug in de huidige versie van de gebruikte browser",
"description": "Tooltip for cloud host disabled"
},
"importAppendLabel": {
"message": "Toevoegen aan stijl",
"description": "Label for the button to import a style and append to the existing sections"
@ -522,10 +538,6 @@
"message": "Controleren op updates",
"description": "Label for the checkbox to save current URL for update check"
},
"installUpdateUnavailable": {
"message": "Als u controle op updates wilt inschakelen, sleep dan het bestand naar de tabbladenstrook of geef een @updateURL op in de metagegevens van de stijl.",
"description": ""
},
"license": {
"message": "Licentie",
"description": "Label for the license"
@ -602,17 +614,17 @@
"description": "The label of live-reload error"
},
"liveReloadInstallHint": {
"message": "Live herladen is ingeschakeld. De geïnstalleerde stijl zal bij externe wijzigingen automatisch worden bijgewerkt als zowel dit tabblad als het tabblad van het bronbestand zijn geopend.",
"message": "Houd dit tabblad geopend om de stijl automatisch bij te werken bij externe wijzigingen.",
"description": "The label of live-reload feature"
},
"liveReloadInstallHintFF": {
"message": "Houd zowel dit tabblad als het oorspronkelijke tabblad geopend om de stijl automatisch bij te werken bij externe wijzigingen.",
"description": "The extra hint of live-reload feature shown only for file:// URLs in Firefox"
},
"liveReloadLabel": {
"message": "Live herladen",
"description": "The label of live-reload feature"
},
"liveReloadUnavailable": {
"message": "Als u live herladen wilt inschakelen, sleep dan het bestand naar de tabbladenstrook (het gebied waar de tabbladtitels worden getoond).",
"description": ""
},
"manageFavicons": {
"message": "Favicons in kolom Van toepassing op",
"description": "Label for the checkbox that toggles applies-to favicons in the new UI on manage page"
@ -901,11 +913,7 @@
"message": "Beheren",
"description": "Link to open the manage page."
},
"openOptionsManage": {
"message": "Opties",
"description": "Go to Options UI"
},
"openOptionsPopup": {
"openOptions": {
"message": "Opties",
"description": "Go to Options UI"
},
@ -965,6 +973,10 @@
"message": "Pop-up",
"description": ""
},
"optionsCustomizeSync": {
"message": "Synchroniseren met cloud",
"description": ""
},
"optionsHeading": {
"message": "Opties",
"description": "Heading for options section on manage page."
@ -1009,6 +1021,70 @@
"message": "Interval voor automatisch bijwerken van gebruikersstijlen in uren (geef 0 op om dit uit te schakelen)",
"description": ""
},
"optionsSyncNone": {
"message": "Geen",
"description": ""
},
"optionsSyncConnect": {
"message": "Koppelen",
"description": ""
},
"optionsSyncDisconnect": {
"message": "Ontkoppelen",
"description": ""
},
"optionsSyncSyncNow": {
"message": "Nu synchroniseren",
"description": ""
},
"optionsSyncLogin": {
"message": "Aanmelden",
"description": ""
},
"optionsSyncStatusPull": {
"message": "Stijl $loaded$ van $total$ ophalen",
"description": "",
"placeholders": {
"loaded": {
"content": "$1"
},
"total": {
"content": "$2"
}
}
},
"optionsSyncStatusPush": {
"message": "Stijl $loaded$ van $total$ uploaden",
"description": "",
"placeholders": {
"loaded": {
"content": "$1"
},
"total": {
"content": "$2"
}
}
},
"optionsSyncStatusSyncing": {
"message": "Synchroniseren...",
"description": ""
},
"optionsSyncStatusConnecting": {
"message": "Koppelen...",
"description": ""
},
"optionsSyncStatusConnected": {
"message": "Gekoppeld",
"description": ""
},
"optionsSyncStatusDisconnecting": {
"message": "Ontkoppelen...",
"description": ""
},
"optionsSyncStatusDisconnected": {
"message": "Ontkoppeld",
"description": ""
},
"paginationCurrent": {
"message": "Huidige pagina",
"description": "Tooltip for the current page index in search results"
@ -1085,6 +1161,10 @@
"message": "Past de wijzigingen tijdelijk toe zonder deze op te slaan.\nSla de stijl op om de wijzigingen permanent te maken.",
"description": "Tooltip for the checkbox in style editor to enable live preview while editing."
},
"reload": {
"message": "Stylus-extensie herladen",
"description": "Context menu reload"
},
"replace": {
"message": "Vervangen",
"description": "Label before the replace input field in the editor shown on Ctrl-H"
@ -1488,6 +1568,10 @@
"message": "Dropbox - Exporteren",
"description": ""
},
"syncDropboxDeprecated": {
"message": "Importeren/exporteren via Dropbox is vervangen door een meer geavanceerde stijlsynchronisatie in de optiespagina.",
"description": ""
},
"retrieveDropboxSync": {
"message": "Dropbox - Importeren",
"description": ""

View File

@ -244,6 +244,14 @@
"message": "Tak",
"description": "'Yes' button in a confirm dialog"
},
"copied": {
"message": "Skopiowane do schowka",
"description": "Message shown when content has been copied to the clipboard"
},
"copy": {
"message": "Skopiuj do schowka",
"description": "Tooltip for elements which can be copied"
},
"dateInstalled": {
"message": "Data zainstalowania",
"description": "Option text for the user to sort the style by install date"
@ -284,6 +292,10 @@
"message": "Upuść twój plik kopii zapasowej gdziekolwiek na tej stronie, aby zaimportować.",
"description": "Drag'n'drop message"
},
"dragDropUsercssTabstrip": {
"message": "Aby zainstalować plik, upuść go na pasku kart (obszar, w którym wyświetlane są tytuły kart).",
"description": "Message popup shown when erroneously dropping a usercss file into the manager page"
},
"editDeleteText": {
"message": "Usuń",
"description": "Label for the context menu item in the editor to delete selected text"
@ -441,6 +453,10 @@
"message": "Naciśnij klawisz skrótu",
"description": "Placeholder text of inputbox in keymap help popup on the edit style page. Must be very short"
},
"hostDisabled": {
"message": "Ten host został wyłączony z powodu błędu w bieżącej wersji używanej przeglądarki",
"description": "Tooltip for cloud host disabled"
},
"importAppendLabel": {
"message": "Dołącz do stylu",
"description": "Label for the button to import a style and append to the existing sections"
@ -534,10 +550,6 @@
"message": "Sprawdź aktualizacje",
"description": "Label for the checkbox to save current URL for update check"
},
"installUpdateUnavailable": {
"message": "Aby włączyć sprawdzanie aktualizacji, upuść plik na pasku kart lub określ @updateURL w metadanych stylu.",
"description": ""
},
"license": {
"message": "Licencja",
"description": "Label for the license"
@ -614,17 +626,17 @@
"description": "The label of live-reload error"
},
"liveReloadInstallHint": {
"message": "Przeładowanie na żywo jest włączone, więc zainstalowany styl zostanie automatycznie zaktualizowany w przypadku zmian zewnętrznych, gdy ta karta i karta pliku źródłowego są otwarte.",
"message": "Pozostaw tę kartę otwartą, aby automatycznie aktualizować styl w przypadku zmian zewnętrznych.",
"description": "The label of live-reload feature"
},
"liveReloadInstallHintFF": {
"message": "Pozostaw zarówno tę kartę, jak i kartę pierwotną otwarte, aby automatycznie aktualizować styl w przypadku zmian zewnętrznych.",
"description": "The extra hint of live-reload feature shown only for file:// URLs in Firefox"
},
"liveReloadLabel": {
"message": "Przeładuj na żywo",
"description": "The label of live-reload feature"
},
"liveReloadUnavailable": {
"message": "Aby włączyć przeładowanie na żywo, upuść plik na pasku kart (obszar, w którym wyświetlane są tytuły kart).",
"description": ""
},
"manageFavicons": {
"message": "Ikony ulubionych w kolumnie dotyczących",
"description": "Label for the checkbox that toggles applies-to favicons in the new UI on manage page"
@ -917,11 +929,7 @@
"message": "Zarządzaj",
"description": "Link to open the manage page."
},
"openOptionsManage": {
"message": "Opcje interfejsu",
"description": "Go to Options UI"
},
"openOptionsPopup": {
"openOptions": {
"message": "Opcje",
"description": "Go to Options UI"
},
@ -985,6 +993,10 @@
"message": "Aktualizacje",
"description": ""
},
"optionsCustomizeSync": {
"message": "Synchronizuj z chmurą",
"description": ""
},
"optionsHeading": {
"message": "Opcje",
"description": "Heading for options section on manage page."
@ -1029,6 +1041,70 @@
"message": "Interwał automatycznej aktualizacji stylu w godzinach (podaj 0, aby wyłączyć)",
"description": ""
},
"optionsSyncNone": {
"message": "Brak",
"description": ""
},
"optionsSyncConnect": {
"message": "Połącz",
"description": ""
},
"optionsSyncDisconnect": {
"message": "Rozłącz",
"description": ""
},
"optionsSyncSyncNow": {
"message": "Synchronizuj teraz",
"description": ""
},
"optionsSyncLogin": {
"message": "Logowanie",
"description": ""
},
"optionsSyncStatusPull": {
"message": "Przyciąganie stylu $loaded$ z $total$",
"description": "",
"placeholders": {
"loaded": {
"content": "$1"
},
"total": {
"content": "$2"
}
}
},
"optionsSyncStatusPush": {
"message": "Dostarczanie stylu $loaded$ z $total$",
"description": "",
"placeholders": {
"loaded": {
"content": "$1"
},
"total": {
"content": "$2"
}
}
},
"optionsSyncStatusSyncing": {
"message": "Synchronizowanie...",
"description": ""
},
"optionsSyncStatusConnecting": {
"message": "Łączenie...",
"description": ""
},
"optionsSyncStatusConnected": {
"message": "Połączono",
"description": ""
},
"optionsSyncStatusDisconnecting": {
"message": "Rozłączanie...",
"description": ""
},
"optionsSyncStatusDisconnected": {
"message": "Rozłączono",
"description": ""
},
"paginationCurrent": {
"message": "Bieżąca strona",
"description": "Tooltip for the current page index in search results"
@ -1105,6 +1181,10 @@
"message": "Tymczasowo stosuje zmiany bez zapisywania.\nZapisz styl, aby zmiany stały się trwałe.",
"description": "Tooltip for the checkbox in style editor to enable live preview while editing."
},
"reload": {
"message": "Przeładuj rozszerzenie Stylus",
"description": "Context menu reload"
},
"replace": {
"message": "Zamień",
"description": "Label before the replace input field in the editor shown on Ctrl-H"
@ -1512,6 +1592,10 @@
"message": "Eksportuj do Dropboksa",
"description": ""
},
"syncDropboxDeprecated": {
"message": "Importowanie i eksportowanie związane z Dropboksem zostaje zastąpione bardziej zaawansowaną synchronizacją stylów na stronie opcji.",
"description": ""
},
"retrieveDropboxSync": {
"message": "Importuj z Dropboksa",
"description": ""

View File

@ -245,10 +245,6 @@
"message": "Gerenciar estilos instalados",
"description": "Link to open the manage page."
},
"openOptionsPopup": {
"message": "Opções",
"description": "Go to Options UI"
},
"optionsActions": {
"message": "Ações",
"description": ""

View File

@ -506,10 +506,6 @@
"message": "Procurar atualizações",
"description": "Label for the checkbox to save current URL for update check"
},
"installUpdateUnavailable": {
"message": "Para ativar a verificação de atualizações, solte o ficheiro na faixa de separadores ou especifique @updateURL nos metadados de estilo.",
"description": ""
},
"license": {
"message": "Licença",
"description": "Label for the license"
@ -585,18 +581,10 @@
"message": "Ocorreu um erro ao vigiar o arquivo",
"description": "The label of live-reload error"
},
"liveReloadInstallHint": {
"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.",
"description": "The label of live-reload feature"
},
"liveReloadLabel": {
"message": "Recarregamento dinâmico",
"description": "The label of live-reload feature"
},
"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).",
"description": ""
},
"manageFavicons": {
"message": "Favicons em colunas de aplica-se a",
"description": "Label for the checkbox that toggles applies-to favicons in the new UI on manage page"
@ -673,14 +661,6 @@
"message": "Gerir",
"description": "Link to open the manage page."
},
"openOptionsManage": {
"message": "interface de Opções",
"description": "Go to Options UI"
},
"openOptionsPopup": {
"message": "Opções",
"description": "Go to Options UI"
},
"openStylesManager": {
"message": "Abrir gestor de estilos",
"description": "Label for the style maanger opener in the browser action context menu."

View File

@ -462,10 +462,6 @@
"message": "Verificați update-urile",
"description": "Label for the checkbox to save current URL for update check"
},
"installUpdateUnavailable": {
"message": "Pentru a activa verificarea de updates. trage fișierul pe taburi (zona cu titluri) sau specifica @updateURL în metadata temei.",
"description": ""
},
"license": {
"message": "Licență",
"description": "Label for the license"
@ -537,14 +533,6 @@
"message": "A avut loc o eroare în timpul monitorizării acestui fișier",
"description": "The label of live-reload error"
},
"liveReloadInstallHint": {
"message": "Reload automat este activat deci tema instalată va fi updatată automat când acest tab si fișierul surca sunt deschise.",
"description": "The label of live-reload feature"
},
"liveReloadUnavailable": {
"message": "Pentru a activa live reload (refresh automat), trage fișierul pe taburi (zona unde titlurile temelor sunt afișate) ",
"description": ""
},
"manageFavicons": {
"message": "Favicons în coloana 'se aplică la'",
"description": "Label for the checkbox that toggles applies-to favicons in the new UI on manage page"
@ -617,14 +605,6 @@
"message": "Managerul",
"description": "Link to open the manage page."
},
"openOptionsManage": {
"message": "UI cu opțiuni",
"description": "Go to Options UI"
},
"openOptionsPopup": {
"message": "Opțiuni",
"description": "Go to Options UI"
},
"openStylesManager": {
"message": "Deschideți managerul de teme",
"description": "Label for the style maanger opener in the browser action context menu."

View File

@ -534,10 +534,6 @@
"message": "Проверить обновления",
"description": "Label for the checkbox to save current URL for update check"
},
"installUpdateUnavailable": {
"message": "Для проверки обновлений перетяните файл на полоску вкладок или впишите @updateURL в мета-данных стиля.",
"description": ""
},
"license": {
"message": "Лицензия",
"description": "Label for the license"
@ -617,18 +613,10 @@
"message": "Ошибка слежения за файлом",
"description": "The label of live-reload error"
},
"liveReloadInstallHint": {
"message": "Включена автозагрузка изменений установленный стиль будет обновляться автоматически пока открыта эта вкладка и вкладка исходного файла.",
"description": "The label of live-reload feature"
},
"liveReloadLabel": {
"message": "Автозагрузка изменений",
"description": "The label of live-reload feature"
},
"liveReloadUnavailable": {
"message": "Для автозагрузки изменений перетяните файл на полоску вкладок (область, где показываются названия вкладок).",
"description": ""
},
"manageFavicons": {
"message": "Пиктограммы для целевых сайтов",
"description": "Label for the checkbox that toggles applies-to favicons in the new UI on manage page"
@ -921,14 +909,6 @@
"message": "Менеджер",
"description": "Link to open the manage page."
},
"openOptionsManage": {
"message": "Настройки",
"description": "Go to Options UI"
},
"openOptionsPopup": {
"message": "Настройки",
"description": "Go to Options UI"
},
"openStylesManager": {
"message": "Менеджер стилей",
"description": "Label for the style maanger opener in the browser action context menu."

View File

@ -236,6 +236,14 @@
"message": "Ja",
"description": "'Yes' button in a confirm dialog"
},
"copied": {
"message": "Kopierad till urklipp",
"description": "Message shown when content has been copied to the clipboard"
},
"copy": {
"message": "Kopiera till urklipp",
"description": "Tooltip for elements which can be copied"
},
"dateInstalled": {
"message": "Installerat datum",
"description": "Option text for the user to sort the style by install date"
@ -514,10 +522,6 @@
"message": "Sök efter uppdateringar",
"description": "Label for the checkbox to save current URL for update check"
},
"installUpdateUnavailable": {
"message": "För att aktivera sök efter uppdateringar, släpp filen på flikremsan eller ange @updateURL i stilmetadatan.",
"description": ""
},
"license": {
"message": "Licens",
"description": "Label for the license"
@ -589,18 +593,10 @@
"message": "Ett fel uppstod medan du tittade på filen",
"description": "The label of live-reload error"
},
"liveReloadInstallHint": {
"message": "Uppdatering i realtid är aktiverat så att den installerade stilen automatiskt uppdateras vid externa ändringar medan både den här fliken och fliken för källfilen är öppna.",
"description": "The label of live-reload feature"
},
"liveReloadLabel": {
"message": "Uppdaterar i realtid",
"description": "The label of live-reload feature"
},
"liveReloadUnavailable": {
"message": "För att aktivera uppdatering i realtid, släpp filen på fliken strip (det område där fliktitlar visas).",
"description": ""
},
"manageFavicons": {
"message": "Ikoner i 'Tillämpad för' kolumnen",
"description": "Label for the checkbox that toggles applies-to favicons in the new UI on manage page"
@ -871,14 +867,6 @@
"message": "Hantera installerade stilar",
"description": "Link to open the manage page."
},
"openOptionsManage": {
"message": "Alternativ UI",
"description": "Go to Options UI"
},
"openOptionsPopup": {
"message": "Alternativ",
"description": "Go to Options UI"
},
"openStylesManager": {
"message": "Öppna stilhanteraren",
"description": "Label for the style maanger opener in the browser action context menu."
@ -939,6 +927,10 @@
"message": "Uppdateringar",
"description": ""
},
"optionsCustomizeSync": {
"message": "Synkronisera till molnet",
"description": ""
},
"optionsHeading": {
"message": "Alternativ",
"description": "Heading for options section on manage page."
@ -983,6 +975,46 @@
"message": "Userstyle automatisk uppdateringsintervall i timmar (ange 0 för att inaktivera)",
"description": ""
},
"optionsSyncNone": {
"message": "Inga",
"description": ""
},
"optionsSyncConnect": {
"message": "Anslut",
"description": ""
},
"optionsSyncDisconnect": {
"message": "Koppla från",
"description": ""
},
"optionsSyncSyncNow": {
"message": "Synkronisera nu",
"description": ""
},
"optionsSyncLogin": {
"message": "Logga in",
"description": ""
},
"optionsSyncStatusSyncing": {
"message": "Synkroniserar...",
"description": ""
},
"optionsSyncStatusConnecting": {
"message": "Ansluter..",
"description": ""
},
"optionsSyncStatusConnected": {
"message": "Ansluten",
"description": ""
},
"optionsSyncStatusDisconnecting": {
"message": "Kopplar från...",
"description": ""
},
"optionsSyncStatusDisconnected": {
"message": "Frånkopplad",
"description": ""
},
"paginationCurrent": {
"message": "Aktuell sida",
"description": "Tooltip for the current page index in search results"

View File

@ -7,6 +7,10 @@
"message": "Stil Ekleyin",
"description": "Title of the page for adding styles"
},
"alphaChannel": {
"message": "Opaklık",
"description": "Label of color's opacity"
},
"appliesAdd": {
"message": "Ekleyin",
"description": "Label for the button to add an 'applies' entry"
@ -56,6 +60,18 @@
"message": "Şununla başlayan URL'ler:",
"description": "Option to make the style apply to the entered string as a URL prefix"
},
"applyAllUpdates": {
"message": "Tüm güncellemeleri uygula",
"description": "Label for the button to apply all detected updates"
},
"author": {
"message": "Yazar",
"description": "Label for the style author"
},
"bckpInstStyles": {
"message": "Dışa aktar",
"description": ""
},
"checkAllUpdates": {
"message": "Tüm stiller için güncellemeleri denetle",
"description": "Label for the button to check all styles for updates"
@ -68,6 +84,106 @@
"message": "Kontrol ediliyor...",
"description": "Text to display when checking a style for an update"
},
"clickToUninstall": {
"message": "Kaldırmak için tıklayın",
"description": "Label for the overlay on a style thumbnail when installed via inline search in the popup"
},
"cm_autoCloseBrackets": {
"message": "Parantezleri ve tırnak işaretlerini otomatik olarak kapat",
"description": "Label for the checkbox in the style editor."
},
"cm_autocompleteOnTyping": {
"message": "Yazarken tamamla",
"description": "Label for the checkbox in the style editor."
},
"cm_colorpicker": {
"message": "CSS renk seçicileri",
"description": "Label for the checkbox controlling colorpicker option for the style editor."
},
"cm_indentWithTabs": {
"message": "Akıllı girintili tab kullan",
"description": "Label for the checkbox controlling tabs with smart indentation option for the style editor."
},
"cm_matchHighlight": {
"message": "Vurgulama",
"description": "Label for the drop-down list controlling the automatic highlighting of current word/selection occurrences in the style editor."
},
"cm_matchHighlightSelection": {
"message": "Yalnızca seçim",
"description": "Style editor's 'highglight' drop-down list option: highlight the occurrences of currently selected text"
},
"cm_tabSize": {
"message": "Tab büyüklüğü",
"description": "Label for the text box controlling tab size option for the style editor."
},
"cm_theme": {
"message": "Tema",
"description": "Label for the style editor's CSS theme."
},
"configOnChange": {
"message": "değişiklikte",
"description": "VERY SHORT label for the checkbox in style config dialog after the save button - when enabled the changes in the dialog are saved and applied automatically without the need to press the Save button"
},
"confirmCancel": {
"message": "İptal",
"description": ""
},
"confirmClose": {
"message": "Kapat",
"description": "'Close' button in a confirm dialog"
},
"confirmDefault": {
"message": "Öntanımlıyı kullan",
"description": "'Set to default' button in a confirm dialog"
},
"confirmDelete": {
"message": "Sil",
"description": ""
},
"confirmDiscardChanges": {
"message": "Değişiklikleri iptal et?",
"description": "Generic label or title displayed when trying to close something (not a style) with unsaved changes"
},
"confirmNo": {
"message": "Hayır",
"description": "'No' button in a confirm dialog"
},
"confirmOK": {
"message": "Tamam",
"description": ""
},
"confirmSave": {
"message": "Kaydet",
"description": "'Save' button in a confirm dialog"
},
"confirmStop": {
"message": "Dur",
"description": "'Stop' button in a confirm dialog"
},
"confirmYes": {
"message": "Evet",
"description": "'Yes' button in a confirm dialog"
},
"copied": {
"message": "Kopyalandı",
"description": "Message shown when content has been copied to the clipboard"
},
"copy": {
"message": "Kopyala",
"description": "Tooltip for elements which can be copied"
},
"dateInstalled": {
"message": "Kurulum tarihi",
"description": "Option text for the user to sort the style by install date"
},
"dateUpdated": {
"message": "Güncelleme tarihi",
"description": "Option text for the user to sort the style by last update date"
},
"defaultTheme": {
"message": "öntanımlı",
"description": "Default CodeMirror CSS theme option on the edit style page"
},
"deleteStyleConfirm": {
"message": "Bu stili silmek istediğinizden emin misiniz?",
"description": "Confirmation before deleting a style"
@ -80,10 +196,22 @@
"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.",
"description": "Extension description"
},
"disableAllStyles": {
"message": "Tüm stilleri kapat",
"description": "Label for the checkbox that turns all enabled styles off."
},
"disableStyleLabel": {
"message": "Devre dışı bırak",
"description": "Label for the button to disable a style"
},
"editDeleteText": {
"message": "Sil",
"description": "Label for the context menu item in the editor to delete selected text"
},
"editGotoLine": {
"message": "Satıra git (veya satır:sütun)",
"description": "Go to line or line:column on Ctrl-G in style code editor"
},
"editStyleHeading": {
"message": "Stili Düzenle",
"description": "Title of the page for editing styles"
@ -101,26 +229,166 @@
}
}
},
"editorStylesButton": {
"message": "Editör stili bul",
"description": "Find styles for the editor"
},
"enableStyleLabel": {
"message": "Etkinleştir",
"description": "Label for the button to enable a style"
},
"exportLabel": {
"message": "Dışa aktar",
"description": "Label for the button to export a style ('edit' page) or all styles ('manage' page)"
},
"externalFeedback": {
"message": "Geribildirim",
"description": "Label for the external link to send feedback for the style"
},
"externalHomepage": {
"message": "Anasayfa",
"description": "Label for the external link to style's homepage"
},
"externalSupport": {
"message": "Destek",
"description": "Label for the external link to style's support site"
},
"findStyles": {
"message": "Stil bul",
"description": "Text for a link that gets a list of styles for the current site"
},
"findStylesForSite": {
"message": "Bu site için başka stiller bul",
"description": "Text for a link that gets a list of styles for the current site"
},
"genericAdd": {
"message": "Ekle",
"description": "Used in various places for an action that adds something"
},
"genericDisabledLabel": {
"message": "Devre dışı",
"description": "Used in various lists/options to indicate that something is disabled"
},
"genericEnabledLabel": {
"message": "Etkin",
"description": "Used in various lists/options to indicate that something is enabled"
},
"genericError": {
"message": "Hata",
"description": "Used in various places to indicate some error occurred."
},
"genericHistoryLabel": {
"message": "Geçmiş",
"description": "Used in various places to show a history log of something"
},
"genericNext": {
"message": "Sonraki",
"description": "Used in various places to select/perform the next step/action"
},
"genericPrevious": {
"message": "Önceki",
"description": "Used in various places to select/perform the previous step/action"
},
"genericSavedMessage": {
"message": "Kaydedildi",
"description": "Used in various parts of the UI to indicate that something was saved"
},
"genericTitle": {
"message": "Başlık",
"description": "Used in various parts of the UI to indicate the title of something"
},
"genericUnknown": {
"message": "Bilinmiyor",
"description": "Used in various parts of the UI to indicate if something is unknown (e.g. an unknown date)"
},
"helpAlt": {
"message": "Yardım",
"description": "Alternate text for help buttons"
},
"importAppendLabel": {
"message": "Stile ekle",
"description": "Label for the button to import a style and append to the existing sections"
},
"importLabel": {
"message": "İçe aktar",
"description": "Label for the button to import a style ('edit' page) or all styles ('manage' page)"
},
"importReplaceLabel": {
"message": "Stilin üzerine yaz",
"description": "Label for the button to import and overwrite current style"
},
"importReportLegendAdded": {
"message": "eklendi",
"description": "Text after the number of styles added in the report shown after importing styles"
},
"importReportLegendUpdatedCode": {
"message": "güncellenmiş kod",
"description": "Text after the number of styles with updated code (meta info is unchanged) in the report shown after importing styles"
},
"importReportUndoneTitle": {
"message": "İçe aktarma geri alındı",
"description": "Title of the message box shown after undoing the import of styles"
},
"installButton": {
"message": "Stil kur",
"description": "Label for install button"
},
"installButtonInstalled": {
"message": "Stil kuruldu",
"description": "Text displayed when the style is successfully installed"
},
"installButtonReinstall": {
"message": "Stili yeniden kur",
"description": "Label for reinstall button"
},
"installButtonUpdate": {
"message": "Stili güncelle",
"description": "Label for update button"
},
"installUpdate": {
"message": "Güncellemeyi yükle",
"description": "Label for the button to install an update for a single style"
},
"installUpdateFromLabel": {
"message": "Güncellemeleri denetle",
"description": "Label for the checkbox to save current URL for update check"
},
"license": {
"message": "Lisans",
"description": "Label for the license"
},
"linkGetHelp": {
"message": "Destek al",
"description": "Homepage link text on the manage page e.g. https://add0n.com/stylus.html#features with chat/FAQ/intro/info"
},
"linkStylusWiki": {
"message": "Viki",
"description": "Wiki link text on the manage page e.g. https://github.com/openstyles/stylus/wiki"
},
"linkTranslate": {
"message": "Çevir",
"description": "Transifex link text on the manage page"
},
"linterIssues": {
"message": "Sorunlar",
"description": "Label for the CSS linter issues block on the style edit page"
},
"manageFilters": {
"message": "Filtreler",
"description": "Label for filters container"
},
"manageHeading": {
"message": "Yüklü Stiller",
"description": "Heading for the manage page"
},
"manageOnlyEnabled": {
"message": "Yalnızca etkin stiller",
"description": "Checkbox to show only enabled styles"
},
"menuShowBadge": {
"message": "Etkin stil sayısını göster",
"description": "Label (must be very short) for the checkbox in the toolbar button context menu controlling toolbar badge text."
},
"noStylesForSite": {
"message": "Bu site için hiçbir stil yüklenmedi.",
"description": "Text displayed when no styles are installed for the current site"
@ -129,6 +397,130 @@
"message": "Yüklü stilleri yönet",
"description": "Link to open the manage page."
},
"openStylesManager": {
"message": "Stil yöneticisini aç",
"description": "Label for the style maanger opener in the browser action context menu."
},
"optionsActions": {
"message": "İşlemler",
"description": ""
},
"optionsBadgeDisabled": {
"message": "Etkin değilken arkaplan rengi",
"description": ""
},
"optionsBadgeNormal": {
"message": "Arkaplan rengi",
"description": ""
},
"optionsCheck": {
"message": "Stilleri güncelle",
"description": ""
},
"optionsHeading": {
"message": "Seçenekler",
"description": "Heading for options section on manage page."
},
"optionsOpen": {
"message": "Aç",
"description": ""
},
"optionsOpenManager": {
"message": "Stilleri yönet",
"description": ""
},
"optionsSubheading": {
"message": "Daha Fazla Seçenek",
"description": "Subheading for options section on manage page."
},
"optionsSyncConnect": {
"message": "Bağlan",
"description": ""
},
"optionsSyncDisconnect": {
"message": "Bağlantıyı kes",
"description": ""
},
"optionsSyncLogin": {
"message": "Giriş yap",
"description": ""
},
"optionsSyncStatusConnecting": {
"message": "Bağlanılıyor...",
"description": ""
},
"optionsSyncStatusConnected": {
"message": "Bağlandı",
"description": ""
},
"optionsSyncStatusDisconnecting": {
"message": "Bağlantı kesiliyor...",
"description": ""
},
"optionsSyncStatusDisconnected": {
"message": "Bağlantı kesildi",
"description": ""
},
"paginationCurrent": {
"message": "Şimdiki sayfa",
"description": "Tooltip for the current page index in search results"
},
"paginationEstimated": {
"message": "Tahmini sayfa sayısı",
"description": "Tooltip for the total page count in search results"
},
"paginationNext": {
"message": "Sonraki sayfa",
"description": "Tooltip for the '->' (next page) button in search results"
},
"paginationPrevious": {
"message": "Önceki sayfa",
"description": "Tooltip for the '<-' button in search results"
},
"paginationTotal": {
"message": "Toplam sayfa",
"description": ""
},
"previewLabel": {
"message": "Canlı önizleme",
"description": "Label for the checkbox in style editor to enable live preview while editing."
},
"replace": {
"message": "Değiştir",
"description": "Label before the replace input field in the editor shown on Ctrl-H"
},
"replaceAll": {
"message": "Hepsini değiştir",
"description": "Label before the replace input field in the editor shown on 'replaceAll' hotkey"
},
"replaceWith": {
"message": "Şununla değiştir",
"description": "Label before the replace-with input field in the editor shown on Ctrl-H etc."
},
"retrieveBckp": {
"message": "Stilleri içe aktar",
"description": ""
},
"search": {
"message": "Ara",
"description": "Label before the search input field in the editor shown on Ctrl-F"
},
"searchResultInstallCount": {
"message": "Toplam kurulum sayısı",
"description": "Text for label that shows the number of times a search result was installed"
},
"searchResultNoneFound": {
"message": "Bu site için stil bulunamadı.",
"description": "Error text in the popup when inline search didn't find any site-specific styles"
},
"searchResultWeeklyCount": {
"message": "Haftalık kurulum sayısı",
"description": "Text for label that shows the number of times a search result was installed during last week"
},
"searchStyles": {
"message": "İçerikleri ara",
"description": "Label for the search filter textbox on the Manage styles page"
},
"sectionAdd": {
"message": "Başka bölüm ekle",
"description": "Label for the button to add a section"
@ -141,6 +533,30 @@
"message": "Bölümü kaldır",
"description": "Label for the button to remove a section"
},
"shortcuts": {
"message": "Kısayollar",
"description": "Go to shortcut configuration"
},
"shortcutsNote": {
"message": "Klavye kısayolları tanımla",
"description": ""
},
"sortDateNewestFirst": {
"message": "önce en yeniler",
"description": "Text added to indicate that sorting a date would add the newest entries at the top"
},
"sortDateOldestFirst": {
"message": "önce en eskiler",
"description": "Text added to indicate that sorting a date would add the oldest entries at the top"
},
"sortStylesHelpTitle": {
"message": "İçeriği sırala",
"description": "Label for the sort info popup on the Manage styles page"
},
"styleBeautify": {
"message": "Güzelleştir",
"description": "Label for the CSS-beautifier button on the edit style page"
},
"styleCancelEditLabel": {
"message": "Yönetim sayfasına dön",
"description": "Label for cancel button for style editing"
@ -153,6 +569,10 @@
"message": "Etkin",
"description": "Label for the enabled state of styles"
},
"styleFromMozillaFormatPrompt": {
"message": "Mozilla biçemiyle yazılmış kodu yapıştır",
"description": "Prompt in the dialog displayed after clicking 'Import from Mozilla format' button"
},
"styleInstall": {
"message": "'$stylename$' Stylus'e yüklensin mi?",
"description": "Confirmation when installing a style",
@ -166,6 +586,10 @@
"message": "Bir ad girin",
"description": "Error displayed when user saves without providing a name"
},
"styleMozillaFormatHeading": {
"message": "Mozilla Biçemi",
"description": "Heading for the section with buttons to import/export Mozilla format of the style"
},
"styleSaveLabel": {
"message": "Kaydet",
"description": "Label for save button for style editing"
@ -174,6 +598,31 @@
"message": "Kodun Mozilla biçimi, Firefox için Stylish ile kullanılabilir ve userstyles.org sitesine gönderilebilir.",
"description": "Help info for the Mozilla format header section that converts the code to/from Mozilla format"
},
"styleToMozillaFormatTitle": {
"message": "Mozilla biçeminde bir stil",
"description": "Title of the popup with the style code in Mozilla format, shown after pressing the Export button on Edit style page"
},
"styleUpdate": {
"message": "\";$stylename$\" stilini güncelleştirmek istiyor musunuz?",
"description": "Confirmation when updating a style",
"placeholders": {
"stylename": {
"content": "$1"
}
}
},
"stylusUnavailableForURL": {
"message": "Stylus bunun gibi sayfalarda çalışmaz.",
"description": "Note in the toolbar pop-up when on a URL Stylus can't affect"
},
"undo": {
"message": "Geri al",
"description": "Button label"
},
"updateAllCheckSucceededNoUpdate": {
"message": "Güncelleme bulunamadı.",
"description": "Text that displays when an update all check completed and no updates are available"
},
"updateCheckFailBadResponseCode": {
"message": "Güncellenemedi: sunucu yanıt olarak $code$ kodunu gönderdi.",
"description": "Text that displays when an update check failed because the response code indicates an error",
@ -187,6 +636,10 @@
"message": "Güncellenemedi: sunucuya erişilemiyor.",
"description": "Text that displays when an update check failed because the update server is unreachable"
},
"updateCheckHistory": {
"message": "Güncelleme kontrolü geçmişi",
"description": ""
},
"updateCheckSucceededNoUpdate": {
"message": "Stil güncel.",
"description": "Text that displays when an update check completed and no update is available"
@ -194,5 +647,41 @@
"updateCompleted": {
"message": "Güncelleme tamamlandı.",
"description": "Text that displays when an update completed"
},
"updatesCurrentlyInstalled": {
"message": "Kurulan güncellemeler:",
"description": "Text that displays when an update is installed on options page. Followed by the number of currently installed updates."
},
"writeStyleForURL": {
"message": "bu URL",
"description": "Text for link in toolbar pop-up to write a new style for the current URL"
},
"syncDropboxStyles": {
"message": "Dropbox'tan aktar",
"description": ""
},
"retrieveDropboxSync": {
"message": "Dropbox'a aktar",
"description": ""
},
"exportSavedSuccess": {
"message": "Dosya başarıyla kaydedildi",
"description": ""
},
"gettingStyles": {
"message": "Tüm stiller alınıyor...",
"description": ""
},
"zipStyles": {
"message": "Stiller sıkıştırılıyor...",
"description": ""
},
"readingStyles": {
"message": "Stiller okunuyor...",
"description": ""
},
"uploadingFile": {
"message": "Dosya Yükleniyor...",
"description": ""
}
}

View File

@ -534,10 +534,6 @@
"message": "检查更新",
"description": "Label for the checkbox to save current URL for update check"
},
"installUpdateUnavailable": {
"message": "若想允许检查更新,请将文件拖动到标签栏上,或在样式的元数据中声明 @updateURL。",
"description": ""
},
"license": {
"message": "许可证",
"description": "Label for the license"
@ -613,18 +609,10 @@
"message": "查看文件时发生错误",
"description": "The label of live-reload error"
},
"liveReloadInstallHint": {
"message": "动态刷新被激活后,当被安装的样式被更新时,只要本网页和目标网页都是开启状态,在样式上进行的更新会实时反映到目标网页上。",
"description": "The label of live-reload feature"
},
"liveReloadLabel": {
"message": "动态刷新",
"description": "The label of live-reload feature"
},
"liveReloadUnavailable": {
"message": "要想激活动态刷新,请将文件拖到标签条(即选项卡的区域)上。",
"description": ""
},
"manageFavicons": {
"message": "显示已应用的图标",
"description": "Label for the checkbox that toggles applies-to favicons in the new UI on manage page"
@ -917,14 +905,6 @@
"message": "管理样式",
"description": "Link to open the manage page."
},
"openOptionsManage": {
"message": "设置用户界面",
"description": "Go to Options UI"
},
"openOptionsPopup": {
"message": "设置用户界面",
"description": "Go to Options UI"
},
"openStylesManager": {
"message": "打开样式管理器",
"description": "Label for the style maanger opener in the browser action context menu."

View File

@ -244,6 +244,14 @@
"message": "是",
"description": "'Yes' button in a confirm dialog"
},
"copied": {
"message": "已複製到剪貼簿",
"description": "Message shown when content has been copied to the clipboard"
},
"copy": {
"message": "複製到剪貼簿",
"description": "Tooltip for elements which can be copied"
},
"dateInstalled": {
"message": "安裝日期",
"description": "Option text for the user to sort the style by install date"
@ -284,6 +292,10 @@
"message": "將您的備份檔拖曳到此頁面的任何地方以匯入。",
"description": "Drag'n'drop message"
},
"dragDropUsercssTabstrip": {
"message": "要安裝檔案,請將其放到分頁上(顯示分頁標題的區域)。",
"description": "Message popup shown when erroneously dropping a usercss file into the manager page"
},
"editDeleteText": {
"message": "删除",
"description": "Label for the context menu item in the editor to delete selected text"
@ -441,6 +453,10 @@
"message": "按下快速鍵",
"description": "Placeholder text of inputbox in keymap help popup on the edit style page. Must be very short"
},
"hostDisabled": {
"message": "由於所用目前版本瀏覽器的臭蟲,此主機已被停用",
"description": "Tooltip for cloud host disabled"
},
"importAppendLabel": {
"message": "追加到樣式",
"description": "Label for the button to import a style and append to the existing sections"
@ -534,10 +550,6 @@
"message": "檢查更新",
"description": "Label for the checkbox to save current URL for update check"
},
"installUpdateUnavailable": {
"message": "要啟用檢查更新,將檔案拖放到分頁條上或是在樣式詮釋資料中指定 @updateURL。",
"description": ""
},
"license": {
"message": "授權條款",
"description": "Label for the license"
@ -614,17 +626,17 @@
"description": "The label of live-reload error"
},
"liveReloadInstallHint": {
"message": "即時重新整理已啟用,以便在這個分頁與來源檔案分頁都開啟時自動於有外部變更時自動更新已安裝樣式。",
"message": "保持此分頁開啟以根據外部變更自動更新樣式。",
"description": "The label of live-reload feature"
},
"liveReloadInstallHintFF": {
"message": "保持此分頁與原始分頁開啟以根據外部變更自動更新樣式。",
"description": "The extra hint of live-reload feature shown only for file:// URLs in Firefox"
},
"liveReloadLabel": {
"message": "即時重新整理",
"description": "The label of live-reload feature"
},
"liveReloadUnavailable": {
"message": "要啟用即時重新整理,將檔案托放到分頁條上(分頁標題顯示的區域)。",
"description": ""
},
"manageFavicons": {
"message": "Favicons 要套用到的欄位",
"description": "Label for the checkbox that toggles applies-to favicons in the new UI on manage page"
@ -917,11 +929,7 @@
"message": "管理已安裝樣式",
"description": "Link to open the manage page."
},
"openOptionsManage": {
"message": "選項介面",
"description": "Go to Options UI"
},
"openOptionsPopup": {
"openOptions": {
"message": "選項",
"description": "Go to Options UI"
},
@ -985,6 +993,10 @@
"message": "更新",
"description": ""
},
"optionsCustomizeSync": {
"message": "同步到雲端",
"description": ""
},
"optionsHeading": {
"message": "選項",
"description": "Heading for options section on manage page."
@ -1029,6 +1041,70 @@
"message": "使用者樣式自動更新間隔(以小時計,指定為 0 以停用)",
"description": ""
},
"optionsSyncNone": {
"message": "無",
"description": ""
},
"optionsSyncConnect": {
"message": "連線",
"description": ""
},
"optionsSyncDisconnect": {
"message": "斷線",
"description": ""
},
"optionsSyncSyncNow": {
"message": "立刻同步",
"description": ""
},
"optionsSyncLogin": {
"message": "登入",
"description": ""
},
"optionsSyncStatusPull": {
"message": "正在拉取樣式 $total$ 中的 $loaded$",
"description": "",
"placeholders": {
"loaded": {
"content": "$1"
},
"total": {
"content": "$2"
}
}
},
"optionsSyncStatusPush": {
"message": "正在拉取樣式 $total$ 中的 $loaded$",
"description": "",
"placeholders": {
"loaded": {
"content": "$1"
},
"total": {
"content": "$2"
}
}
},
"optionsSyncStatusSyncing": {
"message": "正在同步……",
"description": ""
},
"optionsSyncStatusConnecting": {
"message": "正在連線……",
"description": ""
},
"optionsSyncStatusConnected": {
"message": "已連線",
"description": ""
},
"optionsSyncStatusDisconnecting": {
"message": "正在斷線……",
"description": ""
},
"optionsSyncStatusDisconnected": {
"message": "已斷線",
"description": ""
},
"paginationCurrent": {
"message": "目前頁面",
"description": "Tooltip for the current page index in search results"
@ -1105,6 +1181,10 @@
"message": "不儲存而暫時套用變更。\n儲存樣式以永久變更。",
"description": "Tooltip for the checkbox in style editor to enable live preview while editing."
},
"reload": {
"message": "重新載入 Stylus 附加元件",
"description": "Context menu reload"
},
"replace": {
"message": "取代",
"description": "Label before the replace input field in the editor shown on Ctrl-H"
@ -1512,6 +1592,10 @@
"message": "Dropbox 匯出",
"description": ""
},
"syncDropboxDeprecated": {
"message": "Dropbox 匯入/匯出已被選項頁面中的更進階的樣式同步所取代。",
"description": ""
},
"retrieveDropboxSync": {
"message": "Dropbox 匯入",
"description": ""

View File

@ -1,7 +1,8 @@
/* global download prefs openURL FIREFOX CHROME VIVALDI
debounce URLS ignoreChromeError getTab
styleManager msg navigatorUtil iconUtil workerUtil contentScripts sync
colorScheme */
/* global download prefs openURL FIREFOX CHROME
URLS ignoreChromeError usercssHelper
styleManager msg navigatorUtil workerUtil contentScripts sync
findExistingTab createTab activateTab isTabReplaceable getActiveTab
tabManager colorScheme */
'use strict';
// eslint-disable-next-line no-var
@ -29,7 +30,11 @@ window.API_METHODS = Object.assign(window.API_METHODS || {}, {
removeExclusion: styleManager.removeExclusion,
getTabUrlPrefix() {
return this.sender.tab.url.match(/^([\w-]+:\/+[^/#]+)/)[1];
const {url} = this.sender.tab;
if (url.startsWith(URLS.ownOrigin)) {
return 'stylus';
}
return url.match(/^([\w-]+:\/+[^/#]+)/)[1];
},
download(msg) {
@ -43,23 +48,27 @@ window.API_METHODS = Object.assign(window.API_METHODS || {}, {
openEditor,
updateIconBadge(count) {
// TODO: remove once our manifest's minimum_chrome_version is 50+
// Chrome 49 doesn't report own extension pages in webNavigation apparently
// so we do a force update which doesn't use the cache.
if (CHROME && CHROME < 2661 && this.sender.tab.url.startsWith(URLS.ownOrigin)) {
updateIconBadgeForce(this.sender.tab.id, count);
} else {
updateIconBadge(this.sender.tab.id, count);
/* 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 */
openURL(opts) {
const {message} = opts;
return openURL(opts) // will pass the resolved value untouched when `message` is absent or falsy
.then(message && (tab => tab.status === 'complete' ? tab : onTabReady(tab)))
.then(message && (tab => msg.sendTab(tab.id, opts.message)));
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'));
}));
}
return true;
},
// exposed for stuff that requires followup sendMessage() like popup::openSettings
// that would fail otherwise if another extension forced the tab to open
// in the foreground thus auto-closing the popup (in Chrome)
openURL,
optionsCustomizeHotkeys() {
return browser.runtime.openOptionsPage()
.then(() => new Promise(resolve => setTimeout(resolve, 100)))
@ -72,7 +81,9 @@ window.API_METHODS = Object.assign(window.API_METHODS || {}, {
syncStop: sync.stop,
syncNow: sync.syncNow,
getSyncStatus: sync.getStatus,
syncLogin: sync.login
syncLogin: sync.login,
openManage
});
// eslint-disable-next-line no-var
@ -82,23 +93,23 @@ var browserCommands, contextMenus;
// register all listeners
msg.on(onRuntimeMessage);
// tell apply.js to refresh styles for non-committed navigation
navigatorUtil.onUrlChange(({tabId, frameId}, type) => {
if (type === 'committed') {
// styles would be updated when content script is injected.
return;
if (type !== 'committed') {
msg.sendTab(tabId, {method: 'urlChanged'}, {frameId})
.catch(msg.ignoreError);
}
});
tabManager.onUpdate(({tabId, url, oldUrl = ''}) => {
if (usercssHelper.testUrl(url) && !oldUrl.startsWith(URLS.installUsercss)) {
usercssHelper.testContents(tabId, url).then(data => {
if (data.code) usercssHelper.openInstallerPage(tabId, url, data);
});
}
msg.sendTab(tabId, {method: 'urlChanged'}, {frameId})
.catch(msg.ignoreError);
});
if (FIREFOX) {
// FF applies page CSP even to content scripts, https://bugzil.la/1267027
navigatorUtil.onCommitted(webNavUsercssInstallerFF, {
url: [
{pathSuffix: '.user.css'},
{pathSuffix: '.user.styl'},
]
});
// FF misses some about:blank iframes so we inject our content script explicitly
navigatorUtil.onDOMContentLoaded(webNavIframeHelperFF, {
url: [
@ -117,52 +128,15 @@ if (chrome.commands) {
chrome.commands.onCommand.addListener(command => browserCommands[command]());
}
const tabIcons = new Map();
chrome.tabs.onRemoved.addListener(tabId => tabIcons.delete(tabId));
chrome.tabs.onReplaced.addListener((added, removed) => tabIcons.delete(removed));
prefs.subscribe([
'disableAll',
'badgeDisabled',
'badgeNormal',
], () => debounce(refreshIconBadgeColor));
prefs.subscribe([
'show-badge'
], () => debounce(refreshIconBadgeText));
prefs.subscribe([
'disableAll',
'iconset',
], () => debounce(refreshAllIcons));
prefs.initializing.then(() => {
refreshIconBadgeColor();
refreshAllIconsBadgeText();
refreshAllIcons();
});
navigatorUtil.onUrlChange(({tabId, frameId, transitionQualifiers}, type) => {
if (type === 'committed' && !frameId) {
// it seems that the tab icon would be reset by navigation. We
// invalidate the cache here so it would be refreshed by `apply.js`.
tabIcons.delete(tabId);
// however, if the tab was swapped in by forward/backward buttons,
// `apply.js` doesn't notify the background to update the icon,
// so we have to refresh it manually.
if (transitionQualifiers.includes('forward_back')) {
msg.sendTab(tabId, {method: 'updateCount'}).catch(msg.ignoreError);
}
}
});
// *************************************************************************
chrome.runtime.onInstalled.addListener(({reason}) => {
// save install type: "admin", "development", "normal", "sideload" or "other"
// "normal" = addon installed from webstore
chrome.management.getSelf(info => {
localStorage.installType = info.installType;
if (reason === 'install' && info.installType === 'development' && chrome.contextMenus) {
createContextMenus(['reload']);
}
});
if (reason !== 'update') return;
@ -177,12 +151,12 @@ chrome.runtime.onInstalled.addListener(({reason}) => {
// *************************************************************************
// browser commands
browserCommands = {
openManage() {
openURL({url: 'manage.html'});
},
openManage,
openOptions: () => openManage({options: true}),
styleDisableAll(info) {
prefs.set('disableAll', info ? info.checked : !prefs.get('disableAll'));
},
reload: () => chrome.runtime.reload(),
};
// *************************************************************************
@ -200,6 +174,15 @@ contextMenus = {
title: 'openStylesManager',
click: browserCommands.openManage,
},
'open-options': {
title: 'openOptions',
click: browserCommands.openOptions,
},
'reload': {
presentIf: () => localStorage.installType === 'development',
title: 'reload',
click: browserCommands.reload,
},
'editor.contextDelete': {
presentIf: () => !FIREFOX && prefs.get('editor.contextDelete'),
title: 'editDeleteText',
@ -212,28 +195,28 @@ contextMenus = {
}
};
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;
item.title = chrome.i18n.getMessage(item.title);
if (!item.type && typeof prefs.defaults[id] === 'boolean') {
item.type = 'checkbox';
item.checked = prefs.get(id);
}
if (!item.contexts) {
item.contexts = ['browser_action'];
}
delete item.click;
chrome.contextMenus.create(item, ignoreChromeError);
function 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;
item.title = chrome.i18n.getMessage(item.title);
if (!item.type && typeof prefs.defaults[id] === 'boolean') {
item.type = 'checkbox';
item.checked = prefs.get(id);
}
if (!item.contexts) {
item.contexts = ['browser_action'];
}
delete item.click;
chrome.contextMenus.create(item, ignoreChromeError);
}
}
if (chrome.contextMenus) {
// 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())) :
@ -276,22 +259,6 @@ if (FIREFOX && browser.commands && browser.commands.update) {
msg.broadcastTab({method: 'backgroundReady'});
function webNavUsercssInstallerFF(data) {
const {tabId} = data;
Promise.all([
msg.sendTab(tabId, {method: 'ping'})
.catch(() => false),
// 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.openUsercssInstallPage({direct: true}, {tab});
}
});
}
function webNavIframeHelperFF({tabId, frameId}) {
if (!frameId) return;
msg.sendTab(tabId, {method: 'ping'}, {frameId})
@ -310,75 +277,6 @@ function webNavIframeHelperFF({tabId, frameId}) {
});
}
function updateIconBadge(tabId, count) {
let tabIcon = tabIcons.get(tabId);
if (!tabIcon) tabIcons.set(tabId, (tabIcon = {}));
if (tabIcon.count === count) {
return;
}
const oldCount = tabIcon.count;
tabIcon.count = count;
refreshIconBadgeText(tabId, tabIcon);
if (Boolean(oldCount) !== Boolean(count)) {
refreshIcon(tabId, tabIcon);
}
}
function updateIconBadgeForce(tabId, count) {
refreshIconBadgeText(tabId, {count});
refreshIcon(tabId, {count});
}
function refreshIconBadgeText(tabId, icon) {
iconUtil.setBadgeText({
text: prefs.get('show-badge') && icon.count ? String(icon.count) : '',
tabId
});
}
function refreshIcon(tabId, icon) {
const disableAll = prefs.get('disableAll');
const iconset = prefs.get('iconset') === 1 ? 'light/' : '';
const postfix = disableAll ? 'x' : !icon.count ? 'w' : '';
const iconType = iconset + postfix;
if (icon.iconType === iconType) {
return;
}
icon.iconType = iconset + postfix;
const sizes = FIREFOX || CHROME >= 2883 && !VIVALDI ? [16, 32] : [19, 38];
iconUtil.setIcon({
path: sizes.reduce(
(obj, size) => {
obj[size] = `/images/icon/${iconset}${size}${postfix}.png`;
return obj;
},
{}
),
tabId
});
}
function refreshIconBadgeColor() {
const color = prefs.get(prefs.get('disableAll') ? 'badgeDisabled' : 'badgeNormal');
iconUtil.setBadgeBackgroundColor({
color
});
}
function refreshAllIcons() {
for (const [tabId, icon] of tabIcons) {
refreshIcon(tabId, icon);
}
refreshIcon(null, {}); // default icon
}
function refreshAllIconsBadgeText() {
for (const [tabId, icon] of tabIcons) {
refreshIconBadgeText(tabId, icon);
}
}
function onRuntimeMessage(msg, sender) {
if (msg.method !== 'invokeAPI') {
return;
@ -391,15 +289,55 @@ function onRuntimeMessage(msg, sender) {
return fn.apply(context, msg.args);
}
// FIXME: popup.js also open editor but it doesn't use this API.
function openEditor({id}) {
let url = '/edit.html';
if (id) {
url += `?id=${id}`;
function openEditor(params) {
/* Open the editor. Activate if it is already opened
params: {
id?: Number,
domain?: String,
'url-prefix'?: String
}
if (chrome.windows && prefs.get('openEditInWindow')) {
chrome.windows.create(Object.assign({url}, prefs.get('windowPosition')));
} else {
openURL({url});
*/
const searchParams = new URLSearchParams();
for (const key in params) {
searchParams.set(key, params[key]);
}
const search = searchParams.toString();
return openURL({
url: 'edit.html' + (search && `?${search}`),
newWindow: prefs.get('openEditInWindow'),
windowPosition: prefs.get('windowPosition'),
currentWindow: null
});
}
function openManage({options = false, search} = {}) {
let url = chrome.runtime.getURL('manage.html');
if (search) {
url += `?search=${encodeURIComponent(search)}`;
}
if (options) {
url += '#stylus-options';
}
return findExistingTab({
url,
currentWindow: null,
ignoreHash: true,
ignoreSearch: true
})
.then(tab => {
if (tab) {
return Promise.all([
activateTab(tab),
tab.url !== url && msg.sendTab(tab.id, {method: 'pushState', url})
.catch(console.error)
]);
}
return getActiveTab().then(tab => {
if (isTabReplaceable(tab, url)) {
return activateTab(tab, {url});
}
return createTab({url});
});
});
}

View File

@ -1,4 +1,4 @@
/* global msg queryTabs ignoreChromeError */
/* global msg queryTabs ignoreChromeError URLS */
/* exported contentScripts */
'use strict';
@ -15,6 +15,8 @@ const contentScripts = (() => {
m === ALL_URLS ? m : wildcardAsRegExp(m)
));
}
const busyTabs = new Set();
let busyTabsTimer;
return {injectToTab, injectToAllTabs};
function injectToTab({url, tabId, frameId = null}) {
@ -53,10 +55,14 @@ const contentScripts = (() => {
}
function injectToAllTabs() {
return queryTabs().then(tabs => {
return queryTabs({}).then(tabs => {
for (const tab of tabs) {
// skip lazy-loaded aka unloaded tabs that seem to start loading on message in FF
if (tab.width) {
// skip unloaded/discarded/chrome tabs
if (!tab.width || tab.discarded || !URLS.supported(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.url,
tabId: tab.id
@ -65,4 +71,40 @@ const contentScripts = (() => {
}
});
}
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);
}
})();

View File

@ -0,0 +1,84 @@
/* global promisify */
/* exported createChromeStorageDB */
'use strict';
function createChromeStorageDB() {
const get = promisify(chrome.storage.local.get.bind(chrome.storage.local));
const set = promisify(chrome.storage.local.set.bind(chrome.storage.local));
const remove = promisify(chrome.storage.local.remove.bind(chrome.storage.local));
let INC;
const PREFIX = 'style-';
const METHODS = {
// FIXME: we don't use this method at all. Should we remove this?
get: id => get(PREFIX + id)
.then(result => result[PREFIX + id]),
put: obj => Promise.resolve()
.then(() => {
if (!obj.id) {
return prepareInc()
.then(() => {
// FIXME: should we clone the object?
obj.id = INC++;
});
}
})
.then(() => set({[PREFIX + obj.id]: obj}))
.then(() => obj.id),
putMany: items => prepareInc()
.then(() => {
for (const item of items) {
if (!item.id) {
item.id = INC++;
}
}
return set(items.reduce((obj, curr) => {
obj[PREFIX + curr.id] = curr;
return obj;
}, {}));
})
.then(() => items.map(i => i.id)),
delete: id => remove(PREFIX + id),
getAll: () => get(null)
.then(result => {
const output = [];
for (const key in result) {
if (key.startsWith(PREFIX) && Number(key.slice(PREFIX.length))) {
output.push(result[key]);
}
}
return output;
})
};
return {exec};
function exec(method, ...args) {
if (METHODS[method]) {
return METHODS[method](...args)
.then(result => {
if (method === 'putMany' && result.map) {
return result.map(r => ({target: {result: r}}));
}
return {target: {result}};
});
}
return Promise.reject(new Error(`unknown DB method ${method}`));
}
function prepareInc() {
if (INC) return Promise.resolve();
return get(null).then(result => {
INC = 1;
for (const key in result) {
if (key.startsWith(PREFIX)) {
const id = Number(key.slice(PREFIX.length));
if (id >= INC) {
INC = id + 1;
}
}
}
});
}
}

View File

@ -1,4 +1,4 @@
/* global chromeLocal ignoreChromeError workerUtil */
/* global chromeLocal ignoreChromeError workerUtil createChromeStorageDB */
/* exported db */
/*
Initialize a database. There are some problems using IndexedDB in Firefox:
@ -94,7 +94,7 @@ const db = (() => {
}
function useChromeStorage(err) {
exec = dbExecChromeStorage;
exec = createChromeStorageDB().exec;
chromeLocal.set({dbInChromeStorage: true}, ignoreChromeError);
if (err) {
chromeLocal.setValue('dbInChromeStorageReason', workerUtil.cloneError(err));
@ -153,75 +153,4 @@ const db = (() => {
return Promise.all(items.map(item => storeRequest(store, 'put', item)));
}
}
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 getMaxId().then(id => {
data.id = id + 1;
return dbExecChromeStorage('put', data);
});
}
return chromeLocal.setValue(STYLE_KEY_PREFIX + data.id, data)
.then(() => (chrome.runtime.lastError ? Promise.reject() : data.id));
case 'putMany': {
const newItems = data.filter(i => !i.id);
const doPut = () =>
chromeLocal.set(data.reduce((o, item) => {
o[STYLE_KEY_PREFIX + item.id] = item;
return o;
}, {}))
.then(() => data.map(d => ({target: {result: d.id}})));
if (newItems.length) {
return getMaxId().then(id => {
for (const item of newItems) {
item.id = ++id;
}
return doPut();
});
}
return doPut();
}
case 'delete':
return chromeLocal.remove(STYLE_KEY_PREFIX + data);
case 'getAll':
return getAllStyles()
.then(styles => ({target: {result: styles}}));
}
return Promise.reject();
function getAllStyles() {
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 styles;
});
}
function getMaxId() {
return getAllStyles().then(styles => {
let result = 0;
for (const style of styles) {
if (style.id > result) {
result = style.id;
}
}
return result;
});
}
}
})();

136
background/icon-manager.js Normal file
View File

@ -0,0 +1,136 @@
/* global prefs debounce iconUtil FIREFOX CHROME VIVALDI tabManager navigatorUtil API_METHODS */
/* exported iconManager */
'use strict';
const iconManager = (() => {
const ICON_SIZES = FIREFOX || CHROME >= 2883 && !VIVALDI ? [16, 32] : [19, 38];
const staleBadges = new Set();
prefs.subscribe([
'disableAll',
'badgeDisabled',
'badgeNormal',
], () => debounce(refreshIconBadgeColor));
prefs.subscribe([
'show-badge'
], () => debounce(refreshAllIconsBadgeText));
prefs.subscribe([
'disableAll',
'iconset',
], () => debounce(refreshAllIcons));
prefs.initializing.then(() => {
refreshIconBadgeColor();
refreshAllIconsBadgeText();
refreshAllIcons();
});
Object.assign(API_METHODS, {
/** @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;
tabManager.set(tabId, 'styleIds', frameId, value);
debounce(refreshStaleBadges, frameId && lazyBadge ? 250 : 0);
staleBadges.add(tabId);
if (!frameId) refreshIcon(tabId, true);
},
});
navigatorUtil.onCommitted(({tabId, frameId}) => {
if (!frameId) tabManager.set(tabId, 'styleIds', undefined);
});
chrome.runtime.onConnect.addListener(port => {
if (port.name === 'iframe') {
port.onDisconnect.addListener(onPortDisconnected);
}
});
function onPortDisconnected({sender}) {
if (tabManager.get(sender.tab.id, 'styleIds')) {
API_METHODS.updateIconBadge.call({sender}, [], {lazyBadge: true});
}
}
function refreshIconBadgeText(tabId) {
const text = prefs.get('show-badge') ? `${getStyleCount(tabId)}` : '';
iconUtil.setBadgeText({tabId, text});
}
function getIconName(hasStyles = false) {
const iconset = prefs.get('iconset') === 1 ? 'light/' : '';
const postfix = prefs.get('disableAll') ? 'x' : !hasStyles ? 'w' : '';
return `${iconset}$SIZE$${postfix}`;
}
function refreshIcon(tabId, force = false) {
const oldIcon = tabManager.get(tabId, 'icon');
const newIcon = getIconName(tabManager.get(tabId, 'styleIds', 0));
// (changing the icon only for the main page, frameId = 0)
if (!force && oldIcon === newIcon) {
return;
}
tabManager.set(tabId, 'icon', newIcon);
iconUtil.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 = tabManager.get(tabId, 'styleIds') || {};
Object.values(data).forEach(frameIds => frameIds.forEach(id => allIds.add(id)));
return allIds.size || '';
}
function refreshGlobalIcon() {
iconUtil.setIcon({
path: getIconPath(getIconName())
});
}
function refreshIconBadgeColor() {
const color = prefs.get(prefs.get('disableAll') ? 'badgeDisabled' : 'badgeNormal');
iconUtil.setBadgeBackgroundColor({
color
});
}
function refreshAllIcons() {
for (const tabId of tabManager.list()) {
refreshIcon(tabId);
}
refreshGlobalIcon();
}
function refreshAllIconsBadgeText() {
for (const tabId of tabManager.list()) {
refreshIconBadgeText(tabId);
}
}
function refreshStaleBadges() {
for (const tabId of staleBadges) {
refreshIconBadgeText(tabId);
}
staleBadges.clear();
}
})();

View File

@ -1,6 +1,6 @@
/* eslint no-eq-null: 0, eqeqeq: [2, "smart"] */
/* global createCache db calcStyleDigest db tryRegExp styleCodeEmpty
getStyleWithNoCode msg sync uuid colorScheme */
getStyleWithNoCode msg sync uuidv4 colorScheme */
/* exported styleManager */
'use strict';
@ -395,7 +395,7 @@ const styleManager = (() => {
delete style.id;
}
if (!style._id) {
style._id = uuid();
style._id = uuidv4();
}
style._rev = Date.now();
fixUsoMd5Issue(style);
@ -539,7 +539,7 @@ const styleManager = (() => {
function prepare() {
const ADD_MISSING_PROPS = {
name: style => `ID: ${style.id}`,
_id: () => uuid(),
_id: () => uuidv4(),
_rev: () => Date.now()
};

View File

@ -1,4 +1,4 @@
/* global API_METHODS styleManager CHROME prefs updateIconBadge */
/* global API_METHODS styleManager CHROME prefs */
'use strict';
API_METHODS.styleViaAPI = !CHROME && (() => {
@ -31,12 +31,13 @@ API_METHODS.styleViaAPI = !CHROME && (() => {
.then(maybeToggleObserver);
};
function updateCount(request, {tab, frameId}) {
function updateCount(request, sender) {
const {tab, frameId} = sender;
if (frameId) {
throw new Error('we do not count styles for frames');
}
const {frameStyles} = getCachedData(tab.id, frameId);
updateIconBadge(tab.id, Object.keys(frameStyles).length);
API_METHODS.updateIconBadge.call({sender}, Object.keys(frameStyles));
}
function styleApply({id = null, ignoreUrlCheck = false}, {tab, frameId, url}) {

54
background/tab-manager.js Normal file
View File

@ -0,0 +1,54 @@
/* global navigatorUtil */
/* exported tabManager */
'use strict';
const tabManager = (() => {
const listeners = [];
const cache = new Map();
chrome.tabs.onRemoved.addListener(tabId => cache.delete(tabId));
chrome.tabs.onReplaced.addListener((added, removed) => cache.delete(removed));
navigatorUtil.onUrlChange(({tabId, frameId, url}) => {
if (frameId) return;
const oldUrl = tabManager.get(tabId, 'url');
tabManager.set(tabId, 'url', url);
for (const fn of listeners) {
try {
fn({tabId, url, oldUrl});
} catch (err) {
console.error(err);
}
}
});
return {
onUpdate(fn) {
listeners.push(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;
}
},
list() {
return cache.keys();
},
};
})();

View File

@ -1,4 +1,4 @@
/* global chromeLocal promisify */
/* global chromeLocal promisify FIREFOX */
/* exported tokenManager */
'use strict';
@ -43,7 +43,9 @@ const tokenManager = (() => {
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',
redirect_uri: 'https://clngdbkpkpeebahjckkjfobafhncgmne.chromiumapp.org/',
redirect_uri: FIREFOX ?
'https://clngdbkpkpeebahjckkjfobafhncgmne.chromiumapp.org/' :
'https://' + location.hostname + '.chromiumapp.org/',
scopes: ['Files.ReadWrite.AppFolder', 'offline_access']
}
};

View File

@ -1,42 +1,66 @@
/* global API_METHODS usercss chromeLocal styleManager FIREFOX deepCopy openURL
download */
/* global API_METHODS usercss styleManager deepCopy openURL download URLS getTab */
/* exports usercssHelper */
'use strict';
(() => {
// eslint-disable-next-line no-unused-vars
const usercssHelper = (() => {
const installCodeCache = {};
const clearInstallCode = url => delete installCodeCache[url];
const isResponseText = r => /^text\/(css|plain)(;.*?)?$/i.test(r.headers.get('content-type'));
// in Firefox we have to use a content script to read file://
const fileLoader = !chrome.app && // not relying on navigator.ua which can be spoofed
(tabId => browser.tabs.executeScript(tabId, {file: '/content/install-hook-usercss.js'}).then(r => r[0]));
API_METHODS.installUsercss = installUsercss;
API_METHODS.editSaveUsercss = editSaveUsercss;
API_METHODS.configUsercssVars = configUsercssVars;
API_METHODS.buildUsercss = build;
API_METHODS.openUsercssInstallPage = install;
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;
API_METHODS.getUsercssInstallCode = 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;
};
return {
testUrl(url) {
return url.includes('.user.') &&
/^(https?|file|ftps?):/.test(url) &&
/\.user\.(css|styl)$/.test(url.split(/[#?]/, 1)[0]);
},
/** @return {Promise<{ code:string, inTab:boolean } | false>} */
testContents(tabId, url) {
const isFile = url.startsWith('file:');
const inTab = isFile && Boolean(fileLoader);
return Promise.resolve(isFile || fetch(url, {method: 'HEAD'}).then(isResponseText))
.then(ok => ok && (inTab ? fileLoader(tabId) : download(url)))
.then(code => /==userstyle==/i.test(code) && {code, inTab});
},
openInstallerPage(tabId, url, {code, inTab} = {}) {
const newUrl = `${URLS.installUsercss}?updateUrl=${encodeURIComponent(url)}`;
if (inTab) {
getTab(tabId).then(tab =>
openURL({
url: `${newUrl}&tabId=${tabId}`,
active: tab.active,
index: tab.index + 1,
openerTabId: tabId,
currentWindow: null,
}));
} else {
const timer = setTimeout(clearInstallCode, 10e3, url);
installCodeCache[url] = {code, timer};
chrome.tabs.update(tabId, {url: newUrl});
}
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) {
@ -156,33 +180,4 @@
}
});
}
function install({url, direct, downloaded, tab}, sender = this.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);
});
}
})();

View File

@ -1,48 +1,51 @@
/* eslint no-var: 0 */
/* global msg API prefs createStyleInjector */
/* exported APPLY */
'use strict';
// some weird bug in new Chrome: the content script gets injected multiple times
// define a constant so it throws when redefined
const APPLY = (() => {
const CHROME = chrome.app ? parseInt(navigator.userAgent.match(/Chrom\w+\/(?:\d+\.){2}(\d+)|$/)[1]) : NaN;
// Chrome reruns content script when documentElement is replaced.
// 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`.
// eslint-disable-next-line no-unused-expressions
self.INJECTED !== 1 && (() => {
self.INJECTED = 1;
let IS_TAB = !chrome.tabs || location.pathname !== '/popup.html';
const IS_FRAME = window !== parent;
const STYLE_VIA_API = !chrome.app && document instanceof XMLDocument;
const IS_OWN_PAGE = location.protocol.endsWith('-extension:');
const setStyleContent = createSetStyleContent();
const styleInjector = createStyleInjector({
compare: (a, b) => a.id - b.id,
setStyleContent,
onUpdate: onInjectorUpdate
});
const docRootObserver = createDocRootObserver({
onChange: () => {
if (styleInjector.outOfOrder()) {
styleInjector.sort();
return true;
}
}
});
const docRewriteObserver = createDocRewriteObserver({
onChange: () => {
docRootObserver.evade(styleInjector.sort);
}
onUpdate: onInjectorUpdate,
});
const initializing = init();
/** @type chrome.runtime.Port */
let port;
let lazyBadge = IS_FRAME;
// the popup needs a check as it's not a tab but can be opened in a tab manually for whatever reason
if (!IS_TAB) {
chrome.tabs.getCurrent(tab => {
IS_TAB = Boolean(tab);
if (tab && styleInjector.list.length) updateCount();
});
}
// save it now because chrome.runtime will be unavailable in the orphaned script
const orphanEventId = chrome.runtime.id;
let isOrphaned;
// firefox doesn't orphanize content scripts so the old elements stay
if (!chrome.app) styleInjector.clearOrphans();
msg.onTab(applyOnMessage);
if (!IS_OWN_PAGE) {
window.dispatchEvent(new CustomEvent(chrome.runtime.id, {
detail: pageObject({method: 'orphan'})
}));
window.addEventListener(chrome.runtime.id, orphanCheck, true);
if (!chrome.tabs) {
window.dispatchEvent(new CustomEvent(orphanEventId));
window.addEventListener(orphanEventId, orphanCheck, true);
}
let parentDomain;
prefs.subscribe(['disableAll'], (key, value) => doDisableAll(value));
if (window !== parent) {
if (IS_FRAME) {
prefs.subscribe(['exposeIframes'], updateExposeIframes);
}
@ -54,153 +57,25 @@ const APPLY = (() => {
media.addListener(() => API.updateSystemPreferDark().catch(console.error));
function onInjectorUpdate() {
if (!IS_OWN_PAGE && styleInjector.list.length) {
docRewriteObserver.start();
docRootObserver.start();
} else {
docRewriteObserver.stop();
docRootObserver.stop();
if (!isOrphaned) {
updateCount();
updateExposeIframes();
}
updateCount();
updateExposeIframes();
}
function init() {
if (STYLE_VIA_API) {
return API.styleViaAPI({method: 'styleApply'});
}
return API.getSectionsByUrl(getMatchUrl())
.then(result =>
applyStyles(result)
.then(() => {
// CSS transition bug workaround: since we insert styles asynchronously,
// the browsers, especially Firefox, may apply all transitions on page load
if (styleInjector.list.some(s => s.code.includes('transition'))) {
applyTransitionPatch();
}
})
);
}
function pageObject(target) {
// https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/Sharing_objects_with_page_scripts
const obj = new window.Object();
Object.assign(obj, target);
return obj;
}
function createSetStyleContent() {
// FF59+ bug workaround
// See https://github.com/openstyles/stylus/issues/461
// Since it's easy to spoof the browser version in pre-Quantum FF we're checking
// for getPreventDefault which got removed in FF59 https://bugzil.la/691151
const EVENT_NAME = chrome.runtime.id;
let ready;
return (el, content, disabled) =>
checkPageScript().then(ok => {
if (!ok) {
el.textContent = content;
// https://github.com/openstyles/stylus/issues/693
el.disabled = disabled;
} else {
const detail = pageObject({
method: 'setStyleContent',
id: el.id,
content,
disabled
});
window.dispatchEvent(new CustomEvent(EVENT_NAME, {detail}));
}
});
function checkPageScript() {
if (!ready) {
ready = CHROME || IS_OWN_PAGE || Event.prototype.getPreventDefault ?
Promise.resolve(false) : injectPageScript();
}
return ready;
}
function injectPageScript() {
const scriptContent = EVENT_NAME => {
document.currentScript.remove();
const available = checkStyleApplied();
if (available) {
window.addEventListener(EVENT_NAME, function handler(e) {
const {method, id, content, disabled} = e.detail;
if (method === 'setStyleContent') {
const el = document.getElementById(id);
if (!el) {
return;
}
el.textContent = content;
el.disabled = disabled;
} else if (method === 'orphan') {
window.removeEventListener(EVENT_NAME, handler);
}
}, true);
}
window.dispatchEvent(new CustomEvent(EVENT_NAME, {detail: {
method: 'init',
available
}}));
function checkStyleApplied() {
const style = document.createElement('style');
document.documentElement.appendChild(style);
const applied = Boolean(style.sheet);
style.remove();
return applied;
}
};
const code = `(${scriptContent})(${JSON.stringify(EVENT_NAME)})`;
// make sure it works in XML
const script = document.createElementNS('http://www.w3.org/1999/xhtml', 'script');
const {resolve, promise} = deferred();
// use inline script because using src is too slow
// https://github.com/openstyles/stylus/pull/766
script.text = code;
script.onerror = resolveFalse;
window.addEventListener('error', resolveFalse);
window.addEventListener(EVENT_NAME, handleInit);
(document.head || document.documentElement).appendChild(script);
// injection failed if handleInit is not called.
resolveFalse();
return promise.then(result => {
script.remove();
window.removeEventListener(EVENT_NAME, handleInit);
window.removeEventListener('error', resolveFalse);
return result;
});
function resolveFalse() {
resolve(false);
}
function handleInit(e) {
if (e.detail.method === 'init') {
resolve(e.detail.available);
}
}
}
}
function deferred() {
const o = {};
o.promise = new Promise((resolve, reject) => {
o.resolve = resolve;
o.reject = reject;
});
return o;
return STYLE_VIA_API ?
API.styleViaAPI({method: 'styleApply'}) :
API.getSectionsByUrl(getMatchUrl()).then(styleInjector.apply);
}
function getMatchUrl() {
var matchUrl = location.href;
let matchUrl = location.href;
if (!matchUrl.match(/^(http|file|chrome|ftp)/)) {
// dynamic about: and javascript: iframes don't have an URL yet
// so we'll try the parent frame which is guaranteed to have a real URL
try {
if (window !== parent) {
if (IS_FRAME) {
matchUrl = parent.location.href;
}
} catch (e) {}
@ -209,18 +84,20 @@ const APPLY = (() => {
}
function applyOnMessage(request) {
if (request.method === 'ping') {
return true;
}
if (STYLE_VIA_API) {
if (request.method === 'urlChanged') {
request.method = 'styleReplaceAll';
}
API.styleViaAPI(request);
return;
if (/^(style|updateCount)/.test(request.method)) {
API.styleViaAPI(request);
return;
}
}
switch (request.method) {
case 'ping':
return true;
case 'styleDeleted':
styleInjector.remove(request.style.id);
break;
@ -232,7 +109,7 @@ const APPLY = (() => {
if (!sections[request.style.id]) {
styleInjector.remove(request.style.id);
} else {
applyStyles(sections);
styleInjector.apply(sections);
}
});
} else {
@ -243,13 +120,13 @@ const APPLY = (() => {
case 'styleAdded':
if (request.style.enabled) {
API.getSectionsByUrl(getMatchUrl(), request.style.id)
.then(applyStyles);
.then(styleInjector.apply);
}
break;
case 'urlChanged':
API.getSectionsByUrl(getMatchUrl())
.then(replaceAll);
.then(styleInjector.replace);
break;
case 'backgroundReady':
@ -277,13 +154,12 @@ const APPLY = (() => {
}
function fetchParentDomain() {
if (parentDomain) {
return Promise.resolve();
}
return API.getTabUrlPrefix()
.then(newDomain => {
parentDomain = newDomain;
});
return parentDomain ?
Promise.resolve() :
API.getTabUrlPrefix()
.then(newDomain => {
parentDomain = newDomain;
});
}
function updateExposeIframes() {
@ -297,176 +173,32 @@ const APPLY = (() => {
}
function updateCount() {
if (window !== parent) {
// we don't care about iframes
return;
}
if (/^\w+?-extension:\/\/.+(popup|options)\.html$/.test(location.href)) {
// popup and the option page are not tabs
return;
}
if (STYLE_VIA_API) {
API.styleViaAPI({method: 'updateCount'}).catch(msg.ignoreError);
return;
}
// we have to send the tabId so we can't use `sendBg` that is used by `API`
msg.send({
method: 'invokeAPI',
name: 'updateIconBadge',
args: [styleInjector.list.length]
}).catch(console.error);
}
function rootReady() {
if (document.documentElement) {
return Promise.resolve();
}
return new Promise(resolve => {
new MutationObserver((mutations, observer) => {
if (document.documentElement) {
observer.disconnect();
resolve();
}
}).observe(document, {childList: true});
});
}
function applyStyles(sections) {
const styles = Object.values(sections);
if (!styles.length) {
return Promise.resolve();
}
return rootReady().then(() =>
docRootObserver.evade(() =>
styleInjector.addMany(
styles.map(s => ({id: s.id, code: s.code.join('')}))
)
)
);
}
function replaceAll(newStyles) {
styleInjector.replaceAll(
Object.values(newStyles)
.map(s => ({id: s.id, code: s.code.join('')}))
);
}
function applyTransitionPatch() {
// CSS transition bug workaround: since we insert styles asynchronously,
// the browsers, especially Firefox, may apply all transitions on page load
const el = styleInjector.createStyle('transition-patch');
// FIXME: this will trigger docRootObserver and cause a resort. We should
// move this function into style-injector.
document.documentElement.appendChild(el);
setStyleContent(el, `
:root:not(#\\0):not(#\\0) * {
transition: none !important;
if (!IS_TAB) return;
if (IS_FRAME) {
if (!port && styleInjector.list.length) {
port = chrome.runtime.connect({name: 'iframe'});
} else if (port && !styleInjector.list.length) {
port.disconnect();
}
`)
.then(afterPaint)
.then(() => {
el.remove();
});
if (lazyBadge && performance.now() > 1000) lazyBadge = false;
}
(STYLE_VIA_API ?
API.styleViaAPI({method: 'updateCount'}) :
API.updateIconBadge(styleInjector.list.map(style => style.id), {lazyBadge})
).catch(msg.ignoreError);
}
function afterPaint() {
return new Promise(resolve => {
requestAnimationFrame(() => {
setTimeout(resolve);
});
});
}
function orphanCheck(e) {
if (e && e.detail.method !== 'orphan') {
return;
}
if (chrome.i18n && chrome.i18n.getUILanguage()) {
return true;
}
function orphanCheck() {
try {
if (chrome.i18n.getUILanguage()) return;
} catch (e) {}
// In Chrome content script is orphaned on an extension update/reload
// so we need to detach event listeners
window.removeEventListener(orphanEventId, orphanCheck, true);
isOrphaned = true;
styleInjector.clear();
window.removeEventListener(chrome.runtime.id, orphanCheck, true);
try {
msg.off(applyOnMessage);
} catch (e) {}
}
function createDocRewriteObserver({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 createDocRootObserver({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 {start, stop, evade};
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 evade(fn) {
if (!observing) {
return fn();
}
stop();
const r = fn();
start();
return r;
}
}
})();

View File

@ -1,123 +1,22 @@
/* global API */
'use strict';
(() => {
// some weird bug in new Chrome: the content script gets injected multiple times
if (typeof window.initUsercssInstall === 'function') return;
if (!/text\/(css|plain)/.test(document.contentType) ||
!/==userstyle==/i.test(document.body.textContent)) {
return;
}
window.initUsercssInstall = () => {};
orphanCheck();
const DELAY = 500;
const url = location.href;
let sourceCode, port, timer;
chrome.runtime.onConnect.addListener(onConnected);
API.openUsercssInstallPage({url})
.catch(err => alert(err));
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) {
// XHR throws in Chrome 49
// FIXME: choose a correct version
// https://github.com/openstyles/stylus/issues/560
if (getChromeVersion() <= 49) {
return fetch(url)
// preventing reregistration if reinjected by tabs.executeScript for whatever reason, just in case
if (typeof self.oldCode !== 'string') {
self.oldCode = (document.querySelector('body > pre') || document.body).textContent;
chrome.runtime.onConnect.addListener(port => {
if (port.name !== 'downloadSelf') return;
port.onMessage.addListener(({id, timer}) => {
fetch(location.href, {mode: 'same-origin'})
.then(r => r.text())
.catch(() => fetchTextXHR(url));
}
return fetchTextXHR(url);
}
function fetchTextXHR(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();
.then(code => ({id, code: timer && code === self.oldCode ? null : code}))
.catch(error => ({id, error: error.message || `${error}`}))
.then(msg => {
port.postMessage(msg);
if (msg.code != null) self.oldCode = msg.code;
});
});
}
});
}
function getChromeVersion() {
const match = navigator.userAgent.match(/chrome\/(\d+)/i);
return match ? Number(match[1]) : undefined;
}
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);
}
})();
// passing the result to tabs.executeScript
self.oldCode; // eslint-disable-line no-unused-expressions

View File

@ -1,89 +1,185 @@
/* exported createStyleInjector */
'use strict';
function createStyleInjector({compare, setStyleContent, onUpdate}) {
const CHROME = chrome.app ? parseInt(navigator.userAgent.match(/Chrom\w+\/(?:\d+\.){2}(\d+)|$/)[1]) : NaN;
self.createStyleInjector = self.INJECTED === 1 ? self.createStyleInjector : ({
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 IS_OWN_PAGE = Boolean(chrome.tabs);
// detect Chrome 65 via a feature it added since browser version can be spoofed
const isChromePre65 = chrome.app && typeof Worklet !== 'function';
const docRewriteObserver = RewriteObserver(_sort);
const docRootObserver = RootObserver(_sortIfNeeded);
const list = [];
const table = new Map();
let enabled = true;
let isEnabled = true;
let isTransitionPatched;
// will store the original method refs because the page can override them
let creationDoc, createElement, createElementNS;
return {
// manipulation
add,
addMany,
remove,
update,
apply,
clear,
replaceAll,
// method
clearOrphans,
remove,
replace,
toggle,
sort,
// state
outOfOrder,
list,
// static util
createStyle
};
function outOfOrder() {
if (!list.length) {
return false;
function apply(styleMap) {
const styles = _styleMapToArray(styleMap);
return !styles.length ?
Promise.resolve([]) :
docRootObserver.evade(() => {
if (!isTransitionPatched) _applyTransitionPatch(styles);
const els = styles.map(_apply);
_emitUpdate();
return els;
});
}
function clear() {
for (const style of list) {
style.el.remove();
}
let el = list[0].el;
if (el.parentNode !== document.documentElement) {
return true;
}
let i = 0;
while (el) {
if (i < list.length && el === list[i].el) {
i++;
} else if (ORDERED_TAGS.has(el.localName)) {
return true;
list.length = 0;
table.clear();
_emitUpdate();
}
function 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();
}
el = el.nextSibling;
}
// some styles are not injected to the document
return i < list.length;
}
function addMany(styles) {
const pending = Promise.all(styles.map(_add));
emitUpdate();
return pending;
}
function add(style) {
const pending = _add(style);
emitUpdate();
return pending;
}
function _add(style) {
if (table.has(style.id)) {
return update(style);
}
style.el = createStyle(style.id);
const pending = setStyleContent(style.el, style.code, !enabled);
table.set(style.id, style);
const nextIndex = list.findIndex(i => compare(i, style) > 0);
if (nextIndex < 0) {
document.documentElement.appendChild(style.el);
list.push(style);
} else {
document.documentElement.insertBefore(style.el, list[nextIndex].el);
list.splice(nextIndex, 0, style);
}
return pending;
}
function remove(id) {
_remove(id);
emitUpdate();
_emitUpdate();
}
function 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(_apply);
removed.forEach(_remove);
_emitUpdate();
}
function toggle(_enabled) {
if (isEnabled === _enabled) return;
isEnabled = _enabled;
for (const style of list) {
style.el.disabled = !isEnabled;
}
}
function _add(style) {
const el = style.el = _createStyle(style.id, style.code);
table.set(style.id, style);
const nextIndex = list.findIndex(i => compare(i, style) > 0);
if (nextIndex < 0) {
document.documentElement.appendChild(el);
list.push(style);
} else {
document.documentElement.insertBefore(el, list[nextIndex].el);
list.splice(nextIndex, 0, style);
}
// moving an element resets its 'disabled' state
el.disabled = !isEnabled;
return el;
}
function _apply(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(PATCH_ID, `
: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(id, code = '') {
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');
el.textContent = code;
return el;
}
function _emitUpdate() {
if (!IS_OWN_PAGE && list.length) {
docRewriteObserver.start();
docRootObserver.start();
} else {
docRewriteObserver.stop();
docRootObserver.stop();
}
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) {
@ -94,91 +190,149 @@ function createStyleInjector({compare, setStyleContent, onUpdate}) {
style.el.remove();
}
function update({id, code}) {
function _sort() {
docRootObserver.evade(() => {
list.sort(compare);
for (const style of list) {
// moving an element resets its 'disabled' state
document.documentElement.appendChild(style.el);
style.el.disabled = !isEnabled;
}
});
}
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) {
return Object.values(styleMap).map(s => ({
id: s.id,
code: s.code.join(''),
}));
}
function _update({id, code}) {
const style = table.get(id);
if (style.code === code) return;
style.code = code;
// workaround for Chrome devtools bug fixed in v65
// https://github.com/openstyles/stylus/commit/0fa391732ba8e35fa68f326a560fc04c04b8608b
let oldEl;
if (CHROME < 3321) {
oldEl = style.el;
oldEl.id = '';
style.el = createStyle(id);
if (isChromePre65) {
const oldEl = style.el;
style.el = _createStyle(id, code);
oldEl.parentNode.insertBefore(style.el, oldEl.nextSibling);
style.el.disabled = !enabled;
}
return setStyleContent(style.el, code, !enabled)
.then(() => oldEl && oldEl.remove());
}
function createStyle(id) {
let 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');
oldEl.remove();
} else {
// HTML document style; also works on HTML-embedded SVG
el = document.createElement('style');
style.el.textContent = code;
}
el.id = `${PREFIX}${id}`;
el.type = 'text/css';
// SVG className is not a string, but an instance of SVGAnimatedString
el.classList.add('stylus');
return el;
// https://github.com/openstyles/stylus/issues/693
style.el.disabled = !isEnabled;
}
function clear() {
for (const style of list) {
style.el.remove();
}
list.length = 0;
table.clear();
emitUpdate();
}
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 toggle(_enabled) {
if (enabled === _enabled) return;
enabled = _enabled;
for (const style of list) {
style.el.disabled = !enabled;
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 sort() {
list.sort(compare);
for (const style of list) {
// FIXME: do we need this?
// const copy = document.importNode(el, true);
// el.textContent += ' '; // invalidate CSSOM cache
document.documentElement.appendChild(style.el);
// moving an element resets its 'disabled' state
style.el.disabled = !enabled;
function stop() {
if (!observing) return;
clearTimeout(timer);
observer.disconnect();
observing = false;
}
}
function emitUpdate() {
if (onUpdate) {
onUpdate();
}
}
function replaceAll(styles) {
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);
function _check() {
if (root !== document.documentElement) {
root = document.documentElement;
onChange();
}
}
// FIXME: is it possible that `docRootObserver` breaks the process?
return Promise.all(styles.map(_add))
.then(() => {
removed.forEach(_remove);
emitUpdate();
});
}
}
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});
}
}
};

View File

@ -8,6 +8,8 @@ const CODEMIRROR_THEMES = [
'abcdef',
'ambiance',
'ambiance-mobile',
'ayu-dark',
'ayu-mirage',
'base16-dark',
'base16-light',
'bespin',
@ -30,10 +32,14 @@ const CODEMIRROR_THEMES = [
'liquibyte',
'lucario',
'material',
'material-darker',
'material-ocean',
'material-palenight',
'mbo',
'mdn-like',
'midnight',
'monokai',
'moxer',
'neat',
'neo',
'night',

View File

@ -62,6 +62,7 @@ label {
}
#sections {
padding-left: 280px;
min-height: 0;
}
#sections h2 {
margin-top: 1rem;
@ -981,7 +982,6 @@ body.linter-disabled .hidden-unless-compact {
}
#sections {
height: unset !important;
min-height: 0;
padding-left: 0;
display: flex;
flex-direction: column;

View File

@ -823,11 +823,13 @@ onDOMready().then(() => {
if (numApplies === undefined && state.searchInApplies && state.numApplies < 0) {
numApplies = 0;
const elements = state.find ? document.getElementsByClassName(APPLIES_VALUE_CLASS) : [];
const {rx} = state;
for (const el of elements) {
const value = el.value;
if (state.rx) {
state.rx.lastIndex = 0;
while (state.rx.exec(value)) numApplies++;
if (rx) {
rx.lastIndex = 0;
// preventing an infinite loop if matched an empty string and didn't advance
for (let m; (m = rx.exec(value)) && ++numApplies && rx.lastIndex > m.index;) { /* NOP */ }
} else {
let i = -1;
while ((i = value.indexOf(state.find, i + 1)) >= 0) numApplies++;

View File

@ -66,7 +66,7 @@ const regExpTester = (() => {
return rxData;
});
const getMatchInfo = m => m && {text: m[0], pos: m.index};
queryTabs().then(tabs => {
queryTabs({}).then(tabs => {
const supported = tabs.map(tab => tab.url)
.filter(url => URLS.supported(url));
const unique = [...new Set(supported).values()];

View File

@ -58,19 +58,17 @@
<div class="actions">
<h2 class="installed" i18n-text="installButtonInstalled"></h2>
<button class="install" i18n-text="installButton"></button>
<p id="live-reload-install-hint" i18n-text="liveReloadInstallHint" class="hidden"></p>
<p id="live-reload-install-hint" hidden></p>
<label class="set-update-url">
<input type="checkbox">
<svg class="svg-icon checked"><use xlink:href="#svg-icon-checked"/></svg>
<span class="available-message" i18n-text="installUpdateFromLabel"></span>
<span class="unavailable-message" i18n-text="installUpdateUnavailable"></span>
<span i18n-text="installUpdateFromLabel"></span>
<p></p>
</label>
<label class="live-reload">
<input type="checkbox">
<svg class="svg-icon checked"><use xlink:href="#svg-icon-checked"/></svg>
<span class="available-message" i18n-text="liveReloadLabel"></span>
<span class="unavailable-message" i18n-text="liveReloadUnavailable"></span>
<span i18n-text="liveReloadLabel"></span>
</label>
<label class="set-prefer-scheme">
<span i18n-text="installPreferSchemeLabel"></span>

View File

@ -246,18 +246,6 @@ h2.installed.active {
min-width: 0;
}
.unavailable-message,
.unavailable .available-message,
.unavailable .svg-icon,
.live-reload.unavailable input,
.set-update-url.unavailable input {
display: none;
}
.unavailable .unavailable-message {
display: block;
}
.set-update-url {
flex-wrap: wrap;
}
@ -321,7 +309,7 @@ li {
}
/* FIXME: why do we want to give all labels a padding? */
label:not(.unavailable) {
label {
padding-left: 16px;
position: relative;
}

View File

@ -1,46 +1,18 @@
/* global CodeMirror semverCompare closeCurrentTab messageBox download
$ $$ $create $createLink t prefs API getTab */
$ $$ $create $createLink t prefs API */
'use strict';
(() => {
const DUMMY_URL = 'foo:';
// TODO: remove .replace(/^\?/, '') when minimum_chrome_version >= 52 (https://crbug.com/601425)
const params = new URLSearchParams(location.search.replace(/^\?/, ''));
let liveReload = false;
const tabId = params.has('tabId') ? Number(params.get('tabId')) : -1;
const initialUrl = params.get('updateUrl');
let installed = null;
let installedDup = null;
const tabId = Number(params.get('tabId'));
let tabUrl;
let port;
if (params.has('direct')) {
setUnavailable('.live-reload');
getCodeDirectly();
} else {
port = chrome.tabs.connect(tabId);
port.postMessage({method: 'getSourceCode'});
port.onMessage.addListener(msg => {
switch (msg.method) {
case 'getSourceCodeResponse':
if (msg.error) {
messageBox.alert(msg.error, 'pre');
} else {
initSourceCode(msg.sourceCode);
}
break;
case 'sourceCodeChanged':
if (msg.error) {
messageBox.alert(msg.error, 'pre');
} else {
liveReloadUpdate(msg.sourceCode);
}
break;
}
});
port.onDisconnect.addListener(onPortDisconnected);
}
const liveReload = initLiveReload();
liveReload.ready.then(initSourceCode, error => messageBox.alert(error, 'pre'));
const theme = prefs.get('editor.theme');
const cm = CodeMirror($('.main'), {
@ -54,8 +26,13 @@
href: `vendor/codemirror/theme/${theme}.css`
}));
}
let liveReloadPending = Promise.resolve();
window.addEventListener('resize', adjustCodeHeight);
// "History back" in Firefox (for now) restores the old DOM including the messagebox,
// which stays after installing since we don't want to wait for the fadeout animation before resolving.
document.addEventListener('visibilitychange', () => {
if (messageBox.element) messageBox.element.remove();
if (installed) liveReload.onToggled();
});
setTimeout(() => {
if (!installed) {
@ -64,35 +41,6 @@
}
}, 200);
getTab(tabId).then(tab => (tabUrl = tab.url));
chrome.tabs.onUpdated.addListener((id, {url}) => {
if (id === tabId && url && url !== tabUrl) {
closeCurrentTab();
}
});
// close the tab in case the port didn't report onDisconnect
chrome.tabs.onRemoved.addListener(id => {
if (id === tabId) {
closeCurrentTab();
}
});
function liveReloadUpdate(sourceCode) {
liveReloadPending = liveReloadPending.then(() => {
const scrollInfo = cm.getScrollInfo();
const cursor = cm.getCursor();
cm.setValue(sourceCode);
cm.setCursor(cursor);
cm.scrollTo(scrollInfo.left, scrollInfo.top);
API.installUsercss({
id: (installed || installedDup).id,
sourceCode
}).then(style => {
updateMeta(style);
}).catch(showError);
});
}
function updateMeta(style, dup = installedDup) {
installedDup = dup;
@ -209,7 +157,7 @@
$$.remove('.warning');
$('button.install').disabled = true;
$('button.install').classList.add('installed');
$('#live-reload-install-hint').classList.toggle('hidden', !liveReload);
$('#live-reload-install-hint').classList.toggle('hidden', !liveReload.enabled);
$('h2.installed').classList.add('active');
$('.set-update-url input[type=checkbox]').disabled = true;
$('.set-update-url').title = style.updateUrl ?
@ -217,16 +165,18 @@
updateMeta(style);
if (!liveReload && !prefs.get('openEditInWindow')) {
chrome.tabs.update({url: '/edit.html?id=' + style.id});
if (!liveReload.enabled && !prefs.get('openEditInWindow')) {
location.href = '/edit.html?id=' + style.id;
} else {
API.openEditor({id: style.id});
if (!liveReload) {
closeCurrentTab();
if (!liveReload.enabled) {
if (tabId < 0 && history.length > 1) {
history.back();
} else {
closeCurrentTab();
}
}
}
window.dispatchEvent(new CustomEvent('installed'));
}
function initSourceCode(sourceCode) {
@ -312,17 +262,11 @@
// set updateUrl
const checker = $('.set-update-url input[type=checkbox]');
// only use the installation URL if not specified in usercss
const installationUrl = (params.get('updateUrl') || '').replace(/^blob.+/, '');
const updateUrl = new URL(style.updateUrl || installationUrl || DUMMY_URL);
const updateUrl = new URL(style.updateUrl || initialUrl);
if (dup && dup.updateUrl === updateUrl.href) {
checker.checked = true;
// there is no way to "unset" updateUrl, you can only overwrite it.
checker.disabled = true;
} else if (updateUrl.href === DUMMY_URL) {
// drag'n'dropped on the manage page and the style doesn't have @updateURL
setUnavailable('.set-update-url');
return;
} else if (updateUrl.protocol !== 'file:') {
checker.checked = true;
style.updateUrl = updateUrl.href;
@ -340,40 +284,13 @@
};
preferScheme.onchange();
if (!port) {
return;
}
// live reload
const setLiveReload = $('.live-reload input[type=checkbox]');
if (!installationUrl || !installationUrl.startsWith('file:')) {
setLiveReload.parentNode.remove();
if (initialUrl.startsWith('file:')) {
$('.live-reload input').onchange = liveReload.onToggled;
} else {
setLiveReload.addEventListener('change', () => {
liveReload = setLiveReload.checked;
if (installed || installedDup) {
const method = 'liveReload' + (liveReload ? 'Start' : 'Stop');
port.postMessage({method});
$('.install').disabled = liveReload;
$('#live-reload-install-hint').classList.toggle('hidden', !liveReload);
}
});
window.addEventListener('installed', () => {
if (liveReload) {
port.postMessage({method: 'liveReloadStart'});
}
});
$('.live-reload').remove();
}
}
function setUnavailable(label) {
const el = $(label);
el.classList.add('unavailable');
const input = $('input', el);
input.disabled = true;
input.checked = false;
}
function getAppliesTo(style) {
function *_gen() {
for (const section of style.sections) {
@ -402,47 +319,105 @@
}
}
function getCodeDirectly() {
// FF applies page CSP even to content scripts, https://bugzil.la/1267027
// To circumvent that, the bg process downloads the code directly
const key = 'tempUsercssCode' + tabId;
chrome.storage.local.get(key, data => {
const code = data && data[key];
// bg already downloaded the code
if (typeof code === 'string') {
initSourceCode(code);
chrome.storage.local.remove(key);
return;
}
// bg still downloads the code
if (code && code.loading) {
const waitForCodeInStorage = (changes, area) => {
if (area === 'local' && key in changes) {
initSourceCode(changes[key].newValue);
chrome.storage.onChanged.removeListener(waitForCodeInStorage);
chrome.storage.local.remove(key);
function initLiveReload() {
const DELAY = 500;
let isEnabled = false;
let timer = 0;
/** @type function(?options):Promise<string|null> */
let getData = null;
/** @type Promise */
let sequence = null;
if (tabId < 0) {
getData = DirectDownloader();
sequence = API.getUsercssInstallCode(initialUrl).catch(getData);
} else {
getData = PortDownloader();
sequence = getData({timer: false});
}
return {
get enabled() {
return isEnabled;
},
ready: sequence,
onToggled(e) {
if (e) isEnabled = e.target.checked;
if (installed || installedDup) {
(isEnabled ? start : stop)();
$('.install').disabled = isEnabled;
Object.assign($('#live-reload-install-hint'), {
hidden: !isEnabled,
textContent: t(`liveReloadInstallHint${tabId >= 0 ? 'FF' : ''}`),
});
}
},
};
function check() {
getData()
.then(update, logError)
.then(() => {
timer = 0;
start();
});
}
function logError(error) {
console.warn(t('liveReloadError', error));
}
function start() {
timer = timer || setTimeout(check, DELAY);
}
function stop() {
clearTimeout(timer);
timer = 0;
}
function update(code) {
if (code == null) return;
sequence = sequence.catch(console.error).then(() => {
const {id} = installed || installedDup;
const scrollInfo = cm.getScrollInfo();
const cursor = cm.getCursor();
cm.setValue(code);
cm.setCursor(cursor);
cm.scrollTo(scrollInfo.left, scrollInfo.top);
return API.installUsercss({id, sourceCode: code})
.then(updateMeta)
.catch(showError);
});
}
function DirectDownloader() {
let oldCode = null;
const passChangedCode = code => {
const isSame = code === oldCode;
oldCode = code;
return isSame ? null : code;
};
return () => download(initialUrl).then(passChangedCode);
}
function PortDownloader() {
const resolvers = new Map();
const port = chrome.tabs.connect(tabId, {name: 'downloadSelf'});
port.onMessage.addListener(({id, code, error}) => {
const r = resolvers.get(id);
resolvers.delete(id);
if (error) {
r.reject(error);
} else {
r.resolve(code);
}
});
port.onDisconnect.addListener(() => {
chrome.tabs.get(tabId, tab => {
if (chrome.runtime.lastError) {
closeCurrentTab();
} else if (tab.url === initialUrl) {
location.reload();
}
};
chrome.storage.onChanged.addListener(waitForCodeInStorage);
return;
}
// on the off-chance dbExecChromeStorage.getAll ran right after bg download was saved
download(params.get('updateUrl'))
.then(initSourceCode)
.catch(err => messageBox.alert(t('styleInstallFailed', String(err)), 'pre'));
});
}
function onPortDisconnected() {
chrome.tabs.get(tabId, tab => {
if (chrome.runtime.lastError) {
closeCurrentTab();
} else if (tab.url === tabUrl) {
location.reload();
}
});
});
});
return ({timer = true} = {}) => new Promise((resolve, reject) => {
const id = performance.now();
resolvers.set(id, {resolve, reject});
port.postMessage({id, timer});
});
}
}
})();

View File

@ -1,6 +1,7 @@
/* exported getActiveTab onTabReady stringAsRegExp getTabRealURL openURL
/* exported getTab getActiveTab onTabReady stringAsRegExp openURL ignoreChromeError
getStyleWithNoCode tryRegExp sessionStorageHash download deepEqual
closeCurrentTab capitalize */
closeCurrentTab capitalize CHROME_HAS_BORDER_BUG */
/* global promisify */
'use strict';
const CHROME = Boolean(chrome.app) && parseInt(navigator.userAgent.match(/Chrom\w+\/(?:\d+\.){2}(\d+)|$/)[1]);
@ -28,6 +29,7 @@ if (!CHROME && !chrome.browserAction.openPopup) {
const URLS = {
ownOrigin: chrome.runtime.getURL(''),
// FIXME delete?
optionsUI: [
chrome.runtime.getURL('options.html'),
'chrome://extensions/?options=' + chrome.runtime.id,
@ -37,6 +39,8 @@ const URLS = {
OPERA ? 'opera://settings/configureCommands'
: 'chrome://extensions/configureCommands',
installUsercss: chrome.runtime.getURL('install-usercss.html'),
// CWS cannot be scripted in chromium, see ChromeExtensionsClient::IsScriptableURL
// https://cs.chromium.org/chromium/src/chrome/common/extensions/chrome_extensions_client.cc
browserWebStore:
@ -91,12 +95,17 @@ if (IS_BG) {
// Object.defineProperty(window, 'localStorage', {value: {}});
// Object.defineProperty(window, 'sessionStorage', {value: {}});
function queryTabs(options = {}) {
return new Promise(resolve =>
chrome.tabs.query(options, tabs =>
resolve(tabs)));
}
const createTab = promisify(chrome.tabs.create.bind(chrome.tabs));
const queryTabs = promisify(chrome.tabs.query.bind(chrome.tabs));
const updateTab = promisify(chrome.tabs.update.bind(chrome.tabs));
const moveTabs = promisify(chrome.tabs.move.bind(chrome.tabs));
// Android doesn't have chrome.windows
const updateWindow = chrome.windows && promisify(chrome.windows.update.bind(chrome.windows));
const createWindow = chrome.windows && promisify(chrome.windows.create.bind(chrome.windows));
// FF57+ supports openerTabId, but not in Android
// (detecting FF57 by the feature it added, not navigator.ua which may be spoofed in about:config)
const openerTabIdSupported = (!FIREFOX || window.AbortController) && chrome.windows != null;
function getTab(id) {
return new Promise(resolve =>
@ -116,167 +125,120 @@ function getActiveTab() {
.then(tabs => tabs[0]);
}
function getTabRealURL(tab) {
return new Promise(resolve => {
if (tab.url !== 'chrome://newtab/' || URLS.chromeProtectsNTP) {
resolve(tab.url);
} else {
chrome.webNavigation.getFrame({tabId: tab.id, frameId: 0, processId: -1}, frame => {
resolve(frame && frame.url || '');
});
}
});
function urlToMatchPattern(url, ignoreSearch) {
// https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/Match_patterns
if (!/^(http|https|ws|wss|ftp|data|file)$/.test(url.protocol)) {
return undefined;
}
if (ignoreSearch) {
return [
`${url.protocol}//${url.hostname}/${url.pathname}`,
`${url.protocol}//${url.hostname}/${url.pathname}?*`
];
}
// FIXME: is %2f allowed in pathname and search?
return `${url.protocol}//${url.hostname}/${url.pathname}${url.search}`;
}
/**
* Resolves when the [just created] tab is ready for communication.
* @param {Number|Tab} tabOrId
* @returns {Promise<?Tab>}
*/
function onTabReady(tabOrId) {
let tabId, tab;
if (Number.isInteger(tabOrId)) {
tabId = tabOrId;
} else {
tab = tabOrId;
tabId = tab && tab.id;
}
if (!tab) {
return getTab(tabId).then(onTabReady);
}
if (tab.status === 'complete') {
if (!FIREFOX || tab.url !== 'about:blank') {
return Promise.resolve(tab);
} else {
return new Promise(resolve => {
chrome.webNavigation.getFrame({tabId, frameId: 0}, frame => {
ignoreChromeError();
if (frame) {
onTabReady(tab).then(resolve);
} else {
setTimeout(() => onTabReady(tabId).then(resolve));
}
});
});
}
}
return new Promise((resolve, reject) => {
chrome.webNavigation.onCommitted.addListener(onCommitted);
chrome.webNavigation.onErrorOccurred.addListener(onErrorOccurred);
chrome.tabs.onRemoved.addListener(onTabRemoved);
chrome.tabs.onReplaced.addListener(onTabReplaced);
function onCommitted(info) {
if (info.tabId !== tabId) return;
unregister();
getTab(tab.id).then(resolve);
}
function onErrorOccurred(info) {
if (info.tabId !== tabId) return;
unregister();
reject();
}
function onTabRemoved(removedTabId) {
if (removedTabId !== tabId) return;
unregister();
reject();
}
function onTabReplaced(addedTabId, removedTabId) {
onTabRemoved(removedTabId);
}
function unregister() {
chrome.webNavigation.onCommitted.removeListener(onCommitted);
chrome.webNavigation.onErrorOccurred.removeListener(onErrorOccurred);
chrome.tabs.onRemoved.removeListener(onTabRemoved);
chrome.tabs.onReplaced.removeListener(onTabReplaced);
}
});
}
function findExistingTab({url, currentWindow, ignoreHash = true, ignoreSearch = false}) {
url = new URL(url);
return queryTabs({url: urlToMatchPattern(url, ignoreSearch), currentWindow})
// FIXME: is tab.url always normalized?
.then(tabs => tabs.find(matchTab));
function matchTab(tab) {
const tabUrl = new URL(tab.url);
return tabUrl.protocol === url.protocol &&
tabUrl.username === url.username &&
tabUrl.password === url.password &&
tabUrl.hostname === url.hostname &&
tabUrl.port === url.port &&
tabUrl.pathname === url.pathname &&
(ignoreSearch || tabUrl.search === url.search) &&
(ignoreHash || tabUrl.hash === url.hash);
}
}
/**
* Opens a tab or activates an existing one,
* reuses the New Tab page or about:blank if it's focused now
* @param {Object} params
* or just a string e.g. openURL('foo')
* @param {string} params.url
* if relative, it's auto-expanded to the full extension URL
* @param {number} [params.index]
* move the tab to this index in the tab strip, -1 = last
* @param {Boolean} [params.active]
* true to activate the tab (this is the default value in the extensions API),
* false to open in background
* @param {?Boolean} [params.currentWindow]
* pass null to check all windows
* @param {any} [params.message]
* JSONifiable data to be sent to the tab via sendMessage()
* @returns {Promise<Tab>} Promise that resolves to the opened/activated tab
* @param {Object} _
* @param {string} _.url - if relative, it's auto-expanded to the full extension URL
* @param {number} [_.index] move the tab to this index in the tab strip, -1 = last
* @param {number} [_.openerTabId] defaults to the active tab
* @param {Boolean} [_.active=true] `true` to activate the tab
* @param {Boolean|null} [_.currentWindow=true] `null` to check all windows
* @param {Boolean} [_.newWindow=false] `true` to open a new window
* @param {chrome.windows.CreateData} [_.windowPosition] options for chrome.windows.create
* @returns {Promise<chrome.tabs.Tab>} Promise -> opened/activated tab
*/
function openURL({
// https://github.com/eslint/eslint/issues/10639
// eslint-disable-next-line no-undef
url = arguments[0],
url,
index,
active,
openerTabId,
active = true,
currentWindow = true,
newWindow = false,
windowPosition,
}) {
url = url.includes('://') ? url : chrome.runtime.getURL(url);
// [some] chromium forks don't handle their fake branded protocols
url = url.replace(/^(opera|vivaldi)/, 'chrome');
// FF doesn't handle moz-extension:// URLs (bug)
// FF decodes %2F in encoded parameters (bug)
// API doesn't handle the hash-fragment part
const urlQuery =
url.startsWith('moz-extension') ||
url.startsWith('chrome:') ?
undefined :
FIREFOX && url.includes('%2F') ?
url.replace(/%2F.*/, '*').replace(/#.*/, '') :
url.replace(/#.*/, '');
return queryTabs({url: urlQuery, currentWindow}).then(maybeSwitch);
function maybeSwitch(tabs = []) {
const urlWithSlash = url + '/';
const urlFF = FIREFOX && url.replace(/%2F/g, '/');
const tab = tabs.find(({url: u}) => u === url || u === urlFF || u === urlWithSlash);
if (!tab) {
return getActiveTab().then(maybeReplace);
}
if (index !== undefined && tab.index !== index) {
chrome.tabs.move(tab.id, {index});
}
return activateTab(tab);
if (!url.includes('://')) {
url = chrome.runtime.getURL(url);
}
// update current NTP or about:blank
// except when 'url' is chrome:// or chrome-extension:// in incognito
function maybeReplace(tab) {
const chromeInIncognito = tab && tab.incognito && url.startsWith('chrome');
const emptyTab = tab && URLS.emptyTab.includes(tab.url);
if (emptyTab && !chromeInIncognito) {
return new Promise(resolve =>
chrome.tabs.update({url}, resolve));
return findExistingTab({url, currentWindow}).then(tab => {
if (tab) {
return activateTab(tab, {
index,
openerTabId,
// when hash is different we can only set `url` if it has # otherwise the tab would reload
url: url !== tab.url && url.includes('#') ? url : undefined,
});
}
const options = {url, index, active};
// FF57+ supports openerTabId, but not in Android (indicated by the absence of chrome.windows)
if (tab && (!FIREFOX || FIREFOX >= 57 && chrome.windows) && !chromeInIncognito) {
options.openerTabId = tab.id;
if (newWindow && createWindow) {
return createWindow(Object.assign({url}, windowPosition))
.then(wnd => wnd.tabs[0]);
}
return new Promise(resolve =>
chrome.tabs.create(options, resolve));
return getActiveTab().then((activeTab = {url: ''}) =>
isTabReplaceable(activeTab, url) ?
activateTab(activeTab, {url, openerTabId}) : // not moving the tab
createTabWithOpener(activeTab, {url, index, active}));
});
function createTabWithOpener(openerTab, options) {
const id = openerTabId == null ? openerTab.id : openerTabId;
if (id != null && !openerTab.incognito && openerTabIdSupported) {
options.openerTabId = id;
}
return createTab(options);
}
}
// replace empty tab (NTP or about:blank)
// except when new URL is chrome:// or chrome-extension:// and the empty tab is
// in incognito
function isTabReplaceable(tab, newUrl) {
if (!tab || !URLS.emptyTab.includes(tab.url)) {
return false;
}
// FIXME: but why?
if (tab.incognito && newUrl.startsWith('chrome')) {
return false;
}
return true;
}
function activateTab(tab) {
function activateTab(tab, {url, index, openerTabId} = {}) {
const options = {active: true};
if (url) {
options.url = url;
}
if (openerTabId != null && openerTabIdSupported) {
options.openerTabId = openerTabId;
}
return Promise.all([
new Promise(resolve => {
chrome.tabs.update(tab.id, {active: true}, resolve);
}),
chrome.windows && new Promise(resolve => {
chrome.windows.update(tab.windowId, {focused: true}, resolve);
}),
]).then(([tab]) => tab);
updateTab(tab.id, options),
updateWindow && updateWindow(tab.windowId, {focused: true}),
index != null && moveTabs(tab.id, {index})
])
.then(() => tab);
}

View File

@ -1,9 +1,8 @@
/* global promisify deepCopy */
/* exported msg API */
// deepCopy is only used if the script is executed in extension pages.
'use strict';
const msg = (() => {
self.msg = self.INJECTED === 1 ? self.msg : (() => {
const runtimeSend = promisify(chrome.runtime.sendMessage.bind(chrome.runtime));
const tabSend = chrome.tabs && promisify(chrome.tabs.sendMessage.bind(chrome.tabs));
const tabQuery = chrome.tabs && promisify(chrome.tabs.query.bind(chrome.tabs));
@ -239,9 +238,15 @@ const msg = (() => {
}
})();
const API = new Proxy({}, {
self.API = self.INJECTED === 1 ? self.API : new Proxy({
// Handlers for these methods need sender.tab.id which is set by `send` as it uses messaging,
// unlike `sendBg` which invokes the background page directly in our own extension tabs
getTabUrlPrefix: true,
updateIconBadge: true,
styleViaAPI: true,
}, {
get: (target, name) =>
(...args) => Promise.resolve(msg.sendBg({
(...args) => Promise.resolve(self.msg[target[name] ? 'send' : 'sendBg']({
method: 'invokeAPI',
name,
args

View File

@ -1,12 +1,19 @@
'use strict';
(() => {
// eslint-disable-next-line no-unused-expressions
self.INJECTED !== 1 && (() => {
if (!Object.entries) {
Object.entries = obj => Object.keys(obj).map(k => [k, obj[k]]);
}
if (!Object.values) {
Object.values = obj => Object.keys(obj).map(k => obj[k]);
}
// the above was shared by content scripts and workers,
// the rest is only needed for our extension pages
if (!self.chrome || !self.chrome.tabs) return;
if (typeof document === 'object') {
const ELEMENT_METH = {
append: {

View File

@ -1,8 +1,7 @@
/* global promisify */
/* exported prefs */
'use strict';
const prefs = (() => {
self.prefs = self.INJECTED === 1 ? self.prefs : (() => {
const defaults = {
'openEditInWindow': false, // new editor opens in a own browser window
'windowPosition': {}, // detached window position

View File

@ -1,4 +1,3 @@
/* exported promisify */
'use strict';
/*
Convert chrome APIs into promises. Example:
@ -7,8 +6,8 @@ Convert chrome APIs into promises. Example:
storageSyncGet(['key']).then(result => {...});
*/
function promisify(fn) {
return (...args) =>
self.promisify = self.INJECTED === 1 ? self.promisify : fn =>
(...args) =>
new Promise((resolve, reject) => {
fn(...args, (...result) => {
if (chrome.runtime.lastError) {
@ -21,4 +20,3 @@ function promisify(fn) {
);
});
});
}

99
js/router.js Normal file
View File

@ -0,0 +1,99 @@
/* global deepEqual msg */
/* exported router */
'use strict';
const router = (() => {
const buffer = [];
const watchers = [];
document.addEventListener('DOMContentLoaded', () => update());
window.addEventListener('popstate', () => update());
window.addEventListener('hashchange', () => update());
msg.on(e => {
if (e.method === 'pushState' && e.url !== location.href) {
history.pushState(history.state, null, e.url);
update();
return true;
}
});
return {watch, updateSearch, getSearch, updateHash};
function watch(options, callback) {
/* Watch search params or hash and get notified on change.
options: {search?: Array<key: String>, hash?: String}
callback: (Array<value: String | null> | Boolean) => void
`hash` should always start with '#'.
When watching search params, callback receives a list of values.
When watching hash, callback receives a boolean.
*/
watchers.push({options, callback});
}
function updateSearch(key, value) {
const search = new URLSearchParams(location.search.replace(/^\?/, ''));
if (!value) {
search.delete(key);
} else {
search.set(key, value);
}
const finalSearch = search.toString();
if (finalSearch) {
history.replaceState(history.state, null, `?${finalSearch}${location.hash}`);
} else {
history.replaceState(history.state, null, `${location.pathname}${location.hash}`);
}
update(true);
}
function updateHash(hash) {
/* hash: String
Send an empty string to remove the hash.
*/
if (buffer.length > 1) {
if (!hash && !buffer[buffer.length - 2].includes('#') ||
hash && buffer[buffer.length - 2].endsWith(hash)) {
history.back();
return;
}
}
if (!hash) {
hash = ' ';
}
history.pushState(history.state, null, hash);
update();
}
function getSearch(key) {
return new URLSearchParams(location.search.replace(/^\?/, '')).get(key);
}
function update(replace) {
if (!buffer.length) {
buffer.push(location.href);
} else if (buffer[buffer.length - 1] === location.href) {
return;
} else if (replace) {
buffer[buffer.length - 1] = location.href;
} else if (buffer.length > 1 && buffer[buffer.length - 2] === location.href) {
buffer.pop();
} else {
buffer.push(location.href);
}
for (const {options, callback} of watchers) {
let state;
if (options.hash) {
state = options.hash === location.hash;
} else if (options.search) {
// TODO: remove .replace(/^\?/, '') when minimum_chrome_version >= 52 (https://crbug.com/601425)
const search = new URLSearchParams(location.search.replace(/^\?/, ''));
state = options.search.map(key => search.get(key));
}
if (!deepEqual(state, options.currentState)) {
options.currentState = state;
callback(state);
}
}
}
})();

View File

@ -152,6 +152,7 @@
<script src="js/messaging.js"></script>
<script src="js/prefs.js"></script>
<script src="js/msg.js"></script>
<script src="js/router.js"></script>
<script src="content/style-injector.js"></script>
<script src="content/apply.js"></script>
<script src="js/localization.js"></script>
@ -169,12 +170,6 @@
<script src="msgbox/msgbox.js"></script>
<script src="js/sections-util.js"></script>
<script src="js/storage-util.js"></script>
<script src="vendor/dropbox/dropbox-sdk.js" async></script>
<script src="vendor/zipjs-browserify/zip.js" defer></script>
<script src="sync/compress-text.js" defer></script>
<script src="sync/cross-browser-functions.js" defer></script>
<script src="sync/import-export-dropbox.js" async></script>
</head>
<body id="stylus-manage" i18n-dragndrop-hint="dragDropMessage">
@ -358,7 +353,7 @@
</div>
<div id="options-buttons">
<button id="manage-options-button" i18n-text="openOptionsManage"></button>
<button id="manage-options-button" i18n-text="openOptions"></button>
<button id="manage-shortcuts-button" class="chromium-only"
i18n-text="shortcuts"
i18n-title="shortcutsNote"></button>
@ -382,7 +377,7 @@
<div class="dropdown-content">
<a href="#" id="file-all-styles" i18n-text="bckpInstStyles"></a>
<a href="#" id="sync-dropbox-export" i18n-text="syncDropboxStyles"></a>
<a id="sync-dropbox-export" i18n-text="syncDropboxStyles" i18n-title="syncDropboxDeprecated"></a>
</div>
</div>
@ -394,7 +389,7 @@
<div class="dropdown-content">
<a href="#" id="unfile-all-styles" i18n-text="retrieveBckp"></a>
<a href="#" id="sync-dropbox-import" i18n-text="retrieveDropboxSync"></a>
<a id="sync-dropbox-import" i18n-text="retrieveDropboxSync" i18n-title="syncDropboxDeprecated"></a>
</div>
</div>

View File

@ -1,4 +1,4 @@
/* global installed messageBox sorter $ $$ $create t debounce prefs API onDOMready */
/* global installed messageBox sorter $ $$ $create t debounce prefs API router */
/* exported filterAndAppend */
'use strict';
@ -9,11 +9,17 @@ const filtersSelector = {
numTotal: 0,
};
// TODO: remove .replace(/^\?/, '') when minimum_chrome_version >= 52 (https://crbug.com/601425)
const urlFilterParam = new URLSearchParams(location.search.replace(/^\?/, '')).get('url');
if (location.search) {
history.replaceState(0, document.title, location.origin + location.pathname);
}
let initialized = false;
router.watch({search: ['search']}, ([search]) => {
$('#search').value = search || '';
if (!initialized) {
init();
initialized = true;
} else {
searchStyles();
}
});
HTMLSelectElement.prototype.adjustWidth = function () {
const option0 = this.selectedOptions[0];
@ -30,11 +36,11 @@ HTMLSelectElement.prototype.adjustWidth = function () {
parent.replaceChild(this, singleSelect);
};
onDOMready().then(() => {
$('#search').oninput = searchStyles;
if (urlFilterParam) {
$('#search').value = 'url:' + urlFilterParam;
}
function init() {
$('#search').oninput = e => {
router.updateSearch('search', e.target.value);
};
$('#search-help').onclick = event => {
event.preventDefault();
messageBox({
@ -120,6 +126,7 @@ onDOMready().then(() => {
}
}
filterOnChange({forceRefilter: true});
router.updateSearch('search', '');
};
// Adjust width after selects are visible
@ -131,7 +138,7 @@ onDOMready().then(() => {
});
filterOnChange({forceRefilter: true});
});
}
function filterOnChange({target: el, forceRefilter}) {
@ -271,7 +278,7 @@ function showFiltersStats() {
}
function searchStyles({immediately, container}) {
function searchStyles({immediately, container} = {}) {
const el = $('#search');
const query = el.value.trim();
if (query === el.lastValue && !immediately && !container) {

View File

@ -1,4 +1,4 @@
/* global messageBox styleSectionsEqual getOwnTab API onDOMready
/* global messageBox styleSectionsEqual API onDOMready
tryJSONparse scrollElementIntoView $ $$ API $create t animateElement
styleJSONseemsValid */
'use strict';
@ -7,8 +7,12 @@ const STYLISH_DUMP_FILE_EXT = '.txt';
const STYLUS_BACKUP_FILE_EXT = '.json';
onDOMready().then(() => {
$('#file-all-styles').onclick = exportToFile;
$('#unfile-all-styles').onclick = () => {
$('#file-all-styles').onclick = event => {
event.preventDefault();
exportToFile();
};
$('#unfile-all-styles').onclick = event => {
event.preventDefault();
importFromFile({fileTypeFilter: STYLUS_BACKUP_FILE_EXT});
};
@ -83,14 +87,11 @@ function importFromFile({fileTypeFilter, file} = {}) {
const text = event.target.result;
const maybeUsercss = !/^[\s\r\n]*\[/.test(text) &&
(text.includes('==UserStyle==') || /==UserStyle==/i.test(text));
(!maybeUsercss ?
importFromString(text) :
getOwnTab().then(tab => {
tab.url = URL.createObjectURL(new Blob([text], {type: 'text/css'}));
return API.openUsercssInstallPage({direct: true, tab})
.then(() => URL.revokeObjectURL(tab.url));
})
).then(numStyles => {
if (maybeUsercss) {
messageBox.alert(t('dragDropUsercssTabstrip'));
return;
}
importFromString(text).then(numStyles => {
document.body.style.cursor = '';
resolve(numStyles);
});

View File

@ -1109,6 +1109,22 @@ input[id^="manage.newUI"] {
-moz-osx-font-smoothing: grayscale;
}
#stylus-embedded-options {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100vh;
border: 0;
z-index: 2147483647;
background-color: hsla(0, 0%, 0%, .45);
animation: fadein .25s ease-in-out;
}
#stylus-embedded-options.fadeout {
animation: fadeout .25s ease-in-out;
}
@keyframes fadein {
from {
opacity: 0;
@ -1286,3 +1302,14 @@ input[id^="manage.newUI"] {
margin-left: -2px;
}
}
/* Deprecated dropbox backup (dropbox-sync) */
#sync-dropbox-export,
#sync-dropbox-import {
opacity: 0.5;
cursor: not-allowed;
}
#backup-buttons .dropdown-content #sync-dropbox-export:hover,
#backup-buttons .dropdown-content #sync-dropbox-import:hover {
background: transparent;
}

View File

@ -1,13 +1,13 @@
/*
global messageBox getStyleWithNoCode
filterAndAppend urlFilterParam showFiltersStats
filterAndAppend showFiltersStats
checkUpdate handleUpdateInstalled
objectDiff
configDialog
sorter msg prefs API onDOMready $ $$ $create template setupLivePrefs
URLS enforceInputRange t tWordBreak formatDate
getOwnTab getActiveTab openURL animateElement sessionStorageHash debounce
scrollElementIntoView CHROME VIVALDI FIREFOX
scrollElementIntoView CHROME VIVALDI FIREFOX router
*/
'use strict';
@ -35,7 +35,8 @@ const handleEvent = {};
Promise.all([
API.getAllStyles(true),
urlFilterParam && API.searchDB({query: 'url:' + urlFilterParam}),
// FIXME: integrate this into filter.js
router.getSearch('search') && API.searchDB({query: router.getSearch('search')}),
Promise.all([
onDOMready(),
prefs.initializing,
@ -80,7 +81,9 @@ function onRuntimeMessage(msg) {
function initGlobalEvents() {
installed = $('#installed');
installed.onclick = handleEvent.entryClicked;
$('#manage-options-button').onclick = () => chrome.runtime.openOptionsPage();
$('#manage-options-button').onclick = () => {
router.updateHash('#stylus-options');
};
{
const btn = $('#manage-shortcuts-button');
btn.onclick = btn.onclick || (() => openURL({url: URLS.configureCommands}));
@ -700,3 +703,39 @@ function highlightEditedStyle() {
requestAnimationFrame(() => scrollElementIntoView(entry));
}
}
function embedOptions() {
let options = $('#stylus-embedded-options');
if (!options) {
options = document.createElement('iframe');
options.id = 'stylus-embedded-options';
options.src = '/options.html';
document.documentElement.appendChild(options);
}
options.focus();
}
function unembedOptions() {
const options = $('#stylus-embedded-options');
if (options) {
options.contentWindow.document.body.classList.add('scaleout');
options.classList.add('fadeout');
animateElement(options, {
className: 'fadeout',
onComplete: () => options.remove(),
});
}
}
router.watch({hash: '#stylus-options'}, state => {
if (state) {
embedOptions();
} else {
unembedOptions();
}
});
window.addEventListener('closeOptions', () => {
router.updateHash('');
});

View File

@ -1,6 +1,6 @@
{
"name": "Stylus",
"version": "1.5.6",
"version": "1.5.10",
"minimum_chrome_version": "49",
"description": "__MSG_description__",
"homepage_url": "https://add0n.com/stylus.html",
@ -41,11 +41,14 @@
"background/token-manager.js",
"background/sync.js",
"background/content-scripts.js",
"background/db-chrome-storage.js",
"background/db.js",
"background/color-scheme.js",
"background/style-manager.js",
"background/navigator-util.js",
"background/icon-util.js",
"background/tab-manager.js",
"background/icon-manager.js",
"background/background.js",
"background/usercss-helper.js",
"background/style-via-api.js",
@ -59,6 +62,9 @@
"openManage": {
"description": "__MSG_openManage__"
},
"reload": {
"description": "__MSG_reload__"
},
"styleDisableAll": {
"description": "__MSG_disableAllStyles__"
}
@ -89,30 +95,6 @@
"run_at": "document_start",
"all_frames": false,
"js": ["content/install-hook-openusercss.js"]
},
{
"matches": [
"*://*/*.user.css",
"*://*/*.user.styl",
"file://*/*.user.css",
"file://*/*.user.styl",
"ftp://*/*.user.css",
"ftp://*/*.user.styl",
"*://*/*.user.css?*",
"*://*/*.user.styl?*",
"file://*/*.user.css?*",
"file://*/*.user.styl?*",
"ftp://*/*.user.css?*",
"ftp://*/*.user.styl?*"
],
"run_at": "document_idle",
"all_frames": false,
"js": ["content/install-hook-usercss.js"]
}
],
"browser_action": {
@ -126,10 +108,6 @@
"default_popup": "popup.html"
},
"default_locale": "en",
"options_ui": {
"page": "options.html",
"chrome_style": false
},
"applications": {
"gecko": {
"id": "{7a7a4a92-a2a0-41d1-9fd7-1e92480d612d}",

View File

@ -21,8 +21,8 @@
<script src="js/polyfill.js"></script>
<script src="js/dom.js"></script>
<script src="js/messaging.js"></script>
<script src="js/promisify.js"></script>
<script src="js/messaging.js"></script>
<script src="js/msg.js"></script>
<script src="js/localization.js"></script>
<script src="js/prefs.js"></script>
@ -33,6 +33,13 @@
</head>
<body id="stylus-options">
<div id="options-header">
<div id="options-title">
<div id="options-close-icon"><svg viewBox="0 0 20 20" class="svg-icon"><path d="M11.69,10l4.55,4.55-1.69,1.69L10,11.69,5.45,16.23,3.77,14.55,8.31,10,3.77,5.45,5.45,3.77,10,8.31l4.55-4.55,1.69,1.69Z"></path></svg></div>
Stylus</div>
</div>
<div id="options">
<div class="block">
@ -224,7 +231,7 @@
<div class="block" id="actions">
<button data-cmd="reset" i18n-text="optionsResetButton" i18n-title="optionsReset"></button>
<button data-cmd="open-manage" i18n-text="optionsOpenManager"></button>
<button data-cmd="open-manage" i18n-text="styleCancelEditLabel"></button>
<div data-cmd="check-updates">
<button i18n-text="optionsCheck" i18n-title="optionsCheckUpdate">
<span id="update-progress"></span>

View File

@ -1,36 +1,80 @@
html.opera {
html {
display: flex;
align-items: center;
justify-content: center;
height: 100%;
}
html.opera body {
width: auto;
height: 100vh;
background-color: none;
}
body {
background: #fff;
background: none;
margin: 0;
font-family: "Helvetica Neue", Helvetica, sans-serif;
font-size: 12px;
min-width: 480px;
display: flex;
flex-direction: column;
width: auto;
max-width: 800px;
width: max-content;
overflow-x: hidden;
max-height: calc(100vh - 32px);
border: 1px solid #999;
box-shadow: 0px 5px 15px 3px hsla(0, 0%, 0%, .35);
animation: scalein .25s ease-in-out;
}
@supports (-moz-appearance:none) {
body {
--addons-page-left-padding: 6px;
/* compensate 'html.firefox .block' padding-left */
width: calc(100% - var(--addons-page-left-padding));
/* match the default FF theme */
background-color: #f9f9fa;
}
html.firefox .block {
padding-left: var(--addons-page-left-padding);
}
body.scaleout {
animation: scaleout .25s ease-in-out;
}
#options {
background: #fff;
overflow-y: auto;
}
#options-close-icon .svg-icon {
fill: #666;
transition: fill .5s;
}
#options-close-icon:hover .svg-icon {
fill: #000;
}
#options-close-icon {
display: inline-flex;
cursor: pointer;
position: absolute;
right: 6px;
top: 6px;
}
#options-close-icon .svg-icon {
height: 20px;
width: 20px;
}
#options-title {
font-weight: bold;
background-color: rgb(145, 208, 198);
padding: .75rem 26px .75rem calc(30% + 4px);
font-size: 22px;
letter-spacing: .5px;
position: relative;
min-height: 42px;
box-sizing: border-box;
border-bottom: 1px solid #999;
}
#options-title::before {
content: "";
width: 0;
height: 0;
padding: 0 32px 32px 0;
background: url(/images/icon/32.png);
position: absolute;
left: 26px;
top: 0;
bottom: 0;
margin: auto;
}
.firefox .chromium-only,
@ -153,23 +197,20 @@ input[type="color"] {
#actions {
justify-content: space-around;
align-items: stretch;
padding: 1em;
flex-wrap: wrap;
padding: .5em 1em 1em;
white-space: nowrap;
background-color: rgba(0, 0, 0, .05);
margin: 0;
}
.firefox #actions,
.opera #actions {
background-color: transparent;
}
#actions button {
width: auto;
margin-top: .5em;
}
#actions button:not(:last-child) {
margin-right: 8px;
margin-right: 4px;
}
[data-cmd="check-updates"] button {
@ -299,13 +340,16 @@ html:not(.firefox):not(.opera) #updates {
fill: #000;
}
#message-box.note > div {
max-width: calc(100vw - 6rem);
#message-box.note {
align-items: center;
justify-content: center;
}
.opera #message-box.note,
.firefox #message-box.note {
background-color: transparent;
#message-box.note > div {
max-width: calc(100% - 5rem);
top: unset;
right: unset;
position: relative;
}
/* radio group */
@ -375,6 +419,21 @@ input[type="radio"].radio:checked::after {
text-transform: uppercase;
}
.sync-options .actions {
padding-top: 6px;
.sync-options .actions button {
margin-top: .5em;
}
@keyframes scalein {
0% {
transform: scale3d(.3, .3, .3);
}
100% {
transform: scale3d(1, 1, 1);
}
}
@keyframes scaleout {
100% {
transform: scale3d(0, 0, 0);
}
}

View File

@ -8,6 +8,13 @@ setupLivePrefs();
enforceInputRange($('#popupWidth'));
setTimeout(splitLongTooltips);
// TODO: add max version to re-enable once crbug.com/996859 is resolved
if (!FIREFOX && CHROME >= 3809) {
const dropboxOption = $('option[value="dropbox"]');
dropboxOption.disabled = true;
dropboxOption.setAttribute('title', t('hostDisabled'));
}
if (CHROME_HAS_BORDER_BUG) {
const borderOption = $('.chrome-no-popup-border');
if (borderOption) {
@ -38,6 +45,10 @@ if (FIREFOX && 'update' in (chrome.commands || {})) {
}
// actions
$('#options-close-icon').onclick = () => {
top.dispatchEvent(new CustomEvent('closeOptions'));
};
document.onclick = e => {
const target = e.target.closest('[data-cmd]');
if (!target) {
@ -48,7 +59,7 @@ document.onclick = e => {
switch (target.dataset.cmd) {
case 'open-manage':
openURL({url: 'manage.html'});
API.openManage();
break;
case 'check-updates':
@ -268,3 +279,9 @@ function customizeHotkeys() {
}
}
}
window.onkeydown = event => {
if (event.keyCode === 27) {
top.dispatchEvent(new CustomEvent('closeOptions'));
}
};

8481
package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

View File

@ -1,42 +1,46 @@
{
"name": "Stylus",
"version": "1.5.6",
"version": "1.5.10",
"description": "Redesign the web with Stylus, a user styles manager",
"license": "GPL-3.0-only",
"repository": "openstyles/stylus",
"author": "Stylus Team",
"devDependencies": {
"archiver": "^3.1.1",
"codemirror": "^5.48.4",
"codemirror": "^5.51.0",
"db-to-cloud": "^0.4.5",
"dropbox": "^4.0.30",
"endent": "^1.3.0",
"eslint": "^6.3.0",
"endent": "^1.4.0",
"eslint": "^6.8.0",
"fs-extra": "^8.1.0",
"jsonlint": "^1.6.3",
"less-bundle": "github:openstyles/less-bundle#v0.1.0",
"lz-string-unsafe": "^1.4.4-fork-1",
"rimraf": "^3.0.0",
"make-fetch-happen": "^7.1.1",
"semver-bundle": "^0.1.1",
"shx": "^0.3.2",
"stylelint-bundle": "^8.0.0",
"stylus-lang-bundle": "^0.54.5",
"updates": "^9.0.0",
"sync-version": "^1.0.1",
"tiny-glob": "^0.2.6",
"usercss-meta": "^0.9.0",
"web-ext": "^3.1.1",
"webext-tx-fix": "^0.3.2",
"zipjs-browserify": "^1.0.1"
"uuid": "^7.0.0-beta.0",
"web-ext": "^4.1.0",
"webext-tx-fix": "^0.3.3"
},
"scripts": {
"lint": "eslint **/*.js --cache || exit 0",
"update": "npm run update-node && npm run update-main",
"update-quick": "updates -u && npm update && npm run update-main",
"lint": "eslint \"**/*.js\" --cache",
"test": "npm run lint && web-ext lint",
"update-locales": "tx pull --all && webext-tx-fix",
"update-main": "npm run update-versions && npm run update-vendor",
"update-node": "updates -u && node tools/remove-modules.js && npm install",
"update-transifex": "tx push -s",
"update-vendor": "node tools/update-libraries.js && node tools/update-codemirror-themes.js",
"update-versions": "node tools/update-versions",
"zip": "npm run update-versions && node tools/zip.js",
"start": "web-ext run"
"build-vendor": "shx rm -rf vendor/* && node tools/build-vendor",
"zip": "node tools/zip.js",
"start": "web-ext run",
"start-chrome": "web-ext run -t chromium",
"preversion": "npm test",
"version": "sync-version manifest.json && git add .",
"postversion": "npm run zip && git push --follow-tags"
},
"engines": {
"node": ">=10.0.0"
}
}

View File

@ -235,6 +235,7 @@
</span>
</div>
<div id="write-style">
<a id="write-for-frames" href="#" title="<IFRAME>..." hidden></a>
<span id="write-style-for" i18n-text="writeStyleFor"></span>
</div>
</div>
@ -243,7 +244,7 @@
<div id="popup-options">
<button id="popup-manage-button" i18n-text="openManage"
data-href="manage.html" i18n-title="popupManageTooltip"></button>
<button id="popup-options-button" i18n-text="openOptionsPopup"></button>
<button id="popup-options-button" i18n-text="openOptions"></button>
<button id="popup-wiki-button"
i18n-text="linkStylusWiki"
i18n-title="linkGetHelp"

View File

@ -8,12 +8,6 @@
--outer-padding: 9px;
}
html {
/* Chrome 66-?? adds a gap equal to the scrollbar width,
which looks like a bug, see https://crbug.com/821143 */
overflow: overlay;
}
html, body {
height: min-content;
max-height: 600px;
@ -313,6 +307,15 @@ a.configure[target="_blank"] .svg-icon.config {
color: darkred;
}
.frame-url::before {
content: "iframe: ";
color: lightslategray;
}
.frame .style-name {
font-weight: normal;
}
/* entry menu */
.entry .menu {
display: none;
@ -516,13 +519,85 @@ body.blocked .actions > .main-controls {
content: "\00ad"; /* "soft" hyphen */
}
#match {
.about-blank > .breadcrumbs {
pointer-events: none;
}
.about-blank > .breadcrumbs a {
text-decoration: none;
}
.match {
overflow-wrap: break-word;
display: block;
flex-grow: 9;
}
.match[data-frame-id="0"] {
min-width: 200px;
}
.match[data-frame-id="0"] > .match {
margin-top: .25em;
}
.match:not([data-frame-id="0"]) a {
text-decoration: none; /* not underlining iframe links until hovered to reduce visual noise */
}
.match .match {
margin-left: .5rem;
}
.match .match::before {
content: "";
width: .25rem;
height: .25rem;
margin-left: -.5rem;
display: block;
position: absolute;
border-width: 1px;
border-style: none none solid solid;
}
.dupe > .breadcrumbs {
opacity: .5;
}
.dupe:not([data-children]) {
display: none;
}
#write-for-frames {
position: absolute;
width: 5px;
height: 5px;
margin-left: -12px;
margin-top: 4px;
--dash: transparent 2px, currentColor 2px, currentColor 3px, transparent 3px;
background: linear-gradient(var(--dash)), linear-gradient(90deg, var(--dash));
}
#write-for-frames.expanded {
background: linear-gradient(var(--dash));
}
#write-for-frames::after {
position: absolute;
margin: -2px;
border: 1px solid currentColor;
content: "";
top: 0;
left: 0;
right: 0;
bottom: 0;
}
#write-for-frames:not(.expanded) ~ .match:not([data-frame-id="0"]),
#write-for-frames:not(.expanded) ~ .match .match {
display: none;
}
/* "breadcrumbs" 'new style' links */
.breadcrumbs > .write-style-link {
margin-left: 0
@ -714,13 +789,19 @@ body.blocked .actions > .main-controls {
margin: 0;
}
.blocked-info strong {
.blocked-info .copy {
cursor: pointer;
transition: all .1s;
border-bottom: 1px dotted #000;
text-decoration: none;
font-weight: bold;
}
.blocked-info strong.copied {
.blocked-info .copy:hover {
color: #000;
}
.blocked-info .copy.copied {
background: hsl(170, 40%, 80%);
color: #000;
}
@ -745,7 +826,7 @@ body.blocked .actions > .main-controls {
display: block;
}
.blocked-info strong:after {
.blocked-info .copy:after {
content: '';
background: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAoAAAAKCAMAAAC67D+PAAAABGdBTUEAALGPC/xhBQAAAAFzUkdCAK7OHOkAAAAGUExURQAAAAAAAKVnuc8AAAABdFJOUwBA5thmAAAAIElEQVQI12NgYGCEAgYgkwEMGBFijEhixDMZkUSRLQAACpYALjrE2uIAAAAASUVORK5CYII=)center no-repeat;
height: 10px;

View File

@ -1,36 +1,41 @@
/* global configDialog hotkeys onTabReady msg
getActiveTab FIREFOX getTabRealURL URLS API onDOMready $ $$ prefs CHROME
/* global configDialog hotkeys msg
getActiveTab CHROME FIREFOX URLS API onDOMready $ $$ prefs
setupLivePrefs template t $create animateElement
tryJSONparse debounce CHROME_HAS_BORDER_BUG capitalize */
tryJSONparse CHROME_HAS_BORDER_BUG capitalize */
'use strict';
/** @type Element */
let installed;
/** @type string */
let tabURL;
const handleEvent = {};
const ABOUT_BLANK = 'about:blank';
const ENTRY_ID_PREFIX_RAW = 'style-';
const ENTRY_ID_PREFIX = '#' + ENTRY_ID_PREFIX_RAW;
if (CHROME >= 3345 && CHROME < 3533) { // Chrome 66-69 adds a gap, https://crbug.com/821143
document.head.appendChild($create('style', 'html { overflow: overlay }'));
}
toggleSideBorders();
getActiveTab()
.then(tab =>
FIREFOX && tab.url === 'about:blank' && tab.status === 'loading'
? getTabRealURLFirefox(tab)
: getTabRealURL(tab)
)
.then(url => Promise.all([
(tabURL = URLS.supported(url) ? url : '') &&
API.getStylesByUrl(tabURL),
onDOMready().then(initPopup),
]))
.then(([results]) => {
if (!results) {
initTabUrls()
.then(frames =>
Promise.all([
onDOMready().then(() => initPopup(frames)),
...frames
.filter(f => f.url && !f.isDupe)
.map(({url}) => API.getStylesByUrl(url).then(styles => ({styles, url}))),
]))
.then(([, ...results]) => {
if (results[0]) {
showStyles(results);
} else {
// unsupported URL;
return;
$('#popup-manage-button').removeAttribute('title');
}
showStyles(results.map(r => Object.assign(r.data, r)));
})
.catch(console.error);
@ -80,8 +85,32 @@ function toggleSideBorders(state = prefs.get('popup.borders')) {
}
}
function initTabUrls() {
return getActiveTab()
.then((tab = {}) =>
FIREFOX && tab.status === 'loading' && tab.url === ABOUT_BLANK
? waitForTabUrlFF(tab)
: tab)
.then(tab => new Promise(resolve =>
chrome.webNavigation.getAllFrames({tabId: tab.id}, frames =>
resolve({frames, tab}))))
.then(({frames, tab}) => {
let url = tab.pendingUrl || tab.url || ''; // new Chrome uses pendingUrl while connecting
frames = sortTabFrames(frames);
if (url === 'chrome://newtab/' && !URLS.chromeProtectsNTP) {
url = frames[0].url || '';
}
if (!URLS.supported(url)) {
url = '';
frames.length = 1;
}
tabURL = frames[0].url = url;
return frames;
});
}
function initPopup() {
/** @param {chrome.webNavigation.GetAllFrameResultDetails[]} frames */
function initPopup(frames) {
installed = $('#installed');
setPopupWidth();
@ -99,7 +128,7 @@ function initPopup() {
});
$('#popup-options-button').onclick = () => {
chrome.runtime.openOptionsPage();
API.openManage({options: true});
window.close();
};
@ -117,6 +146,13 @@ function initPopup() {
return;
}
frames.forEach(createWriterElement);
if (frames.length > 1) {
const el = $('#write-for-frames');
el.hidden = false;
el.onclick = () => el.classList.toggle('expanded');
}
getActiveTab().then(function ping(tab, retryCountdown = 10) {
msg.sendTab(tab.id, {method: 'ping'}, {frameId: 0})
.catch(() => false)
@ -128,7 +164,7 @@ function initPopup() {
// so we'll wait a bit to handle popup being invoked right after switching
if (retryCountdown > 0 && (
tab.status !== 'complete' ||
FIREFOX && tab.url === 'about:blank')) {
FIREFOX && tab.url === ABOUT_BLANK)) {
setTimeout(ping, 100, tab, --retryCountdown);
return;
}
@ -142,10 +178,12 @@ function initPopup() {
const note = (FIREFOX < 59 ? t('unreachableAMOHintOldFF') : t('unreachableAMOHint')) +
(FIREFOX < 60 ? '' : '\n' + t('unreachableAMOHintNewFF'));
const renderToken = s => s[0] === '<'
? $create('strong', {
? $create('a', {
textContent: s.slice(1, -1),
onclick: handleEvent.copyContent,
tabIndex: -1,
href: '#',
className: 'copy',
tabIndex: 0,
title: t('copy'),
})
: s;
@ -161,24 +199,26 @@ function initPopup() {
document.body.insertBefore(info, document.body.firstChild);
});
});
}
// Write new style links
const writeStyle = $('#write-style');
const matchTargets = document.createElement('span');
const matchWrapper = document.createElement('span');
matchWrapper.id = 'match';
matchWrapper.appendChild(matchTargets);
/** @param {chrome.webNavigation.GetAllFrameResultDetails} frame */
function createWriterElement(frame) {
const {url, frameId, parentFrameId, isDupe} = frame;
const targets = $create('span');
// For this URL
const urlLink = template.writeStyle.cloneNode(true);
const isAboutBlank = url === ABOUT_BLANK;
Object.assign(urlLink, {
href: 'edit.html?url-prefix=' + encodeURIComponent(tabURL),
title: `url-prefix("${tabURL}")`,
href: 'edit.html?url-prefix=' + encodeURIComponent(url),
title: `url-prefix("${url}")`,
tabIndex: isAboutBlank ? -1 : 0,
textContent: prefs.get('popup.breadcrumbs.usePath')
? new URL(tabURL).pathname.slice(1)
// this&nbsp;URL
: t('writeStyleForURL').replace(/ /g, '\u00a0'),
onclick: handleEvent.openLink,
? new URL(url).pathname.slice(1)
: frameId
? isAboutBlank ? url : 'URL'
: t('writeStyleForURL').replace(/ /g, '\u00a0'), // this&nbsp;URL
onclick: e => handleEvent.openEditor(e, {'url-prefix': url}),
});
if (prefs.get('popup.breadcrumbs')) {
urlLink.onmouseenter =
@ -186,10 +226,10 @@ function initPopup() {
urlLink.onmouseleave =
urlLink.onblur = () => urlLink.parentNode.classList.remove('url()');
}
matchTargets.appendChild(urlLink);
targets.appendChild(urlLink);
// For domain
const domains = getDomains(tabURL);
const domains = getDomains(url);
for (const domain of domains) {
const numParts = domain.length - domain.replace(/\./g, '').length + 1;
// Don't include TLD
@ -201,69 +241,95 @@ function initPopup() {
href: 'edit.html?domain=' + encodeURIComponent(domain),
textContent: numParts > 2 ? domain.split('.')[0] : domain,
title: `domain("${domain}")`,
onclick: handleEvent.openLink,
onclick: e => handleEvent.openEditor(e, {domain}),
});
domainLink.setAttribute('subdomain', numParts > 1 ? 'true' : '');
matchTargets.appendChild(domainLink);
targets.appendChild(domainLink);
}
if (prefs.get('popup.breadcrumbs')) {
matchTargets.classList.add('breadcrumbs');
matchTargets.appendChild(matchTargets.removeChild(matchTargets.firstElementChild));
targets.classList.add('breadcrumbs');
targets.appendChild(urlLink); // making it the last element
}
writeStyle.appendChild(matchWrapper);
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;
}
const root = $('#write-style');
const parent = $(`[data-frame-id="${parentFrameId}"]`, root) || root;
const child = $create({
tag: 'span',
className: `match${isDupe ? ' dupe' : ''}${isAboutBlank ? ' about-blank' : ''}`,
dataset: {frameId},
appendChild: targets,
});
parent.appendChild(child);
parent.dataset.children = (Number(parent.dataset.children) || 0) + 1;
}
function getDomains(url) {
let d = url.split(/[/:]+/, 2)[1];
if (!d || url.startsWith('file:')) {
return [];
}
const domains = [d];
while (d.includes('.')) {
d = d.substring(d.indexOf('.') + 1);
domains.push(d);
}
return domains;
}
/** @param {chrome.webNavigation.GetAllFrameResultDetails[]} frames */
function sortTabFrames(frames) {
const unknown = new Map(frames.map(f => [f.frameId, f]));
const known = new Map([[0, unknown.get(0) || {frameId: 0, url: ''}]]);
unknown.delete(0);
let lastSize = 0;
while (unknown.size !== lastSize) {
for (const [frameId, f] of unknown) {
if (known.has(f.parentFrameId)) {
unknown.delete(frameId);
if (!f.errorOccurred) known.set(frameId, f);
if (f.url === ABOUT_BLANK) f.url = known.get(f.parentFrameId).url;
}
}
lastSize = unknown.size; // guard against an infinite loop due to a weird frame structure
}
const sortedFrames = [...known.values(), ...unknown.values()];
const urls = new Set([ABOUT_BLANK]);
for (const f of sortedFrames) {
if (!f.url) f.url = '';
f.isDupe = urls.has(f.url);
urls.add(f.url);
}
return sortedFrames;
}
function sortStyles(entries) {
const enabledFirst = prefs.get('popup.enabledFirst');
entries.sort((a, b) =>
enabledFirst && a.styleMeta.enabled !== b.styleMeta.enabled ?
(a.styleMeta.enabled ? -1 : 1) :
a.styleMeta.name.localeCompare(b.styleMeta.name)
);
return entries.sort(({styleMeta: a}, {styleMeta: b}) =>
Boolean(a.frameUrl) - Boolean(b.frameUrl) ||
enabledFirst && Boolean(b.enabled) - Boolean(a.enabled) ||
a.name.localeCompare(b.name));
}
function showStyles(styles) {
if (!styles) {
return;
}
if (!styles.length) {
function showStyles(frameResults) {
const entries = new Map();
frameResults.forEach(({styles = [], url}, index) => {
styles.forEach(style => {
const {id} = style.data;
if (!entries.has(id)) {
style.frameUrl = index === 0 ? '' : url;
entries.set(id, createStyleElement(Object.assign(style.data, style)));
}
});
});
if (entries.size) {
installed.append(...sortStyles([...entries.values()]));
} else {
installed.appendChild(template.noStyles.cloneNode(true));
window.dispatchEvent(new Event('showStyles:done'));
return;
}
const entries = styles.map(createStyleElement);
sortStyles(entries);
entries.forEach(e => installed.appendChild(e));
window.dispatchEvent(new Event('showStyles:done'));
}
function sortStylesInPlace() {
if (!prefs.get('popup.autoResort')) {
return;
}
const entries = $$('.entry', installed);
if (!entries.length) {
return;
}
sortStyles(entries);
entries.forEach(e => installed.appendChild(e));
}
function createStyleElement(style) {
let entry = $(ENTRY_ID_PREFIX + style.id);
@ -287,7 +353,7 @@ function createStyleElement(style) {
const editLink = $('.style-edit-link', entry);
Object.assign(editLink, {
href: editLink.getAttribute('href') + style.id,
onclick: handleEvent.openLink,
onclick: e => handleEvent.openEditor(e, {id: style.id}),
});
const styleName = $('.style-name', entry);
Object.assign(styleName, {
@ -352,6 +418,14 @@ function createStyleElement(style) {
$('.exclude-by-domain', entry).title = getExcludeRule('domain');
$('.exclude-by-url', entry).title = getExcludeRule('url');
const {frameUrl} = style;
if (frameUrl) {
const sel = 'span.frame-url';
const frameEl = $(sel, entry) || styleName.insertBefore($create(sel), styleName.lastChild);
frameEl.title = frameUrl;
}
entry.classList.toggle('frame', Boolean(frameUrl));
return entry;
}
@ -396,7 +470,11 @@ Object.assign(handleEvent, {
event.stopPropagation();
API
.toggleStyle(handleEvent.getClickedStyleId(event), this.checked)
.then(sortStylesInPlace);
.then(() => {
if (prefs.get('popup.autoResort')) {
installed.append(...sortStyles($$('.entry', installed)));
}
});
},
toggleExclude(event, type) {
@ -527,18 +605,10 @@ Object.assign(handleEvent, {
$('#regexp-explanation').remove();
},
openLink(event) {
if (!chrome.windows || !prefs.get('openEditInWindow', false)) {
handleEvent.openURLandHide.call(this, event);
return;
}
openEditor(event, options) {
event.preventDefault();
chrome.windows.create(
Object.assign({
url: this.href
}, prefs.get('windowPosition', {}))
);
close();
API.openEditor(options);
window.close();
},
maybeEdit(event) {
@ -565,33 +635,32 @@ Object.assign(handleEvent, {
openURLandHide(event) {
event.preventDefault();
const message = tryJSONparse(this.dataset.sendMessage);
getActiveTab()
.then(activeTab => API.openURL({
url: this.href || this.dataset.href,
index: activeTab.index + 1
index: activeTab.index + 1,
message: tryJSONparse(this.dataset.sendMessage),
}))
.then(tab => {
if (message) {
return onTabReady(tab)
.then(() => msg.sendTab(tab.id, message));
}
})
.then(window.close);
},
openManager(event) {
if (event.button === 2 && !tabURL) return;
event.preventDefault();
if (!this.eventHandled) {
// FIXME: this only works if popup is closed
this.eventHandled = true;
this.dataset.href += event.shiftKey || event.button === 2 ?
'?url=' + encodeURIComponent(tabURL) : '';
handleEvent.openURLandHide.call(this, event);
API.openManage({
search: tabURL && (event.shiftKey || event.button === 2) ?
`url:${tabURL}` : null
});
window.close();
}
},
copyContent(event) {
const target = event.target;
event.preventDefault();
const target = document.activeElement;
const message = $('.copy-message');
navigator.clipboard.writeText(target.textContent);
target.classList.add('copied');
@ -639,32 +708,17 @@ function handleDelete(id) {
}
}
function getTabRealURLFirefox(tab) {
// wait for FF tab-on-demand to get a real URL (initially about:blank), 5 sec max
function waitForTabUrlFF(tab) {
return new Promise(resolve => {
function onNavigation({tabId, url, frameId}) {
if (tabId === tab.id && frameId === 0) {
detach();
resolve(url);
}
}
function detach(timedOut) {
if (timedOut) {
resolve(tab.url);
} else {
debounce.unregister(detach);
}
chrome.webNavigation.onBeforeNavigate.removeListener(onNavigation);
chrome.webNavigation.onCommitted.removeListener(onNavigation);
chrome.tabs.onRemoved.removeListener(detach);
chrome.tabs.onReplaced.removeListener(detach);
}
chrome.webNavigation.onBeforeNavigate.addListener(onNavigation);
chrome.webNavigation.onCommitted.addListener(onNavigation);
chrome.tabs.onRemoved.addListener(detach);
chrome.tabs.onReplaced.addListener(detach);
debounce(detach, 5000, {timedOut: true});
browser.tabs.onUpdated.addListener(...[
function onUpdated(tabId, info, updatedTab) {
if (info.url && tabId === tab.id) {
chrome.tabs.onUpdated.removeListener(onUpdated);
resolve(updatedTab);
}
},
...'UpdateFilter' in browser.tabs ? [{tabId: tab.id}] : [],
// TODO: remove both spreads and tabId check when strict_min_version >= 61
]);
});
}

View File

@ -20,17 +20,7 @@ window.addEventListener('showStyles:done', function _() {
const API_URL = BASE_URL + '/api/v1/styles/';
const UPDATE_URL = 'https://update.userstyles.org/%.md5';
// normal category is just one word like 'github' or 'google'
// but for some sites we need a fallback
// key: category.tld
// value <string>: use as category
// value true: fallback to search_terms
const CATEGORY_FALLBACK = {
'userstyles.org': 'userstyles.org',
'last.fm': true,
'Stylus': true,
};
const RX_CATEGORY = /^(?:.*?)([^.]+)(?:\.com?)?\.(\w+)$/;
const STYLUS_CATEGORY = 'chrome-extension';
const DISPLAY_PER_PAGE = 10;
// Millisecs to wait before fetching next batch of search results.
@ -54,7 +44,7 @@ window.addEventListener('showStyles:done', function _() {
let searchTotalPages;
let searchCurrentPage = 1;
let searchExhausted = false;
let searchExhausted = 0; // 1: once, 2: twice (first host.jp, then host)
// currently active USO requests
const xhrSpoofIds = new Set();
@ -79,7 +69,7 @@ window.addEventListener('showStyles:done', function _() {
const dom = {};
Object.assign($('#find-styles-link'), {
href: getSearchPageURL(tabURL),
href: BASE_URL + '/styles/browse/' + getCategory(),
onclick(event) {
if (!prefs.get('popup.findStylesInline') || dom.container) {
handleEvent.openURLandHide.call(this, event);
@ -222,7 +212,7 @@ window.addEventListener('showStyles:done', function _() {
* Initializes search results container, starts fetching results.
*/
function load() {
if (searchExhausted) {
if (searchExhausted > 1) {
if (!processedResults.length) {
error(404);
}
@ -233,21 +223,21 @@ window.addEventListener('showStyles:done', function _() {
dom.container.classList.remove('hidden');
dom.error.classList.add('hidden');
let pass = category ? 1 : 0;
category = category || getCategory();
search({category})
.then(function process(results) {
const data = results.data.filter(sameCategory);
const data = results.data.filter(sameCategoryNoDupes);
pass++;
if (pass === 1 && !data.length) {
category = getCategory({keepTLD: true});
return search({category, restart: true}).then(process);
if (!data.length && searchExhausted <= 1) {
const old = category;
const uso = (processedResults[0] || {}).subcategory;
category = uso !== category && uso || getCategory({retry: true});
if (category !== old) return search({category, restart: true}).then(process);
}
const numIrrelevant = results.data.length - data.length;
totalResults = results.current_page === 1 ? results.total_entries : totalResults;
totalResults += results.current_page === 1 ? results.total_entries : 0;
totalResults = Math.max(0, totalResults - numIrrelevant);
totalPages = Math.ceil(totalResults / DISPLAY_PER_PAGE);
@ -258,7 +248,7 @@ window.addEventListener('showStyles:done', function _() {
processNextResult();
} else if (numIrrelevant) {
load();
} else {
} else if (!processedResults.length) {
return Promise.reject(404);
}
})
@ -610,18 +600,10 @@ window.addEventListener('showStyles:done', function _() {
//endregion
//region USO API wrapper
function getSearchPageURL() {
const category = getCategory();
return BASE_URL +
'/styles/browse/' +
(category in CATEGORY_FALLBACK ? '?search_terms=' : '') +
category;
}
/**
* Resolves the Userstyles.org "category" for a given URL.
*/
function getCategory({keepTLD} = {}) {
function getCategory({retry} = {}) {
const u = tryCatch(() => new URL(tabURL));
if (!u) {
// Invalid URL
@ -629,21 +611,28 @@ window.addEventListener('showStyles:done', function _() {
} else if (u.protocol === 'file:') {
return 'file:';
} else if (u.protocol === location.protocol) {
return 'Stylus';
return STYLUS_CATEGORY;
} else {
// Website address, strip TLD & subdomain
const [, category = u.hostname, tld = ''] = u.hostname.match(RX_CATEGORY) || [];
const categoryWithTLD = category + '.' + tld;
const fallback = CATEGORY_FALLBACK[categoryWithTLD];
return fallback === true && categoryWithTLD || fallback || category + (keepTLD ? tld : '');
const parts = u.hostname.replace(/\.(?:com?|org)(\.\w{2,3})$/, '$1').split('.');
const [tld, main = u.hostname, third, fourth] = parts.reverse();
const keepTld = !retry && !(
tld === 'com' ||
tld === 'org' && main !== 'userstyles'
);
const keepThird = !retry && (
fourth ||
third && third !== 'www' && third !== 'm'
);
return (keepThird && `${third}.` || '') + main + (keepTld || keepThird ? `.${tld}` : '');
}
}
function sameCategory(result) {
return result.subcategory && (
category === result.subcategory ||
category === 'Stylus' && /^(chrome|moz)-extension$/.test(result.subcategory) ||
category.replace('.', '').toLowerCase() === result.subcategory.replace('.', '').toLowerCase()
function sameCategoryNoDupes(result) {
return (
result.subcategory &&
!processedResults.some(pr => pr.id === result.id) &&
(category !== STYLUS_CATEGORY || /\bStylus\b/i.test(result.name + result.description)) &&
category.split('.').includes(result.subcategory.split('.')[0])
);
}
@ -704,10 +693,10 @@ window.addEventListener('showStyles:done', function _() {
.then(json => {
searchCurrentPage = json.current_page + 1;
searchTotalPages = json.total_pages;
searchExhausted = (searchCurrentPage > searchTotalPages);
searchExhausted += searchCurrentPage > searchTotalPages;
return json;
}).catch(reason => {
searchExhausted = true;
searchExhausted++;
return Promise.reject(reason);
});
}

View File

@ -1,41 +0,0 @@
/* global zip onDOMready */
/* exported createZipFileFromText readZipFileFromBlob */
'use strict';
onDOMready().then(() => {
zip.workerScriptsPath = '../vendor/zipjs-browserify/';
});
/**
* @param {String} filename
* @param {String} text content of the file as text
* @returns {Promise<Blob>} resolves to a blob object representing the zip file
*/
function createZipFileFromText(filename, text) {
return new Promise((resolve, reject) => {
zip.createWriter(new zip.BlobWriter('application/zip'), writer => {
writer.add(filename, new zip.TextReader(text), function () {
writer.close(blob => {
resolve(blob);
});
});
}, reject);
});
}
/**
* @param {Object} blob object of zip file
* @returns {Promise<String>} resolves to a string the content of the first file of the zip
*/
function readZipFileFromBlob(blob) {
return new Promise((resolve, reject) => {
zip.createReader(new zip.BlobReader(blob), zipReader => {
zipReader.getEntries(entries => {
entries[0].getData(new zip.BlobWriter('text/plain'), data => {
zipReader.close();
resolve(data);
});
});
}, reject);
});
}

View File

@ -1,25 +0,0 @@
/* exported getRedirectUrlAuthFlow launchWebAuthFlow */
'use strict';
/**
* @returns {String} returns a redirect URL to be used in |launchWebAuthFlow|
*/
function getRedirectUrlAuthFlow() {
const browserApi = typeof browser === 'undefined' ? chrome : browser;
return browserApi.identity.getRedirectURL();
}
/**
* @param {Object} details based on chrome api
* @param {string} details.url url that initiates the auth flow
* @param {boolean} details.interactive if it is true a window will be displayed
* @return {Promise} returns the url containing the token for extraction
*/
function launchWebAuthFlow(details) {
if (typeof browser === 'undefined') {
return new Promise(resolve => {
chrome.identity.launchWebAuthFlow(details, resolve);
});
}
return browser.identity.launchWebAuthFlow(details);
}

View File

@ -1,176 +0,0 @@
/* global messageBox Dropbox createZipFileFromText readZipFileFromBlob
launchWebAuthFlow getRedirectUrlAuthFlow importFromString resolve
$ $create t chromeLocal API getOwnTab */
'use strict';
const DROPBOX_API_KEY = 'zg52vphuapvpng9';
const FILENAME_ZIP_FILE = 'stylus.json';
const DROPBOX_FILE = 'stylus.zip';
const API_ERROR_STATUS_FILE_NOT_FOUND = 409;
const HTTP_STATUS_CANCEL = 499;
function messageProgressBar(data) {
return messageBox({
title: `${data.title}`,
className: 'config-dialog',
contents: [
$create('p', data.text)
],
buttons: [{
textContent: t('confirmClose'),
dataset: {cmd: 'close'},
}],
}).then(() => {
document.body.style.minWidth = '';
document.body.style.minHeight = '';
});
}
function hasDropboxAccessToken() {
return chromeLocal.getValue('dropbox_access_token');
}
function requestDropboxAccessToken() {
const client = new Dropbox.Dropbox({
clientId: DROPBOX_API_KEY,
fetch
});
const authUrl = client.getAuthenticationUrl(getRedirectUrlAuthFlow());
return launchWebAuthFlow({url: authUrl, interactive: true})
.then(urlReturned => {
const params = new URLSearchParams(new URL(urlReturned).hash.replace('#', ''));
chromeLocal.setValue('dropbox_access_token', params.get('access_token'));
return params.get('access_token');
});
}
function uploadFileDropbox(client, stylesText) {
return client.filesUpload({path: '/' + DROPBOX_FILE, contents: stylesText});
}
$('#sync-dropbox-export').onclick = () => {
const mode = localStorage.installType;
const title = t('syncDropboxStyles');
const text = mode === 'normal' ? t('connectingDropbox') : t('connectingDropboxNotAllowed');
messageProgressBar({title, text});
if (mode !== 'normal') return;
hasDropboxAccessToken()
.then(token => token || requestDropboxAccessToken())
.then(token => {
const client = new Dropbox.Dropbox({
clientId: DROPBOX_API_KEY,
accessToken: token,
fetch
});
return client.filesDownload({path: '/' + DROPBOX_FILE})
.then(() => messageBox.confirm(t('overwriteFileExport')))
.then(ok => {
// deletes file if user want to
if (!ok) {
return Promise.reject({status: HTTP_STATUS_CANCEL});
}
return client.filesDelete({path: '/' + DROPBOX_FILE});
})
// file deleted with success, get styles and create a file
.then(() => {
messageProgressBar({title: title, text: t('gettingStyles')});
return API.getAllStyles().then(styles => JSON.stringify(styles, null, '\t'));
})
// create zip file
.then(stylesText => {
messageProgressBar({title: title, text: t('zipStyles')});
return createZipFileFromText(FILENAME_ZIP_FILE, stylesText);
})
// create file dropbox
.then(zipedText => {
messageProgressBar({title: title, text: t('uploadingFile')});
return uploadFileDropbox(client, zipedText);
})
// gives feedback to user
.then(() => messageProgressBar({title: title, text: t('exportSavedSuccess')}))
// handle not found cases and cancel action
.catch(error => {
console.log(error);
// saving file first time
if (error.status === API_ERROR_STATUS_FILE_NOT_FOUND) {
API.getAllStyles()
.then(styles => {
messageProgressBar({title: title, text: t('gettingStyles')});
return JSON.stringify(styles, null, '\t');
})
.then(stylesText => {
messageProgressBar({title: title, text: t('zipStyles')});
return createZipFileFromText(FILENAME_ZIP_FILE, stylesText);
})
.then(zipedText => {
messageProgressBar({title: title, text: t('uploadingFile')});
return uploadFileDropbox(client, zipedText);
})
.then(() => messageProgressBar({title: title, text: t('exportSavedSuccess')}))
.catch(err => messageBox.alert(err));
return;
}
// user cancelled the flow
if (error.status === HTTP_STATUS_CANCEL) {
return;
}
console.error(error);
});
});
};
$('#sync-dropbox-import').onclick = () => {
const mode = localStorage.installType;
const title = t('retrieveDropboxSync');
const text = mode === 'normal' ? t('connectingDropbox') : t('connectingDropboxNotAllowed');
messageProgressBar({title, text});
if (mode !== 'normal') return;
hasDropboxAccessToken()
.then(token => token || requestDropboxAccessToken())
.then(token => {
const client = new Dropbox.Dropbox({
clientId: DROPBOX_API_KEY,
accessToken: token,
fetch
});
return client.filesDownload({path: '/' + DROPBOX_FILE})
.then(response => {
messageProgressBar({title: title, text: t('unzipStyles')});
return readZipFileFromBlob(response.fileBlob);
})
.then(zipedFileBlob => {
messageProgressBar({title: title, text: t('readingStyles')});
document.body.style.cursor = 'wait';
const fReader = new FileReader();
fReader.onloadend = event => {
const text = event.target.result;
const maybeUsercss = !/^[\s\r\n]*\[/.test(text) &&
(text.includes('==UserStyle==') || /==UserStyle==/i.test(text));
(!maybeUsercss ?
importFromString(text) :
getOwnTab().then(tab => {
tab.url = URL.createObjectURL(new Blob([text], {type: 'text/css'}));
return API.openUsercssInstallPage({direct: true, tab})
.then(() => URL.revokeObjectURL(tab.url));
})
).then(numStyles => {
document.body.style.cursor = '';
resolve(numStyles);
});
};
fReader.readAsText(zipedFileBlob, 'utf-8');
})
.catch(error => {
// no file
if (error.status === API_ERROR_STATUS_FILE_NOT_FOUND) {
messageBox.alert(t('noFileToImport'));
return;
}
messageBox.alert(error);
});
});
};

View File

@ -1,9 +0,0 @@
# https://github.com/eslint/eslint/blob/master/docs/rules/README.md
parserOptions:
ecmaVersion: 2017
env:
browser: true
es6: true
node: true

167
tools/build-vendor.js Normal file
View File

@ -0,0 +1,167 @@
/* eslint-env node */
'use strict';
const path = require('path');
const endent = require('endent');
const fetch = require('make-fetch-happen');
const fse = require('fs-extra');
const glob = require('tiny-glob');
const files = {
'codemirror': [
'addon/comment/comment.js',
'addon/dialog',
'addon/edit/closebrackets.js',
'addon/edit/matchbrackets.js',
'addon/fold/brace-fold.js',
'addon/fold/comment-fold.js',
'addon/fold/foldcode.js',
'addon/fold/foldgutter.*',
'addon/fold/indent-fold.js',
'addon/hint/css-hint.js',
'addon/hint/show-hint.*',
'addon/lint/css-lint.js',
'addon/lint/json-lint.js',
'addon/lint/lint.*',
'addon/scroll/annotatescrollbar.js',
'addon/search/matchesonscrollbar.*',
'addon/search/searchcursor.js',
'addon/selection/active-line.js',
'keymap/*',
'lib/*',
'mode/css',
'mode/javascript',
'mode/stylus',
'theme/*'
],
'jsonlint': [
'lib/jsonlint.js → jsonlint.js',
'README.md → LICENSE'
],
'less-bundle': [
'dist/less.min.js → less.min.js'
],
'lz-string-unsafe': [
'lz-string-unsafe.min.js'
],
'semver-bundle': [
'dist/semver.js → semver.js'
],
'stylelint-bundle': [
'stylelint-bundle.min.js',
'https://github.com/stylelint/stylelint/raw/{VERSION}/LICENSE → LICENSE'
],
'stylus-lang-bundle': [
'stylus.min.js'
],
'usercss-meta': [
'dist/usercss-meta.min.js → usercss-meta.min.js'
],
'db-to-cloud': [
'dist/db-to-cloud.min.js → db-to-cloud.min.js'
],
'uuid': [
'dist/umd/uuidv4.min.js → uuid.min.js'
]
};
main().catch(console.error);
async function main() {
for (const pkg in files) {
console.log('\x1b[32m%s\x1b[0m', `Building ${pkg}...`);
// other files
const [fetched, copied] = await buildFiles(pkg, files[pkg]);
// README
await fse.outputFile(`vendor/${pkg}/README.md`, generateReadme(pkg, fetched, copied));
// LICENSE
await copyLicense(pkg);
}
console.log('\x1b[32m%s\x1b[0m', 'updating codemirror themes list...');
await fse.outputFile('edit/codemirror-themes.js', await generateThemeList());
}
async function generateThemeList() {
const themes = (await fse.readdir('vendor/codemirror/theme'))
.filter(name => name.endsWith('.css'))
.map(name => name.replace('.css', ''))
.sort();
return endent`
/* exported CODEMIRROR_THEMES */
// this file is generated by update-codemirror-themes.js
'use strict';
const CODEMIRROR_THEMES = ${JSON.stringify(themes, null, 2)};
`.replace(/"/g, "'") + '\n';
}
async function copyLicense(pkg) {
try {
await fse.access(`vendor/${pkg}/LICENSE`);
return;
} catch (err) {
// pass
}
for (const file of await glob(`node_modules/${pkg}/LICEN[SC]E*`)) {
await fse.copy(file, `vendor/${pkg}/LICENSE`);
return;
}
throw new Error(`cannot find license file for ${pkg}`);
}
async function buildFiles(pkg, patterns) {
const fetchedFiles = [];
const copiedFiles = [];
for (let pattern of patterns) {
pattern = pattern.replace('{VERSION}', require(`${pkg}/package.json`).version);
const [src, dest] = pattern.split(/\s*→\s*/);
if (src.startsWith('http')) {
const content = await (await fetch(src)).text();
await fse.outputFile(`vendor/${pkg}/${dest}`, content);
fetchedFiles.push([src, dest]);
} else {
let dirty = false;
for (const file of await glob(`node_modules/${pkg}/${src}`)) {
if (dest) {
await fse.copy(file, `vendor/${pkg}/${dest}`);
} else {
await fse.copy(file, path.join('vendor', path.relative('node_modules', file)));
}
copiedFiles.push([path.relative(`node_modules/${pkg}`, file), dest]);
dirty = true;
}
if (!dirty) {
throw new Error(`Pattern ${src} matches no files`);
}
}
}
return [fetchedFiles, copiedFiles];
}
function generateReadme(lib, fetched, copied) {
const pkg = require(`${lib}/package.json`);
let txt = `## ${pkg.name} v${pkg.version}\n\n`;
if (fetched.length) {
txt += `Following files are downloaded from HTTP:\n\n${generateList(fetched)}\n\n`;
}
if (copied.length) {
txt += `Following files are copied from npm (node_modules):\n\n${generateList(copied)}\n`;
}
return txt;
}
function generateList(list) {
return list.map(([src, dest]) => {
if (dest) {
return `* ${dest}: ${src}`;
}
return `* ${src}`;
}).join('\n');
}
// Rename CodeMirror$1 -> CodeMirror for development purposes
// FIXME: is this a workaround for old version of codemirror?
// function renameCodeMirrorVariable(filePath) {
// const file = fs.readFileSync(filePath, 'utf8');
// fs.writeFileSync(filePath, file.replace(/CodeMirror\$1/g, 'CodeMirror'));
// }

View File

@ -1,8 +0,0 @@
#!/usr/bin/env node
'use strict';
const fs = require('fs');
const rimraf = require('rimraf');
// See https://github.com/isaacs/rimraf/issues/102#issuecomment-412310309
rimraf('node_modules/!(rimraf|.bin)', fs, () => {});

View File

@ -1,39 +0,0 @@
#!/usr/bin/env node
'use strict';
const fs = require('fs-extra');
const path = require('path');
const endent = require('endent');
// Update theme names list in codemirror-editing-hook.js
async function getThemes() {
const p = path.join(__dirname, '..', 'vendor/codemirror/theme/');
const files = await fs.readdir(p);
return files
.filter(name => name.endsWith('.css'))
.map(name => name.replace('.css', ''))
.sort();
}
async function updateHook(themes) {
const fileName = path.join(__dirname, '..', 'edit/codemirror-themes.js');
fs.writeFile(fileName, endent`
/* exported CODEMIRROR_THEMES */
// this file is generated by update-codemirror-themes.js
'use strict';
const CODEMIRROR_THEMES = ${JSON.stringify(themes, null, 2)};
`.replace(/"/g, "'") + '\n');
}
function exit(err) {
if (err) {
console.error(err);
}
process.exit(err ? 1 : 0);
}
getThemes()
.then(themes => updateHook(themes))
.then(() => console.log('\x1b[32m%s\x1b[0m', 'codemirror themes list updated'))
.catch(exit);

View File

@ -1,138 +0,0 @@
#!/usr/bin/env node
'use strict';
const fs = require('fs-extra');
const path = require('path');
const root = path.join(__dirname, '..');
const files = {
'codemirror': [
'*', // only update existing vendor files
'theme' // update all theme files
],
'dropbox': [
'dist/Dropbox-sdk.js → dropbox-sdk.js'
],
'jsonlint': [
'lib/jsonlint.js → jsonlint.js'
],
'less-bundle': [
'dist/less.min.js → less.min.js'
],
'lz-string-unsafe': [
'lz-string-unsafe.min.js'
],
'semver-bundle': [
'dist/semver.js → semver.js'
],
'stylelint-bundle': [
'stylelint-bundle.min.js'
],
'stylus-lang-bundle': [
'stylus.min.js'
],
'usercss-meta': [
'dist/usercss-meta.min.js → usercss-meta.min.js'
],
'zipjs-browserify': [
'vendor/deflate.js → deflate.js',
'vendor/inflate.js → inflate.js',
'vendor/z-worker.js → z-worker.js',
'vendor/zip.js → zip.js'
],
'db-to-cloud': [
'dist/db-to-cloud.min.js → db-to-cloud.min.js'
]
};
async function updateReadme(lib) {
const pkg = await fs.readJson(`${root}/node_modules/${lib}/package.json`);
const file = `${root}/vendor/${lib}/README.md`;
const txt = await fs.readFile(file, 'utf8');
return fs.writeFile(file, txt.replace(/\b([v@])[\d.]+[-\w]*\b/g, `$1${pkg.version}`));
}
function isFolder(fileOrFolder) {
const stat = fs.statSync(fileOrFolder);
return stat.isDirectory();
}
// Rename CodeMirror$1 -> CodeMirror for development purposes
function renameCodeMirrorVariable(filePath) {
const file = fs.readFileSync(filePath, 'utf8');
fs.writeFileSync(filePath, file.replace(/CodeMirror\$1/g, 'CodeMirror'));
}
function updateExisting(lib) {
const libRoot = `${root}/node_modules/`;
const vendorRoot = `${root}/vendor/`;
const folders = [lib];
const process = function () {
if (folders.length) {
const folder = folders.shift();
const folderRoot = `${vendorRoot}${folder}`;
const entries = fs.readdirSync(folderRoot);
entries.forEach(entry => {
if (entry !== 'README.md' && entry !== 'LICENSE') {
// Ignore README.md & LICENSE files
const entryPath = `${folderRoot}/${entry}`;
try {
if (fs.existsSync(entryPath)) {
if (isFolder(entryPath)) {
folders.push(`${folder}/${entry}`);
} else {
fs.copySync(`${libRoot}${folder}/${entry}`, entryPath);
// Remove $1 from "CodeMirror$1" in codemirror.js
if (entry === 'codemirror.js') {
renameCodeMirrorVariable(entryPath);
}
}
}
} catch (err) {
// Show error in case file exists in vendor, but not in node_modules
console.log('\x1b[36m%s\x1b[0m', `"${entryPath}" doesn't exist!`);
}
}
});
}
if (folders.length) {
process();
}
};
process();
}
async function copy(lib, folder) {
const [src, dest] = folder.split(/\s*→\s*/);
try {
if (folder === '*') {
updateExisting(lib);
} else {
await fs.copy(`${root}/node_modules/${lib}/${src}`, `${root}/vendor/${lib}/${dest || src}`);
}
} catch (err) {
exit(err);
}
}
function exit(err) {
if (err) {
console.error(err);
}
process.exit(err ? 1 : 0);
}
Object.keys(files).forEach(lib => {
updateReadme(lib);
files[lib].forEach(folder => {
if (folder === '*') {
updateExisting(lib);
} else {
copy(lib, folder);
}
});
console.log('\x1b[32m%s\x1b[0m', `${lib} files updated`);
});

View File

@ -1,70 +0,0 @@
#!/usr/bin/env node
'use strict';
const fs = require('fs-extra');
const path = require('path');
const root = path.join(__dirname, '..');
const good = '\x1b[32m%s\x1b[0m';
const warn = '\x1b[36m%s\x1b[0m';
function exit(err) {
if (err) {
console.error(err);
}
process.exit(err ? 1 : 0);
}
function verToArray(v) {
return v.replace('v', '').split('.').map(Number);
}
// Simple compare function since we can't require semverCompare here
function compare(v1, v2) {
if (v1 === v2) {
return 0;
}
const [maj1, min1, pat1] = verToArray(v1);
const [maj2, min2, pat2] = verToArray(v2);
const majMatch = maj1 === maj2;
const minMatch = min1 === min2;
if (
maj1 > maj2 ||
majMatch && min1 > min2 ||
majMatch && minMatch && pat1 > pat2
) {
return 1;
}
return -1;
}
async function updateVersions() {
const regexp = /"([v\d.]+)"/;
const manifest = await fs.readFile(`${root}/manifest.json`, 'utf8');
const pkg = await fs.readFile(`${root}/package.json`, 'utf8');
const manifestVersion = manifest.match(regexp);
const pkgVersion = pkg.match(regexp);
if (manifestVersion && pkgVersion) {
const result = compare(manifestVersion[1], pkgVersion[1]);
let match, version, file, str;
if (result === 0) {
return console.log(good, 'Manifest & package versions match');
} else if (result > 0) {
match = pkgVersion;
version = manifestVersion[1];
file = 'package.json';
str = pkg;
} else {
match = manifestVersion;
version = pkgVersion[1];
file = 'manifest.json';
str = manifest;
}
console.log(warn, `Updating ${file} to ${version}`);
str = str.slice(0, match.index + 1) + version + str.slice(match.index + match[1].length + 1);
return fs.writeFile(`${root}/${file}`, str);
}
throw Error(`Error reading ${manifestVersion ? '' : 'manifest.json'} ${pkgVersion ? '' : 'package.json'}`);
}
updateVersions().catch(err => exit(err));

41
vendor/README.md vendored
View File

@ -1,41 +0,0 @@
# Vendor files are populated by the build script:
## What the build script does
Using this repo, run `npm install`... the latest versions of:
* `CodeMirror` (https://github.com/codemirror/CodeMirror) is installed.
* `jsonlint` (https://github.com/zaach/jsonlint) is installed.
* `less` (https://github.com/less/less.js) is installed.
* `lz-string-unsafe` (https://github.com/openstyles/lz-string-unsafe) is installed.
* `semver-bundle` (https://github.com/openstyles/semver-bundle) is installed.
* `stylus-lang` (https://github.com/openstyles/stylus-lang-bundle) is installed.
* `usercss-meta` (https://github.com/StylishThemes/parse-usercss) is installed.
* The necessary build tools are installed; see `devDependencies` in the `package.json`.
## Running the build script
Use `npm run update` to first update the packages in the `node_modules` folder & then update the vendor folder.
The following changes are made:
* `CodeMirror`: Only existing files are updated directly from the `node_modules` folder; see the [CodeMirror readme](codemirror/README.md) for specifics.
* `jsonlint`: The uncompressed `lib/jsonlint.js` is copied directly to `vendor/jsonlint`.
* `less`: The compressed `dist/less.min.js` file is copied directly into `vendor/less`.
* `lz-string-unsafe`: The compressed `lz-string-unsafe.min.js` file is copied directly into `vendor/lz-string-unsafe`.
* `semver-bundle`: The `dist/semver.js` file is copied directly into `vendor/semver`.
* `stylus-lang-bundle`: The `stylus.min.js` file is copied directly into `vendor/stylus-lang-bundle`.
* `usercss-meta`: The `dist/usercss-meta.min.js` file is copied directly into `vendor/usercss-meta`.
## Creating the ZIP
Use `npm run zip`.
This command creates a zip file that 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).
* `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`.

View File

@ -1,8 +1,3 @@
https://codemirror.net/
https://github.com/codemirror/CodeMirror/blob/master/LICENSE
MIT License
Copyright (C) 2017 by Marijn Haverbeke <marijnh@gmail.com> and others

View File

@ -1,3 +1,97 @@
## CodeMirror v5.48.4
## codemirror v5.51.0
Only files & folders that exist in the `vendor/codemirror` folder are copied from the `node_modules/codemirror` folder. Except all theme files are copied, in case new themes have been added.
Following files are copied from npm (node_modules):
* addon\comment\comment.js
* addon\dialog
* addon\edit\closebrackets.js
* addon\edit\matchbrackets.js
* addon\fold\brace-fold.js
* addon\fold\comment-fold.js
* addon\fold\foldcode.js
* addon\fold\foldgutter.css
* addon\fold\foldgutter.js
* addon\fold\indent-fold.js
* addon\hint\css-hint.js
* addon\hint\show-hint.css
* addon\hint\show-hint.js
* addon\lint\css-lint.js
* addon\lint\json-lint.js
* addon\lint\lint.css
* addon\lint\lint.js
* addon\scroll\annotatescrollbar.js
* addon\search\matchesonscrollbar.css
* addon\search\matchesonscrollbar.js
* addon\search\searchcursor.js
* addon\selection\active-line.js
* keymap\emacs.js
* keymap\sublime.js
* keymap\vim.js
* lib\codemirror.css
* lib\codemirror.js
* mode\css
* mode\javascript
* mode\stylus
* theme\3024-day.css
* theme\3024-night.css
* theme\abcdef.css
* theme\ambiance-mobile.css
* theme\ambiance.css
* theme\ayu-dark.css
* theme\ayu-mirage.css
* theme\base16-dark.css
* theme\base16-light.css
* theme\bespin.css
* theme\blackboard.css
* theme\cobalt.css
* theme\colorforth.css
* theme\darcula.css
* theme\dracula.css
* theme\duotone-dark.css
* theme\duotone-light.css
* theme\eclipse.css
* theme\elegant.css
* theme\erlang-dark.css
* theme\gruvbox-dark.css
* theme\hopscotch.css
* theme\icecoder.css
* theme\idea.css
* theme\isotope.css
* theme\lesser-dark.css
* theme\liquibyte.css
* theme\lucario.css
* theme\material-darker.css
* theme\material-ocean.css
* theme\material-palenight.css
* theme\material.css
* theme\mbo.css
* theme\mdn-like.css
* theme\midnight.css
* theme\monokai.css
* theme\moxer.css
* theme\neat.css
* theme\neo.css
* theme\night.css
* theme\nord.css
* theme\oceanic-next.css
* theme\panda-syntax.css
* theme\paraiso-dark.css
* theme\paraiso-light.css
* theme\pastel-on-dark.css
* theme\railscasts.css
* theme\rubyblue.css
* theme\seti.css
* theme\shadowfox.css
* theme\solarized.css
* theme\ssms.css
* theme\the-matrix.css
* theme\tomorrow-night-bright.css
* theme\tomorrow-night-eighties.css
* theme\ttcn.css
* theme\twilight.css
* theme\vibrant-ink.css
* theme\xq-dark.css
* theme\xq-light.css
* theme\yeti.css
* theme\yonce.css
* theme\zenburn.css

View File

@ -42,7 +42,7 @@
}
if (!range || range.cleared || force === "unfold") return;
var myWidget = makeWidget(cm, options);
var myWidget = makeWidget(cm, options, range);
CodeMirror.on(myWidget, "mousedown", function(e) {
myRange.clear();
CodeMirror.e_preventDefault(e);
@ -58,8 +58,13 @@
CodeMirror.signal(cm, "fold", cm, range.from, range.to);
}
function makeWidget(cm, options) {
function makeWidget(cm, options, range) {
var widget = getOption(cm, options, "widget");
if (typeof widget == "function") {
widget = widget(range.from, range.to);
}
if (typeof widget == "string") {
var text = document.createTextNode(widget);
widget = document.createElement("span");

View File

@ -16,7 +16,7 @@
cm.clearGutter(cm.state.foldGutter.options.gutter);
cm.state.foldGutter = null;
cm.off("gutterClick", onGutterClick);
cm.off("change", onChange);
cm.off("changes", onChange);
cm.off("viewportChange", onViewportChange);
cm.off("fold", onFold);
cm.off("unfold", onFold);
@ -26,7 +26,7 @@
cm.state.foldGutter = new State(parseOptions(val));
updateInViewport(cm);
cm.on("gutterClick", onGutterClick);
cm.on("change", onChange);
cm.on("changes", onChange);
cm.on("viewportChange", onViewportChange);
cm.on("fold", onFold);
cm.on("unfold", onFold);
@ -71,24 +71,36 @@
}
function updateFoldInfo(cm, from, to) {
var opts = cm.state.foldGutter.options, cur = from;
var opts = cm.state.foldGutter.options, cur = from - 1;
var minSize = cm.foldOption(opts, "minFoldSize");
var func = cm.foldOption(opts, "rangeFinder");
// we can reuse the built-in indicator element if its className matches the new state
var clsFolded = typeof opts.indicatorFolded == "string" && classTest(opts.indicatorFolded);
var clsOpen = typeof opts.indicatorOpen == "string" && classTest(opts.indicatorOpen);
cm.eachLine(from, to, function(line) {
++cur;
var mark = null;
var old = line.gutterMarkers;
if (old) old = old[opts.gutter];
if (isFolded(cm, cur)) {
if (clsFolded && old && clsFolded.test(old.className)) return;
mark = marker(opts.indicatorFolded);
} else {
var pos = Pos(cur, 0);
var range = func && func(cm, pos);
if (range && range.to.line - range.from.line >= minSize)
if (range && range.to.line - range.from.line >= minSize) {
if (clsOpen && old && clsOpen.test(old.className)) return;
mark = marker(opts.indicatorOpen);
}
}
if (!mark && !old) return;
cm.setGutterMarker(line, opts.gutter, mark);
++cur;
});
}
// copied from CodeMirror/src/util/dom.js
function classTest(cls) { return new RegExp("(^|\\s)" + cls + "(?:$|\\s)\\s*") }
function updateInViewport(cm) {
var vp = cm.getViewport(), state = cm.state.foldGutter;
if (!state) return;

View File

@ -11,9 +11,15 @@
})(function(CodeMirror) {
"use strict";
var pseudoClasses = {link: 1, visited: 1, active: 1, hover: 1, focus: 1,
"first-letter": 1, "first-line": 1, "first-child": 1,
before: 1, after: 1, lang: 1};
var pseudoClasses = {"active":1, "after":1, "before":1, "checked":1, "default":1,
"disabled":1, "empty":1, "enabled":1, "first-child":1, "first-letter":1,
"first-line":1, "first-of-type":1, "focus":1, "hover":1, "in-range":1,
"indeterminate":1, "invalid":1, "lang":1, "last-child":1, "last-of-type":1,
"link":1, "not":1, "nth-child":1, "nth-last-child":1, "nth-last-of-type":1,
"nth-of-type":1, "only-of-type":1, "only-child":1, "optional":1, "out-of-range":1,
"placeholder":1, "read-only":1, "read-write":1, "required":1, "root":1,
"selection":1, "target":1, "valid":1, "visited":1
};
CodeMirror.registerHelper("hint", "css", function(cm) {
var cur = cm.getCursor(), token = cm.getTokenAt(cur);

View File

@ -322,6 +322,7 @@
CodeMirror.on(hints, "mousedown", function() {
setTimeout(function(){cm.focus();}, 20);
});
this.scrollToActive()
CodeMirror.signal(data, "select", completions[this.selectedHint], hints.childNodes[this.selectedHint]);
return true;
@ -363,11 +364,16 @@
if (node) node.className = node.className.replace(" " + ACTIVE_HINT_ELEMENT_CLASS, "");
node = this.hints.childNodes[this.selectedHint = i];
node.className += " " + ACTIVE_HINT_ELEMENT_CLASS;
this.scrollToActive()
CodeMirror.signal(this.data, "select", this.data.list[this.selectedHint], node);
},
scrollToActive: function() {
var node = this.hints.childNodes[this.selectedHint]
if (node.offsetTop < this.hints.scrollTop)
this.hints.scrollTop = node.offsetTop - 3;
else if (node.offsetTop + node.offsetHeight > this.hints.scrollTop + this.hints.clientHeight)
this.hints.scrollTop = node.offsetTop + node.offsetHeight - this.hints.clientHeight + 3;
CodeMirror.signal(this.data, "select", this.data.list[this.selectedHint], node);
},
screenAmount: function() {

View File

@ -43,7 +43,7 @@
cm.on("markerAdded", this.resizeHandler);
cm.on("markerCleared", this.resizeHandler);
if (options.listenForChanges !== false)
cm.on("change", this.changeHandler = function() {
cm.on("changes", this.changeHandler = function() {
scheduleRedraw(250);
});
}
@ -116,7 +116,7 @@
this.cm.off("refresh", this.resizeHandler);
this.cm.off("markerAdded", this.resizeHandler);
this.cm.off("markerCleared", this.resizeHandler);
if (this.changeHandler) this.cm.off("change", this.changeHandler);
if (this.changeHandler) this.cm.off("changes", this.changeHandler);
this.div.parentNode.removeChild(this.div);
};
});

View File

@ -72,24 +72,26 @@
}
}
function lastMatchIn(string, regexp) {
var cutOff = 0, match
for (;;) {
regexp.lastIndex = cutOff
function lastMatchIn(string, regexp, endMargin) {
var match, from = 0
while (from <= string.length) {
regexp.lastIndex = from
var newMatch = regexp.exec(string)
if (!newMatch) return match
match = newMatch
cutOff = match.index + (match[0].length || 1)
if (cutOff == string.length) return match
if (!newMatch) break
var end = newMatch.index + newMatch[0].length
if (end > string.length - endMargin) break
if (!match || end > match.index + match[0].length)
match = newMatch
from = newMatch.index + 1
}
return match
}
function searchRegexpBackward(doc, regexp, start) {
regexp = ensureFlags(regexp, "g")
for (var line = start.line, ch = start.ch, first = doc.firstLine(); line >= first; line--, ch = -1) {
var string = doc.getLine(line)
if (ch > -1) string = string.slice(0, ch)
var match = lastMatchIn(string, regexp)
var match = lastMatchIn(string, regexp, ch < 0 ? 0 : string.length - ch)
if (match)
return {from: Pos(line, match.index),
to: Pos(line, match.index + match[0].length),
@ -98,16 +100,17 @@
}
function searchRegexpBackwardMultiline(doc, regexp, start) {
if (!maybeMultiline(regexp)) return searchRegexpBackward(doc, regexp, start)
regexp = ensureFlags(regexp, "gm")
var string, chunk = 1
var string, chunkSize = 1, endMargin = doc.getLine(start.line).length - start.ch
for (var line = start.line, first = doc.firstLine(); line >= first;) {
for (var i = 0; i < chunk; i++) {
for (var i = 0; i < chunkSize && line >= first; i++) {
var curLine = doc.getLine(line--)
string = string == null ? curLine.slice(0, start.ch) : curLine + "\n" + string
string = string == null ? curLine : curLine + "\n" + string
}
chunk *= 2
chunkSize *= 2
var match = lastMatchIn(string, regexp)
var match = lastMatchIn(string, regexp, endMargin)
if (match) {
var before = string.slice(0, match.index).split("\n"), inside = match[0].split("\n")
var startLine = line + before.length, startCh = before[before.length - 1].length

View File

@ -22,17 +22,21 @@
if (dir < 0 && start.ch == 0) return doc.clipPos(Pos(start.line - 1));
var line = doc.getLine(start.line);
if (dir > 0 && start.ch >= line.length) return doc.clipPos(Pos(start.line + 1, 0));
var state = "start", type;
for (var pos = start.ch, e = dir < 0 ? 0 : line.length, i = 0; pos != e; pos += dir, i++) {
var state = "start", type, startPos = start.ch;
for (var pos = startPos, e = dir < 0 ? 0 : line.length, i = 0; pos != e; pos += dir, i++) {
var next = line.charAt(dir < 0 ? pos - 1 : pos);
var cat = next != "_" && CodeMirror.isWordChar(next) ? "w" : "o";
if (cat == "w" && next.toUpperCase() == next) cat = "W";
if (state == "start") {
if (cat != "o") { state = "in"; type = cat; }
else startPos = pos + dir
} else if (state == "in") {
if (type != cat) {
if (type == "w" && cat == "W" && dir < 0) pos--;
if (type == "W" && cat == "w" && dir > 0) { type = "w"; continue; }
if (type == "W" && cat == "w" && dir > 0) { // From uppercase to lowercase
if (pos == startPos + 1) { type = "w"; continue; }
else pos--;
}
break;
}
}
@ -144,14 +148,24 @@
cur = cm.getSearchCursor(query, Pos(cm.firstLine(), 0));
found = cur.findNext();
}
if (!found || isSelectedRange(cm.listSelections(), cur.from(), cur.to()))
return CodeMirror.Pass
if (!found || isSelectedRange(cm.listSelections(), cur.from(), cur.to())) return
cm.addSelection(cur.from(), cur.to());
}
if (fullWord)
cm.state.sublimeFindFullWord = cm.doc.sel;
};
cmds.skipAndSelectNextOccurrence = function(cm) {
var prevAnchor = cm.getCursor("anchor"), prevHead = cm.getCursor("head");
cmds.selectNextOccurrence(cm);
if (CodeMirror.cmpPos(prevAnchor, prevHead) != 0) {
cm.doc.setSelections(cm.doc.listSelections()
.filter(function (sel) {
return sel.anchor != prevAnchor || sel.head != prevHead;
}));
}
}
function addCursorToSelection(cm, dir) {
var ranges = cm.listSelections(), newRanges = [];
for (var i = 0; i < ranges.length; i++) {
@ -175,7 +189,8 @@
function isSelectedRange(ranges, from, to) {
for (var i = 0; i < ranges.length; i++)
if (ranges[i].from() == from && ranges[i].to() == to) return true
if (CodeMirror.cmpPos(ranges[i].from(), from) == 0 &&
CodeMirror.cmpPos(ranges[i].to(), to) == 0) return true
return false
}
@ -213,11 +228,15 @@
if (!selectBetweenBrackets(cm)) return CodeMirror.Pass;
};
function puncType(type) {
return !type ? null : /\bpunctuation\b/.test(type) ? type : undefined
}
cmds.goToBracket = function(cm) {
cm.extendSelectionsBy(function(range) {
var next = cm.scanForBracket(range.head, 1);
var next = cm.scanForBracket(range.head, 1, puncType(cm.getTokenTypeAt(range.head)));
if (next && CodeMirror.cmpPos(next.pos, range.head) != 0) return next.pos;
var prev = cm.scanForBracket(range.head, -1);
var prev = cm.scanForBracket(range.head, -1, puncType(cm.getTokenTypeAt(Pos(range.head.line, range.head.ch + 1))));
return prev && Pos(prev.pos.line, prev.pos.ch + 1) || range.head;
});
};
@ -597,6 +616,7 @@
"Shift-Cmd-F2": "clearBookmarks",
"Alt-F2": "selectBookmarks",
"Backspace": "smartBackspace",
"Cmd-K Cmd-D": "skipAndSelectNextOccurrence",
"Cmd-K Cmd-K": "delLineRight",
"Cmd-K Cmd-U": "upcaseAtCursor",
"Cmd-K Cmd-L": "downcaseAtCursor",
@ -657,6 +677,7 @@
"Shift-Ctrl-F2": "clearBookmarks",
"Alt-F2": "selectBookmarks",
"Backspace": "smartBackspace",
"Ctrl-K Ctrl-D": "skipAndSelectNextOccurrence",
"Ctrl-K Ctrl-K": "delLineRight",
"Ctrl-K Ctrl-U": "upcaseAtCursor",
"Ctrl-K Ctrl-L": "downcaseAtCursor",

View File

@ -164,7 +164,9 @@
{ keys: 'A', type: 'action', action: 'enterInsertMode', isEdit: true, actionArgs: { insertAt: 'eol' }, context: 'normal' },
{ keys: 'A', type: 'action', action: 'enterInsertMode', isEdit: true, actionArgs: { insertAt: 'endOfSelectedArea' }, context: 'visual' },
{ keys: 'i', type: 'action', action: 'enterInsertMode', isEdit: true, actionArgs: { insertAt: 'inplace' }, context: 'normal' },
{ keys: 'gi', type: 'action', action: 'enterInsertMode', isEdit: true, actionArgs: { insertAt: 'lastEdit' }, context: 'normal' },
{ keys: 'I', type: 'action', action: 'enterInsertMode', isEdit: true, actionArgs: { insertAt: 'firstNonBlank'}, context: 'normal' },
{ keys: 'gI', type: 'action', action: 'enterInsertMode', isEdit: true, actionArgs: { insertAt: 'bol'}, context: 'normal' },
{ keys: 'I', type: 'action', action: 'enterInsertMode', isEdit: true, actionArgs: { insertAt: 'startOfSelectedArea' }, context: 'visual' },
{ keys: 'o', type: 'action', action: 'newLineAndEnterInsertMode', isEdit: true, interlaceInsertRepeat: true, actionArgs: { after: true }, context: 'normal' },
{ keys: 'O', type: 'action', action: 'newLineAndEnterInsertMode', isEdit: true, interlaceInsertRepeat: true, actionArgs: { after: false }, context: 'normal' },
@ -174,13 +176,15 @@
{ keys: '<C-q>', type: 'action', action: 'toggleVisualMode', actionArgs: { blockwise: true }},
{ keys: 'gv', type: 'action', action: 'reselectLastSelection' },
{ keys: 'J', type: 'action', action: 'joinLines', isEdit: true },
{ keys: 'gJ', type: 'action', action: 'joinLines', actionArgs: { keepSpaces: true }, isEdit: true },
{ keys: 'p', type: 'action', action: 'paste', isEdit: true, actionArgs: { after: true, isEdit: true }},
{ keys: 'P', type: 'action', action: 'paste', isEdit: true, actionArgs: { after: false, isEdit: true }},
{ keys: 'r<character>', type: 'action', action: 'replace', isEdit: true },
{ keys: '@<character>', type: 'action', action: 'replayMacro' },
{ keys: 'q<character>', type: 'action', action: 'enterMacroRecordMode' },
// Handle Replace-mode as a special case of insert mode.
{ keys: 'R', type: 'action', action: 'enterInsertMode', isEdit: true, actionArgs: { replace: true }},
{ keys: 'R', type: 'action', action: 'enterInsertMode', isEdit: true, actionArgs: { replace: true }, context: 'normal'},
{ keys: 'R', type: 'operator', operator: 'change', operatorArgs: { linewise: true, fullLine: true }, context: 'visual', exitVisualBlock: true},
{ keys: 'u', type: 'action', action: 'undo', context: 'normal' },
{ keys: 'u', type: 'operator', operator: 'changeCase', operatorArgs: {toLower: true}, context: 'visual', isEdit: true },
{ keys: 'U', type: 'operator', operator: 'changeCase', operatorArgs: {toLower: false}, context: 'visual', isEdit: true },
@ -594,9 +598,16 @@
}
return mark;
}
function find(cm, offset) {
var oldPointer = pointer;
var mark = move(cm, offset);
pointer = oldPointer;
return mark && mark.find();
}
return {
cachedCursor: undefined, //used for # and * jumps
add: add,
find: find,
move: move
};
};
@ -1293,6 +1304,10 @@
}
inputState.operator = command.operator;
inputState.operatorArgs = copyArgs(command.operatorArgs);
if (command.exitVisualBlock) {
vim.visualBlock = false;
updateCmSelection(cm);
}
if (vim.visualMode) {
// Operating on a selection in visual mode. We don't need a motion.
this.evalInput(cm, vim);
@ -1843,6 +1858,12 @@
var line = motionArgs.forward ? cur.line + repeat : cur.line - repeat;
var first = cm.firstLine();
var last = cm.lastLine();
var posV = cm.findPosV(cur, (motionArgs.forward ? repeat : -repeat), 'line', vim.lastHSPos);
var hasMarkedText = motionArgs.forward ? posV.line > line : posV.line < line;
if (hasMarkedText) {
line = posV.line;
endCh = posV.ch;
}
// Vim go to line begin or line end when cursor at first/last line and
// move to previous/next line is triggered.
if (line < first && cur.line == first){
@ -2098,9 +2119,9 @@
change: function(cm, args, ranges) {
var finalHead, text;
var vim = cm.state.vim;
var anchor = ranges[0].anchor,
head = ranges[0].head;
if (!vim.visualMode) {
var anchor = ranges[0].anchor,
head = ranges[0].head;
text = cm.getRange(anchor, head);
var lastState = vim.lastEditInputState || {};
if (lastState.motion == "moveByWords" && !isWhiteSpaceString(text)) {
@ -2128,6 +2149,13 @@
anchor.ch = Number.MAX_VALUE;
}
finalHead = anchor;
} else if (args.fullLine) {
head.ch = Number.MAX_VALUE;
head.line--;
cm.setSelection(anchor, head)
text = cm.getSelection();
cm.replaceSelection("");
finalHead = anchor;
} else {
text = cm.getSelection();
var replacement = fillArray('', ranges.length);
@ -2355,6 +2383,8 @@
var height = cm.listSelections().length;
if (insertAt == 'eol') {
head = Pos(head.line, lineLength(cm, head.line));
} else if (insertAt == 'bol') {
head = Pos(head.line, 0);
} else if (insertAt == 'charAfter') {
head = offsetCursor(head, 0, 1);
} else if (insertAt == 'firstNonBlank') {
@ -2393,6 +2423,8 @@
if (vim.visualMode){
return;
}
} else if (insertAt == 'lastEdit') {
head = getLastEditPos(cm) || head;
}
cm.setOption('disableInput', false);
if (actionArgs && actionArgs.replace) {
@ -2501,7 +2533,9 @@
var tmp = Pos(curStart.line + 1,
lineLength(cm, curStart.line + 1));
var text = cm.getRange(curStart, tmp);
text = text.replace(/\n\s*/g, ' ');
text = actionArgs.keepSpaces
? text.replace(/\n\r?/g, '')
: text.replace(/\n\s*/g, ' ');
cm.replaceRange(text, curStart, tmp);
}
var curFinalPos = Pos(curStart.line, finalCh);
@ -4318,25 +4352,25 @@
}
function getMarkPos(cm, vim, markName) {
if (markName == '\'') {
var history = cm.doc.history.done;
var event = history[history.length - 2];
return event && event.ranges && event.ranges[0].head;
if (markName == '\'' || markName == '`') {
return vimGlobalState.jumpList.find(cm, -1) || Pos(0, 0);
} else if (markName == '.') {
if (cm.doc.history.lastModTime == 0) {
return // If no changes, bail out; don't bother to copy or reverse history array.
} else {
var changeHistory = cm.doc.history.done.filter(function(el){ if (el.changes !== undefined) { return el } });
changeHistory.reverse();
var lastEditPos = changeHistory[0].changes[0].to;
}
return lastEditPos;
return getLastEditPos(cm);
}
var mark = vim.marks[markName];
return mark && mark.find();
}
function getLastEditPos(cm) {
var done = cm.doc.history.done;
for (var i = done.length; i--;) {
if (done[i].changes) {
return copyCursor(done[i].changes[0].to);
}
}
}
var ExCommandDispatcher = function() {
this.buildCommandMap_();
};

File diff suppressed because it is too large Load Diff

View File

@ -101,6 +101,9 @@ CodeMirror.defineMode("javascript", function(config, parserConfig) {
} else if (ch == "#") {
stream.skipToEnd();
return ret("error", "error");
} else if (ch == "<" && stream.match("!--") || ch == "-" && stream.match("->")) {
stream.skipToEnd()
return ret("comment", "comment")
} else if (isOperatorChar.test(ch)) {
if (ch != ">" || !state.lexical || state.lexical.type != ">") {
if (stream.eat("=")) {

42
vendor/codemirror/theme/ayu-dark.css vendored Normal file
View File

@ -0,0 +1,42 @@
/* Based on https://github.com/dempfi/ayu */
.cm-s-ayu-dark.CodeMirror { background: #0a0e14; color: #b3b1ad; }
.cm-s-ayu-dark div.CodeMirror-selected { background: #273747; }
.cm-s-ayu-dark .CodeMirror-line::selection, .cm-s-ayu-dark .CodeMirror-line > span::selection, .cm-s-ayu-dark .CodeMirror-line > span > span::selection { background: rgba(39, 55, 71, 99); }
.cm-s-ayu-dark .CodeMirror-line::-moz-selection, .cm-s-ayu-dark .CodeMirror-line > span::-moz-selection, .cm-s-ayu-dark .CodeMirror-line > span > span::-moz-selection { background: rgba(39, 55, 71, 99); }
.cm-s-ayu-dark .CodeMirror-gutters { background: #0a0e14; border-right: 0px; }
.cm-s-ayu-dark .CodeMirror-guttermarker { color: white; }
.cm-s-ayu-dark .CodeMirror-guttermarker-subtle { color: #3d424d; }
.cm-s-ayu-dark .CodeMirror-linenumber { color: #3d424d; }
.cm-s-ayu-dark .CodeMirror-cursor { border-left: 1px solid #e6b450; }
.cm-s-ayu-dark span.cm-comment { color: #626a73; }
.cm-s-ayu-dark span.cm-atom { color: #ae81ff; }
.cm-s-ayu-dark span.cm-number { color: #e6b450; }
.cm-s-ayu-dark span.cm-comment.cm-attribute { color: #ffb454; }
.cm-s-ayu-dark span.cm-comment.cm-def { color: rgba(57, 186, 230, 80); }
.cm-s-ayu-dark span.cm-comment.cm-tag { color: #39bae6; }
.cm-s-ayu-dark span.cm-comment.cm-type { color: #5998a6; }
.cm-s-ayu-dark span.cm-property, .cm-s-ayu-dark span.cm-attribute { color: #ffb454; }
.cm-s-ayu-dark span.cm-keyword { color: #ff8f40; }
.cm-s-ayu-dark span.cm-builtin { color: #e6b450; }
.cm-s-ayu-dark span.cm-string { color: #c2d94c; }
.cm-s-ayu-dark span.cm-variable { color: #b3b1ad; }
.cm-s-ayu-dark span.cm-variable-2 { color: #f07178; }
.cm-s-ayu-dark span.cm-variable-3 { color: #39bae6; }
.cm-s-ayu-dark span.cm-type { color: #ff8f40; }
.cm-s-ayu-dark span.cm-def { color: #ffee99; }
.cm-s-ayu-dark span.cm-bracket { color: #f8f8f2; }
.cm-s-ayu-dark span.cm-tag { color: rgba(57, 186, 230, 80); }
.cm-s-ayu-dark span.cm-header { color: #c2d94c; }
.cm-s-ayu-dark span.cm-link { color: #39bae6; }
.cm-s-ayu-dark span.cm-error { color: #ff3333; }
.cm-s-ayu-dark .CodeMirror-activeline-background { background: #01060e; }
.cm-s-ayu-dark .CodeMirror-matchingbracket {
text-decoration: underline;
color: white !important;
}

43
vendor/codemirror/theme/ayu-mirage.css vendored Normal file
View File

@ -0,0 +1,43 @@
/* Based on https://github.com/dempfi/ayu */
.cm-s-ayu-mirage.CodeMirror { background: #1f2430; color: #cbccc6; }
.cm-s-ayu-mirage div.CodeMirror-selected { background: #34455a; }
.cm-s-ayu-mirage .CodeMirror-line::selection, .cm-s-ayu-mirage .CodeMirror-line > span::selection, .cm-s-ayu-mirage .CodeMirror-line > span > span::selection { background: #34455a; }
.cm-s-ayu-mirage .CodeMirror-line::-moz-selection, .cm-s-ayu-mirage .CodeMirror-line > span::-moz-selection, .cm-s-ayu-mirage .CodeMirror-line > span > span::-moz-selection { background: rgba(25, 30, 42, 99); }
.cm-s-ayu-mirage .CodeMirror-gutters { background: #1f2430; border-right: 0px; }
.cm-s-ayu-mirage .CodeMirror-guttermarker { color: white; }
.cm-s-ayu-mirage .CodeMirror-guttermarker-subtle { color: rgba(112, 122, 140, 66); }
.cm-s-ayu-mirage .CodeMirror-linenumber { color: rgba(61, 66, 77, 99); }
.cm-s-ayu-mirage .CodeMirror-cursor { border-left: 1px solid #ffcc66; }
.cm-s-ayu-mirage span.cm-comment { color: #5c6773; font-style:italic; }
.cm-s-ayu-mirage span.cm-atom { color: #ae81ff; }
.cm-s-ayu-mirage span.cm-number { color: #ffcc66; }
.cm-s-ayu-mirage span.cm-comment.cm-attribute { color: #ffd580; }
.cm-s-ayu-mirage span.cm-comment.cm-def { color: #d4bfff; }
.cm-s-ayu-mirage span.cm-comment.cm-tag { color: #5ccfe6; }
.cm-s-ayu-mirage span.cm-comment.cm-type { color: #5998a6; }
.cm-s-ayu-mirage span.cm-property { color: #f29e74; }
.cm-s-ayu-mirage span.cm-attribute { color: #ffd580; }
.cm-s-ayu-mirage span.cm-keyword { color: #ffa759; }
.cm-s-ayu-mirage span.cm-builtin { color: #ffcc66; }
.cm-s-ayu-mirage span.cm-string { color: #bae67e; }
.cm-s-ayu-mirage span.cm-variable { color: #cbccc6; }
.cm-s-ayu-mirage span.cm-variable-2 { color: #f28779; }
.cm-s-ayu-mirage span.cm-variable-3 { color: #5ccfe6; }
.cm-s-ayu-mirage span.cm-type { color: #ffa759; }
.cm-s-ayu-mirage span.cm-def { color: #ffd580; }
.cm-s-ayu-mirage span.cm-bracket { color: rgba(92, 207, 230, 80); }
.cm-s-ayu-mirage span.cm-tag { color: #5ccfe6; }
.cm-s-ayu-mirage span.cm-header { color: #bae67e; }
.cm-s-ayu-mirage span.cm-link { color: #5ccfe6; }
.cm-s-ayu-mirage span.cm-error { color: #ff3333; }
.cm-s-ayu-mirage .CodeMirror-activeline-background { background: #191e2a; }
.cm-s-ayu-mirage .CodeMirror-matchingbracket {
text-decoration: underline;
color: white !important;
}

View File

@ -28,6 +28,8 @@
.cm-s-darcula span.cm-bracket { color: #A9B7C6; }
.cm-s-darcula span.cm-builtin { color: #FF9E59; }
.cm-s-darcula span.cm-special { color: #FF9E59; }
.cm-s-darcula span.cm-matchhighlight { color: #FFFFFF; background-color: rgba(50, 89, 48, .7); font-weight: normal;}
.cm-s-darcula span.cm-searching { color: #FFFFFF; background-color: rgba(61, 115, 59, .7); font-weight: normal;}
.cm-s-darcula .CodeMirror-cursor { border-left: 1px solid #A9B7C6; }
.cm-s-darcula .CodeMirror-activeline-background { background: #323232; }

View File

@ -0,0 +1,135 @@
/*
Name: material
Author: Mattia Astorino (http://github.com/equinusocio)
Website: https://material-theme.site/
*/
.cm-s-material-darker.CodeMirror {
background-color: #212121;
color: #EEFFFF;
}
.cm-s-material-darker .CodeMirror-gutters {
background: #212121;
color: #545454;
border: none;
}
.cm-s-material-darker .CodeMirror-guttermarker,
.cm-s-material-darker .CodeMirror-guttermarker-subtle,
.cm-s-material-darker .CodeMirror-linenumber {
color: #545454;
}
.cm-s-material-darker .CodeMirror-cursor {
border-left: 1px solid #FFCC00;
}
.cm-s-material-darker div.CodeMirror-selected {
background: rgba(97, 97, 97, 0.2);
}
.cm-s-material-darker.CodeMirror-focused div.CodeMirror-selected {
background: rgba(97, 97, 97, 0.2);
}
.cm-s-material-darker .CodeMirror-line::selection,
.cm-s-material-darker .CodeMirror-line>span::selection,
.cm-s-material-darker .CodeMirror-line>span>span::selection {
background: rgba(128, 203, 196, 0.2);
}
.cm-s-material-darker .CodeMirror-line::-moz-selection,
.cm-s-material-darker .CodeMirror-line>span::-moz-selection,
.cm-s-material-darker .CodeMirror-line>span>span::-moz-selection {
background: rgba(128, 203, 196, 0.2);
}
.cm-s-material-darker .CodeMirror-activeline-background {
background: rgba(0, 0, 0, 0.5);
}
.cm-s-material-darker .cm-keyword {
color: #C792EA;
}
.cm-s-material-darker .cm-operator {
color: #89DDFF;
}
.cm-s-material-darker .cm-variable-2 {
color: #EEFFFF;
}
.cm-s-material-darker .cm-variable-3,
.cm-s-material-darker .cm-type {
color: #f07178;
}
.cm-s-material-darker .cm-builtin {
color: #FFCB6B;
}
.cm-s-material-darker .cm-atom {
color: #F78C6C;
}
.cm-s-material-darker .cm-number {
color: #FF5370;
}
.cm-s-material-darker .cm-def {
color: #82AAFF;
}
.cm-s-material-darker .cm-string {
color: #C3E88D;
}
.cm-s-material-darker .cm-string-2 {
color: #f07178;
}
.cm-s-material-darker .cm-comment {
color: #545454;
}
.cm-s-material-darker .cm-variable {
color: #f07178;
}
.cm-s-material-darker .cm-tag {
color: #FF5370;
}
.cm-s-material-darker .cm-meta {
color: #FFCB6B;
}
.cm-s-material-darker .cm-attribute {
color: #C792EA;
}
.cm-s-material-darker .cm-property {
color: #C792EA;
}
.cm-s-material-darker .cm-qualifier {
color: #DECB6B;
}
.cm-s-material-darker .cm-variable-3,
.cm-s-material-darker .cm-type {
color: #DECB6B;
}
.cm-s-material-darker .cm-error {
color: rgba(255, 255, 255, 1.0);
background-color: #FF5370;
}
.cm-s-material-darker .CodeMirror-matchingbracket {
text-decoration: underline;
color: white !important;
}

View File

@ -0,0 +1,135 @@
/*
Name: material
Author: Mattia Astorino (http://github.com/equinusocio)
Website: https://material-theme.site/
*/
.cm-s-material-ocean.CodeMirror {
background-color: #0F111A;
color: #8F93A2;
}
.cm-s-material-ocean .CodeMirror-gutters {
background: #0F111A;
color: #464B5D;
border: none;
}
.cm-s-material-ocean .CodeMirror-guttermarker,
.cm-s-material-ocean .CodeMirror-guttermarker-subtle,
.cm-s-material-ocean .CodeMirror-linenumber {
color: #464B5D;
}
.cm-s-material-ocean .CodeMirror-cursor {
border-left: 1px solid #FFCC00;
}
.cm-s-material-ocean div.CodeMirror-selected {
background: rgba(113, 124, 180, 0.2);
}
.cm-s-material-ocean.CodeMirror-focused div.CodeMirror-selected {
background: rgba(113, 124, 180, 0.2);
}
.cm-s-material-ocean .CodeMirror-line::selection,
.cm-s-material-ocean .CodeMirror-line>span::selection,
.cm-s-material-ocean .CodeMirror-line>span>span::selection {
background: rgba(128, 203, 196, 0.2);
}
.cm-s-material-ocean .CodeMirror-line::-moz-selection,
.cm-s-material-ocean .CodeMirror-line>span::-moz-selection,
.cm-s-material-ocean .CodeMirror-line>span>span::-moz-selection {
background: rgba(128, 203, 196, 0.2);
}
.cm-s-material-ocean .CodeMirror-activeline-background {
background: rgba(0, 0, 0, 0.5);
}
.cm-s-material-ocean .cm-keyword {
color: #C792EA;
}
.cm-s-material-ocean .cm-operator {
color: #89DDFF;
}
.cm-s-material-ocean .cm-variable-2 {
color: #EEFFFF;
}
.cm-s-material-ocean .cm-variable-3,
.cm-s-material-ocean .cm-type {
color: #f07178;
}
.cm-s-material-ocean .cm-builtin {
color: #FFCB6B;
}
.cm-s-material-ocean .cm-atom {
color: #F78C6C;
}
.cm-s-material-ocean .cm-number {
color: #FF5370;
}
.cm-s-material-ocean .cm-def {
color: #82AAFF;
}
.cm-s-material-ocean .cm-string {
color: #C3E88D;
}
.cm-s-material-ocean .cm-string-2 {
color: #f07178;
}
.cm-s-material-ocean .cm-comment {
color: #464B5D;
}
.cm-s-material-ocean .cm-variable {
color: #f07178;
}
.cm-s-material-ocean .cm-tag {
color: #FF5370;
}
.cm-s-material-ocean .cm-meta {
color: #FFCB6B;
}
.cm-s-material-ocean .cm-attribute {
color: #C792EA;
}
.cm-s-material-ocean .cm-property {
color: #C792EA;
}
.cm-s-material-ocean .cm-qualifier {
color: #DECB6B;
}
.cm-s-material-ocean .cm-variable-3,
.cm-s-material-ocean .cm-type {
color: #DECB6B;
}
.cm-s-material-ocean .cm-error {
color: rgba(255, 255, 255, 1.0);
background-color: #FF5370;
}
.cm-s-material-ocean .CodeMirror-matchingbracket {
text-decoration: underline;
color: white !important;
}

View File

@ -0,0 +1,135 @@
/*
Name: material
Author: Mattia Astorino (http://github.com/equinusocio)
Website: https://material-theme.site/
*/
.cm-s-material-palenight.CodeMirror {
background-color: #292D3E;
color: #A6ACCD;
}
.cm-s-material-palenight .CodeMirror-gutters {
background: #292D3E;
color: #676E95;
border: none;
}
.cm-s-material-palenight .CodeMirror-guttermarker,
.cm-s-material-palenight .CodeMirror-guttermarker-subtle,
.cm-s-material-palenight .CodeMirror-linenumber {
color: #676E95;
}
.cm-s-material-palenight .CodeMirror-cursor {
border-left: 1px solid #FFCC00;
}
.cm-s-material-palenight div.CodeMirror-selected {
background: rgba(113, 124, 180, 0.2);
}
.cm-s-material-palenight.CodeMirror-focused div.CodeMirror-selected {
background: rgba(113, 124, 180, 0.2);
}
.cm-s-material-palenight .CodeMirror-line::selection,
.cm-s-material-palenight .CodeMirror-line>span::selection,
.cm-s-material-palenight .CodeMirror-line>span>span::selection {
background: rgba(128, 203, 196, 0.2);
}
.cm-s-material-palenight .CodeMirror-line::-moz-selection,
.cm-s-material-palenight .CodeMirror-line>span::-moz-selection,
.cm-s-material-palenight .CodeMirror-line>span>span::-moz-selection {
background: rgba(128, 203, 196, 0.2);
}
.cm-s-material-palenight .CodeMirror-activeline-background {
background: rgba(0, 0, 0, 0.5);
}
.cm-s-material-palenight .cm-keyword {
color: #C792EA;
}
.cm-s-material-palenight .cm-operator {
color: #89DDFF;
}
.cm-s-material-palenight .cm-variable-2 {
color: #EEFFFF;
}
.cm-s-material-palenight .cm-variable-3,
.cm-s-material-palenight .cm-type {
color: #f07178;
}
.cm-s-material-palenight .cm-builtin {
color: #FFCB6B;
}
.cm-s-material-palenight .cm-atom {
color: #F78C6C;
}
.cm-s-material-palenight .cm-number {
color: #FF5370;
}
.cm-s-material-palenight .cm-def {
color: #82AAFF;
}
.cm-s-material-palenight .cm-string {
color: #C3E88D;
}
.cm-s-material-palenight .cm-string-2 {
color: #f07178;
}
.cm-s-material-palenight .cm-comment {
color: #676E95;
}
.cm-s-material-palenight .cm-variable {
color: #f07178;
}
.cm-s-material-palenight .cm-tag {
color: #FF5370;
}
.cm-s-material-palenight .cm-meta {
color: #FFCB6B;
}
.cm-s-material-palenight .cm-attribute {
color: #C792EA;
}
.cm-s-material-palenight .cm-property {
color: #C792EA;
}
.cm-s-material-palenight .cm-qualifier {
color: #DECB6B;
}
.cm-s-material-palenight .cm-variable-3,
.cm-s-material-palenight .cm-type {
color: #DECB6B;
}
.cm-s-material-palenight .cm-error {
color: rgba(255, 255, 255, 1.0);
background-color: #FF5370;
}
.cm-s-material-palenight .CodeMirror-matchingbracket {
text-decoration: underline;
color: white !important;
}

View File

@ -1,52 +1,134 @@
/*
Name: material
Author: Michael Kaminsky (http://github.com/mkaminsky11)
Original material color scheme by Mattia Astorino (https://github.com/equinusocio/material-theme)
Name: material
Author: Mattia Astorino (http://github.com/equinusocio)
Website: https://material-theme.site/
*/
.cm-s-material.CodeMirror {
background-color: #263238;
color: rgba(233, 237, 237, 1);
color: #EEFFFF;
}
.cm-s-material .CodeMirror-gutters {
background: #263238;
color: rgb(83,127,126);
color: #546E7A;
border: none;
}
.cm-s-material .CodeMirror-guttermarker, .cm-s-material .CodeMirror-guttermarker-subtle, .cm-s-material .CodeMirror-linenumber { color: rgb(83,127,126); }
.cm-s-material .CodeMirror-cursor { border-left: 1px solid #f8f8f0; }
.cm-s-material div.CodeMirror-selected { background: rgba(255, 255, 255, 0.15); }
.cm-s-material.CodeMirror-focused div.CodeMirror-selected { background: rgba(255, 255, 255, 0.10); }
.cm-s-material .CodeMirror-line::selection, .cm-s-material .CodeMirror-line > span::selection, .cm-s-material .CodeMirror-line > span > span::selection { background: rgba(255, 255, 255, 0.10); }
.cm-s-material .CodeMirror-line::-moz-selection, .cm-s-material .CodeMirror-line > span::-moz-selection, .cm-s-material .CodeMirror-line > span > span::-moz-selection { background: rgba(255, 255, 255, 0.10); }
.cm-s-material .CodeMirror-activeline-background { background: rgba(0, 0, 0, 0); }
.cm-s-material .cm-keyword { color: rgba(199, 146, 234, 1); }
.cm-s-material .cm-operator { color: rgba(233, 237, 237, 1); }
.cm-s-material .cm-variable-2 { color: #80CBC4; }
.cm-s-material .cm-variable-3, .cm-s-material .cm-type { color: #82B1FF; }
.cm-s-material .cm-builtin { color: #DECB6B; }
.cm-s-material .cm-atom { color: #F77669; }
.cm-s-material .cm-number { color: #F77669; }
.cm-s-material .cm-def { color: rgba(233, 237, 237, 1); }
.cm-s-material .cm-string { color: #C3E88D; }
.cm-s-material .cm-string-2 { color: #80CBC4; }
.cm-s-material .cm-comment { color: #546E7A; }
.cm-s-material .cm-variable { color: #82B1FF; }
.cm-s-material .cm-tag { color: #80CBC4; }
.cm-s-material .cm-meta { color: #80CBC4; }
.cm-s-material .cm-attribute { color: #FFCB6B; }
.cm-s-material .cm-property { color: #80CBAE; }
.cm-s-material .cm-qualifier { color: #DECB6B; }
.cm-s-material .cm-variable-3, .cm-s-material .cm-type { color: #DECB6B; }
.cm-s-material .cm-tag { color: rgba(255, 83, 112, 1); }
.cm-s-material .CodeMirror-guttermarker,
.cm-s-material .CodeMirror-guttermarker-subtle,
.cm-s-material .CodeMirror-linenumber {
color: #546E7A;
}
.cm-s-material .CodeMirror-cursor {
border-left: 1px solid #FFCC00;
}
.cm-s-material div.CodeMirror-selected {
background: rgba(128, 203, 196, 0.2);
}
.cm-s-material.CodeMirror-focused div.CodeMirror-selected {
background: rgba(128, 203, 196, 0.2);
}
.cm-s-material .CodeMirror-line::selection,
.cm-s-material .CodeMirror-line>span::selection,
.cm-s-material .CodeMirror-line>span>span::selection {
background: rgba(128, 203, 196, 0.2);
}
.cm-s-material .CodeMirror-line::-moz-selection,
.cm-s-material .CodeMirror-line>span::-moz-selection,
.cm-s-material .CodeMirror-line>span>span::-moz-selection {
background: rgba(128, 203, 196, 0.2);
}
.cm-s-material .CodeMirror-activeline-background {
background: rgba(0, 0, 0, 0.5);
}
.cm-s-material .cm-keyword {
color: #C792EA;
}
.cm-s-material .cm-operator {
color: #89DDFF;
}
.cm-s-material .cm-variable-2 {
color: #EEFFFF;
}
.cm-s-material .cm-variable-3,
.cm-s-material .cm-type {
color: #f07178;
}
.cm-s-material .cm-builtin {
color: #FFCB6B;
}
.cm-s-material .cm-atom {
color: #F78C6C;
}
.cm-s-material .cm-number {
color: #FF5370;
}
.cm-s-material .cm-def {
color: #82AAFF;
}
.cm-s-material .cm-string {
color: #C3E88D;
}
.cm-s-material .cm-string-2 {
color: #f07178;
}
.cm-s-material .cm-comment {
color: #546E7A;
}
.cm-s-material .cm-variable {
color: #f07178;
}
.cm-s-material .cm-tag {
color: #FF5370;
}
.cm-s-material .cm-meta {
color: #FFCB6B;
}
.cm-s-material .cm-attribute {
color: #C792EA;
}
.cm-s-material .cm-property {
color: #C792EA;
}
.cm-s-material .cm-qualifier {
color: #DECB6B;
}
.cm-s-material .cm-variable-3,
.cm-s-material .cm-type {
color: #DECB6B;
}
.cm-s-material .cm-error {
color: rgba(255, 255, 255, 1.0);
background-color: #EC5F67;
background-color: #FF5370;
}
.cm-s-material .CodeMirror-matchingbracket {
text-decoration: underline;
color: white !important;

143
vendor/codemirror/theme/moxer.css vendored Normal file
View File

@ -0,0 +1,143 @@
/*
Name: Moxer Theme
Author: Mattia Astorino (http://github.com/equinusocio)
Website: https://github.com/moxer-theme/moxer-code
*/
.cm-s-moxer.CodeMirror {
background-color: #090A0F;
color: #8E95B4;
line-height: 1.8;
}
.cm-s-moxer .CodeMirror-gutters {
background: #090A0F;
color: #35394B;
border: none;
}
.cm-s-moxer .CodeMirror-guttermarker,
.cm-s-moxer .CodeMirror-guttermarker-subtle,
.cm-s-moxer .CodeMirror-linenumber {
color: #35394B;
}
.cm-s-moxer .CodeMirror-cursor {
border-left: 1px solid #FFCC00;
}
.cm-s-moxer div.CodeMirror-selected {
background: rgba(128, 203, 196, 0.2);
}
.cm-s-moxer.CodeMirror-focused div.CodeMirror-selected {
background: #212431;
}
.cm-s-moxer .CodeMirror-line::selection,
.cm-s-moxer .CodeMirror-line>span::selection,
.cm-s-moxer .CodeMirror-line>span>span::selection {
background: #212431;
}
.cm-s-moxer .CodeMirror-line::-moz-selection,
.cm-s-moxer .CodeMirror-line>span::-moz-selection,
.cm-s-moxer .CodeMirror-line>span>span::-moz-selection {
background: #212431;
}
.cm-s-moxer .CodeMirror-activeline-background,
.cm-s-moxer .CodeMirror-activeline-gutter .CodeMirror-linenumber {
background: rgba(33, 36, 49, 0.5);
}
.cm-s-moxer .cm-keyword {
color: #D46C6C;
}
.cm-s-moxer .cm-operator {
color: #D46C6C;
}
.cm-s-moxer .cm-variable-2 {
color: #81C5DA;
}
.cm-s-moxer .cm-variable-3,
.cm-s-moxer .cm-type {
color: #f07178;
}
.cm-s-moxer .cm-builtin {
color: #FFCB6B;
}
.cm-s-moxer .cm-atom {
color: #A99BE2;
}
.cm-s-moxer .cm-number {
color: #7CA4C0;
}
.cm-s-moxer .cm-def {
color: #F5DFA5;
}
.cm-s-moxer .CodeMirror-line .cm-def ~ .cm-def {
color: #81C5DA;
}
.cm-s-moxer .cm-string {
color: #B2E4AE;
}
.cm-s-moxer .cm-string-2 {
color: #f07178;
}
.cm-s-moxer .cm-comment {
color: #3F445A;
}
.cm-s-moxer .cm-variable {
color: #8E95B4;
}
.cm-s-moxer .cm-tag {
color: #FF5370;
}
.cm-s-moxer .cm-meta {
color: #FFCB6B;
}
.cm-s-moxer .cm-attribute {
color: #C792EA;
}
.cm-s-moxer .cm-property {
color: #81C5DA;
}
.cm-s-moxer .cm-qualifier {
color: #DECB6B;
}
.cm-s-moxer .cm-variable-3,
.cm-s-moxer .cm-type {
color: #DECB6B;
}
.cm-s-moxer .cm-error {
color: rgba(255, 255, 255, 1.0);
background-color: #FF5370;
}
.cm-s-moxer .CodeMirror-matchingbracket {
text-decoration: underline;
color: white !important;
}

View File

@ -1,9 +1,5 @@
## db-to-cloud v0.4.5
Installed via npm - source code:
Following files are copied from npm (node_modules):
https://github.com/eight04/db-to-cloud/tree/v0.4.5
Bundled code:
https://unpkg.com/db-to-cloud@0.4.5/dist/db-to-cloud.min.js
* db-to-cloud.min.js: dist\db-to-cloud.min.js

View File

@ -1,20 +0,0 @@
Copyright (c) 2016 Dropbox Inc., http://www.dropbox.com/
Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
"Software"), to deal in the Software without restriction, including
without limitation the rights to use, copy, modify, merge, publish,
distribute, sublicense, and/or sell copies of the Software, and to
permit persons to whom the Software is furnished to do so, subject to
the following conditions:
The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

Some files were not shown because too many files have changed in this diff Show More