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"
}, },
"enableStyleLabel": { "addStyleTitle": {
"message": "تمكين", "message": "إضافة نمط"
"description": "Label for the button to enable a style"
},
"styleMissingName": {
"message": "أدخل اسمًا",
"description": "Error displayed when user saves without providing a name"
},
"appliesDomainOption": {
"message": "عناوين URL في النطاق",
"description": "Option to make the style apply to the entered string as a domain"
},
"checkForUpdate": {
"message": "البحث عن تحديث",
"description": "Label for the button to check a single style for an update"
},
"helpAlt": {
"message": "مساعدة",
"description": "Alternate text for help buttons"
},
"findStylesForSite": {
"message": "البحث عن المزيد من الأنماط لموقع الويب هذا",
"description": "Text for a link that gets a list of styles for the current site"
},
"manageHeading": {
"message": "الأنماط المثبتة",
"description": "Heading for the manage page"
},
"styleEnabledLabel": {
"message": "ممكّن",
"description": "Label for the enabled state of styles"
},
"styleToMozillaFormatHelp": {
"message": "يمكن استخدام تنسيق موزيلا للرمز باستخدام Stylus للمتصفح فايرفوكس ويمكن إرساله إلى userstyles.org.",
"description": "Help info for the Mozilla format header section that converts the code to/from Mozilla format"
},
"sectionAdd": {
"message": "إضافة قسم آخر",
"description": "Label for the button to add a section"
},
"styleSaveLabel": {
"message": "حفظ",
"description": "Label for save button for style editing"
}, },
"appliesAdd": { "appliesAdd": {
"message": "إضافة", "message": "إضافة"
"description": "Label for the button to add an 'applies' entry"
},
"appliesRegexpOption": {
"message": "عناوين URL التي تطابق regexp",
"description": "Option to make the style apply to the entered string as a regular expression"
},
"styleInstall": {
"message": "هل تريد تثبيت '$stylename$' في Stylus؟",
"description": "Confirmation when installing a style",
"placeholders": {
"stylename": {
"content": "$1"
}
}
},
"disableStyleLabel": {
"message": "تعطيل",
"description": "Label for the button to disable a style"
},
"styleCancelEditLabel": {
"message": "رجوع للإدارة",
"description": "Label for cancel button for style editing"
},
"styleChangesNotSaved": {
"message": "لقد أجريت تغييرات على هذا النمط بدون حفظها.",
"description": "Text for the prompt when changes are made to a style and the user tries to leave without saving"
},
"updateCheckFailServerUnreachable": {
"message": "أخفق التحديث - الخادم يتعذر الوصول إليه.",
"description": "Text that displays when an update check failed because the update server is unreachable"
},
"deleteStyleConfirm": {
"message": "هل تريد بالتأكيد حذف هذا النمط؟",
"description": "Confirmation before deleting a style"
}, },
"appliesDisplay": { "appliesDisplay": {
"message": "ينطبق على: $applies$", "message": "ينطبق على: $applies$",
"description": "Text on the manage screen to describe what the style applies to",
"placeholders": { "placeholders": {
"applies": { "applies": {
"content": "$1" "content": "$1"
} }
} }
}, },
"styleSectionsTitle": { "appliesDisplayTruncatedSuffix": {
"message": "الأقسام", "message": "والمزيد"
"description": "Title for the style sections section" },
"appliesDomainOption": {
"message": "عناوين URL في النطاق"
},
"appliesHelp": {
"message": "استخدم عناصر تحكم 'ينطبق على' لتقييد عناوين URL التي ينطبق عليها الرمز في هذا القسم."
},
"appliesLabel": {
"message": "ينطبق على"
},
"appliesRegexpOption": {
"message": "عناوين URL التي تطابق regexp"
},
"appliesRemove": {
"message": "إزالة"
},
"appliesSpecify": {
"message": "تحديد"
},
"appliesToEverything": {
"message": "كل شيء"
},
"appliesUrlOption": {
"message": "عنوان URL"
},
"appliesUrlPrefixOption": {
"message": "عناوين URL البادئة بـ"
},
"checkAllUpdates": {
"message": "البحث عن تحديثات لكل الأنماط"
},
"checkForUpdate": {
"message": "البحث عن تحديث"
},
"checkingForUpdate": {
"message": "جارٍ البحث..."
},
"deleteStyleConfirm": {
"message": "هل تريد بالتأكيد حذف هذا النمط؟"
},
"deleteStyleLabel": {
"message": "حذف"
},
"description": {
"message": "يمكنك تغيير نمط الويب باستخدام Stylus، وهي أداة لإدارة أنماط المستخدم. وتتيح Stylus لك بسهولة تثبيت المظاهر والأشكال الخارجية لكل من Google، وFacebook وYouTube وOrkut فضلاً عن الكثير جدًا من مواقع الويب الأخرى."
},
"disableStyleLabel": {
"message": "تعطيل"
},
"editStyleHeading": {
"message": "تعديل النمط"
},
"editStyleLabel": {
"message": "تعديل"
}, },
"editStyleTitle": { "editStyleTitle": {
"message": "تعديل النمط $stylename$", "message": "تعديل النمط $stylename$",
"description": "Title of the page for editing styles",
"placeholders": { "placeholders": {
"stylename": { "stylename": {
"content": "$1" "content": "$1"
} }
} }
}, },
"updateCheckSucceededNoUpdate": { "enableStyleLabel": {
"message": "النمط محدّث.", "message": "تمكين"
"description": "Text that displays when an update check completed and no update is available"
}, },
"appliesUrlPrefixOption": { "findStylesForSite": {
"message": "عناوين URL البادئة بـ", "message": "البحث عن المزيد من الأنماط لموقع الويب هذا"
"description": "Option to make the style apply to the entered string as a URL prefix"
}, },
"sectionHelp": { "helpAlt": {
"message": "تتيح لك الأقسام تحديد أجزاء مختلفة من الرمز لتطبيقها على مجموعات مختلفة من عناوين URL بالنمط نفسه. فعلى سبيل المثال، يمكن لنمط مفرد تغيير الصفحة الرئيسية لموقع ويب بطريقة، مع تغيير بقية أجزاء موقع الويب بطريقة أخرى.", "message": "مساعدة"
"description": "Help text for sections" },
"installUpdate": {
"message": "تثبيت التحديث"
},
"manageHeading": {
"message": "الأنماط المثبتة"
}, },
"noStylesForSite": { "noStylesForSite": {
"message": "لم يتم تثبيت أي أنماط لموقع الويب هذا.", "message": "لم يتم تثبيت أي أنماط لموقع الويب هذا."
"description": "Text displayed when no styles are installed for the current site"
},
"appliesDisplayTruncatedSuffix": {
"message": "والمزيد",
"description": "Text added to appliesDisplay when there are more sites for the style than are displayed"
},
"appliesRemove": {
"message": "إزالة",
"description": "Label for the button to remove an 'applies' entry"
},
"appliesLabel": {
"message": "ينطبق على",
"description": "Label for 'applies to' fields on the edit/add screen"
}, },
"openManage": { "openManage": {
"message": "إدارة الأنماط المثبتة", "message": "إدارة الأنماط المثبتة"
"description": "Link to open the manage page." },
"sectionAdd": {
"message": "إضافة قسم آخر"
},
"sectionCode": {
"message": "الرمز"
},
"sectionHelp": {
"message": "تتيح لك الأقسام تحديد أجزاء مختلفة من الرمز لتطبيقها على مجموعات مختلفة من عناوين URL بالنمط نفسه. فعلى سبيل المثال، يمكن لنمط مفرد تغيير الصفحة الرئيسية لموقع ويب بطريقة، مع تغيير بقية أجزاء موقع الويب بطريقة أخرى."
},
"sectionRemove": {
"message": "إزالة القسم"
},
"styleCancelEditLabel": {
"message": "رجوع للإدارة"
},
"styleChangesNotSaved": {
"message": "لقد أجريت تغييرات على هذا النمط بدون حفظها."
},
"styleEnabledLabel": {
"message": "ممكّن"
},
"styleInstall": {
"message": "هل تريد تثبيت '$stylename$' في Stylus؟",
"placeholders": {
"stylename": {
"content": "$1"
}
}
},
"styleMissingName": {
"message": "أدخل اسمًا"
},
"styleSaveLabel": {
"message": "حفظ"
},
"styleSectionsTitle": {
"message": "الأقسام"
},
"styleToMozillaFormatHelp": {
"message": "يمكن استخدام تنسيق موزيلا للرمز باستخدام Stylus للمتصفح فايرفوكس ويمكن إرساله إلى userstyles.org."
}, },
"updateCheckFailBadResponseCode": { "updateCheckFailBadResponseCode": {
"message": "أخفق التحديث - استجاب الخادم بالرمز $code$", "message": "أخفق التحديث - استجاب الخادم بالرمز $code$",
"description": "Text that displays when an update check failed because the response code indicates an error",
"placeholders": { "placeholders": {
"code": { "code": {
"content": "$1" "content": "$1"
} }
} }
}, },
"appliesSpecify": { "updateCheckFailServerUnreachable": {
"message": "تحديد", "message": "أخفق التحديث - الخادم يتعذر الوصول إليه."
"description": "Label for the button to make a style apply only to specific sites"
}, },
"installUpdate": { "updateCheckSucceededNoUpdate": {
"message": "تثبيت التحديث", "message": "النمط محدّث."
"description": "Label for the button to install an update for a single style"
},
"sectionRemove": {
"message": "إزالة القسم",
"description": "Label for the button to remove a section"
}, },
"updateCompleted": { "updateCompleted": {
"message": "اكتمل التحديث.", "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"
}, },
"cm_tabSize": { "addStyleTitle": {
"message": "Μέγεθος καρτέλας", "message": "Προσθήκη στυλ"
"description": "Label for the text box controlling tab size option for the style editor."
},
"enableStyleLabel": {
"message": "Ενεργοποίηση",
"description": "Label for the button to enable a style"
},
"styleMissingName": {
"message": "Εισάγετε ένα όνομα",
"description": "Error displayed when user saves without providing a name"
},
"appliesDomainOption": {
"message": "URL στον τομέα",
"description": "Option to make the style apply to the entered string as a domain"
},
"checkForUpdate": {
"message": "Έλεγχος για ενημερώσεις",
"description": "Label for the button to check a single style for an update"
},
"updateAllCheckSucceededNoUpdate": {
"message": "Όλα τα στυλ είναι ενημερωμένα.",
"description": "Text that displays when an update all check completed and no updates are available"
},
"helpAlt": {
"message": "Βοήθεια",
"description": "Alternate text for help buttons"
},
"findStylesForSite": {
"message": "Αναζήτηση περισσότερων στυλ για αυτή την ιστοσελίδα",
"description": "Text for a link that gets a list of styles for the current site"
},
"manageHeading": {
"message": "Εγκατεστημένα Στυλ",
"description": "Heading for the manage page"
},
"styleEnabledLabel": {
"message": "Ενεργοποιημένη",
"description": "Label for the enabled state of styles"
},
"styleToMozillaFormatHelp": {
"message": "Η μορφή του Mozilla κώδικα μπορεί να χρησιμοποιηθεί με το Stylish για το Firefox και μπορεί να υποβληθεί στο userstyles.org.",
"description": "Help info for the Mozilla format header section that converts the code to/from Mozilla format"
},
"sectionAdd": {
"message": "Προσθήκη ένος άλλου τμήματος",
"description": "Label for the button to add a section"
},
"styleSaveLabel": {
"message": "Αποθήκευση",
"description": "Label for save button for style editing"
},
"writeStyleForURL": {
"message": "αυτή την διεύθυνση URL",
"description": "Text for link in toolbar pop-up to write a new style for the current URL"
}, },
"appliesAdd": { "appliesAdd": {
"message": "Προσθήκη", "message": "Προσθήκη"
"description": "Label for the button to add an 'applies' entry"
},
"appliesRegexpOption": {
"message": "Διευθύνσεις URL που ταιριάζουν με την κανονική έκφραση",
"description": "Option to make the style apply to the entered string as a regular expression"
},
"styleInstall": {
"message": "Εγκατάσταση του '$stylename$' στο Stylus;",
"description": "Confirmation when installing a style",
"placeholders": {
"stylename": {
"content": "$1"
}
}
},
"disableStyleLabel": {
"message": "Απενεργοποίηση",
"description": "Label for the button to disable a style"
},
"prefShowBadge": {
"message": "Εμφάνιση αριθμού των στυλ που δραστηριοποιούνται για την τρέχουσα τοποθεσία στην μπάρα εργαλείων",
"description": "Label for the checkbox controlling toolbar badge text."
},
"menuShowBadge": {
"message": "Εμφάνιση ενεργους καταμέτρησης στυλ",
"description": "Label (must be very short) for the checkbox in the toolbar button context menu controlling toolbar badge text."
},
"cm_lineWrapping": {
"message": "Αναδίπλωση λέξεων",
"description": "Label for the checkbox controlling word wrap option for the style editor."
},
"styleCancelEditLabel": {
"message": "Πίσω στη διαχείριση",
"description": "Label for cancel button for style editing"
},
"styleChangesNotSaved": {
"message": "Έχετε κάνει αλλαγές σε αυτό το ύφος χωρίς αποθήκευση.",
"description": "Text for the prompt when changes are made to a style and the user tries to leave without saving"
},
"updateCheckFailServerUnreachable": {
"message": "Αποτυχία ενημέρωσης: απρόσιτος διακομιστής.",
"description": "Text that displays when an update check failed because the update server is unreachable"
},
"manageFilters": {
"message": "Φίλτρα",
"description": "Label for filters container"
},
"applyAllUpdates": {
"message": "Εφαρμογή όλων των ενημερώσεων",
"description": "Label for the button to apply all detected updates"
},
"deleteStyleConfirm": {
"message": "Είστε σίγουροι ότι θέλετε να διαγράψετε αυτό το στυλ;",
"description": "Confirmation before deleting a style"
},
"styleBadRegexp": {
"message": "Το Regexp δεν είναι έγκυρο.",
"description": "Validation message for a bad regexp in a style"
},
"optionsHeading": {
"message": "Επιλογές",
"description": "Heading for options section on manage page."
}, },
"appliesDisplay": { "appliesDisplay": {
"message": "Ισχύει για: $applies$", "message": "Ισχύει για: $applies$",
"description": "Text on the manage screen to describe what the style applies to",
"placeholders": { "placeholders": {
"applies": { "applies": {
"content": "$1" "content": "$1"
} }
} }
}, },
"styleUpdate": { "appliesDisplayTruncatedSuffix": {
"message": "Είστε σίγουροι ότι θέλετε να ενημερώσετε το '$stylename$';", "message": "και πολλά άλλα"
"description": "Confirmation when updating a style",
"placeholders": {
"stylename": {
"content": "$1"
}
}
}, },
"styleSectionsTitle": { "appliesDomainOption": {
"message": "Ενότητες", "message": "URL στον τομέα"
"description": "Title for the style sections section" },
"appliesHelp": {
"message": "Χρησιμοποιήστε το \"Ισχύει για\" έλεγχοι ώστε να περιοριστουν ποιες διευθύνσεις τον κώδικα σε αυτό το τμήμα να εφαρμόζονται."
},
"appliesLabel": {
"message": "Ισχύει για"
},
"appliesRegexpOption": {
"message": "Διευθύνσεις URL που ταιριάζουν με την κανονική έκφραση"
},
"appliesRemove": {
"message": "Αφαίρεση"
},
"appliesSpecify": {
"message": "Καθορισμός"
},
"appliesToEverything": {
"message": "Τα πάντα"
},
"appliesUrlPrefixOption": {
"message": "Διευθύνσεις URL που αρχίζουν με"
},
"applyAllUpdates": {
"message": "Εφαρμογή όλων των ενημερώσεων"
},
"checkAllUpdates": {
"message": "Έλεγχος όλων των στυλ για ενημερώσεις"
},
"checkForUpdate": {
"message": "Έλεγχος για ενημερώσεις"
},
"checkingForUpdate": {
"message": "Έλεγχος..."
},
"cm_indentWithTabs": {
"message": "Χρήση καρτελών με έξυπνη εσοχή"
},
"cm_lineWrapping": {
"message": "Αναδίπλωση λέξεων"
},
"cm_smartIndent": {
"message": "Χρήση έξυπνης εσοχής"
},
"cm_tabSize": {
"message": "Μέγεθος καρτέλας"
},
"dbError": {
"message": "Παρουσιάστηκε σφάλμα χρησιμοποιώντας την κομψή βάση δεδομένων. Θα θέλατε να επισκεφθείτε μια ιστοσελίδα με πιθανές λύσεις;"
},
"deleteStyleConfirm": {
"message": "Είστε σίγουροι ότι θέλετε να διαγράψετε αυτό το στυλ;"
},
"deleteStyleLabel": {
"message": "Διαγραφή"
},
"description": {
"message": "Επαναπροσδιορίση του διαδίκτυου με το Stylus, έναν διαχειριστή στυλ. Το Stylus σας επιτρέπει να εγκαταστήσετε εύκολα themes και skins για πολλές δημοφιλείς ιστοσελίδες."
},
"disableAllStyles": {
"message": "Απενεργοποιηση ολων των στυλ"
},
"disableStyleLabel": {
"message": "Απενεργοποίηση"
},
"editGotoLine": {
"message": "Μετάβαση στη γραμμή (ή line:col)"
},
"editStyleHeading": {
"message": "Επεξεργασία Στυλ"
},
"editStyleLabel": {
"message": "Επεξεργασία"
}, },
"editStyleTitle": { "editStyleTitle": {
"message": "Επεξεργασία του στυλ $stylename$", "message": "Επεξεργασία του στυλ $stylename$",
"description": "Title of the page for editing styles",
"placeholders": { "placeholders": {
"stylename": { "stylename": {
"content": "$1" "content": "$1"
} }
} }
}, },
"updateCheckSucceededNoUpdate": { "enableStyleLabel": {
"message": "Το στυλ είναι ενημερωμένο.", "message": "Ενεργοποίηση"
"description": "Text that displays when an update check completed and no update is available"
}, },
"appliesUrlPrefixOption": { "findStylesForSite": {
"message": "Διευθύνσεις URL που αρχίζουν με", "message": "Αναζήτηση περισσότερων στυλ για αυτή την ιστοσελίδα"
"description": "Option to make the style apply to the entered string as a URL prefix"
}, },
"popupStylesFirst": { "helpAlt": {
"message": "Στυλ λίστας πριν των εντολών στο μενού του κουμπιού γραμμής εργαλείων", "message": "Βοήθεια"
"description": "Label for the checkbox controlling section order in the popup."
}, },
"sectionHelp": { "installUpdate": {
"message": "Ενότητες σας επιτρέπουν να ορίσετε διαφορετικά κομμάτια του κώδικα για να εφαρμόζονται σε διαφορετικά σύνολα των διευθύνσεων URL στο ίδιο στυλ. Για παράδειγμα, ένα ενιαίο ύφος θα μπορούσε να αλλάξει την αρχική σελίδα ενός ιστότοπου με έναν τρόπο, ενώ αλλάζει το υπόλοιπο μιας τοποθεσίας ένας άλλος τρόπος.", "message": "Εγκατάσταση ενημέρωσης"
"description": "Help text for sections"
}, },
"noStylesForSite": { "manageFilters": {
"message": "Δεν υπάρχουν εγκατεστημένα στυλ για αυτή την ιστοσελίδα.", "message": "Φίλτρα"
"description": "Text displayed when no styles are installed for the current site"
}, },
"appliesDisplayTruncatedSuffix": { "manageHeading": {
"message": "και πολλά άλλα", "message": "Εγκατεστημένα Στυλ"
"description": "Text added to appliesDisplay when there are more sites for the style than are displayed"
}, },
"appliesRemove": { "manageOnlyEnabled": {
"message": "Αφαίρεση", "message": "Μόνο ενεργοποιημένα στυλ"
"description": "Label for the button to remove an 'applies' entry"
}, },
"manageTitle": { "manageTitle": {
"message": "Κομψή", "message": "Κομψή"
"description": "Title for the manage page"
}, },
"writeStyleFor": { "menuShowBadge": {
"message": "Γράψτε νέο στυλ για:", "message": "Εμφάνιση ενεργους καταμέτρησης στυλ"
"description": "Label for toolbar pop-up that precedes the links to write a new style"
}, },
"appliesLabel": { "noStylesForSite": {
"message": "Ισχύει για", "message": "Δεν υπάρχουν εγκατεστημένα στυλ για αυτή την ιστοσελίδα."
"description": "Label for 'applies to' fields on the edit/add screen"
}, },
"openManage": { "openManage": {
"message": "Διαχείριση εγκατεστημένων στυλ", "message": "Διαχείριση εγκατεστημένων στυλ"
"description": "Link to open the manage page." },
"optionsHeading": {
"message": "Επιλογές"
},
"popupStylesFirst": {
"message": "Στυλ λίστας πριν των εντολών στο μενού του κουμπιού γραμμής εργαλείων"
},
"prefShowBadge": {
"message": "Εμφάνιση αριθμού των στυλ που δραστηριοποιούνται για την τρέχουσα τοποθεσία στην μπάρα εργαλείων"
},
"sectionAdd": {
"message": "Προσθήκη ένος άλλου τμήματος"
},
"sectionCode": {
"message": "Κώδικας"
},
"sectionHelp": {
"message": "Ενότητες σας επιτρέπουν να ορίσετε διαφορετικά κομμάτια του κώδικα για να εφαρμόζονται σε διαφορετικά σύνολα των διευθύνσεων URL στο ίδιο στυλ. Για παράδειγμα, ένα ενιαίο ύφος θα μπορούσε να αλλάξει την αρχική σελίδα ενός ιστότοπου με έναν τρόπο, ενώ αλλάζει το υπόλοιπο μιας τοποθεσίας ένας άλλος τρόπος."
},
"sectionRemove": {
"message": "Αφαίρεση ενότητας"
},
"styleBadRegexp": {
"message": "Το Regexp δεν είναι έγκυρο."
},
"styleCancelEditLabel": {
"message": "Πίσω στη διαχείριση"
},
"styleChangesNotSaved": {
"message": "Έχετε κάνει αλλαγές σε αυτό το ύφος χωρίς αποθήκευση."
},
"styleEnabledLabel": {
"message": "Ενεργοποιημένη"
},
"styleInstall": {
"message": "Εγκατάσταση του '$stylename$' στο Stylus;",
"placeholders": {
"stylename": {
"content": "$1"
}
}
},
"styleMissingName": {
"message": "Εισάγετε ένα όνομα"
},
"styleSaveLabel": {
"message": "Αποθήκευση"
},
"styleSectionsTitle": {
"message": "Ενότητες"
},
"styleToMozillaFormatHelp": {
"message": "Η μορφή του Mozilla κώδικα μπορεί να χρησιμοποιηθεί με το Stylish για το Firefox και μπορεί να υποβληθεί στο userstyles.org."
},
"styleUpdate": {
"message": "Είστε σίγουροι ότι θέλετε να ενημερώσετε το '$stylename$';",
"placeholders": {
"stylename": {
"content": "$1"
}
}
},
"stylusUnavailableForURL": {
"message": "To Stylus δεν λειτουργεί σε σελίδες όπως αυτή."
},
"updateAllCheckSucceededNoUpdate": {
"message": "Όλα τα στυλ είναι ενημερωμένα."
}, },
"updateCheckFailBadResponseCode": { "updateCheckFailBadResponseCode": {
"message": "Αποτυχία ενημέρωσης: ο διακομιστής ανταποκρίθηκε με κωδικό $code$.", "message": "Αποτυχία ενημέρωσης: ο διακομιστής ανταποκρίθηκε με κωδικό $code$.",
"description": "Text that displays when an update check failed because the response code indicates an error",
"placeholders": { "placeholders": {
"code": { "code": {
"content": "$1" "content": "$1"
} }
} }
}, },
"appliesSpecify": { "updateCheckFailServerUnreachable": {
"message": "Καθορισμός", "message": "Αποτυχία ενημέρωσης: απρόσιτος διακομιστής."
"description": "Label for the button to make a style apply only to specific sites"
}, },
"installUpdate": { "updateCheckSucceededNoUpdate": {
"message": "Εγκατάσταση ενημέρωσης", "message": "Το στυλ είναι ενημερωμένο."
"description": "Label for the button to install an update for a single style"
},
"sectionRemove": {
"message": "Αφαίρεση ενότητας",
"description": "Label for the button to remove a section"
},
"disableAllStyles": {
"message": "Απενεργοποιηση ολων των στυλ",
"description": "Label for the checkbox that turns all enabled styles off."
}, },
"updateCompleted": { "updateCompleted": {
"message": "Η ενημέρωση ολοκληρώθηκε.", "message": "Η ενημέρωση ολοκληρώθηκε."
"description": "Text that displays when an update completed"
}, },
"checkingForUpdate": { "writeStyleFor": {
"message": "Έλεγχος...", "message": "Γράψτε νέο στυλ για:"
"description": "Text to display when checking a style for an update"
}, },
"sectionCode": { "writeStyleForURL": {
"message": "Κώδικας", "message": "αυτή την διεύθυνση URL"
"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"
}, },
"enableStyleLabel": { "addStyleTitle": {
"message": "Aktivoi", "message": "Lisää Tyyli"
"description": "Label for the button to enable a style"
},
"styleMissingName": {
"message": "Syötä nimi",
"description": "Error displayed when user saves without providing a name"
},
"appliesDomainOption": {
"message": "URL ositteita domainilla",
"description": "Option to make the style apply to the entered string as a domain"
},
"checkForUpdate": {
"message": "Hae päivityksiä",
"description": "Label for the button to check a single style for an update"
},
"updateAllCheckSucceededNoUpdate": {
"message": "All styles are up to date.",
"description": "Text that displays when an update all check completed and no updates are available"
},
"helpAlt": {
"message": "Apu",
"description": "Alternate text for help buttons"
},
"findStylesForSite": {
"message": "Hae lisää tyylejä tälle sivustolle",
"description": "Text for a link that gets a list of styles for the current site"
},
"manageHeading": {
"message": "Asennetut Tyylit",
"description": "Heading for the manage page"
},
"styleEnabledLabel": {
"message": "Aktivoitu",
"description": "Label for the enabled state of styles"
},
"styleToMozillaFormatHelp": {
"message": "Mozilla formaattia koodista voidaan käyttää Stylish Firefoxille ohjelmassa ja voidaan lähettää userstyles.orgiin.",
"description": "Help info for the Mozilla format header section that converts the code to/from Mozilla format"
},
"sectionAdd": {
"message": "Lisää uusi osio",
"description": "Label for the button to add a section"
},
"styleSaveLabel": {
"message": "Tallenna",
"description": "Label for save button for style editing"
}, },
"appliesAdd": { "appliesAdd": {
"message": "Lisää", "message": "Lisää"
"description": "Label for the button to add an 'applies' entry"
},
"appliesRegexpOption": {
"message": "URL ositteet jotka vastaavat regexpiä",
"description": "Option to make the style apply to the entered string as a regular expression"
},
"styleInstall": {
"message": "Asennetaanko '$stylename$' Stylusiin?",
"description": "Confirmation when installing a style",
"placeholders": {
"stylename": {
"content": "$1"
}
}
},
"disableStyleLabel": {
"message": "Poista Käytöstä",
"description": "Label for the button to disable a style"
},
"prefShowBadge": {
"message": "Show number of styles active for the current site on the toolbar button",
"description": "Label for the checkbox controlling toolbar badge text."
},
"styleCancelEditLabel": {
"message": "Takaisin hallintapaneeliin",
"description": "Label for cancel button for style editing"
},
"styleChangesNotSaved": {
"message": "Olet tehnyt muutoksia tähän tyyliin tallentamatta.",
"description": "Text for the prompt when changes are made to a style and the user tries to leave without saving"
},
"updateCheckFailServerUnreachable": {
"message": "Päivitys epäonnistui: ei voitu yhdistää palvelimeen.",
"description": "Text that displays when an update check failed because the update server is unreachable"
},
"deleteStyleConfirm": {
"message": "Oletko varma että haluat poistaa tämän tyylin?",
"description": "Confirmation before deleting a style"
},
"styleBadRegexp": {
"message": "Regexp ei kelpaa.",
"description": "Validation message for a bad regexp in a style"
}, },
"appliesDisplay": { "appliesDisplay": {
"message": "Kooskee: $applies$", "message": "Kooskee: $applies$",
"description": "Text on the manage screen to describe what the style applies to",
"placeholders": { "placeholders": {
"applies": { "applies": {
"content": "$1" "content": "$1"
} }
} }
}, },
"styleSectionsTitle": { "appliesDisplayTruncatedSuffix": {
"message": "Osiot", "message": "ja lisää"
"description": "Title for the style sections section" },
"appliesDomainOption": {
"message": "URL ositteita domainilla"
},
"appliesHelp": {
"message": "Käytä 'Koskee' kontrolleja rajoittaaksesi mitä URL osoitteisiin tämä osio koodista koskee."
},
"appliesLabel": {
"message": "Koskee"
},
"appliesRegexpOption": {
"message": "URL ositteet jotka vastaavat regexpiä"
},
"appliesRemove": {
"message": "Poista"
},
"appliesSpecify": {
"message": "Tarkenna"
},
"appliesToEverything": {
"message": "Kaikki"
},
"appliesUrlPrefixOption": {
"message": "URL osoitteet jotka alkavat"
},
"checkAllUpdates": {
"message": "Tarkista kaikki tyylit päivityksien varalta"
},
"checkForUpdate": {
"message": "Hae päivityksiä"
},
"checkingForUpdate": {
"message": "Tarkistetaan..."
},
"deleteStyleConfirm": {
"message": "Oletko varma että haluat poistaa tämän tyylin?"
},
"deleteStyleLabel": {
"message": "Poista"
},
"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."
},
"disableStyleLabel": {
"message": "Poista Käytöstä"
},
"editStyleHeading": {
"message": "Muokkaa Tyyliä"
},
"editStyleLabel": {
"message": "Muokkaa"
}, },
"editStyleTitle": { "editStyleTitle": {
"message": "Muokkaa Tyyliä $stylename$", "message": "Muokkaa Tyyliä $stylename$",
"description": "Title of the page for editing styles",
"placeholders": { "placeholders": {
"stylename": { "stylename": {
"content": "$1" "content": "$1"
} }
} }
}, },
"updateCheckSucceededNoUpdate": { "enableStyleLabel": {
"message": "Tyyli on ajan tasalla.", "message": "Aktivoi"
"description": "Text that displays when an update check completed and no update is available"
}, },
"appliesUrlPrefixOption": { "findStylesForSite": {
"message": "URL osoitteet jotka alkavat", "message": "Hae lisää tyylejä tälle sivustolle"
"description": "Option to make the style apply to the entered string as a URL prefix"
}, },
"popupStylesFirst": { "helpAlt": {
"message": "List styles before commands in the toolbar button menu", "message": "Apu"
"description": "Label for the checkbox controlling section order in the popup."
}, },
"sectionHelp": { "installUpdate": {
"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.", "message": "Asenna päivitys"
"description": "Help text for sections"
}, },
"noStylesForSite": { "manageHeading": {
"message": "Ei asennettuja tyylejä tällä sivustolla.", "message": "Asennetut Tyylit"
"description": "Text displayed when no styles are installed for the current site"
},
"appliesDisplayTruncatedSuffix": {
"message": "ja lisää",
"description": "Text added to appliesDisplay when there are more sites for the style than are displayed"
},
"appliesRemove": {
"message": "Poista",
"description": "Label for the button to remove an 'applies' entry"
}, },
"manageTitle": { "manageTitle": {
"message": "Tyylikäs", "message": "Tyylikäs"
"description": "Title for the manage page"
}, },
"appliesLabel": { "noStylesForSite": {
"message": "Koskee", "message": "Ei asennettuja tyylejä tällä sivustolla."
"description": "Label for 'applies to' fields on the edit/add screen"
}, },
"openManage": { "openManage": {
"message": "Hallitse asennettuja tyylejä", "message": "Hallitse asennettuja tyylejä"
"description": "Link to open the manage page." },
"popupStylesFirst": {
"message": "List styles before commands in the toolbar button menu"
},
"prefShowBadge": {
"message": "Show number of styles active for the current site on the toolbar button"
},
"sectionAdd": {
"message": "Lisää uusi osio"
},
"sectionCode": {
"message": "Koodi"
},
"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": {
"message": "Poista osio"
},
"styleBadRegexp": {
"message": "Regexp ei kelpaa."
},
"styleCancelEditLabel": {
"message": "Takaisin hallintapaneeliin"
},
"styleChangesNotSaved": {
"message": "Olet tehnyt muutoksia tähän tyyliin tallentamatta."
},
"styleEnabledLabel": {
"message": "Aktivoitu"
},
"styleInstall": {
"message": "Asennetaanko '$stylename$' Stylusiin?",
"placeholders": {
"stylename": {
"content": "$1"
}
}
},
"styleMissingName": {
"message": "Syötä nimi"
},
"styleSaveLabel": {
"message": "Tallenna"
},
"styleSectionsTitle": {
"message": "Osiot"
},
"styleToMozillaFormatHelp": {
"message": "Mozilla formaattia koodista voidaan käyttää Stylish Firefoxille ohjelmassa ja voidaan lähettää userstyles.orgiin."
},
"updateAllCheckSucceededNoUpdate": {
"message": "All styles are up to date."
}, },
"updateCheckFailBadResponseCode": { "updateCheckFailBadResponseCode": {
"message": "Päivitys epäonnistui: palvelin vastasi koodilla $code$.", "message": "Päivitys epäonnistui: palvelin vastasi koodilla $code$.",
"description": "Text that displays when an update check failed because the response code indicates an error",
"placeholders": { "placeholders": {
"code": { "code": {
"content": "$1" "content": "$1"
} }
} }
}, },
"appliesSpecify": { "updateCheckFailServerUnreachable": {
"message": "Tarkenna", "message": "Päivitys epäonnistui: ei voitu yhdistää palvelimeen."
"description": "Label for the button to make a style apply only to specific sites"
}, },
"installUpdate": { "updateCheckSucceededNoUpdate": {
"message": "Asenna päivitys", "message": "Tyyli on ajan tasalla."
"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": { "updateCompleted": {
"message": "Päivitys suoritettu.", "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"
}, },
"defaultTheme": { "addStyleTitle": {
"message": "padrão", "message": "Adicionar estilo"
"description": "Default CodeMirror CSS theme option on the edit style page"
},
"bckpInstStyles": {
"message": "Exportar estilos",
"description": ""
},
"exportLabel": {
"message": "Exportar ",
"description": "Label for the button to export a style ('edit' page) or all styles ('manage' page)"
},
"optionsBadgeNormal": {
"message": "Cor de fundo",
"description": ""
},
"enableStyleLabel": {
"message": "Ativar",
"description": "Label for the button to enable a style"
},
"styleMissingName": {
"message": "Insira um nome",
"description": "Error displayed when user saves without providing a name"
},
"appliesDomainOption": {
"message": "URLs no domínio",
"description": "Option to make the style apply to the entered string as a domain"
},
"checkForUpdate": {
"message": "Verificar atualizações",
"description": "Label for the button to check a single style for an update"
},
"importAppendLabel": {
"message": "Anexar ao estilo",
"description": "Label for the button to import a style and append to the existing sections"
},
"updateAllCheckSucceededNoUpdate": {
"message": "Nenhuma atualização encontrada.",
"description": "Text that displays when an update all check completed and no updates are available"
},
"helpAlt": {
"message": "Ajuda",
"description": "Alternate text for help buttons"
},
"search": {
"message": "Buscar",
"description": "Label before the search input field in the editor shown on Ctrl-F"
},
"confirmYes": {
"message": "Sim",
"description": "'Yes' button in a confirm dialog"
},
"findStylesForSite": {
"message": "Procurar mais estilos para este site",
"description": "Text for a link that gets a list of styles for the current site"
},
"manageHeading": {
"message": "Estilos instalados",
"description": "Heading for the manage page"
},
"styleBeautify": {
"message": "Embelezar",
"description": "Label for the CSS-beautifier button on the edit style page"
},
"styleEnabledLabel": {
"message": "Ativado",
"description": "Label for the enabled state of styles"
},
"styleToMozillaFormatHelp": {
"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"
},
"sectionAdd": {
"message": "Adicionar outra seção",
"description": "Label for the button to add a section"
},
"styleSaveLabel": {
"message": "Salvar",
"description": "Label for save button for style editing"
},
"confirmDelete": {
"message": "Deletar",
"description": ""
},
"confirmCancel": {
"message": "Cancelar",
"description": ""
},
"retrieveBckp": {
"message": "Importar estilos",
"description": ""
},
"confirmStop": {
"message": "Parar",
"description": "'Stop' button in a confirm dialog"
},
"writeStyleForURL": {
"message": "esse URL",
"description": "Text for link in toolbar pop-up to write a new style for the current URL"
},
"optionsSubheading": {
"message": "Mais Opções",
"description": "Subheading for options section on manage page."
}, },
"appliesAdd": { "appliesAdd": {
"message": "Adicionar", "message": "Adicionar"
"description": "Label for the button to add an 'applies' entry"
},
"appliesRegexpOption": {
"message": "URLs que correspondem a regexp",
"description": "Option to make the style apply to the entered string as a regular expression"
},
"styleInstall": {
"message": "Instalar \"$stylename$\" no Stylus?",
"description": "Confirmation when installing a style",
"placeholders": {
"stylename": {
"content": "$1"
}
}
},
"optionsBadgeDisabled": {
"message": "Cor de fundo quando desativado",
"description": ""
},
"optionsCheck": {
"message": "Atualizar estilos",
"description": ""
},
"searchStyles": {
"message": "Buscar conteúdos",
"description": "Label for the search filter textbox on the Manage styles page"
},
"disableStyleLabel": {
"message": "Desativar",
"description": "Label for the button to disable a style"
},
"prefShowBadge": {
"message": "Show number of styles active for the current site on the toolbar button",
"description": "Label for the checkbox controlling toolbar badge text."
},
"styleCancelEditLabel": {
"message": "Voltar ao gerenciamento",
"description": "Label for cancel button for style editing"
},
"styleChangesNotSaved": {
"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"
},
"importLabel": {
"message": "Importar",
"description": "Label for the button to import a style ('edit' page) or all styles ('manage' page)"
},
"updateCheckFailServerUnreachable": {
"message": "A atualização falhou: servidor inacessível.",
"description": "Text that displays when an update check failed because the update server is unreachable"
},
"applyAllUpdates": {
"message": "Aplicar todas as atualizações",
"description": "Label for the button to apply all detected updates"
},
"deleteStyleConfirm": {
"message": "Tem certeza de que deseja excluir este estilo?",
"description": "Confirmation before deleting a style"
},
"styleBadRegexp": {
"message": "Expressão regular é inválida",
"description": "Validation message for a bad regexp in a style"
},
"optionsHeading": {
"message": "Opções",
"description": "Heading for options section on manage page."
}, },
"appliesDisplay": { "appliesDisplay": {
"message": "Aplica-se a: $applies$", "message": "Aplica-se a: $applies$",
"description": "Text on the manage screen to describe what the style applies to",
"placeholders": { "placeholders": {
"applies": { "applies": {
"content": "$1" "content": "$1"
} }
} }
}, },
"styleSectionsTitle": { "appliesDisplayTruncatedSuffix": {
"message": "Seções", "message": "e mais"
"description": "Title for the style sections section" },
"appliesDomainOption": {
"message": "URLs no domínio"
},
"appliesHelp": {
"message": "Use os controles \"Aplica-se a\" para limitar a quais URLs o código desta seção se aplica."
},
"appliesLabel": {
"message": "Aplica-se a"
},
"appliesRegexpOption": {
"message": "URLs que correspondem a regexp"
},
"appliesRemove": {
"message": "Remover"
},
"appliesSpecify": {
"message": "Especificar"
},
"appliesToEverything": {
"message": "Tudo"
},
"appliesUrlPrefixOption": {
"message": "URLs que começam com"
},
"applyAllUpdates": {
"message": "Aplicar todas as atualizações"
},
"bckpInstStyles": {
"message": "Exportar estilos"
},
"checkAllUpdates": {
"message": "Verificar atualizações para todos os estilos"
},
"checkForUpdate": {
"message": "Verificar atualizações"
},
"checkingForUpdate": {
"message": "Verificando..."
},
"cm_theme": {
"message": "Tema"
},
"confirmCancel": {
"message": "Cancelar"
},
"confirmDelete": {
"message": "Deletar"
},
"confirmNo": {
"message": "Não"
},
"confirmStop": {
"message": "Parar"
},
"confirmYes": {
"message": "Sim"
},
"defaultTheme": {
"message": "padrão"
},
"deleteStyleConfirm": {
"message": "Tem certeza de que deseja excluir este estilo?"
},
"deleteStyleLabel": {
"message": "Excluir"
},
"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."
},
"disableAllStyles": {
"message": "Desativar todos os estilos"
},
"disableStyleLabel": {
"message": "Desativar"
},
"editGotoLine": {
"message": "Ir para linha (ou linha:coluna)"
},
"editStyleHeading": {
"message": "Editar estilo"
},
"editStyleLabel": {
"message": "Editar"
}, },
"editStyleTitle": { "editStyleTitle": {
"message": "Editar estilo $stylename$", "message": "Editar estilo $stylename$",
"description": "Title of the page for editing styles",
"placeholders": { "placeholders": {
"stylename": { "stylename": {
"content": "$1" "content": "$1"
} }
} }
}, },
"updateCheckSucceededNoUpdate": { "enableStyleLabel": {
"message": "O estilo está atualizado.", "message": "Ativar"
"description": "Text that displays when an update check completed and no update is available"
}, },
"appliesUrlPrefixOption": { "exportLabel": {
"message": "URLs que começam com", "message": "Exportar "
"description": "Option to make the style apply to the entered string as a URL prefix"
}, },
"sectionHelp": { "findStylesForSite": {
"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.", "message": "Procurar mais estilos para este site"
"description": "Help text for sections" },
"helpAlt": {
"message": "Ajuda"
},
"importAppendLabel": {
"message": "Anexar ao estilo"
},
"importAppendTooltip": {
"message": "Anexar o estilo importado ao atual"
},
"importLabel": {
"message": "Importar"
},
"importReplaceLabel": {
"message": "Sobrescrever estilo"
},
"installUpdate": {
"message": "Instalar atualização"
},
"manageHeading": {
"message": "Estilos instalados"
}, },
"noStylesForSite": { "noStylesForSite": {
"message": "Nenhum estilo instalado para este site.", "message": "Nenhum estilo instalado para este site."
"description": "Text displayed when no styles are installed for the current site"
},
"appliesDisplayTruncatedSuffix": {
"message": "e mais",
"description": "Text added to appliesDisplay when there are more sites for the style than are displayed"
},
"appliesRemove": {
"message": "Remover",
"description": "Label for the button to remove an 'applies' entry"
},
"replace": {
"message": "Substituir",
"description": "Label before the replace input field in the editor shown on Ctrl-H"
},
"appliesLabel": {
"message": "Aplica-se a",
"description": "Label for 'applies to' fields on the edit/add screen"
},
"openOptionsPopup": {
"message": "Opções",
"description": "Go to Options UI"
}, },
"openManage": { "openManage": {
"message": "Gerenciar estilos instalados", "message": "Gerenciar estilos instalados"
"description": "Link to open the manage page." },
"openOptionsPopup": {
"message": "Opções"
},
"optionsActions": {
"message": "Ações"
},
"optionsBadgeDisabled": {
"message": "Cor de fundo quando desativado"
},
"optionsBadgeNormal": {
"message": "Cor de fundo"
},
"optionsCheck": {
"message": "Atualizar estilos"
},
"optionsHeading": {
"message": "Opções"
},
"optionsOpen": {
"message": "Abrir"
},
"optionsSubheading": {
"message": "Mais Opções"
},
"prefShowBadge": {
"message": "Show number of styles active for the current site on the toolbar button"
},
"replace": {
"message": "Substituir"
},
"replaceAll": {
"message": "Substituir todos"
},
"replaceWith": {
"message": "Substituir com"
},
"retrieveBckp": {
"message": "Importar estilos"
},
"search": {
"message": "Buscar"
},
"searchStyles": {
"message": "Buscar conteúdos"
},
"sectionAdd": {
"message": "Adicionar outra seção"
},
"sectionCode": {
"message": "Código"
},
"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."
},
"sectionRemove": {
"message": "Remover seção"
},
"styleBadRegexp": {
"message": "Expressão regular é inválida"
},
"styleBeautify": {
"message": "Embelezar"
},
"styleCancelEditLabel": {
"message": "Voltar ao gerenciamento"
},
"styleChangesNotSaved": {
"message": "Você fez alterações neste estilo sem salvar."
},
"styleEnabledLabel": {
"message": "Ativado"
},
"styleInstall": {
"message": "Instalar \"$stylename$\" no Stylus?",
"placeholders": {
"stylename": {
"content": "$1"
}
}
},
"styleMissingName": {
"message": "Insira um nome"
},
"styleMozillaFormatHeading": {
"message": "Formato Mozilla"
},
"styleSaveLabel": {
"message": "Salvar"
},
"styleSectionsTitle": {
"message": "Seções"
},
"styleToMozillaFormatHelp": {
"message": "O formato Mozilla do código pode ser usado com o Stylish para Firefox e pode ser enviado para userstyles.org."
},
"undo": {
"message": "Desfazer"
},
"undoGlobal": {
"message": "Desfazer todas as seções"
},
"updateAllCheckSucceededNoUpdate": {
"message": "Nenhuma atualização encontrada."
}, },
"updateCheckFailBadResponseCode": { "updateCheckFailBadResponseCode": {
"message": "A atualização falhou: o servidor respondeu com código $code$.", "message": "A atualização falhou: o servidor respondeu com código $code$.",
"description": "Text that displays when an update check failed because the response code indicates an error",
"placeholders": { "placeholders": {
"code": { "code": {
"content": "$1" "content": "$1"
} }
} }
}, },
"appliesSpecify": { "updateCheckFailServerUnreachable": {
"message": "Especificar", "message": "A atualização falhou: servidor inacessível."
"description": "Label for the button to make a style apply only to specific sites"
}, },
"installUpdate": { "updateCheckSucceededNoUpdate": {
"message": "Instalar atualização", "message": "O estilo está atualizado."
"description": "Label for the button to install an update for a single style"
},
"styleMozillaFormatHeading": {
"message": "Formato Mozilla",
"description": "Heading for the section with buttons to import/export Mozilla format of the style"
},
"sectionRemove": {
"message": "Remover seção",
"description": "Label for the button to remove a section"
},
"disableAllStyles": {
"message": "Desativar todos os estilos",
"description": "Label for the checkbox that turns all enabled styles off."
},
"undoGlobal": {
"message": "Desfazer todas as seções",
"description": "CSS-beautify global Undo button label"
}, },
"updateCompleted": { "updateCompleted": {
"message": "Atualização concluída.", "message": "Atualização concluída."
"description": "Text that displays when an update completed"
}, },
"checkingForUpdate": { "writeStyleForURL": {
"message": "Verificando...", "message": "esse URL"
"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"
}, },
"linterIssues": { "addStyleTitle": {
"message": "Проблеми", "message": "Додај стил"
"description": "Label for the CSS linter issues block on the style edit page"
},
"defaultTheme": {
"message": "подразумевано",
"description": "Default CodeMirror CSS theme option on the edit style page"
},
"exportLabel": {
"message": "Извези",
"description": "Label for the button to export a style ('edit' page) or all styles ('manage' page)"
},
"cm_tabSize": {
"message": "Величина картице",
"description": "Label for the text box controlling tab size option for the style editor."
},
"enableStyleLabel": {
"message": "Омогући",
"description": "Label for the button to enable a style"
},
"styleMissingName": {
"message": "Унесите назив",
"description": "Error displayed when user saves without providing a name"
},
"appliesDomainOption": {
"message": "УРЛ адресе на домену",
"description": "Option to make the style apply to the entered string as a domain"
},
"checkForUpdate": {
"message": "Проверите ажурирање",
"description": "Label for the button to check a single style for an update"
},
"importAppendLabel": {
"message": "Додај стилу",
"description": "Label for the button to import a style and append to the existing sections"
},
"updateAllCheckSucceededNoUpdate": {
"message": "Сви стилови су ажурирани.",
"description": "Text that displays when an update all check completed and no updates are available"
},
"styleFromMozillaFormatPrompt": {
"message": "Налепи код у Mozilla формату",
"description": "Prompt in the dialog displayed after clicking 'Import from Mozilla format' button"
},
"helpAlt": {
"message": "Помоћ",
"description": "Alternate text for help buttons"
},
"search": {
"message": "Претражи",
"description": "Label before the search input field in the editor shown on Ctrl-F"
},
"confirmYes": {
"message": "Да",
"description": "'Yes' button in a confirm dialog"
},
"findStylesForSite": {
"message": "Пронађи још стилова за овај сајт",
"description": "Text for a link that gets a list of styles for the current site"
},
"manageHeading": {
"message": "Инсталирани стилови",
"description": "Heading for the manage page"
},
"styleBeautify": {
"message": "Улепшај",
"description": "Label for the CSS-beautifier button on the edit style page"
},
"styleEnabledLabel": {
"message": "Омогућено",
"description": "Label for the enabled state of styles"
},
"styleToMozillaFormatHelp": {
"message": "Mozilla формат кода се може користити у Stylish за Firefox и може се послати на userstyles.org.",
"description": "Help info for the Mozilla format header section that converts the code to/from Mozilla format"
},
"sectionAdd": {
"message": "Додај нови одељак",
"description": "Label for the button to add a section"
},
"styleSaveLabel": {
"message": "Сачувај",
"description": "Label for save button for style editing"
},
"confirmStop": {
"message": "Заустави",
"description": "'Stop' button in a confirm dialog"
},
"writeStyleForURL": {
"message": "ову УРЛ адресу",
"description": "Text for link in toolbar pop-up to write a new style for the current URL"
}, },
"appliesAdd": { "appliesAdd": {
"message": "Додај", "message": "Додај"
"description": "Label for the button to add an 'applies' entry"
},
"appliesRegexpOption": {
"message": "УРЛ адресе које одговарају регуларном изразу",
"description": "Option to make the style apply to the entered string as a regular expression"
},
"styleInstall": {
"message": "Инсталирати '$stylename$' у Stylus?",
"description": "Confirmation when installing a style",
"placeholders": {
"stylename": {
"content": "$1"
}
}
},
"linterIssuesHelp": {
"message": "Проблем пронађен од стране $link$:",
"description": "Help popup message for the selected CSS linter issues block on the style edit page",
"placeholders": {
"link": {
"content": "$1"
}
}
},
"searchStyles": {
"message": "Претражи садржај",
"description": "Label for the search filter textbox on the Manage styles page"
},
"linkGetStyles": {
"message": "Преузмите стилове",
"description": "Help link text on the manage page e.g. https://userstyles.org"
},
"disableStyleLabel": {
"message": "Онемогући",
"description": "Label for the button to disable a style"
},
"prefShowBadge": {
"message": "Прикажи број активних стилова за тренутни сајт на дугмету на алатној траци",
"description": "Label for the checkbox controlling toolbar badge text."
},
"menuShowBadge": {
"message": "Прикажи број активних стилова",
"description": "Label (must be very short) for the checkbox in the toolbar button context menu controlling toolbar badge text."
},
"cm_lineWrapping": {
"message": "Преламање текста",
"description": "Label for the checkbox controlling word wrap option for the style editor."
},
"styleCancelEditLabel": {
"message": "Назад на управљање",
"description": "Label for cancel button for style editing"
},
"styleChangesNotSaved": {
"message": "Направили сте измене овог стила које нисте сачували.",
"description": "Text for the prompt when changes are made to a style and the user tries to leave without saving"
},
"importLabel": {
"message": "Увези",
"description": "Label for the button to import a style ('edit' page) or all styles ('manage' page)"
},
"updateCheckFailServerUnreachable": {
"message": "Ажурирање није успело: сервер није доступан.",
"description": "Text that displays when an update check failed because the update server is unreachable"
},
"manageFilters": {
"message": "Филтери",
"description": "Label for filters container"
},
"applyAllUpdates": {
"message": "Примени сва ажурирања",
"description": "Label for the button to apply all detected updates"
},
"deleteStyleConfirm": {
"message": "Да ли сте сигурни да желите да избришете овај стил?",
"description": "Confirmation before deleting a style"
},
"styleBadRegexp": {
"message": "Регуларни израз је неисправан.",
"description": "Validation message for a bad regexp in a style"
},
"optionsHeading": {
"message": "Опције",
"description": "Heading for options section on manage page."
}, },
"appliesDisplay": { "appliesDisplay": {
"message": "Примењује се на: $applies$", "message": "Примењује се на: $applies$",
"description": "Text on the manage screen to describe what the style applies to",
"placeholders": { "placeholders": {
"applies": { "applies": {
"content": "$1" "content": "$1"
} }
} }
}, },
"styleUpdate": { "appliesDisplayTruncatedSuffix": {
"message": "Да ли сте сигурни да желите да ажурирате '$stylename$'?", "message": "и још"
"description": "Confirmation when updating a style",
"placeholders": {
"stylename": {
"content": "$1"
}
}
}, },
"styleSectionsTitle": { "appliesDomainOption": {
"message": "Одељци", "message": "УРЛ адресе на домену"
"description": "Title for the style sections section" },
"appliesHelp": {
"message": "Употреба 'Примењује се на' одређује опсег УРЛ адреса на које се код у овом одељку примењује."
},
"appliesLabel": {
"message": "Примењује се на"
},
"appliesRegexpOption": {
"message": "УРЛ адресе које одговарају регуларном изразу"
},
"appliesRemove": {
"message": "Уклони"
},
"appliesSpecify": {
"message": "Детаљније"
},
"appliesToEverything": {
"message": "Све"
},
"appliesUrlOption": {
"message": "УРЛ"
},
"appliesUrlPrefixOption": {
"message": "УРЛ адресе које почињу са"
},
"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 вам омогућава да лако инсталирате теме и скинове за многе популарне сајтове."
},
"disableAllStyles": {
"message": "Искључи све стилове"
},
"disableStyleLabel": {
"message": "Онемогући"
},
"editGotoLine": {
"message": "Иди на ред (или line:col)"
},
"editStyleHeading": {
"message": "Уреди стил"
},
"editStyleLabel": {
"message": "Уреди"
}, },
"editStyleTitle": { "editStyleTitle": {
"message": "Уреди стил $stylename$", "message": "Уреди стил $stylename$",
"description": "Title of the page for editing styles",
"placeholders": { "placeholders": {
"stylename": { "stylename": {
"content": "$1" "content": "$1"
} }
} }
}, },
"updateCheckSucceededNoUpdate": { "enableStyleLabel": {
"message": "Стил је ажуриран.", "message": "Омогући"
"description": "Text that displays when an update check completed and no update is available"
}, },
"appliesUrlPrefixOption": { "exportLabel": {
"message": "УРЛ адресе које почињу са", "message": "Извези"
"description": "Option to make the style apply to the entered string as a URL prefix"
}, },
"searchRegexp": { "findStylesForSite": {
"message": "Користи /re/ синтаксу за претрагу регуларним изразом", "message": "Пронађи још стилова за овај сајт"
"description": "Label after the search input field in the editor shown on Ctrl-F" },
"helpAlt": {
"message": "Помоћ"
},
"helpKeyMapCommand": {
"message": "Укуцај име команде"
},
"helpKeyMapHotkey": {
"message": "Притисни пречицу"
},
"importAppendLabel": {
"message": "Додај стилу"
},
"importAppendTooltip": {
"message": "Додај увезени стил тренутном стилу"
},
"importLabel": {
"message": "Увези"
},
"importReplaceLabel": {
"message": "Упиши преко стила"
}, },
"importReplaceTooltip": { "importReplaceTooltip": {
"message": "Одбаци садржај тренутног стила и упиши преко њега увезени стил", "message": "Одбаци садржај тренутног стила и упиши преко њега увезени стил"
"description": "Label for the button to import and overwrite current style"
}, },
"popupStylesFirst": { "installUpdate": {
"message": "Излистај стилове пре команди у менију дугмета на алатној траци", "message": "Инсталирај ажурирање"
"description": "Label for the checkbox controlling section order in the popup."
}, },
"sectionHelp": { "linkGetHelp": {
"message": "Одељци вам омогућавају да дефинишете различите делове кода који се примењују на раличите скупове УРЛ-ова у истом стилу. На пример, један исти стил може променити почетну страницу једног сајта на један начин а остатак сајта на други начин.", "message": "Помоћ"
"description": "Help text for sections" },
"linkGetStyles": {
"message": "Преузмите стилове"
},
"linterIssues": {
"message": "Проблеми"
},
"linterIssuesHelp": {
"message": "Проблем пронађен од стране $link$:",
"placeholders": {
"link": {
"content": "$1"
}
}
},
"manageFilters": {
"message": "Филтери"
},
"manageHeading": {
"message": "Инсталирани стилови"
},
"manageOnlyEnabled": {
"message": "Само омогућени стилови"
},
"menuShowBadge": {
"message": "Прикажи број активних стилова"
}, },
"noStylesForSite": { "noStylesForSite": {
"message": "Нема инсталираних стилова за овај сајт.", "message": "Нема инсталираних стилова за овај сајт."
"description": "Text displayed when no styles are installed for the current site"
},
"appliesDisplayTruncatedSuffix": {
"message": "и још",
"description": "Text added to appliesDisplay when there are more sites for the style than are displayed"
},
"appliesRemove": {
"message": "Уклони",
"description": "Label for the button to remove an 'applies' entry"
},
"styleToMozillaFormatTitle": {
"message": "Стил у Mozilla формату",
"description": "Title of the popup with the style code in Mozilla format, shown after pressing the Export button on Edit style page"
},
"writeStyleFor": {
"message": "Упиши стил за:",
"description": "Label for toolbar pop-up that precedes the links to write a new style"
},
"replace": {
"message": "Замени",
"description": "Label before the replace input field in the editor shown on Ctrl-H"
},
"appliesLabel": {
"message": "Примењује се на",
"description": "Label for 'applies to' fields on the edit/add screen"
}, },
"openManage": { "openManage": {
"message": "Управљај инсталираним стиловима", "message": "Управљај инсталираним стиловима"
"description": "Link to open the manage page." },
"optionsHeading": {
"message": "Опције"
},
"popupStylesFirst": {
"message": "Излистај стилове пре команди у менију дугмета на алатној траци"
},
"prefShowBadge": {
"message": "Прикажи број активних стилова за тренутни сајт на дугмету на алатној траци"
},
"replace": {
"message": "Замени"
},
"replaceAll": {
"message": "Замени све"
},
"replaceWith": {
"message": "Замени са"
},
"search": {
"message": "Претражи"
},
"searchRegexp": {
"message": "Користи /re/ синтаксу за претрагу регуларним изразом"
},
"searchStyles": {
"message": "Претражи садржај"
},
"sectionAdd": {
"message": "Додај нови одељак"
},
"sectionCode": {
"message": "Код"
},
"sectionHelp": {
"message": "Одељци вам омогућавају да дефинишете различите делове кода који се примењују на раличите скупове УРЛ-ова у истом стилу. На пример, један исти стил може променити почетну страницу једног сајта на један начин а остатак сајта на други начин."
},
"sectionRemove": {
"message": "Уклони одељак"
},
"styleBadRegexp": {
"message": "Регуларни израз је неисправан."
},
"styleBeautify": {
"message": "Улепшај"
},
"styleCancelEditLabel": {
"message": "Назад на управљање"
},
"styleChangesNotSaved": {
"message": "Направили сте измене овог стила које нисте сачували."
},
"styleEnabledLabel": {
"message": "Омогућено"
},
"styleFromMozillaFormatPrompt": {
"message": "Налепи код у Mozilla формату"
},
"styleInstall": {
"message": "Инсталирати '$stylename$' у Stylus?",
"placeholders": {
"stylename": {
"content": "$1"
}
}
},
"styleMissingName": {
"message": "Унесите назив"
},
"styleMozillaFormatHeading": {
"message": "Mozilla формат"
},
"styleSaveLabel": {
"message": "Сачувај"
},
"styleSectionsTitle": {
"message": "Одељци"
},
"styleToMozillaFormatHelp": {
"message": "Mozilla формат кода се може користити у Stylish за Firefox и може се послати на userstyles.org."
},
"styleToMozillaFormatTitle": {
"message": "Стил у Mozilla формату"
},
"styleUpdate": {
"message": "Да ли сте сигурни да желите да ажурирате '$stylename$'?",
"placeholders": {
"stylename": {
"content": "$1"
}
}
},
"stylusUnavailableForURL": {
"message": "Stylus не ради на страницама као што је ова."
},
"undo": {
"message": "Опозови"
},
"undoGlobal": {
"message": "Опозови (свеобухватно)"
},
"updateAllCheckSucceededNoUpdate": {
"message": "Сви стилови су ажурирани."
}, },
"updateCheckFailBadResponseCode": { "updateCheckFailBadResponseCode": {
"message": "Ажурирање није успело: сервер је одговорио кодом $code$.", "message": "Ажурирање није успело: сервер је одговорио кодом $code$.",
"description": "Text that displays when an update check failed because the response code indicates an error",
"placeholders": { "placeholders": {
"code": { "code": {
"content": "$1" "content": "$1"
} }
} }
}, },
"appliesSpecify": { "updateCheckFailServerUnreachable": {
"message": "Детаљније", "message": "Ажурирање није успело: сервер није доступан."
"description": "Label for the button to make a style apply only to specific sites"
}, },
"installUpdate": { "updateCheckSucceededNoUpdate": {
"message": "Инсталирај ажурирање", "message": "Стил је ажуриран."
"description": "Label for the button to install an update for a single style"
},
"styleMozillaFormatHeading": {
"message": "Mozilla формат",
"description": "Heading for the section with buttons to import/export Mozilla format of the style"
},
"sectionRemove": {
"message": "Уклони одељак",
"description": "Label for the button to remove a section"
},
"disableAllStyles": {
"message": "Искључи све стилове",
"description": "Label for the checkbox that turns all enabled styles off."
},
"undoGlobal": {
"message": "Опозови (свеобухватно)",
"description": "CSS-beautify global Undo button label"
}, },
"updateCompleted": { "updateCompleted": {
"message": "Ажурирање је комплетирано.", "message": "Ажурирање је комплетирано."
"description": "Text that displays when an update completed"
}, },
"checkingForUpdate": { "writeStyleFor": {
"message": "Проверавање...", "message": "Упиши стил за:"
"description": "Text to display when checking a style for an update"
}, },
"sectionCode": { "writeStyleForURL": {
"message": "Код", "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"
},
"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"
}, },
"manageOnlyUsercss": { "addStyleTitle": {
"message": "Endast Usercss stilar", "message": "Lägg till stil"
"description": "Checkbox to show only Usercss styles"
}, },
"exportLabel": { "alphaChannel": {
"message": "Exportera", "message": "Opacitet"
"description": "Label for the button to export a style ('edit' page) or all styles ('manage' page)"
},
"cm_tabSize": {
"message": "Tabbstorlek",
"description": "Label for the text box controlling tab size option for the style editor."
},
"enableStyleLabel": {
"message": "Aktivera",
"description": "Label for the button to enable a style"
},
"styleMissingName": {
"message": "Ange ett namn",
"description": "Error displayed when user saves without providing a name"
},
"genericHistoryLabel": {
"message": "Historik",
"description": "Used in various places to show a history log of something"
},
"appliesDomainOption": {
"message": "URL:er på domänen",
"description": "Option to make the style apply to the entered string as a domain"
},
"checkForUpdate": {
"message": "Leta efter uppdatering",
"description": "Label for the button to check a single style for an update"
},
"styleFromMozillaFormatError": {
"message": "Import av Mozilla-format misslyckades",
"description": "Label for the import error"
},
"importAppendLabel": {
"message": "Lägg till i stil",
"description": "Label for the button to import a style and append to the existing sections"
},
"updateAllCheckSucceededNoUpdate": {
"message": "Alla stilar är fullt uppdaterade.",
"description": "Text that displays when an update all check completed and no updates are available"
},
"styleFromMozillaFormatPrompt": {
"message": "Klistra in koden i Mozilla-formatet",
"description": "Prompt in the dialog displayed after clicking 'Import from Mozilla format' button"
},
"helpAlt": {
"message": "Hjälp",
"description": "Alternate text for help buttons"
},
"search": {
"message": "Sök",
"description": "Label before the search input field in the editor shown on Ctrl-F"
},
"genericEnabledLabel": {
"message": "Aktiverad",
"description": "Used in various lists/options to indicate that something is enabled"
},
"confirmYes": {
"message": "Ja",
"description": "'Yes' button in a confirm dialog"
},
"findStylesForSite": {
"message": "Hitta fler stilar för denna sida",
"description": "Text for a link that gets a list of styles for the current site"
},
"manageHeading": {
"message": "Installerade Stilar",
"description": "Heading for the manage page"
},
"styleEnabledLabel": {
"message": "Aktiverad",
"description": "Label for the enabled state of styles"
},
"styleToMozillaFormatHelp": {
"message": "Mozilla-formatet av koden fungerar i Stylish till Firefox samt vid uppladdandet till userstyles.org.",
"description": "Help info for the Mozilla format header section that converts the code to/from Mozilla format"
},
"sectionAdd": {
"message": "Lägg till ytterligare en sektion",
"description": "Label for the button to add a section"
},
"styleSaveLabel": {
"message": "Spara",
"description": "Label for save button for style editing"
},
"writeStyleForURL": {
"message": "denna URL",
"description": "Text for link in toolbar pop-up to write a new style for the current URL"
}, },
"appliesAdd": { "appliesAdd": {
"message": "Lägg till", "message": "Lägg till"
"description": "Label for the button to add an 'applies' entry"
},
"appliesRegexpOption": {
"message": "URL:er som matchar regexp:en",
"description": "Option to make the style apply to the entered string as a regular expression"
},
"styleInstall": {
"message": "Installera '$stylename$' in i Stylus?",
"description": "Confirmation when installing a style",
"placeholders": {
"stylename": {
"content": "$1"
}
}
},
"manageOnlyLocal": {
"message": "Endast lokalt skapade stilar",
"description": "Checkbox to show only locally created styles i.e. non-updatable"
},
"searchStyles": {
"message": "Sök innehåll",
"description": "Label for the search filter textbox on the Manage styles page"
},
"manageOnlyNonUsercss": {
"message": "Endast icke-Usercss stilar",
"description": "Checkbox to show only non-Usercss (standard) styles"
},
"linkGetStyles": {
"message": "Skaffa stilar",
"description": "Help link text on the manage page e.g. https://userstyles.org"
},
"disableStyleLabel": {
"message": "Inaktivera",
"description": "Label for the button to disable a style"
},
"prefShowBadge": {
"message": "Visa antalet aktiva stilar för den nuvarande sidan på verktygsfältsikonen",
"description": "Label for the checkbox controlling toolbar badge text."
},
"menuShowBadge": {
"message": "Visa antalet aktiva stilar",
"description": "Label (must be very short) for the checkbox in the toolbar button context menu controlling toolbar badge text."
},
"cm_lineWrapping": {
"message": "Radbrytning",
"description": "Label for the checkbox controlling word wrap option for the style editor."
},
"styleCancelEditLabel": {
"message": "Återgå till hantera",
"description": "Label for cancel button for style editing"
},
"styleChangesNotSaved": {
"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"
},
"importLabel": {
"message": "Importera",
"description": "Label for the button to import a style ('edit' page) or all styles ('manage' page)"
},
"updateCheckFailServerUnreachable": {
"message": "Uppdateringen misslyckades: server onåbar.",
"description": "Text that displays when an update check failed because the update server is unreachable"
},
"manageFilters": {
"message": "Filter",
"description": "Label for filters container"
},
"applyAllUpdates": {
"message": "Verkställ alla uppdateringar",
"description": "Label for the button to apply all detected updates"
},
"deleteStyleConfirm": {
"message": "Är du säker på att du vill ta bort denna stil?",
"description": "Confirmation before deleting a style"
},
"styleBadRegexp": {
"message": "Regexp:en är ogiltig",
"description": "Validation message for a bad regexp in a style"
},
"optionsHeading": {
"message": "Alternativ",
"description": "Heading for options section on manage page."
}, },
"appliesDisplay": { "appliesDisplay": {
"message": "Gäller för: $applies$", "message": "Gäller för: $applies$",
"description": "Text on the manage screen to describe what the style applies to",
"placeholders": { "placeholders": {
"applies": { "applies": {
"content": "$1" "content": "$1"
} }
} }
}, },
"styleUpdate": { "appliesDisplayTruncatedSuffix": {
"message": "Är du säker på att du vill uppdatera '$stylename$'?", "message": "och mer"
"description": "Confirmation when updating a style",
"placeholders": {
"stylename": {
"content": "$1"
}
}
}, },
"styleSectionsTitle": { "appliesDomainOption": {
"message": "Sektioner", "message": "URL:er på domänen"
"description": "Title for the style sections section"
}, },
"optionsAdvancedNewStyleAsUsercss": { "appliesHelp": {
"message": "Skriv ny stil som Usercss", "message": "Använd 'Gäller för' alternativet för att begränsa vilka URL:er koden i denna sektion gäller för."
"description": "" },
"appliesLabel": {
"message": "Gäller för"
},
"appliesRegexpOption": {
"message": "URL:er som matchar regexp:en"
},
"appliesRemove": {
"message": "Ta bort"
},
"appliesSpecify": {
"message": "Specificera"
},
"appliesToEverything": {
"message": "Allt"
},
"appliesUrlPrefixOption": {
"message": "URL:er som börjar på"
},
"applyAllUpdates": {
"message": "Verkställ alla uppdateringar"
},
"bckpInstStyles": {
"message": "Exportera stilar"
},
"checkAllUpdates": {
"message": "Sök efter uppdateringar"
},
"checkForUpdate": {
"message": "Leta efter uppdatering"
},
"checkingForUpdate": {
"message": "Letar..."
},
"cm_indentWithTabs": {
"message": "Använd tabbar med smart indrag"
},
"cm_keyMap": {
"message": "Nyckelkarta"
},
"cm_lineWrapping": {
"message": "Radbrytning"
},
"cm_smartIndent": {
"message": "Använd smart indrag"
},
"cm_tabSize": {
"message": "Tabbstorlek"
},
"cm_theme": {
"message": "Tema"
},
"confirmClose": {
"message": "Stäng"
},
"confirmNo": {
"message": "Nej"
},
"confirmSave": {
"message": "Spara"
},
"confirmYes": {
"message": "Ja"
},
"dbError": {
"message": "Ett fel inträffades vid hanteringen av Stylus-databasen. Skulle du vilja besöka en sida med eventuella lösningar?"
},
"deleteStyleConfirm": {
"message": "Är du säker på att du vill ta bort denna stil?"
},
"deleteStyleLabel": {
"message": "Ta bort"
},
"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."
},
"disableAllStyles": {
"message": "Stäng av alla stilar"
},
"disableStyleLabel": {
"message": "Inaktivera"
},
"editGotoLine": {
"message": "Gå till rad (eller rad:kol)"
},
"editStyleHeading": {
"message": "Ändra i Stil"
},
"editStyleLabel": {
"message": "Ändra"
}, },
"editStyleTitle": { "editStyleTitle": {
"message": "Ändra i Stil $stylename$", "message": "Ändra i Stil $stylename$",
"description": "Title of the page for editing styles",
"placeholders": { "placeholders": {
"stylename": { "stylename": {
"content": "$1" "content": "$1"
} }
} }
}, },
"updateCheckSucceededNoUpdate": { "enableStyleLabel": {
"message": "Stilen är fullt uppdaterad.", "message": "Aktivera"
"description": "Text that displays when an update check completed and no update is available"
}, },
"appliesUrlPrefixOption": { "exportLabel": {
"message": "URL:er som börjar på", "message": "Exportera"
"description": "Option to make the style apply to the entered string as a URL prefix"
}, },
"searchRegexp": { "externalUsercssDocument": {
"message": "Använd /re/ för regexp-sökning", "message": "Dokumentation för Usercss"
"description": "Label after the search input field in the editor shown on Ctrl-F"
}, },
"popupStylesFirst": { "findStyles": {
"message": "Lista stilar före kommandon i verktygsfältets knappmeny", "message": "Hitta stilar"
"description": "Label for the checkbox controlling section order in the popup."
}, },
"sectionHelp": { "findStylesForSite": {
"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": "Hitta fler stilar för denna sida"
"description": "Help text for sections" },
"genericAdd": {
"message": "Lägg till"
},
"genericDisabledLabel": {
"message": "Inaktiverad"
},
"genericEnabledLabel": {
"message": "Aktiverad"
},
"genericHistoryLabel": {
"message": "Historik"
},
"genericNext": {
"message": "Nästa"
},
"genericPrevious": {
"message": "Föregående"
},
"genericResetLabel": {
"message": "Återställ"
},
"helpAlt": {
"message": "Hjälp"
},
"importAppendLabel": {
"message": "Lägg till i stil"
},
"importAppendTooltip": {
"message": "Lägg till den importerad stilen i aktuell stil"
},
"importLabel": {
"message": "Importera"
},
"importReplaceLabel": {
"message": "Ersätt stil"
},
"installUpdate": {
"message": "Installera uppdatering"
},
"linkGetHelp": {
"message": "Hjälp"
},
"linkGetStyles": {
"message": "Skaffa stilar"
},
"linkTranslate": {
"message": "Översätt"
},
"manageFavicons": {
"message": "Ikoner i 'Gäller för' kolumnen"
},
"manageFaviconsGray": {
"message": "Nedtonade"
},
"manageFaviconsHelp": {
"message": "Stylus använder en extern tjänst https://www.google.com/s2/favicons"
},
"manageFilters": {
"message": "Filter"
},
"manageHeading": {
"message": "Installerade Stilar"
},
"manageNewStyleAsUsercss": {
"message": "som Usercss"
},
"manageOnlyDisabled": {
"message": "Endast inaktiverade stilar"
},
"manageOnlyEnabled": {
"message": "Endast aktiverade stilar"
},
"manageOnlyExternal": {
"message": "Endast externa stilar"
},
"manageOnlyLocal": {
"message": "Endast lokalt skapade stilar"
},
"manageOnlyNonUsercss": {
"message": "Endast icke-Usercss stilar"
},
"manageOnlyUsercss": {
"message": "Endast Usercss stilar"
},
"menuShowBadge": {
"message": "Visa antalet aktiva stilar"
}, },
"noStylesForSite": { "noStylesForSite": {
"message": "Inga stilar installerade för denna sida.", "message": "Inga stilar installerade för denna sida."
"description": "Text displayed when no styles are installed for the current site"
},
"appliesDisplayTruncatedSuffix": {
"message": "och mer",
"description": "Text added to appliesDisplay when there are more sites for the style than are displayed"
},
"appliesRemove": {
"message": "Ta bort",
"description": "Label for the button to remove an 'applies' entry"
},
"styleToMozillaFormatTitle": {
"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"
},
"writeStyleFor": {
"message": "Skriv stil för:",
"description": "Label for toolbar pop-up that precedes the links to write a new style"
},
"replace": {
"message": "Ersätt",
"description": "Label before the replace input field in the editor shown on Ctrl-H"
},
"appliesLabel": {
"message": "Gäller för",
"description": "Label for 'applies to' fields on the edit/add screen"
}, },
"openManage": { "openManage": {
"message": "Hantera installerade stilar", "message": "Hantera installerade stilar"
"description": "Link to open the manage page." },
"openOptionsPopup": {
"message": "Alternativ"
},
"openStylesManager": {
"message": "Öppna stilhanteraren"
},
"optionsAdvancedNewStyleAsUsercss": {
"message": "Skriv ny stil som Usercss"
},
"optionsCheck": {
"message": "Uppdatera stilar"
},
"optionsCheckUpdate": {
"message": "Leta efter och installera alla tillgängliga uppdateringar"
},
"optionsHeading": {
"message": "Alternativ"
},
"optionsOpenManager": {
"message": "Hantera stilar"
},
"optionsReset": {
"message": "Återställ alternativen till standard"
},
"optionsResetButton": {
"message": "Återställ alternativ"
},
"paginationNext": {
"message": "Nästa sida"
},
"paginationPrevious": {
"message": "Föregående sida"
},
"popupStylesFirst": {
"message": "Lista stilar före kommandon i verktygsfältets knappmeny"
},
"prefShowBadge": {
"message": "Visa antalet aktiva stilar för den nuvarande sidan på verktygsfältsikonen"
},
"replace": {
"message": "Ersätt"
},
"replaceAll": {
"message": "Ersätt alla"
},
"replaceWith": {
"message": "Ersätt med"
},
"retrieveBckp": {
"message": "Importera stilar"
},
"search": {
"message": "Sök"
},
"searchRegexp": {
"message": "Använd /re/ för regexp-sökning"
},
"searchStyles": {
"message": "Sök innehåll"
},
"sectionAdd": {
"message": "Lägg till ytterligare en sektion"
},
"sectionCode": {
"message": "Kod"
},
"sectionHelp": {
"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."
},
"sectionRemove": {
"message": "Ta bort sektion"
},
"sectionRestore": {
"message": "Återställ borttagen sektion"
},
"shortcuts": {
"message": "Genvägar"
},
"shortcutsNote": {
"message": "Ställ in tangentbordsgenvägar"
},
"styleBadRegexp": {
"message": "Regexp:en är ogiltig"
},
"styleCancelEditLabel": {
"message": "Återgå till hantera"
},
"styleChangesNotSaved": {
"message": "Du har gjort ändringar i denna stil utan att spara."
},
"styleEnabledLabel": {
"message": "Aktiverad"
},
"styleFromMozillaFormatError": {
"message": "Import av Mozilla-format misslyckades"
},
"styleFromMozillaFormatPrompt": {
"message": "Klistra in koden i Mozilla-formatet"
},
"styleInstall": {
"message": "Installera '$stylename$' in i Stylus?",
"placeholders": {
"stylename": {
"content": "$1"
}
}
},
"styleMissingName": {
"message": "Ange ett namn"
},
"styleMozillaFormatHeading": {
"message": "Mozilla-format"
},
"styleSaveLabel": {
"message": "Spara"
},
"styleSectionsTitle": {
"message": "Sektioner"
},
"styleToMozillaFormatHelp": {
"message": "Mozilla-formatet av koden fungerar i Stylish till Firefox samt vid uppladdandet till userstyles.org."
},
"styleToMozillaFormatTitle": {
"message": "Stil i Mozilla-format"
},
"styleUpdate": {
"message": "Är du säker på att du vill uppdatera '$stylename$'?",
"placeholders": {
"stylename": {
"content": "$1"
}
}
},
"stylusUnavailableForURL": {
"message": "Stylus fungerar inte på sidor som denna."
},
"undo": {
"message": "Ångra"
},
"undoGlobal": {
"message": "Ångra i alla sektioner"
},
"updateAllCheckSucceededNoUpdate": {
"message": "Alla stilar är fullt uppdaterade."
}, },
"updateCheckFailBadResponseCode": { "updateCheckFailBadResponseCode": {
"message": "Uppdateringen misslyckades: servern svarade med kod $code$.", "message": "Uppdateringen misslyckades: servern svarade med kod $code$.",
"description": "Text that displays when an update check failed because the response code indicates an error",
"placeholders": { "placeholders": {
"code": { "code": {
"content": "$1" "content": "$1"
} }
} }
}, },
"appliesSpecify": { "updateCheckFailServerUnreachable": {
"message": "Specificera", "message": "Uppdateringen misslyckades: server onåbar."
"description": "Label for the button to make a style apply only to specific sites"
}, },
"installUpdate": { "updateCheckSucceededNoUpdate": {
"message": "Installera uppdatering", "message": "Stilen är fullt uppdaterad."
"description": "Label for the button to install an update for a single style"
},
"styleMozillaFormatHeading": {
"message": "Mozilla-format",
"description": "Heading for the section with buttons to import/export Mozilla format of the style"
},
"sectionRemove": {
"message": "Ta bort sektion",
"description": "Label for the button to remove a section"
},
"disableAllStyles": {
"message": "Stäng av alla stilar",
"description": "Label for the checkbox that turns all enabled styles off."
},
"undoGlobal": {
"message": "Ångra i alla sektioner",
"description": "CSS-beautify global Undo button label"
}, },
"updateCompleted": { "updateCompleted": {
"message": "Uppdatering slutförd.", "message": "Uppdatering slutförd."
"description": "Text that displays when an update completed"
}, },
"checkingForUpdate": { "writeStyleFor": {
"message": "Letar...", "message": "Skriv stil för:"
"description": "Text to display when checking a style for an update"
}, },
"sectionCode": { "writeStyleForURL": {
"message": "Kod", "message": "denna URL"
"description": "Label for the code for a section"
},
"externalUsercssDocument": {
"message": "Dokumentation för Usercss",
"description": "Label for the external link to usercss documentation"
},
"cm_smartIndent": {
"message": "Använd smart indrag",
"description": "Label for the checkbox controlling smart indentation option for the style editor."
},
"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.",
"description": "Help text for 'applies to' section"
},
"linkGetHelp": {
"message": "Hjälp",
"description": "Homepage link text on the manage page e.g. https://add0n.com/stylus.html#features with chat/FAQ/intro/info"
},
"editStyleHeading": {
"message": "Ändra i Stil",
"description": "Title of the page for editing styles"
},
"manageOnlyDisabled": {
"message": "Endast inaktiverade stilar",
"description": "Checkbox to show only disabled styles"
},
"stylusUnavailableForURL": {
"message": "Stylus fungerar inte på sidor som denna.",
"description": "Note in the toolbar pop-up when on a URL Stylus can't affect"
},
"addStyleTitle": {
"message": "Lägg till stil",
"description": "Title of the page for adding styles"
},
"importReplaceLabel": {
"message": "Ersätt stil",
"description": "Label for the button to import and overwrite current style"
},
"dbError": {
"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"
},
"importAppendTooltip": {
"message": "Lägg till den importerad stilen i aktuell stil",
"description": "Tooltip for the button to import a style and append to the existing sections"
},
"manageOnlyExternal": {
"message": "Endast externa stilar",
"description": "Checkbox to show only externally installed styles i.e. updatable"
},
"replaceAll": {
"message": "Ersätt alla",
"description": "Label before the replace input field in the editor shown on 'replaceAll' hotkey"
},
"editGotoLine": {
"message": "Gå till rad (eller rad:kol)",
"description": "Go to line or line:column on Ctrl-G in style code editor"
},
"checkAllUpdates": {
"message": "Sök efter uppdateringar",
"description": "Label for the button to check all styles for updates"
},
"manageNewStyleAsUsercss": {
"message": "som Usercss",
"description": "VERY SHORT label for the checkbox next to the 'Write new style' button in the style manager"
},
"confirmNo": {
"message": "Nej",
"description": "'No' button in a confirm dialog"
},
"undo": {
"message": "Ångra",
"description": "Button label"
},
"cm_keyMap": {
"message": "Nyckelkarta",
"description": "Label for the drop-down list controlling the keymap for the style editor."
},
"confirmSave": {
"message": "Spara",
"description": "'Save' button in a confirm dialog"
},
"genericDisabledLabel": {
"message": "Inaktiverad",
"description": "Used in various lists/options to indicate that something is disabled"
},
"cm_indentWithTabs": {
"message": "Använd tabbar med smart indrag",
"description": "Label for the checkbox controlling tabs with smart indentation option for the style editor."
},
"replaceWith": {
"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"
},
"enableStyleLabel": {
"message": "చేతనించు",
"description": "Label for the button to enable a style"
},
"helpAlt": {
"message": "సహాయం",
"description": "Alternate text for help buttons"
},
"manageHeading": {
"message": "స్థాపిత శైలులు",
"description": "Heading for the manage page"
},
"styleSaveLabel": {
"message": "భద్రపరచు",
"description": "Label for save button for style editing"
}, },
"appliesAdd": { "appliesAdd": {
"message": "చేర్చు", "message": "చేర్చు"
"description": "Label for the button to add an 'applies' entry"
},
"disableStyleLabel": {
"message": "అచేతనించు",
"description": "Label for the button to disable a style"
},
"deleteStyleConfirm": {
"message": "మీరు నజంగానే ఈ శైలిని తొలగించాలనుకుంటున్నారా?",
"description": "Confirmation before deleting a style"
}, },
"appliesDisplay": { "appliesDisplay": {
"message": "వేటికి వర్తిస్తుంది; $applies$", "message": "వేటికి వర్తిస్తుంది; $applies$",
"description": "Text on the manage screen to describe what the style applies to",
"placeholders": { "placeholders": {
"applies": { "applies": {
"content": "$1" "content": "$1"
} }
} }
}, },
"styleSectionsTitle": {
"message": "విభాగాలు",
"description": "Title for the style sections section"
},
"appliesDisplayTruncatedSuffix": { "appliesDisplayTruncatedSuffix": {
"message": "ఇంకా మరిన్ని", "message": "ఇంకా మరిన్ని"
"description": "Text added to appliesDisplay when there are more sites for the style than are displayed"
}, },
"appliesRemove": { "appliesRemove": {
"message": "తొలగించు", "message": "తొలగించు"
"description": "Label for the button to remove an 'applies' entry"
}, },
"manageTitle": { "appliesToEverything": {
"message": "స్టైలిష్", "message": "అన్నిటికీ"
"description": "Title for the manage page" },
"deleteStyleConfirm": {
"message": "మీరు నజంగానే ఈ శైలిని తొలగించాలనుకుంటున్నారా?"
}, },
"deleteStyleLabel": { "deleteStyleLabel": {
"message": "తొలగించు", "message": "తొలగించు"
"description": "Label for the button to delete a style"
}, },
"addStyleLabel": { "disableStyleLabel": {
"message": "క్రొత్త స్టైల్ వ్రాయండి", "message": "అచేతనించు"
"description": "Label for the button to go to the add style page"
}, },
"editStyleLabel": { "editStyleLabel": {
"message": "మార్చు", "message": "మార్చు"
"description": "Label for the button to go to the edit style page" },
"enableStyleLabel": {
"message": "చేతనించు"
},
"helpAlt": {
"message": "సహాయం"
},
"manageHeading": {
"message": "స్థాపిత శైలులు"
},
"manageTitle": {
"message": "స్టైలిష్"
},
"styleSaveLabel": {
"message": "భద్రపరచు"
},
"styleSectionsTitle": {
"message": "విభాగాలు"
} }
} }

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"
}, },
"enableStyleLabel": { "addStyleTitle": {
"message": "Etkinleştir", "message": "Stil Ekleyin"
"description": "Label for the button to enable a style"
},
"styleMissingName": {
"message": "Bir ad girin",
"description": "Error displayed when user saves without providing a name"
},
"appliesDomainOption": {
"message": "Alan adındaki URLler",
"description": "Option to make the style apply to the entered string as a domain"
},
"checkForUpdate": {
"message": "Güncellemeleri denetle",
"description": "Label for the button to check a single style for an update"
},
"helpAlt": {
"message": "Yardım",
"description": "Alternate text for help buttons"
},
"findStylesForSite": {
"message": "Bu site için başka stiller bul",
"description": "Text for a link that gets a list of styles for the current site"
},
"manageHeading": {
"message": "Yüklü Stiller",
"description": "Heading for the manage page"
},
"styleEnabledLabel": {
"message": "Etkin",
"description": "Label for the enabled state of styles"
},
"styleToMozillaFormatHelp": {
"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"
},
"sectionAdd": {
"message": "Başka bölüm ekle",
"description": "Label for the button to add a section"
},
"styleSaveLabel": {
"message": "Kaydet",
"description": "Label for save button for style editing"
}, },
"appliesAdd": { "appliesAdd": {
"message": "Ekleyin", "message": "Ekleyin"
"description": "Label for the button to add an 'applies' entry"
},
"appliesRegexpOption": {
"message": "regexp ile eşleşen URL'ler",
"description": "Option to make the style apply to the entered string as a regular expression"
},
"styleInstall": {
"message": "'$stylename$' Stylus'e yüklensin mi?",
"description": "Confirmation when installing a style",
"placeholders": {
"stylename": {
"content": "$1"
}
}
},
"disableStyleLabel": {
"message": "Devre dışı bırak",
"description": "Label for the button to disable a style"
},
"styleCancelEditLabel": {
"message": "Yönetim sayfasına dön",
"description": "Label for cancel button for style editing"
},
"styleChangesNotSaved": {
"message": "Bu stilde yaptığınız, kaydedilmemiş değişiklikler var.",
"description": "Text for the prompt when changes are made to a style and the user tries to leave without saving"
},
"updateCheckFailServerUnreachable": {
"message": "Güncellenemedi: sunucuya erişilemiyor.",
"description": "Text that displays when an update check failed because the update server is unreachable"
},
"deleteStyleConfirm": {
"message": "Bu stili silmek istediğinizden emin misiniz?",
"description": "Confirmation before deleting a style"
}, },
"appliesDisplay": { "appliesDisplay": {
"message": "Şuraya uygulanır: $applies$", "message": "Şuraya uygulanır: $applies$",
"description": "Text on the manage screen to describe what the style applies to",
"placeholders": { "placeholders": {
"applies": { "applies": {
"content": "$1" "content": "$1"
} }
} }
}, },
"styleSectionsTitle": { "appliesDisplayTruncatedSuffix": {
"message": "Bölümler", "message": "ve diğerleri"
"description": "Title for the style sections section" },
"appliesDomainOption": {
"message": "Alan adındaki URLler"
},
"appliesHelp": {
"message": "Bu bölümdeki kodun hangi URLlere uygulanacağını sınırlamak için 'Şuraya uygulanır' denetimlerini kullanın."
},
"appliesLabel": {
"message": "Şuraya uygulanır"
},
"appliesRegexpOption": {
"message": "regexp ile eşleşen URL'ler"
},
"appliesRemove": {
"message": "Kaldır"
},
"appliesSpecify": {
"message": "Belirt"
},
"appliesToEverything": {
"message": "Her şey"
},
"appliesUrlPrefixOption": {
"message": "Şununla başlayan URL'ler:"
},
"checkAllUpdates": {
"message": "Tüm stiller için güncellemeleri denetle"
},
"checkForUpdate": {
"message": "Güncellemeleri denetle"
},
"checkingForUpdate": {
"message": "Kontrol ediliyor..."
},
"deleteStyleConfirm": {
"message": "Bu stili silmek istediğinizden emin misiniz?"
},
"deleteStyleLabel": {
"message": "Sil"
},
"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."
},
"disableStyleLabel": {
"message": "Devre dışı bırak"
},
"editStyleHeading": {
"message": "Stili Düzenle"
},
"editStyleLabel": {
"message": "Düzenle"
}, },
"editStyleTitle": { "editStyleTitle": {
"message": "$stylename$ Stilini Düzenleyin", "message": "$stylename$ Stilini Düzenleyin",
"description": "Title of the page for editing styles",
"placeholders": { "placeholders": {
"stylename": { "stylename": {
"content": "$1" "content": "$1"
} }
} }
}, },
"updateCheckSucceededNoUpdate": { "enableStyleLabel": {
"message": "Stil güncel.", "message": "Etkinleştir"
"description": "Text that displays when an update check completed and no update is available"
}, },
"appliesUrlPrefixOption": { "findStylesForSite": {
"message": "Şununla başlayan URL'ler:", "message": "Bu site için başka stiller bul"
"description": "Option to make the style apply to the entered string as a URL prefix"
}, },
"sectionHelp": { "helpAlt": {
"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": "Yardım"
"description": "Help text for sections" },
"installUpdate": {
"message": "Güncellemeyi yükle"
},
"manageHeading": {
"message": "Yüklü Stiller"
}, },
"noStylesForSite": { "noStylesForSite": {
"message": "Bu site için hiçbir stil yüklenmedi.", "message": "Bu site için hiçbir stil yüklenmedi."
"description": "Text displayed when no styles are installed for the current site"
},
"appliesDisplayTruncatedSuffix": {
"message": "ve diğerleri",
"description": "Text added to appliesDisplay when there are more sites for the style than are displayed"
},
"appliesRemove": {
"message": "Kaldır",
"description": "Label for the button to remove an 'applies' entry"
},
"appliesLabel": {
"message": "Şuraya uygulanır",
"description": "Label for 'applies to' fields on the edit/add screen"
}, },
"openManage": { "openManage": {
"message": "Yüklü stilleri yönet", "message": "Yüklü stilleri yönet"
"description": "Link to open the manage page." },
"sectionAdd": {
"message": "Başka bölüm ekle"
},
"sectionCode": {
"message": "Kod"
},
"sectionHelp": {
"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."
},
"sectionRemove": {
"message": "Bölümü kaldır"
},
"styleCancelEditLabel": {
"message": "Yönetim sayfasına dön"
},
"styleChangesNotSaved": {
"message": "Bu stilde yaptığınız, kaydedilmemiş değişiklikler var."
},
"styleEnabledLabel": {
"message": "Etkin"
},
"styleInstall": {
"message": "'$stylename$' Stylus'e yüklensin mi?",
"placeholders": {
"stylename": {
"content": "$1"
}
}
},
"styleMissingName": {
"message": "Bir ad girin"
},
"styleSaveLabel": {
"message": "Kaydet"
},
"styleSectionsTitle": {
"message": "Bölümler"
},
"styleToMozillaFormatHelp": {
"message": "Kodun Mozilla biçimi, Firefox için Stylish ile kullanılabilir ve userstyles.org sitesine gönderilebilir."
}, },
"updateCheckFailBadResponseCode": { "updateCheckFailBadResponseCode": {
"message": "Güncellenemedi: sunucu yanıt olarak $code$ kodunu gönderdi.", "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",
"placeholders": { "placeholders": {
"code": { "code": {
"content": "$1" "content": "$1"
} }
} }
}, },
"appliesSpecify": { "updateCheckFailServerUnreachable": {
"message": "Belirt", "message": "Güncellenemedi: sunucuya erişilemiyor."
"description": "Label for the button to make a style apply only to specific sites"
}, },
"installUpdate": { "updateCheckSucceededNoUpdate": {
"message": "Güncellemeyi yükle", "message": "Stil güncel."
"description": "Label for the button to install an update for a single style"
},
"sectionRemove": {
"message": "Bölümü kaldır",
"description": "Label for the button to remove a section"
}, },
"updateCompleted": { "updateCompleted": {
"message": "Güncelleme tamamlandı.", "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"
}, },
"enableStyleLabel": { "addStyleTitle": {
"message": "启用", "message": "添加样式"
"description": "Label for the button to enable a style"
},
"styleMissingName": {
"message": "请输入名称",
"description": "Error displayed when user saves without providing a name"
},
"appliesDomainOption": {
"message": "指定域上的URL",
"description": "Option to make the style apply to the entered string as a domain"
},
"checkForUpdate": {
"message": "检查更新",
"description": "Label for the button to check a single style for an update"
},
"helpAlt": {
"message": "帮助",
"description": "Alternate text for help buttons"
},
"findStylesForSite": {
"message": "查找更多适合此站点的样式",
"description": "Text for a link that gets a list of styles for the current site"
},
"manageHeading": {
"message": "已安装的样式",
"description": "Heading for the manage page"
},
"styleEnabledLabel": {
"message": "已启用",
"description": "Label for the enabled state of styles"
},
"styleToMozillaFormatHelp": {
"message": "用于Firefox上的Stylish的Mozilla格式样式代码可以在 userstyles.org 上提交",
"description": "Help info for the Mozilla format header section that converts the code to/from Mozilla format"
},
"sectionAdd": {
"message": "添加新节",
"description": "Label for the button to add a section"
},
"styleSaveLabel": {
"message": "保存",
"description": "Label for save button for style editing"
}, },
"appliesAdd": { "appliesAdd": {
"message": "添加", "message": "添加"
"description": "Label for the button to add an 'applies' entry"
},
"appliesRegexpOption": {
"message": "匹配正则的URL",
"description": "Option to make the style apply to the entered string as a regular expression"
},
"styleInstall": {
"message": "安装 '$stylename$' 到 Stylus?",
"description": "Confirmation when installing a style",
"placeholders": {
"stylename": {
"content": "$1"
}
}
},
"disableStyleLabel": {
"message": "禁用",
"description": "Label for the button to disable a style"
},
"styleCancelEditLabel": {
"message": "返回到管理",
"description": "Label for cancel button for style editing"
},
"styleChangesNotSaved": {
"message": "您已经修改了此样式,但尚未保存",
"description": "Text for the prompt when changes are made to a style and the user tries to leave without saving"
},
"updateCheckFailServerUnreachable": {
"message": "更新失败: 服务器无法访问.",
"description": "Text that displays when an update check failed because the update server is unreachable"
},
"deleteStyleConfirm": {
"message": "确定要删除这个样式吗?",
"description": "Confirmation before deleting a style"
}, },
"appliesDisplay": { "appliesDisplay": {
"message": "应用到: $applies$", "message": "应用到: $applies$",
"description": "Text on the manage screen to describe what the style applies to",
"placeholders": { "placeholders": {
"applies": { "applies": {
"content": "$1" "content": "$1"
} }
} }
}, },
"styleUpdate": { "appliesDisplayTruncatedSuffix": {
"message": "你確定要更新 '$stylename$' 嗎?", "message": "以及更多"
"description": "Confirmation when updating a style",
"placeholders": {
"stylename": {
"content": "$1"
}
}
}, },
"styleSectionsTitle": { "appliesDomainOption": {
"message": "样式节", "message": "指定域上的URL"
"description": "Title for the style sections section" },
"appliesHelp": {
"message": "使用“应用到”来控制这个样式应用到哪些地址上。"
},
"appliesLabel": {
"message": "应用到"
},
"appliesRegexpOption": {
"message": "匹配正则的URL"
},
"appliesRemove": {
"message": "移除"
},
"appliesSpecify": {
"message": "指定站点"
},
"appliesToEverything": {
"message": "所有站点"
},
"appliesUrlOption": {
"message": "地址"
},
"appliesUrlPrefixOption": {
"message": "以指定地址开始"
},
"checkAllUpdates": {
"message": "检查所有样式的更新"
},
"checkForUpdate": {
"message": "检查更新"
},
"checkingForUpdate": {
"message": "检查中..."
},
"deleteStyleConfirm": {
"message": "确定要删除这个样式吗?"
},
"deleteStyleLabel": {
"message": "删除"
},
"description": {
"message": "Stylus一个用户样式管理器帮助您重新定义网页样式。Stylus易于为GoogleFacebookYoutubeOrkut以及其它各类型网站安装样式和主题。"
},
"disableStyleLabel": {
"message": "禁用"
},
"editStyleHeading": {
"message": "编辑样式"
},
"editStyleLabel": {
"message": "编辑"
}, },
"editStyleTitle": { "editStyleTitle": {
"message": "编辑样式 $stylename$", "message": "编辑样式 $stylename$",
"description": "Title of the page for editing styles",
"placeholders": { "placeholders": {
"stylename": { "stylename": {
"content": "$1" "content": "$1"
} }
} }
}, },
"updateCheckSucceededNoUpdate": { "enableStyleLabel": {
"message": "已经是最新的.", "message": "启用"
"description": "Text that displays when an update check completed and no update is available"
}, },
"appliesUrlPrefixOption": { "findStylesForSite": {
"message": "以指定地址开始", "message": "查找更多适合此站点的样式"
"description": "Option to make the style apply to the entered string as a URL prefix"
}, },
"sectionHelp": { "helpAlt": {
"message": "样式节允许你定义多个样式段落并将它们应用于不同的站点匹配规则上。例如,一个样式可以更改使用一个方式来更改主页,而此时其它的样式段可以更改站点的其它部分。", "message": "帮助"
"description": "Help text for sections" },
"installUpdate": {
"message": "安装更新"
},
"manageHeading": {
"message": "已安装的样式"
}, },
"noStylesForSite": { "noStylesForSite": {
"message": "当前站点上没有已安装的样式", "message": "当前站点上没有已安装的样式"
"description": "Text displayed when no styles are installed for the current site"
},
"appliesDisplayTruncatedSuffix": {
"message": "以及更多",
"description": "Text added to appliesDisplay when there are more sites for the style than are displayed"
},
"appliesRemove": {
"message": "移除",
"description": "Label for the button to remove an 'applies' entry"
},
"appliesLabel": {
"message": "应用到",
"description": "Label for 'applies to' fields on the edit/add screen"
}, },
"openManage": { "openManage": {
"message": "管理已安装样式", "message": "管理已安装样式"
"description": "Link to open the manage page." },
"sectionAdd": {
"message": "添加新节"
},
"sectionCode": {
"message": "代码"
},
"sectionHelp": {
"message": "样式节允许你定义多个样式段落并将它们应用于不同的站点匹配规则上。例如,一个样式可以更改使用一个方式来更改主页,而此时其它的样式段可以更改站点的其它部分。"
},
"sectionRemove": {
"message": "移除节"
},
"styleCancelEditLabel": {
"message": "返回到管理"
},
"styleChangesNotSaved": {
"message": "您已经修改了此样式,但尚未保存"
},
"styleEnabledLabel": {
"message": "已启用"
},
"styleInstall": {
"message": "安装 '$stylename$' 到 Stylus?",
"placeholders": {
"stylename": {
"content": "$1"
}
}
},
"styleMissingName": {
"message": "请输入名称"
},
"styleSaveLabel": {
"message": "保存"
},
"styleSectionsTitle": {
"message": "样式节"
},
"styleToMozillaFormatHelp": {
"message": "用于Firefox上的Stylish的Mozilla格式样式代码可以在 userstyles.org 上提交"
},
"styleUpdate": {
"message": "你確定要更新 '$stylename$' 嗎?",
"placeholders": {
"stylename": {
"content": "$1"
}
}
}, },
"updateCheckFailBadResponseCode": { "updateCheckFailBadResponseCode": {
"message": "更新失败: 服务器了返回代码 $code$。", "message": "更新失败: 服务器了返回代码 $code$。",
"description": "Text that displays when an update check failed because the response code indicates an error",
"placeholders": { "placeholders": {
"code": { "code": {
"content": "$1" "content": "$1"
} }
} }
}, },
"appliesSpecify": { "updateCheckFailServerUnreachable": {
"message": "指定站点", "message": "更新失败: 服务器无法访问."
"description": "Label for the button to make a style apply only to specific sites"
}, },
"installUpdate": { "updateCheckSucceededNoUpdate": {
"message": "安装更新", "message": "已经是最新的."
"description": "Label for the button to install an update for a single style"
},
"sectionRemove": {
"message": "移除节",
"description": "Label for the button to remove a section"
}, },
"updateCompleted": { "updateCompleted": {
"message": "更新完成.", "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);
if (reason === 'update') {
// translations may change // translations may change
localStorage.L10N = JSON.stringify({ localStorage.L10N = JSON.stringify({
browserUIlanguage: chrome.i18n.getUILanguage(), browserUIlanguage: chrome.i18n.getUILanguage(),
}); });
// themes may change // themes may change
delete localStorage.codeMirrorThemes; 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 saveStyle(json)
.then(saved => {
style.originalDigest = saved.originalDigest;
return Promise.reject(STATES.SAME_CODE); 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,45 +16,54 @@ 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'},
$('#help-popup').className = 'wide'; // showHelp.close will be defined after showHelp() is invoked
onclick: () => showHelp.close(),
const undoButton = $('#help-popup button[role="undo"]'); }, t('confirmClose')),
undoButton.textContent = t(scope.length === 1 ? 'undo' : 'undoGlobal'); $create('button', {
undoButton.addEventListener('click', () => { attributes: {role: 'undo'},
onclick() {
let undoable = false; let undoable = false;
scope.forEach(cm => { for (const cm of scope) {
if (cm.beautifyChange && cm.beautifyChange[cm.changeGeneration()]) { const data = cm.beautifyChange;
delete cm.beautifyChange[cm.changeGeneration()]; if (!data || !data[cm.changeGeneration()]) continue;
delete data[cm.changeGeneration()];
const {scrollX, scrollY} = window; const {scrollX, scrollY} = window;
cm.undo(); cm.undo();
cm.scrollIntoView(cm.getCursor()); cm.scrollIntoView(cm.getCursor());
window.scrollTo(scrollX, scrollY); window.scrollTo(scrollX, scrollY);
undoable |= cm.beautifyChange[cm.changeGeneration()]; undoable |= data[cm.changeGeneration()];
} }
}); this.disabled = !undoable;
undoButton.disabled = !undoable; },
}); }, t(scope.length === 1 ? 'undo' : 'undoGlobal')),
]),
]));
$('#help-popup').className = 'wide';
scope.forEach(cm => { scope.forEach(cm => {
setTimeout(() => { setTimeout(() => {
@ -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;
}
updateLintReportIfEnabled(null, 0);
// neither confirm() nor custom messages work in modern browsers but just in case // neither confirm() nor custom messages work in modern browsers but just in case
return t('styleChangesNotSaved'); 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 = '';
if (Array.isArray(state.value)) { const extractDefaultOption = (key, value) => {
result.options = state.value.map(text => createOption(text)); if (key.endsWith('*')) {
} else { const option = createOption(key.slice(0, -1), value);
result.options = Object.keys(state.value).map(k => createOption(k, state.value[k])); result.default = option.name;
return option;
} }
return createOption(key, value);
};
if (Array.isArray(state.value)) {
result.options = state.value.map(k => extractDefaultOption(k));
} else {
result.options = Object.keys(state.value).map(k => extractDefaultOption(k, state.value[k]));
}
if (result.default === null) {
result.default = (result.options[0] || {}).name || ''; 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];
}
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,9 +313,29 @@ function configDialog(style) {
} }
} }
function updateVarOnBlur() {
this.value = isDefault(this.va) ? this.va.default : this.va.value;
}
function updateVarOnChange() { function updateVarOnChange() {
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'; 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) {
if (debounced) { if (debounced) {
@ -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);
}) })
.then(() => doTimeout()) ).dispatchEvent(new MouseEvent('click'));
.then(() => link.dispatchEvent(new MouseEvent('click'))) },
.then(() => doTimeout(1000)) // we can't use display:none as some browsers are ignoring such iframes
.then(() => URL.revokeObjectURL(url)) style: `
.then(() => iframe.remove()); all: unset;
width: 0;
height: 0;
position: fixed;
opacity: 0;
border: none;
`.replace(/;/g, '!important;'),
})
);
// we don't remove the iframe or the object URL because the browser may show
// a download dialog and we don't know how long it'll take until the user confirms it
// (some browsers like Vivaldi can't download if we revoke the URL)
}); });
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,15 +259,14 @@ 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(
@ -285,7 +274,6 @@ function createStyleTargetsElement({entry, style, iconsOnly}) {
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() {
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); 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,19 +641,22 @@
//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, '.');
if (isCallable) {
options.callback(lastOutputColor); options.callback(lastOutputColor);
} }
} }
}
function captureMouse({button}, mode) { function captureMouse({button}, mode) {
if (button !== 0) { if (button !== 0) {
@ -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