Merge branch 'master' of https://github.com/openstyles/stylus into dev-exclusions

This commit is contained in:
eight 2018-10-03 15:00:07 +08:00
commit 24b1eea8a4
122 changed files with 20906 additions and 22099 deletions

View File

@ -21,7 +21,7 @@ If not, then provide details describing which page the feature will effect, e.g.
## Adding translations ## Adding translations
You can help us translate the extension on [Transifex](https://www.transifex.com/github-7/Stylus). When `messages.json` file is ready to be merged, please open a new bug report in [stylus/issues](https://github.com/openstyles/stylus/issues). You can help us translate the extension on [Transifex](https://www.transifex.com/github-7/Stylus).
## Pull requests ## Pull requests

1
.gitignore vendored
View File

@ -5,3 +5,4 @@ node_modules/
package-lock.json package-lock.json
yarn.lock yarn.lock
*.zip *.zip
.eslintcache

View File

@ -39,7 +39,7 @@ Stylus is a fork of Stylish for Chrome, also compatible with Firefox as a WebExt
The source is hosted on [GitHub](https://github.com/openstyles/stylus) and pull requests are welcome. The source is hosted on [GitHub](https://github.com/openstyles/stylus) and pull requests are welcome.
You can help us translate the extension on [Transifex](https://www.transifex.com/github-7/Stylus). When `messages.json` file is ready to be merged, please open a new bug report in [stylus/issues](https://github.com/openstyles/stylus/issues). You can help us translate the extension on [Transifex](https://www.transifex.com/github-7/Stylus).
See our [contributing](./.github/CONTRIBUTING.md) page for more details. See our [contributing](./.github/CONTRIBUTING.md) page for more details.

View File

@ -1,210 +1,163 @@
{ {
"appliesToEverything": { "addStyleLabel": {
"message": "كل شيء", "message": "كتابة نمط جديد"
"description": "Text displayed for styles that apply to all sites" },
}, "addStyleTitle": {
"enableStyleLabel": { "message": "إضافة نمط"
"message": "تمكين", },
"description": "Label for the button to enable a style" "appliesAdd": {
}, "message": "إضافة"
"styleMissingName": { },
"message": "أدخل اسمًا", "appliesDisplay": {
"description": "Error displayed when user saves without providing a name" "message": "ينطبق على: $applies$",
}, "placeholders": {
"appliesDomainOption": { "applies": {
"message": "عناوين URL في النطاق", "content": "$1"
"description": "Option to make the style apply to the entered string as a domain" }
}, }
"checkForUpdate": { },
"message": "البحث عن تحديث", "appliesDisplayTruncatedSuffix": {
"description": "Label for the button to check a single style for an update" "message": "والمزيد"
}, },
"helpAlt": { "appliesDomainOption": {
"message": "مساعدة", "message": "عناوين URL في النطاق"
"description": "Alternate text for help buttons" },
}, "appliesHelp": {
"findStylesForSite": { "message": "استخدم عناصر تحكم 'ينطبق على' لتقييد عناوين URL التي ينطبق عليها الرمز في هذا القسم."
"message": "البحث عن المزيد من الأنماط لموقع الويب هذا", },
"description": "Text for a link that gets a list of styles for the current site" "appliesLabel": {
}, "message": "ينطبق على"
"manageHeading": { },
"message": "الأنماط المثبتة", "appliesRegexpOption": {
"description": "Heading for the manage page" "message": "عناوين URL التي تطابق regexp"
}, },
"styleEnabledLabel": { "appliesRemove": {
"message": "ممكّن", "message": "إزالة"
"description": "Label for the enabled state of styles" },
}, "appliesSpecify": {
"styleToMozillaFormatHelp": { "message": "تحديد"
"message": "يمكن استخدام تنسيق موزيلا للرمز باستخدام Stylus للمتصفح فايرفوكس ويمكن إرساله إلى userstyles.org.", },
"description": "Help info for the Mozilla format header section that converts the code to/from Mozilla format" "appliesToEverything": {
}, "message": "كل شيء"
"sectionAdd": { },
"message": "إضافة قسم آخر", "appliesUrlOption": {
"description": "Label for the button to add a section" "message": "عنوان URL"
}, },
"styleSaveLabel": { "appliesUrlPrefixOption": {
"message": "حفظ", "message": "عناوين URL البادئة بـ"
"description": "Label for save button for style editing" },
}, "checkAllUpdates": {
"appliesAdd": { "message": "البحث عن تحديثات لكل الأنماط"
"message": "إضافة", },
"description": "Label for the button to add an 'applies' entry" "checkForUpdate": {
}, "message": "البحث عن تحديث"
"appliesRegexpOption": { },
"message": "عناوين URL التي تطابق regexp", "checkingForUpdate": {
"description": "Option to make the style apply to the entered string as a regular expression" "message": "جارٍ البحث..."
}, },
"styleInstall": { "deleteStyleConfirm": {
"message": "هل تريد تثبيت '$stylename$' في Stylus؟", "message": "هل تريد بالتأكيد حذف هذا النمط؟"
"description": "Confirmation when installing a style", },
"placeholders": { "deleteStyleLabel": {
"stylename": { "message": "حذف"
"content": "$1" },
} "description": {
} "message": "يمكنك تغيير نمط الويب باستخدام Stylus، وهي أداة لإدارة أنماط المستخدم. وتتيح Stylus لك بسهولة تثبيت المظاهر والأشكال الخارجية لكل من Google، وFacebook وYouTube وOrkut فضلاً عن الكثير جدًا من مواقع الويب الأخرى."
}, },
"disableStyleLabel": { "disableStyleLabel": {
"message": "تعطيل", "message": "تعطيل"
"description": "Label for the button to disable a style" },
}, "editStyleHeading": {
"styleCancelEditLabel": { "message": "تعديل النمط"
"message": "رجوع للإدارة", },
"description": "Label for cancel button for style editing" "editStyleLabel": {
}, "message": "تعديل"
"styleChangesNotSaved": { },
"message": "لقد أجريت تغييرات على هذا النمط بدون حفظها.", "editStyleTitle": {
"description": "Text for the prompt when changes are made to a style and the user tries to leave without saving" "message": "تعديل النمط $stylename$",
}, "placeholders": {
"updateCheckFailServerUnreachable": { "stylename": {
"message": "أخفق التحديث - الخادم يتعذر الوصول إليه.", "content": "$1"
"description": "Text that displays when an update check failed because the update server is unreachable" }
}, }
"deleteStyleConfirm": { },
"message": "هل تريد بالتأكيد حذف هذا النمط؟", "enableStyleLabel": {
"description": "Confirmation before deleting a style" "message": "تمكين"
}, },
"appliesDisplay": { "findStylesForSite": {
"message": "ينطبق على: $applies$", "message": "البحث عن المزيد من الأنماط لموقع الويب هذا"
"description": "Text on the manage screen to describe what the style applies to", },
"placeholders": { "helpAlt": {
"applies": { "message": "مساعدة"
"content": "$1" },
} "installUpdate": {
} "message": "تثبيت التحديث"
}, },
"styleSectionsTitle": { "manageHeading": {
"message": "الأقسام", "message": "الأنماط المثبتة"
"description": "Title for the style sections section" },
}, "noStylesForSite": {
"editStyleTitle": { "message": "لم يتم تثبيت أي أنماط لموقع الويب هذا."
"message": "تعديل النمط $stylename$", },
"description": "Title of the page for editing styles", "openManage": {
"placeholders": { "message": "إدارة الأنماط المثبتة"
"stylename": { },
"content": "$1" "sectionAdd": {
} "message": "إضافة قسم آخر"
} },
}, "sectionCode": {
"updateCheckSucceededNoUpdate": { "message": "الرمز"
"message": "النمط محدّث.", },
"description": "Text that displays when an update check completed and no update is available" "sectionHelp": {
}, "message": "تتيح لك الأقسام تحديد أجزاء مختلفة من الرمز لتطبيقها على مجموعات مختلفة من عناوين URL بالنمط نفسه. فعلى سبيل المثال، يمكن لنمط مفرد تغيير الصفحة الرئيسية لموقع ويب بطريقة، مع تغيير بقية أجزاء موقع الويب بطريقة أخرى."
"appliesUrlPrefixOption": { },
"message": "عناوين URL البادئة بـ", "sectionRemove": {
"description": "Option to make the style apply to the entered string as a URL prefix" "message": "إزالة القسم"
}, },
"sectionHelp": { "styleCancelEditLabel": {
"message": "تتيح لك الأقسام تحديد أجزاء مختلفة من الرمز لتطبيقها على مجموعات مختلفة من عناوين URL بالنمط نفسه. فعلى سبيل المثال، يمكن لنمط مفرد تغيير الصفحة الرئيسية لموقع ويب بطريقة، مع تغيير بقية أجزاء موقع الويب بطريقة أخرى.", "message": "رجوع للإدارة"
"description": "Help text for sections" },
}, "styleChangesNotSaved": {
"noStylesForSite": { "message": "لقد أجريت تغييرات على هذا النمط بدون حفظها."
"message": "لم يتم تثبيت أي أنماط لموقع الويب هذا.", },
"description": "Text displayed when no styles are installed for the current site" "styleEnabledLabel": {
}, "message": "ممكّن"
"appliesDisplayTruncatedSuffix": { },
"message": "والمزيد", "styleInstall": {
"description": "Text added to appliesDisplay when there are more sites for the style than are displayed" "message": "هل تريد تثبيت '$stylename$' في Stylus؟",
}, "placeholders": {
"appliesRemove": { "stylename": {
"message": "إزالة", "content": "$1"
"description": "Label for the button to remove an 'applies' entry" }
}, }
"appliesLabel": { },
"message": "ينطبق على", "styleMissingName": {
"description": "Label for 'applies to' fields on the edit/add screen" "message": "أدخل اسمًا"
}, },
"openManage": { "styleSaveLabel": {
"message": "إدارة الأنماط المثبتة", "message": "حفظ"
"description": "Link to open the manage page." },
}, "styleSectionsTitle": {
"updateCheckFailBadResponseCode": { "message": "الأقسام"
"message": "أخفق التحديث - استجاب الخادم بالرمز $code$", },
"description": "Text that displays when an update check failed because the response code indicates an error", "styleToMozillaFormatHelp": {
"placeholders": { "message": "يمكن استخدام تنسيق موزيلا للرمز باستخدام Stylus للمتصفح فايرفوكس ويمكن إرساله إلى userstyles.org."
"code": { },
"content": "$1" "updateCheckFailBadResponseCode": {
} "message": "أخفق التحديث - استجاب الخادم بالرمز $code$",
} "placeholders": {
}, "code": {
"appliesSpecify": { "content": "$1"
"message": "تحديد", }
"description": "Label for the button to make a style apply only to specific sites" }
}, },
"installUpdate": { "updateCheckFailServerUnreachable": {
"message": "تثبيت التحديث", "message": "أخفق التحديث - الخادم يتعذر الوصول إليه."
"description": "Label for the button to install an update for a single style" },
}, "updateCheckSucceededNoUpdate": {
"sectionRemove": { "message": "النمط محدّث."
"message": "إزالة القسم", },
"description": "Label for the button to remove a section" "updateCompleted": {
}, "message": "اكتمل التحديث."
"updateCompleted": { }
"message": "اكتمل التحديث.",
"description": "Text that displays when an update completed"
},
"checkingForUpdate": {
"message": "جارٍ البحث...",
"description": "Text to display when checking a style for an update"
},
"sectionCode": {
"message": "الرمز",
"description": "Label for the code for a section"
},
"appliesHelp": {
"message": "استخدم عناصر تحكم 'ينطبق على' لتقييد عناوين URL التي ينطبق عليها الرمز في هذا القسم.",
"description": "Help text for 'applies to' section"
},
"editStyleHeading": {
"message": "تعديل النمط",
"description": "Title of the page for editing styles"
},
"appliesUrlOption": {
"message": "عنوان URL",
"description": "Option to make the style apply to the entered string as a URL"
},
"addStyleTitle": {
"message": "إضافة نمط",
"description": "Title of the page for adding styles"
},
"checkAllUpdates": {
"message": "البحث عن تحديثات لكل الأنماط",
"description": "Label for the button to check all styles for updates"
},
"deleteStyleLabel": {
"message": "حذف",
"description": "Label for the button to delete a style"
},
"addStyleLabel": {
"message": "كتابة نمط جديد",
"description": "Label for the button to go to the add style page"
},
"editStyleLabel": {
"message": "تعديل",
"description": "Label for the button to go to the edit style page"
},
"description": {
"message": "يمكنك تغيير نمط الويب باستخدام Stylus، وهي أداة لإدارة أنماط المستخدم. وتتيح Stylus لك بسهولة تثبيت المظاهر والأشكال الخارجية لكل من Google، وFacebook وYouTube وOrkut فضلاً عن الكثير جدًا من مواقع الويب الأخرى.",
"description": "Extension description"
}
} }

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,287 @@
{
"addStyleLabel": {
"message": "Напиши нов стил"
},
"addStyleTitle": {
"message": "Добави стил"
},
"appliesAdd": {
"message": "Добави"
},
"appliesDisplay": {
"message": "Прилага се към: $applies$",
"placeholders": {
"applies": {
"content": "$1"
}
}
},
"appliesDisplayTruncatedSuffix": {
"message": "и още"
},
"appliesDomainOption": {
"message": "URLи на домейна"
},
"appliesHelp": {
"message": "Използвайте \"Прилага се към\", за да ограничете адресите, за които ще работи кодът в тази секция."
},
"appliesLabel": {
"message": "Прилага се към"
},
"appliesRegexpOption": {
"message": "Адреси, съвпадащи с regexp"
},
"appliesRemove": {
"message": "Премахни"
},
"appliesSpecify": {
"message": "Уточни"
},
"appliesToEverything": {
"message": "Всички"
},
"appliesUrlPrefixOption": {
"message": "URL започващи с"
},
"applyAllUpdates": {
"message": "Приложи всички промени"
},
"checkAllUpdates": {
"message": "Провери всички стилове за обновления"
},
"checkForUpdate": {
"message": "Провери за обновление"
},
"checkingForUpdate": {
"message": "Проверявам..."
},
"cm_indentWithTabs": {
"message": "Използвай табулация с умно отместване"
},
"cm_keyMap": {
"message": "Клавишни комбинации"
},
"cm_lineWrapping": {
"message": "Автоматично пренасяне"
},
"cm_smartIndent": {
"message": "Използвай умно отместване"
},
"cm_tabSize": {
"message": "Размер на табулацията"
},
"cm_theme": {
"message": "Тема"
},
"confirmNo": {
"message": "Не"
},
"confirmStop": {
"message": "Спри"
},
"confirmYes": {
"message": "Да"
},
"dbError": {
"message": "Грешка в базата данни на Stylus. Желаеш ли да посетиш уебстраницата с възможни решения?"
},
"defaultTheme": {
"message": "по подразбиране"
},
"deleteStyleConfirm": {
"message": "Наистина ли искаш да изтриеш този стил?"
},
"deleteStyleLabel": {
"message": "Изтрий"
},
"description": {
"message": "Промени уеба със Stylus, мениджър на потребителски стилове. Stylus ти позволява лесно да инсталираш теми и скинове за много популярни сайтове."
},
"disableAllStyles": {
"message": "Изключи всички стилове"
},
"disableStyleLabel": {
"message": "Забрани"
},
"editGotoLine": {
"message": "Иди на ред (или ред:кол)"
},
"editStyleHeading": {
"message": "Промени стила"
},
"editStyleLabel": {
"message": "Редактирай"
},
"editStyleTitle": {
"message": "Редактирай стил $stylename$",
"placeholders": {
"stylename": {
"content": "$1"
}
}
},
"enableStyleLabel": {
"message": "Разреши"
},
"exportLabel": {
"message": "Експорт"
},
"helpAlt": {
"message": "Помощ"
},
"helpKeyMapCommand": {
"message": "Напиши име на команда"
},
"helpKeyMapHotkey": {
"message": "Натисни клавишна комбинация"
},
"importAppendLabel": {
"message": "Добави към стил"
},
"importAppendTooltip": {
"message": "Добави импортирания стил към текущия"
},
"importLabel": {
"message": "Импорт"
},
"importReplaceLabel": {
"message": "Презапиши стила"
},
"importReplaceTooltip": {
"message": "Презапишете съдържанието на текущия стил с импортирания"
},
"installButton": {
"message": "Инсталирай стил"
},
"installButtonInstalled": {
"message": "Стилът е инсталиран"
},
"installButtonReinstall": {
"message": "Преинсталирай стила"
},
"installButtonUpdate": {
"message": "Обнови стила"
},
"installUpdate": {
"message": "Инсталирай обновление"
},
"installUpdateFrom": {
"message": "В момента стилът се обновява от $url$",
"placeholders": {
"url": {
"content": "$1"
}
}
},
"installUpdateFromLabel": {
"message": "Провери за обновления"
},
"installUpdateUnavailable": {
"message": "За да разрешите проверка за обновления, пуснете файла върху лентата с табове, или в метаданните на стила укажете @updateURL."
},
"license": {
"message": "Лиценз"
},
"linkGetHelp": {
"message": "Получете помощ"
},
"linkGetStyles": {
"message": "Вземете стилове"
},
"linkTranslate": {
"message": "Преведете"
},
"linterCSSLintIncompatible": {
"message": "CSSLint не поддържа $preprocessorname$ preprocessor",
"placeholders": {
"preprocessorname": {
"content": "$1"
}
}
},
"linterCSSLintSettings": {
"message": "(Укажете правилата: 0 = забранен; 1 = предупреждения; 2 = грешки)"
},
"linterConfigPopupTitle": {
"message": "Настройте конфигурация за $linter$ правила",
"placeholders": {
"linter": {
"content": "$1"
}
}
},
"linterConfigTooltip": {
"message": "Щракнете, за да конфигурирате този linter"
},
"linterInvalidConfigError": {
"message": "Не е записано заради тези неправилни настройки"
},
"linterIssues": {
"message": "Проблеми"
},
"linterIssuesHelp": {
"message": "Тези проблеми бяха намерени от $link$:",
"placeholders": {
"link": {
"content": "$1"
}
}
},
"linterJSONError": {
"message": "Невалиден JSON формат"
},
"linterResetMessage": {
"message": "За да върнете погрешно нулиране, натиснете Ctrl-Z (или Cmd-Z) в текстовия прозорец"
},
"linterRulesLink": {
"message": "Вижте пълния списък с правила"
},
"liveReloadError": {
"message": "Получи се грешка докато наблюдавахме файла"
},
"liveReloadInstallHint": {
"message": "Преглед на живо е разрешен, така че инсталирания стил ще бъде обновен автоматично при външни промени докато двата прозореца с кода и оригинала са отворени."
},
"liveReloadLabel": {
"message": "Преглед на живо"
},
"liveReloadUnavailable": {
"message": "За да разрешите презареждане в реално време, пуснете файла върху лентата с табове (областта, където са показани заглавията на табовете)."
},
"manageFilters": {
"message": "Филтри"
},
"manageHeading": {
"message": "Инсталирани стилове"
},
"manageNewStyleAsUsercss": {
"message": "като Потребителскиcss"
},
"manageNewUI": {
"message": "Нова подредба на UI"
},
"manageOnlyDisabled": {
"message": "Само забранените стилове"
},
"manageOnlyEnabled": {
"message": "Само разрешените стилове"
},
"manageOnlyExternal": {
"message": "Само външните стилове"
},
"manageOnlyLocal": {
"message": "Само локалните стилове"
},
"manageOnlyLocalTooltip": {
"message": "(стиловете не инсталирани чрез страницата на userstyles.org)"
},
"manageOnlyNonUsercss": {
"message": "Само не-Потребителскитеcss стилове"
},
"manageOnlyUpdates": {
"message": "Само с обновления или проблеми"
},
"manageOnlyUsercss": {
"message": "Само Потребителскиcss стилове"
}
}

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -1,295 +1,228 @@
{ {
"appliesToEverything": { "addStyleLabel": {
"message": "Τα πάντα", "message": "Γράψτε νέο στυλ"
"description": "Text displayed for styles that apply to all sites" },
}, "addStyleTitle": {
"cm_tabSize": { "message": "Προσθήκη στυλ"
"message": "Μέγεθος καρτέλας", },
"description": "Label for the text box controlling tab size option for the style editor." "appliesAdd": {
}, "message": "Προσθήκη"
"enableStyleLabel": { },
"message": "Ενεργοποίηση", "appliesDisplay": {
"description": "Label for the button to enable a style" "message": "Ισχύει για: $applies$",
}, "placeholders": {
"styleMissingName": { "applies": {
"message": "Εισάγετε ένα όνομα", "content": "$1"
"description": "Error displayed when user saves without providing a name" }
}, }
"appliesDomainOption": { },
"message": "URL στον τομέα", "appliesDisplayTruncatedSuffix": {
"description": "Option to make the style apply to the entered string as a domain" "message": "και πολλά άλλα"
}, },
"checkForUpdate": { "appliesDomainOption": {
"message": "Έλεγχος για ενημερώσεις", "message": "URL στον τομέα"
"description": "Label for the button to check a single style for an update" },
}, "appliesHelp": {
"updateAllCheckSucceededNoUpdate": { "message": "Χρησιμοποιήστε το \"Ισχύει για\" έλεγχοι ώστε να περιοριστουν ποιες διευθύνσεις τον κώδικα σε αυτό το τμήμα να εφαρμόζονται."
"message": "Όλα τα στυλ είναι ενημερωμένα.", },
"description": "Text that displays when an update all check completed and no updates are available" "appliesLabel": {
}, "message": "Ισχύει για"
"helpAlt": { },
"message": "Βοήθεια", "appliesRegexpOption": {
"description": "Alternate text for help buttons" "message": "Διευθύνσεις URL που ταιριάζουν με την κανονική έκφραση"
}, },
"findStylesForSite": { "appliesRemove": {
"message": "Αναζήτηση περισσότερων στυλ για αυτή την ιστοσελίδα", "message": "Αφαίρεση"
"description": "Text for a link that gets a list of styles for the current site" },
}, "appliesSpecify": {
"manageHeading": { "message": "Καθορισμός"
"message": "Εγκατεστημένα Στυλ", },
"description": "Heading for the manage page" "appliesToEverything": {
}, "message": "Τα πάντα"
"styleEnabledLabel": { },
"message": "Ενεργοποιημένη", "appliesUrlPrefixOption": {
"description": "Label for the enabled state of styles" "message": "Διευθύνσεις URL που αρχίζουν με"
}, },
"styleToMozillaFormatHelp": { "applyAllUpdates": {
"message": "Η μορφή του Mozilla κώδικα μπορεί να χρησιμοποιηθεί με το Stylish για το Firefox και μπορεί να υποβληθεί στο userstyles.org.", "message": "Εφαρμογή όλων των ενημερώσεων"
"description": "Help info for the Mozilla format header section that converts the code to/from Mozilla format" },
}, "checkAllUpdates": {
"sectionAdd": { "message": "Έλεγχος όλων των στυλ για ενημερώσεις"
"message": "Προσθήκη ένος άλλου τμήματος", },
"description": "Label for the button to add a section" "checkForUpdate": {
}, "message": "Έλεγχος για ενημερώσεις"
"styleSaveLabel": { },
"message": "Αποθήκευση", "checkingForUpdate": {
"description": "Label for save button for style editing" "message": "Έλεγχος..."
}, },
"writeStyleForURL": { "cm_indentWithTabs": {
"message": "αυτή την διεύθυνση URL", "message": "Χρήση καρτελών με έξυπνη εσοχή"
"description": "Text for link in toolbar pop-up to write a new style for the current URL" },
}, "cm_lineWrapping": {
"appliesAdd": { "message": "Αναδίπλωση λέξεων"
"message": "Προσθήκη", },
"description": "Label for the button to add an 'applies' entry" "cm_smartIndent": {
}, "message": "Χρήση έξυπνης εσοχής"
"appliesRegexpOption": { },
"message": "Διευθύνσεις URL που ταιριάζουν με την κανονική έκφραση", "cm_tabSize": {
"description": "Option to make the style apply to the entered string as a regular expression" "message": "Μέγεθος καρτέλας"
}, },
"styleInstall": { "dbError": {
"message": "Εγκατάσταση του '$stylename$' στο Stylus;", "message": "Παρουσιάστηκε σφάλμα χρησιμοποιώντας την κομψή βάση δεδομένων. Θα θέλατε να επισκεφθείτε μια ιστοσελίδα με πιθανές λύσεις;"
"description": "Confirmation when installing a style", },
"placeholders": { "deleteStyleConfirm": {
"stylename": { "message": "Είστε σίγουροι ότι θέλετε να διαγράψετε αυτό το στυλ;"
"content": "$1" },
} "deleteStyleLabel": {
} "message": "Διαγραφή"
}, },
"disableStyleLabel": { "description": {
"message": "Απενεργοποίηση", "message": "Επαναπροσδιορίση του διαδίκτυου με το Stylus, έναν διαχειριστή στυλ. Το Stylus σας επιτρέπει να εγκαταστήσετε εύκολα themes και skins για πολλές δημοφιλείς ιστοσελίδες."
"description": "Label for the button to disable a style" },
}, "disableAllStyles": {
"prefShowBadge": { "message": "Απενεργοποιηση ολων των στυλ"
"message": "Εμφάνιση αριθμού των στυλ που δραστηριοποιούνται για την τρέχουσα τοποθεσία στην μπάρα εργαλείων", },
"description": "Label for the checkbox controlling toolbar badge text." "disableStyleLabel": {
}, "message": "Απενεργοποίηση"
"menuShowBadge": { },
"message": "Εμφάνιση ενεργους καταμέτρησης στυλ", "editGotoLine": {
"description": "Label (must be very short) for the checkbox in the toolbar button context menu controlling toolbar badge text." "message": "Μετάβαση στη γραμμή (ή line:col)"
}, },
"cm_lineWrapping": { "editStyleHeading": {
"message": "Αναδίπλωση λέξεων", "message": "Επεξεργασία Στυλ"
"description": "Label for the checkbox controlling word wrap option for the style editor." },
}, "editStyleLabel": {
"styleCancelEditLabel": { "message": "Επεξεργασία"
"message": "Πίσω στη διαχείριση", },
"description": "Label for cancel button for style editing" "editStyleTitle": {
}, "message": "Επεξεργασία του στυλ $stylename$",
"styleChangesNotSaved": { "placeholders": {
"message": "Έχετε κάνει αλλαγές σε αυτό το ύφος χωρίς αποθήκευση.", "stylename": {
"description": "Text for the prompt when changes are made to a style and the user tries to leave without saving" "content": "$1"
}, }
"updateCheckFailServerUnreachable": { }
"message": "Αποτυχία ενημέρωσης: απρόσιτος διακομιστής.", },
"description": "Text that displays when an update check failed because the update server is unreachable" "enableStyleLabel": {
}, "message": "Ενεργοποίηση"
"manageFilters": { },
"message": "Φίλτρα", "findStylesForSite": {
"description": "Label for filters container" "message": "Αναζήτηση περισσότερων στυλ για αυτή την ιστοσελίδα"
}, },
"applyAllUpdates": { "helpAlt": {
"message": "Εφαρμογή όλων των ενημερώσεων", "message": "Βοήθεια"
"description": "Label for the button to apply all detected updates" },
}, "installUpdate": {
"deleteStyleConfirm": { "message": "Εγκατάσταση ενημέρωσης"
"message": "Είστε σίγουροι ότι θέλετε να διαγράψετε αυτό το στυλ;", },
"description": "Confirmation before deleting a style" "manageFilters": {
}, "message": "Φίλτρα"
"styleBadRegexp": { },
"message": "Το Regexp δεν είναι έγκυρο.", "manageHeading": {
"description": "Validation message for a bad regexp in a style" "message": "Εγκατεστημένα Στυλ"
}, },
"optionsHeading": { "manageOnlyEnabled": {
"message": "Επιλογές", "message": "Μόνο ενεργοποιημένα στυλ"
"description": "Heading for options section on manage page." },
}, "manageTitle": {
"appliesDisplay": { "message": "Κομψή"
"message": "Ισχύει για: $applies$", },
"description": "Text on the manage screen to describe what the style applies to", "menuShowBadge": {
"placeholders": { "message": "Εμφάνιση ενεργους καταμέτρησης στυλ"
"applies": { },
"content": "$1" "noStylesForSite": {
} "message": "Δεν υπάρχουν εγκατεστημένα στυλ για αυτή την ιστοσελίδα."
} },
}, "openManage": {
"styleUpdate": { "message": "Διαχείριση εγκατεστημένων στυλ"
"message": "Είστε σίγουροι ότι θέλετε να ενημερώσετε το '$stylename$';", },
"description": "Confirmation when updating a style", "optionsHeading": {
"placeholders": { "message": "Επιλογές"
"stylename": { },
"content": "$1" "popupStylesFirst": {
} "message": "Στυλ λίστας πριν των εντολών στο μενού του κουμπιού γραμμής εργαλείων"
} },
}, "prefShowBadge": {
"styleSectionsTitle": { "message": "Εμφάνιση αριθμού των στυλ που δραστηριοποιούνται για την τρέχουσα τοποθεσία στην μπάρα εργαλείων"
"message": "Ενότητες", },
"description": "Title for the style sections section" "sectionAdd": {
}, "message": "Προσθήκη ένος άλλου τμήματος"
"editStyleTitle": { },
"message": "Επεξεργασία του στυλ $stylename$", "sectionCode": {
"description": "Title of the page for editing styles", "message": "Κώδικας"
"placeholders": { },
"stylename": { "sectionHelp": {
"content": "$1" "message": "Ενότητες σας επιτρέπουν να ορίσετε διαφορετικά κομμάτια του κώδικα για να εφαρμόζονται σε διαφορετικά σύνολα των διευθύνσεων URL στο ίδιο στυλ. Για παράδειγμα, ένα ενιαίο ύφος θα μπορούσε να αλλάξει την αρχική σελίδα ενός ιστότοπου με έναν τρόπο, ενώ αλλάζει το υπόλοιπο μιας τοποθεσίας ένας άλλος τρόπος."
} },
} "sectionRemove": {
}, "message": "Αφαίρεση ενότητας"
"updateCheckSucceededNoUpdate": { },
"message": "Το στυλ είναι ενημερωμένο.", "styleBadRegexp": {
"description": "Text that displays when an update check completed and no update is available" "message": "Το Regexp δεν είναι έγκυρο."
}, },
"appliesUrlPrefixOption": { "styleCancelEditLabel": {
"message": "Διευθύνσεις URL που αρχίζουν με", "message": "Πίσω στη διαχείριση"
"description": "Option to make the style apply to the entered string as a URL prefix" },
}, "styleChangesNotSaved": {
"popupStylesFirst": { "message": "Έχετε κάνει αλλαγές σε αυτό το ύφος χωρίς αποθήκευση."
"message": "Στυλ λίστας πριν των εντολών στο μενού του κουμπιού γραμμής εργαλείων", },
"description": "Label for the checkbox controlling section order in the popup." "styleEnabledLabel": {
}, "message": "Ενεργοποιημένη"
"sectionHelp": { },
"message": "Ενότητες σας επιτρέπουν να ορίσετε διαφορετικά κομμάτια του κώδικα για να εφαρμόζονται σε διαφορετικά σύνολα των διευθύνσεων URL στο ίδιο στυλ. Για παράδειγμα, ένα ενιαίο ύφος θα μπορούσε να αλλάξει την αρχική σελίδα ενός ιστότοπου με έναν τρόπο, ενώ αλλάζει το υπόλοιπο μιας τοποθεσίας ένας άλλος τρόπος.", "styleInstall": {
"description": "Help text for sections" "message": "Εγκατάσταση του '$stylename$' στο Stylus;",
}, "placeholders": {
"noStylesForSite": { "stylename": {
"message": "Δεν υπάρχουν εγκατεστημένα στυλ για αυτή την ιστοσελίδα.", "content": "$1"
"description": "Text displayed when no styles are installed for the current site" }
}, }
"appliesDisplayTruncatedSuffix": { },
"message": "και πολλά άλλα", "styleMissingName": {
"description": "Text added to appliesDisplay when there are more sites for the style than are displayed" "message": "Εισάγετε ένα όνομα"
}, },
"appliesRemove": { "styleSaveLabel": {
"message": "Αφαίρεση", "message": "Αποθήκευση"
"description": "Label for the button to remove an 'applies' entry" },
}, "styleSectionsTitle": {
"manageTitle": { "message": "Ενότητες"
"message": "Κομψή", },
"description": "Title for the manage page" "styleToMozillaFormatHelp": {
}, "message": "Η μορφή του Mozilla κώδικα μπορεί να χρησιμοποιηθεί με το Stylish για το Firefox και μπορεί να υποβληθεί στο userstyles.org."
"writeStyleFor": { },
"message": "Γράψτε νέο στυλ για:", "styleUpdate": {
"description": "Label for toolbar pop-up that precedes the links to write a new style" "message": "Είστε σίγουροι ότι θέλετε να ενημερώσετε το '$stylename$';",
}, "placeholders": {
"appliesLabel": { "stylename": {
"message": "Ισχύει για", "content": "$1"
"description": "Label for 'applies to' fields on the edit/add screen" }
}, }
"openManage": { },
"message": "Διαχείριση εγκατεστημένων στυλ", "stylusUnavailableForURL": {
"description": "Link to open the manage page." "message": "To Stylus δεν λειτουργεί σε σελίδες όπως αυτή."
}, },
"updateCheckFailBadResponseCode": { "updateAllCheckSucceededNoUpdate": {
"message": "Αποτυχία ενημέρωσης: ο διακομιστής ανταποκρίθηκε με κωδικό $code$.", "message": "Όλα τα στυλ είναι ενημερωμένα."
"description": "Text that displays when an update check failed because the response code indicates an error", },
"placeholders": { "updateCheckFailBadResponseCode": {
"code": { "message": "Αποτυχία ενημέρωσης: ο διακομιστής ανταποκρίθηκε με κωδικό $code$.",
"content": "$1" "placeholders": {
} "code": {
} "content": "$1"
}, }
"appliesSpecify": { }
"message": "Καθορισμός", },
"description": "Label for the button to make a style apply only to specific sites" "updateCheckFailServerUnreachable": {
}, "message": "Αποτυχία ενημέρωσης: απρόσιτος διακομιστής."
"installUpdate": { },
"message": "Εγκατάσταση ενημέρωσης", "updateCheckSucceededNoUpdate": {
"description": "Label for the button to install an update for a single style" "message": "Το στυλ είναι ενημερωμένο."
}, },
"sectionRemove": { "updateCompleted": {
"message": "Αφαίρεση ενότητας", "message": "Η ενημέρωση ολοκληρώθηκε."
"description": "Label for the button to remove a section" },
}, "writeStyleFor": {
"disableAllStyles": { "message": "Γράψτε νέο στυλ για:"
"message": "Απενεργοποιηση ολων των στυλ", },
"description": "Label for the checkbox that turns all enabled styles off." "writeStyleForURL": {
}, "message": "αυτή την διεύθυνση URL"
"updateCompleted": { }
"message": "Η ενημέρωση ολοκληρώθηκε.",
"description": "Text that displays when an update completed"
},
"checkingForUpdate": {
"message": "Έλεγχος...",
"description": "Text to display when checking a style for an update"
},
"sectionCode": {
"message": "Κώδικας",
"description": "Label for the code for a section"
},
"cm_smartIndent": {
"message": "Χρήση έξυπνης εσοχής",
"description": "Label for the checkbox controlling smart indentation option for the style editor."
},
"appliesHelp": {
"message": "Χρησιμοποιήστε το \"Ισχύει για\" έλεγχοι ώστε να περιοριστουν ποιες διευθύνσεις τον κώδικα σε αυτό το τμήμα να εφαρμόζονται.",
"description": "Help text for 'applies to' section"
},
"editStyleHeading": {
"message": "Επεξεργασία Στυλ",
"description": "Title of the page for editing styles"
},
"stylusUnavailableForURL": {
"message": "To Stylus δεν λειτουργεί σε σελίδες όπως αυτή.",
"description": "Note in the toolbar pop-up when on a URL Stylus can't affect"
},
"addStyleTitle": {
"message": "Προσθήκη στυλ",
"description": "Title of the page for adding styles"
},
"dbError": {
"message": "Παρουσιάστηκε σφάλμα χρησιμοποιώντας την κομψή βάση δεδομένων. Θα θέλατε να επισκεφθείτε μια ιστοσελίδα με πιθανές λύσεις;",
"description": "Prompt when a DB error is encountered"
},
"editGotoLine": {
"message": "Μετάβαση στη γραμμή (ή line:col)",
"description": "Go to line or line:column on Ctrl-G in style code editor"
},
"checkAllUpdates": {
"message": "Έλεγχος όλων των στυλ για ενημερώσεις",
"description": "Label for the button to check all styles for updates"
},
"cm_indentWithTabs": {
"message": "Χρήση καρτελών με έξυπνη εσοχή",
"description": "Label for the checkbox controlling tabs with smart indentation option for the style editor."
},
"deleteStyleLabel": {
"message": "Διαγραφή",
"description": "Label for the button to delete a style"
},
"addStyleLabel": {
"message": "Γράψτε νέο στυλ",
"description": "Label for the button to go to the add style page"
},
"manageOnlyEnabled": {
"message": "Μόνο ενεργοποιημένα στυλ",
"description": "Checkbox to show only enabled styles"
},
"editStyleLabel": {
"message": "Επεξεργασία",
"description": "Label for the button to go to the edit style page"
},
"description": {
"message": "Επαναπροσδιορίση του διαδίκτυου με το Stylus, έναν διαχειριστή στυλ. Το Stylus σας επιτρέπει να εγκαταστήσετε εύκολα themes και skins για πολλές δημοφιλείς ιστοσελίδες.",
"description": "Extension description"
}
} }

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,75 @@
{
"appliesRemoveError": {
"message": "Cannot remove last 'applies to' entry"
},
"checkAllUpdatesForce": {
"message": "Check again—I didn't edit any styles!"
},
"cm_autoCloseBrackets": {
"message": "Auto-close brackets and quotes"
},
"cm_colorpicker": {
"message": "Colour pickers for CSS colours"
},
"cm_resizeGripHint": {
"message": "Double-click to maximise/restore the height"
},
"colorpickerTooltip": {
"message": "Open colour picker"
},
"description": {
"message": "Redesign the web with Stylus, a user-style manager. Stylus allows you to easily install themes and skins for many popular sites."
},
"editGotoLine": {
"message": "Go to line (or line:col)"
},
"editStyleHeading": {
"message": "Edit style"
},
"installUpdateUnavailable": {
"message": "To enable checking for updates, drop the file on the tab strip or specify @updateURL in the style metadata."
},
"license": {
"message": "Licence"
},
"manageFaviconsGray": {
"message": "Greyed out"
},
"optionsBadgeDisabled": {
"message": "Background colour when disabled"
},
"optionsBadgeNormal": {
"message": "Background colour"
},
"optionsUpdateImportNote": {
"message": "When importing style backups from an old version or from Stylish, do a one-time check for updates manually in the styles manager to ensure all styles are updated."
},
"optionsUpdateInterval": {
"message": "Userstyle auto-update interval in hours (specify 0 to disable)"
},
"styleInstallFailed": {
"message": "Failed to install userstyle\n$error$",
"placeholders": {
"error": {
"content": "$1"
}
}
},
"styleMetaErrorColor": {
"message": "$color$ is not a valid colour",
"placeholders": {
"color": {
"content": "$1"
}
}
},
"styleRegexpPartialExplanation": {
"message": "This style uses partially matching regexps in violation of <a href='https://developer.mozilla.org/docs/Web/CSS/@document'>CSS4 @document specification</a> which requires a full URL match. The affected CSS sections were not applied to the page. This style was probably created in Stylish-for-Chrome, which incorrectly checks 'regexp()' rules since the very first version (known bug)."
},
"styleUpdateDiscardChanges": {
"message": "The style has been changed outside the editor. Would you like to reload the style?"
},
"usercssConfigIncomplete": {
"message": "The style was updated or deleted after the configuration dialogue was shown. These variables were not saved to avoid corrupting the style's metadata:"
}
}

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -1,226 +1,175 @@
{ {
"appliesToEverything": { "addStyleLabel": {
"message": "Kaikki", "message": "Uusi Tyyli"
"description": "Text displayed for styles that apply to all sites" },
}, "addStyleTitle": {
"enableStyleLabel": { "message": "Lisää Tyyli"
"message": "Aktivoi", },
"description": "Label for the button to enable a style" "appliesAdd": {
}, "message": "Lisää"
"styleMissingName": { },
"message": "Syötä nimi", "appliesDisplay": {
"description": "Error displayed when user saves without providing a name" "message": "Kooskee: $applies$",
}, "placeholders": {
"appliesDomainOption": { "applies": {
"message": "URL ositteita domainilla", "content": "$1"
"description": "Option to make the style apply to the entered string as a domain" }
}, }
"checkForUpdate": { },
"message": "Hae päivityksiä", "appliesDisplayTruncatedSuffix": {
"description": "Label for the button to check a single style for an update" "message": "ja lisää"
}, },
"updateAllCheckSucceededNoUpdate": { "appliesDomainOption": {
"message": "All styles are up to date.", "message": "URL ositteita domainilla"
"description": "Text that displays when an update all check completed and no updates are available" },
}, "appliesHelp": {
"helpAlt": { "message": "Käytä 'Koskee' kontrolleja rajoittaaksesi mitä URL osoitteisiin tämä osio koodista koskee."
"message": "Apu", },
"description": "Alternate text for help buttons" "appliesLabel": {
}, "message": "Koskee"
"findStylesForSite": { },
"message": "Hae lisää tyylejä tälle sivustolle", "appliesRegexpOption": {
"description": "Text for a link that gets a list of styles for the current site" "message": "URL ositteet jotka vastaavat regexpiä"
}, },
"manageHeading": { "appliesRemove": {
"message": "Asennetut Tyylit", "message": "Poista"
"description": "Heading for the manage page" },
}, "appliesSpecify": {
"styleEnabledLabel": { "message": "Tarkenna"
"message": "Aktivoitu", },
"description": "Label for the enabled state of styles" "appliesToEverything": {
}, "message": "Kaikki"
"styleToMozillaFormatHelp": { },
"message": "Mozilla formaattia koodista voidaan käyttää Stylish Firefoxille ohjelmassa ja voidaan lähettää userstyles.orgiin.", "appliesUrlPrefixOption": {
"description": "Help info for the Mozilla format header section that converts the code to/from Mozilla format" "message": "URL osoitteet jotka alkavat"
}, },
"sectionAdd": { "checkAllUpdates": {
"message": "Lisää uusi osio", "message": "Tarkista kaikki tyylit päivityksien varalta"
"description": "Label for the button to add a section" },
}, "checkForUpdate": {
"styleSaveLabel": { "message": "Hae päivityksiä"
"message": "Tallenna", },
"description": "Label for save button for style editing" "checkingForUpdate": {
}, "message": "Tarkistetaan..."
"appliesAdd": { },
"message": "Lisää", "deleteStyleConfirm": {
"description": "Label for the button to add an 'applies' entry" "message": "Oletko varma että haluat poistaa tämän tyylin?"
}, },
"appliesRegexpOption": { "deleteStyleLabel": {
"message": "URL ositteet jotka vastaavat regexpiä", "message": "Poista"
"description": "Option to make the style apply to the entered string as a regular expression" },
}, "description": {
"styleInstall": { "message": "Uudelleen stailaa netti Stylusillä, käyttäjän tyyli hallintapaneelilla. Stylus antaa sinun helposti asentaa teemoja ja skinejä palvelluille kuten Google, Facebook, YouTube, Orkut, ja monelle, monelle muulle sivustolle."
"message": "Asennetaanko '$stylename$' Stylusiin?", },
"description": "Confirmation when installing a style", "disableStyleLabel": {
"placeholders": { "message": "Poista Käytöstä"
"stylename": { },
"content": "$1" "editStyleHeading": {
} "message": "Muokkaa Tyyliä"
} },
}, "editStyleLabel": {
"disableStyleLabel": { "message": "Muokkaa"
"message": "Poista Käytöstä", },
"description": "Label for the button to disable a style" "editStyleTitle": {
}, "message": "Muokkaa Tyyliä $stylename$",
"prefShowBadge": { "placeholders": {
"message": "Show number of styles active for the current site on the toolbar button", "stylename": {
"description": "Label for the checkbox controlling toolbar badge text." "content": "$1"
}, }
"styleCancelEditLabel": { }
"message": "Takaisin hallintapaneeliin", },
"description": "Label for cancel button for style editing" "enableStyleLabel": {
}, "message": "Aktivoi"
"styleChangesNotSaved": { },
"message": "Olet tehnyt muutoksia tähän tyyliin tallentamatta.", "findStylesForSite": {
"description": "Text for the prompt when changes are made to a style and the user tries to leave without saving" "message": "Hae lisää tyylejä tälle sivustolle"
}, },
"updateCheckFailServerUnreachable": { "helpAlt": {
"message": "Päivitys epäonnistui: ei voitu yhdistää palvelimeen.", "message": "Apu"
"description": "Text that displays when an update check failed because the update server is unreachable" },
}, "installUpdate": {
"deleteStyleConfirm": { "message": "Asenna päivitys"
"message": "Oletko varma että haluat poistaa tämän tyylin?", },
"description": "Confirmation before deleting a style" "manageHeading": {
}, "message": "Asennetut Tyylit"
"styleBadRegexp": { },
"message": "Regexp ei kelpaa.", "manageTitle": {
"description": "Validation message for a bad regexp in a style" "message": "Tyylikäs"
}, },
"appliesDisplay": { "noStylesForSite": {
"message": "Kooskee: $applies$", "message": "Ei asennettuja tyylejä tällä sivustolla."
"description": "Text on the manage screen to describe what the style applies to", },
"placeholders": { "openManage": {
"applies": { "message": "Hallitse asennettuja tyylejä"
"content": "$1" },
} "popupStylesFirst": {
} "message": "List styles before commands in the toolbar button menu"
}, },
"styleSectionsTitle": { "prefShowBadge": {
"message": "Osiot", "message": "Show number of styles active for the current site on the toolbar button"
"description": "Title for the style sections section" },
}, "sectionAdd": {
"editStyleTitle": { "message": "Lisää uusi osio"
"message": "Muokkaa Tyyliä $stylename$", },
"description": "Title of the page for editing styles", "sectionCode": {
"placeholders": { "message": "Koodi"
"stylename": { },
"content": "$1" "sectionHelp": {
} "message": "Osiot antavat sinun tarkentaa koodin eri osia niin että ne koskevat eri URL osoitteita samassa tyylissä. Esimerkiksi, yksi tyyli voi muokata kotisivua yhdellä tavalla kun se muokkaa koko muuta sivustoa toisella tavalla."
} },
}, "sectionRemove": {
"updateCheckSucceededNoUpdate": { "message": "Poista osio"
"message": "Tyyli on ajan tasalla.", },
"description": "Text that displays when an update check completed and no update is available" "styleBadRegexp": {
}, "message": "Regexp ei kelpaa."
"appliesUrlPrefixOption": { },
"message": "URL osoitteet jotka alkavat", "styleCancelEditLabel": {
"description": "Option to make the style apply to the entered string as a URL prefix" "message": "Takaisin hallintapaneeliin"
}, },
"popupStylesFirst": { "styleChangesNotSaved": {
"message": "List styles before commands in the toolbar button menu", "message": "Olet tehnyt muutoksia tähän tyyliin tallentamatta."
"description": "Label for the checkbox controlling section order in the popup." },
}, "styleEnabledLabel": {
"sectionHelp": { "message": "Aktivoitu"
"message": "Osiot antavat sinun tarkentaa koodin eri osia niin että ne koskevat eri URL osoitteita samassa tyylissä. Esimerkiksi, yksi tyyli voi muokata kotisivua yhdellä tavalla kun se muokkaa koko muuta sivustoa toisella tavalla.", },
"description": "Help text for sections" "styleInstall": {
}, "message": "Asennetaanko '$stylename$' Stylusiin?",
"noStylesForSite": { "placeholders": {
"message": "Ei asennettuja tyylejä tällä sivustolla.", "stylename": {
"description": "Text displayed when no styles are installed for the current site" "content": "$1"
}, }
"appliesDisplayTruncatedSuffix": { }
"message": "ja lisää", },
"description": "Text added to appliesDisplay when there are more sites for the style than are displayed" "styleMissingName": {
}, "message": "Syötä nimi"
"appliesRemove": { },
"message": "Poista", "styleSaveLabel": {
"description": "Label for the button to remove an 'applies' entry" "message": "Tallenna"
}, },
"manageTitle": { "styleSectionsTitle": {
"message": "Tyylikäs", "message": "Osiot"
"description": "Title for the manage page" },
}, "styleToMozillaFormatHelp": {
"appliesLabel": { "message": "Mozilla formaattia koodista voidaan käyttää Stylish Firefoxille ohjelmassa ja voidaan lähettää userstyles.orgiin."
"message": "Koskee", },
"description": "Label for 'applies to' fields on the edit/add screen" "updateAllCheckSucceededNoUpdate": {
}, "message": "All styles are up to date."
"openManage": { },
"message": "Hallitse asennettuja tyylejä", "updateCheckFailBadResponseCode": {
"description": "Link to open the manage page." "message": "Päivitys epäonnistui: palvelin vastasi koodilla $code$.",
}, "placeholders": {
"updateCheckFailBadResponseCode": { "code": {
"message": "Päivitys epäonnistui: palvelin vastasi koodilla $code$.", "content": "$1"
"description": "Text that displays when an update check failed because the response code indicates an error", }
"placeholders": { }
"code": { },
"content": "$1" "updateCheckFailServerUnreachable": {
} "message": "Päivitys epäonnistui: ei voitu yhdistää palvelimeen."
} },
}, "updateCheckSucceededNoUpdate": {
"appliesSpecify": { "message": "Tyyli on ajan tasalla."
"message": "Tarkenna", },
"description": "Label for the button to make a style apply only to specific sites" "updateCompleted": {
}, "message": "Päivitys suoritettu."
"installUpdate": { }
"message": "Asenna päivitys",
"description": "Label for the button to install an update for a single style"
},
"sectionRemove": {
"message": "Poista osio",
"description": "Label for the button to remove a section"
},
"updateCompleted": {
"message": "Päivitys suoritettu.",
"description": "Text that displays when an update completed"
},
"checkingForUpdate": {
"message": "Tarkistetaan...",
"description": "Text to display when checking a style for an update"
},
"sectionCode": {
"message": "Koodi",
"description": "Label for the code for a section"
},
"appliesHelp": {
"message": "Käytä 'Koskee' kontrolleja rajoittaaksesi mitä URL osoitteisiin tämä osio koodista koskee.",
"description": "Help text for 'applies to' section"
},
"editStyleHeading": {
"message": "Muokkaa Tyyliä",
"description": "Title of the page for editing styles"
},
"addStyleTitle": {
"message": "Lisää Tyyli",
"description": "Title of the page for adding styles"
},
"checkAllUpdates": {
"message": "Tarkista kaikki tyylit päivityksien varalta",
"description": "Label for the button to check all styles for updates"
},
"deleteStyleLabel": {
"message": "Poista",
"description": "Label for the button to delete a style"
},
"addStyleLabel": {
"message": "Uusi Tyyli",
"description": "Label for the button to go to the add style page"
},
"editStyleLabel": {
"message": "Muokkaa",
"description": "Label for the button to go to the edit style page"
},
"description": {
"message": "Uudelleen stailaa netti Stylusillä, käyttäjän tyyli hallintapaneelilla. Stylus antaa sinun helposti asentaa teemoja ja skinejä palvelluille kuten Google, Facebook, YouTube, Orkut, ja monelle, monelle muulle sivustolle.",
"description": "Extension description"
}
} }

File diff suppressed because it is too large Load Diff

97
_locales/fy/messages.json Normal file
View File

@ -0,0 +1,97 @@
{
"addStyleLabel": {
"message": "Nije styl skriuwe"
},
"addStyleTitle": {
"message": "Styl tafoegje"
},
"appliesAdd": {
"message": "Tafoegje"
},
"appliesDisplay": {
"message": "Fan tapassing op: $applies$",
"placeholders": {
"applies": {
"content": "$1"
}
}
},
"appliesDisplayTruncatedSuffix": {
"message": "en mear"
},
"appliesDomainOption": {
"message": "URLs op it domein"
},
"appliesHelp": {
"message": "Brûk de Fan tapassing op-funksjes om de URLs foar de koade yn dizze seksje te beheinen."
},
"appliesLabel": {
"message": "Fan tapassing op"
},
"appliesRegexpOption": {
"message": "URLs oerienkommend mei de regexp"
},
"appliesRemove": {
"message": "Fuortsmite"
},
"appliesSpecify": {
"message": "Spesifisearje"
},
"appliesToEverything": {
"message": "Alles"
},
"appliesUrlPrefixOption": {
"message": "URLs begjinnend mei"
},
"applyAllUpdates": {
"message": "Alle fernijingen tapasse"
},
"checkAllUpdates": {
"message": "Alle stilen kontrolearje op fernijingen"
},
"checkForUpdate": {
"message": "Kontrolearje op fernijing"
},
"checkingForUpdate": {
"message": "Kontrolearje..."
},
"cm_indentWithTabs": {
"message": "Ljepblêden mei tûke ynspringing brûke"
},
"cm_keyMap": {
"message": "Toetseboerdyndieling"
},
"cm_lineWrapping": {
"message": "Teksttebekrin"
},
"cm_smartIndent": {
"message": "Tûke ynspringing brûke"
},
"cm_tabSize": {
"message": "Ljepblêdgrutte"
},
"cm_theme": {
"message": "Tema"
},
"confirmNo": {
"message": "Nee"
},
"confirmStop": {
"message": "Stoppe"
},
"confirmYes": {
"message": "Ja"
},
"dbError": {
"message": "Der is in flater bard by it brûken fan de Stylus-database. Wolle jo in webside mei mooglike oplossingen besykje?"
},
"defaultTheme": {
"message": "standert"
},
"deleteStyleConfirm": {
"message": "Binne jo wis dat jo dizze styl fuortsmite wolle?"
},
"deleteStyleLabel": {
"message": "Fuortsmite"
}
}

767
_locales/he/messages.json Normal file
View File

@ -0,0 +1,767 @@
{
"addStyleLabel": {
"message": "כתוב עיצוב חדש"
},
"addStyleTitle": {
"message": "הוספת עיצוב"
},
"alphaChannel": {
"message": "שקיפות"
},
"appliesAdd": {
"message": "הוספה"
},
"appliesDisplay": {
"message": "מוחל על: $applies$",
"placeholders": {
"applies": {
"content": "$1"
}
}
},
"appliesDisplayTruncatedSuffix": {
"message": "עוד"
},
"appliesDomainOption": {
"message": "קישורים תחת הדומיין"
},
"appliesLabel": {
"message": "מוחל על"
},
"appliesLineWidgetLabel": {
"message": "הצג אינפורמציית 'חל על'"
},
"appliesLineWidgetWarning": {
"message": "לא עובד עם CSS מוקטן (minified)"
},
"appliesRegexpOption": {
"message": "קישורים התואמים regexp"
},
"appliesRemove": {
"message": "הסרה"
},
"appliesRemoveError": {
"message": "לא ניתן להסיר את הערך 'חל על' האחרון"
},
"appliesSpecify": {
"message": "פרט"
},
"appliesToEverything": {
"message": "כל האתרים"
},
"appliesUrlOption": {
"message": "קישור (URL)"
},
"appliesUrlPrefixOption": {
"message": "קישורים המתחילים ב"
},
"applyAllUpdates": {
"message": "החל את כל העדכונים"
},
"author": {
"message": "כותב"
},
"backupButtons": {
"message": "גיבוי"
},
"backupMessage": {
"message": "בחר קובץ או גרור ושחרר אותו בדף זה."
},
"bckpInstStyles": {
"message": "ייצא עיצובים"
},
"checkAllUpdates": {
"message": "בדוקים עדכונים עבור כל העיצובים"
},
"checkAllUpdatesForce": {
"message": "בדוק שוב, לא ערכתי אף עיצוב!"
},
"checkForUpdate": {
"message": "בדוק עדכונים"
},
"checkingForUpdate": {
"message": "בודק..."
},
"clickToUninstall": {
"message": "הקלק להסרה"
},
"cm_autoCloseBrackets": {
"message": "סגור באופן אוטומטי סוגריים וגרשיים"
},
"cm_autoCloseBracketsTooltip": {
"message": "הוסף סוגר באופן אוטומטי כשמקלידים את אחת מהפותחים של ()[]{}''\"\""
},
"cm_autocompleteOnTyping": {
"message": "השלמה אוטומטית בזמן הכתיבה"
},
"cm_colorpicker": {
"message": "פלטות בחירת צבעים עבור צבעי CSS"
},
"cm_indentWithTabs": {
"message": "השתמש בטאבים יחד עם הזחה חכמה"
},
"cm_keyMap": {
"message": "מפת מקשים"
},
"cm_lineWrapping": {
"message": "עטיפת מילים"
},
"cm_matchHighlight": {
"message": "הדגש"
},
"cm_matchHighlightSelection": {
"message": "בחירה בלבד"
},
"cm_resizeGripHint": {
"message": "דאבל קליק להגדלה מירבית/איפוס הגובה"
},
"cm_selectByTokens": {
"message": "דאבל קליק בוחר tokens"
},
"cm_smartIndent": {
"message": "השתמש בהזחה חכמה"
},
"cm_tabSize": {
"message": "גודל הכרטיסייה"
},
"cm_theme": {
"message": "ערכת נושא"
},
"colorpickerTooltip": {
"message": "פתח את פלטת בחירת הצבעים"
},
"configOnChange": {
"message": "בעת שינוי"
},
"configOnChangeTooltip": {
"message": "שמור והחל שינויים באופן אוטומטי"
},
"configureStyle": {
"message": "הגדר"
},
"configureStyleOnHomepage": {
"message": "הגדר בדף הבית"
},
"confirmCancel": {
"message": "ביטול"
},
"confirmClose": {
"message": "סגור"
},
"confirmDefault": {
"message": "השתמש בברירת מחדל"
},
"confirmDelete": {
"message": "מחיקה"
},
"confirmDiscardChanges": {
"message": "למחוק את השינויים?"
},
"confirmNo": {
"message": "לא"
},
"confirmOK": {
"message": "אוקיי"
},
"confirmSave": {
"message": "שמור"
},
"confirmStop": {
"message": "עצור"
},
"confirmYes": {
"message": "כן"
},
"dateInstalled": {
"message": "תאריך התקנה"
},
"dateUpdated": {
"message": "תאריך עדכון"
},
"defaultTheme": {
"message": "ברירת מחדל"
},
"deleteStyleConfirm": {
"message": "האם אתה בטוח שברצונך למחוק עיצוב זה?"
},
"deleteStyleLabel": {
"message": "מחק"
},
"disableAllStyles": {
"message": "השבת את כל העיצובים"
},
"disableStyleLabel": {
"message": "השבת"
},
"dragDropMessage": {
"message": "שחרר את קובץ הגיבוי שלך בכל מקום בדף זה על־מנת לייבא אותו."
},
"editDeleteText": {
"message": "מחק"
},
"editGotoLine": {
"message": "Goto לשורה (או line:col)"
},
"editStyleHeading": {
"message": "עריכת עיצוב"
},
"editStyleLabel": {
"message": "עריכה"
},
"editStyleTitle": {
"message": "עריכת העיצוב $stylename$",
"placeholders": {
"stylename": {
"content": "$1"
}
}
},
"editorStylesButton": {
"message": "מצא עיצובים לעורך"
},
"enableStyleLabel": {
"message": "אפשר"
},
"exportLabel": {
"message": "ייצא"
},
"externalFeedback": {
"message": "חוות דעת"
},
"externalHomepage": {
"message": "דף הבית"
},
"externalLink": {
"message": "קישור חיצוני"
},
"externalSupport": {
"message": "תמיכה"
},
"findStyles": {
"message": "מצא עיצובים"
},
"findStylesInline": {
"message": "מוטבע"
},
"genericAdd": {
"message": "הוספה"
},
"genericClone": {
"message": "שכפול"
},
"genericDisabledLabel": {
"message": "מושבת"
},
"genericEnabledLabel": {
"message": "מאופשר"
},
"genericError": {
"message": "שגיאה"
},
"genericHistoryLabel": {
"message": "היסטוריה"
},
"genericNext": {
"message": "הבא"
},
"genericPrevious": {
"message": "הקודם"
},
"genericResetLabel": {
"message": "איפוס"
},
"genericSavedMessage": {
"message": "נשמר"
},
"genericTitle": {
"message": "כותרת"
},
"genericUnknown": {
"message": "לא ידוע"
},
"helpAlt": {
"message": "עזרה"
},
"helpKeyMapCommand": {
"message": "הקלד שם פקודה"
},
"helpKeyMapHotkey": {
"message": "לחץ על המקש החם"
},
"importAppendLabel": {
"message": "צרף לעיצוב"
},
"importAppendTooltip": {
"message": "צרף את העיצוב המיובא לעיצוב הנוכחי"
},
"importLabel": {
"message": "ייבא"
},
"importReplaceLabel": {
"message": "דרוס עיצוב"
},
"importReportLegendAdded": {
"message": "נוספו"
},
"importReportLegendUpdatedCode": {
"message": "קודים עודכנו"
},
"importReportLegendUpdatedMeta": {
"message": "מידע meta עודכנו"
},
"importReportTitle": {
"message": "סיום ייבוא עיצובים"
},
"importReportUnchanged": {
"message": "שום דבר לא השתנה."
},
"importReportUndone": {
"message": "עיצובים הוחזרו"
},
"importReportUndoneTitle": {
"message": "הייבוא בוטל"
},
"installButton": {
"message": "התקן עיצוב"
},
"installButtonInstalled": {
"message": "העיצוב הותקן בהצלחה"
},
"installButtonReinstall": {
"message": "התקן עיצוב מחדש"
},
"installButtonUpdate": {
"message": "עדכן עיצוב"
},
"installUpdate": {
"message": "התקן עדכון"
},
"installUpdateFromLabel": {
"message": "בדוק עדכונים"
},
"installUpdateUnavailable": {
"message": "על־מנת לאפשר בדיקת עדכונים, אנא שחרר את הקובץ על רצועת הכרטיסיות או ציין @updateURL ב־metadata של העיצוב."
},
"license": {
"message": "רישיון"
},
"linkGetHelp": {
"message": "קבל עזרה"
},
"linkGetStyles": {
"message": "מצא עיצובים"
},
"linkTranslate": {
"message": "תרגום"
},
"linterCSSLintSettings": {
"message": "(הגדר כלל כ: 0 = מושבת; 1 = אזהרה; 2 = שגיאה)"
},
"linterConfigPopupTitle": {
"message": "הגדר $linter$ כללי תצורה",
"placeholders": {
"linter": {
"content": "$1"
}
}
},
"linterConfigTooltip": {
"message": "לחץ להגדרת linter זה"
},
"linterIssues": {
"message": "תקלות"
},
"linterIssuesHelp": {
"message": "התקלות האלו נמצאו על־ידי $link$:",
"placeholders": {
"link": {
"content": "$1"
}
}
},
"linterJSONError": {
"message": "פורמט JSON לא תקין"
},
"linterResetMessage": {
"message": "על־מנת לבטל איפוסים לא רצוניים, לחץ Ctrl-Z (או Cmd-Z) בתיבת הטקסט"
},
"linterRulesLink": {
"message": "ראה רשימת חוקים מלאה"
},
"liveReloadError": {
"message": "התרחשה שגיאה בזמן הצפייה בקובץ"
},
"liveReloadLabel": {
"message": "רענון לייב (live)"
},
"liveReloadUnavailable": {
"message": "על־מנת לאפשר רענון לייב (live), אנא שחרר את הקובץ על רצועת הכרטיסיות (האזור בו כותרות הכרטיסיות מוצגות)."
},
"manageFavicons": {
"message": "הצגת אייקונים בעמודת 'חל על'"
},
"manageFaviconsGray": {
"message": "האפרת האייקונים"
},
"manageFaviconsHelp": {
"message": "Stylus משתמש בשירות חיצוני https://www.google.com/s2/favicons"
},
"manageFilters": {
"message": "מסננים"
},
"manageHeading": {
"message": "עיצובים מותקנים"
},
"manageMaxTargets": {
"message": "מספר הפריטים ה־'מוחלים על'"
},
"manageNewStyleAsUsercss": {
"message": "כ־Usercss"
},
"manageOnlyDisabled": {
"message": "רק עיצובים מושבתים"
},
"manageOnlyEnabled": {
"message": "רק עיצובים מאופשרים"
},
"manageOnlyExternal": {
"message": "רק עיצובים חיצוניים"
},
"manageOnlyLocal": {
"message": "רק עיצובים שנוצרו באופן מקומי"
},
"manageOnlyNonUsercss": {
"message": "רק לא עיצובי Usercss"
},
"manageOnlyUpdates": {
"message": "רק עם עדכונים או תקלות"
},
"manageOnlyUsercss": {
"message": "רק עיצובי Usercss"
},
"menuShowBadge": {
"message": "הצג כמות עיצובים מאופשרים"
},
"noStylesForSite": {
"message": "לא הותקנו עיצובים עבור אתר זה."
},
"openManage": {
"message": "ניהול"
},
"openOptionsManage": {
"message": "אפשרויות UI"
},
"openOptionsPopup": {
"message": "אפשרויות"
},
"openStylesManager": {
"message": "פתח את מנהל העיצובים"
},
"optionsActions": {
"message": "פעולות"
},
"optionsAdvanced": {
"message": "מתקדם"
},
"optionsAdvancedContextDelete": {
"message": "הוספת 'מחיקה' בתפריט העורך"
},
"optionsBadgeDisabled": {
"message": "צבע רקע בעת השבתה"
},
"optionsBadgeNormal": {
"message": "צבע רקע"
},
"optionsCheck": {
"message": "עדכן עיצובים"
},
"optionsCheckUpdate": {
"message": "בדוק והתקן את כל העדכונים הזמינים"
},
"optionsCustomizeBadge": {
"message": "תג על האייקון בסרגל הכלים"
},
"optionsCustomizeIcon": {
"message": "אייקון בסרגל הכלים"
},
"optionsCustomizePopup": {
"message": "חלון קופץ"
},
"optionsCustomizeUpdate": {
"message": "עדכונים"
},
"optionsHeading": {
"message": "אפשרויות"
},
"optionsIconDark": {
"message": "ערכות נושא כהות לדפדפן"
},
"optionsIconLight": {
"message": "ערכות נושא בהירות לדפדפן"
},
"optionsOpen": {
"message": "פתח"
},
"optionsOpenManager": {
"message": "נהל עיצובים"
},
"optionsPopupWidth": {
"message": "רוחב החלון הקופץ (בפיקסלים)"
},
"optionsReset": {
"message": "איפוס האפשרויות לערכי ברירת המחדל"
},
"optionsResetButton": {
"message": "איפוס האפשרויות"
},
"optionsSubheading": {
"message": "אפשרויות נוספות"
},
"optionsUpdateInterval": {
"message": "עדכון אוטומטי של Userstyle בשעות (הגדר 0 להשבתה)"
},
"paginationCurrent": {
"message": "הדף הנוכחי"
},
"paginationEstimated": {
"message": "מספר דפים משוער"
},
"paginationNext": {
"message": "הדף הבא"
},
"paginationPrevious": {
"message": "הדף הקודם"
},
"paginationTotal": {
"message": "סה״כ דפים"
},
"popupBorders": {
"message": "הוספת שוליים לבנים בצדדים"
},
"popupHotkeysTooltip": {
"message": "לחץ על־מנת לצפות במקשים החמים הזמינים"
},
"popupOpenEditInWindow": {
"message": "פתח את העורך בחלון חדש"
},
"popupStylesFirst": {
"message": "עיצובים לפני הפקודות"
},
"prefShowBadge": {
"message": "מֿמספר העיצובים המאופשרים באתר הנוכחי"
},
"previewLabel": {
"message": "תצוגת לייב (live)"
},
"previewTooltip": {
"message": "החלת השינויים באופן זמני ללא שמירה.\nשמור את העיצוב על־מנת להפוך את השינויים לקבועים."
},
"replace": {
"message": "החלף"
},
"replaceAll": {
"message": "החלף הכל"
},
"replaceWith": {
"message": "החלף עם"
},
"retrieveBckp": {
"message": "ייבוא עיצובים"
},
"search": {
"message": "חיפוש"
},
"searchCaseSensitive": {
"message": "רגיש לאותיות גדולות/קטנות"
},
"searchNumberOfResults": {
"message": "מספר ההתאמות"
},
"searchNumberOfResults2": {
"message": "מספר ההתאמות בקוד ובערכים ה'מוחלים על'"
},
"searchRegexp": {
"message": "השתמש ב־/re/ לחיפוש באמצעות ביטוי regexp"
},
"searchResultInstallCount": {
"message": "סה״כ התקנות"
},
"searchResultNoneFound": {
"message": "לא נמצאו עיצובים לאתר זה."
},
"searchResultRating": {
"message": "דירוג"
},
"searchResultUpdated": {
"message": "עודכן"
},
"searchResultWeeklyCount": {
"message": "התקנות שבועיות"
},
"sectionRemove": {
"message": "הסר סעיף"
},
"sectionRestore": {
"message": "שחזר סעיף שהוסר"
},
"shortcuts": {
"message": "קיצורי מקשים"
},
"shortcutsNote": {
"message": "הגדר קיצורי מקשים"
},
"sortDateNewestFirst": {
"message": "החדש ביותר ראשון"
},
"sortDateOldestFirst": {
"message": "הישן ביותר ראשון"
},
"sortLabel": {
"message": "בחר שיטת מיון להחלה על העיצובים המותקנים"
},
"styleBadRegexp": {
"message": "ביטוי ה־Regexp לא תקין."
},
"styleBeautify": {
"message": "ייפה CSS"
},
"styleCancelEditLabel": {
"message": "חזרה לניהול"
},
"styleEnabledLabel": {
"message": "מאופשר"
},
"styleFromMozillaFormatPrompt": {
"message": "הדבק את הקוד ב־Mozilla-format"
},
"styleMetaErrorColor": {
"message": "$color$ הוא צבע לא תקין",
"placeholders": {
"color": {
"content": "$1"
}
}
},
"styleMetaErrorPreprocessor": {
"message": "@preprocessor לא נתמך: $preprocessor$",
"placeholders": {
"preprocessor": {
"content": "$1"
}
}
},
"styleMetaErrorSelectValueMismatch": {
"message": "הערך @select: לא קיים ברשימה"
},
"styleMissingMeta": {
"message": "@$key$ metadata חסרים",
"placeholders": {
"key": {
"content": "$1"
}
}
},
"styleMissingName": {
"message": "אנא הזן שם"
},
"styleRegexpTestButton": {
"message": "בדוק RegExp"
},
"styleRegexpTestFull": {
"message": "כרטיסיות תואמות"
},
"styleRegexpTestInvalid": {
"message": "ביטוי regrxp לא תקין"
},
"styleRegexpTestNone": {
"message": "לא תואם אף כרטיסייה"
},
"styleSaveLabel": {
"message": "שמור"
},
"styleSectionsTitle": {
"message": "סעיפים"
},
"styleToMozillaFormatTitle": {
"message": "עיצוב ב־Mozilla format"
},
"styleUpdate": {
"message": "האם אתה בטוח שברצונך לעדכן את '$stylename$'?",
"placeholders": {
"stylename": {
"content": "$1"
}
}
},
"stylusUnavailableForURL": {
"message": "Stylus לא עובד על דפים כמו זה."
},
"syncStorageErrorSaving": {
"message": "הערך לא יכול להשמר. אנא נסה להקטין את גודל הטקסט."
},
"toggleStyle": {
"message": "אפשר או השבת עיצוב"
},
"undo": {
"message": "בטל"
},
"undoGlobal": {
"message": "בטל בכל הסעיפים"
},
"unreachableAMO": {
"message": "Firefox לא מאפשרת גישה לאתר זה."
},
"unreachableContentScript": {
"message": "לא ניתן לתקשר עם הדף. אנא טען מחדש את הכרטיסייה."
},
"updateAllCheckSucceededNoUpdate": {
"message": "לא נמצאו עדכונים."
},
"updateCheckFailBadResponseCode": {
"message": "העדכון נכשל: השרת החזיר תגובה עם הקוד $code$.",
"placeholders": {
"code": {
"content": "$1"
}
}
},
"updateCheckFailServerUnreachable": {
"message": "העדכון נכשל: השרת לא זמין."
},
"updateCheckHistory": {
"message": "היסטוריה של בדיקת עדכונים"
},
"updateCheckSkippedLocallyEdited": {
"message": "עיצוב זה נערך באופן מקומי."
},
"updateCheckSkippedMaybeLocallyEdited": {
"message": "ייתכן כי עיצוב זה נערך באופן מקומי."
},
"updateCheckSucceededNoUpdate": {
"message": "העיצוב מעודכן."
},
"updateCompleted": {
"message": "העדכון הושלם."
},
"updatesCurrentlyInstalled": {
"message": "העדכונים הותקנו."
},
"usercssAvoidOverwriting": {
"message": "אנא שנה את הערך של @name or @namespace על־מנת להמנע מדריסה של עיצוב קיים."
},
"usercssEditorNamePlaceholder": {
"message": "ציין @name בקוד"
},
"usercssReplaceTemplateSectionBody": {
"message": "הכנס כאן קוד..."
},
"versionInvalidOlder": {
"message": "הגרסה ישנה יותר מהעיצוב המותקן."
},
"writeStyleFor": {
"message": "כתוב עיצוב עבור: "
},
"writeStyleForURL": {
"message": "הקישור הנוכחי"
}
}

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -1,358 +1,274 @@
{ {
"appliesToEverything": { "addStyleLabel": {
"message": "Tudo", "message": "Gravar novo estilo"
"description": "Text displayed for styles that apply to all sites" },
}, "addStyleTitle": {
"defaultTheme": { "message": "Adicionar estilo"
"message": "padrão", },
"description": "Default CodeMirror CSS theme option on the edit style page" "appliesAdd": {
}, "message": "Adicionar"
"bckpInstStyles": { },
"message": "Exportar estilos", "appliesDisplay": {
"description": "" "message": "Aplica-se a: $applies$",
}, "placeholders": {
"exportLabel": { "applies": {
"message": "Exportar ", "content": "$1"
"description": "Label for the button to export a style ('edit' page) or all styles ('manage' page)" }
}, }
"optionsBadgeNormal": { },
"message": "Cor de fundo", "appliesDisplayTruncatedSuffix": {
"description": "" "message": "e mais"
}, },
"enableStyleLabel": { "appliesDomainOption": {
"message": "Ativar", "message": "URLs no domínio"
"description": "Label for the button to enable a style" },
}, "appliesHelp": {
"styleMissingName": { "message": "Use os controles \"Aplica-se a\" para limitar a quais URLs o código desta seção se aplica."
"message": "Insira um nome", },
"description": "Error displayed when user saves without providing a name" "appliesLabel": {
}, "message": "Aplica-se a"
"appliesDomainOption": { },
"message": "URLs no domínio", "appliesRegexpOption": {
"description": "Option to make the style apply to the entered string as a domain" "message": "URLs que correspondem a regexp"
}, },
"checkForUpdate": { "appliesRemove": {
"message": "Verificar atualizações", "message": "Remover"
"description": "Label for the button to check a single style for an update" },
}, "appliesSpecify": {
"importAppendLabel": { "message": "Especificar"
"message": "Anexar ao estilo", },
"description": "Label for the button to import a style and append to the existing sections" "appliesToEverything": {
}, "message": "Tudo"
"updateAllCheckSucceededNoUpdate": { },
"message": "Nenhuma atualização encontrada.", "appliesUrlPrefixOption": {
"description": "Text that displays when an update all check completed and no updates are available" "message": "URLs que começam com"
}, },
"helpAlt": { "applyAllUpdates": {
"message": "Ajuda", "message": "Aplicar todas as atualizações"
"description": "Alternate text for help buttons" },
}, "bckpInstStyles": {
"search": { "message": "Exportar estilos"
"message": "Buscar", },
"description": "Label before the search input field in the editor shown on Ctrl-F" "checkAllUpdates": {
}, "message": "Verificar atualizações para todos os estilos"
"confirmYes": { },
"message": "Sim", "checkForUpdate": {
"description": "'Yes' button in a confirm dialog" "message": "Verificar atualizações"
}, },
"findStylesForSite": { "checkingForUpdate": {
"message": "Procurar mais estilos para este site", "message": "Verificando..."
"description": "Text for a link that gets a list of styles for the current site" },
}, "cm_theme": {
"manageHeading": { "message": "Tema"
"message": "Estilos instalados", },
"description": "Heading for the manage page" "confirmCancel": {
}, "message": "Cancelar"
"styleBeautify": { },
"message": "Embelezar", "confirmDelete": {
"description": "Label for the CSS-beautifier button on the edit style page" "message": "Deletar"
}, },
"styleEnabledLabel": { "confirmNo": {
"message": "Ativado", "message": "Não"
"description": "Label for the enabled state of styles" },
}, "confirmStop": {
"styleToMozillaFormatHelp": { "message": "Parar"
"message": "O formato Mozilla do código pode ser usado com o Stylish para Firefox e pode ser enviado para userstyles.org.", },
"description": "Help info for the Mozilla format header section that converts the code to/from Mozilla format" "confirmYes": {
}, "message": "Sim"
"sectionAdd": { },
"message": "Adicionar outra seção", "defaultTheme": {
"description": "Label for the button to add a section" "message": "padrão"
}, },
"styleSaveLabel": { "deleteStyleConfirm": {
"message": "Salvar", "message": "Tem certeza de que deseja excluir este estilo?"
"description": "Label for save button for style editing" },
}, "deleteStyleLabel": {
"confirmDelete": { "message": "Excluir"
"message": "Deletar", },
"description": "" "description": {
}, "message": "Mude o estilo da web com o Stylus, um gerenciador de estilos do usuário. O Stylus permite instalar facilmente temas e skins para Google, Facebook, YouTube, Orkut e muitos, muitos outros sites."
"confirmCancel": { },
"message": "Cancelar", "disableAllStyles": {
"description": "" "message": "Desativar todos os estilos"
}, },
"retrieveBckp": { "disableStyleLabel": {
"message": "Importar estilos", "message": "Desativar"
"description": "" },
}, "editGotoLine": {
"confirmStop": { "message": "Ir para linha (ou linha:coluna)"
"message": "Parar", },
"description": "'Stop' button in a confirm dialog" "editStyleHeading": {
}, "message": "Editar estilo"
"writeStyleForURL": { },
"message": "esse URL", "editStyleLabel": {
"description": "Text for link in toolbar pop-up to write a new style for the current URL" "message": "Editar"
}, },
"optionsSubheading": { "editStyleTitle": {
"message": "Mais Opções", "message": "Editar estilo $stylename$",
"description": "Subheading for options section on manage page." "placeholders": {
}, "stylename": {
"appliesAdd": { "content": "$1"
"message": "Adicionar", }
"description": "Label for the button to add an 'applies' entry" }
}, },
"appliesRegexpOption": { "enableStyleLabel": {
"message": "URLs que correspondem a regexp", "message": "Ativar"
"description": "Option to make the style apply to the entered string as a regular expression" },
}, "exportLabel": {
"styleInstall": { "message": "Exportar "
"message": "Instalar \"$stylename$\" no Stylus?", },
"description": "Confirmation when installing a style", "findStylesForSite": {
"placeholders": { "message": "Procurar mais estilos para este site"
"stylename": { },
"content": "$1" "helpAlt": {
} "message": "Ajuda"
} },
}, "importAppendLabel": {
"optionsBadgeDisabled": { "message": "Anexar ao estilo"
"message": "Cor de fundo quando desativado", },
"description": "" "importAppendTooltip": {
}, "message": "Anexar o estilo importado ao atual"
"optionsCheck": { },
"message": "Atualizar estilos", "importLabel": {
"description": "" "message": "Importar"
}, },
"searchStyles": { "importReplaceLabel": {
"message": "Buscar conteúdos", "message": "Sobrescrever estilo"
"description": "Label for the search filter textbox on the Manage styles page" },
}, "installUpdate": {
"disableStyleLabel": { "message": "Instalar atualização"
"message": "Desativar", },
"description": "Label for the button to disable a style" "manageHeading": {
}, "message": "Estilos instalados"
"prefShowBadge": { },
"message": "Show number of styles active for the current site on the toolbar button", "noStylesForSite": {
"description": "Label for the checkbox controlling toolbar badge text." "message": "Nenhum estilo instalado para este site."
}, },
"styleCancelEditLabel": { "openManage": {
"message": "Voltar ao gerenciamento", "message": "Gerenciar estilos instalados"
"description": "Label for cancel button for style editing" },
}, "openOptionsPopup": {
"styleChangesNotSaved": { "message": "Opções"
"message": "Você fez alterações neste estilo sem salvar.", },
"description": "Text for the prompt when changes are made to a style and the user tries to leave without saving" "optionsActions": {
}, "message": "Ações"
"importLabel": { },
"message": "Importar", "optionsBadgeDisabled": {
"description": "Label for the button to import a style ('edit' page) or all styles ('manage' page)" "message": "Cor de fundo quando desativado"
}, },
"updateCheckFailServerUnreachable": { "optionsBadgeNormal": {
"message": "A atualização falhou: servidor inacessível.", "message": "Cor de fundo"
"description": "Text that displays when an update check failed because the update server is unreachable" },
}, "optionsCheck": {
"applyAllUpdates": { "message": "Atualizar estilos"
"message": "Aplicar todas as atualizações", },
"description": "Label for the button to apply all detected updates" "optionsHeading": {
}, "message": "Opções"
"deleteStyleConfirm": { },
"message": "Tem certeza de que deseja excluir este estilo?", "optionsOpen": {
"description": "Confirmation before deleting a style" "message": "Abrir"
}, },
"styleBadRegexp": { "optionsSubheading": {
"message": "Expressão regular é inválida", "message": "Mais Opções"
"description": "Validation message for a bad regexp in a style" },
}, "prefShowBadge": {
"optionsHeading": { "message": "Show number of styles active for the current site on the toolbar button"
"message": "Opções", },
"description": "Heading for options section on manage page." "replace": {
}, "message": "Substituir"
"appliesDisplay": { },
"message": "Aplica-se a: $applies$", "replaceAll": {
"description": "Text on the manage screen to describe what the style applies to", "message": "Substituir todos"
"placeholders": { },
"applies": { "replaceWith": {
"content": "$1" "message": "Substituir com"
} },
} "retrieveBckp": {
}, "message": "Importar estilos"
"styleSectionsTitle": { },
"message": "Seções", "search": {
"description": "Title for the style sections section" "message": "Buscar"
}, },
"editStyleTitle": { "searchStyles": {
"message": "Editar estilo $stylename$", "message": "Buscar conteúdos"
"description": "Title of the page for editing styles", },
"placeholders": { "sectionAdd": {
"stylename": { "message": "Adicionar outra seção"
"content": "$1" },
} "sectionCode": {
} "message": "Código"
}, },
"updateCheckSucceededNoUpdate": { "sectionHelp": {
"message": "O estilo está atualizado.", "message": "As seções permitem definir diferentes partes de código para aplicar a diferentes conjuntos de URLs no mesmo estilo. Por exemplo, um único estilo poderia alterar a página inicial de um site de uma forma, enquanto alteraria o resto do site de outra forma."
"description": "Text that displays when an update check completed and no update is available" },
}, "sectionRemove": {
"appliesUrlPrefixOption": { "message": "Remover seção"
"message": "URLs que começam com", },
"description": "Option to make the style apply to the entered string as a URL prefix" "styleBadRegexp": {
}, "message": "Expressão regular é inválida"
"sectionHelp": { },
"message": "As seções permitem definir diferentes partes de código para aplicar a diferentes conjuntos de URLs no mesmo estilo. Por exemplo, um único estilo poderia alterar a página inicial de um site de uma forma, enquanto alteraria o resto do site de outra forma.", "styleBeautify": {
"description": "Help text for sections" "message": "Embelezar"
}, },
"noStylesForSite": { "styleCancelEditLabel": {
"message": "Nenhum estilo instalado para este site.", "message": "Voltar ao gerenciamento"
"description": "Text displayed when no styles are installed for the current site" },
}, "styleChangesNotSaved": {
"appliesDisplayTruncatedSuffix": { "message": "Você fez alterações neste estilo sem salvar."
"message": "e mais", },
"description": "Text added to appliesDisplay when there are more sites for the style than are displayed" "styleEnabledLabel": {
}, "message": "Ativado"
"appliesRemove": { },
"message": "Remover", "styleInstall": {
"description": "Label for the button to remove an 'applies' entry" "message": "Instalar \"$stylename$\" no Stylus?",
}, "placeholders": {
"replace": { "stylename": {
"message": "Substituir", "content": "$1"
"description": "Label before the replace input field in the editor shown on Ctrl-H" }
}, }
"appliesLabel": { },
"message": "Aplica-se a", "styleMissingName": {
"description": "Label for 'applies to' fields on the edit/add screen" "message": "Insira um nome"
}, },
"openOptionsPopup": { "styleMozillaFormatHeading": {
"message": "Opções", "message": "Formato Mozilla"
"description": "Go to Options UI" },
}, "styleSaveLabel": {
"openManage": { "message": "Salvar"
"message": "Gerenciar estilos instalados", },
"description": "Link to open the manage page." "styleSectionsTitle": {
}, "message": "Seções"
"updateCheckFailBadResponseCode": { },
"message": "A atualização falhou: o servidor respondeu com código $code$.", "styleToMozillaFormatHelp": {
"description": "Text that displays when an update check failed because the response code indicates an error", "message": "O formato Mozilla do código pode ser usado com o Stylish para Firefox e pode ser enviado para userstyles.org."
"placeholders": { },
"code": { "undo": {
"content": "$1" "message": "Desfazer"
} },
} "undoGlobal": {
}, "message": "Desfazer todas as seções"
"appliesSpecify": { },
"message": "Especificar", "updateAllCheckSucceededNoUpdate": {
"description": "Label for the button to make a style apply only to specific sites" "message": "Nenhuma atualização encontrada."
}, },
"installUpdate": { "updateCheckFailBadResponseCode": {
"message": "Instalar atualização", "message": "A atualização falhou: o servidor respondeu com código $code$.",
"description": "Label for the button to install an update for a single style" "placeholders": {
}, "code": {
"styleMozillaFormatHeading": { "content": "$1"
"message": "Formato Mozilla", }
"description": "Heading for the section with buttons to import/export Mozilla format of the style" }
}, },
"sectionRemove": { "updateCheckFailServerUnreachable": {
"message": "Remover seção", "message": "A atualização falhou: servidor inacessível."
"description": "Label for the button to remove a section" },
}, "updateCheckSucceededNoUpdate": {
"disableAllStyles": { "message": "O estilo está atualizado."
"message": "Desativar todos os estilos", },
"description": "Label for the checkbox that turns all enabled styles off." "updateCompleted": {
}, "message": "Atualização concluída."
"undoGlobal": { },
"message": "Desfazer todas as seções", "writeStyleForURL": {
"description": "CSS-beautify global Undo button label" "message": "esse URL"
}, }
"updateCompleted": {
"message": "Atualização concluída.",
"description": "Text that displays when an update completed"
},
"checkingForUpdate": {
"message": "Verificando...",
"description": "Text to display when checking a style for an update"
},
"sectionCode": {
"message": "Código",
"description": "Label for the code for a section"
},
"appliesHelp": {
"message": "Use os controles \"Aplica-se a\" para limitar a quais URLs o código desta seção se aplica.",
"description": "Help text for 'applies to' section"
},
"editStyleHeading": {
"message": "Editar estilo",
"description": "Title of the page for editing styles"
},
"addStyleTitle": {
"message": "Adicionar estilo",
"description": "Title of the page for adding styles"
},
"importReplaceLabel": {
"message": "Sobrescrever estilo",
"description": "Label for the button to import and overwrite current style"
},
"importAppendTooltip": {
"message": "Anexar o estilo importado ao atual",
"description": "Tooltip for the button to import a style and append to the existing sections"
},
"optionsOpen": {
"message": "Abrir",
"description": ""
},
"replaceAll": {
"message": "Substituir todos",
"description": "Label before the replace input field in the editor shown on 'replaceAll' hotkey"
},
"optionsActions": {
"message": "Ações",
"description": ""
},
"editGotoLine": {
"message": "Ir para linha (ou linha:coluna)",
"description": "Go to line or line:column on Ctrl-G in style code editor"
},
"checkAllUpdates": {
"message": "Verificar atualizações para todos os estilos",
"description": "Label for the button to check all styles for updates"
},
"confirmNo": {
"message": "Não",
"description": "'No' button in a confirm dialog"
},
"undo": {
"message": "Desfazer",
"description": "Button label"
},
"replaceWith": {
"message": "Substituir com",
"description": "Label before the replace-with input field in the editor shown on Ctrl-H etc."
},
"deleteStyleLabel": {
"message": "Excluir",
"description": "Label for the button to delete a style"
},
"addStyleLabel": {
"message": "Gravar novo estilo",
"description": "Label for the button to go to the add style page"
},
"editStyleLabel": {
"message": "Editar",
"description": "Label for the button to go to the edit style page"
},
"cm_theme": {
"message": "Tema",
"description": "Label for the style editor's CSS theme."
},
"description": {
"message": "Mude o estilo da web com o Stylus, um gerenciador de estilos do usuário. O Stylus permite instalar facilmente temas e skins para Google, Facebook, YouTube, Orkut e muitos, muitos outros sites.",
"description": "Extension description"
}
} }

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -1,420 +1,323 @@
{ {
"appliesToEverything": { "addStyleLabel": {
"message": "Све", "message": "Упиши нови стил"
"description": "Text displayed for styles that apply to all sites" },
}, "addStyleTitle": {
"linterIssues": { "message": "Додај стил"
"message": "Проблеми", },
"description": "Label for the CSS linter issues block on the style edit page" "appliesAdd": {
}, "message": "Додај"
"defaultTheme": { },
"message": "подразумевано", "appliesDisplay": {
"description": "Default CodeMirror CSS theme option on the edit style page" "message": "Примењује се на: $applies$",
}, "placeholders": {
"exportLabel": { "applies": {
"message": "Извези", "content": "$1"
"description": "Label for the button to export a style ('edit' page) or all styles ('manage' page)" }
}, }
"cm_tabSize": { },
"message": "Величина картице", "appliesDisplayTruncatedSuffix": {
"description": "Label for the text box controlling tab size option for the style editor." "message": "и још"
}, },
"enableStyleLabel": { "appliesDomainOption": {
"message": "Омогући", "message": "УРЛ адресе на домену"
"description": "Label for the button to enable a style" },
}, "appliesHelp": {
"styleMissingName": { "message": "Употреба 'Примењује се на' одређује опсег УРЛ адреса на које се код у овом одељку примењује."
"message": "Унесите назив", },
"description": "Error displayed when user saves without providing a name" "appliesLabel": {
}, "message": "Примењује се на"
"appliesDomainOption": { },
"message": "УРЛ адресе на домену", "appliesRegexpOption": {
"description": "Option to make the style apply to the entered string as a domain" "message": "УРЛ адресе које одговарају регуларном изразу"
}, },
"checkForUpdate": { "appliesRemove": {
"message": "Проверите ажурирање", "message": "Уклони"
"description": "Label for the button to check a single style for an update" },
}, "appliesSpecify": {
"importAppendLabel": { "message": "Детаљније"
"message": "Додај стилу", },
"description": "Label for the button to import a style and append to the existing sections" "appliesToEverything": {
}, "message": "Све"
"updateAllCheckSucceededNoUpdate": { },
"message": "Сви стилови су ажурирани.", "appliesUrlOption": {
"description": "Text that displays when an update all check completed and no updates are available" "message": "УРЛ"
}, },
"styleFromMozillaFormatPrompt": { "appliesUrlPrefixOption": {
"message": "Налепи код у Mozilla формату", "message": "УРЛ адресе које почињу са"
"description": "Prompt in the dialog displayed after clicking 'Import from Mozilla format' button" },
}, "applyAllUpdates": {
"helpAlt": { "message": "Примени сва ажурирања"
"message": "Помоћ", },
"description": "Alternate text for help buttons" "checkAllUpdates": {
}, "message": "Проверите ажурирања за све стилове"
"search": { },
"message": "Претражи", "checkForUpdate": {
"description": "Label before the search input field in the editor shown on Ctrl-F" "message": "Проверите ажурирање"
}, },
"confirmYes": { "checkingForUpdate": {
"message": "Да", "message": "Проверавање..."
"description": "'Yes' button in a confirm dialog" },
}, "cm_indentWithTabs": {
"findStylesForSite": { "message": "Користи картице са паметним увлачењем редова"
"message": "Пронађи још стилова за овај сајт", },
"description": "Text for a link that gets a list of styles for the current site" "cm_keyMap": {
}, "message": "Мапа тастера"
"manageHeading": { },
"message": "Инсталирани стилови", "cm_lineWrapping": {
"description": "Heading for the manage page" "message": "Преламање текста"
}, },
"styleBeautify": { "cm_smartIndent": {
"message": "Улепшај", "message": "Користи паметно увлачење редова"
"description": "Label for the CSS-beautifier button on the edit style page" },
}, "cm_tabSize": {
"styleEnabledLabel": { "message": "Величина картице"
"message": "Омогућено", },
"description": "Label for the enabled state of styles" "cm_theme": {
}, "message": "Тема"
"styleToMozillaFormatHelp": { },
"message": "Mozilla формат кода се може користити у Stylish за Firefox и може се послати на userstyles.org.", "confirmNo": {
"description": "Help info for the Mozilla format header section that converts the code to/from Mozilla format" "message": "Не"
}, },
"sectionAdd": { "confirmStop": {
"message": "Додај нови одељак", "message": "Заустави"
"description": "Label for the button to add a section" },
}, "confirmYes": {
"styleSaveLabel": { "message": "Да"
"message": "Сачувај", },
"description": "Label for save button for style editing" "dbError": {
}, "message": "Дошло је до грешке користећи Stylus базу података. Да ли желите да посетите веб страницу са могућим решењима?"
"confirmStop": { },
"message": "Заустави", "defaultTheme": {
"description": "'Stop' button in a confirm dialog" "message": "подразумевано"
}, },
"writeStyleForURL": { "deleteStyleConfirm": {
"message": "ову УРЛ адресу", "message": "Да ли сте сигурни да желите да избришете овај стил?"
"description": "Text for link in toolbar pop-up to write a new style for the current URL" },
}, "deleteStyleLabel": {
"appliesAdd": { "message": "Избриши"
"message": "Додај", },
"description": "Label for the button to add an 'applies' entry" "description": {
}, "message": "Измените стил интернет мреже управљачем корисничких стилова. Stylus вам омогућава да лако инсталирате теме и скинове за многе популарне сајтове."
"appliesRegexpOption": { },
"message": "УРЛ адресе које одговарају регуларном изразу", "disableAllStyles": {
"description": "Option to make the style apply to the entered string as a regular expression" "message": "Искључи све стилове"
}, },
"styleInstall": { "disableStyleLabel": {
"message": "Инсталирати '$stylename$' у Stylus?", "message": "Онемогући"
"description": "Confirmation when installing a style", },
"placeholders": { "editGotoLine": {
"stylename": { "message": "Иди на ред (или line:col)"
"content": "$1" },
} "editStyleHeading": {
} "message": "Уреди стил"
}, },
"linterIssuesHelp": { "editStyleLabel": {
"message": "Проблем пронађен од стране $link$:", "message": "Уреди"
"description": "Help popup message for the selected CSS linter issues block on the style edit page", },
"placeholders": { "editStyleTitle": {
"link": { "message": "Уреди стил $stylename$",
"content": "$1" "placeholders": {
} "stylename": {
} "content": "$1"
}, }
"searchStyles": { }
"message": "Претражи садржај", },
"description": "Label for the search filter textbox on the Manage styles page" "enableStyleLabel": {
}, "message": "Омогући"
"linkGetStyles": { },
"message": "Преузмите стилове", "exportLabel": {
"description": "Help link text on the manage page e.g. https://userstyles.org" "message": "Извези"
}, },
"disableStyleLabel": { "findStylesForSite": {
"message": "Онемогући", "message": "Пронађи још стилова за овај сајт"
"description": "Label for the button to disable a style" },
}, "helpAlt": {
"prefShowBadge": { "message": "Помоћ"
"message": "Прикажи број активних стилова за тренутни сајт на дугмету на алатној траци", },
"description": "Label for the checkbox controlling toolbar badge text." "helpKeyMapCommand": {
}, "message": "Укуцај име команде"
"menuShowBadge": { },
"message": "Прикажи број активних стилова", "helpKeyMapHotkey": {
"description": "Label (must be very short) for the checkbox in the toolbar button context menu controlling toolbar badge text." "message": "Притисни пречицу"
}, },
"cm_lineWrapping": { "importAppendLabel": {
"message": "Преламање текста", "message": "Додај стилу"
"description": "Label for the checkbox controlling word wrap option for the style editor." },
}, "importAppendTooltip": {
"styleCancelEditLabel": { "message": "Додај увезени стил тренутном стилу"
"message": "Назад на управљање", },
"description": "Label for cancel button for style editing" "importLabel": {
}, "message": "Увези"
"styleChangesNotSaved": { },
"message": "Направили сте измене овог стила које нисте сачували.", "importReplaceLabel": {
"description": "Text for the prompt when changes are made to a style and the user tries to leave without saving" "message": "Упиши преко стила"
}, },
"importLabel": { "importReplaceTooltip": {
"message": "Увези", "message": "Одбаци садржај тренутног стила и упиши преко њега увезени стил"
"description": "Label for the button to import a style ('edit' page) or all styles ('manage' page)" },
}, "installUpdate": {
"updateCheckFailServerUnreachable": { "message": "Инсталирај ажурирање"
"message": "Ажурирање није успело: сервер није доступан.", },
"description": "Text that displays when an update check failed because the update server is unreachable" "linkGetHelp": {
}, "message": "Помоћ"
"manageFilters": { },
"message": "Филтери", "linkGetStyles": {
"description": "Label for filters container" "message": "Преузмите стилове"
}, },
"applyAllUpdates": { "linterIssues": {
"message": "Примени сва ажурирања", "message": "Проблеми"
"description": "Label for the button to apply all detected updates" },
}, "linterIssuesHelp": {
"deleteStyleConfirm": { "message": "Проблем пронађен од стране $link$:",
"message": "Да ли сте сигурни да желите да избришете овај стил?", "placeholders": {
"description": "Confirmation before deleting a style" "link": {
}, "content": "$1"
"styleBadRegexp": { }
"message": "Регуларни израз је неисправан.", }
"description": "Validation message for a bad regexp in a style" },
}, "manageFilters": {
"optionsHeading": { "message": "Филтери"
"message": "Опције", },
"description": "Heading for options section on manage page." "manageHeading": {
}, "message": "Инсталирани стилови"
"appliesDisplay": { },
"message": "Примењује се на: $applies$", "manageOnlyEnabled": {
"description": "Text on the manage screen to describe what the style applies to", "message": "Само омогућени стилови"
"placeholders": { },
"applies": { "menuShowBadge": {
"content": "$1" "message": "Прикажи број активних стилова"
} },
} "noStylesForSite": {
}, "message": "Нема инсталираних стилова за овај сајт."
"styleUpdate": { },
"message": "Да ли сте сигурни да желите да ажурирате '$stylename$'?", "openManage": {
"description": "Confirmation when updating a style", "message": "Управљај инсталираним стиловима"
"placeholders": { },
"stylename": { "optionsHeading": {
"content": "$1" "message": "Опције"
} },
} "popupStylesFirst": {
}, "message": "Излистај стилове пре команди у менију дугмета на алатној траци"
"styleSectionsTitle": { },
"message": "Одељци", "prefShowBadge": {
"description": "Title for the style sections section" "message": "Прикажи број активних стилова за тренутни сајт на дугмету на алатној траци"
}, },
"editStyleTitle": { "replace": {
"message": "Уреди стил $stylename$", "message": "Замени"
"description": "Title of the page for editing styles", },
"placeholders": { "replaceAll": {
"stylename": { "message": "Замени све"
"content": "$1" },
} "replaceWith": {
} "message": "Замени са"
}, },
"updateCheckSucceededNoUpdate": { "search": {
"message": "Стил је ажуриран.", "message": "Претражи"
"description": "Text that displays when an update check completed and no update is available" },
}, "searchRegexp": {
"appliesUrlPrefixOption": { "message": "Користи /re/ синтаксу за претрагу регуларним изразом"
"message": "УРЛ адресе које почињу са", },
"description": "Option to make the style apply to the entered string as a URL prefix" "searchStyles": {
}, "message": "Претражи садржај"
"searchRegexp": { },
"message": "Користи /re/ синтаксу за претрагу регуларним изразом", "sectionAdd": {
"description": "Label after the search input field in the editor shown on Ctrl-F" "message": "Додај нови одељак"
}, },
"importReplaceTooltip": { "sectionCode": {
"message": "Одбаци садржај тренутног стила и упиши преко њега увезени стил", "message": "Код"
"description": "Label for the button to import and overwrite current style" },
}, "sectionHelp": {
"popupStylesFirst": { "message": "Одељци вам омогућавају да дефинишете различите делове кода који се примењују на раличите скупове УРЛ-ова у истом стилу. На пример, један исти стил може променити почетну страницу једног сајта на један начин а остатак сајта на други начин."
"message": "Излистај стилове пре команди у менију дугмета на алатној траци", },
"description": "Label for the checkbox controlling section order in the popup." "sectionRemove": {
}, "message": "Уклони одељак"
"sectionHelp": { },
"message": "Одељци вам омогућавају да дефинишете различите делове кода који се примењују на раличите скупове УРЛ-ова у истом стилу. На пример, један исти стил може променити почетну страницу једног сајта на један начин а остатак сајта на други начин.", "styleBadRegexp": {
"description": "Help text for sections" "message": "Регуларни израз је неисправан."
}, },
"noStylesForSite": { "styleBeautify": {
"message": "Нема инсталираних стилова за овај сајт.", "message": "Улепшај"
"description": "Text displayed when no styles are installed for the current site" },
}, "styleCancelEditLabel": {
"appliesDisplayTruncatedSuffix": { "message": "Назад на управљање"
"message": "и још", },
"description": "Text added to appliesDisplay when there are more sites for the style than are displayed" "styleChangesNotSaved": {
}, "message": "Направили сте измене овог стила које нисте сачували."
"appliesRemove": { },
"message": "Уклони", "styleEnabledLabel": {
"description": "Label for the button to remove an 'applies' entry" "message": "Омогућено"
}, },
"styleToMozillaFormatTitle": { "styleFromMozillaFormatPrompt": {
"message": "Стил у Mozilla формату", "message": "Налепи код у Mozilla формату"
"description": "Title of the popup with the style code in Mozilla format, shown after pressing the Export button on Edit style page" },
}, "styleInstall": {
"writeStyleFor": { "message": "Инсталирати '$stylename$' у Stylus?",
"message": "Упиши стил за:", "placeholders": {
"description": "Label for toolbar pop-up that precedes the links to write a new style" "stylename": {
}, "content": "$1"
"replace": { }
"message": "Замени", }
"description": "Label before the replace input field in the editor shown on Ctrl-H" },
}, "styleMissingName": {
"appliesLabel": { "message": "Унесите назив"
"message": "Примењује се на", },
"description": "Label for 'applies to' fields on the edit/add screen" "styleMozillaFormatHeading": {
}, "message": "Mozilla формат"
"openManage": { },
"message": "Управљај инсталираним стиловима", "styleSaveLabel": {
"description": "Link to open the manage page." "message": "Сачувај"
}, },
"updateCheckFailBadResponseCode": { "styleSectionsTitle": {
"message": "Ажурирање није успело: сервер је одговорио кодом $code$.", "message": "Одељци"
"description": "Text that displays when an update check failed because the response code indicates an error", },
"placeholders": { "styleToMozillaFormatHelp": {
"code": { "message": "Mozilla формат кода се може користити у Stylish за Firefox и може се послати на userstyles.org."
"content": "$1" },
} "styleToMozillaFormatTitle": {
} "message": "Стил у Mozilla формату"
}, },
"appliesSpecify": { "styleUpdate": {
"message": "Детаљније", "message": "Да ли сте сигурни да желите да ажурирате '$stylename$'?",
"description": "Label for the button to make a style apply only to specific sites" "placeholders": {
}, "stylename": {
"installUpdate": { "content": "$1"
"message": "Инсталирај ажурирање", }
"description": "Label for the button to install an update for a single style" }
}, },
"styleMozillaFormatHeading": { "stylusUnavailableForURL": {
"message": "Mozilla формат", "message": "Stylus не ради на страницама као што је ова."
"description": "Heading for the section with buttons to import/export Mozilla format of the style" },
}, "undo": {
"sectionRemove": { "message": "Опозови"
"message": "Уклони одељак", },
"description": "Label for the button to remove a section" "undoGlobal": {
}, "message": "Опозови (свеобухватно)"
"disableAllStyles": { },
"message": "Искључи све стилове", "updateAllCheckSucceededNoUpdate": {
"description": "Label for the checkbox that turns all enabled styles off." "message": "Сви стилови су ажурирани."
}, },
"undoGlobal": { "updateCheckFailBadResponseCode": {
"message": "Опозови (свеобухватно)", "message": "Ажурирање није успело: сервер је одговорио кодом $code$.",
"description": "CSS-beautify global Undo button label" "placeholders": {
}, "code": {
"updateCompleted": { "content": "$1"
"message": "Ажурирање је комплетирано.", }
"description": "Text that displays when an update completed" }
}, },
"checkingForUpdate": { "updateCheckFailServerUnreachable": {
"message": "Проверавање...", "message": "Ажурирање није успело: сервер није доступан."
"description": "Text to display when checking a style for an update" },
}, "updateCheckSucceededNoUpdate": {
"sectionCode": { "message": "Стил је ажуриран."
"message": "Код", },
"description": "Label for the code for a section" "updateCompleted": {
}, "message": "Ажурирање је комплетирано."
"cm_smartIndent": { },
"message": "Користи паметно увлачење редова", "writeStyleFor": {
"description": "Label for the checkbox controlling smart indentation option for the style editor." "message": "Упиши стил за:"
}, },
"appliesHelp": { "writeStyleForURL": {
"message": "Употреба 'Примењује се на' одређује опсег УРЛ адреса на које се код у овом одељку примењује.", "message": "ову УРЛ адресу"
"description": "Help text for 'applies to' section" }
},
"linkGetHelp": {
"message": "Помоћ",
"description": "Homepage link text on the manage page e.g. https://add0n.com/stylus.html#features with chat/FAQ/intro/info"
},
"editStyleHeading": {
"message": "Уреди стил",
"description": "Title of the page for editing styles"
},
"appliesUrlOption": {
"message": "УРЛ",
"description": "Option to make the style apply to the entered string as a URL"
},
"stylusUnavailableForURL": {
"message": "Stylus не ради на страницама као што је ова.",
"description": "Note in the toolbar pop-up when on a URL Stylus can't affect"
},
"addStyleTitle": {
"message": "Додај стил",
"description": "Title of the page for adding styles"
},
"importReplaceLabel": {
"message": "Упиши преко стила",
"description": "Label for the button to import and overwrite current style"
},
"dbError": {
"message": "Дошло је до грешке користећи Stylus базу података. Да ли желите да посетите веб страницу са могућим решењима?",
"description": "Prompt when a DB error is encountered"
},
"importAppendTooltip": {
"message": "Додај увезени стил тренутном стилу",
"description": "Tooltip for the button to import a style and append to the existing sections"
},
"helpKeyMapHotkey": {
"message": "Притисни пречицу",
"description": "Placeholder text of inputbox in keymap help popup on the edit style page. Must be very short"
},
"replaceAll": {
"message": "Замени све",
"description": "Label before the replace input field in the editor shown on 'replaceAll' hotkey"
},
"editGotoLine": {
"message": "Иди на ред (или line:col)",
"description": "Go to line or line:column on Ctrl-G in style code editor"
},
"checkAllUpdates": {
"message": "Проверите ажурирања за све стилове",
"description": "Label for the button to check all styles for updates"
},
"confirmNo": {
"message": "Не",
"description": "'No' button in a confirm dialog"
},
"undo": {
"message": "Опозови",
"description": "Button label"
},
"cm_keyMap": {
"message": "Мапа тастера",
"description": "Label for the drop-down list controlling the keymap for the style editor."
},
"cm_indentWithTabs": {
"message": "Користи картице са паметним увлачењем редова",
"description": "Label for the checkbox controlling tabs with smart indentation option for the style editor."
},
"replaceWith": {
"message": "Замени са",
"description": "Label before the replace-with input field in the editor shown on Ctrl-H etc."
},
"deleteStyleLabel": {
"message": "Избриши",
"description": "Label for the button to delete a style"
},
"addStyleLabel": {
"message": "Упиши нови стил",
"description": "Label for the button to go to the add style page"
},
"manageOnlyEnabled": {
"message": "Само омогућени стилови",
"description": "Checkbox to show only enabled styles"
},
"editStyleLabel": {
"message": "Уреди",
"description": "Label for the button to go to the edit style page"
},
"cm_theme": {
"message": "Тема",
"description": "Label for the style editor's CSS theme."
},
"helpKeyMapCommand": {
"message": "Укуцај име команде",
"description": "Placeholder text of inputbox in keymap help popup on the edit style page. Must be very short"
},
"description": {
"message": "Измените стил интернет мреже управљачем корисничких стилова. Stylus вам омогућава да лако инсталирате теме и скинове за многе популарне сајтове.",
"description": "Extension description"
}
} }

View File

@ -1,435 +1,405 @@
{ {
"appliesToEverything": { "addStyleLabel": {
"message": "Allt", "message": "Skriv ny stil"
"description": "Text displayed for styles that apply to all sites" },
}, "addStyleTitle": {
"manageOnlyUsercss": { "message": "Lägg till stil"
"message": "Endast Usercss stilar", },
"description": "Checkbox to show only Usercss styles" "alphaChannel": {
}, "message": "Opacitet"
"exportLabel": { },
"message": "Exportera", "appliesAdd": {
"description": "Label for the button to export a style ('edit' page) or all styles ('manage' page)" "message": "Lägg till"
}, },
"cm_tabSize": { "appliesDisplay": {
"message": "Tabbstorlek", "message": "Gäller för: $applies$",
"description": "Label for the text box controlling tab size option for the style editor." "placeholders": {
}, "applies": {
"enableStyleLabel": { "content": "$1"
"message": "Aktivera", }
"description": "Label for the button to enable a style" }
}, },
"styleMissingName": { "appliesDisplayTruncatedSuffix": {
"message": "Ange ett namn", "message": "och mer"
"description": "Error displayed when user saves without providing a name" },
}, "appliesDomainOption": {
"genericHistoryLabel": { "message": "URL:er på domänen"
"message": "Historik", },
"description": "Used in various places to show a history log of something" "appliesHelp": {
}, "message": "Använd 'Gäller för' alternativet för att begränsa vilka URL:er koden i denna sektion gäller för."
"appliesDomainOption": { },
"message": "URL:er på domänen", "appliesLabel": {
"description": "Option to make the style apply to the entered string as a domain" "message": "Gäller för"
}, },
"checkForUpdate": { "appliesRegexpOption": {
"message": "Leta efter uppdatering", "message": "URL:er som matchar regexp:en"
"description": "Label for the button to check a single style for an update" },
}, "appliesRemove": {
"styleFromMozillaFormatError": { "message": "Ta bort"
"message": "Import av Mozilla-format misslyckades", },
"description": "Label for the import error" "appliesSpecify": {
}, "message": "Specificera"
"importAppendLabel": { },
"message": "Lägg till i stil", "appliesToEverything": {
"description": "Label for the button to import a style and append to the existing sections" "message": "Allt"
}, },
"updateAllCheckSucceededNoUpdate": { "appliesUrlPrefixOption": {
"message": "Alla stilar är fullt uppdaterade.", "message": "URL:er som börjar på"
"description": "Text that displays when an update all check completed and no updates are available" },
}, "applyAllUpdates": {
"styleFromMozillaFormatPrompt": { "message": "Verkställ alla uppdateringar"
"message": "Klistra in koden i Mozilla-formatet", },
"description": "Prompt in the dialog displayed after clicking 'Import from Mozilla format' button" "bckpInstStyles": {
}, "message": "Exportera stilar"
"helpAlt": { },
"message": "Hjälp", "checkAllUpdates": {
"description": "Alternate text for help buttons" "message": "Sök efter uppdateringar"
}, },
"search": { "checkForUpdate": {
"message": "Sök", "message": "Leta efter uppdatering"
"description": "Label before the search input field in the editor shown on Ctrl-F" },
}, "checkingForUpdate": {
"genericEnabledLabel": { "message": "Letar..."
"message": "Aktiverad", },
"description": "Used in various lists/options to indicate that something is enabled" "cm_indentWithTabs": {
}, "message": "Använd tabbar med smart indrag"
"confirmYes": { },
"message": "Ja", "cm_keyMap": {
"description": "'Yes' button in a confirm dialog" "message": "Nyckelkarta"
}, },
"findStylesForSite": { "cm_lineWrapping": {
"message": "Hitta fler stilar för denna sida", "message": "Radbrytning"
"description": "Text for a link that gets a list of styles for the current site" },
}, "cm_smartIndent": {
"manageHeading": { "message": "Använd smart indrag"
"message": "Installerade Stilar", },
"description": "Heading for the manage page" "cm_tabSize": {
}, "message": "Tabbstorlek"
"styleEnabledLabel": { },
"message": "Aktiverad", "cm_theme": {
"description": "Label for the enabled state of styles" "message": "Tema"
}, },
"styleToMozillaFormatHelp": { "confirmClose": {
"message": "Mozilla-formatet av koden fungerar i Stylish till Firefox samt vid uppladdandet till userstyles.org.", "message": "Stäng"
"description": "Help info for the Mozilla format header section that converts the code to/from Mozilla format" },
}, "confirmNo": {
"sectionAdd": { "message": "Nej"
"message": "Lägg till ytterligare en sektion", },
"description": "Label for the button to add a section" "confirmSave": {
}, "message": "Spara"
"styleSaveLabel": { },
"message": "Spara", "confirmYes": {
"description": "Label for save button for style editing" "message": "Ja"
}, },
"writeStyleForURL": { "dbError": {
"message": "denna URL", "message": "Ett fel inträffades vid hanteringen av Stylus-databasen. Skulle du vilja besöka en sida med eventuella lösningar?"
"description": "Text for link in toolbar pop-up to write a new style for the current URL" },
}, "deleteStyleConfirm": {
"appliesAdd": { "message": "Är du säker på att du vill ta bort denna stil?"
"message": "Lägg till", },
"description": "Label for the button to add an 'applies' entry" "deleteStyleLabel": {
}, "message": "Ta bort"
"appliesRegexpOption": { },
"message": "URL:er som matchar regexp:en", "description": {
"description": "Option to make the style apply to the entered string as a regular expression" "message": "Style:a om webben med Stylus, en användarstils-hanterare. Stylus låter dig enkelt installera teman och skal för många populära sidor."
}, },
"styleInstall": { "disableAllStyles": {
"message": "Installera '$stylename$' in i Stylus?", "message": "Stäng av alla stilar"
"description": "Confirmation when installing a style", },
"placeholders": { "disableStyleLabel": {
"stylename": { "message": "Inaktivera"
"content": "$1" },
} "editGotoLine": {
} "message": "Gå till rad (eller rad:kol)"
}, },
"manageOnlyLocal": { "editStyleHeading": {
"message": "Endast lokalt skapade stilar", "message": "Ändra i Stil"
"description": "Checkbox to show only locally created styles i.e. non-updatable" },
}, "editStyleLabel": {
"searchStyles": { "message": "Ändra"
"message": "Sök innehåll", },
"description": "Label for the search filter textbox on the Manage styles page" "editStyleTitle": {
}, "message": "Ändra i Stil $stylename$",
"manageOnlyNonUsercss": { "placeholders": {
"message": "Endast icke-Usercss stilar", "stylename": {
"description": "Checkbox to show only non-Usercss (standard) styles" "content": "$1"
}, }
"linkGetStyles": { }
"message": "Skaffa stilar", },
"description": "Help link text on the manage page e.g. https://userstyles.org" "enableStyleLabel": {
}, "message": "Aktivera"
"disableStyleLabel": { },
"message": "Inaktivera", "exportLabel": {
"description": "Label for the button to disable a style" "message": "Exportera"
}, },
"prefShowBadge": { "externalUsercssDocument": {
"message": "Visa antalet aktiva stilar för den nuvarande sidan på verktygsfältsikonen", "message": "Dokumentation för Usercss"
"description": "Label for the checkbox controlling toolbar badge text." },
}, "findStyles": {
"menuShowBadge": { "message": "Hitta stilar"
"message": "Visa antalet aktiva stilar", },
"description": "Label (must be very short) for the checkbox in the toolbar button context menu controlling toolbar badge text." "findStylesForSite": {
}, "message": "Hitta fler stilar för denna sida"
"cm_lineWrapping": { },
"message": "Radbrytning", "genericAdd": {
"description": "Label for the checkbox controlling word wrap option for the style editor." "message": "Lägg till"
}, },
"styleCancelEditLabel": { "genericDisabledLabel": {
"message": "Återgå till hantera", "message": "Inaktiverad"
"description": "Label for cancel button for style editing" },
}, "genericEnabledLabel": {
"styleChangesNotSaved": { "message": "Aktiverad"
"message": "Du har gjort ändringar i denna stil utan att spara.", },
"description": "Text for the prompt when changes are made to a style and the user tries to leave without saving" "genericHistoryLabel": {
}, "message": "Historik"
"importLabel": { },
"message": "Importera", "genericNext": {
"description": "Label for the button to import a style ('edit' page) or all styles ('manage' page)" "message": "Nästa"
}, },
"updateCheckFailServerUnreachable": { "genericPrevious": {
"message": "Uppdateringen misslyckades: server onåbar.", "message": "Föregående"
"description": "Text that displays when an update check failed because the update server is unreachable" },
}, "genericResetLabel": {
"manageFilters": { "message": "Återställ"
"message": "Filter", },
"description": "Label for filters container" "helpAlt": {
}, "message": "Hjälp"
"applyAllUpdates": { },
"message": "Verkställ alla uppdateringar", "importAppendLabel": {
"description": "Label for the button to apply all detected updates" "message": "Lägg till i stil"
}, },
"deleteStyleConfirm": { "importAppendTooltip": {
"message": "Är du säker på att du vill ta bort denna stil?", "message": "Lägg till den importerad stilen i aktuell stil"
"description": "Confirmation before deleting a style" },
}, "importLabel": {
"styleBadRegexp": { "message": "Importera"
"message": "Regexp:en är ogiltig", },
"description": "Validation message for a bad regexp in a style" "importReplaceLabel": {
}, "message": "Ersätt stil"
"optionsHeading": { },
"message": "Alternativ", "installUpdate": {
"description": "Heading for options section on manage page." "message": "Installera uppdatering"
}, },
"appliesDisplay": { "linkGetHelp": {
"message": "Gäller för: $applies$", "message": "Hjälp"
"description": "Text on the manage screen to describe what the style applies to", },
"placeholders": { "linkGetStyles": {
"applies": { "message": "Skaffa stilar"
"content": "$1" },
} "linkTranslate": {
} "message": "Översätt"
}, },
"styleUpdate": { "manageFavicons": {
"message": "Är du säker på att du vill uppdatera '$stylename$'?", "message": "Ikoner i 'Gäller för' kolumnen"
"description": "Confirmation when updating a style", },
"placeholders": { "manageFaviconsGray": {
"stylename": { "message": "Nedtonade"
"content": "$1" },
} "manageFaviconsHelp": {
} "message": "Stylus använder en extern tjänst https://www.google.com/s2/favicons"
}, },
"styleSectionsTitle": { "manageFilters": {
"message": "Sektioner", "message": "Filter"
"description": "Title for the style sections section" },
}, "manageHeading": {
"optionsAdvancedNewStyleAsUsercss": { "message": "Installerade Stilar"
"message": "Skriv ny stil som Usercss", },
"description": "" "manageNewStyleAsUsercss": {
}, "message": "som Usercss"
"editStyleTitle": { },
"message": "Ändra i Stil $stylename$", "manageOnlyDisabled": {
"description": "Title of the page for editing styles", "message": "Endast inaktiverade stilar"
"placeholders": { },
"stylename": { "manageOnlyEnabled": {
"content": "$1" "message": "Endast aktiverade stilar"
} },
} "manageOnlyExternal": {
}, "message": "Endast externa stilar"
"updateCheckSucceededNoUpdate": { },
"message": "Stilen är fullt uppdaterad.", "manageOnlyLocal": {
"description": "Text that displays when an update check completed and no update is available" "message": "Endast lokalt skapade stilar"
}, },
"appliesUrlPrefixOption": { "manageOnlyNonUsercss": {
"message": "URL:er som börjar på", "message": "Endast icke-Usercss stilar"
"description": "Option to make the style apply to the entered string as a URL prefix" },
}, "manageOnlyUsercss": {
"searchRegexp": { "message": "Endast Usercss stilar"
"message": "Använd /re/ för regexp-sökning", },
"description": "Label after the search input field in the editor shown on Ctrl-F" "menuShowBadge": {
}, "message": "Visa antalet aktiva stilar"
"popupStylesFirst": { },
"message": "Lista stilar före kommandon i verktygsfältets knappmeny", "noStylesForSite": {
"description": "Label for the checkbox controlling section order in the popup." "message": "Inga stilar installerade för denna sida."
}, },
"sectionHelp": { "openManage": {
"message": "Sektioner låter dig definiera olika sorters kod som påverkar olika URL:er i samma stil. Till exempel, en stil kan ändra en viss hemsida på ett sätt, samtidigt som den ändrar andra delar på en helt annan sida.", "message": "Hantera installerade stilar"
"description": "Help text for sections" },
}, "openOptionsPopup": {
"noStylesForSite": { "message": "Alternativ"
"message": "Inga stilar installerade för denna sida.", },
"description": "Text displayed when no styles are installed for the current site" "openStylesManager": {
}, "message": "Öppna stilhanteraren"
"appliesDisplayTruncatedSuffix": { },
"message": "och mer", "optionsAdvancedNewStyleAsUsercss": {
"description": "Text added to appliesDisplay when there are more sites for the style than are displayed" "message": "Skriv ny stil som Usercss"
}, },
"appliesRemove": { "optionsCheck": {
"message": "Ta bort", "message": "Uppdatera stilar"
"description": "Label for the button to remove an 'applies' entry" },
}, "optionsCheckUpdate": {
"styleToMozillaFormatTitle": { "message": "Leta efter och installera alla tillgängliga uppdateringar"
"message": "Stil i Mozilla-format", },
"description": "Title of the popup with the style code in Mozilla format, shown after pressing the Export button on Edit style page" "optionsHeading": {
}, "message": "Alternativ"
"writeStyleFor": { },
"message": "Skriv stil för:", "optionsOpenManager": {
"description": "Label for toolbar pop-up that precedes the links to write a new style" "message": "Hantera stilar"
}, },
"replace": { "optionsReset": {
"message": "Ersätt", "message": "Återställ alternativen till standard"
"description": "Label before the replace input field in the editor shown on Ctrl-H" },
}, "optionsResetButton": {
"appliesLabel": { "message": "Återställ alternativ"
"message": "Gäller för", },
"description": "Label for 'applies to' fields on the edit/add screen" "paginationNext": {
}, "message": "Nästa sida"
"openManage": { },
"message": "Hantera installerade stilar", "paginationPrevious": {
"description": "Link to open the manage page." "message": "Föregående sida"
}, },
"updateCheckFailBadResponseCode": { "popupStylesFirst": {
"message": "Uppdateringen misslyckades: servern svarade med kod $code$.", "message": "Lista stilar före kommandon i verktygsfältets knappmeny"
"description": "Text that displays when an update check failed because the response code indicates an error", },
"placeholders": { "prefShowBadge": {
"code": { "message": "Visa antalet aktiva stilar för den nuvarande sidan på verktygsfältsikonen"
"content": "$1" },
} "replace": {
} "message": "Ersätt"
}, },
"appliesSpecify": { "replaceAll": {
"message": "Specificera", "message": "Ersätt alla"
"description": "Label for the button to make a style apply only to specific sites" },
}, "replaceWith": {
"installUpdate": { "message": "Ersätt med"
"message": "Installera uppdatering", },
"description": "Label for the button to install an update for a single style" "retrieveBckp": {
}, "message": "Importera stilar"
"styleMozillaFormatHeading": { },
"message": "Mozilla-format", "search": {
"description": "Heading for the section with buttons to import/export Mozilla format of the style" "message": "Sök"
}, },
"sectionRemove": { "searchRegexp": {
"message": "Ta bort sektion", "message": "Använd /re/ för regexp-sökning"
"description": "Label for the button to remove a section" },
}, "searchStyles": {
"disableAllStyles": { "message": "Sök innehåll"
"message": "Stäng av alla stilar", },
"description": "Label for the checkbox that turns all enabled styles off." "sectionAdd": {
}, "message": "Lägg till ytterligare en sektion"
"undoGlobal": { },
"message": "Ångra i alla sektioner", "sectionCode": {
"description": "CSS-beautify global Undo button label" "message": "Kod"
}, },
"updateCompleted": { "sectionHelp": {
"message": "Uppdatering slutförd.", "message": "Sektioner låter dig definiera olika sorters kod som påverkar olika URL:er i samma stil. Till exempel, en stil kan ändra en viss hemsida på ett sätt, samtidigt som den ändrar andra delar på en helt annan sida."
"description": "Text that displays when an update completed" },
}, "sectionRemove": {
"checkingForUpdate": { "message": "Ta bort sektion"
"message": "Letar...", },
"description": "Text to display when checking a style for an update" "sectionRestore": {
}, "message": "Återställ borttagen sektion"
"sectionCode": { },
"message": "Kod", "shortcuts": {
"description": "Label for the code for a section" "message": "Genvägar"
}, },
"externalUsercssDocument": { "shortcutsNote": {
"message": "Dokumentation för Usercss", "message": "Ställ in tangentbordsgenvägar"
"description": "Label for the external link to usercss documentation" },
}, "styleBadRegexp": {
"cm_smartIndent": { "message": "Regexp:en är ogiltig"
"message": "Använd smart indrag", },
"description": "Label for the checkbox controlling smart indentation option for the style editor." "styleCancelEditLabel": {
}, "message": "Återgå till hantera"
"appliesHelp": { },
"message": "Använd 'Gäller för' alternativet för att begränsa vilka URL:er koden i denna sektion gäller för.", "styleChangesNotSaved": {
"description": "Help text for 'applies to' section" "message": "Du har gjort ändringar i denna stil utan att spara."
}, },
"linkGetHelp": { "styleEnabledLabel": {
"message": "Hjälp", "message": "Aktiverad"
"description": "Homepage link text on the manage page e.g. https://add0n.com/stylus.html#features with chat/FAQ/intro/info" },
}, "styleFromMozillaFormatError": {
"editStyleHeading": { "message": "Import av Mozilla-format misslyckades"
"message": "Ändra i Stil", },
"description": "Title of the page for editing styles" "styleFromMozillaFormatPrompt": {
}, "message": "Klistra in koden i Mozilla-formatet"
"manageOnlyDisabled": { },
"message": "Endast inaktiverade stilar", "styleInstall": {
"description": "Checkbox to show only disabled styles" "message": "Installera '$stylename$' in i Stylus?",
}, "placeholders": {
"stylusUnavailableForURL": { "stylename": {
"message": "Stylus fungerar inte på sidor som denna.", "content": "$1"
"description": "Note in the toolbar pop-up when on a URL Stylus can't affect" }
}, }
"addStyleTitle": { },
"message": "Lägg till stil", "styleMissingName": {
"description": "Title of the page for adding styles" "message": "Ange ett namn"
}, },
"importReplaceLabel": { "styleMozillaFormatHeading": {
"message": "Ersätt stil", "message": "Mozilla-format"
"description": "Label for the button to import and overwrite current style" },
}, "styleSaveLabel": {
"dbError": { "message": "Spara"
"message": "Ett fel inträffades vid hanteringen av Stylus-databasen. Skulle du vilja besöka en sida med eventuella lösningar?", },
"description": "Prompt when a DB error is encountered" "styleSectionsTitle": {
}, "message": "Sektioner"
"importAppendTooltip": { },
"message": "Lägg till den importerad stilen i aktuell stil", "styleToMozillaFormatHelp": {
"description": "Tooltip for the button to import a style and append to the existing sections" "message": "Mozilla-formatet av koden fungerar i Stylish till Firefox samt vid uppladdandet till userstyles.org."
}, },
"manageOnlyExternal": { "styleToMozillaFormatTitle": {
"message": "Endast externa stilar", "message": "Stil i Mozilla-format"
"description": "Checkbox to show only externally installed styles i.e. updatable" },
}, "styleUpdate": {
"replaceAll": { "message": "Är du säker på att du vill uppdatera '$stylename$'?",
"message": "Ersätt alla", "placeholders": {
"description": "Label before the replace input field in the editor shown on 'replaceAll' hotkey" "stylename": {
}, "content": "$1"
"editGotoLine": { }
"message": "Gå till rad (eller rad:kol)", }
"description": "Go to line or line:column on Ctrl-G in style code editor" },
}, "stylusUnavailableForURL": {
"checkAllUpdates": { "message": "Stylus fungerar inte på sidor som denna."
"message": "Sök efter uppdateringar", },
"description": "Label for the button to check all styles for updates" "undo": {
}, "message": "Ångra"
"manageNewStyleAsUsercss": { },
"message": "som Usercss", "undoGlobal": {
"description": "VERY SHORT label for the checkbox next to the 'Write new style' button in the style manager" "message": "Ångra i alla sektioner"
}, },
"confirmNo": { "updateAllCheckSucceededNoUpdate": {
"message": "Nej", "message": "Alla stilar är fullt uppdaterade."
"description": "'No' button in a confirm dialog" },
}, "updateCheckFailBadResponseCode": {
"undo": { "message": "Uppdateringen misslyckades: servern svarade med kod $code$.",
"message": "Ångra", "placeholders": {
"description": "Button label" "code": {
}, "content": "$1"
"cm_keyMap": { }
"message": "Nyckelkarta", }
"description": "Label for the drop-down list controlling the keymap for the style editor." },
}, "updateCheckFailServerUnreachable": {
"confirmSave": { "message": "Uppdateringen misslyckades: server onåbar."
"message": "Spara", },
"description": "'Save' button in a confirm dialog" "updateCheckSucceededNoUpdate": {
}, "message": "Stilen är fullt uppdaterad."
"genericDisabledLabel": { },
"message": "Inaktiverad", "updateCompleted": {
"description": "Used in various lists/options to indicate that something is disabled" "message": "Uppdatering slutförd."
}, },
"cm_indentWithTabs": { "writeStyleFor": {
"message": "Använd tabbar med smart indrag", "message": "Skriv stil för:"
"description": "Label for the checkbox controlling tabs with smart indentation option for the style editor." },
}, "writeStyleForURL": {
"replaceWith": { "message": "denna URL"
"message": "Ersätt med", }
"description": "Label before the replace-with input field in the editor shown on Ctrl-H etc."
},
"deleteStyleLabel": {
"message": "Ta bort",
"description": "Label for the button to delete a style"
},
"addStyleLabel": {
"message": "Skriv ny stil",
"description": "Label for the button to go to the add style page"
},
"manageOnlyEnabled": {
"message": "Endast aktiverade stilar",
"description": "Checkbox to show only enabled styles"
},
"editStyleLabel": {
"message": "Ändra",
"description": "Label for the button to go to the edit style page"
},
"cm_theme": {
"message": "Tema",
"description": "Label for the style editor's CSS theme."
},
"description": {
"message": "Style:a om webben med Stylus, en användarstils-hanterare. Stylus låter dig enkelt installera teman och skal för många populära sidor.",
"description": "Extension description"
},
"confirmClose": {
"message": "Stäng",
"description": "'Close' button in a confirm dialog"
}
} }

View File

@ -1,71 +1,55 @@
{ {
"appliesToEverything": { "addStyleLabel": {
"message": "అన్నిటికీ", "message": "క్రొత్త స్టైల్ వ్రాయండి"
"description": "Text displayed for styles that apply to all sites" },
}, "appliesAdd": {
"enableStyleLabel": { "message": "చేర్చు"
"message": "చేతనించు", },
"description": "Label for the button to enable a style" "appliesDisplay": {
}, "message": "వేటికి వర్తిస్తుంది; $applies$",
"helpAlt": { "placeholders": {
"message": "సహాయం", "applies": {
"description": "Alternate text for help buttons" "content": "$1"
}, }
"manageHeading": { }
"message": "స్థాపిత శైలులు", },
"description": "Heading for the manage page" "appliesDisplayTruncatedSuffix": {
}, "message": "ఇంకా మరిన్ని"
"styleSaveLabel": { },
"message": "భద్రపరచు", "appliesRemove": {
"description": "Label for save button for style editing" "message": "తొలగించు"
}, },
"appliesAdd": { "appliesToEverything": {
"message": "చేర్చు", "message": "అన్నిటికీ"
"description": "Label for the button to add an 'applies' entry" },
}, "deleteStyleConfirm": {
"disableStyleLabel": { "message": "మీరు నజంగానే ఈ శైలిని తొలగించాలనుకుంటున్నారా?"
"message": "అచేతనించు", },
"description": "Label for the button to disable a style" "deleteStyleLabel": {
}, "message": "తొలగించు"
"deleteStyleConfirm": { },
"message": "మీరు నజంగానే ఈ శైలిని తొలగించాలనుకుంటున్నారా?", "disableStyleLabel": {
"description": "Confirmation before deleting a style" "message": "అచేతనించు"
}, },
"appliesDisplay": { "editStyleLabel": {
"message": "వేటికి వర్తిస్తుంది; $applies$", "message": "మార్చు"
"description": "Text on the manage screen to describe what the style applies to", },
"placeholders": { "enableStyleLabel": {
"applies": { "message": "చేతనించు"
"content": "$1" },
} "helpAlt": {
} "message": "సహాయం"
}, },
"styleSectionsTitle": { "manageHeading": {
"message": "విభాగాలు", "message": "స్థాపిత శైలులు"
"description": "Title for the style sections section" },
}, "manageTitle": {
"appliesDisplayTruncatedSuffix": { "message": "స్టైలిష్"
"message": "ఇంకా మరిన్ని", },
"description": "Text added to appliesDisplay when there are more sites for the style than are displayed" "styleSaveLabel": {
}, "message": "భద్రపరచు"
"appliesRemove": { },
"message": "తొలగించు", "styleSectionsTitle": {
"description": "Label for the button to remove an 'applies' entry" "message": "విభాగాలు"
}, }
"manageTitle": {
"message": "స్టైలిష్",
"description": "Title for the manage page"
},
"deleteStyleLabel": {
"message": "తొలగించు",
"description": "Label for the button to delete a style"
},
"addStyleLabel": {
"message": "క్రొత్త స్టైల్ వ్రాయండి",
"description": "Label for the button to go to the add style page"
},
"editStyleLabel": {
"message": "మార్చు",
"description": "Label for the button to go to the edit style page"
}
} }

View File

@ -1,206 +1,160 @@
{ {
"appliesToEverything": { "addStyleLabel": {
"message": "Her şey", "message": "Yeni stil oluşturun"
"description": "Text displayed for styles that apply to all sites" },
}, "addStyleTitle": {
"enableStyleLabel": { "message": "Stil Ekleyin"
"message": "Etkinleştir", },
"description": "Label for the button to enable a style" "appliesAdd": {
}, "message": "Ekleyin"
"styleMissingName": { },
"message": "Bir ad girin", "appliesDisplay": {
"description": "Error displayed when user saves without providing a name" "message": "Şuraya uygulanır: $applies$",
}, "placeholders": {
"appliesDomainOption": { "applies": {
"message": "Alan adındaki URLler", "content": "$1"
"description": "Option to make the style apply to the entered string as a domain" }
}, }
"checkForUpdate": { },
"message": "Güncellemeleri denetle", "appliesDisplayTruncatedSuffix": {
"description": "Label for the button to check a single style for an update" "message": "ve diğerleri"
}, },
"helpAlt": { "appliesDomainOption": {
"message": "Yardım", "message": "Alan adındaki URLler"
"description": "Alternate text for help buttons" },
}, "appliesHelp": {
"findStylesForSite": { "message": "Bu bölümdeki kodun hangi URLlere uygulanacağını sınırlamak için 'Şuraya uygulanır' denetimlerini kullanın."
"message": "Bu site için başka stiller bul", },
"description": "Text for a link that gets a list of styles for the current site" "appliesLabel": {
}, "message": "Şuraya uygulanır"
"manageHeading": { },
"message": "Yüklü Stiller", "appliesRegexpOption": {
"description": "Heading for the manage page" "message": "regexp ile eşleşen URL'ler"
}, },
"styleEnabledLabel": { "appliesRemove": {
"message": "Etkin", "message": "Kaldır"
"description": "Label for the enabled state of styles" },
}, "appliesSpecify": {
"styleToMozillaFormatHelp": { "message": "Belirt"
"message": "Kodun Mozilla biçimi, Firefox için Stylish ile kullanılabilir ve userstyles.org sitesine gönderilebilir.", },
"description": "Help info for the Mozilla format header section that converts the code to/from Mozilla format" "appliesToEverything": {
}, "message": "Her şey"
"sectionAdd": { },
"message": "Başka bölüm ekle", "appliesUrlPrefixOption": {
"description": "Label for the button to add a section" "message": "Şununla başlayan URL'ler:"
}, },
"styleSaveLabel": { "checkAllUpdates": {
"message": "Kaydet", "message": "Tüm stiller için güncellemeleri denetle"
"description": "Label for save button for style editing" },
}, "checkForUpdate": {
"appliesAdd": { "message": "Güncellemeleri denetle"
"message": "Ekleyin", },
"description": "Label for the button to add an 'applies' entry" "checkingForUpdate": {
}, "message": "Kontrol ediliyor..."
"appliesRegexpOption": { },
"message": "regexp ile eşleşen URL'ler", "deleteStyleConfirm": {
"description": "Option to make the style apply to the entered string as a regular expression" "message": "Bu stili silmek istediğinizden emin misiniz?"
}, },
"styleInstall": { "deleteStyleLabel": {
"message": "'$stylename$' Stylus'e yüklensin mi?", "message": "Sil"
"description": "Confirmation when installing a style", },
"placeholders": { "description": {
"stylename": { "message": "Kullanıcı stil yöneticisi Stylus ile Web'in stilini yenileyin. Stylus Google, Facebook, YouTube, Orkut ve diğer pek çok site için temalar ve görünüm yüklemenize imkân tanır."
"content": "$1" },
} "disableStyleLabel": {
} "message": "Devre dışı bırak"
}, },
"disableStyleLabel": { "editStyleHeading": {
"message": "Devre dışı bırak", "message": "Stili Düzenle"
"description": "Label for the button to disable a style" },
}, "editStyleLabel": {
"styleCancelEditLabel": { "message": "Düzenle"
"message": "Yönetim sayfasına dön", },
"description": "Label for cancel button for style editing" "editStyleTitle": {
}, "message": "$stylename$ Stilini Düzenleyin",
"styleChangesNotSaved": { "placeholders": {
"message": "Bu stilde yaptığınız, kaydedilmemiş değişiklikler var.", "stylename": {
"description": "Text for the prompt when changes are made to a style and the user tries to leave without saving" "content": "$1"
}, }
"updateCheckFailServerUnreachable": { }
"message": "Güncellenemedi: sunucuya erişilemiyor.", },
"description": "Text that displays when an update check failed because the update server is unreachable" "enableStyleLabel": {
}, "message": "Etkinleştir"
"deleteStyleConfirm": { },
"message": "Bu stili silmek istediğinizden emin misiniz?", "findStylesForSite": {
"description": "Confirmation before deleting a style" "message": "Bu site için başka stiller bul"
}, },
"appliesDisplay": { "helpAlt": {
"message": "Şuraya uygulanır: $applies$", "message": "Yardım"
"description": "Text on the manage screen to describe what the style applies to", },
"placeholders": { "installUpdate": {
"applies": { "message": "Güncellemeyi yükle"
"content": "$1" },
} "manageHeading": {
} "message": "Yüklü Stiller"
}, },
"styleSectionsTitle": { "noStylesForSite": {
"message": "Bölümler", "message": "Bu site için hiçbir stil yüklenmedi."
"description": "Title for the style sections section" },
}, "openManage": {
"editStyleTitle": { "message": "Yüklü stilleri yönet"
"message": "$stylename$ Stilini Düzenleyin", },
"description": "Title of the page for editing styles", "sectionAdd": {
"placeholders": { "message": "Başka bölüm ekle"
"stylename": { },
"content": "$1" "sectionCode": {
} "message": "Kod"
} },
}, "sectionHelp": {
"updateCheckSucceededNoUpdate": { "message": "Bölümler, aynı stil içindeki farklı URL kümelerine uygulanacak farklı kod parçaları tanımlamanıza olanak sağlar. Örneğin, tek bir stil bir sitenin ana sayfasını belirli bir şekilde değiştirirken, sayfanın kalan kısmını farklı bir şekilde değiştirebilir."
"message": "Stil güncel.", },
"description": "Text that displays when an update check completed and no update is available" "sectionRemove": {
}, "message": "Bölümü kaldır"
"appliesUrlPrefixOption": { },
"message": "Şununla başlayan URL'ler:", "styleCancelEditLabel": {
"description": "Option to make the style apply to the entered string as a URL prefix" "message": "Yönetim sayfasına dön"
}, },
"sectionHelp": { "styleChangesNotSaved": {
"message": "Bölümler, aynı stil içindeki farklı URL kümelerine uygulanacak farklı kod parçaları tanımlamanıza olanak sağlar. Örneğin, tek bir stil bir sitenin ana sayfasını belirli bir şekilde değiştirirken, sayfanın kalan kısmını farklı bir şekilde değiştirebilir.", "message": "Bu stilde yaptığınız, kaydedilmemiş değişiklikler var."
"description": "Help text for sections" },
}, "styleEnabledLabel": {
"noStylesForSite": { "message": "Etkin"
"message": "Bu site için hiçbir stil yüklenmedi.", },
"description": "Text displayed when no styles are installed for the current site" "styleInstall": {
}, "message": "'$stylename$' Stylus'e yüklensin mi?",
"appliesDisplayTruncatedSuffix": { "placeholders": {
"message": "ve diğerleri", "stylename": {
"description": "Text added to appliesDisplay when there are more sites for the style than are displayed" "content": "$1"
}, }
"appliesRemove": { }
"message": "Kaldır", },
"description": "Label for the button to remove an 'applies' entry" "styleMissingName": {
}, "message": "Bir ad girin"
"appliesLabel": { },
"message": "Şuraya uygulanır", "styleSaveLabel": {
"description": "Label for 'applies to' fields on the edit/add screen" "message": "Kaydet"
}, },
"openManage": { "styleSectionsTitle": {
"message": "Yüklü stilleri yönet", "message": "Bölümler"
"description": "Link to open the manage page." },
}, "styleToMozillaFormatHelp": {
"updateCheckFailBadResponseCode": { "message": "Kodun Mozilla biçimi, Firefox için Stylish ile kullanılabilir ve userstyles.org sitesine gönderilebilir."
"message": "Güncellenemedi: sunucu yanıt olarak $code$ kodunu gönderdi.", },
"description": "Text that displays when an update check failed because the response code indicates an error", "updateCheckFailBadResponseCode": {
"placeholders": { "message": "Güncellenemedi: sunucu yanıt olarak $code$ kodunu gönderdi.",
"code": { "placeholders": {
"content": "$1" "code": {
} "content": "$1"
} }
}, }
"appliesSpecify": { },
"message": "Belirt", "updateCheckFailServerUnreachable": {
"description": "Label for the button to make a style apply only to specific sites" "message": "Güncellenemedi: sunucuya erişilemiyor."
}, },
"installUpdate": { "updateCheckSucceededNoUpdate": {
"message": "Güncellemeyi yükle", "message": "Stil güncel."
"description": "Label for the button to install an update for a single style" },
}, "updateCompleted": {
"sectionRemove": { "message": "Güncelleme tamamlandı."
"message": "Bölümü kaldır", }
"description": "Label for the button to remove a section"
},
"updateCompleted": {
"message": "Güncelleme tamamlandı.",
"description": "Text that displays when an update completed"
},
"checkingForUpdate": {
"message": "Kontrol ediliyor...",
"description": "Text to display when checking a style for an update"
},
"sectionCode": {
"message": "Kod",
"description": "Label for the code for a section"
},
"appliesHelp": {
"message": "Bu bölümdeki kodun hangi URLlere uygulanacağını sınırlamak için 'Şuraya uygulanır' denetimlerini kullanın.",
"description": "Help text for 'applies to' section"
},
"editStyleHeading": {
"message": "Stili Düzenle",
"description": "Title of the page for editing styles"
},
"addStyleTitle": {
"message": "Stil Ekleyin",
"description": "Title of the page for adding styles"
},
"checkAllUpdates": {
"message": "Tüm stiller için güncellemeleri denetle",
"description": "Label for the button to check all styles for updates"
},
"deleteStyleLabel": {
"message": "Sil",
"description": "Label for the button to delete a style"
},
"addStyleLabel": {
"message": "Yeni stil oluşturun",
"description": "Label for the button to go to the add style page"
},
"editStyleLabel": {
"message": "Düzenle",
"description": "Label for the button to go to the edit style page"
},
"description": {
"message": "Kullanıcı stil yöneticisi Stylus ile Web'in stilini yenileyin. Stylus Google, Facebook, YouTube, Orkut ve diğer pek çok site için temalar ve görünüm yüklemenize imkân tanır.",
"description": "Extension description"
}
} }

View File

@ -1,219 +1,171 @@
{ {
"appliesToEverything": { "addStyleLabel": {
"message": "所有站点", "message": "编写新的样式"
"description": "Text displayed for styles that apply to all sites" },
}, "addStyleTitle": {
"enableStyleLabel": { "message": "添加样式"
"message": "启用", },
"description": "Label for the button to enable a style" "appliesAdd": {
}, "message": "添加"
"styleMissingName": { },
"message": "请输入名称", "appliesDisplay": {
"description": "Error displayed when user saves without providing a name" "message": "应用到: $applies$",
}, "placeholders": {
"appliesDomainOption": { "applies": {
"message": "指定域上的URL", "content": "$1"
"description": "Option to make the style apply to the entered string as a domain" }
}, }
"checkForUpdate": { },
"message": "检查更新", "appliesDisplayTruncatedSuffix": {
"description": "Label for the button to check a single style for an update" "message": "以及更多"
}, },
"helpAlt": { "appliesDomainOption": {
"message": "帮助", "message": "指定域上的URL"
"description": "Alternate text for help buttons" },
}, "appliesHelp": {
"findStylesForSite": { "message": "使用“应用到”来控制这个样式应用到哪些地址上。"
"message": "查找更多适合此站点的样式", },
"description": "Text for a link that gets a list of styles for the current site" "appliesLabel": {
}, "message": "应用到"
"manageHeading": { },
"message": "已安装的样式", "appliesRegexpOption": {
"description": "Heading for the manage page" "message": "匹配正则的URL"
}, },
"styleEnabledLabel": { "appliesRemove": {
"message": "已启用", "message": "移除"
"description": "Label for the enabled state of styles" },
}, "appliesSpecify": {
"styleToMozillaFormatHelp": { "message": "指定站点"
"message": "用于Firefox上的Stylish的Mozilla格式样式代码可以在 userstyles.org 上提交", },
"description": "Help info for the Mozilla format header section that converts the code to/from Mozilla format" "appliesToEverything": {
}, "message": "所有站点"
"sectionAdd": { },
"message": "添加新节", "appliesUrlOption": {
"description": "Label for the button to add a section" "message": "地址"
}, },
"styleSaveLabel": { "appliesUrlPrefixOption": {
"message": "保存", "message": "以指定地址开始"
"description": "Label for save button for style editing" },
}, "checkAllUpdates": {
"appliesAdd": { "message": "检查所有样式的更新"
"message": "添加", },
"description": "Label for the button to add an 'applies' entry" "checkForUpdate": {
}, "message": "检查更新"
"appliesRegexpOption": { },
"message": "匹配正则的URL", "checkingForUpdate": {
"description": "Option to make the style apply to the entered string as a regular expression" "message": "检查中..."
}, },
"styleInstall": { "deleteStyleConfirm": {
"message": "安装 '$stylename$' 到 Stylus?", "message": "确定要删除这个样式吗?"
"description": "Confirmation when installing a style", },
"placeholders": { "deleteStyleLabel": {
"stylename": { "message": "删除"
"content": "$1" },
} "description": {
} "message": "Stylus一个用户样式管理器帮助您重新定义网页样式。Stylus易于为GoogleFacebookYoutubeOrkut以及其它各类型网站安装样式和主题。"
}, },
"disableStyleLabel": { "disableStyleLabel": {
"message": "禁用", "message": "禁用"
"description": "Label for the button to disable a style" },
}, "editStyleHeading": {
"styleCancelEditLabel": { "message": "编辑样式"
"message": "返回到管理", },
"description": "Label for cancel button for style editing" "editStyleLabel": {
}, "message": "编辑"
"styleChangesNotSaved": { },
"message": "您已经修改了此样式,但尚未保存", "editStyleTitle": {
"description": "Text for the prompt when changes are made to a style and the user tries to leave without saving" "message": "编辑样式 $stylename$",
}, "placeholders": {
"updateCheckFailServerUnreachable": { "stylename": {
"message": "更新失败: 服务器无法访问.", "content": "$1"
"description": "Text that displays when an update check failed because the update server is unreachable" }
}, }
"deleteStyleConfirm": { },
"message": "确定要删除这个样式吗?", "enableStyleLabel": {
"description": "Confirmation before deleting a style" "message": "启用"
}, },
"appliesDisplay": { "findStylesForSite": {
"message": "应用到: $applies$", "message": "查找更多适合此站点的样式"
"description": "Text on the manage screen to describe what the style applies to", },
"placeholders": { "helpAlt": {
"applies": { "message": "帮助"
"content": "$1" },
} "installUpdate": {
} "message": "安装更新"
}, },
"styleUpdate": { "manageHeading": {
"message": "你確定要更新 '$stylename$' 嗎?", "message": "已安装的样式"
"description": "Confirmation when updating a style", },
"placeholders": { "noStylesForSite": {
"stylename": { "message": "当前站点上没有已安装的样式"
"content": "$1" },
} "openManage": {
} "message": "管理已安装样式"
}, },
"styleSectionsTitle": { "sectionAdd": {
"message": "样式节", "message": "添加新节"
"description": "Title for the style sections section" },
}, "sectionCode": {
"editStyleTitle": { "message": "代码"
"message": "编辑样式 $stylename$", },
"description": "Title of the page for editing styles", "sectionHelp": {
"placeholders": { "message": "样式节允许你定义多个样式段落并将它们应用于不同的站点匹配规则上。例如,一个样式可以更改使用一个方式来更改主页,而此时其它的样式段可以更改站点的其它部分。"
"stylename": { },
"content": "$1" "sectionRemove": {
} "message": "移除节"
} },
}, "styleCancelEditLabel": {
"updateCheckSucceededNoUpdate": { "message": "返回到管理"
"message": "已经是最新的.", },
"description": "Text that displays when an update check completed and no update is available" "styleChangesNotSaved": {
}, "message": "您已经修改了此样式,但尚未保存"
"appliesUrlPrefixOption": { },
"message": "以指定地址开始", "styleEnabledLabel": {
"description": "Option to make the style apply to the entered string as a URL prefix" "message": "已启用"
}, },
"sectionHelp": { "styleInstall": {
"message": "样式节允许你定义多个样式段落并将它们应用于不同的站点匹配规则上。例如,一个样式可以更改使用一个方式来更改主页,而此时其它的样式段可以更改站点的其它部分。", "message": "安装 '$stylename$' 到 Stylus?",
"description": "Help text for sections" "placeholders": {
}, "stylename": {
"noStylesForSite": { "content": "$1"
"message": "当前站点上没有已安装的样式", }
"description": "Text displayed when no styles are installed for the current site" }
}, },
"appliesDisplayTruncatedSuffix": { "styleMissingName": {
"message": "以及更多", "message": "请输入名称"
"description": "Text added to appliesDisplay when there are more sites for the style than are displayed" },
}, "styleSaveLabel": {
"appliesRemove": { "message": "保存"
"message": "移除", },
"description": "Label for the button to remove an 'applies' entry" "styleSectionsTitle": {
}, "message": "样式节"
"appliesLabel": { },
"message": "应用到", "styleToMozillaFormatHelp": {
"description": "Label for 'applies to' fields on the edit/add screen" "message": "用于Firefox上的Stylish的Mozilla格式样式代码可以在 userstyles.org 上提交"
}, },
"openManage": { "styleUpdate": {
"message": "管理已安装样式", "message": "你確定要更新 '$stylename$' 嗎?",
"description": "Link to open the manage page." "placeholders": {
}, "stylename": {
"updateCheckFailBadResponseCode": { "content": "$1"
"message": "更新失败: 服务器了返回代码 $code$。", }
"description": "Text that displays when an update check failed because the response code indicates an error", }
"placeholders": { },
"code": { "updateCheckFailBadResponseCode": {
"content": "$1" "message": "更新失败: 服务器了返回代码 $code$。",
} "placeholders": {
} "code": {
}, "content": "$1"
"appliesSpecify": { }
"message": "指定站点", }
"description": "Label for the button to make a style apply only to specific sites" },
}, "updateCheckFailServerUnreachable": {
"installUpdate": { "message": "更新失败: 服务器无法访问."
"message": "安装更新", },
"description": "Label for the button to install an update for a single style" "updateCheckSucceededNoUpdate": {
}, "message": "已经是最新的."
"sectionRemove": { },
"message": "移除节", "updateCompleted": {
"description": "Label for the button to remove a section" "message": "更新完成."
}, }
"updateCompleted": {
"message": "更新完成.",
"description": "Text that displays when an update completed"
},
"checkingForUpdate": {
"message": "检查中...",
"description": "Text to display when checking a style for an update"
},
"sectionCode": {
"message": "代码",
"description": "Label for the code for a section"
},
"appliesHelp": {
"message": "使用“应用到”来控制这个样式应用到哪些地址上。",
"description": "Help text for 'applies to' section"
},
"editStyleHeading": {
"message": "编辑样式",
"description": "Title of the page for editing styles"
},
"appliesUrlOption": {
"message": "地址",
"description": "Option to make the style apply to the entered string as a URL"
},
"addStyleTitle": {
"message": "添加样式",
"description": "Title of the page for adding styles"
},
"checkAllUpdates": {
"message": "检查所有样式的更新",
"description": "Label for the button to check all styles for updates"
},
"deleteStyleLabel": {
"message": "删除",
"description": "Label for the button to delete a style"
},
"addStyleLabel": {
"message": "编写新的样式",
"description": "Label for the button to go to the add style page"
},
"editStyleLabel": {
"message": "编辑",
"description": "Label for the button to go to the edit style page"
},
"description": {
"message": "Stylus一个用户样式管理器帮助您重新定义网页样式。Stylus易于为GoogleFacebookYoutubeOrkut以及其它各类型网站安装样式和主题。",
"description": "Extension description"
}
} }

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -14,6 +14,9 @@ window.API_METHODS = Object.assign(window.API_METHODS || {}, {
saveStyle, saveStyle,
deleteStyle, deleteStyle,
getStyleFromDB: id =>
dbExec('get', id).then(event => event.target.result),
download(msg) { download(msg) {
delete msg.method; delete msg.method;
return download(msg.url, msg); return download(msg.url, msg);
@ -56,6 +59,32 @@ var browserCommands, contextMenus;
// register all listeners // register all listeners
chrome.runtime.onMessage.addListener(onRuntimeMessage); chrome.runtime.onMessage.addListener(onRuntimeMessage);
if (FIREFOX) {
// see notes in apply.js for getStylesFallback
const MSG_GET_STYLES = 'getStyles:';
const MSG_GET_STYLES_LEN = MSG_GET_STYLES.length;
chrome.runtime.onConnect.addListener(port => {
if (!port.name.startsWith(MSG_GET_STYLES)) return;
const tabId = port.sender.tab.id;
const frameId = port.sender.frameId;
const options = tryJSONparse(port.name.slice(MSG_GET_STYLES_LEN));
port.disconnect();
getStyles(options).then(styles => {
if (!styles.length) return;
chrome.tabs.executeScript(tabId, {
code: `
applyOnMessage({
method: 'styleApply',
styles: ${JSON.stringify(styles)},
})
`,
runAt: 'document_start',
frameId,
});
});
});
}
{ {
const listener = const listener =
URLS.chromeProtectsNTP URLS.chromeProtectsNTP
@ -119,22 +148,15 @@ prefs.subscribe(['iconset'], () =>
})); }));
// ************************************************************************* // *************************************************************************
{ chrome.runtime.onInstalled.addListener(({reason}) => {
const onInstall = ({reason}) => { if (reason !== 'update') return;
chrome.runtime.onInstalled.removeListener(onInstall); // translations may change
if (reason === 'update') { localStorage.L10N = JSON.stringify({
// translations may change browserUIlanguage: chrome.i18n.getUILanguage(),
localStorage.L10N = JSON.stringify({ });
browserUIlanguage: chrome.i18n.getUILanguage(), // themes may change
}); delete localStorage.codeMirrorThemes;
// themes may change });
delete localStorage.codeMirrorThemes;
}
};
// bind for 60 seconds max and auto-unbind if it's a normal run
chrome.runtime.onInstalled.addListener(onInstall);
setTimeout(onInstall, 60e3, {reason: 'unbindme'});
}
// ************************************************************************* // *************************************************************************
// browser commands // browser commands

View File

@ -0,0 +1,9 @@
/* global importScripts parserlib CSSLint parseMozFormat */
'use strict';
importScripts('/vendor-overwrites/csslint/parserlib.js', '/js/moz-parser.js');
parserlib.css.Tokens[parserlib.css.Tokens.COMMENT].hide = false;
self.onmessage = ({data}) => {
self.postMessage(parseMozFormat(data));
};

View File

@ -153,9 +153,8 @@ global API_METHODS
case 0: case 0:
// re-install is invalid in a soft upgrade // re-install is invalid in a soft upgrade
if (!ignoreDigest) { if (!ignoreDigest) {
return Promise.reject(STATES.SAME_VERSION); const sameCode = text === style.sourceCode;
} else if (text === style.sourceCode) { return Promise.reject(sameCode ? STATES.SAME_CODE : STATES.SAME_VERSION);
return Promise.reject(STATES.SAME_CODE);
} }
break; break;
case 1: case 1:
@ -186,10 +185,14 @@ global API_METHODS
json.originalName = json.name; json.originalName = json.name;
} }
if (styleSectionsEqual(json, style)) { if (styleSectionsEqual(json, style, {checkSource: true})) {
// update digest even if save === false as there might be just a space added etc. // update digest even if save === false as there might be just a space added etc.
saveStyle(Object.assign(json, {reason: 'update-digest'})); json.reason = 'update-digest';
return Promise.reject(STATES.SAME_CODE); return saveStyle(json)
.then(saved => {
style.originalDigest = saved.originalDigest;
return Promise.reject(STATES.SAME_CODE);
});
} }
if (!style.originalDigest && !ignoreDigest) { if (!style.originalDigest && !ignoreDigest) {

View File

@ -65,10 +65,21 @@
return style; return style;
} }
// Parse the source and find the duplication /**
function build({sourceCode, checkDup = false}) { * Parse the source and find the duplication
return buildMeta({sourceCode}) * @param _
.then(usercss.buildCode) * @param {String} _.sourceCode
* @param {Boolean=} _.checkDup
* @param {Boolean=} _.metaOnly
* @returns {Promise<{style, dup:Boolean?}>}
*/
function build({
sourceCode,
checkDup,
metaOnly,
}) {
const task = buildMeta({sourceCode});
return (metaOnly ? task : task.then(usercss.buildCode))
.then(style => ({ .then(style => ({
style, style,
dup: checkDup && find(style), dup: checkDup && find(style),

View File

@ -18,6 +18,13 @@
var docRewriteObserver; var docRewriteObserver;
var docRootObserver; var docRootObserver;
// FF59+ bug workaround
// See https://github.com/openstyles/stylus/issues/461
// Since it's easy to spoof the browser version in pre-Quantum FF we're checking
// for getPreventDefault which got removed in FF59 https://bugzil.la/691151
const FF_BUG461 = !CHROME && !isOwnPage && !Event.prototype.getPreventDefault;
const pageContextQueue = [];
requestStyles(); requestStyles();
chrome.runtime.onMessage.addListener(applyOnMessage); chrome.runtime.onMessage.addListener(applyOnMessage);
window.applyOnMessage = applyOnMessage; window.applyOnMessage = applyOnMessage;
@ -50,11 +57,35 @@
// On own pages we request the styles directly to minimize delay and flicker // On own pages we request the styles directly to minimize delay and flicker
if (typeof API === 'function') { if (typeof API === 'function') {
API.getStyles(request).then(callback); API.getStyles(request).then(callback);
} else if (!CHROME && getStylesFallback(request)) {
// NOP
} else { } else {
chrome.runtime.sendMessage(request, callback); chrome.runtime.sendMessage(request, callback);
} }
} }
/**
* TODO: remove when FF fixes the bug.
* Firefox borks sendMessage in same-origin iframes that have 'src' with a real path on the site.
* We implement a workaround for the initial styleApply case only.
* Everything else (like toggling of styles) is still buggy.
* @param {Object} msg
* @param {Function} callback
* @returns {Boolean|undefined}
*/
function getStylesFallback(msg) {
if (window !== parent &&
location.href !== 'about:blank') {
try {
if (parent.location.origin === location.origin &&
parent.location.href !== location.href) {
chrome.runtime.connect({name: 'getStyles:' + JSON.stringify(msg)});
return true;
}
} catch (e) {}
}
}
function applyOnMessage(request, sender, sendResponse) { function applyOnMessage(request, sender, sendResponse) {
if (request.styles === 'DIY') { if (request.styles === 'DIY') {
// Do-It-Yourself tells our built-in pages to fetch the styles directly // Do-It-Yourself tells our built-in pages to fetch the styles directly
@ -235,7 +266,11 @@
if (!Array.isArray(sections)) continue; if (!Array.isArray(sections)) continue;
applySections(id, sections.map(({code}) => code).join('\n')); applySections(id, sections.map(({code}) => code).join('\n'));
} }
docRootObserver.start({sort: true}); docRootObserver.firstStart();
}
if (FF_BUG461 && (gotNewStyles || styles.needTransitionPatch)) {
setContentsInPageContext();
} }
if (!isOwnPage && !docRewriteObserver && styleElements.size) { if (!isOwnPage && !docRewriteObserver && styleElements.size) {
@ -260,6 +295,8 @@
// workaround for Chrome devtools bug fixed in v65 // workaround for Chrome devtools bug fixed in v65
el.remove(); el.remove();
el = null; el = null;
} else if (FF_BUG461) {
pageContextQueue.push({id: el.id, el, code});
} else { } else {
el.textContent = code; el.textContent = code;
} }
@ -275,13 +312,15 @@
// HTML document style; also works on HTML-embedded SVG // HTML document style; also works on HTML-embedded SVG
el = document.createElement('style'); el = document.createElement('style');
} }
Object.assign(el, { el.id = id;
id, el.type = 'text/css';
type: 'text/css',
textContent: code,
});
// SVG className is not a string, but an instance of SVGAnimatedString // SVG className is not a string, but an instance of SVGAnimatedString
el.classList.add('stylus'); el.classList.add('stylus');
if (FF_BUG461) {
pageContextQueue.push({id: el.id, el, code});
} else {
el.textContent = code;
}
addStyleElement(el); addStyleElement(el);
} }
styleElements.set(id, el); styleElements.set(id, el);
@ -289,6 +328,34 @@
return el; return el;
} }
function setContentsInPageContext() {
try {
(document.head || ROOT).appendChild(document.createElement('script')).text = `(${queue => {
document.currentScript.remove();
for (const {id, code} of queue) {
const el = document.getElementById(id) ||
document.querySelector('style.stylus[id="' + id + '"]');
if (!el) continue;
const {disabled} = el.sheet;
el.textContent = code;
el.sheet.disabled = disabled;
}
}})(${JSON.stringify(pageContextQueue)})`;
} catch (e) {}
let failedSome;
for (const {el, code} of pageContextQueue) {
if (el.textContent !== code) {
el.textContent = code;
failedSome = true;
}
}
if (failedSome) {
console.debug('Could not set code of some styles in page context, ' +
'see https://github.com/openstyles/stylus/issues/461');
}
pageContextQueue.length = 0;
}
function addStyleElement(newElement) { function addStyleElement(newElement) {
if (!ROOT) { if (!ROOT) {
return; return;
@ -430,13 +497,16 @@
function init() { function init() {
observer = new MutationObserver(sortStyleElements); observer = new MutationObserver(sortStyleElements);
docRootObserver = {start, stop, evade, disconnect: stop}; docRootObserver = {firstStart, start, stop, evade, disconnect: stop};
setTimeout(sortStyleElements); setTimeout(sortStyleElements);
} }
function start({sort = false} = {}) { function firstStart() {
if (sort && sortStyleMap()) { if (sortStyleMap()) {
sortStyleElements(); sortStyleElements();
} }
start();
}
function start() {
if (!observing && ROOT && observer) { if (!observing && ROOT && observer) {
observer.observe(ROOT, {childList: true}); observer.observe(ROOT, {childList: true});
observing = true; observing = true;
@ -477,16 +547,12 @@
} }
} }
function sortStyleElements() { function sortStyleElements() {
if (!observing) { if (!observing) return;
return;
}
let prevExpected = document.documentElement.lastElementChild; let prevExpected = document.documentElement.lastElementChild;
while (prevExpected && isSkippable(prevExpected, true)) { while (prevExpected && isSkippable(prevExpected, true)) {
prevExpected = prevExpected.previousElementSibling; prevExpected = prevExpected.previousElementSibling;
} }
if (!prevExpected) { if (!prevExpected) return;
return;
}
for (const el of styleElements.values()) { for (const el of styleElements.values()) {
if (!isMovable(el)) { if (!isMovable(el)) {
continue; continue;

View File

@ -26,7 +26,6 @@
<script src="js/storage-util.js"></script> <script src="js/storage-util.js"></script>
<script src="js/exclusions.js"></script> <script src="js/exclusions.js"></script>
<script src="content/apply.js"></script> <script src="content/apply.js"></script>
<script src="edit/lint.js"></script>
<script src="edit/util.js"></script> <script src="edit/util.js"></script>
<script src="edit/regexp-tester.js"></script> <script src="edit/regexp-tester.js"></script>
<script src="edit/applies-to-line-widget.js"></script> <script src="edit/applies-to-line-widget.js"></script>
@ -66,6 +65,8 @@
<script src="vendor/codemirror/addon/fold/comment-fold.js"></script> <script src="vendor/codemirror/addon/fold/comment-fold.js"></script>
<link href="vendor/codemirror/addon/lint/lint.css" rel="stylesheet" /> <link href="vendor/codemirror/addon/lint/lint.css" rel="stylesheet" />
<script src="vendor/codemirror/addon/lint/lint.js"></script>
<link href="vendor/codemirror/addon/hint/show-hint.css" rel="stylesheet" /> <link href="vendor/codemirror/addon/hint/show-hint.css" rel="stylesheet" />
<script src="vendor/codemirror/addon/hint/show-hint.js"></script> <script src="vendor/codemirror/addon/hint/show-hint.js"></script>
@ -88,6 +89,16 @@
<link href="edit/codemirror-default.css" rel="stylesheet"> <link href="edit/codemirror-default.css" rel="stylesheet">
<script src="edit/codemirror-default.js"></script> <script src="edit/codemirror-default.js"></script>
<script src="edit/linter.js"></script>
<script src="edit/linter-defaults.js"></script>
<script src="edit/linter-engines.js"></script>
<script src="edit/linter-meta.js"></script>
<script src="edit/linter-help-dialog.js"></script>
<script src="edit/linter-report.js"></script>
<script src="edit/linter-config-dialog.js"></script>
<script src="edit/editor-worker.js"></script>
<link id="cm-theme" rel="stylesheet"> <link id="cm-theme" rel="stylesheet">
<template data-id="appliesTo"> <template data-id="appliesTo">
@ -122,7 +133,7 @@
</template> </template>
<template data-id="section"> <template data-id="section">
<div> <div class="section">
<label i18n-text="sectionCode" class="code-label"></label> <label i18n-text="sectionCode" class="code-label"></label>
<br> <br>
<div class="applies-to"> <div class="applies-to">
@ -135,13 +146,23 @@
</div> </div>
<div class="edit-actions"> <div class="edit-actions">
<button class="remove-section" i18n-text="sectionRemove"></button> <button class="remove-section" i18n-text="sectionRemove"></button>
<button class="add-section" i18n-text="sectionAdd"></button> <button class="add-section" i18n-long-text="sectionAdd" i18n-short-text="genericAdd"></button>
<button class="clone-section" i18n-text="genericClone"></button>
<button class="move-section-up"></button>
<button class="move-section-down"></button>
<button class="beautify-section" i18n-text="styleBeautify"></button> <button class="beautify-section" i18n-text="styleBeautify"></button>
<button class="test-regexp" i18n-text="styleRegexpTestButton"></button> <button class="test-regexp" i18n-text="styleRegexpTestButton"></button>
</div> </div>
</div> </div>
</template> </template>
<!-- not using DIV to make our CSS work for #sections > div:only-of-type .remove-section -->
<template data-id="deletedSection">
<p class="deleted-section">
<button class="restore-section" i18n-text="sectionRestore"></button>
</p>
</template>
<template data-id="searchReplaceDialog"> <template data-id="searchReplaceDialog">
<div id="search-replace-dialog"> <div id="search-replace-dialog">
<div data-type="main"> <div data-type="main">
@ -428,7 +449,7 @@
</a> </a>
</h2> </h2>
</summary> </summary>
<div></div> <div class="lint-report-container"></div>
</details> </details>
<div id="footer" class="hidden"> <div id="footer" class="hidden">
<a href="https://github.com/openstyles/stylus/wiki/Usercss" <a href="https://github.com/openstyles/stylus/wiki/Usercss"

View File

@ -16,46 +16,55 @@ function beautify(event) {
function doBeautify() { function doBeautify() {
const tabs = prefs.get('editor.indentWithTabs'); const tabs = prefs.get('editor.indentWithTabs');
const options = prefs.get('editor.beautify'); const options = prefs.get('editor.beautify');
for (const k of Object.keys(prefs.defaults['editor.beautify'])) {
if (!(k in options)) options[k] = prefs.defaults['editor.beautify'][k];
}
options.indent_size = tabs ? 1 : prefs.get('editor.tabSize'); options.indent_size = tabs ? 1 : prefs.get('editor.tabSize');
options.indent_char = tabs ? '\t' : ' '; options.indent_char = tabs ? '\t' : ' ';
const section = getSectionForChild(event.target); const section = getSectionForChild(event.target);
const scope = section ? [section.CodeMirror] : editors; const scope = section ? [section.CodeMirror] : editors;
showHelp(t('styleBeautify'), '<div class="beautify-options">' + showHelp(t('styleBeautify'),
optionHtml('.selector1,', 'selector_separator_newline') + $create([
optionHtml('.selector2', 'newline_before_open_brace') + $create('.beautify-options', [
optionHtml('{', 'newline_after_open_brace') + $createOption('.selector1,', 'selector_separator_newline'),
optionHtml('border: none;', 'newline_between_properties', true) + $createOption('.selector2', 'newline_before_open_brace'),
optionHtml('display: block;', 'newline_before_close_brace', true) + $createOption('{', 'newline_after_open_brace'),
optionHtml('}', 'newline_between_rules') + $createOption('border: none;', 'newline_between_properties', true),
`<label style="display: block; clear: both;"> $createOption('display: block;', 'newline_before_close_brace', true),
<input data-option="indent_conditional" type="checkbox" $createOption('}', 'newline_between_rules'),
${options.indent_conditional !== false ? 'checked' : ''}> $createLabeledCheckbox('preserve_newlines', 'styleBeautifyPreserveNewlines'),
<svg class="svg-icon checked"><use xlink:href="#svg-icon-checked"/></svg>` + $createLabeledCheckbox('indent_conditional', 'styleBeautifyIndentConditional'),
t('styleBeautifyIndentConditional') + '</label>' + ]),
'</div>' + $create('.buttons', [
'<div><button role="undo"></button></div>'); $create('button', {
attributes: {role: 'close'},
// showHelp.close will be defined after showHelp() is invoked
onclick: () => showHelp.close(),
}, t('confirmClose')),
$create('button', {
attributes: {role: 'undo'},
onclick() {
let undoable = false;
for (const cm of scope) {
const data = cm.beautifyChange;
if (!data || !data[cm.changeGeneration()]) continue;
delete data[cm.changeGeneration()];
const {scrollX, scrollY} = window;
cm.undo();
cm.scrollIntoView(cm.getCursor());
window.scrollTo(scrollX, scrollY);
undoable |= data[cm.changeGeneration()];
}
this.disabled = !undoable;
},
}, t(scope.length === 1 ? 'undo' : 'undoGlobal')),
]),
]));
$('#help-popup').className = 'wide'; $('#help-popup').className = 'wide';
const undoButton = $('#help-popup button[role="undo"]');
undoButton.textContent = t(scope.length === 1 ? 'undo' : 'undoGlobal');
undoButton.addEventListener('click', () => {
let undoable = false;
scope.forEach(cm => {
if (cm.beautifyChange && cm.beautifyChange[cm.changeGeneration()]) {
delete cm.beautifyChange[cm.changeGeneration()];
const {scrollX, scrollY} = window;
cm.undo();
cm.scrollIntoView(cm.getCursor());
window.scrollTo(scrollX, scrollY);
undoable |= cm.beautifyChange[cm.changeGeneration()];
}
});
undoButton.disabled = !undoable;
});
scope.forEach(cm => { scope.forEach(cm => {
setTimeout(() => { setTimeout(() => {
const pos = options.translate_positions = const pos = options.translate_positions =
@ -77,9 +86,9 @@ function beautify(event) {
cm.setSelections(selections); cm.setSelections(selections);
window.scrollTo(scrollX, scrollY); window.scrollTo(scrollX, scrollY);
cm.beautifyChange[cm.changeGeneration()] = true; cm.beautifyChange[cm.changeGeneration()] = true;
undoButton.disabled = false; $('#help-popup button[role="close"]').disabled = false;
} }
}, 0); });
}); });
$('.beautify-options').onchange = ({target}) => { $('.beautify-options').onchange = ({target}) => {
@ -91,21 +100,41 @@ function beautify(event) {
doBeautify(); doBeautify();
}; };
function optionHtml(label, optionName, indent) { function $createOption(label, optionName, indent) {
const value = options[optionName]; const value = options[optionName];
return '<div newline="' + value.toString() + '">' + return (
'<span' + (indent ? ' indent' : '') + '>' + label + '</span>' + $create('div', {attributes: {newline: value}}, [
'<div class="select-resizer">' + $create('span', indent ? {attributes: {indent: ''}} : {}, label),
'<select data-option="' + optionName + '">' + $create('div.select-resizer', [
'<option' + (value ? '' : ' selected') + '>&nbsp;</option>' + $create('select', {dataset: {option: optionName}}, [
'<option' + (value ? ' selected' : '') + '>\\n</option>' + $create('option', {selected: !value}, '\xA0'),
'</select>' + $create('option', {selected: value}, '\\n'),
'<svg class="svg-icon select-arrow" viewBox="0 0 1792 1792">' + ]),
'<path fill-rule="evenodd" d="M1408 704q0 26-19 45l-448 448q-19 19-45 ' + $create('SVG:svg.svg-icon.select-arrow', {viewBox: '0 0 1792 1792'}, [
'19t-45-19l-448-448q-19-19-19-45t19-45 45-19h896q26 0 45 19t19 45z"/>' + $create('SVG:path', {
'</svg>' + 'fill-rule': 'evenodd',
'</div>' + 'd': 'M1408 704q0 26-19 45l-448 448q-19 19-45 ' +
'</div>'; '19t-45-19l-448-448q-19-19-19-45t19-45 45-19h896q26 0 45 19t19 45z'
}),
]),
]),
])
);
}
function $createLabeledCheckbox(optionName, i18nKey) {
return (
$create('label', {style: 'display: block; clear: both;'}, [
$create('input', {
type: 'checkbox',
dataset: {option: optionName},
checked: options[optionName] !== false
}),
$create('SVG:svg.svg-icon.checked',
$create('SVG:use', {'xlink:href': '#svg-icon-checked'})),
t(i18nKey),
])
);
} }
} }
} }

View File

@ -1,5 +1,5 @@
/* /*
global CodeMirror linterConfig loadScript global CodeMirror loadScript
global editors editor styleId ownTabId global editors editor styleId ownTabId
global save toggleStyle setupAutocomplete makeSectionVisible getSectionForChild global save toggleStyle setupAutocomplete makeSectionVisible getSectionForChild
global getSectionsHashes global getSectionsHashes
@ -8,9 +8,6 @@ global messageBox
'use strict'; 'use strict';
onDOMscriptReady('/codemirror.js').then(() => { onDOMscriptReady('/codemirror.js').then(() => {
CodeMirror.defaults.lint = linterConfig.getForCodeMirror();
const COMMANDS = { const COMMANDS = {
save, save,
toggleStyle, toggleStyle,
@ -43,6 +40,9 @@ onDOMscriptReady('/codemirror.js').then(() => {
}); });
CodeMirror.defineInitHook(cm => { CodeMirror.defineInitHook(cm => {
if (!cm.display.wrapper.closest('#sections')) {
return;
}
if (prefs.get('editor.livePreview') && styleId) { if (prefs.get('editor.livePreview') && styleId) {
cm.on('changes', updatePreview); cm.on('changes', updatePreview);
} }
@ -618,14 +618,56 @@ onDOMscriptReady('/codemirror.js').then(() => {
const me = this instanceof Node ? this : $('#editor.livePreview'); const me = this instanceof Node ? this : $('#editor.livePreview');
const previewing = me.checked; const previewing = me.checked;
editors.forEach(cm => cm[previewing ? 'on' : 'off']('changes', updatePreview)); editors.forEach(cm => cm[previewing ? 'on' : 'off']('changes', updatePreview));
const addRemove = previewing ? 'addEventListener' : 'removeEventListener'; const addRemove = EventTarget.prototype[previewing ? 'addEventListener' : 'removeEventListener'];
$('#enabled')[addRemove]('change', updatePreview); addRemove.call($('#enabled'), 'change', updatePreview);
$('#sections')[addRemove]('change', updatePreview); if (!editor) {
for (const el of $$('#sections .applies-to')) {
addRemove.call(el, 'input', updatePreview);
}
toggleLivePreviewSectionsObserver(previewing);
}
if (!previewing || document.body.classList.contains('dirty')) { if (!previewing || document.body.classList.contains('dirty')) {
updatePreview(null, previewing); updatePreview(null, previewing);
} }
} }
/**
* Observes newly added section elements, and sets these event listeners:
* 1. 'changes' on CodeMirror inside
* 2. 'input' on .applies-to inside
* The goal is to avoid listening to 'input' on the entire #sections tree,
* which would trigger updatePreview() twice on any keystroke -
* both for the synthetic event from CodeMirror and the original event.
* Side effects:
* two expando properties on #sections
* 1. __livePreviewObserver
* 2. __livePreviewObserverEnabled
* @param {Boolean} enable
*/
function toggleLivePreviewSectionsObserver(enable) {
const sections = $('#sections');
const observing = sections.__livePreviewObserverEnabled;
let mo = sections.__livePreviewObserver;
if (enable && !mo) {
sections.__livePreviewObserver = mo = new MutationObserver(mutations => {
for (const {addedNodes} of mutations) {
for (const node of addedNodes) {
const el = node.children && $('.applies-to', node);
if (el) el.addEventListener('input', updatePreview);
if (node.CodeMirror) node.CodeMirror.on('changes', updatePreview);
}
}
});
}
if (enable && !observing) {
mo.observe(sections, {childList: true});
sections.__livePreviewObserverEnabled = true;
} else if (!enable && observing) {
mo.disconnect();
sections.__livePreviewObserverEnabled = false;
}
}
function updatePreview(data, previewing) { function updatePreview(data, previewing) {
if (previewing !== true && previewing !== false) { if (previewing !== true && previewing !== false) {
if (data instanceof Event && !data.target.matches('.style-contributor')) return; if (data instanceof Event && !data.target.matches('.style-contributor')) return;
@ -644,7 +686,8 @@ onDOMscriptReady('/codemirror.js').then(() => {
}).then(() => { }).then(() => {
errors.classList.add('hidden'); errors.classList.add('hidden');
}).catch(err => { }).catch(err => {
if (err && editor && !Number.isNaN(err.index)) { if (Array.isArray(err)) err = err.join('\n');
if (err && editor && !isNaN(err.index)) {
const pos = editors[0].posFromIndex(err.index); const pos = editors[0].posFromIndex(err.index);
err = `${pos.line}:${pos.ch} ${err}`; err = `${pos.line}:${pos.ch} ${err}`;
} }

View File

@ -1,41 +0,0 @@
/* global importScripts parserlib CSSLint parseMozFormat */
'use strict';
const CSSLINT_PATH = '/vendor-overwrites/csslint/';
importScripts(CSSLINT_PATH + 'parserlib.js');
parserlib.css.Tokens[parserlib.css.Tokens.COMMENT].hide = false;
self.onmessage = ({data}) => {
const {action = 'run'} = data;
if (action === 'parse') {
if (!self.parseMozFormat) self.importScripts('/js/moz-parser.js');
self.postMessage(parseMozFormat(data));
return;
}
if (!self.CSSLint) self.importScripts(CSSLINT_PATH + 'csslint.js');
switch (action) {
case 'getAllRuleIds':
// the functions are non-tranferable and we need only an id
self.postMessage(CSSLint.getRules().map(rule => rule.id));
return;
case 'getAllRuleInfos':
// the functions are non-tranferable
self.postMessage(CSSLint.getRules().map(rule => JSON.parse(JSON.stringify(rule))));
return;
case 'run': {
const {code, config} = data;
const results = CSSLint.verify(code, config).messages
//.filter(m => !m.message.includes('/*[[') && !m.message.includes(']]*/'))
.map(m => Object.assign(m, {rule: {id: m.rule.id}}));
self.postMessage(results);
return;
}
}
};

View File

@ -301,16 +301,22 @@ input:invalid {
margin-top: 4em; margin-top: 4em;
} }
/************ content ***********/ /************ content ***********/
#sections > div { #sections > * {
margin: 0.7rem; margin: 0.7rem;
padding: 1rem 1rem .3rem; padding: 1rem 1rem .3rem;
} }
#sections > div:first-of-type { #sections > *:first-child {
padding: 0 1rem .3rem; padding: 0 1rem .3rem;
} }
#sections > div:not(:first-of-type) { #sections > *:not(:first-child) {
border-top: 2px solid hsl(0, 0%, 80%); border-top: 2px solid hsl(0, 0%, 80%);
} }
.add-section:after {
content: attr(short-text);
}
#sections > div:only-of-type .add-section:after {
content: attr(long-text);
}
#sections > div:only-of-type .remove-section { #sections > div:only-of-type .remove-section {
display: none; display: none;
} }
@ -328,17 +334,35 @@ input:invalid {
#sections { #sections {
counter-reset: codebox; counter-reset: codebox;
} }
#sections > div > label { #sections > .section > label {
animation: 2s highlight; animation: 2s highlight;
animation-play-state: paused; animation-play-state: paused;
animation-direction: reverse; animation-direction: reverse;
animation-fill-mode: both; animation-fill-mode: both;
} }
#sections > div > label::after { #sections > .section > label::after {
counter-increment: codebox; counter-increment: codebox;
content: counter(codebox); content: counter(codebox);
margin-left: 0.25rem; margin-left: 0.25rem;
} }
.section:only-of-type .move-section-up,
.section:only-of-type .move-section-down {
display: none;
}
.move-section-up:after {
content: "";
display: block;
border-style: solid;
border-width: 0 .3em .5em .3em;
border-color: transparent transparent currentColor transparent;
}
.move-section-down:after {
content: "";
display: block;
border-style: solid;
border-width: .5em .3em 0 .3em;
border-color: currentColor transparent transparent transparent;
}
/* code */ /* code */
.code { .code {
height: 10rem; height: 10rem;
@ -600,15 +624,6 @@ body[data-match-highlight="selection"] .CodeMirror-selection-highlight-scrollbar
right: 4px; right: 4px;
top: .5em; top: .5em;
} }
#help-popup .saved-message {
display: none;
color: #090;
margin-left: 10px;
font-weight: bold;
}
#help-popup .saved-message.show {
display: inline-block;
}
.keymap-list { .keymap-list {
font-size: 12px; font-size: 12px;
@ -627,13 +642,19 @@ body[data-match-highlight="selection"] .CodeMirror-selection-highlight-scrollbar
padding-right: 0.5rem; padding-right: 0.5rem;
} }
#help-popup .buttons {
text-align: center;
}
.non-windows #help-popup .buttons {
direction: rtl;
text-align: right;
}
#help-popup button[name^="import"] { #help-popup button[name^="import"] {
line-height: 1.5rem; line-height: 1.5rem;
padding: 0 0.5rem; padding: 0 0.5rem;
margin: 0.5rem 0 0 0.5rem; margin: 0.5rem 0 0 0.5rem;
pointer-events: none; pointer-events: none;
opacity: 0.5; opacity: 0.5;
float: right;
} }
#help-popup.ready button[name^="import"] { #help-popup.ready button[name^="import"] {
pointer-events: all; pointer-events: all;
@ -667,6 +688,9 @@ body[data-match-highlight="selection"] .CodeMirror-selection-highlight-scrollbar
#lint table:last-child { #lint table:last-child {
margin-bottom: 0; margin-bottom: 0;
} }
#lint table.empty {
display: none;
}
#lint caption { #lint caption {
text-align: left; text-align: left;
font-weight: bold; font-weight: bold;
@ -769,7 +793,7 @@ html:not(.usercss) .usercss-only,
} }
#sections .single-editor, #sections .single-editor,
#sections > div.single-editor:first-of-type { #sections > .single-editor:first-child {
margin: 0; margin: 0;
padding: 0; padding: 0;
display: flex; display: flex;
@ -948,11 +972,11 @@ html:not(.usercss) .usercss-only,
flex-direction: column; flex-direction: column;
flex: 1; flex: 1;
} }
#sections > div { #sections > * {
margin: 0 .5rem .5rem; margin: 0 .5rem .5rem;
padding: .5rem 0 0; padding: .5rem 0 0;
} }
#sections > div:first-of-type { #sections > *:first-child {
margin: .5rem; margin: .5rem;
padding: 0; padding: 0;
} }

View File

@ -1,6 +1,5 @@
/* /*
global CodeMirror parserlib loadScript global CodeMirror loadScript
global CSSLint initLint linterConfig updateLintReport renderLintReport updateLinter
global createSourceEditor global createSourceEditor
global closeCurrentTab regExpTester messageBox global closeCurrentTab regExpTester messageBox
global setupCodeMirror global setupCodeMirror
@ -8,6 +7,7 @@ global beautify
global initWithSectionStyle addSections removeSection getSectionsHashes global initWithSectionStyle addSections removeSection getSectionsHashes
global sectionsToMozFormat global sectionsToMozFormat
global exclusions global exclusions
global moveFocus editorWorker
*/ */
'use strict'; 'use strict';
@ -25,7 +25,8 @@ const CssToProperty = {'url': 'urls', 'url-prefix': 'urlPrefixes', 'domain': 'do
let editor; let editor;
window.onbeforeunload = beforeUnload;
document.addEventListener('visibilitychange', beforeUnload);
chrome.runtime.onMessage.addListener(onRuntimeMessage); chrome.runtime.onMessage.addListener(onRuntimeMessage);
preinit(); preinit();
@ -177,7 +178,8 @@ function onRuntimeMessage(request) {
break; break;
case 'styleDeleted': case 'styleDeleted':
if (styleId === request.id || editor && editor.getStyle().id === request.id) { if (styleId === request.id || editor && editor.getStyle().id === request.id) {
window.onbeforeunload = () => {}; document.removeEventListener('visibilitychange', beforeUnload);
window.onbeforeunload = null;
closeCurrentTab(); closeCurrentTab();
break; break;
} }
@ -199,24 +201,26 @@ function onRuntimeMessage(request) {
} }
} }
/**
* Invoked for 'visibilitychange' event by default.
* Invoked for 'beforeunload' event when the style is modified and unsaved.
* See https://developers.google.com/web/updates/2018/07/page-lifecycle-api#legacy-lifecycle-apis-to-avoid
* > Never add a beforeunload listener unconditionally or use it as an end-of-session signal.
* > Only add it when a user has unsaved work, and remove it as soon as that work has been saved.
*/
function beforeUnload() { function beforeUnload() {
if (saveSizeOnClose) { if (saveSizeOnClose) rememberWindowSize();
rememberWindowSize(); const activeElement = document.activeElement;
if (activeElement) {
// blurring triggers 'change' or 'input' event if needed
activeElement.blur();
// refocus if unloading was canceled
setTimeout(() => activeElement.focus());
} }
document.activeElement.blur(); const isDirty = editor ? editor.isDirty() : !isCleanGlobal();
if (isClean()) { if (isDirty) {
return; // neither confirm() nor custom messages work in modern browsers but just in case
} return t('styleChangesNotSaved');
updateLintReportIfEnabled(null, 0);
// neither confirm() nor custom messages work in modern browsers but just in case
return t('styleChangesNotSaved');
function isClean() {
if (editor) {
return !editor.isDirty();
} else {
return isCleanGlobal();
}
} }
} }
@ -230,7 +234,7 @@ function isUsercss(style) {
function initStyleData() { function initStyleData() {
// TODO: remove .replace(/^\?/, '') when minimum_chrome_version >= 52 (https://crbug.com/601425) // TODO: remove .replace(/^\?/, '') when minimum_chrome_version >= 52 (https://crbug.com/601425)
const params = new URLSearchParams(location.search.replace(/^\?/, '')); const params = new URLSearchParams(location.search.replace(/^\?/, ''));
const id = params.get('id'); const id = Number(params.get('id'));
const createEmptyStyle = () => ({ const createEmptyStyle = () => ({
id: null, id: null,
name: params.get('domain') || name: params.get('domain') ||
@ -246,8 +250,8 @@ function initStyleData() {
) )
], ],
}); });
return API.getStyles({id: id || -1}) return fetchStyle()
.then(([style = createEmptyStyle()]) => { .then(style => {
styleId = style.id; styleId = style.id;
if (styleId) sessionStorage.justEditedStyleId = styleId; if (styleId) sessionStorage.justEditedStyleId = styleId;
// we set "usercss" class on <html> when <body> is empty // we set "usercss" class on <html> when <body> is empty
@ -261,6 +265,13 @@ function initStyleData() {
} }
return style; return style;
}); });
function fetchStyle() {
if (id) {
return API.getStyleFromDB(id);
}
return Promise.resolve(createEmptyStyle());
}
} }
function initHooks() { function initHooks() {
@ -278,9 +289,6 @@ function initHooks() {
$('#save-button').addEventListener('click', save, false); $('#save-button').addEventListener('click', save, false);
$('#sections-help').addEventListener('click', showSectionHelp, false); $('#sections-help').addEventListener('click', showSectionHelp, false);
// TODO: investigate why FF needs this delay
debounce(initLint, FIREFOX ? 100 : 0);
if (!FIREFOX) { if (!FIREFOX) {
$$([ $$([
'input:not([type])', 'input:not([type])',
@ -344,7 +352,7 @@ function isCleanGlobal() {
function setCleanGlobal() { function setCleanGlobal() {
setCleanItem($('#sections'), true); setCleanItem($('#sections'), true);
$$('#header, #sections > div').forEach(setCleanSection); $$('#header, #sections > .section').forEach(setCleanSection);
// forget the dirty applies-to ids from a deleted section after the style was saved // forget the dirty applies-to ids from a deleted section after the style was saved
dirty = {}; dirty = {};
} }
@ -360,7 +368,6 @@ function toggleStyle() {
} }
function save() { function save() {
updateLintReportIfEnabled(null, 0);
if (!validate()) { if (!validate()) {
return; return;
} }
@ -414,20 +421,14 @@ function validate() {
} }
function updateTitle() { function updateTitle() {
const DIRTY_TITLE = '* $';
const name = $('#name').savedValue; const name = $('#name').savedValue;
const clean = isCleanGlobal(); const clean = isCleanGlobal();
const title = styleId === null ? t('addStyleTitle') : t('editStyleTitle', [name]); const title = styleId === null ? t('addStyleTitle') : name;
document.title = clean ? title : DIRTY_TITLE.replace('$', title); document.title = (clean ? '' : '* ') + title;
window.onbeforeunload = clean ? null : beforeUnload;
$('#save-button').disabled = clean; $('#save-button').disabled = clean;
} }
function updateLintReportIfEnabled(...args) {
if (CodeMirror.defaults.lint) {
updateLintReport(...args);
}
}
function showMozillaFormat() { function showMozillaFormat() {
const popup = showCodeMirrorPopup(t('styleToMozillaFormatTitle'), '', {readOnly: true}); const popup = showCodeMirrorPopup(t('styleToMozillaFormatTitle'), '', {readOnly: true});
popup.codebox.setValue(toMozillaFormat()); popup.codebox.setValue(toMozillaFormat());
@ -440,19 +441,19 @@ function toMozillaFormat() {
function fromMozillaFormat() { function fromMozillaFormat() {
const popup = showCodeMirrorPopup(t('styleFromMozillaFormatPrompt'), const popup = showCodeMirrorPopup(t('styleFromMozillaFormatPrompt'),
$create([ $create('.buttons', [
$create('button', {
name: 'import-append',
textContent: t('importAppendLabel'),
title: 'Ctrl-Enter:\n' + t('importAppendTooltip'),
onclick: doImport,
}),
$create('button', { $create('button', {
name: 'import-replace', name: 'import-replace',
textContent: t('importReplaceLabel'), textContent: t('importReplaceLabel'),
title: 'Ctrl-Shift-Enter:\n' + t('importReplaceTooltip'), title: 'Ctrl-Shift-Enter:\n' + t('importReplaceTooltip'),
onclick: () => doImport({replaceOldStyle: true}), onclick: () => doImport({replaceOldStyle: true}),
}), }),
$create('button', {
name: 'import-append',
textContent: t('importAppendLabel'),
title: 'Ctrl-Enter:\n' + t('importAppendTooltip'),
onclick: doImport,
}),
])); ]));
const contents = $('.contents', popup); const contents = $('.contents', popup);
contents.insertBefore(popup.codebox.display.wrapper, contents.firstElementChild); contents.insertBefore(popup.codebox.display.wrapper, contents.firstElementChild);
@ -469,16 +470,7 @@ function fromMozillaFormat() {
function doImport({replaceOldStyle = false}) { function doImport({replaceOldStyle = false}) {
lockPageUI(true); lockPageUI(true);
new Promise(setTimeout) editorWorker.parseMozFormat({code: popup.codebox.getValue().trim()})
.then(() => {
const worker = linterConfig.worker.csslint;
if (!worker.instance) worker.instance = new Worker(worker.path);
})
.then(() => linterConfig.invokeWorker({
linter: 'csslint',
action: 'parse',
code: popup.codebox.getValue().trim(),
}))
.then(({sections, errors}) => { .then(({sections, errors}) => {
// shouldn't happen but just in case // shouldn't happen but just in case
if (!sections.length && errors.length) { if (!sections.length && errors.length) {
@ -491,8 +483,7 @@ function fromMozillaFormat() {
removeOldSections(replaceOldStyle); removeOldSections(replaceOldStyle);
return addSections(sections, div => setCleanItem(div, false)); return addSections(sections, div => setCleanItem(div, false));
}) })
.then(sectionDivs => { .then(() => {
sectionDivs.forEach(div => updateLintReportIfEnabled(div.CodeMirror, 1));
$('.dismiss').dispatchEvent(new Event('click')); $('.dismiss').dispatchEvent(new Event('click'));
}) })
.catch(showError) .catch(showError)
@ -591,10 +582,8 @@ function showHelp(title = '', body) {
window.dispatchEvent(new Event('closeHelp')); window.dispatchEvent(new Event('closeHelp'));
}); });
if (getComputedStyle(div).display === 'none') { window.addEventListener('keydown', showHelp.close, true);
window.addEventListener('keydown', showHelp.close, true); $('.dismiss', div).onclick = showHelp.close;
$('.dismiss', div).onclick = showHelp.close;
}
// reset any inline styles // reset any inline styles
div.style = 'display: block'; div.style = 'display: block';
@ -610,26 +599,38 @@ function showCodeMirrorPopup(title, html, options) {
let cm = popup.codebox = CodeMirror($('.contents', popup), Object.assign({ let cm = popup.codebox = CodeMirror($('.contents', popup), Object.assign({
mode: 'css', mode: 'css',
lineNumbers: true, lineNumbers: true,
lineWrapping: true, lineWrapping: prefs.get('editor.lineWrapping'),
foldGutter: true, foldGutter: true,
gutters: ['CodeMirror-linenumbers', 'CodeMirror-foldgutter', 'CodeMirror-lint-markers'], gutters: ['CodeMirror-linenumbers', 'CodeMirror-foldgutter', 'CodeMirror-lint-markers'],
matchBrackets: true, matchBrackets: true,
lint: linterConfig.getForCodeMirror(),
styleActiveLine: true, styleActiveLine: true,
theme: prefs.get('editor.theme'), theme: prefs.get('editor.theme'),
keyMap: prefs.get('editor.keyMap') keyMap: prefs.get('editor.keyMap')
}, options)); }, options));
cm.focus(); cm.focus();
const rerouteOn = () => cm.rerouteHotkeys(false); cm.rerouteHotkeys(false);
const rerouteOff = () => cm.rerouteHotkeys(true);
cm.on('focus', rerouteOn); document.documentElement.style.pointerEvents = 'none';
cm.on('blur', rerouteOff); popup.style.pointerEvents = 'auto';
const onKeyDown = event => {
if (event.which === 9 && !event.ctrlKey && !event.altKey && !event.metaKey) {
const search = $('#search-replace-dialog');
const area = search && search.contains(document.activeElement) ? search : popup;
moveFocus(area, event.shiftKey ? -1 : 1);
event.preventDefault();
}
};
window.addEventListener('keydown', onKeyDown, true);
window.addEventListener('closeHelp', function _() { window.addEventListener('closeHelp', function _() {
window.removeEventListener('closeHelp', _); window.removeEventListener('closeHelp', _);
cm.off('focus', rerouteOn); window.removeEventListener('keydown', onKeyDown, true);
cm.off('blur', rerouteOff); document.documentElement.style.removeProperty('pointer-events');
cm.rerouteHotkeys(true);
cm = popup.codebox = null; cm = popup.codebox = null;
}); });
return popup; return popup;
} }

118
edit/editor-worker-body.js Normal file
View File

@ -0,0 +1,118 @@
/* global importScripts parseMozFormat parserlib CSSLint require */
'use strict';
createAPI({
csslint: (code, config) => {
loadParserLib();
loadScript(['/vendor-overwrites/csslint/csslint.js']);
return CSSLint.verify(code, config).messages
.map(m => Object.assign(m, {rule: {id: m.rule.id}}));
},
stylelint: (code, config) => {
loadScript(['/vendor/stylelint-bundle/stylelint-bundle.min.js']);
return require('stylelint').lint({code, config});
},
parseMozFormat: data => {
loadParserLib();
loadScript(['/js/moz-parser.js']);
return parseMozFormat(data);
},
getStylelintRules,
getCsslintRules
});
function getCsslintRules() {
loadScript(['/vendor-overwrites/csslint/csslint.js']);
return CSSLint.getRules().map(rule => {
const output = {};
for (const [key, value] of Object.entries(rule)) {
if (typeof value !== 'function') {
output[key] = value;
}
}
return output;
});
}
function getStylelintRules() {
loadScript(['/vendor/stylelint-bundle/stylelint-bundle.min.js']);
const stylelint = require('stylelint');
const options = {};
const rxPossible = /\bpossible:("(?:[^"]*?)"|\[(?:[^\]]*?)\]|\{(?:[^}]*?)\})/g;
const rxString = /"([-\w\s]{3,}?)"/g;
for (const id of Object.keys(stylelint.rules)) {
const ruleCode = String(stylelint.rules[id]);
const sets = [];
let m, mStr;
while ((m = rxPossible.exec(ruleCode))) {
const possible = m[1];
const set = [];
while ((mStr = rxString.exec(possible))) {
const s = mStr[1];
if (s.includes(' ')) {
set.push(...s.split(/\s+/));
} else {
set.push(s);
}
}
if (possible.includes('ignoreAtRules')) {
set.push('ignoreAtRules');
}
if (possible.includes('ignoreShorthands')) {
set.push('ignoreShorthands');
}
if (set.length) {
sets.push(set);
}
}
if (sets.length) {
options[id] = sets;
}
}
return options;
}
function loadParserLib() {
if (typeof parserlib !== 'undefined') {
return;
}
importScripts('/vendor-overwrites/csslint/parserlib.js');
parserlib.css.Tokens[parserlib.css.Tokens.COMMENT].hide = false;
}
const loadedUrls = new Set();
function loadScript(urls) {
urls = urls.filter(u => !loadedUrls.has(u));
importScripts(...urls);
urls.forEach(u => loadedUrls.add(u));
}
function createAPI(methods) {
self.onmessage = e => {
const message = e.data;
Promise.resolve()
.then(() => methods[message.action](...message.args))
.then(result => ({
id: message.id,
error: false,
data: result
}))
.catch(err => ({
id: message.id,
error: true,
data: cloneError(err)
}))
.then(data => self.postMessage(data));
};
}
function cloneError(err) {
return Object.assign({
name: err.name,
stack: err.stack,
message: err.message,
lineNumber: err.lineNumber,
columnNumber: err.columnNumber,
fileName: err.fileName
}, err);
}

39
edit/editor-worker.js Normal file
View File

@ -0,0 +1,39 @@
'use strict';
// eslint-disable-next-line no-var
var editorWorker = (() => {
let worker;
return new Proxy({}, {
get: (target, prop) =>
(...args) => {
if (!worker) {
worker = createWorker();
}
return worker.invoke(prop, args);
}
});
function createWorker() {
let id = 0;
const pendingResponse = new Map();
const worker = new Worker('/edit/editor-worker-body.js');
worker.onmessage = e => {
const message = e.data;
pendingResponse.get(message.id)[message.error ? 'reject' : 'resolve'](message.data);
pendingResponse.delete(message.id);
};
return {invoke};
function invoke(action, args) {
return new Promise((resolve, reject) => {
pendingResponse.set(id, {resolve, reject});
worker.postMessage({
id,
action,
args
});
id++;
});
}
}
})();

View File

@ -565,6 +565,7 @@ onDOMready().then(() => {
Object.assign(dialog, DIALOG_PROPS.dialog); Object.assign(dialog, DIALOG_PROPS.dialog);
dialog.addEventListener('focusout', EVENTS.onfocusout); dialog.addEventListener('focusout', EVENTS.onfocusout);
dialog.dataset.type = type; dialog.dataset.type = type;
dialog.style.pointerEvents = 'auto';
const content = $('[data-type="content"]', dialog); const content = $('[data-type="content"]', dialog);
content.parentNode.replaceChild(template[type].cloneNode(true), content); content.parentNode.replaceChild(template[type].cloneNode(true), content);
@ -881,7 +882,7 @@ onDOMready().then(() => {
function restoreWindowScrollPos({immediately = true} = {}) { function restoreWindowScrollPos({immediately = true} = {}) {
if (!immediately) { if (!immediately) {
// run in the next microtask cycle // run in the next microtask cycle
new Promise(() => restoreWindowScrollPos({immediately: true})); Promise.resolve().then(restoreWindowScrollPos);
return; return;
} }
if (window.scrollX !== state.scrollX || window.scrollY !== state.scrollY) { if (window.scrollX !== state.scrollX || window.scrollY !== state.scrollY) {

View File

@ -1,37 +0,0 @@
/* global CodeMirror linterConfig */
'use strict';
(() => {
CodeMirror.registerHelper('lint', 'csslint', invokeHelper);
CodeMirror.registerHelper('lint', 'stylelint', invokeHelper);
const cookResults = {
csslint: results =>
results.map(({line, col: ch, message, rule, type: severity}) => line && {
message,
from: {line: line - 1, ch: ch - 1},
to: {line: line - 1, ch},
rule: rule.id,
severity,
}).filter(Boolean),
stylelint: ({results}) =>
!results[0] && [] ||
results[0].warnings.map(({line, column: ch, text, severity}) => ({
from: {line: line - 1, ch: ch - 1},
to: {line: line - 1, ch},
message: text
.replace('Unexpected ', '')
.replace(/^./, firstLetter => firstLetter.toUpperCase())
.replace(/\s*\([^(]+\)$/, ''), // strip the rule,
rule: text.replace(/^.*?\s*\(([^(]+)\)$/, '$1'),
severity,
})),
};
function invokeHelper(code) {
const config = linterConfig.getCurrent();
return linterConfig.invokeWorker({code, config})
.then(cookResults[linterConfig.getName()]);
}
})();

View File

@ -1,50 +0,0 @@
'use strict';
/**
* CSSLint Config values
* 0 = disabled; 1 = warning; 2 = error
*/
window.linterConfig.defaults.csslint = {
// Default warnings
'display-property-grouping': 1,
'duplicate-properties': 1,
'empty-rules': 1,
'errors': 1,
'warnings': 1,
'known-properties': 1,
// Default disabled
'adjoining-classes': 0,
'box-model': 0,
'box-sizing': 0,
'bulletproof-font-face': 0,
'compatible-vendor-prefixes': 0,
'duplicate-background-images': 0,
'fallback-colors': 0,
'floats': 0,
'font-faces': 0,
'font-sizes': 0,
'gradients': 0,
'ids': 0,
'import': 0,
'import-ie-limit': 0,
'important': 0,
'order-alphabetical': 0,
'outline-none': 0,
'overqualified-elements': 0,
'qualified-headings': 0,
'regex-selectors': 0,
'rules-count': 0,
'selector-max': 0,
'selector-max-approaching': 0,
'selector-newline': 0,
'shorthand': 0,
'star-property-hack': 0,
'text-indent': 0,
'underscore-property-hack': 0,
'unique-headings': 0,
'universal-selector': 0,
'unqualified-attributes': 0,
'vendor-prefix': 0,
'zero-units': 0
};

View File

@ -1,170 +0,0 @@
'use strict';
window.linterConfig.defaults.stylelint = (defaultSeverity => ({
// 'sugarss' is a indent-based syntax like Sass or Stylus
// ref: https://github.com/postcss/postcss#syntaxes
syntax: 'sugarss',
// ** recommended rules **
// ref: https://github.com/stylelint/stylelint-config-recommended/blob/master/index.js
rules: {
'at-rule-no-unknown': [true, defaultSeverity],
'block-no-empty': [true, defaultSeverity],
'color-no-invalid-hex': [true, defaultSeverity],
'declaration-block-no-duplicate-properties': [true, {
'ignore': ['consecutive-duplicates-with-different-values'],
'severity': 'warning'
}],
'declaration-block-no-shorthand-property-overrides': [true, defaultSeverity],
'font-family-no-duplicate-names': [true, defaultSeverity],
'function-calc-no-unspaced-operator': [true, defaultSeverity],
'function-linear-gradient-no-nonstandard-direction': [true, defaultSeverity],
'keyframe-declaration-no-important': [true, defaultSeverity],
'media-feature-name-no-unknown': [true, defaultSeverity],
/* recommended true */
'no-empty-source': false,
'no-extra-semicolons': [true, defaultSeverity],
'no-invalid-double-slash-comments': [true, defaultSeverity],
'property-no-unknown': [true, defaultSeverity],
'selector-pseudo-class-no-unknown': [true, defaultSeverity],
'selector-pseudo-element-no-unknown': [true, defaultSeverity],
'selector-type-no-unknown': false, // for scss/less/stylus-lang
'string-no-newline': [true, defaultSeverity],
'unit-no-unknown': [true, defaultSeverity],
// ** non-essential rules
'comment-no-empty': false,
'declaration-block-no-redundant-longhand-properties': false,
'shorthand-property-no-redundant-values': false,
// ** stylistic rules **
/*
'at-rule-empty-line-before': [
'always',
{
'except': [
'blockless-after-same-name-blockless',
'first-nested'
],
'ignore': [
'after-comment'
]
}
],
'at-rule-name-case': 'lower',
'at-rule-name-space-after': 'always-single-line',
'at-rule-semicolon-newline-after': 'always',
'block-closing-brace-empty-line-before': 'never',
'block-closing-brace-newline-after': 'always',
'block-closing-brace-newline-before': 'always-multi-line',
'block-closing-brace-space-before': 'always-single-line',
'block-opening-brace-newline-after': 'always-multi-line',
'block-opening-brace-space-after': 'always-single-line',
'block-opening-brace-space-before': 'always',
'color-hex-case': 'lower',
'color-hex-length': 'short',
'comment-empty-line-before': [
'always',
{
'except': [
'first-nested'
],
'ignore': [
'stylelint-commands'
]
}
],
'comment-whitespace-inside': 'always',
'custom-property-empty-line-before': [
'always',
{
'except': [
'after-custom-property',
'first-nested'
],
'ignore': [
'after-comment',
'inside-single-line-block'
]
}
],
'declaration-bang-space-after': 'never',
'declaration-bang-space-before': 'always',
'declaration-block-semicolon-newline-after': 'always-multi-line',
'declaration-block-semicolon-space-after': 'always-single-line',
'declaration-block-semicolon-space-before': 'never',
'declaration-block-single-line-max-declarations': 1,
'declaration-block-trailing-semicolon': 'always',
'declaration-colon-newline-after': 'always-multi-line',
'declaration-colon-space-after': 'always-single-line',
'declaration-colon-space-before': 'never',
'declaration-empty-line-before': [
'always',
{
'except': [
'after-declaration',
'first-nested'
],
'ignore': [
'after-comment',
'inside-single-line-block'
]
}
],
'function-comma-newline-after': 'always-multi-line',
'function-comma-space-after': 'always-single-line',
'function-comma-space-before': 'never',
'function-max-empty-lines': 0,
'function-name-case': 'lower',
'function-parentheses-newline-inside': 'always-multi-line',
'function-parentheses-space-inside': 'never-single-line',
'function-whitespace-after': 'always',
'indentation': 2,
'length-zero-no-unit': true,
'max-empty-lines': 1,
'media-feature-colon-space-after': 'always',
'media-feature-colon-space-before': 'never',
'media-feature-name-case': 'lower',
'media-feature-parentheses-space-inside': 'never',
'media-feature-range-operator-space-after': 'always',
'media-feature-range-operator-space-before': 'always',
'media-query-list-comma-newline-after': 'always-multi-line',
'media-query-list-comma-space-after': 'always-single-line',
'media-query-list-comma-space-before': 'never',
'no-eol-whitespace': true,
'no-missing-end-of-source-newline': true,
'number-leading-zero': 'always',
'number-no-trailing-zeros': true,
'property-case': 'lower',
'rule-empty-line-before': [
'always-multi-line',
{
'except': [
'first-nested'
],
'ignore': [
'after-comment'
]
}
],
'selector-attribute-brackets-space-inside': 'never',
'selector-attribute-operator-space-after': 'never',
'selector-attribute-operator-space-before': 'never',
'selector-combinator-space-after': 'always',
'selector-combinator-space-before': 'always',
'selector-descendant-combinator-no-non-space': true,
'selector-list-comma-newline-after': 'always',
'selector-list-comma-space-before': 'never',
'selector-max-empty-lines': 0,
'selector-pseudo-class-case': 'lower',
'selector-pseudo-class-parentheses-space-inside': 'never',
'selector-pseudo-element-case': 'lower',
'selector-pseudo-element-colon-notation': 'double',
'selector-type-case': 'lower',
'unit-case': 'lower',
'value-list-comma-newline-after': 'always-multi-line',
'value-list-comma-space-after': 'always-single-line',
'value-list-comma-space-before': 'never',
'value-list-max-empty-lines': 0
*/
}
}))({severity: 'warning'});

View File

@ -1,573 +0,0 @@
/* global CodeMirror messageBox */
/* global editors makeSectionVisible showCodeMirrorPopup showHelp */
/* global loadScript require CSSLint stylelint */
'use strict';
onDOMready().then(loadLinterAssets);
// eslint-disable-next-line no-var
var linterConfig = {
csslint: {},
stylelint: {},
defaults: {
// set in lint-defaults-csslint.js
csslint: {},
// set in lint-defaults-stylelint.js
stylelint: {},
},
storageName: {
csslint: 'editorCSSLintConfig',
stylelint: 'editorStylelintConfig',
},
worker: {
csslint: {path: '/edit/csslint-loader.js'},
stylelint: {path: '/edit/stylelint-loader.js'},
},
allRuleIds: {
csslint: null,
stylelint: null,
},
getName() {
// some dirty hacks to override editor.linter getting from prefs
const linter = prefs.get('editor.linter');
const mode = linter && editors[0] && editors[0].doc.mode;
return mode && ((mode.name || mode) !== 'css' || mode.helperType) ? 'stylelint' : linter;
},
getCurrent(linter = linterConfig.getName()) {
return this.fallbackToDefaults(this[linter] || {});
},
getForCodeMirror(linter = linterConfig.getName()) {
return CodeMirror.lint && CodeMirror.lint[linter] ? {
getAnnotations: CodeMirror.lint[linter],
delay: prefs.get('editor.lintDelay'),
onUpdateLinting(annotationsNotSorted, annotations, cm) {
updateLintReport(cm);
},
} : false;
},
fallbackToDefaults(config, linter = linterConfig.getName()) {
if (config && Object.keys(config).length) {
if (linter === 'stylelint') {
// always use default syntax because we don't expose it in config UI
config.syntax = this.defaults.stylelint.syntax;
}
return Object.assign({}, this.defaults[linter] || {}, config);
} else {
return deepCopy(this.defaults[linter] || {});
}
},
setLinter(linter = linterConfig.getName()) {
linter = linter.toLowerCase();
linter = linter === 'csslint' || linter === 'stylelint' ? linter : '';
if (linterConfig.getName() !== linter) {
prefs.set('editor.linter', linter);
}
return linter;
},
invokeWorker(message) {
const worker = linterConfig.worker[message.linter || linterConfig.getName()];
if (!worker.queue) {
worker.queue = [];
worker.instance.onmessage = ({data}) => {
worker.queue.shift().resolve(data);
if (worker.queue.length) {
worker.instance.postMessage(worker.queue[0].message);
}
};
}
return new Promise(resolve => {
worker.queue.push({message, resolve});
if (worker.queue.length === 1) {
worker.instance.postMessage(message);
}
});
},
getAllRuleIds(linter = linterConfig.getName()) {
return Promise.resolve(
this.allRuleIds[linter] ||
this.invokeWorker({linter, action: 'getAllRuleIds'})
.then(ids => (this.allRuleIds[linter] = ids.sort()))
);
},
findInvalidRules(config, linter = linterConfig.getName()) {
return this.getAllRuleIds(linter).then(allRuleIds => {
const allRuleIdsSet = new Set(allRuleIds);
const rules = linter === 'stylelint' ? config.rules : config;
return Object.keys(rules).filter(rule => !allRuleIdsSet.has(rule));
});
},
stringify(config = this.getCurrent()) {
if (linterConfig.getName() === 'stylelint') {
config.syntax = undefined;
}
return JSON.stringify(config, null, 2)
.replace(/,\n\s+\{\n\s+("severity":\s"\w+")\n\s+\}/g, ', {$1}');
},
save(config) {
config = this.fallbackToDefaults(config);
const linter = linterConfig.getName();
this[linter] = config;
chromeSync.setLZValue(this.storageName[linter], config);
return config;
},
loadAll() {
return chromeSync.getLZValues([
'editorCSSLintConfig',
'editorStylelintConfig',
]).then(data => {
this.csslint = this.fallbackToDefaults(data.editorCSSLintConfig, 'csslint');
this.stylelint = this.fallbackToDefaults(data.editorStylelintConfig, 'stylelint');
});
},
watchStorage() {
chrome.storage.onChanged.addListener((changes, area) => {
if (area === 'sync') {
for (const name of ['editorCSSLintConfig', 'editorStylelintConfig']) {
if (name in changes && changes[name].newValue !== changes[name].oldValue) {
this.loadAll().then(updateLinter);
break;
}
}
}
});
},
// this is an event listener so it can't refer to self via 'this'
openOnClick(event) {
event.preventDefault();
setupLinterPopup(linterConfig.stringify());
},
showSavedMessage() {
$('#help-popup .saved-message').classList.add('show');
clearTimeout($('#help-popup .contents').timer);
$('#help-popup .contents').timer = setTimeout(() => {
// popup may be closed at this point
const msg = $('#help-popup .saved-message');
if (msg) {
msg.classList.remove('show');
}
}, 2000);
},
init() {
if (!this.init.pending) this.init.pending = this.loadAll();
return this.init.pending;
}
};
function initLint() {
$('#lint-help').addEventListener('click', showLintHelp);
$('#lint').addEventListener('click', gotoLintIssue);
$('#linter-settings').addEventListener('click', linterConfig.openOnClick);
updateLinter();
linterConfig.watchStorage();
prefs.subscribe(['editor.linter'], updateLinter);
}
function updateLinter({immediately, linter = linterConfig.getName()} = {}) {
if (!immediately) {
debounce(updateLinter, 0, {immediately: true, linter});
return;
}
const GUTTERS_CLASS = 'CodeMirror-lint-markers';
Promise.all([
linterConfig.init(),
loadLinterAssets(linter)
]).then(updateEditors);
$('#linter-settings').style.display = !linter ? 'none' : 'inline-block';
$('#lint').classList.add('hidden');
function updateEditors() {
CodeMirror.defaults.lint = linterConfig.getForCodeMirror(linter);
const guttersOption = prepareGuttersOption();
editors.forEach(cm => {
if (cm.options.lint !== CodeMirror.defaults.lint) {
cm.setOption('lint', CodeMirror.defaults.lint);
}
if (guttersOption) {
cm.setOption('guttersOption', guttersOption);
updateGutters(cm, guttersOption);
cm.refresh();
}
setTimeout(updateLintReport, 0, cm);
});
}
function prepareGuttersOption() {
const gutters = CodeMirror.defaults.gutters;
const needRefresh = Boolean(linter) !== gutters.includes(GUTTERS_CLASS);
if (needRefresh) {
if (linter) {
gutters.push(GUTTERS_CLASS);
} else {
gutters.splice(gutters.indexOf(GUTTERS_CLASS), 1);
}
}
return needRefresh && gutters;
}
function updateGutters(cm, guttersOption) {
cm.options.gutters = guttersOption;
const el = $('.' + GUTTERS_CLASS, cm.display.gutters);
if (linter && !el) {
cm.display.gutters.appendChild($create('.CodeMirror-gutter ' + GUTTERS_CLASS));
} else if (!linter && el) {
el.remove();
}
}
}
function updateLintReport(cm, delay) {
const state = cm && cm.state && cm.state.lint || {};
if (delay === 0) {
// immediately show pending csslint/stylelint messages in onbeforeunload and save
clearTimeout(state.lintTimeout);
updateLintReportInternal(cm);
return;
}
if (delay > 0) {
clearTimeout(state.lintTimeout);
state.lintTimeout = setTimeout(cm => {
if (cm.performLint) {
cm.performLint();
updateLintReportInternal(cm);
}
}, delay, cm);
return;
}
if (state.options) {
clearTimeout(state.reportTimeout);
const delay = cm && cm.state.renderLintReportNow ? 0 : state.options.delay + 100;
state.reportTimeout = setTimeout(updateLintReportInternal, delay, cm, {
postponeNewIssues: delay === undefined || delay === null
});
}
}
function updateLintReportInternal(scope, {postponeNewIssues} = {}) {
const {changed, fixedSome} = (scope ? [scope] : editors).reduce(process, {});
if (changed) {
const renderNow = editors.last.state.renderLintReportNow =
!postponeNewIssues || fixedSome || editors.last.state.renderLintReportNow;
debounce(renderLintReport, renderNow ? 0 : CodeMirror.defaults.lintReportDelay, true);
}
function process(result, cm) {
const lintState = cm.state.lint || {};
const oldMarkers = lintState.stylusMarkers || new Map();
const newMarkers = lintState.stylusMarkers = new Map();
const oldText = (lintState.body || {}).textContentCached || '';
const activeLine = cm.getCursor().line;
const body = !(lintState.marked || {}).length ? {} :
$create('tbody', lintState.marked.map(mark => {
const info = mark.__annotation;
const {line, ch} = info.from;
const isActiveLine = line === activeLine;
const pos = isActiveLine ? 'cursor' : (line + ',' + ch);
const title = clipString(info.message, 1000) + `\n(${info.rule})`;
const message = clipString(info.message, 100);
if (isActiveLine || oldMarkers[pos] === message) {
oldMarkers.delete(pos);
}
newMarkers.set(pos, message);
return $create(`tr.${info.severity}`, [
$create('td', {attributes: {role: 'severity'}, dataset: {rule: info.rule}},
$create('.CodeMirror-lint-marker-' + info.severity, info.severity)),
$create('td', {attributes: {role: 'line'}}, line + 1),
$create('td', {attributes: {role: 'sep'}}, ':'),
$create('td', {attributes: {role: 'col'}}, ch + 1),
$create('td', {attributes: {role: 'message'}, title}, message),
]);
}));
body.textContentCached = body.textContent || '';
lintState.body = body.textContentCached && body;
result.changed |= oldText !== body.textContentCached;
result.fixedSome |= lintState.reportDisplayed && oldMarkers.size;
return result;
}
function clipString(str, limit) {
return str.length <= limit ? str : str.substr(0, limit) + '...';
}
}
function renderLintReport(someBlockChanged) {
const container = $('#lint');
const content = container.children[1];
const label = t('sectionCode');
const newContent = content.cloneNode(false);
let issueCount = 0;
editors.forEach((cm, index) => {
cm.state.renderLintReportNow = false;
const lintState = cm.state.lint || {};
const body = lintState.body;
if (!body) {
return;
}
const newBlock = $create('table', {cm}, [
$create('caption', label + ' ' + (index + 1)),
body,
]);
newContent.appendChild(newBlock);
issueCount += newBlock.rows.length;
const block = content.children[newContent.children.length - 1];
const blockChanged =
!block ||
block.cm !== cm ||
body.textContentCached !== block.textContentCached;
someBlockChanged |= blockChanged;
lintState.reportDisplayed = blockChanged;
});
if (someBlockChanged || newContent.children.length !== content.children.length) {
$('#issue-count').textContent = issueCount;
container.replaceChild(newContent, content);
container.classList.toggle('hidden', !newContent.children.length);
}
}
function gotoLintIssue(event) {
const issue = event.target.closest('tr');
if (!issue) {
return;
}
const block = issue.closest('table');
makeSectionVisible(block.cm);
block.cm.focus();
block.cm.setSelection({
line: parseInt($('td[role="line"]', issue).textContent) - 1,
ch: parseInt($('td[role="col"]', issue).textContent) - 1
});
}
function showLintHelp() {
const linter = linterConfig.getName();
const baseUrl = linter === 'stylelint'
? 'https://stylelint.io/user-guide/rules/'
// some CSSLint rules do not have a url
: 'https://github.com/CSSLint/csslint/issues/535';
let headerLink, template, csslintRules;
if (linter === 'csslint') {
headerLink = $createLink('https://github.com/CSSLint/csslint/wiki/Rules', 'CSSLint');
template = ruleID => {
const rule = csslintRules.find(rule => rule.id === ruleID);
return rule &&
$create('li', [
$create('b', $createLink(rule.url || baseUrl, rule.name)),
$create('br'),
rule.desc,
]);
};
} else {
headerLink = $createLink(baseUrl, 'stylelint');
template = rule =>
$create('li',
rule === 'CssSyntaxError' ? rule : $createLink(baseUrl + rule, rule));
}
const header = t('linterIssuesHelp', '\x01').split('\x01');
const activeRules = new Set($$('#lint td[role="severity"]').map(el => el.dataset.rule));
Promise.resolve(linter !== 'csslint' || linterConfig.invokeWorker({action: 'getAllRuleInfos'}))
.then(data => {
csslintRules = data;
showHelp(t('linterIssues'),
$create([
header[0], headerLink, header[1],
$create('ul.rules', [...activeRules.values()].map(template)),
])
);
});
}
function showLinterErrorMessage(title, contents, popup) {
messageBox({
title,
contents,
className: 'danger center lint-config',
buttons: [t('confirmOK')],
}).then(() => popup && popup.codebox && popup.codebox.focus());
}
function setupLinterPopup(config) {
const linter = linterConfig.getName();
const linterTitle = linter === 'stylelint' ? 'Stylelint' : 'CSSLint';
const defaultConfig = linterConfig.stringify(linterConfig.defaults[linter] || {});
const title = t('linterConfigPopupTitle', linterTitle);
const popup = showCodeMirrorPopup(title, null, {
lint: false,
extraKeys: {'Ctrl-Enter': save},
hintOptions: {hint},
});
$('.contents', popup).appendChild(makeFooter());
let cm = popup.codebox;
cm.focus();
cm.setValue(config);
cm.clearHistory();
cm.markClean();
cm.on('changes', updateButtonState);
updateButtonState();
cm.rerouteHotkeys(false);
window.addEventListener('closeHelp', function _() {
window.removeEventListener('closeHelp', _);
cm.rerouteHotkeys(true);
cm = null;
});
loadScript([
'/vendor/codemirror/mode/javascript/javascript.js',
'/vendor/codemirror/addon/lint/json-lint.js',
'/vendor/jsonlint/jsonlint.js'
]).then(() => {
cm.setOption('mode', 'application/json');
cm.setOption('lint', 'json');
});
function makeFooter() {
return $create('div', [
$create('p', [
$createLink(
linter === 'stylelint'
? 'https://stylelint.io/user-guide/rules/'
: 'https://github.com/CSSLint/csslint/wiki/Rules-by-ID',
t('linterRulesLink')),
linter === 'csslint' ? ' ' + t('linterCSSLintSettings') : '',
]),
$create('button.save', {onclick: save, title: 'Ctrl-Enter'}, t('styleSaveLabel')),
$create('button.cancel', {onclick: cancel}, t('confirmClose')),
$create('button.reset', {onclick: reset, title: t('linterResetMessage')}, t('genericResetLabel')),
$create('span.saved-message', t('genericSavedMessage')),
]);
}
function save(event) {
if (event instanceof Event) {
event.preventDefault();
}
const json = tryJSONparse(cm.getValue());
if (!json) {
showLinterErrorMessage(linter, t('linterJSONError'), popup);
cm.focus();
return;
}
linterConfig.findInvalidRules(json, linter).then(invalid => {
if (invalid.length) {
showLinterErrorMessage(linter, [
t('linterInvalidConfigError'),
$create('ul', invalid.map(name => $create('li', name))),
], popup);
return;
}
linterConfig.setLinter(linter);
linterConfig.save(json);
linterConfig.showSavedMessage();
cm.markClean();
cm.focus();
updateButtonState();
});
}
function reset(event) {
event.preventDefault();
linterConfig.setLinter(linter);
cm.setValue(defaultConfig);
cm.focus();
updateButtonState();
}
function cancel(event) {
event.preventDefault();
$('.dismiss').dispatchEvent(new Event('click'));
}
function updateButtonState() {
$('.save', popup).disabled = cm.isClean();
$('.reset', popup).disabled = cm.getValue() === defaultConfig;
$('.cancel', popup).textContent = t(cm.isClean() ? 'confirmClose' : 'confirmCancel');
}
function hint(cm) {
return Promise.all([
linterConfig.getAllRuleIds(linter),
linter !== 'stylelint' || hint.allOptions ||
linterConfig.invokeWorker({action: 'getAllRuleOptions', linter})
.then(options => (hint.allOptions = options)),
])
.then(([ruleIds, options]) => {
const cursor = cm.getCursor();
const {start, end, string, type, state: {lexical}} = cm.getTokenAt(cursor);
const {line, ch} = cursor;
const quoted = string.startsWith('"');
const leftPart = string.slice(quoted ? 1 : 0, ch - start).trim();
const depth = getLexicalDepth(lexical);
const search = cm.getSearchCursor(/"([-\w]+)"/, {line, ch: start - 1});
let [, prevWord] = search.find(true) || [];
let words = [];
if (depth === 1 && linter === 'stylelint') {
words = quoted ? ['rules'] : [];
} else if ((depth === 1 || depth === 2) && type && type.includes('property')) {
words = ruleIds;
} else if (depth === 2 || depth === 3 && lexical.type === ']') {
words = !quoted ? ['true', 'false', 'null'] :
ruleIds.includes(prevWord) && (options[prevWord] || [])[0] || [];
} else if (depth === 4 && prevWord === 'severity') {
words = ['error', 'warning'];
} else if (depth === 4) {
words = ['ignore', 'ignoreAtRules', 'except', 'severity'];
} else if (depth === 5 && lexical.type === ']' && quoted) {
while (prevWord && !ruleIds.includes(prevWord)) {
prevWord = (search.find(true) || [])[1];
}
words = (options[prevWord] || []).slice(-1)[0] || ruleIds;
}
return {
list: words.filter(word => word.startsWith(leftPart)),
from: {line, ch: start + (quoted ? 1 : 0)},
to: {line, ch: string.endsWith('"') ? end - 1 : end},
};
});
}
function getLexicalDepth(lexicalState) {
let depth = 0;
while ((lexicalState = lexicalState.prev)) {
depth++;
}
return depth;
}
}
function loadLinterAssets(name = linterConfig.getName()) {
const worker = linterConfig.worker[name];
if (!name || !worker) return Promise.resolve();
const scripts = [];
if (!worker.instance) {
worker.instance = new Worker(worker.path);
scripts.push(`/edit/lint-defaults-${name}.js`);
}
if (!CodeMirror.lint) {
scripts.push(
'/vendor/codemirror/addon/lint/lint.css',
'/vendor/codemirror/addon/lint/lint.js',
'/edit/lint-codemirror-helper.js');
}
return scripts.length ? loadScript(scripts) : Promise.resolve();
}

View File

@ -0,0 +1,196 @@
/* global memoize editorWorker showCodeMirrorPopup loadScript messageBox LINTER_DEFAULTS*/
'use strict';
(() => {
document.addEventListener('DOMContentLoaded', () => {
$('#linter-settings').addEventListener('click', showLintConfig);
}, {once: true});
function stringifyConfig(config) {
return JSON.stringify(config, null, 2)
.replace(/,\n\s+\{\n\s+("severity":\s"\w+")\n\s+\}/g, ', {$1}');
}
function showLinterErrorMessage(title, contents, popup) {
messageBox({
title,
contents,
className: 'danger center lint-config',
buttons: [t('confirmOK')],
}).then(() => popup && popup.codebox && popup.codebox.focus());
}
function showLintConfig() {
const linter = $('#editor.linter').value;
if (!linter) {
return;
}
const storageName = linter === 'styleint' ? 'editorStylelintConfig' : 'editorCSSLintConfig';
const getRules = memoize(linter === 'stylelint' ?
editorWorker.getStylelintRules : editorWorker.getCsslintRules);
const linterTitle = linter === 'stylelint' ? 'Stylelint' : 'CSSLint';
const defaultConfig = stringifyConfig(
linter === 'stylelint' ? LINTER_DEFAULTS.STYLELINT : LINTER_DEFAULTS.CSSLINT
);
const title = t('linterConfigPopupTitle', linterTitle);
const popup = showCodeMirrorPopup(title, null, {
lint: false,
extraKeys: {'Ctrl-Enter': save},
hintOptions: {hint},
});
$('.contents', popup).appendChild(makeFooter());
let cm = popup.codebox;
cm.focus();
chromeSync.getLZValue(storageName).then(config => {
cm.setValue(config ? stringifyConfig(config) : defaultConfig);
cm.clearHistory();
cm.markClean();
updateButtonState();
});
cm.on('changes', updateButtonState);
cm.rerouteHotkeys(false);
window.addEventListener('closeHelp', function _() {
window.removeEventListener('closeHelp', _);
cm.rerouteHotkeys(true);
cm = null;
});
loadScript([
'/vendor/codemirror/mode/javascript/javascript.js',
'/vendor/codemirror/addon/lint/json-lint.js',
'/vendor/jsonlint/jsonlint.js'
]).then(() => {
cm.setOption('mode', 'application/json');
cm.setOption('lint', true);
});
function findInvalidRules(config, linter) {
return getRules()
.then(rules => {
if (linter === 'stylelint') {
return Object.keys(config.rules).filter(k => !rules.hasOwnProperty(k));
}
const ruleSet = new Set(rules.map(r => r.id));
return Object.keys(config).filter(k => !ruleSet.has(k));
});
}
function makeFooter() {
return $create('div', [
$create('p', [
$createLink(
linter === 'stylelint'
? 'https://stylelint.io/user-guide/rules/'
: 'https://github.com/CSSLint/csslint/wiki/Rules-by-ID',
t('linterRulesLink')),
linter === 'csslint' ? ' ' + t('linterCSSLintSettings') : '',
]),
$create('.buttons', [
$create('button.save', {onclick: save, title: 'Ctrl-Enter'}, t('styleSaveLabel')),
$create('button.cancel', {onclick: cancel}, t('confirmClose')),
$create('button.reset', {onclick: reset, title: t('linterResetMessage')}, t('genericResetLabel')),
]),
]);
}
function save(event) {
if (event instanceof Event) {
event.preventDefault();
}
const json = tryJSONparse(cm.getValue());
if (!json) {
showLinterErrorMessage(linter, t('linterJSONError'), popup);
cm.focus();
return;
}
findInvalidRules(json, linter).then(invalid => {
if (invalid.length) {
showLinterErrorMessage(linter, [
t('linterInvalidConfigError'),
$create('ul', invalid.map(name => $create('li', name))),
], popup);
return;
}
chromeSync.setLZValue(storageName, json);
cm.markClean();
cm.focus();
updateButtonState();
});
}
function reset(event) {
event.preventDefault();
cm.setValue(defaultConfig);
cm.focus();
updateButtonState();
}
function cancel(event) {
event.preventDefault();
$('.dismiss').dispatchEvent(new Event('click'));
}
function updateButtonState() {
$('.save', popup).disabled = cm.isClean();
$('.reset', popup).disabled = cm.getValue() === defaultConfig;
$('.cancel', popup).textContent = t(cm.isClean() ? 'confirmClose' : 'confirmCancel');
}
function hint(cm) {
return getRules().then(rules => {
let ruleIds, options;
if (linter === 'stylelint') {
ruleIds = Object.keys(rules);
options = rules;
} else {
ruleIds = rules.map(r => r.id);
options = {};
}
const cursor = cm.getCursor();
const {start, end, string, type, state: {lexical}} = cm.getTokenAt(cursor);
const {line, ch} = cursor;
const quoted = string.startsWith('"');
const leftPart = string.slice(quoted ? 1 : 0, ch - start).trim();
const depth = getLexicalDepth(lexical);
const search = cm.getSearchCursor(/"([-\w]+)"/, {line, ch: start - 1});
let [, prevWord] = search.find(true) || [];
let words = [];
if (depth === 1 && linter === 'stylelint') {
words = quoted ? ['rules'] : [];
} else if ((depth === 1 || depth === 2) && type && type.includes('property')) {
words = ruleIds;
} else if (depth === 2 || depth === 3 && lexical.type === ']') {
words = !quoted ? ['true', 'false', 'null'] :
ruleIds.includes(prevWord) && (options[prevWord] || [])[0] || [];
} else if (depth === 4 && prevWord === 'severity') {
words = ['error', 'warning'];
} else if (depth === 4) {
words = ['ignore', 'ignoreAtRules', 'except', 'severity'];
} else if (depth === 5 && lexical.type === ']' && quoted) {
while (prevWord && !ruleIds.includes(prevWord)) {
prevWord = (search.find(true) || [])[1];
}
words = (options[prevWord] || []).slice(-1)[0] || ruleIds;
}
return {
list: words.filter(word => word.startsWith(leftPart)),
from: {line, ch: start + (quoted ? 1 : 0)},
to: {line, ch: string.endsWith('"') ? end - 1 : end},
};
});
}
function getLexicalDepth(lexicalState) {
let depth = 0;
while ((lexicalState = lexicalState.prev)) {
depth++;
}
return depth;
}
}
})();

219
edit/linter-defaults.js Normal file
View File

@ -0,0 +1,219 @@
'use strict';
// eslint-disable-next-line no-var
var LINTER_DEFAULTS = (() => {
const SEVERITY = {severity: 'warning'};
const STYLELINT = {
// 'sugarss' is a indent-based syntax like Sass or Stylus
// ref: https://github.com/postcss/postcss#syntaxes
// syntax: 'sugarss',
// ** recommended rules **
// ref: https://github.com/stylelint/stylelint-config-recommended/blob/master/index.js
rules: {
'at-rule-no-unknown': [true, SEVERITY],
'block-no-empty': [true, SEVERITY],
'color-no-invalid-hex': [true, SEVERITY],
'declaration-block-no-duplicate-properties': [true, {
'ignore': ['consecutive-duplicates-with-different-values'],
'severity': 'warning'
}],
'declaration-block-no-shorthand-property-overrides': [true, SEVERITY],
'font-family-no-duplicate-names': [true, SEVERITY],
'function-calc-no-unspaced-operator': [true, SEVERITY],
'function-linear-gradient-no-nonstandard-direction': [true, SEVERITY],
'keyframe-declaration-no-important': [true, SEVERITY],
'media-feature-name-no-unknown': [true, SEVERITY],
/* recommended true */
'no-empty-source': false,
'no-extra-semicolons': [true, SEVERITY],
'no-invalid-double-slash-comments': [true, SEVERITY],
'property-no-unknown': [true, SEVERITY],
'selector-pseudo-class-no-unknown': [true, SEVERITY],
'selector-pseudo-element-no-unknown': [true, SEVERITY],
'selector-type-no-unknown': false, // for scss/less/stylus-lang
'string-no-newline': [true, SEVERITY],
'unit-no-unknown': [true, SEVERITY],
// ** non-essential rules
'comment-no-empty': false,
'declaration-block-no-redundant-longhand-properties': false,
'shorthand-property-no-redundant-values': false,
// ** stylistic rules **
/*
'at-rule-empty-line-before': [
'always',
{
'except': [
'blockless-after-same-name-blockless',
'first-nested'
],
'ignore': [
'after-comment'
]
}
],
'at-rule-name-case': 'lower',
'at-rule-name-space-after': 'always-single-line',
'at-rule-semicolon-newline-after': 'always',
'block-closing-brace-empty-line-before': 'never',
'block-closing-brace-newline-after': 'always',
'block-closing-brace-newline-before': 'always-multi-line',
'block-closing-brace-space-before': 'always-single-line',
'block-opening-brace-newline-after': 'always-multi-line',
'block-opening-brace-space-after': 'always-single-line',
'block-opening-brace-space-before': 'always',
'color-hex-case': 'lower',
'color-hex-length': 'short',
'comment-empty-line-before': [
'always',
{
'except': [
'first-nested'
],
'ignore': [
'stylelint-commands'
]
}
],
'comment-whitespace-inside': 'always',
'custom-property-empty-line-before': [
'always',
{
'except': [
'after-custom-property',
'first-nested'
],
'ignore': [
'after-comment',
'inside-single-line-block'
]
}
],
'declaration-bang-space-after': 'never',
'declaration-bang-space-before': 'always',
'declaration-block-semicolon-newline-after': 'always-multi-line',
'declaration-block-semicolon-space-after': 'always-single-line',
'declaration-block-semicolon-space-before': 'never',
'declaration-block-single-line-max-declarations': 1,
'declaration-block-trailing-semicolon': 'always',
'declaration-colon-newline-after': 'always-multi-line',
'declaration-colon-space-after': 'always-single-line',
'declaration-colon-space-before': 'never',
'declaration-empty-line-before': [
'always',
{
'except': [
'after-declaration',
'first-nested'
],
'ignore': [
'after-comment',
'inside-single-line-block'
]
}
],
'function-comma-newline-after': 'always-multi-line',
'function-comma-space-after': 'always-single-line',
'function-comma-space-before': 'never',
'function-max-empty-lines': 0,
'function-name-case': 'lower',
'function-parentheses-newline-inside': 'always-multi-line',
'function-parentheses-space-inside': 'never-single-line',
'function-whitespace-after': 'always',
'indentation': 2,
'length-zero-no-unit': true,
'max-empty-lines': 1,
'media-feature-colon-space-after': 'always',
'media-feature-colon-space-before': 'never',
'media-feature-name-case': 'lower',
'media-feature-parentheses-space-inside': 'never',
'media-feature-range-operator-space-after': 'always',
'media-feature-range-operator-space-before': 'always',
'media-query-list-comma-newline-after': 'always-multi-line',
'media-query-list-comma-space-after': 'always-single-line',
'media-query-list-comma-space-before': 'never',
'no-eol-whitespace': true,
'no-missing-end-of-source-newline': true,
'number-leading-zero': 'always',
'number-no-trailing-zeros': true,
'property-case': 'lower',
'rule-empty-line-before': [
'always-multi-line',
{
'except': [
'first-nested'
],
'ignore': [
'after-comment'
]
}
],
'selector-attribute-brackets-space-inside': 'never',
'selector-attribute-operator-space-after': 'never',
'selector-attribute-operator-space-before': 'never',
'selector-combinator-space-after': 'always',
'selector-combinator-space-before': 'always',
'selector-descendant-combinator-no-non-space': true,
'selector-list-comma-newline-after': 'always',
'selector-list-comma-space-before': 'never',
'selector-max-empty-lines': 0,
'selector-pseudo-class-case': 'lower',
'selector-pseudo-class-parentheses-space-inside': 'never',
'selector-pseudo-element-case': 'lower',
'selector-pseudo-element-colon-notation': 'double',
'selector-type-case': 'lower',
'unit-case': 'lower',
'value-list-comma-newline-after': 'always-multi-line',
'value-list-comma-space-after': 'always-single-line',
'value-list-comma-space-before': 'never',
'value-list-max-empty-lines': 0
*/
}
};
const CSSLINT = {
// Default warnings
'display-property-grouping': 1,
'duplicate-properties': 1,
'empty-rules': 1,
'errors': 1,
'warnings': 1,
'known-properties': 1,
// Default disabled
'adjoining-classes': 0,
'box-model': 0,
'box-sizing': 0,
'bulletproof-font-face': 0,
'compatible-vendor-prefixes': 0,
'duplicate-background-images': 0,
'fallback-colors': 0,
'floats': 0,
'font-faces': 0,
'font-sizes': 0,
'gradients': 0,
'ids': 0,
'import': 0,
'import-ie-limit': 0,
'important': 0,
'order-alphabetical': 0,
'outline-none': 0,
'overqualified-elements': 0,
'qualified-headings': 0,
'regex-selectors': 0,
'rules-count': 0,
'selector-max': 0,
'selector-max-approaching': 0,
'selector-newline': 0,
'shorthand': 0,
'star-property-hack': 0,
'text-indent': 0,
'underscore-property-hack': 0,
'unique-headings': 0,
'universal-selector': 0,
'unqualified-attributes': 0,
'vendor-prefix': 0,
'zero-units': 0
};
return {STYLELINT, CSSLINT, SEVERITY};
})();

113
edit/linter-engines.js Normal file
View File

@ -0,0 +1,113 @@
/* global LINTER_DEFAULTS linter editorWorker */
'use strict';
(() => {
registerLinters({
csslint: {
storageName: 'editorCSSLintConfig',
lint: csslint,
validMode: mode => mode === 'css',
getConfig: config => Object.assign({}, LINTER_DEFAULTS.CSSLINT, config)
},
stylelint: {
storageName: 'editorStylelintConfig',
lint: stylelint,
validMode: () => true,
getConfig: config => ({
syntax: 'sugarss',
rules: Object.assign({}, LINTER_DEFAULTS.STYLELINT.rules, config && config.rules)
})
}
});
function stylelint(text, config, mode) {
return editorWorker.stylelint(text, config)
.then(({results}) => {
if (!results[0]) {
return [];
}
const output = results[0].warnings.map(({line, column: ch, text, severity}) =>
({
from: {line: line - 1, ch: ch - 1},
to: {line: line - 1, ch},
message: text
.replace('Unexpected ', '')
.replace(/^./, firstLetter => firstLetter.toUpperCase())
.replace(/\s*\([^(]+\)$/, ''), // strip the rule,
rule: text.replace(/^.*?\s*\(([^(]+)\)$/, '$1'),
severity,
})
);
return mode !== 'stylus' ?
output :
output.filter(({message}) =>
!message.includes('"@css"') || !message.includes('(at-rule-no-unknown)'));
});
}
function csslint(text, config) {
return editorWorker.csslint(text, config)
.then(results =>
results
.map(({line, col: ch, message, rule, type: severity}) => line && {
message,
from: {line: line - 1, ch: ch - 1},
to: {line: line - 1, ch},
rule: rule.id,
severity,
})
.filter(Boolean)
);
}
function registerLinters(engines) {
const configs = new Map();
chrome.storage.onChanged.addListener((changes, area) => {
if (area !== 'sync') {
return;
}
for (const [name, engine] of Object.entries(engines)) {
if (changes.hasOwnProperty(engine.storageName)) {
chromeSync.getLZValue(engine.storageName)
.then(config => {
configs.set(name, engine.getConfig(config));
linter.run();
});
}
}
});
linter.register((text, options, cm) => {
const selectedLinter = prefs.get('editor.linter');
if (!selectedLinter) {
return;
}
const mode = cm.getOption('mode');
if (engines[selectedLinter].validMode(mode)) {
return runLint(selectedLinter);
}
for (const [name, engine] of Object.entries(engines)) {
if (engine.validMode(mode)) {
return runLint(name);
}
}
function runLint(name) {
return getConfig(name)
.then(config => engines[name].lint(text, config, mode));
}
});
function getConfig(name) {
if (configs.has(name)) {
return Promise.resolve(configs.get(name));
}
return chromeSync.getLZValue(engines[name].storageName)
.then(config => {
configs.set(name, engines[name].getConfig(config));
return configs.get(name);
});
}
}
})();

View File

@ -0,0 +1,51 @@
/* global showHelp editorWorker memoize */
'use strict';
function createLinterHelpDialog(getIssues) {
let csslintRules;
const prepareCsslintRules = memoize(() =>
editorWorker.getCsslintRules()
.then(rules => {
csslintRules = rules;
})
);
return {show};
function show() {
// FIXME: implement a linterChooser?
const linter = $('#editor.linter').value;
const baseUrl = linter === 'stylelint'
? 'https://stylelint.io/user-guide/rules/'
// some CSSLint rules do not have a url
: 'https://github.com/CSSLint/csslint/issues/535';
let headerLink, template;
if (linter === 'csslint') {
headerLink = $createLink('https://github.com/CSSLint/csslint/wiki/Rules', 'CSSLint');
template = ({rule: ruleID}) => {
const rule = csslintRules.find(rule => rule.id === ruleID);
return rule &&
$create('li', [
$create('b', $createLink(rule.url || baseUrl, rule.name)),
$create('br'),
rule.desc,
]);
};
} else {
headerLink = $createLink(baseUrl, 'stylelint');
template = ({rule}) =>
$create('li',
rule === 'CssSyntaxError' ? rule : $createLink(baseUrl + rule, rule));
}
const header = t('linterIssuesHelp', '\x01').split('\x01');
const activeRules = getIssues();
Promise.resolve(linter === 'csslint' && prepareCsslintRules())
.then(() =>
showHelp(t('linterIssues'),
$create([
header[0], headerLink, header[1],
$create('ul.rules', [...activeRules.values()].map(template)),
])
)
);
}
}

47
edit/linter-meta.js Normal file
View File

@ -0,0 +1,47 @@
/* global linter */
'use strict';
function createMetaCompiler(cm) {
const updateListeners = [];
let meta = null;
let metaIndex = null;
let cache = [];
linter.register((text, options, _cm) => {
if (_cm !== cm) {
return;
}
const match = text.match(/\/\*\s*==userstyle==[\s\S]*?==\/userstyle==\s*\*\//i);
if (!match) {
return [];
}
if (match[0] === meta && match.index === metaIndex) {
return cache;
}
return API.parseUsercss({sourceCode: match[0], metaOnly: true})
.then(result => result.usercssData)
.then(result => {
for (const cb of updateListeners) {
cb(result);
}
meta = match[0];
metaIndex = match.index;
cache = [];
return cache;
}, err => {
meta = match[0];
metaIndex = match.index;
cache = [{
from: cm.posFromIndex((err.index || 0) + match.index),
to: cm.posFromIndex((err.index || 0) + match.index),
message: err.message,
severity: 'error'
}];
return cache;
});
});
return {
onUpdated: cb => updateListeners.push(cb)
};
}

165
edit/linter-report.js Normal file
View File

@ -0,0 +1,165 @@
/* global linter editors clipString createLinterHelpDialog makeSectionVisible */
'use strict';
// eslint-disable-next-line no-var
Object.assign(linter, (() => {
const tables = new Map();
const helpDialog = createLinterHelpDialog(getIssues);
document.addEventListener('DOMContentLoaded', () => {
$('#lint-help').addEventListener('click', helpDialog.show);
}, {once: true});
linter.onLintingUpdated((annotationsNotSorted, annotations, cm) => {
let table = tables.get(cm);
if (!table) {
table = createTable(cm);
tables.set(cm, table);
const container = $('.lint-report-container');
if (typeof editor === 'object') {
container.append(table.element);
} else {
const nextSibling = findNextSibling(tables, cm);
container.insertBefore(table.element, nextSibling && tables.get(nextSibling).element);
}
}
table.updateCaption();
table.updateAnnotations(annotations);
updateCount();
});
linter.onUnhook(cm => {
const table = tables.get(cm);
if (table) {
table.element.remove();
tables.delete(cm);
}
updateCount();
});
return {refreshReport};
function updateCount() {
const issueCount = Array.from(tables.values())
.reduce((sum, table) => sum + table.trs.length, 0);
$('#lint').classList.toggle('hidden', issueCount === 0);
$('#issue-count').textContent = issueCount;
}
function getIssues() {
const issues = new Set();
for (const table of tables.values()) {
for (const tr of table.trs) {
issues.add(tr.getAnnotation());
}
}
return issues;
}
function findNextSibling(tables, cm) {
let i = editors.indexOf(cm) + 1;
while (i < editors.length) {
if (tables.has(editors[i])) {
return editors[i];
}
i++;
}
}
function refreshReport() {
for (const table of tables.values()) {
table.updateCaption();
}
}
function createTable(cm) {
const caption = $create('caption');
const tbody = $create('tbody');
const table = $create('table', [caption, tbody]);
const trs = [];
return {
element: table,
trs,
updateAnnotations,
updateCaption
};
function updateCaption() {
caption.textContent = typeof editor === 'object' ?
'' : `${t('sectionCode')} ${editors.indexOf(cm) + 1}`;
}
function updateAnnotations(lines) {
let i = 0;
for (const anno of getAnnotations()) {
let tr;
if (i < trs.length) {
tr = trs[i];
} else {
tr = createTr();
trs.push(tr);
tbody.append(tr.element);
}
tr.update(anno);
i++;
}
if (i === 0) {
trs.length = 0;
tbody.textContent = '';
} else {
while (trs.length > i) {
trs.pop().element.remove();
}
}
table.classList.toggle('empty', trs.length === 0);
function *getAnnotations() {
for (const line of lines.filter(Boolean)) {
yield *line;
}
}
}
function createTr() {
let anno;
const severityIcon = $create('div');
const severity = $create('td', {attributes: {role: 'severity'}}, severityIcon);
const line = $create('td', {attributes: {role: 'line'}});
const col = $create('td', {attributes: {role: 'col'}});
const message = $create('td', {attributes: {role: 'message'}});
const trElement = $create('tr', {
onclick: () => gotoLintIssue(cm, anno)
}, [
severity,
line,
$create('td', {attributes: {role: 'sep'}}, ':'),
col,
message
]);
return {
element: trElement,
update,
getAnnotation: () => anno
};
function update(_anno) {
anno = _anno;
trElement.className = anno.severity;
severity.dataset.rule = anno.rule;
severityIcon.className = `CodeMirror-lint-marker-${anno.severity}`;
severityIcon.textContent = anno.severity;
line.textContent = anno.from.line + 1;
col.textContent = anno.from.ch + 1;
message.title = clipString(anno.message, 1000) + `\n(${anno.rule})`;
message.textContent = clipString(anno.message, 100);
}
}
}
function gotoLintIssue(cm, anno) {
makeSectionVisible(cm);
cm.focus();
cm.setSelection(anno.from);
}
})());

65
edit/linter.js Normal file
View File

@ -0,0 +1,65 @@
'use strict';
// eslint-disable-next-line no-var
var linter = (() => {
const lintingUpdatedListeners = [];
const unhookListeners = [];
const linters = [];
const cms = new Set();
return {
register,
run,
enableForEditor,
disableForEditor,
onLintingUpdated,
onUnhook
};
function onUnhook(cb) {
unhookListeners.push(cb);
}
function onLintingUpdated(cb) {
lintingUpdatedListeners.push(cb);
}
function onUpdateLinting(...args) {
for (const cb of lintingUpdatedListeners) {
cb(...args);
}
}
function enableForEditor(cm) {
cm.setOption('lint', {onUpdateLinting, getAnnotations});
cms.add(cm);
}
function disableForEditor(cm) {
cm.setOption('lint', false);
cms.delete(cm);
for (const cb of unhookListeners) {
cb(cm);
}
}
function register(linterFn) {
linters.push(linterFn);
}
function run() {
for (const cm of cms) {
cm.performLint();
}
}
function getAnnotations(...args) {
return Promise.all(linters.map(fn => fn(...args)))
.then(results => [].concat(...results.filter(Boolean)));
}
})();
// FIXME: this should be put inside edit.js
prefs.subscribe(['editor.linter'], () => {
linter.run();
});

View File

@ -1,10 +1,11 @@
/* /*
global CodeMirror global CodeMirror
global editors propertyToCss CssToProperty global editors propertyToCss CssToProperty
global onChange indicateCodeChange initHooks setCleanGlobal global onChange initHooks setCleanGlobal
global fromMozillaFormat maximizeCodeHeight toggleContextMenuDelete global fromMozillaFormat maximizeCodeHeight toggleContextMenuDelete
global setCleanItem updateTitle updateLintReportIfEnabled renderLintReport global setCleanItem updateTitle
global showAppliesToHelp beautify regExpTester setGlobalProgress setCleanSection global showAppliesToHelp beautify regExpTester setGlobalProgress setCleanSection
global clipString linter
*/ */
'use strict'; 'use strict';
@ -14,7 +15,7 @@ function initWithSectionStyle(style, codeIsUpdated) {
$('#url').href = style.url || ''; $('#url').href = style.url || '';
if (codeIsUpdated !== false) { if (codeIsUpdated !== false) {
editors.length = 0; editors.length = 0;
getSections().forEach(div => div.remove()); $('#sections').textContent = '';
addSections(style.sections.length ? style.sections : [{code: ''}]); addSections(style.sections.length ? style.sections : [{code: ''}]);
initHooks(); initHooks();
} }
@ -76,9 +77,12 @@ function addSections(sections, onAdded = () => {}) {
function addSection(event, section) { function addSection(event, section) {
const div = template.section.cloneNode(true); const div = template.section.cloneNode(true);
$('.applies-to-help', div).addEventListener('click', showAppliesToHelp, false); $('.applies-to-help', div).addEventListener('click', showAppliesToHelp);
$('.remove-section', div).addEventListener('click', removeSection, false); $('.remove-section', div).addEventListener('click', removeSection);
$('.add-section', div).addEventListener('click', addSection, false); $('.add-section', div).addEventListener('click', addSection);
$('.clone-section', div).addEventListener('click', cloneSection);
$('.move-section-up', div).addEventListener('click', moveSection);
$('.move-section-down', div).addEventListener('click', moveSection);
$('.beautify-section', div).addEventListener('click', beautify); $('.beautify-section', div).addEventListener('click', beautify);
const code = (section || {}).code || ''; const code = (section || {}).code || '';
@ -97,7 +101,7 @@ function addSection(event, section) {
} }
} }
if (!appliesToAdded) { if (!appliesToAdded) {
addAppliesTo(appliesTo); addAppliesTo(appliesTo, event && 'url-prefix', '');
} }
appliesTo.addEventListener('change', onChange); appliesTo.addEventListener('change', onChange);
@ -134,17 +138,21 @@ function addSection(event, section) {
const sections = $('#sections'); const sections = $('#sections');
let cm; let cm;
if (event) { if (event) {
const clickedSection = getSectionForChild(event.target); let clickedSection = event && getSectionForChild(event.target, {includeDeleted: true});
sections.insertBefore(div, clickedSection.nextElementSibling); clickedSection.insertAdjacentElement('afterend', div);
while (clickedSection && !clickedSection.matches('.section')) {
clickedSection = clickedSection.previousElementSibling;
}
const newIndex = getSections().indexOf(clickedSection) + 1; const newIndex = getSections().indexOf(clickedSection) + 1;
cm = setupCodeMirror(div, code, newIndex); cm = setupCodeMirror(div, code, newIndex);
makeSectionVisible(cm); makeSectionVisible(cm);
renderLintReport();
cm.focus(); cm.focus();
} else { } else {
sections.appendChild(div); sections.appendChild(div);
cm = setupCodeMirror(div, code); cm = setupCodeMirror(div, code);
} }
linter.enableForEditor(cm);
linter.refreshReport();
div.CodeMirror = cm; div.CodeMirror = cm;
setCleanSection(div); setCleanSection(div);
return div; return div;
@ -192,7 +200,31 @@ function addAppliesTo(list, type, value) {
if (toFocus) toFocus.focus(); if (toFocus) toFocus.focus();
} }
function setupCodeMirror(sectionDiv, code, index) { function cloneSection(event) {
const section = getSectionForChild(event.target);
addSection(event, getSectionsHashes([section]).pop());
setCleanItem($('#sections'), false);
updateTitle();
}
function moveSection(event) {
const section = getSectionForChild(event.target);
const dir = event.target.closest('.move-section-up') ? -1 : 1;
const cm = section.CodeMirror;
const index = editors.indexOf(cm);
const newIndex = (index + dir + editors.length) % editors.length;
const currentNextEl = section.nextElementSibling;
const newSection = editors[newIndex].getSection();
newSection.insertAdjacentElement('afterend', section);
section.parentNode.insertBefore(newSection, currentNextEl || null);
cm.focus();
editors[index] = editors[newIndex];
editors[newIndex] = cm;
setCleanItem($('#sections'), false);
updateTitle();
}
function setupCodeMirror(sectionDiv, code, index = editors.length) {
const cm = CodeMirror(wrapper => { const cm = CodeMirror(wrapper => {
$('.code-label', sectionDiv).insertAdjacentElement('afterend', wrapper); $('.code-label', sectionDiv).insertAdjacentElement('afterend', wrapper);
}, { }, {
@ -269,7 +301,7 @@ function setupCodeMirror(sectionDiv, code, index) {
}); });
}; };
editors.splice(index || editors.length, 0, cm); editors.splice(index, 0, cm);
return cm; return cm;
} }
@ -277,7 +309,6 @@ function indicateCodeChange(cm) {
const section = cm.getSection(); const section = cm.getSection();
setCleanItem(section, cm.isClean(section.savedValue)); setCleanItem(section, cm.isClean(section.savedValue));
updateTitle(); updateTitle();
updateLintReportIfEnabled(cm);
} }
function setupAutocomplete(cm, enable = true) { function setupAutocomplete(cm, enable = true) {
@ -385,17 +416,17 @@ function toggleSectionHeight(cm) {
} }
} }
function getSectionForChild(e) { function getSectionForChild(el, {includeDeleted} = {}) {
return e.closest('#sections > div'); return el.closest(`#sections > ${includeDeleted ? '*' : '.section'}`);
} }
function getSections() { function getSections() {
return $$('#sections > div'); return $$('#sections > .section');
} }
function getSectionsHashes() { function getSectionsHashes(elements = getSections()) {
const sections = []; const sections = [];
for (const div of getSections()) { for (const div of elements) {
const meta = {urls: [], urlPrefixes: [], domains: [], regexps: []}; const meta = {urls: [], urlPrefixes: [], domains: [], regexps: []};
for (const li of $('.applies-to-list', div).childNodes) { for (const li of $('.applies-to-list', div).childNodes) {
if (li.className === template.appliesToEverything.className) { if (li.className === template.appliesToEverything.className) {
@ -430,10 +461,36 @@ function removeAppliesTo(event) {
function removeSection(event) { function removeSection(event) {
const section = getSectionForChild(event.target); const section = getSectionForChild(event.target);
const cm = section.CodeMirror; const cm = section.CodeMirror;
if (event instanceof Event && (!cm.isClean() || !cm.isBlank())) {
const stub = template.deletedSection.cloneNode(true);
const MAX_LINES = 10;
const lines = [];
cm.doc.iter(0, MAX_LINES + 1, ({text}) => lines.push(text) && false);
stub.title = t('sectionCode') + '\n' +
'-'.repeat(20) + '\n' +
lines.slice(0, MAX_LINES).map(s => clipString(s, 100)).join('\n') +
(lines.length > MAX_LINES ? '\n...' : '');
$('.restore-section', stub).onclick = () => {
let el = stub;
while (el && !el.matches('.section')) {
el = el.previousElementSibling;
}
const index = el ? editors.indexOf(el) + 1 : 0;
editors.splice(index, 0, cm);
stub.parentNode.replaceChild(section, stub);
setCleanItem(section, false);
updateTitle();
cm.focus();
linter.enableForEditor(cm);
linter.refreshReport();
};
section.insertAdjacentElement('afterend', stub);
}
setCleanItem($('#sections'), false); setCleanItem($('#sections'), false);
removeAreaAndSetDirty(section); removeAreaAndSetDirty(section);
editors.splice(editors.indexOf(cm), 1); editors.splice(editors.indexOf(cm), 1);
renderLintReport(); linter.disableForEditor(cm);
linter.refreshReport();
} }
function removeAreaAndSetDirty(area) { function removeAreaAndSetDirty(area) {

View File

@ -1,10 +1,11 @@
/* /*
global editors styleId: true global editors styleId: true
global CodeMirror dirtyReporter global CodeMirror dirtyReporter
global updateLintReportIfEnabled initLint linterConfig updateLinter
global createAppliesToLineWidget messageBox global createAppliesToLineWidget messageBox
global sectionsToMozFormat global sectionsToMozFormat
global exclusions global exclusions
global beforeUnload
global createMetaCompiler linter
*/ */
'use strict'; 'use strict';
@ -13,14 +14,16 @@ function createSourceEditor(style) {
$('#save-button').disabled = true; $('#save-button').disabled = true;
$('#mozilla-format-container').remove(); $('#mozilla-format-container').remove();
$('#save-button').onclick = save; $('#save-button').onclick = save;
$('#header').addEventListener('wheel', headerOnScroll, {passive: true}); $('#header').addEventListener('wheel', headerOnScroll);
$('#sections').textContent = ''; $('#sections').textContent = '';
$('#sections').appendChild($create('.single-editor')); $('#sections').appendChild($create('.single-editor'));
const dirty = dirtyReporter(); const dirty = dirtyReporter();
dirty.onChange(() => { dirty.onChange(() => {
document.body.classList.toggle('dirty', dirty.isDirty()); const isDirty = dirty.isDirty();
$('#save-button').disabled = !dirty.isDirty(); window.onbeforeunload = isDirty ? beforeUnload : null;
document.body.classList.toggle('dirty', isDirty);
$('#save-button').disabled = !isDirty;
updateTitle(); updateTitle();
}); });
@ -42,7 +45,6 @@ function createSourceEditor(style) {
cm.on('changes', () => { cm.on('changes', () => {
dirty.modify('sourceGeneration', savedGeneration, cm.changeGeneration()); dirty.modify('sourceGeneration', savedGeneration, cm.changeGeneration());
updateLintReportIfEnabled(cm);
}); });
CodeMirror.commands.prevEditor = cm => nextPrevMozDocument(cm, -1); CodeMirror.commands.prevEditor = cm => nextPrevMozDocument(cm, -1);
@ -53,9 +55,17 @@ function createSourceEditor(style) {
cm.operation(initAppliesToLineWidget); cm.operation(initAppliesToLineWidget);
updateMeta().then(() => { const metaCompiler = createMetaCompiler(cm);
metaCompiler.onUpdated(meta => {
style.usercssData = meta;
style.name = meta.name;
style.url = meta.homepageURL;
updateMeta();
});
initLint(); linter.enableForEditor(cm);
updateMeta().then(() => {
let prevMode = NaN; let prevMode = NaN;
cm.on('optionChange', (cm, option) => { cm.on('optionChange', (cm, option) => {
@ -63,7 +73,7 @@ function createSourceEditor(style) {
const mode = getModeName(); const mode = getModeName();
if (mode === prevMode) return; if (mode === prevMode) return;
prevMode = mode; prevMode = mode;
updateLinter(); linter.run();
updateLinterSwitch(); updateLinterSwitch();
}); });
@ -86,7 +96,7 @@ function createSourceEditor(style) {
function updateLinterSwitch() { function updateLinterSwitch() {
const el = $('#editor.linter'); const el = $('#editor.linter');
el.value = linterConfig.getName(); el.value = getCurrentLinter();
const cssLintOption = $('[value="csslint"]', el); const cssLintOption = $('[value="csslint"]', el);
const mode = getModeName(); const mode = getModeName();
if (mode !== 'css') { if (mode !== 'css') {
@ -98,6 +108,14 @@ function createSourceEditor(style) {
} }
} }
function getCurrentLinter() {
const name = prefs.get('editor.linter');
if (cm.getOption('mode') !== 'css' && name === 'csslint') {
return 'stylelint';
}
return name;
}
function setupNewStyle(style) { function setupNewStyle(style) {
style.sections[0].code = ' '.repeat(prefs.get('editor.tabSize')) + style.sections[0].code = ' '.repeat(prefs.get('editor.tabSize')) +
`/* ${t('usercssReplaceTemplateSectionBody')} */`; `/* ${t('usercssReplaceTemplateSectionBody')} */`;
@ -108,12 +126,9 @@ function createSourceEditor(style) {
} }
const DEFAULT_CODE = ` const DEFAULT_CODE = `
/* ==UserStyle== /* ==UserStyle==
@name ${ @name ${''/* a trick to preserve the trailing spaces */}
style.name ||
t('usercssReplaceTemplateName') + ' - ' + new Date().toLocaleString()
}
@namespace github.com/openstyles/stylus @namespace github.com/openstyles/stylus
@version 0.1.0 @version 1.0.0
@description A new userstyle @description A new userstyle
@author Me @author Me
==/UserStyle== */ ==/UserStyle== */
@ -123,9 +138,13 @@ function createSourceEditor(style) {
style.sourceCode = ''; style.sourceCode = '';
chromeSync.getLZValue('usercssTemplate').then(code => { chromeSync.getLZValue('usercssTemplate').then(code => {
const name = style.name || t('usercssReplaceTemplateName');
const date = new Date().toLocaleString();
code = code || DEFAULT_CODE; code = code || DEFAULT_CODE;
code = code.replace(/@name(\s*)(?=[\r\n])/, (str, space) =>
`${str}${space ? '' : ' '}${name} - ${date}`);
// strip the last dummy section if any, add an empty line followed by the section // strip the last dummy section if any, add an empty line followed by the section
style.sourceCode = code.replace(/@-moz-document[^{]*\{[^}]*\}\s*$|\s+$/g, '') + '\n\n' + section; style.sourceCode = code.replace(/\s*@-moz-document[^{]*\{[^}]*\}\s*$|\s+$/g, '') + '\n\n' + section;
cm.startOperation(); cm.startOperation();
cm.setValue(style.sourceCode); cm.setValue(style.sourceCode);
cm.clearHistory(); cm.clearHistory();
@ -146,7 +165,7 @@ function createSourceEditor(style) {
function updateTitle() { function updateTitle() {
const newTitle = (dirty.isDirty() ? '* ' : '') + const newTitle = (dirty.isDirty() ? '* ' : '') +
(style.id ? t('editStyleTitle', [style.name]) : t('addStyleTitle')); (style.id ? style.name : t('addStyleTitle'));
if (document.title !== newTitle) { if (document.title !== newTitle) {
document.title = newTitle; document.title = newTitle;
} }
@ -204,8 +223,8 @@ function createSourceEditor(style) {
id: style.id, id: style.id,
exclusionList exclusionList
}); });
return ( return ensureUniqueStyle(code)
API.saveUsercssUnsafe({ .then(() => API.saveUsercssUnsafe({
id: style.id, id: style.id,
reason: 'editSave', reason: 'editSave',
enabled: style.enabled, enabled: style.enabled,
@ -217,6 +236,7 @@ function createSourceEditor(style) {
if (errors) return Promise.reject(errors); if (errors) return Promise.reject(errors);
}) })
.catch(err => { .catch(err => {
if (err.handled) return;
if (err.message === t('styleMissingMeta', 'name')) { if (err.message === t('styleMissingMeta', 'name')) {
messageBox.confirm(t('usercssReplaceTemplateConfirmation')).then(ok => ok && messageBox.confirm(t('usercssReplaceTemplateConfirmation')).then(ok => ok &&
chromeSync.setLZValue('usercssTemplate', code) chromeSync.setLZValue('usercssTemplate', code)
@ -236,6 +256,20 @@ function createSourceEditor(style) {
}); });
} }
function ensureUniqueStyle(code) {
return style.id ? Promise.resolve() :
API.buildUsercss({
sourceCode: code,
checkDup: true,
metaOnly: true,
}).then(({dup}) => {
if (dup) {
messageBox.alert(t('usercssAvoidOverwriting'), 'danger', t('genericError'));
return Promise.reject({handled: true});
}
});
}
function drawLinePointer(pos) { function drawLinePointer(pos) {
const SIZE = 60; const SIZE = 60;
const line = cm.getLine(pos.line); const line = cm.getLine(pos.line);
@ -328,7 +362,7 @@ function createSourceEditor(style) {
} }
cm.display.scroller.scrollTop += cm.display.scroller.scrollTop +=
// WheelEvent.DOM_DELTA_LINE // WheelEvent.DOM_DELTA_LINE
deltaMode === 1 ? deltaY * cm.display.cachedTextHeight : deltaMode === 1 ? deltaY * cm.defaultTextHeight() :
// WheelEvent.DOM_DELTA_PAGE // WheelEvent.DOM_DELTA_PAGE
deltaMode === 2 || shiftKey ? Math.sign(deltaY) * cm.display.scroller.clientHeight : deltaMode === 2 || shiftKey ? Math.sign(deltaY) * cm.display.scroller.clientHeight :
// WheelEvent.DOM_DELTA_PIXEL // WheelEvent.DOM_DELTA_PIXEL

View File

@ -1,58 +0,0 @@
/* global require importScripts */
'use strict';
importScripts('/vendor/stylelint-bundle/stylelint-bundle.min.js');
const stylelint = require('stylelint');
self.onmessage = ({data: {action = 'run', code, config}}) => {
switch (action) {
case 'getAllRuleIds':
// the functions are non-tranferable
self.postMessage(Object.keys(stylelint.rules));
return;
case 'getAllRuleOptions':
self.postMessage(getAllRuleOptions());
return;
case 'run':
stylelint.lint({code, config}).then(results =>
self.postMessage(results));
return;
}
};
function getAllRuleOptions() {
const options = {};
const rxPossible = /\bpossible:("(?:[^"]*?)"|\[(?:[^\]]*?)\]|\{(?:[^}]*?)\})/g;
const rxString = /"([-\w\s]{3,}?)"/g;
for (const id of Object.keys(stylelint.rules)) {
const ruleCode = String(stylelint.rules[id]);
const sets = [];
let m, mStr;
while ((m = rxPossible.exec(ruleCode))) {
const possible = m[1];
const set = [];
while ((mStr = rxString.exec(possible))) {
const s = mStr[1];
if (s.includes(' ')) {
set.push(...s.split(/\s+/));
} else {
set.push(s);
}
}
if (possible.includes('ignoreAtRules')) {
set.push('ignoreAtRules');
}
if (possible.includes('ignoreShorthands')) {
set.push('ignoreShorthands');
}
if (set.length) {
sets.push(set);
}
}
if (sets.length) {
options[id] = sets;
}
}
return options;
}

View File

@ -116,3 +116,21 @@ function sectionsToMozFormat(style) {
section.code; section.code;
}).join('\n\n'); }).join('\n\n');
} }
function clipString(str, limit = 100) {
return str.length <= limit ? str : str.substr(0, limit) + '...';
}
// this is a decorator. Cache the first call
function memoize(fn) {
let cached = false;
let result;
return (...args) => {
if (!cached) {
result = fn(...args);
cached = true;
}
return result;
};
}

View File

@ -1,3 +1,20 @@
body {
font: normal 12px Arial, system-ui, sans-serif;
}
body:lang(ja) {
font-family: Arial, 'Meiryo UI', 'MS Gothic', system-ui, sans-serif;
}
body:lang(zh-CN) {
font-family: Arial, 'Microsoft YaHei UI', 'Microsoft YaHei', system-ui, sans-serif;
}
body:lang(zh-TW),
body:lang(zh-HK) {
font-family: Arial, 'Microsoft JhengHei UI', 'Microsoft JhengHei', system-ui, sans-serif;
}
button { button {
-webkit-appearance: none; -webkit-appearance: none;
-moz-appearance: none; -moz-appearance: none;
@ -8,7 +25,8 @@ button {
text-overflow: ellipsis; text-overflow: ellipsis;
padding: 2px 7px; padding: 2px 7px;
border: 1px solid hsl(0, 0%, 62%); border: 1px solid hsl(0, 0%, 62%);
font: 400 13.3333px Arial; font: inherit;
font-size: 13px;
color: #000; color: #000;
background-color: hsl(0, 0%, 100%); background-color: hsl(0, 0%, 100%);
background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAAeCAYAAADtlXTHAAAABmJLR0QA/wD/AP+gvaeTAAAACXBIWXMAAAsTAAALEwEAmpwYAAAAB3RJTUUH4QwGBBwIHvKt6QAAAB1pVFh0Q29tbWVudAAAAAAAQ3JlYXRlZCB3aXRoIEdJTVBkLmUHAAAAL0lEQVQI12NoaGgQZ2JgYGBkYmBgYGZiYGBggrMY4VxsYsyoskQQCB2MWAxAMhkADVECDhlW9CoAAAAASUVORK5CYII='); background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAAeCAYAAADtlXTHAAAABmJLR0QA/wD/AP+gvaeTAAAACXBIWXMAAAsTAAALEwEAmpwYAAAAB3RJTUUH4QwGBBwIHvKt6QAAAB1pVFh0Q29tbWVudAAAAAAAQ3JlYXRlZCB3aXRoIEdJTVBkLmUHAAAAL0lEQVQI12NoaGgQZ2JgYGBkYmBgYGZiYGBggrMY4VxsYsyoskQQCB2MWAxAMhkADVECDhlW9CoAAAAASUVORK5CYII=');
@ -36,6 +54,10 @@ button:active {
border-color: hsl(0, 0%, 50%); border-color: hsl(0, 0%, 50%);
} }
input {
font: inherit;
}
input:not([type]) { input:not([type]) {
background: #fff; background: #fff;
color: #000; color: #000;
@ -43,7 +65,7 @@ input:not([type]) {
min-height: 22px!important; min-height: 22px!important;
line-height: 22px; line-height: 22px;
padding: 0 3px; padding: 0 3px;
font: 400 13.3333px Arial; font: inherit;
border: 1px solid hsl(0, 0%, 66%); border: 1px solid hsl(0, 0%, 66%);
} }
@ -110,7 +132,7 @@ select {
-moz-appearance: none; -moz-appearance: none;
-webkit-appearance: none; -webkit-appearance: none;
height: 22px; height: 22px;
font: 400 13.3333px Arial; font: inherit;
color: #000; color: #000;
background-color: transparent; background-color: transparent;
border: 1px solid hsl(0, 0%, 66%); border: 1px solid hsl(0, 0%, 66%);
@ -186,19 +208,6 @@ select[disabled] > option {
outline: none; outline: none;
} }
/* use a readable font for Japanese/Chinese */
.actions:lang(ja) *,
#header:lang(ja) * {
font-family: 'Meiryo UI', system-ui, 'MS Gothic', Arial, sans-serif;
font-weight: normal;
}
.actions:lang(zh) *,
#header:lang(zh) * {
font-family: 'MingLiU', system-ui, Arial, sans-serif;
}
@supports (-moz-appearance: none) { @supports (-moz-appearance: none) {
.moz-appearance-bug .svg-icon.checked, .moz-appearance-bug .svg-icon.checked,
.moz-appearance-bug .onoffswitch input, .moz-appearance-bug .onoffswitch input,
@ -215,7 +224,6 @@ select[disabled] > option {
} }
.firefox select { .firefox select {
font-size: 13px;
padding: 0 20px 0 2px; padding: 0 20px 0 2px;
line-height: 22px!important; line-height: 22px!important;
} }
@ -243,13 +251,8 @@ select[disabled] > option {
padding: 4px; padding: 4px;
} }
.firefox.non-windows .style-name {
font-family: Arial, sans-serif;
}
/* Firefox cannot handle fractions in font-size */ /* Firefox cannot handle fractions in font-size */
.firefox button:not(.install) { .firefox button:not(.install) {
font-size: 13px;
line-height: 13px; line-height: 13px;
padding: 3px 7px; padding: 3px 7px;
} }

View File

@ -306,7 +306,7 @@ li {
user-select: auto; user-select: auto;
} }
label { label:not(.unavailable) {
padding-left: 16px; padding-left: 16px;
position: relative; position: relative;
} }

View File

@ -3,6 +3,8 @@
'use strict'; 'use strict';
(() => { (() => {
const DUMMY_URL = 'foo:';
// TODO: remove .replace(/^\?/, '') when minimum_chrome_version >= 52 (https://crbug.com/601425) // TODO: remove .replace(/^\?/, '') when minimum_chrome_version >= 52 (https://crbug.com/601425)
const params = new URLSearchParams(location.search.replace(/^\?/, '')); const params = new URLSearchParams(location.search.replace(/^\?/, ''));
let liveReload = false; let liveReload = false;
@ -14,7 +16,8 @@
let port; let port;
if (params.has('direct')) { if (params.has('direct')) {
$('.live-reload').remove(); $('.live-reload').textContent = t('liveReloadUnavailable');
$('.live-reload').classList.add('unavailable');
getCodeDirectly(); getCodeDirectly();
} else { } else {
port = chrome.tabs.connect(tabId); port = chrome.tabs.connect(tabId);
@ -308,15 +311,17 @@
const checker = $('.set-update-url input[type=checkbox]'); const checker = $('.set-update-url input[type=checkbox]');
// prefer the installation URL unless drag'n'dropped on the manage page // prefer the installation URL unless drag'n'dropped on the manage page
const installationUrl = (params.get('updateUrl') || '').replace(/^blob.+/, ''); const installationUrl = (params.get('updateUrl') || '').replace(/^blob.+/, '');
const updateUrl = new URL(installationUrl || style.updateUrl || 'foo:bar'); const updateUrl = new URL(installationUrl || style.updateUrl || DUMMY_URL);
$('.set-update-url > span').textContent = t('installUpdateFromLabel'); $('.set-update-url > span').textContent = t('installUpdateFromLabel');
if (dup && dup.updateUrl === updateUrl.href) { if (dup && dup.updateUrl === updateUrl.href) {
checker.checked = true; checker.checked = true;
// there is no way to "unset" updateUrl, you can only overwrite it. // there is no way to "unset" updateUrl, you can only overwrite it.
checker.disabled = true; checker.disabled = true;
} else if (updateUrl.protocol === 'foo:') { } else if (updateUrl.href === DUMMY_URL) {
// drag'n'dropped on the manage page and the style doesn't have @updateURL // drag'n'dropped on the manage page and the style doesn't have @updateURL
checker.disabled = true; $('.set-update-url').textContent = t('installUpdateUnavailable');
$('.set-update-url').classList.add('unavailable');
return;
} else if (updateUrl.protocol !== 'file:') { } else if (updateUrl.protocol !== 'file:') {
checker.checked = true; checker.checked = true;
style.updateUrl = updateUrl.href; style.updateUrl = updateUrl.href;

View File

@ -313,20 +313,31 @@ function initCollapsibles({bindClickOn = 'h2'} = {}) {
} }
} }
// Makes the focus outline appear on keyboard tabbing, but not on mouse clicks.
function focusAccessibility() { function focusAccessibility() {
// Makes the focus outline appear on keyboard tabbing, but not on mouse clicks. // last event's focusedViaClick
// Since we don't want full layout recalc, we modify only the closest focusable element, focusAccessibility.lastFocusedViaClick = false;
// which we try to find in DOM for this many parentElement jumps: // tags of focusable elements;
const focusables = focusAccessibility.ELEMENTS = // to avoid a full layout recalc we modify the closest one
['a', 'button', 'input', 'textarea', 'label', 'select', 'summary']; focusAccessibility.ELEMENTS = [
'a',
'button',
'input',
'textarea',
'label',
'select',
'summary',
];
// try to find a focusable parent for this many parentElement jumps:
const GIVE_UP_DEPTH = 4; const GIVE_UP_DEPTH = 4;
addEventListener('mousedown', suppressOutlineOnClick, {passive: true}); addEventListener('mousedown', suppressOutlineOnClick, {passive: true});
addEventListener('keydown', keepOutlineOnTab, {passive: true}); addEventListener('keydown', keepOutlineOnTab, {passive: true});
function suppressOutlineOnClick({target}) { function suppressOutlineOnClick({target}) {
for (let el = target, i = 0; el && i++ < GIVE_UP_DEPTH; el = el.parentElement) { for (let el = target, i = 0; el && i++ < GIVE_UP_DEPTH; el = el.parentElement) {
if (focusables.includes(el.localName)) { if (focusAccessibility.ELEMENTS.includes(el.localName)) {
focusAccessibility.lastFocusedViaClick = true;
if (el.dataset.focusedViaClick === undefined) { if (el.dataset.focusedViaClick === undefined) {
el.dataset.focusedViaClick = ''; el.dataset.focusedViaClick = '';
} }
@ -337,13 +348,14 @@ function focusAccessibility() {
function keepOutlineOnTab(event) { function keepOutlineOnTab(event) {
if (event.which === 9) { if (event.which === 9) {
focusAccessibility.lastFocusedViaClick = false;
setTimeout(keepOutlineOnTab, 0, true); setTimeout(keepOutlineOnTab, 0, true);
return; return;
} else if (event !== true) { } else if (event !== true) {
return; return;
} }
let el = document.activeElement; let el = document.activeElement;
if (!el || !focusables.includes(el.localName)) { if (!el || !focusAccessibility.ELEMENTS.includes(el.localName)) {
return; return;
} }
if (el.dataset.focusedViaClick !== undefined) { if (el.dataset.focusedViaClick !== undefined) {
@ -355,3 +367,28 @@ function focusAccessibility() {
} }
} }
} }
/**
* Switches to the next/previous keyboard-focusable element
* @param {HTMLElement} rootElement
* @param {Number} step - for exmaple 1 or -1
* @returns {HTMLElement|false|undefined} -
* HTMLElement: focus changed,
* false: focus unchanged,
* undefined: nothing to focus
*/
function moveFocus(rootElement, step) {
const elements = [...rootElement.getElementsByTagName('*')];
const activeIndex = Math.max(0, elements.indexOf(document.activeElement));
const num = elements.length;
const {activeElement} = document;
for (let i = 1; i < num; i++) {
const elementIndex = (activeIndex + i * step + num) % num;
// we don't use positive tabindex so we stop at any valid value
const el = elements[elementIndex];
if (!el.disabled && el.tabIndex >= 0) {
el.focus();
return activeElement !== el && el;
}
}
}

View File

@ -54,6 +54,7 @@ var prefs = new function Prefs() {
newline_between_properties: true, newline_between_properties: true,
newline_before_close_brace: true, newline_before_close_brace: true,
newline_between_rules: false, newline_between_rules: false,
preserve_newlines: true,
end_with_newline: false, end_with_newline: false,
indent_conditional: true, indent_conditional: true,
}, },

View File

@ -1,7 +1,24 @@
'use strict'; 'use strict';
// ignoreCode=true is used by invalidateCache to determine if cached filters should be cleared /**
function styleSectionsEqual({sections: a}, {sections: b}, {ignoreCode = false} = {}) { * @param {Style} a - first style object
* @param {Style} b - second style object
* @param {Object} options
* @param {Boolean=} options.ignoreCode -
* true used by invalidateCache to determine if cached filters should be cleared
* @param {Boolean=} options.checkSource -
* true used by update check to compare the server response
* instead of sections that depend on @preprocessor
* @returns {Boolean|undefined}
*/
function styleSectionsEqual(a, b, {ignoreCode, checkSource} = {}) {
if (checkSource &&
typeof a.sourceCode === 'string' &&
typeof b.sourceCode === 'string') {
return a.sourceCode === b.sourceCode;
}
a = a.sections;
b = b.sections;
if (!a || !b) { if (!a || !b) {
return undefined; return undefined;
} }

View File

@ -29,7 +29,7 @@ var usercss = (() => {
['version', 0], ['version', 0],
]); ]);
const MANDATORY_META = ['name', 'namespace', 'version']; const MANDATORY_META = ['name', 'namespace', 'version'];
const META_VARS = ['text', 'color', 'checkbox', 'select', 'dropdown', 'image']; const META_VARS = ['text', 'color', 'checkbox', 'select', 'dropdown', 'image', 'number', 'range'];
const META_URLS = [...KNOWN_META.keys()].filter(k => k.endsWith('URL')); const META_URLS = [...KNOWN_META.keys()].filter(k => k.endsWith('URL'));
const BUILDER = { const BUILDER = {
@ -194,12 +194,40 @@ var usercss = (() => {
state.errorPrefix = 'Invalid JSON: '; state.errorPrefix = 'Invalid JSON: ';
parseJSONValue(state); parseJSONValue(state);
state.errorPrefix = ''; state.errorPrefix = '';
const extractDefaultOption = (key, value) => {
if (key.endsWith('*')) {
const option = createOption(key.slice(0, -1), value);
result.default = option.name;
return option;
}
return createOption(key, value);
};
if (Array.isArray(state.value)) { if (Array.isArray(state.value)) {
result.options = state.value.map(text => createOption(text)); result.options = state.value.map(k => extractDefaultOption(k));
} else { } else {
result.options = Object.keys(state.value).map(k => createOption(k, state.value[k])); result.options = Object.keys(state.value).map(k => extractDefaultOption(k, state.value[k]));
}
if (result.default === null) {
result.default = (result.options[0] || {}).name || '';
}
break;
}
case 'number':
case 'range': {
state.errorPrefix = 'Invalid JSON: ';
parseJSONValue(state);
state.errorPrefix = '';
// [default, start, end, step, units] (start, end, step & units are optional)
if (Array.isArray(state.value) && state.value.length) {
// label may be placed anywhere
result.units = (state.value.find(i => typeof i === 'string') || '').replace(/[\d.+-]/g, '');
const range = state.value.filter(i => typeof i === 'number' || i === null);
result.default = range[0];
result.min = range[1];
result.max = range[2];
result.step = range[3] === 0 ? 1 : range[3];
} }
result.default = (result.options[0] || {}).name || '';
break; break;
} }
@ -541,6 +569,9 @@ var usercss = (() => {
// TODO: handle customized image // TODO: handle customized image
return va.options.find(o => o.name === va[prop]).value; return va.options.find(o => o.name === va[prop]).value;
} }
if ((va.type === 'number' || va.type === 'range') && va.units) {
return va[prop] + va.units;
}
return va[prop]; return va[prop];
} }
@ -578,6 +609,8 @@ var usercss = (() => {
throw new Error(chrome.i18n.getMessage('styleMetaErrorCheckbox')); throw new Error(chrome.i18n.getMessage('styleMetaErrorCheckbox'));
} else if (va.type === 'color') { } else if (va.type === 'color') {
va[value] = colorConverter.format(colorConverter.parse(va[value]), 'rgb'); va[value] = colorConverter.format(colorConverter.parse(va[value]), 'rgb');
} else if ((va.type === 'number' || va.type === 'range') && typeof va[value] !== 'number') {
throw new Error(chrome.i18n.getMessage('styleMetaErrorRangeOrNumber', va.type));
} }
} }
@ -599,7 +632,7 @@ var usercss = (() => {
function invokeWorker(message) { function invokeWorker(message) {
if (!worker.queue) { if (!worker.queue) {
worker.instance = new Worker('/edit/csslint-loader.js'); worker.instance = new Worker('/background/parserlib-loader.js');
worker.queue = []; worker.queue = [];
worker.instance.onmessage = ({data}) => { worker.instance.onmessage = ({data}) => {
worker.queue.shift().resolve(data.__ERROR__ ? Promise.reject(data.__ERROR__) : data); worker.queue.shift().resolve(data.__ERROR__ ? Promise.reject(data.__ERROR__) : data);

View File

@ -254,7 +254,9 @@
<label id="only-updates" class="hidden"> <label id="only-updates" class="hidden">
<input type="checkbox" <input type="checkbox"
data-filter=".can-update, .update-problem, .update-done" data-filter=".can-update, .update-problem, .update-done"
data-filter-hide=":not(.updatable):not(.update-done), .no-update:not(.update-problem)"> data-filter-hide=":not(.updatable):not(.update-done),
.no-update:not(.update-problem),
.updatable:not(.can-update):not(.update-problem):not(.update-done)">
<svg class="svg-icon checked"><use xlink:href="#svg-icon-checked"/></svg> <svg class="svg-icon checked"><use xlink:href="#svg-icon-checked"/></svg>
<span i18n-text="manageOnlyUpdates"></span> <span i18n-text="manageOnlyUpdates"></span>
</label> </label>
@ -378,6 +380,7 @@
<span><a href="https://userstyles.org" target="_blank" i18n-text="linkGetStyles"></a></span> <span><a href="https://userstyles.org" target="_blank" i18n-text="linkGetStyles"></a></span>
<span><a href="https://add0n.com/stylus.html#features" target="_blank" i18n-text="linkGetHelp"></a></span> <span><a href="https://add0n.com/stylus.html#features" target="_blank" i18n-text="linkGetHelp"></a></span>
<span><a href="https://github.com/openstyles/stylus/wiki" target="_blank" i18n-text="linkStylusWiki"></a></span> <span><a href="https://github.com/openstyles/stylus/wiki" target="_blank" i18n-text="linkStylusWiki"></a></span>
<span><a href="https://www.transifex.com/github-7/Stylus" target="_blank" i18n-text="linkTranslate"></a></span>
</div> </div>
</div> </div>
</div> </div>

View File

@ -98,6 +98,15 @@
margin-right: 4px; margin-right: 4px;
} }
.current-value {
padding: 2px 4px;
margin-right: 4px;
}
.config-number span, .config-range span {
line-height: 22px;
}
.config-body label:not(.nondefault) .config-reset-icon { .config-body label:not(.nondefault) .config-reset-icon {
visibility: hidden; visibility: hidden;
} }

View File

@ -258,16 +258,44 @@ function configDialog(style) {
]; ];
break; break;
case 'range':
case 'number': {
const options = {
va,
type: va.type,
onfocus: va.type === 'number' ? selectAllOnFocus : null,
onblur: va.type === 'number' ? updateVarOnBlur : null,
onchange: updateVarOnChange,
oninput: updateVarOnInput,
required: true
};
if (typeof va.min === 'number') {
options.min = va.min;
}
if (typeof va.max === 'number') {
options.max = va.max;
}
if (typeof va.step === 'number' && isFinite(va.step)) {
options.step = va.step;
}
children = [
va.type === 'range' && $create('span.current-value'),
va.input = $create('input.config-value', options)
];
break;
}
default: default:
children = [ children = [
va.input = $create('input.config-value', { va.input = $create('input.config-value', {
va, va,
type: 'text', type: va.type,
onchange: updateVarOnChange, onchange: updateVarOnChange,
oninput: updateVarOnInput, oninput: updateVarOnInput,
onfocus: selectAllOnFocus,
}), }),
]; ];
break;
} }
resetter = resetter.cloneNode(true); resetter = resetter.cloneNode(true);
@ -285,8 +313,28 @@ function configDialog(style) {
} }
} }
function updateVarOnBlur() {
this.value = isDefault(this.va) ? this.va.default : this.va.value;
}
function updateVarOnChange() { function updateVarOnChange() {
this.va.value = this.type !== 'checkbox' ? this.value : this.checked ? '1' : '0'; if (this.type === 'range') {
this.va.value = Number(this.value);
updateRangeCurrentValue(this.va, this.va.value);
} else if (this.type === 'number') {
if (this.reportValidity()) {
this.va.value = Number(this.value);
}
} else {
this.va.value = this.type !== 'checkbox' ? this.value : this.checked ? '1' : '0';
}
}
function updateRangeCurrentValue(va, value) {
const span = $('.current-value', va.input.closest('.config-range'));
if (span) {
span.textContent = value + (va.units || '');
}
} }
function updateVarOnInput(event, debounced = false) { function updateVarOnInput(event, debounced = false) {
@ -297,8 +345,15 @@ function configDialog(style) {
} }
} }
function selectAllOnFocus(event) {
event.target.select();
}
function renderValues(varsToRender = vars) { function renderValues(varsToRender = vars) {
for (const va of varsToRender) { for (const va of varsToRender) {
if (va.input === document.activeElement) {
continue;
}
const value = isDefault(va) ? va.default : va.value; const value = isDefault(va) ? va.default : va.value;
if (va.type === 'color') { if (va.type === 'color') {
va.input.style.backgroundColor = value; va.input.style.backgroundColor = value;
@ -307,6 +362,9 @@ function configDialog(style) {
} }
} else if (va.type === 'checkbox') { } else if (va.type === 'checkbox') {
va.input.checked = Number(value); va.input.checked = Number(value);
} else if (va.type === 'range') {
va.input.value = value;
updateRangeCurrentValue(va, va.input.value);
} else { } else {
va.input.value = value; va.input.value = value;
} }

View File

@ -203,7 +203,7 @@ function importFromString(jsonString, oldStyles) {
messageBox({ messageBox({
title: t('importReportTitle'), title: t('importReportTitle'),
contents: report.length ? report : t('importReportUnchanged'), contents: report.length ? report : t('importReportUnchanged'),
buttons: [t('confirmOK'), numChanged && t('undo')], buttons: [t('confirmClose'), numChanged && t('undo')],
onshow: bindClick, onshow: bindClick,
}).then(({button}) => { }).then(({button}) => {
if (button === 1) { if (button === 1) {
@ -241,7 +241,7 @@ function importFromString(jsonString, oldStyles) {
.then(() => messageBox({ .then(() => messageBox({
title: t('importReportUndoneTitle'), title: t('importReportUndoneTitle'),
contents: newIds.length + ' ' + t('importReportUndone'), contents: newIds.length + ' ' + t('importReportUndone'),
buttons: [t('confirmOK')], buttons: [t('confirmClose')],
})); }));
} }
@ -275,39 +275,37 @@ function importFromString(jsonString, oldStyles) {
$('#file-all-styles').onclick = () => { $('#file-all-styles').onclick = () => {
API.getStyles().then(styles => { API.getStyles().then(styles => {
const text = JSON.stringify(styles, null, '\t');
const blob = new Blob([text], {type: 'application/json'});
const url = URL.createObjectURL(blob);
let link = $create('a', {
href: url,
type: 'application/json',
download: generateFileName(),
});
// https://crbug.com/714373 // https://crbug.com/714373
if (!FIREFOX && !(CHROME > 3310 && CHROME < Infinity)) { document.documentElement.appendChild(
link.dispatchEvent(new MouseEvent('click')); $create('iframe', {
return doTimeout() onload() {
.then(() => URL.revokeObjectURL(url)); const text = JSON.stringify(styles, null, '\t');
} const type = 'application/json';
const iframe = document.body.appendChild($create('iframe', { this.onload = null;
style: 'width: 0; height: 0; position: fixed; opacity: 0;'.replace(/;/g, '!important;'), this.contentDocument.body.appendChild(
})); $create('a', {
return doTimeout() href: URL.createObjectURL(new Blob([text], {type})),
.then(() => { download: generateFileName(),
link = iframe.contentDocument.importNode(link, true); type,
iframe.contentDocument.body.appendChild(link); })
).dispatchEvent(new MouseEvent('click'));
},
// we can't use display:none as some browsers are ignoring such iframes
style: `
all: unset;
width: 0;
height: 0;
position: fixed;
opacity: 0;
border: none;
`.replace(/;/g, '!important;'),
}) })
.then(() => doTimeout()) );
.then(() => link.dispatchEvent(new MouseEvent('click'))) // we don't remove the iframe or the object URL because the browser may show
.then(() => doTimeout(1000)) // a download dialog and we don't know how long it'll take until the user confirms it
.then(() => URL.revokeObjectURL(url)) // (some browsers like Vivaldi can't download if we revoke the URL)
.then(() => iframe.remove());
}); });
function doTimeout(ms) {
return new Promise(resolve => setTimeout(resolve, ms));
}
function generateFileName() { function generateFileName() {
const today = new Date(); const today = new Date();
const dd = ('0' + today.getDate()).substr(-2); const dd = ('0' + today.getDate()).substr(-2);

View File

@ -9,12 +9,15 @@
body { body {
margin: 0; margin: 0;
font: 12px arial, sans-serif;
/* Firefox: fill the entire page for drag'n'drop to work */ /* Firefox: fill the entire page for drag'n'drop to work */
display: flex; display: flex;
height: 100%; height: 100%;
} }
#header:lang(ja) h1 {
font-weight: normal;
}
body.all-styles-hidden-by-filters:before, body.all-styles-hidden-by-filters:before,
body.all-styles-hidden-by-filters:after { body.all-styles-hidden-by-filters:after {
position: absolute; position: absolute;
@ -45,10 +48,6 @@ a:hover {
color: #666; color: #666;
} }
select {
font-size: 12px;
}
#header { #header {
width: var(--header-width); width: var(--header-width);
height: 100vh; height: 100vh;
@ -72,6 +71,11 @@ select {
max-width: calc(100% - 30px); max-width: calc(100% - 30px);
} }
#check-all-updates-force {
white-space: normal;
text-align: left;
}
#header h1 { #header h1 {
margin-top: 0; margin-top: 0;
margin-bottom: .3em; margin-bottom: .3em;
@ -241,6 +245,8 @@ select {
border-radius: 4px; border-radius: 4px;
margin-left: 1ex; margin-left: 1ex;
white-space: nowrap; white-space: nowrap;
position: relative;
top: 2px;
} }
.entry.usercss .style-name-link::after { .entry.usercss .style-name-link::after {
@ -446,6 +452,7 @@ select {
#manage-text { #manage-text {
display: flex; display: flex;
flex-wrap: wrap;
align-items: baseline; align-items: baseline;
padding-top: .35rem; padding-top: .35rem;
} }
@ -466,7 +473,6 @@ select {
.newUI .style-name { .newUI .style-name {
font-size: 14px; font-size: 14px;
font-family: sans-serif;
text-indent: calc(var(--checkbox-width) - var(--name-padding-left) - 4px); text-indent: calc(var(--checkbox-width) - var(--name-padding-left) - 4px);
padding-left: var(--name-padding-left); padding-left: var(--name-padding-left);
padding-right: var(--name-padding-right); padding-right: var(--name-padding-right);
@ -474,7 +480,7 @@ select {
cursor: pointer; cursor: pointer;
} }
.newUI .entry .style-name::before { .newUI .entry .style-name:hover::before {
content: ""; content: "";
position: absolute; position: absolute;
top: 0; top: 0;
@ -483,13 +489,6 @@ select {
bottom: 0; bottom: 0;
background: linear-gradient(to right, hsla(180, 50%, 30%, 0.2), hsla(180, 20%, 10%, 0.05) 50%, transparent); background: linear-gradient(to right, hsla(180, 50%, 30%, 0.2), hsla(180, 20%, 10%, 0.05) 50%, transparent);
pointer-events: none; pointer-events: none;
opacity: 0;
transition: opacity .1s;
will-change: opacity;
}
.newUI .entry .style-name:hover::before {
opacity: 1;
} }
.newUI .entry.enabled .style-name:hover .style-name-link { .newUI .entry.enabled .style-name:hover .style-name-link {
@ -825,6 +824,15 @@ input[id^="manage.newUI"] {
margin-top: 1ex; margin-top: 1ex;
} }
#update-all-no-updates[data-skipped-edited="true"] {
font-weight: bold;
}
#update-all-no-updates[data-skipped-edited="true"]::after {
font-weight: normal;
display: block;
}
/* highlight updated/added styles */ /* highlight updated/added styles */
.highlight { .highlight {
animation: highlight 10s cubic-bezier(0,.82,.47,.98); animation: highlight 10s cubic-bezier(0,.82,.47,.98);
@ -941,7 +949,6 @@ input[id^="manage.newUI"] {
height: 20px; height: 20px;
box-sizing: border-box; box-sizing: border-box;
padding: 3px 3px 3px 4px; padding: 3px 3px 3px 4px;
font: 400 12px Arial;
color: #000; color: #000;
border: 1px solid hsl(0, 0%, 66%); border: 1px solid hsl(0, 0%, 66%);
} }

View File

@ -73,8 +73,7 @@ function initGlobalEvents() {
installed.addEventListener('mouseover', handleEvent.lazyAddEntryTitle); installed.addEventListener('mouseover', handleEvent.lazyAddEntryTitle);
installed.addEventListener('mouseout', handleEvent.lazyAddEntryTitle); installed.addEventListener('mouseout', handleEvent.lazyAddEntryTitle);
// remember scroll position on normal history navigation document.addEventListener('visibilitychange', onVisibilityChange);
window.onbeforeunload = rememberScrollPosition;
$$('[data-toggle-on-click]').forEach(el => { $$('[data-toggle-on-click]').forEach(el => {
// dataset on SVG doesn't work in Chrome 49-??, works in 57+ // dataset on SVG doesn't work in Chrome 49-??, works in 57+
@ -107,16 +106,15 @@ function initGlobalEvents() {
.disabled h2::after { .disabled h2::after {
content: "${t('genericDisabledLabel')}"; content: "${t('genericDisabledLabel')}";
} }
#update-all-no-updates[data-skipped-edited="true"]:after { #update-all-no-updates[data-skipped-edited="true"]::after {
content: " ${t('updateAllCheckSucceededSomeEdited')}"; content: " ${t('updateAllCheckSucceededSomeEdited')}";
} }
body.all-styles-hidden-by-filters:after { body.all-styles-hidden-by-filters::after {
content: "${t('filteredStylesAllHidden')}"; content: "${t('filteredStylesAllHidden')}";
} }
`)); `));
} }
function showStyles(styles = [], matchUrlIds) { function showStyles(styles = [], matchUrlIds) {
const sorted = sorter.sort({ const sorted = sorter.sort({
styles: styles.map(style => ({ styles: styles.map(style => ({
@ -155,23 +153,15 @@ function showStyles(styles = [], matchUrlIds) {
filterAndAppend({container: renderBin}).then(sorter.updateStripes); filterAndAppend({container: renderBin}).then(sorter.updateStripes);
if (index < sorted.length) { if (index < sorted.length) {
requestAnimationFrame(renderStyles); requestAnimationFrame(renderStyles);
if (firstRun) setTimeout(recreateStyleTargets, 0, {styles, iconsOnly: true}); if (firstRun) setTimeout(getFaviconImgSrc);
firstRun = false; firstRun = false;
return; return;
} }
if (newUI.enabled && newUI.favicons) { setTimeout(getFaviconImgSrc);
setTimeout(recreateStyleTargets, 0, {iconsOnly: true});
}
if ('scrollY' in (history.state || {}) && !sessionStorage.justEditedStyleId) {
setTimeout(window.scrollTo, 0, 0, history.state.scrollY);
}
if (sessionStorage.justEditedStyleId) { if (sessionStorage.justEditedStyleId) {
const entry = $(ENTRY_ID_PREFIX + sessionStorage.justEditedStyleId); highlightEditedStyle();
delete sessionStorage.justEditedStyleId; } else if ('scrollY' in (history.state || {})) {
if (entry) { setTimeout(window.scrollTo, 0, 0, history.state.scrollY);
animateElement(entry);
requestAnimationFrame(() => scrollElementIntoView(entry));
}
} }
} }
} }
@ -247,10 +237,10 @@ function createStyleElement({style, name}) {
} }
function createStyleTargetsElement({entry, style, iconsOnly}) { function createStyleTargetsElement({entry, style}) {
const parts = createStyleElement.parts; const parts = createStyleElement.parts;
const entryTargets = $('.targets', entry); const entryTargets = $('.targets', entry);
const targets = iconsOnly ? entryTargets : parts.targets.cloneNode(true); const targets = parts.targets.cloneNode(true);
let container = targets; let container = targets;
let numTargets = 0; let numTargets = 0;
const displayed = new Set(); const displayed = new Set();
@ -269,23 +259,21 @@ function createStyleTargetsElement({entry, style, iconsOnly}) {
continue; continue;
} }
displayed.add(targetValue); displayed.add(targetValue);
const element = iconsOnly ? targets.children[numTargets] : template.appliesToTarget.cloneNode(true); const element = template.appliesToTarget.cloneNode(true);
if (!newUI.enabled) { if (!newUI.enabled) {
if (numTargets === 10) { if (numTargets === 10) {
container = container.appendChild(template.extraAppliesTo.cloneNode(true)); container = container.appendChild(template.extraAppliesTo.cloneNode(true));
} else if (numTargets > 1) { } else if (numTargets > 0) {
container.appendChild(template.appliesToSeparator.cloneNode(true)); container.appendChild(template.appliesToSeparator.cloneNode(true));
} }
} }
if (!iconsOnly) { element.dataset.type = type;
element.dataset.type = type; element.appendChild(
element.appendChild( document.createTextNode(
document.createTextNode( (parts.decorations[type + 'Before'] || '') +
(parts.decorations[type + 'Before'] || '') + targetValue +
targetValue + (parts.decorations[type + 'After'] || '')));
(parts.decorations[type + 'After'] || ''))); container.appendChild(element);
container.appendChild(element);
}
numTargets++; numTargets++;
} }
} }
@ -296,9 +284,7 @@ function createStyleTargetsElement({entry, style, iconsOnly}) {
} }
} }
if (numTargets) { if (numTargets) {
if (!iconsOnly) { entryTargets.parentElement.replaceChild(targets, entryTargets);
entryTargets.parentElement.replaceChild(targets, entryTargets);
}
} else if (!entry.classList.contains('global') || } else if (!entry.classList.contains('global') ||
!entryTargets.firstElementChild) { !entryTargets.firstElementChild) {
if (entryTargets.firstElementChild) { if (entryTargets.firstElementChild) {
@ -310,35 +296,17 @@ function createStyleTargetsElement({entry, style, iconsOnly}) {
} }
function recreateStyleTargets({styles, iconsOnly = false} = {}) {
Promise.resolve(styles || API.getStyles()).then(styles => {
for (const style of styles) {
const entry = $(ENTRY_ID_PREFIX + style.id);
if (entry) {
createStyleTargetsElement({
entry,
style,
iconsOnly,
});
}
}
if (newUI.enabled && newUI.favicons) {
debounce(getFaviconImgSrc);
}
});
}
function getFaviconImgSrc(container = installed) { function getFaviconImgSrc(container = installed) {
const targets = $$('.target', container); if (!newUI.enabled || !newUI.favicons) return;
const regexpRemoveNegativeLookAhead = /(\?!([^)]+\))|\(\?![\w(]+[^)]+[\w|)]+)/g; const regexpRemoveNegativeLookAhead = /(\?!([^)]+\))|\(\?![\w(]+[^)]+[\w|)]+)/g;
// replace extra characters & all but the first group entry "(abc|def|ghi)xyz" => abcxyz // replace extra characters & all but the first group entry "(abc|def|ghi)xyz" => abcxyz
const regexpReplaceExtraCharacters = /[\\(]|((\|\w+)+\))/g; const regexpReplaceExtraCharacters = /[\\(]|((\|\w+)+\))/g;
const domainExt = 'com,org,co,net,im,io,edu,gov,biz,info,de,cn,uk,nl,eu,ru'.split(','); const regexpMatchRegExp = /[\w-]+[.(]+(com|org|co|net|im|io|edu|gov|biz|info|de|cn|uk|nl|eu|ru)\b/g;
const regexpMatchRegExp = new RegExp(`[\\w-]+[\\.(]+(${domainExt.join('|')})\\b`, 'g');
const regexpMatchDomain = /^.*?:\/\/([^/]+)/; const regexpMatchDomain = /^.*?:\/\/([^/]+)/;
for (const target of targets) { for (const target of $$('.target', container)) {
const type = target.dataset.type; const type = target.dataset.type;
const targetValue = target.textContent; const targetValue = target.textContent;
if (!targetValue) continue;
let favicon = ''; let favicon = '';
if (type === 'domains') { if (type === 'domains') {
favicon = GET_FAVICON_URL + targetValue; favicon = GET_FAVICON_URL + targetValue;
@ -426,7 +394,7 @@ Object.assign(handleEvent, {
}); });
} }
} else { } else {
rememberScrollPosition(); onVisibilityChange();
getActiveTab().then(tab => { getActiveTab().then(tab => {
sessionStorageHash('manageStylesHistory').set(tab.id, url); sessionStorageHash('manageStylesHistory').set(tab.id, url);
location.href = url; location.href = url;
@ -575,9 +543,7 @@ function handleUpdate(style, {reason, method} = {}) {
animateElement(entry); animateElement(entry);
requestAnimationFrame(() => scrollElementIntoView(entry)); requestAnimationFrame(() => scrollElementIntoView(entry));
} }
if (newUI.enabled && newUI.favicons) { getFaviconImgSrc(entry);
getFaviconImgSrc(entry);
}
function handleToggledOrCodeOnly() { function handleToggledOrCodeOnly() {
const newStyleMeta = getStyleWithNoCode(style); const newStyleMeta = getStyleWithNoCode(style);
@ -694,14 +660,41 @@ function switchUI({styleOnly} = {}) {
return; return;
} }
if (missingFavicons) { if (missingFavicons) {
recreateStyleTargets(); debounce(getFaviconImgSrc);
return; return;
} }
} }
function rememberScrollPosition() { function onVisibilityChange() {
history.replaceState({scrollY: window.scrollY}, document.title); switch (document.visibilityState) {
// page restored without reloading via history navigation (currently only in FF)
// the catch here is that DOM may be outdated so we'll at least refresh the just edited style
// assuming other changes aren't important enough to justify making a complicated DOM sync
case 'visible':
if (sessionStorage.justEditedStyleId) {
API.getStyles({id: sessionStorage.justEditedStyleId}).then(([style]) => {
handleUpdate(style, {method: 'styleUpdated'});
});
delete sessionStorage.justEditedStyleId;
}
break;
// going away
case 'hidden':
history.replaceState({scrollY: window.scrollY}, document.title);
break;
}
}
function highlightEditedStyle() {
if (!sessionStorage.justEditedStyleId) return;
const entry = $(ENTRY_ID_PREFIX + sessionStorage.justEditedStyleId);
delete sessionStorage.justEditedStyleId;
if (entry) {
animateElement(entry);
requestAnimationFrame(() => scrollElementIntoView(entry));
}
} }

View File

@ -66,7 +66,6 @@ function checkUpdateAll() {
if (info.updated) { if (info.updated) {
if (++updated === 1) { if (++updated === 1) {
btnApply.disabled = true; btnApply.disabled = true;
btnApply.classList.remove('hidden');
} }
btnApply.dataset.value = updated; btnApply.dataset.value = updated;
} }
@ -156,6 +155,16 @@ function reportUpdateState({updated, style, error, STATES}) {
$('.update-note', entry).textContent = message; $('.update-note', entry).textContent = message;
$('.check-update', entry).title = newUI.enabled ? message : ''; $('.check-update', entry).title = newUI.enabled ? message : '';
$('.update', entry).title = t(edited ? 'updateCheckManualUpdateForce' : 'installUpdate'); $('.update', entry).title = t(edited ? 'updateCheckManualUpdateForce' : 'installUpdate');
// digest may change silently when forcing an update of a locally edited style
// so we need to update it in entry's styleMeta in all open manager tabs
if (error === STATES.SAME_CODE) {
for (const view of chrome.extension.getViews({type: 'tab'})) {
if (view.location.pathname === location.pathname) {
const entry = view.$(ENTRY_ID_PREFIX + style.id);
if (entry) entry.styleMeta.originalDigest = style.originalDigest;
}
}
}
if (!isCheckAll) { if (!isCheckAll) {
renderUpdatesOnlyFilter({show: $('.can-update, .update-problem')}); renderUpdatesOnlyFilter({show: $('.can-update, .update-problem')});
} }
@ -179,6 +188,8 @@ function reportUpdateState({updated, style, error, STATES}) {
if (filtersSelector.hide && isCheckAll) { if (filtersSelector.hide && isCheckAll) {
filterAndAppend({entry}).then(sorter.updateStripes); filterAndAppend({entry}).then(sorter.updateStripes);
} else if (updated && !isCheckAll) {
renderUpdatesOnlyFilter();
} }
} }
@ -195,13 +206,8 @@ function renderUpdatesOnlyFilter({show, check} = {}) {
checkbox.dispatchEvent(new Event('change')); checkbox.dispatchEvent(new Event('change'));
const btnApply = $('#apply-all-updates'); const btnApply = $('#apply-all-updates');
if (!btnApply.matches('.hidden')) { btnApply.classList.toggle('hidden', !numUpdatable);
if (numUpdatable > 0) { btnApply.dataset.value = numUpdatable;
btnApply.dataset.value = numUpdatable;
} else {
btnApply.classList.add('hidden');
}
}
} }

View File

@ -1,6 +1,6 @@
{ {
"name": "Stylus", "name": "Stylus",
"version": "1.4.16", "version": "1.4.22",
"minimum_chrome_version": "49", "minimum_chrome_version": "49",
"description": "__MSG_description__", "description": "__MSG_description__",
"homepage_url": "https://add0n.com/stylus.html", "homepage_url": "https://add0n.com/stylus.html",

View File

@ -1,4 +1,5 @@
/* global focusAccessibility */ /* global focusAccessibility */
/* global moveFocus */
'use strict'; 'use strict';
/** /**
@ -33,12 +34,22 @@ function messageBox({
document.body.appendChild(messageBox.element); document.body.appendChild(messageBox.element);
messageBox.originalFocus = document.activeElement; messageBox.originalFocus = document.activeElement;
moveFocus(1); // skip external links like feedback
while ((moveFocus(messageBox.element, 1) || {}).target === '_blank') {/*NOP*/}
// suppress focus outline when invoked via click
if (focusAccessibility.lastFocusedViaClick && document.activeElement) {
document.activeElement.dataset.focusedViaClick = '';
}
if (typeof onshow === 'function') { if (typeof onshow === 'function') {
onshow(messageBox.element); onshow(messageBox.element);
} }
if (!$('#message-box-title').textContent) {
$('#message-box-title').hidden = true;
$('#message-box-close-icon').hidden = true;
}
return new Promise(_resolve => { return new Promise(_resolve => {
messageBox.resolve = _resolve; messageBox.resolve = _resolve;
}); });
@ -67,7 +78,7 @@ function messageBox({
event.stopPropagation(); event.stopPropagation();
break; break;
case 9: case 9:
moveFocus(shiftKey ? -1 : 1); moveFocus(messageBox.element, shiftKey ? -1 : 1);
event.preventDefault(); event.preventDefault();
return; return;
default: default:
@ -139,30 +150,17 @@ function messageBox({
messageBox.element = null; messageBox.element = null;
messageBox.resolve = null; messageBox.resolve = null;
} }
function moveFocus(dir) {
const elements = [...messageBox.element.getElementsByTagName('*')];
const activeIndex = Math.max(0, elements.indexOf(document.activeElement));
const num = elements.length;
for (let i = 1; i < num; i++) {
const elementIndex = (activeIndex + i * dir + num) % num;
// we don't use positive tabindex so we stop at any valid value
const el = elements[elementIndex];
if (!el.disabled && el.tabIndex >= 0) {
el.focus();
return;
}
}
}
} }
/** /**
* @param {String|Node|Array<String|Node>} contents * @param {String|Node|Array<String|Node>} contents
* @param {String} [className] like 'pre' for monospace font * @param {String} [className] like 'pre' for monospace font
* @param {String} [title]
* @returns {Promise<Boolean>} same as messageBox * @returns {Promise<Boolean>} same as messageBox
*/ */
messageBox.alert = (contents, className) => messageBox.alert = (contents, className, title) =>
messageBox({ messageBox({
title,
contents, contents,
className: `center ${className || ''}`, className: `center ${className || ''}`,
buttons: [t('confirmClose')] buttons: [t('confirmClose')]

View File

@ -1,28 +1,31 @@
{ {
"name": "Stylus", "name": "Stylus",
"version": "1.4.16", "version": "1.4.22",
"description": "Redesign the web with Stylus, a user styles manager", "description": "Redesign the web with Stylus, a user styles manager",
"license": "GPL-3.0-only", "license": "GPL-3.0-only",
"repository": "openstyles/stylus", "repository": "openstyles/stylus",
"author": "Stylus Team", "author": "Stylus Team",
"devDependencies": { "devDependencies": {
"archiver": "^2.1.1", "archiver": "^3.0.0",
"codemirror": "^5.39.2", "codemirror": "^5.40.0",
"eslint": "^5.2.0", "eslint": "^5.4.0",
"fs-extra": "^7.0.0", "fs-extra": "^7.0.0",
"jsonlint": "^1.6.3", "jsonlint": "^1.6.3",
"less": "^3.7.1", "less": "^3.8.1",
"lz-string-unsafe": "^1.4.4-beta", "lz-string-unsafe": "^1.4.4-fork-1",
"semver-bundle": "^0.1.0", "rimraf": "^2.6.2",
"semver-bundle": "^0.1.1",
"stylelint-bundle": "^8.0.0", "stylelint-bundle": "^8.0.0",
"stylus-lang-bundle": "^0.54.5", "stylus-lang-bundle": "^0.54.5",
"updates": "^4.0.1" "updates": "^4.2.1"
}, },
"scripts": { "scripts": {
"lint": "eslint **/*.js || true", "lint": "eslint **/*.js --cache || exit 0",
"update": "npm run update-node && npm run update-versions && npm run update-codemirror", "update": "npm run update-node && npm run update-main",
"update-quick": "updates -u && npm update && npm run update-main",
"update-main": "npm run update-versions && npm run update-codemirror",
"update-node": "updates -u && node tools/remove-modules.js && npm install",
"update-codemirror": "node tools/update-libraries.js && node tools/update-codemirror-themes.js", "update-codemirror": "node tools/update-libraries.js && node tools/update-codemirror-themes.js",
"update-node": "updates -u && npm update",
"update-versions": "node tools/update-versions", "update-versions": "node tools/update-versions",
"zip": "npm run update-versions && node tools/zip.js" "zip": "npm run update-versions && node tools/zip.js"
} }

View File

@ -133,7 +133,7 @@ function initPopup() {
$('label', info).textContent = t('unreachableAMO'); $('label', info).textContent = t('unreachableAMO');
const note = (FIREFOX < 59 ? t('unreachableAMOHintOldFF') : t('unreachableAMOHint')) + const note = (FIREFOX < 59 ? t('unreachableAMOHintOldFF') : t('unreachableAMOHint')) +
(FIREFOX < 60 ? '' : '\n' + t('unreachableAMOHintNewFF')); (FIREFOX < 60 ? '' : '\n' + t('unreachableAMOHintNewFF'));
const renderToken = s => s[0] === '<' ? $create('b', s.slice(1, -1)) : s; const renderToken = s => s[0] === '<' ? $create('b', tWordBreak(s.slice(1, -1))) : s;
const renderLine = line => $create('p', line.split(/(<.*?>)/).map(renderToken)); const renderLine = line => $create('p', line.split(/(<.*?>)/).map(renderToken));
const noteNode = $create('fragment', note.split('\n').map(renderLine)); const noteNode = $create('fragment', note.split('\n').map(renderLine));
const target = $('p', info); const target = $('p', info);
@ -314,7 +314,14 @@ function createStyleElement({
if (check) detectSloppyRegexps([style]); if (check) detectSloppyRegexps([style]);
const oldElement = $(ENTRY_ID_PREFIX + style.id); const oldElement = $(ENTRY_ID_PREFIX + style.id);
if (oldElement) { if (oldElement && oldElement.contains(document.activeElement)) {
// preserve the focused element inside
const {className} = document.activeElement;
oldElement.parentNode.replaceChild(entry, oldElement);
// we're not using $() since className may contain multiple tokens
const el = entry.getElementsByClassName(className)[0];
if (el) el.focus();
} else if (oldElement) {
oldElement.parentNode.replaceChild(entry, oldElement); oldElement.parentNode.replaceChild(entry, oldElement);
} else { } else {
container.appendChild(entry); container.appendChild(entry);
@ -338,6 +345,7 @@ Object.assign(handleEvent, {
}, },
toggle(event) { toggle(event) {
// when fired on checkbox, prevent the parent label from seeing the event, see #501
event.stopPropagation(); event.stopPropagation();
API.saveStyle({ API.saveStyle({
id: handleEvent.getClickedStyleId(event), id: handleEvent.getClickedStyleId(event),

8
tools/remove-modules.js Normal file
View File

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

View File

@ -43,6 +43,12 @@ function isFolder(fileOrFolder) {
return stat.isDirectory(); return stat.isDirectory();
} }
// Rename CodeMirror$1 -> CodeMirror for development purposes
function renameCodeMirrorVariable(filePath) {
const file = fs.readFileSync(filePath, 'utf8');
fs.writeFileSync(filePath, file.replace(/CodeMirror\$1/g, 'CodeMirror'));
}
function updateExisting(lib) { function updateExisting(lib) {
const libRoot = `${root}/node_modules/`; const libRoot = `${root}/node_modules/`;
const vendorRoot = `${root}/vendor/`; const vendorRoot = `${root}/vendor/`;
@ -54,8 +60,8 @@ function updateExisting(lib) {
const folderRoot = `${vendorRoot}${folder}`; const folderRoot = `${vendorRoot}${folder}`;
const entries = fs.readdirSync(folderRoot); const entries = fs.readdirSync(folderRoot);
entries.forEach(entry => { entries.forEach(entry => {
// Ignore README.md & LICENSE files
if (entry !== 'README.md' && entry !== 'LICENSE') { if (entry !== 'README.md' && entry !== 'LICENSE') {
// Ignore README.md & LICENSE files
const entryPath = `${folderRoot}/${entry}`; const entryPath = `${folderRoot}/${entry}`;
try { try {
if (fs.existsSync(entryPath)) { if (fs.existsSync(entryPath)) {
@ -63,6 +69,10 @@ function updateExisting(lib) {
folders.push(`${folder}/${entry}`); folders.push(`${folder}/${entry}`);
} else { } else {
fs.copySync(`${libRoot}${folder}/${entry}`, entryPath); fs.copySync(`${libRoot}${folder}/${entry}`, entryPath);
// Remove $1 from "CodeMirror$1" in codemirror.js
if (entry === 'codemirror.js') {
renameCodeMirrorVariable(entryPath);
}
} }
} }
} catch (err) { } catch (err) {

View File

@ -8,9 +8,12 @@ function createZip() {
const fileName = 'stylus.zip'; const fileName = 'stylus.zip';
const exclude = [ const exclude = [
'.*', // dot files/folders (glob, not regexp) '.*', // dot files/folders (glob, not regexp)
'node_modules', 'vendor/codemirror/lib/**', // get unmodified copy from node_modules
'tools', 'node_modules/**',
'tools/**',
'package.json', 'package.json',
'package-lock.json',
'yarn.lock',
'*.zip' '*.zip'
]; ];
@ -34,7 +37,9 @@ function createZip() {
}); });
archive.pipe(file); archive.pipe(file);
archive.glob(`!(${exclude.join('|')})`); archive.glob('**', {ignore: exclude});
// Don't use modified codemirror.js (see "update-libraries.js")
archive.directory('node_modules/codemirror/lib', 'vendor/codemirror/lib');
archive.finalize(); archive.finalize();
}); });
} }

View File

@ -508,15 +508,11 @@
// sass/less parent reference don't use a space // sass/less parent reference don't use a space
// sass nested pseudo-class don't use a space // sass nested pseudo-class don't use a space
/* Stylus override.
Disabling this block since we don't like the result.
// preserve space before pseudoclasses/pseudoelements, as it means "in any child" // preserve space before pseudoclasses/pseudoelements, as it means "in any child"
if (lookBack(" ") && output[output.length - 1] !== " ") { if (lookBack(" ") && outputPosCol && !/\s$/.test(output[output.length - 1])) {
output.push(" "); output.push(" ");
outputPosCol++; outputPosCol++;
} }
*/
if (peek() === ":") { if (peek() === ":") {
// pseudo-element // pseudo-element

View File

@ -6,8 +6,7 @@
display: inline-block; display: inline-block;
} }
.colorview-swatch::before, .colorview-swatch::before {
.colorview-swatch::after {
content: ""; content: "";
position: absolute; position: absolute;
display: inline-block; display: inline-block;
@ -18,21 +17,14 @@
width: 10px; width: 10px;
height: 10px; height: 10px;
box-sizing: border-box; box-sizing: border-box;
} background: linear-gradient(var(--colorview-swatch), var(--colorview-swatch)), url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAgAAAAICAYAAADED76LAAAAJElEQVQYV2NctWrVfwYkEBYWxojMZ6SDAmT7QGx0K1EcRBsFAADeG/3M/HteAAAAAElFTkSuQmCC");
.colorview-swatch::before {
background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAgAAAAICAYAAADED76LAAAAJElEQVQYV2NctWrVfwYkEBYWxojMZ6SDAmT7QGx0K1EcRBsFAADeG/3M/HteAAAAAElFTkSuQmCC");
background-repeat: repeat; background-repeat: repeat;
background-position: center; background-position: center;
}
.colorview-swatch::after {
border: 1px solid #8e8e8e; border: 1px solid #8e8e8e;
cursor: pointer; cursor: pointer;
background-color: var(--colorview-swatch);
} }
.colorview-swatch:hover::after { .colorview-swatch:hover::before {
border-color: #494949; border-color: #494949;
} }

View File

@ -222,11 +222,9 @@
} }
} }
function hide({notify = true} = {}) { function hide() {
if (shown) { if (shown) {
if (notify) { colorpickerCallback('');
colorpickerCallback('');
}
unregisterEvents(); unregisterEvents();
focusNoScroll(prevFocusedElement); focusNoScroll(prevFocusedElement);
$root.remove(); $root.remove();
@ -623,7 +621,7 @@
case 27: case 27:
e.preventDefault(); e.preventDefault();
e.stopPropagation(); e.stopPropagation();
hide({notify: false}); hide();
break; break;
} }
} }
@ -643,17 +641,20 @@
//region Event utilities //region Event utilities
function colorpickerCallback(colorString = currentColorToString()) { function colorpickerCallback(colorString = currentColorToString()) {
// Esc pressed? const isCallable = typeof options.callback === 'function';
if (!colorString) { // hiding
if (!colorString && isCallable) {
options.callback(''); options.callback('');
return;
} }
if ( if (
userActivity && userActivity &&
$inputs[currentFormat].every(el => el.checkValidity()) && $inputs[currentFormat].every(el => el.checkValidity())
typeof options.callback === 'function'
) { ) {
lastOutputColor = colorString.replace(/\b0\./g, '.'); lastOutputColor = colorString.replace(/\b0\./g, '.');
options.callback(lastOutputColor); if (isCallable) {
options.callback(lastOutputColor);
}
} }
} }
@ -788,8 +789,8 @@
const maxTopUnobscured = options.top <= maxTop ? maxTop : options.top - height - 20; const maxTopUnobscured = options.top <= maxTop ? maxTop : options.top - height - 20;
const maxRight = window.innerWidth - width; const maxRight = window.innerWidth - width;
const maxRightUnobscured = options.left <= maxRight ? maxRight : options.left - width; const maxRightUnobscured = options.left <= maxRight ? maxRight : options.left - width;
const left = constrain(0, maxRightUnobscured, options.left); const left = constrain(0, Math.max(0, maxRightUnobscured), options.left);
const top = constrain(0, maxTopUnobscured, options.top); const top = constrain(0, Math.max(0, maxTopUnobscured), options.top);
$root.style.setProperty('left', left + 'px', 'important'); $root.style.setProperty('left', left + 'px', 'important');
$root.style.setProperty('top', top + 'px', 'important'); $root.style.setProperty('top', top + 'px', 'important');
} }

View File

@ -40,6 +40,9 @@
!CSS.supports('color', 'hsl(1turn, 2%, 3%)') && /deg|g?rad|turn/, !CSS.supports('color', 'hsl(1turn, 2%, 3%)') && /deg|g?rad|turn/,
].filter(Boolean).map(rx => rx.source).join('|') || '^$', 'i'), ].filter(Boolean).map(rx => rx.source).join('|') || '^$', 'i'),
}; };
if (RX_COLOR.unsupported.source === '^$') {
RX_COLOR.unsupported = null;
}
const RX_DETECT = new RegExp('(^|[\\s(){}[\\]:,/"=])' + const RX_DETECT = new RegExp('(^|[\\s(){}[\\]:,/"=])' +
'(' + '(' +
RX_COLOR.hex.source + '|' + RX_COLOR.hex.source + '|' +
@ -100,7 +103,7 @@
if (!this.popup) { if (!this.popup) {
delete CM_EVENTS.mousedown; delete CM_EVENTS.mousedown;
document.head.appendChild(document.createElement('style')).textContent = ` document.head.appendChild(document.createElement('style')).textContent = `
.colorview-swatch::after { .colorview-swatch::before {
cursor: auto; cursor: auto;
} }
`; `;
@ -418,7 +421,7 @@
function getSafeColorValue() { function getSafeColorValue() {
if (isHex && color.length !== 5 && color.length !== 9) return color; if (isHex && color.length !== 5 && color.length !== 9) return color;
if (!isFunc || !RX_COLOR.unsupported.test(color)) return color; if (!RX_COLOR.unsupported || !RX_COLOR.unsupported.test(color)) return color;
const value = colorConverter.parse(color); const value = colorConverter.parse(color);
return colorConverter.format(value, 'rgb'); return colorConverter.format(value, 'rgb');
} }
@ -691,7 +694,7 @@
if (button) return; if (button) return;
const swatch = target.closest('.' + COLORVIEW_CLASS); const swatch = target.closest('.' + COLORVIEW_CLASS);
if (!swatch) return; if (!swatch) return;
const {left, width, height} = getComputedStyle(swatch, '::after'); const {left, width, height} = getComputedStyle(swatch, '::before');
const bounds = swatch.getBoundingClientRect(); const bounds = swatch.getBoundingClientRect();
const swatchClicked = const swatchClicked =
offsetX >= parseFloat(left) - 1 && offsetX >= parseFloat(left) - 1 &&

View File

@ -425,7 +425,7 @@ CSSLint.addRule({
} }
}; };
CSSLint.util.registerBlockEvents(parser, startRule, endRule, property); CSSLint.Util.registerBlockEvents(parser, startRule, endRule, property);
}, },
}); });

View File

@ -59,6 +59,7 @@ self.parserlib = (() => {
vmax: 'length', vmax: 'length',
vmin: 'length', vmin: 'length',
fr: 'length', fr: 'length',
q: 'length',
deg: 'angle', deg: 'angle',
rad: 'angle', rad: 'angle',
@ -591,8 +592,8 @@ self.parserlib = (() => {
'text-align-all': 'start | end | left | right | center | justify | match-parent', 'text-align-all': 'start | end | left | right | center | justify | match-parent',
'text-align-last': 'auto | start | end | left | right | center | justify', 'text-align-last': 'auto | start | end | left | right | center | justify',
'text-anchor': 'start | middle | end', 'text-anchor': 'start | middle | end',
'text-decoration': '<text-decoration-line> || <text-decoration-style> || <text-decoration-color>', 'text-decoration': '<text-decoration-line> || <text-decoration-style> || <color>',
'text-decoration-color': '<text-decoration-color>', 'text-decoration-color': '<color>',
'text-decoration-line': '<text-decoration-line>', 'text-decoration-line': '<text-decoration-line>',
'text-decoration-skip': 'none | [ objects || [ spaces | [ leading-spaces || trailing-spaces ] ] || ' + 'text-decoration-skip': 'none | [ objects || [ spaces | [ leading-spaces || trailing-spaces ] ] || ' +
'edges || box-decoration ]', 'edges || box-decoration ]',
@ -756,7 +757,7 @@ self.parserlib = (() => {
'<gradient>': part => '<gradient>': part =>
part.type === 'function' && part.type === 'function' &&
/^(?:-(?:ms|moz|o|webkit)-)?(?:repeating-)?(?:radial-|linear-)?gradient/i.test(part), /^(?:-(?:ms|moz|o|webkit)-)?(?:repeating-)?(?:radial-|linear-|conic-)?gradient/i.test(part),
//eslint-disable-next-line no-use-before-define //eslint-disable-next-line no-use-before-define
'<hex-color>': part => part.tokenType === Tokens.HASH, '<hex-color>': part => part.tokenType === Tokens.HASH,
@ -859,8 +860,6 @@ self.parserlib = (() => {
'<string>': part => part.type === 'string', '<string>': part => part.type === 'string',
'<text-decoration-color>': '<color>',
'<text-decoration-style>': 'solid | double | dotted | dashed | wavy', '<text-decoration-style>': 'solid | double | dotted | dashed | wavy',
'<time>': part => part.type === 'time', '<time>': part => part.type === 'time',

13
vendor/README.md vendored
View File

@ -24,3 +24,16 @@ The following changes are made:
* `lz-string-unsafe`: The compressed `lz-string-unsafe.min.js` file is copied directly into `vendor/lz-string-unsafe`. * `lz-string-unsafe`: The compressed `lz-string-unsafe.min.js` file is copied directly into `vendor/lz-string-unsafe`.
* `semver-bundle`: The `dist/semver.js` file is copied directly into `vendor/semver`. * `semver-bundle`: The `dist/semver.js` file is copied directly into `vendor/semver`.
* `stylus-lang-bundle`: The `stylus.min.js` file is copied directly into `vendor/stylus-lang-bundle`. * `stylus-lang-bundle`: The `stylus.min.js` file is copied directly into `vendor/stylus-lang-bundle`.
## Creating the ZIP
Use `npm run zip`.
This command creates a zip file that includes all the files from the repository **except**:
* All dot files (e.g. `.eslintrc` & `.gitignore`).
* `node_modules` folder.
* `tools` folder.
* `package.json` file.
* `package-lock.json` and/or `yarn.lock` file(s).
* `vendor/codemirror/lib` files. This path is excluded because it contains a file modified for development purposes only. Instead, the CodeMirror files are copied directly from `node_modules/codemirror/lib`.

View File

@ -1,3 +1,3 @@
## CodeMirror v5.39.2 ## CodeMirror v5.40.0
Only files & folders that exist in the `vendor/codemirror` folder are copied from the `node_modules/codemirror` folder. Except all theme files are copied, in case new themes have been added. Only files & folders that exist in the `vendor/codemirror` folder are copied from the `node_modules/codemirror` folder. Except all theme files are copied, in case new themes have been added.

View File

@ -1,5 +1,5 @@
// CodeMirror, copyright (c) by Marijn Haverbeke and others // CodeMirror, copyright (c) by Marijn Haverbeke and others
// Distributed under an MIT license: http://codemirror.net/LICENSE // Distributed under an MIT license: https://codemirror.net/LICENSE
(function(mod) { (function(mod) {
if (typeof exports == "object" && typeof module == "object") // CommonJS if (typeof exports == "object" && typeof module == "object") // CommonJS

View File

@ -1,5 +1,5 @@
// CodeMirror, copyright (c) by Marijn Haverbeke and others // CodeMirror, copyright (c) by Marijn Haverbeke and others
// Distributed under an MIT license: http://codemirror.net/LICENSE // Distributed under an MIT license: https://codemirror.net/LICENSE
// Open simple dialogs on top of an editor. Relies on dialog.css. // Open simple dialogs on top of an editor. Relies on dialog.css.

View File

@ -1,5 +1,5 @@
// CodeMirror, copyright (c) by Marijn Haverbeke and others // CodeMirror, copyright (c) by Marijn Haverbeke and others
// Distributed under an MIT license: http://codemirror.net/LICENSE // Distributed under an MIT license: https://codemirror.net/LICENSE
(function(mod) { (function(mod) {
if (typeof exports == "object" && typeof module == "object") // CommonJS if (typeof exports == "object" && typeof module == "object") // CommonJS
@ -136,9 +136,7 @@
var prev = cur.ch == 0 ? " " : cm.getRange(Pos(cur.line, cur.ch - 1), cur) var prev = cur.ch == 0 ? " " : cm.getRange(Pos(cur.line, cur.ch - 1), cur)
if (!CodeMirror.isWordChar(next) && prev != ch && !CodeMirror.isWordChar(prev)) curType = "both"; if (!CodeMirror.isWordChar(next) && prev != ch && !CodeMirror.isWordChar(prev)) curType = "both";
else return CodeMirror.Pass; else return CodeMirror.Pass;
} else if (opening && (cm.getLine(cur.line).length == cur.ch || } else if (opening) {
isClosingBracket(next, pairs) ||
/\s/.test(next))) {
curType = "both"; curType = "both";
} else { } else {
return CodeMirror.Pass; return CodeMirror.Pass;
@ -175,11 +173,6 @@
}); });
} }
function isClosingBracket(ch, pairs) {
var pos = pairs.lastIndexOf(ch);
return pos > -1 && pos % 2 == 1;
}
function charsAround(cm, pos) { function charsAround(cm, pos) {
var str = cm.getRange(Pos(pos.line, pos.ch - 1), var str = cm.getRange(Pos(pos.line, pos.ch - 1),
Pos(pos.line, pos.ch + 1)); Pos(pos.line, pos.ch + 1));

View File

@ -1,5 +1,5 @@
// CodeMirror, copyright (c) by Marijn Haverbeke and others // CodeMirror, copyright (c) by Marijn Haverbeke and others
// Distributed under an MIT license: http://codemirror.net/LICENSE // Distributed under an MIT license: https://codemirror.net/LICENSE
(function(mod) { (function(mod) {
if (typeof exports == "object" && typeof module == "object") // CommonJS if (typeof exports == "object" && typeof module == "object") // CommonJS

View File

@ -1,5 +1,5 @@
// CodeMirror, copyright (c) by Marijn Haverbeke and others // CodeMirror, copyright (c) by Marijn Haverbeke and others
// Distributed under an MIT license: http://codemirror.net/LICENSE // Distributed under an MIT license: https://codemirror.net/LICENSE
(function(mod) { (function(mod) {
if (typeof exports == "object" && typeof module == "object") // CommonJS if (typeof exports == "object" && typeof module == "object") // CommonJS

View File

@ -1,5 +1,5 @@
// CodeMirror, copyright (c) by Marijn Haverbeke and others // CodeMirror, copyright (c) by Marijn Haverbeke and others
// Distributed under an MIT license: http://codemirror.net/LICENSE // Distributed under an MIT license: https://codemirror.net/LICENSE
(function(mod) { (function(mod) {
if (typeof exports == "object" && typeof module == "object") // CommonJS if (typeof exports == "object" && typeof module == "object") // CommonJS

View File

@ -1,5 +1,5 @@
// CodeMirror, copyright (c) by Marijn Haverbeke and others // CodeMirror, copyright (c) by Marijn Haverbeke and others
// Distributed under an MIT license: http://codemirror.net/LICENSE // Distributed under an MIT license: https://codemirror.net/LICENSE
(function(mod) { (function(mod) {
if (typeof exports == "object" && typeof module == "object") // CommonJS if (typeof exports == "object" && typeof module == "object") // CommonJS

View File

@ -1,5 +1,5 @@
// CodeMirror, copyright (c) by Marijn Haverbeke and others // CodeMirror, copyright (c) by Marijn Haverbeke and others
// Distributed under an MIT license: http://codemirror.net/LICENSE // Distributed under an MIT license: https://codemirror.net/LICENSE
(function(mod) { (function(mod) {
if (typeof exports == "object" && typeof module == "object") // CommonJS if (typeof exports == "object" && typeof module == "object") // CommonJS

View File

@ -1,5 +1,5 @@
// CodeMirror, copyright (c) by Marijn Haverbeke and others // CodeMirror, copyright (c) by Marijn Haverbeke and others
// Distributed under an MIT license: http://codemirror.net/LICENSE // Distributed under an MIT license: https://codemirror.net/LICENSE
(function(mod) { (function(mod) {
if (typeof exports == "object" && typeof module == "object") // CommonJS if (typeof exports == "object" && typeof module == "object") // CommonJS

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