Version Description
- Added a "Redirects" feature. You can create login redirects, logout redirects, and registration redirects. You can configure redirects for specific roles and users. You can also set up a default redirect that will apply to everyone who doesn't have a specific setting. Redirect URLs can contain shortcodes, but not all shortcodes will work in this context.
- Added a few utility shortcodes:
[ame-wp-admin]
,[ame-home-url]
,[ame-user-info field="..."]
. These are mainly intended to be used to create dynamic redirects, but they will also work in posts and pages. - Slightly improved the appearance of settings page tabs on small screens and in narrow browser windows.
- Fixed a minor conflict where several hidden menu items created by "WP Grid Builder" would unexpectedly show up when AME is active.
- Fixed a conflict with "LoftLoader Pro", "WS Form", and probably a few other plugins that create new admin menu items that link to the theme customizer. Previously, it was impossible to hide or edit those menu items.
- Fixed a few jQuery deprecation warnings.
- Fixed an "Undefined array key" warning that could appear if another plugin created a user role that did not have a "capabilities" key.
- Fixed a minor BuddyBoss Platform compatibility issue where the menu editor would show a "BuddyBoss -> BuddyBoss" menu item that was not present in the actual admin menu. The item is created by BuddyBoss Platform, but it is apparently intended to be hidden.
- Refactored the menu editor and added limited support for editing three level menus. While the free version doesn't have the ability to actually render nested items in the admin menu, it should at least load a menu configuration that includes more than two levels without crashing. This will probably only matter if someone edits the settings in the database or copies a menu configuration from the Pro version.
Download this release
Release Info
Developer | whiteshadow |
Plugin | Admin Menu Editor |
Version | 1.10 |
Comparing to | |
See all releases |
Code changes from version 1.9.10 to 1.10
- css/_main-tabs.scss +37 -0
- css/admin.css +0 -22
- css/menu-editor.css +2051 -1694
- css/menu-editor.scss +8 -2
- includes/ame-utils.php +210 -0
- includes/basic-dependencies.php +1 -0
- includes/editor-page.php +164 -118
- includes/menu-editor-core.php +293 -110
- includes/menu-item.php +42 -1
- includes/menu.php +1 -0
- includes/role-utils.php +5 -2
- includes/settings-page.php +44 -0
- includes/shortcodes.php +114 -0
- js/actor-manager.js +2 -0
- js/editor-tab-fix.js +0 -11
- js/jqueryui.d.ts +493 -338
- js/menu-editor.js +1212 -620
- js/menu-highlight-fix.js +223 -167
- js/tab-utils.js +64 -0
- menu-editor.php +13 -9
- modules/actor-selector/actor-selector.js +6 -1
- modules/actor-selector/actor-selector.ts +7 -1
- modules/redirector/drag-indicator.svg +126 -0
- modules/redirector/knockout-sortable.js +494 -0
- modules/redirector/redirector-template.php +217 -0
- modules/redirector/redirector-ui.js +797 -0
- modules/redirector/redirector-ui.ts +994 -0
- modules/redirector/redirector.css +410 -0
- modules/redirector/redirector.php +1027 -0
- modules/redirector/redirector.scss +450 -0
- readme.txt +26 -3
- uninstall.php +10 -0
css/_main-tabs.scss
ADDED
@@ -0,0 +1,37 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
/***************************************
|
2 |
+
Tabs on the settings page
|
3 |
+
***************************************/
|
4 |
+
|
5 |
+
.wrap.ws-ame-too-many-tabs .ws-ame-nav-tab-list {
|
6 |
+
&.nav-tab-wrapper {
|
7 |
+
border-bottom-color: transparent;
|
8 |
+
}
|
9 |
+
|
10 |
+
.nav-tab {
|
11 |
+
border-bottom: 1px solid #c3c4c7;
|
12 |
+
margin-bottom: 10px;
|
13 |
+
margin-top: 0;
|
14 |
+
}
|
15 |
+
}
|
16 |
+
|
17 |
+
/* Spacing between the page heading and the tab list.
|
18 |
+
|
19 |
+
Normally, this is handled by .nav-tab styles, but WordPress changes the margins at smaller screen sizes
|
20 |
+
and the tabs end up without a left margin. Let's put that margin on the heading instead and remove it
|
21 |
+
from the first tab. */
|
22 |
+
|
23 |
+
#ws_ame_editor_heading {
|
24 |
+
margin-right: 0.305em;
|
25 |
+
}
|
26 |
+
|
27 |
+
.ws-ame-nav-tab-list {
|
28 |
+
a.nav-tab:first-of-type {
|
29 |
+
margin-left: 0;
|
30 |
+
}
|
31 |
+
}
|
32 |
+
|
33 |
+
/* When in "too many tabs" mode, there's too much space between the bottom of the tab list and the rest
|
34 |
+
of the page. I haven't found a good way to change the margins of just the last row, so here's a partial fix. */
|
35 |
+
.ws-ame-too-many-tabs #ws_actor_selector {
|
36 |
+
margin-top: 0;
|
37 |
+
}
|
css/admin.css
CHANGED
@@ -118,25 +118,3 @@ hr.ws-submenu-separator {
|
|
118 |
opacity: 1;
|
119 |
filter: alpha(opacity=100);
|
120 |
}
|
121 |
-
|
122 |
-
/*
|
123 |
-
* Third level menus.
|
124 |
-
*/
|
125 |
-
#adminmenu .ame-deep-submenu {
|
126 |
-
|
127 |
-
}
|
128 |
-
|
129 |
-
#adminmenu li.menu-top.opensub .ame-deep-submenu {
|
130 |
-
top: -1000em;
|
131 |
-
}
|
132 |
-
|
133 |
-
#adminmenu .wp-submenu li.opensub > ul.ame-deep-submenu {
|
134 |
-
top: -1px;
|
135 |
-
}
|
136 |
-
|
137 |
-
.folded #adminmenu li.opensub > ul.ame-deep-submenu,
|
138 |
-
.folded #adminmenu .wp-has-current-submenu.opensub > ul.ame-deep-submenu,
|
139 |
-
.no-js.folded #adminmenu .ame-has-deep-submenu:hover > ul.ame-deep-submenu {
|
140 |
-
top: 0;
|
141 |
-
left: 160px;
|
142 |
-
}
|
118 |
opacity: 1;
|
119 |
filter: alpha(opacity=100);
|
120 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
css/menu-editor.css
CHANGED
@@ -1,1694 +1,2051 @@
|
|
1 |
-
|
2 |
-
|
3 |
-
|
4 |
-
|
5 |
-
|
6 |
-
|
7 |
-
|
8 |
-
|
9 |
-
|
10 |
-
|
11 |
-
|
12 |
-
|
13 |
-
|
14 |
-
|
15 |
-
|
16 |
-
border
|
17 |
-
-
|
18 |
-
-
|
19 |
-
|
20 |
-
|
21 |
-
|
22 |
-
|
23 |
-
|
24 |
-
|
25 |
-
|
26 |
-
|
27 |
-
|
28 |
-
|
29 |
-
|
30 |
-
|
31 |
-
|
32 |
-
|
33 |
-
|
34 |
-
|
35 |
-
|
36 |
-
|
37 |
-
|
38 |
-
|
39 |
-
border
|
40 |
-
|
41 |
-
|
42 |
-
|
43 |
-
|
44 |
-
|
45 |
-
|
46 |
-
|
47 |
-
|
48 |
-
|
49 |
-
|
50 |
-
|
51 |
-
|
52 |
-
|
53 |
-
|
54 |
-
|
55 |
-
|
56 |
-
|
57 |
-
#ws_actor_selector li
|
58 |
-
|
59 |
-
|
60 |
-
|
61 |
-
|
62 |
-
|
63 |
-
|
64 |
-
|
65 |
-
|
66 |
-
|
67 |
-
|
68 |
-
|
69 |
-
|
70 |
-
|
71 |
-
|
72 |
-
|
73 |
-
|
74 |
-
|
75 |
-
|
76 |
-
margin-
|
77 |
-
|
78 |
-
|
79 |
-
|
80 |
-
|
81 |
-
|
82 |
-
|
83 |
-
|
84 |
-
|
85 |
-
|
86 |
-
|
87 |
-
|
88 |
-
|
89 |
-
|
90 |
-
|
91 |
-
|
92 |
-
|
93 |
-
|
94 |
-
|
95 |
-
|
96 |
-
|
97 |
-
|
98 |
-
|
99 |
-
|
100 |
-
|
101 |
-
|
102 |
-
|
103 |
-
|
104 |
-
|
105 |
-
|
106 |
-
|
107 |
-
|
108 |
-
|
109 |
-
|
110 |
-
|
111 |
-
|
112 |
-
|
113 |
-
|
114 |
-
|
115 |
-
|
116 |
-
|
117 |
-
|
118 |
-
|
119 |
-
|
120 |
-
|
121 |
-
|
122 |
-
|
123 |
-
|
124 |
-
|
125 |
-
|
126 |
-
|
127 |
-
#
|
128 |
-
|
129 |
-
|
130 |
-
|
131 |
-
|
132 |
-
|
133 |
-
|
134 |
-
|
135 |
-
|
136 |
-
|
137 |
-
|
138 |
-
|
139 |
-
|
140 |
-
|
141 |
-
|
142 |
-
|
143 |
-
|
144 |
-
|
145 |
-
|
146 |
-
margin:
|
147 |
-
|
148 |
-
|
149 |
-
|
150 |
-
|
151 |
-
|
152 |
-
|
153 |
-
|
154 |
-
|
155 |
-
|
156 |
-
|
157 |
-
|
158 |
-
|
159 |
-
|
160 |
-
|
161 |
-
|
162 |
-
|
163 |
-
|
164 |
-
|
165 |
-
|
166 |
-
|
167 |
-
|
168 |
-
|
169 |
-
|
170 |
-
|
171 |
-
|
172 |
-
|
173 |
-
|
174 |
-
|
175 |
-
|
176 |
-
.
|
177 |
-
|
178 |
-
|
179 |
-
|
180 |
-
|
181 |
-
|
182 |
-
|
183 |
-
|
184 |
-
|
185 |
-
|
186 |
-
|
187 |
-
|
188 |
-
|
189 |
-
|
190 |
-
|
191 |
-
|
192 |
-
|
193 |
-
|
194 |
-
|
195 |
-
|
196 |
-
|
197 |
-
|
198 |
-
|
199 |
-
|
200 |
-
|
201 |
-
|
202 |
-
|
203 |
-
|
204 |
-
|
205 |
-
|
206 |
-
|
207 |
-
|
208 |
-
|
209 |
-
|
210 |
-
|
211 |
-
|
212 |
-
|
213 |
-
|
214 |
-
|
215 |
-
|
216 |
-
|
217 |
-
|
218 |
-
|
219 |
-
|
220 |
-
|
221 |
-
|
222 |
-
|
223 |
-
|
224 |
-
|
225 |
-
|
226 |
-
|
227 |
-
|
228 |
-
|
229 |
-
|
230 |
-
|
231 |
-
|
232 |
-
|
233 |
-
|
234 |
-
|
235 |
-
|
236 |
-
|
237 |
-
|
238 |
-
|
239 |
-
|
240 |
-
|
241 |
-
|
242 |
-
|
243 |
-
|
244 |
-
|
245 |
-
|
246 |
-
|
247 |
-
|
248 |
-
|
249 |
-
|
250 |
-
|
251 |
-
|
252 |
-
|
253 |
-
|
254 |
-
|
255 |
-
.
|
256 |
-
|
257 |
-
|
258 |
-
|
259 |
-
|
260 |
-
|
261 |
-
|
262 |
-
|
263 |
-
|
264 |
-
|
265 |
-
|
266 |
-
|
267 |
-
|
268 |
-
|
269 |
-
.
|
270 |
-
|
271 |
-
|
272 |
-
|
273 |
-
|
274 |
-
|
275 |
-
|
276 |
-
|
277 |
-
|
278 |
-
|
279 |
-
|
280 |
-
|
281 |
-
|
282 |
-
|
283 |
-
|
284 |
-
|
285 |
-
|
286 |
-
|
287 |
-
|
288 |
-
|
289 |
-
|
290 |
-
|
291 |
-
|
292 |
-
margin
|
293 |
-
|
294 |
-
|
295 |
-
|
296 |
-
|
297 |
-
|
298 |
-
|
299 |
-
|
300 |
-
|
301 |
-
|
302 |
-
|
303 |
-
|
304 |
-
|
305 |
-
|
306 |
-
|
307 |
-
|
308 |
-
|
309 |
-
|
310 |
-
|
311 |
-
|
312 |
-
|
313 |
-
|
314 |
-
|
315 |
-
|
316 |
-
|
317 |
-
|
318 |
-
|
319 |
-
|
320 |
-
|
321 |
-
|
322 |
-
|
323 |
-
|
324 |
-
|
325 |
-
|
326 |
-
|
327 |
-
|
328 |
-
#
|
329 |
-
|
330 |
-
|
331 |
-
|
332 |
-
display:
|
333 |
-
|
334 |
-
|
335 |
-
|
336 |
-
|
337 |
-
|
338 |
-
|
339 |
-
|
340 |
-
|
341 |
-
|
342 |
-
|
343 |
-
|
344 |
-
|
345 |
-
|
346 |
-
|
347 |
-
|
348 |
-
|
349 |
-
|
350 |
-
margin:
|
351 |
-
|
352 |
-
|
353 |
-
|
354 |
-
|
355 |
-
|
356 |
-
|
357 |
-
|
358 |
-
|
359 |
-
|
360 |
-
|
361 |
-
|
362 |
-
|
363 |
-
|
364 |
-
|
365 |
-
|
366 |
-
|
367 |
-
|
368 |
-
|
369 |
-
|
370 |
-
|
371 |
-
#
|
372 |
-
|
373 |
-
|
374 |
-
|
375 |
-
|
376 |
-
font-size:
|
377 |
-
line-height:
|
378 |
-
|
379 |
-
|
380 |
-
|
381 |
-
|
382 |
-
|
383 |
-
|
384 |
-
|
385 |
-
|
386 |
-
|
387 |
-
|
388 |
-
|
389 |
-
|
390 |
-
|
391 |
-
|
392 |
-
|
393 |
-
|
394 |
-
|
395 |
-
|
396 |
-
|
397 |
-
|
398 |
-
#ws_menu_editor
|
399 |
-
|
400 |
-
|
401 |
-
|
402 |
-
|
403 |
-
|
404 |
-
|
405 |
-
-
|
406 |
-
|
407 |
-
|
408 |
-
|
409 |
-
|
410 |
-
|
411 |
-
|
412 |
-
|
413 |
-
|
414 |
-
|
415 |
-
|
416 |
-
|
417 |
-
|
418 |
-
|
419 |
-
|
420 |
-
|
421 |
-
|
422 |
-
|
423 |
-
|
424 |
-
|
425 |
-
|
426 |
-
|
427 |
-
|
428 |
-
|
429 |
-
|
430 |
-
|
431 |
-
.
|
432 |
-
|
433 |
-
|
434 |
-
|
435 |
-
|
436 |
-
|
437 |
-
|
438 |
-
|
439 |
-
|
440 |
-
|
441 |
-
|
442 |
-
|
443 |
-
|
444 |
-
|
445 |
-
|
446 |
-
|
447 |
-
|
448 |
-
|
449 |
-
|
450 |
-
|
451 |
-
|
452 |
-
|
453 |
-
|
454 |
-
|
455 |
-
|
456 |
-
|
457 |
-
|
458 |
-
|
459 |
-
/*
|
460 |
-
|
461 |
-
|
462 |
-
|
463 |
-
|
464 |
-
.
|
465 |
-
|
466 |
-
|
467 |
-
|
468 |
-
|
469 |
-
|
470 |
-
|
471 |
-
|
472 |
-
|
473 |
-
|
474 |
-
|
475 |
-
|
476 |
-
|
477 |
-
|
478 |
-
|
479 |
-
|
480 |
-
|
481 |
-
|
482 |
-
|
483 |
-
|
484 |
-
|
485 |
-
|
486 |
-
|
487 |
-
|
488 |
-
|
489 |
-
|
490 |
-
|
491 |
-
|
492 |
-
|
493 |
-
|
494 |
-
|
495 |
-
|
496 |
-
|
497 |
-
|
498 |
-
|
499 |
-
|
500 |
-
|
501 |
-
|
502 |
-
|
503 |
-
|
504 |
-
|
505 |
-
-
|
506 |
-
|
507 |
-
|
508 |
-
|
509 |
-
|
510 |
-
|
511 |
-
|
512 |
-
|
513 |
-
|
514 |
-
|
515 |
-
|
516 |
-
|
517 |
-
|
518 |
-
|
519 |
-
|
520 |
-
|
521 |
-
|
522 |
-
|
523 |
-
-
|
524 |
-
|
525 |
-
|
526 |
-
|
527 |
-
|
528 |
-
|
529 |
-
|
530 |
-
|
531 |
-
|
532 |
-
|
533 |
-
|
534 |
-
|
535 |
-
|
536 |
-
|
537 |
-
|
538 |
-
|
539 |
-
|
540 |
-
|
541 |
-
|
542 |
-
|
543 |
-
|
544 |
-
|
545 |
-
|
546 |
-
|
547 |
-
|
548 |
-
|
549 |
-
|
550 |
-
|
551 |
-
|
552 |
-
|
553 |
-
|
554 |
-
|
555 |
-
|
556 |
-
|
557 |
-
|
558 |
-
|
559 |
-
|
560 |
-
|
561 |
-
|
562 |
-
|
563 |
-
|
564 |
-
|
565 |
-
|
566 |
-
|
567 |
-
|
568 |
-
|
569 |
-
|
570 |
-
|
571 |
-
|
572 |
-
|
573 |
-
|
574 |
-
|
575 |
-
|
576 |
-
|
577 |
-
|
578 |
-
|
579 |
-
|
580 |
-
|
581 |
-
|
582 |
-
|
583 |
-
|
584 |
-
|
585 |
-
|
586 |
-
|
587 |
-
|
588 |
-
|
589 |
-
|
590 |
-
|
591 |
-
|
592 |
-
|
593 |
-
|
594 |
-
|
595 |
-
|
596 |
-
|
597 |
-
|
598 |
-
margin-
|
599 |
-
|
600 |
-
|
601 |
-
|
602 |
-
|
603 |
-
|
604 |
-
|
605 |
-
|
606 |
-
border-radius: 3px;
|
607 |
-
|
608 |
-
|
609 |
-
|
610 |
-
|
611 |
-
|
612 |
-
|
613 |
-
|
614 |
-
|
615 |
-
#
|
616 |
-
|
617 |
-
|
618 |
-
|
619 |
-
|
620 |
-
|
621 |
-
|
622 |
-
|
623 |
-
|
624 |
-
|
625 |
-
|
626 |
-
|
627 |
-
|
628 |
-
|
629 |
-
|
630 |
-
|
631 |
-
|
632 |
-
|
633 |
-
|
634 |
-
|
635 |
-
|
636 |
-
|
637 |
-
|
638 |
-
|
639 |
-
|
640 |
-
|
641 |
-
|
642 |
-
|
643 |
-
|
644 |
-
|
645 |
-
|
646 |
-
|
647 |
-
|
648 |
-
|
649 |
-
|
650 |
-
|
651 |
-
|
652 |
-
|
653 |
-
|
654 |
-
|
655 |
-
|
656 |
-
|
657 |
-
|
658 |
-
|
659 |
-
padding:
|
660 |
-
|
661 |
-
|
662 |
-
|
663 |
-
|
664 |
-
|
665 |
-
|
666 |
-
|
667 |
-
|
668 |
-
|
669 |
-
|
670 |
-
|
671 |
-
|
672 |
-
|
673 |
-
|
674 |
-
margin: 0;
|
675 |
-
|
676 |
-
|
677 |
-
|
678 |
-
|
679 |
-
|
680 |
-
|
681 |
-
|
682 |
-
|
683 |
-
|
684 |
-
|
685 |
-
|
686 |
-
|
687 |
-
|
688 |
-
|
689 |
-
|
690 |
-
|
691 |
-
|
692 |
-
|
693 |
-
|
694 |
-
|
695 |
-
|
696 |
-
|
697 |
-
|
698 |
-
|
699 |
-
|
700 |
-
|
701 |
-
|
702 |
-
|
703 |
-
|
704 |
-
|
705 |
-
|
706 |
-
|
707 |
-
|
708 |
-
|
709 |
-
|
710 |
-
|
711 |
-
position:
|
712 |
-
|
713 |
-
|
714 |
-
#ws_icon_selector
|
715 |
-
|
716 |
-
|
717 |
-
|
718 |
-
|
719 |
-
|
720 |
-
|
721 |
-
|
722 |
-
|
723 |
-
|
724 |
-
|
725 |
-
|
726 |
-
|
727 |
-
|
728 |
-
|
729 |
-
margin: 2px;
|
730 |
-
|
731 |
-
|
732 |
-
|
733 |
-
|
734 |
-
#
|
735 |
-
|
736 |
-
|
737 |
-
|
738 |
-
|
739 |
-
background-
|
740 |
-
|
741 |
-
|
742 |
-
|
743 |
-
.
|
744 |
-
|
745 |
-
|
746 |
-
|
747 |
-
|
748 |
-
|
749 |
-
|
750 |
-
|
751 |
-
|
752 |
-
|
753 |
-
|
754 |
-
|
755 |
-
|
756 |
-
|
757 |
-
|
758 |
-
|
759 |
-
|
760 |
-
|
761 |
-
|
762 |
-
|
763 |
-
|
764 |
-
|
765 |
-
|
766 |
-
|
767 |
-
|
768 |
-
|
769 |
-
|
770 |
-
|
771 |
-
|
772 |
-
|
773 |
-
|
774 |
-
|
775 |
-
|
776 |
-
|
777 |
-
|
778 |
-
|
779 |
-
|
780 |
-
|
781 |
-
margin-right:
|
782 |
-
|
783 |
-
|
784 |
-
|
785 |
-
|
786 |
-
|
787 |
-
|
788 |
-
|
789 |
-
|
790 |
-
|
791 |
-
|
792 |
-
|
793 |
-
|
794 |
-
|
795 |
-
|
796 |
-
|
797 |
-
|
798 |
-
|
799 |
-
|
800 |
-
|
801 |
-
|
802 |
-
|
803 |
-
|
804 |
-
|
805 |
-
|
806 |
-
|
807 |
-
|
808 |
-
|
809 |
-
|
810 |
-
|
811 |
-
|
812 |
-
|
813 |
-
|
814 |
-
|
815 |
-
|
816 |
-
|
817 |
-
|
818 |
-
|
819 |
-
|
820 |
-
|
821 |
-
|
822 |
-
|
823 |
-
|
824 |
-
|
825 |
-
.
|
826 |
-
display:
|
827 |
-
|
828 |
-
|
829 |
-
|
830 |
-
|
831 |
-
|
832 |
-
|
833 |
-
|
834 |
-
|
835 |
-
|
836 |
-
|
837 |
-
|
838 |
-
|
839 |
-
|
840 |
-
|
841 |
-
|
842 |
-
|
843 |
-
|
844 |
-
|
845 |
-
|
846 |
-
|
847 |
-
|
848 |
-
|
849 |
-
|
850 |
-
|
851 |
-
|
852 |
-
|
853 |
-
|
854 |
-
|
855 |
-
|
856 |
-
|
857 |
-
|
858 |
-
|
859 |
-
|
860 |
-
|
861 |
-
|
862 |
-
|
863 |
-
|
864 |
-
|
865 |
-
|
866 |
-
|
867 |
-
|
868 |
-
|
869 |
-
|
870 |
-
|
871 |
-
|
872 |
-
|
873 |
-
|
874 |
-
|
875 |
-
|
876 |
-
|
877 |
-
|
878 |
-
.
|
879 |
-
|
880 |
-
|
881 |
-
|
882 |
-
|
883 |
-
|
884 |
-
|
885 |
-
|
886 |
-
|
887 |
-
border-
|
888 |
-
|
889 |
-
|
890 |
-
|
891 |
-
|
892 |
-
|
893 |
-
|
894 |
-
|
895 |
-
|
896 |
-
|
897 |
-
|
898 |
-
|
899 |
-
|
900 |
-
|
901 |
-
|
902 |
-
|
903 |
-
right:
|
904 |
-
|
905 |
-
|
906 |
-
|
907 |
-
|
908 |
-
|
909 |
-
height:
|
910 |
-
|
911 |
-
|
912 |
-
|
913 |
-
|
914 |
-
|
915 |
-
|
916 |
-
|
917 |
-
|
918 |
-
|
919 |
-
|
920 |
-
|
921 |
-
|
922 |
-
|
923 |
-
|
924 |
-
|
925 |
-
|
926 |
-
|
927 |
-
|
928 |
-
|
929 |
-
|
930 |
-
|
931 |
-
|
932 |
-
|
933 |
-
|
934 |
-
|
935 |
-
|
936 |
-
|
937 |
-
|
938 |
-
|
939 |
-
|
940 |
-
|
941 |
-
|
942 |
-
|
943 |
-
|
944 |
-
|
945 |
-
|
946 |
-
|
947 |
-
|
948 |
-
|
949 |
-
|
950 |
-
|
951 |
-
|
952 |
-
|
953 |
-
|
954 |
-
-
|
955 |
-
-
|
956 |
-
|
957 |
-
|
958 |
-
|
959 |
-
|
960 |
-
|
961 |
-
|
962 |
-
|
963 |
-
|
964 |
-
|
965 |
-
|
966 |
-
|
967 |
-
|
968 |
-
|
969 |
-
|
970 |
-
|
971 |
-
|
972 |
-
|
973 |
-
|
974 |
-
|
975 |
-
|
976 |
-
|
977 |
-
|
978 |
-
|
979 |
-
|
980 |
-
|
981 |
-
|
982 |
-
|
983 |
-
|
984 |
-
|
985 |
-
|
986 |
-
|
987 |
-
|
988 |
-
|
989 |
-
|
990 |
-
|
991 |
-
|
992 |
-
|
993 |
-
|
994 |
-
|
995 |
-
|
996 |
-
|
997 |
-
|
998 |
-
|
999 |
-
|
1000 |
-
|
1001 |
-
|
1002 |
-
|
1003 |
-
.
|
1004 |
-
|
1005 |
-
|
1006 |
-
|
1007 |
-
|
1008 |
-
|
1009 |
-
|
1010 |
-
|
1011 |
-
|
1012 |
-
|
1013 |
-
|
1014 |
-
|
1015 |
-
|
1016 |
-
|
1017 |
-
|
1018 |
-
|
1019 |
-
|
1020 |
-
margin:
|
1021 |
-
|
1022 |
-
|
1023 |
-
|
1024 |
-
|
1025 |
-
|
1026 |
-
|
1027 |
-
|
1028 |
-
|
1029 |
-
|
1030 |
-
|
1031 |
-
|
1032 |
-
|
1033 |
-
|
1034 |
-
|
1035 |
-
margin-
|
1036 |
-
|
1037 |
-
|
1038 |
-
|
1039 |
-
|
1040 |
-
|
1041 |
-
|
1042 |
-
|
1043 |
-
|
1044 |
-
|
1045 |
-
|
1046 |
-
|
1047 |
-
|
1048 |
-
|
1049 |
-
|
1050 |
-
|
1051 |
-
|
1052 |
-
|
1053 |
-
|
1054 |
-
|
1055 |
-
|
1056 |
-
|
1057 |
-
|
1058 |
-
|
1059 |
-
|
1060 |
-
|
1061 |
-
|
1062 |
-
|
1063 |
-
|
1064 |
-
|
1065 |
-
|
1066 |
-
|
1067 |
-
|
1068 |
-
|
1069 |
-
|
1070 |
-
|
1071 |
-
|
1072 |
-
|
1073 |
-
|
1074 |
-
|
1075 |
-
|
1076 |
-
|
1077 |
-
|
1078 |
-
|
1079 |
-
|
1080 |
-
|
1081 |
-
|
1082 |
-
|
1083 |
-
|
1084 |
-
.
|
1085 |
-
|
1086 |
-
|
1087 |
-
|
1088 |
-
|
1089 |
-
|
1090 |
-
|
1091 |
-
-
|
1092 |
-
-
|
1093 |
-
-
|
1094 |
-
border-
|
1095 |
-
border-
|
1096 |
-
|
1097 |
-
|
1098 |
-
border-top:
|
1099 |
-
|
1100 |
-
|
1101 |
-
|
1102 |
-
|
1103 |
-
|
1104 |
-
|
1105 |
-
|
1106 |
-
|
1107 |
-
.
|
1108 |
-
|
1109 |
-
|
1110 |
-
|
1111 |
-
|
1112 |
-
|
1113 |
-
|
1114 |
-
|
1115 |
-
|
1116 |
-
|
1117 |
-
|
1118 |
-
|
1119 |
-
|
1120 |
-
|
1121 |
-
|
1122 |
-
|
1123 |
-
|
1124 |
-
|
1125 |
-
|
1126 |
-
|
1127 |
-
|
1128 |
-
|
1129 |
-
|
1130 |
-
|
1131 |
-
|
1132 |
-
|
1133 |
-
|
1134 |
-
|
1135 |
-
|
1136 |
-
|
1137 |
-
|
1138 |
-
|
1139 |
-
|
1140 |
-
|
1141 |
-
|
1142 |
-
|
1143 |
-
|
1144 |
-
|
1145 |
-
|
1146 |
-
|
1147 |
-
|
1148 |
-
|
1149 |
-
|
1150 |
-
|
1151 |
-
|
1152 |
-
|
1153 |
-
|
1154 |
-
|
1155 |
-
|
1156 |
-
|
1157 |
-
|
1158 |
-
|
1159 |
-
|
1160 |
-
|
1161 |
-
|
1162 |
-
|
1163 |
-
|
1164 |
-
|
1165 |
-
|
1166 |
-
|
1167 |
-
|
1168 |
-
|
1169 |
-
|
1170 |
-
|
1171 |
-
|
1172 |
-
|
1173 |
-
|
1174 |
-
|
1175 |
-
|
1176 |
-
|
1177 |
-
|
1178 |
-
|
1179 |
-
|
1180 |
-
|
1181 |
-
|
1182 |
-
|
1183 |
-
|
1184 |
-
|
1185 |
-
|
1186 |
-
|
1187 |
-
|
1188 |
-
|
1189 |
-
|
1190 |
-
|
1191 |
-
|
1192 |
-
|
1193 |
-
|
1194 |
-
|
1195 |
-
|
1196 |
-
|
1197 |
-
|
1198 |
-
|
1199 |
-
|
1200 |
-
|
1201 |
-
|
1202 |
-
|
1203 |
-
|
1204 |
-
|
1205 |
-
|
1206 |
-
|
1207 |
-
|
1208 |
-
|
1209 |
-
|
1210 |
-
|
1211 |
-
#
|
1212 |
-
|
1213 |
-
|
1214 |
-
|
1215 |
-
|
1216 |
-
display: inline;
|
1217 |
-
|
1218 |
-
|
1219 |
-
|
1220 |
-
|
1221 |
-
|
1222 |
-
|
1223 |
-
/* The
|
1224 |
-
.
|
1225 |
-
|
1226 |
-
|
1227 |
-
|
1228 |
-
|
1229 |
-
|
1230 |
-
|
1231 |
-
|
1232 |
-
|
1233 |
-
|
1234 |
-
|
1235 |
-
|
1236 |
-
|
1237 |
-
|
1238 |
-
|
1239 |
-
|
1240 |
-
|
1241 |
-
|
1242 |
-
|
1243 |
-
|
1244 |
-
|
1245 |
-
|
1246 |
-
|
1247 |
-
|
1248 |
-
|
1249 |
-
|
1250 |
-
font-size:
|
1251 |
-
|
1252 |
-
|
1253 |
-
|
1254 |
-
|
1255 |
-
|
1256 |
-
|
1257 |
-
|
1258 |
-
|
1259 |
-
|
1260 |
-
|
1261 |
-
|
1262 |
-
|
1263 |
-
|
1264 |
-
|
1265 |
-
|
1266 |
-
|
1267 |
-
|
1268 |
-
|
1269 |
-
|
1270 |
-
|
1271 |
-
|
1272 |
-
|
1273 |
-
|
1274 |
-
|
1275 |
-
|
1276 |
-
|
1277 |
-
|
1278 |
-
|
1279 |
-
|
1280 |
-
|
1281 |
-
|
1282 |
-
|
1283 |
-
|
1284 |
-
|
1285 |
-
|
1286 |
-
|
1287 |
-
|
1288 |
-
|
1289 |
-
|
1290 |
-
|
1291 |
-
|
1292 |
-
|
1293 |
-
|
1294 |
-
|
1295 |
-
|
1296 |
-
|
1297 |
-
|
1298 |
-
|
1299 |
-
|
1300 |
-
|
1301 |
-
|
1302 |
-
|
1303 |
-
|
1304 |
-
|
1305 |
-
|
1306 |
-
|
1307 |
-
|
1308 |
-
|
1309 |
-
|
1310 |
-
|
1311 |
-
|
1312 |
-
|
1313 |
-
|
1314 |
-
|
1315 |
-
|
1316 |
-
|
1317 |
-
|
1318 |
-
|
1319 |
-
|
1320 |
-
|
1321 |
-
|
1322 |
-
|
1323 |
-
|
1324 |
-
|
1325 |
-
|
1326 |
-
|
1327 |
-
|
1328 |
-
|
1329 |
-
|
1330 |
-
|
1331 |
-
|
1332 |
-
|
1333 |
-
|
1334 |
-
|
1335 |
-
|
1336 |
-
|
1337 |
-
|
1338 |
-
|
1339 |
-
|
1340 |
-
|
1341 |
-
|
1342 |
-
|
1343 |
-
|
1344 |
-
|
1345 |
-
|
1346 |
-
|
1347 |
-
|
1348 |
-
|
1349 |
-
|
1350 |
-
|
1351 |
-
|
1352 |
-
|
1353 |
-
|
1354 |
-
|
1355 |
-
|
1356 |
-
|
1357 |
-
|
1358 |
-
|
1359 |
-
|
1360 |
-
|
1361 |
-
|
1362 |
-
|
1363 |
-
|
1364 |
-
|
1365 |
-
|
1366 |
-
|
1367 |
-
|
1368 |
-
|
1369 |
-
|
1370 |
-
|
1371 |
-
|
1372 |
-
border-
|
1373 |
-
|
1374 |
-
|
1375 |
-
|
1376 |
-
|
1377 |
-
|
1378 |
-
|
1379 |
-
|
1380 |
-
.
|
1381 |
-
|
1382 |
-
|
1383 |
-
|
1384 |
-
|
1385 |
-
|
1386 |
-
|
1387 |
-
|
1388 |
-
|
1389 |
-
|
1390 |
-
|
1391 |
-
|
1392 |
-
|
1393 |
-
|
1394 |
-
|
1395 |
-
|
1396 |
-
|
1397 |
-
|
1398 |
-
|
1399 |
-
|
1400 |
-
|
1401 |
-
|
1402 |
-
|
1403 |
-
|
1404 |
-
|
1405 |
-
|
1406 |
-
|
1407 |
-
|
1408 |
-
|
1409 |
-
|
1410 |
-
|
1411 |
-
|
1412 |
-
|
1413 |
-
|
1414 |
-
|
1415 |
-
|
1416 |
-
|
1417 |
-
|
1418 |
-
|
1419 |
-
|
1420 |
-
|
1421 |
-
|
1422 |
-
|
1423 |
-
|
1424 |
-
|
1425 |
-
|
1426 |
-
|
1427 |
-
|
1428 |
-
|
1429 |
-
|
1430 |
-
|
1431 |
-
|
1432 |
-
.
|
1433 |
-
|
1434 |
-
|
1435 |
-
|
1436 |
-
|
1437 |
-
|
1438 |
-
|
1439 |
-
|
1440 |
-
|
1441 |
-
|
1442 |
-
|
1443 |
-
|
1444 |
-
|
1445 |
-
|
1446 |
-
|
1447 |
-
|
1448 |
-
|
1449 |
-
|
1450 |
-
|
1451 |
-
|
1452 |
-
|
1453 |
-
|
1454 |
-
|
1455 |
-
|
1456 |
-
|
1457 |
-
|
1458 |
-
|
1459 |
-
|
1460 |
-
|
1461 |
-
|
1462 |
-
|
1463 |
-
|
1464 |
-
|
1465 |
-
|
1466 |
-
|
1467 |
-
|
1468 |
-
|
1469 |
-
|
1470 |
-
|
1471 |
-
|
1472 |
-
|
1473 |
-
|
1474 |
-
|
1475 |
-
|
1476 |
-
|
1477 |
-
|
1478 |
-
|
1479 |
-
|
1480 |
-
|
1481 |
-
|
1482 |
-
|
1483 |
-
|
1484 |
-
|
1485 |
-
|
1486 |
-
|
1487 |
-
|
1488 |
-
|
1489 |
-
|
1490 |
-
|
1491 |
-
|
1492 |
-
|
1493 |
-
|
1494 |
-
|
1495 |
-
|
1496 |
-
|
1497 |
-
|
1498 |
-
|
1499 |
-
|
1500 |
-
|
1501 |
-
|
1502 |
-
|
1503 |
-
|
1504 |
-
|
1505 |
-
|
1506 |
-
|
1507 |
-
|
1508 |
-
|
1509 |
-
|
1510 |
-
|
1511 |
-
|
1512 |
-
|
1513 |
-
|
1514 |
-
|
1515 |
-
|
1516 |
-
|
1517 |
-
padding
|
1518 |
-
|
1519 |
-
|
1520 |
-
|
1521 |
-
|
1522 |
-
|
1523 |
-
|
1524 |
-
|
1525 |
-
|
1526 |
-
|
1527 |
-
|
1528 |
-
|
1529 |
-
|
1530 |
-
|
1531 |
-
|
1532 |
-
|
1533 |
-
|
1534 |
-
|
1535 |
-
|
1536 |
-
|
1537 |
-
|
1538 |
-
|
1539 |
-
|
1540 |
-
|
1541 |
-
|
1542 |
-
|
1543 |
-
|
1544 |
-
|
1545 |
-
|
1546 |
-
|
1547 |
-
|
1548 |
-
|
1549 |
-
|
1550 |
-
|
1551 |
-
#
|
1552 |
-
|
1553 |
-
|
1554 |
-
|
1555 |
-
|
1556 |
-
|
1557 |
-
|
1558 |
-
|
1559 |
-
|
1560 |
-
|
1561 |
-
|
1562 |
-
|
1563 |
-
|
1564 |
-
|
1565 |
-
|
1566 |
-
|
1567 |
-
|
1568 |
-
color: #
|
1569 |
-
|
1570 |
-
|
1571 |
-
|
1572 |
-
|
1573 |
-
|
1574 |
-
|
1575 |
-
|
1576 |
-
|
1577 |
-
|
1578 |
-
|
1579 |
-
|
1580 |
-
|
1581 |
-
|
1582 |
-
|
1583 |
-
|
1584 |
-
|
1585 |
-
|
1586 |
-
|
1587 |
-
|
1588 |
-
|
1589 |
-
|
1590 |
-
|
1591 |
-
|
1592 |
-
|
1593 |
-
|
1594 |
-
|
1595 |
-
|
1596 |
-
|
1597 |
-
|
1598 |
-
|
1599 |
-
|
1600 |
-
|
1601 |
-
|
1602 |
-
|
1603 |
-
|
1604 |
-
|
1605 |
-
|
1606 |
-
|
1607 |
-
|
1608 |
-
|
1609 |
-
|
1610 |
-
|
1611 |
-
|
1612 |
-
width:
|
1613 |
-
|
1614 |
-
|
1615 |
-
|
1616 |
-
|
1617 |
-
|
1618 |
-
.
|
1619 |
-
|
1620 |
-
|
1621 |
-
|
1622 |
-
|
1623 |
-
-
|
1624 |
-
|
1625 |
-
|
1626 |
-
|
1627 |
-
|
1628 |
-
|
1629 |
-
|
1630 |
-
|
1631 |
-
|
1632 |
-
width:
|
1633 |
-
|
1634 |
-
|
1635 |
-
|
1636 |
-
|
1637 |
-
|
1638 |
-
|
1639 |
-
|
1640 |
-
|
1641 |
-
|
1642 |
-
|
1643 |
-
|
1644 |
-
|
1645 |
-
|
1646 |
-
|
1647 |
-
|
1648 |
-
|
1649 |
-
|
1650 |
-
|
1651 |
-
|
1652 |
-
-
|
1653 |
-
|
1654 |
-
|
1655 |
-
|
1656 |
-
|
1657 |
-
|
1658 |
-
|
1659 |
-
|
1660 |
-
|
1661 |
-
|
1662 |
-
|
1663 |
-
|
1664 |
-
|
1665 |
-
|
1666 |
-
|
1667 |
-
|
1668 |
-
|
1669 |
-
|
1670 |
-
|
1671 |
-
|
1672 |
-
|
1673 |
-
|
1674 |
-
-
|
1675 |
-
|
1676 |
-
|
1677 |
-
|
1678 |
-
|
1679 |
-
|
1680 |
-
|
1681 |
-
|
1682 |
-
|
1683 |
-
|
1684 |
-
|
1685 |
-
|
1686 |
-
|
1687 |
-
|
1688 |
-
|
1689 |
-
|
1690 |
-
|
1691 |
-
|
1692 |
-
|
1693 |
-
|
1694 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
@charset "UTF-8";
|
2 |
+
/* Admin Menu Editor CSS file */
|
3 |
+
#ws_menu_editor {
|
4 |
+
min-width: 780px;
|
5 |
+
}
|
6 |
+
|
7 |
+
.ame-is-free-version #ws_menu_editor {
|
8 |
+
margin-top: 9px;
|
9 |
+
}
|
10 |
+
|
11 |
+
.ws_main_container {
|
12 |
+
margin: 2px;
|
13 |
+
width: 316px;
|
14 |
+
float: left;
|
15 |
+
display: block;
|
16 |
+
border: 1px solid #ccd0d4;
|
17 |
+
box-shadow: 0 1px 1px rgba(0, 0, 0, 0.04);
|
18 |
+
background-color: #FFFFFF;
|
19 |
+
border-radius: 0px;
|
20 |
+
-moz-border-radius: 0px;
|
21 |
+
-webkit-border-radius: 0px;
|
22 |
+
}
|
23 |
+
|
24 |
+
.ws_box {
|
25 |
+
min-height: 30px;
|
26 |
+
width: 100%;
|
27 |
+
margin: 0;
|
28 |
+
}
|
29 |
+
|
30 |
+
.ws_basic_container {
|
31 |
+
float: left;
|
32 |
+
display: block;
|
33 |
+
}
|
34 |
+
|
35 |
+
.ws_dropzone {
|
36 |
+
display: block;
|
37 |
+
box-sizing: border-box;
|
38 |
+
margin: 2px 6px;
|
39 |
+
border: 3px none #b4b9be;
|
40 |
+
height: 31px;
|
41 |
+
}
|
42 |
+
|
43 |
+
.ws_dropzone_active,
|
44 |
+
.ws_dropzone_hover,
|
45 |
+
.ws_top_to_submenu_drop_hover .ws_dropzone {
|
46 |
+
border-style: dashed;
|
47 |
+
}
|
48 |
+
|
49 |
+
.ws_dropzone_hover,
|
50 |
+
.ws_top_to_submenu_drop_hover .ws_dropzone {
|
51 |
+
border-width: 1px;
|
52 |
+
}
|
53 |
+
|
54 |
+
/*************************************************
|
55 |
+
Actor UI
|
56 |
+
*************************************************/
|
57 |
+
#ws_actor_selector li:after {
|
58 |
+
content: "| ";
|
59 |
+
}
|
60 |
+
|
61 |
+
#ws_actor_selector li:last-child:after {
|
62 |
+
content: "";
|
63 |
+
}
|
64 |
+
|
65 |
+
#ws_actor_selector li a {
|
66 |
+
display: inline-block;
|
67 |
+
text-align: center;
|
68 |
+
}
|
69 |
+
#ws_actor_selector li a::before {
|
70 |
+
display: block;
|
71 |
+
content: attr(data-text);
|
72 |
+
font-weight: bold;
|
73 |
+
height: 1px;
|
74 |
+
overflow: hidden;
|
75 |
+
visibility: hidden;
|
76 |
+
margin-bottom: -1px;
|
77 |
+
}
|
78 |
+
|
79 |
+
#ws_actor_selector {
|
80 |
+
margin-top: 6px;
|
81 |
+
}
|
82 |
+
|
83 |
+
/**
|
84 |
+
* The checkbox that lets the user show/hide a menu for the currently selected actor.
|
85 |
+
*/
|
86 |
+
#ws_menu_editor .ws_actor_access_checkbox,
|
87 |
+
#ws_menu_editor input[type=checkbox].ws_actor_access_checkbox {
|
88 |
+
margin-right: 2px;
|
89 |
+
margin-left: 2px;
|
90 |
+
margin-top: 1px;
|
91 |
+
vertical-align: text-top;
|
92 |
+
}
|
93 |
+
#ws_menu_editor .ws_actor_access_checkbox:indeterminate:before,
|
94 |
+
#ws_menu_editor input[type=checkbox].ws_actor_access_checkbox:indeterminate:before {
|
95 |
+
content: "■";
|
96 |
+
color: #1e8cbe;
|
97 |
+
margin: -3px 0 0 -1px;
|
98 |
+
font: 400 14px/1 dashicons;
|
99 |
+
float: left;
|
100 |
+
display: inline-block;
|
101 |
+
vertical-align: middle;
|
102 |
+
width: 16px;
|
103 |
+
-webkit-font-smoothing: antialiased;
|
104 |
+
}
|
105 |
+
|
106 |
+
@media screen and (max-width: 782px) {
|
107 |
+
#ws_menu_editor input[type=checkbox].ws_actor_access_checkbox:indeterminate:before {
|
108 |
+
margin: -6px 0 0 1px;
|
109 |
+
font: 400 26px/1 dashicons;
|
110 |
+
}
|
111 |
+
}
|
112 |
+
/* The checkbox is only visible when viewing the menu configuration for a specific actor. */
|
113 |
+
#ws_menu_editor .ws_actor_access_checkbox {
|
114 |
+
display: none;
|
115 |
+
}
|
116 |
+
|
117 |
+
#ws_menu_editor.ws_is_actor_view .ws_actor_access_checkbox {
|
118 |
+
display: inline-block;
|
119 |
+
}
|
120 |
+
|
121 |
+
/* Gray-out items inaccessible to the currently selected actor */
|
122 |
+
.ws_is_actor_view .ws_container.ws_is_hidden_for_actor {
|
123 |
+
background-color: #F9F9F9;
|
124 |
+
}
|
125 |
+
|
126 |
+
.ws_is_actor_view .ws_is_hidden_for_actor .ws_item_title {
|
127 |
+
color: #777;
|
128 |
+
}
|
129 |
+
|
130 |
+
/*
|
131 |
+
* The sidebar
|
132 |
+
*/
|
133 |
+
#ws_editor_sidebar {
|
134 |
+
width: auto;
|
135 |
+
padding: 2px;
|
136 |
+
}
|
137 |
+
|
138 |
+
#ws_menu_editor .ws_main_button {
|
139 |
+
clear: both;
|
140 |
+
display: block;
|
141 |
+
margin: 4px;
|
142 |
+
width: 130px;
|
143 |
+
}
|
144 |
+
|
145 |
+
#ws_menu_editor #ws_save_menu {
|
146 |
+
margin-bottom: 20px;
|
147 |
+
}
|
148 |
+
|
149 |
+
#ws_menu_editor #ws_toggle_editor_layout {
|
150 |
+
display: none;
|
151 |
+
}
|
152 |
+
|
153 |
+
#ws_menu_editor .ws_sidebar_button_separator {
|
154 |
+
display: block;
|
155 |
+
height: 4px;
|
156 |
+
margin: 0;
|
157 |
+
padding: 0;
|
158 |
+
}
|
159 |
+
|
160 |
+
/*
|
161 |
+
* Page heading and tabs
|
162 |
+
*/
|
163 |
+
#ws_ame_editor_heading {
|
164 |
+
float: left;
|
165 |
+
}
|
166 |
+
|
167 |
+
/*
|
168 |
+
* Menu components and widgets
|
169 |
+
*/
|
170 |
+
.ws_container {
|
171 |
+
display: block;
|
172 |
+
width: 296px;
|
173 |
+
padding: 3px;
|
174 |
+
margin: 2px 0 2px 6px;
|
175 |
+
}
|
176 |
+
body.rtl .ws_container {
|
177 |
+
margin-right: 6px;
|
178 |
+
margin-left: 0;
|
179 |
+
}
|
180 |
+
|
181 |
+
.ws_submenu {
|
182 |
+
min-height: 2em;
|
183 |
+
}
|
184 |
+
|
185 |
+
.ws_item_head {
|
186 |
+
padding: 0;
|
187 |
+
}
|
188 |
+
|
189 |
+
.ws_item_title {
|
190 |
+
display: inline-block;
|
191 |
+
padding: 2px;
|
192 |
+
cursor: default;
|
193 |
+
font-size: 13px;
|
194 |
+
line-height: 18px;
|
195 |
+
}
|
196 |
+
|
197 |
+
.ws_edit_link {
|
198 |
+
float: right;
|
199 |
+
margin-right: 0;
|
200 |
+
cursor: pointer;
|
201 |
+
display: block;
|
202 |
+
width: 40px;
|
203 |
+
height: 22px;
|
204 |
+
border-radius: 3px;
|
205 |
+
-moz-border-radius: 3px;
|
206 |
+
-webkit-border-radius: 3px;
|
207 |
+
text-decoration: none;
|
208 |
+
}
|
209 |
+
|
210 |
+
.ws_menu_drop_hover {
|
211 |
+
background-color: #43b529 !important;
|
212 |
+
}
|
213 |
+
|
214 |
+
.ws_container.ui-sortable-helper * {
|
215 |
+
cursor: move !important;
|
216 |
+
}
|
217 |
+
|
218 |
+
.ws_container.ws_sortable_placeholder {
|
219 |
+
outline: 1px dashed #b4b9be;
|
220 |
+
outline-offset: -1px;
|
221 |
+
background: none;
|
222 |
+
border-color: transparent;
|
223 |
+
}
|
224 |
+
|
225 |
+
/*
|
226 |
+
If you ever want to apply a right-arrow style to the currently selected menu item,
|
227 |
+
you can do it like this. Commented out for now since it doesn't look all that great,
|
228 |
+
but might be useful in the future.
|
229 |
+
*/
|
230 |
+
/*
|
231 |
+
.ws_container {
|
232 |
+
position: relative;
|
233 |
+
}
|
234 |
+
|
235 |
+
.ws_menu.ws_active::after {
|
236 |
+
content: "";
|
237 |
+
display: block;
|
238 |
+
z-index: 1002;
|
239 |
+
|
240 |
+
border-left: 14px solid #8EB0F1;
|
241 |
+
border-top: 15px solid rgba(255, 255, 255, 0.1);
|
242 |
+
border-bottom: 15px solid rgba(255, 255, 255, 0.1);
|
243 |
+
background: transparent;
|
244 |
+
|
245 |
+
position: absolute;
|
246 |
+
right: -14px;
|
247 |
+
top: -1px;
|
248 |
+
|
249 |
+
width: 0;
|
250 |
+
height: 0;
|
251 |
+
}
|
252 |
+
*/
|
253 |
+
/*
|
254 |
+
* A left-arrow style alternative. This one is image-based and doesn't suffer from the finicky sizing issues
|
255 |
+
* of CSS triangles.
|
256 |
+
*/
|
257 |
+
.ws_container {
|
258 |
+
position: relative;
|
259 |
+
}
|
260 |
+
|
261 |
+
.ws_menu.ws_active::after {
|
262 |
+
content: "";
|
263 |
+
display: block;
|
264 |
+
position: absolute;
|
265 |
+
right: -19px;
|
266 |
+
top: -1px;
|
267 |
+
width: 19px;
|
268 |
+
height: 30px;
|
269 |
+
background: transparent url("../images/submenu-tip.png") no-repeat center;
|
270 |
+
}
|
271 |
+
|
272 |
+
.ws_container.ws_menu_separator.ws_active::after,
|
273 |
+
.ws_container.ui-sortable-helper::after {
|
274 |
+
background-image: none;
|
275 |
+
}
|
276 |
+
|
277 |
+
/****************************************
|
278 |
+
Per-menu settings fields & panels
|
279 |
+
*****************************************/
|
280 |
+
.ws_editbox {
|
281 |
+
display: block;
|
282 |
+
padding: 4px;
|
283 |
+
border-radius: 2px;
|
284 |
+
border-top-right-radius: 0;
|
285 |
+
-moz-border-radius: 2px;
|
286 |
+
-moz-border-radius-topright: 0;
|
287 |
+
-webkit-border-radius: 2px;
|
288 |
+
-webkit-border-top-right-radius: 0;
|
289 |
+
}
|
290 |
+
|
291 |
+
.ws_edit_panel {
|
292 |
+
margin: 0;
|
293 |
+
padding: 0;
|
294 |
+
border: none;
|
295 |
+
}
|
296 |
+
|
297 |
+
.ws_edit_field {
|
298 |
+
margin-bottom: 6px;
|
299 |
+
min-height: 45px;
|
300 |
+
}
|
301 |
+
.ws_edit_field:after {
|
302 |
+
visibility: hidden;
|
303 |
+
display: block;
|
304 |
+
height: 0;
|
305 |
+
font-size: 0;
|
306 |
+
content: " ";
|
307 |
+
clear: both;
|
308 |
+
}
|
309 |
+
|
310 |
+
.ws_edit_field-custom {
|
311 |
+
margin-top: 10px;
|
312 |
+
}
|
313 |
+
|
314 |
+
.ws_edit_field.ws_no_field_caption {
|
315 |
+
margin-top: 10px;
|
316 |
+
padding-left: 1px;
|
317 |
+
height: 25px;
|
318 |
+
min-height: 25px;
|
319 |
+
}
|
320 |
+
|
321 |
+
/*
|
322 |
+
* Group headings
|
323 |
+
*/
|
324 |
+
.ws_edit_field.ws_field_group_heading {
|
325 |
+
height: 1px;
|
326 |
+
min-height: 0;
|
327 |
+
padding-top: 0;
|
328 |
+
background: #ccc;
|
329 |
+
margin: 8px -4px 5px;
|
330 |
+
}
|
331 |
+
.ws_edit_field.ws_field_group_heading span {
|
332 |
+
display: none;
|
333 |
+
font-weight: bold;
|
334 |
+
}
|
335 |
+
|
336 |
+
/* The reset-to-default button */
|
337 |
+
.ws_reset_button {
|
338 |
+
display: block;
|
339 |
+
float: right;
|
340 |
+
margin-left: 4px;
|
341 |
+
margin-top: 2px;
|
342 |
+
margin-right: 6px;
|
343 |
+
cursor: pointer;
|
344 |
+
width: 16px;
|
345 |
+
height: 16px;
|
346 |
+
vertical-align: top;
|
347 |
+
background: url("../images/pencil_delete_gray.png") no-repeat center;
|
348 |
+
}
|
349 |
+
.ame-is-wp53-plus .ws_reset_button {
|
350 |
+
margin-top: 5px;
|
351 |
+
}
|
352 |
+
|
353 |
+
.ws_reset_button:hover {
|
354 |
+
background-image: url("../images/pencil_delete.png");
|
355 |
+
}
|
356 |
+
|
357 |
+
.ws_input_default input,
|
358 |
+
.ws_input_default select,
|
359 |
+
.ws_input_default .ws_color_scheme_display {
|
360 |
+
color: gray;
|
361 |
+
}
|
362 |
+
|
363 |
+
/* No reset button for fields set to the default value and fields without a default value */
|
364 |
+
.ws_input_default .ws_reset_button,
|
365 |
+
.ws_has_no_default .ws_reset_button {
|
366 |
+
visibility: hidden;
|
367 |
+
}
|
368 |
+
|
369 |
+
/* The input box in each field editor */
|
370 |
+
#ws_menu_editor .ws_editbox input[type=text],
|
371 |
+
#ws_menu_editor .ws_editbox select {
|
372 |
+
display: block;
|
373 |
+
float: left;
|
374 |
+
width: 254px;
|
375 |
+
height: 25px;
|
376 |
+
font-size: 12px;
|
377 |
+
line-height: 17px;
|
378 |
+
padding-top: 3px;
|
379 |
+
padding-bottom: 3px;
|
380 |
+
}
|
381 |
+
.ame-is-wp53-plus #ws_menu_editor .ws_editbox input[type=text],
|
382 |
+
.ame-is-wp53-plus #ws_menu_editor .ws_editbox select {
|
383 |
+
height: 28px;
|
384 |
+
}
|
385 |
+
|
386 |
+
#ws_menu_editor .ws_edit_field label {
|
387 |
+
display: block;
|
388 |
+
float: left;
|
389 |
+
}
|
390 |
+
|
391 |
+
#ws_menu_editor .ws_edit_field-custom input[type=checkbox] {
|
392 |
+
margin-top: 0;
|
393 |
+
}
|
394 |
+
|
395 |
+
#ws_menu_editor input[type=text].ws_field_value {
|
396 |
+
min-height: 25px;
|
397 |
+
}
|
398 |
+
.ame-is-wp53-plus #ws_menu_editor input[type=text].ws_field_value {
|
399 |
+
min-height: 28px;
|
400 |
+
}
|
401 |
+
|
402 |
+
/* Dropdown button for combo-box fields */
|
403 |
+
#ws_menu_editor .ws_dropdown_button,
|
404 |
+
#ws_menu_access_editor .ws_dropdown_button {
|
405 |
+
box-sizing: border-box;
|
406 |
+
width: 25px;
|
407 |
+
height: 25px;
|
408 |
+
min-height: 25px;
|
409 |
+
margin: 1px 1px 1px 0;
|
410 |
+
padding: 0;
|
411 |
+
text-align: center;
|
412 |
+
font-size: 9px !important;
|
413 |
+
line-height: 25px;
|
414 |
+
border-color: #dfdfdf;
|
415 |
+
box-shadow: none;
|
416 |
+
border-top-right-radius: 3px;
|
417 |
+
border-bottom-right-radius: 3px;
|
418 |
+
border-top-left-radius: 0;
|
419 |
+
border-bottom-left-radius: 0;
|
420 |
+
-moz-border-radius-topright: 3px;
|
421 |
+
-moz-border-radius-bottomright: 3px;
|
422 |
+
-moz-border-radius-topleft: 0;
|
423 |
+
-moz-border-radius-bottomleft: 0;
|
424 |
+
-webkit-border-top-right-radius: 3px;
|
425 |
+
-webkit-border-bottom-right-radius: 3px;
|
426 |
+
-webkit-border-top-left-radius: 0;
|
427 |
+
-webkit-border-bottom-left-radius: 0;
|
428 |
+
}
|
429 |
+
|
430 |
+
.ame-is-wp53-plus #ws_menu_editor .ws_dropdown_button,
|
431 |
+
#ws_menu_access_editor.ame-is-wp53-plus .ws_dropdown_button {
|
432 |
+
height: 28px;
|
433 |
+
border-color: #7e8993;
|
434 |
+
background-color: white;
|
435 |
+
border-left-style: none;
|
436 |
+
font-size: 10px !important;
|
437 |
+
line-height: 24px;
|
438 |
+
color: #555;
|
439 |
+
}
|
440 |
+
.ame-is-wp53-plus #ws_menu_editor .ws_dropdown_button:hover,
|
441 |
+
#ws_menu_access_editor.ame-is-wp53-plus .ws_dropdown_button:hover {
|
442 |
+
color: #23282d;
|
443 |
+
}
|
444 |
+
|
445 |
+
#ws_menu_access_editor .ws_dropdown_button {
|
446 |
+
display: inline-block;
|
447 |
+
height: 27px;
|
448 |
+
}
|
449 |
+
|
450 |
+
#ws_menu_access_editor.ame-is-wp53-plus .ws_dropdown_button {
|
451 |
+
height: 30px;
|
452 |
+
}
|
453 |
+
|
454 |
+
#ws_menu_editor .ws_dropdown_button {
|
455 |
+
display: block;
|
456 |
+
float: left;
|
457 |
+
}
|
458 |
+
|
459 |
+
/*
|
460 |
+
The appearance and size of combo-box fields need to be changed
|
461 |
+
to accommodate the drop-down button.
|
462 |
+
*/
|
463 |
+
#ws_menu_editor .ws_has_dropdown input.ws_field_value,
|
464 |
+
#ws_menu_access_editor input.ws_has_dropdown {
|
465 |
+
margin-right: 0;
|
466 |
+
border-right: 0;
|
467 |
+
border-top-right-radius: 0;
|
468 |
+
border-bottom-right-radius: 0;
|
469 |
+
-moz-border-radius-topright: 0;
|
470 |
+
-moz-border-radius-bottomright: 0;
|
471 |
+
-webkit-border-top-right-radius: 0;
|
472 |
+
-webkit-border-bottom-right-radius: 0;
|
473 |
+
}
|
474 |
+
|
475 |
+
#ws_menu_access_editor input.ws_has_dropdown {
|
476 |
+
width: 90%;
|
477 |
+
box-sizing: border-box;
|
478 |
+
height: 27px;
|
479 |
+
}
|
480 |
+
|
481 |
+
#ws_menu_access_editor.ame-is-wp53-plus input.ws_has_dropdown {
|
482 |
+
height: 30px;
|
483 |
+
}
|
484 |
+
|
485 |
+
#ws_menu_editor .ws_has_dropdown input.ws_field_value {
|
486 |
+
width: 229px;
|
487 |
+
}
|
488 |
+
|
489 |
+
/* Unlike others, this field is just a single checkbox, so it has a smaller height */
|
490 |
+
#ws_menu_editor .ws_edit_field-custom {
|
491 |
+
height: 16px;
|
492 |
+
}
|
493 |
+
|
494 |
+
/*
|
495 |
+
* "Show/hide advanced fields"
|
496 |
+
*/
|
497 |
+
.ws_toggle_container {
|
498 |
+
text-align: right;
|
499 |
+
margin-right: 27px;
|
500 |
+
}
|
501 |
+
|
502 |
+
.ws_toggle_advanced_fields {
|
503 |
+
color: #6087CB;
|
504 |
+
text-decoration: none;
|
505 |
+
font-size: 0.85em;
|
506 |
+
}
|
507 |
+
|
508 |
+
.ws_toggle_advanced_fields:visited, .ws_toggle_advanced_fields:active {
|
509 |
+
color: #6087CB;
|
510 |
+
}
|
511 |
+
|
512 |
+
.ws_toggle_advanced_fields:hover {
|
513 |
+
color: #d54e21;
|
514 |
+
text-decoration: underline;
|
515 |
+
}
|
516 |
+
|
517 |
+
/************************************
|
518 |
+
Menu flags
|
519 |
+
*************************************/
|
520 |
+
.ws_flag_container {
|
521 |
+
float: right;
|
522 |
+
margin-right: 4px;
|
523 |
+
padding-top: 2px;
|
524 |
+
}
|
525 |
+
|
526 |
+
.ws_flag {
|
527 |
+
display: block;
|
528 |
+
float: right;
|
529 |
+
width: 16px;
|
530 |
+
height: 16px;
|
531 |
+
margin-left: 4px;
|
532 |
+
background-repeat: no-repeat;
|
533 |
+
}
|
534 |
+
|
535 |
+
/* user-created items */
|
536 |
+
.ws_custom_flag {
|
537 |
+
background-image: url("../images/page-add.png");
|
538 |
+
}
|
539 |
+
|
540 |
+
/* unused items - those that are in the default menu but not in the custom one */
|
541 |
+
.ws_unused_flag {
|
542 |
+
background-image: url("../images/new-menu-badge.png");
|
543 |
+
width: 31px;
|
544 |
+
}
|
545 |
+
|
546 |
+
/* hidden items */
|
547 |
+
.ws_hidden_flag {
|
548 |
+
background-image: url("../images/page-invisible.png");
|
549 |
+
}
|
550 |
+
|
551 |
+
/* items with custom permissions for the selected actor */
|
552 |
+
.ws_custom_actor_permissions_flag {
|
553 |
+
font: 16px/1 "dashicons";
|
554 |
+
}
|
555 |
+
|
556 |
+
.ws_custom_actor_permissions_flag::before {
|
557 |
+
/*content: "\f160";*/
|
558 |
+
/* padlock */
|
559 |
+
content: "";
|
560 |
+
/* human silhouette */
|
561 |
+
color: black;
|
562 |
+
filter: alpha(opacity=25);
|
563 |
+
/*IE 5-7*/
|
564 |
+
opacity: 0.25;
|
565 |
+
}
|
566 |
+
|
567 |
+
/* Hidden from everyone except the current user and Super Admin. */
|
568 |
+
.ws_hidden_from_others_flag {
|
569 |
+
background-image: url("../images/font-awesome/eye-slash.png");
|
570 |
+
}
|
571 |
+
|
572 |
+
/* Item visibility can't be determined because it depends on a meta capability. */
|
573 |
+
.ws_uncertain_meta_cap_flag::before {
|
574 |
+
font: 16px/1 "dashicons";
|
575 |
+
content: "";
|
576 |
+
color: black;
|
577 |
+
filter: alpha(opacity=25);
|
578 |
+
/*IE 5-7*/
|
579 |
+
opacity: 0.25;
|
580 |
+
}
|
581 |
+
|
582 |
+
/* These classes could be used to apply different styles to items depending on their flags */
|
583 |
+
/************************************
|
584 |
+
Toolbars
|
585 |
+
*************************************/
|
586 |
+
.ws_toolbar {
|
587 |
+
display: block;
|
588 |
+
-webkit-box-sizing: border-box;
|
589 |
+
-moz-box-sizing: border-box;
|
590 |
+
box-sizing: border-box;
|
591 |
+
width: 100%;
|
592 |
+
padding: 6px 6px 0 6px;
|
593 |
+
}
|
594 |
+
|
595 |
+
.ws_button {
|
596 |
+
display: block;
|
597 |
+
margin-right: 3px;
|
598 |
+
margin-bottom: 4px;
|
599 |
+
padding: 4px;
|
600 |
+
float: left;
|
601 |
+
-webkit-box-sizing: content-box;
|
602 |
+
-moz-box-sizing: content-box;
|
603 |
+
box-sizing: content-box;
|
604 |
+
width: 16px;
|
605 |
+
height: 16px;
|
606 |
+
border-radius: 3px;
|
607 |
+
-moz-border-radius: 3px;
|
608 |
+
-webkit-border-radius: 3px;
|
609 |
+
}
|
610 |
+
.ws_button img {
|
611 |
+
vertical-align: top;
|
612 |
+
}
|
613 |
+
|
614 |
+
a.ws_button:hover {
|
615 |
+
background-color: #d0e0ff;
|
616 |
+
border-color: #9090c0;
|
617 |
+
}
|
618 |
+
|
619 |
+
.ws_button.ws_button_disabled {
|
620 |
+
border-color: #ccc;
|
621 |
+
}
|
622 |
+
|
623 |
+
a.ws_button.ws_button_disabled:hover {
|
624 |
+
background-color: white;
|
625 |
+
border: 1px solid #ccc;
|
626 |
+
}
|
627 |
+
|
628 |
+
.ws_button_disabled img {
|
629 |
+
filter: grayscale(1);
|
630 |
+
-webkit-filter: grayscale(1);
|
631 |
+
opacity: 0.65;
|
632 |
+
}
|
633 |
+
|
634 |
+
.ws_separator {
|
635 |
+
float: left;
|
636 |
+
width: 5px;
|
637 |
+
}
|
638 |
+
|
639 |
+
#ws_toggle_toolbar, .ws_toggle_toolbar_button {
|
640 |
+
margin-right: 0;
|
641 |
+
}
|
642 |
+
|
643 |
+
/************************************
|
644 |
+
Capability selector
|
645 |
+
*************************************/
|
646 |
+
select.ws_dropdown {
|
647 |
+
width: 252px;
|
648 |
+
height: 20em;
|
649 |
+
z-index: 1002;
|
650 |
+
position: absolute;
|
651 |
+
display: none;
|
652 |
+
font-family: "Lucida Grande", Verdana, Arial, "Bitstream Vera Sans", sans-serif;
|
653 |
+
font-size: 12px;
|
654 |
+
}
|
655 |
+
|
656 |
+
select.ws_dropdown option {
|
657 |
+
font-family: "Lucida Grande", Verdana, Arial, "Bitstream Vera Sans", sans-serif;
|
658 |
+
font-size: 12px;
|
659 |
+
padding: 3px;
|
660 |
+
}
|
661 |
+
|
662 |
+
select.ws_dropdown optgroup option {
|
663 |
+
padding-left: 10px;
|
664 |
+
}
|
665 |
+
|
666 |
+
/************************************
|
667 |
+
Tabs (small)
|
668 |
+
************************************
|
669 |
+
Tabbed navigation for dropdowns and small dialogs.
|
670 |
+
*/
|
671 |
+
.ws_tool_tab_nav {
|
672 |
+
list-style: outside none none;
|
673 |
+
padding: 0;
|
674 |
+
margin: 0 0 0 6px;
|
675 |
+
}
|
676 |
+
.ws_tool_tab_nav li {
|
677 |
+
display: inline-block;
|
678 |
+
border: 1px solid transparent;
|
679 |
+
border-bottom-width: 0;
|
680 |
+
padding: 3px 5px 5px;
|
681 |
+
line-height: 1.35em;
|
682 |
+
margin-bottom: 0;
|
683 |
+
}
|
684 |
+
.ws_tool_tab_nav li.ui-tabs-active {
|
685 |
+
border-color: #dfdfdf;
|
686 |
+
border-bottom-color: #FDFDFD;
|
687 |
+
background: #FDFDFD none;
|
688 |
+
}
|
689 |
+
.ws_tool_tab_nav a {
|
690 |
+
text-decoration: none;
|
691 |
+
}
|
692 |
+
.ws_tool_tab_nav li.ui-tabs-active a {
|
693 |
+
color: #32373C;
|
694 |
+
}
|
695 |
+
|
696 |
+
.ws_tool_tab {
|
697 |
+
border-top: 1px solid #DFDFDF;
|
698 |
+
margin-top: -1px;
|
699 |
+
background-color: #FDFDFD;
|
700 |
+
}
|
701 |
+
|
702 |
+
/************************************
|
703 |
+
Icon selector
|
704 |
+
*************************************/
|
705 |
+
#ws_icon_selector {
|
706 |
+
border: 1px solid silver;
|
707 |
+
border-radius: 3px;
|
708 |
+
background-color: white;
|
709 |
+
width: 216px;
|
710 |
+
padding: 4px 0 0 0;
|
711 |
+
position: absolute;
|
712 |
+
}
|
713 |
+
|
714 |
+
#ws_icon_selector.ws_with_more_icons {
|
715 |
+
width: 570px;
|
716 |
+
}
|
717 |
+
|
718 |
+
#ws_icon_selector .ws_icon_extra {
|
719 |
+
display: none;
|
720 |
+
}
|
721 |
+
|
722 |
+
#ws_icon_selector.ws_with_more_icons .ws_icon_extra {
|
723 |
+
display: inline-block;
|
724 |
+
}
|
725 |
+
|
726 |
+
#ws_icon_selector .ws_icon_option {
|
727 |
+
float: left;
|
728 |
+
height: 30px;
|
729 |
+
margin: 2px;
|
730 |
+
cursor: pointer;
|
731 |
+
border: 1px solid #bbb;
|
732 |
+
border-radius: 3px;
|
733 |
+
/* Gradients and colours cribbed from WP 3.5.1 button styles */
|
734 |
+
background: #f3f3f3;
|
735 |
+
background-image: -webkit-gradient(linear, left top, left bottom, from(#fefefe), to(#f4f4f4));
|
736 |
+
background-image: -webkit-linear-gradient(top, #fefefe, #f4f4f4);
|
737 |
+
background-image: -moz-linear-gradient(top, #fefefe, #f4f4f4);
|
738 |
+
background-image: -o-linear-gradient(top, #fefefe, #f4f4f4);
|
739 |
+
background-image: linear-gradient(to bottom, #fefefe, #f4f4f4);
|
740 |
+
}
|
741 |
+
|
742 |
+
#ws_icon_selector .ws_icon_option:hover {
|
743 |
+
/* Gradients and colours cribbed from WP 3.5.1 button styles */
|
744 |
+
border-color: #999;
|
745 |
+
background: #f3f3f3;
|
746 |
+
background-image: -webkit-gradient(linear, left top, left bottom, from(#fff), to(#f3f3f3));
|
747 |
+
background-image: -webkit-linear-gradient(top, #fff, #f3f3f3);
|
748 |
+
background-image: -moz-linear-gradient(top, #fff, #f3f3f3);
|
749 |
+
background-image: -ms-linear-gradient(top, #fff, #f3f3f3);
|
750 |
+
background-image: -o-linear-gradient(top, #fff, #f3f3f3);
|
751 |
+
background-image: linear-gradient(to bottom, #fff, #f3f3f3);
|
752 |
+
}
|
753 |
+
|
754 |
+
#ws_icon_selector .ws_icon_option.ws_selected_icon {
|
755 |
+
border-color: green;
|
756 |
+
background-color: #deffca;
|
757 |
+
background-image: none;
|
758 |
+
}
|
759 |
+
|
760 |
+
#ws_icon_selector .icon16 {
|
761 |
+
float: none;
|
762 |
+
margin: 0;
|
763 |
+
}
|
764 |
+
|
765 |
+
#ws_icon_selector .ws_icon_option .ws_icon_image.dashicons {
|
766 |
+
width: 20px;
|
767 |
+
height: 20px;
|
768 |
+
padding: 5px;
|
769 |
+
}
|
770 |
+
|
771 |
+
#ws_icon_selector .ws_icon_option img {
|
772 |
+
display: inline-block;
|
773 |
+
margin: 0;
|
774 |
+
padding: 7px;
|
775 |
+
width: 16px;
|
776 |
+
height: 16px;
|
777 |
+
}
|
778 |
+
|
779 |
+
#ws_menu_editor .ws_edit_field-icon_url input.ws_field_value {
|
780 |
+
width: 220px;
|
781 |
+
margin-right: 5px;
|
782 |
+
}
|
783 |
+
|
784 |
+
/* The icon button that displays the pop-up icon selector. */
|
785 |
+
#ws_menu_editor .ws_select_icon {
|
786 |
+
margin: 0;
|
787 |
+
padding: 0;
|
788 |
+
position: relative;
|
789 |
+
box-sizing: border-box;
|
790 |
+
height: 25px;
|
791 |
+
min-height: 25px;
|
792 |
+
}
|
793 |
+
.ame-is-wp53-plus #ws_menu_editor .ws_select_icon {
|
794 |
+
height: 28px;
|
795 |
+
min-height: 28px;
|
796 |
+
margin-top: 1px;
|
797 |
+
}
|
798 |
+
|
799 |
+
/* Current icon node (CSS class version, for the built-in WP icon sprites) */
|
800 |
+
.ws_select_icon .icon16 {
|
801 |
+
margin: 0;
|
802 |
+
float: none;
|
803 |
+
padding: 3px;
|
804 |
+
/*
|
805 |
+
The default .icon16 style has a 6px padding which would normally make it too large
|
806 |
+
to fit in the button. We can't change the padding without making the background-position
|
807 |
+
look wrong, so lets offset the icon so that it fits.
|
808 |
+
*/
|
809 |
+
position: relative;
|
810 |
+
top: -3px;
|
811 |
+
left: -3px;
|
812 |
+
}
|
813 |
+
.ame-is-wp53-plus .ws_select_icon .icon16 {
|
814 |
+
top: -1px;
|
815 |
+
}
|
816 |
+
|
817 |
+
/* Current icon node (image version) */
|
818 |
+
.ws_select_icon img {
|
819 |
+
margin: 0;
|
820 |
+
padding: 4px;
|
821 |
+
width: 16px;
|
822 |
+
height: 16px;
|
823 |
+
}
|
824 |
+
|
825 |
+
#ws_icon_selector .ws_tool_tab_nav {
|
826 |
+
display: inline-block;
|
827 |
+
margin-top: 2px;
|
828 |
+
position: relative;
|
829 |
+
}
|
830 |
+
#ws_icon_selector .ws_tool_tab_nav li {
|
831 |
+
padding: 4px 10px 11px;
|
832 |
+
}
|
833 |
+
#ws_icon_selector .ws_tool_tab {
|
834 |
+
padding: 4px 4px 2px;
|
835 |
+
max-height: 324px;
|
836 |
+
overflow-y: auto;
|
837 |
+
}
|
838 |
+
|
839 |
+
/* MP6 admin style compatibility */
|
840 |
+
#ws_icon_selector .ws_icon_option .icon16::before {
|
841 |
+
margin: 0;
|
842 |
+
padding: 0;
|
843 |
+
}
|
844 |
+
|
845 |
+
.ws_select_icon .icon16::before {
|
846 |
+
padding: 0;
|
847 |
+
margin: 1px 0 0 2px;
|
848 |
+
}
|
849 |
+
|
850 |
+
#ws_choose_icon_from_media {
|
851 |
+
margin: 2px;
|
852 |
+
}
|
853 |
+
|
854 |
+
/************************************
|
855 |
+
Embedded page selector
|
856 |
+
*************************************/
|
857 |
+
#ws_embedded_page_selector {
|
858 |
+
width: 254px;
|
859 |
+
padding: 6px 0 0 0;
|
860 |
+
border: 1px solid silver;
|
861 |
+
border-radius: 3px;
|
862 |
+
background-color: white;
|
863 |
+
box-sizing: border-box;
|
864 |
+
position: absolute;
|
865 |
+
}
|
866 |
+
|
867 |
+
.ws_page_selector_tab_nav {
|
868 |
+
list-style: outside none none;
|
869 |
+
padding: 0;
|
870 |
+
margin: 0 0 0 6px;
|
871 |
+
}
|
872 |
+
|
873 |
+
.ws_page_selector_tab_nav li {
|
874 |
+
display: inline-block;
|
875 |
+
border: 1px solid transparent;
|
876 |
+
border-bottom-width: 0;
|
877 |
+
padding: 3px 5px 5px;
|
878 |
+
line-height: 1.35em;
|
879 |
+
margin-bottom: 0;
|
880 |
+
}
|
881 |
+
|
882 |
+
.ws_page_selector_tab_nav a {
|
883 |
+
text-decoration: none;
|
884 |
+
}
|
885 |
+
|
886 |
+
.ws_page_selector_tab_nav li.ui-tabs-active {
|
887 |
+
border-color: #dfdfdf;
|
888 |
+
background-color: #FDFDFD;
|
889 |
+
border-bottom-color: #FDFDFD;
|
890 |
+
}
|
891 |
+
|
892 |
+
.ws_page_selector_tab_nav li.ui-tabs-active a {
|
893 |
+
color: #32373C;
|
894 |
+
}
|
895 |
+
|
896 |
+
.ws_page_selector_tab {
|
897 |
+
border-top: 1px solid #DFDFDF;
|
898 |
+
padding: 12px;
|
899 |
+
/* The same padding as post editor boxes. */
|
900 |
+
margin-top: -1px;
|
901 |
+
background-color: #FDFDFD;
|
902 |
+
border-bottom-left-radius: 3px;
|
903 |
+
border-bottom-right-radius: 3px;
|
904 |
+
}
|
905 |
+
|
906 |
+
#ws_current_site_pages {
|
907 |
+
width: 100%;
|
908 |
+
min-height: 150px;
|
909 |
+
max-height: 300px;
|
910 |
+
margin-left: 0;
|
911 |
+
margin-right: 0;
|
912 |
+
}
|
913 |
+
|
914 |
+
#ws_embedded_page_selector input {
|
915 |
+
box-sizing: border-box;
|
916 |
+
max-width: 100%;
|
917 |
+
}
|
918 |
+
|
919 |
+
#ws_custom_embedded_page_tab p:first-child {
|
920 |
+
margin-top: 0;
|
921 |
+
}
|
922 |
+
|
923 |
+
/*
|
924 |
+
Make the "Page" field look editable. It is read-only because the user can't change it directly (they have to use
|
925 |
+
the dropdown), but we don't want it to be greyed-out.
|
926 |
+
*/
|
927 |
+
#ws_menu_editor .ws_edit_field-embedded_page_id input.ws_field_value {
|
928 |
+
background-color: white;
|
929 |
+
}
|
930 |
+
|
931 |
+
/************************************
|
932 |
+
Menu color picker
|
933 |
+
*************************************/
|
934 |
+
#ws-ame-menu-color-settings {
|
935 |
+
background: white;
|
936 |
+
display: none;
|
937 |
+
}
|
938 |
+
|
939 |
+
#ame-menu-color-list {
|
940 |
+
height: 500px;
|
941 |
+
overflow-y: auto;
|
942 |
+
}
|
943 |
+
|
944 |
+
.ame-menu-color-column {
|
945 |
+
min-width: 460px;
|
946 |
+
}
|
947 |
+
|
948 |
+
.ame-menu-color-name {
|
949 |
+
display: inline-block;
|
950 |
+
vertical-align: top;
|
951 |
+
padding-top: 2px;
|
952 |
+
line-height: 1.3;
|
953 |
+
font-size: 14px;
|
954 |
+
font-weight: 600;
|
955 |
+
min-width: 180px;
|
956 |
+
}
|
957 |
+
|
958 |
+
.ame-color-option {
|
959 |
+
padding: 10px 0;
|
960 |
+
}
|
961 |
+
.ame-color-option .wp-picker-container {
|
962 |
+
display: inline-block;
|
963 |
+
}
|
964 |
+
|
965 |
+
.ame-advanced-menu-color {
|
966 |
+
display: none;
|
967 |
+
}
|
968 |
+
|
969 |
+
#ws-ame-apply-colors-to-all {
|
970 |
+
display: block;
|
971 |
+
float: left;
|
972 |
+
margin-left: 5px;
|
973 |
+
}
|
974 |
+
|
975 |
+
/* Color presets */
|
976 |
+
#ame-color-preset-container {
|
977 |
+
padding: 0 8px 8px 8px;
|
978 |
+
margin-left: -8px;
|
979 |
+
margin-right: -8px;
|
980 |
+
margin-bottom: 4px;
|
981 |
+
border-bottom: 1px solid #eee;
|
982 |
+
}
|
983 |
+
|
984 |
+
#ame-menu-color-presets {
|
985 |
+
width: 290px;
|
986 |
+
margin-right: 5px;
|
987 |
+
}
|
988 |
+
|
989 |
+
#ws-ame-save-color-preset {
|
990 |
+
/*margin-right: 5px;*/
|
991 |
+
}
|
992 |
+
|
993 |
+
a#ws-ame-delete-color-preset {
|
994 |
+
color: #A00;
|
995 |
+
text-decoration: none;
|
996 |
+
}
|
997 |
+
|
998 |
+
a#ws-ame-delete-color-preset:hover {
|
999 |
+
color: #F00;
|
1000 |
+
}
|
1001 |
+
|
1002 |
+
/* Color scheme display in the editor widget. */
|
1003 |
+
.ws_color_scheme_display {
|
1004 |
+
display: inline-block;
|
1005 |
+
box-sizing: border-box;
|
1006 |
+
height: 26px;
|
1007 |
+
width: 190px;
|
1008 |
+
margin-right: 5px;
|
1009 |
+
margin-left: 1px;
|
1010 |
+
padding: 2px 4px;
|
1011 |
+
font-size: 12px;
|
1012 |
+
border: 1px solid #ddd;
|
1013 |
+
background: white;
|
1014 |
+
cursor: pointer;
|
1015 |
+
line-height: 20px;
|
1016 |
+
}
|
1017 |
+
.ame-is-wp53-plus .ws_color_scheme_display {
|
1018 |
+
border-color: #7e8993;
|
1019 |
+
border-radius: 4px;
|
1020 |
+
margin-top: 1px;
|
1021 |
+
margin-bottom: 1px;
|
1022 |
+
padding: 3px 8px;
|
1023 |
+
height: 28px;
|
1024 |
+
line-height: 20px;
|
1025 |
+
}
|
1026 |
+
|
1027 |
+
.ws_open_color_editor {
|
1028 |
+
width: 58px;
|
1029 |
+
}
|
1030 |
+
|
1031 |
+
.ws_color_display_item {
|
1032 |
+
display: inline-block;
|
1033 |
+
width: 18px;
|
1034 |
+
height: 18px;
|
1035 |
+
margin-right: 4px;
|
1036 |
+
border: 1px solid #ccc;
|
1037 |
+
border-radius: 3px;
|
1038 |
+
}
|
1039 |
+
|
1040 |
+
.ws_color_display_item:last-child {
|
1041 |
+
margin-right: 0;
|
1042 |
+
}
|
1043 |
+
|
1044 |
+
/************************************
|
1045 |
+
Export and import
|
1046 |
+
*************************************/
|
1047 |
+
#export_dialog, #import_dialog {
|
1048 |
+
display: none;
|
1049 |
+
}
|
1050 |
+
|
1051 |
+
.ui-widget-overlay {
|
1052 |
+
background-color: black;
|
1053 |
+
position: fixed;
|
1054 |
+
left: 0;
|
1055 |
+
top: 0;
|
1056 |
+
right: 0;
|
1057 |
+
bottom: 0;
|
1058 |
+
opacity: 0.7;
|
1059 |
+
-moz-opacity: 0.7;
|
1060 |
+
filter: alpha(opacity=70);
|
1061 |
+
width: 100%;
|
1062 |
+
height: 100%;
|
1063 |
+
}
|
1064 |
+
|
1065 |
+
.ui-front {
|
1066 |
+
z-index: 10000;
|
1067 |
+
}
|
1068 |
+
|
1069 |
+
.settings_page_menu_editor .ui-dialog {
|
1070 |
+
background: white;
|
1071 |
+
border: 1px solid #c0c0c0;
|
1072 |
+
padding: 0;
|
1073 |
+
-moz-border-radius: 5px;
|
1074 |
+
-webkit-border-radius: 5px;
|
1075 |
+
border-radius: 5px;
|
1076 |
+
}
|
1077 |
+
.settings_page_menu_editor .ui-dialog .ui-dialog-content {
|
1078 |
+
padding: 8px 8px 8px 8px;
|
1079 |
+
font-size: 1.1em;
|
1080 |
+
}
|
1081 |
+
.settings_page_menu_editor .ui-dialog .ame-scrollable-dialog-content {
|
1082 |
+
max-height: 500px;
|
1083 |
+
overflow-y: auto;
|
1084 |
+
padding-top: 0.5em;
|
1085 |
+
}
|
1086 |
+
.settings_page_menu_editor .ui-dialog-titlebar {
|
1087 |
+
display: block;
|
1088 |
+
height: 22px;
|
1089 |
+
margin: 0;
|
1090 |
+
padding: 4px 4px 4px 8px;
|
1091 |
+
background-color: #86A7E3;
|
1092 |
+
font-size: 1em;
|
1093 |
+
line-height: 22px;
|
1094 |
+
-webkit-border-top-left-radius: 4px;
|
1095 |
+
-webkit-border-top-right-radius: 4px;
|
1096 |
+
-moz-border-radius-topleft: 4px;
|
1097 |
+
-moz-border-radius-topright: 4px;
|
1098 |
+
border-top-left-radius: 4px;
|
1099 |
+
border-top-right-radius: 4px;
|
1100 |
+
border-bottom: 1px solid #809fd9;
|
1101 |
+
}
|
1102 |
+
.settings_page_menu_editor .ui-dialog-title {
|
1103 |
+
color: white;
|
1104 |
+
font-weight: bold;
|
1105 |
+
}
|
1106 |
+
.settings_page_menu_editor .ui-button.ui-dialog-titlebar-close {
|
1107 |
+
background: #86A7E3 url(../images/x.png) no-repeat center;
|
1108 |
+
width: 22px;
|
1109 |
+
height: 22px;
|
1110 |
+
display: block;
|
1111 |
+
float: right;
|
1112 |
+
color: white;
|
1113 |
+
border-radius: 3px;
|
1114 |
+
-moz-border-radius: 3px;
|
1115 |
+
-webkit-border-radius: 3px;
|
1116 |
+
}
|
1117 |
+
.settings_page_menu_editor .ui-dialog-titlebar-close:hover {
|
1118 |
+
/*background-image: url(../images/x-light.png);*/
|
1119 |
+
background-color: #a6c2f5;
|
1120 |
+
}
|
1121 |
+
#export_dialog .ws_dialog_panel {
|
1122 |
+
height: 50px;
|
1123 |
+
}
|
1124 |
+
|
1125 |
+
#import_dialog .ws_dialog_panel {
|
1126 |
+
height: 64px;
|
1127 |
+
}
|
1128 |
+
|
1129 |
+
.ws_dialog_panel .ame-fixed-label-text {
|
1130 |
+
display: inline-block;
|
1131 |
+
min-width: 6em;
|
1132 |
+
}
|
1133 |
+
.ws_dialog_panel .ame-inline-select-with-input {
|
1134 |
+
vertical-align: baseline;
|
1135 |
+
}
|
1136 |
+
.ws_dialog_panel .ame-box-side-sizes {
|
1137 |
+
display: flex;
|
1138 |
+
flex-wrap: wrap;
|
1139 |
+
max-width: 800px;
|
1140 |
+
}
|
1141 |
+
.ws_dialog_panel .ame-box-side-sizes .ame-fixed-label-text {
|
1142 |
+
min-width: 4em;
|
1143 |
+
}
|
1144 |
+
.ws_dialog_panel .ame-box-side-sizes label {
|
1145 |
+
margin-right: 2.5em;
|
1146 |
+
}
|
1147 |
+
.ws_dialog_panel .ame-box-side-sizes input {
|
1148 |
+
margin-bottom: 0.4em;
|
1149 |
+
}
|
1150 |
+
.ws_dialog_panel .ame-box-side-sizes input[type=number] {
|
1151 |
+
width: 6em;
|
1152 |
+
}
|
1153 |
+
|
1154 |
+
.ame-flexbox-break {
|
1155 |
+
flex-basis: 100%;
|
1156 |
+
height: 0;
|
1157 |
+
}
|
1158 |
+
|
1159 |
+
.ws_dialog_buttons {
|
1160 |
+
/*height: 30px;*/
|
1161 |
+
text-align: right;
|
1162 |
+
margin-top: 20px;
|
1163 |
+
margin-bottom: 1px;
|
1164 |
+
clear: both;
|
1165 |
+
}
|
1166 |
+
|
1167 |
+
.ws_dialog_buttons .button-primary {
|
1168 |
+
display: block;
|
1169 |
+
float: left;
|
1170 |
+
margin-top: 0;
|
1171 |
+
}
|
1172 |
+
|
1173 |
+
.ws_dialog_buttons .button {
|
1174 |
+
margin-top: 0;
|
1175 |
+
}
|
1176 |
+
|
1177 |
+
.ws_dialog_buttons.ame-vertical-button-list {
|
1178 |
+
text-align: left;
|
1179 |
+
}
|
1180 |
+
|
1181 |
+
.ws_dialog_buttons.ame-vertical-button-list .button-primary {
|
1182 |
+
float: none;
|
1183 |
+
}
|
1184 |
+
|
1185 |
+
.ws_dialog_buttons.ame-vertical-button-list .button {
|
1186 |
+
width: 100%;
|
1187 |
+
text-align: left;
|
1188 |
+
margin-bottom: 10px;
|
1189 |
+
}
|
1190 |
+
|
1191 |
+
.ws_dialog_buttons.ame-vertical-button-list .button:last-child {
|
1192 |
+
margin-bottom: 0;
|
1193 |
+
}
|
1194 |
+
|
1195 |
+
#import_file_selector {
|
1196 |
+
display: block;
|
1197 |
+
width: 286px;
|
1198 |
+
margin: 6px auto 12px;
|
1199 |
+
}
|
1200 |
+
|
1201 |
+
#ws_start_import {
|
1202 |
+
min-width: 100px;
|
1203 |
+
}
|
1204 |
+
|
1205 |
+
#import_complete_notice {
|
1206 |
+
text-align: center;
|
1207 |
+
font-size: large;
|
1208 |
+
padding-top: 25px;
|
1209 |
+
}
|
1210 |
+
|
1211 |
+
#ws_import_error_response {
|
1212 |
+
width: 100%;
|
1213 |
+
}
|
1214 |
+
|
1215 |
+
.ws_dont_show_again {
|
1216 |
+
display: inline-block;
|
1217 |
+
margin-top: 1em;
|
1218 |
+
}
|
1219 |
+
|
1220 |
+
/************************************
|
1221 |
+
Menu access editor
|
1222 |
+
*************************************/
|
1223 |
+
/* The launch button */
|
1224 |
+
#ws_menu_editor .ws_edit_field-access_level input.ws_field_value {
|
1225 |
+
width: 190px;
|
1226 |
+
margin-right: 5px;
|
1227 |
+
}
|
1228 |
+
|
1229 |
+
.ws_launch_access_editor {
|
1230 |
+
min-width: 40px;
|
1231 |
+
width: 58px;
|
1232 |
+
}
|
1233 |
+
|
1234 |
+
#ws_menu_access_editor {
|
1235 |
+
width: 400px;
|
1236 |
+
display: none;
|
1237 |
+
}
|
1238 |
+
|
1239 |
+
.ws_dialog_subpanel {
|
1240 |
+
margin-bottom: 1em;
|
1241 |
+
}
|
1242 |
+
.ws_dialog_subpanel fieldset p {
|
1243 |
+
margin-top: 0;
|
1244 |
+
margin-bottom: 4px;
|
1245 |
+
}
|
1246 |
+
|
1247 |
+
.ws-ame-dialog-subheading {
|
1248 |
+
display: block;
|
1249 |
+
font-weight: 600;
|
1250 |
+
font-size: 1em;
|
1251 |
+
margin: 0 0 0.2em 0;
|
1252 |
+
}
|
1253 |
+
|
1254 |
+
#ws_menu_access_editor .ws_column_access,
|
1255 |
+
#ws_menu_access_editor .ws_ext_action_check_column {
|
1256 |
+
text-align: center;
|
1257 |
+
width: 1em;
|
1258 |
+
padding-right: 0;
|
1259 |
+
}
|
1260 |
+
|
1261 |
+
#ws_menu_access_editor .ws_column_access input,
|
1262 |
+
#ws_menu_access_editor .ws_ext_action_check_column input {
|
1263 |
+
margin-right: 0;
|
1264 |
+
}
|
1265 |
+
|
1266 |
+
#ws_menu_access_editor .ws_column_role {
|
1267 |
+
white-space: nowrap;
|
1268 |
+
}
|
1269 |
+
|
1270 |
+
#ws_role_table_body_container {
|
1271 |
+
/*max-height: 400px;
|
1272 |
+
overflow: auto;*/
|
1273 |
+
overflow: hidden;
|
1274 |
+
margin-right: -1px;
|
1275 |
+
}
|
1276 |
+
|
1277 |
+
.ws_role_table_body {
|
1278 |
+
margin-top: 2px;
|
1279 |
+
max-width: 354px;
|
1280 |
+
}
|
1281 |
+
|
1282 |
+
.ws_has_separate_header .ws_role_table_header {
|
1283 |
+
border-bottom: none;
|
1284 |
+
-moz-border-radius-bottomleft: 0;
|
1285 |
+
-moz-border-radius-bottomright: 0;
|
1286 |
+
-webkit-border-bottom-left-radius: 0;
|
1287 |
+
-webkit-border-bottom-right-radius: 0;
|
1288 |
+
border-bottom-left-radius: 0;
|
1289 |
+
border-bottom-right-radius: 0;
|
1290 |
+
}
|
1291 |
+
|
1292 |
+
.ws_has_separate_header .ws_role_table_body {
|
1293 |
+
border-top: none;
|
1294 |
+
margin-top: 0;
|
1295 |
+
-moz-border-radius-topleft: 0;
|
1296 |
+
-moz-border-radius-topright: 0;
|
1297 |
+
-webkit-border-top-left-radius: 0;
|
1298 |
+
-webkit-border-top-right-radius: 0;
|
1299 |
+
border-top-left-radius: 0;
|
1300 |
+
border-top-right-radius: 0;
|
1301 |
+
}
|
1302 |
+
|
1303 |
+
.ws_role_id {
|
1304 |
+
display: none;
|
1305 |
+
}
|
1306 |
+
|
1307 |
+
#ws_extra_capability {
|
1308 |
+
width: 100%;
|
1309 |
+
}
|
1310 |
+
|
1311 |
+
#ws_role_access_container {
|
1312 |
+
position: relative;
|
1313 |
+
max-height: 430px;
|
1314 |
+
overflow: auto;
|
1315 |
+
}
|
1316 |
+
|
1317 |
+
#ws_role_access_overlay {
|
1318 |
+
width: 100%;
|
1319 |
+
height: 100%;
|
1320 |
+
position: absolute;
|
1321 |
+
line-height: 100%;
|
1322 |
+
background: white;
|
1323 |
+
filter: alpha(opacity=60);
|
1324 |
+
opacity: 0.6;
|
1325 |
+
-moz-opacity: 0.6;
|
1326 |
+
}
|
1327 |
+
|
1328 |
+
#ws_role_access_overlay_content {
|
1329 |
+
position: absolute;
|
1330 |
+
width: 50%;
|
1331 |
+
left: 22%;
|
1332 |
+
top: 30%;
|
1333 |
+
background: white;
|
1334 |
+
padding: 8px;
|
1335 |
+
border: 2px solid silver;
|
1336 |
+
border-radius: 5px;
|
1337 |
+
color: #555;
|
1338 |
+
}
|
1339 |
+
|
1340 |
+
#ws_menu_access_editor div.error {
|
1341 |
+
margin-left: 0;
|
1342 |
+
margin-right: 0;
|
1343 |
+
margin-bottom: 5px;
|
1344 |
+
}
|
1345 |
+
|
1346 |
+
#ws_hardcoded_role_error {
|
1347 |
+
display: none;
|
1348 |
+
}
|
1349 |
+
|
1350 |
+
/*--------------------------------------------*
|
1351 |
+
The CPT/taxonomy permissions panel
|
1352 |
+
*--------------------------------------------*/
|
1353 |
+
/*
|
1354 |
+
* When there are CPT/taxonomy permissions available, the appearance of the role list changes a bit.
|
1355 |
+
*/
|
1356 |
+
.ws_has_extended_permissions {
|
1357 |
+
/* The role or actor whose CPT/taxonomy permissions are currently expanded. */
|
1358 |
+
}
|
1359 |
+
.ws_has_extended_permissions .ws_role_table_body .ws_column_role {
|
1360 |
+
cursor: pointer;
|
1361 |
+
}
|
1362 |
+
.ws_has_extended_permissions .ws_role_table_body .ws_column_selected_role_tip {
|
1363 |
+
display: table-cell;
|
1364 |
+
}
|
1365 |
+
.ws_has_extended_permissions .ws_role_table_body tr:hover {
|
1366 |
+
background: #EAF2FA;
|
1367 |
+
}
|
1368 |
+
.ws_has_extended_permissions .ws_role_table_body td {
|
1369 |
+
border-top: 1px solid #f1f1f1;
|
1370 |
+
}
|
1371 |
+
.ws_has_extended_permissions .ws_role_table_body tr:first-child td {
|
1372 |
+
border-top-width: 0;
|
1373 |
+
}
|
1374 |
+
.ws_has_extended_permissions .ws_role_table_body tr.ws_cpt_selected_role {
|
1375 |
+
background-color: #dddddd;
|
1376 |
+
}
|
1377 |
+
.ws_has_extended_permissions .ws_role_table_body tr.ws_cpt_selected_role .ws_column_role {
|
1378 |
+
font-weight: bold;
|
1379 |
+
}
|
1380 |
+
.ws_has_extended_permissions .ws_role_table_body tr.ws_cpt_selected_role .ws_cpt_selected_role_tip {
|
1381 |
+
visibility: visible;
|
1382 |
+
}
|
1383 |
+
.ws_has_extended_permissions .ws_role_table_body tr.ws_cpt_selected_role td {
|
1384 |
+
color: #222;
|
1385 |
+
}
|
1386 |
+
|
1387 |
+
#ws_ext_permissions_container {
|
1388 |
+
float: left;
|
1389 |
+
width: 352px;
|
1390 |
+
padding: 0 9px 0 0;
|
1391 |
+
}
|
1392 |
+
|
1393 |
+
#ws_ext_permissions_container_caption {
|
1394 |
+
padding-left: 15px;
|
1395 |
+
max-width: 352px;
|
1396 |
+
position: relative;
|
1397 |
+
white-space: nowrap;
|
1398 |
+
}
|
1399 |
+
|
1400 |
+
#ws_ext_permissions_container .ws_ext_permissions_table {
|
1401 |
+
margin-top: 2px;
|
1402 |
+
}
|
1403 |
+
#ws_ext_permissions_container .ws_ext_permissions_table tr td:first-child {
|
1404 |
+
padding-left: 15px;
|
1405 |
+
}
|
1406 |
+
#ws_ext_permissions_container .ws_ext_permissions_table .ws_ext_group_title {
|
1407 |
+
padding-bottom: 0;
|
1408 |
+
font-weight: bold;
|
1409 |
+
}
|
1410 |
+
#ws_ext_permissions_container .ws_ext_permissions_table .ws_ext_action_check_column,
|
1411 |
+
#ws_ext_permissions_container .ws_ext_permissions_table .ws_ext_action_name_column {
|
1412 |
+
padding-top: 3px;
|
1413 |
+
padding-bottom: 3px;
|
1414 |
+
}
|
1415 |
+
#ws_ext_permissions_container .ws_ext_permissions_table tr.ws_ext_padding_row td {
|
1416 |
+
padding: 0 0 0 0;
|
1417 |
+
height: 1px;
|
1418 |
+
}
|
1419 |
+
#ws_ext_permissions_container .ws_ext_permissions_table .ws_same_as_required_cap {
|
1420 |
+
text-decoration: underline;
|
1421 |
+
}
|
1422 |
+
#ws_ext_permissions_container .ws_ext_permissions_table .ws_ext_has_custom_setting label.ws_ext_action_name::after {
|
1423 |
+
content: " *";
|
1424 |
+
}
|
1425 |
+
|
1426 |
+
#ws_ext_permissions_container #ws_ext_toggle_capability_names {
|
1427 |
+
cursor: pointer;
|
1428 |
+
position: absolute;
|
1429 |
+
right: 0;
|
1430 |
+
color: #0073aa;
|
1431 |
+
}
|
1432 |
+
#ws_ext_permissions_container.ws_ext_readable_names_enabled #ws_ext_toggle_capability_names {
|
1433 |
+
color: #b4b9be;
|
1434 |
+
}
|
1435 |
+
#ws_ext_permissions_container .ws_ext_readable_name {
|
1436 |
+
display: none;
|
1437 |
+
}
|
1438 |
+
#ws_ext_permissions_container .ws_ext_capability {
|
1439 |
+
display: inline;
|
1440 |
+
}
|
1441 |
+
#ws_ext_permissions_container.ws_ext_readable_names_enabled .ws_ext_readable_name {
|
1442 |
+
display: inline;
|
1443 |
+
}
|
1444 |
+
#ws_ext_permissions_container.ws_ext_readable_names_enabled .ws_ext_capability {
|
1445 |
+
display: none;
|
1446 |
+
}
|
1447 |
+
|
1448 |
+
#ws_ext_permissions_container #ws_taxonomy_permissions_table tr:first-child td {
|
1449 |
+
padding-top: 8px;
|
1450 |
+
}
|
1451 |
+
|
1452 |
+
/* The "selected role" indicator. */
|
1453 |
+
.ws_cpt_selected_role_tip {
|
1454 |
+
display: block;
|
1455 |
+
visibility: hidden;
|
1456 |
+
box-sizing: border-box;
|
1457 |
+
width: 26px;
|
1458 |
+
height: 26px;
|
1459 |
+
position: absolute;
|
1460 |
+
right: 0;
|
1461 |
+
background: white;
|
1462 |
+
transform: translate(1px, 0) rotate(-45deg);
|
1463 |
+
transform-origin: top right;
|
1464 |
+
}
|
1465 |
+
|
1466 |
+
.ws_role_table_body .ws_column_selected_role_tip {
|
1467 |
+
display: none;
|
1468 |
+
padding: 0;
|
1469 |
+
width: 40px;
|
1470 |
+
height: 100%;
|
1471 |
+
text-align: right;
|
1472 |
+
overflow: visible;
|
1473 |
+
position: relative;
|
1474 |
+
cursor: pointer;
|
1475 |
+
}
|
1476 |
+
|
1477 |
+
.ws_ame_breadcrumb_separator {
|
1478 |
+
color: #999;
|
1479 |
+
}
|
1480 |
+
|
1481 |
+
#ws_menu_editor .ws_ext_permissions_indicator {
|
1482 |
+
font-size: 16px;
|
1483 |
+
height: 16px;
|
1484 |
+
width: 16px;
|
1485 |
+
visibility: hidden;
|
1486 |
+
vertical-align: bottom;
|
1487 |
+
cursor: pointer;
|
1488 |
+
color: #4aa100;
|
1489 |
+
}
|
1490 |
+
|
1491 |
+
#ws_menu_editor.ws_is_actor_view .ws_ext_permissions_indicator {
|
1492 |
+
visibility: visible;
|
1493 |
+
}
|
1494 |
+
|
1495 |
+
/************************************
|
1496 |
+
Visible users dialog
|
1497 |
+
*************************************/
|
1498 |
+
#ws_visible_users_dialog {
|
1499 |
+
background: white;
|
1500 |
+
padding: 8px;
|
1501 |
+
}
|
1502 |
+
|
1503 |
+
#ws_user_selection_panels {
|
1504 |
+
min-width: 710px;
|
1505 |
+
}
|
1506 |
+
#ws_user_selection_panels .ws_user_selection_panel {
|
1507 |
+
display: block;
|
1508 |
+
float: left;
|
1509 |
+
position: relative;
|
1510 |
+
-webkit-box-sizing: border-box;
|
1511 |
+
-moz-box-sizing: border-box;
|
1512 |
+
box-sizing: border-box;
|
1513 |
+
width: 350px;
|
1514 |
+
height: 400px;
|
1515 |
+
border: 1px solid #e5e5e5;
|
1516 |
+
margin-right: 10px;
|
1517 |
+
padding: 10px;
|
1518 |
+
}
|
1519 |
+
#ws_user_selection_panels #ws_user_selection_target_panel {
|
1520 |
+
margin-right: 0;
|
1521 |
+
}
|
1522 |
+
#ws_user_selection_panels #ws_available_user_query {
|
1523 |
+
-webkit-box-sizing: border-box;
|
1524 |
+
-moz-box-sizing: border-box;
|
1525 |
+
box-sizing: border-box;
|
1526 |
+
width: 100%;
|
1527 |
+
max-height: 28px;
|
1528 |
+
}
|
1529 |
+
#ws_user_selection_panels .ws_user_list_wrapper {
|
1530 |
+
position: absolute;
|
1531 |
+
top: 50px;
|
1532 |
+
left: 10px;
|
1533 |
+
right: 10px;
|
1534 |
+
height: 338px;
|
1535 |
+
overflow-x: auto;
|
1536 |
+
overflow-y: auto;
|
1537 |
+
}
|
1538 |
+
#ws_user_selection_panels .ws_user_selection_list {
|
1539 |
+
min-height: 20px;
|
1540 |
+
border-width: 0;
|
1541 |
+
-webkit-box-shadow: none;
|
1542 |
+
-moz-box-shadow: none;
|
1543 |
+
box-shadow: none;
|
1544 |
+
}
|
1545 |
+
#ws_user_selection_panels .ws_user_selection_list .ws_user_action_column {
|
1546 |
+
width: 20px;
|
1547 |
+
text-align: center;
|
1548 |
+
padding-top: 9px;
|
1549 |
+
padding-bottom: 0;
|
1550 |
+
}
|
1551 |
+
#ws_user_selection_panels .ws_user_selection_list .ws_user_action_button {
|
1552 |
+
cursor: pointer;
|
1553 |
+
color: #b4b9be;
|
1554 |
+
}
|
1555 |
+
#ws_user_selection_panels .ws_user_selection_list .ws_user_username_column {
|
1556 |
+
padding-left: 0;
|
1557 |
+
}
|
1558 |
+
#ws_user_selection_panels .ws_user_selection_list .ws_user_display_name_column {
|
1559 |
+
white-space: nowrap;
|
1560 |
+
}
|
1561 |
+
#ws_user_selection_panels #ws_available_users tr {
|
1562 |
+
cursor: pointer;
|
1563 |
+
}
|
1564 |
+
#ws_user_selection_panels #ws_available_users tr:hover, #ws_user_selection_panels #ws_available_users tr.ws_user_best_match {
|
1565 |
+
background-color: #eaf2fa;
|
1566 |
+
}
|
1567 |
+
#ws_user_selection_panels #ws_available_users tr:hover .ws_user_action_button {
|
1568 |
+
color: #7ad03a;
|
1569 |
+
}
|
1570 |
+
#ws_user_selection_panels #ws_selected_users .ws_user_action_button::before {
|
1571 |
+
content: "";
|
1572 |
+
}
|
1573 |
+
#ws_user_selection_panels #ws_selected_users .ws_user_action_button:hover {
|
1574 |
+
color: #dd3d36;
|
1575 |
+
}
|
1576 |
+
#ws_user_selection_panels #ws_selected_users .ws_user_action_column {
|
1577 |
+
padding-left: 6px;
|
1578 |
+
}
|
1579 |
+
#ws_user_selection_panels #ws_selected_users .ws_user_display_name_column {
|
1580 |
+
display: none;
|
1581 |
+
}
|
1582 |
+
#ws_user_selection_panels #ws_selected_users tr.ws_user_must_be_selected .ws_user_action_button {
|
1583 |
+
display: none;
|
1584 |
+
}
|
1585 |
+
#ws_user_selection_panels #ws_selected_users_caption {
|
1586 |
+
font-size: 14px;
|
1587 |
+
line-height: 1.4em;
|
1588 |
+
padding: 7px 10px;
|
1589 |
+
color: #555;
|
1590 |
+
font-weight: 600;
|
1591 |
+
}
|
1592 |
+
#ws_user_selection_panels::after {
|
1593 |
+
display: block;
|
1594 |
+
height: 1px;
|
1595 |
+
visibility: hidden;
|
1596 |
+
content: " ";
|
1597 |
+
clear: both;
|
1598 |
+
}
|
1599 |
+
|
1600 |
+
#ws_loading_users_indicator {
|
1601 |
+
position: absolute;
|
1602 |
+
right: 10px;
|
1603 |
+
bottom: 10px;
|
1604 |
+
margin-right: 0;
|
1605 |
+
margin-bottom: 0;
|
1606 |
+
}
|
1607 |
+
|
1608 |
+
/************************************
|
1609 |
+
Menu deletion error
|
1610 |
+
*************************************/
|
1611 |
+
#ws-ame-menu-deletion-error {
|
1612 |
+
max-width: 400px;
|
1613 |
+
}
|
1614 |
+
|
1615 |
+
/************************************
|
1616 |
+
Tooltips and hints
|
1617 |
+
*************************************/
|
1618 |
+
.ws_tooltip_trigger, .ws_field_tooltip_trigger {
|
1619 |
+
cursor: pointer;
|
1620 |
+
}
|
1621 |
+
|
1622 |
+
.ws_tooltip_content_list {
|
1623 |
+
list-style: disc;
|
1624 |
+
margin-left: 1em;
|
1625 |
+
margin-bottom: 0;
|
1626 |
+
}
|
1627 |
+
|
1628 |
+
.ws_tooltip_node {
|
1629 |
+
font-size: 13px;
|
1630 |
+
line-height: 1.3;
|
1631 |
+
border-radius: 3px;
|
1632 |
+
max-width: 300px;
|
1633 |
+
}
|
1634 |
+
|
1635 |
+
.ws_field_tooltip_trigger .dashicons {
|
1636 |
+
font-size: 16px;
|
1637 |
+
height: 16px;
|
1638 |
+
vertical-align: bottom;
|
1639 |
+
}
|
1640 |
+
|
1641 |
+
.ws_field_tooltip_trigger {
|
1642 |
+
color: #a1a1a1;
|
1643 |
+
}
|
1644 |
+
|
1645 |
+
#ws_plugin_settings_form .ws_tooltip_trigger .dashicons {
|
1646 |
+
font-size: 18px;
|
1647 |
+
}
|
1648 |
+
|
1649 |
+
.ws_ame_custom_postbox .ws_tooltip_trigger .dashicons {
|
1650 |
+
font-size: 18px;
|
1651 |
+
height: 18px;
|
1652 |
+
vertical-align: bottom;
|
1653 |
+
}
|
1654 |
+
|
1655 |
+
.ws_tooltip_trigger.ame-warning-tooltip {
|
1656 |
+
color: orange;
|
1657 |
+
}
|
1658 |
+
|
1659 |
+
.ws_wide_tooltip {
|
1660 |
+
max-width: 450px;
|
1661 |
+
}
|
1662 |
+
|
1663 |
+
.ws_hint {
|
1664 |
+
background: #FFFFE0;
|
1665 |
+
border: 1px solid #E6DB55;
|
1666 |
+
margin-bottom: 0.5em;
|
1667 |
+
border-radius: 3px;
|
1668 |
+
position: relative;
|
1669 |
+
padding-right: 20px;
|
1670 |
+
}
|
1671 |
+
|
1672 |
+
.ws_hint_close {
|
1673 |
+
border: 1px solid #E6DB55;
|
1674 |
+
border-right: none;
|
1675 |
+
border-top: none;
|
1676 |
+
color: #dcc500;
|
1677 |
+
font-weight: bold;
|
1678 |
+
cursor: pointer;
|
1679 |
+
width: 18px;
|
1680 |
+
text-align: center;
|
1681 |
+
border-radius: 3px;
|
1682 |
+
position: absolute;
|
1683 |
+
right: 0;
|
1684 |
+
top: 0;
|
1685 |
+
}
|
1686 |
+
|
1687 |
+
.ws_hint_close:hover {
|
1688 |
+
background-color: #ffef4c;
|
1689 |
+
border-color: #e0b900;
|
1690 |
+
color: black;
|
1691 |
+
}
|
1692 |
+
|
1693 |
+
.ws_hint_content {
|
1694 |
+
padding: 0.4em 0 0.4em 0.4em;
|
1695 |
+
}
|
1696 |
+
|
1697 |
+
.ws_hint_content ul {
|
1698 |
+
list-style: disc;
|
1699 |
+
list-style-position: inside;
|
1700 |
+
margin-left: 0.5em;
|
1701 |
+
}
|
1702 |
+
|
1703 |
+
.ws_ame_doc_box .hndle, .ws_ame_custom_postbox .hndle {
|
1704 |
+
cursor: default !important;
|
1705 |
+
border-bottom: 1px solid #ccd0d4;
|
1706 |
+
}
|
1707 |
+
.ws_ame_doc_box .handlediv, .ws_ame_custom_postbox .handlediv {
|
1708 |
+
display: block;
|
1709 |
+
float: right;
|
1710 |
+
}
|
1711 |
+
.ws_ame_doc_box .inside, .ws_ame_custom_postbox .inside {
|
1712 |
+
margin-bottom: 0;
|
1713 |
+
}
|
1714 |
+
.ws_ame_doc_box ul, .ws_ame_custom_postbox ul {
|
1715 |
+
list-style: disc outside;
|
1716 |
+
margin-left: 1em;
|
1717 |
+
}
|
1718 |
+
.ws_ame_doc_box li > ul, .ws_ame_custom_postbox li > ul {
|
1719 |
+
margin-top: 6px;
|
1720 |
+
}
|
1721 |
+
.ws_ame_doc_box .button-link .toggle-indicator::before, .ws_ame_custom_postbox .button-link .toggle-indicator::before {
|
1722 |
+
margin-top: 4px;
|
1723 |
+
width: 20px;
|
1724 |
+
-webkit-border-radius: 50%;
|
1725 |
+
border-radius: 50%;
|
1726 |
+
text-indent: -1px;
|
1727 |
+
content: "";
|
1728 |
+
display: inline-block;
|
1729 |
+
font: normal 20px/1 dashicons;
|
1730 |
+
-webkit-font-smoothing: antialiased;
|
1731 |
+
-moz-osx-font-smoothing: grayscale;
|
1732 |
+
text-decoration: none !important;
|
1733 |
+
}
|
1734 |
+
.ws_ame_doc_box.closed .button-link .toggle-indicator::before, .ws_ame_custom_postbox.closed .button-link .toggle-indicator::before {
|
1735 |
+
content: "";
|
1736 |
+
}
|
1737 |
+
|
1738 |
+
.ws_basic_container .ws_ame_custom_postbox {
|
1739 |
+
margin-left: 2px;
|
1740 |
+
margin-right: 2px;
|
1741 |
+
}
|
1742 |
+
|
1743 |
+
.ws_ame_custom_postbox .ame-tutorial-list {
|
1744 |
+
margin: 0;
|
1745 |
+
}
|
1746 |
+
.ws_ame_custom_postbox .ame-tutorial-list a {
|
1747 |
+
text-decoration: none;
|
1748 |
+
display: block;
|
1749 |
+
padding: 4px;
|
1750 |
+
}
|
1751 |
+
.ws_ame_custom_postbox .ame-tutorial-list ul {
|
1752 |
+
margin-left: 1em;
|
1753 |
+
}
|
1754 |
+
.ws_ame_custom_postbox .ame-tutorial-list li {
|
1755 |
+
display: block;
|
1756 |
+
margin: 0;
|
1757 |
+
list-style: none;
|
1758 |
+
}
|
1759 |
+
|
1760 |
+
/************************************
|
1761 |
+
Copy Permissions dialog
|
1762 |
+
*************************************/
|
1763 |
+
#ws-ame-copy-permissions-dialog select {
|
1764 |
+
min-width: 280px;
|
1765 |
+
}
|
1766 |
+
|
1767 |
+
/*********************************************
|
1768 |
+
Capability suggestions and preview
|
1769 |
+
**********************************************/
|
1770 |
+
#ws_capability_suggestions {
|
1771 |
+
padding: 4px;
|
1772 |
+
width: 350px;
|
1773 |
+
border: 1px solid #cdd5d5;
|
1774 |
+
box-shadow: 0 1px 1px rgba(0, 0, 0, 0.04);
|
1775 |
+
background: #fff;
|
1776 |
+
border-top-right-radius: 3px;
|
1777 |
+
border-bottom-right-radius: 3px;
|
1778 |
+
}
|
1779 |
+
#ws_capability_suggestions #ws_previewed_caps {
|
1780 |
+
margin-top: 0;
|
1781 |
+
margin-bottom: 6px;
|
1782 |
+
}
|
1783 |
+
#ws_capability_suggestions td, #ws_capability_suggestions th {
|
1784 |
+
padding-top: 3px;
|
1785 |
+
padding-bottom: 3px;
|
1786 |
+
}
|
1787 |
+
#ws_capability_suggestions tr.ws_preview_has_access .ws_ame_role_name {
|
1788 |
+
background-color: lightgreen;
|
1789 |
+
}
|
1790 |
+
#ws_capability_suggestions .ws_ame_suggested_capability {
|
1791 |
+
cursor: pointer;
|
1792 |
+
}
|
1793 |
+
#ws_capability_suggestions .ws_ame_suggested_capability:hover {
|
1794 |
+
background-color: #d0f2d0;
|
1795 |
+
}
|
1796 |
+
|
1797 |
+
/*********************************************
|
1798 |
+
Settings page stuff
|
1799 |
+
**********************************************/
|
1800 |
+
#ws_plugin_settings_form figure {
|
1801 |
+
margin-left: 0;
|
1802 |
+
margin-top: 0;
|
1803 |
+
margin-bottom: 1em;
|
1804 |
+
}
|
1805 |
+
|
1806 |
+
.ame-available-add-ons tr:first-of-type td {
|
1807 |
+
margin-top: 0;
|
1808 |
+
padding-top: 0;
|
1809 |
+
}
|
1810 |
+
.ame-available-add-ons td {
|
1811 |
+
padding-top: 10px;
|
1812 |
+
padding-bottom: 10px;
|
1813 |
+
}
|
1814 |
+
.ame-available-add-ons .ame-add-on-heading {
|
1815 |
+
padding-left: 0;
|
1816 |
+
}
|
1817 |
+
|
1818 |
+
.ame-add-on-name {
|
1819 |
+
font-weight: 600;
|
1820 |
+
}
|
1821 |
+
|
1822 |
+
.ame-add-on-details-link::after {
|
1823 |
+
/*content: " \f504";
|
1824 |
+
font-family: dashicons, sans-serif;*/
|
1825 |
+
}
|
1826 |
+
|
1827 |
+
/*********************************************
|
1828 |
+
WordPress 5.3+ consistent styles
|
1829 |
+
**********************************************/
|
1830 |
+
.ame-is-wp53-plus .ws_edit_field input[type=button] {
|
1831 |
+
margin-top: 1px;
|
1832 |
+
}
|
1833 |
+
|
1834 |
+
/*********************************************
|
1835 |
+
CSS border style selector
|
1836 |
+
**********************************************/
|
1837 |
+
.ame-css-border-styles .ame-fixed-label-text {
|
1838 |
+
min-width: 5em;
|
1839 |
+
}
|
1840 |
+
.ame-css-border-styles .ame-border-sample-container {
|
1841 |
+
display: inline-block;
|
1842 |
+
vertical-align: top;
|
1843 |
+
min-height: 28px;
|
1844 |
+
}
|
1845 |
+
.ame-css-border-styles .ame-border-sample {
|
1846 |
+
display: inline-block;
|
1847 |
+
width: 14em;
|
1848 |
+
border-top: 0.3em solid #444;
|
1849 |
+
}
|
1850 |
+
|
1851 |
+
/*********************************************
|
1852 |
+
Miscellaneous
|
1853 |
+
**********************************************/
|
1854 |
+
#ws_sidebar_pro_ad {
|
1855 |
+
min-width: 225px;
|
1856 |
+
margin-top: 5px;
|
1857 |
+
margin-left: 3px;
|
1858 |
+
position: fixed;
|
1859 |
+
right: 20px;
|
1860 |
+
bottom: 40px;
|
1861 |
+
z-index: 100;
|
1862 |
+
}
|
1863 |
+
|
1864 |
+
.ws-ame-icon-radio-button-group > label {
|
1865 |
+
display: inline-block;
|
1866 |
+
padding: 8px;
|
1867 |
+
border: 1px solid #ccd0d4;
|
1868 |
+
border-radius: 2px;
|
1869 |
+
margin-right: 0.5em;
|
1870 |
+
}
|
1871 |
+
|
1872 |
+
span.description {
|
1873 |
+
color: #666;
|
1874 |
+
font-style: italic;
|
1875 |
+
}
|
1876 |
+
|
1877 |
+
.test-wrap {
|
1878 |
+
background-color: #444444;
|
1879 |
+
padding: 30px;
|
1880 |
+
}
|
1881 |
+
|
1882 |
+
.test-container {
|
1883 |
+
width: 400px;
|
1884 |
+
height: 200px;
|
1885 |
+
background-color: white;
|
1886 |
+
border: 1px solid black;
|
1887 |
+
border-radius: 10px;
|
1888 |
+
overflow: hidden;
|
1889 |
+
}
|
1890 |
+
|
1891 |
+
.test-header {
|
1892 |
+
background-color: #67d6ff;
|
1893 |
+
padding: 6px;
|
1894 |
+
border-top-left-radius: 8px;
|
1895 |
+
border-top-right-radius: 8px;
|
1896 |
+
}
|
1897 |
+
|
1898 |
+
.test-content {
|
1899 |
+
padding: 8px;
|
1900 |
+
}
|
1901 |
+
|
1902 |
+
/*********************************************
|
1903 |
+
"Test access" dialog
|
1904 |
+
**********************************************/
|
1905 |
+
#ws_ame_test_access_screen {
|
1906 |
+
display: none;
|
1907 |
+
background: #fcfcfc;
|
1908 |
+
}
|
1909 |
+
|
1910 |
+
#ws_ame_test_inputs {
|
1911 |
+
padding-bottom: 16px;
|
1912 |
+
}
|
1913 |
+
|
1914 |
+
.ws_ame_test_input {
|
1915 |
+
display: block;
|
1916 |
+
float: left;
|
1917 |
+
width: 100%;
|
1918 |
+
margin: 2px 0;
|
1919 |
+
box-sizing: content-box;
|
1920 |
+
}
|
1921 |
+
|
1922 |
+
.ws_ame_test_input_name {
|
1923 |
+
display: block;
|
1924 |
+
float: left;
|
1925 |
+
width: 35%;
|
1926 |
+
margin-right: 4%;
|
1927 |
+
text-align: right;
|
1928 |
+
padding-top: 6px;
|
1929 |
+
line-height: 16px;
|
1930 |
+
}
|
1931 |
+
|
1932 |
+
.ws_ame_test_input_value {
|
1933 |
+
display: block;
|
1934 |
+
float: right;
|
1935 |
+
width: 60%;
|
1936 |
+
-webkit-box-sizing: border-box;
|
1937 |
+
-moz-box-sizing: border-box;
|
1938 |
+
box-sizing: border-box;
|
1939 |
+
}
|
1940 |
+
|
1941 |
+
#ws_ame_test_actions {
|
1942 |
+
float: left;
|
1943 |
+
width: 100%;
|
1944 |
+
margin-top: 1em;
|
1945 |
+
}
|
1946 |
+
|
1947 |
+
#ws_ame_test_button_container {
|
1948 |
+
width: 35%;
|
1949 |
+
margin-right: 4%;
|
1950 |
+
float: left;
|
1951 |
+
text-align: right;
|
1952 |
+
}
|
1953 |
+
|
1954 |
+
#ws_ame_test_progress {
|
1955 |
+
display: none;
|
1956 |
+
width: 60%;
|
1957 |
+
float: right;
|
1958 |
+
}
|
1959 |
+
#ws_ame_test_progress .spinner {
|
1960 |
+
float: none;
|
1961 |
+
vertical-align: bottom;
|
1962 |
+
margin-left: 0;
|
1963 |
+
margin-right: 4px;
|
1964 |
+
}
|
1965 |
+
|
1966 |
+
#ws_ame_test_access_body {
|
1967 |
+
width: 100%;
|
1968 |
+
position: relative;
|
1969 |
+
border: 1px solid #ddd;
|
1970 |
+
-webkit-border-radius: 3px;
|
1971 |
+
-moz-border-radius: 3px;
|
1972 |
+
border-radius: 3px;
|
1973 |
+
}
|
1974 |
+
|
1975 |
+
#ws_ame_test_frame_container {
|
1976 |
+
margin-right: 250px;
|
1977 |
+
background: white;
|
1978 |
+
min-height: 500px;
|
1979 |
+
position: relative;
|
1980 |
+
}
|
1981 |
+
|
1982 |
+
#ws_ame_test_access_frame {
|
1983 |
+
-webkit-box-sizing: border-box;
|
1984 |
+
-moz-box-sizing: border-box;
|
1985 |
+
box-sizing: border-box;
|
1986 |
+
width: 100%;
|
1987 |
+
height: 100%;
|
1988 |
+
min-height: 500px;
|
1989 |
+
border: none;
|
1990 |
+
margin: 0;
|
1991 |
+
padding: 0;
|
1992 |
+
}
|
1993 |
+
|
1994 |
+
#ws_ame_test_access_sidebar {
|
1995 |
+
-webkit-box-sizing: border-box;
|
1996 |
+
-moz-box-sizing: border-box;
|
1997 |
+
box-sizing: border-box;
|
1998 |
+
position: absolute;
|
1999 |
+
top: 0;
|
2000 |
+
right: 0;
|
2001 |
+
bottom: 0;
|
2002 |
+
width: 250px;
|
2003 |
+
padding: 16px 24px;
|
2004 |
+
background-color: #f3f3f3;
|
2005 |
+
border-left: 1px solid #ddd;
|
2006 |
+
}
|
2007 |
+
#ws_ame_test_access_sidebar h4:first-of-type {
|
2008 |
+
margin-top: 0;
|
2009 |
+
}
|
2010 |
+
|
2011 |
+
#ws_ame_test_frame_placeholder {
|
2012 |
+
display: block;
|
2013 |
+
padding: 16px 24px;
|
2014 |
+
}
|
2015 |
+
|
2016 |
+
#ws_ame_test_output {
|
2017 |
+
display: none;
|
2018 |
+
}
|
2019 |
+
|
2020 |
+
/***************************************
|
2021 |
+
Tabs on the settings page
|
2022 |
+
***************************************/
|
2023 |
+
.wrap.ws-ame-too-many-tabs .ws-ame-nav-tab-list.nav-tab-wrapper {
|
2024 |
+
border-bottom-color: transparent;
|
2025 |
+
}
|
2026 |
+
.wrap.ws-ame-too-many-tabs .ws-ame-nav-tab-list .nav-tab {
|
2027 |
+
border-bottom: 1px solid #c3c4c7;
|
2028 |
+
margin-bottom: 10px;
|
2029 |
+
margin-top: 0;
|
2030 |
+
}
|
2031 |
+
|
2032 |
+
/* Spacing between the page heading and the tab list.
|
2033 |
+
|
2034 |
+
Normally, this is handled by .nav-tab styles, but WordPress changes the margins at smaller screen sizes
|
2035 |
+
and the tabs end up without a left margin. Let's put that margin on the heading instead and remove it
|
2036 |
+
from the first tab. */
|
2037 |
+
#ws_ame_editor_heading {
|
2038 |
+
margin-right: 0.305em;
|
2039 |
+
}
|
2040 |
+
|
2041 |
+
.ws-ame-nav-tab-list a.nav-tab:first-of-type {
|
2042 |
+
margin-left: 0;
|
2043 |
+
}
|
2044 |
+
|
2045 |
+
/* When in "too many tabs" mode, there's too much space between the bottom of the tab list and the rest
|
2046 |
+
of the page. I haven't found a good way to change the margins of just the last row, so here's a partial fix. */
|
2047 |
+
.ws-ame-too-many-tabs #ws_actor_selector {
|
2048 |
+
margin-top: 0;
|
2049 |
+
}
|
2050 |
+
|
2051 |
+
/*# sourceMappingURL=menu-editor.css.map */
|
css/menu-editor.scss
CHANGED
@@ -740,7 +740,7 @@ a.ws_button.ws_button_disabled:hover {
|
|
740 |
width: 5px;
|
741 |
}
|
742 |
|
743 |
-
#ws_toggle_toolbar {
|
744 |
margin-right: 0;
|
745 |
}
|
746 |
|
@@ -1968,6 +1968,10 @@ $userSelectionPanelPadding: 10px;
|
|
1968 |
vertical-align: bottom;
|
1969 |
}
|
1970 |
|
|
|
|
|
|
|
|
|
1971 |
.ws_wide_tooltip {
|
1972 |
max-width: 450px;
|
1973 |
}
|
@@ -2256,4 +2260,6 @@ span.description {
|
|
2256 |
padding: 8px;
|
2257 |
}
|
2258 |
|
2259 |
-
@import "test-access-screen";
|
|
|
|
740 |
width: 5px;
|
741 |
}
|
742 |
|
743 |
+
#ws_toggle_toolbar, .ws_toggle_toolbar_button {
|
744 |
margin-right: 0;
|
745 |
}
|
746 |
|
1968 |
vertical-align: bottom;
|
1969 |
}
|
1970 |
|
1971 |
+
.ws_tooltip_trigger.ame-warning-tooltip {
|
1972 |
+
color: orange;
|
1973 |
+
}
|
1974 |
+
|
1975 |
.ws_wide_tooltip {
|
1976 |
max-width: 450px;
|
1977 |
}
|
2260 |
padding: 8px;
|
2261 |
}
|
2262 |
|
2263 |
+
@import "test-access-screen";
|
2264 |
+
|
2265 |
+
@import "main-tabs";
|
includes/ame-utils.php
CHANGED
@@ -183,4 +183,214 @@ class ameFileLock {
|
|
183 |
public function __destruct() {
|
184 |
$this->release();
|
185 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
186 |
}
|
183 |
public function __destruct() {
|
184 |
$this->release();
|
185 |
}
|
186 |
+
}
|
187 |
+
|
188 |
+
class ameOrderedMap implements Iterator, Countable {
|
189 |
+
/**
|
190 |
+
* @var ameLinkedListNode[]
|
191 |
+
*/
|
192 |
+
private $nodesByKey = array();
|
193 |
+
|
194 |
+
/**
|
195 |
+
* @var ameLinkedListNode|null
|
196 |
+
*/
|
197 |
+
private $head = null;
|
198 |
+
/**
|
199 |
+
* @var ameLinkedListNode|null
|
200 |
+
*/
|
201 |
+
private $tail = null;
|
202 |
+
/**
|
203 |
+
* @var ameLinkedListNode|null
|
204 |
+
*/
|
205 |
+
private $currentNode = null;
|
206 |
+
|
207 |
+
/**
|
208 |
+
* @param array $items
|
209 |
+
* @return $this
|
210 |
+
*/
|
211 |
+
public function addAll($items) {
|
212 |
+
foreach ($items as $key => $item) {
|
213 |
+
$this->set($key, $item);
|
214 |
+
}
|
215 |
+
return $this;
|
216 |
+
}
|
217 |
+
|
218 |
+
/**
|
219 |
+
* @param string $previousKey
|
220 |
+
* @param array $items
|
221 |
+
* @return $this
|
222 |
+
*/
|
223 |
+
public function insertAllAfter($previousKey, $items) {
|
224 |
+
if ( !isset($this->nodesByKey[$previousKey]) ) {
|
225 |
+
return $this->addAll($items);
|
226 |
+
}
|
227 |
+
|
228 |
+
$previousNode = $this->nodesByKey[$previousKey];
|
229 |
+
foreach ($items as $key => $value) {
|
230 |
+
if ( isset($this->nodesByKey[$key]) ) {
|
231 |
+
$node = $this->nodesByKey[$key];
|
232 |
+
} else {
|
233 |
+
$node = new ameLinkedListNode($value, $key);
|
234 |
+
$this->nodesByKey[$key] = $node;
|
235 |
+
}
|
236 |
+
|
237 |
+
$this->insertNodeAfter($previousNode, $node);
|
238 |
+
$previousNode = $node;
|
239 |
+
}
|
240 |
+
|
241 |
+
return $this;
|
242 |
+
}
|
243 |
+
|
244 |
+
/**
|
245 |
+
* @param string $previousKey
|
246 |
+
* @param string $key
|
247 |
+
* @param mixed $item
|
248 |
+
* @return $this
|
249 |
+
*/
|
250 |
+
public function insertAfter($previousKey, $key, $item) {
|
251 |
+
return $this->insertAllAfter($previousKey, array($key => $item));
|
252 |
+
}
|
253 |
+
|
254 |
+
private function insertNodeAfter($previousNode, $newNode) {
|
255 |
+
$newNode->previous = $previousNode;
|
256 |
+
$newNode->next = $previousNode->next;
|
257 |
+
if ( $newNode->next !== null ) {
|
258 |
+
$newNode->next->previous = $newNode;
|
259 |
+
}
|
260 |
+
|
261 |
+
$previousNode->next = $newNode;
|
262 |
+
|
263 |
+
if ( $this->tail === $previousNode ) {
|
264 |
+
$this->tail = $newNode;
|
265 |
+
}
|
266 |
+
}
|
267 |
+
|
268 |
+
/**
|
269 |
+
* @param string $nextKey
|
270 |
+
* @param string $key
|
271 |
+
* @param $item
|
272 |
+
* @return $this
|
273 |
+
*/
|
274 |
+
public function insertBefore($nextKey, $key, $item) {
|
275 |
+
if ( !isset($this->nodesByKey[$nextKey]) ) {
|
276 |
+
return $this->set($key, $item);
|
277 |
+
}
|
278 |
+
|
279 |
+
$nextNode = $this->nodesByKey[$nextKey];
|
280 |
+
$previousNode = $nextNode->previous;
|
281 |
+
|
282 |
+
if ( isset($this->nodesByKey[$key]) ) {
|
283 |
+
$node = $this->nodesByKey[$key];
|
284 |
+
} else {
|
285 |
+
$node = new ameLinkedListNode($item, $key);
|
286 |
+
$this->nodesByKey[$key] = $node;
|
287 |
+
}
|
288 |
+
|
289 |
+
$node->next = $nextNode;
|
290 |
+
$node->previous = $previousNode;
|
291 |
+
|
292 |
+
$nextNode->previous = $node;
|
293 |
+
if ( $previousNode !== null ) {
|
294 |
+
$previousNode->next = $node;
|
295 |
+
}
|
296 |
+
|
297 |
+
if ( $this->head === $nextNode ) {
|
298 |
+
$this->head = $node;
|
299 |
+
}
|
300 |
+
|
301 |
+
return $this;
|
302 |
+
}
|
303 |
+
|
304 |
+
public function set($key, $item) {
|
305 |
+
if ( isset($this->nodesByKey[$key]) ) {
|
306 |
+
$this->nodesByKey[$key]->value = $item;
|
307 |
+
} else {
|
308 |
+
$this->append($key, $item);
|
309 |
+
}
|
310 |
+
return $this;
|
311 |
+
}
|
312 |
+
|
313 |
+
private function append($key, $item) {
|
314 |
+
$node = new ameLinkedListNode($item, $key);
|
315 |
+
$this->nodesByKey[$key] = $node;
|
316 |
+
|
317 |
+
if ( $this->tail === null ) {
|
318 |
+
$this->head = $node;
|
319 |
+
$this->tail = $node;
|
320 |
+
} else {
|
321 |
+
$this->insertNodeAfter($this->tail, $node);
|
322 |
+
$this->tail = $node;
|
323 |
+
}
|
324 |
+
|
325 |
+
return $this;
|
326 |
+
}
|
327 |
+
|
328 |
+
public function current() {
|
329 |
+
return $this->currentNode->value;
|
330 |
+
}
|
331 |
+
|
332 |
+
public function next() {
|
333 |
+
if ( $this->currentNode !== null ) {
|
334 |
+
$this->currentNode = $this->currentNode->next;
|
335 |
+
}
|
336 |
+
}
|
337 |
+
|
338 |
+
public function key() {
|
339 |
+
return $this->currentNode->key;
|
340 |
+
}
|
341 |
+
|
342 |
+
public function valid() {
|
343 |
+
return ($this->currentNode !== null);
|
344 |
+
}
|
345 |
+
|
346 |
+
public function rewind() {
|
347 |
+
$this->currentNode = $this->head;
|
348 |
+
}
|
349 |
+
|
350 |
+
public function count() {
|
351 |
+
return count($this->nodesByKey);
|
352 |
+
}
|
353 |
+
|
354 |
+
/**
|
355 |
+
* Filter the map using a callback function.
|
356 |
+
* Returns a new map that contains only the items for which the callback function returns a truthy value.
|
357 |
+
*
|
358 |
+
* @param callable $predicate
|
359 |
+
* @return ameOrderedMap
|
360 |
+
*/
|
361 |
+
public function filter($predicate) {
|
362 |
+
$result = new self();
|
363 |
+
foreach($this as $key => $value) {
|
364 |
+
if ( call_user_func($predicate, $value, $key) ) {
|
365 |
+
$result->append($key, $value);
|
366 |
+
}
|
367 |
+
}
|
368 |
+
return $result;
|
369 |
+
}
|
370 |
+
}
|
371 |
+
|
372 |
+
class ameLinkedListNode {
|
373 |
+
/**
|
374 |
+
* @var string
|
375 |
+
*/
|
376 |
+
public $key;
|
377 |
+
|
378 |
+
/**
|
379 |
+
* @var mixed
|
380 |
+
*/
|
381 |
+
public $value;
|
382 |
+
|
383 |
+
/**
|
384 |
+
* @var self|null
|
385 |
+
*/
|
386 |
+
public $next = null;
|
387 |
+
/**
|
388 |
+
* @var self|null
|
389 |
+
*/
|
390 |
+
public $previous = null;
|
391 |
+
|
392 |
+
public function __construct($value, $key = '') {
|
393 |
+
$this->value = $value;
|
394 |
+
$this->key = $key;
|
395 |
+
}
|
396 |
}
|
includes/basic-dependencies.php
CHANGED
@@ -13,6 +13,7 @@ require_once $thisDirectory . '/auto-versioning.php';
|
|
13 |
require_once $thisDirectory . '/../ajax-wrapper/AjaxWrapper.php';
|
14 |
require_once $thisDirectory . '/module.php';
|
15 |
require_once $thisDirectory . '/persistent-module.php';
|
|
|
16 |
|
17 |
if ( !class_exists('WPMenuEditor', false) ) {
|
18 |
require_once $thisDirectory . '/menu-editor-core.php';
|
13 |
require_once $thisDirectory . '/../ajax-wrapper/AjaxWrapper.php';
|
14 |
require_once $thisDirectory . '/module.php';
|
15 |
require_once $thisDirectory . '/persistent-module.php';
|
16 |
+
require_once $thisDirectory . '/shortcodes.php';
|
17 |
|
18 |
if ( !class_exists('WPMenuEditor', false) ) {
|
19 |
require_once $thisDirectory . '/menu-editor-core.php';
|
includes/editor-page.php
CHANGED
@@ -29,8 +29,129 @@ foreach($icons as $name => $url) {
|
|
29 |
}
|
30 |
$icons = apply_filters('admin_menu_editor-toolbar_icons', $icons, $images_url);
|
31 |
|
32 |
-
$
|
33 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
34 |
|
35 |
//Output the "Upgrade to Pro" message
|
36 |
if ( !apply_filters('admin_menu_editor_is_pro', false) ){
|
@@ -66,15 +187,26 @@ if ( $is_pro_version ) {
|
|
66 |
include $extrasDirectory . '/copy-permissions-dialog.php';
|
67 |
}
|
68 |
|
69 |
-
|
70 |
-
|
71 |
-
|
72 |
-
|
73 |
-
|
74 |
-
|
75 |
-
|
76 |
-
|
77 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
78 |
}
|
79 |
|
80 |
?>
|
@@ -93,64 +225,10 @@ function ame_output_sort_buttons($icons) {
|
|
93 |
|
94 |
<div class='ws_main_container'>
|
95 |
<div class='ws_toolbar'>
|
96 |
-
|
97 |
-
|
98 |
-
|
99 |
-
|
100 |
-
|
101 |
-
<div class="ws_separator"> </div>
|
102 |
-
|
103 |
-
<a id='ws_new_menu' class='ws_button' href='javascript:void(0)' title='New menu'><img src='<?php echo $icons['new']; ?>' alt="New menu" /></a>
|
104 |
-
<a id='ws_new_separator' class='ws_button' href='javascript:void(0)' title='New separator'><img src='<?php echo $icons['new-separator']; ?>' alt="New separator" /></a>
|
105 |
-
<?php if ( $is_pro_version ): ?>
|
106 |
-
<a id='ws_new_heading' class='ws_button' href='javascript:void(0)' title='New heading'><img src='<?php echo $icons['new-heading']; ?>' alt="New heading" /></a>
|
107 |
-
<?php endif; ?>
|
108 |
-
|
109 |
-
<?php if ( $is_pro_version ): ?>
|
110 |
-
<div class="ws_separator"> </div>
|
111 |
-
|
112 |
-
<a id='ws_hide_and_deny_menu' class='ws_button ws_hide_and_deny_button' href='javascript:void(0)'
|
113 |
-
title='Hide and prevent access. <?php echo esc_attr($hide_button_extra_tooltip); ?>'>
|
114 |
-
<img src='<?php echo $icons['hide-and-deny']; ?>' alt="Hide" />
|
115 |
-
</a>
|
116 |
-
<?php endif; ?>
|
117 |
-
|
118 |
-
<?php if ( $editor_data['show_deprecated_hide_button'] ): ?>
|
119 |
-
<a id='ws_hide_menu' class='ws_button' href='javascript:void(0)' title='Hide without preventing access (cosmetic)'><img src='<?php echo $icons['hide']; ?>' alt="Hide (cosmetic)" /></a>
|
120 |
-
<?php endif; ?>
|
121 |
-
|
122 |
-
<a id='ws_delete_menu' class='ws_button ws_delete_menu_button' href='javascript:void(0)' title='Delete menu'><img src='<?php echo $icons['delete']; ?>' alt="Delete menu" /></a>
|
123 |
-
|
124 |
-
<div class="ws_separator"> </div>
|
125 |
-
|
126 |
-
<?php
|
127 |
-
if ( !$is_pro_version ) {
|
128 |
-
ame_output_sort_buttons($icons);
|
129 |
-
}
|
130 |
-
?>
|
131 |
-
|
132 |
-
<?php if ( $is_pro_version ): ?>
|
133 |
-
<a id='ws_toggle_toolbar' class='ws_button' href='javascript:void(0)' title='Toggle second toolbar'>
|
134 |
-
<img src='<?php echo $icons['toggle-toolbar']; ?>' alt="Toolbar toggle" />
|
135 |
-
</a>
|
136 |
-
<?php endif; ?>
|
137 |
-
|
138 |
-
<div class="clear"></div>
|
139 |
-
</div>
|
140 |
-
|
141 |
-
<div class="ws_button_container ws_second_toolbar_row <?php
|
142 |
-
if (!$is_second_toolbar_visible) { echo ' hidden'; }
|
143 |
-
?>">
|
144 |
-
|
145 |
-
<?php
|
146 |
-
if ( $is_pro_version ) {
|
147 |
-
ame_output_sort_buttons($icons);
|
148 |
-
}
|
149 |
-
?>
|
150 |
-
|
151 |
-
<?php do_action('admin_menu_editor-toolbar_row_2', $icons); ?>
|
152 |
-
<div class="clear"></div>
|
153 |
-
</div>
|
154 |
</div>
|
155 |
|
156 |
<div id='ws_menu_box' class="ws_box">
|
@@ -159,58 +237,24 @@ function ame_output_sort_buttons($icons) {
|
|
159 |
<?php do_action('admin_menu_editor-container', 'menu'); ?>
|
160 |
</div>
|
161 |
|
162 |
-
<div class='ws_main_container'>
|
163 |
<div class='ws_toolbar'>
|
164 |
-
|
165 |
-
|
166 |
-
|
167 |
-
|
168 |
-
|
169 |
-
<div class="ws_separator"> </div>
|
170 |
-
|
171 |
-
<a id='ws_new_item' class='ws_button' href='javascript:void(0)' title='New menu item'><img src='<?php echo $icons['new']; ?>' alt="New menu item" /></a>
|
172 |
-
<?php if ( $is_pro_version ): ?>
|
173 |
-
<a id='ws_new_submenu_separator' class='ws_button' href='javascript:void(0)' title='New separator'><img src='<?php echo $icons['new-separator']; ?>' alt="New separator" /></a>
|
174 |
-
<?php endif; ?>
|
175 |
-
|
176 |
-
<?php if ( $is_pro_version ): ?>
|
177 |
-
<div class="ws_separator"> </div>
|
178 |
-
|
179 |
-
<a id='ws_hide_and_deny_item' class='ws_button ws_hide_and_deny_button' href='javascript:void(0)'
|
180 |
-
title='Hide and prevent access. <?php echo esc_attr($hide_button_extra_tooltip); ?>'>
|
181 |
-
<img src='<?php echo $icons['hide-and-deny']; ?>' alt="Hide" />
|
182 |
-
</a>
|
183 |
-
<?php endif; ?>
|
184 |
-
|
185 |
-
<?php if ( $editor_data['show_deprecated_hide_button'] ): ?>
|
186 |
-
<a id='ws_hide_item' class='ws_button' href='javascript:void(0)' title='Hide without preventing access (cosmetic)'><img src='<?php echo $icons['hide']; ?>' alt="Show/Hide" /></a>
|
187 |
-
<?php endif; ?>
|
188 |
-
|
189 |
-
<a id='ws_delete_item' class='ws_button ws_delete_menu_button' href='javascript:void(0)' title='Delete menu item'><img src='<?php echo $icons['delete']; ?>' alt="Delete menu item" /></a>
|
190 |
-
|
191 |
-
<div class="ws_separator"> </div>
|
192 |
-
|
193 |
-
<?php
|
194 |
-
if ( !$is_pro_version ) {
|
195 |
-
ame_output_sort_buttons($icons);
|
196 |
-
}
|
197 |
-
?>
|
198 |
-
|
199 |
-
<div class="clear"></div>
|
200 |
-
</div>
|
201 |
-
|
202 |
-
<div class="ws_button_container ws_second_toolbar_row <?php
|
203 |
-
if (!$is_second_toolbar_visible) { echo ' hidden'; }
|
204 |
-
?>">
|
205 |
|
206 |
-
|
207 |
-
|
208 |
-
|
209 |
-
|
210 |
-
?>
|
211 |
|
212 |
-
|
213 |
-
|
|
|
|
|
|
|
|
|
214 |
</div>
|
215 |
|
216 |
<div id='ws_submenu_box' class="ws_box">
|
@@ -238,6 +282,8 @@ function ame_output_sort_buttons($icons) {
|
|
238 |
<input type="hidden" name="expand_menu" id="ws_expand_selected_menu" value="">
|
239 |
<input type="hidden" name="expand_submenu" id="ws_expand_selected_submenu" value="">
|
240 |
|
|
|
|
|
241 |
<input type="button" id='ws_save_menu' class="button-primary ws_main_button" value="Save Changes" />
|
242 |
</form>
|
243 |
|
29 |
}
|
30 |
$icons = apply_filters('admin_menu_editor-toolbar_icons', $icons, $images_url);
|
31 |
|
32 |
+
$toolbarButtons = new ameOrderedMap();
|
33 |
+
$toolbarButtons->addAll(array(
|
34 |
+
'cut' => array(
|
35 |
+
'title' => 'Cut',
|
36 |
+
),
|
37 |
+
'copy' => array(
|
38 |
+
'title' => 'Copy',
|
39 |
+
),
|
40 |
+
'paste' => array(
|
41 |
+
'title' => 'Paste',
|
42 |
+
),
|
43 |
+
'separator-1' => null,
|
44 |
+
'new-menu' => array(
|
45 |
+
'title' => 'New menu',
|
46 |
+
'iconName' => 'new',
|
47 |
+
),
|
48 |
+
'new-separator' => array(
|
49 |
+
'title' => 'New separator',
|
50 |
+
'topLevelOnly' => !$is_pro_version,
|
51 |
+
),
|
52 |
+
'delete' => array(
|
53 |
+
'title' => 'Delete menu',
|
54 |
+
'class' => array('ws_delete_menu_button'),
|
55 |
+
),
|
56 |
+
'separator-2' => null,
|
57 |
+
));
|
58 |
+
|
59 |
+
if ( !$is_pro_version ) {
|
60 |
+
ame_register_sort_buttons($toolbarButtons);
|
61 |
+
}
|
62 |
+
|
63 |
+
if ( $editor_data['show_deprecated_hide_button'] ) {
|
64 |
+
$toolbarButtons->insertBefore(
|
65 |
+
'delete',
|
66 |
+
'hide',
|
67 |
+
array(
|
68 |
+
'title' => 'Hide without preventing access (cosmetic)',
|
69 |
+
'alt' => 'Hide (cosmetic)',
|
70 |
+
)
|
71 |
+
);
|
72 |
+
}
|
73 |
+
|
74 |
+
$secondToolbarRow = new ameOrderedMap();
|
75 |
+
if ( $is_pro_version ) {
|
76 |
+
//In the Pro version, the sort buttons are on the second row.
|
77 |
+
ame_register_sort_buttons($secondToolbarRow);
|
78 |
+
}
|
79 |
+
|
80 |
+
$secondToolbarRowClasses = array('ws_second_toolbar_row');
|
81 |
+
if ( !$is_second_toolbar_visible ) {
|
82 |
+
$secondToolbarRowClasses[] = 'hidden';
|
83 |
+
}
|
84 |
+
|
85 |
+
do_action('admin_menu_editor-register_toolbar_buttons', $toolbarButtons, $secondToolbarRow, $icons);
|
86 |
+
|
87 |
+
if ( count($secondToolbarRow) > 0 ) {
|
88 |
+
$toolbarButtons->set(
|
89 |
+
'toggle-toolbar',
|
90 |
+
array(
|
91 |
+
'title' => 'Toggle second toolbar',
|
92 |
+
'alt' => 'Toolbar toggle',
|
93 |
+
'class' => array('ws_toggle_toolbar_button'),
|
94 |
+
'topLevelOnly' => true,
|
95 |
+
)
|
96 |
+
);
|
97 |
+
}
|
98 |
+
|
99 |
+
/**
|
100 |
+
* @param ameOrderedMap $buttons
|
101 |
+
* @param array $icons
|
102 |
+
* @param array $classes CSS classes to add to the toolbar row.
|
103 |
+
*/
|
104 |
+
function ame_output_toolbar_row($buttons, $icons, $classes = array()) {
|
105 |
+
$classes = array_merge(array('ws_button_container'), $classes);
|
106 |
+
printf('<div class="%s">', esc_attr(implode(' ', $classes)));
|
107 |
+
|
108 |
+
foreach ($buttons as $key => $settings) {
|
109 |
+
if ( $settings === null ) {
|
110 |
+
echo '<div class="ws_separator"> </div>';
|
111 |
+
continue;
|
112 |
+
}
|
113 |
+
|
114 |
+
if ( !isset($settings['title']) ) {
|
115 |
+
$settings['title'] = $key;
|
116 |
+
}
|
117 |
+
$action = isset($settings['action']) ? $settings['action'] : $key;
|
118 |
+
|
119 |
+
$buttonClasses = array('ws_button');
|
120 |
+
if ( !empty($settings['class']) ) {
|
121 |
+
$buttonClasses = array_merge($buttonClasses, $settings['class']);
|
122 |
+
}
|
123 |
+
|
124 |
+
$attributes = array(
|
125 |
+
'data-ame-button-action' => $action,
|
126 |
+
'class' => implode(' ', $buttonClasses),
|
127 |
+
'href' => '#',
|
128 |
+
'title' => $settings['title'],
|
129 |
+
);
|
130 |
+
if ( isset($settings['attributes']) ) {
|
131 |
+
$attributes = array_merge($attributes, $settings['attributes']);
|
132 |
+
}
|
133 |
+
|
134 |
+
$iconName = isset($settings['iconName']) ? $settings['iconName'] : $key;
|
135 |
+
$icon = '';
|
136 |
+
if ( isset($icons[$iconName]) ) {
|
137 |
+
$icon = sprintf(
|
138 |
+
'<img src="%s" alt="%s">',
|
139 |
+
esc_attr($icons[$iconName]),
|
140 |
+
esc_attr(isset($settings['alt']) ? $settings['alt'] : $settings['title'])
|
141 |
+
);
|
142 |
+
}
|
143 |
+
|
144 |
+
$pairs = array();
|
145 |
+
foreach ($attributes as $name => $value) {
|
146 |
+
$pairs[] = $name . '="' . esc_attr($value) . '"';
|
147 |
+
}
|
148 |
+
|
149 |
+
printf('<a %s>%s</a>' . "\n", implode(' ', $pairs), $icon);
|
150 |
+
}
|
151 |
+
|
152 |
+
echo '<div class="clear"></div>' . "\n";
|
153 |
+
echo '</div>';
|
154 |
+
}
|
155 |
|
156 |
//Output the "Upgrade to Pro" message
|
157 |
if ( !apply_filters('admin_menu_editor_is_pro', false) ){
|
187 |
include $extrasDirectory . '/copy-permissions-dialog.php';
|
188 |
}
|
189 |
|
190 |
+
/**
|
191 |
+
* @param ameOrderedMap $toolbar
|
192 |
+
*/
|
193 |
+
function ame_register_sort_buttons($toolbar) {
|
194 |
+
$toolbar->addAll(array(
|
195 |
+
'sort-ascending' => array(
|
196 |
+
'title' => 'Sort ascending',
|
197 |
+
'action' => 'sort',
|
198 |
+
'attributes' => array(
|
199 |
+
'data-sort-direction' => 'asc',
|
200 |
+
),
|
201 |
+
),
|
202 |
+
'sort-descending' => array(
|
203 |
+
'title' => 'Sort descending',
|
204 |
+
'action' => 'sort',
|
205 |
+
'attributes' => array(
|
206 |
+
'data-sort-direction' => 'desc',
|
207 |
+
),
|
208 |
+
),
|
209 |
+
));
|
210 |
}
|
211 |
|
212 |
?>
|
225 |
|
226 |
<div class='ws_main_container'>
|
227 |
<div class='ws_toolbar'>
|
228 |
+
<?php
|
229 |
+
ame_output_toolbar_row($toolbarButtons, $icons);
|
230 |
+
ame_output_toolbar_row($secondToolbarRow, $icons, $secondToolbarRowClasses);
|
231 |
+
?>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
232 |
</div>
|
233 |
|
234 |
<div id='ws_menu_box' class="ws_box">
|
237 |
<?php do_action('admin_menu_editor-container', 'menu'); ?>
|
238 |
</div>
|
239 |
|
240 |
+
<div class='ws_main_container' id="ame-submenu-column-template" style="display: none;">
|
241 |
<div class='ws_toolbar'>
|
242 |
+
<?php
|
243 |
+
function ame_button_can_be_in_submenu_toolbar($settings) {
|
244 |
+
return empty($settings['topLevelOnly']);
|
245 |
+
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
246 |
|
247 |
+
ame_output_toolbar_row(
|
248 |
+
$toolbarButtons->filter('ame_button_can_be_in_submenu_toolbar'),
|
249 |
+
$icons
|
250 |
+
);
|
|
|
251 |
|
252 |
+
ame_output_toolbar_row(
|
253 |
+
$secondToolbarRow->filter('ame_button_can_be_in_submenu_toolbar'),
|
254 |
+
$icons,
|
255 |
+
$secondToolbarRowClasses
|
256 |
+
);
|
257 |
+
?>
|
258 |
</div>
|
259 |
|
260 |
<div id='ws_submenu_box' class="ws_box">
|
282 |
<input type="hidden" name="expand_menu" id="ws_expand_selected_menu" value="">
|
283 |
<input type="hidden" name="expand_submenu" id="ws_expand_selected_submenu" value="">
|
284 |
|
285 |
+
<input type="hidden" name="deep_nesting_enabled" id="ws_is_deep_nesting_enabled" value="">
|
286 |
+
|
287 |
<input type="button" id='ws_save_menu' class="button-primary ws_main_button" value="Save Changes" />
|
288 |
</form>
|
289 |
|
includes/menu-editor-core.php
CHANGED
@@ -113,6 +113,11 @@ class WPMenuEditor extends MenuEd_ShadowPluginFramework {
|
|
113 |
*/
|
114 |
private $caps_used_in_menu = array();
|
115 |
|
|
|
|
|
|
|
|
|
|
|
116 |
public $is_access_test = false;
|
117 |
private $test_menu = null;
|
118 |
/**
|
@@ -206,6 +211,10 @@ class WPMenuEditor extends MenuEd_ShadowPluginFramework {
|
|
206 |
//with any role editing plugin. Disabled by default due to risk of conflicts and the performance impact.
|
207 |
'bbpress_override_enabled' => false,
|
208 |
|
|
|
|
|
|
|
|
|
209 |
//Which modules are active or inactive. Format: ['module-id' => true/false].
|
210 |
'is_active_module' => array(
|
211 |
'highlight-new-menus' => false,
|
@@ -232,7 +241,7 @@ class WPMenuEditor extends MenuEd_ShadowPluginFramework {
|
|
232 |
*
|
233 |
* We can't automatically detect menus like that. Here's why:
|
234 |
* 1) Most plugins remove them too late, e.g. in admin_head. By that point, output has already started.
|
235 |
-
* We need
|
236 |
* 2) It's hard to automatically determine *why* a menu item was removed. We can't distinguish between
|
237 |
* cosmetic changes like the hidden "welcome" items and people removing menus to deny access.
|
238 |
*/
|
@@ -246,6 +255,8 @@ class WPMenuEditor extends MenuEd_ShadowPluginFramework {
|
|
246 |
//BuddyPress 2.3.4
|
247 |
'index.php?page=bp-about' => true,
|
248 |
'index.php?page=bp-credits' => true,
|
|
|
|
|
249 |
//DW Question Answer 1.3.8.1
|
250 |
'index.php?page=dwqa-about' => true,
|
251 |
'index.php?page=dwqa-changelog' => true,
|
@@ -325,6 +336,10 @@ class WPMenuEditor extends MenuEd_ShadowPluginFramework {
|
|
325 |
'index.php?page=simple-calendar_translators' => true,
|
326 |
//Stripe For WooCommerce 3.2.12
|
327 |
'wc_stripe' => 'submenu',
|
|
|
|
|
|
|
|
|
328 |
);
|
329 |
|
330 |
//AJAXify screen options
|
@@ -562,6 +577,9 @@ class WPMenuEditor extends MenuEd_ShadowPluginFramework {
|
|
562 |
//Make sure Lodash doesn't conflict with the copy of Underscore that's bundled with WordPress.
|
563 |
add_filter('script_loader_tag', array($this, 'lodash_noconflict'), 10, 2); //Filter exists since WP 4.1.
|
564 |
|
|
|
|
|
|
|
565 |
//Compatibility fix for All In One Event Calendar; see the callback for details.
|
566 |
add_action("admin_print_scripts-$page", array($this, 'dequeue_ai1ec_scripts'));
|
567 |
|
@@ -625,7 +643,7 @@ class WPMenuEditor extends MenuEd_ShadowPluginFramework {
|
|
625 |
//Save the merged menu for later - the editor page will need it
|
626 |
$this->merged_custom_menu = $custom_menu;
|
627 |
|
628 |
-
do_action('admin_menu_editor-menu_merged', $
|
629 |
|
630 |
//Convert our custom menu to the $menu + $submenu structure used by WP.
|
631 |
//Note: This method sets up multiple internal fields and may cause side-effects.
|
@@ -633,6 +651,8 @@ class WPMenuEditor extends MenuEd_ShadowPluginFramework {
|
|
633 |
$this->build_custom_wp_menu($this->merged_custom_menu['tree']);
|
634 |
$this->user_cap_cache_enabled = false;
|
635 |
|
|
|
|
|
636 |
if ( $this->is_access_test ) {
|
637 |
$this->access_test_runner['wasCustomMenuApplied'] = true;
|
638 |
$this->access_test_runner->setCurrentMenuItem($this->get_current_menu_item());
|
@@ -862,7 +882,15 @@ class WPMenuEditor extends MenuEd_ShadowPluginFramework {
|
|
862 |
|
863 |
$wp_roles = ameRoleUtils::get_roles();
|
864 |
foreach($wp_roles->roles as $role_id => $role) {
|
865 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
866 |
$roles[$role_id] = $role;
|
867 |
}
|
868 |
|
@@ -953,7 +981,7 @@ class WPMenuEditor extends MenuEd_ShadowPluginFramework {
|
|
953 |
$allRealCaps = ameRoleUtils::get_all_capabilities(true);
|
954 |
//Similarly, capabilities that are directly assigned to users are probably real.
|
955 |
foreach($users as $user) {
|
956 |
-
$allRealCaps =
|
957 |
}
|
958 |
//Role IDs can also be used as capabilities.
|
959 |
foreach($roles as $roleId => $role) {
|
@@ -1046,12 +1074,12 @@ class WPMenuEditor extends MenuEd_ShadowPluginFramework {
|
|
1046 |
|
1047 |
$this->register_base_dependencies();
|
1048 |
|
1049 |
-
//
|
1050 |
-
//This is a separate script because it has to run after common.js which is loaded in the page footer.
|
1051 |
wp_enqueue_auto_versioned_script(
|
1052 |
-
'ame-
|
1053 |
-
plugins_url('js/
|
1054 |
-
array('jquery', 'common'),
|
1055 |
true
|
1056 |
);
|
1057 |
|
@@ -1159,6 +1187,8 @@ class WPMenuEditor extends MenuEd_ShadowPluginFramework {
|
|
1159 |
'expandSelectedMenu' => isset($this->get['expand_menu']) && ($this->get['expand_menu'] === '1'),
|
1160 |
'expandSelectedSubmenu' => isset($this->get['expand_submenu']) && ($this->get['expand_submenu'] === '1'),
|
1161 |
|
|
|
|
|
1162 |
'isDemoMode' => defined('IS_DEMO_MODE'),
|
1163 |
'isMasterMode' => defined('IS_MASTER_MODE'),
|
1164 |
);
|
@@ -1719,34 +1749,7 @@ class WPMenuEditor extends MenuEd_ShadowPluginFramework {
|
|
1719 |
|
1720 |
if (!empty($topmenu['items'])) {
|
1721 |
//Iterate over submenu items
|
1722 |
-
|
1723 |
-
if ( !ameMenuItem::get($item, 'custom') ) {
|
1724 |
-
$template_id = ameMenuItem::template_id($item);
|
1725 |
-
|
1726 |
-
//Is this item present in the default WP menu?
|
1727 |
-
if (isset($this->item_templates[$template_id])){
|
1728 |
-
//Yes, load defaults from that item
|
1729 |
-
$item['defaults'] = $this->item_templates[$template_id]['defaults'];
|
1730 |
-
$this->item_templates[$template_id]['used'] = true;
|
1731 |
-
//Add valid, non-custom items to the position index.
|
1732 |
-
$positions_by_template[$template_id] = ameMenuItem::get($item, 'position', 0);
|
1733 |
-
//We must move orphaned items elsewhere. Use the default location if possible.
|
1734 |
-
if ( isset($topmenu['missing']) && $topmenu['missing'] ) {
|
1735 |
-
$orphans[] = $item;
|
1736 |
-
}
|
1737 |
-
} else if ( empty($item['separator']) ) {
|
1738 |
-
//Record as missing, unless it's a menu separator
|
1739 |
-
$item['missing'] = true;
|
1740 |
-
|
1741 |
-
$temp = ameMenuItem::apply_defaults($item);
|
1742 |
-
$temp = $this->set_final_menu_capability($temp);
|
1743 |
-
$this->add_access_lookup($temp, 'submenu', true);
|
1744 |
-
}
|
1745 |
-
} else {
|
1746 |
-
//What if the parent of this custom item is missing?
|
1747 |
-
//Right now the custom item will just disappear.
|
1748 |
-
}
|
1749 |
-
}
|
1750 |
}
|
1751 |
}
|
1752 |
|
@@ -1849,6 +1852,50 @@ class WPMenuEditor extends MenuEd_ShadowPluginFramework {
|
|
1849 |
return $tree;
|
1850 |
}
|
1851 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1852 |
/**
|
1853 |
* Add a page and its required capability to the page access lookup.
|
1854 |
*
|
@@ -1928,6 +1975,7 @@ class WPMenuEditor extends MenuEd_ShadowPluginFramework {
|
|
1928 |
$new_menu = array();
|
1929 |
$new_submenu = array();
|
1930 |
$this->title_lookups = array();
|
|
|
1931 |
|
1932 |
//Prepare the top menu
|
1933 |
$first_nonseparator_found = false;
|
@@ -1948,23 +1996,7 @@ class WPMenuEditor extends MenuEd_ShadowPluginFramework {
|
|
1948 |
}
|
1949 |
|
1950 |
//Prepare the submenu of this menu
|
1951 |
-
$
|
1952 |
-
if( !empty($topmenu['items']) ){
|
1953 |
-
$items = $topmenu['items'];
|
1954 |
-
|
1955 |
-
foreach ($items as $item) {
|
1956 |
-
$item = $this->prepare_for_output($item, 'submenu', $topmenu);
|
1957 |
-
$new_items[] = $item;
|
1958 |
-
|
1959 |
-
//Make a note of the page's correct title so we can fix it later if necessary.
|
1960 |
-
$this->title_lookups[$item['file']] = !empty($item['page_title']) ? $item['page_title'] : $item['menu_title'];
|
1961 |
-
}
|
1962 |
-
|
1963 |
-
//Sort by position
|
1964 |
-
usort($new_items, 'ameMenuItem::compare_position');
|
1965 |
-
}
|
1966 |
-
|
1967 |
-
$topmenu['items'] = $new_items;
|
1968 |
$new_tree[] = $topmenu;
|
1969 |
}
|
1970 |
|
@@ -1983,84 +2015,175 @@ class WPMenuEditor extends MenuEd_ShadowPluginFramework {
|
|
1983 |
|
1984 |
//Convert the prepared tree to the internal WordPress format.
|
1985 |
foreach($new_tree as $topmenu) {
|
1986 |
-
$
|
1987 |
-
|
1988 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1989 |
$reason = sprintf(
|
1990 |
'There is a hidden menu item with the same URL (%1$s) but a higher priority.',
|
1991 |
-
$
|
1992 |
);
|
1993 |
$item['access_decision_reason'] = $reason;
|
1994 |
|
1995 |
-
if ( isset($
|
1996 |
-
$
|
1997 |
'+ Override: %1$s Setting the capability to "%2$s".',
|
1998 |
$reason,
|
1999 |
$trueAccess
|
2000 |
);
|
2001 |
-
$
|
2002 |
}
|
2003 |
}
|
2004 |
|
2005 |
-
if (
|
2006 |
-
|
2007 |
-
$this->reverse_item_lookup[$topmenu['url']] = $topmenu;
|
2008 |
-
}
|
2009 |
}
|
2010 |
|
2011 |
-
|
2012 |
-
|
2013 |
-
|
2014 |
-
|
2015 |
-
$item['access_level'] = $trueAccess;
|
2016 |
-
$reason = sprintf(
|
2017 |
-
'There is a hidden menu item with the same URL (%1$s) but a higher priority.',
|
2018 |
-
$item['url']
|
2019 |
-
);
|
2020 |
-
$item['access_decision_reason'] = $reason;
|
2021 |
|
2022 |
-
|
2023 |
-
|
2024 |
-
|
2025 |
-
$reason,
|
2026 |
-
$trueAccess
|
2027 |
-
);
|
2028 |
-
$item['access_check_log'][] = str_repeat('=', 79);
|
2029 |
-
}
|
2030 |
-
}
|
2031 |
|
2032 |
-
|
2033 |
-
|
2034 |
-
|
2035 |
|
2036 |
-
|
2037 |
-
|
2038 |
-
continue;
|
2039 |
-
}
|
2040 |
|
2041 |
-
|
2042 |
-
|
2043 |
-
|
|
|
2044 |
|
2045 |
-
|
2046 |
-
|
|
|
|
|
|
|
2047 |
|
2048 |
-
|
2049 |
-
|
2050 |
-
continue;
|
2051 |
-
}
|
2052 |
|
2053 |
-
|
2054 |
-
|
2055 |
-
|
2056 |
-
|
2057 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
2058 |
|
2059 |
-
|
|
|
2060 |
}
|
2061 |
|
2062 |
-
$
|
2063 |
-
$
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
2064 |
}
|
2065 |
|
2066 |
/**
|
@@ -2452,6 +2575,14 @@ class WPMenuEditor extends MenuEd_ShadowPluginFramework {
|
|
2452 |
do_action('admin_menu_editor-footer-' . $this->current_tab, $action);
|
2453 |
}
|
2454 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
2455 |
private function repair_database() {
|
2456 |
global $wpdb; /** @var wpdb $wpdb */
|
2457 |
|
@@ -2603,6 +2734,11 @@ class WPMenuEditor extends MenuEd_ShadowPluginFramework {
|
|
2603 |
wp_die(implode("<br>\n", $messages));
|
2604 |
}
|
2605 |
|
|
|
|
|
|
|
|
|
|
|
2606 |
//Redirect back to the editor and display the success message.
|
2607 |
$query = array('message' => 1);
|
2608 |
|
@@ -2735,6 +2871,9 @@ class WPMenuEditor extends MenuEd_ShadowPluginFramework {
|
|
2735 |
//bbPress override support.
|
2736 |
$this->options['bbpress_override_enabled'] = !empty($this->post['bbpress_override_enabled']);
|
2737 |
|
|
|
|
|
|
|
2738 |
//Active modules.
|
2739 |
$activeModules = isset($this->post['active_modules']) ? (array)$this->post['active_modules'] : array();
|
2740 |
$activeModules = array_fill_keys(array_map('strval', $activeModules), true);
|
@@ -2748,6 +2887,36 @@ class WPMenuEditor extends MenuEd_ShadowPluginFramework {
|
|
2748 |
}
|
2749 |
}
|
2750 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
2751 |
private function display_editor_ui() {
|
2752 |
//Prepare a bunch of parameters for the editor.
|
2753 |
$editor_data = array(
|
@@ -2884,7 +3053,7 @@ class WPMenuEditor extends MenuEd_ShadowPluginFramework {
|
|
2884 |
* Display the tabs for the settings page.
|
2885 |
*/
|
2886 |
public function display_editor_tabs() {
|
2887 |
-
echo '<h2 class="nav-tab-wrapper">';
|
2888 |
foreach($this->tabs as $slug => $title) {
|
2889 |
printf(
|
2890 |
'<a href="%s" id="%s" class="nav-tab%s">%s</a>',
|
@@ -3262,11 +3431,12 @@ class WPMenuEditor extends MenuEd_ShadowPluginFramework {
|
|
3262 |
* would not be highlighted properly when the user visits them.
|
3263 |
*/
|
3264 |
public function enqueue_menu_fix_script() {
|
|
|
|
|
3265 |
//Compatibility fix for PRO Theme 1.1.5.
|
3266 |
//This custom admin theme expands the current admin menu via JavaScript by using a "ready" handler.
|
3267 |
//We need to ensure that we highlight the correct current menu before that happens. This means we
|
3268 |
//have to enqueue the script in the header and with a higher priority than the PRO Theme script.
|
3269 |
-
$inFooter = true;
|
3270 |
if ( class_exists('PROTheme', false) ) {
|
3271 |
$inFooter = false;
|
3272 |
}
|
@@ -4578,6 +4748,12 @@ class WPMenuEditor extends MenuEd_ShadowPluginFramework {
|
|
4578 |
'requiredPhpVersion' => '5.3',
|
4579 |
'title' => 'Dashboard Widgets',
|
4580 |
),
|
|
|
|
|
|
|
|
|
|
|
|
|
4581 |
'plugin-visibility' => array(
|
4582 |
'relativePath' => 'modules/plugin-visibility/plugin-visibility.php',
|
4583 |
'className' => 'amePluginVisibility',
|
@@ -4649,6 +4825,13 @@ class WPMenuEditor extends MenuEd_ShadowPluginFramework {
|
|
4649 |
return true;
|
4650 |
}
|
4651 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
4652 |
} //class
|
4653 |
|
4654 |
|
113 |
*/
|
114 |
private $caps_used_in_menu = array();
|
115 |
|
116 |
+
/**
|
117 |
+
* @var bool Tue if the last displayed custom menu had more than two levels.
|
118 |
+
*/
|
119 |
+
private $custom_menu_is_deep = false;
|
120 |
+
|
121 |
public $is_access_test = false;
|
122 |
private $test_menu = null;
|
123 |
/**
|
211 |
//with any role editing plugin. Disabled by default due to risk of conflicts and the performance impact.
|
212 |
'bbpress_override_enabled' => false,
|
213 |
|
214 |
+
//Experimental: Allow more than two levels of menus.
|
215 |
+
'deep_nesting_enabled' => null,
|
216 |
+
'was_nesting_ever_changed' => false,
|
217 |
+
|
218 |
//Which modules are active or inactive. Format: ['module-id' => true/false].
|
219 |
'is_active_module' => array(
|
220 |
'highlight-new-menus' => false,
|
241 |
*
|
242 |
* We can't automatically detect menus like that. Here's why:
|
243 |
* 1) Most plugins remove them too late, e.g. in admin_head. By that point, output has already started.
|
244 |
+
* We need to finalize the list of menu items and their permissions before that.
|
245 |
* 2) It's hard to automatically determine *why* a menu item was removed. We can't distinguish between
|
246 |
* cosmetic changes like the hidden "welcome" items and people removing menus to deny access.
|
247 |
*/
|
255 |
//BuddyPress 2.3.4
|
256 |
'index.php?page=bp-about' => true,
|
257 |
'index.php?page=bp-credits' => true,
|
258 |
+
//BuddyBoss 1.5.9
|
259 |
+
'admin.php?page=buddyboss-platform' => 'submenu',
|
260 |
//DW Question Answer 1.3.8.1
|
261 |
'index.php?page=dwqa-about' => true,
|
262 |
'index.php?page=dwqa-changelog' => true,
|
336 |
'index.php?page=simple-calendar_translators' => true,
|
337 |
//Stripe For WooCommerce 3.2.12
|
338 |
'wc_stripe' => 'submenu',
|
339 |
+
//WP Grid Builder 1.5.9
|
340 |
+
'admin.php?page=wpgb-card-builder' => true,
|
341 |
+
'admin.php?page=wpgb-grid-settings' => true,
|
342 |
+
'admin.php?page=wpgb-facet-settings' => true,
|
343 |
);
|
344 |
|
345 |
//AJAXify screen options
|
577 |
//Make sure Lodash doesn't conflict with the copy of Underscore that's bundled with WordPress.
|
578 |
add_filter('script_loader_tag', array($this, 'lodash_noconflict'), 10, 2); //Filter exists since WP 4.1.
|
579 |
|
580 |
+
//Let modules do something when loading a specific tab but before output starts.
|
581 |
+
add_action('load-' . $page, array($this, 'trigger_tab_load_event'));
|
582 |
+
|
583 |
//Compatibility fix for All In One Event Calendar; see the callback for details.
|
584 |
add_action("admin_print_scripts-$page", array($this, 'dequeue_ai1ec_scripts'));
|
585 |
|
643 |
//Save the merged menu for later - the editor page will need it
|
644 |
$this->merged_custom_menu = $custom_menu;
|
645 |
|
646 |
+
do_action('admin_menu_editor-menu_merged', $this->merged_custom_menu);
|
647 |
|
648 |
//Convert our custom menu to the $menu + $submenu structure used by WP.
|
649 |
//Note: This method sets up multiple internal fields and may cause side-effects.
|
651 |
$this->build_custom_wp_menu($this->merged_custom_menu['tree']);
|
652 |
$this->user_cap_cache_enabled = false;
|
653 |
|
654 |
+
do_action('admin_menu_editor-menu_built', $this->merged_custom_menu, $this);
|
655 |
+
|
656 |
if ( $this->is_access_test ) {
|
657 |
$this->access_test_runner['wasCustomMenuApplied'] = true;
|
658 |
$this->access_test_runner->setCurrentMenuItem($this->get_current_menu_item());
|
882 |
|
883 |
$wp_roles = ameRoleUtils::get_roles();
|
884 |
foreach($wp_roles->roles as $role_id => $role) {
|
885 |
+
//There is at least one plugin that creates a custom role without a "capabilities" key.
|
886 |
+
//We need to check for that to avoid an "undefined array key" warning.
|
887 |
+
if ( array_key_exists('capabilities', $role) ) {
|
888 |
+
//Some plugins use 1, 0, null, or other truthy/falsy values for capability settings.
|
889 |
+
//AME uses booleans. It helps avoid bugs and it's also what WordPress core does.
|
890 |
+
$role['capabilities'] = $this->castValuesToBool($role['capabilities']);
|
891 |
+
} else {
|
892 |
+
$role['capabilities'] = array();
|
893 |
+
}
|
894 |
$roles[$role_id] = $role;
|
895 |
}
|
896 |
|
981 |
$allRealCaps = ameRoleUtils::get_all_capabilities(true);
|
982 |
//Similarly, capabilities that are directly assigned to users are probably real.
|
983 |
foreach($users as $user) {
|
984 |
+
$allRealCaps = $allRealCaps + $user['capabilities'];
|
985 |
}
|
986 |
//Role IDs can also be used as capabilities.
|
987 |
foreach($roles as $roleId => $role) {
|
1074 |
|
1075 |
$this->register_base_dependencies();
|
1076 |
|
1077 |
+
//Tab utilities and fixes.
|
1078 |
+
//This is a separate script because some of it has to run after common.js, which is loaded in the page footer.
|
1079 |
wp_enqueue_auto_versioned_script(
|
1080 |
+
'ame-settings-tab-utils',
|
1081 |
+
plugins_url('js/tab-utils.js', $this->plugin_file),
|
1082 |
+
array('jquery', 'ame-lodash', 'common'),
|
1083 |
true
|
1084 |
);
|
1085 |
|
1187 |
'expandSelectedMenu' => isset($this->get['expand_menu']) && ($this->get['expand_menu'] === '1'),
|
1188 |
'expandSelectedSubmenu' => isset($this->get['expand_submenu']) && ($this->get['expand_submenu'] === '1'),
|
1189 |
|
1190 |
+
'deepNestingEnabled' => $this->options['deep_nesting_enabled'],
|
1191 |
+
|
1192 |
'isDemoMode' => defined('IS_DEMO_MODE'),
|
1193 |
'isMasterMode' => defined('IS_MASTER_MODE'),
|
1194 |
);
|
1749 |
|
1750 |
if (!empty($topmenu['items'])) {
|
1751 |
//Iterate over submenu items
|
1752 |
+
$this->merge_children($topmenu, $positions_by_template, $orphans);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1753 |
}
|
1754 |
}
|
1755 |
|
1852 |
return $tree;
|
1853 |
}
|
1854 |
|
1855 |
+
/**
|
1856 |
+
* Merge the children of a menu item with the default values from the WordPress menu.
|
1857 |
+
* This section was extracted to a method just to make it possible to call it recursively.
|
1858 |
+
*
|
1859 |
+
* @param array $menu
|
1860 |
+
* @param array $positions_by_template
|
1861 |
+
* @param array $orphans
|
1862 |
+
*/
|
1863 |
+
private function merge_children(&$menu, &$positions_by_template, &$orphans) {
|
1864 |
+
foreach ($menu['items'] as &$item){
|
1865 |
+
if ( !ameMenuItem::get($item, 'custom') ) {
|
1866 |
+
$template_id = ameMenuItem::template_id($item);
|
1867 |
+
|
1868 |
+
//Is this item present in the default WP menu?
|
1869 |
+
if (isset($this->item_templates[$template_id])){
|
1870 |
+
//Yes, load defaults from that item
|
1871 |
+
$item['defaults'] = $this->item_templates[$template_id]['defaults'];
|
1872 |
+
$this->item_templates[$template_id]['used'] = true;
|
1873 |
+
//Add valid, non-custom items to the position index.
|
1874 |
+
$positions_by_template[$template_id] = ameMenuItem::get($item, 'position', 0);
|
1875 |
+
//We must move orphaned items elsewhere. Use the default location if possible.
|
1876 |
+
if ( isset($menu['missing']) && $menu['missing'] ) {
|
1877 |
+
$orphans[] = $item;
|
1878 |
+
}
|
1879 |
+
} else if ( empty($item['separator']) ) {
|
1880 |
+
//Record as missing, unless it's a menu separator
|
1881 |
+
$item['missing'] = true;
|
1882 |
+
|
1883 |
+
$temp = ameMenuItem::apply_defaults($item);
|
1884 |
+
$temp = $this->set_final_menu_capability($temp);
|
1885 |
+
$this->add_access_lookup($temp, 'submenu', true);
|
1886 |
+
}
|
1887 |
+
} else {
|
1888 |
+
//What if the parent of this custom item is missing?
|
1889 |
+
//Right now the custom item will just disappear.
|
1890 |
+
}
|
1891 |
+
|
1892 |
+
if ( !empty($item['items']) ) {
|
1893 |
+
//Recursively merge children of submenu items.
|
1894 |
+
$this->merge_children($item, $positions_by_template, $orphans);
|
1895 |
+
}
|
1896 |
+
}
|
1897 |
+
}
|
1898 |
+
|
1899 |
/**
|
1900 |
* Add a page and its required capability to the page access lookup.
|
1901 |
*
|
1975 |
$new_menu = array();
|
1976 |
$new_submenu = array();
|
1977 |
$this->title_lookups = array();
|
1978 |
+
$this->custom_menu_is_deep = false;
|
1979 |
|
1980 |
//Prepare the top menu
|
1981 |
$first_nonseparator_found = false;
|
1996 |
}
|
1997 |
|
1998 |
//Prepare the submenu of this menu
|
1999 |
+
$topmenu['items'] = $this->prepare_children_for_output($topmenu);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
2000 |
$new_tree[] = $topmenu;
|
2001 |
}
|
2002 |
|
2015 |
|
2016 |
//Convert the prepared tree to the internal WordPress format.
|
2017 |
foreach($new_tree as $topmenu) {
|
2018 |
+
$this->build_top_level_item($topmenu, $new_menu, $new_submenu);
|
2019 |
+
}
|
2020 |
+
|
2021 |
+
$this->custom_wp_menu = $new_menu;
|
2022 |
+
$this->custom_wp_submenu = $new_submenu;
|
2023 |
+
}
|
2024 |
+
|
2025 |
+
/**
|
2026 |
+
* Prepare all of the children (i.e. submenu items) of a menu for output.
|
2027 |
+
*
|
2028 |
+
* @param array $menu A menu item.
|
2029 |
+
* @return array
|
2030 |
+
*/
|
2031 |
+
private function prepare_children_for_output($menu) {
|
2032 |
+
if ( empty($menu['items']) ) {
|
2033 |
+
return array();
|
2034 |
+
}
|
2035 |
+
|
2036 |
+
$new_items = array();
|
2037 |
+
|
2038 |
+
foreach ($menu['items'] as $item) {
|
2039 |
+
$item = $this->prepare_for_output($item, 'submenu', $menu);
|
2040 |
+
|
2041 |
+
//Make a note of the page's correct title so we can fix it later if necessary.
|
2042 |
+
$this->title_lookups[$item['file']] = !empty($item['page_title']) ? $item['page_title'] : $item['menu_title'];
|
2043 |
+
|
2044 |
+
if ( !empty($item['items']) ) {
|
2045 |
+
$item['items'] = $this->prepare_children_for_output($item);
|
2046 |
+
}
|
2047 |
+
|
2048 |
+
$new_items[] = $item;
|
2049 |
+
}
|
2050 |
+
|
2051 |
+
//Sort by position
|
2052 |
+
usort($new_items, 'ameMenuItem::compare_position');
|
2053 |
+
|
2054 |
+
return $new_items;
|
2055 |
+
}
|
2056 |
+
|
2057 |
+
/**
|
2058 |
+
* Convert one top level menu and all of its submenu items to the WP menu format.
|
2059 |
+
*
|
2060 |
+
* @param array $topmenu A menu item.
|
2061 |
+
* @param array $menu Top level menu list. The converted item will be added to this list.
|
2062 |
+
* @param array $submenu Submenu list. The converted submenus (if any) will be added to this list.
|
2063 |
+
*/
|
2064 |
+
private function build_top_level_item($topmenu, &$menu, &$submenu) {
|
2065 |
+
$trueAccess = isset($this->page_access_lookup[$topmenu['url']]) ? $this->page_access_lookup[$topmenu['url']] : null;
|
2066 |
+
if ( ($trueAccess === 'do_not_allow') && ($topmenu['access_level'] !== $trueAccess) ) {
|
2067 |
+
$topmenu['access_level'] = $trueAccess;
|
2068 |
+
$reason = sprintf(
|
2069 |
+
'There is a hidden menu item with the same URL (%1$s) but a higher priority.',
|
2070 |
+
$topmenu['url']
|
2071 |
+
);
|
2072 |
+
$item['access_decision_reason'] = $reason;
|
2073 |
+
|
2074 |
+
if ( isset($topmenu['access_check_log']) ) {
|
2075 |
+
$topmenu['access_check_log'][] = sprintf(
|
2076 |
+
'+ Override: %1$s Setting the capability to "%2$s".',
|
2077 |
+
$reason,
|
2078 |
+
$trueAccess
|
2079 |
+
);
|
2080 |
+
$topmenu['access_check_log'][] = str_repeat('=', 79);
|
2081 |
+
}
|
2082 |
+
}
|
2083 |
+
|
2084 |
+
if ( !isset($this->reverse_item_lookup[$topmenu['url']]) ) { //Prefer sub-menus.
|
2085 |
+
if ( $this->is_item_visitable($topmenu) ) {
|
2086 |
+
$this->reverse_item_lookup[$topmenu['url']] = $topmenu;
|
2087 |
+
}
|
2088 |
+
}
|
2089 |
+
|
2090 |
+
$has_submenu_icons = false;
|
2091 |
+
foreach($topmenu['items'] as $item) {
|
2092 |
+
$trueAccess = isset($this->page_access_lookup[$item['url']]) ? $this->page_access_lookup[$item['url']] : null;
|
2093 |
+
if ( ($trueAccess === 'do_not_allow') && ($item['access_level'] !== $trueAccess) ) {
|
2094 |
+
$item['access_level'] = $trueAccess;
|
2095 |
$reason = sprintf(
|
2096 |
'There is a hidden menu item with the same URL (%1$s) but a higher priority.',
|
2097 |
+
$item['url']
|
2098 |
);
|
2099 |
$item['access_decision_reason'] = $reason;
|
2100 |
|
2101 |
+
if ( isset($item['access_check_log']) ) {
|
2102 |
+
$item['access_check_log'][] = sprintf(
|
2103 |
'+ Override: %1$s Setting the capability to "%2$s".',
|
2104 |
$reason,
|
2105 |
$trueAccess
|
2106 |
);
|
2107 |
+
$item['access_check_log'][] = str_repeat('=', 79);
|
2108 |
}
|
2109 |
}
|
2110 |
|
2111 |
+
if ( $this->is_item_visitable($item) ) {
|
2112 |
+
$this->reverse_item_lookup[$item['url']] = $item;
|
|
|
|
|
2113 |
}
|
2114 |
|
2115 |
+
//Skip missing and hidden items
|
2116 |
+
if ( !empty($item['missing']) || !empty($item['hidden']) ) {
|
2117 |
+
continue;
|
2118 |
+
}
|
|
|
|
|
|
|
|
|
|
|
|
|
2119 |
|
2120 |
+
//Keep track of which menus have items with icons. Ignore hidden items.
|
2121 |
+
$has_submenu_icons = $has_submenu_icons
|
2122 |
+
|| (!empty($item['has_submenu_icon']) && $item['access_level'] !== 'do_not_allow');
|
|
|
|
|
|
|
|
|
|
|
|
|
2123 |
|
2124 |
+
if ( !empty($item['items']) ) {
|
2125 |
+
$this->build_nested_submenu($item, $menu, $submenu);
|
2126 |
+
}
|
2127 |
|
2128 |
+
$submenu[$topmenu['file']][] = $this->convert_to_wp_format($item);
|
2129 |
+
}
|
|
|
|
|
2130 |
|
2131 |
+
//Skip missing and hidden menus.
|
2132 |
+
if ( !empty($topmenu['missing']) || !empty($topmenu['hidden']) ) {
|
2133 |
+
return;
|
2134 |
+
}
|
2135 |
|
2136 |
+
//The ame-has-submenu-icons class lets us change the appearance of all submenu items at once,
|
2137 |
+
//without having to add classes/styles to each item individually.
|
2138 |
+
if ( $has_submenu_icons && (strpos($topmenu['css_class'], 'ame-has-submenu-icons') === false) ) {
|
2139 |
+
$topmenu['css_class'] .= ' ame-has-submenu-icons';
|
2140 |
+
}
|
2141 |
|
2142 |
+
$menu[] = $this->convert_to_wp_format($topmenu);
|
2143 |
+
}
|
|
|
|
|
2144 |
|
2145 |
+
/**
|
2146 |
+
* Generate WP-compatible menu items for deeply nested submenus - that is, third level and beyond.
|
2147 |
+
*
|
2148 |
+
* @param array $item
|
2149 |
+
* @param array $wpMenu
|
2150 |
+
* @param array $wpSubmenu
|
2151 |
+
*/
|
2152 |
+
private function build_nested_submenu(&$item, &$wpMenu, &$wpSubmenu) {
|
2153 |
+
static $uniquePrefix = null, $submenuCounter = 0;
|
2154 |
+
if ( empty($item['items']) ) {
|
2155 |
+
return;
|
2156 |
+
}
|
2157 |
+
|
2158 |
+
$this->custom_menu_is_deep = true;
|
2159 |
|
2160 |
+
if ( $uniquePrefix === null ) {
|
2161 |
+
$uniquePrefix = (string) rand(1000, 9999);
|
2162 |
}
|
2163 |
|
2164 |
+
$submenuCounter++;
|
2165 |
+
$uniqueClass = 'ame-ds-m' . $uniquePrefix . $submenuCounter;
|
2166 |
+
$submenuClass = 'ame-ds-child-of-' . $uniqueClass;
|
2167 |
+
|
2168 |
+
//Flag the parent item as having a submenu.
|
2169 |
+
$item['css_class'] .= ' ame-has-deep-submenu ' . $uniqueClass;
|
2170 |
+
|
2171 |
+
//Output the submenu itself as a separate top level menu. The Pro version will then use JS to move it
|
2172 |
+
//to the right place in the DOM and make it work like a nested submenu. The free version doesn't have
|
2173 |
+
//that feature, but the menu will still be usable.
|
2174 |
+
$containerTopLevelMenu = array_merge(
|
2175 |
+
$item,
|
2176 |
+
array(
|
2177 |
+
'css_class' => 'menu-top ' . $submenuClass,
|
2178 |
+
'icon_url' => 'dashicons-menu',
|
2179 |
+
|
2180 |
+
//To avoid ID clashes, it would be useful to give each menu a unique slug/URL.
|
2181 |
+
//However, that breaks menu URL generation because WP also uses the parent URL for that.
|
2182 |
+
//'file' => '#ds' . $submenuCounter . '-' . $item['file'],
|
2183 |
+
)
|
2184 |
+
);
|
2185 |
+
|
2186 |
+
$this->build_top_level_item($containerTopLevelMenu, $wpMenu, $wpSubmenu);
|
2187 |
}
|
2188 |
|
2189 |
/**
|
2575 |
do_action('admin_menu_editor-footer-' . $this->current_tab, $action);
|
2576 |
}
|
2577 |
|
2578 |
+
public function trigger_tab_load_event() {
|
2579 |
+
//Modules can use this hook in place of the "load-$page_hook" action. This way a module
|
2580 |
+
//doesn't need to know what the page hook is, and it can easily target a specific tab.
|
2581 |
+
if ( !empty($this->current_tab) ) {
|
2582 |
+
do_action('admin_menu_editor-load_tab-' . $this->current_tab);
|
2583 |
+
}
|
2584 |
+
}
|
2585 |
+
|
2586 |
private function repair_database() {
|
2587 |
global $wpdb; /** @var wpdb $wpdb */
|
2588 |
|
2734 |
wp_die(implode("<br>\n", $messages));
|
2735 |
}
|
2736 |
|
2737 |
+
//Save nesting settings.
|
2738 |
+
if ( $this->update_nesting_settings($post) ) {
|
2739 |
+
$this->save_options();
|
2740 |
+
}
|
2741 |
+
|
2742 |
//Redirect back to the editor and display the success message.
|
2743 |
$query = array('message' => 1);
|
2744 |
|
2871 |
//bbPress override support.
|
2872 |
$this->options['bbpress_override_enabled'] = !empty($this->post['bbpress_override_enabled']);
|
2873 |
|
2874 |
+
//Three level menus / deep nesting.
|
2875 |
+
$this->update_nesting_settings($this->post);
|
2876 |
+
|
2877 |
//Active modules.
|
2878 |
$activeModules = isset($this->post['active_modules']) ? (array)$this->post['active_modules'] : array();
|
2879 |
$activeModules = array_fill_keys(array_map('strval', $activeModules), true);
|
2887 |
}
|
2888 |
}
|
2889 |
|
2890 |
+
/**
|
2891 |
+
* Update menu nesting/three level settings.
|
2892 |
+
*
|
2893 |
+
* Note: This method does not actually save the new settings to the database,
|
2894 |
+
* it just modifies them in memory.
|
2895 |
+
*
|
2896 |
+
* @param array $post
|
2897 |
+
* @return boolean True if settings were changed, false otherwise.
|
2898 |
+
*/
|
2899 |
+
private function update_nesting_settings($post) {
|
2900 |
+
if ( !isset($post['deep_nesting_enabled']) ) {
|
2901 |
+
return false;
|
2902 |
+
}
|
2903 |
+
|
2904 |
+
$nesting_enabled = $this->json_decode($post['deep_nesting_enabled']);
|
2905 |
+
$valid_nesting_settings = array(null, true, false);
|
2906 |
+
if (
|
2907 |
+
in_array($nesting_enabled, $valid_nesting_settings, true)
|
2908 |
+
&& ($nesting_enabled !== $this->options['deep_nesting_enabled'])
|
2909 |
+
) {
|
2910 |
+
$this->options['deep_nesting_enabled'] = $nesting_enabled;
|
2911 |
+
if ( $nesting_enabled !== null ) {
|
2912 |
+
$this->options['was_nesting_ever_changed'] = true;
|
2913 |
+
}
|
2914 |
+
return true;
|
2915 |
+
}
|
2916 |
+
|
2917 |
+
return false;
|
2918 |
+
}
|
2919 |
+
|
2920 |
private function display_editor_ui() {
|
2921 |
//Prepare a bunch of parameters for the editor.
|
2922 |
$editor_data = array(
|
3053 |
* Display the tabs for the settings page.
|
3054 |
*/
|
3055 |
public function display_editor_tabs() {
|
3056 |
+
echo '<h2 class="nav-tab-wrapper ws-ame-nav-tab-list">';
|
3057 |
foreach($this->tabs as $slug => $title) {
|
3058 |
printf(
|
3059 |
'<a href="%s" id="%s" class="nav-tab%s">%s</a>',
|
3431 |
* would not be highlighted properly when the user visits them.
|
3432 |
*/
|
3433 |
public function enqueue_menu_fix_script() {
|
3434 |
+
$inFooter = !$this->is_custom_menu_deep();
|
3435 |
+
|
3436 |
//Compatibility fix for PRO Theme 1.1.5.
|
3437 |
//This custom admin theme expands the current admin menu via JavaScript by using a "ready" handler.
|
3438 |
//We need to ensure that we highlight the correct current menu before that happens. This means we
|
3439 |
//have to enqueue the script in the header and with a higher priority than the PRO Theme script.
|
|
|
3440 |
if ( class_exists('PROTheme', false) ) {
|
3441 |
$inFooter = false;
|
3442 |
}
|
4748 |
'requiredPhpVersion' => '5.3',
|
4749 |
'title' => 'Dashboard Widgets',
|
4750 |
),
|
4751 |
+
'redirector' => array(
|
4752 |
+
'relativePath' => 'modules/redirector/redirector.php',
|
4753 |
+
'className' => '\\YahnisElsts\\AdminMenuEditor\\Redirects\\Module',
|
4754 |
+
'title' => 'Redirects',
|
4755 |
+
'requiredPhpVersion' => '5.6.20', //Same as WP 5.8.
|
4756 |
+
),
|
4757 |
'plugin-visibility' => array(
|
4758 |
'relativePath' => 'modules/plugin-visibility/plugin-visibility.php',
|
4759 |
'className' => 'amePluginVisibility',
|
4825 |
return true;
|
4826 |
}
|
4827 |
|
4828 |
+
/**
|
4829 |
+
* @return bool
|
4830 |
+
*/
|
4831 |
+
public function is_custom_menu_deep() {
|
4832 |
+
return $this->custom_menu_is_deep;
|
4833 |
+
}
|
4834 |
+
|
4835 |
} //class
|
4836 |
|
4837 |
|
includes/menu-item.php
CHANGED
@@ -257,7 +257,7 @@ abstract class ameMenuItem {
|
|
257 |
//This is because the URL includes a "return" parameter that contains the current page's URL. It also makes
|
258 |
//the template ID different on every page, so it's impossible to identify the menu. To fix that, lets remove
|
259 |
//the "return" parameter from the ID.
|
260 |
-
if (
|
261 |
$item_file = remove_query_arg('return', $item_file);
|
262 |
}
|
263 |
|
@@ -641,4 +641,45 @@ abstract class ameMenuItem {
|
|
641 |
}
|
642 |
return $url;
|
643 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
644 |
}
|
257 |
//This is because the URL includes a "return" parameter that contains the current page's URL. It also makes
|
258 |
//the template ID different on every page, so it's impossible to identify the menu. To fix that, lets remove
|
259 |
//the "return" parameter from the ID.
|
260 |
+
if ( strpos($item_file, 'customize.php?') === 0 ) {
|
261 |
$item_file = remove_query_arg('return', $item_file);
|
262 |
}
|
263 |
|
641 |
}
|
642 |
return $url;
|
643 |
}
|
644 |
+
|
645 |
+
/**
|
646 |
+
* Remove the number of pending updates or other count elements from a menu title.
|
647 |
+
*
|
648 |
+
* @param string $menuTitle
|
649 |
+
* @return string
|
650 |
+
*/
|
651 |
+
public static function remove_update_count($menuTitle) {
|
652 |
+
if ( (stripos($menuTitle, '<span') === false) || !class_exists('DOMDocument', false) ) {
|
653 |
+
return $menuTitle;
|
654 |
+
}
|
655 |
+
|
656 |
+
$dom = new DOMDocument();
|
657 |
+
$uniqueId = 'ame-rex-title-wrapper-' . time();
|
658 |
+
if ( @$dom->loadHTML('<div id="' . $uniqueId . '">' . $menuTitle . '</div>') ) {
|
659 |
+
/** @noinspection PhpComposerExtensionStubsInspection */
|
660 |
+
$xpath = new DOMXpath($dom);
|
661 |
+
$result = $xpath->query('//span[contains(@class,"update-plugins") or contains(@class,"awaiting-mod")]');
|
662 |
+
if ( $result->length > 0 ) {
|
663 |
+
//Remove all matched nodes. We must iterate backwards to prevent messing up the DOMNodeList.
|
664 |
+
for ($i = $result->length - 1; $i >= 0; $i--) {
|
665 |
+
$span = $result->item(0);
|
666 |
+
$span->parentNode->removeChild($span);
|
667 |
+
}
|
668 |
+
|
669 |
+
$innerHtml = '';
|
670 |
+
$children = $dom->getElementById($uniqueId)->childNodes;
|
671 |
+
//In theory, $children should always be a DOMNodeList, but there has been
|
672 |
+
//at least one report about the foreach() statement throwing a warning
|
673 |
+
//because $children was not iterable.
|
674 |
+
if ( $children instanceof Traversable ) {
|
675 |
+
foreach ($children as $child) {
|
676 |
+
$innerHtml .= $child->ownerDocument->saveHTML($child);
|
677 |
+
}
|
678 |
+
}
|
679 |
+
|
680 |
+
return $innerHtml;
|
681 |
+
}
|
682 |
+
}
|
683 |
+
return $menuTitle;
|
684 |
+
}
|
685 |
}
|
includes/menu.php
CHANGED
@@ -227,6 +227,7 @@ abstract class ameMenu {
|
|
227 |
*/
|
228 |
public static function to_json($menu) {
|
229 |
$menu = self::add_format_header($menu);
|
|
|
230 |
$result = json_encode($menu);
|
231 |
if ( !is_string($result) ) {
|
232 |
$message = sprintf(
|
227 |
*/
|
228 |
public static function to_json($menu) {
|
229 |
$menu = self::add_format_header($menu);
|
230 |
+
//todo: Maybe use wp_json_encode() instead. At least one user had invalid UTF-8 characters in their menu.
|
231 |
$result = json_encode($menu);
|
232 |
if ( !is_string($result) ) {
|
233 |
$message = sprintf(
|
includes/role-utils.php
CHANGED
@@ -27,7 +27,10 @@ class ameRoleUtils {
|
|
27 |
//Iterate over all known roles and collect their capabilities
|
28 |
foreach($wp_roles->roles as $role){
|
29 |
if ( !empty($role['capabilities']) && is_array($role['capabilities']) ){ //Being defensive here
|
30 |
-
|
|
|
|
|
|
|
31 |
}
|
32 |
}
|
33 |
$regular_cache = $capabilities;
|
@@ -42,7 +45,7 @@ class ameRoleUtils {
|
|
42 |
'manage_network_options' => 1,
|
43 |
'manage_network_plugins' => 1,
|
44 |
);
|
45 |
-
$capabilities =
|
46 |
$multisite_cache = $capabilities;
|
47 |
}
|
48 |
|
27 |
//Iterate over all known roles and collect their capabilities
|
28 |
foreach($wp_roles->roles as $role){
|
29 |
if ( !empty($role['capabilities']) && is_array($role['capabilities']) ){ //Being defensive here
|
30 |
+
//We use the "+" operator instead of array_merge() to combine arrays because we don't want
|
31 |
+
//integer keys to be renumbered. Technically, capabilities should be strings and not integers,
|
32 |
+
//but in practice some plugins do create integer capabilities.
|
33 |
+
$capabilities = $capabilities + $role['capabilities'];
|
34 |
}
|
35 |
}
|
36 |
$regular_cache = $capabilities;
|
45 |
'manage_network_options' => 1,
|
46 |
'manage_network_plugins' => 1,
|
47 |
);
|
48 |
+
$capabilities = $capabilities + $multisite_caps;
|
49 |
$multisite_cache = $capabilities;
|
50 |
}
|
51 |
|
includes/settings-page.php
CHANGED
@@ -354,6 +354,50 @@ $isProVersion = apply_filters('admin_menu_editor_is_pro', false);
|
|
354 |
</td>
|
355 |
</tr>
|
356 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
357 |
<tr>
|
358 |
<th scope="row">
|
359 |
WPML support
|
354 |
</td>
|
355 |
</tr>
|
356 |
|
357 |
+
<?php
|
358 |
+
//The free version lacks the ability to render deeply nested menus in the dashboard, so the nesting
|
359 |
+
//options are hidden by default. However, if the user somehow acquires a configuration where this
|
360 |
+
//feature is enabled (e.g. by importing config from the Pro version), the free version can display
|
361 |
+
//and even edit that configuration to a limited extent.
|
362 |
+
if ( $isProVersion || !empty($settings['was_nesting_ever_changed']) ):
|
363 |
+
?>
|
364 |
+
<tr>
|
365 |
+
<th scope="row">
|
366 |
+
Three level menus
|
367 |
+
<a class="ws_tooltip_trigger ame-warning-tooltip"
|
368 |
+
title="Caution: Experimental feature.<br>
|
369 |
+
This feature might not work as expected and it could cause conflicts with other plugins or themes.">
|
370 |
+
<div class="dashicons dashicons-admin-tools"></div>
|
371 |
+
</a>
|
372 |
+
</th>
|
373 |
+
<td>
|
374 |
+
<fieldset>
|
375 |
+
<?php
|
376 |
+
$nestingOptions = array(
|
377 |
+
'Ask on first use' => null,
|
378 |
+
'Enabled' . ($isProVersion ? '' : ' (only in editor)') => true,
|
379 |
+
'Disabled' => false,
|
380 |
+
);
|
381 |
+
foreach ($nestingOptions as $label => $nestingSetting):
|
382 |
+
?>
|
383 |
+
<p>
|
384 |
+
<label>
|
385 |
+
<input type="radio" name="deep_nesting_enabled"
|
386 |
+
value="<?php echo esc_attr(json_encode($nestingSetting)); ?>"
|
387 |
+
<?php
|
388 |
+
if ( $settings['deep_nesting_enabled'] === $nestingSetting ) {
|
389 |
+
echo ' checked="checked"';
|
390 |
+
}
|
391 |
+
?>>
|
392 |
+
<?php echo $label; ?>
|
393 |
+
</label>
|
394 |
+
</p>
|
395 |
+
<?php endforeach; ?>
|
396 |
+
</fieldset>
|
397 |
+
</td>
|
398 |
+
</tr>
|
399 |
+
<?php endif; ?>
|
400 |
+
|
401 |
<tr>
|
402 |
<th scope="row">
|
403 |
WPML support
|
includes/shortcodes.php
ADDED
@@ -0,0 +1,114 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
|
3 |
+
class ameCoreShortcodes {
|
4 |
+
protected static $allowedUserFields = array(
|
5 |
+
'ID',
|
6 |
+
'user_login',
|
7 |
+
'display_name',
|
8 |
+
'first_name',
|
9 |
+
'last_name',
|
10 |
+
'nickname',
|
11 |
+
'description',
|
12 |
+
'locale',
|
13 |
+
'user_nicename',
|
14 |
+
'user_url',
|
15 |
+
'user_registered',
|
16 |
+
'user_status',
|
17 |
+
);
|
18 |
+
|
19 |
+
public function register() {
|
20 |
+
add_shortcode('ame-wp-admin', array($this, 'handleAdminUrl'));
|
21 |
+
add_shortcode('ame-home-url', array($this, 'handleHomeUrl'));
|
22 |
+
add_shortcode('ame-user-info', array($this, 'handleUserInfo'));
|
23 |
+
}
|
24 |
+
|
25 |
+
/** @noinspection PhpUnusedParameterInspection Parameters are required by the shortcode API. */
|
26 |
+
public function handleAdminUrl($attributes = array(), $content = null, $tag = 'ame-wp-admin') {
|
27 |
+
if ( is_callable('self_admin_url') ) {
|
28 |
+
return self_admin_url();
|
29 |
+
}
|
30 |
+
return '[' . $tag . ']';
|
31 |
+
}
|
32 |
+
|
33 |
+
/** @noinspection PhpUnusedParameterInspection */
|
34 |
+
public function handleHomeUrl($attributes = array(), $content = null, $tag = 'ame-home-url') {
|
35 |
+
if ( is_callable('home_url') ) {
|
36 |
+
return home_url();
|
37 |
+
}
|
38 |
+
return '[' . $tag . ']';
|
39 |
+
}
|
40 |
+
|
41 |
+
public function handleUserInfo(
|
42 |
+
$attributes = array(), /** @noinspection PhpUnusedParameterInspection */
|
43 |
+
$content = null,
|
44 |
+
$tag = 'ame-user-info'
|
45 |
+
) {
|
46 |
+
$attributes = shortcode_atts(
|
47 |
+
array(
|
48 |
+
'field' => 'user_login',
|
49 |
+
'placeholder' => '(No user)',
|
50 |
+
'escape' => 'auto',
|
51 |
+
),
|
52 |
+
$attributes,
|
53 |
+
$tag
|
54 |
+
);
|
55 |
+
|
56 |
+
$placeholder = $attributes['placeholder'];
|
57 |
+
|
58 |
+
$field = strtolower($attributes['field']);
|
59 |
+
if ( $field === 'id' ) {
|
60 |
+
$field = 'ID';
|
61 |
+
}
|
62 |
+
if ( !in_array($field, self::$allowedUserFields) ) {
|
63 |
+
return '(Error: Unsupported field)';
|
64 |
+
}
|
65 |
+
|
66 |
+
//Get the currently logged-in user.
|
67 |
+
$user = null;
|
68 |
+
if ( is_callable('wp_get_current_user') ) {
|
69 |
+
$user = wp_get_current_user();
|
70 |
+
}
|
71 |
+
|
72 |
+
//Display the placeholder text if nobody is logged in or the user doesn't exist.
|
73 |
+
if ( empty($user) || !isset($user->ID) || ($user->ID === 0) ) {
|
74 |
+
return $placeholder;
|
75 |
+
}
|
76 |
+
|
77 |
+
$escapingHandlers = array(
|
78 |
+
'html' => 'esc_html',
|
79 |
+
'attr' => 'esc_attr',
|
80 |
+
'js' => 'esc_js',
|
81 |
+
'none' => array($this, 'identity'),
|
82 |
+
);
|
83 |
+
|
84 |
+
$escape = $attributes['escape'];
|
85 |
+
//By default, escape HTML special characters only if in the Loop.
|
86 |
+
if ( $escape === 'auto' ) {
|
87 |
+
if ( is_callable('in_the_loop') && in_the_loop() ) {
|
88 |
+
$escape = 'html';
|
89 |
+
} else {
|
90 |
+
$escape = 'none';
|
91 |
+
}
|
92 |
+
}
|
93 |
+
if ( !array_key_exists($escape, $escapingHandlers) ) {
|
94 |
+
return '(Error: Unsupported escape setting)';
|
95 |
+
}
|
96 |
+
if ( is_callable($escapingHandlers[$escape]) ) {
|
97 |
+
$escapeCallback = $escapingHandlers[$escape];
|
98 |
+
} else {
|
99 |
+
return '(Error: The specified escape function is not available)';
|
100 |
+
}
|
101 |
+
|
102 |
+
if ( isset($user->$field) ) {
|
103 |
+
return call_user_func($escapeCallback, $user->$field);
|
104 |
+
}
|
105 |
+
return $placeholder;
|
106 |
+
}
|
107 |
+
|
108 |
+
protected function identity($value) {
|
109 |
+
return $value;
|
110 |
+
}
|
111 |
+
}
|
112 |
+
|
113 |
+
$wsAmeShortcodes = new ameCoreShortcodes();
|
114 |
+
$wsAmeShortcodes->register();
|
js/actor-manager.js
CHANGED
@@ -9,6 +9,8 @@ var __extends = (this && this.__extends) || (function () {
|
|
9 |
return extendStatics(d, b);
|
10 |
};
|
11 |
return function (d, b) {
|
|
|
|
|
12 |
extendStatics(d, b);
|
13 |
function __() { this.constructor = d; }
|
14 |
d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __());
|
9 |
return extendStatics(d, b);
|
10 |
};
|
11 |
return function (d, b) {
|
12 |
+
if (typeof b !== "function" && b !== null)
|
13 |
+
throw new TypeError("Class extends value " + String(b) + " is not a constructor or null");
|
14 |
extendStatics(d, b);
|
15 |
function __() { this.constructor = d; }
|
16 |
d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __());
|
js/editor-tab-fix.js
DELETED
@@ -1,11 +0,0 @@
|
|
1 |
-
jQuery(function($) {
|
2 |
-
//On AME pages, move settings tabs after the heading. This is necessary to make them appear on the right side,
|
3 |
-
//and WordPress breaks that by moving notices like "Settings saved" after the first H1 (see common.js).
|
4 |
-
var menuEditorHeading = $('#ws_ame_editor_heading').first(),
|
5 |
-
menuEditorTabs = $('.nav-tab-wrapper').first();
|
6 |
-
menuEditorTabs = menuEditorTabs.add(menuEditorTabs.next('.clear'));
|
7 |
-
if ((menuEditorHeading.length > 0) && (menuEditorTabs.length > 0)) {
|
8 |
-
menuEditorTabs.insertAfter(menuEditorHeading);
|
9 |
-
}
|
10 |
-
});
|
11 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
js/jqueryui.d.ts
CHANGED
@@ -1,10 +1,10 @@
|
|
1 |
-
// Type definitions for jQueryUI 1.
|
2 |
// Project: http://jqueryui.com/
|
3 |
-
// Definitions by: Boris Yankov <https://github.com/borisyankov
|
4 |
// Definitions: https://github.com/DefinitelyTyped/DefinitelyTyped
|
|
|
5 |
|
6 |
-
|
7 |
-
/// <reference path="jquery.d.ts"/>
|
8 |
|
9 |
declare namespace JQueryUI {
|
10 |
// Accordion //////////////////////////////////////////////////
|
@@ -12,11 +12,11 @@ declare namespace JQueryUI {
|
|
12 |
interface AccordionOptions extends AccordionEvents {
|
13 |
active?: any; // boolean or number
|
14 |
animate?: any; // boolean, number, string or object
|
15 |
-
collapsible?: boolean;
|
16 |
-
disabled?: boolean;
|
17 |
-
event?: string;
|
18 |
-
header?: string;
|
19 |
-
heightStyle?: string;
|
20 |
icons?: any;
|
21 |
}
|
22 |
|
@@ -28,13 +28,13 @@ declare namespace JQueryUI {
|
|
28 |
}
|
29 |
|
30 |
interface AccordionEvent {
|
31 |
-
(event:
|
32 |
}
|
33 |
|
34 |
interface AccordionEvents {
|
35 |
-
activate?: AccordionEvent;
|
36 |
-
beforeActivate?: AccordionEvent;
|
37 |
-
create?: AccordionEvent;
|
38 |
}
|
39 |
|
40 |
interface Accordion extends Widget, AccordionOptions {
|
@@ -45,12 +45,18 @@ declare namespace JQueryUI {
|
|
45 |
|
46 |
interface AutocompleteOptions extends AutocompleteEvents {
|
47 |
appendTo?: any; //Selector;
|
48 |
-
autoFocus?: boolean;
|
49 |
-
delay?: number;
|
50 |
-
disabled?: boolean;
|
51 |
-
minLength?: number;
|
52 |
position?: any; // object
|
53 |
source?: any; // [], string or ()
|
|
|
|
|
|
|
|
|
|
|
|
|
54 |
}
|
55 |
|
56 |
interface AutocompleteUIParams {
|
@@ -58,21 +64,22 @@ declare namespace JQueryUI {
|
|
58 |
* The item selected from the menu, if any. Otherwise the property is null
|
59 |
*/
|
60 |
item?: any;
|
|
|
61 |
}
|
62 |
|
63 |
interface AutocompleteEvent {
|
64 |
-
(event:
|
65 |
}
|
66 |
|
67 |
interface AutocompleteEvents {
|
68 |
-
change?: AutocompleteEvent;
|
69 |
-
close?: AutocompleteEvent;
|
70 |
-
create?: AutocompleteEvent;
|
71 |
-
focus?: AutocompleteEvent;
|
72 |
-
open?: AutocompleteEvent;
|
73 |
-
response?: AutocompleteEvent;
|
74 |
-
search?: AutocompleteEvent;
|
75 |
-
select?: AutocompleteEvent;
|
76 |
}
|
77 |
|
78 |
interface Autocomplete extends Widget, AutocompleteOptions {
|
@@ -84,11 +91,11 @@ declare namespace JQueryUI {
|
|
84 |
// Button //////////////////////////////////////////////////
|
85 |
|
86 |
interface ButtonOptions {
|
87 |
-
disabled?: boolean;
|
88 |
icons?: any;
|
89 |
-
label?: string;
|
90 |
-
text?: string|boolean;
|
91 |
-
click?: (event?: Event) => void;
|
92 |
}
|
93 |
|
94 |
interface Button extends Widget, ButtonOptions {
|
@@ -105,19 +112,19 @@ declare namespace JQueryUI {
|
|
105 |
/**
|
106 |
* The dateFormat to be used for the altField option. This allows one date format to be shown to the user for selection purposes, while a different format is actually sent behind the scenes. For a full list of the possible formats see the formatDate function
|
107 |
*/
|
108 |
-
altFormat?: string;
|
109 |
/**
|
110 |
* The text to display after each date field, e.g., to show the required format.
|
111 |
*/
|
112 |
-
appendText?: string;
|
113 |
/**
|
114 |
* Set to true to automatically resize the input field to accommodate dates in the current dateFormat.
|
115 |
*/
|
116 |
-
autoSize?: boolean;
|
117 |
/**
|
118 |
* A function that takes an input field and current datepicker instance and returns an options object to update the datepicker with. It is called just before the datepicker is displayed.
|
119 |
*/
|
120 |
-
beforeShow?: (input: Element, inst: any) => JQueryUI.DatepickerOptions;
|
121 |
/**
|
122 |
* A function that takes a date as a parameter and must return an array with:
|
123 |
* [0]: true/false indicating whether or not this date is selectable
|
@@ -125,59 +132,59 @@ declare namespace JQueryUI {
|
|
125 |
* [2]: an optional popup tooltip for this date
|
126 |
* The function is called for each day in the datepicker before it is displayed.
|
127 |
*/
|
128 |
-
beforeShowDay?: (date: Date) => any[];
|
129 |
/**
|
130 |
* A URL of an image to use to display the datepicker when the showOn option is set to "button" or "both". If set, the buttonText option becomes the alt value and is not directly displayed.
|
131 |
*/
|
132 |
-
buttonImage?: string;
|
133 |
/**
|
134 |
* Whether the button image should be rendered by itself instead of inside a button element. This option is only relevant if the buttonImage option has also been set.
|
135 |
*/
|
136 |
-
buttonImageOnly?: boolean;
|
137 |
/**
|
138 |
* The text to display on the trigger button. Use in conjunction with the showOn option set to "button" or "both".
|
139 |
*/
|
140 |
-
buttonText?: string;
|
141 |
/**
|
142 |
* A function to calculate the week of the year for a given date. The default implementation uses the ISO 8601 definition: weeks start on a Monday; the first week of the year contains the first Thursday of the year.
|
143 |
*/
|
144 |
-
calculateWeek?: (date: Date) => string;
|
145 |
/**
|
146 |
* Whether the month should be rendered as a dropdown instead of text.
|
147 |
*/
|
148 |
-
changeMonth?: boolean;
|
149 |
/**
|
150 |
* Whether the year should be rendered as a dropdown instead of text. Use the yearRange option to control which years are made available for selection.
|
151 |
*/
|
152 |
-
changeYear?: boolean;
|
153 |
/**
|
154 |
* The text to display for the close link. Use the showButtonPanel option to display this button.
|
155 |
*/
|
156 |
-
closeText?: string;
|
157 |
/**
|
158 |
* When true, entry in the input field is constrained to those characters allowed by the current dateFormat option.
|
159 |
*/
|
160 |
-
constrainInput?: boolean;
|
161 |
/**
|
162 |
* The text to display for the current day link. Use the showButtonPanel option to display this button.
|
163 |
*/
|
164 |
-
currentText?: string;
|
165 |
/**
|
166 |
* The format for parsed and displayed dates. For a full list of the possible formats see the formatDate function.
|
167 |
*/
|
168 |
-
dateFormat?: string;
|
169 |
/**
|
170 |
* The list of long day names, starting from Sunday, for use as requested via the dateFormat option.
|
171 |
*/
|
172 |
-
dayNames?: string[];
|
173 |
/**
|
174 |
* The list of minimised day names, starting from Sunday, for use as column headers within the datepicker.
|
175 |
*/
|
176 |
-
dayNamesMin?: string[];
|
177 |
/**
|
178 |
* The list of abbreviated day names, starting from Sunday, for use as requested via the dateFormat option.
|
179 |
*/
|
180 |
-
dayNamesShort?: string[];
|
181 |
/**
|
182 |
* Set the date to highlight on first opening if the field is blank. Specify either an actual date via a Date object or as a string in the current dateFormat, or a number of days from today (e.g. +7) or a string of values and periods ('y' for years, 'm' for months, 'w' for weeks, 'd' for days, e.g. '+1m +7d'), or null for today.
|
183 |
* Multiple types supported:
|
@@ -189,23 +196,23 @@ declare namespace JQueryUI {
|
|
189 |
/**
|
190 |
* Control the speed at which the datepicker appears, it may be a time in milliseconds or a string representing one of the three predefined speeds ("slow", "normal", "fast").
|
191 |
*/
|
192 |
-
duration?: string;
|
193 |
/**
|
194 |
* Set the first day of the week: Sunday is 0, Monday is 1, etc.
|
195 |
*/
|
196 |
-
firstDay?: number;
|
197 |
/**
|
198 |
* When true, the current day link moves to the currently selected date instead of today.
|
199 |
*/
|
200 |
-
gotoCurrent?: boolean;
|
201 |
/**
|
202 |
* Normally the previous and next links are disabled when not applicable (see the minDate and maxDate options). You can hide them altogether by setting this attribute to true.
|
203 |
*/
|
204 |
-
hideIfNoPrevNext?: boolean;
|
205 |
/**
|
206 |
* Whether the current language is drawn from right to left.
|
207 |
*/
|
208 |
-
isRTL?: boolean;
|
209 |
/**
|
210 |
* The maximum selectable date. When set to null, there is no maximum.
|
211 |
* Multiple types supported:
|
@@ -225,19 +232,19 @@ declare namespace JQueryUI {
|
|
225 |
/**
|
226 |
* The list of full month names, for use as requested via the dateFormat option.
|
227 |
*/
|
228 |
-
monthNames?: string[];
|
229 |
/**
|
230 |
* The list of abbreviated month names, as used in the month header on each datepicker and as requested via the dateFormat option.
|
231 |
*/
|
232 |
-
monthNamesShort?: string[];
|
233 |
/**
|
234 |
* Whether the prevText and nextText options should be parsed as dates by the formatDate function, allowing them to display the target month names for example.
|
235 |
*/
|
236 |
-
navigationAsDateFormat?: boolean;
|
237 |
/**
|
238 |
* The text to display for the next month link. With the standard ThemeRoller styling, this value is replaced by an icon.
|
239 |
*/
|
240 |
-
nextText?: string;
|
241 |
/**
|
242 |
* The number of months to show at once.
|
243 |
* Multiple types supported:
|
@@ -248,23 +255,23 @@ declare namespace JQueryUI {
|
|
248 |
/**
|
249 |
* Called when the datepicker moves to a new month and/or year. The function receives the selected year, month (1-12), and the datepicker instance as parameters. this refers to the associated input field.
|
250 |
*/
|
251 |
-
onChangeMonthYear?: (year: number, month: number, inst: any) => void;
|
252 |
/**
|
253 |
* Called when the datepicker is closed, whether or not a date is selected. The function receives the selected date as text ("" if none) and the datepicker instance as parameters. this refers to the associated input field.
|
254 |
*/
|
255 |
-
onClose?: (dateText: string, inst: any) => void;
|
256 |
/**
|
257 |
* Called when the datepicker is selected. The function receives the selected date as text and the datepicker instance as parameters. this refers to the associated input field.
|
258 |
*/
|
259 |
-
onSelect?: (dateText: string, inst: any) => void;
|
260 |
/**
|
261 |
* The text to display for the previous month link. With the standard ThemeRoller styling, this value is replaced by an icon.
|
262 |
*/
|
263 |
-
prevText?: string;
|
264 |
/**
|
265 |
* Whether days in other months shown before or after the current month are selectable. This only applies if the showOtherMonths option is set to true.
|
266 |
*/
|
267 |
-
selectOtherMonths?: boolean;
|
268 |
/**
|
269 |
* The cutoff year for determining the century for a date (used in conjunction with dateFormat 'y'). Any dates entered with a year value less than or equal to the cutoff year are considered to be in the current century, while those greater than it are deemed to be in the previous century.
|
270 |
* Multiple types supported:
|
@@ -275,23 +282,23 @@ declare namespace JQueryUI {
|
|
275 |
/**
|
276 |
* The name of the animation used to show and hide the datepicker. Use "show" (the default), "slideDown", "fadeIn", any of the jQuery UI effects. Set to an empty string to disable animation.
|
277 |
*/
|
278 |
-
showAnim?: string;
|
279 |
/**
|
280 |
* Whether to display a button pane underneath the calendar. The button pane contains two buttons, a Today button that links to the current day, and a Done button that closes the datepicker. The buttons' text can be customized using the currentText and closeText options respectively.
|
281 |
*/
|
282 |
-
showButtonPanel?: boolean;
|
283 |
/**
|
284 |
* When displaying multiple months via the numberOfMonths option, the showCurrentAtPos option defines which position to display the current month in.
|
285 |
*/
|
286 |
-
showCurrentAtPos?: number;
|
287 |
/**
|
288 |
* Whether to show the month after the year in the header.
|
289 |
*/
|
290 |
-
showMonthAfterYear?: boolean;
|
291 |
/**
|
292 |
* When the datepicker should appear. The datepicker can appear when the field receives focus ("focus"), when a button is clicked ("button"), or when either event occurs ("both").
|
293 |
*/
|
294 |
-
showOn?: string;
|
295 |
/**
|
296 |
* If using one of the jQuery UI effects for the showAnim option, you can provide additional settings for that animation via this option.
|
297 |
*/
|
@@ -299,34 +306,42 @@ declare namespace JQueryUI {
|
|
299 |
/**
|
300 |
* Whether to display dates in other months (non-selectable) at the start or end of the current month. To make these days selectable use the selectOtherMonths option.
|
301 |
*/
|
302 |
-
showOtherMonths?: boolean;
|
303 |
/**
|
304 |
* When true, a column is added to show the week of the year. The calculateWeek option determines how the week of the year is calculated. You may also want to change the firstDay option.
|
305 |
*/
|
306 |
-
showWeek?: boolean;
|
307 |
/**
|
308 |
* Set how many months to move when clicking the previous/next links.
|
309 |
*/
|
310 |
-
stepMonths?: number;
|
311 |
/**
|
312 |
* The text to display for the week of the year column heading. Use the showWeek option to display this column.
|
313 |
*/
|
314 |
-
weekHeader?: string;
|
315 |
/**
|
316 |
* The range of years displayed in the year drop-down: either relative to today's year ("-nn:+nn"), relative to the currently selected year ("c-nn:c+nn"), absolute ("nnnn:nnnn"), or combinations of these formats ("nnnn:-nn"). Note that this option only affects what appears in the drop-down, to restrict which dates may be selected use the minDate and/or maxDate options.
|
317 |
*/
|
318 |
-
yearRange?: string;
|
319 |
/**
|
320 |
* Additional text to display after the year in the month headers.
|
321 |
*/
|
322 |
-
yearSuffix?: string;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
323 |
}
|
324 |
|
325 |
interface DatepickerFormatDateOptions {
|
326 |
-
dayNamesShort?: string[];
|
327 |
-
dayNames?: string[];
|
328 |
-
monthNamesShort?: string[];
|
329 |
-
monthNames?: string[];
|
330 |
}
|
331 |
|
332 |
interface Datepicker extends Widget, DatepickerOptions {
|
@@ -342,67 +357,82 @@ declare namespace JQueryUI {
|
|
342 |
// Dialog //////////////////////////////////////////////////
|
343 |
|
344 |
interface DialogOptions extends DialogEvents {
|
345 |
-
autoOpen?: boolean;
|
346 |
-
buttons?: { [buttonText: string]: (event?: Event) => void } | DialogButtonOptions[];
|
347 |
-
closeOnEscape?: boolean;
|
348 |
-
|
349 |
-
|
350 |
-
|
351 |
-
|
352 |
-
|
353 |
-
|
354 |
-
|
355 |
-
|
356 |
-
|
357 |
-
|
358 |
-
|
359 |
-
|
|
|
360 |
position?: any; // object, string or []
|
361 |
-
resizable?: boolean;
|
362 |
-
show?: boolean | number | string | DialogShowHideOptions;
|
363 |
-
stack?: boolean;
|
364 |
-
title?: string;
|
365 |
width?: any; // number or string
|
366 |
-
zIndex?: number;
|
367 |
|
368 |
-
|
369 |
-
close?: DialogEvent;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
370 |
}
|
371 |
|
372 |
interface DialogButtonOptions {
|
373 |
icons?: any;
|
374 |
-
showText?: string | boolean;
|
375 |
-
text?: string;
|
376 |
-
click?: (eventObject: JQueryEventObject) => any;
|
377 |
[attr: string]: any; // attributes for the <button> element
|
378 |
}
|
379 |
|
380 |
interface DialogShowHideOptions {
|
381 |
effect: string;
|
382 |
-
delay?: number;
|
383 |
-
duration?: number;
|
384 |
-
easing?: string;
|
385 |
}
|
386 |
|
387 |
interface DialogUIParams {
|
388 |
}
|
389 |
|
390 |
interface DialogEvent {
|
391 |
-
(event:
|
392 |
}
|
393 |
|
394 |
interface DialogEvents {
|
395 |
-
beforeClose?: DialogEvent;
|
396 |
-
close?: DialogEvent;
|
397 |
-
create?: DialogEvent;
|
398 |
-
drag?: DialogEvent;
|
399 |
-
dragStart?: DialogEvent;
|
400 |
-
dragStop?: DialogEvent;
|
401 |
-
focus?: DialogEvent;
|
402 |
-
open?: DialogEvent;
|
403 |
-
resize?: DialogEvent;
|
404 |
-
resizeStart?: DialogEvent;
|
405 |
-
resizeStop?: DialogEvent;
|
406 |
}
|
407 |
|
408 |
interface Dialog extends Widget, DialogOptions {
|
@@ -414,49 +444,58 @@ declare namespace JQueryUI {
|
|
414 |
interface DraggableEventUIParams {
|
415 |
helper: JQuery;
|
416 |
position: { top: number; left: number; };
|
|
|
417 |
offset: { top: number; left: number; };
|
418 |
}
|
419 |
|
420 |
interface DraggableEvent {
|
421 |
-
(event:
|
422 |
}
|
423 |
|
424 |
interface DraggableOptions extends DraggableEvents {
|
425 |
-
disabled?: boolean;
|
426 |
-
addClasses?: boolean;
|
427 |
appendTo?: any;
|
428 |
-
axis?: string;
|
429 |
-
cancel?: string;
|
430 |
-
|
|
|
431 |
containment?: any;
|
432 |
-
cursor?: string;
|
433 |
cursorAt?: any;
|
434 |
-
delay?: number;
|
435 |
-
distance?: number;
|
436 |
-
grid?: number[];
|
437 |
handle?: any;
|
438 |
helper?: any;
|
439 |
iframeFix?: any;
|
440 |
-
opacity?: number;
|
441 |
-
refreshPositions?: boolean;
|
442 |
revert?: any;
|
443 |
-
revertDuration?: number;
|
444 |
-
scope?: string;
|
445 |
-
scroll?: boolean;
|
446 |
-
scrollSensitivity?: number;
|
447 |
-
scrollSpeed?: number;
|
448 |
snap?: any;
|
449 |
-
snapMode?: string;
|
450 |
-
snapTolerance?: number;
|
451 |
-
stack?: string;
|
452 |
-
zIndex?: number;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
453 |
}
|
454 |
|
455 |
interface DraggableEvents {
|
456 |
-
create?: DraggableEvent;
|
457 |
-
start?: DraggableEvent;
|
458 |
-
drag?: DraggableEvent;
|
459 |
-
stop?: DraggableEvent;
|
460 |
}
|
461 |
|
462 |
interface Draggable extends Widget, DraggableOptions, DraggableEvent {
|
@@ -473,26 +512,27 @@ declare namespace JQueryUI {
|
|
473 |
}
|
474 |
|
475 |
interface DroppableEvent {
|
476 |
-
(event:
|
477 |
}
|
478 |
|
479 |
interface DroppableOptions extends DroppableEvents {
|
480 |
-
disabled?: boolean;
|
481 |
accept?: any;
|
482 |
-
activeClass?: string;
|
483 |
-
|
484 |
-
|
485 |
-
|
486 |
-
|
|
|
|
|
487 |
}
|
488 |
|
489 |
interface DroppableEvents {
|
490 |
-
create?: DroppableEvent;
|
491 |
-
activate?: DroppableEvent;
|
492 |
-
deactivate?: DroppableEvent;
|
493 |
-
over?: DroppableEvent;
|
494 |
-
out?: DroppableEvent;
|
495 |
-
drop?: DroppableEvent;
|
496 |
}
|
497 |
|
498 |
interface Droppable extends Widget, DroppableOptions {
|
@@ -501,26 +541,26 @@ declare namespace JQueryUI {
|
|
501 |
// Menu //////////////////////////////////////////////////
|
502 |
|
503 |
interface MenuOptions extends MenuEvents {
|
504 |
-
disabled?: boolean;
|
505 |
icons?: any;
|
506 |
-
menus?: string;
|
507 |
position?: any; // TODO
|
508 |
-
role?: string;
|
509 |
}
|
510 |
|
511 |
interface MenuUIParams {
|
512 |
-
item?: JQuery;
|
513 |
}
|
514 |
|
515 |
interface MenuEvent {
|
516 |
-
(event:
|
517 |
}
|
518 |
|
519 |
interface MenuEvents {
|
520 |
-
blur?: MenuEvent;
|
521 |
-
create?: MenuEvent;
|
522 |
-
focus?: MenuEvent;
|
523 |
-
select?: MenuEvent;
|
524 |
}
|
525 |
|
526 |
interface Menu extends Widget, MenuOptions {
|
@@ -530,22 +570,22 @@ declare namespace JQueryUI {
|
|
530 |
// Progressbar //////////////////////////////////////////////////
|
531 |
|
532 |
interface ProgressbarOptions extends ProgressbarEvents {
|
533 |
-
disabled?: boolean;
|
534 |
-
value?: number | boolean;
|
535 |
-
max?: number;
|
536 |
}
|
537 |
|
538 |
interface ProgressbarUIParams {
|
539 |
}
|
540 |
|
541 |
interface ProgressbarEvent {
|
542 |
-
(event:
|
543 |
}
|
544 |
|
545 |
interface ProgressbarEvents {
|
546 |
-
change?: ProgressbarEvent;
|
547 |
-
complete?: ProgressbarEvent;
|
548 |
-
create?: ProgressbarEvent;
|
549 |
}
|
550 |
|
551 |
interface Progressbar extends Widget, ProgressbarOptions {
|
@@ -556,24 +596,24 @@ declare namespace JQueryUI {
|
|
556 |
|
557 |
interface ResizableOptions extends ResizableEvents {
|
558 |
alsoResize?: any; // Selector, JQuery or Element
|
559 |
-
animate?: boolean;
|
560 |
animateDuration?: any; // number or string
|
561 |
-
animateEasing?: string;
|
562 |
aspectRatio?: any; // boolean or number
|
563 |
-
autoHide?: boolean;
|
564 |
-
cancel?: string;
|
565 |
containment?: any; // Selector, Element or string
|
566 |
-
delay?: number;
|
567 |
-
disabled?: boolean;
|
568 |
-
distance?: number;
|
569 |
-
ghost?: boolean;
|
570 |
grid?: any;
|
571 |
handles?: any; // string or object
|
572 |
-
helper?: string;
|
573 |
-
maxHeight?: number;
|
574 |
-
maxWidth?: number;
|
575 |
-
minHeight?: number;
|
576 |
-
minWidth?: number;
|
577 |
}
|
578 |
|
579 |
interface ResizableUIParams {
|
@@ -587,14 +627,14 @@ declare namespace JQueryUI {
|
|
587 |
}
|
588 |
|
589 |
interface ResizableEvent {
|
590 |
-
(event:
|
591 |
}
|
592 |
|
593 |
interface ResizableEvents {
|
594 |
-
resize?: ResizableEvent;
|
595 |
-
start?: ResizableEvent;
|
596 |
-
stop?: ResizableEvent;
|
597 |
-
create?:
|
598 |
}
|
599 |
|
600 |
interface Resizable extends Widget, ResizableOptions {
|
@@ -604,58 +644,111 @@ declare namespace JQueryUI {
|
|
604 |
// Selectable //////////////////////////////////////////////////
|
605 |
|
606 |
interface SelectableOptions extends SelectableEvents {
|
607 |
-
autoRefresh?: boolean;
|
608 |
-
cancel?: string;
|
609 |
-
delay?: number;
|
610 |
-
disabled?: boolean;
|
611 |
-
distance?: number;
|
612 |
-
filter?: string;
|
613 |
-
tolerance?: string;
|
614 |
}
|
615 |
|
616 |
interface SelectableEvents {
|
617 |
-
selected? (event:
|
618 |
-
selecting? (event:
|
619 |
-
start? (event:
|
620 |
-
stop? (event:
|
621 |
-
unselected? (event:
|
622 |
-
unselecting? (event:
|
623 |
}
|
624 |
|
625 |
interface Selectable extends Widget, SelectableOptions {
|
626 |
}
|
627 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
628 |
// Slider //////////////////////////////////////////////////
|
629 |
|
630 |
interface SliderOptions extends SliderEvents {
|
631 |
animate?: any; // boolean, string or number
|
632 |
-
disabled?: boolean;
|
633 |
-
max?: number;
|
634 |
-
min?: number;
|
635 |
-
orientation?: string;
|
636 |
range?: any; // boolean or string
|
637 |
-
step?: number;
|
638 |
-
value?: number;
|
639 |
-
values?: number[];
|
640 |
-
highlight?: boolean;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
641 |
}
|
642 |
|
643 |
interface SliderUIParams {
|
644 |
-
handle?: JQuery;
|
645 |
-
value?: number;
|
646 |
-
values?: number[];
|
647 |
}
|
648 |
|
649 |
interface SliderEvent {
|
650 |
-
(event:
|
651 |
}
|
652 |
|
653 |
interface SliderEvents {
|
654 |
-
change?: SliderEvent;
|
655 |
-
create?: SliderEvent;
|
656 |
-
slide?: SliderEvent;
|
657 |
-
start?: SliderEvent;
|
658 |
-
stop?: SliderEvent;
|
659 |
}
|
660 |
|
661 |
interface Slider extends Widget, SliderOptions {
|
@@ -666,30 +759,31 @@ declare namespace JQueryUI {
|
|
666 |
|
667 |
interface SortableOptions extends SortableEvents {
|
668 |
appendTo?: any; // jQuery, Element, Selector or string
|
669 |
-
|
|
|
670 |
cancel?: any; // Selector
|
671 |
connectWith?: any; // Selector
|
672 |
containment?: any; // Element, Selector or string
|
673 |
-
cursor?: string;
|
674 |
cursorAt?: any;
|
675 |
-
delay?: number;
|
676 |
-
disabled?: boolean;
|
677 |
-
distance?: number;
|
678 |
-
dropOnEmpty?: boolean;
|
679 |
-
forceHelperSize?: boolean;
|
680 |
-
forcePlaceholderSize?: boolean;
|
681 |
-
grid?: number[];
|
682 |
-
helper?: string | ((event:
|
683 |
handle?: any; // Selector or Element
|
684 |
items?: any; // Selector
|
685 |
-
opacity?: number;
|
686 |
-
placeholder?: string;
|
687 |
revert?: any; // boolean or number
|
688 |
-
scroll?: boolean;
|
689 |
-
scrollSensitivity?: number;
|
690 |
-
scrollSpeed?: number;
|
691 |
-
tolerance?: string;
|
692 |
-
zIndex?: number;
|
693 |
}
|
694 |
|
695 |
interface SortableUIParams {
|
@@ -707,18 +801,18 @@ declare namespace JQueryUI {
|
|
707 |
}
|
708 |
|
709 |
interface SortableEvents {
|
710 |
-
activate?: SortableEvent;
|
711 |
-
beforeStop?: SortableEvent;
|
712 |
-
change?: SortableEvent;
|
713 |
-
deactivate?: SortableEvent;
|
714 |
-
out?: SortableEvent;
|
715 |
-
over?: SortableEvent;
|
716 |
-
receive?: SortableEvent;
|
717 |
-
remove?: SortableEvent;
|
718 |
-
sort?: SortableEvent;
|
719 |
-
start?: SortableEvent;
|
720 |
-
stop?: SortableEvent;
|
721 |
-
update?: SortableEvent;
|
722 |
}
|
723 |
|
724 |
interface Sortable extends Widget, SortableOptions, SortableEvents {
|
@@ -728,14 +822,14 @@ declare namespace JQueryUI {
|
|
728 |
// Spinner //////////////////////////////////////////////////
|
729 |
|
730 |
interface SpinnerOptions extends SpinnerEvents {
|
731 |
-
culture?: string;
|
732 |
-
disabled?: boolean;
|
733 |
icons?: any;
|
734 |
incremental?: any; // boolean or ()
|
735 |
max?: any; // number or string
|
736 |
min?: any; // number or string
|
737 |
-
numberFormat?: string;
|
738 |
-
page?: number;
|
739 |
step?: any; // number or string
|
740 |
}
|
741 |
|
@@ -744,15 +838,15 @@ declare namespace JQueryUI {
|
|
744 |
}
|
745 |
|
746 |
interface SpinnerEvent<T> {
|
747 |
-
(event:
|
748 |
}
|
749 |
|
750 |
interface SpinnerEvents {
|
751 |
-
change?: SpinnerEvent<{}
|
752 |
-
create?: SpinnerEvent<{}
|
753 |
-
spin?: SpinnerEvent<SpinnerUIParam
|
754 |
-
start?: SpinnerEvent<{}
|
755 |
-
stop?: SpinnerEvent<{}
|
756 |
}
|
757 |
|
758 |
interface Spinner extends Widget, SpinnerOptions {
|
@@ -763,14 +857,26 @@ declare namespace JQueryUI {
|
|
763 |
|
764 |
interface TabsOptions extends TabsEvents {
|
765 |
active?: any; // boolean or number
|
766 |
-
|
|
|
767 |
disabled?: any; // boolean or []
|
768 |
-
event?: string;
|
769 |
-
heightStyle?: string;
|
770 |
hide?: any; // boolean, number, string or object
|
771 |
show?: any; // boolean, number, string or object
|
772 |
}
|
773 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
774 |
interface TabsActivationUIParams {
|
775 |
newTab: JQuery;
|
776 |
oldTab: JQuery;
|
@@ -791,15 +897,15 @@ declare namespace JQueryUI {
|
|
791 |
}
|
792 |
|
793 |
interface TabsEvent<UI> {
|
794 |
-
(event:
|
795 |
}
|
796 |
|
797 |
interface TabsEvents {
|
798 |
-
activate?: TabsEvent<TabsActivationUIParams
|
799 |
-
beforeActivate?: TabsEvent<TabsActivationUIParams
|
800 |
-
beforeLoad?: TabsEvent<TabsBeforeLoadUIParams
|
801 |
-
load?: TabsEvent<TabsCreateOrLoadUIParams
|
802 |
-
create?: TabsEvent<TabsCreateOrLoadUIParams
|
803 |
}
|
804 |
|
805 |
interface Tabs extends Widget, TabsOptions {
|
@@ -810,25 +916,26 @@ declare namespace JQueryUI {
|
|
810 |
|
811 |
interface TooltipOptions extends TooltipEvents {
|
812 |
content?: any; // () or string
|
813 |
-
disabled?: boolean;
|
814 |
hide?: any; // boolean, number, string or object
|
815 |
-
items?: string;
|
816 |
position?: any; // TODO
|
817 |
show?: any; // boolean, number, string or object
|
818 |
-
tooltipClass?: string;
|
819 |
-
track?: boolean;
|
|
|
820 |
}
|
821 |
|
822 |
interface TooltipUIParams {
|
823 |
}
|
824 |
|
825 |
interface TooltipEvent {
|
826 |
-
(event:
|
827 |
}
|
828 |
|
829 |
interface TooltipEvents {
|
830 |
-
close?: TooltipEvent;
|
831 |
-
open?: TooltipEvent;
|
832 |
}
|
833 |
|
834 |
interface Tooltip extends Widget, TooltipOptions {
|
@@ -839,86 +946,86 @@ declare namespace JQueryUI {
|
|
839 |
|
840 |
interface EffectOptions {
|
841 |
effect: string;
|
842 |
-
easing?: string;
|
843 |
-
duration?: number;
|
844 |
complete: Function;
|
845 |
}
|
846 |
|
847 |
interface BlindEffect {
|
848 |
-
direction?: string;
|
849 |
}
|
850 |
|
851 |
interface BounceEffect {
|
852 |
-
distance?: number;
|
853 |
-
times?: number;
|
854 |
}
|
855 |
|
856 |
interface ClipEffect {
|
857 |
-
direction?: number;
|
858 |
}
|
859 |
|
860 |
interface DropEffect {
|
861 |
-
direction?: number;
|
862 |
}
|
863 |
|
864 |
interface ExplodeEffect {
|
865 |
-
pieces?: number;
|
866 |
}
|
867 |
|
868 |
interface FadeEffect { }
|
869 |
|
870 |
interface FoldEffect {
|
871 |
size?: any;
|
872 |
-
horizFirst?: boolean;
|
873 |
}
|
874 |
|
875 |
interface HighlightEffect {
|
876 |
-
color?: string;
|
877 |
}
|
878 |
|
879 |
interface PuffEffect {
|
880 |
-
percent?: number;
|
881 |
}
|
882 |
|
883 |
interface PulsateEffect {
|
884 |
-
times?: number;
|
885 |
}
|
886 |
|
887 |
interface ScaleEffect {
|
888 |
-
direction?: string;
|
889 |
-
origin?: string[];
|
890 |
-
percent?: number;
|
891 |
-
scale?: string;
|
892 |
}
|
893 |
|
894 |
interface ShakeEffect {
|
895 |
-
direction?: string;
|
896 |
-
distance?: number;
|
897 |
-
times?: number;
|
898 |
}
|
899 |
|
900 |
interface SizeEffect {
|
901 |
to?: any;
|
902 |
-
origin?: string[];
|
903 |
-
scale?: string;
|
904 |
}
|
905 |
|
906 |
interface SlideEffect {
|
907 |
-
direction?: string;
|
908 |
-
distance?: number;
|
909 |
}
|
910 |
|
911 |
interface TransferEffect {
|
912 |
-
className?: string;
|
913 |
-
to?: string;
|
914 |
}
|
915 |
|
916 |
interface JQueryPositionOptions {
|
917 |
-
my?: string;
|
918 |
-
at?: string;
|
919 |
of?: any;
|
920 |
-
collision?: string;
|
921 |
-
using?: Function;
|
922 |
within?: any;
|
923 |
}
|
924 |
|
@@ -926,9 +1033,9 @@ declare namespace JQueryUI {
|
|
926 |
// UI //////////////////////////////////////////////////
|
927 |
|
928 |
interface MouseOptions {
|
929 |
-
cancel?: string;
|
930 |
-
delay?: number;
|
931 |
-
distance?: number;
|
932 |
}
|
933 |
|
934 |
interface KeyCode {
|
@@ -971,6 +1078,7 @@ declare namespace JQueryUI {
|
|
971 |
keyCode: KeyCode;
|
972 |
menu: Menu;
|
973 |
progressbar: Progressbar;
|
|
|
974 |
slider: Slider;
|
975 |
spinner: Spinner;
|
976 |
tabs: Tabs;
|
@@ -982,11 +1090,22 @@ declare namespace JQueryUI {
|
|
982 |
// Widget //////////////////////////////////////////////////
|
983 |
|
984 |
interface WidgetOptions {
|
985 |
-
disabled?: boolean;
|
986 |
hide?: any;
|
987 |
show?: any;
|
988 |
}
|
989 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
990 |
interface Widget {
|
991 |
(methodName: string): JQuery;
|
992 |
(options: WidgetOptions): JQuery;
|
@@ -995,8 +1114,8 @@ declare namespace JQueryUI {
|
|
995 |
(optionLiteral: string, options: WidgetOptions): any;
|
996 |
(optionLiteral: string, optionName: string, optionValue: any): JQuery;
|
997 |
|
998 |
-
(name: string, prototype:
|
999 |
-
(name: string, base: Function, prototype:
|
1000 |
}
|
1001 |
|
1002 |
////////////////////////////////////////////////////////////////////////////////////////////////////
|
@@ -1310,6 +1429,23 @@ interface JQuery {
|
|
1310 |
* @param optionName 'buttonText'
|
1311 |
*/
|
1312 |
datepicker(methodName: 'option', optionName: 'buttonText'): string;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1313 |
/**
|
1314 |
* Set the buttonText option, after initialization
|
1315 |
*
|
@@ -1675,6 +1811,22 @@ interface JQuery {
|
|
1675 |
selectable(optionLiteral: string, options: JQueryUI.SelectableOptions): any;
|
1676 |
selectable(optionLiteral: string, optionName: string, optionValue: any): JQuery;
|
1677 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1678 |
slider(): JQuery;
|
1679 |
slider(methodName: 'destroy'): void;
|
1680 |
slider(methodName: 'disable'): void;
|
@@ -1700,11 +1852,11 @@ interface JQuery {
|
|
1700 |
sortable(methodName: 'disable'): void;
|
1701 |
sortable(methodName: 'enable'): void;
|
1702 |
sortable(methodName: 'widget'): JQuery;
|
1703 |
-
sortable(methodName: 'toArray'): string[];
|
1704 |
sortable(methodName: string): JQuery;
|
1705 |
sortable(options: JQueryUI.SortableOptions): JQuery;
|
1706 |
sortable(optionLiteral: string, optionName: string): any;
|
1707 |
-
sortable(methodName: 'serialize', options?: { key?: string; attribute?: string; expression?: RegExp }): string;
|
1708 |
sortable(optionLiteral: string, options: JQueryUI.SortableOptions): any;
|
1709 |
sortable(optionLiteral: string, optionName: string, optionValue: any): JQuery;
|
1710 |
|
@@ -1728,10 +1880,13 @@ interface JQuery {
|
|
1728 |
tabs(): JQuery;
|
1729 |
tabs(methodName: 'destroy'): void;
|
1730 |
tabs(methodName: 'disable'): void;
|
|
|
1731 |
tabs(methodName: 'enable'): void;
|
|
|
1732 |
tabs(methodName: 'load', index: number): void;
|
1733 |
tabs(methodName: 'refresh'): void;
|
1734 |
tabs(methodName: 'widget'): JQuery;
|
|
|
1735 |
tabs(methodName: string): JQuery;
|
1736 |
tabs(options: JQueryUI.TabsOptions): JQuery;
|
1737 |
tabs(optionLiteral: string, optionName: string): any;
|
@@ -1752,39 +1907,39 @@ interface JQuery {
|
|
1752 |
tooltip(optionLiteral: string, optionName: string, optionValue: any): JQuery;
|
1753 |
|
1754 |
|
1755 |
-
addClass(classNames: string, speed?: number, callback?: Function):
|
1756 |
-
addClass(classNames: string, speed?: string, callback?: Function):
|
1757 |
-
addClass(classNames: string, speed?: number, easing?: string, callback?: Function):
|
1758 |
-
addClass(classNames: string, speed?: string, easing?: string, callback?: Function):
|
1759 |
|
1760 |
-
removeClass(classNames: string, speed?: number, callback?: Function):
|
1761 |
-
removeClass(classNames: string, speed?: string, callback?: Function):
|
1762 |
-
removeClass(classNames: string, speed?: number, easing?: string, callback?: Function):
|
1763 |
-
removeClass(classNames: string, speed?: string, easing?: string, callback?: Function):
|
1764 |
|
1765 |
-
switchClass(removeClassName: string, addClassName: string, duration?: number, easing?: string, complete?: Function):
|
1766 |
-
switchClass(removeClassName: string, addClassName: string, duration?: string, easing?: string, complete?: Function):
|
1767 |
|
1768 |
-
toggleClass(className: string, duration?: number, easing?: string, complete?: Function):
|
1769 |
-
toggleClass(className: string, duration?: string, easing?: string, complete?: Function):
|
1770 |
-
toggleClass(className: string, aswitch?: boolean, duration?: number, easing?: string, complete?: Function):
|
1771 |
-
toggleClass(className: string, aswitch?: boolean, duration?: string, easing?: string, complete?: Function):
|
1772 |
|
1773 |
-
effect(options: any):
|
1774 |
-
effect(effect: string, options?: any, duration?: number, complete?: Function):
|
1775 |
-
effect(effect: string, options?: any, duration?: string, complete?: Function):
|
1776 |
|
1777 |
-
hide(options: any):
|
1778 |
-
hide(effect: string, options?: any, duration?: number, complete?: Function):
|
1779 |
-
hide(effect: string, options?: any, duration?: string, complete?: Function):
|
1780 |
|
1781 |
-
show(options: any):
|
1782 |
-
show(effect: string, options?: any, duration?: number, complete?: Function):
|
1783 |
-
show(effect: string, options?: any, duration?: string, complete?: Function):
|
1784 |
|
1785 |
-
toggle(options: any):
|
1786 |
-
toggle(effect: string, options?: any, duration?: number, complete?: Function):
|
1787 |
-
toggle(effect: string, options?: any, duration?: string, complete?: Function):
|
1788 |
|
1789 |
position(options: JQueryUI.JQueryPositionOptions): JQuery;
|
1790 |
|
@@ -1794,7 +1949,7 @@ interface JQuery {
|
|
1794 |
uniqueId(): JQuery;
|
1795 |
removeUniqueId(): JQuery;
|
1796 |
scrollParent(): JQuery;
|
1797 |
-
zIndex():
|
1798 |
zIndex(zIndex: number): JQuery;
|
1799 |
|
1800 |
widget: JQueryUI.Widget;
|
1 |
+
// Type definitions for jQueryUI 1.12
|
2 |
// Project: http://jqueryui.com/
|
3 |
+
// Definitions by: Boris Yankov <https://github.com/borisyankov>, John Reilly <https://github.com/johnnyreilly>, Dieter Oberkofler <https://github.com/doberkofler>
|
4 |
// Definitions: https://github.com/DefinitelyTyped/DefinitelyTyped
|
5 |
+
// TypeScript Version: 2.3
|
6 |
|
7 |
+
/// <reference path="jquery.d.ts" />
|
|
|
8 |
|
9 |
declare namespace JQueryUI {
|
10 |
// Accordion //////////////////////////////////////////////////
|
12 |
interface AccordionOptions extends AccordionEvents {
|
13 |
active?: any; // boolean or number
|
14 |
animate?: any; // boolean, number, string or object
|
15 |
+
collapsible?: boolean | undefined;
|
16 |
+
disabled?: boolean | undefined;
|
17 |
+
event?: string | undefined;
|
18 |
+
header?: string | undefined;
|
19 |
+
heightStyle?: string | undefined;
|
20 |
icons?: any;
|
21 |
}
|
22 |
|
28 |
}
|
29 |
|
30 |
interface AccordionEvent {
|
31 |
+
(event: JQueryEventObject, ui: AccordionUIParams): void;
|
32 |
}
|
33 |
|
34 |
interface AccordionEvents {
|
35 |
+
activate?: AccordionEvent | undefined;
|
36 |
+
beforeActivate?: AccordionEvent | undefined;
|
37 |
+
create?: AccordionEvent | undefined;
|
38 |
}
|
39 |
|
40 |
interface Accordion extends Widget, AccordionOptions {
|
45 |
|
46 |
interface AutocompleteOptions extends AutocompleteEvents {
|
47 |
appendTo?: any; //Selector;
|
48 |
+
autoFocus?: boolean | undefined;
|
49 |
+
delay?: number | undefined;
|
50 |
+
disabled?: boolean | undefined;
|
51 |
+
minLength?: number | undefined;
|
52 |
position?: any; // object
|
53 |
source?: any; // [], string or ()
|
54 |
+
classes?: AutocompleteClasses | undefined;
|
55 |
+
}
|
56 |
+
|
57 |
+
interface AutocompleteClasses {
|
58 |
+
"ui-autocomplete"?: string | undefined;
|
59 |
+
"ui-autocomplete-input"?: string | undefined;
|
60 |
}
|
61 |
|
62 |
interface AutocompleteUIParams {
|
64 |
* The item selected from the menu, if any. Otherwise the property is null
|
65 |
*/
|
66 |
item?: any;
|
67 |
+
content?: any;
|
68 |
}
|
69 |
|
70 |
interface AutocompleteEvent {
|
71 |
+
(event: JQueryEventObject, ui: AutocompleteUIParams): void;
|
72 |
}
|
73 |
|
74 |
interface AutocompleteEvents {
|
75 |
+
change?: AutocompleteEvent | undefined;
|
76 |
+
close?: AutocompleteEvent | undefined;
|
77 |
+
create?: AutocompleteEvent | undefined;
|
78 |
+
focus?: AutocompleteEvent | undefined;
|
79 |
+
open?: AutocompleteEvent | undefined;
|
80 |
+
response?: AutocompleteEvent | undefined;
|
81 |
+
search?: AutocompleteEvent | undefined;
|
82 |
+
select?: AutocompleteEvent | undefined;
|
83 |
}
|
84 |
|
85 |
interface Autocomplete extends Widget, AutocompleteOptions {
|
91 |
// Button //////////////////////////////////////////////////
|
92 |
|
93 |
interface ButtonOptions {
|
94 |
+
disabled?: boolean | undefined;
|
95 |
icons?: any;
|
96 |
+
label?: string | undefined;
|
97 |
+
text?: string|boolean | undefined;
|
98 |
+
click?: ((event?: Event) => void) | undefined;
|
99 |
}
|
100 |
|
101 |
interface Button extends Widget, ButtonOptions {
|
112 |
/**
|
113 |
* The dateFormat to be used for the altField option. This allows one date format to be shown to the user for selection purposes, while a different format is actually sent behind the scenes. For a full list of the possible formats see the formatDate function
|
114 |
*/
|
115 |
+
altFormat?: string | undefined;
|
116 |
/**
|
117 |
* The text to display after each date field, e.g., to show the required format.
|
118 |
*/
|
119 |
+
appendText?: string | undefined;
|
120 |
/**
|
121 |
* Set to true to automatically resize the input field to accommodate dates in the current dateFormat.
|
122 |
*/
|
123 |
+
autoSize?: boolean | undefined;
|
124 |
/**
|
125 |
* A function that takes an input field and current datepicker instance and returns an options object to update the datepicker with. It is called just before the datepicker is displayed.
|
126 |
*/
|
127 |
+
beforeShow?: ((input: Element, inst: any) => JQueryUI.DatepickerOptions) | undefined;
|
128 |
/**
|
129 |
* A function that takes a date as a parameter and must return an array with:
|
130 |
* [0]: true/false indicating whether or not this date is selectable
|
132 |
* [2]: an optional popup tooltip for this date
|
133 |
* The function is called for each day in the datepicker before it is displayed.
|
134 |
*/
|
135 |
+
beforeShowDay?: ((date: Date) => any[]) | undefined;
|
136 |
/**
|
137 |
* A URL of an image to use to display the datepicker when the showOn option is set to "button" or "both". If set, the buttonText option becomes the alt value and is not directly displayed.
|
138 |
*/
|
139 |
+
buttonImage?: string | undefined;
|
140 |
/**
|
141 |
* Whether the button image should be rendered by itself instead of inside a button element. This option is only relevant if the buttonImage option has also been set.
|
142 |
*/
|
143 |
+
buttonImageOnly?: boolean | undefined;
|
144 |
/**
|
145 |
* The text to display on the trigger button. Use in conjunction with the showOn option set to "button" or "both".
|
146 |
*/
|
147 |
+
buttonText?: string | undefined;
|
148 |
/**
|
149 |
* A function to calculate the week of the year for a given date. The default implementation uses the ISO 8601 definition: weeks start on a Monday; the first week of the year contains the first Thursday of the year.
|
150 |
*/
|
151 |
+
calculateWeek?: ((date: Date) => string) | undefined;
|
152 |
/**
|
153 |
* Whether the month should be rendered as a dropdown instead of text.
|
154 |
*/
|
155 |
+
changeMonth?: boolean | undefined;
|
156 |
/**
|
157 |
* Whether the year should be rendered as a dropdown instead of text. Use the yearRange option to control which years are made available for selection.
|
158 |
*/
|
159 |
+
changeYear?: boolean | undefined;
|
160 |
/**
|
161 |
* The text to display for the close link. Use the showButtonPanel option to display this button.
|
162 |
*/
|
163 |
+
closeText?: string | undefined;
|
164 |
/**
|
165 |
* When true, entry in the input field is constrained to those characters allowed by the current dateFormat option.
|
166 |
*/
|
167 |
+
constrainInput?: boolean | undefined;
|
168 |
/**
|
169 |
* The text to display for the current day link. Use the showButtonPanel option to display this button.
|
170 |
*/
|
171 |
+
currentText?: string | undefined;
|
172 |
/**
|
173 |
* The format for parsed and displayed dates. For a full list of the possible formats see the formatDate function.
|
174 |
*/
|
175 |
+
dateFormat?: string | undefined;
|
176 |
/**
|
177 |
* The list of long day names, starting from Sunday, for use as requested via the dateFormat option.
|
178 |
*/
|
179 |
+
dayNames?: string[] | undefined;
|
180 |
/**
|
181 |
* The list of minimised day names, starting from Sunday, for use as column headers within the datepicker.
|
182 |
*/
|
183 |
+
dayNamesMin?: string[] | undefined;
|
184 |
/**
|
185 |
* The list of abbreviated day names, starting from Sunday, for use as requested via the dateFormat option.
|
186 |
*/
|
187 |
+
dayNamesShort?: string[] | undefined;
|
188 |
/**
|
189 |
* Set the date to highlight on first opening if the field is blank. Specify either an actual date via a Date object or as a string in the current dateFormat, or a number of days from today (e.g. +7) or a string of values and periods ('y' for years, 'm' for months, 'w' for weeks, 'd' for days, e.g. '+1m +7d'), or null for today.
|
190 |
* Multiple types supported:
|
196 |
/**
|
197 |
* Control the speed at which the datepicker appears, it may be a time in milliseconds or a string representing one of the three predefined speeds ("slow", "normal", "fast").
|
198 |
*/
|
199 |
+
duration?: string | undefined;
|
200 |
/**
|
201 |
* Set the first day of the week: Sunday is 0, Monday is 1, etc.
|
202 |
*/
|
203 |
+
firstDay?: number | undefined;
|
204 |
/**
|
205 |
* When true, the current day link moves to the currently selected date instead of today.
|
206 |
*/
|
207 |
+
gotoCurrent?: boolean | undefined;
|
208 |
/**
|
209 |
* Normally the previous and next links are disabled when not applicable (see the minDate and maxDate options). You can hide them altogether by setting this attribute to true.
|
210 |
*/
|
211 |
+
hideIfNoPrevNext?: boolean | undefined;
|
212 |
/**
|
213 |
* Whether the current language is drawn from right to left.
|
214 |
*/
|
215 |
+
isRTL?: boolean | undefined;
|
216 |
/**
|
217 |
* The maximum selectable date. When set to null, there is no maximum.
|
218 |
* Multiple types supported:
|
232 |
/**
|
233 |
* The list of full month names, for use as requested via the dateFormat option.
|
234 |
*/
|
235 |
+
monthNames?: string[] | undefined;
|
236 |
/**
|
237 |
* The list of abbreviated month names, as used in the month header on each datepicker and as requested via the dateFormat option.
|
238 |
*/
|
239 |
+
monthNamesShort?: string[] | undefined;
|
240 |
/**
|
241 |
* Whether the prevText and nextText options should be parsed as dates by the formatDate function, allowing them to display the target month names for example.
|
242 |
*/
|
243 |
+
navigationAsDateFormat?: boolean | undefined;
|
244 |
/**
|
245 |
* The text to display for the next month link. With the standard ThemeRoller styling, this value is replaced by an icon.
|
246 |
*/
|
247 |
+
nextText?: string | undefined;
|
248 |
/**
|
249 |
* The number of months to show at once.
|
250 |
* Multiple types supported:
|
255 |
/**
|
256 |
* Called when the datepicker moves to a new month and/or year. The function receives the selected year, month (1-12), and the datepicker instance as parameters. this refers to the associated input field.
|
257 |
*/
|
258 |
+
onChangeMonthYear?: ((year: number, month: number, inst: any) => void) | undefined;
|
259 |
/**
|
260 |
* Called when the datepicker is closed, whether or not a date is selected. The function receives the selected date as text ("" if none) and the datepicker instance as parameters. this refers to the associated input field.
|
261 |
*/
|
262 |
+
onClose?: ((dateText: string, inst: any) => void) | undefined;
|
263 |
/**
|
264 |
* Called when the datepicker is selected. The function receives the selected date as text and the datepicker instance as parameters. this refers to the associated input field.
|
265 |
*/
|
266 |
+
onSelect?: ((dateText: string, inst: any) => void) | undefined;
|
267 |
/**
|
268 |
* The text to display for the previous month link. With the standard ThemeRoller styling, this value is replaced by an icon.
|
269 |
*/
|
270 |
+
prevText?: string | undefined;
|
271 |
/**
|
272 |
* Whether days in other months shown before or after the current month are selectable. This only applies if the showOtherMonths option is set to true.
|
273 |
*/
|
274 |
+
selectOtherMonths?: boolean | undefined;
|
275 |
/**
|
276 |
* The cutoff year for determining the century for a date (used in conjunction with dateFormat 'y'). Any dates entered with a year value less than or equal to the cutoff year are considered to be in the current century, while those greater than it are deemed to be in the previous century.
|
277 |
* Multiple types supported:
|
282 |
/**
|
283 |
* The name of the animation used to show and hide the datepicker. Use "show" (the default), "slideDown", "fadeIn", any of the jQuery UI effects. Set to an empty string to disable animation.
|
284 |
*/
|
285 |
+
showAnim?: string | undefined;
|
286 |
/**
|
287 |
* Whether to display a button pane underneath the calendar. The button pane contains two buttons, a Today button that links to the current day, and a Done button that closes the datepicker. The buttons' text can be customized using the currentText and closeText options respectively.
|
288 |
*/
|
289 |
+
showButtonPanel?: boolean | undefined;
|
290 |
/**
|
291 |
* When displaying multiple months via the numberOfMonths option, the showCurrentAtPos option defines which position to display the current month in.
|
292 |
*/
|
293 |
+
showCurrentAtPos?: number | undefined;
|
294 |
/**
|
295 |
* Whether to show the month after the year in the header.
|
296 |
*/
|
297 |
+
showMonthAfterYear?: boolean | undefined;
|
298 |
/**
|
299 |
* When the datepicker should appear. The datepicker can appear when the field receives focus ("focus"), when a button is clicked ("button"), or when either event occurs ("both").
|
300 |
*/
|
301 |
+
showOn?: string | undefined;
|
302 |
/**
|
303 |
* If using one of the jQuery UI effects for the showAnim option, you can provide additional settings for that animation via this option.
|
304 |
*/
|
306 |
/**
|
307 |
* Whether to display dates in other months (non-selectable) at the start or end of the current month. To make these days selectable use the selectOtherMonths option.
|
308 |
*/
|
309 |
+
showOtherMonths?: boolean | undefined;
|
310 |
/**
|
311 |
* When true, a column is added to show the week of the year. The calculateWeek option determines how the week of the year is calculated. You may also want to change the firstDay option.
|
312 |
*/
|
313 |
+
showWeek?: boolean | undefined;
|
314 |
/**
|
315 |
* Set how many months to move when clicking the previous/next links.
|
316 |
*/
|
317 |
+
stepMonths?: number | undefined;
|
318 |
/**
|
319 |
* The text to display for the week of the year column heading. Use the showWeek option to display this column.
|
320 |
*/
|
321 |
+
weekHeader?: string | undefined;
|
322 |
/**
|
323 |
* The range of years displayed in the year drop-down: either relative to today's year ("-nn:+nn"), relative to the currently selected year ("c-nn:c+nn"), absolute ("nnnn:nnnn"), or combinations of these formats ("nnnn:-nn"). Note that this option only affects what appears in the drop-down, to restrict which dates may be selected use the minDate and/or maxDate options.
|
324 |
*/
|
325 |
+
yearRange?: string | undefined;
|
326 |
/**
|
327 |
* Additional text to display after the year in the month headers.
|
328 |
*/
|
329 |
+
yearSuffix?: string | undefined;
|
330 |
+
/**
|
331 |
+
* Set to true to automatically hide the datepicker.
|
332 |
+
*/
|
333 |
+
autohide?: boolean | undefined;
|
334 |
+
/**
|
335 |
+
* Set to date to automatically enddate the datepicker.
|
336 |
+
*/
|
337 |
+
endDate?: Date | undefined;
|
338 |
}
|
339 |
|
340 |
interface DatepickerFormatDateOptions {
|
341 |
+
dayNamesShort?: string[] | undefined;
|
342 |
+
dayNames?: string[] | undefined;
|
343 |
+
monthNamesShort?: string[] | undefined;
|
344 |
+
monthNames?: string[] | undefined;
|
345 |
}
|
346 |
|
347 |
interface Datepicker extends Widget, DatepickerOptions {
|
357 |
// Dialog //////////////////////////////////////////////////
|
358 |
|
359 |
interface DialogOptions extends DialogEvents {
|
360 |
+
autoOpen?: boolean | undefined;
|
361 |
+
buttons?: { [buttonText: string]: (event?: Event) => void } | DialogButtonOptions[] | undefined;
|
362 |
+
closeOnEscape?: boolean | undefined;
|
363 |
+
classes?: DialogClasses | undefined;
|
364 |
+
closeText?: string | undefined;
|
365 |
+
appendTo?: string | undefined;
|
366 |
+
dialogClass?: string | undefined;
|
367 |
+
disabled?: boolean | undefined;
|
368 |
+
draggable?: boolean | undefined;
|
369 |
+
height?: number | string | undefined;
|
370 |
+
hide?: boolean | number | string | DialogShowHideOptions | undefined;
|
371 |
+
maxHeight?: number | undefined;
|
372 |
+
maxWidth?: number | undefined;
|
373 |
+
minHeight?: number | undefined;
|
374 |
+
minWidth?: number | undefined;
|
375 |
+
modal?: boolean | undefined;
|
376 |
position?: any; // object, string or []
|
377 |
+
resizable?: boolean | undefined;
|
378 |
+
show?: boolean | number | string | DialogShowHideOptions | undefined;
|
379 |
+
stack?: boolean | undefined;
|
380 |
+
title?: string | undefined;
|
381 |
width?: any; // number or string
|
382 |
+
zIndex?: number | undefined;
|
383 |
|
384 |
+
open?: DialogEvent | undefined;
|
385 |
+
close?: DialogEvent | undefined;
|
386 |
+
}
|
387 |
+
|
388 |
+
interface DialogClasses {
|
389 |
+
"ui-dialog"?: string | undefined;
|
390 |
+
"ui-dialog-content"?: string | undefined;
|
391 |
+
"ui-dialog-dragging"?: string | undefined;
|
392 |
+
"ui-dialog-resizing"?: string | undefined;
|
393 |
+
"ui-dialog-buttons"?: string | undefined;
|
394 |
+
"ui-dialog-titlebar"?: string | undefined;
|
395 |
+
"ui-dialog-title"?: string | undefined;
|
396 |
+
"ui-dialog-titlebar-close"?: string | undefined;
|
397 |
+
"ui-dialog-buttonpane"?: string | undefined;
|
398 |
+
"ui-dialog-buttonset"?: string | undefined;
|
399 |
+
"ui-widget-overlay"?: string | undefined;
|
400 |
}
|
401 |
|
402 |
interface DialogButtonOptions {
|
403 |
icons?: any;
|
404 |
+
showText?: string | boolean | undefined;
|
405 |
+
text?: string | undefined;
|
406 |
+
click?: ((eventObject: JQueryEventObject) => any) | undefined;
|
407 |
[attr: string]: any; // attributes for the <button> element
|
408 |
}
|
409 |
|
410 |
interface DialogShowHideOptions {
|
411 |
effect: string;
|
412 |
+
delay?: number | undefined;
|
413 |
+
duration?: number | undefined;
|
414 |
+
easing?: string | undefined;
|
415 |
}
|
416 |
|
417 |
interface DialogUIParams {
|
418 |
}
|
419 |
|
420 |
interface DialogEvent {
|
421 |
+
(event: JQueryEventObject, ui: DialogUIParams): void;
|
422 |
}
|
423 |
|
424 |
interface DialogEvents {
|
425 |
+
beforeClose?: DialogEvent | undefined;
|
426 |
+
close?: DialogEvent | undefined;
|
427 |
+
create?: DialogEvent | undefined;
|
428 |
+
drag?: DialogEvent | undefined;
|
429 |
+
dragStart?: DialogEvent | undefined;
|
430 |
+
dragStop?: DialogEvent | undefined;
|
431 |
+
focus?: DialogEvent | undefined;
|
432 |
+
open?: DialogEvent | undefined;
|
433 |
+
resize?: DialogEvent | undefined;
|
434 |
+
resizeStart?: DialogEvent | undefined;
|
435 |
+
resizeStop?: DialogEvent | undefined;
|
436 |
}
|
437 |
|
438 |
interface Dialog extends Widget, DialogOptions {
|
444 |
interface DraggableEventUIParams {
|
445 |
helper: JQuery;
|
446 |
position: { top: number; left: number; };
|
447 |
+
originalPosition: { top: number; left: number; };
|
448 |
offset: { top: number; left: number; };
|
449 |
}
|
450 |
|
451 |
interface DraggableEvent {
|
452 |
+
(event: JQueryEventObject, ui: DraggableEventUIParams): void;
|
453 |
}
|
454 |
|
455 |
interface DraggableOptions extends DraggableEvents {
|
456 |
+
disabled?: boolean | undefined;
|
457 |
+
addClasses?: boolean | undefined;
|
458 |
appendTo?: any;
|
459 |
+
axis?: string | undefined;
|
460 |
+
cancel?: string | undefined;
|
461 |
+
classes?: DraggableClasses | undefined;
|
462 |
+
connectToSortable?: Element | Element[] | JQuery | string | undefined;
|
463 |
containment?: any;
|
464 |
+
cursor?: string | undefined;
|
465 |
cursorAt?: any;
|
466 |
+
delay?: number | undefined;
|
467 |
+
distance?: number | undefined;
|
468 |
+
grid?: number[] | undefined;
|
469 |
handle?: any;
|
470 |
helper?: any;
|
471 |
iframeFix?: any;
|
472 |
+
opacity?: number | undefined;
|
473 |
+
refreshPositions?: boolean | undefined;
|
474 |
revert?: any;
|
475 |
+
revertDuration?: number | undefined;
|
476 |
+
scope?: string | undefined;
|
477 |
+
scroll?: boolean | undefined;
|
478 |
+
scrollSensitivity?: number | undefined;
|
479 |
+
scrollSpeed?: number | undefined;
|
480 |
snap?: any;
|
481 |
+
snapMode?: string | undefined;
|
482 |
+
snapTolerance?: number | undefined;
|
483 |
+
stack?: string | undefined;
|
484 |
+
zIndex?: number | undefined;
|
485 |
+
}
|
486 |
+
|
487 |
+
interface DraggableClasses {
|
488 |
+
"ui-draggable"?: string | undefined;
|
489 |
+
"ui-draggable-disabled"?: string | undefined;
|
490 |
+
"ui-draggable-dragging"?: string | undefined;
|
491 |
+
"ui-draggable-handle"?: string | undefined;
|
492 |
}
|
493 |
|
494 |
interface DraggableEvents {
|
495 |
+
create?: DraggableEvent | undefined;
|
496 |
+
start?: DraggableEvent | undefined;
|
497 |
+
drag?: DraggableEvent | undefined;
|
498 |
+
stop?: DraggableEvent | undefined;
|
499 |
}
|
500 |
|
501 |
interface Draggable extends Widget, DraggableOptions, DraggableEvent {
|
512 |
}
|
513 |
|
514 |
interface DroppableEvent {
|
515 |
+
(event: JQueryEventObject, ui: DroppableEventUIParam): void;
|
516 |
}
|
517 |
|
518 |
interface DroppableOptions extends DroppableEvents {
|
|
|
519 |
accept?: any;
|
520 |
+
activeClass?: string | undefined;
|
521 |
+
addClasses?: boolean | undefined;
|
522 |
+
disabled?: boolean | undefined;
|
523 |
+
greedy?: boolean | undefined;
|
524 |
+
hoverClass?: string | undefined;
|
525 |
+
scope?: string | undefined;
|
526 |
+
tolerance?: string | undefined;
|
527 |
}
|
528 |
|
529 |
interface DroppableEvents {
|
530 |
+
create?: DroppableEvent | undefined;
|
531 |
+
activate?: DroppableEvent | undefined;
|
532 |
+
deactivate?: DroppableEvent | undefined;
|
533 |
+
over?: DroppableEvent | undefined;
|
534 |
+
out?: DroppableEvent | undefined;
|
535 |
+
drop?: DroppableEvent | undefined;
|
536 |
}
|
537 |
|
538 |
interface Droppable extends Widget, DroppableOptions {
|
541 |
// Menu //////////////////////////////////////////////////
|
542 |
|
543 |
interface MenuOptions extends MenuEvents {
|
544 |
+
disabled?: boolean | undefined;
|
545 |
icons?: any;
|
546 |
+
menus?: string | undefined;
|
547 |
position?: any; // TODO
|
548 |
+
role?: string | undefined;
|
549 |
}
|
550 |
|
551 |
interface MenuUIParams {
|
552 |
+
item?: JQuery | undefined;
|
553 |
}
|
554 |
|
555 |
interface MenuEvent {
|
556 |
+
(event: JQueryEventObject, ui: MenuUIParams): void;
|
557 |
}
|
558 |
|
559 |
interface MenuEvents {
|
560 |
+
blur?: MenuEvent | undefined;
|
561 |
+
create?: MenuEvent | undefined;
|
562 |
+
focus?: MenuEvent | undefined;
|
563 |
+
select?: MenuEvent | undefined;
|
564 |
}
|
565 |
|
566 |
interface Menu extends Widget, MenuOptions {
|
570 |
// Progressbar //////////////////////////////////////////////////
|
571 |
|
572 |
interface ProgressbarOptions extends ProgressbarEvents {
|
573 |
+
disabled?: boolean | undefined;
|
574 |
+
value?: number | boolean | undefined;
|
575 |
+
max?: number | undefined;
|
576 |
}
|
577 |
|
578 |
interface ProgressbarUIParams {
|
579 |
}
|
580 |
|
581 |
interface ProgressbarEvent {
|
582 |
+
(event: JQueryEventObject, ui: ProgressbarUIParams): void;
|
583 |
}
|
584 |
|
585 |
interface ProgressbarEvents {
|
586 |
+
change?: ProgressbarEvent | undefined;
|
587 |
+
complete?: ProgressbarEvent | undefined;
|
588 |
+
create?: ProgressbarEvent | undefined;
|
589 |
}
|
590 |
|
591 |
interface Progressbar extends Widget, ProgressbarOptions {
|
596 |
|
597 |
interface ResizableOptions extends ResizableEvents {
|
598 |
alsoResize?: any; // Selector, JQuery or Element
|
599 |
+
animate?: boolean | undefined;
|
600 |
animateDuration?: any; // number or string
|
601 |
+
animateEasing?: string | undefined;
|
602 |
aspectRatio?: any; // boolean or number
|
603 |
+
autoHide?: boolean | undefined;
|
604 |
+
cancel?: string | undefined;
|
605 |
containment?: any; // Selector, Element or string
|
606 |
+
delay?: number | undefined;
|
607 |
+
disabled?: boolean | undefined;
|
608 |
+
distance?: number | undefined;
|
609 |
+
ghost?: boolean | undefined;
|
610 |
grid?: any;
|
611 |
handles?: any; // string or object
|
612 |
+
helper?: string | undefined;
|
613 |
+
maxHeight?: number | undefined;
|
614 |
+
maxWidth?: number | undefined;
|
615 |
+
minHeight?: number | undefined;
|
616 |
+
minWidth?: number | undefined;
|
617 |
}
|
618 |
|
619 |
interface ResizableUIParams {
|
627 |
}
|
628 |
|
629 |
interface ResizableEvent {
|
630 |
+
(event: JQueryEventObject, ui: ResizableUIParams): void;
|
631 |
}
|
632 |
|
633 |
interface ResizableEvents {
|
634 |
+
resize?: ResizableEvent | undefined;
|
635 |
+
start?: ResizableEvent | undefined;
|
636 |
+
stop?: ResizableEvent | undefined;
|
637 |
+
create?: ResizableEvent | undefined;
|
638 |
}
|
639 |
|
640 |
interface Resizable extends Widget, ResizableOptions {
|
644 |
// Selectable //////////////////////////////////////////////////
|
645 |
|
646 |
interface SelectableOptions extends SelectableEvents {
|
647 |
+
autoRefresh?: boolean | undefined;
|
648 |
+
cancel?: string | undefined;
|
649 |
+
delay?: number | undefined;
|
650 |
+
disabled?: boolean | undefined;
|
651 |
+
distance?: number | undefined;
|
652 |
+
filter?: string | undefined;
|
653 |
+
tolerance?: string | undefined;
|
654 |
}
|
655 |
|
656 |
interface SelectableEvents {
|
657 |
+
selected? (event: JQueryEventObject, ui: { selected?: Element | undefined; }): void;
|
658 |
+
selecting? (event: JQueryEventObject, ui: { selecting?: Element | undefined; }): void;
|
659 |
+
start? (event: JQueryEventObject, ui: any): void;
|
660 |
+
stop? (event: JQueryEventObject, ui: any): void;
|
661 |
+
unselected? (event: JQueryEventObject, ui: { unselected: Element; }): void;
|
662 |
+
unselecting? (event: JQueryEventObject, ui: { unselecting: Element; }): void;
|
663 |
}
|
664 |
|
665 |
interface Selectable extends Widget, SelectableOptions {
|
666 |
}
|
667 |
|
668 |
+
// SelectMenu //////////////////////////////////////////////////
|
669 |
+
|
670 |
+
interface SelectMenuOptions extends SelectMenuEvents {
|
671 |
+
appendTo?: string | undefined;
|
672 |
+
classes?: SelectMenuClasses | undefined;
|
673 |
+
disabled?: boolean | undefined;
|
674 |
+
icons?: any;
|
675 |
+
position?: JQueryPositionOptions | undefined;
|
676 |
+
width?: number | undefined;
|
677 |
+
}
|
678 |
+
|
679 |
+
interface SelectMenuClasses {
|
680 |
+
"ui-selectmenu-button"?: string | undefined;
|
681 |
+
"ui-selectmenu-button-closed"?: string | undefined;
|
682 |
+
"ui-selectmenu-button-open"?: string | undefined;
|
683 |
+
"ui-selectmenu-text"?: string | undefined;
|
684 |
+
"ui-selectmenu-icon"?: string | undefined;
|
685 |
+
"ui-selectmenu-menu"?: string | undefined;
|
686 |
+
"ui-selectmenu-open"?: string | undefined;
|
687 |
+
"ui-selectmenu-optgroup"?: string | undefined;
|
688 |
+
}
|
689 |
+
|
690 |
+
interface SelectMenuUIParams {
|
691 |
+
item?: JQuery | undefined;
|
692 |
+
}
|
693 |
+
|
694 |
+
interface SelectMenuEvent {
|
695 |
+
(event: JQueryEventObject, ui: SelectMenuUIParams): void;
|
696 |
+
}
|
697 |
+
|
698 |
+
interface SelectMenuEvents {
|
699 |
+
change?: SelectMenuEvent | undefined;
|
700 |
+
close?: SelectMenuEvent | undefined;
|
701 |
+
create?: SelectMenuEvent | undefined;
|
702 |
+
focus?: SelectMenuEvent | undefined;
|
703 |
+
open?: SelectMenuEvent | undefined;
|
704 |
+
select?: SelectMenuEvent | undefined;
|
705 |
+
}
|
706 |
+
|
707 |
+
interface SelectMenu extends Widget, SelectMenuOptions {
|
708 |
+
}
|
709 |
+
|
710 |
// Slider //////////////////////////////////////////////////
|
711 |
|
712 |
interface SliderOptions extends SliderEvents {
|
713 |
animate?: any; // boolean, string or number
|
714 |
+
disabled?: boolean | undefined;
|
715 |
+
max?: number | undefined;
|
716 |
+
min?: number | undefined;
|
717 |
+
orientation?: string | undefined;
|
718 |
range?: any; // boolean or string
|
719 |
+
step?: number | undefined;
|
720 |
+
value?: number | undefined;
|
721 |
+
values?: number[] | undefined;
|
722 |
+
highlight?: boolean | undefined;
|
723 |
+
classes? : SliderClasses | undefined;
|
724 |
+
}
|
725 |
+
|
726 |
+
interface SliderClasses {
|
727 |
+
"ui-slider"?: string | undefined;
|
728 |
+
"ui-slider-horizontal"?: string | undefined;
|
729 |
+
"ui-slider-vertical"?: string | undefined;
|
730 |
+
"ui-slider-handle"?: string | undefined;
|
731 |
+
"ui-slider-range"?: string | undefined;
|
732 |
+
"ui-slider-range-min"?: string | undefined;
|
733 |
+
"ui-slider-range-max"?: string | undefined;
|
734 |
}
|
735 |
|
736 |
interface SliderUIParams {
|
737 |
+
handle?: JQuery | undefined;
|
738 |
+
value?: number | undefined;
|
739 |
+
values?: number[] | undefined;
|
740 |
}
|
741 |
|
742 |
interface SliderEvent {
|
743 |
+
(event: JQueryEventObject, ui: SliderUIParams): void;
|
744 |
}
|
745 |
|
746 |
interface SliderEvents {
|
747 |
+
change?: SliderEvent | undefined;
|
748 |
+
create?: SliderEvent | undefined;
|
749 |
+
slide?: SliderEvent | undefined;
|
750 |
+
start?: SliderEvent | undefined;
|
751 |
+
stop?: SliderEvent | undefined;
|
752 |
}
|
753 |
|
754 |
interface Slider extends Widget, SliderOptions {
|
759 |
|
760 |
interface SortableOptions extends SortableEvents {
|
761 |
appendTo?: any; // jQuery, Element, Selector or string
|
762 |
+
attribute?: string | undefined;
|
763 |
+
axis?: string | undefined;
|
764 |
cancel?: any; // Selector
|
765 |
connectWith?: any; // Selector
|
766 |
containment?: any; // Element, Selector or string
|
767 |
+
cursor?: string | undefined;
|
768 |
cursorAt?: any;
|
769 |
+
delay?: number | undefined;
|
770 |
+
disabled?: boolean | undefined;
|
771 |
+
distance?: number | undefined;
|
772 |
+
dropOnEmpty?: boolean | undefined;
|
773 |
+
forceHelperSize?: boolean | undefined;
|
774 |
+
forcePlaceholderSize?: boolean | undefined;
|
775 |
+
grid?: number[] | undefined;
|
776 |
+
helper?: string | ((event: JQueryEventObject, element: Sortable) => Element) | undefined;
|
777 |
handle?: any; // Selector or Element
|
778 |
items?: any; // Selector
|
779 |
+
opacity?: number | undefined;
|
780 |
+
placeholder?: string | undefined;
|
781 |
revert?: any; // boolean or number
|
782 |
+
scroll?: boolean | undefined;
|
783 |
+
scrollSensitivity?: number | undefined;
|
784 |
+
scrollSpeed?: number | undefined;
|
785 |
+
tolerance?: string | undefined;
|
786 |
+
zIndex?: number | undefined;
|
787 |
}
|
788 |
|
789 |
interface SortableUIParams {
|
801 |
}
|
802 |
|
803 |
interface SortableEvents {
|
804 |
+
activate?: SortableEvent | undefined;
|
805 |
+
beforeStop?: SortableEvent | undefined;
|
806 |
+
change?: SortableEvent | undefined;
|
807 |
+
deactivate?: SortableEvent | undefined;
|
808 |
+
out?: SortableEvent | undefined;
|
809 |
+
over?: SortableEvent | undefined;
|
810 |
+
receive?: SortableEvent | undefined;
|
811 |
+
remove?: SortableEvent | undefined;
|
812 |
+
sort?: SortableEvent | undefined;
|
813 |
+
start?: SortableEvent | undefined;
|
814 |
+
stop?: SortableEvent | undefined;
|
815 |
+
update?: SortableEvent | undefined;
|
816 |
}
|
817 |
|
818 |
interface Sortable extends Widget, SortableOptions, SortableEvents {
|
822 |
// Spinner //////////////////////////////////////////////////
|
823 |
|
824 |
interface SpinnerOptions extends SpinnerEvents {
|
825 |
+
culture?: string | undefined;
|
826 |
+
disabled?: boolean | undefined;
|
827 |
icons?: any;
|
828 |
incremental?: any; // boolean or ()
|
829 |
max?: any; // number or string
|
830 |
min?: any; // number or string
|
831 |
+
numberFormat?: string | undefined;
|
832 |
+
page?: number | undefined;
|
833 |
step?: any; // number or string
|
834 |
}
|
835 |
|
838 |
}
|
839 |
|
840 |
interface SpinnerEvent<T> {
|
841 |
+
(event: JQueryEventObject, ui: T): void;
|
842 |
}
|
843 |
|
844 |
interface SpinnerEvents {
|
845 |
+
change?: SpinnerEvent<{}> | undefined;
|
846 |
+
create?: SpinnerEvent<{}> | undefined;
|
847 |
+
spin?: SpinnerEvent<SpinnerUIParam> | undefined;
|
848 |
+
start?: SpinnerEvent<{}> | undefined;
|
849 |
+
stop?: SpinnerEvent<{}> | undefined;
|
850 |
}
|
851 |
|
852 |
interface Spinner extends Widget, SpinnerOptions {
|
857 |
|
858 |
interface TabsOptions extends TabsEvents {
|
859 |
active?: any; // boolean or number
|
860 |
+
classes?: TabClasses | undefined;
|
861 |
+
collapsible?: boolean | undefined;
|
862 |
disabled?: any; // boolean or []
|
863 |
+
event?: string | undefined;
|
864 |
+
heightStyle?: string | undefined;
|
865 |
hide?: any; // boolean, number, string or object
|
866 |
show?: any; // boolean, number, string or object
|
867 |
}
|
868 |
|
869 |
+
interface TabClasses {
|
870 |
+
"ui-tabs"?: string | undefined;
|
871 |
+
"ui-tabs-collapsible"?: string | undefined;
|
872 |
+
"ui-tabs-nav"?: string | undefined;
|
873 |
+
"ui-tabs-tab"?: string | undefined;
|
874 |
+
"ui-tabs-active"?: string | undefined;
|
875 |
+
"ui-tabs-loading"?: string | undefined;
|
876 |
+
"ui-tabs-anchor"?: string | undefined;
|
877 |
+
"ui-tabs-panel"?: string | undefined;
|
878 |
+
}
|
879 |
+
|
880 |
interface TabsActivationUIParams {
|
881 |
newTab: JQuery;
|
882 |
oldTab: JQuery;
|
897 |
}
|
898 |
|
899 |
interface TabsEvent<UI> {
|
900 |
+
(event: JQueryEventObject, ui: UI): void;
|
901 |
}
|
902 |
|
903 |
interface TabsEvents {
|
904 |
+
activate?: TabsEvent<TabsActivationUIParams> | undefined;
|
905 |
+
beforeActivate?: TabsEvent<TabsActivationUIParams> | undefined;
|
906 |
+
beforeLoad?: TabsEvent<TabsBeforeLoadUIParams> | undefined;
|
907 |
+
load?: TabsEvent<TabsCreateOrLoadUIParams> | undefined;
|
908 |
+
create?: TabsEvent<TabsCreateOrLoadUIParams> | undefined;
|
909 |
}
|
910 |
|
911 |
interface Tabs extends Widget, TabsOptions {
|
916 |
|
917 |
interface TooltipOptions extends TooltipEvents {
|
918 |
content?: any; // () or string
|
919 |
+
disabled?: boolean | undefined;
|
920 |
hide?: any; // boolean, number, string or object
|
921 |
+
items?: string|JQuery | undefined;
|
922 |
position?: any; // TODO
|
923 |
show?: any; // boolean, number, string or object
|
924 |
+
tooltipClass?: string | undefined; // deprecated in jQuery UI 1.12
|
925 |
+
track?: boolean | undefined;
|
926 |
+
classes?: {[key: string]: string} | undefined;
|
927 |
}
|
928 |
|
929 |
interface TooltipUIParams {
|
930 |
}
|
931 |
|
932 |
interface TooltipEvent {
|
933 |
+
(event: JQueryEventObject, ui: TooltipUIParams): void;
|
934 |
}
|
935 |
|
936 |
interface TooltipEvents {
|
937 |
+
close?: TooltipEvent | undefined;
|
938 |
+
open?: TooltipEvent | undefined;
|
939 |
}
|
940 |
|
941 |
interface Tooltip extends Widget, TooltipOptions {
|
946 |
|
947 |
interface EffectOptions {
|
948 |
effect: string;
|
949 |
+
easing?: string | undefined;
|
950 |
+
duration?: number | undefined;
|
951 |
complete: Function;
|
952 |
}
|
953 |
|
954 |
interface BlindEffect {
|
955 |
+
direction?: string | undefined;
|
956 |
}
|
957 |
|
958 |
interface BounceEffect {
|
959 |
+
distance?: number | undefined;
|
960 |
+
times?: number | undefined;
|
961 |
}
|
962 |
|
963 |
interface ClipEffect {
|
964 |
+
direction?: number | undefined;
|
965 |
}
|
966 |
|
967 |
interface DropEffect {
|
968 |
+
direction?: number | undefined;
|
969 |
}
|
970 |
|
971 |
interface ExplodeEffect {
|
972 |
+
pieces?: number | undefined;
|
973 |
}
|
974 |
|
975 |
interface FadeEffect { }
|
976 |
|
977 |
interface FoldEffect {
|
978 |
size?: any;
|
979 |
+
horizFirst?: boolean | undefined;
|
980 |
}
|
981 |
|
982 |
interface HighlightEffect {
|
983 |
+
color?: string | undefined;
|
984 |
}
|
985 |
|
986 |
interface PuffEffect {
|
987 |
+
percent?: number | undefined;
|
988 |
}
|
989 |
|
990 |
interface PulsateEffect {
|
991 |
+
times?: number | undefined;
|
992 |
}
|
993 |
|
994 |
interface ScaleEffect {
|
995 |
+
direction?: string | undefined;
|
996 |
+
origin?: string[] | undefined;
|
997 |
+
percent?: number | undefined;
|
998 |
+
scale?: string | undefined;
|
999 |
}
|
1000 |
|
1001 |
interface ShakeEffect {
|
1002 |
+
direction?: string | undefined;
|
1003 |
+
distance?: number | undefined;
|
1004 |
+
times?: number | undefined;
|
1005 |
}
|
1006 |
|
1007 |
interface SizeEffect {
|
1008 |
to?: any;
|
1009 |
+
origin?: string[] | undefined;
|
1010 |
+
scale?: string | undefined;
|
1011 |
}
|
1012 |
|
1013 |
interface SlideEffect {
|
1014 |
+
direction?: string | undefined;
|
1015 |
+
distance?: number | undefined;
|
1016 |
}
|
1017 |
|
1018 |
interface TransferEffect {
|
1019 |
+
className?: string | undefined;
|
1020 |
+
to?: string | undefined;
|
1021 |
}
|
1022 |
|
1023 |
interface JQueryPositionOptions {
|
1024 |
+
my?: string | undefined;
|
1025 |
+
at?: string | undefined;
|
1026 |
of?: any;
|
1027 |
+
collision?: string | undefined;
|
1028 |
+
using?: Function | undefined;
|
1029 |
within?: any;
|
1030 |
}
|
1031 |
|
1033 |
// UI //////////////////////////////////////////////////
|
1034 |
|
1035 |
interface MouseOptions {
|
1036 |
+
cancel?: string | undefined;
|
1037 |
+
delay?: number | undefined;
|
1038 |
+
distance?: number | undefined;
|
1039 |
}
|
1040 |
|
1041 |
interface KeyCode {
|
1078 |
keyCode: KeyCode;
|
1079 |
menu: Menu;
|
1080 |
progressbar: Progressbar;
|
1081 |
+
selectmenu: SelectMenu;
|
1082 |
slider: Slider;
|
1083 |
spinner: Spinner;
|
1084 |
tabs: Tabs;
|
1090 |
// Widget //////////////////////////////////////////////////
|
1091 |
|
1092 |
interface WidgetOptions {
|
1093 |
+
disabled?: boolean | undefined;
|
1094 |
hide?: any;
|
1095 |
show?: any;
|
1096 |
}
|
1097 |
|
1098 |
+
interface WidgetCommonProperties {
|
1099 |
+
element: JQuery;
|
1100 |
+
defaultElement : string;
|
1101 |
+
document: Document;
|
1102 |
+
namespace: string;
|
1103 |
+
uuid: string;
|
1104 |
+
widgetEventPrefix: string;
|
1105 |
+
widgetFullName: string;
|
1106 |
+
window: Window;
|
1107 |
+
}
|
1108 |
+
|
1109 |
interface Widget {
|
1110 |
(methodName: string): JQuery;
|
1111 |
(options: WidgetOptions): JQuery;
|
1114 |
(optionLiteral: string, options: WidgetOptions): any;
|
1115 |
(optionLiteral: string, optionName: string, optionValue: any): JQuery;
|
1116 |
|
1117 |
+
<T>(name: string, prototype: T & ThisType<T & WidgetCommonProperties>): JQuery;
|
1118 |
+
<T>(name: string, base: Function, prototype: T & ThisType<T & WidgetCommonProperties> ): JQuery;
|
1119 |
}
|
1120 |
|
1121 |
////////////////////////////////////////////////////////////////////////////////////////////////////
|
1429 |
* @param optionName 'buttonText'
|
1430 |
*/
|
1431 |
datepicker(methodName: 'option', optionName: 'buttonText'): string;
|
1432 |
+
|
1433 |
+
/**
|
1434 |
+
* Get the autohide option, after initialization
|
1435 |
+
*
|
1436 |
+
* @param methodName 'option'
|
1437 |
+
* @param optionName 'autohide'
|
1438 |
+
*/
|
1439 |
+
datepicker(methodName: 'option', optionName: 'autohide'): boolean;
|
1440 |
+
|
1441 |
+
|
1442 |
+
/**
|
1443 |
+
* Get the endDate after initialization
|
1444 |
+
*
|
1445 |
+
* @param methodName 'option'
|
1446 |
+
* @param optionName 'endDate'
|
1447 |
+
*/
|
1448 |
+
datepicker(methodName: 'option', optionName: 'endDate'): Date;
|
1449 |
/**
|
1450 |
* Set the buttonText option, after initialization
|
1451 |
*
|
1811 |
selectable(optionLiteral: string, options: JQueryUI.SelectableOptions): any;
|
1812 |
selectable(optionLiteral: string, optionName: string, optionValue: any): JQuery;
|
1813 |
|
1814 |
+
selectmenu(): JQuery;
|
1815 |
+
selectmenu(methodName: 'close'): JQuery;
|
1816 |
+
selectmenu(methodName: 'destroy'): JQuery;
|
1817 |
+
selectmenu(methodName: 'disable'): JQuery;
|
1818 |
+
selectmenu(methodName: 'enable'): JQuery;
|
1819 |
+
selectmenu(methodName: 'instance'): any;
|
1820 |
+
selectmenu(methodName: 'menuWidget'): JQuery;
|
1821 |
+
selectmenu(methodName: 'open'): JQuery;
|
1822 |
+
selectmenu(methodName: 'refresh'): JQuery;
|
1823 |
+
selectmenu(methodName: 'widget'): JQuery;
|
1824 |
+
selectmenu(methodName: string): JQuery;
|
1825 |
+
selectmenu(options: JQueryUI.SelectMenuOptions): JQuery;
|
1826 |
+
selectmenu(optionLiteral: string, optionName: string): any;
|
1827 |
+
selectmenu(optionLiteral: string, options: JQueryUI.SelectMenuOptions): any;
|
1828 |
+
selectmenu(optionLiteral: string, optionName: string, optionValue: any): JQuery;
|
1829 |
+
|
1830 |
slider(): JQuery;
|
1831 |
slider(methodName: 'destroy'): void;
|
1832 |
slider(methodName: 'disable'): void;
|
1852 |
sortable(methodName: 'disable'): void;
|
1853 |
sortable(methodName: 'enable'): void;
|
1854 |
sortable(methodName: 'widget'): JQuery;
|
1855 |
+
sortable(methodName: 'toArray', options?: { attribute?: string | undefined; }): string[];
|
1856 |
sortable(methodName: string): JQuery;
|
1857 |
sortable(options: JQueryUI.SortableOptions): JQuery;
|
1858 |
sortable(optionLiteral: string, optionName: string): any;
|
1859 |
+
sortable(methodName: 'serialize', options?: { key?: string | undefined; attribute?: string | undefined; expression?: RegExp | undefined }): string;
|
1860 |
sortable(optionLiteral: string, options: JQueryUI.SortableOptions): any;
|
1861 |
sortable(optionLiteral: string, optionName: string, optionValue: any): JQuery;
|
1862 |
|
1880 |
tabs(): JQuery;
|
1881 |
tabs(methodName: 'destroy'): void;
|
1882 |
tabs(methodName: 'disable'): void;
|
1883 |
+
tabs(methodName: 'disable', index: number): void;
|
1884 |
tabs(methodName: 'enable'): void;
|
1885 |
+
tabs(methodName: 'enable', index: number): void;
|
1886 |
tabs(methodName: 'load', index: number): void;
|
1887 |
tabs(methodName: 'refresh'): void;
|
1888 |
tabs(methodName: 'widget'): JQuery;
|
1889 |
+
tabs(methodName: 'select', index: number): JQuery;
|
1890 |
tabs(methodName: string): JQuery;
|
1891 |
tabs(options: JQueryUI.TabsOptions): JQuery;
|
1892 |
tabs(optionLiteral: string, optionName: string): any;
|
1907 |
tooltip(optionLiteral: string, optionName: string, optionValue: any): JQuery;
|
1908 |
|
1909 |
|
1910 |
+
addClass(classNames: string, speed?: number, callback?: Function): this;
|
1911 |
+
addClass(classNames: string, speed?: string, callback?: Function): this;
|
1912 |
+
addClass(classNames: string, speed?: number, easing?: string, callback?: Function): this;
|
1913 |
+
addClass(classNames: string, speed?: string, easing?: string, callback?: Function): this;
|
1914 |
|
1915 |
+
removeClass(classNames: string, speed?: number, callback?: Function): this;
|
1916 |
+
removeClass(classNames: string, speed?: string, callback?: Function): this;
|
1917 |
+
removeClass(classNames: string, speed?: number, easing?: string, callback?: Function): this;
|
1918 |
+
removeClass(classNames: string, speed?: string, easing?: string, callback?: Function): this;
|
1919 |
|
1920 |
+
switchClass(removeClassName: string, addClassName: string, duration?: number, easing?: string, complete?: Function): this;
|
1921 |
+
switchClass(removeClassName: string, addClassName: string, duration?: string, easing?: string, complete?: Function): this;
|
1922 |
|
1923 |
+
toggleClass(className: string, duration?: number, easing?: string, complete?: Function): this;
|
1924 |
+
toggleClass(className: string, duration?: string, easing?: string, complete?: Function): this;
|
1925 |
+
toggleClass(className: string, aswitch?: boolean, duration?: number, easing?: string, complete?: Function): this;
|
1926 |
+
toggleClass(className: string, aswitch?: boolean, duration?: string, easing?: string, complete?: Function): this;
|
1927 |
|
1928 |
+
effect(options: any): this;
|
1929 |
+
effect(effect: string, options?: any, duration?: number, complete?: Function): this;
|
1930 |
+
effect(effect: string, options?: any, duration?: string, complete?: Function): this;
|
1931 |
|
1932 |
+
hide(options: any): this;
|
1933 |
+
hide(effect: string, options?: any, duration?: number, complete?: Function): this;
|
1934 |
+
hide(effect: string, options?: any, duration?: string, complete?: Function): this;
|
1935 |
|
1936 |
+
show(options: any): this;
|
1937 |
+
show(effect: string, options?: any, duration?: number, complete?: Function): this;
|
1938 |
+
show(effect: string, options?: any, duration?: string, complete?: Function): this;
|
1939 |
|
1940 |
+
toggle(options: any): this;
|
1941 |
+
toggle(effect: string, options?: any, duration?: number, complete?: Function): this;
|
1942 |
+
toggle(effect: string, options?: any, duration?: string, complete?: Function): this;
|
1943 |
|
1944 |
position(options: JQueryUI.JQueryPositionOptions): JQuery;
|
1945 |
|
1949 |
uniqueId(): JQuery;
|
1950 |
removeUniqueId(): JQuery;
|
1951 |
scrollParent(): JQuery;
|
1952 |
+
zIndex(): number;
|
1953 |
zIndex(zIndex: number): JQuery;
|
1954 |
|
1955 |
widget: JQueryUI.Widget;
|
js/menu-editor.js
CHANGED
@@ -49,6 +49,8 @@
|
|
49 |
* @property {string} wsEditorData.setTestConfigurationNonce
|
50 |
* @property {string} wsEditorData.testAccessNonce
|
51 |
*
|
|
|
|
|
52 |
* @property {boolean} wsEditorData.isDemoMode
|
53 |
* @property {boolean} wsEditorData.isMasterMode
|
54 |
*/
|
@@ -191,6 +193,11 @@ var itemTemplates = {
|
|
191 |
}
|
192 |
};
|
193 |
|
|
|
|
|
|
|
|
|
|
|
194 |
/**
|
195 |
* Set an input field to a value. The only difference from jQuery.val() is that
|
196 |
* setting a checkbox to true/false will check/clear it.
|
@@ -241,25 +248,25 @@ function randomMenuId(prefix, size){
|
|
241 |
AmeEditorApi.randomMenuId = randomMenuId;
|
242 |
|
243 |
function outputWpMenu(menu){
|
244 |
-
|
245 |
-
var menuBox = $('#ws_menu_box');
|
246 |
|
247 |
//Remove the current menu data
|
248 |
-
|
249 |
-
$('#ws_submenu_box').empty();
|
250 |
|
251 |
//Display the new menu
|
252 |
-
|
253 |
-
|
|
|
254 |
if (!menuCopy.hasOwnProperty(filename)){
|
255 |
continue;
|
256 |
}
|
257 |
-
|
258 |
-
i++;
|
259 |
}
|
260 |
|
261 |
//Automatically select the first top-level menu
|
262 |
-
|
|
|
|
|
263 |
}
|
264 |
|
265 |
/**
|
@@ -311,70 +318,779 @@ function loadMenuConfiguration(adminMenu) {
|
|
311 |
$(document).trigger('menuConfigurationLoaded.adminMenuEditor', adminMenu);
|
312 |
}
|
313 |
|
314 |
-
|
315 |
-
|
316 |
-
|
317 |
-
|
318 |
-
|
319 |
-
|
320 |
-
|
321 |
-
|
322 |
-
|
323 |
-
|
324 |
-
function outputTopMenu(menu, afterNode){
|
325 |
-
//Create the menu widget
|
326 |
-
var menu_obj = buildMenuItem(menu, true);
|
327 |
|
328 |
-
|
329 |
-
|
330 |
-
|
331 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
332 |
}
|
333 |
|
334 |
-
|
335 |
-
|
336 |
-
|
337 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
338 |
|
339 |
-
|
340 |
-
|
341 |
-
|
|
|
|
|
|
|
|
|
342 |
|
343 |
-
|
344 |
-
|
345 |
-
|
346 |
-
|
347 |
-
|
|
|
|
|
348 |
|
349 |
-
|
350 |
-
|
351 |
-
|
352 |
-
|
353 |
-
|
354 |
-
|
355 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
356 |
|
357 |
-
|
358 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
359 |
}
|
360 |
|
361 |
-
|
362 |
-
|
363 |
-
|
364 |
-
|
365 |
-
|
366 |
-
|
367 |
-
|
368 |
-
|
369 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
370 |
}
|
371 |
-
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
372 |
}
|
373 |
|
374 |
-
|
375 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
376 |
|
377 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
378 |
}
|
379 |
|
380 |
/**
|
@@ -437,22 +1153,32 @@ function buildMenuItem(itemData, isTopLevel) {
|
|
437 |
}),
|
438 |
|
439 |
'drop' : (function(event, ui){
|
440 |
-
|
441 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
442 |
|
443 |
-
|
444 |
-
var submenu = $('#' + item.data('submenu_id'));
|
445 |
-
submenu.append(new_item);
|
446 |
|
447 |
if ( !event.ctrlKey ) {
|
448 |
-
ui.draggable
|
449 |
}
|
450 |
|
451 |
-
updateItemEditor(
|
452 |
|
453 |
//Moving an item can change aggregate menu permissions. Update the UI accordingly.
|
454 |
updateParentAccessUi(submenu);
|
455 |
-
|
|
|
|
|
456 |
})
|
457 |
});
|
458 |
}
|
@@ -1051,7 +1777,7 @@ function buildEditboxFields(fieldContainer, entry, isTopLevel){
|
|
1051 |
//noinspection JSUnusedLocalSymbols
|
1052 |
function buildEditboxField(entry, field_name, field_settings){
|
1053 |
//Build a form field of the appropriate type
|
1054 |
-
var inputBox
|
1055 |
var basicTextField = '<input type="text" class="ws_field_value">';
|
1056 |
//noinspection FallthroughInSwitchStatementJS
|
1057 |
switch(field_settings.type){
|
@@ -1142,7 +1868,7 @@ function buildEditboxField(entry, field_name, field_settings){
|
|
1142 |
.attr('src', wsEditorData.imagesUrl + '/transparent16.png')
|
1143 |
).data('field_name', field_name);
|
1144 |
|
1145 |
-
var visible
|
1146 |
if (typeof field_settings.visible === 'function') {
|
1147 |
visible = field_settings.visible(entry, field_name);
|
1148 |
} else {
|
@@ -1543,25 +2269,36 @@ AmeEditorApi.forEachMenuItem = function(callback, skipSeparators) {
|
|
1543 |
/**
|
1544 |
* Select the first menu item that has the specified URL.
|
1545 |
*
|
1546 |
-
* @param {string}
|
1547 |
* @param {string} url
|
1548 |
-
* @param {
|
1549 |
* @returns {JQuery}
|
1550 |
*/
|
1551 |
-
AmeEditorApi.selectMenuItemByUrl = function(
|
1552 |
if (typeof expandProperties === 'undefined') {
|
1553 |
expandProperties = null;
|
1554 |
}
|
1555 |
|
1556 |
-
|
1557 |
-
if (
|
1558 |
-
|
|
|
|
|
|
|
|
|
1559 |
}
|
1560 |
|
1561 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1562 |
box.find('.ws_container')
|
1563 |
.filter(function() {
|
1564 |
-
|
1565 |
return (itemUrl === url);
|
1566 |
})
|
1567 |
.first();
|
@@ -1570,7 +2307,7 @@ AmeEditorApi.selectMenuItemByUrl = function(boxSelector, url, expandProperties)
|
|
1570 |
AmeEditorApi.selectItem(containerNode);
|
1571 |
|
1572 |
if (expandProperties !== null) {
|
1573 |
-
|
1574 |
if (expandLink.hasClass('ws_edit_link_expanded') !== expandProperties) {
|
1575 |
expandLink.trigger('click');
|
1576 |
}
|
@@ -1655,9 +2392,9 @@ function readMenuTreeState(){
|
|
1655 |
|
1656 |
/**
|
1657 |
* Losslessly compress the admin menu configuration.
|
1658 |
-
*
|
1659 |
* This is a JS port of the ameMenu::compress() function defined in /includes/menu.php.
|
1660 |
-
*
|
1661 |
* @param {Object} adminMenu
|
1662 |
* @returns {Object}
|
1663 |
*/
|
@@ -1963,7 +2700,7 @@ function actorCanAccessMenu(menuItem, actor) {
|
|
1963 |
//By default, any actor that has the required cap has access to the menu.
|
1964 |
//Users can override this on a per-menu basis.
|
1965 |
var requiredCap = getFieldValue(menuItem, 'access_level', '< Error: access_level is missing! >');
|
1966 |
-
var actorHasAccess
|
1967 |
if (menuItem.grant_access.hasOwnProperty(actor)) {
|
1968 |
actorHasAccess = menuItem.grant_access[actor];
|
1969 |
} else {
|
@@ -2052,6 +2789,9 @@ var generalComponentVisibility = {};
|
|
2052 |
var isDomReadyDone = false;
|
2053 |
|
2054 |
function ameOnDomReady() {
|
|
|
|
|
|
|
2055 |
isDomReadyDone = true;
|
2056 |
|
2057 |
//Some editor elements are only available in the Pro version.
|
@@ -2081,11 +2821,9 @@ function ameOnDomReady() {
|
|
2081 |
/***************************************************************************
|
2082 |
Event handlers for editor widgets
|
2083 |
***************************************************************************/
|
2084 |
-
|
2085 |
-
submenuBox = $('#ws_submenu_box'),
|
2086 |
-
submenuDropZone = submenuBox.closest('.ws_main_container').find('.ws_dropzone');
|
2087 |
|
2088 |
-
|
2089 |
|
2090 |
/**
|
2091 |
* Select a menu item and show its submenu.
|
@@ -2093,32 +2831,7 @@ function ameOnDomReady() {
|
|
2093 |
* @param {JQuery|HTMLElement} container Menu container node.
|
2094 |
*/
|
2095 |
function selectItem(container) {
|
2096 |
-
|
2097 |
-
//The menu item is already selected.
|
2098 |
-
return;
|
2099 |
-
}
|
2100 |
-
|
2101 |
-
//Highlight the active item and un-highlight the previous one
|
2102 |
-
container.addClass('ws_active');
|
2103 |
-
container.siblings('.ws_active').removeClass('ws_active');
|
2104 |
-
if (container.hasClass('ws_menu')) {
|
2105 |
-
//Show/hide the appropriate submenu
|
2106 |
-
if ( currentVisibleSubmenu ){
|
2107 |
-
currentVisibleSubmenu.hide();
|
2108 |
-
}
|
2109 |
-
currentVisibleSubmenu = $('#' + container.data('submenu_id')).show();
|
2110 |
-
|
2111 |
-
updateSubmenuBoxHeight(container);
|
2112 |
-
|
2113 |
-
currentVisibleSubmenu.closest('.ws_main_container')
|
2114 |
-
.find('.ws_toolbar .ws_delete_menu_button')
|
2115 |
-
.toggleClass('ws_button_disabled', !canDeleteItem(getSelectedSubmenuItem()));
|
2116 |
-
}
|
2117 |
-
|
2118 |
-
//Make the "delete" button appear disabled if you can't delete this item.
|
2119 |
-
container.closest('.ws_main_container')
|
2120 |
-
.find('.ws_toolbar .ws_delete_menu_button')
|
2121 |
-
.toggleClass('ws_button_disabled', !canDeleteItem(container));
|
2122 |
}
|
2123 |
AmeEditorApi.selectItem = selectItem;
|
2124 |
|
@@ -2128,6 +2841,15 @@ function ameOnDomReady() {
|
|
2128 |
}));
|
2129 |
|
2130 |
function updateSubmenuBoxHeight(selectedMenu) {
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
2131 |
//Make the submenu box tall enough to reach the selected item.
|
2132 |
//This prevents the menu tip (if any) from floating in empty space.
|
2133 |
if (selectedMenu.hasClass('ws_menu_separator')) {
|
@@ -2253,7 +2975,8 @@ function ameOnDomReady() {
|
|
2253 |
field.removeClass('ws_input_default');
|
2254 |
}
|
2255 |
|
2256 |
-
|
|
|
2257 |
value = null; //null = use default.
|
2258 |
}
|
2259 |
|
@@ -2324,22 +3047,26 @@ function ameOnDomReady() {
|
|
2324 |
* @param containerNode
|
2325 |
* @param {String|Object.<String, Boolean>} actor
|
2326 |
* @param {Boolean} [allowAccess]
|
|
|
2327 |
*/
|
2328 |
-
function setActorAccessForTreeAndUpdateUi(containerNode, actor, allowAccess) {
|
2329 |
setActorAccess(containerNode, actor, allowAccess);
|
2330 |
|
2331 |
//Apply the same permissions to sub-menus.
|
2332 |
-
|
2333 |
-
if (subMenuId
|
2334 |
$('.ws_item', '#' + subMenuId).each(function() {
|
2335 |
-
|
2336 |
-
|
2337 |
-
updateItemEditor(node);
|
2338 |
});
|
2339 |
}
|
2340 |
|
2341 |
updateItemEditor(containerNode);
|
2342 |
-
|
|
|
|
|
|
|
|
|
2343 |
}
|
2344 |
|
2345 |
/**
|
@@ -2502,13 +3229,15 @@ function ameOnDomReady() {
|
|
2502 |
|
2503 |
var isDropdownBeingHidden = false, isSuggestionClick = false;
|
2504 |
|
|
|
|
|
2505 |
//Show/hide the capability drop-down list when the trigger button is clicked
|
2506 |
$('#ws_trigger_capability_dropdown').on('mousedown click', onDropdownTriggerClicked);
|
2507 |
menuEditorNode.on('mousedown click', '.ws_cap_selector_trigger', onDropdownTriggerClicked);
|
2508 |
|
2509 |
function onDropdownTriggerClicked(event){
|
2510 |
/* jshint validthis:true */
|
2511 |
-
var inputBox
|
2512 |
var button = $(this);
|
2513 |
|
2514 |
var isInAccessEditor = false;
|
@@ -2516,7 +3245,7 @@ function ameOnDomReady() {
|
|
2516 |
|
2517 |
//Find the input associated with the button that was clicked.
|
2518 |
if ( button.attr('id') === 'ws_trigger_capability_dropdown' ) {
|
2519 |
-
inputBox = $
|
2520 |
isInAccessEditor = true;
|
2521 |
} else {
|
2522 |
inputBox = button.closest('.ws_edit_field').find('.ws_field_value').first();
|
@@ -2567,14 +3296,14 @@ function ameOnDomReady() {
|
|
2567 |
} else {
|
2568 |
currentDropdownOwnerMenu = currentDropdownOwner.closest('.ws_container').data('menu_item');
|
2569 |
}
|
2570 |
-
|
2571 |
capSelectorDropdown.focus();
|
2572 |
|
2573 |
capSuggestionFeature.show();
|
2574 |
}
|
2575 |
|
2576 |
//Also show it when the user presses the down arrow in the input field (doesn't work in Opera).
|
2577 |
-
$
|
2578 |
if ( event.which === 40 ){
|
2579 |
$('#ws_trigger_capability_dropdown').trigger('click');
|
2580 |
}
|
@@ -3511,92 +4240,106 @@ function ameOnDomReady() {
|
|
3511 |
return false;
|
3512 |
});
|
3513 |
|
|
|
|
|
3514 |
/*************************************************************************
|
3515 |
Menu toolbar buttons
|
3516 |
*************************************************************************/
|
3517 |
function getSelectedMenu() {
|
3518 |
-
return
|
3519 |
}
|
3520 |
AmeEditorApi.getSelectedMenu = getSelectedMenu;
|
3521 |
|
3522 |
//Show/Hide menu
|
3523 |
-
|
3524 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
3525 |
|
3526 |
-
|
3527 |
-
var selection = getSelectedMenu();
|
3528 |
-
if (!selection.length) {
|
3529 |
-
return;
|
3530 |
}
|
3531 |
-
|
3532 |
-
toggleItemHiddenFlag(selection);
|
3533 |
-
});
|
3534 |
|
3535 |
//Hide a menu and deny access.
|
3536 |
-
menuEditorNode.
|
3537 |
-
|
3538 |
-
|
3539 |
-
|
3540 |
-
|
3541 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
3542 |
|
3543 |
-
|
3544 |
-
|
3545 |
-
|
3546 |
-
|
3547 |
-
|
3548 |
-
|
3549 |
-
|
3550 |
|
3551 |
-
|
3552 |
-
|
3553 |
-
|
3554 |
-
|
3555 |
-
|
3556 |
-
|
3557 |
-
|
3558 |
-
|
3559 |
-
|
3560 |
-
|
3561 |
-
|
3562 |
-
|
3563 |
-
|
3564 |
-
|
3565 |
-
|
3566 |
-
|
3567 |
-
|
3568 |
-
|
3569 |
-
|
3570 |
-
|
3571 |
-
|
3572 |
-
|
3573 |
-
|
3574 |
-
|
3575 |
-
|
3576 |
-
|
3577 |
-
|
3578 |
-
|
3579 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
3580 |
}
|
3581 |
-
} else {
|
3582 |
-
//Give back access to the roles and users who previously had access.
|
3583 |
-
//Careful, don't give access to roles that no longer exist.
|
3584 |
-
var actorsWhoHadAccess = _.get(item, 'had_access_before_hiding', []) || [];
|
3585 |
-
actorsWhoHadAccess = _.intersection(actorsWhoHadAccess, validActors);
|
3586 |
-
|
3587 |
-
newAccess = _.assign(objectFillKeys(actorsWhoHadAccess, true), keepEnabled);
|
3588 |
-
delete item.had_access_before_hiding;
|
3589 |
-
}
|
3590 |
|
3591 |
-
|
3592 |
-
|
3593 |
-
|
3594 |
|
3595 |
-
|
3596 |
-
|
3597 |
-
|
|
|
3598 |
}
|
3599 |
-
|
3600 |
|
3601 |
//Delete error dialog. It shows up when the user tries to delete one of the default menus.
|
3602 |
var menuDeletionDialog = $('#ws-ame-menu-deletion-error').dialog({
|
@@ -3675,47 +4418,13 @@ function ameOnDomReady() {
|
|
3675 |
$('#ws_hide_menu_from_everyone').on('click', function() {
|
3676 |
menuDeletionCallback('all');
|
3677 |
});
|
3678 |
-
$('#ws_hide_menu_except_current_user').on('click', function() {
|
3679 |
menuDeletionCallback('except_current_user');
|
3680 |
});
|
3681 |
-
$('#ws_hide_menu_except_administrator').on('click', function() {
|
3682 |
menuDeletionCallback('except_administrator');
|
3683 |
});
|
3684 |
|
3685 |
-
/**
|
3686 |
-
* Check if it's possible to delete a menu item.
|
3687 |
-
*
|
3688 |
-
* @param {JQuery} containerNode
|
3689 |
-
* @returns {boolean}
|
3690 |
-
*/
|
3691 |
-
function canDeleteItem(containerNode) {
|
3692 |
-
if (!containerNode || (containerNode.length < 1)) {
|
3693 |
-
return false;
|
3694 |
-
}
|
3695 |
-
|
3696 |
-
var menuItem = containerNode.data('menu_item');
|
3697 |
-
var isDefaultItem =
|
3698 |
-
( menuItem.template_id !== '')
|
3699 |
-
&& ( menuItem.template_id !== wsEditorData.unclickableTemplateId)
|
3700 |
-
&& ( menuItem.template_id !== wsEditorData.embeddedPageTemplateId)
|
3701 |
-
&& (!menuItem.separator);
|
3702 |
-
|
3703 |
-
var otherCopiesExist = false;
|
3704 |
-
if (isDefaultItem) {
|
3705 |
-
//Check if there are any other menus with the same template ID.
|
3706 |
-
$('#ws_menu_editor').find('.ws_container').each(function() {
|
3707 |
-
var otherItem = $(this).data('menu_item');
|
3708 |
-
if ((menuItem !== otherItem) && (menuItem.template_id === otherItem.template_id)) {
|
3709 |
-
otherCopiesExist = true;
|
3710 |
-
return false;
|
3711 |
-
}
|
3712 |
-
return true;
|
3713 |
-
});
|
3714 |
-
}
|
3715 |
-
|
3716 |
-
return (!isDefaultItem || otherCopiesExist);
|
3717 |
-
}
|
3718 |
-
|
3719 |
/**
|
3720 |
* Attempt to delete a menu item. Will check if the item can actually be deleted and ask the user for confirmation.
|
3721 |
* UI callback.
|
@@ -3738,8 +4447,8 @@ function ameOnDomReady() {
|
|
3738 |
|
3739 |
//Different versions get slightly different options because only the Pro version has
|
3740 |
//role-specific permissions.
|
3741 |
-
$
|
3742 |
-
$
|
3743 |
|
3744 |
menuDeletionDialog.dialog('open');
|
3745 |
|
@@ -3748,17 +4457,12 @@ function ameOnDomReady() {
|
|
3748 |
}
|
3749 |
|
3750 |
if (shouldDelete) {
|
3751 |
-
|
3752 |
-
var submenuId = selection.data('submenu_id');
|
3753 |
-
if (submenuId) {
|
3754 |
-
$('#' + submenuId).remove();
|
3755 |
-
}
|
3756 |
-
var parentSubmenu = selection.closest('.ws_submenu');
|
3757 |
|
3758 |
//Delete the menu.
|
3759 |
-
|
3760 |
|
3761 |
-
if (parentSubmenu) {
|
3762 |
//Refresh permissions UI for this menu's parent (if any).
|
3763 |
updateParentAccessUi(parentSubmenu);
|
3764 |
}
|
@@ -3766,172 +4470,204 @@ function ameOnDomReady() {
|
|
3766 |
}
|
3767 |
|
3768 |
//Delete menu
|
3769 |
-
|
3770 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
3771 |
|
3772 |
-
|
3773 |
-
var selection = getSelectedMenu();
|
3774 |
-
if (!selection.length) {
|
3775 |
-
return;
|
3776 |
}
|
3777 |
-
|
3778 |
-
tryDeleteItem(selection);
|
3779 |
-
});
|
3780 |
|
3781 |
//Copy menu
|
3782 |
-
|
3783 |
-
|
3784 |
|
3785 |
-
|
3786 |
-
|
3787 |
-
|
3788 |
-
|
3789 |
-
|
|
|
|
|
|
|
|
|
3790 |
|
3791 |
-
|
3792 |
-
|
3793 |
-
|
|
|
3794 |
|
3795 |
//Cut menu
|
3796 |
-
|
3797 |
-
|
3798 |
|
3799 |
-
|
3800 |
-
|
3801 |
-
|
3802 |
-
|
3803 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
3804 |
|
3805 |
-
|
3806 |
-
|
3807 |
|
3808 |
-
|
3809 |
-
|
3810 |
-
selection.remove();
|
3811 |
-
});
|
3812 |
|
3813 |
-
|
3814 |
-
|
3815 |
-
//The user shouldn't need to worry about giving separators a unique filename.
|
3816 |
-
if (menu.separator) {
|
3817 |
-
menu.defaults.file = randomMenuId('separator_');
|
3818 |
}
|
|
|
3819 |
|
3820 |
-
|
3821 |
-
|
3822 |
-
|
3823 |
-
|
3824 |
-
|
3825 |
-
|
3826 |
-
|
3827 |
-
|
3828 |
-
|
3829 |
-
|
3830 |
-
|
|
|
3831 |
|
3832 |
-
|
3833 |
-
|
3834 |
-
|
3835 |
-
|
3836 |
-
return outputTopMenu(menu);
|
3837 |
-
}
|
3838 |
-
}
|
3839 |
|
3840 |
-
|
3841 |
-
event.preventDefault();
|
3842 |
|
3843 |
-
|
3844 |
-
|
3845 |
-
return;
|
3846 |
}
|
3847 |
-
|
3848 |
-
var menu = $.extend(true, {}, menu_in_clipboard);
|
3849 |
-
|
3850 |
-
//Get the selected menu
|
3851 |
-
var selection = $('#ws_menu_box').find('.ws_active');
|
3852 |
-
//Paste the menu after the selection.
|
3853 |
-
pasteMenu(menu, (selection.length > 0) ? selection : null);
|
3854 |
-
});
|
3855 |
|
3856 |
//New menu
|
3857 |
-
|
3858 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
3859 |
|
3860 |
-
|
3861 |
|
3862 |
-
|
3863 |
-
|
3864 |
-
|
3865 |
-
|
3866 |
-
|
3867 |
-
|
3868 |
-
|
3869 |
-
|
3870 |
-
|
3871 |
-
menu.defaults = $.extend(true, {}, itemTemplates.getDefaults(''));
|
3872 |
|
3873 |
-
|
3874 |
-
|
3875 |
-
|
3876 |
-
|
3877 |
|
3878 |
-
|
3879 |
-
|
3880 |
-
|
|
|
|
|
|
|
3881 |
|
3882 |
-
|
3883 |
-
|
3884 |
-
|
3885 |
|
3886 |
-
|
3887 |
-
|
3888 |
-
|
|
|
3889 |
|
3890 |
-
|
3891 |
-
|
3892 |
-
|
3893 |
-
|
3894 |
-
|
3895 |
-
|
3896 |
-
|
3897 |
-
|
3898 |
-
|
3899 |
-
|
3900 |
-
|
3901 |
-
|
3902 |
-
|
3903 |
-
|
3904 |
}
|
3905 |
-
});
|
3906 |
|
3907 |
-
|
3908 |
-
|
3909 |
-
|
3910 |
-
|
3911 |
-
|
3912 |
-
|
3913 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
3914 |
}
|
3915 |
-
|
3916 |
|
3917 |
//Toggle all menus for the currently selected actor
|
3918 |
-
|
3919 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
3920 |
|
3921 |
-
|
3922 |
-
|
3923 |
-
|
3924 |
-
}
|
3925 |
|
3926 |
-
|
3927 |
-
//Look at the first menu's permissions and set everything to the opposite.
|
3928 |
-
var allow = ! actorCanAccessMenu(topMenuNodes.eq(0).data('menu_item'), actorSelectorWidget.selectedActor);
|
3929 |
|
3930 |
-
|
3931 |
-
|
3932 |
-
|
3933 |
-
|
3934 |
-
|
|
|
3935 |
|
3936 |
//Copy all menu permissions from one role to another.
|
3937 |
var copyPermissionsDialog = $('#ws-ame-copy-permissions-dialog').dialog({
|
@@ -3944,38 +4680,44 @@ function ameOnDomReady() {
|
|
3944 |
var sourceActorList = $('#ame-copy-source-actor'), destinationActorList = $('#ame-copy-destination-actor');
|
3945 |
|
3946 |
//The "Copy permissions" toolbar button.
|
3947 |
-
|
3948 |
-
|
3949 |
-
|
3950 |
-
|
3951 |
-
|
3952 |
-
|
3953 |
-
|
3954 |
-
|
3955 |
-
|
3956 |
-
|
3957 |
-
|
3958 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
3959 |
});
|
3960 |
-
sourceActorList.append(option);
|
3961 |
-
destinationActorList.append(option.clone());
|
3962 |
-
});
|
3963 |
|
3964 |
-
|
3965 |
-
|
3966 |
-
|
3967 |
-
|
3968 |
|
3969 |
-
|
3970 |
-
|
3971 |
-
|
3972 |
-
|
3973 |
-
|
3974 |
-
|
3975 |
-
|
3976 |
|
3977 |
-
|
3978 |
-
|
|
|
3979 |
|
3980 |
//Actually copy the permissions when the user click the confirmation button.
|
3981 |
var copyConfirmationButton = $('#ws-ame-confirm-copy-permissions');
|
@@ -3989,15 +4731,11 @@ function ameOnDomReady() {
|
|
3989 |
}
|
3990 |
|
3991 |
//Iterate over all menu items and copy the permissions from one actor to the other.
|
3992 |
-
|
3993 |
-
allMenuNodes.each(function() {
|
3994 |
-
var node = $(this);
|
3995 |
-
var menuItem = node.data('menu_item');
|
3996 |
-
|
3997 |
//Only change permissions when they don't match. This ensures we won't unnecessarily overwrite default
|
3998 |
//permissions and bloat the configuration with extra grant_access entries.
|
3999 |
-
|
4000 |
-
|
4001 |
if (sourceAccess !== destinationAccess) {
|
4002 |
setActorAccess(node, destinationActor, sourceAccess);
|
4003 |
//Note: In theory, we could also look at the default permissions for destinationActor and
|
@@ -4030,30 +4768,44 @@ function ameOnDomReady() {
|
|
4030 |
});
|
4031 |
|
4032 |
//Sort menus in ascending or descending order.
|
4033 |
-
menuEditorNode.
|
4034 |
-
|
4035 |
-
|
4036 |
-
|
4037 |
-
|
4038 |
-
|
|
|
|
|
|
|
|
|
|
|
4039 |
|
4040 |
-
|
4041 |
-
|
4042 |
-
|
4043 |
|
4044 |
-
|
4045 |
-
|
4046 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
4047 |
|
4048 |
-
|
4049 |
-
//Moving the first item would change the parent menu URL (WP always links it to the first item),
|
4050 |
-
//which can be unexpected and confusing. The user can always move the first item manually.
|
4051 |
-
if (menuBox.is('#ws_menu_box')) {
|
4052 |
-
$('#ws_submenu_box').find('.ws_submenu').each(function() {
|
4053 |
-
sortMenuItems($(this), direction, true);
|
4054 |
-
});
|
4055 |
}
|
4056 |
-
|
4057 |
|
4058 |
/**
|
4059 |
* Sort menu items by title.
|
@@ -4105,173 +4857,28 @@ function ameOnDomReady() {
|
|
4105 |
}
|
4106 |
|
4107 |
//Toggle the second row of toolbar buttons.
|
4108 |
-
|
4109 |
-
|
4110 |
-
|
4111 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
4112 |
}
|
4113 |
-
|
4114 |
|
4115 |
|
4116 |
/*************************************************************************
|
4117 |
Item toolbar buttons
|
4118 |
*************************************************************************/
|
4119 |
function getSelectedSubmenuItem() {
|
4120 |
-
return
|
4121 |
-
}
|
4122 |
-
|
4123 |
-
//Show/Hide item
|
4124 |
-
$('#ws_hide_item').on('click', function (event) {
|
4125 |
-
event.preventDefault();
|
4126 |
-
|
4127 |
-
//Get the selected item
|
4128 |
-
var selection = getSelectedSubmenuItem();
|
4129 |
-
if (!selection.length) {
|
4130 |
-
return;
|
4131 |
-
}
|
4132 |
-
|
4133 |
-
//Mark the item as hidden/visible
|
4134 |
-
toggleItemHiddenFlag(selection);
|
4135 |
-
});
|
4136 |
-
|
4137 |
-
//Delete item
|
4138 |
-
$('#ws_delete_item').on('click', function (event) {
|
4139 |
-
event.preventDefault();
|
4140 |
-
|
4141 |
-
var selection = getSelectedSubmenuItem();
|
4142 |
-
if (!selection.length) {
|
4143 |
-
return;
|
4144 |
-
}
|
4145 |
-
|
4146 |
-
tryDeleteItem(selection);
|
4147 |
-
});
|
4148 |
-
|
4149 |
-
//Copy item
|
4150 |
-
$('#ws_copy_item').on('click', function (event) {
|
4151 |
-
event.preventDefault();
|
4152 |
-
|
4153 |
-
//Get the selected item
|
4154 |
-
var selection = getSelectedSubmenuItem();
|
4155 |
-
if (!selection.length) {
|
4156 |
-
return;
|
4157 |
-
}
|
4158 |
-
|
4159 |
-
//Store a copy of item state in the clipboard
|
4160 |
-
menu_in_clipboard = readItemState(selection);
|
4161 |
-
});
|
4162 |
-
|
4163 |
-
//Cut item
|
4164 |
-
$('#ws_cut_item').on('click', function (event) {
|
4165 |
-
event.preventDefault();
|
4166 |
-
|
4167 |
-
//Get the selected item
|
4168 |
-
var selection = getSelectedSubmenuItem();
|
4169 |
-
if (!selection.length) {
|
4170 |
-
return;
|
4171 |
-
}
|
4172 |
-
|
4173 |
-
//Store a copy of item state in the clipboard
|
4174 |
-
menu_in_clipboard = readItemState(selection);
|
4175 |
-
|
4176 |
-
var submenu = selection.parent();
|
4177 |
-
//Remove the original item
|
4178 |
-
selection.remove();
|
4179 |
-
updateParentAccessUi(submenu);
|
4180 |
-
});
|
4181 |
-
|
4182 |
-
//Paste item
|
4183 |
-
function pasteItem(item, targetSubmenu) {
|
4184 |
-
//We're pasting this item into a sub-menu, so it can't have a sub-menu of its own.
|
4185 |
-
//Instead, any sub-menu items belonging to this item will be pasted after the item.
|
4186 |
-
var newItems = [];
|
4187 |
-
for (var file in item.items) {
|
4188 |
-
if (item.items.hasOwnProperty(file)) {
|
4189 |
-
newItems.push(buildMenuItem(item.items[file], false));
|
4190 |
-
}
|
4191 |
-
}
|
4192 |
-
item.items = [];
|
4193 |
-
|
4194 |
-
newItems.unshift(buildMenuItem(item, false));
|
4195 |
-
|
4196 |
-
//Paste into the currently visible submenu by default.
|
4197 |
-
targetSubmenu = targetSubmenu || $('#ws_submenu_box').find('.ws_submenu:visible');
|
4198 |
-
//Get the selected menu
|
4199 |
-
var selection = targetSubmenu.find('.ws_active');
|
4200 |
-
for(var i = 0; i < newItems.length; i++) {
|
4201 |
-
if (selection.length > 0) {
|
4202 |
-
//If an item is selected add the pasted items after it
|
4203 |
-
selection.after(newItems[i]);
|
4204 |
-
} else {
|
4205 |
-
//Otherwise add the pasted items at the end
|
4206 |
-
targetSubmenu.append(newItems[i]);
|
4207 |
-
}
|
4208 |
-
|
4209 |
-
updateItemEditor(newItems[i]);
|
4210 |
-
newItems[i].show();
|
4211 |
-
}
|
4212 |
-
|
4213 |
-
updateParentAccessUi(targetSubmenu);
|
4214 |
}
|
4215 |
|
4216 |
-
|
4217 |
-
event.preventDefault();
|
4218 |
-
|
4219 |
-
//Check if anything has been copied/cut
|
4220 |
-
if (!menu_in_clipboard) {
|
4221 |
-
return;
|
4222 |
-
}
|
4223 |
-
|
4224 |
-
//You can only add separators to submenus in the Pro version.
|
4225 |
-
if ( menu_in_clipboard.separator && !wsEditorData.wsMenuEditorPro ) {
|
4226 |
-
return;
|
4227 |
-
}
|
4228 |
-
|
4229 |
-
//Paste it.
|
4230 |
-
var item = $.extend(true, {}, menu_in_clipboard);
|
4231 |
-
pasteItem(item);
|
4232 |
-
});
|
4233 |
-
|
4234 |
-
//New item
|
4235 |
-
$('#ws_new_item').on('click', function (event) {
|
4236 |
-
event.preventDefault();
|
4237 |
-
|
4238 |
-
if ($('.ws_submenu:visible').length < 1) {
|
4239 |
-
return; //Abort if no submenu visible
|
4240 |
-
}
|
4241 |
-
|
4242 |
-
ws_paste_count++;
|
4243 |
-
|
4244 |
-
var entry = $.extend(true, {}, wsEditorData.blankMenuItem, {
|
4245 |
-
custom: true,
|
4246 |
-
template_id : '',
|
4247 |
-
menu_title : 'Custom Item ' + ws_paste_count,
|
4248 |
-
file : randomMenuId(),
|
4249 |
-
items: []
|
4250 |
-
});
|
4251 |
-
entry.defaults = $.extend(true, {}, itemTemplates.getDefaults(''));
|
4252 |
-
|
4253 |
-
//Make it accessible to only the currently selected actor.
|
4254 |
-
if (actorSelectorWidget.selectedActor !== null) {
|
4255 |
-
denyAccessForAllExcept(entry, actorSelectorWidget.selectedActor);
|
4256 |
-
}
|
4257 |
-
|
4258 |
-
var menu = buildMenuItem(entry);
|
4259 |
-
|
4260 |
-
//Insert the item into the currently open submenu.
|
4261 |
-
var visibleSubmenu = $('#ws_submenu_box').find('.ws_submenu:visible');
|
4262 |
-
var selection = visibleSubmenu.find('.ws_active');
|
4263 |
-
if (selection.length > 0) {
|
4264 |
-
selection.after(menu);
|
4265 |
-
} else {
|
4266 |
-
visibleSubmenu.append(menu);
|
4267 |
-
}
|
4268 |
-
updateItemEditor(menu);
|
4269 |
-
|
4270 |
-
//The items's editbox is always open
|
4271 |
-
menu.find('.ws_edit_link').trigger('click');
|
4272 |
-
|
4273 |
-
updateParentAccessUi(menu);
|
4274 |
-
});
|
4275 |
|
4276 |
//==============================================
|
4277 |
// Main buttons
|
@@ -4337,6 +4944,8 @@ function ameOnDomReady() {
|
|
4337 |
$('#ws_data_length').val(data.length);
|
4338 |
$('#ws_selected_actor').val(actorSelectorWidget.selectedActor === null ? '' : actorSelectorWidget.selectedActor);
|
4339 |
|
|
|
|
|
4340 |
var selectedMenu = getSelectedMenu();
|
4341 |
if (selectedMenu.length > 0) {
|
4342 |
$('#ws_selected_menu_url').val(AmeEditorApi.getItemDisplayUrl(selectedMenu.data('menu_item')));
|
@@ -4464,6 +5073,7 @@ function ameOnDomReady() {
|
|
4464 |
closeText: ' ',
|
4465 |
modal: true
|
4466 |
});
|
|
|
4467 |
|
4468 |
$('#ws_cancel_import').on('click', function(){
|
4469 |
$('#import_dialog').dialog('close');
|
@@ -4472,7 +5082,7 @@ function ameOnDomReady() {
|
|
4472 |
$('#ws_import_menu').on('click', function(){
|
4473 |
$('#import_progress_notice, #import_progress_notice2, #import_complete_notice, #ws_import_error').hide();
|
4474 |
$('#ws_import_panel').show();
|
4475 |
-
$
|
4476 |
//The "Upload" button is disabled until the user selects a file
|
4477 |
$('#ws_start_import').attr('disabled', 'disabled');
|
4478 |
|
@@ -4490,7 +5100,7 @@ function ameOnDomReady() {
|
|
4490 |
function handleUnexpectedImportError(xhr, errorMessage) {
|
4491 |
//The server-side code didn't catch this error, so it's probably something serious
|
4492 |
//and retrying won't work.
|
4493 |
-
$
|
4494 |
$('#ws_import_panel').hide();
|
4495 |
|
4496 |
//Display error information.
|
@@ -4501,7 +5111,7 @@ function ameOnDomReady() {
|
|
4501 |
}
|
4502 |
|
4503 |
//AJAXify the upload form
|
4504 |
-
$
|
4505 |
dataType : 'json',
|
4506 |
beforeSubmit: function(formData) {
|
4507 |
|
@@ -4539,7 +5149,7 @@ function ameOnDomReady() {
|
|
4539 |
if ( typeof data.error !== 'undefined' ){
|
4540 |
alert(data.error);
|
4541 |
//Let the user try again
|
4542 |
-
$
|
4543 |
importDialog.find('.hide-when-uploading').show();
|
4544 |
}
|
4545 |
|
@@ -4578,53 +5188,35 @@ function ameOnDomReady() {
|
|
4578 |
}),
|
4579 |
|
4580 |
'drop' : (function(event, ui){
|
4581 |
-
|
4582 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
4583 |
|
4584 |
//If the item was originally a top level menu, also move its original submenu items.
|
4585 |
-
if (getFieldValue(droppedItemData, 'parent') === null) {
|
4586 |
-
|
4587 |
-
|
4588 |
nearbyItems.each(function() {
|
4589 |
-
|
4590 |
submenuItem = containerNode.data('menu_item');
|
4591 |
|
4592 |
//Was this item originally a child of the dragged menu?
|
4593 |
if (getFieldValue(submenuItem, 'parent') === droppedItemFile) {
|
4594 |
-
pasteItem(submenuItem, newItemNodes.submenu);
|
4595 |
if ( !event.ctrlKey ) {
|
4596 |
-
|
4597 |
}
|
4598 |
}
|
4599 |
});
|
4600 |
}
|
4601 |
|
4602 |
if ( !event.ctrlKey ) {
|
4603 |
-
ui.draggable
|
4604 |
-
}
|
4605 |
-
})
|
4606 |
-
});
|
4607 |
-
|
4608 |
-
//...and to drag top level menus to a sub-menu.
|
4609 |
-
submenuBox.closest('.ws_main_container').droppable({
|
4610 |
-
'hoverClass' : 'ws_top_to_submenu_drop_hover',
|
4611 |
-
|
4612 |
-
'accept' : (function(thing){
|
4613 |
-
var visibleSubmenu = $('#ws_submenu_box').find('.ws_submenu:visible');
|
4614 |
-
return (
|
4615 |
-
//Accept top-level menus
|
4616 |
-
thing.hasClass('ws_menu') &&
|
4617 |
-
|
4618 |
-
//Prevent users from dropping a menu on its own sub-menu.
|
4619 |
-
(visibleSubmenu.attr('id') !== thing.data('submenu_id'))
|
4620 |
-
);
|
4621 |
-
}),
|
4622 |
-
|
4623 |
-
'drop' : (function(event, ui){
|
4624 |
-
var droppedItemData = readItemState(ui.draggable);
|
4625 |
-
pasteItem(droppedItemData);
|
4626 |
-
if ( !event.ctrlKey ) {
|
4627 |
-
ui.draggable.remove();
|
4628 |
}
|
4629 |
})
|
4630 |
});
|
@@ -5098,7 +5690,7 @@ var domCheckIntervalId = window.setInterval(function () {
|
|
5098 |
domCheckAttempts++;
|
5099 |
|
5100 |
if ($ && $.isReady) {
|
5101 |
-
|
5102 |
ameOnDomReady();
|
5103 |
}
|
5104 |
}, 1000);
|
49 |
* @property {string} wsEditorData.setTestConfigurationNonce
|
50 |
* @property {string} wsEditorData.testAccessNonce
|
51 |
*
|
52 |
+
* @property {string|null} wsEditorData.deepNestingEnabled
|
53 |
+
*
|
54 |
* @property {boolean} wsEditorData.isDemoMode
|
55 |
* @property {boolean} wsEditorData.isMasterMode
|
56 |
*/
|
193 |
}
|
194 |
};
|
195 |
|
196 |
+
/**
|
197 |
+
* @type {AmeMenuPresenter}
|
198 |
+
*/
|
199 |
+
let menuPresenter;
|
200 |
+
|
201 |
/**
|
202 |
* Set an input field to a value. The only difference from jQuery.val() is that
|
203 |
* setting a checkbox to true/false will check/clear it.
|
248 |
AmeEditorApi.randomMenuId = randomMenuId;
|
249 |
|
250 |
function outputWpMenu(menu){
|
251 |
+
const menuCopy = $.extend(true, {}, menu);
|
|
|
252 |
|
253 |
//Remove the current menu data
|
254 |
+
menuPresenter.clear();
|
|
|
255 |
|
256 |
//Display the new menu
|
257 |
+
const firstColumn = menuPresenter.getColumnImmediate(1);
|
258 |
+
const itemList = firstColumn.getVisibleItemList();
|
259 |
+
for (let filename in menuCopy){
|
260 |
if (!menuCopy.hasOwnProperty(filename)){
|
261 |
continue;
|
262 |
}
|
263 |
+
firstColumn.outputItem(menuCopy[filename], null, itemList);
|
|
|
264 |
}
|
265 |
|
266 |
//Automatically select the first top-level menu
|
267 |
+
if (itemList) {
|
268 |
+
itemList.find('.ws_menu:first').trigger('click');
|
269 |
+
}
|
270 |
}
|
271 |
|
272 |
/**
|
318 |
$(document).trigger('menuConfigurationLoaded.adminMenuEditor', adminMenu);
|
319 |
}
|
320 |
|
321 |
+
/**
|
322 |
+
* Check if it's possible to delete a menu item.
|
323 |
+
*
|
324 |
+
* @param {JQuery} containerNode
|
325 |
+
* @returns {boolean}
|
326 |
+
*/
|
327 |
+
function canDeleteItem(containerNode) {
|
328 |
+
if (!containerNode || (containerNode.length < 1)) {
|
329 |
+
return false;
|
330 |
+
}
|
|
|
|
|
|
|
331 |
|
332 |
+
var menuItem = containerNode.data('menu_item');
|
333 |
+
var isDefaultItem =
|
334 |
+
( menuItem.template_id !== '')
|
335 |
+
&& ( menuItem.template_id !== wsEditorData.unclickableTemplateId)
|
336 |
+
&& ( menuItem.template_id !== wsEditorData.embeddedPageTemplateId)
|
337 |
+
&& (!menuItem.separator);
|
338 |
+
|
339 |
+
var otherCopiesExist = false;
|
340 |
+
if (isDefaultItem) {
|
341 |
+
//Check if there are any other menus with the same template ID.
|
342 |
+
$('#ws_menu_editor').find('.ws_container').each(function() {
|
343 |
+
var otherItem = $(this).data('menu_item');
|
344 |
+
if ((menuItem !== otherItem) && (menuItem.template_id === otherItem.template_id)) {
|
345 |
+
otherCopiesExist = true;
|
346 |
+
return false;
|
347 |
+
}
|
348 |
+
return true;
|
349 |
+
});
|
350 |
+
}
|
351 |
+
|
352 |
+
return (!isDefaultItem || otherCopiesExist);
|
353 |
}
|
354 |
|
355 |
+
/**
|
356 |
+
* Get or create the submenu container of a menu item.
|
357 |
+
*
|
358 |
+
* @param {JQuery|null} container
|
359 |
+
* @param {AmeEditorColumn} [nextColumn]
|
360 |
+
* @return {JQuery|null}
|
361 |
+
*/
|
362 |
+
function getSubmenuOf(container, nextColumn) {
|
363 |
+
if (!container || (container.length < 1)) {
|
364 |
+
return null;
|
365 |
+
}
|
366 |
|
367 |
+
const submenuId = container.data('submenu_id');
|
368 |
+
if (submenuId) {
|
369 |
+
let $submenu = $('#' + submenuId).first();
|
370 |
+
if ($submenu.length > 0) {
|
371 |
+
return $submenu;
|
372 |
+
}
|
373 |
+
}
|
374 |
|
375 |
+
//If a submenu doesn't exist yet, create it in the next column.
|
376 |
+
if (nextColumn) {
|
377 |
+
return createSubmenuFor(container, nextColumn);
|
378 |
+
} else {
|
379 |
+
return null;
|
380 |
+
}
|
381 |
+
}
|
382 |
|
383 |
+
/**
|
384 |
+
* Create a submenu container for a menu item.
|
385 |
+
* @param {JQuery} container
|
386 |
+
* @param {AmeEditorColumn} nextColumn
|
387 |
+
* @return {JQuery}
|
388 |
+
*/
|
389 |
+
function createSubmenuFor(container, nextColumn) {
|
390 |
+
const $submenu = nextColumn.buildSubmenuContainer(container.attr('id'));
|
391 |
+
nextColumn.appendSubmenuContainer($submenu);
|
392 |
+
container.data('submenu_id', $submenu.attr('id'))
|
393 |
+
return $submenu;
|
394 |
+
}
|
395 |
+
|
396 |
+
/**
|
397 |
+
* @param {Number} level
|
398 |
+
* @param {JQuery|null} predecessor
|
399 |
+
* @param {JQuery|null} [container]
|
400 |
+
* @param {Function} [getNextColumn]
|
401 |
+
* @constructor
|
402 |
+
*/
|
403 |
+
function AmeEditorColumn(level, predecessor, container, getNextColumn) {
|
404 |
+
const self = this;
|
405 |
+
|
406 |
+
this.level = level;
|
407 |
+
this.usesSubmenuContainers = (this.level > 1);
|
408 |
+
|
409 |
+
let isNewContainer = false;
|
410 |
+
if ((typeof container === 'undefined') || (container === null)) {
|
411 |
+
isNewContainer = true;
|
412 |
+
container = $('#ame-submenu-column-template').first().clone();
|
413 |
+
container.attr('id', '');
|
414 |
+
container.find('.ws_box').first().attr('id', '');
|
415 |
+
container.show().insertAfter(predecessor);
|
416 |
+
}
|
417 |
+
container.data('ame-menu-level', level);
|
418 |
+
container.addClass('ame-editor-column-' + level);
|
419 |
+
|
420 |
+
this.container = container;
|
421 |
+
this.menuBox = container.find('.ws_box').first();
|
422 |
+
this.dropZone = container.children('.ws_dropzone').first();
|
423 |
+
this.visibleItemList = null;
|
424 |
+
|
425 |
+
if (!this.usesSubmenuContainers) {
|
426 |
+
this.menuBox.addClass('ame-visible-item-list');
|
427 |
+
}
|
428 |
+
|
429 |
+
if (typeof getNextColumn !== 'undefined') {
|
430 |
+
this.getNextColumn = getNextColumn;
|
431 |
+
} else {
|
432 |
+
this.getNextColumn = function(callback) {
|
433 |
+
callback(null);
|
434 |
+
};
|
435 |
+
}
|
436 |
|
437 |
+
this.container.children('.ws_toolbar').on('click', '.ws_button', function() {
|
438 |
+
const $button = $(this);
|
439 |
+
let buttonAction = $button.data('ame-button-action') || 'unknown';
|
440 |
+
let selectedItem = self.getSelectedItem();
|
441 |
+
self.container.trigger(
|
442 |
+
'adminMenuEditor:action-' + buttonAction,
|
443 |
+
[(selectedItem.length > 0) ? selectedItem : null, self, $button]
|
444 |
+
);
|
445 |
+
return false;
|
446 |
+
});
|
447 |
+
|
448 |
+
if (isNewContainer && (this.dropZone.length > 0)) {
|
449 |
+
this.container.closest('.ws_main_container').droppable({
|
450 |
+
'hoverClass' : 'ws_top_to_submenu_drop_hover',
|
451 |
+
|
452 |
+
'accept' : (function(thing) {
|
453 |
+
const visibleSubmenu = self.getVisibleItemList();
|
454 |
+
if (!visibleSubmenu || (visibleSubmenu.length < 1)) {
|
455 |
+
return false; //Can't drop anything on a non-existent submenu.
|
456 |
+
}
|
457 |
+
|
458 |
+
function isParentOf(menuItem, something) {
|
459 |
+
const parent = getParentMenuNode(something)
|
460 |
+
if (menuItem.is(parent)) {
|
461 |
+
return true;
|
462 |
+
} else if (parent.length > 0) {
|
463 |
+
return isParentOf(menuItem, parent);
|
464 |
+
}
|
465 |
+
return false;
|
466 |
+
}
|
467 |
+
|
468 |
+
const thingContainer = thing.closest('.ws_main_container');
|
469 |
+
return (
|
470 |
+
//Accept only menus from other columns.
|
471 |
+
!self.container.is(thingContainer) &&
|
472 |
+
|
473 |
+
//Prevent users from dropping a parent menu on one of its own sub-menus.
|
474 |
+
!isParentOf(thing, visibleSubmenu)
|
475 |
+
);
|
476 |
+
}),
|
477 |
+
|
478 |
+
'drop' : (function(event, ui){
|
479 |
+
const droppedItemData = readItemState(ui.draggable);
|
480 |
+
self.pasteItem(droppedItemData, null);
|
481 |
+
if ( !event.ctrlKey ) {
|
482 |
+
self.destroyItem(ui.draggable);
|
483 |
+
}
|
484 |
+
})
|
485 |
+
});
|
486 |
+
}
|
487 |
}
|
488 |
|
489 |
+
/**
|
490 |
+
* Create editor widgets for a menu item and its submenus.
|
491 |
+
*
|
492 |
+
* @param {Object} itemData An object containing menu data.
|
493 |
+
* @param {JQuery|null} [afterNode] Insert the widget after this node. If it's NULL, the widget
|
494 |
+
* will be added to the end fo the list.
|
495 |
+
* @param {JQuery} [itemList] The container where to insert the widget. Defaults to the currently
|
496 |
+
* visible item list. For columns that don't use submenu containers, it's always the menuBox.
|
497 |
+
* @return {Object} Object with two fields - 'menu' and 'submenu' - containing the jQuery objects
|
498 |
+
* of the created widgets.
|
499 |
+
*/
|
500 |
+
AmeEditorColumn.prototype.outputItem = function(itemData, afterNode, itemList) {
|
501 |
+
if (!itemList) {
|
502 |
+
itemList = this.getVisibleItemList();
|
503 |
+
}
|
504 |
+
const self = this;
|
505 |
+
|
506 |
+
//Create the menu widget
|
507 |
+
const isTopLevel = this.level <= 1;
|
508 |
+
const $item = buildMenuItem(itemData, isTopLevel);
|
509 |
+
|
510 |
+
if ((typeof afterNode !== 'undefined') && (afterNode !== null)) {
|
511 |
+
$(afterNode).after($item);
|
512 |
+
} else {
|
513 |
+
$item.appendTo(itemList);
|
514 |
+
}
|
515 |
+
|
516 |
+
const children = (typeof itemData.items !== 'undefined') ? itemData.items : [];
|
517 |
+
const hasChildren = !_.isEmpty(children);
|
518 |
+
let $submenu = null;
|
519 |
+
|
520 |
+
this.getNextColumn(
|
521 |
+
/**
|
522 |
+
* @param {AmeEditorColumn|null} nextColumn
|
523 |
+
*/
|
524 |
+
function (nextColumn) {
|
525 |
+
if (nextColumn) {
|
526 |
+
//Create a submenu container even if this item doesn't have children.
|
527 |
+
//The user could add submenu items later.
|
528 |
+
$submenu = createSubmenuFor($item, nextColumn);
|
529 |
+
|
530 |
+
//Output children.
|
531 |
+
if (hasChildren) {
|
532 |
+
$.each(children, function (index, item) {
|
533 |
+
nextColumn.outputItem(item, null, $submenu);
|
534 |
+
});
|
535 |
+
}
|
536 |
+
} else {
|
537 |
+
//TODO: This branch could be optimized by letting the recursive outputItem call know that there is no next column.
|
538 |
+
//There is no next column, so any submenu items that belong to this item will be
|
539 |
+
//displayed in the same column, below the item.
|
540 |
+
if (hasChildren) {
|
541 |
+
let $previousItem = $item;
|
542 |
+
$.each(children, function (index, child) {
|
543 |
+
const result = self.outputItem(child, $previousItem, itemList);
|
544 |
+
if (result && result.menu) {
|
545 |
+
$previousItem = result.menu;
|
546 |
+
}
|
547 |
+
});
|
548 |
+
}
|
549 |
+
}
|
550 |
+
|
551 |
+
//Note: Update the menu only after its children are ready. It needs the submenu items to decide
|
552 |
+
//whether to display the access checkbox as checked or indeterminate.
|
553 |
+
updateItemEditor($item);
|
554 |
+
},
|
555 |
+
hasChildren
|
556 |
+
);
|
557 |
+
|
558 |
+
//Note that $submenu could still be NULL at this point if the "get next column" callback
|
559 |
+
//is called asynchronously.
|
560 |
+
return {
|
561 |
+
'menu': $item,
|
562 |
+
'submenu': $submenu
|
563 |
+
};
|
564 |
+
};
|
565 |
+
|
566 |
+
/**
|
567 |
+
* Paste a menu item in this column.
|
568 |
+
*
|
569 |
+
* @param {Object} item
|
570 |
+
* @param {JQuery|null} [afterItem] Defaults to the current selection. Set to NULL to paste at the end of the list.
|
571 |
+
* @param {JQuery} [itemList]
|
572 |
+
*/
|
573 |
+
AmeEditorColumn.prototype.pasteItem = function(item, afterItem, itemList) {
|
574 |
+
if (typeof afterItem === 'undefined') {
|
575 |
+
afterItem = this.getSelectedItem();
|
576 |
+
if (afterItem.length < 1) {
|
577 |
+
afterItem = null;
|
578 |
}
|
579 |
+
}
|
580 |
+
|
581 |
+
if (!itemList) {
|
582 |
+
itemList = this.getVisibleItemList();
|
583 |
+
}
|
584 |
+
|
585 |
+
//The user shouldn't need to worry about giving separators a unique filename.
|
586 |
+
if (item.separator) {
|
587 |
+
item.defaults.file = randomMenuId('separator_');
|
588 |
+
}
|
589 |
+
|
590 |
+
//If we're pasting from a sub-menu into the top level, we may need to fix some properties
|
591 |
+
//that are blank for sub-menu items but required for top level menus.
|
592 |
+
const isTopLevel = this.level <= 1;
|
593 |
+
if (isTopLevel) {
|
594 |
+
function isNonEmptyString(value) {
|
595 |
+
return (typeof value === 'string') && (value !== '');
|
596 |
+
}
|
597 |
+
|
598 |
+
if (!isNonEmptyString(getFieldValue(item, 'css_class', ''))) {
|
599 |
+
item.css_class = 'menu-top';
|
600 |
+
}
|
601 |
+
if (!isNonEmptyString(getFieldValue(item, 'icon_url', ''))) {
|
602 |
+
item.icon_url = 'dashicons-admin-generic';
|
603 |
+
}
|
604 |
+
if (!isNonEmptyString(getFieldValue(item, 'hookname', ''))) {
|
605 |
+
item.hookname = randomMenuId();
|
606 |
+
}
|
607 |
+
}
|
608 |
+
|
609 |
+
const result = this.outputItem(item, afterItem, itemList);
|
610 |
+
|
611 |
+
if (this.level > 1) {
|
612 |
+
updateParentAccessUi(itemList);
|
613 |
+
}
|
614 |
+
|
615 |
+
return result;
|
616 |
+
};
|
617 |
+
|
618 |
+
/**
|
619 |
+
* @return {JQuery|null}
|
620 |
+
*/
|
621 |
+
AmeEditorColumn.prototype.getVisibleItemList = function() {
|
622 |
+
if (this.usesSubmenuContainers) {
|
623 |
+
if (this.visibleItemList) {
|
624 |
+
return this.visibleItemList;
|
625 |
+
}
|
626 |
+
|
627 |
+
const $list = this.menuBox.children('.ws_submenu:visible').first().addClass('ame-visible-item-list');
|
628 |
+
if ($list && ($list.length > 0)) {
|
629 |
+
this.visibleItemList = $list;
|
630 |
+
}
|
631 |
+
return $list;
|
632 |
+
} else {
|
633 |
+
return this.menuBox;
|
634 |
+
}
|
635 |
+
};
|
636 |
+
|
637 |
+
/**
|
638 |
+
* @param {JQuery|null} $submenu
|
639 |
+
*/
|
640 |
+
AmeEditorColumn.prototype.setVisibleItemList = function($submenu) {
|
641 |
+
//Do nothing if the new list is the same as the old one.
|
642 |
+
if (($submenu === this.visibleItemList) || ($submenu && ($submenu.is(this.visibleItemList)))) {
|
643 |
+
return;
|
644 |
+
}
|
645 |
+
|
646 |
+
if (this.visibleItemList) {
|
647 |
+
this.visibleItemList.hide().removeClass('ame-visible-item-list');
|
648 |
+
}
|
649 |
+
this.visibleItemList = $submenu;
|
650 |
+
|
651 |
+
if (this.visibleItemList) {
|
652 |
+
this.visibleItemList.show().addClass('ame-visible-item-list');
|
653 |
+
}
|
654 |
+
|
655 |
+
//Each item list/submenu has its own own selected item, so switching to a different item list
|
656 |
+
//also effectively changes the selected item.
|
657 |
+
this.selectionHasChanged();
|
658 |
+
};
|
659 |
+
|
660 |
+
/**
|
661 |
+
* @return {JQuery}
|
662 |
+
*/
|
663 |
+
AmeEditorColumn.prototype.getAllItemLists = function() {
|
664 |
+
if (this.usesSubmenuContainers) {
|
665 |
+
return this.menuBox.children('.ws_submenu');
|
666 |
+
}
|
667 |
+
return this.menuBox;
|
668 |
+
};
|
669 |
+
|
670 |
+
/**
|
671 |
+
* @return {JQuery}
|
672 |
+
*/
|
673 |
+
AmeEditorColumn.prototype.getSelectedItem = function() {
|
674 |
+
const list = this.getVisibleItemList();
|
675 |
+
if (list && (list.length > 0)) {
|
676 |
+
return list.children('.ws_active').first();
|
677 |
+
}
|
678 |
+
return $([]);
|
679 |
+
};
|
680 |
+
|
681 |
+
/**
|
682 |
+
* @param {JQuery} container
|
683 |
+
*/
|
684 |
+
AmeEditorColumn.prototype.selectItem = function(container) {
|
685 |
+
if (container.hasClass('ws_active')) {
|
686 |
+
//The menu item is already selected.
|
687 |
+
return;
|
688 |
+
}
|
689 |
+
|
690 |
+
//Highlight the active item and un-highlight the previous one
|
691 |
+
container.addClass('ws_active');
|
692 |
+
container.siblings('.ws_active').removeClass('ws_active');
|
693 |
+
|
694 |
+
this.selectionHasChanged(container);
|
695 |
+
};
|
696 |
+
|
697 |
+
/**
|
698 |
+
* @param {JQuery|null} [$item]
|
699 |
+
*/
|
700 |
+
AmeEditorColumn.prototype.selectionHasChanged = function($item) {
|
701 |
+
if (typeof $item === 'undefined') {
|
702 |
+
$item = this.getSelectedItem();
|
703 |
+
}
|
704 |
+
if (!$item || ($item.length < 1)) {
|
705 |
+
$item = null;
|
706 |
+
}
|
707 |
+
|
708 |
+
//Make the "delete" button appear disabled if you can't delete this item.
|
709 |
+
this.container.find('.ws_toolbar .ws_delete_menu_button')
|
710 |
+
.toggleClass('ws_button_disabled', !canDeleteItem($item))
|
711 |
+
|
712 |
+
const self = this;
|
713 |
+
this.getNextColumn(function(nextColumn) {
|
714 |
+
if (nextColumn) {
|
715 |
+
nextColumn.setVisibleItemList(getSubmenuOf($item, nextColumn));
|
716 |
+
if ($item) {
|
717 |
+
self.updateSubmenuBoxHeight($item, nextColumn);
|
718 |
+
}
|
719 |
+
}
|
720 |
+
}, false);
|
721 |
+
};
|
722 |
+
|
723 |
+
/**
|
724 |
+
* @param {JQuery} selectedMenu
|
725 |
+
* @param {AmeEditorColumn} nextColumn
|
726 |
+
*/
|
727 |
+
AmeEditorColumn.prototype.updateSubmenuBoxHeight = function updateSubmenuBoxHeight(selectedMenu, nextColumn) {
|
728 |
+
if (!nextColumn || (nextColumn === this)) {
|
729 |
+
return;
|
730 |
+
}
|
731 |
+
let mainMenuBox = this.menuBox,
|
732 |
+
submenuBox = nextColumn.menuBox,
|
733 |
+
submenuDropZone = nextColumn.dropZone;
|
734 |
+
|
735 |
+
//Make the submenu box tall enough to reach the selected item.
|
736 |
+
//This prevents the menu tip (if any) from floating in empty space.
|
737 |
+
if (selectedMenu.hasClass('ws_menu_separator')) {
|
738 |
+
submenuBox.css('min-height', '');
|
739 |
+
} else {
|
740 |
+
var menuTipHeight = 30,
|
741 |
+
empiricalExtraHeight = 4,
|
742 |
+
verticalBoxOffset = (submenuBox.offset().top - mainMenuBox.offset().top),
|
743 |
+
minSubmenuHeight = (selectedMenu.offset().top - mainMenuBox.offset().top)
|
744 |
+
- verticalBoxOffset
|
745 |
+
+ menuTipHeight - submenuDropZone.outerHeight() + empiricalExtraHeight;
|
746 |
+
minSubmenuHeight = Math.max(minSubmenuHeight, 0);
|
747 |
+
submenuBox.css('min-height', minSubmenuHeight);
|
748 |
+
}
|
749 |
}
|
750 |
|
751 |
+
AmeEditorColumn.prototype.buildSubmenuContainer = function(parentMenuId) {
|
752 |
+
//Create a container for menu items.
|
753 |
+
const submenu = $('<div class="ws_submenu" style="display:none;"></div>');
|
754 |
+
submenu.attr('id', 'ws-submenu-'+(wsIdCounter++));
|
755 |
+
|
756 |
+
if (parentMenuId) {
|
757 |
+
submenu.data('parent_menu_id', parentMenuId);
|
758 |
+
}
|
759 |
+
|
760 |
+
//Make the submenu sortable
|
761 |
+
makeBoxSortable(submenu);
|
762 |
+
|
763 |
+
return submenu;
|
764 |
+
};
|
765 |
+
|
766 |
+
AmeEditorColumn.prototype.appendSubmenuContainer = function($submenu) {
|
767 |
+
this.usesSubmenuContainers = true;
|
768 |
+
$submenu.appendTo(this.menuBox);
|
769 |
+
};
|
770 |
+
|
771 |
+
/**
|
772 |
+
* Delete a menu item and all of its children.
|
773 |
+
*
|
774 |
+
* @param {JQuery} container
|
775 |
+
*/
|
776 |
+
AmeEditorColumn.prototype.destroyItem = function(container) {
|
777 |
+
const wasSelected = container.is('.ws_active');
|
778 |
+
|
779 |
+
//Recursively destroy any submenu items.
|
780 |
+
const submenuId = container.data('submenu_id');
|
781 |
+
if (submenuId) {
|
782 |
+
const self = this;
|
783 |
+
const $submenu = $('#' + submenuId);
|
784 |
+
$submenu.children('.ws_container').each(function() {
|
785 |
+
self.destroyItem($(this));
|
786 |
+
});
|
787 |
+
$submenu.remove();
|
788 |
+
}
|
789 |
+
|
790 |
+
//Destroy the item itself.
|
791 |
+
container.remove();
|
792 |
+
|
793 |
+
if (wasSelected) {
|
794 |
+
this.selectionHasChanged();
|
795 |
+
}
|
796 |
+
};
|
797 |
+
|
798 |
+
/**
|
799 |
+
* Remove all items and item lists from this column.
|
800 |
+
*
|
801 |
+
* Note: Does not remove item submenus that are in other columns.
|
802 |
+
*/
|
803 |
+
AmeEditorColumn.prototype.reset = function() {
|
804 |
+
this.menuBox.empty();
|
805 |
+
this.visibleItemList = null;
|
806 |
+
this.selectionHasChanged(null);
|
807 |
+
};
|
808 |
+
|
809 |
+
/**
|
810 |
+
*
|
811 |
+
* @param {JQuery} editorNode
|
812 |
+
* @param {Boolean|null|string} [deepNestingEnabled]
|
813 |
+
* @param {Number} [maxLevels]
|
814 |
+
* @param {Number} [initialLevels]
|
815 |
+
* @constructor
|
816 |
+
*/
|
817 |
+
function AmeMenuPresenter(editorNode, deepNestingEnabled, maxLevels, initialLevels ) {
|
818 |
+
const self = this;
|
819 |
+
this.editorNode = editorNode;
|
820 |
+
|
821 |
+
if (typeof deepNestingEnabled === 'string') {
|
822 |
+
deepNestingEnabled = (deepNestingEnabled === '1');
|
823 |
+
}
|
824 |
+
this.isDeepNestingEnabled = (typeof deepNestingEnabled !== 'undefined') ? deepNestingEnabled : null;
|
825 |
+
this.nestingQueryPromise = null;
|
826 |
+
|
827 |
+
if (typeof maxLevels === 'undefined') {
|
828 |
+
maxLevels = 3;
|
829 |
+
}
|
830 |
+
if (typeof initialLevels === 'undefined') {
|
831 |
+
if (this.isDeepNestingEnabled) {
|
832 |
+
//If additional levels are enabled, show the maximum number of levels.
|
833 |
+
initialLevels = maxLevels;
|
834 |
+
} else {
|
835 |
+
//WordPress only supports up to two levels by default.
|
836 |
+
initialLevels = Math.min(maxLevels, 2);
|
837 |
+
}
|
838 |
+
}
|
839 |
+
if (initialLevels > this.maxLevels) {
|
840 |
+
initialLevels = this.maxLevels;
|
841 |
+
}
|
842 |
+
|
843 |
+
this.maxLevels = maxLevels;
|
844 |
+
|
845 |
+
const $topLevelContainer = this.editorNode.find('#ws_menu_box').first().closest('.ws_main_container');
|
846 |
+
this.columns = [
|
847 |
+
//Empty zeroth column.
|
848 |
+
new AmeEditorColumn(0, null, $()),
|
849 |
+
//The first column contains top level menus.
|
850 |
+
new AmeEditorColumn(1, null, $topLevelContainer, makeNextColumnGetter(1))
|
851 |
+
];
|
852 |
+
this.currentLevels = this.columns.length - 1;
|
853 |
+
|
854 |
+
function makeNextColumnGetter(ownLevel) {
|
855 |
+
if (ownLevel >= self.maxLevels) {
|
856 |
+
//This column will never have a next column, so we can just use NULL.
|
857 |
+
return function(callback) {
|
858 |
+
callback(null);
|
859 |
+
};
|
860 |
+
}
|
861 |
+
return function(callback, createIfNotExists) {
|
862 |
+
self.getColumn(ownLevel + 1, callback, createIfNotExists);
|
863 |
+
};
|
864 |
+
}
|
865 |
+
|
866 |
+
/**
|
867 |
+
* @param {Number} level
|
868 |
+
* @return {AmeEditorColumn}
|
869 |
+
*/
|
870 |
+
function createColumn(level) {
|
871 |
+
if (level > self.maxLevels) {
|
872 |
+
throw new Error('Cannot exceed maximum nesting level: ' + self.maxLevels);
|
873 |
+
}
|
874 |
+
if (typeof self.columns[level] !== 'undefined') {
|
875 |
+
throw new Error('Cannot overwrite an existing column ' + level);
|
876 |
+
}
|
877 |
+
|
878 |
+
let predecessor;
|
879 |
+
if (typeof self.columns[level - 1] !== 'undefined') {
|
880 |
+
predecessor = self.columns[level - 1].container;
|
881 |
+
} else {
|
882 |
+
predecessor = self.columns[self.currentLevels].container;
|
883 |
+
}
|
884 |
+
|
885 |
+
let newColumn = new AmeEditorColumn(level, predecessor, null, makeNextColumnGetter(level));
|
886 |
+
self.columns.push(newColumn);
|
887 |
+
|
888 |
+
if (level > self.currentLevels) {
|
889 |
+
self.currentLevels = level;
|
890 |
+
}
|
891 |
+
|
892 |
+
return newColumn;
|
893 |
+
}
|
894 |
+
|
895 |
+
/**
|
896 |
+
* Can we create another column?
|
897 |
+
*
|
898 |
+
* @param {Number} level
|
899 |
+
* @param {Function} callback
|
900 |
+
*/
|
901 |
+
function queryCanCreateColumn(level, callback) {
|
902 |
+
if (
|
903 |
+
(level > self.maxLevels) //Do not exceed the maximum depth.
|
904 |
+
|| (typeof self.columns[level] !== 'undefined') //Do not overwrite existing columns.
|
905 |
+
) {
|
906 |
+
callback(false);
|
907 |
+
return;
|
908 |
+
}
|
909 |
+
|
910 |
+
//WordPress core only supports two admin menu levels. We call anything beyond that "deep".
|
911 |
+
const isDeep = (level > 2);
|
912 |
+
if (!isDeep) {
|
913 |
+
callback(true);
|
914 |
+
return;
|
915 |
+
}
|
916 |
+
//Do we already know if we can create deeply nested menus?
|
917 |
+
if (self.isDeepNestingEnabled !== null) {
|
918 |
+
callback(self.isDeepNestingEnabled);
|
919 |
+
return;
|
920 |
+
}
|
921 |
+
|
922 |
+
//If we're already waiting for a decision, just add another callback to the queue.
|
923 |
+
if (self.nestingQueryPromise !== null) {
|
924 |
+
self.nestingQueryPromise.always(function() {
|
925 |
+
callback(self.isDeepNestingEnabled);
|
926 |
+
});
|
927 |
+
return;
|
928 |
+
}
|
929 |
+
|
930 |
+
//Let's allow other code/plugins to decide this. Scripts can add deferred objects or promises
|
931 |
+
//to an array. All deferred objects must resolve successfully to enable deep nesting.
|
932 |
+
let deferreds = [];
|
933 |
+
self.editorNode.trigger('adminMenuEditor:queryDeepNesting', [deferreds]);
|
934 |
+
|
935 |
+
if (deferreds.length > 0) {
|
936 |
+
self.nestingQueryPromise = $.when.apply($, deferreds)
|
937 |
+
.done(function() {
|
938 |
+
self.isDeepNestingEnabled = true;
|
939 |
+
})
|
940 |
+
.fail(function() {
|
941 |
+
self.isDeepNestingEnabled = false;
|
942 |
+
})
|
943 |
+
.always(function() {
|
944 |
+
callback(self.isDeepNestingEnabled);
|
945 |
+
});
|
946 |
+
} else {
|
947 |
+
//Deep nesting is disabled by default.
|
948 |
+
self.isDeepNestingEnabled = false;
|
949 |
+
callback(self.isDeepNestingEnabled);
|
950 |
+
}
|
951 |
+
}
|
952 |
+
|
953 |
+
/**
|
954 |
+
* Get or create a column. The callback will be called with one argument: either the column object,
|
955 |
+
* or NULL if the column does not exist and could not be created.
|
956 |
+
*
|
957 |
+
* @param {Number} level
|
958 |
+
* @param {Function} callback
|
959 |
+
* @param {Boolean} [createIfNotExists] Defaults to true.
|
960 |
+
*/
|
961 |
+
this.getColumn = function(level, callback, createIfNotExists) {
|
962 |
+
if (typeof this.columns[level] !== 'undefined') {
|
963 |
+
callback(this.columns[level]);
|
964 |
+
return;
|
965 |
+
}
|
966 |
+
|
967 |
+
if (typeof createIfNotExists === 'undefined') {
|
968 |
+
createIfNotExists = true;
|
969 |
+
}
|
970 |
+
|
971 |
+
if (createIfNotExists) {
|
972 |
+
queryCanCreateColumn(level, function (isAllowed) {
|
973 |
+
//It could be that another callback has already created the next column,
|
974 |
+
//so we need to check again if it exists.
|
975 |
+
if (typeof self.columns[level] !== 'undefined') {
|
976 |
+
callback(self.columns[level]);
|
977 |
+
} else if (isAllowed) {
|
978 |
+
callback(createColumn(level));
|
979 |
+
} else {
|
980 |
+
callback(null);
|
981 |
+
}
|
982 |
+
});
|
983 |
+
} else {
|
984 |
+
callback(null);
|
985 |
+
}
|
986 |
+
};
|
987 |
+
|
988 |
+
/**
|
989 |
+
* Get or create a column. Like getColumn(), but it will default to not creating deeply nested
|
990 |
+
* menu levels unless that feature is already enabled.
|
991 |
+
*
|
992 |
+
* @param {Number} level
|
993 |
+
* @return {AmeEditorColumn|null}
|
994 |
+
*/
|
995 |
+
this.getColumnImmediate = function(level) {
|
996 |
+
if (typeof this.columns[level] !== 'undefined') {
|
997 |
+
return this.columns[level];
|
998 |
+
}
|
999 |
+
if (level > this.maxLevels) {
|
1000 |
+
return null;
|
1001 |
+
}
|
1002 |
+
|
1003 |
+
if ((level <= 2) || (this.isDeepNestingEnabled === true)) {
|
1004 |
+
return createColumn(level);
|
1005 |
+
}
|
1006 |
+
return null;
|
1007 |
+
};
|
1008 |
+
|
1009 |
+
/**
|
1010 |
+
* Get the column that contains a specific item.
|
1011 |
+
*
|
1012 |
+
* @param {JQuery} container Menu item container.
|
1013 |
+
* @return {AmeEditorColumn|null}
|
1014 |
+
*/
|
1015 |
+
this.getItemColumn = function(container) {
|
1016 |
+
if (!container) {
|
1017 |
+
return null;
|
1018 |
+
}
|
1019 |
+
const level = container.closest('.ws_main_container').data('ame-menu-level');
|
1020 |
+
if (typeof level === 'undefined') {
|
1021 |
+
return null;
|
1022 |
+
}
|
1023 |
+
return this.getColumnImmediate(level);
|
1024 |
+
};
|
1025 |
+
|
1026 |
+
/**
|
1027 |
+
* Create editor widgets for a menu item and its submenus and append them all to the DOM.
|
1028 |
+
*
|
1029 |
+
* @param {Number} level
|
1030 |
+
* @param {Object} itemData
|
1031 |
+
* @param {JQuery} [afterNode] Insert the widget after this node.
|
1032 |
+
*/
|
1033 |
+
this.outputMenuItem = function(level, itemData, afterNode) {
|
1034 |
+
const column = this.getColumnImmediate(level);
|
1035 |
+
return column.outputItem(itemData, afterNode);
|
1036 |
+
}
|
1037 |
+
|
1038 |
+
/**
|
1039 |
+
* Select a menu item and show its submenu.
|
1040 |
+
*
|
1041 |
+
* @param {JQuery} container
|
1042 |
+
*/
|
1043 |
+
this.selectItem = function(container) {
|
1044 |
+
const thisColumn = this.getColumnImmediate(container.closest('.ws_main_container').data('ame-menu-level'));
|
1045 |
+
if (thisColumn) {
|
1046 |
+
thisColumn.selectItem(container);
|
1047 |
+
}
|
1048 |
+
};
|
1049 |
+
|
1050 |
+
/**
|
1051 |
+
* Delete a menu item and all of its children.
|
1052 |
+
*
|
1053 |
+
* @param {JQuery} container
|
1054 |
+
*/
|
1055 |
+
this.destroyItem = function(container) {
|
1056 |
+
const column = this.getItemColumn(container);
|
1057 |
+
if (column) {
|
1058 |
+
column.destroyItem(container);
|
1059 |
+
}
|
1060 |
+
};
|
1061 |
+
|
1062 |
+
/**
|
1063 |
+
* Delete all items and reset all columns.
|
1064 |
+
*/
|
1065 |
+
this.clear = function() {
|
1066 |
+
for (let level = 0; level < this.columns.length; level++) {
|
1067 |
+
if (typeof this.columns[level] !== 'undefined') {
|
1068 |
+
this.columns[level].reset();
|
1069 |
+
}
|
1070 |
+
}
|
1071 |
+
};
|
1072 |
+
|
1073 |
+
//Initialisation.
|
1074 |
+
for (let level = this.currentLevels + 1; level <= initialLevels; level++) {
|
1075 |
+
createColumn(level);
|
1076 |
+
}
|
1077 |
+
}
|
1078 |
|
1079 |
+
/*
|
1080 |
+
* Create edit widgets for a top-level menu and its submenus and append them all to the DOM.
|
1081 |
+
*
|
1082 |
+
* Inputs :
|
1083 |
+
* menu - an object containing menu data
|
1084 |
+
* afterNode - if specified, the new menu widget will be inserted after this node. Otherwise,
|
1085 |
+
* it will be added to the end of the list.
|
1086 |
+
* Outputs :
|
1087 |
+
* Object with two fields - 'menu' and 'submenu' - containing the DOM nodes of the created widgets.
|
1088 |
+
*/
|
1089 |
+
function outputTopMenu(menu, afterNode){
|
1090 |
+
if (!menuPresenter) {
|
1091 |
+
throw new Error('outputTopMenu cannot be called before the menu presenter has been initialised.');
|
1092 |
+
}
|
1093 |
+
return menuPresenter.outputMenuItem(1, menu, afterNode);
|
1094 |
}
|
1095 |
|
1096 |
/**
|
1153 |
}),
|
1154 |
|
1155 |
'drop' : (function(event, ui){
|
1156 |
+
const column = menuPresenter.getItemColumn(item);
|
1157 |
+
if (!column) {
|
1158 |
+
return;
|
1159 |
+
}
|
1160 |
+
const nextColumn = menuPresenter.getColumnImmediate(column.level + 1);
|
1161 |
+
const submenu = getSubmenuOf(item, nextColumn);
|
1162 |
+
if (!submenu || !nextColumn) {
|
1163 |
+
return;
|
1164 |
+
}
|
1165 |
+
|
1166 |
+
const droppedItemData = readItemState(ui.draggable);
|
1167 |
+
const sourceSubmenu = ui.draggable.parent();
|
1168 |
|
1169 |
+
let result = nextColumn.outputItem(droppedItemData, null, submenu);
|
|
|
|
|
1170 |
|
1171 |
if ( !event.ctrlKey ) {
|
1172 |
+
menuPresenter.destroyItem(ui.draggable);
|
1173 |
}
|
1174 |
|
1175 |
+
updateItemEditor(result.menu);
|
1176 |
|
1177 |
//Moving an item can change aggregate menu permissions. Update the UI accordingly.
|
1178 |
updateParentAccessUi(submenu);
|
1179 |
+
if (sourceSubmenu) {
|
1180 |
+
updateParentAccessUi(sourceSubmenu);
|
1181 |
+
}
|
1182 |
})
|
1183 |
});
|
1184 |
}
|
1777 |
//noinspection JSUnusedLocalSymbols
|
1778 |
function buildEditboxField(entry, field_name, field_settings){
|
1779 |
//Build a form field of the appropriate type
|
1780 |
+
var inputBox;
|
1781 |
var basicTextField = '<input type="text" class="ws_field_value">';
|
1782 |
//noinspection FallthroughInSwitchStatementJS
|
1783 |
switch(field_settings.type){
|
1868 |
.attr('src', wsEditorData.imagesUrl + '/transparent16.png')
|
1869 |
).data('field_name', field_name);
|
1870 |
|
1871 |
+
var visible;
|
1872 |
if (typeof field_settings.visible === 'function') {
|
1873 |
visible = field_settings.visible(entry, field_name);
|
1874 |
} else {
|
2269 |
/**
|
2270 |
* Select the first menu item that has the specified URL.
|
2271 |
*
|
2272 |
+
* @param {number|string} selectorOrLevel
|
2273 |
* @param {string} url
|
2274 |
+
* @param {null|Boolean} [expandProperties]
|
2275 |
* @returns {JQuery}
|
2276 |
*/
|
2277 |
+
AmeEditorApi.selectMenuItemByUrl = function(selectorOrLevel, url, expandProperties) {
|
2278 |
if (typeof expandProperties === 'undefined') {
|
2279 |
expandProperties = null;
|
2280 |
}
|
2281 |
|
2282 |
+
let level;
|
2283 |
+
if (selectorOrLevel === '#ws_menu_box') {
|
2284 |
+
level = 1;
|
2285 |
+
} else if (selectorOrLevel === '#ws_submenu_box') {
|
2286 |
+
level = 2;
|
2287 |
+
} else {
|
2288 |
+
level = selectorOrLevel;
|
2289 |
}
|
2290 |
|
2291 |
+
const column = menuPresenter.getColumnImmediate(level);
|
2292 |
+
if (!column) {
|
2293 |
+
return $([]);
|
2294 |
+
}
|
2295 |
+
|
2296 |
+
const box = column.getVisibleItemList();
|
2297 |
+
|
2298 |
+
const containerNode =
|
2299 |
box.find('.ws_container')
|
2300 |
.filter(function() {
|
2301 |
+
const itemUrl = AmeEditorApi.getItemDisplayUrl($(this).data('menu_item'));
|
2302 |
return (itemUrl === url);
|
2303 |
})
|
2304 |
.first();
|
2307 |
AmeEditorApi.selectItem(containerNode);
|
2308 |
|
2309 |
if (expandProperties !== null) {
|
2310 |
+
const expandLink = containerNode.find('.ws_edit_link').first();
|
2311 |
if (expandLink.hasClass('ws_edit_link_expanded') !== expandProperties) {
|
2312 |
expandLink.trigger('click');
|
2313 |
}
|
2392 |
|
2393 |
/**
|
2394 |
* Losslessly compress the admin menu configuration.
|
2395 |
+
*
|
2396 |
* This is a JS port of the ameMenu::compress() function defined in /includes/menu.php.
|
2397 |
+
*
|
2398 |
* @param {Object} adminMenu
|
2399 |
* @returns {Object}
|
2400 |
*/
|
2700 |
//By default, any actor that has the required cap has access to the menu.
|
2701 |
//Users can override this on a per-menu basis.
|
2702 |
var requiredCap = getFieldValue(menuItem, 'access_level', '< Error: access_level is missing! >');
|
2703 |
+
var actorHasAccess;
|
2704 |
if (menuItem.grant_access.hasOwnProperty(actor)) {
|
2705 |
actorHasAccess = menuItem.grant_access[actor];
|
2706 |
} else {
|
2789 |
var isDomReadyDone = false;
|
2790 |
|
2791 |
function ameOnDomReady() {
|
2792 |
+
if (isDomReadyDone) {
|
2793 |
+
return;
|
2794 |
+
}
|
2795 |
isDomReadyDone = true;
|
2796 |
|
2797 |
//Some editor elements are only available in the Pro version.
|
2821 |
/***************************************************************************
|
2822 |
Event handlers for editor widgets
|
2823 |
***************************************************************************/
|
2824 |
+
const menuEditorNode = $('#ws_menu_editor');
|
|
|
|
|
2825 |
|
2826 |
+
menuPresenter = new AmeMenuPresenter(menuEditorNode, wsEditorData.deepNestingEnabled);
|
2827 |
|
2828 |
/**
|
2829 |
* Select a menu item and show its submenu.
|
2831 |
* @param {JQuery|HTMLElement} container Menu container node.
|
2832 |
*/
|
2833 |
function selectItem(container) {
|
2834 |
+
menuPresenter.selectItem(container);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
2835 |
}
|
2836 |
AmeEditorApi.selectItem = selectItem;
|
2837 |
|
2841 |
}));
|
2842 |
|
2843 |
function updateSubmenuBoxHeight(selectedMenu) {
|
2844 |
+
const myColumn = menuPresenter.getColumnImmediate(selectedMenu.closest('.ws_main_container').data('ame-menu-level') || 1);
|
2845 |
+
const nextColumn = menuPresenter.getColumnImmediate(myColumn.level + 1);
|
2846 |
+
if (!nextColumn || (nextColumn === myColumn)) {
|
2847 |
+
return;
|
2848 |
+
}
|
2849 |
+
let mainMenuBox = myColumn.menuBox,
|
2850 |
+
submenuBox = nextColumn.menuBox,
|
2851 |
+
submenuDropZone = nextColumn.container.find('.ws_dropzone').first();
|
2852 |
+
|
2853 |
//Make the submenu box tall enough to reach the selected item.
|
2854 |
//This prevents the menu tip (if any) from floating in empty space.
|
2855 |
if (selectedMenu.hasClass('ws_menu_separator')) {
|
2975 |
field.removeClass('ws_input_default');
|
2976 |
}
|
2977 |
|
2978 |
+
// noinspection EqualityComparisonWithCoercionJS It's been like this so long that I'm afraid to change it.
|
2979 |
+
if (field.hasClass('ws_input_default') && (value == defaultValue)) {
|
2980 |
value = null; //null = use default.
|
2981 |
}
|
2982 |
|
3047 |
* @param containerNode
|
3048 |
* @param {String|Object.<String, Boolean>} actor
|
3049 |
* @param {Boolean} [allowAccess]
|
3050 |
+
* @param {Boolean} [skipParentUiRefresh] Whether to skip updating the parent access UI. Defaults to false.
|
3051 |
*/
|
3052 |
+
function setActorAccessForTreeAndUpdateUi(containerNode, actor, allowAccess, skipParentUiRefresh) {
|
3053 |
setActorAccess(containerNode, actor, allowAccess);
|
3054 |
|
3055 |
//Apply the same permissions to sub-menus.
|
3056 |
+
const subMenuId = containerNode.data('submenu_id');
|
3057 |
+
if (subMenuId) {
|
3058 |
$('.ws_item', '#' + subMenuId).each(function() {
|
3059 |
+
const node = $(this);
|
3060 |
+
setActorAccessForTreeAndUpdateUi(node, actor, allowAccess, true);
|
|
|
3061 |
});
|
3062 |
}
|
3063 |
|
3064 |
updateItemEditor(containerNode);
|
3065 |
+
updateActorAccessUi(containerNode);
|
3066 |
+
|
3067 |
+
if ( !skipParentUiRefresh ) {
|
3068 |
+
updateParentAccessUi(containerNode);
|
3069 |
+
}
|
3070 |
}
|
3071 |
|
3072 |
/**
|
3229 |
|
3230 |
var isDropdownBeingHidden = false, isSuggestionClick = false;
|
3231 |
|
3232 |
+
const $extraCapInAccessEditor = $('#ws_extra_capability');
|
3233 |
+
|
3234 |
//Show/hide the capability drop-down list when the trigger button is clicked
|
3235 |
$('#ws_trigger_capability_dropdown').on('mousedown click', onDropdownTriggerClicked);
|
3236 |
menuEditorNode.on('mousedown click', '.ws_cap_selector_trigger', onDropdownTriggerClicked);
|
3237 |
|
3238 |
function onDropdownTriggerClicked(event){
|
3239 |
/* jshint validthis:true */
|
3240 |
+
var inputBox;
|
3241 |
var button = $(this);
|
3242 |
|
3243 |
var isInAccessEditor = false;
|
3245 |
|
3246 |
//Find the input associated with the button that was clicked.
|
3247 |
if ( button.attr('id') === 'ws_trigger_capability_dropdown' ) {
|
3248 |
+
inputBox = $extraCapInAccessEditor;
|
3249 |
isInAccessEditor = true;
|
3250 |
} else {
|
3251 |
inputBox = button.closest('.ws_edit_field').find('.ws_field_value').first();
|
3296 |
} else {
|
3297 |
currentDropdownOwnerMenu = currentDropdownOwner.closest('.ws_container').data('menu_item');
|
3298 |
}
|
3299 |
+
|
3300 |
capSelectorDropdown.focus();
|
3301 |
|
3302 |
capSuggestionFeature.show();
|
3303 |
}
|
3304 |
|
3305 |
//Also show it when the user presses the down arrow in the input field (doesn't work in Opera).
|
3306 |
+
$extraCapInAccessEditor.bind('keyup', function(event){
|
3307 |
if ( event.which === 40 ){
|
3308 |
$('#ws_trigger_capability_dropdown').trigger('click');
|
3309 |
}
|
4240 |
return false;
|
4241 |
});
|
4242 |
|
4243 |
+
//region Toolbar buttons
|
4244 |
+
|
4245 |
/*************************************************************************
|
4246 |
Menu toolbar buttons
|
4247 |
*************************************************************************/
|
4248 |
function getSelectedMenu() {
|
4249 |
+
return menuPresenter.getColumnImmediate(1).getSelectedItem();
|
4250 |
}
|
4251 |
AmeEditorApi.getSelectedMenu = getSelectedMenu;
|
4252 |
|
4253 |
//Show/Hide menu
|
4254 |
+
menuEditorNode.on(
|
4255 |
+
'adminMenuEditor:action-hide',
|
4256 |
+
/**
|
4257 |
+
* @param event
|
4258 |
+
* @param {JQuery|null} selectedItem
|
4259 |
+
* @param {AmeEditorColumn} column
|
4260 |
+
*/
|
4261 |
+
function(event, selectedItem, column) {
|
4262 |
+
const selection = column.getSelectedItem();
|
4263 |
+
if (selection.length < 1) {
|
4264 |
+
return;
|
4265 |
+
}
|
4266 |
|
4267 |
+
toggleItemHiddenFlag(selection);
|
|
|
|
|
|
|
4268 |
}
|
4269 |
+
);
|
|
|
|
|
4270 |
|
4271 |
//Hide a menu and deny access.
|
4272 |
+
menuEditorNode.on(
|
4273 |
+
'adminMenuEditor:action-deny',
|
4274 |
+
/**
|
4275 |
+
* @param event
|
4276 |
+
* @param {JQuery|null} selectedItem
|
4277 |
+
* @param {AmeEditorColumn} column
|
4278 |
+
*/
|
4279 |
+
function(event, selectedItem, column) {
|
4280 |
+
const selection = column.getSelectedItem();
|
4281 |
+
if (selection.length < 1) {
|
4282 |
+
return;
|
4283 |
+
}
|
4284 |
|
4285 |
+
function objectFillKeys(keys, value) {
|
4286 |
+
let result = {};
|
4287 |
+
_.forEach(keys, function(key) {
|
4288 |
+
result[key] = value;
|
4289 |
+
});
|
4290 |
+
return result;
|
4291 |
+
}
|
4292 |
|
4293 |
+
if (actorSelectorWidget.selectedActor === null) {
|
4294 |
+
//Hide from everyone except Super Admin and the current user.
|
4295 |
+
let menuItem = selection.data('menu_item'),
|
4296 |
+
validActors = _.keys(wsEditorData.actors),
|
4297 |
+
alwaysAllowedActors = _.intersection(
|
4298 |
+
['special:super_admin', 'user:' + wsEditorData.currentUserLogin],
|
4299 |
+
validActors
|
4300 |
+
),
|
4301 |
+
victims = _.difference(validActors, alwaysAllowedActors),
|
4302 |
+
shouldHide;
|
4303 |
+
|
4304 |
+
//First, lets check who has access. Maybe this item is already hidden from the victims.
|
4305 |
+
shouldHide = _.some(victims, _.curry(actorCanAccessMenu, 2)(menuItem));
|
4306 |
+
|
4307 |
+
let keepEnabled = objectFillKeys(alwaysAllowedActors, true),
|
4308 |
+
hideAllExceptAllowed = _.assign(objectFillKeys(victims, false), keepEnabled);
|
4309 |
+
|
4310 |
+
walkMenuTree(selection, function(container, item) {
|
4311 |
+
let newAccess;
|
4312 |
+
if (shouldHide) {
|
4313 |
+
//Yay, hide it now!
|
4314 |
+
newAccess = hideAllExceptAllowed;
|
4315 |
+
//Only update had_access_before_hiding if this item isn't hidden yet or the field is missing.
|
4316 |
+
//We don't want to double-hide an item.
|
4317 |
+
let actorsWithAccess = _.filter(victims, function(actor) {
|
4318 |
+
return actorCanAccessMenu(item, actor);
|
4319 |
+
});
|
4320 |
+
if ((actorsWithAccess.length) > 0 || _.isEmpty(_.get(item, 'had_access_before_hiding', null))) {
|
4321 |
+
item.had_access_before_hiding = actorsWithAccess;
|
4322 |
+
}
|
4323 |
+
} else {
|
4324 |
+
//Give back access to the roles and users who previously had access.
|
4325 |
+
//Careful, don't give access to roles that no longer exist.
|
4326 |
+
let actorsWhoHadAccess = _.get(item, 'had_access_before_hiding', []) || [];
|
4327 |
+
actorsWhoHadAccess = _.intersection(actorsWhoHadAccess, validActors);
|
4328 |
+
|
4329 |
+
newAccess = _.assign(objectFillKeys(actorsWhoHadAccess, true), keepEnabled);
|
4330 |
+
delete item.had_access_before_hiding;
|
4331 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
4332 |
|
4333 |
+
setActorAccess(container, newAccess);
|
4334 |
+
updateItemEditor(container);
|
4335 |
+
});
|
4336 |
|
4337 |
+
} else {
|
4338 |
+
//Just toggle the checkbox.
|
4339 |
+
selection.find('input.ws_actor_access_checkbox').trigger('click');
|
4340 |
+
}
|
4341 |
}
|
4342 |
+
);
|
4343 |
|
4344 |
//Delete error dialog. It shows up when the user tries to delete one of the default menus.
|
4345 |
var menuDeletionDialog = $('#ws-ame-menu-deletion-error').dialog({
|
4418 |
$('#ws_hide_menu_from_everyone').on('click', function() {
|
4419 |
menuDeletionCallback('all');
|
4420 |
});
|
4421 |
+
const $hideExceptCurrentUser = $('#ws_hide_menu_except_current_user').on('click', function() {
|
4422 |
menuDeletionCallback('except_current_user');
|
4423 |
});
|
4424 |
+
const $hideExceptAdmin = $('#ws_hide_menu_except_administrator').on('click', function() {
|
4425 |
menuDeletionCallback('except_administrator');
|
4426 |
});
|
4427 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
4428 |
/**
|
4429 |
* Attempt to delete a menu item. Will check if the item can actually be deleted and ask the user for confirmation.
|
4430 |
* UI callback.
|
4447 |
|
4448 |
//Different versions get slightly different options because only the Pro version has
|
4449 |
//role-specific permissions.
|
4450 |
+
$hideExceptCurrentUser.toggleClass('hidden', !wsEditorData.wsMenuEditorPro);
|
4451 |
+
$hideExceptAdmin.toggleClass('hidden', wsEditorData.wsMenuEditorPro);
|
4452 |
|
4453 |
menuDeletionDialog.dialog('open');
|
4454 |
|
4457 |
}
|
4458 |
|
4459 |
if (shouldDelete) {
|
4460 |
+
const parentSubmenu = selection.closest('.ws_submenu');
|
|
|
|
|
|
|
|
|
|
|
4461 |
|
4462 |
//Delete the menu.
|
4463 |
+
menuPresenter.destroyItem(selection);
|
4464 |
|
4465 |
+
if (parentSubmenu && (parentSubmenu.length > 0)) {
|
4466 |
//Refresh permissions UI for this menu's parent (if any).
|
4467 |
updateParentAccessUi(parentSubmenu);
|
4468 |
}
|
4470 |
}
|
4471 |
|
4472 |
//Delete menu
|
4473 |
+
menuEditorNode.on(
|
4474 |
+
'adminMenuEditor:action-delete',
|
4475 |
+
/**
|
4476 |
+
* @param event
|
4477 |
+
* @param {JQuery|null} selectedItem
|
4478 |
+
* @param {AmeEditorColumn} column
|
4479 |
+
*/
|
4480 |
+
function(event, selectedItem, column) {
|
4481 |
+
const selection = column.getSelectedItem();
|
4482 |
+
if (selection.length < 1) {
|
4483 |
+
return;
|
4484 |
+
}
|
4485 |
|
4486 |
+
tryDeleteItem(selection);
|
|
|
|
|
|
|
4487 |
}
|
4488 |
+
);
|
|
|
|
|
4489 |
|
4490 |
//Copy menu
|
4491 |
+
menuEditorNode.on(
|
4492 |
+
'adminMenuEditor:action-copy',
|
4493 |
|
4494 |
+
/**
|
4495 |
+
* @param event
|
4496 |
+
* @param {JQuery|null} selectedItem
|
4497 |
+
*/
|
4498 |
+
function (event, selectedItem) {
|
4499 |
+
//Get the selected menu
|
4500 |
+
if (!selectedItem || (selectedItem.lengt < 1)) {
|
4501 |
+
return;
|
4502 |
+
}
|
4503 |
|
4504 |
+
//Store a copy of the current menu state in clipboard
|
4505 |
+
menu_in_clipboard = readItemState(selectedItem);
|
4506 |
+
}
|
4507 |
+
);
|
4508 |
|
4509 |
//Cut menu
|
4510 |
+
menuEditorNode.on(
|
4511 |
+
'adminMenuEditor:action-cut',
|
4512 |
|
4513 |
+
/**
|
4514 |
+
* @param event
|
4515 |
+
* @param {JQuery|null} selectedItem
|
4516 |
+
* @param {AmeEditorColumn} column
|
4517 |
+
*/
|
4518 |
+
function (event, selectedItem, column) {
|
4519 |
+
if (selectedItem === null) {
|
4520 |
+
alert('Please select a menu item first.');
|
4521 |
+
return;
|
4522 |
+
}
|
4523 |
+
const submenu = selectedItem.closest('.ws_submenu');
|
4524 |
|
4525 |
+
//Store a copy of the current menu state in clipboard
|
4526 |
+
menu_in_clipboard = readItemState(selectedItem);
|
4527 |
|
4528 |
+
//Remove the original menu and submenu
|
4529 |
+
column.destroyItem(selectedItem);
|
|
|
|
|
4530 |
|
4531 |
+
//If this submenu had mixed permissions, that might have changed now that the item is gone.
|
4532 |
+
updateParentAccessUi(submenu);
|
|
|
|
|
|
|
4533 |
}
|
4534 |
+
);
|
4535 |
|
4536 |
+
menuEditorNode.on(
|
4537 |
+
'adminMenuEditor:action-paste',
|
4538 |
+
/**
|
4539 |
+
* @param event
|
4540 |
+
* @param {JQuery|null} selectedItem
|
4541 |
+
* @param {AmeEditorColumn} column
|
4542 |
+
*/
|
4543 |
+
function(event, selectedItem, column) {
|
4544 |
+
//Check if anything has been copied/cut
|
4545 |
+
if (!menu_in_clipboard) {
|
4546 |
+
return;
|
4547 |
+
}
|
4548 |
|
4549 |
+
//You can only add separators to submenus in the Pro version.
|
4550 |
+
if ( menu_in_clipboard.separator && !wsEditorData.wsMenuEditorPro ) {
|
4551 |
+
return;
|
4552 |
+
}
|
|
|
|
|
|
|
4553 |
|
4554 |
+
const copyOfItem = $.extend(true, {}, menu_in_clipboard);
|
|
|
4555 |
|
4556 |
+
//Paste the menu after the selection.
|
4557 |
+
column.pasteItem(copyOfItem, selectedItem);
|
|
|
4558 |
}
|
4559 |
+
);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
4560 |
|
4561 |
//New menu
|
4562 |
+
menuEditorNode.on(
|
4563 |
+
'adminMenuEditor:action-new-menu',
|
4564 |
+
/**
|
4565 |
+
* @param event
|
4566 |
+
* @param {JQuery|null} selectedItem
|
4567 |
+
* @param {AmeEditorColumn} column
|
4568 |
+
*/
|
4569 |
+
function(event, selectedItem, column) {
|
4570 |
+
const visibleList = column.getVisibleItemList();
|
4571 |
+
if (!visibleList || (visibleList.length < 1)) {
|
4572 |
+
//Abort if there's no item list in this column. This can happen if nothing is selected
|
4573 |
+
//in the previous column.
|
4574 |
+
return;
|
4575 |
+
}
|
4576 |
|
4577 |
+
ws_paste_count++;
|
4578 |
|
4579 |
+
//The new menu starts out rather bare.
|
4580 |
+
let item = $.extend(true, {}, wsEditorData.blankMenuItem, {
|
4581 |
+
custom: true, //Important : flag the new menu as custom, or it won't show up after saving.
|
4582 |
+
template_id: '',
|
4583 |
+
menu_title: 'Custom Menu ' + ws_paste_count,
|
4584 |
+
file: randomMenuId(),
|
4585 |
+
items: []
|
4586 |
+
});
|
4587 |
+
item.defaults = $.extend(true, {}, itemTemplates.getDefaults(''));
|
|
|
4588 |
|
4589 |
+
//Make it accessible only to the current actor if one is selected.
|
4590 |
+
if (actorSelectorWidget.selectedActor !== null) {
|
4591 |
+
denyAccessForAllExcept(item, actorSelectorWidget.selectedActor);
|
4592 |
+
}
|
4593 |
|
4594 |
+
//Insert the new menu item.
|
4595 |
+
let selection = column.getSelectedItem();
|
4596 |
+
if (!selection || (selection.length < 1)) {
|
4597 |
+
selection = null;
|
4598 |
+
}
|
4599 |
+
let result = column.outputItem(item, selection);
|
4600 |
|
4601 |
+
if (result && result.menu) {
|
4602 |
+
//The menu's editbox is always open
|
4603 |
+
result.menu.find('.ws_edit_link').trigger('click');
|
4604 |
|
4605 |
+
updateParentAccessUi(result.menu);
|
4606 |
+
}
|
4607 |
+
}
|
4608 |
+
);
|
4609 |
|
4610 |
+
//New separator
|
4611 |
+
menuEditorNode.on(
|
4612 |
+
'adminMenuEditor:action-new-separator',
|
4613 |
+
/**
|
4614 |
+
* @param event
|
4615 |
+
* @param {JQuery|null} selectedItem
|
4616 |
+
* @param {AmeEditorColumn} column
|
4617 |
+
*/
|
4618 |
+
function(event, selectedItem, column) {
|
4619 |
+
const visibleList = column.getVisibleItemList();
|
4620 |
+
if (!visibleList || (visibleList.length < 1)) {
|
4621 |
+
//Abort if there's no item list in this column. This can happen if nothing is selected
|
4622 |
+
//in the previous column.
|
4623 |
+
return;
|
4624 |
}
|
|
|
4625 |
|
4626 |
+
ws_paste_count++;
|
4627 |
+
|
4628 |
+
const randomId = randomMenuId('separator_');
|
4629 |
+
let item = $.extend(true, {}, wsEditorData.blankMenuItem, {
|
4630 |
+
separator: true, //Flag as a separator
|
4631 |
+
custom: false, //Separators don't need to flagged as custom to be retained.
|
4632 |
+
items: [],
|
4633 |
+
defaults: {
|
4634 |
+
separator: true,
|
4635 |
+
css_class : 'wp-menu-separator',
|
4636 |
+
access_level : 'read',
|
4637 |
+
file : randomId,
|
4638 |
+
hookname : randomId
|
4639 |
+
}
|
4640 |
+
});
|
4641 |
+
|
4642 |
+
const selection = column.getSelectedItem();
|
4643 |
+
column.outputItem(item, (selection.length > 0) ? selection : null);
|
4644 |
}
|
4645 |
+
);
|
4646 |
|
4647 |
//Toggle all menus for the currently selected actor
|
4648 |
+
menuEditorNode.on(
|
4649 |
+
'adminMenuEditor:action-toggle-all',
|
4650 |
+
/**
|
4651 |
+
* @param event
|
4652 |
+
*/
|
4653 |
+
function(event) {
|
4654 |
+
if ( actorSelectorWidget.selectedActor === null ) {
|
4655 |
+
alert("This button enables/disables all menus for the selected role. To use it, click a role and then click this button again.");
|
4656 |
+
return;
|
4657 |
+
}
|
4658 |
|
4659 |
+
//Look at the first menu's permissions and set everything to the opposite.
|
4660 |
+
const firstColumn = menuPresenter.getColumnImmediate(1);
|
4661 |
+
const topMenuNodes = $('.ws_menu', firstColumn.getVisibleItemList());
|
|
|
4662 |
|
4663 |
+
const allow = ! actorCanAccessMenu(topMenuNodes.eq(0).data('menu_item'), actorSelectorWidget.selectedActor);
|
|
|
|
|
4664 |
|
4665 |
+
topMenuNodes.each(function() {
|
4666 |
+
let containerNode = $(this);
|
4667 |
+
setActorAccessForTreeAndUpdateUi(containerNode, actorSelectorWidget.selectedActor, allow);
|
4668 |
+
});
|
4669 |
+
}
|
4670 |
+
);
|
4671 |
|
4672 |
//Copy all menu permissions from one role to another.
|
4673 |
var copyPermissionsDialog = $('#ws-ame-copy-permissions-dialog').dialog({
|
4680 |
var sourceActorList = $('#ame-copy-source-actor'), destinationActorList = $('#ame-copy-destination-actor');
|
4681 |
|
4682 |
//The "Copy permissions" toolbar button.
|
4683 |
+
menuEditorNode.on(
|
4684 |
+
'adminMenuEditor:action-copy-permissions',
|
4685 |
+
/**
|
4686 |
+
* @param event
|
4687 |
+
* @param {JQuery|null} selectedItem
|
4688 |
+
* @param {AmeEditorColumn} column
|
4689 |
+
*/
|
4690 |
+
function(event, selectedItem, column) {
|
4691 |
+
const previousSource = sourceActorList.val();
|
4692 |
+
|
4693 |
+
//Populate source/destination lists.
|
4694 |
+
sourceActorList.find('option').not('[disabled]').remove();
|
4695 |
+
destinationActorList.find('option').not('[disabled]').remove();
|
4696 |
+
$.each(actorSelectorWidget.getVisibleActors(), function(index, actor) {
|
4697 |
+
let option = $('<option>', {
|
4698 |
+
val: actor.id,
|
4699 |
+
text: actorSelectorWidget.getNiceName(actor)
|
4700 |
+
});
|
4701 |
+
sourceActorList.append(option);
|
4702 |
+
destinationActorList.append(option.clone());
|
4703 |
});
|
|
|
|
|
|
|
4704 |
|
4705 |
+
//Pre-select the current actor as the destination.
|
4706 |
+
if (actorSelectorWidget.selectedActor !== null) {
|
4707 |
+
destinationActorList.val(actorSelectorWidget.selectedActor);
|
4708 |
+
}
|
4709 |
|
4710 |
+
//Restore the previous source selection.
|
4711 |
+
if (previousSource) {
|
4712 |
+
sourceActorList.val(previousSource);
|
4713 |
+
}
|
4714 |
+
if (!sourceActorList.val()) {
|
4715 |
+
sourceActorList.find('option').first().prop('selected', true); //Fallback.
|
4716 |
+
}
|
4717 |
|
4718 |
+
copyPermissionsDialog.dialog('open');
|
4719 |
+
}
|
4720 |
+
);
|
4721 |
|
4722 |
//Actually copy the permissions when the user click the confirmation button.
|
4723 |
var copyConfirmationButton = $('#ws-ame-confirm-copy-permissions');
|
4731 |
}
|
4732 |
|
4733 |
//Iterate over all menu items and copy the permissions from one actor to the other.
|
4734 |
+
AmeEditorApi.forEachMenuItem(function (menuItem, node) {
|
|
|
|
|
|
|
|
|
4735 |
//Only change permissions when they don't match. This ensures we won't unnecessarily overwrite default
|
4736 |
//permissions and bloat the configuration with extra grant_access entries.
|
4737 |
+
const sourceAccess = actorCanAccessMenu(menuItem, sourceActor);
|
4738 |
+
const destinationAccess = actorCanAccessMenu(menuItem, destinationActor);
|
4739 |
if (sourceAccess !== destinationAccess) {
|
4740 |
setActorAccess(node, destinationActor, sourceAccess);
|
4741 |
//Note: In theory, we could also look at the default permissions for destinationActor and
|
4768 |
});
|
4769 |
|
4770 |
//Sort menus in ascending or descending order.
|
4771 |
+
menuEditorNode.on(
|
4772 |
+
'adminMenuEditor:action-sort',
|
4773 |
+
/**
|
4774 |
+
* @param event
|
4775 |
+
* @param {JQuery|null} selectedItem
|
4776 |
+
* @param {AmeEditorColumn} column
|
4777 |
+
* @param {JQuery} button
|
4778 |
+
*/
|
4779 |
+
function(event, selectedItem, column, button) {
|
4780 |
+
let direction = button.data('sort-direction') || 'asc',
|
4781 |
+
menuBox = column.getVisibleItemList();
|
4782 |
|
4783 |
+
if (!menuBox || (menuBox.length < 1)) {
|
4784 |
+
return;
|
4785 |
+
}
|
4786 |
|
4787 |
+
function sortRecursively($box, currentColumn) {
|
4788 |
+
//When indirectly sorting the second menu level (regular submenus), leave the first item unmoved.
|
4789 |
+
//Moving the first item would change the parent menu URL (WP always links it to the first item),
|
4790 |
+
//which can be unexpected and confusing. The user can always move the first item manually.
|
4791 |
+
let leaveFirstItem = ((currentColumn !== column) && (currentColumn.level === 2));
|
4792 |
+
sortMenuItems($box, direction, leaveFirstItem);
|
4793 |
+
|
4794 |
+
//Also sort child items in the next columns.
|
4795 |
+
const nextColumn = menuPresenter.getColumnImmediate(currentColumn.level + 1);
|
4796 |
+
if (nextColumn) {
|
4797 |
+
$box.find('.ws_container').each(function () {
|
4798 |
+
const $submenu = getSubmenuOf($(this), null);
|
4799 |
+
if ($submenu) {
|
4800 |
+
sortRecursively($submenu, nextColumn);
|
4801 |
+
}
|
4802 |
+
});
|
4803 |
+
}
|
4804 |
+
}
|
4805 |
|
4806 |
+
sortRecursively(menuBox, column);
|
|
|
|
|
|
|
|
|
|
|
|
|
4807 |
}
|
4808 |
+
);
|
4809 |
|
4810 |
/**
|
4811 |
* Sort menu items by title.
|
4857 |
}
|
4858 |
|
4859 |
//Toggle the second row of toolbar buttons.
|
4860 |
+
menuEditorNode.on(
|
4861 |
+
'adminMenuEditor:action-toggle-toolbar',
|
4862 |
+
/**
|
4863 |
+
* @param event
|
4864 |
+
*/
|
4865 |
+
function(event) {
|
4866 |
+
let visible = menuEditorNode.find('.ws_second_toolbar_row').toggle().is(':visible');
|
4867 |
+
if (typeof $['cookie'] !== 'undefined') {
|
4868 |
+
$.cookie('ame-show-second-toolbar', visible ? '1' : '0', {expires: 90});
|
4869 |
+
}
|
4870 |
}
|
4871 |
+
);
|
4872 |
|
4873 |
|
4874 |
/*************************************************************************
|
4875 |
Item toolbar buttons
|
4876 |
*************************************************************************/
|
4877 |
function getSelectedSubmenuItem() {
|
4878 |
+
return menuPresenter.getColumnImmediate(2).getSelectedItem();
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
4879 |
}
|
4880 |
|
4881 |
+
//endregion
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
4882 |
|
4883 |
//==============================================
|
4884 |
// Main buttons
|
4944 |
$('#ws_data_length').val(data.length);
|
4945 |
$('#ws_selected_actor').val(actorSelectorWidget.selectedActor === null ? '' : actorSelectorWidget.selectedActor);
|
4946 |
|
4947 |
+
$('#ws_is_deep_nesting_enabled').val(JSON.stringify(menuPresenter.isDeepNestingEnabled));
|
4948 |
+
|
4949 |
var selectedMenu = getSelectedMenu();
|
4950 |
if (selectedMenu.length > 0) {
|
4951 |
$('#ws_selected_menu_url').val(AmeEditorApi.getItemDisplayUrl(selectedMenu.data('menu_item')));
|
5073 |
closeText: ' ',
|
5074 |
modal: true
|
5075 |
});
|
5076 |
+
const $importMenuForm = $('#import_menu_form');
|
5077 |
|
5078 |
$('#ws_cancel_import').on('click', function(){
|
5079 |
$('#import_dialog').dialog('close');
|
5082 |
$('#ws_import_menu').on('click', function(){
|
5083 |
$('#import_progress_notice, #import_progress_notice2, #import_complete_notice, #ws_import_error').hide();
|
5084 |
$('#ws_import_panel').show();
|
5085 |
+
$importMenuForm.resetForm();
|
5086 |
//The "Upload" button is disabled until the user selects a file
|
5087 |
$('#ws_start_import').attr('disabled', 'disabled');
|
5088 |
|
5100 |
function handleUnexpectedImportError(xhr, errorMessage) {
|
5101 |
//The server-side code didn't catch this error, so it's probably something serious
|
5102 |
//and retrying won't work.
|
5103 |
+
$importMenuForm.resetForm();
|
5104 |
$('#ws_import_panel').hide();
|
5105 |
|
5106 |
//Display error information.
|
5111 |
}
|
5112 |
|
5113 |
//AJAXify the upload form
|
5114 |
+
$importMenuForm.ajaxForm({
|
5115 |
dataType : 'json',
|
5116 |
beforeSubmit: function(formData) {
|
5117 |
|
5149 |
if ( typeof data.error !== 'undefined' ){
|
5150 |
alert(data.error);
|
5151 |
//Let the user try again
|
5152 |
+
$importMenuForm.resetForm();
|
5153 |
importDialog.find('.hide-when-uploading').show();
|
5154 |
}
|
5155 |
|
5188 |
}),
|
5189 |
|
5190 |
'drop' : (function(event, ui){
|
5191 |
+
const firstColumn = menuPresenter.getColumnImmediate(1);
|
5192 |
+
if (!firstColumn) {
|
5193 |
+
return;
|
5194 |
+
}
|
5195 |
+
const nextColumn = menuPresenter.getColumnImmediate(firstColumn.level + 1);
|
5196 |
+
|
5197 |
+
const droppedItemData = readItemState(ui.draggable);
|
5198 |
+
const newItemNodes = firstColumn.pasteItem(droppedItemData, null);
|
5199 |
|
5200 |
//If the item was originally a top level menu, also move its original submenu items.
|
5201 |
+
if ((getFieldValue(droppedItemData, 'parent') === null) && (newItemNodes.submenu)) {
|
5202 |
+
const droppedItemFile = getFieldValue(droppedItemData, 'file');
|
5203 |
+
const nearbyItems = $(ui.draggable).siblings('.ws_item');
|
5204 |
nearbyItems.each(function() {
|
5205 |
+
const containerNode = $(this),
|
5206 |
submenuItem = containerNode.data('menu_item');
|
5207 |
|
5208 |
//Was this item originally a child of the dragged menu?
|
5209 |
if (getFieldValue(submenuItem, 'parent') === droppedItemFile) {
|
5210 |
+
nextColumn.pasteItem(submenuItem, null, newItemNodes.submenu);
|
5211 |
if ( !event.ctrlKey ) {
|
5212 |
+
menuPresenter.destroyItem(containerNode);
|
5213 |
}
|
5214 |
}
|
5215 |
});
|
5216 |
}
|
5217 |
|
5218 |
if ( !event.ctrlKey ) {
|
5219 |
+
menuPresenter.destroyItem(ui.draggable);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
5220 |
}
|
5221 |
})
|
5222 |
});
|
5690 |
domCheckAttempts++;
|
5691 |
|
5692 |
if ($ && $.isReady) {
|
5693 |
+
window.clearInterval(domCheckIntervalId);
|
5694 |
ameOnDomReady();
|
5695 |
}
|
5696 |
}, 1000);
|
js/menu-highlight-fix.js
CHANGED
@@ -1,4 +1,4 @@
|
|
1 |
-
|
2 |
// parseUri 1.2.2
|
3 |
// (c) Steven Levithan <stevenlevithan.com>
|
4 |
// MIT License
|
@@ -46,174 +46,243 @@ jQuery(function($) {
|
|
46 |
|
47 |
// --- parseUri ends ---
|
48 |
|
49 |
-
|
50 |
-
|
51 |
-
|
52 |
-
|
53 |
-
|
54 |
-
|
55 |
-
|
56 |
-
|
57 |
-
|
58 |
-
|
59 |
-
|
60 |
-
|
61 |
-
|
62 |
-
|
63 |
-
|
64 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
65 |
|
66 |
-
|
67 |
-
|
68 |
-
|
69 |
-
|
70 |
-
|
|
|
71 |
}
|
72 |
-
}
|
73 |
|
74 |
-
|
75 |
-
|
76 |
-
|
77 |
|
78 |
-
|
79 |
-
|
80 |
-
|
81 |
-
|
82 |
-
|
83 |
|
84 |
-
|
85 |
|
86 |
-
|
87 |
-
|
88 |
-
|
89 |
-
|
|
|
90 |
}
|
91 |
-
|
92 |
-
//TODO: Consider using get_current_screen and the current_screen filter to get post types and taxonomies.
|
93 |
|
94 |
-
|
95 |
-
|
96 |
-
|
97 |
-
|
98 |
-
|
99 |
-
|
100 |
|
101 |
-
|
102 |
-
|
103 |
-
|
104 |
|
105 |
-
|
106 |
-
|
107 |
-
|
108 |
-
|
109 |
-
|
110 |
-
|
111 |
-
|
112 |
-
|
113 |
-
|
114 |
-
|
115 |
-
|
116 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
117 |
differentParams++;
|
118 |
}
|
119 |
}
|
120 |
-
|
121 |
-
|
122 |
-
|
123 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
124 |
}
|
125 |
-
}
|
126 |
|
127 |
-
|
128 |
-
|
129 |
-
|
130 |
-
|
131 |
-
|
132 |
-
|
133 |
-
|
134 |
-
|
135 |
-
|
136 |
-
|
137 |
-
}
|
138 |
-
|
139 |
-
|
140 |
-
|
141 |
-
},
|
142 |
-
{
|
143 |
-
better : (isAnchorMatch && (!bestMatch.isAnchorMatch)),
|
144 |
-
equal : (isAnchorMatch === bestMatch.isAnchorMatch)
|
145 |
-
},
|
146 |
-
|
147 |
-
//When a menu has multiple submenus, the first submenu usually has the same URL
|
148 |
-
//as the parent menu. We want to highlight this item and not just the parent.
|
149 |
-
{
|
150 |
-
better : (!isTopMenu && bestMatch.isTopMenu
|
151 |
-
//Is this link a child of the current best match?
|
152 |
-
&& (!bestMatch.link || ($link.closest(bestMatch.link.closest('li')).length > 0))
|
153 |
-
),
|
154 |
-
equal : (isTopMenu === bestMatch.isTopMenu)
|
155 |
-
},
|
156 |
-
|
157 |
-
//All else being equal, the item highlighted by WP is probably a better match.
|
158 |
-
{
|
159 |
-
better : (isHighlighted && !bestMatch.isHighlighted),
|
160 |
-
equal : (isHighlighted === bestMatch.isHighlighted)
|
161 |
-
}
|
162 |
-
];
|
163 |
-
|
164 |
-
var isBetterMatch = false,
|
165 |
-
isEquallyGood = true,
|
166 |
-
j = 0;
|
167 |
-
|
168 |
-
while (isEquallyGood && !isBetterMatch && (j < comparisons.length)) {
|
169 |
-
isBetterMatch = comparisons[j].better;
|
170 |
-
isEquallyGood = comparisons[j].equal;
|
171 |
-
j++;
|
172 |
-
}
|
173 |
|
174 |
-
|
175 |
-
|
176 |
-
|
177 |
-
link : $link,
|
178 |
-
matchingParams : matchingParams,
|
179 |
-
differentParams : differentParams,
|
180 |
-
isAnchorMatch : isAnchorMatch,
|
181 |
-
isTopMenu : isTopMenu,
|
182 |
-
isHighlighted: isHighlighted
|
183 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
184 |
}
|
185 |
-
});
|
186 |
|
187 |
-
|
188 |
-
|
189 |
-
|
190 |
-
|
191 |
-
|
192 |
-
|
193 |
-
|
194 |
-
|
195 |
-
|
196 |
-
|
197 |
-
|
198 |
-
|
199 |
-
|
200 |
-
|
201 |
-
|
202 |
-
|
203 |
-
var
|
204 |
-
|
205 |
-
otherHighlightedMenus
|
206 |
-
|
207 |
-
|
208 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
209 |
}
|
210 |
|
211 |
-
|
212 |
-
|
213 |
-
|
214 |
-
|
215 |
}
|
216 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
217 |
//Note: WordPress switches the admin menu between `position: fixed` and `position: relative` depending on
|
218 |
//how tall it is compared to the browser window. Opening a different submenu can change the menu's height,
|
219 |
//so we must trigger the position update to avoid bugs. If we don't, we can end up with a very tall menu
|
@@ -226,28 +295,15 @@ jQuery(function($) {
|
|
226 |
//We'll resort to faking a resize event to make WP update the menu height and state.
|
227 |
$(document).trigger('wp-window-resized');
|
228 |
}
|
229 |
-
|
230 |
-
//Workaround: Prevent the current submenu from "jumping around" when you click an item. This glitch is
|
231 |
-
//caused by a `focusin` event handler in common.js. WP adds this handler to all top level menus that
|
232 |
-
//are not the current menu. Since we're changing the current menu, we need to also remove this handler.
|
233 |
-
if (typeof parentMenu['off'] === 'function') {
|
234 |
-
parentMenu.off('focusin.adminmenu');
|
235 |
-
}
|
236 |
-
}
|
237 |
-
|
238 |
-
if (isWrongItemHighlighted) {
|
239 |
-
adminMenu.find('.current').removeClass('current');
|
240 |
-
bestMatchLink.addClass('current').closest('li').addClass('current');
|
241 |
}
|
242 |
}
|
243 |
|
244 |
-
|
245 |
-
|
246 |
-
//this might not be the case and we'll need to fix it.
|
247 |
-
var parentOfHighlightedMenu = $('.wp-submenu a.current', '#adminmenu').closest('.menu-top').first();
|
248 |
-
parentOfHighlightedMenu
|
249 |
-
.add('> a.menu-top', parentOfHighlightedMenu)
|
250 |
-
.removeClass('wp-not-current-submenu')
|
251 |
-
.addClass('wp-has-current-submenu wp-menu-open');
|
252 |
|
253 |
-
|
|
|
|
|
|
|
|
|
|
1 |
+
(function ($) {
|
2 |
// parseUri 1.2.2
|
3 |
// (c) Steven Levithan <stevenlevithan.com>
|
4 |
// MIT License
|
46 |
|
47 |
// --- parseUri ends ---
|
48 |
|
49 |
+
var hasRunAtLeastOnce = false;
|
50 |
+
|
51 |
+
function highlightCurrentMenu() {
|
52 |
+
hasRunAtLeastOnce = true;
|
53 |
+
|
54 |
+
//Find the menu item whose URL best matches the currently open page.
|
55 |
+
var currentUri = parseUri(location.href);
|
56 |
+
var bestMatch = {
|
57 |
+
uri: null,
|
58 |
+
/**
|
59 |
+
* @type {JQuery|null}
|
60 |
+
*/
|
61 |
+
link: null,
|
62 |
+
matchingParams: -1,
|
63 |
+
differentParams: 10000,
|
64 |
+
isAnchorMatch: false,
|
65 |
+
isTopMenu: false,
|
66 |
+
level: 0,
|
67 |
+
isHighlighted: false
|
68 |
+
};
|
69 |
+
|
70 |
+
//Special case: ".../wp-admin/" should match ".../wp-admin/index.php".
|
71 |
+
if (currentUri.path.match(/\/wp-admin\/$/)) {
|
72 |
+
currentUri.path = currentUri.path + 'index.php';
|
73 |
+
}
|
74 |
|
75 |
+
//Special case: if post_type is not specified for edit.php and post-new.php,
|
76 |
+
//WordPress assumes it is "post". Here we make this explicit.
|
77 |
+
if ((currentUri.file === 'edit.php') || (currentUri.file === 'post-new.php')) {
|
78 |
+
if (!currentUri.queryKey.hasOwnProperty('post_type')) {
|
79 |
+
currentUri.queryKey['post_type'] = 'post';
|
80 |
+
}
|
81 |
}
|
|
|
82 |
|
83 |
+
var adminMenu = $('#adminmenu');
|
84 |
+
adminMenu.find('li > a').each(function (index, link) {
|
85 |
+
var $link = $(link);
|
86 |
|
87 |
+
//Skip links that have no href or contain nothing but an "#anchor". Both AME and some
|
88 |
+
//other plugins (e.g. S2Member 120703) use them as separators.
|
89 |
+
if (!$link.is('[href]') || ($link.attr('href').substring(0, 1) === '#')) {
|
90 |
+
return;
|
91 |
+
}
|
92 |
|
93 |
+
var uri = parseUri(link.href);
|
94 |
|
95 |
+
//Same as above - use "post" as the default post type.
|
96 |
+
if ((uri.file === 'edit.php') || (uri.file === 'post-new.php')) {
|
97 |
+
if (!uri.queryKey.hasOwnProperty('post_type')) {
|
98 |
+
uri.queryKey['post_type'] = 'post';
|
99 |
+
}
|
100 |
}
|
101 |
+
//TODO: Consider using get_current_screen and the current_screen filter to get post types and taxonomies.
|
|
|
102 |
|
103 |
+
//Check for a close match - everything but query and #anchor.
|
104 |
+
var components = ['protocol', 'host', 'port', 'user', 'password', 'path'];
|
105 |
+
var isCloseMatch = true;
|
106 |
+
for (var i = 0; (i < components.length) && isCloseMatch; i++) {
|
107 |
+
isCloseMatch = isCloseMatch && (uri[components[i]] === currentUri[components[i]]);
|
108 |
+
}
|
109 |
|
110 |
+
if (!isCloseMatch) {
|
111 |
+
return; //Skip to the next link.
|
112 |
+
}
|
113 |
|
114 |
+
//Calculate the number of matching and different query parameters.
|
115 |
+
var matchingParams = 0, differentParams = 0, param;
|
116 |
+
for (param in uri.queryKey) {
|
117 |
+
if (uri.queryKey.hasOwnProperty(param)) {
|
118 |
+
if (currentUri.queryKey.hasOwnProperty(param)) {
|
119 |
+
//All parameters that are present in *both* URLs must have the same exact values.
|
120 |
+
if (uri.queryKey[param] === currentUri.queryKey[param]) {
|
121 |
+
matchingParams++;
|
122 |
+
} else {
|
123 |
+
return; //Skip to the next link.
|
124 |
+
}
|
125 |
+
} else {
|
126 |
+
differentParams++;
|
127 |
+
}
|
128 |
+
}
|
129 |
+
}
|
130 |
+
for (param in currentUri.queryKey) {
|
131 |
+
if (currentUri.queryKey.hasOwnProperty(param) && !uri.queryKey.hasOwnProperty(param)) {
|
132 |
differentParams++;
|
133 |
}
|
134 |
}
|
135 |
+
|
136 |
+
var isAnchorMatch = uri.anchor === currentUri.anchor;
|
137 |
+
var level = $link.parentsUntil(adminMenu, 'li').length;
|
138 |
+
var isHighlighted = $link.is('.current, .wp-has-current-submenu');
|
139 |
+
|
140 |
+
//Figure out if the current link is better than the best found so far.
|
141 |
+
//To do that, we compare them by several criteria (in order of priority):
|
142 |
+
var comparisons = [
|
143 |
+
{
|
144 |
+
better: (matchingParams > bestMatch.matchingParams),
|
145 |
+
equal: (matchingParams === bestMatch.matchingParams)
|
146 |
+
},
|
147 |
+
{
|
148 |
+
better: (differentParams < bestMatch.differentParams),
|
149 |
+
equal: (differentParams === bestMatch.differentParams)
|
150 |
+
},
|
151 |
+
{
|
152 |
+
better: (isAnchorMatch && (!bestMatch.isAnchorMatch)),
|
153 |
+
equal: (isAnchorMatch === bestMatch.isAnchorMatch)
|
154 |
+
},
|
155 |
+
|
156 |
+
//When a menu has multiple submenus, the first submenu usually has the same URL
|
157 |
+
//as the parent menu. We want to highlight this item and not just the parent.
|
158 |
+
{
|
159 |
+
better: ((level > bestMatch.level)
|
160 |
+
//Is this link a child of the current best match?
|
161 |
+
&& (!bestMatch.link || ($link.closest(bestMatch.link.closest('li')).length > 0))
|
162 |
+
),
|
163 |
+
equal: (level === bestMatch.level)
|
164 |
+
},
|
165 |
+
|
166 |
+
//All else being equal, the item highlighted by WP is probably a better match.
|
167 |
+
{
|
168 |
+
better: (isHighlighted && !bestMatch.isHighlighted),
|
169 |
+
equal: (isHighlighted === bestMatch.isHighlighted)
|
170 |
+
}
|
171 |
+
];
|
172 |
+
|
173 |
+
var isBetterMatch = false,
|
174 |
+
isEquallyGood = true,
|
175 |
+
j = 0;
|
176 |
+
|
177 |
+
while (isEquallyGood && !isBetterMatch && (j < comparisons.length)) {
|
178 |
+
isBetterMatch = comparisons[j].better;
|
179 |
+
isEquallyGood = comparisons[j].equal;
|
180 |
+
j++;
|
181 |
}
|
|
|
182 |
|
183 |
+
if (isBetterMatch) {
|
184 |
+
bestMatch = {
|
185 |
+
uri: uri,
|
186 |
+
link: $link,
|
187 |
+
matchingParams: matchingParams,
|
188 |
+
differentParams: differentParams,
|
189 |
+
isAnchorMatch: isAnchorMatch,
|
190 |
+
level: level,
|
191 |
+
isHighlighted: isHighlighted
|
192 |
+
}
|
193 |
+
}
|
194 |
+
});
|
195 |
+
|
196 |
+
var shouldUpdateStickiness = false;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
197 |
|
198 |
+
function isInViewport($thing) {
|
199 |
+
if (!$thing || ($thing.length < 1)) {
|
200 |
+
return false; //A non-existent element is never visible.
|
|
|
|
|
|
|
|
|
|
|
|
|
201 |
}
|
202 |
+
|
203 |
+
var element = $thing.get(0);
|
204 |
+
if (!element.getBoundingClientRect || !window.innerHeight || !window.innerWidth) {
|
205 |
+
return true; //The browser doesn't support the necessary APIs, default to true.
|
206 |
+
}
|
207 |
+
|
208 |
+
var rect = element.getBoundingClientRect();
|
209 |
+
return (
|
210 |
+
(rect.top < window.innerHeight)
|
211 |
+
&& (rect.bottom > 0)
|
212 |
+
&& (rect.left < window.innerWidth)
|
213 |
+
&& (rect.right > 0)
|
214 |
+
);
|
215 |
}
|
|
|
216 |
|
217 |
+
//Highlight and/or expand the best matching menu.
|
218 |
+
if (bestMatch.link !== null) {
|
219 |
+
var bestMatchLink = bestMatch.link;
|
220 |
+
var linkAndItem = bestMatchLink.add(bestMatchLink.closest('li'));
|
221 |
+
var allParentMenus = bestMatchLink.parentsUntil(adminMenu, 'li');
|
222 |
+
var topLevelParent = allParentMenus.filter('li.menu-top').last();
|
223 |
+
//console.log('Best match is: ', bestMatchLink);
|
224 |
+
|
225 |
+
shouldUpdateStickiness = !isInViewport(bestMatchLink);
|
226 |
+
|
227 |
+
var otherHighlightedMenus = $('li.wp-has-current-submenu, li.menu-top.current', '#adminmenu')
|
228 |
+
.not(allParentMenus)
|
229 |
+
.not('.ws-ame-has-always-open-submenu');
|
230 |
+
|
231 |
+
var otherHighlightedSubmenus = adminMenu.find('li.current,a.current').not(linkAndItem);
|
232 |
+
|
233 |
+
var isWrongItemHighlighted = !bestMatchLink.hasClass('current') || (otherHighlightedSubmenus.length > 0);
|
234 |
+
var isWrongMenuHighlighted = !topLevelParent.is('.wp-has-current-submenu, .current') ||
|
235 |
+
(otherHighlightedMenus.length > 0);
|
236 |
+
|
237 |
+
if (isWrongMenuHighlighted) {
|
238 |
+
//Account for users who use the Expanded Admin Menus plugin to keep all menus expanded.
|
239 |
+
var shouldCloseOtherMenus = !$('div.expand-arrow', '#adminmenu').get(0);
|
240 |
+
if (shouldCloseOtherMenus) {
|
241 |
+
otherHighlightedMenus
|
242 |
+
.add('> a', otherHighlightedMenus)
|
243 |
+
.removeClass('wp-menu-open wp-has-current-submenu current')
|
244 |
+
.addClass('wp-not-current-submenu');
|
245 |
+
}
|
246 |
+
|
247 |
+
var parentMenuAndLink = topLevelParent.add('> a.menu-top', topLevelParent.get(0));
|
248 |
+
parentMenuAndLink.removeClass('wp-not-current-submenu');
|
249 |
+
if (topLevelParent.hasClass('wp-has-submenu')) {
|
250 |
+
parentMenuAndLink.addClass('wp-has-current-submenu wp-menu-open');
|
251 |
+
}
|
252 |
+
|
253 |
+
shouldUpdateStickiness = true;
|
254 |
+
|
255 |
+
//Workaround: Prevent the current submenu from "jumping around" when you click an item. This glitch is
|
256 |
+
//caused by a `focusin` event handler in common.js. WP adds this handler to all top level menus that
|
257 |
+
//are not the current menu. Since we're changing the current menu, we need to also remove this handler.
|
258 |
+
if (typeof topLevelParent['off'] === 'function') {
|
259 |
+
topLevelParent.off('focusin.adminmenu');
|
260 |
+
}
|
261 |
}
|
262 |
|
263 |
+
if (isWrongItemHighlighted) {
|
264 |
+
adminMenu.find('.current').removeClass('current');
|
265 |
+
linkAndItem.addClass('current');
|
266 |
+
shouldUpdateStickiness = true;
|
267 |
}
|
268 |
|
269 |
+
//WordPress adds the "current" class only to the <li> that is the direct parent of the highlighted link.
|
270 |
+
//If the highlighted item is a submenu, its top-level parent shouldn't have this class.
|
271 |
+
bestMatchLink.closest('li').parentsUntil(adminMenu, 'li').children('a').addBack().removeClass('current');
|
272 |
+
|
273 |
+
bestMatchLink.closest('ul').parentsUntil(adminMenu, 'li').addClass('ame-has-highlighted-item');
|
274 |
+
}
|
275 |
+
|
276 |
+
//If a submenu is highlighted, so must be its parent.
|
277 |
+
//In some cases, if we decide to stick with the WP-selected highlighted menu,
|
278 |
+
//this might not be the case and we'll need to fix it.
|
279 |
+
var parentOfHighlightedMenu = $('.wp-submenu a.current', '#adminmenu').closest('.menu-top').first();
|
280 |
+
parentOfHighlightedMenu
|
281 |
+
.add('> a.menu-top', parentOfHighlightedMenu)
|
282 |
+
.removeClass('wp-not-current-submenu')
|
283 |
+
.addClass('wp-has-current-submenu wp-menu-open');
|
284 |
+
|
285 |
+
if (shouldUpdateStickiness) {
|
286 |
//Note: WordPress switches the admin menu between `position: fixed` and `position: relative` depending on
|
287 |
//how tall it is compared to the browser window. Opening a different submenu can change the menu's height,
|
288 |
//so we must trigger the position update to avoid bugs. If we don't, we can end up with a very tall menu
|
295 |
//We'll resort to faking a resize event to make WP update the menu height and state.
|
296 |
$(document).trigger('wp-window-resized');
|
297 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
298 |
}
|
299 |
}
|
300 |
|
301 |
+
//Other scripts can trigger this feature by using a custom event.
|
302 |
+
$(document).on('adminMenuEditor:highlightCurrentMenu', highlightCurrentMenu);
|
|
|
|
|
|
|
|
|
|
|
|
|
303 |
|
304 |
+
$(function () {
|
305 |
+
if (!hasRunAtLeastOnce) {
|
306 |
+
highlightCurrentMenu();
|
307 |
+
}
|
308 |
+
});
|
309 |
+
})(jQuery);
|
js/tab-utils.js
ADDED
@@ -0,0 +1,64 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
jQuery(function ($) {
|
2 |
+
var menuEditorHeading = $('#ws_ame_editor_heading').first();
|
3 |
+
var pageWrapper = menuEditorHeading.closest('.wrap');
|
4 |
+
var tabList = pageWrapper.find('.nav-tab-wrapper').first();
|
5 |
+
|
6 |
+
//On AME pages, move settings tabs after the heading. This is necessary to make them appear on the right side,
|
7 |
+
//and WordPress breaks that by moving notices like "Settings saved" after the first H1 (see common.js).
|
8 |
+
var menuEditorTabs = tabList.add(tabList.next('.clear'));
|
9 |
+
if ((menuEditorHeading.length > 0) && (menuEditorTabs.length > 0)) {
|
10 |
+
menuEditorTabs.insertAfter(menuEditorHeading);
|
11 |
+
}
|
12 |
+
|
13 |
+
//Switch tab styles when there are too many tabs and they don't fit on one row.
|
14 |
+
var $firstTab = null,
|
15 |
+
$lastTab = null,
|
16 |
+
knownTabWrapThreshold = -1;
|
17 |
+
|
18 |
+
function updateTabStyles() {
|
19 |
+
if (($firstTab === null) || ($lastTab === null)) {
|
20 |
+
var $tabItems = tabList.children('.nav-tab');
|
21 |
+
$firstTab = $tabItems.first();
|
22 |
+
$lastTab = $tabItems.last();
|
23 |
+
}
|
24 |
+
|
25 |
+
//To detect if any tabs are wrapped to the next row, check if the top of the last tab
|
26 |
+
//is below the bottom of the first tab.
|
27 |
+
var firstPosition = $firstTab.position();
|
28 |
+
var lastPosition = $lastTab.position();
|
29 |
+
var windowWidth = $(window).width();
|
30 |
+
//Sanity check.
|
31 |
+
if (
|
32 |
+
!firstPosition || !lastPosition || !windowWidth
|
33 |
+
|| (typeof firstPosition['top'] === 'undefined')
|
34 |
+
|| (typeof lastPosition['top'] === 'undefined')
|
35 |
+
) {
|
36 |
+
return;
|
37 |
+
}
|
38 |
+
var firstTabBottom = firstPosition.top + $firstTab.outerHeight();
|
39 |
+
var areTabsWrapped = (lastPosition.top >= firstTabBottom);
|
40 |
+
|
41 |
+
//Tab positions may change when we apply different styles, which could lead to the tab bar
|
42 |
+
//rapidly cycling between one and two two rows when the browser width is just right.
|
43 |
+
//To prevent that, remember what the width was when we detected wrapping, and always apply
|
44 |
+
//the alternative styles if the width is lower than that.
|
45 |
+
var wouldWrapByDefault = (windowWidth <= knownTabWrapThreshold);
|
46 |
+
|
47 |
+
var tooManyTabs = areTabsWrapped || wouldWrapByDefault;
|
48 |
+
if (tooManyTabs && (windowWidth > knownTabWrapThreshold)) {
|
49 |
+
knownTabWrapThreshold = windowWidth;
|
50 |
+
}
|
51 |
+
|
52 |
+
pageWrapper.toggleClass('ws-ame-too-many-tabs', tooManyTabs);
|
53 |
+
}
|
54 |
+
|
55 |
+
updateTabStyles();
|
56 |
+
|
57 |
+
$(window).on('resize', wsAmeLodash.debounce(
|
58 |
+
function () {
|
59 |
+
updateTabStyles();
|
60 |
+
},
|
61 |
+
300
|
62 |
+
));
|
63 |
+
});
|
64 |
+
|
menu-editor.php
CHANGED
@@ -3,21 +3,25 @@
|
|
3 |
Plugin Name: Admin Menu Editor
|
4 |
Plugin URI: http://w-shadow.com/blog/2008/12/20/admin-menu-editor-for-wordpress/
|
5 |
Description: Lets you directly edit the WordPress admin menu. You can re-order, hide or rename existing menus, add custom menus and more.
|
6 |
-
Version: 1.
|
7 |
Author: Janis Elsts
|
8 |
Author URI: http://w-shadow.com/blog/
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
9 |
*/
|
10 |
|
11 |
if ( include(dirname(__FILE__) . '/includes/version-conflict-check.php') ) {
|
12 |
return;
|
13 |
}
|
14 |
|
|
|
15 |
require_once dirname(__FILE__) . '/includes/basic-dependencies.php';
|
16 |
-
|
17 |
-
|
18 |
-
if ( is_admin() ) {
|
19 |
-
|
20 |
-
//Load the plugin
|
21 |
-
$wp_menu_editor = new WPMenuEditor(__FILE__, 'ws_menu_editor');
|
22 |
-
|
23 |
-
}//is_admin()
|
3 |
Plugin Name: Admin Menu Editor
|
4 |
Plugin URI: http://w-shadow.com/blog/2008/12/20/admin-menu-editor-for-wordpress/
|
5 |
Description: Lets you directly edit the WordPress admin menu. You can re-order, hide or rename existing menus, add custom menus and more.
|
6 |
+
Version: 1.10
|
7 |
Author: Janis Elsts
|
8 |
Author URI: http://w-shadow.com/blog/
|
9 |
+
License: GPLv3
|
10 |
+
License URI: https://www.gnu.org/licenses/gpl-3.0.html
|
11 |
+
*/
|
12 |
+
|
13 |
+
/*
|
14 |
+
This plugin may include third-party libraries and other content that is licensed under various
|
15 |
+
GPL-compatible licenses. In such cases, the relevant license will usually be stated at the top
|
16 |
+
of the source code file or in "readme.txt", "license.txt" or a similar file located in the same
|
17 |
+
directory as the content.
|
18 |
*/
|
19 |
|
20 |
if ( include(dirname(__FILE__) . '/includes/version-conflict-check.php') ) {
|
21 |
return;
|
22 |
}
|
23 |
|
24 |
+
//Load the plugin
|
25 |
require_once dirname(__FILE__) . '/includes/basic-dependencies.php';
|
26 |
+
global $wp_menu_editor;
|
27 |
+
$wp_menu_editor = new WPMenuEditor(__FILE__, 'ws_menu_editor');
|
|
|
|
|
|
|
|
|
|
|
|
modules/actor-selector/actor-selector.js
CHANGED
@@ -43,7 +43,12 @@ var AmeActorSelector = /** @class */ (function () {
|
|
43 |
}
|
44 |
//Select an actor on click.
|
45 |
this.selectorNode.on('click', 'li a.ws_actor_option', function (event) {
|
46 |
-
var
|
|
|
|
|
|
|
|
|
|
|
47 |
if (actor === '') {
|
48 |
actor = null;
|
49 |
}
|
43 |
}
|
44 |
//Select an actor on click.
|
45 |
this.selectorNode.on('click', 'li a.ws_actor_option', function (event) {
|
46 |
+
var href = jQuery(event.target).attr('href');
|
47 |
+
var fragmentStart = href.indexOf('#');
|
48 |
+
var actor = null;
|
49 |
+
if (fragmentStart >= 0) {
|
50 |
+
actor = href.substring(fragmentStart + 1);
|
51 |
+
}
|
52 |
if (actor === '') {
|
53 |
actor = null;
|
54 |
}
|
modules/actor-selector/actor-selector.ts
CHANGED
@@ -85,7 +85,13 @@ class AmeActorSelector {
|
|
85 |
|
86 |
//Select an actor on click.
|
87 |
this.selectorNode.on('click', 'li a.ws_actor_option', (event) => {
|
88 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
89 |
if (actor === '') {
|
90 |
actor = null;
|
91 |
}
|
85 |
|
86 |
//Select an actor on click.
|
87 |
this.selectorNode.on('click', 'li a.ws_actor_option', (event) => {
|
88 |
+
const href = jQuery(event.target).attr('href');
|
89 |
+
const fragmentStart = href.indexOf('#');
|
90 |
+
|
91 |
+
let actor = null;
|
92 |
+
if (fragmentStart >= 0) {
|
93 |
+
actor = href.substring(fragmentStart + 1);
|
94 |
+
}
|
95 |
if (actor === '') {
|
96 |
actor = null;
|
97 |
}
|
modules/redirector/drag-indicator.svg
ADDED
@@ -0,0 +1,126 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
2 |
+
<svg
|
3 |
+
height="24px"
|
4 |
+
viewBox="0 0 24 24"
|
5 |
+
width="24px"
|
6 |
+
fill="#000000"
|
7 |
+
version="1.1"
|
8 |
+
id="svg23"
|
9 |
+
sodipodi:docname="drag-indicator.svg"
|
10 |
+
inkscape:version="1.1 (c68e22c387, 2021-05-23)"
|
11 |
+
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
12 |
+
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
13 |
+
xmlns:xlink="http://www.w3.org/1999/xlink"
|
14 |
+
xmlns="http://www.w3.org/2000/svg"
|
15 |
+
>
|
16 |
+
<defs
|
17 |
+
id="defs27" />
|
18 |
+
<sodipodi:namedview
|
19 |
+
id="namedview25"
|
20 |
+
pagecolor="#ffffff"
|
21 |
+
bordercolor="#666666"
|
22 |
+
borderopacity="1.0"
|
23 |
+
inkscape:pageshadow="2"
|
24 |
+
inkscape:pageopacity="0.0"
|
25 |
+
inkscape:pagecheckerboard="0"
|
26 |
+
showgrid="true"
|
27 |
+
inkscape:zoom="33.708333"
|
28 |
+
inkscape:cx="7.0160692"
|
29 |
+
inkscape:cy="12"
|
30 |
+
inkscape:window-width="1920"
|
31 |
+
inkscape:window-height="1017"
|
32 |
+
inkscape:window-x="-8"
|
33 |
+
inkscape:window-y="-8"
|
34 |
+
inkscape:window-maximized="1"
|
35 |
+
inkscape:current-layer="symbol1321"
|
36 |
+
inkscape:snap-grids="true"
|
37 |
+
inkscape:snap-page="false"
|
38 |
+
showguides="true"
|
39 |
+
inkscape:guide-bbox="true">
|
40 |
+
<inkscape:grid
|
41 |
+
type="xygrid"
|
42 |
+
id="grid2690" />
|
43 |
+
<sodipodi:guide
|
44 |
+
position="9,18"
|
45 |
+
orientation="1,0"
|
46 |
+
id="guide7197" />
|
47 |
+
<sodipodi:guide
|
48 |
+
position="9,18"
|
49 |
+
orientation="0,-1"
|
50 |
+
id="guide7199" />
|
51 |
+
<sodipodi:guide
|
52 |
+
position="9,12"
|
53 |
+
orientation="0,-1"
|
54 |
+
id="guide7201" />
|
55 |
+
<sodipodi:guide
|
56 |
+
position="9,6"
|
57 |
+
orientation="0,-1"
|
58 |
+
id="guide7203" />
|
59 |
+
<sodipodi:guide
|
60 |
+
position="15,18"
|
61 |
+
orientation="1,0"
|
62 |
+
id="guide7205" />
|
63 |
+
</sodipodi:namedview>
|
64 |
+
<path
|
65 |
+
d="M0 0h24v24H0V0z"
|
66 |
+
fill="none"
|
67 |
+
id="box_border"
|
68 |
+
inkscape:label="box_border" />
|
69 |
+
<g
|
70 |
+
inkscape:groupmode="layer"
|
71 |
+
id="layer2"
|
72 |
+
inkscape:label="Dots"
|
73 |
+
style="display:inline">
|
74 |
+
<use
|
75 |
+
xlink:href="#symbol1321"
|
76 |
+
id="use1346"
|
77 |
+
x="0"
|
78 |
+
y="0"
|
79 |
+
width="100%"
|
80 |
+
height="100%"
|
81 |
+
transform="translate(6)" />
|
82 |
+
<use
|
83 |
+
xlink:href="#symbol1321"
|
84 |
+
id="use2560"
|
85 |
+
x="0"
|
86 |
+
y="0"
|
87 |
+
width="100%"
|
88 |
+
height="100%"
|
89 |
+
transform="translate(6,5.9999999)" />
|
90 |
+
<use
|
91 |
+
xlink:href="#symbol1321"
|
92 |
+
id="use2585"
|
93 |
+
x="0"
|
94 |
+
y="0"
|
95 |
+
width="100%"
|
96 |
+
height="100%"
|
97 |
+
transform="translate(6,12)" />
|
98 |
+
<use
|
99 |
+
xlink:href="#symbol1321"
|
100 |
+
id="use2610"
|
101 |
+
x="0"
|
102 |
+
y="0"
|
103 |
+
width="100%"
|
104 |
+
height="100%"
|
105 |
+
transform="translate(-1.5014649e-8,5.9999999)" />
|
106 |
+
<use
|
107 |
+
xlink:href="#symbol1321"
|
108 |
+
id="use2635"
|
109 |
+
x="0"
|
110 |
+
y="0"
|
111 |
+
width="100%"
|
112 |
+
height="100%"
|
113 |
+
transform="translate(-1.5014649e-8,12)" />
|
114 |
+
<g
|
115 |
+
id="symbol1321"
|
116 |
+
transform="translate(0.41718102,3.7731771)"
|
117 |
+
inkscape:label="BaseDot">
|
118 |
+
<circle
|
119 |
+
style="fill:#737373;fill-opacity:1;stroke:none;stroke-width:7.03132;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:125.448;stroke-opacity:1"
|
120 |
+
id="path269"
|
121 |
+
cy="2.2268229"
|
122 |
+
cx="8.582819"
|
123 |
+
r="1.5" />
|
124 |
+
</g>
|
125 |
+
</g>
|
126 |
+
</svg>
|
modules/redirector/knockout-sortable.js
ADDED
@@ -0,0 +1,494 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
// knockout-sortable 1.2.1 | (c) 2021 Ryan Niemeyer | http://www.opensource.org/licenses/mit-license
|
2 |
+
;(function(factory) {
|
3 |
+
if (typeof define === "function" && define.amd) {
|
4 |
+
// AMD anonymous module
|
5 |
+
define(["knockout", "jquery", "jquery-ui/ui/widgets/sortable", "jquery-ui/ui/widgets/draggable", "jquery-ui/ui/widgets/droppable"], factory);
|
6 |
+
} else if (typeof require === "function" && typeof exports === "object" && typeof module === "object") {
|
7 |
+
// CommonJS module
|
8 |
+
var ko = require("knockout"),
|
9 |
+
jQuery = require("jquery");
|
10 |
+
require("jquery-ui/ui/widgets/sortable");
|
11 |
+
require("jquery-ui/ui/widgets/draggable");
|
12 |
+
require("jquery-ui/ui/widgets/droppable");
|
13 |
+
factory(ko, jQuery);
|
14 |
+
} else {
|
15 |
+
// No module loader (plain <script> tag) - put directly in global namespace
|
16 |
+
factory(window.ko, window.jQuery);
|
17 |
+
}
|
18 |
+
})(function(ko, $) {
|
19 |
+
var ITEMKEY = "ko_sortItem",
|
20 |
+
INDEXKEY = "ko_sourceIndex",
|
21 |
+
LISTKEY = "ko_sortList",
|
22 |
+
PARENTKEY = "ko_parentList",
|
23 |
+
DRAGKEY = "ko_dragItem",
|
24 |
+
unwrap = ko.utils.unwrapObservable,
|
25 |
+
dataGet = ko.utils.domData.get,
|
26 |
+
dataSet = ko.utils.domData.set,
|
27 |
+
version = $.ui && $.ui.version,
|
28 |
+
//1.8.24 included a fix for how events were triggered in nested sortables. indexOf checks will fail if version starts with that value (0 vs. -1)
|
29 |
+
hasNestedSortableFix = version && version.indexOf("1.6.") && version.indexOf("1.7.") && (version.indexOf("1.8.") || version === "1.8.24");
|
30 |
+
|
31 |
+
//internal afterRender that adds meta-data to children
|
32 |
+
var addMetaDataAfterRender = function(elements, data) {
|
33 |
+
ko.utils.arrayForEach(elements, function(element) {
|
34 |
+
if (element.nodeType === 1) {
|
35 |
+
dataSet(element, ITEMKEY, data);
|
36 |
+
dataSet(element, PARENTKEY, dataGet(element.parentNode, LISTKEY));
|
37 |
+
}
|
38 |
+
});
|
39 |
+
};
|
40 |
+
|
41 |
+
//prepare the proper options for the template binding
|
42 |
+
var prepareTemplateOptions = function(valueAccessor, dataName) {
|
43 |
+
var result = {},
|
44 |
+
options = {},
|
45 |
+
actualAfterRender;
|
46 |
+
|
47 |
+
//build our options to pass to the template engine
|
48 |
+
if (ko.utils.peekObservable(valueAccessor()).data) {
|
49 |
+
options = unwrap(valueAccessor() || {});
|
50 |
+
result[dataName] = options.data;
|
51 |
+
if (options.hasOwnProperty("template")) {
|
52 |
+
result.name = options.template;
|
53 |
+
}
|
54 |
+
} else {
|
55 |
+
result[dataName] = valueAccessor();
|
56 |
+
}
|
57 |
+
|
58 |
+
ko.utils.arrayForEach(["afterAdd", "afterRender", "as", "beforeRemove", "includeDestroyed", "templateEngine", "templateOptions", "nodes"], function (option) {
|
59 |
+
if (options.hasOwnProperty(option)) {
|
60 |
+
result[option] = options[option];
|
61 |
+
} else if (ko.bindingHandlers.sortable.hasOwnProperty(option)) {
|
62 |
+
result[option] = ko.bindingHandlers.sortable[option];
|
63 |
+
}
|
64 |
+
});
|
65 |
+
|
66 |
+
//use an afterRender function to add meta-data
|
67 |
+
if (dataName === "foreach") {
|
68 |
+
if (result.afterRender) {
|
69 |
+
//wrap the existing function, if it was passed
|
70 |
+
actualAfterRender = result.afterRender;
|
71 |
+
result.afterRender = function(element, data) {
|
72 |
+
addMetaDataAfterRender.call(data, element, data);
|
73 |
+
actualAfterRender.call(data, element, data);
|
74 |
+
};
|
75 |
+
} else {
|
76 |
+
result.afterRender = addMetaDataAfterRender;
|
77 |
+
}
|
78 |
+
}
|
79 |
+
|
80 |
+
//return options to pass to the template binding
|
81 |
+
return result;
|
82 |
+
};
|
83 |
+
|
84 |
+
var updateIndexFromDestroyedItems = function(index, items) {
|
85 |
+
var unwrapped = unwrap(items);
|
86 |
+
|
87 |
+
if (unwrapped) {
|
88 |
+
for (var i = 0; i <= index; i++) {
|
89 |
+
//add one for every destroyed item we find before the targetIndex in the target array
|
90 |
+
if (unwrapped[i] && unwrap(unwrapped[i]._destroy)) {
|
91 |
+
index++;
|
92 |
+
}
|
93 |
+
}
|
94 |
+
}
|
95 |
+
|
96 |
+
return index;
|
97 |
+
};
|
98 |
+
|
99 |
+
//remove problematic leading/trailing whitespace from templates
|
100 |
+
var stripTemplateWhitespace = function(element, name) {
|
101 |
+
var templateSource,
|
102 |
+
templateElement;
|
103 |
+
|
104 |
+
//process named templates
|
105 |
+
if (name) {
|
106 |
+
templateElement = document.getElementById(name);
|
107 |
+
if (templateElement) {
|
108 |
+
templateSource = new ko.templateSources.domElement(templateElement);
|
109 |
+
templateSource.text($.trim(templateSource.text()));
|
110 |
+
}
|
111 |
+
}
|
112 |
+
else {
|
113 |
+
//remove leading/trailing non-elements from anonymous templates
|
114 |
+
$(element).contents().each(function() {
|
115 |
+
if (this && this.nodeType !== 1) {
|
116 |
+
element.removeChild(this);
|
117 |
+
}
|
118 |
+
});
|
119 |
+
}
|
120 |
+
};
|
121 |
+
|
122 |
+
//connect items with observableArrays
|
123 |
+
ko.bindingHandlers.sortable = {
|
124 |
+
init: function(element, valueAccessor, allBindingsAccessor, data, context) {
|
125 |
+
var $element = $(element),
|
126 |
+
value = unwrap(valueAccessor()) || {},
|
127 |
+
templateOptions = prepareTemplateOptions(valueAccessor, "foreach"),
|
128 |
+
sortable = {},
|
129 |
+
startActual, updateActual;
|
130 |
+
|
131 |
+
stripTemplateWhitespace(element, templateOptions.name);
|
132 |
+
|
133 |
+
//build a new object that has the global options with overrides from the binding
|
134 |
+
$.extend(true, sortable, ko.bindingHandlers.sortable);
|
135 |
+
if (value.options && sortable.options) {
|
136 |
+
ko.utils.extend(sortable.options, value.options);
|
137 |
+
delete value.options;
|
138 |
+
}
|
139 |
+
ko.utils.extend(sortable, value);
|
140 |
+
|
141 |
+
//if allowDrop is an observable or a function, then execute it in a computed observable
|
142 |
+
if (sortable.connectClass && (ko.isObservable(sortable.allowDrop) || typeof sortable.allowDrop == "function")) {
|
143 |
+
ko.computed({
|
144 |
+
read: function() {
|
145 |
+
var value = unwrap(sortable.allowDrop),
|
146 |
+
shouldAdd = typeof value == "function" ? value.call(this, templateOptions.foreach) : value;
|
147 |
+
ko.utils.toggleDomNodeCssClass(element, sortable.connectClass, shouldAdd);
|
148 |
+
},
|
149 |
+
disposeWhenNodeIsRemoved: element
|
150 |
+
}, this);
|
151 |
+
} else {
|
152 |
+
ko.utils.toggleDomNodeCssClass(element, sortable.connectClass, sortable.allowDrop);
|
153 |
+
}
|
154 |
+
|
155 |
+
//wrap the template binding
|
156 |
+
ko.bindingHandlers.template.init(element, function() { return templateOptions; }, allBindingsAccessor, data, context);
|
157 |
+
|
158 |
+
//keep a reference to start/update functions that might have been passed in
|
159 |
+
startActual = sortable.options.start;
|
160 |
+
updateActual = sortable.options.update;
|
161 |
+
|
162 |
+
//ensure draggable table row cells maintain their width while dragging (unless a helper is provided)
|
163 |
+
if ( !sortable.options.helper ) {
|
164 |
+
sortable.options.helper = function(e, ui) {
|
165 |
+
if (ui.is("tr")) {
|
166 |
+
ui.children().each(function() {
|
167 |
+
$(this).width($(this).width());
|
168 |
+
});
|
169 |
+
}
|
170 |
+
return ui;
|
171 |
+
};
|
172 |
+
}
|
173 |
+
|
174 |
+
//initialize sortable binding after template binding has rendered in update function
|
175 |
+
var createTimeout = setTimeout(function() {
|
176 |
+
var dragItem;
|
177 |
+
var originalReceive = sortable.options.receive;
|
178 |
+
|
179 |
+
$element.sortable(ko.utils.extend(sortable.options, {
|
180 |
+
start: function(event, ui) {
|
181 |
+
//track original index
|
182 |
+
var el = ui.item[0];
|
183 |
+
dataSet(el, INDEXKEY, ko.utils.arrayIndexOf(ui.item.parent().children(), el));
|
184 |
+
|
185 |
+
//make sure that fields have a chance to update model
|
186 |
+
ui.item.find("input:focus").change();
|
187 |
+
if (startActual) {
|
188 |
+
startActual.apply(this, arguments);
|
189 |
+
}
|
190 |
+
},
|
191 |
+
receive: function(event, ui) {
|
192 |
+
//optionally apply an existing receive handler
|
193 |
+
if (typeof originalReceive === "function") {
|
194 |
+
originalReceive.call(this, event, ui);
|
195 |
+
}
|
196 |
+
|
197 |
+
dragItem = dataGet(ui.item[0], DRAGKEY);
|
198 |
+
if (dragItem) {
|
199 |
+
//copy the model item, if a clone option is provided
|
200 |
+
if (dragItem.clone) {
|
201 |
+
dragItem = dragItem.clone();
|
202 |
+
}
|
203 |
+
|
204 |
+
//configure a handler to potentially manipulate item before drop
|
205 |
+
if (sortable.dragged) {
|
206 |
+
dragItem = sortable.dragged.call(this, dragItem, event, ui) || dragItem;
|
207 |
+
}
|
208 |
+
}
|
209 |
+
},
|
210 |
+
update: function(event, ui) {
|
211 |
+
var sourceParent, targetParent, sourceIndex, targetIndex, arg,
|
212 |
+
el = ui.item[0],
|
213 |
+
parentEl = ui.item.parent()[0],
|
214 |
+
item = dataGet(el, ITEMKEY) || dragItem;
|
215 |
+
|
216 |
+
if (!item) {
|
217 |
+
$(el).remove();
|
218 |
+
}
|
219 |
+
dragItem = null;
|
220 |
+
|
221 |
+
//make sure that moves only run once, as update fires on multiple containers
|
222 |
+
if (item && (this === parentEl) || (!hasNestedSortableFix && $.contains(this, parentEl))) {
|
223 |
+
//identify parents
|
224 |
+
sourceParent = dataGet(el, PARENTKEY);
|
225 |
+
sourceIndex = dataGet(el, INDEXKEY);
|
226 |
+
targetParent = dataGet(el.parentNode, LISTKEY);
|
227 |
+
targetIndex = ko.utils.arrayIndexOf(ui.item.parent().children(), el);
|
228 |
+
|
229 |
+
//take destroyed items into consideration
|
230 |
+
if (!templateOptions.includeDestroyed) {
|
231 |
+
sourceIndex = updateIndexFromDestroyedItems(sourceIndex, sourceParent);
|
232 |
+
targetIndex = updateIndexFromDestroyedItems(targetIndex, targetParent);
|
233 |
+
}
|
234 |
+
|
235 |
+
//build up args for the callbacks
|
236 |
+
if (sortable.beforeMove || sortable.afterMove) {
|
237 |
+
arg = {
|
238 |
+
item: item,
|
239 |
+
sourceParent: sourceParent,
|
240 |
+
sourceParentNode: sourceParent && ui.sender || el.parentNode,
|
241 |
+
sourceIndex: sourceIndex,
|
242 |
+
targetParent: targetParent,
|
243 |
+
targetIndex: targetIndex,
|
244 |
+
cancelDrop: false
|
245 |
+
};
|
246 |
+
|
247 |
+
//execute the configured callback prior to actually moving items
|
248 |
+
if (sortable.beforeMove) {
|
249 |
+
sortable.beforeMove.call(this, arg, event, ui);
|
250 |
+
}
|
251 |
+
}
|
252 |
+
|
253 |
+
//call cancel on the correct list, so KO can take care of DOM manipulation
|
254 |
+
if (sourceParent) {
|
255 |
+
$(sourceParent === targetParent ? this : ui.sender || this).sortable("cancel");
|
256 |
+
}
|
257 |
+
//for a draggable item just remove the element
|
258 |
+
else {
|
259 |
+
$(el).remove();
|
260 |
+
}
|
261 |
+
|
262 |
+
//if beforeMove told us to cancel, then we are done
|
263 |
+
if (arg && arg.cancelDrop) {
|
264 |
+
return;
|
265 |
+
}
|
266 |
+
|
267 |
+
//if the strategy option is unset or false, employ the order strategy involving removal and insertion of items
|
268 |
+
if (!sortable.hasOwnProperty("strategyMove") || sortable.strategyMove === false) {
|
269 |
+
//do the actual move
|
270 |
+
if (targetIndex >= 0) {
|
271 |
+
if (sourceParent) {
|
272 |
+
sourceParent.splice(sourceIndex, 1);
|
273 |
+
|
274 |
+
//if using deferred updates plugin, force updates
|
275 |
+
if (ko.processAllDeferredBindingUpdates) {
|
276 |
+
ko.processAllDeferredBindingUpdates();
|
277 |
+
}
|
278 |
+
|
279 |
+
//if using deferred updates on knockout 3.4, force updates
|
280 |
+
if (ko.options && ko.options.deferUpdates) {
|
281 |
+
ko.tasks.runEarly();
|
282 |
+
}
|
283 |
+
}
|
284 |
+
|
285 |
+
targetParent.splice(targetIndex, 0, item);
|
286 |
+
}
|
287 |
+
|
288 |
+
//rendering is handled by manipulating the observableArray; ignore dropped element
|
289 |
+
dataSet(el, ITEMKEY, null);
|
290 |
+
}
|
291 |
+
else { //employ the strategy of moving items
|
292 |
+
if (targetIndex >= 0) {
|
293 |
+
if (sourceParent) {
|
294 |
+
if (sourceParent !== targetParent) {
|
295 |
+
// moving from one list to another
|
296 |
+
|
297 |
+
sourceParent.splice(sourceIndex, 1);
|
298 |
+
targetParent.splice(targetIndex, 0, item);
|
299 |
+
|
300 |
+
//rendering is handled by manipulating the observableArray; ignore dropped element
|
301 |
+
dataSet(el, ITEMKEY, null);
|
302 |
+
ui.item.remove();
|
303 |
+
}
|
304 |
+
else {
|
305 |
+
// moving within same list
|
306 |
+
var underlyingList = unwrap(sourceParent);
|
307 |
+
|
308 |
+
// notify 'beforeChange' subscribers
|
309 |
+
if (sourceParent.valueWillMutate) {
|
310 |
+
sourceParent.valueWillMutate();
|
311 |
+
}
|
312 |
+
|
313 |
+
// move from source index ...
|
314 |
+
underlyingList.splice(sourceIndex, 1);
|
315 |
+
// ... to target index
|
316 |
+
underlyingList.splice(targetIndex, 0, item);
|
317 |
+
|
318 |
+
// notify subscribers
|
319 |
+
if (sourceParent.valueHasMutated) {
|
320 |
+
sourceParent.valueHasMutated();
|
321 |
+
}
|
322 |
+
}
|
323 |
+
}
|
324 |
+
else {
|
325 |
+
// drop new element from outside
|
326 |
+
targetParent.splice(targetIndex, 0, item);
|
327 |
+
|
328 |
+
//rendering is handled by manipulating the observableArray; ignore dropped element
|
329 |
+
dataSet(el, ITEMKEY, null);
|
330 |
+
ui.item.remove();
|
331 |
+
}
|
332 |
+
}
|
333 |
+
}
|
334 |
+
|
335 |
+
//if using deferred updates plugin, force updates
|
336 |
+
if (ko.processAllDeferredBindingUpdates) {
|
337 |
+
ko.processAllDeferredBindingUpdates();
|
338 |
+
}
|
339 |
+
|
340 |
+
//allow binding to accept a function to execute after moving the item
|
341 |
+
if (sortable.afterMove) {
|
342 |
+
sortable.afterMove.call(this, arg, event, ui);
|
343 |
+
}
|
344 |
+
}
|
345 |
+
|
346 |
+
if (updateActual) {
|
347 |
+
updateActual.apply(this, arguments);
|
348 |
+
}
|
349 |
+
},
|
350 |
+
connectWith: sortable.connectClass ? "." + sortable.connectClass : false
|
351 |
+
}));
|
352 |
+
|
353 |
+
//handle enabling/disabling sorting
|
354 |
+
if (sortable.isEnabled !== undefined) {
|
355 |
+
ko.computed({
|
356 |
+
read: function() {
|
357 |
+
$element.sortable(unwrap(sortable.isEnabled) ? "enable" : "disable");
|
358 |
+
},
|
359 |
+
disposeWhenNodeIsRemoved: element
|
360 |
+
});
|
361 |
+
}
|
362 |
+
}, 0);
|
363 |
+
|
364 |
+
//handle disposal
|
365 |
+
ko.utils.domNodeDisposal.addDisposeCallback(element, function() {
|
366 |
+
//only call destroy if sortable has been created
|
367 |
+
if ($element.data("ui-sortable") || $element.data("sortable")) {
|
368 |
+
$element.sortable("destroy");
|
369 |
+
}
|
370 |
+
|
371 |
+
ko.utils.toggleDomNodeCssClass(element, sortable.connectClass, false);
|
372 |
+
|
373 |
+
//do not create the sortable if the element has been removed from DOM
|
374 |
+
clearTimeout(createTimeout);
|
375 |
+
});
|
376 |
+
|
377 |
+
return { 'controlsDescendantBindings': true };
|
378 |
+
},
|
379 |
+
update: function(element, valueAccessor, allBindingsAccessor, data, context) {
|
380 |
+
var templateOptions = prepareTemplateOptions(valueAccessor, "foreach");
|
381 |
+
|
382 |
+
//attach meta-data
|
383 |
+
dataSet(element, LISTKEY, templateOptions.foreach);
|
384 |
+
|
385 |
+
//call template binding's update with correct options
|
386 |
+
ko.bindingHandlers.template.update(element, function() { return templateOptions; }, allBindingsAccessor, data, context);
|
387 |
+
},
|
388 |
+
connectClass: 'ko_container',
|
389 |
+
allowDrop: true,
|
390 |
+
afterMove: null,
|
391 |
+
beforeMove: null,
|
392 |
+
options: {}
|
393 |
+
};
|
394 |
+
|
395 |
+
//create a draggable that is appropriate for dropping into a sortable
|
396 |
+
ko.bindingHandlers.draggable = {
|
397 |
+
init: function(element, valueAccessor, allBindingsAccessor, data, context) {
|
398 |
+
var value = unwrap(valueAccessor()) || {},
|
399 |
+
options = value.options || {},
|
400 |
+
draggableOptions = ko.utils.extend({}, ko.bindingHandlers.draggable.options),
|
401 |
+
templateOptions = prepareTemplateOptions(valueAccessor, "data"),
|
402 |
+
connectClass = value.connectClass || ko.bindingHandlers.draggable.connectClass,
|
403 |
+
isEnabled = value.isEnabled !== undefined ? value.isEnabled : ko.bindingHandlers.draggable.isEnabled;
|
404 |
+
|
405 |
+
value = "data" in value ? value.data : value;
|
406 |
+
|
407 |
+
//set meta-data
|
408 |
+
dataSet(element, DRAGKEY, value);
|
409 |
+
|
410 |
+
//override global options with override options passed in
|
411 |
+
ko.utils.extend(draggableOptions, options);
|
412 |
+
|
413 |
+
//setup connection to a sortable
|
414 |
+
draggableOptions.connectToSortable = connectClass ? "." + connectClass : false;
|
415 |
+
|
416 |
+
//initialize draggable
|
417 |
+
$(element).draggable(draggableOptions);
|
418 |
+
|
419 |
+
//handle enabling/disabling sorting
|
420 |
+
if (isEnabled !== undefined) {
|
421 |
+
ko.computed({
|
422 |
+
read: function() {
|
423 |
+
$(element).draggable(unwrap(isEnabled) ? "enable" : "disable");
|
424 |
+
},
|
425 |
+
disposeWhenNodeIsRemoved: element
|
426 |
+
});
|
427 |
+
}
|
428 |
+
|
429 |
+
//handle disposal
|
430 |
+
ko.utils.domNodeDisposal.addDisposeCallback(element, function() {
|
431 |
+
if ($element.data("ui-draggable") || $element.data("draggable")) {
|
432 |
+
$element.draggable("destroy");
|
433 |
+
}
|
434 |
+
});
|
435 |
+
|
436 |
+
return ko.bindingHandlers.template.init(element, function() { return templateOptions; }, allBindingsAccessor, data, context);
|
437 |
+
},
|
438 |
+
update: function(element, valueAccessor, allBindingsAccessor, data, context) {
|
439 |
+
var templateOptions = prepareTemplateOptions(valueAccessor, "data");
|
440 |
+
|
441 |
+
return ko.bindingHandlers.template.update(element, function() { return templateOptions; }, allBindingsAccessor, data, context);
|
442 |
+
},
|
443 |
+
connectClass: ko.bindingHandlers.sortable.connectClass,
|
444 |
+
options: {
|
445 |
+
helper: "clone"
|
446 |
+
}
|
447 |
+
};
|
448 |
+
|
449 |
+
// Simple Droppable Implementation
|
450 |
+
// binding that updates (function or observable)
|
451 |
+
ko.bindingHandlers.droppable = {
|
452 |
+
init: function(element, valueAccessor, allBindingsAccessor, data, context) {
|
453 |
+
var value = unwrap(valueAccessor()) || {},
|
454 |
+
options = value.options || {},
|
455 |
+
droppableOptions = ko.utils.extend({}, ko.bindingHandlers.droppable.options),
|
456 |
+
isEnabled = value.isEnabled !== undefined ? value.isEnabled : ko.bindingHandlers.droppable.isEnabled;
|
457 |
+
|
458 |
+
//override global options with override options passed in
|
459 |
+
ko.utils.extend(droppableOptions, options);
|
460 |
+
|
461 |
+
//get reference to drop method
|
462 |
+
value = "data" in value ? value.data : valueAccessor();
|
463 |
+
|
464 |
+
//set drop method
|
465 |
+
droppableOptions.drop = function(event, ui) {
|
466 |
+
var droppedItem = dataGet(ui.draggable[0], DRAGKEY) || dataGet(ui.draggable[0], ITEMKEY);
|
467 |
+
value(droppedItem);
|
468 |
+
};
|
469 |
+
|
470 |
+
//initialize droppable
|
471 |
+
$(element).droppable(droppableOptions);
|
472 |
+
|
473 |
+
//handle enabling/disabling droppable
|
474 |
+
if (isEnabled !== undefined) {
|
475 |
+
ko.computed({
|
476 |
+
read: function() {
|
477 |
+
$(element).droppable(unwrap(isEnabled) ? "enable": "disable");
|
478 |
+
},
|
479 |
+
disposeWhenNodeIsRemoved: element
|
480 |
+
});
|
481 |
+
}
|
482 |
+
|
483 |
+
//handle disposal
|
484 |
+
ko.utils.domNodeDisposal.addDisposeCallback(element, function() {
|
485 |
+
if ($element.data("ui-droppable") || $element.data("droppable")) {
|
486 |
+
$element.droppable("destroy");
|
487 |
+
}
|
488 |
+
});
|
489 |
+
},
|
490 |
+
options: {
|
491 |
+
accept: "*"
|
492 |
+
}
|
493 |
+
};
|
494 |
+
});
|
modules/redirector/redirector-template.php
ADDED
@@ -0,0 +1,217 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
/**
|
3 |
+
* @var string $moduleTabUrl
|
4 |
+
*/
|
5 |
+
|
6 |
+
$dragIconUrl = plugins_url('drag-indicator.svg', __FILE__);
|
7 |
+
|
8 |
+
if ( defined('AME_DISABLE_REDIRECTS') && constant('AME_DISABLE_REDIRECTS') ) {
|
9 |
+
?>
|
10 |
+
<div class="notice notice-warning">
|
11 |
+
<p>
|
12 |
+
Custom redirects are currently disabled because <code>AME_DISABLE_REDIRECTS</code> is set to
|
13 |
+
<code>true</code>.
|
14 |
+
</p>
|
15 |
+
</div>
|
16 |
+
<?php
|
17 |
+
}
|
18 |
+
?>
|
19 |
+
<div id="ame-redirector-ui-root" data-bind="visible: isLoaded" style="display: none">
|
20 |
+
<!-- A second level of tabs, uh oh! -->
|
21 |
+
<ul data-bind="foreach: availableTriggers" role="tablist" class="ame-rui-trigger-selector ame-rui-sub-tabs">
|
22 |
+
<li class="ame-rui-tab" data-bind="css: { 'ame-rui-active-tab': ($data.trigger === $root.selectedTrigger()) }">
|
23 |
+
<a data-bind="
|
24 |
+
text: label,
|
25 |
+
click: $root.selectedTrigger.bind($root.selectedTrigger, $data.trigger),
|
26 |
+
attr: {'data-text': label}"
|
27 |
+
class="ame-rui-tab-label"
|
28 |
+
role="tab"></a>
|
29 |
+
</li>
|
30 |
+
</ul>
|
31 |
+
|
32 |
+
<div id="ame-rui-column-container">
|
33 |
+
<div id="ame-rui-main-section">
|
34 |
+
|
35 |
+
<!-- ko if: (selectedTrigger() === 'registration') -->
|
36 |
+
<p>
|
37 |
+
The registration redirect happens immediately after someone registers a new account
|
38 |
+
but before they log in for the first time. By default, the user is redirected to
|
39 |
+
a "check your email" page.
|
40 |
+
</p>
|
41 |
+
<!-- /ko -->
|
42 |
+
|
43 |
+
<!-- ko if: currentTriggerView().supportsUserSettings -->
|
44 |
+
<h3>Users</h3>
|
45 |
+
<div data-bind="
|
46 |
+
template: {name: 'ame-redirect-list-template', data: {items: currentTriggerView().users}},
|
47 |
+
visible: currentTriggerView().users().length > 0"></div>
|
48 |
+
|
49 |
+
<!-- ko if: (userSelectionUi === 'dropdown') -->
|
50 |
+
|
51 |
+
<p>
|
52 |
+
<!-- ko if: (addableUsers().length > 0) -->
|
53 |
+
<label for="ame-rui-add-user" class="screen-reader-text">Add a user</label>
|
54 |
+
<select id="ame-rui-add-user" class="ame-rui-add-actor-dropdown"
|
55 |
+
data-bind="options: addableUsers,
|
56 |
+
optionsText: 'userLogin',
|
57 |
+
value: selectedUserToAdd,
|
58 |
+
optionsCaption: 'Add a user',
|
59 |
+
hasFocus: userSelectorHasFocus"></select>
|
60 |
+
<!-- /ko -->
|
61 |
+
<!-- ko if: (addableUsers().length <= 0) -->
|
62 |
+
<span class="description">This list includes all users.</span>
|
63 |
+
<!-- /ko -->
|
64 |
+
</p>
|
65 |
+
<!-- /ko -->
|
66 |
+
<!-- ko if: (userSelectionUi === 'search') -->
|
67 |
+
<form method="post" data-bind="submit: addEnteredUserLogin.bind($root)">
|
68 |
+
<p>
|
69 |
+
<label for="ame-rui-user-search-query" class="screen-reader-text">Search users</label>
|
70 |
+
<input type="text" id="ame-rui-user-search-query" placeholder="Enter a username"
|
71 |
+
data-bind="
|
72 |
+
textInput: userLoginQuery,
|
73 |
+
ameRuiUserAutocomplete: { filter: filterUserAutocompleteResults.bind($root) }">
|
74 |
+
<input type="button" class="button" id="ame-rui-add-user" value="Add user"
|
75 |
+
data-bind="enable: addUserButtonEnabled, click: addEnteredUserLogin.bind($root)">
|
76 |
+
</p>
|
77 |
+
</form>
|
78 |
+
<!-- /ko -->
|
79 |
+
<!-- /ko -->
|
80 |
+
|
81 |
+
<!-- ko if: currentTriggerView().supportsRoleSettings -->
|
82 |
+
<h3>Roles</h3>
|
83 |
+
<div data-bind="
|
84 |
+
template: {name: 'ame-redirect-list-template', data: {items: currentTriggerView().roles}},
|
85 |
+
visible: currentTriggerView().roles().length > 0"></div>
|
86 |
+
|
87 |
+
<p>
|
88 |
+
<!-- ko if: (addableRoles().length > 0) -->
|
89 |
+
<label for="ame-rui-add-role" style="display: none;">Add a role</label>
|
90 |
+
<select id="ame-rui-add-role" class="ame-rui-add-actor-dropdown"
|
91 |
+
data-bind="options: addableRoles,
|
92 |
+
optionsText: 'displayName',
|
93 |
+
value: selectedRoleToAdd,
|
94 |
+
optionsCaption: 'Add a role',
|
95 |
+
hasFocus: roleSelectorHasFocus"></select>
|
96 |
+
<!-- /ko -->
|
97 |
+
<!-- ko if: (addableRoles().length <= 0) -->
|
98 |
+
<span class="description">This list includes all roles.</span>
|
99 |
+
<!-- /ko -->
|
100 |
+
</p>
|
101 |
+
<!-- /ko -->
|
102 |
+
|
103 |
+
<!-- ko if: currentTriggerView().supportsActorSettings -->
|
104 |
+
<h3>Default</h3>
|
105 |
+
<p>
|
106 |
+
The default setting will be applied to users who don't match any of the above rules. Leave the field
|
107 |
+
empty to let WordPress or other plugins choose the redirect.
|
108 |
+
</p>
|
109 |
+
<!-- /ko -->
|
110 |
+
<!-- ko ifnot: currentTriggerView().supportsActorSettings -->
|
111 |
+
<h3>All Users</h3>
|
112 |
+
<!-- /ko -->
|
113 |
+
<div data-bind="using: currentTriggerView().defaultRedirect" class="ame-rui-default-redirect-container">
|
114 |
+
<div class="ame-rui-redirect">
|
115 |
+
<div class="ame-rui-redirect-content">
|
116 |
+
<ame-redirect-url-input params="redirect: $data, menuItems: $root.menuItems"
|
117 |
+
class="ame-rui-url-template"></ame-redirect-url-input>
|
118 |
+
|
119 |
+
<label class="ame-rui-shortcodes-enabled" title="Process shortcodes in the redirect URL">
|
120 |
+
<input type="checkbox" data-bind="checked: shortcodesEnabled, enable: canToggleShortcodes">
|
121 |
+
<span class="dashicons dashicons-shortcode"></span>
|
122 |
+
<span class="ame-rui-button-label">Enable shortcodes</span>
|
123 |
+
</label>
|
124 |
+
</div>
|
125 |
+
</div>
|
126 |
+
</div>
|
127 |
+
|
128 |
+
</div>
|
129 |
+
<div id="ame-rui-sidebar">
|
130 |
+
<div id="ame-rui-main-actions">
|
131 |
+
|
132 |
+
</div>
|
133 |
+
</div>
|
134 |
+
</div>
|
135 |
+
|
136 |
+
<form class="ame-rui-save-form" method="post" data-bind="submit: saveChanges" action="<?php
|
137 |
+
echo esc_attr(add_query_arg(array('noheader' => '1'), $moduleTabUrl));
|
138 |
+
?>">
|
139 |
+
<?php
|
140 |
+
submit_button(
|
141 |
+
null, 'primary', 'submit', true,
|
142 |
+
[
|
143 |
+
'data-bind' => 'disable: isSaving',
|
144 |
+
'disabled' => 'disabled',
|
145 |
+
]
|
146 |
+
);
|
147 |
+
?>
|
148 |
+
<input type="hidden" name="settings" value="" data-bind="value: settingsData">
|
149 |
+
<input type="hidden" name="action" value="ame-save-redirect-settings">
|
150 |
+
<?php wp_nonce_field('ame-save-redirect-settings'); ?>
|
151 |
+
<input type="hidden" name="selectedTrigger" data-bind="value: selectedTrigger">
|
152 |
+
</form>
|
153 |
+
|
154 |
+
<label for="ame-rui-menu-items" style="display: none">Admin menu items</label>
|
155 |
+
<select name="ame-rui-menu-items" id="ame-rui-menu-items" size="10" style="display: none;"
|
156 |
+
data-bind="options: menuDropdownOptions, optionsText: 'title', value: selectedMenuDropdownItem">
|
157 |
+
</select>
|
158 |
+
</div>
|
159 |
+
|
160 |
+
<div style="display: none;">
|
161 |
+
<template id="ame-redirect-list-template">
|
162 |
+
<div data-bind="sortable: {
|
163 |
+
data: $data.items,
|
164 |
+
allowDrop: false,
|
165 |
+
options: {
|
166 |
+
handle: '.ame-rui-drag-handle'
|
167 |
+
}}"
|
168 |
+
class="ame-rui-redirect-list">
|
169 |
+
<div class="ame-rui-redirect">
|
170 |
+
<div class="ame-rui-drag-handle">
|
171 |
+
<img src="<?php echo esc_attr($dragIconUrl); ?>" alt="Drag indicator" width="24">
|
172 |
+
</div>
|
173 |
+
<div class="ame-rui-redirect-content">
|
174 |
+
<div class="ame-rui-actor">
|
175 |
+
<label data-bind="text: displayName(), attr: {'for': inputElementId}"></label>
|
176 |
+
<span class="ame-rui-missing-actor-indicator"
|
177 |
+
data-bind="
|
178 |
+
if: $root.isMissingActor(actor),
|
179 |
+
attr:{title: 'This ' + actorTypeNoun() + ' does not exist on the current site.'}">
|
180 |
+
(missing)
|
181 |
+
</span>
|
182 |
+
</div>
|
183 |
+
<ame-redirect-url-input params="redirect: $data, menuItems: $root.menuItems"
|
184 |
+
class="ame-rui-url-template"></ame-redirect-url-input>
|
185 |
+
<label class="ame-rui-shortcodes-enabled" title="Process shortcodes in the redirect URL">
|
186 |
+
<input type="checkbox" data-bind="checked: shortcodesEnabled, enable: canToggleShortcodes">
|
187 |
+
<span class="dashicons dashicons-shortcode"></span>
|
188 |
+
<span class="ame-rui-button-label">Enable shortcodes</span>
|
189 |
+
</label>
|
190 |
+
<div class="ame-rui-actions">
|
191 |
+
<button class="button ame-rui-delete" title="Remove redirect"
|
192 |
+
data-bind="click: $parent.items.remove.bind($parent.items, $data)">
|
193 |
+
<span class="dashicons dashicons-trash"></span>
|
194 |
+
<span class="ame-rui-button-label">Remove</span>
|
195 |
+
</button>
|
196 |
+
</div>
|
197 |
+
</div>
|
198 |
+
</div>
|
199 |
+
</div>
|
200 |
+
</template>
|
201 |
+
|
202 |
+
<template id="ame-redirect-url-component">
|
203 |
+
<!--suppress HtmlFormInputWithoutLabel -->
|
204 |
+
<input type="text"
|
205 |
+
data-bind="
|
206 |
+
value: displayValue,
|
207 |
+
attr: {'id' : redirect.inputElementId, 'readonly': isUrlReadonly},
|
208 |
+
hasFocus: redirect.inputHasFocus,
|
209 |
+
css: {'ame-rui-has-url-dropdown': redirect.urlDropdownEnabled}"
|
210 |
+
class="regular-text">
|
211 |
+
<!-- ko if: redirect.urlDropdownEnabled -->
|
212 |
+
<div class="ame-rui-url-dropdown-trigger">
|
213 |
+
<span class="am-rui-trigger-icon"></span>
|
214 |
+
</div>
|
215 |
+
<!-- /ko -->
|
216 |
+
</template>
|
217 |
+
</div>
|
modules/redirector/redirector-ui.js
ADDED
@@ -0,0 +1,797 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
/// <reference path="../../js/knockout.d.ts" />
|
2 |
+
/// <reference path="../../js/jquery.d.ts" />
|
3 |
+
/// <reference path="../../js/jqueryui.d.ts" />
|
4 |
+
/// <reference path="../../js/actor-manager.ts" />
|
5 |
+
/// <reference path="../actor-selector/actor-selector.ts" />
|
6 |
+
/// <reference path="../../js/common.d.ts" />
|
7 |
+
/// <reference path="../../ajax-wrapper/ajax-action-wrapper.d.ts" />
|
8 |
+
var __extends = (this && this.__extends) || (function () {
|
9 |
+
var extendStatics = function (d, b) {
|
10 |
+
extendStatics = Object.setPrototypeOf ||
|
11 |
+
({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) ||
|
12 |
+
function (d, b) { for (var p in b) if (Object.prototype.hasOwnProperty.call(b, p)) d[p] = b[p]; };
|
13 |
+
return extendStatics(d, b);
|
14 |
+
};
|
15 |
+
return function (d, b) {
|
16 |
+
if (typeof b !== "function" && b !== null)
|
17 |
+
throw new TypeError("Class extends value " + String(b) + " is not a constructor or null");
|
18 |
+
extendStatics(d, b);
|
19 |
+
function __() { this.constructor = d; }
|
20 |
+
d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __());
|
21 |
+
};
|
22 |
+
})();
|
23 |
+
var AmeRedirectorUi;
|
24 |
+
(function (AmeRedirectorUi) {
|
25 |
+
var AllKnownTriggers = {
|
26 |
+
login: null,
|
27 |
+
logout: null,
|
28 |
+
registration: null,
|
29 |
+
firstLogin: null
|
30 |
+
};
|
31 |
+
var _ = wsAmeLodash;
|
32 |
+
var AbstractTriggerDictionary = /** @class */ (function () {
|
33 |
+
function AbstractTriggerDictionary() {
|
34 |
+
}
|
35 |
+
return AbstractTriggerDictionary;
|
36 |
+
}());
|
37 |
+
var DefaultActorId = 'special:default';
|
38 |
+
var defaultActor = {
|
39 |
+
getDisplayName: function () {
|
40 |
+
return 'Default';
|
41 |
+
},
|
42 |
+
getId: function () {
|
43 |
+
return DefaultActorId;
|
44 |
+
}
|
45 |
+
};
|
46 |
+
var Redirect = /** @class */ (function () {
|
47 |
+
function Redirect(properties, actorProvider) {
|
48 |
+
var _this = this;
|
49 |
+
if (actorProvider === void 0) { actorProvider = null; }
|
50 |
+
this.actorId = properties.actorId;
|
51 |
+
this.trigger = properties.trigger;
|
52 |
+
this.urlTemplate = ko.observable(properties.urlTemplate);
|
53 |
+
this.menuTemplateId = ko.observable(properties.hasOwnProperty('menuTemplateId') ? properties.menuTemplateId : '');
|
54 |
+
this.canToggleShortcodes = ko.pureComputed(function () {
|
55 |
+
return (_this.menuTemplateId().trim() === '');
|
56 |
+
});
|
57 |
+
this.inputHasFocus = ko.observable(false);
|
58 |
+
var internalShortcodesEnabled = ko.observable(properties.shortcodesEnabled);
|
59 |
+
this.shortcodesEnabled = ko.computed({
|
60 |
+
read: function () {
|
61 |
+
//All of the menu items use shortcodes to generate the admin page URL,
|
62 |
+
//so shortcodes must be enabled when a menu item is selected.
|
63 |
+
var menu = _this.menuTemplateId().trim();
|
64 |
+
if (menu !== '') {
|
65 |
+
return true;
|
66 |
+
}
|
67 |
+
return internalShortcodesEnabled();
|
68 |
+
},
|
69 |
+
write: function (value) {
|
70 |
+
if (!_this.canToggleShortcodes()) {
|
71 |
+
return;
|
72 |
+
}
|
73 |
+
internalShortcodesEnabled(value);
|
74 |
+
},
|
75 |
+
deferEvaluation: true
|
76 |
+
});
|
77 |
+
if (this.actorId === DefaultActorId) {
|
78 |
+
this.actor = defaultActor;
|
79 |
+
}
|
80 |
+
else {
|
81 |
+
var provider = actorProvider ? actorProvider : AmeActors;
|
82 |
+
this.actor = provider.getActor(this.actorId);
|
83 |
+
}
|
84 |
+
this.actorTypeNoun = ko.pureComputed(function () {
|
85 |
+
var prefix = _this.actorId.substring(0, _this.actorId.indexOf(':'));
|
86 |
+
if (prefix === 'user') {
|
87 |
+
return 'user';
|
88 |
+
}
|
89 |
+
else if (prefix === 'role') {
|
90 |
+
return 'role';
|
91 |
+
}
|
92 |
+
return 'item';
|
93 |
+
});
|
94 |
+
this.urlDropdownEnabled = ko.pureComputed(function () {
|
95 |
+
//If a menu item is already selected in the dropdown, the dropdown has to be enabled
|
96 |
+
//to give the user the ability to select something else.
|
97 |
+
var menu = _this.menuTemplateId().trim();
|
98 |
+
if (menu !== '') {
|
99 |
+
return true;
|
100 |
+
}
|
101 |
+
//The dropdown only contains admin menu items, so it's only useful if the user
|
102 |
+
//can access the admin dashboard after the trigger happens.
|
103 |
+
//Note: This may need to change if we add other options to the dropdown.
|
104 |
+
return (_this.trigger === 'login') || (_this.trigger === 'firstLogin');
|
105 |
+
});
|
106 |
+
Redirect.inputCounter++;
|
107 |
+
this.inputElementId = 'ame-rui-unique-input-' + Redirect.inputCounter;
|
108 |
+
}
|
109 |
+
Redirect.prototype.toJs = function () {
|
110 |
+
var result = {
|
111 |
+
actorId: this.actorId,
|
112 |
+
urlTemplate: this.urlTemplate().trim(),
|
113 |
+
shortcodesEnabled: this.shortcodesEnabled(),
|
114 |
+
trigger: this.trigger
|
115 |
+
};
|
116 |
+
var menu = this.menuTemplateId().trim();
|
117 |
+
if (menu !== '') {
|
118 |
+
result.menuTemplateId = menu;
|
119 |
+
}
|
120 |
+
return result;
|
121 |
+
};
|
122 |
+
Redirect.prototype.displayName = function () {
|
123 |
+
if (this.actor.hasOwnProperty('userLogin')) {
|
124 |
+
var user = this.actor;
|
125 |
+
return user.userLogin;
|
126 |
+
}
|
127 |
+
else {
|
128 |
+
return this.actor.getDisplayName();
|
129 |
+
}
|
130 |
+
};
|
131 |
+
Redirect.inputCounter = 0;
|
132 |
+
return Redirect;
|
133 |
+
}());
|
134 |
+
AmeRedirectorUi.Redirect = Redirect;
|
135 |
+
var TriggerView = /** @class */ (function () {
|
136 |
+
function TriggerView(trigger, supportsUserSettings, supportsRoleSettings) {
|
137 |
+
var _this = this;
|
138 |
+
if (supportsUserSettings === void 0) { supportsUserSettings = null; }
|
139 |
+
if (supportsRoleSettings === void 0) { supportsRoleSettings = null; }
|
140 |
+
this.users = ko.observableArray([]);
|
141 |
+
this.roles = ko.observableArray([]);
|
142 |
+
this.supportsUserSettings = true;
|
143 |
+
this.supportsRoleSettings = true;
|
144 |
+
if (supportsUserSettings !== null) {
|
145 |
+
this.supportsUserSettings = supportsUserSettings;
|
146 |
+
}
|
147 |
+
if (supportsRoleSettings !== null) {
|
148 |
+
this.supportsRoleSettings = supportsRoleSettings;
|
149 |
+
}
|
150 |
+
this.supportsActorSettings = ko.pureComputed(function () {
|
151 |
+
return _this.supportsUserSettings || _this.supportsRoleSettings;
|
152 |
+
});
|
153 |
+
this.defaultRedirect = ko.observable(new Redirect({
|
154 |
+
actorId: 'special:default',
|
155 |
+
trigger: trigger,
|
156 |
+
shortcodesEnabled: true,
|
157 |
+
urlTemplate: ''
|
158 |
+
}));
|
159 |
+
}
|
160 |
+
TriggerView.prototype.add = function (item) {
|
161 |
+
var actorId = item.actorId;
|
162 |
+
if (actorId === DefaultActorId) {
|
163 |
+
this.defaultRedirect(item);
|
164 |
+
}
|
165 |
+
else if (actorId === 'special:super_admin') {
|
166 |
+
this.roles.push(item);
|
167 |
+
}
|
168 |
+
else {
|
169 |
+
var actorType = actorId.substring(0, actorId.indexOf(':'));
|
170 |
+
switch (actorType) {
|
171 |
+
case 'user':
|
172 |
+
this.users.push(item);
|
173 |
+
break;
|
174 |
+
case 'role':
|
175 |
+
this.roles.push(item);
|
176 |
+
break;
|
177 |
+
default:
|
178 |
+
console.log('Unknown actor type for a trigger view: ' + actorType);
|
179 |
+
}
|
180 |
+
}
|
181 |
+
};
|
182 |
+
TriggerView.prototype.toArray = function () {
|
183 |
+
var results = [];
|
184 |
+
results.push.apply(results, this.users());
|
185 |
+
results.push.apply(results, this.roles());
|
186 |
+
//Include the default redirect only if it's not empty.
|
187 |
+
var defaultRedirect = this.defaultRedirect();
|
188 |
+
var url = defaultRedirect.urlTemplate().trim();
|
189 |
+
if (url !== '') {
|
190 |
+
results.push(defaultRedirect);
|
191 |
+
}
|
192 |
+
return results;
|
193 |
+
};
|
194 |
+
return TriggerView;
|
195 |
+
}());
|
196 |
+
var MenuCollection = /** @class */ (function () {
|
197 |
+
function MenuCollection(usableMenuItems) {
|
198 |
+
this.menusByTemplate = {};
|
199 |
+
this.menusByTemplate = {};
|
200 |
+
for (var i = 0; i < usableMenuItems.length; i++) {
|
201 |
+
this.menusByTemplate[usableMenuItems[i].templateId] = usableMenuItems[i];
|
202 |
+
}
|
203 |
+
}
|
204 |
+
MenuCollection.prototype.findSelectedMenu = function (redirect) {
|
205 |
+
var templateId = redirect.menuTemplateId();
|
206 |
+
if (templateId === '') {
|
207 |
+
return null;
|
208 |
+
}
|
209 |
+
if (!this.menusByTemplate.hasOwnProperty(templateId)) {
|
210 |
+
return null;
|
211 |
+
}
|
212 |
+
var menu = this.menusByTemplate[templateId];
|
213 |
+
var url = redirect.urlTemplate();
|
214 |
+
if (menu.url === url) {
|
215 |
+
return menu;
|
216 |
+
}
|
217 |
+
return null;
|
218 |
+
};
|
219 |
+
return MenuCollection;
|
220 |
+
}());
|
221 |
+
var RedirectsByTrigger = /** @class */ (function (_super) {
|
222 |
+
__extends(RedirectsByTrigger, _super);
|
223 |
+
function RedirectsByTrigger() {
|
224 |
+
var _this = _super.call(this) || this;
|
225 |
+
_this.login = new TriggerView('login');
|
226 |
+
_this.logout = new TriggerView('logout');
|
227 |
+
_this.registration = new TriggerView('registration', false, false);
|
228 |
+
_this.firstLogin = new TriggerView('firstLogin', false, true);
|
229 |
+
return _this;
|
230 |
+
}
|
231 |
+
RedirectsByTrigger.fromArray = function (redirects) {
|
232 |
+
var instance = new RedirectsByTrigger();
|
233 |
+
var length = redirects.length;
|
234 |
+
for (var i = 0; i < length; i++) {
|
235 |
+
var item = redirects[i];
|
236 |
+
if (instance.hasOwnProperty(item.trigger)) {
|
237 |
+
var view = instance[item.trigger];
|
238 |
+
view.add(item);
|
239 |
+
}
|
240 |
+
}
|
241 |
+
return instance;
|
242 |
+
};
|
243 |
+
RedirectsByTrigger.prototype.toArray = function () {
|
244 |
+
var results = [];
|
245 |
+
for (var key in AllKnownTriggers) {
|
246 |
+
if (this.hasOwnProperty(key)) {
|
247 |
+
var view = this[key];
|
248 |
+
results.push.apply(results, view.toArray());
|
249 |
+
}
|
250 |
+
}
|
251 |
+
//Remove redirects that don't have a URL.
|
252 |
+
results = results.filter(function (redirect) {
|
253 |
+
var url = redirect.urlTemplate().trim();
|
254 |
+
return ((typeof url) === 'string') && (url !== '');
|
255 |
+
});
|
256 |
+
return results;
|
257 |
+
};
|
258 |
+
return RedirectsByTrigger;
|
259 |
+
}(AbstractTriggerDictionary));
|
260 |
+
var RedirectUrlInputComponent = /** @class */ (function () {
|
261 |
+
function RedirectUrlInputComponent(params) {
|
262 |
+
var _this = this;
|
263 |
+
this.redirect = ko.unwrap(params.redirect);
|
264 |
+
this.menuItems = params.menuItems;
|
265 |
+
this.displayValue = ko.computed({
|
266 |
+
read: function () {
|
267 |
+
var menu = _this.menuItems.findSelectedMenu(_this.redirect);
|
268 |
+
if (menu) {
|
269 |
+
return menu.title;
|
270 |
+
}
|
271 |
+
else {
|
272 |
+
return _this.redirect.urlTemplate();
|
273 |
+
}
|
274 |
+
},
|
275 |
+
write: function (value) {
|
276 |
+
var menu = _this.menuItems.findSelectedMenu(_this.redirect);
|
277 |
+
if (menu !== null) {
|
278 |
+
//Can't manually edit the URL because a menu item is selected.
|
279 |
+
return;
|
280 |
+
}
|
281 |
+
_this.redirect.urlTemplate(value);
|
282 |
+
}
|
283 |
+
});
|
284 |
+
this.isUrlReadonly = ko.pureComputed(function () {
|
285 |
+
if (_this.menuItems.findSelectedMenu(_this.redirect) !== null) {
|
286 |
+
return true;
|
287 |
+
}
|
288 |
+
return null;
|
289 |
+
});
|
290 |
+
}
|
291 |
+
return RedirectUrlInputComponent;
|
292 |
+
}());
|
293 |
+
AmeRedirectorUi.RedirectUrlInputComponent = RedirectUrlInputComponent;
|
294 |
+
/**
|
295 |
+
* Proxy class that automatically creates placeholders for missing actors.
|
296 |
+
*/
|
297 |
+
var ActorProviderProxy = /** @class */ (function () {
|
298 |
+
function ActorProviderProxy(realProvider) {
|
299 |
+
this.provider = realProvider;
|
300 |
+
this.placeholders = {};
|
301 |
+
}
|
302 |
+
ActorProviderProxy.prototype.getActor = function (actorId) {
|
303 |
+
if (actorId === DefaultActorId) {
|
304 |
+
return defaultActor;
|
305 |
+
}
|
306 |
+
var existingActor = this.provider.getActor(actorId);
|
307 |
+
if (existingActor) {
|
308 |
+
return existingActor;
|
309 |
+
}
|
310 |
+
else if (this.placeholders.hasOwnProperty(actorId)) {
|
311 |
+
return this.placeholders[actorId];
|
312 |
+
}
|
313 |
+
//If the actor hasn't been loaded or created by now, that means it has been deleted
|
314 |
+
//or it was invalid to begin with. Let's use a placeholder object to represent it.
|
315 |
+
var missingActor;
|
316 |
+
if (_.startsWith(actorId, 'user:')) {
|
317 |
+
missingActor = new MissingUserPlaceholder(actorId);
|
318 |
+
}
|
319 |
+
else if (_.startsWith(actorId, 'role:')) {
|
320 |
+
missingActor = new MissingRolePlaceholder(actorId);
|
321 |
+
}
|
322 |
+
else {
|
323 |
+
missingActor = new MissingActorPlaceholder(actorId);
|
324 |
+
}
|
325 |
+
this.placeholders[actorId] = missingActor;
|
326 |
+
return missingActor;
|
327 |
+
};
|
328 |
+
return ActorProviderProxy;
|
329 |
+
}());
|
330 |
+
var MinimalUser = /** @class */ (function (_super) {
|
331 |
+
__extends(MinimalUser, _super);
|
332 |
+
function MinimalUser() {
|
333 |
+
return _super !== null && _super.apply(this, arguments) || this;
|
334 |
+
}
|
335 |
+
MinimalUser.createFromProperties = function (properties) {
|
336 |
+
return new MinimalUser(properties.user_login, properties.display_name, {}, [], false);
|
337 |
+
};
|
338 |
+
return MinimalUser;
|
339 |
+
}(AmeUser));
|
340 |
+
AmeRedirectorUi.MinimalUser = MinimalUser;
|
341 |
+
var MissingActorPlaceholder = /** @class */ (function () {
|
342 |
+
function MissingActorPlaceholder(id, displayName) {
|
343 |
+
if (displayName === void 0) { displayName = null; }
|
344 |
+
this.actorId = id;
|
345 |
+
if (displayName !== null) {
|
346 |
+
this.displayName = displayName;
|
347 |
+
}
|
348 |
+
else {
|
349 |
+
this.displayName = this.idWithoutPrefix(id);
|
350 |
+
}
|
351 |
+
}
|
352 |
+
MissingActorPlaceholder.prototype.getDisplayName = function () {
|
353 |
+
return this.displayName;
|
354 |
+
};
|
355 |
+
MissingActorPlaceholder.prototype.getId = function () {
|
356 |
+
return this.actorId;
|
357 |
+
};
|
358 |
+
MissingActorPlaceholder.prototype.idWithoutPrefix = function (actorId) {
|
359 |
+
var delimiterPos = actorId.indexOf(':');
|
360 |
+
if (delimiterPos < 0) {
|
361 |
+
return actorId;
|
362 |
+
}
|
363 |
+
return actorId.substring(delimiterPos + 1);
|
364 |
+
};
|
365 |
+
return MissingActorPlaceholder;
|
366 |
+
}());
|
367 |
+
var MissingRolePlaceholder = /** @class */ (function (_super) {
|
368 |
+
__extends(MissingRolePlaceholder, _super);
|
369 |
+
function MissingRolePlaceholder() {
|
370 |
+
return _super !== null && _super.apply(this, arguments) || this;
|
371 |
+
}
|
372 |
+
return MissingRolePlaceholder;
|
373 |
+
}(MissingActorPlaceholder));
|
374 |
+
var MissingUserPlaceholder = /** @class */ (function (_super) {
|
375 |
+
__extends(MissingUserPlaceholder, _super);
|
376 |
+
function MissingUserPlaceholder(actorId) {
|
377 |
+
var _this = _super.call(this, actorId) || this;
|
378 |
+
_this.isSuperAdmin = false;
|
379 |
+
_this.userLogin = _this.idWithoutPrefix(actorId);
|
380 |
+
return _this;
|
381 |
+
}
|
382 |
+
return MissingUserPlaceholder;
|
383 |
+
}(MissingActorPlaceholder));
|
384 |
+
var App = /** @class */ (function () {
|
385 |
+
function App(settings) {
|
386 |
+
var _this = this;
|
387 |
+
this.isLoaded = ko.observable(false);
|
388 |
+
this.availableTriggers = [
|
389 |
+
{ trigger: 'login', label: 'Login Redirect' },
|
390 |
+
{ trigger: 'logout', label: 'Logout Redirect' },
|
391 |
+
{ trigger: 'registration', label: 'Registration Redirect' },
|
392 |
+
{ trigger: 'firstLogin', label: 'First Login Redirect' }
|
393 |
+
];
|
394 |
+
this.customUrlOption = {
|
395 |
+
templateId: '',
|
396 |
+
url: '',
|
397 |
+
title: '[ Custom URL ]'
|
398 |
+
};
|
399 |
+
this.ignoreNextDropdownClick = null;
|
400 |
+
this.userSelectionUi = 'dropdown';
|
401 |
+
var self = this;
|
402 |
+
this.actorProvider = new ActorProviderProxy(AmeActors);
|
403 |
+
//Users need to be loaded before redirects because redirects use actor objects.
|
404 |
+
var loadedUsers = settings.users.map(function (props) {
|
405 |
+
var existingInstance = AmeActors.getUser(props.user_login);
|
406 |
+
if (existingInstance) {
|
407 |
+
return existingInstance;
|
408 |
+
}
|
409 |
+
else {
|
410 |
+
var newUser = MinimalUser.createFromProperties(props);
|
411 |
+
AmeActors.addUsers([newUser]);
|
412 |
+
return newUser;
|
413 |
+
}
|
414 |
+
});
|
415 |
+
loadedUsers.sort(function (a, b) {
|
416 |
+
return a.userLogin.localeCompare(b.userLogin);
|
417 |
+
});
|
418 |
+
this.redirects = ko.observableArray(settings.redirects.map(function (props) { return new Redirect(props, _this.actorProvider); }));
|
419 |
+
this.menuItems = new MenuCollection(settings.usableMenuItems);
|
420 |
+
this.menuDropdownOptions = [this.customUrlOption].concat(settings.usableMenuItems);
|
421 |
+
this.menuDropdownParent = ko.observable(null);
|
422 |
+
this.selectedMenuDropdownItem = ko.computed({
|
423 |
+
read: function () {
|
424 |
+
var currentRedirect = _this.menuDropdownParent();
|
425 |
+
if (currentRedirect === null) {
|
426 |
+
return _this.customUrlOption;
|
427 |
+
}
|
428 |
+
else {
|
429 |
+
//Find the option that matches this template ID and URL.
|
430 |
+
var foundMenu = _this.menuItems.findSelectedMenu(currentRedirect);
|
431 |
+
if (foundMenu === null) {
|
432 |
+
foundMenu = _this.customUrlOption;
|
433 |
+
}
|
434 |
+
return foundMenu;
|
435 |
+
}
|
436 |
+
},
|
437 |
+
write: function (newValue) {
|
438 |
+
var currentRedirect = _this.menuDropdownParent();
|
439 |
+
if (!currentRedirect) {
|
440 |
+
return; //Nothing to do!
|
441 |
+
}
|
442 |
+
if (!newValue) {
|
443 |
+
newValue = _this.customUrlOption;
|
444 |
+
}
|
445 |
+
currentRedirect.menuTemplateId(newValue.templateId);
|
446 |
+
if (newValue.templateId !== '') {
|
447 |
+
currentRedirect.urlTemplate(newValue.url);
|
448 |
+
}
|
449 |
+
},
|
450 |
+
owner: self,
|
451 |
+
deferEvaluation: true
|
452 |
+
});
|
453 |
+
this.menuDropdown = jQuery('#ame-rui-menu-items');
|
454 |
+
//Hide the dropdown when it loses focus.
|
455 |
+
this.menuDropdown.on('blur', function () {
|
456 |
+
_this.closeMenuDropdown();
|
457 |
+
});
|
458 |
+
this.menuDropdown.on('keydown', function (event) {
|
459 |
+
//Also hide the dropdown if the user presses Esc.
|
460 |
+
if (event.which === 27) {
|
461 |
+
_this.closeMenuDropdown(true);
|
462 |
+
}
|
463 |
+
else if (event.which === 13) {
|
464 |
+
//Close the dropdown when the user presses Enter.
|
465 |
+
//Since we currently update the redirect on every change, there's no difference between
|
466 |
+
//this and pressing Esc.
|
467 |
+
_this.closeMenuDropdown(true);
|
468 |
+
}
|
469 |
+
});
|
470 |
+
//Close the dropdown when the user selects an option by clicking it.
|
471 |
+
this.menuDropdown.on('click', 'option', function () {
|
472 |
+
_this.closeMenuDropdown();
|
473 |
+
});
|
474 |
+
//this.addTestData();
|
475 |
+
this.byTrigger = ko.observable(RedirectsByTrigger.fromArray(this.redirects()));
|
476 |
+
//Reselect the previous trigger, or just the first trigger.
|
477 |
+
this.selectedTrigger = ko.observable(settings.selectedTrigger ? settings.selectedTrigger : this.availableTriggers[0].trigger);
|
478 |
+
this.currentTriggerView = ko.pureComputed(function () {
|
479 |
+
var trigger = _this.selectedTrigger();
|
480 |
+
var mapping = _this.byTrigger();
|
481 |
+
if (mapping.hasOwnProperty(trigger) && (mapping[trigger] instanceof TriggerView)) {
|
482 |
+
return mapping[trigger];
|
483 |
+
}
|
484 |
+
else {
|
485 |
+
return mapping.login;
|
486 |
+
}
|
487 |
+
});
|
488 |
+
this.addableRoles = ko.pureComputed(function () {
|
489 |
+
var allRoles = _.values(AmeActors.getRoles());
|
490 |
+
var usedRoles = _.map(_this.currentTriggerView().roles(), function (redirect) {
|
491 |
+
return redirect.actor;
|
492 |
+
});
|
493 |
+
return _.difference(allRoles, usedRoles);
|
494 |
+
});
|
495 |
+
this.selectedRoleToAdd = ko.observable(void 0);
|
496 |
+
this.roleSelectorHasFocus = ko.observable(false);
|
497 |
+
this.addableUsers = ko.pureComputed(function () {
|
498 |
+
var usedUsers = _.map(_this.currentTriggerView().users(), function (redirect) {
|
499 |
+
return redirect.actor;
|
500 |
+
});
|
501 |
+
return _.difference(loadedUsers, usedUsers);
|
502 |
+
});
|
503 |
+
this.selectedUserToAdd = ko.observable(void 0);
|
504 |
+
this.userSelectorHasFocus = ko.observable(false);
|
505 |
+
this.selectedRoleToAdd.subscribe(function (newSelection) {
|
506 |
+
_this.addSelectedActorTo(newSelection, _this.currentTriggerView().roles);
|
507 |
+
_this.roleSelectorHasFocus(false);
|
508 |
+
_this.selectedRoleToAdd(void 0);
|
509 |
+
});
|
510 |
+
this.selectedUserToAdd.subscribe(function (newSelection) {
|
511 |
+
_this.addSelectedActorTo(newSelection, _this.currentTriggerView().users);
|
512 |
+
_this.userSelectorHasFocus(false);
|
513 |
+
_this.selectedUserToAdd(void 0);
|
514 |
+
});
|
515 |
+
this.userLoginQuery = ko.observable('');
|
516 |
+
this.addUserButtonEnabled = ko.pureComputed(function () {
|
517 |
+
return (_this.userLoginQuery().trim() !== '');
|
518 |
+
});
|
519 |
+
if (settings.hasMoreUsers) {
|
520 |
+
this.userSelectionUi = 'search';
|
521 |
+
}
|
522 |
+
this.isSaving = ko.observable(false);
|
523 |
+
this.settingsData = ko.observable('');
|
524 |
+
this.isLoaded(true);
|
525 |
+
}
|
526 |
+
App.prototype.getSettings = function () {
|
527 |
+
return {
|
528 |
+
redirects: this.byTrigger().toArray().map(function (redirect) { return redirect.toJs(); })
|
529 |
+
};
|
530 |
+
};
|
531 |
+
App.prototype.onDropdownTrigger = function (event) {
|
532 |
+
//Note: There probably is some jQuery feature or library that makes dropdowns easier,
|
533 |
+
//but I already did this the hard way.
|
534 |
+
var $input = jQuery(event.target).closest('.ame-rui-url-template,ame-redirect-url-input').find('input').first();
|
535 |
+
var $node = $input.closest('.ame-rui-redirect');
|
536 |
+
if ($node.length < 1) {
|
537 |
+
return;
|
538 |
+
}
|
539 |
+
var redirect = ko.dataFor($node.get(0));
|
540 |
+
if (!(redirect instanceof AmeRedirectorUi.Redirect)) {
|
541 |
+
return;
|
542 |
+
}
|
543 |
+
//Clicking the same trigger a second time closes the dropdown.
|
544 |
+
if (event.type === 'mousedown') {
|
545 |
+
var isSameTrigger = this.menuDropdown.is(':visible') && (this.menuDropdownParent() === redirect);
|
546 |
+
if (isSameTrigger) {
|
547 |
+
//The dropdown will be automatically closed by its "blur" event handler,
|
548 |
+
//but we need to ignore the next click event on this element.
|
549 |
+
this.ignoreNextDropdownClick = event.target;
|
550 |
+
}
|
551 |
+
else {
|
552 |
+
this.ignoreNextDropdownClick = null;
|
553 |
+
}
|
554 |
+
return;
|
555 |
+
}
|
556 |
+
if ((event.type === 'click') && (event.target === this.ignoreNextDropdownClick)) {
|
557 |
+
return;
|
558 |
+
}
|
559 |
+
//Move the drop-down near the input box.
|
560 |
+
this.menuDropdown
|
561 |
+
.css({
|
562 |
+
position: 'absolute',
|
563 |
+
zIndex: 100 //The dropdown should be displayed above other elements. This may not be required.
|
564 |
+
})
|
565 |
+
.show()
|
566 |
+
.outerWidth(Math.max($input.outerWidth(), 100))
|
567 |
+
.position({
|
568 |
+
my: 'right top',
|
569 |
+
at: 'right bottom',
|
570 |
+
of: $input
|
571 |
+
});
|
572 |
+
//Move focus to the dropdown.
|
573 |
+
var $select = this.menuDropdown;
|
574 |
+
if (!this.menuDropdown.is('select, input')) {
|
575 |
+
$select = this.menuDropdown.find('select, input').first();
|
576 |
+
}
|
577 |
+
$select.trigger('focus');
|
578 |
+
//Select the current option and scroll it into view. It looks like the browser will automatically
|
579 |
+
//scroll to the selected option, but only if the select element is already visible, so we need to
|
580 |
+
//do this *after* we show the dropdown.
|
581 |
+
this.menuDropdownParent(redirect);
|
582 |
+
};
|
583 |
+
App.prototype.closeMenuDropdown = function (moveFocusToInput) {
|
584 |
+
if (moveFocusToInput === void 0) { moveFocusToInput = false; }
|
585 |
+
var currentRedirect = this.menuDropdownParent();
|
586 |
+
this.menuDropdown.hide();
|
587 |
+
this.menuDropdownParent(null);
|
588 |
+
//Refocus on the URL input after closing the dropdown.
|
589 |
+
if (moveFocusToInput && currentRedirect) {
|
590 |
+
currentRedirect.inputHasFocus(true);
|
591 |
+
}
|
592 |
+
};
|
593 |
+
App.prototype.addSelectedActorTo = function (actor, list) {
|
594 |
+
//The list includes a caption item that is displayed when nothing is selected.
|
595 |
+
//The value of that option is supposed to be undefined.
|
596 |
+
if ((typeof actor === 'undefined') || (actor === null) || !this.currentTriggerView()) {
|
597 |
+
return;
|
598 |
+
}
|
599 |
+
//Add a redirect for the selected role.
|
600 |
+
var newRedirect = new Redirect({
|
601 |
+
actorId: actor.getId(),
|
602 |
+
shortcodesEnabled: true,
|
603 |
+
urlTemplate: '',
|
604 |
+
trigger: this.selectedTrigger()
|
605 |
+
}, this.actorProvider);
|
606 |
+
list.push(newRedirect);
|
607 |
+
newRedirect.inputHasFocus(true);
|
608 |
+
};
|
609 |
+
App.prototype.addEnteredUserLogin = function () {
|
610 |
+
var userLogin = this.userLoginQuery().trim();
|
611 |
+
if (userLogin === '') {
|
612 |
+
return;
|
613 |
+
}
|
614 |
+
var actorId = 'user:' + userLogin;
|
615 |
+
if (!AmeActors.actorExists(actorId)) {
|
616 |
+
if (console && console.warn) {
|
617 |
+
console.warn('User "' + userLogin + '" has not been initialized. Creating a minimal actor now.');
|
618 |
+
}
|
619 |
+
AmeActors.addUsers([
|
620 |
+
MinimalUser.createFromProperties({
|
621 |
+
user_login: userLogin,
|
622 |
+
display_name: userLogin
|
623 |
+
})
|
624 |
+
]);
|
625 |
+
}
|
626 |
+
//Only add each user once.
|
627 |
+
var alreadyAdded = _.some(this.currentTriggerView().users(), function (redirect) {
|
628 |
+
return redirect.actorId === actorId;
|
629 |
+
});
|
630 |
+
if (alreadyAdded) {
|
631 |
+
alert('Error: Duplicate entry. User "' + userLogin + '" has already been added.');
|
632 |
+
return;
|
633 |
+
}
|
634 |
+
var newRedirect = new Redirect({
|
635 |
+
actorId: actorId,
|
636 |
+
shortcodesEnabled: true,
|
637 |
+
urlTemplate: '',
|
638 |
+
trigger: this.selectedTrigger()
|
639 |
+
}, this.actorProvider);
|
640 |
+
this.currentTriggerView().users.push(newRedirect);
|
641 |
+
this.userLoginQuery('');
|
642 |
+
};
|
643 |
+
App.prototype.filterUserAutocompleteResults = function (results) {
|
644 |
+
//Filter out users that are already in the current list.
|
645 |
+
var usedLogins = _.indexBy(this.currentTriggerView().users(), function (redirect) {
|
646 |
+
return redirect.actor.userLogin;
|
647 |
+
});
|
648 |
+
return _.filter(results, function (props) {
|
649 |
+
return !(usedLogins.hasOwnProperty(props.user_login));
|
650 |
+
});
|
651 |
+
};
|
652 |
+
App.prototype.isMissingActor = function (actor) {
|
653 |
+
return (actor instanceof MissingActorPlaceholder);
|
654 |
+
};
|
655 |
+
App.prototype.saveChanges = function () {
|
656 |
+
this.isSaving(true);
|
657 |
+
this.settingsData(ko.toJSON(this.getSettings()));
|
658 |
+
return true;
|
659 |
+
};
|
660 |
+
App.prototype.addTestData = function () {
|
661 |
+
//Add some test data.
|
662 |
+
this.redirects.push(new Redirect({
|
663 |
+
actorId: 'role:editor',
|
664 |
+
urlTemplate: '[wp-admin]edit.php',
|
665 |
+
trigger: 'login',
|
666 |
+
shortcodesEnabled: true
|
667 |
+
}, this.actorProvider));
|
668 |
+
this.redirects.push(new Redirect({
|
669 |
+
actorId: 'role:author',
|
670 |
+
urlTemplate: '[wp-admin]profile.php',
|
671 |
+
trigger: 'login',
|
672 |
+
shortcodesEnabled: true
|
673 |
+
}, this.actorProvider));
|
674 |
+
this.redirects.push(new Redirect({
|
675 |
+
actorId: 'user:admin',
|
676 |
+
urlTemplate: '[wp-admin]index.php',
|
677 |
+
trigger: 'login',
|
678 |
+
shortcodesEnabled: true
|
679 |
+
}, this.actorProvider));
|
680 |
+
this.redirects.push(new Redirect({
|
681 |
+
actorId: 'role:contributor',
|
682 |
+
urlTemplate: '[wp-admin]index.php',
|
683 |
+
trigger: 'login',
|
684 |
+
shortcodesEnabled: true
|
685 |
+
}, this.actorProvider));
|
686 |
+
this.redirects.push(new Redirect({
|
687 |
+
actorId: 'role:nonexistent',
|
688 |
+
urlTemplate: '[wp-admin]options-general.php',
|
689 |
+
trigger: 'login',
|
690 |
+
shortcodesEnabled: true
|
691 |
+
}, this.actorProvider));
|
692 |
+
this.redirects.push(new Redirect({
|
693 |
+
actorId: 'user:notarealuser',
|
694 |
+
urlTemplate: '[wp-admin]index.php',
|
695 |
+
trigger: 'login',
|
696 |
+
shortcodesEnabled: true
|
697 |
+
}, this.actorProvider));
|
698 |
+
this.redirects.push(new Redirect({
|
699 |
+
actorId: DefaultActorId,
|
700 |
+
urlTemplate: '[wp-admin]index.php?this-is-the-default=yep',
|
701 |
+
trigger: 'login',
|
702 |
+
shortcodesEnabled: true
|
703 |
+
}, this.actorProvider));
|
704 |
+
this.redirects.push(new Redirect({
|
705 |
+
actorId: 'role:administrator',
|
706 |
+
urlTemplate: '[wp-admin]options-general.php',
|
707 |
+
trigger: 'login',
|
708 |
+
shortcodesEnabled: true
|
709 |
+
}, this.actorProvider));
|
710 |
+
};
|
711 |
+
return App;
|
712 |
+
}());
|
713 |
+
AmeRedirectorUi.App = App;
|
714 |
+
})(AmeRedirectorUi || (AmeRedirectorUi = {}));
|
715 |
+
jQuery(function ($) {
|
716 |
+
ko.components.register('ame-redirect-url-input', {
|
717 |
+
viewModel: AmeRedirectorUi.RedirectUrlInputComponent,
|
718 |
+
template: { element: 'ame-redirect-url-component' }
|
719 |
+
});
|
720 |
+
//The user autocomplete feature is implemented as a custom binding only because that makes it easier
|
721 |
+
//to correctly initialise it when Knockout changes the DOM. The binding is not intended to be reusable.
|
722 |
+
ko.bindingHandlers.ameRuiUserAutocomplete = {
|
723 |
+
init: function (element, valueAccessor) {
|
724 |
+
var options = ko.unwrap(valueAccessor());
|
725 |
+
options = wsAmeLodash.defaults(options, {
|
726 |
+
filter: function (suggestions) {
|
727 |
+
return suggestions;
|
728 |
+
}
|
729 |
+
});
|
730 |
+
jQuery(element).autocomplete({
|
731 |
+
minLength: 2,
|
732 |
+
source: function (request, response) {
|
733 |
+
var action = AjawV1.getAction('ws-ame-rui-search-users');
|
734 |
+
action.get({ term: request.term }, function (results) {
|
735 |
+
//Filter received users.
|
736 |
+
if (options.filter) {
|
737 |
+
results = options.filter(results);
|
738 |
+
}
|
739 |
+
response(results);
|
740 |
+
}, function (error) {
|
741 |
+
response([]);
|
742 |
+
if (console && console.error) {
|
743 |
+
console.error(error);
|
744 |
+
}
|
745 |
+
});
|
746 |
+
},
|
747 |
+
select: function (unusedEvent, ui) {
|
748 |
+
var props = ui.item;
|
749 |
+
var existingUser = AmeActors.getUser(props.user_login);
|
750 |
+
if (existingUser === null) {
|
751 |
+
AmeActors.addUsers([AmeRedirectorUi.MinimalUser.createFromProperties(props)]);
|
752 |
+
}
|
753 |
+
},
|
754 |
+
classes: {
|
755 |
+
'ui-autocomplete': 'ame-rui-found-users'
|
756 |
+
}
|
757 |
+
});
|
758 |
+
ko.utils.domNodeDisposal.addDisposeCallback(element, function () {
|
759 |
+
jQuery(element).autocomplete('destroy');
|
760 |
+
});
|
761 |
+
}
|
762 |
+
};
|
763 |
+
var $container = $('#ame-redirector-ui-root');
|
764 |
+
var ameRedirectorApp = new AmeRedirectorUi.App(wsAmeRedirectorSettings);
|
765 |
+
ko.applyBindings(ameRedirectorApp, $container.get(0));
|
766 |
+
//Open the menu dropdown when the user clicks the trigger icon or presses
|
767 |
+
//the down arrow key in the redirect input field.
|
768 |
+
$container.on('mousedown click', '.ame-rui-url-dropdown-trigger', function (event) {
|
769 |
+
ameRedirectorApp.onDropdownTrigger(event);
|
770 |
+
});
|
771 |
+
/*
|
772 |
+
Releasing the "down" key only opens the dropdown if the key was pressed in the same input.
|
773 |
+
This is to avoid a confusing situation where the user selects a role from the "add a role"
|
774 |
+
dropdown using arrow keys and then the menu dropdown immediately shows up because the focus
|
775 |
+
moved to the redirect input before the user could release the key.
|
776 |
+
*/
|
777 |
+
var redirectInputSelector = '.ame-rui-url-template input[type=text].ame-rui-has-url-dropdown';
|
778 |
+
var lastDownArrowTarget = null;
|
779 |
+
$container.on('focus', redirectInputSelector, function () {
|
780 |
+
lastDownArrowTarget = null;
|
781 |
+
});
|
782 |
+
$container.on('keydown', redirectInputSelector, function (event) {
|
783 |
+
//Ignore repeated "keydown" events. These will happen even if the key was originally
|
784 |
+
//pressed in a different element.
|
785 |
+
if ((typeof event.originalEvent['repeat'] !== 'undefined') && (event.originalEvent['repeat'] === true)) {
|
786 |
+
return;
|
787 |
+
}
|
788 |
+
if (event.which === 40) {
|
789 |
+
lastDownArrowTarget = event.target;
|
790 |
+
}
|
791 |
+
});
|
792 |
+
$container.on('keyup', redirectInputSelector, function (event) {
|
793 |
+
if ((event.which === 40) && (event.target === lastDownArrowTarget)) {
|
794 |
+
ameRedirectorApp.onDropdownTrigger(event);
|
795 |
+
}
|
796 |
+
});
|
797 |
+
});
|
modules/redirector/redirector-ui.ts
ADDED
@@ -0,0 +1,994 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
/// <reference path="../../js/knockout.d.ts" />
|
2 |
+
/// <reference path="../../js/jquery.d.ts" />
|
3 |
+
/// <reference path="../../js/jqueryui.d.ts" />
|
4 |
+
/// <reference path="../../js/actor-manager.ts" />
|
5 |
+
/// <reference path="../actor-selector/actor-selector.ts" />
|
6 |
+
/// <reference path="../../js/common.d.ts" />
|
7 |
+
/// <reference path="../../ajax-wrapper/ajax-action-wrapper.d.ts" />
|
8 |
+
|
9 |
+
declare var wsAmeRedirectorSettings: AmeRedirectorUi.ScriptData;
|
10 |
+
declare var wsAmeLodash: _.LoDashStatic;
|
11 |
+
|
12 |
+
namespace AmeRedirectorUi {
|
13 |
+
const AllKnownTriggers = {
|
14 |
+
login: null,
|
15 |
+
logout: null,
|
16 |
+
registration: null,
|
17 |
+
firstLogin: null
|
18 |
+
}
|
19 |
+
|
20 |
+
const _ = wsAmeLodash;
|
21 |
+
|
22 |
+
type RedirectTrigger = keyof typeof AllKnownTriggers;
|
23 |
+
|
24 |
+
type TriggerDictionary<ValueType> = {
|
25 |
+
[property in RedirectTrigger]: ValueType;
|
26 |
+
}
|
27 |
+
|
28 |
+
abstract class AbstractTriggerDictionary<ValueType> implements TriggerDictionary<ValueType> {
|
29 |
+
login: ValueType;
|
30 |
+
logout: ValueType;
|
31 |
+
registration: ValueType;
|
32 |
+
firstLogin: ValueType;
|
33 |
+
}
|
34 |
+
|
35 |
+
interface RedirectProperties {
|
36 |
+
actorId: string;
|
37 |
+
urlTemplate: string;
|
38 |
+
menuTemplateId?: string;
|
39 |
+
|
40 |
+
shortcodesEnabled: boolean;
|
41 |
+
trigger: RedirectTrigger;
|
42 |
+
}
|
43 |
+
|
44 |
+
interface MenuItemProperties {
|
45 |
+
templateId: string;
|
46 |
+
url: string;
|
47 |
+
title: string;
|
48 |
+
}
|
49 |
+
|
50 |
+
interface StorableSettings {
|
51 |
+
redirects: RedirectProperties[];
|
52 |
+
}
|
53 |
+
|
54 |
+
export interface ScriptData extends StorableSettings {
|
55 |
+
usableMenuItems: MenuItemProperties[];
|
56 |
+
users: MinimalUserProperties[];
|
57 |
+
hasMoreUsers: boolean;
|
58 |
+
selectedTrigger?: RedirectTrigger;
|
59 |
+
}
|
60 |
+
|
61 |
+
const DefaultActorId = 'special:default';
|
62 |
+
const defaultActor: IAmeActor = {
|
63 |
+
getDisplayName(): string {
|
64 |
+
return 'Default';
|
65 |
+
},
|
66 |
+
getId(): string {
|
67 |
+
return DefaultActorId;
|
68 |
+
}
|
69 |
+
}
|
70 |
+
|
71 |
+
export class Redirect {
|
72 |
+
protected static inputCounter: number = 0
|
73 |
+
|
74 |
+
actorId: RedirectProperties['actorId'];
|
75 |
+
actor: IAmeActor;
|
76 |
+
urlTemplate: KnockoutObservable<string>;
|
77 |
+
menuTemplateId: KnockoutObservable<string>;
|
78 |
+
shortcodesEnabled: KnockoutObservable<boolean>;
|
79 |
+
trigger: RedirectProperties['trigger'];
|
80 |
+
|
81 |
+
canToggleShortcodes: KnockoutObservable<boolean>;
|
82 |
+
|
83 |
+
inputElementId: string;
|
84 |
+
inputHasFocus: KnockoutObservable<boolean>;
|
85 |
+
|
86 |
+
actorTypeNoun: KnockoutObservable<string>;
|
87 |
+
|
88 |
+
urlDropdownEnabled: KnockoutComputed<boolean>;
|
89 |
+
|
90 |
+
constructor(properties: RedirectProperties, actorProvider: ActorProvider = null) {
|
91 |
+
this.actorId = properties.actorId;
|
92 |
+
this.trigger = properties.trigger;
|
93 |
+
this.urlTemplate = ko.observable(properties.urlTemplate);
|
94 |
+
|
95 |
+
this.menuTemplateId = ko.observable(
|
96 |
+
properties.hasOwnProperty('menuTemplateId') ? properties.menuTemplateId : ''
|
97 |
+
);
|
98 |
+
|
99 |
+
this.canToggleShortcodes = ko.pureComputed(() => {
|
100 |
+
return (this.menuTemplateId().trim() === '');
|
101 |
+
});
|
102 |
+
this.inputHasFocus = ko.observable(false);
|
103 |
+
|
104 |
+
const internalShortcodesEnabled = ko.observable(properties.shortcodesEnabled);
|
105 |
+
this.shortcodesEnabled = ko.computed<boolean>({
|
106 |
+
read: () => {
|
107 |
+
//All of the menu items use shortcodes to generate the admin page URL,
|
108 |
+
//so shortcodes must be enabled when a menu item is selected.
|
109 |
+
const menu = this.menuTemplateId().trim();
|
110 |
+
if (menu !== '') {
|
111 |
+
return true;
|
112 |
+
}
|
113 |
+
return internalShortcodesEnabled();
|
114 |
+
},
|
115 |
+
write: (value: boolean) => {
|
116 |
+
if (!this.canToggleShortcodes()) {
|
117 |
+
return;
|
118 |
+
}
|
119 |
+
internalShortcodesEnabled(value);
|
120 |
+
},
|
121 |
+
deferEvaluation: true
|
122 |
+
});
|
123 |
+
|
124 |
+
if (this.actorId === DefaultActorId) {
|
125 |
+
this.actor = defaultActor;
|
126 |
+
} else {
|
127 |
+
const provider: ActorProvider = actorProvider ? actorProvider : AmeActors;
|
128 |
+
this.actor = provider.getActor(this.actorId);
|
129 |
+
}
|
130 |
+
|
131 |
+
this.actorTypeNoun = ko.pureComputed(() => {
|
132 |
+
const prefix = this.actorId.substring(0, this.actorId.indexOf(':'));
|
133 |
+
if (prefix === 'user') {
|
134 |
+
return 'user';
|
135 |
+
} else if (prefix === 'role') {
|
136 |
+
return 'role'
|
137 |
+
}
|
138 |
+
return 'item';
|
139 |
+
});
|
140 |
+
|
141 |
+
this.urlDropdownEnabled = ko.pureComputed(() => {
|
142 |
+
//If a menu item is already selected in the dropdown, the dropdown has to be enabled
|
143 |
+
//to give the user the ability to select something else.
|
144 |
+
const menu = this.menuTemplateId().trim();
|
145 |
+
if (menu !== '') {
|
146 |
+
return true;
|
147 |
+
}
|
148 |
+
|
149 |
+
//The dropdown only contains admin menu items, so it's only useful if the user
|
150 |
+
//can access the admin dashboard after the trigger happens.
|
151 |
+
//Note: This may need to change if we add other options to the dropdown.
|
152 |
+
return (this.trigger === 'login') || (this.trigger === 'firstLogin');
|
153 |
+
});
|
154 |
+
|
155 |
+
Redirect.inputCounter++;
|
156 |
+
this.inputElementId = 'ame-rui-unique-input-' + Redirect.inputCounter;
|
157 |
+
}
|
158 |
+
|
159 |
+
toJs(): RedirectProperties {
|
160 |
+
let result: RedirectProperties = {
|
161 |
+
actorId: this.actorId,
|
162 |
+
urlTemplate: this.urlTemplate().trim(),
|
163 |
+
shortcodesEnabled: this.shortcodesEnabled(),
|
164 |
+
trigger: this.trigger
|
165 |
+
};
|
166 |
+
|
167 |
+
const menu = this.menuTemplateId().trim();
|
168 |
+
if (menu !== '') {
|
169 |
+
result.menuTemplateId = menu;
|
170 |
+
}
|
171 |
+
|
172 |
+
return result;
|
173 |
+
}
|
174 |
+
|
175 |
+
displayName(): string {
|
176 |
+
if (this.actor.hasOwnProperty('userLogin')) {
|
177 |
+
const user = this.actor as IAmeUser;
|
178 |
+
return user.userLogin;
|
179 |
+
} else {
|
180 |
+
return this.actor.getDisplayName();
|
181 |
+
}
|
182 |
+
}
|
183 |
+
}
|
184 |
+
|
185 |
+
class TriggerView {
|
186 |
+
users: KnockoutObservableArray<Redirect> = ko.observableArray([]);
|
187 |
+
roles: KnockoutObservableArray<Redirect> = ko.observableArray([]);
|
188 |
+
defaultRedirect: KnockoutObservable<Redirect>;
|
189 |
+
|
190 |
+
supportsUserSettings: boolean = true;
|
191 |
+
supportsRoleSettings: boolean = true;
|
192 |
+
supportsActorSettings: KnockoutComputed<boolean>;
|
193 |
+
|
194 |
+
constructor(
|
195 |
+
trigger: RedirectTrigger,
|
196 |
+
supportsUserSettings: boolean = null,
|
197 |
+
supportsRoleSettings: boolean = null
|
198 |
+
) {
|
199 |
+
if (supportsUserSettings !== null) {
|
200 |
+
this.supportsUserSettings = supportsUserSettings;
|
201 |
+
}
|
202 |
+
if (supportsRoleSettings !== null) {
|
203 |
+
this.supportsRoleSettings = supportsRoleSettings;
|
204 |
+
}
|
205 |
+
this.supportsActorSettings = ko.pureComputed(() => {
|
206 |
+
return this.supportsUserSettings || this.supportsRoleSettings;
|
207 |
+
});
|
208 |
+
|
209 |
+
this.defaultRedirect = ko.observable(new Redirect({
|
210 |
+
actorId: 'special:default',
|
211 |
+
trigger: trigger,
|
212 |
+
shortcodesEnabled: true,
|
213 |
+
urlTemplate: ''
|
214 |
+
}));
|
215 |
+
}
|
216 |
+
|
217 |
+
add(item: Redirect) {
|
218 |
+
const actorId = item.actorId;
|
219 |
+
if (actorId === DefaultActorId) {
|
220 |
+
this.defaultRedirect(item);
|
221 |
+
} else if (actorId === 'special:super_admin') {
|
222 |
+
this.roles.push(item);
|
223 |
+
} else {
|
224 |
+
const actorType = actorId.substring(0, actorId.indexOf(':'));
|
225 |
+
switch (actorType) {
|
226 |
+
case 'user':
|
227 |
+
this.users.push(item);
|
228 |
+
break;
|
229 |
+
case 'role':
|
230 |
+
this.roles.push(item);
|
231 |
+
break;
|
232 |
+
default:
|
233 |
+
console.log('Unknown actor type for a trigger view: ' + actorType);
|
234 |
+
}
|
235 |
+
}
|
236 |
+
}
|
237 |
+
|
238 |
+
toArray(): Redirect[] {
|
239 |
+
let results = [];
|
240 |
+
results.push(...this.users());
|
241 |
+
results.push(...this.roles());
|
242 |
+
|
243 |
+
//Include the default redirect only if it's not empty.
|
244 |
+
const defaultRedirect = this.defaultRedirect();
|
245 |
+
const url = defaultRedirect.urlTemplate().trim();
|
246 |
+
if (url !== '') {
|
247 |
+
results.push(defaultRedirect);
|
248 |
+
}
|
249 |
+
|
250 |
+
return results;
|
251 |
+
}
|
252 |
+
}
|
253 |
+
|
254 |
+
class MenuCollection {
|
255 |
+
menusByTemplate: AmeDictionary<MenuItemProperties> = {};
|
256 |
+
|
257 |
+
constructor(usableMenuItems: MenuItemProperties[]) {
|
258 |
+
this.menusByTemplate = {};
|
259 |
+
for (let i = 0; i < usableMenuItems.length; i++) {
|
260 |
+
this.menusByTemplate[usableMenuItems[i].templateId] = usableMenuItems[i];
|
261 |
+
}
|
262 |
+
}
|
263 |
+
|
264 |
+
findSelectedMenu(redirect: Redirect): MenuItemProperties | null {
|
265 |
+
const templateId = redirect.menuTemplateId();
|
266 |
+
if (templateId === '') {
|
267 |
+
return null;
|
268 |
+
}
|
269 |
+
if (!this.menusByTemplate.hasOwnProperty(templateId)) {
|
270 |
+
return null;
|
271 |
+
}
|
272 |
+
|
273 |
+
const menu = this.menusByTemplate[templateId];
|
274 |
+
const url = redirect.urlTemplate();
|
275 |
+
if (menu.url === url) {
|
276 |
+
return menu;
|
277 |
+
}
|
278 |
+
return null;
|
279 |
+
}
|
280 |
+
}
|
281 |
+
|
282 |
+
class RedirectsByTrigger extends AbstractTriggerDictionary<TriggerView> {
|
283 |
+
constructor() {
|
284 |
+
super();
|
285 |
+
this.login = new TriggerView('login');
|
286 |
+
this.logout = new TriggerView('logout');
|
287 |
+
this.registration = new TriggerView('registration', false, false);
|
288 |
+
this.firstLogin = new TriggerView('firstLogin', false, true);
|
289 |
+
}
|
290 |
+
|
291 |
+
public static fromArray(redirects: Redirect[]): RedirectsByTrigger {
|
292 |
+
const instance = new RedirectsByTrigger();
|
293 |
+
|
294 |
+
const length = redirects.length;
|
295 |
+
for (let i = 0; i < length; i++) {
|
296 |
+
const item = redirects[i];
|
297 |
+
if (instance.hasOwnProperty(item.trigger)) {
|
298 |
+
const view = instance[item.trigger] as TriggerView;
|
299 |
+
view.add(item);
|
300 |
+
}
|
301 |
+
}
|
302 |
+
|
303 |
+
return instance;
|
304 |
+
}
|
305 |
+
|
306 |
+
toArray(): Redirect[] {
|
307 |
+
let results: Redirect[] = [];
|
308 |
+
|
309 |
+
for (let key in AllKnownTriggers) {
|
310 |
+
if (this.hasOwnProperty(key)) {
|
311 |
+
const view = this[key] as TriggerView;
|
312 |
+
results.push(...view.toArray());
|
313 |
+
}
|
314 |
+
}
|
315 |
+
|
316 |
+
//Remove redirects that don't have a URL.
|
317 |
+
results = results.filter(function (redirect) {
|
318 |
+
const url = redirect.urlTemplate().trim();
|
319 |
+
return ((typeof url) === 'string') && (url !== '');
|
320 |
+
});
|
321 |
+
|
322 |
+
return results;
|
323 |
+
}
|
324 |
+
}
|
325 |
+
|
326 |
+
export class RedirectUrlInputComponent {
|
327 |
+
redirect: Redirect;
|
328 |
+
displayValue: KnockoutComputed<string>;
|
329 |
+
isUrlReadonly: KnockoutComputed<boolean | null>;
|
330 |
+
menuItems: MenuCollection;
|
331 |
+
|
332 |
+
constructor(params: AmeDictionary<any>) {
|
333 |
+
this.redirect = ko.unwrap(params.redirect) as Redirect;
|
334 |
+
this.menuItems = params.menuItems as MenuCollection;
|
335 |
+
|
336 |
+
this.displayValue = ko.computed({
|
337 |
+
read: (): string => {
|
338 |
+
const menu = this.menuItems.findSelectedMenu(this.redirect);
|
339 |
+
if (menu) {
|
340 |
+
return menu.title;
|
341 |
+
} else {
|
342 |
+
return this.redirect.urlTemplate();
|
343 |
+
}
|
344 |
+
},
|
345 |
+
write: (value: string) => {
|
346 |
+
const menu = this.menuItems.findSelectedMenu(this.redirect);
|
347 |
+
if (menu !== null) {
|
348 |
+
//Can't manually edit the URL because a menu item is selected.
|
349 |
+
return;
|
350 |
+
}
|
351 |
+
this.redirect.urlTemplate(value);
|
352 |
+
}
|
353 |
+
});
|
354 |
+
|
355 |
+
this.isUrlReadonly = ko.pureComputed(() => {
|
356 |
+
if (this.menuItems.findSelectedMenu(this.redirect) !== null) {
|
357 |
+
return true;
|
358 |
+
}
|
359 |
+
return null;
|
360 |
+
});
|
361 |
+
}
|
362 |
+
}
|
363 |
+
|
364 |
+
interface ActorProvider {
|
365 |
+
getActor(actorId): IAmeActor;
|
366 |
+
}
|
367 |
+
|
368 |
+
/**
|
369 |
+
* Proxy class that automatically creates placeholders for missing actors.
|
370 |
+
*/
|
371 |
+
class ActorProviderProxy implements ActorProvider {
|
372 |
+
private provider: ActorProvider;
|
373 |
+
private readonly placeholders: AmeDictionary<IAmeActor>;
|
374 |
+
|
375 |
+
constructor(realProvider: ActorProvider) {
|
376 |
+
this.provider = realProvider;
|
377 |
+
this.placeholders = {};
|
378 |
+
}
|
379 |
+
|
380 |
+
getActor(actorId): IAmeActor {
|
381 |
+
if (actorId === DefaultActorId) {
|
382 |
+
return defaultActor;
|
383 |
+
}
|
384 |
+
|
385 |
+
const existingActor = this.provider.getActor(actorId);
|
386 |
+
if (existingActor) {
|
387 |
+
return existingActor;
|
388 |
+
} else if (this.placeholders.hasOwnProperty(actorId)) {
|
389 |
+
return this.placeholders[actorId];
|
390 |
+
}
|
391 |
+
|
392 |
+
//If the actor hasn't been loaded or created by now, that means it has been deleted
|
393 |
+
//or it was invalid to begin with. Let's use a placeholder object to represent it.
|
394 |
+
let missingActor;
|
395 |
+
if (_.startsWith(actorId, 'user:')) {
|
396 |
+
missingActor = new MissingUserPlaceholder(actorId);
|
397 |
+
} else if (_.startsWith(actorId, 'role:')) {
|
398 |
+
missingActor = new MissingRolePlaceholder(actorId);
|
399 |
+
} else {
|
400 |
+
missingActor = new MissingActorPlaceholder(actorId);
|
401 |
+
}
|
402 |
+
this.placeholders[actorId] = missingActor;
|
403 |
+
|
404 |
+
return missingActor;
|
405 |
+
}
|
406 |
+
}
|
407 |
+
|
408 |
+
//For this feature we only need enough information to display and identify a user.
|
409 |
+
export interface MinimalUserProperties {
|
410 |
+
user_login: string;
|
411 |
+
display_name: string;
|
412 |
+
}
|
413 |
+
|
414 |
+
export class MinimalUser extends AmeUser {
|
415 |
+
static createFromProperties(properties: MinimalUserProperties): MinimalUser {
|
416 |
+
return new MinimalUser(
|
417 |
+
properties.user_login,
|
418 |
+
properties.display_name,
|
419 |
+
{},
|
420 |
+
[],
|
421 |
+
false
|
422 |
+
);
|
423 |
+
}
|
424 |
+
}
|
425 |
+
|
426 |
+
class MissingActorPlaceholder implements IAmeActor {
|
427 |
+
protected actorId: string;
|
428 |
+
protected displayName: string;
|
429 |
+
|
430 |
+
constructor(id: string, displayName: string = null) {
|
431 |
+
this.actorId = id;
|
432 |
+
if (displayName !== null) {
|
433 |
+
this.displayName = displayName;
|
434 |
+
} else {
|
435 |
+
this.displayName = this.idWithoutPrefix(id);
|
436 |
+
}
|
437 |
+
}
|
438 |
+
|
439 |
+
getDisplayName(): string {
|
440 |
+
return this.displayName;
|
441 |
+
}
|
442 |
+
|
443 |
+
getId(): string {
|
444 |
+
return this.actorId;
|
445 |
+
}
|
446 |
+
|
447 |
+
protected idWithoutPrefix(actorId: string): string {
|
448 |
+
const delimiterPos = actorId.indexOf(':');
|
449 |
+
if (delimiterPos < 0) {
|
450 |
+
return actorId;
|
451 |
+
}
|
452 |
+
return actorId.substring(delimiterPos + 1);
|
453 |
+
}
|
454 |
+
}
|
455 |
+
|
456 |
+
class MissingRolePlaceholder extends MissingActorPlaceholder {
|
457 |
+
}
|
458 |
+
|
459 |
+
class MissingUserPlaceholder extends MissingActorPlaceholder implements IAmeUser {
|
460 |
+
isSuperAdmin: boolean = false;
|
461 |
+
userLogin: string;
|
462 |
+
|
463 |
+
constructor(actorId: string) {
|
464 |
+
super(actorId);
|
465 |
+
this.userLogin = this.idWithoutPrefix(actorId);
|
466 |
+
}
|
467 |
+
}
|
468 |
+
|
469 |
+
export class App {
|
470 |
+
isLoaded: KnockoutObservable<boolean> = ko.observable(false);
|
471 |
+
|
472 |
+
redirects: KnockoutObservableArray<Redirect>;
|
473 |
+
|
474 |
+
selectedTrigger: KnockoutObservable<RedirectTrigger>;
|
475 |
+
byTrigger: KnockoutObservable<RedirectsByTrigger>;
|
476 |
+
currentTriggerView: KnockoutComputed<TriggerView>;
|
477 |
+
|
478 |
+
availableTriggers: { trigger: RedirectTrigger, label: string }[] = [
|
479 |
+
{trigger: 'login', label: 'Login Redirect'},
|
480 |
+
{trigger: 'logout', label: 'Logout Redirect'},
|
481 |
+
{trigger: 'registration', label: 'Registration Redirect'},
|
482 |
+
{trigger: 'firstLogin', label: 'First Login Redirect'}
|
483 |
+
];
|
484 |
+
|
485 |
+
menuItems: MenuCollection;
|
486 |
+
|
487 |
+
menuDropdownParent: KnockoutObservable<Redirect | null>;
|
488 |
+
menuDropdownOptions: MenuItemProperties[];
|
489 |
+
selectedMenuDropdownItem: KnockoutObservable<MenuItemProperties>;
|
490 |
+
readonly customUrlOption: MenuItemProperties = {
|
491 |
+
templateId: '',
|
492 |
+
url: '',
|
493 |
+
title: '[ Custom URL ]'
|
494 |
+
};
|
495 |
+
|
496 |
+
private readonly menuDropdown: JQuery;
|
497 |
+
private ignoreNextDropdownClick: JQueryEventObject['target'] = null;
|
498 |
+
|
499 |
+
addableRoles: KnockoutComputed<IAmeActor[]>;
|
500 |
+
selectedRoleToAdd: KnockoutObservable<IAmeActor | undefined>;
|
501 |
+
roleSelectorHasFocus: KnockoutObservable<boolean>;
|
502 |
+
|
503 |
+
addableUsers: KnockoutComputed<IAmeUser[]>;
|
504 |
+
selectedUserToAdd: KnockoutObservable<IAmeUser | undefined>;
|
505 |
+
userSelectorHasFocus: KnockoutObservable<boolean>;
|
506 |
+
userSelectionUi: 'dropdown' | 'search' = 'dropdown';
|
507 |
+
|
508 |
+
userLoginQuery: KnockoutObservable<string>;
|
509 |
+
addUserButtonEnabled: KnockoutObservable<boolean>;
|
510 |
+
|
511 |
+
actorProvider: ActorProviderProxy;
|
512 |
+
|
513 |
+
isSaving: KnockoutObservable<boolean>;
|
514 |
+
settingsData: KnockoutObservable<string>;
|
515 |
+
|
516 |
+
constructor(settings: ScriptData) {
|
517 |
+
const self = this;
|
518 |
+
|
519 |
+
this.actorProvider = new ActorProviderProxy(AmeActors);
|
520 |
+
|
521 |
+
//Users need to be loaded before redirects because redirects use actor objects.
|
522 |
+
let loadedUsers = settings.users.map(
|
523 |
+
(props) => {
|
524 |
+
const existingInstance = AmeActors.getUser(props.user_login);
|
525 |
+
if (existingInstance) {
|
526 |
+
return existingInstance;
|
527 |
+
} else {
|
528 |
+
const newUser = MinimalUser.createFromProperties(props);
|
529 |
+
AmeActors.addUsers([newUser]);
|
530 |
+
return newUser;
|
531 |
+
}
|
532 |
+
}
|
533 |
+
);
|
534 |
+
loadedUsers.sort(function (a, b) {
|
535 |
+
return a.userLogin.localeCompare(b.userLogin);
|
536 |
+
});
|
537 |
+
|
538 |
+
this.redirects = ko.observableArray(settings.redirects.map(
|
539 |
+
props => new Redirect(props, this.actorProvider))
|
540 |
+
);
|
541 |
+
this.menuItems = new MenuCollection(settings.usableMenuItems);
|
542 |
+
|
543 |
+
this.menuDropdownOptions = [this.customUrlOption].concat(settings.usableMenuItems);
|
544 |
+
this.menuDropdownParent = ko.observable(null);
|
545 |
+
|
546 |
+
this.selectedMenuDropdownItem = ko.computed<MenuItemProperties>({
|
547 |
+
read: () => {
|
548 |
+
const currentRedirect = this.menuDropdownParent();
|
549 |
+
if (currentRedirect === null) {
|
550 |
+
return this.customUrlOption;
|
551 |
+
} else {
|
552 |
+
//Find the option that matches this template ID and URL.
|
553 |
+
let foundMenu = this.menuItems.findSelectedMenu(currentRedirect);
|
554 |
+
if (foundMenu === null) {
|
555 |
+
foundMenu = this.customUrlOption;
|
556 |
+
}
|
557 |
+
return foundMenu;
|
558 |
+
}
|
559 |
+
},
|
560 |
+
write: (newValue) => {
|
561 |
+
const currentRedirect = this.menuDropdownParent();
|
562 |
+
if (!currentRedirect) {
|
563 |
+
return; //Nothing to do!
|
564 |
+
}
|
565 |
+
|
566 |
+
if (!newValue) {
|
567 |
+
newValue = this.customUrlOption;
|
568 |
+
}
|
569 |
+
|
570 |
+
currentRedirect.menuTemplateId(newValue.templateId);
|
571 |
+
if (newValue.templateId !== '') {
|
572 |
+
currentRedirect.urlTemplate(newValue.url);
|
573 |
+
}
|
574 |
+
},
|
575 |
+
owner: self,
|
576 |
+
deferEvaluation: true
|
577 |
+
});
|
578 |
+
|
579 |
+
this.menuDropdown = jQuery('#ame-rui-menu-items');
|
580 |
+
|
581 |
+
//Hide the dropdown when it loses focus.
|
582 |
+
this.menuDropdown.on('blur', () => {
|
583 |
+
this.closeMenuDropdown();
|
584 |
+
});
|
585 |
+
|
586 |
+
this.menuDropdown.on('keydown', (event) => {
|
587 |
+
//Also hide the dropdown if the user presses Esc.
|
588 |
+
if (event.which === 27) {
|
589 |
+
this.closeMenuDropdown(true);
|
590 |
+
} else if (event.which === 13) {
|
591 |
+
//Close the dropdown when the user presses Enter.
|
592 |
+
//Since we currently update the redirect on every change, there's no difference between
|
593 |
+
//this and pressing Esc.
|
594 |
+
this.closeMenuDropdown(true);
|
595 |
+
}
|
596 |
+
});
|
597 |
+
|
598 |
+
//Close the dropdown when the user selects an option by clicking it.
|
599 |
+
this.menuDropdown.on('click', 'option', () => {
|
600 |
+
this.closeMenuDropdown();
|
601 |
+
});
|
602 |
+
|
603 |
+
//this.addTestData();
|
604 |
+
|
605 |
+
this.byTrigger = ko.observable(RedirectsByTrigger.fromArray(this.redirects()));
|
606 |
+
|
607 |
+
//Reselect the previous trigger, or just the first trigger.
|
608 |
+
this.selectedTrigger = ko.observable(
|
609 |
+
settings.selectedTrigger ? settings.selectedTrigger : this.availableTriggers[0].trigger
|
610 |
+
);
|
611 |
+
|
612 |
+
this.currentTriggerView = ko.pureComputed(() => {
|
613 |
+
const trigger = this.selectedTrigger();
|
614 |
+
const mapping = this.byTrigger();
|
615 |
+
|
616 |
+
if (mapping.hasOwnProperty(trigger) && (mapping[trigger] instanceof TriggerView)) {
|
617 |
+
return mapping[trigger];
|
618 |
+
} else {
|
619 |
+
return mapping.login;
|
620 |
+
}
|
621 |
+
});
|
622 |
+
|
623 |
+
this.addableRoles = ko.pureComputed(() => {
|
624 |
+
const allRoles: IAmeActor[] = _.values(AmeActors.getRoles());
|
625 |
+
const usedRoles = _.map(
|
626 |
+
this.currentTriggerView().roles(),
|
627 |
+
(redirect) => {
|
628 |
+
return redirect.actor;
|
629 |
+
}
|
630 |
+
);
|
631 |
+
return _.difference(allRoles, usedRoles);
|
632 |
+
});
|
633 |
+
|
634 |
+
this.selectedRoleToAdd = ko.observable(void 0);
|
635 |
+
this.roleSelectorHasFocus = ko.observable(false);
|
636 |
+
|
637 |
+
this.addableUsers = ko.pureComputed(() => {
|
638 |
+
const usedUsers = _.map(
|
639 |
+
this.currentTriggerView().users(),
|
640 |
+
(redirect) => {
|
641 |
+
return redirect.actor as IAmeUser;
|
642 |
+
}
|
643 |
+
);
|
644 |
+
return _.difference(loadedUsers, usedUsers);
|
645 |
+
});
|
646 |
+
|
647 |
+
this.selectedUserToAdd = ko.observable(void 0);
|
648 |
+
this.userSelectorHasFocus = ko.observable(false);
|
649 |
+
|
650 |
+
this.selectedRoleToAdd.subscribe((newSelection) => {
|
651 |
+
this.addSelectedActorTo(newSelection, this.currentTriggerView().roles);
|
652 |
+
this.roleSelectorHasFocus(false);
|
653 |
+
this.selectedRoleToAdd(void 0);
|
654 |
+
});
|
655 |
+
|
656 |
+
this.selectedUserToAdd.subscribe((newSelection) => {
|
657 |
+
this.addSelectedActorTo(newSelection, this.currentTriggerView().users);
|
658 |
+
this.userSelectorHasFocus(false);
|
659 |
+
this.selectedUserToAdd(void 0);
|
660 |
+
});
|
661 |
+
|
662 |
+
this.userLoginQuery = ko.observable('');
|
663 |
+
this.addUserButtonEnabled = ko.pureComputed(() => {
|
664 |
+
return (this.userLoginQuery().trim() !== '');
|
665 |
+
});
|
666 |
+
|
667 |
+
if (settings.hasMoreUsers) {
|
668 |
+
this.userSelectionUi = 'search';
|
669 |
+
}
|
670 |
+
|
671 |
+
this.isSaving = ko.observable(false);
|
672 |
+
this.settingsData = ko.observable('');
|
673 |
+
|
674 |
+
this.isLoaded(true);
|
675 |
+
}
|
676 |
+
|
677 |
+
getSettings(): StorableSettings {
|
678 |
+
return {
|
679 |
+
redirects: this.byTrigger().toArray().map(redirect => redirect.toJs())
|
680 |
+
}
|
681 |
+
}
|
682 |
+
|
683 |
+
onDropdownTrigger(event: JQueryEventObject) {
|
684 |
+
//Note: There probably is some jQuery feature or library that makes dropdowns easier,
|
685 |
+
//but I already did this the hard way.
|
686 |
+
|
687 |
+
const $input = jQuery(event.target).closest('.ame-rui-url-template,ame-redirect-url-input').find('input').first();
|
688 |
+
const $node = $input.closest('.ame-rui-redirect');
|
689 |
+
if ($node.length < 1) {
|
690 |
+
return;
|
691 |
+
}
|
692 |
+
|
693 |
+
const redirect = ko.dataFor($node.get(0));
|
694 |
+
if (!(redirect instanceof AmeRedirectorUi.Redirect)) {
|
695 |
+
return;
|
696 |
+
}
|
697 |
+
|
698 |
+
//Clicking the same trigger a second time closes the dropdown.
|
699 |
+
if (event.type === 'mousedown') {
|
700 |
+
const isSameTrigger = this.menuDropdown.is(':visible') && (this.menuDropdownParent() === redirect);
|
701 |
+
if (isSameTrigger) {
|
702 |
+
//The dropdown will be automatically closed by its "blur" event handler,
|
703 |
+
//but we need to ignore the next click event on this element.
|
704 |
+
this.ignoreNextDropdownClick = event.target;
|
705 |
+
} else {
|
706 |
+
this.ignoreNextDropdownClick = null;
|
707 |
+
}
|
708 |
+
return;
|
709 |
+
}
|
710 |
+
if ((event.type === 'click') && (event.target === this.ignoreNextDropdownClick)) {
|
711 |
+
return;
|
712 |
+
}
|
713 |
+
|
714 |
+
//Move the drop-down near the input box.
|
715 |
+
this.menuDropdown
|
716 |
+
.css({
|
717 |
+
position: 'absolute',
|
718 |
+
zIndex: 100 //The dropdown should be displayed above other elements. This may not be required.
|
719 |
+
})
|
720 |
+
.show()
|
721 |
+
.outerWidth(Math.max($input.outerWidth(), 100))
|
722 |
+
.position({
|
723 |
+
my: 'right top',
|
724 |
+
at: 'right bottom',
|
725 |
+
of: $input
|
726 |
+
});
|
727 |
+
|
728 |
+
//Move focus to the dropdown.
|
729 |
+
let $select = this.menuDropdown;
|
730 |
+
if (!this.menuDropdown.is('select, input')) {
|
731 |
+
$select = this.menuDropdown.find('select, input').first();
|
732 |
+
}
|
733 |
+
$select.trigger('focus');
|
734 |
+
|
735 |
+
//Select the current option and scroll it into view. It looks like the browser will automatically
|
736 |
+
//scroll to the selected option, but only if the select element is already visible, so we need to
|
737 |
+
//do this *after* we show the dropdown.
|
738 |
+
this.menuDropdownParent(redirect);
|
739 |
+
}
|
740 |
+
|
741 |
+
closeMenuDropdown(moveFocusToInput: boolean = false) {
|
742 |
+
const currentRedirect = this.menuDropdownParent();
|
743 |
+
|
744 |
+
this.menuDropdown.hide();
|
745 |
+
this.menuDropdownParent(null);
|
746 |
+
|
747 |
+
//Refocus on the URL input after closing the dropdown.
|
748 |
+
if (moveFocusToInput && currentRedirect) {
|
749 |
+
currentRedirect.inputHasFocus(true);
|
750 |
+
}
|
751 |
+
}
|
752 |
+
|
753 |
+
protected addSelectedActorTo(actor: IAmeActor | undefined, list: KnockoutObservableArray<Redirect>) {
|
754 |
+
//The list includes a caption item that is displayed when nothing is selected.
|
755 |
+
//The value of that option is supposed to be undefined.
|
756 |
+
if ((typeof actor === 'undefined') || (actor === null) || !this.currentTriggerView()) {
|
757 |
+
return;
|
758 |
+
}
|
759 |
+
|
760 |
+
//Add a redirect for the selected role.
|
761 |
+
let newRedirect = new Redirect({
|
762 |
+
actorId: actor.getId(),
|
763 |
+
shortcodesEnabled: true,
|
764 |
+
urlTemplate: '',
|
765 |
+
trigger: this.selectedTrigger()
|
766 |
+
}, this.actorProvider);
|
767 |
+
|
768 |
+
list.push(newRedirect);
|
769 |
+
|
770 |
+
newRedirect.inputHasFocus(true);
|
771 |
+
}
|
772 |
+
|
773 |
+
addEnteredUserLogin() {
|
774 |
+
const userLogin = this.userLoginQuery().trim();
|
775 |
+
if (userLogin === '') {
|
776 |
+
return;
|
777 |
+
}
|
778 |
+
|
779 |
+
const actorId = 'user:' + userLogin;
|
780 |
+
if (!AmeActors.actorExists(actorId)) {
|
781 |
+
if (console && console.warn) {
|
782 |
+
console.warn('User "' + userLogin + '" has not been initialized. Creating a minimal actor now.');
|
783 |
+
}
|
784 |
+
AmeActors.addUsers([
|
785 |
+
MinimalUser.createFromProperties({
|
786 |
+
user_login: userLogin,
|
787 |
+
display_name: userLogin
|
788 |
+
})
|
789 |
+
]);
|
790 |
+
}
|
791 |
+
|
792 |
+
//Only add each user once.
|
793 |
+
const alreadyAdded = _.some(this.currentTriggerView().users(), function (redirect) {
|
794 |
+
return redirect.actorId === actorId;
|
795 |
+
});
|
796 |
+
if (alreadyAdded) {
|
797 |
+
alert('Error: Duplicate entry. User "' + userLogin + '" has already been added.');
|
798 |
+
return;
|
799 |
+
}
|
800 |
+
|
801 |
+
let newRedirect = new Redirect({
|
802 |
+
actorId: actorId,
|
803 |
+
shortcodesEnabled: true,
|
804 |
+
urlTemplate: '',
|
805 |
+
trigger: this.selectedTrigger()
|
806 |
+
}, this.actorProvider);
|
807 |
+
|
808 |
+
this.currentTriggerView().users.push(newRedirect);
|
809 |
+
|
810 |
+
this.userLoginQuery('');
|
811 |
+
}
|
812 |
+
|
813 |
+
filterUserAutocompleteResults(results: MinimalUserProperties[]): MinimalUserProperties[] {
|
814 |
+
//Filter out users that are already in the current list.
|
815 |
+
const usedLogins = _.indexBy(
|
816 |
+
this.currentTriggerView().users(),
|
817 |
+
(redirect) => {
|
818 |
+
return (redirect.actor as IAmeUser).userLogin;
|
819 |
+
}
|
820 |
+
);
|
821 |
+
return _.filter(results, function (props) {
|
822 |
+
return !(usedLogins.hasOwnProperty(props.user_login));
|
823 |
+
});
|
824 |
+
}
|
825 |
+
|
826 |
+
isMissingActor(actor: IAmeActor): boolean {
|
827 |
+
return (actor instanceof MissingActorPlaceholder);
|
828 |
+
}
|
829 |
+
|
830 |
+
saveChanges() {
|
831 |
+
this.isSaving(true);
|
832 |
+
this.settingsData(ko.toJSON(this.getSettings()));
|
833 |
+
return true;
|
834 |
+
}
|
835 |
+
|
836 |
+
private addTestData() {
|
837 |
+
//Add some test data.
|
838 |
+
this.redirects.push(new Redirect({
|
839 |
+
actorId: 'role:editor',
|
840 |
+
urlTemplate: '[wp-admin]edit.php',
|
841 |
+
trigger: 'login',
|
842 |
+
shortcodesEnabled: true
|
843 |
+
}, this.actorProvider));
|
844 |
+
|
845 |
+
this.redirects.push(new Redirect({
|
846 |
+
actorId: 'role:author',
|
847 |
+
urlTemplate: '[wp-admin]profile.php',
|
848 |
+
trigger: 'login',
|
849 |
+
shortcodesEnabled: true
|
850 |
+
}, this.actorProvider));
|
851 |
+
|
852 |
+
this.redirects.push(new Redirect({
|
853 |
+
actorId: 'user:admin',
|
854 |
+
urlTemplate: '[wp-admin]index.php',
|
855 |
+
trigger: 'login',
|
856 |
+
shortcodesEnabled: true
|
857 |
+
}, this.actorProvider));
|
858 |
+
|
859 |
+
this.redirects.push(new Redirect({
|
860 |
+
actorId: 'role:contributor',
|
861 |
+
urlTemplate: '[wp-admin]index.php',
|
862 |
+
trigger: 'login',
|
863 |
+
shortcodesEnabled: true
|
864 |
+
}, this.actorProvider));
|
865 |
+
|
866 |
+
this.redirects.push(new Redirect({
|
867 |
+
actorId: 'role:nonexistent',
|
868 |
+
urlTemplate: '[wp-admin]options-general.php',
|
869 |
+
trigger: 'login',
|
870 |
+
shortcodesEnabled: true
|
871 |
+
}, this.actorProvider));
|
872 |
+
|
873 |
+
this.redirects.push(new Redirect({
|
874 |
+
actorId: 'user:notarealuser',
|
875 |
+
urlTemplate: '[wp-admin]index.php',
|
876 |
+
trigger: 'login',
|
877 |
+
shortcodesEnabled: true
|
878 |
+
}, this.actorProvider));
|
879 |
+
|
880 |
+
this.redirects.push(new Redirect({
|
881 |
+
actorId: DefaultActorId,
|
882 |
+
urlTemplate: '[wp-admin]index.php?this-is-the-default=yep',
|
883 |
+
trigger: 'login',
|
884 |
+
shortcodesEnabled: true
|
885 |
+
}, this.actorProvider));
|
886 |
+
|
887 |
+
this.redirects.push(new Redirect({
|
888 |
+
actorId: 'role:administrator',
|
889 |
+
urlTemplate: '[wp-admin]options-general.php',
|
890 |
+
trigger: 'login',
|
891 |
+
shortcodesEnabled: true
|
892 |
+
}, this.actorProvider));
|
893 |
+
}
|
894 |
+
}
|
895 |
+
}
|
896 |
+
|
897 |
+
jQuery(function ($) {
|
898 |
+
ko.components.register(
|
899 |
+
'ame-redirect-url-input',
|
900 |
+
{
|
901 |
+
viewModel: AmeRedirectorUi.RedirectUrlInputComponent,
|
902 |
+
template: {element: 'ame-redirect-url-component'}
|
903 |
+
}
|
904 |
+
);
|
905 |
+
|
906 |
+
//The user autocomplete feature is implemented as a custom binding only because that makes it easier
|
907 |
+
//to correctly initialise it when Knockout changes the DOM. The binding is not intended to be reusable.
|
908 |
+
ko.bindingHandlers.ameRuiUserAutocomplete = {
|
909 |
+
init: function (element, valueAccessor) {
|
910 |
+
let options = ko.unwrap(valueAccessor());
|
911 |
+
|
912 |
+
options = wsAmeLodash.defaults(options, {
|
913 |
+
filter: function (suggestions) {
|
914 |
+
return suggestions;
|
915 |
+
}
|
916 |
+
});
|
917 |
+
|
918 |
+
jQuery(element).autocomplete({
|
919 |
+
minLength: 2,
|
920 |
+
source: function (request, response) {
|
921 |
+
const action = AjawV1.getAction('ws-ame-rui-search-users');
|
922 |
+
action.get(
|
923 |
+
{term: request.term},
|
924 |
+
function (results) {
|
925 |
+
//Filter received users.
|
926 |
+
if (options.filter) {
|
927 |
+
results = options.filter(results);
|
928 |
+
}
|
929 |
+
response(results)
|
930 |
+
},
|
931 |
+
function (error) {
|
932 |
+
response([]);
|
933 |
+
if (console && console.error) {
|
934 |
+
console.error(error);
|
935 |
+
}
|
936 |
+
}
|
937 |
+
);
|
938 |
+
},
|
939 |
+
select: function (unusedEvent, ui) {
|
940 |
+
const props = ui.item as AmeRedirectorUi.MinimalUserProperties;
|
941 |
+
const existingUser = AmeActors.getUser(props.user_login);
|
942 |
+
if (existingUser === null) {
|
943 |
+
AmeActors.addUsers([AmeRedirectorUi.MinimalUser.createFromProperties(props)]);
|
944 |
+
}
|
945 |
+
},
|
946 |
+
classes: {
|
947 |
+
'ui-autocomplete': 'ame-rui-found-users'
|
948 |
+
}
|
949 |
+
});
|
950 |
+
|
951 |
+
ko.utils.domNodeDisposal.addDisposeCallback(element, function () {
|
952 |
+
jQuery(element).autocomplete('destroy');
|
953 |
+
});
|
954 |
+
}
|
955 |
+
};
|
956 |
+
|
957 |
+
const $container = $('#ame-redirector-ui-root');
|
958 |
+
|
959 |
+
const ameRedirectorApp = new AmeRedirectorUi.App(wsAmeRedirectorSettings);
|
960 |
+
ko.applyBindings(ameRedirectorApp, $container.get(0));
|
961 |
+
|
962 |
+
//Open the menu dropdown when the user clicks the trigger icon or presses
|
963 |
+
//the down arrow key in the redirect input field.
|
964 |
+
$container.on('mousedown click', '.ame-rui-url-dropdown-trigger', function (event) {
|
965 |
+
ameRedirectorApp.onDropdownTrigger(event);
|
966 |
+
});
|
967 |
+
|
968 |
+
/*
|
969 |
+
Releasing the "down" key only opens the dropdown if the key was pressed in the same input.
|
970 |
+
This is to avoid a confusing situation where the user selects a role from the "add a role"
|
971 |
+
dropdown using arrow keys and then the menu dropdown immediately shows up because the focus
|
972 |
+
moved to the redirect input before the user could release the key.
|
973 |
+
*/
|
974 |
+
const redirectInputSelector = '.ame-rui-url-template input[type=text].ame-rui-has-url-dropdown';
|
975 |
+
let lastDownArrowTarget = null;
|
976 |
+
$container.on('focus', redirectInputSelector, function () {
|
977 |
+
lastDownArrowTarget = null;
|
978 |
+
});
|
979 |
+
$container.on('keydown', redirectInputSelector, function (event) {
|
980 |
+
//Ignore repeated "keydown" events. These will happen even if the key was originally
|
981 |
+
//pressed in a different element.
|
982 |
+
if ((typeof event.originalEvent['repeat'] !== 'undefined') && (event.originalEvent['repeat'] === true)) {
|
983 |
+
return;
|
984 |
+
}
|
985 |
+
if (event.which === 40) {
|
986 |
+
lastDownArrowTarget = event.target;
|
987 |
+
}
|
988 |
+
});
|
989 |
+
$container.on('keyup', redirectInputSelector, function (event) {
|
990 |
+
if ((event.which === 40) && (event.target === lastDownArrowTarget)) {
|
991 |
+
ameRedirectorApp.onDropdownTrigger(event);
|
992 |
+
}
|
993 |
+
});
|
994 |
+
});
|
modules/redirector/redirector.css
ADDED
@@ -0,0 +1,410 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
@charset "UTF-8";
|
2 |
+
.ame-rui-redirect-list {
|
3 |
+
display: flex;
|
4 |
+
flex-direction: column;
|
5 |
+
padding-top: 1px;
|
6 |
+
}
|
7 |
+
|
8 |
+
.ame-rui-redirect {
|
9 |
+
display: flex;
|
10 |
+
flex-direction: row;
|
11 |
+
background: #fff;
|
12 |
+
border: 1px solid #dcdcde;
|
13 |
+
margin-top: -1px;
|
14 |
+
}
|
15 |
+
.ame-rui-redirect.ui-sortable-helper {
|
16 |
+
border: 1px solid #8c8f94;
|
17 |
+
box-shadow: 1px 1px 5px 0 rgba(0, 0, 0, 0.3);
|
18 |
+
}
|
19 |
+
|
20 |
+
.ame-rui-redirect-content {
|
21 |
+
flex-grow: 1;
|
22 |
+
display: flex;
|
23 |
+
flex-direction: row;
|
24 |
+
padding: 10px 10px 10px 0;
|
25 |
+
}
|
26 |
+
|
27 |
+
.ame-rui-actor {
|
28 |
+
margin-right: 10px;
|
29 |
+
font-size: 14px;
|
30 |
+
line-height: 27px;
|
31 |
+
}
|
32 |
+
|
33 |
+
.ame-rui-url-template, ame-redirect-url-input {
|
34 |
+
flex-grow: 1;
|
35 |
+
display: block;
|
36 |
+
margin-right: 10px;
|
37 |
+
position: relative;
|
38 |
+
}
|
39 |
+
.ame-rui-url-template input[type=text], ame-redirect-url-input input[type=text] {
|
40 |
+
width: 100%;
|
41 |
+
}
|
42 |
+
.ame-rui-url-template input[type=text].ame-rui-has-url-dropdown, ame-redirect-url-input input[type=text].ame-rui-has-url-dropdown {
|
43 |
+
padding-right: 32px;
|
44 |
+
}
|
45 |
+
.ame-rui-url-template input[readonly], ame-redirect-url-input input[readonly] {
|
46 |
+
background-color: #dbeeff;
|
47 |
+
}
|
48 |
+
|
49 |
+
.ame-rui-url-dropdown-trigger {
|
50 |
+
display: block;
|
51 |
+
position: absolute;
|
52 |
+
width: 30px;
|
53 |
+
height: 28px;
|
54 |
+
top: 1px;
|
55 |
+
right: 1px;
|
56 |
+
border-top-right-radius: 4px;
|
57 |
+
border-bottom-right-radius: 4px;
|
58 |
+
cursor: pointer;
|
59 |
+
text-align: center;
|
60 |
+
color: #787c82;
|
61 |
+
}
|
62 |
+
.ame-rui-url-dropdown-trigger:hover {
|
63 |
+
color: #1d2327;
|
64 |
+
}
|
65 |
+
.ame-rui-url-dropdown-trigger .am-rui-trigger-icon:before {
|
66 |
+
content: "";
|
67 |
+
font: normal 20px dashicons;
|
68 |
+
line-height: 28px;
|
69 |
+
vertical-align: middle;
|
70 |
+
}
|
71 |
+
|
72 |
+
.ame-rui-shortcodes-enabled {
|
73 |
+
margin: 0 10px 0 0;
|
74 |
+
display: inline-block;
|
75 |
+
box-sizing: border-box;
|
76 |
+
min-height: 30px;
|
77 |
+
padding: 0 10px;
|
78 |
+
line-height: 28px;
|
79 |
+
border: 1px solid #dcdcde;
|
80 |
+
border-radius: 3px;
|
81 |
+
background: #f6f7f7;
|
82 |
+
}
|
83 |
+
.ame-rui-shortcodes-enabled .dashicons {
|
84 |
+
vertical-align: top;
|
85 |
+
line-height: 28px;
|
86 |
+
}
|
87 |
+
.ame-rui-shortcodes-enabled:hover, .ame-rui-shortcodes-enabled:active {
|
88 |
+
background: #f0f0f1;
|
89 |
+
border-color: #0a4b78;
|
90 |
+
}
|
91 |
+
|
92 |
+
.ame-rui-drag-handle {
|
93 |
+
cursor: grab;
|
94 |
+
min-width: 30px;
|
95 |
+
display: flex;
|
96 |
+
align-items: center;
|
97 |
+
justify-content: center;
|
98 |
+
}
|
99 |
+
.ui-sortable-helper > .ame-rui-drag-handle {
|
100 |
+
cursor: grabbing;
|
101 |
+
}
|
102 |
+
|
103 |
+
.ame-rui-drag-icon {
|
104 |
+
fill: #1e1e1e;
|
105 |
+
}
|
106 |
+
|
107 |
+
#ame-rui-menu-items {
|
108 |
+
background-image: none;
|
109 |
+
max-width: 40rem;
|
110 |
+
padding-right: 8px;
|
111 |
+
box-shadow: 3px 3px 4px -2px rgba(0, 0, 0, 0.5);
|
112 |
+
}
|
113 |
+
#ame-rui-menu-items:hover, #ame-rui-menu-items:focus {
|
114 |
+
color: unset;
|
115 |
+
}
|
116 |
+
#ame-rui-menu-items option:hover {
|
117 |
+
background-color: #f0f0f5;
|
118 |
+
}
|
119 |
+
|
120 |
+
.ame-rui-default-redirect-container {
|
121 |
+
max-width: 768px;
|
122 |
+
}
|
123 |
+
.ame-rui-default-redirect-container .ame-rui-redirect-content {
|
124 |
+
padding-left: 10px;
|
125 |
+
}
|
126 |
+
|
127 |
+
.ame-rui-add-actor-dropdown, #ame-rui-user-search-query {
|
128 |
+
min-width: 15em;
|
129 |
+
}
|
130 |
+
|
131 |
+
.ame-rui-found-users li.ui-menu-item {
|
132 |
+
padding: 0;
|
133 |
+
}
|
134 |
+
.ame-rui-found-users .ui-menu-item-wrapper {
|
135 |
+
padding: 4px 10px 6px;
|
136 |
+
}
|
137 |
+
.ame-rui-found-users .ui-state-active {
|
138 |
+
background-color: #2271b1;
|
139 |
+
color: #fff;
|
140 |
+
}
|
141 |
+
|
142 |
+
.ame-rui-missing-actor-indicator {
|
143 |
+
vertical-align: middle;
|
144 |
+
font-style: italic;
|
145 |
+
}
|
146 |
+
|
147 |
+
.ame-rui-trigger-selector {
|
148 |
+
display: inline-block;
|
149 |
+
margin-bottom: 0;
|
150 |
+
list-style: none;
|
151 |
+
font-size: 14px;
|
152 |
+
}
|
153 |
+
.ame-rui-trigger-selector li {
|
154 |
+
display: inline-block;
|
155 |
+
margin: 0;
|
156 |
+
}
|
157 |
+
.ame-rui-trigger-selector li a {
|
158 |
+
text-decoration: none;
|
159 |
+
transition: none;
|
160 |
+
}
|
161 |
+
|
162 |
+
.ame-rui-small-tabs {
|
163 |
+
display: inline-block;
|
164 |
+
padding-left: 7px;
|
165 |
+
border-bottom: 1px solid #c3c4c7;
|
166 |
+
}
|
167 |
+
.ame-rui-small-tabs li {
|
168 |
+
display: inline-block;
|
169 |
+
margin: 0;
|
170 |
+
background: #e3e3e5;
|
171 |
+
border: 1px solid #c3c4c7;
|
172 |
+
border-bottom: none;
|
173 |
+
}
|
174 |
+
.ame-rui-small-tabs li a {
|
175 |
+
display: inline-block;
|
176 |
+
transition: none;
|
177 |
+
min-width: 7em;
|
178 |
+
font-size: 14px;
|
179 |
+
padding: 5px 8px 6px 8px;
|
180 |
+
cursor: pointer;
|
181 |
+
text-decoration: none;
|
182 |
+
color: #444;
|
183 |
+
}
|
184 |
+
.ame-rui-small-tabs li a:hover {
|
185 |
+
color: #2271b1;
|
186 |
+
}
|
187 |
+
.ame-rui-small-tabs .ame-rui-active-tab {
|
188 |
+
background: transparent;
|
189 |
+
border-bottom: 1px solid #f0f0f1;
|
190 |
+
margin-bottom: -1px;
|
191 |
+
}
|
192 |
+
.ame-rui-small-tabs .ame-rui-active-tab a {
|
193 |
+
color: #000;
|
194 |
+
}
|
195 |
+
.ame-rui-small-tabs .ame-rui-active-tab a:hover {
|
196 |
+
color: #000;
|
197 |
+
}
|
198 |
+
|
199 |
+
.ame-rui-filter-like-tabs {
|
200 |
+
display: inline-flex;
|
201 |
+
margin: 13px 0 0 0;
|
202 |
+
list-style: none;
|
203 |
+
border: 1px solid #c3c4c7;
|
204 |
+
background: #fff;
|
205 |
+
}
|
206 |
+
.ame-rui-filter-like-tabs li {
|
207 |
+
display: inline-block;
|
208 |
+
margin: 0;
|
209 |
+
padding: 0;
|
210 |
+
border-style: none;
|
211 |
+
}
|
212 |
+
.ame-rui-filter-like-tabs li a {
|
213 |
+
display: inline-block;
|
214 |
+
margin: 0;
|
215 |
+
padding: 10px 14px;
|
216 |
+
min-width: 7em;
|
217 |
+
border: none;
|
218 |
+
border-bottom: 4px solid transparent;
|
219 |
+
color: #646970;
|
220 |
+
text-decoration: none;
|
221 |
+
cursor: pointer;
|
222 |
+
}
|
223 |
+
.ame-rui-filter-like-tabs li a:hover {
|
224 |
+
color: #135e96;
|
225 |
+
border-bottom-color: transparent;
|
226 |
+
}
|
227 |
+
.ame-rui-filter-like-tabs .ame-rui-active-tab a {
|
228 |
+
border-bottom-color: #007cba;
|
229 |
+
}
|
230 |
+
.ame-rui-filter-like-tabs .ame-rui-active-tab a:hover {
|
231 |
+
color: #646970;
|
232 |
+
border-bottom-color: #007cba;
|
233 |
+
}
|
234 |
+
|
235 |
+
.ame-rui-sub-tabs {
|
236 |
+
margin-top: 6px;
|
237 |
+
font-size: 13px;
|
238 |
+
}
|
239 |
+
.ame-rui-sub-tabs li::after {
|
240 |
+
content: "|";
|
241 |
+
}
|
242 |
+
.ame-rui-sub-tabs li:last-child::after {
|
243 |
+
content: "";
|
244 |
+
}
|
245 |
+
.ame-rui-sub-tabs li a {
|
246 |
+
display: inline-block;
|
247 |
+
text-align: center;
|
248 |
+
padding: 0.2em;
|
249 |
+
line-height: 2;
|
250 |
+
cursor: pointer;
|
251 |
+
}
|
252 |
+
.ame-rui-sub-tabs li a::before {
|
253 |
+
display: block;
|
254 |
+
content: attr(data-text);
|
255 |
+
font-weight: bold;
|
256 |
+
height: 1px;
|
257 |
+
overflow: hidden;
|
258 |
+
visibility: hidden;
|
259 |
+
margin-bottom: -1px;
|
260 |
+
}
|
261 |
+
.ame-rui-sub-tabs li:first-child a {
|
262 |
+
padding-left: 0;
|
263 |
+
text-align: left;
|
264 |
+
}
|
265 |
+
.ame-rui-sub-tabs .ame-rui-active-tab a {
|
266 |
+
font-weight: 600;
|
267 |
+
color: #000;
|
268 |
+
}
|
269 |
+
|
270 |
+
.ame-rui-actions button .dashicons {
|
271 |
+
vertical-align: inherit;
|
272 |
+
line-height: 28px;
|
273 |
+
}
|
274 |
+
|
275 |
+
#ame-rui-column-container {
|
276 |
+
display: flex;
|
277 |
+
flex-direction: row;
|
278 |
+
}
|
279 |
+
|
280 |
+
#ame-rui-main-section {
|
281 |
+
display: block;
|
282 |
+
flex-grow: 1;
|
283 |
+
max-width: 1320px;
|
284 |
+
}
|
285 |
+
|
286 |
+
#ame-rui-sidebar {
|
287 |
+
display: none;
|
288 |
+
width: 300px;
|
289 |
+
flex-shrink: 0;
|
290 |
+
flex-grow: 0;
|
291 |
+
align-self: flex-start;
|
292 |
+
margin-left: 20px;
|
293 |
+
}
|
294 |
+
|
295 |
+
#ame-rui-main-actions {
|
296 |
+
position: relative;
|
297 |
+
box-shadow: 0 1px 1px rgba(0, 0, 0, 0.04);
|
298 |
+
background: #fff;
|
299 |
+
margin-bottom: 20px;
|
300 |
+
margin-bottom: 0;
|
301 |
+
padding: 10px 8px;
|
302 |
+
border: 1px solid #ccd0d4;
|
303 |
+
}
|
304 |
+
#ame-rui-main-actions .ws-ame-postbox-header {
|
305 |
+
position: relative;
|
306 |
+
font-size: 14px;
|
307 |
+
margin: 0;
|
308 |
+
line-height: 1.4;
|
309 |
+
border: 1px solid #ccd0d4;
|
310 |
+
}
|
311 |
+
#ame-rui-main-actions .ws-ame-postbox-header h3 {
|
312 |
+
padding: 10px 12px;
|
313 |
+
margin: 0;
|
314 |
+
font-size: 1em;
|
315 |
+
line-height: 1;
|
316 |
+
white-space: nowrap;
|
317 |
+
text-overflow: ellipsis;
|
318 |
+
overflow: hidden;
|
319 |
+
}
|
320 |
+
#ame-rui-main-actions .ws-ame-postbox-toggle {
|
321 |
+
color: #72777c;
|
322 |
+
background: #fff;
|
323 |
+
display: block;
|
324 |
+
font: normal 20px/1 dashicons;
|
325 |
+
text-align: center;
|
326 |
+
cursor: pointer;
|
327 |
+
border: none;
|
328 |
+
position: absolute;
|
329 |
+
top: 0;
|
330 |
+
right: 0;
|
331 |
+
bottom: 0;
|
332 |
+
width: 36px;
|
333 |
+
height: 100%;
|
334 |
+
padding: 0;
|
335 |
+
}
|
336 |
+
#ame-rui-main-actions .ws-ame-postbox-toggle:hover {
|
337 |
+
color: #23282d;
|
338 |
+
}
|
339 |
+
#ame-rui-main-actions .ws-ame-postbox-toggle:active, #ame-rui-main-actions .ws-ame-postbox-toggle:focus {
|
340 |
+
outline: none;
|
341 |
+
padding: 0;
|
342 |
+
}
|
343 |
+
#ame-rui-main-actions .ws-ame-postbox-toggle:before {
|
344 |
+
content: "";
|
345 |
+
display: inline-block;
|
346 |
+
vertical-align: middle;
|
347 |
+
}
|
348 |
+
#ame-rui-main-actions .ws-ame-postbox-toggle:after {
|
349 |
+
display: inline-block;
|
350 |
+
content: "";
|
351 |
+
vertical-align: middle;
|
352 |
+
height: 100%;
|
353 |
+
}
|
354 |
+
#ame-rui-main-actions .ws-ame-postbox-content {
|
355 |
+
border: 1px solid #ccd0d4;
|
356 |
+
border-top: none;
|
357 |
+
padding: 12px;
|
358 |
+
}
|
359 |
+
#ame-rui-main-actions.ws-ame-closed-postbox .ws-ame-postbox-content {
|
360 |
+
display: none;
|
361 |
+
}
|
362 |
+
#ame-rui-main-actions.ws-ame-closed-postbox .ws-ame-postbox-toggle:before {
|
363 |
+
content: "";
|
364 |
+
}
|
365 |
+
#ame-rui-main-actions input.button {
|
366 |
+
max-width: 150px;
|
367 |
+
width: 100%;
|
368 |
+
}
|
369 |
+
|
370 |
+
@media only screen and (min-width: 961px) {
|
371 |
+
.ame-rui-actor {
|
372 |
+
min-width: 12em;
|
373 |
+
}
|
374 |
+
|
375 |
+
.ame-rui-actions button .dashicons {
|
376 |
+
display: none;
|
377 |
+
}
|
378 |
+
}
|
379 |
+
@media only screen and (max-width: 960px) {
|
380 |
+
.ame-rui-actor {
|
381 |
+
min-width: 10em;
|
382 |
+
}
|
383 |
+
|
384 |
+
.ame-rui-shortcodes-enabled .ame-rui-button-label, .ame-rui-actions .ame-rui-button-label {
|
385 |
+
display: none;
|
386 |
+
}
|
387 |
+
}
|
388 |
+
@media only screen and (max-width: 782px) {
|
389 |
+
.ame-rui-actor {
|
390 |
+
line-height: 38px;
|
391 |
+
}
|
392 |
+
|
393 |
+
.ame-rui-redirect .ame-rui-actions button {
|
394 |
+
margin-bottom: 0;
|
395 |
+
}
|
396 |
+
.ame-rui-redirect .ame-rui-actions button .dashicons {
|
397 |
+
line-height: 38px;
|
398 |
+
vertical-align: top;
|
399 |
+
}
|
400 |
+
|
401 |
+
.ame-rui-shortcodes-enabled {
|
402 |
+
line-height: 38px;
|
403 |
+
}
|
404 |
+
.ame-rui-shortcodes-enabled .dashicons {
|
405 |
+
vertical-align: top;
|
406 |
+
line-height: 38px;
|
407 |
+
}
|
408 |
+
}
|
409 |
+
|
410 |
+
/*# sourceMappingURL=redirector.css.map */
|
modules/redirector/redirector.php
ADDED
@@ -0,0 +1,1027 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
|
3 |
+
namespace YahnisElsts\AdminMenuEditor\Redirects;
|
4 |
+
|
5 |
+
use ameMenuItem;
|
6 |
+
use amePersistentModule;
|
7 |
+
use ameRoleUtils;
|
8 |
+
use ameUtils;
|
9 |
+
use DateTime;
|
10 |
+
use DateTimeZone;
|
11 |
+
use Exception;
|
12 |
+
use RuntimeException;
|
13 |
+
use WP_Error;
|
14 |
+
use WP_User;
|
15 |
+
|
16 |
+
class Module extends amePersistentModule {
|
17 |
+
const FILTER_PRIORITY = 1000000;
|
18 |
+
const UI_SCRIPT_HANDLE = 'ame-redirector-ui';
|
19 |
+
|
20 |
+
const FIRST_LOGIN_AGE_LIMIT_IN_DAYS = 14;
|
21 |
+
const FIRST_LOGIN_META_KEY = 'ame_rui_first_login_done';
|
22 |
+
|
23 |
+
const SETTINGS_INIT_TIME_KEY = 'ws_ame_rui_first_change';
|
24 |
+
|
25 |
+
const PRELOADED_USER_LIMIT = 50;
|
26 |
+
const SEARCH_USER_LIMIT = 30;
|
27 |
+
protected static $desiredUserFields = array('ID', 'display_name', 'user_login');
|
28 |
+
|
29 |
+
protected $tabSlug = 'redirects';
|
30 |
+
protected $tabTitle = 'Redirects';
|
31 |
+
protected $optionName = 'ws_ame_redirects';
|
32 |
+
|
33 |
+
protected $settingsFormAction = 'ame-save-redirect-settings';
|
34 |
+
|
35 |
+
/**
|
36 |
+
* @var RedirectCollection|null
|
37 |
+
*/
|
38 |
+
protected $redirects = null;
|
39 |
+
|
40 |
+
protected $searchUsersAction;
|
41 |
+
|
42 |
+
public function __construct($menuEditor) {
|
43 |
+
parent::__construct($menuEditor);
|
44 |
+
|
45 |
+
if ( !$this->isEnabledForRequest() ) {
|
46 |
+
return;
|
47 |
+
}
|
48 |
+
|
49 |
+
//Let the user disable all redirects in wp-config.php.
|
50 |
+
$allRedirectsDisabled = defined('AME_DISABLE_REDIRECTS') && constant('AME_DISABLE_REDIRECTS');
|
51 |
+
if ( !$allRedirectsDisabled ) {
|
52 |
+
//Login redirect.
|
53 |
+
add_filter('login_redirect', [$this, 'filterLoginRedirect'], self::FILTER_PRIORITY, 3);
|
54 |
+
//Logout redirect. We might need to also use the "wp_logout" action if something bypasses wp-login.php.
|
55 |
+
add_filter('logout_redirect', [$this, 'filterLogoutRedirect'], self::FILTER_PRIORITY, 3);
|
56 |
+
//Registration redirect. This happens after the user is created but before the user logs in.
|
57 |
+
add_filter('registration_redirect', [$this, 'filterRegistrationRedirect'], self::FILTER_PRIORITY, 1);
|
58 |
+
}
|
59 |
+
|
60 |
+
if ( is_admin() ) {
|
61 |
+
$this->searchUsersAction = ajaw_v1_CreateAction('ws-ame-rui-search-users')
|
62 |
+
->requiredParam('term')
|
63 |
+
->method('get')
|
64 |
+
->permissionCallback(array($this, 'userCanSearchUsers'))
|
65 |
+
->handler(array($this, 'ajaxSearchUsers'))
|
66 |
+
->register();
|
67 |
+
|
68 |
+
add_action('admin_menu_editor-load_tab-' . $this->tabSlug, [$this, 'addContextualHelp']);
|
69 |
+
}
|
70 |
+
}
|
71 |
+
|
72 |
+
/**
|
73 |
+
* Get the redirect that best matches the given trigger and user.
|
74 |
+
*
|
75 |
+
* When there are multiple redirects that could apply in this context, only the redirect
|
76 |
+
* with the highest priority will be returned.
|
77 |
+
*
|
78 |
+
* @param string $trigger
|
79 |
+
* @param WP_User $user
|
80 |
+
* @return Option<Redirect>
|
81 |
+
*/
|
82 |
+
protected function getBestRedirectFor($trigger, WP_User $user) {
|
83 |
+
$redirectsForTrigger = $this->getRedirects()->filterByTrigger($trigger);
|
84 |
+
if ( empty($redirectsForTrigger) ) {
|
85 |
+
return None::getInstance();
|
86 |
+
}
|
87 |
+
$actors = $this->getUserActors($user);
|
88 |
+
|
89 |
+
//Redirects should already be sorted by priority, so we can just return the first match.
|
90 |
+
foreach ($redirectsForTrigger as $redirect) {
|
91 |
+
if ( array_key_exists($redirect->getActorId(), $actors) ) {
|
92 |
+
return new Some($redirect);
|
93 |
+
}
|
94 |
+
}
|
95 |
+
return None::getInstance();
|
96 |
+
}
|
97 |
+
|
98 |
+
/**
|
99 |
+
* @param WP_User|null $user
|
100 |
+
* @return array<string,bool>
|
101 |
+
*/
|
102 |
+
protected function getUserActors(WP_User $user) {
|
103 |
+
$actorIds = ['special:default' => true]; //The "default" setting applies to every user.
|
104 |
+
|
105 |
+
if ( !isset($user) ) {
|
106 |
+
return $actorIds;
|
107 |
+
}
|
108 |
+
|
109 |
+
if ( isset($user->user_login) ) {
|
110 |
+
$actorIds['user:' . $user->user_login] = true;
|
111 |
+
}
|
112 |
+
|
113 |
+
if ( isset($user->roles) && is_array($user->roles) ) {
|
114 |
+
foreach ($user->roles as $roleId) {
|
115 |
+
$actorIds['role:' . $roleId] = true;
|
116 |
+
}
|
117 |
+
}
|
118 |
+
|
119 |
+
if ( is_multisite() && is_super_admin($user) ) {
|
120 |
+
$actorIds['special:super_admin'] = true;
|
121 |
+
}
|
122 |
+
|
123 |
+
return $actorIds;
|
124 |
+
}
|
125 |
+
|
126 |
+
/**
|
127 |
+
* @return RedirectCollection
|
128 |
+
*/
|
129 |
+
protected function getRedirects() {
|
130 |
+
if ( $this->redirects !== null ) {
|
131 |
+
return $this->redirects;
|
132 |
+
}
|
133 |
+
|
134 |
+
$settings = $this->loadSettings();
|
135 |
+
if ( isset($settings['redirects']) ) {
|
136 |
+
$this->redirects = RedirectCollection::fromDbFormat($settings['redirects']);
|
137 |
+
} else {
|
138 |
+
$this->redirects = new RedirectCollection();
|
139 |
+
}
|
140 |
+
|
141 |
+
return $this->redirects;
|
142 |
+
}
|
143 |
+
|
144 |
+
public function saveSettings() {
|
145 |
+
if ( isset($this->redirects) ) {
|
146 |
+
$this->loadSettings();
|
147 |
+
$this->settings['redirects'] = $this->redirects->toDbFormat();
|
148 |
+
}
|
149 |
+
parent::saveSettings();
|
150 |
+
}
|
151 |
+
|
152 |
+
/**
|
153 |
+
* @param string $redirectTo
|
154 |
+
* @param string $requestedRedirectTo
|
155 |
+
* @param WP_User|WP_Error $user
|
156 |
+
* @return string
|
157 |
+
* @noinspection PhpUnusedParameterInspection The parameters are defined by the hook and can't be changed.
|
158 |
+
*/
|
159 |
+
public function filterLoginRedirect($redirectTo, $requestedRedirectTo, $user = null) {
|
160 |
+
if ( $this->checkFirstLogin($user) ) {
|
161 |
+
$trigger = Triggers::FIRST_LOGIN;
|
162 |
+
} else {
|
163 |
+
$trigger = Triggers::LOGIN;
|
164 |
+
}
|
165 |
+
return $this->filterRedirect($trigger, $redirectTo, $requestedRedirectTo, $user);
|
166 |
+
}
|
167 |
+
|
168 |
+
public function filterLogoutRedirect($redirectTo, $requestedRedirectTo, $user = null) {
|
169 |
+
return $this->filterRedirect(Triggers::LOGOUT, $redirectTo, $requestedRedirectTo, $user);
|
170 |
+
}
|
171 |
+
|
172 |
+
public function filterRegistrationRedirect($redirectTo) {
|
173 |
+
//Note that this does not depend on the user's role as the user isn't logged in yet.
|
174 |
+
return $this->filterRedirect(Triggers::REGISTRATION, $redirectTo, '');
|
175 |
+
}
|
176 |
+
|
177 |
+
/**
|
178 |
+
* @param string $trigger
|
179 |
+
* @param string $redirectTo
|
180 |
+
* @param string $requestedRedirectTo
|
181 |
+
* @param WP_User|WP_Error $user
|
182 |
+
* @return string
|
183 |
+
* @noinspection PhpUnusedParameterInspection The requested URL is unused right now, but might be useful in the future.
|
184 |
+
*/
|
185 |
+
protected function filterRedirect($trigger, $redirectTo, $requestedRedirectTo, $user = null) {
|
186 |
+
if ( !($user instanceof WP_User) ) {
|
187 |
+
return $redirectTo;
|
188 |
+
}
|
189 |
+
|
190 |
+
$found = $this->getBestRedirectFor($trigger, $user);
|
191 |
+
if ( $found->nonEmpty() ) {
|
192 |
+
/** @var Redirect $customRedirect */
|
193 |
+
$customRedirect = $found->get();
|
194 |
+
$url = $customRedirect->getUrl();
|
195 |
+
|
196 |
+
//WordPress uses wp_safe_redirect() for login, logout, and registration redirects, which
|
197 |
+
//only allows local redirects by default. Let's temporarily add the domain name of the URL
|
198 |
+
//to the allowed host list to let the user set any redirect URL they want.
|
199 |
+
$redirectHost = parse_url($url, PHP_URL_HOST);
|
200 |
+
if ( !empty($redirectHost) ) {
|
201 |
+
add_filter('allowed_redirect_hosts', function ($allowedHosts) use ($redirectHost) {
|
202 |
+
$allowedHosts[] = $redirectHost;
|
203 |
+
return $allowedHosts;
|
204 |
+
});
|
205 |
+
}
|
206 |
+
|
207 |
+
return $url;
|
208 |
+
} else {
|
209 |
+
return $redirectTo;
|
210 |
+
}
|
211 |
+
}
|
212 |
+
|
213 |
+
/**
|
214 |
+
* @param WP_User|null $user
|
215 |
+
* @return bool
|
216 |
+
*/
|
217 |
+
private function checkFirstLogin($user) {
|
218 |
+
if ( !($user instanceof WP_User) ) {
|
219 |
+
return false;
|
220 |
+
}
|
221 |
+
|
222 |
+
/* WordPress doesn't record logins by default, so we use a few checks to help ensure that
|
223 |
+
* this redirect will only happen when a new user logs in for the first time:
|
224 |
+
*
|
225 |
+
* - Account doesn't have the custom "first login done" flag.
|
226 |
+
* - Account is less than X days old.
|
227 |
+
* - Account was created after the admin changed redirect settings for the first time.
|
228 |
+
*/
|
229 |
+
|
230 |
+
//Check the first login flag.
|
231 |
+
$isFirstLoginDone = !empty(get_user_meta($user->ID, self::FIRST_LOGIN_META_KEY, true));
|
232 |
+
if ( $isFirstLoginDone ) {
|
233 |
+
return false;
|
234 |
+
}
|
235 |
+
//This may or may not be the first login, but any future logins definitely won't be first.
|
236 |
+
update_user_meta($user->ID, self::FIRST_LOGIN_META_KEY, 1);
|
237 |
+
|
238 |
+
//Account age.
|
239 |
+
//Handle invalid timestamps by acting as if the user was registered just now.
|
240 |
+
$registrationTime = $this->getRegistrationTimestamp($user, time());
|
241 |
+
$accountAgeInDays = (time() - $registrationTime) / (24 * 3600);
|
242 |
+
if ( $accountAgeInDays > self::FIRST_LOGIN_AGE_LIMIT_IN_DAYS ) {
|
243 |
+
return false;
|
244 |
+
}
|
245 |
+
|
246 |
+
//Account created after using the "redirects" feature, not before.
|
247 |
+
if ( $registrationTime <= $this->getFirstSettingsActivityTime() ) {
|
248 |
+
return false;
|
249 |
+
}
|
250 |
+
return true;
|
251 |
+
}
|
252 |
+
|
253 |
+
/**
|
254 |
+
* @param WP_User $user
|
255 |
+
* @param int $default
|
256 |
+
*/
|
257 |
+
private function getRegistrationTimestamp($user, $default) {
|
258 |
+
if ( !isset($user, $user->user_registered) ) {
|
259 |
+
return $default;
|
260 |
+
}
|
261 |
+
|
262 |
+
try {
|
263 |
+
$dateTime = new DateTime($user->user_registered, new DateTimeZone('UTC'));
|
264 |
+
return $dateTime->getTimestamp();
|
265 |
+
} catch (Exception $e) {
|
266 |
+
return $default;
|
267 |
+
}
|
268 |
+
}
|
269 |
+
|
270 |
+
/**
|
271 |
+
* @return int
|
272 |
+
*/
|
273 |
+
private function getFirstSettingsActivityTime() {
|
274 |
+
$activityTimestamp = get_site_option(self::SETTINGS_INIT_TIME_KEY, 0);
|
275 |
+
if ( is_numeric($activityTimestamp) ) {
|
276 |
+
return intval($activityTimestamp);
|
277 |
+
} else {
|
278 |
+
return 0;
|
279 |
+
}
|
280 |
+
}
|
281 |
+
|
282 |
+
public function registerScripts() {
|
283 |
+
parent::registerScripts();
|
284 |
+
|
285 |
+
wp_register_auto_versioned_script(
|
286 |
+
'ame-knockout-sortable',
|
287 |
+
plugins_url('knockout-sortable.js', __FILE__),
|
288 |
+
['knockout', 'jquery', 'jquery-ui-sortable', 'jquery-ui-draggable', 'jquery-ui-droppable']
|
289 |
+
);
|
290 |
+
}
|
291 |
+
|
292 |
+
public function enqueueTabScripts() {
|
293 |
+
parent::enqueueTabScripts();
|
294 |
+
|
295 |
+
wp_enqueue_auto_versioned_script(
|
296 |
+
self::UI_SCRIPT_HANDLE,
|
297 |
+
plugins_url('redirector-ui.js', __FILE__),
|
298 |
+
[
|
299 |
+
'jquery',
|
300 |
+
'jquery-ui-position',
|
301 |
+
'jquery-ui-autocomplete',
|
302 |
+
'knockout',
|
303 |
+
'ame-actor-selector',
|
304 |
+
'ame-actor-manager',
|
305 |
+
'ame-knockout-sortable',
|
306 |
+
'ame-lodash',
|
307 |
+
$this->searchUsersAction->getScriptHandle(),
|
308 |
+
]
|
309 |
+
);
|
310 |
+
|
311 |
+
$flattenedRedirects = $this->getRedirects()->flatten();
|
312 |
+
|
313 |
+
$usableMenuItems = [];
|
314 |
+
$adminMenu = $this->menuEditor->get_active_admin_menu();
|
315 |
+
if ( !empty($adminMenu['tree']) ) {
|
316 |
+
$extractor = new MenuExtractor($adminMenu['tree']);
|
317 |
+
$usableMenuItems = $extractor->getUsableItems();
|
318 |
+
}
|
319 |
+
|
320 |
+
$wpRoles = ameRoleUtils::get_roles();
|
321 |
+
$roles = [];
|
322 |
+
foreach ($wpRoles->role_objects as $roleId => $role) {
|
323 |
+
$roles[] = [
|
324 |
+
'name' => $roleId,
|
325 |
+
'displayName' => ameUtils::get($wpRoles->role_names, $roleId, $roleId),
|
326 |
+
];
|
327 |
+
}
|
328 |
+
|
329 |
+
list($loadedUsers, $hasMoreUsers) = $this->preloadUsers($flattenedRedirects);
|
330 |
+
|
331 |
+
$scriptData = [
|
332 |
+
'redirects' => $flattenedRedirects,
|
333 |
+
'usableMenuItems' => $usableMenuItems,
|
334 |
+
'roles' => $roles,
|
335 |
+
'users' => $loadedUsers,
|
336 |
+
'hasMoreUsers' => $hasMoreUsers,
|
337 |
+
];
|
338 |
+
|
339 |
+
if ( isset($_GET['selectedTrigger']) && in_array($_GET['selectedTrigger'], Triggers::getValues()) ) {
|
340 |
+
$scriptData['selectedTrigger'] = $_GET['selectedTrigger'];
|
341 |
+
}
|
342 |
+
|
343 |
+
wp_add_inline_script(
|
344 |
+
self::UI_SCRIPT_HANDLE,
|
345 |
+
sprintf('wsAmeRedirectorSettings = (%s);', wp_json_encode($scriptData)),
|
346 |
+
'before'
|
347 |
+
);
|
348 |
+
}
|
349 |
+
|
350 |
+
public function enqueueTabStyles() {
|
351 |
+
parent::enqueueTabStyles();
|
352 |
+
|
353 |
+
wp_enqueue_auto_versioned_style(
|
354 |
+
'ame-redirector-ui-css',
|
355 |
+
plugins_url('redirector.css', __FILE__)
|
356 |
+
);
|
357 |
+
}
|
358 |
+
|
359 |
+
public function handleSettingsForm($post = array()) {
|
360 |
+
parent::handleSettingsForm($post);
|
361 |
+
|
362 |
+
$submittedSettings = json_decode($post['settings'], true);
|
363 |
+
$validationResult = $this->validateSubmittedSettings($submittedSettings);
|
364 |
+
|
365 |
+
if ( is_wp_error($validationResult) ) {
|
366 |
+
//It seems that wp_die() doesn't automatically escape special characters, so let's do that.
|
367 |
+
$message = esc_html($validationResult->get_error_message());
|
368 |
+
wp_die($message);
|
369 |
+
}
|
370 |
+
|
371 |
+
$newRedirects = new RedirectCollection();
|
372 |
+
foreach ($submittedSettings['redirects'] as $redirect) {
|
373 |
+
$newRedirects->add($redirect);
|
374 |
+
}
|
375 |
+
$this->redirects = $newRedirects;
|
376 |
+
$this->saveSettings();
|
377 |
+
|
378 |
+
//Remember the first time the admin changes settings. This can then be used to avoid applying
|
379 |
+
//"first login" redirects to users who existed before any custom redirects did.
|
380 |
+
$activityTimestamp = get_site_option(self::SETTINGS_INIT_TIME_KEY, null);
|
381 |
+
if ( empty($activityTimestamp) ) {
|
382 |
+
add_site_option(self::SETTINGS_INIT_TIME_KEY, time());
|
383 |
+
}
|
384 |
+
|
385 |
+
$params = ['updated' => 1];
|
386 |
+
if ( !empty($post['selectedTrigger']) ) {
|
387 |
+
$params['selectedTrigger'] = strval($post['selectedTrigger']);
|
388 |
+
}
|
389 |
+
|
390 |
+
wp_redirect($this->getTabUrl($params));
|
391 |
+
exit;
|
392 |
+
}
|
393 |
+
|
394 |
+
/**
|
395 |
+
* @param $settings
|
396 |
+
* @return bool|WP_Error
|
397 |
+
*/
|
398 |
+
protected function validateSubmittedSettings($settings) {
|
399 |
+
if ( !is_array($settings) ) {
|
400 |
+
return new WP_Error(
|
401 |
+
'ame_invalid_json',
|
402 |
+
sprintf('Invalid JSON data. Expected an associative array, got %s.', gettype($settings))
|
403 |
+
);
|
404 |
+
}
|
405 |
+
|
406 |
+
if ( !array_key_exists('redirects', $settings) ) {
|
407 |
+
return new WP_Error('rui_missing_redirects_key', 'The required "redirects" field is missing.');
|
408 |
+
}
|
409 |
+
|
410 |
+
$allowedProperties = [
|
411 |
+
//Actor IDs always follow the "prefix:value" format.
|
412 |
+
'actorId' => /** @lang RegExp */
|
413 |
+
'@^[a-z]{1,15}+:[^\s].{0,300}+$@i',
|
414 |
+
//The URL can be basically anything, so we don't try to validate it.
|
415 |
+
'urlTemplate' => null,
|
416 |
+
//Menu template IDs are based on menu URLs, so they're pretty unpredictable. If one is given, it must be non-empty.
|
417 |
+
'menuTemplateId' => /** @lang RegExp */
|
418 |
+
'@^.@',
|
419 |
+
//A trigger is always a lowercase string. We could just list the supported values once dev. is done.
|
420 |
+
'trigger' => /** @lang RegExp */
|
421 |
+
'@^[a-z\-]{2,20}+$@i',
|
422 |
+
//The shortcode flag is a boolean value. No regex for that.
|
423 |
+
'shortcodesEnabled' => null,
|
424 |
+
];
|
425 |
+
$requiredProperties = [
|
426 |
+
'actorId' => true,
|
427 |
+
'urlTemplate' => true,
|
428 |
+
'shortcodesEnabled' => true,
|
429 |
+
'trigger' => true,
|
430 |
+
];
|
431 |
+
|
432 |
+
foreach ($settings['redirects'] as $key => $redirect) {
|
433 |
+
if ( !is_array($redirect) ) {
|
434 |
+
return new WP_Error(
|
435 |
+
'rui_bad_redirect_data_type',
|
436 |
+
sprintf('Redirect %s should be an array but it is actually %s', $key, gettype($redirect))
|
437 |
+
);
|
438 |
+
}
|
439 |
+
|
440 |
+
//Verify that it has all the required properties.
|
441 |
+
$missingProperties = array_diff_key($requiredProperties, $redirect);
|
442 |
+
if ( !empty($missingProperties) ) {
|
443 |
+
$firstMissingProp = reset($missingProperties);
|
444 |
+
return new WP_Error(
|
445 |
+
'rui_missing_key',
|
446 |
+
sprintf('Redirect %s is missing the required property "%s"', $key, $firstMissingProp)
|
447 |
+
);
|
448 |
+
}
|
449 |
+
|
450 |
+
//Verify that the redirect has only allowed properties.
|
451 |
+
$badProperties = array_diff_key($redirect, $allowedProperties);
|
452 |
+
if ( !empty($badProperties) ) {
|
453 |
+
$firstBadProp = reset($badProperties);
|
454 |
+
return new WP_Error(
|
455 |
+
'rui_bad_key',
|
456 |
+
sprintf('Redirect %s has an unsupported property "%s"', $key, $firstBadProp)
|
457 |
+
);
|
458 |
+
}
|
459 |
+
|
460 |
+
//String properties must match their validation regex (if any).
|
461 |
+
foreach ($allowedProperties as $property => $regex) {
|
462 |
+
if (
|
463 |
+
is_string($regex) && isset($redirect[$property])
|
464 |
+
&& (!is_string($redirect[$property]) || !preg_match($regex, $redirect[$property]))
|
465 |
+
) {
|
466 |
+
return new WP_Error(
|
467 |
+
'rui_invalid_property_value',
|
468 |
+
sprintf('Redirect %s: Property "%s" has an invalid value.', $key, $property)
|
469 |
+
);
|
470 |
+
}
|
471 |
+
}
|
472 |
+
|
473 |
+
//shortcodesEnabled must be a boolean.
|
474 |
+
if ( array_key_exists('shortcodesEnabled', $redirect) && !is_bool($redirect['shortcodesEnabled']) ) {
|
475 |
+
return new WP_Error(
|
476 |
+
'rui_invalid_property_value',
|
477 |
+
sprintf(
|
478 |
+
'Redirect %s: The "shortcodesEnabled" property is invalid.'
|
479 |
+
. ' Expected a boolean, but actual type is "%s".',
|
480 |
+
$key,
|
481 |
+
gettype($redirect['shortcodesEnabled'])
|
482 |
+
)
|
483 |
+
);
|
484 |
+
}
|
485 |
+
|
486 |
+
//URL template must be a string.
|
487 |
+
if ( !is_string($redirect['urlTemplate']) ) {
|
488 |
+
return new WP_Error(
|
489 |
+
'rui_invalid_property_value',
|
490 |
+
sprintf(
|
491 |
+
'Redirect %s: The "urlTemplate" property is invalid.'
|
492 |
+
. ' Expected a string, but actual type is "%s".',
|
493 |
+
$key,
|
494 |
+
gettype($redirect['urlTemplate'])
|
495 |
+
)
|
496 |
+
);
|
497 |
+
}
|
498 |
+
|
499 |
+
//URL template must be non-empty.
|
500 |
+
if ( trim($redirect['urlTemplate']) === '' ) {
|
501 |
+
return new WP_Error(
|
502 |
+
'rui_empty_url',
|
503 |
+
sprintf('Redirect %s: The "urlTemplate" property is empty.', $key)
|
504 |
+
);
|
505 |
+
}
|
506 |
+
}
|
507 |
+
|
508 |
+
return true;
|
509 |
+
}
|
510 |
+
|
511 |
+
/**
|
512 |
+
* Load user data for display in the redirect management UI.
|
513 |
+
*
|
514 |
+
* Will load some or all users depending on how many users there are in total.
|
515 |
+
* Users that have custom redirects are always loaded.
|
516 |
+
*
|
517 |
+
* @param array $flattenedRedirects
|
518 |
+
* @return array{array,boolean} An array of users and a boolean indicating if the total number exceeds the limit.
|
519 |
+
*/
|
520 |
+
protected function preloadUsers(array $flattenedRedirects) {
|
521 |
+
$loadedUsers = get_users([
|
522 |
+
//In Multisite, include all sites and not just the current site. Note that this might not work if used
|
523 |
+
//together with some other arguments (judging by WP_User_Query::prepare_query source code).
|
524 |
+
'blog_id' => 0,
|
525 |
+
'number' => self::PRELOADED_USER_LIMIT + 1,
|
526 |
+
'count_total' => false, //Allegedly, this can improve performance.
|
527 |
+
'fields' => self::$desiredUserFields,
|
528 |
+
]);
|
529 |
+
|
530 |
+
$hasMoreUsers = count($loadedUsers) > self::PRELOADED_USER_LIMIT;
|
531 |
+
|
532 |
+
$isUserLoaded = [];
|
533 |
+
foreach ($loadedUsers as $user) {
|
534 |
+
$isUserLoaded[$user->user_login] = true;
|
535 |
+
}
|
536 |
+
|
537 |
+
//Always load users that already have custom redirects.
|
538 |
+
$userPrefix = 'user:';
|
539 |
+
$userPrefixLength = strlen($userPrefix);
|
540 |
+
$usersToLoad = [];
|
541 |
+
foreach ($flattenedRedirects as $details) {
|
542 |
+
if ( substr($details['actorId'], 0, $userPrefixLength) === $userPrefix ) {
|
543 |
+
$userLogin = substr($details['actorId'], $userPrefixLength);
|
544 |
+
if (
|
545 |
+
is_string($userLogin) && ($userLogin !== '')
|
546 |
+
&& empty($isUserLoaded[$userLogin])
|
547 |
+
&& empty($usersToLoad[$userLogin])
|
548 |
+
) {
|
549 |
+
$usersToLoad[$userLogin] = true;
|
550 |
+
}
|
551 |
+
}
|
552 |
+
}
|
553 |
+
|
554 |
+
if ( !empty($usersToLoad) ) {
|
555 |
+
$additionalUsers = get_users([
|
556 |
+
'blog_id' => 0,
|
557 |
+
'count_total' => false,
|
558 |
+
'fields' => self::$desiredUserFields,
|
559 |
+
'login__in' => array_values($usersToLoad),
|
560 |
+
]);
|
561 |
+
$loadedUsers = array_merge($loadedUsers, $additionalUsers);
|
562 |
+
}
|
563 |
+
|
564 |
+
return [$loadedUsers, $hasMoreUsers];
|
565 |
+
}
|
566 |
+
|
567 |
+
public function userCanSearchUsers() {
|
568 |
+
return $this->menuEditor->current_user_can_edit_menu();
|
569 |
+
}
|
570 |
+
|
571 |
+
public function ajaxSearchUsers($params) {
|
572 |
+
$foundUsers = get_users([
|
573 |
+
'search' => '*' . $params['term'] . '*',
|
574 |
+
'search_columns' => ['user_login', 'display_name'],
|
575 |
+
'blog_id' => 0,
|
576 |
+
'number' => self::SEARCH_USER_LIMIT,
|
577 |
+
'count_total' => false,
|
578 |
+
'fields' => self::$desiredUserFields,
|
579 |
+
]);
|
580 |
+
|
581 |
+
$results = [];
|
582 |
+
foreach ($foundUsers as $user) {
|
583 |
+
$user = (array)$user;
|
584 |
+
$results[] = array_merge($user, ['label' => $user['user_login']]);
|
585 |
+
}
|
586 |
+
|
587 |
+
return $results;
|
588 |
+
}
|
589 |
+
|
590 |
+
public function addContextualHelp() {
|
591 |
+
if ( !is_callable('get_current_screen') ) {
|
592 |
+
return;
|
593 |
+
}
|
594 |
+
$screen = get_current_screen();
|
595 |
+
if ( $screen ) {
|
596 |
+
$screen->add_help_tab([
|
597 |
+
'title' => 'Shortcodes',
|
598 |
+
'id' => 'ame-rui-help-shortcodes',
|
599 |
+
'content' => $this->getShortcodeHelp(),
|
600 |
+
]);
|
601 |
+
|
602 |
+
$screen->add_help_tab([
|
603 |
+
'title' => 'Priority',
|
604 |
+
'id' => 'ame-rui-help-priority',
|
605 |
+
'content' => $this->getPriorityHelp(),
|
606 |
+
]);
|
607 |
+
|
608 |
+
$screen->add_help_tab([
|
609 |
+
'title' => 'First Login',
|
610 |
+
'id' => 'ame-rui-help-first-login',
|
611 |
+
'content' => $this->getFirstLoginHelp(),
|
612 |
+
]);
|
613 |
+
|
614 |
+
$screen->add_help_tab([
|
615 |
+
'title' => 'Disabling Redirects',
|
616 |
+
'id' => 'ame-rui-emergency-shutdown',
|
617 |
+
'content' => $this->getEmergencyShutdownHelp(),
|
618 |
+
]);
|
619 |
+
}
|
620 |
+
}
|
621 |
+
|
622 |
+
private function getShortcodeHelp() {
|
623 |
+
$message = '<p>You can use shortcodes in redirect URLs. This plugin comes with a few shortcodes that could be useful for redirects:</p>';
|
624 |
+
$message .= '<ul>';
|
625 |
+
|
626 |
+
$message .= '<li>' . $this->formatShortcodeInfo(
|
627 |
+
'ame-wp-admin',
|
628 |
+
'base URL of the WordPress dashboard. Includes the trailing slash.',
|
629 |
+
'[ame-wp-admin]'
|
630 |
+
) . '</li>';
|
631 |
+
|
632 |
+
$message .= '<li>' . $this->formatShortcodeInfo(
|
633 |
+
'ame-home-url',
|
634 |
+
'site URL. Usually, this will be the same as the "Site Address (URL)" value in <em>Settings → General</em>.',
|
635 |
+
'[ame-home-url]'
|
636 |
+
) . '</li>';
|
637 |
+
|
638 |
+
$message .= '<li>' . $this->formatShortcodeInfo(
|
639 |
+
'ame-user-info',
|
640 |
+
'information about the logged-in user. Use the <code>field</code> parameter to specify which field to output. Examples:'
|
641 |
+
) . '<ul>';
|
642 |
+
|
643 |
+
$userExampleFields = [
|
644 |
+
'ID',
|
645 |
+
'user_login',
|
646 |
+
'display_name',
|
647 |
+
'locale',
|
648 |
+
'user_nicename',
|
649 |
+
];
|
650 |
+
foreach ($userExampleFields as $field) {
|
651 |
+
$code = '[ame-user-info field="' . $field . '"]';
|
652 |
+
$message .= sprintf('<li><code>%s</code> = %s</li>', $code, $this->getExampleShortcodeOutput($code));
|
653 |
+
}
|
654 |
+
|
655 |
+
$message .= '</ul></ul>';
|
656 |
+
|
657 |
+
$message .= '<p>Some shortcodes from other plugins may also work, but it depends on the shortcode.</p>';
|
658 |
+
|
659 |
+
return $message;
|
660 |
+
}
|
661 |
+
|
662 |
+
private function formatShortcodeInfo($tag, $description, $exampleCode = null) {
|
663 |
+
$result = sprintf('<code>[%s]</code> - %s', esc_html($tag), $description);
|
664 |
+
if ( $exampleCode !== null ) {
|
665 |
+
$result .= ' Example output:<br>' . $this->getExampleShortcodeOutput($exampleCode);
|
666 |
+
}
|
667 |
+
return $result;
|
668 |
+
}
|
669 |
+
|
670 |
+
private function getExampleShortcodeOutput($exampleCode) {
|
671 |
+
$output = do_shortcode($exampleCode);
|
672 |
+
if ( $output === '' ) {
|
673 |
+
return '<em>(empty string)</em>';
|
674 |
+
}
|
675 |
+
return sprintf('<code>%s</code>', esc_html($output));
|
676 |
+
}
|
677 |
+
|
678 |
+
private function getPriorityHelp() {
|
679 |
+
$tips = [
|
680 |
+
'Redirects are processed from top to bottom and the first matching setting is used.',
|
681 |
+
'You can drag and drop redirects to change their priority.',
|
682 |
+
'When you create redirects for specific users their order doesn\'t matter, but you can still move them around to organize them.',
|
683 |
+
];
|
684 |
+
|
685 |
+
return '<ul><li>'
|
686 |
+
. implode("</li>\n<li>", array_map('esc_html', $tips))
|
687 |
+
. '</li></ul>';
|
688 |
+
}
|
689 |
+
|
690 |
+
private function getFirstLoginHelp() {
|
691 |
+
$conditions = [
|
692 |
+
sprintf('The user was registered less than %d days ago.', self::FIRST_LOGIN_AGE_LIMIT_IN_DAYS),
|
693 |
+
'The user was registered <em>after</em> redirect settings were changed for the first time.',
|
694 |
+
sprintf('The user has not logged in while this plugin and the "%s" module is active.', $this->tabTitle),
|
695 |
+
];
|
696 |
+
|
697 |
+
return '<p>A "first login" redirect happens when a new user logs in for the first time.<p>'
|
698 |
+
. '<p>WordPress does not record logins, so sometimes it\'s not possible to reliably
|
699 |
+
determine if a user has already logged in before or not. To help avoid unnecessary
|
700 |
+
redirects, the plugin will only perform a "first login" redirect when all of the following
|
701 |
+
conditions are met:</p>'
|
702 |
+
. '<ul><li>'
|
703 |
+
. implode("</li>\n<li>", $conditions)
|
704 |
+
. '</li></ul>';
|
705 |
+
}
|
706 |
+
|
707 |
+
private function getEmergencyShutdownHelp() {
|
708 |
+
return '<p>If something goes wrong, you can disable all custom redirects by adding this code to wp-config.php:</p>'
|
709 |
+
. '<p><code>define(\'AME_DISABLE_REDIRECTS\', true);</code></p>'
|
710 |
+
. '<p>Note that this only applies to redirects created using this plugin. It will not prevent other plugins or themes from redirecting users.</p>';
|
711 |
+
}
|
712 |
+
}
|
713 |
+
|
714 |
+
class Redirect {
|
715 |
+
/**
|
716 |
+
* @var string
|
717 |
+
*/
|
718 |
+
private $actorId;
|
719 |
+
/**
|
720 |
+
* @var string
|
721 |
+
*/
|
722 |
+
private $urlTemplate;
|
723 |
+
|
724 |
+
/**
|
725 |
+
* @var boolean
|
726 |
+
*/
|
727 |
+
private $shortcodesEnabled;
|
728 |
+
|
729 |
+
protected function __construct(
|
730 |
+
$actorId,
|
731 |
+
$urlTemplate,
|
732 |
+
$shortcodesEnabled = false
|
733 |
+
) {
|
734 |
+
$this->actorId = $actorId;
|
735 |
+
$this->urlTemplate = $urlTemplate;
|
736 |
+
$this->shortcodesEnabled = $shortcodesEnabled;
|
737 |
+
}
|
738 |
+
|
739 |
+
public static function fromArray(array $properties) {
|
740 |
+
return new static(
|
741 |
+
$properties['actorId'],
|
742 |
+
$properties['urlTemplate'],
|
743 |
+
!empty($properties['shortcodesEnabled'])
|
744 |
+
);
|
745 |
+
}
|
746 |
+
|
747 |
+
/**
|
748 |
+
* @return string
|
749 |
+
*/
|
750 |
+
public function getActorId() {
|
751 |
+
return $this->actorId;
|
752 |
+
}
|
753 |
+
|
754 |
+
/**
|
755 |
+
* @return string
|
756 |
+
*/
|
757 |
+
public function getUrl() {
|
758 |
+
$url = $this->urlTemplate;
|
759 |
+
if ( $this->shortcodesEnabled && function_exists('do_shortcode') ) {
|
760 |
+
$url = do_shortcode($url);
|
761 |
+
}
|
762 |
+
return $url;
|
763 |
+
}
|
764 |
+
}
|
765 |
+
|
766 |
+
class RedirectCollection {
|
767 |
+
/**
|
768 |
+
* @var array<string,array[]>
|
769 |
+
*/
|
770 |
+
protected $rawItems = [];
|
771 |
+
|
772 |
+
public function __construct($rawItems = []) {
|
773 |
+
$this->rawItems = $rawItems;
|
774 |
+
}
|
775 |
+
|
776 |
+
/**
|
777 |
+
* @param string $trigger
|
778 |
+
* @return Redirect[]
|
779 |
+
*/
|
780 |
+
public function filterByTrigger($trigger) {
|
781 |
+
if ( isset($this->rawItems[$trigger]) ) {
|
782 |
+
return array_map([Redirect::class, 'fromArray'], $this->rawItems[$trigger]);
|
783 |
+
} else {
|
784 |
+
return [];
|
785 |
+
}
|
786 |
+
}
|
787 |
+
|
788 |
+
/**
|
789 |
+
* @return array
|
790 |
+
*/
|
791 |
+
public function toDbFormat() {
|
792 |
+
return $this->rawItems;
|
793 |
+
}
|
794 |
+
|
795 |
+
/**
|
796 |
+
* @param array $items
|
797 |
+
* @return static
|
798 |
+
*/
|
799 |
+
public static function fromDbFormat($items) {
|
800 |
+
return new static($items);
|
801 |
+
}
|
802 |
+
|
803 |
+
/**
|
804 |
+
* @return array
|
805 |
+
*/
|
806 |
+
public function flatten() {
|
807 |
+
$results = [];
|
808 |
+
foreach ($this->rawItems as $trigger => $items) {
|
809 |
+
foreach ($items as $properties) {
|
810 |
+
if ( !is_array($properties) ) {
|
811 |
+
continue;
|
812 |
+
}
|
813 |
+
$properties['trigger'] = $trigger;
|
814 |
+
$results[] = $properties;
|
815 |
+
}
|
816 |
+
}
|
817 |
+
return $results;
|
818 |
+
}
|
819 |
+
|
820 |
+
/**
|
821 |
+
* Add a redirect to the collection.
|
822 |
+
*
|
823 |
+
* @param array $redirectProperties
|
824 |
+
*/
|
825 |
+
public function add($redirectProperties) {
|
826 |
+
$trigger = $redirectProperties['trigger'];
|
827 |
+
if ( !isset($this->rawItems[$trigger]) ) {
|
828 |
+
$this->rawItems[$trigger] = [];
|
829 |
+
}
|
830 |
+
$this->rawItems[$trigger][] = $redirectProperties;
|
831 |
+
}
|
832 |
+
}
|
833 |
+
|
834 |
+
abstract class Triggers {
|
835 |
+
const LOGIN = 'login';
|
836 |
+
const LOGOUT = 'logout';
|
837 |
+
const REGISTRATION = 'registration';
|
838 |
+
const FIRST_LOGIN = 'firstLogin';
|
839 |
+
|
840 |
+
public static function getValues() {
|
841 |
+
return [self::LOGIN, self::LOGOUT, self::REGISTRATION, self::FIRST_LOGIN];
|
842 |
+
}
|
843 |
+
}
|
844 |
+
|
845 |
+
/**
|
846 |
+
* Really basic Option type implementation.
|
847 |
+
*
|
848 |
+
* @template T
|
849 |
+
*/
|
850 |
+
abstract class Option {
|
851 |
+
/**
|
852 |
+
* @return T
|
853 |
+
*/
|
854 |
+
abstract public function get();
|
855 |
+
|
856 |
+
/**
|
857 |
+
* @return boolean
|
858 |
+
*/
|
859 |
+
abstract public function isEmpty();
|
860 |
+
|
861 |
+
/**
|
862 |
+
* @return boolean
|
863 |
+
*/
|
864 |
+
abstract public function nonEmpty();
|
865 |
+
|
866 |
+
/**
|
867 |
+
* @return boolean
|
868 |
+
*/
|
869 |
+
public function isDefined() {
|
870 |
+
return $this->nonEmpty();
|
871 |
+
}
|
872 |
+
}
|
873 |
+
|
874 |
+
/**
|
875 |
+
* @template T
|
876 |
+
* @extends Option<T>
|
877 |
+
*/
|
878 |
+
final class Some extends Option {
|
879 |
+
/**
|
880 |
+
* @var T
|
881 |
+
*/
|
882 |
+
private $value;
|
883 |
+
|
884 |
+
/**
|
885 |
+
* Some constructor.
|
886 |
+
*
|
887 |
+
* @param T $value
|
888 |
+
*/
|
889 |
+
public function __construct($value) {
|
890 |
+
$this->value = $value;
|
891 |
+
}
|
892 |
+
|
893 |
+
/**
|
894 |
+
* @return T
|
895 |
+
*/
|
896 |
+
public function get() {
|
897 |
+
return $this->value;
|
898 |
+
}
|
899 |
+
|
900 |
+
public function isEmpty() {
|
901 |
+
return false;
|
902 |
+
}
|
903 |
+
|
904 |
+
public function nonEmpty() {
|
905 |
+
return true;
|
906 |
+
}
|
907 |
+
}
|
908 |
+
|
909 |
+
/**
|
910 |
+
* @template T
|
911 |
+
* @extends Option<T>
|
912 |
+
*/
|
913 |
+
final class None extends Option {
|
914 |
+
private function __construct() {
|
915 |
+
//This constructor only exists to prevent others from creating instances.
|
916 |
+
}
|
917 |
+
|
918 |
+
public function get() {
|
919 |
+
throw new RuntimeException('Option value is not set');
|
920 |
+
}
|
921 |
+
|
922 |
+
public function isEmpty() {
|
923 |
+
return true;
|
924 |
+
}
|
925 |
+
|
926 |
+
public function nonEmpty() {
|
927 |
+
return false;
|
928 |
+
}
|
929 |
+
|
930 |
+
public static function getInstance() {
|
931 |
+
static $instance = null;
|
932 |
+
if ( $instance === null ) {
|
933 |
+
$instance = new self();
|
934 |
+
}
|
935 |
+
return $instance;
|
936 |
+
}
|
937 |
+
}
|
938 |
+
|
939 |
+
class MenuExtractor {
|
940 |
+
private $items = [];
|
941 |
+
|
942 |
+
public function __construct($menuTree) {
|
943 |
+
foreach ($menuTree as $item) {
|
944 |
+
$this->processItem($item);
|
945 |
+
}
|
946 |
+
}
|
947 |
+
|
948 |
+
private function processItem($item, $parentTitle = null) {
|
949 |
+
//Skip separators.
|
950 |
+
if ( !empty($item['separator']) ) {
|
951 |
+
return;
|
952 |
+
}
|
953 |
+
|
954 |
+
$templateId = ameMenuItem::get($item, 'template_id');
|
955 |
+
$url = ameMenuItem::get($item, 'url');
|
956 |
+
|
957 |
+
$rawTitle = ameMenuItem::get($item, 'menu_title', '[Untitled]');
|
958 |
+
$fullTitle = trim(strip_tags(ameMenuItem::remove_update_count($rawTitle)));
|
959 |
+
if ( $parentTitle !== null ) {
|
960 |
+
$fullTitle = $parentTitle . ' → ' . $fullTitle;
|
961 |
+
}
|
962 |
+
|
963 |
+
if ( empty($item['custom']) && ($templateId !== null) && !$this->looksLikeUnusableSlug($url) ) {
|
964 |
+
//Add the admin URL shortcode to the URL if it looks like a relative URL that points
|
965 |
+
//to a dashboard page.
|
966 |
+
if ( $this->looksLikeDashboardUrl($url) ) {
|
967 |
+
$url = '[ame-wp-admin]' . $url;
|
968 |
+
}
|
969 |
+
|
970 |
+
$this->items[] = [
|
971 |
+
'templateId' => $templateId,
|
972 |
+
'title' => $fullTitle,
|
973 |
+
'url' => $url,
|
974 |
+
];
|
975 |
+
}
|
976 |
+
|
977 |
+
if ( !empty($item['items']) ) {
|
978 |
+
foreach ($item['items'] as $submenu) {
|
979 |
+
$this->processItem($submenu, $fullTitle);
|
980 |
+
}
|
981 |
+
}
|
982 |
+
}
|
983 |
+
|
984 |
+
/**
|
985 |
+
* @param string $url
|
986 |
+
* @return boolean
|
987 |
+
*/
|
988 |
+
private function looksLikeDashboardUrl($url) {
|
989 |
+
$scheme = parse_url($url, PHP_URL_SCHEME);
|
990 |
+
if ( !empty($scheme) ) {
|
991 |
+
return false;
|
992 |
+
}
|
993 |
+
|
994 |
+
return preg_match('@^[a-z0-9][a-z0-9_-]{0,30}?\.php@i', $url) === 1;
|
995 |
+
}
|
996 |
+
|
997 |
+
/**
|
998 |
+
* Check if a string looks like a plain admin menu slug and not a usable URL.
|
999 |
+
*
|
1000 |
+
* Sometimes plugins create admin menus that don't have a callback function. A menu like that
|
1001 |
+
* will show up fine, and it can be used as a parent for other menu items. However, the menu
|
1002 |
+
* itself won't have a working URL, so we don't want to offer it as a redirect option.
|
1003 |
+
*
|
1004 |
+
* For example, the top level "WooCommerce" menu doesn't have a valid URL, it just has
|
1005 |
+
* a slug: "woocommerce". You just usually don't notice this because WordPress automatically
|
1006 |
+
* replaces the menu URL with the URL of the first child item.
|
1007 |
+
*
|
1008 |
+
* @param string|mixed $url
|
1009 |
+
* @return bool
|
1010 |
+
*/
|
1011 |
+
private function looksLikeUnusableSlug($url) {
|
1012 |
+
if ( !is_string($url) ) {
|
1013 |
+
return true;
|
1014 |
+
}
|
1015 |
+
|
1016 |
+
//Technically, a menu slug could be anything, so we can't easily determine if a string
|
1017 |
+
//is really a slug or just a weird relative URL. However, it seems safe to assume that
|
1018 |
+
//a "URL" that has no dots (so no file extension or domain name) and no slashes (so no
|
1019 |
+
//protocol or relative directories) is probably unusable.
|
1020 |
+
$suspiciousSegmentLength = strcspn($url, './');
|
1021 |
+
return ($suspiciousSegmentLength === strlen($url));
|
1022 |
+
}
|
1023 |
+
|
1024 |
+
public function getUsableItems() {
|
1025 |
+
return $this->items;
|
1026 |
+
}
|
1027 |
+
}
|
modules/redirector/redirector.scss
ADDED
@@ -0,0 +1,450 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
@import "../../css/boxes";
|
2 |
+
|
3 |
+
$redirectInputSpacing: 10px;
|
4 |
+
|
5 |
+
$itemBackground: #fff;
|
6 |
+
$defaultItemBorderColor: #dcdcde;
|
7 |
+
$itemPadding: 10px;
|
8 |
+
|
9 |
+
$inputHeight: 30px;
|
10 |
+
$inputBorderWidth: 1px;
|
11 |
+
$dropdownTriggerWidth: 30px;
|
12 |
+
|
13 |
+
.ame-rui-redirect-list {
|
14 |
+
display: flex;
|
15 |
+
flex-direction: column;
|
16 |
+
|
17 |
+
padding-top: 1px; //To offset the negative margin on the first item.
|
18 |
+
}
|
19 |
+
|
20 |
+
.ame-rui-redirect {
|
21 |
+
display: flex;
|
22 |
+
flex-direction: row;
|
23 |
+
|
24 |
+
background: $itemBackground;
|
25 |
+
border: 1px solid $defaultItemBorderColor;
|
26 |
+
margin-top: -1px;
|
27 |
+
|
28 |
+
&.ui-sortable-helper {
|
29 |
+
border: 1px solid #8c8f94;
|
30 |
+
box-shadow: 1px 1px 5px 0 rgba(0, 0, 0, 0.30);
|
31 |
+
}
|
32 |
+
}
|
33 |
+
|
34 |
+
.ame-rui-redirect-content {
|
35 |
+
flex-grow: 1;
|
36 |
+
display: flex;
|
37 |
+
flex-direction: row;
|
38 |
+
|
39 |
+
padding: $itemPadding $itemPadding $itemPadding 0;
|
40 |
+
}
|
41 |
+
|
42 |
+
.ame-rui-actor {
|
43 |
+
margin-right: $redirectInputSpacing;
|
44 |
+
|
45 |
+
font-size: 14px;
|
46 |
+
line-height: 27px;
|
47 |
+
}
|
48 |
+
|
49 |
+
.ame-rui-url-template, ame-redirect-url-input {
|
50 |
+
flex-grow: 1;
|
51 |
+
display: block; //Only needed when outside a flex container.
|
52 |
+
|
53 |
+
margin-right: $redirectInputSpacing;
|
54 |
+
position: relative;
|
55 |
+
|
56 |
+
input[type=text] {
|
57 |
+
width: 100%;
|
58 |
+
|
59 |
+
&.ame-rui-has-url-dropdown {
|
60 |
+
padding-right: $dropdownTriggerWidth + 2px;
|
61 |
+
}
|
62 |
+
}
|
63 |
+
|
64 |
+
input[readonly] {
|
65 |
+
background-color: #dbeeff; //Eeeh. Choosing colours is hard.
|
66 |
+
}
|
67 |
+
}
|
68 |
+
|
69 |
+
.ame-rui-url-dropdown-trigger {
|
70 |
+
$triggerHeight: $inputHeight - 2 * $inputBorderWidth;
|
71 |
+
|
72 |
+
display: block;
|
73 |
+
position: absolute;
|
74 |
+
width: $dropdownTriggerWidth;
|
75 |
+
height: $triggerHeight;
|
76 |
+
|
77 |
+
top: $inputBorderWidth;
|
78 |
+
right: $inputBorderWidth;
|
79 |
+
border-top-right-radius: 4px;
|
80 |
+
border-bottom-right-radius: 4px;
|
81 |
+
|
82 |
+
cursor: pointer;
|
83 |
+
text-align: center;
|
84 |
+
|
85 |
+
color: #787c82;
|
86 |
+
|
87 |
+
&:hover {
|
88 |
+
color: #1d2327;
|
89 |
+
}
|
90 |
+
|
91 |
+
.am-rui-trigger-icon {
|
92 |
+
&:before {
|
93 |
+
content: "\f140";
|
94 |
+
font: normal 20px dashicons;
|
95 |
+
line-height: $triggerHeight;
|
96 |
+
vertical-align: middle;
|
97 |
+
}
|
98 |
+
}
|
99 |
+
}
|
100 |
+
|
101 |
+
.ame-rui-shortcodes-enabled {
|
102 |
+
margin: 0 $redirectInputSpacing 0 0;
|
103 |
+
|
104 |
+
display: inline-block;
|
105 |
+
box-sizing: border-box;
|
106 |
+
min-height: $inputHeight;
|
107 |
+
padding: 0 10px;
|
108 |
+
|
109 |
+
$lineHeight: $inputHeight - 2 * $inputBorderWidth;
|
110 |
+
line-height: $lineHeight;
|
111 |
+
|
112 |
+
border: $inputBorderWidth solid #dcdcde;
|
113 |
+
border-radius: 3px;
|
114 |
+
background: #f6f7f7;
|
115 |
+
|
116 |
+
.dashicons {
|
117 |
+
vertical-align: top;
|
118 |
+
line-height: $lineHeight;
|
119 |
+
}
|
120 |
+
|
121 |
+
&:hover, &:active {
|
122 |
+
background: #f0f0f1;
|
123 |
+
border-color: #0a4b78;
|
124 |
+
}
|
125 |
+
}
|
126 |
+
|
127 |
+
.ame-rui-drag-handle {
|
128 |
+
cursor: grab;
|
129 |
+
min-width: 30px;
|
130 |
+
|
131 |
+
display: flex;
|
132 |
+
align-items: center;
|
133 |
+
justify-content: center;
|
134 |
+
|
135 |
+
.ui-sortable-helper > & {
|
136 |
+
cursor: grabbing;
|
137 |
+
}
|
138 |
+
}
|
139 |
+
|
140 |
+
.ame-rui-drag-icon {
|
141 |
+
fill: rgb(30, 30, 30);
|
142 |
+
}
|
143 |
+
|
144 |
+
#ame-rui-menu-items {
|
145 |
+
background-image: none;
|
146 |
+
max-width: 40rem; //WP 5.8 limits the width to 25 rem, but the menu input can be wider than that.
|
147 |
+
padding-right: 8px;
|
148 |
+
|
149 |
+
box-shadow: 3px 3px 4px -2px rgba(0, 0, 0, 0.50);
|
150 |
+
|
151 |
+
&:hover, &:focus {
|
152 |
+
color: unset;
|
153 |
+
}
|
154 |
+
|
155 |
+
option:hover {
|
156 |
+
background-color: #f0f0f5;
|
157 |
+
}
|
158 |
+
}
|
159 |
+
|
160 |
+
.ame-rui-default-redirect-container {
|
161 |
+
max-width: 768px;
|
162 |
+
|
163 |
+
.ame-rui-redirect-content {
|
164 |
+
padding-left: $itemPadding;
|
165 |
+
}
|
166 |
+
}
|
167 |
+
|
168 |
+
.ame-rui-add-actor-dropdown, #ame-rui-user-search-query {
|
169 |
+
min-width: 15em;
|
170 |
+
}
|
171 |
+
|
172 |
+
.ame-rui-found-users {
|
173 |
+
//Add a background color to the selected item. WP 5.8 doesn't have it by default, at least not for all
|
174 |
+
//autocomplete menus. The "ui-state-active" class is added to the wrapper, not to the entire LI, so we
|
175 |
+
//also need to adjust the paddings to make the wrapper expand to fill the item.
|
176 |
+
li.ui-menu-item {
|
177 |
+
padding: 0;
|
178 |
+
}
|
179 |
+
|
180 |
+
.ui-menu-item-wrapper {
|
181 |
+
padding: 4px 10px 6px;
|
182 |
+
}
|
183 |
+
|
184 |
+
.ui-state-active {
|
185 |
+
background-color: #2271b1;
|
186 |
+
color: #fff;
|
187 |
+
}
|
188 |
+
}
|
189 |
+
|
190 |
+
.ame-rui-missing-actor-indicator {
|
191 |
+
vertical-align: middle;
|
192 |
+
font-style: italic;
|
193 |
+
}
|
194 |
+
|
195 |
+
.ame-rui-trigger-selector {
|
196 |
+
display: inline-block;
|
197 |
+
margin-bottom: 0;
|
198 |
+
|
199 |
+
list-style: none;
|
200 |
+
font-size: 14px;
|
201 |
+
|
202 |
+
li {
|
203 |
+
display: inline-block;
|
204 |
+
margin: 0;
|
205 |
+
|
206 |
+
a {
|
207 |
+
text-decoration: none;
|
208 |
+
transition: none;
|
209 |
+
}
|
210 |
+
}
|
211 |
+
}
|
212 |
+
|
213 |
+
.ame-rui-small-tabs {
|
214 |
+
$tabBorderColor: #c3c4c7;
|
215 |
+
$tabBackgroundColor: #e3e3e5;
|
216 |
+
$activeTabBackground: transparent;
|
217 |
+
$contentBackgroundColor: #f0f0f1;
|
218 |
+
$activeTabText: #000;
|
219 |
+
|
220 |
+
display: inline-block;
|
221 |
+
padding-left: 7px;
|
222 |
+
border-bottom: 1px solid $tabBorderColor;
|
223 |
+
|
224 |
+
|
225 |
+
li {
|
226 |
+
display: inline-block;
|
227 |
+
margin: 0;
|
228 |
+
background: $tabBackgroundColor;
|
229 |
+
border: 1px solid $tabBorderColor;
|
230 |
+
border-bottom: none;
|
231 |
+
|
232 |
+
a {
|
233 |
+
display: inline-block;
|
234 |
+
transition: none;
|
235 |
+
min-width: 7em;
|
236 |
+
|
237 |
+
font-size: 14px;
|
238 |
+
padding: 5px 8px 6px 8px;
|
239 |
+
cursor: pointer;
|
240 |
+
text-decoration: none;
|
241 |
+
|
242 |
+
color: #444;
|
243 |
+
|
244 |
+
&:hover {
|
245 |
+
color: #2271b1;
|
246 |
+
}
|
247 |
+
}
|
248 |
+
}
|
249 |
+
|
250 |
+
.ame-rui-active-tab {
|
251 |
+
background: $activeTabBackground;
|
252 |
+
border-bottom: 1px solid $contentBackgroundColor;
|
253 |
+
margin-bottom: -1px;
|
254 |
+
|
255 |
+
a {
|
256 |
+
color: $activeTabText;
|
257 |
+
|
258 |
+
&:hover {
|
259 |
+
color: $activeTabText;
|
260 |
+
}
|
261 |
+
}
|
262 |
+
}
|
263 |
+
}
|
264 |
+
|
265 |
+
.ame-rui-filter-like-tabs {
|
266 |
+
$textColor: #646970;
|
267 |
+
$markerColor: #007cba; //Blue like in Gutenberg.
|
268 |
+
//$markerColor: #646970; //Grey like on the "Plugins -> Add New" page.
|
269 |
+
|
270 |
+
$hoverTextColor: #135e96; //Default for links in WP 5.8.
|
271 |
+
//$hoverMarkerColor: #c1cbd9; //Light grey.
|
272 |
+
$hoverMarkerColor: transparent;
|
273 |
+
|
274 |
+
display: inline-flex;
|
275 |
+
margin: 13px 0 0 0;
|
276 |
+
list-style: none;
|
277 |
+
|
278 |
+
border: 1px solid #c3c4c7;
|
279 |
+
background: #fff;
|
280 |
+
|
281 |
+
li {
|
282 |
+
display: inline-block;
|
283 |
+
margin: 0;
|
284 |
+
padding: 0;
|
285 |
+
border-style: none;
|
286 |
+
|
287 |
+
a {
|
288 |
+
display: inline-block;
|
289 |
+
margin: 0;
|
290 |
+
padding: 10px 14px;
|
291 |
+
min-width: 7em;
|
292 |
+
|
293 |
+
border: none;
|
294 |
+
border-bottom: 4px solid transparent;
|
295 |
+
|
296 |
+
color: $textColor;
|
297 |
+
text-decoration: none;
|
298 |
+
cursor: pointer;
|
299 |
+
|
300 |
+
&:hover {
|
301 |
+
color: $hoverTextColor;
|
302 |
+
border-bottom-color: $hoverMarkerColor;
|
303 |
+
}
|
304 |
+
}
|
305 |
+
}
|
306 |
+
|
307 |
+
.ame-rui-active-tab {
|
308 |
+
a {
|
309 |
+
border-bottom-color: $markerColor;
|
310 |
+
}
|
311 |
+
|
312 |
+
a:hover {
|
313 |
+
color: $textColor;
|
314 |
+
border-bottom-color: $markerColor;
|
315 |
+
}
|
316 |
+
}
|
317 |
+
}
|
318 |
+
|
319 |
+
.ame-rui-sub-tabs {
|
320 |
+
margin-top: 6px;
|
321 |
+
font-size: 13px;
|
322 |
+
|
323 |
+
li {
|
324 |
+
&::after {
|
325 |
+
content: '|';
|
326 |
+
}
|
327 |
+
|
328 |
+
&:last-child::after {
|
329 |
+
content: '';
|
330 |
+
}
|
331 |
+
|
332 |
+
a {
|
333 |
+
display: inline-block;
|
334 |
+
text-align: center;
|
335 |
+
padding: 0.2em;
|
336 |
+
line-height: 2;
|
337 |
+
cursor: pointer;
|
338 |
+
|
339 |
+
&::before {
|
340 |
+
display: block;
|
341 |
+
content: attr(data-text);
|
342 |
+
font-weight: bold;
|
343 |
+
height: 1px;
|
344 |
+
overflow: hidden;
|
345 |
+
visibility: hidden;
|
346 |
+
margin-bottom: -1px;
|
347 |
+
}
|
348 |
+
}
|
349 |
+
|
350 |
+
&:first-child a {
|
351 |
+
padding-left: 0;
|
352 |
+
text-align: left;
|
353 |
+
}
|
354 |
+
}
|
355 |
+
|
356 |
+
.ame-rui-active-tab {
|
357 |
+
a {
|
358 |
+
font-weight: 600;
|
359 |
+
color: #000;
|
360 |
+
}
|
361 |
+
}
|
362 |
+
}
|
363 |
+
|
364 |
+
.ame-rui-actions button {
|
365 |
+
.dashicons {
|
366 |
+
vertical-align: inherit;
|
367 |
+
line-height: 28px;
|
368 |
+
}
|
369 |
+
}
|
370 |
+
|
371 |
+
#ame-rui-column-container {
|
372 |
+
display: flex;
|
373 |
+
flex-direction: row;
|
374 |
+
}
|
375 |
+
|
376 |
+
#ame-rui-main-section {
|
377 |
+
display: block;
|
378 |
+
flex-grow: 1;
|
379 |
+
max-width: 1320px;
|
380 |
+
}
|
381 |
+
|
382 |
+
#ame-rui-sidebar {
|
383 |
+
display: none;
|
384 |
+
width: 300px;
|
385 |
+
flex-shrink: 0;
|
386 |
+
flex-grow: 0;
|
387 |
+
align-self: flex-start;
|
388 |
+
|
389 |
+
margin-left: 20px;
|
390 |
+
}
|
391 |
+
|
392 |
+
#ame-rui-main-actions {
|
393 |
+
@include ame-emulated-postbox();
|
394 |
+
margin-bottom: 0;
|
395 |
+
padding: 10px 8px;
|
396 |
+
border: 1px solid $amePostboxBorderColor;
|
397 |
+
|
398 |
+
input.button {
|
399 |
+
max-width: 150px;
|
400 |
+
width: 100%;
|
401 |
+
}
|
402 |
+
}
|
403 |
+
|
404 |
+
@media only screen and (min-width: 961px) {
|
405 |
+
.ame-rui-actor {
|
406 |
+
min-width: 12em;
|
407 |
+
}
|
408 |
+
|
409 |
+
.ame-rui-actions button .dashicons {
|
410 |
+
display: none;
|
411 |
+
}
|
412 |
+
}
|
413 |
+
|
414 |
+
@media only screen and (max-width: 960px) {
|
415 |
+
.ame-rui-actor {
|
416 |
+
min-width: 10em;
|
417 |
+
}
|
418 |
+
|
419 |
+
.ame-rui-shortcodes-enabled, .ame-rui-actions {
|
420 |
+
.ame-rui-button-label {
|
421 |
+
display: none;
|
422 |
+
}
|
423 |
+
}
|
424 |
+
}
|
425 |
+
|
426 |
+
@media only screen and (max-width: 782px) {
|
427 |
+
$lineHeight: 38px;
|
428 |
+
|
429 |
+
.ame-rui-actor {
|
430 |
+
line-height: $lineHeight;
|
431 |
+
}
|
432 |
+
|
433 |
+
.ame-rui-redirect .ame-rui-actions button {
|
434 |
+
margin-bottom: 0;
|
435 |
+
|
436 |
+
.dashicons {
|
437 |
+
line-height: $lineHeight;
|
438 |
+
vertical-align: top;
|
439 |
+
}
|
440 |
+
}
|
441 |
+
|
442 |
+
.ame-rui-shortcodes-enabled {
|
443 |
+
line-height: $lineHeight;
|
444 |
+
|
445 |
+
.dashicons {
|
446 |
+
vertical-align: top;
|
447 |
+
line-height: $lineHeight;
|
448 |
+
}
|
449 |
+
}
|
450 |
+
}
|
readme.txt
CHANGED
@@ -3,13 +3,13 @@ Contributors: whiteshadow
|
|
3 |
Donate link: https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=A6P9S6CE3SRSW
|
4 |
Tags: admin, dashboard, menu, security, wpmu
|
5 |
Requires at least: 4.1
|
6 |
-
Tested up to: 5.
|
7 |
-
Stable tag: 1.
|
8 |
|
9 |
Lets you edit the WordPress admin menu. You can re-order, hide or rename menus, add custom menus and more.
|
10 |
|
11 |
== Description ==
|
12 |
-
Admin Menu Editor lets you manually edit the Dashboard menu. You can reorder the menus, show/hide specific items, change
|
13 |
|
14 |
**Features**
|
15 |
|
@@ -19,9 +19,21 @@ Admin Menu Editor lets you manually edit the Dashboard menu. You can reorder the
|
|
19 |
* Move a menu item to a different submenu.
|
20 |
* Create custom menus that point to any part of the Dashboard or an external URL.
|
21 |
* Hide/show any menu or menu item. A hidden menu is invisible to all users, including administrators.
|
|
|
22 |
|
23 |
The [Pro version](http://w-shadow.com/AdminMenuEditor/) lets you set per-role menu permissions, hide a menu from everyone except a specific user, export your admin menu, drag items between menu levels, make menus open in a new window and more. [Try online demo](http://amedemo.com/wpdemo/demo.php).
|
24 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
25 |
**Notes**
|
26 |
|
27 |
* If you delete any of the default menus they will reappear after saving. This is by design. To get rid of a menu for good, either hide it or change it's access permissions.
|
@@ -63,6 +75,17 @@ Plugins installed in the `mu-plugins` directory are treated as "always on", so y
|
|
63 |
|
64 |
== Changelog ==
|
65 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
66 |
= 1.9.10 =
|
67 |
* Fixed a bug where the plugin could incorrectly identify a separator as the current menu item.
|
68 |
* Fixed submenu box not expanding to align with the selected parent item.
|
3 |
Donate link: https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=A6P9S6CE3SRSW
|
4 |
Tags: admin, dashboard, menu, security, wpmu
|
5 |
Requires at least: 4.1
|
6 |
+
Tested up to: 5.8.1
|
7 |
+
Stable tag: 1.10
|
8 |
|
9 |
Lets you edit the WordPress admin menu. You can re-order, hide or rename menus, add custom menus and more.
|
10 |
|
11 |
== Description ==
|
12 |
+
Admin Menu Editor lets you manually edit the Dashboard menu. You can reorder the menus, show/hide specific items, change permissions, and more.
|
13 |
|
14 |
**Features**
|
15 |
|
19 |
* Move a menu item to a different submenu.
|
20 |
* Create custom menus that point to any part of the Dashboard or an external URL.
|
21 |
* Hide/show any menu or menu item. A hidden menu is invisible to all users, including administrators.
|
22 |
+
* Create login redirects and logout redirects.
|
23 |
|
24 |
The [Pro version](http://w-shadow.com/AdminMenuEditor/) lets you set per-role menu permissions, hide a menu from everyone except a specific user, export your admin menu, drag items between menu levels, make menus open in a new window and more. [Try online demo](http://amedemo.com/wpdemo/demo.php).
|
25 |
|
26 |
+
**Shortcodes**
|
27 |
+
|
28 |
+
The plugin provides a few utility shortcodes. These are mainly intended to help with creating login/logout redirects, but you can also use them in posts and pages.
|
29 |
+
|
30 |
+
* `[ame-wp-admin]` - URL of the WordPress dashboard (with a trailing slash).
|
31 |
+
* `[ame-home-url]` - Site URL. Usually, this is the same as the URL in the "Site Address" field in *Settings -> General*.
|
32 |
+
* `[ame-user-info field="..."]` - Information about the logged-in user. Parameters:
|
33 |
+
* `field` - The part of user profile to display. Supported fields include: `ID`, `user_login`, `display_name`, `locale`, `user_nicename`, `user_url`, and so on.
|
34 |
+
* `placeholder` - Optional. Text that will be shown if the visitor is not logged in.
|
35 |
+
* `encoding` - Optional. How to encode or escape the output. This is useful if you want to use the shortcode in your own HTML or JS code. Supported values: `auto` (default), `html`, `attr`, `js`, `none`.
|
36 |
+
|
37 |
**Notes**
|
38 |
|
39 |
* If you delete any of the default menus they will reappear after saving. This is by design. To get rid of a menu for good, either hide it or change it's access permissions.
|
75 |
|
76 |
== Changelog ==
|
77 |
|
78 |
+
= 1.10 =
|
79 |
+
* Added a "Redirects" feature. You can create login redirects, logout redirects, and registration redirects. You can configure redirects for specific roles and users. You can also set up a default redirect that will apply to everyone who doesn't have a specific setting. Redirect URLs can contain shortcodes, but not all shortcodes will work in this context.
|
80 |
+
* Added a few utility shortcodes: `[ame-wp-admin]`, `[ame-home-url]`, `[ame-user-info field="..."]`. These are mainly intended to be used to create dynamic redirects, but they will also work in posts and pages.
|
81 |
+
* Slightly improved the appearance of settings page tabs on small screens and in narrow browser windows.
|
82 |
+
* Fixed a minor conflict where several hidden menu items created by "WP Grid Builder" would unexpectedly show up when AME is active.
|
83 |
+
* Fixed a conflict with "LoftLoader Pro", "WS Form", and probably a few other plugins that create new admin menu items that link to the theme customizer. Previously, it was impossible to hide or edit those menu items.
|
84 |
+
* Fixed a few jQuery deprecation warnings.
|
85 |
+
* Fixed an "Undefined array key" warning that could appear if another plugin created a user role that did not have a "capabilities" key.
|
86 |
+
* Fixed a minor BuddyBoss Platform compatibility issue where the menu editor would show a "BuddyBoss -> BuddyBoss" menu item that was not present in the actual admin menu. The item is created by BuddyBoss Platform, but it is apparently intended to be hidden.
|
87 |
+
* Refactored the menu editor and added limited support for editing three level menus. While the free version doesn't have the ability to actually render nested items in the admin menu, it should at least load a menu configuration that includes more than two levels without crashing. This will probably only matter if someone edits the settings in the database or copies a menu configuration from the Pro version.
|
88 |
+
|
89 |
= 1.9.10 =
|
90 |
* Fixed a bug where the plugin could incorrectly identify a separator as the current menu item.
|
91 |
* Fixed submenu box not expanding to align with the selected parent item.
|
uninstall.php
CHANGED
@@ -28,4 +28,14 @@ if( defined( 'ABSPATH') && defined('WP_UNINSTALL_PLUGIN') ) {
|
|
28 |
delete_site_option('ws_ame_hide_pv_notice');
|
29 |
delete_site_option('ws_ame_dashboard_widgets');
|
30 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
31 |
}
|
28 |
delete_site_option('ws_ame_hide_pv_notice');
|
29 |
delete_site_option('ws_ame_dashboard_widgets');
|
30 |
}
|
31 |
+
|
32 |
+
//Remove redirect settings.
|
33 |
+
delete_option('ws_ame_redirects');
|
34 |
+
if ( function_exists('delete_site_option') ) {
|
35 |
+
delete_site_option('ws_ame_redirects');
|
36 |
+
delete_site_option('ws_ame_rui_first_change');
|
37 |
+
}
|
38 |
+
if ( function_exists('delete_metadata') ) {
|
39 |
+
delete_metadata('user', 0, 'ame_rui_first_login_done', '', true);
|
40 |
+
}
|
41 |
}
|