diff --git a/.eslintrc b/.eslintrc.yml similarity index 96% rename from .eslintrc rename to .eslintrc.yml index 20603487..87af0074 100644 --- a/.eslintrc +++ b/.eslintrc.yml @@ -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 diff --git a/.github/CONTRIBUTING.md b/.github/CONTRIBUTING.md index 56afb80b..205df7b2 100644 --- a/.github/CONTRIBUTING.md +++ b/.github/CONTRIBUTING.md @@ -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 diff --git a/.gitignore b/.gitignore index 657469e8..b05c94c7 100644 --- a/.gitignore +++ b/.gitignore @@ -2,7 +2,6 @@ pull_locales_login.rb .vscode node_modules/ -package-lock.json yarn.lock *.zip .eslintcache diff --git a/BUILD.md b/BUILD.md new file mode 100644 index 00000000..e4028448 --- /dev/null +++ b/BUILD.md @@ -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). + + + +## 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. diff --git a/_locales/bg/messages.json b/_locales/bg/messages.json index b73b4418..0e02bbf8 100644 --- a/_locales/bg/messages.json +++ b/_locales/bg/messages.json @@ -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." diff --git a/_locales/bg_BG/messages.json b/_locales/bg_BG/messages.json index 1c50a3da..a52e967d 100644 --- a/_locales/bg_BG/messages.json +++ b/_locales/bg_BG/messages.json @@ -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" diff --git a/_locales/cs/messages.json b/_locales/cs/messages.json index 965357bc..51c2f253 100644 --- a/_locales/cs/messages.json +++ b/_locales/cs/messages.json @@ -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." diff --git a/_locales/de/messages.json b/_locales/de/messages.json index f63daada..594eaa5a 100644 --- a/_locales/de/messages.json +++ b/_locales/de/messages.json @@ -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": "" diff --git a/_locales/en/messages.json b/_locales/en/messages.json index 8a2ab7da..33a7ddc0 100644 --- a/_locales/en/messages.json +++ b/_locales/en/messages.json @@ -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" }, diff --git a/_locales/en_GB/messages.json b/_locales/en_GB/messages.json index d3aaf68e..e6a09217 100644 --- a/_locales/en_GB/messages.json +++ b/_locales/en_GB/messages.json @@ -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" diff --git a/_locales/es/messages.json b/_locales/es/messages.json index d4e09bd4..ffb6ba7a 100644 --- a/_locales/es/messages.json +++ b/_locales/es/messages.json @@ -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." diff --git a/_locales/et/messages.json b/_locales/et/messages.json index 7c103184..e2568512 100644 --- a/_locales/et/messages.json +++ b/_locales/et/messages.json @@ -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": "" diff --git a/_locales/fr/messages.json b/_locales/fr/messages.json index 02e37c70..674a662b 100644 --- a/_locales/fr/messages.json +++ b/_locales/fr/messages.json @@ -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 l’onglet 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 « s’applique à »", "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": "" diff --git a/_locales/he/messages.json b/_locales/he/messages.json index 6c2ca738..22266d83 100644 --- a/_locales/he/messages.json +++ b/_locales/he/messages.json @@ -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 תואם חלקית בניגוד למפרט CSS4 @document המצריך התאמה מלאה של כתובת אתר. קטעי ה־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": "" } } \ No newline at end of file diff --git a/_locales/hu/messages.json b/_locales/hu/messages.json index 2e6ebc16..1ab582b4 100644 --- a/_locales/hu/messages.json +++ b/_locales/hu/messages.json @@ -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": "" } } \ No newline at end of file diff --git a/_locales/it/messages.json b/_locales/it/messages.json index b9955937..1ccff03a 100644 --- a/_locales/it/messages.json +++ b/_locales/it/messages.json @@ -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." diff --git a/_locales/ja/messages.json b/_locales/ja/messages.json index 72b33c2b..2f98bc39 100644 --- a/_locales/ja/messages.json +++ b/_locales/ja/messages.json @@ -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": "" diff --git a/_locales/nl/messages.json b/_locales/nl/messages.json index c1eaa18c..6c97d286 100644 --- a/_locales/nl/messages.json +++ b/_locales/nl/messages.json @@ -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": "" diff --git a/_locales/pl/messages.json b/_locales/pl/messages.json index 5710a931..cef4e512 100644 --- a/_locales/pl/messages.json +++ b/_locales/pl/messages.json @@ -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": "" diff --git a/_locales/pt_BR/messages.json b/_locales/pt_BR/messages.json index 04e0de3b..ba33f129 100644 --- a/_locales/pt_BR/messages.json +++ b/_locales/pt_BR/messages.json @@ -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": "" diff --git a/_locales/pt_PT/messages.json b/_locales/pt_PT/messages.json index 23bb78a3..a54ba31e 100644 --- a/_locales/pt_PT/messages.json +++ b/_locales/pt_PT/messages.json @@ -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." diff --git a/_locales/ro/messages.json b/_locales/ro/messages.json index f5773c7d..0a34d235 100644 --- a/_locales/ro/messages.json +++ b/_locales/ro/messages.json @@ -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." diff --git a/_locales/ru/messages.json b/_locales/ru/messages.json index 83283648..74addc6f 100644 --- a/_locales/ru/messages.json +++ b/_locales/ru/messages.json @@ -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." diff --git a/_locales/sv/messages.json b/_locales/sv/messages.json index c77341df..91ff2c39 100644 --- a/_locales/sv/messages.json +++ b/_locales/sv/messages.json @@ -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" diff --git a/_locales/tr/messages.json b/_locales/tr/messages.json index dfdb9734..67c60856 100644 --- a/_locales/tr/messages.json +++ b/_locales/tr/messages.json @@ -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": "" } } \ No newline at end of file diff --git a/_locales/zh_CN/messages.json b/_locales/zh_CN/messages.json index 9c5256e6..6dc328cd 100644 --- a/_locales/zh_CN/messages.json +++ b/_locales/zh_CN/messages.json @@ -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." diff --git a/_locales/zh_TW/messages.json b/_locales/zh_TW/messages.json index b3895479..f98b154a 100644 --- a/_locales/zh_TW/messages.json +++ b/_locales/zh_TW/messages.json @@ -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": "" diff --git a/background/background.js b/background/background.js index 3186b3cc..248bcfda 100644 --- a/background/background.js +++ b/background/background.js @@ -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}); + }); + }); } diff --git a/background/content-scripts.js b/background/content-scripts.js index 1cfecc19..4be73294 100644 --- a/background/content-scripts.js +++ b/background/content-scripts.js @@ -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); + } })(); diff --git a/background/db-chrome-storage.js b/background/db-chrome-storage.js new file mode 100644 index 00000000..46cfadb1 --- /dev/null +++ b/background/db-chrome-storage.js @@ -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; + } + } + } + }); + } +} diff --git a/background/db.js b/background/db.js index 46fd0c9b..2549a3ce 100644 --- a/background/db.js +++ b/background/db.js @@ -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; - }); - } - } })(); diff --git a/background/icon-manager.js b/background/icon-manager.js new file mode 100644 index 00000000..7319b32e --- /dev/null +++ b/background/icon-manager.js @@ -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(); + } +})(); diff --git a/background/style-manager.js b/background/style-manager.js index ec7c559d..cd221738 100644 --- a/background/style-manager.js +++ b/background/style-manager.js @@ -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() }; diff --git a/background/style-via-api.js b/background/style-via-api.js index 79f5c289..6793c65c 100644 --- a/background/style-via-api.js +++ b/background/style-via-api.js @@ -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}) { diff --git a/background/tab-manager.js b/background/tab-manager.js new file mode 100644 index 00000000..49061fcf --- /dev/null +++ b/background/tab-manager.js @@ -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(); + }, + }; +})(); diff --git a/background/token-manager.js b/background/token-manager.js index 9c21f85a..0b0fd3e0 100644 --- a/background/token-manager.js +++ b/background/token-manager.js @@ -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'] } }; diff --git a/background/usercss-helper.js b/background/usercss-helper.js index 546f1172..ea083ae9 100644 --- a/background/usercss-helper.js +++ b/background/usercss-helper.js @@ -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); - }); - } })(); diff --git a/content/apply.js b/content/apply.js index b2422d84..6407de16 100644 --- a/content/apply.js +++ b/content/apply.js @@ -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 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; - } - } })(); diff --git a/content/install-hook-usercss.js b/content/install-hook-usercss.js index f52d5a0f..80e837ea 100644 --- a/content/install-hook-usercss.js +++ b/content/install-hook-usercss.js @@ -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 diff --git a/content/style-injector.js b/content/style-injector.js index 5024d944..96755c73 100644 --- a/content/style-injector.js +++ b/content/style-injector.js @@ -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}); + } + } +}; diff --git a/edit/codemirror-themes.js b/edit/codemirror-themes.js index 433a185c..d9e25d19 100644 --- a/edit/codemirror-themes.js +++ b/edit/codemirror-themes.js @@ -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', diff --git a/edit/edit.css b/edit/edit.css index 6e8c1dca..7b8d4bc5 100644 --- a/edit/edit.css +++ b/edit/edit.css @@ -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; diff --git a/edit/global-search.js b/edit/global-search.js index d6f18d13..349b8ac1 100644 --- a/edit/global-search.js +++ b/edit/global-search.js @@ -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++; diff --git a/edit/regexp-tester.js b/edit/regexp-tester.js index 348b5cb7..590714d3 100644 --- a/edit/regexp-tester.js +++ b/edit/regexp-tester.js @@ -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()]; diff --git a/install-usercss.html b/install-usercss.html index 6920bcce..6824d571 100644 --- a/install-usercss.html +++ b/install-usercss.html @@ -58,19 +58,17 @@

- +
- + @@ -382,7 +377,7 @@
@@ -394,7 +389,7 @@ diff --git a/manage/filters.js b/manage/filters.js index b09c3193..11e11feb 100644 --- a/manage/filters.js +++ b/manage/filters.js @@ -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) { diff --git a/manage/import-export.js b/manage/import-export.js index 327314c0..18abd480 100644 --- a/manage/import-export.js +++ b/manage/import-export.js @@ -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); }); diff --git a/manage/manage.css b/manage/manage.css index 25d9e2d8..74375b25 100644 --- a/manage/manage.css +++ b/manage/manage.css @@ -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; +} diff --git a/manage/manage.js b/manage/manage.js index 741fc9bc..ddafe0fd 100644 --- a/manage/manage.js +++ b/manage/manage.js @@ -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(''); +}); diff --git a/manifest.json b/manifest.json index 114d4788..a701014c 100644 --- a/manifest.json +++ b/manifest.json @@ -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}", diff --git a/options.html b/options.html index ec8a217c..c381eb3c 100644 --- a/options.html +++ b/options.html @@ -21,8 +21,8 @@ - + @@ -33,6 +33,13 @@ + +
+
+
+ Stylus
+
+
@@ -224,7 +231,7 @@
- +
+
@@ -243,7 +244,7 @@