Compare commits
45 Commits
Author | SHA1 | Date | |
---|---|---|---|
|
fc39f0d5a6 | ||
|
1725c0ecb9 | ||
|
79dff2775b | ||
|
b22bbaaec0 | ||
|
fff59ee12f | ||
|
540e2af62c | ||
|
0128489bbb | ||
|
a5b11ac687 | ||
|
5d3a9dccf3 | ||
|
09691a6362 | ||
|
62987fe5f8 | ||
|
405a93f9e5 | ||
|
da4bdc6821 | ||
|
d1f5468a81 | ||
|
efc6d09d49 | ||
|
0bb0d32c29 | ||
|
f5397b8aec | ||
|
1594b4dcd8 | ||
|
527d7c0fbc | ||
|
cda606e7cc | ||
|
5cb30c8b69 | ||
|
1b914a397e | ||
|
7b64deeb37 | ||
|
9398857d93 | ||
|
b45825c015 | ||
|
3aae3f181a | ||
|
48d90544f6 | ||
|
ef998e423e | ||
|
6979958908 | ||
|
4236eb4e29 | ||
|
c351413c3f | ||
|
d91cf11366 | ||
|
e49644f1c8 | ||
|
15b59ae207 | ||
|
ad43560016 | ||
|
c423025c5d | ||
|
030621462c | ||
|
bf3dd0318d | ||
|
3489b513c9 | ||
|
984fa6e425 | ||
|
6110cbee68 | ||
|
40ec2c000f | ||
|
9022f6b318 | ||
|
c5667b0352 | ||
|
5379f62c90 |
|
@ -346,6 +346,9 @@
|
|||
"genericSavedMessage": {
|
||||
"message": "Gespeichert"
|
||||
},
|
||||
"genericSize": {
|
||||
"message": "Größe"
|
||||
},
|
||||
"genericTitle": {
|
||||
"message": "Name"
|
||||
},
|
||||
|
@ -543,6 +546,9 @@
|
|||
"manageMaxTargets": {
|
||||
"message": "Anzahl der \"Gilt für\" Elemente"
|
||||
},
|
||||
"manageMinColumnWidth": {
|
||||
"message": "Minimale Spaltenbreite (in Pixeln. 9999 deaktiviert den Mehrspalten-Modus)"
|
||||
},
|
||||
"manageNewStyleAsUsercss": {
|
||||
"message": "als UserCSS"
|
||||
},
|
||||
|
@ -1115,7 +1121,7 @@
|
|||
"message": "Wöchentliche Installationen"
|
||||
},
|
||||
"searchStyleQueryHint": {
|
||||
"message": "Stylenamen ohne Beachtung der Groß-/Kleinschreibung suchen:\nMehrere Suchworte - alle Wörter in beliebiger Reihenfolge\n\"Bestimmte Phrase\" - genau diese Phrase ohne Anführungszeichen\n2020 - zeigt auch Styles, die 2020 aktualisiert wurden"
|
||||
"message": "Stylenamen durchsuchen (Groß-Kleinschreibung wird beachtet, sobald ein Großbuchstabe benutzt wird):\nHaus Baum Sonne - sucht nach allen Wörtern in beliebiger Reihenfolge\n\"Baum Haus\" - sucht exakt diesen Ausdruck (ohne Anführungszeichen)\n/foo.*bar/i - Regulärer Ausdruck ohne Leerzeichen (nutze stattdessen \\s)"
|
||||
},
|
||||
"searchStylesAll": {
|
||||
"message": "Alles"
|
||||
|
|
|
@ -186,6 +186,10 @@
|
|||
"message": "Theme",
|
||||
"description": "Label for the style editor's CSS theme."
|
||||
},
|
||||
"cm_arrowKeysTraverse": {
|
||||
"message": "Arrow keys ↑↓ traverse sections",
|
||||
"description": "Label for the option in the editor."
|
||||
},
|
||||
"colorpickerPaletteHint": {
|
||||
"message": "Right-click a swatch to cycle through its source lines"
|
||||
},
|
||||
|
@ -498,6 +502,9 @@
|
|||
"message": "Saved",
|
||||
"description": "Used in various parts of the UI to indicate that something was saved"
|
||||
},
|
||||
"genericSize": {
|
||||
"message": "Size"
|
||||
},
|
||||
"genericTest": {
|
||||
"message": "Test",
|
||||
"description": "Label for the action that runs some test e.g. opens the regexp tester panel in the editor"
|
||||
|
@ -759,6 +766,9 @@
|
|||
"message": "Number of applies-to items",
|
||||
"description": "Label for the numeric input box to limit max number of applies-to targets in the new UI on manage page"
|
||||
},
|
||||
"manageMinColumnWidth": {
|
||||
"message": "Minimum column width (in pixels; 9999 disables multi-column mode)"
|
||||
},
|
||||
"manageNewStyleAsUsercss": {
|
||||
"message": "as Usercss",
|
||||
"description": "VERY SHORT label for the checkbox next to the 'Write new style' button in the style manager"
|
||||
|
@ -1434,7 +1444,7 @@
|
|||
"description": "Text for label that shows the number of times a search result was installed during last week"
|
||||
},
|
||||
"searchStyleQueryHint": {
|
||||
"message": "Search style names case-insensitively:\nsome words - all words in any order\n\"some phrase\" - this exact phrase without quotes\n2020 - a year like this also shows styles updated in 2020",
|
||||
"message": "Search style names (case-sensitively if an uppercase letter is used):\nsome words - all these words in any order\n\"some phrase\" - this exact phrase without quotes\n/foo.*bar/i - regular expression without spaces (use \\s instead)",
|
||||
"description": "Tooltip shown for the text input in the popup's inline style finder"
|
||||
},
|
||||
"searchStylesAll": {
|
||||
|
|
|
@ -1143,9 +1143,6 @@
|
|||
"searchResultWeeklyCount": {
|
||||
"message": "Instalaciones semanales"
|
||||
},
|
||||
"searchStyleQueryHint": {
|
||||
"message": "La búsqueda de nombres de estilo no distingue mayúsculas de minúsculas:\nalgunas palabras: todas las palabras en cualquier orden\n\"una frase\": esta frase exacta (sin comillas)\n2020: al escribir un año, se muestran estilos que se actualizaron ese año"
|
||||
},
|
||||
"searchStylesAll": {
|
||||
"message": "Todos"
|
||||
},
|
||||
|
|
|
@ -1051,9 +1051,6 @@
|
|||
"searchResultWeeklyCount": {
|
||||
"message": "Instalaciones semanales"
|
||||
},
|
||||
"searchStyleQueryHint": {
|
||||
"message": "La búsqueda de nombres de estilo no distingue mayúsculas de minúsculas:\nalgunas palabras: todas las palabras en cualquier orden\n\"una frase\": esta frase exacta (sin comillas)\n2020: al escribir un año, se muestran estilos que se actualizaron ese año"
|
||||
},
|
||||
"searchStylesAll": {
|
||||
"message": "Todos"
|
||||
},
|
||||
|
|
|
@ -355,6 +355,9 @@
|
|||
"genericSavedMessage": {
|
||||
"message": "保存しました"
|
||||
},
|
||||
"genericSize": {
|
||||
"message": "サイズ"
|
||||
},
|
||||
"genericTest": {
|
||||
"message": "テスト"
|
||||
},
|
||||
|
@ -558,6 +561,9 @@
|
|||
"manageMaxTargets": {
|
||||
"message": "適用先欄の表示件数"
|
||||
},
|
||||
"manageMinColumnWidth": {
|
||||
"message": "最小カラム幅 (ピクセル単位。9999にすると、マルチカラムモードを無効にします)"
|
||||
},
|
||||
"manageNewStyleAsUsercss": {
|
||||
"message": "Usercssとして"
|
||||
},
|
||||
|
@ -1139,7 +1145,7 @@
|
|||
"message": "週間インストール数"
|
||||
},
|
||||
"searchStyleQueryHint": {
|
||||
"message": "スタイル名を大文字小文字の区別なく検索します:\n複数の単語 - 全単語を順番の区別なく検索します\n\"複数の単語からなるフレーズ\" - 正確なフレーズを (引用符は除いて) 検索します\n2020 - このように数値 (年) を指定すると、2020年に更新されたスタイルも結果に表示します"
|
||||
"message": "スタイル名を検索します (大文字を使用すると、大文字小文字を区別して検索します) :\n複数の単語 - すべての単語を (順不同で) 含む\n\"複数の単語からなるフレーズ\" - 正確なフレーズを (引用符は除いて) 探す\n/foo.*bar/i - 正規表現 (空白は使用不可。空白を使いたい場合は、代わりに \\s を使ってください)"
|
||||
},
|
||||
"searchStylesAll": {
|
||||
"message": "すべて"
|
||||
|
|
|
@ -1058,9 +1058,6 @@
|
|||
"searchResultWeeklyCount": {
|
||||
"message": "주간 설치 수"
|
||||
},
|
||||
"searchStyleQueryHint": {
|
||||
"message": "대소문자와 무관하게 스타일 이름을 검색하십시오:\n이런 검색어 - 순서에 무관하게 모든 단어가 포함하기만 하면 스타일을 불러옴\n\"이런 검색어\" - 따옴표 속 정확한 문장을 포함하는 스타일을 불러옴\n2020 - 해당 연도, 여기서는 2020년에 갱신된 스타일을 불러옴"
|
||||
},
|
||||
"searchStylesAll": {
|
||||
"message": "모두"
|
||||
},
|
||||
|
|
|
@ -62,7 +62,7 @@
|
|||
"message": "Alle updates toepassen"
|
||||
},
|
||||
"author": {
|
||||
"message": "Auteur"
|
||||
"message": "Maker"
|
||||
},
|
||||
"backupButtons": {
|
||||
"message": "Back-up"
|
||||
|
@ -98,7 +98,7 @@
|
|||
"message": "Automatisch aanvullen tijdens typen"
|
||||
},
|
||||
"cm_colorpicker": {
|
||||
"message": "Kleurkiezer voor CSS-kleuren"
|
||||
"message": "Kleurkiezers voor CSS-kleuren"
|
||||
},
|
||||
"cm_indentWithTabs": {
|
||||
"message": "Tabs met slimme inspringing gebruiken"
|
||||
|
@ -371,6 +371,9 @@
|
|||
"genericSavedMessage": {
|
||||
"message": "Opgeslagen"
|
||||
},
|
||||
"genericSize": {
|
||||
"message": "Grootte"
|
||||
},
|
||||
"genericTest": {
|
||||
"message": "Testen"
|
||||
},
|
||||
|
@ -568,6 +571,9 @@
|
|||
"manageMaxTargets": {
|
||||
"message": "Aantal ‘Van toepassing op’-items"
|
||||
},
|
||||
"manageMinColumnWidth": {
|
||||
"message": "Minimale kolombreedte (in pixels; 9999 schakelt de modus met meerdere kolommen uit)"
|
||||
},
|
||||
"manageNewStyleAsUsercss": {
|
||||
"message": "als Usercss"
|
||||
},
|
||||
|
@ -1146,7 +1152,7 @@
|
|||
"message": "Wekelijks aantal installaties"
|
||||
},
|
||||
"searchStyleQueryHint": {
|
||||
"message": "Stijlnamen niet hoofdlettergevoelig doorzoeken:\nbepaalde woorden - alle woorden in willekeurige volgorde\n\"bepaalde woordgroep\" - deze exacte woordgroep zonder aanhalingstekens\n2020 - een jaar als dit toont ook stijlen die in 2020 zijn bijgewerkt"
|
||||
"message": "Stijlnamen doorzoeken (hoofdlettergevoelig als een hoofdletter wordt gebruikt):\nenkele woorden - al deze woorden in willekeurige volgorde\n\"bepaalde woordgroep\" - deze exacte woordgroep zonder aanhalingstekens\n/foo.*bar/i - reguliere expressie zonder spaties (gebruik in plaats daarvan \\s)"
|
||||
},
|
||||
"searchStylesAll": {
|
||||
"message": "Alles"
|
||||
|
@ -1335,7 +1341,7 @@
|
|||
"message": "Niet volledig overeenkomend, dus overgeslagen"
|
||||
},
|
||||
"styleRegexpTestTitle": {
|
||||
"message": "Lijst van overeenkomende geopende tabbladen (klik op de URL om de focus op het tabblad te leggen)"
|
||||
"message": "Lijst met overeenkomende geopende tabbladen (klik op de URL om de focus op het tabblad te leggen)"
|
||||
},
|
||||
"styleSaveLabel": {
|
||||
"message": "Opslaan"
|
||||
|
|
|
@ -393,6 +393,9 @@
|
|||
"genericSavedMessage": {
|
||||
"message": "Zapisano"
|
||||
},
|
||||
"genericSize": {
|
||||
"message": "Rozmiar"
|
||||
},
|
||||
"genericTest": {
|
||||
"message": "Testuj"
|
||||
},
|
||||
|
@ -593,6 +596,9 @@
|
|||
"manageMaxTargets": {
|
||||
"message": "Liczba dotyczących elementów "
|
||||
},
|
||||
"manageMinColumnWidth": {
|
||||
"message": "Minimalna szerokość kolumny (w pikselach; 9999 wyłącza tryb wielokolumnowy)"
|
||||
},
|
||||
"manageNewStyleAsUsercss": {
|
||||
"message": "jako Usercss"
|
||||
},
|
||||
|
@ -1177,7 +1183,7 @@
|
|||
"message": "Tygodniowa liczba instalacji"
|
||||
},
|
||||
"searchStyleQueryHint": {
|
||||
"message": "Szukaj nazw stylów bez rozróżniania wielkości liter:\njakieś słowa - wszystkie słowa w dowolnej kolejności\n\"jakieś zdanie\" - dokładnie to zdanie bez cudzysłowów\n2020 - rok jak ten pokazuje również style zaktualizowane w 2020"
|
||||
"message": "Szukaj nazwy stylów (z uwzględnieniem wielkości liter, jeśli używana jest wielka litera):\njakieś słowa – wszystkie te słowa w dowolnej kolejności\n\"jakaś fraza\" – ta fraza bez cudzysłowów\n/foo.*bar/i – wyrażenie regularne bez spacji (zamiast tego użyj \\s)"
|
||||
},
|
||||
"searchStylesAll": {
|
||||
"message": "Wszystkie"
|
||||
|
|
|
@ -1173,9 +1173,6 @@
|
|||
"searchResultWeeklyCount": {
|
||||
"message": "Загрузок за неделю"
|
||||
},
|
||||
"searchStyleQueryHint": {
|
||||
"message": "Поиск стилистических имён по падежам:\nнекоторые слова - все слова в любом порядке\n\"какая-то фраза\" - именно эта фраза без кавычек\n2020 - такой год также показывает стили, обновлённые в 2020 году"
|
||||
},
|
||||
"searchStylesAll": {
|
||||
"message": "Все"
|
||||
},
|
||||
|
|
379
_locales/vi/messages.json
Normal file
379
_locales/vi/messages.json
Normal file
|
@ -0,0 +1,379 @@
|
|||
{
|
||||
"InaccessibleFileHint": {
|
||||
"message": "Stylus không thể truy cập một số kiểu tập tin (chẳng hạn như PDF và JSON)."
|
||||
},
|
||||
"addStyleLabel": {
|
||||
"message": "Viết bảng định kiểu mới"
|
||||
},
|
||||
"addStyleTitle": {
|
||||
"message": "Thêm bảng định kiểu"
|
||||
},
|
||||
"alphaChannel": {
|
||||
"message": "Độ mờ"
|
||||
},
|
||||
"appliesAdd": {
|
||||
"message": "Thêm"
|
||||
},
|
||||
"appliesDisplay": {
|
||||
"message": "Áp dụng với: $applies$",
|
||||
"placeholders": {
|
||||
"applies": {
|
||||
"content": "$1"
|
||||
}
|
||||
}
|
||||
},
|
||||
"appliesDisplayTruncatedSuffix": {
|
||||
"message": "và một số khác"
|
||||
},
|
||||
"appliesDomainOption": {
|
||||
"message": "Các địa chỉ thuộc tên miền này"
|
||||
},
|
||||
"appliesHelp": {
|
||||
"message": "Dùng tuỳ chọn \"Áp dụng với\" để giới hạn các địa chỉ cho đoạn mã này"
|
||||
},
|
||||
"appliesLabel": {
|
||||
"message": "Áp dụng với"
|
||||
},
|
||||
"appliesLineWidgetLabel": {
|
||||
"message": "Hiển thị thông tin \"Áp dụng với\""
|
||||
},
|
||||
"appliesLineWidgetWarning": {
|
||||
"message": "Không hoạt động với CSS tối giản"
|
||||
},
|
||||
"appliesRegexpOption": {
|
||||
"message": "URL khớp với biểu thức chính quy"
|
||||
},
|
||||
"appliesRemove": {
|
||||
"message": "Xoá"
|
||||
},
|
||||
"appliesRemoveError": {
|
||||
"message": "Không thể xoá mục \"Áp dụng với\" cuối cùng"
|
||||
},
|
||||
"appliesSpecify": {
|
||||
"message": "Chỉ định"
|
||||
},
|
||||
"appliesToEverything": {
|
||||
"message": "Tất cả"
|
||||
},
|
||||
"appliesUrlPrefixOption": {
|
||||
"message": "URL bắt đầu bằng"
|
||||
},
|
||||
"author": {
|
||||
"message": "Tác giả"
|
||||
},
|
||||
"backupButtons": {
|
||||
"message": "Sao lưu"
|
||||
},
|
||||
"backupMessage": {
|
||||
"message": "Để nhập tập tin sao lưu, kéo và thả nó vào trang này hoặc nhấp vào nút Nhập.\n\nĐể xuất một bản sao lưu tương thích với Stylus trước phiên bản 1.5.18, nhấp chuột phải hoặc nhấn giữ Shift khi nhấp chuột trái vào nút Xuất."
|
||||
},
|
||||
"bckpInstStyles": {
|
||||
"message": "Xuất bảng định kiểu"
|
||||
},
|
||||
"checkForUpdate": {
|
||||
"message": "Kiểm tra bản cập nhật mới"
|
||||
},
|
||||
"checkingForUpdate": {
|
||||
"message": "Đang kiểm tra..."
|
||||
},
|
||||
"clickToUninstall": {
|
||||
"message": "Nhấp để huỷ kích hoạt"
|
||||
},
|
||||
"cm_autoCloseBrackets": {
|
||||
"message": "Tự động đóng ngoặc và nháy"
|
||||
},
|
||||
"cm_autoCloseBracketsTooltip": {
|
||||
"message": "Tự động thêm dấu đóng tương ứng khi nhập một trong các dấu (, [, {, ' và \"."
|
||||
},
|
||||
"cm_autocompleteOnTyping": {
|
||||
"message": "Tự động hoàn thành"
|
||||
},
|
||||
"cm_colorpicker": {
|
||||
"message": "Bộ chọn màu cho màu CSS"
|
||||
},
|
||||
"cm_indentWithTabs": {
|
||||
"message": "Lùi đầu dòng thông minh bằng tab"
|
||||
},
|
||||
"cm_lineWrapping": {
|
||||
"message": "Gập dòng dài"
|
||||
},
|
||||
"cm_linter": {
|
||||
"message": "Trình phân tích cú pháp"
|
||||
},
|
||||
"cm_matchHighlight": {
|
||||
"message": "Làm nổi"
|
||||
},
|
||||
"cm_matchHighlightSelection": {
|
||||
"message": "Chỉ vùng được chọn"
|
||||
},
|
||||
"cm_matchHighlightToken": {
|
||||
"message": "Token nằm dưới con trỏ văn bản"
|
||||
},
|
||||
"cm_selectByTokens": {
|
||||
"message": "Nhấp đúp để chọn token"
|
||||
},
|
||||
"cm_selectByTokensTooltip": {
|
||||
"message": "Ví dụ về token: .foo-bar-2 #aabbcc 0.32 !important\nKhi tắt: Chọn từ (phân tách bằng dấu câu)."
|
||||
},
|
||||
"cm_smartIndent": {
|
||||
"message": "Lùi đầu dòng thông minh"
|
||||
},
|
||||
"cm_tabSize": {
|
||||
"message": "Chiều rộng tab"
|
||||
},
|
||||
"cm_theme": {
|
||||
"message": "Chủ đề"
|
||||
},
|
||||
"colorpickerSwitchFormatTooltip": {
|
||||
"message": "Đổi định dạng: HEX → RGB → HSL.\nNhấn giữ phím Shift khi nhấp để đảo thứ tự.\nPhím tắt: PgUp và PgDn."
|
||||
},
|
||||
"colorpickerTooltip": {
|
||||
"message": "Mở bộ chọn màu"
|
||||
},
|
||||
"configOnChange": {
|
||||
"message": "khi thay đổi"
|
||||
},
|
||||
"configOnChangeTooltip": {
|
||||
"message": "Tự động lưu và áp dụng"
|
||||
},
|
||||
"configureStyle": {
|
||||
"message": "Thiết lập"
|
||||
},
|
||||
"configureStyleOnHomepage": {
|
||||
"message": "Thiết lập trên trang chủ"
|
||||
},
|
||||
"confirmCancel": {
|
||||
"message": "Huỷ"
|
||||
},
|
||||
"confirmClose": {
|
||||
"message": "Đóng"
|
||||
},
|
||||
"confirmDefault": {
|
||||
"message": "Dùng mặc định"
|
||||
},
|
||||
"confirmDelete": {
|
||||
"message": "Xoá"
|
||||
},
|
||||
"confirmDiscardChanges": {
|
||||
"message": "Huỷ thay đổi?"
|
||||
},
|
||||
"confirmNo": {
|
||||
"message": "Không"
|
||||
},
|
||||
"confirmSave": {
|
||||
"message": "Lưu"
|
||||
},
|
||||
"confirmStop": {
|
||||
"message": "Dừng"
|
||||
},
|
||||
"confirmYes": {
|
||||
"message": "Có"
|
||||
},
|
||||
"connectingDropbox": {
|
||||
"message": "Đang kết nối với Dropbox..."
|
||||
},
|
||||
"connectingDropboxNotAllowed": {
|
||||
"message": "Tính năng kết nối với Dropbox chỉ khả dụng khi cài ứng dụng trực tiếp từ cửa hàng web"
|
||||
},
|
||||
"copied": {
|
||||
"message": "Đã chép vào khay nhớ tạm"
|
||||
},
|
||||
"copy": {
|
||||
"message": "Chép vào khay nhớ tạm"
|
||||
},
|
||||
"dateAbbrDay": {
|
||||
"message": "$value$ ngày",
|
||||
"placeholders": {
|
||||
"value": {
|
||||
"content": "$1"
|
||||
}
|
||||
}
|
||||
},
|
||||
"dateAbbrHour": {
|
||||
"message": "$value$ giờ",
|
||||
"placeholders": {
|
||||
"value": {
|
||||
"content": "$1"
|
||||
}
|
||||
}
|
||||
},
|
||||
"dateAbbrMonth": {
|
||||
"message": "$value$ tháng",
|
||||
"placeholders": {
|
||||
"value": {
|
||||
"content": "$1"
|
||||
}
|
||||
}
|
||||
},
|
||||
"dateAbbrYear": {
|
||||
"message": "$value$ năm",
|
||||
"placeholders": {
|
||||
"value": {
|
||||
"content": "$1"
|
||||
}
|
||||
}
|
||||
},
|
||||
"dateInstalled": {
|
||||
"message": "Ngày cài đặt"
|
||||
},
|
||||
"dateUpdated": {
|
||||
"message": "Ngày cập nhật"
|
||||
},
|
||||
"defaultTheme": {
|
||||
"message": "mặc định"
|
||||
},
|
||||
"deleteStyleConfirm": {
|
||||
"message": "Bạn có chắc chắn muốn xoá bảng định kiểu này không?"
|
||||
},
|
||||
"deleteStyleLabel": {
|
||||
"message": "Xoá"
|
||||
},
|
||||
"disableAllStyles": {
|
||||
"message": "Tắt tất cả bảng định kiểu"
|
||||
},
|
||||
"disableAllStylesOff": {
|
||||
"message": "Bật tất cả bảng định kiểu"
|
||||
},
|
||||
"disableStyleLabel": {
|
||||
"message": "Vô hiệu hoá"
|
||||
},
|
||||
"draftAction": {
|
||||
"message": "Chọn \"Có\" để tải bản nháp hoặc \"Không\" để xoá nó đi."
|
||||
},
|
||||
"editDeleteText": {
|
||||
"message": "Xoá"
|
||||
},
|
||||
"editGotoLine": {
|
||||
"message": "Đi đến dòng (hoặc dòng:cột)"
|
||||
},
|
||||
"editStyleHeading": {
|
||||
"message": "Sửa bảng định kiểu"
|
||||
},
|
||||
"editStyleLabel": {
|
||||
"message": "Sửa"
|
||||
},
|
||||
"editStyleTitle": {
|
||||
"message": "Sửa bảng định kiểu $stylename$",
|
||||
"placeholders": {
|
||||
"stylename": {
|
||||
"content": "$1"
|
||||
}
|
||||
}
|
||||
},
|
||||
"editorSettings": {
|
||||
"message": "Cài đặt trình soạn thảo"
|
||||
},
|
||||
"enableStyleLabel": {
|
||||
"message": "Kích hoạt"
|
||||
},
|
||||
"exportCompatible": {
|
||||
"message": "Xuất (chế độ tương thích)"
|
||||
},
|
||||
"exportLabel": {
|
||||
"message": "Xuất"
|
||||
},
|
||||
"exportSavedSuccess": {
|
||||
"message": "Lưu thành công"
|
||||
},
|
||||
"externalFeedback": {
|
||||
"message": "Phản hồi"
|
||||
},
|
||||
"externalHomepage": {
|
||||
"message": "Trang chủ"
|
||||
},
|
||||
"externalLink": {
|
||||
"message": "Liên kết ngoài"
|
||||
},
|
||||
"externalSupport": {
|
||||
"message": "Ủng hộ"
|
||||
},
|
||||
"genericAdd": {
|
||||
"message": "Thêm"
|
||||
},
|
||||
"genericDescription": {
|
||||
"message": "Mô tả"
|
||||
},
|
||||
"genericDisabledLabel": {
|
||||
"message": "Vô hiệu hoá"
|
||||
},
|
||||
"genericEnabledLabel": {
|
||||
"message": "Kích hoạt"
|
||||
},
|
||||
"genericError": {
|
||||
"message": "Lỗi"
|
||||
},
|
||||
"genericHistoryLabel": {
|
||||
"message": "Lịch sử"
|
||||
},
|
||||
"genericNext": {
|
||||
"message": "Sau"
|
||||
},
|
||||
"genericPrevious": {
|
||||
"message": "Trước"
|
||||
},
|
||||
"genericResetLabel": {
|
||||
"message": "Đặt lại"
|
||||
},
|
||||
"genericSavedMessage": {
|
||||
"message": "Đã lưu"
|
||||
},
|
||||
"genericTest": {
|
||||
"message": "Thứ"
|
||||
},
|
||||
"genericTitle": {
|
||||
"message": "Tiêu đề"
|
||||
},
|
||||
"genericUnknown": {
|
||||
"message": "Không xác định"
|
||||
},
|
||||
"helpAlt": {
|
||||
"message": "Trợ giúp"
|
||||
},
|
||||
"importLabel": {
|
||||
"message": "Nhập"
|
||||
},
|
||||
"importReportLegendAdded": {
|
||||
"message": "đã thêm"
|
||||
},
|
||||
"importReportLegendIdentical": {
|
||||
"message": "Đã bỏ qua các tệp trùng lặp"
|
||||
},
|
||||
"importReportLegendInvalid": {
|
||||
"message": "Đã bỏ qua các tệp không hợp lệ"
|
||||
},
|
||||
"importReportLegendUpdatedBoth": {
|
||||
"message": "đã cập nhật siêu thông tin và mã"
|
||||
},
|
||||
"importReportLegendUpdatedCode": {
|
||||
"message": "đã cập nhật mã"
|
||||
},
|
||||
"importReportLegendUpdatedMeta": {
|
||||
"message": "đã cập nhật siêu thông tin"
|
||||
},
|
||||
"importReportTitle": {
|
||||
"message": "Đã nhập xong"
|
||||
},
|
||||
"installUpdateFromLabel": {
|
||||
"message": "Kiểm tra bản cập nhật mới"
|
||||
},
|
||||
"license": {
|
||||
"message": "Giấy phép"
|
||||
},
|
||||
"linterCSSLintIncompatible": {
|
||||
"message": "CSSLint không hỗ trợ bộ tiền xử lý $preprocessorname$",
|
||||
"placeholders": {
|
||||
"preprocessorname": {
|
||||
"content": "$1"
|
||||
}
|
||||
}
|
||||
},
|
||||
"linterJSONError": {
|
||||
"message": "Định dạng JSON không hợp lệ"
|
||||
},
|
||||
"styleEnabledLabel": {
|
||||
"message": "Kích hoạt"
|
||||
},
|
||||
"styleSaveLabel": {
|
||||
"message": "Lưu"
|
||||
}
|
||||
}
|
|
@ -393,6 +393,9 @@
|
|||
"genericSavedMessage": {
|
||||
"message": "已保存"
|
||||
},
|
||||
"genericSize": {
|
||||
"message": "大小"
|
||||
},
|
||||
"genericTest": {
|
||||
"message": "测试"
|
||||
},
|
||||
|
@ -1170,9 +1173,6 @@
|
|||
"searchResultWeeklyCount": {
|
||||
"message": "本周安装次数"
|
||||
},
|
||||
"searchStyleQueryHint": {
|
||||
"message": "空格多词 -- 同时精确匹配(无需双引号)\n2020 -- 也含更新日期的匹配结果\n大小写不敏感"
|
||||
},
|
||||
"searchStylesAll": {
|
||||
"message": "全部"
|
||||
},
|
||||
|
@ -1392,7 +1392,7 @@
|
|||
"message": "更新 URL"
|
||||
},
|
||||
"stylusUnavailableForURL": {
|
||||
"message": "Stylus 无法介入到此类页面"
|
||||
"message": "Stylus 不能在此类页面上工作"
|
||||
},
|
||||
"stylusUnavailableForURLdetails": {
|
||||
"message": "出于安全考虑,浏览器禁止扩展程序影响其内置页面(例如 chrome://version,Chrome 61 后的标准新标签页,about:addons 等等)以及其他扩展程序的页面。每个浏览器也限制了对于自己扩展程序库的介入〔例如 Chrome 网上应用店、Firefox 附加组件(addons.mozilla.org)〕。"
|
||||
|
|
|
@ -393,6 +393,9 @@
|
|||
"genericSavedMessage": {
|
||||
"message": "已儲存"
|
||||
},
|
||||
"genericSize": {
|
||||
"message": "大小"
|
||||
},
|
||||
"genericTest": {
|
||||
"message": "測試"
|
||||
},
|
||||
|
@ -593,6 +596,9 @@
|
|||
"manageMaxTargets": {
|
||||
"message": "已套用項目的數量"
|
||||
},
|
||||
"manageMinColumnWidth": {
|
||||
"message": "最小欄位寬度(以像素為單位;9999 停用多欄模式)"
|
||||
},
|
||||
"manageNewStyleAsUsercss": {
|
||||
"message": "做為 Usercss"
|
||||
},
|
||||
|
@ -1174,7 +1180,7 @@
|
|||
"message": "每週安裝"
|
||||
},
|
||||
"searchStyleQueryHint": {
|
||||
"message": "搜尋樣式名稱時是區分大小寫的:\nsome words - 以任意順序搜尋所有文字\n\"some phrase\" - 精確符合引號內的詞語\n2020 - 如此年份顯示在2020年更新的樣式"
|
||||
"message": "搜尋樣式名稱(若使用大寫字母,則區分大小寫):\n一些字 - 以任何順序符合所有字\n\"一些詞\" - 精確符合引號內的詞\n/foo.*bar/i - 沒有空格的正規表示式(使用 \\s 代替)"
|
||||
},
|
||||
"searchStylesAll": {
|
||||
"message": "全部"
|
||||
|
|
|
@ -8,7 +8,7 @@
|
|||
/* global usercssMan */
|
||||
/* global usoApi */
|
||||
/* global uswApi */
|
||||
/* global FIREFOX UA activateTab findExistingTab openURL */ // toolbox.js
|
||||
/* global FIREFOX UA activateTab openURL */ // toolbox.js
|
||||
/* global colorScheme */ // color-scheme.js
|
||||
'use strict';
|
||||
|
||||
|
@ -87,28 +87,25 @@ addAPI(/** @namespace API */ {
|
|||
|
||||
/** @returns {Promise<chrome.tabs.Tab>} */
|
||||
async openManage({options = false, search, searchMode} = {}) {
|
||||
let url = chrome.runtime.getURL('manage.html');
|
||||
if (search) {
|
||||
url += `?search=${encodeURIComponent(search)}&searchMode=${searchMode}`;
|
||||
const setUrlParams = url => {
|
||||
const u = new URL(url);
|
||||
if (search) u.searchParams.set('search', search);
|
||||
if (searchMode) u.searchParams.set('searchMode', searchMode);
|
||||
if (options) u.hash = '#stylus-options';
|
||||
return u.href;
|
||||
};
|
||||
const base = chrome.runtime.getURL('manage.html');
|
||||
const url = setUrlParams(base);
|
||||
const tabs = await browser.tabs.query({url: base + '*'});
|
||||
const same = tabs.find(t => t.url === url);
|
||||
let tab = same || tabs[0];
|
||||
if (!tab) {
|
||||
API.prefsDb.get('badFavs'); // prime the cache to avoid flicker/delay when opening the page
|
||||
tab = await openURL({url, newTab: true});
|
||||
} else if (!same) {
|
||||
msg.sendTab(tab.id, {method: 'pushState', url: setUrlParams(tab.url)});
|
||||
}
|
||||
if (options) {
|
||||
url += '#stylus-options';
|
||||
}
|
||||
const tab = await findExistingTab({
|
||||
url,
|
||||
currentWindow: null,
|
||||
ignoreHash: true,
|
||||
ignoreSearch: true,
|
||||
});
|
||||
if (tab) {
|
||||
await activateTab(tab);
|
||||
if (url !== (tab.pendingUrl || tab.url)) {
|
||||
await msg.sendTab(tab.id, {method: 'pushState', url}).catch(console.error);
|
||||
}
|
||||
return tab;
|
||||
}
|
||||
API.prefsDb.get('badFavs'); // prime the cache to avoid flicker/delay when opening the page
|
||||
return openURL({url, ignoreExisting: true}).then(activateTab); // activateTab unminimizes the window
|
||||
return activateTab(tab); // activateTab unminimizes the window
|
||||
},
|
||||
|
||||
/**
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
/* global API msg */// msg.js
|
||||
/* global CHROME URLS deepEqual isEmptyObj mapObj stringAsRegExp tryRegExp tryURL */// toolbox.js
|
||||
/* global bgReady createCache uuidIndex */// common.js
|
||||
/* global calcStyleDigest styleCodeEmpty styleSectionGlobal */// sections-util.js
|
||||
/* global calcStyleDigest styleCodeEmpty */// sections-util.js
|
||||
/* global db */
|
||||
/* global prefs */
|
||||
/* global tabMan */
|
||||
|
@ -74,6 +74,29 @@ const styleMan = (() => {
|
|||
_rev: 0,
|
||||
};
|
||||
uuidIndex.addCustomId(orderWrap, {set: setOrder});
|
||||
|
||||
class MatchQuery {
|
||||
constructor(url) {
|
||||
this.url = url;
|
||||
}
|
||||
get urlWithoutHash() {
|
||||
return this._set('urlWithoutHash', this.url.split('#', 1)[0]);
|
||||
}
|
||||
get urlWithoutParams() {
|
||||
return this._set('urlWithoutParams', this.url.split(/[?#]/, 1)[0]);
|
||||
}
|
||||
get domain() {
|
||||
return this._set('domain', tryURL(this.url).hostname);
|
||||
}
|
||||
get isOwnPage() {
|
||||
return this._set('isOwnPage', this.url.startsWith(URLS.ownOrigin));
|
||||
}
|
||||
_set(name, value) {
|
||||
Object.defineProperty(this, name, {value});
|
||||
return value;
|
||||
}
|
||||
}
|
||||
|
||||
/** @type {Promise|boolean} will be `true` to avoid wasting a microtask tick on each `await` */
|
||||
let ready = Promise.all([init(), prefs.ready]);
|
||||
|
||||
|
@ -176,6 +199,18 @@ const styleMan = (() => {
|
|||
|
||||
getOrder: () => orderWrap.value,
|
||||
|
||||
/** @returns {Promise<string | {[remoteId:string]: styleId}>}>} */
|
||||
async getRemoteInfo(id) {
|
||||
if (ready.then) await ready;
|
||||
if (id) return calcRemoteId(id2style(id));
|
||||
const res = {};
|
||||
for (const {style} of dataMap.values()) {
|
||||
const [rid, vars] = calcRemoteId(style);
|
||||
if (rid) res[rid] = [style.id, vars];
|
||||
}
|
||||
return res;
|
||||
},
|
||||
|
||||
/** @returns {Promise<StyleSectionsToApply>} */
|
||||
async getSectionsByUrl(url, id, isInitialApply) {
|
||||
if (ready.then) await ready;
|
||||
|
@ -226,7 +261,7 @@ const styleMan = (() => {
|
|||
const styles = id
|
||||
? [id2style(id)].filter(Boolean)
|
||||
: getAllAsArray();
|
||||
const query = createMatchQuery(url);
|
||||
const query = new MatchQuery(url);
|
||||
for (const style of styles) {
|
||||
let excluded = false;
|
||||
let excludedScheme = false;
|
||||
|
@ -248,10 +283,7 @@ const styleMan = (() => {
|
|||
excludedScheme = true;
|
||||
}
|
||||
for (const section of style.sections) {
|
||||
if (styleSectionGlobal(section) && styleCodeEmpty(section.code)) {
|
||||
continue;
|
||||
}
|
||||
const match = urlMatchSection(query, section);
|
||||
const match = urlMatchSection(query, section, true);
|
||||
if (match) {
|
||||
if (match === 'sloppy') {
|
||||
sloppy = true;
|
||||
|
@ -346,6 +378,17 @@ const styleMan = (() => {
|
|||
return id2style(uuidIndex.get(uuid));
|
||||
}
|
||||
|
||||
function calcRemoteId({md5Url, updateUrl, usercssData: ucd} = {}) {
|
||||
let id;
|
||||
id = (id = /\d+/.test(md5Url) || URLS.extractUsoArchiveId(updateUrl)) && `uso-${id}`
|
||||
|| (id = URLS.extractUSwId(updateUrl)) && `usw-${id}`
|
||||
|| '';
|
||||
return id && [
|
||||
id,
|
||||
ucd && !isEmptyObj(ucd.vars),
|
||||
];
|
||||
}
|
||||
|
||||
/** @returns {StyleObj} */
|
||||
function createNewStyle() {
|
||||
return /** @namespace StyleObj */ {
|
||||
|
@ -431,7 +474,7 @@ const styleMan = (() => {
|
|||
cache.maybeMatch.add(id);
|
||||
continue;
|
||||
}
|
||||
const code = getAppliedCode(createMatchQuery(url), style);
|
||||
const code = getAppliedCode(new MatchQuery(url), style);
|
||||
if (code) {
|
||||
updated.add(url);
|
||||
buildCacheEntry(cache, style, code);
|
||||
|
@ -601,40 +644,56 @@ const styleMan = (() => {
|
|||
return true;
|
||||
}
|
||||
|
||||
function urlMatchSection(query, section) {
|
||||
function urlMatchSection(query, section, skipEmptyGlobal) {
|
||||
let dd, ddL, pp, ppL, rr, rrL, uu, uuL;
|
||||
if (
|
||||
section.domains &&
|
||||
section.domains.some(d => d === query.domain || query.domain.endsWith(`.${d}`))
|
||||
(dd = section.domains) && (ddL = dd.length) && dd.some(urlMatchDomain, query) ||
|
||||
(pp = section.urlPrefixes) && (ppL = pp.length) && pp.some(urlMatchPrefix, query) ||
|
||||
/* Per the specification the fragment portion is ignored in @-moz-document:
|
||||
https://www.w3.org/TR/2012/WD-css3-conditional-20120911/#url-of-doc
|
||||
but the spec is outdated and doesn't account for SPA sites,
|
||||
so we only respect it for `url()` function */
|
||||
(uu = section.urls) && (uuL = uu.length) && (
|
||||
uu.includes(query.url) ||
|
||||
uu.includes(query.urlWithoutHash)
|
||||
) ||
|
||||
(rr = section.regexps) && (rrL = rr.length) && rr.some(urlMatchRegexp, query)
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
if (section.urlPrefixes && section.urlPrefixes.some(p => p && query.url.startsWith(p))) {
|
||||
return true;
|
||||
}
|
||||
// as per spec the fragment portion is ignored in @-moz-document:
|
||||
// https://www.w3.org/TR/2012/WD-css3-conditional-20120911/#url-of-doc
|
||||
// but the spec is outdated and doesn't account for SPA sites
|
||||
// so we only respect it for `url()` function
|
||||
if (section.urls && (
|
||||
section.urls.includes(query.url) ||
|
||||
section.urls.includes(query.urlWithoutHash)
|
||||
)) {
|
||||
return true;
|
||||
}
|
||||
if (section.regexps && section.regexps.some(r => compileRe(r).test(query.url))) {
|
||||
return true;
|
||||
}
|
||||
/*
|
||||
According to CSS4 @document specification the entire URL must match.
|
||||
Stylish-for-Chrome implemented it incorrectly since the very beginning.
|
||||
We'll detect styles that abuse the bug by finding the sections that
|
||||
would have been applied by Stylish but not by us as we follow the spec.
|
||||
*/
|
||||
if (section.regexps && section.regexps.some(r => compileSloppyRe(r).test(query.url))) {
|
||||
if (rrL && rr.some(urlMatchRegexpSloppy, query)) {
|
||||
return 'sloppy';
|
||||
}
|
||||
// TODO: check for invalid regexps?
|
||||
return styleSectionGlobal(section);
|
||||
return !rrL && !ppL && !uuL && !ddL &&
|
||||
!query.isOwnPage && // We allow only intentionally targeted sections for own pages
|
||||
(!skipEmptyGlobal || !styleCodeEmpty(section.code));
|
||||
}
|
||||
/** @this {MatchQuery} */
|
||||
function urlMatchDomain(d) {
|
||||
const _d = this.domain;
|
||||
return d === _d ||
|
||||
_d[_d.length - d.length - 1] === '.' && _d.endsWith(d);
|
||||
}
|
||||
/** @this {MatchQuery} */
|
||||
function urlMatchPrefix(p) {
|
||||
return p && this.url.startsWith(p);
|
||||
}
|
||||
/** @this {MatchQuery} */
|
||||
function urlMatchRegexp(r) {
|
||||
return (!this.isOwnPage || /\bextension\b/.test(r)) &&
|
||||
compileRe(r).test(this.url);
|
||||
}
|
||||
/** @this {MatchQuery} */
|
||||
function urlMatchRegexpSloppy(r) {
|
||||
return (!this.isOwnPage || /\bextension\b/.test(r)) &&
|
||||
compileSloppyRe(r).test(this.url);
|
||||
}
|
||||
|
||||
function createCompiler(compile) {
|
||||
|
@ -674,37 +733,8 @@ const styleMan = (() => {
|
|||
'$';
|
||||
}
|
||||
|
||||
function createMatchQuery(url) {
|
||||
let urlWithoutHash;
|
||||
let urlWithoutParams;
|
||||
let domain;
|
||||
return {
|
||||
url,
|
||||
get urlWithoutHash() {
|
||||
if (!urlWithoutHash) {
|
||||
urlWithoutHash = url.split('#')[0];
|
||||
}
|
||||
return urlWithoutHash;
|
||||
},
|
||||
get urlWithoutParams() {
|
||||
if (!urlWithoutParams) {
|
||||
const u = tryURL(url);
|
||||
urlWithoutParams = u.origin + u.pathname;
|
||||
}
|
||||
return urlWithoutParams;
|
||||
},
|
||||
get domain() {
|
||||
if (!domain) {
|
||||
const u = tryURL(url);
|
||||
domain = u.hostname;
|
||||
}
|
||||
return domain;
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
function buildCache(cache, url, styleList) {
|
||||
const query = createMatchQuery(url);
|
||||
const query = new MatchQuery(url);
|
||||
for (const {style, appliesTo, preview} of styleList) {
|
||||
const code = getAppliedCode(query, preview || style);
|
||||
if (code) {
|
||||
|
|
|
@ -71,9 +71,10 @@ const usoApi = {};
|
|||
return true;
|
||||
};
|
||||
|
||||
function ping(id, resolve) {
|
||||
return fetch(`${URLS.uso}styles/install/${id}?source=stylish-ch`)
|
||||
.then(resolve);
|
||||
async function ping(id, resolve) {
|
||||
await fetch(`${URLS.uso}styles/install/${id}?source=stylish-ch`);
|
||||
if (resolve) resolve(true);
|
||||
return true;
|
||||
}
|
||||
|
||||
function makeKey(key, {badKeys, newKeys}) {
|
||||
|
|
|
@ -35,7 +35,7 @@ const uswApi = (() => {
|
|||
const maxKeyLen = meta.reduce((res, [k]) => Math.max(res, k.length), 0);
|
||||
return [
|
||||
'/* ==UserStyle==',
|
||||
...meta.map(([k, v]) => `${k}${' '.repeat(maxKeyLen - k.length + 2)}${v || ''}`),
|
||||
...meta.map(([k, v]) => v && `${k}${' '.repeat(maxKeyLen - k.length + 2)}${v}`).filter(Boolean),
|
||||
'==/UserStyle== */',
|
||||
].join('\n') + '\n\n';
|
||||
}
|
||||
|
@ -77,14 +77,15 @@ const uswApi = (() => {
|
|||
*/
|
||||
async publish(id, sourceCode) {
|
||||
const style = await API.styles.get(id);
|
||||
const code = style.usercssData ? sourceCode
|
||||
: fakeUsercssHeader(style) + sourceCode;
|
||||
const data = (style._usw || {}).token
|
||||
? style._usw
|
||||
: await linkStyle(style, sourceCode);
|
||||
const header = style.usercssData ? '' : fakeUsercssHeader(style);
|
||||
: await linkStyle(style, code);
|
||||
return uswFetch(`style/${data.id}`, data.token, {
|
||||
method: 'POST',
|
||||
headers: {'Content-Type': 'application/json'},
|
||||
body: JSON.stringify({code: header + sourceCode}),
|
||||
body: JSON.stringify({code}),
|
||||
});
|
||||
},
|
||||
|
||||
|
|
|
@ -10,13 +10,12 @@
|
|||
const USO = 'https://userstyles.org';
|
||||
const apiUrl = `${USO}/api/v1/styles/${usoId}`;
|
||||
const md5Url = `https://update.userstyles.org/${usoId}.md5`;
|
||||
const CLICK = {
|
||||
customize: '.customize_button',
|
||||
install: '#install_style_button',
|
||||
uninstall: '#uninstall_style_button',
|
||||
update: '#update_style_button',
|
||||
};
|
||||
const CLICK_SEL = Object.values(CLICK).join(',');
|
||||
const CLICK = [
|
||||
['#install_stylish_style_button', onInstall],
|
||||
['#update_stylish_style_button', onInstall],
|
||||
['.customize_style_button', onCustomize],
|
||||
['.uninstall_stylish_style_button', onUninstall],
|
||||
];
|
||||
const pageEventId = `${performance.now()}${Math.random()}`;
|
||||
const contentEventId = pageEventId + ':';
|
||||
const orphanEventId = chrome.runtime.id; // id won't be available in the orphaned script
|
||||
|
@ -43,7 +42,7 @@
|
|||
document.body || new Promise(resolve => addEventListener('load', resolve, {once: true})),
|
||||
]);
|
||||
|
||||
if (!dup.id) {
|
||||
if (!dup) {
|
||||
sendStylishEvent('styleCanBeInstalledChrome');
|
||||
} else if (dup.originalMd5 && dup.originalMd5 !== md5 || !dup.usercssData || !dup.md5Url) {
|
||||
// allow update if 1) changed, 2) is a classic USO style, 3) is from USO-archive
|
||||
|
@ -53,38 +52,47 @@
|
|||
}
|
||||
|
||||
async function onClick(e) {
|
||||
const el = e.target.closest(CLICK_SEL);
|
||||
if (!el) return;
|
||||
el.disabled = true;
|
||||
const {id} = dup;
|
||||
try {
|
||||
if (el.matches(CLICK.uninstall)) {
|
||||
dup = style = false;
|
||||
removeEventListener('change', onChange);
|
||||
await API.styles.delete(id);
|
||||
return;
|
||||
for (const [sel, fn] of CLICK) {
|
||||
const el = e.target.closest(sel);
|
||||
if (!el) continue;
|
||||
try {
|
||||
el.disabled = true;
|
||||
await fn(e);
|
||||
} catch (e) {
|
||||
alert(chrome.i18n.getMessage('styleInstallFailed', e.message || e));
|
||||
} finally {
|
||||
el.disabled = false;
|
||||
}
|
||||
if (el.matches(CLICK.customize)) {
|
||||
const isOn = dup && !$('#style-settings');
|
||||
toggleListener(isOn, 'change', onChange);
|
||||
observeColors(isOn);
|
||||
return;
|
||||
}
|
||||
e.stopPropagation();
|
||||
if (!style) await buildStyle();
|
||||
style = dup = await API.usercss.install(style, {
|
||||
dup: {id},
|
||||
vars: getPageVars(),
|
||||
});
|
||||
sendStylishEvent('styleInstalledChrome');
|
||||
API.uso.pingback(id);
|
||||
} catch (e) {
|
||||
alert(chrome.i18n.getMessage('styleInstallFailed', e.message || e));
|
||||
} finally {
|
||||
el.disabled = false;
|
||||
}
|
||||
}
|
||||
|
||||
function onCustomize() {
|
||||
const ss = $('#style-settings');
|
||||
const willShow = !ss || !ss.offsetHeight;
|
||||
observeColors(willShow);
|
||||
toggleListener(willShow, 'change', onChange);
|
||||
}
|
||||
|
||||
async function onInstall(e) {
|
||||
const {id} = dup;
|
||||
e.stopPropagation();
|
||||
if (!style) await buildStyle();
|
||||
style = dup = await API.usercss.install(style, {
|
||||
dup: {id},
|
||||
vars: getPageVars(),
|
||||
});
|
||||
sendStylishEvent('styleInstalledChrome');
|
||||
API.uso.pingback(id);
|
||||
}
|
||||
|
||||
function onUninstall() {
|
||||
const {id} = dup;
|
||||
dup = style = false;
|
||||
observeColors(false);
|
||||
removeEventListener('change', onChange);
|
||||
return API.styles.delete(id);
|
||||
}
|
||||
|
||||
function onChange({target: el}) {
|
||||
if (dup && el.matches('[name^="ik-"], [type=file]')) {
|
||||
API.usercss.configVars(dup.id, getPageVars());
|
||||
|
@ -121,7 +129,7 @@
|
|||
const {vars} = (style || dup).usercssData;
|
||||
for (const el of document.querySelectorAll('[name^="ik-"]')) {
|
||||
const name = el.name.slice(3); // dropping "ik-"
|
||||
const ik = badKeys[name] || name;
|
||||
const ik = (badKeys || {})[name] || name;
|
||||
const v = vars[ik] || false;
|
||||
const isImage = el.type === 'radio';
|
||||
if (v && (!isImage || el.checked)) {
|
||||
|
@ -185,45 +193,56 @@
|
|||
})();
|
||||
|
||||
function inPageContext(eventId, eventIdHost, styleId, apiUrl) {
|
||||
let done, orphaned, vars;
|
||||
// `chrome` may be empty if no extensions use externally_connectable but USO needs it
|
||||
if (!window.chrome) window.chrome = {runtime: {sendMessage: () => {}}};
|
||||
const EXT_ID = 'fjnbnpbmkenffdnngjfgmeleoegfcffe';
|
||||
const {defineProperty} = Object;
|
||||
const {dispatchEvent, CustomEvent, removeEventListener} = window;
|
||||
const apply = Map.call.bind(Map.apply);
|
||||
const CR = chrome.runtime;
|
||||
const SEND = 'sendMessage';
|
||||
const RP = Response.prototype;
|
||||
const ORIG = {json: RP.json, [SEND]: CR[SEND]};
|
||||
let done, orphaned, vars;
|
||||
CR[SEND] = ovrSend;
|
||||
RP.json = ovrJson;
|
||||
const OVR = [
|
||||
[chrome.runtime, 'sendMessage', (fn, me, args) => {
|
||||
const [id, /*msg*/, opts, cb = opts] = args;
|
||||
if (id !== EXT_ID) return apply(fn, me, args);
|
||||
if (typeof cb !== 'function') return Promise.resolve(true);
|
||||
cb(true);
|
||||
}],
|
||||
[Response.prototype, 'json', async (fn, me, args) => {
|
||||
const res = await apply(fn, me, args);
|
||||
try {
|
||||
if (!done && me.url === apiUrl) {
|
||||
done = true;
|
||||
send(res);
|
||||
setVars(res);
|
||||
}
|
||||
} catch (e) {}
|
||||
return res;
|
||||
}],
|
||||
[window, 'fetch', (fn, me, args) =>
|
||||
args[0] === `chrome-extension://${EXT_ID}/index.html`
|
||||
? Promise.resolve(new Response('<!doctype html><html lang="en"></html>'))
|
||||
: apply(fn, me, args),
|
||||
],
|
||||
];
|
||||
OVR.forEach(([obj, name, caller], i) => {
|
||||
const orig = obj[name];
|
||||
const ovr = new Proxy(orig, {
|
||||
apply(fn, me, args) {
|
||||
if (orphaned) restore(obj, name, ovr, fn);
|
||||
return (orphaned ? apply : caller)(fn, me, args);
|
||||
},
|
||||
});
|
||||
defineProperty(obj, name, {value: ovr});
|
||||
OVR[i] = [obj, name, ovr, orig]; // same args as restore()
|
||||
});
|
||||
/* We set `isInstalled` at page start intentionally not trying to replicate Stylish login events.
|
||||
* This difference allows USO site to detect presence of Stylus (or another similar extension). */
|
||||
window.isInstalled = true;
|
||||
addEventListener(eventId, onCommand, true);
|
||||
function ovrSend(id, msg, opts, cb = opts) {
|
||||
if (!orphaned &&
|
||||
id === 'fjnbnpbmkenffdnngjfgmeleoegfcffe' &&
|
||||
msg && msg.type === 'deleteStyle' &&
|
||||
typeof cb === 'function') {
|
||||
cb(true);
|
||||
} else {
|
||||
return ORIG[SEND](...arguments);
|
||||
}
|
||||
}
|
||||
async function ovrJson() {
|
||||
const res = await apply(ORIG.json, this, arguments);
|
||||
try {
|
||||
if (!done && this.url === apiUrl) {
|
||||
if (RP.json === ovrJson) RP.json = ORIG.json;
|
||||
done = true;
|
||||
send(res);
|
||||
setVars(res);
|
||||
}
|
||||
} catch (e) {}
|
||||
return res;
|
||||
}
|
||||
function onCommand(e) {
|
||||
if (e.detail === 'quit') {
|
||||
removeEventListener(eventId, onCommand, true);
|
||||
// We can restore the hooks only if another script didn't modify them
|
||||
if (CR[SEND] === ovrSend) CR[SEND] = ovrSend;
|
||||
if (RP.json === ovrJson) RP.json = ORIG.json;
|
||||
OVR.forEach(restore);
|
||||
done = orphaned = true;
|
||||
} else if (/^vars:/.test(e.detail)) {
|
||||
vars = JSON.parse(e.detail.slice(5));
|
||||
|
@ -231,6 +250,11 @@ function inPageContext(eventId, eventIdHost, styleId, apiUrl) {
|
|||
send(e.relatedTarget.uploadedData);
|
||||
}
|
||||
}
|
||||
function restore(obj, name, ovr, orig) { // same order as OVR after patching
|
||||
if (obj[name] === ovr) {
|
||||
defineProperty(obj, name, {value: orig});
|
||||
}
|
||||
}
|
||||
function send(data) {
|
||||
dispatchEvent(new CustomEvent(eventIdHost, {__proto: null, detail: data}));
|
||||
}
|
||||
|
@ -261,7 +285,7 @@ function inPageContext(eventId, eventIdHost, styleId, apiUrl) {
|
|||
if (ss.setting_type === 'image') {
|
||||
let isListed;
|
||||
for (const opt of ss.style_setting_options) {
|
||||
isListed |= opt.default = (opt.value === value);
|
||||
isListed |= opt.default = (opt.install_key === value);
|
||||
}
|
||||
images.set(ik, {url: isNew && !isListed ? vars[`${ik}-custom`].value : value, isListed});
|
||||
} else if (value.startsWith('ik-') || isNew && vars[ik].type === 'select') {
|
||||
|
|
|
@ -338,6 +338,12 @@
|
|||
<svg class="svg-icon checked"><use xlink:href="#svg-icon-checked"/></svg>
|
||||
</label>
|
||||
</div>
|
||||
<div class="option sectioned-only">
|
||||
<label i18n="cm_arrowKeysTraverse">
|
||||
<input id="editor.arrowKeysTraverse" type="checkbox">
|
||||
<svg class="svg-icon checked"><use xlink:href="#svg-icon-checked"/></svg>
|
||||
</label>
|
||||
</div>
|
||||
<div class="option">
|
||||
<label i18n="cm_colorpicker">
|
||||
<input id="editor.colorpicker" type="checkbox">
|
||||
|
|
|
@ -12,7 +12,7 @@
|
|||
const USO_VAR = 'uso-variable';
|
||||
const USO_VALID_VAR = 'variable-3 ' + USO_VAR;
|
||||
const USO_INVALID_VAR = 'error ' + USO_VAR;
|
||||
const rxPROP = /^(prop(erty)?|variable-2)\b/;
|
||||
const rxPROP = /^(prop(erty)?|variable-2|string-2)\b/;
|
||||
const rxVAR = /(^|[^-.\w\u0080-\uFFFF])var\(/iyu;
|
||||
const rxCONSUME = /([-\w]*\s*:\s?)?/yu;
|
||||
const cssMime = CodeMirror.mimeModes['text/css'];
|
||||
|
@ -41,6 +41,7 @@
|
|||
const {line, ch} = pos;
|
||||
const {styles, text} = cm.getLineHandle(line);
|
||||
const {style, index} = cm.getStyleAtPos({styles, pos: ch}) || {};
|
||||
const isLessLang = cm.doc.mode.helperType === 'less';
|
||||
const isStylusLang = cm.doc.mode.name === 'stylus';
|
||||
const type = style && style.split(' ', 1)[0] || 'prop?';
|
||||
if (!type || type === 'comment' || type === 'string') {
|
||||
|
@ -86,6 +87,7 @@
|
|||
'@supports',
|
||||
'@viewport',
|
||||
];
|
||||
if (isLessLang) list = findAllCssVars(cm, left, '\\s*:').concat(list);
|
||||
break;
|
||||
|
||||
case '#': // prevents autocomplete for #hex colors
|
||||
|
@ -142,16 +144,16 @@
|
|||
leftLC = leftLC.replace(/^[^\w\s]\s*/, '');
|
||||
}
|
||||
if (prop.startsWith('--')) prop = 'color'; // assuming 90% of variables are colors
|
||||
if (!cssValues) cssValues = await linterMan.worker.getCssPropsValues();
|
||||
list = [...new Set([...cssValues.own[prop] || [], ...cssValues.global])];
|
||||
if (!cssProps) await initCssProps();
|
||||
list = [...new Set([...cssValues.all[prop] || [], ...cssValues.global])];
|
||||
end = prev + execAt(/(\s*[-a-z(]+)?/y, prev, text)[0].length;
|
||||
}
|
||||
}
|
||||
// properties and media features
|
||||
if (!list &&
|
||||
/^(prop(erty|\?)|atom|error)/.test(type) &&
|
||||
/^(prop(erty|\?)|atom|error|tag)/.test(type) &&
|
||||
/^(block|atBlock_parens|maybeprop)/.test(getTokenState())) {
|
||||
if (!cssProps) initCssProps();
|
||||
if (!cssProps) await initCssProps();
|
||||
if (type === 'prop?') {
|
||||
prev += leftLC.length;
|
||||
leftLC = '';
|
||||
|
@ -174,8 +176,9 @@
|
|||
};
|
||||
}
|
||||
|
||||
function initCssProps() {
|
||||
cssProps = addSuffix(cssMime.propertyKeywords);
|
||||
async function initCssProps() {
|
||||
cssValues = await linterMan.worker.getCssPropsValues();
|
||||
cssProps = addSuffix(cssValues.all);
|
||||
cssMedia = [].concat(...Object.entries(cssMime).map(getMediaKeys).filter(Boolean)).sort();
|
||||
}
|
||||
|
||||
|
@ -195,13 +198,15 @@
|
|||
!style.startsWith(USO_VALID_VAR) && !style.startsWith(USO_INVALID_VAR);
|
||||
}
|
||||
|
||||
function findAllCssVars(cm, leftPart) {
|
||||
function findAllCssVars(cm, leftPart, rightPart = '') {
|
||||
// simplified regex without CSS escapes
|
||||
const [, prefixed, named] = leftPart.match(/^(--|@)?(\S)?/);
|
||||
const rx = new RegExp(
|
||||
'(?:^|[\\s/;{])(' +
|
||||
(leftPart.startsWith('--') ? leftPart : '--') +
|
||||
(leftPart.length <= 2 ? '[a-zA-Z_\u0080-\uFFFF]' : '') +
|
||||
'[-0-9a-zA-Z_\u0080-\uFFFF]*)',
|
||||
(prefixed ? leftPart : '--') +
|
||||
(named ? '' : '[a-zA-Z_\u0080-\uFFFF]') +
|
||||
'[-0-9a-zA-Z_\u0080-\uFFFF]*)' +
|
||||
rightPart,
|
||||
'g');
|
||||
const list = new Set();
|
||||
cm.eachLine(({text}) => {
|
||||
|
|
13
edit/base.js
13
edit/base.js
|
@ -1,4 +1,4 @@
|
|||
/* global $$ $ $create messageBoxProxy setupLivePrefs */// dom.js
|
||||
/* global $$ $ $create messageBoxProxy setInputValue setupLivePrefs */// dom.js
|
||||
/* global API */// msg.js
|
||||
/* global CODEMIRROR_THEMES */
|
||||
/* global CodeMirror */
|
||||
|
@ -136,15 +136,8 @@ function EditorHeader() {
|
|||
});
|
||||
resetEl.hidden = !editor.style.customName;
|
||||
resetEl.onclick = () => {
|
||||
const {style} = editor;
|
||||
nameEl.focus();
|
||||
nameEl.select();
|
||||
// trying to make it undoable via Ctrl-Z
|
||||
if (!document.execCommand('insertText', false, style.name)) {
|
||||
nameEl.value = style.name;
|
||||
editor.updateName(true);
|
||||
}
|
||||
style.customName = null; // to delete it from db
|
||||
editor.style.customName = null; // to delete it from db
|
||||
setInputValue(nameEl, editor.style.name);
|
||||
resetEl.hidden = true;
|
||||
};
|
||||
const enabledEl = $('#enabled');
|
||||
|
|
|
@ -102,6 +102,7 @@
|
|||
const m = this.doc.mode;
|
||||
if (force || (m.helperType ? m.helperType !== pp : m.name !== name)) {
|
||||
this.setOption('mode', name);
|
||||
this.doc.mode.lineComment = ''; // stylelint chokes on line comments a lot
|
||||
}
|
||||
},
|
||||
/** Superfast GC-friendly check that runs until the first non-space line */
|
||||
|
|
|
@ -67,6 +67,7 @@
|
|||
k.slice('editor.'.length);
|
||||
const prefKeys = prefs.knownKeys.filter(k =>
|
||||
k !== 'editor.colorpicker' && // handled in colorpicker-helper.js
|
||||
k !== 'editor.arrowKeysTraverse' && // handled in sections-editor.js
|
||||
prefToCmOpt(k) in CodeMirror.defaults);
|
||||
const {insertTab, insertSoftTab} = CodeMirror.commands;
|
||||
|
||||
|
|
|
@ -75,8 +75,8 @@ html:not(.is-new-style) #heading::before {
|
|||
right: 24px;
|
||||
}
|
||||
/************ checkbox & select************/
|
||||
.options-column > div[class="option"] {
|
||||
margin-bottom: 4px;
|
||||
.options-column > .option {
|
||||
margin-bottom: .25rem;
|
||||
}
|
||||
|
||||
.options-column > .usercss-only {
|
||||
|
@ -107,6 +107,8 @@ label {
|
|||
}
|
||||
#sections {
|
||||
padding-left: var(--header-width);
|
||||
}
|
||||
.usercss #sections {
|
||||
min-height: 0;
|
||||
height: 100%;
|
||||
}
|
||||
|
|
12
edit/edit.js
12
edit/edit.js
|
@ -4,7 +4,7 @@
|
|||
/* global SectionsEditor */
|
||||
/* global SourceEditor */
|
||||
/* global clipString createHotkeyInput helpPopup */// util.js
|
||||
/* global closeCurrentTab deepEqual sessionStore tryJSONparse */// toolbox.js
|
||||
/* global closeCurrentTab deepEqual mapObj sessionStore tryJSONparse */// toolbox.js
|
||||
/* global cmFactory */
|
||||
/* global editor EditorHeader */// base.js
|
||||
/* global linterMan */
|
||||
|
@ -207,11 +207,10 @@ function EditorMethods() {
|
|||
applyScrollInfo(cm, si = (editor.scrollInfo.cms || [])[0]) {
|
||||
if (si && si.sel) {
|
||||
const bmOpts = {sublimeBookmark: true, clearWhenEmpty: false}; // copied from sublime.js
|
||||
cm.operation(() => {
|
||||
cm.setSelections(...si.sel, {scroll: false});
|
||||
cm.scrollIntoView(cm.getCursor(), si.parentHeight / 2);
|
||||
cm.state.sublimeBookmarks = si.bookmarks.map(b => cm.markText(b.from, b.to, bmOpts));
|
||||
});
|
||||
cm.setSelections(...si.sel, {scroll: false});
|
||||
cm.state.sublimeBookmarks = si.bookmarks.map(b => cm.markText(b.from, b.to, bmOpts));
|
||||
Object.assign(cm.display.scroller, si.scroll); // for source editor
|
||||
Object.assign(cm.doc, si.scroll); // for sectioned editor
|
||||
}
|
||||
},
|
||||
|
||||
|
@ -223,6 +222,7 @@ function EditorMethods() {
|
|||
focus: cm.hasFocus(),
|
||||
height: cm.display.wrapper.style.height.replace('100vh', ''),
|
||||
parentHeight: cm.display.wrapper.parentElement.offsetHeight,
|
||||
scroll: mapObj(cm.doc, null, ['scrollLeft', 'scrollTop']),
|
||||
sel: [cm.doc.sel.ranges, cm.doc.sel.primIndex],
|
||||
})),
|
||||
};
|
||||
|
|
|
@ -26,6 +26,7 @@
|
|||
// moving vendor-prefixed props to the end
|
||||
const cmp = (a, b) => a[0] === '-' && b[0] !== '-' ? 1 : a < b ? -1 : a > b;
|
||||
for (const [k, v] of Object.entries(Properties)) {
|
||||
res[k] = false;
|
||||
if (typeof v === 'string') {
|
||||
let last = '';
|
||||
const uniq = [];
|
||||
|
@ -42,7 +43,7 @@
|
|||
if (uniq.length) res[k] = uniq;
|
||||
}
|
||||
}
|
||||
return {own: res, global: GlobalKeywords};
|
||||
return {all: res, global: GlobalKeywords};
|
||||
},
|
||||
|
||||
getRules(linter) {
|
||||
|
@ -78,8 +79,8 @@
|
|||
return collectStylelintResults(res, opts);
|
||||
} catch (e) {
|
||||
const fatal = pass === -1 ||
|
||||
!pass && !/^CssSyntaxError:.+?Unnecessary curly bracket/.test(e.stack) ||
|
||||
pass && !/^CssSyntaxError:.+?Unknown word[\s\S]*?\.decl\s/.test(e.stack);
|
||||
!pass && !/^CssSyntaxError:.+?Unnecessary curly bracket/.test(e) ||
|
||||
pass && !/^CssSyntaxError:.+?Unknown word[\s\S]*?\.decl\s/.test(`${e}${e.stack}`);
|
||||
if (fatal) {
|
||||
return [{
|
||||
from: {line: e.line - 1, ch: e.column - 1},
|
||||
|
@ -149,7 +150,8 @@
|
|||
/* We hide nonfatal "//" warnings since we lint with sugarss without applying @preprocessor.
|
||||
* We can't easily pre-remove "//" comments which may be inside strings, comments, url(), etc.
|
||||
* And even if we did, it'd be wrong to hide potential bugs in stylus-lang like #1460 */
|
||||
const slashCommentAllowed = mode === 'stylus' || mode === 'text/x-less';
|
||||
const isLess = mode === 'text/x-less';
|
||||
const slashCommentAllowed = isLess || mode === 'stylus';
|
||||
const res = [];
|
||||
for (const m of messages) {
|
||||
if (/deprecation|invalidOption/.test(m.stylelintType)) {
|
||||
|
@ -157,10 +159,8 @@
|
|||
}
|
||||
const {rule} = m;
|
||||
const msg = m.text.replace(/^Unexpected\s+/, '').replace(` (${rule})`, '');
|
||||
if (slashCommentAllowed && (
|
||||
rule === 'no-invalid-double-slash-comments' ||
|
||||
rule === 'property-no-unknown' && msg.includes('"//"')
|
||||
)) {
|
||||
if (slashCommentAllowed && msg.includes('"//"') ||
|
||||
isLess && /^unknown at-rule "@[-\w]+:"/.test(msg) /* LESS variables */) {
|
||||
continue;
|
||||
}
|
||||
res.push({
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
/* global $ $$ $create $remove focusAccessibility toggleDataset */// dom.js
|
||||
/* global $ $$ $create $remove focusAccessibility setInputValue toggleDataset */// dom.js
|
||||
/* global CodeMirror */
|
||||
/* global chromeLocal */// storage-util.js
|
||||
/* global colorMimicry */
|
||||
|
@ -930,18 +930,5 @@
|
|||
})));
|
||||
}
|
||||
|
||||
|
||||
function setInputValue(input, value) {
|
||||
input.focus();
|
||||
input.select();
|
||||
// using execCommand to add to the input's undo history
|
||||
document.execCommand(value ? 'insertText' : 'delete', false, value);
|
||||
// some versions of Firefox ignore execCommand
|
||||
if (input.value !== value) {
|
||||
input.value = value;
|
||||
input.dispatchEvent(new Event('input', {bubbles: true}));
|
||||
}
|
||||
}
|
||||
|
||||
//endregion
|
||||
})();
|
||||
|
|
|
@ -31,6 +31,7 @@ function createSection(originalSection, genId, si) {
|
|||
value: originalSection.code,
|
||||
});
|
||||
el.CodeMirror = cm; // used by getAssociatedEditor
|
||||
cm.el = el;
|
||||
editor.applyScrollInfo(cm, si);
|
||||
|
||||
const changeListeners = new Set();
|
||||
|
@ -114,49 +115,10 @@ function createSection(originalSection, genId, si) {
|
|||
changeGeneration = newGeneration;
|
||||
emitSectionChange('code');
|
||||
});
|
||||
cm.display.wrapper.on('keydown', event => handleKeydown(cm, event), true);
|
||||
$('.test-regexp', el).onclick = () => updateRegexpTester(true);
|
||||
initBeautifyButton($('.beautify-section', el), [cm]);
|
||||
}
|
||||
|
||||
function handleKeydown(cm, event) {
|
||||
if (event.shiftKey || event.altKey || event.metaKey) {
|
||||
return;
|
||||
}
|
||||
const {key} = event;
|
||||
const {line, ch} = cm.getCursor();
|
||||
switch (key) {
|
||||
case 'ArrowLeft':
|
||||
if (line || ch) {
|
||||
return;
|
||||
}
|
||||
// fallthrough
|
||||
case 'ArrowUp':
|
||||
cm = line === 0 && editor.prevEditor(cm, false);
|
||||
if (!cm) {
|
||||
return;
|
||||
}
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
cm.setCursor(cm.doc.size - 1, key === 'ArrowLeft' ? 1e20 : ch);
|
||||
break;
|
||||
case 'ArrowRight':
|
||||
if (line < cm.doc.size - 1 || ch < cm.getLine(line).length - 1) {
|
||||
return;
|
||||
}
|
||||
// fallthrough
|
||||
case 'ArrowDown':
|
||||
cm = line === cm.doc.size - 1 && editor.nextEditor(cm, false);
|
||||
if (!cm) {
|
||||
return;
|
||||
}
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
cm.setCursor(0, 0);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
async function updateRegexpTester(toggle) {
|
||||
const isLoaded = typeof regexpTester === 'object' ||
|
||||
toggle && await require(['/edit/regexp-tester']); /* global regexpTester */
|
||||
|
|
|
@ -6,6 +6,7 @@
|
|||
/* global createSection */// sections-editor-section.js
|
||||
/* global editor */
|
||||
/* global linterMan */
|
||||
/* global prefs */
|
||||
/* global styleSectionsEqual */ // sections-util.js
|
||||
/* global t */// localization.js
|
||||
'use strict';
|
||||
|
@ -21,6 +22,7 @@ function SectionsEditor() {
|
|||
let sectionOrder = '';
|
||||
let headerOffset; // in compact mode the header is at the top so it reduces the available height
|
||||
let cmExtrasHeight; // resize grip + borders
|
||||
let upDownJumps;
|
||||
|
||||
updateMeta();
|
||||
rerouteHotkeys.toggle(true); // enabled initially because we don't always focus a CodeMirror
|
||||
|
@ -30,6 +32,10 @@ function SectionsEditor() {
|
|||
$('#from-mozilla').on('click', () => showMozillaFormatImport());
|
||||
document.on('wheel', scrollEntirePageOnCtrlShift, {passive: false});
|
||||
CodeMirror.defaults.extraKeys['Shift-Ctrl-Wheel'] = 'scrollWindow';
|
||||
prefs.subscribe('editor.arrowKeysTraverse', (_, val) => {
|
||||
for (const {cm} of sections) handleKeydownSetup(cm, val);
|
||||
upDownJumps = val;
|
||||
}, {runNow: true});
|
||||
|
||||
/** @namespace Editor */
|
||||
Object.assign(editor, {
|
||||
|
@ -70,15 +76,15 @@ function SectionsEditor() {
|
|||
}
|
||||
},
|
||||
|
||||
nextEditor(cm, cycle = true) {
|
||||
return cycle || cm !== findLast(sections, s => !s.removed).cm
|
||||
? nextPrevEditor(cm, 1)
|
||||
nextEditor(cm, upDown) {
|
||||
return !upDown || cm !== findLast(sections, s => !s.removed).cm
|
||||
? nextPrevEditor(cm, 1, upDown)
|
||||
: null;
|
||||
},
|
||||
|
||||
prevEditor(cm, cycle = true) {
|
||||
return cycle || cm !== sections.find(s => !s.removed).cm
|
||||
? nextPrevEditor(cm, -1)
|
||||
prevEditor(cm, upDown) {
|
||||
return !upDown || cm !== sections.find(s => !s.removed).cm
|
||||
? nextPrevEditor(cm, -1, upDown)
|
||||
: null;
|
||||
},
|
||||
|
||||
|
@ -112,14 +118,16 @@ function SectionsEditor() {
|
|||
editor.useSavedStyle(newStyle);
|
||||
},
|
||||
|
||||
scrollToEditor(cm) {
|
||||
const {el} = sections.find(s => s.cm === cm);
|
||||
const r = el.getBoundingClientRect();
|
||||
const h = window.innerHeight;
|
||||
if (r.bottom > h && r.top > 0 ||
|
||||
r.bottom < h && r.top < 0) {
|
||||
window.scrollBy(0, (r.top + r.bottom - h) / 2 | 0);
|
||||
}
|
||||
scrollToEditor(cm, partial) {
|
||||
const cc = partial && cm.cursorCoords(true, 'window');
|
||||
const {top: y1, bottom: y2} = cm.el.getBoundingClientRect();
|
||||
const rc = container.getBoundingClientRect();
|
||||
const rcY1 = Math.max(rc.top, 0);
|
||||
const rcY2 = Math.min(rc.bottom, innerHeight);
|
||||
const bad = partial
|
||||
? cc.top < rcY1 || cc.top > rcY2 - 30
|
||||
: y1 >= rcY1 ^ y2 <= rcY2;
|
||||
if (bad) window.scrollBy(0, (y1 + y2 - rcY2 + rcY1) / 2 | 0);
|
||||
},
|
||||
});
|
||||
|
||||
|
@ -291,10 +299,36 @@ function SectionsEditor() {
|
|||
}
|
||||
}
|
||||
|
||||
function nextPrevEditor(cm, direction) {
|
||||
function handleKeydown(event) {
|
||||
if (event.shiftKey || event.altKey || event.metaKey ||
|
||||
event.key !== 'ArrowUp' && event.key !== 'ArrowDown') {
|
||||
return;
|
||||
}
|
||||
let pos;
|
||||
let cm = this.CodeMirror;
|
||||
const {line, ch} = cm.getCursor();
|
||||
if (event.key === 'ArrowUp') {
|
||||
cm = line === 0 && editor.prevEditor(cm, true);
|
||||
pos = cm && [cm.doc.size - 1, ch];
|
||||
} else {
|
||||
cm = line === cm.doc.size - 1 && editor.nextEditor(cm, true);
|
||||
pos = cm && [0, 0];
|
||||
}
|
||||
if (cm) {
|
||||
cm.setCursor(...pos);
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
}
|
||||
}
|
||||
|
||||
function handleKeydownSetup(cm, state) {
|
||||
cm.display.wrapper[state ? 'on' : 'off']('keydown', handleKeydown, true);
|
||||
}
|
||||
|
||||
function nextPrevEditor(cm, direction, upDown) {
|
||||
const editors = editor.getEditors();
|
||||
cm = editors[(editors.indexOf(cm) + direction + editors.length) % editors.length];
|
||||
editor.scrollToEditor(cm);
|
||||
editor.scrollToEditor(cm, upDown);
|
||||
cm.focus();
|
||||
return cm;
|
||||
}
|
||||
|
@ -577,6 +611,9 @@ function SectionsEditor() {
|
|||
cm.focus();
|
||||
editor.scrollToEditor(cm);
|
||||
}
|
||||
if (upDownJumps) {
|
||||
handleKeydownSetup(cm, true);
|
||||
}
|
||||
updateSectionOrder();
|
||||
updateLivePreview();
|
||||
section.onChange(updateLivePreview);
|
||||
|
|
|
@ -101,7 +101,6 @@ async function SourceEditor() {
|
|||
'editor.toc.expanded': (k, val) => sectionFinder.onOff(editor.updateToc, val),
|
||||
}, {runNow: true});
|
||||
|
||||
editor.applyScrollInfo(cm);
|
||||
cm.clearHistory();
|
||||
cm.markClean();
|
||||
savedGeneration = cm.changeGeneration();
|
||||
|
@ -121,6 +120,7 @@ async function SourceEditor() {
|
|||
if (!$isTextInput(document.activeElement)) {
|
||||
cm.focus();
|
||||
}
|
||||
editor.applyScrollInfo(cm); // WARNING! Place it after all cm.XXX calls that change scroll pos
|
||||
|
||||
async function preprocess(style) {
|
||||
const res = await API.usercss.build({
|
||||
|
|
|
@ -37,7 +37,7 @@ async function InjectionOrder(show, el, selector) {
|
|||
parts.name.href = '/edit.html?id=' + style.id;
|
||||
parts.name.textContent = style.name;
|
||||
return Object.assign(entry.cloneNode(true), {
|
||||
styleNameLowerCase: style.name.toLocaleLowerCase(),
|
||||
styleNameLC: style.name.toLocaleLowerCase(),
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
@ -1,32 +1,23 @@
|
|||
'use strict';
|
||||
|
||||
const colorConverter = (() => {
|
||||
const colorConverter = (NAMED_COLORS => {
|
||||
|
||||
const RXS_NUM = /\s*([+-]?(?:\d+\.?\d*|\d*\.\d+))(?:e[+-]?\d+)?/.source;
|
||||
const RXS_NUM_ANGLE = `${RXS_NUM}(deg|g?rad|turn)?`;
|
||||
// All groups in RXS_NUM must use ?: in order to enable \1 in RX_COLOR.rgb
|
||||
const RXS_NUM = /\s*[+-]?(\.\d+|\d+(\.\d*)?)(e[+-]?\d+)?/.source.replace(/\(/g, '(?:');
|
||||
const RXS_ANGLE = '(?:deg|g?rad|turn)?';
|
||||
const expandRe = re => RegExp(re.source.replace(/N/g, RXS_NUM).replace(/A/g, RXS_ANGLE), 'iy');
|
||||
const RX_COLOR = {
|
||||
hex: /#([a-f\d]{3}(?:[a-f\d](?:[a-f\d]{2}){0,2})?)\b/iy,
|
||||
|
||||
hsl: new RegExp([
|
||||
// num_or_angle, pct, pct [ , num_or_pct]?
|
||||
`^(${RXS_NUM_ANGLE})\\s*,(${RXS_NUM}%\\s*(,|$)){2}(${RXS_NUM}%?)?\\s*$`,
|
||||
// num_or_angle pct pct [ / num_or_pct]?
|
||||
`^(${RXS_NUM_ANGLE})\\s+(${RXS_NUM}%\\s*(\\s|$)){2}(/${RXS_NUM}%?)?\\s*$`,
|
||||
].join('|'), 'iy'),
|
||||
|
||||
hwb: new RegExp(
|
||||
// num|angle|none pct|none pct|none [ / num|pct|none ]?
|
||||
`^(${RXS_NUM_ANGLE}|none)(\\s+(${RXS_NUM}%|none)){2}(\\s+|$)(/${RXS_NUM}%?|none)?\\s*$`,
|
||||
'iy'),
|
||||
|
||||
rgb: new RegExp([
|
||||
// num, num, num [ , num_or_pct]?
|
||||
// pct, pct, pct [ , num_or_pct]?
|
||||
`^((${RXS_NUM}\\s*(,|$)){3}|(${RXS_NUM}%\\s*(,|$)){3})(${RXS_NUM}%?)?\\s*$`,
|
||||
// num num num [ / num_or_pct]?
|
||||
// pct pct pct [ / num_or_pct]?
|
||||
`^((${RXS_NUM}\\s*(\\s|$)){3}|(${RXS_NUM}%\\s*(\\s|$)){3})(/${RXS_NUM}%?)?\\s*$`,
|
||||
].join('|'), 'iy'),
|
||||
// num_or_angle, pct, pct [ , num_or_pct]?
|
||||
// num_or_angle pct pct [ / num_or_pct]?
|
||||
hsl: expandRe(/^NA(\s*(,N%\s*){2}(,N%?\s*)?|(\s+N%){2}\s*(\/N%?\s*)?)$/),
|
||||
// num_or_angle|none pct|none pct|none [ / num_or_pct|none ]?
|
||||
hwb: expandRe(/^(NA|none)(\s+(N%|none)){2}\s*(\/(N%?|none)\s*)?$/),
|
||||
// num, num, num [ , num_or_pct]?
|
||||
// pct, pct, pct [ , num_or_pct]?
|
||||
// num num num [ / num_or_pct]?
|
||||
// pct pct pct [ / num_or_pct]?
|
||||
rgb: expandRe(/^N(%?)(\s*,N\1\s*,N\1\s*(,N%?\s*)?|\s+N\1\s+N\1\s*(\/N%?\s*)?)$/),
|
||||
};
|
||||
const ANGLE_TO_DEG = {
|
||||
grad: 360 / 400,
|
||||
|
@ -51,6 +42,7 @@ const colorConverter = (() => {
|
|||
'v' in c ? 'hsv' :
|
||||
'l' in c ? 'hsl' :
|
||||
undefined;
|
||||
let HEX;
|
||||
|
||||
return {
|
||||
parse,
|
||||
|
@ -64,8 +56,8 @@ const colorConverter = (() => {
|
|||
snapToInt,
|
||||
testAt,
|
||||
ALPHA_DIGITS: 3,
|
||||
NAMED_COLORS,
|
||||
RX_COLOR,
|
||||
// NAMED_COLORS is added below
|
||||
};
|
||||
|
||||
function format(color = '', type = color.type, {hexUppercase, usoMode, round} = {}) {
|
||||
|
@ -95,57 +87,71 @@ const colorConverter = (() => {
|
|||
}
|
||||
}
|
||||
|
||||
function parse(str) {
|
||||
if (typeof str !== 'string') return;
|
||||
str = str.trim().toLowerCase();
|
||||
if (!str) return;
|
||||
|
||||
if (str[0] !== '#' && !str.includes('(')) {
|
||||
// eslint-disable-next-line no-use-before-define
|
||||
str = colorConverter.NAMED_COLORS.get(str);
|
||||
if (!str) return;
|
||||
function parse(s) {
|
||||
if (typeof s !== 'string' || !(s = s.trim())) {
|
||||
return;
|
||||
} else if (s[0] === '#') {
|
||||
return parseHex(s);
|
||||
} else if (s.endsWith(')') && (s = s.match(/^(hwb|(hsl|rgb)a?)\(\s*([^)]+)/i))) {
|
||||
return parseFunc((s[2] || s[1]).toLowerCase(), s[3]);
|
||||
} else {
|
||||
return colorConverter.NAMED_COLORS.get(s.toLowerCase());
|
||||
}
|
||||
}
|
||||
|
||||
if (str[0] === '#') {
|
||||
if (!testAt(RX_COLOR.hex, 0, str)) {
|
||||
return;
|
||||
}
|
||||
str = str.slice(1);
|
||||
const [r, g, b, a = 255] = str.length <= 4 ?
|
||||
str.match(/(.)/g).map(c => parseInt(c + c, 16)) :
|
||||
str.match(/(..)/g).map(c => parseInt(c, 16));
|
||||
return {
|
||||
type: 'hex',
|
||||
r,
|
||||
g,
|
||||
b,
|
||||
a: a === 255 ? undefined : a / 255,
|
||||
};
|
||||
function initHexMap() {
|
||||
HEX = Array(256).fill(-0xFFFF); // ensuring a PACKED_SMI array
|
||||
for (let i = 48; i < 58; i++) HEX[i] = i - 48; // 0123456789
|
||||
for (let i = 65; i < 71; i++) HEX[i] = i - 65 + 10; // ABCDEF
|
||||
for (let i = 97; i < 103; i++) HEX[i] = i - 97 + 10; // abcdef
|
||||
}
|
||||
|
||||
function parseHex(str) {
|
||||
if (!HEX) initHexMap();
|
||||
let r, g, b, a;
|
||||
const len = str.length;
|
||||
if (len === 4 || len === 5
|
||||
? (r = HEX[str.charCodeAt(1)] * 0x11) >= 0 &&
|
||||
(g = HEX[str.charCodeAt(2)] * 0x11) >= 0 &&
|
||||
(b = HEX[str.charCodeAt(3)] * 0x11) >= 0 &&
|
||||
(len < 5 || (a = HEX[str.charCodeAt(4)] * 0x11 / 255) >= 0)
|
||||
: (len === 7 || len === 9) &&
|
||||
(r = HEX[str.charCodeAt(1)] * 0x10 + HEX[str.charCodeAt(2)]) >= 0 &&
|
||||
(g = HEX[str.charCodeAt(3)] * 0x10 + HEX[str.charCodeAt(4)]) >= 0 &&
|
||||
(b = HEX[str.charCodeAt(5)] * 0x10 + HEX[str.charCodeAt(6)]) >= 0 &&
|
||||
(len < 9 || (a = (HEX[str.charCodeAt(7)] * 0x10 + HEX[str.charCodeAt(8)]) / 255) >= 0)
|
||||
) {
|
||||
return {type: 'hex', r, g, b, a};
|
||||
}
|
||||
}
|
||||
|
||||
const [, func, type = func, value] = str.match(/^((rgb|hsl)a?|hwb)\(\s*(.*?)\s*\)|$/);
|
||||
if (!func || !testAt(RX_COLOR[type], 0, value)) {
|
||||
function parseFunc(type, val) {
|
||||
if (!testAt(RX_COLOR[type], 0, val)) {
|
||||
return;
|
||||
}
|
||||
const [s1, s2, s3, sA] = value.split(/\s*[,/]\s*|\s+/);
|
||||
const a = isNaN(sA) ? 1 : constrain(0, 1, sA / (sA.endsWith('%') ? 100 : 1));
|
||||
|
||||
// Not using destructuring because it's slow
|
||||
const parts = val.trim().split(/\s*[,/]\s*|\s+/);
|
||||
const n1 = parseFloat(parts[0]);
|
||||
const n2 = parseFloat(parts[1]);
|
||||
const n3 = parseFloat(parts[2]);
|
||||
const nA = parseFloat(parts[3]);
|
||||
const a = isNaN(nA) ? undefined : constrain(0, 1, parts[3].endsWith('%') ? nA / 100 : nA);
|
||||
if (type === 'rgb') {
|
||||
const k = s1.endsWith('%') ? 2.55 : 1;
|
||||
const k = parts[0].endsWith('%') ? 2.55 : 1;
|
||||
return {
|
||||
type,
|
||||
r: constrain(0, 255, Math.round(s1 * k)),
|
||||
g: constrain(0, 255, Math.round(s2 * k)),
|
||||
b: constrain(0, 255, Math.round(s3 * k)),
|
||||
r: constrain(0, 255, Math.round(n1 * k)),
|
||||
g: constrain(0, 255, Math.round(n2 * k)),
|
||||
b: constrain(0, 255, Math.round(n3 * k)),
|
||||
a,
|
||||
};
|
||||
}
|
||||
const h = constrainHue(parseFloat(s1) * (ANGLE_TO_DEG[s1.match(/\D*$/)[0]] || 1));
|
||||
const n2 = constrain(0, 100, parseFloat(s2) || 0);
|
||||
const n3 = constrain(0, 100, parseFloat(s3) || 0);
|
||||
const h = constrainHue(n1 * (ANGLE_TO_DEG[parts[0].match(/\D*$/)[0].toLowerCase()] || 1));
|
||||
const n2c = constrain(0, 100, n2 || 0);
|
||||
const n3c = constrain(0, 100, n3 || 0);
|
||||
return type === 'hwb'
|
||||
? {type, h, w: n2, b: n3, a}
|
||||
: {type, h, s: n2, l: n3, a};
|
||||
? {type, h, w: n2c, b: n3c, a}
|
||||
: {type, h, s: n2c, l: n3c, a};
|
||||
}
|
||||
|
||||
function formatAlpha(a) {
|
||||
|
@ -265,157 +271,154 @@ const colorConverter = (() => {
|
|||
rx.lastIndex = index;
|
||||
return rx.test(text);
|
||||
}
|
||||
})();
|
||||
|
||||
colorConverter.NAMED_COLORS = new Map([
|
||||
['transparent', 'rgba(0, 0, 0, 0)'],
|
||||
// CSS4 named colors
|
||||
['aliceblue', '#f0f8ff'],
|
||||
['antiquewhite', '#faebd7'],
|
||||
['aqua', '#00ffff'],
|
||||
['aquamarine', '#7fffd4'],
|
||||
['azure', '#f0ffff'],
|
||||
['beige', '#f5f5dc'],
|
||||
['bisque', '#ffe4c4'],
|
||||
['black', '#000000'],
|
||||
['blanchedalmond', '#ffebcd'],
|
||||
['blue', '#0000ff'],
|
||||
['blueviolet', '#8a2be2'],
|
||||
['brown', '#a52a2a'],
|
||||
['burlywood', '#deb887'],
|
||||
['cadetblue', '#5f9ea0'],
|
||||
['chartreuse', '#7fff00'],
|
||||
['chocolate', '#d2691e'],
|
||||
['coral', '#ff7f50'],
|
||||
['cornflowerblue', '#6495ed'],
|
||||
['cornsilk', '#fff8dc'],
|
||||
['crimson', '#dc143c'],
|
||||
['cyan', '#00ffff'],
|
||||
['darkblue', '#00008b'],
|
||||
['darkcyan', '#008b8b'],
|
||||
['darkgoldenrod', '#b8860b'],
|
||||
['darkgray', '#a9a9a9'],
|
||||
['darkgrey', '#a9a9a9'],
|
||||
['darkgreen', '#006400'],
|
||||
['darkkhaki', '#bdb76b'],
|
||||
['darkmagenta', '#8b008b'],
|
||||
['darkolivegreen', '#556b2f'],
|
||||
['darkorange', '#ff8c00'],
|
||||
['darkorchid', '#9932cc'],
|
||||
['darkred', '#8b0000'],
|
||||
['darksalmon', '#e9967a'],
|
||||
['darkseagreen', '#8fbc8f'],
|
||||
['darkslateblue', '#483d8b'],
|
||||
['darkslategray', '#2f4f4f'],
|
||||
['darkslategrey', '#2f4f4f'],
|
||||
['darkturquoise', '#00ced1'],
|
||||
['darkviolet', '#9400d3'],
|
||||
['deeppink', '#ff1493'],
|
||||
['deepskyblue', '#00bfff'],
|
||||
['dimgray', '#696969'],
|
||||
['dimgrey', '#696969'],
|
||||
['dodgerblue', '#1e90ff'],
|
||||
['firebrick', '#b22222'],
|
||||
['floralwhite', '#fffaf0'],
|
||||
['forestgreen', '#228b22'],
|
||||
['fuchsia', '#ff00ff'],
|
||||
['gainsboro', '#dcdcdc'],
|
||||
['ghostwhite', '#f8f8ff'],
|
||||
['gold', '#ffd700'],
|
||||
['goldenrod', '#daa520'],
|
||||
['gray', '#808080'],
|
||||
['grey', '#808080'],
|
||||
['green', '#008000'],
|
||||
['greenyellow', '#adff2f'],
|
||||
['honeydew', '#f0fff0'],
|
||||
['hotpink', '#ff69b4'],
|
||||
['indianred', '#cd5c5c'],
|
||||
['indigo', '#4b0082'],
|
||||
['ivory', '#fffff0'],
|
||||
['khaki', '#f0e68c'],
|
||||
['lavender', '#e6e6fa'],
|
||||
['lavenderblush', '#fff0f5'],
|
||||
['lawngreen', '#7cfc00'],
|
||||
['lemonchiffon', '#fffacd'],
|
||||
['lightblue', '#add8e6'],
|
||||
['lightcoral', '#f08080'],
|
||||
['lightcyan', '#e0ffff'],
|
||||
['lightgoldenrodyellow', '#fafad2'],
|
||||
['lightgray', '#d3d3d3'],
|
||||
['lightgrey', '#d3d3d3'],
|
||||
['lightgreen', '#90ee90'],
|
||||
['lightpink', '#ffb6c1'],
|
||||
['lightsalmon', '#ffa07a'],
|
||||
['lightseagreen', '#20b2aa'],
|
||||
['lightskyblue', '#87cefa'],
|
||||
['lightslategray', '#778899'],
|
||||
['lightslategrey', '#778899'],
|
||||
['lightsteelblue', '#b0c4de'],
|
||||
['lightyellow', '#ffffe0'],
|
||||
['lime', '#00ff00'],
|
||||
['limegreen', '#32cd32'],
|
||||
['linen', '#faf0e6'],
|
||||
['magenta', '#ff00ff'],
|
||||
['maroon', '#800000'],
|
||||
['mediumaquamarine', '#66cdaa'],
|
||||
['mediumblue', '#0000cd'],
|
||||
['mediumorchid', '#ba55d3'],
|
||||
['mediumpurple', '#9370db'],
|
||||
['mediumseagreen', '#3cb371'],
|
||||
['mediumslateblue', '#7b68ee'],
|
||||
['mediumspringgreen', '#00fa9a'],
|
||||
['mediumturquoise', '#48d1cc'],
|
||||
['mediumvioletred', '#c71585'],
|
||||
['midnightblue', '#191970'],
|
||||
['mintcream', '#f5fffa'],
|
||||
['mistyrose', '#ffe4e1'],
|
||||
['moccasin', '#ffe4b5'],
|
||||
['navajowhite', '#ffdead'],
|
||||
['navy', '#000080'],
|
||||
['oldlace', '#fdf5e6'],
|
||||
['olive', '#808000'],
|
||||
['olivedrab', '#6b8e23'],
|
||||
['orange', '#ffa500'],
|
||||
['orangered', '#ff4500'],
|
||||
['orchid', '#da70d6'],
|
||||
['palegoldenrod', '#eee8aa'],
|
||||
['palegreen', '#98fb98'],
|
||||
['paleturquoise', '#afeeee'],
|
||||
['palevioletred', '#db7093'],
|
||||
['papayawhip', '#ffefd5'],
|
||||
['peachpuff', '#ffdab9'],
|
||||
['peru', '#cd853f'],
|
||||
['pink', '#ffc0cb'],
|
||||
['plum', '#dda0dd'],
|
||||
['powderblue', '#b0e0e6'],
|
||||
['purple', '#800080'],
|
||||
['rebeccapurple', '#663399'],
|
||||
['red', '#ff0000'],
|
||||
['rosybrown', '#bc8f8f'],
|
||||
['royalblue', '#4169e1'],
|
||||
['saddlebrown', '#8b4513'],
|
||||
['salmon', '#fa8072'],
|
||||
['sandybrown', '#f4a460'],
|
||||
['seagreen', '#2e8b57'],
|
||||
['seashell', '#fff5ee'],
|
||||
['sienna', '#a0522d'],
|
||||
['silver', '#c0c0c0'],
|
||||
['skyblue', '#87ceeb'],
|
||||
['slateblue', '#6a5acd'],
|
||||
['slategray', '#708090'],
|
||||
['slategrey', '#708090'],
|
||||
['snow', '#fffafa'],
|
||||
['springgreen', '#00ff7f'],
|
||||
['steelblue', '#4682b4'],
|
||||
['tan', '#d2b48c'],
|
||||
['teal', '#008080'],
|
||||
['thistle', '#d8bfd8'],
|
||||
['tomato', '#ff6347'],
|
||||
['turquoise', '#40e0d0'],
|
||||
['violet', '#ee82ee'],
|
||||
['wheat', '#f5deb3'],
|
||||
['white', '#ffffff'],
|
||||
['whitesmoke', '#f5f5f5'],
|
||||
['yellow', '#ffff00'],
|
||||
['yellowgreen', '#9acd32'],
|
||||
]);
|
||||
})(new Map([
|
||||
['transparent', {r: 0, g: 0, b: 0, a: 0, type: 'rgb'}],
|
||||
['aliceblue', {r: 240, g: 248, b: 255, type: 'hex'}],
|
||||
['antiquewhite', {r: 250, g: 235, b: 215, type: 'hex'}],
|
||||
['aqua', {r: 0, g: 255, b: 255, type: 'hex'}],
|
||||
['aquamarine', {r: 127, g: 255, b: 212, type: 'hex'}],
|
||||
['azure', {r: 240, g: 255, b: 255, type: 'hex'}],
|
||||
['beige', {r: 245, g: 245, b: 220, type: 'hex'}],
|
||||
['bisque', {r: 255, g: 228, b: 196, type: 'hex'}],
|
||||
['black', {r: 0, g: 0, b: 0, type: 'hex'}],
|
||||
['blanchedalmond', {r: 255, g: 235, b: 205, type: 'hex'}],
|
||||
['blue', {r: 0, g: 0, b: 255, type: 'hex'}],
|
||||
['blueviolet', {r: 138, g: 43, b: 226, type: 'hex'}],
|
||||
['brown', {r: 165, g: 42, b: 42, type: 'hex'}],
|
||||
['burlywood', {r: 222, g: 184, b: 135, type: 'hex'}],
|
||||
['cadetblue', {r: 95, g: 158, b: 160, type: 'hex'}],
|
||||
['chartreuse', {r: 127, g: 255, b: 0, type: 'hex'}],
|
||||
['chocolate', {r: 210, g: 105, b: 30, type: 'hex'}],
|
||||
['coral', {r: 255, g: 127, b: 80, type: 'hex'}],
|
||||
['cornflowerblue', {r: 100, g: 149, b: 237, type: 'hex'}],
|
||||
['cornsilk', {r: 255, g: 248, b: 220, type: 'hex'}],
|
||||
['crimson', {r: 220, g: 20, b: 60, type: 'hex'}],
|
||||
['cyan', {r: 0, g: 255, b: 255, type: 'hex'}],
|
||||
['darkblue', {r: 0, g: 0, b: 139, type: 'hex'}],
|
||||
['darkcyan', {r: 0, g: 139, b: 139, type: 'hex'}],
|
||||
['darkgoldenrod', {r: 184, g: 134, b: 11, type: 'hex'}],
|
||||
['darkgray', {r: 169, g: 169, b: 169, type: 'hex'}],
|
||||
['darkgrey', {r: 169, g: 169, b: 169, type: 'hex'}],
|
||||
['darkgreen', {r: 0, g: 100, b: 0, type: 'hex'}],
|
||||
['darkkhaki', {r: 189, g: 183, b: 107, type: 'hex'}],
|
||||
['darkmagenta', {r: 139, g: 0, b: 139, type: 'hex'}],
|
||||
['darkolivegreen', {r: 85, g: 107, b: 47, type: 'hex'}],
|
||||
['darkorange', {r: 255, g: 140, b: 0, type: 'hex'}],
|
||||
['darkorchid', {r: 153, g: 50, b: 204, type: 'hex'}],
|
||||
['darkred', {r: 139, g: 0, b: 0, type: 'hex'}],
|
||||
['darksalmon', {r: 233, g: 150, b: 122, type: 'hex'}],
|
||||
['darkseagreen', {r: 143, g: 188, b: 143, type: 'hex'}],
|
||||
['darkslateblue', {r: 72, g: 61, b: 139, type: 'hex'}],
|
||||
['darkslategray', {r: 47, g: 79, b: 79, type: 'hex'}],
|
||||
['darkslategrey', {r: 47, g: 79, b: 79, type: 'hex'}],
|
||||
['darkturquoise', {r: 0, g: 206, b: 209, type: 'hex'}],
|
||||
['darkviolet', {r: 148, g: 0, b: 211, type: 'hex'}],
|
||||
['deeppink', {r: 255, g: 20, b: 147, type: 'hex'}],
|
||||
['deepskyblue', {r: 0, g: 191, b: 255, type: 'hex'}],
|
||||
['dimgray', {r: 105, g: 105, b: 105, type: 'hex'}],
|
||||
['dimgrey', {r: 105, g: 105, b: 105, type: 'hex'}],
|
||||
['dodgerblue', {r: 30, g: 144, b: 255, type: 'hex'}],
|
||||
['firebrick', {r: 178, g: 34, b: 34, type: 'hex'}],
|
||||
['floralwhite', {r: 255, g: 250, b: 240, type: 'hex'}],
|
||||
['forestgreen', {r: 34, g: 139, b: 34, type: 'hex'}],
|
||||
['fuchsia', {r: 255, g: 0, b: 255, type: 'hex'}],
|
||||
['gainsboro', {r: 220, g: 220, b: 220, type: 'hex'}],
|
||||
['ghostwhite', {r: 248, g: 248, b: 255, type: 'hex'}],
|
||||
['gold', {r: 255, g: 215, b: 0, type: 'hex'}],
|
||||
['goldenrod', {r: 218, g: 165, b: 32, type: 'hex'}],
|
||||
['gray', {r: 128, g: 128, b: 128, type: 'hex'}],
|
||||
['grey', {r: 128, g: 128, b: 128, type: 'hex'}],
|
||||
['green', {r: 0, g: 128, b: 0, type: 'hex'}],
|
||||
['greenyellow', {r: 173, g: 255, b: 47, type: 'hex'}],
|
||||
['honeydew', {r: 240, g: 255, b: 240, type: 'hex'}],
|
||||
['hotpink', {r: 255, g: 105, b: 180, type: 'hex'}],
|
||||
['indianred', {r: 205, g: 92, b: 92, type: 'hex'}],
|
||||
['indigo', {r: 75, g: 0, b: 130, type: 'hex'}],
|
||||
['ivory', {r: 255, g: 255, b: 240, type: 'hex'}],
|
||||
['khaki', {r: 240, g: 230, b: 140, type: 'hex'}],
|
||||
['lavender', {r: 230, g: 230, b: 250, type: 'hex'}],
|
||||
['lavenderblush', {r: 255, g: 240, b: 245, type: 'hex'}],
|
||||
['lawngreen', {r: 124, g: 252, b: 0, type: 'hex'}],
|
||||
['lemonchiffon', {r: 255, g: 250, b: 205, type: 'hex'}],
|
||||
['lightblue', {r: 173, g: 216, b: 230, type: 'hex'}],
|
||||
['lightcoral', {r: 240, g: 128, b: 128, type: 'hex'}],
|
||||
['lightcyan', {r: 224, g: 255, b: 255, type: 'hex'}],
|
||||
['lightgoldenrodyellow', {r: 250, g: 250, b: 210, type: 'hex'}],
|
||||
['lightgray', {r: 211, g: 211, b: 211, type: 'hex'}],
|
||||
['lightgrey', {r: 211, g: 211, b: 211, type: 'hex'}],
|
||||
['lightgreen', {r: 144, g: 238, b: 144, type: 'hex'}],
|
||||
['lightpink', {r: 255, g: 182, b: 193, type: 'hex'}],
|
||||
['lightsalmon', {r: 255, g: 160, b: 122, type: 'hex'}],
|
||||
['lightseagreen', {r: 32, g: 178, b: 170, type: 'hex'}],
|
||||
['lightskyblue', {r: 135, g: 206, b: 250, type: 'hex'}],
|
||||
['lightslategray', {r: 119, g: 136, b: 153, type: 'hex'}],
|
||||
['lightslategrey', {r: 119, g: 136, b: 153, type: 'hex'}],
|
||||
['lightsteelblue', {r: 176, g: 196, b: 222, type: 'hex'}],
|
||||
['lightyellow', {r: 255, g: 255, b: 224, type: 'hex'}],
|
||||
['lime', {r: 0, g: 255, b: 0, type: 'hex'}],
|
||||
['limegreen', {r: 50, g: 205, b: 50, type: 'hex'}],
|
||||
['linen', {r: 250, g: 240, b: 230, type: 'hex'}],
|
||||
['magenta', {r: 255, g: 0, b: 255, type: 'hex'}],
|
||||
['maroon', {r: 128, g: 0, b: 0, type: 'hex'}],
|
||||
['mediumaquamarine', {r: 102, g: 205, b: 170, type: 'hex'}],
|
||||
['mediumblue', {r: 0, g: 0, b: 205, type: 'hex'}],
|
||||
['mediumorchid', {r: 186, g: 85, b: 211, type: 'hex'}],
|
||||
['mediumpurple', {r: 147, g: 112, b: 219, type: 'hex'}],
|
||||
['mediumseagreen', {r: 60, g: 179, b: 113, type: 'hex'}],
|
||||
['mediumslateblue', {r: 123, g: 104, b: 238, type: 'hex'}],
|
||||
['mediumspringgreen', {r: 0, g: 250, b: 154, type: 'hex'}],
|
||||
['mediumturquoise', {r: 72, g: 209, b: 204, type: 'hex'}],
|
||||
['mediumvioletred', {r: 199, g: 21, b: 133, type: 'hex'}],
|
||||
['midnightblue', {r: 25, g: 25, b: 112, type: 'hex'}],
|
||||
['mintcream', {r: 245, g: 255, b: 250, type: 'hex'}],
|
||||
['mistyrose', {r: 255, g: 228, b: 225, type: 'hex'}],
|
||||
['moccasin', {r: 255, g: 228, b: 181, type: 'hex'}],
|
||||
['navajowhite', {r: 255, g: 222, b: 173, type: 'hex'}],
|
||||
['navy', {r: 0, g: 0, b: 128, type: 'hex'}],
|
||||
['oldlace', {r: 253, g: 245, b: 230, type: 'hex'}],
|
||||
['olive', {r: 128, g: 128, b: 0, type: 'hex'}],
|
||||
['olivedrab', {r: 107, g: 142, b: 35, type: 'hex'}],
|
||||
['orange', {r: 255, g: 165, b: 0, type: 'hex'}],
|
||||
['orangered', {r: 255, g: 69, b: 0, type: 'hex'}],
|
||||
['orchid', {r: 218, g: 112, b: 214, type: 'hex'}],
|
||||
['palegoldenrod', {r: 238, g: 232, b: 170, type: 'hex'}],
|
||||
['palegreen', {r: 152, g: 251, b: 152, type: 'hex'}],
|
||||
['paleturquoise', {r: 175, g: 238, b: 238, type: 'hex'}],
|
||||
['palevioletred', {r: 219, g: 112, b: 147, type: 'hex'}],
|
||||
['papayawhip', {r: 255, g: 239, b: 213, type: 'hex'}],
|
||||
['peachpuff', {r: 255, g: 218, b: 185, type: 'hex'}],
|
||||
['peru', {r: 205, g: 133, b: 63, type: 'hex'}],
|
||||
['pink', {r: 255, g: 192, b: 203, type: 'hex'}],
|
||||
['plum', {r: 221, g: 160, b: 221, type: 'hex'}],
|
||||
['powderblue', {r: 176, g: 224, b: 230, type: 'hex'}],
|
||||
['purple', {r: 128, g: 0, b: 128, type: 'hex'}],
|
||||
['rebeccapurple', {r: 102, g: 51, b: 153, type: 'hex'}],
|
||||
['red', {r: 255, g: 0, b: 0, type: 'hex'}],
|
||||
['rosybrown', {r: 188, g: 143, b: 143, type: 'hex'}],
|
||||
['royalblue', {r: 65, g: 105, b: 225, type: 'hex'}],
|
||||
['saddlebrown', {r: 139, g: 69, b: 19, type: 'hex'}],
|
||||
['salmon', {r: 250, g: 128, b: 114, type: 'hex'}],
|
||||
['sandybrown', {r: 244, g: 164, b: 96, type: 'hex'}],
|
||||
['seagreen', {r: 46, g: 139, b: 87, type: 'hex'}],
|
||||
['seashell', {r: 255, g: 245, b: 238, type: 'hex'}],
|
||||
['sienna', {r: 160, g: 82, b: 45, type: 'hex'}],
|
||||
['silver', {r: 192, g: 192, b: 192, type: 'hex'}],
|
||||
['skyblue', {r: 135, g: 206, b: 235, type: 'hex'}],
|
||||
['slateblue', {r: 106, g: 90, b: 205, type: 'hex'}],
|
||||
['slategray', {r: 112, g: 128, b: 144, type: 'hex'}],
|
||||
['slategrey', {r: 112, g: 128, b: 144, type: 'hex'}],
|
||||
['snow', {r: 255, g: 250, b: 250, type: 'hex'}],
|
||||
['springgreen', {r: 0, g: 255, b: 127, type: 'hex'}],
|
||||
['steelblue', {r: 70, g: 130, b: 180, type: 'hex'}],
|
||||
['tan', {r: 210, g: 180, b: 140, type: 'hex'}],
|
||||
['teal', {r: 0, g: 128, b: 128, type: 'hex'}],
|
||||
['thistle', {r: 216, g: 191, b: 216, type: 'hex'}],
|
||||
['tomato', {r: 255, g: 99, b: 71, type: 'hex'}],
|
||||
['turquoise', {r: 64, g: 224, b: 208, type: 'hex'}],
|
||||
['violet', {r: 238, g: 130, b: 238, type: 'hex'}],
|
||||
['wheat', {r: 245, g: 222, b: 179, type: 'hex'}],
|
||||
['white', {r: 255, g: 255, b: 255, type: 'hex'}],
|
||||
['whitesmoke', {r: 245, g: 245, b: 245, type: 'hex'}],
|
||||
['yellow', {r: 255, g: 255, b: 0, type: 'hex'}],
|
||||
['yellowgreen', {r: 154, g: 205, b: 50, type: 'hex'}],
|
||||
]));
|
||||
|
|
|
@ -301,7 +301,7 @@ self.parserlib = (() => {
|
|||
'font-variant-position': 'normal | sub | super',
|
||||
'font-variation-settings': 'normal | [ <string> <number> ]#',
|
||||
'font-weight': '<font-weight>',
|
||||
'forced-color-adjust': 'auto | none',
|
||||
'forced-color-adjust': 'auto | none | preserve-parent-color',
|
||||
'-ms-flex-align': 1,
|
||||
'-ms-flex-order': 1,
|
||||
'-ms-flex-pack': 1,
|
||||
|
@ -3503,7 +3503,7 @@ self.parserlib = (() => {
|
|||
do {
|
||||
this._ws();
|
||||
if ((t = stream.get(true)).type === Tokens.IDENT) {
|
||||
ids.push(t.value);
|
||||
ids.push(this._layerName(t));
|
||||
this._ws();
|
||||
t = stream.get(true);
|
||||
}
|
||||
|
@ -3521,6 +3521,16 @@ self.parserlib = (() => {
|
|||
this._ws();
|
||||
}
|
||||
|
||||
_layerName(start) {
|
||||
let res = '';
|
||||
const stream = this._tokenStream;
|
||||
for (let t; (t = start || stream.match(Tokens.IDENT));) {
|
||||
res += t.value + (stream.match(Tokens.DOT) ? '.' : '');
|
||||
start = false;
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
_stylesheet() {
|
||||
const stream = this._tokenStream;
|
||||
this.fire('startstylesheet');
|
||||
|
@ -3590,7 +3600,7 @@ self.parserlib = (() => {
|
|||
this._ws();
|
||||
t = stream.get(true);
|
||||
if (/^layer(\()?$/i.test(t.value)) {
|
||||
layer = RegExp.$1 ? stream.mustMatch(Tokens.IDENT) : '';
|
||||
layer = RegExp.$1 ? this._layerName() : '';
|
||||
if (layer) stream.mustMatch(Tokens.RPAREN);
|
||||
this._ws();
|
||||
t = stream.get(true);
|
||||
|
|
22
js/dom.js
22
js/dom.js
|
@ -13,6 +13,7 @@
|
|||
messageBoxProxy
|
||||
moveFocus
|
||||
scrollElementIntoView
|
||||
setInputValue
|
||||
setupLivePrefs
|
||||
showSpinner
|
||||
toggleDataset
|
||||
|
@ -296,6 +297,18 @@ function scrollElementIntoView(element, {invalidMarginRatio = 0} = {}) {
|
|||
}
|
||||
}
|
||||
|
||||
function setInputValue(input, value) {
|
||||
input.focus();
|
||||
input.select();
|
||||
// using execCommand to add to the input's undo history
|
||||
document.execCommand(value ? 'insertText' : 'delete', false, value);
|
||||
// some versions of Firefox ignore execCommand
|
||||
if (input.value !== value) {
|
||||
input.value = value;
|
||||
input.dispatchEvent(new Event('input', {bubbles: true}));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Accepts an array of pref names (values are fetched via prefs.get)
|
||||
* and establishes a two-way connection between the document elements and the actual prefs
|
||||
|
@ -314,9 +327,7 @@ function setupLivePrefs(ids) {
|
|||
function getValue(el) {
|
||||
const type = el.dataset.valueType || el.type;
|
||||
return type === 'checkbox' ? el.checked :
|
||||
// https://stackoverflow.com/questions/18062069/why-does-valueasnumber-return-nan-as-a-value
|
||||
// valueAsNumber is not applicable for input[text/radio] or select
|
||||
type === 'number' ? Number(el.value) :
|
||||
type === 'number' ? parseFloat(el.value) :
|
||||
el.value;
|
||||
}
|
||||
function isSame(el, oldValue, value) {
|
||||
|
@ -465,6 +476,7 @@ prefs.ready.then(() => {
|
|||
const max = (innerWidth < 850 ? screen.availWidth : innerWidth) / 3;
|
||||
width = Math.round(Math.max(200, Math.min(max, Number(width) || 0)));
|
||||
$.root.style.setProperty('--header-width', width + 'px');
|
||||
dom.HWval = width;
|
||||
return width;
|
||||
},
|
||||
});
|
||||
|
@ -481,9 +493,7 @@ prefs.ready.then(() => {
|
|||
}));
|
||||
}
|
||||
}
|
||||
window.requestIdleCallback(() => {
|
||||
require(lazyScripts);
|
||||
});
|
||||
setTimeout(() => requestIdleCallback(() => require(lazyScripts)));
|
||||
})();
|
||||
|
||||
//#endregion
|
||||
|
|
|
@ -11,13 +11,18 @@
|
|||
*/
|
||||
|
||||
function t(key, params, strict = true) {
|
||||
const s = chrome.i18n.getMessage(key, params);
|
||||
const s = !params && t.cache[key]
|
||||
|| (t.cache[key] = chrome.i18n.getMessage(key, params));
|
||||
if (!s && strict) throw `Missing string "${key}"`;
|
||||
return s;
|
||||
}
|
||||
|
||||
Object.assign(t, {
|
||||
template: {},
|
||||
cache: {},
|
||||
template: new Proxy({}, {
|
||||
get: (obj, k, _) => obj[k] ||
|
||||
(_ = $(`template[data-id="${k}"]`)) && (obj[k] = t.createTemplate(_)),
|
||||
}),
|
||||
ALLOWED_TAGS: ['a', 'b', 'code', 'i', 'sub', 'sup', 'wbr'],
|
||||
RX_WORD_BREAK: new RegExp([
|
||||
'(',
|
||||
|
@ -27,9 +32,9 @@ Object.assign(t, {
|
|||
'|',
|
||||
/((?!\s)\W){10}/,
|
||||
')',
|
||||
/(?!\b|\s|$)/,
|
||||
/(?!\s|$)/,
|
||||
].map(rx => rx.source || rx).join(''), 'gu'),
|
||||
SELECTOR: '[i18n], template',
|
||||
SELECTOR: '[i18n]',
|
||||
|
||||
HTML(html) {
|
||||
return typeof html !== 'string'
|
||||
|
@ -45,10 +50,6 @@ Object.assign(t, {
|
|||
}
|
||||
for (const node of nodes) {
|
||||
if (!node.localName) continue;
|
||||
if (node.localName === 'template') {
|
||||
t.createTemplate(node);
|
||||
continue;
|
||||
}
|
||||
const attr = node.getAttribute('i18n');
|
||||
if (!attr) continue;
|
||||
for (const part of attr.split(',')) {
|
||||
|
|
|
@ -55,6 +55,7 @@
|
|||
'manage.actions.expanded': true,
|
||||
'manage.backup.expanded': true,
|
||||
'manage.filters.expanded': true,
|
||||
'manage.minColumnWidth': 750,
|
||||
// the new compact layout doesn't look good on Android yet
|
||||
'manage.newUI': true,
|
||||
'manage.newUI.favicons': false, // show favicons for the sites in applies-to
|
||||
|
@ -98,7 +99,7 @@
|
|||
// "Delete" item in context menu for browsers that don't have it
|
||||
'editor.contextDelete': false,
|
||||
'editor.selectByTokens': true,
|
||||
|
||||
'editor.arrowKeysTraverse': true,
|
||||
'editor.appliesToLineWidget': true, // show applies-to line widget on the editor
|
||||
'editor.autosaveDraft': 10, // seconds
|
||||
'editor.livePreview': true,
|
||||
|
|
11
js/router.js
11
js/router.js
|
@ -78,9 +78,16 @@ const router = {
|
|||
router.update();
|
||||
},
|
||||
|
||||
updateSearch(key, value) {
|
||||
/**
|
||||
* @param {Object|string} what - an object or a single key
|
||||
* @param {string} [value] - for `key` mode
|
||||
*/
|
||||
updateSearch(what, value) {
|
||||
const u = new URL(location);
|
||||
u.searchParams[value ? 'set' : 'delete'](key, value);
|
||||
const entries = typeof what === 'object' ? Object.entries(what) : [[what, value]];
|
||||
for (const [key, val] of entries) {
|
||||
u.searchParams[val ? 'set' : 'delete'](key, val);
|
||||
}
|
||||
history.replaceState(history.state, null, `${u}`);
|
||||
router.update(true);
|
||||
},
|
||||
|
|
|
@ -5,7 +5,6 @@
|
|||
MozDocMapper
|
||||
styleCodeEmpty
|
||||
styleJSONseemsValid
|
||||
styleSectionGlobal
|
||||
styleSectionsEqual
|
||||
*/
|
||||
|
||||
|
@ -83,14 +82,6 @@ function styleCodeEmpty(code) {
|
|||
return false;
|
||||
}
|
||||
|
||||
/** Checks if section is global i.e. has no targets at all */
|
||||
function styleSectionGlobal(section) {
|
||||
return (!section.regexps || !section.regexps.length) &&
|
||||
(!section.urlPrefixes || !section.urlPrefixes.length) &&
|
||||
(!section.urls || !section.urls.length) &&
|
||||
(!section.domains || !section.domains.length);
|
||||
}
|
||||
|
||||
/**
|
||||
* The sections are checked in successive order because it matters when many sections
|
||||
* match the same URL and they have rules with the same CSS specificity
|
||||
|
|
|
@ -160,40 +160,6 @@ async function getActiveTab() {
|
|||
return (await browser.tabs.query({currentWindow: true, active: true}))[0];
|
||||
}
|
||||
|
||||
function urlToMatchPattern(url, ignoreSearch) {
|
||||
// https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/Match_patterns
|
||||
if (!/^(http|https|ws|wss|ftp|data|file)$/.test(url.protocol)) {
|
||||
return undefined;
|
||||
}
|
||||
if (ignoreSearch) {
|
||||
return [
|
||||
`${url.protocol}//${url.hostname}/${url.pathname}`,
|
||||
`${url.protocol}//${url.hostname}/${url.pathname}?*`,
|
||||
];
|
||||
}
|
||||
// FIXME: is %2f allowed in pathname and search?
|
||||
return `${url.protocol}//${url.hostname}/${url.pathname}${url.search}`;
|
||||
}
|
||||
|
||||
async function findExistingTab({url, currentWindow, ignoreHash = true, ignoreSearch = false}) {
|
||||
url = tryURL(url);
|
||||
const tabs = await browser.tabs.query({
|
||||
url: urlToMatchPattern(url, ignoreSearch),
|
||||
currentWindow,
|
||||
});
|
||||
return tabs.find(tab => {
|
||||
const tabUrl = tryURL(tab.pendingUrl || tab.url);
|
||||
return tabUrl.protocol === url.protocol &&
|
||||
tabUrl.username === url.username &&
|
||||
tabUrl.password === url.password &&
|
||||
tabUrl.hostname === url.hostname &&
|
||||
tabUrl.port === url.port &&
|
||||
tabUrl.pathname === url.pathname &&
|
||||
(ignoreSearch || tabUrl.search === url.search) &&
|
||||
(ignoreHash || tabUrl.hash === url.hash);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Opens a tab or activates an existing one,
|
||||
* reuses the New Tab page or about:blank if it's focused now
|
||||
|
@ -204,7 +170,7 @@ async function findExistingTab({url, currentWindow, ignoreHash = true, ignoreSea
|
|||
* @param {Boolean} [_.active=true] `true` to activate the tab
|
||||
* @param {Boolean|null} [_.currentWindow=true] `null` to check all windows
|
||||
* @param {chrome.windows.CreateData} [_.newWindow] creates a new window with these params if specified
|
||||
* @param {boolean} [_.ignoreExisting] specify to skip findExistingTab
|
||||
* @param {boolean} [_.newTab] `true` to force a new tab instead of switching to an existing tab
|
||||
* @returns {Promise<chrome.tabs.Tab>} Promise -> opened/activated tab
|
||||
*/
|
||||
async function openURL({
|
||||
|
@ -214,12 +180,12 @@ async function openURL({
|
|||
active = true,
|
||||
currentWindow = true,
|
||||
newWindow,
|
||||
ignoreExisting,
|
||||
newTab,
|
||||
}) {
|
||||
if (!url.includes('://')) {
|
||||
url = chrome.runtime.getURL(url);
|
||||
}
|
||||
let tab = !ignoreExisting && await findExistingTab({url, currentWindow});
|
||||
let tab = !newTab && (await browser.tabs.query({url: url.split('#')[0], currentWindow}))[0];
|
||||
if (tab) {
|
||||
return activateTab(tab, {
|
||||
index,
|
||||
|
|
|
@ -18,6 +18,8 @@
|
|||
<a target="_blank" class="homepage"></a>
|
||||
</h2>
|
||||
<p class="applies-to">
|
||||
<span class="style-info" data-type="age"></span>.
|
||||
<label i18n="genericSize">:</label><span class="style-info" data-type="size"></span>.
|
||||
<label i18n="appliesDisplay"></label>
|
||||
<span class="targets"></span>
|
||||
</p>
|
||||
|
@ -29,7 +31,7 @@
|
|||
<button class="disable" i18n="disableStyleLabel"></button>
|
||||
<button class="delete" i18n="deleteStyleLabel"></button>
|
||||
<button class="check-update" i18n="checkForUpdate"></button>
|
||||
<button class="update" i18n="installUpdate"></button>
|
||||
<button class="update" i18n="installUpdate" hidden></button>
|
||||
<button class="configure-usercss" i18n="configureStyle"></button>
|
||||
<span class="update-note"></span>
|
||||
</p>
|
||||
|
@ -47,9 +49,9 @@
|
|||
|
||||
<span class="style-info" data-type="version"></span>
|
||||
</a>
|
||||
<a target="_blank" class="homepage" tabindex="0"></a>
|
||||
</h2>
|
||||
<p class="actions">
|
||||
<a target="_blank" class="homepage" tabindex="0"></a>
|
||||
<a class="delete" i18n="title:deleteStyleLabel" tabindex="0">
|
||||
<svg class="svg-icon" viewBox="0 0 20 20">
|
||||
<polygon points="16.2,5.5 14.5,3.8 10,8.3 5.5,3.8 3.8,5.5 8.3,10 3.8,14.5
|
||||
|
@ -57,6 +59,7 @@
|
|||
</svg>
|
||||
</a>
|
||||
</p>
|
||||
<p class="style-info" data-type="size"></p>
|
||||
<p class="style-info" data-type="age"></p>
|
||||
<div class="applies-to">
|
||||
<a class="expander" tabindex="0">
|
||||
|
@ -388,6 +391,7 @@
|
|||
</template>
|
||||
|
||||
<link rel="stylesheet" href="manage/manage.css">
|
||||
<link rel="stylesheet" href="manage/manage-newui.css" id="newUI">
|
||||
<script src="js/dark-themer.js"></script> <!-- must be last in HEAD to avoid FOUC -->
|
||||
</head>
|
||||
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
/* global API */// msg.js
|
||||
/* global changeQueue installed newUI */// manage.js
|
||||
/* global checkUpdate handleUpdateInstalled */// updater-ui.js
|
||||
/* global createStyleElement createTargetsElement getFaviconSrc */// render.js
|
||||
/* global createStyleElement createTargetsElement getFaviconSrc styleToDummyEntry */// render.js
|
||||
/* global debounce getOwnTab openURL sessionStore */// toolbox.js
|
||||
/* global filterAndAppend showFiltersStats */// filters.js
|
||||
/* global sorter */
|
||||
|
@ -130,7 +130,9 @@ const Events = {
|
|||
},
|
||||
|
||||
name(event, entry) {
|
||||
if (newUI.enabled) Events.edit(event, entry);
|
||||
if (newUI.enabled && !event.target.closest('.homepage')) {
|
||||
Events.edit(event, entry);
|
||||
}
|
||||
},
|
||||
|
||||
toggle(event, entry) {
|
||||
|
@ -191,9 +193,9 @@ function handleUpdate(style, {reason, method} = {}) {
|
|||
if (oldEntry && method === 'styleUpdated') {
|
||||
handleToggledOrCodeOnly();
|
||||
}
|
||||
entry = entry || createStyleElement({style});
|
||||
entry = entry || createStyleElement(styleToDummyEntry(style));
|
||||
if (oldEntry) {
|
||||
if (oldEntry.styleNameLowerCase === entry.styleNameLowerCase) {
|
||||
if (oldEntry.styleNameLC === entry.styleNameLC) {
|
||||
installed.replaceChild(entry, oldEntry);
|
||||
} else {
|
||||
oldEntry.remove();
|
||||
|
|
|
@ -114,7 +114,7 @@ function initFilters() {
|
|||
}
|
||||
}
|
||||
filterOnChange({forceRefilter: true});
|
||||
router.updateSearch('search', '');
|
||||
router.updateSearch({search: '', searchMode: ''});
|
||||
};
|
||||
|
||||
filterOnChange({forceRefilter: true});
|
||||
|
|
|
@ -57,7 +57,7 @@
|
|||
let found;
|
||||
for (const entry of rotated || entries) {
|
||||
if (entry.classList.contains('hidden')) continue;
|
||||
const name = entry.styleNameLowerCase;
|
||||
const name = entry.styleNameLC;
|
||||
const pos = name.indexOf(text);
|
||||
if (pos === 0) {
|
||||
found = entry;
|
||||
|
@ -73,7 +73,7 @@
|
|||
if (found && found !== focusedEntry) {
|
||||
focusedEntry = found;
|
||||
focusedLink = $('a', found);
|
||||
focusedName = found.styleNameLowerCase;
|
||||
focusedName = found.styleNameLC;
|
||||
scrollElementIntoView(found, {invalidMarginRatio: .25});
|
||||
animateElement(found, 'highlight-quick');
|
||||
replaceInlineStyle({
|
||||
|
|
281
manage/manage-newui.css
Normal file
281
manage/manage-newui.css
Normal file
|
@ -0,0 +1,281 @@
|
|||
.disabled.entry .svg-icon {
|
||||
color: var(--c50);
|
||||
fill: var(--c80);
|
||||
font-weight: normal;
|
||||
transition: color .5s .1s, fill .5s .1s;
|
||||
}
|
||||
#installed {
|
||||
margin-top: .75rem;
|
||||
margin-bottom: .75rem;
|
||||
}
|
||||
.entry {
|
||||
padding: 0 .5em;
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
border: none;
|
||||
}
|
||||
.entry.odd {
|
||||
background-color: rgba(128, 128, 128, 0.05);
|
||||
}
|
||||
.entry > * {
|
||||
padding: .5rem 0;
|
||||
margin: 0;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
.entry .actions {
|
||||
position: relative;
|
||||
}
|
||||
.style-info[data-type=size],
|
||||
.style-info[data-type=age] {
|
||||
color: var(--c50);
|
||||
justify-content: end;
|
||||
}
|
||||
.style-info[data-type=age] {
|
||||
flex: 0 0 4ch;
|
||||
}
|
||||
.style-info[data-type=size] {
|
||||
flex: 0 0 var(--size-width);
|
||||
}
|
||||
.style-info[data-type=version] {
|
||||
color: var(--c40);
|
||||
padding-left: .5em;
|
||||
font-weight: normal;
|
||||
}
|
||||
.style-info[data-type=version][data-is-date],
|
||||
.style-info[data-type=version][data-value=""],
|
||||
.style-info[data-type=version][data-value="1.0.0"] {
|
||||
display: none;
|
||||
}
|
||||
.entry input[type="checkbox"]:not(.slider) {
|
||||
pointer-events: all;
|
||||
}
|
||||
.style-name {
|
||||
font-size: 14px;
|
||||
padding-left: var(--name-padding-left);
|
||||
position: relative;
|
||||
cursor: pointer;
|
||||
justify-content: space-between;
|
||||
flex: 0 0 var(--name-width);
|
||||
min-width: 25%;
|
||||
max-width: 50%;
|
||||
}
|
||||
#installed[style*="--num-targets:0"] .style-name {
|
||||
max-width: none;
|
||||
flex-grow: 1;
|
||||
}
|
||||
.entry .checkmate {
|
||||
flex-shrink: 0;
|
||||
}
|
||||
.style-name-link {
|
||||
width: 100%;
|
||||
word-break: break-word;
|
||||
}
|
||||
.style-name:hover .style-name-link {
|
||||
text-decoration: underline;
|
||||
}
|
||||
.entry.enabled .style-name:hover .style-name-link {
|
||||
color: var(--accent-1);
|
||||
}
|
||||
.homepage {
|
||||
margin-top: -3px;
|
||||
}
|
||||
.actions {
|
||||
flex: 0 0 calc(3 * (var(--action-size) + var(--action-margin)));
|
||||
flex-wrap: nowrap;
|
||||
z-index: 100;
|
||||
}
|
||||
.actions > * {
|
||||
width: var(--action-size);
|
||||
height: var(--action-size);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
.updater-icons > * {
|
||||
transition: opacity 1s;
|
||||
display: none;
|
||||
}
|
||||
.entry .svg-icon {
|
||||
fill: var(--c60);
|
||||
}
|
||||
.entry:hover .svg-icon {
|
||||
fill: var(--c40);
|
||||
}
|
||||
.entry .svg-icon.checked,
|
||||
.entry:hover .svg-icon.checked,
|
||||
.entry:hover .svg-icon:hover {
|
||||
fill: var(--fg);
|
||||
}
|
||||
.checking-update .check-update {
|
||||
opacity: 0;
|
||||
display: inline-block;
|
||||
pointer-events: none;
|
||||
}
|
||||
.can-update .update,
|
||||
.no-update:not(.update-problem):not(.update-done) .up-to-date,
|
||||
.no-update.update-problem .check-update,
|
||||
.update-done .updated {
|
||||
display: inline-block;
|
||||
}
|
||||
.up-to-date svg,
|
||||
.updated svg {
|
||||
cursor: auto;
|
||||
}
|
||||
.update-done .updated svg {
|
||||
top: -4px;
|
||||
position: relative;
|
||||
filter: drop-shadow(0 5px 0 currentColor);
|
||||
}
|
||||
.can-update .update,
|
||||
.no-update.update-problem .check-update {
|
||||
cursor: pointer;
|
||||
}
|
||||
.can-update[data-details$="locally edited"] .update svg,
|
||||
.update-problem .check-update svg {
|
||||
fill: #ef6969;
|
||||
}
|
||||
.can-update[data-details$="locally edited"]:hover .update svg,
|
||||
.entry.update-problem:hover .check-update svg {
|
||||
fill: #fd4040;
|
||||
}
|
||||
.can-update[data-details$="locally edited"]:hover .update svg:hover,
|
||||
.entry.update-problem:hover .check-update svg:hover {
|
||||
fill: red;
|
||||
}
|
||||
.updater-icons > :not(.check-update):after {
|
||||
content: attr(title);
|
||||
position: absolute;
|
||||
display: block;
|
||||
width: max-content;
|
||||
max-width: 25vw;
|
||||
padding: 1ex 1.5ex;
|
||||
border: 1px solid #ded597;
|
||||
background-color: #fffbd6;
|
||||
border-radius: 4px;
|
||||
box-shadow: 2px 3px 10px rgba(0,0,0,.25);
|
||||
font-size: 90%;
|
||||
animation: fadeout 10s;
|
||||
animation-fill-mode: both;
|
||||
}
|
||||
.update-problem .check-update:after {
|
||||
background-color: red;
|
||||
border: 1px solid #d40000;
|
||||
color: white;
|
||||
animation: none;
|
||||
}
|
||||
.can-update .update:after {
|
||||
animation: none;
|
||||
}
|
||||
.can-update:not([data-details$="locally edited"]) .update:after {
|
||||
background-color: #c0fff0;
|
||||
border: 1px solid #89cac9;
|
||||
}
|
||||
.applies-to {
|
||||
padding: .25em 0 .25em 1em;
|
||||
flex-grow: 999;
|
||||
}
|
||||
#installed[style*="--num-targets:0"] .applies-to {
|
||||
display: none;
|
||||
}
|
||||
.targets {
|
||||
overflow: hidden;
|
||||
max-height: calc(var(--num-targets) * 18px);
|
||||
width: 100%;
|
||||
}
|
||||
.expander {
|
||||
cursor: pointer;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
right: 4px;
|
||||
bottom: 0;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
.expander:hover {
|
||||
background: hsla(0, 0%, 50%, .05);
|
||||
}
|
||||
.expander svg {
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
fill: var(--c50);
|
||||
}
|
||||
.applies-to:not(.has-more) .expander {
|
||||
display: none;
|
||||
}
|
||||
.target {
|
||||
display: block;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
padding-right: 1em;
|
||||
line-height: 18px;
|
||||
width: 0;
|
||||
min-width: 100%;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
.target img {
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
vertical-align: middle;
|
||||
margin: -1px 4px 0 -20px;
|
||||
transition: opacity .5s, filter .5s;
|
||||
/* workaround for the buggy CSS filter: images in the hidden overflow are shown on Mac */
|
||||
backface-visibility: hidden;
|
||||
visibility: hidden;
|
||||
}
|
||||
.expanded svg {
|
||||
transform: rotate(180deg);
|
||||
transform-origin: 8px 8px;
|
||||
}
|
||||
.applies-to.expanded {
|
||||
max-width: 100%;
|
||||
align-items: flex-start;
|
||||
}
|
||||
.applies-to.expanded .targets {
|
||||
max-height: none;
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
.applies-to.expanded .target {
|
||||
width: auto;
|
||||
min-width: 18em;
|
||||
overflow-wrap: anywhere;
|
||||
white-space: normal;
|
||||
}
|
||||
.favicons-grayed .target img {
|
||||
filter: grayscale(1);
|
||||
opacity: .25;
|
||||
}
|
||||
.has-favicons .target {
|
||||
padding-left: 20px;
|
||||
}
|
||||
.has-favicons .target img[src] {
|
||||
visibility: visible;
|
||||
}
|
||||
.entry:hover .target img {
|
||||
opacity: 1;
|
||||
filter: none;
|
||||
}
|
||||
.target b::after {
|
||||
content: '?';
|
||||
margin: -2px 4px 0 -20px;
|
||||
display: inline-block;
|
||||
vertical-align: baseline;
|
||||
background: var(--c85);
|
||||
width: 16px;
|
||||
line-height: 16px;
|
||||
text-align: center;
|
||||
border-radius: 50%;
|
||||
color: var(--bg);
|
||||
}
|
||||
@media (max-width: 850px) {
|
||||
:root {
|
||||
--name-padding-left: 14px;
|
||||
}
|
||||
.entry {
|
||||
padding: 0;
|
||||
}
|
||||
.entry > .style-info {
|
||||
display: none;
|
||||
}
|
||||
}
|
|
@ -1,7 +1,9 @@
|
|||
:root {
|
||||
--name-padding-left: 20px;
|
||||
--name-padding-right: 40px;
|
||||
--actions-width: 75px;
|
||||
--name-width: 30ch;
|
||||
--size-width: 4ch;
|
||||
--action-size: 20px;
|
||||
--action-margin: 6px;
|
||||
}
|
||||
body {
|
||||
/* Fill the entire viewport to enable json import via drag'n'drop */
|
||||
|
@ -124,17 +126,21 @@ a:hover {
|
|||
}
|
||||
|
||||
#installed {
|
||||
position: relative;
|
||||
padding-left: var(--header-width);
|
||||
box-sizing: border-box;
|
||||
width: 100%;
|
||||
align-self: start;
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.entry {
|
||||
margin: 0;
|
||||
padding: 1.25em 2em;
|
||||
border-top: 1px solid var(--c85);
|
||||
box-sizing: border-box;
|
||||
position: relative;
|
||||
width: calc(100% / var(--columns, 1));
|
||||
}
|
||||
|
||||
.entry:first-child {
|
||||
|
@ -158,15 +164,13 @@ a:hover {
|
|||
margin-top: .25em;
|
||||
overflow-wrap: break-word;
|
||||
}
|
||||
|
||||
.style-name a, .style-edit-link {
|
||||
.entry a:not(:hover) {
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.style-name span,
|
||||
.applies-to {
|
||||
overflow-wrap: break-word;
|
||||
overflow-wrap: anywhere;
|
||||
}
|
||||
|
||||
.applies-to,
|
||||
.actions {
|
||||
padding-left: 15px;
|
||||
|
@ -181,18 +185,17 @@ a:hover {
|
|||
|
||||
.actions > * {
|
||||
margin-bottom: .25rem;
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.actions > *:not(:last-child) {
|
||||
margin-right: .25rem;
|
||||
margin-right: var(--action-margin);
|
||||
}
|
||||
|
||||
.applies-to label {
|
||||
margin-right: .5ex;
|
||||
}
|
||||
|
||||
.applies-to .target:hover {
|
||||
.oldUI .applies-to .target:hover {
|
||||
background-color: rgba(128, 128, 128, .15);
|
||||
}
|
||||
|
||||
|
@ -240,8 +243,7 @@ a:hover {
|
|||
}
|
||||
|
||||
.disabled h2 .style-name-link,
|
||||
.disabled .applies-to,
|
||||
.newUI .disabled.entry .svg-icon {
|
||||
.disabled .applies-to {
|
||||
color: var(--c50);
|
||||
fill: var(--c80);
|
||||
font-weight: normal;
|
||||
|
@ -319,55 +321,8 @@ a:hover {
|
|||
|
||||
/* compact layout */
|
||||
|
||||
.newUI #installed {
|
||||
display: table;
|
||||
margin-top: .75rem;
|
||||
margin-bottom: .75rem;
|
||||
}
|
||||
|
||||
.newUI .entry {
|
||||
display: table-row;
|
||||
padding-top: 0;
|
||||
padding-bottom: 0;
|
||||
}
|
||||
|
||||
.newUI .entry.odd {
|
||||
background-color: rgba(128, 128, 128, 0.05);
|
||||
}
|
||||
|
||||
.newUI .entry > * {
|
||||
padding: .5rem 0;
|
||||
margin: 0;
|
||||
display: table-cell;
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
.newUI .entry .actions {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.style-info[data-type=version] {
|
||||
color: var(--c40);
|
||||
padding-left: .5em;
|
||||
font-weight: normal;
|
||||
}
|
||||
.newUI .style-info[data-type=version][data-is-date],
|
||||
.newUI .style-info[data-type=version][data-value=""],
|
||||
.newUI .style-info[data-type=version][data-value="1.0.0"] {
|
||||
display: none;
|
||||
}
|
||||
.newUI .entry .style-info[data-type=age] {
|
||||
color: var(--c60);
|
||||
text-align: right;
|
||||
padding-right: 1em;
|
||||
}
|
||||
|
||||
/************ checkbox & select************/
|
||||
|
||||
#newUIoptions > div, #newUIoptions > label {
|
||||
margin: 4px 0;
|
||||
}
|
||||
|
||||
.filter-selection {
|
||||
position: relative;
|
||||
left: -9px;
|
||||
|
@ -392,10 +347,6 @@ a:hover {
|
|||
margin-top: -2px;
|
||||
}
|
||||
|
||||
.newUI #newUIoptions > label {
|
||||
padding-left: 0;
|
||||
}
|
||||
|
||||
.filter-selection select {
|
||||
height: 18px;
|
||||
border: none;
|
||||
|
@ -438,7 +389,7 @@ a:hover {
|
|||
|
||||
.entry .checkmate {
|
||||
vertical-align: middle;
|
||||
margin: -2px 1ex 0 0;
|
||||
margin-right: 1ch;
|
||||
}
|
||||
|
||||
#manage-text {
|
||||
|
@ -455,242 +406,10 @@ a:hover {
|
|||
margin: 0 .5em;
|
||||
}
|
||||
|
||||
.newUI .entry input[type="checkbox"]:not(.slider) {
|
||||
pointer-events: all;
|
||||
}
|
||||
|
||||
.newUI .style-name {
|
||||
font-size: 14px;
|
||||
padding-left: var(--name-padding-left);
|
||||
padding-right: var(--name-padding-right);
|
||||
position: relative;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.newUI .entry .style-name:hover::before {
|
||||
content: "";
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
background: linear-gradient(to right, hsla(180, 50%, 30%, 0.2), hsla(180, 20%, 10%, 0.05) 50%, transparent);
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.newUI .entry.enabled .style-name:hover .style-name-link {
|
||||
color: var(--accent-1);
|
||||
}
|
||||
|
||||
.newUI .style-name:after {
|
||||
text-indent: 1.2rem;
|
||||
}
|
||||
|
||||
.newUI .actions:after {
|
||||
text-indent: -25px;
|
||||
}
|
||||
|
||||
.newUI .actions .homepage[href=""] {
|
||||
display: inline-block;
|
||||
visibility: hidden;
|
||||
height: 0;
|
||||
}
|
||||
|
||||
.newUI .actions {
|
||||
width: var(--actions-width);
|
||||
height: 20px;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.newUI .actions > * {
|
||||
margin: 0 6px 0 0;
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
}
|
||||
|
||||
.newUI .updater-icons > * {
|
||||
transition: opacity 1s;
|
||||
display: none;
|
||||
}
|
||||
|
||||
.newUI .entry .svg-icon {
|
||||
fill: var(--c60);
|
||||
}
|
||||
|
||||
.newUI .entry:hover .svg-icon {
|
||||
fill: var(--c40);
|
||||
}
|
||||
|
||||
button .svg-icon,
|
||||
.newUI .entry .svg-icon.checked,
|
||||
.newUI .entry:hover .svg-icon.checked,
|
||||
.newUI .entry:hover .svg-icon:hover {
|
||||
button .svg-icon {
|
||||
fill: var(--fg);
|
||||
}
|
||||
|
||||
.newUI .checking-update .check-update {
|
||||
opacity: 0;
|
||||
display: inline-block;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.newUI .can-update .update,
|
||||
.newUI .no-update:not(.update-problem):not(.update-done) .up-to-date,
|
||||
.newUI .no-update.update-problem .check-update,
|
||||
.newUI .update-done .updated {
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.newUI .up-to-date svg,
|
||||
.newUI .updated svg {
|
||||
cursor: auto;
|
||||
}
|
||||
|
||||
.newUI .update-done .updated svg {
|
||||
top: -4px;
|
||||
position: relative;
|
||||
filter: drop-shadow(0 5px 0 currentColor);
|
||||
}
|
||||
|
||||
.newUI .can-update .update,
|
||||
.newUI .no-update.update-problem .check-update {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.newUI .can-update[data-details$="locally edited"] .update svg,
|
||||
.newUI .update-problem .check-update svg {
|
||||
fill: #ef6969;
|
||||
}
|
||||
|
||||
.newUI .can-update[data-details$="locally edited"]:hover .update svg,
|
||||
.newUI .entry.update-problem:hover .check-update svg {
|
||||
fill: #fd4040;
|
||||
}
|
||||
|
||||
.newUI .can-update[data-details$="locally edited"]:hover .update svg:hover,
|
||||
.newUI .entry.update-problem:hover .check-update svg:hover {
|
||||
fill: red;
|
||||
}
|
||||
|
||||
.newUI .actions {
|
||||
z-index: 100;
|
||||
}
|
||||
|
||||
.newUI .updater-icons > :not(.check-update):after {
|
||||
content: attr(title);
|
||||
position: absolute;
|
||||
margin-top: 18px;
|
||||
margin-left: -36px;
|
||||
padding: 1ex 1.5ex;
|
||||
border: 1px solid #ded597;
|
||||
background-color: #fffbd6;
|
||||
border-radius: 4px;
|
||||
box-shadow: 2px 3px 10px rgba(0,0,0,.25);
|
||||
font-size: 90%;
|
||||
animation: fadeout 10s;
|
||||
animation-fill-mode: both;
|
||||
}
|
||||
|
||||
.newUI .update-problem .check-update:after {
|
||||
background-color: red;
|
||||
border: 1px solid #d40000;
|
||||
color: white;
|
||||
animation: none;
|
||||
}
|
||||
|
||||
.newUI .can-update .update:after {
|
||||
animation: none;
|
||||
}
|
||||
|
||||
.newUI .can-update:not([data-details$="locally edited"]) .update:after {
|
||||
background-color: #c0fff0;
|
||||
border: 1px solid #89cac9;
|
||||
}
|
||||
|
||||
.newUI .applies-to {
|
||||
padding-top: .25rem;
|
||||
padding-bottom: .25rem;
|
||||
}
|
||||
.newUI .targets {
|
||||
overflow: hidden;
|
||||
max-height: calc(var(--num-targets) * 18px);
|
||||
}
|
||||
.newUI .applies-to.expanded .targets {
|
||||
max-height: none;
|
||||
}
|
||||
.newUI .target {
|
||||
display: block;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
max-width: calc(75vw - var(--header-width) - var(--actions-width) - var(--name-padding-left) - var(--name-padding-right) - 6rem);
|
||||
box-sizing: border-box;
|
||||
padding-right: 1rem;
|
||||
line-height: 18px;
|
||||
}
|
||||
.expander {
|
||||
cursor: pointer;
|
||||
position: absolute;
|
||||
right: 4px;
|
||||
}
|
||||
.expander svg {
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
fill: var(--c50);
|
||||
}
|
||||
.expanded .expander {
|
||||
transform: rotate(180deg);
|
||||
transform-origin: 8px 8px;
|
||||
}
|
||||
.newUI .applies-to:not(.has-more) .expander {
|
||||
display: none;
|
||||
}
|
||||
.newUI .target:hover {
|
||||
background-color: inherit;
|
||||
}
|
||||
|
||||
.newUI .target img {
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
vertical-align: middle;
|
||||
margin: -1px 4px 0 -20px;
|
||||
transition: opacity .5s, filter .5s;
|
||||
/* workaround for the buggy CSS filter: images in the hidden overflow are shown on Mac */
|
||||
backface-visibility: hidden;
|
||||
visibility: hidden;
|
||||
}
|
||||
|
||||
.newUI .favicons-grayed .target img {
|
||||
filter: grayscale(1);
|
||||
opacity: .25;
|
||||
}
|
||||
|
||||
.newUI .has-favicons .target {
|
||||
padding-left: 20px;
|
||||
}
|
||||
|
||||
.newUI .has-favicons .target img[src] {
|
||||
visibility: visible;
|
||||
}
|
||||
|
||||
.newUI .entry:hover .target img {
|
||||
opacity: 1;
|
||||
filter: none;
|
||||
}
|
||||
|
||||
.newUI .target b::after {
|
||||
content: '?';
|
||||
margin: -2px 4px 0 -20px;
|
||||
display: inline-block;
|
||||
vertical-align: baseline;
|
||||
background: var(--c85);
|
||||
width: 16px;
|
||||
line-height: 16px;
|
||||
text-align: center;
|
||||
border-radius: 50%;
|
||||
color: var(--bg);
|
||||
}
|
||||
|
||||
/* Default, no update buttons */
|
||||
.updater-icons .update,
|
||||
.updater-icons .check-update {
|
||||
|
@ -765,7 +484,7 @@ button .svg-icon,
|
|||
|
||||
/* highlight updated/added styles */
|
||||
.highlight {
|
||||
animation: highlight 10s cubic-bezier(0,.82,.47,.98);
|
||||
animation: highlight 1s cubic-bezier(0, .4, .6, 1);
|
||||
}
|
||||
.highlight-quick {
|
||||
animation: highlight .5s;
|
||||
|
@ -1031,19 +750,10 @@ button .svg-icon,
|
|||
}
|
||||
}
|
||||
|
||||
@media (max-width: 1000px) {
|
||||
.newUI .entry > .style-info {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 850px) {
|
||||
:root {
|
||||
--name-padding-left: 34px;
|
||||
}
|
||||
|
||||
body {
|
||||
display: table;
|
||||
display: block;
|
||||
height: auto;
|
||||
}
|
||||
|
||||
body.all-styles-hidden-by-filters:before {
|
||||
|
@ -1057,18 +767,10 @@ button .svg-icon,
|
|||
left: 3.75rem;
|
||||
}
|
||||
|
||||
html:not(.newUI) .applies-to {
|
||||
.oldUI .applies-to {
|
||||
word-break: break-all;
|
||||
}
|
||||
|
||||
#installed {
|
||||
table-layout: fixed;
|
||||
}
|
||||
|
||||
.newUI .entry .actions {
|
||||
padding-right: 30px
|
||||
}
|
||||
|
||||
#search-wrapper,
|
||||
#sort-wrapper,
|
||||
#header summary {
|
||||
|
@ -1084,11 +786,13 @@ button .svg-icon,
|
|||
}
|
||||
|
||||
#header {
|
||||
display: table-header-group;
|
||||
height: auto;
|
||||
padding: 0;
|
||||
width: 100%;
|
||||
position: static;
|
||||
position: sticky;
|
||||
top: 0;
|
||||
z-index: 1000;
|
||||
background: var(--bg);
|
||||
border-right: none;
|
||||
border-bottom: 1px dashed var(--c65);
|
||||
}
|
||||
|
@ -1113,54 +817,14 @@ button .svg-icon,
|
|||
padding-left: 0;
|
||||
}
|
||||
|
||||
#header h1,
|
||||
#backup-message {
|
||||
#header h1 {
|
||||
display: none;
|
||||
}
|
||||
|
||||
#backup-buttons {
|
||||
margin-top: 0;
|
||||
}
|
||||
|
||||
#header summary {
|
||||
margin-top: 0;
|
||||
padding-bottom: .25rem;
|
||||
}
|
||||
|
||||
.newUI .entry {
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.newUI .entry .checkmate {
|
||||
position: absolute;
|
||||
left: 14px;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
margin: auto;
|
||||
}
|
||||
|
||||
.newUI .entry .style-name {
|
||||
text-indent: unset;
|
||||
}
|
||||
|
||||
.newUI .entry .actions {
|
||||
width: 104px;
|
||||
padding: .5rem 0 .5rem 6px;
|
||||
}
|
||||
|
||||
.newUI .entry .applies-to {
|
||||
padding: .25rem .5rem .25rem 0;
|
||||
}
|
||||
|
||||
.newUI .entry .target {
|
||||
max-width: 100%;
|
||||
padding-right: 0;
|
||||
}
|
||||
|
||||
.newUI .style-name-link::after {
|
||||
text-indent: 0;
|
||||
display: inline-block;
|
||||
}
|
||||
}
|
||||
|
||||
@supports (-moz-appearance: none) {
|
||||
|
|
|
@ -28,9 +28,16 @@ const newUI = {
|
|||
Object.assign(newUI, {
|
||||
ids: Object.keys(newUI),
|
||||
prefKeyForId: id => `manage.newUI.${id}`.replace(/\.enabled$/, ''),
|
||||
readPrefs(dest = newUI, cb) {
|
||||
for (const id of newUI.ids) {
|
||||
const val = dest[id] = prefs.get(newUI.prefKeyForId(id));
|
||||
if (cb) cb(id, val);
|
||||
}
|
||||
},
|
||||
renderClass: () => {
|
||||
$.rootCL.toggle('newUI', newUI.enabled);
|
||||
$.rootCL.toggle('oldUI', !newUI.enabled);
|
||||
$('#newUI').media = newUI.enabled ? '' : '?';
|
||||
},
|
||||
hasFavs: () => newUI.enabled && newUI.favicons,
|
||||
badFavsKey: 'badFavs',
|
||||
|
@ -41,9 +48,7 @@ Object.assign(newUI, {
|
|||
},
|
||||
});
|
||||
// ...read the actual values
|
||||
for (const id of newUI.ids) {
|
||||
newUI[id] = prefs.get(newUI.prefKeyForId(id));
|
||||
}
|
||||
newUI.readPrefs();
|
||||
newUI.renderClass();
|
||||
|
||||
(async function init() {
|
||||
|
|
|
@ -3,7 +3,6 @@
|
|||
/* global URLS debounce getOwnTab isEmptyObj sessionStore stringAsRegExp */// toolbox.js
|
||||
/* global filterAndAppend */// filters.js
|
||||
/* global installed newUI */// manage.js
|
||||
/* global prefs */
|
||||
/* global sorter */
|
||||
/* global t */// localization.js
|
||||
'use strict';
|
||||
|
@ -17,6 +16,8 @@ const AGES = [
|
|||
[12, 'm', t('dateAbbrMonth', '\x01')],
|
||||
[Infinity, 'y', t('dateAbbrYear', '\x01')],
|
||||
];
|
||||
const groupThousands = num => `${num}`.replace(/\d(?=(\d{3})+$)/g, '$&\xA0');
|
||||
const renderSize = size => groupThousands(Math.round(size / 1024)) + 'k';
|
||||
|
||||
(() => {
|
||||
const proto = HTMLImageElement.prototype;
|
||||
|
@ -70,7 +71,15 @@ function createAgeText(el, style) {
|
|||
}
|
||||
}
|
||||
|
||||
function createStyleElement({style, name: nameLC}) {
|
||||
function calcObjSize(obj) {
|
||||
// Inaccurate but simple
|
||||
return typeof obj !== 'object' ? `${obj}`.length :
|
||||
!obj ? 0 :
|
||||
Array.isArray(obj) ? obj.reduce((sum, v) => sum + calcObjSize(v), 0) :
|
||||
Object.entries(obj).reduce((sum, [k, v]) => sum + k.length + calcObjSize(v), 0);
|
||||
}
|
||||
|
||||
function createStyleElement({styleMeta: style, styleNameLC: nameLC, styleSize: size}) {
|
||||
// query the sub-elements just once, then reuse the references
|
||||
if ((elementParts || {}).newUI !== newUI.enabled) {
|
||||
const entry = t.template[newUI.enabled ? 'styleNewUI' : 'style'].cloneNode(true);
|
||||
|
@ -85,6 +94,7 @@ function createStyleElement({style, name: nameLC}) {
|
|||
homepage: $('.homepage', entry),
|
||||
homepageIcon: t.template[`homepageIcon${newUI.enabled ? 'Small' : 'Big'}`],
|
||||
infoAge: $('[data-type=age]', entry),
|
||||
infoSize: $('[data-type=size]', entry),
|
||||
infoVer: $('[data-type=version]', entry),
|
||||
appliesTo: $('.applies-to', entry),
|
||||
targets: $('.targets', entry),
|
||||
|
@ -95,7 +105,6 @@ function createStyleElement({style, name: nameLC}) {
|
|||
},
|
||||
oldConfigure: !newUI.enabled && $('.configure-usercss', entry),
|
||||
oldCheckUpdate: !newUI.enabled && $('.check-update', entry),
|
||||
oldUpdate: !newUI.enabled && $('.update', entry),
|
||||
};
|
||||
}
|
||||
const parts = elementParts;
|
||||
|
@ -114,12 +123,13 @@ function createStyleElement({style, name: nameLC}) {
|
|||
} else {
|
||||
delete parts.infoVer.dataset.isDate;
|
||||
}
|
||||
if (newUI.enabled) {
|
||||
createAgeText(parts.infoAge, style);
|
||||
} else {
|
||||
createAgeText(parts.infoAge, style);
|
||||
parts.infoSize.dataset.value = Math.log10(size || 1) >> 0; // for CSS to target big/small styles
|
||||
parts.infoSize.textContent = renderSize(size);
|
||||
parts.infoSize.title = `${t('genericSize')}: ${groupThousands(size)} B`;
|
||||
if (!newUI.enabled) {
|
||||
parts.oldConfigure.classList.toggle('hidden', !configurable);
|
||||
parts.oldCheckUpdate.classList.toggle('hidden', !style.updateUrl);
|
||||
parts.oldUpdate.classList.toggle('hidden', !style.updateUrl);
|
||||
}
|
||||
|
||||
// clear the code to free up some memory
|
||||
|
@ -130,8 +140,9 @@ function createStyleElement({style, name: nameLC}) {
|
|||
const entry = parts.entry.cloneNode(true);
|
||||
entry.id = ENTRY_ID_PREFIX_RAW + style.id;
|
||||
entry.styleId = style.id;
|
||||
entry.styleNameLowerCase = nameLC || name.toLocaleLowerCase() + '\n' + name;
|
||||
entry.styleNameLC = nameLC;
|
||||
entry.styleMeta = style;
|
||||
entry.styleSize = size;
|
||||
entry.className = parts.entryClassBase + ' ' +
|
||||
(style.enabled ? 'enabled' : 'disabled') +
|
||||
(style.updateUrl ? ' updatable' : '') +
|
||||
|
@ -153,6 +164,12 @@ function createStyleElement({style, name: nameLC}) {
|
|||
}
|
||||
|
||||
function createTargetsElement({entry, expanded, style = entry.styleMeta}) {
|
||||
const maxTargets = expanded ? 1000 : newUI.enabled ? newUI.targets : 10;
|
||||
if (!maxTargets) {
|
||||
entry._numTargets = 0;
|
||||
return;
|
||||
}
|
||||
const displayed = new Set();
|
||||
const entryTargets = $('.targets', entry);
|
||||
const expanderCls = $('.applies-to', entry).classList;
|
||||
const targets = elementParts.targets.cloneNode(true);
|
||||
|
@ -160,8 +177,6 @@ function createTargetsElement({entry, expanded, style = entry.styleMeta}) {
|
|||
let el = entryTargets.firstElementChild;
|
||||
let numTargets = 0;
|
||||
let allTargetsRendered = true;
|
||||
const maxTargets = expanded ? 1000 : newUI.enabled ? newUI.targets : 10;
|
||||
const displayed = new Set();
|
||||
for (const type of TARGET_TYPES) {
|
||||
for (const section of style.sections) {
|
||||
for (const targetValue of section[type] || []) {
|
||||
|
@ -339,35 +354,40 @@ function padLeft(val, width) {
|
|||
return ' '.repeat(Math.max(0, width - val.length)) + val;
|
||||
}
|
||||
|
||||
function fitNameColumn(styles) {
|
||||
const align = 1e9; // required by sort()
|
||||
const lengths = styles.map(s => align +
|
||||
(s = s.displayName || s.name || '').length +
|
||||
s.replace(/[^\u3000-\uFE00]+/g, '').length).sort(); // CJK glyphs are twice as wide
|
||||
const pick = .8; // for example, .8 = 80% in single line, 20% multiline
|
||||
const extras = 5; // an average for " UC ", "v1.0.0"
|
||||
const res = lengths[styles.length * pick | 0] - align + extras;
|
||||
$.root.style.setProperty('--name-width', res + 'ch');
|
||||
}
|
||||
|
||||
function fitSizeColumn(entries) {
|
||||
const max = entries.reduce((res, e) => Math.max(res, e.styleSize), 0);
|
||||
$.root.style.setProperty('--size-width', renderSize(max).length + 'ch');
|
||||
}
|
||||
|
||||
function showStyles(styles = [], matchUrlIds) {
|
||||
const sorted = sorter.sort({
|
||||
styles: styles.map(style => {
|
||||
const name = style.customName || style.name || '';
|
||||
return {
|
||||
style,
|
||||
// sort case-insensitively the whole list then sort dupes like `Foo` and `foo` case-sensitively
|
||||
name: name.toLocaleLowerCase() + '\n' + name,
|
||||
};
|
||||
}),
|
||||
});
|
||||
const dummies = styles.map(styleToDummyEntry);
|
||||
const sorted = sorter.sort(dummies);
|
||||
let index = 0;
|
||||
let firstRun = true;
|
||||
installed.dataset.total = styles.length;
|
||||
const scrollY = (history.state || {}).scrollY;
|
||||
const shouldRenderAll = scrollY > window.innerHeight || sessionStore.justEditedStyleId;
|
||||
const renderBin = document.createDocumentFragment();
|
||||
if (scrollY) {
|
||||
renderStyles();
|
||||
} else {
|
||||
requestAnimationFrame(renderStyles);
|
||||
}
|
||||
fitNameColumn(styles);
|
||||
fitSizeColumn(dummies);
|
||||
renderStyles();
|
||||
|
||||
function renderStyles() {
|
||||
const t0 = performance.now();
|
||||
while (index < sorted.length && (shouldRenderAll || performance.now() - t0 < 20)) {
|
||||
const info = sorted[index++];
|
||||
const entry = createStyleElement(info);
|
||||
if (matchUrlIds && !matchUrlIds.includes(info.style.id)) {
|
||||
while (index < sorted.length && (shouldRenderAll || performance.now() - t0 < 50)) {
|
||||
const entry = createStyleElement(sorted[index++]);
|
||||
if (matchUrlIds && !matchUrlIds.includes(entry.styleMeta.id)) {
|
||||
entry.classList.add('not-matching');
|
||||
}
|
||||
renderBin.appendChild(entry);
|
||||
|
@ -388,18 +408,26 @@ function showStyles(styles = [], matchUrlIds) {
|
|||
}
|
||||
}
|
||||
|
||||
function styleToDummyEntry(style) {
|
||||
const name = style.customName || style.name || '';
|
||||
return {
|
||||
styleMeta: style,
|
||||
styleSize: calcObjSize(style),
|
||||
// sort case-insensitively the whole list then sort dupes like `Foo` and `foo` case-sensitively
|
||||
styleNameLC: name.toLocaleLowerCase() + '\n' + name,
|
||||
};
|
||||
}
|
||||
|
||||
/* exported switchUI */
|
||||
function switchUI({styleOnly} = {}) {
|
||||
const current = {};
|
||||
const changed = {};
|
||||
let someChanged = false;
|
||||
for (const id of newUI.ids) {
|
||||
const value = prefs.get(newUI.prefKeyForId(id));
|
||||
newUI.readPrefs(current, (id, value) => {
|
||||
const valueChanged = value !== newUI[id] && (id === 'enabled' || current.enabled);
|
||||
current[id] = value;
|
||||
changed[id] = valueChanged;
|
||||
someChanged |= valueChanged;
|
||||
}
|
||||
});
|
||||
|
||||
if (!styleOnly && !someChanged) {
|
||||
return;
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
/* global $ $create messageBoxProxy */// dom.js
|
||||
/* global $ $create dom messageBoxProxy */// dom.js
|
||||
/* global installed */// manage.js
|
||||
/* global prefs */
|
||||
/* global t */// localization.js
|
||||
|
@ -6,6 +6,10 @@
|
|||
|
||||
const sorter = (() => {
|
||||
|
||||
const COL_MIN = 300; // same as options.html
|
||||
const COL_MAX = 9999; // same as options.html
|
||||
const COL_PROP = '--columns';
|
||||
|
||||
const sorterType = {
|
||||
alpha: (a, b) => a < b ? -1 : a === b ? 0 : 1,
|
||||
number: (a, b) => (a || 0) - (b || 0),
|
||||
|
@ -14,27 +18,32 @@ const sorter = (() => {
|
|||
const tagData = {
|
||||
title: {
|
||||
text: t('genericTitle'),
|
||||
parse: ({name}) => name,
|
||||
parse: v => v.styleNameLC,
|
||||
sorter: sorterType.alpha,
|
||||
},
|
||||
usercss: {
|
||||
text: 'Usercss',
|
||||
parse: ({style}) => style.usercssData ? 0 : 1,
|
||||
parse: v => v.styleMeta.usercssData ? 0 : 1,
|
||||
sorter: sorterType.number,
|
||||
},
|
||||
disabled: {
|
||||
text: '', // added as either "enabled" or "disabled" by the addOptions function
|
||||
parse: ({style}) => style.enabled ? 1 : 0,
|
||||
parse: v => v.styleMeta.enabled ? 1 : 0,
|
||||
sorter: sorterType.number,
|
||||
},
|
||||
dateInstalled: {
|
||||
text: t('dateInstalled'),
|
||||
parse: ({style}) => style.installDate,
|
||||
parse: v => v.styleMeta.installDate,
|
||||
sorter: sorterType.number,
|
||||
},
|
||||
dateUpdated: {
|
||||
text: t('dateUpdated'),
|
||||
parse: ({style}) => style.updateDate || style.installDate,
|
||||
parse: ({styleMeta: s}) => s.updateDate || s.installDate,
|
||||
sorter: sorterType.number,
|
||||
},
|
||||
size: {
|
||||
text: t('genericSize'),
|
||||
parse: v => v.styleSize,
|
||||
sorter: sorterType.number,
|
||||
},
|
||||
};
|
||||
|
@ -53,6 +62,7 @@ const sorter = (() => {
|
|||
'disabled,asc, title,asc',
|
||||
'disabled,desc, title,asc',
|
||||
'disabled,desc, usercss,asc, title,asc',
|
||||
'size,desc, title,asc',
|
||||
'{groupDesc}',
|
||||
'title,desc',
|
||||
'usercss,asc, title,desc',
|
||||
|
@ -65,12 +75,13 @@ const sorter = (() => {
|
|||
const getPref = () => prefs.get(ID) || prefs.defaults[ID];
|
||||
|
||||
let columns = 1;
|
||||
let minWidth;
|
||||
|
||||
function init() {
|
||||
prefs.subscribe(ID, sorter.update);
|
||||
$('#sorter-help').onclick = showHelp;
|
||||
addOptions();
|
||||
updateColumnCount();
|
||||
prefs.subscribe('manage.minColumnWidth', updateColumnWidth, {runNow: true});
|
||||
}
|
||||
|
||||
function addOptions() {
|
||||
|
@ -120,7 +131,7 @@ const sorter = (() => {
|
|||
|
||||
init,
|
||||
|
||||
sort({styles}) {
|
||||
sort(styles) {
|
||||
const sortBy = getPref().split(splitRegex);
|
||||
const len = sortBy.length;
|
||||
return styles.sort((a, b) => {
|
||||
|
@ -140,17 +151,9 @@ const sorter = (() => {
|
|||
update() {
|
||||
if (!installed) return;
|
||||
const current = [...installed.children];
|
||||
const sorted = sorter.sort({
|
||||
styles: current.map(entry => ({
|
||||
entry,
|
||||
name: entry.styleNameLowerCase,
|
||||
style: entry.styleMeta,
|
||||
})),
|
||||
});
|
||||
if (current.some((entry, index) => entry !== sorted[index].entry)) {
|
||||
const renderBin = document.createDocumentFragment();
|
||||
sorted.forEach(({entry}) => renderBin.appendChild(entry));
|
||||
installed.appendChild(renderBin);
|
||||
const sorted = sorter.sort([...current]);
|
||||
if (current.some((el, i) => el !== sorted[i])) {
|
||||
installed.append(...sorted);
|
||||
}
|
||||
sorter.updateStripes();
|
||||
},
|
||||
|
@ -174,21 +177,40 @@ const sorter = (() => {
|
|||
};
|
||||
|
||||
function updateColumnCount() {
|
||||
let newValue = 1;
|
||||
for (let el = $.root.lastElementChild;
|
||||
el.localName === 'style';
|
||||
el = el.previousElementSibling) {
|
||||
if (el.textContent.includes('--columns:')) {
|
||||
newValue = Math.max(1, getComputedStyle($.root).getPropertyValue('--columns') | 0);
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (columns !== newValue) {
|
||||
columns = newValue;
|
||||
const useStyle = [].some.call($.root.children,
|
||||
el => el.tagName === 'STYLE' && el.textContent.includes(COL_PROP + ':'));
|
||||
const v = useStyle ? Math.max(1, getComputedStyle($.root).getPropertyValue(COL_PROP) >> 0)
|
||||
: minWidth ? onResize()
|
||||
: columns;
|
||||
if (columns !== v) {
|
||||
columns = v;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
function updateColumnWidth(_, val) {
|
||||
minWidth = Math.max(val, COL_MIN);
|
||||
if (val < COL_MAX) {
|
||||
window.on('resize', onResize);
|
||||
} else {
|
||||
window.off('resize', onResize);
|
||||
$.root.style.removeProperty(COL_PROP);
|
||||
}
|
||||
sorter.updateStripes({onlyWhenColumnsChanged: true});
|
||||
}
|
||||
|
||||
function onResize(evt) {
|
||||
const c = Math.max(1, (window.innerWidth - dom.HWval) / minWidth >> 0);
|
||||
if (columns !== c) {
|
||||
$.root.style.setProperty(COL_PROP, c);
|
||||
if (evt) {
|
||||
columns = c;
|
||||
sorter.updateStripes();
|
||||
}
|
||||
}
|
||||
return c;
|
||||
}
|
||||
|
||||
async function showHelp(event) {
|
||||
event.preventDefault();
|
||||
messageBoxProxy.show({
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "Stylus",
|
||||
"version": "1.5.26",
|
||||
"version": "1.5.28",
|
||||
"minimum_chrome_version": "55",
|
||||
"description": "__MSG_description__",
|
||||
"homepage_url": "https://add0n.com/stylus.html",
|
||||
|
|
|
@ -66,7 +66,7 @@
|
|||
<h1 i18n="optionsCustomizeUpdate"></h1>
|
||||
<div class="items">
|
||||
<label>
|
||||
<span i18n="optionsUpdateInterval">
|
||||
<span i18n="optionsUpdateInterval" data-clickable="0">
|
||||
<a i18n="title:optionsUpdateImportNote"
|
||||
data-cmd="note" class="svg-inline-wrapper" tabindex="0">
|
||||
<svg class="svg-icon info"><use xlink:href="#svg-icon-help"/></svg>
|
||||
|
@ -240,6 +240,10 @@
|
|||
<div class="block">
|
||||
<h1 i18n="openManage"></h1>
|
||||
<div class="items">
|
||||
<label>
|
||||
<span i18n="manageMinColumnWidth" data-clickable="9999"></span>
|
||||
<input id="manage.minColumnWidth" type="number" min="300" max="9999">
|
||||
</label>
|
||||
<label>
|
||||
<span i18n="manageNewUI"></span>
|
||||
<span class="onoffswitch">
|
||||
|
@ -268,7 +272,7 @@
|
|||
</label>
|
||||
<label>
|
||||
<span i18n="manageMaxTargets"></span>
|
||||
<input id="manage.newUI.targets" type="number" min="1" max="99">
|
||||
<input id="manage.newUI.targets" type="number" min="0" max="99">
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -47,6 +47,11 @@ a:hover .svg-icon,
|
|||
fill: var(--fg);
|
||||
}
|
||||
|
||||
.clickable {
|
||||
text-decoration: underline dotted;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.svg-inline-wrapper .svg-icon {
|
||||
pointer-events: none;
|
||||
}
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
/* global API */// msg.js
|
||||
/* global prefs */
|
||||
/* global t */// localization.js
|
||||
/* global $ $$ getEventKeyName messageBoxProxy setupLivePrefs */// dom.js
|
||||
/* global $ $$ $create getEventKeyName messageBoxProxy setInputValue setupLivePrefs */// dom.js
|
||||
/* global
|
||||
CHROME_POPUP_BORDER_BUG
|
||||
FIREFOX
|
||||
|
@ -44,6 +44,14 @@ $('#reset').onclick = async () => {
|
|||
}
|
||||
}
|
||||
};
|
||||
$$('[data-clickable]').forEach(el => {
|
||||
const input = $('input', el.closest('label'));
|
||||
const value = el.dataset.clickable;
|
||||
const rx = new RegExp(`\\b(${value})\\b`, 'g');
|
||||
const onclick = () => setInputValue(input, value);
|
||||
const parts = elementize(el.textContent, rx, s => $create('span.clickable', {onclick}, s));
|
||||
el.firstChild.replaceWith(...parts);
|
||||
});
|
||||
|
||||
function customizeHotkeys() {
|
||||
messageBoxProxy.show({
|
||||
|
@ -73,6 +81,10 @@ function customizeHotkeys() {
|
|||
}
|
||||
}
|
||||
|
||||
function elementize(str, rx, cb) {
|
||||
return str.split(rx).map((s, i) => i % 2 ? cb(s) : s).filter(Boolean);
|
||||
}
|
||||
|
||||
function enforceInputRange(element) {
|
||||
const min = Number(element.min);
|
||||
const max = Number(element.max);
|
||||
|
|
7098
package-lock.json
generated
7098
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "Stylus",
|
||||
"version": "1.5.26",
|
||||
"version": "1.5.28",
|
||||
"description": "Redesign the web with Stylus, a user styles manager",
|
||||
"license": "GPL-3.0-only",
|
||||
"repository": "openstyles/stylus",
|
||||
|
@ -10,7 +10,7 @@
|
|||
},
|
||||
"dependencies": {
|
||||
"@eight04/draggable-list": "^0.3.0",
|
||||
"codemirror": "5.65.7",
|
||||
"codemirror": "5.65.8",
|
||||
"db-to-cloud": "^0.7.0",
|
||||
"jsonlint": "^1.6.3",
|
||||
"less-bundle": "github:openstyles/less-bundle#v0.1.0",
|
||||
|
@ -27,7 +27,7 @@
|
|||
"glob": "^8.0.3",
|
||||
"node-fetch": "^2.6.7",
|
||||
"sync-version": "^1.0.1",
|
||||
"web-ext": "^6.8.0"
|
||||
"web-ext": "^7.2.0"
|
||||
},
|
||||
"scripts": {
|
||||
"lint": "eslint \"**/*.js\" --cache",
|
||||
|
|
|
@ -98,6 +98,10 @@
|
|||
</div>
|
||||
</template>
|
||||
|
||||
<template data-id="writeForFrames">
|
||||
<div id="write-for-frames" tabindex="0"><iframe>...</div>
|
||||
</template>
|
||||
|
||||
<script src="js/polyfill.js"></script>
|
||||
<script src="js/msg.js"></script>
|
||||
<script src="js/toolbox.js"></script>
|
||||
|
@ -143,7 +147,6 @@
|
|||
<svg class="svg-icon checked"><use xlink:href="#svg-icon-checked"/></svg>
|
||||
<div>U</div>
|
||||
</label>
|
||||
<a id="write-for-frames" title="‹iframe›..." tabindex="0" hidden></a>
|
||||
<span id="write-style-for" i18n="writeStyleFor"></span>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -491,6 +491,7 @@ a:hover .svg-icon {
|
|||
margin-left: .5rem;
|
||||
}
|
||||
|
||||
#write-for-frames::before,
|
||||
.match .match::before {
|
||||
content: "";
|
||||
width: .25rem;
|
||||
|
@ -510,33 +511,18 @@ a:hover .svg-icon {
|
|||
display: none;
|
||||
}
|
||||
|
||||
#write-for-frames:not([hidden]) {
|
||||
position: relative;
|
||||
width: 5px;
|
||||
height: 5px;
|
||||
display: inline-block;
|
||||
margin: 0 2px 2px;
|
||||
--dash: transparent 2px, currentColor 2px, currentColor 3px, transparent 3px;
|
||||
background: linear-gradient(var(--dash)), linear-gradient(90deg, var(--dash));
|
||||
#write-for-frames {
|
||||
cursor: pointer;
|
||||
margin: 0 0 -.25em .5rem;
|
||||
color: var(--c50);
|
||||
transition: color .2s;
|
||||
}
|
||||
#write-for-frames:hover {
|
||||
color: var(--fg);
|
||||
}
|
||||
|
||||
#write-for-frames.expanded {
|
||||
background: linear-gradient(var(--dash));
|
||||
}
|
||||
|
||||
#write-for-frames::after {
|
||||
position: absolute;
|
||||
margin: -2px;
|
||||
border: 1px solid currentColor;
|
||||
content: "";
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
}
|
||||
|
||||
#write-for-frames:not(.expanded) ~ .match:not([data-frame-id="0"]),
|
||||
#write-for-frames:not(.expanded) ~ .match .match {
|
||||
#write-style:not(.expanded) .match:not([data-frame-id="0"]),
|
||||
#write-style:not(.expanded) .match .match {
|
||||
display: none;
|
||||
}
|
||||
|
||||
|
@ -624,11 +610,11 @@ a:hover .svg-icon {
|
|||
fill: transparent;
|
||||
stroke: currentColor;
|
||||
}
|
||||
html:not(.styles-last) #popup-options .split-btn-menu {
|
||||
html:not(.styles-last):not(.search-results-shown) #popup-options .split-btn-menu {
|
||||
bottom: 0;
|
||||
transform: translateY(-20px); /* global button style: 13(font) * 1.2(line) + 4(pad) + 2(border) */
|
||||
}
|
||||
html:not(.styles-last) .split-btn-pedal::after {
|
||||
html:not(.styles-last):not(.search-results-shown) .split-btn-pedal::after {
|
||||
border-top: var(--side) solid transparent;
|
||||
border-bottom: calc(var(--side) * 1.3) solid currentColor;
|
||||
vertical-align: top;
|
||||
|
|
|
@ -144,10 +144,15 @@ async function initPopup(frames) {
|
|||
}
|
||||
|
||||
frames.forEach(createWriterElement);
|
||||
Object.assign($('#write-for-frames'), {
|
||||
onclick: e => e.currentTarget.classList.toggle('expanded'),
|
||||
hidden: frames.length < 2 || !$('.match .match:not(.dupe)'),
|
||||
});
|
||||
|
||||
if (frames.length > 1 && $('.match .match:not(.dupe)')) {
|
||||
$('#write-style').append(Object.assign(t.template.writeForFrames, {
|
||||
onclick() {
|
||||
this.remove();
|
||||
$('#write-style').classList.add('expanded');
|
||||
},
|
||||
}));
|
||||
}
|
||||
|
||||
const isStore = tabURL.startsWith(URLS.browserWebStore);
|
||||
if (isStore && !FIREFOX) {
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
This file is loaded dynamically when the inline search is invoked.
|
||||
So don't put main popup's stuff here. */
|
||||
|
||||
body.search-results-shown {
|
||||
.search-results-shown body {
|
||||
overflow-y: auto;
|
||||
overflow-x: hidden;
|
||||
}
|
||||
|
@ -134,6 +134,10 @@ body.search-results-shown {
|
|||
width: 100%;
|
||||
}
|
||||
|
||||
.search-result[data-installed] {
|
||||
box-shadow: 1px 1px 10px darkcyan;
|
||||
border-color: darkcyan;
|
||||
}
|
||||
.search-result:not([data-installed]) .search-result-actions {
|
||||
opacity: 0;
|
||||
transition: opacity .5s;
|
||||
|
@ -288,35 +292,39 @@ search-result-meta [data-type="rating"][data-class="none"] dd {
|
|||
flex-direction: row;
|
||||
text-align: center;
|
||||
word-break: keep-all;
|
||||
line-height: 24px;
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
.search-results-nav[data-type="top"] {
|
||||
padding-top: 1em;
|
||||
margin-bottom: 1em;
|
||||
}
|
||||
|
||||
.search-results-nav[data-type="bottom"] {
|
||||
margin-top: -1em;
|
||||
margin-bottom: 1em;
|
||||
}
|
||||
|
||||
.search-results-nav label {
|
||||
vertical-align: middle;
|
||||
-moz-user-select: none;
|
||||
user-select: none;
|
||||
}
|
||||
.search-results-nav [data-type="page"] {
|
||||
font-weight: bold;
|
||||
}
|
||||
#search-results .search-results-nav button {
|
||||
background: none;
|
||||
border: none;
|
||||
padding: 0 1rem;
|
||||
padding: 0 .5rem;
|
||||
margin: 0 .5rem;
|
||||
font-size: 150%;
|
||||
line-height: 24px;
|
||||
font-size: 18px;
|
||||
vertical-align: middle;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
#search-results .search-results-nav button:disabled {
|
||||
cursor: auto;
|
||||
opacity: .25;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
#search-results .search-results-nav button:not(:disabled):hover {
|
||||
text-shadow: 0 1px 4px rgba(0, 0, 0, .5);
|
||||
}
|
||||
|
@ -338,3 +346,8 @@ search-result-meta [data-type="rating"][data-class="none"] dd {
|
|||
margin-right: .5em;
|
||||
flex: 1 1 0;
|
||||
}
|
||||
|
||||
#search-years {
|
||||
text-align: center;
|
||||
width: 100%;
|
||||
}
|
||||
|
|
|
@ -3,6 +3,17 @@
|
|||
<div id="search-results" hidden>
|
||||
<div class="search-results-nav" data-type="top"></div>
|
||||
<div id="search-params">
|
||||
<div id="search-years">
|
||||
<div class="select-resizer">
|
||||
<select></select>
|
||||
<svg class="svg-icon select-arrow"><use xlink:href="#svg-icon-select-arrow"/></svg>
|
||||
</div>
|
||||
-
|
||||
<div class="select-resizer">
|
||||
<select></select>
|
||||
<svg class="svg-icon select-arrow"><use xlink:href="#svg-icon-select-arrow"/></svg>
|
||||
</div>
|
||||
</div>
|
||||
<input id="search-query" type="search" i18n="placeholder:search, title:searchStyleQueryHint">
|
||||
<div class="select-resizer">
|
||||
<select id="search-order" i18n="title:sortStylesHelpTitle">
|
||||
|
@ -31,6 +42,7 @@
|
|||
<span data-type="page" i18n="title:paginationCurrent">-</span>
|
||||
/
|
||||
<span data-type="total" i18n="title:paginationEstimated">-</span>
|
||||
(<span data-type="num"></span>)
|
||||
</label>
|
||||
<button data-type="next" i18n="title:paginationNext" disabled>►</button>
|
||||
</div>
|
||||
|
|
204
popup/search.js
204
popup/search.js
|
@ -2,7 +2,7 @@
|
|||
/* global $entry tabURL */// popup.js
|
||||
/* global API */// msg.js
|
||||
/* global Events */
|
||||
/* global FIREFOX URLS debounce download tryURL */// toolbox.js
|
||||
/* global FIREFOX URLS debounce download isEmptyObj stringAsRegExp tryRegExp tryURL */// toolbox.js
|
||||
/* global prefs */
|
||||
/* global t */// localization.js
|
||||
'use strict';
|
||||
|
@ -17,7 +17,7 @@
|
|||
title: URLS.usw,
|
||||
});
|
||||
const STYLUS_CATEGORY = 'chrome-extension';
|
||||
const PAGE_LENGTH = 10;
|
||||
const PAGE_LENGTH = 100;
|
||||
// update USO style install counter if the style isn't uninstalled immediately
|
||||
const PINGBACK_DELAY = 5e3;
|
||||
const BUSY_DELAY = .5e3;
|
||||
|
@ -27,7 +27,7 @@
|
|||
/**
|
||||
* @typedef IndexEntry
|
||||
* @prop {'uso' | 'uso-android'} f - format
|
||||
* @prop {Number} i - id
|
||||
* @prop {Number} i - id, later replaced with string like `uso-123`
|
||||
* @prop {string} n - name
|
||||
* @prop {string} c - category
|
||||
* @prop {Number} u - updatedTime
|
||||
|
@ -38,23 +38,28 @@
|
|||
* @prop {string} an - authorName
|
||||
* @prop {string} sn - screenshotName
|
||||
* @prop {boolean} sa - screenshotArchived
|
||||
* --------------------- Stylus' internally added extras
|
||||
* @prop {boolean} installed
|
||||
* @prop {number} installedStyleId
|
||||
*
|
||||
* @prop {number} _styleId - installed style id
|
||||
* @prop {boolean} _styleVars - installed style has vars
|
||||
* @prop {number} _year
|
||||
*/
|
||||
/** @type IndexEntry[] */
|
||||
let results;
|
||||
let results, resultsAllYears;
|
||||
/** @type IndexEntry[] */
|
||||
let index;
|
||||
let category = '';
|
||||
/** @type RegExp */
|
||||
let rxCategory;
|
||||
let searchGlobals = $('#search-globals').checked;
|
||||
/** @type string[] */
|
||||
/** @type {RegExp[]} */
|
||||
let query = [];
|
||||
let order = prefs.get('popup.findSort');
|
||||
let scrollToFirstResult = true;
|
||||
let displayedPage = 1;
|
||||
let totalPages = 1;
|
||||
let ready;
|
||||
/** @type {?Promise} */
|
||||
let indexing;
|
||||
|
||||
let imgType = '.jpg';
|
||||
// detect WebP support
|
||||
|
@ -68,6 +73,7 @@
|
|||
const entry = el.closest(RESULT_SEL);
|
||||
return {entry, result: entry && entry._result};
|
||||
};
|
||||
const rid2id = rid => rid.split('-')[1];
|
||||
Events.searchInline = () => {
|
||||
calcCategory();
|
||||
ready = start();
|
||||
|
@ -97,17 +103,24 @@
|
|||
};
|
||||
$('#search-query').oninput = function () {
|
||||
query = [];
|
||||
const text = this.value.trim().toLocaleLowerCase();
|
||||
const thisYear = new Date().getFullYear();
|
||||
for (let re = /"(.+?)"|(\S+)/g, m; (m = re.exec(text));) {
|
||||
const n = Number(m[2]);
|
||||
query.push(n >= 2000 && n <= thisYear ? n : m[1] || m[2]);
|
||||
const text = this.value.trim();
|
||||
for (let re = /(")(.+?)"|((\/)?(\S+?)(\/\w*)?)(?=\s|$)/g, m; (m = re.exec(text));) {
|
||||
const [
|
||||
all,
|
||||
q, qt,
|
||||
t, rx1 = '', rx, rx2 = '',
|
||||
] = m;
|
||||
query.push(rx1 && rx2 && tryRegExp(rx, rx2.slice(1)) ||
|
||||
stringAsRegExp(q ? qt : t, all === all.toLocaleLowerCase() ? 'i' : ''));
|
||||
}
|
||||
if (category === STYLUS_CATEGORY && !query.includes('stylus')) {
|
||||
query.push('stylus');
|
||||
if (category === STYLUS_CATEGORY) {
|
||||
query.push(/\bStylus\b/);
|
||||
}
|
||||
ready = ready.then(start);
|
||||
};
|
||||
$('#search-years').onchange = () => {
|
||||
ready = ready.then(() => start({keepYears: true}));
|
||||
};
|
||||
$('#search-order').value = order;
|
||||
$('#search-order').onchange = function () {
|
||||
order = this.value;
|
||||
|
@ -146,18 +159,22 @@
|
|||
|
||||
window.on('styleDeleted', ({detail: {style: {id}}}) => {
|
||||
restoreScrollPosition();
|
||||
const result = results.find(r => r.installedStyleId === id);
|
||||
if (result) {
|
||||
API.uso.pingback(result.i, false);
|
||||
renderActionButtons(result.i, -1);
|
||||
const r = results.find(r => r._styleId === id);
|
||||
if (r) {
|
||||
if (r.f) API.uso.pingback(rid2id(r.i), false);
|
||||
delete r._styleId;
|
||||
renderActionButtons(r.i);
|
||||
}
|
||||
});
|
||||
|
||||
window.on('styleAdded', async ({detail: {style}}) => {
|
||||
restoreScrollPosition();
|
||||
const id = calcId(style) || calcId(await API.styles.get(style.id));
|
||||
if (id && results.find(r => r.i === id)) {
|
||||
renderActionButtons(id, style.id);
|
||||
const ri = await API.styles.getRemoteInfo(style.id);
|
||||
const r = ri && results.find(r => ri[0] === r.i);
|
||||
if (r) {
|
||||
r._styleId = style.id;
|
||||
r._styleVars = ri[1];
|
||||
renderActionButtons(ri[0]);
|
||||
}
|
||||
});
|
||||
|
||||
|
@ -178,11 +195,18 @@
|
|||
dom.error.hidden = false;
|
||||
dom.list.hidden = true;
|
||||
if (dom.error.getBoundingClientRect().bottom < 0) {
|
||||
dom.error.scrollIntoView({behavior: 'smooth', block: 'start'});
|
||||
dom.error.scrollIntoView(true);
|
||||
}
|
||||
}
|
||||
|
||||
async function start() {
|
||||
function errorIfNoneFound() {
|
||||
if (!results.length && !$('#search-query').value) {
|
||||
return indexing ? indexing.then(errorIfNoneFound)
|
||||
: Promise.reject(t('searchResultNoneFound'));
|
||||
}
|
||||
}
|
||||
|
||||
async function start({keepYears} = {}) {
|
||||
resetUI.timer = resetUI.timer || setTimeout(resetUI, 500);
|
||||
try {
|
||||
results = [];
|
||||
|
@ -190,17 +214,17 @@
|
|||
results = await search({retry});
|
||||
}
|
||||
if (results.length) {
|
||||
const installedStyles = await API.styles.getAll();
|
||||
const allSupportedIds = new Set(installedStyles.map(calcId));
|
||||
results = results.filter(r => !allSupportedIds.has(r.i));
|
||||
const info = await API.styles.getRemoteInfo();
|
||||
for (const r of results) {
|
||||
[r._styleId, r._styleVars] = info[r.i] || [];
|
||||
}
|
||||
}
|
||||
if (!keepYears) resultsAllYears = results;
|
||||
renderYears();
|
||||
render();
|
||||
dom.list.hidden = !results.length;
|
||||
if (!results.length && !$('#search-query').value) {
|
||||
if (index._ready) error(t('searchResultNoneFound'));
|
||||
} else {
|
||||
resetUI();
|
||||
}
|
||||
await errorIfNoneFound();
|
||||
resetUI();
|
||||
} catch (reason) {
|
||||
error(reason);
|
||||
}
|
||||
|
@ -209,12 +233,44 @@
|
|||
}
|
||||
|
||||
function resetUI() {
|
||||
document.body.classList.add('search-results-shown');
|
||||
$.rootCL.add('search-results-shown');
|
||||
dom.container.hidden = false;
|
||||
dom.list.hidden = false;
|
||||
dom.error.hidden = true;
|
||||
}
|
||||
|
||||
function renderYears() {
|
||||
const SCALE = 1000;
|
||||
const BASE = new Date(0).getFullYear(); // 1970
|
||||
const DAYS = 365.2425;
|
||||
const DAY = 24 * 3600e3;
|
||||
const YEAR = DAYS * DAY / SCALE;
|
||||
const SAFETY = 1 / DAYS; // 1 day safety margin: recheck Jan 1 and Dec 31
|
||||
const years = [];
|
||||
for (const r of resultsAllYears) {
|
||||
let y = r._year;
|
||||
if (!y) {
|
||||
y = r.u / YEAR + BASE;
|
||||
r._year = y = Math.abs(y % 1 - 1) <= SAFETY
|
||||
? new Date(r.u * SCALE).getFullYear()
|
||||
: y | 0;
|
||||
}
|
||||
years[y] = (years[y] || 0) + 1;
|
||||
}
|
||||
const texts = years.reduceRight((res, num, y) => res.push(`${y} (${num})`) && res, []);
|
||||
const selects = $$('#search-years select');
|
||||
selects.forEach((sel, selNum) => {
|
||||
if (texts.length !== sel.length || texts.some((v, i) => v !== sel[i].text)) {
|
||||
const {value} = sel;
|
||||
sel.textContent = '';
|
||||
sel.append(...texts.map(t => $create('option', {value: t.split(' ')[0]}, t)));
|
||||
sel.value = value in years ? value : (sel[`${selNum ? 'first' : 'last'}Child`] || {}).value;
|
||||
}
|
||||
});
|
||||
const [y1, y2] = selects.map(el => Number(el.value)).sort();
|
||||
results = y1 ? resultsAllYears.filter(r => (r = r._year) >= y1 && r <= y2) : resultsAllYears;
|
||||
}
|
||||
|
||||
function render() {
|
||||
totalPages = Math.ceil(results.length / PAGE_LENGTH);
|
||||
displayedPage = Math.min(displayedPage, totalPages) || 1;
|
||||
|
@ -262,13 +318,14 @@
|
|||
nav._next.disabled = displayedPage >= totalPages;
|
||||
nav._page.textContent = displayedPage;
|
||||
nav._total.textContent = totalPages;
|
||||
nav._num.textContent = results.length;
|
||||
}
|
||||
}
|
||||
|
||||
function doScrollToFirstResult() {
|
||||
if (dom.container.scrollHeight > window.innerHeight * 2) {
|
||||
scrollToFirstResult = false;
|
||||
dom.container.scrollIntoView({behavior: 'smooth', block: 'start'});
|
||||
dom.container.scrollIntoView(true);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -279,7 +336,7 @@
|
|||
function createSearchResultNode(result) {
|
||||
const entry = t.template.searchResult.cloneNode(true);
|
||||
const {
|
||||
i: id,
|
||||
i: rid,
|
||||
n: name,
|
||||
r: rating,
|
||||
u: updateTime,
|
||||
|
@ -291,7 +348,8 @@
|
|||
sn: shot,
|
||||
f: fmt,
|
||||
} = entry._result = result;
|
||||
entry.id = RESULT_ID_PREFIX + id;
|
||||
const id = rid2id(rid);
|
||||
entry.id = RESULT_ID_PREFIX + rid;
|
||||
// title
|
||||
Object.assign($('.search-result-title', entry), {
|
||||
onclick: Events.openURLandHide,
|
||||
|
@ -370,22 +428,19 @@
|
|||
}
|
||||
}
|
||||
|
||||
function renderActionButtons(entry, installedId) {
|
||||
if (Number(entry)) {
|
||||
function renderActionButtons(entry) {
|
||||
if (typeof entry !== 'object') {
|
||||
entry = $('#' + RESULT_ID_PREFIX + entry);
|
||||
}
|
||||
if (!entry) return;
|
||||
const result = entry._result;
|
||||
if (typeof installedId === 'number') {
|
||||
result.installed = installedId > 0;
|
||||
result.installedStyleId = installedId;
|
||||
}
|
||||
const isInstalled = result.installed;
|
||||
const installedId = result._styleId;
|
||||
const isInstalled = installedId > 0; // must be boolean for comparisons below
|
||||
const status = $('.search-result-status', entry).textContent =
|
||||
isInstalled ? t('clickToUninstall') :
|
||||
entry.dataset.noImage != null ? t('installButton') :
|
||||
'';
|
||||
const notMatching = installedId > 0 && !$entry(installedId);
|
||||
const notMatching = isInstalled && !$entry(installedId);
|
||||
if (notMatching !== entry.classList.contains('not-matching')) {
|
||||
entry.classList.toggle('not-matching');
|
||||
if (notMatching) {
|
||||
|
@ -405,6 +460,7 @@
|
|||
disabled: notMatching,
|
||||
});
|
||||
toggleDataset(entry, 'installed', isInstalled);
|
||||
toggleDataset(entry, 'customizable', result._styleVars);
|
||||
}
|
||||
|
||||
function renderFullInfo(entry, style) {
|
||||
|
@ -418,17 +474,20 @@
|
|||
textContent: description,
|
||||
title: description,
|
||||
});
|
||||
vars = !isEmptyObj(vars);
|
||||
entry._result._styleVars = vars;
|
||||
toggleDataset(entry, 'customizable', vars);
|
||||
}
|
||||
|
||||
function configure() {
|
||||
const styleEntry = $entry($resultEntry(this).result.installedStyleId);
|
||||
const styleEntry = $entry($resultEntry(this).result._styleId);
|
||||
Events.configure.call(this, {target: styleEntry});
|
||||
}
|
||||
|
||||
async function install() {
|
||||
const {entry, result} = $resultEntry(this);
|
||||
const {i: id, f: fmt} = result;
|
||||
const {f: fmt} = result;
|
||||
const id = rid2id(result.i);
|
||||
const installButton = $('.search-result-install', entry);
|
||||
|
||||
showSpinner(entry);
|
||||
|
@ -456,7 +515,7 @@
|
|||
function uninstall() {
|
||||
const {entry, result} = $resultEntry(this);
|
||||
saveScrollPosition(entry);
|
||||
API.styles.delete(result.installedStyleId);
|
||||
API.styles.delete(result._styleId);
|
||||
}
|
||||
|
||||
function saveScrollPosition(entry) {
|
||||
|
@ -495,22 +554,24 @@
|
|||
);
|
||||
category = (keepThird && `${third}.` || '') + main + (keepTld || keepThird ? `.${tld}` : '');
|
||||
}
|
||||
rxCategory = new RegExp(`\\b${stringAsRegExp(category, '', true)}\\b`, 'i');
|
||||
return category !== old;
|
||||
}
|
||||
|
||||
async function fetchIndex() {
|
||||
const timer = setTimeout(showSpinner, BUSY_DELAY, dom.list);
|
||||
const jobs = [
|
||||
[INDEX_URL, json => json.filter(entry => entry.f === 'uso')],
|
||||
[USW_INDEX_URL, json => json.data],
|
||||
].map(async ([url, transform]) => {
|
||||
[INDEX_URL, 'uso', json => json.filter(v => v.f === 'uso')],
|
||||
[USW_INDEX_URL, 'usw', json => json.data],
|
||||
].map(async ([url, prefix, transform]) => {
|
||||
const res = transform(await download(url, {responseType: 'json'}));
|
||||
for (const v of res) v.i = `${prefix}-${v.i}`;
|
||||
index = index ? index.concat(res) : res;
|
||||
if (index !== res) ready = ready.then(start);
|
||||
});
|
||||
// TODO: use Promise.allSettled when "minimum_chrome_version" >= 76 and "strict_min_version" >= 71
|
||||
Promise.all(jobs.map(j => j.catch(e => e))).then(() => {
|
||||
index._ready = true;
|
||||
indexing = Promise.all(jobs.map(j => j.catch(e => e))).then(() => {
|
||||
indexing = null;
|
||||
});
|
||||
await Promise.race(jobs);
|
||||
clearTimeout(timer);
|
||||
|
@ -519,31 +580,29 @@
|
|||
}
|
||||
|
||||
async function search({retry} = {}) {
|
||||
return retry && !calcCategory({retry})
|
||||
return retry && !query.length && !calcCategory({retry})
|
||||
? []
|
||||
: (index || await fetchIndex()).filter(isResultMatching).sort(comparator);
|
||||
}
|
||||
|
||||
function isResultMatching(res) {
|
||||
// We're trying to call calcHaystack only when needed, not on all 100K items
|
||||
const {c} = res;
|
||||
return (
|
||||
c === category ||
|
||||
(category === STYLUS_CATEGORY
|
||||
? c === 'stylus' // USW
|
||||
: c === 'global' && searchGlobals &&
|
||||
(query.length || calcHaystack(res)._nLC.includes(category))
|
||||
(query.length || rxCategory.test(res.n))
|
||||
)
|
||||
) && (
|
||||
!query.length || // to skip calling calcHaystack
|
||||
query.every(isInHaystack, calcHaystack(res))
|
||||
);
|
||||
) && query.every(isInHaystack, res);
|
||||
}
|
||||
|
||||
/** @this {IndexEntry} haystack */
|
||||
function isInHaystack(needle) {
|
||||
return this._year === needle && this.c !== 'global' ||
|
||||
this._nLC.includes(needle);
|
||||
/**
|
||||
* @this {IndexEntry} haystack
|
||||
* @param {RegExp} q
|
||||
*/
|
||||
function isInHaystack(q) {
|
||||
return q.test(this.n);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -557,23 +616,4 @@
|
|||
: b[order] - a[order]
|
||||
) || b.t - a.t;
|
||||
}
|
||||
|
||||
function calcUsoId({md5Url: m, updateUrl}) {
|
||||
return Number(m && m.match(/\d+|$/)[0]) ||
|
||||
URLS.extractUsoArchiveId(updateUrl);
|
||||
}
|
||||
|
||||
function calcUswId({updateUrl}) {
|
||||
return URLS.extractUSwId(updateUrl) || 0;
|
||||
}
|
||||
|
||||
function calcId(style) {
|
||||
return calcUsoId(style) || calcUswId(style);
|
||||
}
|
||||
|
||||
function calcHaystack(res) {
|
||||
if (!res._nLC) res._nLC = res.n.toLocaleLowerCase();
|
||||
if (!res._year) res._year = new Date(res.u * 1000).getFullYear();
|
||||
return res;
|
||||
}
|
||||
})();
|
||||
|
|
|
@ -1,13 +1,12 @@
|
|||
'use strict';
|
||||
|
||||
const fs = require('fs');
|
||||
const fse = require('fs-extra');
|
||||
|
||||
const DIR = '_locales/';
|
||||
const RX_LNG_CODE = /^\w\w(_\w{2,3})?$/; // like `en` or `en_GB`
|
||||
|
||||
const makeFileName = lng => `${DIR}${lng}/messages.json`;
|
||||
const readLngJson = lng => fse.readJsonSync(makeFileName(lng));
|
||||
const readLngJson = lng => JSON.parse(fs.readFileSync(makeFileName(lng), 'utf8'));
|
||||
const sortAlpha = ([a], [b]) => a < b ? -1 : a > b;
|
||||
|
||||
const src = readLngJson('en');
|
||||
|
@ -63,7 +62,7 @@ function fixLngFile(lng) {
|
|||
fs.rmSync(`${DIR}${lng}`, {recursive: true, force: true});
|
||||
err = 'no translations -> deleted';
|
||||
} else {
|
||||
fse.outputFileSync(makeFileName(lng), resStr + '\n');
|
||||
fs.writeFileSync(makeFileName(lng), resStr + '\n', 'utf8');
|
||||
err = [
|
||||
numUnknown && `${numUnknown} unknown (dropped)`,
|
||||
numUntranslated && `${numUntranslated} untranslated (dropped)`,
|
||||
|
|
2
vendor/codemirror/README.md
vendored
2
vendor/codemirror/README.md
vendored
|
@ -1,4 +1,4 @@
|
|||
## codemirror v5.65.7
|
||||
## codemirror v5.65.8
|
||||
|
||||
Files copied from NPM (node_modules):
|
||||
* addon/comment/comment.js
|
||||
|
|
6
vendor/codemirror/addon/fold/foldgutter.js
vendored
6
vendor/codemirror/addon/fold/foldgutter.js
vendored
|
@ -21,6 +21,7 @@
|
|||
cm.off("fold", onFold);
|
||||
cm.off("unfold", onFold);
|
||||
cm.off("swapDoc", onChange);
|
||||
cm.off("optionChange", optionChange);
|
||||
}
|
||||
if (val) {
|
||||
cm.state.foldGutter = new State(parseOptions(val));
|
||||
|
@ -31,6 +32,7 @@
|
|||
cm.on("fold", onFold);
|
||||
cm.on("unfold", onFold);
|
||||
cm.on("swapDoc", onChange);
|
||||
cm.on("optionChange", optionChange);
|
||||
}
|
||||
});
|
||||
|
||||
|
@ -120,6 +122,10 @@
|
|||
else cm.foldCode(Pos(line, 0), opts);
|
||||
}
|
||||
|
||||
function optionChange(cm, option) {
|
||||
if (option == "mode") onChange(cm)
|
||||
}
|
||||
|
||||
function onChange(cm) {
|
||||
var state = cm.state.foldGutter;
|
||||
if (!state) return;
|
||||
|
|
11
vendor/codemirror/lib/codemirror.js
vendored
11
vendor/codemirror/lib/codemirror.js
vendored
|
@ -7772,7 +7772,7 @@
|
|||
for (var i = newBreaks.length - 1; i >= 0; i--)
|
||||
{ replaceRange(cm.doc, val, newBreaks[i], Pos(newBreaks[i].line, newBreaks[i].ch + val.length)); }
|
||||
});
|
||||
option("specialChars", /[\u0000-\u001f\u007f-\u009f\u00ad\u061c\u200b\u200e\u200f\u2028\u2029\ufeff\ufff9-\ufffc]/g, function (cm, val, old) {
|
||||
option("specialChars", /[\u0000-\u001f\u007f-\u009f\u00ad\u061c\u200b\u200e\u200f\u2028\u2029\u202d\u202e\u2066\u2067\u2069\ufeff\ufff9-\ufffc]/g, function (cm, val, old) {
|
||||
cm.state.specialChars = new RegExp(val.source + (val.test("\t") ? "" : "|\t"), "g");
|
||||
if (old != Init) { cm.refresh(); }
|
||||
});
|
||||
|
@ -9368,6 +9368,7 @@
|
|||
// Used to work around IE issue with selection being forgotten when focus moves away from textarea
|
||||
this.hasSelection = false;
|
||||
this.composing = null;
|
||||
this.resetting = false;
|
||||
};
|
||||
|
||||
TextareaInput.prototype.init = function (display) {
|
||||
|
@ -9500,8 +9501,9 @@
|
|||
// Reset the input to correspond to the selection (or to be empty,
|
||||
// when not typing and nothing is selected)
|
||||
TextareaInput.prototype.reset = function (typing) {
|
||||
if (this.contextMenuPending || this.composing) { return }
|
||||
if (this.contextMenuPending || this.composing && typing) { return }
|
||||
var cm = this.cm;
|
||||
this.resetting = true;
|
||||
if (cm.somethingSelected()) {
|
||||
this.prevInput = "";
|
||||
var content = cm.getSelection();
|
||||
|
@ -9512,6 +9514,7 @@
|
|||
this.prevInput = this.textarea.value = "";
|
||||
if (ie && ie_version >= 9) { this.hasSelection = null; }
|
||||
}
|
||||
this.resetting = false;
|
||||
};
|
||||
|
||||
TextareaInput.prototype.getField = function () { return this.textarea };
|
||||
|
@ -9573,7 +9576,7 @@
|
|||
// possible when it is clear that nothing happened. hasSelection
|
||||
// will be the case when there is a lot of text in the textarea,
|
||||
// in which case reading its value would be expensive.
|
||||
if (this.contextMenuPending || !cm.state.focused ||
|
||||
if (this.contextMenuPending || this.resetting || !cm.state.focused ||
|
||||
(hasSelection(input) && !prevInput && !this.composing) ||
|
||||
cm.isReadOnly() || cm.options.disableInput || cm.state.keySeq)
|
||||
{ return false }
|
||||
|
@ -9860,7 +9863,7 @@
|
|||
|
||||
addLegacyProps(CodeMirror);
|
||||
|
||||
CodeMirror.version = "5.65.7";
|
||||
CodeMirror.version = "5.65.8";
|
||||
|
||||
return CodeMirror;
|
||||
|
||||
|
|
Loading…
Reference in New Issue
Block a user