Compare commits

..

1 Commits

Author SHA1 Message Date
eight04
6e1bc133f0 WIP: refactor editor CSS 2021-12-10 04:44:44 +08:00
259 changed files with 21359 additions and 19540 deletions

View File

@ -24,9 +24,6 @@ If not, then provide details describing which page the feature will effect, e.g.
## Adding translations
You can help us translate the extension on [Transifex](https://www.transifex.com/github-7/Stylus).
Only the languages supported by the web store are allowed:
https://developer.chrome.com/docs/webstore/i18n/#localeTable
## Pull requests

8
.gitignore vendored
View File

@ -1,8 +1,8 @@
*.zip
.DS_Store
.eslintcache
.transifexrc
pull_locales_login.rb
.vscode
desktop.ini
node_modules/
yarn.lock
*.zip
.eslintcache
.transifexrc

View File

@ -50,7 +50,7 @@ Copyright © 2005-2014 [Jason Barnabe](jason.barnabe@gmail.com)
Current Stylus:
Copyright © 2017-2022 [Stylus Team](https://github.com/openstyles/stylus/graphs/contributors)
Copyright © 2017-2019 [Stylus Team](https://github.com/openstyles/stylus/graphs/contributors)
**[GNU GPLv3](./LICENSE)**

View File

@ -58,12 +58,6 @@
"checkingForUpdate": {
"message": "جارٍ البحث..."
},
"confirmDelete": {
"message": "حذف"
},
"confirmSave": {
"message": "حفظ"
},
"deleteStyleConfirm": {
"message": "هل تريد بالتأكيد حذف هذا النمط؟"
},
@ -76,9 +70,6 @@
"disableStyleLabel": {
"message": "تعطيل"
},
"editDeleteText": {
"message": "حذف"
},
"editStyleHeading": {
"message": "تعديل النمط"
},
@ -96,11 +87,8 @@
"enableStyleLabel": {
"message": "تمكين"
},
"genericAdd": {
"message": "إضافة"
},
"genericEnabledLabel": {
"message": "ممكّن"
"findStylesForSite": {
"message": "البحث عن المزيد من الأنماط لموقع الويب هذا"
},
"helpAlt": {
"message": "مساعدة"
@ -117,9 +105,6 @@
"openManage": {
"message": "إدارة الأنماط المثبتة"
},
"optionsSyncUrl": {
"message": "عنوان URL"
},
"sectionAdd": {
"message": "إضافة قسم آخر"
},
@ -129,9 +114,6 @@
"sectionRemove": {
"message": "إزالة القسم"
},
"sections": {
"message": "الأقسام"
},
"styleCancelEditLabel": {
"message": "رجوع للإدارة"
},

View File

@ -52,6 +52,9 @@
"backupButtons": {
"message": "Резервни копия"
},
"backupMessage": {
"message": "Изберете файл или го влачете до страницата."
},
"bckpInstStyles": {
"message": "Изнасяне на стилове"
},
@ -112,9 +115,6 @@
"confirmOK": {
"message": "Добре"
},
"confirmSave": {
"message": "Запазване"
},
"confirmStop": {
"message": "Спиране"
},
@ -171,15 +171,12 @@
"exportLabel": {
"message": "Изнасяне"
},
"genericAdd": {
"message": "Добавяне"
"findStylesForSite": {
"message": "Още стилове за този сайт"
},
"genericDisabledLabel": {
"message": "Изключено"
},
"genericEnabledLabel": {
"message": "Включено"
},
"genericHistoryLabel": {
"message": "Хронология"
},
@ -263,6 +260,9 @@
"manageFaviconsGray": {
"message": "Сиви"
},
"manageFaviconsHelp": {
"message": "Разширението използва външна услуга https://www.google.com/s2/favicons"
},
"manageFilters": {
"message": "Филтри"
},
@ -299,9 +299,6 @@
"openManage": {
"message": "Управление"
},
"openOptions": {
"message": "Настройки"
},
"openStylesManager": {
"message": "Управление на стиловете"
},
@ -368,9 +365,6 @@
"optionsSubheading": {
"message": "Още настройки"
},
"optionsSyncUrl": {
"message": "Адрес"
},
"optionsUpdateImportNote": {
"message": "При внасянето на резервни копия от стари версии или от Стайлиш направете ръчна проверка за обновления, за да сте сигурни, че стиловете са актуални."
},
@ -407,9 +401,6 @@
"sectionRemove": {
"message": "Премахване на отдела"
},
"sections": {
"message": "Отдели"
},
"shortcuts": {
"message": "Клавишни комбинации"
},
@ -463,6 +454,9 @@
"styleRegexpProblemTooltip": {
"message": "Брой на неприложените отдели поради неправилно използване на регулярни изрази"
},
"styleRegexpTestButton": {
"message": "Тест на регулярния израз"
},
"styleRegexpTestFull": {
"message": "Съвпадащи подпрозорци"
},

View File

@ -67,6 +67,9 @@
"backupButtons": {
"message": "Zálohovat"
},
"backupMessage": {
"message": "Vyberte soubor nebo ho přetáhněte na tuto stránku."
},
"bckpInstStyles": {
"message": "Exportovat styly"
},
@ -272,6 +275,15 @@
"findStyles": {
"message": "Najít styly"
},
"findStylesForSite": {
"message": "Najít styly pro tento web"
},
"findStylesInline": {
"message": "Zobrazit zde"
},
"findStylesInlineTooltip": {
"message": "Zobrazit výsledky vyhledávání v tomto okně."
},
"genericAdd": {
"message": "Přidat"
},
@ -461,7 +473,7 @@
"message": "Zešednutí"
},
"manageFaviconsHelp": {
"message": "Stylus používá externí službu https://icons.duckduckgo.com"
"message": "Stylus používá externí službu https://www.google.com/s2/favicons"
},
"manageFilters": {
"message": "Filtry"
@ -505,9 +517,6 @@
"menuShowBadge": {
"message": "Zobrazit počet aktivních stylů"
},
"meta_invalidCheckboxDefault": {
"message": "Neplatný @var checkbox: hodnota musí být 0 nebo 1"
},
"meta_invalidNumber": {
"message": "Očekáváno číslo"
},
@ -553,9 +562,6 @@
"openManage": {
"message": "Spravovat"
},
"openOptions": {
"message": "Možnosti"
},
"openStylesManager": {
"message": "Otevřít správce stylů"
},
@ -625,9 +631,6 @@
"optionsSubheading": {
"message": "Další možnosti"
},
"optionsSyncUrl": {
"message": "URL adresa"
},
"optionsUpdateImportNote": {
"message": "Importujete-li zálohy stylů ze starší verze nebo z rozšíření Stylish, proveďte jednorázovou ruční kontrolu aktualizací ve správci stylů, aby byly všechny styly aktuální."
},
@ -742,9 +745,6 @@
"sectionRestore": {
"message": "Obnovit odstraněnou sekci"
},
"sections": {
"message": "Sekce"
},
"shortcuts": {
"message": "Zkratky"
},
@ -844,6 +844,9 @@
"styleRegexpProblemTooltip": {
"message": "Počet sekcí nepoužitých kvůli nesprávnému použití „regexp()“"
},
"styleRegexpTestButton": {
"message": "Otestovat RegExp"
},
"styleRegexpTestFull": {
"message": "Odpovídající listy"
},
@ -912,9 +915,6 @@
"unreachableFileHint": {
"message": "Stylus může přistupovat k file:// URL pouze při povolení odpovídající možnosti pro rozšíření Stylus ve správci chrome://extensions."
},
"unreachableMozSiteHintOldFF": {
"message": "Pouze Firefox 59 a novější může být nakonfigurován tak, aby rozšíření typu WebExtensions mohla přidávat styly na stránky chráněné CSP (Content Security Policy) jako je tato."
},
"unzipStyles": {
"message": "Rozbalování stylů…"
},
@ -974,6 +974,9 @@
"usercssReplaceTemplateConfirmation": {
"message": "Nahradit výchozí šablonu pro nové Usercss styly aktuálním kódem?"
},
"usercssReplaceTemplateName": {
"message": "Prázdné @name nahrazuje výchozí šablonu"
},
"usercssReplaceTemplateSectionBody": {
"message": "Sem vložte kód…"
},

View File

@ -58,6 +58,9 @@
"author": {
"message": "Forfatter"
},
"backupMessage": {
"message": "Vælg en fil eller træk og slip til denne side."
},
"bckpInstStyles": {
"message": "Eksportér stil"
},
@ -93,8 +96,5 @@
},
"cm_keyMap": {
"message": "Tastegenveje"
},
"genericAdd": {
"message": "Tilføj"
}
}

View File

@ -68,7 +68,7 @@
"message": "Datensicherung"
},
"backupMessage": {
"message": "Um die Backupdatei zu importieren, ziehe sie in diese Seite oder klicke auf die Import-Schaltfläche.\n\nZum Exportieren einer mit Stylus 1.5.18 (und älter) kompatiblen Backupdatei, rechtsklicke oder Shift-klicke auf die Export-Schaltfläche."
"message": "Wähle eine Datei aus oder ziehe die Datei auf diese Seite. (Drag and Drop)"
},
"bckpInstStyles": {
"message": "Styles exportieren"
@ -223,23 +223,9 @@
"disableAllStyles": {
"message": "Alle Styles deaktivieren"
},
"disableAllStylesOff": {
"message": "Styles sind ausgeschaltet"
},
"disableStyleLabel": {
"message": "Deaktivieren"
},
"draftAction": {
"message": "Wähle \"Ja\", um diesen Entwurf zu laden oder \"Nein\", um ihn zu verwerfen."
},
"draftTitle": {
"message": "Wiederherstellung ungespeicherter Entwürfe, erstellt vor $date$",
"placeholders": {
"date": {
"content": "$1"
}
}
},
"dragDropMessage": {
"message": "Ziehe die Backup Datei zum importieren an irgendeinen Ort auf dieser Seite."
},
@ -266,9 +252,6 @@
}
}
},
"editorSettings": {
"message": "Editor Einstellungen"
},
"enableStyleLabel": {
"message": "Aktivieren"
},
@ -278,9 +261,6 @@
"excludeStyleByUrlLabel": {
"message": "Aktuelle URL ausschließen"
},
"exportCompatible": {
"message": "Exportieren (Kompatibilitätsmodus)"
},
"exportLabel": {
"message": "Exportieren"
},
@ -313,6 +293,15 @@
"findStyles": {
"message": "Styles finden"
},
"findStylesForSite": {
"message": "Weitere Styles für diese Seite finden"
},
"findStylesInline": {
"message": "Ergebnisse hier anzeigen"
},
"findStylesInlineTooltip": {
"message": "Suchergebnisse in diesem Fenster anzeigen."
},
"genericAdd": {
"message": "Hinzufügen"
},
@ -346,9 +335,6 @@
"genericSavedMessage": {
"message": "Gespeichert"
},
"genericSize": {
"message": "Größe"
},
"genericTitle": {
"message": "Name"
},
@ -358,9 +344,6 @@
"gettingStyles": {
"message": "Empfange alle Styles..."
},
"headerResizerHint": {
"message": "Halte Shift gedrückt, um nur diese Art der Benutzeroberfläche (z.B. Editor, Manager, Installer) zu verändern"
},
"helpAlt": {
"message": "Hilfe"
},
@ -535,7 +518,7 @@
"message": "Ausgegraut"
},
"manageFaviconsHelp": {
"message": "Stylus nutzt hierzu den externen Dienst https://icons.duckduckgo.com"
"message": "Stylus nutzt hierzu den externen Dienst https://www.google.com/s2/favicons"
},
"manageFilters": {
"message": "Filter"
@ -546,9 +529,6 @@
"manageMaxTargets": {
"message": "Anzahl der \"Gilt für\" Elemente"
},
"manageMinColumnWidth": {
"message": "Minimale Spaltenbreite (in Pixeln. 9999 deaktiviert den Mehrspalten-Modus)"
},
"manageNewStyleAsUsercss": {
"message": "als UserCSS"
},
@ -798,15 +778,6 @@
"optionsAdvanced": {
"message": "Erweitert"
},
"optionsAdvancedAutoSwitchSchemeBySystem": {
"message": "Nach Systemeinstellung"
},
"optionsAdvancedAutoSwitchSchemeByTime": {
"message": "Bei Nacht:"
},
"optionsAdvancedAutoSwitchSchemeNever": {
"message": "Deaktiviert. Die hell / dunkel Einstellung in Styles wird ignoriert."
},
"optionsAdvancedContextDelete": {
"message": "\"Löschen\" im Editor-Kontextmenü hinzufügen"
},
@ -816,17 +787,11 @@
"optionsAdvancedExposeIframesNote": {
"message": "Style wirkt sich auch auf iframes der anvisierten (obersten) Domain aus.\nIframe-spezifisches CSS ist dann wie folgt möglich:\nhtml[stylus-iframe$$=\"twitter.com\"] h1 { display:none }"
},
"optionsAdvancedExposeStyleName": {
"message": "Stylename anzeigen"
},
"optionsAdvancedExposeStyleNameNote": {
"message": "Zeigt den Stylenamen auf der Seite an, um das Debuggen von Styles in den Devtools zu erleichtern. Bitte lade die Tabs neu, um die neue Einstellung zu übernehmen."
},
"optionsAdvancedNewStyleAsUsercss": {
"message": "Schreibe neuen Style als UserCSS"
},
"optionsAdvancedPatchCsp": {
"message": "<code>CSP</code> abändern, um externe Ressourcen zu erlauben"
"message": "<code>CSP</code>abändern, um externe Ressourcen zu erlauben"
},
"optionsAdvancedPatchCspNote": {
"message": "Aktivieren, wenn Styles Bilder oder Schriftarten enthalten, die aufgrund strenger <code>CSP</code> (<code>Content-Security-Policy</code>) Regeln mancher Seiten nicht laden.\n\nDas Aktivieren wird <code>CSP</code>Beschränkungen lockern, um für den Style erforderliche Ressourcen zu laden. Diese Option ist für fortgeschrittene Benutzer gedacht, die sich den möglichen Sicherheitsrisiken bewusst sind und die die Verantwortung dafür tragen, die nachgeladenen Inhalte selbst zu überwachen. Informiere dich über \"CSS-basierte Angriffe\" um mehr zu erfahren.\n\nBeachte außerdem, dass diese Option nicht garantiert funktioniert, falls eine andere installierte Erweiterung die Netzwerkantwort (CSP-header) zuerst abändert."
@ -861,9 +826,6 @@
"optionsHeading": {
"message": "Optionen"
},
"optionsIconAuto": {
"message": "An Hell- / Dunkelmodus angleichen"
},
"optionsIconDark": {
"message": "Dunkle Browser-Themes"
},
@ -886,7 +848,7 @@
"message": "Optionen zurücksetzen"
},
"optionsStylusThemes": {
"message": "Klicke auf einer beliebigen Stylus Seite auf das Stylus-Symbol in der Werkzeugleiste des Browsers, dann klicke auf \"Styles finden\""
"message": "Stylus UI-Theme suchen"
},
"optionsSubheading": {
"message": "Mehr Optionen"
@ -903,9 +865,6 @@
"optionsSyncNone": {
"message": "Nichts"
},
"optionsSyncPassword": {
"message": "Passwort"
},
"optionsSyncStatusConnected": {
"message": "Verbunden"
},
@ -949,9 +908,6 @@
"optionsSyncSyncNow": {
"message": "Jetzt synchronisieren"
},
"optionsSyncUsername": {
"message": "Benutzername"
},
"optionsUpdateImportNote": {
"message": "Nach dem Importieren von Styles aus einer alten Version oder von Stylish ist eine einmalige manuelle Updatesuche in der Verwaltung nötig. Dies stellt sicher, dass alle Styles auf dem aktuellsten Stand sind."
},
@ -994,9 +950,6 @@
"popupHotkeysTooltip": {
"message": "Klicke, um Tastenkürzel zu sehen"
},
"popupManageSiteStyles": {
"message": "Styles dieser Seite verwalten"
},
"popupManageTooltip": {
"message": "Shift+Klick oder Rechtsklick öffnet den Stylemanager mit Filter für Styles der aktuellen Seite"
},
@ -1018,21 +971,6 @@
"prefShowBadge": {
"message": "Anzahl der aktiven Styles auf der aktuellen Seite"
},
"preferScheme": {
"message": "Hell- / Dunkelmodus Vorrang"
},
"preferSchemeAlways": {
"message": "Derzeit ignoriert (Style wird immer angewendet), weil der allgemeine Hell- / Dunkelmodus nicht aktiv ist"
},
"preferSchemeDark": {
"message": "Dunkel"
},
"preferSchemeLight": {
"message": "Hell"
},
"preferSchemeNone": {
"message": "Keines (immer aktiv)"
},
"previewLabel": {
"message": "Echtzeitvorschau"
},
@ -1061,7 +999,7 @@
"message": "Lese Styles..."
},
"reload": {
"message": "Neu laden"
"message": "Stylus Addon neu laden"
},
"replace": {
"message": "Ersetzen"
@ -1072,15 +1010,9 @@
"replaceWith": {
"message": "Ersetzen durch"
},
"restoreTemplate": {
"message": "Standard-Template wiederherstellen.\n\n(Die gerade geöffneten Editorseiten werden nicht verändert.)"
},
"retrieveBckp": {
"message": "Styles importieren"
},
"saveAsTemplate": {
"message": "Als Template speichern"
},
"search": {
"message": "Suche"
},
@ -1121,7 +1053,7 @@
"message": "Wöchentliche Installationen"
},
"searchStyleQueryHint": {
"message": "Stylenamen durchsuchen (Groß-Kleinschreibung wird beachtet, sobald ein Großbuchstabe benutzt wird):\nHaus Baum Sonne - sucht nach allen Wörtern in beliebiger Reihenfolge\n\"Baum Haus\" - sucht exakt diesen Ausdruck (ohne Anführungszeichen)\n/foo.*bar/i - Regulärer Ausdruck ohne Leerzeichen (nutze stattdessen \\s)"
"message": "Stylenamen ohne Beachtung der Groß-/Kleinschreibung suchen:\nMehrere Suchworte - alle Wörter in beliebiger Reihenfolge\n\"Bestimmte Phrase\" - genau diese Phrase ohne Anführungszeichen\n2020 - zeigt auch Styles, die 2020 aktualisiert wurden"
},
"searchStylesAll": {
"message": "Alles"
@ -1150,18 +1082,12 @@
"sections": {
"message": "Bereiche"
},
"settings": {
"message": "Einstellungen"
},
"shortcuts": {
"message": "Tastenkürzel"
},
"shortcutsNote": {
"message": "Eine Tastenkombination definieren"
},
"shortcutsNoteFF": {
"message": "In Firefox 66+ kannst du die eingebaute Tastenkürzelverwaltung selbst öffnen:\n1) Rechtsklicke das Stylus-Symbol in der Werkzeugleiste und wähle \"Erweiterung verwalten\". Alternativ kannst du about:addons über das Hauptmenü oder per Tastenkürzel Strg-Umschalt-A öffnen.\n2) Klicke auf der geöffneten Seite (about:addons) auf das Zahnrad oben rechts.\n3) Wähle \"Tastenkombinationen von Erweiterungen verwalten\".\n\nHier kannst du alle Tastenkürzel individuell anpassen."
},
"sortDateNewestFirst": {
"message": "neueste zuerst"
},
@ -1207,30 +1133,12 @@
"styleEnabledLabel": {
"message": "Aktiviert"
},
"styleExcludeLabel": {
"message": "Benutzerdefinierte ausgeschlossene URLs"
},
"styleFromMozillaFormatError": {
"message": "Import vom Mozilla Format fehlgeschlagen"
},
"styleFromMozillaFormatPrompt": {
"message": "Mozilla-Format Code einfügen"
},
"styleIncludeLabel": {
"message": "Benutzerdefinierte Ziel-URLs"
},
"styleInjectionImportance": {
"message": "Style als wichtig markieren"
},
"styleInjectionOrder": {
"message": "Applikationsreihenfolge der Styles"
},
"styleInjectionOrderHint": {
"message": "Ziehe den Style mit der Maus an die gewünschte Stelle, um die Applikationsreihenfolge zu ändern. Styles weiter unten können die oberen überschreiben."
},
"styleInjectionOrderHint_prio": {
"message": "Die unten als wichtig gekennzeichneten Styles werden immer zuletzt angewendet, sodass sie auch neu installierte Styles überschreiben können. Klicke auf das Zeichen des Styles, um die Wichtigkeit umzuschalten."
},
"styleInstall": {
"message": "\"$stylename$\" mit Stylus installieren?",
"placeholders": {
@ -1270,15 +1178,6 @@
"styleNotAppliedRegexpProblemTooltip": {
"message": "Der Style wurde aufgrund ungültiger RegExp nicht angewandt."
},
"styleNotAppliedSchemeDark": {
"message": "Der Style wird nur im dunklen Modus angewendet"
},
"styleNotAppliedSchemeLight": {
"message": "Der Style wird nur im hellen Modus angewendet"
},
"stylePreferSchemeLabel": {
"message": "Dunkler/Heller Modus"
},
"styleRegexpInvalidExplanation": {
"message": "Einige RegExp konnten nicht kompiliert werden."
},
@ -1288,6 +1187,9 @@
"styleRegexpProblemTooltip": {
"message": "Anzahl der Bereiche, welche aufgrund nicht korrekt verwendeter RegExp nicht angewendet wurden"
},
"styleRegexpTestButton": {
"message": "RegExp testen"
},
"styleRegexpTestFull": {
"message": "Zutreffende Tabs"
},
@ -1309,9 +1211,6 @@
"styleSaveLabel": {
"message": "Speichern"
},
"styleSettings": {
"message": "Style Einstellungen"
},
"styleToMozillaFormatHelp": {
"message": "Das Mozilla-Format des Codes kann mit Stylish für Firefox verwendet werden und bei userstyles.org eingereicht werden."
},
@ -1333,7 +1232,7 @@
"message": "Stylus funktioniert nicht auf Seiten wie diesen."
},
"stylusUnavailableForURLdetails": {
"message": "Als Sicherheitsvorkehrung verbietet der Browser es Erweiterungen, seine eigenen Seiten (wie z.B. chrome://version oder about:addons) und eigene Seiten anderer Erweiterungen zu verändern. Auch die jeweils browser-eigene Erweiterungsgalerie (wie Chrome Web Store oder addons.mozilla.org) kann nicht verändert werden."
"message": "Als Sicherheitsvorkehrung verbietet der Browser es Erweiterungen, seine eingebauten Seiten (wie chrome://version, dem neuen Standard-Tab ab Chrome 61, about:addons, usw.), sowie Seiten anderer Erweiterungen zu beeinflussen. Jeder Browser beschränkt auch den Zugriff auf seine eigene Erweiterungsgalerie (wie Chrome Web Store oder addons.mozilla.org)."
},
"syncDropboxDeprecated": {
"message": "Dropbox Import / Export wurde durch einen fortschrittlicheren Mechanismus auf der Optionsseite ersetzt."
@ -1341,16 +1240,8 @@
"syncError": {
"message": "Synchronisation fehlgeschlagen"
},
"syncErrorLock": {
"message": "Die Datenbank wird bereits verwendet. Die Sperre wird um $TIME$ aufgehoben.",
"placeholders": {
"time": {
"content": "$1"
}
}
},
"syncErrorRelogin": {
"message": "Synchronisation erfolglos. Du wurdest ausgeloggt.\nVersuche, dich in den Stylus Einstellungen wieder einzuloggen."
"message": "Synchronisation fehlgeschlagen.\nVersuche, dich in den Optionen neu einzuloggen:\nKlicke erst \"Trennen\", dann \"Verbinden\"."
},
"syncStorageErrorSaving": {
"message": "Der Wert kann nicht gespeichert werden. Versuche, die Textmenge zu reduzieren."
@ -1441,6 +1332,9 @@
"usercssReplaceTemplateConfirmation": {
"message": "Ersetze das vorgegebene Template für neue UserCSS styles mit dem vorliegenden Code?"
},
"usercssReplaceTemplateName": {
"message": "Ein leeres @name ersetzt das vorgegebene Template"
},
"usercssReplaceTemplateSectionBody": {
"message": "Quelltext hier eingeben..."
},

View File

@ -64,6 +64,9 @@
"backupButtons": {
"message": "Δημιουργήστε αντίγραφο ασφαλείας"
},
"backupMessage": {
"message": "Επιλέξτε ένα αρχείο ή σύρετέ το σε αυτήν τη σελίδα"
},
"bckpInstStyles": {
"message": "Εξαγωγή στυλ"
},
@ -298,6 +301,9 @@
"findStyles": {
"message": "Εύρεση στυλ"
},
"findStylesForSite": {
"message": "Αναζήτηση περισσότερων στυλ για αυτή την ιστοσελίδα"
},
"genericAdd": {
"message": "Προσθήκη"
},
@ -409,6 +415,9 @@
"linterResetMessage": {
"message": "Για αναίρεση μιας κατά λάθος επαναφοράς, πατήστε Ctrl-Z (ή Cmd-Z) στο πλαίσιο κειμένου"
},
"manageFaviconsHelp": {
"message": "Το Stylus χρησιμοποιεί μία εξωτερική υπηρεσία https://www.google.com/s2/favicons"
},
"manageFilters": {
"message": "Φίλτρα"
},
@ -529,9 +538,6 @@
"optionsSyncSyncNow": {
"message": "Συγχρονισμός τώρα"
},
"optionsSyncUrl": {
"message": "διεύθυνση URL"
},
"optionsUpdateInterval": {
"message": "Διάστημα αυτόματης ενημέρωσης των στυλ σε ώρες (0 για απενεργοποίηση)"
},
@ -604,9 +610,6 @@
"sectionRemove": {
"message": "Αφαίρεση ενότητας"
},
"sections": {
"message": "Ενότητες"
},
"shortcuts": {
"message": "Συντομεύσεις"
},
@ -677,6 +680,9 @@
"syncError": {
"message": "Ο συγχρονισμός απέτυχε"
},
"syncErrorRelogin": {
"message": "Ο συγχρονισμός απέτυχε.\nΠροσπαθήστε να συνδεθείτε ξανά στις επιλογές Stylus:\nκάντε κλικ στο 'αποσύνδεση' πρώτα και μετά στο 'σύνδεση'."
},
"toggleStyle": {
"message": "Αλλαγή στυλ"
},

View File

@ -93,8 +93,8 @@
"description": "Heading for backup"
},
"backupMessage": {
"message": "To import the backup file, drag'n'drop it into this page or click the Import button.\n\nTo export a compatible backup for Stylus older than 1.5.18, right-click or shift-click the Export button.",
"description": "Text for Backup section's (i) in the manager"
"message": "Select a file or drag and drop to this page.",
"description": "Message for backup"
},
"bckpInstStyles": {
"message": "Export styles"
@ -186,10 +186,6 @@
"message": "Theme",
"description": "Label for the style editor's CSS theme."
},
"cm_arrowKeysTraverse": {
"message": "Arrow keys ↑↓ traverse sections",
"description": "Label for the option in the editor."
},
"colorpickerPaletteHint": {
"message": "Right-click a swatch to cycle through its source lines"
},
@ -340,29 +336,12 @@
},
"disableAllStyles": {
"message": "Turn all styles off",
"description": "Label for the checkbox that turns all styles off."
},
"disableAllStylesOff": {
"message": "Styles are turned off",
"description": "Label for the checkbox that turns all styles off when it's checked."
"description": "Label for the checkbox that turns all enabled styles off."
},
"disableStyleLabel": {
"message": "Disable",
"description": "Label for the button to disable a style"
},
"draftTitle": {
"message": "Draft recovery, created $date$",
"placeholders": {
"date": {
"content": "$1"
}
},
"description": "Title of the modal displayed in the editor when an unsaved draft is found, the $date$ looks like '1 hour ago' in user's current UI language"
},
"draftAction": {
"message": "Choose 'Yes' to load this draft or 'No' to discard it.",
"description": "Displayed in the editor after the browser/extension crashed"
},
"dragDropMessage": {
"message": "Drop your backup file anywhere on this page to import.",
"description": "Drag'n'drop message"
@ -396,8 +375,11 @@
},
"description": "Title of the page for editing styles"
},
"editorSettings": {
"message": "Editor settings"
"editorCodeLabel": {
"message": "Code"
},
"editorSettingLabel": {
"message": "Settings"
},
"enableStyleLabel": {
"message": "Enable",
@ -413,9 +395,6 @@
"message": "Export",
"description": "Label for the button to export a style ('edit' page) or all styles ('manage' page)"
},
"exportCompatible": {
"message": "Export (compatible mode)"
},
"exportSavedSuccess": {
"message": "File saved with success"
},
@ -459,6 +438,18 @@
"message": "Find styles",
"description": "Text for a link that gets a list of styles for the current site"
},
"findStylesForSite": {
"message": "Find more styles for this site",
"description": "Text for a link that gets a list of styles for the current site"
},
"findStylesInline": {
"message": "Inline",
"description": "Text for a checkbox that opens search results 'inline' (within the Stylus popup window)"
},
"findStylesInlineTooltip": {
"message": "Display search results inside this window.",
"description": "Text for a checkbox that displays search results within the Stylus popup."
},
"genericAdd": {
"message": "Add",
"description": "Used in various places for an action that adds something"
@ -502,13 +493,6 @@
"message": "Saved",
"description": "Used in various parts of the UI to indicate that something was saved"
},
"genericSize": {
"message": "Size"
},
"genericTest": {
"message": "Test",
"description": "Label for the action that runs some test e.g. opens the regexp tester panel in the editor"
},
"genericTitle": {
"message": "Title",
"description": "Used in various parts of the UI to indicate the title of something"
@ -520,10 +504,6 @@
"gettingStyles": {
"message": "Getting all styles..."
},
"headerResizerHint": {
"message": "Hold Shift to resize only in this type of UI, i.e. editor, manager, installer",
"description": "Tooltip for the header panel resizer"
},
"helpAlt": {
"message": "Help",
"description": "Alternate text for help buttons"
@ -622,6 +602,18 @@
"message": "Update style",
"description": "Label for update button"
},
"installPreferSchemeLabel": {
"message": "The style should be applied:"
},
"installPreferSchemeNone": {
"message": "Always"
},
"installPreferSchemeDark": {
"message": "In Dark Mode"
},
"installPreferSchemeLight": {
"message": "In Light Mode"
},
"installUpdate": {
"message": "Install update",
"description": "Label for the button to install an update for a single style"
@ -751,7 +743,7 @@
"description": "Label for the checkbox that toggles grayed out mode of applies-to favicons in the new UI on manage page"
},
"manageFaviconsHelp": {
"message": "Stylus uses an external service https://icons.duckduckgo.com",
"message": "Stylus uses an external service https://www.google.com/s2/favicons",
"description": "Label for the checkbox that toggles applies-to favicons in the new UI on manage page"
},
"manageFilters": {
@ -766,9 +758,6 @@
"message": "Number of applies-to items",
"description": "Label for the numeric input box to limit max number of applies-to targets in the new UI on manage page"
},
"manageMinColumnWidth": {
"message": "Minimum column width (in pixels; 9999 disables multi-column mode)"
},
"manageNewStyleAsUsercss": {
"message": "as Usercss",
"description": "VERY SHORT label for the checkbox next to the 'Write new style' button in the style manager"
@ -1079,17 +1068,14 @@
"message": "Exposes the top site domain in each iframe.\nEnables writing iframe-specific CSS like this:\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."
},
"optionsAdvancedExposeStyleName": {
"message": "Expose style name"
},
"optionsAdvancedExposeStyleNameNote": {
"message": "Exposes the style name in the page to facilitate debugging of styles in devtools. Please reload the tab(s) to apply the new setting."
},
"optionsAdvancedNewStyleAsUsercss": {
"message": "Write new style as usercss"
},
"optionsAdvancedAutoSwitchScheme": {
"message": "Toggle Light/Dark Mode styles automatically"
},
"optionsAdvancedAutoSwitchSchemeNever": {
"message": "Disabled. The dark/light setting in styles is ignored."
"message": "Never"
},
"optionsAdvancedAutoSwitchSchemeBySystem": {
"message": "By system preference"
@ -1140,9 +1126,6 @@
"message": "Options",
"description": "Heading for options section on manage page."
},
"optionsIconAuto": {
"message": "Match the Dark/Light mode"
},
"optionsIconDark": {
"message": "Dark browser themes"
},
@ -1165,7 +1148,7 @@
"message": "Reset options"
},
"optionsStylusThemes": {
"message": "Click Stylus icon in the browser toolbar on any Stylus page including this one, then click 'Find styles'"
"message": "Find a Stylus UI theme"
},
"optionsSubheading": {
"message": "More Options",
@ -1217,15 +1200,6 @@
}
}
},
"optionsSyncUsername": {
"message": "Username"
},
"optionsSyncPassword": {
"message": "Password"
},
"optionsSyncUrl": {
"message": "URL"
},
"optionsSyncStatusRelogin": {
"message": "Session expired, please login again."
},
@ -1285,10 +1259,6 @@
"message": "Click to see available hotkeys",
"description": "Tooltip displayed when hovering the right edge of the extension popup"
},
"popupManageSiteStyles": {
"message": "Manage site styles",
"description": "Item in the dropdown menu for the 'Manage' button in the popup that opens manager with styles applicable for current site."
},
"popupManageTooltip": {
"message": "Shift-click or right-click opens manager with styles applicable for current site",
"description": "Tooltip for the 'Manage' button in the popup."
@ -1313,21 +1283,6 @@
"message": "Styles before commands",
"description": "Label for the checkbox controlling section order in the popup."
},
"preferScheme": {
"message": "Dark/Light mode preference"
},
"preferSchemeAlways": {
"message": "Currently ignored (the style always applies) because the global Dark/Light mode is disabled"
},
"preferSchemeDark": {
"message": "Dark"
},
"preferSchemeLight": {
"message": "Light"
},
"preferSchemeNone": {
"message": "None (always applied)"
},
"prefShowBadge": {
"message": "Number of styles active for the current site",
"description": "Label for the checkbox controlling toolbar badge text."
@ -1366,8 +1321,8 @@
"message": "Reading styles..."
},
"reload": {
"message": "Reload",
"description": "Context menu to reload the extension when installed in developer mode"
"message": "Reload Stylus extension",
"description": "Context menu reload"
},
"replace": {
"message": "Replace",
@ -1381,18 +1336,12 @@
"message": "Replace with",
"description": "Label before the replace-with input field in the editor shown on Ctrl-H etc."
},
"restoreTemplate": {
"message": "Restore the default template.\n\n(The currently open editor pages won't change.)"
},
"retrieveBckp": {
"message": "Import styles"
},
"retrieveDropboxSync": {
"message": "Dropbox Import"
},
"saveAsTemplate": {
"message": "Save as template"
},
"search": {
"message": "Search",
"description": "Label before the search input field in the editor shown on Ctrl-F"
@ -1444,7 +1393,7 @@
"description": "Text for label that shows the number of times a search result was installed during last week"
},
"searchStyleQueryHint": {
"message": "Search style names (case-sensitively if an uppercase letter is used):\nsome words - all these words in any order\n\"some phrase\" - this exact phrase without quotes\n/foo.*bar/i - regular expression without spaces (use \\s instead)",
"message": "Search style names case-insensitively:\nsome words - all words in any order\n\"some phrase\" - this exact phrase without quotes\n2020 - a year like this also shows styles updated in 2020",
"description": "Tooltip shown for the text input in the popup's inline style finder"
},
"searchStylesAll": {
@ -1491,10 +1440,6 @@
"message": "Sections",
"description": "Header for the table of contents block listing style section names in the left panel of the classic editor"
},
"settings": {
"message": "Settings",
"description": "Generic label/title for settings"
},
"shortcuts": {
"message": "Shortcuts",
"description": "Go to shortcut configuration"
@ -1502,9 +1447,6 @@
"shortcutsNote": {
"message": "Define keyboard shortcuts"
},
"shortcutsNoteFF": {
"message": "In Firefox 66+ you can open the built-in shortcuts UI manually:\n1) right-click Stylus icon in the toolbar and choose 'Manage'\n(alternatively, open about:addons via the main menu or Ctrl-Shift-A),\n2) in the page that opens click the cog wheel icon in the top right corner,\n3) choose 'Manage extension shortcuts'.\n\nYou can also customize the shortcuts here."
},
"sortDateNewestFirst": {
"message": "newest first",
"description": "Text added to indicate that sorting a date would add the newest entries at the top"
@ -1637,6 +1579,10 @@
"message": "Number of sections not applied due to incorrect usage of 'regexp()'",
"description": "Tooltip in the popup for styles that were applied only partially"
},
"styleRegexpTestButton": {
"message": "RegExp test",
"description": "RegExp test button label in the editor shown when applies-to list has a regexp value"
},
"styleRegexpTestFull": {
"message": "Matching tabs",
"description": "RegExp test report: label for the fully matching expressions"
@ -1665,10 +1611,6 @@
"message": "Save",
"description": "Label for save button for style editing"
},
"styleSettings": {
"message": "Style settings",
"description": "Label/title for style settings dialog"
},
"styleToMozillaFormatHelp": {
"message": "The Mozilla format of the code can be submitted to userstyles.org and used with the classic Stylish for Firefox",
"description": "Help info for the Mozilla format header section that converts the code to/from Mozilla format"
@ -1698,6 +1640,9 @@
"message": "As a security precaution, the browser prohibits extensions from affecting its built-in pages (like chrome://version, the standard new tab page as of Chrome 61, about:addons, and so on) as well as other extensions' pages. Each browser also restricts access to its own extensions gallery (like Chrome Web Store or AMO).",
"description": "Sub-note in the toolbar pop-up when on a URL Stylus can't affect"
},
"styleOriginLabel": {
"message": "Style origin"
},
"styleUpdateUrlLabel": {
"message": "Update URL"
},
@ -1707,21 +1652,6 @@
"styleIncludeLabel": {
"message": "Custom included sites"
},
"styleInjectionImportance": {
"message": "Toggle style's importance"
},
"styleInjectionOrder": {
"message": "Style injection order",
"description": "Tooltip for the button in the manager to open the dialog and also the title of this dialog"
},
"styleInjectionOrderHint": {
"message": "Drag'n'drop a style to change its position. Styles are injected sequentially in the order shown below so a style further down the list can override the earlier styles.",
"description": "Hint in the injection order dialog in the manager"
},
"styleInjectionOrderHint_prio": {
"message": "Important styles listed below are always injected last so they can override any newly installed styles. Click the style's mark to toggle its importance.",
"description": "Hint at the bottom of the injection order dialog in the manager"
},
"styleExcludeLabel": {
"message": "Custom excluded sites"
},
@ -1859,6 +1789,10 @@
"usercssReplaceTemplateConfirmation": {
"message": "Replace the default template for new Usercss styles with the current code?"
},
"usercssReplaceTemplateName": {
"message": "Empty @name replaces the default template",
"description": "The text shown after @name when creating a new Usercss style"
},
"usercssReplaceTemplateSectionBody": {
"message": "Insert code here...",
"description": "The code placeholder comment in a new style created by clicking 'Write style' in the popup"

View File

@ -29,7 +29,7 @@
"message": "URLs en el dominio"
},
"appliesHelp": {
"message": "Utilice los controles 'Se aplica a' para limitar las URL a las que se aplica el código de esta sección."
"message": "Utilice los controles 'Se aplica a' para limitar a qué URLs se aplica el código de esta sección."
},
"appliesLabel": {
"message": "Se aplica a"
@ -68,7 +68,7 @@
"message": "Respaldo"
},
"backupMessage": {
"message": "Para importar el archivo de copia de seguridad, arrástrelo a esta página o haga clic en el botón Importar.\n\nPara exportar una copia de seguridad compatible con Stylus anterior a la versión 1.5.18, haga clic en el triángulo del botón Exportar y pulse en Modo compatible."
"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."
},
"bckpInstStyles": {
"message": "Exportar estilos"
@ -128,7 +128,7 @@
"message": "Al hacer doble clic se seleccionan los tokens"
},
"cm_selectByTokensTooltip": {
"message": "Ejemplos de tokens: .foo-bar-2 #aabbcc 0.32 !important\nCuando está desactivado: se seleccionan las palabras delimitadas por puntos."
"message": "Ejemplos de tokens: .foo-bar-2 #aabbcc 0.32 !important\nCuando está deshabilitado: se seleccionan las palabras delimitadas por puntos."
},
"cm_smartIndent": {
"message": "Usar sangría inteligente"
@ -140,7 +140,7 @@
"message": "Temas"
},
"colorpickerPaletteHint": {
"message": "Haga clic derecho en una muestra para desplazarse por las líneas del código fuente"
"message": "Haz clic con el botón derecho en una muestra para mostrar las líneas de código fuente"
},
"colorpickerSwitchFormatTooltip": {
"message": "Alternar formatos: HEX -> RGB -> HSL.\nMayús+Clic para invertir la dirección.\nTambién con las teclas RePág (Retroceso de página), AvPág (Avance de página)."
@ -200,10 +200,10 @@
"message": "Copiar al portapapeles"
},
"customNameHint": {
"message": "Introduzca aquí un nombre personalizado para cambiar el nombre del estilo en la interfaz sin que se interrumpan sus actualizaciones"
"message": "Ingresa un nombre personalizado aquí para cambiar el nombre de estilo del diseño sin romper sus actualizaciones"
},
"customNameResetHint": {
"message": "Dejar de usar el nombre personalizado, cambiar al nombre propio del estilo"
"message": "Dejar de usar el nombre personalizado, cambiar al nombre del estilo"
},
"dateAbbrYear": {
"message": "$value$a",
@ -220,13 +220,13 @@
"message": "Fecha de actualización"
},
"dbError": {
"message": "Se ha producido un error al utilizar la base de datos de Stylus. ¿Desea visitar una página web con posibles soluciones?"
"message": "Ocurrió un error con la base de datos de Stylus. ¿Deseas visitar una página web con posibles soluciones?"
},
"defaultTheme": {
"message": "predeterminado"
},
"deleteStyleConfirm": {
"message": "¿Está seguro que desea eliminar este estilo?"
"message": "¿Estás seguro de que quieres eliminar este estilo?"
},
"deleteStyleLabel": {
"message": "Eliminar"
@ -237,22 +237,8 @@
"disableAllStyles": {
"message": "Desactivar todos los estilos"
},
"disableAllStylesOff": {
"message": "Los estilos están desactivados"
},
"disableStyleLabel": {
"message": "Desactivar"
},
"draftAction": {
"message": "Elija 'Sí' para cargar este proyecto o 'No' para descartarlo."
},
"draftTitle": {
"message": "Proyecto de recuperación, creado$date$",
"placeholders": {
"date": {
"content": "$1"
}
}
"message": "Deshabilitar"
},
"dragDropMessage": {
"message": "Arrastra tu archivo de respaldo en cualquier lugar de esta página para importar."
@ -280,21 +266,15 @@
}
}
},
"editorSettings": {
"message": "Configuración del editor"
},
"enableStyleLabel": {
"message": "Activar"
"message": "Habilitar"
},
"excludeStyleByDomainLabel": {
"message": "Excluir el dominio actual"
"message": "Excluir el dominio Actual"
},
"excludeStyleByUrlLabel": {
"message": "Excluir la URL actual"
},
"exportCompatible": {
"message": "Exportar (modo compatible)"
},
"exportLabel": {
"message": "Exportar"
},
@ -328,11 +308,20 @@
}
},
"filteredStylesAllHidden": {
"message": "Los filtros aplicados actualmente no coinciden con ningún estilo"
"message": "Los filtros actualmente aplicados no coinciden con estilo alguno"
},
"findStyles": {
"message": "Buscar estilos"
},
"findStylesForSite": {
"message": "Buscar más estilos para este sitio"
},
"findStylesInline": {
"message": "Dentro"
},
"findStylesInlineTooltip": {
"message": "Muestra los resultados de búsqueda dentro de esta ventana."
},
"genericAdd": {
"message": "Añadir"
},
@ -343,10 +332,10 @@
"message": "Descripción"
},
"genericDisabledLabel": {
"message": "Desactivado"
"message": "Deshabilitado"
},
"genericEnabledLabel": {
"message": "Activado"
"message": "Habilitado"
},
"genericHistoryLabel": {
"message": "Historial"
@ -363,9 +352,6 @@
"genericSavedMessage": {
"message": "Guardado"
},
"genericTest": {
"message": "Prueba"
},
"genericTitle": {
"message": "Título"
},
@ -375,9 +361,6 @@
"gettingStyles": {
"message": "Obteniendo todos los estilos..."
},
"headerResizerHint": {
"message": "Presione Mayúsculas para cambiar el tamaño solo en este tipo de interfaz. Por ejemplo Editor, Administrador, Instalador"
},
"helpAlt": {
"message": "Ayuda"
},
@ -388,7 +371,7 @@
"message": "Pulsa un atajo de teclado"
},
"hostDisabled": {
"message": "Este host ha sido desactivado debido a un error en la versión actual del navegador utilizado."
"message": "Este host ha sido deshabilitado debido a un error en la versión actual del navegador que se está utilizando."
},
"importAppendLabel": {
"message": "Adicionar al estilo"
@ -400,10 +383,10 @@
"message": "Importar"
},
"importPreprocessor": {
"message": "Los estilos con <code>@preprocessor</code> no funcionarán en el modo clásico. Puedes cambiar el editor al modo Usercss: 1) Abre el administrador de estilos. 2) Activa la casilla 'como UserCSS'. 3) Haz clic en 'Escribir un estilo nuevo'\n\n¿Quieres importarlo de todos modos?"
"message": "Los estilos con <code>@preprocessor</code> no funcionarán en el modo clásico. Puedes cambiar el editor para usar el modo UserCSS: 1) Abre el administrador de estilos. 2) Activa la casilla «como UserCSS». 3) Haz clic en «Escribir un estilo nuevo»\n\n¿Quieres importarlo de todos modos?"
},
"importPreprocessorTitle": {
"message": "Posible problema debido a @preprocessor"
"message": "Problema potencial debido a @preprocessor"
},
"importReplaceLabel": {
"message": "Sobrescribir estilo"
@ -473,18 +456,9 @@
"linkGetHelp": {
"message": "Obtener ayuda"
},
"linkGetShareStyles": {
"message": "Obtener y compartir estilos"
},
"linkGetShareStylesInfo": {
"message": "El nuevo sitio userstyles.world, gestionado por la comunidad, ha sido creado por los creadores de usertyles para sustituir a userstyles.org, que ha sido muy lento y no ha respondido durante el último año, por lo que muchos creadores han dejado de actualizar sus estilos."
},
"linkGetStyles": {
"message": "Obtener estilos"
},
"linkGetStylesInfo": {
"message": "Este repositorio fue creado por un miembro de la comunidad userstyle para suplir el lento y disfuncional userstyles.org. El repositorio se actualiza aproximadamente una vez al día."
},
"linkTranslate": {
"message": "Traducir"
},
@ -497,7 +471,7 @@
}
},
"linterCSSLintSettings": {
"message": "(Establezca la regla como: 0 = desactivado; 1 = advertencia; 2 = error)"
"message": "(Establecer regla como: 0 = deshabilitada; 1 = advertencia; 2 = error)"
},
"linterConfigPopupTitle": {
"message": "Establecer configuración de $linter$ reglas",
@ -552,7 +526,7 @@
"message": "Atenuado"
},
"manageFaviconsHelp": {
"message": "Stylus usa un servicio externo https://icons.duckduckgo.com"
"message": "Stylus usa un servicio externo https://www.google.com/s2/favicons"
},
"manageFilters": {
"message": "Filtros"
@ -570,10 +544,10 @@
"message": "Nuevo diseño de la administración"
},
"manageOnlyDisabled": {
"message": "Solo estilos desactivados"
"message": "Solo estilos deshabilitados"
},
"manageOnlyEnabled": {
"message": "Solamente estilos activados"
"message": "Solo estilos habilitados"
},
"manageOnlyExternal": {
"message": "Solo estilos externos"
@ -812,15 +786,6 @@
"optionsAdvanced": {
"message": "Avanzado"
},
"optionsAdvancedAutoSwitchSchemeBySystem": {
"message": "Por preferencia del sistema"
},
"optionsAdvancedAutoSwitchSchemeByTime": {
"message": "Por la noche:"
},
"optionsAdvancedAutoSwitchSchemeNever": {
"message": "Desactivado. Se ignora la configuración de oscuro/claro en los estilos."
},
"optionsAdvancedContextDelete": {
"message": "Añadir 'Eliminar' al menú contextual del editor"
},
@ -830,12 +795,6 @@
"optionsAdvancedExposeIframesNote": {
"message": "Muestra el dominio del sitio principal en cada iframe.\nPermite escribir código CSS específico para iframes, como en el ejemplo siguiente:\nhtml[stylus-iframe$$=\"twitter.com\"] h1 { display:none }"
},
"optionsAdvancedExposeStyleName": {
"message": "Exponer el nombre del estilo"
},
"optionsAdvancedExposeStyleNameNote": {
"message": "Expone el nombre del estilo en la página para facilitar la depuración de los estilos en devtools. Recargue las pestañas para aplicar la nueva configuración."
},
"optionsAdvancedNewStyleAsUsercss": {
"message": "Escribir nuevo estilo como UserCSS"
},
@ -852,7 +811,7 @@
"message": "Activa esta opción si aparecen y desaparecen rápidamente contenidos sin estilo al cargarse páginas (el efecto es más visible con temas oscuros).\n\nLa explicación técnica es que Chrome/Chromium pospone la comunicación asincrónica de las extensiones. Esta función intenta mejorar la velocidad de carga de las páginas, aunque no siempre es efectiva y puede causar que los estilos se apliquen con retraso. Para evitar este problema (ya que las extensiones del tipo WebExtensions no pueden usar una API sincrónica), Stylus ofrece esta opción para usar la API web XMLHttpRequest sincrónica (en desuso) para obtener los estilos correspondientes. No debería causar problemas, ya que la petición se completa en solo unos milisegundos, mientras se carga la página del servidor.\n\nDe todos modos, Chromium mostrará una advertencia en la consola de herramientas de desarrollador. Para evitar que se muestren este tipo de advertencias en el futuro, haz clic con el botón derecho sobre una advertencia y selecciona la opción para ocultarla."
},
"optionsBadgeDisabled": {
"message": "Color de fondo cuando se desactiva"
"message": "Color de fondo cuando está deshabilitado"
},
"optionsBadgeNormal": {
"message": "Color de fondo"
@ -881,9 +840,6 @@
"optionsHeading": {
"message": "Opciones"
},
"optionsIconAuto": {
"message": "Adaptar el modo oscuro/claro"
},
"optionsIconDark": {
"message": "Temas de navegador oscuros"
},
@ -906,7 +862,7 @@
"message": "Restablecer opciones"
},
"optionsStylusThemes": {
"message": "Haga clic en el icono de Stylus en la barra de herramientas del navegador y luego haga clic en 'Buscar estilos'."
"message": "Buscar un tema de Stylus"
},
"optionsSubheading": {
"message": "Más opciones"
@ -923,9 +879,6 @@
"optionsSyncNone": {
"message": "Ninguno"
},
"optionsSyncPassword": {
"message": "Contraseña"
},
"optionsSyncStatusConnected": {
"message": "Conectado"
},
@ -969,14 +922,11 @@
"optionsSyncSyncNow": {
"message": "Sincronizar ahora"
},
"optionsSyncUsername": {
"message": "Usuario"
},
"optionsUpdateImportNote": {
"message": "Al importar los respaldos de estilos desde una versión antigua o desde Stylish, comprueba una vez las actualizaciones manualmente en el administrador de estilos para asegurarte de que todos los estilos estén actualizados."
},
"optionsUpdateInterval": {
"message": "Intervalo de actualización automática del estilo en horas (escriba 0 para desactivar)"
"message": "Intervalo de actualización automática en horas (especificar 0 para deshabilitar)"
},
"overwriteFileExport": {
"message": "¿Desea sobrescribir un archivo existente?"
@ -1009,14 +959,11 @@
"message": "Útil para temas oscuros en el nuevo Chrome ya que no pinta los bordes laterales"
},
"popupHotkeysInfo": {
"message": "<1>-<9>, <0>, también en el teclado numérico - acciona el enésimo estilo (0 es 10)\n<A>-<Z> acciona el primer estilo con un nombre que comienza con la letra\n<Shift> abre el editor en lugar de accionar\n<Numpad +> activa los estilos listados\n<Numpad > desactiva los estilos listados\n<Numpad *> y < ` > (acento grave) - acciona los estilos inicialmente activados; no se aplica a los estilos posteriormente activados mientras la ventana emergente está abierta para que pueda restaurar la selección inicial después de probar las cosas: simplemente desactive todo, luego accione, por ejemplo <Numpad > <Numpad *>\nMás información en la wiki"
"message": "<1>-<9>, <0>, también en el teclado numérico - acciona el n-esimo estilo (0 es 10)\n<A>-<Z> acciona el primer estilo con un nombre que comienza con la letra\n<Shift> abre el editor en lugar de accionar\n<Numpad +> habilita los estilos listados\n<Numpad > deshabilita los estilos listados\n<Numpad *> y <`> (acento grave) - acciona los estilos habilitados inicialmente; no se aplica a los estilos habilitados subsecuentemente mientras el cuadro emergente está abierto, así que puede restaurar la selección inicial tras probar el material: simplemente deshabilite todo, y luego accione, es decir <Numpad > <Numpad *>\nMás información en el wiki"
},
"popupHotkeysTooltip": {
"message": "Haz clic para ver los atajos de teclado disponibles"
},
"popupManageSiteStyles": {
"message": "Administrar estilos del sitio"
},
"popupManageTooltip": {
"message": "Mayús+clic o clic secundario abre el administrador con estilos aplicables para el sitio actual."
},
@ -1030,7 +977,7 @@
"message": "Abrir editor en una nueva ventana"
},
"popupOpenEditInWindowTooltip": {
"message": "También se activa al desacoplar la pestaña del editor de una ventana del navegador,\ny se desactiva acoplando una única pestaña del editor en otra ventana."
"message": "También se habilita al desacoplar la pestaña del editor de una ventana de navegador,\ny se deshabilita al acoplar una única pestaña de editor en otra ventana."
},
"popupStylesFirst": {
"message": "Estilos antes que las órdenes"
@ -1038,21 +985,6 @@
"prefShowBadge": {
"message": "Número de estilos activos en el sitio actual"
},
"preferScheme": {
"message": "Preferencia de modo oscuro/claro"
},
"preferSchemeAlways": {
"message": "De momento se ignora (el estilo siempre se aplica) porque el modo global oscuro/claro está desactivado"
},
"preferSchemeDark": {
"message": "Oscuro"
},
"preferSchemeLight": {
"message": "Claro"
},
"preferSchemeNone": {
"message": "Ninguno (siempre aplicado)"
},
"previewLabel": {
"message": "Vista previa en tiempo real"
},
@ -1081,7 +1013,7 @@
"message": "Leyendo los estilos..."
},
"reload": {
"message": "Recargar"
"message": "Recargar extensión Stylus"
},
"replace": {
"message": "Reemplazar"
@ -1092,18 +1024,12 @@
"replaceWith": {
"message": "Reemplazar con"
},
"restoreTemplate": {
"message": "Restaurar la plantilla predeterminada.\n\n(Las páginas del editor actualmente abiertas no cambiarán.)"
},
"retrieveBckp": {
"message": "Importar estilos"
},
"retrieveDropboxSync": {
"message": "Importar desde Dropbox"
},
"saveAsTemplate": {
"message": "Guardar como plantilla"
},
"search": {
"message": "Buscar"
},
@ -1132,7 +1058,7 @@
"message": "El estilo se ha instalado, pero no es válido para la URL del sitio actual."
},
"searchResultNotMatchingNote": {
"message": "Pruebe solicitando al autor de este estilo que añada la URL.\n\nTambién puede abrir el estilo en el administrador y editarlo\nusted mismo, pero tenga en cuenta que esto desactiva las\nactualizaciones automáticas para este estilo."
"message": "Intenta pedir al autor de este estilo de usuario que añada la URL.\n\nTambién puedes abrir el estilo en el administrador y editarlo tú mismo,\npero ten en cuenta que deshabilita las actualizaciones automáticas de\neste estilo."
},
"searchResultRating": {
"message": "Valoración"
@ -1143,6 +1069,9 @@
"searchResultWeeklyCount": {
"message": "Instalaciones semanales"
},
"searchStyleQueryHint": {
"message": "La búsqueda de nombres de estilo no distingue mayúsculas de minúsculas:\nalgunas palabras: todas las palabras en cualquier orden\n\"una frase\": esta frase exacta (sin comillas)\n2020: al escribir un año, se muestran estilos que se actualizaron ese año"
},
"searchStylesAll": {
"message": "Todos"
},
@ -1176,18 +1105,12 @@
"sections": {
"message": "Secciones"
},
"settings": {
"message": "Configuración"
},
"shortcuts": {
"message": "Atajos"
},
"shortcutsNote": {
"message": "Defina atajos de teclado"
},
"shortcutsNoteFF": {
"message": "En Firefox 66+ puedes abrir manualmente la interfaz de atajos integrada:\n1) Clic derecho en el icono Stylus de la barra de herramientas y pulse 'Gestionar extensión'.\n(o bien, abra about:addons mediante el menú principal o Ctrl-Shift-A),\n2) En la página que se abre, haga clic en el icono de la rueda dentada en la esquina superior derecha,\n3) Elija 'Administrar atajos de extensiones'.\n\nTambién puede personalizar los accesos directos aquí."
},
"sortDateNewestFirst": {
"message": "los más nuevos primero"
},
@ -1204,7 +1127,7 @@
"message": "Título descendentemente"
},
"sortStylesHelp": {
"message": "Elija el tipo de ordenamiento que desea aplicar a las entradas instaladas desde el menú desplegable de ordenamiento. La configuración predeterminada aplica un ordenamiento ascendente (de la A a la Z) a los títulos de las entradas. El ordenamiento dentro del grupo 'Título descendente' aplicará un ordenamiento descendente (de la Z a la A) al título.\nHay otros preajustes que permiten ordenar las entradas por múltiples criterios. Imagínese que es como ordenar una tabla con múltiples columnas y que cada categoría en una selección (entre los signos de suma) representa una columna, o un grupo.\nPor ejemplo, si la configuración es 'Activado (primero) + Título', las entradas se ordenarían de manera que todas las entradas activadas se sitúen al principio de la lista, y luego se aplica un ordenamiento ascendente del título de la entrada (de la A a la Z) a las entradas activadas y desactivadas por separado."
"message": "Escoja el tipo de orden a aplicar a las entradas instaladas desde el menú desplegable de ordenación. La configuración predeterminada aplica un orden ascendente (A a Z) a los títulos de las entradas. Las ordenaciones del grupo \"Título descendentemente\" aplicarán un orden descendente (Z a A) al título.\nHay otros preajustes que permitirán ordenar las entradas según múltiples criterios. Piense en esto como en ordenar una tabla con múltiples columnas, y en cada categoría en una selección (entre los signos más) representa una columna o grupo.\nPor ejemplo, si la configuración es \"Habilitados (primero) + Título\", las entradas se ordenarían de forma que las entradas habilitadas quedarían en la parte superior de la lista, luego se aplica un orden ascendente (A a Z) de títulos de las entradas tanto para las entradas habilitadas como las deshabilitadas de forma separada."
},
"sortStylesHelpTitle": {
"message": "Ordenar contenidos"
@ -1233,30 +1156,12 @@
"styleEnabledLabel": {
"message": "Activado"
},
"styleExcludeLabel": {
"message": "Sitios excluidos personalizados"
},
"styleFromMozillaFormatError": {
"message": "No se pudo importar desde el formato Mozilla"
},
"styleFromMozillaFormatPrompt": {
"message": "Pegue el código de formato Mozilla"
},
"styleIncludeLabel": {
"message": "Sitios incluidos personalizados"
},
"styleInjectionImportance": {
"message": "Alternar la importancia del estilo"
},
"styleInjectionOrder": {
"message": "Orden de inyección de estilo"
},
"styleInjectionOrderHint": {
"message": "Arrastre y suelte un estilo para cambiar su posición. Los estilos se inyectan secuencialmente en el orden que se muestra a continuación, por lo que un estilo que esté más abajo en la lista puede anular los estilos anteriores."
},
"styleInjectionOrderHint_prio": {
"message": "Los estilos importantes que se indican a continuación se inyectan siempre en último lugar para que puedan anular cualquier estilo recién instalado. Haga clic en la marca del estilo para cambiar su importancia."
},
"styleInstall": {
"message": "¿Quiere instalar «$stylename$» en Stylus?",
"placeholders": {
@ -1299,15 +1204,6 @@
"styleNotAppliedRegexpProblemTooltip": {
"message": "El estilo no se aplicó debido a su uso incorrecto de 'regexp()'"
},
"styleNotAppliedSchemeDark": {
"message": "Este estilo solo se aplica en el modo oscuro"
},
"styleNotAppliedSchemeLight": {
"message": "Este estilo solo se aplica en el modo claro"
},
"stylePreferSchemeLabel": {
"message": "Modo oscuro/claro"
},
"styleRegexpInvalidExplanation": {
"message": "Algunas reglas «regexp()» que no se pudieron compilar en absoluto."
},
@ -1317,6 +1213,9 @@
"styleRegexpProblemTooltip": {
"message": "Número de secciones no aplicadas debido a un uso incorrecto de 'regexp()'"
},
"styleRegexpTestButton": {
"message": "Probar regexp"
},
"styleRegexpTestFull": {
"message": "Pestañas coincidentes"
},
@ -1338,9 +1237,6 @@
"styleSaveLabel": {
"message": "Guardar"
},
"styleSettings": {
"message": "Configuración del estilo"
},
"styleToMozillaFormatHelp": {
"message": "El formato Mozilla del código se puede enviar a userstyles.org y usarse con el Stylish para Firefox clásico."
},
@ -1358,9 +1254,6 @@
"styleUpdateDiscardChanges": {
"message": "El estilo se ha modificado fuera del editor. ¿Desea recargar el estilo?"
},
"styleUpdateUrlLabel": {
"message": "Actualizar URL"
},
"stylusUnavailableForURL": {
"message": "Stylus no funciona en páginas como esta."
},
@ -1376,16 +1269,8 @@
"syncError": {
"message": "No se pudo sincronizar"
},
"syncErrorLock": {
"message": "La base de datos ya está en uso. El bloqueo expirará en $TIME$",
"placeholders": {
"time": {
"content": "$1"
}
}
},
"syncErrorRelogin": {
"message": "Sincronización fallida. Se ha cerrado la sesión.\nIntenta reiniciar la sesión en las opciones de Stylus."
"message": "No se pudo sincronizar.\nIntenta volver a iniciar sesión en las opciones de Stylus:\nprimero haz clic en 'desconectar', y luego en 'conectar'."
},
"syncStorageErrorSaving": {
"message": "El valor no se puede guardar. Trate de reducir la longitud del texto."
@ -1476,6 +1361,9 @@
"usercssReplaceTemplateConfirmation": {
"message": "¿Reemplazar con el código actual la plantilla predeterminada para nuevos estilos UserCSS?"
},
"usercssReplaceTemplateName": {
"message": "@name vacío reemplaza la plantilla predeterminada"
},
"usercssReplaceTemplateSectionBody": {
"message": "Inserte el código aquí..."
},

File diff suppressed because it is too large Load Diff

View File

@ -67,6 +67,9 @@
"backupButtons": {
"message": "Varunda"
},
"backupMessage": {
"message": "Vali fail või lohista see siia lehele."
},
"bckpInstStyles": {
"message": "Ekspordi stiilid"
},
@ -290,6 +293,15 @@
"findStyles": {
"message": "Leia stiile"
},
"findStylesForSite": {
"message": "Leia sellele saidile veel stiile"
},
"findStylesInline": {
"message": "Aknasisene"
},
"findStylesInlineTooltip": {
"message": "Näita otsingu tulemusi selles aknas."
},
"genericAdd": {
"message": "Lisa"
},
@ -485,7 +497,7 @@
"message": "Tee halliks"
},
"manageFaviconsHelp": {
"message": "Stylus kasutab välist teenust https://icons.duckduckgo.com"
"message": "Stylus kasutab välist teenust https://www.google.com/s2/favicons"
},
"manageFilters": {
"message": "Filtrid"
@ -865,9 +877,6 @@
"sectionRestore": {
"message": "Taasta eemaldatud jaotis"
},
"sections": {
"message": "Jaotised"
},
"shortcuts": {
"message": "Otseteed"
},
@ -970,6 +979,9 @@
"styleRegexpProblemTooltip": {
"message": "\"regexp()\" vale kasutuse tõttu rakendamata jaotiste arv"
},
"styleRegexpTestButton": {
"message": "Regulaaravaldise katsetus"
},
"styleRegexpTestFull": {
"message": "Vastavad kaardid"
},
@ -1044,9 +1056,6 @@
"unreachableFileHint": {
"message": "Stylus saab ligi pääseda file:// URLidele ainult siis, kui märgistad vastava kasti Stylus laiendusel chrome://extensions lehel"
},
"unreachableMozSiteHintOldFF": {
"message": "Ainult Firefox 59 ja uuem on võimalik seadistada lubama WebExtensionsil lisada stiilielemente CSP-kaitstud saitidele nagu see."
},
"unzipStyles": {
"message": "Stiilide lahtipakkimine..."
},
@ -1106,6 +1115,9 @@
"usercssReplaceTemplateConfirmation": {
"message": "Asendad vaikimisi malli uutes kasutajacss stiilides praeguse koodiga?"
},
"usercssReplaceTemplateName": {
"message": "Tühi @name asendab vaikimisi malli"
},
"usercssReplaceTemplateSectionBody": {
"message": "Sisesta kood siia..."
},

View File

@ -52,12 +52,6 @@
"checkingForUpdate": {
"message": "Tarkistetaan..."
},
"confirmDelete": {
"message": "Poista"
},
"confirmSave": {
"message": "Tallenna"
},
"deleteStyleConfirm": {
"message": "Oletko varma että haluat poistaa tämän tyylin?"
},
@ -70,9 +64,6 @@
"disableStyleLabel": {
"message": "Poista Käytöstä"
},
"editDeleteText": {
"message": "Poista"
},
"editStyleHeading": {
"message": "Muokkaa Tyyliä"
},
@ -90,11 +81,8 @@
"enableStyleLabel": {
"message": "Aktivoi"
},
"genericAdd": {
"message": "Lisää"
},
"genericEnabledLabel": {
"message": "Aktivoitu"
"findStylesForSite": {
"message": "Hae lisää tyylejä tälle sivustolle"
},
"helpAlt": {
"message": "Apu"
@ -129,9 +117,6 @@
"sectionRemove": {
"message": "Poista osio"
},
"sections": {
"message": "Osiot"
},
"styleBadRegexp": {
"message": "Regexp ei kelpaa."
},

View File

@ -68,7 +68,7 @@
"message": "Sauvegarde"
},
"backupMessage": {
"message": "Pour importer le fichier de sauvegarder, glissez-déposez-le sur dans page ou cliquez sur le bouton Importer.\n\nPour export une sauvegarde compatible avec une version de Stylus plus ancienne que 1.5.18, effectuez un clic droit ou un appuie sur la touche Maj + clic sur le bouton Exporter."
"message": "Sélectionner un fichier ou le glisser-déposer sur cette page"
},
"bckpInstStyles": {
"message": "Exporter des styles"
@ -199,28 +199,6 @@
"copy": {
"message": "Copier dans le presse-papier"
},
"customNameHint": {
"message": "Entrez un nom personnalisé ici pour renommer le style dans l'IU sans casser les mises à jour"
},
"customNameResetHint": {
"message": "Arrête d'utiliser le nom personnalisé, change vers le propre nom du style"
},
"dateAbbrDay": {
"message": "$value$j",
"placeholders": {
"value": {
"content": "$1"
}
}
},
"dateAbbrYear": {
"message": "$value$a",
"placeholders": {
"value": {
"content": "$1"
}
}
},
"dateInstalled": {
"message": "Date d'installation"
},
@ -245,23 +223,9 @@
"disableAllStyles": {
"message": "Désactiver tous les styles"
},
"disableAllStylesOff": {
"message": "Les styles sont désactivés"
},
"disableStyleLabel": {
"message": "Désactiver"
},
"draftAction": {
"message": "Choisissez « Oui » pour charger ce brouillon ou « Non » pour l'abandonner."
},
"draftTitle": {
"message": "Sauvegarde du brouillon, créée $date$",
"placeholders": {
"date": {
"content": "$1"
}
}
},
"dragDropMessage": {
"message": "Glisser votre fichier de sauvegarde nimporte où sur cette page pour limporter."
},
@ -288,9 +252,6 @@
}
}
},
"editorSettings": {
"message": "Paramètres de léditeur"
},
"enableStyleLabel": {
"message": "Activer"
},
@ -300,9 +261,6 @@
"excludeStyleByUrlLabel": {
"message": "Exclure lURL actuelle"
},
"exportCompatible": {
"message": "Export (mode de compatibilité)"
},
"exportLabel": {
"message": "Exporter"
},
@ -338,6 +296,15 @@
"findStyles": {
"message": "Trouver des styles"
},
"findStylesForSite": {
"message": "Rechercher d'autres styles pour ce site"
},
"findStylesInline": {
"message": "Dans la popup"
},
"findStylesInlineTooltip": {
"message": "Affiche les résultats de recherche dans cette fenêtre"
},
"genericAdd": {
"message": "Ajouter"
},
@ -377,9 +344,6 @@
"gettingStyles": {
"message": "Récupération de tous les styles…"
},
"headerResizerHint": {
"message": "Maintenir la touche Maj pour redimensionner uniquement dans ce type d'interface, par exemple, éditeur, gestionnaire, installateur"
},
"helpAlt": {
"message": "Aide"
},
@ -401,12 +365,6 @@
"importLabel": {
"message": "Importer"
},
"importPreprocessor": {
"message": "Les styles avec un <code>@preprocessor</code> ne fonctionnera pas en mode classique. Vous pouvez changer l'éditeur en mode Usercss : 1) Ouvrez le gestionnaire de styles, 2) Activer l'option \"en tant que Usercss\", 3) Cliquez \"écrire un nouveau style\"\n\nImporter de toute façon ?"
},
"importPreprocessorTitle": {
"message": "Problème potentiel à cause de @preprocessor"
},
"importReplaceLabel": {
"message": "Remplacer le style"
},
@ -475,18 +433,9 @@
"linkGetHelp": {
"message": "Consulter l'aide"
},
"linkGetShareStyles": {
"message": "Obtenir et partager les styles"
},
"linkGetShareStylesInfo": {
"message": "Le nouveau userstyles.world géré par la communauté est créé par les auteurs d'userstyles dans le but de remplacer userstyles.org, qui a été tellement lent et inconscient durant l'année dernière que beaucoup d'auteurs ont arrêté de mettre à jour leurs styles."
},
"linkGetStyles": {
"message": "Obtenir des styles"
},
"linkGetStylesInfo": {
"message": "Ce site d'archive à été créé par un membre de la communauté userstyle pour archiver le lent et inconscient site userstyles.org. Cette archive est mise à jour approximativement chaque jour."
},
"linkTranslate": {
"message": "Traduire"
},
@ -554,7 +503,7 @@
"message": "Grisés"
},
"manageFaviconsHelp": {
"message": "Stylus utilise le service externe https://icons.duckduckgo.com"
"message": "Stylus utilise le service externe https://www.google.com/s2/favicons"
},
"manageFilters": {
"message": "Filtres"
@ -760,17 +709,6 @@
}
}
},
"meta_unknownMetaTypo": {
"message": "Peut-être @$keyOk$? Métadonnée inconnue: @$keyErr$",
"placeholders": {
"keyErr": {
"content": "$1"
},
"keyOk": {
"content": "$2"
}
}
},
"meta_unknownPreprocessor": {
"message": "@preprocessor inconnu: $preprocessor$",
"placeholders": {
@ -796,9 +734,6 @@
"noStylesForSite": {
"message": "Aucun style n'est installé pour ce site."
},
"numberedLine": {
"message": "Ligne :"
},
"openManage": {
"message": "Gestion"
},
@ -808,15 +743,6 @@
"optionsAdvanced": {
"message": "Avancé"
},
"optionsAdvancedAutoSwitchSchemeBySystem": {
"message": "Par préférence système"
},
"optionsAdvancedAutoSwitchSchemeByTime": {
"message": "Par horaire nocturne :"
},
"optionsAdvancedAutoSwitchSchemeNever": {
"message": "Désactivé. Le paramètre clair/obscur dans les styles est ignoré."
},
"optionsAdvancedContextDelete": {
"message": "Ajouter « Supprimer » dans le menu contextuel de lextension"
},
@ -826,24 +752,9 @@
"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 }"
},
"optionsAdvancedExposeStyleName": {
"message": "Afficher le nom du style"
},
"optionsAdvancedExposeStyleNameNote": {
"message": "Afficher le nom du style dans la page pour faciliter le débogage des styles dans les outils de développement. Veuillez recharger le ou les onglets pour appliquer ce nouveau paramètre."
},
"optionsAdvancedNewStyleAsUsercss": {
"message": "Écrire un nouveau style en tant que usercss"
},
"optionsAdvancedPatchCsp": {
"message": "Corrige <code>CSP</code> pour permettre les ressources de style"
},
"optionsAdvancedPatchCspNote": {
"message": "Activez cela si les styles comportent des images ou des polices qui échoue à charger sur les sites avec une politique de sécurité de contenus stricte <code>CSP</code> (<code>Content-Security-Policy</code>).\n\nActiver cette option permettra d'alléger les restrictions du <code>CSP</code>, permettant les styles essentiels de charger. Cette option n'est entendue que pour les utilisateurs expérimentés qui comprennent les potentielles implications de sécurité que cela introduit, et accepte la responsabilité de surveiller le contenu qu'ils autorisent. Veuillez lire les attaques basé sur le CSS pour plus dinformation.\n\nAussi sachez, que cette option en particulier n'est pas garanti de fonctionner si une autre extension modifie la réponse réseau avant."
},
"optionsAdvancedStyleViaXhr": {
"message": "Mode d'injection instantanée"
},
"optionsBadgeDisabled": {
"message": "Couleur d'arrière plan si désactivé"
},
@ -904,9 +815,6 @@
"optionsSyncNone": {
"message": "Aucun"
},
"optionsSyncPassword": {
"message": "Mot de passe"
},
"optionsSyncStatusConnected": {
"message": "Connecté"
},
@ -941,18 +849,12 @@
}
}
},
"optionsSyncStatusRelogin": {
"message": "Session expirée, veuillez vous reconnecter à nouveau."
},
"optionsSyncStatusSyncing": {
"message": "Synchronisation..."
},
"optionsSyncSyncNow": {
"message": "Synchroniser maintenant"
},
"optionsSyncUsername": {
"message": "Nom dutilisateur"
},
"optionsUpdateImportNote": {
"message": "Quand vous importez des sauvegardes de style dune ancienne version ou de Stylish, faites une vérification manuellement pour vous assurez que tous les styles sont à jour."
},
@ -1001,9 +903,6 @@
"popupMenuButtonTooltip": {
"message": "Actions"
},
"popupOpenEditInPopup": {
"message": "Utiliser une simple fenêtre (pas d'omnibox)"
},
"popupOpenEditInWindow": {
"message": "Ouvrir l'éditeur dans une nouvelle fenêtre"
},
@ -1016,41 +915,17 @@
"prefShowBadge": {
"message": "Afficher le nombre de styles actifs pour le site actuel sur le boutton Stylus"
},
"preferSchemeDark": {
"message": "Obscur"
},
"preferSchemeLight": {
"message": "Clair"
},
"preferSchemeNone": {
"message": "Aucun (quand appliqué)"
},
"previewLabel": {
"message": "Prévisualiser en direct"
},
"previewTooltip": {
"message": "Applique temporairement les changements sans sauvegarder.\nSauvegarde le style pour rendre les changements permanents."
},
"publish": {
"message": "Publier"
},
"publishPush": {
"message": "Pousser la mise à jour"
},
"publishReconnect": {
"message": "Essayez de vous déconnecter et de publier à nouveau"
},
"publishStyle": {
"message": "Publier le style"
},
"publishUsw": {
"message": "Utiliser <userstyles.world>"
},
"readingStyles": {
"message": "Lecture des styles…"
},
"reload": {
"message": "Recharger"
"message": "Recharger l´extension Stylus"
},
"replace": {
"message": "Remplacer"
@ -1067,18 +942,12 @@
"retrieveDropboxSync": {
"message": "Importer depuis Dropbox"
},
"saveAsTemplate": {
"message": "Sauvegarder comme modèle"
},
"search": {
"message": "Rechercher"
},
"searchCaseSensitive": {
"message": "Sensible à la casse"
},
"searchGlobalStyles": {
"message": "Chercher aussi des styles globaux."
},
"searchNumberOfResults": {
"message": "Nombre de correspondances"
},
@ -1094,12 +963,6 @@
"searchResultNoneFound": {
"message": "Aucun style trouvé pour ce site"
},
"searchResultNotMatching": {
"message": "Ce style est installé mais il ne s'applique pas au lien du site actuel."
},
"searchResultNotMatchingNote": {
"message": "Essaye de demander lauteur pour ajouter le lien l'URL.\n\nVous pouvez aussi ouvrir le style dans le gestionnaire et l'éditer soit-même,\nmais soyez avertis que cela désactive les mises à jour automatiques pour ce style."
},
"searchResultRating": {
"message": "Note"
},
@ -1109,21 +972,6 @@
"searchResultWeeklyCount": {
"message": "Installations hebdomadaires"
},
"searchStylesAll": {
"message": "Tout"
},
"searchStylesCode": {
"message": "Code CSS"
},
"searchStylesMatchUrl": {
"message": "Par lien"
},
"searchStylesMeta": {
"message": "Meta-données"
},
"searchStylesName": {
"message": "Nom"
},
"sectionAdd": {
"message": "Ajouter une section"
},
@ -1133,9 +981,6 @@
"sectionRestore": {
"message": "Restaurer la section supprimée"
},
"settings": {
"message": "Paramètres"
},
"shortcuts": {
"message": "Raccourcis"
},
@ -1169,9 +1014,6 @@
"styleBeautify": {
"message": "Embellir "
},
"styleBeautifyHint": {
"message": "Astuce : Cliquez-droit le bouton \"Beautify\" ou utilisez le raccourci clavier définit ci-dessous pour beautify sans montrer le panneau."
},
"styleBeautifyIndentConditional": {
"message": "Indenter @media, @supports"
},
@ -1187,9 +1029,6 @@
"styleEnabledLabel": {
"message": "Activé"
},
"styleExcludeLabel": {
"message": "Personnalisation des sites exclus"
},
"styleFromMozillaFormatError": {
"message": "Échec de l'importation depuis le format Mozilla"
},
@ -1232,21 +1071,9 @@
"styleMozillaFormatHeading": {
"message": "Format Mozilla"
},
"styleName": {
"message": "Nom du style"
},
"styleNotAppliedRegexpProblemTooltip": {
"message": "Le style n'a pu s'appliquer en raison d'une utilisation erronée de 'regexp()'"
},
"styleNotAppliedSchemeDark": {
"message": "Ce style est uniquement appliqué en mode sombre"
},
"styleNotAppliedSchemeLight": {
"message": "Ce style est uniquement appliqué en mode clair"
},
"stylePreferSchemeLabel": {
"message": "Mode clair/sombre"
},
"styleRegexpInvalidExplanation": {
"message": "Quelques règles 'regexp()' qui nont pas pu être compilées"
},
@ -1256,6 +1083,9 @@
"styleRegexpProblemTooltip": {
"message": "Nombre de sections non appliquées à cause dune utilisation incorrecte de 'regexp()'"
},
"styleRegexpTestButton": {
"message": "Test dexpression rationnelle"
},
"styleRegexpTestFull": {
"message": "Onglets correspondants"
},
@ -1277,9 +1107,6 @@
"styleSaveLabel": {
"message": "Enregistrer"
},
"styleSettings": {
"message": "Paramètres du style"
},
"styleToMozillaFormatHelp": {
"message": "Le code au format Mozilla peut être utilisé dans Stylish for Firefox et envoyé à userstyles.org."
},
@ -1297,9 +1124,6 @@
"styleUpdateDiscardChanges": {
"message": "Le style a été changé en dehors de léditeur. Voulez-vous recharger le style ?"
},
"styleUpdateUrlLabel": {
"message": "URL de mise à jour"
},
"stylusUnavailableForURL": {
"message": "Stylus ne fonctionne pas sur les pages de ce genre"
},
@ -1312,17 +1136,6 @@
"syncDropboxStyles": {
"message": "Exporter vers Dropbox"
},
"syncError": {
"message": "Synchronisation échouée"
},
"syncErrorLock": {
"message": "Cette base de données est déjà utilisée. Le verrouillage expirera le $TIME$",
"placeholders": {
"time": {
"content": "$1"
}
}
},
"syncStorageErrorSaving": {
"message": "La valeur ne peut pas être sauvegardée. Essayez de réduire la quantité de texte."
},
@ -1347,12 +1160,6 @@
"unreachableFileHint": {
"message": "Stylus peut accéder aux URL file:// uniquement si vous cochez la case correspondante pour lextension Stylus sur la page chrome://extensions."
},
"unreachableMozSiteHint": {
"message": "Dans Firefox 60 et plus récent vous devez retirer ce domaine de <extensions.webextensions.restrictedDomains> dans <about:config>."
},
"unreachableMozSiteHintOldFF": {
"message": "Seulement dans Firefox 59 et plus récent que les extensions-web peuvent être configurés pour autoriser de rajouter des éléments de style sur les sites protégés CSP comme celui-ci."
},
"unzipStyles": {
"message": "Décompression des styles…"
},
@ -1412,6 +1219,9 @@
"usercssReplaceTemplateConfirmation": {
"message": "Remplacer le modèle par défaut pour les nouveaux styles Usercss par le code actuel ?"
},
"usercssReplaceTemplateName": {
"message": "Un @name vide remplace le modèle par défaut"
},
"usercssReplaceTemplateSectionBody": {
"message": "Insérer le code ici..."
},

View File

@ -67,6 +67,9 @@
"backupButtons": {
"message": "גיבוי"
},
"backupMessage": {
"message": "בחר קובץ או גרור ושחרר אותו בדף זה."
},
"bckpInstStyles": {
"message": "ייצא עיצובים"
},
@ -293,6 +296,15 @@
"findStyles": {
"message": "מצא עיצובים"
},
"findStylesForSite": {
"message": "מצא סגנונות נוספים לאתר זה"
},
"findStylesInline": {
"message": "מוטבע"
},
"findStylesInlineTooltip": {
"message": "הצג תוצאות חיפוש בחלון זה."
},
"genericAdd": {
"message": "הוספה"
},
@ -484,6 +496,9 @@
"manageFaviconsGray": {
"message": "האפרת האייקונים"
},
"manageFaviconsHelp": {
"message": "Stylus משתמש בשירות חיצוני https://www.google.com/s2/favicons"
},
"manageFilters": {
"message": "מסננים"
},
@ -688,9 +703,6 @@
"optionsSyncSyncNow": {
"message": "סנכרן כעת"
},
"optionsSyncUrl": {
"message": "כתובת אתר"
},
"optionsUpdateImportNote": {
"message": "בעת ייבוא גיבויים לסגנון מגרסה ישנה או מ־Stylish, בדוק באופן חד פעמי אם יש עדכונים באופן ידני במנהל הסגנונות כדי לוודא שכל הסגנונות מעודכנים."
},
@ -754,6 +766,9 @@
"readingStyles": {
"message": "קורא עיצובים..."
},
"reload": {
"message": "טען מחדש את Stylus"
},
"replace": {
"message": "החלף"
},
@ -811,9 +826,6 @@
"sectionRestore": {
"message": "שחזר סעיף שהוסר"
},
"sections": {
"message": "סעיפים"
},
"shortcuts": {
"message": "קיצורי מקשים"
},
@ -907,6 +919,9 @@
"styleRegexpProblemTooltip": {
"message": "מספר המקטעים שלא הוחלו עקב שימוש שגוי ב־'regexp()'"
},
"styleRegexpTestButton": {
"message": "בדוק RegExp"
},
"styleRegexpTestFull": {
"message": "כרטיסיות תואמות"
},

View File

@ -67,6 +67,9 @@
"backupButtons": {
"message": "Biztonsági mentés"
},
"backupMessage": {
"message": "Válassz ki egy fájlt vagy húzd erre az oldalra!"
},
"bckpInstStyles": {
"message": "Stílusok exportálása"
},
@ -287,6 +290,15 @@
"findStyles": {
"message": "Stílusok keresése"
},
"findStylesForSite": {
"message": "További stílusok keresése ehhez az oldalhoz"
},
"findStylesInline": {
"message": "Helyben"
},
"findStylesInlineTooltip": {
"message": "Keresési eredmények megjelenítése ebben az ablakban."
},
"genericAdd": {
"message": "Hozzáadás"
},
@ -476,7 +488,7 @@
"message": "Megjelenítés szürkítve"
},
"manageFaviconsHelp": {
"message": "A Stylus külső szolgáltatást használ (https://icons.duckduckgo.com)"
"message": "A Stylus külső szolgáltatást használ (https://www.google.com/s2/favicons)"
},
"manageFilters": {
"message": "Szűrők"
@ -710,9 +722,6 @@
"openManage": {
"message": "Kezelés"
},
"openOptions": {
"message": "Beállítások"
},
"openStylesManager": {
"message": "Stíluskezelő megnyitása"
},
@ -966,9 +975,6 @@
"sectionRestore": {
"message": "Eltávolított szekció visszaállítása"
},
"sections": {
"message": "Szekciók"
},
"shortcuts": {
"message": "Gyorsbillentyűk"
},
@ -1071,6 +1077,9 @@
"styleRegexpProblemTooltip": {
"message": "A nem alkalmazott szeckiók száma helytelen 'regexp()' használat miatt"
},
"styleRegexpTestButton": {
"message": "RegExp tesztelése"
},
"styleRegexpTestFull": {
"message": "Illeszkedő fülek"
},
@ -1142,9 +1151,6 @@
"unreachableFileHint": {
"message": "A Stylus csak akkor képes hozzáférni a file:// URL-ekhez, ha engedélyezed az erre vonatkozó beállítást a Stylus kiegészítőre a chrome://extensions oldalon."
},
"unreachableMozSiteHintOldFF": {
"message": "Csak a Firefox 59 vagy annál újabb állítható be az ilyen CSP-védett oldalak stílusának átszabása WebExtensions kiegészítőkön keresztül."
},
"unzipStyles": {
"message": "Stílusok kibontása..."
},
@ -1204,6 +1210,9 @@
"usercssReplaceTemplateConfirmation": {
"message": "Le legyen cserélve az alapértelmezett sablon az új Usercss stílusokhoz a jelenlegi kóddal?"
},
"usercssReplaceTemplateName": {
"message": "Az üres @name lecseréli az alapértelmezett sablont"
},
"usercssReplaceTemplateSectionBody": {
"message": "Írj kódot ide…"
},

View File

@ -1,9 +1,6 @@
{
"InaccessibleFileHint": {
"message": "Stylus non può accedere ad alcuni tipi di file (ad es. pdf & JSON)"
},
"addStyleLabel": {
"message": "Scrivi un nuovo stile"
"message": "Scrivi nuovo stile"
},
"addStyleTitle": {
"message": "Aggiungi stili"
@ -62,7 +59,7 @@
"message": "Autore"
},
"backupMessage": {
"message": "Per importare il file di backup, selezionalo e rilascialo in questa pagina oppure utilizza il bottone Importa\n\nPer esportare un backup compatibile con Stylus versione 1.5.18 e oltre, selezionalo col tasto destro oppure shift-click il pulsante Esporta"
"message": "Seleziona un file o trascinalo in questa pagina."
},
"bckpInstStyles": {
"message": "Esporta stili"
@ -127,9 +124,6 @@
"cm_theme": {
"message": "Tema"
},
"colorpickerSwitchFormatTooltip": {
"message": "Cambia formato: HEX -> RGB -> HSL.\nShift-click per invertire la direzione.\nAnche con i tasti PgUp (PaginaSu), PgDn (PaginaGiù)."
},
"colorpickerTooltip": {
"message": "Apri selettore colore"
},
@ -166,18 +160,6 @@
"confirmYes": {
"message": "Si"
},
"connectingDropboxNotAllowed": {
"message": "La connessione a Dropbox è disponibile soltanto nelle app installate direttamente dal webstore"
},
"copied": {
"message": "Copiato negli appunti"
},
"copy": {
"message": "Copia negli appunti"
},
"customNameHint": {
"message": "Inserisci qui un nome personalizzato per rinominare lo stile nell'interfaccia utente (UI) senza rompere i suoi aggiornamenti"
},
"dateInstalled": {
"message": "Data installazione"
},
@ -257,6 +239,12 @@
"findStyles": {
"message": "Trova stili"
},
"findStylesForSite": {
"message": "Trova più stili per questo sito"
},
"findStylesInlineTooltip": {
"message": "Visualizza risultati in questa finestra."
},
"genericAdd": {
"message": "Aggiungi"
},
@ -411,6 +399,9 @@
"liveReloadLabel": {
"message": "Ricaricamento live"
},
"manageFaviconsHelp": {
"message": "Stylus utilizza un servizio esterno https://www.google.com/s2/favicons"
},
"manageFilters": {
"message": "Filtri"
},
@ -497,9 +488,6 @@
"openManage": {
"message": "Gestisci gli stili installati"
},
"openOptions": {
"message": "Opzioni"
},
"openStylesManager": {
"message": "Apri gestore stili"
},
@ -638,9 +626,6 @@
"sectionRemove": {
"message": "Rimuovi sezione"
},
"sections": {
"message": "Sezioni"
},
"shortcuts": {
"message": "Scorciatoie"
},
@ -728,6 +713,9 @@
"styleRegexpProblemTooltip": {
"message": "Numero di sezioni non applicato a causa dell'uso errato di 'regexp()'"
},
"styleRegexpTestButton": {
"message": "Test RegExp"
},
"styleRegexpTestInvalid": {
"message": "Regexp invalida ignorata"
},
@ -766,9 +754,6 @@
"unreachableContentScript": {
"message": "Impossibile comunicare con la pagina. Prova a ricaricare la scheda."
},
"unreachableMozSiteHintOldFF": {
"message": "Solo Firefox 59 e successivi possono essere configurati per consentire alle WebExtensions di aggiungere elementi degli stili su siti CSP-protected come questo."
},
"updateAllCheckSucceededNoUpdate": {
"message": "Nessun aggiornamento trovato"
},

View File

@ -68,7 +68,7 @@
"message": "バックアップ"
},
"backupMessage": {
"message": "バックアップファイルをインポートする場合、このページにドラッグ&ドロップするか、インポートボタンをクリックしてください。\nまた、Stylus 1.5.18 より古い形式でバックアップしたい場合は、エクスポートボタンを 右クリック もしくは shift-クリック してください。"
"message": "ファイルを選択するか、このページにドラッグ&ドロップしてください。"
},
"bckpInstStyles": {
"message": "スタイルをエクスポート"
@ -226,23 +226,9 @@
"disableAllStyles": {
"message": "すべてのスタイルをオフにする"
},
"disableAllStylesOff": {
"message": "全スタイルをOFFにする"
},
"disableStyleLabel": {
"message": "無効化"
},
"draftAction": {
"message": "このデータをロードするならば「はい」を、破棄するならば「いいえ」を選択してください。"
},
"draftTitle": {
"message": "$date$に作成されたデータ",
"placeholders": {
"date": {
"content": "$1"
}
}
},
"dragDropMessage": {
"message": "このページの任意の場所にバックアップファイルをドロップしてインポートします。"
},
@ -269,9 +255,6 @@
}
}
},
"editorSettings": {
"message": "エディタ設定"
},
"enableStyleLabel": {
"message": "有効化"
},
@ -281,9 +264,6 @@
"excludeStyleByUrlLabel": {
"message": "現在のURLを除外"
},
"exportCompatible": {
"message": "エクスポート(互換モード)"
},
"exportLabel": {
"message": "エクスポート"
},
@ -322,6 +302,15 @@
"findStyles": {
"message": "スタイルを検索"
},
"findStylesForSite": {
"message": "このサイト用のスタイルを検索"
},
"findStylesInline": {
"message": "結果を下に表示"
},
"findStylesInlineTooltip": {
"message": "検索結果をこのウィンドウに表示"
},
"genericAdd": {
"message": "追加"
},
@ -355,12 +344,6 @@
"genericSavedMessage": {
"message": "保存しました"
},
"genericSize": {
"message": "サイズ"
},
"genericTest": {
"message": "テスト"
},
"genericTitle": {
"message": "タイトル"
},
@ -370,9 +353,6 @@
"gettingStyles": {
"message": "全スタイルを取得中..."
},
"headerResizerHint": {
"message": "この種のUIエディタ、管理、インストーラの中では、Shiftを押しながらリサイズしてください"
},
"helpAlt": {
"message": "ヘルプ"
},
@ -550,7 +530,7 @@
"message": "ファビコンをグレー表示"
},
"manageFaviconsHelp": {
"message": "Stylusは外部サービスを使用します https://icons.duckduckgo.com"
"message": "Stylusは外部サービスを使用します https://www.google.com/s2/favicons"
},
"manageFilters": {
"message": "フィルター"
@ -561,9 +541,6 @@
"manageMaxTargets": {
"message": "適用先欄の表示件数"
},
"manageMinColumnWidth": {
"message": "最小カラム幅 (ピクセル単位。9999にすると、マルチカラムモードを無効にします)"
},
"manageNewStyleAsUsercss": {
"message": "Usercssとして"
},
@ -813,15 +790,6 @@
"optionsAdvanced": {
"message": "上級者向け"
},
"optionsAdvancedAutoSwitchSchemeBySystem": {
"message": "システム設定による"
},
"optionsAdvancedAutoSwitchSchemeByTime": {
"message": "夜間時間による"
},
"optionsAdvancedAutoSwitchSchemeNever": {
"message": "無効。スタイルの暗い/明るい設定は無視されます。"
},
"optionsAdvancedContextDelete": {
"message": "エディタのコンテキストメニューに「削除」を追加する"
},
@ -831,12 +799,6 @@
"optionsAdvancedExposeIframesNote": {
"message": "各iframeにトップサイトドメインを公開します。\n次のようにiframe用のCSSを書くことができるようになります:\nhtml[stylus-iframe$$=\"twitter.com\"] h1 { display:none }"
},
"optionsAdvancedExposeStyleName": {
"message": "スタイル名を公開"
},
"optionsAdvancedExposeStyleNameNote": {
"message": "開発ツールでスタイルのデバッグを容易にするため、ページ内にスタイル名を公開します。タブをリロードして新しい設定を適用してください。"
},
"optionsAdvancedNewStyleAsUsercss": {
"message": "新しいスタイルを usercss として作成します"
},
@ -882,9 +844,6 @@
"optionsHeading": {
"message": "オプション"
},
"optionsIconAuto": {
"message": "暗い/明るいモード設定に合わせます"
},
"optionsIconDark": {
"message": "暗いブラウザのテーマ"
},
@ -907,7 +866,7 @@
"message": "オプションをリセット"
},
"optionsStylusThemes": {
"message": "このページを含む任意のStylusのページで、ブラウザのツールバー内のStylusアイコンをクリックして、「スタイルを見つける」をクリックします"
"message": "StylusのUIテーマを見つける"
},
"optionsSubheading": {
"message": "その他のオプション"
@ -924,9 +883,6 @@
"optionsSyncNone": {
"message": "未選択"
},
"optionsSyncPassword": {
"message": "パスワード"
},
"optionsSyncStatusConnected": {
"message": "接続状態"
},
@ -970,9 +926,6 @@
"optionsSyncSyncNow": {
"message": "すぐに同期"
},
"optionsSyncUsername": {
"message": "ユーザ名"
},
"optionsUpdateImportNote": {
"message": "旧バージョンまたはStylishからスタイルのバックアップをインポートする場合は、スタイルマネージャで一度手動で更新チェックを行い、すべてのスタイルが確実に更新されるようにしてください。"
},
@ -1015,9 +968,6 @@
"popupHotkeysTooltip": {
"message": "クリックすると使用可能なホットキーを表示します"
},
"popupManageSiteStyles": {
"message": "サイトのスタイルを管理"
},
"popupManageTooltip": {
"message": "シフトを押しながらクリック または 右クリック で、現在のサイトに適用可能なスタイルの管理画面を開きます"
},
@ -1039,21 +989,6 @@
"prefShowBadge": {
"message": "現在のサイトで有効なスタイルの数を表示"
},
"preferScheme": {
"message": "暗い/明るいモード設定"
},
"preferSchemeAlways": {
"message": "グローバルな暗い/明るいモード設定が無効になっているため、現在は無視されます(スタイルは常に適用されます)"
},
"preferSchemeDark": {
"message": "暗い"
},
"preferSchemeLight": {
"message": "明るい"
},
"preferSchemeNone": {
"message": "なし(常に適用)"
},
"previewLabel": {
"message": "自動プレビュー"
},
@ -1082,7 +1017,7 @@
"message": "スタイルを読み込み中..."
},
"reload": {
"message": "リロード"
"message": "Stylus拡張をリロードする"
},
"replace": {
"message": "置換"
@ -1093,18 +1028,12 @@
"replaceWith": {
"message": "次に置換"
},
"restoreTemplate": {
"message": "デフォルトのテンプレートに戻す。\n\n現在エディタで開いているものは変化しません。"
},
"retrieveBckp": {
"message": "スタイルをインポート"
},
"retrieveDropboxSync": {
"message": "Dropboxからインポート"
},
"saveAsTemplate": {
"message": "テンプレートとして保存"
},
"search": {
"message": "検索"
},
@ -1145,7 +1074,7 @@
"message": "週間インストール数"
},
"searchStyleQueryHint": {
"message": "スタイル名を検索します (大文字を使用すると、大文字小文字を区別して検索します) :\n複数の単語 - すべての単語を (順不同で) 含む\n\"複数の単語からなるフレーズ\" - 正確なフレーズを (引用符は除いて) 探す\n/foo.*bar/i - 正規表現 (空白は使用不可。空白を使いたい場合は、代わりに \\s を使ってください)"
"message": "スタイル名を大文字小文字の区別なく検索します:\n複数の単語 - 全単語を順番の区別なく検索します\n\"複数の単語からなるフレーズ\" - 正確なフレーズを (引用符は除いて) 検索します\n2020 - このように数値 (年) を指定すると、2020年に更新されたスタイルも結果に表示します"
},
"searchStylesAll": {
"message": "すべて"
@ -1180,18 +1109,12 @@
"sections": {
"message": "セクション"
},
"settings": {
"message": "設定"
},
"shortcuts": {
"message": "ショートカット"
},
"shortcutsNote": {
"message": "キーボードショートカットを定義する"
},
"shortcutsNoteFF": {
"message": "Firefox 66+ では、ビルトインのショートカットUIを手動で開けます。\n1ツールバーのStylusアイコンを右クリックして、「管理」を選択します。\nもしくは、メインメニューでabout:addonsを開くかCtrl-Shift-Aを押します\n2開いたページの右上隅にある歯車のアイコンをクリックします。\n3「拡張機能のショートカットの管理」を選択します。\n\nまた、ショートカットをここで設定することもできます。"
},
"sortDateNewestFirst": {
"message": "新しい順"
},
@ -1237,30 +1160,12 @@
"styleEnabledLabel": {
"message": "有効"
},
"styleExcludeLabel": {
"message": "含めないサイトを設定"
},
"styleFromMozillaFormatError": {
"message": "Mozilla形式のインポートに失敗しました"
},
"styleFromMozillaFormatPrompt": {
"message": "Mozilla形式のコードを貼り付ける"
},
"styleIncludeLabel": {
"message": "含めるサイトを設定"
},
"styleInjectionImportance": {
"message": "スタイルの重要度を設定/解除する"
},
"styleInjectionOrder": {
"message": "スタイルの適用順"
},
"styleInjectionOrderHint": {
"message": "ドラッグ&ドロップでスタイルの位置を変更できます。スタイルは表示の順番に適用されるため、リストのより下にあるスタイルは、上のものを上書きできます。"
},
"styleInjectionOrderHint_prio": {
"message": "以下に表示される重要なスタイルは、常に最後に挿入されるため、新しくインストールしたスタイルを上書きできます。スタイルをクリックして重要度を設定/解除してください。"
},
"styleInstall": {
"message": "「$stylename$」を Stylus にインストールしますか?",
"placeholders": {
@ -1300,15 +1205,6 @@
"styleNotAppliedRegexpProblemTooltip": {
"message": "'regexp()' の誤った使用のためスタイルを適用できませんでした"
},
"styleNotAppliedSchemeDark": {
"message": "スタイルはダークモード時のみ適用されます"
},
"styleNotAppliedSchemeLight": {
"message": "スタイルはライトモード時のみ適用されます"
},
"stylePreferSchemeLabel": {
"message": "暗い/明るい モード"
},
"styleRegexpInvalidExplanation": {
"message": "いくつかの 'regexp()' のルールはコンパイルできませんでした"
},
@ -1318,6 +1214,9 @@
"styleRegexpProblemTooltip": {
"message": "'regexp()' の誤った使用のためいくつかのセクションを適用できませんでした"
},
"styleRegexpTestButton": {
"message": "正規表現をテスト"
},
"styleRegexpTestFull": {
"message": "一致するタブ"
},
@ -1339,9 +1238,6 @@
"styleSaveLabel": {
"message": "保存"
},
"styleSettings": {
"message": "スタイル設定"
},
"styleToMozillaFormatHelp": {
"message": "Mozilla形式のコードは、userstyles.org に投稿することができ、また従来の Stylish for Firefox でも使用できます"
},
@ -1359,9 +1255,6 @@
"styleUpdateDiscardChanges": {
"message": "このスタイルはエディタの外部で変更されました。このスタイルをリロードしますか?"
},
"styleUpdateUrlLabel": {
"message": "更新URL"
},
"stylusUnavailableForURL": {
"message": "このページではStylusは動作しません。"
},
@ -1377,16 +1270,8 @@
"syncError": {
"message": "同期に失敗"
},
"syncErrorLock": {
"message": "データベースが使用中です。\nロック状態は$TIME$に解除されます。",
"placeholders": {
"time": {
"content": "$1"
}
}
},
"syncErrorRelogin": {
"message": "同期に失敗しました。ログアウトした可能性があります。\nStylusのオプション画面で再ログインしてください。"
"message": "同期に失敗しました。\nStylusのオプション画面で、再ログインしてください :\n「切断」をクリック後、「接続」をクリック"
},
"syncStorageErrorSaving": {
"message": "値を保存できません。テキストの量を減らしてください。"
@ -1477,6 +1362,9 @@
"usercssReplaceTemplateConfirmation": {
"message": "現在のコードで、新しいUsercssスタイルのデフォルト・テンプレートを置き換えますか"
},
"usercssReplaceTemplateName": {
"message": "@name が指定されていません"
},
"usercssReplaceTemplateSectionBody": {
"message": "ここにコードを挿入..."
},

View File

@ -67,6 +67,9 @@
"backupButtons": {
"message": "백업"
},
"backupMessage": {
"message": "파일을 선택하거나, 이 페이지로 파일을 드래그앤드롭하세요."
},
"bckpInstStyles": {
"message": "스타일 내보내기"
},
@ -133,9 +136,6 @@
"cm_theme": {
"message": "테마"
},
"colorpickerPaletteHint": {
"message": "견본을 우클릭하여 소스 코드와 번갈아 가며 확인"
},
"colorpickerSwitchFormatTooltip": {
"message": "16 진수-> RGB -> HSL 순서로 형식을 변환합니다.\nShift+클릭을 이용해 역순으로 변환할 수 있습니다.\nPgUp(PageUp), PgDn(PageDown) 키도 사용 가능합니다."
},
@ -302,15 +302,21 @@
"findStyles": {
"message": "스타일 찾기"
},
"findStylesForSite": {
"message": "현재 사이트에 맞는 스타일 찾기"
},
"findStylesInline": {
"message": "팝업창 내에서"
},
"findStylesInlineTooltip": {
"message": "이 창에서 검색 결과 표시"
},
"genericAdd": {
"message": "추가"
},
"genericClone": {
"message": "복제"
},
"genericDescription": {
"message": "설명"
},
"genericDisabledLabel": {
"message": "비활성화됨"
},
@ -439,18 +445,9 @@
"linkGetHelp": {
"message": "도움말"
},
"linkGetShareStyles": {
"message": "스타일 주고받기"
},
"linkGetShareStylesInfo": {
"message": "커뮤니티에 의해 새롭게 운영되는 userstyles.world 사이트는 userstyle 투고자들이 userstyle.org를 대체하기 위해 만들었습니다. userstyle.org는 지난 몇 년간 많은 투고자들이 스타일을 업데이트하는 것을 멈춰서 느리고 둔감해졌습니다."
},
"linkGetStyles": {
"message": "스타일 찾기"
},
"linkGetStylesInfo": {
"message": "본 아카이브 사이트는 느리고 응답이 늦는 userstyles.org를 백업하기 위해 Userstyle 커뮤니티 회원에 의해 제작되었습니다. 하루에 약 한 번 아카이브 내 콘텐츠가 갱신됩니다."
},
"linkStylusWiki": {
"message": "위키"
},
@ -521,7 +518,7 @@
"message": "회색으로 표시"
},
"manageFaviconsHelp": {
"message": "Stylus는 https://icons.duckduckgo.com 외부 서비스를 이용합니다"
"message": "Stylus는 https://www.google.com/s2/favicons 외부 서비스를 이용합니다"
},
"manageFilters": {
"message": "필터"
@ -727,17 +724,6 @@
}
}
},
"meta_unknownMetaTypo": {
"message": "혹시 @$keyOk$입니까? 알 수 없는 메타데이터: @$keyErr$",
"placeholders": {
"keyErr": {
"content": "$1"
},
"keyOk": {
"content": "$2"
}
}
},
"meta_unknownPreprocessor": {
"message": "알 수 없는 @preprocessor $preprocessor$",
"placeholders": {
@ -796,15 +782,9 @@
"optionsAdvancedPatchCsp": {
"message": "스타일 자원을 허용하도록 <code>CSP</code>패치하기"
},
"optionsAdvancedPatchCspNote": {
"message": "엄격한 <code>콘텐츠 보안 정책</code>(<code>Content-Security-Policy; CSP</code>)이 적용된 사이트에서 사진이나 글꼴이 포함된 스타일을 불러올 수 없는 경우 이 설정을 켜십시오.\n\n설정을 켜면 <code>CSP</code> 제한이 풀려, 필수적인 스타일 콘텐츠를 불러올 수 있도록 합니다. 이 설정은 잠재적인 보안 영향을 이해하고, 콘텐츠 모니터링 허용 시 발생하는 문제에 대한 책임을 질 수 있는 사용자에게만 권장됩니다. 자세한 정보를 알고 싶다면 CSS 기반 공격에 대해 알아 보십시오.\n\n또, 이 설정은 설치된 다른 확장 프로그램이 네트워크 응답을 먼저 수정하는 경우 작동하지 않을 수도 있으므로 이 점에 유의하십시오."
},
"optionsAdvancedStyleViaXhr": {
"message": "즉시 주입 모드"
},
"optionsAdvancedStyleViaXhrNote": {
"message": "만약 브라우징 도중 스타일이 적용되지 않은 콘텐츠가 깜빡거리며 나오는 경우(FOUC), 특히 다크 테마를 사용하는 중 눈에 두드러지게 깜빡이는 경우 이 설정을 켜십시오.\n\n기술적으로 Chrome/Chromium이 확장 프로그램과의 비동기 통신을 미루기 때문에 발생하는 문제이며, 페이지를 더 빨리 불러오려는 시도이지만 보통은 무의미하고 스타일이 늦게 적용되도록 하는 요인으로 작용합니다. 확장 프로그램에는 이를 회피하기 위한 동기형 API가 제공되지 않으므로, Stylus는 \"비권장\" 상태인 동기형 XMLHttpRquest 웹 API를 사용하여 스타일을 가져오는 설정을 제공합니다. 페이지를 서버에서 내려받는 몇 밀리초 내에 이런 과정이 끝나므로 로드 속도에는 악영향이 없습니다.\n\n그럼에도 불구하고 Chromium 브라우저에서는 개발자 도구의 콘솔에 경고를 출력할 것입니다. 경고를 우클릭하여 숨기면 이후에는 경고가 표시되지 않습니다."
},
"optionsBadgeDisabled": {
"message": "비활성화 시의 배경 색"
},
@ -856,6 +836,9 @@
"optionsResetButton": {
"message": "설정 초기화"
},
"optionsStylusThemes": {
"message": "스타일러스 UI 테마 찾기"
},
"optionsSubheading": {
"message": "추가 옵션"
},
@ -983,27 +966,12 @@
"previewTooltip": {
"message": "변경 사항은 저장되지 않고 일시적으로 적용됩니다.\n영구적으로 변경하려면 스타일을 저장하세요."
},
"publish": {
"message": "배포"
},
"publishPush": {
"message": "업데이트 푸시"
},
"publishReconnect": {
"message": "연결을 해제한 뒤 배포를 다시 시도해보세요"
},
"publishRetry": {
"message": "Stylus에서는 계속 이 스타일을 배포하려고 하고 있습니다. 하지만 인증 창이 뜨지 않는 경우 다시 시도할 수 있습니다. 지금 다시 시도하시겠습니까?"
},
"publishStyle": {
"message": "스타일 배포"
},
"publishUsw": {
"message": "<userstyles.world> 사용"
},
"readingStyles": {
"message": "스타일 읽어들이는 중..."
},
"reload": {
"message": "Stylus 확장 프로그램 리로드"
},
"replace": {
"message": "바꾸기"
},
@ -1064,9 +1032,6 @@
"searchStylesCode": {
"message": "CSS 코드"
},
"searchStylesHelp": {
"message": "</> 또는 <Ctrl-F> 키를 누르면 검색창에 포커스합니다.\nDefault mode is plain text search for all space-separated terms in any order.\n정확한 단어 검색: 검색어를 큰따옴표로 감쌉니다. 예: <\".header ~ div\">\n정규표현식: 빗금 및 플래그 문자를 포함합니다. 예: </body.*?\\ba\\b/i>\nURL로 찾기: 완전히 특정된 URL에 적용되는 스타일을 검색합니다. 예: https://www.example.org/\n메타데이터로 찾기: 이름, \"적용 대상\" 지정자, 설치 URL, 업데이트 URL 등 usercss 스타일의 모든 메타데이터 블록을 기반으로 검색합니다."
},
"searchStylesMatchUrl": {
"message": "URL별"
},
@ -1181,9 +1146,6 @@
"styleMozillaFormatHeading": {
"message": "Mozilla 형식"
},
"styleName": {
"message": "스타일 이름"
},
"styleNotAppliedRegexpProblemTooltip": {
"message": "잘못된 'regexp()'의 사용으로 인해 스타일을 적용할 수 없습니다"
},
@ -1196,6 +1158,9 @@
"styleRegexpProblemTooltip": {
"message": "잘못된 'regexp()' 사용으로 인해 몇몇 섹션을 적용할 수 없습니다."
},
"styleRegexpTestButton": {
"message": "정규 표현식 테스트"
},
"styleRegexpTestFull": {
"message": "일치하는 탭 목록"
},
@ -1273,12 +1238,6 @@
"unreachableFileHint": {
"message": "Stylus가 file:// URL에 접근할 수 있도록 하려면 chrome://extensions 페이지에서 파일 URL에 대한 액세스를 허용해야 합니다."
},
"unreachableMozSiteHint": {
"message": "Firefox 60 이후의 버전에서는 <about:config> 내의 <extensions.webextensions.restrictedDomains>에서 이 도메인을 제거해야 합니다. "
},
"unreachableMozSiteHintOldFF": {
"message": "Firefox 59 버전 이상에서만 이처럼 CSP 보호 정책이 적용된 사이트에 웹 확장 기능으로 스타일 요소를 추가할 수 있도록 설정 가능합니다."
},
"unzipStyles": {
"message": "스타일 압축 푸는 중..."
},
@ -1338,6 +1297,9 @@
"usercssReplaceTemplateConfirmation": {
"message": "현재 코드로 새 Usercss 스타일의 기본 템플릿을 대체하시겠습니까?"
},
"usercssReplaceTemplateName": {
"message": "@name을 비우면 기본 템플릿이 대체됩니다."
},
"usercssReplaceTemplateSectionBody": {
"message": "코드를 여기 입력하세요"
},

View File

@ -62,13 +62,13 @@
"message": "Alle updates toepassen"
},
"author": {
"message": "Maker"
"message": "Auteur"
},
"backupButtons": {
"message": "Back-up"
},
"backupMessage": {
"message": "Sleep het back-upbestand naar deze pagina of klik op de knop Importeren om het te importeren.\n\nRechtsklik of shift-klik op de knop Exporteren om een compatibele back-up voor Stylus ouder dan 1.5.18 te exporteren."
"message": "Selecteer een bestand of sleep het naar deze pagina."
},
"bckpInstStyles": {
"message": "Stijlen exporteren"
@ -98,7 +98,7 @@
"message": "Automatisch aanvullen tijdens typen"
},
"cm_colorpicker": {
"message": "Kleurkiezers voor CSS-kleuren"
"message": "Kleurkiezer voor CSS-kleuren"
},
"cm_indentWithTabs": {
"message": "Tabs met slimme inspringing gebruiken"
@ -245,23 +245,9 @@
"disableAllStyles": {
"message": "Alle stijlen uitschakelen"
},
"disableAllStylesOff": {
"message": "Stijlen zijn uitgeschakeld"
},
"disableStyleLabel": {
"message": "Uitschakelen"
},
"draftAction": {
"message": "Klik op Ja om dit concept te laden, of op Nee om het te verwerpen."
},
"draftTitle": {
"message": "Conceptherstel; gemaakt: $date$",
"placeholders": {
"date": {
"content": "$1"
}
}
},
"dragDropMessage": {
"message": "Sleep uw back-upbestand naar deze pagina om het te importeren."
},
@ -288,9 +274,6 @@
}
}
},
"editorSettings": {
"message": "Editor-instellingen"
},
"enableStyleLabel": {
"message": "Inschakelen"
},
@ -300,9 +283,6 @@
"excludeStyleByUrlLabel": {
"message": "De huidige URL uitsluiten"
},
"exportCompatible": {
"message": "Exporteren (compatibele modus)"
},
"exportLabel": {
"message": "Exporteren"
},
@ -338,6 +318,12 @@
"findStyles": {
"message": "Stijlen zoeken"
},
"findStylesForSite": {
"message": "Meer stijlen voor deze website zoeken"
},
"findStylesInlineTooltip": {
"message": "Zoekresultaten binnen dit venster weergeven"
},
"genericAdd": {
"message": "Toevoegen"
},
@ -371,12 +357,6 @@
"genericSavedMessage": {
"message": "Opgeslagen"
},
"genericSize": {
"message": "Grootte"
},
"genericTest": {
"message": "Testen"
},
"genericTitle": {
"message": "Titel"
},
@ -386,9 +366,6 @@
"gettingStyles": {
"message": "Alle stijlen ophalen..."
},
"headerResizerHint": {
"message": "Houd Shift ingedrukt om alleen in dit type UI de grootte te wijzigen, m.a.w. editor, beheerder, installer"
},
"helpAlt": {
"message": "Hulp"
},
@ -563,7 +540,7 @@
"message": "Niet beschikbaar"
},
"manageFaviconsHelp": {
"message": "Stylus gebruikt een externe dienst: https://icons.duckduckgo.com"
"message": "Stylus gebruikt een externe dienst: https://www.google.com/s2/favicons"
},
"manageHeading": {
"message": "Geïnstalleerde stijlen"
@ -571,9 +548,6 @@
"manageMaxTargets": {
"message": "Aantal Van toepassing op-items"
},
"manageMinColumnWidth": {
"message": "Minimale kolombreedte (in pixels; 9999 schakelt de modus met meerdere kolommen uit)"
},
"manageNewStyleAsUsercss": {
"message": "als Usercss"
},
@ -823,15 +797,6 @@
"optionsAdvanced": {
"message": "Geavanceerd"
},
"optionsAdvancedAutoSwitchSchemeBySystem": {
"message": "Systeemvoorkeur volgen"
},
"optionsAdvancedAutoSwitchSchemeByTime": {
"message": "Door nachttijd:"
},
"optionsAdvancedAutoSwitchSchemeNever": {
"message": "Uitgeschakeld. De instelling voor donker/licht in stijlen wordt genegeerd."
},
"optionsAdvancedContextDelete": {
"message": "Verwijderen toevoegen in contextmenu van editor"
},
@ -841,12 +806,6 @@
"optionsAdvancedExposeIframesNote": {
"message": "Legt het bovenliggende domein van de website bloot in elk iframe.\nSchakelt schrijven in voor iframe-specifieke CSS, zoals:\nhtml[stylus-iframe$$=\"twitter.com\"] h1 { display:none }"
},
"optionsAdvancedExposeStyleName": {
"message": "Stijlnaam tonen"
},
"optionsAdvancedExposeStyleNameNote": {
"message": "Toont de stijlnaam in de pagina om het opsporen van stijlfouten in devtools mogelijk te maken. Vernieuw de tabbladen om de nieuwe instelling toe te passen."
},
"optionsAdvancedNewStyleAsUsercss": {
"message": "Nieuwe stijl schrijven als usercss"
},
@ -889,9 +848,6 @@
"optionsHeading": {
"message": "Opties"
},
"optionsIconAuto": {
"message": "Volgens de Donkere/Lichte modus"
},
"optionsIconDark": {
"message": "Donkere browserthemas"
},
@ -914,7 +870,7 @@
"message": "Standaardwaarden"
},
"optionsStylusThemes": {
"message": "Klik op een willekeurige Stylus-pagina (waaronder deze) op het Stylus-pictogram in de browserwerkbalk, en klik daarna op Stijlen zoeken."
"message": "Een UI-thema voor Stylus zoeken"
},
"optionsSubheading": {
"message": "Meer opties"
@ -931,9 +887,6 @@
"optionsSyncNone": {
"message": "Geen"
},
"optionsSyncPassword": {
"message": "Wachtwoord"
},
"optionsSyncStatusConnected": {
"message": "Gekoppeld"
},
@ -977,9 +930,6 @@
"optionsSyncSyncNow": {
"message": "Nu synchroniseren"
},
"optionsSyncUsername": {
"message": "Gebruikersnaam"
},
"optionsUpdateImportNote": {
"message": "Als u stijlen vanuit een oudere versie of vanuit Stylish importeert, controleer dan eenmalig op updates in de stijlbeheerder om er zeker van te zijn dat alle stijlen zijn bijgewerkt."
},
@ -1022,9 +972,6 @@
"popupHotkeysTooltip": {
"message": "Klik om beschikbare sneltoetsen te tonen"
},
"popupManageSiteStyles": {
"message": "Websitestijlen beheren"
},
"popupManageTooltip": {
"message": "Shift-klik of rechtsklik opent de beheerder met stijlen die op de huidige website van toepassing zijn"
},
@ -1046,21 +993,6 @@
"prefShowBadge": {
"message": "Aantal actieve stijlen voor de huidige website"
},
"preferScheme": {
"message": "Voorkeur voor Donkere/Lichte modus"
},
"preferSchemeAlways": {
"message": "Momenteel genegeerd (de stijl is altijd van toepassing), omdat de globale Donkere/Lichte modus is uitgeschakeld"
},
"preferSchemeDark": {
"message": "Donker"
},
"preferSchemeLight": {
"message": "Licht"
},
"preferSchemeNone": {
"message": "Geen (altijd toegepast)"
},
"previewLabel": {
"message": "Live-voorbeeld"
},
@ -1089,7 +1021,7 @@
"message": "Stijlen lezen..."
},
"reload": {
"message": "Opnieuw laden"
"message": "Stylus-extensie herladen"
},
"replace": {
"message": "Vervangen"
@ -1100,18 +1032,12 @@
"replaceWith": {
"message": "Vervangen door"
},
"restoreTemplate": {
"message": "De standaardsjabloon herstellen.\n\n(De momenteel geopende editorpaginas worden niet gewijzigd.)"
},
"retrieveBckp": {
"message": "Stijlen importeren"
},
"retrieveDropboxSync": {
"message": "Dropbox - Importeren"
},
"saveAsTemplate": {
"message": "Opslaan als sjabloon"
},
"search": {
"message": "Zoeken"
},
@ -1152,7 +1078,7 @@
"message": "Wekelijks aantal installaties"
},
"searchStyleQueryHint": {
"message": "Stijlnamen doorzoeken (hoofdlettergevoelig als een hoofdletter wordt gebruikt):\nenkele woorden - al deze woorden in willekeurige volgorde\n\"bepaalde woordgroep\" - deze exacte woordgroep zonder aanhalingstekens\n/foo.*bar/i - reguliere expressie zonder spaties (gebruik in plaats daarvan \\s)"
"message": "Stijlnamen niet hoofdlettergevoelig doorzoeken:\nbepaalde woorden - alle woorden in willekeurige volgorde\n\"bepaalde woordgroep\" - deze exacte woordgroep zonder aanhalingstekens\n2020 - een jaar als dit toont ook stijlen die in 2020 zijn bijgewerkt"
},
"searchStylesAll": {
"message": "Alles"
@ -1184,18 +1110,12 @@
"sections": {
"message": "Secties"
},
"settings": {
"message": "Instellingen"
},
"shortcuts": {
"message": "Sneltoetsen"
},
"shortcutsNote": {
"message": "Sneltoetsen definiëren"
},
"shortcutsNoteFF": {
"message": "In Firefox 66+ kunt u de ingebouwde UI voor sneltoetsen handmatig openen:\n1) klik met rechts op het Stylus-pictogram in de werkbalk en kies Extensie beheren\n(als alternatief kunt u about:addons openen via het hoofdmenu of Ctrl-Shift-A),\n2) klik in de pagina die wordt geopend op het tandwielpictogram in de rechterbovenhoek,\n3) kies Extensiesneltoetsen beheren.\n\nHier kunt u ook de sneltoetsen aanpassen."
},
"sortDateNewestFirst": {
"message": "nieuwste eerst"
},
@ -1241,30 +1161,12 @@
"styleEnabledLabel": {
"message": "Ingeschakeld"
},
"styleExcludeLabel": {
"message": "Eigen uitgesloten websites"
},
"styleFromMozillaFormatError": {
"message": "Importeren vanuit Mozilla-opmaak mislukt"
},
"styleFromMozillaFormatPrompt": {
"message": "Plak de code in Mozilla-opmaak"
},
"styleIncludeLabel": {
"message": "Eigen opgenomen websites"
},
"styleInjectionImportance": {
"message": "Urgentie van stijl bepalen"
},
"styleInjectionOrder": {
"message": "Stijlinjectievolgorde"
},
"styleInjectionOrderHint": {
"message": "Versleep een stijl om de positie ervan te wijzigen. Stijlen worden geïnjecteerd op basis van de onderstaande volgorde, dus een stijl die lager op de lijst staat, kan de eerdere stijlen vervangen."
},
"styleInjectionOrderHint_prio": {
"message": "Belangrijke stijlen hieronder worden altijd als laatste geïnjecteerd, zodat ze eventuele pas geïnstalleerde stijlen kunnen vervangen. Klik op het sterpictogram van een stijl om de urgentie ervan te bepalen."
},
"styleInstall": {
"message": "$stylename$ installeren in Stylus?",
"placeholders": {
@ -1307,15 +1209,6 @@
"styleNotAppliedRegexpProblemTooltip": {
"message": "Stijl is niet toegepast vanwege onjuist gebruik van regexp()"
},
"styleNotAppliedSchemeDark": {
"message": "Deze stijl wordt alleen in Donkere modus toegepast"
},
"styleNotAppliedSchemeLight": {
"message": "Deze stijl wordt alleen in Lichte modus toegepast"
},
"stylePreferSchemeLabel": {
"message": "Donkere/Lichte modus"
},
"styleRegexpInvalidExplanation": {
"message": "Sommige regexp()-regels konden niet worden gecompileerd."
},
@ -1325,6 +1218,9 @@
"styleRegexpProblemTooltip": {
"message": "Aantal secties dat niet wordt toegepast vanwege onjuist gebruik van regexp()"
},
"styleRegexpTestButton": {
"message": "RegExp-test"
},
"styleRegexpTestFull": {
"message": "Overeenkomende tabbladen"
},
@ -1341,14 +1237,11 @@
"message": "Niet volledig overeenkomend, dus overgeslagen"
},
"styleRegexpTestTitle": {
"message": "Lijst met overeenkomende geopende tabbladen (klik op de URL om de focus op het tabblad te leggen)"
"message": "Lijst van overeenkomende geopende tabbladen (klik op de URL om de focus op het tabblad te leggen)"
},
"styleSaveLabel": {
"message": "Opslaan"
},
"styleSettings": {
"message": "Stijlinstellingen"
},
"styleToMozillaFormatHelp": {
"message": "De Mozilla-opmaak van de code kan bij userstyles.org worden ingediend en met het klassieke Stylish voor Firefox worden gebruikt."
},
@ -1366,9 +1259,6 @@
"styleUpdateDiscardChanges": {
"message": "Deze stijl is buiten de editor om gewijzigd. Wilt u de stijl opnieuw laden?"
},
"styleUpdateUrlLabel": {
"message": "Update-URL"
},
"stylusUnavailableForURL": {
"message": "Stylus werkt niet op paginas als deze."
},
@ -1384,16 +1274,8 @@
"syncError": {
"message": "Synchronisatie mislukt"
},
"syncErrorLock": {
"message": "De database is al in gebruik. De vergrendeling vervalt om $TIME$.",
"placeholders": {
"time": {
"content": "$1"
}
}
},
"syncErrorRelogin": {
"message": "Synchronisatie mislukt. U bent afgemeld.\nProbeer u opnieuw aan te melden in de Stylus-opties."
"message": "Synchronisatie mislukt.\nProbeer u opnieuw aan te melden in de Stylus-opties:\nklik eerst op ontkoppelen, daarna op koppelen."
},
"syncStorageErrorSaving": {
"message": "De waarde kan niet worden opgeslagen. Probeer de hoeveelheid tekst te verminderen."
@ -1484,6 +1366,9 @@
"usercssReplaceTemplateConfirmation": {
"message": "Wilt u de standaardsjabloon voor nieuwe Usercss-stijlen vervangen door de huidige code?"
},
"usercssReplaceTemplateName": {
"message": "Lege @name vervangt de standaardsjabloon"
},
"usercssReplaceTemplateSectionBody": {
"message": "Voer hier code in..."
},

View File

@ -71,7 +71,7 @@
"message": "Kopia zapasowa"
},
"backupMessage": {
"message": "Aby zaimportować plik kopii zapasowej, przeciągnij go i upuść na tej stronie lub kliknij przycisk Importuj.\n\nAby wyeksportować kompatybilną kopię zapasową Stylusa w wersji starszej niż 1.5.18, kliknij prawym przyciskiem myszy lub kliknij z wciśniętym klawiszem Shift przycisk Eksportuj."
"message": "Wybierz plik lub przeciągnij i upuść go na tę stronę."
},
"bckpInstStyles": {
"message": "Eksportuj style"
@ -264,23 +264,9 @@
"disableAllStyles": {
"message": "Wyłącz wszystkie style"
},
"disableAllStylesOff": {
"message": "Style są wyłączone"
},
"disableStyleLabel": {
"message": "Wyłącz"
},
"draftAction": {
"message": "Wybierz 'Tak', aby załadować tę wersję roboczą lub 'Nie', aby ją odrzucić."
},
"draftTitle": {
"message": "Odzyskiwanie wersji roboczej, utworzonej $date$",
"placeholders": {
"date": {
"content": "$1"
}
}
},
"dragDropMessage": {
"message": "Upuść twój plik kopii zapasowej gdziekolwiek na tej stronie, aby zaimportować."
},
@ -307,9 +293,6 @@
}
}
},
"editorSettings": {
"message": "Ustawienia edytora"
},
"enableStyleLabel": {
"message": "Włącz"
},
@ -319,9 +302,6 @@
"excludeStyleByUrlLabel": {
"message": "Wyklucz bieżący adres URL"
},
"exportCompatible": {
"message": "Eksportuj (tryb kompatybilny)"
},
"exportLabel": {
"message": "Eksportuj"
},
@ -360,6 +340,15 @@
"findStyles": {
"message": "Znajdź style"
},
"findStylesForSite": {
"message": "Znajdź więcej stylów dla tej strony"
},
"findStylesInline": {
"message": "Wstawka"
},
"findStylesInlineTooltip": {
"message": "Wyświetlaj wyniki wyszukiwania w tym oknie."
},
"genericAdd": {
"message": "Dodaj"
},
@ -393,12 +382,6 @@
"genericSavedMessage": {
"message": "Zapisano"
},
"genericSize": {
"message": "Rozmiar"
},
"genericTest": {
"message": "Testuj"
},
"genericTitle": {
"message": "Tytuł"
},
@ -408,9 +391,6 @@
"gettingStyles": {
"message": "Uzyskiwanie wszystkich stylów..."
},
"headerResizerHint": {
"message": "Przytrzymaj Shift, aby zmienić rozmiar tylko w tego typu interfejsie, tj. edytorze, menedżerze, instalatorze"
},
"helpAlt": {
"message": "Pomoc"
},
@ -585,7 +565,7 @@
"message": "Wyszarzone"
},
"manageFaviconsHelp": {
"message": "Stylus korzysta z usługi zewnętrznej https://icons.duckduckgo.com"
"message": "Stylus korzysta z usługi zewnętrznej https://www.google.com/s2/favicons"
},
"manageFilters": {
"message": "Filtry"
@ -596,9 +576,6 @@
"manageMaxTargets": {
"message": "Liczba dotyczących elementów "
},
"manageMinColumnWidth": {
"message": "Minimalna szerokość kolumny (w pikselach; 9999 wyłącza tryb wielokolumnowy)"
},
"manageNewStyleAsUsercss": {
"message": "jako Usercss"
},
@ -848,15 +825,6 @@
"optionsAdvanced": {
"message": "Zaawansowane"
},
"optionsAdvancedAutoSwitchSchemeBySystem": {
"message": "Według preferencji systemowych"
},
"optionsAdvancedAutoSwitchSchemeByTime": {
"message": "W nocy:"
},
"optionsAdvancedAutoSwitchSchemeNever": {
"message": "Wyłączone. Ustawienie ciemny/jasny w stylach jest ignorowane."
},
"optionsAdvancedContextDelete": {
"message": "Dodaj 'Usuń' do menu kontekstowego edytora"
},
@ -866,12 +834,6 @@
"optionsAdvancedExposeIframesNote": {
"message": "Odsłania domenę najwyższego poziomu w każdym elemencie iframe.\nWłącza pisanie CSS specyficznych dla iframe w taki sposób:\nhtml[stylus-iframe$$=\"twitter.com\"] h1 { display:none }"
},
"optionsAdvancedExposeStyleName": {
"message": "Eksponuj nazwę stylu"
},
"optionsAdvancedExposeStyleNameNote": {
"message": "Eksponuje nazwę stylu na stronie, aby ułatwić debugowanie stylów w narzędziach dla twórców witryn. Załaduj ponownie kartę(-y), aby zastosować nowe ustawienie."
},
"optionsAdvancedNewStyleAsUsercss": {
"message": "Napisz nowy styl jako usercss"
},
@ -917,9 +879,6 @@
"optionsHeading": {
"message": "Opcje"
},
"optionsIconAuto": {
"message": "Dopasuj tryb ciemny/jasny"
},
"optionsIconDark": {
"message": "Ciemne motywy przeglądarki"
},
@ -942,7 +901,7 @@
"message": "Resetuj opcje"
},
"optionsStylusThemes": {
"message": "Kliknij ikonę Stylusa na pasku narzędzi przeglądarki na dowolnej stronie Stylusa, w tym tej, a następnie kliknij 'Znajdź style'"
"message": "Znajdź motyw interfejsu Stylusa"
},
"optionsSubheading": {
"message": "Więcej opcji"
@ -959,9 +918,6 @@
"optionsSyncNone": {
"message": "Brak"
},
"optionsSyncPassword": {
"message": "Hasło"
},
"optionsSyncStatusConnected": {
"message": "Połączono"
},
@ -1005,12 +961,6 @@
"optionsSyncSyncNow": {
"message": "Synchronizuj teraz"
},
"optionsSyncUrl": {
"message": "Adres URL"
},
"optionsSyncUsername": {
"message": "Nazwa użytkownika"
},
"optionsUpdateImportNote": {
"message": "Podczas importowania kopii zapasowych stylu ze starej wersji lub ze Stylish jednorazowo sprawdź aktualizacje ręcznie w menedżerze stylów, aby upewnić się, że wszystkie style są zaktualizowane."
},
@ -1053,9 +1003,6 @@
"popupHotkeysTooltip": {
"message": "Kliknij, aby zobaczyć dostępne skróty klawiszowe"
},
"popupManageSiteStyles": {
"message": "Zarządzaj stylami witryny"
},
"popupManageTooltip": {
"message": "Shift + kliknięcie lub kliknięcie prawym przyciskiem otwiera menedżera ze stylami obowiązującymi dla bieżącej witryny"
},
@ -1077,21 +1024,6 @@
"prefShowBadge": {
"message": "Liczba aktywnych stylów dla bieżącej witryny"
},
"preferScheme": {
"message": "Preferencje trybu ciemnego/jasnego"
},
"preferSchemeAlways": {
"message": "Obecnie ignorowane (styl zawsze obowiązuje), ponieważ globalny tryb ciemny/jasny jest wyłączony"
},
"preferSchemeDark": {
"message": "Ciemny"
},
"preferSchemeLight": {
"message": "Jasny"
},
"preferSchemeNone": {
"message": "Brak (zawsze stosowane)"
},
"previewLabel": {
"message": "Podgląd na żywo"
},
@ -1120,7 +1052,7 @@
"message": "Odczytywanie stylów..."
},
"reload": {
"message": "Przeładuj"
"message": "Przeładuj rozszerzenie Stylus"
},
"replace": {
"message": "Zamień"
@ -1131,18 +1063,12 @@
"replaceWith": {
"message": "Zamień na"
},
"restoreTemplate": {
"message": "Przywróć szablon domyślny.\n\n(Aktualnie otwarte strony edytora nie ulegną zmianie.)"
},
"retrieveBckp": {
"message": "Importuj style"
},
"retrieveDropboxSync": {
"message": "Importuj z Dropboksa"
},
"saveAsTemplate": {
"message": "Zapisz jako szablon"
},
"search": {
"message": "Szukaj"
},
@ -1183,7 +1109,7 @@
"message": "Tygodniowa liczba instalacji"
},
"searchStyleQueryHint": {
"message": "Szukaj nazwy stylów (z uwzględnieniem wielkości liter, jeśli używana jest wielka litera):\njakieś słowa wszystkie te słowa w dowolnej kolejności\n\"jakaś fraza\" ta fraza bez cudzysłowów\n/foo.*bar/i wyrażenie regularne bez spacji (zamiast tego użyj \\s)"
"message": "Szukaj nazw stylów bez rozróżniania wielkości liter:\njakieś słowa - wszystkie słowa w dowolnej kolejności\n\"jakieś zdanie\" - dokładnie to zdanie bez cudzysłowów\n2020 - rok jak ten pokazuje również style zaktualizowane w 2020"
},
"searchStylesAll": {
"message": "Wszystkie"
@ -1218,18 +1144,12 @@
"sections": {
"message": "Sekcje"
},
"settings": {
"message": "Ustawienia"
},
"shortcuts": {
"message": "Skróty"
},
"shortcutsNote": {
"message": "Zdefiniuj skróty klawiaturowe"
},
"shortcutsNoteFF": {
"message": "W przeglądarce Firefox 66+ możesz ręcznie otworzyć interfejs wbudowanych skrótów:\n1) kliknij prawym przyciskiem myszy ikonę Stylusa na pasku narzędzi i wybierz 'Zarządzaj'\n(alternatywnie otwórz about:addon z menu głównego lub Ctrl-Shift-A),\n2) na stronie, która się otworzy, kliknij ikonę koła zębatego w prawym górnym rogu,\n3) wybierz 'Zarządzaj skrótami rozszerzeń'.\n\nMożesz także dostosować skróty tutaj."
},
"sortDateNewestFirst": {
"message": "najpierw najnowsze"
},
@ -1275,30 +1195,12 @@
"styleEnabledLabel": {
"message": "Włączony"
},
"styleExcludeLabel": {
"message": "Niestandardowe wykluczone witryny"
},
"styleFromMozillaFormatError": {
"message": "Nie udało się zaimportować z formatu Mozilla"
},
"styleFromMozillaFormatPrompt": {
"message": "Wprowadź kod w formacie Mozilla"
},
"styleIncludeLabel": {
"message": "Niestandardowe uwzględnione witryny"
},
"styleInjectionImportance": {
"message": "Przełącz ważność stylu"
},
"styleInjectionOrder": {
"message": "Kolejność wstrzykiwania stylów"
},
"styleInjectionOrderHint": {
"message": "Przeciągnij i upuść styl, aby zmienić jego pozycję. Style są wstrzykiwane sekwencyjnie w kolejności pokazanej poniżej, dzięki czemu styl znajdujący się dalej na liście może zastąpić wcześniejsze style."
},
"styleInjectionOrderHint_prio": {
"message": "Ważne style wymienione poniżej są zawsze wstrzykiwane jako ostatnie, dzięki czemu mogą zastąpić nowo zainstalowane style. Kliknij oznaczenie stylu, aby zmienić jego ważność."
},
"styleInstall": {
"message": "Zainstalować '$stylename$' do Stylusa?",
"placeholders": {
@ -1341,15 +1243,6 @@
"styleNotAppliedRegexpProblemTooltip": {
"message": "Styl nie został zastosowany z powodu nieprawidłowego użycia 'regexp()'"
},
"styleNotAppliedSchemeDark": {
"message": "Ten styl jest stosowany tylko w trybie ciemnym"
},
"styleNotAppliedSchemeLight": {
"message": "Ten styl jest stosowany tylko w trybie jasnym"
},
"stylePreferSchemeLabel": {
"message": "Tryb ciemny/jasny"
},
"styleRegexpInvalidExplanation": {
"message": "Niektóre reguły 'regexp()', których nie można było w ogóle skompilować."
},
@ -1359,6 +1252,9 @@
"styleRegexpProblemTooltip": {
"message": "Liczba sekcji, które nie zostały zastosowane z powodu nieprawidłowego użycia 'regexp()'"
},
"styleRegexpTestButton": {
"message": "Test RegExp"
},
"styleRegexpTestFull": {
"message": "Dopasowane karty"
},
@ -1380,9 +1276,6 @@
"styleSaveLabel": {
"message": "Zapisz"
},
"styleSettings": {
"message": "Ustawienia stylu"
},
"styleToMozillaFormatHelp": {
"message": "Kod w formacie Mozilla może być przesłany do userstyles.org i użyty z klasycznym dodatkiem Stylish dla Firefoksa"
},
@ -1400,9 +1293,6 @@
"styleUpdateDiscardChanges": {
"message": "Styl zmieniono poza edytorem. Czy chcesz przeładować styl?"
},
"styleUpdateUrlLabel": {
"message": "Adres URL aktualizacji"
},
"stylusUnavailableForURL": {
"message": "Stylus nie działa na takich stronach."
},
@ -1418,16 +1308,8 @@
"syncError": {
"message": "Synchronizacja nie powiodła się"
},
"syncErrorLock": {
"message": "Baza danych jest już w użyciu. Blokada wygaśnie o $TIME$",
"placeholders": {
"time": {
"content": "$1"
}
}
},
"syncErrorRelogin": {
"message": "Synchronizacja nie powiodła się. Wylogowano.\nSpróbuj ponownie zalogować się w opcjach Stylusa."
"message": "Synchronizacja nie powiodła się.\nSpróbuj ponownie zalogować się w opcjach Stylusa:\nkliknij najpierw 'rozłącz' , następnie 'połącz'."
},
"syncStorageErrorSaving": {
"message": "Nie można zapisać wartości. Spróbuj zmniejszyć ilość tekstu."
@ -1518,6 +1400,9 @@
"usercssReplaceTemplateConfirmation": {
"message": "Zastąpić domyślny szablon dla nowych stylów Usercss aktualnym kodem?"
},
"usercssReplaceTemplateName": {
"message": "Puste @name zastępuje szablon domyślny"
},
"usercssReplaceTemplateSectionBody": {
"message": "Wstaw kod tutaj..."
},

View File

@ -64,6 +64,9 @@
"author": {
"message": "Autor"
},
"backupMessage": {
"message": "Selecione um arquivo ou arraste e solte nessa página."
},
"bckpInstStyles": {
"message": "Exportar estilos"
},
@ -121,9 +124,6 @@
"cm_selectByTokens": {
"message": "Clicar duas vezes seleciona tokens"
},
"cm_selectByTokensTooltip": {
"message": "Exemplos de tokens: .foo-bar-2 #aabbcc 0.32 !Important\nQuando desativado: as palavras delimitadas por pontuação são selecionadas."
},
"cm_smartIndent": {
"message": "Usar indentação inteligente"
},
@ -231,9 +231,6 @@
"disableAllStyles": {
"message": "Desativar todos os estilos"
},
"disableAllStylesOff": {
"message": "Estilos estão desligados"
},
"disableStyleLabel": {
"message": "Desativar"
},
@ -310,15 +307,21 @@
"findStyles": {
"message": "Encontrar estilos"
},
"findStylesForSite": {
"message": "Procurar mais estilos para este site"
},
"findStylesInline": {
"message": "Em linha"
},
"findStylesInlineTooltip": {
"message": "Mostrar resultados dentro dessa janela"
},
"genericAdd": {
"message": "Adicionar"
},
"genericClone": {
"message": "Clonar"
},
"genericDescription": {
"message": "Descrição"
},
"genericDisabledLabel": {
"message": "Desativado"
},
@ -507,63 +510,27 @@
"liveReloadInstallHintFF": {
"message": "Mantenha juntamente esta guia e a guia original abertas para atualizar automaticamente o estilo sob mudanças externas."
},
"liveReloadLabel": {
"message": "Recarregamento dinâmico"
},
"manageFavicons": {
"message": "Favicons em colunas de aplica-se a"
},
"manageFaviconsGray": {
"message": "Acinzentado(s)"
},
"manageFaviconsHelp": {
"message": "O Stylus usa um serviço externo https://icons.duckduckgo.com"
},
"manageFilters": {
"message": "Filtros"
},
"manageHeading": {
"message": "Estilos instalados"
},
"manageMaxTargets": {
"message": "Número de aplica-se a itens"
},
"manageNewStyleAsUsercss": {
"message": "como UserCSS"
},
"manageNewUI": {
"message": "Nova interface do gestor"
},
"manageOnlyDisabled": {
"message": "Somente estilos desativados"
},
"manageOnlyEnabled": {
"message": "Apenas estilos habilitados"
},
"manageOnlyExternal": {
"message": "Apenas estilos externos"
},
"manageOnlyLocal": {
"message": "Apenas estilos criados localmente"
},
"manageOnlyLocalTooltip": {
"message": "(os estilos não instalados através de uma página userstyles.org)"
},
"manageOnlyNonUsercss": {
"message": "Apenas estilos sem UserCSS"
},
"manageOnlyUpdates": {
"message": "Apenas com atualizações ou problemas"
},
"manageOnlyUsercss": {
"message": "Apenas estilos com UserCSS"
},
"menuShowBadge": {
"message": "Mostrar a contagem de estilos ativados"
},
"meta_invalidCheckboxDefault": {
"message": "Inválida @var checkbox: o valor deve ser 0 ou 1"
},
"meta_invalidNumber": {
"message": "Espera-se um número"
},
@ -634,15 +601,6 @@
"optionsActions": {
"message": "Ações"
},
"optionsAdvanced": {
"message": "Avançadas"
},
"optionsAdvancedContextDelete": {
"message": "Adicionar 'Eliminar' no menu de contexto do editor"
},
"optionsAdvancedExposeIframes": {
"message": "Expor iframes via HTML[stylus-iframe]"
},
"optionsAdvancedNewStyleAsUsercss": {
"message": "Escrever novo estilo como UserCSS"
},
@ -658,12 +616,6 @@
"optionsCheckUpdate": {
"message": "Verifique e instale todas as atualizações disponíveis"
},
"optionsCustomizeBadge": {
"message": "Distintivo no ícone da barra de ferramentas"
},
"optionsCustomizeIcon": {
"message": "Ícone da barra de ferramentas"
},
"optionsCustomizeSync": {
"message": "Sincronizar para a nuvem"
},
@ -673,12 +625,6 @@
"optionsHeading": {
"message": "Opções"
},
"optionsIconDark": {
"message": "Temas escuros do browser"
},
"optionsIconLight": {
"message": "Temas claros do browser"
},
"optionsOpen": {
"message": "Abrir"
},
@ -688,12 +634,6 @@
"optionsPopupWidth": {
"message": "Largura do popup (em pixels)"
},
"optionsReset": {
"message": "Restabelecer as opções aos valores predefinidos"
},
"optionsResetButton": {
"message": "Restabelecer opções"
},
"optionsSubheading": {
"message": "Mais Opções"
},
@ -706,9 +646,6 @@
"optionsSyncNone": {
"message": "Nenhum"
},
"optionsSyncPassword": {
"message": "Senha"
},
"optionsSyncStatusConnected": {
"message": "Conectado"
},
@ -727,86 +664,20 @@
"optionsSyncSyncNow": {
"message": "Sincronizar agora"
},
"optionsUpdateImportNote": {
"message": "Ao importar uma cópia de segurança de estilo da versão antiga ou do Stylish, faça uma verificação única de atualizações manualmente no gestor de estilos para garantir que todos os estilos sejam atualizados."
},
"optionsUpdateInterval": {
"message": "Intervalo de atualização automática do estilo de usuário em horas (especifique 0 para desativar)"
},
"overwriteFileExport": {
"message": "Você gostaria de substituir um arquivo existente?"
},
"paginationCurrent": {
"message": "Pagina atual"
},
"paginationEstimated": {
"message": "Número estimado de páginas"
},
"paginationNext": {
"message": "Próxima página"
},
"paginationPrevious": {
"message": "Pagina anterior"
},
"paginationTotal": {
"message": "Páginas totais"
},
"parseUsercssError": {
"message": "Não foi possível analisar o UserCSS:"
},
"popupBorders": {
"message": "Adicionar margens laterais brancas "
},
"popupBordersTooltip": {
"message": "Útil para temas escuros no novo Chrome, já que não pinta mais as margens laterais"
},
"popupHotkeysInfo": {
"message": "<1>- <9>,<0>, também no teclado numérico - alterna o Nth estilo (0 é 10)\n<A>- <Z>alterna o primeiro estilo com um nome que começa com a letra\n<Shift>abre o editor em vez de alternar\n<Numpad +>ativa estilos listados\n<Numpad >desativa estilos listados\n<Numpad *>e <`> (backtick) - alterna os estilos inicialmente ativados; não se aplica a estilos habilitados subsequentemente enquanto o popup está aberto, para que você possa restaurar a seleção inicial depois de testar o material: basta desabilitar todos e, em seguida, alternar, i.e. <Numpad ><Numpad *>\nMais informações no wiki"
},
"popupHotkeysTooltip": {
"message": "Clique para ver as teclas de atalho disponíveis"
},
"popupManageTooltip": {
"message": "Shift-clique ou clique com o botão direito abre o gestor com estilos aplicáveis ao site atual"
},
"popupOpenEditInWindow": {
"message": "Abra o editor em uma nova janela"
},
"popupOpenEditInWindowTooltip": {
"message": "Também ativado ao desanexar o separador do editor de uma janela do browser, e desativado por anexar um separador único do editor a outra janela."
},
"popupStylesFirst": {
"message": "Estilos antes de comandos"
},
"prefShowBadge": {
"message": "Número de estilos ativos para o site atual"
},
"preferSchemeDark": {
"message": "Escuro"
},
"preferSchemeLight": {
"message": "Claro"
},
"preferSchemeNone": {
"message": "Nenhum (sempre aplicado)"
},
"previewLabel": {
"message": "Pré-visualização dinâmica"
},
"previewTooltip": {
"message": "Temporariamente aplica as alterações sem guardar.\nGuarde o estilo para tornar as alterações permanentes."
},
"publish": {
"message": "Publicar"
},
"publishStyle": {
"message": "Publicar estilo"
},
"readingStyles": {
"message": "Lendo estilos..."
},
"reload": {
"message": "Recarregar"
"message": "Recarregar a extensão do Stylus"
},
"replace": {
"message": "Substituir"
@ -826,39 +697,9 @@
"search": {
"message": "Buscar"
},
"searchCaseSensitive": {
"message": "Sensível a maiúsculas e minúsculas"
},
"searchGlobalStyles": {
"message": "Também buscar estilos globais"
},
"searchNumberOfResults": {
"message": "Número de correspondências"
},
"searchNumberOfResults2": {
"message": "Número de correspondências no código e aplica-se a valores"
},
"searchRegexp": {
"message": "Use a sintaxe /re/ para busca por regexp"
},
"searchResultInstallCount": {
"message": "Instalações totais"
},
"searchResultNoneFound": {
"message": "Nenhum estilo encontrado para este site."
},
"searchResultRating": {
"message": "Classificação"
},
"searchResultUpdated": {
"message": "Atualizado"
},
"searchResultWeeklyCount": {
"message": "Instalações semanais"
},
"searchStylesCode": {
"message": "Código CSS"
},
"sectionAdd": {
"message": "Adicionar outra seção"
},
@ -868,39 +709,9 @@
"sectionRemove": {
"message": "Remover seção"
},
"sectionRestore": {
"message": "Restaurar secção removida"
},
"sections": {
"message": "Seções"
},
"shortcuts": {
"message": "Atalhos"
},
"shortcutsNote": {
"message": "Definir atalhos de teclado"
},
"sortDateNewestFirst": {
"message": "mais recente primeiro"
},
"sortDateOldestFirst": {
"message": "mais antigos primeiro"
},
"sortLabel": {
"message": "Selecione uma ordenação para aplicar aos estilos instalados"
},
"sortLabelTitleAsc": {
"message": "Título Ascendente"
},
"sortLabelTitleDesc": {
"message": "Título Descendente"
},
"sortStylesHelp": {
"message": "Escolha o tipo de ordenação a ser aplicado às entradas instaladas na lista suspensa de ordenação. A configuração predefinida aplica uma ordem crescente (A a Z) aos títulos de entrada. As ordenações dentro do grupo \"Título Decrescente\" aplicarão uma classificação decrescente (Z a A) ao título. Existem outras predefinições que permitirão classificar as entradas por vários critérios. Pense sobre isso como ordenar uma tabela com várias colunas e cada categoria em cada seleção (entre os sinais de mais) representa uma coluna ou grupo. Por exemplo, se a configuração for \"Ativado (primeiro) + Título\", as entradas serão ordenadas de modo que todas as entradas ativadas sejam classificadas no topo da lista, então uma classificação crescente de título de entrada (A a Z) será aplicada a ambas as entradas ativadas e desativadas separadamente."
},
"sortStylesHelpTitle": {
"message": "Ordenar conteúdos"
},
"styleBadRegexp": {
"message": "Expressão regular é inválida"
},
@ -910,12 +721,6 @@
"styleBeautifyHint": {
"message": "Dica: clique com o botão direito no botão \"Embelezar\" ou use o atalho de teclado definido para embelezar sem mostrar esse painel"
},
"styleBeautifyIndentConditional": {
"message": "Indentar @media, @supports"
},
"styleBeautifyPreserveNewlines": {
"message": "Preserve novas linhas"
},
"styleCancelEditLabel": {
"message": "Voltar ao gerenciamento"
},
@ -925,12 +730,6 @@
"styleEnabledLabel": {
"message": "Ativado"
},
"styleFromMozillaFormatError": {
"message": "Falha ao importar do formato Mozilla"
},
"styleFromMozillaFormatPrompt": {
"message": "Colar o código formato-Mozilla"
},
"styleInstall": {
"message": "Instalar \"$stylename$\" no Stylus?",
"placeholders": {
@ -939,79 +738,18 @@
}
}
},
"styleInstallFailed": {
"message": "Falha ao instalar o estilo de usuário!\n$error$",
"placeholders": {
"error": {
"content": "$1"
}
}
},
"styleInstallOverwrite": {
"message": "$stylename$já está instalado. Substituir?\nVersão:$oldVersion$-> $newVersion$",
"placeholders": {
"newVersion": {
"content": "$3"
},
"oldVersion": {
"content": "$2"
},
"stylename": {
"content": "$1"
}
}
},
"styleMissingName": {
"message": "Insira um nome"
},
"styleMozillaFormatHeading": {
"message": "Formato Mozilla"
},
"styleName": {
"message": "Nome do estilo"
},
"styleNotAppliedRegexpProblemTooltip": {
"message": "O estilo não foi aplicado devido ao uso incorreto de 'regexp ()'"
},
"stylePreferSchemeLabel": {
"message": "Modo Escuro/Claro"
},
"styleRegexpInvalidExplanation": {
"message": "Algumas regras de 'regexp()' que não puderam ser compiladas."
},
"styleRegexpPartialExplanation": {
"message": "Este estilo usa regexps parcialmente correspondentes em violação da <a href='https://developer.mozilla.org/docs/Web/CSS/@document'>CSS4 @document specification</a> que requer um URL correspondente inteiro. As secções de CSS afetadas não foram aplicadas nesta página. Este estilo foi provavelmente criado no Stylish-for-Chrome o qual verifica incorretamente as regras 'regexp()' desde a primeira versão (bug conhecido)."
},
"styleRegexpProblemTooltip": {
"message": "Número de secções não aplicadas devido ao uso incorreto de 'regexp ()'"
},
"styleRegexpTestFull": {
"message": "Separadores correspondentes"
},
"styleRegexpTestInvalid": {
"message": "Regexps inválidos ignorados"
},
"styleRegexpTestNone": {
"message": "Nenhum separador correspondente"
},
"styleRegexpTestNote": {
"message": "Nota: use um único \\ para escapar no campo de entrada regexp, que será automaticamente convertido para \\\\ no código de estilo conforme especificação para strings entre aspas em CSS."
},
"styleRegexpTestPartial": {
"message": "Não corresponde totalmente, portanto ignorado"
},
"styleRegexpTestTitle": {
"message": "Lista de separadores correspondentes abertos (clique no URL para focar no separador)"
},
"styleSaveLabel": {
"message": "Salvar"
},
"styleToMozillaFormatHelp": {
"message": "O formato Mozilla do código pode ser usado com o Stylish para Firefox e pode ser enviado para userstyles.org."
},
"styleToMozillaFormatTitle": {
"message": "Estilo no formato Mozilla"
},
"styleUpdate": {
"message": "Você tem certeza que quer atualizar '$stylename$'?",
"placeholders": {
@ -1020,57 +758,21 @@
}
}
},
"styleUpdateDiscardChanges": {
"message": "O estilo é alterado fora do editor. Gostaria de recarregar o estilo?"
},
"styleUpdateUrlLabel": {
"message": "Atualizar URL"
},
"stylusUnavailableForURL": {
"message": "O Stylus não funciona em páginas como esta."
},
"stylusUnavailableForURLdetails": {
"message": "Como precaução de segurança, o browser proíbe extensões de afetar as páginas embutidas (como chrome://version, a página de novo separador predefinido no Chrome 61, about:addons, e assim sucessivamente) tal como páginas de outras extensões. Cada browser também restringe acesso à sua própria galeria de extensões (como Chrome Web Store ou AMO)"
},
"syncDropboxStyles": {
"message": "Exportar para Dropbox"
},
"syncStorageErrorSaving": {
"message": "O valor não pode ser guardado. Tente reduzir a quantidade de texto."
},
"toggleStyle": {
"message": "Alternar estilo"
},
"undo": {
"message": "Desfazer"
},
"undoGlobal": {
"message": "Desfazer todas as seções"
},
"unreachableAMO": {
"message": "O Firefox proíbe o acesso ao site."
},
"unreachableAMOHint": {
"message": "Para permitir o acesso abra<about:config>,clique com o botão direito na lista, clique em \"Novo\", depois em \"Booleano\", cole <privacy.resistFingerprinting.block_mozAddonManager> e clique em OK,<true>,OK, recarregue a página <addons.mozilla.org>."
},
"unreachableContentScript": {
"message": "Não foi possível comunicar com a página. Tente recarregar o separador."
},
"unreachableFileHint": {
"message": "O Stylus pode aceder URLs file:// apenas se ativar a checkbox correspondente para a extensão Stylus na página chrome://extensions"
},
"unreachableMozSiteHintOldFF": {
"message": "Somente o Firefox 59 e o mais recente podem ser configurados para permitir que WebExtensions incluam elementos de estilo em sites protegidos por CSP como este."
},
"unzipStyles": {
"message": "Descompactando estilos..."
},
"updateAllCheckSucceededNoUpdate": {
"message": "Nenhuma atualização encontrada."
},
"updateAllCheckSucceededSomeEdited": {
"message": "Alguns estilos que podem ser atualizados não foram verificados para evitar perder possíveis edições locais. As atualizações podem ser forçadas ao verificar individualmente, ou fazendo outra verificação para todos os estilos (edições locais vão ser sobrescrevidas)"
},
"updateCheckFailBadResponseCode": {
"message": "A atualização falhou: o servidor respondeu com código $code$.",
"placeholders": {
@ -1082,12 +784,6 @@
"updateCheckFailServerUnreachable": {
"message": "A atualização falhou: servidor inacessível."
},
"updateCheckHistory": {
"message": "Histórico de verificação de atualizações"
},
"updateCheckManualUpdateForce": {
"message": "Instalar atualização (edições locais vão ser sobrescritas)"
},
"updateCheckManualUpdateHint": {
"message": "Forçar uma atualização irá sobrescrever qualquer alteração local."
},
@ -1109,18 +805,12 @@
"uploadingFile": {
"message": "Enviando arquivo..."
},
"usercssAvoidOverwriting": {
"message": "Por favor modifique o valor de @name ou @namespace para evitar sobrescrever um estilo existente."
},
"usercssConfigIncomplete": {
"message": "O estilo foi atualizado ou eliminado após a exibição do diálogo de configuração. Essas variáveis não foram guardadas para evitar corromper os metadados do estilo:"
},
"usercssEditorNamePlaceholder": {
"message": "Especificar @name no código"
},
"usercssReplaceTemplateConfirmation": {
"message": "Substituir o template padrão por novos estilos com UserCSS com o código atual?"
},
"usercssReplaceTemplateName": {
"message": "@nome vazio substitui o template padrão"
},
"usercssReplaceTemplateSectionBody": {
"message": "Insira o código aqui..."
},

View File

@ -1,7 +1,4 @@
{
"InaccessibleFileHint": {
"message": "Stylus não pode acessar alguns tipos de arquivos (ex: arquivos pdf e json)."
},
"addStyleLabel": {
"message": "Escrever novo estilo"
},
@ -67,6 +64,9 @@
"backupButtons": {
"message": "Cópia de segurança"
},
"backupMessage": {
"message": "Selecione um ficheiro ou arraste e solte-o nesta página."
},
"bckpInstStyles": {
"message": "Exportar estilos"
},
@ -133,9 +133,6 @@
"cm_theme": {
"message": "Tema"
},
"colorpickerPaletteHint": {
"message": "Clique com o botão direito em uma amostra para percorrer suas linhas de código"
},
"colorpickerSwitchFormatTooltip": {
"message": "Mudar formatos: HEX -> RGB -> HSL.\nShift-clique para inverter a direção.\nTambém através das teclas PgUp (PageUp), PgDn (PageDown)."
},
@ -181,32 +178,6 @@
"confirmYes": {
"message": "Sim"
},
"connectingDropbox": {
"message": "Conectando ao Dropbox..."
},
"connectingDropboxNotAllowed": {
"message": "Conectar ao Dropbox somente é disponível em apps instalados diretamente da loja web"
},
"copied": {
"message": "Copiado para a área de transferência"
},
"copy": {
"message": "Copiar para a área de transferência"
},
"customNameHint": {
"message": "Digite um nome personalizado para renomear o estilo na UI sem quebrar suas atualizações"
},
"customNameResetHint": {
"message": "Deixar de usar o nome personalizado, usar o próprio nome do estilo"
},
"dateAbbrYear": {
"message": "$value$a",
"placeholders": {
"value": {
"content": "$1"
}
}
},
"dateInstalled": {
"message": "Data de instalação"
},
@ -237,9 +208,6 @@
"dragDropMessage": {
"message": "Solte o ficheiro da sua cópia de segurança em qualquer sítio nesta página para importar."
},
"dragDropUsercssTabstrip": {
"message": "Para instalar o arquivo, solte-o na linha das abas (a área onde os títulos das abas são mostrados)."
},
"editDeleteText": {
"message": "Eliminar"
},
@ -263,18 +231,9 @@
"enableStyleLabel": {
"message": "Ativar"
},
"excludeStyleByDomainLabel": {
"message": "Excluir o domínio atual"
},
"excludeStyleByUrlLabel": {
"message": "Excluir a URL atual"
},
"exportLabel": {
"message": "Exportar"
},
"exportSavedSuccess": {
"message": "Arquivo salvo com sucesso"
},
"externalLink": {
"message": "Hiperligação externa"
},
@ -301,6 +260,15 @@
"findStyles": {
"message": "Localizar estilos"
},
"findStylesForSite": {
"message": "Encontrar mais estilos para este site"
},
"findStylesInline": {
"message": "inline"
},
"findStylesInlineTooltip": {
"message": "Exibir os resultados da pesquisa dentro desta janela."
},
"genericAdd": {
"message": "Adicionar"
},
@ -334,9 +302,6 @@
"genericUnknown": {
"message": "Desconhecido"
},
"gettingStyles": {
"message": "Obtendo todos os estilos..."
},
"helpAlt": {
"message": "Ajuda"
},
@ -346,9 +311,6 @@
"helpKeyMapHotkey": {
"message": "Prima uma tecla de atalho"
},
"hostDisabled": {
"message": "Este hospedeiro foi desabilitado devido a um bug na versão atual que o navegador utilizado se encontra"
},
"importAppendLabel": {
"message": "Acrescentar ao estilo"
},
@ -358,12 +320,6 @@
"importLabel": {
"message": "Importar"
},
"importPreprocessor": {
"message": "Estilos com <code>@preprocessor</code> não irão funcionar no modo clássico. Você pode trocar o editor para o modo UserCSS: 1) abre o gerenciador de estilos, 2) ative a caixa \"como UserCSS\", 3) clique \"Escrever novo estilo\"\n\nDeseja importar mesmo assim?"
},
"importPreprocessorTitle": {
"message": "Possível problema causado pelo @preprocessor"
},
"importReplaceLabel": {
"message": "Sobrescrever estilo"
},
@ -486,12 +442,6 @@
"liveReloadError": {
"message": "Ocorreu um erro ao vigiar o arquivo"
},
"liveReloadInstallHint": {
"message": "Mantenha esta guia aberta para atualizar automaticamente o estilo sob mudanças externas."
},
"liveReloadInstallHintFF": {
"message": "Mantenha juntamente esta guia e a guia original abertas para atualizar automaticamente o estilo sob mudanças externas."
},
"liveReloadLabel": {
"message": "Recarregamento dinâmico"
},
@ -502,7 +452,7 @@
"message": "Acinzentado(s)"
},
"manageFaviconsHelp": {
"message": "O Stylus usa um serviço externo https://icons.duckduckgo.com"
"message": "O Stylus usa um serviço externo https://www.google.com/s2/favicons"
},
"manageFilters": {
"message": "Filtros"
@ -546,73 +496,12 @@
"menuShowBadge": {
"message": "Mostrar a contagem de estilos ativados"
},
"meta_invalidCheckboxDefault": {
"message": "Inválida @var checkbox: o valor deve ser 0 ou 1"
},
"meta_invalidNumber": {
"message": "Espera-se um número"
},
"meta_invalidRange": {
"message": "@var inválido $type$: valor deve ser um número ou um vetor",
"placeholders": {
"type": {
"content": "$1"
}
}
},
"meta_invalidString": {
"message": "Espera-se um texto entre aspas"
},
"meta_invalidWord": {
"message": "Espera-se uma palavra"
},
"meta_missingChar": {
"message": "Caracteres esperados: $chars$",
"placeholders": {
"chars": {
"content": "$1"
}
}
},
"meta_missingMandatory": {
"message": "Metadata obrigatório não encontrado: $keys$",
"placeholders": {
"keys": {
"content": "$1"
}
}
},
"meta_unknownMeta": {
"message": "Metadata desconhecido: $key$",
"placeholders": {
"key": {
"content": "$1"
}
}
},
"meta_unknownVarType": {
"message": "Tipo desconhecido da variável @$varkey$: $vartype$",
"placeholders": {
"varkey": {
"content": "$1"
},
"vartype": {
"content": "$2"
}
}
},
"noFileToImport": {
"message": "Para importar seus estilos, você deve exportar primeiro."
},
"noStylesForSite": {
"message": "Nenhum estilo instalado para este site."
},
"openManage": {
"message": "Gerir"
},
"openOptions": {
"message": "Opções"
},
"openStylesManager": {
"message": "Abrir gestor de estilos"
},
@ -649,9 +538,6 @@
"optionsCustomizeIcon": {
"message": "Ícone da barra de ferramentas"
},
"optionsCustomizeSync": {
"message": "Sincronizar para a nuvem"
},
"optionsCustomizeUpdate": {
"message": "Atualizações"
},
@ -682,42 +568,12 @@
"optionsSubheading": {
"message": "Mais Opções"
},
"optionsSyncConnect": {
"message": "Conectar"
},
"optionsSyncDisconnect": {
"message": "Desconectar"
},
"optionsSyncNone": {
"message": "Nenhum"
},
"optionsSyncStatusConnected": {
"message": "Conectado"
},
"optionsSyncStatusConnecting": {
"message": "Conectando..."
},
"optionsSyncStatusDisconnected": {
"message": "Desconectado"
},
"optionsSyncStatusDisconnecting": {
"message": "Desconectando..."
},
"optionsSyncStatusSyncing": {
"message": "Sincronizando..."
},
"optionsSyncSyncNow": {
"message": "Sincronizar agora"
},
"optionsUpdateImportNote": {
"message": "Ao importar uma cópia de segurança de estilo da versão antiga ou do Stylish, faça uma verificação única de atualizações manualmente no gestor de estilos para garantir que todos os estilos sejam atualizados."
},
"optionsUpdateInterval": {
"message": "Intervalo de atualização automática do estilo de usuário em horas (especifique 0 para desativar)"
},
"overwriteFileExport": {
"message": "Você gostaria de substituir um arquivo existente?"
},
"paginationCurrent": {
"message": "Pagina atual"
},
@ -769,9 +625,6 @@
"previewTooltip": {
"message": "Temporariamente aplica as alterações sem guardar.\nGuarde o estilo para tornar as alterações permanentes."
},
"readingStyles": {
"message": "Lendo estilos..."
},
"replace": {
"message": "Substituir"
},
@ -784,9 +637,6 @@
"retrieveBckp": {
"message": "Importar estilos"
},
"retrieveDropboxSync": {
"message": "Importar do Dropbox"
},
"search": {
"message": "Pesquisar"
},
@ -829,9 +679,6 @@
"sectionRestore": {
"message": "Restaurar secção removida"
},
"sections": {
"message": "Secções"
},
"shortcuts": {
"message": "Atalhos"
},
@ -865,9 +712,6 @@
"styleBeautify": {
"message": "Embelezar"
},
"styleBeautifyHint": {
"message": "Dica: clique com o botão direito no botão \"Embelezar\" ou use o atalho de teclado definido para embelezar sem mostrar esse painel"
},
"styleBeautifyIndentConditional": {
"message": "Indentar @media, @supports"
},
@ -937,6 +781,9 @@
"styleRegexpProblemTooltip": {
"message": "Número de secções não aplicadas devido ao uso incorreto de 'regexp ()'"
},
"styleRegexpTestButton": {
"message": "Testar RegExp"
},
"styleRegexpTestFull": {
"message": "Separadores correspondentes"
},
@ -981,9 +828,6 @@
"stylusUnavailableForURLdetails": {
"message": "Como precaução de segurança, o browser proíbe extensões de afetar as páginas embutidas (como chrome://version, a página de novo separador predefinido no Chrome 61, about:addons, e assim sucessivamente) tal como páginas de outras extensões. Cada browser também restringe acesso à sua própria galeria de extensões (como Chrome Web Store ou AMO)"
},
"syncDropboxStyles": {
"message": "Exportar para Dropbox"
},
"syncStorageErrorSaving": {
"message": "O valor não pode ser guardado. Tente reduzir a quantidade de texto."
},
@ -1008,12 +852,6 @@
"unreachableFileHint": {
"message": "O Stylus pode aceder URLs file:// apenas se ativar a checkbox correspondente para a extensão Stylus na página chrome://extensions"
},
"unreachableMozSiteHintOldFF": {
"message": "Somente o Firefox 59 e o mais recente podem ser configurados para permitir que WebExtensions incluam elementos de estilo em sites protegidos por CSP como este."
},
"unzipStyles": {
"message": "Descompactando estilos..."
},
"updateAllCheckSucceededNoUpdate": {
"message": "Nenhuma atualização encontrada."
},
@ -1055,9 +893,6 @@
"updatesCurrentlyInstalled": {
"message": "Atualizações instaladas:"
},
"uploadingFile": {
"message": "Enviando arquivo..."
},
"usercssAvoidOverwriting": {
"message": "Por favor modifique o valor de @name ou @namespace para evitar sobrescrever um estilo existente."
},
@ -1070,6 +905,9 @@
"usercssReplaceTemplateConfirmation": {
"message": "Substituir o modelo predefinido para novos estilos de Usercss com o código atual?"
},
"usercssReplaceTemplateName": {
"message": "@name vazio substitui o modelo predefinido"
},
"usercssReplaceTemplateSectionBody": {
"message": "Insira o código aqui..."
},
@ -1081,8 +919,5 @@
},
"writeStyleForURL": {
"message": "este URL"
},
"zipStyles": {
"message": "Compactando estilos..."
}
}

View File

@ -61,6 +61,9 @@
"author": {
"message": "Autor"
},
"backupMessage": {
"message": "Selectați un fișier sau drag-and-drop aici"
},
"bckpInstStyles": {
"message": "Exportați teme"
},
@ -230,6 +233,12 @@
"findStyles": {
"message": "Găsiți teme"
},
"findStylesForSite": {
"message": "Gasiți mai multe teme pentru acest site."
},
"findStylesInlineTooltip": {
"message": "Arătați rezultatele căutării în această pagină."
},
"genericAdd": {
"message": "Adaugă"
},
@ -404,7 +413,7 @@
"message": "Hașurat"
},
"manageFaviconsHelp": {
"message": "Stylus folosește un serviciu extern https://icons.duckduckgo.com"
"message": "Stylus folosește un serviciu extern https://www.google.com/s2/favicons"
},
"manageFilters": {
"message": "Filtre"
@ -445,18 +454,12 @@
"menuShowBadge": {
"message": "Afișați numărul temelor active"
},
"meta_invalidCheckboxDefault": {
"message": "@var checkbox invalidă: valuarea trebuie să fie 0 sau 1"
},
"noStylesForSite": {
"message": "Nicio temă instalată pentru acest site."
},
"openManage": {
"message": "Managerul"
},
"openOptions": {
"message": "Opțiuni"
},
"openStylesManager": {
"message": "Deschideți managerul de teme"
},
@ -622,9 +625,6 @@
"sectionRestore": {
"message": "Restaurează o secțiune ștearsă"
},
"sections": {
"message": "Secțiuni"
},
"shortcutsNote": {
"message": "Creeați keyboard shortcuts"
},
@ -789,9 +789,6 @@
"unreachableFileHint": {
"message": "Stylus poate accesa file:// URLs doar când este activată opțiunea respectivă din pagina cu setări chrome://extensions"
},
"unreachableMozSiteHintOldFF": {
"message": "Doar Firefox 59 sau mai nou poate fi configurat să permită WebExtension-urilor să adauge elemente la site-uri CSP-protected precum acesta."
},
"updateAllCheckSucceededNoUpdate": {
"message": "Niciun update găsit."
},
@ -845,6 +842,9 @@
"usercssReplaceTemplateConfirmation": {
"message": "Înlocuiți tema de bază a formatului Usercss cu acest cod?"
},
"usercssReplaceTemplateName": {
"message": "@name este gol și înlocuiețte valoarea de bază"
},
"usercssReplaceTemplateSectionBody": {
"message": "Introduce cod aici..."
},

View File

@ -68,7 +68,7 @@
"message": "Резервное копирование"
},
"backupMessage": {
"message": "Чтобы импортировать архив стилей, перетащите файл в эту страницу или нажмите кнопку Импорт.\n\nЧтобы экспортировать совместимый архив для старого Stylus ранее чем 1.5.18, кликните на кнопку Экспорт правой кнопкой мыши или с нажатой клавишей Shift."
"message": "Нажмите «Импорт», чтобы выбрать файл или просто перетащите его на эту страницу"
},
"bckpInstStyles": {
"message": "Экспорт стилей"
@ -267,17 +267,6 @@
"disableStyleLabel": {
"message": "Отключить"
},
"draftAction": {
"message": "Выберите «Да» для загрузки черновика или «Нет», чтобы выкинуть его."
},
"draftTitle": {
"message": "Восстановление черновика, созданного $date$",
"placeholders": {
"date": {
"content": "$1"
}
}
},
"dragDropMessage": {
"message": "Перетащите файл с резервной копией стилей в любое место этой страницы, чтобы импортировать его."
},
@ -304,9 +293,6 @@
}
}
},
"editorSettings": {
"message": "Настройки редактирования"
},
"enableStyleLabel": {
"message": "Включить"
},
@ -316,9 +302,6 @@
"excludeStyleByUrlLabel": {
"message": "Исключить текущий URL"
},
"exportCompatible": {
"message": "Экспорт (режим совместимости)"
},
"exportLabel": {
"message": "Экспорт"
},
@ -357,6 +340,15 @@
"findStyles": {
"message": "Найти стили"
},
"findStylesForSite": {
"message": "Найти ещё стили для этого веб-сайта"
},
"findStylesInline": {
"message": "и показать здесь"
},
"findStylesInlineTooltip": {
"message": "Показывать найденные стили в этом окне."
},
"genericAdd": {
"message": "Добавить"
},
@ -390,9 +382,6 @@
"genericSavedMessage": {
"message": "Сохранено"
},
"genericTest": {
"message": "Тест"
},
"genericTitle": {
"message": "Имя"
},
@ -402,9 +391,6 @@
"gettingStyles": {
"message": "Получение всех стилей..."
},
"headerResizerHint": {
"message": "С клавишей Shift размер меняется только для такого же типа страниц, т.е. редактор, менеджер, установщик"
},
"helpAlt": {
"message": "Справка"
},
@ -500,18 +486,9 @@
"linkGetHelp": {
"message": "Помощь"
},
"linkGetShareStyles": {
"message": "Поиск/публикация стилей"
},
"linkGetShareStylesInfo": {
"message": "Новый сайт userstyles.world, созданный и развиваемый сообществом энтузиастов и авторов пользовательских стилей с целью заменить userstyles.org, который стал таким медленным и зависающим за последний год, что многие авторы перестали обновлять свои стили."
},
"linkGetStyles": {
"message": "Скачать стили"
},
"linkGetStylesInfo": {
"message": "Архивный сайт от одного энтузиаста и автора пользовательских стилей, зеркальная копия userstyles.org, ныне медленного и зависающего. Архив обновляется примерно раз в день."
},
"linkStylusWiki": {
"message": "Вики"
},
@ -582,7 +559,7 @@
"message": "Обесцвечивать"
},
"manageFaviconsHelp": {
"message": "Используется сторонний сервис https://icons.duckduckgo.com"
"message": "Используется сторонний сервис https://www.google.com/s2/favicons"
},
"manageFilters": {
"message": "Фильтры"
@ -842,15 +819,6 @@
"optionsAdvanced": {
"message": "Другое"
},
"optionsAdvancedAutoSwitchSchemeBySystem": {
"message": "По настройке операционной системы"
},
"optionsAdvancedAutoSwitchSchemeByTime": {
"message": "В ночное время:"
},
"optionsAdvancedAutoSwitchSchemeNever": {
"message": "Отключен. Настройка режима в стилях игнорируется."
},
"optionsAdvancedContextDelete": {
"message": "Показывать команду \"Удалить\" в контекстном меню редактора"
},
@ -860,12 +828,6 @@
"optionsAdvancedExposeIframesNote": {
"message": "Выставляет верхний домен сайта в каждом iframe.\nПозволяет писать iframe-specific CSS следующим образом:\nhtml[stylus-iframe$$=\"twitter.com\"] h1 { display:none }"
},
"optionsAdvancedExposeStyleName": {
"message": "Проставлять имя стиля"
},
"optionsAdvancedExposeStyleNameNote": {
"message": "Проставляет имя стиля внутри страницы чтобы облегчить отладку стилей в инструментах разработчика (devtools). Чтобы применить новую настройку пожалуйста обновите вкладку со страницей."
},
"optionsAdvancedNewStyleAsUsercss": {
"message": "Создавать стили в формате usercss"
},
@ -911,9 +873,6 @@
"optionsHeading": {
"message": "Настройки"
},
"optionsIconAuto": {
"message": "В соответствии с ночным/дневным режимом"
},
"optionsIconDark": {
"message": "Тёмный интерфейс браузера"
},
@ -936,7 +895,7 @@
"message": "Сброс настроек"
},
"optionsStylusThemes": {
"message": "Нажмите иконку Stylus в верхней панели браузера на любой странице Stylus, в том числе и этой, затем нажмите «Найти стили»"
"message": "Найти тему интерфейса Stylus"
},
"optionsSubheading": {
"message": "Дополнительно"
@ -953,9 +912,6 @@
"optionsSyncNone": {
"message": "Ничего"
},
"optionsSyncPassword": {
"message": "Пароль"
},
"optionsSyncStatusConnected": {
"message": "Подключено"
},
@ -999,12 +955,6 @@
"optionsSyncSyncNow": {
"message": "Синхронизировать"
},
"optionsSyncUrl": {
"message": "URL адрес"
},
"optionsSyncUsername": {
"message": "Имя"
},
"optionsUpdateImportNote": {
"message": "После импорта базы стилей из старой версии или из Stylish запустите проверку на обновления из менеджера стилей, чтобы удостовериться в обновлении всех стилей."
},
@ -1047,9 +997,6 @@
"popupHotkeysTooltip": {
"message": "Показать горячие клавиши"
},
"popupManageSiteStyles": {
"message": "Управлять стилями сайта"
},
"popupManageTooltip": {
"message": "Shift-ЛКМ или ПКМ откроет менеджер с стилями только для этого сайта."
},
@ -1071,21 +1018,6 @@
"prefShowBadge": {
"message": "Показывать количество активных стилей для открытого сайта"
},
"preferScheme": {
"message": "Ночной/дневной режим"
},
"preferSchemeAlways": {
"message": "Пока что игнорируется (стиль всегда включен), т.к. общий режим ночи/дня отключен"
},
"preferSchemeDark": {
"message": "Ночной"
},
"preferSchemeLight": {
"message": "Дневной"
},
"preferSchemeNone": {
"message": "Нет (всегда включен)"
},
"previewLabel": {
"message": "Предпросмотр"
},
@ -1113,6 +1045,9 @@
"readingStyles": {
"message": "Чтение стилей..."
},
"reload": {
"message": "Перезагрузить расширение Stylus"
},
"replace": {
"message": "Заменить"
},
@ -1122,18 +1057,12 @@
"replaceWith": {
"message": "Заменить на"
},
"restoreTemplate": {
"message": "Восстановить шаблон по-умолчанию.\n\n(Не влияет на уже открытые страницы редактирования)"
},
"retrieveBckp": {
"message": "Импорт стилей"
},
"retrieveDropboxSync": {
"message": "Импорт Dropbox"
},
"saveAsTemplate": {
"message": "Сохранить как шаблон"
},
"search": {
"message": "Поиск"
},
@ -1173,6 +1102,9 @@
"searchResultWeeklyCount": {
"message": "Загрузок за неделю"
},
"searchStyleQueryHint": {
"message": "Поиск стилистических имён по падежам:\nнекоторые слова - все слова в любом порядке\n\"какая-то фраза\" - именно эта фраза без кавычек\n2020 - такой год также показывает стили, обновлённые в 2020 году"
},
"searchStylesAll": {
"message": "Все"
},
@ -1206,18 +1138,12 @@
"sections": {
"message": "Разделы"
},
"settings": {
"message": "Настройки"
},
"shortcuts": {
"message": "Клавиши"
},
"shortcutsNote": {
"message": "Назначить клавишу"
},
"shortcutsNoteFF": {
"message": "В Firefox 66+ вы можете открыть интерфейс клавиатурных сочетаний самостоятельно:\n1) правой кнопкой мыши на иконке Stylus в панели браузера, затем «Менеджер»\n(либо откройте about:addons из главного меню или нажмите Ctrl-Shift-A),\n2) на странице, которая откроется, нажмите иконку шестеренки в правом верхнем углу,\n3) выберите «Управление горячими клавишами расширений».\n\nТакже настроить клавиши можно и здесь."
},
"sortDateNewestFirst": {
"message": "новые сверху"
},
@ -1263,30 +1189,12 @@
"styleEnabledLabel": {
"message": "Включено"
},
"styleExcludeLabel": {
"message": "Личный список исключенных сайтов"
},
"styleFromMozillaFormatError": {
"message": "Ошибка импорта формата Mozilla"
},
"styleFromMozillaFormatPrompt": {
"message": "Вставьте содержимое стиля в формате Mozilla"
},
"styleIncludeLabel": {
"message": "Личный список включенных сайтов"
},
"styleInjectionImportance": {
"message": "Переключить важность стиля"
},
"styleInjectionOrder": {
"message": "Порядок применения стилей"
},
"styleInjectionOrderHint": {
"message": "Перетащите стиль для изменения его позиции. Стили применяются последовательно в нижеуказанном порядке, так что последующий стиль может изменить эффект предыдущих."
},
"styleInjectionOrderHint_prio": {
"message": "Важные стили всегда применяются после новоустановленных стилей, чтобы иметь «последнее слово». Для изменения важности стиля нажмите на его значок."
},
"styleInstall": {
"message": "Установить \"$stylename$\" в Stylus?",
"placeholders": {
@ -1329,15 +1237,6 @@
"styleNotAppliedRegexpProblemTooltip": {
"message": "Стиль не был применён из-за некорректного regexp()"
},
"styleNotAppliedSchemeDark": {
"message": "Стиль активен только в ночном режиме"
},
"styleNotAppliedSchemeLight": {
"message": "Стиль активен только в дневном режиме"
},
"stylePreferSchemeLabel": {
"message": "Ночной/дневной режим"
},
"styleRegexpInvalidExplanation": {
"message": "Некоторые 'regexp()' выражения не удалось скомпилировать."
},
@ -1347,6 +1246,9 @@
"styleRegexpProblemTooltip": {
"message": "Кол-во пропущенных разделов из-за неправильного regexp()"
},
"styleRegexpTestButton": {
"message": "Тест регулярки"
},
"styleRegexpTestFull": {
"message": "Соответствующие вкладки"
},
@ -1368,9 +1270,6 @@
"styleSaveLabel": {
"message": "Сохранить"
},
"styleSettings": {
"message": "Настройки стиля"
},
"styleToMozillaFormatHelp": {
"message": "Формат кода Mozilla можно отправлять на сайт userstyles.org и использовать в дополнении Stylish для Firefox."
},
@ -1388,9 +1287,6 @@
"styleUpdateDiscardChanges": {
"message": "Стиль был изменён вне редактора. Перезагрузить?"
},
"styleUpdateUrlLabel": {
"message": "URL адрес обновлений"
},
"stylusUnavailableForURL": {
"message": "Такие адреса не поддерживаются."
},
@ -1406,16 +1302,8 @@
"syncError": {
"message": "Сбой синхронизации"
},
"syncErrorLock": {
"message": "Хранилище данных уже занято. Доступ закрыт до $TIME$",
"placeholders": {
"time": {
"content": "$1"
}
}
},
"syncErrorRelogin": {
"message": "Ошибка синхронизации. Произведен выход из учетной записи.\n\nПопробуйте подключиться еще раз в настройках Stylus."
"message": "Синхронизация не удалась.\nПопробуйте повторно войти в систему в опциях Stylus:\nсначала нажмите \"отсоединить\", затем \"подключить\"."
},
"syncStorageErrorSaving": {
"message": "Ошибка сохранения. Попробуйте уменьшить количество текста."
@ -1506,6 +1394,9 @@
"usercssReplaceTemplateConfirmation": {
"message": "Заменить шаблон по умолчанию для нового стиля в формате Usercss текущим кодом?"
},
"usercssReplaceTemplateName": {
"message": "Пустой @name заменяет шаблон по умолчанию"
},
"usercssReplaceTemplateSectionBody": {
"message": "Место для CSS кода..."
},

View File

@ -76,15 +76,9 @@
"cm_theme": {
"message": "Тема"
},
"confirmDelete": {
"message": "Избриши"
},
"confirmNo": {
"message": "Не"
},
"confirmSave": {
"message": "Сачувај"
},
"confirmStop": {
"message": "Заустави"
},
@ -112,9 +106,6 @@
"disableStyleLabel": {
"message": "Онемогући"
},
"editDeleteText": {
"message": "Избриши"
},
"editGotoLine": {
"message": "Иди на ред (или line:col)"
},
@ -138,11 +129,8 @@
"exportLabel": {
"message": "Извези"
},
"genericAdd": {
"message": "Додај"
},
"genericEnabledLabel": {
"message": "Омогућено"
"findStylesForSite": {
"message": "Пронађи још стилова за овај сајт"
},
"helpAlt": {
"message": "Помоћ"
@ -206,15 +194,9 @@
"openManage": {
"message": "Управљај инсталираним стиловима"
},
"openOptions": {
"message": "Опције"
},
"optionsHeading": {
"message": "Опције"
},
"optionsSyncUrl": {
"message": "УРЛ"
},
"popupStylesFirst": {
"message": "Излистај стилове пре команди у менију дугмета на алатној траци"
},
@ -245,9 +227,6 @@
"sectionRemove": {
"message": "Уклони одељак"
},
"sections": {
"message": "Одељци"
},
"styleBadRegexp": {
"message": "Регуларни израз је неисправан."
},

View File

@ -70,6 +70,9 @@
"backupButtons": {
"message": "Säkerhetskopiera"
},
"backupMessage": {
"message": "Välj en fil eller dra och släpp till den här sidan."
},
"bckpInstStyles": {
"message": "Exportera stilar"
},
@ -287,6 +290,12 @@
"findStyles": {
"message": "Hitta stilar"
},
"findStylesForSite": {
"message": "Hitta fler stilar för denna webbplats"
},
"findStylesInlineTooltip": {
"message": "Visa sökresultat i det här fönstret."
},
"genericAdd": {
"message": "Lägg till"
},
@ -473,7 +482,7 @@
"message": "Nedtonade"
},
"manageFaviconsHelp": {
"message": "Stylus använder en extern tjänst https://icons.duckduckgo.com"
"message": "Stylus använder en extern tjänst https://www.google.com/s2/favicons"
},
"manageFilters": {
"message": "Filter"
@ -815,9 +824,6 @@
"optionsSyncSyncNow": {
"message": "Synkronisera nu"
},
"optionsSyncUrl": {
"message": "Webbadress"
},
"optionsUpdateImportNote": {
"message": "När du importerar säkerhetskopior av stilar från gammal version eller från Stylish, gör en engångskontroll för uppdateringar manuellt i stilhanteraren för att säkerställa att alla stilar uppdateras."
},
@ -956,9 +962,6 @@
"sectionRestore": {
"message": "Återställ borttagen sektion"
},
"sections": {
"message": "Sektioner"
},
"shortcuts": {
"message": "Genvägar"
},
@ -1058,6 +1061,9 @@
"styleRegexpProblemTooltip": {
"message": "Antal avsnitt som inte tillämpas på grund av felaktig användning av \"regexp()\""
},
"styleRegexpTestButton": {
"message": "RegExp-test"
},
"styleRegexpTestFull": {
"message": "Matchande flikar"
},
@ -1129,9 +1135,6 @@
"unreachableFileHint": {
"message": "Stylus kan endast komma åt fil:// webbadresser om du aktiverar motsvarande kryssruta för Stylus-tillägg på chrome://extensions-sidan."
},
"unreachableMozSiteHintOldFF": {
"message": "Endast Firefox 59 och nyare kan konfigureras för att tillåta WebExtensions att lägga till stilelement på CSP-skyddade webbplatser som den här."
},
"unzipStyles": {
"message": "Packar upp stilar..."
},
@ -1191,6 +1194,9 @@
"usercssReplaceTemplateConfirmation": {
"message": "Ersätt standardmallen för nya Usercss-stilar med den aktuella koden?"
},
"usercssReplaceTemplateName": {
"message": "Tom @name ersätter standardmallen"
},
"usercssReplaceTemplateSectionBody": {
"message": "Lägg in kod här..."
},

View File

@ -22,12 +22,6 @@
"appliesToEverything": {
"message": "అన్నిటికీ"
},
"confirmDelete": {
"message": "తొలగించు"
},
"confirmSave": {
"message": "భద్రపరచు"
},
"deleteStyleConfirm": {
"message": "మీరు నజంగానే ఈ శైలిని తొలగించాలనుకుంటున్నారా?"
},
@ -37,18 +31,12 @@
"disableStyleLabel": {
"message": "అచేతనించు"
},
"editDeleteText": {
"message": "తొలగించు"
},
"editStyleLabel": {
"message": "మార్చు"
},
"enableStyleLabel": {
"message": "చేతనించు"
},
"genericAdd": {
"message": "చేర్చు"
},
"helpAlt": {
"message": "సహాయం"
},
@ -58,9 +46,6 @@
"manageTitle": {
"message": "స్టైలిష్"
},
"sections": {
"message": "విభాగాలు"
},
"styleSaveLabel": {
"message": "భద్రపరచు"
}

View File

@ -58,6 +58,9 @@
"backupButtons": {
"message": "Yedek"
},
"backupMessage": {
"message": "Bir dosya seçin veya bu sayfaya sürükleyip bırakın."
},
"bckpInstStyles": {
"message": "Dışa aktar"
},
@ -284,6 +287,15 @@
"findStyles": {
"message": "Stil bul"
},
"findStylesForSite": {
"message": "Bu site için başka stiller bul"
},
"findStylesInline": {
"message": "Hizada"
},
"findStylesInlineTooltip": {
"message": "Arama sonuçlarını bu pencerede görüntüleyin."
},
"genericAdd": {
"message": "Ekle"
},
@ -414,7 +426,7 @@
"message": "Gri renkte"
},
"manageFaviconsHelp": {
"message": "Stylus harici bir servis kullanır https://icons.duckduckgo.com"
"message": "Stylus harici bir servis kullanır https://www.google.com/s2/favicons"
},
"manageFilters": {
"message": "Filtreler"
@ -452,9 +464,6 @@
"openManage": {
"message": "Yüklü stilleri yönet"
},
"openOptions": {
"message": "Seçenekler"
},
"openStylesManager": {
"message": "Stil yöneticisini aç"
},
@ -602,9 +611,6 @@
"sectionRemove": {
"message": "Bölümü kaldır"
},
"sections": {
"message": "Bölümler"
},
"shortcuts": {
"message": "Kısayollar"
},
@ -658,6 +664,9 @@
"styleRegexpProblemTooltip": {
"message": "'Regexp ()' yanlış kullanımı nedeniyle uygulanmayan bölüm sayısı"
},
"styleRegexpTestButton": {
"message": "RegExp testi"
},
"styleRegexpTestFull": {
"message": "Eşleşen sekmeler"
},
@ -773,6 +782,9 @@
"usercssReplaceTemplateConfirmation": {
"message": "Yeni Usercss stilleri için varsayılan şablonu geçerli kodla değiştir?"
},
"usercssReplaceTemplateName": {
"message": "Boş @name, varsayılan şablonun yerini alır"
},
"usercssReplaceTemplateSectionBody": {
"message": "Kodu buraya ekle..."
},

View File

@ -61,6 +61,9 @@
"backupButtons": {
"message": "Резервне копіювання"
},
"backupMessage": {
"message": "Виберіть файл або перетягніть на цю сторінку."
},
"bckpInstStyles": {
"message": "Експорт стилів"
},
@ -97,12 +100,18 @@
"disableStyleLabel": {
"message": "Вимкнути"
},
"editDeleteText": {
"message": "Видалити"
},
"findStyles": {
"message": "Знайти стилі"
},
"findStylesForSite": {
"message": "Знайти більше стилів для цього сайту"
},
"findStylesInline": {
"message": "і показати тут"
},
"findStylesInlineTooltip": {
"message": "Показувати знайдені стилі в цьому вікні."
},
"genericAdd": {
"message": "Додати"
},
@ -169,20 +178,11 @@
"importReportTitle": {
"message": "Імпорт стилів закінчено"
},
"installUpdate": {
"message": "Встановити оновлення"
},
"manageFavicons": {
"message": "Піктограми для цільових сайтів"
},
"manageFilters": {
"message": "Фільтри"
},
"manageHeading": {
"message": "Встановити Styles"
},
"manageNewUI": {
"message": "Новий макет інтерфейсу управління користувача"
"message": "Новий інтерфейс"
},
"meta_unknownJSONLiteral": {
"message": "Невірний JSON: $literal$не є дійсним літералом JSON",
@ -193,13 +193,7 @@
}
},
"openManage": {
"message": "Керування"
},
"openOptions": {
"message": "Налаштування"
},
"optionsHeading": {
"message": "Налаштування"
"message": "Менеджер"
},
"optionsReset": {
"message": "Скидання налаштувань до значень за замовчуванням"
@ -213,39 +207,9 @@
"optionsSyncLogin": {
"message": "Логін"
},
"optionsSyncStatusRelogin": {
"message": "Сеанс закінчився, будь ласка, увійдіть ще раз."
},
"paginationNext": {
"message": "Наступна сторінка"
},
"paginationPrevious": {
"message": "Попередня сторінка"
},
"replace": {
"message": "Замінити"
},
"replaceAll": {
"message": "Замінити все"
},
"retrieveBckp": {
"message": "Імпорт стилів"
},
"search": {
"message": "Пошук"
},
"searchStylesAll": {
"message": "Усі"
},
"searchStylesCode": {
"message": "CSS код"
},
"searchStylesHelp": {
"message": "</> або <Ctrl-F>клавіша фокусує поле пошуку.\nРежим за замовчуванням — це пошук у звичайному тексті для всіх термінів, розділених пробілами, у будь-якому порядку.\nТочні слова: оберніть запит у подвійні лапки, напр. <.header ~ div\">\nРегулярні вирази: включають косі риски та прапорці, напр.</body.*?\\ba\\b/i>\n«By URL» в селекторі області: знаходить стилі, які застосовуються до повністю вказаної URL-адреси, напр. https://www.example.org/\n\"Metadata\" в селектрі області: шукає в іменах, \"applies to\" специфікаторів, URL-адреси встановлення, URL-адресі оновлення та всьому блоку метаданих для стилів CSS користувача."
},
"searchStylesName": {
"message": "Назва"
},
"sectionCode": {
"message": "Код"
},
@ -256,29 +220,14 @@
"message": "Облагородити"
},
"styleCancelEditLabel": {
"message": "Повернутися до керування"
},
"styleEnabledLabel": {
"message": "Увімкнено"
},
"stylePreferSchemeLabel": {
"message": "Темна/Світла тема"
},
"styleSaveLabel": {
"message": "Зберегти"
"message": "Всі стилі"
},
"syncErrorRelogin": {
"message": "Помилка синхронізації. Ви вийшли із системи. \nСпробуйте повторно увійти до системи в налаштуваннях Stylus."
"message": "Помилка синхронізації.\nСпробуйте повторно ввійти в налаштування Stylus:\nнатисніть спочатку «від’єднати», а потім «підключити». "
},
"toggleStyle": {
"message": "Включити/виключити стиль"
},
"writeStyleFor": {
"message": "Створити стиль для:"
},
"writeStyleForURL": {
"message": "цей URL"
},
"zipStyles": {
"message": "Запаковування стилів ... "
}

View File

@ -1,379 +0,0 @@
{
"InaccessibleFileHint": {
"message": "Stylus không thể truy cập một số kiểu tập tin (chẳng hạn như PDF và JSON)."
},
"addStyleLabel": {
"message": "Viết bảng định kiểu mới"
},
"addStyleTitle": {
"message": "Thêm bảng định kiểu"
},
"alphaChannel": {
"message": "Độ mờ"
},
"appliesAdd": {
"message": "Thêm"
},
"appliesDisplay": {
"message": "Áp dụng với: $applies$",
"placeholders": {
"applies": {
"content": "$1"
}
}
},
"appliesDisplayTruncatedSuffix": {
"message": "và một số khác"
},
"appliesDomainOption": {
"message": "Các địa chỉ thuộc tên miền này"
},
"appliesHelp": {
"message": "Dùng tuỳ chọn \"Áp dụng với\" để giới hạn các địa chỉ cho đoạn mã này"
},
"appliesLabel": {
"message": "Áp dụng với"
},
"appliesLineWidgetLabel": {
"message": "Hiển thị thông tin \"Áp dụng với\""
},
"appliesLineWidgetWarning": {
"message": "Không hoạt động với CSS tối giản"
},
"appliesRegexpOption": {
"message": "URL khớp với biểu thức chính quy"
},
"appliesRemove": {
"message": "Xoá"
},
"appliesRemoveError": {
"message": "Không thể xoá mục \"Áp dụng với\" cuối cùng"
},
"appliesSpecify": {
"message": "Chỉ định"
},
"appliesToEverything": {
"message": "Tất cả"
},
"appliesUrlPrefixOption": {
"message": "URL bắt đầu bằng"
},
"author": {
"message": "Tác giả"
},
"backupButtons": {
"message": "Sao lưu"
},
"backupMessage": {
"message": "Để nhập tập tin sao lưu, kéo và thả nó vào trang này hoặc nhấp vào nút Nhập.\n\nĐể xuất một bản sao lưu tương thích với Stylus trước phiên bản 1.5.18, nhấp chuột phải hoặc nhấn giữ Shift khi nhấp chuột trái vào nút Xuất."
},
"bckpInstStyles": {
"message": "Xuất bảng định kiểu"
},
"checkForUpdate": {
"message": "Kiểm tra bản cập nhật mới"
},
"checkingForUpdate": {
"message": "Đang kiểm tra..."
},
"clickToUninstall": {
"message": "Nhấp để huỷ kích hoạt"
},
"cm_autoCloseBrackets": {
"message": "Tự động đóng ngoặc và nháy"
},
"cm_autoCloseBracketsTooltip": {
"message": "Tự động thêm dấu đóng tương ứng khi nhập một trong các dấu (, [, {, ' và \"."
},
"cm_autocompleteOnTyping": {
"message": "Tự động hoàn thành"
},
"cm_colorpicker": {
"message": "Bộ chọn màu cho màu CSS"
},
"cm_indentWithTabs": {
"message": "Lùi đầu dòng thông minh bằng tab"
},
"cm_lineWrapping": {
"message": "Gập dòng dài"
},
"cm_linter": {
"message": "Trình phân tích cú pháp"
},
"cm_matchHighlight": {
"message": "Làm nổi"
},
"cm_matchHighlightSelection": {
"message": "Chỉ vùng được chọn"
},
"cm_matchHighlightToken": {
"message": "Token nằm dưới con trỏ văn bản"
},
"cm_selectByTokens": {
"message": "Nhấp đúp để chọn token"
},
"cm_selectByTokensTooltip": {
"message": "Ví dụ về token: .foo-bar-2 #aabbcc 0.32 !important\nKhi tắt: Chọn từ (phân tách bằng dấu câu)."
},
"cm_smartIndent": {
"message": "Lùi đầu dòng thông minh"
},
"cm_tabSize": {
"message": "Chiều rộng tab"
},
"cm_theme": {
"message": "Chủ đề"
},
"colorpickerSwitchFormatTooltip": {
"message": "Đổi định dạng: HEX → RGB → HSL.\nNhấn giữ phím Shift khi nhấp để đảo thứ tự.\nPhím tắt: PgUp và PgDn."
},
"colorpickerTooltip": {
"message": "Mở bộ chọn màu"
},
"configOnChange": {
"message": "khi thay đổi"
},
"configOnChangeTooltip": {
"message": "Tự động lưu và áp dụng"
},
"configureStyle": {
"message": "Thiết lập"
},
"configureStyleOnHomepage": {
"message": "Thiết lập trên trang chủ"
},
"confirmCancel": {
"message": "Huỷ"
},
"confirmClose": {
"message": "Đóng"
},
"confirmDefault": {
"message": "Dùng mặc định"
},
"confirmDelete": {
"message": "Xoá"
},
"confirmDiscardChanges": {
"message": "Huỷ thay đổi?"
},
"confirmNo": {
"message": "Không"
},
"confirmSave": {
"message": "Lưu"
},
"confirmStop": {
"message": "Dừng"
},
"confirmYes": {
"message": "Có"
},
"connectingDropbox": {
"message": "Đang kết nối với Dropbox..."
},
"connectingDropboxNotAllowed": {
"message": "Tính năng kết nối với Dropbox chỉ khả dụng khi cài ứng dụng trực tiếp từ cửa hàng web"
},
"copied": {
"message": "Đã chép vào khay nhớ tạm"
},
"copy": {
"message": "Chép vào khay nhớ tạm"
},
"dateAbbrDay": {
"message": "$value$ ngày",
"placeholders": {
"value": {
"content": "$1"
}
}
},
"dateAbbrHour": {
"message": "$value$ giờ",
"placeholders": {
"value": {
"content": "$1"
}
}
},
"dateAbbrMonth": {
"message": "$value$ tháng",
"placeholders": {
"value": {
"content": "$1"
}
}
},
"dateAbbrYear": {
"message": "$value$ năm",
"placeholders": {
"value": {
"content": "$1"
}
}
},
"dateInstalled": {
"message": "Ngày cài đặt"
},
"dateUpdated": {
"message": "Ngày cập nhật"
},
"defaultTheme": {
"message": "mặc định"
},
"deleteStyleConfirm": {
"message": "Bạn có chắc chắn muốn xoá bảng định kiểu này không?"
},
"deleteStyleLabel": {
"message": "Xoá"
},
"disableAllStyles": {
"message": "Tắt tất cả bảng định kiểu"
},
"disableAllStylesOff": {
"message": "Bật tất cả bảng định kiểu"
},
"disableStyleLabel": {
"message": "Vô hiệu hoá"
},
"draftAction": {
"message": "Chọn \"Có\" để tải bản nháp hoặc \"Không\" để xoá nó đi."
},
"editDeleteText": {
"message": "Xoá"
},
"editGotoLine": {
"message": "Đi đến dòng (hoặc dòng:cột)"
},
"editStyleHeading": {
"message": "Sửa bảng định kiểu"
},
"editStyleLabel": {
"message": "Sửa"
},
"editStyleTitle": {
"message": "Sửa bảng định kiểu $stylename$",
"placeholders": {
"stylename": {
"content": "$1"
}
}
},
"editorSettings": {
"message": "Cài đặt trình soạn thảo"
},
"enableStyleLabel": {
"message": "Kích hoạt"
},
"exportCompatible": {
"message": "Xuất (chế độ tương thích)"
},
"exportLabel": {
"message": "Xuất"
},
"exportSavedSuccess": {
"message": "Lưu thành công"
},
"externalFeedback": {
"message": "Phản hồi"
},
"externalHomepage": {
"message": "Trang chủ"
},
"externalLink": {
"message": "Liên kết ngoài"
},
"externalSupport": {
"message": "Ủng hộ"
},
"genericAdd": {
"message": "Thêm"
},
"genericDescription": {
"message": "Mô tả"
},
"genericDisabledLabel": {
"message": "Vô hiệu hoá"
},
"genericEnabledLabel": {
"message": "Kích hoạt"
},
"genericError": {
"message": "Lỗi"
},
"genericHistoryLabel": {
"message": "Lịch sử"
},
"genericNext": {
"message": "Sau"
},
"genericPrevious": {
"message": "Trước"
},
"genericResetLabel": {
"message": "Đặt lại"
},
"genericSavedMessage": {
"message": "Đã lưu"
},
"genericTest": {
"message": "Thứ"
},
"genericTitle": {
"message": "Tiêu đề"
},
"genericUnknown": {
"message": "Không xác định"
},
"helpAlt": {
"message": "Trợ giúp"
},
"importLabel": {
"message": "Nhập"
},
"importReportLegendAdded": {
"message": "đã thêm"
},
"importReportLegendIdentical": {
"message": "Đã bỏ qua các tệp trùng lặp"
},
"importReportLegendInvalid": {
"message": "Đã bỏ qua các tệp không hợp lệ"
},
"importReportLegendUpdatedBoth": {
"message": "đã cập nhật siêu thông tin và mã"
},
"importReportLegendUpdatedCode": {
"message": "đã cập nhật mã"
},
"importReportLegendUpdatedMeta": {
"message": "đã cập nhật siêu thông tin"
},
"importReportTitle": {
"message": "Đã nhập xong"
},
"installUpdateFromLabel": {
"message": "Kiểm tra bản cập nhật mới"
},
"license": {
"message": "Giấy phép"
},
"linterCSSLintIncompatible": {
"message": "CSSLint không hỗ trợ bộ tiền xử lý $preprocessorname$",
"placeholders": {
"preprocessorname": {
"content": "$1"
}
}
},
"linterJSONError": {
"message": "Định dạng JSON không hợp lệ"
},
"styleEnabledLabel": {
"message": "Kích hoạt"
},
"styleSaveLabel": {
"message": "Lưu"
}
}

View File

@ -71,7 +71,7 @@
"message": "备份"
},
"backupMessage": {
"message": "选择/拖拽 JSON 备份文件到本页面来导入备份。\n\n若要导出 Stylus V1.5.18 之前的兼容备份请右击或Shift+左击「导出」按钮。"
"message": "拖拽JSON备份文件到本页面也可导入。"
},
"bckpInstStyles": {
"message": "导出所有样式"
@ -270,17 +270,6 @@
"disableStyleLabel": {
"message": "禁用"
},
"draftAction": {
"message": "选择“是”以加载此草稿,或“否”以丢弃它。"
},
"draftTitle": {
"message": "恢复 $date$ 个小时前的草稿",
"placeholders": {
"date": {
"content": "$1"
}
}
},
"dragDropMessage": {
"message": "拖放JSON备份文件到管理器页面即可导入!"
},
@ -307,9 +296,6 @@
}
}
},
"editorSettings": {
"message": "编辑器设置"
},
"enableStyleLabel": {
"message": "启用"
},
@ -319,9 +305,6 @@
"excludeStyleByUrlLabel": {
"message": "排除当前链接"
},
"exportCompatible": {
"message": "导出(兼容模式)"
},
"exportLabel": {
"message": "导出"
},
@ -360,15 +343,21 @@
"findStyles": {
"message": "查找样式"
},
"findStylesForSite": {
"message": "查找该域名的在线样式"
},
"findStylesInline": {
"message": "嵌入此页"
},
"findStylesInlineTooltip": {
"message": "将搜索结果嵌入到此页面显示"
},
"genericAdd": {
"message": "添加"
},
"genericClone": {
"message": "创建副本"
},
"genericDescription": {
"message": "描述"
},
"genericDisabledLabel": {
"message": "禁用"
},
@ -393,9 +382,6 @@
"genericSavedMessage": {
"message": "已保存"
},
"genericTest": {
"message": "测试"
},
"genericTitle": {
"message": "标题"
},
@ -405,9 +391,6 @@
"gettingStyles": {
"message": "正在获取所有样式..."
},
"headerResizerHint": {
"message": "仅在此类 编辑器/管理器/安装器 UI , 按住 Shift 可调整大小"
},
"helpAlt": {
"message": "帮助"
},
@ -503,18 +486,9 @@
"linkGetHelp": {
"message": "帮助"
},
"linkGetShareStyles": {
"message": "获取|分享 样式"
},
"linkGetShareStylesInfo": {
"message": "由社区驱动的新站点 userstyles.world 是由 userstyle 作者创建的,目的是取代 userstyles.org该站点在过去一年中缓慢且反应迟钝以至于许多作者停止更新他们的样式。"
},
"linkGetStyles": {
"message": "获取样式"
},
"linkGetStylesInfo": {
"message": "该存档站点由 userstyle 社区成员创建,约每天更新一次其内容,用于备份缓慢且无响应的 userstyles.org。 "
},
"linkTranslate": {
"message": "翻译"
},
@ -582,7 +556,7 @@
"message": "显示为灰色图标"
},
"manageFaviconsHelp": {
"message": "Stylus 使用外部服务 https://icons.duckduckgo.com 来获取图标"
"message": "Stylus 使用外部服务 https://www.google.com/s2/favicons 来获取图标"
},
"manageFilters": {
"message": "过滤器"
@ -623,6 +597,9 @@
"manageOnlyUsercss": {
"message": "只显示 UserCSS"
},
"manageTitle": {
"message": "Stylus管理器"
},
"menuShowBadge": {
"message": "计数器角标"
},
@ -788,17 +765,6 @@
}
}
},
"meta_unknownMetaTypo": {
"message": "可能的 @$keyOk$ ? 未知元数据 @$keyErr$",
"placeholders": {
"keyErr": {
"content": "$1"
},
"keyOk": {
"content": "$2"
}
}
},
"meta_unknownPreprocessor": {
"message": "未知的预处理器 @preprocessor$preprocessor$",
"placeholders": {
@ -842,15 +808,6 @@
"optionsAdvanced": {
"message": "高级设置"
},
"optionsAdvancedAutoSwitchSchemeBySystem": {
"message": "按系统偏好设置"
},
"optionsAdvancedAutoSwitchSchemeByTime": {
"message": "按夜间时间"
},
"optionsAdvancedAutoSwitchSchemeNever": {
"message": "已停用。樣式中的深色/淺色設定會被忽略。"
},
"optionsAdvancedContextDelete": {
"message": "向编辑器右键菜单添加“删除”"
},
@ -860,12 +817,6 @@
"optionsAdvancedExposeIframesNote": {
"message": "给每个iframe内嵌框架的html注入 stylus-iframe=\"父域名hostname\" 属性(值)\n\n用途: 用作iframe选择器来一键确定 iframe 父窗(window.top) 页面 的唯一选择器.\n\n示例 html[stylus-iframe] 或 html[stylus-iframe$=\"twitter.com\"] h1 {...}\n\n详见源码github.com/openstyles/stylus/blob/master/content/apply.js#L270"
},
"optionsAdvancedExposeStyleName": {
"message": "暴露样式名称"
},
"optionsAdvancedExposeStyleNameNote": {
"message": "在页面中暴露样式名称以方便在 DevTools 中调试样式。请重新加载标签页以应用新设置。"
},
"optionsAdvancedNewStyleAsUsercss": {
"message": "将新样式的格式设为UserCSS"
},
@ -911,9 +862,6 @@
"optionsHeading": {
"message": "选项"
},
"optionsIconAuto": {
"message": "符合深色/淺色模式"
},
"optionsIconDark": {
"message": "高对比度暗色图标"
},
@ -936,7 +884,7 @@
"message": "恢复默认值"
},
"optionsStylusThemes": {
"message": "點擊任何 Stylus 頁面(包含這個)的瀏覽器工具列中的 Stylus 圖示,然後點擊「尋找樣式」"
"message": "查找 Stylus UI 主题ᐝ"
},
"optionsSubheading": {
"message": "附加选项"
@ -953,9 +901,6 @@
"optionsSyncNone": {
"message": "无"
},
"optionsSyncPassword": {
"message": "密码"
},
"optionsSyncStatusConnected": {
"message": "已连接"
},
@ -990,21 +935,12 @@
}
}
},
"optionsSyncStatusRelogin": {
"message": "工作階段已過期,請再次登入。"
},
"optionsSyncStatusSyncing": {
"message": "同步中..."
},
"optionsSyncSyncNow": {
"message": "现在同步"
},
"optionsSyncUrl": {
"message": "URL "
},
"optionsSyncUsername": {
"message": "用户名"
},
"optionsUpdateImportNote": {
"message": "从旧版的 Stylus 或 Stylish 中导入备份样式时,\n请手动检测升级一次以确保所有样式都可更新到最新版本。"
},
@ -1047,9 +983,6 @@
"popupHotkeysTooltip": {
"message": "查看Popup列表快捷键"
},
"popupManageSiteStyles": {
"message": "管理网站样式"
},
"popupManageTooltip": {
"message": "右击 || Shift+左击: 打开管理器且筛选当前URL(不含iframe)"
},
@ -1071,50 +1004,17 @@
"prefShowBadge": {
"message": "计数器角标 (或图标右键)"
},
"preferScheme": {
"message": "深色/淺色模式偏好設定"
},
"preferSchemeAlways": {
"message": "目前被忽略(樣式一律套用),因為全域深色/淺色模式已被停用"
},
"preferSchemeDark": {
"message": "深色"
},
"preferSchemeLight": {
"message": "淺色"
},
"preferSchemeNone": {
"message": "無(一律套用)"
},
"previewLabel": {
"message": "实时预览"
},
"previewTooltip": {
"message": "无需保存即可临时预览样式效果"
},
"publish": {
"message": "发布"
},
"publishPush": {
"message": "发布更新"
},
"publishReconnect": {
"message": "尝试断开连接然后再次发布"
},
"publishRetry": {
"message": "Stylus 仍在尝试发布此样式,但如果您没有看到身份验证活动或弹出窗口,您可以重试, 现在重试 "
},
"publishStyle": {
"message": "发布样式"
},
"publishUsw": {
"message": "使用 <userstyles.world>"
},
"readingStyles": {
"message": "正在读取样式..."
},
"reload": {
"message": "重启"
"message": "重启 Stylus"
},
"replace": {
"message": "替换"
@ -1125,18 +1025,12 @@
"replaceWith": {
"message": "替换为"
},
"restoreTemplate": {
"message": "還原預設範本。\n\n不會變更目前開啟的編輯器頁面。"
},
"retrieveBckp": {
"message": "导入所有样式"
},
"retrieveDropboxSync": {
"message": "从 Dropbox 导入"
},
"saveAsTemplate": {
"message": "另存为模板"
},
"search": {
"message": "搜索"
},
@ -1161,12 +1055,6 @@
"searchResultNoneFound": {
"message": "没有找到与此页面相关的样式。"
},
"searchResultNotMatching": {
"message": "样式已安装但不适于当前 URL"
},
"searchResultNotMatchingNote": {
"message": "尝试请求该用户样式的作者添加 URL。\n\n您也可以在管理器中打开样式自己编辑\n但请注意这会禁用此样式的自动更新。"
},
"searchResultRating": {
"message": "评价"
},
@ -1212,18 +1100,12 @@
"sections": {
"message": "章节"
},
"settings": {
"message": "设置"
},
"shortcuts": {
"message": "快捷键"
},
"shortcutsNote": {
"message": "设置快捷键"
},
"shortcutsNoteFF": {
"message": "在 Firefox 66 或更新的版本中,您可以手動開啟內建的快捷鍵使用者介面:\n1) 右鍵點擊工具列中的 Stylus 圖示並選擇「管理」\n或是透過主選單或 Ctrl-Shift-A 開啟 about:addons\n2) 在開啟的頁面中點擊右上角的齒輪圖示,\n3) 選擇「管理擴充套件快捷鍵」。\n\n您也可以在此自訂快捷鍵。"
},
"sortDateNewestFirst": {
"message": "最新的优先"
},
@ -1269,30 +1151,12 @@
"styleEnabledLabel": {
"message": "启用"
},
"styleExcludeLabel": {
"message": "自定义排除网站"
},
"styleFromMozillaFormatError": {
"message": "导入 Mozilla 格式失败"
},
"styleFromMozillaFormatPrompt": {
"message": "粘贴 Mozilla 格式代码"
},
"styleIncludeLabel": {
"message": "自定义包括网站"
},
"styleInjectionImportance": {
"message": "切换样式的优先级"
},
"styleInjectionOrder": {
"message": "樣式注入順序"
},
"styleInjectionOrderHint": {
"message": "拖曳樣式可變更其位置。樣式按下列順序注入,所以清單下方的樣式可覆寫較早樣式。"
},
"styleInjectionOrderHint_prio": {
"message": "下面列出的重要样式始终是最后注入的,因此它们可以覆盖任何新安装的样式。单击样式的标记以切换其重要性。"
},
"styleInstall": {
"message": "要将“$stylename$”安装到 Stylus 中吗?",
"placeholders": {
@ -1329,21 +1193,9 @@
"styleMozillaFormatHeading": {
"message": "Mozilla 格式"
},
"styleName": {
"message": "樣式名稱"
},
"styleNotAppliedRegexpProblemTooltip": {
"message": "正则表达式错误,样式无法正常运行"
},
"styleNotAppliedSchemeDark": {
"message": "此样式仅在深色模式下应用"
},
"styleNotAppliedSchemeLight": {
"message": "此样式仅在浅色模式下应用"
},
"stylePreferSchemeLabel": {
"message": "深色/浅色模式"
},
"styleRegexpInvalidExplanation": {
"message": "部分正则表达式无法生效。"
},
@ -1353,6 +1205,9 @@
"styleRegexpProblemTooltip": {
"message": "多个部分代码无法正常处理正则表达式"
},
"styleRegexpTestButton": {
"message": "正则测试"
},
"styleRegexpTestFull": {
"message": "匹配到标签页的正则"
},
@ -1374,9 +1229,6 @@
"styleSaveLabel": {
"message": "保存"
},
"styleSettings": {
"message": "样式设置"
},
"styleToMozillaFormatHelp": {
"message": "导入 FireFox 格式即 @-moz-document ...的 CSS 代码后,会自动转换成 Stylus 使用的分段式 CSS 代码。\n反过来分段式的 CSS 也可以导出为 FireFox 格式。\n需要注意的是如果你要向 userstyles.org 提交你的代码,则必须使用 FireFox 格式。\n\n导入快捷键: 剪贴板含有 @-moz-document ....代码,则可以直接在编辑器里 Ctrl+V 会自动弹出导入对话框"
},
@ -1394,9 +1246,6 @@
"styleUpdateDiscardChanges": {
"message": "样式已经在编辑器外被修改。需要重新加载样式吗?"
},
"styleUpdateUrlLabel": {
"message": "更新 URL"
},
"stylusUnavailableForURL": {
"message": "Stylus 无法介入到此类页面"
},
@ -1409,20 +1258,6 @@
"syncDropboxStyles": {
"message": "导出至 Dropbox"
},
"syncError": {
"message": "同步失敗"
},
"syncErrorLock": {
"message": "数据库已在使用中。锁定将在 $TIME$ 过期",
"placeholders": {
"time": {
"content": "$1"
}
}
},
"syncErrorRelogin": {
"message": "同步失敗。您已被登出。\n嘗試在 Stylus 選項中重新登入。"
},
"syncStorageErrorSaving": {
"message": "不能保存该值,请尝试减少文本数量。"
},
@ -1447,12 +1282,6 @@
"unreachableFileHint": {
"message": "! Stylus 无法介入到官方商店页面 !\n若是文件页面 或 小号无痕页面 请检查:\n1) 右键 - Stylus图标 并点击-\"管理扩展程序\"\n2) 勾选 - \"允许访问文件网址\" 以在 file:// 工作\n3) 勾选 - \"在无痕模式下启用\" 以在无痕页面和小号标签页工作 (有的浏览器带有小号功能、如CentBrowser)"
},
"unreachableMozSiteHint": {
"message": "在 Firefox ⩾60 版本里, 你需要在 <about:config> 里移除 extensions.webextensions.restrictedDomains"
},
"unreachableMozSiteHintOldFF": {
"message": "仅 Firefox 59 或更新的版本才能让你设置 WebExtensions 在例如这样一个有 CSP 保护的页面上新增样式。"
},
"unzipStyles": {
"message": "正在解压样式..."
},
@ -1512,6 +1341,9 @@
"usercssReplaceTemplateConfirmation": {
"message": "使用当前的 UserStyle 替换为新的UserCSS默认模板 ?"
},
"usercssReplaceTemplateName": {
"message": "该赋值为空的保存可设置默认模板"
},
"usercssReplaceTemplateSectionBody": {
"message": "在此插入代码..."
},

View File

@ -12,7 +12,7 @@
"message": "透明度"
},
"appliesAdd": {
"message": "添加"
"message": "添加部分"
},
"appliesDisplay": {
"message": "应用于:$applies$",
@ -35,7 +35,7 @@
"message": "应用于:"
},
"appliesLineWidgetLabel": {
"message": "显示「应用于」信息"
"message": "显示应用于部件"
},
"appliesLineWidgetWarning": {
"message": "对经过压缩的 CSS 无效"
@ -71,7 +71,7 @@
"message": "备份"
},
"backupMessage": {
"message": "选择/拖拽 JSON 备份文件到本页面来导入备份。\n\n若要导出 Stylus V1.5.18 之前的兼容备份请右击或Shift+左击「导出」按钮。"
"message": "选择备份文件,或将 JSON 备份文件拖放到本页面,即可导入备份。"
},
"bckpInstStyles": {
"message": "导出所有样式"
@ -259,33 +259,19 @@
"message": "删除"
},
"description": {
"message": "Stylus 是一个调整网页外观的用户样式管理器。它可让您轻松为许多热门网站安装主题和皮肤。"
"message": "Stylus 是一个调整网页外观的用户样式管理器。它可让您轻松为许多热门网站网站安装主题和皮肤。"
},
"disableAllStyles": {
"message": "禁用所有样式"
},
"disableAllStylesOff": {
"message": "已禁用样式"
},
"disableStyleLabel": {
"message": "禁用"
},
"draftAction": {
"message": "选择“是”以加载此草稿,或“否”以丢弃它。"
},
"draftTitle": {
"message": "恢复 $date$ 个小时前的草稿",
"placeholders": {
"date": {
"content": "$1"
}
}
},
"dragDropMessage": {
"message": "把 JSON 备份文件拖放到该页面任意位置即可导入"
},
"dragDropUsercssTabstrip": {
"message": "要安装文件,就将其放在标签页条(显示标签页标题的区域)上。"
"message": "要安装文件,就将其放在选项卡条(显示选项卡标题的区域)上。"
},
"editDeleteText": {
"message": "删除"
@ -307,9 +293,6 @@
}
}
},
"editorSettings": {
"message": "编辑器设置"
},
"enableStyleLabel": {
"message": "启用"
},
@ -319,9 +302,6 @@
"excludeStyleByUrlLabel": {
"message": "排除当前链接"
},
"exportCompatible": {
"message": "导出(兼容模式)"
},
"exportLabel": {
"message": "导出"
},
@ -344,7 +324,7 @@
"message": "UserCSS 文档"
},
"filteredStyles": {
"message": "已显示 $numShown$ 个,共 $numTotal$ 个",
"message": "共 $numTotal$ 个,已显示 $numShown$ 个",
"placeholders": {
"numShown": {
"content": "$1"
@ -360,6 +340,15 @@
"findStyles": {
"message": "查找更多样式"
},
"findStylesForSite": {
"message": "查找适合此网站的更多样式"
},
"findStylesInline": {
"message": "嵌入到此页面"
},
"findStylesInlineTooltip": {
"message": "在此弹窗内显示搜索结果。"
},
"genericAdd": {
"message": "添加"
},
@ -393,12 +382,6 @@
"genericSavedMessage": {
"message": "已保存"
},
"genericSize": {
"message": "大小"
},
"genericTest": {
"message": "测试"
},
"genericTitle": {
"message": "标题"
},
@ -408,9 +391,6 @@
"gettingStyles": {
"message": "正在获取所有样式..."
},
"headerResizerHint": {
"message": "仅在此类 编辑器/管理器/安装器 UI , 按住 Shift 可调整大小"
},
"helpAlt": {
"message": "帮助"
},
@ -510,13 +490,13 @@
"message": "获取|分享 样式"
},
"linkGetShareStylesInfo": {
"message": "由社区驱动的新站点 userstyles.world 是用户样式的作者们创建的,目的是取代 userstyles.org。userstyles.org 在过去一年中运行缓慢且反应迟钝,以至于许多作者都不再更新他们的样式。"
"message": "由社区驱动的新站点 userstyles.world 是由 userstyle 作者创建的,目的是取代 userstyles.org该站点在过去一年中缓慢且反应迟钝以至于许多作者停止更新他们的样式。"
},
"linkGetStyles": {
"message": "获取样式"
},
"linkGetStylesInfo": {
"message": "该存档站点是一位用户样式社区的成员创建的,约每天更新一次其内容,用于备份运行缓慢且反应迟钝的 userstyles.org。"
"message": "该存档站点由 userstyle 社区成员创建,约每天更新一次其内容,用于备份缓慢且无响应的 userstyles.org。 "
},
"linkTranslate": {
"message": "翻译"
@ -570,22 +550,22 @@
"message": "查看文件时发生错误"
},
"liveReloadInstallHint": {
"message": "保持此标签页为打开状态,以便在外部更改时自动更新样式。"
"message": "保持此选项卡为打开状态,将在外部更改后自动更新样式。"
},
"liveReloadInstallHintFF": {
"message": "保持此标签页和原始标签页处于打开状态,以便在外部更改时自动更新样式。"
"message": "保持此选项卡和原始选项卡处于打开状态,将在外部更改时自动重新更新样式。"
},
"liveReloadLabel": {
"message": "动态刷新"
},
"manageFavicons": {
"message": "显示「应用于」列的网站图标favicon"
"message": "显示 \"应用于\" 的favicon图标"
},
"manageFaviconsGray": {
"message": "显示为灰色图标"
},
"manageFaviconsHelp": {
"message": "Stylus 使用外部服务 https://icons.duckduckgo.com 来获取图标"
"message": "Stylus 使用外部服务 https://www.google.com/s2/favicons 来获取图标"
},
"manageFilters": {
"message": "过滤器"
@ -845,15 +825,6 @@
"optionsAdvanced": {
"message": "高级设置"
},
"optionsAdvancedAutoSwitchSchemeBySystem": {
"message": "按系统偏好设置"
},
"optionsAdvancedAutoSwitchSchemeByTime": {
"message": "按夜间时间"
},
"optionsAdvancedAutoSwitchSchemeNever": {
"message": "停用。忽略样式的深色/浅色模式设置。"
},
"optionsAdvancedContextDelete": {
"message": "向编辑器右键菜单添加“删除”"
},
@ -863,12 +834,6 @@
"optionsAdvancedExposeIframesNote": {
"message": "给每个iframe框架的html注入window.top父域名的内联属性(值)\n即 html[stylus-iframe=\"hostname\"] \n\n用途: 确定或排除 iframe选择器以避免伤及无辜.\nhtml:not([stylus-iframe]) {...}\nhtml[stylus-iframe] 或 html[stylus-iframe$=\"twitter.com\"] h1 {...}\n\ngithub.com/openstyles/stylus/blob/master/content/apply.js#L270"
},
"optionsAdvancedExposeStyleName": {
"message": "暴露样式名称"
},
"optionsAdvancedExposeStyleNameNote": {
"message": "在页面中暴露样式名称以方便在 DevTools 中调试样式。请重新加载标签页以应用新设置。"
},
"optionsAdvancedNewStyleAsUsercss": {
"message": "将新样式的格式设为 UserCSS"
},
@ -902,6 +867,9 @@
"optionsCustomizeIcon": {
"message": "工具栏图标风格"
},
"optionsCustomizePopup": {
"message": "Popup "
},
"optionsCustomizeSync": {
"message": "同步到云端"
},
@ -911,14 +879,11 @@
"optionsHeading": {
"message": "选项"
},
"optionsIconAuto": {
"message": "跟随深色/浅色模式"
},
"optionsIconDark": {
"message": "深色浏览器主题"
"message": "高对比度暗色图标"
},
"optionsIconLight": {
"message": "浅色浏览器主题"
"message": "低对比度亮色图标"
},
"optionsOpen": {
"message": "打开"
@ -936,7 +901,7 @@
"message": "恢复默认值"
},
"optionsStylusThemes": {
"message": "在任意 Stylus 页面(包括这个)点击浏览器工具栏的 Stylus 图标,然后点击「寻找样式」"
"message": "查找 Stylus UI 主题ᐝ"
},
"optionsSubheading": {
"message": "附加选项"
@ -953,9 +918,6 @@
"optionsSyncNone": {
"message": "无"
},
"optionsSyncPassword": {
"message": "密码"
},
"optionsSyncStatusConnected": {
"message": "已连接"
},
@ -969,7 +931,7 @@
"message": "正在断开连接..."
},
"optionsSyncStatusPull": {
"message": "正在拉取样式 $loaded$ / $total$",
"message": "正在获取样式 $loaded$ 中的 $total$",
"placeholders": {
"loaded": {
"content": "$1"
@ -980,7 +942,7 @@
}
},
"optionsSyncStatusPush": {
"message": "正在推送样式 $loaded$ / $total$",
"message": "正在上传样式 $loaded$ 中的 $total$",
"placeholders": {
"loaded": {
"content": "$1"
@ -999,9 +961,6 @@
"optionsSyncSyncNow": {
"message": "现在同步"
},
"optionsSyncUsername": {
"message": "用户名"
},
"optionsUpdateImportNote": {
"message": "从旧版本的 Stylus 或 Stylish 中导入样式备份时,请在样式管理器中手动检测升级一次,以确保所有样式都可更新到最新版本。"
},
@ -1039,14 +998,11 @@
"message": "对于新 Chrome 中的暗色主题很有用,因为它不再绘制边框"
},
"popupHotkeysInfo": {
"message": "<1>-<9>, <0> 开启/关闭第 N 个样式0 就是 10\n<A>-<Z> 开启/关闭相应英文首字母的首个样式\n附加 <Shift> 打开编辑器而不是开启/关闭\n<Numpad +> 启用列出的样式\n<Numpad > 禁用列出的样式\n<Numpad *> 或 <`>(反引号)- 开启/关闭已启用的首个样式,当弹出菜单开启时不应用后来启用的样式,因此你可以在测试完成后恢复初始状态:简单地禁用所有样式,然后切换如 <Numpad > <Numpad *>\n更多信息见 Wiki"
"message": "<1>-<9>, <0> 按第1 - 10个列表 来ON/OFF\n<A>-<Z> 按英文首字母 来ON/OFF\n附加<Shift>: 打开对应列表的编辑器\n<Numpad +> ON 全部列表\n<Numpad > OFF 全部列表\n<Numpad *> 或 <`> 仅针对Popup弹出时的初始ON列表, 它只会ON/OFF初始ON列表, 故:\n <Numpad > <Numpad *> 能快速恢复初始状态.\n 更多信息见Wiki"
},
"popupHotkeysTooltip": {
"message": "查看 Popup 快捷键"
},
"popupManageSiteStyles": {
"message": "管理网站样式"
},
"popupManageTooltip": {
"message": "右键单击或 Shift + 左键单击可打开管理器并显示适用于当前网站的样式"
},
@ -1068,21 +1024,6 @@
"prefShowBadge": {
"message": "在当前网站生效的样式数量"
},
"preferScheme": {
"message": "深色/浅色模式偏好设置"
},
"preferSchemeAlways": {
"message": "目前已忽略(总是应用的样式),因为已停用全局深色/浅色模式。"
},
"preferSchemeDark": {
"message": "深色"
},
"preferSchemeLight": {
"message": "浅色"
},
"preferSchemeNone": {
"message": "无(总是应用)"
},
"previewLabel": {
"message": "实时预览"
},
@ -1093,7 +1034,7 @@
"message": "发布"
},
"publishPush": {
"message": "推送更新"
"message": "发布更新"
},
"publishReconnect": {
"message": "尝试断开连接然后再次发布"
@ -1111,7 +1052,7 @@
"message": "正在读取样式..."
},
"reload": {
"message": "重启"
"message": "重启 Stylus"
},
"replace": {
"message": "替换"
@ -1122,18 +1063,12 @@
"replaceWith": {
"message": "替换为"
},
"restoreTemplate": {
"message": "恢复默认模板。\n\n不会改变当前打开的编辑器页面"
},
"retrieveBckp": {
"message": "导入所有样式"
},
"retrieveDropboxSync": {
"message": "从 Dropbox 导入"
},
"saveAsTemplate": {
"message": "另存为模板"
},
"search": {
"message": "搜索"
},
@ -1153,7 +1088,7 @@
"message": "/regex/ 用 / 包裹语法来正则搜索"
},
"searchResultInstallCount": {
"message": "总安装次数"
"message": "总安装次数"
},
"searchResultNoneFound": {
"message": "没有找到与此页面相关的样式。"
@ -1173,6 +1108,9 @@
"searchResultWeeklyCount": {
"message": "本周安装次数"
},
"searchStyleQueryHint": {
"message": "空格多词 -- 同时精确匹配(无需双引号)\n2020 -- 也含更新日期的匹配结果\n大小写不敏感"
},
"searchStylesAll": {
"message": "全部"
},
@ -1206,18 +1144,12 @@
"sections": {
"message": "章节"
},
"settings": {
"message": "设置"
},
"shortcuts": {
"message": "快捷键"
},
"shortcutsNote": {
"message": "设置快捷键"
},
"shortcutsNoteFF": {
"message": "在 Firefox 66 及之后更新的版本中:\n1) 右键单击工具栏的的 Stylus 图标,选择「管理」\n也可以通过主菜单或 Ctrl-Shift-A 打开 about:addons\n2) 在打开的页面点击右上角的齿轮图标;\n3) 选择「管理扩展快捷键」。\n\n您也可以在这自定义快捷键。"
},
"sortDateNewestFirst": {
"message": "最新的优先"
},
@ -1263,30 +1195,12 @@
"styleEnabledLabel": {
"message": "启用"
},
"styleExcludeLabel": {
"message": "自定义排除网站"
},
"styleFromMozillaFormatError": {
"message": "导入 Mozilla 格式失败"
},
"styleFromMozillaFormatPrompt": {
"message": "粘贴 Mozilla 格式代码"
},
"styleIncludeLabel": {
"message": "自定义包括网站"
},
"styleInjectionImportance": {
"message": "切换样式的优先级"
},
"styleInjectionOrder": {
"message": "样式注入顺序"
},
"styleInjectionOrderHint": {
"message": "拖拽样式可更改排序。样式会按如下顺序注入,所以在列表更下方样式可覆盖较前面的样式。"
},
"styleInjectionOrderHint_prio": {
"message": "下面列出的重要样式始终是最后注入的,因此它们可以覆盖任何新安装的样式。单击样式的标记以切换其重要性。"
},
"styleInstall": {
"message": "要将“$stylename$”安装到 Stylus 中吗?",
"placeholders": {
@ -1329,15 +1243,6 @@
"styleNotAppliedRegexpProblemTooltip": {
"message": "正则表达式错误,样式无法正常运行"
},
"styleNotAppliedSchemeDark": {
"message": "此样式仅在深色模式下应用"
},
"styleNotAppliedSchemeLight": {
"message": "此样式仅在浅色模式下应用"
},
"stylePreferSchemeLabel": {
"message": "深色/浅色模式"
},
"styleRegexpInvalidExplanation": {
"message": "部分正则表达式无法生效。"
},
@ -1347,6 +1252,9 @@
"styleRegexpProblemTooltip": {
"message": "多个部分代码无法正常处理正则表达式"
},
"styleRegexpTestButton": {
"message": "正则测试"
},
"styleRegexpTestFull": {
"message": "匹配到标签页的正则"
},
@ -1368,9 +1276,6 @@
"styleSaveLabel": {
"message": "保存"
},
"styleSettings": {
"message": "样式设置"
},
"styleToMozillaFormatHelp": {
"message": "导入 FireFox 格式即 @-moz-document ...的 CSS 代码后,会自动转换成 Stylus 使用的分段式 CSS 代码。\n反过来分段式的 CSS 也可以导出为 FireFox 格式。\n需要注意的是如果你要向 userstyles.org 提交你的代码,则必须使用 FireFox 格式。\n\n导入快捷键: 剪贴板含有 @-moz-document ....代码,则可以直接在编辑器里 Ctrl+V 会自动弹出导入对话框"
},
@ -1388,11 +1293,8 @@
"styleUpdateDiscardChanges": {
"message": "样式已经在编辑器外被修改。需要重新加载样式吗?"
},
"styleUpdateUrlLabel": {
"message": "更新 URL"
},
"stylusUnavailableForURL": {
"message": "Stylus 不能在此类页面上工作"
"message": "Stylus 无法介入到此类页面"
},
"stylusUnavailableForURLdetails": {
"message": "出于安全考虑,浏览器禁止扩展程序影响其内置页面(例如 chrome://versionChrome 61 后的标准新标签页about:addons 等等)以及其他扩展程序的页面。每个浏览器也限制了对于自己扩展程序库的介入〔例如 Chrome 网上应用店、Firefox 附加组件addons.mozilla.org。"
@ -1406,16 +1308,8 @@
"syncError": {
"message": "同步失败"
},
"syncErrorLock": {
"message": "数据库已在使用中。锁定将在 $TIME$ 过期",
"placeholders": {
"time": {
"content": "$1"
}
}
},
"syncErrorRelogin": {
"message": "同步失败。你已退出登录。\n请尝试在 Stylus 选项里重新登录。"
"message": "同步失败\n请尝试在 Stylus 选项里重新登录\n先点击「断开连接」再点击「连接」。"
},
"syncStorageErrorSaving": {
"message": "不能保存该值,请尝试减少文本数量。"
@ -1506,6 +1400,9 @@
"usercssReplaceTemplateConfirmation": {
"message": "使用当前 UserStyle 代码替换为新的默认模板吗 ?"
},
"usercssReplaceTemplateName": {
"message": "@name 为空值 可设置新的默认模板"
},
"usercssReplaceTemplateSectionBody": {
"message": "在此插入代码..."
},

View File

@ -71,7 +71,7 @@
"message": "備份"
},
"backupMessage": {
"message": "要匯入備份檔案,請將其拖曳至此頁面或點擊匯入按鈕。\n\n要匯出相容於比 1.5.18 還舊的 Stylus 版本的備份,請右鍵點擊或按住 Shift 然後點擊匯出按鈕。"
"message": "選取檔案並拖曳到此頁面。"
},
"bckpInstStyles": {
"message": "匯出樣式"
@ -264,23 +264,9 @@
"disableAllStyles": {
"message": "停用所有樣式"
},
"disableAllStylesOff": {
"message": "樣式已關閉"
},
"disableStyleLabel": {
"message": "停用"
},
"draftAction": {
"message": "選擇「是」以載入此草稿,或「否」以放棄"
},
"draftTitle": {
"message": "還原草稿,已建立 $date$",
"placeholders": {
"date": {
"content": "$1"
}
}
},
"dragDropMessage": {
"message": "將您的備份檔拖曳到此頁面的任何地方以匯入。"
},
@ -307,9 +293,6 @@
}
}
},
"editorSettings": {
"message": "編輯器設定"
},
"enableStyleLabel": {
"message": "啟用"
},
@ -319,9 +302,6 @@
"excludeStyleByUrlLabel": {
"message": "排除目前的 URL"
},
"exportCompatible": {
"message": "匯出(相容模式)"
},
"exportLabel": {
"message": "匯出"
},
@ -360,6 +340,15 @@
"findStyles": {
"message": "尋找樣式"
},
"findStylesForSite": {
"message": "查找更多適合此網站的樣式"
},
"findStylesInline": {
"message": "嵌入"
},
"findStylesInlineTooltip": {
"message": "在此視窗中顯示搜尋結果。"
},
"genericAdd": {
"message": "新增"
},
@ -393,12 +382,6 @@
"genericSavedMessage": {
"message": "已儲存"
},
"genericSize": {
"message": "大小"
},
"genericTest": {
"message": "測試"
},
"genericTitle": {
"message": "標題"
},
@ -408,9 +391,6 @@
"gettingStyles": {
"message": "正在取得所有樣式……"
},
"headerResizerHint": {
"message": "按住 Shift 以僅在此類型的 UI 中調整大小,例如編輯器、管理程式、安裝程式等"
},
"helpAlt": {
"message": "說明"
},
@ -585,7 +565,7 @@
"message": "灰階淡出"
},
"manageFaviconsHelp": {
"message": "Stylus 使用外部服務 https://icons.duckduckgo.com"
"message": "Stylus 使用外部服務 https://www.google.com/s2/favicons"
},
"manageFilters": {
"message": "過濾器"
@ -596,9 +576,6 @@
"manageMaxTargets": {
"message": "已套用項目的數量"
},
"manageMinColumnWidth": {
"message": "最小欄位寬度以像素為單位9999 停用多欄模式)"
},
"manageNewStyleAsUsercss": {
"message": "做為 Usercss"
},
@ -848,15 +825,6 @@
"optionsAdvanced": {
"message": "進階"
},
"optionsAdvancedAutoSwitchSchemeBySystem": {
"message": "按系統偏好設定"
},
"optionsAdvancedAutoSwitchSchemeByTime": {
"message": "到夜間:"
},
"optionsAdvancedAutoSwitchSchemeNever": {
"message": "已停用。樣式中的深色/淺色設定會被忽略。"
},
"optionsAdvancedContextDelete": {
"message": "在編輯器的右鍵選單中加入「刪除」"
},
@ -866,12 +834,6 @@
"optionsAdvancedExposeIframesNote": {
"message": "公開每個 iframe 中的頂級頁面網域。\n啟用編寫特別用於 iframe 的 CSS如這個\nhtml[stylus-iframe$$=\"twitter.com\"] h1 { display:none }"
},
"optionsAdvancedExposeStyleName": {
"message": "發佈樣式名稱"
},
"optionsAdvancedExposeStyleNameNote": {
"message": "在頁面中發佈樣式名稱以方便在 devtools 中對樣式進行除錯。請重新載入分頁以套用新設定。"
},
"optionsAdvancedNewStyleAsUsercss": {
"message": "以 usercss 編寫新樣式"
},
@ -917,9 +879,6 @@
"optionsHeading": {
"message": "選項"
},
"optionsIconAuto": {
"message": "符合深色/淺色模式"
},
"optionsIconDark": {
"message": "暗色瀏覽器主題"
},
@ -942,7 +901,7 @@
"message": "重設選項"
},
"optionsStylusThemes": {
"message": "點擊任何 Stylus 頁面(包含這個)的瀏覽器工具列中的 Stylus 圖示,然後點擊「尋找樣式」"
"message": "尋找 Sytlus UI 佈景主題"
},
"optionsSubheading": {
"message": "更多選項"
@ -959,9 +918,6 @@
"optionsSyncNone": {
"message": "無"
},
"optionsSyncPassword": {
"message": "密碼"
},
"optionsSyncStatusConnected": {
"message": "已連線"
},
@ -1005,9 +961,6 @@
"optionsSyncSyncNow": {
"message": "立刻同步"
},
"optionsSyncUsername": {
"message": "使用者名稱"
},
"optionsUpdateImportNote": {
"message": "當從舊版本或是從 Stylish 匯入樣式備份時,在樣式管理員中手動進行一次更新檢查,以確保所有樣式都被更新。"
},
@ -1050,9 +1003,6 @@
"popupHotkeysTooltip": {
"message": "點選以檢視可用的快速鍵"
},
"popupManageSiteStyles": {
"message": "管理網站樣式"
},
"popupManageTooltip": {
"message": "Shift + 點選或右鍵 + 點選以在管理員中開啟目前頁面可用的樣式"
},
@ -1074,21 +1024,6 @@
"prefShowBadge": {
"message": "在工具欄按鈕上顯示當前網站已生效的樣式表數目。"
},
"preferScheme": {
"message": "深色/淺色模式偏好設定"
},
"preferSchemeAlways": {
"message": "目前被忽略(樣式一律套用),因為全域深色/淺色模式已被停用"
},
"preferSchemeDark": {
"message": "深色"
},
"preferSchemeLight": {
"message": "淺色"
},
"preferSchemeNone": {
"message": "無(一律套用)"
},
"previewLabel": {
"message": "即時預覽"
},
@ -1117,7 +1052,7 @@
"message": "正在讀取樣式……"
},
"reload": {
"message": "重"
"message": "重新載入 Stylus 附加元件"
},
"replace": {
"message": "取代"
@ -1128,18 +1063,12 @@
"replaceWith": {
"message": "取代為"
},
"restoreTemplate": {
"message": "還原預設範本。\n\n不會變更目前開啟的編輯器頁面。"
},
"retrieveBckp": {
"message": "匯入樣式"
},
"retrieveDropboxSync": {
"message": "Dropbox 匯入"
},
"saveAsTemplate": {
"message": "另存為範本"
},
"search": {
"message": "搜尋"
},
@ -1180,7 +1109,7 @@
"message": "每週安裝"
},
"searchStyleQueryHint": {
"message": "搜尋樣式名稱(若使用大寫字母,則區分大小寫):\n一些字 - 以任何順序符合所有字\n\"一些詞\" - 精確符合引號內的詞\n/foo.*bar/i - 沒有空格的正規表示式(使用 \\s 代替)"
"message": "搜尋樣式名稱時是區分大小寫的:\nsome words - 以任意順序搜尋所有文字\n\"some phrase\" - 精確符合引號內的詞語\n2020 - 如此年份顯示在2020年更新的樣式"
},
"searchStylesAll": {
"message": "全部"
@ -1215,18 +1144,12 @@
"sections": {
"message": "樣式段"
},
"settings": {
"message": "設定"
},
"shortcuts": {
"message": "快速鍵"
},
"shortcutsNote": {
"message": "自訂鍵盤快速鍵"
},
"shortcutsNoteFF": {
"message": "在 Firefox 66 或更新的版本中,您可以手動開啟內建的快捷鍵使用者介面:\n1) 右鍵點擊工具列中的 Stylus 圖示並選擇「管理」\n或是透過主選單或 Ctrl-Shift-A 開啟 about:addons\n2) 在開啟的頁面中點擊右上角的齒輪圖示,\n3) 選擇「管理擴充套件快捷鍵」。\n\n您也可以在此自訂快捷鍵。"
},
"sortDateNewestFirst": {
"message": "最新的優先"
},
@ -1272,30 +1195,12 @@
"styleEnabledLabel": {
"message": "已啟用"
},
"styleExcludeLabel": {
"message": "自訂排除網站"
},
"styleFromMozillaFormatError": {
"message": "從 Mozilla 格式匯入失敗"
},
"styleFromMozillaFormatPrompt": {
"message": "貼上 Mozilla 格式代碼"
},
"styleIncludeLabel": {
"message": "自訂包含網站"
},
"styleInjectionImportance": {
"message": "切換樣式重要程度"
},
"styleInjectionOrder": {
"message": "樣式注入順序"
},
"styleInjectionOrderHint": {
"message": "拖曳樣式可變更其位置。樣式按下列順序注入,所以清單下方的樣式可覆寫較早樣式。"
},
"styleInjectionOrderHint_prio": {
"message": "下方列出的重要樣式一律最後注入,因此它們會覆寫新安裝的樣式。點擊樣式的標記以切換其重要程度。"
},
"styleInstall": {
"message": "安裝 '$stylename$' 到 Stylus ",
"placeholders": {
@ -1338,15 +1243,6 @@
"styleNotAppliedRegexpProblemTooltip": {
"message": "因為不正確的 'regexp()' 使用導致未套用"
},
"styleNotAppliedSchemeDark": {
"message": "此樣式僅於深色模式中套用"
},
"styleNotAppliedSchemeLight": {
"message": "此樣式僅於淺色模式中套用"
},
"stylePreferSchemeLabel": {
"message": "深色/淺色模式"
},
"styleRegexpInvalidExplanation": {
"message": "部份 'regexp()' 規則可能不再能被編譯。"
},
@ -1356,6 +1252,9 @@
"styleRegexpProblemTooltip": {
"message": "因為不正確的 'regexp()' 使用導致未套用的樣式段數量"
},
"styleRegexpTestButton": {
"message": "正規表示式測試"
},
"styleRegexpTestFull": {
"message": "符合的分頁"
},
@ -1377,9 +1276,6 @@
"styleSaveLabel": {
"message": "儲存"
},
"styleSettings": {
"message": "樣式設定"
},
"styleToMozillaFormatHelp": {
"message": "Mozilla格式的樣式代碼能在火狐版Stylus使用也可以提交至 userstyles.org 。"
},
@ -1397,9 +1293,6 @@
"styleUpdateDiscardChanges": {
"message": "樣式已在編輯器外變更。您想要重新載入樣式嗎?"
},
"styleUpdateUrlLabel": {
"message": "更新 URL"
},
"stylusUnavailableForURL": {
"message": "Stylus 不能在諸如此類的網頁上生效。"
},
@ -1415,16 +1308,8 @@
"syncError": {
"message": "同步失敗"
},
"syncErrorLock": {
"message": "資料庫已在使用中。鎖將於 $TIME$ 過期",
"placeholders": {
"time": {
"content": "$1"
}
}
},
"syncErrorRelogin": {
"message": "同步失敗。您已被登出。\n嘗試在 Stylus 選項中重新登入。"
"message": "同步失敗。\n重是在 Stylus 選項重新登入:\n先點擊「斷線」然後「連線」。"
},
"syncStorageErrorSaving": {
"message": "無法儲存值。嘗試減少文字量。"
@ -1515,6 +1400,9 @@
"usercssReplaceTemplateConfirmation": {
"message": "為新的 Usercss 樣式取代預設的範本為目前的程式碼?"
},
"usercssReplaceTemplateName": {
"message": "清空 @name 取代目前範本"
},
"usercssReplaceTemplateSectionBody": {
"message": "在此插入程式碼……"
},

View File

@ -6,9 +6,15 @@
/* global syncMan */
/* global updateMan */
/* global usercssMan */
/* global usoApi */
/* global uswApi */
/* global FIREFOX UA activateTab openURL */ // toolbox.js
/* global
FIREFOX
URLS
activateTab
download
findExistingTab
openURL
*/ // toolbox.js
/* global colorScheme */ // color-scheme.js
'use strict';
@ -35,12 +41,17 @@ addAPI(/** @namespace API */ {
sync: syncMan,
updater: updateMan,
usercss: usercssMan,
uso: usoApi,
usw: uswApi,
colorScheme,
/** @type {BackgroundWorker} */
worker: createWorker({url: '/background/background-worker'}),
download(url, opts) {
return typeof url === 'string' && url.startsWith(URLS.uso) &&
this.sender.url.startsWith(URLS.uso) &&
download(url, opts || {});
},
/** @returns {string} */
getTabUrlPrefix() {
return this.sender.tab.url.match(/^([\w-]+:\/+[^/#]+)/)[1];
@ -58,24 +69,10 @@ addAPI(/** @namespace API */ {
async openEditor(params) {
const u = new URL(chrome.runtime.getURL('edit.html'));
u.search = new URLSearchParams(params);
const wnd = chrome.windows && prefs.get('openEditInWindow');
const wnd = prefs.get('openEditInWindow');
const wndPos = wnd && prefs.get('windowPosition');
const wndBase = wnd && prefs.get('openEditInWindow.popup') ? {type: 'popup'} : {};
const ffBug = wnd && FIREFOX; // https://bugzil.la/1271047
if (wndPos) {
const {left, top, width, height} = wndPos;
const r = left + width;
const b = top + height;
const peek = 32;
if (isNaN(r) || r < peek || left > screen.availWidth - peek || width < 100) {
delete wndPos.left;
delete wndPos.width;
}
if (isNaN(b) || b < peek || top > screen.availHeight - peek || height < 100) {
delete wndPos.top;
delete wndPos.height;
}
}
const tab = await openURL({
url: `${u}`,
currentWindow: null,
@ -87,25 +84,27 @@ addAPI(/** @namespace API */ {
/** @returns {Promise<chrome.tabs.Tab>} */
async openManage({options = false, search, searchMode} = {}) {
const setUrlParams = url => {
const u = new URL(url);
if (search) u.searchParams.set('search', search);
if (searchMode) u.searchParams.set('searchMode', searchMode);
if (options) u.hash = '#stylus-options';
return u.href;
};
const base = chrome.runtime.getURL('manage.html');
const url = setUrlParams(base);
const tabs = await browser.tabs.query({url: base + '*'});
const same = tabs.find(t => t.url === url);
let tab = same || tabs[0];
if (!tab) {
API.prefsDb.get('badFavs'); // prime the cache to avoid flicker/delay when opening the page
tab = await openURL({url, newTab: true});
} else if (!same) {
msg.sendTab(tab.id, {method: 'pushState', url: setUrlParams(tab.url)});
let url = chrome.runtime.getURL('manage.html');
if (search) {
url += `?search=${encodeURIComponent(search)}&searchMode=${searchMode}`;
}
return activateTab(tab); // activateTab unminimizes the window
if (options) {
url += '#stylus-options';
}
const tab = await findExistingTab({
url,
currentWindow: null,
ignoreHash: true,
ignoreSearch: true,
});
if (tab) {
await activateTab(tab);
if (url !== (tab.pendingUrl || tab.url)) {
await msg.sendTab(tab.id, {method: 'pushState', url}).catch(console.error);
}
return tab;
}
return openURL({url, ignoreExisting: true}).then(activateTab); // activateTab unminimizes the window
},
/**
@ -158,25 +157,10 @@ if (chrome.commands) {
}
chrome.runtime.onInstalled.addListener(({reason, previousVersion}) => {
if (reason === 'install') {
if (UA.mobile) prefs.set('manage.newUI', false);
if (UA.windows) prefs.set('editor.keyMap', 'sublime');
}
// TODO: remove this before 1.5.23 as it's only for a few users who installed git 26b75e77
if (reason === 'update' && previousVersion === '1.5.22') {
for (const dbName of ['drafts', prefs.STORAGE_KEY]) {
try {
indexedDB.open(dbName).onsuccess = async e => {
const idb = /** @type IDBDatabase */ e.target.result;
const ta = idb.objectStoreNames[0] === 'data' && idb.transaction(['data']);
if (ta && ta.objectStore('data').autoIncrement) {
ta.abort();
idb.close();
await new Promise(setTimeout);
indexedDB.deleteDatabase(dbName);
}
};
} catch (e) {}
if (reason === 'update') {
const [a, b, c] = (previousVersion || '').split('.');
if (a <= 1 && b <= 5 && c <= 13) { // 1.5.13
require(['/background/remove-unused-storage']);
}
}
});
@ -208,6 +192,6 @@ Promise.all([
require(['/background/context-menus']),
]).then(() => {
bgReady._resolveAll();
msg.ready = true;
msg.isBgReady = true;
msg.broadcast({method: 'backgroundReady'});
});

View File

@ -4,58 +4,33 @@
'use strict';
const colorScheme = (() => {
let systemPreferDark = false;
let timePreferDark = false;
const changeListeners = new Set();
const kSTATE = 'schemeSwitcher.enabled';
const kSTART = 'schemeSwitcher.nightStart';
const kEND = 'schemeSwitcher.nightEnd';
const SCHEMES = ['dark', 'light'];
const isDark = {
never: null,
dark: true,
light: false,
system: false,
time: false,
};
let isDarkNow = false;
prefs.subscribe(kSTATE, () => update());
prefs.subscribe([kSTART, kEND], (key, value) => {
const checkTime = ['schemeSwitcher.nightStart', 'schemeSwitcher.nightEnd'];
prefs.subscribe(checkTime, (key, value) => {
updateTimePreferDark();
createAlarm(key, value);
}, {runNow: true});
chrome.alarms.onAlarm.addListener(({name}) => {
if (name === kSTART || name === kEND) {
});
checkTime.forEach(key => createAlarm(key, prefs.get(key)));
prefs.subscribe(['schemeSwitcher.enabled'], emitChange);
chrome.alarms.onAlarm.addListener(info => {
if (checkTime.includes(info.name)) {
updateTimePreferDark();
}
});
return {
SCHEMES,
onChange(listener) {
changeListeners.add(listener);
},
isDark: () => isDarkNow,
/** @param {StyleObj} _ */
shouldIncludeStyle({preferScheme: ps}) {
return prefs.get(kSTATE) === 'never' ||
!SCHEMES.includes(ps) ||
isDarkNow === (ps === 'dark');
},
updateSystemPreferDark(val) {
update('system', val);
return true;
},
};
updateSystemPreferDark();
updateTimePreferDark();
function calcTime(key) {
const [h, m] = prefs.get(key).split(':');
return (h * 3600 + m * 60) * 1000;
}
return {shouldIncludeStyle, onChange, updateSystemPreferDark};
function createAlarm(key, value) {
const date = new Date();
const [h, m] = value.split(':');
date.setHours(h, m, 0, 0);
applyDate(date, value);
if (date.getTime() < Date.now()) {
date.setDate(date.getDate() + 1);
}
@ -65,27 +40,61 @@ const colorScheme = (() => {
});
}
function updateTimePreferDark() {
const now = Date.now() - new Date().setHours(0, 0, 0, 0);
const start = calcTime(kSTART);
const end = calcTime(kEND);
const val = start > end ?
now >= start || now < end :
now >= start && now < end;
update('time', val);
function shouldIncludeStyle(style) {
const isDark = style.preferScheme === 'dark';
const isLight = style.preferScheme === 'light';
if (!isDark && !isLight) {
return true;
}
const switcherState = prefs.get('schemeSwitcher.enabled');
if (switcherState === 'never') {
return true;
}
if (switcherState === 'system') {
return systemPreferDark && isDark ||
!systemPreferDark && isLight;
}
return timePreferDark && isDark ||
!timePreferDark && isLight;
}
function update(type, val) {
if (type) {
if (isDark[type] === val) return;
isDark[type] = val;
function updateSystemPreferDark() {
const oldValue = systemPreferDark;
systemPreferDark = window.matchMedia('(prefers-color-scheme: dark)').matches;
if (systemPreferDark !== oldValue) {
emitChange();
}
val = isDark[prefs.get(kSTATE)];
if (isDarkNow !== val) {
isDarkNow = val;
return true;
}
function updateTimePreferDark() {
const oldValue = timePreferDark;
const date = new Date();
const now = date.getTime();
applyDate(date, prefs.get('schemeSwitcher.nightStart'));
const start = date.getTime();
applyDate(date, prefs.get('schemeSwitcher.nightEnd'));
const end = date.getTime();
timePreferDark = start > end ?
now >= start || now < end :
now >= start && now < end;
if (timePreferDark !== oldValue) {
emitChange();
}
}
function applyDate(date, time) {
const [h, m] = time.split(':').map(Number);
date.setHours(h, m, 0, 0);
}
function onChange(listener) {
changeListeners.add(listener);
}
function emitChange() {
for (const listener of changeListeners) {
listener(isDarkNow);
}
listener();
}
}
})();

View File

@ -5,19 +5,16 @@
* Common stuff that's loaded first so it's immediately available to all background scripts
*/
window.bgReady = {}; /* global bgReady */
/* exported
addAPI
bgReady
compareRevision
*/
const bgReady = {};
bgReady.styles = new Promise(r => (bgReady._resolveStyles = r));
bgReady.all = new Promise(r => (bgReady._resolveAll = r));
const uuidIndex = Object.assign(new Map(), {
custom: {},
/** `obj` must have a unique `id`, a UUIDv4 `_id`, and Date.now() for `_rev`. */
addCustomId(obj, {get = () => obj, set}) {
Object.defineProperty(uuidIndex.custom, obj.id, {get, set});
},
});
/* exported addAPI */
function addAPI(methods) {
for (const [key, val] of Object.entries(methods)) {
const old = API[key];
@ -29,64 +26,6 @@ function addAPI(methods) {
}
}
/* exported createCache */
/** Creates a FIFO limit-size map. */
function createCache({size = 1000, onDeleted} = {}) {
const map = new Map();
const buffer = Array(size);
let index = 0;
let lastIndex = 0;
return {
get(id) {
const item = map.get(id);
return item && item.data;
},
set(id, data) {
if (map.size === size) {
// full
map.delete(buffer[lastIndex].id);
if (onDeleted) {
onDeleted(buffer[lastIndex].id, buffer[lastIndex].data);
}
lastIndex = (lastIndex + 1) % size;
}
const item = {id, data, index};
map.set(id, item);
buffer[index] = item;
index = (index + 1) % size;
},
delete(id) {
const item = map.get(id);
if (!item) {
return false;
}
map.delete(item.id);
const lastItem = buffer[lastIndex];
lastItem.index = item.index;
buffer[item.index] = lastItem;
lastIndex = (lastIndex + 1) % size;
if (onDeleted) {
onDeleted(item.id, item.data);
}
return true;
},
clear() {
map.clear();
index = lastIndex = 0;
},
has: id => map.has(id),
*entries() {
for (const [id, item] of map) {
yield [id, item.data];
}
},
*values() {
for (const item of map.values()) {
yield item.data;
}
},
get size() {
return map.size;
},
};
function compareRevision(rev1, rev2) {
return rev1 - rev2;
}

View File

@ -1,61 +1,80 @@
/* global browserCommands */// background.js
/* global msg */
/* global prefs */
/* global CHROME URLS ignoreChromeError */// toolbox.js
/* global CHROME FIREFOX URLS ignoreChromeError */// toolbox.js
'use strict';
chrome.management.getSelf(ext => {
const contextMenus = Object.assign({
(() => {
const contextMenus = {
'show-badge': {
title: 'menuShowBadge',
click: togglePref,
click: info => prefs.set(info.menuItemId, info.checked),
},
'disableAll': {
title: 'disableAllStyles',
click: browserCommands.styleDisableAll,
},
'open-manager': {
title: 'optionsOpenManager',
title: 'openStylesManager',
click: browserCommands.openManage,
},
'open-options': {
title: 'openOptions',
click: browserCommands.openOptions,
},
}, ext.installType === 'development' && {
'reload': {
presentIf: async () => (await browser.management.getSelf()).installType === 'development',
title: 'reload',
click: browserCommands.reload,
},
}, CHROME && {
'editor.contextDelete': {
presentIf: () => !FIREFOX && prefs.get('editor.contextDelete'),
title: 'editDeleteText',
type: 'normal',
contexts: ['editable'],
documentUrlPatterns: [URLS.ownOrigin + '*'],
documentUrlPatterns: [URLS.ownOrigin + 'edit*'],
click: (info, tab) => {
msg.sendTab(tab.id, {method: 'editDeleteText'}, undefined, 'extension')
.catch(msg.ignoreError);
},
},
});
};
// "Delete" item in context menu for browsers that don't have it
if (CHROME &&
// looking at the end of UA string
/(Vivaldi|Safari)\/[\d.]+$/.test(navigator.userAgent) &&
// skip forks with Flash as those are likely to have the menu e.g. CentBrowser
!Array.from(navigator.plugins).some(p => p.name === 'Shockwave Flash')) {
prefs.__defaults['editor.contextDelete'] = true;
}
const keys = Object.keys(contextMenus);
prefs.subscribe(keys.filter(id => typeof prefs.defaults[id] === 'boolean'),
CHROME >= 62 && CHROME <= 64 ? toggleCheckmarkBugged : toggleCheckmark);
prefs.subscribe(keys.filter(id => contextMenus[id].presentIf && prefs.knownKeys.includes(id)),
togglePresence);
createContextMenus(keys);
createContextMenus(Object.keys(contextMenus));
chrome.contextMenus.onClicked.addListener((info, tab) =>
contextMenus[info.menuItemId].click(info, tab));
function createContextMenus(ids) {
async function createContextMenus(ids) {
for (const id of ids) {
const item = Object.assign({id, contexts: ['browser_action']}, contextMenus[id]);
let item = contextMenus[id];
if (item.presentIf && !await item.presentIf()) {
continue;
}
item = Object.assign({id}, item);
delete item.presentIf;
item.title = chrome.i18n.getMessage(item.title);
if (typeof prefs.defaults[id] === 'boolean') {
if (item.type) {
prefs.subscribe(id, togglePresence);
} else {
if (!item.type && typeof prefs.defaults[id] === 'boolean') {
item.type = 'checkbox';
item.checked = prefs.get(id);
prefs.subscribe(id, CHROME >= 62 && CHROME <= 64 ? toggleCheckmarkBugged : toggleCheckmark);
}
if (!item.contexts) {
item.contexts = ['browser_action'];
}
delete item.click;
chrome.contextMenus.create(item, ignoreChromeError);
@ -72,11 +91,6 @@ chrome.management.getSelf(ext => {
createContextMenus([id]);
}
/** @param {chrome.contextMenus.OnClickData} info */
function togglePref(info) {
prefs.set(info.menuItemId, info.checked);
}
function togglePresence(id, checked) {
if (checked) {
createContextMenus([id]);
@ -84,4 +98,4 @@ chrome.management.getSelf(ext => {
chrome.contextMenus.remove(id, ignoreChromeError);
}
}
});
})();

View File

@ -2,17 +2,17 @@
'use strict';
/* exported createChromeStorageDB */
function createChromeStorageDB(PREFIX) {
function createChromeStorageDB() {
let INC;
const isMain = !PREFIX;
if (!PREFIX) PREFIX = 'style-';
return {
const PREFIX = 'style-';
const METHODS = {
delete(id) {
return chromeLocal.remove(PREFIX + id);
},
// FIXME: we don't use this method at all. Should we remove this?
get(id) {
return chromeLocal.getValue(PREFIX + id);
},
@ -21,9 +21,7 @@ function createChromeStorageDB(PREFIX) {
const all = await chromeLocal.get();
if (!INC) prepareInc(all);
return Object.entries(all)
.map(([key, val]) => key.startsWith(PREFIX) &&
(!isMain || Number(key.slice(PREFIX.length))) &&
val)
.map(([key, val]) => key.startsWith(PREFIX) && Number(key.slice(PREFIX.length)) && val)
.filter(Boolean);
},
@ -61,4 +59,8 @@ function createChromeStorageDB(PREFIX) {
}
}
}
return function dbExecChromeStorage(method, ...args) {
return METHODS[method](...args);
};
}

View File

@ -1,8 +1,5 @@
/* global addAPI */// common.js
/* global chromeLocal */// storage-util.js
/* global cloneError */// worker-util.js
/* global deepCopy */// toolbox.js
/* global prefs */
'use strict';
/*
@ -14,49 +11,16 @@
/* exported db */
const db = (() => {
let exec = async (...args) => (
exec = await tryUsingIndexedDB().catch(useChromeStorage)
)(...args);
const DB = 'stylish';
const DATABASE = 'stylish';
const STORE = 'styles';
const FALLBACK = 'dbInChromeStorage';
const ID_AS_KEY = {[DB]: true};
const getStoreName = dbName => dbName === DB ? 'styles' : 'data';
const cache = {};
const proxies = {};
const proxyHandler = {
get: ({dbName}, cmd) =>
(...args) =>
(dbName === DB ? exec : cachedExec)(dbName, cmd, ...args),
const dbApi = {
async exec(...args) {
dbApi.exec = await tryUsingIndexedDB().catch(useChromeStorage);
return dbApi.exec(...args);
},
};
/**
* @param {string} dbName
* @return {IDBObjectStore | {putMany: function(items:?[]):Promise<?[]>}}
*/
const getProxy = dbName => proxies[dbName] || (
(proxies[dbName] = new Proxy({dbName}, proxyHandler))
);
addAPI(/** @namespace API */ {
drafts: getProxy('drafts'),
/** Storage for big items that may exceed 8kB limit of chrome.storage.sync.
* To make an item syncable register it with uuidIndex.addCustomId. */
prefsDb: getProxy(prefs.STORAGE_KEY),
});
return {
styles: getProxy(DB),
};
async function cachedExec(dbName, cmd, a, b) {
const hub = cache[dbName] || (cache[dbName] = {});
const res = cmd === 'get' && a in hub ? hub[a] : await exec(...arguments);
if (cmd === 'get') {
hub[a] = deepCopy(res);
} else if (cmd === 'put') {
hub[ID_AS_KEY[dbName] ? a.id : b] = deepCopy(a);
} else if (cmd === 'delete') {
delete hub[a];
}
return res;
}
return dbApi;
async function tryUsingIndexedDB() {
// we use chrome.storage.local fallback if IndexedDB doesn't save data,
@ -76,9 +40,9 @@ const db = (() => {
async function testDB() {
const id = `${performance.now()}.${Math.random()}.${Date.now()}`;
await dbExecIndexedDB(DB, 'put', {id});
const e = await dbExecIndexedDB(DB, 'get', id);
await dbExecIndexedDB(DB, 'delete', e.id); // throws if `e` or id is null
await dbExecIndexedDB('put', {id});
const e = await dbExecIndexedDB('get', id);
await dbExecIndexedDB('delete', e.id); // throws if `e` or id is null
}
async function useChromeStorage(err) {
@ -88,18 +52,12 @@ const db = (() => {
console.warn('Failed to access indexedDB. Switched to storage API.', err);
}
await require(['/background/db-chrome-storage']); /* global createChromeStorageDB */
const BASES = {};
return (dbName, method, ...args) => (
BASES[dbName] || (
BASES[dbName] = createChromeStorageDB(dbName !== DB && `${dbName}-`)
)
)[method](...args);
return createChromeStorageDB();
}
async function dbExecIndexedDB(dbName, method, ...args) {
async function dbExecIndexedDB(method, ...args) {
const mode = method.startsWith('get') ? 'readonly' : 'readwrite';
const storeName = getStoreName(dbName);
const store = (await open(dbName)).transaction([storeName], mode).objectStore(storeName);
const store = (await open()).transaction([STORE], mode).objectStore(STORE);
const fn = method === 'putMany' ? putMany : storeRequest;
return fn(store, method, ...args);
}
@ -117,34 +75,21 @@ const db = (() => {
return Promise.all(items.map(item => storeRequest(store, 'put', item)));
}
function open(name) {
function open() {
return new Promise((resolve, reject) => {
const request = indexedDB.open(name, 2);
request.onsuccess = e => resolve(create(e));
const request = indexedDB.open(DATABASE, 2);
request.onsuccess = () => resolve(request.result);
request.onerror = reject;
request.onupgradeneeded = create;
});
}
function create(event) {
/** @type IDBDatabase */
const idb = event.target.result;
const dbName = idb.name;
const sn = getStoreName(dbName);
if (!idb.objectStoreNames.contains(sn)) {
if (event.type === 'success') {
idb.close();
return new Promise(resolve => {
indexedDB.deleteDatabase(dbName).onsuccess = () => {
resolve(open(dbName));
};
});
}
idb.createObjectStore(sn, ID_AS_KEY[dbName] ? {
if (event.oldVersion === 0) {
event.target.result.createObjectStore(STORE, {
keyPath: 'id',
autoIncrement: true,
} : undefined);
});
}
return idb;
}
})();

View File

@ -1,20 +1,19 @@
/* global API */// msg.js
/* global addAPI bgReady */// common.js
/* global colorScheme */
/* global prefs */
/* global tabMan */
/* global CHROME FIREFOX UA debounce ignoreChromeError */// toolbox.js
/* global CHROME FIREFOX VIVALDI debounce ignoreChromeError */// toolbox.js
'use strict';
/* exported iconMan */
const iconMan = (() => {
const ICON_SIZES = FIREFOX || CHROME && !UA.vivaldi ? [16, 32] : [19, 38];
const ICON_SIZES = FIREFOX || CHROME >= 55 && !VIVALDI ? [16, 32] : [19, 38];
const staleBadges = new Set();
const imageDataCache = new Map();
const badgeOvr = {color: '', text: ''};
// https://github.com/openstyles/stylus/issues/1287 Fenix can't use custom ImageData
const FIREFOX_ANDROID = FIREFOX && UA.mobile;
let isDark;
const FIREFOX_ANDROID = FIREFOX && navigator.userAgent.includes('Android');
// https://github.com/openstyles/stylus/issues/335
let hasCanvas = FIREFOX_ANDROID ? false : loadImage(`/images/icon/${ICON_SIZES[0]}.png`)
.then(({data}) => (hasCanvas = data.some(b => b !== 255)));
@ -38,17 +37,13 @@ const iconMan = (() => {
chrome.webNavigation.onCommitted.addListener(({tabId, frameId}) => {
if (!frameId) tabMan.set(tabId, 'styleIds', undefined);
});
chrome.runtime.onConnect.addListener(port => {
if (port.name === 'iframe') {
port.onDisconnect.addListener(onPortDisconnected);
}
});
colorScheme.onChange(val => {
isDark = val;
if (prefs.get('iconset') === -1) {
debounce(refreshAllIcons);
}
});
bgReady.all.then(() => {
prefs.subscribe([
'disableAll',
@ -100,10 +95,9 @@ const iconMan = (() => {
}
function getIconName(hasStyles = false) {
const i = prefs.get('iconset');
const prefix = i === 0 || i === -1 && isDark ? '' : 'light/';
const iconset = prefs.get('iconset') === 1 ? 'light/' : '';
const postfix = prefs.get('disableAll') ? 'x' : !hasStyles ? 'w' : '';
return `${prefix}$SIZE$${postfix}`;
return `${iconset}$SIZE$${postfix}`;
}
function refreshIcon(tabId, force = false) {

View File

@ -0,0 +1,15 @@
/* global chromeLocal */// storage-util.js
'use strict';
// Removing unused stuff from storage on extension update
// TODO: delete this by the middle of 2021
try {
localStorage.clear();
} catch (e) {}
setTimeout(async () => {
const del = Object.keys(await chromeLocal.get())
.filter(key => key.startsWith('usoSearchCache'));
if (del.length) chromeLocal.remove(del);
}, 15e3);

View File

@ -1,7 +1,7 @@
/* global API msg */// msg.js
/* global CHROME URLS deepEqual isEmptyObj mapObj stringAsRegExp tryRegExp tryURL */// toolbox.js
/* global bgReady createCache uuidIndex */// common.js
/* global calcStyleDigest styleCodeEmpty */// sections-util.js
/* global CHROME URLS isEmptyObj stringAsRegExp tryRegExp tryURL */// toolbox.js
/* global bgReady compareRevision */// common.js
/* global calcStyleDigest styleCodeEmpty styleSectionGlobal */// sections-util.js
/* global db */
/* global prefs */
/* global tabMan */
@ -18,26 +18,18 @@ The live preview feature relies on `runtime.connect` and `port.onDisconnect`
to cleanup the temporary code. See livePreview in /edit.
*/
const styleUtil = {};
/* exported styleMan */
const styleMan = (() => {
Object.assign(styleUtil, {
id2style,
handleSave,
uuid2style,
});
//#region Declarations
/** @typedef {{
style: StyleObj,
preview?: StyleObj,
appliesTo: Set<string>,
style: StyleObj
preview?: StyleObj
appliesTo: Set<string>
}} StyleMapData */
/** @type {Map<number,StyleMapData>} */
const dataMap = new Map();
const uuidIndex = new Map();
/** @typedef {Object<styleId,{id: number, code: string[]}>} StyleSectionsToApply */
/** @type {Map<string,{maybeMatch: Set<styleId>, sections: StyleSectionsToApply}>} */
const cachedStyleForUrl = createCache({
@ -63,55 +55,18 @@ const styleMan = (() => {
name: style => `ID: ${style.id}`,
_id: () => uuidv4(),
_rev: () => Date.now(),
_usw: () => ({}),
};
const DELETE_IF_NULL = ['id', 'customName', 'md5Url', 'originalMd5'];
const INJ_ORDER = 'injectionOrder';
const order = {main: {}, prio: {}};
const orderWrap = {
id: INJ_ORDER,
value: mapObj(order, () => []),
_id: `${chrome.runtime.id}-${INJ_ORDER}`,
_rev: 0,
};
uuidIndex.addCustomId(orderWrap, {set: setOrder});
class MatchQuery {
constructor(url) {
this.url = url;
}
get urlWithoutHash() {
return this._set('urlWithoutHash', this.url.split('#', 1)[0]);
}
get urlWithoutParams() {
return this._set('urlWithoutParams', this.url.split(/[?#]/, 1)[0]);
}
get domain() {
return this._set('domain', tryURL(this.url).hostname);
}
get isOwnPage() {
return this._set('isOwnPage', this.url.startsWith(URLS.ownOrigin));
}
_set(name, value) {
Object.defineProperty(this, name, {value});
return value;
}
}
/** @type {Promise|boolean} will be `true` to avoid wasting a microtask tick on each `await` */
let ready = Promise.all([init(), prefs.ready]);
let ready = init();
chrome.runtime.onConnect.addListener(port => {
if (port.name === 'livePreview') {
handleLivePreview(port);
} else if (port.name.startsWith('draft:')) {
handleDraft(port);
}
});
colorScheme.onChange(value => {
msg.broadcastExtension({method: 'colorScheme', value});
for (const {style} of dataMap.values()) {
if (colorScheme.SCHEMES.includes(style.preferScheme)) {
broadcastStyleUpdated(style, 'colorScheme');
chrome.runtime.onConnect.addListener(handleLivePreview);
// function handleColorScheme() {
colorScheme.onChange(() => {
for (const {style: data} of dataMap.values()) {
if (data.preferScheme === 'dark' || data.preferScheme === 'light') {
broadcastStyleUpdated(data, 'colorScheme', undefined, false);
}
}
});
@ -124,28 +79,22 @@ const styleMan = (() => {
/** @returns {Promise<number>} style id */
async delete(id, reason) {
if (ready.then) await ready;
const {style, appliesTo} = dataMap.get(id);
const sync = reason !== 'sync';
const uuid = style._id;
db.styles.delete(id);
if (sync) API.sync.delete(uuid, Date.now());
const data = id2data(id);
const {style, appliesTo} = data;
await db.exec('delete', id);
if (reason !== 'sync') {
API.sync.delete(style._id, Date.now());
}
for (const url of appliesTo) {
const cache = cachedStyleForUrl.get(url);
if (cache) delete cache.sections[id];
}
dataMap.delete(id);
uuidIndex.delete(uuid);
mapObj(orderWrap.value, (group, type) => {
delete order[type][id];
const i = group.indexOf(uuid);
if (i >= 0) group.splice(i, 1);
});
setOrder(orderWrap, {calc: false});
uuidIndex.delete(style._id);
if (style._usw && style._usw.token) {
// Must be called after the style is deleted from dataMap
API.usw.revoke(id);
}
API.drafts.delete(id);
await msg.broadcast({
method: 'styleDeleted',
style: {id},
@ -153,6 +102,17 @@ const styleMan = (() => {
return id;
},
/** @returns {Promise<number>} style id */
async deleteByUUID(_id, rev) {
if (ready.then) await ready;
const id = uuidIndex.get(_id);
const oldDoc = id && id2style(id);
if (oldDoc && compareRevision(oldDoc._rev, rev) <= 0) {
// FIXME: does it make sense to set reason to 'sync' in deleteByUUID?
return styleMan.delete(id, 'sync');
}
},
/** @returns {Promise<StyleObj>} */
async editSave(style) {
if (ready.then) await ready;
@ -162,67 +122,35 @@ const styleMan = (() => {
},
/** @returns {Promise<?StyleObj>} */
async find(...filters) {
async find(filter) {
if (ready.then) await ready;
for (const filter of filters) {
const filterEntries = Object.entries(filter);
for (const {style} of dataMap.values()) {
if (filterEntries.every(([key, val]) => style[key] === val)) {
return style;
}
}
}
return null;
},
/** @returns {Promise<StyleObj[]>} */
async getAll() {
if (ready.then) await ready;
return getAllAsArray();
return Array.from(dataMap.values(), data2style);
},
/** @returns {Promise<Object<string,StyleObj[]>>}>} */
async getAllOrdered(keys) {
/** @returns {Promise<StyleObj>} */
async getByUUID(uuid) {
if (ready.then) await ready;
const res = mapObj(orderWrap.value, group => group.map(uuid2style).filter(Boolean));
if (res.main.length + res.prio.length < dataMap.size) {
for (const {style} of dataMap.values()) {
if (!(style.id in order.main) && !(style.id in order.prio)) {
res.main.push(style);
}
}
}
return keys
? mapObj(res, group => group.map(style => mapObj(style, null, keys)))
: res;
},
getOrder: () => orderWrap.value,
/** @returns {Promise<string | {[remoteId:string]: styleId}>}>} */
async getRemoteInfo(id) {
if (ready.then) await ready;
if (id) return calcRemoteId(id2style(id));
const res = {};
for (const {style} of dataMap.values()) {
const [rid, vars] = calcRemoteId(style);
if (rid) res[rid] = [style.id, vars];
}
return res;
return id2style(uuidIndex.get(uuid));
},
/** @returns {Promise<StyleSectionsToApply>} */
async getSectionsByUrl(url, id, isInitialApply) {
if (ready.then) await ready;
if (isInitialApply && prefs.get('disableAll')) {
return {
cfg: {
disableAll: true,
},
};
return {disableAll: true};
}
// TODO: enable in FF when it supports sourceURL comment in style elements (also options.html)
const {exposeStyleName} = CHROME && prefs.__values;
const sender = CHROME && this && this.sender || {};
if (sender.frameId === 0) {
/* Chrome hides text frament from location.href of the page e.g. #:~:text=foo
@ -241,9 +169,9 @@ const styleMan = (() => {
} else if (cache.maybeMatch.size) {
buildCache(cache, url, Array.from(cache.maybeMatch, id2data).filter(Boolean));
}
return Object.assign({cfg: {exposeStyleName, order}},
id ? mapObj(cache.sections, null, [id])
: cache.sections);
return id
? cache.sections[id] ? {[id]: cache.sections[id]} : {}
: cache.sections;
},
/** @returns {Promise<StyleObj>} */
@ -260,8 +188,8 @@ const styleMan = (() => {
const result = [];
const styles = id
? [id2style(id)].filter(Boolean)
: getAllAsArray();
const query = new MatchQuery(url);
: Array.from(dataMap.values(), data2style);
const query = createMatchQuery(url);
for (const style of styles) {
let excluded = false;
let excludedScheme = false;
@ -283,7 +211,10 @@ const styleMan = (() => {
excludedScheme = true;
}
for (const section of style.sections) {
const match = urlMatchSection(query, section, true);
if (styleSectionGlobal(section) && styleCodeEmpty(section.code)) {
continue;
}
const match = urlMatchSection(query, section);
if (match) {
if (match === 'sloppy') {
sloppy = true;
@ -309,10 +240,11 @@ const styleMan = (() => {
await usercssMan.buildCode(style);
}
}
const events = await db.styles.putMany(items);
return Promise.all(items.map((item, i) =>
handleSave(item, {reason: 'import'}, events[i])
));
const events = await db.exec('putMany', items);
return Promise.all(items.map((item, i) => {
afterSave(item, events[i]);
return handleSave(item, {reason: 'import'});
}));
},
/** @returns {Promise<StyleObj>} */
@ -325,13 +257,33 @@ const styleMan = (() => {
return saveStyle(style, {reason});
},
save: saveStyle,
async setOrder(value) {
/** @returns {Promise<?StyleObj>} */
async putByUUID(doc) {
if (ready.then) await ready;
return setOrder({value}, {broadcast: true, sync: true});
const id = uuidIndex.get(doc._id);
if (id) {
doc.id = id;
} else {
delete doc.id;
}
const oldDoc = id && id2style(id);
let diff = -1;
if (oldDoc) {
diff = compareRevision(oldDoc._rev, doc._rev);
if (diff > 0) {
API.sync.put(oldDoc._id, oldDoc._rev);
return;
}
}
if (diff < 0) {
doc.id = await db.exec('put', doc);
uuidIndex.set(doc._id, doc.id);
return handleSave(doc, {reason: 'sync'});
}
},
save: saveStyle,
/** @returns {Promise<number>} style id */
async toggle(id, enabled) {
if (ready.then) await ready;
@ -354,8 +306,7 @@ const styleMan = (() => {
async config(id, prop, value) {
if (ready.then) await ready;
const style = Object.assign({}, id2style(id));
const {preview = {}} = dataMap.get(id);
style[prop] = preview[prop] = value;
style[prop] = value;
return saveStyle(style, {reason: 'config'});
},
};
@ -370,23 +321,12 @@ const styleMan = (() => {
/** @returns {?StyleObj} */
function id2style(id) {
return (dataMap.get(Number(id)) || {}).style;
return (dataMap.get(id) || {}).style;
}
/** @returns {?StyleObj} */
function uuid2style(uuid) {
return id2style(uuidIndex.get(uuid));
}
function calcRemoteId({md5Url, updateUrl, usercssData: ucd} = {}) {
let id;
id = (id = /\d+/.test(md5Url) || URLS.extractUsoArchiveId(updateUrl)) && `uso-${id}`
|| (id = URLS.extractUSwId(updateUrl)) && `usw-${id}`
|| '';
return id && [
id,
ucd && !isEmptyObj(ucd.vars),
];
function data2style(data) {
return data && data.style;
}
/** @returns {StyleObj} */
@ -407,7 +347,6 @@ const styleMan = (() => {
style,
appliesTo: new Set(),
});
uuidIndex.set(style._id, style.id);
}
/** @returns {StyleObj} */
@ -417,12 +356,10 @@ const styleMan = (() => {
style);
}
function handleDraft(port) {
const id = port.name.split(':').pop();
port.onDisconnect.addListener(() => API.drafts.delete(Number(id) || id));
}
function handleLivePreview(port) {
if (port.name !== 'livePreview') {
return;
}
let id;
port.onMessage.addListener(style => {
if (!id) id = style.id;
@ -474,10 +411,10 @@ const styleMan = (() => {
cache.maybeMatch.add(id);
continue;
}
const code = getAppliedCode(new MatchQuery(url), style);
const code = getAppliedCode(createMatchQuery(url), style);
if (code) {
updated.add(url);
buildCacheEntry(cache, style, code);
cache.sections[id] = {id, code};
} else {
excluded.add(url);
delete cache.sections[id];
@ -511,24 +448,29 @@ const styleMan = (() => {
fixKnownProblems(style);
}
async function saveStyle(style, handlingOptions) {
beforeSave(style);
const newId = await db.styles.put(style);
return handleSave(style, handlingOptions, newId);
function afterSave(style, newId) {
if (style.id == null) {
style.id = newId;
}
uuidIndex.set(style._id, style.id);
API.sync.put(style._id, style._rev);
}
function handleSave(style, {reason, broadcast = true}, id = style.id) {
if (style.id == null) style.id = id;
const data = id2data(id);
async function saveStyle(style, handlingOptions) {
beforeSave(style);
const newId = await db.exec('put', style);
afterSave(style, newId);
return handleSave(style, handlingOptions);
}
function handleSave(style, {reason, broadcast = true}) {
const data = id2data(style.id);
const method = data ? 'styleUpdated' : 'styleAdded';
if (!data) {
storeInMap(style);
} else {
data.style = style;
}
if (reason !== 'sync') {
API.sync.putDoc(style);
}
if (broadcast) broadcastStyleUpdated(style, reason, method);
return style;
}
@ -553,14 +495,15 @@ const styleMan = (() => {
}
async function init() {
const orderPromise = API.prefsDb.get(orderWrap.id);
const styles = await db.styles.getAll() || [];
const styles = await db.exec('getAll') || [];
const updated = await Promise.all(styles.map(fixKnownProblems).filter(Boolean));
if (updated.length) {
await db.styles.putMany(updated);
await db.exec('putMany', updated);
}
for (const style of styles) {
storeInMap(style);
uuidIndex.set(style._id, style.id);
}
setOrder(await orderPromise, {store: false});
styles.forEach(storeInMap);
ready = true;
bgReady._resolveStyles();
}
@ -644,56 +587,40 @@ const styleMan = (() => {
return true;
}
function urlMatchSection(query, section, skipEmptyGlobal) {
let dd, ddL, pp, ppL, rr, rrL, uu, uuL;
function urlMatchSection(query, section) {
if (
(dd = section.domains) && (ddL = dd.length) && dd.some(urlMatchDomain, query) ||
(pp = section.urlPrefixes) && (ppL = pp.length) && pp.some(urlMatchPrefix, query) ||
/* Per the specification the fragment portion is ignored in @-moz-document:
https://www.w3.org/TR/2012/WD-css3-conditional-20120911/#url-of-doc
but the spec is outdated and doesn't account for SPA sites,
so we only respect it for `url()` function */
(uu = section.urls) && (uuL = uu.length) && (
uu.includes(query.url) ||
uu.includes(query.urlWithoutHash)
) ||
(rr = section.regexps) && (rrL = rr.length) && rr.some(urlMatchRegexp, query)
section.domains &&
section.domains.some(d => d === query.domain || query.domain.endsWith(`.${d}`))
) {
return true;
}
if (section.urlPrefixes && section.urlPrefixes.some(p => p && query.url.startsWith(p))) {
return true;
}
// as per spec the fragment portion is ignored in @-moz-document:
// https://www.w3.org/TR/2012/WD-css3-conditional-20120911/#url-of-doc
// but the spec is outdated and doesn't account for SPA sites
// so we only respect it for `url()` function
if (section.urls && (
section.urls.includes(query.url) ||
section.urls.includes(query.urlWithoutHash)
)) {
return true;
}
if (section.regexps && section.regexps.some(r => compileRe(r).test(query.url))) {
return true;
}
/*
According to CSS4 @document specification the entire URL must match.
Stylish-for-Chrome implemented it incorrectly since the very beginning.
We'll detect styles that abuse the bug by finding the sections that
would have been applied by Stylish but not by us as we follow the spec.
*/
if (rrL && rr.some(urlMatchRegexpSloppy, query)) {
if (section.regexps && section.regexps.some(r => compileSloppyRe(r).test(query.url))) {
return 'sloppy';
}
// TODO: check for invalid regexps?
return !rrL && !ppL && !uuL && !ddL &&
!query.isOwnPage && // We allow only intentionally targeted sections for own pages
(!skipEmptyGlobal || !styleCodeEmpty(section.code));
}
/** @this {MatchQuery} */
function urlMatchDomain(d) {
const _d = this.domain;
return d === _d ||
_d[_d.length - d.length - 1] === '.' && _d.endsWith(d);
}
/** @this {MatchQuery} */
function urlMatchPrefix(p) {
return p && this.url.startsWith(p);
}
/** @this {MatchQuery} */
function urlMatchRegexp(r) {
return (!this.isOwnPage || /\bextension\b/.test(r)) &&
compileRe(r).test(this.url);
}
/** @this {MatchQuery} */
function urlMatchRegexpSloppy(r) {
return (!this.isOwnPage || /\bextension\b/.test(r)) &&
compileSloppyRe(r).test(this.url);
return styleSectionGlobal(section);
}
function createCompiler(compile) {
@ -733,28 +660,45 @@ const styleMan = (() => {
'$';
}
function buildCache(cache, url, styleList) {
const query = new MatchQuery(url);
for (const {style, appliesTo, preview} of styleList) {
const code = getAppliedCode(query, preview || style);
if (code) {
buildCacheEntry(cache, style, code);
appliesTo.add(url);
function createMatchQuery(url) {
let urlWithoutHash;
let urlWithoutParams;
let domain;
return {
url,
get urlWithoutHash() {
if (!urlWithoutHash) {
urlWithoutHash = url.split('#')[0];
}
return urlWithoutHash;
},
get urlWithoutParams() {
if (!urlWithoutParams) {
const u = tryURL(url);
urlWithoutParams = u.origin + u.pathname;
}
return urlWithoutParams;
},
get domain() {
if (!domain) {
const u = tryURL(url);
domain = u.hostname;
}
function buildCacheEntry(cache, style, code = style.code) {
cache.sections[style.id] = {
code,
id: style.id,
name: style.customName || style.name,
return domain;
},
};
}
/** @returns {StyleObj[]} */
function getAllAsArray() {
return Array.from(dataMap.values(), v => v.style);
function buildCache(cache, url, styleList) {
const query = createMatchQuery(url);
for (const {style, appliesTo, preview} of styleList) {
const code = getAppliedCode(query, preview || style);
if (code) {
const id = style.id;
cache.sections[id] = {id, code};
appliesTo.add(url);
}
}
}
/** uuidv4 helper: converts to a 4-digit hex string and adds "-" at required positions */
@ -762,30 +706,66 @@ const styleMan = (() => {
return (num + 0x10000).toString(16).slice(-4) + (i >= 1 && i <= 4 ? '-' : '');
}
async function setOrder(data, {broadcast, calc = true, store = true, sync} = {}) {
if (!data || !data.value || deepEqual(data.value, orderWrap.value)) {
return;
}
Object.assign(orderWrap, data, sync && {_rev: Date.now()});
if (calc) {
for (const [type, group] of Object.entries(data.value)) {
const dst = order[type] = {};
group.forEach((uuid, i) => {
const id = uuidIndex.get(uuid);
if (id) dst[id] = i;
});
}
}
if (broadcast) {
msg.broadcast({method: 'styleSort', order});
}
if (store) {
await API.prefsDb.put(orderWrap, orderWrap.id);
}
if (sync) {
API.sync.putDoc(orderWrap);
}
}
//#endregion
})();
/** Creates a FIFO limit-size map. */
function createCache({size = 1000, onDeleted} = {}) {
const map = new Map();
const buffer = Array(size);
let index = 0;
let lastIndex = 0;
return {
get(id) {
const item = map.get(id);
return item && item.data;
},
set(id, data) {
if (map.size === size) {
// full
map.delete(buffer[lastIndex].id);
if (onDeleted) {
onDeleted(buffer[lastIndex].id, buffer[lastIndex].data);
}
lastIndex = (lastIndex + 1) % size;
}
const item = {id, data, index};
map.set(id, item);
buffer[index] = item;
index = (index + 1) % size;
},
delete(id) {
const item = map.get(id);
if (!item) {
return false;
}
map.delete(item.id);
const lastItem = buffer[lastIndex];
lastItem.index = item.index;
buffer[item.index] = lastItem;
lastIndex = (lastIndex + 1) % size;
if (onDeleted) {
onDeleted(item.id, item.data);
}
return true;
},
clear() {
map.clear();
index = lastIndex = 0;
},
has: id => map.has(id),
*entries() {
for (const [id, item] of map) {
yield [id, item.data];
}
},
*values() {
for (const item of map.values()) {
yield item.data;
}
},
get size() {
return map.size;
},
};
}

View File

@ -56,7 +56,6 @@
return NOP;
}
return API.styles.getSectionsByUrl(url, id).then(sections => {
delete sections.cfg;
const tasks = [];
for (const section of Object.values(sections)) {
const styleId = section.id;

View File

@ -1,5 +1,5 @@
/* global API */// msg.js
/* global CHROME URLS ignoreChromeError */// toolbox.js
/* global CHROME ignoreChromeError */// toolbox.js
/* global prefs */
'use strict';
@ -49,12 +49,6 @@
if (CHROME && !off) {
chrome.webNavigation.onCommitted.addListener(injectData, {url: [{urlPrefix: 'http'}]});
}
if (CHROME) {
chrome.webRequest.onBeforeRequest.addListener(openNamedStyle, {
urls: [URLS.ownOrigin + '*.user.css'],
types: ['main_frame'],
}, ['blocking']);
}
state.csp = csp;
state.off = off;
state.xhr = xhr;
@ -152,14 +146,6 @@
}
}
/** @param {chrome.webRequest.WebRequestBodyDetails} req */
function openNamedStyle(req) {
if (!req.url.includes('?')) { // skipping our usercss installer
chrome.tabs.update(req.tabId, {url: 'edit.html?id=' + req.url.split('#')[1]});
return {cancel: true};
}
}
function req2key(req) {
return req.tabId + ':' + req.frameId;
}

View File

@ -1,10 +1,8 @@
/* global API msg */// msg.js
/* global bgReady uuidIndex */// common.js
/* global chromeLocal chromeSync */// storage-util.js
/* global db */
/* global chromeLocal */// storage-util.js
/* global compareRevision */// common.js
/* global iconMan */
/* global prefs */
/* global styleUtil */
/* global tokenMan */
'use strict';
@ -20,7 +18,6 @@ const syncMan = (() => {
disconnecting: 'disconnecting',
});
const STORAGE_KEY = 'sync/state/';
const NO_LOGIN = ['webdav'];
const status = /** @namespace SyncManager.Status */ {
STATES,
state: STATES.disconnected,
@ -30,12 +27,11 @@ const syncMan = (() => {
errorMessage: null,
login: false,
};
const compareRevision = (rev1, rev2) => rev1 - rev2;
let lastError = null;
let ctrl;
let currentDrive;
/** @type {Promise|boolean} will be `true` to avoid wasting a microtask tick on each `await` */
let ready = bgReady.styles.then(() => {
let ready = prefs.ready.then(() => {
ready = true;
prefs.subscribe('sync.enabled',
(_, val) => val === 'none'
@ -82,21 +78,11 @@ const syncMan = (() => {
}
},
async putDoc({_id, _rev}) {
async put(...args) {
if (ready.then) await ready;
if (!currentDrive) return;
schedule();
return ctrl.put(_id, _rev);
},
async setDriveOptions(driveName, options) {
const key = `secure/sync/driveOptions/${driveName}`;
await chromeSync.setValue(key, options);
},
async getDriveOptions(driveName) {
const key = `secure/sync/driveOptions/${driveName}`;
return await chromeSync.getValue(key) || {};
return ctrl.put(...args);
},
async start(name, fromPref = false) {
@ -104,14 +90,14 @@ const syncMan = (() => {
if (!ctrl) await initController();
if (currentDrive) return;
currentDrive = await getDrive(name);
currentDrive = getDrive(name);
ctrl.use(currentDrive);
status.state = STATES.connecting;
status.currentDriveName = currentDrive.name;
emitStatusChange();
if (fromPref || NO_LOGIN.includes(currentDrive.name)) {
if (fromPref) {
status.login = true;
} else {
try {
@ -179,35 +165,19 @@ const syncMan = (() => {
//#region Utils
async function initController() {
await require(['/vendor/db-to-cloud/db-to-cloud']); /* global dbToCloud */
await require(['/vendor/db-to-cloud/db-to-cloud.min']); /* global dbToCloud */
ctrl = dbToCloud.dbToCloud({
onGet: styleUtil.uuid2style,
async onPut(doc) {
const id = uuidIndex.get(doc._id);
const oldCust = uuidIndex.custom[id];
const oldDoc = oldCust || styleUtil.id2style(id);
const diff = oldDoc ? compareRevision(oldDoc._rev, doc._rev) : -1;
if (!diff) return;
if (diff > 0) {
syncMan.putDoc(oldDoc);
} else if (oldCust) {
uuidIndex.custom[id] = doc;
} else {
delete doc.id;
if (id) doc.id = id;
doc.id = await db.styles.put(doc);
await styleUtil.handleSave(doc, {reason: 'sync'});
}
onGet(id) {
return API.styles.getByUUID(id);
},
onDelete(_id, rev) {
const id = uuidIndex.get(_id);
const oldDoc = styleUtil.id2style(id);
return oldDoc &&
compareRevision(oldDoc._rev, rev) <= 0 &&
API.styles.delete(id, 'sync');
onPut(doc) {
return API.styles.putByUUID(doc);
},
onDelete(id, rev) {
return API.styles.deleteByUUID(id, rev);
},
async onFirstSync() {
for (const i of Object.values(uuidIndex.custom).concat(await API.styles.getAll())) {
for (const i of await API.styles.getAll()) {
ctrl.put(i._id, i._rev);
}
},
@ -270,25 +240,15 @@ const syncMan = (() => {
}
}
async function getDrive(name) {
if (name === 'dropbox' || name === 'google' || name === 'onedrive' || name === 'webdav') {
const options = await syncMan.getDriveOptions(name);
options.getAccessToken = () => tokenMan.getToken(name);
options.fetch = name === 'webdav' ? fetchWebDAV.bind(options) : fetch;
return dbToCloud.drive[name](options);
function getDrive(name) {
if (name === 'dropbox' || name === 'google' || name === 'onedrive') {
return dbToCloud.drive[name]({
getAccessToken: () => tokenMan.getToken(name),
});
}
throw new Error(`unknown cloud name: ${name}`);
}
/** @this {Object} DriveOptions */
function fetchWebDAV(url, init = {}) {
init.credentials = 'omit'; // circumventing nextcloud CSRF token error
init.headers = Object.assign({}, init.headers, {
Authorization: `Basic ${btoa(`${this.username || ''}:${this.password || ''}`)}`,
});
return fetch(url, init);
}
function schedule(delay = SYNC_DELAY) {
chrome.alarms.create('syncNow', {
delayInMinutes: delay, // fractional values are supported

View File

@ -44,6 +44,9 @@ const tokenMan = (() => {
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: FIREFOX ?
'https://clngdbkpkpeebahjckkjfobafhncgmne.chromiumapp.org/' :
'https://' + location.hostname + '.chromiumapp.org/',
scopes: ['Files.ReadWrite.AppFolder', 'offline_access'],
},
userstylesworld: {
@ -57,9 +60,8 @@ const tokenMan = (() => {
},
};
const NETWORK_LATENCY = 30; // seconds
const DEFAULT_REDIRECT_URI = 'https://clngdbkpkpeebahjckkjfobafhncgmne.chromiumapp.org/';
let alwaysUseTab = !chrome.windows || (FIREFOX ? false : null);
let alwaysUseTab = FIREFOX ? false : null;
class TokenError extends Error {
constructor(provider, message) {
@ -144,14 +146,14 @@ const tokenMan = (() => {
}
async function authUser(keys, name, interactive = false, hooks = null) {
await require(['/vendor/webext-launch-web-auth-flow/webext-launch-web-auth-flow']);
await require(['/vendor/webext-launch-web-auth-flow/webext-launch-web-auth-flow.min']);
/* global webextLaunchWebAuthFlow */
const provider = AUTH[name];
const state = Math.random().toFixed(8).slice(2);
const query = {
response_type: provider.flow,
client_id: provider.clientId,
redirect_uri: provider.redirect_uri || DEFAULT_REDIRECT_URI,
redirect_uri: provider.redirect_uri || chrome.identity.getRedirectURL(),
state,
};
if (provider.scopes) {
@ -167,13 +169,13 @@ const tokenMan = (() => {
const url = `${provider.authURL}?${new URLSearchParams(query)}`;
const width = Math.min(screen.availWidth - 100, 800);
const height = Math.min(screen.availHeight - 100, 800);
const wnd = !alwaysUseTab && await browser.windows.getLastFocused();
const wnd = await browser.windows.getLastFocused();
const finalUrl = await webextLaunchWebAuthFlow({
url,
alwaysUseTab,
interactive,
redirect_uri: query.redirect_uri,
windowOptions: wnd && Object.assign({
windowOptions: Object.assign({
state: 'normal',
width,
height,
@ -248,7 +250,7 @@ const tokenMan = (() => {
// Workaround for https://github.com/openstyles/stylus/issues/1182
// Note that modern Vivaldi isn't exposed in `navigator.userAgent` but it adds `extData` to tabs
const anyTab = await getActiveTab() || (await browser.tabs.query({}))[0];
if (anyTab && !(anyTab.extData || anyTab.vivExtData)) {
if (anyTab && !anyTab.extData) {
return false;
}
let bugged = true;

View File

@ -1,6 +1,6 @@
/* global API */// msg.js
/* global RX_META URLS debounce deepMerge download ignoreChromeError */// toolbox.js
/* global calcStyleDigest styleSectionsEqual */ // sections-util.js
/* global RX_META URLS debounce download ignoreChromeError */// toolbox.js
/* global calcStyleDigest styleJSONseemsValid styleSectionsEqual */ // sections-util.js
/* global chromeLocal */// storage-util.js
/* global compareVersion */// cmpver.js
/* global db */
@ -23,7 +23,6 @@ const updateMan = (() => {
ERROR_JSON: 'error: JSON is invalid',
ERROR_VERSION: 'error: version is older than installed style',
};
const USO_STYLES_API = `${URLS.uso}api/v1/styles/`;
const RH_ETAG = {responseHeaders: ['etag']}; // a hashsum of file contents
const RX_DATE2VER = new RegExp([
/^(\d{4})/,
@ -38,7 +37,6 @@ const updateMan = (() => {
503, // service unavailable
429, // too many requests
];
let usoReferers = 0;
let lastUpdateTime;
let checkingAll = false;
let logQueue = [];
@ -65,7 +63,7 @@ const updateMan = (() => {
checkingAll = true;
const port = observe && chrome.runtime.connect({name: 'updater'});
const styles = (await API.styles.getAll())
.filter(style => style.updateUrl && style.updatable !== false);
.filter(style => style.updateUrl);
if (port) port.postMessage({count: styles.length});
log('');
log(`${save ? 'Scheduled' : 'Manual'} update check for ${styles.length} styles`);
@ -80,17 +78,17 @@ const updateMan = (() => {
/**
* @param {{
id?: number,
style?: StyleObj,
port?: chrome.runtime.Port,
save?: boolean,
ignoreDigest?: boolean,
id?: number
style?: StyleObj
port?: chrome.runtime.Port
save?: boolean = true
ignoreDigest?: boolean
}} opts
* @returns {{
style: StyleObj,
updated?: boolean,
error?: any,
STATES: UpdaterStates,
style: StyleObj
updated?: boolean
error?: any
STATES: UpdaterStates
}}
Original style digests are calculated in these cases:
@ -115,13 +113,12 @@ const updateMan = (() => {
save,
} = opts;
if (!id) id = style.id;
const {md5Url} = style;
let {usercssData: ucd, updateUrl} = style;
const ucd = style.usercssData;
let res, state;
try {
await checkIfEdited();
res = {
style: await (ucd && !md5Url ? updateUsercss : updateUSO)().then(maybeSave),
style: await (ucd ? updateUsercss : updateUSO)().then(maybeSave),
updated: true,
};
state = STATES.UPDATED;
@ -130,7 +127,7 @@ const updateMan = (() => {
err && err.message ||
err;
res = {error, style, STATES};
state = `${STATES.SKIPPED} (${Array.isArray(err) ? err[0].message : error})`;
state = `${STATES.SKIPPED} (${error})`;
}
log(`${state} #${id} ${style.customName || style.name}`);
if (port) port.postMessage(res);
@ -145,45 +142,76 @@ const updateMan = (() => {
}
async function updateUSO() {
const md5 = await tryDownload(md5Url);
const url = URLS.makeUsoArchiveCodeUrl(style.md5Url.match(/\d+/)[0]);
const req = await tryDownload(url, RH_ETAG).catch(() => null);
if (req) {
return updateToUSOArchive(url, req);
}
const md5 = await tryDownload(style.md5Url);
if (!md5 || md5.length !== 32) {
return Promise.reject(STATES.ERROR_MD5);
}
if (md5 === style.originalMd5 && style.originalDigest && !ignoreDigest) {
return Promise.reject(STATES.SAME_MD5);
}
let varsUrl = '';
if (!ucd) {
ucd = {};
varsUrl = updateUrl;
updateUrl = style.updateUrl = `${USO_STYLES_API}${md5Url.match(/\/(\d+)/)[1]}`;
}
usoSpooferStart();
let json;
try {
json = await tryDownload(style.updateUrl, {responseType: 'json'});
json = await updateUsercss(json.css) ||
(await API.uso.toUsercss(json)).style;
if (varsUrl) await API.uso.useVarsUrl(json, varsUrl);
} finally {
usoSpooferStop();
const json = await tryDownload(style.updateUrl, {responseType: 'json'});
if (!styleJSONseemsValid(json)) {
return Promise.reject(STATES.ERROR_JSON);
}
// USO may not provide a correctly updated originalMd5 (#555)
json.originalMd5 = md5;
return json;
}
async function updateUsercss(css) {
async function updateToUSOArchive(url, req) {
const m2 = getUsoEmbeddedMeta(req.response);
if (m2) {
url = (await m2).updateUrl;
req = await tryDownload(url, RH_ETAG);
}
const json = await API.usercss.buildMeta({
id,
etag: req.headers.etag,
md5Url: null,
originalMd5: null,
sourceCode: req.response,
updateUrl: url,
url: URLS.extractUsoArchiveInstallUrl(url),
});
const varUrlValues = style.updateUrl.split('?')[1];
const varData = json.usercssData.vars;
if (varUrlValues && varData) {
const IK = 'ik-';
const IK_LEN = IK.length;
for (let [key, val] of new URLSearchParams(varUrlValues)) {
if (!key.startsWith(IK)) continue;
key = key.slice(IK_LEN);
const varDef = varData[key];
if (!varDef) continue;
if (varDef.options) {
let sel = val.startsWith(IK) && getVarOptByName(varDef, val.slice(IK_LEN));
if (!sel) {
key += '-custom';
sel = getVarOptByName(varDef, key + '-dropdown');
if (sel) varData[key].value = val;
}
if (sel) varDef.value = sel.name;
} else {
varDef.value = val;
}
}
}
return API.usercss.buildCode(json);
}
async function updateUsercss() {
let oldVer = ucd.version;
let {etag: oldEtag, updateUrl} = style;
const m2 = (css || URLS.extractUsoArchiveId(updateUrl)) &&
await getUsoEmbeddedMeta(css);
if (m2 && m2.updateUrl) {
let m2 = URLS.extractUsoArchiveId(updateUrl) && getUsoEmbeddedMeta();
if (m2 && (m2 = await m2).updateUrl) {
updateUrl = m2.updateUrl;
oldVer = m2.usercssData.version || '0';
oldEtag = '';
} else if (css) {
return;
}
if (oldEtag && oldEtag === await downloadEtag()) {
return Promise.reject(STATES.SAME_CODE);
@ -206,7 +234,7 @@ const updateMan = (() => {
if (err && etag && !style.etag) {
// first check of ETAG, gonna write it directly to DB as it's too trivial to sync or announce
style.etag = etag;
await db.styles.put(style);
await db.exec('put', style);
}
return err
? Promise.reject(err)
@ -236,7 +264,6 @@ const updateMan = (() => {
let {retryDelay = 1000} = opts;
while (true) {
try {
params = deepMerge(params || {}, {headers: {'Cache-Control': 'no-cache'}});
return await download(url, params);
} catch (code) {
if (!RETRY_ERRORS.includes(code) ||
@ -256,7 +283,8 @@ const updateMan = (() => {
}
function getDateFromVer(style) {
const m = RX_DATE2VER.exec((style.usercssData || {}).version);
const m = URLS.extractUsoArchiveId(style.updateUrl) &&
style.usercssData.version.match(RX_DATE2VER);
if (m) {
m[2]--; // month is 0-based in `Date` constructor
return new Date(...m.slice(1)).getTime();
@ -265,10 +293,13 @@ const updateMan = (() => {
/** UserCSS metadata may be embedded in the original USO style so let's use its updateURL */
function getUsoEmbeddedMeta(code = style.sourceCode) {
const isRaw = arguments[0];
const m = code.includes('@updateURL') && (isRaw ? code : code.replace(RX_META, '')).match(RX_META);
const m = code.includes('@updateURL') && code.replace(RX_META, '').match(RX_META);
return m && API.usercss.buildMeta({sourceCode: m[0]}).catch(() => null);
}
function getVarOptByName(varDef, name) {
return varDef.options.find(o => o.name === name);
}
}
function schedule() {
@ -317,32 +348,4 @@ const updateMan = (() => {
logLastWriteTime = Date.now();
logQueue = [];
}
function usoSpooferStart() {
if (++usoReferers === 1) {
chrome.webRequest.onBeforeSendHeaders.addListener(
usoSpoofer,
{types: ['xmlhttprequest'], urls: [USO_STYLES_API + '*']},
['blocking', 'requestHeaders', chrome.webRequest.OnBeforeSendHeadersOptions.EXTRA_HEADERS]
.filter(Boolean));
}
}
function usoSpooferStop() {
if (--usoReferers <= 0) {
usoReferers = 0;
chrome.webRequest.onBeforeSendHeaders.removeListener(usoSpoofer);
}
}
/** @param {chrome.webRequest.WebResponseHeadersDetails | browser.webRequest._OnBeforeSendHeadersDetails} info */
function usoSpoofer(info) {
if (info.tabId < 0 && URLS.ownOrigin.startsWith(info.initiator || info.originUrl || '')) {
const {requestHeaders: hh} = info;
const i = (hh.findIndex(h => /^referer$/i.test(h.name)) + 1 || hh.push({})) - 1;
hh[i].name = 'referer';
hh[i].value = URLS.uso;
return {requestHeaders: hh};
}
}
})();

View File

@ -74,10 +74,6 @@ bgReady.all.then(() => {
) && download(url);
}
function makeInstallerUrl(url) {
return `${URLS.installUsercss}?updateUrl=${encodeURIComponent(url)}`;
}
function makeUsercssGlobs(host, path) {
return '%css,%css?*,%styl,%styl?*'.replace(/%/g, `*://${host}${path}.user.`).split(',');
}
@ -86,11 +82,11 @@ bgReady.all.then(() => {
if (url.includes('.user.') &&
/^(https?|file|ftps?):/.test(url) &&
/\.user\.(css|styl)$/.test(url.split(/[#?]/, 1)[0]) &&
!oldUrl.startsWith(makeInstallerUrl(url))) {
!oldUrl.startsWith(URLS.installUsercss)) {
const inTab = url.startsWith('file:') && !chrome.app;
const code = await (inTab ? loadFromFile : loadFromUrl)(tabId, url);
if (!/^\s*</.test(code) && RX_META.test(code)) {
await openInstallerPage(tabId, url, {code, inTab});
openInstallerPage(tabId, url, {code, inTab});
}
}
}
@ -103,33 +99,25 @@ bgReady.all.then(() => {
openInstallerPage(tabId, url, {});
// Silently suppress navigation.
// Don't redirect to the install URL as it'll flash the text!
return {cancel: true};
return {redirectUrl: 'javascript:void 0'}; // eslint-disable-line no-script-url
}
}
async function openInstallerPage(tabId, url, {code, inTab} = {}) {
const newUrl = makeInstallerUrl(url);
function openInstallerPage(tabId, url, {code, inTab} = {}) {
const newUrl = `${URLS.installUsercss}?updateUrl=${encodeURIComponent(url)}`;
if (inTab) {
const tab = await browser.tabs.get(tabId);
return openURL({
browser.tabs.get(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};
try {
await browser.tabs.update(tabId, {url: newUrl});
} catch (err) {
// FIXME: remove this when kiwi supports tabs.update
// https://github.com/openstyles/stylus/issues/1367
if (/Tabs cannot be edited right now/i.test(err.message)) {
return browser.tabs.create({url: newUrl});
}
throw err;
chrome.tabs.update(tabId, {url: newUrl});
}
}

View File

@ -12,12 +12,10 @@ const usercssMan = {
name: null,
}),
/** `src` is a style or vars */
async assignVars(style, src) {
async assignVars(style, oldStyle) {
const meta = style.usercssData;
const meta2 = src.usercssData;
const {vars} = meta;
const oldVars = meta2 ? meta2.vars : src;
const vars = meta.vars;
const oldVars = (oldStyle.usercssData || {}).vars;
if (vars && oldVars) {
// The type of var might be changed during the update. Set value to null if the value is invalid.
for (const [key, v] of Object.entries(vars)) {
@ -45,7 +43,7 @@ const usercssMan = {
let log;
if (!metaOnly) {
if (vars || assignVars) {
await usercssMan.assignVars(style, vars || dup);
await usercssMan.assignVars(style, vars ? {usercssData: {vars}} : dup);
}
await usercssMan.buildCode(style);
log = style.log; // extracting the non-enumerable prop, otherwise it won't survive messaging
@ -139,18 +137,17 @@ const usercssMan = {
}
},
async install(style, opts) {
return API.styles.install(await usercssMan.parse(style, opts));
async install(style) {
return API.styles.install(await usercssMan.parse(style));
},
async parse(style, {dup, vars} = {}) {
async parse(style) {
style = await usercssMan.buildMeta(style);
// preserve style.vars during update
if (dup || (dup = await usercssMan.find(style))) {
const dup = await usercssMan.find(style);
if (dup) {
style.id = dup.id;
}
if (vars || (vars = dup)) {
await usercssMan.assignVars(style, vars);
await usercssMan.assignVars(style, dup);
}
return usercssMan.buildCode(style);
},

View File

@ -1,158 +0,0 @@
/* global URLS stringAsRegExp */// toolbox.js
/* global usercssMan */
'use strict';
const usoApi = {};
(() => {
const pingers = {};
usoApi.pingback = (usoId, delay) => {
clearTimeout(pingers[usoId]);
delete pingers[usoId];
if (delay > 0) {
return new Promise(resolve => (pingers[usoId] = setTimeout(ping, delay, usoId, resolve)));
} else if (delay !== false) {
return ping(usoId);
}
};
/**
* Replicating USO-Archive format
* https://github.com/33kk/uso-archive/blob/flomaster/lib/uso.js
* https://github.com/33kk/uso-archive/blob/flomaster/lib/converters.js
*/
usoApi.toUsercss = async (data, {metaOnly = true, varsUrl} = {}) => {
const badKeys = {};
const newKeys = [];
const descr = JSON.stringify(data.description.trim());
const vars = (data.style_settings || []).map(makeVar, {badKeys, newKeys}).join('');
const sourceCode = `\
/* ==UserStyle==
@name ${data.name}
@namespace USO Archive
@version ${data.updated.replace(/-/g, '').replace(/[T:]/g, '.').slice(0, 14)}
@description ${/^"['`]|\\/.test(descr) ? descr : descr.slice(1, -1)}
@author ${(data.user || {}).name || '?'}
@license ${makeLicense(data.license)}${vars ? '\n@preprocessor uso' + vars : ''}`
.replace(/\*\//g, '*\\/') +
`==/UserStyle== */\n${newKeys[0] ? useNewKeys(data.css, badKeys) : data.css}`;
const {style} = await usercssMan.build({sourceCode, metaOnly});
usoApi.useVarsUrl(style, varsUrl);
return {style, badKeys, newKeys};
};
usoApi.useVarsUrl = (style, url) => {
if (!/\?ik-/.test(url)) {
return;
}
const cfg = {badKeys: {}, newKeys: []};
const {vars} = style.usercssData;
if (!vars) {
return;
}
for (let [key, val] of new URLSearchParams(url.split('?')[1])) {
if (!key.startsWith('ik-')) continue;
key = makeKey(key.slice(3), cfg);
const v = vars[key];
if (!v) continue;
if (v.options) {
let sel = val.startsWith('ik-') && optByName(v, makeKey(val.slice(3), cfg));
if (!sel) {
key += '-custom';
sel = optByName(v, key + '-dropdown');
if (sel) vars[key].value = val;
}
if (sel) v.value = sel.name;
} else {
v.value = val;
}
}
return true;
};
async function ping(id, resolve) {
await fetch(`${URLS.uso}styles/install/${id}?source=stylish-ch`);
if (resolve) resolve(true);
return true;
}
function makeKey(key, {badKeys, newKeys}) {
let res = badKeys[key];
if (!res) {
res = key.replace(/[^-\w]/g, '-');
res += newKeys.includes(res) ? '-' : '';
if (key !== res) {
badKeys[key] = res;
newKeys.push(res);
}
}
return res;
}
function makeLicense(s) {
return !s ? 'NO-REDISTRIBUTION' :
s === 'publicdomain' ? 'CC0-1.0' :
s.startsWith('ccby') ? `${s.toUpperCase().match(/(..)/g).join('-')}-4.0` :
s;
}
function makeVar({
label,
setting_type: type,
install_key: ik,
style_setting_options: opts,
}) {
const cfg = this;
let value, suffix;
ik = makeKey(ik, cfg);
label = JSON.stringify(label);
switch (type) {
case 'color':
value = opts[0].value;
break;
case 'text':
value = JSON.stringify(opts[0].value);
break;
case 'image': {
const ikCust = `${ik}-custom`;
opts.push({
label: 'Custom',
install_key: `${ikCust}-dropdown`,
value: `/*[[${ikCust}]]*/`,
});
suffix = `\n@advanced text ${ikCust} ${label.slice(0, -1)} (Custom)" "https://foo.com/123.jpg"`;
type = 'dropdown';
} // fallthrough
case 'dropdown':
value = '';
for (const o of opts) {
const def = o.default ? '*' : '';
const val = o.value;
const s = ` ${makeKey(o.install_key, cfg)} ${JSON.stringify(o.label + def)} <<<EOT${
val.includes('\n') ? '\n' : ' '}${val} EOT;\n`;
value = def ? s + value : value + s;
}
value = `{\n${value}}`;
break;
default:
value = '"ERROR: unknown type"';
}
return `\n@advanced ${type} ${ik} ${label} ${value}${suffix || ''}`;
}
function optByName(v, name) {
return v.options.find(o => o.name === name);
}
function useNewKeys(css, badKeys) {
const rxsKeys = stringAsRegExp(Object.keys(badKeys).join('\n'), '', true).replace(/\n/g, '|');
const rxUsoVars = new RegExp(`(/\\*\\[\\[)(${rxsKeys})(?=]]\\*/)`, 'g');
return css.replace(rxUsoVars, (s, a, key) => a + badKeys[key]);
}
})();

View File

@ -35,7 +35,7 @@ const uswApi = (() => {
const maxKeyLen = meta.reduce((res, [k]) => Math.max(res, k.length), 0);
return [
'/* ==UserStyle==',
...meta.map(([k, v]) => v && `${k}${' '.repeat(maxKeyLen - k.length + 2)}${v}`).filter(Boolean),
...meta.map(([k, v]) => `${k}${' '.repeat(maxKeyLen - k.length + 2)}${v || ''}`),
'==/UserStyle== */',
].join('\n') + '\n\n';
}
@ -77,15 +77,14 @@ const uswApi = (() => {
*/
async publish(id, sourceCode) {
const style = await API.styles.get(id);
const code = style.usercssData ? sourceCode
: fakeUsercssHeader(style) + sourceCode;
const data = (style._usw || {}).token
? style._usw
: await linkStyle(style, code);
: await linkStyle(style, sourceCode);
const header = style.usercssData ? '' : fakeUsercssHeader(style);
return uswFetch(`style/${data.id}`, data.token, {
method: 'POST',
headers: {'Content-Type': 'application/json'},
body: JSON.stringify({code}),
body: JSON.stringify({code: header + sourceCode}),
});
},

View File

@ -5,7 +5,6 @@
(() => {
if (window.INJECTED === 1) return;
window.INJECTED = 1;
/** true -> when the page styles are received,
* false -> when disableAll mode is on at start, the styles won't be sent
@ -14,16 +13,11 @@
let hasStyles = false;
let isDisabled = false;
let isTab = !chrome.tabs || location.pathname !== '/popup.html';
const order = {main: [], prio: []};
const calcOrder = ({id}) =>
(order.prio[id] || 0) * 1e6 ||
order.main[id] ||
id + .5e6; // no order = at the end of `main`
const isFrame = window !== parent;
const isFrameAboutBlank = isFrame && location.href === 'about:blank';
const isUnstylable = !chrome.app && document instanceof XMLDocument;
const styleInjector = StyleInjector({
compare: (a, b) => calcOrder(a) - calcOrder(b),
compare: (a, b) => a.id - b.id,
onUpdate: onInjectorUpdate,
});
// dynamic iframes don't have a URL yet so we'll use their parent's URL (hash isn't inherited)
@ -44,23 +38,20 @@
/* about:blank iframes are often used by sites for file upload or background tasks
* and they may break if unexpected DOM stuff is present at `load` event
* so we'll add the styles only if the iframe becomes visible */
const {IntersectionObserver} = window;
const xoEventId = `${Math.random()}`;
/** @type IntersectionObserver */
let xo;
if (IntersectionObserver) {
window[Symbol.for('xo')] = (el, cb) => {
if (!xo) xo = new IntersectionObserver(onIntersect, {rootMargin: '100%'});
el.addEventListener(xoEventId, cb, {once: true});
xo.observe(el);
};
// FIXME: move this to background page when following bugs are fixed:
// https://bugzil.la/1587723, https://crbug.com/968651
const mqDark = matchMedia('(prefers-color-scheme: dark)');
mqDark.onchange = e => API.colorScheme.updateSystemPreferDark(e.matches);
mqDark.onchange(mqDark);
}
// Declare all vars before init() or it'll throw due to "temporal dead zone" of const/let
init();
const ready = init();
// the popup needs a check as it's not a tab but can be opened in a tab manually for whatever reason
if (!isTab) {
@ -71,17 +62,19 @@
}
msg.onTab(applyOnMessage);
window.addEventListener('pageshow', e => {
if (e.isTrusted && e.persisted) { // bfcache
updateCount();
}
});
if (!chrome.tabs) {
window.dispatchEvent(new CustomEvent(orphanEventId));
window.addEventListener(orphanEventId, orphanCheck, true);
}
// detect media change in content script
// FIXME: move this to background page when following bugs are fixed:
// https://bugzilla.mozilla.org/show_bug.cgi?id=1561546
// https://bugs.chromium.org/p/chromium/issues/detail?id=968651
const media = window.matchMedia('(prefers-color-scheme: dark)');
media.addListener(() => API.colorScheme.updateSystemPreferDark().catch(console.error));
function onInjectorUpdate() {
if (!isOrphaned) {
updateCount();
@ -107,10 +100,7 @@
parentStyles && await new Promise(onFrameElementInView) && parentStyles ||
!isFrameAboutBlank && chrome.app && !chrome.tabs && tryCatch(getStylesViaXhr) ||
await API.styles.getSectionsByUrl(matchUrl, null, true);
if (styles.cfg) {
isDisabled = styles.cfg.disableAll;
Object.assign(order, styles.cfg.order);
}
isDisabled = styles.disableAll;
hasStyles = !isDisabled;
if (hasStyles) {
window[SYM] = styles;
@ -176,11 +166,6 @@
}
break;
case 'styleSort':
Object.assign(order, request.order);
styleInjector.sort();
break;
case 'urlChanged':
if (!hasStyles && isDisabled || matchUrl === request.url) break;
matchUrl = request.url;
@ -190,6 +175,13 @@
});
break;
case 'backgroundReady':
ready.catch(err =>
msg.isIgnorableError(err)
? init()
: console.error(err));
break;
case 'updateCount':
updateCount();
break;
@ -239,7 +231,11 @@
}
function onFrameElementInView(cb) {
if (IntersectionObserver) {
parent[parent.Symbol.for('xo')](frameElement, cb);
} else {
requestAnimationFrame(cb);
}
}
/** @param {IntersectionObserverEntry[]} entries */
@ -259,11 +255,10 @@
}
function orphanCheck() {
if (chrome.runtime.id) return;
if (tryCatch(() => chrome.i18n.getUILanguage())) return;
// In Chrome content script is orphaned on an extension update/reload
// so we need to detach event listeners
window.removeEventListener(orphanEventId, orphanCheck, true);
mqDark.onchange = null;
isOrphaned = true;
setTimeout(styleInjector.clear, 1000); // avoiding FOUC
tryCatch(msg.off, applyOnMessage);

View File

@ -1,305 +1,354 @@
/* global API */// msg.js
/* global API msg */// msg.js
'use strict';
// eslint-disable-next-line no-unused-expressions
/^\/styles\/(\d+)(\/([^/]*))?([?#].*)?$/.test(location.pathname) && (async () => {
if (window.INJECTED_USO === 1) return;
window.INJECTED_USO = 1;
const usoId = RegExp.$1;
const USO = 'https://userstyles.org';
const apiUrl = `${USO}/api/v1/styles/${usoId}`;
const md5Url = `https://update.userstyles.org/${usoId}.md5`;
const CLICK = [
['#install_stylish_style_button', onInstall],
['#update_stylish_style_button', onInstall],
['.customize_style_button', onCustomize],
['.uninstall_stylish_style_button', onUninstall],
];
/^\/styles\/(\d+)(\/([^/]*))?([?#].*)?$/.test(location.pathname) && (() => {
const styleId = RegExp.$1;
const pageEventId = `${performance.now()}${Math.random()}`;
const contentEventId = pageEventId + ':';
const orphanEventId = chrome.runtime.id; // id won't be available in the orphaned script
const $ = (sel, base = document) => base.querySelector(sel);
const toggleListener = (isOn, ...args) => (isOn ? addEventListener : removeEventListener)(...args);
const togglePageListener = isOn => toggleListener(isOn, contentEventId, onPageEvent, true);
const mo = new MutationObserver(onMutation);
const observeColors = isOn =>
isOn ? mo.observe(document.body, {subtree: true, attributes: true, attributeFilter: ['value']})
: mo.disconnect();
window.dispatchEvent(new CustomEvent(chrome.runtime.id + '-install'));
window.addEventListener(chrome.runtime.id + '-install', orphanCheck, true);
let style, dup, md5, pageData, badKeys;
document.addEventListener('stylishInstallChrome', onClick);
document.addEventListener('stylishUpdateChrome', onClick);
runInPage(inPageContext, pageEventId, contentEventId, usoId, apiUrl);
addEventListener(orphanEventId, orphanCheck, true);
addEventListener('click', onClick, true);
togglePageListener(true);
msg.on(onMessage);
[md5, dup] = await Promise.all([
fetch(md5Url).then(r => r.text()),
API.styles.find({md5Url}, {installationUrl: `https://uso.kkx.one/style/${usoId}`})
.then(sendVarsToPage),
document.body || new Promise(resolve => addEventListener('load', resolve, {once: true})),
]);
let currentMd5;
const md5Url = getMeta('stylish-md5-url') || `https://update.userstyles.org/${styleId}.md5`;
Promise.all([
API.styles.find({md5Url}),
getResource(md5Url),
onDOMready(),
]).then(checkUpdatability);
if (!dup) {
sendStylishEvent('styleCanBeInstalledChrome');
} else if (dup.originalMd5 && dup.originalMd5 !== md5 || !dup.usercssData || !dup.md5Url) {
// allow update if 1) changed, 2) is a classic USO style, 3) is from USO-archive
sendStylishEvent('styleCanBeUpdatedChrome');
} else {
sendStylishEvent('styleAlreadyInstalledChrome');
document.documentElement.appendChild(
Object.assign(document.createElement('script'), {
textContent: `(${inPageContext})('${pageEventId}')`,
}));
function onMessage(msg) {
switch (msg.method) {
case 'ping':
// orphaned content script check
return true;
case 'openSettings':
openSettings();
return true;
}
}
async function onClick(e) {
for (const [sel, fn] of CLICK) {
const el = e.target.closest(sel);
if (!el) continue;
/* since we are using "stylish-code-chrome" meta key on all browsers and
US.o does not provide "advanced settings" on this url if browser is not Chrome,
we need to fix this URL using "stylish-update-url" meta key
*/
function getStyleURL() {
const textUrl = getMeta('stylish-update-url') || '';
const jsonUrl = getMeta('stylish-code-chrome') ||
textUrl.replace(/styles\/(\d+)\/[^?]*/, 'styles/chrome/$1.json');
const paramsMissing = !jsonUrl.includes('?') && textUrl.includes('?');
return jsonUrl + (paramsMissing ? textUrl.replace(/^[^?]+/, '') : '');
}
function checkUpdatability([installedStyle, md5]) {
// TODO: remove the following statement when USO is fixed
document.dispatchEvent(new CustomEvent(pageEventId, {
detail: installedStyle && installedStyle.updateUrl,
}));
currentMd5 = md5;
if (!installedStyle) {
sendEvent({type: 'styleCanBeInstalledChrome'});
return;
}
const isCustomizable = /\?/.test(installedStyle.updateUrl);
const md5Url = getMeta('stylish-md5-url');
if (md5Url && installedStyle.md5Url && installedStyle.originalMd5) {
reportUpdatable(isCustomizable || md5 !== installedStyle.originalMd5);
} else {
getStyleJson().then(json => {
reportUpdatable(
isCustomizable ||
!json ||
!styleSectionsEqual(json, installedStyle));
});
}
function prepareInstallButton() {
return new Promise(resolve => {
const observer = new MutationObserver(check);
observer.observe(document.documentElement, {
childList: true,
subtree: true,
});
check();
function check() {
if (document.querySelector('#install_style_button')) {
resolve();
observer.disconnect();
}
}
});
}
function reportUpdatable(isUpdatable) {
prepareInstallButton().then(() => {
sendEvent({
type: isUpdatable
? 'styleCanBeUpdatedChrome'
: 'styleAlreadyInstalledChrome',
detail: {
updateUrl: installedStyle.updateUrl,
},
});
});
}
}
function sendEvent(event) {
sendEvent.lastEvent = event;
let {type, detail = null} = event;
if (typeof cloneInto !== 'undefined') {
// Firefox requires explicit cloning, however USO can't process our messages anyway
// because USO tries to use a global "event" variable deprecated in Firefox
detail = cloneInto({detail}, document); /* global cloneInto */
} else {
detail = {detail};
}
document.dispatchEvent(new CustomEvent(type, detail));
}
function onClick(event) {
if (onClick.processing || !orphanCheck()) {
return;
}
onClick.processing = true;
doInstall()
.then(() => {
if (!event.type.includes('Update')) {
// FIXME: sometimes the button is broken i.e. the button sends
// 'install' instead of 'update' event while the style is already
// install.
// This triggers an incorrect install count but we don't really care.
return getResource(getMeta('stylish-install-ping-url-chrome'));
}
})
.catch(console.error)
.then(done);
function done() {
setTimeout(() => {
onClick.processing = false;
});
}
}
function doInstall() {
let oldStyle;
return API.styles.find({
md5Url: getMeta('stylish-md5-url') || location.href,
})
.then(_oldStyle => {
oldStyle = _oldStyle;
return oldStyle ?
oldStyle.name :
getResource(getMeta('stylish-description'));
})
.then(name => {
const props = {};
if (oldStyle) {
props.id = oldStyle.id;
}
return saveStyleCode(oldStyle ? 'styleUpdate' : 'styleInstall', name, props);
});
}
async function saveStyleCode(message, name, addProps = {}) {
const isNew = message === 'styleInstall';
const needsConfirmation = isNew || !saveStyleCode.confirmed;
if (needsConfirmation && !confirm(chrome.i18n.getMessage(message, [name]))) {
return Promise.reject();
}
saveStyleCode.confirmed = true;
enableUpdateButton(false);
const json = await getStyleJson();
if (!json) {
prompt(chrome.i18n.getMessage('styleInstallFailed', ''),
'https://github.com/openstyles/stylus/issues/195');
return;
}
// Update originalMd5 since USO changed it (2018-11-11) to NOT match the current md5
const style = await API.styles.install(Object.assign(json, addProps, {originalMd5: currentMd5}));
if (!isNew && style.updateUrl.includes('?')) {
enableUpdateButton(true);
} else {
sendEvent({type: 'styleInstalledChrome'});
}
function enableUpdateButton(state) {
const important = s => s.replace(/;/g, '!important;');
const button = document.getElementById('update_style_button');
if (button) {
button.style.cssText = state ? '' : important('pointer-events: none; opacity: .35;');
const icon = button.querySelector('img[src*=".svg"]');
if (icon) {
icon.style.cssText = state ? '' : important('transition: transform 5s; transform: rotate(0);');
if (state) {
setTimeout(() => (icon.style.cssText += important('transform: rotate(10turn);')));
}
}
}
}
}
function getMeta(name) {
const e = document.querySelector(`link[rel="${name}"]`);
return e ? e.getAttribute('href') : null;
}
async function getResource(url, opts) {
try {
el.disabled = true;
await fn(e);
} catch (e) {
alert(chrome.i18n.getMessage('styleInstallFailed', e.message || e));
} finally {
el.disabled = false;
}
}
}
function onCustomize() {
const ss = $('#style-settings');
const willShow = !ss || !ss.offsetHeight;
observeColors(willShow);
toggleListener(willShow, 'change', onChange);
}
async function onInstall(e) {
const {id} = dup;
e.stopPropagation();
if (!style) await buildStyle();
style = dup = await API.usercss.install(style, {
dup: {id},
vars: getPageVars(),
});
sendStylishEvent('styleInstalledChrome');
API.uso.pingback(id);
}
function onUninstall() {
const {id} = dup;
dup = style = false;
observeColors(false);
removeEventListener('change', onChange);
return API.styles.delete(id);
}
function onChange({target: el}) {
if (dup && el.matches('[name^="ik-"], [type=file]')) {
API.usercss.configVars(dup.id, getPageVars());
return url.startsWith('#')
? document.getElementById(url.slice(1)).textContent
: await API.download(url, opts);
} catch (error) {
alert('Error\n' + error.message);
return Promise.reject(error);
}
}
function onMutation(mutations) {
for (const {target: el} of mutations) {
if (el.style.display === 'none' &&
/^ik-/.test(el.name) &&
/^#[\da-f]{6}$/.test(el.value)) {
onChange({target: el});
// USO providing md5Url as "https://update.update.userstyles.org/#####.md5"
// instead of "https://update.userstyles.org/#####.md5"
async function getStyleJson() {
try {
const style = await getResource(getStyleURL(), {responseType: 'json'});
const codeElement = document.getElementById('stylish-code');
if (!style || !Array.isArray(style.sections) || style.sections.length ||
codeElement && !codeElement.textContent.trim()) {
return style;
}
const code = await getResource(getMeta('stylish-update-url'));
style.sections = (await API.worker.parseMozFormat({code})).sections;
if (style.md5Url) style.md5Url = style.md5Url.replace('update.update', 'update');
return style;
} catch (e) {}
}
/**
* The sections are checked in successive order because it matters when many sections
* match the same URL and they have rules with the same CSS specificity
* @param {Object} a - first style object
* @param {Object} b - second style object
* @returns {?boolean}
*/
function styleSectionsEqual({sections: a}, {sections: b}) {
const targets = ['urls', 'urlPrefixes', 'domains', 'regexps'];
return a && b && a.length === b.length && a.every(sameSection);
function sameSection(secA, i) {
return equalOrEmpty(secA.code, b[i].code, 'string', (a, b) => a === b) &&
targets.every(target => equalOrEmpty(secA[target], b[i][target], 'array', arrayMirrors));
}
function equalOrEmpty(a, b, type, comparator) {
const typeA = type === 'array' ? Array.isArray(a) : typeof a === type;
const typeB = type === 'array' ? Array.isArray(b) : typeof b === type;
return typeA && typeB && comparator(a, b) ||
(a == null || typeA && !a.length) &&
(b == null || typeB && !b.length);
}
function arrayMirrors(a, b) {
return a.length === b.length &&
a.every(el => b.includes(el)) &&
b.every(el => a.includes(el));
}
}
function onPageEvent(e) {
pageData = e.detail;
togglePageListener(false);
function onDOMready() {
return document.readyState !== 'loading'
? Promise.resolve()
: new Promise(resolve => document.addEventListener('DOMContentLoaded', resolve, {once: true}));
}
async function buildStyle() {
if (!pageData) pageData = await (await fetch(apiUrl)).json();
({style, badKeys} = await API.uso.toUsercss(pageData, {varsUrl: dup.updateUrl}));
Object.assign(style, {
md5Url,
id: dup.id,
originalMd5: md5,
updateUrl: apiUrl,
});
}
function getPageVars() {
const {vars} = (style || dup).usercssData;
for (const el of document.querySelectorAll('[name^="ik-"]')) {
const name = el.name.slice(3); // dropping "ik-"
const ik = (badKeys || {})[name] || name;
const v = vars[ik] || false;
const isImage = el.type === 'radio';
if (v && (!isImage || el.checked)) {
const val = el.value;
const isFile = val === 'user-upload';
if (isImage && (isFile || val === 'user-url')) {
const el2 = $(`[type=${isFile ? 'file' : 'url'}]`, el.parentElement);
const ikCust = `${ik}-custom`;
v.value = `${ikCust}-dropdown`;
vars[ikCust].value = isFile ? getFileUriFromPage(el2) : el2.value;
function openSettings(countdown = 10e3) {
const button = document.querySelector('.customize_button');
if (button) {
button.dispatchEvent(new MouseEvent('click', {bubbles: true}));
setTimeout(function pollArea(countdown = 2000) {
const area = document.getElementById('advancedsettings_area');
if (area || countdown < 0) {
(area || button).scrollIntoView({behavior: 'smooth', block: area ? 'end' : 'center'});
} else {
v.value = v.type === 'select' ? val.replace(/^ik-/, '') : val;
setTimeout(pollArea, 100, countdown - 100);
}
}, 500);
} else if (countdown > 0) {
setTimeout(openSettings, 100, countdown - 100);
}
}
return vars;
}
function getFileUriFromPage(el) {
togglePageListener(true);
sendPageEvent(el);
return pageData;
}
function runInPage(fn, ...args) {
const div = document.createElement('div');
div.attachShadow({mode: 'closed'})
.appendChild(document.createElement('script'))
.textContent = `(${fn})(${JSON.stringify(args).slice(1, -1)})`;
document.documentElement.appendChild(div).remove();
}
function sendPageEvent(data) {
dispatchEvent(data instanceof Node
? new MouseEvent(pageEventId, {relatedTarget: data})
: new CustomEvent(pageEventId, {detail: data}));
//* global cloneInto */// WARNING! Firefox requires cloning of an object `detail`
}
function sendStylishEvent(type) {
document.dispatchEvent(new Event(type));
}
function sendVarsToPage(style) {
if (style) {
const vars = (style.usercssData || {}).vars || `${style.updateUrl}`.split('?')[1];
if (vars) sendPageEvent('vars:' + JSON.stringify(vars));
}
return style || false;
}
function orphanCheck() {
if (chrome.runtime.id) return true;
removeEventListener(orphanEventId, orphanCheck, true);
removeEventListener('click', onClick, true);
removeEventListener('change', onChange);
sendPageEvent('quit');
observeColors(false);
togglePageListener(false);
try {
if (chrome.i18n.getUILanguage()) {
return true;
}
} catch (e) {}
// In Chrome content script is orphaned on an extension update/reload
// so we need to detach event listeners
window.removeEventListener(chrome.runtime.id + '-install', orphanCheck, true);
document.removeEventListener('stylishInstallChrome', onClick);
document.removeEventListener('stylishUpdateChrome', onClick);
try {
msg.off(onMessage);
} catch (e) {}
}
})();
function inPageContext(eventId, eventIdHost, styleId, apiUrl) {
let done, orphaned, vars;
// `chrome` may be empty if no extensions use externally_connectable but USO needs it
if (!window.chrome) window.chrome = {runtime: {sendMessage: () => {}}};
const EXT_ID = 'fjnbnpbmkenffdnngjfgmeleoegfcffe';
const {defineProperty} = Object;
const {dispatchEvent, CustomEvent, removeEventListener} = window;
const apply = Map.call.bind(Map.apply);
const OVR = [
[chrome.runtime, 'sendMessage', (fn, me, args) => {
const [id, /*msg*/, opts, cb = opts] = args;
if (id !== EXT_ID) return apply(fn, me, args);
if (typeof cb !== 'function') return Promise.resolve(true);
cb(true);
}],
[Response.prototype, 'json', async (fn, me, args) => {
const res = await apply(fn, me, args);
try {
if (!done && me.url === apiUrl) {
done = true;
send(res);
setVars(res);
}
} catch (e) {}
return res;
}],
[window, 'fetch', (fn, me, args) =>
args[0] === `chrome-extension://${EXT_ID}/index.html`
? Promise.resolve(new Response('<!doctype html><html lang="en"></html>'))
: apply(fn, me, args),
],
];
OVR.forEach(([obj, name, caller], i) => {
const orig = obj[name];
const ovr = new Proxy(orig, {
apply(fn, me, args) {
if (orphaned) restore(obj, name, ovr, fn);
return (orphaned ? apply : caller)(fn, me, args);
},
});
defineProperty(obj, name, {value: ovr});
OVR[i] = [obj, name, ovr, orig]; // same args as restore()
});
/* We set `isInstalled` at page start intentionally not trying to replicate Stylish login events.
* This difference allows USO site to detect presence of Stylus (or another similar extension). */
function inPageContext(eventId) {
document.currentScript.remove();
window.isInstalled = true;
addEventListener(eventId, onCommand, true);
function onCommand(e) {
if (e.detail === 'quit') {
removeEventListener(eventId, onCommand, true);
OVR.forEach(restore);
done = orphaned = true;
} else if (/^vars:/.test(e.detail)) {
vars = JSON.parse(e.detail.slice(5));
} else if (e.relatedTarget) {
send(e.relatedTarget.uploadedData);
}
}
function restore(obj, name, ovr, orig) { // same order as OVR after patching
if (obj[name] === ovr) {
defineProperty(obj, name, {value: orig});
}
}
function send(data) {
dispatchEvent(new CustomEvent(eventIdHost, {__proto: null, detail: data}));
}
function setVars(json) {
const images = new Map();
const isNew = typeof vars === 'object';
const badKeys = {};
const newKeys = [];
const makeKey = ({install_key: key}) => {
let res = isNew ? badKeys[key] : key;
if (!res) {
res = key.replace(/[^-\w]/g, '-');
res += newKeys.includes(res) ? '-' : '';
if (key !== res) {
badKeys[key] = res;
newKeys.push(res);
}
}
return res;
const origMethods = {
json: Response.prototype.json,
byId: document.getElementById,
};
if (!isNew) vars = new URLSearchParams(vars);
for (const ss of json.style_settings || []) {
const ik = makeKey(ss);
let value = isNew ? (vars[ik] || {}).value : vars.get('ik-' + ik);
if (value == null || !(ss.style_setting_options || [])[0]) {
let vars;
// USO bug workaround: prevent errors in console after install and busy cursor
document.getElementById = id =>
origMethods.byId.call(document, id) ||
(/^(stylish-code|stylish-installed-style-installed-\w+|post-install-ad|style-install-unknown)$/.test(id)
? Object.assign(document.createElement('p'), {className: 'afterdownload-ad'})
: null);
// USO bug workaround: use the actual image data in customized settings
document.addEventListener(eventId, ({detail}) => {
vars = /\?/.test(detail) && new URL(detail).searchParams;
if (!vars) Response.prototype.json = origMethods.json;
}, {once: true});
Response.prototype.json = async function () {
const json = await origMethods.json.apply(this, arguments);
if (vars && json && Array.isArray(json.style_settings)) {
Response.prototype.json = origMethods.json;
const images = new Map();
for (const ss of json.style_settings) {
let value = vars.get('ik-' + ss.install_key);
if (!value || !(ss.style_setting_options || [])[0]) {
continue;
}
if (ss.setting_type === 'image') {
let isListed;
for (const opt of ss.style_setting_options) {
isListed |= opt.default = (opt.install_key === value);
}
images.set(ik, {url: isNew && !isListed ? vars[`${ik}-custom`].value : value, isListed});
} else if (value.startsWith('ik-') || isNew && vars[ik].type === 'select') {
if (value.startsWith('ik-')) {
value = value.replace(/^ik-/, '');
const def = ss.style_setting_options.find(item => item.default);
if (!def || makeKey(def) !== value) {
if (!def || def.install_key !== value) {
if (def) def.default = false;
for (const item of ss.style_setting_options) {
if (makeKey(item) === value) {
if (item.install_key === value) {
item.default = true;
break;
}
}
}
} else if (ss.setting_type === 'image') {
let isListed;
for (const opt of ss.style_setting_options) {
isListed |= opt.default = (opt.value === value);
}
images.set(ss.install_key, {url: value, isListed});
} else {
const item = ss.style_setting_options[0];
if (item.value !== value && item.install_key === 'placeholder') {
@ -307,18 +356,23 @@ function inPageContext(eventId, eventIdHost, styleId, apiUrl) {
}
}
}
if (!images.size) return;
if (images.size) {
new MutationObserver((_, observer) => {
if (!document.getElementById('style-settings')) return;
if (document.getElementById('style-settings')) {
observer.disconnect();
for (const [name, {url, isListed}] of images) {
const elRadio = document.querySelector(`input[name="ik-${name}"][value="user-url"]`);
const elUrl = elRadio && document.getElementById(elRadio.id.replace('url-choice', 'user-url'));
const elUrl = elRadio &&
document.getElementById(elRadio.id.replace('url-choice', 'user-url'));
if (elUrl) {
elRadio.checked = !isListed;
elUrl.value = url;
}
}
}
}).observe(document, {childList: true, subtree: true});
}
}
return json;
};
}

View File

@ -9,14 +9,12 @@ window.StyleInjector = window.INJECTED === 1 ? window.StyleInjector : ({
const PATCH_ID = 'transition-patch';
// styles are out of order if any of these elements is injected between them
const ORDERED_TAGS = new Set(['head', 'body', 'frameset', 'style', 'link']);
const docRewriteObserver = RewriteObserver(sort);
const docRootObserver = RootObserver(sortIfNeeded);
const toSafeChar = c => String.fromCharCode(0xFF00 + c.charCodeAt(0) - 0x20);
const docRewriteObserver = RewriteObserver(_sort);
const docRootObserver = RootObserver(_sortIfNeeded);
const list = [];
const table = new Map();
let isEnabled = true;
let isTransitionPatched = chrome.app && CSS.supports('accent-color', 'red'); // Chrome 93
let exposeStyleName;
let isTransitionPatched;
// will store the original method refs because the page can override them
let creationDoc, createElement, createElementNS;
@ -25,24 +23,24 @@ window.StyleInjector = window.INJECTED === 1 ? window.StyleInjector : ({
list,
async apply(styleMap) {
const styles = styleMapToArray(styleMap);
const styles = _styleMapToArray(styleMap);
const value = !styles.length
? []
: await docRootObserver.evade(() => {
if (!isTransitionPatched && isEnabled) {
applyTransitionPatch(styles);
_applyTransitionPatch(styles);
}
return styles.map(addUpdate);
return styles.map(_addUpdate);
});
emitUpdate();
_emitUpdate();
return value;
},
clear() {
addRemoveElements(false);
_addRemoveElements(false);
list.length = 0;
table.clear();
emitUpdate();
_emitUpdate();
},
clearOrphans() {
@ -55,12 +53,12 @@ window.StyleInjector = window.INJECTED === 1 ? window.StyleInjector : ({
},
remove(id) {
remove(id);
emitUpdate();
_remove(id);
_emitUpdate();
},
replace(styleMap) {
const styles = styleMapToArray(styleMap);
const styles = _styleMapToArray(styleMap);
const added = new Set(styles.map(s => s.id));
const removed = [];
for (const style of list) {
@ -68,24 +66,22 @@ window.StyleInjector = window.INJECTED === 1 ? window.StyleInjector : ({
removed.push(style.id);
}
}
styles.forEach(addUpdate);
removed.forEach(remove);
emitUpdate();
styles.forEach(_addUpdate);
removed.forEach(_remove);
_emitUpdate();
},
toggle(enable) {
if (isEnabled === enable) return;
isEnabled = enable;
if (!enable) toggleObservers(false);
addRemoveElements(enable);
if (enable) toggleObservers(true);
if (!enable) _toggleObservers(false);
_addRemoveElements(enable);
if (enable) _toggleObservers(true);
},
sort: sort,
};
function add(style) {
const el = style.el = createStyle(style);
function _add(style) {
const el = style.el = _createStyle(style.id, style.code);
const i = list.findIndex(item => compare(item, style) > 0);
table.set(style.id, style);
if (isEnabled) {
@ -95,7 +91,7 @@ window.StyleInjector = window.INJECTED === 1 ? window.StyleInjector : ({
return el;
}
function addRemoveElements(add) {
function _addRemoveElements(add) {
for (const {el} of list) {
if (add) {
document.documentElement.appendChild(el);
@ -105,11 +101,11 @@ window.StyleInjector = window.INJECTED === 1 ? window.StyleInjector : ({
}
}
function addUpdate(style) {
return table.has(style.id) ? update(style) : add(style);
function _addUpdate(style) {
return table.has(style.id) ? _update(style) : _add(style);
}
function applyTransitionPatch(styles) {
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
@ -118,20 +114,19 @@ window.StyleInjector = window.INJECTED === 1 ? window.StyleInjector : ({
!styles.some(s => s.code.includes('transition'))) {
return;
}
const el = createStyle({id: PATCH_ID, code: `
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(style = {}) {
const {id} = style;
if (!creationDoc) initCreationDoc();
function _createStyle(id, code = '') {
if (!creationDoc) _initCreationDoc();
let el;
if (document.documentElement instanceof SVGSVGElement) {
// SVG document style
@ -151,27 +146,18 @@ window.StyleInjector = window.INJECTED === 1 ? window.StyleInjector : ({
el.type = 'text/css';
// SVG className is not a string, but an instance of SVGAnimatedString
el.classList.add('stylus');
setTextAndName(el, style);
el.textContent = code;
return el;
}
function setTextAndName(el, {id, code = '', name}) {
if (exposeStyleName && name) {
el.dataset.name = name;
name = encodeURIComponent(name.replace(/[?#/']/g, toSafeChar));
code += `\n/*# sourceURL=${chrome.runtime.getURL(name)}.user.css#${id} */`;
}
el.textContent = code;
}
function toggleObservers(shouldStart) {
function _toggleObservers(shouldStart) {
const onOff = shouldStart && isEnabled ? 'start' : 'stop';
docRewriteObserver[onOff]();
docRootObserver[onOff]();
}
function emitUpdate() {
toggleObservers(list.length);
function _emitUpdate() {
_toggleObservers(list.length);
onUpdate();
}
@ -182,11 +168,11 @@ window.StyleInjector = window.INJECTED === 1 ? window.StyleInjector : ({
and since userAgent.navigator can be spoofed via about:config or devtools,
we're checking for getPreventDefault that was removed in FF59
*/
function initCreationDoc() {
function _initCreationDoc() {
creationDoc = !Event.prototype.getPreventDefault && document.wrappedJSObject;
if (creationDoc) {
({createElement, createElementNS} = creationDoc);
const el = document.documentElement.appendChild(createStyle());
const el = document.documentElement.appendChild(_createStyle());
const isApplied = el.sheet;
el.remove();
if (isApplied) return;
@ -195,7 +181,7 @@ window.StyleInjector = window.INJECTED === 1 ? window.StyleInjector : ({
({createElement, createElementNS} = document);
}
function remove(id) {
function _remove(id) {
const style = table.get(id);
if (!style) return;
table.delete(id);
@ -203,14 +189,14 @@ window.StyleInjector = window.INJECTED === 1 ? window.StyleInjector : ({
style.el.remove();
}
function sort() {
function _sort() {
docRootObserver.evade(() => {
list.sort(compare);
addRemoveElements(true);
_addRemoveElements(true);
});
}
function sortIfNeeded() {
function _sortIfNeeded() {
let needsSort;
let el = list.length && list[0].el;
if (!el) {
@ -231,29 +217,22 @@ window.StyleInjector = window.INJECTED === 1 ? window.StyleInjector : ({
// some styles are not injected to the document
if (i < list.length) needsSort = true;
}
if (needsSort) sort();
if (needsSort) _sort();
return needsSort;
}
function styleMapToArray(styleMap) {
if (styleMap.cfg) {
({exposeStyleName} = styleMap.cfg);
delete styleMap.cfg;
}
return Object.values(styleMap).map(({id, code, name}) => ({
id,
name,
code: code.join(''),
function _styleMapToArray(styleMap) {
return Object.values(styleMap).map(s => ({
id: s.id,
code: s.code.join(''),
}));
}
function update(newStyle) {
const {id, code} = newStyle;
function _update({id, code}) {
const style = table.get(id);
if (style.code !== code ||
style.name !== newStyle.name && exposeStyleName) {
if (style.code !== code) {
style.code = code;
setTextAndName(style.el, newStyle);
style.el.textContent = code;
}
}
@ -262,14 +241,14 @@ window.StyleInjector = window.INJECTED === 1 ? window.StyleInjector : ({
let root;
let observing = false;
let timer;
const observer = new MutationObserver(check);
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);
timer = setTimeout(_check);
observer.observe(document, {childList: true});
observing = true;
}
@ -281,7 +260,7 @@ window.StyleInjector = window.INJECTED === 1 ? window.StyleInjector : ({
observing = false;
}
function check() {
function _check() {
if (root !== document.documentElement) {
root = document.documentElement;
onChange();
@ -311,7 +290,7 @@ window.StyleInjector = window.INJECTED === 1 ? window.StyleInjector : ({
function evade(fn) {
const restore = observing && start;
stop();
return new Promise(resolve => run(fn, resolve, waitForRoot))
return new Promise(resolve => _run(fn, resolve, _waitForRoot))
.then(restore);
}
@ -329,7 +308,7 @@ window.StyleInjector = window.INJECTED === 1 ? window.StyleInjector : ({
observing = false;
}
function run(fn, resolve, wait) {
function _run(fn, resolve, wait) {
if (document.documentElement) {
resolve(fn());
return true;
@ -337,8 +316,8 @@ window.StyleInjector = window.INJECTED === 1 ? window.StyleInjector : ({
if (wait) wait(fn, resolve);
}
function waitForRoot(...args) {
new MutationObserver((_, observer) => run(...args) && observer.disconnect())
function _waitForRoot(...args) {
new MutationObserver((_, observer) => _run(...args) && observer.disconnect())
.observe(document, {childList: true});
}
}

266
edit.html
View File

@ -5,8 +5,7 @@
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link href="global.css" rel="stylesheet">
<link href="global-dark.css" rel="stylesheet">
<style id="cm-theme"></style>
<link id="cm-theme" rel="stylesheet">
<script src="js/polyfill.js"></script>
<script src="js/toolbox.js"></script>
@ -18,7 +17,7 @@
<script src="content/apply.js"></script>
<script src="js/sections-util.js"></script>
<script src="js/storage-util.js"></script>
<script src="js/event-emitter.js"></script>
<script src="edit/codemirror-themes.js"></script> <!-- must precede base.js -->
<script src="edit/base.js"></script>
@ -41,15 +40,15 @@
<script src="vendor/codemirror/addon/lint/lint.js"></script>
<script src="vendor/codemirror/addon/hint/show-hint.js"></script>
<script src="vendor/codemirror/addon/hint/css-hint.js"></script>
<script src="vendor/codemirror/keymap/emacs.js"></script>
<script src="vendor/codemirror/keymap/sublime.js"></script>
<script src="vendor/codemirror/keymap/vim.js"></script>
<script src="vendor-overwrites/codemirror-addon/match-highlighter.js"></script>
<script src="vendor/lz-string-unsafe/lz-string-unsafe.min.js"></script>
<script src="js/color/color-converter.js"></script>
<script src="js/color/color-mimicry.js"></script>
<script src="js/color/color-picker.js"></script>
<script src="js/color/color-view.js"></script>
<script src="js/storage-util.js"></script>
<script src="js/worker-util.js"></script>
<script src="edit/util.js"></script>
@ -63,24 +62,26 @@
<script src="edit/sections-editor-section.js"></script>
<script src="edit/sections-editor.js"></script>
<script src="edit/usw-integration.js"></script>
<script src="edit/settings.js"></script>
<script src="edit/edit.js"></script>
<template data-id="appliesTo">
<li class="applies-to-item">
<div class="select-resizer">
<select name="applies-type" class="applies-type style-contributor">
<option value="url" i18n="appliesUrlOption"></option>
<option value="url-prefix" i18n="appliesUrlPrefixOption"></option>
<option value="domain" i18n="appliesDomainOption"></option>
<option value="regexp" i18n="appliesRegexpOption"></option>
<option value="url" i18n-text="appliesUrlOption"></option>
<option value="url-prefix" i18n-text="appliesUrlPrefixOption"></option>
<option value="domain" i18n-text="appliesDomainOption"></option>
<option value="regexp" i18n-text="appliesRegexpOption"></option>
</select>
<svg class="svg-icon select-arrow"><use xlink:href="#svg-icon-select-arrow"/></svg>
</div>
<div class="applies-value-wrapper">
<input name="applies-value" class="applies-value style-contributor" spellcheck="false">
<a class="remove-applies-to" i18n="appliesRemove, title:appliesRemove" tabindex="0">
<a class="remove-applies-to" i18n-text="appliesRemove" i18n-title="appliesRemove" tabindex="0">
<svg class="svg-icon remove"><use xlink:href="#svg-icon-minus"/></svg>
</a>
<a class="add-applies-to" i18n="appliesAdd, title:appliesAdd" tabindex="0">
<a class="add-applies-to" i18n-text="appliesAdd" i18n-title="appliesAdd" tabindex="0">
<svg class="svg-icon add"><use xlink:href="#svg-icon-plus"/></svg>
</a>
</div>
@ -88,8 +89,8 @@
</template>
<template data-id="appliesToEverything">
<li class="applies-to-everything" i18n="appliesToEverything">
<a class="add-applies-to" i18n="appliesAdd, title:appliesAdd" tabindex="0">
<li class="applies-to-everything" i18n-text="appliesToEverything">
<a class="add-applies-to" i18n-text="appliesAdd" i18n-title="appliesAdd" tabindex="0">
<svg class="svg-icon add"><use xlink:href="#svg-icon-plus"/></svg>
</a>
</li>
@ -99,11 +100,11 @@
<div class="section">
<!-- not using DIV to make our CSS work for #sections > div:only-of-type .remove-section -->
<p class="deleted-section">
<button class="restore-section" i18n="sectionRestore"></button>
<button class="restore-section" i18n-text="sectionRestore"></button>
</p>
<label i18n="sectionCode" class="code-label"></label>
<label i18n-text="sectionCode" class="code-label"></label>
<div class="applies-to">
<label i18n="appliesLabel, title:appliesHelp" data-cmd="note">
<label i18n-text="appliesLabel">
<a class="svg-inline-wrapper applies-to-help" tabindex="0">
<svg class="svg-icon info"><use xlink:href="#svg-icon-help"/></svg>
</a>
@ -111,13 +112,13 @@
<ul class="applies-to-list"></ul>
</div>
<div class="edit-actions">
<button class="remove-section" i18n="sectionRemove"></button>
<button class="add-section" i18n="long-text:sectionAdd, short-text:genericAdd"></button>
<button class="clone-section" i18n="genericClone"></button>
<button class="remove-section" i18n-text="sectionRemove"></button>
<button class="add-section" i18n-long-text="sectionAdd" i18n-short-text="genericAdd"></button>
<button class="clone-section" i18n-text="genericClone"></button>
<button class="move-section-up"></button>
<button class="move-section-down"></button>
<button class="beautify-section" i18n="styleBeautify"></button>
<button class="test-regexp" i18n="genericTest"></button>
<button class="beautify-section" i18n-text="styleBeautify"></button>
<button class="test-regexp" i18n-text="styleRegexpTestButton"></button>
</div>
</div>
</template>
@ -127,27 +128,27 @@
<div data-type="main">
<div data-type="content"></div>
<div data-type="actions">
<a data-action="case" i18n="title:searchCaseSensitive" tabindex="0">Aa</a>
<a data-action="prev" i18n="title:genericPrevious" data-hotkey-tooltip="findPrev" tabindex="0">
<a data-action="case" i18n-title="searchCaseSensitive" tabindex="0">Aa</a>
<a data-action="prev" i18n-title="genericPrevious" data-hotkey-tooltip="findPrev" tabindex="0">
<svg class="svg-icon" style="transform: rotate(180deg)"><use xlink:href="#svg-icon-v"/></svg>
</a>
<a data-action="next" i18n="title:genericNext" data-hotkey-tooltip="findNext" tabindex="0">
<a data-action="next" i18n-title="genericNext" data-hotkey-tooltip="findNext" tabindex="0">
<svg class="svg-icon"><use xlink:href="#svg-icon-v"/></svg>
</a>
<a data-action="close" i18n="title:confirmClose" data-hotkey-tooltip="=Esc" tabindex="0">
<a data-action="close" i18n-title="confirmClose" data-hotkey-tooltip="=Esc" tabindex="0">
<svg class="svg-icon dismiss"><use xlink:href="#svg-icon-close"/></svg>
</a>
</div>
</div>
<div data-type="status">
<div class="CodeMirror-search-hint" i18n-text="searchRegexp"></div>
<div data-type="tally" i18n="title:searchNumberOfResults"></div>
<div data-type="tally" i18n-title="searchNumberOfResults"></div>
</div>
</div>
</template>
<template data-id="clearSearch">
<div data-type="hover" i18n="title:confirmDelete">
<div data-type="hover" i18n-title="confirmDelete">
<svg data-action="clear" class="svg-icon"><use xlink:href="#svg-icon-close"></use></svg>
</div>
</template>
@ -156,7 +157,7 @@
<div data-type="content">
<div data-type="input-wrapper">
<textarea class="CodeMirror-search-field" rows="1" spellcheck="false" required
i18n="placeholder:search"></textarea>
i18n-placeholder="search"></textarea>
</div>
</div>
</template>
@ -165,36 +166,36 @@
<div data-type="content">
<div data-type="input-wrapper">
<textarea data-type="replace-from"
i18n="placeholder:replace"
i18n-placeholder="replace"
class="CodeMirror-search-field" rows="1" required
spellcheck="false"></textarea>
</div>
<div data-type="input-wrapper">
<textarea data-type="replace-to"
i18n="placeholder:replaceWith"
i18n-placeholder="replaceWith"
class="CodeMirror-search-field" rows="1" required
spellcheck="false"></textarea>
</div>
<button data-action="replace" i18n="replace" disabled></button>
<button data-action="replaceAll" i18n="replaceAll" disabled></button>
<button data-action="undo" i18n="undo" disabled></button>
<button data-action="replace" i18n-text="replace" disabled></button>
<button data-action="replaceAll" i18n-text="replaceAll" disabled></button>
<button data-action="undo" i18n-text="undo" disabled></button>
<!--
Using a separate set of buttons because
1. FF can display tooltips only when specified on the <button>, ignores the nested <title> in <svg>
2. the icon doesn't fill the entire button area so tooltips aren't shown when the edges are hovered
-->
<button class="hidden" data-action="replace" i18n="title:replace" disabled>
<button class="hidden" data-action="replace" i18n-title="replace" disabled>
<svg class="svg-icon" viewBox="0 0 20 20">
<polygon points="15.83 4.75 8.76 11.82 5.2 8.26 3.51 9.95 8.76 15.19 17.52 6.43 15.83 4.75"/>
</svg>
</button>
<button class="hidden" data-action="replaceAll" i18n="title:replaceAll" disabled>
<button class="hidden" data-action="replaceAll" i18n-title="replaceAll" disabled>
<svg class="svg-icon" viewBox="0 0 20 20">
<polygon points="15.8,1.8 8.8,8.8 5.2,5.3 3.5,6.9 8.8,12.2 17.5,3.4 "/>
<polygon points="15.8,7.8 8.8,14.8 5.2,11.3 3.5,12.9 8.8,18.2 17.5,9.4 "/>
</svg>
</button>
<button class="hidden" data-action="undo" i18n="title:undo" disabled>
<button class="hidden" data-action="undo" i18n-title="undo" disabled>
<svg class="svg-icon" viewBox="0 0 20 20">
<path d="M11.3,5.5H8.7V1.4L1.9,6.5l6.8,5.1V7.5h2.6c1.8,0,3.2,1.4,3.2,3.2s-1.4,3.2-3.2,3.2H7.8v2h3.5c2.9,0,5.2-2.3,5.2-5.2S14.2,5.5,11.3,5.5z"/>
</svg>
@ -203,7 +204,7 @@
</template>
<template data-id="jumpToLine">
<span i18n="editGotoLine">: <input class="CodeMirror-jump-field" type="text"></span>
<span i18n-text="editGotoLine">: <input class="CodeMirror-jump-field" type="text"></span>
</template>
<template data-id="regexpTestPartial">
@ -211,15 +212,15 @@
</template>
<template data-id="resizeGrip">
<div class="resize-grip" i18n="title:cm_resizeGripHint"></div>
<div class="resize-grip" i18n-title="cm_resizeGripHint"></div>
</template>
<template data-id="keymapHelp">
<table class="keymap-list">
<thead>
<tr>
<th><input i18n="placeholder:helpKeyMapHotkey" type="search"></th>
<th><input i18n="placeholder:helpKeyMapCommand" type="search"></th>
<th><input i18n-placeholder="helpKeyMapHotkey" type="search" class="can-close-on-esc"></th>
<th><input i18n-placeholder="helpKeyMapCommand" type="search" class="can-close-on-esc" spellcheck="false"></th>
</tr>
</thead>
<tbody>
@ -239,20 +240,18 @@
<link href="vendor/codemirror/addon/search/matchesonscrollbar.css" rel="stylesheet">
<link href="js/color/color-picker.css" rel="stylesheet">
<link href="edit/codemirror-default.css" rel="stylesheet">
<link href="edit/edit.css" rel="stylesheet">
<link rel="stylesheet" href="edit/tab.css">
<link rel="stylesheet" href="edit/settings.css">
</head>
<template data-id="body"> <!-- https://crbug.com/1288447 -->
<body id="stylus-edit">
<div id="header">
<h1 id="heading" i18n="data-edit:editStyleHeading, data-add:addStyleTitle">
<a class="usercss-only"
href="https://github.com/openstyles/stylus/wiki/Usercss"
i18n="title:externalUsercssDocument" target="_blank">
<svg class="svg-icon info"><use xlink:href="#svg-icon-help"/></svg>
</a>
</h1>
<h1 id="heading" i18n-data-edit="editStyleHeading" i18n-data-add="addStyleTitle"></h1>
<section id="basic-info">
<div id="basic-info-name">
<input id="name" class="style-contributor" spellcheck="false">
<a id="reset-name" i18n="title:customNameResetHint" tabindex="0" hidden>
<a id="reset-name" i18n-title="customNameResetHint" tabindex="0" hidden>
<svg class="svg-icon" viewBox="0 0 20 20">
<polygon points="16.2,5.5 14.5,3.8 10,8.3 5.5,3.8 3.8,5.5 8.3,10 3.8,14.5
5.5,16.2 10,11.7 14.5,16.2 16.2,14.5 11.7,10 "/>
@ -262,99 +261,87 @@
</div>
<div id="basic-info-enabled">
<label id="enabled-label"
i18n="styleEnabledLabel, title:toggleStyle"
i18n-text="styleEnabledLabel"
i18n-title="toggleStyle"
data-hotkey-tooltip="toggleStyle">
<input type="checkbox" id="enabled" class="style-contributor">
<svg class="svg-icon checked"><use xlink:href="#svg-icon-checked"/></svg>
</label>
<label id="preview-label" i18n="previewLabel, title:previewTooltip">
<label id="preview-label" i18n-text="previewLabel" i18n-title="previewTooltip">
<input type="checkbox" id="editor.livePreview">
<svg class="svg-icon checked"><use xlink:href="#svg-icon-checked"/></svg>
</label>
<label id="disableAll-label" i18n="data-on:disableAllStyles, data-off:disableAllStylesOff">
<input id="disableAll" type="checkbox">
<svg class="svg-icon checked"><use xlink:href="#svg-icon-checked"/></svg>
</label>
<span id="preview-errors" hidden>!</span>
<span id="preview-errors" class="hidden">!</span>
</div>
</section>
<section id="actions">
<div class="buttons">
<div class="split-btn">
<button id="save-button" i18n="styleSaveLabel" data-hotkey-tooltip="save" disabled></button
><button class="split-btn-pedal usercss-only" i18n="menu-tpl:saveAsTemplate"></button>
<div>
<button id="save-button" i18n-text="styleSaveLabel" data-hotkey-tooltip="save" disabled></button>
<button id="beautify" i18n-text="styleBeautify"></button>
<a href="manage.html" tabindex="-1"><button id="cancel-button" i18n-text="styleCancelEditLabel"></button></a>
</div>
<button id="beautify" i18n="styleBeautify"></button>
<button id="style-settings-btn" i18n="settings"></button>
<button id="cancel-button" i18n="title:styleCancelEditLabel"></button>
</div>
<div id="mozilla-format-buttons" class="buttons sectioned-only">
<button id="from-mozilla" i18n="importLabel"></button>
<button id="to-mozilla" i18n="exportLabel"></button>
<div id="mozilla-format-buttons" class="sectioned-only">
<button id="from-mozilla" i18n-text="importLabel"></button>
<button id="to-mozilla" i18n-text="exportLabel"></button>
<a id="to-mozilla-help" class="svg-inline-wrapper" tabindex="0"
i18n="title:styleMozillaFormatHeading">
i18n-title="styleMozillaFormatHeading">
<svg class="svg-icon info"><use xlink:href="#svg-icon-help"/></svg>
</a>
</div>
</section>
<div id="details-wrapper">
<details id="options" data-pref="editor.options.expanded" class="ignore-pref-if-compact">
<summary><h2 id="options-heading" i18n="editorSettings"></h2></summary>
<summary><h2 id="options-heading" i18n-text="optionsHeading"></h2></summary>
<div id="options-wrapper">
<div class="options-column">
<div class="option">
<label id="lineWrapping-label" i18n="cm_lineWrapping">
<label id="lineWrapping-label" i18n-text="cm_lineWrapping">
<input id="editor.lineWrapping" type="checkbox">
<svg class="svg-icon checked"><use xlink:href="#svg-icon-checked"/></svg>
</label>
</div>
<div class="option">
<label id="smartIndent-label" i18n="cm_smartIndent">
<label id="smartIndent-label" i18n-text="cm_smartIndent">
<input id="editor.smartIndent" type="checkbox">
<svg class="svg-icon checked"><use xlink:href="#svg-icon-checked"/></svg>
</label>
</div>
<div class="option">
<label id="indentWithTabs-label" i18n="cm_indentWithTabs">
<label id="indentWithTabs-label" i18n-text="cm_indentWithTabs">
<input id="editor.indentWithTabs" type="checkbox">
<svg class="svg-icon checked"><use xlink:href="#svg-icon-checked"/></svg>
</label>
</div>
<div class="option">
<label i18n="cm_autoCloseBrackets, title:cm_autoCloseBracketsTooltip">
<label i18n-text="cm_autoCloseBrackets" i18n-title="cm_autoCloseBracketsTooltip">
<input id="editor.autoCloseBrackets" type="checkbox">
<svg class="svg-icon checked"><use xlink:href="#svg-icon-checked"/></svg>
</label>
</div>
<div class="option">
<label i18n="cm_autocompleteOnTyping">
<label i18n-text="cm_autocompleteOnTyping">
<input id="editor.autocompleteOnTyping" type="checkbox">
<svg class="svg-icon checked"><use xlink:href="#svg-icon-checked"/></svg>
</label>
</div>
<div class="option">
<label i18n="cm_selectByTokens, title:cm_selectByTokensTooltip">
<label i18n-text="cm_selectByTokens"
i18n-title="cm_selectByTokensTooltip">
<input id="editor.selectByTokens" type="checkbox">
<svg class="svg-icon checked"><use xlink:href="#svg-icon-checked"/></svg>
</label>
</div>
<div class="option sectioned-only">
<label i18n="cm_arrowKeysTraverse">
<input id="editor.arrowKeysTraverse" type="checkbox">
<svg class="svg-icon checked"><use xlink:href="#svg-icon-checked"/></svg>
</label>
</div>
<div class="option">
<label i18n="cm_colorpicker">
<label i18n-text="cm_colorpicker">
<input id="editor.colorpicker" type="checkbox">
<svg class="svg-icon checked"><use xlink:href="#svg-icon-checked"/></svg>
</label>
<a id="colorpicker-settings" class="svg-inline-wrapper" i18n="title:shortcutsNote" tabindex="0">
<a id="colorpicker-settings" class="svg-inline-wrapper" i18n-title="shortcutsNote" tabindex="0">
<svg class="svg-icon settings"><use xlink:href="#svg-icon-config"/></svg>
</a>
</div>
<div class="option usercss-only">
<label i18n="appliesLineWidgetLabel, title:appliesLineWidgetWarning">
<label i18n-text="appliesLineWidgetLabel" i18n-title="appliesLineWidgetWarning">
<input id="editor.appliesToLineWidget" type="checkbox">
<svg class="svg-icon checked"><use xlink:href="#svg-icon-checked"/></svg>
</label>
@ -362,11 +349,11 @@
</div>
<div class="options-column">
<div class="option aligned">
<label id="tabSize-label" for="editor.tabSize" i18n="cm_tabSize"></label>
<label id="tabSize-label" for="editor.tabSize" i18n-text="cm_tabSize"></label>
<input id="editor.tabSize" type="number" min="0">
</div>
<div class="option aligned">
<label id="keyMap-label" for="editor.keyMap" i18n="cm_keyMap"></label>
<label id="keyMap-label" for="editor.keyMap" i18n-text="cm_keyMap"></label>
<div class="select-resizer">
<select id="editor.keyMap"></select>
<svg class="svg-icon select-arrow"><use xlink:href="#svg-icon-select-arrow"/></svg>
@ -376,34 +363,34 @@
</a>
</div>
<div class="option aligned">
<label id="theme-label" for="editor.theme" i18n="cm_theme"></label>
<label id="theme-label" for="editor.theme" i18n-text="cm_theme"></label>
<div class="select-resizer">
<select id="editor.theme"></select>
<svg class="svg-icon select-arrow"><use xlink:href="#svg-icon-select-arrow"/></svg>
</div>
</div>
<div class="option aligned">
<label id="highlight-label" for="editor.matchHighlight" i18n="cm_matchHighlight"></label>
<label id="highlight-label" for="editor.matchHighlight" i18n-text="cm_matchHighlight"></label>
<div class="select-resizer">
<select id="editor.matchHighlight">
<option i18n="cm_matchHighlightToken" value="token">
<option i18n="cm_matchHighlightSelection" value="selection">
<option i18n="genericDisabledLabel" value="">
<option i18n-text="cm_matchHighlightToken" value="token">
<option i18n-text="cm_matchHighlightSelection" value="selection">
<option i18n-text="genericDisabledLabel" value="">
</select>
<svg class="svg-icon select-arrow"><use xlink:href="#svg-icon-select-arrow"/></svg>
</div>
</div>
<div class="option aligned">
<label id="linter-label" for="editor.linter" i18n="cm_linter"></label>
<label id="linter-label" for="editor.linter" i18n-text="cm_linter"></label>
<div class="select-resizer">
<select id="editor.linter">
<option value="csslint" selected>CSSLint</option>
<option value="stylelint">Stylelint</option>
<option value="" i18n="genericDisabledLabel"></option>
<option value="" i18n-text="genericDisabledLabel"></option>
</select>
<svg class="svg-icon select-arrow"><use xlink:href="#svg-icon-select-arrow"/></svg>
</div>
<a id="linter-settings" class="svg-inline-wrapper" i18n="title:linterConfigTooltip" tabindex="0">
<a id="linter-settings" class="svg-inline-wrapper" i18n-title="linterConfigTooltip" tabindex="0">
<svg class="svg-icon settings"><use xlink:href="#svg-icon-config"/></svg>
</a>
</div>
@ -411,44 +398,92 @@
</div>
</details>
<details id="publish" data-pref="editor.publish.expanded" class="ignore-pref-if-compact">
<summary><h2 i18n="publish"></h2></summary>
<summary><h2 i18n-text="publish"></h2></summary>
<div>
<a id="usw-url" href="https://userstyles.world" target="_blank">&nbsp;</a>
<div id="usw-link-info">
<dl><dt i18n="styleName"></dt><dd data-usw="name"></dd></dl>
<dl><dt i18n="genericDescription"></dt><dd data-usw="description"></dd></dl>
<dl><dt i18n-text="styleName"></dt><dd data-usw="name"></dd></dl>
<dl><dt i18n-text="genericDescription"></dt><dd data-usw="description"></dd></dl>
</div>
<div>
<button id="usw-publish-style"
i18n="data-publish:publishStyle, data-push:publishPush"></button>
<button id="usw-disconnect" i18n="optionsSyncDisconnect"></button>
i18n-data-publish="publishStyle"
i18n-data-push="publishPush"></button>
<button id="usw-disconnect" i18n-text="optionsSyncDisconnect"></button>
<span id="usw-progress"></span>
</div>
</div>
</details>
<details id="sections-list" data-pref="editor.toc.expanded" class="ignore-pref-if-compact">
<summary><h2 i18n="sections"></h2></summary>
<summary><h2 i18n-text="sections"></h2></summary>
<ol id="toc"></ol>
</details>
<details id="lint" data-pref="editor.lint.expanded" class="ignore-pref-if-compact" hidden>
<details id="lint" data-pref="editor.lint.expanded" class="ignore-pref-if-compact">
<summary>
<h2><span i18n="linterIssues"></span><span id="issue-count"></span>
<h2 i18n-text="linterIssues">: <span id="issue-count"></span>
<a id="lint-help" class="svg-inline-wrapper intercepts-click" tabindex="0">
<svg class="svg-icon info"><use xlink:href="#svg-icon-help"/></svg>
</a>
</h2>
</summary>
<div class="lint-scroll-container">
<div class="lint-report-container"></div>
</div>
</details>
</div>
<div id="header-resizer" i18n="title:headerResizerHint"></div>
<div id="footer" class="hidden">
<div id="footer">
<a href="https://github.com/openstyles/stylus/wiki/Usercss"
i18n="externalUsercssDocument"
i18n-text="externalUsercssDocument"
target="_blank"></a>
</div>
</div>
<section id="sections"></section>
<div class="main tab-container">
<div class="tab-bar">
<div class="tab-bar-item active" i18n-text="editorCodeLabel"></div>
<div class="tab-bar-item" i18n-text="editorSettingLabel"></div>
</div>
<div class="tab-panel">
<section id="sections" class="active"></section>
<fieldset class="style-settings" disabled>
<!-- <label class="style-origin">
<span class="form-label" i18n-text="styleOriginLabel"></span>
<input id="styleOrigin" type="text">
</label> -->
<label class="form-group style-update-url">
<span class="form-label" i18n-text="styleUpdateUrlLabel"></span>
<input type="text">
</label>
<div class="form-group style-prefer-scheme radio-group">
<!-- FIXME: should we use a different message from install page? -->
<span class="form-label" i18n-text="installPreferSchemeLabel"></span>
<label class="radio-item">
<input type="radio" name="preferScheme" value="none">
<span class="radio-label" i18n-text="installPreferSchemeNone"></span>
</label>
<label class="radio-item">
<input type="radio" name="preferScheme" value="dark">
<span class="radio-label" i18n-text="installPreferSchemeDark"></span>
</label>
<label class="radio-item">
<input type="radio" name="preferScheme" value="light">
<span class="radio-label" i18n-text="installPreferSchemeLight"></span>
</label>
</div>
<label class="form-group style-include">
<span class="form-label" i18n-text="styleIncludeLabel"></span>
<textarea spellcheck="false" placeholder="*://site1.com/*&#10;*://site2.com/*"></textarea>
</label>
<label class="form-group style-exclude">
<span class="form-label" i18n-text="styleExcludeLabel"></span>
<textarea spellcheck="false" placeholder="*://site1.com/*&#10;*://site2.com/*"></textarea>
</label>
<!-- <label class="style-always-important">
<input type="checkbox">
<span class="form-label" i18n-text="styleAlwaysImportantLabel"></span>
</label> -->
</fieldset>
</div>
</div>
<div id="help-popup">
<div class="title"></div><svg id="sections-help" class="svg-icon dismiss"><use xlink:href="#svg-icon-close"/></svg>
<div class="contents"></div>
@ -460,10 +495,8 @@
<path d="M0 0v8h8v-2h-1v1h-6v-6h1v-1h-2zm4 0l1.5 1.5-2.5 2.5 1 1 2.5-2.5 1.5 1.5v-4h-4z"></path>
</symbol>
<symbol id="svg-icon-help" viewBox="0 0 14 16" i18n="alt:helpAlt">
<circle cx="7" cy="5" r="1"/>
<path d="M8,8c0-0.5-0.5-1-1-1H6C5.5,7,5,7.4,5,8h1v3c0,0.5,0.5,1,1,1h1c0.5,0,1-0.4,1-1H8V8z"/>
<path d="M7,1c3.9,0,7,3.1,7,7s-3.1,7-7,7s-7-3.1-7-7S3.1,1,7,1z M7,2.3C3.9,2.3,1.3,4.9,1.3,8s2.6,5.7,5.7,5.7s5.7-2.6,5.7-5.7S10.1,2.3,7,2.3C7,2.3,7,2.3,7,2.3z"/>
<symbol id="svg-icon-help" viewBox="0 0 14 16" i18n-alt="helpAlt">
<path fill-rule="evenodd" d="M6.3 5.69a.942.942 0 0 1-.28-.7c0-.28.09-.52.28-.7.19-.18.42-.28.7-.28.28 0 .52.09.7.28.18.19.28.42.28.7 0 .28-.09.52-.28.7a1 1 0 0 1-.7.3c-.28 0-.52-.11-.7-.3zM8 7.99c-.02-.25-.11-.48-.31-.69-.2-.19-.42-.3-.69-.31H6c-.27.02-.48.13-.69.31-.2.2-.3.44-.31.69h1v3c.02.27.11.5.31.69.2.2.42.31.69.31h1c.27 0 .48-.11.69-.31.2-.19.3-.42.31-.69H8V7.98v.01zM7 2.3c-3.14 0-5.7 2.54-5.7 5.68 0 3.14 2.56 5.7 5.7 5.7s5.7-2.55 5.7-5.7c0-3.15-2.56-5.69-5.7-5.69v.01zM7 .98c3.86 0 7 3.14 7 7s-3.14 7-7 7-7-3.12-7-7 3.14-7 7-7z"></path>
</symbol>
<symbol id="svg-icon-close" viewBox="0 0 12 16">
@ -487,21 +520,14 @@
</symbol>
<symbol id="svg-icon-plus" viewBox="0 0 8 8">
<path d="M3 0v3h-3v2h3v3h2v-3h3v-2h-3v-3h-2z"/>
<path fill-rule="evenodd" d="M3 0v3h-3v2h3v3h2v-3h3v-2h-3v-3h-2z"/>
</symbol>
<symbol id="svg-icon-minus" viewBox="0 0 8 8">
<path d="M0 3v2h8v-2h-8z"/>
<path fill-rule="evenodd" d="M0 3v2h8v-2h-8z"/>
</symbol>
</svg>
</template>
<link href="edit/edit.css" rel="stylesheet">
<script src="js/dark-themer.js"></script> <!-- must be last in HEAD to avoid FOUC -->
</head>
<body id="stylus-edit">
<script src="edit/edit.js"></script>
<script src="edit/tab.js"></script>
</body>
</html>

View File

@ -12,7 +12,7 @@
const USO_VAR = 'uso-variable';
const USO_VALID_VAR = 'variable-3 ' + USO_VAR;
const USO_INVALID_VAR = 'error ' + USO_VAR;
const rxPROP = /^(prop(erty)?|variable-2|string-2)\b/;
const rxPROP = /^(prop(erty)?|variable-2)\b/;
const rxVAR = /(^|[^-.\w\u0080-\uFFFF])var\(/iyu;
const rxCONSUME = /([-\w]*\s*:\s?)?/yu;
const cssMime = CodeMirror.mimeModes['text/css'];
@ -41,7 +41,6 @@
const {line, ch} = pos;
const {styles, text} = cm.getLineHandle(line);
const {style, index} = cm.getStyleAtPos({styles, pos: ch}) || {};
const isLessLang = cm.doc.mode.helperType === 'less';
const isStylusLang = cm.doc.mode.name === 'stylus';
const type = style && style.split(' ', 1)[0] || 'prop?';
if (!type || type === 'comment' || type === 'string') {
@ -87,7 +86,6 @@
'@supports',
'@viewport',
];
if (isLessLang) list = findAllCssVars(cm, left, '\\s*:').concat(list);
break;
case '#': // prevents autocomplete for #hex colors
@ -144,16 +142,16 @@
leftLC = leftLC.replace(/^[^\w\s]\s*/, '');
}
if (prop.startsWith('--')) prop = 'color'; // assuming 90% of variables are colors
if (!cssProps) await initCssProps();
list = [...new Set([...cssValues.all[prop] || [], ...cssValues.global])];
if (!cssValues) cssValues = await linterMan.worker.getCssPropsValues();
list = [...new Set([...cssValues.own[prop] || [], ...cssValues.global])];
end = prev + execAt(/(\s*[-a-z(]+)?/y, prev, text)[0].length;
}
}
// properties and media features
if (!list &&
/^(prop(erty|\?)|atom|error|tag)/.test(type) &&
/^(prop(erty|\?)|atom|error)/.test(type) &&
/^(block|atBlock_parens|maybeprop)/.test(getTokenState())) {
if (!cssProps) await initCssProps();
if (!cssProps) initCssProps();
if (type === 'prop?') {
prev += leftLC.length;
leftLC = '';
@ -176,9 +174,8 @@
};
}
async function initCssProps() {
cssValues = await linterMan.worker.getCssPropsValues();
cssProps = addSuffix(cssValues.all);
function initCssProps() {
cssProps = addSuffix(cssMime.propertyKeywords);
cssMedia = [].concat(...Object.entries(cssMime).map(getMediaKeys).filter(Boolean)).sort();
}
@ -198,15 +195,13 @@
!style.startsWith(USO_VALID_VAR) && !style.startsWith(USO_INVALID_VAR);
}
function findAllCssVars(cm, leftPart, rightPart = '') {
function findAllCssVars(cm, leftPart) {
// simplified regex without CSS escapes
const [, prefixed, named] = leftPart.match(/^(--|@)?(\S)?/);
const rx = new RegExp(
'(?:^|[\\s/;{])(' +
(prefixed ? leftPart : '--') +
(named ? '' : '[a-zA-Z_\u0080-\uFFFF]') +
'[-0-9a-zA-Z_\u0080-\uFFFF]*)' +
rightPart,
(leftPart.startsWith('--') ? leftPart : '--') +
(leftPart.length <= 2 ? '[a-zA-Z_\u0080-\uFFFF]' : '') +
'[-0-9a-zA-Z_\u0080-\uFFFF]*)',
'g');
const list = new Set();
cm.eachLine(({text}) => {

View File

@ -1,44 +1,43 @@
/* global $$ $ $create messageBoxProxy setInputValue setupLivePrefs */// dom.js
/* global $ $$ $create setupLivePrefs waitForSelector */// dom.js
/* global API */// msg.js
/* global CODEMIRROR_THEMES */
/* global CodeMirror */
/* global MozDocMapper */// sections-util.js
/* global chromeSync */// storage-util.js
/* global initBeautifyButton */// beautify.js
/* global prefs */
/* global t */// localization.js
/* global FIREFOX getOwnTab sessionStore tryJSONparse tryURL */// toolbox.js
/* global
FIREFOX
debounce
getOwnTab
sessionStore
tryJSONparse
tryURL
*/// toolbox.js
/* global EventEmitter */
'use strict';
/**
* @type Editor
* @namespace Editor
*/
const editor = {
const editor = Object.assign(EventEmitter(), {
style: null,
dirty: DirtyReporter(),
isUsercss: false,
isWindowed: false,
livePreview: LivePreview(),
lazyKeymaps: {
emacs: '/vendor/codemirror/keymap/emacs',
vim: '/vendor/codemirror/keymap/vim',
},
livePreview: null,
/** @type {'customName'|'name'} */
nameTarget: 'name',
previewDelay: 200, // Chrome devtools uses 200
saving: false,
scrollInfo: null,
cancel: () => location.assign('/manage.html'),
updateClass() {
$.rootCL.toggle('is-new-style', !editor.style.id);
},
updateTheme(name) {
if (!CODEMIRROR_THEMES[name]) {
name = 'default';
prefs.set('editor.theme', name);
}
$('#cm-theme').dataset.theme = name;
$('#cm-theme').textContent = CODEMIRROR_THEMES[name] || '';
onStyleUpdated() {
document.documentElement.classList.toggle('is-new-style', !editor.style.id);
},
updateTitle(isDirty = editor.dirty.isDirty()) {
@ -49,24 +48,37 @@ const editor = {
customName || name || t('styleMissingName')
} - Stylus`; // the suffix enables external utilities to process our windows e.g. pin on top
},
};
});
//#region pre-init
(() => {
const mqCompact = matchMedia('(max-width: 850px)');
const toggleCompact = mq => $.rootCL.toggle('compact-layout', mq.matches);
mqCompact.on('change', toggleCompact);
toggleCompact(mqCompact);
Object.assign(editor, /** @namespace Editor */ {
mqCompact,
styleReady: prefs.ready.then(loadStyle),
});
const baseInit = (() => {
const domReady = waitForSelector('#sections');
return {
domReady,
ready: Promise.all([
domReady,
loadStyle(),
prefs.ready.then(() =>
Promise.all([
loadTheme(),
loadKeymaps(),
])),
]),
};
/** Preloads vim/emacs keymap only if it's the active one, otherwise will load later */
function loadKeymaps() {
const km = prefs.get('editor.keyMap');
return /emacs/i.test(km) && require([editor.lazyKeymaps.emacs]) ||
/vim/i.test(km) && require([editor.lazyKeymaps.vim]);
}
async function loadStyle() {
const params = new URLSearchParams(location.search);
let id = Number(params.get('id'));
const id = Number(params.get('id'));
const style = id && await API.styles.get(id) || {
id: id = null, // resetting the non-existent id
name: params.get('domain') ||
tryURL(params.get('url-prefix')).hostname ||
'',
@ -76,37 +88,90 @@ const editor = {
],
};
// switching the mode here to show the correct page ASAP, usually before DOMContentLoaded
const isUC = Boolean(style.usercssData || !id && prefs.get('newStyleAsUsercss'));
Object.assign(editor, /** @namespace Editor */ {
style,
isUsercss: isUC,
template: isUC && !id && chromeSync.getLZValue(chromeSync.LZ_KEY.usercssTemplate), // promise
});
editor.updateClass();
editor.updateTheme(prefs.get('editor.theme'));
editor.isUsercss = Boolean(style.usercssData || !style.id && prefs.get('newStyleAsUsercss'));
editor.style = style;
editor.onStyleUpdated();
editor.updateTitle(false);
$.rootCL.add(isUC ? 'usercss' : 'sectioned');
sessionStore.justEditedStyleId = id || '';
document.documentElement.classList.toggle('usercss', editor.isUsercss);
sessionStore.justEditedStyleId = style.id || '';
// no such style so let's clear the invalid URL parameters
if (!id) history.replaceState({}, '', location.pathname);
if (!style.id) history.replaceState({}, '', location.pathname);
}
/** Preloads the theme so CodeMirror can use the correct metrics in its first render */
async function loadTheme() {
const theme = prefs.get('editor.theme');
if (!CODEMIRROR_THEMES.includes(theme)) {
prefs.set('editor.theme', 'default');
return;
}
if (theme !== 'default') {
const el = $('#cm-theme');
const el2 = await require([`/vendor/codemirror/theme/${theme}.css`]);
el2.id = el.id;
el.remove();
// FF containers take more time to load CSS
for (let retry = 0; !el2.sheet && ++retry <= 10;) {
await new Promise(requestAnimationFrame);
}
}
}
})();
//#endregion
//#region init layout/resize
// baseInit.domReady.then(() => {
// let headerHeight;
// detectLayout(true);
// window.on('resize', () => detectLayout());
// function detectLayout(now) {
// const compact = window.innerWidth <= 850;
// if (compact) {
// document.body.classList.add('compact-layout');
// if (!editor.isUsercss) {
// if (now) fixedHeader();
// else debounce(fixedHeader, 250);
// window.on('scroll', fixedHeader, {passive: true});
// }
// } else {
// document.body.classList.remove('compact-layout', 'fixed-header');
// window.off('scroll', fixedHeader);
// }
// for (const el of $$('details[data-pref]')) {
// el.open = compact ? false : prefs.get(el.dataset.pref);
// }
// }
// function fixedHeader() {
// const headerFixed = $('.fixed-header');
// if (!headerFixed) headerHeight = $('#header').clientHeight;
// const scrollPoint = headerHeight - 43;
// if (window.scrollY >= scrollPoint && !headerFixed) {
// $('body').style.setProperty('--fixed-padding', ` ${headerHeight}px`);
// $('body').classList.add('fixed-header');
// } else if (window.scrollY < scrollPoint && headerFixed) {
// $('body').classList.remove('fixed-header');
// }
// }
// });
//#endregion
//#region init header
/* exported EditorHeader */
function EditorHeader() {
baseInit.ready.then(() => {
initBeautifyButton($('#beautify'));
initKeymapElement();
initNameArea();
initThemeElement();
setupLivePrefs();
window.on('load', () => {
require(Object.values(editor.lazyKeymaps), () => {
initKeymapElement();
prefs.subscribe('editor.keyMap', showHotkeyInTooltip, {runNow: true});
window.on('showHotkeyInTooltip', showHotkeyInTooltip);
}, {once: true});
});
function findKeyForCommand(command, map) {
if (typeof map === 'string') map = CodeMirror.keyMap[map];
@ -132,12 +197,19 @@ function EditorHeader() {
nameEl.title = isCustomName ? t('customNameHint') : '';
nameEl.on('input', () => {
editor.updateName(true);
resetEl.hidden = !editor.style.customName;
resetEl.hidden = false;
});
resetEl.hidden = !editor.style.customName;
resetEl.onclick = () => {
editor.style.customName = null; // to delete it from db
setInputValue(nameEl, editor.style.name);
const {style} = editor;
nameEl.focus();
nameEl.select();
// trying to make it undoable via Ctrl-Z
if (!document.execCommand('insertText', false, style.name)) {
nameEl.value = style.name;
editor.updateName(true);
}
style.customName = null; // to delete it from db
resetEl.hidden = true;
};
const enabledEl = $('#enabled');
@ -147,7 +219,7 @@ function EditorHeader() {
function initThemeElement() {
$('#editor.theme').append(...[
$create('option', {value: 'default'}, t('defaultTheme')),
...Object.keys(CODEMIRROR_THEMES).map(s => $create('option', s)),
...CODEMIRROR_THEMES.map(s => $create('option', s)),
]);
// move the theme after built-in CSS so that its same-specificity selectors win
document.head.appendChild($('#cm-theme'));
@ -203,7 +275,7 @@ function EditorHeader() {
}
}
}
}
});
//#endregion
//#region init windowed mode
@ -220,17 +292,22 @@ function EditorHeader() {
}
}
getOwnTab().then(tab => {
getOwnTab().then(async tab => {
ownTabId = tab.id;
// use browser history back when 'back to manage' is clicked
if (sessionStore['manageStylesHistory' + ownTabId] === location.href) {
editor.cancel = () => history.back();
await baseInit.domReady;
$('#cancel-button').onclick = event => {
event.stopPropagation();
event.preventDefault();
history.back();
};
}
});
async function initWindowedMode() {
chrome.tabs.onAttached.addListener(onTabAttached);
// Chrome 96+ bug: the type is 'app' for a window that was restored via Ctrl-Shift-T
const isSimple = ['app', 'popup'].includes((await browser.windows.getCurrent()).type);
const isSimple = (await browser.windows.getCurrent()).type === 'popup';
if (isSimple) require(['/edit/embedded-popup']);
editor.isWindowed = isSimple || (
history.length === 1 &&
@ -266,15 +343,9 @@ function EditorHeader() {
function DirtyReporter() {
const data = new Map();
const listeners = new Set();
const dataListeners = new Set();
const notifyChange = wasDirty => {
const isDirty = data.size > 0;
const flipped = isDirty !== wasDirty;
if (flipped) {
listeners.forEach(cb => cb(isDirty));
}
if (flipped || isDirty) {
dataListeners.forEach(cb => cb(isDirty));
if (wasDirty !== (data.size > 0)) {
listeners.forEach(cb => cb());
}
};
/** @namespace DirtyReporter */
@ -291,19 +362,17 @@ function DirtyReporter() {
saved.newValue = value;
saved.type = 'modify';
}
} else {
return;
}
notifyChange(wasDirty);
},
clear(...objs) {
if (data.size && (
objs.length
? objs.map(data.delete, data).includes(true)
: (data.clear(), true)
)) {
notifyChange(true);
clear(obj) {
const wasDirty = data.size > 0;
if (obj === undefined) {
data.clear();
} else {
data.delete(obj);
}
notifyChange(wasDirty);
},
has(key) {
return data.has(key);
@ -317,8 +386,6 @@ function DirtyReporter() {
if (!saved) {
if (oldValue !== newValue) {
data.set(obj, {type: 'modify', savedValue: oldValue, newValue});
} else {
return;
}
} else if (saved.type === 'modify') {
if (saved.savedValue === newValue) {
@ -328,17 +395,12 @@ function DirtyReporter() {
}
} else if (saved.type === 'add') {
saved.newValue = newValue;
} else {
return;
}
notifyChange(wasDirty);
},
onChange(cb, add = true) {
listeners[add ? 'add' : 'delete'](cb);
},
onDataChange(cb, add = true) {
dataListeners[add ? 'add' : 'delete'](cb);
},
remove(obj, value) {
const wasDirty = data.size > 0;
const saved = data.get(obj);
@ -348,80 +410,10 @@ function DirtyReporter() {
data.delete(obj);
} else if (saved.type === 'modify') {
saved.type = 'remove';
} else {
return;
}
notifyChange(wasDirty);
},
};
}
function LivePreview() {
let el;
let data;
let port;
let preprocess;
let enabled = prefs.get('editor.livePreview');
prefs.subscribe('editor.livePreview', (key, value) => {
if (!value) {
if (port) {
port.disconnect();
port = null;
}
} else if (data && data.id && (data.enabled || editor.dirty.has('enabled'))) {
createPreviewer();
updatePreviewer(data);
}
enabled = value;
});
return {
/**
* @param {Function} [fn] - preprocessor
*/
init(fn) {
preprocess = fn;
},
update(newData) {
data = newData;
if (!port) {
if (!data.id || !data.enabled || !enabled) {
return;
}
createPreviewer();
}
updatePreviewer(data);
},
};
function createPreviewer() {
port = chrome.runtime.connect({name: 'livePreview'});
port.onDisconnect.addListener(err => {
throw err;
});
el = $('#preview-errors');
el.onclick = () => messageBoxProxy.alert(el.title, 'pre');
}
async function updatePreviewer(data) {
try {
port.postMessage(preprocess ? await preprocess(data) : data);
el.hidden = true;
} catch (err) {
if (Array.isArray(err)) {
err = err.map(e => e.message || e).join('\n');
} else if (err && err.index != null) {
// FIXME: this would fail if editors[0].getValue() !== data.sourceCode
const pos = editor.getEditors()[0].posFromIndex(err.index);
err.message = `${pos.line}:${pos.ch} ${err.message || err}`;
}
el.title = err.message || `${err}`;
el.hidden = false;
}
}
}
//#endregion

View File

@ -65,7 +65,7 @@ function beautifyEditor(cm, options, ui) {
window.scrollTo(scrollX, scrollY);
cm.beautifyChange[cm.changeGeneration()] = true;
if (ui) {
$('button[role="close"]', helpPopup.div).disabled = false;
$('#help-popup button[role="close"]').disabled = false;
}
}
}
@ -82,13 +82,12 @@ function createBeautifyUI(scope, options) {
$createOption('}', 'newline_between_rules'),
$createLabeledCheckbox('preserve_newlines', 'styleBeautifyPreserveNewlines'),
$createLabeledCheckbox('indent_conditional', 'styleBeautifyIndentConditional'),
editor.isUsercss && $createLabeledCheckbox('indent_mozdoc', '', '... @-moz-document'),
]),
$create('p.beautify-hint', [
$create('span', t('styleBeautifyHint') + '\u00A0'),
createHotkeyInput('editor.beautify.hotkey', {
buttons: false,
onDone: () => moveFocus(helpPopup.div, 0),
onDone: () => moveFocus($('#help-popup'), 0),
}),
]),
$create('.buttons', [
@ -114,16 +113,16 @@ function createBeautifyUI(scope, options) {
},
}, t(scope.length === 1 ? 'undo' : 'undoGlobal')),
]),
]),
{
className: 'wide',
});
]));
$('#help-popup').className = 'wide';
$('.beautify-options').onchange = ({target}) => {
const value = target.type === 'checkbox' ? target.checked : target.selectedIndex > 0;
const elLine = target.closest('[newline]');
if (elLine) elLine.setAttribute('newline', value);
prefs.set('editor.beautify', Object.assign(options, {[target.dataset.option]: value}));
if (target.parentNode.hasAttribute('newline')) {
target.parentNode.setAttribute('newline', value.toString());
}
beautify(scope, false);
};
@ -149,7 +148,7 @@ function createBeautifyUI(scope, options) {
);
}
function $createLabeledCheckbox(optionName, i18nKey, text) {
function $createLabeledCheckbox(optionName, i18nKey) {
return (
$create('label', {style: 'display: block; clear: both;'}, [
$create('input', {
@ -159,7 +158,7 @@ function createBeautifyUI(scope, options) {
}),
$create('SVG:svg.svg-icon.checked',
$create('SVG:use', {'xlink:href': '#svg-icon-checked'})),
i18nKey ? t(i18nKey) : text,
t(i18nKey),
])
);
}

View File

@ -4,23 +4,13 @@
z-index: 999;
}
.CodeMirror-hint:hover {
color: var(--bg);
color: white;
background: #08f;
}
.CodeMirror {
border: solid var(--c80) 1px;
border: solid #CCC 1px;
transition: box-shadow .1s;
}
.CodeMirror {
color: inherit;
background-color: inherit;
border: solid var(--c80) 1px;
transition: box-shadow .1s;
}
.CodeMirror-gutters {
background-color: var(--c95);
border-color: var(--c85);
}
#stylus#stylus .CodeMirror {
/* Using a specificity hack to override userstyles */
/* Not using the ring-color hack as it became ugly in new Chrome */
@ -36,7 +26,7 @@
width: 5em;
}
.CodeMirror-search-hint {
color: var(--c50);
color: #888;
}
.CodeMirror-activeline .applies-to:before {
background-color: hsla(214, 100%, 90%, 0.15);
@ -75,10 +65,6 @@
.CodeMirror-linenumber {
cursor: pointer; /* for bookmarking */
}
.cm-matchhighlight,
.CodeMirror-selection-highlight-scrollbar {
background: hsla(200, 100%, 50%, var(--match-hl-opacity, .1));
}
/* Custom stuff we add to CodeMirror */
@ -88,61 +74,3 @@
.gutter-bookmark {
background: linear-gradient(0deg, hsla(180, 100%, 30%, .75) 2px, hsla(180, 100%, 30%, .2) 2px);
}
@media screen and (prefers-color-scheme: dark), dark {
.CodeMirror {
--match-hl-opacity: .18;
}
.CodeMirror-dialog {
background-color: #333;
}
.CodeMirror-dialog-top {
border-color: #555;
}
.CodeMirror-activeline-background {
background: hsl(180, 21%, 18%);
}
.CodeMirror-selected,
.CodeMirror-focused .CodeMirror-selected,
.CodeMirror-line::selection,
.CodeMirror-line > span::selection,
.CodeMirror-line > span > span::selection {
background: #444;
}
.CodeMirror-line::-moz-selection,
.CodeMirror-line > span::-moz-selection,
.CodeMirror-line > span > span::-moz-selection {
/* TODO: remove this when strict_min_version >= 62 */
background: #444;
}
.cm-s-default div.CodeMirror-cursor {
border-left: 1px solid #fff;
}
/* Using Chromium's dark devtools colors */
.cm-s-default .cm-atom,
.cm-s-default .cm-number { color: #a1f7b5 }
.cm-s-default .cm-attribute { color: #6194c6 }
.cm-s-default .cm-bracket { color: #997 }
.cm-s-default .cm-builtin,
.cm-s-default .cm-link { color: #9fb4d6 }
.cm-s-default .cm-comment { color: #747474 }
.cm-s-default .cm-qualifier { color: #ffa34f }
.cm-s-default .cm-def,
.cm-s-default .cm-header,
.cm-s-default .cm-tag,
.cm-s-default .cm-type { color: #5db0d7 }
.cm-s-default .cm-hr { color: #999 }
.cm-s-default .cm-keyword { color: #9a7fd5 }
.cm-s-default .cm-meta { color: #ddfb55 }
.cm-s-default .cm-operator { color: #d2c057 }
.cm-s-default .cm-string { color: #f28b54 }
.cm-s-default .cm-variable { color: #d9d9d9 }
.cm-s-default .cm-variable-2 { color: #72b9ff }
.cm-s-default .cm-variable-3 { color: #9bbbdc }
@keyframes highlight {
from {
background-color: #888;
}
}
}

View File

@ -1,6 +1,5 @@
/* global $ */// dom.js
/* global CodeMirror */
/* global UA */// toolbox.js
/* global editor */
/* global prefs */
/* global t */// localization.js
@ -26,7 +25,7 @@
matchBrackets: true,
hintOptions: {},
lintReportDelay: prefs.get('editor.lintReportDelay'),
styleActiveLine: {nonEmpty: true},
styleActiveLine: true,
theme: prefs.get('editor.theme'),
keyMap: prefs.get('editor.keyMap'),
extraKeys: Object.assign(CodeMirror.defaults.extraKeys || {}, {
@ -42,7 +41,7 @@
Object.assign(CodeMirror.defaults, defaults, prefs.get('editor.options'));
// Adding hotkeys to some keymaps except 'basic' which is primitive by design
{
require(Object.values(typeof editor === 'object' && editor.lazyKeymaps || {}), () => {
const KM = CodeMirror.keyMap;
const extras = Object.values(CodeMirror.defaults.extraKeys);
if (!extras.includes('jumpToLine')) {
@ -63,7 +62,7 @@
if (!extras.includes('blockComment')) {
KM.sublime['Shift-Ctrl-/'] = 'commentSelection';
}
if (UA.windows) {
if (navigator.appVersion.includes('Windows')) {
// 'pcDefault' keymap on Windows should have F3/Shift-F3/Ctrl-R
if (!extras.includes('findNext')) KM.pcDefault['F3'] = 'findNext';
if (!extras.includes('findPrev')) KM.pcDefault['Shift-F3'] = 'findPrev';
@ -90,7 +89,7 @@
}
}
}
}
});
Object.assign(CodeMirror.prototype, {
/**
@ -102,7 +101,6 @@
const m = this.doc.mode;
if (force || (m.helperType ? m.helperType !== pp : m.name !== name)) {
this.setOption('mode', name);
this.doc.mode.lineComment = ''; // stylelint chokes on line comments a lot
}
},
/** Superfast GC-friendly check that runs until the first non-space line */

View File

@ -1,3 +1,4 @@
/* global $ */// dom.js
/* global CodeMirror */
/* global editor */
/* global prefs */
@ -31,7 +32,7 @@
globalSetOption(key, value) {
CodeMirror.defaults[key] = value;
if (cms.size > 4 && lazyOpt.names.includes(key)) {
if (cms.size > 4 && lazyOpt && lazyOpt.names.includes(key)) {
lazyOpt.set(key, value);
} else {
cms.forEach(cm => cm.setOption(key, value));
@ -47,10 +48,8 @@
cm.lastActive = Date.now();
};
const onCmBlur = cm => {
setTimeout(() => {
/* Delaying to next tick to avoid double-processing of the currently processed keyboard event
* when it bubbles up from CodeMirror to `document` where the rerouter listens */
rerouteHotkeys.toggle(true);
setTimeout(() => {
const {wrapper} = cm.display;
wrapper.classList.toggle('CodeMirror-active', wrapper.contains(document.activeElement));
});
@ -67,7 +66,6 @@
k.slice('editor.'.length);
const prefKeys = prefs.knownKeys.filter(k =>
k !== 'editor.colorpicker' && // handled in colorpicker-helper.js
k !== 'editor.arrowKeysTraverse' && // handled in sections-editor.js
prefToCmOpt(k) in CodeMirror.defaults);
const {insertTab, insertSoftTab} = CodeMirror.commands;
@ -83,7 +81,6 @@
const opt = (showToken || value === 'selection') && {
showToken,
annotateScrollbar: true,
delay: 0,
onUpdate: updateMatchHighlightCount,
};
cm.setOption('highlightSelectionMatches', opt || null);
@ -97,13 +94,17 @@
}
prefs.subscribe(prefKeys, (key, val) => {
if (key === 'editor.theme') editor.updateTheme(val);
cmFactory.globalSetOption(prefToCmOpt(key), val);
const name = prefToCmOpt(key);
if (name === 'theme') {
loadCmTheme(val);
} else {
cmFactory.globalSetOption(name, val);
}
});
// lazy propagation
lazyOpt = {
lazyOpt = window.IntersectionObserver && {
names: ['theme', 'lineWrapping'],
set(key, value) {
const {observer, queue} = lazyOpt;
@ -177,6 +178,25 @@
//#endregion
//#region CM option handlers
async function loadCmTheme(name) {
let el2;
const el = $('#cm-theme');
if (name === 'default') {
el.href = '';
} else {
const path = `/vendor/codemirror/theme/${name}.css`;
if (el.href !== location.origin + path) {
// avoid flicker: wait for the second stylesheet to load, then apply the theme
el2 = await require([path]);
}
}
cmFactory.globalSetOption('theme', name);
if (el2) {
el.remove();
el2.id = el.id;
}
}
function updateMatchHighlightCount(cm, state) {
cm.display.wrapper.dataset.matchHighlightCount = state.matchesonscroll.matches.length;
}

File diff suppressed because one or more lines are too long

View File

@ -1,68 +0,0 @@
/* global messageBoxProxy */// dom.js
/* global API */// msg.js
/* global clamp debounce */// toolbox.js
/* global editor */
/* global prefs */
/* global t */// localization.js
'use strict';
(async function AutosaveDrafts() {
const makeId = () => editor.style.id || 'new';
let delay;
let port;
connectPort();
const draft = await API.drafts.get(makeId());
if (draft && draft.isUsercss === editor.isUsercss) {
const date = makeRelativeDate(draft.date);
if (await messageBoxProxy.confirm(t('draftAction'), 'danger', t('draftTitle', date))) {
await editor.replaceStyle(draft.style, draft);
} else {
API.drafts.delete(makeId());
}
}
editor.dirty.onChange(isDirty => isDirty ? connectPort() : port.disconnect());
editor.dirty.onDataChange(isDirty => debounce(updateDraft, isDirty ? delay : 0));
prefs.subscribe('editor.autosaveDraft', (key, val) => {
delay = clamp(val * 1000 | 0, 1000, 2 ** 32 - 1);
const t = debounce.timers.get(updateDraft);
if (t != null) debounce(updateDraft, t ? delay : 0);
}, {runNow: true});
function connectPort() {
port = chrome.runtime.connect({name: 'draft:' + makeId()});
}
function makeRelativeDate(date) {
let delta = (Date.now() - date) / 1000;
if (delta >= 0 && Intl.RelativeTimeFormat) {
for (const [span, unit, frac = 1] of [
[60, 'second', 0],
[60, 'minute', 0],
[24, 'hour'],
[7, 'day'],
[4, 'week'],
[12, 'month'],
[1e99, 'year'],
]) {
if (delta < span) {
return new Intl.RelativeTimeFormat({style: 'short'}).format(-delta.toFixed(frac), unit);
}
delta /= span;
}
}
return date.toLocaleString();
}
function updateDraft(isDirty = editor.dirty.isDirty()) {
if (!isDirty) return;
API.drafts.put({
date: Date.now(),
isUsercss: editor.isUsercss,
style: editor.getValue(true),
si: editor.makeScrollInfo(),
}, makeId());
}
})();

File diff suppressed because it is too large Load Diff

View File

@ -1,34 +1,37 @@
/* global $$ $ $create */// dom.js
/* global API msg */// msg.js
/* global $ $create messageBoxProxy waitForSheet */// dom.js
/* global msg API */// msg.js
/* global CodeMirror */
/* global SectionsEditor */
/* global SourceEditor */
/* global baseInit */
/* global clipString createHotkeyInput helpPopup */// util.js
/* global closeCurrentTab deepEqual mapObj sessionStore tryJSONparse */// toolbox.js
/* global closeCurrentTab deepEqual sessionStore tryJSONparse */// toolbox.js
/* global cmFactory */
/* global editor EditorHeader */// base.js
/* global editor */
/* global linterMan */
/* global prefs */
/* global t */// localization.js
/* global StyleSettings */// settings.js
'use strict';
//#region init
document.body.appendChild(t.template.body);
EditorMethods();
editor.styleReady.then(async () => {
EditorHeader();
dispatchEvent(new Event('domReady'));
await (editor.isUsercss ? SourceEditor : SectionsEditor)();
baseInit.ready.then(async () => {
await waitForSheet();
(editor.isUsercss ? SourceEditor : SectionsEditor)();
StyleSettings(editor);
await editor.ready;
editor.ready = true;
editor.dirty.onChange(editor.updateDirty);
prefs.subscribe('editor.linter', () => linterMan.run());
prefs.subscribe('editor.linter', (key, value) => {
document.body.classList.toggle('linter-disabled', value === '');
linterMan.run();
});
// enabling after init to prevent flash of validation failure on an empty name
$('#name').required = !editor.isUsercss;
$('#save-button').onclick = editor.save;
$('#cancel-button').onclick = editor.cancel;
const elSec = $('#sections-list');
// editor.toc.expanded pref isn't saved in compact-layout so prefs.subscribe won't work
@ -45,65 +48,36 @@ editor.styleReady.then(async () => {
require(['/edit/linter-dialogs'], () => linterMan.showLintConfig());
$('#lint-help').onclick = () =>
require(['/edit/linter-dialogs'], () => linterMan.showLintHelp());
$('#style-settings-btn').onclick = () => require([
'/edit/settings.css',
'/edit/settings', /* global StyleSettings */
], () => StyleSettings());
require([
'/edit/autocomplete',
'/edit/drafts',
'/edit/global-search',
]);
});
editor.styleReady.then(async () => {
// Set up mini-header on scroll
const {isUsercss} = editor;
const el = $create({
style: `
top: 0;
height: 1px;
position: absolute;
visibility: hidden;
`.replace(/;/g, '!important;'),
});
const scroller = isUsercss ? $('.CodeMirror-scroll') : document.body;
const xoRoot = isUsercss ? scroller : undefined;
const xo = new IntersectionObserver(onScrolled, {root: xoRoot});
scroller.appendChild(el);
onCompactToggled(editor.mqCompact);
editor.mqCompact.on('change', onCompactToggled);
/** @param {MediaQueryList} mq */
function onCompactToggled(mq) {
for (const el of $$('details[data-pref]')) {
el.open = mq.matches ? false : prefs.get(el.dataset.pref);
}
if (mq.matches) {
xo.observe(el);
} else {
xo.disconnect();
}
}
/** @param {IntersectionObserverEntry[]} entries */
function onScrolled(entries) {
const h = $('#header');
const sticky = !entries.pop().isIntersecting;
if (!isUsercss) scroller.style.paddingTop = sticky ? h.offsetHeight + 'px' : '';
h.classList.toggle('sticky', sticky);
}
});
//#endregion
//#region events
const IGNORE_UPDATE_REASONS = [
'editPreview',
'editPreviewEnd',
'editSave',
// https://github.com/openstyles/stylus/issues/807 is closed without fix
// 'config,
];
msg.onExtension(request => {
const {style} = request;
switch (request.method) {
case 'styleUpdated':
if (editor.style.id === style.id) {
handleExternalUpdate(request);
if (editor.style.id === style.id && !IGNORE_UPDATE_REASONS.includes(request.reason)) {
if (request.reason === 'toggle') {
editor.emit('styleToggled', request.style);
} else {
API.styles.get(request.style.id)
.then(style => {
editor.emit('styleChange', style, request.reason);
});
}
}
break;
case 'styleDeleted':
@ -111,47 +85,17 @@ msg.onExtension(request => {
closeCurrentTab();
}
break;
case 'editDeleteText':
document.execCommand('delete');
break;
}
});
async function handleExternalUpdate({style, reason}) {
if (reason === 'editPreview' ||
reason === 'editPreviewEnd') {
return;
}
if (reason === 'editSave' && editor.saving) {
editor.saving = false;
return;
}
if (reason === 'toggle') {
if (editor.dirty.isDirty()) {
editor.toggleStyle(style.enabled);
} else {
Object.assign(editor.style, style);
}
editor.updateMeta();
editor.updateLivePreview();
return;
}
style = await API.styles.get(style.id);
if (reason === 'config') {
delete style.sourceCode;
delete style.sections;
delete style.name;
delete style.enabled;
Object.assign(editor.style, style);
} else {
await editor.replaceStyle(style);
}
window.dispatchEvent(new Event('styleSettings'));
}
window.on('beforeunload', e => {
let pos;
if (editor.isWindowed &&
document.visibilityState === 'visible' &&
prefs.get('openEditInWindow') &&
screenX !== -32000 && // Chrome uses this value for minimized windows
( // only if not maximized
screenX > 0 || outerWidth < screen.availWidth ||
screenY > 0 || outerHeight < screen.availHeight ||
@ -168,7 +112,16 @@ window.on('beforeunload', e => {
prefs.set('windowPosition', pos);
}
sessionStore.windowPos = JSON.stringify(pos || {});
sessionStore['editorScrollInfo' + editor.style.id] = JSON.stringify(editor.makeScrollInfo());
sessionStore['editorScrollInfo' + editor.style.id] = JSON.stringify({
scrollY: window.scrollY,
cms: editor.getEditors().map(cm => /** @namespace EditorScrollInfo */({
bookmarks: (cm.state.sublimeBookmarks || []).map(b => b.find()),
focus: cm.hasFocus(),
height: cm.display.wrapper.style.height.replace('100vh', ''),
parentHeight: cm.display.wrapper.parentElement.offsetHeight,
sel: cm.isClean() && [cm.doc.sel.ranges, cm.doc.sel.primIndex],
})),
});
const activeElement = document.activeElement;
if (activeElement) {
// blurring triggers 'change' or 'input' event if needed
@ -185,7 +138,7 @@ window.on('beforeunload', e => {
//#endregion
//#region editor methods
function EditorMethods() {
(() => {
const toc = [];
const {dirty} = editor;
let {style} = editor;
@ -207,35 +160,15 @@ function EditorMethods() {
applyScrollInfo(cm, si = (editor.scrollInfo.cms || [])[0]) {
if (si && si.sel) {
const bmOpts = {sublimeBookmark: true, clearWhenEmpty: false}; // copied from sublime.js
cm.operation(() => {
cm.setSelections(...si.sel, {scroll: false});
cm.scrollIntoView(cm.getCursor(), si.parentHeight / 2);
cm.state.sublimeBookmarks = si.bookmarks.map(b => cm.markText(b.from, b.to, bmOpts));
Object.assign(cm.display.scroller, si.scroll); // for source editor
Object.assign(cm.doc, si.scroll); // for sectioned editor
});
}
},
makeScrollInfo() {
return {
scrollY: window.scrollY,
cms: editor.getEditors().map(cm => /** @namespace EditorScrollInfo */({
bookmarks: (cm.state.sublimeBookmarks || []).map(b => b.find()),
focus: cm.hasFocus(),
height: cm.display.wrapper.style.height.replace('100vh', ''),
parentHeight: cm.display.wrapper.parentElement.offsetHeight,
scroll: mapObj(cm.doc, null, ['scrollLeft', 'scrollTop']),
sel: [cm.doc.sel.ranges, cm.doc.sel.primIndex],
})),
};
},
async save() {
if (dirty.isDirty()) {
editor.saving = true;
await editor.saveImpl();
}
},
toggleStyle(enabled = !style.enabled) {
toggleStyle(enabled = style.enabled) {
$('#enabled').checked = enabled;
editor.updateEnabledness(enabled);
},
@ -303,18 +236,79 @@ function EditorMethods() {
el.classList.add(cls);
}
},
useSavedStyle(newStyle) {
if (style.id !== newStyle.id) {
history.replaceState({}, '', `?id=${newStyle.id}`);
}
sessionStore.justEditedStyleId = newStyle.id;
Object.assign(style, newStyle);
editor.updateClass();
editor.updateMeta();
},
});
}
})();
//#endregion
//#region editor livePreview
editor.livePreview = (() => {
let data;
let port;
let preprocess;
let enabled = prefs.get('editor.livePreview');
prefs.subscribe('editor.livePreview', (key, value) => {
if (!value) {
if (port) {
port.disconnect();
port = null;
}
} else if (data && data.id && (data.enabled || editor.dirty.has('enabled'))) {
createPreviewer();
updatePreviewer(data);
}
enabled = value;
});
return {
/**
* @param {Function} [fn] - preprocessor
*/
init(fn) {
preprocess = fn;
},
update(newData) {
data = newData;
if (!port) {
if (!data.id || !data.enabled || !enabled) {
return;
}
createPreviewer();
}
updatePreviewer(data);
},
};
function createPreviewer() {
port = chrome.runtime.connect({name: 'livePreview'});
port.onDisconnect.addListener(err => {
throw err;
});
}
async function updatePreviewer(data) {
const errorContainer = $('#preview-errors');
try {
port.postMessage(preprocess ? await preprocess(data) : data);
errorContainer.classList.add('hidden');
} catch (err) {
if (Array.isArray(err)) {
err = err.join('\n');
} else if (err && err.index != null) {
// FIXME: this would fail if editors[0].getValue() !== data.sourceCode
const pos = editor.getEditors()[0].posFromIndex(err.index);
err.message = `${pos.line}:${pos.ch} ${err.message || err}`;
}
errorContainer.classList.remove('hidden');
errorContainer.onclick = () => {
messageBoxProxy.alert(err.message || `${err}`, 'pre');
};
}
}
})();
//#endregion
//#region colorpickerHelper
@ -372,6 +366,8 @@ function EditorMethods() {
cmFactory.globalSetOption('colorpicker', defaults.colorpicker);
}, {runNow: true});
await baseInit.domReady;
$('#colorpicker-settings').onclick = function (event) {
event.preventDefault();
const input = createHotkeyInput('editor.colorpicker.hotkey', {onDone: () => helpPopup.close()});

View File

@ -2,7 +2,9 @@
'use strict';
(() => {
let sugarss = false;
const hasCurlyBraceError = warning =>
warning.text === 'Unnecessary curly bracket (CssSyntaxError)';
let sugarssFallback;
/** @namespace EditorWorker */
createWorkerApi({
@ -26,7 +28,6 @@
// moving vendor-prefixed props to the end
const cmp = (a, b) => a[0] === '-' && b[0] !== '-' ? 1 : a < b ? -1 : a > b;
for (const [k, v] of Object.entries(Properties)) {
res[k] = false;
if (typeof v === 'string') {
let last = '';
const uniq = [];
@ -43,7 +44,7 @@
if (uniq.length) res[k] = uniq;
}
}
return {all: res, global: GlobalKeywords};
return {own: res, global: GlobalKeywords};
},
getRules(linter) {
@ -65,32 +66,23 @@
async stylelint(opts) {
require(['/vendor/stylelint-bundle/stylelint-bundle.min']); /* global stylelint */
// Stylus-lang allows a trailing ";" but sugarss doesn't, so we monkeypatch it
stylelint.SugarSSParser.prototype.checkSemicolon = tt => {
while (tt.length && tt[tt.length - 1][0] === ';') tt.pop();
};
for (const pass of opts.mode === 'stylus' ? [sugarss, !sugarss] : [-1]) {
/* We try sugarss (for indented stylus-lang), then css mode, switching them on failure,
* so that the succeeding syntax will be used next time first. */
opts.config.customSyntax = !pass ? 'sugarss' : '';
try {
const res = await stylelint.createLinter(opts)._lintSource(opts);
if (pass !== -1) sugarss = pass;
return collectStylelintResults(res, opts);
let res;
let pass = 0;
/* sugarss is used for stylus-lang by default,
but it fails on normal css syntax so we retry in css mode. */
const isSugarSS = opts.syntax === 'sugarss';
if (sugarssFallback && isSugarSS) opts.syntax = sugarssFallback;
while (
++pass <= 2 &&
(res = (await stylelint.lint(opts)).results[0]) &&
isSugarSS && res.warnings.some(hasCurlyBraceError)
) sugarssFallback = opts.syntax = 'css';
delete res._postcssResult; // huge and unused
return res;
} catch (e) {
const fatal = pass === -1 ||
!pass && !/^CssSyntaxError:.+?Unnecessary curly bracket/.test(e) ||
pass && !/^CssSyntaxError:.+?Unknown word[\s\S]*?\.decl\s/.test(`${e}${e.stack}`);
if (fatal) {
return [{
from: {line: e.line - 1, ch: e.column - 1},
to: {line: e.line - 1, ch: e.column - 1},
message: e.reason,
severity: 'error',
rule: e.name,
}];
}
}
delete e.postcssNode; // huge, unused, non-transferable
throw e;
}
},
});
@ -145,32 +137,4 @@
return options;
},
};
function collectStylelintResults({messages}, {mode}) {
/* We hide nonfatal "//" warnings since we lint with sugarss without applying @preprocessor.
* We can't easily pre-remove "//" comments which may be inside strings, comments, url(), etc.
* And even if we did, it'd be wrong to hide potential bugs in stylus-lang like #1460 */
const isLess = mode === 'text/x-less';
const slashCommentAllowed = isLess || mode === 'stylus';
const res = [];
for (const m of messages) {
if (/deprecation|invalidOption/.test(m.stylelintType)) {
continue;
}
const {rule} = m;
const msg = m.text.replace(/^Unexpected\s+/, '').replace(` (${rule})`, '');
if (slashCommentAllowed && msg.includes('"//"') ||
isLess && /^unknown at-rule "@[-\w]+:"/.test(msg) /* LESS variables */) {
continue;
}
res.push({
from: {line: m.line - 1, ch: m.column - 1},
to: {line: m.endLine - 1, ch: m.endColumn - 1},
message: msg[0].toUpperCase() + msg.slice(1),
severity: m.severity,
rule,
});
}
return res;
}
})();

View File

@ -1,5 +1,6 @@
/* global $ $create $remove getEventKeyName */// dom.js
/* global CodeMirror */
/* global baseInit */// base.js
/* global prefs */
/* global t */// localization.js
'use strict';
@ -19,13 +20,12 @@
title: t('optionsCustomizePopup') + '\n' + POPUP_HOTKEY,
onclick: embedPopup,
});
$.root.appendChild(btn);
$.rootCL.add('popup-window');
window.on('domReady', () => {
document.documentElement.appendChild(btn);
baseInit.domReady.then(() => {
document.body.appendChild(btn);
// Adding a dummy command to show in keymap help popup
CodeMirror.defaults.extraKeys[POPUP_HOTKEY] = 'openStylusPopup';
}, {once: true});
});
prefs.subscribe('iconset', (_, val) => {
const prefix = `images/icon/${val ? 'light/' : ''}`;
@ -60,9 +60,14 @@
const body = pw.document.body;
pw.on('keydown', removePopupOnEsc);
pw.close = removePopup;
if (pw.IntersectionObserver) {
new pw.IntersectionObserver(onIntersect).observe(body.appendChild(
$create('div', {style: {height: '1px', marginTop: '-1px'}})
));
} else {
frame.dataset.loaded = '';
frame.height = body.scrollHeight;
}
new pw.MutationObserver(onMutation).observe(body, {
attributes: true,
attributeFilter: ['style'],

View File

@ -1,4 +1,4 @@
/* global $ $$ $create $remove focusAccessibility setInputValue toggleDataset */// dom.js
/* global $ $$ $create $remove focusAccessibility toggleDataset */// dom.js
/* global CodeMirror */
/* global chromeLocal */// storage-util.js
/* global colorMimicry */
@ -54,7 +54,7 @@
undoHistory: [],
searchInApplies: !editor.isUsercss,
searchInApplies: !document.documentElement.classList.contains('usercss'),
};
//endregion
@ -588,7 +588,7 @@
input: colorMimicry($('input:not(:disabled)'), {bg: 'backgroundColor'}),
icon: colorMimicry($$('svg.info')[1], {fill: 'fill'}),
};
$.root.appendChild(
document.documentElement.appendChild(
$(DIALOG_STYLE_SELECTOR) ||
$create('style' + DIALOG_STYLE_SELECTOR)
).textContent = `
@ -607,10 +607,10 @@
}
#search-replace-dialog[data-type="replace"] button:hover svg,
#search-replace-dialog svg:hover {
fill: var(--cmin);
fill: inherit;
}
#search-replace-dialog [data-action="case"]:hover {
color: var(--cmin);
color: inherit;
}
#search-replace-dialog [data-action="clear"] {
background-color: ${colors.input.bg.replace(/[^,]+$/, '') + '.75)'};
@ -930,5 +930,18 @@
})));
}
function setInputValue(input, value) {
input.focus();
input.select();
// using execCommand to add to the input's undo history
document.execCommand(value ? 'insertText' : 'delete', false, value);
// some versions of Firefox ignore execCommand
if (input.value !== value) {
input.value = value;
input.dispatchEvent(new Event('input', {bubbles: true}));
}
}
//endregion
})();

View File

@ -202,7 +202,35 @@ linterMan.DEFAULTS = {
getConfig: config => ({
rules: Object.assign({}, DEFAULTS.stylelint.rules, config && config.rules),
}),
lint: (code, config, mode) => worker.stylelint({code, config, mode}),
async lint(code, config, mode) {
const isLess = mode === 'text/x-less';
const isStylus = mode === 'stylus';
const syntax = isLess ? 'less' : isStylus ? 'sugarss' : 'css';
const raw = await worker.stylelint({code, config, syntax});
if (!raw) {
return [];
}
// Hiding the errors about "//" comments as we're preprocessing only when saving/applying
// and we can't just pre-remove the comments since "//" may be inside a string token
const slashCommentAllowed = isLess || isStylus;
const res = [];
for (const w of raw.warnings) {
const msg = w.text.match(/^(?:Unexpected\s+)?(.*?)\s*\([^()]+\)$|$/)[1] || w.text;
if (!slashCommentAllowed || !(
w.rule === 'no-invalid-double-slash-comments' ||
w.rule === 'property-no-unknown' && msg.includes('"//"')
)) {
res.push({
from: {line: w.line - 1, ch: w.column - 1},
to: {line: w.line - 1, ch: w.column},
message: msg.slice(0, 1).toUpperCase() + msg.slice(1),
severity: w.severity,
rule: w.rule,
});
}
}
return res;
},
},
};
@ -287,7 +315,7 @@ linterMan.DEFAULTS = {
function updateCount() {
const issueCount = Array.from(tables.values())
.reduce((sum, table) => sum + table.trs.length, 0);
$('#lint').hidden = !issueCount;
$('#lint').classList.toggle('hidden', issueCount === 0);
$('#issue-count').textContent = issueCount;
}
@ -303,20 +331,19 @@ linterMan.DEFAULTS = {
}
function createTable(cm) {
const caption = $create('.caption');
const table = $create('table');
const report = $create('.report', [caption, table]);
const caption = $create('caption');
const tbody = $create('tbody');
const table = $create('table', [caption, tbody]);
const trs = [];
return {
element: report,
element: table,
trs,
updateAnnotations,
updateCaption,
};
function updateCaption() {
const t = editor.getEditorTitle(cm);
Object.assign(caption, typeof t == 'string' ? {textContent: t} : t);
caption.textContent = editor.getEditorTitle(cm);
}
function updateAnnotations(lines) {
@ -328,20 +355,20 @@ linterMan.DEFAULTS = {
} else {
tr = createTr();
trs.push(tr);
table.appendChild(tr.element);
tbody.append(tr.element);
}
tr.update(anno);
i++;
}
if (i === 0) {
trs.length = 0;
table.textContent = '';
tbody.textContent = '';
} else {
while (trs.length > i) {
trs.pop().element.remove();
}
}
report.classList.toggle('empty', !trs.length);
table.classList.toggle('empty', trs.length === 0);
function *getAnnotations() {
for (const line of lines.filter(Boolean)) {

View File

@ -4,7 +4,9 @@
/* global colorMimicry */
/* global editor */
/* global msg */
/* global prefs */
/* global t */// localization.js
/* global tryCatch */// toolbox.js
'use strict';
/* exported MozSectionWidget */
@ -38,12 +40,11 @@ function MozSectionWidget(cm, finder = MozSectionFinder(cm)) {
};
function init() {
const hint = {title: t('appliesHelp')};
enabled = true;
TPL = {
container:
$create('div' + C_CONTAINER, [
$create(C_LABEL, hint, t('appliesLabel')),
$create(C_LABEL, t('appliesLabel')),
$create('ul' + C_LIST),
]),
listItem:
@ -52,9 +53,8 @@ function MozSectionWidget(cm, finder = MozSectionFinder(cm)) {
$create('li.applies-to-everything', t('appliesToEverything')),
};
Object.assign($(C_TYPE, TPL.listItem), hint);
$(C_VALUE, TPL.listItem).after(
$create('button.test-regexp', t('genericTest')));
$create('button.test-regexp', t('styleRegexpTestButton')));
CLICK_ROUTE = {
'.test-regexp': showRegExpTester,
@ -156,7 +156,6 @@ function MozSectionWidget(cm, finder = MozSectionFinder(cm)) {
}
if (msg.style || msg.styles ||
msg.prefs && 'disableAll' in msg.prefs ||
msg.method === 'colorScheme' ||
msg.method === 'styleDeleted') {
requestAnimationFrame(updateWidgetStyle);
}
@ -164,6 +163,11 @@ function MozSectionWidget(cm, finder = MozSectionFinder(cm)) {
function updateWidgetStyle() {
funcHeight = 0;
if (prefs.get('editor.theme') !== 'default' &&
!tryCatch(() => $('#cm-theme').sheet.cssRules)) {
requestAnimationFrame(updateWidgetStyle);
return;
}
const MIN_LUMA = .05;
const MIN_LUMA_DIFF = .4;
const color = {
@ -199,6 +203,7 @@ function MozSectionWidget(cm, finder = MozSectionFinder(cm)) {
color: ${fore};
}
${C_CONTAINER} input,
${C_CONTAINER} button,
${C_CONTAINER} select {
background: rgba(255, 255, 255, ${
Math.max(MIN_LUMA, Math.pow(Math.max(0, color.gutter.bgLuma - MIN_LUMA * 2), 2)).toFixed(2)
@ -212,7 +217,7 @@ function MozSectionWidget(cm, finder = MozSectionFinder(cm)) {
transition: none;
}
`;
$.root.appendChild(actualStyle);
document.documentElement.appendChild(actualStyle);
}
/**
@ -269,14 +274,13 @@ function MozSectionWidget(cm, finder = MozSectionFinder(cm)) {
let widget = old && old.widget;
const height = Math.round(funcHeight * (sec.funcs.length || 1)) || undefined;
const node = renderContainer(sec, widget);
if (widget && widget.line.lineNo() === sec.start.line) {
if (widget) {
widget.node = node;
if (height && height !== widget.height) {
widget.height = height;
widget.changed();
}
} else {
if (widget) widget.clear();
widget = cm.addLineWidget(sec.start.line, node, {
coverGutter: true,
noHScroll: true,

View File

@ -5,6 +5,7 @@
'use strict';
const regexpTester = (() => {
const GET_FAVICON_URL = 'https://www.google.com/s2/favicons?domain=';
const OWN_ICON = chrome.runtime.getManifest().icons['16'];
const cachedRegexps = new Map();
let currentRegexps = [];
@ -92,7 +93,7 @@ const regexpTester = (() => {
for (const [url, match] of urls.entries()) {
const faviconUrl = url.startsWith(URLS.ownOrigin)
? OWN_ICON
: URLS.favicon(new URL(url).hostname);
: GET_FAVICON_URL + new URL(url).hostname;
const icon = $create('img', {src: faviconUrl});
if (match.text.length === url.length) {
full.push($create('a', {tabIndex: 0}, [

View File

@ -1,4 +1,4 @@
/* global $ toggleDataset */// dom.js
/* global $ */// dom.js
/* global MozDocMapper trimCommentLabel */// util.js
/* global cmFactory */
/* global debounce tryRegExp */// toolbox.js
@ -23,7 +23,7 @@ function createSection(originalSection, genId, si) {
const elLabel = $('.code-label', el);
const cm = cmFactory.create(wrapper => {
// making it tall during initial load so IntersectionObserver sees only one adjacent CM
if (editor.loading) {
if (editor.ready !== true) {
wrapper.style.height = si ? si.height : '100vh';
}
elLabel.after(wrapper);
@ -31,13 +31,11 @@ function createSection(originalSection, genId, si) {
value: originalSection.code,
});
el.CodeMirror = cm; // used by getAssociatedEditor
cm.el = el;
editor.applyScrollInfo(cm, si);
const changeListeners = new Set();
const appliesToContainer = $('.applies-to', el);
const appliesToList = $('.applies-to-list', el);
const appliesToContainer = $('.applies-to-list', el);
const appliesTo = [];
MozDocMapper.forEachProp(originalSection, (type, value) =>
insertApplyAfter({type, value}));
@ -115,21 +113,66 @@ function createSection(originalSection, genId, si) {
changeGeneration = newGeneration;
emitSectionChange('code');
});
cm.display.wrapper.on('keydown', event => handleKeydown(cm, event), true);
$('.test-regexp', el).onclick = () => updateRegexpTester(true);
initBeautifyButton($('.beautify-section', el), [cm]);
}
function handleKeydown(cm, event) {
if (event.shiftKey || event.altKey || event.metaKey) {
return;
}
const {key} = event;
const {line, ch} = cm.getCursor();
switch (key) {
case 'ArrowLeft':
if (line || ch) {
return;
}
// fallthrough
case 'ArrowUp':
cm = line === 0 && editor.prevEditor(cm, false);
if (!cm) {
return;
}
event.preventDefault();
event.stopPropagation();
cm.setCursor(cm.doc.size - 1, key === 'ArrowLeft' ? 1e20 : ch);
break;
case 'ArrowRight':
if (line < cm.doc.size - 1 || ch < cm.getLine(line).length - 1) {
return;
}
// fallthrough
case 'ArrowDown':
cm = line === cm.doc.size - 1 && editor.nextEditor(cm, false);
if (!cm) {
return;
}
event.preventDefault();
event.stopPropagation();
cm.setCursor(0, 0);
break;
}
}
async function updateRegexpTester(toggle) {
const isLoaded = typeof regexpTester === 'object' ||
toggle && await require(['/edit/regexp-tester']); /* global regexpTester */
if (toggle != null) {
const isLoaded = typeof regexpTester === 'object';
if (toggle && !isLoaded) {
await require(['/edit/regexp-tester']); /* global regexpTester */
}
if (toggle != null && isLoaded) {
regexpTester.toggle(toggle);
}
const regexps = appliesTo.filter(a => a.type === 'regexp')
.map(a => a.value);
const hasRe = regexps.length > 0;
if (hasRe && isLoaded) regexpTester.update(regexps);
el.classList.toggle('has-regexp', hasRe);
if (regexps.length) {
el.classList.add('has-regexp');
if (isLoaded) regexpTester.update(regexps);
} else {
el.classList.remove('has-regexp');
if (isLoaded) regexpTester.toggle(false);
}
}
function updateTocEntry(origin) {
@ -197,13 +240,11 @@ function createSection(originalSection, genId, si) {
function insertApplyAfter(init, base) {
const apply = createApply(init);
appliesTo.splice(base ? appliesTo.indexOf(base) + 1 : appliesTo.length, 0, apply);
appliesToList.insertBefore(apply.el, base ? base.el.nextSibling : null);
toggleDataset(appliesToContainer, 'all', init.all);
appliesToContainer.insertBefore(apply.el, base ? base.el.nextSibling : null);
dirty.add(apply, apply);
if (appliesTo.length > 1 && appliesTo[0].all) {
removeApply(appliesTo[0]);
}
if (base) requestAnimationFrame(shrinkSectionBy1);
emitSectionChange('apply');
return apply;
}
@ -312,15 +353,6 @@ function createSection(originalSection, genId, si) {
dirty.add(`${dirtyPrefix}.value`, value);
}
}
function shrinkSectionBy1() {
const cmEl = cm.display.wrapper;
const cmH = cmEl.offsetHeight;
const viewH = el.parentElement.offsetHeight;
if (el.offsetHeight > viewH && cmH > Math.min(viewH / 2, cm.display.sizer.offsetHeight + 30)) {
cmEl.style.height = (cmH - appliesToContainer.offsetHeight / (appliesTo.length || 1) | 0) + 'px';
}
}
}
function createResizeGrip(cm) {
@ -349,7 +381,8 @@ function createResizeGrip(cm) {
cm.display.lineDiv.offsetParent.offsetTop +
/* borders */
wrapper.offsetHeight - wrapper.clientHeight;
document.body.classList.add('resizing-v');
wrapper.style.pointerEvents = 'none';
document.body.style.cursor = 's-resize';
document.on('mousemove', resize);
document.on('mouseup', resizeStop);
@ -365,7 +398,8 @@ function createResizeGrip(cm) {
function resizeStop() {
document.off('mouseup', resizeStop);
document.off('mousemove', resize);
document.body.classList.remove('resizing-v');
wrapper.style.pointerEvents = '';
document.body.style.cursor = '';
}
};

View File

@ -1,13 +1,12 @@
/* global $ $create $remove messageBoxProxy */// dom.js
/* global $ $$ $create $remove messageBoxProxy */// dom.js
/* global API */// msg.js
/* global CodeMirror */
/* global RX_META debounce */// toolbox.js
/* global FIREFOX RX_META debounce ignoreChromeError sessionStore */// toolbox.js
/* global MozDocMapper clipString helpPopup rerouteHotkeys showCodeMirrorPopup */// util.js
/* global createSection */// sections-editor-section.js
/* global editor */
/* global linterMan */
/* global prefs */
/* global styleSectionsEqual */ // sections-util.js
/* global t */// localization.js
'use strict';
@ -17,25 +16,26 @@ function SectionsEditor() {
const container = $('#sections');
/** @type {EditorSection[]} */
const sections = [];
const xo = new IntersectionObserver(refreshOnViewListener, {rootMargin: '100%'});
const xo = window.IntersectionObserver &&
new IntersectionObserver(refreshOnViewListener, {rootMargin: '100%'});
let INC_ID = 0; // an increment id that is used by various object to track the order
let sectionOrder = '';
let headerOffset; // in compact mode the header is at the top so it reduces the available height
let cmExtrasHeight; // resize grip + borders
let upDownJumps;
updateMeta();
updateHeader();
rerouteHotkeys.toggle(true); // enabled initially because we don't always focus a CodeMirror
editor.livePreview.init();
container.classList.add('section-editor');
$('#to-mozilla').on('click', showMozillaFormat);
$('#to-mozilla-help').on('click', showToMozillaHelp);
$('#from-mozilla').on('click', () => showMozillaFormatImport());
document.on('wheel', scrollEntirePageOnCtrlShift, {passive: false});
CodeMirror.defaults.extraKeys['Shift-Ctrl-Wheel'] = 'scrollWindow';
prefs.subscribe('editor.arrowKeysTraverse', (_, val) => {
for (const {cm} of sections) handleKeydownSetup(cm, val);
upDownJumps = val;
}, {runNow: true});
if (!FIREFOX) {
$$('input:not([type]), input[type=text], input[type=search], input[type=number]')
.forEach(e => e.on('mousedown', toggleContextMenuDelete));
}
/** @namespace Editor */
Object.assign(editor, {
@ -44,18 +44,14 @@ function SectionsEditor() {
closestVisible,
updateLivePreview,
updateMeta,
getEditors() {
return sections.filter(s => !s.removed).map(s => s.cm);
},
getEditorTitle(cm) {
const index = editor.getEditors().indexOf(cm) + 1;
return {
textContent: `#${index}`,
title: `${t('sectionCode')} ${index}`,
};
const index = editor.getEditors().indexOf(cm);
return `${t('sectionCode')} ${index + 1}`;
},
getValue(asObject) {
@ -76,62 +72,83 @@ function SectionsEditor() {
}
},
nextEditor(cm, upDown) {
return !upDown || cm !== findLast(sections, s => !s.removed).cm
? nextPrevEditor(cm, 1, upDown)
nextEditor(cm, cycle = true) {
return cycle || cm !== findLast(sections, s => !s.removed).cm
? nextPrevEditor(cm, 1)
: null;
},
prevEditor(cm, upDown) {
return !upDown || cm !== sections.find(s => !s.removed).cm
? nextPrevEditor(cm, -1, upDown)
prevEditor(cm, cycle = true) {
return cycle || cm !== sections.find(s => !s.removed).cm
? nextPrevEditor(cm, -1)
: null;
},
async replaceStyle(newStyle, draft) {
const sameCode = styleSectionsEqual(newStyle, getModel());
if (!sameCode && !draft && !await messageBoxProxy.confirm(t('styleUpdateDiscardChanges'))) {
return;
}
if (!draft) {
async replaceStyle(newStyle) {
dirty.clear();
}
// FIXME: avoid recreating all editors?
if (!sameCode) {
await initSections(newStyle.sections, {
keepDirty: draft,
replace: true,
si: draft && draft.si,
});
await initSections(newStyle.sections, {replace: true});
Object.assign(style, newStyle);
editor.onStyleUpdated();
updateHeader();
// Go from new style URL to edit style URL
if (style.id && !/[&?]id=/.test(location.search)) {
history.replaceState({}, document.title, `${location.pathname}?id=${style.id}`);
}
editor.useSavedStyle(newStyle);
updateLivePreview();
},
async saveImpl() {
async save() {
if (!dirty.isDirty()) {
return;
}
let newStyle = getModel();
if (!validate(newStyle)) {
return;
}
newStyle = await API.styles.editSave(newStyle);
dirty.clear();
editor.useSavedStyle(newStyle);
destroyRemovedSections();
if (!style.id) {
editor.emit('styleChange', newStyle, 'new');
}
sessionStore.justEditedStyleId = newStyle.id;
editor.replaceStyle(newStyle, false);
},
scrollToEditor(cm, partial) {
const cc = partial && cm.cursorCoords(true, 'window');
const {top: y1, bottom: y2} = cm.el.getBoundingClientRect();
const rc = container.getBoundingClientRect();
const rcY1 = Math.max(rc.top, 0);
const rcY2 = Math.min(rc.bottom, innerHeight);
const bad = partial
? cc.top < rcY1 || cc.top > rcY2 - 30
: y1 >= rcY1 ^ y2 <= rcY2;
if (bad) window.scrollBy(0, (y1 + y2 - rcY2 + rcY1) / 2 | 0);
scrollToEditor(cm) {
const {el} = sections.find(s => s.cm === cm);
const r = el.getBoundingClientRect();
const h = window.innerHeight;
if (r.bottom > h && r.top > 0 ||
r.bottom < h && r.top < 0) {
window.scrollBy(0, (r.top + r.bottom - h) / 2 | 0);
}
},
});
return initSections(style.sections);
editor.ready = initSections(style.sections);
editor.on('styleToggled', newStyle => {
if (!dirty.isDirty()) {
Object.assign(style, newStyle);
} else {
editor.toggleStyle(newStyle.enabled);
}
updateHeader();
updateLivePreview();
});
editor.on('styleChange', (newStyle, reason) => {
if (reason === 'new') return; // nothing is new for us
if (reason === 'config') {
delete newStyle.sections;
delete newStyle.name;
delete newStyle.enabled;
Object.assign(style, newStyle);
updateLivePreview();
return;
}
editor.replaceStyle(newStyle);
});
/** @param {EditorSection} section */
function fitToContent(section) {
@ -299,36 +316,10 @@ function SectionsEditor() {
}
}
function handleKeydown(event) {
if (event.shiftKey || event.altKey || event.metaKey ||
event.key !== 'ArrowUp' && event.key !== 'ArrowDown') {
return;
}
let pos;
let cm = this.CodeMirror;
const {line, ch} = cm.getCursor();
if (event.key === 'ArrowUp') {
cm = line === 0 && editor.prevEditor(cm, true);
pos = cm && [cm.doc.size - 1, ch];
} else {
cm = line === cm.doc.size - 1 && editor.nextEditor(cm, true);
pos = cm && [0, 0];
}
if (cm) {
cm.setCursor(...pos);
event.preventDefault();
event.stopPropagation();
}
}
function handleKeydownSetup(cm, state) {
cm.display.wrapper[state ? 'on' : 'off']('keydown', handleKeydown, true);
}
function nextPrevEditor(cm, direction, upDown) {
function nextPrevEditor(cm, direction) {
const editors = editor.getEditors();
cm = editors[(editors.indexOf(cm) + direction + editors.length) % editors.length];
editor.scrollToEditor(cm, upDown);
editor.scrollToEditor(cm);
cm.focus();
return cm;
}
@ -430,7 +421,7 @@ function SectionsEditor() {
}
function lockPageUI(locked) {
$.root.style.pointerEvents = locked ? 'none' : '';
document.documentElement.style.pointerEvents = locked ? 'none' : '';
if (popup.codebox) {
popup.classList.toggle('ready', locked ? false : !popup.codebox.isBlank());
popup.codebox.options.readOnly = locked;
@ -486,7 +477,19 @@ function SectionsEditor() {
return true;
}
function updateMeta() {
function destroyRemovedSections() {
for (let i = 0; i < sections.length;) {
if (!sections[i].removed) {
i++;
continue;
}
sections[i].destroy();
sections[i].el.remove();
sections.splice(i, 1);
}
}
function updateHeader() {
$('#name').value = style.customName || style.name || '';
$('#enabled').checked = style.enabled !== false;
$('#url').href = style.url || '';
@ -504,15 +507,14 @@ function SectionsEditor() {
async function initSections(src, {
focusOn = 0,
replace = false,
keepDirty = false,
si = editor.scrollInfo,
keepDirty = false, // used by import
} = {}) {
Object.assign(editor, /** @namespace Editor */ {loading: true});
if (replace) {
sections.forEach(s => s.remove(true));
sections.length = 0;
container.textContent = '';
}
let si = editor.scrollInfo;
if (si && si.cms && si.cms.length === src.length) {
si.scrollY2 = si.scrollY + window.innerHeight;
container.style.height = si.scrollY2 + 'px';
@ -540,12 +542,9 @@ function SectionsEditor() {
if (!keepDirty) dirty.clear();
if (i === focusOn) sections[i].cm.focus();
}
if (!si || si.cms.every(cm => !cm.height)) {
requestAnimationFrame(fitToAvailableSpace);
}
if (!si) requestAnimationFrame(fitToAvailableSpace);
container.style.removeProperty('height');
setGlobalProgress();
editor.loading = false;
}
/** @param {EditorSection} section */
@ -611,9 +610,6 @@ function SectionsEditor() {
cm.focus();
editor.scrollToEditor(cm);
}
if (upDownJumps) {
handleKeydownSetup(cm, true);
}
updateSectionOrder();
updateLivePreview();
section.onChange(updateLivePreview);
@ -646,6 +642,7 @@ function SectionsEditor() {
/** @param {EditorSection} section */
function registerEvents(section) {
const {el, cm} = section;
$('.applies-to-help', el).onclick = () => helpPopup.show(t('appliesLabel'), t('appliesHelp'));
$('.remove-section', el).onclick = () => removeSection(section);
$('.add-section', el).onclick = () => insertSectionAfter(undefined, section);
$('.clone-section', el).onclick = () => insertSectionAfter(section.getModel(), section);
@ -653,13 +650,16 @@ function SectionsEditor() {
$('.move-section-down', el).onclick = () => moveSectionDown(section);
$('.restore-section', el).onclick = () => restoreSection(section);
cm.on('paste', maybeImportOnPaste);
if (!FIREFOX) {
cm.on('mousedown', (cm, event) => toggleContextMenuDelete.call(cm, event));
}
}
function maybeImportOnPaste(cm, event) {
const text = event.clipboardData.getData('text') || '';
if (/@-moz-document/i.test(text) &&
/@-moz-document\s+(url|url-prefix|domain|regexp)\(/i
.test(text.replace(/\/\*([^*]+|\*(?!\/))*(\*\/|$)/g, ''))
.test(text.replace(/\/\*([^*]|\*(?!\/))*(\*\/|$)/g, ''))
) {
event.preventDefault();
showMozillaFormatImport(text);
@ -670,7 +670,7 @@ function SectionsEditor() {
if (code) {
linterMan.enableForEditor(cm, code);
}
if (force) {
if (force || !xo) {
refreshOnViewNow(cm);
} else {
xo.observe(cm.display.wrapper);
@ -697,4 +697,15 @@ function SectionsEditor() {
linterMan.enableForEditor(cm);
cm.refresh();
}
function toggleContextMenuDelete(event) {
if (chrome.contextMenus && event.button === 2 && prefs.get('editor.contextDelete')) {
chrome.contextMenus.update('editor.contextDelete', {
enabled: Boolean(
this.selectionStart !== this.selectionEnd ||
this.somethingSelected && this.somethingSelected()
),
}, ignoreChromeError);
}
}
}

View File

@ -1,47 +1,46 @@
#help-popup.style-settings-popup.dirty .title::after {
content: ' *';
}
.compact-layout #help-popup.style-settings-popup {
width: 90%;
}
.style-settings {
padding: 0 1px; /* for focus outline */
padding: 0.7rem 1.7rem;
border: 0;
margin: 0;
}
.style-settings > * {
.form-group {
display: block;
margin: 1rem 0;
margin: .6em 0;
padding: 0;
}
.style-settings > :first-child {
margin-top: 0;
.form-label {
display: inline-block;
margin: .3em 0;
}
.style-settings > :last-child {
margin-bottom: 0;
[disabled] .form-label {
opacity: 0.4;
}
.style-settings input:disabled ~ label {
opacity: .5;
}
.style-settings .w100 {
.form-group input[type=text],
.form-group input[type=number],
.form-group select,
.form-group textarea {
display: block;
width: 100%;
margin-top: .25em;
box-sizing: border-box;
}
.radio-group .form-label {
display: block;
}
.radio-item {
display: flex;
margin: 0.3em 0 .3em;
padding: 0;
align-items: center;
width: max-content;
}
.radio-item input {
margin: 0 0.6em 0 0;
}
[disabled] .radio-label {
opacity: 0.4;
}
.style-settings textarea {
resize: vertical;
min-width: 33vw;
min-height: 2.5em;
max-height: 50vh;
}
.style-settings textarea:not(:placeholder-shown) {
min-width: 50vw;
}
.style-settings .radio-wrapper {
display: inline-flex;
padding: 0 .8em 0 0;
}
a[data-cmd=note] {
vertical-align: text-bottom;
}

View File

@ -1,40 +0,0 @@
<div>
<fieldset class="style-settings">
<div class="rel">
<input id="ss-updatable" type="checkbox">
<svg class="svg-icon checked"><use xlink:href="#svg-icon-checked"/></svg>
<label i18n="installUpdateFromLabel" for="ss-updatable"></label>
<input id="ss-update-url" type="url" class="w100" i18n="placeholder:styleUpdateUrlLabel">
</div>
<div id="ss-scheme">
<div i18n="preferScheme">
<div><small id="ss-scheme-off" i18n="preferSchemeAlways" hidden></small></div>
</div>
<label i18n="+preferSchemeNone" class="radio-wrapper">
<input name="ss-scheme" type="radio" value="none">
</label>
<label i18n="+preferSchemeDark" class="radio-wrapper">
<input name="ss-scheme" type="radio" value="dark">
</label>
<label i18n="+preferSchemeLight" class="radio-wrapper">
<input name="ss-scheme" type="radio" value="light">
</label>
</div>
<label i18n="styleIncludeLabel">
<textarea id="ss-inclusions" spellcheck="false" class="w100"
placeholder="*://site1.com/*&#10;*://site2.com/*"></textarea>
</label>
<label i18n="styleExcludeLabel">
<textarea id="ss-exclusions" spellcheck="false" class="w100"
placeholder="*://site1.com/*&#10;*://site2.com/*"></textarea>
</label>
</fieldset>
<div class="buttons">
<button id="ss-save" i18n="confirmSave" disabled></button>
<label i18n="+configOnChange, title:configOnChangeTooltip">
<input id="config.autosave" type="checkbox">
<svg class="svg-icon checked"><use xlink:href="#svg-icon-checked"/></svg>
</label>
<button id="ss-close" i18n="confirmClose"></button>
</div>
</div>

View File

@ -1,124 +1,84 @@
/* global $ moveFocus setupLivePrefs */// dom.js
/* global $ $$ */// dom.js
/* global API */// msg.js
/* global editor */
/* global helpPopup */// util.js
/* global prefs */
/* global t */// localization.js
/* global debounce tryURL */// toolbox.js
/* exported StyleSettings */
'use strict';
/* exported StyleSettings */
async function StyleSettings() {
const AUTOSAVE_DELAY = 500; // same as config-dialog.js
const SS_ID = 'styleSettings';
const PASS = val => val;
await t.fetchTemplate('/edit/settings.html', SS_ID);
const {style} = editor;
const ui = t.template[SS_ID].cloneNode(true);
const elAuto = $('#config\\.autosave', ui);
const elSave = $('#ss-save', ui);
const elUpd = $('#ss-updatable', ui);
const pendingSetters = new Map();
const updaters = [
initCheckbox(elUpd, 'updatable', tryURL(style.updateUrl).href),
initInput('#ss-update-url', 'updateUrl', '', {
validate(el) {
elUpd.disabled = !el.value || !el.validity.valid;
return el.validity.valid;
},
}),
initRadio('ss-scheme', 'preferScheme', 'none'),
initArea('inclusions'),
initArea('exclusions'),
function StyleSettings(editor) {
let {style} = editor;
const inputs = [
createInput('.style-update-url input', () => style.updateUrl || '',
e => API.styles.config(style.id, 'updateUrl', e.target.value)),
createRadio('.style-prefer-scheme input', () => style.preferScheme || 'none',
e => API.styles.config(style.id, 'preferScheme', e.target.value)),
...[
['.style-include', 'inclusions'],
['.style-exclude', 'exclusions'],
].map(createArea),
];
update();
prefs.subscribe('schemeSwitcher.enabled', (_, val) => {
$('#ss-scheme-off', ui).hidden = val !== 'never';
}, {runNow: true});
window.on(SS_ID, update);
window.on('closeHelp', () => window.off(SS_ID, update), {once: true});
helpPopup.show(t(SS_ID), ui, {
className: 'style-settings-popup',
});
elSave.onclick = save;
$('#ss-close', ui).onclick = helpPopup.close;
setupLivePrefs([elAuto.id]);
moveFocus(ui, 0);
function autosave(el, setter) {
pendingSetters.set(el, setter);
helpPopup.div.classList.add('dirty');
elSave.disabled = false;
if (elAuto.checked) debounce(save, AUTOSAVE_DELAY);
}
update(style);
function initArea(type) {
return initInput(`#ss-${type}`, type, [], {
get: textToList,
set: list => list.join('\n'),
validate(el) {
const val = el.value;
el.rows = val.match(/^/gm).length + !val.endsWith('\n');
},
});
}
function initCheckbox(el, key, defVal) {
return initInput(el, key, Boolean(defVal), {dom: 'checked'});
}
function initInput(el, key, defVal, {
dom = 'value', // DOM property name
get = PASS, // transformer function(val) after getting DOM value
set = PASS, // transformer function(val) before setting DOM value
validate = PASS, // function(el) - return `false` to prevent saving
} = {}) {
if (typeof el === 'string') {
el = $(el, ui);
}
el.oninput = () => {
if (validate(el) !== false) {
autosave(el, {dom, get, key});
}
};
return () => {
let val = style[key];
val = set(val != null ? val : defVal);
// Skipping if unchanged to preserve the Undo history of the input
if (el[dom] !== val) el[dom] = val;
validate(el);
};
}
function initRadio(name, key, defVal) {
$(`#${name}`, ui).oninput = e => {
if (e.target.checked) {
autosave(e.target, {key});
}
};
return () => {
const val = style[key] || defVal;
const el = $(`[name="${name}"][value="${val}"]`, ui);
el.checked = true;
};
}
function save() {
pendingSetters.forEach(saveValue);
pendingSetters.clear();
helpPopup.div.classList.remove('dirty');
elSave.disabled = true;
}
function saveValue({dom = 'value', get = PASS, key}, el) {
return API.styles.config(style.id, key, get(el[dom]));
}
editor.on('styleChange', update);
function textToList(text) {
return text.split(/\n/).map(s => s.trim()).filter(Boolean);
const list = text.split(/\s*\r?\n\s*/g);
return list.filter(Boolean);
}
function update() {
updaters.forEach(fn => fn());
function update(newStyle, reason) {
if (!newStyle.id) return;
if (reason === 'editSave') return;
style = newStyle;
$('.style-settings').disabled = false;
inputs.forEach(i => i.update());
}
function createArea([parentSel, type]) {
const sel = parentSel + ' textarea';
const el = $(sel);
el.on('input', () => {
const val = el.value;
el.rows = val.match(/^/gm).length + !val.endsWith('\n');
});
return createInput(sel,
() => {
const list = style[type] || [];
const text = list.join('\n');
el.rows = (list.length || 1) + 1;
return text;
},
() => API.styles.config(style.id, type, textToList(el.value))
);
}
function createRadio(selector, getter, setter) {
const els = $$(selector);
for (const el of els) {
el.addEventListener('change', e => {
if (el.checked) {
setter(e);
}
});
}
return {
update() {
for (const el of els) {
if (el.value === getter()) {
el.checked = true;
}
}
},
};
}
function createInput(selector, getter, setter) {
const el = $(selector);
el.addEventListener('change', setter);
return {
update() {
el.value = getter();
},
};
}
}

View File

@ -4,7 +4,7 @@
/* global MozDocMapper */// util.js
/* global MozSectionFinder */
/* global MozSectionWidget */
/* global RX_META debounce */// toolbox.js
/* global RX_META debounce sessionStore */// toolbox.js
/* global chromeSync */// storage-util.js
/* global cmFactory */
/* global editor */
@ -14,31 +14,23 @@
'use strict';
/* exported SourceEditor */
async function SourceEditor() {
function SourceEditor() {
const {style, /** @type DirtyReporter */dirty} = editor;
const DEFAULT_TEMPLATE = `
/* ==UserStyle==
@name ${''/* a trick to preserve the trailing spaces */}
@namespace github.com/openstyles/stylus
@version 1.0.0
@description A new userstyle
@author Me
==/UserStyle== */
`.replace(/^\s+/gm, '');
let savedGeneration;
let placeholderName = '';
let prevMode = NaN;
$$remove('.sectioned-only');
$('#header').on('wheel', headerOnScroll);
$('#sections').textContent = '';
$('#sections').appendChild($create('.single-editor'));
$('#save-button').on('split-btn', saveTemplate);
if (!style.id) setupNewStyle(style);
const cm = cmFactory.create($('.single-editor'));
const sectionFinder = MozSectionFinder(cm);
const sectionWidget = MozSectionWidget(cm, sectionFinder);
editor.livePreview.init(preprocess);
if (!style.id) setupNewStyle(await editor.template);
createMetaCompiler(meta => {
style.usercssData = meta;
style.name = meta.name;
@ -53,17 +45,10 @@ async function SourceEditor() {
sections: sectionFinder.sections,
replaceStyle,
updateLivePreview,
updateMeta,
closestVisible: () => cm,
getEditors: () => [cm],
getEditorTitle: () => '',
getValue: asObject => asObject
? {
customName: style.customName,
enabled: style.enabled,
sourceCode: cm.getValue(),
}
: cm.getValue(),
getValue: () => cm.getValue(),
getSearchableInputs: () => [],
prevEditor: nextPrevSection.bind(null, -1),
nextEditor: nextPrevSection.bind(null, 1),
@ -75,7 +60,8 @@ async function SourceEditor() {
cm.focus();
}
},
async saveImpl() {
async save() {
if (!dirty.isDirty()) return;
const sourceCode = cm.getValue();
try {
const {customName, enabled, id} = style;
@ -84,12 +70,21 @@ async function SourceEditor() {
messageBoxProxy.alert(t('usercssAvoidOverwriting'), 'danger', t('genericError'));
} else {
res = await API.usercss.editSave({customName, enabled, id, sourceCode});
if (!id) {
editor.emit('styleChange', res.style, 'new');
}
// Awaiting inside `try` so that exceptions go to our `catch`
await replaceStyle(res.style);
}
showLog(res);
} catch (err) {
showSaveError(err);
const i = err.index;
const isNameEmpty = i > 0 &&
err.code === 'missingValue' &&
sourceCode.slice(sourceCode.lastIndexOf('\n', i - 1), i).trim().endsWith('@name');
return isNameEmpty
? saveTemplate(sourceCode)
: showSaveError(err);
}
},
scrollToEditor: () => {},
@ -101,6 +96,7 @@ async function SourceEditor() {
'editor.toc.expanded': (k, val) => sectionFinder.onOff(editor.updateToc, val),
}, {runNow: true});
editor.applyScrollInfo(cm);
cm.clearHistory();
cm.markClean();
savedGeneration = cm.changeGeneration();
@ -120,7 +116,26 @@ async function SourceEditor() {
if (!$isTextInput(document.activeElement)) {
cm.focus();
}
editor.applyScrollInfo(cm); // WARNING! Place it after all cm.XXX calls that change scroll pos
editor.on('styleToggled', newStyle => {
if (dirty.isDirty()) {
editor.toggleStyle(newStyle.enabled);
} else {
style.enabled = newStyle.enabled;
}
updateMeta();
updateLivePreview();
});
editor.on('styleChange', (newStyle, reason) => {
if (reason === 'new') return;
if (reason === 'config') {
delete newStyle.sourceCode;
delete newStyle.name;
Object.assign(style, newStyle);
updateLivePreview();
return;
}
replaceStyle(newStyle);
});
async function preprocess(style) {
const res = await API.usercss.build({
@ -168,21 +183,34 @@ async function SourceEditor() {
return name;
}
function setupNewStyle(tpl) {
const comment = `/* ${t('usercssReplaceTemplateSectionBody')} */`;
const sec0 = style.sections[0];
sec0.code = ' '.repeat(prefs.get('editor.tabSize')) + comment;
if (Object.keys(sec0).length === 1) { // the only key is 'code'
sec0.domains = ['example.com'];
async function setupNewStyle(style) {
style.sections[0].code = ' '.repeat(prefs.get('editor.tabSize')) +
`/* ${t('usercssReplaceTemplateSectionBody')} */`;
let section = MozDocMapper.styleToCss(style);
if (!section.includes('@-moz-document')) {
style.sections[0].domains = ['example.com'];
section = MozDocMapper.styleToCss(style);
}
style.name = [style.name, new Date().toLocaleString()].filter(Boolean).join(' - ');
style.sourceCode = (tpl || DEFAULT_TEMPLATE)
.replace(/(@name)(?:([\t\x20]+).*|\n)/, (_, k, space) => `${k}${space || ' '}${style.name}`)
.replace(/\s*@-moz-document[^{]*{([^}]*)}\s*$/g, // stripping dummy sections
(s, body) => body.trim() === comment ? '\n\n' : s)
.trim() +
'\n\n' +
MozDocMapper.styleToCss(style);
const DEFAULT_CODE = `
/* ==UserStyle==
@name ${''/* a trick to preserve the trailing spaces */}
@namespace github.com/openstyles/stylus
@version 1.0.0
@description A new userstyle
@author Me
==/UserStyle== */
`.replace(/^\s+/gm, '');
dirty.clear('sourceGeneration');
style.sourceCode = '';
placeholderName = `${style.name || t('usercssReplaceTemplateName')} - ${new Date().toLocaleString()}`;
let code = await chromeSync.getLZValue(chromeSync.LZ_KEY.usercssTemplate);
code = code || DEFAULT_CODE;
code = code.replace(/@name(\s*)(?=[\r\n])/, (str, space) =>
`${str}${space ? '' : ' '}${placeholderName}`);
// strip the last dummy section if any, add an empty line followed by the section
style.sourceCode = code.replace(/\s*@-moz-document[^{]*{[^}]*}\s*$|\s+$/g, '') + '\n\n' + section;
cm.startOperation();
cm.setValue(style.sourceCode);
cm.clearHistory();
@ -194,60 +222,57 @@ async function SourceEditor() {
function updateMeta() {
const name = style.customName || style.name;
if (name !== placeholderName) {
$('#name').value = name;
}
$('#enabled').checked = style.enabled;
$('#url').href = style.url;
editor.updateName();
cm.setPreprocessor((style.usercssData || {}).preprocessor);
}
async function replaceStyle(newStyle, draft) {
function replaceStyle(newStyle) {
dirty.clear('name');
const sameCode = newStyle.sourceCode === cm.getValue();
if (sameCode) {
savedGeneration = cm.changeGeneration();
dirty.clear('sourceGeneration');
editor.useSavedStyle(newStyle);
updateEnvironment();
dirty.clear('enabled');
updateLivePreview();
return;
}
if (draft || await messageBoxProxy.confirm(t('styleUpdateDiscardChanges'))) {
editor.useSavedStyle(newStyle);
Promise.resolve(messageBoxProxy.confirm(t('styleUpdateDiscardChanges'))).then(ok => {
if (!ok) return;
updateEnvironment();
if (!sameCode) {
const si0 = draft && draft.si.cms[0];
const cursor = !si0 && cm.getCursor();
const cursor = cm.getCursor();
cm.setValue(style.sourceCode);
if (si0) {
editor.applyScrollInfo(cm, si0);
} else {
cm.setCursor(cursor);
}
savedGeneration = cm.changeGeneration();
}
if (sameCode) {
// the code is same but the environment is changed
updateLivePreview();
}
if (!draft) {
dirty.clear();
});
function updateEnvironment() {
if (style.id !== newStyle.id) {
history.replaceState({}, '', `?id=${newStyle.id}`);
}
sessionStore.justEditedStyleId = newStyle.id;
Object.assign(style, newStyle);
editor.onStyleUpdated();
updateMeta();
}
}
async function saveTemplate() {
const res = await messageBoxProxy.show({
contents: t('usercssReplaceTemplateConfirmation'),
className: 'center',
buttons: [t('confirmYes'), t('confirmNo'), {
textContent: t('genericResetLabel'),
title: t('restoreTemplate'),
}],
});
if (res.enter || res.button !== 1) {
async function saveTemplate(code) {
if (await messageBoxProxy.confirm(t('usercssReplaceTemplateConfirmation'))) {
const key = chromeSync.LZ_KEY.usercssTemplate;
const code = res.button === 2 ? DEFAULT_TEMPLATE : cm.getValue();
await chromeSync.setLZValue(key, code);
if (await chromeSync.getLZValue(key) !== code) {
messageBoxProxy.alert(t('syncStorageErrorSaving'));

28
edit/tab.css Normal file
View File

@ -0,0 +1,28 @@
.tab-container {
display: flex;
flex-direction: column;
}
.tab-bar {
display: flex;
flex-direction: row;
border-bottom: 1px solid silver;
padding: 5px 5px 0 15px;
}
.tab-bar-item {
margin: 0 0.3em;
padding: 0.3em 0.6em;
border: 1px solid silver;
border-bottom: none;
background: silver;
cursor: pointer;
}
.tab-bar-item.active {
background: white;
}
.tab-panel {
flex-grow: 1;
}
.tab-panel > :not(.active) {
display: none;
}

19
edit/tab.js Normal file
View File

@ -0,0 +1,19 @@
'use strict';
(() => {
for (const container of document.querySelectorAll('.tab-container')) {
init(container);
}
function init(container) {
const tabButtons = [...container.querySelector('.tab-bar').children];
const tabPanels = [...container.querySelector('.tab-panel').children];
tabButtons.forEach((button, i) => button.addEventListener('click', () => activate(i)));
function activate(index) {
const toggleActive = (button, i) => button.classList.toggle('active', i === index);
tabButtons.forEach(toggleActive);
tabPanels.forEach(toggleActive);
}
}
})();

View File

@ -1,6 +1,7 @@
/* global $ $create $remove messageBoxProxy showSpinner toggleDataset */// dom.js
/* global API msg */// msg.js
/* global URLS */// toolbox.js
/* global baseInit */
/* global editor */
/* global t */// localization.js
'use strict';
@ -21,11 +22,11 @@
}
});
window.on('domReady', () => {
baseInit.ready.then(() => {
updateUI();
$('#usw-publish-style').onclick = disableWhileActive(publishStyle);
$('#usw-disconnect').onclick = disableWhileActive(disconnect);
}, {once: true});
});
async function publishStyle() {
const {id} = editor.style;

View File

@ -7,54 +7,47 @@
const helpPopup = {
/**
* @param {string} title - plain text
* @param {string|Node} body - Node, html or plain text
* @param {Node} [props] - DOM props for the popup element
* @returns {Element} the popup
*/
show(title = '', body, props) {
show(title = '', body) {
const div = $('#help-popup');
const contents = $('.contents', div);
div.style = '';
div.className = '';
contents.textContent = '';
Object.assign(div, props);
if (body) {
contents.appendChild(typeof body === 'string' ? t.HTML(body) : body);
}
$('.title', div).textContent = title;
$('.dismiss', div).onclick = helpPopup.close;
window.on('keydown', helpPopup.close, true);
div.style.display = 'block';
// reset any inline styles
div.style = 'display: block';
helpPopup.originalFocus = document.activeElement;
helpPopup.div = div;
moveFocus(div, 0);
return div;
},
close(event) {
let el;
const canClose =
!event ||
event.type === 'click' ||
getEventKeyName(event) === 'Escape' && !$('.CodeMirror-hints, #message-box') && (
!(el = document.activeElement) ||
!el.closest('#search-replace-dialog')
event.type === 'click' || (
getEventKeyName(event) === 'Escape' &&
!$('.CodeMirror-hints, #message-box') && (
!document.activeElement ||
!document.activeElement.closest('#search-replace-dialog') &&
document.activeElement.matches(':not(input), .can-close-on-esc')
)
);
const {div} = helpPopup;
const div = $('#help-popup');
if (!canClose || !div) {
return;
}
if (event && (el = div.codebox) && !el.options.readOnly && !el.isClean()) {
if (event && div.codebox && !div.codebox.options.readOnly && !div.codebox.isClean()) {
setTimeout(async () => {
const ok = await messageBoxProxy.confirm(t('confirmDiscardChanges'));
return ok && helpPopup.close();
});
return;
}
if (div.contains(document.activeElement) && (el = helpPopup.originalFocus)) {
el.focus();
if (div.contains(document.activeElement) && helpPopup.originalFocus) {
helpPopup.originalFocus.focus();
}
const contents = $('.contents', div);
div.style.display = '';
@ -177,7 +170,8 @@ function createHotkeyInput(prefId, {buttons = true, onDone}) {
/* exported showCodeMirrorPopup */
function showCodeMirrorPopup(title, html, options) {
const popup = helpPopup.show(title, html, {className: 'big'});
const popup = helpPopup.show(title, html);
popup.classList.add('big');
let cm = popup.codebox = CodeMirror($('.contents', popup), Object.assign({
mode: 'css',
@ -192,7 +186,7 @@ function showCodeMirrorPopup(title, html, options) {
}, options));
cm.focus();
$.root.style.pointerEvents = 'none';
document.documentElement.style.pointerEvents = 'none';
popup.style.pointerEvents = 'auto';
const onKeyDown = event => {
@ -207,7 +201,7 @@ function showCodeMirrorPopup(title, html, options) {
window.on('closeHelp', () => {
window.off('keydown', onKeyDown, true);
$.root.style.removeProperty('pointer-events');
document.documentElement.style.removeProperty('pointer-events');
cm = popup.codebox = null;
}, {once: true});

View File

@ -1,160 +0,0 @@
@media screen and (prefers-color-scheme: dark), dark {
:root {
/* Comfortable dark themes don't use absolutes so the range is compressed */
--c00: hsl(0, 0%, 80%);
--c10: hsl(0, 0%, 73.5%);
--c20: hsl(0, 0%, 66%);
--c30: hsl(0, 0%, 59.5%);
--c40: hsl(0, 0%, 53%);
--c45: hsl(0, 0%, 49.75%);
--c50: hsl(0, 0%, 46.5%);
--c60: hsl(0, 0%, 40%);
--c65: hsl(0, 0%, 36.75%);
--c70: hsl(0, 0%, 33.5%);
--c75: hsl(0, 0%, 30.25%);
--c80: hsl(0, 0%, 27%);
--c85: hsl(0, 0%, 23.75%);
--c90: hsl(0, 0%, 20.5%);
--c95: hsl(0, 0%, 17.25%);
--c100: hsl(0, 0%, 14%);
/* min/max are exposed in case we want to use an overdrive color for emphasis */
--cmin: hsl(0, 0%, 100%);
--cmax: hsl(0, 0%, 0%);
--accent-1: hsl(180, 100%, 95%);
--accent-3: hsl(180, 30%, 18%);
--input-bg: var(--c95);
--red1: hsl(0, 85%, 55%);
}
textarea,
input[type=url],
input[type=time] {
background-color: var(--input-bg);
color: var(--fg);
}
input::-webkit-inner-spin-button {
filter: invert(.8);
}
input[type=radio]:checked:after {
background-color: var(--fg);
}
input[type=time]::-webkit-calendar-picker-indicator {
filter: invert(1);
}
select {
background-color: var(--bg);
}
.onoffswitch {
--knob: var(--c50);
}
.CodeMirror-scrollbar-filler,
.CodeMirror-gutter-filler {
background-color: var(--bg) !important;
border: 0;
}
::-webkit-scrollbar {
width: 17px;
height: 17px;
background: var(--bg);
}
::-webkit-scrollbar-corner {
background: var(--bg);
border: 0;
}
/* buttons */
::-webkit-scrollbar-button:single-button {
height: 17px;
width: 17px;
background-size: 9px;
background-position: 4px 7px;
background-repeat: no-repeat;
}
::-webkit-scrollbar-button:horizontal:single-button {
background-position: 7px 4px;
}
/* up */
::-webkit-scrollbar-button:single-button:vertical:decrement {
background-image: url("data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' width='2' height='2' fill='hsl(0, 0%, 35%)'><polygon points='1,0 0,1 2,1'/></svg>");
}
::-webkit-scrollbar-button:single-button:vertical:decrement:hover {
background-image: url("data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' width='2' height='2' fill='hsl(0, 0%, 45%)'><polygon points='1,0 0,1 2,1'/></svg>");
}
::-webkit-scrollbar-button:single-button:vertical:decrement:active {
background-image: url("data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' width='2' height='2' fill='hsl(0, 0%, 55%)'><polygon points='1,0 0,1 2,1'/></svg>");
}
/* down */
::-webkit-scrollbar-button:single-button:vertical:increment {
background-image: url("data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' width='2' height='2' fill='hsl(0, 0%, 35%)'><polygon points='0,0 2,0 1,1'/></svg>");
}
::-webkit-scrollbar-button:single-button:vertical:increment:hover {
background-image: url("data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' width='2' height='2' fill='hsl(0, 0%, 45%)'><polygon points='0,0 2,0 1,1'/></svg>");
}
::-webkit-scrollbar-button:single-button:vertical:increment:active {
background-image: url("data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' width='2' height='2' fill='hsl(0, 0%, 55%)'><polygon points='0,0 2,0 1,1'/></svg>");
}
/* left */
::-webkit-scrollbar-button:single-button:horizontal:decrement {
background-image: url("data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' width='2' height='2' fill='hsl(0, 0%, 35%)'><polygon points='0,1 1,2 1,0'/></svg>");
}
::-webkit-scrollbar-button:single-button:horizontal:decrement:hover {
background-image: url("data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' width='2' height='2' fill='hsl(0, 0%, 45%)'><polygon points='0,1 1,2 1,0'/></svg>");
}
::-webkit-scrollbar-button:single-button:horizontal:decrement:active {
background-image: url("data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' width='2' height='2' fill='hsl(0, 0%, 55%)'><polygon points='0,1 1,2 1,0'/></svg>");
}
/* right */
::-webkit-scrollbar-button:single-button:horizontal:increment {
background-image: url("data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' width='2' height='2' fill='hsl(0, 0%, 35%)'><polygon points='0,0 0,2 1,1'/></svg>");
}
::-webkit-scrollbar-button:single-button:horizontal:increment:hover {
background-image: url("data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' width='2' height='2' fill='hsl(0, 0%, 45%)'><polygon points='0,0 0,2 1,1'/></svg>");
}
::-webkit-scrollbar-button:single-button:horizontal:increment:active {
background-image: url("data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' width='2' height='2' fill='hsl(0, 0%, 55%)'><polygon points='0,0 0,2 1,1'/></svg>");
}
::-webkit-scrollbar-track-piece {
background: hsl(0, 0%, 17%);
border: 1px solid var(--bg);
}
::-webkit-scrollbar-track-piece:hover {
background: hsl(0, 0%, 20%);
}
::-webkit-scrollbar-track-piece:active {
background: hsl(0, 0%, 25%);
}
::-webkit-scrollbar-thumb {
background: url("data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' width='1' height='1' fill='hsl(0, 0%, 30%)'><rect width='1' height='1'/></svg>") 2px 2px no-repeat;
}
::-webkit-scrollbar-thumb:horizontal {
background-size: 100% 13px;
}
::-webkit-scrollbar-thumb:vertical {
background-size: 13px 100%;
}
::-webkit-scrollbar-thumb:hover {
background-image: url("data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' width='1' height='1' fill='hsl(0, 0%, 33%)'><rect width='1' height='1'/></svg>");
}
::-webkit-scrollbar-thumb:active {
background-image: url("data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' width='1' height='1' fill='hsl(0, 0%, 40%)'><rect width='1' height='1'/></svg>");
}
::-webkit-resizer {
background: var(--input-bg) linear-gradient(-45deg,
transparent 3px, #888 3px,
#888 4px, transparent 4px,
transparent 6px, #888 6px,
#888 7px, transparent 7px) no-repeat;
border: 2px solid transparent;
}
:-webkit-autofill {
box-shadow: 0 0 0 1000px var(--input-bg) inset;
-webkit-text-fill-color: #fff;
}
@supports (-moz-appearance: none) {
/* Workarounds for FF bugs/quirks */
textarea {
border: 1px solid var(--c65);
}
* {
scrollbar-color: var(--c75) var(--bg);
}
}
}

View File

@ -1,52 +1,21 @@
@supports not (accent-color: red) {
html#stylus #header *:not(#\1transition-suppressor) {
/* This suppresses a bug in all? browsers: they apply transitions during page load.
* It was fixed by crrev.com/886802 in Chrome 93, which we detect via `accent-color`.
* Using an increased specificity to override sane selectors in user styles.
* Using \1 to simplify js code because \0 is converted to \xFFFD per spec. */
html#stylus #header *:not(#\1transition-suppressor) {
transition: none !important;
}
}
:root {
--family: Arial, "Helvetica Neue", Helvetica, system-ui, sans-serif;
--input-height: 22px;
--cmin: hsl(0, 0%, 00%);
--c00: hsl(0, 0%, 00%);
--c10: hsl(0, 0%, 10%);
--c20: hsl(0, 0%, 20%);
--c30: hsl(0, 0%, 30%);
--c40: hsl(0, 0%, 40%);
--c45: hsl(0, 0%, 45%);
--c50: hsl(0, 0%, 50%);
--c60: hsl(0, 0%, 60%);
--c65: hsl(0, 0%, 65%);
--c70: hsl(0, 0%, 70%);
--c75: hsl(0, 0%, 75%);
--c80: hsl(0, 0%, 80%);
--c85: hsl(0, 0%, 85%);
--c90: hsl(0, 0%, 90%);
--c95: hsl(0, 0%, 95%);
--c100: hsl(0, 0%, 100%);
--cmax: hsl(0, 0%, 100%);
--bg: var(--c100);
--fg: var(--c00);
--accent-1: hsl(180, 100%, 15%);
--accent-2: hsl(180, 50%, 40%);
--accent-3: hsl(180, 40%, 69%);
--red1: hsl(0, 70%, 45%);
}
body {
font: normal 12px var(--family);
background-color: var(--bg);
color: var(--fg);
margin: 0;
font: normal 12px Arial, system-ui, sans-serif;
}
body:lang(ja) {
font-family: Arial, 'Meiryo UI', 'MS Gothic', system-ui, sans-serif;
}
body:lang(zh-CN) {
font-family: Arial, 'Microsoft YaHei UI', 'Microsoft YaHei', system-ui, sans-serif;
}
body:lang(zh-TW),
body:lang(zh-HK) {
font-family: Arial, 'Microsoft JhengHei UI', 'Microsoft JhengHei', system-ui, sans-serif;
@ -61,12 +30,11 @@ button {
overflow: hidden;
text-overflow: ellipsis;
padding: 2px 7px;
border: 1px solid var(--c60);
border: 1px solid hsl(0, 0%, 62%);
font: inherit;
font-size: 13px;
line-height: 1.2;
color: var(--fg);
background-color: var(--bg);
color: #000;
background-color: hsl(0, 0%, 100%);
background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAAeCAYAAADtlXTHAAAABmJLR0QA/wD/AP+gvaeTAAAACXBIWXMAAAsTAAALEwEAmpwYAAAAB3RJTUUH4QwGBBwIHvKt6QAAAB1pVFh0Q29tbWVudAAAAAAAQ3JlYXRlZCB3aXRoIEdJTVBkLmUHAAAAL0lEQVQI12NoaGgQZ2JgYGBkYmBgYGZiYGBggrMY4VxsYsyoskQQCB2MWAxAMhkADVECDhlW9CoAAAAASUVORK5CYII=');
background-repeat: repeat-x;
background-size: 100% 100%;
@ -74,55 +42,39 @@ button {
}
button:not(:disabled):hover {
background-color: var(--c95);
border-color: var(--c50);
background-color: hsl(0, 0%, 95%);
border-color: hsl(0, 0%, 52%);
}
button:active {
background-color: var(--c95);
border-color: var(--c50);
background-color: hsl(0, 0%, 95%);
border-color: hsl(0, 0%, 52%);
background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAAeCAYAAADtlXTHAAAABmJLR0QA/wD/AP+gvaeTAAAACXBIWXMAAAsTAAALEwEAmpwYAAAAB3RJTUUH4QwJARIWJNZvuQAAAB1pVFh0Q29tbWVudAAAAAAAQ3JlYXRlZCB3aXRoIEdJTVBkLmUHAAAAMElEQVQI12NoaGgIZmJgYPjLxMDA8I+JgYHhP5z1Dy7xH5X7jxQCzWQ0A9DEILYBABm5HtJk2jPHAAAAAElFTkSuQmCC');
background-repeat: repeat-x;
background-size: 100% 100%;
}
button .svg-icon {
cursor: auto;
}
[data-ui-theme="light"] button .svg-icon {
/* Our svgs are pixel-aligned so the default #000 looks too strong */
fill: #333;
}
/* For some odd reason these hovers appear lighter than all other button hovers in every browser */
#message-box-buttons button:not(:disabled):hover {
background-color: var(--c90);
border-color: var(--c50);
background-color: hsl(0, 0%, 90%);
border-color: hsl(0, 0%, 50%);
}
input {
font: inherit;
border: 1px solid var(--c65);
border: 1px solid hsl(0, 0%, 66%);
transition: border-color .1s, box-shadow .1s;
}
input:not([type]),
input[type=text],
input[type=number],
input[type=search] {
background: var(--bg);
color: var(--fg);
height: var(--input-height);
min-height: var(--input-height)!important;
line-height: var(--input-height);
box-sizing: border-box;
background: #fff;
color: #000;
height: 22px;
min-height: 22px!important;
line-height: 22px;
padding: 0 3px;
border: 1px solid var(--c65);
}
input:invalid {
background-color: rgba(255, 0, 0, 0.1);
color: darkred;
border: 1px solid hsl(0, 0%, 66%);
}
.svg-icon {
@ -131,11 +83,11 @@ input:invalid {
transition: fill .5s;
width: 20px;
height: 20px;
fill: var(--c40);
fill: #666;
}
.svg-icon:hover {
fill: var(--fg);
fill: #000;
}
.svg-icon.info {
@ -154,7 +106,7 @@ input:invalid {
height: 8px;
width: 8px;
display: none;
fill: var(--fg);
fill: #000;
margin: 2px 0 0 2px;
}
@ -169,7 +121,7 @@ input[type="checkbox"]:not(.slider) {
position: absolute;
left: 0;
top: 0;
border: 1px solid var(--c45);
border: 1px solid hsl(0, 0%, 46%);
height: 12px;
width: 12px;
display: inline-flex;
@ -180,8 +132,8 @@ input[type="checkbox"]:not(.slider) {
}
input[type="checkbox"]:not(.slider):hover {
border-color: var(--c30);
background-color: var(--c80);
border-color: hsl(0, 0%, 32%);
background-color: hsl(0, 0%, 82%);
}
input[type="checkbox"]:not(.slider):checked + .svg-icon.checked {
@ -193,33 +145,29 @@ input[type="checkbox"]:not(.slider):checked + .svg-icon.checked {
input[type="checkbox"]:not(.slider):disabled {
background-color: transparent;
border-color: var(--c50);
border-color: hsl(0, 0%, 50%);
}
input[type="checkbox"]:not(.slider):disabled + .svg-icon.checked {
fill: var(--c50);
fill: hsl(0, 0%, 50%);
}
input[type="checkbox"]:not(.slider):disabled + .svg-icon.checked + span {
color: var(--c50);
color: hsl(0, 0%, 50%);
}
label {
transition: color .1s;
}
.checkbox-wrapper {
padding-left: 16px;
position: relative;
}
select {
-moz-appearance: none;
-webkit-appearance: none;
height: var(--input-height);
height: 22px;
font: inherit;
color: var(--fg);
color: #000;
background-color: transparent;
border: 1px solid var(--c65);
border: 1px solid hsl(0, 0%, 66%);
padding: 0 20px 0 6px;
transition: color .5s;
}
@ -237,7 +185,7 @@ select {
display: inline-flex;
height: 14px;
width: 14px;
fill: var(--fg);
fill: #000;
position: absolute;
top: 4px;
right: 4px;
@ -247,15 +195,15 @@ select {
input[type="radio"] {
-webkit-appearance: none;
-moz-appearance: none;
background: var(--c90);
background: hsl(0, 0%, 88%);
border-radius: 50%;
border: 1px solid var(--c60);
border: 1px solid hsl(0, 0%, 60%);
cursor: default;
height: 13px;
width: 13px;
position: relative;
margin: 0 4px 1px 0;
}
input[type="radio"]:after {
content: '';
background-color: transparent;
@ -269,15 +217,11 @@ input[type="radio"]:after {
top: 2px;
position: absolute;
}
input[type="radio"]:checked:after {
background-color: var(--c30);
background-color: hsl(0, 0%, 30%);
transform: scale(1);
}
.radio-wrapper {
display: flex;
align-items: center;
line-height: 1.5;
}
/* restore disabled state dimming */
button:disabled,
@ -289,7 +233,7 @@ select[disabled] > option {
select:disabled + .select-arrow,
select[disabled] + .select-arrow {
fill: var(--c50);
fill: hsl(0, 0%, 50%);
}
summary {
@ -301,32 +245,6 @@ summary {
.hidden {
display: none !important;
}
.rel {
position: relative;
}
.abs {
position: absolute;
}
html:not(.all-disabled) body:not(#stylus-popup) #disableAll-label:not([data-persist]) {
display: none;
}
html:not(.all-disabled) #disableAll-label::before {
content: attr(data-on);
}
.all-disabled #disableAll-label::before {
content: attr(data-off);
}
.all-disabled #disableAll-label {
font-weight: bold;
color: var(--red1);
}
.all-disabled #disableAll-label .svg-icon {
fill: var(--red1);
}
.all-disabled #disableAll {
border-color: var(--red1);
}
:focus,
.CodeMirror-focused,
@ -334,7 +252,6 @@ html:not(.all-disabled) #disableAll-label::before {
textarea[data-focused-via-click]:focus,
input:not([type])[data-focused-via-click]:focus, /* same as "text" */
input[type="text"][data-focused-via-click]:focus,
input[type="url"][data-focused-via-click]:focus,
input[type="search"][data-focused-via-click]:focus,
input[type="number"][data-focused-via-click]:focus {
/* Using box-shadow instead of the ugly outline in new Chrome */
@ -348,95 +265,24 @@ input[type="number"][data-focused-via-click]:focus {
box-shadow: none;
}
/* header resizer */
:root {
--header-width: 280px;
--header-resizer-width: 8px;
}
#header-resizer {
position: absolute;
top: 0;
right: 0;
bottom: 0;
width: var(--header-resizer-width);
box-sizing: border-box;
cursor: e-resize;
border-width: 0 1px;
border-style: solid;
color: hsla(0, 0%, 50%, .5);
border-color: currentColor;
pointer-events: auto;
}
#header-resizer:active {
border-color: var(--c50);
}
#header-resizer::after {
content: '';
position: absolute;
border-right: 2px dotted currentColor;
left: 2px;
width: 0;
height: 100%;
}
body.resizing-h {
cursor: e-resize;
}
body.resizing-v {
cursor: n-resize;
}
body.resizing-h > *,
body.resizing-v > * {
pointer-events: none;
-moz-user-select: none;
user-select: none;
}
/* header resizer - end */
.split-btn {
position: relative;
white-space: nowrap;
--menu-pad: .5em;
}
.split-btn-pedal {
margin-left: -1px !important;
padding-left: .25em !important;
padding-right: .25em !important;
min-width: 0 !important;
}
.split-btn-pedal::after {
--side: 4px;
content: '';
border: var(--side) solid transparent;
display: inline-block;
border-top: calc(var(--side) * 1.3) solid currentColor;
vertical-align: bottom;
}
.split-btn.active .split-btn-pedal {
box-shadow: inset 0 0 100px rgba(0, 0, 0, .2);
}
.split-btn-menu {
background: var(--bg);
position: absolute;
box-shadow: 2px 3px 7px rgba(0, 0, 0, .5);
border: 1px solid hsl(180deg, 50%, 50%);
white-space: nowrap;
cursor: pointer;
padding: .25em 0;
z-index: 1000;
}
.split-btn-menu > * {
padding: var(--menu-pad) 1em;
display: block;
}
.split-btn-menu > :hover {
background-color: hsla(180deg, 50%, 50%, .25);
color: var(--fg);
}
@supports (-moz-appearance: none) {
.moz-appearance-bug .svg-icon.checked,
.moz-appearance-bug .onoffswitch input,
.moz-appearance-bug input[type="radio"]:after {
display: none !important;
}
.moz-appearance-bug input[type="checkbox"] {
-moz-appearance: checkbox !important;
}
.moz-appearance-bug input[type="radio"] {
-moz-appearance: radio !important;
}
.firefox select {
padding: 0 20px 0 2px;
line-height: var(--input-height)!important;
line-height: 22px!important;
}
svg {
@ -446,9 +292,9 @@ body.resizing-v > * {
/* We can customize everything about number inputs except arrows. They're horrible in Linux FF, so we'll hide them unless hovered or focused. */
.firefox.non-windows input[type="number"] {
-moz-appearance: textfield;
background: var(--bg);
color: var(--fg);
border: 1px solid var(--c65);
background: #fff;
color: #000;
border: 1px solid hsl(0, 0%, 66%);
}
.firefox.non-windows input[type="number"]:not(:disabled):hover,
@ -457,14 +303,18 @@ body.resizing-v > * {
}
.firefox.non-windows input[type="color"] {
background: var(--bg);
border: 1px solid var(--c65);
background: #fff;
border: 1px solid hsl(0, 0%, 66%);
padding: 4px;
}
}
@media (max-width: 850px) {
#header-resizer {
display: none !important;
/* Firefox cannot handle fractions in font-size */
.firefox button:not(.install) {
line-height: 13px;
padding: 3px 7px;
}
.firefox.moz-appearance-bug button:not(.install) {
padding: 2px 4px;
}
}

View File

@ -1,103 +0,0 @@
.injection-order > div {
height: 100%;
max-width: 80vw;
}
.injection-order #incremental-search {
transform: scaleY(.55);
transform-origin: top;
}
.injection-order #message-box-contents,
.injection-order section {
padding: 0;
overflow: hidden;
display: flex;
flex-direction: column;
}
.injection-order section[data-main] {
flex: 1 0;
}
.injection-order header {
padding: 1rem;
width: 0;
min-width: 100%;
box-sizing: border-box;
}
.injection-order ol {
padding: 0;
margin: 0;
font-size: 14px;
overflow-y: auto;
}
.injection-order ol:empty {
display: none;
}
.injection-order [data-prio] header {
background-color: hsla(40, 80%, 50%, 0.4);
}
.injection-order [data-prio] {
height: min-content;
min-height: 2em;
max-height: 50%;
}
.injection-order-entry {
display: flex;
justify-content: space-between;
position: relative; /* for incremental-search */
padding: 1px 1px 1px 1rem; /* keyboard focus outline */
color: var(--fg);
transition: transform .25s ease-in-out;
z-index: 1;
user-select: none;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
cursor: move;
}
.injection-order-entry a[href] {
padding: .4em 0;
cursor: inherit;
}
.injection-order-entry.enabled a[href] {
font-weight: bold;
}
.injection-order-entry a {
text-decoration: none;
}
.injection-order-entry a[href]:hover {
text-decoration: underline;
}
.injection-order-toggle {
display: flex;
align-items: center;
padding: 0 .5rem;
cursor: pointer;
opacity: .5;
transition: .15s;
}
.injection-order-toggle::after {
content: '\2606';
font-size: 20px;
line-height: 1;
transition: .15s;
}
.injection-order-entry:hover .injection-order-toggle {
opacity: 1;
}
[data-prio] .injection-order-toggle::after {
content: '\2605';
}
.injection-order-toggle:hover::after {
color: hsl(30, 80%, 50%);
}
.injection-order [data-prio] header,
.injection-order ol,
.injection-order #message-box-buttons,
.injection-order-entry:nth-child(n + 2) {
border-top: 1px solid rgba(128, 128, 128, .25);
}
.injection-order .draggable-list-target {
position: relative;
background: lightcyan;
transition: none;
z-index: 100;
}

View File

@ -1,83 +0,0 @@
/* global $create messageBoxProxy */// dom.js
/* global API */// msg.js
/* global DraggableList */
/* global t */// localization.js
'use strict';
/* exported InjectionOrder */
async function InjectionOrder(show, el, selector) {
if (!show) {
return messageBoxProxy.close();
}
const SEL_ENTRY = '.injection-order-entry';
const groups = await API.styles.getAllOrdered(['_id', 'id', 'name', 'enabled']);
const ols = {};
const parts = {};
const entry = $create('li' + SEL_ENTRY, [
parts.name = $create('a', {
target: '_blank',
draggable: false,
}),
$create('a.injection-order-toggle', {
tabIndex: 0,
draggable: false,
title: t('styleInjectionImportance'),
}),
]);
await messageBoxProxy.show({
title: t('styleInjectionOrder'),
contents: $create('fragment', Object.entries(groups).map(makeList)),
className: 'center-dialog ' + selector.slice(1),
blockScroll: true,
buttons: [t('confirmClose')],
});
function makeEntry(style) {
entry.classList.toggle('enabled', style.enabled);
parts.name.href = '/edit.html?id=' + style.id;
parts.name.textContent = style.name;
return Object.assign(entry.cloneNode(true), {
styleNameLC: style.name.toLocaleLowerCase(),
});
}
function makeList([type, styles]) {
const ids = groups[type] = styles.map(s => s._id);
const ol = ols[type] = $create('ol.scroller');
let maxTranslateY;
ol.append(...styles.map(makeEntry));
ol.on('d:dragstart', ({detail: d}) => {
d.origin.dataTransfer.setDragImage(new Image(), 0, 0);
maxTranslateY =
ol.scrollHeight + ol.offsetTop - d.dragTarget.offsetHeight - d.dragTarget.offsetTop;
});
ol.on('d:dragmove', ({detail: d}) => {
d.origin.stopPropagation(); // preserves dropEffect
d.origin.dataTransfer.dropEffect = 'move';
const y = Math.min(d.currentPos.y - d.startPos.y, maxTranslateY);
d.dragTarget.style.transform = `translateY(${y}px)`;
});
ol.on('d:dragend', ({detail: d}) => {
const [item] = ids.splice(d.originalIndex, 1);
ids.splice(d.spliceIndex, 0, item);
ol.insertBefore(d.dragTarget, d.insertBefore);
API.styles.setOrder(groups);
});
ol.on('click', e => {
if (e.target.closest('.injection-order-toggle')) {
const el = e.target.closest(SEL_ENTRY);
const i = [].indexOf.call(el.parentNode.children, el);
const [item] = ids.splice(i, 1);
const type2 = type === 'main' ? 'prio' : 'main';
groups[type2].push(item);
ols[type2].appendChild(el);
API.styles.setOrder(groups);
}
});
DraggableList(ol, {scrollContainer: ol});
return $create('section', {dataset: {[type]: ''}}, [
$create('header', t(`styleInjectionOrderHint${type === 'main' ? '' : '_' + type}`)),
ol,
]);
}
}

View File

@ -7,125 +7,86 @@
<title>Loading...</title>
<link href="global.css" rel="stylesheet">
<link href="global-dark.css" rel="stylesheet">
<script src="js/polyfill.js"></script>
<script src="js/msg.js"></script>
<script src="js/toolbox.js"></script>
<script src="install-usercss/preinit.js"></script>
<script src="js/prefs.js"></script>
<script src="js/dom.js"></script>
<script src="js/localization.js"></script>
<script src="install-usercss/preinit.js"></script>
<script src="content/style-injector.js"></script>
<script src="content/apply.js"></script>
<template data-id="jumpToLine">
<span i18n="editGotoLine">: <input class="CodeMirror-jump-field" type="text"></span>
</template>
<script src="vendor/codemirror/lib/codemirror.js"></script>
<script src="vendor/codemirror/mode/css/css.js"></script>
<script src="vendor/codemirror/mode/stylus/stylus.js"></script>
<script src="vendor/codemirror/addon/dialog/dialog.js"></script>
<script src="vendor/codemirror/addon/edit/closebrackets.js"></script>
<script src="vendor/codemirror/addon/scroll/annotatescrollbar.js"></script>
<script src="vendor/codemirror/addon/search/searchcursor.js"></script>
<script src="vendor/codemirror/addon/search/matchesonscrollbar.js"></script>
<script src="vendor/codemirror/addon/comment/comment.js"></script>
<script src="vendor/codemirror/addon/selection/active-line.js"></script>
<script src="vendor/codemirror/addon/edit/matchbrackets.js"></script>
<script src="vendor/codemirror/addon/fold/foldcode.js"></script>
<script src="vendor/codemirror/addon/fold/foldgutter.js"></script>
<script src="vendor/codemirror/addon/fold/brace-fold.js"></script>
<script src="vendor/codemirror/addon/fold/indent-fold.js"></script>
<script src="vendor/codemirror/addon/fold/comment-fold.js"></script>
<script src="vendor/codemirror/addon/lint/lint.js"></script>
<script src="vendor/codemirror/addon/hint/show-hint.js"></script>
<script src="vendor/codemirror/addon/hint/css-hint.js"></script>
<script src="vendor/codemirror/keymap/sublime.js"></script>
<script src="vendor/codemirror/keymap/emacs.js"></script>
<script src="vendor/codemirror/keymap/vim.js"></script>
<script src="vendor-overwrites/codemirror-addon/match-highlighter.js"></script>
<script src="edit/codemirror-default.js"></script>
<script src="edit/codemirror-themes.js"></script>
<script src="js/color/color-converter.js"></script>
<script src="js/color/color-view.js"></script>
<script src="js/sections-util.js"></script>
<script src="js/cmpver.js"></script>
<link href="vendor/codemirror/lib/codemirror.css" rel="stylesheet">
<link href="vendor/codemirror/addon/dialog/dialog.css" rel="stylesheet">
<link href="vendor/codemirror/addon/fold/foldgutter.css" rel="stylesheet">
<link href="vendor/codemirror/addon/search/matchesonscrollbar.css" rel="stylesheet">
<link href="edit/codemirror-default.css" rel="stylesheet">
<link href="spinner.css" rel="stylesheet">
<link href="install-usercss/install-usercss.css" rel="stylesheet">
<script src="js/dark-themer.js"></script> <!-- must be last in HEAD to avoid FOUC -->
</head>
<body id="stylus-install-usercss">
<div class="container">
<div id="header">
<div id="header-contents">
<h1 class="w100">
<div id="header-content-wrapper">
<h1>
<span class="meta-name"></span>
<small class="meta-version"></small>
</h1>
<div id="install-wrapper">
<h2 class="install-show" i18n="installButtonInstalled"></h2>
<button class="install install-hide" i18n="installButton"></button>
<a class="configure-usercss" i18n="title:configureStyle" tabindex="0">
<div class="actions">
<h2 hidden class="installed" i18n-text="installButtonInstalled"></h2>
<button class="install" i18n-text="installButton"></button>
<a class="configure-usercss" i18n-title="configureStyle" tabindex="0">
<svg class="svg-icon config"><use xlink:href="#svg-icon-config"></use></svg>
</a>
<div class="install-show w100-full">
<a href="manage.html"><button i18n="openManage"></button></a>
<a id="edit" href="edit.html?id="><button i18n="editStyleLabel"></button></a>
<a id="delete" tabindex="0"><button i18n="deleteStyleLabel"></button></a>
</div>
</div>
<div id="ss-scheme" class="install-dim"></div>
<div class="set-update-url w100 checkbox-wrapper install-disable">
<label>
<p id="live-reload-install-hint" hidden></p>
<label class="set-update-url">
<input type="checkbox">
<svg class="svg-icon checked"><use xlink:href="#svg-icon-checked"/></svg>
<span i18n="installUpdateFromLabel"></span>
</label>
<span i18n-text="installUpdateFromLabel"></span>
<p></p>
</div>
<label class="live-reload checkbox-wrapper">
</label>
<label class="live-reload">
<input type="checkbox">
<svg class="svg-icon checked"><use xlink:href="#svg-icon-checked"/></svg>
<span i18n="liveReloadLabel"></span>
<span i18n-text="liveReloadLabel"></span>
</label>
<div id="live-reload-install-hint" class="w100" hidden></div>
<div class="meta-description w100 hide-empty"></div>
<label class="set-prefer-scheme">
<span i18n-text="installPreferSchemeLabel"></span>
<select>
<option value="none" i18n-text="installPreferSchemeNone"></option>
<option value="dark" i18n-text="installPreferSchemeDark"></option>
<option value="light" i18n-text="installPreferSchemeLight"></option>
</select>
</label>
<p hidden class="installed-actions">
<a href="manage.html" tabindex="0"><button i18n-text="openManage"></button></a>
<a href="edit.html?id=" tabindex="0"><button i18n-text="editStyleLabel"></button></a>
<a id="delete" tabindex="0"><button i18n-text="deleteStyleLabel"></button></a>
</p>
</div>
<p class="meta-description"></p>
<div>
<h3 i18n="author"></h3>
<h3 i18n-text="author"></h3>
<span class="meta-author"></span>
</div>
<div>
<h3 i18n="license"></h3>
<h3 i18n-text="license"></h3>
<span class="meta-license"></span>
</div>
<div class="external-link hide-empty"></div>
<div class="w100">
<h3 i18n="appliesLabel"></h3>
<div class="external-link"></div>
<div id="applies-to-wrapper">
<h3 i18n-text="appliesLabel"></h3>
<ul class="applies-to">
</ul>
</div>
</div>
<div id="header-resizer" i18n="title:headerResizerHint"></div>
</div>
<div class="main">
<div class="warnings"></div>
</div>
</div>
<svg xmlns="http://www.w3.org/2000/svg" style="display: none !important;">
<symbol id="svg-icon-help" viewBox="0 0 14 16" i18n="alt:helpAlt">
<circle cx="7" cy="5" r="1"/>
<path d="M8,8c0-0.5-0.5-1-1-1H6C5.5,7,5,7.4,5,8h1v3c0,0.5,0.5,1,1,1h1c0.5,0,1-0.4,1-1H8V8z"/>
<path d="M7,1c3.9,0,7,3.1,7,7s-3.1,7-7,7s-7-3.1-7-7S3.1,1,7,1z M7,2.3C3.9,2.3,1.3,4.9,1.3,8s2.6,5.7,5.7,5.7s5.7-2.6,5.7-5.7S10.1,2.3,7,2.3C7,2.3,7,2.3,7,2.3z"/>
</symbol>
<symbol id="svg-icon-checked" viewBox="0 0 1000 1000">
<path fill-rule="evenodd" d="M983.2,184.3L853,69.8c-4-3.5-9.3-5.3-14.5-5c-5.3,0.4-10.3,2.8-13.8,6.8L352.3,609.2L184.4,386.9c-3.2-4.2-8-7-13.2-7.8c-5.3-0.8-10.6,0.6-14.9,3.9L18,487.5c-8.8,6.7-10.6,19.3-3.9,28.1L325,927.2c3.6,4.8,9.3,7.7,15.3,8c0.2,0,0.5,0,0.7,0c5.8,0,11.3-2.5,15.1-6.8L985,212.6C992.3,204.3,991.5,191.6,983.2,184.3z"/>
</symbol>

View File

@ -1,16 +1,16 @@
body {
overflow: hidden;
display: flex;
height: 100vh;
margin: 0;
background: white;
}
a {
color: var(--fg);
color: #000;
transition: color .5s;
}
a:hover {
color: var(--c40);
color: #666;
}
img.icon {
@ -19,32 +19,29 @@ img.icon {
}
input:disabled + span {
color: var(--c50);
color: rgb(128, 128, 128);
}
.container {
display: flex;
height: 100vh;
}
#header,
.warnings {
flex: 0 0 var(--header-width);
flex: 0 0 280px;
box-sizing: border-box;
padding: 1rem;
border-right: 1px dashed #aaa;
box-shadow: 0 0 50px -18px black;
word-break: break-all;
overflow-wrap: break-word;
overflow-y: auto;
z-index: 100;
}
#header {
--child-gap: 1rem;
}
#header-contents > :nth-last-child(n + 2) {
margin-bottom: var(--child-gap);
}
#header.meta-init-error {
display: none;
}
#header-contents ul {
margin: 0;
}
.warnings {
display: none;
@ -52,7 +49,7 @@ input:disabled + span {
flex-basis: auto;
background: #ffe2e2;
border-right: none;
border-bottom: 1px dashed var(--c65);
border-bottom: 1px dashed #aaa;
}
.has-warnings .warnings {
@ -76,45 +73,31 @@ input:disabled + span {
h1 {
margin-top: 0;
display: flex;
align-items: baseline;
flex-wrap: wrap;
}
.meta-name {
margin-right: .5em;
}
.meta-version {
h1 small {
font-size: 0.6em;
white-space: nowrap;
}
.meta-version::before {
content: "v";
content: " v";
}
.checkbox-wrapper {
box-sizing: border-box;
display: block;
.actions {
margin-bottom: 1em;
}
.set-update-url p {
word-break: break-all;
opacity: .5;
margin: .25em 0 0;
}
#install-wrapper {
.actions label {
max-width: -moz-fit-content;
max-width: fit-content;
display: flex;
align-items: center;
flex-wrap: wrap;
}
#install-wrapper > :nth-last-child(n + 2) {
margin-right: .5rem;
}
#live-reload-install-hint {
color: darkcyan;
}
.w100 {
width: 100%;
margin: 0.5em 0;
}
.install {
font-family: Arial, "DejaVu Sans", Verdana, Geneva, sans-serif;
font-size: 14px;
background-color: hsl(0, 0%, 33%);
color: #eee;
border-radius: 4px;
@ -125,6 +108,7 @@ h1 {
-webkit-appearance: none;
-moz-appearance: none;
border-style: none;
margin-bottom: 1ex;
cursor: pointer;
box-shadow: inset 0 -1px 0 0 hsl(0, 0%, 24%), inset 0 1px 0 0 hsl(0, 0%, 30%), inset 1px 0 0 0 hsl(0, 0%, 24%);
transition: color .25s, background-color .25s;
@ -139,7 +123,7 @@ h1 {
.install:hover:not(:disabled) {
background-color: hsl(0, 0%, 38%);
color: var(--bg);
color: #fff;
text-shadow: none;
}
@ -229,25 +213,44 @@ h1 {
filter: hue-rotate(-18deg) brightness(.7) contrast(2);
}
h2 {
.install.installed {
display: none;
}
h2.installed.active {
display: inline-block;
font-weight: bold;
margin: 0;
margin-top: 0;
color: darkcyan;
}
.installed .configure-usercss svg {
h2.installed.active ~ .configure-usercss svg {
fill: hsl(180, 100%, 20%);
}
.installed .configure-usercss:hover svg {
h2.installed.active ~ .configure-usercss:hover svg {
fill: hsl(180, 100%, 30%);
}
#header-contents > .hide-empty:empty,
body:not(.installed) .install-show,
.installed .install-hide {
display: none !important;
.actions label input {
margin: 0 0.5em 0 0;
flex: 0 0 auto;
}
.installed .install-dim {
.actions label span {
min-width: 0;
}
.set-update-url {
flex-wrap: wrap;
}
.set-update-url p {
word-break: break-all;
opacity: .5;
width: 100%;
margin: .25em 0 .25em;
}
label.set-prefer-scheme:not(.unavailable) {
padding-left: 0;
}
.external {
@ -268,8 +271,8 @@ li {
.main,
.main .CodeMirror {
height: 100%;
width: 100%;
height: 100% !important;
width: 100% !important;
border: none;
}
@ -288,13 +291,22 @@ li {
}
#header.meta-init > * {
opacity: 1;
transition: opacity .5s;
-moz-user-select: auto;
user-select: auto;
}
.meta-init[data-arrived-fast="true"] > * {
#header.meta-init[data-arrived-fast="true"] > * {
transition-duration: .1s;
}
label {
/* FIXME: why do we want to give all labels a padding? */
padding-left: 16px;
position: relative;
}
.lds-spinner {
top: 50px;
opacity: .2;
@ -305,6 +317,7 @@ li {
.configure-usercss .svg-icon.config {
width: 20px;
height: 20px;
margin-top: -3px;
}
#message-box.config-dialog {
width: 0;
@ -319,71 +332,118 @@ li {
@media (max-width: 850px) {
body {
overflow: hidden;
}
.container {
flex-direction: column;
}
#header {
border-bottom: 1px dashed var(--c65);
min-height: 6rem;
max-height: 40vh;
resize: vertical;
flex: 0 1 auto;
--child-gap: .75rem;
border-right: none;
border-bottom: 1px dashed #AAA;
overflow-x: auto;
overflow-y: hidden;
padding: 0;
}
#header:not(.meta-init) {
min-height: 300px;
}
#header-contents {
.main {
flex: 1;
}
#header-content-wrapper {
display: flex;
flex-wrap: wrap;
align-content: flex-start;
align-items: flex-start;
padding: .5rem 0 0 1rem;
box-sizing: border-box;
height: min-content;
}
#header-contents > :not(.w100) {
margin-right: 1rem;
#header-content-wrapper > * {
flex-grow: 1;
margin: 0;
padding: 0 1rem .5rem 0;
min-width: 0;
}
.set-update-url {
#header-content-wrapper > .meta-description + .flex-wrapper {
display: flex;
justify-content: space-between;
flex-wrap: wrap;
padding: 0;
}
#header-content-wrapper > .meta-description + .flex-wrapper > * {
display: flex;
flex-direction: column;
flex: 1;
flex-wrap: wrap;
white-space: nowrap;
padding: 0 1rem .5rem 0;
box-sizing: border-box;
}
.set-update-url p {
margin: 0 0 0 1rem;
.flex-wrapper ul {
margin: 0;
}
#header-content-wrapper > .meta-description {
flex-basis: 100%;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.actions {
display: flex;
flex-wrap: wrap;
align-items: flex-start;
}
.set-update-url p {
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.actions label {
min-width: 100px;
flex: 1;
}
.actions label span {
white-space: nowrap;
}
.has-warnings #header {
min-height: 4em;
max-height: 20%;
}
.warnings {
max-height: 20%;
}
.warning:not(:last-child) {
border-bottom: 1px dashed #b57c7c;
padding-bottom: 1em;
}
#header-contents h3 {
ul.applies-to,
.actions label {
margin: 0;
}
#header-content-wrapper > h1 {
font-size: 1.75em;
display: flex;
align-items: baseline;
}
#header-content-wrapper > h1 > .meta-version {
padding-left: 3px;
}
#header-content-wrapper > h1 > .meta-name {
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
#header-content-wrapper > * h3 {
margin: 0 0 .5rem;
}
.install {
flex-shrink: 0;
margin-right: 1em;
}
#message-box.config-dialog > div {
top: auto;
bottom: 3rem;
}
h1 {
flex-wrap: nowrap;
}
.meta-name {
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.main {
height: auto;
flex: 1;
}
}
@media (min-width: 850px) {
#header {
height: 100% !important; /* overrides user resize */
}
.w100-full {
width: 100%;
margin-top: var(--child-gap);
}
}
/* Retina-specific stuff here */

View File

@ -1,19 +1,12 @@
/* global $$ $ $create $createLink $$remove showSpinner */// dom.js
/* global $ $create $createLink $$remove showSpinner */// dom.js
/* global API */// msg.js
/* global CODEMIRROR_THEMES */
/* global CodeMirror */
/* global URLS closeCurrentTab deepEqual */// toolbox.js
/* global compareVersion */// cmpver.js
/* global messageBox */
/* global prefs */
/* global preinit */
/* global styleCodeEmpty */// sections-util.js
/* global t */// localization.js
'use strict';
const CFG_SEL = '#message-box.config-dialog';
let cfgShown = true;
let cm;
let initialUrl;
let installed;
@ -42,16 +35,34 @@ setTimeout(() => !cm && showSpinner($('#header')), 200);
(async function init() {
const theme = prefs.get('editor.theme');
if (theme !== 'default') {
document.head.append($create('style', CODEMIRROR_THEMES[theme] || ''));
require([`/vendor/codemirror/theme/${theme}.css`]); // not awaiting as it may be absent
}
const scriptsReady = require([
'/vendor/codemirror/lib/codemirror', /* global CodeMirror */
]).then(() => require([
'/vendor/codemirror/keymap/sublime',
'/vendor/codemirror/keymap/emacs',
'/vendor/codemirror/keymap/vim', // TODO: load conditionally
'/vendor/codemirror/mode/css/css',
'/vendor/codemirror/addon/search/searchcursor',
'/vendor/codemirror/addon/fold/foldcode',
'/vendor/codemirror/addon/fold/foldgutter',
'/vendor/codemirror/addon/fold/brace-fold',
'/vendor/codemirror/addon/fold/indent-fold',
'/vendor/codemirror/addon/selection/active-line',
'/vendor/codemirror/lib/codemirror.css',
'/vendor/codemirror/addon/fold/foldgutter.css',
'/js/cmpver', /* global compareVersion */
'/js/sections-util', /* global styleCodeEmpty */
'/js/color/color-converter',
'/edit/codemirror-default.css',
])).then(() => require([
'/edit/codemirror-default',
'/js/color/color-view',
]));
({tabId, initialUrl} = preinit);
liveReload = initLiveReload();
preinit.tpl.then(el => {
$('#ss-scheme').append(...$('#ss-scheme', el).children);
prefs.subscribe('schemeSwitcher.enabled', (_, val) => {
$('#ss-scheme-off').hidden = val !== 'never';
}, {runNow: true});
});
const [
{dup, style, error, sourceCode},
@ -64,6 +75,7 @@ setTimeout(() => !cm && showSpinner($('#header')), 200);
messageBox.alert(isNaN(error) ? `${error}` : 'HTTP Error ' + error, 'pre');
return;
}
await scriptsReady;
cm = CodeMirror($('.main'), {
value: sourceCode || style.sourceCode,
readOnly: true,
@ -85,10 +97,12 @@ setTimeout(() => !cm && showSpinner($('#header')), 200);
// update UI
if (versionTest < 0) {
$('h1').after($create('.warning', t('versionInvalidOlder')));
$('.actions').parentNode.insertBefore(
$create('.warning', t('versionInvalidOlder')),
$('.actions')
);
}
$('button.install').onclick = () => {
shouldShowConfig();
(!dup ?
Promise.resolve(true) :
messageBox.confirm($create('span', t('styleInstallOverwrite', [
@ -122,9 +136,11 @@ setTimeout(() => !cm && showSpinner($('#header')), 200);
updateUrl.href.slice(0, 300) + '...';
// set prefer scheme
$('#ss-scheme').onchange = e => {
style.preferScheme = e.target.value;
const preferScheme = $('.set-prefer-scheme select');
preferScheme.onchange = () => {
style.preferScheme = preferScheme.value;
};
preferScheme.onchange();
if (URLS.isLocalhost(initialUrl)) {
$('.live-reload input').onchange = liveReload.onToggled;
@ -154,28 +170,41 @@ function updateMeta(style, dup = installedDup) {
!dup ? 'install' :
versionTest > 0 ? 'update' :
'reinstall');
$('.set-update-url').title = dup && dup.updateUrl &&
(t('installUpdateFrom', dup.updateUrl) || '').replace(/\S+$/, '\n$&');
$('.set-update-url').title = dup && dup.updateUrl && t('installUpdateFrom', dup.updateUrl) || '';
$('.meta-name').textContent = data.name;
$('.meta-version').textContent = data.version;
$('.meta-description').textContent = data.description;
$$('#ss-scheme input').forEach(el => {
el.checked = el.value === (style.preferScheme || 'none');
});
$('.set-prefer-scheme select').value =
style.preferScheme === 'dark' ? 'dark' :
style.preferScheme === 'light' ? 'light' : 'none';
replaceChildren($('.meta-author'), makeAuthor(data.author), true);
replaceChildren($('.meta-license'), data.license, true);
replaceChildren($('.external-link'), makeExternalLink());
if (data.author) {
$('.meta-author').parentNode.style.display = '';
$('.meta-author').textContent = '';
$('.meta-author').appendChild(makeAuthor(data.author));
} else {
$('.meta-author').parentNode.style.display = 'none';
}
$('.meta-license').parentNode.style.display = data.license ? '' : 'none';
$('.meta-license').textContent = data.license;
$('.applies-to').textContent = '';
getAppliesTo(style).then(list =>
replaceChildren($('.applies-to'), list.map(s => $create('li', s))));
$('.applies-to').append(...list.map(s => $create('li', s))));
$('.external-link').textContent = '';
const externalLink = makeExternalLink();
if (externalLink) {
$('.external-link').appendChild(externalLink);
}
Object.assign($('.configure-usercss'), {
hidden: !data.vars,
onclick: openConfigDialog,
});
if (!data.vars) {
cfgShown = false;
$$remove(CFG_SEL);
$$remove('#message-box.config-dialog');
} else if (!deepEqual(data.vars, vars)) {
vars = data.vars;
// Use the user-customized vars from the installed style
@ -185,8 +214,6 @@ function updateMeta(style, dup = installedDup) {
v.value = dv.value;
}
}
}
if (shouldShowConfig()) {
openConfigDialog();
}
@ -200,26 +227,26 @@ function updateMeta(style, dup = installedDup) {
if (dup) enablePostActions();
function makeAuthor(text) {
const match = text && text.match(/^(.+?)(?:\s+<(.+?)>)?(?:\s+\((.+?)\))?$/);
const match = text.match(/^(.+?)(?:\s+<(.+?)>)?(?:\s+\((.+?)\))?$/);
if (!match) {
return text;
return document.createTextNode(text);
}
const [, name, email, url] = match;
const elems = [];
const frag = document.createDocumentFragment();
if (email) {
elems.push($createLink(`mailto:${email}`, name));
frag.appendChild($createLink(`mailto:${email}`, name));
} else {
elems.push($create('span', name));
frag.appendChild($create('span', name));
}
if (url) {
elems.push($createLink(url,
frag.appendChild($createLink(url,
$create('SVG:svg.svg-icon', {viewBox: '0 0 20 20'},
$create('SVG:path', {
d: 'M4,4h5v2H6v8h8v-3h2v5H4V4z M11,3h6v6l-2-2l-4,4L9,9l4-4L11,3z',
}))
));
}
return elems;
return frag;
}
function makeExternalLink() {
@ -247,7 +274,7 @@ function updateMeta(style, dup = installedDup) {
function showError(err) {
$('.warnings').textContent = '';
$('.warnings').classList.toggle('visible', Boolean(err));
document.body.classList.toggle('has-warnings', Boolean(err));
$('.container').classList.toggle('has-warnings', Boolean(err));
err = Array.isArray(err) ? err : [err];
if (err[0]) {
let i;
@ -283,11 +310,12 @@ function install(style) {
$$remove('.warning');
$('button.install').disabled = true;
$('button.install').classList.add('installed');
$('#live-reload-install-hint').hidden = !liveReload.enabled;
$('#live-reload-install-hint').classList.toggle('hidden', !liveReload.enabled);
$('h2.installed').classList.add('active');
$('.set-update-url input[type=checkbox]').disabled = true;
$('.set-update-url').title = style.updateUrl ?
t('installUpdateFrom', style.updateUrl) : '';
$$('.install-disable input').forEach(el => (el.disabled = true));
document.body.classList.add('installed');
$('.set-prefer-scheme select').disabled = true;
enablePostActions();
updateMeta(style);
}
@ -295,7 +323,9 @@ function install(style) {
function enablePostActions() {
const {id} = installed || installedDup;
sessionStorage.justEditedStyleId = id;
$('#edit').search = `?id=${id}`;
$('h2.installed').hidden = !installed;
$('.installed-actions').hidden = false;
$('.installed-actions a[href*="edit.html"]').search = `?id=${id}`;
$('#delete').onclick = async () => {
if (await messageBox.confirm(t('deleteStyleConfirm'), 'danger center', t('confirmDelete'))) {
await API.styles.delete(id);
@ -404,16 +434,3 @@ function initLiveReload() {
});
}
}
function shouldShowConfig() {
// TODO: rewrite message-box to support multiple instances or find an existing tiny library
const prev = cfgShown;
cfgShown = $(CFG_SEL) != null;
return prev && !cfgShown;
}
function replaceChildren(el, children, toggleParent) {
if (el.firstChild) el.textContent = '';
if (children) el.append(...Array.isArray(children) ? children : [children]);
if (toggleParent) el.parentNode.hidden = !el.firstChild;
}

View File

@ -1,6 +1,5 @@
/* global API */// msg.js
/* global closeCurrentTab download */// toolbox.js
/* global t */// localization.js
'use strict';
/* exported preinit */
@ -90,7 +89,5 @@ const preinit = (() => {
return {error, sourceCode};
}
})(),
tpl: t.fetchTemplate('/edit/settings.html', 'styleSettings'),
};
})();

View File

@ -1,75 +1,30 @@
'use strict';
const colorConverter = (NAMED_COLORS => {
// All groups in RXS_NUM must use ?: in order to enable \1 in RX_COLOR.rgb
const RXS_NUM = /\s*[+-]?(\.\d+|\d+(\.\d*)?)(e[+-]?\d+)?/.source.replace(/\(/g, '(?:');
const RXS_ANGLE = '(?:deg|g?rad|turn)?';
const expandRe = re => RegExp(re.source.replace(/N/g, RXS_NUM).replace(/A/g, RXS_ANGLE), 'iy');
const RX_COLOR = {
hex: /#([a-f\d]{3}(?:[a-f\d](?:[a-f\d]{2}){0,2})?)\b/iy,
// num_or_angle, pct, pct [ , num_or_pct]?
// num_or_angle pct pct [ / num_or_pct]?
hsl: expandRe(/^NA(\s*(,N%\s*){2}(,N%?\s*)?|(\s+N%){2}\s*(\/N%?\s*)?)$/),
// num_or_angle|none pct|none pct|none [ / num_or_pct|none ]?
hwb: expandRe(/^(NA|none)(\s+(N%|none)){2}\s*(\/(N%?|none)\s*)?$/),
// num, num, num [ , num_or_pct]?
// pct, pct, pct [ , num_or_pct]?
// num num num [ / num_or_pct]?
// pct pct pct [ / num_or_pct]?
rgb: expandRe(/^N(%?)(\s*,N\1\s*,N\1\s*(,N%?\s*)?|\s+N\1\s+N\1\s*(\/N%?\s*)?)$/),
};
const ANGLE_TO_DEG = {
grad: 360 / 400,
rad: 180 / Math.PI,
turn: 360,
};
const TO_HSV = {
hex: RGBtoHSV,
hsl: HSLtoHSV,
hwb: HWBtoHSV,
rgb: RGBtoHSV,
};
const FROM_HSV = {
hex: HSVtoRGB,
hsl: HSVtoHSL,
hwb: HSVtoHWB,
rgb: HSVtoRGB,
};
const guessType = c =>
'r' in c ? 'rgb' :
'w' in c ? 'hwb' :
'v' in c ? 'hsv' :
'l' in c ? 'hsl' :
undefined;
let HEX;
const colorConverter = (() => {
return {
parse,
format,
formatAlpha,
fromHSV: (color, type) => FROM_HSV[type](color),
toHSV: color => TO_HSV[color.type || 'rgb'](color),
constrain,
RGBtoHSV,
HSVtoRGB,
HSLtoHSV,
HSVtoHSL,
constrainHue,
guessType,
snapToInt,
testAt,
ALPHA_DIGITS: 3,
NAMED_COLORS,
RX_COLOR,
// NAMED_COLORS is added below
};
function format(color = '', type = color.type, {hexUppercase, usoMode, round} = {}) {
function format(color = '', type = color.type, hexUppercase, usoMode) {
if (!color || !type) return typeof color === 'string' ? color : '';
const {a, type: src = guessType(color)} = color;
const aFmt = formatAlpha(a);
const aStr = aFmt ? ', ' + aFmt : '';
const srcConv = src === 'hex' ? 'rgb' : src;
const dstConv = type === 'hex' ? 'rgb' : type;
color = srcConv === dstConv ? color : FROM_HSV[dstConv](TO_HSV[srcConv](color));
round = round ? Math.round : v => v;
const {r, g, b, h, s, l, w} = color;
const {a} = color;
let aStr = formatAlpha(a);
if (aStr) aStr = ', ' + aStr;
if (type !== 'hsl' && color.type === 'hsl') {
color = HSVtoRGB(HSLtoHSV(color));
}
const {r, g, b, h, s, l} = color;
switch (type) {
case 'hex': {
let res = '#' + hex2(r) + hex2(g) + hex2(b) + (aStr ? hex2(Math.round(a * 255)) : '');
@ -81,77 +36,101 @@ const colorConverter = (NAMED_COLORS => {
return usoMode ? rgb : `rgb${aStr ? 'a' : ''}(${rgb}${aStr})`;
}
case 'hsl':
return `hsl${aStr ? 'a' : ''}(${round(h)}, ${round(s)}%, ${round(l)}%${aStr})`;
case 'hwb':
return `hwb(${round(h)} ${round(w)}% ${round(b)}%${aFmt ? ' / ' + aFmt : ''})`;
return `hsl${aStr ? 'a' : ''}(${h}, ${s}%, ${l}%${aStr})`;
}
}
function parse(s) {
if (typeof s !== 'string' || !(s = s.trim())) {
return;
} else if (s[0] === '#') {
return parseHex(s);
} else if (s.endsWith(')') && (s = s.match(/^(hwb|(hsl|rgb)a?)\(\s*([^)]+)/i))) {
return parseFunc((s[2] || s[1]).toLowerCase(), s[3]);
// Copied from _hexcolor() in parserlib.js
function validateHex(color) {
return /^#[a-f\d]+$/i.test(color) && [4, 5, 7, 9].some(n => color.length === n);
}
function validateRGB(nums) {
const isPercentage = nums[0].endsWith('%');
const valid = isPercentage ? validatePercentage : validateNum;
return nums.slice(0, 3).every(valid);
}
function validatePercentage(s) {
if (!s.endsWith('%')) return false;
const n = Number(s.slice(0, -1));
return n >= 0 && n <= 100;
}
function validateNum(s) {
const n = Number(s);
return n >= 0 && n <= 255;
}
function validateHSL(nums) {
return validateAngle(nums[0]) && nums.slice(1, 3).every(validatePercentage);
}
function validateAngle(s) {
return /^-?(\d+|\d*\.\d+)(deg|grad|rad|turn)?$/i.test(s);
}
function validateAlpha(alpha) {
if (alpha.endsWith('%')) {
return validatePercentage(alpha);
}
const n = Number(alpha);
return n >= 0 && n <= 1;
}
function parse(str) {
if (typeof str !== 'string') return;
str = str.trim();
if (!str) return;
if (str[0] !== '#' && !str.includes('(')) {
// eslint-disable-next-line no-use-before-define
str = colorConverter.NAMED_COLORS.get(str);
if (!str) return;
}
if (str[0] === '#') {
if (!validateHex(str)) {
return null;
}
str = str.slice(1);
const [r, g, b, a = 255] = str.length <= 4 ?
str.match(/(.)/g).map(c => parseInt(c + c, 16)) :
str.match(/(..)/g).map(c => parseInt(c, 16));
return {type: 'hex', r, g, b, a: a === 255 ? undefined : a / 255};
}
const [, type, value] = str.match(/^(rgb|hsl)a?\((.*?)\)|$/i);
if (!type) return;
const comma = value.includes(',') && !value.includes('/');
const num = value.trim().split(comma ? /\s*,\s*/ : /\s+(?!\/)|\s*\/\s*/);
if (num.length < 3 || num.length > 4) return;
if (num[3] && !validateAlpha(num[3])) return null;
let a = !num[3] ? 1 : parseFloat(num[3]) / (num[3].endsWith('%') ? 100 : 1);
if (isNaN(a)) a = 1;
const first = num[0];
if (/rgb/i.test(type)) {
if (!validateRGB(num)) {
return null;
}
const k = first.endsWith('%') ? 2.55 : 1;
const [r, g, b] = num.map(s => Math.round(parseFloat(s) * k));
return {type: 'rgb', r, g, b, a};
} else {
return colorConverter.NAMED_COLORS.get(s.toLowerCase());
if (!validateHSL(num)) {
return null;
}
let h = parseFloat(first);
if (first.endsWith('grad')) h *= 360 / 400;
else if (first.endsWith('rad')) h *= 180 / Math.PI;
else if (first.endsWith('turn')) h *= 360;
const s = parseFloat(num[1]);
const l = parseFloat(num[2]);
return {type: 'hsl', h, s, l, a};
}
function initHexMap() {
HEX = Array(256).fill(-0xFFFF); // ensuring a PACKED_SMI array
for (let i = 48; i < 58; i++) HEX[i] = i - 48; // 0123456789
for (let i = 65; i < 71; i++) HEX[i] = i - 65 + 10; // ABCDEF
for (let i = 97; i < 103; i++) HEX[i] = i - 97 + 10; // abcdef
}
function parseHex(str) {
if (!HEX) initHexMap();
let r, g, b, a;
const len = str.length;
if (len === 4 || len === 5
? (r = HEX[str.charCodeAt(1)] * 0x11) >= 0 &&
(g = HEX[str.charCodeAt(2)] * 0x11) >= 0 &&
(b = HEX[str.charCodeAt(3)] * 0x11) >= 0 &&
(len < 5 || (a = HEX[str.charCodeAt(4)] * 0x11 / 255) >= 0)
: (len === 7 || len === 9) &&
(r = HEX[str.charCodeAt(1)] * 0x10 + HEX[str.charCodeAt(2)]) >= 0 &&
(g = HEX[str.charCodeAt(3)] * 0x10 + HEX[str.charCodeAt(4)]) >= 0 &&
(b = HEX[str.charCodeAt(5)] * 0x10 + HEX[str.charCodeAt(6)]) >= 0 &&
(len < 9 || (a = (HEX[str.charCodeAt(7)] * 0x10 + HEX[str.charCodeAt(8)]) / 255) >= 0)
) {
return {type: 'hex', r, g, b, a};
}
}
function parseFunc(type, val) {
if (!testAt(RX_COLOR[type], 0, val)) {
return;
}
// Not using destructuring because it's slow
const parts = val.trim().split(/\s*[,/]\s*|\s+/);
const n1 = parseFloat(parts[0]);
const n2 = parseFloat(parts[1]);
const n3 = parseFloat(parts[2]);
const nA = parseFloat(parts[3]);
const a = isNaN(nA) ? undefined : constrain(0, 1, parts[3].endsWith('%') ? nA / 100 : nA);
if (type === 'rgb') {
const k = parts[0].endsWith('%') ? 2.55 : 1;
return {
type,
r: constrain(0, 255, Math.round(n1 * k)),
g: constrain(0, 255, Math.round(n2 * k)),
b: constrain(0, 255, Math.round(n3 * k)),
a,
};
}
const h = constrainHue(n1 * (ANGLE_TO_DEG[parts[0].match(/\D*$/)[0].toLowerCase()] || 1));
const n2c = constrain(0, 100, n2 || 0);
const n3c = constrain(0, 100, n3 || 0);
return type === 'hwb'
? {type, h, w: n2c, b: n3c, a}
: {type, h, s: n2c, l: n3c, a};
}
function formatAlpha(a) {
@ -185,8 +164,8 @@ const colorConverter = (NAMED_COLORS => {
};
}
function HSVtoRGB({h, s, v, a}) {
h = constrainHue(h);
function HSVtoRGB({h, s, v}) {
h = constrainHue(h) % 360;
const C = s * v;
const X = C * (1 - Math.abs((h / 60) % 2 - 1));
const m = v - C;
@ -201,11 +180,9 @@ const colorConverter = (NAMED_COLORS => {
r: snapToInt(Math.round((r + m) * 255)),
g: snapToInt(Math.round((g + m) * 255)),
b: snapToInt(Math.round((b + m) * 255)),
a,
};
}
function HSLtoHSV({h, s, l, a}) {
const t = s * (l < 50 ? l : 100 - l) / 100;
return {
@ -216,41 +193,16 @@ const colorConverter = (NAMED_COLORS => {
};
}
function HSVtoHSL({h, s, v, a}) {
function HSVtoHSL({h, s, v}) {
const l = (2 - s) * v / 2;
const t = l < .5 ? l * 2 : 2 - l * 2;
return {
h: constrainHue(h),
s: t ? s * v / t * 100 : 0,
l: l * 100,
a,
h: Math.round(constrainHue(h)),
s: Math.round(t ? s * v / t * 100 : 0),
l: Math.round(l * 100),
};
}
function HWBtoHSV({h, w, b, a}) {
w = constrain(0, 100, w) / 100;
b = constrain(0, 100, b) / 100;
return {
h: constrainHue(h),
s: b === 1 ? 0 : 1 - w / (1 - b),
v: 1 - b,
a,
};
}
function HSVtoHWB({h, s, v, a}) {
return {
h: constrainHue(h),
w: (1 - s) * v * 100,
b: (1 - v) * 100,
a,
};
}
function constrain(min, max, value) {
return value < min ? min : value > max ? max : value;
}
function constrainHue(h) {
return h < 0 ? h % 360 + 360 :
h > 360 ? h % 360 :
@ -263,162 +215,159 @@ const colorConverter = (NAMED_COLORS => {
}
function hex2(val) {
return (val < 16 ? '0' : '') + Math.round(val).toString(16);
return (val < 16 ? '0' : '') + (val >> 0).toString(16);
}
})();
function testAt(rx, index, text) {
if (!rx) return false;
rx.lastIndex = index;
return rx.test(text);
}
})(new Map([
['transparent', {r: 0, g: 0, b: 0, a: 0, type: 'rgb'}],
['aliceblue', {r: 240, g: 248, b: 255, type: 'hex'}],
['antiquewhite', {r: 250, g: 235, b: 215, type: 'hex'}],
['aqua', {r: 0, g: 255, b: 255, type: 'hex'}],
['aquamarine', {r: 127, g: 255, b: 212, type: 'hex'}],
['azure', {r: 240, g: 255, b: 255, type: 'hex'}],
['beige', {r: 245, g: 245, b: 220, type: 'hex'}],
['bisque', {r: 255, g: 228, b: 196, type: 'hex'}],
['black', {r: 0, g: 0, b: 0, type: 'hex'}],
['blanchedalmond', {r: 255, g: 235, b: 205, type: 'hex'}],
['blue', {r: 0, g: 0, b: 255, type: 'hex'}],
['blueviolet', {r: 138, g: 43, b: 226, type: 'hex'}],
['brown', {r: 165, g: 42, b: 42, type: 'hex'}],
['burlywood', {r: 222, g: 184, b: 135, type: 'hex'}],
['cadetblue', {r: 95, g: 158, b: 160, type: 'hex'}],
['chartreuse', {r: 127, g: 255, b: 0, type: 'hex'}],
['chocolate', {r: 210, g: 105, b: 30, type: 'hex'}],
['coral', {r: 255, g: 127, b: 80, type: 'hex'}],
['cornflowerblue', {r: 100, g: 149, b: 237, type: 'hex'}],
['cornsilk', {r: 255, g: 248, b: 220, type: 'hex'}],
['crimson', {r: 220, g: 20, b: 60, type: 'hex'}],
['cyan', {r: 0, g: 255, b: 255, type: 'hex'}],
['darkblue', {r: 0, g: 0, b: 139, type: 'hex'}],
['darkcyan', {r: 0, g: 139, b: 139, type: 'hex'}],
['darkgoldenrod', {r: 184, g: 134, b: 11, type: 'hex'}],
['darkgray', {r: 169, g: 169, b: 169, type: 'hex'}],
['darkgrey', {r: 169, g: 169, b: 169, type: 'hex'}],
['darkgreen', {r: 0, g: 100, b: 0, type: 'hex'}],
['darkkhaki', {r: 189, g: 183, b: 107, type: 'hex'}],
['darkmagenta', {r: 139, g: 0, b: 139, type: 'hex'}],
['darkolivegreen', {r: 85, g: 107, b: 47, type: 'hex'}],
['darkorange', {r: 255, g: 140, b: 0, type: 'hex'}],
['darkorchid', {r: 153, g: 50, b: 204, type: 'hex'}],
['darkred', {r: 139, g: 0, b: 0, type: 'hex'}],
['darksalmon', {r: 233, g: 150, b: 122, type: 'hex'}],
['darkseagreen', {r: 143, g: 188, b: 143, type: 'hex'}],
['darkslateblue', {r: 72, g: 61, b: 139, type: 'hex'}],
['darkslategray', {r: 47, g: 79, b: 79, type: 'hex'}],
['darkslategrey', {r: 47, g: 79, b: 79, type: 'hex'}],
['darkturquoise', {r: 0, g: 206, b: 209, type: 'hex'}],
['darkviolet', {r: 148, g: 0, b: 211, type: 'hex'}],
['deeppink', {r: 255, g: 20, b: 147, type: 'hex'}],
['deepskyblue', {r: 0, g: 191, b: 255, type: 'hex'}],
['dimgray', {r: 105, g: 105, b: 105, type: 'hex'}],
['dimgrey', {r: 105, g: 105, b: 105, type: 'hex'}],
['dodgerblue', {r: 30, g: 144, b: 255, type: 'hex'}],
['firebrick', {r: 178, g: 34, b: 34, type: 'hex'}],
['floralwhite', {r: 255, g: 250, b: 240, type: 'hex'}],
['forestgreen', {r: 34, g: 139, b: 34, type: 'hex'}],
['fuchsia', {r: 255, g: 0, b: 255, type: 'hex'}],
['gainsboro', {r: 220, g: 220, b: 220, type: 'hex'}],
['ghostwhite', {r: 248, g: 248, b: 255, type: 'hex'}],
['gold', {r: 255, g: 215, b: 0, type: 'hex'}],
['goldenrod', {r: 218, g: 165, b: 32, type: 'hex'}],
['gray', {r: 128, g: 128, b: 128, type: 'hex'}],
['grey', {r: 128, g: 128, b: 128, type: 'hex'}],
['green', {r: 0, g: 128, b: 0, type: 'hex'}],
['greenyellow', {r: 173, g: 255, b: 47, type: 'hex'}],
['honeydew', {r: 240, g: 255, b: 240, type: 'hex'}],
['hotpink', {r: 255, g: 105, b: 180, type: 'hex'}],
['indianred', {r: 205, g: 92, b: 92, type: 'hex'}],
['indigo', {r: 75, g: 0, b: 130, type: 'hex'}],
['ivory', {r: 255, g: 255, b: 240, type: 'hex'}],
['khaki', {r: 240, g: 230, b: 140, type: 'hex'}],
['lavender', {r: 230, g: 230, b: 250, type: 'hex'}],
['lavenderblush', {r: 255, g: 240, b: 245, type: 'hex'}],
['lawngreen', {r: 124, g: 252, b: 0, type: 'hex'}],
['lemonchiffon', {r: 255, g: 250, b: 205, type: 'hex'}],
['lightblue', {r: 173, g: 216, b: 230, type: 'hex'}],
['lightcoral', {r: 240, g: 128, b: 128, type: 'hex'}],
['lightcyan', {r: 224, g: 255, b: 255, type: 'hex'}],
['lightgoldenrodyellow', {r: 250, g: 250, b: 210, type: 'hex'}],
['lightgray', {r: 211, g: 211, b: 211, type: 'hex'}],
['lightgrey', {r: 211, g: 211, b: 211, type: 'hex'}],
['lightgreen', {r: 144, g: 238, b: 144, type: 'hex'}],
['lightpink', {r: 255, g: 182, b: 193, type: 'hex'}],
['lightsalmon', {r: 255, g: 160, b: 122, type: 'hex'}],
['lightseagreen', {r: 32, g: 178, b: 170, type: 'hex'}],
['lightskyblue', {r: 135, g: 206, b: 250, type: 'hex'}],
['lightslategray', {r: 119, g: 136, b: 153, type: 'hex'}],
['lightslategrey', {r: 119, g: 136, b: 153, type: 'hex'}],
['lightsteelblue', {r: 176, g: 196, b: 222, type: 'hex'}],
['lightyellow', {r: 255, g: 255, b: 224, type: 'hex'}],
['lime', {r: 0, g: 255, b: 0, type: 'hex'}],
['limegreen', {r: 50, g: 205, b: 50, type: 'hex'}],
['linen', {r: 250, g: 240, b: 230, type: 'hex'}],
['magenta', {r: 255, g: 0, b: 255, type: 'hex'}],
['maroon', {r: 128, g: 0, b: 0, type: 'hex'}],
['mediumaquamarine', {r: 102, g: 205, b: 170, type: 'hex'}],
['mediumblue', {r: 0, g: 0, b: 205, type: 'hex'}],
['mediumorchid', {r: 186, g: 85, b: 211, type: 'hex'}],
['mediumpurple', {r: 147, g: 112, b: 219, type: 'hex'}],
['mediumseagreen', {r: 60, g: 179, b: 113, type: 'hex'}],
['mediumslateblue', {r: 123, g: 104, b: 238, type: 'hex'}],
['mediumspringgreen', {r: 0, g: 250, b: 154, type: 'hex'}],
['mediumturquoise', {r: 72, g: 209, b: 204, type: 'hex'}],
['mediumvioletred', {r: 199, g: 21, b: 133, type: 'hex'}],
['midnightblue', {r: 25, g: 25, b: 112, type: 'hex'}],
['mintcream', {r: 245, g: 255, b: 250, type: 'hex'}],
['mistyrose', {r: 255, g: 228, b: 225, type: 'hex'}],
['moccasin', {r: 255, g: 228, b: 181, type: 'hex'}],
['navajowhite', {r: 255, g: 222, b: 173, type: 'hex'}],
['navy', {r: 0, g: 0, b: 128, type: 'hex'}],
['oldlace', {r: 253, g: 245, b: 230, type: 'hex'}],
['olive', {r: 128, g: 128, b: 0, type: 'hex'}],
['olivedrab', {r: 107, g: 142, b: 35, type: 'hex'}],
['orange', {r: 255, g: 165, b: 0, type: 'hex'}],
['orangered', {r: 255, g: 69, b: 0, type: 'hex'}],
['orchid', {r: 218, g: 112, b: 214, type: 'hex'}],
['palegoldenrod', {r: 238, g: 232, b: 170, type: 'hex'}],
['palegreen', {r: 152, g: 251, b: 152, type: 'hex'}],
['paleturquoise', {r: 175, g: 238, b: 238, type: 'hex'}],
['palevioletred', {r: 219, g: 112, b: 147, type: 'hex'}],
['papayawhip', {r: 255, g: 239, b: 213, type: 'hex'}],
['peachpuff', {r: 255, g: 218, b: 185, type: 'hex'}],
['peru', {r: 205, g: 133, b: 63, type: 'hex'}],
['pink', {r: 255, g: 192, b: 203, type: 'hex'}],
['plum', {r: 221, g: 160, b: 221, type: 'hex'}],
['powderblue', {r: 176, g: 224, b: 230, type: 'hex'}],
['purple', {r: 128, g: 0, b: 128, type: 'hex'}],
['rebeccapurple', {r: 102, g: 51, b: 153, type: 'hex'}],
['red', {r: 255, g: 0, b: 0, type: 'hex'}],
['rosybrown', {r: 188, g: 143, b: 143, type: 'hex'}],
['royalblue', {r: 65, g: 105, b: 225, type: 'hex'}],
['saddlebrown', {r: 139, g: 69, b: 19, type: 'hex'}],
['salmon', {r: 250, g: 128, b: 114, type: 'hex'}],
['sandybrown', {r: 244, g: 164, b: 96, type: 'hex'}],
['seagreen', {r: 46, g: 139, b: 87, type: 'hex'}],
['seashell', {r: 255, g: 245, b: 238, type: 'hex'}],
['sienna', {r: 160, g: 82, b: 45, type: 'hex'}],
['silver', {r: 192, g: 192, b: 192, type: 'hex'}],
['skyblue', {r: 135, g: 206, b: 235, type: 'hex'}],
['slateblue', {r: 106, g: 90, b: 205, type: 'hex'}],
['slategray', {r: 112, g: 128, b: 144, type: 'hex'}],
['slategrey', {r: 112, g: 128, b: 144, type: 'hex'}],
['snow', {r: 255, g: 250, b: 250, type: 'hex'}],
['springgreen', {r: 0, g: 255, b: 127, type: 'hex'}],
['steelblue', {r: 70, g: 130, b: 180, type: 'hex'}],
['tan', {r: 210, g: 180, b: 140, type: 'hex'}],
['teal', {r: 0, g: 128, b: 128, type: 'hex'}],
['thistle', {r: 216, g: 191, b: 216, type: 'hex'}],
['tomato', {r: 255, g: 99, b: 71, type: 'hex'}],
['turquoise', {r: 64, g: 224, b: 208, type: 'hex'}],
['violet', {r: 238, g: 130, b: 238, type: 'hex'}],
['wheat', {r: 245, g: 222, b: 179, type: 'hex'}],
['white', {r: 255, g: 255, b: 255, type: 'hex'}],
['whitesmoke', {r: 245, g: 245, b: 245, type: 'hex'}],
['yellow', {r: 255, g: 255, b: 0, type: 'hex'}],
['yellowgreen', {r: 154, g: 205, b: 50, type: 'hex'}],
]));
colorConverter.NAMED_COLORS = new Map([
['transparent', 'rgba(0, 0, 0, 0)'],
// CSS4 named colors
['aliceblue', '#f0f8ff'],
['antiquewhite', '#faebd7'],
['aqua', '#00ffff'],
['aquamarine', '#7fffd4'],
['azure', '#f0ffff'],
['beige', '#f5f5dc'],
['bisque', '#ffe4c4'],
['black', '#000000'],
['blanchedalmond', '#ffebcd'],
['blue', '#0000ff'],
['blueviolet', '#8a2be2'],
['brown', '#a52a2a'],
['burlywood', '#deb887'],
['cadetblue', '#5f9ea0'],
['chartreuse', '#7fff00'],
['chocolate', '#d2691e'],
['coral', '#ff7f50'],
['cornflowerblue', '#6495ed'],
['cornsilk', '#fff8dc'],
['crimson', '#dc143c'],
['cyan', '#00ffff'],
['darkblue', '#00008b'],
['darkcyan', '#008b8b'],
['darkgoldenrod', '#b8860b'],
['darkgray', '#a9a9a9'],
['darkgrey', '#a9a9a9'],
['darkgreen', '#006400'],
['darkkhaki', '#bdb76b'],
['darkmagenta', '#8b008b'],
['darkolivegreen', '#556b2f'],
['darkorange', '#ff8c00'],
['darkorchid', '#9932cc'],
['darkred', '#8b0000'],
['darksalmon', '#e9967a'],
['darkseagreen', '#8fbc8f'],
['darkslateblue', '#483d8b'],
['darkslategray', '#2f4f4f'],
['darkslategrey', '#2f4f4f'],
['darkturquoise', '#00ced1'],
['darkviolet', '#9400d3'],
['deeppink', '#ff1493'],
['deepskyblue', '#00bfff'],
['dimgray', '#696969'],
['dimgrey', '#696969'],
['dodgerblue', '#1e90ff'],
['firebrick', '#b22222'],
['floralwhite', '#fffaf0'],
['forestgreen', '#228b22'],
['fuchsia', '#ff00ff'],
['gainsboro', '#dcdcdc'],
['ghostwhite', '#f8f8ff'],
['gold', '#ffd700'],
['goldenrod', '#daa520'],
['gray', '#808080'],
['grey', '#808080'],
['green', '#008000'],
['greenyellow', '#adff2f'],
['honeydew', '#f0fff0'],
['hotpink', '#ff69b4'],
['indianred', '#cd5c5c'],
['indigo', '#4b0082'],
['ivory', '#fffff0'],
['khaki', '#f0e68c'],
['lavender', '#e6e6fa'],
['lavenderblush', '#fff0f5'],
['lawngreen', '#7cfc00'],
['lemonchiffon', '#fffacd'],
['lightblue', '#add8e6'],
['lightcoral', '#f08080'],
['lightcyan', '#e0ffff'],
['lightgoldenrodyellow', '#fafad2'],
['lightgray', '#d3d3d3'],
['lightgrey', '#d3d3d3'],
['lightgreen', '#90ee90'],
['lightpink', '#ffb6c1'],
['lightsalmon', '#ffa07a'],
['lightseagreen', '#20b2aa'],
['lightskyblue', '#87cefa'],
['lightslategray', '#778899'],
['lightslategrey', '#778899'],
['lightsteelblue', '#b0c4de'],
['lightyellow', '#ffffe0'],
['lime', '#00ff00'],
['limegreen', '#32cd32'],
['linen', '#faf0e6'],
['magenta', '#ff00ff'],
['maroon', '#800000'],
['mediumaquamarine', '#66cdaa'],
['mediumblue', '#0000cd'],
['mediumorchid', '#ba55d3'],
['mediumpurple', '#9370db'],
['mediumseagreen', '#3cb371'],
['mediumslateblue', '#7b68ee'],
['mediumspringgreen', '#00fa9a'],
['mediumturquoise', '#48d1cc'],
['mediumvioletred', '#c71585'],
['midnightblue', '#191970'],
['mintcream', '#f5fffa'],
['mistyrose', '#ffe4e1'],
['moccasin', '#ffe4b5'],
['navajowhite', '#ffdead'],
['navy', '#000080'],
['oldlace', '#fdf5e6'],
['olive', '#808000'],
['olivedrab', '#6b8e23'],
['orange', '#ffa500'],
['orangered', '#ff4500'],
['orchid', '#da70d6'],
['palegoldenrod', '#eee8aa'],
['palegreen', '#98fb98'],
['paleturquoise', '#afeeee'],
['palevioletred', '#db7093'],
['papayawhip', '#ffefd5'],
['peachpuff', '#ffdab9'],
['peru', '#cd853f'],
['pink', '#ffc0cb'],
['plum', '#dda0dd'],
['powderblue', '#b0e0e6'],
['purple', '#800080'],
['rebeccapurple', '#663399'],
['red', '#ff0000'],
['rosybrown', '#bc8f8f'],
['royalblue', '#4169e1'],
['saddlebrown', '#8b4513'],
['salmon', '#fa8072'],
['sandybrown', '#f4a460'],
['seagreen', '#2e8b57'],
['seashell', '#fff5ee'],
['sienna', '#a0522d'],
['silver', '#c0c0c0'],
['skyblue', '#87ceeb'],
['slateblue', '#6a5acd'],
['slategray', '#708090'],
['slategrey', '#708090'],
['snow', '#fffafa'],
['springgreen', '#00ff7f'],
['steelblue', '#4682b4'],
['tan', '#d2b48c'],
['teal', '#008080'],
['thistle', '#d8bfd8'],
['tomato', '#ff6347'],
['turquoise', '#40e0d0'],
['violet', '#ee82ee'],
['wheat', '#f5deb3'],
['white', '#ffffff'],
['whitesmoke', '#f5f5f5'],
['yellow', '#ffff00'],
['yellowgreen', '#9acd32'],
]);

View File

@ -18,7 +18,7 @@ function colorMimicry(el, targets, dummyContainer = document.body) {
let numTotal = 0;
const rootStyle = getStyle(document.documentElement);
for (const k in targets) {
const base = {r: 0, g: 0, b: 0, a: 0};
const base = {r: 255, g: 255, b: 255, a: 1};
blend(base, rootStyle[targets[k]]);
colors[k] = base;
numTotal++;
@ -45,10 +45,6 @@ function colorMimicry(el, targets, dummyContainer = document.body) {
el.remove();
}
for (const k in targets) {
const c = colors[k];
if (!isOpaque(c)) {
blend(colors[k] = {r: 255, g: 255, b: 255, a: 1}, c);
}
const {r, g, b, a} = colors[k];
colors[k] = `rgba(${r}, ${g}, ${b}, ${a})`;
// https://www.w3.org/TR/AERT#color-contrast
@ -73,7 +69,7 @@ function colorMimicry(el, targets, dummyContainer = document.body) {
base.b = Math.round(b * q1 + base.b * q2);
base.a = mixedA;
}
return isOpaque(base);
return Math.abs(base.a - 1) < 1e-3;
}
// speed-up for sequential invocations within the same event loop cycle
@ -90,8 +86,4 @@ function colorMimicry(el, targets, dummyContainer = document.body) {
function clearCache() {
styleCache.clear();
}
function isOpaque({a}) {
return Math.abs(a - 1) < 1e-3;
}
}

View File

@ -3,7 +3,6 @@
'use strict';
(window.CodeMirror ? window.CodeMirror.prototype : window).colorpicker = function () {
const {constrain} = colorConverter;
const cm = window.CodeMirror && this;
const CSS_PREFIX = 'colorpicker-';
const HUE_COLORS = [
@ -41,6 +40,8 @@
let /** @type {HTMLElement} */ $palette;
const $inputGroups = {};
const $inputs = {};
const $rgb = {};
const $hsl = {};
const $hexLettercase = {};
const allowInputFocus = !('ontouchstart' in document) || window.innerHeight > 800;
@ -79,26 +80,11 @@
}
const el = document.createElement(props.tag || 'div');
el.className = toArray(cls).map(c => c ? CSS_PREFIX + c : '').join(' ');
el.append(...toArray(children).filter(Boolean));
el.append(...toArray(children));
if (props) delete props.tag;
return Object.assign(el, props);
}
const alphaPattern = /^\s*(0+\.?|0*\.\d+|0*1\.?|0*1\.0*)?\s*$/.source;
const nestedObj = (obj, key) => (obj[key] || (obj[key] = {}));
const makeNum = (type, channel, props, min, max) =>
$(['input-field', `${type}-${channel}`], [
(nestedObj($inputs, type)[channel] =
$('input', props || {tag: 'input', type: 'number', min, max, step: 1})),
$('title', channel.toUpperCase()),
]);
const ColorGroup = (type, channels) => (
$inputGroups[type] = $(['input-group', type], [
...Object.entries(channels).map(([k, v]) =>
makeNum(type, k, null, v[0], v[1])),
makeNum(type, 'a',
{tag: 'input', type: 'text', pattern: alphaPattern, spellcheck: false}),
])
);
$root = $('popup', {
oninput: setFromInputs,
onkeydown: setFromKeyboard,
@ -142,9 +128,42 @@
]),
]),
]),
ColorGroup('rgb', {r: [0, 255], g: [0, 255], b: [0, 255]}),
ColorGroup('hsl', {h: [], s: [0, 100], l: [0, 100]}),
ColorGroup('hwb', {h: [], w: [0, 100], b: [0, 100]}),
$inputGroups.rgb = $(['input-group', 'rgb'], [
$(['input-field', 'rgb-r'], [
$rgb.r = $('input', {tag: 'input', type: 'number', min: 0, max: 255, step: 1}),
$('title', 'R'),
]),
$(['input-field', 'rgb-g'], [
$rgb.g = $('input', {tag: 'input', type: 'number', min: 0, max: 255, step: 1}),
$('title', 'G'),
]),
$(['input-field', 'rgb-b'], [
$rgb.b = $('input', {tag: 'input', type: 'number', min: 0, max: 255, step: 1}),
$('title', 'B'),
]),
$(['input-field', 'rgb-a'], [
$rgb.a = $('input', {tag: 'input', type: 'text', pattern: alphaPattern, spellcheck: false}),
$('title', 'A'),
]),
]),
$inputGroups.hsl = $(['input-group', 'hsl'], [
$(['input-field', 'hsl-h'], [
$hsl.h = $('input', {tag: 'input', type: 'number', step: 1}),
$('title', 'H'),
]),
$(['input-field', 'hsl-s'], [
$hsl.s = $('input', {tag: 'input', type: 'number', min: 0, max: 100, step: 1}),
$('title', 'S'),
]),
$(['input-field', 'hsl-l'], [
$hsl.l = $('input', {tag: 'input', type: 'number', min: 0, max: 100, step: 1}),
$('title', 'L'),
]),
$(['input-field', 'hsl-a'], [
$hsl.a = $('input', {tag: 'input', type: 'text', pattern: alphaPattern, spellcheck: false}),
$('title', 'A'),
]),
]),
$('format-change', [
$formatChangeButton = $('format-change-button', {onclick: setFromFormatElement}, '↔'),
]),
@ -161,26 +180,19 @@
}),
]);
const inputsToObj = type => {
const res = {type};
for (const [k, el] of Object.entries($inputs[type])) {
res[k] = parseFloat(el.value);
}
return res;
};
for (const [key, val] of Object.entries($inputs)) {
Object.defineProperty(val, 'color', {
get: inputsToObj.bind(null, key),
});
}
Object.defineProperty($inputs.hex = [$hexCode], 'color', {
get: () => $hexCode.value.trim(),
});
Object.defineProperty($inputs, 'color', {
get: () => $inputs[currentFormat].color,
});
$inputs.hex = [$hexCode];
$inputs.rgb = [$rgb.r, $rgb.g, $rgb.b, $rgb.a];
$inputs.hsl = [$hsl.h, $hsl.s, $hsl.l, $hsl.a];
const inputsToArray = inputs => inputs.map(el => parseFloat(el.value));
const inputsToHexString = () => $hexCode.value.trim();
const inputsToRGB = ([r, g, b, a] = inputsToArray($inputs.rgb)) => ({r, g, b, a, type: 'rgb'});
const inputsToHSL = ([h, s, l, a] = inputsToArray($inputs.hsl)) => ({h, s, l, a, type: 'hsl'});
Object.defineProperty($inputs.hex, 'color', {get: inputsToHexString});
Object.defineProperty($inputs.rgb, 'color', {get: inputsToRGB});
Object.defineProperty($inputs.hsl, 'color', {get: inputsToHSL});
Object.defineProperty($inputs, 'color', {get: () => $inputs[currentFormat].color});
Object.defineProperty($inputs, 'colorString', {
get: () => currentFormat && colorConverter.format($inputs[currentFormat].color, undefined, {round: true}),
get: () => currentFormat && colorConverter.format($inputs[currentFormat].color),
});
HUE_COLORS.forEach(color => Object.assign(color, colorConverter.parse(color.hex)));
@ -198,7 +210,6 @@
HSV = {};
currentFormat = '';
options = PUBLIC_API.options = opt;
if (opt.round !== false) opt.round = true;
prevFocusedElement = document.activeElement;
userActivity = 0;
lastOutputColor = opt.color || '';
@ -235,19 +246,33 @@
}
function setColor(color) {
if (typeof color === 'string') {
switch (typeof color) {
case 'string':
color = colorConverter.parse(color);
} else if (typeof color === 'object' && color && !color.type) {
color = Object.assign({}, color, {type: colorConverter.guessType(color)});
break;
case 'object': {
const {r, g, b, a} = color;
if (!isNaN(r) && !isNaN(g) && !isNaN(b)) {
color = {r, g, b, a, type: 'rgb'};
break;
}
if (!color || !color.type) {
const {h, s, l} = color;
if (!isNaN(h) && !isNaN(s) && !isNaN(l)) {
color = {h, s, l, a, type: 'hsl'};
break;
}
}
// fallthrough
default:
return false;
}
if (color) {
if (!initialized) {
init();
}
setFromColor(color);
return true;
}
return Boolean(color);
}
function getColor(type) {
@ -255,7 +280,9 @@
return;
}
readCurrentColorFromRamps();
const color = colorConverter.fromHSV(HSV, type);
const color = type === 'hsl' ?
colorConverter.HSVtoHSL(HSV) :
colorConverter.HSVtoRGB(HSV);
return type ? colorToString(color, type) : color;
}
@ -314,7 +341,7 @@
function setFromFormatElement({shiftKey}) {
userActivity = performance.now();
HSV.a = isNaN(HSV.a) ? 1 : HSV.a;
const formats = Object.keys($inputGroups);
const formats = ['hex', 'rgb', 'hsl'];
const dir = shiftKey ? -1 : 1;
const total = formats.length;
if ($inputs.colorString === $inputs.prevColorString) {
@ -335,7 +362,7 @@
function setFromInputs(event) {
userActivity = event ? performance.now() : userActivity;
if (Object.values($inputs[currentFormat]).every(validateInput)) {
if ($inputs[currentFormat].every(validateInput)) {
setFromColor($inputs.color);
}
}
@ -348,7 +375,7 @@
case 'PageDown':
if (!ctrl && !alt && !meta) {
const el = document.activeElement;
const inputs = Object.values($inputs[currentFormat]);
const inputs = $inputs[currentFormat];
const lastInput = inputs[inputs.length - 1];
if (key === 'Tab' && shift && el === inputs[0]) {
maybeFocus(lastInput);
@ -397,8 +424,8 @@
newValue = options.hexUppercase ? newValue.toUpperCase() : newValue.toLowerCase();
} else if (!alt) {
value = parseFloat(el.value);
const isHue = el.title === 'H';
const isAlpha = el === $inputs[currentFormat].a;
const isHue = el === $inputs.hsl[0];
const isAlpha = el === $inputs[currentFormat][3];
const isRGB = currentFormat === 'rgb';
const min = isHue ? -360 : 0;
const max = isHue ? 360 : isAlpha ? 1 : isRGB ? 255 : 100;
@ -419,7 +446,7 @@
}
function validateInput(el) {
const isAlpha = el === $inputs[currentFormat].a;
const isAlpha = el === $inputs[currentFormat][3];
let isValid = (isAlpha || el.value.trim()) && el.checkValidity();
if (!isAlpha && !isValid && currentFormat === 'rgb') {
isValid = parseAs(el, parseInt);
@ -437,7 +464,9 @@
function setFromColor(color) {
color = typeof color === 'string' ? colorConverter.parse(color) : color;
color = color || colorConverter.parse('#f00');
const newHSV = colorConverter.toHSV(color);
const newHSV = color.type === 'hsl' ?
colorConverter.HSLtoHSV(color) :
colorConverter.RGBtoHSV(color);
if (Object.entries(newHSV).every(([k, v]) => v === HSV[k] || Math.abs(v - HSV[k]) < 1e-3)) {
return;
}
@ -459,7 +488,7 @@
}
}
$inputGroups[format].dataset.active = '';
maybeFocus(Object.values($inputs[format])[0]);
maybeFocus($inputs[format][0]);
currentFormat = format;
}
@ -481,13 +510,25 @@
}
function renderInputs() {
const rgb = colorConverter.fromHSV(HSV, 'rgb');
if (currentFormat === 'hex') {
const rgb = colorConverter.HSVtoRGB(HSV);
switch (currentFormat) {
case 'hex':
rgb.a = HSV.a;
$hexCode.value = colorToString(rgb, 'hex');
} else {
for (const [k, v] of Object.entries(colorConverter.fromHSV(HSV, currentFormat))) {
const el = $inputs[currentFormat][k];
if (el) el.value = k === 'a' ? alphaToString() || 1 : Math.round(v);
break;
case 'rgb': {
$rgb.r.value = rgb.r;
$rgb.g.value = rgb.g;
$rgb.b.value = rgb.b;
$rgb.a.value = alphaToString() || 1;
break;
}
case 'hsl': {
const {h, s, l} = colorConverter.HSVtoHSL(HSV);
$hsl.h.value = h;
$hsl.s.value = s;
$hsl.l.value = l;
$hsl.a.value = alphaToString() || 1;
}
}
$swatch.style.backgroundColor = colorToString(rgb, 'rgb');
@ -663,7 +704,7 @@
}
if (
userActivity &&
Object.values($inputs[currentFormat]).every(el => el.checkValidity())
$inputs[currentFormat].every(el => el.checkValidity())
) {
lastOutputColor = colorString.replace(/\b0\./g, '.');
if (isCallable) {
@ -729,7 +770,7 @@
//region Color conversion utilities
function colorToString(color, type = currentFormat) {
return colorConverter.format(color, type, options);
return colorConverter.format(color, type, options.hexUppercase);
}
function alphaToString(a = HSV.a) {
@ -737,7 +778,9 @@
}
function currentColorToString(format = currentFormat, alpha = HSV.a) {
const converted = colorConverter.fromHSV(HSV, format);
const converted = format === 'hsl' ?
colorConverter.HSVtoHSL(HSV) :
colorConverter.HSVtoRGB(HSV);
converted.a = isNaN(alpha) || alpha === 1 ? undefined : alpha;
return colorToString(converted, format);
}
@ -836,6 +879,10 @@
return bgLuma < .5 ? 'dark' : 'light';
}
function constrain(min, max, value) {
return value < min ? min : value > max ? max : value;
}
function parseAs(el, parser) {
const num = parser(el.value);
if (!isNaN(num) &&

View File

@ -8,28 +8,51 @@
const COLORVIEW_CLASS = 'colorview';
const COLORVIEW_SWATCH_CLASS = COLORVIEW_CLASS + '-swatch';
const COLORVIEW_SWATCH_CSS = `--${COLORVIEW_SWATCH_CLASS}:`;
const CLOSE_POPUP_EVENT = 'close-colorpicker-popup';
const {RX_COLOR, testAt} = colorConverter;
const RX_UNSUPPORTED = (s => s && new RegExp(s))([
const RXS_NUM = /\s*([+-]?(?:\d+\.?\d*|\d*\.\d+))(?:e[+-]?\d+)?/.source;
const RX_COLOR = {
hex: /#(?:[a-f\d]{3}(?:[a-f\d](?:[a-f\d]{2}){0,2})?)\b/iy,
rgb: new RegExp([
// num, num, num [ , num_or_pct]?
// pct, pct, pct [ , num_or_pct]?
`^((${RXS_NUM}\\s*(,|$)){3}|(${RXS_NUM}%\\s*(,|$)){3})(${RXS_NUM}%?)?\\s*$`,
// num num num [ / num_or_pct]?
// pct pct pct [ / num_or_pct]?
`^((${RXS_NUM}\\s*(\\s|$)){3}|(${RXS_NUM}%\\s*(\\s|$)){3})(/${RXS_NUM}%?)?\\s*$`,
].join('|'), 'iy'),
hsl: new RegExp([
// num_or_angle, pct, pct [ , num_or_pct]?
`^(${RXS_NUM}(|deg|g?rad|turn)\\s*),(${RXS_NUM}%\\s*(,|$)){2}(${RXS_NUM}%?)?\\s*$`,
// num_or_angle pct pct [ / num_or_pct]?
`^(${RXS_NUM}(|deg|g?rad|turn)\\s*)\\s(${RXS_NUM}%\\s*(\\s|$)){2}(/${RXS_NUM}%?)?\\s*$`,
].join('|'), 'iy'),
unsupported: new RegExp([
!CSS.supports('color', '#abcd') && /#(.{4}){1,2}$/,
!CSS.supports('color', 'hwb(1 0% 0%)') && /^hwb\(/,
!CSS.supports('color', 'rgb(1e2,0,0)') && /\de/,
!CSS.supports('color', 'rgb(1.5,0,0)') &&
/^rgba?\((([^,]+,){0,2}[^,]*\.|(\s*\S+\s+){0,2}\S*\.)/,
!CSS.supports('color', 'rgb(1.5,0,0)') && /^rgba?\((([^,]+,){0,2}[^,]*\.|(\s*\S+\s+){0,2}\S*\.)/,
!CSS.supports('color', 'rgb(1,2,3,.5)') && /[^a]\(([^,]+,){3}/,
!CSS.supports('color', 'rgb(1,2,3,50%)') && /\((([^,]+,){3}|(\s*\S+[\s/]+){3}).*?%/,
!CSS.supports('color', 'rgb(1 2 3 / 1)') && /^[^,]+$/,
!CSS.supports('color', 'hsl(1turn, 2%, 3%)') && /deg|g?rad|turn/,
].filter(Boolean).map(rx => rx.source).join('|'));
].filter(Boolean).map(rx => rx.source).join('|') || '^$', 'i'),
};
if (RX_COLOR.unsupported.source === '^$') {
RX_COLOR.unsupported = null;
}
const RX_DETECT = new RegExp('(^|[\\s(){}[\\]:,/"=])' +
'(' +
RX_COLOR.hex.source + '|' +
'(?:(?:rgb|hsl)a?|hwb)(?=\\()|(?:' + [...colorConverter.NAMED_COLORS.keys()].join('|') + ')' +
'(?:rgb|hsl)a?(?=\\()|(?:' + [...colorConverter.NAMED_COLORS.keys()].join('|') + ')' +
'(?=[\\s;(){}[\\]/"!]|$)' +
')', 'gi');
const RX_DETECT_FUNC = /((rgb|hsl)a?|hwb)\(/iy;
const RX_COMMENT = /\/\*([^*]+|\*(?!\/))*(\*\/|$)/g;
const RX_DETECT_FUNC = /(rgb|hsl)a?\(/iy;
const RX_COMMENT = /\/\*([^*]|\*(?!\/))*(\*\/|$)/g;
const SPACE1K = ' '.repeat(1000);
// milliseconds to work on invisible colors per one run
@ -416,7 +439,7 @@
function getSafeColorValue() {
if (isHex && color.length !== 5 && color.length !== 9) return color;
if (!RX_UNSUPPORTED || !RX_UNSUPPORTED.test(color)) return color;
if (!RX_COLOR.unsupported || !RX_COLOR.unsupported.test(color)) return color;
const value = colorConverter.parse(color);
return colorConverter.format(value, 'rgb');
}
@ -687,6 +710,14 @@
setTimeout(() => el.remove(), DURATION_SEC * 1000);
}
function testAt(rx, index, text) {
if (!rx) return false;
rx.lastIndex = index;
return rx.test(text);
}
function getStyleAtPos({
line,
styles = this.getLineHandle(line).styles,

View File

@ -112,7 +112,7 @@ class Reporter {
//eslint-disable-next-line no-var
var CSSLint = (() => {
const RX_EMBEDDED = /\/\*\s*csslint\s+((?:[^*]+|\*(?!\/))+?)\*\//ig;
const RX_EMBEDDED = /\/\*\s*csslint\s+((?:[^*]|\*(?!\/))+?)\*\//ig;
const EBMEDDED_RULE_VALUE_MAP = {
// error
'true': 2,
@ -330,7 +330,7 @@ CSSLint.Util = {
/** Gets the lower-cased text without vendor prefix */
getPropName(prop) {
return prop._propName ||
(prop._propName = prop.text.match(parserlib.util.rxVendorPrefix)[2].toLowerCase());
(prop._propName = prop.text.replace(parserlib.util.rxVendorPrefix, '').toLowerCase());
},
registerRuleEvents(parser, {start, property, end}) {
@ -1327,13 +1327,14 @@ CSSLint.addRule['known-pseudos'] = [{
const rx = /^(:+)(?:-(\w+)-)?([^(]+)(\()?/i;
const allowsFunc = Func + FuncToo;
const allowsPrefix = WK + Moz;
const {lower} = parserlib.util;
const checkSelector = ({parts}) => {
for (const {modifiers} of parts || []) {
if (!modifiers) continue;
for (const mod of modifiers) {
if (mod.type === 'pseudo') {
const {text} = mod;
const [all, colons, prefix, name, paren] = rx.exec(text.toLowerCase()) || 0;
const [all, colons, prefix, name, paren] = rx.exec(lower(text)) || 0;
const defPrefixed = definitionsPrefixed[name];
const def = definitions[name] || defPrefixed;
for (const err of !def ? ['Unknown pseudo'] : [

File diff suppressed because it is too large Load Diff

View File

@ -1,35 +0,0 @@
/* global $ */// dom.js
/* global API msg */// msg.js
'use strict';
/**
* This file must be loaded in a <script> tag placed after all the <link> tags
* that contain dark themes so that the stylesheets are loaded by the time this script runs.
* The CSS must use `@media screen and (prefers-color-scheme: dark), dark {}` that also works
* in old browsers and ensures CSS loads before the first paint, then we toggle the media here,
* which also happens before the first paint unless the browser "yields", but that's abnormal
* and not even a problem in the most popular case of using system dark/light mode.
*/
API.colorScheme.isDark().then(isDark => {
const ON = 'screen';
const OFF = 'not all';
const map = {[ON]: true, [OFF]: false};
toggleDarkStyles();
msg.onExtension(e => {
if (e.method === 'colorScheme') {
isDark = e.value;
toggleDarkStyles();
}
});
function toggleDarkStyles() {
$.root.dataset.uiTheme = isDark ? 'dark' : 'light';
for (const sheet of document.styleSheets) {
for (const {media: m} of sheet.cssRules) {
if (m && m[1] === 'dark' && map[m[0]] !== isDark) {
m.mediaText = `${isDark ? ON : OFF},dark`;
}
}
}
}
});

View File

@ -49,7 +49,7 @@
}
.config-body label:not(:first-child) {
border-top: 1px dotted var(--c80);
border-top: 1px dotted #ccc;
}
.config-body .dirty {
@ -67,7 +67,7 @@
.config-body .onoffswitch {
width: var(--onoffswitch-width);
margin: 0;
height: var(--input-height);
height: 22px;
box-sizing: border-box;
vertical-align: middle;
}
@ -112,7 +112,7 @@
}
.config-number span, .config-range span {
line-height: var(--input-height);
line-height: 22px;
}
.config-body label:not(.nondefault) .config-reset-icon {
@ -125,7 +125,7 @@
.config-reset-icon .svg-icon {
cursor: pointer;
fill: var(--c65);
fill: #aaa;
width: var(--pad);
height: var(--pad);
padding: 0 1px;
@ -134,7 +134,7 @@
}
.config-reset-icon:hover .svg-icon {
fill: var(--c40);
fill: #666;
}
#config-autosave-wrapper {
@ -188,7 +188,7 @@
position: absolute;
top: 0;
left: 0;
border: 1px solid var(--c50);
border: 1px solid gray;
cursor: pointer;
opacity: 1;
z-index: 2;

View File

@ -26,7 +26,7 @@
position: fixed;
display: flex;
flex-direction: column;
background-color: var(--bg);
background-color: white;
box-shadow: 5px 5px 50px rgba(0, 0, 0, 0.225);
z-index: 9999991;
-moz-user-select: text;
@ -68,7 +68,7 @@
#message-box-title {
font-weight: bold;
background-color: var(--accent-3);
background-color: rgb(145, 208, 198);
padding: .75rem 24px .75rem 52px;
font-size: 1rem;
position: relative;
@ -143,7 +143,7 @@
#message-box-buttons {
padding: .75rem .375rem;
background-color: var(--c95);
background-color: #f0f0f0;
text-align: center;
}

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