');
152 |
153 | headings.each(function(i, heading) {
154 | var $h = $(heading);
155 |
156 | var anchor = $('
').attr('id', opts.anchorName(i, heading, opts.prefix) + ANCHOR_PREFIX).insertBefore($h);
157 |
158 | var span = $('
')
159 | .text(opts.headerText(i, heading, $h));
160 |
161 | //build TOC item
162 | var a = $('
')
163 | .append(span)
164 | .attr('href', '#' + opts.anchorName(i, heading, opts.prefix))
165 | .bind('click', function(e) {
166 | scrollTo(e);
167 | el.trigger('selected', $(this).attr('href'));
168 | });
169 |
170 | span.addClass(opts.itemClass(i, heading, $h, opts.prefix));
171 |
172 | tocs.push(a);
173 |
174 | ul.append(a);
175 | });
176 | el.html(ul);
177 |
178 | calcHadingOffsets();
179 | });
180 | };
181 |
182 |
183 | jQuery.fn.toc.defaults = {
184 | container: 'body',
185 | selectors: 'h1,h2,h3',
186 | smoothScrolling: true,
187 | prefix: 'toc',
188 | onHighlight: function() {},
189 | highlightOnScroll: true,
190 | navbarOffset: 0,
191 | anchorName: function(i, heading, prefix) {
192 | return prefix+i;
193 | },
194 | headerText: function(i, heading, $heading) {
195 | return $heading.text();
196 | },
197 | itemClass: function(i, heading, $heading, prefix) {
198 | return prefix + '-' + $heading[0].tagName.toLowerCase();
199 | }
200 |
201 | };
202 |
203 | })(jQuery);
204 |
--------------------------------------------------------------------------------
/docs/jsapi/styles/jsdoc-default.css:
--------------------------------------------------------------------------------
1 | @font-face {
2 | font-family: 'Open Sans';
3 | font-weight: normal;
4 | font-style: normal;
5 | src: url('../fonts/OpenSans-Regular-webfont.eot');
6 | src:
7 | local('Open Sans'),
8 | local('OpenSans'),
9 | url('../fonts/OpenSans-Regular-webfont.eot?#iefix') format('embedded-opentype'),
10 | url('../fonts/OpenSans-Regular-webfont.woff') format('woff'),
11 | url('../fonts/OpenSans-Regular-webfont.svg#open_sansregular') format('svg');
12 | }
13 |
14 | @font-face {
15 | font-family: 'Open Sans Light';
16 | font-weight: normal;
17 | font-style: normal;
18 | src: url('../fonts/OpenSans-Light-webfont.eot');
19 | src:
20 | local('Open Sans Light'),
21 | local('OpenSans Light'),
22 | url('../fonts/OpenSans-Light-webfont.eot?#iefix') format('embedded-opentype'),
23 | url('../fonts/OpenSans-Light-webfont.woff') format('woff'),
24 | url('../fonts/OpenSans-Light-webfont.svg#open_sanslight') format('svg');
25 | }
26 |
27 | html
28 | {
29 | overflow: auto;
30 | background-color: #fff;
31 | font-size: 14px;
32 | }
33 |
34 | body
35 | {
36 | font-family: 'Open Sans', sans-serif;
37 | line-height: 1.5;
38 | color: #4d4e53;
39 | background-color: white;
40 | }
41 |
42 | a, a:visited, a:active {
43 | color: #0095dd;
44 | text-decoration: none;
45 | }
46 |
47 | a:hover {
48 | text-decoration: underline;
49 | }
50 |
51 | header
52 | {
53 | display: block;
54 | padding: 0px 4px;
55 | }
56 |
57 | tt, code, kbd, samp {
58 | font-family: Consolas, Monaco, 'Andale Mono', monospace;
59 | }
60 |
61 | .class-description {
62 | font-size: 130%;
63 | line-height: 140%;
64 | margin-bottom: 1em;
65 | margin-top: 1em;
66 | }
67 |
68 | .class-description:empty {
69 | margin: 0;
70 | }
71 |
72 | #main {
73 | float: left;
74 | width: 70%;
75 | }
76 |
77 | article dl {
78 | margin-bottom: 40px;
79 | }
80 |
81 | article img {
82 | max-width: 100%;
83 | }
84 |
85 | section
86 | {
87 | display: block;
88 | background-color: #fff;
89 | padding: 12px 24px;
90 | border-bottom: 1px solid #ccc;
91 | margin-right: 30px;
92 | }
93 |
94 | .variation {
95 | display: none;
96 | }
97 |
98 | .signature-attributes {
99 | font-size: 60%;
100 | color: #aaa;
101 | font-style: italic;
102 | font-weight: lighter;
103 | }
104 |
105 | nav
106 | {
107 | display: block;
108 | float: right;
109 | margin-top: 28px;
110 | width: 30%;
111 | box-sizing: border-box;
112 | border-left: 1px solid #ccc;
113 | padding-left: 16px;
114 | }
115 |
116 | nav ul {
117 | font-family: 'Lucida Grande', 'Lucida Sans Unicode', arial, sans-serif;
118 | font-size: 100%;
119 | line-height: 17px;
120 | padding: 0;
121 | margin: 0;
122 | list-style-type: none;
123 | }
124 |
125 | nav ul a, nav ul a:visited, nav ul a:active {
126 | font-family: Consolas, Monaco, 'Andale Mono', monospace;
127 | line-height: 18px;
128 | color: #4D4E53;
129 | }
130 |
131 | nav h3 {
132 | margin-top: 12px;
133 | }
134 |
135 | nav li {
136 | margin-top: 6px;
137 | }
138 |
139 | footer {
140 | display: block;
141 | padding: 6px;
142 | margin-top: 12px;
143 | font-style: italic;
144 | font-size: 90%;
145 | }
146 |
147 | h1, h2, h3, h4 {
148 | font-weight: 200;
149 | margin: 0;
150 | }
151 |
152 | h1
153 | {
154 | font-family: 'Open Sans Light', sans-serif;
155 | font-size: 48px;
156 | letter-spacing: -2px;
157 | margin: 12px 24px 20px;
158 | }
159 |
160 | h2, h3.subsection-title
161 | {
162 | font-size: 30px;
163 | font-weight: 700;
164 | letter-spacing: -1px;
165 | margin-bottom: 12px;
166 | }
167 |
168 | h3
169 | {
170 | font-size: 24px;
171 | letter-spacing: -0.5px;
172 | margin-bottom: 12px;
173 | }
174 |
175 | h4
176 | {
177 | font-size: 18px;
178 | letter-spacing: -0.33px;
179 | margin-bottom: 12px;
180 | color: #4d4e53;
181 | }
182 |
183 | h5, .container-overview .subsection-title
184 | {
185 | font-size: 120%;
186 | font-weight: bold;
187 | letter-spacing: -0.01em;
188 | margin: 8px 0 3px 0;
189 | }
190 |
191 | h6
192 | {
193 | font-size: 100%;
194 | letter-spacing: -0.01em;
195 | margin: 6px 0 3px 0;
196 | font-style: italic;
197 | }
198 |
199 | table
200 | {
201 | border-spacing: 0;
202 | border: 0;
203 | border-collapse: collapse;
204 | }
205 |
206 | td, th
207 | {
208 | border: 1px solid #ddd;
209 | margin: 0px;
210 | text-align: left;
211 | vertical-align: top;
212 | padding: 4px 6px;
213 | display: table-cell;
214 | }
215 |
216 | thead tr
217 | {
218 | background-color: #ddd;
219 | font-weight: bold;
220 | }
221 |
222 | th { border-right: 1px solid #aaa; }
223 | tr > th:last-child { border-right: 1px solid #ddd; }
224 |
225 | .ancestors, .attribs { color: #999; }
226 | .ancestors a, .attribs a
227 | {
228 | color: #999 !important;
229 | text-decoration: none;
230 | }
231 |
232 | .clear
233 | {
234 | clear: both;
235 | }
236 |
237 | .important
238 | {
239 | font-weight: bold;
240 | color: #950B02;
241 | }
242 |
243 | .yes-def {
244 | text-indent: -1000px;
245 | }
246 |
247 | .type-signature {
248 | color: #aaa;
249 | }
250 |
251 | .name, .signature {
252 | font-family: Consolas, Monaco, 'Andale Mono', monospace;
253 | }
254 |
255 | .details { margin-top: 14px; border-left: 2px solid #DDD; }
256 | .details dt { width: 120px; float: left; padding-left: 10px; padding-top: 6px; }
257 | .details dd { margin-left: 70px; }
258 | .details ul { margin: 0; }
259 | .details ul { list-style-type: none; }
260 | .details li { margin-left: 30px; padding-top: 6px; }
261 | .details pre.prettyprint { margin: 0 }
262 | .details .object-value { padding-top: 0; }
263 |
264 | .description {
265 | margin-bottom: 1em;
266 | margin-top: 1em;
267 | }
268 |
269 | .code-caption
270 | {
271 | font-style: italic;
272 | font-size: 107%;
273 | margin: 0;
274 | }
275 |
276 | .prettyprint
277 | {
278 | border: 1px solid #ddd;
279 | width: 80%;
280 | overflow: auto;
281 | }
282 |
283 | .prettyprint.source {
284 | width: inherit;
285 | }
286 |
287 | .prettyprint code
288 | {
289 | font-size: 100%;
290 | line-height: 18px;
291 | display: block;
292 | padding: 4px 12px;
293 | margin: 0;
294 | background-color: #fff;
295 | color: #4D4E53;
296 | }
297 |
298 | .prettyprint code span.line
299 | {
300 | display: inline-block;
301 | }
302 |
303 | .prettyprint.linenums
304 | {
305 | padding-left: 70px;
306 | -webkit-user-select: none;
307 | -moz-user-select: none;
308 | -ms-user-select: none;
309 | user-select: none;
310 | }
311 |
312 | .prettyprint.linenums ol
313 | {
314 | padding-left: 0;
315 | }
316 |
317 | .prettyprint.linenums li
318 | {
319 | border-left: 3px #ddd solid;
320 | }
321 |
322 | .prettyprint.linenums li.selected,
323 | .prettyprint.linenums li.selected *
324 | {
325 | background-color: lightyellow;
326 | }
327 |
328 | .prettyprint.linenums li *
329 | {
330 | -webkit-user-select: text;
331 | -moz-user-select: text;
332 | -ms-user-select: text;
333 | user-select: text;
334 | }
335 |
336 | .params .name, .props .name, .name code {
337 | color: #4D4E53;
338 | font-family: Consolas, Monaco, 'Andale Mono', monospace;
339 | font-size: 100%;
340 | }
341 |
342 | .params td.description > p:first-child,
343 | .props td.description > p:first-child
344 | {
345 | margin-top: 0;
346 | padding-top: 0;
347 | }
348 |
349 | .params td.description > p:last-child,
350 | .props td.description > p:last-child
351 | {
352 | margin-bottom: 0;
353 | padding-bottom: 0;
354 | }
355 |
356 | .disabled {
357 | color: #454545;
358 | }
359 |
--------------------------------------------------------------------------------
/docs/jsapi/styles/prettify-jsdoc.css:
--------------------------------------------------------------------------------
1 | /* JSDoc prettify.js theme */
2 |
3 | /* plain text */
4 | .pln {
5 | color: #000000;
6 | font-weight: normal;
7 | font-style: normal;
8 | }
9 |
10 | /* string content */
11 | .str {
12 | color: #006400;
13 | font-weight: normal;
14 | font-style: normal;
15 | }
16 |
17 | /* a keyword */
18 | .kwd {
19 | color: #000000;
20 | font-weight: bold;
21 | font-style: normal;
22 | }
23 |
24 | /* a comment */
25 | .com {
26 | font-weight: normal;
27 | font-style: italic;
28 | }
29 |
30 | /* a type name */
31 | .typ {
32 | color: #000000;
33 | font-weight: normal;
34 | font-style: normal;
35 | }
36 |
37 | /* a literal value */
38 | .lit {
39 | color: #006400;
40 | font-weight: normal;
41 | font-style: normal;
42 | }
43 |
44 | /* punctuation */
45 | .pun {
46 | color: #000000;
47 | font-weight: bold;
48 | font-style: normal;
49 | }
50 |
51 | /* lisp open bracket */
52 | .opn {
53 | color: #000000;
54 | font-weight: bold;
55 | font-style: normal;
56 | }
57 |
58 | /* lisp close bracket */
59 | .clo {
60 | color: #000000;
61 | font-weight: bold;
62 | font-style: normal;
63 | }
64 |
65 | /* a markup tag name */
66 | .tag {
67 | color: #006400;
68 | font-weight: normal;
69 | font-style: normal;
70 | }
71 |
72 | /* a markup attribute name */
73 | .atn {
74 | color: #006400;
75 | font-weight: normal;
76 | font-style: normal;
77 | }
78 |
79 | /* a markup attribute value */
80 | .atv {
81 | color: #006400;
82 | font-weight: normal;
83 | font-style: normal;
84 | }
85 |
86 | /* a declaration */
87 | .dec {
88 | color: #000000;
89 | font-weight: bold;
90 | font-style: normal;
91 | }
92 |
93 | /* a variable name */
94 | .var {
95 | color: #000000;
96 | font-weight: normal;
97 | font-style: normal;
98 | }
99 |
100 | /* a function name */
101 | .fun {
102 | color: #000000;
103 | font-weight: bold;
104 | font-style: normal;
105 | }
106 |
107 | /* Specify class=linenums on a pre to get line numbering */
108 | ol.linenums {
109 | margin-top: 0;
110 | margin-bottom: 0;
111 | }
112 |
--------------------------------------------------------------------------------
/docs/jsapi/styles/prettify-tomorrow.css:
--------------------------------------------------------------------------------
1 | /* Tomorrow Theme */
2 | /* Original theme - https://github.com/chriskempson/tomorrow-theme */
3 | /* Pretty printing styles. Used with prettify.js. */
4 | /* SPAN elements with the classes below are added by prettyprint. */
5 | /* plain text */
6 | .pln {
7 | color: #4d4d4c; }
8 |
9 | @media screen {
10 | /* string content */
11 | .str {
12 | color: #718c00; }
13 |
14 | /* a keyword */
15 | .kwd {
16 | color: #8959a8; }
17 |
18 | /* a comment */
19 | .com {
20 | color: #8e908c; }
21 |
22 | /* a type name */
23 | .typ {
24 | color: #4271ae; }
25 |
26 | /* a literal value */
27 | .lit {
28 | color: #f5871f; }
29 |
30 | /* punctuation */
31 | .pun {
32 | color: #4d4d4c; }
33 |
34 | /* lisp open bracket */
35 | .opn {
36 | color: #4d4d4c; }
37 |
38 | /* lisp close bracket */
39 | .clo {
40 | color: #4d4d4c; }
41 |
42 | /* a markup tag name */
43 | .tag {
44 | color: #c82829; }
45 |
46 | /* a markup attribute name */
47 | .atn {
48 | color: #f5871f; }
49 |
50 | /* a markup attribute value */
51 | .atv {
52 | color: #3e999f; }
53 |
54 | /* a declaration */
55 | .dec {
56 | color: #f5871f; }
57 |
58 | /* a variable name */
59 | .var {
60 | color: #c82829; }
61 |
62 | /* a function name */
63 | .fun {
64 | color: #4271ae; } }
65 | /* Use higher contrast and text-weight for printable form. */
66 | @media print, projection {
67 | .str {
68 | color: #060; }
69 |
70 | .kwd {
71 | color: #006;
72 | font-weight: bold; }
73 |
74 | .com {
75 | color: #600;
76 | font-style: italic; }
77 |
78 | .typ {
79 | color: #404;
80 | font-weight: bold; }
81 |
82 | .lit {
83 | color: #044; }
84 |
85 | .pun, .opn, .clo {
86 | color: #440; }
87 |
88 | .tag {
89 | color: #006;
90 | font-weight: bold; }
91 |
92 | .atn {
93 | color: #404; }
94 |
95 | .atv {
96 | color: #060; } }
97 | /* Style */
98 | /*
99 | pre.prettyprint {
100 | background: white;
101 | font-family: Menlo, Monaco, Consolas, monospace;
102 | font-size: 12px;
103 | line-height: 1.5;
104 | border: 1px solid #ccc;
105 | padding: 10px; }
106 | */
107 |
108 | /* Specify class=linenums on a pre to get line numbering */
109 | ol.linenums {
110 | margin-top: 0;
111 | margin-bottom: 0; }
112 |
113 | /* IE indents via margin-left */
114 | li.L0,
115 | li.L1,
116 | li.L2,
117 | li.L3,
118 | li.L4,
119 | li.L5,
120 | li.L6,
121 | li.L7,
122 | li.L8,
123 | li.L9 {
124 | /* */ }
125 |
126 | /* Alternate shading for lines */
127 | li.L1,
128 | li.L3,
129 | li.L5,
130 | li.L7,
131 | li.L9 {
132 | /* */ }
133 |
--------------------------------------------------------------------------------
/docs/jsapi/styles/sunlight.default.css:
--------------------------------------------------------------------------------
1 | /* global styles */
2 | .sunlight-container {
3 | clear: both !important;
4 | position: relative !important;
5 | margin: 10px 0 !important;
6 | }
7 | .sunlight-code-container {
8 | clear: both !important;
9 | position: relative !important;
10 | border: none;
11 | border-color: #969696 !important;
12 | background-color: #FFFFFF !important;
13 | }
14 | .sunlight-highlighted, .sunlight-container, .sunlight-container textarea {
15 | font-family: Consolas, Inconsolata, Monaco, "Courier New" !important;
16 | font-size: 12px !important;
17 | line-height: 15px !important;
18 | }
19 | .sunlight-highlighted, .sunlight-container textarea {
20 | color: #000000 !important;
21 | margin: 0 !important;
22 | }
23 | .sunlight-container textarea {
24 | padding-left: 0 !important;
25 | margin-left: 0 !important;
26 | margin-right: 0 !important;
27 | padding-right: 0 !important;
28 | }
29 | .sunlight-code-container > .sunlight-highlighted {
30 | white-space: pre;
31 | overflow-x: auto;
32 | overflow-y: hidden; /* ie requires this wtf? */
33 | }
34 | .sunlight-highlighted {
35 | z-index: 1;
36 | position: relative;
37 | }
38 | .sunlight-highlighted * {
39 | background: transparent;
40 | }
41 | .sunlight-line-number-margin {
42 | float: left !important;
43 | margin-right: 5px !important;
44 | margin-top: 0 !important;
45 | margin-bottom: 0 !important;
46 | padding: 0 !important;
47 | padding-right: 4px !important;
48 | padding-left: 4px !important;
49 | border-right: 1px solid #CCCCCC !important;
50 | background-color: #EEEEEE !important;
51 | color: #848484 !important;
52 | text-align: right !important;
53 | position: relative;
54 | z-index: 3;
55 | }
56 | .sunlight-highlighted a, .sunlight-line-number-margin a {
57 | border: none !important;
58 | text-decoration: none !important;
59 | font-weight: normal !important;
60 | font-style: normal !important;
61 | padding: 0 !important;
62 | }
63 | .sunlight-line-number-margin a {
64 | color: inherit !important;
65 | }
66 | .sunlight-line-highlight-overlay {
67 | position: absolute;
68 | top: 0;
69 | left: 0;
70 | width: 100%;
71 | z-index: 0;
72 | }
73 | .sunlight-line-highlight-overlay div {
74 | height: 15px;
75 | width: 100%;
76 | }
77 | .sunlight-line-highlight-overlay .sunlight-line-highlight-active {
78 | background-color: #E7FCFA;
79 | }
80 |
81 | /* menu */
82 | .sunlight-menu {
83 | background-color: #FFFFCC;
84 | color: #000000;
85 | }
86 | .sunlight-menu ul {
87 | margin: 0 !important;
88 | padding: 0 !important;
89 | list-style-type: none !important;
90 | }
91 | .sunlight-menu li {
92 | float: right !important;
93 | margin-left: 5px !important;
94 | }
95 | .sunlight-menu a, .sunlight-menu img {
96 | color: #000099 !important;
97 | text-decoration: none !important;
98 | border: none !important;
99 | }
100 |
101 |
102 |
103 |
104 | .sunlight-string,
105 | .sunlight-char,
106 | .sunlight-heredoc,
107 | .sunlight-heredocDeclaration,
108 | .sunlight-nowdoc,
109 | .sunlight-longString,
110 | .sunlight-rawString,
111 | .sunlight-binaryString,
112 | .sunlight-rawLongString,
113 | .sunlight-binaryLongString,
114 | .sunlight-verbatimString,
115 | .sunlight-diff .sunlight-removed {
116 | color: #990000 !important;
117 | }
118 |
119 | .sunlight-ident,
120 | .sunlight-operator,
121 | .sunlight-punctuation,
122 | .sunlight-delimiter,
123 | .sunlight-diff .sunlight-unchanged {
124 | color: #000000 !important;
125 | }
126 |
127 | .sunlight-comment,
128 | .sunlight-xmlDocCommentContent,
129 | .sunlight-nginx .sunlight-ssiCommand,
130 | .sunlight-sln .sunlight-formatDeclaration,
131 | .sunlight-diff .sunlight-added {
132 | color: #009900 !important;
133 | }
134 | .sunlight-number,
135 | .sunlight-guid,
136 | .sunlight-cdata {
137 | color: #CC6600 !important;
138 | }
139 |
140 | .sunlight-named-ident,
141 | .sunlight-constant,
142 | .sunlight-javascript .sunlight-globalVariable,
143 | .sunlight-globalObject,
144 | .sunlight-python .sunlight-attribute,
145 | .sunlight-nginx .sunlight-context,
146 | .sunlight-httpd .sunlight-context,
147 | .sunlight-haskell .sunlight-class,
148 | .sunlight-haskell .sunlight-type,
149 | .sunlight-lisp .sunlight-declarationSpecifier,
150 | .sunlight-erlang .sunlight-userDefinedFunction,
151 | .sunlight-diff .sunlight-header {
152 | color: #2B91AF !important;
153 | }
154 | .sunlight-keyword,
155 | .sunlight-languageConstruct,
156 | .sunlight-css
157 | .sunlight-element,
158 | .sunlight-bash .sunlight-command,
159 | .sunlight-specialOperator,
160 | .sunlight-erlang .sunlight-moduleAttribute,
161 | .sunlight-xml .sunlight-tagName,
162 | .sunlight-xml .sunlight-operator,
163 | .sunlight-diff .sunlight-modified {
164 | color: #0000FF !important;
165 | }
166 | .sunlight-shortOpenTag,
167 | .sunlight-openTag,
168 | .sunlight-closeTag,
169 | .sunlight-xmlOpenTag,
170 | .sunlight-xmlCloseTag,
171 | .sunlight-aspOpenTag,
172 | .sunlight-aspCloseTag,
173 | .sunlight-label,
174 | .sunlight-css .sunlight-importantFlag {
175 | background-color: #FFFF99 !important;
176 | color: #000000 !important;
177 | }
178 | .sunlight-function,
179 | .sunlight-globalFunction,
180 | .sunlight-ruby .sunlight-specialFunction,
181 | .sunlight-objective-c .sunlight-messageDestination,
182 | .sunlight-6502asm .sunlight-illegalOpcode,
183 | .sunlight-powershell .sunlight-switch,
184 | .sunlight-lisp .sunlight-macro,
185 | .sunlight-lisp .sunlight-specialForm,
186 | .sunlight-lisp .sunlight-type,
187 | .sunlight-sln .sunlight-sectionName,
188 | .sunlight-diff .sunlight-rangeInfo {
189 | color: #B069AF !important;
190 | }
191 |
192 | .sunlight-variable,
193 | .sunlight-specialVariable,
194 | .sunlight-environmentVariable,
195 | .sunlight-objective-c .sunlight-messageArgumentName,
196 | .sunlight-lisp .sunlight-globalVariable,
197 | .sunlight-ruby .sunlight-globalVariable,
198 | .sunlight-ruby .sunlight-instanceVariable,
199 | .sunlight-sln .sunlight-operator {
200 | color: #325484 !important;
201 | }
202 | .sunlight-regexLiteral,
203 | .sunlight-lisp .sunlight-operator,
204 | .sunlight-6502asm .sunlight-pseudoOp,
205 | .sunlight-erlang .sunlight-macro {
206 | color: #FF00B2 !important;
207 | }
208 | .sunlight-specialVariable {
209 | font-style: italic !important;
210 | font-weight: bold !important;
211 | }
212 | .sunlight-csharp .sunlight-pragma,
213 | .sunlight-preprocessorDirective,
214 | .sunlight-vb .sunlight-compilerDirective,
215 | .sunlight-diff .sunlight-mergeHeader,
216 | .sunlight-diff .sunlight-noNewLine {
217 | color: #999999 !important;
218 | font-style: italic !important;
219 | }
220 | .sunlight-xmlDocCommentMeta,
221 | .sunlight-java .sunlight-annotation,
222 | .sunlight-scala .sunlight-annotation,
223 | .sunlight-docComment {
224 | color: #808080 !important;
225 | }
226 | .sunlight-quotedIdent,
227 | .sunlight-ruby .sunlight-subshellCommand,
228 | .sunlight-lisp .sunlight-keywordArgument,
229 | .sunlight-haskell .sunlight-infixOperator,
230 | .sunlight-erlang .sunlight-quotedAtom {
231 | color: #999900 !important;
232 | }
233 |
234 |
235 |
236 | /* xml */
237 | .sunlight-xml .sunlight-string {
238 | color: #990099 !important;
239 | }
240 | .sunlight-xml .sunlight-attribute {
241 | color: #FF0000 !important;
242 | }
243 | .sunlight-xml .sunlight-entity {
244 | background-color: #EEEEEE !important;
245 | color: #000000 !important;
246 | border: 1px solid #000000 !important;
247 | }
248 | .sunlight-xml .sunlight-doctype {
249 | color: #2B91AF !important;
250 | }
251 |
252 | /* javascript */
253 | .sunlight-javascript .sunlight-reservedWord {
254 | font-style: italic !important;
255 | }
256 |
257 | /* css */
258 | .sunlight-css .sunlight-microsoftFilterPrefix {
259 | color: #FF00FF !important;
260 | }
261 | .sunlight-css .sunlight-rule {
262 | color: #0099FF !important;
263 | }
264 | .sunlight-css .sunlight-keyword {
265 | color: #4E65B8 !important;
266 | }
267 | .sunlight-css .sunlight-class {
268 | color: #FF0000 !important;
269 | }
270 | .sunlight-css .sunlight-id {
271 | color: #8A8E13 !important;
272 | }
273 | .sunlight-css .sunlight-pseudoClass,
274 | .sunlight-css .sunlight-pseudoElement {
275 | color: #368B87 !important;
276 | }
277 |
278 | /* bash */
279 | .sunlight-bash .sunlight-hashBang {
280 | color: #3D97F5 !important;
281 | }
282 | .sunlight-bash .sunlight-verbatimCommand {
283 | color: #999900 !important;
284 | }
285 | .sunlight-bash .sunlight-variable,
286 | .sunlight-bash .sunlight-specialVariable {
287 | color: #FF0000 !important;
288 | }
289 |
290 | /* python */
291 | .sunlight-python .sunlight-specialMethod {
292 | font-weight: bold !important;
293 | color: #A07DD3;
294 | }
295 |
296 | /* ruby */
297 | .sunlight-ruby .sunlight-symbol {
298 | font-weight: bold !important;
299 | color: #ED7272 !important;
300 | }
301 |
302 | /* brainfuck */
303 | .sunlight-brainfuck {
304 | font-weight: bold !important;
305 | color: #000000 !important;
306 | }
307 | .sunlight-brainfuck .sunlight-increment {
308 | background-color: #FF9900 !important;
309 | }
310 | .sunlight-brainfuck .sunlight-decrement {
311 | background-color: #FF99FF !important;
312 | }
313 | .sunlight-brainfuck .sunlight-incrementPointer {
314 | background-color: #FFFF99 !important;
315 | }
316 | .sunlight-brainfuck .sunlight-decrementPointer {
317 | background-color: #66CCFF !important;
318 | }
319 | .sunlight-brainfuck .sunlight-read {
320 | background-color: #FFFFFF !important;
321 | }
322 | .sunlight-brainfuck .sunlight-write {
323 | background-color: #99FF99 !important;
324 | }
325 | .sunlight-brainfuck .sunlight-openLoop, .sunlight-brainfuck .sunlight-closeLoop {
326 | background-color: #FFFFFF !important;
327 | }
328 |
329 | /* 6502 asm */
330 | .sunlight-6502asm .sunlight-label {
331 | font-weight: bold !important;
332 | color: #000000 !important;
333 | background: none !important;
334 | }
335 |
336 | /* lisp */
337 | .sunlight-lisp .sunlight-macro {
338 | font-style: italic !important;
339 | }
340 |
341 | /* erlang */
342 | .sunlight-erlang .sunlight-atom {
343 | font-weight: bold !important;
344 | }
--------------------------------------------------------------------------------
/docs/testMenu.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 | nvUltra Beta
13 |
14 |
15 | About nvUltra
16 | Check For Updates
17 |
18 | Preferences…
19 |
20 | Services [removed]
21 |
22 | Hide nvUltra
23 | Hide Others
24 | Show All
25 |
26 | Quit nvUltra
27 | Quit and Close All Windows
28 |
29 | File
30 |
31 |
32 | New Folder
33 | Open Folder…
34 | Move Selected File(s) to Trash
35 | Search/Create
36 | Open Recent
37 |
38 |
39 | content
40 | nvALT2.2
41 |
42 | Clear Menu
43 |
44 |
45 | Edit File(s) in MultiMarkdown Composer
46 | Preview File(s) in Marked
47 |
48 | Close
49 | Close All
50 |
51 | Page Setup…
52 | Print…
53 | Print Preview Using Browser…
54 |
55 | Edit
56 |
57 |
58 | Undo
59 | Redo
60 |
61 | Cut
62 | Copy
63 | Copy As
64 |
65 |
66 | Copy As HTML
67 | Copy As LaTeX
68 |
69 | Paste
70 | Paste As
71 |
72 |
73 | Paste As Blockquote
74 | Paste As Bulleted List
75 | Paste As Enumerated List
76 | Paste As Table
77 | Paste As Table Rows
78 |
79 | Paste and Match Style
80 | Adjust Selected Line(s)
81 |
82 |
83 | Decrease ATX Header Level
84 | Increase ATX Header Level
85 |
86 | Selection
87 |
88 |
89 | Select All
90 | Increase Selection
91 | Decrease Selection
92 |
93 | Move to Next Paragraph
94 | Move to Previous Paragraph
95 |
96 | Move Selected Text
97 |
98 |
99 | Shift Left
100 | Shift Right
101 | Shift Up
102 | Shift Down
103 |
104 | Delete
105 | Select All
106 |
107 | Find
108 |
109 |
110 | Find…
111 | Find and Replace…
112 | Find Next
113 | Find Previous
114 | Use Selection for Find
115 | Jump to Selection
116 |
117 | Spelling and Grammar
118 |
119 |
120 | Show Spelling and Grammar
121 | Check Document Now
122 |
123 | Check Spelling While Typing
124 | Check Grammar With Spelling
125 | Correct Spelling Automatically
126 |
127 | Substitutions
128 |
129 |
130 | Show Substitutions
131 |
132 | Smart Copy/Paste
133 | Smart Quotes
134 | Smart Dashes
135 | Smart Links
136 | Data Detectors
137 | Text Replacement
138 |
139 | Transformations
140 |
141 |
142 | Make Upper Case
143 | Make Lower Case
144 | Capitalize
145 |
146 | Speech
147 |
148 |
149 | Start Speaking
150 | Stop Speaking
151 |
152 |
153 | Start Dictation…
154 | Emoji & Symbols
155 |
156 | Format
157 |
158 |
159 | Bold
160 | Italic
161 | Cleanup
162 |
163 |
164 | Selected HTML Entities
165 | Selected List(s)
166 | Selected Metadata
167 | Selected Paragraph(s)
168 | Selected “Smart Typography”
169 | Selected Table(s)
170 |
171 | Full Cleanup
172 |
173 |
174 | Highlight Selection for CriticMarkup
175 | Insert CriticMarkup Comment
176 |
177 | Apply Lower Case to Selection
178 | Apply Sentence Case to Selection
179 | Apply Title Case to Selection
180 | Apply Upper Case to Selection
181 |
182 | Convert Selection To
183 |
184 |
185 | Convert to ATX Header
186 | Convert to Blockquote
187 | Convert to Bulleted List
188 | Convert to Enumerated List
189 | Convert to Fenced Code Block
190 | Convert to Regular Text
191 |
192 | Toggle List Type
193 | Toggle List Indent to Space
194 | Toggle List Indent to Tab
195 |
196 | CriticMarkup
197 |
198 |
199 | Accept Selected Change(s)
200 | Reject Selected Change(s)
201 |
202 | Move Selection to Previous Change
203 | Move Selection to Next Change
204 |
205 |
206 | View
207 |
208 |
209 | Show Tab Bar
210 | Show All Tabs
211 |
212 | Typewriter mode
213 | Auto Zoom
214 | Zoom
215 |
216 |
217 | Zoom Editor In
218 | Zoom Editor Out
219 |
220 |
221 | Show Line Numbers
222 | Show Paragraph Numbers
223 |
224 | Theme
225 |
226 |
227 | Default
228 | Default-Dark
229 | Default-Dark-FTP
230 | Default-FTP
231 | Empty
232 | Modern Red
233 | Pretentious
234 | Printing
235 | Solarized
236 | Solarized-Dark
237 |
238 |
239 | Show Invisible Characters
240 | Show Linebreaks
241 |
242 | Track Changes
243 | Enter Full Screen
244 |
245 | Window
246 |
247 |
248 | Minimize
249 | Minimize All
250 | Zoom
251 | Zoom All
252 |
253 | Show Previous Tab
254 | Show Next Tab
255 | Move Tab to New Window
256 | Merge All Windows
257 |
258 | Toggle File List
259 | Toggle Info
260 | Toggle Preview
261 | Refresh Preview
262 |
263 | Bring All to Front
264 | Arrange in Front
265 |
266 | content
267 | nvALT2.2
268 |
269 | Help
270 |
271 |
278 |
279 |
280 |
Tutorial
281 |
282 | Hover to reveal menus
283 | Click to lock in place
284 | Double click to highlight
285 | Hold shift and double click to highlight hierarchy
286 | Option-click to add arrow
287 | Use the help menu to (fuzzy) search menu items
288 | See the settings menu (lower right)
289 |
290 |
291 |
292 |
296 |
297 | Dark Mode (D)
298 | Exposé (E)
299 | Background Image
300 | Arrow style: Arrow
301 |
302 |
303 |
304 |
305 |
306 |
307 |
308 |
--------------------------------------------------------------------------------
/javascript/.jshintrc:
--------------------------------------------------------------------------------
1 | {
2 | "globals": {
3 | "$": false
4 | }
5 | }
6 |
--------------------------------------------------------------------------------
/javascript/README.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
The primary namespace is NiftyAPI
, which is a chainable set of functions covering searching, clicking, and callouts.
11 |
12 |
Configuration functions
13 |
14 |
Methods that adjust the display do not require an element to be passed, but can still be chained in with other functions. The primary display command is config()
, which accepts an object containing keys for any/all of the display options. Example:
15 |
16 |
NiftyAPI.config({
17 | 'arrowStyle': 'arrow',
18 | 'bgImage': true,
19 | 'expose': false,
20 | 'darkMode': false,
21 | 'wallpaper': 'default'
22 | });
23 |
24 |
25 |
There are shorcuts for Dark Mode (NiftyAPI.darkMode()
) and Exposé (NiftyAPI.expose()
).
26 |
27 |
Chainable functions
28 |
29 |
All chainable functions should start with find
(or a variable containing the result of a find
). The found element can then have one or more callout functions applied:
30 |
31 |
32 | lock()
(equivalent to a click)
33 | callout()
(equivalent to a double click)
34 | arrow()
(equivalent to an Option-click)
35 | shortcut()
(equivalent to Option-clicking a keyboard shortcut)
36 |
37 |
38 |
Example:
39 |
40 |
// Find the File->Save menu item and apply a callout ring to it
41 | // and all parent elements up the chain
42 | NiftyAPI.find('file/save').callout(true, true);
43 |
44 |
45 |
All callout functions can be passed a boolean parameter to enable/disable the callout. Running NiftyAPI.clear()
will disable all callouts and clicks.
46 |
47 |
In the case of callout()
a second boolean parameter determines whether parent items of the selected menu item will also receive callouts. This defaults to false
.
48 |
49 |
Screenshots
50 |
51 |
html2canvas
is built in, but the screenshot capability currently only works properly in Chrome . In Chrome you can use .shoot()
to take a screenshot and immediately download it with a name based on the menu items path. See notes.
52 |
53 |
NiftyAPI.find('view/next tab').lock().shoot();
54 |
55 |
56 |
57 |
58 |
59 |
--------------------------------------------------------------------------------
/javascript/README.md:
--------------------------------------------------------------------------------
1 | ### The NiftyMenu API
2 |
3 | The primary namespace is `NiftyAPI`, which is a chainable set of functions covering searching, clicking, and callouts.
4 |
5 | ### Configuration functions
6 |
7 | Methods that adjust the display do not require an element to be passed, but can still be chained in with other functions. The primary display command is `config()`, which accepts an object containing keys for any/all of the display options. Example:
8 |
9 | NiftyAPI.config({
10 | 'arrowStyle': 'arrow',
11 | 'bgImage': true,
12 | 'expose': false,
13 | 'darkMode': false,
14 | 'wallpaper': 'default'
15 | });
16 |
17 | There are shorcuts for Dark Mode (`NiftyAPI.darkMode()`) and Exposé (`NiftyAPI.expose()`).
18 |
19 | ### Chainable functions
20 |
21 | All chainable functions should start with `find` (or a variable containing the result of a `find`). The found element can then have one or more callout functions applied:
22 |
23 | - `lock()` (equivalent to a click)
24 | - `callout()` (equivalent to a double click)
25 | - `arrow()` (equivalent to an Option-click)
26 | - `shortcut()` (equivalent to Option-clicking a keyboard shortcut)
27 |
28 | Example:
29 |
30 | // Find the File->Save menu item and apply a callout ring to it
31 | // and all parent elements up the chain
32 | NiftyAPI.find('file/save').callout(true, true);
33 |
34 | All callout functions can be passed a boolean parameter to enable/disable the callout. Running `NiftyAPI.clear()` will disable all callouts and clicks.
35 |
36 | In the case of `callout()` a second boolean parameter determines whether parent items of the selected menu item will also receive callouts. This defaults to `false`.
37 |
38 | # Screenshots
39 |
40 | `html2canvas` is built in, but the screenshot capability currently ***only works properly in Chrome***. In Chrome you can use `.shoot()` to take a screenshot and immediately download it with a name based on the menu items path. If an argument is passed with `shoot` it will be used as the filename. [See notes.](https://ttscoff.github.io/niftymenu/jsapi/NiftyAPI.html#.shoot__anchor)
41 |
42 | NiftyAPI.find('view/next tab').lock().shoot();
43 |
44 |
45 |
--------------------------------------------------------------------------------
/javascript/app.js:
--------------------------------------------------------------------------------
1 | import Nifty from './modules/nifty.js';
2 | import NiftyAPI from './modules/api.js';
3 |
4 | $(function() {
5 | Nifty.init();
6 | window.NiftyAPI = NiftyAPI;
7 | });
8 |
9 |
10 |
--------------------------------------------------------------------------------
/javascript/modules/api.js:
--------------------------------------------------------------------------------
1 | import { Callout, Prefs, Search, Util } from 'modules.js';
2 |
3 | /**
4 | * @namespace NiftyAPI
5 | * @description Chainable automation API. All chains that affect a menu item
6 | * should start with a .find('search string') call.
7 | *
8 | * @example NiftyAPI.find('file/save').arrow(); // locate File->Save menu item and add an arrow callout
9 | */
10 | const NiftyAPI = {
11 | targetEl: null,
12 |
13 | /**
14 | * Show current configuration options
15 | *
16 | * @return {object} The configuration.
17 | */
18 | getConfig: function() {
19 | return Prefs.get();
20 | },
21 |
22 | /**
23 | * Set multiple display options via a configuration object
24 | *
25 | * @param {Object} options object containing settings
26 | * @example
27 | * NiftyAPI.config({
28 | * 'arrowStyle': 'arrow',
29 | * 'bgImage': true,
30 | * 'darkMode': false,
31 | * 'wallpaper': 'default'
32 | * });
33 | */
34 | config: function(options={}) {
35 | let defaults = Prefs.config;
36 |
37 | let config = $.extend({}, defaults, options);
38 |
39 | Util.setDarkMode(Prefs.truthy(config.darkMode));
40 | Util.setBG(Prefs.truthy(config.bgImage));
41 | Util.setWallpaper(config.wallpaper);
42 | Callout.setArrowStyle(config.arrowStyle);
43 | return this;
44 | },
45 |
46 | /**
47 | * Case insensitive string match for menu item search. Use / to separate
48 | * heirarchical menu search items. This function can be chained for use with
49 | * other functions.
50 | * @example
51 | * NiftyAPI.find('insert/toc/section');
52 | * NiftyAPI.find('insert/toc/section').arrow();
53 | *
54 | * @param {string} str The string to search for
55 | * @return {jQuery} single jQuery element or null
56 | */
57 | find: function(str) {
58 | this.targetEl = Search.find(str);
59 | return this;
60 | },
61 |
62 | /**
63 | * Clear all clicks, callouts, and arrows
64 | * @example
65 | * NiftyAPI.clear();
66 | */
67 | clear: function() {
68 | Util.clearClicks(true);
69 | return this;
70 | },
71 |
72 | /**
73 | * Lock menu item. Removes any existing locks. Equivalent to clicking a menu
74 | * item.
75 | * @example
76 | * NiftyAPI.find('file/save').lock();
77 | */
78 | lock: function() {
79 | Util.clearClicks(true);
80 | this.targetEl.click();
81 | this.targetEl.get(0).scrollIntoView({behavior: "auto", block: "end", inline: "center"});
82 | return this;
83 | },
84 |
85 | /**
86 | * Add callout to menu item. Equivalent to double clicking a menu item.
87 | *
88 | * @param {boolean} [bool=true] Callout on or off (default: true)
89 | * @param {boolean} [recurse=false] Call out parent items (default:
90 | * false)
91 | * @example
92 | * NiftyAPI.find('file/open').callout();
93 | *NiftyAPI.find('file/open').callout(true, true); // add callout to item and parents
94 | *NiftyAPI.find('file/open').callout(false); // remove callout
95 | */
96 | callout: function(bool, recurse=false) {
97 | if (bool === undefined) {
98 | bool = true;
99 | }
100 |
101 | Util.clearClicks(true);
102 |
103 | if (bool) {
104 | this.targetEl.dblclick();
105 | this.targetEl.get(0).scrollIntoView({behavior: "auto", block: "end", inline: "center"});
106 |
107 | if (recurse) {
108 | this.targetEl.parents('.clicked').addClass('callout');
109 | }
110 | }
111 |
112 | return this;
113 | },
114 |
115 | /**
116 | * Set callout arrow for menu item. Equivalent to option-clicking a menu item.
117 | *
118 | * @param {boolean} [bool=true] Arrow on or off
119 | * @example
120 | * NiftyAPI.find('view/merge').arrow();
121 | *NiftyAPI.find('view/merge').arrow(false); // remove arrow
122 | */
123 | arrow: function(bool) {
124 | if (bool === undefined) {
125 | bool = true;
126 | }
127 | Callout.setArrow(bool, this.targetEl);
128 | return this;
129 | },
130 |
131 | /**
132 | * Set shortcut callout for menu item. Equivalent to option-clicking a
133 | * shortcut on a menu item.
134 | *
135 | * @param {boolean} [bool=true] Shortcut callout on or off
136 | * @example
137 | * NiftyAPI.find('file/save').shortcut();
138 | *NiftyAPI.find('file/save').shortcut(false); // remove arrow
139 | */
140 | shortcut: function(bool) {
141 | if (bool === undefined) {
142 | bool = true;
143 | }
144 | Callout.setShortcut(bool, this.targetEl);
145 | return this;
146 | },
147 |
148 | /**
149 | * Turn Dark Mode on or off
150 | *
151 | * @param {boolean} [bool=true] Dark Mode on or off
152 | * @example
153 | * NiftyAPI.darkMode(); // turn dark mode on
154 | *NiftyAPI.darkMode(false); // turn dark mode off
155 | */
156 | darkMode: function(bool) {
157 | if (bool === undefined) {
158 | bool = true;
159 | }
160 | Util.setDarkMode(bool);
161 | return this;
162 | },
163 |
164 | /**
165 | * Turn Expose on or off
166 | *
167 | * @param {boolean} [bool=true] Expose on or off
168 | * @example
169 | * NiftyAPI.expose(); // turn expose on
170 | *NiftyAPI.expose(false); // turn expose off
171 | */
172 | expose: function(bool) {
173 | if (bool === undefined) {
174 | bool = true;
175 | }
176 | Util.setExpose(bool);
177 | return this;
178 | },
179 |
180 | /**
181 | * Take a screenshot of selected menu item. Currently experimental, **only
182 | * works in Chrome**.
183 | *
184 | * Background images work if they're remote (hosted, with proper CORS
185 | * headers). Local images seem to taint the canvas, making it impossible to
186 | * save with Chrome's security restrictions. Thus, background images are
187 | * disabled during screenshot if using a file: protocol.
188 | *
189 | * @param {string} [title=null] The title for downloaded image, no extension
190 | *
191 | * @example
192 | * NiftyAPI.find('edit/paste as').arrow().shoot('filename');
193 | *
194 | */
195 | shoot: function(title=null) {
196 | Util.screenshot(false, title);
197 | return this;
198 | }
199 | };
200 |
201 | export default NiftyAPI;
202 |
--------------------------------------------------------------------------------
/javascript/modules/callout.js:
--------------------------------------------------------------------------------
1 | import Prefs from 'prefs.js'
2 |
3 | /**
4 | * @namespace Callout
5 | * @private
6 | * @memberof Nifty
7 | * @description Methods for adding callouts to items
8 | */
9 | const Callout = (function() {
10 | /**
11 | * Sets the arrow callout
12 | * @memberof Callout
13 | *
14 | * @param {boolean} bool Add or remove arrow
15 | * @param {element} el DOM element or jQuery object, applies to all .arrow if empty
16 | * @return {boolean} Result
17 | */
18 | const setArrow = (bool, el) => {
19 | if (!el && !bool) {
20 | $('.arrow').each(function(i,n) {
21 | setArrow(false,$(n));
22 | });
23 | return;
24 | }
25 |
26 | if (!(el instanceof jQuery)) {
27 | el = $(el);
28 | }
29 |
30 | if (el.get(0).tagName !== 'LI') {
31 | el = el.parents('li').first();
32 | }
33 |
34 | if (bool) {
35 | setArrow(false);
36 | setShortcut(false);
37 | const style = Prefs.get('arrowStyle') || 'arrow';
38 | const direction = el.find('ul').length ? 'left' : 'right';
39 | $('.clicked').removeClass('clicked');
40 | el.addClass('arrow arrow-'+style+' clicked '+direction).append('
');
41 | el.parents('li').addClass('clicked');
42 | } else {
43 | el.removeClass('arrow arrow-arrow arrow-circle').find('b').remove();
44 | }
45 | };
46 |
47 | /**
48 | * Toggles the arrow callout
49 | * @memberof Callout
50 | *
51 | * @param {jquery} el jQuery object, all .arrow if empty
52 | * @return {boolean} Result
53 | */
54 | const toggleArrow = (el) => {
55 | if (!el) {
56 | setArrow(false);
57 | return;
58 | }
59 |
60 | if (!(el instanceof jQuery)) {
61 | el = $(el);
62 | }
63 |
64 | if (el.hasClass('arrow')) {
65 | setArrow(false, el);
66 | } else {
67 | setArrow(false);
68 | setShortcut(false);
69 | setArrow(true, el);
70 | }
71 | };
72 |
73 | /**
74 | * Sets the shortcut callout
75 | * @memberof Callout
76 | *
77 | * @param {boolean} bool Add or remove shortcut callout
78 | * @param {element} el DOM element or jQuery object containing shortcut,
79 | * applies to all .shortcut-callout if empty
80 | * @return {boolean} Result
81 | */
82 | const setShortcut = (bool, el) => {
83 | if (!el && !bool) {
84 | $('.shortcut-callout').each(function(i,n) {
85 | setShortcut(false,$(n));
86 | });
87 | return;
88 | }
89 |
90 | if (!(el instanceof jQuery)) {
91 | el = $(el);
92 | }
93 |
94 | if (el.get(0).tagName !== 'LI') {
95 | el = el.parents('li').first();
96 | }
97 |
98 | if (bool) {
99 | setShortcut(false);
100 | setArrow(false);
101 | $('.clicked').removeClass('clicked');
102 | el.addClass('clicked').find('.shortcut').addClass('shortcut-callout');
103 | el.parents('li').addClass('clicked');
104 | } else {
105 | el.find('.shortcut').removeClass('shortcut-callout');
106 | }
107 | };
108 |
109 | /**
110 | * Toggles the shortcut callout
111 | * @memberof Callout
112 | *
113 | * @param {jquery} el jQuery object, all .arrow if empty
114 | * @return {boolean} Result
115 | */
116 | const toggleShortcut = (el) => {
117 | if (!el) {
118 | setShortcut(false);
119 | return;
120 | }
121 |
122 | if (!(el instanceof jQuery)) {
123 | el = $(el);
124 | }
125 |
126 | if (el.has('.shortcut-callout').length) {
127 | setShortcut(false, el);
128 | } else {
129 | setShortcut(false);
130 | setShortcut(true, el);
131 | }
132 | };
133 |
134 | /**
135 | * Toggles a checkmark on the clicked menu item.
136 | * @memberof Callout
137 | * @param {jquery} el jQuery object, all .arrow if empty.
138 | * @return {boolean} Result
139 | */
140 | const toggleCheckmark = (el) => {
141 | if (!(el instanceof jQuery)) {
142 | el = $(el);
143 | }
144 |
145 | if (el.hasClass('checked')) {
146 | el.removeClass('checked');
147 | } else {
148 | el.addClass('checked');
149 | }
150 | return true;
151 | };
152 |
153 | /**
154 | * Sets the style of the callout arrow
155 | * @memberof Callout
156 | * @param {string} style 'circle' or 'arrow'
157 | */
158 | const setArrowStyle = (style) => {
159 | let newStyle = 'arrow';
160 | if (style && style === 'circle') {
161 | newStyle = 'circle';
162 | $('.arrow-arrow').removeClass('arrow-arrow').addClass('arrow-circle');
163 | } else {
164 | $('.arrow-circle').removeClass('arrow-circle').addClass('arrow-arrow');
165 | }
166 | Prefs.set('arrowStyle', newStyle);
167 | };
168 |
169 | /**
170 | * Toggles arrow style between circle and arrow.
171 | * @memberof Callout
172 | */
173 | const toggleArrowStyle = () => {
174 | let newStyle;
175 | const current = Prefs.get('arrowStyle');
176 | if (current === 'circle') {
177 | newStyle = 'arrow';
178 | $('#arrowStyle','.controls').text('Arrow style: Arrow');
179 | } else {
180 | newStyle = 'circle';
181 | $('#arrowStyle','.controls').text('Arrow style: Circle');
182 | }
183 | setArrowStyle(newStyle);
184 | };
185 |
186 | return {
187 | setArrow,
188 | setArrowStyle,
189 | toggleArrowStyle,
190 | toggleArrow,
191 | toggleCheckmark,
192 | setShortcut,
193 | toggleShortcut
194 | }
195 | })();
196 |
197 | export default Callout;
198 |
--------------------------------------------------------------------------------
/javascript/modules/handler.js:
--------------------------------------------------------------------------------
1 | import Util from 'util.js'
2 | import Callout from 'callout.js';
3 | import Prefs from 'prefs.js';
4 | import Search from 'search.js';
5 |
6 | /**
7 | * @namespace Nifty.handlers
8 | * @private
9 | * @memberof Nifty
10 | * @description Event handlers
11 | */
12 |
13 | const Handler = (function() {
14 | /**
15 | * live search for the help menu, function ~ macOS
16 | * @private
17 | * @memberof Nifty.handlers
18 | *
19 | * @param {event} e Event
20 | */
21 | const liveSearch = (e) => {
22 |
23 | let $field = $('.helpsearch input'),
24 | string = $field.val(),
25 | shouldScroll = false;
26 |
27 | if (e.code === 'Escape') {
28 | e.preventDefault();
29 | $field.val('').blur();
30 | Util.clearClicks(true);
31 | return true;
32 | }
33 |
34 | if (e.code === 'Enter' || e.code === 'Return') {
35 | e.preventDefault();
36 | $('.persist').removeClass('persist');
37 | shouldScroll = true;
38 | // return true;
39 | }
40 |
41 | if (string.length < 2) {
42 | Util.clearClicks(false);
43 | return true;
44 | }
45 |
46 | let $item = Search.find(string);
47 |
48 | if ($item) {
49 | Util.clearClicks(false);
50 | if ($item.parents('li')) {
51 | $item.parents('li').addClass('clicked');
52 | }
53 | $item.addClass('clicked last');
54 |
55 | if (shouldScroll) {
56 | $field.blur();
57 | $item.get(0).scrollIntoView({behavior: "smooth", block: "end", inline: "center"});
58 | }
59 | } else {
60 | Util.clearClicks(false);
61 | }
62 |
63 | return true;
64 | };
65 |
66 | /**
67 | * click handler for menu items
68 | * @private
69 | * @memberof Nifty.handlers
70 | * @param {event} e Event
71 | * @return {boolean} continue handling event
72 | */
73 | const itemClick = (e) => {
74 | e.preventDefault();
75 |
76 | let $this,
77 | shortcutClicked = false;
78 |
79 | if (e.target.tagName === 'SPAN') {
80 | $this = $(e.target).closest('li');
81 | if ($(e.target).hasClass('shortcut')) {
82 | shortcutClicked = true;
83 | }
84 | } else {
85 | $this = $(e.target);
86 | }
87 |
88 | if (e.metaKey || e.altKey) {
89 | if (e.metaKey) {
90 | Callout.toggleCheckmark($this);
91 | } else if (e.altKey) {
92 | if (shortcutClicked) {
93 | Callout.toggleShortcut($this);
94 | } else {
95 | Callout.toggleArrow($this);
96 | }
97 | }
98 | return false;
99 | }
100 |
101 |
102 | $('.callout').removeClass('callout');
103 | $('.persist').removeClass('persist');
104 |
105 | if (e.target.tagName === 'BODY') {
106 | $('.clicked').removeClass('clicked');
107 | $('.last').removeClass('last');
108 | Callout.setArrow(false);
109 | Callout.setShortcut(false);
110 | } else {
111 | if ($this.hasClass('clicked')) {
112 | if ($this.find('.last').length) {
113 | Util.clearClicks();
114 | $this.parents('li').addClass('clicked');
115 | $this.addClass('clicked last');
116 | } else {
117 | $('.last').removeClass('last');
118 | if ($this.parents('.clicked').length) {
119 | $this.removeClass('clicked');
120 | $this.siblings('.clicked').removeClass('clicked');
121 | $this.parents('.clicked').first().addClass('last');
122 | } else {
123 | $('.clicked').removeClass('clicked');
124 | }
125 |
126 | Callout.setArrow(false);
127 | Callout.setShortcut(false);
128 | }
129 | return false;
130 | } else {
131 | Callout.setArrow(false);
132 | Callout.setShortcut(false);
133 | $('li.clicked').removeClass('clicked');
134 | $('.last').removeClass('last');
135 | $this.parents('li').addClass('clicked');
136 | if (e.altKey) {
137 | Callout.setArrow(true, $this);
138 | }
139 | $this.addClass('clicked last');
140 | }
141 |
142 | if (e.type === 'dblclick') {
143 | $this.addClass('callout');
144 | if (e.shiftKey) {
145 | $this.parents('.clicked').addClass('callout');
146 | }
147 | }
148 | }
149 | return false;
150 | };
151 | /**
152 | * handler for all clicks within the .controls element
153 | * @private
154 | * @memberof Nifty.handlers
155 | * @param {event} e Event
156 | * @return {boolean} continue handling event
157 | */
158 | const controlsClick = (e) => {
159 | e.preventDefault();
160 | let $this = e.target;
161 |
162 | switch($this.id) {
163 | case 'darkModeToggle':
164 | Util.toggleDarkMode();
165 | break;
166 | case 'exposeToggle':
167 | Util.toggleExpose();
168 | break;
169 | case 'backgroundToggle':
170 | Util.toggleBG();
171 | break;
172 | case 'chooseWallpaper':
173 | chooseWallpaper();
174 | break;
175 | case 'randomWallpaper':
176 | randomWallpaper();
177 | break;
178 | case 'resetWallpaper':
179 | Util.setWallpaper(false);
180 | break;
181 | case 'arrowStyle':
182 | Callout.toggleArrowStyle();
183 | break;
184 | case 'screenshot':
185 | Util.screenshot(e);
186 | break;
187 | case 'commandshell':
188 | Util.terminal(e);
189 | break;
190 | default:
191 | throw('Element ID unrecognized');
192 | }
193 |
194 | return false;
195 | };
196 |
197 | /**
198 | * Allow entry of a url to load as wallpaper
199 | * @memberof Handlers
200 | * @private
201 | */
202 | const chooseWallpaper = () => {
203 | let url = prompt("Enter URL for background image");
204 | Prefs.set('wallpaper', url);
205 | Util.loadWallpaper();
206 | };
207 |
208 | /**
209 | * Choose random unsplash wallpaper
210 | * @memberof Handlers
211 | * @private
212 | */
213 | const randomWallpaper = () => {
214 | let keywords = prompt("Enter optional keywords (comma separated words)"),
215 | url = 'https://source.unsplash.com/random/1900x1200?' + encodeURIComponent(keywords);
216 |
217 | Prefs.set('wallpaper', url);
218 | Util.loadWallpaper();
219 | };
220 |
221 |
222 |
223 | /**
224 | * reveal and focus the help search field
225 | * @memberof Nifty.handlers
226 | *
227 | * @param {event} e Event
228 | * @return {boolean} continue handling event
229 | */
230 | const focusSearch = (e) => {
231 | e.preventDefault();
232 | $('li.callout').removeClass('callout');
233 | $('.clicked').removeClass('clicked');
234 | Util.clearClicks();
235 | let $search = $('.helpsearch').first();
236 | $search.parents('li').addClass('clicked persist');
237 | $search.get(0).scrollIntoView({behavior: "smooth", block: "end", inline: "end"});
238 | $('input',$search).focus();
239 | return false;
240 | };
241 |
242 | return {
243 | itemClick,
244 | controlsClick,
245 | liveSearch,
246 | focusSearch
247 | }
248 | }());
249 |
250 | export default Handler;
251 |
--------------------------------------------------------------------------------
/javascript/modules/modules.js:
--------------------------------------------------------------------------------
1 | import Callout from 'callout.js';
2 | import Handler from 'handler.js'
3 | import Prefs from 'prefs.js';
4 | import Search from 'search.js';
5 | import Util from 'util.js';
6 |
7 | export { Callout, Handler, Prefs, Search, Util };
8 |
--------------------------------------------------------------------------------
/javascript/modules/nifty.js:
--------------------------------------------------------------------------------
1 | import { Util, Prefs, Callout, Handler, Search } from 'modules.js';
2 |
3 | /**
4 | * @namespace Nifty
5 | * @private
6 | * @description Automation API and event handlers
7 | */
8 | const Nifty = (function() {
9 | 'use strict';
10 |
11 | /**
12 | * Setup function, cache menu items and init preferences
13 | * @memberof Nifty
14 | */
15 | const init = () => {
16 | Search.getOrderedMenuItemTitles();
17 | if (Prefs.getBool('darkMode')) {
18 | Util.setDarkMode(true);
19 | }
20 | if (Prefs.getBool('expose')) {
21 | Util.setExpose(true);
22 | }
23 | Util.loadWallpaper();
24 | if (Prefs.getBool('bgImage')) {
25 | Util.setBG(true);
26 | }
27 | Callout.setArrowStyle(Prefs.get('arrowStyle'));
28 |
29 | // set up handlers
30 |
31 |
32 | $('body,li').on('click dblclick', Handler.itemClick);
33 |
34 | $('span','.controls').on('click', Handler.controlsClick);
35 |
36 | $('.helpsearch input').on('keyup', Handler.liveSearch);
37 |
38 | $('.helpsearch').on('click', Handler.focusSearch);
39 |
40 | $('.helpsearch').on('blur', () => {
41 | $('.persist').removeClass('persist');
42 | });
43 |
44 | // $('body').on('click', () => {
45 | // $('#screenshotHolder').empty();
46 | // });
47 |
48 | // Load demo overlay if viewed on GitHub
49 | if (window.location.host === 'ttscoff.github.io') { $('body').addClass('demo'); }
50 |
51 | // bind some keys
52 |
53 | Mousetrap.bind('shift+/', Handler.focusSearch);
54 | Mousetrap.bind('shift+d', Util.toggleDarkMode);
55 | Mousetrap.bind('shift+e', Util.toggleExpose);
56 | Mousetrap.bind('shift+s', Util.screenshot);
57 | Mousetrap.bind('$', Util.terminal);
58 | };
59 |
60 | /**
61 | * search for a menu item by string and click
62 | * @memberof Nifty
63 | *
64 | * @param {string} str The string to search and click
65 | * @param {boolean} force If false/undefined, clicking a focused item
66 | * will hide it. Pass true to always open the
67 | * item.
68 | * @return {null} Nothing
69 | */
70 | const click = (str, force=false) => {
71 | if (force) {
72 | Util.clearClicks(true);
73 | }
74 |
75 | if (!str || /^\s*$/.test(str)) {
76 | Util.clearClicks(true);
77 | return;
78 | }
79 | let match = Search.find(str);
80 | if (match) {
81 | match.click();
82 | match.get(0).scrollIntoView({behavior: "auto", block: "end", inline: "center"});
83 | }
84 | };
85 |
86 | /**
87 | * search for a menu item by string and double-click
88 | * @memberof Nifty
89 | *
90 | * @param {string} str The string to search and double click
91 | * @param {boolean} force If false/undefined, clicking a focused item
92 | * will hide it. Pass true to always open the
93 | * item.
94 | * @return {null} Nothing
95 | */
96 | const dblClick = (str, force=false) => {
97 |
98 | if (force) {
99 | Util.clearClicks(true);
100 | }
101 |
102 | if (!str || /^\s*$/.test(str)) {
103 | Util.clearClicks(true);
104 | return;
105 | }
106 | let match = Search.find(str);
107 | if (match) {
108 | match.dblclick();
109 | match.get(0).scrollIntoView({behavior: "auto", block: "end", inline: "center"});
110 | }
111 | };
112 |
113 | return {
114 | orderedMenuItemTitles: [],
115 | init,
116 | click,
117 | dblClick
118 | }
119 | })();
120 |
121 | export default Nifty;
122 |
--------------------------------------------------------------------------------
/javascript/modules/prefs.js:
--------------------------------------------------------------------------------
1 | /**
2 | * @namespace Prefs
3 | * @private
4 | * @description Utility methods for storing/retrieving preferences
5 | */
6 | const Prefs = (function() {
7 | 'use strict';
8 |
9 | const CONFIG_KEY = 'niftyPrefs';
10 |
11 | const defaults = {
12 | 'arrowStyle' : 'arrow',
13 | 'bgImage' : 1,
14 | 'expose' : 0,
15 | 'darkMode' : 0,
16 | 'wallpaper' : 'default'
17 | };
18 |
19 | let config = null;
20 |
21 | const getConfig = () => {
22 | if (!config) {
23 | let storedConfig = JSON.parse(localStorage.getItem(CONFIG_KEY));
24 |
25 | if (storedConfig) {
26 | config = $.extend({}, defaults, storedConfig);
27 | } else {
28 | config = defaults;
29 | }
30 |
31 | localStorage.setItem(CONFIG_KEY, JSON.stringify(config));
32 | }
33 |
34 | return config;
35 | };
36 |
37 | /**
38 | * Retrieve preference key as a boolean value
39 | *
40 | * @param {string} key The key
41 | * @return {boolean} True for positive integer or truthy string
42 | */
43 | const getBool = (key) => {
44 | let value = get(key);
45 | return truthy(value);
46 | };
47 |
48 |
49 | /**
50 | * Determine truthiness of value
51 | *
52 | * @param {any} value The value: boolean, integer, or string
53 | * @return {boolean} determined value
54 | */
55 | const truthy = (value) => {
56 | if (typeof value === 'boolean') {
57 | return value;
58 | }
59 | if (Number(value)) {
60 | return Boolean(Number(value));
61 | } else {
62 | if (/(y(es)?|true)/i.test(value)) {
63 | return true;
64 | }
65 | return false;
66 | }
67 | };
68 |
69 | /**
70 | * Set a preference value
71 | *
72 | * @param {string} key The config item's key
73 | * @param {string} value Value to set for key
74 | */
75 | const set = (key, value) => {
76 | config = get();
77 | config[key] = value;
78 | localStorage.setItem(CONFIG_KEY, JSON.stringify(config));
79 | };
80 |
81 | /**
82 | * Retrive the raw preference for a key
83 | *
84 | * @param {string} key The config item's key
85 | * @return {string} raw string from preferences, not decoded or JSONified
86 | */
87 | const get = (key) => {
88 |
89 | let _config = getConfig();
90 |
91 | if (_config && key) {
92 | if (_config.hasOwnProperty(key)) {
93 | return config[key];
94 | } else {
95 | return null;
96 | }
97 | } else {
98 | return _config;
99 | }
100 |
101 | };
102 |
103 | const Prefs = {
104 | config,
105 | set,
106 | get,
107 | getBool,
108 | truthy
109 | };
110 |
111 | return Prefs;
112 | })();
113 |
114 | export default Prefs;
115 |
--------------------------------------------------------------------------------
/javascript/modules/search.js:
--------------------------------------------------------------------------------
1 | /**
2 | * @namespace Search
3 | * @private
4 | * @description Automation API and event handlers
5 | */
6 | const Search = (function() {
7 | 'use strict';
8 | let orderedMenuItemTitles;
9 |
10 | const itemForPath = (path) => {
11 | return $('li').filter(function(i,n) {
12 | if ($(n).data('path') === path) {
13 | return true;
14 | }
15 | return false;
16 | }).first();
17 | };
18 |
19 | /**
20 | * Case insensitive string match for menu item search. Use / to separate
21 | * heirarchical menu search items
22 | * @example Search.find('insert/toc/section')
23 | *
24 | * @param {string} query The string to search for
25 | * @return {jQuery} single jQuery element or null
26 | */
27 | const find = (query) => {
28 | if (/^\s*$/.test(query)) {
29 | return null;
30 | }
31 |
32 | query = query.replace(/>/g,"/");
33 |
34 | let titles = getOrderedMenuItemTitles();
35 | let results = fuzzysort.go(query, titles);
36 | if (results.length) {
37 | return itemForPath(results[0].target);
38 | } else {
39 | return null;
40 | }
41 | };
42 |
43 | /**
44 | * Get an array of all item tiles with hierarchy
45 | * @private
46 | *
47 | * @return {array} The menu item titles.
48 | */
49 | const getOrderedMenuItemTitles = () => {
50 | if (orderedMenuItemTitles && orderedMenuItemTitles.length > 0) {
51 | return orderedMenuItemTitles;
52 | }
53 | let titles = [];
54 |
55 | $('li').each(function(i,n) {
56 | let thisTitle = n.innerText.split(/\n/)[0].trim();
57 | $(n).parents('li').each(function(i,n) {
58 | if (n.innerText.length) {
59 | thisTitle = n.innerText.split(/\n/)[0].trim() + "/" + thisTitle;
60 | }
61 | });
62 | $(n).data('path',thisTitle);
63 | titles.push(thisTitle);
64 | });
65 | orderedMenuItemTitles = titles;
66 | return orderedMenuItemTitles;
67 | };
68 |
69 | return {
70 | orderedMenuItemTitles: [],
71 | getOrderedMenuItemTitles,
72 | find
73 | }
74 | })();
75 |
76 | export default Search;
77 |
--------------------------------------------------------------------------------
/javascript/modules/util.js:
--------------------------------------------------------------------------------
1 | import Prefs from 'prefs.js';
2 | import Callout from 'callout.js';
3 |
4 | /**
5 | * @namespace Util
6 | * @private
7 | * @description DOM/interface utilities
8 | */
9 | const Util = (function() {
10 | /**
11 | * Clear all active clicks (menu items held in place)
12 | *
13 | * @param {boolean} persist The help menu gets a special class to keep
14 | * it open while other menus are active, even
15 | * when it's not hovered. Setting this to true
16 | * removes that class as well.
17 | */
18 | const clearClicks = (persist) => {
19 | $('li.callout').removeClass('callout');
20 | $('.clicked').removeClass('clicked');
21 | $('.last').removeClass('last');
22 | Callout.setArrow(false);
23 | Callout.setShortcut(false);
24 | if (persist) {
25 | $('.persist').removeClass('persist');
26 | }
27 | };
28 |
29 | /**
30 | * Sets the wallpaper image to use when bgImage is enabled
31 | * @param {string} url URL for background image
32 | */
33 | const setWallpaper = (url) => {
34 | Prefs.set('wallpaper', url);
35 | loadWallpaper();
36 | };
37 |
38 | /**
39 | * Add a style rule for the defined wallpaper
40 | * @private
41 | * @param {string} url URL for background image
42 | */
43 | const loadWallpaper = () => {
44 | let url = Prefs.get('wallpaper');
45 | if (!url || url === 'default') {
46 | if (Prefs.getBool('darkMode')) {
47 | url = 'images/darkbackground.jpg';
48 | } else {
49 | url = 'images/background.jpg';
50 | }
51 | }
52 | addStyleRule('body.bgimage, body.dark.bgimage {background-image: url('+url+')}');
53 | };
54 |
55 | /**
56 | * Add a style rule to main stylesheet
57 | * @private
58 | */
59 | const addStyleRule = (rule) => {
60 | var sheet = (function() {
61 | var style = document.createElement("style");
62 | style.appendChild(document.createTextNode(""));
63 | document.head.appendChild(style);
64 | return style.sheet;
65 | })();
66 | sheet.insertRule(rule);
67 | };
68 |
69 | /**
70 | * Sets the background image on or off. Use the boolean paramater to
71 | * determine which.
72 | * @param {boolean} bool true turns background image on,
73 | * false for off
74 | */
75 | const setBG = (bool) => {
76 | let $body = $('body');
77 | if (bool) {
78 | $body.addClass('bgimage');
79 | Prefs.set('bgImage',1);
80 | loadWallpaper();
81 | } else {
82 | $body.removeClass('bgimage');
83 | Prefs.set('bgImage',0);
84 | }
85 | };
86 |
87 | /**
88 | * Toggle background image
89 | */
90 | const toggleBG = () => {
91 | let $body = $('body');
92 | if ($body.hasClass('bgimage')) {
93 | setBG(false);
94 | } else {
95 | setBG(true);
96 | }
97 | };
98 |
99 | /**
100 | * Toggle Dark Mode
101 | */
102 | const toggleDarkMode = () => {
103 | let test = $('body').hasClass('dark');
104 |
105 | if (test) {
106 | setDarkMode(false);
107 | } else {
108 | setDarkMode(true);
109 | }
110 | };
111 |
112 | /**
113 | * Set Dark Mode
114 | * @param {boolean} [bool=true] Dark Mode on or off
115 | */
116 | const setDarkMode = (bool=true) => {
117 | let $body = $('body');
118 | if (!bool) {
119 | $body.removeClass('dark');
120 | Prefs.set('darkMode',0);
121 | } else {
122 | $body.addClass('dark');
123 | Prefs.set('darkMode',1);
124 | }
125 | loadWallpaper();
126 | };
127 |
128 | /**
129 | * Force Expose on or off. Use the boolean paramater to determine which.
130 | * @param {boolean} [bool=true] true turns Expose on, false for off
131 | */
132 | const setExpose = (bool) => {
133 | let $body = $('body');
134 | if (bool) {
135 | $body.addClass('expose');
136 | $('html,body').width($('body>ul').width());
137 | Prefs.set('expose',1);
138 | } else {
139 | Prefs.set('expose',0);
140 | window.location.reload();
141 | }
142 | };
143 |
144 | /**
145 | * Toggle Expose
146 | */
147 | const toggleExpose = () => {
148 | let $body = $('body');
149 | if ($body.hasClass('expose')) {
150 | setExpose(false);
151 | } else {
152 | setExpose(true);
153 | }
154 | };
155 |
156 | /**
157 | * Take a screenshot of selected menu item. Experimental, currently only works
158 | * in Chrome. If called via NiftyAPI.shoot(), downloads immediately to
159 | * Downloads folder, names file based on menu path.
160 | *
161 | * Background images work if they're remote. Local images seem to taint the
162 | * canvas, making it impossible to save with Chrome's security restrictions.
163 | *
164 | * @param {event} e If e is undefined, download immediately
165 | */
166 | const screenshot = (e, filename) => {
167 | $('body').addClass('screenshot');
168 | let clicks = $('li.clicked');
169 | if (!clicks.length) {
170 | throw("No menu items selected");
171 | }
172 |
173 | let title;
174 |
175 | if (filename) {
176 | title = filename;
177 | } else {
178 | title = [];
179 | clicks.each((i,n) => {
180 | title.push($(n).find('>strong').text());
181 | });
182 |
183 | let last = clicks.last();
184 | if (last.children('.shortcut').length > 0) {
185 | title.push(last.find('.menuitem').text());
186 | } else {
187 | title.push(last.text());
188 | }
189 |
190 | title = title.join('-').replace(/-+/g,'-').replace(/ +/g,'_');
191 | }
192 |
193 | let menus = clicks.parents('ul').not('body>ul'),
194 | left = Math.floor(clicks.first().offset().left - 70),
195 | width = 150,
196 | height = 0;
197 |
198 | menus.each((i,n) => {
199 | width += $(n).width();
200 | let ulTop = $(n).offset().top;
201 | let ulHeight = $(n).height();
202 | if (ulTop + ulHeight > height) {
203 | height = ulTop + ulHeight;
204 | }
205 | });
206 |
207 | // if the last item clicked has an open submenu,
208 | // include it in screenshot
209 | if (clicks.last().find('ul').length) {
210 | width += clicks.last().find('ul').width();
211 | }
212 |
213 | let useCors = false;
214 | let hadBG = Prefs.getBool('bgImage');
215 |
216 | if ((/url\("http/).test($('body').css('background-image'))) {
217 | useCors = true;
218 | } else if ((/url\("file/).test($('body').css('background-image'))) {
219 | setBG(false);
220 | }
221 |
222 | html2canvas(document.querySelector("body"), {
223 | x: left,
224 | y: 0,
225 | width: width,
226 | height: height + 50,
227 | useCORS: useCors,
228 | imageTimeout: 60000
229 | }).then(canvas => {
230 | $('#screenshotHolder').empty();
231 | $('#screenshotHolder').append(canvas);
232 | clearClicks(true);
233 | $('body').removeClass('screenshot');
234 | setBG(hadBG);
235 |
236 | // if called from a handler, display screenshot for download
237 | if (e) {
238 | let $controls = $('
');
239 | $('
Cancel ')
240 | .addClass('screenshot-close')
241 | .on('click', () => {
242 | $('#screenshotHolder').empty();
243 | })
244 | .appendTo($controls);
245 | $('
Save ')
246 | .addClass('screenshot-dl')
247 | .data('title', title)
248 | .on('click', () => {
249 | downloadScreenshot(title);
250 | })
251 | .appendTo($controls);
252 | $controls.appendTo('#screenshotHolder');
253 | // if called from API, download immediately
254 | } else {
255 | downloadScreenshot(title);
256 | }
257 |
258 | });
259 | }
260 |
261 | /**
262 | * Download a screenshot (handler)
263 | *
264 | * @param {string} title Filename to use
265 | */
266 | const downloadScreenshot = (title) => {
267 | if (!title)
268 | title = 'NiftyMenu_Screenshot';
269 |
270 | let canvas = document.querySelector('#screenshotHolder canvas'),
271 | link = document.createElement('a');
272 |
273 | link.download = `${title}.png`;
274 | link.href = canvas.toDataURL("image/png").replace("image/png", "image/octet-stream");
275 | link.click();
276 | $('#screenshotHolder').empty();
277 | }
278 |
279 | /**
280 | * Launch a shell for running automations
281 | */
282 | const terminal = (e) => {
283 | e.preventDefault();
284 | if ($('#terminal').length > 0) {
285 | $('#terminal').toggle();
286 | if ($('#terminal').is(':visible')) {
287 | $('#terminal>textarea').focus();
288 | }
289 | } else {
290 | $('