Merge branch 'master' into dev-sort

# Conflicts:
#	js/dom.js
This commit is contained in:
tophf 2022-01-13 12:50:09 +03:00
commit c7da33a028
58 changed files with 1499 additions and 650 deletions

View File

@ -52,9 +52,6 @@
"backupButtons": {
"message": "Резервни копия"
},
"backupMessage": {
"message": "Изберете файл или го влачете до страницата."
},
"bckpInstStyles": {
"message": "Изнасяне на стилове"
},

View File

@ -67,9 +67,6 @@
"backupButtons": {
"message": "Zálohovat"
},
"backupMessage": {
"message": "Vyberte soubor nebo ho přetáhněte na tuto stránku."
},
"bckpInstStyles": {
"message": "Exportovat styly"
},

View File

@ -58,9 +58,6 @@
"author": {
"message": "Forfatter"
},
"backupMessage": {
"message": "Vælg en fil eller træk og slip til denne side."
},
"bckpInstStyles": {
"message": "Eksportér stil"
},

View File

@ -68,7 +68,7 @@
"message": "Datensicherung"
},
"backupMessage": {
"message": "Wähle eine Datei aus oder ziehe die Datei auf diese Seite. (Drag and Drop)"
"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."
},
"bckpInstStyles": {
"message": "Styles exportieren"
@ -252,6 +252,9 @@
}
}
},
"editorSettings": {
"message": "Editor Einstellungen"
},
"enableStyleLabel": {
"message": "Aktivieren"
},
@ -419,6 +422,18 @@
"installButtonUpdate": {
"message": "Style updaten"
},
"installPreferSchemeDark": {
"message": "Im dunklen Modus"
},
"installPreferSchemeLabel": {
"message": "Der Style wird aktiviert:"
},
"installPreferSchemeLight": {
"message": "Im hellen Modus"
},
"installPreferSchemeNone": {
"message": "Immer"
},
"installUpdate": {
"message": "Update installieren"
},
@ -778,6 +793,18 @@
"optionsAdvanced": {
"message": "Erweitert"
},
"optionsAdvancedAutoSwitchScheme": {
"message": "Hellen/Dunklen Modus automatisch umschalten"
},
"optionsAdvancedAutoSwitchSchemeBySystem": {
"message": "Nach Systemeinstellung"
},
"optionsAdvancedAutoSwitchSchemeByTime": {
"message": "Bei Nacht:"
},
"optionsAdvancedAutoSwitchSchemeNever": {
"message": "Nie"
},
"optionsAdvancedContextDelete": {
"message": "\"Löschen\" im Editor-Kontextmenü hinzufügen"
},
@ -791,7 +818,7 @@
"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."
@ -865,6 +892,9 @@
"optionsSyncNone": {
"message": "Nichts"
},
"optionsSyncPassword": {
"message": "Passwort"
},
"optionsSyncStatusConnected": {
"message": "Verbunden"
},
@ -908,6 +938,9 @@
"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."
},
@ -1082,6 +1115,9 @@
"sections": {
"message": "Bereiche"
},
"settings": {
"message": "Einstellungen"
},
"shortcuts": {
"message": "Tastenkürzel"
},
@ -1133,12 +1169,18 @@
"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"
},
"styleInstall": {
"message": "\"$stylename$\" mit Stylus installieren?",
"placeholders": {
@ -1178,6 +1220,15 @@
"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."
},
@ -1211,6 +1262,9 @@
"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."
},
@ -1240,8 +1294,16 @@
"syncError": {
"message": "Synchronisation fehlgeschlagen"
},
"syncErrorLock": {
"message": "Die Datenbank wird bereits verwendet. Die Sperre wird um $TIME$ aufgehoben.",
"placeholders": {
"time": {
"content": "$1"
}
}
},
"syncErrorRelogin": {
"message": "Synchronisation fehlgeschlagen.\nVersuche, dich in den Optionen neu einzuloggen:\nKlicke erst \"Trennen\", dann \"Verbinden\"."
"message": "Synchronisation erfolglos. Du wurdest ausgeloggt.\nVersuche, dich in den Stylus Einstellungen wieder einzuloggen."
},
"syncStorageErrorSaving": {
"message": "Der Wert kann nicht gespeichert werden. Versuche, die Textmenge zu reduzieren."

View File

@ -64,9 +64,6 @@
"backupButtons": {
"message": "Δημιουργήστε αντίγραφο ασφαλείας"
},
"backupMessage": {
"message": "Επιλέξτε ένα αρχείο ή σύρετέ το σε αυτήν τη σελίδα"
},
"bckpInstStyles": {
"message": "Εξαγωγή στυλ"
},
@ -680,9 +677,6 @@
"syncError": {
"message": "Ο συγχρονισμός απέτυχε"
},
"syncErrorRelogin": {
"message": "Ο συγχρονισμός απέτυχε.\nΠροσπαθήστε να συνδεθείτε ξανά στις επιλογές Stylus:\nκάντε κλικ στο 'αποσύνδεση' πρώτα και μετά στο 'σύνδεση'."
},
"toggleStyle": {
"message": "Αλλαγή στυλ"
},

View File

@ -93,8 +93,8 @@
"description": "Heading for backup"
},
"backupMessage": {
"message": "Select a file or drag and drop to this page.",
"description": "Message for backup"
"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"
},
"bckpInstStyles": {
"message": "Export styles"
@ -375,11 +375,8 @@
},
"description": "Title of the page for editing styles"
},
"editorCodeLabel": {
"message": "Code"
},
"editorSettingLabel": {
"message": "Settings"
"editorSettings": {
"message": "Editor settings"
},
"enableStyleLabel": {
"message": "Enable",
@ -504,6 +501,10 @@
"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"
@ -1203,6 +1204,15 @@
}
}
},
"optionsSyncUsername": {
"message": "Username"
},
"optionsSyncPassword": {
"message": "Password"
},
"optionsSyncUrl": {
"message": "URL"
},
"optionsSyncStatusRelogin": {
"message": "Session expired, please login again."
},
@ -1443,6 +1453,10 @@
"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"
@ -1614,6 +1628,10 @@
"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"
@ -1643,9 +1661,6 @@
"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"
},

View File

@ -67,9 +67,6 @@
"backupButtons": {
"message": "Respaldo"
},
"backupMessage": {
"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"
},
@ -1269,9 +1266,6 @@
"syncError": {
"message": "No se pudo sincronizar"
},
"syncErrorRelogin": {
"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."
},

View File

@ -67,9 +67,6 @@
"backupButtons": {
"message": "Varunda"
},
"backupMessage": {
"message": "Vali fail või lohista see siia lehele."
},
"bckpInstStyles": {
"message": "Ekspordi stiilid"
},

View File

@ -67,9 +67,6 @@
"backupButtons": {
"message": "Sauvegarde"
},
"backupMessage": {
"message": "Sélectionner un fichier ou le glisser-déposer sur cette page"
},
"bckpInstStyles": {
"message": "Exporter des styles"
},
@ -199,6 +196,28 @@
"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"
},
@ -365,6 +384,12 @@
"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"
},
@ -433,9 +458,18 @@
"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"
},
@ -709,6 +743,17 @@
}
}
},
"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": {
@ -734,6 +779,9 @@
"noStylesForSite": {
"message": "Aucun style n'est installé pour ce site."
},
"numberedLine": {
"message": "Ligne :"
},
"openManage": {
"message": "Gestion"
},
@ -755,6 +803,15 @@
"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é"
},
@ -800,6 +857,9 @@
"optionsResetButton": {
"message": "Ré-initialiser les options"
},
"optionsStylusThemes": {
"message": "Trouver un thème d'interface de Stylus"
},
"optionsSubheading": {
"message": "Plus de paramètres"
},
@ -849,6 +909,9 @@
}
}
},
"optionsSyncStatusRelogin": {
"message": "Session expirée, veuillez vous reconnecter à nouveau."
},
"optionsSyncStatusSyncing": {
"message": "Synchronisation..."
},
@ -903,6 +966,9 @@
"popupMenuButtonTooltip": {
"message": "Actions"
},
"popupOpenEditInPopup": {
"message": "Utiliser une simple fenêtre (pas d'omnibox)"
},
"popupOpenEditInWindow": {
"message": "Ouvrir l'éditeur dans une nouvelle fenêtre"
},
@ -921,6 +987,21 @@
"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…"
},
@ -948,6 +1029,9 @@
"searchCaseSensitive": {
"message": "Sensible à la casse"
},
"searchGlobalStyles": {
"message": "Chercher aussi des styles globaux."
},
"searchNumberOfResults": {
"message": "Nombre de correspondances"
},
@ -963,6 +1047,12 @@
"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"
},
@ -972,6 +1062,21 @@
"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"
},
@ -1014,6 +1119,9 @@
"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"
},
@ -1071,6 +1179,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()'"
},
@ -1136,6 +1247,9 @@
"syncDropboxStyles": {
"message": "Exporter vers Dropbox"
},
"syncError": {
"message": "Synchronisation échouée"
},
"syncStorageErrorSaving": {
"message": "La valeur ne peut pas être sauvegardée. Essayez de réduire la quantité de texte."
},
@ -1160,6 +1274,12 @@
"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…"
},

View File

@ -67,9 +67,6 @@
"backupButtons": {
"message": "גיבוי"
},
"backupMessage": {
"message": "בחר קובץ או גרור ושחרר אותו בדף זה."
},
"bckpInstStyles": {
"message": "ייצא עיצובים"
},

View File

@ -67,9 +67,6 @@
"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"
},

View File

@ -58,9 +58,6 @@
"author": {
"message": "Autore"
},
"backupMessage": {
"message": "Seleziona un file o trascinalo in questa pagina."
},
"bckpInstStyles": {
"message": "Esporta stili"
},

View File

@ -68,7 +68,7 @@
"message": "バックアップ"
},
"backupMessage": {
"message": "ファイルを選択するか、このページにドラッグ&ドロップしてください。"
"message": "バックアップファイルをインポートする場合、このページにドラッグ&ドロップするか、インポートボタンをクリックしてください。\nまた、Stylus 1.5.18 より古い形式でバックアップしたい場合は、エクスポートボタンを 右クリック もしくは shift-クリック してください。"
},
"bckpInstStyles": {
"message": "スタイルをエクスポート"
@ -255,6 +255,9 @@
}
}
},
"editorSettings": {
"message": "エディタ設定"
},
"enableStyleLabel": {
"message": "有効化"
},
@ -428,6 +431,18 @@
"installButtonUpdate": {
"message": "更新"
},
"installPreferSchemeDark": {
"message": "ダークモード時"
},
"installPreferSchemeLabel": {
"message": "適用するスタイル:"
},
"installPreferSchemeLight": {
"message": "ライトモード時"
},
"installPreferSchemeNone": {
"message": "常時"
},
"installUpdate": {
"message": "更新をインストール"
},
@ -790,6 +805,18 @@
"optionsAdvanced": {
"message": "上級者向け"
},
"optionsAdvancedAutoSwitchScheme": {
"message": "ライト/ダークモードのスタイルを自動で切り替える"
},
"optionsAdvancedAutoSwitchSchemeBySystem": {
"message": "システム設定による"
},
"optionsAdvancedAutoSwitchSchemeByTime": {
"message": "夜間時間による"
},
"optionsAdvancedAutoSwitchSchemeNever": {
"message": "なし"
},
"optionsAdvancedContextDelete": {
"message": "エディタのコンテキストメニューに「削除」を追加する"
},
@ -883,6 +910,9 @@
"optionsSyncNone": {
"message": "未選択"
},
"optionsSyncPassword": {
"message": "パスワード"
},
"optionsSyncStatusConnected": {
"message": "接続状態"
},
@ -926,6 +956,9 @@
"optionsSyncSyncNow": {
"message": "すぐに同期"
},
"optionsSyncUsername": {
"message": "ユーザ名"
},
"optionsUpdateImportNote": {
"message": "旧バージョンまたはStylishからスタイルのバックアップをインポートする場合は、スタイルマネージャで一度手動で更新チェックを行い、すべてのスタイルが確実に更新されるようにしてください。"
},
@ -1109,6 +1142,9 @@
"sections": {
"message": "セクション"
},
"settings": {
"message": "設定"
},
"shortcuts": {
"message": "ショートカット"
},
@ -1160,12 +1196,18 @@
"styleEnabledLabel": {
"message": "有効"
},
"styleExcludeLabel": {
"message": "含めないサイトを設定"
},
"styleFromMozillaFormatError": {
"message": "Mozilla形式のインポートに失敗しました"
},
"styleFromMozillaFormatPrompt": {
"message": "Mozilla形式のコードを貼り付ける"
},
"styleIncludeLabel": {
"message": "含めるサイトを設定"
},
"styleInstall": {
"message": "「$stylename$」を Stylus にインストールしますか?",
"placeholders": {
@ -1205,6 +1247,15 @@
"styleNotAppliedRegexpProblemTooltip": {
"message": "'regexp()' の誤った使用のためスタイルを適用できませんでした"
},
"styleNotAppliedSchemeDark": {
"message": "スタイルはダークモード時のみ適用されます"
},
"styleNotAppliedSchemeLight": {
"message": "スタイルはライトモード時のみ適用されます"
},
"stylePreferSchemeLabel": {
"message": "暗い/明るい モード"
},
"styleRegexpInvalidExplanation": {
"message": "いくつかの 'regexp()' のルールはコンパイルできませんでした"
},
@ -1238,6 +1289,9 @@
"styleSaveLabel": {
"message": "保存"
},
"styleSettings": {
"message": "スタイル設定"
},
"styleToMozillaFormatHelp": {
"message": "Mozilla形式のコードは、userstyles.org に投稿することができ、また従来の Stylish for Firefox でも使用できます"
},
@ -1255,6 +1309,9 @@
"styleUpdateDiscardChanges": {
"message": "このスタイルはエディタの外部で変更されました。このスタイルをリロードしますか?"
},
"styleUpdateUrlLabel": {
"message": "更新URL"
},
"stylusUnavailableForURL": {
"message": "このページではStylusは動作しません。"
},
@ -1270,8 +1327,16 @@
"syncError": {
"message": "同期に失敗"
},
"syncErrorLock": {
"message": "データベースが使用中です。\nロック状態は$TIME$に解除されます。",
"placeholders": {
"time": {
"content": "$1"
}
}
},
"syncErrorRelogin": {
"message": "同期に失敗しました。\nStylusのオプション画面で、再ログインしてください :\n「切断」をクリック後、「接続」をクリック"
"message": "同期に失敗しました。ログアウトした可能性があります。\nStylusのオプション画面で再ログインしてください。"
},
"syncStorageErrorSaving": {
"message": "値を保存できません。テキストの量を減らしてください。"

View File

@ -67,9 +67,6 @@
"backupButtons": {
"message": "백업"
},
"backupMessage": {
"message": "파일을 선택하거나, 이 페이지로 파일을 드래그앤드롭하세요."
},
"bckpInstStyles": {
"message": "스타일 내보내기"
},
@ -136,6 +133,9 @@
"cm_theme": {
"message": "테마"
},
"colorpickerPaletteHint": {
"message": "견본을 우클릭하여 소스 코드와 번갈아 가며 확인"
},
"colorpickerSwitchFormatTooltip": {
"message": "16 진수-> RGB -> HSL 순서로 형식을 변환합니다.\nShift+클릭을 이용해 역순으로 변환할 수 있습니다.\nPgUp(PageUp), PgDn(PageDown) 키도 사용 가능합니다."
},
@ -317,6 +317,9 @@
"genericClone": {
"message": "복제"
},
"genericDescription": {
"message": "설명"
},
"genericDisabledLabel": {
"message": "비활성화됨"
},
@ -445,9 +448,18 @@
"linkGetHelp": {
"message": "도움말"
},
"linkGetShareStyles": {
"message": "스타일 주고받기"
},
"linkGetShareStylesInfo": {
"message": "커뮤니티에 의해 새롭게 운영되는 userstyles.world 사이트는 userstyle 투고자들이 userstyle.org를 대체하기 위해 만들었습니다. userstyle.org는 지난 몇 년간 많은 투고자들이 스타일을 업데이트하는 것을 멈춰서 느리고 둔감해졌습니다."
},
"linkGetStyles": {
"message": "스타일 찾기"
},
"linkGetStylesInfo": {
"message": "본 아카이브 사이트는 느리고 응답이 늦는 userstyles.org를 백업하기 위해 Userstyle 커뮤니티 회원에 의해 제작되었습니다. 하루에 약 한 번 아카이브 내 콘텐츠가 갱신됩니다."
},
"linkStylusWiki": {
"message": "위키"
},
@ -724,6 +736,17 @@
}
}
},
"meta_unknownMetaTypo": {
"message": "혹시 @$keyOk$입니까? 알 수 없는 메타데이터: @$keyErr$",
"placeholders": {
"keyErr": {
"content": "$1"
},
"keyOk": {
"content": "$2"
}
}
},
"meta_unknownPreprocessor": {
"message": "알 수 없는 @preprocessor $preprocessor$",
"placeholders": {
@ -782,9 +805,15 @@
"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": "비활성화 시의 배경 색"
},
@ -966,6 +995,24 @@
"previewTooltip": {
"message": "변경 사항은 저장되지 않고 일시적으로 적용됩니다.\n영구적으로 변경하려면 스타일을 저장하세요."
},
"publish": {
"message": "배포"
},
"publishPush": {
"message": "업데이트 푸시"
},
"publishReconnect": {
"message": "연결을 해제한 뒤 배포를 다시 시도해보세요"
},
"publishRetry": {
"message": "Stylus에서는 계속 이 스타일을 배포하려고 하고 있습니다. 하지만 인증 창이 뜨지 않는 경우 다시 시도할 수 있습니다. 지금 다시 시도하시겠습니까?"
},
"publishStyle": {
"message": "스타일 배포"
},
"publishUsw": {
"message": "<userstyles.world> 사용"
},
"readingStyles": {
"message": "스타일 읽어들이는 중..."
},
@ -1026,12 +1073,18 @@
"searchResultWeeklyCount": {
"message": "주간 설치 수"
},
"searchStyleQueryHint": {
"message": "대소문자와 무관하게 스타일 이름을 검색하십시오:\n이런 검색어 - 순서에 무관하게 모든 단어가 포함하기만 하면 스타일을 불러옴\n\"이런 검색어\" - 따옴표 속 정확한 문장을 포함하는 스타일을 불러옴\n2020 - 해당 연도, 여기서는 2020년에 갱신된 스타일을 불러옴"
},
"searchStylesAll": {
"message": "모두"
},
"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별"
},
@ -1146,6 +1199,9 @@
"styleMozillaFormatHeading": {
"message": "Mozilla 형식"
},
"styleName": {
"message": "스타일 이름"
},
"styleNotAppliedRegexpProblemTooltip": {
"message": "잘못된 'regexp()'의 사용으로 인해 스타일을 적용할 수 없습니다"
},
@ -1238,6 +1294,12 @@
"unreachableFileHint": {
"message": "Stylus가 file:// URL에 접근할 수 있도록 하려면 chrome://extensions 페이지에서 파일 URL에 대한 액세스를 허용해야 합니다."
},
"unreachableMozSiteHint": {
"message": "Firefox 60 이후의 버전에서는 <about:config> 내의 <extensions.webextensions.restrictedDomains>에서 이 도메인을 제거해야 합니다. "
},
"unreachableMozSiteHintOldFF": {
"message": "Firefox 59 버전 이상에서만 이처럼 CSP 보호 정책이 적용된 사이트에 웹 확장 기능으로 스타일 요소를 추가할 수 있도록 설정 가능합니다."
},
"unzipStyles": {
"message": "스타일 압축 푸는 중..."
},

View File

@ -68,7 +68,7 @@
"message": "Back-up"
},
"backupMessage": {
"message": "Selecteer een bestand of sleep het naar deze pagina."
"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."
},
"bckpInstStyles": {
"message": "Stijlen exporteren"
@ -274,6 +274,9 @@
}
}
},
"editorSettings": {
"message": "Editor-instellingen"
},
"enableStyleLabel": {
"message": "Inschakelen"
},
@ -441,6 +444,18 @@
"installButtonUpdate": {
"message": "Stijl bijwerken"
},
"installPreferSchemeDark": {
"message": "In Donkere modus"
},
"installPreferSchemeLabel": {
"message": "De stijl wordt toegepast:"
},
"installPreferSchemeLight": {
"message": "In Lichte modus"
},
"installPreferSchemeNone": {
"message": "Altijd"
},
"installUpdate": {
"message": "Update installeren"
},
@ -797,6 +812,18 @@
"optionsAdvanced": {
"message": "Geavanceerd"
},
"optionsAdvancedAutoSwitchScheme": {
"message": "Lichte-/Donkere-modusstijlen automatisch in-/uitschakelen"
},
"optionsAdvancedAutoSwitchSchemeBySystem": {
"message": "Systeemvoorkeur volgen"
},
"optionsAdvancedAutoSwitchSchemeByTime": {
"message": "Door nachttijd:"
},
"optionsAdvancedAutoSwitchSchemeNever": {
"message": "Nooit"
},
"optionsAdvancedContextDelete": {
"message": "Verwijderen toevoegen in contextmenu van editor"
},
@ -887,6 +914,9 @@
"optionsSyncNone": {
"message": "Geen"
},
"optionsSyncPassword": {
"message": "Wachtwoord"
},
"optionsSyncStatusConnected": {
"message": "Gekoppeld"
},
@ -930,6 +960,9 @@
"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."
},
@ -1110,6 +1143,9 @@
"sections": {
"message": "Secties"
},
"settings": {
"message": "Instellingen"
},
"shortcuts": {
"message": "Sneltoetsen"
},
@ -1161,12 +1197,18 @@
"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"
},
"styleInstall": {
"message": "$stylename$ installeren in Stylus?",
"placeholders": {
@ -1209,6 +1251,15 @@
"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."
},
@ -1242,6 +1293,9 @@
"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."
},
@ -1259,6 +1313,9 @@
"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."
},
@ -1274,8 +1331,16 @@
"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.\nProbeer u opnieuw aan te melden in de Stylus-opties:\nklik eerst op ontkoppelen, daarna op koppelen."
"message": "Synchronisatie mislukt. U bent afgemeld.\nProbeer u opnieuw aan te melden in de Stylus-opties."
},
"syncStorageErrorSaving": {
"message": "De waarde kan niet worden opgeslagen. Probeer de hoeveelheid tekst te verminderen."

View File

@ -71,7 +71,7 @@
"message": "Kopia zapasowa"
},
"backupMessage": {
"message": "Wybierz plik lub przeciągnij i upuść go na tę stronę."
"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."
},
"bckpInstStyles": {
"message": "Eksportuj style"
@ -293,6 +293,9 @@
}
}
},
"editorSettings": {
"message": "Ustawienia edytora"
},
"enableStyleLabel": {
"message": "Włącz"
},
@ -341,7 +344,7 @@
"message": "Znajdź style"
},
"findStylesForSite": {
"message": "Znajdź więcej stylów dla tej strony"
"message": "Znajdź więcej stylów dla tej witryny"
},
"findStylesInline": {
"message": "Wstawka"
@ -466,6 +469,18 @@
"installButtonUpdate": {
"message": "Zaktualizuj styl"
},
"installPreferSchemeDark": {
"message": "W trybie ciemnym"
},
"installPreferSchemeLabel": {
"message": "Należy stosować styl:"
},
"installPreferSchemeLight": {
"message": "W trybie jasnym"
},
"installPreferSchemeNone": {
"message": "Zawsze"
},
"installUpdate": {
"message": "Zainstaluj aktualizację"
},
@ -825,6 +840,18 @@
"optionsAdvanced": {
"message": "Zaawansowane"
},
"optionsAdvancedAutoSwitchScheme": {
"message": "Automatycznie przełączaj style trybu jasnego/ciemnego"
},
"optionsAdvancedAutoSwitchSchemeBySystem": {
"message": "Według preferencji systemowych"
},
"optionsAdvancedAutoSwitchSchemeByTime": {
"message": "W nocy:"
},
"optionsAdvancedAutoSwitchSchemeNever": {
"message": "Nigdy"
},
"optionsAdvancedContextDelete": {
"message": "Dodaj 'Usuń' do menu kontekstowego edytora"
},
@ -918,6 +945,9 @@
"optionsSyncNone": {
"message": "Brak"
},
"optionsSyncPassword": {
"message": "Hasło"
},
"optionsSyncStatusConnected": {
"message": "Połączono"
},
@ -961,6 +991,12 @@
"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."
},
@ -1144,6 +1180,9 @@
"sections": {
"message": "Sekcje"
},
"settings": {
"message": "Ustawienia"
},
"shortcuts": {
"message": "Skróty"
},
@ -1195,12 +1234,18 @@
"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"
},
"styleInstall": {
"message": "Zainstalować '$stylename$' do Stylusa?",
"placeholders": {
@ -1243,6 +1288,15 @@
"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ć."
},
@ -1276,6 +1330,9 @@
"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"
},
@ -1293,6 +1350,9 @@
"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."
},
@ -1308,8 +1368,16 @@
"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ę.\nSpróbuj ponownie zalogować się w opcjach Stylusa:\nkliknij najpierw 'rozłącz' , następnie 'połącz'."
"message": "Synchronizacja nie powiodła się. Wylogowano.\nSpróbuj ponownie zalogować się w opcjach Stylusa."
},
"syncStorageErrorSaving": {
"message": "Nie można zapisać wartości. Spróbuj zmniejszyć ilość tekstu."

View File

@ -64,9 +64,6 @@
"author": {
"message": "Autor"
},
"backupMessage": {
"message": "Selecione um arquivo ou arraste e solte nessa página."
},
"bckpInstStyles": {
"message": "Exportar estilos"
},

View File

@ -64,9 +64,6 @@
"backupButtons": {
"message": "Cópia de segurança"
},
"backupMessage": {
"message": "Selecione um ficheiro ou arraste e solte-o nesta página."
},
"bckpInstStyles": {
"message": "Exportar estilos"
},

View File

@ -61,9 +61,6 @@
"author": {
"message": "Autor"
},
"backupMessage": {
"message": "Selectați un fișier sau drag-and-drop aici"
},
"bckpInstStyles": {
"message": "Exportați teme"
},

View File

@ -68,7 +68,7 @@
"message": "Резервное копирование"
},
"backupMessage": {
"message": "Нажмите «Импорт», чтобы выбрать файл или просто перетащите его на эту страницу"
"message": "Чтобы импортировать архив стилей, перетащите файл в эту страницу или нажмите кнопку Импорт.\n\nЧтобы экспортировать совместимый архив для старого Stylus ранее чем 1.5.18, кликните на кнопку Экспорт правой кнопкой мыши или с нажатой клавишей Shift."
},
"bckpInstStyles": {
"message": "Экспорт стилей"
@ -293,6 +293,9 @@
}
}
},
"editorSettings": {
"message": "Настройки редактирования"
},
"enableStyleLabel": {
"message": "Включить"
},
@ -466,6 +469,18 @@
"installButtonUpdate": {
"message": "Обновить стиль"
},
"installPreferSchemeDark": {
"message": "В ночном режиме"
},
"installPreferSchemeLabel": {
"message": "Когда применять стиль:"
},
"installPreferSchemeLight": {
"message": "В дневном режиме"
},
"installPreferSchemeNone": {
"message": "Всегда"
},
"installUpdate": {
"message": "Установить обновление"
},
@ -486,9 +501,18 @@
"linkGetHelp": {
"message": "Помощь"
},
"linkGetShareStyles": {
"message": "Поиск/публикация стилей"
},
"linkGetShareStylesInfo": {
"message": "Новый сайт userstyles.world, созданный и развиваемый сообществом энтузиастов и авторов пользовательских стилей с целью заменить userstyles.org, который стал таким медленным и зависающим за последний год, что многие авторы перестали обновлять свои стили."
},
"linkGetStyles": {
"message": "Скачать стили"
},
"linkGetStylesInfo": {
"message": "Архивный сайт от одного энтузиаста и автора пользовательских стилей, зеркальная копия userstyles.org, ныне медленного и зависающего. Архив обновляется примерно раз в день."
},
"linkStylusWiki": {
"message": "Вики"
},
@ -819,6 +843,18 @@
"optionsAdvanced": {
"message": "Другое"
},
"optionsAdvancedAutoSwitchScheme": {
"message": "Автоматически переключать дневные/ночные стили"
},
"optionsAdvancedAutoSwitchSchemeBySystem": {
"message": "По настройке операционной системы"
},
"optionsAdvancedAutoSwitchSchemeByTime": {
"message": "В ночное время:"
},
"optionsAdvancedAutoSwitchSchemeNever": {
"message": "Никогда"
},
"optionsAdvancedContextDelete": {
"message": "Показывать команду \"Удалить\" в контекстном меню редактора"
},
@ -912,6 +948,9 @@
"optionsSyncNone": {
"message": "Ничего"
},
"optionsSyncPassword": {
"message": "Пароль"
},
"optionsSyncStatusConnected": {
"message": "Подключено"
},
@ -955,6 +994,12 @@
"optionsSyncSyncNow": {
"message": "Синхронизировать"
},
"optionsSyncUrl": {
"message": "URL адрес"
},
"optionsSyncUsername": {
"message": "Имя"
},
"optionsUpdateImportNote": {
"message": "После импорта базы стилей из старой версии или из Stylish запустите проверку на обновления из менеджера стилей, чтобы удостовериться в обновлении всех стилей."
},
@ -1138,6 +1183,9 @@
"sections": {
"message": "Разделы"
},
"settings": {
"message": "Настройки"
},
"shortcuts": {
"message": "Клавиши"
},
@ -1189,12 +1237,18 @@
"styleEnabledLabel": {
"message": "Включено"
},
"styleExcludeLabel": {
"message": "Личный список исключенных сайтов"
},
"styleFromMozillaFormatError": {
"message": "Ошибка импорта формата Mozilla"
},
"styleFromMozillaFormatPrompt": {
"message": "Вставьте содержимое стиля в формате Mozilla"
},
"styleIncludeLabel": {
"message": "Личный список включенных сайтов"
},
"styleInstall": {
"message": "Установить \"$stylename$\" в Stylus?",
"placeholders": {
@ -1237,6 +1291,15 @@
"styleNotAppliedRegexpProblemTooltip": {
"message": "Стиль не был применён из-за некорректного regexp()"
},
"styleNotAppliedSchemeDark": {
"message": "Стиль активен только в ночном режиме"
},
"styleNotAppliedSchemeLight": {
"message": "Стиль активен только в дневном режиме"
},
"stylePreferSchemeLabel": {
"message": "Ночной/дневной режим"
},
"styleRegexpInvalidExplanation": {
"message": "Некоторые 'regexp()' выражения не удалось скомпилировать."
},
@ -1270,6 +1333,9 @@
"styleSaveLabel": {
"message": "Сохранить"
},
"styleSettings": {
"message": "Настройки стиля"
},
"styleToMozillaFormatHelp": {
"message": "Формат кода Mozilla можно отправлять на сайт userstyles.org и использовать в дополнении Stylish для Firefox."
},
@ -1287,6 +1353,9 @@
"styleUpdateDiscardChanges": {
"message": "Стиль был изменён вне редактора. Перезагрузить?"
},
"styleUpdateUrlLabel": {
"message": "URL адрес обновлений"
},
"stylusUnavailableForURL": {
"message": "Такие адреса не поддерживаются."
},
@ -1302,8 +1371,16 @@
"syncError": {
"message": "Сбой синхронизации"
},
"syncErrorLock": {
"message": "Хранилище данных уже занято. Доступ закрыт до $TIME$",
"placeholders": {
"time": {
"content": "$1"
}
}
},
"syncErrorRelogin": {
"message": "Синхронизация не удалась.\nПопробуйте повторно войти в систему в опциях Stylus:\nсначала нажмите \"отсоединить\", затем \"подключить\"."
"message": "Ошибка синхронизации. Произведен выход из учетной записи.\n\nПопробуйте подключиться еще раз в настройках Stylus."
},
"syncStorageErrorSaving": {
"message": "Ошибка сохранения. Попробуйте уменьшить количество текста."

View File

@ -70,9 +70,6 @@
"backupButtons": {
"message": "Säkerhetskopiera"
},
"backupMessage": {
"message": "Välj en fil eller dra och släpp till den här sidan."
},
"bckpInstStyles": {
"message": "Exportera stilar"
},

View File

@ -58,9 +58,6 @@
"backupButtons": {
"message": "Yedek"
},
"backupMessage": {
"message": "Bir dosya seçin veya bu sayfaya sürükleyip bırakın."
},
"bckpInstStyles": {
"message": "Dışa aktar"
},

View File

@ -61,9 +61,6 @@
"backupButtons": {
"message": "Резервне копіювання"
},
"backupMessage": {
"message": "Виберіть файл або перетягніть на цю сторінку."
},
"bckpInstStyles": {
"message": "Експорт стилів"
},
@ -178,9 +175,21 @@
"importReportTitle": {
"message": "Імпорт стилів закінчено"
},
"installPreferSchemeNone": {
"message": "Завжди"
},
"installUpdate": {
"message": "Встановити оновлення"
},
"manageFavicons": {
"message": "Піктограми для цільових сайтів"
},
"manageFilters": {
"message": "Фільтри"
},
"manageHeading": {
"message": "Встановити Styles"
},
"manageNewUI": {
"message": "Новий інтерфейс"
},
@ -195,6 +204,12 @@
"openManage": {
"message": "Менеджер"
},
"openOptions": {
"message": "Налаштування"
},
"optionsAdvancedAutoSwitchSchemeNever": {
"message": "Ніколи"
},
"optionsReset": {
"message": "Скидання налаштувань до значень за замовчуванням"
},
@ -207,9 +222,33 @@
"optionsSyncLogin": {
"message": "Логін"
},
"paginationNext": {
"message": "Наступна сторінка"
},
"paginationPrevious": {
"message": "Попередня сторінка"
},
"replace": {
"message": "Замінити"
},
"replaceAll": {
"message": "Замінити все"
},
"retrieveBckp": {
"message": "Імпорт стилів"
},
"search": {
"message": "Пошук"
},
"searchStylesAll": {
"message": "Усі"
},
"searchStylesCode": {
"message": "CSS код"
},
"searchStylesName": {
"message": "Назва"
},
"sectionCode": {
"message": "Код"
},
@ -222,12 +261,21 @@
"styleCancelEditLabel": {
"message": "Всі стилі"
},
"syncErrorRelogin": {
"message": "Помилка синхронізації.\nСпробуйте повторно ввійти в налаштування Stylus:\nнатисніть спочатку «від’єднати», а потім «підключити». "
"stylePreferSchemeLabel": {
"message": "Темна/Світла тема"
},
"styleSaveLabel": {
"message": "Зберегти"
},
"toggleStyle": {
"message": "Включити/виключити стиль"
},
"writeStyleFor": {
"message": "Створити стиль для:"
},
"writeStyleForURL": {
"message": "цей URL"
},
"zipStyles": {
"message": "Запаковування стилів ... "
}

View File

@ -70,9 +70,6 @@
"backupButtons": {
"message": "备份"
},
"backupMessage": {
"message": "拖拽JSON备份文件到本页面也可导入。"
},
"bckpInstStyles": {
"message": "导出所有样式"
},
@ -597,9 +594,6 @@
"manageOnlyUsercss": {
"message": "只显示 UserCSS"
},
"manageTitle": {
"message": "Stylus管理器"
},
"menuShowBadge": {
"message": "计数器角标"
},

View File

@ -71,7 +71,7 @@
"message": "备份"
},
"backupMessage": {
"message": "选择备份文件,或将 JSON 备份文件拖放到本页面,即可导入备份。"
"message": "选择/拖拽 JSON 备份文件到本页面来导入备份。\n\n若要导出 Stylus V1.5.18 之前的兼容备份请右击或Shift+左击「导出」按钮。"
},
"bckpInstStyles": {
"message": "导出所有样式"
@ -293,6 +293,9 @@
}
}
},
"editorSettings": {
"message": "编辑器设置"
},
"enableStyleLabel": {
"message": "启用"
},
@ -466,6 +469,18 @@
"installButtonUpdate": {
"message": "更新样式"
},
"installPreferSchemeDark": {
"message": "深色模式"
},
"installPreferSchemeLabel": {
"message": "此样式将在以下模式中应用:"
},
"installPreferSchemeLight": {
"message": "浅色模式"
},
"installPreferSchemeNone": {
"message": "始终"
},
"installUpdate": {
"message": "安装更新"
},
@ -825,6 +840,18 @@
"optionsAdvanced": {
"message": "高级设置"
},
"optionsAdvancedAutoSwitchScheme": {
"message": "自动切换深色/浅色模式样式"
},
"optionsAdvancedAutoSwitchSchemeBySystem": {
"message": "按系统偏好设置"
},
"optionsAdvancedAutoSwitchSchemeByTime": {
"message": "按夜间时间"
},
"optionsAdvancedAutoSwitchSchemeNever": {
"message": "从不"
},
"optionsAdvancedContextDelete": {
"message": "向编辑器右键菜单添加“删除”"
},
@ -918,6 +945,9 @@
"optionsSyncNone": {
"message": "无"
},
"optionsSyncPassword": {
"message": "密码"
},
"optionsSyncStatusConnected": {
"message": "已连接"
},
@ -961,6 +991,9 @@
"optionsSyncSyncNow": {
"message": "现在同步"
},
"optionsSyncUsername": {
"message": "用户名"
},
"optionsUpdateImportNote": {
"message": "从旧版本的 Stylus 或 Stylish 中导入样式备份时,请在样式管理器中手动检测升级一次,以确保所有样式都可更新到最新版本。"
},
@ -1088,7 +1121,7 @@
"message": "/regex/ 用 / 包裹语法来正则搜索"
},
"searchResultInstallCount": {
"message": "总安装次数"
"message": "总安装次数"
},
"searchResultNoneFound": {
"message": "没有找到与此页面相关的样式。"
@ -1144,6 +1177,9 @@
"sections": {
"message": "章节"
},
"settings": {
"message": "设置"
},
"shortcuts": {
"message": "快捷键"
},
@ -1195,12 +1231,18 @@
"styleEnabledLabel": {
"message": "启用"
},
"styleExcludeLabel": {
"message": "自定义排除网站"
},
"styleFromMozillaFormatError": {
"message": "导入 Mozilla 格式失败"
},
"styleFromMozillaFormatPrompt": {
"message": "粘贴 Mozilla 格式代码"
},
"styleIncludeLabel": {
"message": "自定义包括网站"
},
"styleInstall": {
"message": "要将“$stylename$”安装到 Stylus 中吗?",
"placeholders": {
@ -1243,6 +1285,15 @@
"styleNotAppliedRegexpProblemTooltip": {
"message": "正则表达式错误,样式无法正常运行"
},
"styleNotAppliedSchemeDark": {
"message": "此样式仅在深色模式下应用"
},
"styleNotAppliedSchemeLight": {
"message": "此样式仅在浅色模式下应用"
},
"stylePreferSchemeLabel": {
"message": "深色/浅色模式"
},
"styleRegexpInvalidExplanation": {
"message": "部分正则表达式无法生效。"
},
@ -1276,6 +1327,9 @@
"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 会自动弹出导入对话框"
},
@ -1293,6 +1347,9 @@
"styleUpdateDiscardChanges": {
"message": "样式已经在编辑器外被修改。需要重新加载样式吗?"
},
"styleUpdateUrlLabel": {
"message": "更新 URL"
},
"stylusUnavailableForURL": {
"message": "Stylus 无法介入到此类页面"
},
@ -1308,8 +1365,16 @@
"syncError": {
"message": "同步失败"
},
"syncErrorLock": {
"message": "数据库已在使用中。锁定将在 $TIME$ 过期",
"placeholders": {
"time": {
"content": "$1"
}
}
},
"syncErrorRelogin": {
"message": "同步失败\n请尝试在 Stylus 选项里重新登录\n先点击「断开连接」再点击「连接」。"
"message": "同步失败。你已退出登录。\n请尝试在 Stylus 选项里重新登录。"
},
"syncStorageErrorSaving": {
"message": "不能保存该值,请尝试减少文本数量。"

View File

@ -71,7 +71,7 @@
"message": "備份"
},
"backupMessage": {
"message": "選取檔案並拖曳到此頁面。"
"message": "要匯入備份檔案,請將其拖曳至此頁面或點擊匯入按鈕。\n\n要匯出相容於比 1.5.18 還舊的 Stylus 版本的備份,請右鍵點擊或按住 Shift 然後點擊匯出按鈕。"
},
"bckpInstStyles": {
"message": "匯出樣式"
@ -293,6 +293,9 @@
}
}
},
"editorSettings": {
"message": "編輯器設定"
},
"enableStyleLabel": {
"message": "啟用"
},
@ -466,6 +469,18 @@
"installButtonUpdate": {
"message": "更新樣式"
},
"installPreferSchemeDark": {
"message": "在深色模式"
},
"installPreferSchemeLabel": {
"message": "應該套用的樣式:"
},
"installPreferSchemeLight": {
"message": "在淺色模式"
},
"installPreferSchemeNone": {
"message": "一律"
},
"installUpdate": {
"message": "安裝更新"
},
@ -825,6 +840,18 @@
"optionsAdvanced": {
"message": "進階"
},
"optionsAdvancedAutoSwitchScheme": {
"message": "自動切換淺色/深色模式樣式"
},
"optionsAdvancedAutoSwitchSchemeBySystem": {
"message": "按系統偏好設定"
},
"optionsAdvancedAutoSwitchSchemeByTime": {
"message": "到夜間:"
},
"optionsAdvancedAutoSwitchSchemeNever": {
"message": "從不"
},
"optionsAdvancedContextDelete": {
"message": "在編輯器的右鍵選單中加入「刪除」"
},
@ -918,6 +945,9 @@
"optionsSyncNone": {
"message": "無"
},
"optionsSyncPassword": {
"message": "密碼"
},
"optionsSyncStatusConnected": {
"message": "已連線"
},
@ -961,6 +991,9 @@
"optionsSyncSyncNow": {
"message": "立刻同步"
},
"optionsSyncUsername": {
"message": "使用者名稱"
},
"optionsUpdateImportNote": {
"message": "當從舊版本或是從 Stylish 匯入樣式備份時,在樣式管理員中手動進行一次更新檢查,以確保所有樣式都被更新。"
},
@ -1144,6 +1177,9 @@
"sections": {
"message": "樣式段"
},
"settings": {
"message": "設定"
},
"shortcuts": {
"message": "快速鍵"
},
@ -1195,12 +1231,18 @@
"styleEnabledLabel": {
"message": "已啟用"
},
"styleExcludeLabel": {
"message": "自訂排除網站"
},
"styleFromMozillaFormatError": {
"message": "從 Mozilla 格式匯入失敗"
},
"styleFromMozillaFormatPrompt": {
"message": "貼上 Mozilla 格式代碼"
},
"styleIncludeLabel": {
"message": "自訂包含網站"
},
"styleInstall": {
"message": "安裝 '$stylename$' 到 Stylus ",
"placeholders": {
@ -1243,6 +1285,15 @@
"styleNotAppliedRegexpProblemTooltip": {
"message": "因為不正確的 'regexp()' 使用導致未套用"
},
"styleNotAppliedSchemeDark": {
"message": "此樣式僅於深色模式中套用"
},
"styleNotAppliedSchemeLight": {
"message": "此樣式僅於淺色模式中套用"
},
"stylePreferSchemeLabel": {
"message": "深色/淺色模式"
},
"styleRegexpInvalidExplanation": {
"message": "部份 'regexp()' 規則可能不再能被編譯。"
},
@ -1276,6 +1327,9 @@
"styleSaveLabel": {
"message": "儲存"
},
"styleSettings": {
"message": "樣式設定"
},
"styleToMozillaFormatHelp": {
"message": "Mozilla格式的樣式代碼能在火狐版Stylus使用也可以提交至 userstyles.org 。"
},
@ -1293,6 +1347,9 @@
"styleUpdateDiscardChanges": {
"message": "樣式已在編輯器外變更。您想要重新載入樣式嗎?"
},
"styleUpdateUrlLabel": {
"message": "更新 URL"
},
"stylusUnavailableForURL": {
"message": "Stylus 不能在諸如此類的網頁上生效。"
},
@ -1308,8 +1365,16 @@
"syncError": {
"message": "同步失敗"
},
"syncErrorLock": {
"message": "資料庫已在使用中。鎖將於 $TIME$ 過期",
"placeholders": {
"time": {
"content": "$1"
}
}
},
"syncErrorRelogin": {
"message": "同步失敗。\n重是在 Stylus 選項重新登入:\n先點擊「斷線」然後「連線」。"
"message": "同步失敗。您已被登出。\n嘗試在 Stylus 選項中重新登入。"
},
"syncStorageErrorSaving": {
"message": "無法儲存值。嘗試減少文字量。"

View File

@ -73,6 +73,20 @@ addAPI(/** @namespace API */ {
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,

View File

@ -55,7 +55,6 @@ const styleMan = (() => {
name: style => `ID: ${style.id}`,
_id: () => uuidv4(),
_rev: () => Date.now(),
_usw: () => ({}),
};
const DELETE_IF_NULL = ['id', 'customName', 'md5Url', 'originalMd5'];
/** @type {Promise|boolean} will be `true` to avoid wasting a microtask tick on each `await` */

View File

@ -1,5 +1,5 @@
/* global API msg */// msg.js
/* global chromeLocal */// storage-util.js
/* global chromeLocal chromeSync */// storage-util.js
/* global compareRevision */// common.js
/* global iconMan */
/* global prefs */
@ -18,6 +18,7 @@ const syncMan = (() => {
disconnecting: 'disconnecting',
});
const STORAGE_KEY = 'sync/state/';
const NO_LOGIN = ['webdav'];
const status = /** @namespace SyncManager.Status */ {
STATES,
state: STATES.disconnected,
@ -85,19 +86,29 @@ const syncMan = (() => {
return ctrl.put(...args);
},
async setDriveOptions(driveName, options) {
const key = `secure/sync/driveOptions/${driveName}`;
await chromeSync.setValue(key, options);
},
async getDriveOptions(driveName) {
const key = `secure/sync/driveOptions/${driveName}`;
return await chromeSync.getValue(key) || {};
},
async start(name, fromPref = false) {
if (ready.then) await ready;
if (!ctrl) await initController();
if (currentDrive) return;
currentDrive = getDrive(name);
currentDrive = await getDrive(name);
ctrl.use(currentDrive);
status.state = STATES.connecting;
status.currentDriveName = currentDrive.name;
emitStatusChange();
if (fromPref) {
if (fromPref || NO_LOGIN.includes(currentDrive.name)) {
status.login = true;
} else {
try {
@ -240,11 +251,11 @@ const syncMan = (() => {
}
}
function getDrive(name) {
if (name === 'dropbox' || name === 'google' || name === 'onedrive') {
return dbToCloud.drive[name]({
getAccessToken: () => tokenMan.getToken(name),
});
async function getDrive(name) {
if (name === 'dropbox' || name === 'google' || name === 'onedrive' || name === 'webdav') {
const options = await syncMan.getDriveOptions(name);
options.getAccessToken = () => tokenMan.getToken(name);
return dbToCloud.drive[name](options);
}
throw new Error(`unknown cloud name: ${name}`);
}

View File

@ -86,7 +86,7 @@ bgReady.all.then(() => {
const inTab = url.startsWith('file:') && !chrome.app;
const code = await (inTab ? loadFromFile : loadFromUrl)(tabId, url);
if (!/^\s*</.test(code) && RX_META.test(code)) {
openInstallerPage(tabId, url, {code, inTab});
await openInstallerPage(tabId, url, {code, inTab});
}
}
}
@ -99,25 +99,33 @@ bgReady.all.then(() => {
openInstallerPage(tabId, url, {});
// Silently suppress navigation.
// Don't redirect to the install URL as it'll flash the text!
return {redirectUrl: 'javascript:void 0'}; // eslint-disable-line no-script-url
return {cancel: true};
}
}
function openInstallerPage(tabId, url, {code, inTab} = {}) {
async function openInstallerPage(tabId, url, {code, inTab} = {}) {
const newUrl = `${URLS.installUsercss}?updateUrl=${encodeURIComponent(url)}`;
if (inTab) {
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};
chrome.tabs.update(tabId, {url: newUrl});
const tab = await browser.tabs.get(tabId);
return openURL({
url: `${newUrl}&tabId=${tabId}`,
active: tab.active,
index: tab.index + 1,
openerTabId: tabId,
currentWindow: null,
});
}
const timer = setTimeout(clearInstallCode, 10e3, url);
installCodeCache[url] = {code, timer};
try {
await browser.tabs.update(tabId, {url: newUrl});
} catch (err) {
// FIXME: remove this when kiwi supports tabs.update
// https://github.com/openstyles/stylus/issues/1367
if (/Tabs cannot be edited right now/i.test(err.message)) {
return browser.tabs.create({url: newUrl});
}
throw err;
}
}

103
edit.html
View File

@ -17,7 +17,6 @@
<script src="content/apply.js"></script>
<script src="js/sections-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>
@ -62,7 +61,6 @@
<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">
@ -216,11 +214,11 @@
</template>
<template data-id="keymapHelp">
<table class="keymap-list">
<table class="keymap-list can-close-on-esc">
<thead>
<tr>
<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>
<th><input i18n-placeholder="helpKeyMapHotkey" type="search"></th>
<th><input i18n-placeholder="helpKeyMapCommand" type="search"></th>
</tr>
</thead>
<tbody>
@ -232,6 +230,44 @@
</table>
</template>
<template data-id="styleSettings">
<div>
<fieldset class="style-settings can-close-on-esc">
<label i18n-text="styleUpdateUrlLabel">
<input id="ss-update-url" type="text">
</label>
<div>
<div i18n-text="installPreferSchemeLabel"></div>
<label i18n-text-append="installPreferSchemeNone">
<input name="ss-scheme" type="radio" value="none">
</label>
<label i18n-text-append="installPreferSchemeDark">
<input name="ss-scheme" type="radio" value="dark">
</label>
<label i18n-text-append="installPreferSchemeLight">
<input name="ss-scheme" type="radio" value="light">
</label>
</div>
<label i18n-text="styleIncludeLabel">
<textarea id="ss-inclusions" spellcheck="false"
placeholder="*://site1.com/*&#10;*://site2.com/*"></textarea>
</label>
<label i18n-text="styleExcludeLabel">
<textarea id="ss-exclusions" spellcheck="false"
placeholder="*://site1.com/*&#10;*://site2.com/*"></textarea>
</label>
</fieldset>
<div class="buttons">
<button id="ss-save" i18n-text="confirmSave" disabled></button>
<label i18n-title="configOnChangeTooltip" i18n-text-append="configOnChange">
<input id="config.autosave" type="checkbox">
<svg class="svg-icon checked"><use xlink:href="#svg-icon-checked"/></svg>
</label>
<button id="ss-close" i18n-text="confirmClose"></button>
</div>
</div>
</template>
<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">
@ -241,8 +277,6 @@
<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>
<body id="stylus-edit">
@ -278,7 +312,8 @@
<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>
<button id="style-settings-btn" i18n-text="settings"></button>
<button id="cancel-button" i18n-title="styleCancelEditLabel"></button>
</div>
<div id="mozilla-format-buttons" class="sectioned-only">
<button id="from-mozilla" i18n-text="importLabel"></button>
@ -291,7 +326,7 @@
</section>
<div id="details-wrapper">
<details id="options" data-pref="editor.options.expanded" class="ignore-pref-if-compact">
<summary><h2 id="options-heading" i18n-text="optionsHeading"></h2></summary>
<summary><h2 id="options-heading" i18n-text="editorSettings"></h2></summary>
<div id="options-wrapper">
<div class="options-column">
<div class="option">
@ -431,59 +466,14 @@
</div>
</details>
</div>
<div id="header-resizer" i18n-title="headerResizerHint"></div>
<div id="footer" class="hidden">
<a href="https://github.com/openstyles/stylus/wiki/Usercss"
i18n-text="externalUsercssDocument"
target="_blank"></a>
</div>
</div>
<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>
<section id="sections"></section>
<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>
@ -528,6 +518,5 @@
</symbol>
</svg>
<script src="edit/tab.js"></script>
</body>
</html>

View File

@ -14,14 +14,13 @@
tryJSONparse
tryURL
*/// toolbox.js
/* global EventEmitter */
'use strict';
/**
* @type Editor
* @namespace Editor
*/
const editor = Object.assign(EventEmitter(), {
const editor = {
style: null,
dirty: DirtyReporter(),
isUsercss: false,
@ -34,9 +33,12 @@ const editor = Object.assign(EventEmitter(), {
/** @type {'customName'|'name'} */
nameTarget: 'name',
previewDelay: 200, // Chrome devtools uses 200
saving: false,
scrollInfo: null,
onStyleUpdated() {
cancel: () => location.assign('/manage.html'),
updateClass() {
document.documentElement.classList.toggle('is-new-style', !editor.style.id);
},
@ -48,7 +50,7 @@ const editor = Object.assign(EventEmitter(), {
customName || name || t('styleMissingName')
} - Stylus`; // the suffix enables external utilities to process our windows e.g. pin on top
},
});
};
//#region pre-init
@ -90,7 +92,7 @@ const baseInit = (() => {
// switching the mode here to show the correct page ASAP, usually before DOMContentLoaded
editor.isUsercss = Boolean(style.usercssData || !style.id && prefs.get('newStyleAsUsercss'));
editor.style = style;
editor.onStyleUpdated();
editor.updateClass();
editor.updateTitle(false);
document.documentElement.classList.toggle('usercss', editor.isUsercss);
sessionStore.justEditedStyleId = style.id || '';
@ -292,16 +294,10 @@ baseInit.ready.then(() => {
}
}
getOwnTab().then(async tab => {
getOwnTab().then(tab => {
ownTabId = tab.id;
// use browser history back when 'back to manage' is clicked
if (sessionStore['manageStylesHistory' + ownTabId] === location.href) {
await baseInit.domReady;
$('#cancel-button').onclick = event => {
event.stopPropagation();
event.preventDefault();
history.back();
};
editor.cancel = () => history.back();
}
});

View File

@ -65,7 +65,7 @@ function beautifyEditor(cm, options, ui) {
window.scrollTo(scrollX, scrollY);
cm.beautifyChange[cm.changeGeneration()] = true;
if (ui) {
$('#help-popup button[role="close"]').disabled = false;
$('button[role="close"]', helpPopup.div).disabled = false;
}
}
}
@ -87,7 +87,7 @@ function createBeautifyUI(scope, options) {
$create('span', t('styleBeautifyHint') + '\u00A0'),
createHotkeyInput('editor.beautify.hotkey', {
buttons: false,
onDone: () => moveFocus($('#help-popup'), 0),
onDone: () => moveFocus(helpPopup.div, 0),
}),
]),
$create('.buttons', [
@ -113,16 +113,16 @@ function createBeautifyUI(scope, options) {
},
}, t(scope.length === 1 ? 'undo' : 'undoGlobal')),
]),
]));
$('#help-popup').className = 'wide';
]),
{
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);
};

View File

@ -34,6 +34,7 @@ a:hover {
}
html.is-new-style #preview-label,
html.is-new-style #style-settings-btn,
html.is-new-style #publish,
.hidden {
display: none !important;
@ -89,13 +90,12 @@ label {
/************ header ************/
#header {
width: 280px;
width: var(--header-width);
height: 100vh;
overflow: auto;
position: fixed;
top: 0;
padding: 1rem;
border-right: 1px dashed #AAA;
box-shadow: 0 0 3rem -1.2rem black;
box-sizing: border-box;
z-index: 10;
@ -105,11 +105,8 @@ label {
#header h1 {
margin-top: 0;
}
.main {
padding-left: 280px;
height: 100%;
}
#sections {
padding-left: var(--header-width);
min-height: 0;
height: 100%;
}
@ -284,10 +281,6 @@ input:invalid {
margin: 0 .2rem .5rem 0;
}
#actions #cancel-button {
margin: 0;
}
#options:not([open]) + #lint h2 {
margin-top: 0;
}
@ -417,10 +410,6 @@ input:invalid {
.edit-actions button {
margin-right: .2rem;
}
.dirty > label::before {
content: "*";
font-weight: bold;
}
#sections {
counter-reset: codebox;
}
@ -736,6 +725,9 @@ body:not(.find-open) [data-match-highlight-count="1"] .CodeMirror-selection-high
}
/************ help popup ************/
#help-popup {
--pad-x: 1.5rem;
--pad-y: 1rem;
--pad-y2: calc(var(--pad-y) / 1.5);
top: 3rem;
right: 3rem;
max-width: 50vw;
@ -743,7 +735,7 @@ body:not(.find-open) [data-match-highlight-count="1"] .CodeMirror-selection-high
display: none;
background-color: white;
box-shadow: 3px 3px 30px rgba(0, 0, 0, 0.5);
padding: 0.5rem;
padding: var(--pad-y) var(--pad-x) 0;
z-index: 99;
}
#help-popup.big,
@ -752,7 +744,7 @@ body:not(.find-open) [data-match-highlight-count="1"] .CodeMirror-selection-high
}
#help-popup.big {
box-shadow: rgba(0, 0, 0, 0.45) 0px 0px 0px 100000px !important;
left: calc(280px - 3rem);
left: calc(var(--header-width) - 3rem);
}
#help-popup.big .CodeMirror {
min-height: 2rem;
@ -761,22 +753,19 @@ body:not(.find-open) [data-match-highlight-count="1"] .CodeMirror-selection-high
#help-popup .title {
font-weight: bold;
background-color: rgba(0,0,0,0.05);
margin: -0.5rem -0.5rem 0.5rem;
padding: .5rem 32px .5rem .5rem;
margin: calc(-1 * var(--pad-y)) calc(-1 * var(--pad-x)) 0;
padding: var(--pad-y2) var(--pad-x);
}
#help-popup .contents {
max-height: calc(100vh - 8rem);
overflow-y: auto;
}
#help-popup .settings {
min-width: 500px;
min-height: 200px;
max-width: 48vw;
padding: var(--pad-y) 0;
}
#help-popup .dismiss {
position: absolute;
right: 4px;
top: .5em;
right: 0;
top: 0;
padding: var(--pad-y2) .5em;
}
#help-popup input[type="search"],
#help-popup .CodeMirror {
@ -804,12 +793,14 @@ body:not(.find-open) [data-match-highlight-count="1"] .CodeMirror-selection-high
}
#help-popup .buttons {
text-align: center;
margin-top: .75em;
display: flex;
justify-content: center;
align-items: center;
margin: var(--pad-y2) 0 calc(var(--pad-y2) - var(--pad-y)) 0;
}
.non-windows #help-popup .buttons {
direction: rtl;
text-align: right;
justify-content: start;
}
#help-popup button[name^="import"] {
line-height: 1.5rem;
@ -831,8 +822,8 @@ body:not(.find-open) [data-match-highlight-count="1"] .CodeMirror-selection-high
#help-popup .rules p {
margin: .25em 0;
}
#help-popup .buttons button:nth-child(n + 2) {
margin-left: .5em;
#help-popup .buttons > :nth-child(n + 2) {
margin-inline: .5em 0;
}
/************ lint ************/
@ -1180,16 +1171,13 @@ body.linter-disabled .hidden-unless-compact {
#lint:not([open]) + #footer {
margin: .25em 0 -1em .25em;
}
.main {
#sections {
height: unset !important;
padding-left: 0;
display: flex;
flex-direction: column;
flex: 1;
}
.tab-bar {
margin-top: var(--fixed-height);
}
#sections > :not(.single-editor) {
margin: 0 .5rem;
padding: .5rem 0;

View File

@ -1,5 +1,5 @@
/* global $ $create messageBoxProxy waitForSheet */// dom.js
/* global msg API */// msg.js
/* global API msg */// msg.js
/* global CodeMirror */
/* global SectionsEditor */
/* global SourceEditor */
@ -11,7 +11,6 @@
/* global linterMan */
/* global prefs */
/* global t */// localization.js
/* global StyleSettings */// settings.js
'use strict';
//#region init
@ -19,7 +18,6 @@
baseInit.ready.then(async () => {
await waitForSheet();
(editor.isUsercss ? SourceEditor : SectionsEditor)();
StyleSettings(editor);
await editor.ready;
editor.ready = true;
editor.dirty.onChange(editor.updateDirty);
@ -32,6 +30,7 @@ baseInit.ready.then(async () => {
// 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
@ -48,6 +47,11 @@ baseInit.ready.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/global-search',
@ -57,27 +61,12 @@ baseInit.ready.then(async () => {
//#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 && !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);
});
}
if (editor.style.id === style.id) {
handleExternalUpdate(request);
}
break;
case 'styleDeleted':
@ -91,11 +80,45 @@ msg.onExtension(request => {
}
});
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);
editor.updateLivePreview();
} 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 +191,14 @@ window.on('beforeunload', e => {
}
},
toggleStyle(enabled = style.enabled) {
async save() {
if (dirty.isDirty()) {
editor.saving = true;
await editor.saveImpl();
}
},
toggleStyle(enabled = !style.enabled) {
$('#enabled').checked = enabled;
editor.updateEnabledness(enabled);
},
@ -236,6 +266,16 @@ window.on('beforeunload', e => {
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();
},
});
})();

View File

@ -381,8 +381,7 @@ function createResizeGrip(cm) {
cm.display.lineDiv.offsetParent.offsetTop +
/* borders */
wrapper.offsetHeight - wrapper.clientHeight;
wrapper.style.pointerEvents = 'none';
document.body.style.cursor = 's-resize';
document.body.classList.add('resizing-v');
document.on('mousemove', resize);
document.on('mouseup', resizeStop);
@ -398,8 +397,7 @@ function createResizeGrip(cm) {
function resizeStop() {
document.off('mouseup', resizeStop);
document.off('mousemove', resize);
wrapper.style.pointerEvents = '';
document.body.style.cursor = '';
document.body.classList.remove('resizing-v');
}
};

View File

@ -1,12 +1,13 @@
/* global $ $$ $create $remove messageBoxProxy */// dom.js
/* global API */// msg.js
/* global CodeMirror */
/* global FIREFOX RX_META debounce ignoreChromeError sessionStore */// toolbox.js
/* global FIREFOX RX_META debounce ignoreChromeError */// 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';
@ -23,7 +24,7 @@ function SectionsEditor() {
let headerOffset; // in compact mode the header is at the top so it reduces the available height
let cmExtrasHeight; // resize grip + borders
updateHeader();
updateMeta();
rerouteHotkeys.toggle(true); // enabled initially because we don't always focus a CodeMirror
editor.livePreview.init();
container.classList.add('section-editor');
@ -44,6 +45,7 @@ function SectionsEditor() {
closestVisible,
updateLivePreview,
updateMeta,
getEditors() {
return sections.filter(s => !s.removed).map(s => s.cm);
@ -85,34 +87,27 @@ function SectionsEditor() {
},
async replaceStyle(newStyle) {
const sameCode = styleSectionsEqual(newStyle, getModel());
if (!sameCode && !await messageBoxProxy.confirm(t('styleUpdateDiscardChanges'))) {
return;
}
dirty.clear();
// FIXME: avoid recreating all editors?
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}`);
if (!sameCode) {
await initSections(newStyle.sections, {replace: true});
}
editor.useSavedStyle(newStyle);
updateLivePreview();
},
async save() {
if (!dirty.isDirty()) {
return;
}
async saveImpl() {
let newStyle = getModel();
if (!validate(newStyle)) {
return;
}
newStyle = await API.styles.editSave(newStyle);
destroyRemovedSections();
if (!style.id) {
editor.emit('styleChange', newStyle, 'new');
}
sessionStore.justEditedStyleId = newStyle.id;
editor.replaceStyle(newStyle, false);
dirty.clear();
editor.useSavedStyle(newStyle);
},
scrollToEditor(cm) {
@ -128,28 +123,6 @@ function SectionsEditor() {
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) {
const {cm, cm: {display: {wrapper, sizer}}} = section;
@ -477,19 +450,7 @@ function SectionsEditor() {
return true;
}
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() {
function updateMeta() {
$('#name').value = style.customName || style.name || '';
$('#enabled').checked = style.enabled !== false;
$('#url').href = style.url || '';

View File

@ -1,46 +1,43 @@
#help-popup.style-settings-popup.dirty .title::after {
content: ' *';
}
.compact-layout #help-popup.style-settings-popup {
width: 90%;
}
.style-settings {
padding: 0.7rem 1.7rem;
padding: 0;
border: 0;
margin: 0;
}
.form-group {
.style-settings > * {
display: block;
margin: .6em 0;
margin: 1rem 0;
padding: 0;
}
.form-label {
display: inline-block;
margin: .3em 0;
.style-settings > :first-child {
margin-top: 0;
}
[disabled] .form-label {
opacity: 0.4;
.style-settings > :last-child {
margin-bottom: 0;
}
.form-group input[type=text],
.form-group input[type=number],
.form-group select,
.form-group textarea {
.style-settings input[type=radio] {
margin-left: -.5em; /* compensate for label's 16px margin in edit.css */
}
.style-settings input[type=text],
.style-settings input[type=number],
.style-settings select,
.style-settings 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;
}

View File

@ -1,84 +1,97 @@
/* global $ $$ */// dom.js
/* global $ $$ moveFocus setupLivePrefs */// dom.js
/* global API */// msg.js
/* global editor */
/* global helpPopup */// util.js
/* global t */// localization.js
/* global debounce */// toolbox.js
/* exported StyleSettings */
'use strict';
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),
function StyleSettings() {
const AUTOSAVE_DELAY = 500; // same as config-dialog.js
const SS_ID = 'styleSettings';
const {style} = editor;
const ui = t.template[SS_ID].cloneNode(true);
const elAuto = $('[id="config.autosave"]', ui);
const elSave = $('#ss-save', ui);
const pendingSetters = new Map();
const updaters = [
initInput('#ss-update-url', () => style.updateUrl || '',
val => API.styles.config(style.id, 'updateUrl', val)),
initRadio('ss-scheme', () => style.preferScheme || 'none',
val => API.styles.config(style.id, 'preferScheme', val)),
initArea('inclusions'),
initArea('exclusions'),
];
update();
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);
update(style);
editor.on('styleChange', update);
function textToList(text) {
const list = text.split(/\s*\r?\n\s*/g);
return list.filter(Boolean);
function autosave(el, setter) {
pendingSetters.set(el, setter);
helpPopup.div.classList.add('dirty');
elSave.disabled = false;
if (elAuto.checked) debounce(save, AUTOSAVE_DELAY);
}
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', () => {
function initArea(type) {
const selector = `#ss-${type}`;
const el = $(selector, ui);
el.oninput = () => {
const val = el.value;
el.rows = val.match(/^/gm).length + !val.endsWith('\n');
});
return createInput(sel,
};
return initInput(selector,
() => {
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))
val => API.styles.config(style.id, type, textToList(val))
);
}
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 initInput(selector, getter, setter) {
const el = $(selector, ui);
el.oninput = () => autosave(el, setter);
return () => {
const val = getter();
// Skipping if unchanged to preserve the Undo history of the input
if (el.value !== val) el.value = val;
};
}
function createInput(selector, getter, setter) {
const el = $(selector);
el.addEventListener('change', setter);
return {
update() {
el.value = getter();
},
function initRadio(name, getter, setter) {
for (const el of $$(`[name="${name}"]`, ui)) {
el.onchange = () => {
if (el.checked) autosave(el, setter);
};
}
return () => {
$(`[name="${name}"][value="${getter()}"]`, ui).checked = true;
};
}
function save() {
pendingSetters.forEach((fn, el) => fn(el.value));
pendingSetters.clear();
helpPopup.div.classList.remove('dirty');
elSave.disabled = true;
}
function textToList(text) {
return text.split(/\n/).map(s => s.trim()).filter(Boolean);
}
function update() {
updaters.forEach(fn => fn());
}
}

View File

@ -4,7 +4,7 @@
/* global MozDocMapper */// util.js
/* global MozSectionFinder */
/* global MozSectionWidget */
/* global RX_META debounce sessionStore */// toolbox.js
/* global RX_META debounce */// toolbox.js
/* global chromeSync */// storage-util.js
/* global cmFactory */
/* global editor */
@ -45,6 +45,7 @@ function SourceEditor() {
sections: sectionFinder.sections,
replaceStyle,
updateLivePreview,
updateMeta,
closestVisible: () => cm,
getEditors: () => [cm],
getEditorTitle: () => '',
@ -60,8 +61,7 @@ function SourceEditor() {
cm.focus();
}
},
async save() {
if (!dirty.isDirty()) return;
async saveImpl() {
const sourceCode = cm.getValue();
try {
const {customName, enabled, id} = style;
@ -70,9 +70,6 @@ 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);
}
@ -116,26 +113,6 @@ function SourceEditor() {
if (!$isTextInput(document.activeElement)) {
cm.focus();
}
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({
@ -231,21 +208,20 @@ function SourceEditor() {
cm.setPreprocessor((style.usercssData || {}).preprocessor);
}
function replaceStyle(newStyle) {
async function replaceStyle(newStyle) {
dirty.clear('name');
const sameCode = newStyle.sourceCode === cm.getValue();
if (sameCode) {
savedGeneration = cm.changeGeneration();
dirty.clear('sourceGeneration');
updateEnvironment();
editor.useSavedStyle(newStyle);
dirty.clear('enabled');
updateLivePreview();
return;
}
Promise.resolve(messageBoxProxy.confirm(t('styleUpdateDiscardChanges'))).then(ok => {
if (!ok) return;
updateEnvironment();
if (await messageBoxProxy.confirm(t('styleUpdateDiscardChanges'))) {
editor.useSavedStyle(newStyle);
if (!sameCode) {
const cursor = cm.getCursor();
cm.setValue(style.sourceCode);
@ -257,16 +233,6 @@ function SourceEditor() {
updateLivePreview();
}
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();
}
}

View File

@ -7,20 +7,28 @@
const helpPopup = {
show(title = '', body) {
/**
* @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) {
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);
// reset any inline styles
div.style = 'display: block';
div.style.display = 'block';
helpPopup.originalFocus = document.activeElement;
helpPopup.div = div;
return div;
},
@ -31,11 +39,13 @@ const helpPopup = {
getEventKeyName(event) === 'Escape' &&
!$('.CodeMirror-hints, #message-box') && (
!document.activeElement ||
!document.activeElement.closest('#search-replace-dialog') &&
document.activeElement.matches(':not(input), .can-close-on-esc')
!document.activeElement.closest('#search-replace-dialog') && (
document.activeElement.tagName !== 'INPUT' ||
document.activeElement.closest('.can-close-on-esc')
)
)
);
const div = $('#help-popup');
const {div} = helpPopup;
if (!canClose || !div) {
return;
}
@ -170,8 +180,7 @@ function createHotkeyInput(prefId, {buttons = true, onDone}) {
/* exported showCodeMirrorPopup */
function showCodeMirrorPopup(title, html, options) {
const popup = helpPopup.show(title, html);
popup.classList.add('big');
const popup = helpPopup.show(title, html, {className: 'big'});
let cm = popup.codebox = CodeMirror($('.contents', popup), Object.assign({
mode: 'css',

View File

@ -265,6 +265,48 @@ input[type="number"][data-focused-via-click]:focus {
box-shadow: none;
}
/* header resizer */
:root {
--header-width: 280px;
}
#header-resizer {
position: absolute;
top: 0;
right: 0;
bottom: 0;
width: 6px;
cursor: e-resize;
border-width: 0 1px;
border-style: solid;
color: #8888;
border-color: currentColor;
pointer-events: auto;
}
#header-resizer:active {
border-color: #888;
}
#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 */
@supports (-moz-appearance: none) {
.moz-appearance-bug .svg-icon.checked,
.moz-appearance-bug .onoffswitch input,
@ -318,3 +360,9 @@ input[type="number"][data-focused-via-click]:focus {
padding: 2px 4px;
}
}
@media (max-width: 850px) {
#header-resizer {
display: none !important;
}
}

View File

@ -80,6 +80,7 @@
</ul>
</div>
</div>
<div id="header-resizer" i18n-title="headerResizerHint"></div>
</div>
<div class="main">
<div class="warnings"></div>

View File

@ -29,10 +29,9 @@ input:disabled + span {
#header,
.warnings {
flex: 0 0 280px;
flex: 0 0 var(--header-width);
box-sizing: border-box;
padding: 1rem;
border-right: 1px dashed #aaa;
box-shadow: 0 0 50px -18px black;
overflow-wrap: break-word;
overflow-y: auto;

View File

@ -195,7 +195,7 @@ self.parserlib = (() => {
'color-interpolation-filters': 'auto | sRGB | linearRGB',
'color-profile': 1,
'color-rendering': 'auto | optimizeSpeed | optimizeQuality',
'color-scheme': 'normal | [ light | dark ]+',
'color-scheme': 'normal | [ light | dark | <ident> ]+ && only?',
'column-count': '<integer> | auto',
'column-fill': 'auto | balance',
'column-gap': '<column-gap>',
@ -522,6 +522,7 @@ self.parserlib = (() => {
'scroll-snap-type': 'none | [ x | y | block | inline | both ] [ mandatory | proximity ]?',
'scrollbar-color': 'auto | dark | light | <color>{2}',
'scrollbar-gutter': 'auto | [ [ stable | always ] && both-edges? && force? ] || match-parent',
'scrollbar-width': 'auto | thin | none',
'shape-inside': 'auto | outside-shape | [ <basic-shape> || shape-box ] | <image> | display',
'shape-rendering': 'auto | optimizeSpeed | crispEdges | geometricPrecision',
@ -983,12 +984,12 @@ self.parserlib = (() => {
'matrix( <number>#{6} ) | ' +
'matrix3d( <number>#{16} ) | ' +
'perspective( <len0+> | none ) | ' +
'rotate( <angle-or-0> ) | ' +
'rotate( <angle-or-0> | none ) | ' +
'rotate3d( <number>#{3} , <angle-or-0> ) | ' +
'rotateX( <angle-or-0> ) | ' +
'rotateY( <angle-or-0> ) | ' +
'rotateZ( <angle-or-0> ) | ' +
'scale( [ <num-pct> ]#{1,2} ) | ' +
'scale( [ <num-pct> ]#{1,2} | none ) | ' +
'scale3d( <num-pct>#{3} ) | ' +
'scaleX( <num-pct> ) | ' +
'scaleY( <num-pct> ) | ' +
@ -996,7 +997,7 @@ self.parserlib = (() => {
'skew( <angle-or-0> [ , <angle-or-0> ]? ) | ' +
'skewX( <angle-or-0> ) | ' +
'skewY( <angle-or-0> ) | ' +
'translate( <len-pct>#{1,2} ) | ' +
'translate( <len-pct>#{1,2} | none ) | ' +
'translate3d( <len-pct>#{2} , <length> ) | ' +
'translateX( <len-pct> ) | ' +
'translateY( <len-pct> ) | ' +
@ -4126,8 +4127,8 @@ self.parserlib = (() => {
this.fire(event, property);
if (consumeSemicolon) {
while (stream.match(TT.semiS)) {/*NOP*/}
this._ws();
}
this._ws();
return true;
}
@ -4391,7 +4392,7 @@ self.parserlib = (() => {
readMargins && this._margin() ||
(tt && stream.unget(), this._declaration(true, Props)) ||
(next = stream.LT(1)).value === ';' ||
this._ws(next, true)) {
this._ws(null, true)) {
continue;
}
break;

124
js/dom-on-load.js Normal file
View File

@ -0,0 +1,124 @@
/* global $ $$ focusAccessibility getEventKeyName */// dom.js
/* global debounce */// toolbox.js
/* global t */// localization.js
'use strict';
/** DOM housekeeping after a page finished loading */
(() => {
splitLongTooltips();
addTooltipsToEllipsized();
window.on('mousedown', suppressFocusRingOnClick, {passive: true});
window.on('keydown', keepFocusRingOnTabbing, {passive: true});
window.on('keypress', clickDummyLinkOnEnter);
window.on('wheel', changeFocusedInputOnWheel, {capture: true, passive: false});
window.on('click', showTooltipNote);
window.on('resize', () => debounce(addTooltipsToEllipsized, 100));
// Removing transition-suppressor rule
const {sheet} = $('link[href^="global.css"]');
for (let i = 0, rule; (rule = sheet.cssRules[i]); i++) {
if (/#\\1\s?transition-suppressor/.test(rule.selectorText)) {
sheet.deleteRule(i);
break;
}
}
function changeFocusedInputOnWheel(event) {
const el = document.activeElement;
if (!el || el !== event.target && !el.contains(event.target)) {
return;
}
const isSelect = el.tagName === 'SELECT';
if (isSelect || el.tagName === 'INPUT' && el.type === 'range') {
const key = isSelect ? 'selectedIndex' : 'valueAsNumber';
const old = el[key];
const rawVal = old + Math.sign(event.deltaY) * (el.step || 1);
el[key] = Math.max(el.min || 0, Math.min(el.max || el.length - 1, rawVal));
if (el[key] !== old) {
el.dispatchEvent(new Event('change', {bubbles: true}));
}
event.preventDefault();
}
event.stopImmediatePropagation();
}
/** Displays a full text tooltip on buttons with ellipsis overflow and no inherent title */
function addTooltipsToEllipsized() {
for (const btn of document.getElementsByTagName('button')) {
if (btn.title && !btn.titleIsForEllipsis) {
continue;
}
const width = btn.offsetWidth;
if (!width || btn.preresizeClientWidth === width) {
continue;
}
btn.preresizeClientWidth = width;
if (btn.scrollWidth > width) {
const text = btn.textContent;
btn.title = text.includes('\u00AD') ? text.replace(/\u00AD/g, '') : text;
btn.titleIsForEllipsis = true;
} else if (btn.title) {
btn.title = '';
}
}
}
function clickDummyLinkOnEnter(e) {
if (getEventKeyName(e) === 'Enter') {
const a = e.target.closest('a');
const isDummy = a && !a.href && a.tabIndex === 0;
if (isDummy) a.dispatchEvent(new MouseEvent('click', {bubbles: true}));
}
}
function keepFocusRingOnTabbing(event) {
if (event.key === 'Tab' && !event.ctrlKey && !event.altKey && !event.metaKey) {
focusAccessibility.lastFocusedViaClick = false;
setTimeout(() => {
let el = document.activeElement;
if (el) {
el = el.closest('[data-focused-via-click]');
if (el) delete el.dataset.focusedViaClick;
}
});
}
}
function suppressFocusRingOnClick({target}) {
const el = focusAccessibility.closest(target);
if (el) {
focusAccessibility.lastFocusedViaClick = true;
if (el.dataset.focusedViaClick === undefined) {
el.dataset.focusedViaClick = '';
}
}
}
function showTooltipNote(event) {
const el = event.target.closest('[data-cmd=note]');
if (el) {
event.preventDefault();
window.messageBoxProxy.show({
className: 'note center-dialog',
contents: el.dataset.title || el.title,
buttons: [t('confirmClose')],
});
}
}
function splitLongTooltips() {
for (const el of $$('[title]')) {
el.dataset.title = el.title;
el.title = el.title.replace(/<\/?\w+>/g, ''); // strip html tags
if (el.title.length < 50) {
continue;
}
const newTitle = el.title
.split('\n')
.map(s => s.replace(/([^.][.。?!]|.{50,60},)\s+/g, '$1\n'))
.map(s => s.replace(/(.{50,80}(?=.{40,}))\s+/g, '$1\n'))
.join('\n');
if (newTitle !== el.title) el.title = newTitle;
}
}
})();

169
js/dom.js
View File

@ -1,6 +1,5 @@
/* global FIREFOX debounce */// toolbox.js
/* global FIREFOX */// toolbox.js
/* global prefs */
/* global t */// localization.js
'use strict';
/* exported
@ -9,9 +8,11 @@
$remove
$$remove
animateElement
focusAccessibility
getEventKeyName
messageBoxProxy
moveFocus
onDOMready
scrollElementIntoView
setupLivePrefs
showSpinner
@ -427,6 +428,8 @@ async function waitForSheet({
//#endregion
//#region Internals
const dom = {};
(() => {
const Collapsible = {
@ -459,42 +462,33 @@ async function waitForSheet({
}
},
};
window.on('mousedown', suppressFocusRingOnClick, {passive: true});
window.on('keydown', keepFocusRingOnTabbing, {passive: true});
const lazyScripts = [
'/js/dom-on-load',
];
const elHtml = document.documentElement;
if (!/^Win\d+/.test(navigator.platform)) {
document.documentElement.classList.add('non-windows');
elHtml.classList.add('non-windows');
}
// set language for a) CSS :lang pseudo and b) hyphenation
document.documentElement.setAttribute('lang', chrome.i18n.getUILanguage());
document.on('keypress', clickDummyLinkOnEnter);
document.on('wheel', changeFocusedInputOnWheel, {capture: true, passive: false});
document.on('click', showTooltipNote);
Promise.resolve().then(async () => {
if (!chrome.app) addFaviconFF();
await prefs.ready;
waitForSelector('details[data-pref]', {recur: Collapsible.bindEvents});
});
onDOMready().then(() => {
splitLongTooltips();
debounce(addTooltipsToEllipsized, 500);
window.on('resize', () => debounce(addTooltipsToEllipsized, 100));
});
window.on('load', () => {
const {sheet} = $('link[href$="global.css"]');
for (let i = 0, rule; (rule = sheet.cssRules[i]); i++) {
if (/#\\1\s?transition-suppressor/.test(rule.selectorText)) {
sheet.deleteRule(i);
break;
}
}
}, {once: true});
function addFaviconFF() {
elHtml.setAttribute('lang', chrome.i18n.getUILanguage());
// set up header width resizer
const HW = 'headerWidth.';
const HWprefId = HW + location.pathname.match(/^.(\w*)/)[1];
if (prefs.knownKeys.includes(HWprefId)) {
Object.assign(dom, {
HW,
HWprefId,
setHWProp(width) {
width = Math.round(Math.max(200, Math.min(innerWidth / 3, Number(width) || 0)));
elHtml.style.setProperty('--header-width', width + 'px');
return width;
},
});
prefs.ready.then(() => dom.setHWProp(prefs.get(HWprefId)));
lazyScripts.push('/js/header-resizer');
}
// add favicon in FF
if (FIREFOX) {
const iconset = ['', 'light/'][prefs.get('iconset')] || '';
for (const size of [38, 32, 19, 16]) {
document.head.appendChild($create('link', {
@ -504,105 +498,12 @@ async function waitForSheet({
}));
}
}
function changeFocusedInputOnWheel(event) {
const el = document.activeElement;
if (!el || el !== event.target && !el.contains(event.target)) {
return;
}
const isSelect = el.tagName === 'SELECT';
if (isSelect || el.tagName === 'INPUT' && el.type === 'range') {
const key = isSelect ? 'selectedIndex' : 'valueAsNumber';
const old = el[key];
const rawVal = old + Math.sign(event.deltaY) * (el.step || 1);
el[key] = Math.max(el.min || 0, Math.min(el.max || el.length - 1, rawVal));
if (el[key] !== old) {
el.dispatchEvent(new Event('change', {bubbles: true}));
}
event.preventDefault();
}
event.stopImmediatePropagation();
}
/** Displays a full text tooltip on buttons with ellipsis overflow and no inherent title */
function addTooltipsToEllipsized() {
for (const btn of document.getElementsByTagName('button')) {
if (btn.title && !btn.titleIsForEllipsis) {
continue;
}
const width = btn.offsetWidth;
if (!width || btn.preresizeClientWidth === width) {
continue;
}
btn.preresizeClientWidth = width;
if (btn.scrollWidth > width) {
const text = btn.textContent;
btn.title = text.includes('\u00AD') ? text.replace(/\u00AD/g, '') : text;
btn.titleIsForEllipsis = true;
} else if (btn.title) {
btn.title = '';
}
}
}
function clickDummyLinkOnEnter(e) {
if (getEventKeyName(e) === 'Enter') {
const a = e.target.closest('a');
const isDummy = a && !a.href && a.tabIndex === 0;
if (isDummy) a.dispatchEvent(new MouseEvent('click', {bubbles: true}));
}
}
function keepFocusRingOnTabbing(event) {
if (event.key === 'Tab' && !event.ctrlKey && !event.altKey && !event.metaKey) {
focusAccessibility.lastFocusedViaClick = false;
setTimeout(() => {
let el = document.activeElement;
if (el) {
el = el.closest('[data-focused-via-click]');
if (el) delete el.dataset.focusedViaClick;
}
});
}
}
function suppressFocusRingOnClick({target}) {
const el = focusAccessibility.closest(target);
if (el) {
focusAccessibility.lastFocusedViaClick = true;
if (el.dataset.focusedViaClick === undefined) {
el.dataset.focusedViaClick = '';
}
}
}
function showTooltipNote(event) {
const el = event.target.closest('[data-cmd=note]');
if (el) {
event.preventDefault();
window.messageBoxProxy.show({
className: 'note center-dialog',
contents: el.dataset.title || el.title,
buttons: [t('confirmClose')],
});
}
}
function splitLongTooltips() {
for (const el of $$('[title]')) {
el.dataset.title = el.title;
el.title = el.title.replace(/<\/?\w+>/g, ''); // strip html tags
if (el.title.length < 50) {
continue;
}
const newTitle = el.title
.split('\n')
.map(s => s.replace(/([^.][.。?!]|.{50,60},)\s+/g, '$1\n'))
.map(s => s.replace(/(.{50,80}(?=.{40,}))\s+/g, '$1\n'))
.join('\n');
if (newTitle !== el.title) el.title = newTitle;
}
}
prefs.ready.then(() => {
waitForSelector('details[data-pref]', {recur: Collapsible.bindEvents});
});
window.on('load', () => {
require(lazyScripts);
}, {once: true});
})();
//#endregion

View File

@ -1,37 +0,0 @@
/* exported EventEmitter */
'use strict';
function EventEmitter() {
const listeners = new Map();
return {
on(ev, cb, opt) {
if (!listeners.has(ev)) {
listeners.set(ev, new Map());
}
listeners.get(ev).set(cb, opt);
if (opt && opt.runNow) {
cb();
}
},
off(ev, cb) {
const cbs = listeners.get(ev);
if (cbs) {
cbs.delete(cb);
}
},
emit(ev, ...args) {
const cbs = listeners.get(ev);
if (!cbs) return;
for (const [cb, opt] of cbs.entries()) {
try {
cb(...args);
} catch (err) {
console.error(err);
}
if (opt && opt.once) {
cbs.delete(cb);
}
}
},
};
}

48
js/header-resizer.js Normal file
View File

@ -0,0 +1,48 @@
/* global $ $$ dom */// dom.js
/* global prefs */
'use strict';
(() => {
let curW = $('#header').offsetWidth;
let offset, perPage;
prefs.subscribe(dom.HWprefId, (key, val) => setWidth(val));
$('#header-resizer').onmousedown = e => {
if (e.button) return;
offset = curW - e.clientX;
perPage = e.shiftKey;
document.body.classList.add('resizing-h');
document.on('mousemove', resize);
document.on('mouseup', resizeStop);
};
function resize(e) {
setWidth(offset + e.clientX);
}
function resizeStop() {
document.off('mouseup', resizeStop);
document.off('mousemove', resize);
document.body.classList.remove('resizing-h');
save();
}
function save() {
if (perPage) {
prefs.set(dom.HWprefId, curW);
} else {
for (const k of prefs.knownKeys) {
if (k.startsWith(dom.HW)) prefs.set(k, curW);
}
}
}
function setWidth(w) {
const delta = (w = dom.setHWProp(w)) - curW;
if (delta) {
curW = w;
for (const el of $$('.CodeMirror-linewidget[style*="width:"]')) {
el.style.width = parseFloat(el.style.width) - delta + 'px';
}
}
}
})();

View File

@ -78,12 +78,12 @@
send(data, target = 'extension') {
return browser.runtime.sendMessage({data, target})
.then(unwrapResponse);
.then(unwrapResponseFactory('send'));
},
sendTab(tabId, data, options, target = 'tab') {
return browser.tabs.sendMessage(tabId, {data, target}, options)
.then(unwrapResponse);
.then(unwrapResponseFactory('sendTab'));
},
_execute(types, ...args) {
@ -138,9 +138,16 @@
};
}
function unwrapResponse({data, error} = {error: {message: ERR_NO_RECEIVER}}) {
function unwrapResponseFactory(name) {
// Saving the local callstack before making an async call
return unwrapResponse.bind(null, new Error(`Callstack before invoking msg.${name}:`));
}
function unwrapResponse(localErr, {data, error} = {error: {message: ERR_NO_RECEIVER}}) {
return error
? Promise.reject(Object.assign(new Error(error.message), error))
? Promise.reject(Object.assign(localErr, error, error.stack && {
stack: `${error.stack}\n${localErr.stack}`,
}))
: data;
}

View File

@ -23,14 +23,21 @@
};
const promisify = function (fn, ...args) {
let res;
let resolve, reject;
// Saving the local callstack before making an async call
const err = new Error();
try {
let resolve, reject;
/* Some callbacks have 2 parameters so we're resolving as an array in that case.
For example, chrome.runtime.requestUpdateCheck and chrome.webRequest.onAuthRequired */
args.push((...results) =>
chrome.runtime.lastError ?
reject(new Error(chrome.runtime.lastError.message)) :
resolve(results.length <= 1 ? results[0] : results));
args.push((...results) => {
const {lastError} = chrome.runtime;
if (lastError) {
err.message = lastError.message;
reject(err);
} else {
/* Some callbacks have 2 parameters so we're resolving as an array in that case.
For example, chrome.runtime.requestUpdateCheck and chrome.webRequest.onAuthRequired */
resolve(results.length <= 1 ? results[0] : results);
}
});
fn.apply(this, args);
res = new Promise((...rr) => ([resolve, reject] = rr));
} catch (err) {

View File

@ -123,6 +123,12 @@
'badgeDisabled': '#8B0000', // badge background color when disabled
'badgeNormal': '#006666', // badge background color
/* Using separate values instead of a single {} to ensure type control.
* Sub-key is the first word in the html's file name. */
'headerWidth.edit': 280,
'headerWidth.install': 280,
'headerWidth.manage': 280,
'popupWidth': 246, // popup width in pixels
'updateInterval': 24, // user-style automatic update interval, hours (0 = disable)

View File

@ -320,8 +320,13 @@
<div class="settings-column">
<details id="backup" data-pref="manage.backup.expanded">
<summary><h2 id="backup-title" i18n-text="backupButtons"></h2></summary>
<span id="backup-message" i18n-text="backupMessage"></span>
<summary>
<h2 id="backup-title" i18n-text="backupButtons">
<a tabindex="0" i18n-title="backupMessage" data-cmd="note">
<svg class="svg-icon info"><use xlink:href="#svg-icon-help"/></svg>
</a>
</h2>
</summary>
<div id="backup-buttons">
<button id="file-all-styles" i18n-text="exportLabel"></button>
<button id="unfile-all-styles" i18n-text="importLabel"></button>
@ -349,6 +354,7 @@
</div>
</div>
<div id="header-resizer" i18n-title="headerResizerHint"></div>
</div>
<div id="installed"></div>

View File

@ -15,6 +15,7 @@
'use strict';
$('#file-all-styles').onclick = exportToFile;
$('#file-all-styles').oncontextmenu = exportToFile;
$('#unfile-all-styles').onclick = () => importFromFile({fileTypeFilter: '.json'});
Object.assign(document.body, {
@ -124,7 +125,14 @@ async function importFromString(jsonString) {
if (item && !item.id && item[prefs.STORAGE_KEY]) {
return analyzeStorage(item);
}
if (typeof item !== 'object' || !styleJSONseemsValid(item)) {
if (
!item ||
typeof item !== 'object' || (
isEmptyObj(item.usercssData)
? !styleJSONseemsValid(item)
: typeof item.sourceCode !== 'string'
)
) {
stats.invalid.names.push(`#${index}: ${limitString(item && item.name || '')}`);
return;
}
@ -144,8 +152,8 @@ async function importFromString(jsonString) {
item.id = byName.id;
oldStyle = byName;
}
const metaEqual = oldStyle && deepEqual(oldStyle, item, ['sections', '_rev']);
const codeEqual = oldStyle && styleSectionsEqual(oldStyle, item);
const metaEqual = oldStyle && deepEqual(oldStyle, item, ['sections', 'sourceCode', '_rev']);
const codeEqual = oldStyle && sameCode(oldStyle, item);
if (metaEqual && codeEqual) {
stats.unchanged.names.push(oldStyle.name);
stats.unchanged.ids.push(oldStyle.id);
@ -172,6 +180,14 @@ async function importFromString(jsonString) {
}
}
function sameCode(oldStyle, newStyle) {
const d1 = oldStyle.usercssData;
const d2 = newStyle.usercssData;
return !d1 + !d2
? styleSectionsEqual(oldStyle, newStyle)
: oldStyle.sourceCode === newStyle.sourceCode && deepEqual(d1.vars, d2.vars);
}
function sameStyle(oldStyle, newStyle) {
return oldStyle.name.trim() === newStyle.name.trim() ||
['updateUrl', 'originalMd5', 'originalDigest']
@ -318,13 +334,16 @@ async function importFromString(jsonString) {
}
}
async function exportToFile() {
/** @param {MouseEvent} e */
async function exportToFile(e) {
e.preventDefault();
await require(['/js/storage-util']);
const keepDupSections = e.type === 'contextmenu' || e.shiftKey;
const data = [
Object.assign({
[prefs.STORAGE_KEY]: prefs.values,
}, await chromeSync.getLZValues()),
...await API.styles.getAll(),
...(await API.styles.getAll()).map(cleanupStyle),
];
const text = JSON.stringify(data, null, ' ');
const type = 'application/json';
@ -333,6 +352,20 @@ async function exportToFile() {
download: generateFileName(),
type,
}).dispatchEvent(new MouseEvent('click'));
/** strip `sections`, `null` and empty objects */
function cleanupStyle(style) {
const copy = {};
for (let [key, val] of Object.entries(style)) {
if (key === 'sections'
// Keeping dummy `sections` for compatibility with older Stylus
// even in deduped backup so the user can resave/reconfigure the style to rebuild it.
? !style.usercssData || keepDupSections || (val = [{code: ''}])
: typeof val !== 'object' || !isEmptyObj(val)) {
copy[key] = val;
}
}
return copy;
}
function generateFileName() {
const today = new Date();
const dd = ('0' + today.getDate()).substr(-2);

View File

@ -1,5 +1,4 @@
:root {
--header-width: 280px;
--name-padding-left: 20px;
--name-padding-right: 40px;
--actions-width: 75px;
@ -7,9 +6,9 @@
body {
margin: 0;
/* Firefox: fill the entire page for drag'n'drop to work */
/* Fill the entire viewport to enable json import via drag'n'drop */
display: flex;
height: 100%;
height: 100vh;
}
#header:lang(ja) h1 {
@ -52,7 +51,6 @@ a:hover {
position: fixed;
top: 0;
padding: 1rem;
border-right: 1px dashed #AAA;
box-shadow: 0 0 50px -18px black;
overflow: auto;
box-sizing: border-box;
@ -262,7 +260,6 @@ a:hover {
#backup-buttons {
display: flex;
flex-wrap: wrap;
margin-top: .5rem;
}
#backup-buttons button {
@ -300,10 +297,13 @@ a:hover {
margin-top: .1em;
margin-bottom: .1em;
}
#header summary:hover h2 {
border-color: #bbb;
}
#header summary h2 [data-cmd="note"] {
display: flex;
align-items: center;
}
/* compact layout */

View File

@ -199,10 +199,27 @@
<option value="dropbox">Dropbox</option>
<option value="google">Google Drive</option>
<option value="onedrive">OneDrive</option>
<option value="webdav">WebDAV</option>
</select>
<svg class="svg-icon select-arrow"><use xlink:href="#svg-icon-select-arrow"/></svg>
</div>
</div>
<fieldset class="drive-options">
<div class="webdav-options" data-drive="webdav">
<label class="url">
<span i18n-text="optionsSyncUrl"></span>
<input type="text" data-option="url">
</label>
<label class="username">
<span i18n-text="optionsSyncUsername"></span>
<input type="text" data-option="username">
</label>
<label class="password">
<span i18n-text="optionsSyncPassword"></span>
<input type="password" data-option="password">
</label>
</div>
</fieldset>
<div class="actions">
<button type="button" class="connect" i18n-text="optionsSyncConnect"></button>
<button type="button" class="disconnect" i18n-text="optionsSyncDisconnect"></button>

View File

@ -163,7 +163,7 @@ label > :first-child {
}
label:not([disabled]),
label:not([disabled]) :not([type="number"]) {
label:not([disabled]) :not([type="number"]):not([type="text"]):not([type="password"]) {
cursor: pointer;
}
@ -437,7 +437,25 @@ input[type="radio"].radio:checked::after {
.sync-status::first-letter {
text-transform: uppercase;
}
.sync-options .drive-options {
margin: 0;
padding: 0;
border: 0;
}
.drive-options > :not([hidden]) {
display: table;
width: 100%;
}
.drive-options > * > label {
display: table-row;
}
.drive-options > * > label > * {
display: table-cell;
}
.drive-options > * input {
width: 100%;
box-sizing: border-box;
}
.sync-options .actions button {
margin-top: .5em;
}

View File

@ -96,6 +96,7 @@ document.onclick = e => {
const elSyncNow = $('.sync-options .sync-now');
const elStatus = $('.sync-options .sync-status');
const elLogin = $('.sync-options .sync-login');
const elDriveOptions = $('.sync-options .drive-options');
/** @type {Sync.Status} */
let status = {};
msg.onExtension(e => {
@ -108,7 +109,10 @@ document.onclick = e => {
elCloud.on('change', updateButtons);
for (const [btn, fn] of [
[elStart, () => API.sync.start(elCloud.value)],
[elStart, async () => {
await API.sync.setDriveOptions(elCloud.value, getDriveOptions());
await API.sync.start(elCloud.value);
}],
[elStop, API.sync.stop],
[elSyncNow, API.sync.syncNow],
[elLogin, async () => {
@ -123,12 +127,26 @@ document.onclick = e => {
});
}
function getDriveOptions() {
const result = {};
for (const el of $$(`[data-drive=${elCloud.value}] [data-option]`)) {
result[el.dataset.option] = el.value;
}
return result;
}
function setDriveOptions(options) {
for (const el of $$(`[data-drive=${elCloud.value}] [data-option]`)) {
el.value = options[el.dataset.option] || '';
}
}
function setStatus(newStatus) {
status = newStatus;
updateButtons();
}
function updateButtons() {
async function updateButtons() {
const {state, STATES} = status;
const isConnected = state === STATES.connected;
const isDisconnected = state === STATES.disconnected;
@ -137,6 +155,7 @@ document.onclick = e => {
}
for (const [el, enable] of [
[elCloud, isDisconnected],
[elDriveOptions, isDisconnected],
[elStart, isDisconnected && elCloud.value !== 'none'],
[elStop, isConnected && !status.syncing],
[elSyncNow, isConnected && !status.syncing && status.login],
@ -145,6 +164,10 @@ document.onclick = e => {
}
elStatus.textContent = getStatusText();
elLogin.hidden = !isConnected || status.login;
for (const el of elDriveOptions.children) {
el.hidden = el.dataset.drive !== elCloud.value;
}
setDriveOptions(await API.sync.getDriveOptions(elCloud.value));
}
function getStatusText() {