Compare commits

..

808 Commits

Author SHA1 Message Date
tophf
fc39f0d5a6 parserlib: dot-separated layer names 2022-10-19 22:47:19 +03:00
tophf
1725c0ecb9 show installed styles in popup finder
fixes #1488
2022-10-12 20:04:45 +03:00
tophf
79dff2775b add upDownKeyJumps option, remove left/right 2022-10-08 13:07:57 +03:00
tophf
b22bbaaec0 make <iframe> more evident 2022-10-08 13:05:40 +03:00
tophf
fff59ee12f use a self-explanatory "..." in #write-for-frames 2022-09-17 21:42:39 +03:00
tophf
540e2af62c micro-optimize colorConverter.parse
#hex, named colors: 15x faster
rgb() and other functions: 1.6x faster
2022-09-17 20:10:18 +03:00
tophf
0128489bbb unbork rgb colors with % 2022-09-17 01:29:11 +03:00
dependabot[bot]
a5b11ac687
Bump node-forge and web-ext (#1474) 2022-09-16 16:24:27 +03:00
Rob Garrison
5d3a9dccf3 1.5.28 2022-09-16 08:11:25 -05:00
tophf
09691a6362 add a comment for USO devs 2022-09-16 15:33:11 +03:00
tophf
62987fe5f8 update locales 2022-09-16 12:47:55 +03:00
tophf
405a93f9e5 no need for fse dep here 2022-09-16 12:47:37 +03:00
tophf
da4bdc6821 API should return something 2022-09-16 12:40:14 +03:00
tophf
d1f5468a81 fix USO install button 2022-09-16 12:39:46 +03:00
tophf
efc6d09d49 properly show "no styles found" 2022-09-15 17:14:13 +03:00
tophf
0bb0d32c29 simplify fitNameColumn
as it wasn't using average and an average is too small anyway
2022-09-15 13:05:00 +03:00
tophf
f5397b8aec simplify hover highlight to an underline 2022-09-15 12:57:01 +03:00
tophf
1594b4dcd8 help auto-hyphenation at word boundaries 2022-09-15 12:54:26 +03:00
tophf
527d7c0fbc add fake header before linking to USW 2022-09-11 20:44:19 +03:00
tophf
cda606e7cc tall applies-to toggle + simplify CSS 2022-09-08 00:20:25 +03:00
tophf
5cb30c8b69 simplify oldUI CSS 2022-09-07 23:57:19 +03:00
tophf
1b914a397e simplify hover animation: 50% less CPU 2022-09-06 21:25:13 +03:00
tophf
7b64deeb37 create templates on demand 2022-09-06 18:41:22 +03:00
tophf
9398857d93 clear searchMode along with search 2022-09-06 01:07:14 +03:00
tophf
b45825c015 preserve current URL params in openManage 2022-09-06 00:54:52 +03:00
tophf
3aae3f181a linkify numbers in option labels 2022-09-05 23:08:03 +03:00
tophf
48d90544f6 reuse setInputValue 2022-09-05 22:41:17 +03:00
tophf
ef998e423e add multi-column mode option 2022-09-04 22:50:27 +03:00
tophf
6979958908 use localization cache 2022-09-04 22:50:26 +03:00
tophf
4236eb4e29 shorten highlight animation
...because it was invisible 90% of time anyway
2022-09-04 22:50:26 +03:00
tophf
c351413c3f show Size column + simplify sorter 2022-09-03 20:08:24 +03:00
tophf
d91cf11366 parserlib: update forced-color-adjust 2022-09-02 09:12:32 +03:00
tophf
e49644f1c8 CodeMirror 5.65.8 2022-09-01 11:35:37 +03:00
tophf
15b59ae207 update locales 2022-09-01 11:35:37 +03:00
tophf
ad43560016 fix and simplify applyScrollInfo 2022-09-01 11:35:24 +03:00
tophf
c423025c5d postpone lazyScripts more 2022-09-01 10:37:29 +03:00
tophf
030621462c error.stack has no message in FF 2022-09-01 10:30:46 +03:00
tophf
bf3dd0318d don't apply global section to Stylus pages
unless it was intentionally targeted via url(),  url-prefix(), or regexp(). The regexp must contain the word "extension" without quotes.
2022-08-31 16:27:31 +03:00
tophf
3489b513c9 [autocomplete] find LESS vars 2022-08-30 10:31:21 +03:00
Rob Garrison
984fa6e425 1.5.27 2022-08-29 17:07:09 -05:00
tophf
6110cbee68 use /* */ for line comments in less/stylus 2022-08-28 22:48:49 +03:00
tophf
40ec2c000f [autocomplete] use parserlib's list of CSS props 2022-08-27 21:58:29 +03:00
tophf
9022f6b318 [autocomplete] add ":" to LESS props 2022-08-27 21:58:28 +03:00
tophf
c5667b0352 hide lint errors for LESS vars 2022-08-27 21:58:27 +03:00
tophf
5379f62c90
add year selector in popup search (#1411) 2022-08-27 21:57:14 +03:00
Rob Garrison
9d3fa1c5f9 1.5.26 2022-08-06 17:48:47 -05:00
tophf
906a5ca960 use license from stylelint-bundle 2022-08-06 23:35:32 +03:00
tophf
e1e351d956 report network failures in build-vendor 2022-08-06 23:35:01 +03:00
tophf
7a5569e9d5 stylelint 14.9.1 2022-08-06 23:20:29 +03:00
tophf
2d21bb1dd6 generate separate zip for FF with options_ui 2022-08-05 18:35:16 +03:00
tophf
8302c50d54 remove the unused identity permission 2022-08-05 18:34:38 +03:00
dependabot[bot]
27a878aed8
Bump moment from 2.29.2 to 2.29.4 (#1463) 2022-08-05 17:56:52 +03:00
tophf
f9e6df116f update deps 2022-08-05 17:50:31 +03:00
tophf
a832b51d9c update locales 2022-08-05 17:41:08 +03:00
tophf
b88a978843 improve orphan check + cosmetics 2022-08-03 23:58:55 +03:00
tophf
685bf1fa3e
fix USO site installation (#1461) 2022-08-03 22:37:04 +03:00
tophf
6995483ec0 parserlib: cosmetics/simplifications 2022-08-03 22:35:01 +03:00
tophf
6ff5f17140 assume bg is ready if messaging succeeded 2022-08-03 22:20:41 +03:00
tophf
7a1045b45a keep valid url path chars intact 2022-07-30 20:48:43 +03:00
tophf
079e7e50f1 retry API on browser startup automatically 2022-07-25 19:28:25 +03:00
tophf
00b732177f speed up regex for block comments 2022-07-23 23:54:24 +03:00
tophf
484ff24950 parserlib: container query 2022-07-17 12:38:07 +03:00
tophf
31177f1017 parserlib: fix custom-ident and use it more 2022-07-17 12:38:07 +03:00
tophf
e406e2b5dc fetch all indexes before showing "not found" 2022-07-07 17:17:33 +03:00
tophf
bd2b435781 parserlib: simplify/flatten _keyframe 2022-07-01 10:29:43 +03:00
tophf
b742ed2c65 parserlib: forbid default in custom-ident 2022-07-01 10:26:46 +03:00
tophf
d68433c867 fix usage of chrome.windows in android 2022-07-01 07:55:11 +03:00
tophf
a10003ee80 parserlib: has() selector 2022-06-30 13:40:39 +03:00
tophf
3fcd4f8027 code cosmetics 2022-06-27 12:42:27 +03:00
tophf
9364ef585f
fix nextcloud WebDAV csrf error (#1448) 2022-06-27 12:41:39 +03:00
tophf
6e0cfb7d0f fix infinite loop in readUnknownSym 2022-06-26 18:13:13 +03:00
tophf
91501cce19 parserlib: simplified media query L4 2022-06-24 21:09:14 +03:00
tophf
95a4514f15 parserlib: object-overflow, object-view-box 2022-06-24 08:25:33 +03:00
tophf
3d6b55e53e error may be an object 2022-06-23 21:29:52 +03:00
tophf
88a0a3858c update locales 2022-06-23 21:21:56 +03:00
tophf
685ee625ee update codemirror 5.65.6 2022-06-23 21:21:56 +03:00
tophf
944a7f23ef update stylus-lang-bundle 0.58.1 2022-06-23 21:16:02 +03:00
tophf
d78329d295
add transifex languages info 2022-06-07 21:08:39 +03:00
tophf
b5fd8b63dc new Vivaldi uses vivExtData 2022-06-04 19:20:20 +03:00
tophf
a77996df60 window type may be 'app' 2022-06-04 18:02:41 +03:00
tophf
2e1961bdf7 messageBox.element is nulled in close() 2022-06-03 18:41:25 +03:00
tophf
cd88aff733 hide undefined label in colorpicker in FF 2022-06-02 21:52:49 +03:00
Rob Garrison
0f3ba30066 1.5.25 2022-05-31 12:11:33 -05:00
tophf
cda6e4fb9e recreate idb with missing stores 2022-05-31 16:08:03 +03:00
tophf
97520be9b2 create missing idb store 2022-05-31 15:32:01 +03:00
Rob Garrison
30af48a69a 1.5.24 2022-05-30 09:24:45 -05:00
Rob Garrison
b33b5ba624 Update locales 2022-05-30 09:11:47 -05:00
tophf
ad5c90e240 actually set INJECTED 2022-04-24 00:21:25 +03:00
tophf
3522a53ad4 limit section's height to viewport 2022-04-11 14:05:02 +03:00
tophf
79ada00a01 don't show reset-name on local classic styles 2022-04-11 09:57:38 +03:00
tophf
a34fe5f400 parserlib: add webkit-optimize-contrast 2022-04-09 09:00:08 +03:00
dependabot[bot]
ffb72cc656
Bump moment from 2.29.1 to 2.29.2 (#1424) 2022-04-09 08:59:30 +03:00
tophf
a74ae5ac4f extract popup search + show error + CtrlF 2022-04-05 13:49:48 +03:00
tophf
3907116328 getEventKeyName: uppercase for single letters 2022-04-05 13:46:37 +03:00
tophf
fd42f2d365 update icon on bfcache navigation 2022-04-04 10:11:41 +03:00
tophf
537372dffa colorpicker: add hwb colors 2022-04-04 09:52:01 +03:00
tophf
f54d145bf5 parserlib: add font-palette, hwb 2022-04-04 09:52:00 +03:00
tophf
2efb6c5cd1 strip source mapping from vendor js 2022-03-31 14:00:12 +03:00
tophf
ea388ea9a7 add [x] as usercss to write-style in popup 2022-03-31 12:27:11 +03:00
tophf
24aaf2fdb8
improve power-off switch (#1412)
* show it in manager/editor when active
* reflect the state in the label when active
2022-03-31 12:25:37 +03:00
tophf
963e58f237 unbork 57a7939b 2022-03-27 22:42:35 +03:00
tophf
57a7939b5e show livepreview error in compact mode 2022-03-27 18:47:52 +03:00
tophf
ec5a685ee2 limit details width in compact mode 2022-03-27 18:47:52 +03:00
dependabot[bot]
b21497c1b2
Bump minimist from 1.2.5 to 1.2.6 (#1420) 2022-03-26 09:12:05 +03:00
tophf
ba1f10b865 fix/simplify editor loader
fixes #1419
2022-03-24 17:52:26 +03:00
Rob Garrison
7ab95cce33 1.5.23 2022-03-19 17:15:43 -05:00
dependabot[bot]
0a84d802d3
Bump node-fetch from 2.6.6 to 2.6.7 (#1415) 2022-03-17 09:21:29 +03:00
dependabot[bot]
2c42f40acb
Bump nanoid from 3.1.30 to 3.3.1 (#1416) 2022-03-17 09:21:17 +03:00
tophf
ee0826e538 update CodeMirror 5.65.2 2022-03-17 09:18:59 +03:00
tophf
411d7f7b95 update locales 2022-03-17 09:18:04 +03:00
tophf
614dce0e8c transition patch is redundant in Chrome 93+ 2022-03-08 23:38:51 +03:00
tophf
619f771b4a strip www for greasyfork search 2022-03-07 01:49:14 +03:00
tophf
9ed550c882 fix popup find error 2022-03-06 03:02:41 +03:00
tophf
6d721fe7a5 actually hide invisible "?" favicons 2022-03-05 22:37:31 +03:00
tophf
d9ad0fec3c parserlib: new values for mix-blend-mode 2022-03-04 19:07:52 +03:00
tophf
d623aa679f properly update hash when closing UI, part 2
for the history log
2022-03-04 08:10:46 +03:00
tophf
9e009726c5 properly update hash when closing UI 2022-03-03 05:23:34 +03:00
tophf
bcc98d913b fix/simplify dom::collapsible
now it observes the `open` attribute as the `click` was too fragile due to dependency on timing
2022-03-02 02:54:25 +03:00
tophf
2e31cae71e instant match hl same as other editors
also enable it when the find dialog is open because selection may be different
2022-02-28 10:18:15 +03:00
tophf
b80c3e2f73 enable activeline highlight with selection 2022-02-28 10:15:11 +03:00
tophf
37baf8a2c6 reuse deepCopy 2022-02-26 10:32:58 +03:00
tophf
b4f10cb296 fix popup buttons in styles-last mode 2022-02-25 03:12:43 +03:00
tophf
8fc6c8bcde cosmetics 2022-02-25 01:26:50 +03:00
tophf
29cafd619c extract initBadFavs 2022-02-25 00:43:21 +03:00
tophf
bd9d51308a simplify context menu + shorten titles 2022-02-24 07:53:14 +03:00
tophf
d2a99b5be1 simplify getProxy 2022-02-24 07:29:11 +03:00
tophf
e567b6d56d use the public defaults 2022-02-24 07:29:11 +03:00
tophf
329d0caac1 avoid flicker/delay when opening manager 2022-02-23 09:07:30 +03:00
tophf
51e9b09f52 load favicons earlier + show bads as ? 2022-02-23 06:11:28 +03:00
tophf
f5b2fe2fc6 properly show another split-btn + tiny popup 2022-02-23 00:17:14 +03:00
tophf
38ac974d8a remember and skip bad favicons 2022-02-22 01:39:03 +03:00
tophf
5ff664f9b9 simplify/generalize close-on-esc 2022-02-21 23:45:42 +03:00
tophf
b75e2cb56b add @-moz-document indent to beautifier 2022-02-21 23:35:49 +03:00
tophf
d162928fb9 right-click to expand all applies-to targets 2022-02-21 07:12:11 +03:00
tophf
ce7137c54d preload nearby favicons 2022-02-21 07:11:09 +03:00
tophf
62974751ff limit disable-all area to its text 2022-02-21 03:18:12 +03:00
tophf
e0c9c13856 explicitly hyphenate domains in popup 2022-02-21 02:36:03 +03:00
tophf
6ff68aaa74 simplify conditional css in popup 2022-02-21 02:24:34 +03:00
tophf
bd0e8273c1 add USA/USW/US/GF splitter to Find styles 2022-02-21 02:01:50 +03:00
tophf
268c7b758b give history log a navigatable #hash 2022-02-20 19:57:11 +03:00
tophf
38ddf5d79d simplify and speed up init in manage/edit 2022-02-20 19:26:26 +03:00
tophf
90a71c0afc deflicker name element in new usercss 2022-02-20 18:34:51 +03:00
tophf
dd1f30e6aa fix popup regression on blocked page 2022-02-19 17:20:32 +03:00
tophf
b804e39de6 workaround for crbug.com/1288447 2022-02-19 17:20:26 +03:00
tophf
16f7e19915 attenuate dark scrollbar colors 2022-02-19 17:08:05 +03:00
tophf
ad969fca6a
simplify css/html in popup (#1408)
+ reuse global checkbox-wrapper more
2022-02-19 11:18:14 +03:00
tophf
9d64e9ba54 wait for real prefs on bg startup
+ convert msg.sendXXX to async
2022-02-19 10:05:47 +03:00
tophf
cc3c85be58 fix update when using embedded meta 2022-02-19 06:48:55 +03:00
tophf
f7bbfd6ead fix mozdoc widget when duplicating section 2022-02-18 22:29:02 +03:00
tophf
dff0c133b4 hide 'manage site styles' if blocked 2022-02-18 08:02:47 +03:00
tophf
1364d3a8ba add colorScheme.isDark() 2022-02-18 06:03:52 +03:00
tophf
bef70f6db3 fix light mode in dark system, properly 2022-02-18 04:40:23 +03:00
tophf
77562ecd8d fix light mode in dark system 2022-02-18 04:35:36 +03:00
tophf
9fbf641571 fix some colors in dark theme 2022-02-18 03:59:00 +03:00
tophf
a58af7f816 speed up and simplify i18n 2022-02-18 03:49:16 +03:00
tophf
8d3e01e05a
shuffle and tidy up options (#1406)
* move updates/sync to the top, theme to the bottom
* remove font override
* replace 'Back to manage' with 'Close'
* add a note for the built-in shortcuts UI in FF
- update button
+ confirm reset
* one button to connect/disconnect
* shorten ids
* simplify/extract sync js
* reuse :invalid style
2022-02-18 03:47:22 +03:00
tophf
c9b8593830 fix setupLivePrefs regression 2022-02-17 19:51:58 +03:00
tophf
6fb2727f6b restore button's svg color in light mode 2022-02-17 03:35:51 +03:00
tophf
1721bad7d8 reuse radio-wrapper for visual uniformity 2022-02-17 03:26:20 +03:00
tophf
944c44ffb8 use default sorter pref for resiliency 2022-02-17 03:12:11 +03:00
tophf
adef93c3ed speed up setupLivePrefs 2022-02-17 03:12:11 +03:00
tophf
b7cfbe6e66
use color palette and enable a simple dark theme (#1405)
* add 'auto' iconset and use it by default
* expose `data-ui-theme` on html

Co-authored-by: narcolepticinsomniac <therealdoctorgonzo@gmail.com>
2022-02-17 03:10:59 +03:00
tophf
dd38856eda
scrollable details + sticky header (#1400)
* shorten section labels in lint report
* `sectioned` class on html for sectioned editor
* fix scrollElementIntoView
2022-02-14 22:19:20 +03:00
tophf
225a2cec31 unbork installer, regressed in 81a5acfd 2022-02-13 16:19:58 +03:00
tophf
2e021f6ac9 preserve style element name during livePreview
also strip _ from methods as we don't use such convention
2022-02-13 04:36:33 +03:00
tophf
fcad0ee523 avoid double handling of keyboard event in rerouter
...and thus fix the bug with restoring keyboard focus to codemirror when closing color picker
2022-02-12 21:31:35 +03:00
tophf
f9a2b9de35 fix/simplify i18n in templates
fix: double-translation, regressed in cc7eba9
2022-02-12 01:44:52 +03:00
tophf
648b295ef2 parserlib: handle layer statements anywhere 2022-02-12 01:18:03 +03:00
tophf
81a5acfda3 .user.css in virtual style urls
benefits:
* styles are listed directly under `Stylus` in devtools source file tree
* easy to differentiate from site's css
2022-02-11 00:08:20 +03:00
tophf
10de02f04d
expose style name (#1403) 2022-02-10 21:28:47 +03:00
tophf
b86cb6a36c reuse standard style of applies-to in usercss
+ shorten the Test button label
2022-02-10 21:25:09 +03:00
tophf
290a0f99d2 keep internal tooltips in js 2022-02-10 21:09:49 +03:00
tophf
ae6f7024ce identify by UUID when importing 2022-02-10 09:53:02 +03:00
tophf
de5eb32d2d update locales 2022-02-09 05:03:47 +03:00
tophf
c29048c366 parserlib: units L4, scrollbar-gutter 2022-02-08 15:13:35 +03:00
tophf
6b87f7840f parserlib: text-emphasis-color 2022-02-04 13:26:47 +03:00
tophf
421526d60b parserlib: cascade layers
+ simplify Parser.ACTIONS
2022-02-04 02:28:29 +03:00
tophf
9b0db09b6c
use hardcoded redirect_uri fallback (#1399) 2022-02-03 13:06:29 +03:00
tophf
bab1ba17a9 use standard web cursor for controls in options
+ increase clickable area of (i) notes
2022-02-02 13:46:55 +03:00
tophf
50da5f1966 intercept click on a note properly
regressed in cc7eba97
2022-02-02 13:38:05 +03:00
tophf
2e2c6765b5
keep router buffer on tab reload (#1393) 2022-01-31 17:27:58 +03:00
tophf
b2bc18c3d5 restore usercss section numbering and applies-to gaps
regressed in f9e11f58
2022-01-31 01:27:30 +03:00
tophf
84f4262176 fix rmDir deprecation warning 2022-01-30 14:23:02 +03:00
tophf
799423629a use standard cursor for svg inside buttons 2022-01-30 05:51:09 +03:00
tophf
f282623ef7
fix split-btn with popup.stylesFirst (#1394) 2022-01-30 05:50:13 +03:00
tophf
513ced6d57 lazy-load favicons 2022-01-29 18:53:49 +03:00
tophf
d048c480c3 require FF >= 55 2022-01-29 18:19:21 +03:00
tophf
f966b2ef96 allow non-object values in db 2022-01-29 16:39:30 +03:00
tophf
c597303692 skip deleted styles in order 2022-01-29 03:41:47 +03:00
tophf
26b75e77b3
separate storage for order + important styles (#1390)
* use Proxy for `db`
* don't merge arrays in deepMerge by default
* extract sync and cache from styleMan
2022-01-29 02:54:56 +03:00
tophf
46785b0550 use pageshow/pagehide events
...to avoid DOM update/scroll on switching tabs
2022-01-29 02:46:12 +03:00
tophf
554d121c45 remove duplicate toggleDataset call 2022-01-29 00:32:11 +03:00
tophf
f9e11f5806
align 'Applies-to' and actions (#1392) 2022-01-28 15:52:55 +03:00
tophf
5529cbec2b fix and simplify editDeleteText context menu
* enable it on inputs added by the user later
* enable it in all of our pages
2022-01-28 03:11:25 +03:00
tophf
5253c863b9 remove the unused collapsible from options 2022-01-27 15:27:10 +03:00
tophf
60834f7bd6 check userAgentData when available 2022-01-27 15:21:02 +03:00
tophf
8afab0eaeb fix first click on regexp tester 2022-01-27 06:03:37 +03:00
tophf
4c4a319b33 use duckduckgo favicons 2022-01-27 05:10:17 +03:00
tophf
ea7c26ce71 toLoad is always an array 2022-01-26 14:53:17 +03:00
tophf
f740686cb5 fix icon when opening an unstyled Options frame
...and the containing page is styled
2022-01-24 22:49:27 +03:00
tophf
e54178a43c
draft recovery in editor (#1388)
+ use toolbox::clamp() more
2022-01-23 12:44:25 +03:00
tophf
60f59e9f06 fix split button [re]activation 2022-01-22 22:21:14 +03:00
tophf
0e31557748 show a button to reset the template 2022-01-22 12:29:35 +03:00
tophf
45eeedbe97 strip only dummy sections from the template 2022-01-22 12:06:57 +03:00
tophf
b4b135826c use css borders for split button triangle 2022-01-22 12:06:29 +03:00
tophf
ce9e74e2a0 add per-style setting for autoupdate checks 2022-01-21 19:45:56 +03:00
tophf
f041f2265a fix $ for detached nodes 2022-01-21 19:45:56 +03:00
tophf
98da86f816 update CodeMirror, stylelint, deps 2022-01-20 17:43:31 +03:00
tophf
4c696fefbc show search results for the faster index first
+ refresh after the slower index is downloaded
+ use an existing `f` instead of `isUsw`
2022-01-20 00:45:51 +03:00
tophf
6a07ad0f56 split button for manage::export, popup::manage 2022-01-19 16:13:53 +03:00
tophf
b692cf9608 show source code in build error when updating
fixes #891
2022-01-19 14:46:09 +03:00
tophf
be43bf3f23 force no-cache in update checker 2022-01-19 14:46:08 +03:00
tophf
cc7eba979e
save-as-template button in editor (#1385)
+ keep i18n attributes to use them as CSS selectors
+ reduce flicker when creating a new style
+ split button
2022-01-19 14:45:45 +03:00
tophf
594ca3520c
actually use the global font everywhere (#1384) 2022-01-19 00:25:11 +03:00
tophf
42d6e2f2af parserlib: reimplement d9a80623 properly 2022-01-18 20:24:41 +03:00
tophf
f7729eac15 remove unused files 2022-01-18 17:23:51 +03:00
tophf
0705392fb2
fix/deduplicate/simplify installer html/css/js (#1383) 2022-01-18 16:39:33 +03:00
tophf
936f5b40d2 pixel-align (i) icon 2022-01-18 01:10:45 +03:00
tophf
9136631bc6 manager: trivial alignments
* removed `Filters` left shift
* removed inadvertent extra padding in compact mode
* fixed `Backup` label in verbose languages
* flexible width of header blocks in compact mode
2022-01-18 01:10:45 +03:00
tophf
14e0a418bf create URL fallback only when necessary 2022-01-18 01:10:45 +03:00
tophf
12eb243610 replace installer if another file is drag'n'dropped 2022-01-16 20:22:42 +03:00
tophf
956b60e1ef fix header width when maximizing initially small window 2022-01-16 15:45:55 +03:00
tophf
0c20ef5d17 refactor a complex ternary 2022-01-14 18:47:09 +03:00
eight
ddc09f3511
Add: a draggable list to customize injection order (#1364)
+ implement messageBox.close()
+ fix require() with root urls in /dir/page.html
+ limit messageBox focus shift to config-dialog
+ flatten vendor dirs and simplify build-vendor:
  + replace the unicode symbol with ASCII `->`
  + flatten dirs by default to simplify writing the rules and improve their readability
  + rename and sort functions in the order they run
  + use `node-fetch` instead of the gargantuan `make-fetch-happen`
  + use `glob` which is already installed by other deps

Co-authored-by: tophf <tophf@gmx.com>
2022-01-14 15:44:48 +03:00
tophf
8ddeef221b
resizable header panel (#1378) 2022-01-13 12:47:37 +03:00
tophf
cce483dd22 update locales 2022-01-12 01:30:46 +03:00
tophf
d9a80623e1 parserlib: fix endless loop on incomplete tokens
regressed in 048c2672
fixes #1381
2022-01-11 21:18:37 +03:00
tophf
ca5402136d removed an unused string 2022-01-11 12:21:44 +03:00
tophf
5791ae7b05 removed an unused duplicate string 2022-01-11 11:46:20 +03:00
tophf
68dc584749 deduplicate editor.useSavedStyle
...and call it when saving in sectioned editor (regressed in ba9d904c)
2022-01-10 21:31:15 +03:00
tophf
6e4fc3236e confirm reload in sectioned editor 2022-01-10 19:12:29 +03:00
tophf
ba9d904ccc don't rebuild editor DOM on save, fixes #1380 2022-01-10 17:57:46 +03:00
tophf
51e56110e8 parserlib: color-scheme, scrollbar-gutter, transforms 2022-01-10 07:32:02 +03:00
tophf
906508832b
show style settings in a dialog (#1374)
+ simplify css/html
+ save button and autosave checkbox just like in config-dialog
+ generalize can-close-on-esc
+ add `props` parameter to helpPopup.show
+ deduplicate usage of #help-popup id
+ uniform padding in popups
+ disambiguate style settings from editor options
2021-12-29 22:57:22 +03:00
tophf
8128100cef ensure editor window is visible
fixes #1375
2021-12-26 19:35:13 +03:00
tophf
6afb4dc634 fix toggling of newline in beautifier dialog 2021-12-25 22:17:35 +03:00
tophf
440395db9f export in compat mode on shift-click/right-click 2021-12-25 18:05:29 +03:00
tophf
3cdf526fa3
don't export redundant values (#1373)
+ implement proper check for same code in usercss so unchanged styles won't be unnecessarily imported
2021-12-25 13:08:38 +03:00
tophf
6b9cdf2bc2 extend drop zone to viewport in Chrome too 2021-12-22 20:05:42 +03:00
tophf
249196d414 don't add _usw to all styles 2021-12-22 19:49:21 +03:00
tophf
0ac01d2e22
add a callstack to errors in browser and msg (#1369) 2021-12-16 20:21:02 +03:00
eight
9d2854c272
Fix: compatibility with kiwi (#1368) 2021-12-16 16:04:22 +03:00
eight
f6e6a138db
Add: webdav sync (#1363) 2021-12-12 03:05:58 +03:00
eight
3ea7e45624
Change: stop revoking google token, change syncErrorRelogin message, recognize token manager errors as grant error (#1362)
* WIP: don't revoke google token, add TokenError

* Fix: stop suggesting disconnecting

* Add: recognize token error as grant error

* Change: sync immediately after re-login
2021-12-09 12:00:38 +08:00
eight
7e3c6f16e9
Fix: bump db-to-cloud, show detailed LockError (#1361)
* Bump db-to-cloud

* Fix: bump db-to-cloud, show detailed LockError

* Fix: used -> use

* Change: drop our retry code
2021-12-09 00:00:30 +08:00
eight
e23077a7ea
Add: support inclusions (#1359)
* Add: support inclusions

* Fix: refresh settings page after configuring in popup
2021-12-08 18:30:16 +08:00
tophf
9ab5369393
autosize textareas in style settings (#1360) 2021-12-07 14:43:21 +03:00
eight
9d1243073b
Add: style settings (#1358)
* Add: style settings

* Change: use radio instead of select for dark/light mode

* Change: x -> Delete

* Change: (in|ex)clusion messages

* Fix: avoid extra space when there is no rule

* Fix: UI in mobile

* Change: delete priority

* Change: use textarea for include/exclude, remove isCodeUpdated

* Fix: separate toggle

* Fix: minor

* Fix: remove codeIsUpdated in styleman
2021-12-07 12:44:49 +08:00
tophf
a59aab73fc enforce plain-text for styleInstallOverwrite
fixes #1357
2021-12-03 15:37:47 +03:00
eight
6c13db1468
Add: toggle dark/night mode styles automatically (#736)
* Add: color-scheme.js

* Add: handle color scheme

* Add: styleManager.setMeta

* Add: make setupLivePrefs work with radio

* Change: drop setupRadioButtons

* Add: UI for schemeSwitcher

* Add: prefer-scheme select in installation page

* Fix: add alarm listener

* Add: display excluded reason in popup

* Fix: rely on data-value-type instead of input name

* Fix: oldValue and newValue should have the same type

* Change: detect media change in content script

* Fix: duplicate capitalize

* Fix: minor

* Update web-ext

* Fix: valueAsNumber doesn't work for all inputs

* Fix: disable colorscheme selection after install

* Fix: API error
2021-12-03 00:49:03 +08:00
tophf
19ebeedf6a
add vars after @import in compiled code (#1348)
fixes #1347
2021-11-14 11:00:12 +03:00
tophf
b17eef4053 allow live-reload on localhost and updates on file://
...if file access is allowed
2021-11-02 18:15:26 +03:00
tophf
7ad3f94697 unbork and simplify applies-to css 2021-10-24 00:11:09 +03:00
tophf
8b67eb885d fix compact usercss height + vcenter fixed header 2021-10-23 23:56:38 +03:00
tophf
707d8bb1a7 parserlib: update transforms 2021-10-21 23:25:32 +03:00
tophf
0034dcb941 preserve installationUrl 2021-10-12 20:26:57 +03:00
tophf
992b89f0eb more stuff in fixKnownProblems
fixes #1342
2021-10-12 20:20:54 +03:00
tophf
37a174b092 CodeMirror 5.63.3 2021-10-12 16:05:21 +03:00
tophf
4243815349 update external urls in manager 2021-10-04 09:46:33 +03:00
tophf
11028bd635 update locales 2021-10-01 17:49:38 +03:00
tophf
dc18320b60 codemirror 5.63.1 2021-10-01 17:48:04 +03:00
tophf
eaef854bcf show iframe "+" only if there's a unique url 2021-09-24 19:18:57 +03:00
tophf
e5807a7823 fix tooltip for iframe "+" 2021-09-24 19:14:34 +03:00
tophf
818031a86b preserve iframe's sender.url 2021-09-24 11:05:55 +03:00
tophf
fce049a911
fix iframe visibility detection (#1336) 2021-09-24 09:43:10 +03:00
tophf
cf0ecbfd4a support EyeDropper API in color picker
Chrome 95+
https://wicg.github.io/eyedropper-api/
2021-09-24 09:40:49 +03:00
tophf
997f1fe8de avoid FOUC for dark themes in applies-to widget 2021-09-21 22:51:18 +03:00
tophf
94f727dc09 avoid startup flicker in applies-to widget 2021-09-21 14:24:17 +03:00
tophf
05bc4301ad 349a8c38 follow-up: stretch editor in compact mode 2021-09-21 10:50:50 +03:00
tophf
58ad6f64d6 focus editor when clicking section name 2021-09-21 10:12:58 +03:00
tophf
349a8c3878 graduate from Quirks mode 2021-09-21 09:43:30 +03:00
tophf
4a0f74764a update regexp report when tab is removed 2021-09-11 15:58:59 +03:00
tophf
dfd8f7a1b1
fix regexp report overflow (#1332) 2021-09-11 15:53:10 +03:00
tophf
59cac7a469 fix USO-archive site search link 2021-09-08 19:13:27 +03:00
tophf
e7d5fff736 use normal warning if typo candidate is empty 2021-09-07 12:42:06 +03:00
tophf
048c267266 parserlib: check @font-face + shorten grammar tokens 2021-09-07 12:36:36 +03:00
tophf
a56676122f
csslint: add "known-pseudos" rule enabled by default (#1328) 2021-09-02 00:12:29 +03:00
tophf
701c30a6c8 fix links in linter help dialog 2021-08-31 17:22:10 +03:00
tophf
a08dd2800d fix objectDiff 2021-08-30 21:15:44 +03:00
Rob Garrison
31dd972c7a 1.5.22 2021-08-30 08:38:44 -05:00
tophf
d84e1bddab update locales 2021-08-30 14:07:47 +03:00
Gusted
d181298e56
Improve issue template (#1319)
* Improve issue template

- Add a CSS section(so I don't have to see those comments about posting CSS etc).
- Make it into more sections and separate them.
- Add notes to each section.

* Apply feedback

* Add restart/profile note

* tweaks

Co-authored-by: narcolepticinsomniac <therealdoctorgonzo@gmail.com>
2021-08-28 15:44:29 -04:00
tophf
2c31dc2af8
installer: show action buttons for installed styles (#1322) 2021-08-26 22:10:08 +03:00
tophf
abced603b4 parserlib: fix ieFunction
fixes #1325
2021-08-26 22:03:30 +03:00
tophf
a5a8d97767 csslint: fix bugs in 'box-model' and 'ids' 2021-08-26 21:01:53 +03:00
tophf
12764baacb parserlib: accept uso-var as ident/string 2021-08-24 14:38:19 +03:00
tophf
b0ed85c5ea use second metablock's @updateURL in USO-archive styles
fixes #1323
2021-08-23 16:27:31 +03:00
tophf
2eeab2c1be openusercss is on an indefinite hiatus 2021-08-22 21:09:47 +03:00
tophf
bfd0d03871 fix #1321 2021-08-22 20:30:09 +03:00
tophf
8ac43fca31
keep the original color format in usercss @var (#1320) 2021-08-22 11:57:35 +03:00
Rob Garrison
32c5e17d08 1.5.21 2021-08-20 08:01:40 -05:00
tophf
95205c70e5 parserlib: check if prop with vars inside is known 2021-08-20 13:03:23 +03:00
tophf
8e36e0277f parserlib: reuse global keywords 2021-08-20 13:01:56 +03:00
tophf
9d6542a39d CodeMirror 5.62.3 2021-08-20 12:16:24 +03:00
tophf
c08747202a fix CSS linting in new CodeMirror
fixes #1317
2021-08-17 22:03:10 +03:00
tophf
6927fa5d70 update translations 2021-08-16 16:59:20 +03:00
Rob Garrison
46b0c9005d 1.5.20 2021-08-15 12:48:52 -05:00
tophf
9722554b3b
show action hints on preview-less search results (#1313)
+ use CSS to control visibility of action buttons
+ avoid mutating DOM unnecessarily in toggleDataset()
2021-08-13 23:03:01 +03:00
Gusted
07291f9486
Add comment to await (#1311) 2021-08-13 13:40:24 +03:00
Gusted
598735fc7b
Fix Firefox's android icon (#1312) 2021-08-13 08:28:50 +03:00
tophf
1e5f118d2d
update USO-archive urls (#1308) 2021-08-12 20:35:56 +03:00
tophf
49af723078 don't let wrapped text flow below the usw icon 2021-08-12 16:56:35 +03:00
tophf
304dcb1489
show installation error inline, allow retrying (#1309) 2021-08-12 16:44:02 +03:00
tophf
434e7ff6c6
rework/simplify external links (#1244)
* rework/simplify external links
* remove title from svg (i) icon as it randomly shows instead of the parent's title
* ensure original element isn't focused when showing modal
* center more help modals
2021-08-12 16:40:27 +03:00
Gusted
2dbccf71db
Prefer webp when available (#1306) 2021-08-12 16:02:48 +03:00
Gusted
ce2250a1f3
clarify purpose of unused rule (#1307) 2021-08-12 15:30:22 +03:00
tophf
50717465b9
show config for usercss vars in installer (#1302)
* simplify messageBox code
* also bind events correctly in case messageBox is called when a messageBox was already shown
2021-08-12 14:40:03 +03:00
tophf
91324a4a48 use center-dialog class for import&history
regressed in a2c8953e which centered the contents too
2021-08-06 19:50:36 +03:00
tophf
6798114196 add buttons to hotkey input, reset on Del/BackSpace 2021-08-06 16:01:55 +03:00
tophf
ba7b55c23d parserlib: add accent-color 2021-08-03 19:45:44 +03:00
tophf
295cb4541c reduce margins for closed details
to provide more space for sections/issues
2021-08-01 21:42:23 +03:00
tophf
b6cc6a09b9 detect typo in metadata when linting
makes use of https://github.com/openstyles/usercss-meta/pull/78
2021-08-01 19:41:52 +03:00
tophf
404efcecf9 unbork max-width for details in compact mode
regressed in 6650a371
2021-08-01 19:28:08 +03:00
tophf
55046ef68c custom version validator is redundant now
...thanks to 4913ba1d2c
2021-08-01 18:38:36 +03:00
tophf
f35bf6a2a5 typo 2021-07-30 17:28:35 +03:00
tophf
c34b054642
refactor usw content script (#1300)
* extract handlers to async functions
* limit event source to the current window
2021-07-30 17:04:15 +03:00
Gusted
e1b65ae21f
Send requested info (#1294) 2021-07-30 15:52:27 +03:00
tophf
6e591b0d52
patchCSP option: allow @import from any URL (#1297) 2021-07-30 15:45:27 +03:00
tophf
6650a37194
tidy up USW-related UI and code (#1285)
* shortened the title to "Publish" and fixed the compact mode
* made collapsed <details> share the same line in compact mode
* made hard-coded strings localizable
* IIFE modules instead of generically named globals
* unified and sorted the names of localized messages
* adjusted spacing of header items
* center auth popup to current window

Co-authored-by: Gusted <williamzijl7@hotmail.com>
2021-07-30 15:44:06 +03:00
tophf
23d86c53a7 properly account for scrollbar in applies-to widget 2021-07-30 15:38:38 +03:00
tophf
ebb5fcafbc show sections on compact->normal layout switch 2021-07-30 07:58:08 +03:00
tophf
39c51435ca avoid recursion when closing regexp tester 2021-07-30 07:37:33 +03:00
tophf
a46bb103c5 update usercss-meta 0.11.0
fixes #1220
2021-07-29 18:54:37 +03:00
tophf
02db1aab6f CM apparently fixed their formula for max-height 2021-07-26 09:48:16 +03:00
tophf
7a0ac57b06 CodeMirror 5.62.2 2021-07-21 15:32:01 +03:00
tophf
33ff2c8373 CodeMirror 5.62.0
skipping 5.62.1 due to a bug in its lint.js
2021-07-21 13:01:39 +03:00
tophf
2d9d0ad1f8 synchronize version 2021-07-21 13:00:27 +03:00
Gusted
61c7d4f08c
Don't rely on _isUswLinked (#1288) 2021-07-20 15:56:45 +03:00
Gusted
69ccdb0591
Show error to user when USw returns error (#1286)
Related USw commit: d4306f2f71

Co-authored-by: tophf <tophf@gmx.com>
2021-07-18 07:25:32 +03:00
Gusted
8abcf9e754
Fix handleSave and sections TOC (#1284) 2021-07-17 02:57:50 +03:00
Rob Garrison
654403eb00 1.5.19 2021-07-15 08:28:07 -05:00
Gusted
ffcdf47ab5
Passing object -> Passing property (#1277)
- Resolves #1276
- Just pass the parameter which is either undefined or an number. No need to get it from a object, while we can just pass the property already.
2021-07-10 04:10:11 -04:00
Rob Garrison
9e9c9061dc 1.5.18 2021-07-07 22:06:55 -05:00
narcolepticinsomniac
ab44c60522
More specific name 2021-07-07 10:07:29 -04:00
narcolepticinsomniac
e8e18abe58
More specific name 2021-07-07 07:51:00 -04:00
Gusted
58fc531515
Fix integration (#1275)
* Fix integration

- Don't use sourceCode as "temporary" value as it's being used as legit value(consider that we delete it after it's non longer needed).
- Wrap the metadata into a `try {}` as some styles doesn't have any metadata.

* Typos adios magios and bonjour gutentag
2021-07-06 18:39:52 -04:00
tophf
44b08dc089
randomize sync interval to avoid infinite deadlocks (#1250)
* randomize sync interval to avoid infinite deadlocks

* autoretry with a randomly increasing delay
2021-07-06 17:19:18 -04:00
Gusted
264544dfa9
Allow dragging the message box (#1274)
* Allow dragging the message box

- Allows the message box to be dragged to any position on the screen, so the user can set it to their wishes.
- Something as discussed in #1270.

* Use CSS to prevent user select

* Remove whitespace

* Imagine wasted new lines

* Make it more niece

* Remove unnecessary check

* Fix calculation code

* cursor: move;

* fixup

* Don't declare variable here

* Cap the move to 30px in each side

* I should pay attention to my english lessons

Co-authored-by: tophf <tophf@gmx.com>
2021-07-06 17:19:00 -04:00
Gusted
a2c8953e63
Use flex to center #message-box (#1271)
* Use flex to center #message-box

- Resolves #1270
- See details on the issue mentioned.

* Use existing .center

* Remove center rule at non-center selector
2021-07-06 17:18:42 -04:00
Gusted
9e72784b2a
Change USw endpoints (#1269)
- Changes the necessary endpoints for OAuth to newer ones that make more sense.
- Related commit on USw: cf6384cf4d
2021-07-01 20:12:24 -04:00
tophf
fa43c6d94d
fix flicker when hovering buttons in popup [Firefox] (#1267) 2021-06-29 05:37:30 -04:00
Gusted
fe45781545
Linking Styles to USW (#1256)
* Prototype

Just able to log the token for the requested style.

* Store USw Token

* Fix linting

* Add revoke capabilities

* Add upload capabilities + UI?

* Add credentials for production server

* Patch up several things

* Send styleInfo

We will be adding the feature to add style based of the currentStyle, see paring commit 31813da300

* Fix clientSecret

* Pass styleInfo trough usw's hook

Related commit on USW: 461ddb03c7

* Adjusted behavior

Applied suggestions from Narco.

* Wait for `usw-ready`before sending style

* don't use `window.`

* Ensure correct style is pre-filled

* Send over metadata

Related USW commit: 7d8c4c1248

* Title Case => Title case

* _linking => _isUswLinked
2021-06-29 05:36:59 -04:00
tophf
ada46e8277 fix bookmarks being orphanized/stranded 2021-06-29 05:45:04 +03:00
tophf
4e87a060f5 use crypto.randomUUID if present 2021-06-17 05:12:25 +03:00
Gusted
83a6808c67
Disable Offscreencanvas for firefox (#1258)
Currently the offscreencanvas is being used for '2d' context, however Firefox doesn't implement this as for now, see: https://searchfox.org/mozilla-central/source/dom/canvas/OffscreenCanvas.cpp#105-110 and it will error out multiple times within the console.
2021-05-31 17:21:17 +03:00
tophf
4bd956f891 don't stretch the table in new browsers
fixes #1255
2021-05-27 18:38:42 +03:00
tophf
367ae56047 reuse openURL so that the opener tab id is set 2021-05-27 14:36:10 +03:00
tophf
18265b94c6 tab url may be empty for about:blank tabs
fixes #1254
2021-05-27 14:35:03 +03:00
Gusted
440a9f4763
Add USw hook to remove get stylus button (#1239) 2021-04-30 16:39:07 +03:00
tophf
d736a00bc1 csslint: allow globals like @import inside sections 2021-04-28 17:33:27 +03:00
tophf
be4fd17113 wait for just created script elements to load
fixes #1233
2021-04-24 16:37:30 +03:00
tophf
b0a03d53fc move lazyKeymaps definition to the base init 2021-04-24 16:26:14 +03:00
tophf
23aa036db5 show USW styles with 'stylus' category 2021-04-20 21:05:49 +03:00
tophf
0aa6d3b463 read css escapes per spec 2021-04-20 20:40:29 +03:00
Gusted
f10ebffeff
Add USw to the inline search and proper installing (#1223)
* remember sort order in popup, use "updated" by default
* add a USW link in the manager

Co-authored-by: tophf <tophf@gmx.com>
2021-04-20 20:40:04 +03:00
tophf
892b295897 CodeMirror 5.61.0 2021-04-20 11:29:24 +03:00
tophf
afcd8ebcf4 prevent FF from double-handling clicks, fixes #1232 2021-04-20 07:22:19 +03:00
tophf
711f6502b5 make transition suppressor rule more robust 2021-04-19 16:09:31 +03:00
Rob Garrison
43f1da1b4c copy policy from app details 2021-04-18 20:15:07 -05:00
Rob Garrison
baca54bf19 Add privacy policy 2021-04-18 20:06:24 -05:00
tophf
0b9b972338 update swatch's lines to match current code 2021-04-08 14:33:36 +03:00
silverwind
a934571dc7
update setup-node plugin to v2 (#1215) 2021-04-08 10:49:24 +03:00
vVde3S88xHW6EZaB63HyXQUipgFtaWooYGDs35g
82d2530669
fix installer for local files in FF (#1224) 2021-04-08 10:45:43 +03:00
tophf
33e7f920a3 fixup: load codemirror-themes.js earlier 2021-04-06 07:10:03 +03:00
tophf
fa2dec724a fix editor theme in FF containers, fixes #1222 2021-04-06 06:56:35 +03:00
tophf
2d5788766a fix Enter key clearing selected text, fixes #1219
regressed in 7a479edc
2021-04-02 07:12:44 +03:00
tophf
8c160ed40c CodeMirror 5.60.0 2021-03-20 14:23:49 +03:00
tophf
ed238183eb revert 4ae2c670's changes for the install button
as it's no longer required after 3f4fb061
2021-03-19 23:02:02 +03:00
tophf
3f4fb0617e reimplement #1192 using dummy links
as omitting href attribute is sufficient to avoid the useless tooltip
2021-03-19 22:53:52 +03:00
tophf
35e0a9d032 avoid FOUC in tabs on update/reload 2021-03-19 19:00:48 +03:00
tophf
21d902c48c deduplicate keys in prefs.subscribe 2021-03-14 20:30:44 +03:00
tophf
a7ae3fbc55 unbork selectByTokens option
...by moving custom options definitions before isEditorPref checks CodeMirror.defaults
2021-03-14 19:44:47 +03:00
tophf
db77e03e97 print stylus-lang's p() to editor console, fixes #894 2021-03-14 10:07:59 +03:00
tophf
692d3c9826
try to show applicable values in autocomplete for props (#1211)
+ restore proper toggling of autocompleteOnTyping
2021-03-14 08:33:26 +03:00
tophf
9531698dd7 show unreachableMozSiteHint on any site in FF
fixes #1210
2021-03-12 08:51:57 +03:00
tophf
9d19d61913
show time/weekday in tooltip for style entries (#1205) 2021-03-11 07:20:38 +03:00
tophf
ff63b84489 add a test for the transition suppressor rule 2021-03-09 19:24:37 +03:00
tophf
4ae2c67033 unbork install button style + dedupe svg-icon
regressed in b8f6f5db
2021-03-06 21:22:24 +03:00
tophf
65ac351699
preserve opacity in colorpicker for preprocessor uso config (#1200)
USO has always produced 6-digit #rrggbb so some styles rely on it
and use /*[[color]]*/11 notation to specify the transparency.
Now we will try to preserve the opacity customized by the user
via colorpicker unless the style specifies it inline.
2021-03-05 17:25:05 +03:00
tophf
7d08fea5e1 followup for 102121ad: apply suppressor on all pages 2021-03-01 08:46:47 +03:00
tophf
8eca4e9139 followup for 4228758c: only input should be clickable 2021-02-28 23:39:09 +03:00
tophf
102121ad8f
suppress transition bug on page open in Chrome, too (#1193) 2021-02-28 23:01:49 +03:00
tophf
4228758cec don't hide the input in onoffswitch
as it wasn't necessary and turns out it causes problems with event routing when the parent <label> includes a <button> which we use now instead of dummy links
2021-02-28 22:58:24 +03:00
tophf
c0eace302f inject styles only in visible frames
fixes #1033
2021-02-28 22:33:16 +03:00
tophf
a56e528b31 trust sender's URL in FF
fixes #1194
2021-02-28 18:01:26 +03:00
tophf
01e2e09fa5 ignore HTTP502 error when syncing 2021-02-28 08:37:56 +03:00
tophf
b8f6f5db8d
switch from dummy links to dummy buttons (#1192)
The reason is that dummy links like <a href="#"> cause the built-in tooltip pop up on hover which is just useless noise
2021-02-27 20:42:49 +03:00
tophf
41533e863d enforce eslint radix rule for parseInt 2021-02-26 13:02:54 +03:00
tophf
53c71614fc relaxed parsing of usercss @version
fixes #821
2021-02-26 10:03:32 +03:00
tophf
9b46da5846
shrink screenshots to make readme readmeable 2021-02-25 10:26:32 +03:00
tophf
81fa6b1e79 avoid a scrollbar due to rounding errors 2021-02-25 10:16:50 +03:00
Rob Garrison
a5d2d96717 1.5.17 2021-02-24 18:34:09 -06:00
tophf
69308d04e9 CodeMirror 5.59.4 2021-02-24 13:41:05 +03:00
tophf
75ae8e79bf replace webext-tx-fix with a local tools script 2021-02-24 13:37:57 +03:00
tophf
14efa1f052 fix locales 2021-02-24 11:27:39 +03:00
Rob Garrison
3e415460c6 1.5.16 2021-02-23 21:37:53 -06:00
Rob Garrison
ea6359307b Update locales 2021-02-23 21:35:26 -06:00
tophf
acaf12f694 ensure button panel is tall enough
see #1188
2021-02-23 15:13:01 +03:00
tophf
abd018d750 fix autocomplete for words starting with d/r/u
fixes #1188
2021-02-23 14:59:56 +03:00
tophf
cf1f51af0a
vivaldi bug workaround: open webAuth flow in a tab (#1186) 2021-02-22 15:12:19 +03:00
tophf
b61cd75b25 ignore style messages if started in disableAll mode 2021-02-22 00:36:40 +03:00
tophf
c5e2baaf87 pass disableAll to styleInjector, fixes #1187 2021-02-22 00:36:37 +03:00
tophf
76ee0992e7 update deps: CM 5.59.3, webAuth 0.1.1 2021-02-21 09:17:33 +03:00
tophf
a674874861 show global styles for frames if main page is blocked
fixes #1183
2021-02-16 17:03:18 +03:00
tophf
58ae4704b2 fix css autocomplete sort order 2021-02-14 21:48:44 +03:00
tophf
3102738cfb
improve linter info and config popup (#1171)
* improve linter info popup

* show rule id so the user can configure it
* add "configure" button to show the linter config UI
* add margins between items
* emphasize active rules in linter config dialog
2021-02-14 20:30:50 +03:00
eight
c17dddb0ee
Fix: less intrusive authorization (#1172)
* Update db-to-cloud

* Change: refactor sync logic, disallow implicit auth

* Add: better relog message in options page

* read prefs only when `ready`
* show the internal error text in icon tooltip
* show the internal error text in options fully
Co-authored-by: tophf <tophf@gmx.com>

* Update _locales/en/messages.json
Co-authored-by: Enrico Lamperti <910672+elamperti@users.noreply.github.com>
2021-02-14 18:24:49 +03:00
tophf
75db3601d0 fix #1177
regressed in fdbfb235
2021-02-10 19:47:07 +03:00
tophf
83adc5aa1e fix #1176 2021-02-10 18:29:34 +03:00
tophf
890ff395c0
use a new solid gear icon everywhere (#1173) 2021-02-10 12:11:52 +03:00
tophf
c00c748c1e simplify/deduplicate badge error logic
also correctly restore the real badge info when error is cleared
2021-02-09 12:53:03 +03:00
tophf
c60c764d34
add: indicate sync error as 'x' in icon badge (#1166)
* indicate sync error as 'x' in icon badge

* fix/simplify

* Add: show more errors on badge

Co-authored-by: eight04 <eight04@gmail.com>
2021-02-09 14:58:30 +08:00
tophf
1746235d0d fix showLintHelp dialog 2021-02-07 00:17:52 +03:00
tophf
94cf673f68 load semver faster, fixes #1167 2021-02-06 19:10:22 +03:00
tophf
ade8d1981b restore getStylesViaXhr check for about:blank frames
regressed in 50959354
2021-02-05 10:51:39 +03:00
tophf
50959354ec wait for next paint in about:blank frames, fixes #1165 2021-02-05 10:35:02 +03:00
tophf
784a1018f8 fixup: call getDateFromVer when updateUrl is set, #1159 2021-02-05 09:50:52 +03:00
tophf
34c067c3be hide USO-archive version in manager
* it's a hard-to-read blob of digits: 2021110.8.46
* it's somewhat redundant as we have the 'update age' column
2021-02-04 14:10:30 +03:00
tophf
c12d3fc5e3 convert USO styles to USO-archive on update 2021-02-04 14:10:30 +03:00
Rob Garrison
272dea01a2 1.5.15 2021-02-03 19:05:23 -06:00
Rob Garrison
5304805c63 Update locales 2021-02-03 19:04:35 -06:00
tophf
8ee964c045 simplify rerouteHotkeys to avoid enabling it twice 2021-02-02 22:49:00 +03:00
tophf
24a0077783 chrome bug: onCommitted is fired twice 2021-02-02 00:40:30 +03:00
tophf
c41a5f92c3 preserve dirty after importing moz-format [v2], fixes #1163 2021-02-02 00:33:56 +03:00
tophf
3a3104b30a update parserlib
* add aspect-ratio, color-adjust, forced-color-adjust
* remove ar units
2021-02-01 19:00:30 +03:00
tophf
afa4a1ac14 restore USO bug workaround for style settings
fixes #1158
2021-01-23 10:14:23 +03:00
tophf
2c9ea4fdc0 CodeMirror update leftovers 2021-01-23 10:14:16 +03:00
tophf
8cabf8a8aa CodeMirror 5.59.2 2021-01-20 14:59:31 +03:00
tophf
02bd682135 ignore executeScript errors e.g. due to frame removal 2021-01-19 09:36:53 +03:00
tophf
a88996be6f restore correct handling of openEditInWindow
regressed in fdbfb235
fixes #1156
2021-01-19 09:27:19 +03:00
tophf
a66a1f8ed6 code cosmetics 2021-01-18 09:50:27 +03:00
tophf
4338f67352 LESS: use math:parens-division
This is the default mode in LESS4.
Fixes #1154.
2021-01-16 09:18:54 +03:00
tophf
914943ed4c remove sliders
* can be already implemented as a userstyle
* will be exposed in usercss config dialog later
2021-01-15 13:24:21 +03:00
tophf
eb5fd90dc7 remember CM bookmarks on reload/revisit 2021-01-12 17:39:24 +03:00
tophf
b00e6d23fe revert CodeMirror to 5.59.0
5.59.1 is bugged https://github.com/codemirror/CodeMirror/issues/6558
2021-01-10 13:44:38 +03:00
tophf
fe176c9b62 [compact layout] show sections TOC on first click 2021-01-09 13:21:06 +03:00
tophf
ac7b33d7e0 fix resizing of last section if page is scrolled 2021-01-07 14:52:38 +03:00
tophf
39e03b0a9f fix autocomplete after var(, autocompleteOnTyping 2021-01-07 14:52:38 +03:00
tophf
312f444ec7 fix 'true' === true check in setupLivePrefs 2021-01-07 14:52:38 +03:00
tophf
1308efb8d0 fix/simplify fitSelectBox 2021-01-07 14:52:38 +03:00
tophf
11d311d1e8 avoid forced layout in highlightEditedStyle 2021-01-07 14:52:38 +03:00
tophf
767b2017e0 wait for main stylesheet to load before forcing layout 2021-01-05 19:54:49 +03:00
tophf
dfb9135f6a stylelint 13.8.0 2021-01-05 18:51:01 +03:00
tophf
7d6b4fc8ac csslint: code cosmetics 2021-01-05 18:27:46 +03:00
tophf
57233db546 csslint: add 'shorthand-overrides' rule 2021-01-05 12:14:26 +03:00
tophf
cb85fe9392 get the full list of stylelint rules 2021-01-04 09:28:59 +03:00
tophf
fd890f8e61 show "Options" heading in options UI 2021-01-02 23:34:42 +03:00
tophf
8807819f16 parserlib: update props 2021-01-01 19:49:38 +03:00
tophf
6563aca141 CodeMirror 5.59.1 + use emptyDirSync instead of shx
+ restore the original jsonlint with its trailing spaces, accidentally fixed by fdbfb235
2021-01-01 18:36:05 +03:00
tophf
d26dd92f44 use .meta.css in update check on greasyfork 2021-01-01 18:20:25 +03:00
tophf
c35302dff6 add margins for updateAllCheckSucceededSomeEdited 2021-01-01 18:20:25 +03:00
tophf
fdbfb23547
API groups + use executeScript for early injection (#1149)
* parserlib: fast section extraction, tweaks and speedups
* csslint: "simple-not" rule
* csslint: enable and fix "selector-newline" rule
* simplify db: resolve with result
* simplify download()
* remove noCode param as it wastes more time/memory on copying
* styleManager: switch style<->data names to reflect their actual contents
* inline method bodies to avoid indirection and enable better autocomplete/hint/jump support in IDE
* upgrade getEventKeyName to handle mouse clicks
* don't trust location.href as it hides text fragment
* getAllKeys is implemented since Chrome48, FF44
* allow recoverable css errors + async'ify usercss.js
* openManage: unminimize windows
* remove the obsolete Chrome pre-65 workaround
* fix temporal dead zone in apply.js
* ff bug workaround for simple editor window
* consistent window scrolling in scrollToEditor and jumpToPos
* rework waitForSelector and collapsible <details>
* blank paint frame workaround for new Chrome
* extract stuff from edit.js and load on demand
* simplify regexpTester::isShown
* move MozDocMapper to sections-util.js
* extract fitSelectBox()
* initialize router earlier
* use helpPopup.close()
* fix autofocus in popups, follow-up to 5bb1b5ef
* clone objects in prefs.get() + cosmetics
* reuse getAll result for INC
2021-01-01 17:27:58 +03:00
tophf
06823bd5b4 CodeMirror 5.59.0 + bump deps 2020-12-22 01:17:56 +03:00
tophf
1e614fa2bc don't count&title removed sections 2020-12-15 01:52:10 +03:00
tophf
7140993e6c parserlib: fix reading \\, regressed in 6d04c0e 2020-12-14 21:18:15 +03:00
tophf
775d77a3a8 clone change positions to avoid breaking CodeMirror 2020-12-10 23:25:07 +03:00
tophf
579750fbc0 fix linting when style was initially error-free
regressed in e6f94378
2020-12-10 11:00:24 +03:00
tophf
0b540fbabd parserlib: up to 10% faster first run
* inline generation of trivial tokens
* remove Tokens.CUSTOM_PROP
* allow fit-content keyword as it's used in the wild
* add color-scheme
* add path()
* don't try to validate props with vars
* auto-compile function grammar
* remove CSS4 color functions
* show full error stack
2020-12-10 01:30:17 +03:00
tophf
75cf46aaa7 fix search-target-editor style 2020-12-08 23:03:13 +03:00
tophf
26a539bd62 fix closestVisible nearby check 2020-12-08 18:50:14 +03:00
tophf
463c3b5139 fix subsequent clicks in colorpicker
seems like new CodeMirror modifies the position object
2020-12-08 14:46:03 +03:00
tophf
6d04c0eb7d optimize parserlib by ~10%
* add skipValidation option
* set `recoverable:true` on errors inside declarations
2020-11-29 15:11:31 +03:00
tophf
e6f94378bf re-enable linter after import + async'ify
regressed in 420733b9
2020-11-29 14:25:07 +03:00
tophf
207afccd65
improve autocomplete (#1136)
* fix interaction when search overlay splits `styles`
* handle @rules and @-moz-document functions
* show props in stylus-lang
2020-11-28 23:29:33 +03:00
tophf
6847681ed3 fix findSections for {} + detect reusedEnd 2020-11-27 15:10:03 +03:00
tophf
a87c49f0fc autoresize embedded popup for config dialogs 2020-11-27 15:09:16 +03:00
tophf
cb64a6bac9 use jumpToPos more, fix coords calc 2020-11-26 17:04:51 +03:00
tophf
6451eb533c switch to styleMissingName 2020-11-26 12:09:11 +03:00
tophf
355f240779 add a suffix in editor title 2020-11-26 12:04:42 +03:00
tophf
a91183e1bb fix scrolling in jumpToPos 2020-11-25 23:28:22 +03:00
tophf
a5848682b3 normalize linebreaks in parseMozFormat 2020-11-24 21:04:11 +03:00
tophf
7c2b46be83 fix PortDownloader::onDisconnect 2020-11-24 17:06:25 +03:00
tophf
c635f2e38c monkeypatch next/prevBookmark commands to use jumpToPos 2020-11-24 15:17:14 +03:00
tophf
657798d219 improve bookmarking + rework codemirror-factory.js
* pull editing-only stuff from codemirror-default.js
* switch throttledSetOption to IntersectionObserver
2020-11-24 13:16:51 +03:00
tophf
b4ca17c531 call rerouteHotkeys when restoring scrollInfo
...because we intentionally don't focus any CM in this case as it's bugged as hell
2020-11-24 13:16:51 +03:00
tophf
32cca90ddd limit button reposition to usercss (8b58b228 fixup) 2020-11-24 12:54:39 +03:00
tophf
8b58b22825 [simple-window editor] embed popup as iframe 2020-11-24 12:16:23 +03:00
tophf
b59737a012 $create: assign style property as a string/object 2020-11-24 12:16:23 +03:00
tophf
eb99101f35 inform if the found style doesn't match site url 2020-11-24 00:07:49 +03:00
tophf
5bb1b5ef35 dedup code for modals in popup 2020-11-24 00:07:49 +03:00
tophf
4d198f56e2 faster popup load to avoid resize flicker 2020-11-24 00:07:49 +03:00
tophf
51f125113d restore top margin for #no-styles after 2bf30ed1 2020-11-23 07:29:30 +03:00
tophf
00ae843f78 unicode-aware word breaking, fixes #1124 2020-11-22 14:53:10 +03:00
tophf
2bf30ed16d
tweaks and fixes (#1123)
* same color for disabled styles in popup/manager
* reduce slider knob
* fix click-to-edit in popup on slider and iframe badge
* indicate slider interactivity on hovering name
* remove 2px gap when first/last entry is striped
* stretch 'blocked' separator to full width
* unreachable dimming should not apply to frames
* restore CWS check in popup
* increase not-applied opacity on name as it's #999 now
* oldUI: restore 'disabled' bubble + show 'usercss' fully
* adjust disabled colors to match perception because transparent text is rendered using gamma-blending whereas colored text uses LCD-antialiasing so to match opacity .6 of #000 we need #888 not #666
2020-11-22 14:09:59 +03:00
tophf
e6988d2f9e update usercss-meta, #1108 2020-11-21 20:35:23 +03:00
tophf
008e33254d add option to use sliders in manager and popup 2020-11-21 10:13:30 +03:00
tophf
3dc684f85f reduce usercss bubble to UC + tweak CSS
* remove 'disabled' bubble
* simplify .style-info
* fix double padding between version and UC
* match manager's font-weight for disabled styles in popup
2020-11-21 10:13:30 +03:00
tophf
17a0bd69c0 fix applies-to expander's collapse-on-click 2020-11-20 18:08:04 +03:00
tophf
70e3ba15b7 use 'default' internally for the default theme element
* the pref won't be influenced by the current UI language
* also reset to 'default' if failed to load the theme's css file
2020-11-20 16:10:15 +03:00
tophf
420480887e update CodeMirror 5.58.3 2020-11-20 09:50:19 +03:00
tophf
1bd366beb9 restore styleSectionsEqual in updater for non-usercss
...which was broken in bc8d8b2
2020-11-19 18:18:05 +03:00
tophf
79cd6da824 increase connection timeout to 60sec 2020-11-19 18:18:05 +03:00
tophf
420733b93a
PatchCSP + tweaks/fixes/features (#1107)
* add Patch CSP option
* show style version, size, and update age in manager
* add scope selector to style search in manager
* keep scroll position and selections in tab's session
* directly install usercss from raw github links
* ditch localStorage, use on-demand SessionStore proxy
* simplify localization
* allow <code> tag in i18n-html
* keep &nbsp; nodes in HTML templates
* API.getAllStyles is actually faster with code untouched
* fix fitToContent when applies-to is taller than window
* dedupe linter.enableForEditor calls
* prioritize visible CMs in refreshOnViewListener
* don't scroll to last style on editing a new one
* delay colorview for invisible CMs
* eslint comma-dangle error + autofix files
* styleViaXhr: also toggle for disableAll pref
* styleViaXhr: allow cookies for sandbox CSP
* simplify notes in options
* simplify getStylesViaXhr
* oldUI fixups:
  * remove separator before 1st applies-to
  * center name bubbles
* fix updateToc focus on a newly added section
* fix fitToContent when cloning section
* remove CSS `contain` as it makes no difference
* replace overrides with declarative CSS + code cosmetics
* simplify adjustWidth and make it work in FF
2020-11-18 14:17:15 +03:00
Rob Garrison
7fa4d10fd6 1.5.14 2020-11-13 19:28:48 -06:00
Rob Garrison
a065039d50 update locales 2020-11-13 19:28:21 -06:00
tophf
0b3e027bfd auto-promisify browser.* methods on call 2020-11-13 20:07:43 +03:00
tophf
3db6662d2f fix 1px shift of applies-to text when favicon is added in FF 2020-11-11 20:42:13 +03:00
tophf
6259cc2e79 speed up manager: render more targets only on demand 2020-11-11 20:27:54 +03:00
tophf
a26115154a fix getPrefs error on browser startup in the active tab 2020-11-11 13:28:20 +03:00
narcolepticinsomniac
da6361637d
Replace USO link 2020-11-10 17:47:23 -05:00
tophf
d183779fb5 accept any content-type for text/ except text/html 2020-11-10 21:40:50 +03:00
tophf
3a8f47f4db revert d405bc64 - obsolete since stylus-lang 0.54.7 2020-11-10 20:40:47 +03:00
tophf
eb70e5a2aa update stylus-lang dependency to 0.54.7 2020-11-10 20:40:47 +03:00
tophf
30b9378d2c use mousewheel to change a focused input[type=range] 2020-11-09 22:59:42 +03:00
tophf
dc4819e7d0
Merge pull request #1101 from tophf/import-prefs
import/export options in backup json
2020-11-09 21:18:23 +03:00
tophf
ff1fa07267 import/export options in backup json
* import options on demand
* auto-grant declarativeContent
* include lint configs and usercss template
* simplify exportFile as crbug.com/798705 was fixed
2020-11-09 21:12:14 +03:00
tophf
7d18376cf2 always use deepCopy for prefs.values for safety 2020-11-09 21:08:50 +03:00
tophf
bc8d8b235c fix equalOrEmpty for empty strings 2020-11-09 21:08:47 +03:00
tophf
a94969e47d remove padding from linter report items
(icons already take care of proper spacing)
2020-11-08 20:29:10 +03:00
tophf
4ac92a4f9b unbork marks in linter report
CodeMirror 5.58+ uses an additional class name for common stuff
2020-11-08 20:29:09 +03:00
tophf
5e5fecbcfe
editor: section labels, TOC, tweaks (#1086)
* section labels, TOC, speedups and fixes

* show section numbers in widgets
* debounce livePreview in usercss editor
* better fixed header and compact layout compatibility
* fix section sizing for compact layout + layout speedup
* DocFuncMapper + cosmetics + fix Clone button
* don't run linter during initSections
* remove unused/unnecessary DOM polyfills
* report invalid @document function as parser error
* rewrite section finder
* simplify focusedViaClick
* simplify setPreprocessor and make it synchronous
* throttle offscreen line widgets in usercss with lots of sections
* add on, off aliases for add/removeEventListener + onOff
* use on/off aliases in changed files
* use getters in more places
2020-11-08 11:12:42 +03:00
tophf
71cabc2029 fix animateElement() when animation is disabled 2020-11-06 21:04:10 +03:00
tophf
aac0f476b2 tweaks/fixes for popup search link
* use a less specific category if the inline search wasn't used yet
* set a href in html to prevent transitions during init
2020-11-05 22:45:22 +03:00
tophf
635fc705f9 correctly clear gutter marks for sublime bookmarks 2020-11-04 17:47:41 +03:00
tophf
31558d5071 we use 'true' and 'false' strings as boolean T_T
fixup for b56dacb
2020-11-04 12:50:24 +03:00
tophf
97ad0753e0 restore direct fetching of styles in the options frame
regressed in bf40fa81
2020-11-02 22:20:41 +03:00
tophf
a997ecbe24 update CSSLint
* fix missing <zero>
* retry/consume attr()
* code cosmetics
2020-11-02 22:08:14 +03:00
tophf
32728b023b respond with null to avoid "port closed" errors 2020-11-01 22:48:42 +03:00
narcolepticinsomniac
ad44fe47c8
typo 2020-11-01 14:06:18 -05:00
narcolepticinsomniac
1c7e06e980
typos 2020-11-01 14:01:44 -05:00
tophf
21d4221df9 position colorpicker correctly, cosmetics (89431615 fixup) 2020-10-31 23:45:52 +03:00
tophf
972a83d5bc restore simpleDeepEqual (b56dacb6 fixup) 2020-10-31 21:00:19 +03:00
silverwind
caec255e16 Simplify CI action
Only run on a single node version, that way you won't get tripe error
annotations, and it's really not neccessary for this repo to test on
multiples anyways.
2020-10-30 08:22:53 +03:00
tophf
72cb5bdc9a don't spam console errors on contextMenu 'delete' command 2020-10-29 00:13:15 +03:00
tophf
b56dacb6b2 save prefs in bg to avoid data loss
* add `now` to simplify usage of prefs.subscribe
* tweak/simplify bits by separating bg/content concerns
2020-10-28 21:10:57 +03:00
tophf
be47cfc471 throttle colorview on page load 2020-10-28 21:05:19 +03:00
tophf
6d7bd650e9 strip stylelint warnings for // comments with @preprocessor 2020-10-28 13:19:17 +03:00
tophf
a81e1b8ac3 async'ify worker-util, reduce indirection 2020-10-28 13:19:17 +03:00
tophf
4764f91453 fix radiateArray when focusing search with extra CMs around 2020-10-28 13:08:00 +03:00
tophf
74364b9d63 hide incremental search textarea, 2a6850c0 fixup 2020-10-27 13:00:32 +03:00
tophf
2747d3930b simplify resizing of editor-in-new-window 2020-10-26 18:04:37 +03:00
tophf
bd3f630617 use a safe regexp for comments 2020-10-26 18:04:37 +03:00
tophf
2a6850c02e avoid scrollbar due to incremental search + subsequent filter 2020-10-26 18:04:36 +03:00
tophf
2c674bdc0c remove the extraneous margin between applies-to 2020-10-26 18:04:35 +03:00
tophf
2ed936af00 don't autojump to first match when opening search 2020-10-26 18:04:34 +03:00
tophf
89431615b3
improve colorpicker dialog (#1079)
* switch to a user-resizable palette
* allow moving
* remove hideDelay
2020-10-26 18:03:41 +03:00
tophf
bf40fa81e8
async'ify msg, don't throw for flow control (#1078) 2020-10-26 17:39:07 +03:00
tophf
1a7b51be6b
warn when paste-importing usercss with @preprocessor, #1082 2020-10-26 17:37:31 +03:00
tophf
4fade0fdfe async'ify replaceStyle 2020-10-26 17:33:23 +03:00
tophf
34ad3cfaef embed replaceSections as replace option of initSections 2020-10-26 17:24:11 +03:00
tophf
92fcb02a57 handle document.cookie exceptions in sandboxed frames 2020-10-26 07:49:08 +03:00
tophf
4eabdf3f57 warn when paste-importing usercss with @preprocessor 2020-10-25 22:36:41 +03:00
tophf
5ba111dce9 update parserlib
* min(), max(), clamp(), also in @media
* add/fix some props and units
* handle `attr()`
* use lowerCmp()
* approve functions with USO vars
2020-10-25 00:24:28 +03:00
tophf
3a615e4e06 CodeMirror 5.58.2 2020-10-23 22:35:14 +03:00
tophf
2af83ee846 remove more -webkit- prefixes 2020-10-23 21:32:02 +03:00
narcolepticinsomniac
8598b71a73
Options tweaks (#1077)
* Options tweaks

* account for last-child change
2020-10-23 09:51:46 -04:00
tophf
595b037ab1 remove unused IS_BG and wrong AMO condition
* the user may have allowed access to AMO via about:config
* the code was wrong anyway, should be `!FIREFOX`
2020-10-23 15:22:18 +03:00
tophf
5a5512aa0f use own implementation of UUIDv4 2020-10-23 09:27:33 +03:00
tophf
54605c838b set customName only on user input 2020-10-22 23:48:17 +03:00
tophf
e6e7d7d158 leave name input empty in new usercss style 2020-10-22 23:47:46 +03:00
tophf
2d9785be6e clear dirty upon swapping style 2020-10-22 23:31:09 +03:00
tophf
0199b2c0bb preserve dirty after importing moz-format, fixes #1075 2020-10-22 23:18:58 +03:00
tophf
6593d5c05a get disableAll pref earlier, fixes #1074 2020-10-22 22:58:45 +03:00
tophf
f9804036b2
instant style injection via synchronous XHR (#1070)
* don't run web-ext test as it fails on Chrome-only permissions

* generate stylus-firefox.zip without declarativeContent

* limit note's width in options

* run updateExposeIframes only in frames
2020-10-22 22:16:55 +03:00
tophf
7f15ae324d
Merge pull request #1054 from tophf/custom-name
fix local name customization for usercss/legacy
2020-10-22 15:07:33 +03:00
narcolepticinsomniac
76e2a90392 moz-format CM focus style 2020-10-22 15:05:54 +03:00
tophf
d1b9338707 make manager load real fast 2020-10-22 15:05:54 +03:00
tophf
bc6c9c826a make editor load even faster
* reorder scripts
* make style request earlier
2020-10-22 15:03:07 +03:00
tophf
2e1a903cc7 fix local name customization for usercss/legacy 2020-10-22 15:01:49 +03:00
tophf
34f899fc45 don't start incremental search on Space or Shift-Space 2020-10-22 08:51:48 +03:00
tophf
3cb9cbb862
show a palette for current editor in color picker (#1068)
also keep the dialog visible for 30 seconds instead of 5
2020-10-18 16:40:11 +03:00
tophf
e6d73be049
option to open editor in a simple window (no omnibox) (#1067) 2020-10-18 16:37:42 +03:00
tophf
d405bc64ae ignore empty documents produced by stylus-lang bug 2020-10-18 16:34:31 +03:00
tophf
5501efb1be expose version for styles installed from greasyfork/sleazyfork 2020-10-18 16:33:52 +03:00
tophf
3d0b733e9a parserlib: skip spaces before "," in @document foo() , bar() 2020-10-16 20:09:27 +03:00
tophf
56a8212fdf parserlib: add text to background-clip 2020-10-15 22:31:13 +03:00
tophf
492b75d84e parserlib: implement @supports selector() 2020-10-14 21:33:29 +03:00
tophf
a71b621bf9 remove -webkit- prefix on standardized features 2020-10-14 19:48:59 +03:00
tophf
9e487b03e5
tweak editor (#1063)
* also apply live-preview if an unsaved style was disabled

* use box-shadow instead of outline for focus everywhere

* allow focus outline on click in text/search input or textarea

* search inputs should use the same style as text inputs

* also use box-shadow focus on delete buttons

* remove URLSearchParams workaround, not needed since Chrome 55

* use `once` in addEventListener, available since Chrome 55

* update USO bug workarounds, remove obsolete ones

* ping/pong to fix openURL with `message` in FF

* use unprefixed CSS filter, available since Chrome 53

* use unprefixed CSS user-select, available since Chrome 54

* focus tweaks

* also use text query in inline search for Stylus category

* use event.key, available since Chrome 51

Co-authored-by: narcolepticinsomniac
2020-10-13 21:14:54 +03:00
tophf
60fc6f2456
Editor fixes, make sectioned editor open quickly again (#1061)
* make usercss editor full-height again

* make sectioned editor open quickly again

* remove leftovers

* autofocus when add/clone button is clicked

* don't fit to content on clicking the add button

* scroll the window to show a manually added section entirely

* autofocus on a manually added applies-to

* disable Save button while loading

* use standard CSS for a focused CodeMirror outline

* trigger refresh sooner by one viewport in advance

* declare refreshOnView as a standard function

* run fixedHeader asynchronously to prevent self-triggering

* account for header in compact mode when fitting to content

* code cosmetics
2020-10-11 17:13:25 +03:00
tophf
ad24ee0c15
switch to USO-archive for inline search in popup, #1056 2020-10-11 16:53:42 +03:00
tophf
740a16a563 disconnect port explicitly in FF 2020-10-11 14:37:55 +03:00
tophf
4d1110986c update CSSLint
* Scroll Snap L1 (CR 2020-09-18)
* dedupe border*
* fix font-variation-settings grammar
2020-10-11 09:59:05 +03:00
tophf
af726405e1 also search in global styles 2020-10-10 14:25:43 +03:00
tophf
11ce144efb remove the redundant stylus-lang warning filter 2020-10-09 19:40:11 +03:00
tophf
707cd6576f process current contents when live-reload is enabled 2020-10-09 19:22:13 +03:00
tophf
4913da2e19 use installation url on known sites as homepage 2020-10-09 13:47:58 +03:00
tophf
78b0e33ba4 faster install from known sites 2020-10-09 13:47:57 +03:00
tophf
0162f39163 switch to USO-archive for inline search in popup
feature: retry sub.domain.tld as domain.tld if no styles are found

old bug fix: show newly added style in popup

dedupe/simplify bits of popup.js
2020-10-09 13:47:57 +03:00
tophf
5196f96ee3 trigger change on wheeling inside <select> 2020-10-09 13:47:57 +03:00
tophf
b840d4897d cleanup usoSearchCache + tidy up db.js 2020-10-09 13:47:57 +03:00
tophf
9994811819 recognize 'backdrop-filter' 2020-10-08 12:19:14 +03:00
tophf
a01bd3cd61 update polyfill for Chrome>=55 2020-10-08 11:19:18 +03:00
tophf
7c205880d2 require Chrome 55 and allow native async/await syntax 2020-10-08 11:19:18 +03:00
tophf
6b2dff6687 treat empty url-prefix() as non-matching 2020-10-08 11:07:13 +03:00
tophf
cb89be8682 ignore empty code only in global (non-targeted) sections 2020-10-08 11:05:07 +03:00
tophf
7c89f7b21d autosort style elements on own pages too 2020-10-05 21:08:39 +03:00
tophf
15d854f913 fixup for c416fa7c: remove the leftovers, take 2 2020-10-05 16:19:18 +03:00
tophf
e7a6e86b6c fixup for c416fa7c: remove the leftovers 2020-10-05 13:05:35 +03:00
tophf
c416fa7ca0
rework and move newUI+theme to options.html (#1050)
* rework and move newUI+theme to options.html

* rephrase/clarify the find styles label

* switch to USO-archive

* search for 'Stylus' keyword to filter out Stylish crud

* use archive's default search order
2020-10-02 11:10:52 -04:00
tophf
3f6c85637c Containment Module L2 (WD, 2020-06-03) 2020-10-02 12:41:57 +03:00
tophf
e0a7372f4f enable starHack option 2020-10-02 12:41:57 +03:00
narcolepticinsomniac
038629517e
remove dropbox disabled (#1041) 2020-09-22 12:04:19 -04:00
tophf
ee30aa1407
convert colors in uso preprocessor to match USO site (#997)
#rrggbb for /*[[color]]*/
r,g,b for /*[[color-rgb]]*/

(no alpha channel)
2020-09-22 07:15:40 -04:00
tophf
2b149f97a5
CodeMirror 5.58.0 (#1037) 2020-09-22 07:03:31 -04:00
tophf
30983db679
Scroll Anchoring L1 (ED 2020-09-18) (#1038) 2020-09-22 06:56:53 -04:00
tophf
fa1496ecb8
use tab.pendingUrl (#1040) 2020-09-22 06:54:48 -04:00
tophf
aa43507478 parserlib: consume unknown @-rules per CSS grammar 2020-09-21 11:19:53 +03:00
eight
07ba44cc2c
Change: switch to launchWebAuthFlow polyfill (#1017)
* WIP: add webextLaunchWebAuthFlow

* Change: switch to webextLaunchWebAuthFlow

* Bump dependencies

* Fix: use minimized version

* Fix: wrong call to promisifyChrome
2020-08-31 16:38:18 +08:00
narcolepticinsomniac
01cfb435f6
Remove deprecated dropbox and add sync button (#1025)
* Remove deprecated dropbox and add sync button

* re-use existing message
2020-08-24 12:27:23 -04:00
tophf
5109f9abb3
CodeMirror 5.57 (#1023)
* codemirror 5.57

* dedupe the props defined in new codemirror
2020-08-23 11:44:27 -04:00
dependabot[bot]
e0aadb752a Bump minimist from 1.2.0 to 1.2.5
Bumps [minimist](https://github.com/substack/minimist) from 1.2.0 to 1.2.5.
- [Release notes](https://github.com/substack/minimist/releases)
- [Commits](https://github.com/substack/minimist/compare/1.2.0...1.2.5)

Signed-off-by: dependabot[bot] <support@github.com>
2020-08-20 21:16:13 +03:00
dependabot[bot]
db3747d5f0 Bump lodash from 4.17.15 to 4.17.19
Bumps [lodash](https://github.com/lodash/lodash) from 4.17.15 to 4.17.19.
- [Release notes](https://github.com/lodash/lodash/releases)
- [Commits](https://github.com/lodash/lodash/compare/4.17.15...4.17.19)

Signed-off-by: dependabot[bot] <support@github.com>
2020-08-20 21:10:55 +03:00
tophf
56f1574433
fix styling of options frame in FF (#1022) 2020-08-19 15:33:04 -04:00
eight
a0172c262c
Fix: enable linter after processing metadata (#1018) 2020-08-17 21:06:52 +08:00
eight
7d93608186
Fix: show sync start error (#1016)
* Update lock file

* Fix: the first sync doesn't collect error messages
2020-08-14 22:32:24 +08:00
tophf
54b1f218e0
Enhance: promisify chrome into browser, drop promisify (#866)
* promisify `chrome` into `browser`

* comment

* comment

* comment

* Add: a naive browser polyfill

* Fix: polyfill doesn't detect content script env correctly

Co-authored-by: eight04 <eight04@gmail.com>
2020-08-14 20:16:01 +08:00
tophf
3d94c641b3
CodeMirror 5.56 + csslint update (#1014)
* CodeMirror 5.56

* csslint: Overscroll Behavior L1 (ED 2020-01-16)

* don't list rare/obsolete/future/existing css props
2020-08-13 13:58:09 -04:00
tophf
96c87fc55d fix colorpicker rgb/hsl parser with leading/trailing space 2020-08-09 15:03:20 +03:00
tophf
d18314357f
use onBoundsChanged to remember editor size/position (#1007) 2020-08-02 00:00:44 -04:00
tophf
f8402a2211
use mousewheel to change focused "input[type=number], select" (#1010)
* use mousewheel to change focused "input[type=number], select"

* revert 0f394fa8 (no longer needed)
2020-08-01 23:50:12 -04:00
tophf
0f394fa8d8 don't scroll CM when target is a scrollable input 2020-08-01 18:00:08 +03:00
tophf
7e7839bb1e
avoid adding # to the page URL when clicking dummy links (#1006) 2020-07-29 21:30:00 -04:00
tophf
c43315e697
restore Shift-Ctrl-Wheel to scroll window in multi-section mode (#1005) 2020-07-29 21:26:38 -04:00
Rob Garrison
837b119e47 1.5.13 2020-07-24 08:19:00 -05:00
Rob Garrison
282a1e7432 Update translations 2020-07-24 07:48:25 -05:00
tophf
e1807c8851 restore autosize-on-type of find input 2020-07-24 07:42:58 +03:00
tophf
7e195594b5
show all spaces in find input in firefox (#1002) 2020-07-23 17:27:29 -04:00
tophf
079b7a39f1
update the style list after DOM (#998)
regressed in 7e6edb9e
2020-07-17 15:42:21 -04:00
tophf
7e6edb9e1b
add/remove style elements on global toggle (#990) 2020-07-15 00:31:33 -04:00
tophf
787466fc40
consume leftovers when auto-completing properties (#992) 2020-07-14 16:25:19 -04:00
tophf
09f6e8d44a
prolong worker lifetime to 5 minutes (#993) 2020-07-14 16:24:00 -04:00
tophf
ca3633b896
fix column combinator detection (#994) 2020-07-14 16:21:47 -04:00
tophf
2f4658657d
increase USO style search timeout (#984) 2020-06-29 12:16:44 -04:00
tophf
429c34ca8b
csslint: Selectors L4 parts (ED 2020-04-07) (#981)
* add :where(), remove :matches()

* add "s" case-sensitivity flag

* add "||" column combinator
2020-06-26 12:47:12 -04:00
tophf
4e146d0e54
use current-line mode for csslint allow override (#977) 2020-06-25 12:08:04 -04:00
tophf
574f11b552
when sorting on updateDate use installDate as a fallback (#975) 2020-06-25 11:51:08 -04:00
tophf
60a37af0e0
add a hotkey & right-click to beautify silently (#972)
* add a hotkey & right-click to beautify silently

* fix closestVisible
2020-06-22 12:14:41 -04:00
tophf
e1ed3bf222
update CSSLint (#967)
* refactor Tokens to enable goto-symbol and find-usages

* refactor <length-percentage>

* CSS Text Module L3 (ED 2020-06-08)

* CSS Fonts Module L4 parts (ED 2020-06-11)

* CSS Scrollbars Module L1 (ED 2020-02-24)

* skip all successfully parsed parts after var()
2020-06-17 09:13:28 -04:00
silverwind
2ffad1b6bb
Add Github Actions (#958)
The tests do spew a few warnings but no errors so this should pass. I
did not bother adding macOS/Windows to the test matrix as there's
probably nothing platform-dependant in this repo, but it could be done
on request.
2020-06-11 00:48:48 -04:00
tophf
015bda764a
update deps + split devdeps/deps (#953) 2020-06-02 04:08:02 -04:00
narcolepticinsomniac
60d314b165
Improve delete confirmation autofocus visual indication (#956) 2020-06-02 02:33:07 -04:00
tophf
a1b0eb7df1
show sublime bookmarks (#951)
* remove redundant setGutterMarker optimization

* show sublime bookmarks
2020-06-01 00:54:49 -04:00
tophf
39c62e684e
throttle DOM updates in manager while importing (#950) 2020-05-31 20:14:42 -04:00
MATE
d9f5ef138c Add Korean translation 2020-05-31 22:36:40 +03:00
tophf
a7a9ee7205
use major browser version in CHROME constant (#946) 2020-05-31 01:43:56 -04:00
tophf
a8fe66550b
Fixes for csslint parserlib (#947)
* css parser: vars in @supports(--var: foo)

* css parser: @keyframes inside @supports and @media
2020-05-29 23:33:29 -04:00
tophf
379c825408
focus search field on Ctrl-F in manager (#935) 2020-05-22 23:12:54 -04:00
mcpower
367d1672c5
Add unlimitedStorage permission (#930)
IndexedDB storage may be evicted by the browser at its discretion [1].
Granting the unlimitedStorage permission ensures that IndexedDB data
will not be evicted [2], and allows the extension to store more than
5MB of data if needed [3].

[1]: https://web.dev/storage-for-the-web/#eviction
[2]: https://crbug.com/680392#c5
[3]: https://developer.chrome.com/apps/declare_permissions#unlimitedStorage
2020-05-16 15:51:50 -04:00
tophf
43f6bdf4ed
use a separate loadTimeout for the actual data transfer (#931) 2020-05-16 15:50:04 -04:00
tophf
7ab0651e4d
add ":" to prop names in css autocomplete menu (#915) 2020-05-01 01:52:18 -04:00
narcolepticinsomniac
81b6137d8a
Max version Dropbox disable and no sync pull animations (#913)
* Max version Dropbox disable and no sync pull animations

Max version Dropbox disable and no sync pull animations.

* Remove TODO

* Kill sync animations entirely, add ref
2020-04-28 10:06:57 -04:00
narcolepticinsomniac
c50b3cfddc
Fix missing CWS page icon (#907) 2020-04-23 09:43:04 -04:00
Rob Garrison
bdf32a7174 1.5.12 2020-04-22 07:26:24 -05:00
Rob Garrison
23a718d8bc 1.5.11 2020-04-20 22:54:05 -05:00
narcolepticinsomniac
0b4e946c82
CM scrollbar pointer-events (#902) 2020-04-17 07:36:37 -04:00
tophf
fea0d08f50
fix infinite loop in colorizeInvisible (#889) 2020-04-17 07:34:52 -04:00
narcolepticinsomniac
7ae0798364
fix FF shortcuts button (#900) 2020-04-16 06:17:12 -04:00
Gusted
d7ff697aa6
Contributing.md - Fix Build.md link (#887)
The non Capitalized link will give you the 404 page not found error.
[Wrong link Current one](https://github.com/openstyles/stylus/blob/master/Build.md)
[Correct link Commited one](https://github.com/openstyles/stylus/blob/master/BUILD.md)
2020-03-23 19:06:54 -04:00
narcolepticinsomniac
50aecffe4c
set min-height outside of query (#873) 2020-03-03 11:13:21 -05:00
Rob Garrison
ad4da5f37d 1.5.10 2020-02-27 08:07:47 -06:00
Rob Garrison
dc3faeeead Update locales 2020-02-27 08:04:52 -06:00
tophf
8192fab1b8
show write-style entries for iframes in popup (#861)
* account for iframes in popup list/write-style and badge

* fix and simplify openURL + onTabReady + message from popup

* fixup! resolve about:blank iframes to their parent URL

* fixup! don't underline iframe links until hovered

* fix width bug in popup only when needed (Chrome 66-69)

* fixup! reset styleIds on main page navigation

* fixup! call updateCount explicitly on extension pages

* fixup! ensure frame url is present

* fixup! frameResults entry may be empty

* fixup! init main frame first

* fixup! track iframes via ports

* fixup! reduce badge update rate during page load

* fixup! cosmetics

* fixup! don't add frames with errors

* fixup! cosmetics
2020-02-24 18:16:45 -05:00
tophf
4bbce7cb9f
fix and simplify .user.css URL installer (#856)
* fix and simplify .user.css URL installer

* Refactor: pull out tab-manager and icon-manager

* fixes/cosmetics

* usercss installer url check
* extract downloaders
* simplify tabManager
* rework/split openInstallerPage
* use a simple object instead of map
* trivial bugfixes
* cosmetics

* fixup! updateIconBadge in styleViaAPI

Co-authored-by: eight <eight04@gmail.com>
2020-02-23 10:43:26 -05:00
tophf
c3b7657433
fix category check when searching for foo.bar.tld (#863) 2020-02-22 07:37:22 -05:00
tophf
df8c258c84
fix usage of openerTabId in openURL + cosmetics (#859)
* fix usage of openerTabId in openURL + cosmetics

* fixups

* fixup
2020-02-20 17:54:54 -05:00
eight
8380a674b9
Upgrade uuid (#858)
* Upgrade uuid

* Fix: eslint
2020-02-20 07:17:15 -05:00
Rob Garrison
6875cc33b4 1.5.9 2020-02-16 11:23:47 -06:00
Rob Garrison
8ad0ec2518 README: update build instructions 2020-02-16 11:08:24 -06:00
Rob Garrison
146cd6d3a6 Update locales 2020-02-16 10:53:11 -06:00
Rob Garrison
948d596efa Meta: Update package-lock.json 2020-02-16 10:52:21 -06:00
eight
4831183207
Rewrite the build script (#852)
* npm install

* Drop remove-modules

* Drop updates

* Refactor: rewrite build script

* Add: BUILD.md

* Add: start-chrome command

* Rebuild vendor

* Update contributing.md

* Update BUILD.md

* Update BUILD.md

Co-authored-by: narcolepticinsomniac <therealdoctorgonzo@gmail.com>
2020-02-14 12:02:57 -05:00
eight
9f2261ae95
Fix: Onedrive sync is broken on Firefox (#854)
* Update lock file

* Fix: Onedrive sync is broken on Firefox
2020-02-14 11:51:00 -05:00
Rob Garrison
0defa94c6e 1.5.8 2020-02-13 21:09:19 -06:00
tophf
108907d4f1
skip transition-patch in invisible tabs (#851) 2020-02-12 11:44:25 -05:00
tophf
ccb2e899b3
Simplify & speed up style injection (#843)
* use wrappedJSObject to create style elements in page context

* skip unnecessary polyfills in content scripts

* group all style management stuff in injector

* support all API methods in content scripts
2020-02-12 09:39:00 -05:00
tophf
0b5115fc8a
also handle removed/replaced/errored tabs in injectToAllTabs (#850) 2020-02-12 17:08:25 +03:00
tophf
7109d33e4e
wait for tabs to load when reinjecting (#849) 2020-02-12 07:49:14 -05:00
narcolepticinsomniac
02a575a9d6
Add reload context menu item (#848)
* Add reload context menu item

* Recheck on initial install

* convert createContextMenus to function

* reload command
2020-02-12 07:47:24 -05:00
tophf
438fdebc5c
apply show-badge option to all tabs (#847) 2020-02-11 09:14:09 -05:00
eight
0a79bde610
Refactor chromeStorageDB (#840)
* Fix: the return type of dbExecChromeStorage('put') is wrong

* Refactor: pull out db-chrome-storage

* Fix: the signature of putMany is different
2020-02-10 09:56:07 -05:00
Rob Garrison
f32b6b6989 1.5.7 2020-02-02 11:07:55 -06:00
Rob Garrison
9d765e91c6 Update vendor dependencies 2020-02-02 11:07:22 -06:00
Rob Garrison
8e07ee697c Meta: Update dependencies 2020-02-02 10:55:32 -06:00
narcolepticinsomniac
ff6e941958
Account for Dropbox bug (#833)
* Account for Dropbox bug

* Breaking: deprecate dropbox backup

* Drop: dropbox and zipjs-browserify

* Breaking: remove dropbox backup

* Fix: remove href from the command

Co-authored-by: eight <eight04@gmail.com>
2020-02-02 09:02:25 -05:00
narcolepticinsomniac
1f12d50aaf
Embed options in manager (#828)
* Embed options in manager

* fix indent again

* Fix edit URL detected as manage URL when creating manager style from popup

* Syntax, hash only, and prevent empty hash

* Fix: move origin check to background

* Rename eslintrc

* Refactor: openURL

* Add: fixme comment about openEditor

* Fix: allow activating manager in other windows

* Add: trimHash method

* Fix: limit the scope of styleViaAPI

* Breaking: add router, keep search params

* Fix: focus options when activated

* Add: some fixme

* Fix: remove unused fixme

* Fix: minor

* Fix: remove unused message

* Add: doc

* Change: activate manager in other windows

* Fix: make sure sender is available in getTabUrlPrefix

* Add: openManage API

* Change: reuse editor in openEditor

* Fix: greedly pop the buffer

* Fix: backward detection

* Fix: remove unused important

* Fix: remove unused workaround

* Fix: avoid empty search param

* Change: detect all kinds of manager in openManage

* Fix: minor

* Manage button text

Co-authored-by: eight <eight04@gmail.com>
2020-02-01 23:36:54 -05:00
tophf
d3ee6d9498 fix an infinite loop in editor search with /^/ or /$/ (#832) 2020-01-27 21:39:36 -05:00
tophf
2b985a0a91 automatic category fallback in style search (#831)
Even better. Seems way more reliable. Thanks tophf.
2020-01-27 21:36:43 -05:00
narcolepticinsomniac
a3c22325b8
Onedrive beta and Opera compatibility 2020-01-14 19:06:35 -05:00
narcolepticinsomniac
6cb038535e
Copyable elements accessibility (#808) 2020-01-06 19:13:49 -05:00
Rob Garrison
2ea5290ea0 Fix word breaks in popup (#805)
* Fix word breaks in popup. See #312

* Click to copy

* Copy styling

* Copy message

* Copy styling

* cleanup

* cleanup
2019-11-17 22:23:12 -05:00
Conrad
a2a8908e70 Update README.md (#806) 2019-11-17 08:34:57 -06:00
narcolepticinsomniac
2742cbe120
Sync status shouldn't be clickable (#804)
* Sync status shouldn't be clickable

* sync status shouldn't be clickable
2019-11-14 19:17:25 -05:00
narcolepticinsomniac
41aa869c33
Style sync select (#803)
* Style sync select

* Style sync select

* cleanup
2019-11-14 00:50:38 -05:00
eight
f9db43a2e9 Add: sync database to a cloud drive (#787)
* Add key

* Add: a second index uuid, push changes to sync controller

* Add: sync.js

* Add: tokenManager

* Change: log entire body for http error

* Add: token flow

* Fix: minor

* Fix: move cleanup to stop function

* Add: syncNow

* Update dependencies

* Fix: handle 401 error

* Add: handle 401 error

* Fix: then -> catch

* Add: sync options to options page

* Update db-to-cloud

* Change: make prefs.set return a promise

* Add: disble selector if connected

* Add: update selector state

* Fix: return promise in prefs.set

* Fix: manage complex state

* Fix: handle prefs change

* Change: manage sync status in background

* Add: show current status in the UI

* Add: schedule a faster sync when db changed

* Update dependencies

* Add: include progress in sync status

* Add: more detail status

* Show status text only

* Bump dependencies

* Change: show loaded and total

* Fix: syncTarget is undefined

* Add: google and onedrive

* Fix: token is not reused

* Bump dependencies

* Don't use minified version since it is hard to debug

* Fix: expire time is incorrect

* Change: switch google to code flow

* Bump dependencies

* Change: only modify pref if the initialization success?

* Don't stop the sync if the first sync is not triggered by the user

* Add: implement refresh token

* Change: switch microsoft to code flow

* Add: subtract expire with a latency

* Add: microsoft client secret

* Add: display error message

* Fix: fromPref is not used

* Change: try to revoke the token when log out

* Add: revoke dropbox token

* Fix: Google only generates one refresh token for one user by default

* Bump dependencies, fix onedrive list issue

* Fix: arguments sent to sync.put is wrong

* Fix: don't schedule a sync on db changed if not connected

* Bump dependencies. Fix issue of switching drives

* Bump db-to-cloud, fix switching drive issue

* Fix: only auth user on 401 error, don't display login window without user interaction

* Fix: don't call revoke() if token is undefined

* Add: login button to generate the access token interactively

* Fix: make addMissingProperties a local

* Fix: store missing props in an object

* Fix: sync.getStatus should be sync

* LATENCY -> NETWORK_LATENCY

* Fix: cache the token forever if there is no expire time e.g. dropbox

* Add some comments

* Fix: i18n

* Fix: i18n sync status

* fixup! Fix: i18n sync status

* Fix: 'sync to cloud' is displayed twice
2019-11-05 14:30:45 -05:00
tophf
c0fd71dda6 only recalc applies-to css on live-update if style targets the editor 2019-10-13 20:59:50 +03:00
tophf
5459130111 find @-moz-doc sections faster in the editor (#786)
* find @-moz-doc sections faster in the editor

* only recreate widgets if section data is changed

* CodeMirror speedup: reuse the old folding marks

* add a reminder to remove the CodeMirror hack in the future

* use precise getTokenAt

* check doc type for string/comment to be more mode-agnostic

* fix setGutterMarker hack

* fix skipSpace: EOL is a space too

* move deepEqual next to deepCopy

* fix getTokenTypeAt check for some cases

* remove the unnecessary \s*
2019-09-25 04:44:33 -04:00
Rob Garrison
a7defbfc40
Popup border (#781)
* Popup: Fix horizontal scrollbar in Chrome

* Hide popup borer option in newer Chrome versions. Closes #780

* Add chrome bug range constant
2019-09-24 08:21:36 -05:00
Rob Garrison
d8c2cd449e Meta: Update dependencies 2019-09-11 07:19:00 -05:00
Rob Garrison
03959ffcc8 1.5.6 2019-09-11 07:05:39 -05:00
Rob Garrison
277e56ea44 Fix linting issues 2019-09-11 06:56:19 -05:00
eight
f4b1ea8a90 Change: check style.sheet instead of getComputedStyle (#779) 2019-09-11 03:32:34 -04:00
Roger
da9178096b Add primary header to README and replace HTML with markdown equivalent (#765) 2019-09-09 01:00:41 -04:00
eight
450984abf3 Fix: don't assign undefined to style object (#764) 2019-09-09 00:48:44 -04:00
eight
34dfd83056 Change: a better way to defer page script injection, switch to inline script (#766)
* Revert "Revert defer page script injection (#758)"

This reverts commit 7d52326eb7.

* Add: store style count in sessionStorage

* Change: use localStorage instead of sessionStorage

* Update web-ext

* Fix: page script doesn't work in XML

* Fix: promise never returns if the script is rejected

* Change: disable page script preload

* Drop: stylusStyleCount
2019-09-09 00:45:08 -04:00
eight
a7445011a9 Enhance: faster msg.sendBg (#774) 2019-09-09 00:40:07 -04:00
Rob Garrison
0e9d5ce08c 1.5.5 2019-08-07 21:20:10 -05:00
narcolepticinsomniac
793dc20722
linter and compact layout improvements (#749)
* linter and compact layout improvements

Closes #748

While investigating the best way to fix linter scrolling, when I double-checked the compact layout, an old bug I meant to fix a long time ago was immediately apparent. Basically, the linter adds/removes errors as you type, causing the editor to bounce up and down, making it practically unusable. 

This PR fixes scrolling, and also collapses options and the linter in the compact layout, but always shows the collapsed linter so you're aware of the error count without the content jumping. It also collapses options in the non-compact layout if the viewport is too short to accommodate them, factoring in the min-height of the linter. All automatic collapsing factors in whether a linter is active so they can adjust accordingly, and disables the setting of collapsed state prefs, since we're deciding the pref anyway, and this allows for re-expanding on resize based on the previous pref.

It's quite possible I failed to account for certain scenarios, so try to break it. Also think it's problematic for the linter to not always be visible if enabled, so I hooked up a 40px fixed header on scroll with just the linter in it for the compact layout.

A few other little details are included. I removed redundant line and column numbers spelled out at the end of lint messages to prevent horizontal overflow. I noticed that the expand/collapse prefs do not toggle correctly when clicking directly on the details-marker arrow. Simplest solution was covering them with the `h2` (we may wanna hook up the manager as well). Also, unrelated, but I switched to opacity to hide resizing sectioned editors, because `visibility: hidden;` breaks editor auto-focus.

If either of you guys wanna fix any bugs, or improve any code, feel free to just commit to this PR directly.

* linter and compact layout improvements

* linter and compact layout improvements

* No usercss scroll listener and delay header check

* Some code tweaks
2019-08-04 13:09:50 -04:00
narcolepticinsomniac
7d52326eb7
Revert defer page script injection (#758)
Closes #756

I edited this a couple days ago, so let's double check the diff and make sure I didn't screw anything up.
2019-08-04 13:04:02 -04:00
narcolepticinsomniac
00c2455b07
Slightly wider menu button 2019-07-22 02:31:11 -04:00
tophf
7206f4cd9e Fix colorpicker (#745)
* process CM5.48+ new token for color functions

* restore scientific number notation support in colors

rgba(1.5E2 0 0 / .5e2%)
2019-07-14 20:16:53 -04:00
Rob Garrison
c2e83fb3c4 1.5.4 2019-07-08 07:07:31 -05:00
Rob Garrison
b7b6ed2ff5 Update vendor libraries
Codemirror & Dropbox sdk
2019-07-08 04:59:28 -05:00
Rob Garrison
b32bafd149 Update translations 2019-07-08 04:50:51 -05:00
Rob Garrison
d415643775 Meta: Update dependencies 2019-07-08 04:33:51 -05:00
narcolepticinsomniac
bc56a7abd6
menus as dialogs (#727)
* menus as dialogs

I think it's better in general, and also eliminates the glitchiness of expandable menus in FF, not to mention possible scroll issues on long style lists with overflow, plus the fact that we could add many menu items in the future without the menu looking ridiculous.

IDEK how the formatting got screwed up and borked the diffs in the other PR. I updated my editor config plugin, so let's see if that helps. I restarted with a fresh master and copy/pasted the changes, so if it's still screwy, I guess my editor needs more help. If not, I'm not playing around with it anymore right now, and the other PR does at least work.

* menus as dialogs
2019-06-20 15:37:40 -04:00
eight
6fbc291b19 Fix: single editor doesn't stretch to full height (#728)
* Fix: single editor doesn't stretch to full height

* Fix: remove redundant selectors
2019-06-20 15:36:53 -04:00
eight
d1db77e5b0 Add: auto resize editors on init in section editor (#729)
* Add: auto resize section editor on init

* Fix: calculate cm height correctly

* Fix: query heights before set

* Fix: work with small layout, hide sections before ready
2019-06-20 15:36:27 -04:00
eight
c61806974f Fix: db initializing error, polyfill localStorage/sessionStorage (#616)
* Fix: make sure all errors are caught when initializing

* Fix: polyfill localStorage and sessionStorage
2019-06-20 15:35:41 -04:00
narcolepticinsomniac
514fa3204f
Simplify exclusions (#724)
* Change: exclusion should match urlWithoutParams

Revert to eight04's initial two commits in #681 which make exclusion toggles domain and singular URLs only, plus reincorporate the js menu height calculation.

* Change: drop excludeStyleByUrlRedundant plus menu height

* menu item text

* Make exclusion rules work like match pattern and handle invalid URLs

* Exclude rules in tooltips

* Remove leftover code

* Cross-browser overflow consistency
2019-06-11 10:44:32 -04:00
narcolepticinsomniac
19c71868a0
Improve height restriction for popup config dialog (#721)
* Improve height restriction for popup config dialog

Noticed unnecessary overflow in Github Dark's config dialog. We should allow it to utilize available height.

* Shave off 2px to allow for borders without overflow
2019-06-01 05:12:52 -04:00
eight
19ee4d46bc Change: report updatable after the install button is prepared. Prevent installing duplicated styles from USO (#717)
* Fix: don't install duplicate styles on USO

* Change: report updatable after the install button is prepared
2019-06-01 05:12:06 -04:00
eight
0d6c2bdb42 Fix: onAlarms is registered multiple times (#702) 2019-06-01 05:11:22 -04:00
eight
60693e8c3f Fix: change disabled state after the content is set (#701) 2019-06-01 05:10:44 -04:00
narcolepticinsomniac
eb6888e44c
Avoid potential transition patch conflicts (#720)
8's fix - closes #706, closes #707
2019-05-26 06:12:02 -04:00
Rob Garrison
8906cbbb1a
Prevent importing styles with no section. Fixes #687 (#697)
* Prevent importing styles with no section. Fixes #687

And prevent issues with existing styles with no section

* Revert sections checks for empty array

* Add styleJSONseemsValid function

* Clean up

* Fix eslint nags
2019-04-16 07:32:06 -05:00
stonecrusher
b48817fe01 Update Stylelint defaults for Stylus-lang at-rules (#695)
* Update Stylelint defaults for Stylus-lang at-rules

* Minor tweak
2019-04-14 08:39:22 -05:00
Rob Garrison
ab83ed8f0f Fix linter config issues. Closes #689 2019-04-05 07:42:37 -05:00
narcolepticinsomniac
6e0d073cbf Fix missing FF favicons Closes #683 (#686)
* Fix missing FF favicons Closes #683

I think the combo of  swapping `window.prefs` for `prefs`, and moving the function inside `onDOMready()` does the trick. 

@eight04 Any idea why `window.prefs` no longer works? Does the rest LGTY?

* Add suggestions
2019-03-25 07:48:53 -05:00
Rob Garrison
5f21213693 1.5.3
This version is a placeholder, the commit history does not match
the content available in the current extension
2019-03-24 19:00:05 -05:00
Rob Garrison
a29d7dc4ca Fix USO install button
Narcolepticinsomniac worked out the code
2019-03-24 18:08:01 -05:00
eight
b40849acad Refactor: rewrite style injector (#664)
* Refactor: style injector/docRootObserver/docRewriteObserver

* Fix: minor

* Fix: disabled state

* Fix: use evade

* Fix: apply.js is broken in our pages

* Fix: transition patch is broken

* Fix: also check elements after the last userstyle

* Fix: remove outdated FIXME. styleInjector.toggle now toggle all styles

* Fix: call Object.keys twice

* Add a fixme

* Fix: typo

* Add a fixme

* Fix: don't argue for mutations generated by other extensions
2019-03-09 20:58:17 -06:00
Rob Garrison
744bf01147
Firefox allow CSP UserCSS install from anywhere. See #618 (#623)
* Firefox allow CSP UserCSS install from anywhere. See #618

* Fix suffix extension
2019-03-03 20:42:03 -06:00
tophf
55189f1fdd CSSLint and parserlib (#646)
* CSSLint: add mask-image

https://drafts.fxtf.org/css-masking-1/#the-mask-image

* CSSLint: update <image> type

https://drafts.csswg.org/css-images-3/#typedef-image

* CodeMirror CSS mode: add 'mask-image'

* CodeMirror CSS mode: add CSS Round Display L1

https://www.w3.org/TR/css-round-display-1/

* CSSLint: CSS Round Display L1 (ED 2018-09-26)

https://drafts.csswg.org/css-round-display/

* CSSLint: CSS Environment Variables L1 (ED 2018-08-03)

https://drafts.csswg.org/css-env-1/

* CSSLint: parts of CSS Overflow Module L3 (WD 2018-07-31)

only overflow-* properties are added since the rest seem tentative
https://www.w3.org/TR/css-overflow-3/

* CSSLint: Selectors L4 :is() supersedes :matches()

https://drafts.csswg.org/selectors-4/#matches

* CSSLint: Text Decoration L3 (CR 2018-06-26)

https://drafts.csswg.org/css-text-decor-3/

* CSSLint: fix '&&' in grammarParser

consequences:
* fixed text-shadow
* fixed <display-listitem>
* switched to a string in <shadow>

* CSSLint: fix definition for 'rotate'

* CSSLint: fix applyEmbeddedOverrides

* CSSLint: update definition for 'rotate'

* CSSLint: reset parserlib cache when inline overrides change

* CSSLint: code cosmetics

* CSSLint: fixup d5971e9c

* CSSLint: code cosmetics

* CSSLint: start ignoring from the comment's line number
2019-03-03 16:55:15 -06:00
eight
cdc7f98150 Add: user-frendly exclusions (#666)
* WIP: popup UI

* Fix: use simple menu toggle

* Add: inclusion/exclusion API

* Add: hook exclusion UI

* Fix: minor

* Fix: don't self-edit

* Icons and accessibility

* Icons and accessibility

* Fix: disable redundant exclude-by-url checkbox

* Disabled cursor and delete leftover code

* Generic menu button tooltip and tweak menu item cursors

* Generic menu button tooltip and tweak menu item cursors

* Generic menu button tooltip and tweak menu item cursors
2019-03-03 16:54:37 -06:00
eight
1ff34fc449 Fix: return true in icon API (#669) 2019-03-03 08:29:26 -06:00
eight
4262882ac9 Add: enable usercss updateURL (#661)
* Add: use metadata.updateURL as style.updateUrl

* Change: only use the installation URL as the update URL if not specified in usercss

* Fix: hide live reload checkbox according to installationUrl
2019-02-13 19:09:18 -06:00
eight
dfb9db34c3 Fix: update live preview when the style is toggled/replaced (#662) 2019-02-03 14:00:13 -05:00
Rob Garrison
0bd738b49f Set updateDate on editSave. Closes #663 2019-02-02 07:01:08 -06:00
tophf
93a15a8284 fix colorview detection with inline comments (#660) 2019-02-01 07:36:08 -06:00
narcolepticinsomniac
756fafe263
Ensure applySections is complete 2019-01-29 23:30:37 -05:00
narcolepticinsomniac
b18d062d51
Use setTimeout for transition patch Fixes #648 (#656) 2019-01-29 11:14:07 -05:00
Rob Garrison
1bdd0512ae Prevent JS error in messages 2019-01-27 18:37:12 -06:00
Rob Garrison
e8ba51f940 Fix applies to alignment 2019-01-17 06:41:47 -06:00
narcolepticinsomniac
c56f5c32f1
Revert expose iframes by default
I don't I think I care enough to argue with someone who's technically correct.
2019-01-09 23:43:28 -05:00
narcolepticinsomniac
ae7e0799fe
Expose iframes by default 2019-01-09 21:22:43 -05:00
narcolepticinsomniac
89b18dca45
Account for max-content bug in Chrome 49 2019-01-08 02:41:39 -05:00
narcolepticinsomniac
ab688368e1
Specify width for newer versions of Chromium Fixes #639 2019-01-08 02:15:53 -05:00
narcolepticinsomniac
ddc6e79af6
Return pointer-events to error modals Fixes #637 2019-01-06 17:07:52 -05:00
eight
ad5c56c774 Fix: handle editDeleteText message (#635) 2019-01-02 07:46:51 -06:00
Rob Garrison
1fbbeae9b9
Update popup style sort after toggle. Closes #619 (#624)
* Update popup style  sort after toggle. Closes #619

* Add popup auto resort option

* Switch autoResort to true by default

* Refactor sorting

* Fix: simplify sortStyles function

* Change: autoResort=false
2018-12-31 23:11:45 -06:00
eight
f5db0a5ab0 Refactor: pull out content-scripts.js (#625) 2018-12-30 18:01:54 -06:00
eight
aa56f96585 Fix: catch syntax error (#627) 2018-12-28 20:55:52 -06:00
Rob Garrison
0c8e69fb9d Make beautify options persistent. Fixes #630 2018-12-27 07:53:37 -06:00
Rob Garrison
3ea6e94e83 Fix expose iframe option example in popup. Closes #629 2018-12-26 22:40:56 -06:00
eight
52b7742a45 Change: don't wait when broadcasting messages (#622) 2018-12-26 06:51:21 -06:00
Rob Garrison
ddd03eab1d Prevent JS error in multiple section search 2018-12-19 07:09:16 -06:00
Rob Garrison
4c11c8324e 1.5.2 2018-12-11 07:34:05 -06:00
Rob Garrison
3edea88b13 Fix linting issues 2018-12-11 07:28:30 -06:00
Rob Garrison
b57d30a70b Update locales 2018-12-11 06:25:41 -06:00
Rob Garrison
8e3ea662e4 Update dependencies 2018-12-11 06:25:23 -06:00
eight
626148c117 Fix: remove unused code (#612) 2018-12-11 06:19:19 -06:00
Rob Garrison
7f3013f477
Fix & cleanup config in popup. Closes #602 (#603)
* Fix & cleanup config in popup. Closes #602

* Hide icon for USO with no config
2018-12-10 07:57:44 -06:00
tophf
8b8a3d60ab spoof HTTP Referer for USO directly (#608) 2018-12-10 07:14:43 -06:00
Rob Garrison
68ce3a653e Focus first editor on init. Fixes #605 2018-12-08 07:51:09 -06:00
Rob Garrison
8a3c260eb9 Fix save template. Closes #607 2018-12-08 07:21:32 -06:00
eight
30d00dbd3a Fix: don't use null as id (#606) 2018-12-07 21:57:43 -06:00
Rob Garrison
779a6e4e35 Fix keyboard interaction on config icon 2018-12-03 18:25:13 -06:00
Rob Garrison
da5b4c029b Remove unnecessary code 2018-12-03 18:23:41 -06:00
Rob Garrison
9fe721945d Set installType for on new installs
Fixes Dropbox thinking the browser is in dev mode
2018-11-29 22:04:43 -06:00
Rob Garrison
aca0572f4d 1.5.1 2018-11-29 20:21:26 -06:00
Rob Garrison
53d130a179 Update vendors 2018-11-29 20:11:39 -06:00
Rob Garrison
c4af63237c update locales 2018-11-29 19:41:18 -06:00
eight
8d6c88e377 Support Chrome 49 (#561)
* Add: polyfill to support chrome 49

* Fix: fetch text in Chrome 49

* Add: polyfill element method

* Update usercss-meta

* Fix: buggy destructuring

* Fix: dialog position?

* Fix: unneeded warning

* Fix: getChromeVersion

* Fix: don't cache tab icon in old chrome

* Fix: static -> relative

* Fix: use XHR as fallback
2018-11-29 19:35:21 -06:00
Rob Garrison
5778ac0d23
Subscribe to all UI updates. Fixes #590 & #591 (#593)
* Subscribe to all UI updates. Fixes #590 & #591

* Remove unnecessary subscription to sort changes
2018-11-29 19:34:06 -06:00
Rob Garrison
6aaff7aa12
Fix usercss install error. Closes #589 (#592)
* Fix usercss install error. Closes #589

* Cleanup

* Disable input & hide icon
2018-11-29 19:33:54 -06:00
Rob Garrison
a4c08084bf
Move dropbox vendors (#576)
* Move vendor files

* Move sync/vendor & config updates

* Update dropbox sdk from v4.0.9 to v4.0.13

* Fix typo

* Fix zip path

* Pass fetch option to Dropbox
2018-11-28 18:56:17 -06:00
Rob Garrison
eafa5110c0 Fix update log error message 2018-11-28 18:07:05 -06:00
eight
43a4671c64 Change: defer page script injection. Try to inject to head (#584) 2018-11-27 22:57:44 -06:00
Rob Garrison
a1b17bb553
Fix dirty style updating. Closes #585 (#586)
* Fix dirty style updating. Closes #585

* Move common code to edit.js

* init updateDirty
2018-11-27 22:54:36 -06:00
narcolepticinsomniac
eb0b9f58f5 Fix search highlight conflict (#587)
* Fix search highlight conflict 

Regular highlight styling and search highlight styling shouldn't both be applied at the same time. Search highlight styling should also be removed when search is closed. This PR resolves those conflicts.

* Remove unnecessary dummy animation

Not sure what the point of it ever was, but I'm pretty sure it should go.
2018-11-27 22:48:45 -06:00
narcolepticinsomniac
237d5c0c06 Fix inline search (#588)
For some unknown reason, USO now requires a new param.
2018-11-27 22:39:35 -06:00
eight
e97a3ef269 Change: modify match-highlighter plugin (#578)
* Change: modify match-highlighter plugin

* Fix: boundary character should only be used when the query starts/ends with alphabet
2018-11-25 07:28:37 -06:00
eight
4120907957 Fix: failed to find the old style when the name/namespace is changed (#581) 2018-11-25 07:27:10 -06:00
tophf
319ec320c7 specify the end token for simple block in _expr() (#580) 2018-11-24 23:13:33 -06:00
Rob Garrison
764fe399f3
Fix inaccessible file message. Closes #574 (#575)
* Fix inaccessible file message. Closes #574

* Reword inaccessible message
2018-11-24 06:03:01 -06:00
eight
05ec2fb1c7 Fix: detect style CSP (#573) 2018-11-21 11:09:48 -06:00
eight
25fb5acabe Fix: cycle through editors (#572)
* Fix: cycle through editors

* Fix: command is broken
2018-11-21 09:47:28 -06:00
Rob Garrison
9250d5c624 Remove unnecessary API method 2018-11-20 19:49:20 -06:00
Rob Garrison
21b6429205 Beautify: stop new line injection before comment. Fixes #564 2018-11-20 19:41:31 -06:00
Rob Garrison
81d27288f2 Remove duplicate rules in issue modal 2018-11-20 18:57:25 -06:00
Rob Garrison
17339e933b
Show Dropbox unavailable in dev mode (#568) 2018-11-20 17:32:03 -06:00
Rob Garrison
33df061b53 Allow /*! in UserCSS metadata. Closes #571 2018-11-20 17:07:42 -06:00
Rob Garrison
99951efc80
Show error in tooltip (#566)
* Show error in tooltip

* Move error.message check
2018-11-19 22:55:38 -06:00
Rob Garrison
df2b08a75a Remove soft-hyphens in popup. Closes #562 2018-11-19 09:07:14 -06:00
Rob Garrison
62b201c6f6 Fix syntax of gecko min version 2018-11-18 22:42:02 -06:00
Rob Garrison
28dc768ce5 1.5.0 2018-11-18 22:18:50 -06:00
Rob Garrison
a6691031d5 Update dependencies 2018-11-18 22:16:00 -06:00
Rob Garrison
7ea0200234
Validate UserCSS meta colors. Fixes #554 (#559)
* Validate UserCSS meta colors. Fixes #554

* Add suggestions & optimize code

* Fix parsePercentage return value

* cleanup

* Fix: remove unused variable

* Fix: validate function should return a boolean

* Revert indent

* Fix: cleaner validateRGB

* Fix: validateHSL

* Fix: validateAlpha

* Cleanup

* Fix: remove invalid comment

* Limit color channels to integers
2018-11-18 22:05:38 -06:00
Rob Garrison
7261a074cd
Fix USO update button (#555)
* Fix USO update button

* Fix background & search-results

* Prevent JSON parse error

* Get & set currentMd5 outside of condition

* fetchMd5 with API without options

* Only update originalMd5 for valid styles

* Always set currentMd5 & add a delay before firing USO event
2018-11-18 07:30:47 -06:00
Rob Garrison
b41a39d36e Add counter(s) to CSSLint parserlib. Closes #553 2018-11-17 23:23:55 -06:00
Rob Garrison
1ff89acda3 Fix popup inline style install/uninstall 2018-11-15 07:46:52 -06:00
Rob Garrison
59c02dfbed Adjust style on backup button & dropdown 2018-11-11 21:02:13 -06:00
Rob Garrison
dab3515195 Remove text-decoration-skip:ink; Closes #229 2018-11-11 15:42:36 -06:00
eight
b2657e3ebd Change: simplify msg.js (#544)
* Fix: make API work in private windows

* Change: simplify msg.js
2018-11-11 07:07:30 -06:00
eight
b622ebc172 Add: store the reason why db failed (#550)
* Add: store the reason why db failed

* Add: add a warning

* fixup! Add: add a warning
2018-11-11 07:05:09 -06:00
eight
deeba1b900
Change: drop less, switch to less-bundle (#542)
* Change: drop less, switch to less-bundle

* fixup! Change: drop less, switch to less-bundle
2018-11-11 14:06:25 +08:00
eight
5646b8a0d9
Add: improve import performance (#547)
* Add: implement db putMany

* Fix: putMany returns a list of ids
2018-11-11 14:04:22 +08:00
eight
8291da0bb5 Chore: include stylus version in the issue template (#549) 2018-11-08 07:35:40 -06:00
eight
a1a85efc75
Update usercss-meta (#540)
* Update usercss-meta

* Add: translation for new errors

* Fix: define `anno.rule` in meta linter

* Fix: typo

* Fix: don't display rule id if undefined

* fixup! Fix: don't display rule id if undefined
2018-11-07 23:33:56 +08:00
eight
4db8a9ea9a Fix: output codemirror themes to a separated file (#538) 2018-11-07 05:33:14 -06:00
eight
e3d3604afc
Refactor the entire storage system and the section editor (#518)
* Squashed commit of the following:

commit d84c4dc3fe
Author: eight <eight04@gmail.com>
Date:   Sun Oct 14 19:13:29 2018 +0800

    Fix: remove unused comment

commit 46027120ec
Author: eight <eight04@gmail.com>
Date:   Sun Oct 14 19:09:06 2018 +0800

    Add: handle styleUpdated message

commit f85d4de39b
Author: eight <eight04@gmail.com>
Date:   Sun Oct 14 18:59:29 2018 +0800

    Fix: handle styleAdded message in popup

commit 81f3e69574
Author: eight <eight04@gmail.com>
Date:   Sun Oct 14 18:50:54 2018 +0800

    Change: getStylesInfoByUrl -> getStylesByUrl

commit f9dc04558f
Author: eight <eight04@gmail.com>
Date:   Sun Oct 14 18:48:20 2018 +0800

    Fix: drop getStylesInfo

commit fea04d591f
Author: eight <eight04@gmail.com>
Date:   Sun Oct 14 18:39:28 2018 +0800

    Fix: remove unused ignoreChromeError

commit 2aff14e213
Author: eight <eight04@gmail.com>
Date:   Sun Oct 14 18:09:53 2018 +0800

    Fix: don't dup promisify in prefs

commit d4ddfcc713
Author: eight <eight04@gmail.com>
Date:   Sun Oct 14 17:56:16 2018 +0800

    Change: drop .last and .rotate

commit 85e70491e4
Author: eight <eight04@gmail.com>
Date:   Sun Oct 14 17:36:00 2018 +0800

    Fix: unused renderIndex

commit 7acb131642
Author: eight <eight04@gmail.com>
Date:   Sun Oct 14 17:32:49 2018 +0800

    Fix: update title on input

commit a39405ac4c
Author: eight <eight04@gmail.com>
Date:   Sun Oct 14 17:17:20 2018 +0800

    Fix: remove unused messages

commit 14c2fdbb58
Author: eight <eight04@gmail.com>
Date:   Sun Oct 14 16:36:12 2018 +0800

    Fix: dirty state for new added applies

commit fb1b49b8bb
Author: eight <eight04@gmail.com>
Date:   Sun Oct 14 16:27:17 2018 +0800

    Fix: minor

commit 2c2d849fa4
Author: eight <eight04@gmail.com>
Date:   Sun Oct 14 16:20:14 2018 +0800

    Fix: drop unused getCode

commit f133c3e67a
Author: eight <eight04@gmail.com>
Date:   Sun Oct 14 16:18:14 2018 +0800

    Fix: drop unused lastActive

commit 05a6208f5c
Author: eight <eight04@gmail.com>
Date:   Sun Oct 14 16:17:45 2018 +0800

    Fix: minor

commit 05a87ed00f
Author: eight <eight04@gmail.com>
Date:   Sun Oct 14 15:58:33 2018 +0800

    Fix: minor

commit 576f73f333
Author: eight <eight04@gmail.com>
Date:   Sun Oct 14 03:03:35 2018 +0800

    Fix: always register listeners

commit e93819deb4
Author: eight <eight04@gmail.com>
Date:   Sun Oct 14 02:58:49 2018 +0800

    Fix: unused statement

commit 39b11685b4
Author: eight <eight04@gmail.com>
Date:   Sun Oct 14 02:54:29 2018 +0800

    Fix: minor

commit 9dd3cd43c1
Author: eight <eight04@gmail.com>
Date:   Sun Oct 14 02:49:22 2018 +0800

    Fix: don't reorder options

commit 90aadfd728
Author: eight <eight04@gmail.com>
Date:   Sun Oct 14 02:43:52 2018 +0800

    Fix: drop __ERROR__

commit 838c21e3b3
Author: eight <eight04@gmail.com>
Date:   Sun Oct 14 02:36:20 2018 +0800

    Fix: use findStyle API

commit 93a4cdf595
Author: eight <eight04@gmail.com>
Date:   Sun Oct 14 02:34:05 2018 +0800

    Add: findStyle API

commit 8e75871b9b
Author: eight <eight04@gmail.com>
Date:   Sun Oct 14 02:19:01 2018 +0800

    Breaking: drop getStylesFallback

commit ad06551440
Author: eight <eight04@gmail.com>
Date:   Sun Oct 14 02:16:48 2018 +0800

    Fix: use dataurl to inject page script

commit cb5cbb4d10
Author: eight <eight04@gmail.com>
Date:   Sun Oct 14 01:39:50 2018 +0800

    Fix: various

commit 53efd78b89
Author: eight <eight04@gmail.com>
Date:   Sun Oct 14 01:12:57 2018 +0800

    Update doc

commit 7d005f3eaa
Author: eight <eight04@gmail.com>
Date:   Sun Oct 14 01:09:22 2018 +0800

    Change: kill style.reason

commit fc53bed3de
Author: eight <eight04@gmail.com>
Date:   Sun Oct 14 00:56:04 2018 +0800

    Fix: doo many indents

commit 14e321d258
Author: eight <eight04@gmail.com>
Date:   Sun Oct 14 00:40:23 2018 +0800

    Fix: don't update icon for popup and options

commit 01bdd529bc
Author: eight <eight04@gmail.com>
Date:   Sun Oct 14 00:39:17 2018 +0800

    Fix: updateCount

commit b9968830d3
Author: eight <eight04@gmail.com>
Date:   Sun Oct 14 00:38:49 2018 +0800

    Fix: don't send null value

commit ff3bf6f52d
Author: eight <eight04@gmail.com>
Date:   Sun Oct 14 00:03:34 2018 +0800

    Add: styleViaAPI updateCount

commit 39d21c3d29
Author: eight <eight04@gmail.com>
Date:   Sat Oct 13 23:57:45 2018 +0800

    Fix: broadcastError -> ignoreError

commit ecb622c93c
Author: eight <eight04@gmail.com>
Date:   Sat Oct 13 21:29:06 2018 +0800

    Fix: implement styleViaAPI

commit 7c3d49c005
Author: eight <eight04@gmail.com>
Date:   Sat Oct 13 17:50:28 2018 +0800

    Fix: ROOT may change in XML pages

commit 3fd8d937f3
Author: eight <eight04@gmail.com>
Date:   Sat Oct 13 16:49:43 2018 +0800

    Fix: various

commit 859afc8ee9
Author: eight <eight04@gmail.com>
Date:   Sat Oct 13 16:39:54 2018 +0800

    Enhance: don't cache enabled state

commit fbe77a8d15
Author: eight <eight04@gmail.com>
Date:   Sat Oct 13 16:15:07 2018 +0800

    Fix: various

commit a4fc3e9162
Author: eight <eight04@gmail.com>
Date:   Sat Oct 13 16:11:38 2018 +0800

    Fix: various

commit 7e0eddeb8f
Author: eight <eight04@gmail.com>
Date:   Sat Oct 13 15:58:31 2018 +0800

    Fix: various

commit 8b4ab47d89
Author: eight <eight04@gmail.com>
Date:   Sat Oct 13 15:20:10 2018 +0800

    Add: some type hint

commit 7d340d62dc
Author: eight <eight04@gmail.com>
Date:   Sat Oct 13 15:13:11 2018 +0800

    Change: drop storage.js, some functions are moved to sections-util

commit d286997d6a
Author: eight <eight04@gmail.com>
Date:   Sat Oct 13 15:12:00 2018 +0800

    Fix: minor

commit d60db9dbef
Author: eight <eight04@gmail.com>
Date:   Sat Oct 13 15:03:10 2018 +0800

    Fix: minor

commit 43afa31fa0
Author: eight <eight04@gmail.com>
Date:   Sat Oct 13 14:50:31 2018 +0800

    Fix: update tab icon on forward/backward

commit f08faea149
Author: eight <eight04@gmail.com>
Date:   Sat Oct 13 13:50:03 2018 +0800

    Fix: parallel import

commit 4d06435486
Author: eight <eight04@gmail.com>
Date:   Fri Oct 12 23:32:03 2018 +0800

    Add: importStyle API

commit c55675912e
Author: eight <eight04@gmail.com>
Date:   Fri Oct 12 23:14:46 2018 +0800

    Fix: refactor import-export

commit 86ea846a89
Author: eight <eight04@gmail.com>
Date:   Fri Oct 12 17:34:36 2018 +0800

    Fix: search db is broken

commit 831ca07c2d
Author: eight <eight04@gmail.com>
Date:   Fri Oct 12 17:29:35 2018 +0800

    fixup! Add: implement sloppy regexp indicator

commit e67b7f4f36
Author: eight <eight04@gmail.com>
Date:   Fri Oct 12 17:27:19 2018 +0800

    Add: implement sloppy regexp indicator

commit 36e13f88f0
Author: eight <eight04@gmail.com>
Date:   Fri Oct 12 16:59:23 2018 +0800

    Add: return excluded/sloppy state in getStylesInfoByUrl

commit f6ce78f55b
Author: eight <eight04@gmail.com>
Date:   Fri Oct 12 16:39:47 2018 +0800

    Fix: dead object

commit 5ae95a1ad9
Author: eight <eight04@gmail.com>
Date:   Fri Oct 12 16:27:54 2018 +0800

    Fix: don't reinit all editors on save

commit 1a5a206fe6
Author: eight <eight04@gmail.com>
Date:   Fri Oct 12 16:18:40 2018 +0800

    Refactor: pull out sections editor section

commit 8016346035
Author: eight <eight04@gmail.com>
Date:   Fri Oct 12 15:30:35 2018 +0800

    Fix: replaceStyle make style name undefined

commit fa080d1913
Author: eight <eight04@gmail.com>
Date:   Fri Oct 12 15:21:36 2018 +0800

    Fix: catch csp error

commit e0b064115d
Author: eight <eight04@gmail.com>
Date:   Fri Oct 12 15:03:00 2018 +0800

    Fix: use a simple eval to execute page scripts

commit 405b7f8f06
Author: eight <eight04@gmail.com>
Date:   Fri Oct 12 03:48:13 2018 +0800

    Fix: removed unused API

commit 1b2c88f926
Author: eight <eight04@gmail.com>
Date:   Fri Oct 12 03:46:51 2018 +0800

    Fix: no need to access db

commit a8131fc9c5
Author: eight <eight04@gmail.com>
Date:   Fri Oct 12 03:43:31 2018 +0800

    Fix: remove unused methods

commit 3ae0c4dd13
Author: eight <eight04@gmail.com>
Date:   Fri Oct 12 03:10:26 2018 +0800

    Enhance: allow matcher to return verbose info

commit 0ea7ada48f
Author: eight <eight04@gmail.com>
Date:   Fri Oct 12 02:02:14 2018 +0800

    Fix: content script may load before the background is ready

commit 04c2d6bbf6
Author: eight <eight04@gmail.com>
Date:   Fri Oct 12 01:49:52 2018 +0800

    Fix: throw receiving end doesn't exist message

commit f0c0bc4d6a
Author: eight <eight04@gmail.com>
Date:   Fri Oct 12 01:11:17 2018 +0800

    Fix: unwrap error

commit 4d42765d6c
Author: eight <eight04@gmail.com>
Date:   Thu Oct 11 23:55:16 2018 +0800

    fixup! Fix: match subdomain

commit 99626e4a48
Author: eight <eight04@gmail.com>
Date:   Thu Oct 11 23:54:58 2018 +0800

    Fix: match subdomain

commit a57b3b2716
Author: eight <eight04@gmail.com>
Date:   Thu Oct 11 23:39:11 2018 +0800

    Fix: firefox

commit 5cfea3933f
Author: eight <eight04@gmail.com>
Date:   Thu Oct 11 22:46:34 2018 +0800

    Add some comment to db.js

commit 25fd3a1c2b
Author: eight <eight04@gmail.com>
Date:   Thu Oct 11 22:14:56 2018 +0800

    Fix: remove unused prop

commit bdae1c3697
Author: eight <eight04@gmail.com>
Date:   Thu Oct 11 20:00:25 2018 +0800

    Change: simpler styleCodeEmpty

commit bd4a453f45
Merge: c1bf9f5 9058c06
Author: eight <eight04@gmail.com>
Date:   Thu Oct 11 19:49:37 2018 +0800

    Merge branch 'dev-usercss-meta' into dev-exclusions

commit c1bf9f57e9
Author: eight <eight04@gmail.com>
Date:   Thu Oct 11 19:29:17 2018 +0800

    Fix: minor

commit fd5eeb4b81
Author: eight <eight04@gmail.com>
Date:   Thu Oct 11 19:00:05 2018 +0800

    Add: refresh on view

commit 3e38810a49
Author: eight <eight04@gmail.com>
Date:   Thu Oct 11 18:13:24 2018 +0800

    Fix: make sure icons are refreshed at startup

commit c657d7e55c
Author: eight <eight04@gmail.com>
Date:   Thu Oct 11 17:32:27 2018 +0800

    Add: implement bug 461

commit 7ed39ab6ef
Author: eight <eight04@gmail.com>
Date:   Thu Oct 11 15:42:44 2018 +0800

    fixup! Add: icon-util

commit 30e494eda9
Author: eight <eight04@gmail.com>
Date:   Thu Oct 11 15:42:23 2018 +0800

    Add: icon-util

commit 510a886e14
Author: eight <eight04@gmail.com>
Date:   Thu Oct 11 03:21:38 2018 +0800

    Fix: exposeIframes

commit c7f81662c4
Author: eight <eight04@gmail.com>
Date:   Thu Oct 11 02:19:14 2018 +0800

    Fix: autoCloseBrackets is true by default

commit f3a103645d
Author: eight <eight04@gmail.com>
Date:   Thu Oct 11 02:11:14 2018 +0800

    Fix: various

commit d4436cde20
Author: eight <eight04@gmail.com>
Date:   Thu Oct 11 01:39:10 2018 +0800

    Add: implement exposeIframe

commit 43db875fd8
Author: eight <eight04@gmail.com>
Date:   Thu Oct 11 01:26:24 2018 +0800

    Kill more globals

commit dc491e9be3
Author: eight <eight04@gmail.com>
Date:   Thu Oct 11 01:22:13 2018 +0800

    Kill old storage, storage-dummy

commit ba64b95575
Author: eight <eight04@gmail.com>
Date:   Thu Oct 11 00:54:38 2018 +0800

    WIP: kill cachedStyles

commit 7eba890a21
Merge: d2b36a1 81e4823
Author: eight <eight04@gmail.com>
Date:   Wed Oct 10 23:15:14 2018 +0800

    Merge branch 'dev-private-prefs' into dev-exclusions

commit d2b36a168e
Author: eight <eight04@gmail.com>
Date:   Wed Oct 10 23:05:20 2018 +0800

    Kill hidden globals

commit 22d4767511
Author: eight <eight04@gmail.com>
Date:   Wed Oct 10 19:23:34 2018 +0800

    Fix: margin for deleted sections

commit 00687983f0
Author: eight <eight04@gmail.com>
Date:   Wed Oct 10 18:21:07 2018 +0800

    Fix: default value

commit ff6fd8cad3
Author: eight <eight04@gmail.com>
Date:   Wed Oct 10 18:02:51 2018 +0800

    Fix: default options

commit c23f315c52
Author: eight <eight04@gmail.com>
Date:   Wed Oct 10 17:40:07 2018 +0800

    Refactor: use CodeMirror.defineOption

commit 4419c5dc1e
Author: eight <eight04@gmail.com>
Date:   Wed Oct 10 16:32:39 2018 +0800

    Change: kill editors, styleId

commit 6494985b50
Author: eight <eight04@gmail.com>
Date:   Wed Oct 10 16:14:51 2018 +0800

    Fix: various

commit 37e1f43f75
Author: eight <eight04@gmail.com>
Date:   Wed Oct 10 15:04:03 2018 +0800

    Fix: minor

commit d26ce3238e
Author: eight <eight04@gmail.com>
Date:   Wed Oct 10 14:49:37 2018 +0800

    Add: codemirror-factory

commit 15a1f552f6
Author: eight <eight04@gmail.com>
Date:   Wed Oct 10 12:08:35 2018 +0800

    WIP: kill getSection

commit ba6159e067
Author: eight <eight04@gmail.com>
Date:   Wed Oct 10 02:43:09 2018 +0800

    WIP: edit page

commit fd9ab5d6e5
Author: eight <eight04@gmail.com>
Date:   Wed Oct 10 00:41:07 2018 +0800

    Fix: switch to editor

commit 06e22d0d18
Author: eight <eight04@gmail.com>
Date:   Tue Oct 9 23:38:29 2018 +0800

    Change: add sections-editor

commit 30e8662946
Author: eight <eight04@gmail.com>
Date:   Mon Oct 8 20:12:39 2018 +0800

    Add: preview error

commit 47b2b4fc49
Author: eight <eight04@gmail.com>
Date:   Mon Oct 8 18:38:01 2018 +0800

    Add: livePreview.show

commit 7b5e7c96d5
Author: eight <eight04@gmail.com>
Date:   Mon Oct 8 18:16:45 2018 +0800

    Hook up live preview

commit 15efafff3c
Author: eight <eight04@gmail.com>
Date:   Mon Oct 8 17:49:57 2018 +0800

    Add: live preview

commit a38558ef78
Author: eight <eight04@gmail.com>
Date:   Mon Oct 8 15:30:39 2018 +0800

    WIP: make notifyAllTabs a noop

commit 582e9078af
Author: eight <eight04@gmail.com>
Date:   Mon Oct 8 14:39:08 2018 +0800

    Fix: inject all scripts

commit f4651da8d8
Author: eight <eight04@gmail.com>
Date:   Sun Oct 7 23:41:46 2018 +0800

    Drop deleteStyle

commit 0489fb3b2f
Author: eight <eight04@gmail.com>
Date:   Sun Oct 7 23:33:51 2018 +0800

    Drop saveStyle

commit 02f471f077
Author: eight <eight04@gmail.com>
Date:   Sun Oct 7 23:28:41 2018 +0800

    Fix: usercss API

commit 057111b171
Author: eight <eight04@gmail.com>
Date:   Sun Oct 7 22:59:31 2018 +0800

    Update usercss API

commit 69cae02381
Author: eight <eight04@gmail.com>
Date:   Sun Oct 7 21:40:29 2018 +0800

    Drop getStyles

commit c5d41529d9
Author: eight <eight04@gmail.com>
Date:   Sun Oct 7 21:28:51 2018 +0800

    Minor fixes

commit 5b3b4e680f
Author: eight <eight04@gmail.com>
Date:   Sun Oct 7 21:20:39 2018 +0800

    Add: navigator-util

commit b5107b78a5
Author: eight <eight04@gmail.com>
Date:   Sun Oct 7 01:42:43 2018 +0800

    Add: broadcast messages with reasons

commit e7ef4948cd
Author: eight <eight04@gmail.com>
Date:   Sat Oct 6 18:10:47 2018 +0800

    Fix: observer is unavailable?

commit 1c635b5bc1
Author: eight <eight04@gmail.com>
Date:   Sat Oct 6 17:47:43 2018 +0800

    Drop requestStyles

commit 75f2561154
Author: eight <eight04@gmail.com>
Date:   Sat Oct 6 16:38:04 2018 +0800

    Fix: don't recreate element when style update in popup

commit 583ca31d97
Author: eight <eight04@gmail.com>
Date:   Sat Oct 6 15:40:07 2018 +0800

    fixup! Add: isCodeEmpty

commit 1cf6008514
Author: eight <eight04@gmail.com>
Date:   Sat Oct 6 15:33:18 2018 +0800

    Add: isCodeEmpty

commit 450cd60aeb
Author: eight <eight04@gmail.com>
Date:   Sat Oct 6 15:22:04 2018 +0800

    Fix: ignore comment block

commit 196b6aac63
Author: eight <eight04@gmail.com>
Date:   Sat Oct 6 15:16:00 2018 +0800

    Fix: the return value of getSectionsByUrl is changed

commit 3122d28c1a
Author: eight <eight04@gmail.com>
Date:   Sat Oct 6 15:14:05 2018 +0800

    Fix: always use promise in API call

commit e594b8ccb1
Author: eight <eight04@gmail.com>
Date:   Sat Oct 6 15:11:01 2018 +0800

    Cache enabled state

commit 1f18b13a92
Author: eight <eight04@gmail.com>
Date:   Sat Oct 6 13:48:46 2018 +0800

    Add: match global sections

commit fedf844ddd
Author: eight <eight04@gmail.com>
Date:   Sat Oct 6 13:45:37 2018 +0800

    Add: getStylesInfoByUrl

commit 095998f07c
Author: eight <eight04@gmail.com>
Date:   Sat Oct 6 13:27:58 2018 +0800

    Change: switch to msg.js

commit fa3127d988
Author: eight <eight04@gmail.com>
Date:   Sat Oct 6 13:02:45 2018 +0800

    Change: switch to msg.js

commit 05d582c726
Author: eight <eight04@gmail.com>
Date:   Sat Oct 6 11:43:42 2018 +0800

    Add: msg.sendBg

commit 171339f710
Author: eight <eight04@gmail.com>
Date:   Sat Oct 6 04:39:48 2018 +0800

    WIP: drop api.js

commit 3a618aca2a
Author: eight <eight04@gmail.com>
Date:   Sat Oct 6 03:19:51 2018 +0800

    WIP: use deepCopy

commit bb1cb58024
Author: eight <eight04@gmail.com>
Date:   Sat Oct 6 03:10:04 2018 +0800

    WIP: msg.js

commit 2472e91f57
Author: eight <eight04@gmail.com>
Date:   Fri Oct 5 21:28:19 2018 +0800

    WIP: emitChangesToTabs

commit 34497ebe16
Author: eight <eight04@gmail.com>
Date:   Fri Oct 5 18:47:52 2018 +0800

    WIP: switch to API

commit f1639cc33e
Author: eight <eight04@gmail.com>
Date:   Fri Oct 5 01:03:40 2018 +0800

    WIP: broadcastMessage

commit 81e4823f46
Author: eight <eight04@gmail.com>
Date:   Thu Oct 4 19:39:59 2018 +0800

    Debounce updateAllTabsIcon

commit dc5f3e209f
Author: eight <eight04@gmail.com>
Date:   Thu Oct 4 19:34:36 2018 +0800

    Fix: settings could be empty on the first install

commit 2328cf623a
Author: eight <eight04@gmail.com>
Date:   Thu Oct 4 19:34:22 2018 +0800

    Change: start-firefox -> start

commit 7be6a1cba9
Author: eight <eight04@gmail.com>
Date:   Thu Oct 4 19:24:35 2018 +0800

    Add: applications

commit 630725196f
Author: eight <eight04@gmail.com>
Date:   Thu Oct 4 19:22:44 2018 +0800

    fixup! Fix: update all icons when some prefs changed

commit 0d0e1b4dc0
Author: eight <eight04@gmail.com>
Date:   Thu Oct 4 19:20:36 2018 +0800

    Fix: update all icons when some prefs changed

commit 5c0288e9ba
Author: eight <eight04@gmail.com>
Date:   Thu Oct 4 19:20:11 2018 +0800

    fixup! Remove unused FIREFOX_NO_DOM_STORAGE

commit 56b737b65a
Author: eight <eight04@gmail.com>
Date:   Thu Oct 4 18:14:57 2018 +0800

    Remove unused FIREFOX_NO_DOM_STORAGE

commit 829a134ed1
Author: eight <eight04@gmail.com>
Date:   Thu Oct 4 18:10:53 2018 +0800

    Fix: this -> prefs

commit d35f92250e
Author: eight <eight04@gmail.com>
Date:   Thu Oct 4 18:08:19 2018 +0800

    Fixme: styleViaAPI

commit 8a6e8ac03a
Author: eight <eight04@gmail.com>
Date:   Thu Oct 4 18:05:41 2018 +0800

    Change: drop prefChanged, use prefs service

commit 10f9449144
Author: eight <eight04@gmail.com>
Date:   Thu Oct 4 17:46:45 2018 +0800

    Change: move setupLivePrefs to dom.js. Remove prefs.js dependencies

commit dd2b8ed091
Author: eight <eight04@gmail.com>
Date:   Thu Oct 4 17:18:38 2018 +0800

    Fix: type error

commit 3af310c341
Author: eight <eight04@gmail.com>
Date:   Thu Oct 4 17:09:26 2018 +0800

    Fix: open-manager has no default value

commit 874a2da33e
Author: eight <eight04@gmail.com>
Date:   Thu Oct 4 17:04:23 2018 +0800

    Enhance: make prefs use storage.sync

commit c01f93f62c
Author: eight <eight04@gmail.com>
Date:   Thu Oct 4 15:57:02 2018 +0800

    WIP

commit 6d32ffb76b
Author: eight <eight04@gmail.com>
Date:   Thu Oct 4 12:46:19 2018 +0800

    WIP

commit 0f148eac32
Author: eight <eight04@gmail.com>
Date:   Thu Oct 4 03:35:07 2018 +0800

    WIP

commit 282bdf7706
Author: eight <eight04@gmail.com>
Date:   Wed Oct 3 20:24:06 2018 +0800

    Fix: numbers are not compared correctly

commit 24b1eea8a4
Merge: 8a6011d 5cbe8a8
Author: eight <eight04@gmail.com>
Date:   Wed Oct 3 15:00:07 2018 +0800

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

commit 5cbe8a8d78
Author: eight <eight04@gmail.com>
Date:   Tue Oct 2 20:22:18 2018 +0800

    Add: fetch style object from DB directly in the editor (#507)

commit 9058c06c54
Author: eight <eight04@gmail.com>
Date:   Mon Oct 1 23:24:29 2018 +0800

    Fix: bad API

commit 1f2d116aae
Author: eight <eight04@gmail.com>
Date:   Mon Oct 1 23:14:56 2018 +0800

    Fix: use meta parser

commit 918e47b1ed
Author: eight <eight04@gmail.com>
Date:   Mon Oct 1 23:01:21 2018 +0800

    Fix: emit update event if no fatal errors

commit 81a7bb9ac9
Author: eight <eight04@gmail.com>
Date:   Mon Oct 1 22:56:25 2018 +0800

    Add: editorWorker.metalint

commit f47d57aea8
Author: eight <eight04@gmail.com>
Date:   Mon Oct 1 22:49:16 2018 +0800

    Change: use editorWorker.metalint

commit 5778d5c858
Author: eight <eight04@gmail.com>
Date:   Mon Oct 1 22:39:01 2018 +0800

    Change: editor-worker-body -> editor-worker

commit 268e1716b4
Author: eight <eight04@gmail.com>
Date:   Mon Oct 1 22:38:06 2018 +0800

    Change: switch to worker-util

commit cc2980b647
Author: eight <eight04@gmail.com>
Date:   Mon Oct 1 22:30:16 2018 +0800

    Drop: parserlib-loader

commit 08adcb60f2
Merge: 6909c73 2fd531e
Author: eight <eight04@gmail.com>
Date:   Mon Oct 1 22:29:39 2018 +0800

    Merge branch 'master' into dev-usercss-meta

commit e4135ce35d
Author: eight <eight04@gmail.com>
Date:   Fri Sep 28 11:57:34 2018 +0800

    Fix: remove unused function

commit 39a6d1909f
Author: eight <eight04@gmail.com>
Date:   Fri Sep 28 00:26:29 2018 +0800

    Fix: prefs doesn't work in FF's private windows. Add web-ext. Drop prefs.readOnlyValues

commit 6909c73c69
Author: eight <eight04@gmail.com>
Date:   Wed Sep 26 12:16:33 2018 +0800

    Fix: minor

commit 79833d8bba
Author: eight <eight04@gmail.com>
Date:   Wed Sep 26 11:40:04 2018 +0800

    Fix: a better way to draw list?

commit a849fd6dda
Author: eight <eight04@gmail.com>
Date:   Wed Sep 26 11:39:53 2018 +0800

    Fix: missing placeholders

commit d5ee31a080
Author: eight <eight04@gmail.com>
Date:   Wed Sep 26 11:37:50 2018 +0800

    Fix: a better way to draw character list?

commit 7b959af3e3
Author: eight <eight04@gmail.com>
Date:   Wed Sep 26 11:30:10 2018 +0800

    Update usercss-meta

commit fefa987c4d
Author: eight <eight04@gmail.com>
Date:   Wed Sep 26 10:37:28 2018 +0800

    Change: sections-equal -> sections-util

commit 2abbf670d8
Author: eight <eight04@gmail.com>
Date:   Wed Sep 26 10:37:14 2018 +0800

    Fix: check err.code

commit 1fe0586b29
Author: eight <eight04@gmail.com>
Date:   Wed Sep 26 10:33:02 2018 +0800

    Add: i18n error message

commit ab0ef239cf
Author: eight <eight04@gmail.com>
Date:   Wed Sep 26 09:34:57 2018 +0800

    Change: move styleCodeEmpty to sections-util, load colorConverter in background worker

commit d5ade807f0
Author: eight <eight04@gmail.com>
Date:   Wed Sep 26 09:27:30 2018 +0800

    Fix: display error message

commit 4f5337e51d
Author: eight <eight04@gmail.com>
Date:   Wed Sep 26 09:26:55 2018 +0800

    Fix: remove unused colorconverter

commit 29b8f51292
Author: eight <eight04@gmail.com>
Date:   Tue Sep 25 23:21:44 2018 +0800

    Fix: vars could be undefined

commit a7cfeb22e4
Author: eight <eight04@gmail.com>
Date:   Tue Sep 25 22:54:40 2018 +0800

    Fix: window is undefined

commit 9713c6a3be
Author: eight <eight04@gmail.com>
Date:   Tue Sep 25 21:56:38 2018 +0800

    Fix: throw an error for unparsable color

commit 3c30bc3eb0
Author: eight <eight04@gmail.com>
Date:   Tue Sep 25 21:55:55 2018 +0800

    Fix: try to get error message

commit 3d32b0428b
Author: eight <eight04@gmail.com>
Date:   Tue Sep 25 21:38:40 2018 +0800

    Fix: vars might be empty

commit 7d75dd8754
Author: eight <eight04@gmail.com>
Date:   Tue Sep 25 21:18:39 2018 +0800

    Add: meta-parser

commit a4df641b96
Author: eight <eight04@gmail.com>
Date:   Tue Sep 25 21:18:18 2018 +0800

    Enhance: set flag in parserlib so we don't need another loader

commit 8028a3529f
Author: eight <eight04@gmail.com>
Date:   Tue Sep 25 21:17:40 2018 +0800

    Include util, worker-util in background

commit ba5d6cc31a
Author: eight <eight04@gmail.com>
Date:   Tue Sep 25 21:16:59 2018 +0800

    Fix: use spread syntax in loadScript

commit b853be13f8
Author: eight <eight04@gmail.com>
Date:   Tue Sep 25 21:14:46 2018 +0800

    Enhance: swith to usercss-meta (in worker)

commit a3e7915199
Author: eight <eight04@gmail.com>
Date:   Tue Sep 25 21:11:54 2018 +0800

    Fix: use promise API

commit 5d07a8cd4e
Author: eight <eight04@gmail.com>
Date:   Tue Sep 25 21:11:09 2018 +0800

    Fix: buildMeta now returns a promise

commit a004bc3c7d
Author: eight <eight04@gmail.com>
Date:   Tue Sep 25 21:10:35 2018 +0800

    Move styleCodeEmpty to util

commit 41ac66a137
Author: eight <eight04@gmail.com>
Date:   Tue Sep 25 21:09:40 2018 +0800

    Add: background worker

commit ffb13bf1db
Author: eight <eight04@gmail.com>
Date:   Tue Sep 25 21:09:04 2018 +0800

    Enhance: move moz-parser/meta-parser/usercss compiler to worker

commit 42e97ef153
Author: eight <eight04@gmail.com>
Date:   Tue Sep 25 20:45:07 2018 +0800

    Fix: display error on install page

commit 64aa9fcf53
Author: eight <eight04@gmail.com>
Date:   Tue Sep 25 17:34:54 2018 +0800

    Add: background worker

commit b0e407e98f
Author: eight <eight04@gmail.com>
Date:   Tue Sep 25 14:52:35 2018 +0800

    Add: worker util

commit 7a24547e09
Author: eight <eight04@gmail.com>
Date:   Tue Sep 25 00:01:18 2018 +0800

    Add: usercss-meta

commit 8a6011de8c
Author: Rob Garrison <wowmotty@gmail.com>
Date:   Sun Jul 22 09:15:09 2018 -0500

    Attempt to update icon count

commit 4fcb1a88d7
Author: Rob Garrison <wowmotty@gmail.com>
Date:   Sun Jul 15 13:44:29 2018 -0500

    Fix empty exclusion storage error

commit bfe54ab4c4
Author: Rob Garrison <wowmotty@gmail.com>
Date:   Sun Jul 15 12:59:51 2018 -0500

    Add tab communication

commit 983a7bc219
Author: Rob Garrison <wowmotty@gmail.com>
Date:   Sun Jul 15 10:51:11 2018 -0500

    Fix escaped regex example

commit 3950482f34
Author: Rob Garrison <wowmotty@gmail.com>
Date:   Wed Apr 25 18:11:37 2018 -0500

    Fix undefined error

commit e94c7edb38
Author: Rob Garrison <wowmotty@gmail.com>
Date:   Wed Apr 25 17:09:45 2018 -0500

    Attempt to fix popup exclusion issues

commit 2b4a1a5635
Author: Rob Garrison <wowmotty@gmail.com>
Date:   Thu Apr 19 13:00:27 2018 -0500

    Modify input method

commit 9f75b69cd8
Author: Rob Garrison <wowmotty@gmail.com>
Date:   Wed Mar 7 11:54:05 2018 -0600

    Include iframe urls in exclusion popup

commit 68dfa0153c
Author: Rob Garrison <wowmotty@gmail.com>
Date:   Wed Jan 24 19:42:02 2018 -0600

    Add style exclusions. Closes #113

* Revert: exclusions

* Fix: pass eslint

* Fix: the style is injected twice

* Fix: don't load script async

* Fix: styleCodeEmpty returns true for empty string

* Fix: drop array selection

* Fix: the config dialog is broken

* Fix: popup doesn't use getStyle/getStylesByUrl correctly

* Fix: keep disabled state in setStyleContent

* Fix: allow live-preview to assign newest vars

* Fix: transition fix is broken because setStyleContent becomes async

* Fix: typo, TypeError in styleExists

* Fix: use new API

* Fix: pass linter

* Fix: LICENCE -> LICENSE

* Fix: remove unused distroy function
2018-11-07 14:09:29 +08:00
Matheus Faustino
79c6506c5c Implement Dropbox export (#82) (#393)
* Implement Dropbox export (#82)

* Remove wrong dropbox api key

* Improve implementation of Dropbox by using identity.launchWebAuthFlow api and get rid of web_accessible_resources

* We don't need a dropbox receiver anymore, remove constante with the html file

* Implement compression in dropbox export

* Add LICENSE file from dropbox and zipjs

* Fix code style error

* Fix code style and folder structure of the feature

* Fix eslint error in dropbox implementation

* Add real dropbox api key from stylus dropbox account

* For test only: fixed addon's ID on firefox

* Change the file not found message to a better one

* Add dropdown style on export and import buttons

* Changes arrow from buttons to svg

* Remove applications entry on manifest.json

* Remove unnecessary break line
2018-11-03 13:33:35 -05:00
Rob Garrison
5c6cf72a4c 1.4.23 2018-11-03 12:58:39 -05:00
Rob Garrison
14144c287c Update dev dependencies & CodeMirror 2018-11-03 12:12:02 -05:00
Rob Garrison
5536f7ad22
Locales (#532)
* Add gitattributes

* Add update locales script

* Update locales

* Remove old script files

* Switch to use transifex client app
2018-11-03 09:37:25 -05:00
Rob Garrison
30cf5b2458
Fix incorrect USO md5Url (#523) (#527)
* Fix incorrect USO md5Url (#523)

* Fix md5Url in getAll

* Remove variable reassignment
2018-10-26 08:48:58 -05:00
eight
100e1dc28d Fix: TypeError in style-via-api (#521) 2018-10-18 19:19:36 -05:00
342 changed files with 70526 additions and 41634 deletions

View File

@ -1,4 +1,2 @@
vendor/
vendor-overwrites/*
!vendor-overwrites/colorpicker
!vendor-overwrites/csslint
vendor-overwrites/

View File

@ -1,7 +1,7 @@
# https://github.com/eslint/eslint/blob/master/docs/rules/README.md
parserOptions:
ecmaVersion: 2015
ecmaVersion: 2017
env:
browser: true
@ -9,62 +9,7 @@ env:
webextensions: true
globals:
# messaging.js
KEEP_CHANNEL_OPEN: false
CHROME: false
FIREFOX: false
VIVALDI: false
OPERA: false
URLS: false
BG: false
API: false
notifyAllTabs: false
sendMessage: false
queryTabs: false
getTab: false
getOwnTab: false
getActiveTab: false
getActiveTabRealURL: false
getTabRealURL: false
openURL: false
activateTab: false
stringAsRegExp: false
ignoreChromeError: false
tryCatch: false
tryRegExp: false
tryJSONparse: false
debounce: false
deepCopy: false
sessionStorageHash: false
download: false
invokeOrPostpone: false
# localization.js
template: false
t: false
o: false
tE: false
tHTML: false
tNodeList: false
tDocLoader: false
tWordBreak: false
formatDate: false
# dom.js
onDOMready: false
onDOMscriptReady: false
scrollElementIntoView: false
enforceInputRange: false
animateElement: false
$: false
$$: false
$create: false
$createLink: false
# prefs.js
prefs: false
setupLivePrefs: false
# storage-util.js
chromeLocal: false
chromeSync: false
LZString: false
require: readonly # in polyfill.js
rules:
accessor-pairs: [2]
@ -77,7 +22,7 @@ rules:
brace-style: [2, 1tbs, {allowSingleLine: false}]
camelcase: [2, {properties: never}]
class-methods-use-this: [2]
comma-dangle: [0]
comma-dangle: [2, {arrays: always-multiline, objects: always-multiline}]
comma-spacing: [2, {before: false, after: true}]
comma-style: [2, last]
complexity: [0]
@ -89,7 +34,7 @@ rules:
dot-location: [2, property]
dot-notation: [0]
eol-last: [2]
eqeqeq: [1, always]
eqeqeq: [1, smart]
func-call-spacing: [2, never]
func-name-matching: [0]
func-names: [0]
@ -100,7 +45,15 @@ rules:
id-blacklist: [0]
id-length: [0]
id-match: [0]
indent-legacy: [2, 2, {VariableDeclarator: 0, SwitchCase: 1}]
indent: [2, 2, {
SwitchCase: 1,
ignoreComments: true,
ignoredNodes: [
"TemplateLiteral > *",
"ConditionalExpression",
"ForStatement"
]
}]
jsx-quotes: [0]
key-spacing: [0]
keyword-spacing: [2]
@ -142,9 +95,9 @@ rules:
no-empty-function: [0]
no-empty-pattern: [2]
no-empty: [2, {allowEmptyCatch: true}]
no-eq-null: [2]
no-eq-null: [0]
no-eval: [2]
no-ex-assign: [2]
no-ex-assign: [0]
no-extend-native: [2]
no-extra-bind: [2]
no-extra-boolean-cast: [2]
@ -194,6 +147,9 @@ rules:
no-proto: [2]
no-redeclare: [2]
no-regex-spaces: [2]
no-restricted-globals: [2, name, event]
# `name` and `event` (in Chrome) are built-in globals
# but we don't use these globals so it's most likely a mistake/typo
no-restricted-imports: [0]
no-restricted-modules: [2, domain, freelist, smalloc, sys]
no-restricted-syntax: [2, WithStatement]
@ -214,7 +170,6 @@ rules:
no-trailing-spaces: [2]
no-undef-init: [2]
no-undef: [2]
no-undefined: [0]
no-underscore-dangle: [0]
no-unexpected-multiline: [2]
no-unmodified-loop-condition: [0]
@ -222,9 +177,9 @@ rules:
no-unreachable: [2]
no-unsafe-finally: [2]
no-unsafe-negation: [2]
no-unused-expressions: [1]
no-unused-expressions: [2]
no-unused-labels: [0]
no-unused-vars: [1, {args: after-used, vars: local, argsIgnorePattern: ^_}]
no-unused-vars: [2, {args: after-used}]
no-use-before-define: [2, nofunc]
no-useless-call: [2]
no-useless-computed-key: [2]
@ -248,7 +203,7 @@ rules:
prefer-const: [1, {destructuring: all, ignoreReadBeforeAssign: true}]
quote-props: [0]
quotes: [1, single, avoid-escape]
radix: [2, as-needed]
radix: [2, always]
require-jsdoc: [0]
require-yield: [2]
semi-spacing: [2, {before: false, after: true}]
@ -270,3 +225,16 @@ rules:
wrap-iife: [2, inside]
yield-star-spacing: [2, {before: true, after: false}]
yoda: [2, never]
overrides:
- files: [tools/*]
env:
node: true
browser: false
webextensions: false
parserOptions:
ecmaVersion: 2017
- files: ["**/*worker*.js"]
env:
worker: true

2
.gitattributes vendored Normal file
View File

@ -0,0 +1,2 @@
# Auto detect text files and perform LF normalization
* text=auto

View File

@ -4,7 +4,9 @@
2. [How to report issues](#how-to-report-issues)
3. [Adding translations](#adding-translations)
4. [Pull requests](#pull-requests)
5. [Contact us](#contact-us)
5. [Scripts](#scripts)
6. [Updating locale files](#updating-locale-files-admin-only)
7. [Contact us](#contact-us)
## Getting involved
@ -22,6 +24,9 @@ If not, then provide details describing which page the feature will effect, e.g.
## Adding translations
You can help us translate the extension on [Transifex](https://www.transifex.com/github-7/Stylus).
Only the languages supported by the web store are allowed:
https://developer.chrome.com/docs/webstore/i18n/#localeTable
## Pull requests
@ -32,6 +37,10 @@ You can help us translate the extension on [Transifex](https://www.transifex.com
* Make any changes within a branch of this repository (not the `master` branch).
* Submit a pull request and include a reference to the initial issue with the discussion.
## Build scripts
See [BUILD.md](../BUILD.md) for more information.
## Contact us
If you prefer a more informal method of getting in touch or starting a conversation, please [join us on Discord](https://discordapp.com/widget?id=379521691774353408) or leave a comment in the [discussion section](https://add0n.com/stylus.html#reviews). We will monitor any discussions there and join in, and it may be a more appropriate venue for opinions and less urgent suggestions.

View File

@ -1,8 +0,0 @@
* **Browser**:
* **Operating System**:
* **Screenshot**:
<!--
Please make sure you checked that your issue wasn't already addressed.
If the issue persists, please help us identifying the cause by providing the above details.
-->

48
.github/ISSUE_TEMPLATE/bug_report.md vendored Normal file
View File

@ -0,0 +1,48 @@
---
name: Bug Report
about: Create a report about a bug you experienced while using Stylus.
title: "[Bug] Replace with title"
assignees: ''
---
<!--
⚠⚠ Do not delete this issue template! ⚠⚠
Reported issues must use this template and have all the necessary information provided.
Incomplete reports are likely to be ignored and closed.
-->
<!--
Thank you for taking the time to create a report about a bug.
Ensure that there are no other existing reports for this bug.
Please check if the issue is resolved after a restart of the browser.
Additionally, you should check if the issue persists in a new browser profile.
Remember to fill out every section on this report and remove any that are not needed.
Finally, place a brief description in the title of this report.
-->
# Bug Report
### Bug Description
<!-- Provide a clear and concise description, which will allow us to properly troubleshoot this bug. -->
### Screenshots
<!-- If applicable, add screenshots to help explain this bug. -->
### CSS Code
<!--
If the bug is related to (user)CSS or the editor,
please post the code (with a service like pastebin) in this bug report.
-->
### System Information
<!--
Specify the browser name and version as well as the Stylus version you are using.
Please do an online search for help if you are not familiar with how to get this information.
-->
- OS: <!-- e.g. Windows, macOS, Linux -->
- Browser: <!-- e.g. Chrome 91, Firefox 90, Edge 91, Safari 14 -->
- Stylus Version: <!-- e.g. 1.5.21 -->
### Additional Context
<!-- Provide any additional information about this bug. -->

14
.github/workflows/ci.yml vendored Normal file
View File

@ -0,0 +1,14 @@
name: ci
on: [push, pull_request]
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- uses: actions/setup-node@v2
with:
node-version: '14'
- run: npm install
- run: npm test

12
.gitignore vendored
View File

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

10
.tx/config Normal file
View File

@ -0,0 +1,10 @@
[main]
host = https://www.transifex.com
[Stylus.messages]
file_filter = _locales/<lang>/messages.json
minimum_perc = 0
source_file = _locales/en/messages.json
source_lang = en_US
type = CHROME

70
BUILD.md Normal file
View File

@ -0,0 +1,70 @@
# Build this project
## Preparation
1. Install [Node.js](https://nodejs.org/en/).
2. Go to the project root, run `npm install`. This will install all required dependencies.
Extra preparations are needed if you want to pull locale files from Transifex:
1. Install Transifex client. Follow the instructions on [this page](https://docs.transifex.com/client/installing-the-client).
2. You need a `.transifexrc` file in the root folder. Contact another admin if you need one. It includes the API key to use Transifex's API.
## Generate the ZIP release
Use the following command to generate a ZIP file that can be submitted to AMO or CWS:
```
npm run zip
```
The zip file includes all the files from the repository **except**:
* All dot files (e.g. `.eslintrc` & `.gitignore`).
* `node_modules` folder.
* `tools` folder.
* `package.json` file.
* `package-lock.json` and/or `yarn.lock` file(s).
<!-- FIXME: is this statement still true?
* `vendor/codemirror/lib` files. This path is excluded because it contains a file modified for development purposes only. Instead, the CodeMirror files are copied directly from `node_modules/codemirror/lib`.
-->
## Tag a release/Bump the version
Use the `npm version (major | minor | patch)` command to tag a release.
There are some scripts that will run automatically before/after tagging a version. Includes:
1. Test.
2. Update version number in `manifest.json`.
3. Generate the ZIP file.
4. Push the tag to github.
## Translation
We host locale files (`message.json`) on Transifex. All the files exist in our GitHub repository, but if you need to update the locale files, you will need to install the [Transifex client](https://docs.transifex.com/client/installing-the-client)
To pull files from Transifex, run
```
npm run update-locales
```
To push files to Transifex:
```
npm run update-transifex
```
## 3rd-party libraries
3rd-party libraries are managed by `npm`. Since Stylus is built with vanilla JS, we only use libraries that can run in the browser.
We keep a copy of these libraries inside the `vendor` directory so users can side-load this repository without executing the build script. These files are downloaded from CDN or pulled from npm (`node_modules`).
To add/update a library to the latest version, run `npm install PACKAGE_NAME@latest`.
To remove a library, run `npm uninstall PACKAGE_NAME`.
After the (un)installation, specify files which should be copied in `tools/build-vendor.js` and run `npm run build-vendor` to rebuild the vendor folder.

View File

@ -1,3 +1,5 @@
# Stylus
Stylus is a fork of Stylish for Chrome, also compatible with Firefox as a WebExtension
## Highlights
@ -19,18 +21,15 @@ Stylus is a fork of Stylish for Chrome, also compatible with Firefox as a WebExt
## Screenshots
![Style manager](https://user-images.githubusercontent.com/1310400/34453460-214eaa5c-ed67-11e7-843b-d8960b71db6e.png)
![Style editor](https://user-images.githubusercontent.com/1310400/34459585-3932cd94-ee05-11e7-9a1b-679522dddfb3.png)
![Popup config for usercss](https://user-images.githubusercontent.com/1310400/34453462-218a589a-ed67-11e7-9040-7d0469eeadc3.png)
![Popup inline search](https://user-images.githubusercontent.com/1310400/34453463-21a44368-ed67-11e7-93b2-e1c8f5aac868.png)
![Style manager config for usercss](https://user-images.githubusercontent.com/1310400/34453464-21bdaf9c-ed67-11e7-8517-62d2f02e1918.png)
![Options](https://user-images.githubusercontent.com/1310400/34453461-216aee4c-ed67-11e7-92db-ea21c1da5826.png)
Manager | Editor | Popup search | Popup config | Manager config | Options
-|-|-|-|-|-
![Style manager](https://user-images.githubusercontent.com/1310400/34453460-214eaa5c-ed67-11e7-843b-d8960b71db6e.png) | ![Style editor](https://user-images.githubusercontent.com/1310400/34459585-3932cd94-ee05-11e7-9a1b-679522dddfb3.png) | ![Popup inline search](https://user-images.githubusercontent.com/1310400/34453463-21a44368-ed67-11e7-93b2-e1c8f5aac868.png) | ![Popup config for usercss](https://user-images.githubusercontent.com/1310400/34453462-218a589a-ed67-11e7-9040-7d0469eeadc3.png) | ![Style manager config for usercss](https://user-images.githubusercontent.com/1310400/34453464-21bdaf9c-ed67-11e7-8517-62d2f02e1918.png) | ![Options](https://user-images.githubusercontent.com/1310400/34453461-216aee4c-ed67-11e7-92db-ea21c1da5826.png)
## Help
- [Stylus help and FAQ in our Wiki](https://github.com/openstyles/stylus/wiki)
- [Discussion section](https://add0n.com/stylus.html#reviews) of our representation on add0n.com
- Discord: [![Discord][chat-image]][chat-link]
* [Stylus help and FAQ in our Wiki](https://github.com/openstyles/stylus/wiki)
* [Discussion section](https://add0n.com/stylus.html#reviews) of our representation on add0n.com
* Discord: [![Discord][chat-image]][chat-link]
[chat-image]: https://img.shields.io/discord/379521691774353408.svg
[chat-link]: https://discordapp.com/widget?id=379521691774353408
@ -45,13 +44,16 @@ See our [contributing](./.github/CONTRIBUTING.md) page for more details.
## License
Inherited code from the original [Stylish](https://github.com/stylish-userstyles/stylish/):<br>
Inherited code from the original [Stylish](https://github.com/stylish-userstyles/stylish/):
Copyright &copy; 2005-2014 [Jason Barnabe](jason.barnabe@gmail.com)
Current Stylus:<br>
Copyright &copy; 2017-2018 [Stylus Team](https://github.com/openstyles/stylus/graphs/contributors)
Current Stylus:
Copyright &copy; 2017-2022 [Stylus Team](https://github.com/openstyles/stylus/graphs/contributors)
**[GNU GPLv3](./LICENSE)**
**[GNU GPLv3](./LICENSE)**<br>
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or

View File

@ -1,4 +1,7 @@
{
"InaccessibleFileHint": {
"message": "Stylus لا يستطيع الوصول الى بعض انواع الملفات ( ملفات pdf و json )"
},
"addStyleLabel": {
"message": "كتابة نمط جديد"
},
@ -17,7 +20,7 @@
}
},
"appliesDisplayTruncatedSuffix": {
"message": المزيد"
"message": المزيد"
},
"appliesDomainOption": {
"message": "عناوين URL في النطاق"
@ -55,6 +58,12 @@
"checkingForUpdate": {
"message": "جارٍ البحث..."
},
"confirmDelete": {
"message": "حذف"
},
"confirmSave": {
"message": "حفظ"
},
"deleteStyleConfirm": {
"message": "هل تريد بالتأكيد حذف هذا النمط؟"
},
@ -67,6 +76,9 @@
"disableStyleLabel": {
"message": "تعطيل"
},
"editDeleteText": {
"message": "حذف"
},
"editStyleHeading": {
"message": "تعديل النمط"
},
@ -84,8 +96,11 @@
"enableStyleLabel": {
"message": "تمكين"
},
"findStylesForSite": {
"message": "البحث عن المزيد من الأنماط لموقع الويب هذا"
"genericAdd": {
"message": "إضافة"
},
"genericEnabledLabel": {
"message": "ممكّن"
},
"helpAlt": {
"message": "مساعدة"
@ -102,18 +117,21 @@
"openManage": {
"message": "إدارة الأنماط المثبتة"
},
"optionsSyncUrl": {
"message": "عنوان URL"
},
"sectionAdd": {
"message": "إضافة قسم آخر"
},
"sectionCode": {
"message": "الرمز"
},
"sectionHelp": {
"message": "تتيح لك الأقسام تحديد أجزاء مختلفة من الرمز لتطبيقها على مجموعات مختلفة من عناوين URL بالنمط نفسه. فعلى سبيل المثال، يمكن لنمط مفرد تغيير الصفحة الرئيسية لموقع ويب بطريقة، مع تغيير بقية أجزاء موقع الويب بطريقة أخرى."
},
"sectionRemove": {
"message": "إزالة القسم"
},
"sections": {
"message": "الأقسام"
},
"styleCancelEditLabel": {
"message": "رجوع للإدارة"
},
@ -137,9 +155,6 @@
"styleSaveLabel": {
"message": "حفظ"
},
"styleSectionsTitle": {
"message": "الأقسام"
},
"styleToMozillaFormatHelp": {
"message": "يمكن استخدام تنسيق موزيلا للرمز باستخدام Stylus للمتصفح فايرفوكس ويمكن إرساله إلى userstyles.org."
},

View File

@ -52,9 +52,6 @@
"backupButtons": {
"message": "Резервни копия"
},
"backupMessage": {
"message": "Изберете файл или го влачете до страницата."
},
"bckpInstStyles": {
"message": "Изнасяне на стилове"
},
@ -115,6 +112,9 @@
"confirmOK": {
"message": "Добре"
},
"confirmSave": {
"message": "Запазване"
},
"confirmStop": {
"message": "Спиране"
},
@ -165,21 +165,21 @@
}
}
},
"editorStylesButton": {
"message": "Стилове за редактора"
},
"enableStyleLabel": {
"message": "Включване"
},
"exportLabel": {
"message": "Изнасяне"
},
"findStylesForSite": {
"message": "Още стилове за този сайт"
"genericAdd": {
"message": "Добавяне"
},
"genericDisabledLabel": {
"message": "Изключено"
},
"genericEnabledLabel": {
"message": "Включено"
},
"genericHistoryLabel": {
"message": "Хронология"
},
@ -263,9 +263,6 @@
"manageFaviconsGray": {
"message": "Сиви"
},
"manageFaviconsHelp": {
"message": "Разширението използва външна услуга https://www.google.com/s2/favicons"
},
"manageFilters": {
"message": "Филтри"
},
@ -302,10 +299,7 @@
"openManage": {
"message": "Управление"
},
"openOptionsManage": {
"message": "Прозорец за настройките"
},
"openOptionsPopup": {
"openOptions": {
"message": "Настройки"
},
"openStylesManager": {
@ -374,6 +368,9 @@
"optionsSubheading": {
"message": "Още настройки"
},
"optionsSyncUrl": {
"message": "Адрес"
},
"optionsUpdateImportNote": {
"message": "При внасянето на резервни копия от стари версии или от Стайлиш направете ръчна проверка за обновления, за да сте сигурни, че стиловете са актуални."
},
@ -401,21 +398,18 @@
"searchRegexp": {
"message": "Използвайте синтаксиса /re/ за търсене с регулярни изрази"
},
"searchStyles": {
"message": "Търсене на съдържанието"
},
"sectionAdd": {
"message": "Добавяне на друг отдел"
},
"sectionCode": {
"message": "Код"
},
"sectionHelp": {
"message": "Отделите ви позволяват в един и същи стил да зададете различни части от кода да се прилагат за различни адреси. Например, един стил може да промени началната страница на даден сайт по един начин, а останалата част на сайта по друг начин."
},
"sectionRemove": {
"message": "Премахване на отдела"
},
"sections": {
"message": "Отдели"
},
"shortcuts": {
"message": "Клавишни комбинации"
},
@ -469,9 +463,6 @@
"styleRegexpProblemTooltip": {
"message": "Брой на неприложените отдели поради неправилно използване на регулярни изрази"
},
"styleRegexpTestButton": {
"message": "Тест на регулярния израз"
},
"styleRegexpTestFull": {
"message": "Съвпадащи подпрозорци"
},
@ -490,9 +481,6 @@
"styleSaveLabel": {
"message": "Запазване"
},
"styleSectionsTitle": {
"message": "Отдели"
},
"styleToMozillaFormatHelp": {
"message": "Форматът на Мозила може да се подаде в userstyles.org и да се използва със Стайлиш (Stylish)"
},

View File

@ -176,9 +176,6 @@
"installUpdateFromLabel": {
"message": "Провери за обновления"
},
"installUpdateUnavailable": {
"message": "За да разрешите проверка за обновления, пуснете файла върху лентата с табове, или в метаданните на стила укажете @updateURL."
},
"license": {
"message": "Лиценз"
},
@ -239,15 +236,9 @@
"liveReloadError": {
"message": "Получи се грешка докато наблюдавахме файла"
},
"liveReloadInstallHint": {
"message": "Преглед на живо е разрешен, така че инсталирания стил ще бъде обновен автоматично при външни промени докато двата прозореца с кода и оригинала са отворени."
},
"liveReloadLabel": {
"message": "Преглед на живо"
},
"liveReloadUnavailable": {
"message": "За да разрешите презареждане в реално време, пуснете файла върху лентата с табове (областта, където са показани заглавията на табовете)."
},
"manageFilters": {
"message": "Филтри"
},

View File

@ -67,9 +67,6 @@
"backupButtons": {
"message": "Zálohovat"
},
"backupMessage": {
"message": "Vyberte soubor nebo ho přetáhněte na tuto stránku."
},
"bckpInstStyles": {
"message": "Exportovat styly"
},
@ -181,6 +178,9 @@
"confirmYes": {
"message": "Ano"
},
"connectingDropbox": {
"message": "Připojování k Dropboxu…"
},
"dateInstalled": {
"message": "Datum instalace"
},
@ -231,15 +231,15 @@
}
}
},
"editorStylesButton": {
"message": "Najít styly pro editor"
},
"enableStyleLabel": {
"message": "Povolit"
},
"exportLabel": {
"message": "Exportovat"
},
"exportSavedSuccess": {
"message": "Soubor úspěšně uložen"
},
"externalFeedback": {
"message": "Zpětná vazba"
},
@ -272,15 +272,6 @@
"findStyles": {
"message": "Najít styly"
},
"findStylesForSite": {
"message": "Najít styly pro tento web"
},
"findStylesInline": {
"message": "Zobrazit zde"
},
"findStylesInlineTooltip": {
"message": "Zobrazit výsledky vyhledávání v tomto okně."
},
"genericAdd": {
"message": "Přidat"
},
@ -317,6 +308,9 @@
"genericUnknown": {
"message": "Neznámé"
},
"gettingStyles": {
"message": "Získávání všech stylů…"
},
"helpAlt": {
"message": "Nápověda"
},
@ -457,9 +451,6 @@
"liveReloadError": {
"message": "Při sledování souboru došlo k chybě"
},
"liveReloadInstallHint": {
"message": "Živá aktualizace je povolena, takže nainstalovaný styl bude automaticky aktualizován při externích změnách, dokud budou tento list a list zdrojového souboru otevřeny."
},
"liveReloadLabel": {
"message": "Živá aktualizace"
},
@ -470,7 +461,7 @@
"message": "Zešednutí"
},
"manageFaviconsHelp": {
"message": "Stylus používá externí službu https://www.google.com/s2/favicons"
"message": "Stylus používá externí službu https://icons.duckduckgo.com"
},
"manageFilters": {
"message": "Filtry"
@ -514,16 +505,55 @@
"menuShowBadge": {
"message": "Zobrazit počet aktivních stylů"
},
"meta_invalidCheckboxDefault": {
"message": "Neplatný @var checkbox: hodnota musí být 0 nebo 1"
},
"meta_invalidNumber": {
"message": "Očekáváno číslo"
},
"meta_invalidString": {
"message": "Očekáván řetězec v uvozovkách"
},
"meta_invalidWord": {
"message": "Očekáván text"
},
"meta_missingChar": {
"message": "Očekávané znaky: $chars$",
"placeholders": {
"chars": {
"content": "$1"
}
}
},
"meta_unknownMeta": {
"message": "Neznámá metadata: $key$",
"placeholders": {
"key": {
"content": "$1"
}
}
},
"meta_unknownVarType": {
"message": "Neznámý typ @$varkey$: $vartype$",
"placeholders": {
"varkey": {
"content": "$1"
},
"vartype": {
"content": "$2"
}
}
},
"noFileToImport": {
"message": "Před importem stylů byste je měli nejprve exportovat."
},
"noStylesForSite": {
"message": "Pro tento web není nainstalován žádný styl."
},
"openManage": {
"message": "Spravovat"
},
"openOptionsManage": {
"message": "Možnosti rozhraní"
},
"openOptionsPopup": {
"openOptions": {
"message": "Možnosti"
},
"openStylesManager": {
@ -595,12 +625,18 @@
"optionsSubheading": {
"message": "Další možnosti"
},
"optionsSyncUrl": {
"message": "URL adresa"
},
"optionsUpdateImportNote": {
"message": "Importujete-li zálohy stylů ze starší verze nebo z rozšíření Stylish, proveďte jednorázovou ruční kontrolu aktualizací ve správci stylů, aby byly všechny styly aktuální."
},
"optionsUpdateInterval": {
"message": "Interval automatické aktualizace stylů v hodinách (0 = vypnuto)"
},
"overwriteFileExport": {
"message": "Chcete přepsat stávající soubor?"
},
"paginationCurrent": {
"message": "Aktuální stránka"
},
@ -649,6 +685,9 @@
"previewTooltip": {
"message": "Dočasně použije změny bez uložení.\nUložte styl pro trvalé zachování změn."
},
"readingStyles": {
"message": "Čtení stylů…"
},
"replace": {
"message": "Nahradit"
},
@ -691,24 +730,21 @@
"searchResultWeeklyCount": {
"message": "Týdenní počet instalací"
},
"searchStyles": {
"message": "Prohledat obsah"
},
"sectionAdd": {
"message": "Přidat další sekci"
},
"sectionCode": {
"message": "Kód"
},
"sectionHelp": {
"message": "Pomocí sekcí můžete v jednom stylu nadefinovat více částí kódu pro různé skupiny URL adres. Jeden styl pak může např. změnit hlavní stránku webu jinak, než jeho zbytek."
},
"sectionRemove": {
"message": "Odstranit sekci"
},
"sectionRestore": {
"message": "Obnovit odstraněnou sekci"
},
"sections": {
"message": "Sekce"
},
"shortcuts": {
"message": "Zkratky"
},
@ -742,6 +778,9 @@
"styleBeautifyIndentConditional": {
"message": "Odsadit @media, @supports"
},
"styleBeautifyPreserveNewlines": {
"message": "Zachovat nové řádky"
},
"styleCancelEditLabel": {
"message": "Zpět ke správě"
},
@ -787,36 +826,6 @@
}
}
},
"styleMetaErrorCheckbox": {
"message": "Neplatný @var checkbox: hodnota musí být 0 nebo 1"
},
"styleMetaErrorColor": {
"message": "$color$je neplatná barva",
"placeholders": {
"color": {
"content": "$1"
}
}
},
"styleMetaErrorPreprocessor": {
"message": "Nepodporovaný @preprocessor: $preprocessor$",
"placeholders": {
"preprocessor": {
"content": "$1"
}
}
},
"styleMetaErrorSelectValueMismatch": {
"message": "Neplatný @select: hodnota v seznamu neexistuje"
},
"styleMissingMeta": {
"message": "Chybějící metadata @$key$",
"placeholders": {
"key": {
"content": "$1"
}
}
},
"styleMissingName": {
"message": "Zadejte název"
},
@ -835,9 +844,6 @@
"styleRegexpProblemTooltip": {
"message": "Počet sekcí nepoužitých kvůli nesprávnému použití „regexp()“"
},
"styleRegexpTestButton": {
"message": "Otestovat RegExp"
},
"styleRegexpTestFull": {
"message": "Odpovídající listy"
},
@ -859,9 +865,6 @@
"styleSaveLabel": {
"message": "Uložit"
},
"styleSectionsTitle": {
"message": "Sekce"
},
"styleToMozillaFormatHelp": {
"message": "Kód v Mozilla formátu může být odeslán na userstyles.org a použit v aplikaci Stylish pro Firefox"
},
@ -903,15 +906,18 @@
"unreachableAMOHint": {
"message": "Povolení přístupu: přejděte na <about:config>, pravým myšidlem zvolte „Nový“, dále „Boolean“, zadejte kód <privacy.resistFingerprinting.block_mozAddonManager> a potvrďte, <true>, OK, načtěte stránku <addons.mozilla.org>."
},
"unreachableAMOHintOldFF": {
"message": "Pouze Firefox 59 a novější může být nakonfigurován tak, aby rozšíření typu WebExtensions mohla přidávat styly na stránky chráněné CSP (Content Security Policy) jako je tato."
},
"unreachableContentScript": {
"message": "Se stránkou nelze komunikovat. Zkuste znovu načíst list."
},
"unreachableFileHint": {
"message": "Stylus může přistupovat k file:// URL pouze při povolení odpovídající možnosti pro rozšíření Stylus ve správci chrome://extensions."
},
"unreachableMozSiteHintOldFF": {
"message": "Pouze Firefox 59 a novější může být nakonfigurován tak, aby rozšíření typu WebExtensions mohla přidávat styly na stránky chráněné CSP (Content Security Policy) jako je tato."
},
"unzipStyles": {
"message": "Rozbalování stylů…"
},
"updateAllCheckSucceededNoUpdate": {
"message": "Aktualizace nenalezeny."
},
@ -953,6 +959,12 @@
"updatesCurrentlyInstalled": {
"message": "Instalované aktualizace:"
},
"uploadingFile": {
"message": "Nahrávání souboru…"
},
"usercssAvoidOverwriting": {
"message": "Prosím, změňte hodnotu @name nebo @namespace ať nedojde k přepsání existujícího stylu."
},
"usercssConfigIncomplete": {
"message": "Styl byl aktualizován nebo smazán po zobrazení dialogu konfigurace. Tyto proměnné nebyly uloženy aby se předešlo poškození metadat stylu."
},
@ -962,9 +974,6 @@
"usercssReplaceTemplateConfirmation": {
"message": "Nahradit výchozí šablonu pro nové Usercss styly aktuálním kódem?"
},
"usercssReplaceTemplateName": {
"message": "Prázdné @name nahrazuje výchozí šablonu"
},
"usercssReplaceTemplateSectionBody": {
"message": "Sem vložte kód…"
},
@ -976,5 +985,8 @@
},
"writeStyleForURL": {
"message": "tuto URL"
},
"zipStyles": {
"message": "Balení stylů…"
}
}

100
_locales/da/messages.json Normal file
View File

@ -0,0 +1,100 @@
{
"addStyleLabel": {
"message": "Skriv ny stil"
},
"addStyleTitle": {
"message": "Tilføj stil"
},
"alphaChannel": {
"message": "Gennemsigtighed"
},
"appliesAdd": {
"message": "Tilføj"
},
"appliesDisplay": {
"message": "Anvendes på: $applies$",
"placeholders": {
"applies": {
"content": "$1"
}
}
},
"appliesDisplayTruncatedSuffix": {
"message": "og mere"
},
"appliesDomainOption": {
"message": "URL'er på domænet"
},
"appliesHelp": {
"message": "Brug 'Anvendt på'-styring til at begrænse hvilke URL'er koden i denne sektion anvendes på."
},
"appliesLabel": {
"message": "Anvendes på"
},
"appliesLineWidgetLabel": {
"message": "Vis 'Anvendes på'-info"
},
"appliesRegexpOption": {
"message": "URL'er der matcher regexp'en"
},
"appliesRemove": {
"message": "Fjern"
},
"appliesRemoveError": {
"message": "Kan ikke fjerne sidste 'Anvendes på'-optegnelse"
},
"appliesSpecify": {
"message": "Specificér"
},
"appliesToEverything": {
"message": "Alt"
},
"appliesUrlPrefixOption": {
"message": "URL'er der starter med"
},
"applyAllUpdates": {
"message": "Anvend alle opdateringer"
},
"author": {
"message": "Forfatter"
},
"bckpInstStyles": {
"message": "Eksportér stil"
},
"checkAllUpdates": {
"message": "Tjek alle stiler for opdateringer"
},
"checkAllUpdatesForce": {
"message": "Tjek igen, jeg redigerede ikke nogen stil!"
},
"checkForUpdate": {
"message": "Tjek efter opdatering"
},
"checkingForUpdate": {
"message": "Tjekker..."
},
"clickToUninstall": {
"message": "Klik for at afinstallere"
},
"cm_autoCloseBrackets": {
"message": "Luk automatisk paranteser og citationstegn"
},
"cm_autoCloseBracketsTooltip": {
"message": "Tilføj automatisk et lukket par når man åbner en af ()[]{}''\"\""
},
"cm_autocompleteOnTyping": {
"message": "Autoudfyld på indtastning"
},
"cm_colorpicker": {
"message": "Farvevælgere for CSS-farver"
},
"cm_indentWithTabs": {
"message": "Brug tabs med smart indrykning"
},
"cm_keyMap": {
"message": "Tastegenveje"
},
"genericAdd": {
"message": "Tilføj"
}
}

View File

@ -1,4 +1,7 @@
{
"InaccessibleFileHint": {
"message": "Stylus kann auf einige Dateitypen nicht zugreifen (z.B. pdf oder json)"
},
"addStyleLabel": {
"message": "Neuen Style erstellen"
},
@ -65,7 +68,7 @@
"message": "Datensicherung"
},
"backupMessage": {
"message": "Wähle eine Datei aus oder ziehe die Datei auf diese Seite. (Drag and Drop)"
"message": "Um die Backupdatei zu importieren, ziehe sie in diese Seite oder klicke auf die Import-Schaltfläche.\n\nZum Exportieren einer mit Stylus 1.5.18 (und älter) kompatiblen Backupdatei, rechtsklicke oder Shift-klicke auf die Export-Schaltfläche."
},
"bckpInstStyles": {
"message": "Styles exportieren"
@ -130,6 +133,9 @@
"cm_tabSize": {
"message": "Tab-Größe"
},
"colorpickerPaletteHint": {
"message": "Rechtsklicke auf ein Farbmuster, um durch die entsprechenden Codezeilen zu springen"
},
"colorpickerSwitchFormatTooltip": {
"message": "Formate wechseln: HEX -> RGB ->HSL.\nShift-Klick, um Richtung umzukehren.\nKürzel: Bild auf- und Bild ab-Tasten."
},
@ -175,6 +181,24 @@
"confirmYes": {
"message": "Ja"
},
"connectingDropbox": {
"message": "Verbinde mit Dropbox..."
},
"connectingDropboxNotAllowed": {
"message": "Verbindung zur Dropbox ist nur in Apps verfügbar, die direkt vom Webstore installiert wurden"
},
"copied": {
"message": "In die Zwischenablage kopiert"
},
"copy": {
"message": "In die Zwischenablage kopieren"
},
"customNameHint": {
"message": "Neuen Anzeigenamen des Styles hier eingeben, um trotzdem weiterhin Updates zu erhalten"
},
"customNameResetHint": {
"message": "Eigenen Anzeigenamen nicht mehr benutzen, wechsle wieder zum Standardnamen des Styles"
},
"dateInstalled": {
"message": "Installationsdatum"
},
@ -199,12 +223,29 @@
"disableAllStyles": {
"message": "Alle Styles deaktivieren"
},
"disableAllStylesOff": {
"message": "Styles sind ausgeschaltet"
},
"disableStyleLabel": {
"message": "Deaktivieren"
},
"draftAction": {
"message": "Wähle \"Ja\", um diesen Entwurf zu laden oder \"Nein\", um ihn zu verwerfen."
},
"draftTitle": {
"message": "Wiederherstellung ungespeicherter Entwürfe, erstellt vor $date$",
"placeholders": {
"date": {
"content": "$1"
}
}
},
"dragDropMessage": {
"message": "Ziehe die Backup Datei zum importieren an irgendeinen Ort auf dieser Seite."
},
"dragDropUsercssTabstrip": {
"message": "Ziehe die Datei auf die Tableiste, um sie zu installieren."
},
"editDeleteText": {
"message": "Löschen"
},
@ -225,15 +266,27 @@
}
}
},
"editorStylesButton": {
"message": "Editor Styles finden"
"editorSettings": {
"message": "Editor Einstellungen"
},
"enableStyleLabel": {
"message": "Aktivieren"
},
"excludeStyleByDomainLabel": {
"message": "Aktuelle Domain ausschließen"
},
"excludeStyleByUrlLabel": {
"message": "Aktuelle URL ausschließen"
},
"exportCompatible": {
"message": "Exportieren (Kompatibilitätsmodus)"
},
"exportLabel": {
"message": "Exportieren"
},
"exportSavedSuccess": {
"message": "Datei erfolgreich gespeichert"
},
"externalLink": {
"message": "Externer Link"
},
@ -260,21 +313,15 @@
"findStyles": {
"message": "Styles finden"
},
"findStylesForSite": {
"message": "Weitere Styles für diese Seite finden"
},
"findStylesInline": {
"message": "Ergebnisse hier anzeigen"
},
"findStylesInlineTooltip": {
"message": "Suchergebnisse in diesem Fenster anzeigen."
},
"genericAdd": {
"message": "Hinzufügen"
},
"genericClone": {
"message": "Kopieren"
},
"genericDescription": {
"message": "Beschreibung"
},
"genericDisabledLabel": {
"message": "Deaktiviert"
},
@ -299,12 +346,21 @@
"genericSavedMessage": {
"message": "Gespeichert"
},
"genericSize": {
"message": "Größe"
},
"genericTitle": {
"message": "Name"
},
"genericUnknown": {
"message": "Unbekannt"
},
"gettingStyles": {
"message": "Empfange alle Styles..."
},
"headerResizerHint": {
"message": "Halte Shift gedrückt, um nur diese Art der Benutzeroberfläche (z.B. Editor, Manager, Installer) zu verändern"
},
"helpAlt": {
"message": "Hilfe"
},
@ -314,6 +370,9 @@
"helpKeyMapHotkey": {
"message": "Hotkey drücken"
},
"hostDisabled": {
"message": "Dieser Host wurde deaktiviert, weil die aktuell genutzte Version deines Browsers einen Fehler enthält."
},
"importAppendLabel": {
"message": "Zum Style anfügen"
},
@ -323,6 +382,12 @@
"importLabel": {
"message": "Importieren"
},
"importPreprocessor": {
"message": "Ein Style mit einem <code>@preprocessor</code>funktioniert nicht im klassischen Modus. Du kannst den Editor auf UserCSS umstellen: 1) Öffne den Style-Manager, 2) Aktiviere das Kästchen \"Als UserCSS\", 3) Klicke auf \"Neuen Style erstellen\"\n\nTrotzdem jetzt importieren?"
},
"importPreprocessorTitle": {
"message": "Mögliches Problem wegen @preprocessor"
},
"importReplaceLabel": {
"message": "Style überschreiben"
},
@ -363,7 +428,7 @@
"message": "Style installieren"
},
"installButtonInstalled": {
"message": "Style installiert"
"message": "Style erfolgreich installiert"
},
"installButtonReinstall": {
"message": "Style neu installieren"
@ -385,18 +450,24 @@
"installUpdateFromLabel": {
"message": "Nach Updates suchen"
},
"installUpdateUnavailable": {
"message": "Ziehe die Datei auf die Tableiste oder definiere @updateURL in den Metadaten des Styles, um automatisch nach Updates zu suchen."
},
"license": {
"message": "Lizenz"
},
"linkGetHelp": {
"message": "Hilfeseite anzeigen"
},
"linkGetShareStyles": {
"message": "Styles finden und teilen"
},
"linkGetShareStylesInfo": {
"message": "Die neue, von der Community betriebene Website userstyles.world wurde von Userstyle-Autoren erstellt, um userstyles.org zu ersetzen. Dieses war im letzten Jahr so langsam und unzuverlässig geworden, dass viele Autoren ihre Styles nicht mehr aktualisierten."
},
"linkGetStyles": {
"message": "Styles beziehen"
},
"linkGetStylesInfo": {
"message": "Diese Archivseite wurde von einem Nutzer der Userstyle Community erstellt, um eine Sicherungskopie des langsamen und unzuverlässigen userstyles.org bereitzustellen. Die Inhalte werden ungefähr einmal am Tag aktualisiert."
},
"linkTranslate": {
"message": "Übersetzen"
},
@ -449,14 +520,14 @@
"message": "Bei der Echtzeitaktualisierung der Datei ist ein Fehler aufgetreten"
},
"liveReloadInstallHint": {
"message": "Echtzeitaktualisierung ist aktiviert, sodass die Darstellung des jeweiligen Styles automatisch aktualisiert wird, wenn externe Änderungen erfolgen."
"message": "Lasse diesen Tab geöffnet, um den Style bei externen Änderungen automatisch zu aktualisieren."
},
"liveReloadInstallHintFF": {
"message": "Lasse diesen und den Originaltab offen, um den Style bei externen Änderungen automatisch zu aktualisieren."
},
"liveReloadLabel": {
"message": "Echtzeitaktualisierung"
},
"liveReloadUnavailable": {
"message": "Ziehe die Datei auf die Tableiste, um die Echtzeitaktualisierung nutzen zu können."
},
"manageFavicons": {
"message": "Favicons in der \"Gilt für\" Spalte anzeigen"
},
@ -464,7 +535,7 @@
"message": "Ausgegraut"
},
"manageFaviconsHelp": {
"message": "Stylus nutzt hierzu den externen Dienst https://www.google.com/s2/favicons"
"message": "Stylus nutzt hierzu den externen Dienst https://icons.duckduckgo.com"
},
"manageFilters": {
"message": "Filter"
@ -475,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"
},
@ -508,16 +582,211 @@
"menuShowBadge": {
"message": "Anzahl der aktiven Styles anzeigen"
},
"meta_invalidCheckboxDefault": {
"message": "Ungültige @var Checkbox: Wert muss 0 oder 1 sein"
},
"meta_invalidColor": {
"message": "Ungültige @var Farbe: $color$ ist keine Farbe",
"placeholders": {
"color": {
"content": "$1"
}
}
},
"meta_invalidNumber": {
"message": "Erwarte Zahl"
},
"meta_invalidRange": {
"message": "Ungültige @var $type$: Wert muss eine Zahl oder ein Array sein",
"placeholders": {
"type": {
"content": "$1"
}
}
},
"meta_invalidRangeDefault": {
"message": "Ungültige @var $type$: Standardwert ist null",
"placeholders": {
"type": {
"content": "$1"
}
}
},
"meta_invalidRangeMax": {
"message": "Ungültige @var $type$: Standardwert ist größer als das Maximum",
"placeholders": {
"type": {
"content": "$1"
}
}
},
"meta_invalidRangeMin": {
"message": "Ungültige @var $type$: Standardwert ist geringer als das Minimum",
"placeholders": {
"type": {
"content": "$1"
}
}
},
"meta_invalidRangeMultipleUnits": {
"message": "Ungültige @var $type$: Mehrere Einheiten sind definiert",
"placeholders": {
"type": {
"content": "$1"
}
}
},
"meta_invalidRangeStep": {
"message": "Ungültige @var $type$: Standardwert ist kein Vielfaches der Schrittweite",
"placeholders": {
"type": {
"content": "$1"
}
}
},
"meta_invalidRangeTooManyValues": {
"message": "Ungültige @var $type$: Der Array enthält zu viele Elemente",
"placeholders": {
"type": {
"content": "$1"
}
}
},
"meta_invalidRangeUnits": {
"message": "Ungültige @var $type$: '$units$' ist keine gültige Einheit",
"placeholders": {
"type": {
"content": "$1"
},
"units": {
"content": "$2"
}
}
},
"meta_invalidRangeValue": {
"message": "Ungültige @var $type$: Elemente im Array müssen Zahlen, Strings oder null sein",
"placeholders": {
"type": {
"content": "$1"
}
}
},
"meta_invalidSelect": {
"message": "Ungültiges @var select: Der Standardwert muss eine Array oder Objekt sein"
},
"meta_invalidSelectEmptyOptions": {
"message": "Ungültiges @var select: Die Auswahlliste ist leer"
},
"meta_invalidSelectLabel": {
"message": "Ungültiges @var select: Beschriftung der Auswahlmöglichkeiten ist leer"
},
"meta_invalidSelectMultipleDefaults": {
"message": "Ungültiges @var select: Mehrere Standardauswahlen sind definiert"
},
"meta_invalidSelectNameDuplicated": {
"message": "Ungültiges @var select: Doppelte Optionsnamen"
},
"meta_invalidSelectValue": {
"message": "Ungültiges @var select: Werte innerhalb des Arrays/Objekts müssen Strings sein"
},
"meta_invalidSelectValueMismatch": {
"message": "Ungültiges @var select: Wert existiert nicht in der Auswahlliste"
},
"meta_invalidString": {
"message": "Erwarte String in Anführungszeichen"
},
"meta_invalidURLProtocol": {
"message": "Ungültiges URL Protokoll. Nur http und https sind erlaubt: $protocol$",
"placeholders": {
"protocol": {
"content": "$1"
}
}
},
"meta_invalidVersion": {
"message": "Ungültige Versionsnummer"
},
"meta_invalidWord": {
"message": "Erwarte ein Wort"
},
"meta_missingChar": {
"message": "Erwarte Zeichen: $chars$",
"placeholders": {
"chars": {
"content": "$1"
}
}
},
"meta_missingEOT": {
"message": "Erwarte EOT Daten"
},
"meta_missingMandatory": {
"message": "Erforderliche Metadaten fehlen: $keys$",
"placeholders": {
"keys": {
"content": "$1"
}
}
},
"meta_unknownJSONLiteral": {
"message": "Ungültiges JSON: $literal$ ist kein gültiges JSON Symbol",
"placeholders": {
"literal": {
"content": "$1"
}
}
},
"meta_unknownMeta": {
"message": "Unbekannte Metadaten: $key$",
"placeholders": {
"key": {
"content": "$1"
}
}
},
"meta_unknownMetaTypo": {
"message": "Vielleicht @$keyOk$? Unbekannte Metadaten: @$keyErr$",
"placeholders": {
"keyErr": {
"content": "$1"
},
"keyOk": {
"content": "$2"
}
}
},
"meta_unknownPreprocessor": {
"message": "Unbekannter @preprocessor: $preprocessor$",
"placeholders": {
"preprocessor": {
"content": "$1"
}
}
},
"meta_unknownVarType": {
"message": "Unbekannter @$varkey$ Typ: $vartype$",
"placeholders": {
"varkey": {
"content": "$1"
},
"vartype": {
"content": "$2"
}
}
},
"noFileToImport": {
"message": "Keine Importdatei vorhanden. Um Styles zu importieren, solltest du sie zuerst exportieren."
},
"noStylesForSite": {
"message": "Für diese Webseite sind keine Styles installiert."
},
"numberedLine": {
"message": "Zeile:"
},
"openManage": {
"message": "Verwalten"
},
"openOptionsManage": {
"message": "Optionen"
},
"openOptionsPopup": {
"openOptions": {
"message": "Optionen"
},
"openStylesManager": {
@ -529,6 +798,15 @@
"optionsAdvanced": {
"message": "Erweitert"
},
"optionsAdvancedAutoSwitchSchemeBySystem": {
"message": "Nach Systemeinstellung"
},
"optionsAdvancedAutoSwitchSchemeByTime": {
"message": "Bei Nacht:"
},
"optionsAdvancedAutoSwitchSchemeNever": {
"message": "Deaktiviert. Die hell / dunkel Einstellung in Styles wird ignoriert."
},
"optionsAdvancedContextDelete": {
"message": "\"Löschen\" im Editor-Kontextmenü hinzufügen"
},
@ -536,11 +814,29 @@
"message": "Ermögliche Iframes via HTML[stylus-iframe]"
},
"optionsAdvancedExposeIframesNote": {
"message": "Style wirkt sich auch auf iframes der anvisierten Domain aus.\nIframe-spezifisches CSS ist dann möglich wie folgt:\nhtml[stylus-iframe$=\"twitter.com\"] h1 { display:none }"
"message": "Style wirkt sich auch auf iframes der anvisierten (obersten) Domain aus.\nIframe-spezifisches CSS ist dann wie folgt möglich:\nhtml[stylus-iframe$$=\"twitter.com\"] h1 { display:none }"
},
"optionsAdvancedExposeStyleName": {
"message": "Stylename anzeigen"
},
"optionsAdvancedExposeStyleNameNote": {
"message": "Zeigt den Stylenamen auf der Seite an, um das Debuggen von Styles in den Devtools zu erleichtern. Bitte lade die Tabs neu, um die neue Einstellung zu übernehmen."
},
"optionsAdvancedNewStyleAsUsercss": {
"message": "Schreibe neuen Style als UserCSS"
},
"optionsAdvancedPatchCsp": {
"message": "<code>CSP</code> abändern, um externe Ressourcen zu erlauben"
},
"optionsAdvancedPatchCspNote": {
"message": "Aktivieren, wenn Styles Bilder oder Schriftarten enthalten, die aufgrund strenger <code>CSP</code> (<code>Content-Security-Policy</code>) Regeln mancher Seiten nicht laden.\n\nDas Aktivieren wird <code>CSP</code>Beschränkungen lockern, um für den Style erforderliche Ressourcen zu laden. Diese Option ist für fortgeschrittene Benutzer gedacht, die sich den möglichen Sicherheitsrisiken bewusst sind und die die Verantwortung dafür tragen, die nachgeladenen Inhalte selbst zu überwachen. Informiere dich über \"CSS-basierte Angriffe\" um mehr zu erfahren.\n\nBeachte außerdem, dass diese Option nicht garantiert funktioniert, falls eine andere installierte Erweiterung die Netzwerkantwort (CSP-header) zuerst abändert."
},
"optionsAdvancedStyleViaXhr": {
"message": "Style unverzüglich einfügen"
},
"optionsAdvancedStyleViaXhrNote": {
"message": "Aktivieren, falls während dem Browsen ungestylter Inhalt aufblitzt (FOUC). Besonders auffällig bei dunklen Styles.\n\nDer technische Grund ist, dass Chrome/Chromium die asynchrone Kommunikation von Erweiterungen zeitlich nach hinten verschiebt, um vermeintlich die Seitenladegeschwindigkeit zu erhöhen. Dies kann das Einfügen von Styles spürbar verzögern. Da für WebExtensions keine synchrone API bereitgestellt wird, versucht Stylus als Notbehelf, Styles über die veraltete XMLHttpRequest web API zu laden. Es sollten keine nachteiligen Effekte eintreten, weil die Anfrage innerhalb weniger Millisekunden abgearbeitet wird, während die Seite noch vom Server geladen wird.\n\nTrotzdem wird Chromium eine Warnung in der Entwicklerkonsole ausgeben. Über Rechtsklick auf die Warnung können zukünftige Meldungen versteckt werden."
},
"optionsBadgeDisabled": {
"message": "Hintergrundfarbe wenn deaktiviert"
},
@ -559,9 +855,15 @@
"optionsCustomizeIcon": {
"message": "Symbolleisten-Icon"
},
"optionsCustomizeSync": {
"message": "Mit Cloud synchronisieren"
},
"optionsHeading": {
"message": "Optionen"
},
"optionsIconAuto": {
"message": "An Hell- / Dunkelmodus angleichen"
},
"optionsIconDark": {
"message": "Dunkle Browser-Themes"
},
@ -583,15 +885,82 @@
"optionsResetButton": {
"message": "Optionen zurücksetzen"
},
"optionsStylusThemes": {
"message": "Klicke auf einer beliebigen Stylus Seite auf das Stylus-Symbol in der Werkzeugleiste des Browsers, dann klicke auf \"Styles finden\""
},
"optionsSubheading": {
"message": "Mehr Optionen"
},
"optionsSyncConnect": {
"message": "Verbinden"
},
"optionsSyncDisconnect": {
"message": "Trennen"
},
"optionsSyncLogin": {
"message": "Anmelden"
},
"optionsSyncNone": {
"message": "Nichts"
},
"optionsSyncPassword": {
"message": "Passwort"
},
"optionsSyncStatusConnected": {
"message": "Verbunden"
},
"optionsSyncStatusConnecting": {
"message": "Verbinde..."
},
"optionsSyncStatusDisconnected": {
"message": "Verbindung getrennt"
},
"optionsSyncStatusDisconnecting": {
"message": "Trenne Verbindung..."
},
"optionsSyncStatusPull": {
"message": "Hole Style $loaded$ von $total$",
"placeholders": {
"loaded": {
"content": "$1"
},
"total": {
"content": "$2"
}
}
},
"optionsSyncStatusPush": {
"message": "Lade Style $loaded$ von $total$ hoch",
"placeholders": {
"loaded": {
"content": "$1"
},
"total": {
"content": "$2"
}
}
},
"optionsSyncStatusRelogin": {
"message": "Session abgelaufen, bitte neu einloggen."
},
"optionsSyncStatusSyncing": {
"message": "Synchronisiere..."
},
"optionsSyncSyncNow": {
"message": "Jetzt synchronisieren"
},
"optionsSyncUsername": {
"message": "Benutzername"
},
"optionsUpdateImportNote": {
"message": "Nach dem Importieren von Styles aus einer alten Version oder von Stylish ist eine einmalige manuelle Updatesuche in der Verwaltung nötig. Dies stellt sicher, dass alle Styles auf dem aktuellsten Stand sind."
},
"optionsUpdateInterval": {
"message": "Style-Updateintervall in Stunden (0 zum Deaktivieren)"
},
"overwriteFileExport": {
"message": "Willst du die existierende Datei überschreiben?"
},
"paginationCurrent": {
"message": "Aktuelle Seite"
},
@ -610,6 +979,9 @@
"parseUsercssError": {
"message": "UserCSS parsen fehlgeschlagen:"
},
"popupAutoResort": {
"message": "Styles im Popup sofort neu sortieren"
},
"popupBorders": {
"message": "Weiße Rahmen an den Seiten hinzufügen"
},
@ -622,9 +994,18 @@
"popupHotkeysTooltip": {
"message": "Klicke, um Tastenkürzel zu sehen"
},
"popupManageSiteStyles": {
"message": "Styles dieser Seite verwalten"
},
"popupManageTooltip": {
"message": "Shift+Klick oder Rechtsklick öffnet den Stylemanager mit Filter für Styles der aktuellen Seite"
},
"popupMenuButtonTooltip": {
"message": "Aktionsmenü"
},
"popupOpenEditInPopup": {
"message": "Einfaches Fenster verwenden (keine Omnibox)"
},
"popupOpenEditInWindow": {
"message": "Editor in neuem Fenster öffnen"
},
@ -637,12 +1018,51 @@
"prefShowBadge": {
"message": "Anzahl der aktiven Styles auf der aktuellen Seite"
},
"preferScheme": {
"message": "Hell- / Dunkelmodus Vorrang"
},
"preferSchemeAlways": {
"message": "Derzeit ignoriert (Style wird immer angewendet), weil der allgemeine Hell- / Dunkelmodus nicht aktiv ist"
},
"preferSchemeDark": {
"message": "Dunkel"
},
"preferSchemeLight": {
"message": "Hell"
},
"preferSchemeNone": {
"message": "Keines (immer aktiv)"
},
"previewLabel": {
"message": "Echtzeitvorschau"
},
"previewTooltip": {
"message": "Übernimmt die Änderungen nur vorübergehend.\nSpeichere den Style, um die Änderungen dauerhaft anzuwenden."
},
"publish": {
"message": "Veröffentlichen"
},
"publishPush": {
"message": "Erneut veröffentlichen"
},
"publishReconnect": {
"message": "Versuche, die Verbindung zu trennen; dann erneut veröffentlichen"
},
"publishRetry": {
"message": "Stylus versucht noch, den Style zu veröffentlichen, aber du kannst es erneut versuchen, wenn du keine Aktivität oder Popups siehst. Jetzt neu versuchen?"
},
"publishStyle": {
"message": "Style veröffentlichen"
},
"publishUsw": {
"message": "Via <userstyles.world>"
},
"readingStyles": {
"message": "Lese Styles..."
},
"reload": {
"message": "Neu laden"
},
"replace": {
"message": "Ersetzen"
},
@ -652,15 +1072,24 @@
"replaceWith": {
"message": "Ersetzen durch"
},
"restoreTemplate": {
"message": "Standard-Template wiederherstellen.\n\n(Die gerade geöffneten Editorseiten werden nicht verändert.)"
},
"retrieveBckp": {
"message": "Styles importieren"
},
"saveAsTemplate": {
"message": "Als Template speichern"
},
"search": {
"message": "Suche"
},
"searchCaseSensitive": {
"message": "Groß- / Kleinschreibung beachten"
},
"searchGlobalStyles": {
"message": "Auch globale Styles suchen"
},
"searchNumberOfResults": {
"message": "Trefferanzahl"
},
@ -676,6 +1105,12 @@
"searchResultNoneFound": {
"message": "Keine Styles für diese Seite gefunden."
},
"searchResultNotMatching": {
"message": "Der Style wurde installiert, gilt aber nicht für die aktuelle URL."
},
"searchResultNotMatchingNote": {
"message": "Frage den Autor des Styles, ob er die URL hinzufügt.\n\nDu kannst den Style auch selbst bearbeiten,\naber dann werden automatische Updates für diesen Style deaktiviert."
},
"searchResultRating": {
"message": "Bewertung"
},
@ -685,30 +1120,48 @@
"searchResultWeeklyCount": {
"message": "Wöchentliche Installationen"
},
"searchStyles": {
"message": "Inhalte durchsuchen"
"searchStyleQueryHint": {
"message": "Stylenamen durchsuchen (Groß-Kleinschreibung wird beachtet, sobald ein Großbuchstabe benutzt wird):\nHaus Baum Sonne - sucht nach allen Wörtern in beliebiger Reihenfolge\n\"Baum Haus\" - sucht exakt diesen Ausdruck (ohne Anführungszeichen)\n/foo.*bar/i - Regulärer Ausdruck ohne Leerzeichen (nutze stattdessen \\s)"
},
"searchStylesAll": {
"message": "Alles"
},
"searchStylesCode": {
"message": "CSS Code"
},
"searchStylesHelp": {
"message": "Die </>-Taste (Numpad) fokussiert das Suchfeld.\nEinfacher Text: Sucht im Namen, Quelltext, Homepage und anvisierten URLs. Wörter mit weniger als 3 Buchstaben werden ignoriert.\nStyles, die auf eine URL passen: Stelle der Suche <url:> voran, z.B. <url:https://github.com/openstyles/stylus>\nRegExp: Nutze Slash und Flags, z.B. </body.*?\\ba\\b/simguy>\nSuche nach genauem Ausdruck: Anführungszeichen verwenden, z.B. <\".header ~ div\"> "
"message": "Die </>-Taste (Numpad) oder <Ctrl-F> fokussieren das Suchfeld.\nEinfacher Text: Sucht mit Leerzeichen getrennte Wörter in beliebiger Reihenfolge.\nSuche nach genauem Ausdruck: Anführungszeichen verwenden, z.B. <\".header ~ div\">\nRegExp: Nutze Schrägstrich und Flags, z.B. </body.*?\\ba\\b/i>\nStyles, die auf eine URL passen: Aktiviere \"Nach URL\" und suche nach einer vollständigen URL, z.B. https://www.example.org/\nMetadaten: Aktiviere das Kontrollkästchen für Metadaten, um in Namen, \"Gilt für\" Feldern, Installations-URL, Update-URL und dem gesamten Metadatenblock in UserCSS Styles zu suchen."
},
"searchStylesMatchUrl": {
"message": "Nach URL"
},
"searchStylesMeta": {
"message": "Metadaten"
},
"sectionAdd": {
"message": "Weiteren Bereich hinzufügen"
},
"sectionHelp": {
"message": "Mit Bereichen kannst Du unterschiedliche Code-Teile auf unterschiedliche URLs in dem gleichen Style anwenden. Beispielsweise kann über einen einzigen Style die Startseite der Website geändert werden, während für den Rest der Website andere Änderungen gelten."
},
"sectionRemove": {
"message": "Bereich entfernen"
},
"sectionRestore": {
"message": "Gelöschten Bereich wiederherstellen"
},
"sections": {
"message": "Bereiche"
},
"settings": {
"message": "Einstellungen"
},
"shortcuts": {
"message": "Tastenkürzel"
},
"shortcutsNote": {
"message": "Eine Tastenkombination definieren"
},
"shortcutsNoteFF": {
"message": "In Firefox 66+ kannst du die eingebaute Tastenkürzelverwaltung selbst öffnen:\n1) Rechtsklicke das Stylus-Symbol in der Werkzeugleiste und wähle \"Erweiterung verwalten\". Alternativ kannst du about:addons über das Hauptmenü oder per Tastenkürzel Strg-Umschalt-A öffnen.\n2) Klicke auf der geöffneten Seite (about:addons) auf das Zahnrad oben rechts.\n3) Wähle \"Tastenkombinationen von Erweiterungen verwalten\".\n\nHier kannst du alle Tastenkürzel individuell anpassen."
},
"sortDateNewestFirst": {
"message": "neueste zuerst"
},
@ -736,6 +1189,9 @@
"styleBeautify": {
"message": "Code formatieren"
},
"styleBeautifyHint": {
"message": "Hinweis: Mache einen Rechtsklick auf \"Code formatieren\" oder nutze das weiter unten definierte Tastenkürzel, um den Code zu formatieren ohne dieses Fenster anzuzeigen."
},
"styleBeautifyIndentConditional": {
"message": "Rücke @media / @supports ein"
},
@ -751,12 +1207,30 @@
"styleEnabledLabel": {
"message": "Aktiviert"
},
"styleExcludeLabel": {
"message": "Benutzerdefinierte ausgeschlossene URLs"
},
"styleFromMozillaFormatError": {
"message": "Import vom Mozilla Format fehlgeschlagen"
},
"styleFromMozillaFormatPrompt": {
"message": "Mozilla-Format Code einfügen"
},
"styleIncludeLabel": {
"message": "Benutzerdefinierte Ziel-URLs"
},
"styleInjectionImportance": {
"message": "Style als wichtig markieren"
},
"styleInjectionOrder": {
"message": "Applikationsreihenfolge der Styles"
},
"styleInjectionOrderHint": {
"message": "Ziehe den Style mit der Maus an die gewünschte Stelle, um die Applikationsreihenfolge zu ändern. Styles weiter unten können die oberen überschreiben."
},
"styleInjectionOrderHint_prio": {
"message": "Die unten als wichtig gekennzeichneten Styles werden immer zuletzt angewendet, sodass sie auch neu installierte Styles überschreiben können. Klicke auf das Zeichen des Styles, um die Wichtigkeit umzuschalten."
},
"styleInstall": {
"message": "\"$stylename$\" mit Stylus installieren?",
"placeholders": {
@ -787,42 +1261,24 @@
}
}
},
"styleMetaErrorCheckbox": {
"message": "Ungültige @var Checkbox: Wert muss 0 oder 1 sein"
},
"styleMetaErrorColor": {
"message": "$color$ist kein gültiger Farbcode",
"placeholders": {
"color": {
"content": "$1"
}
}
},
"styleMetaErrorPreprocessor": {
"message": "Nicht unterstützter @preprocessor: $preprocessor$",
"placeholders": {
"preprocessor": {
"content": "$1"
}
}
},
"styleMetaErrorSelectValueMismatch": {
"message": "Ungültiges @select: Wert existiert nicht in der Liste"
},
"styleMissingMeta": {
"message": "Erforderliche Metadaten fehlen: @$key$",
"placeholders": {
"key": {
"content": "$1"
}
}
},
"styleMissingName": {
"message": "Bitte Namen eingeben"
},
"styleName": {
"message": "Stylename"
},
"styleNotAppliedRegexpProblemTooltip": {
"message": "Der Style wurde aufgrund ungültiger RegExp nicht angewandt."
},
"styleNotAppliedSchemeDark": {
"message": "Der Style wird nur im dunklen Modus angewendet"
},
"styleNotAppliedSchemeLight": {
"message": "Der Style wird nur im hellen Modus angewendet"
},
"stylePreferSchemeLabel": {
"message": "Dunkler/Heller Modus"
},
"styleRegexpInvalidExplanation": {
"message": "Einige RegExp konnten nicht kompiliert werden."
},
@ -832,9 +1288,6 @@
"styleRegexpProblemTooltip": {
"message": "Anzahl der Bereiche, welche aufgrund nicht korrekt verwendeter RegExp nicht angewendet wurden"
},
"styleRegexpTestButton": {
"message": "RegExp testen"
},
"styleRegexpTestFull": {
"message": "Zutreffende Tabs"
},
@ -856,8 +1309,8 @@
"styleSaveLabel": {
"message": "Speichern"
},
"styleSectionsTitle": {
"message": "Bereiche"
"styleSettings": {
"message": "Style Einstellungen"
},
"styleToMozillaFormatHelp": {
"message": "Das Mozilla-Format des Codes kann mit Stylish für Firefox verwendet werden und bei userstyles.org eingereicht werden."
@ -880,7 +1333,24 @@
"message": "Stylus funktioniert nicht auf Seiten wie diesen."
},
"stylusUnavailableForURLdetails": {
"message": "Als Sicherheitsvorkehrung verbietet der Browser es Erweiterungen, seine eingebauten Seiten (wie chrome://version, dem neuen Standard-Tab ab Chrome 61, about:addons, usw.), sowie Seiten anderer Erweiterungen zu beeinflussen. Jeder Browser beschränkt auch den Zugriff auf seine eigene Erweiterungsgalerie (wie Chrome Web Store oder addons.mozilla.org)."
"message": "Als Sicherheitsvorkehrung verbietet der Browser es Erweiterungen, seine eigenen Seiten (wie z.B. chrome://version oder about:addons) und eigene Seiten anderer Erweiterungen zu verändern. Auch die jeweils browser-eigene Erweiterungsgalerie (wie Chrome Web Store oder addons.mozilla.org) kann nicht verändert werden."
},
"syncDropboxDeprecated": {
"message": "Dropbox Import / Export wurde durch einen fortschrittlicheren Mechanismus auf der Optionsseite ersetzt."
},
"syncError": {
"message": "Synchronisation fehlgeschlagen"
},
"syncErrorLock": {
"message": "Die Datenbank wird bereits verwendet. Die Sperre wird um $TIME$ aufgehoben.",
"placeholders": {
"time": {
"content": "$1"
}
}
},
"syncErrorRelogin": {
"message": "Synchronisation erfolglos. Du wurdest ausgeloggt.\nVersuche, dich in den Stylus Einstellungen wieder einzuloggen."
},
"syncStorageErrorSaving": {
"message": "Der Wert kann nicht gespeichert werden. Versuche, die Textmenge zu reduzieren."
@ -900,18 +1370,21 @@
"unreachableAMOHint": {
"message": "Um den Zugriff zu erlauben, öffne <about:config>, rechtsklicke in die Liste, klicke \"Neu\", dann \"Boolean\", füge <privacy.resistFingerprinting.block_mozAddonManager> ein und klicke OK, <true>, OK, lade <addons.mozilla.org> neu."
},
"unreachableAMOHintNewFF": {
"message": "In Firefox 60 und neuer muss auch die addons.mozilla.org domain von <extensions.webextensions.restrictedDomains> in <about:config> entfernt werden."
},
"unreachableAMOHintOldFF": {
"message": "Nur in Firefox 59 und neuer ist es möglich, WebExtenstions den Zugriff auf CSP-geschützte Seiten wie dieser zu erlauben."
},
"unreachableContentScript": {
"message": "Konnte nicht mit der Seite kommunizieren. Bitte versuchen Sie, die Seite erneut zu laden."
},
"unreachableFileHint": {
"message": "Stylus kann nur auf das file:// Protokoll in der URL zugreifen, wenn dies in den Einstellungen der Erweiterung unter chrome://extensions festgelegt wurde."
},
"unreachableMozSiteHint": {
"message": "In Firefox 60 oder neuer muss diese Domain von <extensions.webextensions.restrictedDomains> in <about:config> entfernt werden."
},
"unreachableMozSiteHintOldFF": {
"message": "Erst ab Firefox 59 können WebExtensions Style-Elemente auf CSP-geschützten Seiten wie dieser hinzufügen."
},
"unzipStyles": {
"message": "Entpacke Styles..."
},
"updateAllCheckSucceededNoUpdate": {
"message": "Keine Updates gefunden."
},
@ -953,6 +1426,9 @@
"updatesCurrentlyInstalled": {
"message": "Installierte Updates:"
},
"uploadingFile": {
"message": "Lade Styles hoch..."
},
"usercssAvoidOverwriting": {
"message": "Bitte ändere @name oder @namespace, um das Überschreiben eines bereits existierenden Styles zu verhindern."
},
@ -965,9 +1441,6 @@
"usercssReplaceTemplateConfirmation": {
"message": "Ersetze das vorgegebene Template für neue UserCSS styles mit dem vorliegenden Code?"
},
"usercssReplaceTemplateName": {
"message": "Ein leeres @name ersetzt das vorgegebene Template"
},
"usercssReplaceTemplateSectionBody": {
"message": "Quelltext hier eingeben..."
},
@ -979,5 +1452,8 @@
},
"writeStyleForURL": {
"message": "diese URL"
},
"zipStyles": {
"message": "Packe Styles..."
}
}

View File

@ -1,10 +1,16 @@
{
"InaccessibleFileHint": {
"message": "Το Stylus δεν έχει πρόσβαση σε κάποια αρχεία (π.χ. τα αρχεία PDF και JSON)"
},
"addStyleLabel": {
"message": "Γράψτε νέο στυλ"
},
"addStyleTitle": {
"message": "Προσθήκη στυλ"
},
"alphaChannel": {
"message": "Αδιαφάνεια"
},
"appliesAdd": {
"message": "Προσθήκη"
},
@ -28,6 +34,9 @@
"appliesLabel": {
"message": "Ισχύει για"
},
"appliesLineWidgetWarning": {
"message": "Δε λειτουργεί με minified CSS."
},
"appliesRegexpOption": {
"message": "Διευθύνσεις URL που ταιριάζουν με την κανονική έκφραση"
},
@ -40,36 +49,167 @@
"appliesToEverything": {
"message": "Τα πάντα"
},
"appliesUrlOption": {
"message": "διεύθυνση URL"
},
"appliesUrlPrefixOption": {
"message": "Διευθύνσεις URL που αρχίζουν με"
},
"applyAllUpdates": {
"message": "Εφαρμογή όλων των ενημερώσεων"
},
"author": {
"message": "Συντάκτης"
},
"backupButtons": {
"message": "Δημιουργήστε αντίγραφο ασφαλείας"
},
"bckpInstStyles": {
"message": "Εξαγωγή στυλ"
},
"checkAllUpdates": {
"message": "Έλεγχος όλων των στυλ για ενημερώσεις"
},
"checkAllUpdatesForce": {
"message": "Ελέγξτε πάλι, δεν επεξεργάστηκα κανένα στυλ!"
},
"checkForUpdate": {
"message": "Έλεγχος για ενημερώσεις"
},
"checkingForUpdate": {
"message": "Έλεγχος..."
},
"clickToUninstall": {
"message": "Πατήστε για απεγκατάσταση"
},
"cm_autoCloseBrackets": {
"message": "Αυτόματο κλείσιμο παρενθέσεων και εισαγωγικών"
},
"cm_autocompleteOnTyping": {
"message": "Αυτόματη συμπλήρωση καθώς πληκτρολογείτε"
},
"cm_indentWithTabs": {
"message": "Χρήση καρτελών με έξυπνη εσοχή"
},
"cm_keyMap": {
"message": "Συντομεύσεις πληκτρολογίου"
},
"cm_lineWrapping": {
"message": "Αναδίπλωση λέξεων"
},
"cm_matchHighlight": {
"message": "Υπογράμμιση"
},
"cm_matchHighlightSelection": {
"message": "Μόνο επιλογή"
},
"cm_resizeGripHint": {
"message": "Διπλό κλικ για μεγιστοποίηση/επαναφορά ύψους"
},
"cm_smartIndent": {
"message": "Χρήση έξυπνης εσοχής"
},
"cm_tabSize": {
"message": "Μέγεθος καρτέλας"
},
"cm_theme": {
"message": "Θέμα"
},
"configOnChange": {
"message": "στην αλλαγή"
},
"configOnChangeTooltip": {
"message": "Αυτόματη αποθήκευση και εφαρμογή αλλαγών"
},
"configureStyle": {
"message": "Ρυθμίσεις"
},
"configureStyleOnHomepage": {
"message": "Ρυθμίσεις στην ιστοσελίδα"
},
"confirmCancel": {
"message": "Άκυρο"
},
"confirmClose": {
"message": "Κλείσιμο"
},
"confirmDefault": {
"message": "Χρήση προεπιλογής"
},
"confirmDelete": {
"message": "Διαγραφή"
},
"confirmDiscardChanges": {
"message": "Απόρριψη αλλαγών;"
},
"confirmNo": {
"message": "Όχι"
},
"confirmOK": {
"message": "ΟΚ"
},
"confirmSave": {
"message": "Αποθήκευση"
},
"confirmYes": {
"message": "Ναι"
},
"connectingDropbox": {
"message": "Σύνδεση με το Dropbox..."
},
"connectingDropboxNotAllowed": {
"message": "Η σύνδεση με το Dropbox είναι διαθέσιμη μόνο σε εφαρμογές εγκατεστημένες απευθείας από το κατάστημα ιστού webstore"
},
"copied": {
"message": "Αντιγράφηκε στο πρόχειρο"
},
"copy": {
"message": "Αντιγραφή στο πρόχειρο"
},
"dateAbbrDay": {
"message": "$value$μ",
"placeholders": {
"value": {
"content": "$1"
}
}
},
"dateAbbrHour": {
"message": "$value$ω",
"placeholders": {
"value": {
"content": "$1"
}
}
},
"dateAbbrMonth": {
"message": "$value$λ",
"placeholders": {
"value": {
"content": "$1"
}
}
},
"dateAbbrYear": {
"message": "$value$χ",
"placeholders": {
"value": {
"content": "$1"
}
}
},
"dateInstalled": {
"message": "Ημερομηνία εγκατάστασης"
},
"dateUpdated": {
"message": "Ημερομηνία ενημέρωσης"
},
"dbError": {
"message": "Παρουσιάστηκε σφάλμα χρησιμοποιώντας την κομψή βάση δεδομένων. Θα θέλατε να επισκεφθείτε μια ιστοσελίδα με πιθανές λύσεις;"
},
"defaultTheme": {
"message": "προεπιλογή"
},
"deleteStyleConfirm": {
"message": "Είστε σίγουροι ότι θέλετε να διαγράψετε αυτό το στυλ;"
},
@ -85,6 +225,15 @@
"disableStyleLabel": {
"message": "Απενεργοποίηση"
},
"dragDropMessage": {
"message": "Αποθέστε το αντίγραφο ασφαλείας σας οπουδήποτε σε αυτήν τη σελίδα για εισαγωγή."
},
"dragDropUsercssTabstrip": {
"message": "Για να εγκαταστήσετε το αρχείο, αποθέστε το στη λωρίδα καρτελών (την περιοχή όπου εμφανίζονται οι τίτλοι καρτελών)."
},
"editDeleteText": {
"message": "Διαγραφή"
},
"editGotoLine": {
"message": "Μετάβαση στη γραμμή (ή line:col)"
},
@ -105,60 +254,380 @@
"enableStyleLabel": {
"message": "Ενεργοποίηση"
},
"findStylesForSite": {
"message": "Αναζήτηση περισσότερων στυλ για αυτή την ιστοσελίδα"
"excludeStyleByDomainLabel": {
"message": "Εξαίρεση του τρέχοντος τομέα"
},
"excludeStyleByUrlLabel": {
"message": "Εξαίρεση του τρέχοντος URL"
},
"exportLabel": {
"message": "Εξαγωγή"
},
"exportSavedSuccess": {
"message": "Το αρχείο αποθηκεύτηκε επιτυχώς."
},
"externalFeedback": {
"message": "Σχόλια"
},
"externalHomepage": {
"message": "Αρχική σελίδα"
},
"externalLink": {
"message": "Εξωτερική σύνδεση"
},
"externalSupport": {
"message": "Υποστήριξη"
},
"externalUsercssDocument": {
"message": "Τεκμηρίωση για Usercss"
},
"filteredStyles": {
"message": "Βλέπετε $numShown$ από 2$numTotal$ συνολικά",
"placeholders": {
"numShown": {
"content": "$1"
},
"numTotal": {
"content": "$2"
}
}
},
"filteredStylesAllHidden": {
"message": "Τα φίλτρα που εφαρμόζονται αυτήν τη στιγμή δεν ταιριάζουν με κανένα στυλ"
},
"findStyles": {
"message": "Εύρεση στυλ"
},
"genericAdd": {
"message": "Προσθήκη"
},
"genericClone": {
"message": "Δημιουργία αντιγράφου"
},
"genericDisabledLabel": {
"message": "Απενεργοποιημένο"
},
"genericEnabledLabel": {
"message": "Ενεργοποιημένο"
},
"genericError": {
"message": "Σφάλμα"
},
"genericHistoryLabel": {
"message": "Ιστορικό"
},
"genericNext": {
"message": "Επόμενο"
},
"genericPrevious": {
"message": "Προηγούμενο"
},
"genericResetLabel": {
"message": "Επαναφορά"
},
"genericSavedMessage": {
"message": "Αποθηκεύτηκε"
},
"genericTitle": {
"message": "Τίτλος"
},
"genericUnknown": {
"message": "Άγνωστο"
},
"gettingStyles": {
"message": "Λήψη όλων των στυλ..."
},
"helpAlt": {
"message": "Βοήθεια"
},
"helpKeyMapCommand": {
"message": "Πληκτρολογήστε μια εντολή"
},
"helpKeyMapHotkey": {
"message": "Πληκτρολογήστε ένα hotkey"
},
"importLabel": {
"message": "Εισαγωγή"
},
"importReplaceLabel": {
"message": "Αντικατάσταση στυλ"
},
"importReportLegendAdded": {
"message": "προστέθηκαν"
},
"importReportLegendUpdatedCode": {
"message": "ενημερωμένος κώδικας"
},
"importReportTitle": {
"message": "Η εισαγωγή στυλ τελείωσε"
},
"importReportUnchanged": {
"message": "Τίποτα δεν άλλαξε"
},
"importReportUndoneTitle": {
"message": "Η εισαγωγή έχει αναιρεθεί"
},
"installButton": {
"message": "Εγκατάσταση στυλ"
},
"installButtonInstalled": {
"message": "Το στυλ έχει εγκατασταθεί."
},
"installButtonReinstall": {
"message": "Επανεγκατάσταση στυλ"
},
"installButtonUpdate": {
"message": "Ενημέρωση στυλ"
},
"installUpdate": {
"message": "Εγκατάσταση ενημέρωσης"
},
"installUpdateFromLabel": {
"message": "Έλεγχος για ενημερώσεις"
},
"license": {
"message": "Άδεια χρήσης"
},
"linkGetHelp": {
"message": "Βοήθεια"
},
"linkGetStyles": {
"message": "Λήψη στυλ"
},
"linkTranslate": {
"message": "Μετάφραση"
},
"linterConfigTooltip": {
"message": "Πατήστε εδώ για να ρυθμίσετε το linter"
},
"linterIssues": {
"message": "Ζητήματα"
},
"linterJSONError": {
"message": "Μη έγκυρη μορφή JSON"
},
"linterResetMessage": {
"message": "Για αναίρεση μιας κατά λάθος επαναφοράς, πατήστε Ctrl-Z (ή Cmd-Z) στο πλαίσιο κειμένου"
},
"manageFilters": {
"message": "Φίλτρα"
},
"manageHeading": {
"message": "Εγκατεστημένα Στυλ"
},
"manageNewUI": {
"message": "Νέα διαχείριση διάταξης UI"
},
"manageOnlyDisabled": {
"message": "Μόνο απενεργοποιημένα στυλ"
},
"manageOnlyEnabled": {
"message": "Μόνο ενεργοποιημένα στυλ"
},
"manageOnlyExternal": {
"message": "Μόνο στυλ από άλλες ιστοσελίδες"
},
"manageOnlyLocal": {
"message": "Μόνο στυλ δημιουργημένα τοπικά"
},
"manageTitle": {
"message": "Κομψή"
},
"menuShowBadge": {
"message": "Εμφάνιση ενεργους καταμέτρησης στυλ"
},
"noFileToImport": {
"message": "Για να εισάγετε τα στυλ σας, πρέπει πρώτα να τα εξάγετε."
},
"noStylesForSite": {
"message": "Δεν υπάρχουν εγκατεστημένα στυλ για αυτή την ιστοσελίδα."
},
"openManage": {
"message": "Διαχείριση εγκατεστημένων στυλ"
},
"openOptions": {
"message": "Επιλογές"
},
"openStylesManager": {
"message": "Άνοιγμα διαχείρισης στυλ"
},
"optionsActions": {
"message": "Ενέργειες"
},
"optionsAdvanced": {
"message": "Για προχωρημένους"
},
"optionsAdvancedContextDelete": {
"message": "Προσθήκη του 'Delete' στο μενού περιβάλλοντος του προγράμματος επεξεργασίας"
},
"optionsBadgeDisabled": {
"message": "Χρώμα φόντου όταν είναι απενεργοποιημένο"
},
"optionsBadgeNormal": {
"message": "Χρώμα υποβάθρου"
},
"optionsCheck": {
"message": "Ενημέρωση στυλ"
},
"optionsCheckUpdate": {
"message": "Έλεγχος και εγκατάσταση διαθέσιμων ενημερώσεων"
},
"optionsCustomizeBadge": {
"message": "Σήμα στο εικονίδιο της γραμμής εργαλείων"
},
"optionsCustomizePopup": {
"message": "Αναδυόμενο παράθυρο"
},
"optionsCustomizeUpdate": {
"message": "Ενημερώσεις"
},
"optionsHeading": {
"message": "Επιλογές"
},
"optionsIconDark": {
"message": "Σκούρο θέμα φυλλομετρητή"
},
"optionsOpen": {
"message": "Άνοιγμα"
},
"optionsOpenManager": {
"message": "Διαχείριση στυλ"
},
"optionsPopupWidth": {
"message": "Πλάτος αναδυόμενου παραθύρου (σε pixels)"
},
"optionsReset": {
"message": "Επαναφορά ρυθμίσεων στις προεπιλεγμένες"
},
"optionsResetButton": {
"message": "Επαναφορά επιλογών"
},
"optionsSubheading": {
"message": "Περισσότερες επιλογές"
},
"optionsSyncConnect": {
"message": "Σύνδεση"
},
"optionsSyncDisconnect": {
"message": "Αποσύνδεση"
},
"optionsSyncStatusConnected": {
"message": "Συνδεδεμένο"
},
"optionsSyncStatusConnecting": {
"message": "Σύνδεση..."
},
"optionsSyncStatusDisconnected": {
"message": "Αποσυνδέθηκε"
},
"optionsSyncStatusDisconnecting": {
"message": "Αποσύνδεση..."
},
"optionsSyncStatusSyncing": {
"message": "Συγχρονισμός ..."
},
"optionsSyncSyncNow": {
"message": "Συγχρονισμός τώρα"
},
"optionsSyncUrl": {
"message": "διεύθυνση URL"
},
"optionsUpdateInterval": {
"message": "Διάστημα αυτόματης ενημέρωσης των στυλ σε ώρες (0 για απενεργοποίηση)"
},
"paginationNext": {
"message": "Επόμενη σελίδα"
},
"paginationPrevious": {
"message": "Προηγούμενη σελίδα"
},
"popupBordersTooltip": {
"message": "Χρήσιμο για σκούρα θέματα στο καινούριο Chrome, καθώς δε βάφει πλέον τα ακριανά περιθώρια."
},
"popupOpenEditInPopup": {
"message": "Χρήση ενός απλού παραθύρου (χωρίς omnibox)"
},
"popupOpenEditInWindow": {
"message": "Άνοιγμα επεξαργαστή σε νέο παράθυρο"
},
"popupStylesFirst": {
"message": "Στυλ λίστας πριν των εντολών στο μενού του κουμπιού γραμμής εργαλείων"
},
"prefShowBadge": {
"message": "Εμφάνιση αριθμού των στυλ που δραστηριοποιούνται για την τρέχουσα τοποθεσία στην μπάρα εργαλείων"
},
"readingStyles": {
"message": "Ανάγνωση στυλ..."
},
"replace": {
"message": "Αντικατάσταση"
},
"replaceAll": {
"message": "Αντικατάσταση όλων"
},
"replaceWith": {
"message": "Αντικατάσταση με"
},
"retrieveBckp": {
"message": "Εισαγωγή στυλ"
},
"retrieveDropboxSync": {
"message": "Εισαγωγή από το Dropbox"
},
"search": {
"message": "Αναζήτηση"
},
"searchGlobalStyles": {
"message": "Επίσης, αναζητήστε καθολικά στυλ"
},
"searchRegexp": {
"message": "Χρησιμοποιήστε τη σύνταξη /re/ για αναζήτηση με regexp."
},
"searchResultInstallCount": {
"message": "Συνολικός αριθμός εγκαταστάσεων"
},
"searchResultUpdated": {
"message": "Ενημερωμένο"
},
"searchResultWeeklyCount": {
"message": "Εβδομαδιαίος αριθμός εγκαταστάσεων"
},
"searchStylesName": {
"message": "Όνομα"
},
"sectionAdd": {
"message": "Προσθήκη ένος άλλου τμήματος"
},
"sectionCode": {
"message": "Κώδικας"
},
"sectionHelp": {
"message": "Ενότητες σας επιτρέπουν να ορίσετε διαφορετικά κομμάτια του κώδικα για να εφαρμόζονται σε διαφορετικά σύνολα των διευθύνσεων URL στο ίδιο στυλ. Για παράδειγμα, ένα ενιαίο ύφος θα μπορούσε να αλλάξει την αρχική σελίδα ενός ιστότοπου με έναν τρόπο, ενώ αλλάζει το υπόλοιπο μιας τοποθεσίας ένας άλλος τρόπος."
},
"sectionRemove": {
"message": "Αφαίρεση ενότητας"
},
"sections": {
"message": "Ενότητες"
},
"shortcuts": {
"message": "Συντομεύσεις"
},
"sortDateNewestFirst": {
"message": "πιο πρόσφατα πρώτα"
},
"sortDateOldestFirst": {
"message": "πιο παλιά πρώτα"
},
"styleBadRegexp": {
"message": "Το Regexp δεν είναι έγκυρο."
},
"styleBeautify": {
"message": "Ωραιοποίηση"
},
"styleBeautifyIndentConditional": {
"message": "Διόρθωση εσοχής για @media και @supports"
},
"styleBeautifyPreserveNewlines": {
"message": "Διατήρηση νέων γραμμών (newlines)"
},
"styleCancelEditLabel": {
"message": "Πίσω στη διαχείριση"
},
@ -179,12 +648,12 @@
"styleMissingName": {
"message": "Εισάγετε ένα όνομα"
},
"styleRegexpTestNone": {
"message": "Δε βρέθηκαν καρτέλες που αντιστοιχούν."
},
"styleSaveLabel": {
"message": "Αποθήκευση"
},
"styleSectionsTitle": {
"message": "Ενότητες"
},
"styleToMozillaFormatHelp": {
"message": "Η μορφή του Mozilla κώδικα μπορεί να χρησιμοποιηθεί με το Stylish για το Firefox και μπορεί να υποβληθεί στο userstyles.org."
},
@ -199,9 +668,36 @@
"stylusUnavailableForURL": {
"message": "To Stylus δεν λειτουργεί σε σελίδες όπως αυτή."
},
"stylusUnavailableForURLdetails": {
"message": "Ως μέτρο ασφαλείας, ο φυλλομετρητής απαγορεύει στα πρόσθετα να επέμβουν στις built-in σελίδες του (όπως π.χ. chrome://version, η σελίδα νέας καρτέλας από το Chrome 61 και μετά, about:addons, κλπ.), καθώς και τις σελίδες άλλωων προσθέτων. Επιπλέον, κάθε φυλλομετρητής περιορίζει την πρόσβαση στο κατάστημα προσθέτων (όπως το Chrome Web Store ή το AMO)."
},
"syncDropboxStyles": {
"message": "Εξαγωγή από το Dropbox"
},
"syncError": {
"message": "Ο συγχρονισμός απέτυχε"
},
"toggleStyle": {
"message": "Αλλαγή στυλ"
},
"undo": {
"message": "Αναίρεση"
},
"undoGlobal": {
"message": "Αναίρεση όλων των ενεργειών"
},
"unreachableFileHint": {
"message": "Το Stylus έχει πρόσβαση στις file:// διευθύνσεις URL μόνο αν έχετε επιλέξει το αντίστοιχο πλαίσιο ελέγχου για το πρόσθετο Stylus στη σελίδα chrome://extensions."
},
"unzipStyles": {
"message": "Αποσυμπίεση στυλ..."
},
"updateAllCheckSucceededNoUpdate": {
"message": "Όλα τα στυλ είναι ενημερωμένα."
},
"updateAllCheckSucceededSomeEdited": {
"message": "Δεν έχει γίνει έλεγχος ενημερώσεων για κάποια στυλ, για να αποφευχθεί η πιθανότητα απώλειας τοπικών επεξεργασιών. Οι ενημερώσεις μπορούν να εξαναγκαστούν ελέγχοντας το κάθε στυλ ξεχωριστά ή ελέγχοντας πάλι όλα τα στυλ (τοπικές επεξεργασίες θα αντικατασταθούν)"
},
"updateCheckFailBadResponseCode": {
"message": "Αποτυχία ενημέρωσης: ο διακομιστής ανταποκρίθηκε με κωδικό $code$.",
"placeholders": {
@ -213,16 +709,37 @@
"updateCheckFailServerUnreachable": {
"message": "Αποτυχία ενημέρωσης: απρόσιτος διακομιστής."
},
"updateCheckSkippedLocallyEdited": {
"message": "Το στυλ επεξεργάστηκε τοπικά στον υπολογιστή σας."
},
"updateCheckSkippedMaybeLocallyEdited": {
"message": "Το στυλ αυτό μπορεί να έχει επεξεργαστεί τοπικά στον υπολογιστή σας."
},
"updateCheckSucceededNoUpdate": {
"message": "Το στυλ είναι ενημερωμένο."
},
"updateCompleted": {
"message": "Η ενημέρωση ολοκληρώθηκε."
},
"updatesCurrentlyInstalled": {
"message": "Ενημερώσεις που εγκαταστάθηκαν"
},
"uploadingFile": {
"message": "Μεταφόρτωση αρχείου..."
},
"usercssEditorNamePlaceholder": {
"message": "Καθορίστε το @name στον κώδικα"
},
"versionInvalidOlder": {
"message": "Η έκδοση αυτή είναι παλαιότερη από αυτήν που είναι ήδη εγκατεστημένη."
},
"writeStyleFor": {
"message": "Γράψτε νέο στυλ για:"
},
"writeStyleForURL": {
"message": "αυτή την διεύθυνση URL"
},
"zipStyles": {
"message": "Συμπίεση στυλ..."
}
}

File diff suppressed because it is too large Load Diff

View File

@ -26,9 +26,6 @@
"editStyleHeading": {
"message": "Edit style"
},
"installUpdateUnavailable": {
"message": "To enable checking for updates, drop the file on the tab strip or specify @updateURL in the style metadata."
},
"license": {
"message": "Licence"
},
@ -55,14 +52,6 @@
}
}
},
"styleMetaErrorColor": {
"message": "$color$ is not a valid colour",
"placeholders": {
"color": {
"content": "$1"
}
}
},
"styleRegexpPartialExplanation": {
"message": "This style uses partially matching regexps in violation of <a href='https://developer.mozilla.org/docs/Web/CSS/@document'>CSS4 @document specification</a> which requires a full URL match. The affected CSS sections were not applied to the page. This style was probably created in Stylish-for-Chrome, which incorrectly checks 'regexp()' rules since the very first version (known bug)."
},

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -1,4 +1,7 @@
{
"InaccessibleFileHint": {
"message": "Stylus ei saa osadele failitüüpidele ligi pääseda (nt. pdf & json failid)."
},
"addStyleLabel": {
"message": "Kirjuta uus stiil"
},
@ -64,9 +67,6 @@
"backupButtons": {
"message": "Varunda"
},
"backupMessage": {
"message": "Vali fail või lohista see siia lehele."
},
"bckpInstStyles": {
"message": "Ekspordi stiilid"
},
@ -178,6 +178,18 @@
"confirmYes": {
"message": "Jah"
},
"connectingDropbox": {
"message": "Dropboxiga ühendumine..."
},
"connectingDropboxNotAllowed": {
"message": "Dropboxiga ühendumine on saadaval ainult veebipoe kaudu installitud rakendustes"
},
"copied": {
"message": "Kopeeritud lõikelauale"
},
"copy": {
"message": "Kopeeri lõikelauale"
},
"dateInstalled": {
"message": "Paigaldamise kuupäev"
},
@ -208,6 +220,9 @@
"dragDropMessage": {
"message": "Lohista importimiseks oma varundusfail kuskile siia lehele."
},
"dragDropUsercssTabstrip": {
"message": "Faili installimiseks lohista see kaartide ribale (ala, kus näidatakse kaartide pealkirju)."
},
"editDeleteText": {
"message": "Kustuta"
},
@ -228,15 +243,21 @@
}
}
},
"editorStylesButton": {
"message": "Leia redaktori stiile"
},
"enableStyleLabel": {
"message": "Luba"
},
"excludeStyleByDomainLabel": {
"message": "Välista praegune domeen"
},
"excludeStyleByUrlLabel": {
"message": "Välista praegune URL"
},
"exportLabel": {
"message": "Ekspordi"
},
"exportSavedSuccess": {
"message": "Fail edukalt salvestatud"
},
"externalFeedback": {
"message": "Tagasiside"
},
@ -269,15 +290,6 @@
"findStyles": {
"message": "Leia stiile"
},
"findStylesForSite": {
"message": "Leia sellele saidile veel stiile"
},
"findStylesInline": {
"message": "Aknasisene"
},
"findStylesInlineTooltip": {
"message": "Näita otsingu tulemusi selles aknas."
},
"genericAdd": {
"message": "Lisa"
},
@ -314,6 +326,9 @@
"genericUnknown": {
"message": "Tundmatu"
},
"gettingStyles": {
"message": "Kõigi stiilide hankimine..."
},
"helpAlt": {
"message": "Abi"
},
@ -323,6 +338,9 @@
"helpKeyMapHotkey": {
"message": "Vajuta kiirklahvi"
},
"hostDisabled": {
"message": "See host on keelatud hetkel kasutatavas brauseri praeguses versioonis oleva vea tõttu"
},
"importAppendLabel": {
"message": "Lisa stiilile"
},
@ -394,9 +412,6 @@
"installUpdateFromLabel": {
"message": "Kontrolli uuendusi"
},
"installUpdateUnavailable": {
"message": "Uuenduste kontrollimise lubamiseks lohista failid kaartide ribale või määratle stiili metaandmetes @updateURL."
},
"license": {
"message": "Litsents"
},
@ -460,9 +475,6 @@
"liveReloadError": {
"message": "Faili vaatamisel esines viga"
},
"liveReloadInstallHint": {
"message": "Reaalajas uuestilaadimine on lubatud, seega paigaldatud stiili uuendatakse väliste muudatuste korral automaatselt, kuniks see kaart ja lähtefaili kaart mõlemad lahti on."
},
"liveReloadLabel": {
"message": "Reaalajas uuestilaadimine"
},
@ -473,7 +485,7 @@
"message": "Tee halliks"
},
"manageFaviconsHelp": {
"message": "Stylus kasutab välist teenust https://www.google.com/s2/favicons"
"message": "Stylus kasutab välist teenust https://icons.duckduckgo.com"
},
"manageFilters": {
"message": "Filtrid"
@ -509,7 +521,7 @@
"message": "Ainult mitte-Usercss stiilid"
},
"manageOnlyUpdates": {
"message": "Ainult uuenduste ja vigadega"
"message": "Ainult uuenduste või vigadega"
},
"manageOnlyUsercss": {
"message": "Ainult Usercss stiilid"
@ -517,16 +529,121 @@
"menuShowBadge": {
"message": "Kuva aktiivsete stiilide hulka"
},
"meta_invalidCheckboxDefault": {
"message": "Vigane @var märkeruut: väärtus peab olema 0 või 1"
},
"meta_invalidColor": {
"message": "Vigane @var värv: $color$ pole värv",
"placeholders": {
"color": {
"content": "$1"
}
}
},
"meta_invalidRange": {
"message": "Vigane @var $type$: väärtus peab olema arv või massiiv",
"placeholders": {
"type": {
"content": "$1"
}
}
},
"meta_invalidRangeMax": {
"message": "Vigane @var $type$: vaikimisi väärtus on maksimumist suurem",
"placeholders": {
"type": {
"content": "$1"
}
}
},
"meta_invalidRangeMin": {
"message": "Vigane @var $type$: vaikimisi väärtus on miinimumist madalam",
"placeholders": {
"type": {
"content": "$1"
}
}
},
"meta_invalidRangeMultipleUnits": {
"message": "Vigane @var $type$: määratletud on mitu ühikut",
"placeholders": {
"type": {
"content": "$1"
}
}
},
"meta_invalidRangeTooManyValues": {
"message": "Vigane @var $type$: massiiv sisaldab liiga palju elemente",
"placeholders": {
"type": {
"content": "$1"
}
}
},
"meta_invalidRangeUnits": {
"message": "Vigane @var $type$: '$units$' pole lubatud ühik",
"placeholders": {
"type": {
"content": "$1"
},
"units": {
"content": "$2"
}
}
},
"meta_invalidURLProtocol": {
"message": "Vigane URL protokoll. Lubatud on ainult http ja https: $protocol$",
"placeholders": {
"protocol": {
"content": "$1"
}
}
},
"meta_missingMandatory": {
"message": "Kohustuslikud metaandmed puudu: $keys$",
"placeholders": {
"keys": {
"content": "$1"
}
}
},
"meta_unknownMeta": {
"message": "Tundmatud metaandmed: $key$",
"placeholders": {
"key": {
"content": "$1"
}
}
},
"meta_unknownPreprocessor": {
"message": "Tundmatu @preprocessor: $preprocessor$",
"placeholders": {
"preprocessor": {
"content": "$1"
}
}
},
"meta_unknownVarType": {
"message": "Tundmatu @$varkey$ tüüp: $vartype$",
"placeholders": {
"varkey": {
"content": "$1"
},
"vartype": {
"content": "$2"
}
}
},
"noFileToImport": {
"message": "Oma stiilide importimiseks peaksid esmalt selle eksportima."
},
"noStylesForSite": {
"message": "Sellele saidile pole stiile paigaldatud."
},
"openManage": {
"message": "Halda"
},
"openOptionsManage": {
"message": "Valikute liides"
},
"openOptionsPopup": {
"openOptions": {
"message": "Valikud"
},
"openStylesManager": {
@ -545,7 +662,7 @@
"message": "Paljasta iframe-id HTML[stylus-iframe] kaudu"
},
"optionsAdvancedExposeIframesNote": {
"message": "Paljastab tipp-domeeni igas iframe'is.\nVõimaldab iframe'i-põhise CSS-koodi kirjutamise näiteks selliselt:\nhtml[stylus-iframe$=\"twitter.com\"] h1 { display:none }"
"message": "Paljastab tipp-domeeni igas iframe'is.\nVõimaldab iframe'i-põhise CSS-koodi kirjutamise näiteks selliselt:\nhtml[stylus-iframe$$=\"twitter.com\"] h1 { display:none }"
},
"optionsAdvancedNewStyleAsUsercss": {
"message": "Kirjuta uus stiil usercss-ina"
@ -571,6 +688,9 @@
"optionsCustomizePopup": {
"message": "Hüpikaken"
},
"optionsCustomizeSync": {
"message": "Sünkrooni pilve"
},
"optionsCustomizeUpdate": {
"message": "Uuendused"
},
@ -601,12 +721,36 @@
"optionsSubheading": {
"message": "Rohkem valikuid"
},
"optionsSyncNone": {
"message": "Pole"
},
"optionsSyncStatusConnected": {
"message": "Ühendatud"
},
"optionsSyncStatusConnecting": {
"message": "Ühendumine..."
},
"optionsSyncStatusDisconnected": {
"message": "Lahti ühendatud"
},
"optionsSyncStatusDisconnecting": {
"message": "Lahtiühendamine..."
},
"optionsSyncStatusSyncing": {
"message": "Sünkroonimine..."
},
"optionsSyncSyncNow": {
"message": "Sünkrooni kohe"
},
"optionsUpdateImportNote": {
"message": "Kui impordid stiilide varundusi vanast versioonist või Stylish'ist, tee ühekordne käsitsi uuenduste kontroll stiilide halduris, et veenduda kõikide stiilide ajakohasuses."
},
"optionsUpdateInterval": {
"message": "Kasutajastiilide automaatse uuendamise ajavahe (keelamiseks pane 0)"
},
"overwriteFileExport": {
"message": "Kas tahad olemasoleva faili üle kirjutada?"
},
"paginationCurrent": {
"message": "Praegune leht"
},
@ -640,6 +784,9 @@
"popupManageTooltip": {
"message": "Shift+klõps või paremklõps avab halduri praeguse saidi kohta käivate stiilidega"
},
"popupMenuButtonTooltip": {
"message": "Tegevuste menüü"
},
"popupOpenEditInWindow": {
"message": "Ava redaktor uues aknas"
},
@ -658,6 +805,9 @@
"previewTooltip": {
"message": "Ajutiselt rakendab muudatused ilma salvestamata.\nMuudatuste püsivaks tegemiseks salvesta stiil."
},
"readingStyles": {
"message": "Stiilide lugemine..."
},
"replace": {
"message": "Asenda"
},
@ -670,6 +820,9 @@
"retrieveBckp": {
"message": "Impordi stiilid"
},
"retrieveDropboxSync": {
"message": "Dropboxi importimine"
},
"search": {
"message": "Otsi"
},
@ -700,27 +853,21 @@
"searchResultWeeklyCount": {
"message": "Paigaldus nädalas"
},
"searchStyles": {
"message": "Otsi sisu"
},
"searchStylesHelp": {
"message": "</> klahv fokuseerib otsinguvälja.\nLihttekst: otsi nimest, koodist, kodulehe URList ja rakendatavatest saitidest. Sõnu alla 3 tähemärgi ignoreeritakse.\nVastav URL: kasuta otsingus eesliidet <url:>, nt <url:https://github.com/openstyles/stylus>\nRegulaaravaldised sisaldavad kaldkriipse ja lippe, nt </body.*?\\ba\\b/simguy>\nTäpsed sõnad: kirjuta väljendi ümber jutumärgid, nt <\".header ~ div\">"
},
"sectionAdd": {
"message": "Lisa uus jaotis"
},
"sectionCode": {
"message": "Kood"
},
"sectionHelp": {
"message": "Jaotised võimaldavad sul määrata erinevaid koodiosi, mida rakendada mitmetele URLide kogumikele samas stiilis. Näiteks võib üks stiil muuta saidi kodulehte ühtemoodi, seejuures muutes ülejäänud saiti teistmoodi."
},
"sectionRemove": {
"message": "Eemalda jaotis"
},
"sectionRestore": {
"message": "Taasta eemaldatud jaotis"
},
"sections": {
"message": "Jaotised"
},
"shortcuts": {
"message": "Otseteed"
},
@ -805,36 +952,6 @@
}
}
},
"styleMetaErrorCheckbox": {
"message": "Vigane @var märkeruut: väärtus peab olema 0 või 1"
},
"styleMetaErrorColor": {
"message": "$color$ ei ole sobiv värv",
"placeholders": {
"color": {
"content": "$1"
}
}
},
"styleMetaErrorPreprocessor": {
"message": "Mittetoetatud eeltöötleja: $preprocessor$",
"placeholders": {
"preprocessor": {
"content": "$1"
}
}
},
"styleMetaErrorSelectValueMismatch": {
"message": "Vigane @select: väärtust pole loendis olemas"
},
"styleMissingMeta": {
"message": "@$key$ metaandmed puuduvad",
"placeholders": {
"key": {
"content": "$1"
}
}
},
"styleMissingName": {
"message": "Sisesta nimi"
},
@ -853,9 +970,6 @@
"styleRegexpProblemTooltip": {
"message": "\"regexp()\" vale kasutuse tõttu rakendamata jaotiste arv"
},
"styleRegexpTestButton": {
"message": "Regulaaravaldise katsetus"
},
"styleRegexpTestFull": {
"message": "Vastavad kaardid"
},
@ -877,9 +991,6 @@
"styleSaveLabel": {
"message": "Salvesta"
},
"styleSectionsTitle": {
"message": "Jaotised"
},
"styleToMozillaFormatHelp": {
"message": "Koodi Mozilla-vormingut saab üles laadida saidile userstyles.org ja kasutada klassikalise Firefoxi Stylish'iga"
},
@ -903,6 +1014,12 @@
"stylusUnavailableForURLdetails": {
"message": "Turvakaalutlustel keelab brauser laiendustel sisseehitatud lehtede (nagu chrome://version, standardne uue kaardi leht alates versioonist Chrome 61, about:addons jne) ning ka teiste laienduste lehtede muutmise. Iga brauser piirab lisaks juurdepääsu oma laienduste galeriile (nagu Chrome'i veebipood või AMO)."
},
"syncDropboxDeprecated": {
"message": "Dropboxi importimine/eksportimine on asendatud edasijõudnuma stiilide sünkroonijaga valikute lehel."
},
"syncDropboxStyles": {
"message": "Dropboxi eksportimine"
},
"syncStorageErrorSaving": {
"message": "Väärtust ei saa salvestada. Proovi teksti hulka vähendada."
},
@ -921,18 +1038,18 @@
"unreachableAMOHint": {
"message": "Ligipääsu lubamiseks ava <about:config>, parem-klõpsa nimekirjal, vali 'Uus', seejärel 'Tõeväärtus', kleebi <privacy.resistFingerprinting.block_mozAddonManager> ja klõpsa Sobib, <true>, Sobib, laadi leht <addons.mozilla.org> uuesti."
},
"unreachableAMOHintNewFF": {
"message": "Firefox 60-s ja uuemas pead ka lehel <about:config> eelistusest <extensions.webextensions.restrictedDomains> eemaldama AMO domeeni."
},
"unreachableAMOHintOldFF": {
"message": "Ainult Firefox 59 ja uuem on võimalik seadistada lubama WebExtensionsil lisada stiilielemente CSP-kaitstud saitidele nagu see."
},
"unreachableContentScript": {
"message": "Lehega ei saanud ühendust. Proovi kaart uuesti laadida."
},
"unreachableFileHint": {
"message": "Stylus saab ligi pääseda file:// URLidele ainult siis, kui märgistad vastava kasti Stylus laiendusel chrome://extensions lehel"
},
"unreachableMozSiteHintOldFF": {
"message": "Ainult Firefox 59 ja uuem on võimalik seadistada lubama WebExtensionsil lisada stiilielemente CSP-kaitstud saitidele nagu see."
},
"unzipStyles": {
"message": "Stiilide lahtipakkimine..."
},
"updateAllCheckSucceededNoUpdate": {
"message": "Uuendusi ei leitud."
},
@ -974,6 +1091,9 @@
"updatesCurrentlyInstalled": {
"message": "Uuendused paigaldatud:"
},
"uploadingFile": {
"message": "Faili üleslaadimine..."
},
"usercssAvoidOverwriting": {
"message": "Olemasoleva stiili ülekirjutamise vältimiseks palun muuda @name või @namespace väärtused."
},
@ -986,9 +1106,6 @@
"usercssReplaceTemplateConfirmation": {
"message": "Asendad vaikimisi malli uutes kasutajacss stiilides praeguse koodiga?"
},
"usercssReplaceTemplateName": {
"message": "Tühi @name asendab vaikimisi malli"
},
"usercssReplaceTemplateSectionBody": {
"message": "Sisesta kood siia..."
},
@ -1000,5 +1117,8 @@
},
"writeStyleForURL": {
"message": "see URL"
},
"zipStyles": {
"message": "Stiilide kokkupakkimine..."
}
}

View File

@ -52,6 +52,12 @@
"checkingForUpdate": {
"message": "Tarkistetaan..."
},
"confirmDelete": {
"message": "Poista"
},
"confirmSave": {
"message": "Tallenna"
},
"deleteStyleConfirm": {
"message": "Oletko varma että haluat poistaa tämän tyylin?"
},
@ -64,6 +70,9 @@
"disableStyleLabel": {
"message": "Poista Käytöstä"
},
"editDeleteText": {
"message": "Poista"
},
"editStyleHeading": {
"message": "Muokkaa Tyyliä"
},
@ -81,8 +90,11 @@
"enableStyleLabel": {
"message": "Aktivoi"
},
"findStylesForSite": {
"message": "Hae lisää tyylejä tälle sivustolle"
"genericAdd": {
"message": "Lisää"
},
"genericEnabledLabel": {
"message": "Aktivoitu"
},
"helpAlt": {
"message": "Apu"
@ -114,12 +126,12 @@
"sectionCode": {
"message": "Koodi"
},
"sectionHelp": {
"message": "Osiot antavat sinun tarkentaa koodin eri osia niin että ne koskevat eri URL osoitteita samassa tyylissä. Esimerkiksi, yksi tyyli voi muokata kotisivua yhdellä tavalla kun se muokkaa koko muuta sivustoa toisella tavalla."
},
"sectionRemove": {
"message": "Poista osio"
},
"sections": {
"message": "Osiot"
},
"styleBadRegexp": {
"message": "Regexp ei kelpaa."
},
@ -146,9 +158,6 @@
"styleSaveLabel": {
"message": "Tallenna"
},
"styleSectionsTitle": {
"message": "Osiot"
},
"styleToMozillaFormatHelp": {
"message": "Mozilla formaattia koodista voidaan käyttää Stylish Firefoxille ohjelmassa ja voidaan lähettää userstyles.orgiin."
},

View File

@ -1,4 +1,7 @@
{
"InaccessibleFileHint": {
"message": "Stylus ne peut pas accéder à certains types de fichiers (ex: fichiers .pdf et .json)."
},
"addStyleLabel": {
"message": "Créer un nouveau style"
},
@ -65,19 +68,19 @@
"message": "Sauvegarde"
},
"backupMessage": {
"message": "Sélectionner un fichier ou le glisser-déposer sur cette page"
"message": "Pour importer le fichier de sauvegarder, glissez-déposez-le sur dans page ou cliquez sur le bouton Importer.\n\nPour export une sauvegarde compatible avec une version de Stylus plus ancienne que 1.5.18, effectuez un clic droit ou un appuie sur la touche Maj + clic sur le bouton Exporter."
},
"bckpInstStyles": {
"message": "Exporter des styles"
},
"checkAllUpdates": {
"message": "Rechercher des mises à jour pour tous les styles"
"message": "Rechercher des mises à jour"
},
"checkAllUpdatesForce": {
"message": "Vérifiez à nouveau, je nai modifié aucun style !"
},
"checkForUpdate": {
"message": "Rechercher des mises à jour"
"message": "Rechercher une mise à jour"
},
"checkingForUpdate": {
"message": "Vérification en cours…"
@ -184,6 +187,40 @@
"confirmYes": {
"message": "Oui"
},
"connectingDropbox": {
"message": "Connexion à Dropbox…"
},
"connectingDropboxNotAllowed": {
"message": "La connexion à Dropbox est disponible uniquement dans les applications installées directement depuis le Web Store."
},
"copied": {
"message": "Copié dans le presse-papier"
},
"copy": {
"message": "Copier dans le presse-papier"
},
"customNameHint": {
"message": "Entrez un nom personnalisé ici pour renommer le style dans l'IU sans casser les mises à jour"
},
"customNameResetHint": {
"message": "Arrête d'utiliser le nom personnalisé, change vers le propre nom du style"
},
"dateAbbrDay": {
"message": "$value$j",
"placeholders": {
"value": {
"content": "$1"
}
}
},
"dateAbbrYear": {
"message": "$value$a",
"placeholders": {
"value": {
"content": "$1"
}
}
},
"dateInstalled": {
"message": "Date d'installation"
},
@ -208,12 +245,29 @@
"disableAllStyles": {
"message": "Désactiver tous les styles"
},
"disableAllStylesOff": {
"message": "Les styles sont désactivés"
},
"disableStyleLabel": {
"message": "Désactiver"
},
"draftAction": {
"message": "Choisissez « Oui » pour charger ce brouillon ou « Non » pour l'abandonner."
},
"draftTitle": {
"message": "Sauvegarde du brouillon, créée $date$",
"placeholders": {
"date": {
"content": "$1"
}
}
},
"dragDropMessage": {
"message": "Glisser votre fichier de sauvegarde nimporte où sur cette page pour limporter."
},
"dragDropUsercssTabstrip": {
"message": "Pour installer le fichier, glisser le fichier sur la barre des onglets (la zone où les titres des onglets sont affichés)."
},
"editDeleteText": {
"message": "Supprimer"
},
@ -234,15 +288,27 @@
}
}
},
"editorStylesButton": {
"message": "Trouver des styles pour léditeur"
"editorSettings": {
"message": "Paramètres de léditeur"
},
"enableStyleLabel": {
"message": "Activer"
},
"excludeStyleByDomainLabel": {
"message": "Exclure le domaine actuel"
},
"excludeStyleByUrlLabel": {
"message": "Exclure lURL actuelle"
},
"exportCompatible": {
"message": "Export (mode de compatibilité)"
},
"exportLabel": {
"message": "Exporter"
},
"exportSavedSuccess": {
"message": "Fichier sauvegardé avec succès"
},
"externalFeedback": {
"message": "Commentaires"
},
@ -272,15 +338,6 @@
"findStyles": {
"message": "Trouver des styles"
},
"findStylesForSite": {
"message": "Rechercher d'autres styles pour ce site"
},
"findStylesInline": {
"message": "Dans la popup"
},
"findStylesInlineTooltip": {
"message": "Affiche les résultats de recherche dans cette fenêtre"
},
"genericAdd": {
"message": "Ajouter"
},
@ -317,6 +374,12 @@
"genericUnknown": {
"message": "Inconnu(e)"
},
"gettingStyles": {
"message": "Récupération de tous les styles…"
},
"headerResizerHint": {
"message": "Maintenir la touche Maj pour redimensionner uniquement dans ce type d'interface, par exemple, éditeur, gestionnaire, installateur"
},
"helpAlt": {
"message": "Aide"
},
@ -326,6 +389,9 @@
"helpKeyMapHotkey": {
"message": "Pressez un raccourci clavier"
},
"hostDisabled": {
"message": "Cet hôte a été désactivé en raison d'un bogue dans la version actuelle du navigateur utilisé"
},
"importAppendLabel": {
"message": "Ajouter au style"
},
@ -335,6 +401,12 @@
"importLabel": {
"message": "Importer"
},
"importPreprocessor": {
"message": "Les styles avec un <code>@preprocessor</code> ne fonctionnera pas en mode classique. Vous pouvez changer l'éditeur en mode Usercss : 1) Ouvrez le gestionnaire de styles, 2) Activer l'option \"en tant que Usercss\", 3) Cliquez \"écrire un nouveau style\"\n\nImporter de toute façon ?"
},
"importPreprocessorTitle": {
"message": "Problème potentiel à cause de @preprocessor"
},
"importReplaceLabel": {
"message": "Remplacer le style"
},
@ -397,18 +469,24 @@
"installUpdateFromLabel": {
"message": "Rechercher les mises à jour"
},
"installUpdateUnavailable": {
"message": "Pour activer la vérification des mises à jour, glissez le fichier sur la barre des onglets ou spécifiez @updateURL dans les métadonnées du style."
},
"license": {
"message": "Licence"
},
"linkGetHelp": {
"message": "Consulter l'aide"
},
"linkGetShareStyles": {
"message": "Obtenir et partager les styles"
},
"linkGetShareStylesInfo": {
"message": "Le nouveau userstyles.world géré par la communauté est créé par les auteurs d'userstyles dans le but de remplacer userstyles.org, qui a été tellement lent et inconscient durant l'année dernière que beaucoup d'auteurs ont arrêté de mettre à jour leurs styles."
},
"linkGetStyles": {
"message": "Obtenir des styles"
},
"linkGetStylesInfo": {
"message": "Ce site d'archive à été créé par un membre de la communauté userstyle pour archiver le lent et inconscient site userstyles.org. Cette archive est mise à jour approximativement chaque jour."
},
"linkTranslate": {
"message": "Traduire"
},
@ -461,14 +539,14 @@
"message": "Une erreur est survenue durant la surveillance du fichier"
},
"liveReloadInstallHint": {
"message": "Le rechargement automatique est activé, donc le style installé sera mis à jour automatiquement après une modification externe quand à la fois cet onglet et longlet du fichier source sont ouverts."
"message": "Gardez cet onglet ouvert pour mettre à jour le style automatiquement basé sur les modifications extérieures."
},
"liveReloadInstallHintFF": {
"message": "Gardez cet onglet ouvert ainsi que l´onglet original pour mettre à jour le style automatiquement basé sur les modifications extérieures."
},
"liveReloadLabel": {
"message": "Rechargement immédiat"
},
"liveReloadUnavailable": {
"message": "Pour activer le rechargement automatique, glisser le fichier sur la barre des onglets (la zone où les titres des onglets sont affichés)."
},
"manageFavicons": {
"message": "Favicons dans la colonne « sapplique à »"
},
@ -476,7 +554,7 @@
"message": "Grisés"
},
"manageFaviconsHelp": {
"message": "Stylus utilise le service externe https://www.google.com/s2/favicons"
"message": "Stylus utilise le service externe https://icons.duckduckgo.com"
},
"manageFilters": {
"message": "Filtres"
@ -509,35 +587,236 @@
"message": "(les styles non installés via une page userstyles.org)"
},
"manageOnlyNonUsercss": {
"message": "Uniquement les styles non Usercss"
"message": "Styles non Usercss uniquement"
},
"manageOnlyUpdates": {
"message": "Uniquement avec mises à jour ou problèmes"
"message": "Avec mises à jour ou problèmes uniquement"
},
"manageOnlyUsercss": {
"message": "Uniquement les styles Usercss"
"message": "Styles Usercss uniquement"
},
"menuShowBadge": {
"message": "Montrer le nombre de styles actifs"
},
"meta_invalidCheckboxDefault": {
"message": "Case à cocher @var invalide : la valeur doit être 0 ou 1"
},
"meta_invalidColor": {
"message": "Couleur @var invalide : $color$ nest pas une couleur",
"placeholders": {
"color": {
"content": "$1"
}
}
},
"meta_invalidNumber": {
"message": "Nombre attendu"
},
"meta_invalidRange": {
"message": "@var $type$ invalide : la valeur doit être un nombre ou un tableau",
"placeholders": {
"type": {
"content": "$1"
}
}
},
"meta_invalidRangeDefault": {
"message": "@var $type$ invalide : la valeur par défaut est null",
"placeholders": {
"type": {
"content": "$1"
}
}
},
"meta_invalidRangeMax": {
"message": "@var $type$ invalide : la valeur par défaut est au-dessus du maximum",
"placeholders": {
"type": {
"content": "$1"
}
}
},
"meta_invalidRangeMin": {
"message": "@var $type$ invalide : la valeur par défaut est en dessous du minimum",
"placeholders": {
"type": {
"content": "$1"
}
}
},
"meta_invalidRangeMultipleUnits": {
"message": "@var $type$ invalide : plusieurs unités sont définies",
"placeholders": {
"type": {
"content": "$1"
}
}
},
"meta_invalidRangeStep": {
"message": "@var $type$ invalide : la valeur par défaut nest pas un multiple du pas",
"placeholders": {
"type": {
"content": "$1"
}
}
},
"meta_invalidRangeTooManyValues": {
"message": "@var $type$ invalide : le tableau contient trop ditems",
"placeholders": {
"type": {
"content": "$1"
}
}
},
"meta_invalidRangeUnits": {
"message": "@var $type$ invalide : '$units$' nest pas une unité valide",
"placeholders": {
"type": {
"content": "$1"
},
"units": {
"content": "$2"
}
}
},
"meta_invalidRangeValue": {
"message": "@var $type$ invalide : les items dans le tableau doivent être des nombres, chaines de caractères ou null",
"placeholders": {
"type": {
"content": "$1"
}
}
},
"meta_invalidSelect": {
"message": "@var select invalide : la valeur par défaut doit être un tableau ou un objet"
},
"meta_invalidSelectEmptyOptions": {
"message": "@var select invalide : la liste doptions est vide"
},
"meta_invalidSelectLabel": {
"message": "@var select invalide : le label de loption est vide"
},
"meta_invalidSelectMultipleDefaults": {
"message": "@var select invalide : plusieurs options par défaut sont définies"
},
"meta_invalidSelectNameDuplicated": {
"message": "@var select invalide : nom doption dupliqué"
},
"meta_invalidSelectValue": {
"message": "@var select invalide : les valeurs dans le tableau/objet doivent être des chaines de caractères"
},
"meta_invalidSelectValueMismatch": {
"message": "@var select invalide : la valeur nexiste pas dans la liste doptions"
},
"meta_invalidString": {
"message": "Chaine entre guillemets attendue"
},
"meta_invalidURLProtocol": {
"message": "Protocole de lURL invalide. Seuls http et https sont autorisés : $protocol$",
"placeholders": {
"protocol": {
"content": "$1"
}
}
},
"meta_invalidVersion": {
"message": "Numéro de version invalide"
},
"meta_invalidWord": {
"message": "Mot attendu"
},
"meta_missingChar": {
"message": "Caractères attendus : $chars$",
"placeholders": {
"chars": {
"content": "$1"
}
}
},
"meta_missingEOT": {
"message": "Données EOT attendues"
},
"meta_missingMandatory": {
"message": "Métadonnée obligatoire manquante: $keys$",
"placeholders": {
"keys": {
"content": "$1"
}
}
},
"meta_unknownJSONLiteral": {
"message": "JSON invalide : $literal$ nest pas une valeur JSON valide",
"placeholders": {
"literal": {
"content": "$1"
}
}
},
"meta_unknownMeta": {
"message": "Métadonnée inconnue: $key$",
"placeholders": {
"key": {
"content": "$1"
}
}
},
"meta_unknownMetaTypo": {
"message": "Peut-être @$keyOk$? Métadonnée inconnue: @$keyErr$",
"placeholders": {
"keyErr": {
"content": "$1"
},
"keyOk": {
"content": "$2"
}
}
},
"meta_unknownPreprocessor": {
"message": "@preprocessor inconnu: $preprocessor$",
"placeholders": {
"preprocessor": {
"content": "$1"
}
}
},
"meta_unknownVarType": {
"message": "Type @$varkey$ inconnu : $vartype$",
"placeholders": {
"varkey": {
"content": "$1"
},
"vartype": {
"content": "$2"
}
}
},
"noFileToImport": {
"message": "Pour importer vos styles, vous devez dabord les exporter."
},
"noStylesForSite": {
"message": "Aucun style n'est installé pour ce site."
},
"numberedLine": {
"message": "Ligne :"
},
"openManage": {
"message": "Gestion"
},
"openOptionsManage": {
"message": "Paramètres d'interface graphique"
},
"openOptionsPopup": {
"message": "Paramètres"
},
"openStylesManager": {
"message": "Ouvrir le gestionnaire de styles"
},
"optionsAdvanced": {
"message": "Avancé"
},
"optionsAdvancedAutoSwitchSchemeBySystem": {
"message": "Par préférence système"
},
"optionsAdvancedAutoSwitchSchemeByTime": {
"message": "Par horaire nocturne :"
},
"optionsAdvancedAutoSwitchSchemeNever": {
"message": "Désactivé. Le paramètre clair/obscur dans les styles est ignoré."
},
"optionsAdvancedContextDelete": {
"message": "Ajouter « Supprimer » dans le menu contextuel de lextension"
},
@ -545,11 +824,26 @@
"message": "Exposer les iframes via HTML[stylus-iframe]"
},
"optionsAdvancedExposeIframesNote": {
"message": "Expose le domaine principal de chaque iframe.\nPermet décrire du CSS spécifique à une iframe tel que :\nhtml[stylus-iframe$=\"twitter.com\"] h1 { display:none }"
"message": "Expose le domaine principal dans chaque iframe.\nPermet décrire du CSS spécifique aux iframe tel que :\nhtml[stylus-iframe$$=\"twitter.com\"] h1 { display:none }"
},
"optionsAdvancedExposeStyleName": {
"message": "Afficher le nom du style"
},
"optionsAdvancedExposeStyleNameNote": {
"message": "Afficher le nom du style dans la page pour faciliter le débogage des styles dans les outils de développement. Veuillez recharger le ou les onglets pour appliquer ce nouveau paramètre."
},
"optionsAdvancedNewStyleAsUsercss": {
"message": "Écrire un nouveau style en tant que usercss"
},
"optionsAdvancedPatchCsp": {
"message": "Corrige <code>CSP</code> pour permettre les ressources de style"
},
"optionsAdvancedPatchCspNote": {
"message": "Activez cela si les styles comportent des images ou des polices qui échoue à charger sur les sites avec une politique de sécurité de contenus stricte <code>CSP</code> (<code>Content-Security-Policy</code>).\n\nActiver cette option permettra d'alléger les restrictions du <code>CSP</code>, permettant les styles essentiels de charger. Cette option n'est entendue que pour les utilisateurs expérimentés qui comprennent les potentielles implications de sécurité que cela introduit, et accepte la responsabilité de surveiller le contenu qu'ils autorisent. Veuillez lire les attaques basé sur le CSS pour plus dinformation.\n\nAussi sachez, que cette option en particulier n'est pas garanti de fonctionner si une autre extension modifie la réponse réseau avant."
},
"optionsAdvancedStyleViaXhr": {
"message": "Mode d'injection instantanée"
},
"optionsBadgeDisabled": {
"message": "Couleur d'arrière plan si désactivé"
},
@ -568,6 +862,9 @@
"optionsCustomizeIcon": {
"message": "Icône de la barre doutils"
},
"optionsCustomizeSync": {
"message": "Synchroniser dans le nuage"
},
"optionsCustomizeUpdate": {
"message": "Mises à jour"
},
@ -595,12 +892,76 @@
"optionsSubheading": {
"message": "Plus de paramètres"
},
"optionsSyncConnect": {
"message": "Connecter"
},
"optionsSyncDisconnect": {
"message": "Déconnecter"
},
"optionsSyncLogin": {
"message": "Se connecter"
},
"optionsSyncNone": {
"message": "Aucun"
},
"optionsSyncPassword": {
"message": "Mot de passe"
},
"optionsSyncStatusConnected": {
"message": "Connecté"
},
"optionsSyncStatusConnecting": {
"message": "Connection..."
},
"optionsSyncStatusDisconnected": {
"message": "Déconnecté"
},
"optionsSyncStatusDisconnecting": {
"message": "Déconnexion..."
},
"optionsSyncStatusPull": {
"message": "Récupération du style $loaded$de $total$",
"placeholders": {
"loaded": {
"content": "$1"
},
"total": {
"content": "$2"
}
}
},
"optionsSyncStatusPush": {
"message": "Application du style $loaded$ de $total$",
"placeholders": {
"loaded": {
"content": "$1"
},
"total": {
"content": "$2"
}
}
},
"optionsSyncStatusRelogin": {
"message": "Session expirée, veuillez vous reconnecter à nouveau."
},
"optionsSyncStatusSyncing": {
"message": "Synchronisation..."
},
"optionsSyncSyncNow": {
"message": "Synchroniser maintenant"
},
"optionsSyncUsername": {
"message": "Nom dutilisateur"
},
"optionsUpdateImportNote": {
"message": "Quand vous importez des sauvegardes de style dune ancienne version ou de Stylish, faites une vérification manuellement pour vous assurez que tous les styles sont à jour."
},
"optionsUpdateInterval": {
"message": "Intervalle de mise à jour automatique des styles utilisateur en heures (spécifier 0 pour désactiver)"
},
"overwriteFileExport": {
"message": "Voulez-vous écraser un fichier existant ?"
},
"paginationCurrent": {
"message": "Page courante"
},
@ -619,6 +980,9 @@
"parseUsercssError": {
"message": "Stylus a échoué à parser le usercss :"
},
"popupAutoResort": {
"message": "Retrier les styles dans la popup à chaque (dés)activation"
},
"popupBorders": {
"message": "Ajouter des bordures blanches sur les côtés"
},
@ -634,6 +998,12 @@
"popupManageTooltip": {
"message": "Maj-clic ou clic droit ouvre le gestionnaire avec les styles applicables au site courant."
},
"popupMenuButtonTooltip": {
"message": "Actions"
},
"popupOpenEditInPopup": {
"message": "Utiliser une simple fenêtre (pas d'omnibox)"
},
"popupOpenEditInWindow": {
"message": "Ouvrir l'éditeur dans une nouvelle fenêtre"
},
@ -646,12 +1016,42 @@
"prefShowBadge": {
"message": "Afficher le nombre de styles actifs pour le site actuel sur le boutton Stylus"
},
"preferSchemeDark": {
"message": "Obscur"
},
"preferSchemeLight": {
"message": "Clair"
},
"preferSchemeNone": {
"message": "Aucun (quand appliqué)"
},
"previewLabel": {
"message": "Prévisualiser en direct"
},
"previewTooltip": {
"message": "Applique temporairement les changements sans sauvegarder.\nSauvegarde le style pour rendre les changements permanents."
},
"publish": {
"message": "Publier"
},
"publishPush": {
"message": "Pousser la mise à jour"
},
"publishReconnect": {
"message": "Essayez de vous déconnecter et de publier à nouveau"
},
"publishStyle": {
"message": "Publier le style"
},
"publishUsw": {
"message": "Utiliser <userstyles.world>"
},
"readingStyles": {
"message": "Lecture des styles…"
},
"reload": {
"message": "Recharger"
},
"replace": {
"message": "Remplacer"
},
@ -664,12 +1064,21 @@
"retrieveBckp": {
"message": "Importer des styles"
},
"retrieveDropboxSync": {
"message": "Importer depuis Dropbox"
},
"saveAsTemplate": {
"message": "Sauvegarder comme modèle"
},
"search": {
"message": "Rechercher"
},
"searchCaseSensitive": {
"message": "Sensible à la casse"
},
"searchGlobalStyles": {
"message": "Chercher aussi des styles globaux."
},
"searchNumberOfResults": {
"message": "Nombre de correspondances"
},
@ -685,6 +1094,12 @@
"searchResultNoneFound": {
"message": "Aucun style trouvé pour ce site"
},
"searchResultNotMatching": {
"message": "Ce style est installé mais il ne s'applique pas au lien du site actuel."
},
"searchResultNotMatchingNote": {
"message": "Essaye de demander lauteur pour ajouter le lien l'URL.\n\nVous pouvez aussi ouvrir le style dans le gestionnaire et l'éditer soit-même,\nmais soyez avertis que cela désactive les mises à jour automatiques pour ce style."
},
"searchResultRating": {
"message": "Note"
},
@ -694,24 +1109,33 @@
"searchResultWeeklyCount": {
"message": "Installations hebdomadaires"
},
"searchStyles": {
"message": "Rechercher dans le contenu"
"searchStylesAll": {
"message": "Tout"
},
"searchStylesHelp": {
"message": "La touche </> place le curseur dans le champ de recherche.\nText brut : cherche parmi le nom, code, URL de la page daccueil et les sites auquel il sapplique. Les mots avec moins de 3 lettres sont ignorés.\nStyles correspondant à une URL complète : préfixez la recherche avec <url:>, par ex. <url:https://github.com/openstyles/stylus>\nExpressions rationnelles : incluez les slash et les chevrons, par ex. </body.*?\\ba\\b/simguy>\nMots exacts : entourez la requête de guillemets droits, par ex. <\".header ~ div\">"
"searchStylesCode": {
"message": "Code CSS"
},
"searchStylesMatchUrl": {
"message": "Par lien"
},
"searchStylesMeta": {
"message": "Meta-données"
},
"searchStylesName": {
"message": "Nom"
},
"sectionAdd": {
"message": "Ajouter une section"
},
"sectionHelp": {
"message": "Les sections vous permettent de définir différentes portions de code correspondant à un même style que vous pouvez appliquer à des ensembles d'URL distincts. Par exemple, un même style appliqué à la page d'accueil peut modifier celle-ci d'une certaine manière et modifier le reste du site Web d'une autre manière."
},
"sectionRemove": {
"message": "Supprimer la section"
},
"sectionRestore": {
"message": "Restaurer la section supprimée"
},
"settings": {
"message": "Paramètres"
},
"shortcuts": {
"message": "Raccourcis"
},
@ -745,6 +1169,9 @@
"styleBeautify": {
"message": "Embellir "
},
"styleBeautifyHint": {
"message": "Astuce : Cliquez-droit le bouton \"Beautify\" ou utilisez le raccourci clavier définit ci-dessous pour beautify sans montrer le panneau."
},
"styleBeautifyIndentConditional": {
"message": "Indenter @media, @supports"
},
@ -760,6 +1187,9 @@
"styleEnabledLabel": {
"message": "Activé"
},
"styleExcludeLabel": {
"message": "Personnalisation des sites exclus"
},
"styleFromMozillaFormatError": {
"message": "Échec de l'importation depuis le format Mozilla"
},
@ -796,45 +1226,27 @@
}
}
},
"styleMetaErrorCheckbox": {
"message": "Boite à cocher @var invalide : elle doit valoir 0 ou 1"
},
"styleMetaErrorColor": {
"message": "$color$ n'est pas une couleur valide",
"placeholders": {
"color": {
"content": "$1"
}
}
},
"styleMetaErrorPreprocessor": {
"message": "@preprocessor non pris en charge : $preprocessor$",
"placeholders": {
"preprocessor": {
"content": "$1"
}
}
},
"styleMetaErrorSelectValueMismatch": {
"message": "@select invalide : la valeur nexiste pas dans la liste"
},
"styleMissingMeta": {
"message": "Metadonnée $key$ manquante",
"placeholders": {
"key": {
"content": "$1"
}
}
},
"styleMissingName": {
"message": "Veuillez saisir un nom"
},
"styleMozillaFormatHeading": {
"message": "Format Mozilla"
},
"styleName": {
"message": "Nom du style"
},
"styleNotAppliedRegexpProblemTooltip": {
"message": "Le style n'a pu s'appliquer en raison d'une utilisation erronée de 'regexp()'"
},
"styleNotAppliedSchemeDark": {
"message": "Ce style est uniquement appliqué en mode sombre"
},
"styleNotAppliedSchemeLight": {
"message": "Ce style est uniquement appliqué en mode clair"
},
"stylePreferSchemeLabel": {
"message": "Mode clair/sombre"
},
"styleRegexpInvalidExplanation": {
"message": "Quelques règles 'regexp()' qui nont pas pu être compilées"
},
@ -844,9 +1256,6 @@
"styleRegexpProblemTooltip": {
"message": "Nombre de sections non appliquées à cause dune utilisation incorrecte de 'regexp()'"
},
"styleRegexpTestButton": {
"message": "Test dexpression rationnelle"
},
"styleRegexpTestFull": {
"message": "Onglets correspondants"
},
@ -868,6 +1277,9 @@
"styleSaveLabel": {
"message": "Enregistrer"
},
"styleSettings": {
"message": "Paramètres du style"
},
"styleToMozillaFormatHelp": {
"message": "Le code au format Mozilla peut être utilisé dans Stylish for Firefox et envoyé à userstyles.org."
},
@ -885,12 +1297,32 @@
"styleUpdateDiscardChanges": {
"message": "Le style a été changé en dehors de léditeur. Voulez-vous recharger le style ?"
},
"styleUpdateUrlLabel": {
"message": "URL de mise à jour"
},
"stylusUnavailableForURL": {
"message": "Stylus ne fonctionne pas sur les pages de ce genre"
},
"stylusUnavailableForURLdetails": {
"message": "Par mesure de sécurité, le navigateur interdit aux extensions daffecter ses pages internes (comme chrome://version, la page nouvel onglet standard de Chrome 61, about:addons, etc.) ainsi que les pages dautres extensions. Chaque navigateur restreint également laccès à sa propre galerie dextensions (tel que le Chrome Web Store ou AMO)."
},
"syncDropboxDeprecated": {
"message": "L´import/export Dropbox est remplacé par une méthode de synchronisation de styles plus avancés dans la page des options"
},
"syncDropboxStyles": {
"message": "Exporter vers Dropbox"
},
"syncError": {
"message": "Synchronisation échouée"
},
"syncErrorLock": {
"message": "Cette base de données est déjà utilisée. Le verrouillage expirera le $TIME$",
"placeholders": {
"time": {
"content": "$1"
}
}
},
"syncStorageErrorSaving": {
"message": "La valeur ne peut pas être sauvegardée. Essayez de réduire la quantité de texte."
},
@ -909,20 +1341,23 @@
"unreachableAMOHint": {
"message": "Pour autoriser laccès, ouvrir <about:config>, clic-droit sur la liste, cliquez sur « Nouvelle » puis « Valeur booléenne », coller <privacy.resistFingerprinting.block_mozAddonManager> et cliquer sur OK, <true>, OK, et rechargez la page <addons.mozilla.org>."
},
"unreachableAMOHintNewFF": {
"message": "Pour Firefox 60 et suivants, vous devez également supprimer le domaine AMO de <extensions.webextensions.restrictedDomains> dans <about:config>."
},
"unreachableAMOHintOldFF": {
"message": "Seul Firefox 59 et supérieur peut être configuré pour autoriser les WebExtensions à ajouter des éléments de style sur des sites protégés par CSP comme celui-ci."
},
"unreachableContentScript": {
"message": "Impossible de communiquer avec la page. Essayez de recharger longlet."
},
"unreachableFileHint": {
"message": "Stylus peut accéder aux URL file:// uniquement si vous cochez la case correspondante pour lextension Stylus sur la page chrome://extensions."
},
"unreachableMozSiteHint": {
"message": "Dans Firefox 60 et plus récent vous devez retirer ce domaine de <extensions.webextensions.restrictedDomains> dans <about:config>."
},
"unreachableMozSiteHintOldFF": {
"message": "Seulement dans Firefox 59 et plus récent que les extensions-web peuvent être configurés pour autoriser de rajouter des éléments de style sur les sites protégés CSP comme celui-ci."
},
"unzipStyles": {
"message": "Décompression des styles…"
},
"updateAllCheckSucceededNoUpdate": {
"message": "All styles are up to date."
"message": "Aucune mise à jour trouvée."
},
"updateAllCheckSucceededSomeEdited": {
"message": "Certains styles pouvant être mis à jour nont pas été vérifiés pour éviter de perdre de potentiels modifications locales. Les mises à jour peuvent être forcées en vérifiant individuellement, ou en lançant une autre vérification pour tous les styles (les modifications locales seront perdues)."
@ -962,6 +1397,9 @@
"updatesCurrentlyInstalled": {
"message": "Mises à jour installées :"
},
"uploadingFile": {
"message": "Envoi du fichier…"
},
"usercssAvoidOverwriting": {
"message": "Veuillez changer la valeur de @name ou @namespace afin d'éviter d'écraser un style pré-existant."
},
@ -974,9 +1412,6 @@
"usercssReplaceTemplateConfirmation": {
"message": "Remplacer le modèle par défaut pour les nouveaux styles Usercss par le code actuel ?"
},
"usercssReplaceTemplateName": {
"message": "Un @name vide remplace le modèle par défaut"
},
"usercssReplaceTemplateSectionBody": {
"message": "Insérer le code ici..."
},
@ -988,5 +1423,8 @@
},
"writeStyleForURL": {
"message": "cette URL"
},
"zipStyles": {
"message": "Compression des styles…"
}
}

52
_locales/gl/messages.json Normal file
View File

@ -0,0 +1,52 @@
{
"addStyleTitle": {
"message": "Engadir Estilo"
},
"alphaChannel": {
"message": "Opacidade"
},
"appliesAdd": {
"message": "Engadir"
},
"appliesDisplay": {
"message": "Aplica a: $applies$",
"placeholders": {
"applies": {
"content": "$1"
}
}
},
"appliesDisplayTruncatedSuffix": {
"message": "e mais"
},
"appliesDomainOption": {
"message": "URLs no dominio"
},
"appliesLabel": {
"message": "Aplica para"
},
"appliesLineWidgetWarning": {
"message": "Non funciona con CSS minificado"
},
"appliesRegexpOption": {
"message": "URLs que concorden co regexp"
},
"appliesRemove": {
"message": "Suprimir"
},
"appliesSpecify": {
"message": "Especificar"
},
"appliesToEverything": {
"message": "Todo"
},
"appliesUrlPrefixOption": {
"message": "URLs que comecen por"
},
"applyAllUpdates": {
"message": "Aplicar tódalas actualizacións"
},
"author": {
"message": "Autor"
}
}

View File

@ -3,16 +3,16 @@
"message": "כתוב עיצוב חדש"
},
"addStyleTitle": {
"message": "הוספת עיצוב"
"message": "הוסף עיצוב"
},
"alphaChannel": {
"message": "שקיפות"
},
"appliesAdd": {
"message": "הוספה"
"message": "הוסף"
},
"appliesDisplay": {
"message": "מוחל על: $applies$",
"message": "חל על: $applies$",
"placeholders": {
"applies": {
"content": "$1"
@ -25,8 +25,11 @@
"appliesDomainOption": {
"message": "קישורים תחת הדומיין"
},
"appliesHelp": {
"message": "השתמש בהגדרות 'חל על' כדי להגביל את כתובות האתרים שהקוד בסעיף זה חל עליהם."
},
"appliesLabel": {
"message": "מוחל על"
"message": "חל על"
},
"appliesLineWidgetLabel": {
"message": "הצג אינפורמציית 'חל על'"
@ -35,10 +38,10 @@
"message": "לא עובד עם CSS מוקטן (minified)"
},
"appliesRegexpOption": {
"message": "קישורים התואמים regexp"
"message": "קישורים התואמים את ה־regexp"
},
"appliesRemove": {
"message": "הסרה"
"message": "הסר"
},
"appliesRemoveError": {
"message": "לא ניתן להסיר את הערך 'חל על' האחרון"
@ -50,7 +53,7 @@
"message": "כל האתרים"
},
"appliesUrlOption": {
"message": "קישור (URL)"
"message": "כתובת אתר"
},
"appliesUrlPrefixOption": {
"message": "קישורים המתחילים ב"
@ -59,14 +62,11 @@
"message": "החל את כל העדכונים"
},
"author": {
"message": "כותב"
"message": "מחבר"
},
"backupButtons": {
"message": "גיבוי"
},
"backupMessage": {
"message": "בחר קובץ או גרור ושחרר אותו בדף זה."
},
"bckpInstStyles": {
"message": "ייצא עיצובים"
},
@ -106,17 +106,26 @@
"cm_lineWrapping": {
"message": "עטיפת מילים"
},
"cm_linter": {
"message": "עורך שגיאות CSS"
},
"cm_matchHighlight": {
"message": "הדגש"
},
"cm_matchHighlightSelection": {
"message": "בחירה בלבד"
},
"cm_matchHighlightToken": {
"message": "תג תחת הסמן"
},
"cm_resizeGripHint": {
"message": "דאבל קליק להגדלה מירבית/איפוס הגובה"
},
"cm_selectByTokens": {
"message": "דאבל קליק בוחר tokens"
"message": "דאבל קליק בוחר תגים"
},
"cm_selectByTokensTooltip": {
"message": "דוגמאות לתגים: .foo-bar-2 #aabbcc 0.32 !important\nבמצב מושבת: נבחרות מילים המופרדות בין פיסוק."
},
"cm_smartIndent": {
"message": "השתמש בהזחה חכמה"
@ -127,6 +136,9 @@
"cm_theme": {
"message": "ערכת נושא"
},
"colorpickerSwitchFormatTooltip": {
"message": "החלף פורמטים: HEX -> RGB -> HSL.\nלחץ Shift כדי להפוך את הכיוון.\nגם באמצעות מקשי PgUp (PageUp), PgDn (PageDown)."
},
"colorpickerTooltip": {
"message": "פתח את פלטת בחירת הצבעים"
},
@ -172,12 +184,27 @@
"confirmYes": {
"message": "כן"
},
"connectingDropbox": {
"message": "מתחבר אל Dropbox..."
},
"connectingDropboxNotAllowed": {
"message": "התחברות אל Dropbox זמינה רק בהרחבות המותקנות ישירות מחנות האינטרנט"
},
"copied": {
"message": "הועתק אל לוח ההעתקה"
},
"copy": {
"message": "העתק אל לוח ההעתקה"
},
"dateInstalled": {
"message": "תאריך התקנה"
},
"dateUpdated": {
"message": "תאריך עדכון"
},
"dbError": {
"message": "אירעה שגיאה בשימוש במסד הנתונים של Stylus. האם ברצונך לבקר בדף אינטרנט עם פתרונות אפשריים?"
},
"defaultTheme": {
"message": "ברירת מחדל"
},
@ -187,6 +214,9 @@
"deleteStyleLabel": {
"message": "מחק"
},
"description": {
"message": "עיצוב מחדש של האינטרנט באמצעות Stylus, מנהל סגנונות משתמש. Stylus מאפשר לך להתקין בקלות עיצובים וערכות נושא עבור אתרים פופולריים רבים."
},
"disableAllStyles": {
"message": "השבת את כל העיצובים"
},
@ -200,13 +230,13 @@
"message": "מחק"
},
"editGotoLine": {
"message": "Goto לשורה (או line:col)"
"message": "גש לשורה (או line:col)"
},
"editStyleHeading": {
"message": "עריכת עיצוב"
"message": "ערוך עיצוב"
},
"editStyleLabel": {
"message": "עריכה"
"message": "ערוך"
},
"editStyleTitle": {
"message": "עריכת העיצוב $stylename$",
@ -216,15 +246,21 @@
}
}
},
"editorStylesButton": {
"message": "מצא עיצובים לעורך"
},
"enableStyleLabel": {
"message": "אפשר"
},
"excludeStyleByDomainLabel": {
"message": "אל תכלול את הדומיין הנוכחי"
},
"excludeStyleByUrlLabel": {
"message": "אל תכלול את כתובת האתר הנוכחית"
},
"exportLabel": {
"message": "ייצא"
},
"exportSavedSuccess": {
"message": "קובץ נשמר בהצלחה"
},
"externalFeedback": {
"message": "חוות דעת"
},
@ -237,12 +273,26 @@
"externalSupport": {
"message": "תמיכה"
},
"externalUsercssDocument": {
"message": "תיעוד ל־Usercss"
},
"filteredStyles": {
"message": "$numShown$ מתוך $numTotal$ סך־הכל",
"placeholders": {
"numShown": {
"content": "$1"
},
"numTotal": {
"content": "$2"
}
}
},
"filteredStylesAllHidden": {
"message": "מסננים שהוחלו כעת אינם תואמים עיצובים"
},
"findStyles": {
"message": "מצא עיצובים"
},
"findStylesInline": {
"message": "מוטבע"
},
"genericAdd": {
"message": "הוספה"
},
@ -279,6 +329,9 @@
"genericUnknown": {
"message": "לא ידוע"
},
"gettingStyles": {
"message": "טוען את כל העיצובים..."
},
"helpAlt": {
"message": "עזרה"
},
@ -288,6 +341,9 @@
"helpKeyMapHotkey": {
"message": "לחץ על המקש החם"
},
"hostDisabled": {
"message": "מארח זה הושבת בגלל באג בגרסה הנוכחית של הדפדפן בו נעשה שימוש"
},
"importAppendLabel": {
"message": "צרף לעיצוב"
},
@ -300,14 +356,26 @@
"importReplaceLabel": {
"message": "דרוס עיצוב"
},
"importReplaceTooltip": {
"message": "הסר את התוכן של הסגנון הנוכחי והחלף אותו עם העיצוב המיובא"
},
"importReportLegendAdded": {
"message": "נוספו"
},
"importReportLegendIdentical": {
"message": "זהה דולג"
},
"importReportLegendInvalid": {
"message": "לא חוקי דולג"
},
"importReportLegendUpdatedBoth": {
"message": "מטא דאטה והקוד עודכנו"
},
"importReportLegendUpdatedCode": {
"message": "קודים עודכנו"
},
"importReportLegendUpdatedMeta": {
"message": "מידע meta עודכנו"
"message": טא דאטה עודכן"
},
"importReportTitle": {
"message": "סיום ייבוא עיצובים"
@ -336,12 +404,17 @@
"installUpdate": {
"message": "התקן עדכון"
},
"installUpdateFrom": {
"message": "נכון לעכשיו הסגנון מעודכן מ־ $url$",
"placeholders": {
"url": {
"content": "$1"
}
}
},
"installUpdateFromLabel": {
"message": "בדוק עדכונים"
},
"installUpdateUnavailable": {
"message": "על־מנת לאפשר בדיקת עדכונים, אנא שחרר את הקובץ על רצועת הכרטיסיות או ציין @updateURL ב־metadata של העיצוב."
},
"license": {
"message": "רישיון"
},
@ -354,6 +427,14 @@
"linkTranslate": {
"message": "תרגום"
},
"linterCSSLintIncompatible": {
"message": "עורך שגיאות ה־CSS אינו תומך ב־ $preprocessorname$ כמעבד מקדים",
"placeholders": {
"preprocessorname": {
"content": "$1"
}
}
},
"linterCSSLintSettings": {
"message": "(הגדר כלל כ: 0 = מושבת; 1 = אזהרה; 2 = שגיאה)"
},
@ -368,6 +449,9 @@
"linterConfigTooltip": {
"message": "לחץ להגדרת linter זה"
},
"linterInvalidConfigError": {
"message": "לא נשמר עקב הגדרות תצורה לא חוקיות אלה:"
},
"linterIssues": {
"message": "תקלות"
},
@ -394,18 +478,12 @@
"liveReloadLabel": {
"message": "רענון לייב (live)"
},
"liveReloadUnavailable": {
"message": "על־מנת לאפשר רענון לייב (live), אנא שחרר את הקובץ על רצועת הכרטיסיות (האזור בו כותרות הכרטיסיות מוצגות)."
},
"manageFavicons": {
"message": "הצגת אייקונים בעמודת 'חל על'"
},
"manageFaviconsGray": {
"message": "האפרת האייקונים"
},
"manageFaviconsHelp": {
"message": "Stylus משתמש בשירות חיצוני https://www.google.com/s2/favicons"
},
"manageFilters": {
"message": "מסננים"
},
@ -418,6 +496,9 @@
"manageNewStyleAsUsercss": {
"message": "כ־Usercss"
},
"manageNewUI": {
"message": "פריסת ממשק משתמש ניהול חדשה"
},
"manageOnlyDisabled": {
"message": "רק עיצובים מושבתים"
},
@ -430,6 +511,9 @@
"manageOnlyLocal": {
"message": "רק עיצובים שנוצרו באופן מקומי"
},
"manageOnlyLocalTooltip": {
"message": "(הסגנונות שלא הותקנו דרך דף userstyles.org)"
},
"manageOnlyNonUsercss": {
"message": "רק לא עיצובי Usercss"
},
@ -442,16 +526,39 @@
"menuShowBadge": {
"message": "הצג כמות עיצובים מאופשרים"
},
"meta_invalidCheckboxDefault": {
"message": "תיבת סימון @var לא חוקית: הערך חייב להיות 0 או 1"
},
"meta_invalidNumber": {
"message": "נדרש מספר"
},
"meta_invalidString": {
"message": "נדרשת מחרוזת בתוך גרשיים"
},
"meta_invalidWord": {
"message": "נדרשת מילה"
},
"meta_missingChar": {
"message": "נדרשים תווים: $chars$",
"placeholders": {
"chars": {
"content": "$1"
}
}
},
"meta_missingEOT": {
"message": "נדרש מידע EOT"
},
"noFileToImport": {
"message": "כדי לייבא את הסגנונות שלך, עליך לייצא אותם תחילה."
},
"noStylesForSite": {
"message": "לא הותקנו עיצובים עבור אתר זה."
},
"openManage": {
"message": "ניהול"
},
"openOptionsManage": {
"message": "אפשרויות UI"
},
"openOptionsPopup": {
"openOptions": {
"message": "אפשרויות"
},
"openStylesManager": {
@ -464,7 +571,16 @@
"message": "מתקדם"
},
"optionsAdvancedContextDelete": {
"message": "הוספת 'מחיקה' בתפריט העורך"
"message": "הוספת 'מחק' בתפריט העורך"
},
"optionsAdvancedExposeIframes": {
"message": "חשוף iframes באמצעות HTML [stylus-iframe]"
},
"optionsAdvancedExposeIframesNote": {
"message": "חושף את תחום האתר העליון בכל iframe.\nמאפשר כתיבת CSS ספציפית ל־iframe כך:\nhtml[stylus-iframe$$=\"twitter.com\"] h1 { display:none }"
},
"optionsAdvancedNewStyleAsUsercss": {
"message": "כתוב עיצוב חדש בתור usercss"
},
"optionsBadgeDisabled": {
"message": "צבע רקע בעת השבתה"
@ -487,6 +603,9 @@
"optionsCustomizePopup": {
"message": "חלון קופץ"
},
"optionsCustomizeSync": {
"message": "סנכרון לענן"
},
"optionsCustomizeUpdate": {
"message": "עדכונים"
},
@ -512,14 +631,75 @@
"message": "איפוס האפשרויות לערכי ברירת המחדל"
},
"optionsResetButton": {
"message": יפוס האפשרויות"
"message": פס אפשרויות"
},
"optionsSubheading": {
"message": "אפשרויות נוספות"
},
"optionsSyncConnect": {
"message": "התחבר"
},
"optionsSyncDisconnect": {
"message": "התנתק"
},
"optionsSyncLogin": {
"message": "היכנס"
},
"optionsSyncNone": {
"message": "ללא"
},
"optionsSyncStatusConnected": {
"message": "מחובר"
},
"optionsSyncStatusConnecting": {
"message": "מתחבר..."
},
"optionsSyncStatusDisconnected": {
"message": "מנותק"
},
"optionsSyncStatusDisconnecting": {
"message": "מתנתק..."
},
"optionsSyncStatusPull": {
"message": "מושך סגנון $loaded$ מתוך $total$",
"placeholders": {
"loaded": {
"content": "$1"
},
"total": {
"content": "$2"
}
}
},
"optionsSyncStatusPush": {
"message": "מפרסם סגנון $loaded$ מתוך $total$",
"placeholders": {
"loaded": {
"content": "$1"
},
"total": {
"content": "$2"
}
}
},
"optionsSyncStatusSyncing": {
"message": "מסנכרן..."
},
"optionsSyncSyncNow": {
"message": "סנכרן כעת"
},
"optionsSyncUrl": {
"message": "כתובת אתר"
},
"optionsUpdateImportNote": {
"message": "בעת ייבוא גיבויים לסגנון מגרסה ישנה או מ־Stylish, בדוק באופן חד פעמי אם יש עדכונים באופן ידני במנהל הסגנונות כדי לוודא שכל הסגנונות מעודכנים."
},
"optionsUpdateInterval": {
"message": "עדכון אוטומטי של Userstyle בשעות (הגדר 0 להשבתה)"
},
"overwriteFileExport": {
"message": "האם ברצונך להחליף קובץ קיים?"
},
"paginationCurrent": {
"message": "הדף הנוכחי"
},
@ -535,20 +715,35 @@
"paginationTotal": {
"message": "סה״כ דפים"
},
"parseUsercssError": {
"message": "Stylus נכשל בניתוח usercss:"
},
"popupBorders": {
"message": "הוספת שוליים לבנים בצדדים"
},
"popupBordersTooltip": {
"message": "שימושי לעיצובים כהים ב־Chrome החדש מכיוון שהוא כבר לא מצייר את גבולות הצד"
},
"popupHotkeysTooltip": {
"message": "לחץ על־מנת לצפות במקשים החמים הזמינים"
},
"popupManageTooltip": {
"message": "לחיצה על Shift או מקש ימני בעכבר פותח את מנהל העיצובים"
},
"popupMenuButtonTooltip": {
"message": "תפריט פעולות"
},
"popupOpenEditInWindow": {
"message": "פתח את העורך בחלון חדש"
},
"popupOpenEditInWindowTooltip": {
"message": "מופעל גם על ידי ניתוק לשונית העורך מחלון הדפדפן,\nומושבת על ידי חיבור לשונית עורך יחידה לחלון אחר."
},
"popupStylesFirst": {
"message": "עיצובים לפני הפקודות"
},
"prefShowBadge": {
"message": ֿמספר העיצובים המאופשרים באתר הנוכחי"
"message": ספר העיצובים המאופשרים באתר הנוכחי"
},
"previewLabel": {
"message": "תצוגת לייב (live)"
@ -556,6 +751,9 @@
"previewTooltip": {
"message": "החלת השינויים באופן זמני ללא שמירה.\nשמור את העיצוב על־מנת להפוך את השינויים לקבועים."
},
"readingStyles": {
"message": "קורא עיצובים..."
},
"replace": {
"message": "החלף"
},
@ -568,6 +766,9 @@
"retrieveBckp": {
"message": "ייבוא עיצובים"
},
"retrieveDropboxSync": {
"message": "Dropbox ייבוא"
},
"search": {
"message": "חיפוש"
},
@ -598,12 +799,21 @@
"searchResultWeeklyCount": {
"message": "התקנות שבועיות"
},
"sectionAdd": {
"message": "הוסף מקטע נוסף"
},
"sectionCode": {
"message": "קוד"
},
"sectionRemove": {
"message": "הסר סעיף"
},
"sectionRestore": {
"message": "שחזר סעיף שהוסר"
},
"sections": {
"message": "סעיפים"
},
"shortcuts": {
"message": "קיצורי מקשים"
},
@ -619,53 +829,83 @@
"sortLabel": {
"message": "בחר שיטת מיון להחלה על העיצובים המותקנים"
},
"sortStylesHelpTitle": {
"message": "סידור תכנים"
},
"styleBadRegexp": {
"message": "ביטוי ה־Regexp לא תקין."
"message": "ביטוי ה־regexp לא תקין."
},
"styleBeautify": {
"message": "ייפה CSS"
},
"styleBeautifyIndentConditional": {
"message": "הזחת @media, @supports"
},
"styleBeautifyPreserveNewlines": {
"message": "שמור שורות חדשות"
},
"styleCancelEditLabel": {
"message": "חזרה לניהול"
},
"styleChangesNotSaved": {
"message": "ביצעת שינויים בסגנון זה מבלי לשמור."
},
"styleEnabledLabel": {
"message": "מאופשר"
},
"styleFromMozillaFormatError": {
"message": "הייבוא נכשל מ־Mozilla format"
},
"styleFromMozillaFormatPrompt": {
"message": "הדבק את הקוד ב־Mozilla-format"
},
"styleMetaErrorColor": {
"message": "$color$ הוא צבע לא תקין",
"styleInstall": {
"message": "להתקין ';$stylename$' אל Stylus?",
"placeholders": {
"color": {
"stylename": {
"content": "$1"
}
}
},
"styleMetaErrorPreprocessor": {
"message": "@preprocessor לא נתמך: $preprocessor$",
"styleInstallFailed": {
"message": "נכשל בהתקנת העיצוב!\n$error$",
"placeholders": {
"preprocessor": {
"error": {
"content": "$1"
}
}
},
"styleMetaErrorSelectValueMismatch": {
"message": "הערך @select: לא קיים ברשימה"
},
"styleMissingMeta": {
"message": "@$key$ metadata חסרים",
"styleInstallOverwrite": {
"message": "';$stylename$' כבר מותקן. האם לדרוס?\nגרסה: $oldVersion$ -> $newVersion$",
"placeholders": {
"key": {
"newVersion": {
"content": "$3"
},
"oldVersion": {
"content": "$2"
},
"stylename": {
"content": "$1"
}
}
},
"styleMissingName": {
"message": "אנא הזן שם"
"message": "הזן שם"
},
"styleRegexpTestButton": {
"message": "בדוק RegExp"
"styleMozillaFormatHeading": {
"message": "Mozilla-format"
},
"styleNotAppliedRegexpProblemTooltip": {
"message": "הסגנון לא הוחל בגלל השימוש בו שגוי ב־'regexp()'"
},
"styleRegexpInvalidExplanation": {
"message": "לא היה ניתן להחיל מספר כללי 'regexp()' כלל."
},
"styleRegexpPartialExplanation": {
"message": "סגנון זה משתמש ב- regexps תואם חלקית בניגוד למפרט <a href='https://developer.mozilla.org/docs/Web/CSS/@document'>CSS4 @document</a> המצריך התאמה מלאה של כתובת אתר. קטעי ה־CSS המושפעים לא הוחלו על הדף. סגנון זה נוצר ככל הנראה ב־Stylish-for-Chrome שבודק באופן שגוי את כללי 'regexp()' מאז הגרסה הראשונה (באג ידוע)."
},
"styleRegexpProblemTooltip": {
"message": "מספר המקטעים שלא הוחלו עקב שימוש שגוי ב־'regexp()'"
},
"styleRegexpTestFull": {
"message": "כרטיסיות תואמות"
@ -676,14 +916,20 @@
"styleRegexpTestNone": {
"message": "לא תואם אף כרטיסייה"
},
"styleRegexpTestPartial": {
"message": "לא תואם לחלוטין, לכן דולג"
},
"styleRegexpTestTitle": {
"message": "רשימת לשוניות פתוחות בהתאמה (לחץ על כתובת אתר למיקוד הלשונית שלה)"
},
"styleSaveLabel": {
"message": "שמור"
},
"styleSectionsTitle": {
"message": "סעיפים"
"styleToMozillaFormatHelp": {
"message": "ניתן לפרסם את ה־Mozilla format של הקוד לאתר userstyles.org ולהשתמש בו עם Stylish הקלאסי עבור Firefox"
},
"styleToMozillaFormatTitle": {
"message": "עיצוב ב־Mozilla format"
"message": "עיצוב ב־Mozilla-format"
},
"styleUpdate": {
"message": "האם אתה בטוח שברצונך לעדכן את '$stylename$'?",
@ -696,6 +942,12 @@
"stylusUnavailableForURL": {
"message": "Stylus לא עובד על דפים כמו זה."
},
"stylusUnavailableForURLdetails": {
"message": "כאמצעי אבטחה, הדפדפן אוסר על הרחבות להשפיע על הדפים המובנים שלו (כמו chrome://version, הכרטיסייה החדשה הרגילה החל מ־Chrome 61, about:addons וכן הלאה), וכן על דפי הרחבות אחרים. כל דפדפן גם מגביל את הגישה לגלריית התוספים שלו (כמו חנות האינטרנט של Chrome או AMO)."
},
"syncDropboxStyles": {
"message": "ייצוא Dropbox"
},
"syncStorageErrorSaving": {
"message": "הערך לא יכול להשמר. אנא נסה להקטין את גודל הטקסט."
},
@ -714,9 +966,18 @@
"unreachableContentScript": {
"message": "לא ניתן לתקשר עם הדף. אנא טען מחדש את הכרטיסייה."
},
"unreachableFileHint": {
"message": "Stylus יכול לגשת לכתובות אתר file:// רק אם תיבת הסימון המתאימה עבור סיומת Stylus פעילה בדף chrome://extensions."
},
"unzipStyles": {
"message": "מחלץ עיצובים..."
},
"updateAllCheckSucceededNoUpdate": {
"message": "לא נמצאו עדכונים."
},
"updateAllCheckSucceededSomeEdited": {
"message": "כמה סגנונות הניתנים לעדכון לא נבדקו כדי להימנע מאבדן עריכות מקומיות אפשריות. ניתן לאלץ עדכונים על ידי בדיקה באופן פרטני, או על ידי הפעלת בדיקה אחרת לכל הסגנונות (עריכות מקומיות יידרסו)."
},
"updateCheckFailBadResponseCode": {
"message": "העדכון נכשל: השרת החזיר תגובה עם הקוד $code$.",
"placeholders": {
@ -731,6 +992,12 @@
"updateCheckHistory": {
"message": "היסטוריה של בדיקת עדכונים"
},
"updateCheckManualUpdateForce": {
"message": "התקן עדכון (עריכות מקומיות יידרסו)"
},
"updateCheckManualUpdateHint": {
"message": "אילוץ עדכון ידרוס כל עריכה מקומית."
},
"updateCheckSkippedLocallyEdited": {
"message": "עיצוב זה נערך באופן מקומי."
},
@ -746,6 +1013,9 @@
"updatesCurrentlyInstalled": {
"message": "העדכונים הותקנו."
},
"uploadingFile": {
"message": "מעלה קובץ..."
},
"usercssAvoidOverwriting": {
"message": "אנא שנה את הערך של @name or @namespace על־מנת להמנע מדריסה של עיצוב קיים."
},
@ -763,5 +1033,8 @@
},
"writeStyleForURL": {
"message": "הקישור הנוכחי"
},
"zipStyles": {
"message": "מקבץ עיצובים..."
}
}

View File

@ -1,4 +1,7 @@
{
"InaccessibleFileHint": {
"message": "A Stylus nem tud bizonyos fájltípusokhoz hozzáférni (pl. PDF- & JSON-fájlokhoz)"
},
"addStyleLabel": {
"message": "Új stílus írása"
},
@ -12,7 +15,7 @@
"message": "Hozzáadás"
},
"appliesDisplay": {
"message": "A következőre érvényesül:$applies$",
"message": "Érvényes erre:$applies$",
"placeholders": {
"applies": {
"content": "$1"
@ -23,28 +26,28 @@
"message": "és ennél is több"
},
"appliesDomainOption": {
"message": "URL-ek a doménon"
"message": "URL-ek a tartományban"
},
"appliesHelp": {
"message": "Használd az \"A következőre érvényesül\" részt, hogy korlátozd, milyen URL-ekre vonatkozzon az itt lévő kód!"
"message": "Az \"Érvényes erre\" beállítással korlártozható, hogy milyen URL-ekre vonatkozik az itt lévő kód."
},
"appliesLabel": {
"message": "Amire érvényesül"
"message": "Érvényes erre"
},
"appliesLineWidgetLabel": {
"message": "Információ megjelenítése arról, hogy mire van alkalmazva"
"message": "Információ arról, hogy mire érvényes"
},
"appliesLineWidgetWarning": {
"message": "Nem működik minimalizált CSS-szel"
"message": "Nem működik minimalizált CSS-sel"
},
"appliesRegexpOption": {
"message": "Reguláris kifejezésekre (regexp) illeszkedő URL-ek"
"message": "Reguláris kifejezésre (regexp) illeszkedő URL-ek"
},
"appliesRemove": {
"message": "Eltávolítás"
},
"appliesRemoveError": {
"message": "Nem lehet eltávolítani az utolsó alkalmazási szabályt"
"message": "Nem lehet eltávolítani az utolsó 'érvényes erre' bejegyzést"
},
"appliesSpecify": {
"message": "Szűkítés"
@ -64,14 +67,11 @@
"backupButtons": {
"message": "Biztonsági mentés"
},
"backupMessage": {
"message": "Válassz ki egy fájlt vagy húzd erre az oldalra!"
},
"bckpInstStyles": {
"message": "Stílusok exportálása"
},
"checkAllUpdates": {
"message": "Az összes stílus frissítésének ellenőrzése"
"message": "Összes stílus frissítésének ellenőrzése"
},
"checkAllUpdatesForce": {
"message": "Ellenőrizd újra, nem módosítottam egy stílust sem!"
@ -89,13 +89,13 @@
"message": "Zárójelek és idézőjelek automatikus bezárása"
},
"cm_autoCloseBracketsTooltip": {
"message": "Automatikusan legyen hozzáadva záró jelpár a következők gépelésekor: ()[]{}''\"\""
"message": "Nyitó '([{'zárójel gépelésekor lezáró ')]}' zárójel automatikus hozzáadása"
},
"cm_autocompleteOnTyping": {
"message": "Automatikus kiegészítés gépeléskor"
},
"cm_colorpicker": {
"message": "Színválasztó CSS színekhez"
"message": "Színválasztó CSS-színekhez"
},
"cm_indentWithTabs": {
"message": "Tabulátorok használata intelligens behúzásra"
@ -107,7 +107,7 @@
"message": "Automatikus sortörés"
},
"cm_matchHighlight": {
"message": "Kijelöl"
"message": "Kijelölés"
},
"cm_matchHighlightSelection": {
"message": "Csak kiválasztás"
@ -125,7 +125,7 @@
"message": "Példák kifejezésekre: .valami-2 #aabbcc 0.32 !important\nAmikor ki van kapcsolva: a központosítással elválasztott szavak ki lesznek jelölve."
},
"cm_smartIndent": {
"message": "Intelligens behúzás használata"
"message": "Intelligens behúzás"
},
"cm_tabSize": {
"message": "Tabulátorméret"
@ -178,6 +178,18 @@
"confirmYes": {
"message": "Igen"
},
"connectingDropbox": {
"message": "Kapcsolódás Dropboxhoz..."
},
"connectingDropboxNotAllowed": {
"message": "Dropboxhoz csak közvetlenül a web-boltból telepített alkalmazásból lehet kapcsolódni."
},
"copied": {
"message": "Vágólapra másolva"
},
"copy": {
"message": "Másolás vágólapra"
},
"dateInstalled": {
"message": "Telepítés dátuma"
},
@ -200,7 +212,7 @@
"message": "Tervezd újra a webet a Stylus stíluskezelővel. A Stylus lehetővé teszi a témák és egyéni külsők egyszerű telepítését sok népszerű oldalhoz."
},
"disableAllStyles": {
"message": "Az összes stílus kikapcsolása"
"message": "Összes stílus kikapcsolása"
},
"disableStyleLabel": {
"message": "Letiltás"
@ -228,15 +240,21 @@
}
}
},
"editorStylesButton": {
"message": "A szerkesztő stílusainak keresése"
},
"enableStyleLabel": {
"message": "Engedélyezés"
},
"excludeStyleByDomainLabel": {
"message": "Aktuális tartományt kivéve"
},
"excludeStyleByUrlLabel": {
"message": "Aktuális URL-t kivéve"
},
"exportLabel": {
"message": "Exportálás"
},
"exportSavedSuccess": {
"message": "A fájl sikeresen elmentve."
},
"externalFeedback": {
"message": "Visszajelzés"
},
@ -269,15 +287,6 @@
"findStyles": {
"message": "Stílusok keresése"
},
"findStylesForSite": {
"message": "További stílusok keresése ehhez az oldalhoz"
},
"findStylesInline": {
"message": "Helyben"
},
"findStylesInlineTooltip": {
"message": "Keresési eredmények megjelenítése ebben az ablakban."
},
"genericAdd": {
"message": "Hozzáadás"
},
@ -314,6 +323,9 @@
"genericUnknown": {
"message": "Ismeretlen"
},
"gettingStyles": {
"message": "Minden stílus fogadása..."
},
"helpAlt": {
"message": "Segítség"
},
@ -321,10 +333,10 @@
"message": "Írj be egy parancsot"
},
"helpKeyMapHotkey": {
"message": "Gyorsgomb"
"message": "Gyorsbillentyű"
},
"importAppendLabel": {
"message": "Hozzáadás stílushoz"
"message": "Hozzáfűzés stílushoz"
},
"importAppendTooltip": {
"message": "Az importált stílus hozzáadása a jelenlegi stílushoz"
@ -357,10 +369,10 @@
"message": "metainfó frissítve"
},
"importReportTitle": {
"message": "A stílusok importálása befejeződött"
"message": "A stílusok importálása befejeződött."
},
"importReportUnchanged": {
"message": "Semmi sem változott."
"message": "Nincs változás."
},
"importReportUndone": {
"message": "stílusok visszavonva"
@ -384,7 +396,7 @@
"message": "Frissítés telepítése"
},
"installUpdateFrom": {
"message": "A stílus most a következő helyről frissül: $url$",
"message": "A stílus jelenleg innen frissül: $url$",
"placeholders": {
"url": {
"content": "$1"
@ -398,7 +410,7 @@
"message": "Licenc"
},
"linkGetHelp": {
"message": "Kérj segítséget"
"message": "Segítség kérése"
},
"linkGetStyles": {
"message": "Szerezz be stílusokat"
@ -426,10 +438,10 @@
}
},
"linterConfigTooltip": {
"message": "Kattints ennek a linternek a beállításához"
"message": "Linter beállítása"
},
"linterInvalidConfigError": {
"message": "Nincs mentve ezek miatt az érvénytelen beállítások miatt:"
"message": "Nincs mentve a következő érvénytelen beállítások miatt:"
},
"linterIssues": {
"message": "Problémák"
@ -443,31 +455,28 @@
}
},
"linterJSONError": {
"message": "Érvénytelen JSON formátum"
"message": "Érvénytelen JSON-formátum"
},
"linterResetMessage": {
"message": "Egy véletlen visszaállítás visszavonásához nyomj Ctrl-Z-t (vagy Cmd-Z) a szövegdobozon belül)"
},
"linterRulesLink": {
"message": "Lista az összes stílusról"
"message": "Összes szabály listája"
},
"liveReloadError": {
"message": "Hiba történt a fájl figyelése közben"
},
"liveReloadInstallHint": {
"message": "A valós idejű újratöltés engedélyezve van, így a telepített stílus automatikusan frissítve lesz kűlső változtatások során amíg ez a fül és a forrásfájlt tartalmazó fül nyitva van."
},
"liveReloadLabel": {
"message": "Valós idejű újratöltés"
},
"manageFavicons": {
"message": "Faviconok az alkalmazási oszlopban"
"message": "Faviconok az 'Érvényes erre' oszlopban"
},
"manageFaviconsGray": {
"message": "Szürke mód"
"message": "Megjelenítés szürkítve"
},
"manageFaviconsHelp": {
"message": "A Stylus egy külső szolgáltatást használ (https://www.google.com/s2/favicons)"
"message": "A Stylus külső szolgáltatást használ (https://icons.duckduckgo.com)"
},
"manageFilters": {
"message": "Szűrők"
@ -476,16 +485,16 @@
"message": "Telepített stílusok"
},
"manageMaxTargets": {
"message": "Megjelenítendő célok száma"
"message": "'Érvényes erre' elemek kijelzendő száma"
},
"manageNewStyleAsUsercss": {
"message": "Usercss-ként"
},
"manageNewUI": {
"message": "Az új kezelési felületkiosztás"
"message": "Új kezelői felület"
},
"manageOnlyDisabled": {
"message": "Csak letiltott stílusok"
"message": "Csak a letiltott stílusok"
},
"manageOnlyEnabled": {
"message": "Csak a telepített stílusok"
@ -497,13 +506,13 @@
"message": "Csak helyileg létrehozott stílusok"
},
"manageOnlyLocalTooltip": {
"message": "(a stílusok nem egy userstyles.org oldalon lettek telepítve)"
"message": "(a stílusok nem a userstyles.org egyik oldaláról települtek)"
},
"manageOnlyNonUsercss": {
"message": "Csak nem Usercss stílusok"
},
"manageOnlyUpdates": {
"message": "Csak frissíthetőek vagy problémásak"
"message": "Csak a frissíthetők vagy problémásak"
},
"manageOnlyUsercss": {
"message": "Csak Usercss stílusok"
@ -511,20 +520,201 @@
"menuShowBadge": {
"message": "Aktív stílusok számlálójának mutatása"
},
"meta_invalidCheckboxDefault": {
"message": "Érvénytelen jelölő négyzet @var: az érték csak 0 vagy 1 lehet"
},
"meta_invalidColor": {
"message": "Érvénytelen szín @var: a(z) $color$ nem szín",
"placeholders": {
"color": {
"content": "$1"
}
}
},
"meta_invalidNumber": {
"message": "Várt elem: szám"
},
"meta_invalidRange": {
"message": "Érvénytelen változó @var (típus: $type$): az érték csak szám vagy tömb lehet",
"placeholders": {
"type": {
"content": "$1"
}
}
},
"meta_invalidRangeDefault": {
"message": "Érvénytelen változó @var (típus: $type$): az alapértelmezett értéke null",
"placeholders": {
"type": {
"content": "$1"
}
}
},
"meta_invalidRangeMax": {
"message": "Érvénytelen változó @var (típus: $type$): az alapértelmezett érték nagyobb, mint a maximum",
"placeholders": {
"type": {
"content": "$1"
}
}
},
"meta_invalidRangeMin": {
"message": "Érvénytelen változó @var (típus: $type$): az alapértelmezett érték kisebb, mint a minimum",
"placeholders": {
"type": {
"content": "$1"
}
}
},
"meta_invalidRangeMultipleUnits": {
"message": "Érvénytelen változó @var (típus: $type$): többféle egység van megadva",
"placeholders": {
"type": {
"content": "$1"
}
}
},
"meta_invalidRangeStep": {
"message": "Érvénytelen változó @var (típus: $type$): az alapértelmezett érték nem a lépésköz többszöröse",
"placeholders": {
"type": {
"content": "$1"
}
}
},
"meta_invalidRangeTooManyValues": {
"message": "Érvénytelen változó @var (típus: $type$): a tömb túl sok elemet tartalmaz",
"placeholders": {
"type": {
"content": "$1"
}
}
},
"meta_invalidRangeUnits": {
"message": "Érvénytelen változó @var (típus: $type$): a(z) '$units$' nem érvényes egység",
"placeholders": {
"type": {
"content": "$1"
},
"units": {
"content": "$2"
}
}
},
"meta_invalidRangeValue": {
"message": "Érvénytelen változó @var (típus: $type$): csak szám, karakterlánc vagy null típusú elem lehet a tömbben",
"placeholders": {
"type": {
"content": "$1"
}
}
},
"meta_invalidSelect": {
"message": "Érvénytelen kiválasztó lista @var: az alapértelmezett érték tömb vagy objektum lehet csak"
},
"meta_invalidSelectEmptyOptions": {
"message": "Érvénytelen kiválasztó lista @var: a lista üres"
},
"meta_invalidSelectLabel": {
"message": "Érvénytelen kiválasztó lista @var: a listaelem címkéje üres"
},
"meta_invalidSelectMultipleDefaults": {
"message": "Érvénytelen kiválasztó lista @var: egynél több alapértelmezett elem van megadva"
},
"meta_invalidSelectNameDuplicated": {
"message": "Érvénytelen kiválasztó lista @var: egy listaelem kétszer szerepel"
},
"meta_invalidSelectValue": {
"message": "Érvénytelen kiválasztó lista @var: a tömbön/objektumon belüli érték csak karakterlánc lehet"
},
"meta_invalidSelectValueMismatch": {
"message": "Érvénytelen kiválasztó lista @var: az érték nem található a listaelemek között"
},
"meta_invalidString": {
"message": "Várt elem: idézőjelek közti karakterlánc"
},
"meta_invalidURLProtocol": {
"message": "Érvénytelen URL-protokoll. Csak 'http' vagy 'https' engedett: $protocol$",
"placeholders": {
"protocol": {
"content": "$1"
}
}
},
"meta_invalidVersion": {
"message": "Érvénytelen verziószám"
},
"meta_invalidWord": {
"message": "Várt elem: szó"
},
"meta_missingChar": {
"message": "Várt karakterek: $chars$",
"placeholders": {
"chars": {
"content": "$1"
}
}
},
"meta_missingEOT": {
"message": "Várt elem: EOT-adat"
},
"meta_missingMandatory": {
"message": "Kötelező metaadat hiányzik: $keys$",
"placeholders": {
"keys": {
"content": "$1"
}
}
},
"meta_unknownJSONLiteral": {
"message": "Érvénytelen JSON: a(z) $literal$ nem érvényes JSON-karakterlánc (literális)",
"placeholders": {
"literal": {
"content": "$1"
}
}
},
"meta_unknownMeta": {
"message": "Ismeretlen metaadat: $key$",
"placeholders": {
"key": {
"content": "$1"
}
}
},
"meta_unknownPreprocessor": {
"message": "Ismeretlen @preprocesszor (előfeldolgozó): $preprocessor$",
"placeholders": {
"preprocessor": {
"content": "$1"
}
}
},
"meta_unknownVarType": {
"message": "Ismeretlen @$varkey$ változó, típus: $vartype$",
"placeholders": {
"varkey": {
"content": "$1"
},
"vartype": {
"content": "$2"
}
}
},
"noFileToImport": {
"message": "A stílusok importálásához előbb exportálni kell őket."
},
"noStylesForSite": {
"message": "Nincs telepítve stílus ehhez az oldalhoz."
},
"openManage": {
"message": "Kezelés"
},
"openOptionsManage": {
"message": "A beállítások felülete"
},
"openOptionsPopup": {
"openOptions": {
"message": "Beállítások"
},
"openStylesManager": {
"message": "A frissítéskezelő megnyitása"
"message": "Stíluskezelő megnyitása"
},
"optionsActions": {
"message": "Műveletek"
@ -533,13 +723,13 @@
"message": "Haladó"
},
"optionsAdvancedContextDelete": {
"message": "Delete hozzáadása a gyorsmenühöz"
"message": "'Törlés' parancs a szerkesztő helyi menüjében"
},
"optionsAdvancedExposeIframes": {
"message": "iframe-ek kitevése HTML[stylus-iframe]-en keresztül"
},
"optionsAdvancedExposeIframesNote": {
"message": "Engedélyezi a legfelsőbb szintű domain elérését\nmindegyik iframe-ben. Lehetővé teszi az olyan\niframe-specifikus CSS írását, mint:\nhtml[stylus-iframe$=\"twitter.com\"] h1 { display:none }"
"message": "Az oldal felső tartományának jelölése minden iframe-ben.\nLehetővé teszi az iframe-specifikus CSS írását, mint pl.:\nhtml[stylus-iframe$$=\"twitter.com\"] h1 { display:none }"
},
"optionsAdvancedNewStyleAsUsercss": {
"message": "Új stílus írása usercss-ként"
@ -557,13 +747,16 @@
"message": "Az összes frissítés ellenőrzése és telepítése"
},
"optionsCustomizeBadge": {
"message": "Jelvény az eszköztárikonon"
"message": "Ikon képe az eszköztáron"
},
"optionsCustomizeIcon": {
"message": "Eszköztárikon"
},
"optionsCustomizePopup": {
"message": "Felugró"
"message": "Felugró ablak"
},
"optionsCustomizeSync": {
"message": "Szinkronizálás felhővel"
},
"optionsCustomizeUpdate": {
"message": "Frissítések"
@ -587,19 +780,74 @@
"message": "Felugró ablak szélessége (pixelben)"
},
"optionsReset": {
"message": "Beállítások visszaállítása alapértelmezett értékekre."
"message": "Beállítások visszaállítása alapértelmezett értékre"
},
"optionsResetButton": {
"message": "Beállítások visszaállítása alapra"
"message": "Alapértelmezés visszaállítása"
},
"optionsSubheading": {
"message": "Még több beállítás"
"message": "További beállítások"
},
"optionsSyncConnect": {
"message": "Kapcsolódás"
},
"optionsSyncDisconnect": {
"message": "Kapcsolat bontása"
},
"optionsSyncLogin": {
"message": "Bejelentkezés"
},
"optionsSyncNone": {
"message": "Nincs"
},
"optionsSyncStatusConnected": {
"message": "Kapcsolódva"
},
"optionsSyncStatusConnecting": {
"message": "Kapcsolódás..."
},
"optionsSyncStatusDisconnected": {
"message": "Nincs kapcsolat"
},
"optionsSyncStatusDisconnecting": {
"message": "Kapcsolat bontása..."
},
"optionsSyncStatusPull": {
"message": "Stílusok helyi frissítése (pull): $loaded$/$total$",
"placeholders": {
"loaded": {
"content": "$1"
},
"total": {
"content": "$2"
}
}
},
"optionsSyncStatusPush": {
"message": "Stílusok távoli frissítése (push): $loaded$/$total$",
"placeholders": {
"loaded": {
"content": "$1"
},
"total": {
"content": "$2"
}
}
},
"optionsSyncStatusSyncing": {
"message": "Szinkronizálás..."
},
"optionsSyncSyncNow": {
"message": "Szinkronizálás most"
},
"optionsUpdateImportNote": {
"message": "Amikor régebbi verzióból vagy a Stylishból importálsz stílusokat, egyszer manuálisan frissítsd a stílusokat a stíluskezelőben, hogy megbizonyosodj afelől, hogy mindegyik frissítve van!"
},
"optionsUpdateInterval": {
"message": "Stílus automatikus frissítési intervalluma órában megadva (álltsd 0-ra a kikapcsoláshoz)"
"message": "Felhasználói stílus automatikus frissítési időköze órában (0=kikapcsolva)"
},
"overwriteFileExport": {
"message": "Felülírjuk a létező fájlt?"
},
"paginationCurrent": {
"message": "Jelenlegi oldal"
@ -619,20 +867,26 @@
"parseUsercssError": {
"message": "A Stylus nem tudta elemezni a usercss-t"
},
"popupAutoResort": {
"message": "Stílusok újrarendezése a felugró ablakban átkapcsoláskor"
},
"popupBorders": {
"message": "Fehér szegélyek használata két oldalt"
"message": "Fehér szegély az oldalakon"
},
"popupBordersTooltip": {
"message": "Hasznos az új Chromehoz létrehozott sötét témákhoz, mert nem színezi át az oldalszegélyeket"
"message": "Hasznos az új Chrome sötét témáinál, mert nem színezi át az oldalszegélyeket"
},
"popupHotkeysInfo": {
"message": "<1>-<9>, <0>, a numpaden is - be-/kikapcsolja az n-nedik stílust (a 0 10-et jelent)\n<A>-<Z> az adott betűvel kezdődő első stílust kapcsolja be/ki \n<Shift> kapcsolgatás helyett megnyitja a szerkesztőt\n<Numpad +> listázott stílusokat engedélyez\n<Numpad > listázott stílusokat tilt le\n<Numpad *> és <`> (fordított félidézőjel) - kezdeti állapotban engedélyezett stílusokat kapcsol; nem vonatkozik azokra a stílusokra, amiket később engedélyeztél, mialatt a felugró ablakocska nyitva volt, hogy vissza tudd állítani a kezdeti kiválasztást tesztelés után: egyszerűen tilts le mindent, majd pl. <Numpad > vagy <Numpad *>\nTovábbi információ a wikiben"
},
"popupHotkeysTooltip": {
"message": "Kattints az elérhető gyorsbillentyűk megtekintéséhez"
"message": "Elérhető gyorsbillentyűk megtekintése"
},
"popupManageTooltip": {
"message": "Shift-kattintás vagy jobb kattintás megnyitja a stíluskezelőt a jelenlegi oldalra érvényesülő stílusokkal"
"message": "Shift+kattintás vagy jobb kattintás: a stíluskezelő megnyitása a jelenlegi oldalra érvényes stílusokkal"
},
"popupMenuButtonTooltip": {
"message": "Műveletmenü"
},
"popupOpenEditInWindow": {
"message": "Szerkesztő megnyitása új ablakban"
@ -641,7 +895,7 @@
"message": "A fül a böngésző ablakától történő leválasztásával is engedélyezhető, és letiltható a fül egy másik ablakhoz való hozzácsatolásával."
},
"popupStylesFirst": {
"message": "Parancsok előtti stílusok"
"message": "Stílusnevek a parancsok előtt"
},
"prefShowBadge": {
"message": "A jelenlegi oldalon aktív stílusok száma"
@ -652,6 +906,9 @@
"previewTooltip": {
"message": "Átmenetileg alkalmazza a változtatásokat mentés nélkül.\nMentsd a stílust, hogy a változtatások véglegesek legyenek!"
},
"readingStyles": {
"message": "Stílusok olvasása..."
},
"replace": {
"message": "Csere"
},
@ -664,6 +921,9 @@
"retrieveBckp": {
"message": "Stílusok importálása"
},
"retrieveDropboxSync": {
"message": "Importálás Dropboxból"
},
"search": {
"message": "Keresés"
},
@ -694,27 +954,21 @@
"searchResultWeeklyCount": {
"message": "Heti telepítések"
},
"searchStyles": {
"message": "Tartalom keresése"
},
"searchStylesHelp": {
"message": "A </> billentyűvel a keresési mezőre ugrasz.\nSima szöveg: keresés a névben, kódban, honlap URL-ben és érvényes oldalakban. A 3 betűnél kevesebb szavak figyelmen kívül vannak hagyva.\nIlleszkedő URL: kezdd a keresést „<url:>”-lel, pl. <url:https://github.com/openstyles/stylus>\nReguláris kifejezések: ide tartoznak a per-jelek és a flagek, pl. </body.*?\\ba\\b/simguy>\nPontos szavak: írj idézőjeleket a keresési kifejezés köré, pl. <\".header ~ div\">"
},
"sectionAdd": {
"message": "Egy újabb szekció hozzáadása"
"message": "Új szakasz hozzáadása"
},
"sectionCode": {
"message": "Kód"
},
"sectionHelp": {
"message": "A szekciók segítenek egy stílus kódrészleteit különböző URL-ekre alkalmazni. Például egyetlen stílus megváltoztathatja egy oldal honlapját egy bizonyos módon, míg az oldal többi részét máshogyan."
},
"sectionRemove": {
"message": "Szekció eltávolítása"
},
"sectionRestore": {
"message": "Eltávolított szekció visszaállítása"
},
"sections": {
"message": "Szekciók"
},
"shortcuts": {
"message": "Gyorsbillentyűk"
},
@ -722,28 +976,28 @@
"message": "Gyorsbillentyűk megadása"
},
"sortDateNewestFirst": {
"message": "a legújabb elöl"
"message": "újabb elöl"
},
"sortDateOldestFirst": {
"message": "a legrégebbi elöl"
"message": "régebbi elöl"
},
"sortLabel": {
"message": "Válaszd ki, hogyan szeretnéd sorba rendezni a telepített stílusokat"
"message": "Telepített stílusok sorbarendezésének módja"
},
"sortLabelTitleAsc": {
"message": "Cím növekvő sorrendben"
"message": "Cím - növekvő sorrend"
},
"sortLabelTitleDesc": {
"message": "Cím csökkenő sorrendben"
"message": "Cím - csökkenő sorrend"
},
"sortStylesHelp": {
"message": "A legördülő menüben válaszd ki, hogyan szeretnéd sorba rendezni a telepített bejegyzéseket. Az alapértelmezett beállítás növekvő sorrend (A-tól Z-ig) a bejegyzések címei szerint. A „Cím csökkenő sorrendben” csoportban megadott rendezési módok csökkenő sorrendet (Z-től A-ig) alkalmaznak a címre.\nMás beállítások azt is lehetővé teszik, hogy több szempont alapján rendezd a bejegyzéseket. Elképzelheted ezt úgy, mint egy többoszlopos rendezési táblázatot, amelyben minden egyes (a plusz jelek között) kiválasztott kategória egy oszlopot vagy egy csoportot jelképez.\nPéldául ha az van beállítva, hogy „Engedélyezve (első) + Cím”, az engedélyezett bejegyzések kerülnek a lista tetejére, aztán az engedélyezett és a letiltott bejegyzések címei külön-külön növekvő sorrendbe A-tól Z-ig) vannak rendezve."
"message": "A legördülő menüben válaszhatjuk ki, hogyan szeretnénk sorbarendezni a telepített stílusok bejegyzéseit. Az alapértelmezett beállítás a bejegyzések címei szerinti növekvő (A-tól Z-ig) sorrend. A „Cím - csökkenő sorrend” csoportban megadott rendezési módok csökkenő (Z-től A-ig) sorrendben rendezik a címeket.\nEgyéb beállításokkal több szempont alapján is rendezhetők a bejegyzések. Tekintsünk erre úgy, mint egy többoszlopos, rendezhető táblázatra, amelyben minden egyes (a plusz jelek között) kiválasztott kategória egy oszlopot vagy egy csoportot jelképez.\nPéldául, ha az van beállítva, hogy „Engedélyezve (első) + Cím”, az engedélyezett bejegyzések kerülnek a lista tetejére, majd az engedélyezett és a letiltott bejegyzések címei külön-külön, növekvő (A-tól Z-ig) sorrendben jelennek meg."
},
"sortStylesHelpTitle": {
"message": "Tartalom rendezése"
},
"styleBadRegexp": {
"message": "Érvénytelen regexp."
"message": "Érvénytelen reguláris kifejezés."
},
"styleBeautify": {
"message": "Csinosít"
@ -799,62 +1053,29 @@
}
}
},
"styleMetaErrorCheckbox": {
"message": "Érvénytelen @var jelölődoboz: az értéknek 0-nak vagy 1-nek kell lennie"
},
"styleMetaErrorColor": {
"message": "A(z) $color$ nem valódi szín",
"placeholders": {
"color": {
"content": "$1"
}
}
},
"styleMetaErrorPreprocessor": {
"message": "Nem támogatott @preprocessor: $preprocessor$",
"placeholders": {
"preprocessor": {
"content": "$1"
}
}
},
"styleMetaErrorSelectValueMismatch": {
"message": "Érvénytelen @select: az érték nem létezik a listában"
},
"styleMissingMeta": {
"message": "Hiányzó metaadat: @$key$",
"placeholders": {
"key": {
"content": "$1"
}
}
},
"styleMissingName": {
"message": "Írj be egy nevet!"
},
"styleMozillaFormatHeading": {
"message": "Mozilla formátum"
"message": "Mozilla-formátum"
},
"styleNotAppliedRegexpProblemTooltip": {
"message": "A stílus nem lett alkalmazva a „regexp()” helytelen használata miatt"
},
"styleRegexpInvalidExplanation": {
"message": "Egyes „regexp()” szabályokat nem lehetett lefordítani."
"message": "Néhány „regexp()” szabály nem lefordítható."
},
"styleRegexpPartialExplanation": {
"message": "Ez a stílus olyan részlegesen illeszkedő reguláris kifejezéseket használ, melyek sértik a<a href='https://developer.mozilla.org/docs/Web/CSS/@document'>CSS4@dokumentumspecifikációt</a>, mely szerint egy teljes URL-illeszkedésre van szükség. Az érintett CSS-szekciók nem kerültek alkalmazásra az oldalon. Ez a stílus valószínűleg a Stylish Chrome-kiegészítőben lett létrehozva, amely helytelenül ellenőrzi a „regexp()” szabályokat az első verziótól fogva (ismert hiba)."
"message": "A stílus olyan részlegesen illeszkedő reguláris kifejezéseket használ, amelyek sértik a<a href='https://developer.mozilla.org/docs/Web/CSS/@document'>CSS4@dokumentumspecifikációt</a>, amely megköveteli az URL teljes illeszkedését. Az érintett CSS-szekciók nem kerültek alkalmazásra az oldalon. A stílus valószínűleg a Stylish-for-Chrome kiegészítőben készült, amely helytelenül ellenőrzi a „regexp()” szabályokat a legelső verziótól fogva (ismert hiba)."
},
"styleRegexpProblemTooltip": {
"message": "A nem alkalmazott szeckiók száma helytelen 'regexp()' használat miatt"
},
"styleRegexpTestButton": {
"message": "RegExp teszt"
},
"styleRegexpTestFull": {
"message": "Illeszkedő fülek"
},
"styleRegexpTestInvalid": {
"message": "Kihagyott érvénytelen reguláris kifejezések"
"message": "Kihagyott, érvénytelen reguláris kifejezések"
},
"styleRegexpTestNone": {
"message": "Nincs illeszkedő fül"
@ -871,9 +1092,6 @@
"styleSaveLabel": {
"message": "Mentés"
},
"styleSectionsTitle": {
"message": "Szekciók"
},
"styleToMozillaFormatHelp": {
"message": "A Mozilla formátumú kódot beküldheted a userstyles.org-ra és használhatod a klasszikus Stylish Firefoxkiegészítővel."
},
@ -897,6 +1115,9 @@
"stylusUnavailableForURLdetails": {
"message": "Biztonsági okokból a böngésző megtiltja, hogy a kiegészítők változtatásokat tegyenek a beépített oldalain (pl. chrome://verzió, a Chrome 61 alapértelmezett „új lap” oldala, about:addons és így tovább) valamint más kiterjesztések oldalain. Ezen kívül mindegyik böngésző korlátozza a saját kiegészítőgalériájának elérését (pl. Chrome webáruház vagy a Mozilla kiegészítők oldala)"
},
"syncDropboxStyles": {
"message": "Exportálás Dropboxba"
},
"syncStorageErrorSaving": {
"message": "Az értéket nem lehet menteni. Próbáld meg csökkenteni a szövegmennyiséget!"
},
@ -915,18 +1136,18 @@
"unreachableAMOHint": {
"message": "Az elérés engedélyezéséhez nyisd meg az <about:config>-ot, kattints jobb egérgombbal, majd „Új”, „Logikai”, és illeszd be, hogy <privacy.resistFingerprinting.block_mozAddonManager>, és kattints az OK-ra,<true> OK, és töltsd újra az oldalt!"
},
"unreachableAMOHintNewFF": {
"message": "A Firefox 60-ban vagy annál újabb verzióban az AMO doménjét is el kell távolítanod a <about:config>-ban levő <extensions.webextensions.restrictedDomains>-ból."
},
"unreachableAMOHintOldFF": {
"message": "Csak a Firefox 59 vagy annál újabb állítható be az ilyen CSP-védett oldalak stílusának átszabása WebExtensions kiegészítőkön keresztül."
},
"unreachableContentScript": {
"message": "Nem sikerült az oldallal történő kommunikáció. Próbáld meg újratölteni az oldalt!"
},
"unreachableFileHint": {
"message": "A Stylus csak akkor képes hozzáférni a file:// URL-ekhez, ha engedélyezed az erre vonatkozó beállítást a Stylus kiegészítőre a chrome://extensions oldalon."
},
"unreachableMozSiteHintOldFF": {
"message": "Csak a Firefox 59 vagy annál újabb állítható be az ilyen CSP-védett oldalak stílusának átszabása WebExtensions kiegészítőkön keresztül."
},
"unzipStyles": {
"message": "Stílusok kibontása..."
},
"updateAllCheckSucceededNoUpdate": {
"message": "Nem találhatók frissítések."
},
@ -968,6 +1189,9 @@
"updatesCurrentlyInstalled": {
"message": "Frissítve:"
},
"uploadingFile": {
"message": "Fájl feltöltése..."
},
"usercssAvoidOverwriting": {
"message": "Kérlek, módosítsd a @name vagy a @namespace értékét, nehogy felül legyen írva az egyik létező stílus!"
},
@ -980,9 +1204,6 @@
"usercssReplaceTemplateConfirmation": {
"message": "Le legyen cserélve az alapértelmezett sablon az új Usercss stílusokhoz a jelenlegi kóddal?"
},
"usercssReplaceTemplateName": {
"message": "Az üres @name lecseréli az alapértelmezett sablont"
},
"usercssReplaceTemplateSectionBody": {
"message": "Írj kódot ide…"
},
@ -994,5 +1215,8 @@
},
"writeStyleForURL": {
"message": "ehhez az URL-hez"
},
"zipStyles": {
"message": "Stílusok tömörítése..."
}
}

View File

@ -1,6 +1,9 @@
{
"InaccessibleFileHint": {
"message": "Stylus non può accedere ad alcuni tipi di file (ad es. pdf & JSON)"
},
"addStyleLabel": {
"message": "Scrivi nuovo stile"
"message": "Scrivi un nuovo stile"
},
"addStyleTitle": {
"message": "Aggiungi stili"
@ -29,7 +32,7 @@
"message": "Utilizza i controlli \"Applica a\" per limitare gli URL a cui viene applicato il codice in questa sezione."
},
"appliesLabel": {
"message": "Vale per"
"message": "Applica a"
},
"appliesLineWidgetLabel": {
"message": "Visualizza info 'Applica a'"
@ -59,7 +62,7 @@
"message": "Autore"
},
"backupMessage": {
"message": "Seleziona un file o trascinalo in questa pagina."
"message": "Per importare il file di backup, selezionalo e rilascialo in questa pagina oppure utilizza il bottone Importa\n\nPer esportare un backup compatibile con Stylus versione 1.5.18 e oltre, selezionalo col tasto destro oppure shift-click il pulsante Esporta"
},
"bckpInstStyles": {
"message": "Esporta stili"
@ -77,7 +80,13 @@
"message": "Verifica in corso..."
},
"clickToUninstall": {
"message": "Clica per disinstallare"
"message": "Clicca per disinstallare"
},
"cm_autoCloseBrackets": {
"message": "Chiudi automaticamente parentesi e virgolette"
},
"cm_autoCloseBracketsTooltip": {
"message": "Aggiungi automaticamente il simbolo di chiusura quando apri ()[]{}''\"\""
},
"cm_autocompleteOnTyping": {
"message": "Completamento automatico durante digitazione"
@ -86,7 +95,7 @@
"message": "Selezionatore colore per colori CSS"
},
"cm_indentWithTabs": {
"message": "Usa schede con indentazione intellingente"
"message": "Usa tabulazioni con indentazione intellingente"
},
"cm_keyMap": {
"message": "Mappa caratteri"
@ -97,15 +106,30 @@
"cm_matchHighlightSelection": {
"message": "Solo selezione"
},
"cm_matchHighlightToken": {
"message": "Token sotto il puntatore"
},
"cm_resizeGripHint": {
"message": "Doppio click per massimizzare/ripristinare l'altezza"
},
"cm_selectByTokens": {
"message": "Doppio click seleziona i token"
},
"cm_selectByTokensTooltip": {
"message": "Esempi di token: foo-bar-2 #aabbcc 0.32 !important\nQuando disabilitato: vengono selezionate le parole delimitate dalla punteggiatura."
},
"cm_smartIndent": {
"message": "Usa indentazione intelligente"
},
"cm_tabSize": {
"message": "Dimensione scheda"
"message": "Dimensione tabulazione"
},
"cm_theme": {
"message": "Tema"
},
"colorpickerSwitchFormatTooltip": {
"message": "Cambia formato: HEX -> RGB -> HSL.\nShift-click per invertire la direzione.\nAnche con i tasti PgUp (PaginaSu), PgDn (PaginaGiù)."
},
"colorpickerTooltip": {
"message": "Apri selettore colore"
},
@ -142,6 +166,18 @@
"confirmYes": {
"message": "Si"
},
"connectingDropboxNotAllowed": {
"message": "La connessione a Dropbox è disponibile soltanto nelle app installate direttamente dal webstore"
},
"copied": {
"message": "Copiato negli appunti"
},
"copy": {
"message": "Copia negli appunti"
},
"customNameHint": {
"message": "Inserisci qui un nome personalizzato per rinominare lo stile nell'interfaccia utente (UI) senza rompere i suoi aggiornamenti"
},
"dateInstalled": {
"message": "Data installazione"
},
@ -172,6 +208,9 @@
"editDeleteText": {
"message": "Elimina"
},
"editGotoLine": {
"message": "Vai alla riga (oppure riga:col)"
},
"editStyleHeading": {
"message": "Modifica di stili"
},
@ -186,9 +225,6 @@
}
}
},
"editorStylesButton": {
"message": "Cerca stili editor"
},
"enableStyleLabel": {
"message": "Attiva"
},
@ -215,15 +251,12 @@
}
}
},
"filteredStylesAllHidden": {
"message": "Nessuno stile corrispondente ai filtri applicati"
},
"findStyles": {
"message": "Trova stili"
},
"findStylesForSite": {
"message": "Trova più stili per questo sito"
},
"findStylesInlineTooltip": {
"message": "Visualizza risultati in questa finestra."
},
"genericAdd": {
"message": "Aggiungi"
},
@ -266,6 +299,9 @@
"helpKeyMapCommand": {
"message": "Inserisci il nome di un comando"
},
"helpKeyMapHotkey": {
"message": "Digita una scorciatoia"
},
"importAppendLabel": {
"message": "Aggiungi ad uno stile"
},
@ -349,6 +385,17 @@
"linkTranslate": {
"message": "Traduci"
},
"linterCSSLintSettings": {
"message": "(Imposta regola: 0 = disabilitata; 1 = warning; 2 = errore)"
},
"linterConfigPopupTitle": {
"message": "Imposta configurazione delle regole di $linter$",
"placeholders": {
"linter": {
"content": "$1"
}
}
},
"linterInvalidConfigError": {
"message": "Non salvato a causa di queste impostazioni di configurazione non valide:"
},
@ -364,9 +411,6 @@
"liveReloadLabel": {
"message": "Ricaricamento live"
},
"manageFaviconsHelp": {
"message": "Stylus utilizza un servizio esterno https://www.google.com/s2/favicons"
},
"manageFilters": {
"message": "Filtri"
},
@ -403,16 +447,57 @@
"menuShowBadge": {
"message": "Mostra contatore stili attivi"
},
"meta_invalidNumber": {
"message": "Atteso un numero"
},
"meta_invalidString": {
"message": "Attesa una stringa tra apici"
},
"meta_invalidWord": {
"message": "Attesa una parola"
},
"meta_missingChar": {
"message": "Attesi caratteri: $chars$",
"placeholders": {
"chars": {
"content": "$1"
}
}
},
"meta_missingEOT": {
"message": "Atteso EOT"
},
"meta_missingMandatory": {
"message": "Manca metadata obbligatorio: $keys$",
"placeholders": {
"keys": {
"content": "$1"
}
}
},
"meta_unknownJSONLiteral": {
"message": "JSON non valido: $literal$ non è un letterale JSON valido",
"placeholders": {
"literal": {
"content": "$1"
}
}
},
"meta_unknownMeta": {
"message": "metadata sconosciuto: $key$",
"placeholders": {
"key": {
"content": "$1"
}
}
},
"noStylesForSite": {
"message": "Nessuno stile installato per questo sito."
},
"openManage": {
"message": "Gestisci gli stili installati"
},
"openOptionsManage": {
"message": "Opzioni UI"
},
"openOptionsPopup": {
"openOptions": {
"message": "Opzioni"
},
"openStylesManager": {
@ -493,6 +578,9 @@
"popupBordersTooltip": {
"message": "Utile per temi scuri nelle nuove versioni di Chrome dato che non colora più i bordi laterali"
},
"popupHotkeysTooltip": {
"message": "Click per visualizzare le scorciatoie disponibili"
},
"popupManageTooltip": {
"message": "Shift-click o click destro apre il gestore con gli stili applicabili per il sito corrente"
},
@ -523,6 +611,9 @@
"search": {
"message": "Cerca"
},
"searchNumberOfResults": {
"message": "Numero di occorrenze"
},
"searchResultInstallCount": {
"message": "Installazioni totali"
},
@ -538,21 +629,18 @@
"searchResultWeeklyCount": {
"message": "Installazioni settimanali"
},
"searchStyles": {
"message": "Contenuti ricerca"
},
"sectionAdd": {
"message": "Aggiungi un'altra sezione"
},
"sectionCode": {
"message": "Codice"
},
"sectionHelp": {
"message": "Le sezioni consentono di definire diverse parti di codice da applicare a diversi insiemi di URL dello stesso stile. Ad esempio, un unico stile potrebbe modificare la home page di un sito diversamente da come modificherebbe il resto del sito."
},
"sectionRemove": {
"message": "Rimuovi sezione"
},
"sections": {
"message": "Sezioni"
},
"shortcuts": {
"message": "Scorciatoie"
},
@ -577,6 +665,9 @@
"styleBadRegexp": {
"message": "Regexp non valida."
},
"styleBeautifyPreserveNewlines": {
"message": "Mantieni gli \"a capo\""
},
"styleCancelEditLabel": {
"message": "Torna a gestione"
},
@ -622,29 +713,27 @@
}
}
},
"styleMetaErrorColor": {
"message": "$color$ non è un colore valido",
"placeholders": {
"color": {
"content": "$1"
}
}
},
"styleMissingName": {
"message": "Inserisci un nome"
},
"styleMozillaFormatHeading": {
"message": "Formato Mozilla"
},
"styleRegexpTestButton": {
"message": "Test RegExp"
"styleNotAppliedRegexpProblemTooltip": {
"message": "Lo stile non è stato applicato a causa dell'uso errato di 'regexp()'"
},
"styleRegexpInvalidExplanation": {
"message": "Qualche regola 'regexp()' che non può essere compilata."
},
"styleRegexpProblemTooltip": {
"message": "Numero di sezioni non applicato a causa dell'uso errato di 'regexp()'"
},
"styleRegexpTestInvalid": {
"message": "Regexp invalida ignorata"
},
"styleSaveLabel": {
"message": "Salva"
},
"styleSectionsTitle": {
"message": "Sezioni"
},
"styleToMozillaFormatHelp": {
"message": "Il formato Mozilla del codice può essere utilizzato con Stylish per Firefox e può essere inviato a userstyles.org."
},
@ -671,12 +760,15 @@
"undoGlobal": {
"message": "Annulla in tutte le sezioni"
},
"unreachableAMOHintOldFF": {
"message": "Solo Firefox 59 e successivi possono essere configurati per consentire alle WebExtensions di aggiungere elementi degli stili su siti CSP-protected come questo."
"unreachableAMO": {
"message": "Firefox proibisce l'accesso al sito."
},
"unreachableContentScript": {
"message": "Impossibile comunicare con la pagina. Prova a ricaricare la scheda."
},
"unreachableMozSiteHintOldFF": {
"message": "Solo Firefox 59 e successivi possono essere configurati per consentire alle WebExtensions di aggiungere elementi degli stili su siti CSP-protected come questo."
},
"updateAllCheckSucceededNoUpdate": {
"message": "Nessun aggiornamento trovato"
},

File diff suppressed because it is too large Load Diff

1356
_locales/ko/messages.json Normal file

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -1,4 +1,7 @@
{
"InaccessibleFileHint": {
"message": "Stylus não pode acessar alguns tipos de arquivos (ex: arquivos pdf e json)."
},
"addStyleLabel": {
"message": "Escrever novo estilo"
},
@ -64,9 +67,6 @@
"backupButtons": {
"message": "Cópia de segurança"
},
"backupMessage": {
"message": "Selecione um ficheiro ou arraste e solte-o nesta página."
},
"bckpInstStyles": {
"message": "Exportar estilos"
},
@ -133,6 +133,9 @@
"cm_theme": {
"message": "Tema"
},
"colorpickerPaletteHint": {
"message": "Clique com o botão direito em uma amostra para percorrer suas linhas de código"
},
"colorpickerSwitchFormatTooltip": {
"message": "Mudar formatos: HEX -> RGB -> HSL.\nShift-clique para inverter a direção.\nTambém através das teclas PgUp (PageUp), PgDn (PageDown)."
},
@ -178,6 +181,32 @@
"confirmYes": {
"message": "Sim"
},
"connectingDropbox": {
"message": "Conectando ao Dropbox..."
},
"connectingDropboxNotAllowed": {
"message": "Conectar ao Dropbox somente é disponível em apps instalados diretamente da loja web"
},
"copied": {
"message": "Copiado para a área de transferência"
},
"copy": {
"message": "Copiar para a área de transferência"
},
"customNameHint": {
"message": "Digite um nome personalizado para renomear o estilo na UI sem quebrar suas atualizações"
},
"customNameResetHint": {
"message": "Deixar de usar o nome personalizado, usar o próprio nome do estilo"
},
"dateAbbrYear": {
"message": "$value$a",
"placeholders": {
"value": {
"content": "$1"
}
}
},
"dateInstalled": {
"message": "Data de instalação"
},
@ -208,6 +237,9 @@
"dragDropMessage": {
"message": "Solte o ficheiro da sua cópia de segurança em qualquer sítio nesta página para importar."
},
"dragDropUsercssTabstrip": {
"message": "Para instalar o arquivo, solte-o na linha das abas (a área onde os títulos das abas são mostrados)."
},
"editDeleteText": {
"message": "Eliminar"
},
@ -228,15 +260,21 @@
}
}
},
"editorStylesButton": {
"message": "Encontrar estilos para o editor"
},
"enableStyleLabel": {
"message": "Ativar"
},
"excludeStyleByDomainLabel": {
"message": "Excluir o domínio atual"
},
"excludeStyleByUrlLabel": {
"message": "Excluir a URL atual"
},
"exportLabel": {
"message": "Exportar"
},
"exportSavedSuccess": {
"message": "Arquivo salvo com sucesso"
},
"externalLink": {
"message": "Hiperligação externa"
},
@ -263,15 +301,6 @@
"findStyles": {
"message": "Localizar estilos"
},
"findStylesForSite": {
"message": "Encontrar mais estilos para este site"
},
"findStylesInline": {
"message": "inline"
},
"findStylesInlineTooltip": {
"message": "Exibir os resultados da pesquisa dentro desta janela."
},
"genericAdd": {
"message": "Adicionar"
},
@ -305,6 +334,9 @@
"genericUnknown": {
"message": "Desconhecido"
},
"gettingStyles": {
"message": "Obtendo todos os estilos..."
},
"helpAlt": {
"message": "Ajuda"
},
@ -314,6 +346,9 @@
"helpKeyMapHotkey": {
"message": "Prima uma tecla de atalho"
},
"hostDisabled": {
"message": "Este hospedeiro foi desabilitado devido a um bug na versão atual que o navegador utilizado se encontra"
},
"importAppendLabel": {
"message": "Acrescentar ao estilo"
},
@ -323,6 +358,12 @@
"importLabel": {
"message": "Importar"
},
"importPreprocessor": {
"message": "Estilos com <code>@preprocessor</code> não irão funcionar no modo clássico. Você pode trocar o editor para o modo UserCSS: 1) abre o gerenciador de estilos, 2) ative a caixa \"como UserCSS\", 3) clique \"Escrever novo estilo\"\n\nDeseja importar mesmo assim?"
},
"importPreprocessorTitle": {
"message": "Possível problema causado pelo @preprocessor"
},
"importReplaceLabel": {
"message": "Sobrescrever estilo"
},
@ -385,9 +426,6 @@
"installUpdateFromLabel": {
"message": "Procurar atualizações"
},
"installUpdateUnavailable": {
"message": "Para ativar a verificação de atualizações, solte o ficheiro na faixa de separadores ou especifique @updateURL nos metadados de estilo."
},
"license": {
"message": "Licença"
},
@ -449,14 +487,14 @@
"message": "Ocorreu um erro ao vigiar o arquivo"
},
"liveReloadInstallHint": {
"message": "O recarregamento dinâmico está ativado para que o estilo instalado seja atualizado automaticamente em alterações externas enquanto esse separador e o separador do arquivo de origem estiverem abertos."
"message": "Mantenha esta guia aberta para atualizar automaticamente o estilo sob mudanças externas."
},
"liveReloadInstallHintFF": {
"message": "Mantenha juntamente esta guia e a guia original abertas para atualizar automaticamente o estilo sob mudanças externas."
},
"liveReloadLabel": {
"message": "Recarregamento dinâmico"
},
"liveReloadUnavailable": {
"message": "Para ativar recarregamento dinâmico, solte o ficheiro na faixa de separadores (a área onde os títulos dos separadores são mostrados)."
},
"manageFavicons": {
"message": "Favicons em colunas de aplica-se a"
},
@ -464,7 +502,7 @@
"message": "Acinzentado(s)"
},
"manageFaviconsHelp": {
"message": "O Stylus usa um serviço externo https://www.google.com/s2/favicons"
"message": "O Stylus usa um serviço externo https://icons.duckduckgo.com"
},
"manageFilters": {
"message": "Filtros"
@ -508,16 +546,71 @@
"menuShowBadge": {
"message": "Mostrar a contagem de estilos ativados"
},
"meta_invalidCheckboxDefault": {
"message": "Inválida @var checkbox: o valor deve ser 0 ou 1"
},
"meta_invalidNumber": {
"message": "Espera-se um número"
},
"meta_invalidRange": {
"message": "@var inválido $type$: valor deve ser um número ou um vetor",
"placeholders": {
"type": {
"content": "$1"
}
}
},
"meta_invalidString": {
"message": "Espera-se um texto entre aspas"
},
"meta_invalidWord": {
"message": "Espera-se uma palavra"
},
"meta_missingChar": {
"message": "Caracteres esperados: $chars$",
"placeholders": {
"chars": {
"content": "$1"
}
}
},
"meta_missingMandatory": {
"message": "Metadata obrigatório não encontrado: $keys$",
"placeholders": {
"keys": {
"content": "$1"
}
}
},
"meta_unknownMeta": {
"message": "Metadata desconhecido: $key$",
"placeholders": {
"key": {
"content": "$1"
}
}
},
"meta_unknownVarType": {
"message": "Tipo desconhecido da variável @$varkey$: $vartype$",
"placeholders": {
"varkey": {
"content": "$1"
},
"vartype": {
"content": "$2"
}
}
},
"noFileToImport": {
"message": "Para importar seus estilos, você deve exportar primeiro."
},
"noStylesForSite": {
"message": "Nenhum estilo instalado para este site."
},
"openManage": {
"message": "Gerir"
},
"openOptionsManage": {
"message": "interface de Opções"
},
"openOptionsPopup": {
"openOptions": {
"message": "Opções"
},
"openStylesManager": {
@ -535,9 +628,6 @@
"optionsAdvancedExposeIframes": {
"message": "Expor iframes via HTML[stylus-iframe]"
},
"optionsAdvancedExposeIframesNote": {
"message": "Expõe o domínio do site principal em cada iframe.\nPermite escrever CSS específico para iframe assim:\nhtml [stylus-iframe $ = \"twitter.com\"] h1 {display: none}"
},
"optionsAdvancedNewStyleAsUsercss": {
"message": "Escrever novo estilo como usercss"
},
@ -559,6 +649,9 @@
"optionsCustomizeIcon": {
"message": "Ícone da barra de ferramentas"
},
"optionsCustomizeSync": {
"message": "Sincronizar para a nuvem"
},
"optionsCustomizeUpdate": {
"message": "Atualizações"
},
@ -589,12 +682,42 @@
"optionsSubheading": {
"message": "Mais Opções"
},
"optionsSyncConnect": {
"message": "Conectar"
},
"optionsSyncDisconnect": {
"message": "Desconectar"
},
"optionsSyncNone": {
"message": "Nenhum"
},
"optionsSyncStatusConnected": {
"message": "Conectado"
},
"optionsSyncStatusConnecting": {
"message": "Conectando..."
},
"optionsSyncStatusDisconnected": {
"message": "Desconectado"
},
"optionsSyncStatusDisconnecting": {
"message": "Desconectando..."
},
"optionsSyncStatusSyncing": {
"message": "Sincronizando..."
},
"optionsSyncSyncNow": {
"message": "Sincronizar agora"
},
"optionsUpdateImportNote": {
"message": "Ao importar uma cópia de segurança de estilo da versão antiga ou do Stylish, faça uma verificação única de atualizações manualmente no gestor de estilos para garantir que todos os estilos sejam atualizados."
},
"optionsUpdateInterval": {
"message": "Intervalo de atualização automática do estilo de usuário em horas (especifique 0 para desativar)"
},
"overwriteFileExport": {
"message": "Você gostaria de substituir um arquivo existente?"
},
"paginationCurrent": {
"message": "Pagina atual"
},
@ -646,6 +769,9 @@
"previewTooltip": {
"message": "Temporariamente aplica as alterações sem guardar.\nGuarde o estilo para tornar as alterações permanentes."
},
"readingStyles": {
"message": "Lendo estilos..."
},
"replace": {
"message": "Substituir"
},
@ -658,6 +784,9 @@
"retrieveBckp": {
"message": "Importar estilos"
},
"retrieveDropboxSync": {
"message": "Importar do Dropbox"
},
"search": {
"message": "Pesquisar"
},
@ -688,27 +817,21 @@
"searchResultWeeklyCount": {
"message": "Instalações semanais"
},
"searchStyles": {
"message": "Pesquisar conteúdos"
},
"searchStylesHelp": {
"message": "A tecla </> foca o campo de pesquisa.\nTexto simples: pesquisa dentro do nome, código, URL da homepage e aplica-se a sites . Palavras com menos de 3 letras são ignoradas.\nEstilos que correspondem a um URL completo: prefixe a pesquisa com<url:>, e.g. <url:https://github.com/openstyles/stylus>\nExpressões regulares: inclua barras e sinalizadores, e.g. </body.*?\\ba\\b/simguy>\nPalavras exatas: envolva a consulta entre aspas duplas, e.g. <\". header ~ div\">"
},
"sectionAdd": {
"message": "Adicionar outra secção"
},
"sectionCode": {
"message": "Código"
},
"sectionHelp": {
"message": "As secções deixam-lhe definir diferentes peças de código a aplicar em diferentes conjuntos de URLs no mesmo estilo. Por exemplo, um só estilo pode mudar a homepage de um site de uma maneira, enquanto muda o resto do site de outra maneira."
},
"sectionRemove": {
"message": "Remover secção"
},
"sectionRestore": {
"message": "Restaurar secção removida"
},
"sections": {
"message": "Secções"
},
"shortcuts": {
"message": "Atalhos"
},
@ -742,6 +865,9 @@
"styleBeautify": {
"message": "Embelezar"
},
"styleBeautifyHint": {
"message": "Dica: clique com o botão direito no botão \"Embelezar\" ou use o atalho de teclado definido para embelezar sem mostrar esse painel"
},
"styleBeautifyIndentConditional": {
"message": "Indentar @media, @supports"
},
@ -793,36 +919,6 @@
}
}
},
"styleMetaErrorCheckbox": {
"message": "Inválida @var checkbox: o valor deve ser 0 ou 1"
},
"styleMetaErrorColor": {
"message": "$color$não é uma cor válida",
"placeholders": {
"color": {
"content": "$1"
}
}
},
"styleMetaErrorPreprocessor": {
"message": "Não e suportado o @preprocessador:$preprocessor$",
"placeholders": {
"preprocessor": {
"content": "$1"
}
}
},
"styleMetaErrorSelectValueMismatch": {
"message": "Inválido @select: o valor não existe na lista"
},
"styleMissingMeta": {
"message": "Metadados em falta @$key$",
"placeholders": {
"key": {
"content": "$1"
}
}
},
"styleMissingName": {
"message": "Introduzir um nome"
},
@ -841,9 +937,6 @@
"styleRegexpProblemTooltip": {
"message": "Número de secções não aplicadas devido ao uso incorreto de 'regexp ()'"
},
"styleRegexpTestButton": {
"message": "Testar RegExp"
},
"styleRegexpTestFull": {
"message": "Separadores correspondentes"
},
@ -865,9 +958,6 @@
"styleSaveLabel": {
"message": "Guardar"
},
"styleSectionsTitle": {
"message": "Secções"
},
"styleToMozillaFormatHelp": {
"message": "O formato Mozilla do código pode ser submetido aouserstyles.org e usado com o clássico Stylish para o Firefox"
},
@ -891,6 +981,9 @@
"stylusUnavailableForURLdetails": {
"message": "Como precaução de segurança, o browser proíbe extensões de afetar as páginas embutidas (como chrome://version, a página de novo separador predefinido no Chrome 61, about:addons, e assim sucessivamente) tal como páginas de outras extensões. Cada browser também restringe acesso à sua própria galeria de extensões (como Chrome Web Store ou AMO)"
},
"syncDropboxStyles": {
"message": "Exportar para Dropbox"
},
"syncStorageErrorSaving": {
"message": "O valor não pode ser guardado. Tente reduzir a quantidade de texto."
},
@ -909,18 +1002,18 @@
"unreachableAMOHint": {
"message": "Para permitir o acesso abra<about:config>,clique com o botão direito na lista, clique em \"Novo\", depois em \"Booleano\", cole <privacy.resistFingerprinting.block_mozAddonManager> e clique em OK,<true>,OK, recarregue a página <addons.mozilla.org>."
},
"unreachableAMOHintNewFF": {
"message": "No Firefox 60 e mais recente, você também terá que remover o domínio AMO de <extensions.webextensions.restrictedDomains> em <about:config>."
},
"unreachableAMOHintOldFF": {
"message": "Somente o Firefox 59 e o mais recente podem ser configurados para permitir que WebExtensions incluam elementos de estilo em sites protegidos por CSP como este."
},
"unreachableContentScript": {
"message": "Não foi possível comunicar com a página. Tente recarregar o separador."
},
"unreachableFileHint": {
"message": "O Stylus pode aceder URLs file:// apenas se ativar a checkbox correspondente para a extensão Stylus na página chrome://extensions"
},
"unreachableMozSiteHintOldFF": {
"message": "Somente o Firefox 59 e o mais recente podem ser configurados para permitir que WebExtensions incluam elementos de estilo em sites protegidos por CSP como este."
},
"unzipStyles": {
"message": "Descompactando estilos..."
},
"updateAllCheckSucceededNoUpdate": {
"message": "Nenhuma atualização encontrada."
},
@ -962,6 +1055,9 @@
"updatesCurrentlyInstalled": {
"message": "Atualizações instaladas:"
},
"uploadingFile": {
"message": "Enviando arquivo..."
},
"usercssAvoidOverwriting": {
"message": "Por favor modifique o valor de @name ou @namespace para evitar sobrescrever um estilo existente."
},
@ -974,9 +1070,6 @@
"usercssReplaceTemplateConfirmation": {
"message": "Substituir o modelo predefinido para novos estilos de Usercss com o código atual?"
},
"usercssReplaceTemplateName": {
"message": "@name vazio substitui o modelo predefinido"
},
"usercssReplaceTemplateSectionBody": {
"message": "Insira o código aqui..."
},
@ -988,5 +1081,8 @@
},
"writeStyleForURL": {
"message": "este URL"
},
"zipStyles": {
"message": "Compactando estilos..."
}
}

View File

@ -61,9 +61,6 @@
"author": {
"message": "Autor"
},
"backupMessage": {
"message": "Selectați un fișier sau drag-and-drop aici"
},
"bckpInstStyles": {
"message": "Exportați teme"
},
@ -204,9 +201,6 @@
}
}
},
"editorStylesButton": {
"message": "Găsiți teme pentru editor"
},
"enableStyleLabel": {
"message": "Activați"
},
@ -236,12 +230,6 @@
"findStyles": {
"message": "Găsiți teme"
},
"findStylesForSite": {
"message": "Gasiți mai multe teme pentru acest site."
},
"findStylesInlineTooltip": {
"message": "Arătați rezultatele căutării în această pagină."
},
"genericAdd": {
"message": "Adaugă"
},
@ -352,9 +340,6 @@
"installUpdateFromLabel": {
"message": "Verificați update-urile"
},
"installUpdateUnavailable": {
"message": "Pentru a activa verificarea de updates. trage fișierul pe taburi (zona cu titluri) sau specifica @updateURL în metadata temei."
},
"license": {
"message": "Licență"
},
@ -412,12 +397,6 @@
"liveReloadError": {
"message": "A avut loc o eroare în timpul monitorizării acestui fișier"
},
"liveReloadInstallHint": {
"message": "Reload automat este activat deci tema instalată va fi updatată automat când acest tab si fișierul surca sunt deschise."
},
"liveReloadUnavailable": {
"message": "Pentru a activa live reload (refresh automat), trage fișierul pe taburi (zona unde titlurile temelor sunt afișate) "
},
"manageFavicons": {
"message": "Favicons în coloana 'se aplică la'"
},
@ -425,7 +404,7 @@
"message": "Hașurat"
},
"manageFaviconsHelp": {
"message": "Stylus folosește un serviciu extern https://www.google.com/s2/favicons"
"message": "Stylus folosește un serviciu extern https://icons.duckduckgo.com"
},
"manageFilters": {
"message": "Filtre"
@ -466,16 +445,16 @@
"menuShowBadge": {
"message": "Afișați numărul temelor active"
},
"meta_invalidCheckboxDefault": {
"message": "@var checkbox invalidă: valuarea trebuie să fie 0 sau 1"
},
"noStylesForSite": {
"message": "Nicio temă instalată pentru acest site."
},
"openManage": {
"message": "Managerul"
},
"openOptionsManage": {
"message": "UI cu opțiuni"
},
"openOptionsPopup": {
"openOptions": {
"message": "Opțiuni"
},
"openStylesManager": {
@ -493,9 +472,6 @@
"optionsAdvancedExposeIframes": {
"message": "Expuneți iframes via HTML[stylus-iframe]"
},
"optionsAdvancedExposeIframesNote": {
"message": "Expune domain-ul site-ului in fiecare iframe.\nActivează scrierea de CSS specific pentru iframe precum:\nhtml[stylus-iframe$=\"twitter.com\"] h1 { display:none }"
},
"optionsAdvancedNewStyleAsUsercss": {
"message": "Scrieți temă nouă în formatul usercss"
},
@ -634,27 +610,21 @@
"searchResultWeeklyCount": {
"message": "Instalări săptămânale"
},
"searchStyles": {
"message": "Conținutul căutării"
},
"searchStylesHelp": {
"message": "</> aduce în focus bara de căutare.\nText simplu: caută în nume, cod, homepage URL și site-uri la care se aplică tema. Cuvinte cu mai puțin de 3 litere sunt ignorate.\nURL găsit: adaugă căutarea ca prefix <url:>, ex. <url:https://github.com/openstyles/stylus>\nExpresii obișnuite: include slashes și flags, ex. </body.*?\\ba\\b/simguy>\nCuvinte exacte: introduceți între ghilimele, ex. <\".header ~ div\">"
},
"sectionAdd": {
"message": "Adăugați o altă secțiune"
},
"sectionCode": {
"message": "Cod"
},
"sectionHelp": {
"message": "Secțiunile permit definirea de bucăți de cod aplicabile la URL diferite, în cadrul aceleași teme. Spre exemplu, o singură temă poate modifica pagina de pornire a unui site într-un mod diferit de restul site-ului."
},
"sectionRemove": {
"message": "Ștergeți secțiunea"
},
"sectionRestore": {
"message": "Restaurează o secțiune ștearsă"
},
"sections": {
"message": "Secțiuni"
},
"shortcutsNote": {
"message": "Creeați keyboard shortcuts"
},
@ -736,36 +706,6 @@
}
}
},
"styleMetaErrorCheckbox": {
"message": "@var checkbox invalidă: valuarea trebuie să fie 0 sau 1"
},
"styleMetaErrorColor": {
"message": "$color$ nu este o culoare validă",
"placeholders": {
"color": {
"content": "$1"
}
}
},
"styleMetaErrorPreprocessor": {
"message": "@preprocessor nesuportat: $preprocessor$",
"placeholders": {
"preprocessor": {
"content": "$1"
}
}
},
"styleMetaErrorSelectValueMismatch": {
"message": "Valoare @select invalidă: valoarea nu există în listă"
},
"styleMissingMeta": {
"message": "Metadata nu a fost găsită @$key$",
"placeholders": {
"key": {
"content": "$1"
}
}
},
"styleMissingName": {
"message": "Introduceți un nume"
},
@ -805,9 +745,6 @@
"styleSaveLabel": {
"message": "Salvați"
},
"styleSectionsTitle": {
"message": "Secțiuni"
},
"styleToMozillaFormatHelp": {
"message": "Formatul Mozilla al codului poate fi uploadat pe userstyles.org și folosit de clasicul Stylish pentru Firefox."
},
@ -846,18 +783,15 @@
"unreachableAMOHint": {
"message": "Pentru a permite accesul deschideți <about:config>, right-click pe listă, click 'New', apoi 'Boolean', paste <privacy.resistFingerprinting.block_mozAddonManager> și click OK, <true>, OK, reâncărcați pagina <addons.mozilla.org>."
},
"unreachableAMOHintNewFF": {
"message": "În Firefox 60+ va trebui sa fie șters domain-ul AMO din <extensions.webextensions.restrictedDomains> din <about:config>."
},
"unreachableAMOHintOldFF": {
"message": "Doar Firefox 59 sau mai nou poate fi configurat să permită WebExtension-urilor să adauge elemente la site-uri CSP-protected precum acesta."
},
"unreachableContentScript": {
"message": "Nu s-a putut comunica cu pagina. Reîncărcați tabul."
},
"unreachableFileHint": {
"message": "Stylus poate accesa file:// URLs doar când este activată opțiunea respectivă din pagina cu setări chrome://extensions"
},
"unreachableMozSiteHintOldFF": {
"message": "Doar Firefox 59 sau mai nou poate fi configurat să permită WebExtension-urilor să adauge elemente la site-uri CSP-protected precum acesta."
},
"updateAllCheckSucceededNoUpdate": {
"message": "Niciun update găsit."
},
@ -911,9 +845,6 @@
"usercssReplaceTemplateConfirmation": {
"message": "Înlocuiți tema de bază a formatului Usercss cu acest cod?"
},
"usercssReplaceTemplateName": {
"message": "@name este gol și înlocuiețte valoarea de bază"
},
"usercssReplaceTemplateSectionBody": {
"message": "Introduce cod aici..."
},

File diff suppressed because it is too large Load Diff

View File

@ -76,9 +76,15 @@
"cm_theme": {
"message": "Тема"
},
"confirmDelete": {
"message": "Избриши"
},
"confirmNo": {
"message": "Не"
},
"confirmSave": {
"message": "Сачувај"
},
"confirmStop": {
"message": "Заустави"
},
@ -106,6 +112,9 @@
"disableStyleLabel": {
"message": "Онемогући"
},
"editDeleteText": {
"message": "Избриши"
},
"editGotoLine": {
"message": "Иди на ред (или line:col)"
},
@ -129,8 +138,11 @@
"exportLabel": {
"message": "Извези"
},
"findStylesForSite": {
"message": "Пронађи још стилова за овај сајт"
"genericAdd": {
"message": "Додај"
},
"genericEnabledLabel": {
"message": "Омогућено"
},
"helpAlt": {
"message": "Помоћ"
@ -194,9 +206,15 @@
"openManage": {
"message": "Управљај инсталираним стиловима"
},
"openOptions": {
"message": "Опције"
},
"optionsHeading": {
"message": "Опције"
},
"optionsSyncUrl": {
"message": "УРЛ"
},
"popupStylesFirst": {
"message": "Излистај стилове пре команди у менију дугмета на алатној траци"
},
@ -218,21 +236,18 @@
"searchRegexp": {
"message": "Користи /re/ синтаксу за претрагу регуларним изразом"
},
"searchStyles": {
"message": "Претражи садржај"
},
"sectionAdd": {
"message": "Додај нови одељак"
},
"sectionCode": {
"message": "Код"
},
"sectionHelp": {
"message": "Одељци вам омогућавају да дефинишете различите делове кода који се примењују на раличите скупове УРЛ-ова у истом стилу. На пример, један исти стил може променити почетну страницу једног сајта на један начин а остатак сајта на други начин."
},
"sectionRemove": {
"message": "Уклони одељак"
},
"sections": {
"message": "Одељци"
},
"styleBadRegexp": {
"message": "Регуларни израз је неисправан."
},
@ -268,9 +283,6 @@
"styleSaveLabel": {
"message": "Сачувај"
},
"styleSectionsTitle": {
"message": "Одељци"
},
"styleToMozillaFormatHelp": {
"message": "Mozilla формат кода се може користити у Stylish за Firefox и може се послати на userstyles.org."
},

File diff suppressed because it is too large Load Diff

View File

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

View File

@ -5,6 +5,9 @@
"addStyleTitle": {
"message": "Stil Ekleyin"
},
"alphaChannel": {
"message": "Opaklık"
},
"appliesAdd": {
"message": "Ekleyin"
},
@ -28,6 +31,9 @@
"appliesLabel": {
"message": "Şuraya uygulanır"
},
"appliesLineWidgetWarning": {
"message": "Küçültülmüş CSS ile çalışmaz"
},
"appliesRegexpOption": {
"message": "regexp ile eşleşen URL'ler"
},
@ -43,15 +49,153 @@
"appliesUrlPrefixOption": {
"message": "Şununla başlayan URL'ler:"
},
"applyAllUpdates": {
"message": "Tüm güncellemeleri uygula"
},
"author": {
"message": "Yazar"
},
"backupButtons": {
"message": "Yedek"
},
"bckpInstStyles": {
"message": "Dışa aktar"
},
"checkAllUpdates": {
"message": "Tüm stiller için güncellemeleri denetle"
},
"checkAllUpdatesForce": {
"message": "Tekrar kontrol et, herhangi bir stil düzenlemedim!"
},
"checkForUpdate": {
"message": "Güncellemeleri denetle"
},
"checkingForUpdate": {
"message": "Kontrol ediliyor..."
},
"clickToUninstall": {
"message": "Kaldırmak için tıklayın"
},
"cm_autoCloseBrackets": {
"message": "Parantezleri ve tırnak işaretlerini otomatik olarak kapat"
},
"cm_autoCloseBracketsTooltip": {
"message": "()[]{}''\"\" Açılışlarından birini yazarken otomatik olarak bir kapanış çifti ekleyin"
},
"cm_autocompleteOnTyping": {
"message": "Yazarken tamamla"
},
"cm_colorpicker": {
"message": "CSS renk seçicileri"
},
"cm_indentWithTabs": {
"message": "Akıllı girintili tab kullan"
},
"cm_keyMap": {
"message": "Tuşeşlem"
},
"cm_lineWrapping": {
"message": "Kelime kaydırma"
},
"cm_matchHighlight": {
"message": "Vurgulama"
},
"cm_matchHighlightSelection": {
"message": "Yalnızca seçim"
},
"cm_matchHighlightToken": {
"message": "İmleç altındaki token"
},
"cm_resizeGripHint": {
"message": "Yüksekliği en üst düzeye çıkarmak/geri yüklemek için çift tıklayın"
},
"cm_selectByTokens": {
"message": "Çift tıklamak tokenleri seçer"
},
"cm_selectByTokensTooltip": {
"message": "Token örnekleri: .foo-bar-2 #aabbcc 0.32 !important\nDevre dışı bırakıldığında: noktalama işareti ile ayrılmış kelimeler seçilir."
},
"cm_smartIndent": {
"message": "Akıllı girinti kullanma"
},
"cm_tabSize": {
"message": "Tab büyüklüğü"
},
"cm_theme": {
"message": "Tema"
},
"colorpickerSwitchFormatTooltip": {
"message": "Format değişimleri: HEX -> RGB -> HSL.\nYönü ters çevirmek için Shift tuşunu basılı tutup tıklatın.\nAyrıca PgUp (PageUp), PgDn (PageDown) tuşları ile."
},
"colorpickerTooltip": {
"message": "Renk seçiciyi aç"
},
"configOnChange": {
"message": "değişiklikte"
},
"configOnChangeTooltip": {
"message": "Otomatik kaydetme ve değişiklikleri otomatik olarak uygulama"
},
"configureStyle": {
"message": "Yapılandır"
},
"configureStyleOnHomepage": {
"message": "Ana sayfada yapılandır"
},
"confirmCancel": {
"message": "İptal"
},
"confirmClose": {
"message": "Kapat"
},
"confirmDefault": {
"message": "Öntanımlıyı kullan"
},
"confirmDelete": {
"message": "Sil"
},
"confirmDiscardChanges": {
"message": "Değişiklikleri iptal et?"
},
"confirmNo": {
"message": "Hayır"
},
"confirmOK": {
"message": "Tamam"
},
"confirmSave": {
"message": "Kaydet"
},
"confirmStop": {
"message": "Dur"
},
"confirmYes": {
"message": "Evet"
},
"connectingDropbox": {
"message": "Dropbox'a bağlanıyor..."
},
"connectingDropboxNotAllowed": {
"message": "Dropbox'a bağlanmak yalnızca doğrudan web mağazasından yüklenen uygulamalarda kullanılabilir"
},
"copied": {
"message": "Kopyalandı"
},
"copy": {
"message": "Kopyala"
},
"dateInstalled": {
"message": "Kurulum tarihi"
},
"dateUpdated": {
"message": "Güncelleme tarihi"
},
"dbError": {
"message": "Stylus veritabanı kullanılırken bir hata oluştu. Olası çözümler içeren bir web sayfasını ziyaret etmek ister misiniz?"
},
"defaultTheme": {
"message": "öntanımlı"
},
"deleteStyleConfirm": {
"message": "Bu stili silmek istediğinizden emin misiniz?"
},
@ -61,9 +205,24 @@
"description": {
"message": "Kullanıcı stil yöneticisi Stylus ile Web'in stilini yenileyin. Stylus Google, Facebook, YouTube, Orkut ve diğer pek çok site için temalar ve görünüm yüklemenize imkân tanır."
},
"disableAllStyles": {
"message": "Tüm stilleri kapat"
},
"disableStyleLabel": {
"message": "Devre dışı bırak"
},
"dragDropMessage": {
"message": "İçe aktarmak için yedek dosyanızı bu sayfada herhangi bir yere bırakın."
},
"dragDropUsercssTabstrip": {
"message": "Dosyayı yüklemek için sekme şeridine (sekme başlıklarının gösterildiği alan) bırakın."
},
"editDeleteText": {
"message": "Sil"
},
"editGotoLine": {
"message": "Satıra git (veya satır:sütun)"
},
"editStyleHeading": {
"message": "Stili Düzenle"
},
@ -81,36 +240,392 @@
"enableStyleLabel": {
"message": "Etkinleştir"
},
"findStylesForSite": {
"message": "Bu site için başka stiller bul"
"excludeStyleByDomainLabel": {
"message": "Mevcut alan adını hariç tut"
},
"excludeStyleByUrlLabel": {
"message": "Mevcut URL'yi hariç tut"
},
"exportLabel": {
"message": "Dışa aktar"
},
"exportSavedSuccess": {
"message": "Dosya başarıyla kaydedildi"
},
"externalFeedback": {
"message": "Geribildirim"
},
"externalHomepage": {
"message": "Anasayfa"
},
"externalLink": {
"message": "Harici bağlantı"
},
"externalSupport": {
"message": "Destek"
},
"externalUsercssDocument": {
"message": "Usercss belgeleri"
},
"filteredStyles": {
"message": "Toplam $numTotal$ gösterilen $numShown$",
"placeholders": {
"numShown": {
"content": "$1"
},
"numTotal": {
"content": "$2"
}
}
},
"filteredStylesAllHidden": {
"message": "Şu anda uygulanan filtreler hiçbir stille eşleşmiyor"
},
"findStyles": {
"message": "Stil bul"
},
"genericAdd": {
"message": "Ekle"
},
"genericClone": {
"message": "Klon"
},
"genericDisabledLabel": {
"message": "Devre dışı"
},
"genericEnabledLabel": {
"message": "Etkin"
},
"genericError": {
"message": "Hata"
},
"genericHistoryLabel": {
"message": "Geçmiş"
},
"genericNext": {
"message": "Sonraki"
},
"genericPrevious": {
"message": "Önceki"
},
"genericResetLabel": {
"message": "Yenile"
},
"genericSavedMessage": {
"message": "Kaydedildi"
},
"genericTitle": {
"message": "Başlık"
},
"genericUnknown": {
"message": "Bilinmiyor"
},
"gettingStyles": {
"message": "Tüm stiller alınıyor..."
},
"helpAlt": {
"message": "Yardım"
},
"helpKeyMapCommand": {
"message": "Bir komut adı yazın"
},
"helpKeyMapHotkey": {
"message": "Bir kısayol tuşuna basın"
},
"hostDisabled": {
"message": "Bu ana bilgisayar, kullanılan tarayıcının mevcut sürümündeki bir hata nedeniyle devre dışı bırakıldı"
},
"importAppendLabel": {
"message": "Stile ekle"
},
"importAppendTooltip": {
"message": "İçe aktarılan stili geçerli stile ekleme"
},
"importLabel": {
"message": "İçe aktar"
},
"importReplaceLabel": {
"message": "Stilin üzerine yaz"
},
"importReplaceTooltip": {
"message": "Geçerli stilin içeriğini atın ve içe aktarılan stil ile üzerine yazın"
},
"importReportLegendAdded": {
"message": "eklendi"
},
"importReportLegendIdentical": {
"message": "Özdeş olan atlandı"
},
"importReportLegendInvalid": {
"message": "geçersiz olan atlandı"
},
"importReportLegendUpdatedBoth": {
"message": "hem meta bilgi hem de kod güncellendi"
},
"importReportLegendUpdatedCode": {
"message": "güncellenmiş kod"
},
"importReportTitle": {
"message": "Stillerin içeri aktarılması tamamlandı."
},
"importReportUnchanged": {
"message": "Hiçbir şey değiştirilmedi."
},
"importReportUndone": {
"message": "stiller geri çevrildi"
},
"importReportUndoneTitle": {
"message": "İçe aktarma geri alındı"
},
"installButton": {
"message": "Stil kur"
},
"installButtonInstalled": {
"message": "Stil kuruldu"
},
"installButtonReinstall": {
"message": "Stili yeniden kur"
},
"installButtonUpdate": {
"message": "Stili güncelle"
},
"installUpdate": {
"message": "Güncellemeyi yükle"
},
"installUpdateFromLabel": {
"message": "Güncellemeleri denetle"
},
"license": {
"message": "Lisans"
},
"linkGetHelp": {
"message": "Destek al"
},
"linkStylusWiki": {
"message": "Viki"
},
"linkTranslate": {
"message": "Çevir"
},
"linterIssues": {
"message": "Sorunlar"
},
"manageFaviconsGray": {
"message": "Gri renkte"
},
"manageFaviconsHelp": {
"message": "Stylus harici bir servis kullanır https://icons.duckduckgo.com"
},
"manageFilters": {
"message": "Filtreler"
},
"manageHeading": {
"message": "Yüklü Stiller"
},
"manageMaxTargets": {
"message": "Uygulanan ögelerin sayısı"
},
"manageNewUI": {
"message": "Yeni yönetim ara yüzü düzeni"
},
"manageOnlyEnabled": {
"message": "Yalnızca etkin stiller"
},
"manageOnlyLocal": {
"message": "Yalnızca yerelde oluşturulmuş stiller"
},
"manageOnlyLocalTooltip": {
"message": "Stiller, bir userstyles.org sayfası üzerinden yülenmemiş."
},
"manageOnlyUpdates": {
"message": "Sadece güncellemeler ya da sorunlar ile"
},
"menuShowBadge": {
"message": "Etkin stil sayısını göster"
},
"noFileToImport": {
"message": "Stillerinizi içe aktarmak için önce dışa aktarmanız gerekir."
},
"noStylesForSite": {
"message": "Bu site için hiçbir stil yüklenmedi."
},
"openManage": {
"message": "Yüklü stilleri yönet"
},
"openOptions": {
"message": "Seçenekler"
},
"openStylesManager": {
"message": "Stil yöneticisini aç"
},
"optionsActions": {
"message": "İşlemler"
},
"optionsAdvanced": {
"message": "Gelişmiş"
},
"optionsAdvancedContextDelete": {
"message": "Editör bağlam menüsüne 'Sil' seçeneği ekle"
},
"optionsAdvancedExposeIframes": {
"message": "HTML[stylus-iframe] vasıtasıyla iframe'leri gösterin"
},
"optionsBadgeDisabled": {
"message": "Etkin değilken arkaplan rengi"
},
"optionsBadgeNormal": {
"message": "Arkaplan rengi"
},
"optionsCheck": {
"message": "Stilleri güncelle"
},
"optionsCheckUpdate": {
"message": "Uygun tüm güncellemeleri kontrol et ve yükle."
},
"optionsCustomizePopup": {
"message": "Açılır pencere"
},
"optionsCustomizeUpdate": {
"message": "Güncellemeler"
},
"optionsHeading": {
"message": "Seçenekler"
},
"optionsOpen": {
"message": "Aç"
},
"optionsOpenManager": {
"message": "Stilleri yönet"
},
"optionsPopupWidth": {
"message": "Popup genişliği (piksel cinsinden)"
},
"optionsReset": {
"message": "Ayarları varsayılanlara sıfırla."
},
"optionsResetButton": {
"message": "Sıfırlama seçenekleri"
},
"optionsSubheading": {
"message": "Daha Fazla Seçenek"
},
"optionsSyncConnect": {
"message": "Bağlan"
},
"optionsSyncDisconnect": {
"message": "Bağlantıyı kes"
},
"optionsSyncLogin": {
"message": "Giriş yap"
},
"optionsSyncStatusConnected": {
"message": "Bağlandı"
},
"optionsSyncStatusConnecting": {
"message": "Bağlanılıyor..."
},
"optionsSyncStatusDisconnected": {
"message": "Bağlantı kesildi"
},
"optionsSyncStatusDisconnecting": {
"message": "Bağlantı kesiliyor..."
},
"optionsUpdateImportNote": {
"message": "Eski versiyonlardan ya da Stylish'ten stil yedeklerini içeri aktarırken, hepsinin güncel olduğundan emin olmak için stil yöneticisinden bir defa elle güncelleme kontrolü yapın"
},
"optionsUpdateInterval": {
"message": "Saat cinsinden kullanıcı stili otomatik güncelleme aralığı (devre dışı bırakmak için 0 yazın) "
},
"overwriteFileExport": {
"message": "Mevcut bir dosyanın üzerine yazmak istiyor musunuz?"
},
"paginationCurrent": {
"message": "Şimdiki sayfa"
},
"paginationEstimated": {
"message": "Tahmini sayfa sayısı"
},
"paginationNext": {
"message": "Sonraki sayfa"
},
"paginationPrevious": {
"message": "Önceki sayfa"
},
"paginationTotal": {
"message": "Toplam sayfa"
},
"prefShowBadge": {
"message": "Şu an açık olan site için aktif olan stil sayısı"
},
"previewLabel": {
"message": "Canlı önizleme"
},
"readingStyles": {
"message": "Stiller okunuyor..."
},
"replace": {
"message": "Değiştir"
},
"replaceAll": {
"message": "Hepsini değiştir"
},
"replaceWith": {
"message": "Şununla değiştir"
},
"retrieveBckp": {
"message": "Stilleri içe aktar"
},
"retrieveDropboxSync": {
"message": "Dropbox'a aktar"
},
"search": {
"message": "Ara"
},
"searchRegexp": {
"message": "Regexp araması için /re/ sözdizimini (syntax) kullanın."
},
"searchResultInstallCount": {
"message": "Toplam kurulum sayısı"
},
"searchResultNoneFound": {
"message": "Bu site için stil bulunamadı."
},
"searchResultWeeklyCount": {
"message": "Haftalık kurulum sayısı"
},
"sectionAdd": {
"message": "Başka bölüm ekle"
},
"sectionCode": {
"message": "Kod"
},
"sectionHelp": {
"message": "Bölümler, aynı stil içindeki farklı URL kümelerine uygulanacak farklı kod parçaları tanımlamanıza olanak sağlar. Örneğin, tek bir stil bir sitenin ana sayfasını belirli bir şekilde değiştirirken, sayfanın kalan kısmını farklı bir şekilde değiştirebilir."
},
"sectionRemove": {
"message": "Bölümü kaldır"
},
"sections": {
"message": "Bölümler"
},
"shortcuts": {
"message": "Kısayollar"
},
"shortcutsNote": {
"message": "Klavye kısayolları tanımla"
},
"sortDateNewestFirst": {
"message": "önce en yeniler"
},
"sortDateOldestFirst": {
"message": "önce en eskiler"
},
"sortStylesHelpTitle": {
"message": "İçeriği sırala"
},
"styleBadRegexp": {
"message": "Regexp geçerli değil."
},
"styleBeautify": {
"message": "Güzelleştir"
},
"styleCancelEditLabel": {
"message": "Yönetim sayfasına dön"
},
@ -120,6 +635,9 @@
"styleEnabledLabel": {
"message": "Etkin"
},
"styleFromMozillaFormatPrompt": {
"message": "Mozilla biçemiyle yazılmış kodu yapıştır"
},
"styleInstall": {
"message": "'$stylename$' Stylus'e yüklensin mi?",
"placeholders": {
@ -131,15 +649,80 @@
"styleMissingName": {
"message": "Bir ad girin"
},
"styleMozillaFormatHeading": {
"message": "Mozilla Biçemi"
},
"styleRegexpInvalidExplanation": {
"message": "Hiç derlenemeyen bazı 'regexp ()' kuralları."
},
"styleRegexpProblemTooltip": {
"message": "'Regexp ()' yanlış kullanımı nedeniyle uygulanmayan bölüm sayısı"
},
"styleRegexpTestFull": {
"message": "Eşleşen sekmeler"
},
"styleRegexpTestInvalid": {
"message": "Geçersiz regexp'ler atlandı"
},
"styleRegexpTestNone": {
"message": "Eşleşen sekme yok"
},
"styleRegexpTestPartial": {
"message": "Tam olarak eşleşmediğinden atlandı"
},
"styleRegexpTestTitle": {
"message": "Eşleşen açık sekmelerin listesi (sekmesine gitmek için URL'ye tıklayın)"
},
"styleSaveLabel": {
"message": "Kaydet"
},
"styleSectionsTitle": {
"message": "Bölümler"
},
"styleToMozillaFormatHelp": {
"message": "Kodun Mozilla biçimi, Firefox için Stylish ile kullanılabilir ve userstyles.org sitesine gönderilebilir."
},
"styleToMozillaFormatTitle": {
"message": "Mozilla biçeminde bir stil"
},
"styleUpdate": {
"message": "\";$stylename$\" stilini güncelleştirmek istiyor musunuz?",
"placeholders": {
"stylename": {
"content": "$1"
}
}
},
"stylusUnavailableForURL": {
"message": "Stylus bunun gibi sayfalarda çalışmaz."
},
"stylusUnavailableForURLdetails": {
"message": "Bir güvenlik önlemi olarak tarayıcı eklentilerin, kendinin yerleşik sayfalarını (chrome://version, Chrome 61'den itibaren standart yeni sekme sayfası, about: addons vb.) ve diğer uzantı sayfalarını etkilemesini engeller. Ayrıca her tarayıcı kendi uzantı galerisine (Chrome Web Mağazası veya AMO gibi) erişimi de kısıtlar. "
},
"syncDropboxDeprecated": {
"message": "Dropbox içe/dışa aktarma, seçenekler sayfasında daha gelişmiş bir stil senkronizasyonu ile değiştirilir."
},
"syncDropboxStyles": {
"message": "Dropbox'tan aktar"
},
"toggleStyle": {
"message": "Stili değiştir"
},
"undo": {
"message": "Geri al"
},
"undoGlobal": {
"message": "Tüm bölümlerde geri alma işlemi uygula."
},
"unreachableContentScript": {
"message": "Sayfayla iletişim kurulamadı. Sekmeyi yeniden yüklemeyi deneyin."
},
"unreachableFileHint": {
"message": "Stylus, eğer siz Stylus chrome://extensions sayfasından ilgili onay kutusunu işaretlerseniz file:// adreslerine erişebilir."
},
"unzipStyles": {
"message": "Stiller açılıyor..."
},
"updateAllCheckSucceededNoUpdate": {
"message": "Güncelleme bulunamadı."
},
"updateCheckFailBadResponseCode": {
"message": "Güncellenemedi: sunucu yanıt olarak $code$ kodunu gönderdi.",
"placeholders": {
@ -151,10 +734,58 @@
"updateCheckFailServerUnreachable": {
"message": "Güncellenemedi: sunucuya erişilemiyor."
},
"updateCheckHistory": {
"message": "Güncelleme kontrolü geçmişi"
},
"updateCheckManualUpdateForce": {
"message": "Güncellemeyi kur (yerel düzenlemelerin üzerine yazılacak)"
},
"updateCheckManualUpdateHint": {
"message": "Bir güncellemeyi zorla yapmak yerel düzenlemelerin üstüne yazacaktır."
},
"updateCheckSkippedLocallyEdited": {
"message": "Bu stil yerel olarak düzenlendi."
},
"updateCheckSkippedMaybeLocallyEdited": {
"message": "Bu stil yerel olarak düzenlenmiş olabilir."
},
"updateCheckSucceededNoUpdate": {
"message": "Stil güncel."
},
"updateCompleted": {
"message": "Güncelleme tamamlandı."
},
"updatesCurrentlyInstalled": {
"message": "Kurulan güncellemeler:"
},
"uploadingFile": {
"message": "Dosya Yükleniyor..."
},
"usercssAvoidOverwriting": {
"message": "Mevcut bir stilin üzerine yazmaktan kaçınmak için lütfen @name veya @ ad alanının değerini değiştirin."
},
"usercssConfigIncomplete": {
"message": "Stil, yapılandırma iletişim kutusu gösterildikten sonra güncellendi veya silindi. Bu değişkenler, stilin meta verilerini bozmamak için kaydedilmedi:"
},
"usercssEditorNamePlaceholder": {
"message": "Kodda @name belirtin"
},
"usercssReplaceTemplateConfirmation": {
"message": "Yeni Usercss stilleri için varsayılan şablonu geçerli kodla değiştir?"
},
"usercssReplaceTemplateSectionBody": {
"message": "Kodu buraya ekle..."
},
"versionInvalidOlder": {
"message": "Sürüm, kurulu stillerden daha eski."
},
"writeStyleFor": {
"message": "Şunun için stil yaz:"
},
"writeStyleForURL": {
"message": "bu URL"
},
"zipStyles": {
"message": "Stiller sıkıştırılıyor..."
}
}

285
_locales/uk/messages.json Normal file
View File

@ -0,0 +1,285 @@
{
"InaccessibleFileHint": {
"message": "Stylus не може отримати доступ до деяких типів файлів (наприклад, файли PDF і JSON)."
},
"addStyleLabel": {
"message": "Написати новий стиль"
},
"addStyleTitle": {
"message": "Додати стиль"
},
"alphaChannel": {
"message": "Прозорість"
},
"appliesAdd": {
"message": "Додати"
},
"appliesDisplay": {
"message": "Застосувати до $applies$",
"placeholders": {
"applies": {
"content": "$1"
}
}
},
"appliesDisplayTruncatedSuffix": {
"message": "та інші"
},
"appliesDomainOption": {
"message": "URL в домені"
},
"appliesHelp": {
"message": "Щоб вказати, до яких URL відноситься код в цьому розділі, скористайтеся параметром \"Застосувати до\"."
},
"appliesLabel": {
"message": "Застосувати до"
},
"appliesLineWidgetLabel": {
"message": "Показати цільові сайти секцій"
},
"appliesRemove": {
"message": "Видалити"
},
"appliesRemoveError": {
"message": "Не можна видалити останній елемент"
},
"appliesSpecify": {
"message": "Вказати"
},
"appliesToEverything": {
"message": "Все"
},
"appliesUrlPrefixOption": {
"message": "URL, що починаються з "
},
"applyAllUpdates": {
"message": "Застосувати всі оновлення"
},
"author": {
"message": "Автор"
},
"backupButtons": {
"message": "Резервне копіювання"
},
"bckpInstStyles": {
"message": "Експорт стилів"
},
"checkingForUpdate": {
"message": "Перевірка ... "
},
"clickToUninstall": {
"message": "Натисніть, щоб видалити "
},
"cm_selectByTokensTooltip": {
"message": "Приклади токенов: .foo-бар-2 #aabbcc 0,32 !important\nЯкщо вимкнено: вибираються слова, розділені розділовими знаками."
},
"confirmCancel": {
"message": "Скасувати "
},
"confirmDelete": {
"message": "Видалити "
},
"confirmNo": {
"message": "Ні"
},
"confirmSave": {
"message": "Зберегти"
},
"confirmStop": {
"message": "Зупинити"
},
"confirmYes": {
"message": "Так"
},
"deleteStyleLabel": {
"message": "Видалити"
},
"disableStyleLabel": {
"message": "Вимкнути"
},
"editDeleteText": {
"message": "Видалити"
},
"findStyles": {
"message": "Знайти стилі"
},
"genericAdd": {
"message": "Додати"
},
"genericDescription": {
"message": "Опис"
},
"genericDisabledLabel": {
"message": "Відключено"
},
"genericEnabledLabel": {
"message": "Увімкнено"
},
"genericError": {
"message": "Помилка"
},
"genericHistoryLabel": {
"message": "Історія"
},
"genericNext": {
"message": "Наступний "
},
"genericPrevious": {
"message": "Попередній"
},
"genericResetLabel": {
"message": "Скинути"
},
"genericSavedMessage": {
"message": "Збережено"
},
"genericTitle": {
"message": "Ім'я"
},
"genericUnknown": {
"message": "Невідомо"
},
"gettingStyles": {
"message": "Отримання всіх стилів ..."
},
"helpAlt": {
"message": "Довідка"
},
"helpKeyMapCommand": {
"message": "Введіть ім'я команди"
},
"helpKeyMapHotkey": {
"message": "Натисніть клавішу"
},
"hostDisabled": {
"message": "Цей хост вимкнено через помилку в поточній версії браузера, що використовується"
},
"importLabel": {
"message": "Імпорт"
},
"importReplaceLabel": {
"message": "Замінити стиль"
},
"importReportLegendIdentical": {
"message": "ідентичні пропущені"
},
"importReportLegendInvalid": {
"message": "недісних пропущено"
},
"importReportTitle": {
"message": "Імпорт стилів закінчено"
},
"installUpdate": {
"message": "Встановити оновлення"
},
"manageFavicons": {
"message": "Піктограми для цільових сайтів"
},
"manageFilters": {
"message": "Фільтри"
},
"manageHeading": {
"message": "Встановити Styles"
},
"manageNewUI": {
"message": "Новий макет інтерфейсу управління користувача"
},
"meta_unknownJSONLiteral": {
"message": "Невірний JSON: $literal$не є дійсним літералом JSON",
"placeholders": {
"literal": {
"content": "$1"
}
}
},
"openManage": {
"message": "Керування"
},
"openOptions": {
"message": "Налаштування"
},
"optionsHeading": {
"message": "Налаштування"
},
"optionsReset": {
"message": "Скидання налаштувань до значень за замовчуванням"
},
"optionsResetButton": {
"message": "Скинути параметри"
},
"optionsSubheading": {
"message": "Додатково"
},
"optionsSyncLogin": {
"message": "Логін"
},
"optionsSyncStatusRelogin": {
"message": "Сеанс закінчився, будь ласка, увійдіть ще раз."
},
"paginationNext": {
"message": "Наступна сторінка"
},
"paginationPrevious": {
"message": "Попередня сторінка"
},
"replace": {
"message": "Замінити"
},
"replaceAll": {
"message": "Замінити все"
},
"retrieveBckp": {
"message": "Імпорт стилів"
},
"search": {
"message": "Пошук"
},
"searchStylesAll": {
"message": "Усі"
},
"searchStylesCode": {
"message": "CSS код"
},
"searchStylesHelp": {
"message": "</> або <Ctrl-F>клавіша фокусує поле пошуку.\nРежим за замовчуванням — це пошук у звичайному тексті для всіх термінів, розділених пробілами, у будь-якому порядку.\nТочні слова: оберніть запит у подвійні лапки, напр. <.header ~ div\">\nРегулярні вирази: включають косі риски та прапорці, напр.</body.*?\\ba\\b/i>\n«By URL» в селекторі області: знаходить стилі, які застосовуються до повністю вказаної URL-адреси, напр. https://www.example.org/\n\"Metadata\" в селектрі області: шукає в іменах, \"applies to\" специфікаторів, URL-адреси встановлення, URL-адресі оновлення та всьому блоку метаданих для стилів CSS користувача."
},
"searchStylesName": {
"message": "Назва"
},
"sectionCode": {
"message": "Код"
},
"sections": {
"message": "Розділи"
},
"styleBeautify": {
"message": "Облагородити"
},
"styleCancelEditLabel": {
"message": "Повернутися до керування"
},
"styleEnabledLabel": {
"message": "Увімкнено"
},
"stylePreferSchemeLabel": {
"message": "Темна/Світла тема"
},
"styleSaveLabel": {
"message": "Зберегти"
},
"syncErrorRelogin": {
"message": "Помилка синхронізації. Ви вийшли із системи. \nСпробуйте повторно увійти до системи в налаштуваннях Stylus."
},
"toggleStyle": {
"message": "Включити/виключити стиль"
},
"writeStyleFor": {
"message": "Створити стиль для:"
},
"writeStyleForURL": {
"message": "цей URL"
},
"zipStyles": {
"message": "Запаковування стилів ... "
}
}

379
_locales/vi/messages.json Normal file
View 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"
}
}

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,26 @@
/* global createWorkerApi */// worker-util.js
'use strict';
/** @namespace BackgroundWorker */
createWorkerApi({
async compileUsercss(...args) {
require(['/js/usercss-compiler']); /* global compileUsercss */
return compileUsercss(...args);
},
nullifyInvalidVars(vars) {
require(['/js/meta-parser']); /* global metaParser */
return metaParser.nullifyInvalidVars(vars);
},
parseMozFormat(...args) {
require(['/js/moz-parser']); /* global extractSections */
return extractSections(...args);
},
parseUsercssMeta(text) {
require(['/js/meta-parser']);
return metaParser.parse(text);
},
});

View File

@ -1,513 +1,213 @@
/*
global dbExec getStyles saveStyle deleteStyle
global handleCssTransitionBug detectSloppyRegexps
global openEditor
global styleViaAPI
global loadScript
global usercss
*/
/* global API msg */// msg.js
/* global addAPI bgReady */// common.js
/* global createWorker */// worker-util.js
/* global prefs */
/* global styleMan */
/* global syncMan */
/* global updateMan */
/* global usercssMan */
/* global usoApi */
/* global uswApi */
/* global FIREFOX UA activateTab openURL */ // toolbox.js
/* global colorScheme */ // color-scheme.js
'use strict';
window.API_METHODS = Object.assign(window.API_METHODS || {}, {
//#region API
getStyles,
saveStyle,
deleteStyle,
addAPI(/** @namespace API */ {
getStyleFromDB: id =>
dbExec('get', id).then(event => event.target.result),
/** Temporary storage for data needed elsewhere e.g. in a content script */
data: ((data = {}) => ({
del: key => delete data[key],
get: key => data[key],
has: key => key in data,
pop: key => {
const val = data[key];
delete data[key];
return val;
},
set: (key, val) => {
data[key] = val;
},
}))(),
download(msg) {
delete msg.method;
return download(msg.url, msg);
styles: styleMan,
sync: syncMan,
updater: updateMan,
usercss: usercssMan,
uso: usoApi,
usw: uswApi,
colorScheme,
/** @type {BackgroundWorker} */
worker: createWorker({url: '/background/background-worker'}),
/** @returns {string} */
getTabUrlPrefix() {
return this.sender.tab.url.match(/^([\w-]+:\/+[^/#]+)/)[1];
},
parseCss({code}) {
return usercss.invokeWorker({action: 'parse', code});
},
getPrefs: prefs.getAll,
healthCheck: () => dbExec().then(() => true),
detectSloppyRegexps,
openEditor,
updateIcon,
// exposed for stuff that requires followup sendMessage() like popup::openSettings
// that would fail otherwise if another extension forced the tab to open
// in the foreground thus auto-closing the popup (in Chrome)
openURL,
closeTab: (msg, sender, respond) => {
chrome.tabs.remove(msg.tabId || sender.tab.id, () => {
if (chrome.runtime.lastError && msg.tabId !== sender.tab.id) {
respond(new Error(chrome.runtime.lastError.message));
/**
* Opens the editor or activates an existing tab
* @param {{
id?: number
domain?: string
'url-prefix'?: string
}} params
* @returns {Promise<chrome.tabs.Tab>}
*/
async openEditor(params) {
const u = new URL(chrome.runtime.getURL('edit.html'));
u.search = new URLSearchParams(params);
const wnd = chrome.windows && prefs.get('openEditInWindow');
const wndPos = wnd && prefs.get('windowPosition');
const wndBase = wnd && prefs.get('openEditInWindow.popup') ? {type: 'popup'} : {};
const ffBug = wnd && FIREFOX; // https://bugzil.la/1271047
if (wndPos) {
const {left, top, width, height} = wndPos;
const r = left + width;
const b = top + height;
const peek = 32;
if (isNaN(r) || r < peek || left > screen.availWidth - peek || width < 100) {
delete wndPos.left;
delete wndPos.width;
}
if (isNaN(b) || b < peek || top > screen.availHeight - peek || height < 100) {
delete wndPos.top;
delete wndPos.height;
}
}
const tab = await openURL({
url: `${u}`,
currentWindow: null,
newWindow: wnd && Object.assign(wndBase, !ffBug && wndPos),
});
return KEEP_CHANNEL_OPEN;
if (ffBug) await browser.windows.update(tab.windowId, wndPos);
return tab;
},
optionsCustomizeHotkeys() {
return browser.runtime.openOptionsPage()
.then(() => new Promise(resolve => setTimeout(resolve, 100)))
.then(() => sendMessage({method: 'optionsCustomizeHotkeys'}));
/** @returns {Promise<chrome.tabs.Tab>} */
async openManage({options = false, search, searchMode} = {}) {
const setUrlParams = url => {
const u = new URL(url);
if (search) u.searchParams.set('search', search);
if (searchMode) u.searchParams.set('searchMode', searchMode);
if (options) u.hash = '#stylus-options';
return u.href;
};
const base = chrome.runtime.getURL('manage.html');
const url = setUrlParams(base);
const tabs = await browser.tabs.query({url: base + '*'});
const same = tabs.find(t => t.url === url);
let tab = same || tabs[0];
if (!tab) {
API.prefsDb.get('badFavs'); // prime the cache to avoid flicker/delay when opening the page
tab = await openURL({url, newTab: true});
} else if (!same) {
msg.sendTab(tab.id, {method: 'pushState', url: setUrlParams(tab.url)});
}
return activateTab(tab); // activateTab unminimizes the window
},
/**
* Same as openURL, the only extra prop in `opts` is `message` - it'll be sent
* when the tab is ready, which is needed in the popup, otherwise another
* extension could force the tab to open in foreground thus auto-closing the
* popup (in Chrome at least) and preventing the sendMessage code from running
* @returns {Promise<chrome.tabs.Tab>}
*/
async openURL(opts) {
const tab = await openURL(opts);
if (opts.message) {
await onTabReady(tab);
await msg.sendTab(tab.id, opts.message);
}
return tab;
function onTabReady(tab) {
return new Promise((resolve, reject) =>
setTimeout(function ping(numTries = 10, delay = 100) {
msg.sendTab(tab.id, {method: 'ping'})
.catch(() => false)
.then(pong => pong
? resolve(tab)
: numTries && setTimeout(ping, delay, numTries - 1, delay * 1.5) ||
reject('timeout'));
}));
}
},
prefs: {
getValues: () => prefs.__values, // will be deepCopy'd by apiHandler
set: prefs.set,
},
});
// eslint-disable-next-line no-var
var browserCommands, contextMenus;
//#endregion
//#region Events
// *************************************************************************
// register all listeners
chrome.runtime.onMessage.addListener(onRuntimeMessage);
if (FIREFOX) {
// see notes in apply.js for getStylesFallback
const MSG_GET_STYLES = 'getStyles:';
const MSG_GET_STYLES_LEN = MSG_GET_STYLES.length;
chrome.runtime.onConnect.addListener(port => {
if (!port.name.startsWith(MSG_GET_STYLES)) return;
const tabId = port.sender.tab.id;
const frameId = port.sender.frameId;
const options = tryJSONparse(port.name.slice(MSG_GET_STYLES_LEN));
port.disconnect();
getStyles(options).then(styles => {
if (!styles.length) return;
chrome.tabs.executeScript(tabId, {
code: `
applyOnMessage({
method: 'styleApply',
styles: ${JSON.stringify(styles)},
})
`,
runAt: 'document_start',
frameId,
});
});
});
}
{
const listener =
URLS.chromeProtectsNTP
? webNavigationListenerChrome
: webNavigationListener;
chrome.webNavigation.onBeforeNavigate.addListener(data =>
listener(null, data));
chrome.webNavigation.onCommitted.addListener(data =>
listener('styleApply', data));
chrome.webNavigation.onHistoryStateUpdated.addListener(data =>
listener('styleReplaceAll', data));
chrome.webNavigation.onReferenceFragmentUpdated.addListener(data =>
listener('styleReplaceAll', data));
if (FIREFOX) {
// FF applies page CSP even to content scripts, https://bugzil.la/1267027
chrome.webNavigation.onCommitted.addListener(webNavUsercssInstallerFF, {
url: [
{hostSuffix: '.githubusercontent.com', urlSuffix: '.user.css'},
{hostSuffix: '.githubusercontent.com', urlSuffix: '.user.styl'},
]
});
// FF misses some about:blank iframes so we inject our content script explicitly
chrome.webNavigation.onDOMContentLoaded.addListener(webNavIframeHelperFF, {
url: [
{urlEquals: 'about:blank'},
]
});
}
}
if (chrome.contextMenus) {
chrome.contextMenus.onClicked.addListener((info, tab) =>
contextMenus[info.menuItemId].click(info, tab));
}
if (chrome.commands) {
// Not available in Firefox - https://bugzilla.mozilla.org/show_bug.cgi?id=1240350
chrome.commands.onCommand.addListener(command => browserCommands[command]());
}
if (!chrome.browserAction ||
!['setIcon', 'setBadgeBackgroundColor', 'setBadgeText'].every(name => chrome.browserAction[name])) {
window.updateIcon = () => {};
}
const tabIcons = new Map();
chrome.tabs.onRemoved.addListener(tabId => tabIcons.delete(tabId));
chrome.tabs.onReplaced.addListener((added, removed) => tabIcons.delete(removed));
// *************************************************************************
// set the default icon displayed after a tab is created until webNavigation kicks in
prefs.subscribe(['iconset'], () =>
updateIcon({
tab: {id: undefined},
styles: {},
}));
// *************************************************************************
chrome.runtime.onInstalled.addListener(({reason}) => {
if (reason !== 'update') return;
// translations may change
localStorage.L10N = JSON.stringify({
browserUIlanguage: chrome.i18n.getUILanguage(),
});
// themes may change
delete localStorage.codeMirrorThemes;
});
// *************************************************************************
// browser commands
browserCommands = {
openManage() {
openURL({url: 'manage.html'});
},
const browserCommands = {
openManage: () => API.openManage(),
openOptions: () => API.openManage({options: true}),
reload: () => chrome.runtime.reload(),
styleDisableAll(info) {
prefs.set('disableAll', info ? info.checked : !prefs.get('disableAll'));
},
};
// *************************************************************************
// context menus
contextMenus = {
'show-badge': {
title: 'menuShowBadge',
click: info => prefs.set(info.menuItemId, info.checked),
},
'disableAll': {
title: 'disableAllStyles',
click: browserCommands.styleDisableAll,
},
'open-manager': {
title: 'openStylesManager',
click: browserCommands.openManage,
},
'editor.contextDelete': {
presentIf: () => !FIREFOX && prefs.get('editor.contextDelete'),
title: 'editDeleteText',
type: 'normal',
contexts: ['editable'],
documentUrlPatterns: [URLS.ownOrigin + 'edit*'],
click: (info, tab) => {
sendMessage({tabId: tab.id, method: 'editDeleteText'});
},
}
};
if (chrome.contextMenus) {
const createContextMenus = ids => {
for (const id of ids) {
let item = contextMenus[id];
if (item.presentIf && !item.presentIf()) {
continue;
}
item = Object.assign({id}, item);
delete item.presentIf;
const prefValue = prefs.readOnlyValues[id];
item.title = chrome.i18n.getMessage(item.title);
if (!item.type && typeof prefValue === 'boolean') {
item.type = 'checkbox';
item.checked = prefValue;
}
if (!item.contexts) {
item.contexts = ['browser_action'];
}
delete item.click;
chrome.contextMenus.create(item, ignoreChromeError);
}
};
// circumvent the bug with disabling check marks in Chrome 62-64
const toggleCheckmark = CHROME >= 3172 && CHROME <= 3288 ?
(id => chrome.contextMenus.remove(id, () => createContextMenus([id]) + ignoreChromeError())) :
((id, checked) => chrome.contextMenus.update(id, {checked}, ignoreChromeError));
const togglePresence = (id, checked) => {
if (checked) {
createContextMenus([id]);
} else {
chrome.contextMenus.remove(id, ignoreChromeError);
}
};
const keys = Object.keys(contextMenus);
prefs.subscribe(keys.filter(id => typeof prefs.readOnlyValues[id] === 'boolean'), toggleCheckmark);
prefs.subscribe(keys.filter(id => contextMenus[id].presentIf), togglePresence);
createContextMenus(keys);
if (chrome.commands) {
chrome.commands.onCommand.addListener(id => browserCommands[id]());
}
// *************************************************************************
// [re]inject content scripts
window.addEventListener('storageReady', function _() {
window.removeEventListener('storageReady', _);
updateIcon({
tab: {id: undefined},
styles: {},
});
// Firefox injects content script automatically
if (FIREFOX) return;
const NTP = 'chrome://newtab/';
const ALL_URLS = '<all_urls>';
const contentScripts = chrome.runtime.getManifest().content_scripts;
// expand * as .*?
const wildcardAsRegExp = (s, flags) => new RegExp(
s.replace(/[{}()[\]/\\.+?^$:=!|]/g, '\\$&')
.replace(/\*/g, '.*?'), flags);
for (const cs of contentScripts) {
cs.matches = cs.matches.map(m => (
m === ALL_URLS ? m : wildcardAsRegExp(m)
));
chrome.runtime.onInstalled.addListener(({reason, previousVersion}) => {
if (reason === 'install') {
if (UA.mobile) prefs.set('manage.newUI', false);
if (UA.windows) prefs.set('editor.keyMap', 'sublime');
}
// TODO: remove this before 1.5.23 as it's only for a few users who installed git 26b75e77
if (reason === 'update' && previousVersion === '1.5.22') {
for (const dbName of ['drafts', prefs.STORAGE_KEY]) {
try {
indexedDB.open(dbName).onsuccess = async e => {
const idb = /** @type IDBDatabase */ e.target.result;
const ta = idb.objectStoreNames[0] === 'data' && idb.transaction(['data']);
if (ta && ta.objectStore('data').autoIncrement) {
ta.abort();
idb.close();
await new Promise(setTimeout);
indexedDB.deleteDatabase(dbName);
}
};
} catch (e) {}
}
}
const injectCS = (cs, tabId) => {
ignoreChromeError();
chrome.tabs.executeScript(tabId, {
file: cs.js[0],
runAt: cs.run_at,
allFrames: cs.all_frames,
matchAboutBlank: cs.match_about_blank,
}, ignoreChromeError);
};
const pingCS = (cs, {id, url}) => {
const maybeInject = pong => !pong && injectCS(cs, id);
cs.matches.some(match => {
if ((match === ALL_URLS || url.match(match)) &&
(!url.startsWith('chrome') || url === NTP)) {
sendMessage({method: 'ping', tabId: id}, maybeInject);
return true;
}
});
};
queryTabs().then(tabs =>
tabs.forEach(tab => {
// skip lazy-loaded aka unloaded tabs that seem to start loading on message in FF
if (tab.width) {
contentScripts.forEach(cs =>
setTimeout(pingCS, 0, cs, tab));
}
}));
});
// *************************************************************************
{
const getStylesForFrame = (msg, sender) => {
const stylesTask = getStyles(msg);
if (!sender || !sender.frameId) return stylesTask;
return Promise.all([
stylesTask,
getTab(sender.tab.id),
]).then(([styles, tab]) => {
if (tab) styles.exposeIframes = tab.url.replace(/(\/\/[^/]*).*/, '$1');
return styles;
});
};
const updateAPI = (_, enabled) => {
window.API_METHODS.getStylesForFrame = enabled ? getStylesForFrame : getStyles;
};
prefs.subscribe(['exposeIframes'], updateAPI);
updateAPI(null, prefs.readOnlyValues.exposeIframes);
}
// *************************************************************************
function webNavigationListener(method, {url, tabId, frameId}) {
Promise.all([
getStyles({matchUrl: url, asHash: true}),
frameId && prefs.readOnlyValues.exposeIframes && getTab(tabId),
]).then(([styles, tab]) => {
if (method && URLS.supported(url) && tabId >= 0) {
if (method === 'styleApply') {
handleCssTransitionBug({tabId, frameId, url, styles});
}
if (tab) styles.exposeIframes = tab.url.replace(/(\/\/[^/]*).*/, '$1');
sendMessage({
tabId,
frameId,
method,
// ping own page so it retrieves the styles directly
styles: url.startsWith(URLS.ownOrigin) ? 'DIY' : styles,
});
}
// main page frame id is 0
if (frameId === 0) {
tabIcons.delete(tabId);
updateIcon({tab: {id: tabId, url}, styles});
}
});
}
function webNavigationListenerChrome(method, data) {
// Chrome 61.0.3161+ doesn't run content scripts on NTP
if (
!data.url.startsWith('https://www.google.') ||
!data.url.includes('/_/chrome/newtab?')
) {
webNavigationListener(method, data);
return;
msg.on((msg, sender) => {
if (msg.method === 'invokeAPI') {
let res = msg.path.reduce((res, name) => res && res[name], API);
if (!res) throw new Error(`Unknown API.${msg.path.join('.')}`);
res = res.apply({msg, sender}, msg.args);
return res === undefined ? null : res;
}
getTab(data.tabId).then(tab => {
if (tab.url === 'chrome://newtab/') {
data.url = tab.url;
}
webNavigationListener(method, data);
});
}
});
//#endregion
function webNavUsercssInstallerFF(data) {
const {tabId} = data;
Promise.all([
sendMessage({tabId, method: 'ping'}),
// we need tab index to open the installer next to the original one
// and also to skip the double-invocation in FF which assigns tab url later
getTab(tabId),
]).then(([pong, tab]) => {
if (pong !== true && tab.url !== 'about:blank') {
window.API_METHODS.installUsercss({direct: true}, {tab});
}
});
}
function webNavIframeHelperFF({tabId, frameId}) {
if (!frameId) return;
sendMessage({method: 'ping', tabId, frameId}, pong => {
ignoreChromeError();
if (pong) return;
chrome.tabs.executeScript(tabId, {
frameId,
file: '/content/apply.js',
matchAboutBlank: true,
}, ignoreChromeError);
});
}
function updateIcon({tab, styles}) {
if (tab.id < 0) {
return;
}
if (URLS.chromeProtectsNTP && tab.url === 'chrome://newtab/') {
styles = {};
}
if (styles) {
stylesReceived(styles);
return;
}
getTabRealURL(tab)
.then(url => getStyles({matchUrl: url, asHash: true}))
.then(stylesReceived);
function stylesReceived(styles) {
const disableAll = 'disableAll' in styles ? styles.disableAll : prefs.get('disableAll');
const postfix = disableAll ? 'x' : !styles.length ? 'w' : '';
const color = prefs.get(disableAll ? 'badgeDisabled' : 'badgeNormal');
const text = prefs.get('show-badge') && styles.length ? String(styles.length) : '';
const iconset = ['', 'light/'][prefs.get('iconset')] || '';
let tabIcon = tabIcons.get(tab.id);
if (!tabIcon) tabIcons.set(tab.id, (tabIcon = {}));
if (tabIcon.iconType !== iconset + postfix) {
tabIcon.iconType = iconset + postfix;
const sizes = FIREFOX || CHROME >= 2883 && !VIVALDI ? [16, 32] : [19, 38];
const usePath = tabIcons.get('usePath');
Promise.all(sizes.map(size => {
const src = `/images/icon/${iconset}${size}${postfix}.png`;
return usePath ? src : tabIcons.get(src) || loadIcon(src);
})).then(data => {
const imageKey = typeof data[0] === 'string' ? 'path' : 'imageData';
const imageData = {};
sizes.forEach((size, i) => (imageData[size] = data[i]));
chrome.browserAction.setIcon({
tabId: tab.id,
[imageKey]: imageData,
}, ignoreChromeError);
});
}
if (tab.id === undefined) return;
let defaultIcon = tabIcons.get(undefined);
if (!defaultIcon) tabIcons.set(undefined, (defaultIcon = {}));
if (defaultIcon.color !== color) {
defaultIcon.color = color;
chrome.browserAction.setBadgeBackgroundColor({color});
}
if (tabIcon.text === text) return;
tabIcon.text = text;
try {
// Chrome supports the callback since 67.0.3381.0, see https://crbug.com/451320
chrome.browserAction.setBadgeText({text, tabId: tab.id}, ignoreChromeError);
} catch (e) {
setTimeout(() => {
getTab(tab.id).then(realTab => {
// skip pre-rendered tabs
if (realTab.index >= 0) {
chrome.browserAction.setBadgeText({text, tabId: tab.id});
}
});
});
}
}
function loadIcon(src, resolve) {
if (!resolve) return new Promise(resolve => loadIcon(src, resolve));
const canvas = document.createElement('canvas');
const ctx = canvas.getContext('2d');
const img = new Image();
img.src = src;
img.onload = () => {
const w = canvas.width = img.width;
const h = canvas.height = img.height;
ctx.clearRect(0, 0, w, h);
ctx.drawImage(img, 0, 0, w, h);
const data = ctx.getImageData(0, 0, w, h);
// Firefox breaks Canvas when privacy.resistFingerprinting=true, https://bugzil.la/1412961
let usePath = tabIcons.get('usePath');
if (usePath === undefined) {
usePath = data.data.every(b => b === 255);
tabIcons.set('usePath', usePath);
}
if (usePath) {
resolve(src);
return;
}
tabIcons.set(src, data);
resolve(data);
};
}
}
function onRuntimeMessage(msg, sender, sendResponse) {
const fn = window.API_METHODS[msg.method];
if (!fn) return;
// wrap 'Error' object instance as {__ERROR__: message},
// which will be unwrapped by sendMessage,
// and prevent exceptions on sending to a closed tab
const respond = data =>
tryCatch(sendResponse,
data instanceof Error ? {__ERROR__: data.message} : data);
const result = fn(msg, sender, respond);
if (result instanceof Promise) {
result
.catch(e => ({__ERROR__: e instanceof Error ? e.message : e}))
.then(respond);
return KEEP_CHANNEL_OPEN;
} else if (result === KEEP_CHANNEL_OPEN) {
return KEEP_CHANNEL_OPEN;
} else if (result !== undefined) {
respond(result);
}
}
Promise.all([
browser.extension.isAllowedFileSchemeAccess()
.then(res => API.data.set('hasFileAccess', res)),
bgReady.styles,
/* These are loaded conditionally.
Each item uses `require` individually so IDE can jump to the source and track usage. */
FIREFOX &&
require(['/background/style-via-api']),
FIREFOX && ((browser.commands || {}).update) &&
require(['/background/browser-cmd-hotkeys']),
!FIREFOX &&
require(['/background/content-scripts']),
chrome.contextMenus &&
require(['/background/context-menus']),
]).then(() => {
bgReady._resolveAll();
msg.ready = true;
msg.broadcast({method: 'backgroundReady'});
});

View File

@ -0,0 +1,22 @@
/* global prefs */
'use strict';
/*
Registers hotkeys in FF
*/
(() => {
const hotkeyPrefs = prefs.knownKeys.filter(k => k.startsWith('hotkey.'));
prefs.subscribe(hotkeyPrefs, updateHotkey, {runNow: true});
async function updateHotkey(name, value) {
try {
name = name.split('.')[1];
if (value.trim()) {
await browser.commands.update({name, shortcut: value});
} else {
await browser.commands.reset(name);
}
} catch (e) {}
}
})();

View File

@ -0,0 +1,91 @@
/* global prefs */
/* exported colorScheme */
'use strict';
const colorScheme = (() => {
const changeListeners = new Set();
const kSTATE = 'schemeSwitcher.enabled';
const kSTART = 'schemeSwitcher.nightStart';
const kEND = 'schemeSwitcher.nightEnd';
const SCHEMES = ['dark', 'light'];
const isDark = {
never: null,
dark: true,
light: false,
system: false,
time: false,
};
let isDarkNow = false;
prefs.subscribe(kSTATE, () => update());
prefs.subscribe([kSTART, kEND], (key, value) => {
updateTimePreferDark();
createAlarm(key, value);
}, {runNow: true});
chrome.alarms.onAlarm.addListener(({name}) => {
if (name === kSTART || name === kEND) {
updateTimePreferDark();
}
});
return {
SCHEMES,
onChange(listener) {
changeListeners.add(listener);
},
isDark: () => isDarkNow,
/** @param {StyleObj} _ */
shouldIncludeStyle({preferScheme: ps}) {
return prefs.get(kSTATE) === 'never' ||
!SCHEMES.includes(ps) ||
isDarkNow === (ps === 'dark');
},
updateSystemPreferDark(val) {
update('system', val);
return true;
},
};
function calcTime(key) {
const [h, m] = prefs.get(key).split(':');
return (h * 3600 + m * 60) * 1000;
}
function createAlarm(key, value) {
const date = new Date();
const [h, m] = value.split(':');
date.setHours(h, m, 0, 0);
if (date.getTime() < Date.now()) {
date.setDate(date.getDate() + 1);
}
chrome.alarms.create(key, {
when: date.getTime(),
periodInMinutes: 24 * 60,
});
}
function updateTimePreferDark() {
const now = Date.now() - new Date().setHours(0, 0, 0, 0);
const start = calcTime(kSTART);
const end = calcTime(kEND);
const val = start > end ?
now >= start || now < end :
now >= start && now < end;
update('time', val);
}
function update(type, val) {
if (type) {
if (isDark[type] === val) return;
isDark[type] = val;
}
val = isDark[prefs.get(kSTATE)];
if (isDarkNow !== val) {
isDarkNow = val;
for (const listener of changeListeners) {
listener(isDarkNow);
}
}
}
})();

92
background/common.js Normal file
View File

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

View File

@ -0,0 +1,117 @@
/* global bgReady */// common.js
/* global msg */
/* global URLS ignoreChromeError */// toolbox.js
'use strict';
/*
Reinject content scripts when the extension is reloaded/updated.
Not used in Firefox as it reinjects automatically.
*/
bgReady.all.then(() => {
const NTP = 'chrome://newtab/';
const ALL_URLS = '<all_urls>';
const SCRIPTS = chrome.runtime.getManifest().content_scripts;
// expand * as .*?
const wildcardAsRegExp = (s, flags) => new RegExp(
s.replace(/[{}()[\]/\\.+?^$:=!|]/g, '\\$&')
.replace(/\*/g, '.*?'), flags);
for (const cs of SCRIPTS) {
cs.matches = cs.matches.map(m => (
m === ALL_URLS ? m : wildcardAsRegExp(m)
));
}
const busyTabs = new Set();
let busyTabsTimer;
setTimeout(injectToAllTabs);
function injectToTab({url, tabId, frameId = null}) {
for (const script of SCRIPTS) {
if (
script.matches.some(match =>
(match === ALL_URLS || url.match(match)) &&
(!url.startsWith('chrome') || url === NTP))
) {
doInject(tabId, frameId, script);
}
}
}
function doInject(tabId, frameId, script) {
const options = frameId === null ? {} : {frameId};
msg.sendTab(tabId, {method: 'ping'}, options)
.catch(() => false)
.then(pong => {
if (pong) {
return;
}
const options = {
runAt: script.run_at,
allFrames: script.all_frames,
matchAboutBlank: script.match_about_blank,
};
if (frameId !== null) {
options.allFrames = false;
options.frameId = frameId;
}
for (const file of script.js) {
chrome.tabs.executeScript(tabId, Object.assign({file}, options), ignoreChromeError);
}
});
}
function injectToAllTabs() {
return browser.tabs.query({}).then(tabs => {
for (const tab of tabs) {
// skip unloaded/discarded/chrome tabs
if (!tab.width || tab.discarded || !URLS.supported(tab.pendingUrl || tab.url)) continue;
// our content scripts may still be pending injection at browser start so it's too early to ping them
if (tab.status === 'loading') {
trackBusyTab(tab.id, true);
} else {
injectToTab({
url: tab.pendingUrl || tab.url,
tabId: tab.id,
});
}
}
});
}
function toggleBusyTabListeners(state) {
const toggle = state ? 'addListener' : 'removeListener';
chrome.webNavigation.onCompleted[toggle](onBusyTabUpdated);
chrome.webNavigation.onErrorOccurred[toggle](onBusyTabUpdated);
chrome.webNavigation.onTabReplaced[toggle](onBusyTabReplaced);
chrome.tabs.onRemoved[toggle](onBusyTabRemoved);
if (state) {
busyTabsTimer = setTimeout(toggleBusyTabListeners, 15e3, false);
} else {
clearTimeout(busyTabsTimer);
}
}
function trackBusyTab(tabId, state) {
busyTabs[state ? 'add' : 'delete'](tabId);
if (state && busyTabs.size === 1) toggleBusyTabListeners(true);
if (!state && !busyTabs.size) toggleBusyTabListeners(false);
}
function onBusyTabUpdated({error, frameId, tabId, url}) {
if (!frameId && busyTabs.has(tabId)) {
trackBusyTab(tabId, false);
if (url && !error) {
injectToTab({tabId, url});
}
}
}
function onBusyTabReplaced({replacedTabId}) {
trackBusyTab(replacedTabId, false);
}
function onBusyTabRemoved(tabId) {
trackBusyTab(tabId, false);
}
});

View File

@ -0,0 +1,87 @@
/* global browserCommands */// background.js
/* global msg */
/* global prefs */
/* global CHROME URLS ignoreChromeError */// toolbox.js
'use strict';
chrome.management.getSelf(ext => {
const contextMenus = Object.assign({
'show-badge': {
title: 'menuShowBadge',
click: togglePref,
},
'disableAll': {
title: 'disableAllStyles',
click: browserCommands.styleDisableAll,
},
'open-manager': {
title: 'optionsOpenManager',
click: browserCommands.openManage,
},
'open-options': {
title: 'openOptions',
click: browserCommands.openOptions,
},
}, ext.installType === 'development' && {
'reload': {
title: 'reload',
click: browserCommands.reload,
},
}, CHROME && {
'editor.contextDelete': {
title: 'editDeleteText',
type: 'normal',
contexts: ['editable'],
documentUrlPatterns: [URLS.ownOrigin + '*'],
click: (info, tab) => {
msg.sendTab(tab.id, {method: 'editDeleteText'}, undefined, 'extension')
.catch(msg.ignoreError);
},
},
});
createContextMenus(Object.keys(contextMenus));
chrome.contextMenus.onClicked.addListener((info, tab) =>
contextMenus[info.menuItemId].click(info, tab));
function createContextMenus(ids) {
for (const id of ids) {
const item = Object.assign({id, contexts: ['browser_action']}, contextMenus[id]);
item.title = chrome.i18n.getMessage(item.title);
if (typeof prefs.defaults[id] === 'boolean') {
if (item.type) {
prefs.subscribe(id, togglePresence);
} else {
item.type = 'checkbox';
item.checked = prefs.get(id);
prefs.subscribe(id, CHROME >= 62 && CHROME <= 64 ? toggleCheckmarkBugged : toggleCheckmark);
}
}
delete item.click;
chrome.contextMenus.create(item, ignoreChromeError);
}
}
function toggleCheckmark(id, checked) {
chrome.contextMenus.update(id, {checked}, ignoreChromeError);
}
/** Circumvents the bug with disabling check marks in Chrome 62-64 */
async function toggleCheckmarkBugged(id) {
await browser.contextMenus.remove(id).catch(ignoreChromeError);
createContextMenus([id]);
}
/** @param {chrome.contextMenus.OnClickData} info */
function togglePref(info) {
prefs.set(info.menuItemId, info.checked);
}
function togglePresence(id, checked) {
if (checked) {
createContextMenus([id]);
} else {
chrome.contextMenus.remove(id, ignoreChromeError);
}
}
});

View File

@ -0,0 +1,64 @@
/* global chromeLocal */// storage-util.js
'use strict';
/* exported createChromeStorageDB */
function createChromeStorageDB(PREFIX) {
let INC;
const isMain = !PREFIX;
if (!PREFIX) PREFIX = 'style-';
return {
delete(id) {
return chromeLocal.remove(PREFIX + id);
},
get(id) {
return chromeLocal.getValue(PREFIX + id);
},
async getAll() {
const all = await chromeLocal.get();
if (!INC) prepareInc(all);
return Object.entries(all)
.map(([key, val]) => key.startsWith(PREFIX) &&
(!isMain || Number(key.slice(PREFIX.length))) &&
val)
.filter(Boolean);
},
async put(item) {
if (!item.id) {
if (!INC) await prepareInc();
item.id = INC++;
}
await chromeLocal.setValue(PREFIX + item.id, item);
return item.id;
},
async putMany(items) {
const data = {};
for (const item of items) {
if (!item.id) {
if (!INC) await prepareInc();
item.id = INC++;
}
data[PREFIX + item.id] = item;
}
await chromeLocal.set(data);
return items.map(_ => _.id);
},
};
async function prepareInc(data) {
INC = 1;
for (const key in data || await chromeLocal.get()) {
if (key.startsWith(PREFIX)) {
const id = Number(key.slice(PREFIX.length));
if (id >= INC) {
INC = id + 1;
}
}
}
}
}

150
background/db.js Normal file
View File

@ -0,0 +1,150 @@
/* global addAPI */// common.js
/* global chromeLocal */// storage-util.js
/* global cloneError */// worker-util.js
/* global deepCopy */// toolbox.js
/* global prefs */
'use strict';
/*
Initialize a database. There are some problems using IndexedDB in Firefox:
https://www.reddit.com/r/firefox/comments/74wttb/note_to_firefox_webextension_developers_who_use/
Some of them are fixed in FF59:
https://www.reddit.com/r/firefox/comments/7ijuaq/firefox_59_webextensions_can_use_indexeddb_when/
*/
/* exported db */
const db = (() => {
let exec = async (...args) => (
exec = await tryUsingIndexedDB().catch(useChromeStorage)
)(...args);
const DB = 'stylish';
const FALLBACK = 'dbInChromeStorage';
const ID_AS_KEY = {[DB]: true};
const getStoreName = dbName => dbName === DB ? 'styles' : 'data';
const cache = {};
const proxies = {};
const proxyHandler = {
get: ({dbName}, cmd) =>
(...args) =>
(dbName === DB ? exec : cachedExec)(dbName, cmd, ...args),
};
/**
* @param {string} dbName
* @return {IDBObjectStore | {putMany: function(items:?[]):Promise<?[]>}}
*/
const getProxy = dbName => proxies[dbName] || (
(proxies[dbName] = new Proxy({dbName}, proxyHandler))
);
addAPI(/** @namespace API */ {
drafts: getProxy('drafts'),
/** Storage for big items that may exceed 8kB limit of chrome.storage.sync.
* To make an item syncable register it with uuidIndex.addCustomId. */
prefsDb: getProxy(prefs.STORAGE_KEY),
});
return {
styles: getProxy(DB),
};
async function cachedExec(dbName, cmd, a, b) {
const hub = cache[dbName] || (cache[dbName] = {});
const res = cmd === 'get' && a in hub ? hub[a] : await exec(...arguments);
if (cmd === 'get') {
hub[a] = deepCopy(res);
} else if (cmd === 'put') {
hub[ID_AS_KEY[dbName] ? a.id : b] = deepCopy(a);
} else if (cmd === 'delete') {
delete hub[a];
}
return res;
}
async function tryUsingIndexedDB() {
// we use chrome.storage.local fallback if IndexedDB doesn't save data,
// which, once detected on the first run, is remembered in chrome.storage.local
// note that accessing indexedDB may throw, https://github.com/openstyles/stylus/issues/615
if (typeof indexedDB === 'undefined') {
throw new Error('indexedDB is undefined');
}
switch (await chromeLocal.getValue(FALLBACK)) {
case true: throw null;
case false: break;
default: await testDB();
}
chromeLocal.setValue(FALLBACK, false);
return dbExecIndexedDB;
}
async function testDB() {
const id = `${performance.now()}.${Math.random()}.${Date.now()}`;
await dbExecIndexedDB(DB, 'put', {id});
const e = await dbExecIndexedDB(DB, 'get', id);
await dbExecIndexedDB(DB, 'delete', e.id); // throws if `e` or id is null
}
async function useChromeStorage(err) {
chromeLocal.setValue(FALLBACK, true);
if (err) {
chromeLocal.setValue(FALLBACK + 'Reason', cloneError(err));
console.warn('Failed to access indexedDB. Switched to storage API.', err);
}
await require(['/background/db-chrome-storage']); /* global createChromeStorageDB */
const BASES = {};
return (dbName, method, ...args) => (
BASES[dbName] || (
BASES[dbName] = createChromeStorageDB(dbName !== DB && `${dbName}-`)
)
)[method](...args);
}
async function dbExecIndexedDB(dbName, method, ...args) {
const mode = method.startsWith('get') ? 'readonly' : 'readwrite';
const storeName = getStoreName(dbName);
const store = (await open(dbName)).transaction([storeName], mode).objectStore(storeName);
const fn = method === 'putMany' ? putMany : storeRequest;
return fn(store, method, ...args);
}
function storeRequest(store, method, ...args) {
return new Promise((resolve, reject) => {
/** @type {IDBRequest} */
const request = store[method](...args);
request.onsuccess = () => resolve(request.result);
request.onerror = reject;
});
}
function putMany(store, _method, items) {
return Promise.all(items.map(item => storeRequest(store, 'put', item)));
}
function open(name) {
return new Promise((resolve, reject) => {
const request = indexedDB.open(name, 2);
request.onsuccess = e => resolve(create(e));
request.onerror = reject;
request.onupgradeneeded = create;
});
}
function create(event) {
/** @type IDBDatabase */
const idb = event.target.result;
const dbName = idb.name;
const sn = getStoreName(dbName);
if (!idb.objectStoreNames.contains(sn)) {
if (event.type === 'success') {
idb.close();
return new Promise(resolve => {
indexedDB.deleteDatabase(dbName).onsuccess = () => {
resolve(open(dbName));
};
});
}
idb.createObjectStore(sn, ID_AS_KEY[dbName] ? {
keyPath: 'id',
autoIncrement: true,
} : undefined);
}
return idb;
}
})();

232
background/icon-manager.js Normal file
View File

@ -0,0 +1,232 @@
/* global API */// msg.js
/* global addAPI bgReady */// common.js
/* global colorScheme */
/* global prefs */
/* global tabMan */
/* global CHROME FIREFOX UA debounce ignoreChromeError */// toolbox.js
'use strict';
/* exported iconMan */
const iconMan = (() => {
const ICON_SIZES = FIREFOX || CHROME && !UA.vivaldi ? [16, 32] : [19, 38];
const staleBadges = new Set();
const imageDataCache = new Map();
const badgeOvr = {color: '', text: ''};
// https://github.com/openstyles/stylus/issues/1287 Fenix can't use custom ImageData
const FIREFOX_ANDROID = FIREFOX && UA.mobile;
let isDark;
// https://github.com/openstyles/stylus/issues/335
let hasCanvas = FIREFOX_ANDROID ? false : loadImage(`/images/icon/${ICON_SIZES[0]}.png`)
.then(({data}) => (hasCanvas = data.some(b => b !== 255)));
addAPI(/** @namespace API */ {
/**
* @param {(number|string)[]} styleIds
* @param {boolean} [lazyBadge=false] preventing flicker during page load
*/
updateIconBadge(styleIds, {lazyBadge} = {}) {
// FIXME: in some cases, we only have to redraw the badge. is it worth a optimization?
const {frameId, tab: {id: tabId}} = this.sender;
const value = styleIds.length ? styleIds.map(Number) : undefined;
tabMan.set(tabId, 'styleIds', frameId, value);
debounce(refreshStaleBadges, frameId && lazyBadge ? 250 : 0);
staleBadges.add(tabId);
if (!frameId) refreshIcon(tabId, true);
},
});
chrome.webNavigation.onCommitted.addListener(({tabId, frameId}) => {
if (!frameId) tabMan.set(tabId, 'styleIds', undefined);
});
chrome.runtime.onConnect.addListener(port => {
if (port.name === 'iframe') {
port.onDisconnect.addListener(onPortDisconnected);
}
});
colorScheme.onChange(val => {
isDark = val;
if (prefs.get('iconset') === -1) {
debounce(refreshAllIcons);
}
});
bgReady.all.then(() => {
prefs.subscribe([
'disableAll',
'badgeDisabled',
'badgeNormal',
], () => debounce(refreshIconBadgeColor), {runNow: true});
prefs.subscribe([
'show-badge',
], () => debounce(refreshAllIconsBadgeText), {runNow: true});
prefs.subscribe([
'disableAll',
'iconset',
], () => debounce(refreshAllIcons), {runNow: true});
});
return {
/** Calling with no params clears the override */
overrideBadge({text = '', color = '', title = ''} = {}) {
if (badgeOvr.text === text) {
return;
}
badgeOvr.text = text;
badgeOvr.color = color;
refreshIconBadgeColor();
setBadgeText({text});
for (const tabId of tabMan.list()) {
if (text) {
setBadgeText({tabId, text});
} else {
refreshIconBadgeText(tabId);
}
}
chrome.browserAction.setTitle({
title: title && chrome.i18n.getMessage(title) || title || '',
});
},
};
function onPortDisconnected({sender}) {
if (tabMan.get(sender.tab.id, 'styleIds')) {
API.updateIconBadge.call({sender}, [], {lazyBadge: true});
}
}
function refreshIconBadgeText(tabId) {
if (badgeOvr.text) return;
const text = prefs.get('show-badge') ? `${getStyleCount(tabId)}` : '';
setBadgeText({tabId, text});
}
function getIconName(hasStyles = false) {
const i = prefs.get('iconset');
const prefix = i === 0 || i === -1 && isDark ? '' : 'light/';
const postfix = prefs.get('disableAll') ? 'x' : !hasStyles ? 'w' : '';
return `${prefix}$SIZE$${postfix}`;
}
function refreshIcon(tabId, force = false) {
const oldIcon = tabMan.get(tabId, 'icon');
const newIcon = getIconName(tabMan.get(tabId, 'styleIds', 0));
// (changing the icon only for the main page, frameId = 0)
if (!force && oldIcon === newIcon) {
return;
}
tabMan.set(tabId, 'icon', newIcon);
setIcon({
path: getIconPath(newIcon),
tabId,
});
}
function getIconPath(icon) {
return ICON_SIZES.reduce(
(obj, size) => {
obj[size] = `/images/icon/${icon.replace('$SIZE$', size)}.png`;
return obj;
},
{}
);
}
/** @return {number | ''} */
function getStyleCount(tabId) {
const allIds = new Set();
const data = tabMan.get(tabId, 'styleIds') || {};
Object.values(data).forEach(frameIds => frameIds.forEach(id => allIds.add(id)));
return allIds.size || '';
}
// Caches imageData for icon paths
async function loadImage(url) {
const {OffscreenCanvas} = !FIREFOX && self.createImageBitmap && self || {};
const img = OffscreenCanvas
? await createImageBitmap(await (await fetch(url)).blob())
: await new Promise((resolve, reject) =>
Object.assign(new Image(), {
src: url,
onload: e => resolve(e.target),
onerror: reject,
}));
const {width: w, height: h} = img;
const canvas = OffscreenCanvas
? new OffscreenCanvas(w, h)
: Object.assign(document.createElement('canvas'), {width: w, height: h});
const ctx = canvas.getContext('2d');
ctx.drawImage(img, 0, 0, w, h);
const result = ctx.getImageData(0, 0, w, h);
imageDataCache.set(url, result);
return result;
}
function refreshGlobalIcon() {
setIcon({
path: getIconPath(getIconName()),
});
}
function refreshIconBadgeColor() {
setBadgeBackgroundColor({
color: badgeOvr.color ||
prefs.get(prefs.get('disableAll') ? 'badgeDisabled' : 'badgeNormal'),
});
}
function refreshAllIcons() {
for (const tabId of tabMan.list()) {
refreshIcon(tabId);
}
refreshGlobalIcon();
}
function refreshAllIconsBadgeText() {
for (const tabId of tabMan.list()) {
refreshIconBadgeText(tabId);
}
}
function refreshStaleBadges() {
for (const tabId of staleBadges) {
refreshIconBadgeText(tabId);
}
staleBadges.clear();
}
function safeCall(method, data) {
const {browserAction = {}} = chrome;
const fn = browserAction[method];
if (fn) {
try {
// Chrome supports the callback since 67.0.3381.0, see https://crbug.com/451320
fn.call(browserAction, data, ignoreChromeError);
} catch (e) {
// FIXME: skip pre-rendered tabs?
fn.call(browserAction, data);
}
}
}
/** @param {chrome.browserAction.TabIconDetails} data */
async function setIcon(data) {
if (hasCanvas === true || await hasCanvas) {
data.imageData = {};
for (const [key, url] of Object.entries(data.path)) {
data.imageData[key] = imageDataCache.get(url) || await loadImage(url);
}
delete data.path;
}
safeCall('setIcon', data);
}
/** @param {chrome.browserAction.BadgeTextDetails} data */
function setBadgeText(data) {
safeCall('setBadgeText', data);
}
/** @param {chrome.browserAction.BadgeBackgroundColorDetails} data */
function setBadgeBackgroundColor(data) {
safeCall('setBadgeBackgroundColor', data);
}
})();

View File

@ -0,0 +1,103 @@
/* global CHROME FIREFOX URLS deepEqual ignoreChromeError */// toolbox.js
/* global bgReady */// common.js
/* global msg */
'use strict';
/* exported navMan */
const navMan = (() => {
const listeners = new Set();
let prevData = {};
chrome.webNavigation.onCommitted.addListener(onNavigation.bind('committed'));
chrome.webNavigation.onHistoryStateUpdated.addListener(onFakeNavigation.bind('history'));
chrome.webNavigation.onReferenceFragmentUpdated.addListener(onFakeNavigation.bind('hash'));
return {
/** @param {function(data: Object, type: ('committed'|'history'|'hash'))} fn */
onUrlChange(fn) {
listeners.add(fn);
},
};
/** @this {string} type */
async function onNavigation(data) {
if (CHROME && data.timeStamp === prevData.timeStamp && deepEqual(data, prevData)) {
return; // Chrome bug: listener is called twice with identical data
}
prevData = data;
if (CHROME &&
URLS.chromeProtectsNTP &&
data.url.startsWith('https://www.google.') &&
data.url.includes('/_/chrome/newtab?')) {
// Modern Chrome switched to WebUI NTP so this is obsolete, but there may be exceptions
// TODO: investigate, and maybe use a separate listener for CHROME <= ver
const tab = await browser.tabs.get(data.tabId);
const url = tab.pendingUrl || tab.url;
if (url === 'chrome://newtab/') {
data.url = url;
}
}
listeners.forEach(fn => fn(data, this));
}
/** @this {string} type */
function onFakeNavigation(data) {
const {url, frameId} = data;
onNavigation.call(this, data);
msg.sendTab(data.tabId, {method: 'urlChanged', url}, {frameId})
.catch(msg.ignoreError);
}
})();
bgReady.all.then(() => {
/*
* Expose style version on greasyfork/sleazyfork 1) info page and 2) code page
* Not using manifest.json as adding a content script disables the extension on update.
*/
const urlMatches = '/scripts/\\d+[^/]*(/code)?([?#].*)?$';
chrome.webNavigation.onCommitted.addListener(({tabId}) => {
chrome.tabs.executeScript(tabId, {
file: '/content/install-hook-greasyfork.js',
runAt: 'document_start',
});
}, {
url: [
{hostEquals: 'greasyfork.org', urlMatches},
{hostEquals: 'sleazyfork.org', urlMatches},
],
});
/*
* Removes the Get Stylus button on style pages.
* Not using manifest.json as adding a content script disables the extension on update.
*/
chrome.webNavigation.onCommitted.addListener(({tabId}) => {
chrome.tabs.executeScript(tabId, {
file: '/content/install-hook-userstylesworld.js',
runAt: 'document_start',
});
}, {
url: [
{hostEquals: 'userstyles.world'},
],
});
/*
* FF misses some about:blank iframes so we inject our content script explicitly
*/
if (FIREFOX) {
chrome.webNavigation.onDOMContentLoaded.addListener(async ({tabId, frameId}) => {
if (frameId &&
!await msg.sendTab(tabId, {method: 'ping'}, {frameId}).catch(ignoreChromeError)) {
for (const file of chrome.runtime.getManifest().content_scripts[0].js) {
chrome.tabs.executeScript(tabId, {
frameId,
file,
matchAboutBlank: true,
}, ignoreChromeError);
}
}
}, {
url: [{urlEquals: 'about:blank'}],
});
}
});

View File

@ -1,102 +0,0 @@
'use strict';
(() => {
// begin:nanographql - Tiny graphQL client library
// Author: yoshuawuyts (https://github.com/yoshuawuyts)
// License: MIT
// Modified by DecentM to fit project standards
const getOpname = /(query|mutation) ?([\w\d-_]+)? ?\(.*?\)? \{/;
const gql = str => {
str = Array.isArray(str) ? str.join('') : str;
const name = getOpname.exec(str);
return variables => {
const data = {query: str};
if (variables) data.variables = JSON.stringify(variables);
if (name && name.length) {
const operationName = name[2];
if (operationName) data.operationName = name[2];
}
return JSON.stringify(data);
};
};
// end:nanographql
const api = 'https://api.openusercss.org';
const doQuery = ({id}, queryString) => {
const query = gql(queryString);
return fetch(api, {
method: 'POST',
headers: new Headers({
'Content-Type': 'application/json'
}),
body: query({
id
})
})
.then(res => res.json());
};
window.API_METHODS = Object.assign(window.API_METHODS || {}, {
/**
* This function can be used to retrieve a theme object from the
* GraphQL API, set above
*
* Example:
* chrome.runtime.sendMessage({
* 'method': 'oucThemeById',
* 'id': '5a2f819f7c57c751001b49df'
* }, console.log);
*
* @param {ID} $0.id MongoDB style ID
* @returns {Promise.<{data: object}>} The GraphQL result with the `theme` object
*/
oucThemeById: params => doQuery(params, `
query($id: ID!) {
theme(id: $id) {
_id
title
description
createdAt
lastUpdate
version
screenshots
user {
_id
displayname
}
}
}
`),
/**
* This function can be used to retrieve a user object from the
* GraphQL API, set above
*
* Example:
* chrome.runtime.sendMessage({
* 'method': 'oucUserById',
* 'id': '5a2f0361ba666f0b00b9c827'
* }, console.log);
*
* @param {ID} $0.id MongoDB style ID
* @returns {Promise.<{data: object}>} The GraphQL result with the `user` object
*/
oucUserById: params => doQuery(params, `
query($id: ID!) {
user(id: $id) {
_id
displayname
avatarUrl
smallAvatarUrl
bio
}
}
`),
});
})();

View File

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

View File

@ -1,226 +0,0 @@
/*
global API_METHODS cachedStyles
global getStyles filterStyles invalidateCache normalizeStyleSections
global updateIcon
*/
'use strict';
(() => {
const previewFromTabs = new Map();
/**
* When style id and state is provided, only that style is propagated.
* Otherwise all styles are replaced and the toolbar icon is updated.
* @param {Object} [msg]
* @param {{id:Number, enabled?:Boolean, sections?: (Array|String)}} [msg.style] -
* style to propagate
* @param {Boolean} [msg.codeIsUpdated]
* @returns {Promise<void>}
*/
API_METHODS.refreshAllTabs = (msg = {}) =>
Promise.all([
queryTabs(),
maybeParseUsercss(msg),
getStyles(),
]).then(([tabs, style]) =>
new Promise(resolve => {
if (style) msg.style.sections = normalizeStyleSections(style);
run(tabs, msg, resolve);
}));
function run(tabs, msg, resolve) {
const {style, codeIsUpdated, refreshOwnTabs} = msg;
// the style was updated/saved so we need to remove the old copy of the original style
if (msg.method === 'styleUpdated' && msg.reason !== 'editPreview') {
for (const [tabId, original] of previewFromTabs.entries()) {
if (style.id === original.id) {
previewFromTabs.delete(tabId);
}
}
if (!previewFromTabs.size) {
unregisterTabListeners();
}
}
if (!style) {
msg = {method: 'styleReplaceAll'};
// live preview puts the code in cachedStyles, saves the original in previewFromTabs,
// and if preview is being disabled, but the style is already deleted, we bail out
} else if (msg.reason === 'editPreview' && !updateCache(msg)) {
return;
// simple style update:
// * if disabled, apply.js will remove the element
// * if toggled and code is unchanged, apply.js will toggle the element
} else if (!style.enabled || codeIsUpdated === false) {
msg = {
method: 'styleUpdated',
reason: msg.reason,
style: {
id: style.id,
enabled: style.enabled,
},
codeIsUpdated,
};
// live preview normal operation, the new code is already in cachedStyles
} else {
msg.method = 'styleApply';
msg.style = {id: msg.style.id};
}
if (!tabs || !tabs.length) {
resolve();
return;
}
const last = tabs[tabs.length - 1];
for (const tab of tabs) {
if (FIREFOX && !tab.width) continue;
if (refreshOwnTabs === false && tab.url.startsWith(URLS.ownOrigin)) continue;
chrome.webNavigation.getAllFrames({tabId: tab.id}, frames =>
refreshFrame(tab, frames, msg, tab === last && resolve));
}
}
function refreshFrame(tab, frames, msg, resolve) {
ignoreChromeError();
if (!frames || !frames.length) {
frames = [{
frameId: 0,
url: tab.url,
}];
}
msg.tabId = tab.id;
const styleId = msg.style && msg.style.id;
for (const frame of frames) {
const styles = filterStyles({
matchUrl: getFrameUrl(frame, frames),
asHash: true,
id: styleId,
});
msg = Object.assign({}, msg);
msg.frameId = frame.frameId;
if (msg.method !== 'styleUpdated') {
msg.styles = styles;
}
if (msg.method === 'styleApply' && !styles.length) {
// remove the style from a previously matching frame
invokeOrPostpone(tab.active, sendMessage, {
method: 'styleUpdated',
reason: 'editPreview',
style: {
id: styleId,
enabled: false,
},
tabId: tab.id,
frameId: frame.frameId,
}, ignoreChromeError);
} else {
invokeOrPostpone(tab.active, sendMessage, msg, ignoreChromeError);
}
if (!frame.frameId) {
setTimeout(updateIcon, 0, {
tab,
styles: msg.method === 'styleReplaceAll' ? styles : undefined,
});
}
}
if (resolve) resolve();
}
function getFrameUrl(frame, frames) {
while (frame.url === 'about:blank' && frame.frameId > 0) {
const parent = frames.find(f => f.frameId === frame.parentFrameId);
if (!parent) break;
frame.url = parent.url;
frame = parent;
}
return (frame || frames[0]).url;
}
function maybeParseUsercss({style}) {
if (style && typeof style.sections === 'string') {
return API_METHODS.parseUsercss({sourceCode: style.sections});
}
}
function updateCache(msg) {
const {style, tabId, restoring} = msg;
const spoofed = !restoring && previewFromTabs.get(tabId);
const original = cachedStyles.byId.get(style.id);
if (style.sections && !restoring) {
if (!previewFromTabs.size) {
registerTabListeners();
}
if (!spoofed) {
previewFromTabs.set(tabId, Object.assign({}, original));
}
} else {
previewFromTabs.delete(tabId);
if (!previewFromTabs.size) {
unregisterTabListeners();
}
if (!original) {
return;
}
if (!restoring) {
msg.style = spoofed || original;
}
}
invalidateCache({updated: msg.style});
return true;
}
function registerTabListeners() {
chrome.tabs.onRemoved.addListener(onTabRemoved);
chrome.tabs.onReplaced.addListener(onTabReplaced);
chrome.webNavigation.onCommitted.addListener(onTabNavigated);
}
function unregisterTabListeners() {
chrome.tabs.onRemoved.removeListener(onTabRemoved);
chrome.tabs.onReplaced.removeListener(onTabReplaced);
chrome.webNavigation.onCommitted.removeListener(onTabNavigated);
}
function onTabRemoved(tabId) {
const style = previewFromTabs.get(tabId);
if (style) {
API_METHODS.refreshAllTabs({
style,
tabId,
reason: 'editPreview',
restoring: true,
});
}
}
function onTabReplaced(addedTabId, removedTabId) {
onTabRemoved(removedTabId);
}
function onTabNavigated({tabId}) {
onTabRemoved(tabId);
}
})();

View File

@ -1,100 +0,0 @@
/* global API_METHODS filterStyles cachedStyles */
'use strict';
(() => {
// toLocaleLowerCase cache, autocleared after 1 minute
const cache = new Map();
// top-level style properties to be searched
const PARTS = {
name: searchText,
url: searchText,
sourceCode: searchText,
sections: searchSections,
};
/**
* @param params
* @param {string} params.query - 1. url:someurl 2. text (may contain quoted parts like "qUot Ed")
* @param {number[]} [params.ids] - if not specified, all styles are searched
* @returns {number[]} - array of matched styles ids
*/
API_METHODS.searchDB = ({query, ids}) => {
let rx, words, icase, matchUrl;
query = query.trim();
if (/^url:/i.test(query)) {
matchUrl = query.slice(query.indexOf(':') + 1).trim();
if (matchUrl) {
return filterStyles({matchUrl}).map(style => style.id);
}
}
if (query.startsWith('/') && /^\/(.+?)\/([gimsuy]*)$/.test(query)) {
rx = tryRegExp(RegExp.$1, RegExp.$2);
}
if (!rx) {
words = query
.split(/(".*?")|\s+/)
.filter(Boolean)
.map(w => w.startsWith('"') && w.endsWith('"')
? w.slice(1, -1)
: w)
.filter(w => w.length > 1);
words = words.length ? words : [query];
icase = words.some(w => w === lower(w));
}
const results = [];
for (const item of ids || cachedStyles.list) {
const id = isNaN(item) ? item.id : item;
if (!query || words && !words.length) {
results.push(id);
continue;
}
const style = isNaN(item) ? item : cachedStyles.byId.get(item);
if (!style) continue;
for (const part in PARTS) {
const text = style[part];
if (text && PARTS[part](text, rx, words, icase)) {
results.push(id);
break;
}
}
}
if (cache.size) debounce(clearCache, 60e3);
return results;
};
function searchText(text, rx, words, icase) {
if (rx) return rx.test(text);
for (let pass = 1; pass <= (icase ? 2 : 1); pass++) {
if (words.every(w => text.includes(w))) return true;
text = lower(text);
}
}
function searchSections(sections, rx, words, icase) {
for (const section of sections) {
for (const prop in section) {
const value = section[prop];
if (typeof value === 'string') {
if (searchText(value, rx, words, icase)) return true;
} else if (Array.isArray(value)) {
if (value.some(str => searchText(str, rx, words, icase))) return true;
}
}
}
}
function lower(text) {
let result = cache.get(text);
if (result) return result;
result = text.toLocaleLowerCase();
cache.set(text, result);
return result;
}
function clearCache() {
cache.clear();
}
})();

View File

@ -1,78 +0,0 @@
'use strict';
// eslint-disable-next-line no-unused-expressions
(chrome.runtime.id.includes('@temporary') || !('sync' in chrome.storage)) && (() => {
const listeners = new Set();
Object.assign(chrome.storage.onChanged, {
addListener: fn => listeners.add(fn),
hasListener: fn => listeners.has(fn),
removeListener: fn => listeners.delete(fn),
});
for (const name of ['local', 'sync']) {
const dummy = tryJSONparse(localStorage['dummyStorage.' + name]) || {};
chrome.storage[name] = {
get(data, cb) {
let result = {};
if (data === null) {
result = deepCopy(dummy);
} else if (Array.isArray(data)) {
for (const key of data) {
result[key] = dummy[key];
}
} else if (typeof data === 'object') {
const hasOwnProperty = Object.prototype.hasOwnProperty;
for (const key in data) {
if (hasOwnProperty.call(data, key)) {
const value = dummy[key];
result[key] = value === undefined ? data[key] : value;
}
}
} else {
result[data] = dummy[data];
}
if (typeof cb === 'function') cb(result);
},
set(data, cb) {
const hasOwnProperty = Object.prototype.hasOwnProperty;
const changes = {};
for (const key in data) {
if (!hasOwnProperty.call(data, key)) continue;
const newValue = data[key];
changes[key] = {newValue, oldValue: dummy[key]};
dummy[key] = newValue;
}
localStorage['dummyStorage.' + name] = JSON.stringify(dummy);
if (typeof cb === 'function') cb();
notify(changes);
},
remove(keyOrKeys, cb) {
const changes = {};
for (const key of Array.isArray(keyOrKeys) ? keyOrKeys : [keyOrKeys]) {
changes[key] = {oldValue: dummy[key]};
delete dummy[key];
}
localStorage['dummyStorage.' + name] = JSON.stringify(dummy);
if (typeof cb === 'function') cb();
notify(changes);
},
};
}
window.API_METHODS = Object.assign(window.API_METHODS || {}, {
dummyStorageGet: ({data, name}) => new Promise(resolve => chrome.storage[name].get(data, resolve)),
dummyStorageSet: ({data, name}) => new Promise(resolve => chrome.storage[name].set(data, resolve)),
dummyStorageRemove: ({data, name}) => new Promise(resolve => chrome.storage[name].remove(data, resolve)),
});
function notify(changes, name) {
for (const fn of listeners.values()) {
fn(changes, name);
}
sendMessage({
dummyStorageChanges: changes,
dummyStorageName: name,
}, ignoreChromeError);
}
})();

View File

@ -1,836 +0,0 @@
/* global getStyleWithNoCode styleSectionsEqual */
'use strict';
const RX_NAMESPACE = /\s*(@namespace\s+(?:\S+\s+)?url\(http:\/\/.*?\);)\s*/g;
const RX_CHARSET = /\s*@charset\s+(['"]).*?\1\s*;\s*/g;
const RX_CSS_COMMENTS = /\/\*[\s\S]*?(?:\*\/|$)/g;
// eslint-disable-next-line no-var
var SLOPPY_REGEXP_PREFIX = '\0';
// CSS transition bug workaround: since we insert styles asynchronously,
// the browsers, especially Firefox, may apply all transitions on page load
const CSS_TRANSITION_SUPPRESSOR = '* { transition: none !important; }';
const RX_CSS_TRANSITION_DETECTOR = /([\s\n;/{]|-webkit-|-moz-)transition[\s\n]*:[\s\n]*(?!none)/;
// Note, only 'var'-declared variables are visible from another extension page
// eslint-disable-next-line no-var
var cachedStyles = {
list: null, // array of all styles
byId: new Map(), // all styles indexed by id
filters: new Map(), // filterStyles() parameters mapped to the returned results, 10k max
regexps: new Map(), // compiled style regexps
urlDomains: new Map(), // getDomain() results for 100 last checked urls
needTransitionPatch: new Map(), // FF bug workaround
mutex: {
inProgress: true, // while getStyles() is reading IndexedDB all subsequent calls
// (initially 'true' to prevent rogue getStyles before dbExec.initialized)
onDone: [], // to getStyles() are queued and resolved when the first one finishes
},
};
// eslint-disable-next-line no-var
var dbExec = dbExecIndexedDB;
dbExec.initialized = false;
// we use chrome.storage.local fallback if IndexedDB doesn't save data,
// which, once detected on the first run, is remembered in chrome.storage.local
// for reliablility and in localStorage for fast synchronous access
// (FF may block localStorage depending on its privacy options)
do {
const done = () => {
cachedStyles.mutex.inProgress = false;
getStyles().then(() => {
dbExec.initialized = true;
window.dispatchEvent(new Event('storageReady'));
});
};
const fallback = () => {
dbExec = dbExecChromeStorage;
chromeLocal.set({dbInChromeStorage: true});
localStorage.dbInChromeStorage = 'true';
ignoreChromeError();
done();
};
const fallbackSet = localStorage.dbInChromeStorage;
if (fallbackSet === 'true' || !tryCatch(() => indexedDB)) {
fallback();
break;
} else if (fallbackSet === 'false') {
done();
break;
}
chromeLocal.get('dbInChromeStorage')
.then(data =>
data && data.dbInChromeStorage && Promise.reject())
.then(() =>
tryCatch(dbExecIndexedDB, 'getAllKeys', IDBKeyRange.lowerBound(1), 1) ||
Promise.reject())
.then(({target}) => (
(target.result || [])[0] ?
Promise.reject('ok') :
dbExecIndexedDB('put', {id: -1})))
.then(() =>
dbExecIndexedDB('get', -1))
.then(({target}) => (
(target.result || {}).id === -1 ?
dbExecIndexedDB('delete', -1) :
Promise.reject()))
.then(() =>
Promise.reject('ok'))
.catch(result => {
if (result === 'ok') {
chromeLocal.set({dbInChromeStorage: false});
localStorage.dbInChromeStorage = 'false';
done();
} else {
fallback();
}
});
} while (0);
function dbExecIndexedDB(method, ...args) {
return new Promise((resolve, reject) => {
Object.assign(indexedDB.open('stylish', 2), {
onsuccess(event) {
const database = event.target.result;
if (!method) {
resolve(database);
} else {
const transaction = database.transaction(['styles'], 'readwrite');
const store = transaction.objectStore('styles');
Object.assign(store[method](...args), {
onsuccess: event => resolve(event, store, transaction, database),
onerror: reject,
});
}
},
onerror(event) {
console.warn(event.target.error || event.target.errorCode);
reject(event);
},
onupgradeneeded(event) {
if (event.oldVersion === 0) {
event.target.result.createObjectStore('styles', {
keyPath: 'id',
autoIncrement: true,
});
}
},
});
});
}
function dbExecChromeStorage(method, data) {
const STYLE_KEY_PREFIX = 'style-';
switch (method) {
case 'get':
return chromeLocal.getValue(STYLE_KEY_PREFIX + data)
.then(result => ({target: {result}}));
case 'put':
if (!data.id) {
return getStyles().then(() => {
data.id = 1;
for (const style of cachedStyles.list) {
data.id = Math.max(data.id, style.id + 1);
}
return dbExecChromeStorage('put', data);
});
}
return chromeLocal.setValue(STYLE_KEY_PREFIX + data.id, data)
.then(() => (chrome.runtime.lastError ? Promise.reject() : data.id));
case 'delete':
return chromeLocal.remove(STYLE_KEY_PREFIX + data);
case 'getAll':
return chromeLocal.get(null).then(storage => {
const styles = [];
for (const key in storage) {
if (key.startsWith(STYLE_KEY_PREFIX) &&
Number(key.substr(STYLE_KEY_PREFIX.length))) {
styles.push(storage[key]);
}
}
return {target: {result: styles}};
});
}
return Promise.reject();
}
function getStyles(options) {
if (cachedStyles.list) {
return Promise.resolve(filterStyles(options));
}
if (cachedStyles.mutex.inProgress) {
return new Promise(resolve => {
cachedStyles.mutex.onDone.push({options, resolve});
});
}
cachedStyles.mutex.inProgress = true;
return dbExec('getAll').then(event => {
cachedStyles.list = event.target.result || [];
cachedStyles.byId.clear();
for (const style of cachedStyles.list) {
cachedStyles.byId.set(style.id, style);
if (!style.name) {
style.name = 'ID: ' + style.id;
}
}
cachedStyles.mutex.inProgress = false;
for (const {options, resolve} of cachedStyles.mutex.onDone) {
resolve(filterStyles(options));
}
cachedStyles.mutex.onDone = [];
return filterStyles(options);
});
}
function filterStyles({
enabled = null,
id = null,
matchUrl = null,
md5Url = null,
asHash = null,
omitCode,
strictRegexp = true, // used by the popup to detect bad regexps
} = {}) {
if (id) id = Number(id);
if (asHash) enabled = true;
if (
enabled === null &&
id === null &&
matchUrl === null &&
md5Url === null &&
asHash !== true
) {
return cachedStyles.list;
}
if (matchUrl && !URLS.supported(matchUrl)) {
return asHash ? {length: 0} : [];
}
const blankHash = asHash && {
length: 0,
disableAll: prefs.get('disableAll'),
exposeIframes: prefs.get('exposeIframes'),
};
// make sure to use the same order in updateFiltersCache()
const cacheKey =
enabled + '\t' +
id + '\t' +
matchUrl + '\t' +
md5Url + '\t' +
asHash + '\t' +
strictRegexp;
const cached = cachedStyles.filters.get(cacheKey);
let styles;
if (cached) {
cached.hits++;
cached.lastHit = Date.now();
styles = asHash
? Object.assign(blankHash, cached.styles)
: cached.styles.slice();
} else {
styles = filterStylesInternal({
enabled,
id,
matchUrl,
md5Url,
asHash,
strictRegexp,
blankHash,
cacheKey,
});
}
if (!omitCode) return styles;
if (!asHash) return styles.map(getStyleWithNoCode);
for (const id in styles) {
const sections = styles[id];
if (Array.isArray(sections)) {
styles[id] = getStyleWithNoCode({sections}).sections;
}
}
return styles;
}
function filterStylesInternal({
// js engines don't like big functions (V8 often deoptimized the original filterStyles)
// it also makes sense to extract the less frequently executed code
enabled,
id,
matchUrl,
md5Url,
asHash,
strictRegexp,
blankHash,
cacheKey,
}) {
if (matchUrl && !cachedStyles.urlDomains.has(matchUrl)) {
cachedStyles.urlDomains.set(matchUrl, getDomains(matchUrl));
for (let i = cachedStyles.urlDomains.size - 100; i > 0; i--) {
const firstKey = cachedStyles.urlDomains.keys().next().value;
cachedStyles.urlDomains.delete(firstKey);
}
}
const styles = id === null
? cachedStyles.list
: [cachedStyles.byId.get(id)];
if (!styles[0]) {
// may happen when users [accidentally] reopen an old URL
// of edit.html with a non-existent style id parameter
return asHash ? blankHash : [];
}
const filtered = asHash ? {length: 0} : [];
const needSections = asHash || matchUrl !== null;
const matchUrlBase = matchUrl && matchUrl.includes('#') && matchUrl.split('#', 1)[0];
let style;
for (let i = 0; (style = styles[i]); i++) {
if ((enabled === null || style.enabled === enabled)
&& (md5Url === null || style.md5Url === md5Url)
&& (id === null || style.id === id)) {
const sections = needSections &&
getApplicableSections({
style,
matchUrl,
strictRegexp,
stopOnFirst: !asHash,
skipUrlCheck: true,
matchUrlBase,
});
if (asHash) {
if (sections.length) {
filtered[style.id] = sections;
filtered.length++;
}
} else if (matchUrl === null || sections.length) {
filtered.push(style);
}
}
}
cachedStyles.filters.set(cacheKey, {
styles: filtered,
lastHit: Date.now(),
hits: 1,
});
if (cachedStyles.filters.size > 10000) {
cleanupCachedFilters();
}
// a shallow copy is needed because the cache doesn't store options like disableAll
return asHash
? Object.assign(blankHash, filtered)
: filtered;
}
function saveStyle(style) {
const id = Number(style.id) || null;
const reason = style.reason;
const notify = style.notify !== false;
delete style.method;
delete style.reason;
delete style.notify;
if (!style.name) {
delete style.name;
}
let existed;
let codeIsUpdated;
return maybeCalcDigest()
.then(maybeImportFix)
.then(decide);
function maybeCalcDigest() {
if (['install', 'update', 'update-digest'].includes(reason)) {
return calcStyleDigest(style).then(digest => {
style.originalDigest = digest;
});
}
return Promise.resolve();
}
function maybeImportFix() {
if (reason === 'import') {
style.originalDigest = style.originalDigest || style.styleDigest; // TODO: remove in the future
delete style.styleDigest; // TODO: remove in the future
if (typeof style.originalDigest !== 'string' || style.originalDigest.length !== 40) {
delete style.originalDigest;
}
}
}
function decide() {
if (id !== null) {
// Update or create
style.id = id;
return dbExec('get', id).then((event, store) => {
const oldStyle = event.target.result;
existed = Boolean(oldStyle);
if (reason === 'update-digest' && oldStyle.originalDigest === style.originalDigest) {
return style;
}
codeIsUpdated = !existed || 'sections' in style && !styleSectionsEqual(style, oldStyle);
style = Object.assign({installDate: Date.now()}, oldStyle, style);
return write(style, store);
});
} else {
// Create
delete style.id;
style = Object.assign({
// Set optional things if they're undefined
enabled: true,
updateUrl: null,
md5Url: null,
url: null,
originalMd5: null,
installDate: Date.now(),
}, style);
return write(style);
}
}
function write(style, store) {
style.sections = normalizeStyleSections(style);
if (store) {
return new Promise(resolve => {
store.put(style).onsuccess = event => resolve(done(event));
});
} else {
return dbExec('put', style).then(done);
}
}
function done(event) {
if (reason === 'update-digest') {
return style;
}
style.id = style.id || event.target.result;
invalidateCache(existed ? {updated: style} : {added: style});
if (notify) {
notifyAllTabs({
method: existed ? 'styleUpdated' : 'styleAdded',
style, codeIsUpdated, reason,
});
}
return style;
}
}
function deleteStyle({id, notify = true}) {
id = Number(id);
return dbExec('delete', id).then(() => {
invalidateCache({deletedId: id});
if (notify) {
notifyAllTabs({method: 'styleDeleted', id});
}
return id;
});
}
function getApplicableSections({
style,
matchUrl,
strictRegexp = true,
// filterStylesInternal() sets the following to avoid recalc on each style:
stopOnFirst,
skipUrlCheck,
matchUrlBase = matchUrl.includes('#') && matchUrl.split('#', 1)[0],
// 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 in case of url("http://exact.url/without/hash")
}) {
if (!skipUrlCheck && !URLS.supported(matchUrl)) {
return [];
}
const sections = [];
for (const section of style.sections) {
const {urls, domains, urlPrefixes, regexps, code} = section;
const isGlobal = !urls.length && !urlPrefixes.length && !domains.length && !regexps.length;
const isMatching = !isGlobal && (
urls.length
&& (urls.includes(matchUrl) || matchUrlBase && urls.includes(matchUrlBase))
|| urlPrefixes.length
&& arraySomeIsPrefix(urlPrefixes, matchUrl)
|| domains.length
&& arraySomeIn(cachedStyles.urlDomains.get(matchUrl) || getDomains(matchUrl), domains)
|| regexps.length
&& arraySomeMatches(regexps, matchUrl, strictRegexp));
if (isGlobal && !styleCodeEmpty(code) || isMatching) {
sections.push(section);
if (stopOnFirst) {
break;
}
}
}
return sections;
function arraySomeIsPrefix(array, string) {
for (const prefix of array) {
if (string.startsWith(prefix)) {
return true;
}
}
return false;
}
function arraySomeIn(array, haystack) {
for (const el of array) {
if (haystack.indexOf(el) >= 0) {
return true;
}
}
return false;
}
function arraySomeMatches(array, matchUrl, strictRegexp) {
for (const regexp of array) {
for (let pass = 1; pass <= (strictRegexp ? 1 : 2); pass++) {
const cacheKey = pass === 1 ? regexp : SLOPPY_REGEXP_PREFIX + regexp;
let rx = cachedStyles.regexps.get(cacheKey);
if (rx === false) {
// invalid regexp
break;
}
if (!rx) {
const anchored = pass === 1 ? '^(?:' + regexp + ')$' : '^' + regexp + '$';
rx = tryRegExp(anchored);
cachedStyles.regexps.set(cacheKey, rx || false);
if (!rx) {
// invalid regexp
break;
}
}
if (rx.test(matchUrl)) {
return true;
}
}
}
return false;
}
}
function styleCodeEmpty(code) {
// Collect the global section if it's not empty, not comment-only, not namespace-only.
const cmtOpen = code && code.indexOf('/*');
if (cmtOpen >= 0) {
const cmtCloseLast = code.lastIndexOf('*/');
if (cmtCloseLast < 0) {
code = code.substr(0, cmtOpen);
} else {
code = code.substr(0, cmtOpen) +
code.substring(cmtOpen, cmtCloseLast + 2).replace(RX_CSS_COMMENTS, '') +
code.substr(cmtCloseLast + 2);
}
}
if (!code || !code.trim()) return true;
if (code.includes('@namespace')) code = code.replace(RX_NAMESPACE, '').trim();
if (code.includes('@charset')) code = code.replace(RX_CHARSET, '').trim();
return !code;
}
function invalidateCache({added, updated, deletedId} = {}) {
if (!cachedStyles.list) return;
const id = added ? added.id : updated ? updated.id : deletedId;
const cached = cachedStyles.byId.get(id);
if (updated) {
if (cached) {
const isSectionGlobal = section =>
!section.urls.length &&
!section.urlPrefixes.length &&
!section.domains.length &&
!section.regexps.length;
const hadOrHasGlobals = cached.sections.some(isSectionGlobal) ||
updated.sections.some(isSectionGlobal);
const reenabled = !cached.enabled && updated.enabled;
const equal = !hadOrHasGlobals &&
!reenabled &&
styleSectionsEqual(updated, cached, {ignoreCode: true});
Object.assign(cached, updated);
if (equal) {
updateFiltersCache(cached);
} else {
cachedStyles.filters.clear();
}
cachedStyles.needTransitionPatch.delete(id);
return;
} else {
added = updated;
}
}
if (added) {
if (!cached) {
cachedStyles.list.push(added);
cachedStyles.byId.set(added.id, added);
cachedStyles.filters.clear();
cachedStyles.needTransitionPatch.delete(id);
}
return;
}
if (deletedId !== undefined) {
if (cached) {
const cachedIndex = cachedStyles.list.indexOf(cached);
cachedStyles.list.splice(cachedIndex, 1);
cachedStyles.byId.delete(deletedId);
for (const {styles} of cachedStyles.filters.values()) {
if (Array.isArray(styles)) {
const index = styles.findIndex(({id}) => id === deletedId);
if (index >= 0) styles.splice(index, 1);
} else if (deletedId in styles) {
delete styles[deletedId];
styles.length--;
}
}
cachedStyles.needTransitionPatch.delete(id);
return;
}
}
cachedStyles.list = null;
cachedStyles.filters.clear();
cachedStyles.needTransitionPatch.clear(id);
}
function updateFiltersCache(style) {
const {id} = style;
for (const [key, {styles}] of cachedStyles.filters.entries()) {
if (Array.isArray(styles)) {
const index = styles.findIndex(style => style.id === id);
if (index >= 0) styles[index] = Object.assign({}, style);
continue;
}
if (id in styles) {
const [, , matchUrl, , , strictRegexp] = key.split('\t');
if (!style.enabled) {
delete styles[id];
styles.length--;
continue;
}
const matchUrlBase = matchUrl && matchUrl.includes('#') && matchUrl.split('#', 1)[0];
const sections = getApplicableSections({
style,
matchUrl,
matchUrlBase,
strictRegexp,
skipUrlCheck: true,
});
if (sections.length) {
styles[id] = sections;
} else {
delete styles[id];
styles.length--;
}
}
}
}
function cleanupCachedFilters({force = false} = {}) {
if (!force) {
debounce(cleanupCachedFilters, 1000, {force: true});
return;
}
const size = cachedStyles.filters.size;
const oldestHit = cachedStyles.filters.values().next().value.lastHit;
const now = Date.now();
const timeSpan = now - oldestHit;
const recencyWeight = 5 / size;
const hitWeight = 1 / 4; // we make ~4 hits per URL
const lastHitWeight = 10;
// delete the oldest 10%
[...cachedStyles.filters.entries()]
.map(([id, v], index) => ({
id,
weight:
index * recencyWeight +
v.hits * hitWeight +
(v.lastHit - oldestHit) / timeSpan * lastHitWeight,
}))
.sort((a, b) => a.weight - b.weight)
.slice(0, size / 10 + 1)
.forEach(({id}) => cachedStyles.filters.delete(id));
}
function getDomains(url) {
let d = /.*?:\/*([^/:]+)|$/.exec(url)[1];
if (!d || url.startsWith('file:')) {
return [];
}
const domains = [d];
while (d.indexOf('.') !== -1) {
d = d.substring(d.indexOf('.') + 1);
domains.push(d);
}
return domains;
}
function normalizeStyleSections({sections}) {
// retain known properties in an arbitrarily predefined order
return (sections || []).map(section => ({
code: section.code || '',
urls: section.urls || [],
urlPrefixes: section.urlPrefixes || [],
domains: section.domains || [],
regexps: section.regexps || [],
}));
}
function calcStyleDigest(style) {
const jsonString = style.usercssData ?
style.sourceCode : JSON.stringify(normalizeStyleSections(style));
const text = new TextEncoder('utf-8').encode(jsonString);
return crypto.subtle.digest('SHA-1', text).then(hex);
function hex(buffer) {
const parts = [];
const PAD8 = '00000000';
const view = new DataView(buffer);
for (let i = 0; i < view.byteLength; i += 4) {
parts.push((PAD8 + view.getUint32(i).toString(16)).slice(-8));
}
return parts.join('');
}
}
function handleCssTransitionBug({tabId, frameId, url, styles}) {
for (let id in styles) {
id |= 0;
if (!id) {
continue;
}
let need = cachedStyles.needTransitionPatch.get(id);
if (need === false) {
continue;
}
if (need !== true) {
need = styles[id].some(sectionContainsTransitions);
cachedStyles.needTransitionPatch.set(id, need);
if (!need) {
continue;
}
}
if (FIREFOX && !url.startsWith(URLS.ownOrigin)) {
patchFirefox();
} else {
styles.needTransitionPatch = true;
}
break;
}
function patchFirefox() {
const options = {
frameId,
code: CSS_TRANSITION_SUPPRESSOR,
matchAboutBlank: true,
};
if (FIREFOX >= 53) {
options.cssOrigin = 'user';
}
browser.tabs.insertCSS(tabId, Object.assign(options, {
runAt: 'document_start',
})).then(() => setTimeout(() => {
browser.tabs.removeCSS(tabId, options).catch(ignoreChromeError);
})).catch(ignoreChromeError);
}
function sectionContainsTransitions(section) {
let code = section.code;
const firstTransition = code.indexOf('transition');
if (firstTransition < 0) {
return false;
}
const firstCmt = code.indexOf('/*');
// check the part before the first comment
if (firstCmt < 0 || firstTransition < firstCmt) {
if (quickCheckAround(code, firstTransition)) {
return true;
} else if (firstCmt < 0) {
return false;
}
}
// check the rest
const lastCmt = code.lastIndexOf('*/');
if (lastCmt < firstCmt) {
// the comment is unclosed and we already checked the preceding part
return false;
}
let mid = code.slice(firstCmt, lastCmt + 2);
mid = mid.indexOf('*/') === mid.length - 2 ? '' : mid.replace(RX_CSS_COMMENTS, '');
code = mid + code.slice(lastCmt + 2);
return quickCheckAround(code) || RX_CSS_TRANSITION_DETECTOR.test(code);
}
function quickCheckAround(code, pos = code.indexOf('transition')) {
return RX_CSS_TRANSITION_DETECTOR.test(code.substr(Math.max(0, pos - 10), 50));
}
}
/*
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.
Additionally we'll check for invalid regexps.
*/
function detectSloppyRegexps({matchUrl, ids}) {
const results = [];
for (const id of ids) {
const style = cachedStyles.byId.get(id);
if (!style) continue;
// make sure all regexps are compiled
const rxCache = cachedStyles.regexps;
let hasRegExp = false;
for (const section of style.sections) {
for (const regexp of section.regexps) {
hasRegExp = true;
for (let pass = 1; pass <= 2; pass++) {
const cacheKey = pass === 1 ? regexp : SLOPPY_REGEXP_PREFIX + regexp;
if (!rxCache.has(cacheKey)) {
// according to CSS4 @document specification the entire URL must match
const anchored = pass === 1 ? '^(?:' + regexp + ')$' : '^' + regexp + '$';
// create in the bg context to avoid leaking of "dead objects"
const rx = tryRegExp(anchored);
rxCache.set(cacheKey, rx || false);
}
}
}
}
if (!hasRegExp) continue;
const applied = getApplicableSections({style, matchUrl});
const wannabe = getApplicableSections({style, matchUrl, strictRegexp: false});
results.push({
id,
applied,
skipped: wannabe.length - applied.length,
hasInvalidRegexps: wannabe.some(({regexps}) => regexps.some(rx => !rxCache.has(rx))),
});
}
return results;
}

791
background/style-manager.js Normal file
View File

@ -0,0 +1,791 @@
/* global API msg */// msg.js
/* global CHROME URLS deepEqual isEmptyObj mapObj stringAsRegExp tryRegExp tryURL */// toolbox.js
/* global bgReady createCache uuidIndex */// common.js
/* global calcStyleDigest styleCodeEmpty */// sections-util.js
/* global db */
/* global prefs */
/* global tabMan */
/* global usercssMan */
/* global colorScheme */
'use strict';
/*
This style manager is a layer between content script and the DB. When a style
is added/updated, it broadcast a message to content script and the content
script would try to fetch the new code.
The live preview feature relies on `runtime.connect` and `port.onDisconnect`
to cleanup the temporary code. See livePreview in /edit.
*/
const styleUtil = {};
/* exported styleMan */
const styleMan = (() => {
Object.assign(styleUtil, {
id2style,
handleSave,
uuid2style,
});
//#region Declarations
/** @typedef {{
style: StyleObj,
preview?: StyleObj,
appliesTo: Set<string>,
}} StyleMapData */
/** @type {Map<number,StyleMapData>} */
const dataMap = new Map();
/** @typedef {Object<styleId,{id: number, code: string[]}>} StyleSectionsToApply */
/** @type {Map<string,{maybeMatch: Set<styleId>, sections: StyleSectionsToApply}>} */
const cachedStyleForUrl = createCache({
onDeleted(url, cache) {
for (const section of Object.values(cache.sections)) {
const data = id2data(section.id);
if (data) data.appliesTo.delete(url);
}
},
});
const BAD_MATCHER = {test: () => false};
const compileRe = createCompiler(text => `^(${text})$`);
const compileSloppyRe = createCompiler(text => `^${text}$`);
const compileExclusion = createCompiler(buildExclusion);
const uuidv4 = crypto.randomUUID ? crypto.randomUUID.bind(crypto) : (() => {
const seeds = crypto.getRandomValues(new Uint16Array(8));
// 00001111-2222-M333-N444-555566667777
seeds[3] = seeds[3] & 0x0FFF | 0x4000; // UUID version 4, M = 4
seeds[4] = seeds[4] & 0x3FFF | 0x8000; // UUID variant 1, N = 8..0xB
return Array.from(seeds, hex4dashed).join('');
});
const MISSING_PROPS = {
name: style => `ID: ${style.id}`,
_id: () => uuidv4(),
_rev: () => Date.now(),
};
const DELETE_IF_NULL = ['id', 'customName', 'md5Url', 'originalMd5'];
const INJ_ORDER = 'injectionOrder';
const order = {main: {}, prio: {}};
const orderWrap = {
id: INJ_ORDER,
value: mapObj(order, () => []),
_id: `${chrome.runtime.id}-${INJ_ORDER}`,
_rev: 0,
};
uuidIndex.addCustomId(orderWrap, {set: setOrder});
class MatchQuery {
constructor(url) {
this.url = url;
}
get urlWithoutHash() {
return this._set('urlWithoutHash', this.url.split('#', 1)[0]);
}
get urlWithoutParams() {
return this._set('urlWithoutParams', this.url.split(/[?#]/, 1)[0]);
}
get domain() {
return this._set('domain', tryURL(this.url).hostname);
}
get isOwnPage() {
return this._set('isOwnPage', this.url.startsWith(URLS.ownOrigin));
}
_set(name, value) {
Object.defineProperty(this, name, {value});
return value;
}
}
/** @type {Promise|boolean} will be `true` to avoid wasting a microtask tick on each `await` */
let ready = Promise.all([init(), prefs.ready]);
chrome.runtime.onConnect.addListener(port => {
if (port.name === 'livePreview') {
handleLivePreview(port);
} else if (port.name.startsWith('draft:')) {
handleDraft(port);
}
});
colorScheme.onChange(value => {
msg.broadcastExtension({method: 'colorScheme', value});
for (const {style} of dataMap.values()) {
if (colorScheme.SCHEMES.includes(style.preferScheme)) {
broadcastStyleUpdated(style, 'colorScheme');
}
}
});
//#endregion
//#region Exports
return {
/** @returns {Promise<number>} style id */
async delete(id, reason) {
if (ready.then) await ready;
const {style, appliesTo} = dataMap.get(id);
const sync = reason !== 'sync';
const uuid = style._id;
db.styles.delete(id);
if (sync) API.sync.delete(uuid, Date.now());
for (const url of appliesTo) {
const cache = cachedStyleForUrl.get(url);
if (cache) delete cache.sections[id];
}
dataMap.delete(id);
uuidIndex.delete(uuid);
mapObj(orderWrap.value, (group, type) => {
delete order[type][id];
const i = group.indexOf(uuid);
if (i >= 0) group.splice(i, 1);
});
setOrder(orderWrap, {calc: false});
if (style._usw && style._usw.token) {
// Must be called after the style is deleted from dataMap
API.usw.revoke(id);
}
API.drafts.delete(id);
await msg.broadcast({
method: 'styleDeleted',
style: {id},
});
return id;
},
/** @returns {Promise<StyleObj>} */
async editSave(style) {
if (ready.then) await ready;
style = mergeWithMapped(style);
style.updateDate = Date.now();
return saveStyle(style, {reason: 'editSave'});
},
/** @returns {Promise<?StyleObj>} */
async find(...filters) {
if (ready.then) await ready;
for (const filter of filters) {
const filterEntries = Object.entries(filter);
for (const {style} of dataMap.values()) {
if (filterEntries.every(([key, val]) => style[key] === val)) {
return style;
}
}
}
return null;
},
/** @returns {Promise<StyleObj[]>} */
async getAll() {
if (ready.then) await ready;
return getAllAsArray();
},
/** @returns {Promise<Object<string,StyleObj[]>>}>} */
async getAllOrdered(keys) {
if (ready.then) await ready;
const res = mapObj(orderWrap.value, group => group.map(uuid2style).filter(Boolean));
if (res.main.length + res.prio.length < dataMap.size) {
for (const {style} of dataMap.values()) {
if (!(style.id in order.main) && !(style.id in order.prio)) {
res.main.push(style);
}
}
}
return keys
? mapObj(res, group => group.map(style => mapObj(style, null, keys)))
: res;
},
getOrder: () => orderWrap.value,
/** @returns {Promise<string | {[remoteId:string]: styleId}>}>} */
async getRemoteInfo(id) {
if (ready.then) await ready;
if (id) return calcRemoteId(id2style(id));
const res = {};
for (const {style} of dataMap.values()) {
const [rid, vars] = calcRemoteId(style);
if (rid) res[rid] = [style.id, vars];
}
return res;
},
/** @returns {Promise<StyleSectionsToApply>} */
async getSectionsByUrl(url, id, isInitialApply) {
if (ready.then) await ready;
if (isInitialApply && prefs.get('disableAll')) {
return {
cfg: {
disableAll: true,
},
};
}
// TODO: enable in FF when it supports sourceURL comment in style elements (also options.html)
const {exposeStyleName} = CHROME && prefs.__values;
const sender = CHROME && this && this.sender || {};
if (sender.frameId === 0) {
/* Chrome hides text frament from location.href of the page e.g. #:~:text=foo
so we'll use the real URL reported by webNavigation API.
TODO: if FF will do the same, this won't work as is: FF reports onCommitted too late */
url = tabMan.get(sender.tab.id, 'url', 0) || url;
}
let cache = cachedStyleForUrl.get(url);
if (!cache) {
cache = {
sections: {},
maybeMatch: new Set(),
};
buildCache(cache, url, dataMap.values());
cachedStyleForUrl.set(url, cache);
} else if (cache.maybeMatch.size) {
buildCache(cache, url, Array.from(cache.maybeMatch, id2data).filter(Boolean));
}
return Object.assign({cfg: {exposeStyleName, order}},
id ? mapObj(cache.sections, null, [id])
: cache.sections);
},
/** @returns {Promise<StyleObj>} */
async get(id) {
if (ready.then) await ready;
return id2style(id);
},
/** @returns {Promise<StylesByUrlResult[]>} */
async getByUrl(url, id = null) {
if (ready.then) await ready;
// FIXME: do we want to cache this? Who would like to open popup rapidly
// or search the DB with the same URL?
const result = [];
const styles = id
? [id2style(id)].filter(Boolean)
: getAllAsArray();
const query = new MatchQuery(url);
for (const style of styles) {
let excluded = false;
let excludedScheme = false;
let included = false;
let sloppy = false;
let sectionMatched = false;
const match = urlMatchStyle(query, style);
// TODO: enable this when the function starts returning false
// if (match === false) {
// continue;
// }
if (match === 'included') {
included = true;
}
if (match === 'excluded') {
excluded = true;
}
if (match === 'excludedScheme') {
excludedScheme = true;
}
for (const section of style.sections) {
const match = urlMatchSection(query, section, true);
if (match) {
if (match === 'sloppy') {
sloppy = true;
}
sectionMatched = true;
break;
}
}
if (sectionMatched || included) {
result.push(/** @namespace StylesByUrlResult */ {
style, excluded, sloppy, excludedScheme, sectionMatched, included});
}
}
return result;
},
/** @returns {Promise<StyleObj[]>} */
async importMany(items) {
if (ready.then) await ready;
for (const style of items) {
beforeSave(style);
if (style.sourceCode && style.usercssData) {
await usercssMan.buildCode(style);
}
}
const events = await db.styles.putMany(items);
return Promise.all(items.map((item, i) =>
handleSave(item, {reason: 'import'}, events[i])
));
},
/** @returns {Promise<StyleObj>} */
async install(style, reason = null) {
if (ready.then) await ready;
reason = reason || dataMap.has(style.id) ? 'update' : 'install';
style = mergeWithMapped(style);
style.originalDigest = await calcStyleDigest(style);
// FIXME: update updateDate? what about usercss config?
return saveStyle(style, {reason});
},
save: saveStyle,
async setOrder(value) {
if (ready.then) await ready;
return setOrder({value}, {broadcast: true, sync: true});
},
/** @returns {Promise<number>} style id */
async toggle(id, enabled) {
if (ready.then) await ready;
const style = Object.assign({}, id2style(id), {enabled});
await saveStyle(style, {reason: 'toggle'});
return id;
},
// using bind() to skip step-into when debugging
/** @returns {Promise<StyleObj>} */
addExclusion: addIncludeExclude.bind(null, 'exclusions'),
/** @returns {Promise<StyleObj>} */
addInclusion: addIncludeExclude.bind(null, 'inclusions'),
/** @returns {Promise<?StyleObj>} */
removeExclusion: removeIncludeExclude.bind(null, 'exclusions'),
/** @returns {Promise<?StyleObj>} */
removeInclusion: removeIncludeExclude.bind(null, 'inclusions'),
async config(id, prop, value) {
if (ready.then) await ready;
const style = Object.assign({}, id2style(id));
const {preview = {}} = dataMap.get(id);
style[prop] = preview[prop] = value;
return saveStyle(style, {reason: 'config'});
},
};
//#endregion
//#region Implementation
/** @returns {StyleMapData} */
function id2data(id) {
return dataMap.get(id);
}
/** @returns {?StyleObj} */
function id2style(id) {
return (dataMap.get(Number(id)) || {}).style;
}
/** @returns {?StyleObj} */
function uuid2style(uuid) {
return id2style(uuidIndex.get(uuid));
}
function calcRemoteId({md5Url, updateUrl, usercssData: ucd} = {}) {
let id;
id = (id = /\d+/.test(md5Url) || URLS.extractUsoArchiveId(updateUrl)) && `uso-${id}`
|| (id = URLS.extractUSwId(updateUrl)) && `usw-${id}`
|| '';
return id && [
id,
ucd && !isEmptyObj(ucd.vars),
];
}
/** @returns {StyleObj} */
function createNewStyle() {
return /** @namespace StyleObj */ {
enabled: true,
updateUrl: null,
md5Url: null,
url: null,
originalMd5: null,
installDate: Date.now(),
};
}
/** @returns {void} */
function storeInMap(style) {
dataMap.set(style.id, {
style,
appliesTo: new Set(),
});
uuidIndex.set(style._id, style.id);
}
/** @returns {StyleObj} */
function mergeWithMapped(style) {
return Object.assign({},
id2style(style.id) || createNewStyle(),
style);
}
function handleDraft(port) {
const id = port.name.split(':').pop();
port.onDisconnect.addListener(() => API.drafts.delete(Number(id) || id));
}
function handleLivePreview(port) {
let id;
port.onMessage.addListener(style => {
if (!id) id = style.id;
const data = id2data(id);
data.preview = style;
broadcastStyleUpdated(style, 'editPreview');
});
port.onDisconnect.addListener(() => {
port = null;
if (id) {
const data = id2data(id);
if (data) {
data.preview = null;
broadcastStyleUpdated(data.style, 'editPreviewEnd');
}
}
});
}
async function addIncludeExclude(type, id, rule) {
if (ready.then) await ready;
const style = Object.assign({}, id2style(id));
const list = style[type] || (style[type] = []);
if (list.includes(rule)) {
throw new Error('The rule already exists');
}
style[type] = list.concat([rule]);
return saveStyle(style, {reason: 'config'});
}
async function removeIncludeExclude(type, id, rule) {
if (ready.then) await ready;
const style = Object.assign({}, id2style(id));
const list = style[type];
if (!list || !list.includes(rule)) {
return;
}
style[type] = list.filter(r => r !== rule);
return saveStyle(style, {reason: 'config'});
}
function broadcastStyleUpdated(style, reason, method = 'styleUpdated') {
const {id} = style;
const data = id2data(id);
const excluded = new Set();
const updated = new Set();
for (const [url, cache] of cachedStyleForUrl.entries()) {
if (!data.appliesTo.has(url)) {
cache.maybeMatch.add(id);
continue;
}
const code = getAppliedCode(new MatchQuery(url), style);
if (code) {
updated.add(url);
buildCacheEntry(cache, style, code);
} else {
excluded.add(url);
delete cache.sections[id];
}
}
data.appliesTo = updated;
return msg.broadcast({
method,
reason,
style: {
id,
md5Url: style.md5Url,
enabled: style.enabled,
},
});
}
function beforeSave(style) {
if (!style.name) {
throw new Error('Style name is empty');
}
for (const key of DELETE_IF_NULL) {
if (style[key] == null) {
delete style[key];
}
}
if (!style._id) {
style._id = uuidv4();
}
style._rev = Date.now();
fixKnownProblems(style);
}
async function saveStyle(style, handlingOptions) {
beforeSave(style);
const newId = await db.styles.put(style);
return handleSave(style, handlingOptions, newId);
}
function handleSave(style, {reason, broadcast = true}, id = style.id) {
if (style.id == null) style.id = id;
const data = id2data(id);
const method = data ? 'styleUpdated' : 'styleAdded';
if (!data) {
storeInMap(style);
} else {
data.style = style;
}
if (reason !== 'sync') {
API.sync.putDoc(style);
}
if (broadcast) broadcastStyleUpdated(style, reason, method);
return style;
}
// get styles matching a URL, including sloppy regexps and excluded items.
function getAppliedCode(query, data) {
const result = urlMatchStyle(query, data);
if (result === 'included') {
// return all sections
return data.sections.map(s => s.code);
}
if (result !== true) {
return;
}
const code = [];
for (const section of data.sections) {
if (urlMatchSection(query, section) === true && !styleCodeEmpty(section.code)) {
code.push(section.code);
}
}
return code.length && code;
}
async function init() {
const orderPromise = API.prefsDb.get(orderWrap.id);
const styles = await db.styles.getAll() || [];
const updated = await Promise.all(styles.map(fixKnownProblems).filter(Boolean));
if (updated.length) {
await db.styles.putMany(updated);
}
setOrder(await orderPromise, {store: false});
styles.forEach(storeInMap);
ready = true;
bgReady._resolveStyles();
}
function fixKnownProblems(style, initIndex, initArray) {
let res = 0;
for (const key in MISSING_PROPS) {
if (!style[key]) {
style[key] = MISSING_PROPS[key](style);
res = 1;
}
}
/* Upgrade the old way of customizing local names */
const {originalName} = style;
if (originalName) {
if (originalName !== style.name) {
style.customName = style.name;
style.name = originalName;
}
delete style.originalName;
res = 1;
}
/* wrong homepage url in 1.5.20-1.5.21 due to commit 1e5f118d */
for (const key of ['url', 'installationUrl']) {
const url = style[key];
const fixedUrl = url && url.replace(/([^:]\/)\//, '$1');
if (fixedUrl !== url) {
res = 1;
style[key] = fixedUrl;
}
}
let url;
/* USO bug, duplicate "update" subdomain, see #523 */
if ((url = style.md5Url) && url.includes('update.update.userstyles')) {
res = style.md5Url = url.replace('update.update.userstyles', 'update.userstyles');
}
/* Default homepage URL for external styles installed from a known distro */
if (
(!style.url || !style.installationUrl) &&
(url = style.updateUrl) &&
(url = URLS.extractGreasyForkInstallUrl(url) ||
URLS.extractUsoArchiveInstallUrl(url) ||
URLS.extractUSwInstallUrl(url)
)
) {
if (!style.url) res = style.url = url;
if (!style.installationUrl) res = style.installationUrl = url;
}
/* @import must precede `vars` that we add at beginning */
if (
initArray &&
!isEmptyObj((style.usercssData || {}).vars) &&
style.sections.some(({code}) =>
code.startsWith(':root {\n --') &&
/@import\s/i.test(code))
) {
return usercssMan.buildCode(style);
}
return res && style;
}
function urlMatchStyle(query, style) {
if (
style.exclusions &&
style.exclusions.some(e => compileExclusion(e).test(query.urlWithoutParams))
) {
return 'excluded';
}
if (!style.enabled) {
return 'disabled';
}
if (!colorScheme.shouldIncludeStyle(style)) {
return 'excludedScheme';
}
if (
style.inclusions &&
style.inclusions.some(r => compileExclusion(r).test(query.urlWithoutParams))
) {
return 'included';
}
return true;
}
function urlMatchSection(query, section, skipEmptyGlobal) {
let dd, ddL, pp, ppL, rr, rrL, uu, uuL;
if (
(dd = section.domains) && (ddL = dd.length) && dd.some(urlMatchDomain, query) ||
(pp = section.urlPrefixes) && (ppL = pp.length) && pp.some(urlMatchPrefix, query) ||
/* Per the specification the fragment portion is ignored in @-moz-document:
https://www.w3.org/TR/2012/WD-css3-conditional-20120911/#url-of-doc
but the spec is outdated and doesn't account for SPA sites,
so we only respect it for `url()` function */
(uu = section.urls) && (uuL = uu.length) && (
uu.includes(query.url) ||
uu.includes(query.urlWithoutHash)
) ||
(rr = section.regexps) && (rrL = rr.length) && rr.some(urlMatchRegexp, query)
) {
return true;
}
/*
According to CSS4 @document specification the entire URL must match.
Stylish-for-Chrome implemented it incorrectly since the very beginning.
We'll detect styles that abuse the bug by finding the sections that
would have been applied by Stylish but not by us as we follow the spec.
*/
if (rrL && rr.some(urlMatchRegexpSloppy, query)) {
return 'sloppy';
}
// TODO: check for invalid regexps?
return !rrL && !ppL && !uuL && !ddL &&
!query.isOwnPage && // We allow only intentionally targeted sections for own pages
(!skipEmptyGlobal || !styleCodeEmpty(section.code));
}
/** @this {MatchQuery} */
function urlMatchDomain(d) {
const _d = this.domain;
return d === _d ||
_d[_d.length - d.length - 1] === '.' && _d.endsWith(d);
}
/** @this {MatchQuery} */
function urlMatchPrefix(p) {
return p && this.url.startsWith(p);
}
/** @this {MatchQuery} */
function urlMatchRegexp(r) {
return (!this.isOwnPage || /\bextension\b/.test(r)) &&
compileRe(r).test(this.url);
}
/** @this {MatchQuery} */
function urlMatchRegexpSloppy(r) {
return (!this.isOwnPage || /\bextension\b/.test(r)) &&
compileSloppyRe(r).test(this.url);
}
function createCompiler(compile) {
// FIXME: FIFO cache doesn't work well here, if we want to match many
// regexps more than the cache size, we will never hit the cache because
// the first cache is deleted. So we use a simple map but it leaks memory.
const cache = new Map();
return text => {
let re = cache.get(text);
if (!re) {
re = tryRegExp(compile(text));
if (!re) {
re = BAD_MATCHER;
}
cache.set(text, re);
}
return re;
};
}
function compileGlob(text) {
return stringAsRegExp(text, '', true)
.replace(/\\\\\\\*|\\\*/g, m => m.length > 2 ? m : '.*');
}
function buildExclusion(text) {
// match pattern
const match = text.match(/^(\*|[\w-]+):\/\/(\*\.)?([\w.]+\/.*)/);
if (!match) {
return '^' + compileGlob(text) + '$';
}
return '^' +
(match[1] === '*' ? '[\\w-]+' : match[1]) +
'://' +
(match[2] ? '(?:[\\w.]+\\.)?' : '') +
compileGlob(match[3]) +
'$';
}
function buildCache(cache, url, styleList) {
const query = new MatchQuery(url);
for (const {style, appliesTo, preview} of styleList) {
const code = getAppliedCode(query, preview || style);
if (code) {
buildCacheEntry(cache, style, code);
appliesTo.add(url);
}
}
}
function buildCacheEntry(cache, style, code = style.code) {
cache.sections[style.id] = {
code,
id: style.id,
name: style.customName || style.name,
};
}
/** @returns {StyleObj[]} */
function getAllAsArray() {
return Array.from(dataMap.values(), v => v.style);
}
/** uuidv4 helper: converts to a 4-digit hex string and adds "-" at required positions */
function hex4dashed(num, i) {
return (num + 0x10000).toString(16).slice(-4) + (i >= 1 && i <= 4 ? '-' : '');
}
async function setOrder(data, {broadcast, calc = true, store = true, sync} = {}) {
if (!data || !data.value || deepEqual(data.value, orderWrap.value)) {
return;
}
Object.assign(orderWrap, data, sync && {_rev: Date.now()});
if (calc) {
for (const [type, group] of Object.entries(data.value)) {
const dst = order[type] = {};
group.forEach((uuid, i) => {
const id = uuidIndex.get(uuid);
if (id) dst[id] = i;
});
}
}
if (broadcast) {
msg.broadcast({method: 'styleSort', order});
}
if (store) {
await API.prefsDb.put(orderWrap, orderWrap.id);
}
if (sync) {
API.sync.putDoc(orderWrap);
}
}
//#endregion
})();

View File

@ -0,0 +1,108 @@
/* global API */// msg.js
/* global RX_META debounce stringAsRegExp tryRegExp */// toolbox.js
/* global addAPI */// common.js
'use strict';
(() => {
// toLocaleLowerCase cache, autocleared after 1 minute
const cache = new Map();
const METAKEYS = ['customName', 'name', 'url', 'installationUrl', 'updateUrl'];
const extractMeta = style =>
style.usercssData
? (style.sourceCode.match(RX_META) || [''])[0]
: null;
const stripMeta = style =>
style.usercssData
? style.sourceCode.replace(RX_META, '')
: null;
const MODES = Object.assign(Object.create(null), {
code: (style, test) =>
style.usercssData
? test(stripMeta(style))
: searchSections(style, test, 'code'),
meta: (style, test, part) =>
METAKEYS.some(key => test(style[key])) ||
test(part === 'all' ? style.sourceCode : extractMeta(style)) ||
searchSections(style, test, 'funcs'),
name: (style, test) =>
test(style.customName) ||
test(style.name),
all: (style, test) =>
MODES.meta(style, test, 'all') ||
!style.usercssData && MODES.code(style, test),
});
addAPI(/** @namespace API */ {
styles: {
/**
* @param params
* @param {string} params.query - 1. url:someurl 2. text (may contain quoted parts like "qUot Ed")
* @param {'name'|'meta'|'code'|'all'|'url'} [params.mode=all]
* @param {number[]} [params.ids] - if not specified, all styles are searched
* @returns {number[]} - array of matched styles ids
*/
async searchDB({query, mode = 'all', ids}) {
let res = [];
if (mode === 'url' && query) {
res = (await API.styles.getByUrl(query)).map(r => r.style.id);
} else if (mode in MODES) {
const modeHandler = MODES[mode];
const m = /^\/(.+?)\/([gimsuy]*)$/.exec(query);
const rx = m && tryRegExp(m[1], m[2]);
const test = rx ? rx.test.bind(rx) : createTester(query);
res = (await API.styles.getAll())
.filter(style =>
(!ids || ids.includes(style.id)) &&
(!query || modeHandler(style, test)))
.map(style => style.id);
if (cache.size) debounce(clearCache, 60e3);
}
return res;
},
},
});
function createTester(query) {
const flags = `u${lower(query) === query ? 'i' : ''}`;
const words = query
.split(/(".*?")|\s+/)
.filter(Boolean)
.map(w => w.startsWith('"') && w.endsWith('"')
? w.slice(1, -1)
: w)
.filter(w => w.length > 1);
const rxs = (words.length ? words : [query])
.map(w => stringAsRegExp(w, flags));
return text => rxs.every(rx => rx.test(text));
}
function searchSections({sections}, test, part) {
const inCode = part === 'code' || part === 'all';
const inFuncs = part === 'funcs' || part === 'all';
for (const section of sections) {
for (const prop in section) {
const value = section[prop];
if (inCode && prop === 'code' && test(value) ||
inFuncs && Array.isArray(value) && value.some(str => test(str))) {
return true;
}
}
}
}
function lower(text) {
let result = cache.get(text);
if (!result) cache.set(text, result = text.toLocaleLowerCase());
return result;
}
function clearCache() {
cache.clear();
}
})();

View File

@ -1,7 +1,14 @@
/* global getStyles API_METHODS */
/* global API */// msg.js
/* global addAPI */// common.js
/* global isEmptyObj */// toolbox.js
/* global prefs */
'use strict';
API_METHODS.styleViaAPI = !CHROME && (() => {
/**
* Uses chrome.tabs.insertCSS
*/
(() => {
const ACTIONS = {
styleApply,
styleDeleted,
@ -9,28 +16,38 @@ API_METHODS.styleViaAPI = !CHROME && (() => {
styleAdded,
styleReplaceAll,
prefChanged,
updateCount,
};
const NOP = Promise.resolve(new Error('NOP'));
const NOP = new Error('NOP');
const onError = () => {};
/* <tabId>: Object
<frameId>: Object
url: String, non-enumerable
<styleId>: Array of strings
section code */
const cache = new Map();
let observingTabs = false;
return (request, sender) => {
const action = ACTIONS[request.action];
return !action ? NOP :
action(request, sender)
.catch(onError)
.then(maybeToggleObserver);
};
addAPI(/** @namespace API */ {
async styleViaAPI(request) {
try {
const fn = ACTIONS[request.method];
return fn ? fn(request, this.sender) : NOP;
} catch (e) {}
maybeToggleObserver();
},
});
function styleApply({id = null, ignoreUrlCheck}, {tab, frameId, url}) {
function updateCount(request, sender) {
const {tab, frameId} = sender;
if (frameId) {
throw new Error('we do not count styles for frames');
}
const {frameStyles} = getCachedData(tab.id, frameId);
API.updateIconBadge.call({sender}, Object.keys(frameStyles));
}
function styleApply({id = null, ignoreUrlCheck = false}, {tab, frameId, url}) {
if (prefs.get('disableAll')) {
return NOP;
}
@ -38,24 +55,16 @@ API_METHODS.styleViaAPI = !CHROME && (() => {
if (id === null && !ignoreUrlCheck && frameStyles.url === url) {
return NOP;
}
return getStyles({id, matchUrl: url, asHash: true}).then(styles => {
return API.styles.getSectionsByUrl(url, id).then(sections => {
delete sections.cfg;
const tasks = [];
for (const styleId in styles) {
if (isNaN(parseInt(styleId))) {
continue;
}
// shallow-extract code from the sections array in order to reuse references
// in other places whereas the combined string gets garbage-collected
const styleSections = styles[styleId].map(section => section.code);
const code = styleSections.join('\n');
if (!code) {
delete frameStyles[styleId];
continue;
}
for (const section of Object.values(sections)) {
const styleId = section.id;
const code = section.code.join('\n');
if (code === (frameStyles[styleId] || []).join('\n')) {
continue;
}
frameStyles[styleId] = styleSections;
frameStyles[styleId] = section.code;
tasks.push(
browser.tabs.insertCSS(tab.id, {
code,
@ -70,16 +79,18 @@ API_METHODS.styleViaAPI = !CHROME && (() => {
cache.set(tab.id, tabFrames);
}
return Promise.all(tasks);
});
})
.then(() => updateCount(null, {tab, frameId}));
}
function styleDeleted({id}, {tab, frameId}) {
function styleDeleted({style: {id}}, {tab, frameId}) {
const {tabFrames, frameStyles, styleSections} = getCachedData(tab.id, frameId, id);
const code = styleSections.join('\n');
if (code && !duplicateCodeExists({frameStyles, id, code})) {
delete frameStyles[id];
removeFrameIfEmpty(tab.id, frameId, tabFrames, frameStyles);
return removeCSS(tab.id, frameId, code);
return removeCSS(tab.id, frameId, code)
.then(() => updateCount(null, {tab, frameId}));
} else {
return NOP;
}
@ -87,7 +98,7 @@ API_METHODS.styleViaAPI = !CHROME && (() => {
function styleUpdated({style}, sender) {
if (!style.enabled) {
return styleDeleted(style, sender);
return styleDeleted({style}, sender);
}
const {tab, frameId} = sender;
const {frameStyles, styleSections} = getCachedData(tab.id, frameId, style.id);
@ -122,7 +133,7 @@ API_METHODS.styleViaAPI = !CHROME && (() => {
}
const {tab, frameId} = sender;
const {tabFrames, frameStyles} = getCachedData(tab.id, frameId);
if (isEmpty(frameStyles)) {
if (isEmptyObj(frameStyles)) {
return NOP;
}
removeFrameIfEmpty(tab.id, frameId, tabFrames, {});
@ -157,9 +168,9 @@ API_METHODS.styleViaAPI = !CHROME && (() => {
return;
}
const tabFrames = cache.get(tabId);
if (frameId in tabFrames) {
if (tabFrames && frameId in tabFrames) {
delete tabFrames[frameId];
if (isEmpty(tabFrames)) {
if (isEmptyObj(tabFrames)) {
onTabRemoved(tabId);
}
}
@ -175,9 +186,9 @@ API_METHODS.styleViaAPI = !CHROME && (() => {
}
function removeFrameIfEmpty(tabId, frameId, tabFrames, frameStyles) {
if (isEmpty(frameStyles)) {
if (isEmptyObj(frameStyles)) {
delete tabFrames[frameId];
if (isEmpty(tabFrames)) {
if (isEmptyObj(tabFrames)) {
cache.delete(tabId);
}
return true;
@ -220,11 +231,4 @@ API_METHODS.styleViaAPI = !CHROME && (() => {
return browser.tabs.removeCSS(tabId, {frameId, code, matchAboutBlank: true})
.catch(onError);
}
function isEmpty(obj) {
for (const k in obj) {
return false;
}
return true;
}
})();

View File

@ -0,0 +1,166 @@
/* global API */// msg.js
/* global CHROME URLS ignoreChromeError */// toolbox.js
/* global prefs */
'use strict';
(() => {
const idCSP = 'patchCsp';
const idOFF = 'disableAll';
const idXHR = 'styleViaXhr';
const rxHOST = /^('none'|(https?:\/\/)?[^']+?[^:'])$/; // strips CSP sources covered by *
const blobUrlPrefix = 'blob:' + chrome.runtime.getURL('/');
/** @type {Object<string,StylesToPass>} */
const stylesToPass = {};
const state = {};
const injectedCode = CHROME && `${data => {
if (self.INJECTED !== 1) { // storing data only if apply.js hasn't run yet
window[Symbol.for('styles')] = data;
}
}}`;
toggle();
prefs.subscribe([idXHR, idOFF, idCSP], toggle);
function toggle() {
const off = prefs.get(idOFF);
const csp = prefs.get(idCSP) && !off;
const xhr = prefs.get(idXHR) && !off;
if (xhr === state.xhr && csp === state.csp && off === state.off) {
return;
}
const reqFilter = {
urls: ['*://*/*'],
types: ['main_frame', 'sub_frame'],
};
chrome.webNavigation.onCommitted.removeListener(injectData);
chrome.webRequest.onBeforeRequest.removeListener(prepareStyles);
chrome.webRequest.onHeadersReceived.removeListener(modifyHeaders);
if (xhr || csp) {
// We unregistered it above so that the optional EXTRA_HEADERS is properly re-registered
chrome.webRequest.onHeadersReceived.addListener(modifyHeaders, reqFilter, [
'blocking',
'responseHeaders',
xhr && chrome.webRequest.OnHeadersReceivedOptions.EXTRA_HEADERS,
].filter(Boolean));
}
if (CHROME ? !off : xhr || csp) {
chrome.webRequest.onBeforeRequest.addListener(prepareStyles, reqFilter);
}
if (CHROME && !off) {
chrome.webNavigation.onCommitted.addListener(injectData, {url: [{urlPrefix: 'http'}]});
}
if (CHROME) {
chrome.webRequest.onBeforeRequest.addListener(openNamedStyle, {
urls: [URLS.ownOrigin + '*.user.css'],
types: ['main_frame'],
}, ['blocking']);
}
state.csp = csp;
state.off = off;
state.xhr = xhr;
}
/** @param {chrome.webRequest.WebRequestBodyDetails} req */
async function prepareStyles(req) {
const sections = await API.styles.getSectionsByUrl(req.url);
stylesToPass[req2key(req)] = /** @namespace StylesToPass */ {
blobId: '',
str: JSON.stringify(sections),
timer: setTimeout(cleanUp, 600e3, req),
};
}
function injectData(req) {
const data = stylesToPass[req2key(req)];
if (data && !data.injected) {
data.injected = true;
chrome.tabs.executeScript(req.tabId, {
frameId: req.frameId,
runAt: 'document_start',
code: `(${injectedCode})(${data.str})`,
}, ignoreChromeError);
if (!state.xhr) cleanUp(req);
}
}
/** @param {chrome.webRequest.WebResponseHeadersDetails} req */
function modifyHeaders(req) {
const {responseHeaders} = req;
const data = stylesToPass[req2key(req)];
if (!data || data.str === '{}') {
cleanUp(req);
return;
}
if (state.xhr) {
data.blobId = URL.createObjectURL(new Blob([data.str])).slice(blobUrlPrefix.length);
responseHeaders.push({
name: 'Set-Cookie',
value: `${chrome.runtime.id}=${data.blobId}`,
});
}
const csp = state.csp &&
responseHeaders.find(h => h.name.toLowerCase() === 'content-security-policy');
if (csp) {
patchCsp(csp);
}
if (state.xhr || csp) {
return {responseHeaders};
}
}
/** @param {chrome.webRequest.HttpHeader} csp */
function patchCsp(csp) {
const src = {};
for (let p of csp.value.split(';')) {
p = p.trim().split(/\s+/);
src[p[0]] = p.slice(1);
}
// Allow style assets
patchCspSrc(src, 'img-src', 'data:', '*');
patchCspSrc(src, 'font-src', 'data:', '*');
// Allow our DOM styles, allow @import from any URL
patchCspSrc(src, 'style-src', "'unsafe-inline'", '*');
// Allow our XHR cookies in CSP sandbox (known case: raw github urls)
if (src.sandbox && !src.sandbox.includes('allow-same-origin')) {
src.sandbox.push('allow-same-origin');
}
csp.value = Object.entries(src).map(([k, v]) =>
`${k}${v.length ? ' ' : ''}${v.join(' ')}`).join('; ');
}
function patchCspSrc(src, name, ...values) {
let def = src['default-src'];
let list = src[name];
if (def || list) {
if (!def) def = [];
if (!list) list = [...def];
if (values.includes('*')) list = src[name] = list.filter(v => !rxHOST.test(v));
list.push(...values.filter(v => !list.includes(v) && !def.includes(v)));
if (!list.length) delete src[name];
}
}
function cleanUp(req) {
const key = req2key(req);
const data = stylesToPass[key];
if (data) {
delete stylesToPass[key];
clearTimeout(data.timer);
if (data.blobId) {
URL.revokeObjectURL(blobUrlPrefix + data.blobId);
}
}
}
/** @param {chrome.webRequest.WebRequestBodyDetails} req */
function openNamedStyle(req) {
if (!req.url.includes('?')) { // skipping our usercss installer
chrome.tabs.update(req.tabId, {url: 'edit.html?id=' + req.url.split('#')[1]});
return {cancel: true};
}
}
function req2key(req) {
return req.tabId + ':' + req.frameId;
}
})();

307
background/sync-manager.js Normal file
View File

@ -0,0 +1,307 @@
/* global API msg */// msg.js
/* global bgReady uuidIndex */// common.js
/* global chromeLocal chromeSync */// storage-util.js
/* global db */
/* global iconMan */
/* global prefs */
/* global styleUtil */
/* global tokenMan */
'use strict';
const syncMan = (() => {
//#region Init
const SYNC_DELAY = 1; // minutes
const SYNC_INTERVAL = 30; // minutes
const STATES = Object.freeze({
connected: 'connected',
connecting: 'connecting',
disconnected: 'disconnected',
disconnecting: 'disconnecting',
});
const STORAGE_KEY = 'sync/state/';
const NO_LOGIN = ['webdav'];
const status = /** @namespace SyncManager.Status */ {
STATES,
state: STATES.disconnected,
syncing: false,
progress: null,
currentDriveName: null,
errorMessage: null,
login: false,
};
const compareRevision = (rev1, rev2) => rev1 - rev2;
let lastError = null;
let ctrl;
let currentDrive;
/** @type {Promise|boolean} will be `true` to avoid wasting a microtask tick on each `await` */
let ready = bgReady.styles.then(() => {
ready = true;
prefs.subscribe('sync.enabled',
(_, val) => val === 'none'
? syncMan.stop()
: syncMan.start(val, true),
{runNow: true});
});
chrome.alarms.onAlarm.addListener(async ({name}) => {
if (name === 'syncNow') {
await syncMan.syncNow();
}
});
//#endregion
//#region Exports
return {
async delete(...args) {
if (ready.then) await ready;
if (!currentDrive) return;
schedule();
return ctrl.delete(...args);
},
/** @returns {Promise<SyncManager.Status>} */
async getStatus() {
return status;
},
async login(name) {
if (ready.then) await ready;
if (!name) name = prefs.get('sync.enabled');
await tokenMan.revokeToken(name);
try {
await tokenMan.getToken(name, true);
status.login = true;
} catch (err) {
status.login = false;
throw err;
} finally {
emitStatusChange();
}
},
async putDoc({_id, _rev}) {
if (ready.then) await ready;
if (!currentDrive) return;
schedule();
return ctrl.put(_id, _rev);
},
async setDriveOptions(driveName, options) {
const key = `secure/sync/driveOptions/${driveName}`;
await chromeSync.setValue(key, options);
},
async getDriveOptions(driveName) {
const key = `secure/sync/driveOptions/${driveName}`;
return await chromeSync.getValue(key) || {};
},
async start(name, fromPref = false) {
if (ready.then) await ready;
if (!ctrl) await initController();
if (currentDrive) return;
currentDrive = await getDrive(name);
ctrl.use(currentDrive);
status.state = STATES.connecting;
status.currentDriveName = currentDrive.name;
emitStatusChange();
if (fromPref || NO_LOGIN.includes(currentDrive.name)) {
status.login = true;
} else {
try {
await syncMan.login(name);
} catch (err) {
console.error(err);
status.errorMessage = err.message;
lastError = err;
emitStatusChange();
return syncMan.stop();
}
}
await ctrl.init();
await syncMan.syncNow(name);
prefs.set('sync.enabled', name);
status.state = STATES.connected;
schedule(SYNC_INTERVAL);
emitStatusChange();
},
async stop() {
if (ready.then) await ready;
if (!currentDrive) return;
chrome.alarms.clear('syncNow');
status.state = STATES.disconnecting;
emitStatusChange();
try {
await ctrl.uninit();
await tokenMan.revokeToken(currentDrive.name);
await chromeLocal.remove(STORAGE_KEY + currentDrive.name);
} catch (e) {}
currentDrive = null;
prefs.set('sync.enabled', 'none');
status.state = STATES.disconnected;
status.currentDriveName = null;
status.login = false;
emitStatusChange();
},
async syncNow() {
if (ready.then) await ready;
if (!currentDrive || !status.login) {
console.warn('cannot sync when disconnected');
return;
}
try {
await ctrl.syncNow();
status.errorMessage = null;
lastError = null;
} catch (err) {
err.message = translateErrorMessage(err);
status.errorMessage = err.message;
lastError = err;
if (isGrantError(err)) {
status.login = false;
}
}
emitStatusChange();
},
};
//#endregion
//#region Utils
async function initController() {
await require(['/vendor/db-to-cloud/db-to-cloud']); /* global dbToCloud */
ctrl = dbToCloud.dbToCloud({
onGet: styleUtil.uuid2style,
async onPut(doc) {
const id = uuidIndex.get(doc._id);
const oldCust = uuidIndex.custom[id];
const oldDoc = oldCust || styleUtil.id2style(id);
const diff = oldDoc ? compareRevision(oldDoc._rev, doc._rev) : -1;
if (!diff) return;
if (diff > 0) {
syncMan.putDoc(oldDoc);
} else if (oldCust) {
uuidIndex.custom[id] = doc;
} else {
delete doc.id;
if (id) doc.id = id;
doc.id = await db.styles.put(doc);
await styleUtil.handleSave(doc, {reason: 'sync'});
}
},
onDelete(_id, rev) {
const id = uuidIndex.get(_id);
const oldDoc = styleUtil.id2style(id);
return oldDoc &&
compareRevision(oldDoc._rev, rev) <= 0 &&
API.styles.delete(id, 'sync');
},
async onFirstSync() {
for (const i of Object.values(uuidIndex.custom).concat(await API.styles.getAll())) {
ctrl.put(i._id, i._rev);
}
},
onProgress(e) {
if (e.phase === 'start') {
status.syncing = true;
} else if (e.phase === 'end') {
status.syncing = false;
status.progress = null;
} else {
status.progress = e;
}
emitStatusChange();
},
compareRevision,
getState(drive) {
return chromeLocal.getValue(STORAGE_KEY + drive.name);
},
setState(drive, state) {
return chromeLocal.setValue(STORAGE_KEY + drive.name, state);
},
retryMaxAttempts: 10,
retryExp: 1.2,
retryDelay: 6,
});
}
function emitStatusChange() {
msg.broadcastExtension({method: 'syncStatusUpdate', status});
iconMan.overrideBadge(getErrorBadge());
}
function isNetworkError(err) {
return (
err.name === 'TypeError' && /networkerror|failed to fetch/i.test(err.message) ||
err.code === 502
);
}
function isGrantError(err) {
if (err.code === 401) return true;
if (err.code === 400 && /invalid_grant/.test(err.message)) return true;
if (err.name === 'TokenError') return true;
return false;
}
function getErrorBadge() {
if (status.state === STATES.connected &&
(!status.login || lastError && !isNetworkError(lastError))) {
return {
text: 'x',
color: '#F00',
title: !status.login ? 'syncErrorRelogin' : `${
chrome.i18n.getMessage('syncError')
}\n---------------------\n${
// splitting to limit each line length
lastError.message.replace(/.{60,}?\s(?=.{30,})/g, '$&\n')
}`,
};
}
}
async function getDrive(name) {
if (name === 'dropbox' || name === 'google' || name === 'onedrive' || name === 'webdav') {
const options = await syncMan.getDriveOptions(name);
options.getAccessToken = () => tokenMan.getToken(name);
options.fetch = name === 'webdav' ? fetchWebDAV.bind(options) : fetch;
return dbToCloud.drive[name](options);
}
throw new Error(`unknown cloud name: ${name}`);
}
/** @this {Object} DriveOptions */
function fetchWebDAV(url, init = {}) {
init.credentials = 'omit'; // circumventing nextcloud CSRF token error
init.headers = Object.assign({}, init.headers, {
Authorization: `Basic ${btoa(`${this.username || ''}:${this.password || ''}`)}`,
});
return fetch(url, init);
}
function schedule(delay = SYNC_DELAY) {
chrome.alarms.create('syncNow', {
delayInMinutes: delay, // fractional values are supported
periodInMinutes: SYNC_INTERVAL,
});
}
function translateErrorMessage(err) {
if (err.name === 'LockError') {
return browser.i18n.getMessage('syncErrorLock', new Date(err.expire).toLocaleString([], {timeStyle: 'short'}));
}
return err.message || String(err);
}
//#endregion
})();

62
background/tab-manager.js Normal file
View File

@ -0,0 +1,62 @@
/* global bgReady */// common.js
/* global navMan */
'use strict';
const tabMan = (() => {
const listeners = new Set();
const cache = new Map();
chrome.tabs.onRemoved.addListener(tabId => cache.delete(tabId));
chrome.tabs.onReplaced.addListener((added, removed) => cache.delete(removed));
bgReady.all.then(() => {
navMan.onUrlChange(({tabId, frameId, url}) => {
const oldUrl = !frameId && tabMan.get(tabId, 'url', frameId);
tabMan.set(tabId, 'url', frameId, url);
if (frameId) return;
for (const fn of listeners) {
try {
fn({tabId, url, oldUrl});
} catch (err) {
console.error(err);
}
}
});
});
return {
onUpdate(fn) {
listeners.add(fn);
},
get(tabId, ...keys) {
return keys.reduce((meta, key) => meta && meta[key], cache.get(tabId));
},
/**
* number of keys is arbitrary, last arg is value, `undefined` will delete the last key from meta
* (tabId, 'foo', 123) will set tabId's meta to {foo: 123},
* (tabId, 'foo', 'bar', 'etc', 123) will set tabId's meta to {foo: {bar: {etc: 123}}}
*/
set(tabId, ...args) {
let meta = cache.get(tabId);
if (!meta) {
meta = {};
cache.set(tabId, meta);
}
const value = args.pop();
const lastKey = args.pop();
for (const key of args) meta = meta[key] || (meta[key] = {});
if (value === undefined) {
delete meta[lastKey];
} else {
meta[lastKey] = value;
}
},
/** @returns {IterableIterator<number>} */
list() {
return cache.keys();
},
};
})();

270
background/token-manager.js Normal file
View File

@ -0,0 +1,270 @@
/* global FIREFOX getActiveTab waitForTabUrl URLS */// toolbox.js
/* global chromeLocal */// storage-util.js
'use strict';
/* exported tokenMan */
const tokenMan = (() => {
const AUTH = {
dropbox: {
flow: 'token',
clientId: 'zg52vphuapvpng9',
authURL: 'https://www.dropbox.com/oauth2/authorize',
tokenURL: 'https://api.dropboxapi.com/oauth2/token',
revoke: token =>
fetch('https://api.dropboxapi.com/2/auth/token/revoke', {
method: 'POST',
headers: {
'Authorization': `Bearer ${token}`,
},
}),
},
google: {
flow: 'code',
clientId: '283762574871-d4u58s4arra5jdan2gr00heasjlttt1e.apps.googleusercontent.com',
clientSecret: 'J0nc5TlR_0V_ex9-sZk-5faf',
authURL: 'https://accounts.google.com/o/oauth2/v2/auth',
authQuery: {
// NOTE: Google needs 'prompt' parameter to deliver multiple refresh
// tokens for multiple machines.
// https://stackoverflow.com/q/18519185
access_type: 'offline',
prompt: 'consent',
},
tokenURL: 'https://oauth2.googleapis.com/token',
scopes: ['https://www.googleapis.com/auth/drive.appdata'],
// FIXME: https://github.com/openstyles/stylus/issues/1248
// revoke: token => {
// const params = {token};
// return postQuery(`https://accounts.google.com/o/oauth2/revoke?${new URLSearchParams(params)}`);
// },
},
onedrive: {
flow: 'code',
clientId: '3864ce03-867c-4ad8-9856-371a097d47b1',
clientSecret: '9Pj=TpsrStq8K@1BiwB9PIWLppM:@s=w',
authURL: 'https://login.microsoftonline.com/common/oauth2/v2.0/authorize',
tokenURL: 'https://login.microsoftonline.com/common/oauth2/v2.0/token',
scopes: ['Files.ReadWrite.AppFolder', 'offline_access'],
},
userstylesworld: {
flow: 'code',
clientId: 'zeDmKhJIfJqULtcrGMsWaxRtWHEimKgS',
clientSecret: 'wqHsvTuThQmXmDiVvOpZxPwSIbyycNFImpAOTxjaIRqDbsXcTOqrymMJKsOMuibFaij' +
'ZZAkVYTDbLkQuYFKqgpMsMlFlgwQOYHvHFbgxQHDTwwdOroYhOwFuekCwXUlk',
authURL: URLS.usw + 'api/oauth/style/link',
tokenURL: URLS.usw + 'api/oauth/token',
redirect_uri: 'https://gusted.xyz/callback_helper/',
},
};
const NETWORK_LATENCY = 30; // seconds
const DEFAULT_REDIRECT_URI = 'https://clngdbkpkpeebahjckkjfobafhncgmne.chromiumapp.org/';
let alwaysUseTab = !chrome.windows || (FIREFOX ? false : null);
class TokenError extends Error {
constructor(provider, message) {
super(`[${provider}] ${message}`);
this.name = 'TokenError';
this.provider = provider;
if (Error.captureStackTrace) {
Error.captureStackTrace(this, TokenError);
}
}
}
return {
buildKeys(name, hooks) {
const prefix = `secure/token/${hooks ? hooks.keyName(name) : name}/`;
const k = {
TOKEN: `${prefix}token`,
EXPIRE: `${prefix}expire`,
REFRESH: `${prefix}refresh`,
};
k.LIST = Object.values(k);
return k;
},
getClientId(name) {
return AUTH[name].clientId;
},
async getToken(name, interactive, hooks) {
const k = tokenMan.buildKeys(name, hooks);
const obj = await chromeLocal.get(k.LIST);
if (obj[k.TOKEN]) {
if (!obj[k.EXPIRE] || Date.now() < obj[k.EXPIRE]) {
return obj[k.TOKEN];
}
if (obj[k.REFRESH]) {
return refreshToken(name, k, obj);
}
}
if (!interactive) {
throw new TokenError(name, 'Token is missing');
}
return authUser(k, name, interactive, hooks);
},
async revokeToken(name, hooks) {
const provider = AUTH[name];
const k = tokenMan.buildKeys(name, hooks);
if (provider.revoke) {
try {
const token = await chromeLocal.getValue(k.TOKEN);
if (token) await provider.revoke(token);
} catch (e) {
console.error(e);
}
}
await chromeLocal.remove(k.LIST);
},
};
async function refreshToken(name, k, obj) {
if (!obj[k.REFRESH]) {
throw new TokenError(name, 'No refresh token');
}
const provider = AUTH[name];
const body = {
client_id: provider.clientId,
refresh_token: obj[k.REFRESH],
grant_type: 'refresh_token',
scope: provider.scopes.join(' '),
};
if (provider.clientSecret) {
body.client_secret = provider.clientSecret;
}
const result = await postQuery(provider.tokenURL, body);
if (!result.refresh_token) {
// reuse old refresh token
result.refresh_token = obj[k.REFRESH];
}
return handleTokenResult(result, k);
}
async function authUser(keys, name, interactive = false, hooks = null) {
await require(['/vendor/webext-launch-web-auth-flow/webext-launch-web-auth-flow']);
/* global webextLaunchWebAuthFlow */
const provider = AUTH[name];
const state = Math.random().toFixed(8).slice(2);
const query = {
response_type: provider.flow,
client_id: provider.clientId,
redirect_uri: provider.redirect_uri || DEFAULT_REDIRECT_URI,
state,
};
if (provider.scopes) {
query.scope = provider.scopes.join(' ');
}
if (provider.authQuery) {
Object.assign(query, provider.authQuery);
}
if (alwaysUseTab == null) {
alwaysUseTab = await detectVivaldiWebRequestBug();
}
if (hooks) hooks.query(query);
const url = `${provider.authURL}?${new URLSearchParams(query)}`;
const width = Math.min(screen.availWidth - 100, 800);
const height = Math.min(screen.availHeight - 100, 800);
const wnd = !alwaysUseTab && await browser.windows.getLastFocused();
const finalUrl = await webextLaunchWebAuthFlow({
url,
alwaysUseTab,
interactive,
redirect_uri: query.redirect_uri,
windowOptions: wnd && Object.assign({
state: 'normal',
width,
height,
}, wnd.state !== 'minimized' && {
// Center the popup to the current window
top: Math.ceil(wnd.top + (wnd.height - width) / 2),
left: Math.ceil(wnd.left + (wnd.width - width) / 2),
}),
});
const params = new URLSearchParams(
provider.flow === 'token' ?
new URL(finalUrl).hash.slice(1) :
new URL(finalUrl).search.slice(1)
);
if (params.get('state') !== state) {
throw new TokenError(name, `Unexpected state: ${params.get('state')}, expected: ${state}`);
}
let result;
if (provider.flow === 'token') {
const obj = {};
for (const [key, value] of params) {
obj[key] = value;
}
result = obj;
} else {
const code = params.get('code');
const body = {
code,
grant_type: 'authorization_code',
client_id: provider.clientId,
redirect_uri: query.redirect_uri,
state,
};
if (provider.clientSecret) {
body.client_secret = provider.clientSecret;
}
result = await postQuery(provider.tokenURL, body);
}
return handleTokenResult(result, keys);
}
async function handleTokenResult(result, k) {
await chromeLocal.set({
[k.TOKEN]: result.access_token,
[k.EXPIRE]: result.expires_in
? Date.now() + (result.expires_in - NETWORK_LATENCY) * 1000
: undefined,
[k.REFRESH]: result.refresh_token,
});
return result.access_token;
}
async function postQuery(url, body) {
const options = {
method: 'POST',
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
},
body: body ? new URLSearchParams(body) : null,
};
const r = await fetch(url, options);
if (r.ok) {
return r.json();
}
const text = await r.text();
const err = new Error(`Failed to fetch (${r.status}): ${text}`);
err.code = r.status;
throw err;
}
async function detectVivaldiWebRequestBug() {
// Workaround for https://github.com/openstyles/stylus/issues/1182
// Note that modern Vivaldi isn't exposed in `navigator.userAgent` but it adds `extData` to tabs
const anyTab = await getActiveTab() || (await browser.tabs.query({}))[0];
if (anyTab && !(anyTab.extData || anyTab.vivExtData)) {
return false;
}
let bugged = true;
const TEST_URL = chrome.runtime.getURL('manifest.json');
const check = ({url}) => {
bugged = url !== TEST_URL;
};
chrome.webRequest.onBeforeRequest.addListener(check, {urls: [TEST_URL], types: ['main_frame']});
const {tabs: [tab]} = await browser.windows.create({
type: 'popup',
state: 'minimized',
url: TEST_URL,
});
await waitForTabUrl(tab);
chrome.windows.remove(tab.windowId);
chrome.webRequest.onBeforeRequest.removeListener(check);
return bugged;
}
})();

View File

@ -0,0 +1,348 @@
/* global API */// msg.js
/* global RX_META URLS debounce deepMerge download ignoreChromeError */// toolbox.js
/* global calcStyleDigest styleSectionsEqual */ // sections-util.js
/* global chromeLocal */// storage-util.js
/* global compareVersion */// cmpver.js
/* global db */
/* global prefs */
'use strict';
/* exported updateMan */
const updateMan = (() => {
const STATES = /** @namespace UpdaterStates */ {
UPDATED: 'updated',
SKIPPED: 'skipped',
UNREACHABLE: 'server unreachable',
// details for SKIPPED status
EDITED: 'locally edited',
MAYBE_EDITED: 'may be locally edited',
SAME_MD5: 'up-to-date: MD5 is unchanged',
SAME_CODE: 'up-to-date: code sections are unchanged',
SAME_VERSION: 'up-to-date: version is unchanged',
ERROR_MD5: 'error: MD5 is invalid',
ERROR_JSON: 'error: JSON is invalid',
ERROR_VERSION: 'error: version is older than installed style',
};
const USO_STYLES_API = `${URLS.uso}api/v1/styles/`;
const RH_ETAG = {responseHeaders: ['etag']}; // a hashsum of file contents
const RX_DATE2VER = new RegExp([
/^(\d{4})/,
/(0[1-9]|1(?:0|[12](?=\d\d))?|[2-9])/, // in ambiguous cases like yyyy123 the month will be 1
/(0[1-9]|[1-2][0-9]?|3[0-1]?|[4-9])/,
/\.([01][0-9]?|2[0-3]?|[3-9])/,
/\.([0-5][0-9]?|[6-9])$/,
].map(rx => rx.source).join(''));
const ALARM_NAME = 'scheduledUpdate';
const MIN_INTERVAL_MS = 60e3;
const RETRY_ERRORS = [
503, // service unavailable
429, // too many requests
];
let usoReferers = 0;
let lastUpdateTime;
let checkingAll = false;
let logQueue = [];
let logLastWriteTime = 0;
chromeLocal.getValue('lastUpdateTime').then(val => {
lastUpdateTime = val || Date.now();
prefs.subscribe('updateInterval', schedule, {runNow: true});
chrome.alarms.onAlarm.addListener(onAlarm);
});
return {
checkAllStyles,
checkStyle,
getStates: () => STATES,
};
async function checkAllStyles({
save = true,
ignoreDigest,
observe,
} = {}) {
resetInterval();
checkingAll = true;
const port = observe && chrome.runtime.connect({name: 'updater'});
const styles = (await API.styles.getAll())
.filter(style => style.updateUrl && style.updatable !== false);
if (port) port.postMessage({count: styles.length});
log('');
log(`${save ? 'Scheduled' : 'Manual'} update check for ${styles.length} styles`);
await Promise.all(
styles.map(style =>
checkStyle({style, port, save, ignoreDigest})));
if (port) port.postMessage({done: true});
if (port) port.disconnect();
log('');
checkingAll = false;
}
/**
* @param {{
id?: number,
style?: StyleObj,
port?: chrome.runtime.Port,
save?: boolean,
ignoreDigest?: boolean,
}} opts
* @returns {{
style: StyleObj,
updated?: boolean,
error?: any,
STATES: UpdaterStates,
}}
Original style digests are calculated in these cases:
* style is installed or updated from server
* non-usercss style is checked for an update and styleSectionsEqual considers it unchanged
Update check proceeds in these cases:
* style has the original digest and it's equal to the current digest
* [ignoreDigest: true] style doesn't yet have the original digest but we ignore it
* [ignoreDigest: none/false] style doesn't yet have the original digest
so we compare the code to the server code and if it's the same we save the digest,
otherwise we skip the style and report MAYBE_EDITED status
'ignoreDigest' option is set on the second manual individual update check on the manage page.
*/
async function checkStyle(opts) {
let {id} = opts;
const {
style = await API.styles.get(id),
ignoreDigest,
port,
save,
} = opts;
if (!id) id = style.id;
const {md5Url} = style;
let {usercssData: ucd, updateUrl} = style;
let res, state;
try {
await checkIfEdited();
res = {
style: await (ucd && !md5Url ? updateUsercss : updateUSO)().then(maybeSave),
updated: true,
};
state = STATES.UPDATED;
} catch (err) {
const error = err === 0 && STATES.UNREACHABLE ||
err && err.message ||
err;
res = {error, style, STATES};
state = `${STATES.SKIPPED} (${Array.isArray(err) ? err[0].message : error})`;
}
log(`${state} #${id} ${style.customName || style.name}`);
if (port) port.postMessage(res);
return res;
async function checkIfEdited() {
if (!ignoreDigest &&
style.originalDigest &&
style.originalDigest !== await calcStyleDigest(style)) {
return Promise.reject(STATES.EDITED);
}
}
async function updateUSO() {
const md5 = await tryDownload(md5Url);
if (!md5 || md5.length !== 32) {
return Promise.reject(STATES.ERROR_MD5);
}
if (md5 === style.originalMd5 && style.originalDigest && !ignoreDigest) {
return Promise.reject(STATES.SAME_MD5);
}
let varsUrl = '';
if (!ucd) {
ucd = {};
varsUrl = updateUrl;
updateUrl = style.updateUrl = `${USO_STYLES_API}${md5Url.match(/\/(\d+)/)[1]}`;
}
usoSpooferStart();
let json;
try {
json = await tryDownload(style.updateUrl, {responseType: 'json'});
json = await updateUsercss(json.css) ||
(await API.uso.toUsercss(json)).style;
if (varsUrl) await API.uso.useVarsUrl(json, varsUrl);
} finally {
usoSpooferStop();
}
// USO may not provide a correctly updated originalMd5 (#555)
json.originalMd5 = md5;
return json;
}
async function updateUsercss(css) {
let oldVer = ucd.version;
let {etag: oldEtag, updateUrl} = style;
const m2 = (css || URLS.extractUsoArchiveId(updateUrl)) &&
await getUsoEmbeddedMeta(css);
if (m2 && m2.updateUrl) {
updateUrl = m2.updateUrl;
oldVer = m2.usercssData.version || '0';
oldEtag = '';
} else if (css) {
return;
}
if (oldEtag && oldEtag === await downloadEtag()) {
return Promise.reject(STATES.SAME_CODE);
}
// TODO: when sourceCode is > 100kB use http range request(s) for version check
const {headers: {etag}, response} = await tryDownload(updateUrl, RH_ETAG);
const json = await API.usercss.buildMeta({sourceCode: response, etag, updateUrl});
const delta = compareVersion(json.usercssData.version, oldVer);
let err;
if (!delta && !ignoreDigest) {
// re-install is invalid in a soft upgrade
err = response === style.sourceCode
? STATES.SAME_CODE
: !URLS.isLocalhost(updateUrl) && STATES.SAME_VERSION;
}
if (delta < 0) {
// downgrade is always invalid
err = STATES.ERROR_VERSION;
}
if (err && etag && !style.etag) {
// first check of ETAG, gonna write it directly to DB as it's too trivial to sync or announce
style.etag = etag;
await db.styles.put(style);
}
return err
? Promise.reject(err)
: API.usercss.buildCode(json);
}
async function maybeSave(json) {
json.id = id;
// keep current state
delete json.customName;
delete json.enabled;
const newStyle = Object.assign({}, style, json);
newStyle.updateDate = getDateFromVer(newStyle) || Date.now();
// update digest even if save === false as there might be just a space added etc.
if (!ucd && styleSectionsEqual(json, style)) {
style.originalDigest = (await API.styles.install(newStyle)).originalDigest;
return Promise.reject(STATES.SAME_CODE);
}
if (!style.originalDigest && !ignoreDigest) {
return Promise.reject(STATES.MAYBE_EDITED);
}
return !save ? newStyle :
(ucd ? API.usercss.install : API.styles.install)(newStyle);
}
async function tryDownload(url, params) {
let {retryDelay = 1000} = opts;
while (true) {
try {
params = deepMerge(params || {}, {headers: {'Cache-Control': 'no-cache'}});
return await download(url, params);
} catch (code) {
if (!RETRY_ERRORS.includes(code) ||
retryDelay > MIN_INTERVAL_MS) {
return Promise.reject(code);
}
}
retryDelay *= 1.25;
await new Promise(resolve => setTimeout(resolve, retryDelay));
}
}
async function downloadEtag() {
const opts = Object.assign({method: 'head'}, RH_ETAG);
const req = await tryDownload(style.updateUrl, opts);
return req.headers.etag;
}
function getDateFromVer(style) {
const m = RX_DATE2VER.exec((style.usercssData || {}).version);
if (m) {
m[2]--; // month is 0-based in `Date` constructor
return new Date(...m.slice(1)).getTime();
}
}
/** UserCSS metadata may be embedded in the original USO style so let's use its updateURL */
function getUsoEmbeddedMeta(code = style.sourceCode) {
const isRaw = arguments[0];
const m = code.includes('@updateURL') && (isRaw ? code : code.replace(RX_META, '')).match(RX_META);
return m && API.usercss.buildMeta({sourceCode: m[0]}).catch(() => null);
}
}
function schedule() {
const interval = prefs.get('updateInterval') * 60 * 60 * 1000;
if (interval > 0) {
const elapsed = Math.max(0, Date.now() - lastUpdateTime);
chrome.alarms.create(ALARM_NAME, {
when: Date.now() + Math.max(MIN_INTERVAL_MS, interval - elapsed),
});
} else {
chrome.alarms.clear(ALARM_NAME, ignoreChromeError);
}
}
function onAlarm({name}) {
if (name === ALARM_NAME) checkAllStyles();
}
function resetInterval() {
chromeLocal.setValue('lastUpdateTime', lastUpdateTime = Date.now());
schedule();
}
function log(text) {
logQueue.push({text, time: new Date().toLocaleString()});
debounce(flushQueue, text && checkingAll ? 1000 : 0);
}
async function flushQueue(lines) {
if (!lines) {
flushQueue(await chromeLocal.getValue('updateLog') || []);
return;
}
const time = Date.now() - logLastWriteTime > 11e3 ?
logQueue[0].time + ' ' :
'';
if (logQueue[0] && !logQueue[0].text) {
logQueue.shift();
if (lines[lines.length - 1]) lines.push('');
}
lines.splice(0, lines.length - 1000);
lines.push(time + (logQueue[0] && logQueue[0].text || ''));
lines.push(...logQueue.slice(1).map(item => item.text));
chromeLocal.setValue('updateLog', lines);
logLastWriteTime = Date.now();
logQueue = [];
}
function usoSpooferStart() {
if (++usoReferers === 1) {
chrome.webRequest.onBeforeSendHeaders.addListener(
usoSpoofer,
{types: ['xmlhttprequest'], urls: [USO_STYLES_API + '*']},
['blocking', 'requestHeaders', chrome.webRequest.OnBeforeSendHeadersOptions.EXTRA_HEADERS]
.filter(Boolean));
}
}
function usoSpooferStop() {
if (--usoReferers <= 0) {
usoReferers = 0;
chrome.webRequest.onBeforeSendHeaders.removeListener(usoSpoofer);
}
}
/** @param {chrome.webRequest.WebResponseHeadersDetails | browser.webRequest._OnBeforeSendHeadersDetails} info */
function usoSpoofer(info) {
if (info.tabId < 0 && URLS.ownOrigin.startsWith(info.initiator || info.originUrl || '')) {
const {requestHeaders: hh} = info;
const i = (hh.findIndex(h => /^referer$/i.test(h.name)) + 1 || hh.push({})) - 1;
hh[i].name = 'referer';
hh[i].value = URLS.uso;
return {requestHeaders: hh};
}
}
})();

View File

@ -1,264 +0,0 @@
/*
global getStyles saveStyle styleSectionsEqual
global calcStyleDigest cachedStyles getStyleWithNoCode
global usercss semverCompare
global API_METHODS
*/
'use strict';
(() => {
const STATES = {
UPDATED: 'updated',
SKIPPED: 'skipped',
// details for SKIPPED status
EDITED: 'locally edited',
MAYBE_EDITED: 'may be locally edited',
SAME_MD5: 'up-to-date: MD5 is unchanged',
SAME_CODE: 'up-to-date: code sections are unchanged',
SAME_VERSION: 'up-to-date: version is unchanged',
ERROR_MD5: 'error: MD5 is invalid',
ERROR_JSON: 'error: JSON is invalid',
ERROR_VERSION: 'error: version is older than installed style',
};
const ALARM_NAME = 'scheduledUpdate';
const MIN_INTERVAL_MS = 60e3;
let lastUpdateTime = parseInt(localStorage.lastUpdateTime) || Date.now();
let checkingAll = false;
let logQueue = [];
let logLastWriteTime = 0;
const retrying = new Set();
API_METHODS.updateCheckAll = checkAllStyles;
API_METHODS.updateCheck = checkStyle;
API_METHODS.getUpdaterStates = () => STATES;
prefs.subscribe(['updateInterval'], schedule);
schedule();
return {checkAllStyles, checkStyle, STATES};
function checkAllStyles({
save = true,
ignoreDigest,
observe,
} = {}) {
resetInterval();
checkingAll = true;
retrying.clear();
const port = observe && chrome.runtime.connect({name: 'updater'});
return getStyles({}).then(styles => {
styles = styles.filter(style => style.updateUrl);
if (port) port.postMessage({count: styles.length});
log('');
log(`${save ? 'Scheduled' : 'Manual'} update check for ${styles.length} styles`);
return Promise.all(
styles.map(style =>
checkStyle({style, port, save, ignoreDigest})));
}).then(() => {
if (port) port.postMessage({done: true});
if (port) port.disconnect();
log('');
checkingAll = false;
retrying.clear();
});
}
function checkStyle({
id,
style = cachedStyles.byId.get(id),
port,
save = true,
ignoreDigest,
}) {
/*
Original style digests are calculated in these cases:
* style is installed or updated from server
* style is checked for an update and its code is equal to the server code
Update check proceeds in these cases:
* style has the original digest and it's equal to the current digest
* [ignoreDigest: true] style doesn't yet have the original digest but we ignore it
* [ignoreDigest: none/false] style doesn't yet have the original digest
so we compare the code to the server code and if it's the same we save the digest,
otherwise we skip the style and report MAYBE_EDITED status
'ignoreDigest' option is set on the second manual individual update check on the manage page.
*/
return Promise.resolve(style)
.then([calcStyleDigest][!ignoreDigest ? 0 : 'skip'])
.then([checkIfEdited][!ignoreDigest ? 0 : 'skip'])
.then([maybeUpdateUSO, maybeUpdateUsercss][style.usercssData ? 1 : 0])
.then(maybeSave)
.then(reportSuccess)
.catch(reportFailure);
function reportSuccess(saved) {
log(STATES.UPDATED + ` #${style.id} ${style.name}`);
const info = {updated: true, style: saved};
if (port) port.postMessage(info);
return info;
}
function reportFailure(error) {
if ((
error === 503 || // Service Unavailable
error === 429 // Too Many Requests
) && !retrying.has(id)) {
retrying.add(id);
return new Promise(resolve => {
setTimeout(() => {
resolve(checkStyle({id, style, port, save, ignoreDigest}));
}, 1000);
});
}
error = error === 0 ? 'server unreachable' : error;
log(STATES.SKIPPED + ` (${error}) #${style.id} ${style.name}`);
const info = {error, STATES, style: getStyleWithNoCode(style)};
if (port) port.postMessage(info);
return info;
}
function checkIfEdited(digest) {
if (style.originalDigest && style.originalDigest !== digest) {
return Promise.reject(STATES.EDITED);
}
}
function maybeUpdateUSO() {
return download(style.md5Url).then(md5 => {
if (!md5 || md5.length !== 32) {
return Promise.reject(STATES.ERROR_MD5);
}
if (md5 === style.originalMd5 && style.originalDigest && !ignoreDigest) {
return Promise.reject(STATES.SAME_MD5);
}
// USO can't handle POST requests for style json
return download(style.updateUrl, {body: null})
.then(text => tryJSONparse(text));
});
}
function maybeUpdateUsercss() {
// TODO: when sourceCode is > 100kB use http range request(s) for version check
return download(style.updateUrl).then(text => {
const json = usercss.buildMeta(text);
const {usercssData: {version}} = style;
const {usercssData: {version: newVersion}} = json;
switch (Math.sign(semverCompare(version, newVersion))) {
case 0:
// re-install is invalid in a soft upgrade
if (!ignoreDigest) {
const sameCode = text === style.sourceCode;
return Promise.reject(sameCode ? STATES.SAME_CODE : STATES.SAME_VERSION);
}
break;
case 1:
// downgrade is always invalid
return Promise.reject(STATES.ERROR_VERSION);
}
return usercss.buildCode(json);
});
}
function maybeSave(json = {}) {
// usercss is already validated while building
if (!json.usercssData && !styleJSONseemsValid(json)) {
return Promise.reject(STATES.ERROR_JSON);
}
json.id = style.id;
json.updateDate = Date.now();
json.reason = 'update';
// keep current state
delete json.enabled;
// keep local name customizations
if (style.originalName !== style.name && style.name !== json.name) {
delete json.name;
} else {
json.originalName = json.name;
}
if (styleSectionsEqual(json, style, {checkSource: true})) {
// update digest even if save === false as there might be just a space added etc.
json.reason = 'update-digest';
return saveStyle(json)
.then(saved => {
style.originalDigest = saved.originalDigest;
return Promise.reject(STATES.SAME_CODE);
});
}
if (!style.originalDigest && !ignoreDigest) {
return Promise.reject(STATES.MAYBE_EDITED);
}
return save ?
API_METHODS[json.usercssData ? 'saveUsercss' : 'saveStyle'](json) :
json;
}
function styleJSONseemsValid(json) {
return json
&& json.sections
&& json.sections.length
&& typeof json.sections.every === 'function'
&& typeof json.sections[0].code === 'string';
}
}
function schedule() {
const interval = prefs.get('updateInterval') * 60 * 60 * 1000;
if (interval > 0) {
const elapsed = Math.max(0, Date.now() - lastUpdateTime);
chrome.alarms.create(ALARM_NAME, {
when: Date.now() + Math.max(MIN_INTERVAL_MS, interval - elapsed),
});
chrome.alarms.onAlarm.addListener(onAlarm);
} else {
chrome.alarms.clear(ALARM_NAME, ignoreChromeError);
chrome.alarms.onAlarm.removeListener(onAlarm);
}
}
function onAlarm({name}) {
if (name === ALARM_NAME) checkAllStyles();
}
function resetInterval() {
localStorage.lastUpdateTime = lastUpdateTime = Date.now();
schedule();
}
function log(text) {
logQueue.push({text, time: new Date().toLocaleString()});
debounce(flushQueue, text && checkingAll ? 1000 : 0);
}
function flushQueue(lines) {
if (!lines) {
chromeLocal.getValue('updateLog', []).then(flushQueue);
return;
}
const time = Date.now() - logLastWriteTime > 11e3 ?
logQueue[0].time + ' ' :
'';
if (logQueue[0] && !logQueue[0].text) {
logQueue.shift();
if (lines[lines.length - 1]) lines.push('');
}
lines.splice(0, lines.length - 1000);
lines.push(time + (logQueue[0] && logQueue[0].text || ''));
lines.push(...logQueue.slice(1).map(item => item.text));
chromeLocal.setValue('updateLog', lines);
logLastWriteTime = Date.now();
logQueue = [];
}
})();

View File

@ -1,153 +0,0 @@
/* global API_METHODS usercss saveStyle getStyles chromeLocal cachedStyles */
'use strict';
(() => {
API_METHODS.saveUsercss = style => save(style, false);
API_METHODS.saveUsercssUnsafe = style => save(style, true);
API_METHODS.buildUsercss = build;
API_METHODS.installUsercss = install;
API_METHODS.parseUsercss = parse;
API_METHODS.findUsercss = find;
const TEMP_CODE_PREFIX = 'tempUsercssCode';
const TEMP_CODE_CLEANUP_DELAY = 60e3;
let tempCodeLastWriteDate = 0;
if (FIREFOX) {
// the temp code is created on direct installation of usercss URLs in FF
// and can be left behind in case the install page didn't open in time before
// the extension was updated/reloaded/disabled or the browser was closed
setTimeout(function poll() {
if (Date.now() - tempCodeLastWriteDate < TEMP_CODE_CLEANUP_DELAY) {
setTimeout(poll, TEMP_CODE_CLEANUP_DELAY);
return;
}
chrome.storage.local.get(null, storage => {
const leftovers = [];
for (const key in storage) {
if (key.startsWith(TEMP_CODE_PREFIX)) {
leftovers.push(key);
}
}
if (leftovers.length) {
chrome.storage.local.remove(leftovers);
}
});
}, TEMP_CODE_CLEANUP_DELAY);
}
function buildMeta(style) {
if (style.usercssData) {
return Promise.resolve(style);
}
try {
const {sourceCode} = style;
// allow sourceCode to be normalized
delete style.sourceCode;
return Promise.resolve(Object.assign(usercss.buildMeta(sourceCode), style));
} catch (e) {
return Promise.reject(e);
}
}
function assignVars(style) {
if (style.reason === 'config' && style.id) {
return style;
}
const dup = find(style);
if (dup) {
style.id = dup.id;
if (style.reason !== 'config') {
// preserve style.vars during update
usercss.assignVars(style, dup);
}
}
return style;
}
/**
* Parse the source and find the duplication
* @param _
* @param {String} _.sourceCode
* @param {Boolean=} _.checkDup
* @param {Boolean=} _.metaOnly
* @returns {Promise<{style, dup:Boolean?}>}
*/
function build({
sourceCode,
checkDup,
metaOnly,
}) {
const task = buildMeta({sourceCode});
return (metaOnly ? task : task.then(usercss.buildCode))
.then(style => ({
style,
dup: checkDup && find(style),
}));
}
// Parse the source, apply customizations, report fatal/syntax errors
function parse(style, allowErrors = false) {
// restore if stripped by getStyleWithNoCode
if (typeof style.sourceCode !== 'string') {
style.sourceCode = cachedStyles.byId.get(style.id).sourceCode;
}
return buildMeta(style)
.then(assignVars)
.then(style => usercss.buildCode(style, allowErrors));
}
function save(style, allowErrors = false) {
return parse(style, allowErrors)
.then(result =>
allowErrors ?
saveStyle(result.style).then(style => ({style, errors: result.errors})) :
saveStyle(result));
}
/**
* @param {Style|{name:string, namespace:string}} styleOrData
* @returns {Style}
*/
function find(styleOrData) {
if (styleOrData.id) return cachedStyles.byId.get(styleOrData.id);
const {name, namespace} = styleOrData.usercssData || styleOrData;
for (const dup of cachedStyles.list) {
const data = dup.usercssData;
if (!data) continue;
if (data.name === name &&
data.namespace === namespace) {
return dup;
}
}
}
function install({url, direct, downloaded, tab}, sender) {
tab = tab !== undefined ? tab : sender.tab;
url = url || tab.url;
if (direct && !downloaded) {
prefetchCodeForInstallation(tab.id, url);
}
return openURL({
url: '/install-usercss.html' +
'?updateUrl=' + encodeURIComponent(url) +
'&tabId=' + tab.id +
(direct ? '&direct=yes' : ''),
index: tab.index + 1,
openerTabId: tab.id,
currentWindow: null,
});
}
function prefetchCodeForInstallation(tabId, url) {
const key = TEMP_CODE_PREFIX + tabId;
tempCodeLastWriteDate = Date.now();
Promise.all([
download(url),
chromeLocal.setValue(key, {loading: true}),
]).then(([code]) => {
chromeLocal.setValue(key, code);
setTimeout(() => chromeLocal.remove(key), TEMP_CODE_CLEANUP_DELAY);
});
}
})();

View File

@ -0,0 +1,141 @@
/* global RX_META URLS download openURL */// toolbox.js
/* global addAPI bgReady */// common.js
/* global tabMan */// msg.js
'use strict';
bgReady.all.then(() => {
const installCodeCache = {};
addAPI(/** @namespace API */ {
usercss: {
getInstallCode(url) {
// when the installer tab is reloaded after the cache is expired, this will throw intentionally
const {code, timer} = installCodeCache[url];
clearInstallCode(url);
clearTimeout(timer);
return code;
},
},
});
// `glob`: pathname match pattern for webRequest
// `rx`: pathname regex to verify the URL really looks like a raw usercss
const maybeDistro = {
// https://github.com/StylishThemes/GitHub-Dark/raw/master/github-dark.user.css
'github.com': {
glob: '/*/raw/*',
rx: /^\/[^/]+\/[^/]+\/raw\/[^/]+\/[^/]+?\.user\.(css|styl)$/,
},
// https://raw.githubusercontent.com/StylishThemes/GitHub-Dark/master/github-dark.user.css
'raw.githubusercontent.com': {
glob: '/*',
rx: /^(\/[^/]+?){4}\.user\.(css|styl)$/,
},
};
chrome.webRequest.onBeforeSendHeaders.addListener(maybeInstallFromDistro, {
urls: [
URLS.usw + 'api/style/*.user.css',
...URLS.usoArchiveRaw.map(s => s + 'usercss/*.user.css'),
...['greasy', 'sleazy'].map(s => `*://${s}fork.org/scripts/*/code/*.user.css`),
...[].concat(
...Object.entries(maybeDistro)
.map(([host, {glob}]) => makeUsercssGlobs(host, glob))),
],
types: ['main_frame'],
}, ['blocking']);
chrome.webRequest.onHeadersReceived.addListener(rememberContentType, {
urls: makeUsercssGlobs('*', '/*'),
types: ['main_frame'],
}, ['responseHeaders']);
tabMan.onUpdate(maybeInstall);
function clearInstallCode(url) {
return delete installCodeCache[url];
}
/** Sites may be using custom types like text/stylus so this coarse filter only excludes html */
function isContentTypeText(type) {
return /^text\/(?!html)/i.test(type);
}
// in Firefox we have to use a content script to read file://
async function loadFromFile(tabId) {
return (await browser.tabs.executeScript(tabId, {file: '/content/install-hook-usercss.js'}))[0];
}
async function loadFromUrl(tabId, url) {
return (
url.startsWith('file:') ||
tabMan.get(tabId, isContentTypeText.name) ||
isContentTypeText((await fetch(url, {method: 'HEAD'})).headers.get('content-type'))
) && download(url);
}
function makeInstallerUrl(url) {
return `${URLS.installUsercss}?updateUrl=${encodeURIComponent(url)}`;
}
function makeUsercssGlobs(host, path) {
return '%css,%css?*,%styl,%styl?*'.replace(/%/g, `*://${host}${path}.user.`).split(',');
}
async function maybeInstall({tabId, url, oldUrl = ''}) {
if (url.includes('.user.') &&
/^(https?|file|ftps?):/.test(url) &&
/\.user\.(css|styl)$/.test(url.split(/[#?]/, 1)[0]) &&
!oldUrl.startsWith(makeInstallerUrl(url))) {
const inTab = url.startsWith('file:') && !chrome.app;
const code = await (inTab ? loadFromFile : loadFromUrl)(tabId, url);
if (!/^\s*</.test(code) && RX_META.test(code)) {
await openInstallerPage(tabId, url, {code, inTab});
}
}
}
/** Faster installation on known distribution sites to avoid flicker of css text */
function maybeInstallFromDistro({tabId, url}) {
const u = new URL(url);
const m = maybeDistro[u.hostname];
if (!m || m.rx.test(u.pathname)) {
openInstallerPage(tabId, url, {});
// Silently suppress navigation.
// Don't redirect to the install URL as it'll flash the text!
return {cancel: true};
}
}
async function openInstallerPage(tabId, url, {code, inTab} = {}) {
const newUrl = makeInstallerUrl(url);
if (inTab) {
const tab = await browser.tabs.get(tabId);
return openURL({
url: `${newUrl}&tabId=${tabId}`,
active: tab.active,
index: tab.index + 1,
openerTabId: tabId,
currentWindow: null,
});
}
const timer = setTimeout(clearInstallCode, 10e3, url);
installCodeCache[url] = {code, timer};
try {
await browser.tabs.update(tabId, {url: newUrl});
} catch (err) {
// FIXME: remove this when kiwi supports tabs.update
// https://github.com/openstyles/stylus/issues/1367
if (/Tabs cannot be edited right now/i.test(err.message)) {
return browser.tabs.create({url: newUrl});
}
throw err;
}
}
/** Remember Content-Type to avoid wasting time to re-fetch in loadFromUrl **/
function rememberContentType({tabId, responseHeaders}) {
const h = responseHeaders.find(h => h.name.toLowerCase() === 'content-type');
tabMan.set(tabId, isContentTypeText.name, h && isContentTypeText(h.value) || undefined);
}
});

View File

@ -0,0 +1,163 @@
/* global API */// msg.js
/* global RX_META deepCopy download */// toolbox.js
'use strict';
const usercssMan = {
GLOBAL_META: Object.entries({
author: null,
description: null,
homepageURL: 'url',
updateURL: 'updateUrl',
name: null,
}),
/** `src` is a style or vars */
async assignVars(style, src) {
const meta = style.usercssData;
const meta2 = src.usercssData;
const {vars} = meta;
const oldVars = meta2 ? meta2.vars : src;
if (vars && oldVars) {
// The type of var might be changed during the update. Set value to null if the value is invalid.
for (const [key, v] of Object.entries(vars)) {
const old = oldVars[key] && oldVars[key].value;
if (old) v.value = old;
}
meta.vars = await API.worker.nullifyInvalidVars(vars);
}
},
async build({
styleId,
sourceCode,
vars,
checkDup,
metaOnly,
assignVars,
initialUrl,
}) {
// downloading here while install-usercss page is loading to avoid the wait
if (initialUrl) sourceCode = await download(initialUrl);
const style = await usercssMan.buildMeta({sourceCode});
const dup = (checkDup || assignVars) &&
await usercssMan.find(styleId ? {id: styleId} : style);
let log;
if (!metaOnly) {
if (vars || assignVars) {
await usercssMan.assignVars(style, vars || dup);
}
await usercssMan.buildCode(style);
log = style.log; // extracting the non-enumerable prop, otherwise it won't survive messaging
}
return {style, dup, log};
},
async buildCode(style) {
const {sourceCode: code, usercssData: {vars, preprocessor}} = style;
const match = code.match(RX_META);
const i = match.index;
const j = i + match[0].length;
const codeNoMeta = code.slice(0, i) + blankOut(code, i, j) + code.slice(j);
const {sections, errors, log} = await API.worker.compileUsercss(preprocessor, codeNoMeta, vars);
const recoverable = errors.every(e => e.recoverable);
if (!sections.length || !recoverable) {
throw !recoverable ? errors : 'Style does not contain any actual CSS to apply.';
}
style.sections = sections;
// adding a non-enumerable prop so it won't be written to storage
if (log) Object.defineProperty(style, 'log', {value: log});
return style;
},
async buildMeta(style) {
if (style.usercssData) {
return style;
}
// remember normalized sourceCode
let code = style.sourceCode = style.sourceCode.replace(/\r\n?/g, '\n');
style = Object.assign({
enabled: true,
sections: [],
}, style);
const match = code.match(RX_META);
if (!match) {
return Promise.reject(new Error('Could not find metadata.'));
}
try {
code = blankOut(code, 0, match.index) + match[0];
const {metadata} = await API.worker.parseUsercssMeta(code);
style.usercssData = metadata;
// https://github.com/openstyles/stylus/issues/560#issuecomment-440561196
for (const [key, globalKey] of usercssMan.GLOBAL_META) {
const val = metadata[key];
if (val !== undefined) {
style[globalKey || key] = val;
}
}
return style;
} catch (err) {
if (err.code) {
const args = err.code === 'missingMandatory' || err.code === 'missingChar'
? err.args.map(e => e.length === 1 ? JSON.stringify(e) : e).join(', ')
: err.args;
const msg = chrome.i18n.getMessage(`meta_${(err.code)}`, args);
if (msg) err.message = msg;
}
return Promise.reject(err);
}
},
async configVars(id, vars) {
const style = deepCopy(await API.styles.get(id));
style.usercssData.vars = vars;
await usercssMan.buildCode(style);
return (await API.styles.install(style, 'config'))
.usercssData.vars;
},
async editSave(style) {
style = await usercssMan.parse(style);
return {
log: style.log, // extracting the non-enumerable prop, otherwise it won't survive messaging
style: await API.styles.editSave(style),
};
},
async find(styleOrData) {
if (styleOrData.id) {
return API.styles.get(styleOrData.id);
}
const {name, namespace} = styleOrData.usercssData || styleOrData;
for (const dup of await API.styles.getAll()) {
const data = dup.usercssData;
if (data &&
data.name === name &&
data.namespace === namespace) {
return dup;
}
}
},
async install(style, opts) {
return API.styles.install(await usercssMan.parse(style, opts));
},
async parse(style, {dup, vars} = {}) {
style = await usercssMan.buildMeta(style);
// preserve style.vars during update
if (dup || (dup = await usercssMan.find(style))) {
style.id = dup.id;
}
if (vars || (vars = dup)) {
await usercssMan.assignVars(style, vars);
}
return usercssMan.buildCode(style);
},
};
/** Replaces everything with spaces to keep the original length,
* but preserves the line breaks to keep the original line/col relation */
function blankOut(str, start = 0, end = str.length) {
return str.slice(start, end).replace(/[^\r\n]/g, ' ');
}

158
background/uso-api.js Normal file
View File

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

120
background/usw-api.js Normal file
View File

@ -0,0 +1,120 @@
/* global API msg */// msg.js
/* global URLS */ // toolbox.js
/* global tokenMan */
'use strict';
const uswApi = (() => {
//#region Internals
class TokenHooks {
constructor(id) {
this.id = id;
}
keyName(name) {
return `${name}/${this.id}`;
}
query(query) {
return Object.assign(query, {vendor_data: this.id});
}
}
function fakeUsercssHeader(style) {
const {name, _usw: u = {}} = style;
const meta = Object.entries({
'@name': u.name || name || '?',
'@version': // Same as USO-archive version: YYYYMMDD.hh.mm
new Date().toISOString().replace(/^(\d+)-(\d+)-(\d+)T(\d+):(\d+).+/, '$1$2$3.$4.$5'),
'@namespace': u.namespace !== '?' && u.namespace ||
u.username && `userstyles.world/user/${u.username}` ||
'?',
'@description': u.description,
'@author': u.username,
'@license': u.license,
});
const maxKeyLen = meta.reduce((res, [k]) => Math.max(res, k.length), 0);
return [
'/* ==UserStyle==',
...meta.map(([k, v]) => v && `${k}${' '.repeat(maxKeyLen - k.length + 2)}${v}`).filter(Boolean),
'==/UserStyle== */',
].join('\n') + '\n\n';
}
async function linkStyle(style, sourceCode) {
const {id} = style;
const metadata = await API.worker.parseUsercssMeta(sourceCode).catch(console.warn) || {};
const uswData = Object.assign({}, style, {metadata, sourceCode});
API.data.set('usw' + id, uswData);
const token = await tokenMan.getToken('userstylesworld', true, new TokenHooks(id));
const info = await uswFetch('style', token);
const data = style._usw = Object.assign({token}, info);
style.url = style.url || data.homepage || `${URLS.usw}style/${data.id}`;
await uswSave(style);
return data;
}
async function uswFetch(path, token, opts) {
opts = Object.assign({credentials: 'omit'}, opts);
opts.headers = Object.assign({Authorization: `Bearer ${token}`}, opts.headers);
return (await (await fetch(`${URLS.usw}api/${path}`, opts)).json()).data;
}
/** Uses a custom method when broadcasting and avoids needlessly sending the entire style */
async function uswSave(style) {
const {id, _usw} = style;
await API.styles.save(style, {broadcast: false});
msg.broadcastExtension({method: 'uswData', style: {id, _usw}});
}
//#endregion
//#region Exports
return {
/**
* @param {number} id
* @param {string} sourceCode
* @return {Promise<string>}
*/
async publish(id, sourceCode) {
const style = await API.styles.get(id);
const code = style.usercssData ? sourceCode
: fakeUsercssHeader(style) + sourceCode;
const data = (style._usw || {}).token
? style._usw
: await linkStyle(style, code);
return uswFetch(`style/${data.id}`, data.token, {
method: 'POST',
headers: {'Content-Type': 'application/json'},
body: JSON.stringify({code}),
});
},
/**
* @param {number} id
* @return {Promise<void>}
*/
async revoke(id) {
await tokenMan.revokeToken('userstylesworld', new TokenHooks(id));
const style = await API.styles.get(id);
if (style) {
style._usw = {};
await uswSave(style);
}
},
};
//#endregion
})();
/* Doing this outside so we don't break IDE's recognition of the exported methods in IIFE */
for (const [k, fn] of Object.entries(uswApi)) {
uswApi[k] = async (id, ...args) => {
API.data.set('usw' + id, true);
try {
/* Awaiting inside `try` so that `finally` runs when done */
return await fn(id, ...args);
} finally {
API.data.del('usw' + id);
}
};
}

View File

@ -1,616 +1,271 @@
/* eslint no-var: 0 */
/* global API msg */// msg.js
/* global StyleInjector */
/* global prefs */
'use strict';
(() => {
if (typeof window.applyOnMessage === 'function') {
// some weird bug in new Chrome: the content script gets injected multiple times
return;
}
const CHROME = chrome.app ? parseInt(navigator.userAgent.match(/Chrom\w+\/(?:\d+\.){2}(\d+)|$/)[1]) : NaN;
var ID_PREFIX = 'stylus-';
var ROOT = document.documentElement;
var isOwnPage = location.protocol.endsWith('-extension:');
var disableAll = false;
var exposeIframes = false;
var styleElements = new Map();
var disabledElements = new Map();
var retiredStyleTimers = new Map();
var docRewriteObserver;
var docRootObserver;
if (window.INJECTED === 1) return;
window.INJECTED = 1;
// FF59+ bug workaround
// See https://github.com/openstyles/stylus/issues/461
// Since it's easy to spoof the browser version in pre-Quantum FF we're checking
// for getPreventDefault which got removed in FF59 https://bugzil.la/691151
const FF_BUG461 = !CHROME && !isOwnPage && !Event.prototype.getPreventDefault;
const pageContextQueue = [];
/** true -> when the page styles are received,
* false -> when disableAll mode is on at start, the styles won't be sent
* so while disableAll lasts we can ignore messages about style updates because
* the tab will explicitly ask for all styles in bulk when disableAll mode ends */
let hasStyles = false;
let isDisabled = false;
let isTab = !chrome.tabs || location.pathname !== '/popup.html';
const order = {main: [], prio: []};
const calcOrder = ({id}) =>
(order.prio[id] || 0) * 1e6 ||
order.main[id] ||
id + .5e6; // no order = at the end of `main`
const isFrame = window !== parent;
const isFrameAboutBlank = isFrame && location.href === 'about:blank';
const isUnstylable = !chrome.app && document instanceof XMLDocument;
const styleInjector = StyleInjector({
compare: (a, b) => calcOrder(a) - calcOrder(b),
onUpdate: onInjectorUpdate,
});
// dynamic iframes don't have a URL yet so we'll use their parent's URL (hash isn't inherited)
let matchUrl = isFrameAboutBlank && tryCatch(() => parent.location.href.split('#')[0]) ||
location.href;
requestStyles();
chrome.runtime.onMessage.addListener(applyOnMessage);
window.applyOnMessage = applyOnMessage;
// save it now because chrome.runtime will be unavailable in the orphaned script
const orphanEventId = chrome.runtime.id;
let isOrphaned;
// firefox doesn't orphanize content scripts so the old elements stay
if (!chrome.app) styleInjector.clearOrphans();
if (!isOwnPage) {
window.dispatchEvent(new CustomEvent(chrome.runtime.id));
window.addEventListener(chrome.runtime.id, orphanCheck, true);
/** @type chrome.runtime.Port */
let port;
let lazyBadge = isFrame;
let parentDomain;
/* about:blank iframes are often used by sites for file upload or background tasks
* and they may break if unexpected DOM stuff is present at `load` event
* so we'll add the styles only if the iframe becomes visible */
const xoEventId = `${Math.random()}`;
/** @type IntersectionObserver */
let xo;
window[Symbol.for('xo')] = (el, cb) => {
if (!xo) xo = new IntersectionObserver(onIntersect, {rootMargin: '100%'});
el.addEventListener(xoEventId, cb, {once: true});
xo.observe(el);
};
// FIXME: move this to background page when following bugs are fixed:
// https://bugzil.la/1587723, https://crbug.com/968651
const mqDark = matchMedia('(prefers-color-scheme: dark)');
mqDark.onchange = e => API.colorScheme.updateSystemPreferDark(e.matches);
mqDark.onchange(mqDark);
// Declare all vars before init() or it'll throw due to "temporal dead zone" of const/let
init();
// the popup needs a check as it's not a tab but can be opened in a tab manually for whatever reason
if (!isTab) {
chrome.tabs.getCurrent(tab => {
isTab = Boolean(tab);
if (tab && styleInjector.list.length) updateCount();
});
}
function requestStyles(options, callback = applyStyles) {
if (!chrome.app && document instanceof XMLDocument) {
chrome.runtime.sendMessage({method: 'styleViaAPI', action: 'styleApply'});
return;
}
var matchUrl = location.href;
if (!matchUrl.match(/^(http|file|chrome|ftp)/)) {
// dynamic about: and javascript: iframes don't have an URL yet
// so we'll try the parent frame which is guaranteed to have a real URL
try {
if (window !== parent) {
matchUrl = parent.location.href;
}
} catch (e) {}
}
const request = Object.assign({
method: 'getStylesForFrame',
asHash: true,
matchUrl,
}, options);
// On own pages we request the styles directly to minimize delay and flicker
if (typeof API === 'function') {
API.getStyles(request).then(callback);
} else if (!CHROME && getStylesFallback(request)) {
// NOP
} else {
chrome.runtime.sendMessage(request, callback);
msg.onTab(applyOnMessage);
window.addEventListener('pageshow', e => {
if (e.isTrusted && e.persisted) { // bfcache
updateCount();
}
});
if (!chrome.tabs) {
window.dispatchEvent(new CustomEvent(orphanEventId));
window.addEventListener(orphanEventId, orphanCheck, true);
}
/**
* TODO: remove when FF fixes the bug.
* Firefox borks sendMessage in same-origin iframes that have 'src' with a real path on the site.
* We implement a workaround for the initial styleApply case only.
* Everything else (like toggling of styles) is still buggy.
* @param {Object} msg
* @param {Function} callback
* @returns {Boolean|undefined}
*/
function getStylesFallback(msg) {
if (window !== parent &&
location.href !== 'about:blank') {
try {
if (parent.location.origin === location.origin &&
parent.location.href !== location.href) {
chrome.runtime.connect({name: 'getStyles:' + JSON.stringify(msg)});
return true;
}
} catch (e) {}
}
}
function applyOnMessage(request, sender, sendResponse) {
if (request.styles === 'DIY') {
// Do-It-Yourself tells our built-in pages to fetch the styles directly
// which is faster because IPC messaging JSON-ifies everything internally
requestStyles({}, styles => {
request.styles = styles;
applyOnMessage(request);
});
return;
}
if (!chrome.app && document instanceof XMLDocument && request.method !== 'ping') {
request.action = request.method;
request.method = 'styleViaAPI';
request.styles = null;
if (request.style) {
request.style.sections = null;
function onInjectorUpdate() {
if (!isOrphaned) {
updateCount();
const onOff = prefs[styleInjector.list.length ? 'subscribe' : 'unsubscribe'];
onOff('disableAll', updateDisableAll);
if (isFrame) {
updateExposeIframes();
onOff('exposeIframes', updateExposeIframes);
}
}
}
async function init() {
if (isUnstylable) {
await API.styleViaAPI({method: 'styleApply'});
} else {
const SYM_ID = 'styles';
const SYM = Symbol.for(SYM_ID);
const parentStyles = isFrameAboutBlank &&
tryCatch(() => parent[parent.Symbol.for(SYM_ID)]);
const styles =
window[SYM] ||
parentStyles && await new Promise(onFrameElementInView) && parentStyles ||
!isFrameAboutBlank && chrome.app && !chrome.tabs && tryCatch(getStylesViaXhr) ||
await API.styles.getSectionsByUrl(matchUrl, null, true);
if (styles.cfg) {
isDisabled = styles.cfg.disableAll;
Object.assign(order, styles.cfg.order);
}
hasStyles = !isDisabled;
if (hasStyles) {
window[SYM] = styles;
await styleInjector.apply(styles);
} else {
delete window[SYM];
prefs.subscribe('disableAll', updateDisableAll);
}
styleInjector.toggle(hasStyles);
}
}
/** Must be executed inside try/catch */
function getStylesViaXhr() {
const blobId = document.cookie.split(chrome.runtime.id + '=')[1].split(';')[0];
const url = 'blob:' + chrome.runtime.getURL(blobId);
document.cookie = `${chrome.runtime.id}=1; max-age=0`; // remove our cookie
const xhr = new XMLHttpRequest();
xhr.open('GET', url, false); // synchronous
xhr.send();
URL.revokeObjectURL(url);
return JSON.parse(xhr.response);
}
function applyOnMessage(request) {
const {method} = request;
if (isUnstylable) {
if (method === 'urlChanged') {
request.method = 'styleReplaceAll';
}
if (/^(style|updateCount)/.test(method)) {
API.styleViaAPI(request);
return;
}
chrome.runtime.sendMessage(request);
return;
}
switch (request.method) {
const {style} = request;
switch (method) {
case 'ping':
return true;
case 'styleDeleted':
removeStyle(request);
styleInjector.remove(style.id);
break;
case 'styleUpdated':
if (request.codeIsUpdated === false) {
applyStyleState(request.style);
break;
}
if (request.style.enabled) {
removeStyle({id: request.style.id, retire: true});
requestStyles({id: request.style.id});
if (!hasStyles && isDisabled) break;
if (style.enabled) {
API.styles.getSectionsByUrl(matchUrl, style.id).then(sections =>
sections[style.id]
? styleInjector.apply(sections)
: styleInjector.remove(style.id));
} else {
removeStyle(request.style);
styleInjector.remove(style.id);
}
break;
case 'styleAdded':
if (request.style.enabled) {
requestStyles({id: request.style.id});
if (!hasStyles && isDisabled) break;
if (style.enabled) {
API.styles.getSectionsByUrl(matchUrl, style.id)
.then(styleInjector.apply);
}
break;
case 'styleApply':
applyStyles(request.styles);
case 'styleSort':
Object.assign(order, request.order);
styleInjector.sort();
break;
case 'styleReplaceAll':
replaceAll(request.styles);
case 'urlChanged':
if (!hasStyles && isDisabled || matchUrl === request.url) break;
matchUrl = request.url;
API.styles.getSectionsByUrl(matchUrl).then(sections => {
hasStyles = true;
styleInjector.replace(sections);
});
break;
case 'prefChanged':
if ('disableAll' in request.prefs) {
doDisableAll(request.prefs.disableAll);
}
if ('exposeIframes' in request.prefs) {
doExposeIframes(request.prefs.exposeIframes);
}
break;
case 'ping':
sendResponse(true);
case 'updateCount':
updateCount();
break;
}
}
function doDisableAll(disable = disableAll) {
if (!disable === !disableAll) {
return;
}
disableAll = disable;
Array.prototype.forEach.call(document.styleSheets, stylesheet => {
if (stylesheet.ownerNode.matches(`style.stylus[id^="${ID_PREFIX}"]`)
&& stylesheet.disabled !== disable) {
stylesheet.disabled = disable;
}
});
}
function doExposeIframes(state = exposeIframes) {
if (state === exposeIframes ||
state === true && typeof exposeIframes === 'string' ||
window === parent) {
return;
}
exposeIframes = state;
const attr = document.documentElement.getAttribute('stylus-iframe');
if (state && state !== attr) {
document.documentElement.setAttribute('stylus-iframe', state);
} else if (!state && attr !== undefined) {
document.documentElement.removeAttribute('stylus-iframe');
}
}
function applyStyleState({id, enabled}) {
const inCache = disabledElements.get(id) || styleElements.get(id);
const inDoc = document.getElementById(ID_PREFIX + id);
if (enabled) {
if (inDoc) {
return;
} else if (inCache) {
addStyleElement(inCache);
disabledElements.delete(id);
} else {
requestStyles({id});
}
function updateDisableAll(key, disableAll) {
isDisabled = disableAll;
if (isUnstylable) {
API.styleViaAPI({method: 'prefChanged', prefs: {disableAll}});
} else if (!hasStyles && !disableAll) {
init();
} else {
if (inDoc) {
disabledElements.set(id, inDoc);
docRootObserver.evade(() => inDoc.remove());
styleInjector.toggle(!disableAll);
}
}
async function updateExposeIframes(key, value = prefs.get('exposeIframes')) {
const attr = 'stylus-iframe';
const el = document.documentElement;
if (!el) return; // got no styles so styleInjector didn't wait for <html>
if (!value || !styleInjector.list.length) {
el.removeAttribute(attr);
} else {
if (!parentDomain) parentDomain = await API.getTabUrlPrefix();
// Check first to avoid triggering DOM mutation
if (el.getAttribute(attr) !== parentDomain) {
el.setAttribute(attr, parentDomain);
}
}
}
function removeStyle({id, retire = false}) {
const el = document.getElementById(ID_PREFIX + id);
if (el) {
if (retire) {
// to avoid page flicker when the style is updated
// instead of removing it immediately we rename its ID and queue it
// to be deleted in applyStyles after a new version is fetched and applied
const deadID = id + '-ghost';
el.id = ID_PREFIX + deadID;
// in case something went wrong and new style was never applied
retiredStyleTimers.set(deadID, setTimeout(removeStyle, 1000, {id: deadID}));
} else {
docRootObserver.evade(() => el.remove());
function updateCount() {
if (!isTab) return;
if (isFrame) {
if (!port && styleInjector.list.length) {
port = chrome.runtime.connect({name: 'iframe'});
} else if (port && !styleInjector.list.length) {
port.disconnect();
}
if (lazyBadge && performance.now() > 1000) lazyBadge = false;
}
styleElements.delete(ID_PREFIX + id);
disabledElements.delete(id);
retiredStyleTimers.delete(id);
(isUnstylable ?
API.styleViaAPI({method: 'updateCount'}) :
API.updateIconBadge(styleInjector.list.map(style => style.id), {lazyBadge})
).catch(msg.ignoreError);
}
function applyStyles(styles) {
if (!styles) {
// Chrome is starting up
requestStyles();
return;
}
function onFrameElementInView(cb) {
parent[parent.Symbol.for('xo')](frameElement, cb);
}
if (!document.documentElement) {
new MutationObserver((mutations, observer) => {
if (document.documentElement) {
observer.disconnect();
applyStyles(styles);
}
}).observe(document, {childList: true});
return;
}
if ('disableAll' in styles) {
doDisableAll(styles.disableAll);
}
if ('exposeIframes' in styles) {
doExposeIframes(styles.exposeIframes);
}
const gotNewStyles = styles.length || styles.needTransitionPatch;
if (gotNewStyles) {
if (docRootObserver) {
docRootObserver.stop();
} else {
initDocRootObserver();
/** @param {IntersectionObserverEntry[]} entries */
function onIntersect(entries) {
for (const e of entries) {
if (e.isIntersecting) {
xo.unobserve(e.target);
e.target.dispatchEvent(new Event(xoEventId));
}
}
if (styles.needTransitionPatch) {
applyTransitionPatch();
}
if (gotNewStyles) {
for (const id in styles) {
const sections = styles[id];
if (!Array.isArray(sections)) continue;
applySections(id, sections.map(({code}) => code).join('\n'));
}
docRootObserver.firstStart();
}
if (FF_BUG461 && (gotNewStyles || styles.needTransitionPatch)) {
setContentsInPageContext();
}
if (!isOwnPage && !docRewriteObserver && styleElements.size) {
initDocRewriteObserver();
}
if (retiredStyleTimers.size) {
setTimeout(() => {
for (const [id, timer] of retiredStyleTimers.entries()) {
removeStyle({id});
clearTimeout(timer);
}
});
}
}
function applySections(styleId, code) {
const id = ID_PREFIX + styleId;
let el = styleElements.get(id) || document.getElementById(id);
if (el && el.textContent !== code) {
if (CHROME < 3321) {
// workaround for Chrome devtools bug fixed in v65
el.remove();
el = null;
} else if (FF_BUG461) {
pageContextQueue.push({id: el.id, el, code});
} else {
el.textContent = code;
}
}
if (!el) {
if (document.documentElement instanceof SVGSVGElement) {
// SVG document style
el = document.createElementNS('http://www.w3.org/2000/svg', 'style');
} else if (document instanceof XMLDocument) {
// XML document style
el = document.createElementNS('http://www.w3.org/1999/xhtml', 'style');
} else {
// HTML document style; also works on HTML-embedded SVG
el = document.createElement('style');
}
el.id = id;
el.type = 'text/css';
// SVG className is not a string, but an instance of SVGAnimatedString
el.classList.add('stylus');
if (FF_BUG461) {
pageContextQueue.push({id: el.id, el, code});
} else {
el.textContent = code;
}
addStyleElement(el);
}
styleElements.set(id, el);
disabledElements.delete(Number(styleId));
return el;
}
function setContentsInPageContext() {
function tryCatch(func, ...args) {
try {
(document.head || ROOT).appendChild(document.createElement('script')).text = `(${queue => {
document.currentScript.remove();
for (const {id, code} of queue) {
const el = document.getElementById(id) ||
document.querySelector('style.stylus[id="' + id + '"]');
if (!el) continue;
const {disabled} = el.sheet;
el.textContent = code;
el.sheet.disabled = disabled;
}
}})(${JSON.stringify(pageContextQueue)})`;
return func(...args);
} catch (e) {}
let failedSome;
for (const {el, code} of pageContextQueue) {
if (el.textContent !== code) {
el.textContent = code;
failedSome = true;
}
}
if (failedSome) {
console.debug('Could not set code of some styles in page context, ' +
'see https://github.com/openstyles/stylus/issues/461');
}
pageContextQueue.length = 0;
}
function addStyleElement(newElement) {
if (!ROOT) {
return;
}
let next;
const newStyleId = getStyleId(newElement);
for (const el of styleElements.values()) {
if (el.parentNode && !el.id.endsWith('-ghost') && getStyleId(el) > newStyleId) {
next = el.parentNode === ROOT ? el : null;
break;
}
}
if (next === newElement.nextElementSibling) {
return;
}
docRootObserver.evade(() => {
ROOT.insertBefore(newElement, next || null);
if (disableAll) {
newElement.disabled = true;
}
});
}
function replaceAll(newStyles) {
if ('disableAll' in newStyles &&
disableAll === newStyles.disableAll &&
styleElements.size === countStylesInHash(newStyles) &&
[...styleElements.values()].every(el =>
el.disabled === disableAll &&
el.parentNode === ROOT &&
el.textContent === (newStyles[getStyleId(el)] || []).map(({code}) => code).join('\n'))) {
return;
}
const oldStyles = Array.prototype.slice.call(
document.querySelectorAll(`style.stylus[id^="${ID_PREFIX}"]`));
oldStyles.forEach(el => (el.id += '-ghost'));
styleElements.clear();
disabledElements.clear();
[...retiredStyleTimers.values()].forEach(clearTimeout);
retiredStyleTimers.clear();
applyStyles(newStyles);
docRootObserver.evade(() =>
oldStyles.forEach(el => el.remove()));
}
function applyTransitionPatch() {
// CSS transition bug workaround: since we insert styles asynchronously,
// the browsers, especially Firefox, may apply all transitions on page load
const className = chrome.runtime.id + '-transition-bug-fix';
const docId = document.documentElement.id ? '#' + document.documentElement.id : '';
document.documentElement.classList.add(className);
applySections(0, `
${docId}.${className}:root * {
transition: none !important;
}
`);
setTimeout(() => {
removeStyle({id: 0});
document.documentElement.classList.remove(className);
});
}
function getStyleId(el) {
return parseInt(el.id.substr(ID_PREFIX.length));
}
function countStylesInHash(styleHash) {
let num = 0;
for (const k in styleHash) {
num += !isNaN(parseInt(k)) ? 1 : 0;
}
return num;
}
function orphanCheck() {
if (chrome.i18n && chrome.i18n.getUILanguage()) {
return true;
}
if (chrome.runtime.id) return;
// In Chrome content script is orphaned on an extension update/reload
// so we need to detach event listeners
[docRewriteObserver, docRootObserver].forEach(ob => ob && ob.disconnect());
window.removeEventListener(chrome.runtime.id, orphanCheck, true);
try {
chrome.runtime.onMessage.removeListener(applyOnMessage);
} catch (e) {}
}
function initDocRewriteObserver() {
// detect documentElement being rewritten from inside the script
docRewriteObserver = new MutationObserver(mutations => {
for (let m = mutations.length; --m >= 0;) {
const added = mutations[m].addedNodes;
for (let n = added.length; --n >= 0;) {
if (added[n].localName === 'html') {
reinjectStyles();
return;
}
}
}
});
docRewriteObserver.observe(document, {childList: true});
// detect dynamic iframes rewritten after creation by the embedder i.e. externally
setTimeout(() => {
if (document.documentElement !== ROOT) {
reinjectStyles();
}
});
// re-add styles if we detect documentElement being recreated
function reinjectStyles() {
if (!styleElements) {
orphanCheck();
return;
}
ROOT = document.documentElement;
docRootObserver.stop();
const imported = [];
for (const [id, el] of styleElements.entries()) {
const copy = document.importNode(el, true);
el.textContent += ' '; // invalidate CSSOM cache
imported.push([id, copy]);
addStyleElement(copy);
}
docRootObserver.start();
styleElements = new Map(imported);
}
}
function initDocRootObserver() {
let lastRestorationTime = 0;
let restorationCounter = 0;
let observing = false;
let sorting = false;
let observer;
// allow any types of elements between ours, except for the following:
const ORDERED_TAGS = ['head', 'body', 'frameset', 'style', 'link'];
init();
return;
function init() {
observer = new MutationObserver(sortStyleElements);
docRootObserver = {firstStart, start, stop, evade, disconnect: stop};
setTimeout(sortStyleElements);
}
function firstStart() {
if (sortStyleMap()) {
sortStyleElements();
}
start();
}
function start() {
if (!observing && ROOT && observer) {
observer.observe(ROOT, {childList: true});
observing = true;
}
}
function stop() {
if (observing) {
observer.takeRecords();
observer.disconnect();
observing = false;
}
}
function evade(fn) {
const wasObserving = observing;
if (observing) {
stop();
}
fn();
if (wasObserving) {
start();
}
}
function sortStyleMap() {
const list = [];
let prevStyleId = 0;
let needsSorting = false;
for (const entry of styleElements.entries()) {
list.push(entry);
const el = entry[1];
const styleId = getStyleId(el);
el.styleId = styleId;
needsSorting |= styleId < prevStyleId;
prevStyleId = styleId;
}
if (needsSorting) {
styleElements = new Map(list.sort((a, b) => a[1].styleId - b[1].styleId));
return true;
}
}
function sortStyleElements() {
if (!observing) return;
let prevExpected = document.documentElement.lastElementChild;
while (prevExpected && isSkippable(prevExpected, true)) {
prevExpected = prevExpected.previousElementSibling;
}
if (!prevExpected) return;
for (const el of styleElements.values()) {
if (!isMovable(el)) {
continue;
}
while (true) {
const next = prevExpected.nextElementSibling;
if (next && isSkippable(next)) {
prevExpected = next;
} else if (
next === el ||
next === el.previousElementSibling && next ||
moveAfter(el, next || prevExpected)) {
prevExpected = el;
break;
} else {
return;
}
}
}
if (sorting) {
sorting = false;
if (observer) observer.takeRecords();
if (!restorationLimitExceeded()) {
start();
} else {
setTimeout(start, 1000);
}
}
}
function isMovable(el) {
return el.parentNode || !disabledElements.has(getStyleId(el));
}
function isSkippable(el, skipOwnStyles) {
return !ORDERED_TAGS.includes(el.localName) ||
el.id.startsWith(ID_PREFIX) &&
(skipOwnStyles || el.id.endsWith('-ghost')) &&
el.localName === 'style' &&
el.className === 'stylus';
}
function moveAfter(el, expected) {
if (!sorting) {
sorting = true;
stop();
}
expected.insertAdjacentElement('afterend', el);
if (el.disabled !== disableAll) {
// moving an element resets its 'disabled' state
el.disabled = disableAll;
}
return true;
}
function restorationLimitExceeded() {
const t = performance.now();
if (t - lastRestorationTime > 1000) {
restorationCounter = 0;
}
lastRestorationTime = t;
return ++restorationCounter > 5;
}
window.removeEventListener(orphanEventId, orphanCheck, true);
mqDark.onchange = null;
isOrphaned = true;
setTimeout(styleInjector.clear, 1000); // avoiding FOUC
tryCatch(msg.off, applyOnMessage);
}
})();

View File

@ -0,0 +1,21 @@
/* global API */// msg.js
'use strict';
// onCommitted may fire twice
// Note, we're checking against a literal `1`, not just `if (truthy)`,
// because <html id="INJECTED"> is exposed per HTML spec as a global variable and `window.INJECTED`.
if (window.INJECTED_GREASYFORK !== 1) {
window.INJECTED_GREASYFORK = 1;
addEventListener('message', async function onMessage(e) {
if (e.origin === location.origin &&
e.data &&
e.data.name &&
e.data.type === 'style-version-query') {
removeEventListener('message', onMessage);
const style = await API.usercss.find(e.data) || {};
const {version} = style.usercssData || {};
postMessage({type: 'style-version', version}, '*');
}
});
}

View File

@ -1,176 +0,0 @@
'use strict';
(() => {
const manifest = chrome.runtime.getManifest();
const allowedOrigins = [
'https://openusercss.org',
'https://openusercss.com'
];
const sendPostMessage = message => {
if (allowedOrigins.includes(location.origin)) {
window.postMessage(message, location.origin);
}
};
const askHandshake = () => {
// Tell the page that we exist and that it should send the handshake
sendPostMessage({
type: 'ouc-begin-handshake'
});
};
// Listen for queries by the site and respond with a callback object
const sendInstalledCallback = styleData => {
sendPostMessage({
type: 'ouc-is-installed-response',
style: styleData
});
};
const installedHandler = event => {
if (event.data
&& event.data.type === 'ouc-is-installed'
&& allowedOrigins.includes(event.origin)
) {
chrome.runtime.sendMessage({
method: 'findUsercss',
name: event.data.name,
namespace: event.data.namespace
}, style => {
const data = {event};
const callbackObject = {
installed: Boolean(style),
enabled: style.enabled,
name: data.name,
namespace: data.namespace
};
sendInstalledCallback(callbackObject);
});
}
};
const attachInstalledListeners = () => {
window.addEventListener('message', installedHandler);
};
const doHandshake = () => {
// This is a representation of features that Stylus is capable of
const implementedFeatures = [
'install-usercss',
'event:install-usercss',
'event:is-installed',
'configure-after-install',
'builtin-editor',
'create-usercss',
'edit-usercss',
'import-moz-export',
'export-moz-export',
'update-manual',
'update-auto',
'export-json-backups',
'import-json-backups',
'manage-local'
];
const reportedFeatures = [];
// The handshake question includes a list of required and optional features
// we match them with features we have implemented, and build a union array.
event.data.featuresList.required.forEach(feature => {
if (implementedFeatures.includes(feature)) {
reportedFeatures.push(feature);
}
});
event.data.featuresList.optional.forEach(feature => {
if (implementedFeatures.includes(feature)) {
reportedFeatures.push(feature);
}
});
// We send the handshake response, which includes the key we got, plus some
// additional metadata
sendPostMessage({
type: 'ouc-handshake-response',
key: event.data.key,
extension: {
name: manifest.name,
capabilities: reportedFeatures
}
});
};
const handshakeHandler = event => {
if (event.data
&& event.data.type === 'ouc-handshake-question'
&& allowedOrigins.includes(event.origin)
) {
doHandshake();
}
};
const attachHandshakeListeners = () => {
// Wait for the handshake request, then start it
window.addEventListener('message', handshakeHandler);
};
const sendInstallCallback = data => {
// Send an install callback to the site in order to let it know
// we were able to install the theme and it may display a success message
sendPostMessage({
type: 'ouc-install-callback',
key: data.key
});
};
const installHandler = event => {
if (event.data
&& event.data.type === 'ouc-install-usercss'
&& allowedOrigins.includes(event.origin)
) {
chrome.runtime.sendMessage({
method: 'saveUsercss',
reason: 'install',
name: event.data.title,
sourceCode: event.data.code,
}, style => {
sendInstallCallback({
enabled: style.enabled,
key: event.data.key
});
});
}
};
const attachInstallListeners = () => {
// Wait for an install event, then save the theme
window.addEventListener('message', installHandler);
};
const orphanCheck = () => {
const eventName = chrome.runtime.id + '-install-hook-openusercss';
const orphanCheckRequest = () => {
// If we can't get the UI language, it means we are orphaned, and should
// remove our event handlers
if (chrome.i18n && chrome.i18n.getUILanguage()) return true;
window.removeEventListener('message', installHandler);
window.removeEventListener('message', handshakeHandler);
window.removeEventListener('message', installedHandler);
window.removeEventListener(eventName, orphanCheckRequest, true);
};
// Send the event before we listen for it, for other possible
// running instances of the content script.
dispatchEvent(new Event(eventName));
addEventListener(eventName, orphanCheckRequest, true);
};
orphanCheck();
attachHandshakeListeners();
attachInstallListeners();
attachInstalledListeners();
askHandshake();
})();

View File

@ -1,105 +1,26 @@
'use strict';
(() => {
// some weird bug in new Chrome: the content script gets injected multiple times
if (typeof window.initUsercssInstall === 'function') return;
if (!/text\/(css|plain)/.test(document.contentType) ||
!/==userstyle==/i.test(document.body.textContent)) {
return;
}
window.initUsercssInstall = () => {};
orphanCheck();
const DELAY = 500;
const url = location.href;
let sourceCode, port, timer;
chrome.runtime.onConnect.addListener(onConnected);
chrome.runtime.sendMessage({method: 'installUsercss', url}, r =>
r && r.__ERROR__ && alert(r.__ERROR__));
function onConnected(newPort) {
port = newPort;
port.onDisconnect.addListener(stop);
port.onMessage.addListener(onMessage);
}
function onMessage(msg, port) {
switch (msg.method) {
case 'getSourceCode':
fetchText(url)
.then(text => {
sourceCode = sourceCode || text;
port.postMessage({
method: msg.method + 'Response',
sourceCode,
});
})
.catch(err => port.postMessage({
method: msg.method + 'Response',
error: err.message || String(err),
}));
break;
case 'liveReloadStart':
start();
break;
case 'liveReloadStop':
stop();
break;
}
}
function fetchText(url) {
return new Promise((resolve, reject) => {
// you can't use fetch in Chrome under 'file:' protocol
const xhr = new XMLHttpRequest();
xhr.open('GET', url);
xhr.addEventListener('load', () => resolve(xhr.responseText));
xhr.addEventListener('error', () => reject(xhr));
xhr.send();
});
}
function start() {
timer = timer || setTimeout(check, DELAY);
}
function stop() {
clearTimeout(timer);
timer = null;
}
function check() {
fetchText(url)
.then(text => {
if (sourceCode === text) return;
sourceCode = text;
port.postMessage({method: 'sourceCodeChanged', sourceCode});
})
.catch(error => {
console.log(chrome.i18n.getMessage('liveReloadError', error));
})
.then(() => {
timer = null;
start();
});
}
function orphanCheck() {
const eventName = chrome.runtime.id + '-install-hook-usercss';
const orphanCheckRequest = () => {
if (chrome.i18n && chrome.i18n.getUILanguage()) return true;
// In Chrome content script is orphaned on an extension update/reload
// so we need to detach event listeners
removeEventListener(eventName, orphanCheckRequest, true);
// preventing reregistration if reinjected by tabs.executeScript for whatever reason, just in case
if (typeof window.oldCode !== 'string') {
window.oldCode = (document.querySelector('body > pre') || document.body).textContent;
chrome.runtime.onConnect.addListener(port => {
if (port.name !== 'downloadSelf') return;
port.onMessage.addListener(async ({id, force}) => {
const msg = {id};
try {
chrome.runtime.onConnect.removeListener(onConnected);
} catch (e) {}
};
dispatchEvent(new Event(eventName));
addEventListener(eventName, orphanCheckRequest, true);
}
})();
const code = await (await fetch(location.href, {mode: 'same-origin'})).text();
if (code !== window.oldCode || force) {
msg.code = window.oldCode = code;
}
} catch (error) {
msg.error = error.message || `${error}`;
}
port.postMessage(msg);
});
// FF keeps content scripts connected on navigation https://github.com/openstyles/stylus/issues/864
addEventListener('pagehide', () => port.disconnect(), {once: true});
});
}
// passing the result to tabs.executeScript
window.oldCode; // eslint-disable-line no-unused-expressions

View File

@ -1,487 +1,324 @@
/* global cloneInto */
/* global API */// msg.js
'use strict';
(() => {
window.dispatchEvent(new CustomEvent(chrome.runtime.id + '-install'));
window.addEventListener(chrome.runtime.id + '-install', orphanCheck, true);
// eslint-disable-next-line no-unused-expressions
/^\/styles\/(\d+)(\/([^/]*))?([?#].*)?$/.test(location.pathname) && (async () => {
if (window.INJECTED_USO === 1) return;
window.INJECTED_USO = 1;
document.addEventListener('stylishInstallChrome', onClick);
document.addEventListener('stylishUpdateChrome', onClick);
const usoId = RegExp.$1;
const USO = 'https://userstyles.org';
const apiUrl = `${USO}/api/v1/styles/${usoId}`;
const md5Url = `https://update.userstyles.org/${usoId}.md5`;
const CLICK = [
['#install_stylish_style_button', onInstall],
['#update_stylish_style_button', onInstall],
['.customize_style_button', onCustomize],
['.uninstall_stylish_style_button', onUninstall],
];
const pageEventId = `${performance.now()}${Math.random()}`;
const contentEventId = pageEventId + ':';
const orphanEventId = chrome.runtime.id; // id won't be available in the orphaned script
const $ = (sel, base = document) => base.querySelector(sel);
const toggleListener = (isOn, ...args) => (isOn ? addEventListener : removeEventListener)(...args);
const togglePageListener = isOn => toggleListener(isOn, contentEventId, onPageEvent, true);
chrome.runtime.onMessage.addListener(onMessage);
const mo = new MutationObserver(onMutation);
const observeColors = isOn =>
isOn ? mo.observe(document.body, {subtree: true, attributes: true, attributeFilter: ['value']})
: mo.disconnect();
onDOMready().then(() => {
window.postMessage({
direction: 'from-content-script',
message: 'StylishInstalled',
}, '*');
});
let style, dup, md5, pageData, badKeys;
let gotBody = false;
new MutationObserver(observeDOM).observe(document.documentElement, {
childList: true,
subtree: true,
});
observeDOM();
runInPage(inPageContext, pageEventId, contentEventId, usoId, apiUrl);
addEventListener(orphanEventId, orphanCheck, true);
addEventListener('click', onClick, true);
togglePageListener(true);
function observeDOM() {
if (!gotBody) {
if (!document.body) return;
gotBody = true;
// TODO: remove the following statement when USO pagination title is fixed
document.title = document.title.replace(/^(\d+)&\w+=/, '#$1: ');
chrome.runtime.sendMessage({
method: 'getStyles',
md5Url: getMeta('stylish-md5-url') || location.href
}, checkUpdatability);
}
if (document.getElementById('install_button')) {
onDOMready().then(() => {
requestAnimationFrame(() => {
sendEvent(sendEvent.lastEvent);
});
});
}
[md5, dup] = await Promise.all([
fetch(md5Url).then(r => r.text()),
API.styles.find({md5Url}, {installationUrl: `https://uso.kkx.one/style/${usoId}`})
.then(sendVarsToPage),
document.body || new Promise(resolve => addEventListener('load', resolve, {once: true})),
]);
if (!dup) {
sendStylishEvent('styleCanBeInstalledChrome');
} else if (dup.originalMd5 && dup.originalMd5 !== md5 || !dup.usercssData || !dup.md5Url) {
// allow update if 1) changed, 2) is a classic USO style, 3) is from USO-archive
sendStylishEvent('styleCanBeUpdatedChrome');
} else {
sendStylishEvent('styleAlreadyInstalledChrome');
}
function onMessage(msg, sender, sendResponse) {
switch (msg.method) {
case 'ping':
// orphaned content script check
sendResponse(true);
break;
case 'openSettings':
openSettings();
sendResponse(true);
break;
}
}
/* since we are using "stylish-code-chrome" meta key on all browsers and
US.o does not provide "advanced settings" on this url if browser is not Chrome,
we need to fix this URL using "stylish-update-url" meta key
*/
function getStyleURL() {
const textUrl = getMeta('stylish-update-url') || '';
const jsonUrl = getMeta('stylish-code-chrome') ||
textUrl.replace(/styles\/(\d+)\/[^?]*/, 'styles/chrome/$1.json');
const paramsMissing = !jsonUrl.includes('?') && textUrl.includes('?');
return jsonUrl + (paramsMissing ? textUrl.replace(/^[^?]+/, '') : '');
}
function checkUpdatability([installedStyle]) {
// TODO: remove the following statement when USO is fixed
document.dispatchEvent(new CustomEvent('stylusFixBuggyUSOsettings', {
detail: installedStyle && installedStyle.updateUrl,
}));
if (!installedStyle) {
sendEvent({type: 'styleCanBeInstalledChrome'});
return;
}
const isCustomizable = /\?/.test(installedStyle.updateUrl);
const md5Url = getMeta('stylish-md5-url');
if (md5Url && installedStyle.md5Url && installedStyle.originalMd5) {
getResource(md5Url).then(md5 => {
reportUpdatable(
isCustomizable ||
md5 !== installedStyle.originalMd5);
});
} else {
getStyleJson().then(json => {
reportUpdatable(
isCustomizable ||
!json ||
!styleSectionsEqual(json, installedStyle));
});
}
function reportUpdatable(isUpdatable) {
sendEvent({
type: isUpdatable
? 'styleCanBeUpdatedChrome'
: 'styleAlreadyInstalledChrome',
detail: {
updateUrl: installedStyle.updateUrl
},
});
}
}
function sendEvent(event) {
sendEvent.lastEvent = event;
let {type, detail = null} = event;
if (typeof cloneInto !== 'undefined') {
// Firefox requires explicit cloning, however USO can't process our messages anyway
// because USO tries to use a global "event" variable deprecated in Firefox
detail = cloneInto({detail}, document);
} else {
detail = {detail};
}
onDOMready().then(() => {
document.dispatchEvent(new CustomEvent(type, detail));
});
}
function onClick(event) {
if (onClick.processing || !orphanCheck()) {
return;
}
onClick.processing = true;
(event.type.includes('Update') ? onUpdate() : onInstall())
.then(done, done);
function done() {
setTimeout(() => {
onClick.processing = false;
});
}
}
function onInstall() {
return getResource(getMeta('stylish-description'))
.then(name => saveStyleCode('styleInstall', name))
.then(() => getResource(getMeta('stylish-install-ping-url-chrome')));
}
function onUpdate() {
return new Promise((resolve, reject) => {
chrome.runtime.sendMessage({
method: 'getStyles',
md5Url: getMeta('stylish-md5-url') || location.href,
}, ([style]) => {
saveStyleCode('styleUpdate', style.name, {id: style.id})
.then(resolve, reject);
});
});
}
function saveStyleCode(message, name, addProps) {
return new Promise((resolve, reject) => {
const isNew = message === 'styleInstall';
const needsConfirmation = isNew || !saveStyleCode.confirmed;
if (needsConfirmation && !confirm(chrome.i18n.getMessage(message, [name]))) {
reject();
return;
}
saveStyleCode.confirmed = true;
enableUpdateButton(false);
getStyleJson().then(json => {
if (!json) {
prompt(chrome.i18n.getMessage('styleInstallFailed', ''),
'https://github.com/openstyles/stylus/issues/195');
return;
}
chrome.runtime.sendMessage(
Object.assign(json, addProps, {
method: 'saveStyle',
reason: isNew ? 'install' : 'update',
}),
style => {
if (!isNew && style.updateUrl.includes('?')) {
enableUpdateButton(true);
} else {
sendEvent({type: 'styleInstalledChrome'});
}
}
);
resolve();
});
});
function enableUpdateButton(state) {
const important = s => s.replace(/;/g, '!important;');
const button = document.getElementById('update_style_button');
if (button) {
button.style.cssText = state ? '' : important('pointer-events: none; opacity: .35;');
const icon = button.querySelector('img[src*=".svg"]');
if (icon) {
icon.style.cssText = state ? '' : important('transition: transform 5s; transform: rotate(0);');
if (state) {
setTimeout(() => (icon.style.cssText += important('transform: rotate(10turn);')));
}
}
async function onClick(e) {
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;
}
}
}
function getMeta(name) {
const e = document.querySelector(`link[rel="${name}"]`);
return e ? e.getAttribute('href') : null;
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 getResource(url, options) {
return new Promise(resolve => {
if (url.startsWith('#')) {
resolve(document.getElementById(url.slice(1)).textContent);
} else {
chrome.runtime.sendMessage(Object.assign({
url,
method: 'download',
timeout: 60e3,
// USO can't handle POST requests for style json
body: null,
}, options), result => {
const error = result && result.__ERROR__;
if (error) {
alert('Error' + (error ? '\n' + error : ''));
} else {
resolve(result);
}
});
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());
}
}
function onMutation(mutations) {
for (const {target: el} of mutations) {
if (el.style.display === 'none' &&
/^ik-/.test(el.name) &&
/^#[\da-f]{6}$/.test(el.value)) {
onChange({target: el});
}
}
}
function onPageEvent(e) {
pageData = e.detail;
togglePageListener(false);
}
async function buildStyle() {
if (!pageData) pageData = await (await fetch(apiUrl)).json();
({style, badKeys} = await API.uso.toUsercss(pageData, {varsUrl: dup.updateUrl}));
Object.assign(style, {
md5Url,
id: dup.id,
originalMd5: md5,
updateUrl: apiUrl,
});
}
function getStyleJson() {
return getResource(getStyleURL(), {responseType: 'json'})
.then(style => {
if (!style || !Array.isArray(style.sections) || style.sections.length) {
return style;
}
const codeElement = document.getElementById('stylish-code');
if (codeElement && !codeElement.textContent.trim()) {
return style;
}
return getResource(getMeta('stylish-update-url')).then(code => new Promise(resolve => {
chrome.runtime.sendMessage({method: 'parseCss', code}, ({sections}) => {
style.sections = sections;
resolve(style);
});
}));
})
.catch(() => null);
}
function styleSectionsEqual({sections: a}, {sections: b}) {
if (!a || !b) {
return undefined;
}
if (a.length !== b.length) {
return false;
}
// order of sections should be identical to account for the case of multiple
// sections matching the same URL because the order of rules is part of cascading
return a.every((sectionA, index) => propertiesEqual(sectionA, b[index]));
function propertiesEqual(secA, secB) {
for (const name of ['urlPrefixes', 'urls', 'domains', 'regexps']) {
if (!equalOrEmpty(secA[name], secB[name], 'every', arrayMirrors)) {
return false;
}
}
return equalOrEmpty(secA.code, secB.code, 'substr', (a, b) => a === b);
}
function equalOrEmpty(a, b, telltale, comparator) {
const typeA = a && typeof a[telltale] === 'function';
const typeB = b && typeof b[telltale] === 'function';
return (
(a === null || a === undefined || (typeA && !a.length)) &&
(b === null || b === undefined || (typeB && !b.length))
) || typeA && typeB && a.length === b.length && comparator(a, b);
}
function arrayMirrors(array1, array2) {
return (
array1.every(el => array2.includes(el)) &&
array2.every(el => array1.includes(el))
);
}
}
function onDOMready() {
if (document.readyState !== 'loading') {
return Promise.resolve();
}
return new Promise(resolve => {
document.addEventListener('DOMContentLoaded', function _() {
document.removeEventListener('DOMContentLoaded', _);
resolve();
});
});
}
function openSettings(countdown = 10e3) {
const button = document.querySelector('.customize_button');
if (button) {
button.dispatchEvent(new MouseEvent('click', {bubbles: true}));
setTimeout(function pollArea(countdown = 2000) {
const area = document.getElementById('advancedsettings_area');
if (area || countdown < 0) {
(area || button).scrollIntoView({behavior: 'smooth', block: area ? 'end' : 'center'});
function getPageVars() {
const {vars} = (style || dup).usercssData;
for (const el of document.querySelectorAll('[name^="ik-"]')) {
const name = el.name.slice(3); // dropping "ik-"
const ik = (badKeys || {})[name] || name;
const v = vars[ik] || false;
const isImage = el.type === 'radio';
if (v && (!isImage || el.checked)) {
const val = el.value;
const isFile = val === 'user-upload';
if (isImage && (isFile || val === 'user-url')) {
const el2 = $(`[type=${isFile ? 'file' : 'url'}]`, el.parentElement);
const ikCust = `${ik}-custom`;
v.value = `${ikCust}-dropdown`;
vars[ikCust].value = isFile ? getFileUriFromPage(el2) : el2.value;
} else {
setTimeout(pollArea, 100, countdown - 100);
v.value = v.type === 'select' ? val.replace(/^ik-/, '') : val;
}
}, 500);
} else if (countdown > 0) {
setTimeout(openSettings, 100, countdown - 100);
}
}
return vars;
}
function getFileUriFromPage(el) {
togglePageListener(true);
sendPageEvent(el);
return pageData;
}
function runInPage(fn, ...args) {
const div = document.createElement('div');
div.attachShadow({mode: 'closed'})
.appendChild(document.createElement('script'))
.textContent = `(${fn})(${JSON.stringify(args).slice(1, -1)})`;
document.documentElement.appendChild(div).remove();
}
function sendPageEvent(data) {
dispatchEvent(data instanceof Node
? new MouseEvent(pageEventId, {relatedTarget: data})
: new CustomEvent(pageEventId, {detail: data}));
//* global cloneInto */// WARNING! Firefox requires cloning of an object `detail`
}
function sendStylishEvent(type) {
document.dispatchEvent(new Event(type));
}
function sendVarsToPage(style) {
if (style) {
const vars = (style.usercssData || {}).vars || `${style.updateUrl}`.split('?')[1];
if (vars) sendPageEvent('vars:' + JSON.stringify(vars));
}
return style || false;
}
function orphanCheck() {
// TODO: switch to install-hook-usercss.js impl, and remove explicit orphanCheck() calls
if (chrome.i18n && chrome.i18n.getUILanguage()) {
return true;
}
// In Chrome content script is orphaned on an extension update/reload
// so we need to detach event listeners
window.removeEventListener(chrome.runtime.id + '-install', orphanCheck, true);
document.removeEventListener('stylishInstallChrome', onClick);
document.removeEventListener('stylishUpdateChrome', onClick);
try {
chrome.runtime.onMessage.removeListener(onMessage);
} catch (e) {}
if (chrome.runtime.id) return true;
removeEventListener(orphanEventId, orphanCheck, true);
removeEventListener('click', onClick, true);
removeEventListener('change', onChange);
sendPageEvent('quit');
observeColors(false);
togglePageListener(false);
}
})();
// run in page context
document.documentElement.appendChild(document.createElement('script')).text = '(' + (
EXTENSION_ORIGIN => {
document.currentScript.remove();
// spoof Stylish extension presence in Chrome
if (window.chrome && chrome.app) {
const realImage = window.Image;
window.Image = function Image(...args) {
return new Proxy(new realImage(...args), {
get(obj, key) {
return obj[key];
},
set(obj, key, value) {
if (key === 'src' && /^chrome-extension:/i.test(value)) {
setTimeout(() => typeof obj.onload === 'function' && obj.onload());
} else {
obj[key] = value;
}
return true;
},
});
};
}
// spoof USO referrer for style search in the popup
if (window !== top && location.pathname === '/') {
window.addEventListener('message', ({data, origin}) => {
if (!data ||
!data.xhr ||
origin !== EXTENSION_ORIGIN) {
return;
function inPageContext(eventId, eventIdHost, styleId, apiUrl) {
let done, orphaned, vars;
// `chrome` may be empty if no extensions use externally_connectable but USO needs it
if (!window.chrome) window.chrome = {runtime: {sendMessage: () => {}}};
const EXT_ID = 'fjnbnpbmkenffdnngjfgmeleoegfcffe';
const {defineProperty} = Object;
const {dispatchEvent, CustomEvent, removeEventListener} = window;
const apply = Map.call.bind(Map.apply);
const OVR = [
[chrome.runtime, 'sendMessage', (fn, me, args) => {
const [id, /*msg*/, opts, cb = opts] = args;
if (id !== EXT_ID) return apply(fn, me, args);
if (typeof cb !== 'function') return Promise.resolve(true);
cb(true);
}],
[Response.prototype, 'json', async (fn, me, args) => {
const res = await apply(fn, me, args);
try {
if (!done && me.url === apiUrl) {
done = true;
send(res);
setVars(res);
}
const xhr = new XMLHttpRequest();
xhr.onloadend = xhr.onerror = () => {
top.postMessage({
id: data.xhr.id,
status: xhr.status,
// [being overcautious] a string response is used instead of relying on responseType=json
// because it was invoked in a web page context so another extension may have incorrectly spoofed it
response: xhr.response,
}, EXTENSION_ORIGIN);
};
xhr.open('GET', data.xhr.url);
xhr.send();
});
}
// USO bug workaround: use the actual style settings in API response
let settings;
const originalResponseJson = Response.prototype.json;
document.addEventListener('stylusFixBuggyUSOsettings', function _({detail}) {
document.removeEventListener('stylusFixBuggyUSOsettings', _);
// TODO: remove .replace(/^\?/, '') when minimum_chrome_version >= 52 (https://crbug.com/601425)
settings = /\?/.test(detail) && new URLSearchParams(new URL(detail).search.replace(/^\?/, ''));
if (!settings) {
Response.prototype.json = originalResponseJson;
}
} 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);
},
});
Response.prototype.json = function (...args) {
return originalResponseJson.call(this, ...args).then(json => {
if (!settings || typeof ((json || {}).style_settings || {}).every !== 'function') {
return json;
}
Response.prototype.json = originalResponseJson;
const images = new Map();
for (const jsonSetting of json.style_settings) {
let value = settings.get('ik-' + jsonSetting.install_key);
if (!value
|| !jsonSetting.style_setting_options
|| !jsonSetting.style_setting_options[0]) {
continue;
}
if (value.startsWith('ik-')) {
value = value.replace(/^ik-/, '');
const defaultItem = jsonSetting.style_setting_options.find(item => item.default);
if (!defaultItem || defaultItem.install_key !== value) {
if (defaultItem) {
defaultItem.default = false;
}
jsonSetting.style_setting_options.some(item => {
if (item.install_key === value) {
item.default = true;
return true;
}
});
}
} else if (jsonSetting.setting_type === 'image') {
jsonSetting.style_setting_options.some(item => {
if (item.default) {
item.default = false;
return true;
}
});
images.set(jsonSetting.install_key, value);
} else {
const item = jsonSetting.style_setting_options[0];
if (item.value !== value && item.install_key === 'placeholder') {
item.value = value;
}
}
}
if (images.size) {
new MutationObserver((_, observer) => {
if (!document.getElementById('style-settings')) {
return;
}
observer.disconnect();
for (const [name, url] of images.entries()) {
const elRadio = document.querySelector(`input[name="ik-${name}"][value="user-url"]`);
const elUrl = elRadio && document.getElementById(elRadio.id.replace('url-choice', 'user-url'));
if (elUrl) {
elUrl.value = url;
}
}
}).observe(document, {childList: true, subtree: true});
}
return json;
});
};
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 onCommand(e) {
if (e.detail === 'quit') {
removeEventListener(eventId, onCommand, true);
OVR.forEach(restore);
done = orphaned = true;
} else if (/^vars:/.test(e.detail)) {
vars = JSON.parse(e.detail.slice(5));
} else if (e.relatedTarget) {
send(e.relatedTarget.uploadedData);
}
}
) + `)('${chrome.runtime.getURL('').slice(0, -1)}')`;
// TODO: remove the following statement when USO pagination is fixed
if (location.search.includes('category=')) {
document.addEventListener('DOMContentLoaded', function _() {
document.removeEventListener('DOMContentLoaded', _);
new MutationObserver((_, observer) => {
if (!document.getElementById('pagination')) {
return;
function restore(obj, name, ovr, orig) { // same order as OVR after patching
if (obj[name] === ovr) {
defineProperty(obj, name, {value: orig});
}
}
function send(data) {
dispatchEvent(new CustomEvent(eventIdHost, {__proto: null, detail: data}));
}
function setVars(json) {
const images = new Map();
const isNew = typeof vars === 'object';
const badKeys = {};
const newKeys = [];
const makeKey = ({install_key: key}) => {
let res = isNew ? badKeys[key] : key;
if (!res) {
res = key.replace(/[^-\w]/g, '-');
res += newKeys.includes(res) ? '-' : '';
if (key !== res) {
badKeys[key] = res;
newKeys.push(res);
}
}
return res;
};
if (!isNew) vars = new URLSearchParams(vars);
for (const ss of json.style_settings || []) {
const ik = makeKey(ss);
let value = isNew ? (vars[ik] || {}).value : vars.get('ik-' + ik);
if (value == null || !(ss.style_setting_options || [])[0]) {
continue;
}
if (ss.setting_type === 'image') {
let isListed;
for (const opt of ss.style_setting_options) {
isListed |= opt.default = (opt.install_key === value);
}
images.set(ik, {url: isNew && !isListed ? vars[`${ik}-custom`].value : value, isListed});
} else if (value.startsWith('ik-') || isNew && vars[ik].type === 'select') {
value = value.replace(/^ik-/, '');
const def = ss.style_setting_options.find(item => item.default);
if (!def || makeKey(def) !== value) {
if (def) def.default = false;
for (const item of ss.style_setting_options) {
if (makeKey(item) === value) {
item.default = true;
break;
}
}
}
} else {
const item = ss.style_setting_options[0];
if (item.value !== value && item.install_key === 'placeholder') {
item.value = value;
}
}
}
if (!images.size) return;
new MutationObserver((_, observer) => {
if (!document.getElementById('style-settings')) return;
observer.disconnect();
const category = '&' + location.search.match(/category=[^&]+/)[0];
const links = document.querySelectorAll('#pagination a[href*="page="]:not([href*="category="])');
for (let i = 0; i < links.length; i++) {
links[i].href += category;
for (const [name, {url, isListed}] of images) {
const elRadio = document.querySelector(`input[name="ik-${name}"][value="user-url"]`);
const elUrl = elRadio && document.getElementById(elRadio.id.replace('url-choice', 'user-url'));
if (elUrl) {
elRadio.checked = !isListed;
elUrl.value = url;
}
}
}).observe(document, {childList: true, subtree: true});
});
}
}

View File

@ -0,0 +1,43 @@
/* global API */// msg.js
'use strict';
(() => {
const ORIGIN = 'https://userstyles.world';
const HANDLERS = Object.assign(Object.create(null), {
async 'usw-ready'() {
send({type: 'usw-remove-stylus-button'});
if (location.pathname === '/api/oauth/style/new') {
const styleId = Number(new URLSearchParams(location.search).get('vendor_data'));
const data = await API.data.pop('usw' + styleId);
send({type: 'usw-fill-new-style', data});
}
},
async 'usw-style-info-request'(data) {
switch (data.requestType) {
case 'installed': {
const updateUrl = `${ORIGIN}/api/style/${data.styleID}.user.css`;
const style = await API.styles.find({updateUrl});
send({
type: 'usw-style-info-response',
data: {installed: Boolean(style), requestType: 'installed'},
});
break;
}
}
},
});
window.addEventListener('message', ({data, source, origin}) => {
// Some browsers don't reveal `source` to extensions e.g. Firefox
if (data && (source ? source === window : origin === ORIGIN)) {
const fn = HANDLERS[data.type];
if (fn) fn(data);
}
});
function send(msg) {
window.postMessage(msg, ORIGIN);
}
})();

345
content/style-injector.js Normal file
View File

@ -0,0 +1,345 @@
'use strict';
/** @type {function(opts):StyleInjector} */
window.StyleInjector = window.INJECTED === 1 ? window.StyleInjector : ({
compare,
onUpdate = () => {},
}) => {
const PREFIX = 'stylus-';
const PATCH_ID = 'transition-patch';
// styles are out of order if any of these elements is injected between them
const ORDERED_TAGS = new Set(['head', 'body', 'frameset', 'style', 'link']);
const docRewriteObserver = RewriteObserver(sort);
const docRootObserver = RootObserver(sortIfNeeded);
const toSafeChar = c => String.fromCharCode(0xFF00 + c.charCodeAt(0) - 0x20);
const list = [];
const table = new Map();
let isEnabled = true;
let isTransitionPatched = chrome.app && CSS.supports('accent-color', 'red'); // Chrome 93
let exposeStyleName;
// will store the original method refs because the page can override them
let creationDoc, createElement, createElementNS;
return /** @namespace StyleInjector */ {
list,
async apply(styleMap) {
const styles = styleMapToArray(styleMap);
const value = !styles.length
? []
: await docRootObserver.evade(() => {
if (!isTransitionPatched && isEnabled) {
applyTransitionPatch(styles);
}
return styles.map(addUpdate);
});
emitUpdate();
return value;
},
clear() {
addRemoveElements(false);
list.length = 0;
table.clear();
emitUpdate();
},
clearOrphans() {
for (const el of document.querySelectorAll(`style[id^="${PREFIX}"].stylus`)) {
const id = el.id.slice(PREFIX.length);
if (/^\d+$/.test(id) || id === PATCH_ID) {
el.remove();
}
}
},
remove(id) {
remove(id);
emitUpdate();
},
replace(styleMap) {
const styles = styleMapToArray(styleMap);
const added = new Set(styles.map(s => s.id));
const removed = [];
for (const style of list) {
if (!added.has(style.id)) {
removed.push(style.id);
}
}
styles.forEach(addUpdate);
removed.forEach(remove);
emitUpdate();
},
toggle(enable) {
if (isEnabled === enable) return;
isEnabled = enable;
if (!enable) toggleObservers(false);
addRemoveElements(enable);
if (enable) toggleObservers(true);
},
sort: sort,
};
function add(style) {
const el = style.el = createStyle(style);
const i = list.findIndex(item => compare(item, style) > 0);
table.set(style.id, style);
if (isEnabled) {
document.documentElement.insertBefore(el, i < 0 ? null : list[i].el);
}
list.splice(i < 0 ? list.length : i, 0, style);
return el;
}
function addRemoveElements(add) {
for (const {el} of list) {
if (add) {
document.documentElement.appendChild(el);
} else {
el.remove();
}
}
}
function addUpdate(style) {
return table.has(style.id) ? update(style) : add(style);
}
function applyTransitionPatch(styles) {
isTransitionPatched = true;
// CSS transition bug workaround: since we insert styles asynchronously,
// the browsers, especially Firefox, may apply all transitions on page load
if (document.readyState === 'complete' ||
document.visibilityState === 'hidden' ||
!styles.some(s => s.code.includes('transition'))) {
return;
}
const el = createStyle({id: PATCH_ID, code: `
:root:not(#\\0):not(#\\0) * {
transition: none !important;
}
`});
document.documentElement.appendChild(el);
// wait for the next paint to complete
// note: requestAnimationFrame won't fire in inactive tabs
requestAnimationFrame(() => setTimeout(() => el.remove()));
}
function createStyle(style = {}) {
const {id} = style;
if (!creationDoc) initCreationDoc();
let el;
if (document.documentElement instanceof SVGSVGElement) {
// SVG document style
el = createElementNS.call(creationDoc, 'http://www.w3.org/2000/svg', 'style');
} else if (document instanceof XMLDocument) {
// XML document style
el = createElementNS.call(creationDoc, 'http://www.w3.org/1999/xhtml', 'style');
} else {
// HTML document style; also works on HTML-embedded SVG
el = createElement.call(creationDoc, 'style');
}
if (id) {
el.id = `${PREFIX}${id}`;
const oldEl = document.getElementById(el.id);
if (oldEl) oldEl.id += '-superseded-by-Stylus';
}
el.type = 'text/css';
// SVG className is not a string, but an instance of SVGAnimatedString
el.classList.add('stylus');
setTextAndName(el, style);
return el;
}
function setTextAndName(el, {id, code = '', name}) {
if (exposeStyleName && name) {
el.dataset.name = name;
name = encodeURIComponent(name.replace(/[?#/']/g, toSafeChar));
code += `\n/*# sourceURL=${chrome.runtime.getURL(name)}.user.css#${id} */`;
}
el.textContent = code;
}
function toggleObservers(shouldStart) {
const onOff = shouldStart && isEnabled ? 'start' : 'stop';
docRewriteObserver[onOff]();
docRootObserver[onOff]();
}
function emitUpdate() {
toggleObservers(list.length);
onUpdate();
}
/*
FF59+ workaround: allow the page to read our sheets, https://github.com/openstyles/stylus/issues/461
First we're trying the page context document where inline styles may be forbidden by CSP
https://bugzilla.mozilla.org/show_bug.cgi?id=1579345#c3
and since userAgent.navigator can be spoofed via about:config or devtools,
we're checking for getPreventDefault that was removed in FF59
*/
function initCreationDoc() {
creationDoc = !Event.prototype.getPreventDefault && document.wrappedJSObject;
if (creationDoc) {
({createElement, createElementNS} = creationDoc);
const el = document.documentElement.appendChild(createStyle());
const isApplied = el.sheet;
el.remove();
if (isApplied) return;
}
creationDoc = document;
({createElement, createElementNS} = document);
}
function remove(id) {
const style = table.get(id);
if (!style) return;
table.delete(id);
list.splice(list.indexOf(style), 1);
style.el.remove();
}
function sort() {
docRootObserver.evade(() => {
list.sort(compare);
addRemoveElements(true);
});
}
function sortIfNeeded() {
let needsSort;
let el = list.length && list[0].el;
if (!el) {
needsSort = false;
} else if (el.parentNode !== creationDoc.documentElement) {
needsSort = true;
} else {
let i = 0;
while (el) {
if (i < list.length && el === list[i].el) {
i++;
} else if (ORDERED_TAGS.has(el.localName)) {
needsSort = true;
break;
}
el = el.nextElementSibling;
}
// some styles are not injected to the document
if (i < list.length) needsSort = true;
}
if (needsSort) sort();
return needsSort;
}
function styleMapToArray(styleMap) {
if (styleMap.cfg) {
({exposeStyleName} = styleMap.cfg);
delete styleMap.cfg;
}
return Object.values(styleMap).map(({id, code, name}) => ({
id,
name,
code: code.join(''),
}));
}
function update(newStyle) {
const {id, code} = newStyle;
const style = table.get(id);
if (style.code !== code ||
style.name !== newStyle.name && exposeStyleName) {
style.code = code;
setTextAndName(style.el, newStyle);
}
}
function RewriteObserver(onChange) {
// detect documentElement being rewritten from inside the script
let root;
let observing = false;
let timer;
const observer = new MutationObserver(check);
return {start, stop};
function start() {
if (observing) return;
// detect dynamic iframes rewritten after creation by the embedder i.e. externally
root = document.documentElement;
timer = setTimeout(check);
observer.observe(document, {childList: true});
observing = true;
}
function stop() {
if (!observing) return;
clearTimeout(timer);
observer.disconnect();
observing = false;
}
function check() {
if (root !== document.documentElement) {
root = document.documentElement;
onChange();
}
}
}
function RootObserver(onChange) {
let digest = 0;
let lastCalledTime = NaN;
let observing = false;
const observer = new MutationObserver(() => {
if (digest) {
if (performance.now() - lastCalledTime > 1000) {
digest = 0;
} else if (digest > 5) {
throw new Error('The page keeps generating mutations. Skip the event.');
}
}
if (onChange()) {
digest++;
lastCalledTime = performance.now();
}
});
return {evade, start, stop};
function evade(fn) {
const restore = observing && start;
stop();
return new Promise(resolve => run(fn, resolve, waitForRoot))
.then(restore);
}
function start() {
if (observing) return;
observer.observe(document.documentElement, {childList: true});
observing = true;
}
function stop() {
if (!observing) return;
// FIXME: do we need this?
observer.takeRecords();
observer.disconnect();
observing = false;
}
function run(fn, resolve, wait) {
if (document.documentElement) {
resolve(fn());
return true;
}
if (wait) wait(fn, resolve);
}
function waitForRoot(...args) {
new MutationObserver((_, observer) => run(...args) && observer.disconnect())
.observe(document, {childList: true});
}
}
};

521
edit.html
View File

@ -1,122 +1,86 @@
<!DOCTYPE html>
<html id="stylus">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link href="global.css" rel="stylesheet">
<link href="edit/edit.css" rel="stylesheet">
<link rel="stylesheet" href="msgbox/msgbox.css">
<link href="global-dark.css" rel="stylesheet">
<style id="cm-theme"></style>
<style id="firefox-transitions-bug-suppressor">
/* restrict to FF */
@supports (-moz-appearance:none) {
/* increased specificity to override sane selectors in user styles */
html#stylus.firefox #stylus-edit #header *,
html#stylus.firefox #stylus-edit #sections * {
transition: none !important;
}
}
</style>
<script src="js/dom.js"></script>
<script src="js/messaging.js"></script>
<script src="js/polyfill.js"></script>
<script src="js/toolbox.js"></script>
<script src="js/msg.js"></script>
<script src="js/prefs.js"></script>
<script src="js/dom.js"></script>
<script src="js/localization.js"></script>
<script src="js/script-loader.js"></script>
<script src="js/storage-util.js"></script>
<script src="content/style-injector.js"></script>
<script src="content/apply.js"></script>
<script src="edit/util.js"></script>
<script src="edit/regexp-tester.js"></script>
<script src="edit/applies-to-line-widget.js"></script>
<script src="edit/source-editor.js"></script>
<script src="edit/colorpicker-helper.js"></script>
<script src="edit/beautify.js"></script>
<script src="edit/sections.js"></script>
<script src="edit/show-keymap-help.js"></script>
<script src="edit/codemirror-editing-hooks.js"></script>
<script src="edit/edit.js"></script>
<script src="msgbox/msgbox.js" async></script>
<script src="js/sections-util.js"></script>
<script src="js/storage-util.js"></script>
<script src="edit/codemirror-themes.js"></script> <!-- must precede base.js -->
<script src="edit/base.js"></script>
<link href="vendor/codemirror/lib/codemirror.css" rel="stylesheet">
<script src="vendor/codemirror/lib/codemirror.js"></script>
<script src="vendor/codemirror/mode/css/css.js"></script>
<link href="vendor/codemirror/addon/dialog/dialog.css" rel="stylesheet">
<script src="vendor/codemirror/mode/stylus/stylus.js"></script>
<script src="vendor/codemirror/addon/dialog/dialog.js"></script>
<link href="vendor/codemirror/addon/search/matchesonscrollbar.css" rel="stylesheet">
<script src="vendor/codemirror/addon/search/matchesonscrollbar.js"></script>
<script src="vendor/codemirror/addon/edit/closebrackets.js"></script>
<script src="vendor/codemirror/addon/scroll/annotatescrollbar.js"></script>
<script src="vendor/codemirror/addon/search/match-highlighter.js"></script>
<script src="vendor/codemirror/addon/search/searchcursor.js"></script>
<script src="vendor/codemirror/addon/search/matchesonscrollbar.js"></script>
<script src="vendor/codemirror/addon/comment/comment.js"></script>
<script src="vendor/codemirror/addon/selection/active-line.js"></script>
<script src="vendor/codemirror/addon/edit/matchbrackets.js"></script>
<link href="vendor/codemirror/addon/fold/foldgutter.css" rel="stylesheet" />
<script src="vendor/codemirror/addon/fold/foldcode.js"></script>
<script src="vendor/codemirror/addon/fold/foldgutter.js"></script>
<script src="vendor/codemirror/addon/fold/brace-fold.js"></script>
<script src="vendor/codemirror/addon/fold/indent-fold.js"></script>
<script src="vendor/codemirror/addon/fold/comment-fold.js"></script>
<link href="vendor/codemirror/addon/lint/lint.css" rel="stylesheet" />
<script src="vendor/codemirror/addon/lint/lint.js"></script>
<link href="vendor/codemirror/addon/hint/show-hint.css" rel="stylesheet" />
<script src="vendor/codemirror/addon/hint/show-hint.js"></script>
<script src="vendor/codemirror/addon/hint/css-hint.js"></script>
<script src="vendor/codemirror/keymap/sublime.js"></script>
<script src="vendor/codemirror/keymap/emacs.js"></script>
<script src="vendor/codemirror/keymap/sublime.js"></script>
<script src="vendor/codemirror/keymap/vim.js"></script>
<script src="vendor-overwrites/codemirror-addon/match-highlighter.js"></script>
<link href="vendor-overwrites/colorpicker/colorpicker.css" rel="stylesheet">
<script src="vendor-overwrites/colorpicker/colorconverter.js"></script>
<script src="vendor-overwrites/colorpicker/colorpicker.js"></script>
<script src="vendor-overwrites/colorpicker/colorview.js"></script>
<script src="js/color/color-converter.js"></script>
<script src="js/color/color-mimicry.js"></script>
<script src="js/color/color-picker.js"></script>
<script src="js/color/color-view.js"></script>
<script src="js/worker-util.js"></script>
<link href="edit/global-search.css" rel="stylesheet">
<script src="edit/global-search.js"></script>
<script src="edit/match-highlighter-helper.js"></script>
<link href="edit/codemirror-default.css" rel="stylesheet">
<script src="edit/util.js"></script>
<script src="edit/codemirror-default.js"></script>
<script src="edit/linter.js"></script>
<script src="edit/linter-defaults.js"></script>
<script src="edit/linter-engines.js"></script>
<script src="edit/linter-meta.js"></script>
<script src="edit/linter-help-dialog.js"></script>
<script src="edit/linter-report.js"></script>
<script src="edit/linter-config-dialog.js"></script>
<script src="edit/editor-worker.js"></script>
<link id="cm-theme" rel="stylesheet">
<script src="edit/codemirror-factory.js"></script>
<script src="edit/moz-section-finder.js"></script>
<script src="edit/moz-section-widget.js"></script>
<script src="edit/linter-manager.js"></script>
<script src="edit/beautify.js"></script>
<script src="edit/source-editor.js"></script>
<script src="edit/sections-editor-section.js"></script>
<script src="edit/sections-editor.js"></script>
<script src="edit/usw-integration.js"></script>
<template data-id="appliesTo">
<li class="applies-to-item">
<div class="select-resizer">
<select name="applies-type" class="applies-type style-contributor">
<option value="url" i18n-text="appliesUrlOption"></option>
<option value="url-prefix" i18n-text="appliesUrlPrefixOption"></option>
<option value="domain" i18n-text="appliesDomainOption"></option>
<option value="regexp" i18n-text="appliesRegexpOption"></option>
<option value="url" i18n="appliesUrlOption"></option>
<option value="url-prefix" i18n="appliesUrlPrefixOption"></option>
<option value="domain" i18n="appliesDomainOption"></option>
<option value="regexp" i18n="appliesRegexpOption"></option>
</select>
<svg class="svg-icon select-arrow"><use xlink:href="#svg-icon-select-arrow"/></svg>
</div>
<div class="applies-value-wrapper">
<input name="applies-value" class="applies-value style-contributor" spellcheck="false">
<a class="remove-applies-to" href="#" i18n-text="appliesRemove" i18n-title="appliesRemove">
<a class="remove-applies-to" i18n="appliesRemove, title:appliesRemove" tabindex="0">
<svg class="svg-icon remove"><use xlink:href="#svg-icon-minus"/></svg>
</a>
<a class="add-applies-to" href="#" i18n-text="appliesAdd" i18n-title="appliesAdd">
<a class="add-applies-to" i18n="appliesAdd, title:appliesAdd" tabindex="0">
<svg class="svg-icon add"><use xlink:href="#svg-icon-plus"/></svg>
</a>
</div>
@ -124,8 +88,8 @@
</template>
<template data-id="appliesToEverything">
<li class="applies-to-everything" i18n-text="appliesToEverything">
<a class="add-applies-to" i18n-text="appliesAdd" i18n-title="appliesAdd" href="#">
<li class="applies-to-everything" i18n="appliesToEverything">
<a class="add-applies-to" i18n="appliesAdd, title:appliesAdd" tabindex="0">
<svg class="svg-icon add"><use xlink:href="#svg-icon-plus"/></svg>
</a>
</li>
@ -133,61 +97,57 @@
<template data-id="section">
<div class="section">
<label i18n-text="sectionCode" class="code-label"></label>
<br>
<!-- not using DIV to make our CSS work for #sections > div:only-of-type .remove-section -->
<p class="deleted-section">
<button class="restore-section" i18n="sectionRestore"></button>
</p>
<label i18n="sectionCode" class="code-label"></label>
<div class="applies-to">
<label i18n-text="appliesLabel">
<a href="#" class="svg-inline-wrapper applies-to-help" tabindex="0">
<label i18n="appliesLabel, title:appliesHelp" data-cmd="note">
<a class="svg-inline-wrapper applies-to-help" tabindex="0">
<svg class="svg-icon info"><use xlink:href="#svg-icon-help"/></svg>
</a>
</label>
<ul class="applies-to-list"></ul>
</div>
<div class="edit-actions">
<button class="remove-section" i18n-text="sectionRemove"></button>
<button class="add-section" i18n-long-text="sectionAdd" i18n-short-text="genericAdd"></button>
<button class="clone-section" i18n-text="genericClone"></button>
<button class="remove-section" i18n="sectionRemove"></button>
<button class="add-section" i18n="long-text:sectionAdd, short-text:genericAdd"></button>
<button class="clone-section" i18n="genericClone"></button>
<button class="move-section-up"></button>
<button class="move-section-down"></button>
<button class="beautify-section" i18n-text="styleBeautify"></button>
<button class="test-regexp" i18n-text="styleRegexpTestButton"></button>
<button class="beautify-section" i18n="styleBeautify"></button>
<button class="test-regexp" i18n="genericTest"></button>
</div>
</div>
</template>
<!-- not using DIV to make our CSS work for #sections > div:only-of-type .remove-section -->
<template data-id="deletedSection">
<p class="deleted-section">
<button class="restore-section" i18n-text="sectionRestore"></button>
</p>
</template>
<template data-id="searchReplaceDialog">
<div id="search-replace-dialog">
<div data-type="main">
<div data-type="content"></div>
<div data-type="actions">
<a data-action="case" i18n-title="searchCaseSensitive" href="#" tabindex="0">Aa</a>
<a data-action="prev" i18n-title="genericPrevious" href="#" data-hotkey-tooltip="findPrev" tabindex="0">
<a data-action="case" i18n="title:searchCaseSensitive" tabindex="0">Aa</a>
<a data-action="prev" i18n="title:genericPrevious" data-hotkey-tooltip="findPrev" tabindex="0">
<svg class="svg-icon" style="transform: rotate(180deg)"><use xlink:href="#svg-icon-v"/></svg>
</a>
<a data-action="next" i18n-title="genericNext" href="#" data-hotkey-tooltip="findNext" tabindex="0">
<a data-action="next" i18n="title:genericNext" data-hotkey-tooltip="findNext" tabindex="0">
<svg class="svg-icon"><use xlink:href="#svg-icon-v"/></svg>
</a>
<a data-action="close" i18n-title="confirmClose" href="#" data-hotkey-tooltip="=Esc" tabindex="0">
<a data-action="close" i18n="title:confirmClose" data-hotkey-tooltip="=Esc" tabindex="0">
<svg class="svg-icon dismiss"><use xlink:href="#svg-icon-close"/></svg>
</a>
</div>
</div>
<div data-type="status">
<div class="CodeMirror-search-hint" i18n-text="searchRegexp"></div>
<div data-type="tally" i18n-title="searchNumberOfResults"></div>
<div data-type="tally" i18n="title:searchNumberOfResults"></div>
</div>
</div>
</template>
<template data-id="clearSearch">
<div data-type="hover" i18n-title="confirmDelete">
<div data-type="hover" i18n="title:confirmDelete">
<svg data-action="clear" class="svg-icon"><use xlink:href="#svg-icon-close"></use></svg>
</div>
</template>
@ -196,7 +156,7 @@
<div data-type="content">
<div data-type="input-wrapper">
<textarea class="CodeMirror-search-field" rows="1" spellcheck="false" required
i18n-placeholder="search"></textarea>
i18n="placeholder:search"></textarea>
</div>
</div>
</template>
@ -205,36 +165,36 @@
<div data-type="content">
<div data-type="input-wrapper">
<textarea data-type="replace-from"
i18n-placeholder="replace"
i18n="placeholder:replace"
class="CodeMirror-search-field" rows="1" required
spellcheck="false"></textarea>
</div>
<div data-type="input-wrapper">
<textarea data-type="replace-to"
i18n-placeholder="replaceWith"
i18n="placeholder:replaceWith"
class="CodeMirror-search-field" rows="1" required
spellcheck="false"></textarea>
</div>
<button data-action="replace" i18n-text="replace" disabled></button>
<button data-action="replaceAll" i18n-text="replaceAll" disabled></button>
<button data-action="undo" i18n-text="undo" disabled></button>
<button data-action="replace" i18n="replace" disabled></button>
<button data-action="replaceAll" i18n="replaceAll" disabled></button>
<button data-action="undo" i18n="undo" disabled></button>
<!--
Using a separate set of buttons because
1. FF can display tooltips only when specified on the <button>, ignores the nested <title> in <svg>
2. the icon doesn't fill the entire button area so tooltips aren't shown when the edges are hovered
-->
<button class="hidden" data-action="replace" i18n-title="replace" disabled>
<button class="hidden" data-action="replace" i18n="title:replace" disabled>
<svg class="svg-icon" viewBox="0 0 20 20">
<polygon points="15.83 4.75 8.76 11.82 5.2 8.26 3.51 9.95 8.76 15.19 17.52 6.43 15.83 4.75"/>
</svg>
</button>
<button class="hidden" data-action="replaceAll" i18n-title="replaceAll" disabled>
<button class="hidden" data-action="replaceAll" i18n="title:replaceAll" disabled>
<svg class="svg-icon" viewBox="0 0 20 20">
<polygon points="15.8,1.8 8.8,8.8 5.2,5.3 3.5,6.9 8.8,12.2 17.5,3.4 "/>
<polygon points="15.8,7.8 8.8,14.8 5.2,11.3 3.5,12.9 8.8,18.2 17.5,9.4 "/>
</svg>
</button>
<button class="hidden" data-action="undo" i18n-title="undo" disabled>
<button class="hidden" data-action="undo" i18n="title:undo" disabled>
<svg class="svg-icon" viewBox="0 0 20 20">
<path d="M11.3,5.5H8.7V1.4L1.9,6.5l6.8,5.1V7.5h2.6c1.8,0,3.2,1.4,3.2,3.2s-1.4,3.2-3.2,3.2H7.8v2h3.5c2.9,0,5.2-2.3,5.2-5.2S14.2,5.5,11.3,5.5z"/>
</svg>
@ -243,7 +203,7 @@
</template>
<template data-id="jumpToLine">
<span i18n-text="editGotoLine">: <input class="CodeMirror-jump-field" type="text"></span>
<span i18n="editGotoLine">: <input class="CodeMirror-jump-field" type="text"></span>
</template>
<template data-id="regexpTestPartial">
@ -251,15 +211,15 @@
</template>
<template data-id="resizeGrip">
<div class="resize-grip" i18n-title="cm_resizeGripHint"></div>
<div class="resize-grip" i18n="title:cm_resizeGripHint"></div>
</template>
<template data-id="keymapHelp">
<table class="keymap-list">
<thead>
<tr>
<th><input i18n-placeholder="helpKeyMapHotkey" type="search" class="can-close-on-esc"></th>
<th><input i18n-placeholder="helpKeyMapCommand" type="search" class="can-close-on-esc" spellcheck="false"></th>
<th><input i18n="placeholder:helpKeyMapHotkey" type="search"></th>
<th><input i18n="placeholder:helpKeyMapCommand" type="search"></th>
</tr>
</thead>
<tbody>
@ -270,179 +230,225 @@
</tbody>
</table>
</template>
</head>
<body id="stylus-edit" class="truegray-alpha-2">
<link href="vendor/codemirror/lib/codemirror.css" rel="stylesheet">
<link href="vendor/codemirror/addon/dialog/dialog.css" rel="stylesheet">
<link href="vendor/codemirror/addon/fold/foldgutter.css" rel="stylesheet">
<link href="vendor/codemirror/addon/hint/show-hint.css" rel="stylesheet">
<link href="vendor/codemirror/addon/lint/lint.css" rel="stylesheet">
<link href="vendor/codemirror/addon/search/matchesonscrollbar.css" rel="stylesheet">
<link href="js/color/color-picker.css" rel="stylesheet">
<link href="edit/codemirror-default.css" rel="stylesheet">
<template data-id="body"> <!-- https://crbug.com/1288447 -->
<div id="header">
<h1 id="heading">&nbsp;</h1> <!-- nbsp allocates the actual height which prevents page shift -->
<h1 id="heading" i18n="data-edit:editStyleHeading, data-add:addStyleTitle">
<a class="usercss-only"
href="https://github.com/openstyles/stylus/wiki/Usercss"
i18n="title:externalUsercssDocument" target="_blank">
<svg class="svg-icon info"><use xlink:href="#svg-icon-help"/></svg>
</a>
</h1>
<section id="basic-info">
<div id="basic-info-name">
<input id="name" class="style-contributor" spellcheck="false">
<a id="reset-name" i18n="title:customNameResetHint" tabindex="0" hidden>
<svg class="svg-icon" viewBox="0 0 20 20">
<polygon points="16.2,5.5 14.5,3.8 10,8.3 5.5,3.8 3.8,5.5 8.3,10 3.8,14.5
5.5,16.2 10,11.7 14.5,16.2 16.2,14.5 11.7,10 "/>
</svg>
</a>
<a id="url" target="_blank"><svg class="svg-icon"><use xlink:href="#svg-icon-external-link"/></svg></a>
</div>
<div id="basic-info-enabled">
<label id="enabled-label"
i18n-text="styleEnabledLabel"
i18n-title="toggleStyle"
i18n="styleEnabledLabel, title:toggleStyle"
data-hotkey-tooltip="toggleStyle">
<input type="checkbox" id="enabled" class="style-contributor">
<svg class="svg-icon checked"><use xlink:href="#svg-icon-checked"/></svg>
</label>
<label id="preview-label" i18n-text="previewLabel" i18n-title="previewTooltip" class="hidden">
<label id="preview-label" i18n="previewLabel, title:previewTooltip">
<input type="checkbox" id="editor.livePreview">
<svg class="svg-icon checked"><use xlink:href="#svg-icon-checked"/></svg>
</label>
<span id="preview-errors" class="hidden">!</span>
<label id="disableAll-label" i18n="data-on:disableAllStyles, data-off:disableAllStylesOff">
<input id="disableAll" type="checkbox">
<svg class="svg-icon checked"><use xlink:href="#svg-icon-checked"/></svg>
</label>
<span id="preview-errors" hidden>!</span>
</div>
</section>
<section id="actions">
<div>
<button id="save-button" i18n-text="styleSaveLabel" data-hotkey-tooltip="save"></button>
<button id="beautify" i18n-text="styleBeautify"></button>
<a href="manage.html" tabindex="-1"><button id="cancel-button" i18n-text="styleCancelEditLabel"></button></a>
</div>
<div id="mozilla-format-container">
<h2 id="mozilla-format-heading" i18n-text="styleMozillaFormatHeading">
<a id="to-mozilla-help" class="svg-inline-wrapper" href="#" tabindex="0">
<svg class="svg-icon info"><use xlink:href="#svg-icon-help"/></svg>
</a>
</h2>
<div id="mozilla-format-buttons">
<button id="from-mozilla" i18n-text="importLabel"></button>
<button id="to-mozilla" i18n-text="exportLabel"></button>
<div class="buttons">
<div class="split-btn">
<button id="save-button" i18n="styleSaveLabel" data-hotkey-tooltip="save" disabled></button
><button class="split-btn-pedal usercss-only" i18n="menu-tpl:saveAsTemplate"></button>
</div>
<button id="beautify" i18n="styleBeautify"></button>
<button id="style-settings-btn" i18n="settings"></button>
<button id="cancel-button" i18n="title:styleCancelEditLabel"></button>
</div>
<div id="mozilla-format-buttons" class="buttons sectioned-only">
<button id="from-mozilla" i18n="importLabel"></button>
<button id="to-mozilla" i18n="exportLabel"></button>
<a id="to-mozilla-help" class="svg-inline-wrapper" tabindex="0"
i18n="title:styleMozillaFormatHeading">
<svg class="svg-icon info"><use xlink:href="#svg-icon-help"/></svg>
</a>
</div>
</section>
<details id="options" data-pref="editor.options.expanded">
<summary><h2 id="options-heading" i18n-text="optionsHeading"></h2></summary>
<div id="options-wrapper">
<div class="options-column">
<div class="option">
<label id="lineWrapping-label" i18n-text="cm_lineWrapping">
<input id="editor.lineWrapping" type="checkbox">
<svg class="svg-icon checked"><use xlink:href="#svg-icon-checked"/></svg>
</label>
</div>
<div class="option">
<label id="smartIndent-label" i18n-text="cm_smartIndent">
<input id="editor.smartIndent" type="checkbox">
<svg class="svg-icon checked"><use xlink:href="#svg-icon-checked"/></svg>
</label>
</div>
<div class="option">
<label id="indentWithTabs-label" i18n-text="cm_indentWithTabs">
<input id="editor.indentWithTabs" type="checkbox">
<svg class="svg-icon checked"><use xlink:href="#svg-icon-checked"/></svg>
</label>
</div>
<div class="option">
<label i18n-text="cm_autoCloseBrackets" i18n-title="cm_autoCloseBracketsTooltip">
<input id="editor.autoCloseBrackets" type="checkbox">
<svg class="svg-icon checked"><use xlink:href="#svg-icon-checked"/></svg>
</label>
</div>
<div class="option">
<label i18n-text="cm_autocompleteOnTyping">
<input id="editor.autocompleteOnTyping" type="checkbox">
<svg class="svg-icon checked"><use xlink:href="#svg-icon-checked"/></svg>
</label>
</div>
<div class="option">
<label i18n-text="cm_selectByTokens"
i18n-title="cm_selectByTokensTooltip">
<input id="editor.selectByTokens" type="checkbox">
<svg class="svg-icon checked"><use xlink:href="#svg-icon-checked"/></svg>
</label>
</div>
<div class="option">
<label i18n-text="cm_colorpicker">
<input id="editor.colorpicker" type="checkbox">
<svg class="svg-icon checked"><use xlink:href="#svg-icon-checked"/></svg>
</label>
<a id="colorpicker-settings" href="#" class="svg-inline-wrapper" i18n-title="shortcutsNote" tabindex="0">
<svg class="svg-icon settings"><use xlink:href="#svg-icon-settings"/></svg>
</a>
</div>
<div class="option usercss-only">
<label i18n-text="appliesLineWidgetLabel" i18n-title="appliesLineWidgetWarning">
<input id="editor.appliesToLineWidget" type="checkbox">
<svg class="svg-icon checked"><use xlink:href="#svg-icon-checked"/></svg>
</label>
</div>
</div>
<div class="options-column">
<div class="option aligned">
<label id="tabSize-label" for="editor.tabSize" i18n-text="cm_tabSize"></label>
<input id="editor.tabSize" type="number" min="0">
</div>
<div class="option aligned">
<label id="keyMap-label" for="editor.keyMap" i18n-text="cm_keyMap"></label>
<div class="select-resizer">
<select id="editor.keyMap"></select>
<svg class="svg-icon select-arrow"><use xlink:href="#svg-icon-select-arrow"/></svg>
<div id="details-wrapper">
<details id="options" data-pref="editor.options.expanded" class="ignore-pref-if-compact">
<summary><h2 id="options-heading" i18n="editorSettings"></h2></summary>
<div id="options-wrapper">
<div class="options-column">
<div class="option">
<label id="lineWrapping-label" i18n="cm_lineWrapping">
<input id="editor.lineWrapping" type="checkbox">
<svg class="svg-icon checked"><use xlink:href="#svg-icon-checked"/></svg>
</label>
</div>
<a id="keyMap-help" href="#" class="svg-inline-wrapper" tabindex="0">
<svg class="svg-icon info"><use xlink:href="#svg-icon-help"/></svg>
</a>
</div>
<div class="option aligned">
<label id="theme-label" for="editor.theme" i18n-text="cm_theme"></label>
<div class="select-resizer">
<select id="editor.theme"></select>
<svg class="svg-icon select-arrow"><use xlink:href="#svg-icon-select-arrow"/></svg>
<div class="option">
<label id="smartIndent-label" i18n="cm_smartIndent">
<input id="editor.smartIndent" type="checkbox">
<svg class="svg-icon checked"><use xlink:href="#svg-icon-checked"/></svg>
</label>
</div>
<div class="option">
<label id="indentWithTabs-label" i18n="cm_indentWithTabs">
<input id="editor.indentWithTabs" type="checkbox">
<svg class="svg-icon checked"><use xlink:href="#svg-icon-checked"/></svg>
</label>
</div>
<div class="option">
<label i18n="cm_autoCloseBrackets, title:cm_autoCloseBracketsTooltip">
<input id="editor.autoCloseBrackets" type="checkbox">
<svg class="svg-icon checked"><use xlink:href="#svg-icon-checked"/></svg>
</label>
</div>
<div class="option">
<label i18n="cm_autocompleteOnTyping">
<input id="editor.autocompleteOnTyping" type="checkbox">
<svg class="svg-icon checked"><use xlink:href="#svg-icon-checked"/></svg>
</label>
</div>
<div class="option">
<label i18n="cm_selectByTokens, title:cm_selectByTokensTooltip">
<input id="editor.selectByTokens" type="checkbox">
<svg class="svg-icon checked"><use xlink:href="#svg-icon-checked"/></svg>
</label>
</div>
<div class="option sectioned-only">
<label i18n="cm_arrowKeysTraverse">
<input id="editor.arrowKeysTraverse" type="checkbox">
<svg class="svg-icon checked"><use xlink:href="#svg-icon-checked"/></svg>
</label>
</div>
<div class="option">
<label i18n="cm_colorpicker">
<input id="editor.colorpicker" type="checkbox">
<svg class="svg-icon checked"><use xlink:href="#svg-icon-checked"/></svg>
</label>
<a id="colorpicker-settings" class="svg-inline-wrapper" i18n="title:shortcutsNote" tabindex="0">
<svg class="svg-icon settings"><use xlink:href="#svg-icon-config"/></svg>
</a>
</div>
<div class="option usercss-only">
<label i18n="appliesLineWidgetLabel, title:appliesLineWidgetWarning">
<input id="editor.appliesToLineWidget" type="checkbox">
<svg class="svg-icon checked"><use xlink:href="#svg-icon-checked"/></svg>
</label>
</div>
</div>
<div class="option aligned">
<label id="highlight-label" for="editor.matchHighlight" i18n-text="cm_matchHighlight"></label>
<div class="select-resizer">
<select id="editor.matchHighlight">
<option i18n-text="cm_matchHighlightToken" value="token">
<option i18n-text="cm_matchHighlightSelection" value="selection">
<option i18n-text="genericDisabledLabel" value="">
</select>
<svg class="svg-icon select-arrow"><use xlink:href="#svg-icon-select-arrow"/></svg>
<div class="options-column">
<div class="option aligned">
<label id="tabSize-label" for="editor.tabSize" i18n="cm_tabSize"></label>
<input id="editor.tabSize" type="number" min="0">
</div>
</div>
<div class="option aligned">
<label id="linter-label" for="editor.linter" i18n-text="cm_linter"></label>
<div class="option aligned">
<label id="keyMap-label" for="editor.keyMap" i18n="cm_keyMap"></label>
<div class="select-resizer">
<select id="editor.linter">
<option value="csslint" selected>CSSLint</option>
<option value="stylelint">Stylelint</option>
<option value="" i18n-text="genericDisabledLabel"></option>
<select id="editor.keyMap"></select>
<svg class="svg-icon select-arrow"><use xlink:href="#svg-icon-select-arrow"/></svg>
</div>
<a id="keyMap-help" class="svg-inline-wrapper" tabindex="0">
<svg class="svg-icon info"><use xlink:href="#svg-icon-help"/></svg>
</a>
</div>
<div class="option aligned">
<label id="theme-label" for="editor.theme" i18n="cm_theme"></label>
<div class="select-resizer">
<select id="editor.theme"></select>
<svg class="svg-icon select-arrow"><use xlink:href="#svg-icon-select-arrow"/></svg>
</div>
</div>
<div class="option aligned">
<label id="highlight-label" for="editor.matchHighlight" i18n="cm_matchHighlight"></label>
<div class="select-resizer">
<select id="editor.matchHighlight">
<option i18n="cm_matchHighlightToken" value="token">
<option i18n="cm_matchHighlightSelection" value="selection">
<option i18n="genericDisabledLabel" value="">
</select>
<svg class="svg-icon select-arrow"><use xlink:href="#svg-icon-select-arrow"/></svg>
</div>
<a id="linter-settings" href="#" class="svg-inline-wrapper" i18n-title="linterConfigTooltip" tabindex="0">
<svg class="svg-icon settings"><use xlink:href="#svg-icon-settings"/></svg>
</a>
</div>
<div class="option aligned">
<label id="linter-label" for="editor.linter" i18n="cm_linter"></label>
<div class="select-resizer">
<select id="editor.linter">
<option value="csslint" selected>CSSLint</option>
<option value="stylelint">Stylelint</option>
<option value="" i18n="genericDisabledLabel"></option>
</select>
<svg class="svg-icon select-arrow"><use xlink:href="#svg-icon-select-arrow"/></svg>
</div>
<a id="linter-settings" class="svg-inline-wrapper" i18n="title:linterConfigTooltip" tabindex="0">
<svg class="svg-icon settings"><use xlink:href="#svg-icon-config"/></svg>
</a>
</div>
</div>
</div>
</div>
</details>
<details id="lint" class="hidden" data-pref="editor.lint.expanded">
<summary>
<h2 i18n-text="linterIssues">: <span id="issue-count"></span>
<a id="lint-help" href="#" class="svg-inline-wrapper intercepts-click" tabindex="0">
<svg class="svg-icon info"><use xlink:href="#svg-icon-help"/></svg>
</a>
</h2>
</summary>
<div class="lint-report-container"></div>
</details>
</details>
<details id="publish" data-pref="editor.publish.expanded" class="ignore-pref-if-compact">
<summary><h2 i18n="publish"></h2></summary>
<div>
<a id="usw-url" href="https://userstyles.world" target="_blank">&nbsp;</a>
<div id="usw-link-info">
<dl><dt i18n="styleName"></dt><dd data-usw="name"></dd></dl>
<dl><dt i18n="genericDescription"></dt><dd data-usw="description"></dd></dl>
</div>
<div>
<button id="usw-publish-style"
i18n="data-publish:publishStyle, data-push:publishPush"></button>
<button id="usw-disconnect" i18n="optionsSyncDisconnect"></button>
<span id="usw-progress"></span>
</div>
</div>
</details>
<details id="sections-list" data-pref="editor.toc.expanded" class="ignore-pref-if-compact">
<summary><h2 i18n="sections"></h2></summary>
<ol id="toc"></ol>
</details>
<details id="lint" data-pref="editor.lint.expanded" class="ignore-pref-if-compact" hidden>
<summary>
<h2><span i18n="linterIssues"></span><span id="issue-count"></span>
<a id="lint-help" class="svg-inline-wrapper intercepts-click" tabindex="0">
<svg class="svg-icon info"><use xlink:href="#svg-icon-help"/></svg>
</a>
</h2>
</summary>
<div class="lint-report-container"></div>
</details>
</div>
<div id="header-resizer" i18n="title:headerResizerHint"></div>
<div id="footer" class="hidden">
<a href="https://github.com/openstyles/stylus/wiki/Usercss"
i18n-text="externalUsercssDocument"
i18n="externalUsercssDocument"
target="_blank"></a>
</div>
</div>
<section id="sections">
<h2><span id="sections-heading" i18n-text="styleSectionsTitle"></span>
<a id="sections-help" href="#" class="svg-inline-wrapper" tabindex="0">
<svg class="svg-icon info"><use xlink:href="#svg-icon-help"/></svg>
</a>
</h2>
</section>
<section id="sections"></section>
<div id="help-popup">
<div class="title"></div><svg id="sections-help" class="svg-icon dismiss"><use xlink:href="#svg-icon-close"/></svg>
<div class="contents"></div>
@ -454,8 +460,10 @@
<path d="M0 0v8h8v-2h-1v1h-6v-6h1v-1h-2zm4 0l1.5 1.5-2.5 2.5 1 1 2.5-2.5 1.5 1.5v-4h-4z"></path>
</symbol>
<symbol id="svg-icon-help" viewBox="0 0 14 16" i18n-alt="helpAlt">
<path fill-rule="evenodd" d="M6.3 5.69a.942.942 0 0 1-.28-.7c0-.28.09-.52.28-.7.19-.18.42-.28.7-.28.28 0 .52.09.7.28.18.19.28.42.28.7 0 .28-.09.52-.28.7a1 1 0 0 1-.7.3c-.28 0-.52-.11-.7-.3zM8 7.99c-.02-.25-.11-.48-.31-.69-.2-.19-.42-.3-.69-.31H6c-.27.02-.48.13-.69.31-.2.2-.3.44-.31.69h1v3c.02.27.11.5.31.69.2.2.42.31.69.31h1c.27 0 .48-.11.69-.31.2-.19.3-.42.31-.69H8V7.98v.01zM7 2.3c-3.14 0-5.7 2.54-5.7 5.68 0 3.14 2.56 5.7 5.7 5.7s5.7-2.55 5.7-5.7c0-3.15-2.56-5.69-5.7-5.69v.01zM7 .98c3.86 0 7 3.14 7 7s-3.14 7-7 7-7-3.12-7-7 3.14-7 7-7z"></path>
<symbol id="svg-icon-help" viewBox="0 0 14 16" i18n="alt:helpAlt">
<circle cx="7" cy="5" r="1"/>
<path d="M8,8c0-0.5-0.5-1-1-1H6C5.5,7,5,7.4,5,8h1v3c0,0.5,0.5,1,1,1h1c0.5,0,1-0.4,1-1H8V8z"/>
<path d="M7,1c3.9,0,7,3.1,7,7s-3.1,7-7,7s-7-3.1-7-7S3.1,1,7,1z M7,2.3C3.9,2.3,1.3,4.9,1.3,8s2.6,5.7,5.7,5.7s5.7-2.6,5.7-5.7S10.1,2.3,7,2.3C7,2.3,7,2.3,7,2.3z"/>
</symbol>
<symbol id="svg-icon-close" viewBox="0 0 12 16">
@ -466,8 +474,8 @@
<path d="M8,11.5L2.8,6.3l1.5-1.5L8,8.6l3.7-3.7l1.5,1.5L8,11.5z"/>
</symbol>
<symbol id="svg-icon-settings" viewBox="0 0 16 16">
<path d="M8,0C7.6,0,7.3,0,6.9,0.1v2.2C6.1,2.5,5.4,2.8,4.8,3.2L3.2,1.6c-0.6,0.4-1.1,1-1.6,1.6l1.6,1.6C2.8,5.4,2.5,6.1,2.3,6.9H0.1C0,7.3,0,7.6,0,8c0,0.4,0,0.7,0.1,1.1h2.2c0.1,0.8,0.4,1.5,0.9,2.1l-1.6,1.6c0.4,0.6,1,1.1,1.6,1.6l1.6-1.6c0.6,0.4,1.4,0.7,2.1,0.9v2.2C7.3,16,7.6,16,8,16c0.4,0,0.7,0,1.1-0.1v-2.2c0.8-0.1,1.5-0.4,2.1-0.9l1.6,1.6c0.6-0.4,1.1-1,1.6-1.6l-1.6-1.6c0.4-0.6,0.7-1.4,0.9-2.1h2.2C16,8.7,16,8.4,16,8c0-0.4,0-0.7-0.1-1.1h-2.2c-0.1-0.8-0.4-1.5-0.9-2.1l1.6-1.6c-0.4-0.6-1-1.1-1.6-1.6l-1.6,1.6c-0.6-0.4-1.4-0.7-2.1-0.9V0.1C8.7,0,8.4,0,8,0z M8,4.3c2.1,0,3.7,1.7,3.7,3.7c0,0,0,0,0,0c0,2.1-1.7,3.7-3.7,3.7c0,0,0,0,0,0c-2.1,0-3.7-1.7-3.7-3.7c0,0,0,0,0,0C4.3,5.9,5.9,4.3,8,4.3C8,4.3,8,4.3,8,4.3z"/>
<symbol id="svg-icon-config" viewBox="0 0 16 16">
<path d="M13.3,12.8l1.5-2.6l-2.2-1.5c0-0.2,0.1-0.5,0.1-0.7c0-0.2,0-0.5-0.1-0.7l2.2-1.5l-1.5-2.6l-2.4,1.2 c-0.4-0.3-0.8-0.5-1.2-0.7L9.5,1h-3L6.3,3.7C5.9,3.8,5.5,4.1,5.1,4.4L2.7,3.2L1.2,5.8l2.2,1.5c0,0.2-0.1,0.5-0.1,0.7 c0,0.2,0,0.5,0.1,0.7l-2.2,1.5l1.5,2.6l2.4-1.2c0.4,0.3,0.8,0.5,1.2,0.7L6.5,15h3l0.2-2.7c0.4-0.2,0.8-0.4,1.2-0.7L13.3,12.8z M8,10.3c-1.3,0-2.3-1-2.3-2.3c0-1.3,1-2.3,2.3-2.3c1.3,0,2.3,1,2.3,2.3C10.3,9.3,9.3,10.3,8,10.3z"/>
</symbol>
<symbol id="svg-icon-select-arrow" viewBox="0 0 1792 1792">
@ -479,14 +487,21 @@
</symbol>
<symbol id="svg-icon-plus" viewBox="0 0 8 8">
<path fill-rule="evenodd" d="M3 0v3h-3v2h3v3h2v-3h3v-2h-3v-3h-2z"/>
<path d="M3 0v3h-3v2h3v3h2v-3h3v-2h-3v-3h-2z"/>
</symbol>
<symbol id="svg-icon-minus" viewBox="0 0 8 8">
<path fill-rule="evenodd" d="M0 3v2h8v-2h-8z"/>
<path d="M0 3v2h8v-2h-8z"/>
</symbol>
</svg>
</template>
<link href="edit/edit.css" rel="stylesheet">
<script src="js/dark-themer.js"></script> <!-- must be last in HEAD to avoid FOUC -->
</head>
<body id="stylus-edit">
<script src="edit/edit.js"></script>
</body>
</html>

View File

@ -1,545 +0,0 @@
/* global regExpTester debounce messageBox CodeMirror template colorMimicry */
'use strict';
function createAppliesToLineWidget(cm) {
const THROTTLE_DELAY = 400;
let TPL, EVENTS, CLICK_ROUTE;
let widgets = [];
let fromLine, toLine, actualStyle;
let initialized = false;
return {toggle};
function toggle(newState = !initialized) {
newState = Boolean(newState);
if (newState !== initialized) {
if (newState) {
init();
} else {
uninit();
}
}
}
function init() {
initialized = true;
TPL = {
container:
$create('div.applies-to', [
$create('label', t('appliesLabel')),
$create('ul.applies-to-list'),
]),
listItem: template.appliesTo.cloneNode(true),
appliesToEverything:
$create('li.applies-to-everything', t('appliesToEverything')),
};
$('.applies-value', TPL.listItem).insertAdjacentElement('afterend',
$create('button.test-regexp', t('styleRegexpTestButton')));
CLICK_ROUTE = {
'.test-regexp': showRegExpTester,
'.remove-applies-to': (item, apply, event) => {
event.preventDefault();
const applies = item.closest('.applies-to').__applies;
const i = applies.indexOf(apply);
let repl;
let from;
let to;
if (applies.length < 2) {
messageBox({
contents: t('appliesRemoveError'),
buttons: [t('confirmClose')]
});
return;
}
if (i === 0) {
from = apply.mark.find().from;
to = applies[i + 1].mark.find().from;
repl = '';
} else if (i === applies.length - 1) {
from = applies[i - 1].mark.find().to;
to = apply.mark.find().to;
repl = '';
} else {
from = applies[i - 1].mark.find().to;
to = applies[i + 1].mark.find().from;
repl = ', ';
}
cm.replaceRange(repl, from, to, 'appliesTo');
clearApply(apply);
item.remove();
applies.splice(i, 1);
},
'.add-applies-to': (item, apply, event) => {
event.preventDefault();
const applies = item.closest('.applies-to').__applies;
const i = applies.indexOf(apply);
const pos = apply.mark.find().to;
const text = `, ${apply.type.text}("")`;
cm.replaceRange(text, pos, pos, 'appliesTo');
const newApply = createApply(
cm.indexFromPos(pos) + 2,
apply.type.text,
'',
true
);
setupApplyMarkers(newApply);
applies.splice(i + 1, 0, newApply);
item.insertAdjacentElement('afterend', buildChildren(applies, newApply));
},
};
EVENTS = {
onchange({target}) {
const typeElement = target.closest('.applies-type');
if (typeElement) {
const item = target.closest('.applies-to-item');
const apply = item.__apply;
changeItem(item, apply, 'type', typeElement.value);
item.dataset.type = apply.type.text;
} else {
return EVENTS.oninput.apply(this, arguments);
}
},
oninput({target}) {
if (target.matches('.applies-value')) {
const item = target.closest('.applies-to-item');
const apply = item.__apply;
changeItem(item, apply, 'value', target.value);
}
},
onclick(event) {
const {target} = event;
for (const selector in CLICK_ROUTE) {
const routed = target.closest(selector);
if (routed) {
const item = routed.closest('.applies-to-item');
CLICK_ROUTE[selector].call(routed, item, item.__apply, event);
return;
}
}
}
};
actualStyle = $create('style');
fromLine = 0;
toLine = cm.doc.size;
cm.on('change', onChange);
cm.on('optionChange', onOptionChange);
chrome.runtime.onMessage.addListener(onRuntimeMessage);
requestAnimationFrame(updateWidgetStyle);
update();
}
function uninit() {
initialized = false;
widgets.forEach(clearWidget);
widgets.length = 0;
cm.off('change', onChange);
cm.off('optionChange', onOptionChange);
chrome.runtime.onMessage.removeListener(onRuntimeMessage);
actualStyle.remove();
}
function onChange(cm, event) {
const {from, to, origin} = event;
if (origin === 'appliesTo') {
return;
}
const lastChanged = CodeMirror.changeEnd(event).line;
fromLine = Math.min(fromLine === null ? from.line : fromLine, from.line);
toLine = Math.max(toLine === null ? lastChanged : toLine, to.line);
if (origin === 'setValue') {
update();
} else {
debounce(update, THROTTLE_DELAY);
}
}
function onOptionChange(cm, option) {
if (option === 'theme') {
updateWidgetStyle();
}
}
function onRuntimeMessage(msg) {
if (msg.style || msg.styles ||
msg.prefs && 'disableAll' in msg.prefs ||
msg.method === 'styleDeleted') {
requestAnimationFrame(updateWidgetStyle);
}
}
function update() {
const changed = {fromLine, toLine};
fromLine = Math.max(fromLine || 0, cm.display.viewFrom);
toLine = Math.min(toLine === null ? cm.doc.size : toLine, cm.display.viewTo || toLine);
const visible = {fromLine, toLine};
const {curOp} = cm;
if (fromLine >= cm.display.viewFrom && toLine <= (cm.display.viewTo || toLine)) {
if (!curOp) cm.startOperation();
doUpdate();
if (!curOp) cm.endOperation();
}
if (changed.fromLine !== visible.fromLine || changed.toLine !== visible.toLine) {
setTimeout(updateInvisible, 0, changed, visible);
}
}
function updateInvisible(changed, visible) {
let inOp = false;
if (changed.fromLine < visible.fromLine) {
fromLine = Math.min(fromLine, changed.fromLine);
toLine = Math.min(changed.toLine, visible.fromLine);
inOp = true;
cm.startOperation();
doUpdate();
}
if (changed.toLine > visible.toLine) {
fromLine = Math.max(fromLine, changed.toLine);
toLine = Math.max(changed.toLine, visible.toLine);
if (!inOp) {
inOp = true;
cm.startOperation();
}
doUpdate();
}
if (inOp) {
cm.endOperation();
}
}
function updateWidgetStyle() {
if (prefs.get('editor.theme') !== 'default' &&
!tryCatch(() => $('#cm-theme').sheet.cssRules)) {
requestAnimationFrame(updateWidgetStyle);
return;
}
if (prefs.get('editor.theme') !== 'default') {
const MIN_LUMA = .05;
const MIN_LUMA_DIFF = .4;
const color = {
wrapper: colorMimicry.get(cm.display.wrapper),
gutter: colorMimicry.get(cm.display.gutters, {
bg: 'backgroundColor',
border: 'borderRightColor',
}),
line: colorMimicry.get('.CodeMirror-linenumber', null, cm.display.lineDiv),
comment: colorMimicry.get('span.cm-comment', null, cm.display.lineDiv),
};
const hasBorder =
color.gutter.style.borderRightWidth !== '0px' &&
!/transparent|\b0\)/g.test(color.gutter.style.borderRightColor);
const diff = {
wrapper: Math.abs(color.gutter.bgLuma - color.wrapper.foreLuma),
border: hasBorder ? Math.abs(color.gutter.bgLuma - color.gutter.borderLuma) : 0,
line: Math.abs(color.gutter.bgLuma - color.line.foreLuma),
};
const preferLine = diff.line > diff.wrapper || diff.line > MIN_LUMA_DIFF;
const fore = preferLine ? color.line.fore : color.wrapper.fore;
const border = fore.replace(/[\d.]+(?=\))/, MIN_LUMA_DIFF / 2);
const borderStyleForced = `1px ${hasBorder ? color.gutter.style.borderRightStyle : 'solid'} ${border}`;
actualStyle.textContent = `
.applies-to {
background-color: ${color.gutter.bg};
border-top: ${borderStyleForced};
border-bottom: ${borderStyleForced};
}
.applies-to label {
color: ${fore};
}
.applies-to input,
.applies-to select {
background-color: rgba(255, 255, 255, ${
Math.max(MIN_LUMA, Math.pow(Math.max(0, color.gutter.bgLuma - MIN_LUMA * 2), 2)).toFixed(2)
});
border: ${borderStyleForced};
transition: none;
color: ${fore};
}
.applies-to .svg-icon.select-arrow {
fill: ${fore} !important;
}
.applies-to select option {
background-color: ${color.gutter.bg};
}
`;
document.documentElement.appendChild(actualStyle);
} else if (prefs.get('editor.theme') === 'default') {
actualStyle.textContent = '';
}
}
function doUpdate() {
// find which widgets needs to be update
// some widgets (lines) might be deleted
widgets = widgets.filter(w => w.line.lineNo() !== null);
let i = widgets.findIndex(w => w.line.lineNo() > fromLine) - 1;
let j = widgets.findIndex(w => w.line.lineNo() > toLine);
if (i === -2) {
i = widgets.length - 1;
}
if (j < 0) {
j = widgets.length;
}
// decide search range
const fromPos = {line: widgets[i] ? widgets[i].line.lineNo() : 0, ch: 0};
const toPos = {line: widgets[j] ? widgets[j].line.lineNo() : toLine + 1, ch: 0};
// calc index->pos lookup table
let line = 0;
let index = 0;
let fromIndex, toIndex;
const lineIndexes = [index];
cm.doc.iter(({text}) => {
fromIndex = line === fromPos.line ? index : fromIndex;
lineIndexes.push((index += text.length + 1));
line++;
toIndex = line >= toPos.line ? index : toIndex;
return toIndex;
});
// splice
i = Math.max(0, i);
widgets.splice(i, 0, ...createWidgets(fromIndex, toIndex, widgets.splice(i, j - i), lineIndexes));
fromLine = null;
toLine = null;
}
function *createWidgets(start, end, removed, lineIndexes) {
let i = 0;
let itemHeight;
for (const section of findAppliesTo(start, end)) {
let removedWidget = removed[i];
while (removedWidget && removedWidget.line.lineNo() < section.pos.line) {
clearWidget(removed[i]);
removedWidget = removed[++i];
}
for (const a of section.applies) {
setupApplyMarkers(a, lineIndexes);
}
if (removedWidget && removedWidget.line.lineNo() === section.pos.line) {
// reuse old widget
removedWidget.section.applies.forEach(apply => {
apply.type.mark.clear();
apply.value.mark.clear();
});
removedWidget.section = section;
const newNode = buildElement(section);
const removedNode = removedWidget.node;
if (removedNode.parentNode) {
removedNode.parentNode.replaceChild(newNode, removedNode);
}
removedWidget.node = newNode;
removedWidget.changed();
yield removedWidget;
i++;
continue;
}
// new widget
const widget = cm.addLineWidget(section.pos.line, buildElement(section), {
coverGutter: true,
noHScroll: true,
above: true,
height: itemHeight ? section.applies.length * itemHeight : undefined,
});
widget.section = section;
itemHeight = itemHeight || widget.node.offsetHeight / (section.applies.length || 1);
yield widget;
}
removed.slice(i).forEach(clearWidget);
}
function clearWidget(widget) {
widget.clear();
widget.section.applies.forEach(clearApply);
}
function clearApply(apply) {
apply.type.mark.clear();
apply.value.mark.clear();
apply.mark.clear();
}
function setupApplyMarkers(apply, lineIndexes) {
apply.type.mark = cm.markText(
posFromIndex(cm, apply.type.start, lineIndexes),
posFromIndex(cm, apply.type.end, lineIndexes),
{clearWhenEmpty: false}
);
apply.value.mark = cm.markText(
posFromIndex(cm, apply.value.start, lineIndexes),
posFromIndex(cm, apply.value.end, lineIndexes),
{clearWhenEmpty: false}
);
apply.mark = cm.markText(
posFromIndex(cm, apply.start, lineIndexes),
posFromIndex(cm, apply.end, lineIndexes),
{clearWhenEmpty: false}
);
}
function posFromIndex(cm, index, lineIndexes) {
if (!lineIndexes) {
return cm.posFromIndex(index);
}
let line = lineIndexes.prev || 0;
const prev = lineIndexes[line];
const next = lineIndexes[line + 1];
if (prev <= index && index < next) {
return {line, ch: index - prev};
}
let a = index < prev ? 0 : line;
let b = index < next ? line + 1 : lineIndexes.length - 1;
while (a < b - 1) {
const mid = (a + b) >> 1;
if (lineIndexes[mid] < index) {
a = mid;
} else {
b = mid;
}
}
line = lineIndexes[b] > index ? a : b;
Object.defineProperty(lineIndexes, 'prev', {value: line, configurable: true});
return {line, ch: index - lineIndexes[line]};
}
function buildElement({applies}) {
const container = TPL.container.cloneNode(true);
const list = $('.applies-to-list', container);
for (const apply of applies) {
list.appendChild(buildChildren(applies, apply));
}
if (!list.children[0]) {
list.appendChild(TPL.appliesToEverything.cloneNode(true));
}
return Object.assign(container, EVENTS, {__applies: applies});
}
function buildChildren(applies, apply) {
const el = TPL.listItem.cloneNode(true);
el.dataset.type = apply.type.text;
el.__apply = apply;
$('.applies-type', el).value = apply.type.text;
$('.applies-value', el).value = apply.value.text;
return el;
}
function changeItem(itemElement, apply, part, newText) {
if (!apply) {
return;
}
part = apply[part];
const range = part.mark.find();
part.mark.clear();
newText = unescapeDoubleslash(newText).replace(/\\/g, '\\\\');
cm.replaceRange(newText, range.from, range.to, 'appliesTo');
part.mark = cm.markText(
range.from,
cm.findPosH(range.from, newText.length, 'char'),
{clearWhenEmpty: false}
);
part.text = newText;
if (part === apply.type) {
const range = apply.mark.find();
apply.mark.clear();
apply.mark = cm.markText(
part.mark.find().from,
range.to,
{clearWhenEmpty: false}
);
}
if (apply.type.text === 'regexp' && apply.value.text.trim()) {
showRegExpTester(itemElement);
}
}
function createApply(pos, typeText, valueText, isQuoted = false) {
typeText = typeText.toLowerCase();
const start = pos;
const typeStart = start;
const typeEnd = typeStart + typeText.length;
const valueStart = typeEnd + 1 + Number(isQuoted);
const valueEnd = valueStart + valueText.length;
const end = valueEnd + Number(isQuoted) + 1;
return {
start,
type: {
text: typeText,
start: typeStart,
end: typeEnd,
},
value: {
text: unescapeDoubleslash(valueText),
start: valueStart,
end: valueEnd,
},
end
};
}
function *findAppliesTo(posStart, posEnd) {
const text = cm.getValue();
const re = /^[\t ]*@-moz-document[\s\n]+/gm;
const applyRe = new RegExp([
/(?:\/\*[\s\S]*?(?:\*\/\s*|$))*/,
/(url|url-prefix|domain|regexp)/,
/\(((['"])(?:\\\\|\\\n|\\\3|[^\n])*?\3|[^)\n]*)\)\s*(,\s*)?/,
].map(rx => rx.source).join(''), 'giy');
let match;
re.lastIndex = posStart;
while ((match = re.exec(text))) {
if (match.index >= posEnd) {
return;
}
const applies = [];
let m;
applyRe.lastIndex = re.lastIndex;
while ((m = applyRe.exec(text))) {
const apply = createApply(
m.index,
m[1],
unquote(m[2]),
unquote(m[2]) !== m[2]
);
applies.push(apply);
re.lastIndex = applyRe.lastIndex;
}
yield {
pos: cm.posFromIndex(match.index),
applies
};
}
}
function unquote(s) {
const first = s.charAt(0);
return (first === '"' || first === "'") && s.endsWith(first) ? s.slice(1, -1) : s;
}
function unescapeDoubleslash(s) {
const hasSingleEscapes = /([^\\]|^)\\([^\\]|$)/.test(s);
return hasSingleEscapes ? s : s.replace(/\\\\/g, '\\');
}
function showRegExpTester(item) {
regExpTester.toggle(true);
regExpTester.update(
item.closest('.applies-to').__applies
.filter(a => a.type.text === 'regexp')
.map(a => unescapeDoubleslash(a.value.text)));
}
}

274
edit/autocomplete.js Normal file
View File

@ -0,0 +1,274 @@
/* global CodeMirror */
/* global cmFactory */
/* global debounce */// toolbox.js
/* global editor */
/* global linterMan */
/* global prefs */
'use strict';
/* Registers 'hint' helper and 'autocompleteOnTyping' option in CodeMirror */
(() => {
const USO_VAR = 'uso-variable';
const USO_VALID_VAR = 'variable-3 ' + USO_VAR;
const USO_INVALID_VAR = 'error ' + USO_VAR;
const rxPROP = /^(prop(erty)?|variable-2|string-2)\b/;
const rxVAR = /(^|[^-.\w\u0080-\uFFFF])var\(/iyu;
const rxCONSUME = /([-\w]*\s*:\s?)?/yu;
const cssMime = CodeMirror.mimeModes['text/css'];
const docFuncs = addSuffix(cssMime.documentTypes, '(');
const {tokenHooks} = cssMime;
const originalCommentHook = tokenHooks['/'];
const originalHelper = CodeMirror.hint.css || (() => {});
let cssMedia, cssProps, cssValues;
const AOT_ID = 'autocompleteOnTyping';
const AOT_PREF_ID = 'editor.' + AOT_ID;
const aot = prefs.get(AOT_PREF_ID);
CodeMirror.defineOption(AOT_ID, aot, (cm, value) => {
cm[value ? 'on' : 'off']('changes', autocompleteOnTyping);
cm[value ? 'on' : 'off']('pick', autocompletePicked);
});
prefs.subscribe(AOT_PREF_ID, (key, val) => cmFactory.globalSetOption(AOT_ID, val), {runNow: aot});
CodeMirror.registerHelper('hint', 'css', helper);
CodeMirror.registerHelper('hint', 'stylus', helper);
tokenHooks['/'] = tokenizeUsoVariables;
async function helper(cm) {
const pos = cm.getCursor();
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') {
return originalHelper(cm);
}
// not using getTokenAt until the need is unavoidable because it reparses text
// and runs a whole lot of complex calc inside which is slow on long lines
// especially if autocomplete is auto-shown on each keystroke
let prev, end, state;
let i = index;
while (
(prev == null || `${styles[i - 1]}`.startsWith(type)) &&
(prev = i > 2 ? styles[i - 2] : 0) &&
isSameToken(text, style, prev)
) i -= 2;
i = index;
while (
(end == null || `${styles[i + 1]}`.startsWith(type)) &&
(end = styles[i]) &&
isSameToken(text, style, end)
) i += 2;
const getTokenState = () => state || (state = cm.getTokenAt(pos, true).state.state);
const str = text.slice(prev, end);
const left = text.slice(prev, ch).trim();
let leftLC = left.toLowerCase();
let list;
switch (leftLC[0]) {
case '!':
list = '!important'.startsWith(leftLC) ? ['!important'] : [];
break;
case '@':
list = [
'@-moz-document',
'@charset',
'@font-face',
'@import',
'@keyframes',
'@media',
'@namespace',
'@page',
'@supports',
'@viewport',
];
if (isLessLang) list = findAllCssVars(cm, left, '\\s*:').concat(list);
break;
case '#': // prevents autocomplete for #hex colors
break;
case '-': // --variable
case '(': // var(
list = str.startsWith('--') || testAt(rxVAR, ch - 5, text)
? findAllCssVars(cm, left)
: [];
if (str.startsWith('(')) {
prev++;
leftLC = left.slice(1);
} else {
leftLC = left;
}
break;
case '/': // USO vars
if (str.startsWith('/*[[') && str.endsWith(']]*/')) {
prev += 4;
end -= 4;
end -= text.slice(end - 4, end) === '-rgb' ? 4 : 0;
list = Object.keys((editor.style.usercssData || {}).vars || {}).sort();
leftLC = left.slice(4);
}
break;
case 'u': // url(), url-prefix()
case 'd': // domain()
case 'r': // regexp()
if (/^(variable|tag|error)/.test(type) &&
docFuncs.some(s => s.startsWith(leftLC)) &&
/^(top|documentTypes|atBlock)/.test(getTokenState())) {
end++;
list = docFuncs;
break;
}
// fallthrough to `default`
default:
// property values
if (isStylusLang || getTokenState() === 'prop') {
while (i > 0 && !rxPROP.test(styles[i + 1])) i -= 2;
const propEnd = styles[i];
let prop;
if (propEnd > text.lastIndexOf(';', ch - 1)) {
while (i > 0 && rxPROP.test(styles[i + 1])) i -= 2;
prop = text.slice(styles[i] || 0, propEnd).match(/([-\w]+)?$/u)[1];
}
if (prop) {
if (/[^-\w]/.test(leftLC)) {
prev += execAt(/[\s:()]*/y, prev, text)[0].length;
leftLC = leftLC.replace(/^[^\w\s]\s*/, '');
}
if (prop.startsWith('--')) prop = 'color'; // assuming 90% of variables are colors
if (!cssProps) await initCssProps();
list = [...new Set([...cssValues.all[prop] || [], ...cssValues.global])];
end = prev + execAt(/(\s*[-a-z(]+)?/y, prev, text)[0].length;
}
}
// properties and media features
if (!list &&
/^(prop(erty|\?)|atom|error|tag)/.test(type) &&
/^(block|atBlock_parens|maybeprop)/.test(getTokenState())) {
if (!cssProps) await initCssProps();
if (type === 'prop?') {
prev += leftLC.length;
leftLC = '';
}
list = state === 'atBlock_parens' ? cssMedia : cssProps;
end -= /\W$/u.test(str); // e.g. don't consume ) when inside ()
end += execAt(rxCONSUME, end, text)[0].length;
}
if (!list) {
return isStylusLang
? CodeMirror.hint.fromList(cm, {words: CodeMirror.hintWords.stylus})
: originalHelper(cm);
}
}
return {
list: (list || []).filter(s => s.startsWith(leftLC)),
from: {line, ch: prev + str.match(/^\s*/)[0].length},
to: {line, ch: end},
};
}
async function initCssProps() {
cssValues = await linterMan.worker.getCssPropsValues();
cssProps = addSuffix(cssValues.all);
cssMedia = [].concat(...Object.entries(cssMime).map(getMediaKeys).filter(Boolean)).sort();
}
function addSuffix(obj, suffix = ': ') {
// Sorting first, otherwise "foo-bar:" would precede "foo:"
return Object.keys(obj).sort().map(k => k + suffix);
}
function getMediaKeys([k, v]) {
return k === 'mediaFeatures' && addSuffix(v) ||
k.startsWith('media') && Object.keys(v);
}
/** makes sure we don't process a different adjacent comment */
function isSameToken(text, style, i) {
return !style || text[i] !== '/' && text[i + 1] !== '*' ||
!style.startsWith(USO_VALID_VAR) && !style.startsWith(USO_INVALID_VAR);
}
function findAllCssVars(cm, leftPart, rightPart = '') {
// simplified regex without CSS escapes
const [, prefixed, named] = leftPart.match(/^(--|@)?(\S)?/);
const rx = new RegExp(
'(?:^|[\\s/;{])(' +
(prefixed ? leftPart : '--') +
(named ? '' : '[a-zA-Z_\u0080-\uFFFF]') +
'[-0-9a-zA-Z_\u0080-\uFFFF]*)' +
rightPart,
'g');
const list = new Set();
cm.eachLine(({text}) => {
for (let m; (m = rx.exec(text));) {
list.add(m[1]);
}
});
return [...list].sort();
}
function tokenizeUsoVariables(stream) {
const token = originalCommentHook.apply(this, arguments);
if (token[1] === 'comment') {
const {string, start, pos} = stream;
if (testAt(/\/\*\[\[/y, start, string) &&
testAt(/]]\*\//y, pos - 4, string)) {
const vars = (editor.style.usercssData || {}).vars;
token[0] =
vars && vars.hasOwnProperty(string.slice(start + 4, pos - 4).replace(/-rgb$/, ''))
? USO_VALID_VAR
: USO_INVALID_VAR;
}
}
return token;
}
function execAt(rx, index, text) {
rx.lastIndex = index;
return rx.exec(text);
}
function testAt(rx, index, text) {
rx.lastIndex = Math.max(0, index);
return rx.test(text);
}
function autocompleteOnTyping(cm, [info], debounced) {
const lastLine = info.text[info.text.length - 1];
if (cm.state.completionActive ||
info.origin && !info.origin.includes('input') ||
!lastLine) {
return;
}
if (cm.state.autocompletePicked) {
cm.state.autocompletePicked = false;
return;
}
if (!debounced) {
debounce(autocompleteOnTyping, 100, cm, [info], true);
return;
}
if (lastLine.match(/[-a-z!]+$/i)) {
cm.state.autocompletePicked = false;
cm.options.hintOptions.completeSingle = false;
cm.execCommand('autocomplete');
setTimeout(() => {
cm.options.hintOptions.completeSingle = true;
});
}
}
function autocompletePicked(cm) {
cm.state.autocompletePicked = true;
}
})();

427
edit/base.js Normal file
View File

@ -0,0 +1,427 @@
/* global $$ $ $create messageBoxProxy setInputValue setupLivePrefs */// dom.js
/* global API */// msg.js
/* global CODEMIRROR_THEMES */
/* global CodeMirror */
/* global MozDocMapper */// sections-util.js
/* global chromeSync */// storage-util.js
/* global initBeautifyButton */// beautify.js
/* global prefs */
/* global t */// localization.js
/* global FIREFOX getOwnTab sessionStore tryJSONparse tryURL */// toolbox.js
'use strict';
/**
* @type Editor
* @namespace Editor
*/
const editor = {
style: null,
dirty: DirtyReporter(),
isUsercss: false,
isWindowed: false,
livePreview: LivePreview(),
/** @type {'customName'|'name'} */
nameTarget: 'name',
previewDelay: 200, // Chrome devtools uses 200
saving: false,
scrollInfo: null,
cancel: () => location.assign('/manage.html'),
updateClass() {
$.rootCL.toggle('is-new-style', !editor.style.id);
},
updateTheme(name) {
if (!CODEMIRROR_THEMES[name]) {
name = 'default';
prefs.set('editor.theme', name);
}
$('#cm-theme').dataset.theme = name;
$('#cm-theme').textContent = CODEMIRROR_THEMES[name] || '';
},
updateTitle(isDirty = editor.dirty.isDirty()) {
const {customName, name} = editor.style;
document.title = `${
isDirty ? '* ' : ''
}${
customName || name || t('styleMissingName')
} - Stylus`; // the suffix enables external utilities to process our windows e.g. pin on top
},
};
//#region pre-init
(() => {
const mqCompact = matchMedia('(max-width: 850px)');
const toggleCompact = mq => $.rootCL.toggle('compact-layout', mq.matches);
mqCompact.on('change', toggleCompact);
toggleCompact(mqCompact);
Object.assign(editor, /** @namespace Editor */ {
mqCompact,
styleReady: prefs.ready.then(loadStyle),
});
async function loadStyle() {
const params = new URLSearchParams(location.search);
let id = Number(params.get('id'));
const style = id && await API.styles.get(id) || {
id: id = null, // resetting the non-existent id
name: params.get('domain') ||
tryURL(params.get('url-prefix')).hostname ||
'',
enabled: true,
sections: [
MozDocMapper.toSection([...params], {code: ''}),
],
};
// switching the mode here to show the correct page ASAP, usually before DOMContentLoaded
const isUC = Boolean(style.usercssData || !id && prefs.get('newStyleAsUsercss'));
Object.assign(editor, /** @namespace Editor */ {
style,
isUsercss: isUC,
template: isUC && !id && chromeSync.getLZValue(chromeSync.LZ_KEY.usercssTemplate), // promise
});
editor.updateClass();
editor.updateTheme(prefs.get('editor.theme'));
editor.updateTitle(false);
$.rootCL.add(isUC ? 'usercss' : 'sectioned');
sessionStore.justEditedStyleId = id || '';
// no such style so let's clear the invalid URL parameters
if (!id) history.replaceState({}, '', location.pathname);
}
})();
//#endregion
//#region init header
/* exported EditorHeader */
function EditorHeader() {
initBeautifyButton($('#beautify'));
initKeymapElement();
initNameArea();
initThemeElement();
setupLivePrefs();
window.on('load', () => {
prefs.subscribe('editor.keyMap', showHotkeyInTooltip, {runNow: true});
window.on('showHotkeyInTooltip', showHotkeyInTooltip);
}, {once: true});
function findKeyForCommand(command, map) {
if (typeof map === 'string') map = CodeMirror.keyMap[map];
let key = Object.keys(map).find(k => map[k] === command);
if (key) {
return key;
}
for (const ft of Array.isArray(map.fallthrough) ? map.fallthrough : [map.fallthrough]) {
key = ft && findKeyForCommand(command, ft);
if (key) {
return key;
}
}
return '';
}
function initNameArea() {
const nameEl = $('#name');
const resetEl = $('#reset-name');
const isCustomName = editor.style.updateUrl || editor.isUsercss;
editor.nameTarget = isCustomName ? 'customName' : 'name';
nameEl.placeholder = t(editor.isUsercss ? 'usercssEditorNamePlaceholder' : 'styleMissingName');
nameEl.title = isCustomName ? t('customNameHint') : '';
nameEl.on('input', () => {
editor.updateName(true);
resetEl.hidden = !editor.style.customName;
});
resetEl.hidden = !editor.style.customName;
resetEl.onclick = () => {
editor.style.customName = null; // to delete it from db
setInputValue(nameEl, editor.style.name);
resetEl.hidden = true;
};
const enabledEl = $('#enabled');
enabledEl.onchange = () => editor.updateEnabledness(enabledEl.checked);
}
function initThemeElement() {
$('#editor.theme').append(...[
$create('option', {value: 'default'}, t('defaultTheme')),
...Object.keys(CODEMIRROR_THEMES).map(s => $create('option', s)),
]);
// move the theme after built-in CSS so that its same-specificity selectors win
document.head.appendChild($('#cm-theme'));
}
function initKeymapElement() {
// move 'pc' or 'mac' prefix to the end of the displayed label
const maps = Object.keys(CodeMirror.keyMap)
.map(name => ({
value: name,
name: name.replace(/^(pc|mac)(.+)/, (s, arch, baseName) =>
baseName.toLowerCase() + '-' + (arch === 'mac' ? 'Mac' : 'PC')),
}))
.sort((a, b) => a.name < b.name && -1 || a.name > b.name && 1);
const fragment = document.createDocumentFragment();
let bin = fragment;
let groupName;
// group suffixed maps in <optgroup>
maps.forEach(({value, name}, i) => {
groupName = !name.includes('-') ? name : groupName;
const groupWithNext = maps[i + 1] && maps[i + 1].name.startsWith(groupName);
if (groupWithNext) {
if (bin === fragment) {
bin = fragment.appendChild($create('optgroup', {label: name.split('-')[0]}));
}
}
const el = bin.appendChild($create('option', {value}, name));
if (value === prefs.defaults['editor.keyMap']) {
el.dataset.default = '';
el.title = t('defaultTheme');
}
if (!groupWithNext) bin = fragment;
});
const selector = $('#editor.keyMap');
selector.textContent = '';
selector.appendChild(fragment);
selector.value = prefs.get('editor.keyMap');
}
function showHotkeyInTooltip(_, mapName = prefs.get('editor.keyMap')) {
const extraKeys = CodeMirror.defaults.extraKeys;
for (const el of $$('[data-hotkey-tooltip]')) {
if (el._hotkeyTooltipKeyMap !== mapName) {
el._hotkeyTooltipKeyMap = mapName;
const title = el._hotkeyTooltipTitle = el._hotkeyTooltipTitle || el.title;
const cmd = el.dataset.hotkeyTooltip;
const key = cmd[0] === '=' ? cmd.slice(1) :
findKeyForCommand(cmd, mapName) ||
extraKeys && findKeyForCommand(cmd, extraKeys);
const newTitle = title + (title && key ? '\n' : '') + (key || '');
if (el.title !== newTitle) el.title = newTitle;
}
}
}
}
//#endregion
//#region init windowed mode
(() => {
let ownTabId;
if (chrome.windows) {
initWindowedMode();
const pos = tryJSONparse(sessionStore.windowPos);
delete sessionStore.windowPos;
// resize the window on 'undo close'
if (pos && pos.left != null) {
chrome.windows.update(chrome.windows.WINDOW_ID_CURRENT, pos);
}
}
getOwnTab().then(tab => {
ownTabId = tab.id;
if (sessionStore['manageStylesHistory' + ownTabId] === location.href) {
editor.cancel = () => history.back();
}
});
async function initWindowedMode() {
chrome.tabs.onAttached.addListener(onTabAttached);
// Chrome 96+ bug: the type is 'app' for a window that was restored via Ctrl-Shift-T
const isSimple = ['app', 'popup'].includes((await browser.windows.getCurrent()).type);
if (isSimple) require(['/edit/embedded-popup']);
editor.isWindowed = isSimple || (
history.length === 1 &&
await prefs.ready && prefs.get('openEditInWindow') &&
(await browser.windows.getAll()).length > 1 &&
(await browser.tabs.query({currentWindow: true})).length === 1
);
}
async function onTabAttached(tabId, info) {
if (tabId !== ownTabId) {
return;
}
if (info.newPosition !== 0) {
prefs.set('openEditInWindow', false);
return;
}
const win = await browser.windows.get(info.newWindowId, {populate: true});
// If there's only one tab in this window, it's been dragged to new window
const openEditInWindow = win.tabs.length === 1;
// FF-only because Chrome retardedly resets the size during dragging
if (openEditInWindow && FIREFOX) {
chrome.windows.update(info.newWindowId, prefs.get('windowPosition'));
}
prefs.set('openEditInWindow', openEditInWindow);
}
})();
//#endregion
//#region internals
/** @returns DirtyReporter */
function DirtyReporter() {
const data = new Map();
const listeners = new Set();
const dataListeners = new Set();
const notifyChange = wasDirty => {
const isDirty = data.size > 0;
const flipped = isDirty !== wasDirty;
if (flipped) {
listeners.forEach(cb => cb(isDirty));
}
if (flipped || isDirty) {
dataListeners.forEach(cb => cb(isDirty));
}
};
/** @namespace DirtyReporter */
return {
add(obj, value) {
const wasDirty = data.size > 0;
const saved = data.get(obj);
if (!saved) {
data.set(obj, {type: 'add', newValue: value});
} else if (saved.type === 'remove') {
if (saved.savedValue === value) {
data.delete(obj);
} else {
saved.newValue = value;
saved.type = 'modify';
}
} else {
return;
}
notifyChange(wasDirty);
},
clear(...objs) {
if (data.size && (
objs.length
? objs.map(data.delete, data).includes(true)
: (data.clear(), true)
)) {
notifyChange(true);
}
},
has(key) {
return data.has(key);
},
isDirty() {
return data.size > 0;
},
modify(obj, oldValue, newValue) {
const wasDirty = data.size > 0;
const saved = data.get(obj);
if (!saved) {
if (oldValue !== newValue) {
data.set(obj, {type: 'modify', savedValue: oldValue, newValue});
} else {
return;
}
} else if (saved.type === 'modify') {
if (saved.savedValue === newValue) {
data.delete(obj);
} else {
saved.newValue = newValue;
}
} else if (saved.type === 'add') {
saved.newValue = newValue;
} else {
return;
}
notifyChange(wasDirty);
},
onChange(cb, add = true) {
listeners[add ? 'add' : 'delete'](cb);
},
onDataChange(cb, add = true) {
dataListeners[add ? 'add' : 'delete'](cb);
},
remove(obj, value) {
const wasDirty = data.size > 0;
const saved = data.get(obj);
if (!saved) {
data.set(obj, {type: 'remove', savedValue: value});
} else if (saved.type === 'add') {
data.delete(obj);
} else if (saved.type === 'modify') {
saved.type = 'remove';
} else {
return;
}
notifyChange(wasDirty);
},
};
}
function LivePreview() {
let el;
let data;
let port;
let preprocess;
let enabled = prefs.get('editor.livePreview');
prefs.subscribe('editor.livePreview', (key, value) => {
if (!value) {
if (port) {
port.disconnect();
port = null;
}
} else if (data && data.id && (data.enabled || editor.dirty.has('enabled'))) {
createPreviewer();
updatePreviewer(data);
}
enabled = value;
});
return {
/**
* @param {Function} [fn] - preprocessor
*/
init(fn) {
preprocess = fn;
},
update(newData) {
data = newData;
if (!port) {
if (!data.id || !data.enabled || !enabled) {
return;
}
createPreviewer();
}
updatePreviewer(data);
},
};
function createPreviewer() {
port = chrome.runtime.connect({name: 'livePreview'});
port.onDisconnect.addListener(err => {
throw err;
});
el = $('#preview-errors');
el.onclick = () => messageBoxProxy.alert(el.title, 'pre');
}
async function updatePreviewer(data) {
try {
port.postMessage(preprocess ? await preprocess(data) : data);
el.hidden = true;
} catch (err) {
if (Array.isArray(err)) {
err = err.map(e => e.message || e).join('\n');
} else if (err && err.index != null) {
// FIXME: this would fail if editors[0].getValue() !== data.sourceCode
const pos = editor.getEditors()[0].posFromIndex(err.index);
err.message = `${pos.line}:${pos.ch} ${err.message || err}`;
}
el.title = err.message || `${err}`;
el.hidden = false;
}
}
}
//#endregion

View File

@ -1,140 +1,174 @@
/*
global CodeMirror loadScript css_beautify
global editors getSectionForChild showHelp
*/
/* global $ $create moveFocus */// dom.js
/* global CodeMirror */
/* global createHotkeyInput helpPopup */// util.js
/* global editor */
/* global prefs */
/* global t */// localization.js
'use strict';
function beautify(event) {
loadScript('/vendor-overwrites/beautify/beautify-css-mod.js')
.then(() => {
if (!window.css_beautify && window.exports) {
window.css_beautify = window.exports.css_beautify;
}
})
.then(doBeautify);
CodeMirror.commands.beautify = cm => {
// using per-section mode when code editor or applies-to block is focused
const isPerSection = cm.display.wrapper.parentElement.contains(document.activeElement);
beautify(isPerSection ? [cm] : editor.getEditors(), false);
};
function doBeautify() {
const tabs = prefs.get('editor.indentWithTabs');
const options = prefs.get('editor.beautify');
for (const k of Object.keys(prefs.defaults['editor.beautify'])) {
if (!(k in options)) options[k] = prefs.defaults['editor.beautify'][k];
prefs.subscribe('editor.beautify.hotkey', (key, value) => {
const {extraKeys} = CodeMirror.defaults;
for (const [key, cmd] of Object.entries(extraKeys)) {
if (cmd === 'beautify') {
delete extraKeys[key];
break;
}
options.indent_size = tabs ? 1 : prefs.get('editor.tabSize');
options.indent_char = tabs ? '\t' : ' ';
}
if (value) {
extraKeys[value] = 'beautify';
}
}, {runNow: true});
const section = getSectionForChild(event.target);
const scope = section ? [section.CodeMirror] : editors;
/**
* @name beautify
* @param {CodeMirror[]} scope
* @param {boolean} [ui=true]
*/
async function beautify(scope, ui = true) {
await require(['/vendor-overwrites/beautify/beautify-css-mod']); /* global css_beautify */
const tabs = prefs.get('editor.indentWithTabs');
const options = Object.assign(prefs.defaults['editor.beautify'], prefs.get('editor.beautify'));
options.indent_size = tabs ? 1 : prefs.get('editor.tabSize');
options.indent_char = tabs ? '\t' : ' ';
if (ui) {
createBeautifyUI(scope, options);
}
for (const cm of scope) {
setTimeout(beautifyEditor, 0, cm, options, ui);
}
}
showHelp(t('styleBeautify'),
$create([
$create('.beautify-options', [
$createOption('.selector1,', 'selector_separator_newline'),
$createOption('.selector2', 'newline_before_open_brace'),
$createOption('{', 'newline_after_open_brace'),
$createOption('border: none;', 'newline_between_properties', true),
$createOption('display: block;', 'newline_before_close_brace', true),
$createOption('}', 'newline_between_rules'),
$createLabeledCheckbox('preserve_newlines', 'styleBeautifyPreserveNewlines'),
$createLabeledCheckbox('indent_conditional', 'styleBeautifyIndentConditional'),
]),
$create('.buttons', [
$create('button', {
attributes: {role: 'close'},
// showHelp.close will be defined after showHelp() is invoked
onclick: () => showHelp.close(),
}, t('confirmClose')),
$create('button', {
attributes: {role: 'undo'},
onclick() {
let undoable = false;
for (const cm of scope) {
const data = cm.beautifyChange;
if (!data || !data[cm.changeGeneration()]) continue;
delete data[cm.changeGeneration()];
const {scrollX, scrollY} = window;
cm.undo();
cm.scrollIntoView(cm.getCursor());
window.scrollTo(scrollX, scrollY);
undoable |= data[cm.changeGeneration()];
}
this.disabled = !undoable;
},
}, t(scope.length === 1 ? 'undo' : 'undoGlobal')),
]),
]));
$('#help-popup').className = 'main-bg wide';
scope.forEach(cm => {
setTimeout(() => {
const pos = options.translate_positions =
[].concat.apply([], cm.doc.sel.ranges.map(r =>
[Object.assign({}, r.anchor), Object.assign({}, r.head)]));
const text = cm.getValue();
const newText = css_beautify(text, options);
if (newText !== text) {
if (!cm.beautifyChange || !cm.beautifyChange[cm.changeGeneration()]) {
// clear the list if last change wasn't a css-beautify
cm.beautifyChange = {};
}
cm.setValue(newText);
const selections = [];
for (let i = 0; i < pos.length; i += 2) {
selections.push({anchor: pos[i], head: pos[i + 1]});
}
const {scrollX, scrollY} = window;
cm.setSelections(selections);
window.scrollTo(scrollX, scrollY);
cm.beautifyChange[cm.changeGeneration()] = true;
$('#help-popup button[role="close"]').disabled = false;
}
});
});
$('.beautify-options').onchange = ({target}) => {
const value = target.type === 'checkbox' ? target.checked : target.selectedIndex > 0;
prefs.set('editor.beautify', Object.assign(options, {[target.dataset.option]: value}));
if (target.parentNode.hasAttribute('newline')) {
target.parentNode.setAttribute('newline', value.toString());
}
doBeautify();
};
function $createOption(label, optionName, indent) {
const value = options[optionName];
return (
$create('div', {attributes: {newline: value}}, [
$create('span', indent ? {attributes: {indent: ''}} : {}, label),
$create('div.select-resizer', [
$create('select', {dataset: {option: optionName}}, [
$create('option', {selected: !value}, '\xA0'),
$create('option', {selected: value}, '\\n'),
]),
$create('SVG:svg.svg-icon.select-arrow', {viewBox: '0 0 1792 1792'}, [
$create('SVG:path', {
'fill-rule': 'evenodd',
'd': 'M1408 704q0 26-19 45l-448 448q-19 19-45 ' +
'19t-45-19l-448-448q-19-19-19-45t19-45 45-19h896q26 0 45 19t19 45z'
}),
]),
]),
])
);
function beautifyEditor(cm, options, ui) {
const pos = options.translate_positions =
[].concat.apply([], cm.doc.sel.ranges.map(r =>
[Object.assign({}, r.anchor), Object.assign({}, r.head)]));
const text = cm.getValue();
const newText = css_beautify(text, options);
if (newText !== text) {
if (!cm.beautifyChange || !cm.beautifyChange[cm.changeGeneration()]) {
// clear the list if last change wasn't a css-beautify
cm.beautifyChange = {};
}
function $createLabeledCheckbox(optionName, i18nKey) {
return (
$create('label', {style: 'display: block; clear: both;'}, [
$create('input', {
type: 'checkbox',
dataset: {option: optionName},
checked: options[optionName] !== false
}),
$create('SVG:svg.svg-icon.checked',
$create('SVG:use', {'xlink:href': '#svg-icon-checked'})),
t(i18nKey),
])
);
cm.setValue(newText);
const selections = [];
for (let i = 0; i < pos.length; i += 2) {
selections.push({anchor: pos[i], head: pos[i + 1]});
}
const {scrollX, scrollY} = window;
cm.setSelections(selections);
window.scrollTo(scrollX, scrollY);
cm.beautifyChange[cm.changeGeneration()] = true;
if (ui) {
$('button[role="close"]', helpPopup.div).disabled = false;
}
}
}
function createBeautifyUI(scope, options) {
helpPopup.show(t('styleBeautify'),
$create([
$create('.beautify-options', [
$createOption('.selector1,', 'selector_separator_newline'),
$createOption('.selector2', 'newline_before_open_brace'),
$createOption('{', 'newline_after_open_brace'),
$createOption('border: none;', 'newline_between_properties', true),
$createOption('display: block;', 'newline_before_close_brace', true),
$createOption('}', 'newline_between_rules'),
$createLabeledCheckbox('preserve_newlines', 'styleBeautifyPreserveNewlines'),
$createLabeledCheckbox('indent_conditional', 'styleBeautifyIndentConditional'),
editor.isUsercss && $createLabeledCheckbox('indent_mozdoc', '', '... @-moz-document'),
]),
$create('p.beautify-hint', [
$create('span', t('styleBeautifyHint') + '\u00A0'),
createHotkeyInput('editor.beautify.hotkey', {
buttons: false,
onDone: () => moveFocus(helpPopup.div, 0),
}),
]),
$create('.buttons', [
$create('button', {
attributes: {role: 'close'},
onclick: helpPopup.close,
}, t('confirmClose')),
$create('button', {
attributes: {role: 'undo'},
onclick() {
let undoable = false;
for (const cm of scope) {
const data = cm.beautifyChange;
if (!data || !data[cm.changeGeneration()]) continue;
delete data[cm.changeGeneration()];
const {scrollX, scrollY} = window;
cm.undo();
cm.scrollIntoView(cm.getCursor());
window.scrollTo(scrollX, scrollY);
undoable |= data[cm.changeGeneration()];
}
this.disabled = !undoable;
},
}, t(scope.length === 1 ? 'undo' : 'undoGlobal')),
]),
]),
{
className: 'wide',
});
$('.beautify-options').onchange = ({target}) => {
const value = target.type === 'checkbox' ? target.checked : target.selectedIndex > 0;
const elLine = target.closest('[newline]');
if (elLine) elLine.setAttribute('newline', value);
prefs.set('editor.beautify', Object.assign(options, {[target.dataset.option]: value}));
beautify(scope, false);
};
function $createOption(label, optionName, indent) {
const value = options[optionName];
return (
$create('div', {attributes: {newline: value}}, [
$create('span', indent ? {attributes: {indent: ''}} : {}, label),
$create('div.select-resizer', [
$create('select', {dataset: {option: optionName}}, [
$create('option', {selected: !value}, '\xA0'),
$create('option', {selected: value}, '\\n'),
]),
$create('SVG:svg.svg-icon.select-arrow', {viewBox: '0 0 1792 1792'}, [
$create('SVG:path', {
'fill-rule': 'evenodd',
'd': 'M1408 704q0 26-19 45l-448 448q-19 19-45 ' +
'19t-45-19l-448-448q-19-19-19-45t19-45 45-19h896q26 0 45 19t19 45z',
}),
]),
]),
])
);
}
function $createLabeledCheckbox(optionName, i18nKey, text) {
return (
$create('label', {style: 'display: block; clear: both;'}, [
$create('input', {
type: 'checkbox',
dataset: {option: optionName},
checked: options[optionName] !== false,
}),
$create('SVG:svg.svg-icon.checked',
$create('SVG:use', {'xlink:href': '#svg-icon-checked'})),
i18nKey ? t(i18nKey) : text,
])
);
}
}
/* exported initBeautifyButton */
function initBeautifyButton(btn, scope) {
btn.onclick = btn.oncontextmenu = e => {
e.preventDefault();
beautify(scope || editor.getEditors(), e.type === 'click');
};
}

View File

@ -1,54 +1,33 @@
:root {
--applies-to-pseudo: hsla(214, 100%, 90%, 0.15);
}
/************ CM default ************/
.CodeMirror.cm-s-default {
background: var(--gray-lightness-93);
}
.CodeMirror {
outline-style: solid;
outline-color: transparent;
outline-width: 1px;
outline-offset: -1px;
transition: outline-color .25s;
}
.CodeMirror-focused {
outline-color: var(--focus-outline);
}
.CodeMirror.cm-s-default .CodeMirror-gutters {
background-color: var(--truegray-alpha-1);
border-right: 1px solid var(--truegray-alpha-2);
}
.CodeMirror.cm-s-default .CodeMirror-activeline-background {
background: var(--cm-activeline-bg);
}
.CodeMirror-hints.default {
background-color: var(--main-bg);
}
/* Built-in CodeMirror and addon customization */
.CodeMirror-hints {
z-index: 999;
}
/* match Windows select hover, so no variables */
.CodeMirror-hint:hover {
color: #fff;
color: var(--bg);
background: #08f;
}
.CodeMirror {
border: 1px solid var(--gray-lightness-76);
border: solid var(--c80) 1px;
transition: box-shadow .1s;
}
.CodeMirror-lint-mark-warning {
background: none;
.CodeMirror {
color: inherit;
background-color: inherit;
border: solid var(--c80) 1px;
transition: box-shadow .1s;
}
.CodeMirror-gutters {
background-color: var(--c95);
border-color: var(--c85);
}
#stylus#stylus .CodeMirror {
/* Using a specificity hack to override userstyles */
/* Not using the ring-color hack as it became ugly in new Chrome */
outline: none !important;
}
.CodeMirror-dialog {
-webkit-animation: highlight 3s cubic-bezier(.18, .02, 0, .94);
animation: highlight 3s cubic-bezier(.18, .02, 0, .94);
}
.CodeMirror-search-field {
width: 10em;
@ -57,14 +36,10 @@
width: 5em;
}
.CodeMirror-search-hint {
color: var(--truegray);
color: var(--c50);
}
.cm-uso-variable {
font-weight: bold;
}
.CodeMirror-activeline .applies-to:before {
background-color: var(--applies-to-pseudo);
background-color: hsla(214, 100%, 90%, 0.15);
content: "";
top: 1em;
left: 0;
@ -73,11 +48,9 @@
position: absolute;
pointer-events: none;
}
.CodeMirror-activeline .applies-to ul {
z-index: 2;
}
.CodeMirror-foldgutter-open::after,
.CodeMirror-foldgutter-folded::after {
top: 5px;
@ -89,15 +62,87 @@
opacity: .5;
left: 1px;
}
.CodeMirror-foldgutter-open::after {
border-width: 5px 3px 0 3px;
border-color: currentColor transparent transparent transparent;
}
.CodeMirror-foldgutter-folded::after {
margin-top: -2px;
margin-left: 1px;
border-width: 4px 0 4px 5px;
border-color: transparent transparent transparent currentColor;
}
.CodeMirror-linenumber {
cursor: pointer; /* for bookmarking */
}
.cm-matchhighlight,
.CodeMirror-selection-highlight-scrollbar {
background: hsla(200, 100%, 50%, var(--match-hl-opacity, .1));
}
/* Custom stuff we add to CodeMirror */
.cm-uso-variable {
font-weight: bold;
}
.gutter-bookmark {
background: linear-gradient(0deg, hsla(180, 100%, 30%, .75) 2px, hsla(180, 100%, 30%, .2) 2px);
}
@media screen and (prefers-color-scheme: dark), dark {
.CodeMirror {
--match-hl-opacity: .18;
}
.CodeMirror-dialog {
background-color: #333;
}
.CodeMirror-dialog-top {
border-color: #555;
}
.CodeMirror-activeline-background {
background: hsl(180, 21%, 18%);
}
.CodeMirror-selected,
.CodeMirror-focused .CodeMirror-selected,
.CodeMirror-line::selection,
.CodeMirror-line > span::selection,
.CodeMirror-line > span > span::selection {
background: #444;
}
.CodeMirror-line::-moz-selection,
.CodeMirror-line > span::-moz-selection,
.CodeMirror-line > span > span::-moz-selection {
/* TODO: remove this when strict_min_version >= 62 */
background: #444;
}
.cm-s-default div.CodeMirror-cursor {
border-left: 1px solid #fff;
}
/* Using Chromium's dark devtools colors */
.cm-s-default .cm-atom,
.cm-s-default .cm-number { color: #a1f7b5 }
.cm-s-default .cm-attribute { color: #6194c6 }
.cm-s-default .cm-bracket { color: #997 }
.cm-s-default .cm-builtin,
.cm-s-default .cm-link { color: #9fb4d6 }
.cm-s-default .cm-comment { color: #747474 }
.cm-s-default .cm-qualifier { color: #ffa34f }
.cm-s-default .cm-def,
.cm-s-default .cm-header,
.cm-s-default .cm-tag,
.cm-s-default .cm-type { color: #5db0d7 }
.cm-s-default .cm-hr { color: #999 }
.cm-s-default .cm-keyword { color: #9a7fd5 }
.cm-s-default .cm-meta { color: #ddfb55 }
.cm-s-default .cm-operator { color: #d2c057 }
.cm-s-default .cm-string { color: #f28b54 }
.cm-s-default .cm-variable { color: #d9d9d9 }
.cm-s-default .cm-variable-2 { color: #72b9ff }
.cm-s-default .cm-variable-3 { color: #9bbbdc }
@keyframes highlight {
from {
background-color: #888;
}
}
}

View File

@ -1,14 +1,19 @@
/* global CodeMirror prefs loadScript editor editors */
/* global $ */// dom.js
/* global CodeMirror */
/* global UA */// toolbox.js
/* global editor */
/* global prefs */
/* global t */// localization.js
'use strict';
(function () {
(() => {
// CodeMirror miserably fails on keyMap='' so let's ensure it's not
if (!prefs.get('editor.keyMap')) {
prefs.reset('editor.keyMap');
}
const defaults = {
autoCloseBrackets: prefs.get('editor.autoCloseBrackets'),
mode: 'css',
lineNumbers: true,
lineWrapping: prefs.get('editor.lineWrapping'),
@ -19,14 +24,13 @@
...(prefs.get('editor.linter') ? ['CodeMirror-lint-markers'] : []),
],
matchBrackets: true,
highlightSelectionMatches: {showToken: /[#.\-\w]/, annotateScrollbar: true},
hintOptions: {},
lintReportDelay: prefs.get('editor.lintReportDelay'),
styleActiveLine: true,
theme: 'default',
styleActiveLine: {nonEmpty: true},
theme: prefs.get('editor.theme'),
keyMap: prefs.get('editor.keyMap'),
extraKeys: Object.assign(CodeMirror.defaults.extraKeys || {}, {
// independent of current keyMap
// independent of current keyMap; some are implemented only for the edit page
'Alt-Enter': 'toggleStyle',
'Alt-PageDown': 'nextEditor',
'Alt-PageUp': 'prevEditor',
@ -37,381 +41,108 @@
Object.assign(CodeMirror.defaults, defaults, prefs.get('editor.options'));
// 'basic' keymap only has basic keys by design, so we skip it
const extraKeysCommands = {};
Object.keys(CodeMirror.defaults.extraKeys).forEach(key => {
extraKeysCommands[CodeMirror.defaults.extraKeys[key]] = true;
});
if (!extraKeysCommands.jumpToLine) {
CodeMirror.keyMap.sublime['Ctrl-G'] = 'jumpToLine';
CodeMirror.keyMap.emacsy['Ctrl-G'] = 'jumpToLine';
CodeMirror.keyMap.pcDefault['Ctrl-J'] = 'jumpToLine';
CodeMirror.keyMap.macDefault['Cmd-J'] = 'jumpToLine';
}
if (!extraKeysCommands.autocomplete) {
// will be used by 'sublime' on PC via fallthrough
CodeMirror.keyMap.pcDefault['Ctrl-Space'] = 'autocomplete';
// OSX uses Ctrl-Space and Cmd-Space for something else
CodeMirror.keyMap.macDefault['Alt-Space'] = 'autocomplete';
// copied from 'emacs' keymap
CodeMirror.keyMap.emacsy['Alt-/'] = 'autocomplete';
// 'vim' and 'emacs' define their own autocomplete hotkeys
}
if (!extraKeysCommands.blockComment) {
CodeMirror.keyMap.sublime['Shift-Ctrl-/'] = 'commentSelection';
}
if (navigator.appVersion.includes('Windows')) {
// 'pcDefault' keymap on Windows should have F3/Shift-F3/Ctrl-R
if (!extraKeysCommands.findNext) {
CodeMirror.keyMap.pcDefault['F3'] = 'findNext';
// Adding hotkeys to some keymaps except 'basic' which is primitive by design
{
const KM = CodeMirror.keyMap;
const extras = Object.values(CodeMirror.defaults.extraKeys);
if (!extras.includes('jumpToLine')) {
KM.sublime['Ctrl-G'] = 'jumpToLine';
KM.emacsy['Ctrl-G'] = 'jumpToLine';
KM.pcDefault['Ctrl-J'] = 'jumpToLine';
KM.macDefault['Cmd-J'] = 'jumpToLine';
}
if (!extraKeysCommands.findPrev) {
CodeMirror.keyMap.pcDefault['Shift-F3'] = 'findPrev';
if (!extras.includes('autocomplete')) {
// will be used by 'sublime' on PC via fallthrough
KM.pcDefault['Ctrl-Space'] = 'autocomplete';
// OSX uses Ctrl-Space and Cmd-Space for something else
KM.macDefault['Alt-Space'] = 'autocomplete';
// copied from 'emacs' keymap
KM.emacsy['Alt-/'] = 'autocomplete';
// 'vim' and 'emacs' define their own autocomplete hotkeys
}
if (!extraKeysCommands.replace) {
CodeMirror.keyMap.pcDefault['Ctrl-R'] = 'replace';
if (!extras.includes('blockComment')) {
KM.sublime['Shift-Ctrl-/'] = 'commentSelection';
}
// try to remap non-interceptable Ctrl-(Shift-)N/T/W hotkeys
['N', 'T', 'W'].forEach(char => {
[
{from: 'Ctrl-', to: ['Alt-', 'Ctrl-Alt-']},
// Note: modifier order in CodeMirror is S-C-A
{from: 'Shift-Ctrl-', to: ['Ctrl-Alt-', 'Shift-Ctrl-Alt-']}
].forEach(remap => {
const oldKey = remap.from + char;
Object.keys(CodeMirror.keyMap).forEach(keyMapName => {
const keyMap = CodeMirror.keyMap[keyMapName];
const command = keyMap[oldKey];
if (!command) {
return;
}
remap.to.some(newMod => {
const newKey = newMod + char;
if (!(newKey in keyMap)) {
delete keyMap[oldKey];
keyMap[newKey] = command;
return true;
if (UA.windows) {
// 'pcDefault' keymap on Windows should have F3/Shift-F3/Ctrl-R
if (!extras.includes('findNext')) KM.pcDefault['F3'] = 'findNext';
if (!extras.includes('findPrev')) KM.pcDefault['Shift-F3'] = 'findPrev';
if (!extras.includes('replace')) KM.pcDefault['Ctrl-R'] = 'replace';
// try to remap non-interceptable (Shift-)Ctrl-N/T/W hotkeys
// Note: modifier order in CodeMirror is S-C-A
for (const char of ['N', 'T', 'W']) {
for (const remap of [
{from: 'Ctrl-', to: ['Alt-', 'Ctrl-Alt-']},
{from: 'Shift-Ctrl-', to: ['Ctrl-Alt-', 'Shift-Ctrl-Alt-']},
]) {
const oldKey = remap.from + char;
for (const km of Object.values(KM)) {
const command = km[oldKey];
if (!command) continue;
for (const newMod of remap.to) {
const newKey = newMod + char;
if (newKey in km) continue;
km[newKey] = command;
delete km[oldKey];
break;
}
});
});
});
});
}
}
}
}
}
Object.assign(CodeMirror.mimeModes['text/css'].propertyKeywords, {
// CSS Backgrounds and Borders Module L4
'background-position-x': true,
'background-position-y': true,
// CSS Logical Properties and Values L1
'block-size': true,
'border-block-color': true,
'border-block-end': true,
'border-block-end-color': true,
'border-block-end-style': true,
'border-block-end-width': true,
'border-block-start': true,
'border-block-start-color': true,
'border-block-start-style': true,
'border-block-start-width': true,
'border-block-style': true,
'border-block-width': true,
'border-inline-color': true,
'border-inline-end': true,
'border-inline-end-color': true,
'border-inline-end-style': true,
'border-inline-end-width': true,
'border-inline-start': true,
'border-inline-start-color': true,
'border-inline-start-style': true,
'border-inline-start-width': true,
'border-inline-style': true,
'border-inline-width': true,
'inline-size': true,
'inset': true,
'inset-block': true,
'inset-block-end': true,
'inset-block-start': true,
'inset-inline': true,
'inset-inline-end': true,
'inset-inline-start': true,
'margin-block': true,
'margin-block-end': true,
'margin-block-start': true,
'margin-inline': true,
'margin-inline-end': true,
'margin-inline-start': true,
'max-block-size': true,
'max-inline-size': true,
'min-block-size': true,
'min-inline-size': true,
'padding-block': true,
'padding-block-end': true,
'padding-block-start': true,
'padding-inline': true,
'padding-inline-end': true,
'padding-inline-start': true,
'text-align-all': true,
'contain': true,
'mix-blend-mode': true,
'isolation': true,
'zoom': true,
// nonstandard https://compat.spec.whatwg.org/
'box-reflect': true,
'text-fill-color': true,
'text-stroke': true,
'text-stroke-color': true,
'text-stroke-width': true,
// end
});
Object.assign(CodeMirror.mimeModes['text/css'].valueKeywords, {
'isolate': true,
'recto': true,
'verso': true,
});
Object.assign(CodeMirror.mimeModes['text/css'].colorKeywords, {
'darkgrey': true,
'darkslategrey': true,
'dimgrey': true,
'grey': true,
'lightgrey': true,
'lightslategrey': true,
'slategrey': true,
});
const MODE = {
less: {
family: 'css',
value: 'text/x-less',
isActive: cm =>
cm.doc.mode &&
cm.doc.mode.name === 'css' &&
cm.doc.mode.helperType === 'less',
Object.assign(CodeMirror.prototype, {
/**
* @param {'less' | 'stylus' | ?} [pp] - any value besides `less` or `stylus` sets `css` mode
* @param {boolean} [force]
*/
setPreprocessor(pp, force) {
const name = pp === 'less' ? 'text/x-less' : pp === 'stylus' ? pp : 'css';
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 */
isBlank() {
let filled;
this.eachLine(({text}) => (filled = text && /\S/.test(text)));
return !filled;
},
/**
* Sets cursor and centers it in view if `pos` was out of view
* @param {CodeMirror.Pos} pos
* @param {CodeMirror.Pos} [end] - will set a selection from `pos` to `end`
*/
jumpToPos(pos, end = pos) {
const {curOp} = this;
if (!curOp) this.startOperation();
const y = this.cursorCoords(pos, 'window').top;
const rect = this.display.wrapper.getBoundingClientRect();
// case 1) outside of CM viewport or too close to edge so tell CM to render a new viewport
if (y < rect.top + 50 || y > rect.bottom - 100) {
this.scrollIntoView(pos, rect.height / 2);
// case 2) inside CM viewport but outside of window viewport so just scroll the window
} else if (y < 0 || y > innerHeight) {
editor.scrollToEditor(this);
}
// Using prototype since our bookmark patch sets cm.setSelection to jumpToPos
CodeMirror.prototype.setSelection.call(this, pos, end);
if (!curOp) this.endOperation();
},
stylus: 'stylus',
uso: 'css'
};
CodeMirror.defineExtension('setPreprocessor', function (preprocessor, force = false) {
const mode = MODE[preprocessor] || 'css';
const isActive = mode.isActive || (
cm => cm.doc.mode === mode ||
cm.doc.mode && (cm.doc.mode.name + (cm.doc.mode.helperType || '') === mode)
);
if (!force && isActive(this)) {
return Promise.resolve();
}
if ((mode.family || mode) === 'css') {
// css.js is always loaded via html
this.setOption('mode', mode.value || mode);
return Promise.resolve();
}
return loadScript(`/vendor/codemirror/mode/${mode}/${mode}.js`).then(() => {
this.setOption('mode', mode);
});
});
CodeMirror.defineExtension('isBlank', function () {
// superfast checking as it runs only until the first non-blank line
let isBlank = true;
this.doc.eachLine(line => {
if (line.text && line.text.trim()) {
isBlank = false;
return true;
}
});
return isBlank;
Object.assign(CodeMirror.commands, {
jumpToLine(cm) {
const cur = cm.getCursor();
const oldDialog = $('.CodeMirror-dialog', cm.display.wrapper);
if (oldDialog) cm.focus(); // close the currently opened minidialog
cm.openDialog(t.template.jumpToLine.cloneNode(true), str => {
const [line, ch] = str.match(/^\s*(\d+)(?:\s*:\s*(\d+))?\s*$|$/);
if (line) cm.setCursor(line - 1, ch ? ch - 1 : cur.ch);
}, {value: cur.line + 1});
},
});
// doubleclick option
if (typeof editors !== 'undefined') {
const fn = (cm, repeat) =>
repeat === 'double' ?
{unit: selectTokenOnDoubleclick} :
{};
const configure = (_, enabled) => {
editors.forEach(cm => cm.setOption('configureMouse', enabled ? fn : null));
CodeMirror.defaults.configureMouse = enabled ? fn : null;
};
configure(null, prefs.get('editor.selectByTokens'));
prefs.subscribe(['editor.selectByTokens'], configure);
}
function selectTokenOnDoubleclick(cm, pos) {
let {ch} = pos;
const {line, sticky} = pos;
const {text, styles} = cm.getLineHandle(line);
const execAt = (rx, i) => (rx.lastIndex = i) && null || rx.exec(text);
const at = (rx, i) => (rx.lastIndex = i) && null || rx.test(text);
const atWord = ch => at(/\w/y, ch);
const atSpace = ch => at(/\s/y, ch);
const atTokenEnd = styles.indexOf(ch, 1);
ch += atTokenEnd < 0 ? 0 : sticky === 'before' && atWord(ch - 1) ? 0 : atSpace(ch + 1) ? 0 : 1;
ch = Math.min(text.length, ch);
const type = cm.getTokenTypeAt({line, ch: ch + (sticky === 'after' ? 1 : 0)});
if (atTokenEnd > 0) ch--;
const isCss = type && !/^(comment|string)/.test(type);
const isNumber = type === 'number';
const isSpace = atSpace(ch);
let wordChars =
isNumber ? /[-+\w.%]/y :
isCss ? /[-\w@]/y :
isSpace ? /\s/y :
atWord(ch) ? /\w/y : /[^\w\s]/y;
let a = ch;
while (a && at(wordChars, a)) a--;
a += !a && at(wordChars, a) || isCss && at(/[.!#@]/y, a) ? 0 : at(wordChars, a + 1);
let b, found;
if (isNumber) {
b = a + execAt(/[+-]?[\d.]+(e\d+)?|$/yi, a)[0].length;
found = b >= ch;
if (!found) {
a = b;
ch = a;
}
}
if (!found) {
wordChars = isCss ? /[-\w]*/y : new RegExp(wordChars.source + '*', 'uy');
b = ch + execAt(wordChars, ch)[0].length;
}
return {
from: {line, ch: a},
to: {line, ch: b},
};
}
})();
// eslint-disable-next-line no-unused-expressions
CodeMirror.hint && (() => {
const USO_VAR = 'uso-variable';
const USO_VALID_VAR = 'variable-3 ' + USO_VAR;
const USO_INVALID_VAR = 'error ' + USO_VAR;
const RX_IMPORTANT = /(i(m(p(o(r(t(a(nt?)?)?)?)?)?)?)?)?(?=\b|\W|$)/iy;
const RX_VAR_KEYWORD = /(^|[^-\w\u0080-\uFFFF])var\(/iy;
const RX_END_OF_VAR = /[\s,)]|$/g;
const originalHelper = CodeMirror.hint.css || (() => {});
const helper = cm => {
const pos = cm.getCursor();
const {line, ch} = pos;
const {styles, text} = cm.getLineHandle(line);
if (!styles) return originalHelper(cm);
const {style, index} = cm.getStyleAtPos({styles, pos: ch}) || {};
if (style && (style.startsWith('comment') || style.startsWith('string'))) {
return originalHelper(cm);
}
// !important
if (text[ch - 1] === '!' && /i|\W|^$/i.test(text[ch] || '')) {
RX_IMPORTANT.lastIndex = ch;
return {
list: ['important'],
from: pos,
to: {line, ch: ch + RX_IMPORTANT.exec(text)[0].length},
};
}
let prev = index > 2 ? styles[index - 2] : 0;
let end = styles[index];
// #hex colors
if (text[prev] === '#') {
return {list: [], from: pos, to: pos};
}
// adjust cursor position for /*[[ and ]]*/
const adjust = text[prev] === '/' ? 4 : 0;
prev += adjust;
end -= adjust;
const leftPart = text.slice(prev, ch);
// --css-variables
const startsWithDoubleDash = text[prev] === '-' && text[prev + 1] === '-';
if (startsWithDoubleDash ||
leftPart === '(' && testAt(RX_VAR_KEYWORD, Math.max(0, prev - 4), text)) {
// simplified regex without CSS escapes
const RX_CSS_VAR = new RegExp(
'(?:^|[\\s/;{])(' +
(leftPart.startsWith('--') ? leftPart : '--') +
(leftPart.length <= 2 ? '[a-zA-Z_\u0080-\uFFFF]' : '') +
'[-0-9a-zA-Z_\u0080-\uFFFF]*)',
'gm');
const cursor = cm.getSearchCursor(RX_CSS_VAR, null, {caseFold: false, multiline: false});
const list = new Set();
while (cursor.findNext()) {
list.add(cursor.pos.match[1]);
}
if (!startsWithDoubleDash) {
prev++;
}
RX_END_OF_VAR.lastIndex = prev;
end = RX_END_OF_VAR.exec(text).index;
return {
list: [...list.keys()].sort(),
from: {line, ch: prev},
to: {line, ch: end},
};
}
if (!editor || !style || !style.includes(USO_VAR)) {
return originalHelper(cm);
}
// USO vars in usercss mode editor
const list = Object.keys(editor.getStyle().usercssData.vars)
.filter(name => name.startsWith(leftPart));
return {
list,
from: {line, ch: prev},
to: {line, ch: end},
};
};
CodeMirror.registerHelper('hint', 'css', helper);
CodeMirror.registerHelper('hint', 'stylus', helper);
const hooks = CodeMirror.mimeModes['text/css'].tokenHooks;
const originalCommentHook = hooks['/'];
hooks['/'] = tokenizeUsoVariables;
function tokenizeUsoVariables(stream) {
const token = originalCommentHook.apply(this, arguments);
if (token[1] !== 'comment') {
return token;
}
const {string, start, pos} = stream;
// /*[[install-key]]*/
// 01234 43210
if (string[start + 2] === '[' &&
string[start + 3] === '[' &&
string[pos - 3] === ']' &&
string[pos - 4] === ']') {
const vars = typeof editor !== 'undefined' && (editor.getStyle().usercssData || {}).vars;
const name = vars && string.slice(start + 4, pos - 4);
if (vars && Object.hasOwnProperty.call(vars, name.endsWith('-rgb') ? name.slice(0, -4) : name)) {
token[0] = USO_VALID_VAR;
} else {
token[0] = USO_INVALID_VAR;
}
}
return token;
}
function testAt(rx, index, text) {
if (!rx) return false;
rx.lastIndex = index;
return rx.test(text);
}
})();

View File

@ -1,699 +0,0 @@
/*
global CodeMirror loadScript
global editors editor styleId ownTabId
global save toggleStyle setupAutocomplete makeSectionVisible getSectionForChild
global getSectionsHashes
global messageBox
*/
'use strict';
onDOMscriptReady('/codemirror.js').then(() => {
const COMMANDS = {
save,
toggleStyle,
toggleEditorFocus,
jumpToLine,
nextEditor, prevEditor,
commentSelection,
};
const ORIGINAL_COMMANDS = {
insertTab: CodeMirror.commands.insertTab,
};
// reroute handling to nearest editor when keypress resolves to one of these commands
const REROUTED = new Set([
'save',
'toggleStyle',
'jumpToLine',
'nextEditor', 'prevEditor',
'toggleEditorFocus',
'find', 'findNext', 'findPrev', 'replace', 'replaceAll',
'colorpicker',
]);
Object.assign(CodeMirror, {
getOption,
setOption,
closestVisible,
});
Object.assign(CodeMirror.prototype, {
getSection,
rerouteHotkeys,
});
CodeMirror.defineInitHook(cm => {
if (!cm.display.wrapper.closest('#sections')) {
return;
}
if (prefs.get('editor.livePreview') && styleId) {
cm.on('changes', updatePreview);
}
if (prefs.get('editor.autocompleteOnTyping')) {
setupAutocomplete(cm);
}
const wrapper = cm.display.wrapper;
cm.on('blur', () => {
editors.lastActive = cm;
cm.rerouteHotkeys(true);
setTimeout(() => {
wrapper.classList.toggle('CodeMirror-active', wrapper.contains(document.activeElement));
});
});
cm.on('focus', () => {
cm.rerouteHotkeys(false);
wrapper.classList.add('CodeMirror-active');
});
});
new MutationObserver((mutations, observer) => {
if (!$('#sections')) {
return;
}
observer.disconnect();
prefs.subscribe(['editor.keyMap'], showHotkeyInTooltip);
addEventListener('showHotkeyInTooltip', showHotkeyInTooltip);
showHotkeyInTooltip();
// N.B. the onchange event listeners should be registered before setupLivePrefs()
$('#options').addEventListener('change', onOptionElementChanged);
setupLivePreview();
buildThemeElement();
buildKeymapElement();
setupLivePrefs();
Object.assign(CodeMirror.commands, COMMANDS);
rerouteHotkeys(true);
}).observe(document, {childList: true, subtree: true});
return;
////////////////////////////////////////////////
function getOption(o) {
return CodeMirror.defaults[o];
}
function setOption(o, v) {
CodeMirror.defaults[o] = v;
if (editors.length > 4 && (o === 'theme' || o === 'lineWrapping')) {
throttleSetOption({key: o, value: v, index: 0});
return;
}
editors.forEach(editor => {
editor.setOption(o, v);
});
}
function throttleSetOption({
key,
value,
index,
timeStart = performance.now(),
cmStart = editors.lastActive || editors[0],
editorsCopy = editors.slice(),
progress,
}) {
if (index === 0) {
if (!cmStart) {
return;
}
cmStart.setOption(key, value);
}
const THROTTLE_AFTER_MS = 100;
const THROTTLE_SHOW_PROGRESS_AFTER_MS = 100;
const t0 = performance.now();
const total = editorsCopy.length;
while (index < total) {
const cm = editorsCopy[index++];
if (cm === cmStart ||
cm !== editors[index] && !editors.includes(cm)) {
continue;
}
cm.setOption(key, value);
if (performance.now() - t0 > THROTTLE_AFTER_MS) {
break;
}
}
if (index >= total) {
$.remove(progress);
return;
}
if (!progress &&
index < total / 2 &&
t0 - timeStart > THROTTLE_SHOW_PROGRESS_AFTER_MS) {
let option = $('#editor.' + key);
if (option) {
if (option.type === 'checkbox') {
option = (option.labels || [])[0] || option.nextElementSibling || option;
}
progress = document.body.appendChild(
$create('.set-option-progress', {targetElement: option}));
}
}
if (progress) {
const optionBounds = progress.targetElement.getBoundingClientRect();
const bounds = {
top: optionBounds.top + window.scrollY + 1,
left: optionBounds.left + window.scrollX + 1,
width: (optionBounds.width - 2) * index / total | 0,
height: optionBounds.height - 2,
};
const style = progress.style;
for (const prop in bounds) {
if (bounds[prop] !== parseFloat(style[prop])) {
style[prop] = bounds[prop] + 'px';
}
}
}
setTimeout(throttleSetOption, 0, {
key,
value,
index,
timeStart,
cmStart,
editorsCopy,
progress,
});
}
function getSection() {
return this.display.wrapper.parentNode;
}
function nextEditor(cm) {
return nextPrevEditor(cm, 1);
}
function prevEditor(cm) {
return nextPrevEditor(cm, -1);
}
function nextPrevEditor(cm, direction) {
cm = editors[(editors.indexOf(cm) + direction + editors.length) % editors.length];
makeSectionVisible(cm);
cm.focus();
return cm;
}
function jumpToLine(cm) {
const cur = cm.getCursor();
refocusMinidialog(cm);
cm.openDialog(template.jumpToLine.cloneNode(true), str => {
const m = str.match(/^\s*(\d+)(?:\s*:\s*(\d+))?\s*$/);
if (m) {
cm.setCursor(m[1] - 1, m[2] ? m[2] - 1 : cur.ch);
}
}, {value: cur.line + 1});
}
function commentSelection(cm) {
cm.blockComment(cm.getCursor('from'), cm.getCursor('to'), {fullLines: false});
}
function toggleEditorFocus(cm) {
if (!cm) return;
if (cm.hasFocus()) {
setTimeout(() => cm.display.input.blur());
} else {
cm.focus();
}
}
function refocusMinidialog(cm) {
const section = cm.getSection();
if (!$('.CodeMirror-dialog', section)) {
return;
}
// close the currently opened minidialog
cm.focus();
// make sure to focus the input in newly opened minidialog
setTimeout(() => {
$('.CodeMirror-dialog', section).focus();
});
}
function onOptionElementChanged(event) {
const el = event.target;
let option = el.id.replace(/^editor\./, '');
if (!option) {
console.error('no "cm_option"', el);
return;
}
let value = el.type === 'checkbox' ? el.checked : el.value;
switch (option) {
case 'tabSize':
value = Number(value);
CodeMirror.setOption('indentUnit', value);
break;
case 'indentWithTabs':
CodeMirror.commands.insertTab = value ?
ORIGINAL_COMMANDS.insertTab :
CodeMirror.commands.insertSoftTab;
break;
case 'theme': {
const themeLink = $('#cm-theme');
// use non-localized 'default' internally
if (!value || value === 'default' || value === t('defaultTheme')) {
value = 'default';
if (prefs.get(el.id) !== value) {
prefs.set(el.id, value);
}
themeLink.href = '';
el.selectedIndex = 0;
break;
}
const url = chrome.runtime.getURL('vendor/codemirror/theme/' + value + '.css');
if (themeLink.href === url) {
// preloaded in initCodeMirror()
break;
}
// avoid flicker: wait for the second stylesheet to load, then apply the theme
document.head.appendChild($create('link#cm-theme2', {rel: 'stylesheet', href: url}));
setTimeout(() => {
CodeMirror.setOption(option, value);
themeLink.remove();
$('#cm-theme2').id = 'cm-theme';
}, 100);
return;
}
case 'autocompleteOnTyping':
editors.forEach(cm => setupAutocomplete(cm, el.checked));
return;
case 'autoCloseBrackets':
Promise.resolve(value && loadScript('/vendor/codemirror/addon/edit/closebrackets.js')).then(() => {
CodeMirror.setOption(option, value);
});
return;
case 'matchHighlight':
switch (value) {
case 'token':
case 'selection':
document.body.dataset[option] = value;
value = {showToken: value === 'token' && /[#.\-\w]/, annotateScrollbar: true};
break;
default:
value = null;
document.body.removeAttribute('data-match-highlight');
}
option = 'highlightSelectionMatches';
break;
case 'colorpicker':
return;
}
CodeMirror.setOption(option, value);
}
function buildThemeElement() {
const themeElement = $('#editor.theme');
const themeList = localStorage.codeMirrorThemes;
const optionsFromArray = options => {
const fragment = document.createDocumentFragment();
options.forEach(opt => fragment.appendChild($create('option', opt)));
themeElement.appendChild(fragment);
};
if (themeList) {
optionsFromArray(themeList.split(/\s+/));
} else {
// Chrome is starting up and shows our edit.html, but the background page isn't loaded yet
const theme = prefs.get('editor.theme');
optionsFromArray([theme === 'default' ? t('defaultTheme') : theme]);
getCodeMirrorThemes().then(() => {
const themes = (localStorage.codeMirrorThemes || '').split(/\s+/);
optionsFromArray(themes);
themeElement.selectedIndex = Math.max(0, themes.indexOf(theme));
});
}
}
function buildKeymapElement() {
// move 'pc' or 'mac' prefix to the end of the displayed label
const maps = Object.keys(CodeMirror.keyMap)
.map(name => ({
value: name,
name: name.replace(/^(pc|mac)(.+)/, (s, arch, baseName) =>
baseName.toLowerCase() + '-' + (arch === 'mac' ? 'Mac' : 'PC')),
}))
.sort((a, b) => a.name < b.name && -1 || a.name > b.name && 1);
const fragment = document.createDocumentFragment();
let bin = fragment;
let groupName;
// group suffixed maps in <optgroup>
maps.forEach(({value, name}, i) => {
groupName = !name.includes('-') ? name : groupName;
const groupWithNext = maps[i + 1] && maps[i + 1].name.startsWith(groupName);
if (groupWithNext) {
if (bin === fragment) {
bin = fragment.appendChild($create('optgroup', {label: name.split('-')[0]}));
}
}
const el = bin.appendChild($create('option', {value}, name));
if (value === prefs.defaults['editor.keyMap']) {
el.dataset.default = '';
el.title = t('defaultTheme');
}
if (!groupWithNext) bin = fragment;
});
$('#editor.keyMap').appendChild(fragment);
}
////////////////////////////////////////////////
function rerouteHotkeys(enable, immediately) {
if (!immediately) {
debounce(rerouteHotkeys, 0, enable, true);
} else if (enable) {
document.addEventListener('keydown', rerouteHandler);
} else {
document.removeEventListener('keydown', rerouteHandler);
}
}
function rerouteHandler(event) {
const keyName = CodeMirror.keyName(event);
if (!keyName) {
return;
}
const rerouteCommand = name => {
if (REROUTED.has(name)) {
CodeMirror.commands[name](closestVisible(event.target));
return true;
}
};
if (CodeMirror.lookupKey(keyName, CodeMirror.defaults.keyMap, rerouteCommand) === 'handled' ||
CodeMirror.lookupKey(keyName, CodeMirror.defaults.extraKeys, rerouteCommand) === 'handled') {
event.preventDefault();
event.stopPropagation();
}
}
////////////////////////////////////////////////
// priority:
// 1. associated CM for applies-to element
// 2. last active if visible
// 3. first visible
function closestVisible(nearbyElement) {
const cm =
nearbyElement instanceof CodeMirror ? nearbyElement :
nearbyElement instanceof Node && (getSectionForChild(nearbyElement) || {}).CodeMirror ||
editors.lastActive;
if (nearbyElement instanceof Node && cm) {
const {left, top} = nearbyElement.getBoundingClientRect();
const bounds = cm.display.wrapper.getBoundingClientRect();
if (top >= 0 && top >= bounds.top &&
left >= 0 && left >= bounds.left) {
return cm;
}
}
// closest editor should have at least 2 lines visible
const lineHeight = editors[0].defaultTextHeight();
const scrollY = window.scrollY;
const windowBottom = scrollY + window.innerHeight - 2 * lineHeight;
const allSectionsContainerTop = scrollY + $('#sections').getBoundingClientRect().top;
const distances = [];
const alreadyInView = cm && offscreenDistance(null, cm) === 0;
return alreadyInView ? cm : findClosest();
function offscreenDistance(index, cm) {
if (index >= 0 && distances[index] !== undefined) {
return distances[index];
}
const section = (cm || editors[index]).getSection();
if (!section) {
return 1e9;
}
const top = allSectionsContainerTop + section.offsetTop;
if (top < scrollY + lineHeight) {
return Math.max(0, scrollY - top - lineHeight);
}
if (top < windowBottom) {
return 0;
}
const distance = top - windowBottom + section.offsetHeight;
if (index >= 0) {
distances[index] = distance;
}
return distance;
}
function findClosest() {
const last = editors.length - 1;
let a = 0;
let b = last;
let c;
let distance;
while (a < b - 1) {
c = (a + b) / 2 | 0;
distance = offscreenDistance(c);
if (!distance || !c) {
break;
}
const distancePrev = offscreenDistance(c - 1);
const distanceNext = c < last ? offscreenDistance(c + 1) : 1e20;
if (distancePrev <= distance && distance <= distanceNext) {
b = c;
} else {
a = c;
}
}
while (b && offscreenDistance(b - 1) <= offscreenDistance(b)) {
b--;
}
const cm = editors[b];
if (distances[b] > 0) {
makeSectionVisible(cm);
}
return cm;
}
}
////////////////////////////////////////////////
function getCodeMirrorThemes() {
if (!chrome.runtime.getPackageDirectoryEntry) {
const themes = [
chrome.i18n.getMessage('defaultTheme'),
/* populate-theme-start */
'3024-day',
'3024-night',
'abcdef',
'ambiance',
'ambiance-mobile',
'base16-dark',
'base16-light',
'bespin',
'blackboard',
'cobalt',
'colorforth',
'darcula',
'dracula',
'duotone-dark',
'duotone-light',
'eclipse',
'elegant',
'erlang-dark',
'gruvbox-dark',
'hopscotch',
'icecoder',
'idea',
'isotope',
'lesser-dark',
'liquibyte',
'lucario',
'material',
'mbo',
'mdn-like',
'midnight',
'monokai',
'neat',
'neo',
'night',
'oceanic-next',
'panda-syntax',
'paraiso-dark',
'paraiso-light',
'pastel-on-dark',
'railscasts',
'rubyblue',
'seti',
'shadowfox',
'solarized',
'ssms',
'the-matrix',
'tomorrow-night-bright',
'tomorrow-night-eighties',
'ttcn',
'twilight',
'vibrant-ink',
'xq-dark',
'xq-light',
'yeti',
'zenburn',
/* populate-theme-end */
];
localStorage.codeMirrorThemes = themes.join(' ');
return Promise.resolve(themes);
}
return new Promise(resolve => {
chrome.runtime.getPackageDirectoryEntry(rootDir => {
rootDir.getDirectory('vendor/codemirror/theme', {create: false}, themeDir => {
themeDir.createReader().readEntries(entries => {
const themes = [
chrome.i18n.getMessage('defaultTheme')
].concat(
entries.filter(entry => entry.isFile)
.sort((a, b) => (a.name < b.name ? -1 : 1))
.map(entry => entry.name.replace(/\.css$/, ''))
);
localStorage.codeMirrorThemes = themes.join(' ');
resolve(themes);
});
});
});
});
}
function showHotkeyInTooltip(_, mapName = prefs.get('editor.keyMap')) {
const extraKeys = CodeMirror.defaults.extraKeys;
for (const el of $$('[data-hotkey-tooltip]')) {
if (el._hotkeyTooltipKeyMap !== mapName) {
el._hotkeyTooltipKeyMap = mapName;
const title = el._hotkeyTooltipTitle = el._hotkeyTooltipTitle || el.title;
const cmd = el.dataset.hotkeyTooltip;
const key = cmd[0] === '=' ? cmd.slice(1) :
findKeyForCommand(cmd, mapName) ||
extraKeys && findKeyForCommand(cmd, extraKeys);
const newTitle = title + (title && key ? '\n' : '') + (key || '');
if (el.title !== newTitle) el.title = newTitle;
}
}
}
function findKeyForCommand(command, map) {
if (typeof map === 'string') map = CodeMirror.keyMap[map];
let key = Object.keys(map).find(k => map[k] === command);
if (key) {
return key;
}
for (const ft of Array.isArray(map.fallthrough) ? map.fallthrough : [map.fallthrough]) {
key = ft && findKeyForCommand(command, ft);
if (key) {
return key;
}
}
return '';
}
function setupLivePreview() {
if (!prefs.get('editor.livePreview') && !editors.length) {
setTimeout(setupLivePreview);
return;
}
if (styleId) {
$('#editor.livePreview').onchange = livePreviewToggled;
return;
}
// wait for #preview-label's class to lose 'hidden' after the first save
new MutationObserver((_, observer) => {
if (!styleId) return;
observer.disconnect();
setupLivePreview();
livePreviewToggled();
}).observe($('#preview-label'), {
attributes: true,
attributeFilter: ['class'],
});
}
function livePreviewToggled() {
const me = this instanceof Node ? this : $('#editor.livePreview');
const previewing = me.checked;
editors.forEach(cm => cm[previewing ? 'on' : 'off']('changes', updatePreview));
const addRemove = EventTarget.prototype[previewing ? 'addEventListener' : 'removeEventListener'];
addRemove.call($('#enabled'), 'change', updatePreview);
if (!editor) {
for (const el of $$('#sections .applies-to')) {
addRemove.call(el, 'input', updatePreview);
}
toggleLivePreviewSectionsObserver(previewing);
}
if (!previewing || document.body.classList.contains('dirty')) {
updatePreview(null, previewing);
}
}
/**
* Observes newly added section elements, and sets these event listeners:
* 1. 'changes' on CodeMirror inside
* 2. 'input' on .applies-to inside
* The goal is to avoid listening to 'input' on the entire #sections tree,
* which would trigger updatePreview() twice on any keystroke -
* both for the synthetic event from CodeMirror and the original event.
* Side effects:
* two expando properties on #sections
* 1. __livePreviewObserver
* 2. __livePreviewObserverEnabled
* @param {Boolean} enable
*/
function toggleLivePreviewSectionsObserver(enable) {
const sections = $('#sections');
const observing = sections.__livePreviewObserverEnabled;
let mo = sections.__livePreviewObserver;
if (enable && !mo) {
sections.__livePreviewObserver = mo = new MutationObserver(mutations => {
for (const {addedNodes} of mutations) {
for (const node of addedNodes) {
const el = node.children && $('.applies-to', node);
if (el) el.addEventListener('input', updatePreview);
if (node.CodeMirror) node.CodeMirror.on('changes', updatePreview);
}
}
});
}
if (enable && !observing) {
mo.observe(sections, {childList: true});
sections.__livePreviewObserverEnabled = true;
} else if (!enable && observing) {
mo.disconnect();
sections.__livePreviewObserverEnabled = false;
}
}
function updatePreview(data, previewing) {
if (previewing !== true && previewing !== false) {
if (data instanceof Event && !data.target.matches('.style-contributor')) return;
debounce(updatePreview, data && data.id === 'enabled' ? 0 : 400, null, true);
return;
}
const errors = $('#preview-errors');
API.refreshAllTabs({
reason: 'editPreview',
tabId: ownTabId,
style: {
id: styleId,
enabled: $('#enabled').checked,
sections: previewing && (editor ? editors[0].getValue() : getSectionsHashes()),
},
}).then(() => {
errors.classList.add('hidden');
}).catch(err => {
if (Array.isArray(err)) err = err.join('\n');
if (err && editor && !isNaN(err.index)) {
const pos = editors[0].posFromIndex(err.index);
err = `${pos.line}:${pos.ch} ${err}`;
}
errors.classList.remove('hidden');
errors.onclick = () => messageBox.alert(String(err), 'pre');
});
}
});

313
edit/codemirror-factory.js Normal file
View File

@ -0,0 +1,313 @@
/* global CodeMirror */
/* global editor */
/* global prefs */
/* global rerouteHotkeys */// util.js
'use strict';
/*
All cm instances created by this module are collected so we can broadcast prefs
settings to them. You should `cmFactory.destroy(cm)` to unregister the listener
when the instance is not used anymore.
*/
(() => {
//#region Factory
const cms = new Set();
let lazyOpt;
const cmFactory = window.cmFactory = {
create(place, options) {
const cm = CodeMirror(place, options);
cm.lastActive = 0;
cms.add(cm);
return cm;
},
destroy(cm) {
cms.delete(cm);
},
globalSetOption(key, value) {
CodeMirror.defaults[key] = value;
if (cms.size > 4 && lazyOpt.names.includes(key)) {
lazyOpt.set(key, value);
} else {
cms.forEach(cm => cm.setOption(key, value));
}
},
};
// focus and blur
const onCmFocus = cm => {
rerouteHotkeys.toggle(false);
cm.display.wrapper.classList.add('CodeMirror-active');
cm.lastActive = Date.now();
};
const onCmBlur = cm => {
setTimeout(() => {
/* Delaying to next tick to avoid double-processing of the currently processed keyboard event
* when it bubbles up from CodeMirror to `document` where the rerouter listens */
rerouteHotkeys.toggle(true);
const {wrapper} = cm.display;
wrapper.classList.toggle('CodeMirror-active', wrapper.contains(document.activeElement));
});
};
CodeMirror.defineInitHook(cm => {
cm.on('focus', onCmFocus);
cm.on('blur', onCmBlur);
});
// propagated preferences
const prefToCmOpt = k =>
k.startsWith('editor.') &&
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;
for (const [key, fn] of Object.entries({
'editor.tabSize'(cm, value) {
cm.setOption('indentUnit', Number(value));
},
'editor.indentWithTabs'(cm, value) {
CodeMirror.commands.insertTab = value ? insertTab : insertSoftTab;
},
'editor.matchHighlight'(cm, value) {
const showToken = value === 'token' && /[#.\-\w]/;
const opt = (showToken || value === 'selection') && {
showToken,
annotateScrollbar: true,
delay: 0,
onUpdate: updateMatchHighlightCount,
};
cm.setOption('highlightSelectionMatches', opt || null);
},
'editor.selectByTokens'(cm, value) {
cm.setOption('configureMouse', value ? configureMouseFn : null);
},
})) {
CodeMirror.defineOption(prefToCmOpt(key), prefs.get(key), fn);
prefKeys.push(key);
}
prefs.subscribe(prefKeys, (key, val) => {
if (key === 'editor.theme') editor.updateTheme(val);
cmFactory.globalSetOption(prefToCmOpt(key), val);
});
// lazy propagation
lazyOpt = {
names: ['theme', 'lineWrapping'],
set(key, value) {
const {observer, queue} = lazyOpt;
for (const cm of cms) {
let opts = queue.get(cm);
if (!opts) queue.set(cm, opts = {});
opts[key] = value;
observer.observe(cm.display.wrapper);
}
},
setNow({cm, data}) {
cm.operation(() => data.forEach(kv => cm.setOption(...kv)));
},
onView(entries) {
const {queue, observer} = lazyOpt;
const delayed = [];
for (const e of entries) {
const r = e.isIntersecting && e.intersectionRect;
if (!r) continue;
const cm = e.target.CodeMirror;
const data = Object.entries(queue.get(cm) || {});
queue.delete(cm);
observer.unobserve(e.target);
if (!data.every(([key, val]) => cm.getOption(key) === val)) {
if (r.bottom > 0 && r.top < window.innerHeight) {
lazyOpt.setNow({cm, data});
} else {
delayed.push({cm, data});
}
}
}
if (delayed.length) {
setTimeout(() => delayed.forEach(lazyOpt.setNow));
}
},
get observer() {
if (!lazyOpt._observer) {
// must exceed refreshOnView's 100%
lazyOpt._observer = new IntersectionObserver(lazyOpt.onView, {rootMargin: '150%'});
lazyOpt.queue = new WeakMap();
}
return lazyOpt._observer;
},
};
//#endregion
//#region Commands
Object.assign(CodeMirror.commands, {
commentSelection(cm) {
cm.blockComment(cm.getCursor('from'), cm.getCursor('to'), {fullLines: false});
},
toggleEditorFocus(cm) {
if (!cm) return;
if (cm.hasFocus()) {
setTimeout(() => cm.display.input.blur());
} else {
cm.focus();
}
},
});
for (const cmd of [
'nextEditor',
'prevEditor',
'save',
'toggleStyle',
]) {
CodeMirror.commands[cmd] = (...args) => editor[cmd](...args);
}
//#endregion
//#region CM option handlers
function updateMatchHighlightCount(cm, state) {
cm.display.wrapper.dataset.matchHighlightCount = state.matchesonscroll.matches.length;
}
function configureMouseFn(cm, repeat) {
return repeat === 'double' ?
{unit: selectTokenOnDoubleclick} :
{};
}
function selectTokenOnDoubleclick(cm, pos) {
let {ch} = pos;
const {line, sticky} = pos;
const {text, styles} = cm.getLineHandle(line);
const execAt = (rx, i) => (rx.lastIndex = i) && null || rx.exec(text);
const at = (rx, i) => (rx.lastIndex = i) && null || rx.test(text);
const atWord = ch => at(/\w/y, ch);
const atSpace = ch => at(/\s/y, ch);
const atTokenEnd = styles.indexOf(ch, 1);
ch += atTokenEnd < 0 ? 0 : sticky === 'before' && atWord(ch - 1) ? 0 : atSpace(ch + 1) ? 0 : 1;
ch = Math.min(text.length, ch);
const type = cm.getTokenTypeAt({line, ch: ch + (sticky === 'after' ? 1 : 0)});
if (atTokenEnd > 0) ch--;
const isCss = type && !/^(comment|string)/.test(type);
const isNumber = type === 'number';
const isSpace = atSpace(ch);
let wordChars =
isNumber ? /[-+\w.%]/y :
isCss ? /[-\w@]/y :
isSpace ? /\s/y :
atWord(ch) ? /\w/y : /[^\w\s]/y;
let a = ch;
while (a && at(wordChars, a)) a--;
a += !a && at(wordChars, a) || isCss && at(/[.!#@]/y, a) ? 0 : at(wordChars, a + 1);
let b, found;
if (isNumber) {
b = a + execAt(/[+-]?[\d.]+(e\d+)?|$/yi, a)[0].length;
found = b >= ch;
if (!found) {
a = b;
ch = a;
}
}
if (!found) {
wordChars = isCss ? /[-\w]*/y : new RegExp(wordChars.source + '*', 'uy');
b = ch + execAt(wordChars, ch)[0].length;
}
return {
from: {line, ch: a},
to: {line, ch: b},
};
}
//#endregion
//#region Bookmarks
const BM_CLS = 'gutter-bookmark';
const BM_BRAND = 'sublimeBookmark';
const BM_CLICKER = 'CodeMirror-linenumbers';
const BM_DATA = Symbol('data');
// TODO: revisit when https://github.com/codemirror/CodeMirror/issues/6716 is fixed
const tmProto = CodeMirror.TextMarker.prototype;
const tmProtoOvr = {};
for (const k of ['clear', 'attachLine', 'detachLine']) {
tmProtoOvr[k] = function (line) {
const {cm} = this.doc;
const withOp = !cm.curOp;
if (withOp) cm.startOperation();
tmProto[k].apply(this, arguments);
cm.curOp.ownsGroup.delayedCallbacks.push(toggleMark.bind(this, this.lines[0], line));
if (withOp) cm.endOperation();
};
}
for (const name of ['prevBookmark', 'nextBookmark']) {
const cmdFn = CodeMirror.commands[name];
CodeMirror.commands[name] = cm => {
cm.setSelection = cm.jumpToPos;
cmdFn(cm);
delete cm.setSelection;
};
}
CodeMirror.defineInitHook(cm => {
cm.on('gutterClick', onGutterClick);
cm.on('gutterContextMenu', onGutterContextMenu);
cm.on('markerAdded', onMarkAdded);
});
// TODO: reimplement bookmarking so next/prev order is decided solely by the line numbers
function onGutterClick(cm, line, name, e) {
switch (name === BM_CLICKER && e.button) {
case 0: {
// main button: toggle
const [mark] = cm.findMarks({line, ch: 0}, {line, ch: 1e9}, m => m[BM_BRAND]);
cm.setCursor(mark ? mark.find(-1) : {line, ch: 0});
cm.execCommand('toggleBookmark');
break;
}
case 1:
// middle button: select all marks
cm.execCommand('selectBookmarks');
break;
}
}
function onGutterContextMenu(cm, line, name, e) {
if (name === BM_CLICKER) {
cm.execCommand(e.ctrlKey ? 'prevBookmark' : 'nextBookmark');
e.preventDefault();
}
}
function onMarkAdded(cm, mark) {
if (mark[BM_BRAND]) {
// CM bug workaround to keep the mark at line start when the above line is removed
mark.inclusiveRight = true;
Object.assign(mark, tmProtoOvr);
toggleMark.call(mark, true, mark[BM_DATA] = mark.lines[0]);
}
}
function toggleMark(state, line = this[BM_DATA]) {
this.doc[state ? 'addLineClass' : 'removeLineClass'](line, 'gutter', BM_CLS);
if (state) {
const bms = this.doc.cm.state.sublimeBookmarks;
if (!bms.includes(this)) bms.push(this);
}
}
//#endregion
})();

71
edit/codemirror-themes.js Normal file

File diff suppressed because one or more lines are too long

View File

@ -1,115 +0,0 @@
/* global CodeMirror loadScript editors showHelp */
'use strict';
onDOMscriptReady('/colorview.js').then(() => {
onDOMready().then(() => {
$('#colorpicker-settings').onclick = configureColorpicker;
});
prefs.subscribe(['editor.colorpicker.hotkey'], registerHotkey);
prefs.subscribe(['editor.colorpicker'], setColorpickerOption);
setColorpickerOption(null, prefs.get('editor.colorpicker'));
function setColorpickerOption(id, enabled) {
const defaults = CodeMirror.defaults;
const keyName = prefs.get('editor.colorpicker.hotkey');
defaults.colorpicker = enabled;
if (enabled) {
if (keyName) {
CodeMirror.commands.colorpicker = invokeColorpicker;
defaults.extraKeys = defaults.extraKeys || {};
defaults.extraKeys[keyName] = 'colorpicker';
}
defaults.colorpicker = {
forceUpdate: editors.length > 0,
tooltip: t('colorpickerTooltip'),
popup: {
tooltipForSwitcher: t('colorpickerSwitchFormatTooltip'),
hexUppercase: prefs.get('editor.colorpicker.hexUppercase'),
hideDelay: 5000,
embedderCallback: state => {
['hexUppercase', 'color']
.filter(name => state[name] !== prefs.get('editor.colorpicker.' + name))
.forEach(name => prefs.set('editor.colorpicker.' + name, state[name]));
},
},
};
} else {
if (defaults.extraKeys) {
delete defaults.extraKeys[keyName];
}
}
// on page load runs before CodeMirror.setOption is defined
editors.forEach(cm => cm.setOption('colorpicker', defaults.colorpicker));
}
function registerHotkey(id, hotkey) {
CodeMirror.commands.colorpicker = invokeColorpicker;
const extraKeys = CodeMirror.defaults.extraKeys;
for (const key in extraKeys) {
if (extraKeys[key] === 'colorpicker') {
delete extraKeys[key];
break;
}
}
if (hotkey) {
extraKeys[hotkey] = 'colorpicker';
}
}
function invokeColorpicker(cm) {
cm.state.colorpicker.openPopup(prefs.get('editor.colorpicker.color'));
}
function configureColorpicker(event) {
event.preventDefault();
const input = $create('input', {
type: 'search',
spellcheck: false,
value: prefs.get('editor.colorpicker.hotkey'),
onkeydown(event) {
event.preventDefault();
event.stopPropagation();
const key = CodeMirror.keyName(event);
switch (key) {
case 'Enter':
if (this.checkValidity()) {
$('#help-popup .dismiss').onclick();
}
return;
case 'Esc':
$('#help-popup .dismiss').onclick();
return;
default:
// disallow: [Shift?] characters, modifiers-only, [modifiers?] + Esc, Tab, nav keys
if (!key || new RegExp('^(' + [
'(Back)?Space',
'(Shift-)?.', // a single character
'(Shift-?|Ctrl-?|Alt-?|Cmd-?){0,2}(|Esc|Tab|(Page)?(Up|Down)|Left|Right|Home|End|Insert|Delete)',
].join('|') + ')$', 'i').test(key)) {
this.value = key || this.value;
this.setCustomValidity('Not allowed');
return;
}
}
this.value = key;
this.setCustomValidity('');
prefs.set('editor.colorpicker.hotkey', key);
},
oninput() {
// fired on pressing "x" to clear the field
prefs.set('editor.colorpicker.hotkey', '');
},
onpaste(event) {
event.preventDefault();
}
});
const popup = showHelp(t('helpKeyMapHotkey'), input);
if (this instanceof Element) {
const bounds = this.getBoundingClientRect();
popup.style.left = bounds.right + 10 + 'px';
popup.style.top = bounds.top - popup.clientHeight / 2 + 'px';
popup.style.right = 'auto';
}
input.focus();
}
});

68
edit/drafts.js Normal file
View File

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

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

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

View File

@ -1,39 +1,176 @@
/* global createWorkerApi */// worker-util.js
'use strict';
// eslint-disable-next-line no-var
var editorWorker = (() => {
let worker;
return new Proxy({}, {
get: (target, prop) =>
(...args) => {
if (!worker) {
worker = createWorker();
(() => {
let sugarss = false;
/** @namespace EditorWorker */
createWorkerApi({
async csslint(code, config) {
require(['/js/csslint/parserlib', '/js/csslint/csslint']); /* global CSSLint */
return CSSLint
.verify(code, config).messages
.map(m => Object.assign(m, {rule: {id: m.rule.id}}));
},
getCssPropsValues() {
require(['/js/csslint/parserlib']); /* global parserlib */
const {
css: {Colors, GlobalKeywords, Properties},
util: {describeProp},
} = parserlib;
const namedColors = Object.keys(Colors);
const rxNonWord = /(?:<.+?>|[^-\w<(]+\d*)+/g;
const res = {};
// 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 = [];
// strip definitions of function arguments
const desc = describeProp(v).replace(/([-\w]+)\(.*?\)/g, 'z-$1');
const descNoColors = desc.replace(/<named-color>/g, '');
// add a prefix to functions to group them at the end
const words = descNoColors.split(rxNonWord).sort(cmp);
for (let w of words) {
if (w.startsWith('z-')) w = w.slice(2) + '(';
if (w !== last) uniq.push(last = w);
}
if (desc !== descNoColors) uniq.push(...namedColors);
if (uniq.length) res[k] = uniq;
}
return worker.invoke(prop, args);
}
return {all: res, global: GlobalKeywords};
},
getRules(linter) {
return ruleRetriever[linter](); // eslint-disable-line no-use-before-define
},
metalint(code) {
require(['/js/meta-parser']); /* global metaParser */
const result = metaParser.lint(code);
// extract needed info
result.errors = result.errors.map(err => ({
code: err.code,
args: err.args,
message: err.message,
index: err.index,
}));
return result;
},
async stylelint(opts) {
require(['/vendor/stylelint-bundle/stylelint-bundle.min']); /* global stylelint */
// Stylus-lang allows a trailing ";" but sugarss doesn't, so we monkeypatch it
stylelint.SugarSSParser.prototype.checkSemicolon = tt => {
while (tt.length && tt[tt.length - 1][0] === ';') tt.pop();
};
for (const pass of opts.mode === 'stylus' ? [sugarss, !sugarss] : [-1]) {
/* We try sugarss (for indented stylus-lang), then css mode, switching them on failure,
* so that the succeeding syntax will be used next time first. */
opts.config.customSyntax = !pass ? 'sugarss' : '';
try {
const res = await stylelint.createLinter(opts)._lintSource(opts);
if (pass !== -1) sugarss = pass;
return collectStylelintResults(res, opts);
} catch (e) {
const fatal = pass === -1 ||
!pass && !/^CssSyntaxError:.+?Unnecessary curly bracket/.test(e) ||
pass && !/^CssSyntaxError:.+?Unknown word[\s\S]*?\.decl\s/.test(`${e}${e.stack}`);
if (fatal) {
return [{
from: {line: e.line - 1, ch: e.column - 1},
to: {line: e.line - 1, ch: e.column - 1},
message: e.reason,
severity: 'error',
rule: e.name,
}];
}
}
}
},
});
function createWorker() {
let id = 0;
const pendingResponse = new Map();
const worker = new Worker('/edit/editor-worker-body.js');
worker.onmessage = e => {
const message = e.data;
pendingResponse.get(message.id)[message.error ? 'reject' : 'resolve'](message.data);
pendingResponse.delete(message.id);
};
return {invoke};
const ruleRetriever = {
function invoke(action, args) {
return new Promise((resolve, reject) => {
pendingResponse.set(id, {resolve, reject});
worker.postMessage({
id,
action,
args
});
id++;
csslint() {
require(['/js/csslint/csslint']);
return CSSLint.getRuleList().map(rule => {
const output = {};
for (const [key, value] of Object.entries(rule)) {
if (typeof value !== 'function') {
output[key] = value;
}
}
return output;
});
},
stylelint() {
require(['/vendor/stylelint-bundle/stylelint-bundle.min']);
const options = {};
const rxPossible = /\bpossible:("(?:[^"]*?)"|\[(?:[^\]]*?)\]|\{(?:[^}]*?)\})/g;
const rxString = /"([-\w\s]{3,}?)"/g;
for (const [id, rule] of Object.entries(stylelint.rules)) {
const ruleCode = `${rule()}`;
const sets = [];
let m, mStr;
while ((m = rxPossible.exec(ruleCode))) {
const possible = m[1];
const set = [];
while ((mStr = rxString.exec(possible))) {
const s = mStr[1];
if (s.includes(' ')) {
set.push(...s.split(/\s+/));
} else {
set.push(s);
}
}
if (possible.includes('ignoreAtRules')) {
set.push('ignoreAtRules');
}
if (possible.includes('ignoreShorthands')) {
set.push('ignoreShorthands');
}
if (set.length) {
sets.push(set);
}
}
options[id] = sets;
}
return options;
},
};
function collectStylelintResults({messages}, {mode}) {
/* We hide nonfatal "//" warnings since we lint with sugarss without applying @preprocessor.
* We can't easily pre-remove "//" comments which may be inside strings, comments, url(), etc.
* And even if we did, it'd be wrong to hide potential bugs in stylus-lang like #1460 */
const isLess = mode === 'text/x-less';
const slashCommentAllowed = isLess || mode === 'stylus';
const res = [];
for (const m of messages) {
if (/deprecation|invalidOption/.test(m.stylelintType)) {
continue;
}
const {rule} = m;
const msg = m.text.replace(/^Unexpected\s+/, '').replace(` (${rule})`, '');
if (slashCommentAllowed && msg.includes('"//"') ||
isLess && /^unknown at-rule "@[-\w]+:"/.test(msg) /* LESS variables */) {
continue;
}
res.push({
from: {line: m.line - 1, ch: m.column - 1},
to: {line: m.endLine - 1, ch: m.endColumn - 1},
message: msg[0].toUpperCase() + msg.slice(1),
severity: m.severity,
rule,
});
}
return res;
}
})();

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