Admin Menu Editor - Version 1.5

Version Description

  • Added "Keep this menu open" checkbox. This setting keeps a top level menu expanded even if it is not the current menu.
  • Added sort buttons to the top level menu toolbar.
  • Added an arrow that points from the current submenu to the currently selected parent menu. This might help new users understand that the left column shows top level menus and the right column shows the corresponding submenu(s).
  • Added a new editor colour scheme that makes the menu editor look more like other WordPress admin pages (e.g. Appearance -> Menus). You can enable it through the plugin settings page.
  • New and unused menu items will now show up in the same relative position as they would be in the default admin menu. Alternatively, they can be displayed at the bottom of the menu. You can configure this in plugin settings.
  • Fixed a rare bug where the menu editor would crash if one of the menu items had a null menu title. Technically, it's not valid to set the title to null, but it turns out that some plugins do that anyway.
  • Top level menus that have an empty title ("", an empty string) are no longer treated as separators.
  • Made all text fields and dropdowns the same height and gave them consistent margins.
  • Fixed a number of layout bugs that could cause field labels to show up in the wrong place or get wrapped/broken in half when another plugin changed the default font or input size.
  • Fixed a minor layout bug that caused the "expand menu properties" arrow to move down slightly when holding down the mouse button.
  • Fixed a minor bug that could cause toolbar buttons to change size or position if another plugin happens to override the default link and image CSS.
  • Added a workaround for plugins that create "Welcome", "What's New" or "Getting Started" menu items and then hide those items in a non-standard way. Now (some of) these items will no longer show up unnecessarily. If you find menus like that which still show up when not needed, please report them.
  • Fixed a few other layout inconsistencies.
  • Improved compatibility with buggy plugins that unintentionally corrupt the list of users' roles by misusing array_shift.
  • Fixed a URL parsing bug that caused AME to mix up the "Customize", "Header" and "Background" menu items in some configurations.
  • Fixed a layout issue where starting to drag one menu item would cause some other items to move around or change size very slightly.
  • Fixed JavaScript error "_.empty is not a function".
  • Increased minimum required WordPress version to 4.1.
  • Renamed the "Show/Hide" button to "Hide without preventing access". Changed the icon from a grey puzzle piece to a rectangle with a dashed border.
  • Made the plugin more resilient to JavaScript crashes caused by other plugins.
  • Use <h1> headings for admin pages in WordPress 4.2 and above.
  • Made the "delete" button appear disabled when the selected menu item can't be deleted.
  • Moved the "new separator" button so that it's next to the "new menu" button.
  • Changed the close icon of plugin dialogs to a plain white "X".
  • Increased tooltip text size.
  • Improved compatibility with IP Geo Block.
Download this release

Release Info

Developer whiteshadow
Plugin Icon 128x128 Admin Menu Editor
Version 1.5
Comparing to
See all releases

Code changes from version 1.4.5 to 1.5

css/admin.css CHANGED
@@ -100,4 +100,26 @@ hr.ws-submenu-separator {
100
  #adminmenu .wp-submenu li.current .ame-submenu-icon img {
101
  opacity: 1;
102
  filter: alpha(opacity=100);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
103
  }
100
  #adminmenu .wp-submenu li.current .ame-submenu-icon img {
101
  opacity: 1;
102
  filter: alpha(opacity=100);
103
+ }
104
+
105
+ /*
106
+ * Third level menus.
107
+ */
108
+ #adminmenu .ame-deep-submenu {
109
+
110
+ }
111
+
112
+ #adminmenu li.menu-top.opensub .ame-deep-submenu {
113
+ top: -1000em;
114
+ }
115
+
116
+ #adminmenu .wp-submenu li.opensub > ul.ame-deep-submenu {
117
+ top: -7px;
118
+ }
119
+
120
+ .folded #adminmenu li.opensub > ul.ame-deep-submenu,
121
+ .folded #adminmenu .wp-has-current-submenu.opensub > ul.ame-deep-submenu,
122
+ .no-js.folded #adminmenu .ame-has-deep-submenu:hover > ul.ame-deep-submenu {
123
+ top: 0;
124
+ left: 160px;
125
  }
css/menu-editor.css CHANGED
@@ -1,185 +1,173 @@
1
  /* Admin Menu Editor CSS file */
2
-
3
  #ws_menu_editor {
4
- min-width: 780px;
5
- }
6
 
7
  .ws_main_container {
8
- margin: 2px;
9
- width: 310px;
10
- float: left;
11
- display:block;
12
-
13
- border: 1px solid #cdd5d5;
14
- background-color: #FFFFFF;
15
-
16
- border-radius: 3px;
17
- -moz-border-radius: 3px;
18
- -webkit-border-radius: 3px;
19
- }
20
 
21
  .ws_box {
22
- min-height: 30px;
23
- width: 100%;
24
- margin: 0;
25
- padding-top: 2px;
26
- }
27
 
28
  .ws_basic_container {
29
- float: left;
30
- display:block;
31
- }
32
-
33
- #ws_menu_box {
34
- }
35
-
36
- #ws_submenu_box {
37
- }
38
 
39
  .ws_dropzone {
40
- display: block;
41
- box-sizing: border-box;
42
-
43
- margin: 2px 6px;
44
- border: 3px none #b4b9be;
45
-
46
- height: 31px;
47
- }
48
 
49
  .ws_dropzone_active,
50
  .ws_dropzone_hover,
51
  .ws_top_to_submenu_drop_hover .ws_dropzone {
52
- border-style: dashed;
53
- }
54
 
55
  .ws_dropzone_hover,
56
  .ws_top_to_submenu_drop_hover .ws_dropzone {
57
- border-width: 1px;
58
- }
59
 
60
  /*************************************************
61
  Actor UI
62
  *************************************************/
63
  #ws_actor_selector li:after {
64
- content: ' | ';
65
- }
66
 
67
  #ws_actor_selector li:last-child:after {
68
- content: '';
69
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
70
 
71
  /**
72
  * The checkbox that lets the user show/hide a menu for the currently selected actor.
73
  */
74
  #ws_menu_editor .ws_actor_access_checkbox,
75
- #ws_menu_editor input[type="checkbox"].ws_actor_access_checkbox /* Ensure we override WP defaults. */
76
- {
77
- margin-right: 2px;
78
- margin-left: 2px;
79
- margin-top: 1px;
80
- vertical-align: text-top;
81
- }
82
-
 
 
 
 
 
 
 
 
 
 
 
 
 
83
  /* The checkbox is only visible when viewing the menu configuration for a specific actor. */
84
  #ws_menu_editor .ws_actor_access_checkbox {
85
- display: none;
86
- }
87
 
88
  #ws_menu_editor.ws_is_actor_view .ws_actor_access_checkbox {
89
- display: inline-block;
90
- }
91
 
92
  /* Gray-out items inaccessible to the currently selected actor */
93
-
94
  .ws_is_actor_view .ws_container.ws_is_hidden_for_actor {
95
- background-color: #F9F9F9;
96
- }
97
 
98
  .ws_is_actor_view .ws_is_hidden_for_actor .ws_item_title {
99
- color: #777;
100
- }
101
 
102
  /*
103
  * The sidebar
104
  */
105
-
106
  #ws_editor_sidebar {
107
- width: auto;
108
- padding: 2px;
109
- }
110
 
111
  #ws_menu_editor .ws_main_button {
112
- clear: both;
113
- display: block;
114
- margin: 4px;
115
- width: 130px;
116
- }
117
 
118
  #ws_menu_editor #ws_save_menu {
119
- margin-bottom: 20px;
120
- }
121
 
122
  #ws_menu_editor #ws_export_menu {
123
- margin-top: 12px;
124
- }
 
 
125
 
126
  /*
127
  * Menu components and widgets
128
  */
129
-
130
  .ws_container {
131
- display: block;
132
- width: 290px;
133
-
134
- padding : 3px;
135
- margin: 2px auto;
136
- }
137
-
138
- .ws_active { }
139
-
140
- .ws_menu { }
141
- .ws_item { }
142
-
143
- .ws_menu_separator { }
144
 
145
  .ws_submenu {
146
- min-height: 2em;
147
- }
148
-
149
 
150
  .ws_item_head {
151
- padding: 0;
152
- }
153
 
154
  .ws_item_title {
155
- display: inline-block;
156
- padding: 2px;
157
- cursor: default;
158
- }
 
159
 
160
  .ws_edit_link {
161
- float: right;
162
- margin-right: 0;
163
- cursor: pointer;
164
- display:block;
165
- width: 40px;
166
- height: 22px;
167
-
168
- border-radius: 3px;
169
- -moz-border-radius: 3px;
170
- -webkit-border-radius: 3px;
171
- }
172
-
173
- .ws_edit_link_expanded { }
174
-
175
 
176
  .ws_menu_drop_hover {
177
- background-color: #43b529 !important;
178
- }
179
 
180
  .ws_container.ui-sortable-helper * {
181
- cursor: move !important;
182
- }
 
 
 
 
 
183
 
184
  /*
185
  If you ever want to apply a right-arrow style to the currently selected menu item,
@@ -197,9 +185,9 @@
197
  z-index: 1002;
198
 
199
  border-left: 14px solid #8EB0F1;
200
- border-top: 14px solid transparent;
201
- border-bottom: 14px solid transparent;
202
- background: #8EB0F1;
203
 
204
  position: absolute;
205
  right: -14px;
@@ -209,910 +197,1096 @@
209
  height: 0;
210
  }
211
  */
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
212
 
213
  /****************************************
214
  Per-menu settings fields & panels
215
  *****************************************/
216
-
217
  .ws_editbox {
218
- display: block;
219
- padding: 4px;
220
-
221
- border-radius: 2px;
222
- border-top-right-radius: 0;
223
-
224
- -moz-border-radius: 2px;
225
- -moz-border-radius-topright: 0;
226
-
227
- -webkit-border-radius: 2px;
228
- -webkit-border-top-right-radius: 0;
229
- }
230
 
231
  .ws_edit_panel {
232
- margin: 0;
233
- padding: 0;
234
- border: none;
235
- }
236
 
237
  .ws_edit_field {
238
- margin-bottom: 6px;
239
- height: 45px;
240
- }
 
 
 
 
 
 
241
 
242
  .ws_edit_field-custom {
243
- margin-top: 10px;
244
- }
 
 
 
 
 
245
 
246
  /* The reset-to-default button */
247
  .ws_reset_button {
248
- display: block;
249
- float: right;
250
-
251
- margin-left: 4px;
252
- margin-top: 2px;
253
- margin-right: 6px;
254
- cursor: pointer;
255
-
256
- width: 16px;
257
- height: 16px;
258
- vertical-align: top;
259
-
260
- background: url("../images/pencil_delete_gray.png") no-repeat center;
261
- }
262
 
263
  .ws_reset_button:hover {
264
- background-image: url("../images/pencil_delete.png");
265
- }
266
 
267
  .ws_input_default input,
268
  .ws_input_default select,
269
  .ws_input_default .ws_color_scheme_display {
270
- color: gray;
271
- }
272
 
273
  /* No reset button for fields set to the default value and fields without a default value */
274
- .ws_input_default .ws_reset_button,
275
  .ws_has_no_default .ws_reset_button {
276
- visibility: hidden;
277
- }
278
 
279
  /* The input box in each field editor */
280
- #ws_menu_editor .ws_editbox input[type="text"],
281
  #ws_menu_editor .ws_editbox select {
282
- display: block;
283
- float: left;
284
- width: 254px;
285
-
286
- font-size: 12px;
287
- padding: 3px;
288
- }
 
289
 
290
  #ws_menu_editor .ws_edit_field label {
291
- display: block;
292
- float: left;
293
- }
294
 
295
  #ws_menu_editor .ws_edit_field-custom input[type="checkbox"] {
296
- margin-top: 0;
297
- }
298
 
299
  #ws_menu_editor input[type="text"].ws_field_value {
300
- min-height: 25px;
301
- }
302
 
303
  /* Dropdown button for combo-box fields */
304
  #ws_menu_editor .ws_dropdown_button,
305
- #ws_menu_access_editor .ws_dropdown_button
306
- {
307
- box-sizing: border-box;
308
- width: 20px;
309
- height: 25px;
310
-
311
- margin: 1px 1px 1px 0;
312
- padding: 0;
313
-
314
- text-align: center;
315
- font-size: 9px !important;
316
-
317
- border-color: #dfdfdf;
318
-
319
- border-top-right-radius: 3px;
320
- border-bottom-right-radius: 3px;
321
- border-top-left-radius: 0;
322
- border-bottom-left-radius: 0;
323
-
324
- -moz-border-radius-topright: 3px;
325
- -moz-border-radius-bottomright: 3px;
326
- -moz-border-radius-topleft: 0;
327
- -moz-border-radius-bottomleft: 0;
328
-
329
- -webkit-border-top-right-radius: 3px;
330
- -webkit-border-bottom-right-radius: 3px;
331
- -webkit-border-top-left-radius: 0;
332
- -webkit-border-bottom-left-radius: 0;
333
- }
334
 
335
  #ws_menu_access_editor .ws_dropdown_button {
336
- display: inline-block;
337
- height: 27px;
338
- }
339
 
340
  #ws_menu_editor .ws_dropdown_button {
341
- display: block;
342
- float: left;
343
- }
344
 
345
  /*
346
  The appearance and size of combo-box fields need to be changed
347
  to accommodate the drop-down button.
348
  */
349
  #ws_menu_editor .ws_has_dropdown input.ws_field_value,
350
- #ws_menu_access_editor input.ws_has_dropdown
351
- {
352
- margin-right: 0;
353
- border-right: 0;
354
-
355
- border-top-right-radius: 0;
356
- border-bottom-right-radius: 0;
357
-
358
- -moz-border-radius-topright: 0;
359
- -moz-border-radius-bottomright: 0;
360
-
361
- -webkit-border-top-right-radius: 0;
362
- -webkit-border-bottom-right-radius: 0;
363
- }
364
 
365
  #ws_menu_access_editor input.ws_has_dropdown {
366
- width: 90%;
367
- box-sizing: border-box;
368
- height: 27px;
369
- }
370
 
371
  #ws_menu_editor .ws_has_dropdown input.ws_field_value {
372
- width: 230px;
373
- }
374
 
375
  /* Unlike others, this field is just a single checkbox, so it has a smaller height */
376
  #ws_menu_editor .ws_edit_field-custom {
377
- height: 16px;
378
- }
379
 
380
  /*
381
  * "Show/hide advanced fields"
382
  */
383
  .ws_toggle_container {
384
- text-align: right;
385
- margin-right: 27px;
386
- }
387
 
388
  .ws_toggle_advanced_fields {
389
- color: #6087CB;
390
- text-decoration: none;
391
- font-size: 0.85em;
392
- }
393
 
394
  .ws_toggle_advanced_fields:visited, .ws_toggle_advanced_fields:active {
395
- color: #6087CB;
396
- }
397
 
398
  .ws_toggle_advanced_fields:hover {
399
- color: #d54e21;
400
- text-decoration: underline;
401
- }
402
 
403
  /************************************
404
  Menu flags
405
  *************************************/
406
-
407
  .ws_flag_container {
408
- float: right;
409
- margin-right: 4px;
410
- padding-top: 2px;
411
- }
412
 
413
  .ws_flag {
414
- display: block;
415
- float: right;
416
- width: 16px;
417
- height: 16px;
418
- margin-left: 4px;
419
- background-repeat: no-repeat;
420
- }
421
 
422
  /* user-created items */
423
  .ws_custom_flag {
424
- background-image: url('../images/page-add.png');
425
- }
426
 
427
  /* unused items - those that are in the default menu but not in the custom one */
428
  .ws_unused_flag {
429
- background-image: url('../images/plugin_add.png');
430
- }
431
 
432
  /* hidden items */
433
  .ws_hidden_flag {
434
- background-image: url('../images/icon-extension-grey.png');
435
- }
436
 
437
  /* items with custom permissions for the selected actor */
438
  .ws_custom_actor_permissions_flag {
439
- font: 16px/1 'dashicons';
440
- }
441
- .ws_custom_actor_permissions_flag::before {
442
- /*content: "\f160";*/ /* padlock */
443
- content: "\f110"; /* human silhouette */
444
- color: black;
445
 
446
- filter: alpha(opacity=25); /*IE 5-7*/
447
- opacity: 0.25;
448
- }
 
 
 
 
 
 
 
 
 
 
449
 
450
  /* These classes could be used to apply different styles to items depending on their flags */
451
- .ws_custom { }
452
- .ws_hidden { }
453
- .ws_unused { }
454
-
455
-
456
  /************************************
457
  Toolbars
458
  *************************************/
459
-
460
  .ws_toolbar {
461
- display: block;
462
- width: 100%;
463
- height: 34px;
464
- }
465
-
466
- .ws_button_container {
467
- padding-left: 6px;
468
- padding-top: 6px;
469
- }
470
 
471
  .ws_button {
472
- display: block;
473
- margin-right: 3px;
474
- padding: 4px;
475
- float: left;
476
-
477
- width: 16px;
478
- height: 16px;
479
-
480
- border-radius: 3px;
481
- -moz-border-radius: 3px;
482
- -webkit-border-radius: 3px;
483
- }
 
 
 
484
 
485
  a.ws_button:hover {
486
- background-color: #d0e0ff;
487
- border-color: #9090c0;
488
- }
 
 
 
 
 
 
 
 
 
 
 
489
 
490
  .ws_separator {
491
- float: left;
492
- width: 5px;
493
- }
494
 
495
  /************************************
496
  Capability selector
497
  *************************************/
498
-
499
  select.ws_dropdown {
500
- width: 252px;
501
- height: 20em;
502
-
503
- z-index: 1002;
504
- position: absolute;
505
- display: none;
506
-
507
- font-family : "Lucida Grande",Verdana,Arial,"Bitstream Vera Sans",sans-serif;
508
- font-size: 12px;
509
- }
510
 
511
  select.ws_dropdown option {
512
- font-family : "Lucida Grande",Verdana,Arial,"Bitstream Vera Sans",sans-serif;
513
- font-size: 12px;
514
- padding: 3px;
515
- }
516
 
517
  select.ws_dropdown optgroup option {
518
- padding-left: 10px;
519
- }
520
 
521
  /************************************
522
  Icon selector
523
  *************************************/
524
-
525
  #ws_icon_selector {
526
- border: 1px solid silver;
527
- border-radius: 3px;
528
- background-color: white;
529
- width: 216px;
530
- padding: 2px;
531
- position: absolute;
532
- }
533
 
534
  #ws_icon_selector.ws_with_more_icons {
535
- width: 504px;
536
- }
537
 
538
  #ws_icon_selector .ws_icon_extra {
539
- display: none;
540
- }
541
 
542
  #ws_icon_selector.ws_with_more_icons .ws_icon_extra {
543
- display: inline-block;
544
- }
545
-
546
 
547
  #ws_icon_selector .ws_icon_option {
548
- float: left;
549
- height: 30px;
550
-
551
- margin: 2px;
552
- cursor: pointer;
553
- border: 1px solid #bbb;
554
- border-radius: 3px;
555
-
556
- /* Gradients and colours cribbed from WP 3.5.1 button styles */
557
- background: #f3f3f3;
558
- background-image: -webkit-gradient(linear, left top, left bottom, from(#fefefe), to(#f4f4f4));
559
- background-image: -webkit-linear-gradient(top, #fefefe, #f4f4f4);
560
- background-image: -moz-linear-gradient(top, #fefefe, #f4f4f4);
561
- background-image: -o-linear-gradient(top, #fefefe, #f4f4f4);
562
- background-image: linear-gradient(to bottom, #fefefe, #f4f4f4);
563
- }
564
 
565
  #ws_icon_selector .ws_icon_option:hover {
566
- /* Gradients and colours cribbed from WP 3.5.1 button styles */
567
- border-color: #999;
568
- background: #f3f3f3;
569
- background-image: -webkit-gradient(linear, left top, left bottom, from(#fff), to(#f3f3f3));
570
- background-image: -webkit-linear-gradient(top, #fff, #f3f3f3);
571
- background-image: -moz-linear-gradient(top, #fff, #f3f3f3);
572
- background-image: -ms-linear-gradient(top, #fff, #f3f3f3);
573
- background-image: -o-linear-gradient(top, #fff, #f3f3f3);
574
- background-image: linear-gradient(to bottom, #fff, #f3f3f3);
575
- }
576
 
577
  #ws_icon_selector .ws_icon_option.ws_selected_icon {
578
- border-color: green;
579
- background-color: #deffca;
580
- background-image: none;
581
- }
582
 
583
  #ws_icon_selector .icon16 {
584
- float: none;
585
- margin: 0;
586
- }
587
 
588
  #ws_icon_selector .ws_icon_option .ws_icon_image.dashicons {
589
- width: 20px;
590
- height: 20px;
591
- padding: 5px;
592
- }
593
 
594
  #ws_icon_selector .ws_icon_option img {
595
- display: inline-block;
596
- margin: 0;
597
- padding: 7px;
598
-
599
- width: 16px;
600
- height: 16px;
601
- }
602
 
603
  #ws_menu_editor .ws_edit_field-icon_url input.ws_field_value {
604
- width: 220px;
605
- margin-right: 5px;
606
- }
607
 
608
  /* The icon button that displays the pop-up icon selector. */
609
  #ws_menu_editor .ws_select_icon {
610
- margin: 0;
611
- padding: 0;
612
- position: relative;
613
-
614
- box-sizing: border-box;
615
- height: 25px;
616
- }
617
 
618
  /* Current icon node (CSS class version, for the built-in WP icon sprites) */
619
  .ws_select_icon .icon16 {
620
- margin: 0;
621
- float: none;
622
- padding: 3px;
623
-
624
- /*
625
- The default .icon16 style has a 6px padding which would normally make it too large
626
- to fit in the button. We can't change the padding without making the background-position
627
- look wrong, so lets offset the icon so that it fits.
628
- */
629
- position: relative;
630
- top: -3px;
631
- left: -3px;
632
- }
633
 
634
  /* Current icon node (image version) */
635
  .ws_select_icon img {
636
- margin: 0;
637
- padding: 4px;
638
- width: 16px;
639
- height: 16px;
640
- }
641
 
642
  /* MP6 admin style compatibility */
643
  #ws_icon_selector .ws_icon_option .icon16::before {
644
- margin: 0;
645
- padding: 0;
646
- }
647
  .ws_select_icon .icon16::before {
648
- padding: 0;
649
- margin: 1px 0 0 2px;
650
- }
651
 
652
  #ws_choose_icon_from_media {
653
- margin: 2px;
654
- }
655
 
656
  #ws_show_more_icons {
657
- margin: 2px;
658
- height: 30px;
659
- width: 68px;
660
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
661
 
 
 
 
 
 
 
662
 
663
  /************************************
664
  Menu color picker
665
  *************************************/
666
-
667
  #ws-ame-menu-color-settings {
668
- background: white;
669
- display: none;
670
- }
671
 
672
  #ame-menu-color-list {
673
- height: 500px;
674
- overflow-y: auto;
675
- }
676
 
677
  .ame-menu-color-column {
678
- min-width: 460px;
679
- }
680
 
681
  .ame-menu-color-name {
682
- display: inline-block;
683
- vertical-align: top;
684
- padding-top: 2px;
685
-
686
- line-height: 1.3;
687
- font-size: 14px;
688
- font-weight: 600;
689
-
690
- min-width: 180px;
691
- }
692
 
693
  .ame-color-option {
694
- padding: 10px 0;
695
- }
696
 
697
  .ame-advanced-menu-color {
698
- display: none;
699
- }
700
 
701
- /* Color scheme display in the editor widget. */
 
 
 
702
 
703
- .ws_color_scheme_display {
704
- display: inline-block;
705
- height: 20px;
706
- width: 186px;
 
 
 
707
 
708
- margin-right: 5px;
709
- padding: 2px 3px;
 
710
 
711
- border: 1px solid #ddd;
712
- background: white;
713
- cursor: pointer;
714
- }
715
 
716
- .ws_color_display_item {
717
- display: inline-block;
718
- width: 18px;
719
- height: 18px;
720
 
721
- margin-right: 4px;
722
- border: 1px solid #ccc;
723
- border-radius: 3px;
724
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
725
 
726
  .ws_color_display_item:last-child {
727
- margin-right: 0;
728
- }
729
 
730
  /************************************
731
  Export and import
732
  *************************************/
733
-
734
  #export_dialog, #import_dialog {
735
- display: none;
736
- }
737
 
738
  .ui-widget-overlay {
739
- background-color: black;
740
- position: fixed;
741
- left: 0;
742
- top: 0;
743
- opacity: 0.70;
744
- -moz-opacity: 0.70;
745
- filter: alpha(opacity=70);
746
-
747
- width: 100%;
748
- height: 100%;
749
- }
750
 
751
  .ui-front {
752
- z-index: 10000;
753
- }
754
 
755
  .ui-dialog {
756
- background: white;
757
- border: 1px solid #c0c0c0;
758
-
759
- padding: 0;
760
-
761
- -moz-border-radius: 5px;
762
- -webkit-border-radius: 5px;
763
- border-radius: 5px;
764
- }
765
 
766
  .ui-dialog-titlebar {
767
- display: block;
768
- height: 22px;
769
- margin: 0;
770
- padding: 4px 4px 4px 8px;
771
-
772
- background-color: #86A7E3;
773
- font-size: 1.0em;
774
- line-height: 22px;
775
-
776
- -webkit-border-top-left-radius: 4px;
777
- -webkit-border-top-right-radius: 4px;
778
-
779
- -moz-border-radius-topleft: 4px;
780
- -moz-border-radius-topright: 4px;
781
-
782
- border-top-left-radius: 4px;
783
- border-top-right-radius: 4px;
784
-
785
- border-bottom: 1px solid #809fd9;
786
- }
787
 
788
  .ui-dialog-title {
789
- color: white;
790
- font-weight: bold;
791
- }
792
 
793
  .ui-dialog-titlebar-close {
794
- background: #86A7E3 url(../images/x.png) no-repeat center;
795
- width: 22px;
796
- height: 22px;
797
- display: block;
798
- float: right;
799
- color: white;
800
-
801
- border-radius: 3px;
802
- -moz-border-radius: 3px;
803
- -webkit-border-radius: 3px;
804
- }
805
 
806
  .ui-dialog-titlebar-close:hover {
807
- /*background-image: url(../images/x-light.png);*/
808
- background-color: #a6c2f5;
809
- }
810
-
811
- .ui-icon-closethick {
812
-
813
- }
814
 
815
  .ui-dialog-content {
816
- padding: 8px 8px 8px 8px;
817
- font-size: 1.1em;
818
- }
819
 
820
  #export_dialog .ws_dialog_panel {
821
- height: 50px;
822
- }
823
 
824
  #import_dialog .ws_dialog_panel {
825
- height: 64px;
826
- }
827
 
828
  .ws_dialog_buttons {
829
- /*height: 30px;*/
830
- text-align: right;
831
- margin-top: 20px;
832
- margin-bottom: 1px;
833
- clear: both;
834
- }
835
 
836
  .ws_dialog_buttons .button-primary {
837
- display: block;
838
- float: left;
839
- margin-top: 0;
840
- }
841
 
842
  .ws_dialog_buttons .button {
843
- margin-top: 0;
844
- }
845
 
846
  .ws_dialog_buttons.ame-vertical-button-list {
847
- text-align: left;
848
- }
849
 
850
  .ws_dialog_buttons.ame-vertical-button-list .button-primary {
851
- float: none;
852
- }
853
 
854
  .ws_dialog_buttons.ame-vertical-button-list .button {
855
- width: 100%;
856
- text-align: left;
857
- margin-bottom: 10px;
858
- }
859
 
860
  .ws_dialog_buttons.ame-vertical-button-list .button:last-child {
861
- margin-bottom: 0;
862
- }
863
 
864
  #import_file_selector {
865
- display: block;
866
- width: 286px;
867
-
868
- margin: 6px auto 12px;
869
- }
870
 
871
  #ws_start_import {
872
- min-width: 100px;
873
- }
874
 
875
  #import_complete_notice {
876
- text-align: center;
877
- font-size: large;
878
- padding-top: 25px;
879
- }
880
 
881
  #ws_import_error_response {
882
- width: 100%;
883
- }
884
 
885
  .ws_dont_show_again {
886
- display: inline-block;
887
- margin-top: 1em;
888
- }
889
 
890
  /************************************
891
  Menu access editor
892
  *************************************/
893
-
894
  /* The launch button */
895
- #ws_menu_editor .ws_edit_field-access_level input.ws_field_value
896
- {
897
- width: 190px;
898
- margin-right: 5px;
899
- }
900
 
901
  .ws_launch_access_editor {
902
- min-width: 40px;
903
- }
904
 
905
  #ws_menu_access_editor {
906
- width: 400px;
907
- display: none;
908
- }
909
 
910
  .ws_dialog_subpanel {
911
- margin-bottom: 1em;
912
- }
913
 
914
- #ws_menu_access_editor .ws_column_access {
915
- text-align: center;
916
- width: 5em;
917
- }
 
 
 
 
 
 
 
 
918
 
919
  #ws_role_table_body_container {
920
- max-height: 400px;
921
- overflow: auto;
922
- }
 
923
 
924
  .ws_role_table_body {
925
- margin-top: 2px;
926
- }
927
 
928
  .ws_has_separate_header .ws_role_table_header {
929
- border-bottom: none;
930
-
931
- -moz-border-radius-bottomleft: 0;
932
- -moz-border-radius-bottomright: 0;
933
- -webkit-border-bottom-left-radius: 0;
934
- -webkit-border-bottom-right-radius: 0;
935
- border-bottom-left-radius: 0;
936
- border-bottom-right-radius: 0;
937
- }
938
 
939
  .ws_has_separate_header .ws_role_table_body {
940
- border-top: none;
941
- margin-top: 0;
942
-
943
- -moz-border-radius-topleft: 0;
944
- -moz-border-radius-topright: 0;
945
- -webkit-border-top-left-radius: 0;
946
- -webkit-border-top-right-radius: 0;
947
- border-top-left-radius: 0;
948
- border-top-right-radius: 0;
949
- }
950
 
951
  .ws_role_id {
952
- display: none;
953
- }
954
 
955
  #ws_extra_capability {
956
- width: 100%;
957
- }
958
 
959
  #ws_role_access_container {
960
- position: relative;
961
- }
 
962
 
963
  #ws_role_access_overlay {
964
- width: 100%;
965
- height: 100%;
966
- position: absolute;
967
-
968
- line-height: 100%;
969
-
970
- background: white;
971
- filter: alpha(opacity=60);
972
- opacity: 0.6;
973
- -moz-opacity:0.6;
974
- -khtml-opacity: 0.6;
975
- }
976
 
977
  #ws_role_access_overlay_content {
978
- position: absolute;
979
- width: 50%;
980
- left: 22%;
981
- top: 30%;
982
-
983
- background: white;
984
- padding: 8px;
985
-
986
- border: 2px solid silver;
987
- border-radius: 5px;
988
- color: #555;
989
- }
990
 
991
  #ws_menu_access_editor div.error {
992
- margin-left: 0;
993
- margin-right: 0;
994
- margin-bottom: 5px;
995
- }
996
 
997
  #ws_hardcoded_role_error {
998
- display: none;
999
- }
1000
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1001
 
1002
  /************************************
1003
- Menu deletion error
1004
  *************************************/
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1005
 
 
 
 
1006
  #ws-ame-menu-deletion-error {
1007
- max-width: 400px;
1008
- }
1009
-
1010
-
1011
-
1012
 
1013
  /************************************
1014
  Tooltips and hints
1015
  *************************************/
1016
-
1017
  .ws_tooltip_trigger {
1018
- cursor: pointer;
1019
- }
1020
 
1021
  .ws_tooltip_content_list {
1022
- list-style: disc;
1023
- margin-left: 1em;
1024
- }
1025
 
1026
- .ws_hint {
1027
- background: #FFFFE0;
1028
- border: 1px solid #E6DB55;
 
 
1029
 
1030
- margin-bottom: 0.5em;
1031
- border-radius: 3px;
1032
- position: relative;
1033
- padding-right: 20px;
1034
- }
1035
 
1036
- .ws_hint_close {
1037
- border: 1px solid #E6DB55;
1038
- border-right: none;
1039
- border-top: none;
1040
- color: #dcc500;
1041
- font-weight: bold;
1042
- cursor: pointer;
1043
 
1044
- width: 18px;
1045
- text-align: center;
1046
- border-radius: 3px;
 
 
 
 
1047
 
1048
- position: absolute;
1049
- right: 0;
1050
- top: 0;
1051
- }
 
 
 
 
 
 
 
 
 
1052
 
1053
  .ws_hint_close:hover {
1054
- background-color: #ffef4c;
1055
- border-color: #e0b900;
1056
- color: black;
1057
- }
1058
 
1059
  .ws_hint_content {
1060
- padding: 0.4em 0 0.4em 0.4em;
1061
- }
1062
 
1063
  .ws_hint_content ul {
1064
- list-style: disc;
1065
- list-style-position: inside;
1066
- margin-left: 0.5em;
1067
- }
1068
-
1069
 
1070
  /************************************
1071
  Copy Permissions dialog
1072
  *************************************/
1073
  #ws-ame-copy-permissions-dialog select {
1074
- min-width: 280px;
1075
- }
1076
-
1077
-
1078
 
1079
  #ws_sidebar_pro_ad {
1080
- min-width: 225px;
1081
-
1082
- margin-top: 5px;
1083
- margin-left: 3px;
1084
-
1085
- position: fixed;
1086
- right: 20px;
1087
- bottom: 40px;
1088
- z-index: 100;
1089
- }
1090
-
1091
 
1092
  .test-wrap {
1093
- background-color: #444444;
1094
- padding: 30px;
1095
- }
1096
 
1097
  .test-container {
1098
- width: 400px;
1099
- height: 200px;
1100
- background-color: white;
1101
-
1102
- border: 1px solid black;
1103
- border-radius: 10px;
1104
-
1105
- overflow: hidden;
1106
- }
1107
 
1108
  .test-header {
1109
- background-color: #67d6ff;
1110
- padding: 6px;
1111
-
1112
- border-top-left-radius: 8px;
1113
- border-top-right-radius: 8px;
1114
- }
1115
 
1116
  .test-content {
1117
- padding: 8px;
1118
- }
 
1
  /* Admin Menu Editor CSS file */
 
2
  #ws_menu_editor {
3
+ min-width: 780px; }
 
4
 
5
  .ws_main_container {
6
+ margin: 2px;
7
+ width: 310px;
8
+ float: left;
9
+ display: block;
10
+ border: 1px solid #cdd5d5;
11
+ background-color: #FFFFFF;
12
+ border-radius: 3px;
13
+ -moz-border-radius: 3px;
14
+ -webkit-border-radius: 3px; }
 
 
 
15
 
16
  .ws_box {
17
+ min-height: 30px;
18
+ width: 100%;
19
+ margin: 0; }
 
 
20
 
21
  .ws_basic_container {
22
+ float: left;
23
+ display: block; }
 
 
 
 
 
 
 
24
 
25
  .ws_dropzone {
26
+ display: block;
27
+ box-sizing: border-box;
28
+ margin: 2px 6px;
29
+ border: 3px none #b4b9be;
30
+ height: 31px; }
 
 
 
31
 
32
  .ws_dropzone_active,
33
  .ws_dropzone_hover,
34
  .ws_top_to_submenu_drop_hover .ws_dropzone {
35
+ border-style: dashed; }
 
36
 
37
  .ws_dropzone_hover,
38
  .ws_top_to_submenu_drop_hover .ws_dropzone {
39
+ border-width: 1px; }
 
40
 
41
  /*************************************************
42
  Actor UI
43
  *************************************************/
44
  #ws_actor_selector li:after {
45
+ content: '| '; }
 
46
 
47
  #ws_actor_selector li:last-child:after {
48
+ content: ''; }
49
+
50
+ #ws_actor_selector li a {
51
+ display: inline-block;
52
+ text-align: center; }
53
+ #ws_actor_selector li a::before {
54
+ display: block;
55
+ content: attr(data-text);
56
+ font-weight: bold;
57
+ height: 1px;
58
+ overflow: hidden;
59
+ visibility: hidden;
60
+ margin-bottom: -1px; }
61
+
62
+ #ws_actor_selector {
63
+ margin-top: 5px; }
64
 
65
  /**
66
  * The checkbox that lets the user show/hide a menu for the currently selected actor.
67
  */
68
  #ws_menu_editor .ws_actor_access_checkbox,
69
+ #ws_menu_editor input[type="checkbox"].ws_actor_access_checkbox {
70
+ margin-right: 2px;
71
+ margin-left: 2px;
72
+ margin-top: 1px;
73
+ vertical-align: text-top; }
74
+ #ws_menu_editor .ws_actor_access_checkbox:indeterminate:before,
75
+ #ws_menu_editor input[type="checkbox"].ws_actor_access_checkbox:indeterminate:before {
76
+ content: '\25a0';
77
+ color: #1e8cbe;
78
+ margin: -3px 0 0 -1px;
79
+ font: 400 14px/1 dashicons;
80
+ float: left;
81
+ display: inline-block;
82
+ vertical-align: middle;
83
+ width: 16px;
84
+ -webkit-font-smoothing: antialiased; }
85
+
86
+ @media screen and (max-width: 782px) {
87
+ #ws_menu_editor input[type="checkbox"].ws_actor_access_checkbox:indeterminate:before {
88
+ margin: -6px 0 0 1px;
89
+ font: 400 26px/1 dashicons; } }
90
  /* The checkbox is only visible when viewing the menu configuration for a specific actor. */
91
  #ws_menu_editor .ws_actor_access_checkbox {
92
+ display: none; }
 
93
 
94
  #ws_menu_editor.ws_is_actor_view .ws_actor_access_checkbox {
95
+ display: inline-block; }
 
96
 
97
  /* Gray-out items inaccessible to the currently selected actor */
 
98
  .ws_is_actor_view .ws_container.ws_is_hidden_for_actor {
99
+ background-color: #F9F9F9; }
 
100
 
101
  .ws_is_actor_view .ws_is_hidden_for_actor .ws_item_title {
102
+ color: #777; }
 
103
 
104
  /*
105
  * The sidebar
106
  */
 
107
  #ws_editor_sidebar {
108
+ width: auto;
109
+ padding: 2px; }
 
110
 
111
  #ws_menu_editor .ws_main_button {
112
+ clear: both;
113
+ display: block;
114
+ margin: 4px;
115
+ width: 130px; }
 
116
 
117
  #ws_menu_editor #ws_save_menu {
118
+ margin-bottom: 20px; }
 
119
 
120
  #ws_menu_editor #ws_export_menu {
121
+ margin-top: 12px; }
122
+
123
+ #ws_menu_editor #ws_toggle_editor_layout {
124
+ display: none; }
125
 
126
  /*
127
  * Menu components and widgets
128
  */
 
129
  .ws_container {
130
+ display: block;
131
+ width: 290px;
132
+ padding: 3px;
133
+ margin: 2px auto; }
 
 
 
 
 
 
 
 
 
134
 
135
  .ws_submenu {
136
+ min-height: 2em; }
 
 
137
 
138
  .ws_item_head {
139
+ padding: 0; }
 
140
 
141
  .ws_item_title {
142
+ display: inline-block;
143
+ padding: 2px;
144
+ cursor: default;
145
+ font-size: 13px;
146
+ line-height: 18px; }
147
 
148
  .ws_edit_link {
149
+ float: right;
150
+ margin-right: 0;
151
+ cursor: pointer;
152
+ display: block;
153
+ width: 40px;
154
+ height: 22px;
155
+ border-radius: 3px;
156
+ -moz-border-radius: 3px;
157
+ -webkit-border-radius: 3px;
158
+ text-decoration: none; }
 
 
 
 
159
 
160
  .ws_menu_drop_hover {
161
+ background-color: #43b529 !important; }
 
162
 
163
  .ws_container.ui-sortable-helper * {
164
+ cursor: move !important; }
165
+
166
+ .ws_container.ws_sortable_placeholder {
167
+ outline: 1px dashed #b4b9be;
168
+ outline-offset: -1px;
169
+ background: none;
170
+ border-color: transparent; }
171
 
172
  /*
173
  If you ever want to apply a right-arrow style to the currently selected menu item,
185
  z-index: 1002;
186
 
187
  border-left: 14px solid #8EB0F1;
188
+ border-top: 15px solid rgba(255, 255, 255, 0.1);
189
+ border-bottom: 15px solid rgba(255, 255, 255, 0.1);
190
+ background: transparent;
191
 
192
  position: absolute;
193
  right: -14px;
197
  height: 0;
198
  }
199
  */
200
+ /*
201
+ * A left-arrow style alternative. This one is image-based and doesn't suffer from the finicky sizing issues
202
+ * of CSS triangles.
203
+ */
204
+ .ws_container {
205
+ position: relative; }
206
+
207
+ .ws_menu.ws_active::after {
208
+ content: "";
209
+ display: block;
210
+ position: absolute;
211
+ right: -19px;
212
+ top: -1px;
213
+ width: 19px;
214
+ height: 30px;
215
+ background: transparent url("../images/submenu-tip.png") no-repeat center; }
216
+
217
+ .ws_container.ws_menu_separator.ws_active::after,
218
+ .ws_container.ui-sortable-helper::after {
219
+ background-image: none; }
220
 
221
  /****************************************
222
  Per-menu settings fields & panels
223
  *****************************************/
 
224
  .ws_editbox {
225
+ display: block;
226
+ padding: 4px;
227
+ border-radius: 2px;
228
+ border-top-right-radius: 0;
229
+ -moz-border-radius: 2px;
230
+ -moz-border-radius-topright: 0;
231
+ -webkit-border-radius: 2px;
232
+ -webkit-border-top-right-radius: 0; }
 
 
 
 
233
 
234
  .ws_edit_panel {
235
+ margin: 0;
236
+ padding: 0;
237
+ border: none; }
 
238
 
239
  .ws_edit_field {
240
+ margin-bottom: 6px;
241
+ min-height: 45px; }
242
+ .ws_edit_field:after {
243
+ visibility: hidden;
244
+ display: block;
245
+ height: 0;
246
+ font-size: 0;
247
+ content: " ";
248
+ clear: both; }
249
 
250
  .ws_edit_field-custom {
251
+ margin-top: 10px; }
252
+
253
+ .ws_edit_field.ws_no_field_caption {
254
+ margin-top: 10px;
255
+ padding-left: 1px;
256
+ height: 25px;
257
+ min-height: 25px; }
258
 
259
  /* The reset-to-default button */
260
  .ws_reset_button {
261
+ display: block;
262
+ float: right;
263
+ margin-left: 4px;
264
+ margin-top: 2px;
265
+ margin-right: 6px;
266
+ cursor: pointer;
267
+ width: 16px;
268
+ height: 16px;
269
+ vertical-align: top;
270
+ background: url("../images/pencil_delete_gray.png") no-repeat center; }
 
 
 
 
271
 
272
  .ws_reset_button:hover {
273
+ background-image: url("../images/pencil_delete.png"); }
 
274
 
275
  .ws_input_default input,
276
  .ws_input_default select,
277
  .ws_input_default .ws_color_scheme_display {
278
+ color: gray; }
 
279
 
280
  /* No reset button for fields set to the default value and fields without a default value */
281
+ .ws_input_default .ws_reset_button,
282
  .ws_has_no_default .ws_reset_button {
283
+ visibility: hidden; }
 
284
 
285
  /* The input box in each field editor */
286
+ #ws_menu_editor .ws_editbox input[type="text"],
287
  #ws_menu_editor .ws_editbox select {
288
+ display: block;
289
+ float: left;
290
+ width: 254px;
291
+ height: 25px;
292
+ font-size: 12px;
293
+ line-height: 17px;
294
+ padding-top: 3px;
295
+ padding-bottom: 3px; }
296
 
297
  #ws_menu_editor .ws_edit_field label {
298
+ display: block;
299
+ float: left; }
 
300
 
301
  #ws_menu_editor .ws_edit_field-custom input[type="checkbox"] {
302
+ margin-top: 0; }
 
303
 
304
  #ws_menu_editor input[type="text"].ws_field_value {
305
+ min-height: 25px; }
 
306
 
307
  /* Dropdown button for combo-box fields */
308
  #ws_menu_editor .ws_dropdown_button,
309
+ #ws_menu_access_editor .ws_dropdown_button {
310
+ box-sizing: border-box;
311
+ width: 20px;
312
+ height: 25px;
313
+ margin: 1px 1px 1px 0;
314
+ padding: 0;
315
+ text-align: center;
316
+ font-size: 9px !important;
317
+ line-height: 25px;
318
+ border-color: #dfdfdf;
319
+ box-shadow: none;
320
+ border-top-right-radius: 3px;
321
+ border-bottom-right-radius: 3px;
322
+ border-top-left-radius: 0;
323
+ border-bottom-left-radius: 0;
324
+ -moz-border-radius-topright: 3px;
325
+ -moz-border-radius-bottomright: 3px;
326
+ -moz-border-radius-topleft: 0;
327
+ -moz-border-radius-bottomleft: 0;
328
+ -webkit-border-top-right-radius: 3px;
329
+ -webkit-border-bottom-right-radius: 3px;
330
+ -webkit-border-top-left-radius: 0;
331
+ -webkit-border-bottom-left-radius: 0; }
 
 
 
 
 
 
332
 
333
  #ws_menu_access_editor .ws_dropdown_button {
334
+ display: inline-block;
335
+ height: 27px; }
 
336
 
337
  #ws_menu_editor .ws_dropdown_button {
338
+ display: block;
339
+ float: left; }
 
340
 
341
  /*
342
  The appearance and size of combo-box fields need to be changed
343
  to accommodate the drop-down button.
344
  */
345
  #ws_menu_editor .ws_has_dropdown input.ws_field_value,
346
+ #ws_menu_access_editor input.ws_has_dropdown {
347
+ margin-right: 0;
348
+ border-right: 0;
349
+ border-top-right-radius: 0;
350
+ border-bottom-right-radius: 0;
351
+ -moz-border-radius-topright: 0;
352
+ -moz-border-radius-bottomright: 0;
353
+ -webkit-border-top-right-radius: 0;
354
+ -webkit-border-bottom-right-radius: 0; }
 
 
 
 
 
355
 
356
  #ws_menu_access_editor input.ws_has_dropdown {
357
+ width: 90%;
358
+ box-sizing: border-box;
359
+ height: 27px; }
 
360
 
361
  #ws_menu_editor .ws_has_dropdown input.ws_field_value {
362
+ width: 234px; }
 
363
 
364
  /* Unlike others, this field is just a single checkbox, so it has a smaller height */
365
  #ws_menu_editor .ws_edit_field-custom {
366
+ height: 16px; }
 
367
 
368
  /*
369
  * "Show/hide advanced fields"
370
  */
371
  .ws_toggle_container {
372
+ text-align: right;
373
+ margin-right: 27px; }
 
374
 
375
  .ws_toggle_advanced_fields {
376
+ color: #6087CB;
377
+ text-decoration: none;
378
+ font-size: 0.85em; }
 
379
 
380
  .ws_toggle_advanced_fields:visited, .ws_toggle_advanced_fields:active {
381
+ color: #6087CB; }
 
382
 
383
  .ws_toggle_advanced_fields:hover {
384
+ color: #d54e21;
385
+ text-decoration: underline; }
 
386
 
387
  /************************************
388
  Menu flags
389
  *************************************/
 
390
  .ws_flag_container {
391
+ float: right;
392
+ margin-right: 4px;
393
+ padding-top: 2px; }
 
394
 
395
  .ws_flag {
396
+ display: block;
397
+ float: right;
398
+ width: 16px;
399
+ height: 16px;
400
+ margin-left: 4px;
401
+ background-repeat: no-repeat; }
 
402
 
403
  /* user-created items */
404
  .ws_custom_flag {
405
+ background-image: url("../images/page-add.png"); }
 
406
 
407
  /* unused items - those that are in the default menu but not in the custom one */
408
  .ws_unused_flag {
409
+ background-image: url("../images/plugin_add.png"); }
 
410
 
411
  /* hidden items */
412
  .ws_hidden_flag {
413
+ background-image: url("../images/page-invisible.png"); }
 
414
 
415
  /* items with custom permissions for the selected actor */
416
  .ws_custom_actor_permissions_flag {
417
+ font: 16px/1 'dashicons'; }
 
 
 
 
 
418
 
419
+ .ws_custom_actor_permissions_flag::before {
420
+ /*content: "\f160";*/
421
+ /* padlock */
422
+ content: "\f110";
423
+ /* human silhouette */
424
+ color: black;
425
+ filter: alpha(opacity=25);
426
+ /*IE 5-7*/
427
+ opacity: 0.25; }
428
+
429
+ /* Hidden from everyone except the current user and Super Admin. */
430
+ .ws_hidden_from_others_flag {
431
+ background-image: url("../images/font-awesome/eye-slash.png"); }
432
 
433
  /* These classes could be used to apply different styles to items depending on their flags */
 
 
 
 
 
434
  /************************************
435
  Toolbars
436
  *************************************/
 
437
  .ws_toolbar {
438
+ display: block;
439
+ -webkit-box-sizing: border-box;
440
+ -moz-box-sizing: border-box;
441
+ box-sizing: border-box;
442
+ width: 100%;
443
+ padding: 6px 6px 0 6px; }
 
 
 
444
 
445
  .ws_button {
446
+ display: block;
447
+ margin-right: 3px;
448
+ margin-bottom: 4px;
449
+ padding: 4px;
450
+ float: left;
451
+ -webkit-box-sizing: content-box;
452
+ -moz-box-sizing: content-box;
453
+ box-sizing: content-box;
454
+ width: 16px;
455
+ height: 16px;
456
+ border-radius: 3px;
457
+ -moz-border-radius: 3px;
458
+ -webkit-border-radius: 3px; }
459
+ .ws_button img {
460
+ vertical-align: top; }
461
 
462
  a.ws_button:hover {
463
+ background-color: #d0e0ff;
464
+ border-color: #9090c0; }
465
+
466
+ .ws_button.ws_button_disabled {
467
+ border-color: #ccc; }
468
+
469
+ a.ws_button.ws_button_disabled:hover {
470
+ background-color: white;
471
+ border: 1px solid #ccc; }
472
+
473
+ .ws_button_disabled img {
474
+ filter: grayscale(1);
475
+ -webkit-filter: grayscale(1);
476
+ opacity: 0.65; }
477
 
478
  .ws_separator {
479
+ float: left;
480
+ width: 5px; }
 
481
 
482
  /************************************
483
  Capability selector
484
  *************************************/
 
485
  select.ws_dropdown {
486
+ width: 252px;
487
+ height: 20em;
488
+ z-index: 1002;
489
+ position: absolute;
490
+ display: none;
491
+ font-family: "Lucida Grande",Verdana,Arial,"Bitstream Vera Sans",sans-serif;
492
+ font-size: 12px; }
 
 
 
493
 
494
  select.ws_dropdown option {
495
+ font-family: "Lucida Grande",Verdana,Arial,"Bitstream Vera Sans",sans-serif;
496
+ font-size: 12px;
497
+ padding: 3px; }
 
498
 
499
  select.ws_dropdown optgroup option {
500
+ padding-left: 10px; }
 
501
 
502
  /************************************
503
  Icon selector
504
  *************************************/
 
505
  #ws_icon_selector {
506
+ border: 1px solid silver;
507
+ border-radius: 3px;
508
+ background-color: white;
509
+ width: 216px;
510
+ padding: 2px;
511
+ position: absolute; }
 
512
 
513
  #ws_icon_selector.ws_with_more_icons {
514
+ width: 504px; }
 
515
 
516
  #ws_icon_selector .ws_icon_extra {
517
+ display: none; }
 
518
 
519
  #ws_icon_selector.ws_with_more_icons .ws_icon_extra {
520
+ display: inline-block; }
 
 
521
 
522
  #ws_icon_selector .ws_icon_option {
523
+ float: left;
524
+ height: 30px;
525
+ margin: 2px;
526
+ cursor: pointer;
527
+ border: 1px solid #bbb;
528
+ border-radius: 3px;
529
+ /* Gradients and colours cribbed from WP 3.5.1 button styles */
530
+ background: #f3f3f3;
531
+ background-image: -webkit-gradient(linear, left top, left bottom, from(#fefefe), to(#f4f4f4));
532
+ background-image: -webkit-linear-gradient(top, #fefefe, #f4f4f4);
533
+ background-image: -moz-linear-gradient(top, #fefefe, #f4f4f4);
534
+ background-image: -o-linear-gradient(top, #fefefe, #f4f4f4);
535
+ background-image: linear-gradient(to bottom, #fefefe, #f4f4f4); }
 
 
 
536
 
537
  #ws_icon_selector .ws_icon_option:hover {
538
+ /* Gradients and colours cribbed from WP 3.5.1 button styles */
539
+ border-color: #999;
540
+ background: #f3f3f3;
541
+ background-image: -webkit-gradient(linear, left top, left bottom, from(#fff), to(#f3f3f3));
542
+ background-image: -webkit-linear-gradient(top, #fff, #f3f3f3);
543
+ background-image: -moz-linear-gradient(top, #fff, #f3f3f3);
544
+ background-image: -ms-linear-gradient(top, #fff, #f3f3f3);
545
+ background-image: -o-linear-gradient(top, #fff, #f3f3f3);
546
+ background-image: linear-gradient(to bottom, #fff, #f3f3f3); }
 
547
 
548
  #ws_icon_selector .ws_icon_option.ws_selected_icon {
549
+ border-color: green;
550
+ background-color: #deffca;
551
+ background-image: none; }
 
552
 
553
  #ws_icon_selector .icon16 {
554
+ float: none;
555
+ margin: 0; }
 
556
 
557
  #ws_icon_selector .ws_icon_option .ws_icon_image.dashicons {
558
+ width: 20px;
559
+ height: 20px;
560
+ padding: 5px; }
 
561
 
562
  #ws_icon_selector .ws_icon_option img {
563
+ display: inline-block;
564
+ margin: 0;
565
+ padding: 7px;
566
+ width: 16px;
567
+ height: 16px; }
 
 
568
 
569
  #ws_menu_editor .ws_edit_field-icon_url input.ws_field_value {
570
+ width: 220px;
571
+ margin-right: 5px; }
 
572
 
573
  /* The icon button that displays the pop-up icon selector. */
574
  #ws_menu_editor .ws_select_icon {
575
+ margin: 0;
576
+ padding: 0;
577
+ position: relative;
578
+ box-sizing: border-box;
579
+ height: 25px; }
 
 
580
 
581
  /* Current icon node (CSS class version, for the built-in WP icon sprites) */
582
  .ws_select_icon .icon16 {
583
+ margin: 0;
584
+ float: none;
585
+ padding: 3px;
586
+ /*
587
+ The default .icon16 style has a 6px padding which would normally make it too large
588
+ to fit in the button. We can't change the padding without making the background-position
589
+ look wrong, so lets offset the icon so that it fits.
590
+ */
591
+ position: relative;
592
+ top: -3px;
593
+ left: -3px; }
 
 
594
 
595
  /* Current icon node (image version) */
596
  .ws_select_icon img {
597
+ margin: 0;
598
+ padding: 4px;
599
+ width: 16px;
600
+ height: 16px; }
 
601
 
602
  /* MP6 admin style compatibility */
603
  #ws_icon_selector .ws_icon_option .icon16::before {
604
+ margin: 0;
605
+ padding: 0; }
606
+
607
  .ws_select_icon .icon16::before {
608
+ padding: 0;
609
+ margin: 1px 0 0 2px; }
 
610
 
611
  #ws_choose_icon_from_media {
612
+ margin: 2px; }
 
613
 
614
  #ws_show_more_icons {
615
+ margin: 2px;
616
+ height: 30px;
617
+ width: 68px; }
618
+
619
+ /************************************
620
+ Embedded page selector
621
+ *************************************/
622
+ #ws_embedded_page_selector {
623
+ width: 254px;
624
+ padding: 6px 0 0 0;
625
+ border: 1px solid silver;
626
+ border-radius: 3px;
627
+ background-color: white;
628
+ box-sizing: border-box;
629
+ position: absolute; }
630
+
631
+ .ws_page_selector_tab_nav {
632
+ list-style: outside none none;
633
+ padding: 0;
634
+ margin: 0 0 0 6px; }
635
+
636
+ .ws_page_selector_tab_nav li {
637
+ display: inline-block;
638
+ border: 1px solid transparent;
639
+ border-bottom-width: 0;
640
+ padding: 3px 5px 5px;
641
+ line-height: 1.35em;
642
+ margin-bottom: 0; }
643
+
644
+ .ws_page_selector_tab_nav a {
645
+ text-decoration: none; }
646
+
647
+ .ws_page_selector_tab_nav li.ui-tabs-active {
648
+ border-color: #dfdfdf;
649
+ background-color: #FDFDFD;
650
+ border-bottom-color: #FDFDFD; }
651
+
652
+ .ws_page_selector_tab_nav li.ui-tabs-active a {
653
+ color: #32373C; }
654
+
655
+ .ws_page_selector_tab {
656
+ border-top: 1px solid #DFDFDF;
657
+ padding: 12px;
658
+ /* The same padding as post editor boxes. */
659
+ margin-top: -1px;
660
+ background-color: #FDFDFD;
661
+ border-bottom-left-radius: 3px;
662
+ border-bottom-right-radius: 3px; }
663
+
664
+ #ws_current_site_pages {
665
+ width: 100%;
666
+ min-height: 150px;
667
+ max-height: 300px;
668
+ margin-left: 0;
669
+ margin-right: 0; }
670
+
671
+ #ws_embedded_page_selector input {
672
+ box-sizing: border-box;
673
+ max-width: 100%; }
674
+
675
+ #ws_custom_embedded_page_tab p:first-child {
676
+ margin-top: 0; }
677
 
678
+ /*
679
+ Make the "Page" field look editable. It is read-only because the user can't change it directly (they have to use
680
+ the dropdown), but we don't want it to be greyed-out.
681
+ */
682
+ #ws_menu_editor .ws_edit_field-embedded_page_id input.ws_field_value {
683
+ background-color: white; }
684
 
685
  /************************************
686
  Menu color picker
687
  *************************************/
 
688
  #ws-ame-menu-color-settings {
689
+ background: white;
690
+ display: none; }
 
691
 
692
  #ame-menu-color-list {
693
+ height: 500px;
694
+ overflow-y: auto; }
 
695
 
696
  .ame-menu-color-column {
697
+ min-width: 460px; }
 
698
 
699
  .ame-menu-color-name {
700
+ display: inline-block;
701
+ vertical-align: top;
702
+ padding-top: 2px;
703
+ line-height: 1.3;
704
+ font-size: 14px;
705
+ font-weight: 600;
706
+ min-width: 180px; }
 
 
 
707
 
708
  .ame-color-option {
709
+ padding: 10px 0; }
 
710
 
711
  .ame-advanced-menu-color {
712
+ display: none; }
 
713
 
714
+ #ws-ame-apply-colors-to-all {
715
+ display: block;
716
+ float: left;
717
+ margin-left: 5px; }
718
 
719
+ /* Color presets */
720
+ #ame-color-preset-container {
721
+ padding: 0 8px 8px 8px;
722
+ margin-left: -8px;
723
+ margin-right: -8px;
724
+ margin-bottom: 4px;
725
+ border-bottom: 1px solid #eee; }
726
 
727
+ #ame-menu-color-presets {
728
+ width: 290px;
729
+ margin-right: 5px; }
730
 
731
+ #ws-ame-save-color-preset {
732
+ /*margin-right: 5px;*/ }
 
 
733
 
734
+ a#ws-ame-delete-color-preset {
735
+ color: #A00;
736
+ text-decoration: none; }
 
737
 
738
+ a#ws-ame-delete-color-preset:hover {
739
+ color: #F00; }
740
+
741
+ /* Color scheme display in the editor widget. */
742
+ .ws_color_scheme_display {
743
+ display: inline-block;
744
+ height: 20px;
745
+ width: 186px;
746
+ margin-right: 5px;
747
+ padding: 2px 3px;
748
+ font-size: 12px;
749
+ border: 1px solid #ddd;
750
+ background: white;
751
+ cursor: pointer; }
752
+
753
+ .ws_color_display_item {
754
+ display: inline-block;
755
+ width: 18px;
756
+ height: 18px;
757
+ margin-right: 4px;
758
+ border: 1px solid #ccc;
759
+ border-radius: 3px; }
760
 
761
  .ws_color_display_item:last-child {
762
+ margin-right: 0; }
 
763
 
764
  /************************************
765
  Export and import
766
  *************************************/
 
767
  #export_dialog, #import_dialog {
768
+ display: none; }
 
769
 
770
  .ui-widget-overlay {
771
+ background-color: black;
772
+ position: fixed;
773
+ left: 0;
774
+ top: 0;
775
+ opacity: 0.70;
776
+ -moz-opacity: 0.70;
777
+ filter: alpha(opacity=70);
778
+ width: 100%;
779
+ height: 100%; }
 
 
780
 
781
  .ui-front {
782
+ z-index: 10000; }
 
783
 
784
  .ui-dialog {
785
+ background: white;
786
+ border: 1px solid #c0c0c0;
787
+ padding: 0;
788
+ -moz-border-radius: 5px;
789
+ -webkit-border-radius: 5px;
790
+ border-radius: 5px; }
 
 
 
791
 
792
  .ui-dialog-titlebar {
793
+ display: block;
794
+ height: 22px;
795
+ margin: 0;
796
+ padding: 4px 4px 4px 8px;
797
+ background-color: #86A7E3;
798
+ font-size: 1.0em;
799
+ line-height: 22px;
800
+ -webkit-border-top-left-radius: 4px;
801
+ -webkit-border-top-right-radius: 4px;
802
+ -moz-border-radius-topleft: 4px;
803
+ -moz-border-radius-topright: 4px;
804
+ border-top-left-radius: 4px;
805
+ border-top-right-radius: 4px;
806
+ border-bottom: 1px solid #809fd9; }
 
 
 
 
 
 
807
 
808
  .ui-dialog-title {
809
+ color: white;
810
+ font-weight: bold; }
 
811
 
812
  .ui-dialog-titlebar-close {
813
+ background: #86A7E3 url(../images/x.png) no-repeat center;
814
+ width: 22px;
815
+ height: 22px;
816
+ display: block;
817
+ float: right;
818
+ color: white;
819
+ border-radius: 3px;
820
+ -moz-border-radius: 3px;
821
+ -webkit-border-radius: 3px; }
 
 
822
 
823
  .ui-dialog-titlebar-close:hover {
824
+ /*background-image: url(../images/x-light.png);*/
825
+ background-color: #a6c2f5; }
 
 
 
 
 
826
 
827
  .ui-dialog-content {
828
+ padding: 8px 8px 8px 8px;
829
+ font-size: 1.1em; }
 
830
 
831
  #export_dialog .ws_dialog_panel {
832
+ height: 50px; }
 
833
 
834
  #import_dialog .ws_dialog_panel {
835
+ height: 64px; }
 
836
 
837
  .ws_dialog_buttons {
838
+ /*height: 30px;*/
839
+ text-align: right;
840
+ margin-top: 20px;
841
+ margin-bottom: 1px;
842
+ clear: both; }
 
843
 
844
  .ws_dialog_buttons .button-primary {
845
+ display: block;
846
+ float: left;
847
+ margin-top: 0; }
 
848
 
849
  .ws_dialog_buttons .button {
850
+ margin-top: 0; }
 
851
 
852
  .ws_dialog_buttons.ame-vertical-button-list {
853
+ text-align: left; }
 
854
 
855
  .ws_dialog_buttons.ame-vertical-button-list .button-primary {
856
+ float: none; }
 
857
 
858
  .ws_dialog_buttons.ame-vertical-button-list .button {
859
+ width: 100%;
860
+ text-align: left;
861
+ margin-bottom: 10px; }
 
862
 
863
  .ws_dialog_buttons.ame-vertical-button-list .button:last-child {
864
+ margin-bottom: 0; }
 
865
 
866
  #import_file_selector {
867
+ display: block;
868
+ width: 286px;
869
+ margin: 6px auto 12px; }
 
 
870
 
871
  #ws_start_import {
872
+ min-width: 100px; }
 
873
 
874
  #import_complete_notice {
875
+ text-align: center;
876
+ font-size: large;
877
+ padding-top: 25px; }
 
878
 
879
  #ws_import_error_response {
880
+ width: 100%; }
 
881
 
882
  .ws_dont_show_again {
883
+ display: inline-block;
884
+ margin-top: 1em; }
 
885
 
886
  /************************************
887
  Menu access editor
888
  *************************************/
 
889
  /* The launch button */
890
+ #ws_menu_editor .ws_edit_field-access_level input.ws_field_value {
891
+ width: 190px;
892
+ margin-right: 5px; }
 
 
893
 
894
  .ws_launch_access_editor {
895
+ min-width: 40px; }
 
896
 
897
  #ws_menu_access_editor {
898
+ width: 400px;
899
+ display: none; }
 
900
 
901
  .ws_dialog_subpanel {
902
+ margin-bottom: 1em; }
 
903
 
904
+ #ws_menu_access_editor .ws_column_access,
905
+ #ws_menu_access_editor .ws_ext_action_check_column {
906
+ text-align: center;
907
+ width: 1em;
908
+ padding-right: 0; }
909
+
910
+ #ws_menu_access_editor .ws_column_access input,
911
+ #ws_menu_access_editor .ws_ext_action_check_column input {
912
+ margin-right: 0; }
913
+
914
+ #ws_menu_access_editor .ws_column_role {
915
+ white-space: nowrap; }
916
 
917
  #ws_role_table_body_container {
918
+ /*max-height: 400px;
919
+ overflow: auto;*/
920
+ overflow: hidden;
921
+ margin-right: -1px; }
922
 
923
  .ws_role_table_body {
924
+ margin-top: 2px;
925
+ max-width: 354px; }
926
 
927
  .ws_has_separate_header .ws_role_table_header {
928
+ border-bottom: none;
929
+ -moz-border-radius-bottomleft: 0;
930
+ -moz-border-radius-bottomright: 0;
931
+ -webkit-border-bottom-left-radius: 0;
932
+ -webkit-border-bottom-right-radius: 0;
933
+ border-bottom-left-radius: 0;
934
+ border-bottom-right-radius: 0; }
 
 
935
 
936
  .ws_has_separate_header .ws_role_table_body {
937
+ border-top: none;
938
+ margin-top: 0;
939
+ -moz-border-radius-topleft: 0;
940
+ -moz-border-radius-topright: 0;
941
+ -webkit-border-top-left-radius: 0;
942
+ -webkit-border-top-right-radius: 0;
943
+ border-top-left-radius: 0;
944
+ border-top-right-radius: 0; }
 
 
945
 
946
  .ws_role_id {
947
+ display: none; }
 
948
 
949
  #ws_extra_capability {
950
+ width: 100%; }
 
951
 
952
  #ws_role_access_container {
953
+ position: relative;
954
+ max-height: 430px;
955
+ overflow: auto; }
956
 
957
  #ws_role_access_overlay {
958
+ width: 100%;
959
+ height: 100%;
960
+ position: absolute;
961
+ line-height: 100%;
962
+ background: white;
963
+ filter: alpha(opacity=60);
964
+ opacity: 0.6;
965
+ -moz-opacity: 0.6;
966
+ -khtml-opacity: 0.6; }
 
 
 
967
 
968
  #ws_role_access_overlay_content {
969
+ position: absolute;
970
+ width: 50%;
971
+ left: 22%;
972
+ top: 30%;
973
+ background: white;
974
+ padding: 8px;
975
+ border: 2px solid silver;
976
+ border-radius: 5px;
977
+ color: #555; }
 
 
 
978
 
979
  #ws_menu_access_editor div.error {
980
+ margin-left: 0;
981
+ margin-right: 0;
982
+ margin-bottom: 5px; }
 
983
 
984
  #ws_hardcoded_role_error {
985
+ display: none; }
 
986
 
987
+ /*--------------------------------------------*
988
+ The CPT/taxonomy permissions panel
989
+ *--------------------------------------------*/
990
+ /*
991
+ * When there are CPT/taxonomy permissions available, the appearance of the role list changes a bit.
992
+ */
993
+ .ws_has_extended_permissions {
994
+ /* The role or actor whose CPT/taxonomy permissions are currently expanded. */ }
995
+ .ws_has_extended_permissions .ws_role_table_body .ws_column_role {
996
+ cursor: pointer; }
997
+ .ws_has_extended_permissions .ws_role_table_body .ws_column_selected_role_tip {
998
+ display: table-cell; }
999
+ .ws_has_extended_permissions .ws_role_table_body tr:hover {
1000
+ background: #EAF2FA; }
1001
+ .ws_has_extended_permissions .ws_role_table_body td {
1002
+ border-top: 1px solid #f1f1f1; }
1003
+ .ws_has_extended_permissions .ws_role_table_body tr:first-child td {
1004
+ border-top-width: 0; }
1005
+ .ws_has_extended_permissions .ws_role_table_body tr.ws_cpt_selected_role {
1006
+ background-color: #dddddd; }
1007
+ .ws_has_extended_permissions .ws_role_table_body tr.ws_cpt_selected_role .ws_column_role {
1008
+ font-weight: bold; }
1009
+ .ws_has_extended_permissions .ws_role_table_body tr.ws_cpt_selected_role .ws_cpt_selected_role_tip {
1010
+ visibility: visible; }
1011
+ .ws_has_extended_permissions .ws_role_table_body tr.ws_cpt_selected_role td {
1012
+ color: #222; }
1013
+
1014
+ #ws_ext_permissions_container {
1015
+ float: left;
1016
+ width: 352px;
1017
+ padding: 0 9px 0 0; }
1018
+
1019
+ #ws_ext_permissions_container_caption {
1020
+ padding-left: 15px;
1021
+ max-width: 352px;
1022
+ position: relative;
1023
+ white-space: nowrap; }
1024
+
1025
+ #ws_ext_permissions_container .ws_ext_permissions_table {
1026
+ margin-top: 2px; }
1027
+ #ws_ext_permissions_container .ws_ext_permissions_table tr td:first-child {
1028
+ padding-left: 15px; }
1029
+ #ws_ext_permissions_container .ws_ext_permissions_table .ws_ext_group_title {
1030
+ padding-bottom: 0;
1031
+ font-weight: bold; }
1032
+ #ws_ext_permissions_container .ws_ext_permissions_table .ws_ext_action_check_column,
1033
+ #ws_ext_permissions_container .ws_ext_permissions_table .ws_ext_action_name_column {
1034
+ padding-top: 3px;
1035
+ padding-bottom: 3px; }
1036
+ #ws_ext_permissions_container .ws_ext_permissions_table tr.ws_ext_padding_row td {
1037
+ padding: 0 0 0 0;
1038
+ height: 1px; }
1039
+ #ws_ext_permissions_container .ws_ext_permissions_table .ws_same_as_required_cap {
1040
+ text-decoration: underline; }
1041
+ #ws_ext_permissions_container .ws_ext_permissions_table .ws_ext_has_custom_setting label.ws_ext_action_name::after {
1042
+ content: " *"; }
1043
+
1044
+ #ws_ext_permissions_container #ws_ext_toggle_capability_names {
1045
+ cursor: pointer;
1046
+ position: absolute;
1047
+ right: 0;
1048
+ color: #0073aa; }
1049
+ #ws_ext_permissions_container.ws_ext_readable_names_enabled #ws_ext_toggle_capability_names {
1050
+ color: #b4b9be; }
1051
+ #ws_ext_permissions_container .ws_ext_readable_name {
1052
+ display: none; }
1053
+ #ws_ext_permissions_container .ws_ext_capability {
1054
+ display: inline; }
1055
+ #ws_ext_permissions_container.ws_ext_readable_names_enabled .ws_ext_readable_name {
1056
+ display: inline; }
1057
+ #ws_ext_permissions_container.ws_ext_readable_names_enabled .ws_ext_capability {
1058
+ display: none; }
1059
+
1060
+ #ws_ext_permissions_container #ws_taxonomy_permissions_table tr:first-child td {
1061
+ padding-top: 8px; }
1062
+
1063
+ /* The "selected role" indicator. */
1064
+ .ws_cpt_selected_role_tip {
1065
+ display: block;
1066
+ visibility: hidden;
1067
+ box-sizing: border-box;
1068
+ width: 26px;
1069
+ height: 26px;
1070
+ position: absolute;
1071
+ right: 0;
1072
+ background: white;
1073
+ transform: translate(1px, 0) rotate(-45deg);
1074
+ transform-origin: top right; }
1075
+
1076
+ .ws_role_table_body .ws_column_selected_role_tip {
1077
+ display: none;
1078
+ padding: 0;
1079
+ width: 40px;
1080
+ height: 100%;
1081
+ text-align: right;
1082
+ overflow: visible;
1083
+ position: relative;
1084
+ cursor: pointer; }
1085
+
1086
+ .ws_ame_breadcrumb_separator {
1087
+ color: #999; }
1088
+
1089
+ #ws_menu_editor .ws_ext_permissions_indicator {
1090
+ font-size: 16px;
1091
+ height: 16px;
1092
+ width: 16px;
1093
+ visibility: hidden;
1094
+ vertical-align: bottom;
1095
+ cursor: pointer;
1096
+ color: #4aa100; }
1097
+
1098
+ #ws_menu_editor.ws_is_actor_view .ws_ext_permissions_indicator {
1099
+ visibility: visible; }
1100
 
1101
  /************************************
1102
+ Visible users dialog
1103
  *************************************/
1104
+ #ws_visible_users_dialog {
1105
+ background: white;
1106
+ padding: 8px; }
1107
+
1108
+ #ws_user_selection_panels {
1109
+ min-width: 710px; }
1110
+ #ws_user_selection_panels .ws_user_selection_panel {
1111
+ display: block;
1112
+ float: left;
1113
+ position: relative;
1114
+ -webkit-box-sizing: border-box;
1115
+ -moz-box-sizing: border-box;
1116
+ box-sizing: border-box;
1117
+ width: 350px;
1118
+ height: 400px;
1119
+ border: 1px solid #e5e5e5;
1120
+ margin-right: 10px;
1121
+ padding: 10px; }
1122
+ #ws_user_selection_panels #ws_user_selection_target_panel {
1123
+ margin-right: 0; }
1124
+ #ws_user_selection_panels #ws_available_user_query {
1125
+ -webkit-box-sizing: border-box;
1126
+ -moz-box-sizing: border-box;
1127
+ box-sizing: border-box;
1128
+ width: 100%;
1129
+ max-height: 28px; }
1130
+ #ws_user_selection_panels .ws_user_list_wrapper {
1131
+ position: absolute;
1132
+ top: 50px;
1133
+ left: 10px;
1134
+ right: 10px;
1135
+ height: 338px;
1136
+ overflow-x: auto;
1137
+ overflow-y: auto; }
1138
+ #ws_user_selection_panels .ws_user_selection_list {
1139
+ min-height: 20px;
1140
+ border-width: 0;
1141
+ -webkit-box-shadow: none;
1142
+ -moz-box-shadow: none;
1143
+ box-shadow: none; }
1144
+ #ws_user_selection_panels .ws_user_selection_list .ws_user_action_column {
1145
+ width: 20px;
1146
+ text-align: center;
1147
+ padding-top: 9px;
1148
+ padding-bottom: 0; }
1149
+ #ws_user_selection_panels .ws_user_selection_list .ws_user_action_button {
1150
+ cursor: pointer;
1151
+ color: #b4b9be; }
1152
+ #ws_user_selection_panels .ws_user_selection_list .ws_user_username_column {
1153
+ padding-left: 0; }
1154
+ #ws_user_selection_panels .ws_user_selection_list .ws_user_display_name_column {
1155
+ white-space: nowrap; }
1156
+ #ws_user_selection_panels #ws_available_users tr {
1157
+ cursor: pointer; }
1158
+ #ws_user_selection_panels #ws_available_users tr:hover, #ws_user_selection_panels #ws_available_users tr.ws_user_best_match {
1159
+ background-color: #eaf2fa; }
1160
+ #ws_user_selection_panels #ws_available_users tr:hover .ws_user_action_button {
1161
+ color: #7ad03a; }
1162
+ #ws_user_selection_panels #ws_selected_users .ws_user_action_button::before {
1163
+ content: "\f158"; }
1164
+ #ws_user_selection_panels #ws_selected_users .ws_user_action_button:hover {
1165
+ color: #dd3d36; }
1166
+ #ws_user_selection_panels #ws_selected_users .ws_user_action_column {
1167
+ padding-left: 6px; }
1168
+ #ws_user_selection_panels #ws_selected_users .ws_user_display_name_column {
1169
+ display: none; }
1170
+ #ws_user_selection_panels #ws_selected_users tr.ws_is_current_user .ws_user_action_button {
1171
+ display: none; }
1172
+ #ws_user_selection_panels #ws_selected_users_caption {
1173
+ font-size: 14px;
1174
+ line-height: 1.4em;
1175
+ padding: 7px 10px;
1176
+ color: #555;
1177
+ font-weight: 600; }
1178
+ #ws_user_selection_panels::after {
1179
+ display: block;
1180
+ height: 1px;
1181
+ visibility: hidden;
1182
+ content: ' ';
1183
+ clear: both; }
1184
+
1185
+ #ws_loading_users_indicator {
1186
+ position: absolute;
1187
+ right: 10px;
1188
+ bottom: 10px;
1189
+ margin-right: 0;
1190
+ margin-bottom: 0; }
1191
 
1192
+ /************************************
1193
+ Menu deletion error
1194
+ *************************************/
1195
  #ws-ame-menu-deletion-error {
1196
+ max-width: 400px; }
 
 
 
 
1197
 
1198
  /************************************
1199
  Tooltips and hints
1200
  *************************************/
 
1201
  .ws_tooltip_trigger {
1202
+ cursor: pointer; }
 
1203
 
1204
  .ws_tooltip_content_list {
1205
+ list-style: disc;
1206
+ margin-left: 1em;
1207
+ margin-bottom: 0; }
1208
 
1209
+ .ws_tooltip_node {
1210
+ font-size: 13px;
1211
+ line-height: 1.3;
1212
+ border-radius: 3px;
1213
+ max-width: 300px; }
1214
 
1215
+ #ws_plugin_settings_form .ws_tooltip_trigger .dashicons {
1216
+ font-size: 18px; }
 
 
 
1217
 
1218
+ .ws_wide_tooltip {
1219
+ max-width: 450px; }
 
 
 
 
 
1220
 
1221
+ .ws_hint {
1222
+ background: #FFFFE0;
1223
+ border: 1px solid #E6DB55;
1224
+ margin-bottom: 0.5em;
1225
+ border-radius: 3px;
1226
+ position: relative;
1227
+ padding-right: 20px; }
1228
 
1229
+ .ws_hint_close {
1230
+ border: 1px solid #E6DB55;
1231
+ border-right: none;
1232
+ border-top: none;
1233
+ color: #dcc500;
1234
+ font-weight: bold;
1235
+ cursor: pointer;
1236
+ width: 18px;
1237
+ text-align: center;
1238
+ border-radius: 3px;
1239
+ position: absolute;
1240
+ right: 0;
1241
+ top: 0; }
1242
 
1243
  .ws_hint_close:hover {
1244
+ background-color: #ffef4c;
1245
+ border-color: #e0b900;
1246
+ color: black; }
 
1247
 
1248
  .ws_hint_content {
1249
+ padding: 0.4em 0 0.4em 0.4em; }
 
1250
 
1251
  .ws_hint_content ul {
1252
+ list-style: disc;
1253
+ list-style-position: inside;
1254
+ margin-left: 0.5em; }
 
 
1255
 
1256
  /************************************
1257
  Copy Permissions dialog
1258
  *************************************/
1259
  #ws-ame-copy-permissions-dialog select {
1260
+ min-width: 280px; }
 
 
 
1261
 
1262
  #ws_sidebar_pro_ad {
1263
+ min-width: 225px;
1264
+ margin-top: 5px;
1265
+ margin-left: 3px;
1266
+ position: fixed;
1267
+ right: 20px;
1268
+ bottom: 40px;
1269
+ z-index: 100; }
 
 
 
 
1270
 
1271
  .test-wrap {
1272
+ background-color: #444444;
1273
+ padding: 30px; }
 
1274
 
1275
  .test-container {
1276
+ width: 400px;
1277
+ height: 200px;
1278
+ background-color: white;
1279
+ border: 1px solid black;
1280
+ border-radius: 10px;
1281
+ overflow: hidden; }
 
 
 
1282
 
1283
  .test-header {
1284
+ background-color: #67d6ff;
1285
+ padding: 6px;
1286
+ border-top-left-radius: 8px;
1287
+ border-top-right-radius: 8px; }
 
 
1288
 
1289
  .test-content {
1290
+ padding: 8px; }
1291
+
1292
+ /*# sourceMappingURL=menu-editor.css.map */
css/menu-editor.scss ADDED
@@ -0,0 +1,1793 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /* Admin Menu Editor CSS file */
2
+
3
+ #ws_menu_editor {
4
+ min-width: 780px;
5
+ }
6
+
7
+ .ws_main_container {
8
+ margin: 2px;
9
+ width: 310px;
10
+ float: left;
11
+ display:block;
12
+
13
+ border: 1px solid #cdd5d5;
14
+ background-color: #FFFFFF;
15
+
16
+ border-radius: 3px;
17
+ -moz-border-radius: 3px;
18
+ -webkit-border-radius: 3px;
19
+ }
20
+
21
+ .ws_box {
22
+ min-height: 30px;
23
+ width: 100%;
24
+ margin: 0;
25
+ }
26
+
27
+ .ws_basic_container {
28
+ float: left;
29
+ display:block;
30
+ }
31
+
32
+ #ws_menu_box {
33
+ }
34
+
35
+ #ws_submenu_box {
36
+ }
37
+
38
+ .ws_dropzone {
39
+ display: block;
40
+ box-sizing: border-box;
41
+
42
+ margin: 2px 6px;
43
+ border: 3px none #b4b9be;
44
+
45
+ height: 31px;
46
+ }
47
+
48
+ .ws_dropzone_active,
49
+ .ws_dropzone_hover,
50
+ .ws_top_to_submenu_drop_hover .ws_dropzone {
51
+ border-style: dashed;
52
+ }
53
+
54
+ .ws_dropzone_hover,
55
+ .ws_top_to_submenu_drop_hover .ws_dropzone {
56
+ border-width: 1px;
57
+ }
58
+
59
+ /*************************************************
60
+ Actor UI
61
+ *************************************************/
62
+ #ws_actor_selector li:after {
63
+ content: '| ';
64
+ }
65
+
66
+ #ws_actor_selector li:last-child:after {
67
+ content: '';
68
+ }
69
+
70
+ #ws_actor_selector li {
71
+ a {
72
+ display: inline-block;
73
+ text-align: center;
74
+ }
75
+
76
+ a::before {
77
+ display: block;
78
+ content: attr(data-text);
79
+ font-weight: bold;
80
+
81
+ height: 1px;
82
+ overflow: hidden;
83
+ visibility: hidden;
84
+ margin-bottom: -1px;
85
+ }
86
+ }
87
+
88
+ #ws_actor_selector {
89
+ margin-top: 5px;
90
+ }
91
+
92
+ /**
93
+ * The checkbox that lets the user show/hide a menu for the currently selected actor.
94
+ */
95
+ #ws_menu_editor .ws_actor_access_checkbox,
96
+ #ws_menu_editor input[type="checkbox"].ws_actor_access_checkbox /* Ensure we override WP defaults. */
97
+ {
98
+ margin-right: 2px;
99
+ margin-left: 2px;
100
+ margin-top: 1px;
101
+ vertical-align: text-top;
102
+
103
+ &:indeterminate:before {
104
+ content: '\25a0'; //Unicode black square. Another option would be BLACK LARGE SQUARE (U+2B1B).
105
+ color: #1e8cbe;
106
+
107
+ //Large square.
108
+ //margin: -6px 0 0 -1px;
109
+ //font: 400 18px/1 dashicons;
110
+
111
+ //Smaller square.
112
+ margin: -3px 0 0 -1px;
113
+ font: 400 14px/1 dashicons;
114
+
115
+ //Even smaller square.
116
+ //margin: -2px 0 0 -1px;
117
+ //font: 400 13px/1 dashicons;
118
+
119
+ float: left;
120
+ display: inline-block;
121
+ vertical-align: middle;
122
+ width: 16px;
123
+ -webkit-font-smoothing: antialiased;
124
+ }
125
+ }
126
+
127
+ @media screen and (max-width: 782px) {
128
+ #ws_menu_editor input[type="checkbox"].ws_actor_access_checkbox {
129
+ &:indeterminate:before {
130
+ margin: -6px 0 0 1px;
131
+ font: 400 26px/1 dashicons;
132
+ }
133
+ }
134
+ }
135
+
136
+ /* The checkbox is only visible when viewing the menu configuration for a specific actor. */
137
+ #ws_menu_editor .ws_actor_access_checkbox {
138
+ display: none;
139
+ }
140
+
141
+ #ws_menu_editor.ws_is_actor_view .ws_actor_access_checkbox {
142
+ display: inline-block;
143
+ }
144
+
145
+ /* Gray-out items inaccessible to the currently selected actor */
146
+
147
+ .ws_is_actor_view .ws_container.ws_is_hidden_for_actor {
148
+ background-color: #F9F9F9;
149
+ }
150
+
151
+ .ws_is_actor_view .ws_is_hidden_for_actor .ws_item_title {
152
+ color: #777;
153
+ }
154
+
155
+ /*
156
+ * The sidebar
157
+ */
158
+
159
+ #ws_editor_sidebar {
160
+ width: auto;
161
+ padding: 2px;
162
+ }
163
+
164
+ #ws_menu_editor .ws_main_button {
165
+ clear: both;
166
+ display: block;
167
+ margin: 4px;
168
+ width: 130px;
169
+ }
170
+
171
+ #ws_menu_editor #ws_save_menu {
172
+ margin-bottom: 20px;
173
+ }
174
+
175
+ #ws_menu_editor #ws_export_menu {
176
+ margin-top: 12px;
177
+ }
178
+
179
+ #ws_menu_editor #ws_toggle_editor_layout {
180
+ display: none;
181
+ }
182
+
183
+ /*
184
+ * Menu components and widgets
185
+ */
186
+
187
+ .ws_container {
188
+ display: block;
189
+ width: 290px;
190
+
191
+ padding : 3px;
192
+ margin: 2px auto;
193
+ }
194
+
195
+ .ws_active { }
196
+
197
+ .ws_menu { }
198
+ .ws_item { }
199
+
200
+ .ws_menu_separator { }
201
+
202
+ .ws_submenu {
203
+ min-height: 2em;
204
+ }
205
+
206
+
207
+ .ws_item_head {
208
+ padding: 0;
209
+ }
210
+
211
+ .ws_item_title {
212
+ display: inline-block;
213
+ padding: 2px;
214
+ cursor: default;
215
+
216
+ font-size: 13px;
217
+ line-height: 18px;
218
+ }
219
+
220
+ .ws_edit_link {
221
+ float: right;
222
+ margin-right: 0;
223
+ cursor: pointer;
224
+ display:block;
225
+ width: 40px;
226
+ height: 22px;
227
+
228
+ border-radius: 3px;
229
+ -moz-border-radius: 3px;
230
+ -webkit-border-radius: 3px;
231
+
232
+ text-decoration: none;
233
+ }
234
+
235
+ .ws_edit_link_expanded { }
236
+
237
+
238
+ .ws_menu_drop_hover {
239
+ background-color: #43b529 !important;
240
+ }
241
+
242
+ .ws_container.ui-sortable-helper * {
243
+ cursor: move !important;
244
+ }
245
+
246
+ .ws_container.ws_sortable_placeholder {
247
+ outline: 1px dashed #b4b9be;
248
+ outline-offset: -1px;
249
+ background: none;
250
+ border-color: transparent;
251
+ }
252
+
253
+ /*
254
+ If you ever want to apply a right-arrow style to the currently selected menu item,
255
+ you can do it like this. Commented out for now since it doesn't look all that great,
256
+ but might be useful in the future.
257
+ */
258
+ /*
259
+ .ws_container {
260
+ position: relative;
261
+ }
262
+
263
+ .ws_menu.ws_active::after {
264
+ content: "";
265
+ display: block;
266
+ z-index: 1002;
267
+
268
+ border-left: 14px solid #8EB0F1;
269
+ border-top: 15px solid rgba(255, 255, 255, 0.1);
270
+ border-bottom: 15px solid rgba(255, 255, 255, 0.1);
271
+ background: transparent;
272
+
273
+ position: absolute;
274
+ right: -14px;
275
+ top: -1px;
276
+
277
+ width: 0;
278
+ height: 0;
279
+ }
280
+ */
281
+
282
+ /*
283
+ * A left-arrow style alternative. This one is image-based and doesn't suffer from the finicky sizing issues
284
+ * of CSS triangles.
285
+ */
286
+
287
+ .ws_container {
288
+ position: relative;
289
+ }
290
+
291
+ .ws_menu.ws_active::after {
292
+ //These should match the background image size.
293
+ $submenuTipHeight: 30px;
294
+ $submenuTipWidth: 19px;
295
+
296
+ content: "";
297
+ display: block;
298
+ //z-index: 1002;
299
+
300
+ position: absolute;
301
+ right: -$submenuTipWidth;
302
+ top: -1px;
303
+
304
+ width: $submenuTipWidth;
305
+ height: $submenuTipHeight;
306
+ background: transparent url("../images/submenu-tip.png") no-repeat center;
307
+ }
308
+
309
+ //No arrows for separators and items that are being dragged.
310
+ .ws_container.ws_menu_separator.ws_active::after,
311
+ .ws_container.ui-sortable-helper::after {
312
+ background-image: none;
313
+ }
314
+
315
+ /****************************************
316
+ Per-menu settings fields & panels
317
+ *****************************************/
318
+
319
+ .ws_editbox {
320
+ display: block;
321
+ padding: 4px;
322
+
323
+ border-radius: 2px;
324
+ border-top-right-radius: 0;
325
+
326
+ -moz-border-radius: 2px;
327
+ -moz-border-radius-topright: 0;
328
+
329
+ -webkit-border-radius: 2px;
330
+ -webkit-border-top-right-radius: 0;
331
+ }
332
+
333
+ .ws_edit_panel {
334
+ margin: 0;
335
+ padding: 0;
336
+ border: none;
337
+ }
338
+
339
+ .ws_edit_field {
340
+ margin-bottom: 6px;
341
+ min-height: 45px;
342
+
343
+ //Clear-fix.
344
+ &:after {
345
+ visibility: hidden;
346
+ display: block;
347
+ height: 0;
348
+ font-size: 0;
349
+
350
+ content: " ";
351
+ clear: both;
352
+ }
353
+ }
354
+
355
+ .ws_edit_field-custom {
356
+ margin-top: 10px;
357
+ }
358
+
359
+ .ws_edit_field.ws_no_field_caption {
360
+ margin-top: 10px;
361
+ padding-left: 1px;
362
+ height: 25px;
363
+ min-height: 25px;
364
+ }
365
+
366
+ /* The reset-to-default button */
367
+ .ws_reset_button {
368
+ display: block;
369
+ float: right;
370
+
371
+ margin-left: 4px;
372
+ margin-top: 2px;
373
+ margin-right: 6px;
374
+ cursor: pointer;
375
+
376
+ width: 16px;
377
+ height: 16px;
378
+ vertical-align: top;
379
+
380
+ background: url("../images/pencil_delete_gray.png") no-repeat center;
381
+ }
382
+
383
+ .ws_reset_button:hover {
384
+ background-image: url("../images/pencil_delete.png");
385
+ }
386
+
387
+ .ws_input_default input,
388
+ .ws_input_default select,
389
+ .ws_input_default .ws_color_scheme_display {
390
+ color: gray;
391
+ }
392
+
393
+ /* No reset button for fields set to the default value and fields without a default value */
394
+ .ws_input_default .ws_reset_button,
395
+ .ws_has_no_default .ws_reset_button {
396
+ visibility: hidden;
397
+ }
398
+
399
+ /* The input box in each field editor */
400
+ #ws_menu_editor .ws_editbox input[type="text"],
401
+ #ws_menu_editor .ws_editbox select {
402
+ display: block;
403
+ float: left;
404
+ width: 254px;
405
+ height: 25px;
406
+
407
+ font-size: 12px;
408
+ line-height: 17px;
409
+
410
+ padding-top: 3px;
411
+ padding-bottom: 3px;
412
+ }
413
+
414
+ #ws_menu_editor .ws_edit_field label {
415
+ display: block;
416
+ float: left;
417
+ }
418
+
419
+ #ws_menu_editor .ws_edit_field-custom input[type="checkbox"] {
420
+ margin-top: 0;
421
+ }
422
+
423
+ #ws_menu_editor input[type="text"].ws_field_value {
424
+ min-height: 25px;
425
+ }
426
+
427
+ /* Dropdown button for combo-box fields */
428
+ #ws_menu_editor .ws_dropdown_button,
429
+ #ws_menu_access_editor .ws_dropdown_button
430
+ {
431
+ box-sizing: border-box;
432
+ width: 20px;
433
+ height: 25px;
434
+
435
+ margin: 1px 1px 1px 0;
436
+ padding: 0;
437
+
438
+ text-align: center;
439
+ font-size: 9px !important;
440
+ line-height: 25px;
441
+
442
+ border-color: #dfdfdf;
443
+ box-shadow: none;
444
+
445
+ border-top-right-radius: 3px;
446
+ border-bottom-right-radius: 3px;
447
+ border-top-left-radius: 0;
448
+ border-bottom-left-radius: 0;
449
+
450
+ -moz-border-radius-topright: 3px;
451
+ -moz-border-radius-bottomright: 3px;
452
+ -moz-border-radius-topleft: 0;
453
+ -moz-border-radius-bottomleft: 0;
454
+
455
+ -webkit-border-top-right-radius: 3px;
456
+ -webkit-border-bottom-right-radius: 3px;
457
+ -webkit-border-top-left-radius: 0;
458
+ -webkit-border-bottom-left-radius: 0;
459
+ }
460
+
461
+ #ws_menu_access_editor .ws_dropdown_button {
462
+ display: inline-block;
463
+ height: 27px;
464
+ }
465
+
466
+ #ws_menu_editor .ws_dropdown_button {
467
+ display: block;
468
+ float: left;
469
+ }
470
+
471
+ /*
472
+ The appearance and size of combo-box fields need to be changed
473
+ to accommodate the drop-down button.
474
+ */
475
+ #ws_menu_editor .ws_has_dropdown input.ws_field_value,
476
+ #ws_menu_access_editor input.ws_has_dropdown
477
+ {
478
+ margin-right: 0;
479
+ border-right: 0;
480
+
481
+ border-top-right-radius: 0;
482
+ border-bottom-right-radius: 0;
483
+
484
+ -moz-border-radius-topright: 0;
485
+ -moz-border-radius-bottomright: 0;
486
+
487
+ -webkit-border-top-right-radius: 0;
488
+ -webkit-border-bottom-right-radius: 0;
489
+ }
490
+
491
+ #ws_menu_access_editor input.ws_has_dropdown {
492
+ width: 90%;
493
+ box-sizing: border-box;
494
+ height: 27px;
495
+ }
496
+
497
+ #ws_menu_editor .ws_has_dropdown input.ws_field_value {
498
+ width: 234px;
499
+ }
500
+
501
+ /* Unlike others, this field is just a single checkbox, so it has a smaller height */
502
+ #ws_menu_editor .ws_edit_field-custom {
503
+ height: 16px;
504
+ }
505
+
506
+ /*
507
+ * "Show/hide advanced fields"
508
+ */
509
+ .ws_toggle_container {
510
+ text-align: right;
511
+ margin-right: 27px;
512
+ }
513
+
514
+ .ws_toggle_advanced_fields {
515
+ color: #6087CB;
516
+ text-decoration: none;
517
+ font-size: 0.85em;
518
+ }
519
+
520
+ .ws_toggle_advanced_fields:visited, .ws_toggle_advanced_fields:active {
521
+ color: #6087CB;
522
+ }
523
+
524
+ .ws_toggle_advanced_fields:hover {
525
+ color: #d54e21;
526
+ text-decoration: underline;
527
+ }
528
+
529
+ /************************************
530
+ Menu flags
531
+ *************************************/
532
+
533
+ .ws_flag_container {
534
+ float: right;
535
+ margin-right: 4px;
536
+ padding-top: 2px;
537
+ }
538
+
539
+ .ws_flag {
540
+ display: block;
541
+ float: right;
542
+ width: 16px;
543
+ height: 16px;
544
+ margin-left: 4px;
545
+ background-repeat: no-repeat;
546
+ }
547
+
548
+ /* user-created items */
549
+ .ws_custom_flag {
550
+ background-image: url('../images/page-add.png');
551
+ }
552
+
553
+ /* unused items - those that are in the default menu but not in the custom one */
554
+ .ws_unused_flag {
555
+ background-image: url('../images/plugin_add.png');
556
+ }
557
+
558
+ /* hidden items */
559
+ .ws_hidden_flag {
560
+ background-image: url('../images/page-invisible.png');
561
+ }
562
+
563
+ /* items with custom permissions for the selected actor */
564
+ .ws_custom_actor_permissions_flag {
565
+ font: 16px/1 'dashicons';
566
+ }
567
+ .ws_custom_actor_permissions_flag::before {
568
+ /*content: "\f160";*/ /* padlock */
569
+ content: "\f110"; /* human silhouette */
570
+ color: black;
571
+
572
+ filter: alpha(opacity=25); /*IE 5-7*/
573
+ opacity: 0.25;
574
+ }
575
+
576
+ /* Hidden from everyone except the current user and Super Admin. */
577
+ .ws_hidden_from_others_flag {
578
+ background-image: url('../images/font-awesome/eye-slash.png');
579
+ }
580
+
581
+ /* These classes could be used to apply different styles to items depending on their flags */
582
+ .ws_custom { }
583
+ .ws_hidden { }
584
+ .ws_unused { }
585
+
586
+
587
+ /************************************
588
+ Toolbars
589
+ *************************************/
590
+
591
+ .ws_toolbar {
592
+ display: block;
593
+
594
+ -webkit-box-sizing: border-box;
595
+ -moz-box-sizing: border-box;
596
+ box-sizing: border-box;
597
+
598
+ width: 100%;
599
+ padding: 6px 6px 0 6px;
600
+ //height: 34px;
601
+ }
602
+
603
+ .ws_button_container {
604
+ //padding-left: 6px;
605
+ //padding-top: 6px;
606
+ }
607
+
608
+ .ws_button {
609
+ display: block;
610
+ margin-right: 3px;
611
+ margin-bottom: 4px;
612
+
613
+ padding: 4px;
614
+ float: left;
615
+
616
+ -webkit-box-sizing: content-box;
617
+ -moz-box-sizing: content-box;
618
+ box-sizing: content-box;
619
+
620
+ width: 16px;
621
+ height: 16px;
622
+
623
+ border-radius: 3px;
624
+ -moz-border-radius: 3px;
625
+ -webkit-border-radius: 3px;
626
+
627
+ img {
628
+ vertical-align: top;
629
+ }
630
+ }
631
+
632
+ a.ws_button:hover {
633
+ background-color: #d0e0ff;
634
+ border-color: #9090c0;
635
+ }
636
+
637
+ //Disabled button state.
638
+ .ws_button.ws_button_disabled {
639
+ border-color: #ccc;
640
+ }
641
+ a.ws_button.ws_button_disabled:hover {
642
+ background-color: white;
643
+ border: 1px solid #ccc;
644
+ }
645
+ .ws_button_disabled img {
646
+ filter: grayscale(1);
647
+ -webkit-filter: grayscale(1);
648
+ opacity: 0.65;
649
+ }
650
+
651
+ .ws_separator {
652
+ float: left;
653
+ width: 5px;
654
+ }
655
+
656
+ /************************************
657
+ Capability selector
658
+ *************************************/
659
+
660
+ select.ws_dropdown {
661
+ width: 252px;
662
+ height: 20em;
663
+
664
+ z-index: 1002;
665
+ position: absolute;
666
+ display: none;
667
+
668
+ font-family : "Lucida Grande",Verdana,Arial,"Bitstream Vera Sans",sans-serif;
669
+ font-size: 12px;
670
+ }
671
+
672
+ select.ws_dropdown option {
673
+ font-family : "Lucida Grande",Verdana,Arial,"Bitstream Vera Sans",sans-serif;
674
+ font-size: 12px;
675
+ padding: 3px;
676
+ }
677
+
678
+ select.ws_dropdown optgroup option {
679
+ padding-left: 10px;
680
+ }
681
+
682
+ /************************************
683
+ Icon selector
684
+ *************************************/
685
+
686
+ #ws_icon_selector {
687
+ border: 1px solid silver;
688
+ border-radius: 3px;
689
+ background-color: white;
690
+
691
+ width: 216px;
692
+ padding: 2px;
693
+ position: absolute;
694
+ }
695
+
696
+ #ws_icon_selector.ws_with_more_icons {
697
+ width: 504px;
698
+ }
699
+
700
+ #ws_icon_selector .ws_icon_extra {
701
+ display: none;
702
+ }
703
+
704
+ #ws_icon_selector.ws_with_more_icons .ws_icon_extra {
705
+ display: inline-block;
706
+ }
707
+
708
+
709
+ #ws_icon_selector .ws_icon_option {
710
+ float: left;
711
+ height: 30px;
712
+
713
+ margin: 2px;
714
+ cursor: pointer;
715
+ border: 1px solid #bbb;
716
+ border-radius: 3px;
717
+
718
+ /* Gradients and colours cribbed from WP 3.5.1 button styles */
719
+ background: #f3f3f3;
720
+ background-image: -webkit-gradient(linear, left top, left bottom, from(#fefefe), to(#f4f4f4));
721
+ background-image: -webkit-linear-gradient(top, #fefefe, #f4f4f4);
722
+ background-image: -moz-linear-gradient(top, #fefefe, #f4f4f4);
723
+ background-image: -o-linear-gradient(top, #fefefe, #f4f4f4);
724
+ background-image: linear-gradient(to bottom, #fefefe, #f4f4f4);
725
+ }
726
+
727
+ #ws_icon_selector .ws_icon_option:hover {
728
+ /* Gradients and colours cribbed from WP 3.5.1 button styles */
729
+ border-color: #999;
730
+ background: #f3f3f3;
731
+ background-image: -webkit-gradient(linear, left top, left bottom, from(#fff), to(#f3f3f3));
732
+ background-image: -webkit-linear-gradient(top, #fff, #f3f3f3);
733
+ background-image: -moz-linear-gradient(top, #fff, #f3f3f3);
734
+ background-image: -ms-linear-gradient(top, #fff, #f3f3f3);
735
+ background-image: -o-linear-gradient(top, #fff, #f3f3f3);
736
+ background-image: linear-gradient(to bottom, #fff, #f3f3f3);
737
+ }
738
+
739
+ #ws_icon_selector .ws_icon_option.ws_selected_icon {
740
+ border-color: green;
741
+ background-color: #deffca;
742
+ background-image: none;
743
+ }
744
+
745
+ #ws_icon_selector .icon16 {
746
+ float: none;
747
+ margin: 0;
748
+ }
749
+
750
+ #ws_icon_selector .ws_icon_option .ws_icon_image.dashicons {
751
+ width: 20px;
752
+ height: 20px;
753
+ padding: 5px;
754
+ }
755
+
756
+ #ws_icon_selector .ws_icon_option img {
757
+ display: inline-block;
758
+ margin: 0;
759
+ padding: 7px;
760
+
761
+ width: 16px;
762
+ height: 16px;
763
+ }
764
+
765
+ #ws_menu_editor .ws_edit_field-icon_url input.ws_field_value {
766
+ width: 220px;
767
+ margin-right: 5px;
768
+ }
769
+
770
+ /* The icon button that displays the pop-up icon selector. */
771
+ #ws_menu_editor .ws_select_icon {
772
+ margin: 0;
773
+ padding: 0;
774
+ position: relative;
775
+
776
+ box-sizing: border-box;
777
+ height: 25px;
778
+ }
779
+
780
+ /* Current icon node (CSS class version, for the built-in WP icon sprites) */
781
+ .ws_select_icon .icon16 {
782
+ margin: 0;
783
+ float: none;
784
+ padding: 3px;
785
+
786
+ /*
787
+ The default .icon16 style has a 6px padding which would normally make it too large
788
+ to fit in the button. We can't change the padding without making the background-position
789
+ look wrong, so lets offset the icon so that it fits.
790
+ */
791
+ position: relative;
792
+ top: -3px;
793
+ left: -3px;
794
+ }
795
+
796
+ /* Current icon node (image version) */
797
+ .ws_select_icon img {
798
+ margin: 0;
799
+ padding: 4px;
800
+ width: 16px;
801
+ height: 16px;
802
+ }
803
+
804
+ /* MP6 admin style compatibility */
805
+ #ws_icon_selector .ws_icon_option .icon16::before {
806
+ margin: 0;
807
+ padding: 0;
808
+ }
809
+ .ws_select_icon .icon16::before {
810
+ padding: 0;
811
+ margin: 1px 0 0 2px;
812
+ }
813
+
814
+ #ws_choose_icon_from_media {
815
+ margin: 2px;
816
+ }
817
+
818
+ #ws_show_more_icons {
819
+ margin: 2px;
820
+ height: 30px;
821
+ width: 68px;
822
+ }
823
+
824
+ /************************************
825
+ Embedded page selector
826
+ *************************************/
827
+
828
+ #ws_embedded_page_selector {
829
+ width: 254px;
830
+ padding: 6px 0 0 0;
831
+
832
+ border: 1px solid silver;
833
+ border-radius: 3px;
834
+ background-color: white;
835
+
836
+ box-sizing: border-box;
837
+ position: absolute;
838
+ }
839
+
840
+ .ws_page_selector_tab_nav {
841
+ list-style: outside none none;
842
+ padding: 0;
843
+ margin: 0 0 0 6px;
844
+ }
845
+
846
+ .ws_page_selector_tab_nav li {
847
+ display: inline-block;
848
+
849
+ border: 1px solid transparent;
850
+ border-bottom-width: 0;
851
+ padding: 3px 5px 5px;
852
+ line-height: 1.35em;
853
+
854
+ margin-bottom: 0;
855
+ }
856
+
857
+ .ws_page_selector_tab_nav a {
858
+ text-decoration: none;
859
+ }
860
+
861
+ .ws_page_selector_tab_nav li.ui-tabs-active {
862
+ border-color: #dfdfdf;
863
+ background-color: #FDFDFD;
864
+ border-bottom-color: #FDFDFD;
865
+ }
866
+
867
+ .ws_page_selector_tab_nav li.ui-tabs-active a {
868
+ color: #32373C;
869
+ }
870
+
871
+ .ws_page_selector_tab {
872
+ border-top: 1px solid #DFDFDF;
873
+ padding: 12px; /* The same padding as post editor boxes. */
874
+ margin-top: -1px;
875
+ background-color: #FDFDFD;
876
+
877
+ border-bottom-left-radius: 3px;
878
+ border-bottom-right-radius: 3px;
879
+ }
880
+
881
+ #ws_current_site_pages {
882
+ width: 100%;
883
+ min-height: 150px;
884
+ max-height: 300px;
885
+
886
+ margin-left: 0;
887
+ margin-right: 0;
888
+ }
889
+
890
+ #ws_embedded_page_selector input {
891
+ box-sizing: border-box;
892
+ max-width: 100%;
893
+ }
894
+
895
+ #ws_custom_embedded_page_tab p:first-child {
896
+ margin-top: 0;
897
+ }
898
+
899
+ /*
900
+ Make the "Page" field look editable. It is read-only because the user can't change it directly (they have to use
901
+ the dropdown), but we don't want it to be greyed-out.
902
+ */
903
+ #ws_menu_editor .ws_edit_field-embedded_page_id input.ws_field_value {
904
+ background-color: white;
905
+ }
906
+
907
+
908
+ /************************************
909
+ Menu color picker
910
+ *************************************/
911
+
912
+ #ws-ame-menu-color-settings {
913
+ background: white;
914
+ display: none;
915
+ }
916
+
917
+ #ame-menu-color-list {
918
+ height: 500px;
919
+ overflow-y: auto;
920
+ }
921
+
922
+ .ame-menu-color-column {
923
+ min-width: 460px;
924
+ }
925
+
926
+ .ame-menu-color-name {
927
+ display: inline-block;
928
+ vertical-align: top;
929
+ padding-top: 2px;
930
+
931
+ line-height: 1.3;
932
+ font-size: 14px;
933
+ font-weight: 600;
934
+
935
+ min-width: 180px;
936
+ }
937
+
938
+ .ame-color-option {
939
+ padding: 10px 0;
940
+ }
941
+
942
+ .ame-advanced-menu-color {
943
+ display: none;
944
+ }
945
+
946
+ #ws-ame-apply-colors-to-all {
947
+ display: block;
948
+ float: left;
949
+ margin-left: 5px;
950
+ }
951
+
952
+ /* Color presets */
953
+ #ame-color-preset-container {
954
+ padding: 0 8px 8px 8px;
955
+
956
+ margin-left: -8px;
957
+ margin-right: -8px;
958
+ margin-bottom: 4px;
959
+
960
+ border-bottom: 1px solid #eee;
961
+ }
962
+
963
+ #ame-menu-color-presets {
964
+ width: 290px;
965
+ margin-right: 5px;
966
+ }
967
+
968
+ #ws-ame-save-color-preset {
969
+ /*margin-right: 5px;*/
970
+ }
971
+
972
+ a#ws-ame-delete-color-preset {
973
+ color: #A00;
974
+ text-decoration: none;
975
+ }
976
+ a#ws-ame-delete-color-preset:hover {
977
+ color: #F00;
978
+ }
979
+
980
+ /* Color scheme display in the editor widget. */
981
+
982
+ .ws_color_scheme_display {
983
+ display: inline-block;
984
+ height: 20px;
985
+ width: 186px;
986
+
987
+ margin-right: 5px;
988
+ padding: 2px 3px;
989
+
990
+ font-size: 12px;
991
+ border: 1px solid #ddd;
992
+ background: white;
993
+ cursor: pointer;
994
+ }
995
+
996
+ .ws_color_display_item {
997
+ display: inline-block;
998
+ width: 18px;
999
+ height: 18px;
1000
+
1001
+ margin-right: 4px;
1002
+ border: 1px solid #ccc;
1003
+ border-radius: 3px;
1004
+ }
1005
+
1006
+ .ws_color_display_item:last-child {
1007
+ margin-right: 0;
1008
+ }
1009
+
1010
+ /************************************
1011
+ Export and import
1012
+ *************************************/
1013
+
1014
+ #export_dialog, #import_dialog {
1015
+ display: none;
1016
+ }
1017
+
1018
+ .ui-widget-overlay {
1019
+ background-color: black;
1020
+ position: fixed;
1021
+ left: 0;
1022
+ top: 0;
1023
+ opacity: 0.70;
1024
+ -moz-opacity: 0.70;
1025
+ filter: alpha(opacity=70);
1026
+
1027
+ width: 100%;
1028
+ height: 100%;
1029
+ }
1030
+
1031
+ .ui-front {
1032
+ z-index: 10000;
1033
+ }
1034
+
1035
+ .ui-dialog {
1036
+ background: white;
1037
+ border: 1px solid #c0c0c0;
1038
+
1039
+ padding: 0;
1040
+
1041
+ -moz-border-radius: 5px;
1042
+ -webkit-border-radius: 5px;
1043
+ border-radius: 5px;
1044
+ }
1045
+
1046
+ .ui-dialog-titlebar {
1047
+ display: block;
1048
+ height: 22px;
1049
+ margin: 0;
1050
+ padding: 4px 4px 4px 8px;
1051
+
1052
+ background-color: #86A7E3;
1053
+ font-size: 1.0em;
1054
+ line-height: 22px;
1055
+
1056
+ -webkit-border-top-left-radius: 4px;
1057
+ -webkit-border-top-right-radius: 4px;
1058
+
1059
+ -moz-border-radius-topleft: 4px;
1060
+ -moz-border-radius-topright: 4px;
1061
+
1062
+ border-top-left-radius: 4px;
1063
+ border-top-right-radius: 4px;
1064
+
1065
+ border-bottom: 1px solid #809fd9;
1066
+ }
1067
+
1068
+ .ui-dialog-title {
1069
+ color: white;
1070
+ font-weight: bold;
1071
+ }
1072
+
1073
+ .ui-dialog-titlebar-close {
1074
+ background: #86A7E3 url(../images/x.png) no-repeat center;
1075
+ width: 22px;
1076
+ height: 22px;
1077
+ display: block;
1078
+ float: right;
1079
+ color: white;
1080
+
1081
+ border-radius: 3px;
1082
+ -moz-border-radius: 3px;
1083
+ -webkit-border-radius: 3px;
1084
+ }
1085
+
1086
+ .ui-dialog-titlebar-close:hover {
1087
+ /*background-image: url(../images/x-light.png);*/
1088
+ background-color: #a6c2f5;
1089
+ }
1090
+
1091
+ .ui-icon-closethick {
1092
+
1093
+ }
1094
+
1095
+ .ui-dialog-content {
1096
+ padding: 8px 8px 8px 8px;
1097
+ font-size: 1.1em;
1098
+ }
1099
+
1100
+ #export_dialog .ws_dialog_panel {
1101
+ height: 50px;
1102
+ }
1103
+
1104
+ #import_dialog .ws_dialog_panel {
1105
+ height: 64px;
1106
+ }
1107
+
1108
+ .ws_dialog_buttons {
1109
+ /*height: 30px;*/
1110
+ text-align: right;
1111
+ margin-top: 20px;
1112
+ margin-bottom: 1px;
1113
+ clear: both;
1114
+ }
1115
+
1116
+ .ws_dialog_buttons .button-primary {
1117
+ display: block;
1118
+ float: left;
1119
+ margin-top: 0;
1120
+ }
1121
+
1122
+ .ws_dialog_buttons .button {
1123
+ margin-top: 0;
1124
+ }
1125
+
1126
+ .ws_dialog_buttons.ame-vertical-button-list {
1127
+ text-align: left;
1128
+ }
1129
+
1130
+ .ws_dialog_buttons.ame-vertical-button-list .button-primary {
1131
+ float: none;
1132
+ }
1133
+
1134
+ .ws_dialog_buttons.ame-vertical-button-list .button {
1135
+ width: 100%;
1136
+ text-align: left;
1137
+ margin-bottom: 10px;
1138
+ }
1139
+
1140
+ .ws_dialog_buttons.ame-vertical-button-list .button:last-child {
1141
+ margin-bottom: 0;
1142
+ }
1143
+
1144
+ #import_file_selector {
1145
+ display: block;
1146
+ width: 286px;
1147
+
1148
+ margin: 6px auto 12px;
1149
+ }
1150
+
1151
+ #ws_start_import {
1152
+ min-width: 100px;
1153
+ }
1154
+
1155
+ #import_complete_notice {
1156
+ text-align: center;
1157
+ font-size: large;
1158
+ padding-top: 25px;
1159
+ }
1160
+
1161
+ #ws_import_error_response {
1162
+ width: 100%;
1163
+ }
1164
+
1165
+ .ws_dont_show_again {
1166
+ display: inline-block;
1167
+ margin-top: 1em;
1168
+ }
1169
+
1170
+ /************************************
1171
+ Menu access editor
1172
+ *************************************/
1173
+
1174
+ /* The launch button */
1175
+ #ws_menu_editor .ws_edit_field-access_level input.ws_field_value
1176
+ {
1177
+ width: 190px;
1178
+ margin-right: 5px;
1179
+ }
1180
+
1181
+ .ws_launch_access_editor {
1182
+ min-width: 40px;
1183
+ }
1184
+
1185
+ #ws_menu_access_editor {
1186
+ width: 400px;
1187
+ display: none;
1188
+ }
1189
+
1190
+ .ws_dialog_subpanel {
1191
+ margin-bottom: 1em;
1192
+ }
1193
+
1194
+ #ws_menu_access_editor .ws_column_access,
1195
+ #ws_menu_access_editor .ws_ext_action_check_column {
1196
+ text-align: center;
1197
+ width: 1em;
1198
+ padding-right: 0;
1199
+ }
1200
+
1201
+ #ws_menu_access_editor .ws_column_access input,
1202
+ #ws_menu_access_editor .ws_ext_action_check_column input {
1203
+ margin-right: 0;
1204
+ }
1205
+
1206
+ #ws_menu_access_editor .ws_column_role {
1207
+ white-space: nowrap;
1208
+ }
1209
+
1210
+ #ws_role_table_body_container {
1211
+ /*max-height: 400px;
1212
+ overflow: auto;*/
1213
+ overflow: hidden;
1214
+ margin-right: -1px;
1215
+ }
1216
+
1217
+ .ws_role_table_body {
1218
+ margin-top: 2px;
1219
+ max-width: 354px;
1220
+ }
1221
+
1222
+ .ws_has_separate_header .ws_role_table_header {
1223
+ border-bottom: none;
1224
+
1225
+ -moz-border-radius-bottomleft: 0;
1226
+ -moz-border-radius-bottomright: 0;
1227
+ -webkit-border-bottom-left-radius: 0;
1228
+ -webkit-border-bottom-right-radius: 0;
1229
+ border-bottom-left-radius: 0;
1230
+ border-bottom-right-radius: 0;
1231
+ }
1232
+
1233
+ .ws_has_separate_header .ws_role_table_body {
1234
+ border-top: none;
1235
+ margin-top: 0;
1236
+
1237
+ -moz-border-radius-topleft: 0;
1238
+ -moz-border-radius-topright: 0;
1239
+ -webkit-border-top-left-radius: 0;
1240
+ -webkit-border-top-right-radius: 0;
1241
+ border-top-left-radius: 0;
1242
+ border-top-right-radius: 0;
1243
+ }
1244
+
1245
+ .ws_role_id {
1246
+ display: none;
1247
+ }
1248
+
1249
+ #ws_extra_capability {
1250
+ width: 100%;
1251
+ }
1252
+
1253
+ #ws_role_access_container {
1254
+ position: relative;
1255
+ max-height: 430px;
1256
+ overflow: auto;
1257
+ }
1258
+
1259
+ #ws_role_access_overlay {
1260
+ width: 100%;
1261
+ height: 100%;
1262
+ position: absolute;
1263
+
1264
+ line-height: 100%;
1265
+
1266
+ background: white;
1267
+ filter: alpha(opacity=60);
1268
+ opacity: 0.6;
1269
+ -moz-opacity:0.6;
1270
+ -khtml-opacity: 0.6;
1271
+ }
1272
+
1273
+ #ws_role_access_overlay_content {
1274
+ position: absolute;
1275
+ width: 50%;
1276
+ left: 22%;
1277
+ top: 30%;
1278
+
1279
+ background: white;
1280
+ padding: 8px;
1281
+
1282
+ border: 2px solid silver;
1283
+ border-radius: 5px;
1284
+ color: #555;
1285
+ }
1286
+
1287
+ #ws_menu_access_editor div.error {
1288
+ margin-left: 0;
1289
+ margin-right: 0;
1290
+ margin-bottom: 5px;
1291
+ }
1292
+
1293
+ #ws_hardcoded_role_error {
1294
+ display: none;
1295
+ }
1296
+
1297
+ /*--------------------------------------------*
1298
+ The CPT/taxonomy permissions panel
1299
+ *--------------------------------------------*/
1300
+
1301
+ /*
1302
+ * When there are CPT/taxonomy permissions available, the appearance of the role list changes a bit.
1303
+ */
1304
+ .ws_has_extended_permissions {
1305
+ //The role name column also functions as a select button.
1306
+ .ws_role_table_body .ws_column_role {
1307
+ cursor: pointer;
1308
+ }
1309
+
1310
+ .ws_role_table_body .ws_column_selected_role_tip {
1311
+ display: table-cell;
1312
+ }
1313
+
1314
+ .ws_role_table_body tr:hover {
1315
+ background: #EAF2FA;
1316
+ }
1317
+
1318
+ .ws_role_table_body {
1319
+ td {
1320
+ border-top: 1px solid #f1f1f1;
1321
+ }
1322
+ tr:first-child td {
1323
+ border-top-width: 0;
1324
+ }
1325
+ }
1326
+
1327
+ /* The role or actor whose CPT/taxonomy permissions are currently expanded. */
1328
+ .ws_role_table_body tr.ws_cpt_selected_role {
1329
+ background-color: #dddddd;
1330
+
1331
+ .ws_column_role {
1332
+ font-weight: bold;
1333
+ }
1334
+
1335
+ .ws_cpt_selected_role_tip {
1336
+ visibility: visible;
1337
+ }
1338
+
1339
+ td {
1340
+ color: #222;
1341
+ }
1342
+ }
1343
+ }
1344
+
1345
+ #ws_ext_permissions_container {
1346
+ float: left;
1347
+ width: 352px;
1348
+ padding: 0 9px 0 0;
1349
+ }
1350
+
1351
+ $extendedPanelLeftPadding: 15px;
1352
+
1353
+ #ws_ext_permissions_container_caption {
1354
+ padding-left: $extendedPanelLeftPadding;
1355
+ max-width: 352px;
1356
+ position: relative;
1357
+ white-space: nowrap;
1358
+ }
1359
+
1360
+ #ws_ext_permissions_container .ws_ext_permissions_table {
1361
+ margin-top: 2px;
1362
+
1363
+ tr td:first-child {
1364
+ padding-left: $extendedPanelLeftPadding;
1365
+ }
1366
+
1367
+ .ws_ext_group_title {
1368
+ padding-bottom: 0;
1369
+ font-weight: bold;
1370
+ }
1371
+
1372
+ .ws_ext_action_check_column,
1373
+ .ws_ext_action_name_column {
1374
+ padding-top: 3px;
1375
+ padding-bottom: 3px;
1376
+ }
1377
+
1378
+ tr.ws_ext_padding_row td {
1379
+ padding: 0 0 0 0;
1380
+ height: 1px;
1381
+ }
1382
+
1383
+ .ws_same_as_required_cap {
1384
+ text-decoration: underline;
1385
+ }
1386
+
1387
+ .ws_ext_has_custom_setting {
1388
+ label.ws_ext_action_name::after {
1389
+ content: " *";
1390
+ }
1391
+ }
1392
+ }
1393
+
1394
+ #ws_ext_permissions_container {
1395
+ //Toggle between readable names and capabilities.
1396
+ //The default is to show readable names (toggle = off), and the alternative is to show capabilities (toggle = on).
1397
+ #ws_ext_toggle_capability_names {
1398
+ cursor: pointer;
1399
+ position: absolute;
1400
+ right: 0;
1401
+ color: #0073aa;
1402
+ }
1403
+
1404
+ &.ws_ext_readable_names_enabled #ws_ext_toggle_capability_names {
1405
+ color: #b4b9be;
1406
+ }
1407
+
1408
+ //State: Show capabilities.
1409
+ .ws_ext_readable_name {
1410
+ display: none;
1411
+ }
1412
+ .ws_ext_capability {
1413
+ display: inline;
1414
+ }
1415
+
1416
+ //State: Show readable names. This is the plugin default.
1417
+ &.ws_ext_readable_names_enabled {
1418
+ .ws_ext_readable_name {
1419
+ display: inline;
1420
+ }
1421
+ .ws_ext_capability {
1422
+ display: none;
1423
+ }
1424
+ }
1425
+ }
1426
+
1427
+ //The taxonomy table doesn't have capability groups (they're not needed - there are only 4 taxonomy permissions),
1428
+ //so its first row needs a bit of extra padding to align vertically with the first role.
1429
+ #ws_ext_permissions_container #ws_taxonomy_permissions_table {
1430
+ tr:first-child td {
1431
+ padding-top: 8px;
1432
+ }
1433
+ }
1434
+
1435
+ /* The "selected role" indicator. */
1436
+ .ws_cpt_selected_role_tip {
1437
+ display: block;
1438
+ visibility: hidden;
1439
+
1440
+ box-sizing: border-box;
1441
+ width: 26px;
1442
+ height: 26px;
1443
+
1444
+ position: absolute;
1445
+ right: 0;
1446
+
1447
+ //border: 1px solid #E5E5E5;
1448
+ background: white;
1449
+
1450
+ transform: translate(1px, 0) rotate(-45deg);
1451
+ transform-origin: top right;
1452
+ }
1453
+
1454
+ .ws_role_table_body .ws_column_selected_role_tip {
1455
+ display: none;
1456
+
1457
+ padding: 0;
1458
+ width: 40px;
1459
+ height: 100%;
1460
+ text-align: right;
1461
+ overflow: visible;
1462
+
1463
+ position: relative;
1464
+
1465
+ cursor: pointer;
1466
+ }
1467
+
1468
+ .ws_ame_breadcrumb_separator {
1469
+ color: #999;
1470
+ }
1471
+
1472
+ //The "additional permissions available" notification for individual menu items.
1473
+ #ws_menu_editor .ws_ext_permissions_indicator {
1474
+ $indicatorSize: 16px;
1475
+
1476
+ font-size: $indicatorSize;
1477
+ height: $indicatorSize;
1478
+ width: $indicatorSize;
1479
+
1480
+ //Only show when an actor is selected.
1481
+ visibility: hidden;
1482
+
1483
+ vertical-align: bottom;
1484
+ cursor: pointer;
1485
+ color: #4aa100; //Derived from the border-color of an .updated notice. Lab color space, reduced lightness.
1486
+ }
1487
+
1488
+ #ws_menu_editor.ws_is_actor_view .ws_ext_permissions_indicator {
1489
+ visibility: visible;
1490
+ }
1491
+
1492
+ /************************************
1493
+ Visible users dialog
1494
+ *************************************/
1495
+
1496
+ $userSelectionPanelWidth: 350px;
1497
+ $userSelectionPanelHeight: 400px;
1498
+ $userSelectionPanelPadding: 10px;
1499
+
1500
+ #ws_visible_users_dialog {
1501
+ $userDialogPadding: 8px;
1502
+ background: white;
1503
+ padding: $userDialogPadding;
1504
+ }
1505
+
1506
+ #ws_user_selection_panels {
1507
+ min-width: $userSelectionPanelWidth * 2 + $userSelectionPanelPadding;
1508
+
1509
+ .ws_user_selection_panel {
1510
+ display: block;
1511
+ float: left;
1512
+ position: relative;
1513
+
1514
+ -webkit-box-sizing: border-box;
1515
+ -moz-box-sizing: border-box;
1516
+ box-sizing: border-box;
1517
+
1518
+ width: $userSelectionPanelWidth;
1519
+ height: $userSelectionPanelHeight;
1520
+
1521
+ border: 1px solid #e5e5e5;
1522
+ margin-right: 10px;
1523
+ padding: 10px;
1524
+ }
1525
+
1526
+ #ws_user_selection_target_panel {
1527
+ margin-right: 0;
1528
+ }
1529
+
1530
+ #ws_available_user_query {
1531
+ -webkit-box-sizing: border-box;
1532
+ -moz-box-sizing: border-box;
1533
+ box-sizing: border-box;
1534
+ width: 100%;
1535
+ max-height: 28px;
1536
+ }
1537
+
1538
+ .ws_user_list_wrapper {
1539
+ position: absolute;
1540
+ $userListTop: 50px;
1541
+
1542
+ top: $userListTop;
1543
+ left: $userSelectionPanelPadding;
1544
+ right: $userSelectionPanelPadding;
1545
+
1546
+ //width: $userSelectionPanelWidth - 2 * $userSelectionPanelPadding - 2px;
1547
+ height: $userSelectionPanelHeight - $userListTop - $userSelectionPanelPadding - 2px;
1548
+
1549
+ overflow-x: auto;
1550
+ overflow-y: auto; //Allow scrolling.
1551
+ }
1552
+
1553
+ .ws_user_selection_list {
1554
+ min-height: 20px;
1555
+
1556
+ //No borders. The panels themselves already have borders.
1557
+ border-width: 0;
1558
+ -webkit-box-shadow: none;
1559
+ -moz-box-shadow: none;
1560
+ box-shadow: none;
1561
+
1562
+ .ws_user_action_column {
1563
+ width: 20px;
1564
+ text-align: center;
1565
+
1566
+ padding-top: 9px;
1567
+ padding-bottom: 0;
1568
+ }
1569
+
1570
+ .ws_user_action_button {
1571
+ cursor: pointer;
1572
+ color: #b4b9be;
1573
+ }
1574
+
1575
+ .ws_user_username_column {
1576
+ padding-left: 0;
1577
+ }
1578
+
1579
+ .ws_user_display_name_column {
1580
+ white-space: nowrap;
1581
+ }
1582
+ }
1583
+
1584
+ #ws_available_users {
1585
+ tr {
1586
+ cursor: pointer;
1587
+ }
1588
+
1589
+ tr:hover, tr.ws_user_best_match {
1590
+ background-color: #eaf2fa;
1591
+ }
1592
+
1593
+ //The "add user" button.
1594
+ tr:hover .ws_user_action_button {
1595
+ color: #7ad03a;
1596
+ }
1597
+ }
1598
+
1599
+ #ws_selected_users {
1600
+ //The "remove from list" button.
1601
+ .ws_user_action_button::before {
1602
+ content: "\f158";
1603
+ }
1604
+ .ws_user_action_button:hover {
1605
+ color: #dd3d36;
1606
+ }
1607
+ .ws_user_action_column {
1608
+ padding-left: 6px;
1609
+ }
1610
+
1611
+ .ws_user_display_name_column {
1612
+ display: none;
1613
+ }
1614
+
1615
+ //You can't deselect the current user. It always stays in the actor list.
1616
+ tr.ws_is_current_user {
1617
+ .ws_user_action_button {
1618
+ display: none;
1619
+ }
1620
+ }
1621
+
1622
+ td {
1623
+ //padding-bottom: 8px;
1624
+ }
1625
+ tr:not(:first-child) td {
1626
+ //padding-top: 0;
1627
+ }
1628
+
1629
+ }
1630
+
1631
+ #ws_selected_users_caption {
1632
+ font-size: 14px;
1633
+ line-height: 1.4em;
1634
+ padding: 7px 10px;
1635
+
1636
+ color: #555;
1637
+ font-weight: 600;
1638
+ }
1639
+
1640
+ &::after {
1641
+ display: block;
1642
+ height: 1px;
1643
+ visibility: hidden;
1644
+ content: ' ';
1645
+ clear: both;;
1646
+ }
1647
+ }
1648
+
1649
+ #ws_loading_users_indicator {
1650
+ position: absolute;
1651
+ right: $userSelectionPanelPadding;
1652
+ bottom: $userSelectionPanelPadding;
1653
+
1654
+ margin-right: 0;
1655
+ margin-bottom: 0;
1656
+ }
1657
+
1658
+
1659
+
1660
+ /************************************
1661
+ Menu deletion error
1662
+ *************************************/
1663
+
1664
+ #ws-ame-menu-deletion-error {
1665
+ max-width: 400px;
1666
+ }
1667
+
1668
+
1669
+
1670
+
1671
+ /************************************
1672
+ Tooltips and hints
1673
+ *************************************/
1674
+
1675
+ .ws_tooltip_trigger {
1676
+ cursor: pointer;
1677
+ }
1678
+
1679
+ .ws_tooltip_content_list {
1680
+ list-style: disc;
1681
+ margin-left: 1em;
1682
+ margin-bottom: 0;
1683
+ }
1684
+
1685
+ .ws_tooltip_node {
1686
+ font-size: 13px;
1687
+ line-height: 1.3;
1688
+ border-radius: 3px;
1689
+ max-width: 300px;
1690
+ }
1691
+
1692
+ //Tooltips on the settings page.
1693
+ #ws_plugin_settings_form .ws_tooltip_trigger .dashicons {
1694
+ font-size: 18px;
1695
+ }
1696
+
1697
+ .ws_wide_tooltip {
1698
+ max-width: 450px;
1699
+ }
1700
+
1701
+ .ws_hint {
1702
+ background: #FFFFE0;
1703
+ border: 1px solid #E6DB55;
1704
+
1705
+ margin-bottom: 0.5em;
1706
+ border-radius: 3px;
1707
+ position: relative;
1708
+ padding-right: 20px;
1709
+ }
1710
+
1711
+ .ws_hint_close {
1712
+ border: 1px solid #E6DB55;
1713
+ border-right: none;
1714
+ border-top: none;
1715
+ color: #dcc500;
1716
+ font-weight: bold;
1717
+ cursor: pointer;
1718
+
1719
+ width: 18px;
1720
+ text-align: center;
1721
+ border-radius: 3px;
1722
+
1723
+ position: absolute;
1724
+ right: 0;
1725
+ top: 0;
1726
+ }
1727
+
1728
+ .ws_hint_close:hover {
1729
+ background-color: #ffef4c;
1730
+ border-color: #e0b900;
1731
+ color: black;
1732
+ }
1733
+
1734
+ .ws_hint_content {
1735
+ padding: 0.4em 0 0.4em 0.4em;
1736
+ }
1737
+
1738
+ .ws_hint_content ul {
1739
+ list-style: disc;
1740
+ list-style-position: inside;
1741
+ margin-left: 0.5em;
1742
+ }
1743
+
1744
+
1745
+ /************************************
1746
+ Copy Permissions dialog
1747
+ *************************************/
1748
+ #ws-ame-copy-permissions-dialog select {
1749
+ min-width: 280px;
1750
+ }
1751
+
1752
+
1753
+
1754
+ #ws_sidebar_pro_ad {
1755
+ min-width: 225px;
1756
+
1757
+ margin-top: 5px;
1758
+ margin-left: 3px;
1759
+
1760
+ position: fixed;
1761
+ right: 20px;
1762
+ bottom: 40px;
1763
+ z-index: 100;
1764
+ }
1765
+
1766
+
1767
+ .test-wrap {
1768
+ background-color: #444444;
1769
+ padding: 30px;
1770
+ }
1771
+
1772
+ .test-container {
1773
+ width: 400px;
1774
+ height: 200px;
1775
+ background-color: white;
1776
+
1777
+ border: 1px solid black;
1778
+ border-radius: 10px;
1779
+
1780
+ overflow: hidden;
1781
+ }
1782
+
1783
+ .test-header {
1784
+ background-color: #67d6ff;
1785
+ padding: 6px;
1786
+
1787
+ border-top-left-radius: 8px;
1788
+ border-top-right-radius: 8px;
1789
+ }
1790
+
1791
+ .test-content {
1792
+ padding: 8px;
1793
+ }
css/style-classic.css CHANGED
@@ -1,3 +1,7 @@
 
 
 
 
1
  .ws_container {
2
  border: 1px solid #a9badb;
3
  background-color: #bdd3ff;
@@ -19,7 +23,7 @@
19
  .ws_edit_link {
20
  background-image: url('../images/bullet_arrow_down2.png');
21
  background-repeat: no-repeat;
22
- background-position: center;
23
  }
24
 
25
  a.ws_edit_link:hover {
@@ -29,11 +33,11 @@ a.ws_edit_link:hover {
29
 
30
  .ws_edit_link:active {
31
  background-repeat: no-repeat;
32
- background-position: center;
33
  }
34
 
35
  .ws_edit_link_expanded {
36
- background: #ffffd0 url('../images/bullet_arrow_down2.png') center 3px no-repeat;
37
  border-bottom: none;
38
  border-color: #ffffd0;
39
 
@@ -107,11 +111,30 @@ a.ws_button:hover {
107
  }
108
 
109
  .ui-dialog-titlebar-close {
110
- background: #86A7E3 url(../images/x.png) no-repeat center;
111
- color: white;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
112
  }
113
 
114
  .ui-dialog-titlebar-close:hover {
115
- /*background-image: url(../images/x-light.png);*/
116
- background-color: #a6c2f5;
117
  }
1
+ .ws_main_container {
2
+ padding-bottom: 4px;
3
+ }
4
+
5
  .ws_container {
6
  border: 1px solid #a9badb;
7
  background-color: #bdd3ff;
23
  .ws_edit_link {
24
  background-image: url('../images/bullet_arrow_down2.png');
25
  background-repeat: no-repeat;
26
+ background-position: center 3px;
27
  }
28
 
29
  a.ws_edit_link:hover {
33
 
34
  .ws_edit_link:active {
35
  background-repeat: no-repeat;
36
+ background-position: center 3px;
37
  }
38
 
39
  .ws_edit_link_expanded {
40
+ background-color: #ffffd0;
41
  border-bottom: none;
42
  border-color: #ffffd0;
43
 
111
  }
112
 
113
  .ui-dialog-titlebar-close {
114
+ background-color: transparent;
115
+ border-style: none;
116
+
117
+ color: white; /* WP default: #666; */
118
+ cursor: pointer;
119
+ padding: 0;
120
+ position: absolute;
121
+ top: 0;
122
+ right: 0;
123
+ width: 36px;
124
+ height: 30px;
125
+ text-align: center;
126
+ }
127
+
128
+ .ui-dialog-titlebar-close::before {
129
+ font: normal 20px/30px 'dashicons';
130
+ content: '\f158';
131
+
132
+ vertical-align: top;
133
+ width: 36px;
134
+ height: 30px;
135
  }
136
 
137
  .ui-dialog-titlebar-close:hover {
138
+ background: transparent none;
139
+ color: #004665;
140
  }
css/style-modern-one.css ADDED
@@ -0,0 +1,182 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /*
2
+ //Alternative: like the "invalid" state in the menu customizer.
3
+ $hiddenItemBackground: #f6c9cc;
4
+ $hiddenItemBorder: #f1acb1;
5
+ //*/
6
+ .ws_container {
7
+ border: 0 solid transparent;
8
+ background: #fafafa;
9
+ -webkit-box-sizing: border-box;
10
+ -moz-box-sizing: border-box;
11
+ box-sizing: border-box;
12
+ width: 298px;
13
+ padding: 0;
14
+ margin-top: 0;
15
+ margin-bottom: 9px;
16
+ box-shadow: 0 1px 1px rgba(0, 0, 0, 0.04); }
17
+ .ws_container .ws_item_title {
18
+ color: #23282D;
19
+ padding-left: 0;
20
+ font-weight: 600;
21
+ font-size: 13px; }
22
+ .ws_container .ws_item_head {
23
+ border: 1px solid #dfdfdf;
24
+ padding: 7px 0 7px 7px; }
25
+ .ws_container .ws_flag_container .ws_custom_actor_permissions_flag,
26
+ .ws_container .ws_flag_container .ws_custom_flag {
27
+ display: none; }
28
+
29
+ #ws_menu_editor.ws_is_actor_view input[type="checkbox"].ws_actor_access_checkbox {
30
+ margin-right: 5px; }
31
+
32
+ .ws_editbox {
33
+ background: white;
34
+ padding: 7px;
35
+ border: 1px solid #dfdfdf;
36
+ border-top-width: 0;
37
+ -webkit-border-radius: 0;
38
+ -moz-border-radius: 0;
39
+ border-radius: 0; }
40
+
41
+ .ws_edit_link {
42
+ background: transparent;
43
+ color: #A0A5AA;
44
+ text-align: center;
45
+ border-radius: 0; }
46
+ .ws_edit_link::before {
47
+ content: "\f140";
48
+ font: normal 20px/1 dashicons;
49
+ display: block; }
50
+ .ws_edit_link:hover {
51
+ color: #777; }
52
+
53
+ .ws_edit_link.ws_edit_link_expanded::before {
54
+ content: "\f142"; }
55
+
56
+ .ws_toolbar .ws_button {
57
+ border: 1px solid #C0C0C0; }
58
+
59
+ .ws_box {
60
+ margin-top: 6px; }
61
+
62
+ .ws_menu_separator .ws_item_head::after {
63
+ content: '';
64
+ display: inline-block;
65
+ -webkit-box-sizing: border-box;
66
+ -moz-box-sizing: border-box;
67
+ box-sizing: border-box;
68
+ vertical-align: middle;
69
+ width: 244.36px;
70
+ height: 0;
71
+ border: 2px inset rgba(0, 0, 0, 0.2); }
72
+ .ws_menu_separator .ws_item_title {
73
+ width: 0;
74
+ padding-left: 0;
75
+ padding-right: 0; }
76
+
77
+ .ws_menu.ws_active::after {
78
+ right: -22px; }
79
+
80
+ .ws_container.ws_active, .ws_container.ws_is_hidden_for_actor.ws_active {
81
+ z-index: 2; }
82
+ .ws_container.ws_active .ws_item_head, .ws_container.ws_is_hidden_for_actor.ws_active .ws_item_head {
83
+ border-color: #999;
84
+ background-color: #c7c7c7; }
85
+ .ws_container.ws_active .ws_item_title, .ws_container.ws_is_hidden_for_actor.ws_active .ws_item_title {
86
+ color: #23282D; }
87
+ .ws_container.ws_active .ws_editbox, .ws_container.ws_is_hidden_for_actor.ws_active .ws_editbox {
88
+ border-color: #999; }
89
+
90
+ .ws_container.ws_is_hidden_for_actor .ws_item_head {
91
+ border-color: #dfdfdf;
92
+ background-color: #e3e3e3; }
93
+ .ws_container.ws_is_hidden_for_actor .ws_editbox {
94
+ border-color: #dfdfdf; }
95
+ .ws_container.ws_is_hidden_for_actor .ws_item_title {
96
+ color: #888; }
97
+
98
+ .ws_compact_layout .ws_container {
99
+ margin-top: -1px;
100
+ margin-bottom: 0;
101
+ -webkit-box-shadow: none;
102
+ -moz-box-shadow: none;
103
+ box-shadow: none; }
104
+ .ws_compact_layout .ws_container .ws_item_head {
105
+ padding-top: 4px;
106
+ padding-bottom: 4px; }
107
+ .ws_compact_layout .ws_container.ws_active {
108
+ z-index: 2; }
109
+ .ws_compact_layout .ws_container:last-child {
110
+ margin-bottom: 9px; }
111
+ .ws_compact_layout.ws_is_hidden_for_actor .ws_item_head, .ws_compact_layout.ws_is_hidden_for_actor .ws_editbox {
112
+ border-color: #dfdfdf; }
113
+ .ws_compact_layout.ws_active .ws_item_head, .ws_compact_layout.ws_active .ws_editbox {
114
+ border-color: #999; }
115
+
116
+ #ws_menu_editor #ws_toggle_editor_layout {
117
+ display: block; }
118
+
119
+ #ws_icon_selector {
120
+ z-index: 3; }
121
+
122
+ .ws_container.ui-sortable-helper {
123
+ box-shadow: 1px 3px 6px 0 rgba(1, 1, 1, 0.4); }
124
+
125
+ .ws_main_container {
126
+ width: 318px; }
127
+ .ws_main_container .ws_toolbar {
128
+ padding: 10px 10px 0; }
129
+ .ws_main_container .ws_dropzone {
130
+ margin-left: 10px;
131
+ margin-right: 10px; }
132
+
133
+ #ws_editor_sidebar {
134
+ padding: 6px 10px; }
135
+ #ws_editor_sidebar .ws_main_button {
136
+ margin-left: 0;
137
+ margin-right: 0; }
138
+
139
+ .ui-dialog {
140
+ background: white;
141
+ border: 1px solid #c0c0c0;
142
+ border-radius: 0; }
143
+
144
+ .ui-dialog-titlebar {
145
+ background-color: #fcfcfc;
146
+ border-bottom: 1px solid #dfdfdf;
147
+ height: auto;
148
+ padding: 0; }
149
+
150
+ .ui-dialog-title {
151
+ color: #444444;
152
+ font-size: 18px;
153
+ font-weight: 600;
154
+ line-height: 36px;
155
+ padding: 0 36px 0 8px;
156
+ display: block; }
157
+
158
+ .ui-dialog-titlebar-close {
159
+ background: none;
160
+ border-style: none;
161
+ color: #666;
162
+ cursor: pointer;
163
+ padding: 0;
164
+ position: absolute;
165
+ top: 0;
166
+ right: 0;
167
+ width: 36px;
168
+ height: 36px;
169
+ text-align: center; }
170
+
171
+ .ui-dialog-titlebar-close::before {
172
+ font: normal 20px/36px 'dashicons';
173
+ content: '\f158';
174
+ vertical-align: middle;
175
+ width: 36px;
176
+ height: 36px; }
177
+
178
+ .ui-dialog-titlebar-close:hover {
179
+ background: transparent none;
180
+ color: #2ea2cc; }
181
+
182
+ /*# sourceMappingURL=style-modern-one.css.map */
css/style-modern-one.scss ADDED
@@ -0,0 +1,336 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ $itemBackground: #fafafa;
2
+ $itemBorder: #dfdfdf;
3
+
4
+ //$itemBackground: #f7f7f7;
5
+ //$itemBorder: #cacaca;
6
+
7
+ $itemText: #23282D; //WordPress default.
8
+ //$itemText: #5a5a5a; //CodePress default.
9
+ //$itemText: #555; //Theme customizer widget headings.
10
+
11
+ $horizontalPadding: 7px;
12
+ $headVerticalPadding: 7px;
13
+ $itemWidth: 298px;
14
+ $itemMarginBottom: 9px;
15
+
16
+ $selectedItemBackground: #c7c7c7;
17
+ $selectedItemBorder: #999;
18
+ $selectedItemText: #23282D;
19
+
20
+ /*
21
+ //Alternative: like the "invalid" state in the menu customizer.
22
+ $hiddenItemBackground: #f6c9cc;
23
+ $hiddenItemBorder: #f1acb1;
24
+ //*/
25
+
26
+ $hiddenItemBackground: darken($itemBackground, 9);
27
+ $hiddenItemBorder: $itemBorder;
28
+ $hiddenItemText: #888; //#9a9ea5; //#82878C; //#999
29
+
30
+ .ws_container {
31
+ border: 0 solid transparent;
32
+ background: $itemBackground;
33
+
34
+ -webkit-box-sizing: border-box;
35
+ -moz-box-sizing: border-box;
36
+ box-sizing: border-box;
37
+
38
+ width: $itemWidth;
39
+ padding: 0;
40
+
41
+ margin-top: 0;
42
+ margin-bottom: $itemMarginBottom;
43
+
44
+ box-shadow: 0 1px 1px rgba(0, 0, 0, 0.04);
45
+
46
+ .ws_item_title {
47
+ color: $itemText;
48
+ padding-left: 0;
49
+ font-weight: 600;
50
+ font-size: 13px;
51
+ }
52
+
53
+ .ws_item_head {
54
+ border: 1px solid $itemBorder;
55
+ padding: $headVerticalPadding 0 $headVerticalPadding $horizontalPadding;
56
+ }
57
+
58
+ .ws_flag_container {
59
+ .ws_custom_actor_permissions_flag,
60
+ .ws_custom_flag {
61
+ display: none;
62
+ }
63
+ }
64
+ }
65
+
66
+ #ws_menu_editor.ws_is_actor_view input[type="checkbox"].ws_actor_access_checkbox {
67
+ margin-right: 5px;
68
+ }
69
+
70
+ .ws_editbox {
71
+ background: white;
72
+ padding: $horizontalPadding;
73
+
74
+ border: 1px solid $itemBorder;
75
+ border-top-width: 0;
76
+
77
+ -webkit-border-radius: 0;
78
+ -moz-border-radius: 0;
79
+ border-radius: 0;
80
+ }
81
+
82
+ .ws_edit_link {
83
+ background: transparent;
84
+ color: #A0A5AA;
85
+
86
+ text-align: center;
87
+ border-radius: 0;
88
+
89
+ &::before {
90
+ content: "\f140";
91
+ font: normal 20px/1 dashicons;
92
+ display: block;
93
+ }
94
+
95
+ &:hover {
96
+ color: #777;
97
+ }
98
+ }
99
+
100
+ .ws_edit_link.ws_edit_link_expanded::before {
101
+ content: "\f142";
102
+ }
103
+
104
+ .ws_toolbar .ws_button {
105
+ border: 1px solid #C0C0C0;
106
+ }
107
+
108
+ .ws_box {
109
+ margin-top: 6px;
110
+ }
111
+
112
+ .ws_menu_separator {
113
+ .ws_item_head {
114
+ //background: url("../images/menu-arrows.png") no-repeat ($horizontalPadding + 22px) center;
115
+ }
116
+
117
+ .ws_item_head::after {
118
+ content: '';
119
+ display: inline-block;
120
+ -webkit-box-sizing: border-box;
121
+ -moz-box-sizing: border-box;
122
+ box-sizing: border-box;
123
+
124
+ vertical-align: middle;
125
+ width: $itemWidth * 0.82;
126
+ height: 0;
127
+ $separatorColor: rgba(0, 0, 0, 0.2);
128
+
129
+ border: 2px inset $separatorColor;
130
+ }
131
+
132
+ .ws_item_title {
133
+ width: 0;
134
+ padding-left: 0;
135
+ padding-right: 0;
136
+ }
137
+ }
138
+
139
+
140
+ .ws_menu.ws_active::after {
141
+ right: -22px;
142
+ }
143
+
144
+ //==============================================
145
+ // Selected state
146
+ //==============================================
147
+ .ws_container.ws_active, .ws_container.ws_is_hidden_for_actor.ws_active {
148
+ z-index: 2;
149
+
150
+ .ws_item_head {
151
+ border-color: $selectedItemBorder;
152
+ background-color: $selectedItemBackground;
153
+ }
154
+
155
+ .ws_item_title {
156
+ color: $selectedItemText;
157
+ }
158
+
159
+ .ws_editbox {
160
+ border-color: $selectedItemBorder;
161
+ }
162
+ }
163
+
164
+ //==============================================
165
+ // Hidden state
166
+ //==============================================
167
+ .ws_container.ws_is_hidden_for_actor {
168
+ .ws_item_head {
169
+ border-color: $hiddenItemBorder;
170
+ background-color: $hiddenItemBackground;
171
+ }
172
+
173
+ .ws_editbox {
174
+ border-color: $hiddenItemBorder;
175
+ }
176
+
177
+ .ws_item_title {
178
+ color: $hiddenItemText;
179
+ }
180
+ }
181
+
182
+ //==============================================
183
+ // Compact layout option
184
+ //==============================================
185
+
186
+ .ws_compact_layout {
187
+ $compactBorderColor: $itemBorder; //Alternative: #cacaca
188
+ $compactVerticalPadding: 4px;
189
+
190
+ .ws_container {
191
+ margin-top: -1px;
192
+ margin-bottom: 0;
193
+
194
+ -webkit-box-shadow: none;
195
+ -moz-box-shadow: none;
196
+ box-shadow: none;
197
+
198
+ .ws_item_head {
199
+ padding-top: $compactVerticalPadding;
200
+ padding-bottom: $compactVerticalPadding;
201
+ }
202
+
203
+ .ws_item_title {
204
+ //Alternative: thinner, less in-your-face title text.
205
+ //font-weight: normal;
206
+ //font-size: 14px;
207
+ }
208
+
209
+ &.ws_active {
210
+ z-index: 2;
211
+ }
212
+ }
213
+
214
+ .ws_container:last-child {
215
+ margin-bottom: $itemMarginBottom;
216
+ }
217
+
218
+ &.ws_is_hidden_for_actor {
219
+ .ws_item_head, .ws_editbox {
220
+ border-color: $hiddenItemBorder;
221
+ }
222
+ }
223
+ &.ws_active {
224
+ .ws_item_head, .ws_editbox {
225
+ border-color: $selectedItemBorder;
226
+ }
227
+ }
228
+ }
229
+
230
+ #ws_menu_editor #ws_toggle_editor_layout {
231
+ display: block;
232
+ }
233
+
234
+ //==============================================
235
+ // Icon selector
236
+ //==============================================
237
+
238
+ #ws_icon_selector {
239
+ //Appear above the selected item.
240
+ z-index: 3;
241
+ }
242
+
243
+ //==============================================
244
+ // Item dragging
245
+ //==============================================
246
+
247
+ .ws_container.ui-sortable-helper {
248
+ box-shadow: 1px 3px 6px 0 rgba(1, 1, 1, 0.4);
249
+ }
250
+
251
+ //==============================================
252
+ // Columns / containers
253
+ //==============================================
254
+
255
+ $columnPadding: 10px;
256
+
257
+ .ws_main_container {
258
+ width: $itemWidth + $columnPadding * 2;
259
+
260
+ .ws_toolbar {
261
+ padding: $columnPadding $columnPadding 0;
262
+ }
263
+
264
+ .ws_dropzone {
265
+ margin-left: $columnPadding;
266
+ margin-right: $columnPadding;
267
+ }
268
+ }
269
+
270
+ #ws_editor_sidebar {
271
+ padding: ($columnPadding - 4px) $columnPadding;
272
+
273
+ .ws_main_button {
274
+ margin-left: 0;
275
+ margin-right: 0;
276
+ }
277
+ }
278
+
279
+
280
+ //==============================================
281
+ // Dialogs
282
+ //==============================================
283
+
284
+ .ui-dialog {
285
+ background: white;
286
+ border: 1px solid #c0c0c0;
287
+ border-radius: 0;
288
+ }
289
+
290
+ .ui-dialog-titlebar {
291
+ background-color: #fcfcfc;
292
+ border-bottom: 1px solid #dfdfdf;
293
+ height: auto;
294
+ padding: 0;
295
+ }
296
+
297
+ .ui-dialog-title {
298
+ color: #444444;
299
+ font-size: 18px;
300
+ font-weight: 600;
301
+ line-height: 36px;
302
+
303
+ padding: 0 36px 0 8px;
304
+ display: block;
305
+ }
306
+
307
+ .ui-dialog-titlebar-close {
308
+ background: none;
309
+ border-style: none;
310
+
311
+ color: #666;
312
+ cursor: pointer;
313
+ padding: 0;
314
+ position: absolute;
315
+ top: 0;
316
+ right: 0;
317
+
318
+ width: 36px;
319
+ height: 36px;
320
+
321
+ text-align: center;
322
+ }
323
+
324
+ .ui-dialog-titlebar-close::before {
325
+ font: normal 20px/36px 'dashicons';
326
+ content: '\f158';
327
+
328
+ vertical-align: middle;
329
+ width: 36px;
330
+ height: 36px;
331
+ }
332
+
333
+ .ui-dialog-titlebar-close:hover {
334
+ background: transparent none;
335
+ color: #2ea2cc;
336
+ }
images/font-awesome/angle-double-down.png ADDED
Binary file
images/font-awesome/eye-slash-color.png ADDED
Binary file
images/font-awesome/eye-slash.png ADDED
Binary file
images/font-awesome/readme.txt ADDED
@@ -0,0 +1,10 @@
 
 
 
 
 
 
 
 
 
 
1
+ This directory contains icons from the Font Awesome 4.4.0 icon font:
2
+ http://fortawesome.github.io/Font-Awesome/
3
+
4
+ The Font Awesome font is licensed under SIL OFL 1.1:
5
+ http://scripts.sil.org/OFL
6
+
7
+ ------------------------------------------------------------------------------
8
+
9
+ The icons have been converted to PNG and in some cases minor adjustments have
10
+ been made, such as adding a background colour or a drop shadow.
images/page-invisible.png ADDED
Binary file
images/submenu-tip.png ADDED
Binary file
includes/access-editor-dialog.php DELETED
@@ -1,85 +0,0 @@
1
- <div id="ws_menu_access_editor" title="Permissions">
2
-
3
- <div class="ws_dialog_panel">
4
- <div class="ws_hint" id="ws_hint_menu_permissions">
5
- <div class="ws_hint_close" title="Close">x</div>
6
- <div class="ws_hint_content">
7
- <strong>Hint:</strong> Individual submenus can override these settings.
8
- As a result, this menu will stay visible as long as at least one of its
9
- submenus is accessible.
10
- </div>
11
- </div>
12
-
13
- <div class="error inline" id="ws_hardcoded_role_error">
14
- <p>
15
- <strong>Note:</strong>
16
- Only users with the "<span id="ws_hardcoded_role_name">[role]</span>" role
17
- can access this menu. This restriction is hard&shy;coded in the plugin that
18
- created the menu.
19
- </p>
20
- </div>
21
-
22
- <div id="ws_role_access_container" class="ws_dialog_subpanel">
23
- <strong>Grant access</strong>
24
- <a class="ws_tooltip_trigger" title="
25
- Check a box to give that role or user the required capability and access to this menu.
26
- Clear the box to prevent access.
27
- ">[?]</a>
28
- <br>
29
-
30
- <div id="ws_role_table_body_container">
31
- <div id="ws_role_access_overlay" class="ws_hide_if_pro"></div>
32
- <div id="ws_role_access_overlay_content" class="ws_hide_if_pro">
33
- Pro only feature.
34
- Use capabilities (below) instead.
35
- </div>
36
-
37
- <table class="widefat fixed ws_role_table_body">
38
- <tbody>
39
- <!-- Table contents will be generated by JavaScript. -->
40
- </tbody>
41
- </table>
42
- </div>
43
- </div>
44
-
45
-
46
- <div id="ws_required_cap_container" class="ws_dialog_subpanel">
47
- <strong>Required capability</strong>
48
- <a class="ws_tooltip_trigger" title="
49
- This capability check is hard-coded in WordPress or the plugin that created the menu.
50
-
51
- &lt;ul class=&quot;ws_tooltip_content_list&quot;&gt;
52
- &lt;li&gt;
53
- Only roles with the this capability will be able to access this menu.
54
- &lt;li&gt;
55
- Admin Menu Editor will automatically grant the required capability to
56
- all roles you check in the &quot;Roles&quot; list.
57
- &lt;li&gt;
58
- Custom menus have no hard-coded capability requirements.
59
- &lt;/ul&gt;
60
- ">[?]</a>
61
- <br>
62
- <span id="ws_required_capability">capability_here</span>
63
- </div>
64
-
65
- <div id="ws_extra_cap_container" class="ws_dialog_subpanel">
66
- <label for="ws_extra_capability">
67
- <strong>Extra capability</strong>
68
- </label>
69
- <a class="ws_tooltip_trigger" title="
70
- Optional. An additional capability check that will be applied on top of
71
- the &quot;Roles&quot; and &quot;Required capability&quot; settings.
72
- Leave empty to disable.
73
- ">[?]</a>
74
- <br>
75
- <input type="text" id="ws_extra_capability" class="ws_has_dropdown" value=""><input type="button" id="ws_trigger_capability_dropdown" value="&#9660;"
76
- class="button ws_dropdown_button" tabindex="-1">
77
- </div>
78
- </div>
79
-
80
- <div class="ws_dialog_buttons">
81
- <input type="button" class="button-primary" value="Save Changes" id="ws_save_access_settings">
82
- <input type="button" class="button ws_close_dialog" value="Cancel">
83
- </div>
84
-
85
- </div>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
includes/consistency-check.php CHANGED
@@ -66,7 +66,7 @@ $requiredFiles = array(
66
  'images/spinner.gif',
67
  'includes/editor-page.php',
68
  'includes/menu-editor-core.php',
69
- 'includes/access-editor-dialog.php',
70
  'includes/menu-item.php',
71
  'menu-editor.php',
72
  'uninstall.php',
66
  'images/spinner.gif',
67
  'includes/editor-page.php',
68
  'includes/menu-editor-core.php',
69
+ 'modules/access-editor/access-editor-template.php',
70
  'includes/menu-item.php',
71
  'menu-editor.php',
72
  'uninstall.php',
includes/editor-page.php CHANGED
@@ -4,22 +4,33 @@
4
  */
5
  $current_user = wp_get_current_user();
6
  $images_url = $editor_data['images_url'];
 
 
 
 
7
 
8
  $icons = array(
9
  'cut' => '/gnome-icon-theme/edit-cut-blue.png',
10
  'copy' => '/gion/edit-copy.png',
11
  'paste' => '/gnome-icon-theme/edit-paste.png',
12
- 'hide' => '/icon-extension-grey.png',
 
13
  'new' => '/page-add.png',
14
  'delete' => '/page-delete.png',
15
  'new-separator' => '/separator-add.png',
16
  'toggle-all' => '/check-all.png',
17
  'copy-permissions' => '/copy-permissions.png',
 
 
 
18
  );
19
  foreach($icons as $name => $url) {
20
  $icons[$name] = $images_url . $url;
21
  }
22
 
 
 
 
23
  //Output the "Upgrade to Pro" message
24
  if ( !apply_filters('admin_menu_editor_is_pro', false) ){
25
  ?>
@@ -37,11 +48,11 @@ if ( !apply_filters('admin_menu_editor_is_pro', false) ){
37
 
38
  ?>
39
  <div class="wrap">
40
- <h2>
41
  <?php echo apply_filters('admin_menu_editor-self_page_title', 'Menu Editor'); ?>
42
  <a href="<?php echo esc_attr($editor_data['settings_page_url']); ?>" class="add-new-h2" id="ws_plugin_settings_button"
43
  title="Configure plugin settings">Settings</a>
44
- </h2>
45
 
46
  <?php
47
  if ( !empty($_GET['message']) ){
@@ -52,14 +63,35 @@ if ( !empty($_GET['message']) ){
52
  }
53
  }
54
 
55
- include dirname(__FILE__) . '/access-editor-dialog.php';
 
56
  if ( apply_filters('admin_menu_editor_is_pro', false) ) {
57
- include dirname(__FILE__) . '/../extras/menu-color-dialog.php';
58
- include dirname(__FILE__) . '/../extras/copy-permissions-dialog.php';
 
 
 
 
 
 
 
 
 
 
 
 
59
  }
 
60
  ?>
61
 
62
- <div id='ws_menu_editor'>
 
 
 
 
 
 
 
63
  <div id="ws_actor_selector_container">
64
  <ul id="ws_actor_selector" class="subsubsub" style="display: none;">
65
  <!-- Contents will be generated by JS -->
@@ -79,18 +111,51 @@ if ( apply_filters('admin_menu_editor_is_pro', false) ) {
79
  <div class="ws_separator">&nbsp;</div>
80
 
81
  <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>
 
 
 
 
 
 
 
 
 
 
82
 
83
  <?php if ( $editor_data['show_deprecated_hide_button'] ): ?>
84
- <a id='ws_hide_menu' class='ws_button' href='javascript:void(0)' title='Show/Hide'><img src='<?php echo $icons['hide']; ?>' alt="Show/Hide" /></a>
85
  <?php endif; ?>
86
 
87
- <a id='ws_delete_menu' class='ws_button' href='javascript:void(0)' title='Delete menu'><img src='<?php echo $icons['delete']; ?>' alt="Delete menu" /></a>
88
 
89
  <div class="ws_separator">&nbsp;</div>
90
 
91
- <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>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
92
 
93
- <?php if ( apply_filters('admin_menu_editor_is_pro', false) ): ?>
94
  <div class="ws_separator">&nbsp;</div>
95
 
96
  <a id='ws_toggle_all_menus' class='ws_button' href='javascript:void(0)'
@@ -98,6 +163,8 @@ if ( apply_filters('admin_menu_editor_is_pro', false) ) {
98
 
99
  <a id='ws_copy_role_permissions' class='ws_button' href='javascript:void(0)'
100
  title='Copy all menu permissions from one role to another'><img src='<?php echo $icons['copy-permissions']; ?>' alt="Copy permissions" /></a>
 
 
101
  <?php endif; ?>
102
 
103
  <div class="clear"></div>
@@ -120,24 +187,45 @@ if ( apply_filters('admin_menu_editor_is_pro', false) ) {
120
  <div class="ws_separator">&nbsp;</div>
121
 
122
  <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>
 
 
 
 
 
 
 
 
 
 
 
 
 
123
  <?php if ( $editor_data['show_deprecated_hide_button'] ): ?>
124
- <a id='ws_hide_item' class='ws_button' href='javascript:void(0)' title='Show/Hide'><img src='<?php echo $icons['hide']; ?>' alt="Show/Hide" /></a>
125
  <?php endif; ?>
126
- <a id='ws_delete_item' class='ws_button' href='javascript:void(0)' title='Delete menu item'><img src='<?php echo $icons['delete']; ?>' alt="Delete menu item" /></a>
 
127
 
128
  <div class="ws_separator">&nbsp;</div>
129
 
130
- <?php if ( apply_filters('admin_menu_editor_is_pro', false) ): ?>
131
- <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>
132
- <div class="ws_separator">&nbsp;</div>
133
- <?php endif; ?>
 
134
 
135
- <a id='ws_sort_ascending' class='ws_button' href='javascript:void(0)' title='Sort ascending'>
136
- <img src='<?php echo $images_url; ?>/sort_ascending.png' alt="Sort ascending" />
137
- </a>
138
- <a id='ws_sort_descending' class='ws_button' href='javascript:void(0)' title='Sort descending'>
139
- <img src='<?php echo $images_url; ?>/sort_descending.png' alt="Sort descending" />
140
- </a>
 
 
 
 
 
 
141
 
142
  <div class="clear"></div>
143
  </div>
@@ -158,12 +246,24 @@ if ( apply_filters('admin_menu_editor_is_pro', false) ) {
158
  <input type="hidden" name="data" id="ws_data" value="">
159
  <input type="hidden" name="data_length" id="ws_data_length" value="">
160
  <input type="hidden" name="selected_actor" id="ws_selected_actor" value="">
 
161
  <input type="button" id='ws_save_menu' class="button-primary ws_main_button" value="Save Changes" />
162
  </form>
163
 
164
  <input type="button" id='ws_reset_menu' value="Undo changes" class="button ws_main_button" />
165
  <input type="button" id='ws_load_menu' value="Load default menu" class="button ws_main_button" />
166
 
 
 
 
 
 
 
 
 
 
 
 
167
  <?php
168
  do_action('admin_menu_editor-sidebar');
169
  ?>
@@ -446,6 +546,12 @@ if ( apply_filters('admin_menu_editor_is_pro', false) ) {
446
  </div>
447
  </div>
448
 
 
 
 
 
 
 
449
 
450
  <script type='text/javascript'>
451
  var defaultMenu = <?php echo $editor_data['default_menu_js']; ?>;
4
  */
5
  $current_user = wp_get_current_user();
6
  $images_url = $editor_data['images_url'];
7
+ $is_pro_version = apply_filters('admin_menu_editor_is_pro', false);
8
+ $is_second_toolbar_visible = isset($_COOKIE['ame-show-second-toolbar']) && (intval($_COOKIE['ame-show-second-toolbar']) === 1);
9
+ $is_compact_layout_enabled = isset($_COOKIE['ame-compact-layout']) && (intval($_COOKIE['ame-compact-layout']) === 1);
10
+ $is_multisite = is_multisite();
11
 
12
  $icons = array(
13
  'cut' => '/gnome-icon-theme/edit-cut-blue.png',
14
  'copy' => '/gion/edit-copy.png',
15
  'paste' => '/gnome-icon-theme/edit-paste.png',
16
+ 'hide' => '/page-invisible.png',
17
+ 'hide-and-deny' => '/font-awesome/eye-slash-color.png',
18
  'new' => '/page-add.png',
19
  'delete' => '/page-delete.png',
20
  'new-separator' => '/separator-add.png',
21
  'toggle-all' => '/check-all.png',
22
  'copy-permissions' => '/copy-permissions.png',
23
+ 'toggle-toolbar' => '/font-awesome/angle-double-down.png',
24
+ 'sort-ascending' => '/sort_ascending.png',
25
+ 'sort-descending' => '/sort_descending.png',
26
  );
27
  foreach($icons as $name => $url) {
28
  $icons[$name] = $images_url . $url;
29
  }
30
 
31
+ $hide_button_extra_tooltip = 'When "All" is selected, this will hide the menu from everyone except the current user'
32
+ . ($is_multisite ? ' and Super Admin' : '') . '.';
33
+
34
  //Output the "Upgrade to Pro" message
35
  if ( !apply_filters('admin_menu_editor_is_pro', false) ){
36
  ?>
48
 
49
  ?>
50
  <div class="wrap">
51
+ <?php echo '<', WPMenuEditor::$admin_heading_tag, '>'; ?>
52
  <?php echo apply_filters('admin_menu_editor-self_page_title', 'Menu Editor'); ?>
53
  <a href="<?php echo esc_attr($editor_data['settings_page_url']); ?>" class="add-new-h2" id="ws_plugin_settings_button"
54
  title="Configure plugin settings">Settings</a>
55
+ <?php echo '</', WPMenuEditor::$admin_heading_tag, '>'; ?>
56
 
57
  <?php
58
  if ( !empty($_GET['message']) ){
63
  }
64
  }
65
 
66
+ include dirname(__FILE__) . '/../modules/access-editor/access-editor-template.php';
67
+ $extrasDirectory = dirname(__FILE__) . '/../extras';
68
  if ( apply_filters('admin_menu_editor_is_pro', false) ) {
69
+ include $extrasDirectory . '/menu-color-dialog.php';
70
+ include $extrasDirectory . '/copy-permissions-dialog.php';
71
+ include $extrasDirectory . '/modules/visible-users/visible-users-template.php';
72
+ }
73
+
74
+ function ame_output_sort_buttons($icons) {
75
+ ?>
76
+ <a class='ws_button ws_sort_menus_button' data-sort-direction="asc" href='javascript:void(0)' title='Sort ascending'>
77
+ <img src='<?php echo $icons['sort-ascending']; ?>' alt="Sort ascending" />
78
+ </a>
79
+ <a class='ws_button ws_sort_menus_button' data-sort-direction="desc" href='javascript:void(0)' title='Sort descending'>
80
+ <img src='<?php echo $icons['sort-descending']; ?>' alt="Sort descending" />
81
+ </a>
82
+ <?php
83
  }
84
+
85
  ?>
86
 
87
+ <div id='ws_menu_editor' class="<?php
88
+ if ( $is_compact_layout_enabled ) {
89
+ echo 'ws_compact_layout';
90
+ } else {
91
+ echo 'ws_large_layout';
92
+ }
93
+ ?>">
94
+
95
  <div id="ws_actor_selector_container">
96
  <ul id="ws_actor_selector" class="subsubsub" style="display: none;">
97
  <!-- Contents will be generated by JS -->
111
  <div class="ws_separator">&nbsp;</div>
112
 
113
  <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>
114
+ <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>
115
+
116
+ <?php if ( $is_pro_version ): ?>
117
+ <div class="ws_separator">&nbsp;</div>
118
+
119
+ <a id='ws_hide_and_deny_menu' class='ws_button ws_hide_and_deny_button' href='javascript:void(0)'
120
+ title='Hide and prevent access. &#10;<?php echo esc_attr($hide_button_extra_tooltip); ?>'>
121
+ <img src='<?php echo $icons['hide-and-deny']; ?>' alt="Hide" />
122
+ </a>
123
+ <?php endif; ?>
124
 
125
  <?php if ( $editor_data['show_deprecated_hide_button'] ): ?>
126
+ <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>
127
  <?php endif; ?>
128
 
129
+ <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>
130
 
131
  <div class="ws_separator">&nbsp;</div>
132
 
133
+ <?php
134
+ if ( !$is_pro_version ) {
135
+ ame_output_sort_buttons($icons);
136
+ }
137
+ ?>
138
+
139
+ <?php if ( $is_pro_version ): ?>
140
+ <a id='ws_toggle_toolbar' class='ws_button' href='javascript:void(0)' title='Toggle second toolbar'>
141
+ <img src='<?php echo $icons['toggle-toolbar']; ?>' alt="Toolbar toggle" />
142
+ </a>
143
+ <?php endif; ?>
144
+
145
+ <div class="clear"></div>
146
+ </div>
147
+
148
+ <div class="ws_button_container ws_second_toolbar_row <?php
149
+ if (!$is_second_toolbar_visible) { echo ' hidden'; }
150
+ ?>">
151
+
152
+ <?php
153
+ if ( $is_pro_version ) {
154
+ ame_output_sort_buttons($icons);
155
+ }
156
+ ?>
157
 
158
+ <?php if ( $is_pro_version ): ?>
159
  <div class="ws_separator">&nbsp;</div>
160
 
161
  <a id='ws_toggle_all_menus' class='ws_button' href='javascript:void(0)'
163
 
164
  <a id='ws_copy_role_permissions' class='ws_button' href='javascript:void(0)'
165
  title='Copy all menu permissions from one role to another'><img src='<?php echo $icons['copy-permissions']; ?>' alt="Copy permissions" /></a>
166
+
167
+ <div class="ws_separator">&nbsp;</div>
168
  <?php endif; ?>
169
 
170
  <div class="clear"></div>
187
  <div class="ws_separator">&nbsp;</div>
188
 
189
  <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>
190
+ <?php if ( $is_pro_version ): ?>
191
+ <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>
192
+ <?php endif; ?>
193
+
194
+ <?php if ( $is_pro_version ): ?>
195
+ <div class="ws_separator">&nbsp;</div>
196
+
197
+ <a id='ws_hide_and_deny_item' class='ws_button ws_hide_and_deny_button' href='javascript:void(0)'
198
+ title='Hide and prevent access. &#10;<?php echo esc_attr($hide_button_extra_tooltip); ?>'>
199
+ <img src='<?php echo $icons['hide-and-deny']; ?>' alt="Hide" />
200
+ </a>
201
+ <?php endif; ?>
202
+
203
  <?php if ( $editor_data['show_deprecated_hide_button'] ): ?>
204
+ <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>
205
  <?php endif; ?>
206
+
207
+ <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>
208
 
209
  <div class="ws_separator">&nbsp;</div>
210
 
211
+ <?php
212
+ if ( !$is_pro_version ) {
213
+ ame_output_sort_buttons($icons);
214
+ }
215
+ ?>
216
 
217
+ <div class="clear"></div>
218
+ </div>
219
+
220
+ <div class="ws_button_container ws_second_toolbar_row <?php
221
+ if (!$is_second_toolbar_visible) { echo ' hidden'; }
222
+ ?>">
223
+
224
+ <?php
225
+ if ( $is_pro_version ) {
226
+ ame_output_sort_buttons($icons);
227
+ }
228
+ ?>
229
 
230
  <div class="clear"></div>
231
  </div>
246
  <input type="hidden" name="data" id="ws_data" value="">
247
  <input type="hidden" name="data_length" id="ws_data_length" value="">
248
  <input type="hidden" name="selected_actor" id="ws_selected_actor" value="">
249
+ <input type="hidden" name="visible_users" id="ws_visible_users_json" value="">
250
  <input type="button" id='ws_save_menu' class="button-primary ws_main_button" value="Save Changes" />
251
  </form>
252
 
253
  <input type="button" id='ws_reset_menu' value="Undo changes" class="button ws_main_button" />
254
  <input type="button" id='ws_load_menu' value="Load default menu" class="button ws_main_button" />
255
 
256
+ <?php
257
+ $compact_layout_title = 'Compact layout';
258
+ if ( $is_compact_layout_enabled ) {
259
+ $compact_layout_title = '&#x2713; ' . $compact_layout_title;
260
+ }
261
+ ?>
262
+ <input type="button"
263
+ id='ws_toggle_editor_layout'
264
+ value="<?php echo $compact_layout_title; ?>"
265
+ class="button ws_main_button" />
266
+
267
  <?php
268
  do_action('admin_menu_editor-sidebar');
269
  ?>
546
  </div>
547
  </div>
548
 
549
+ <?php
550
+ if ( apply_filters('admin_menu_editor_is_pro', false) ) {
551
+ include $extrasDirectory . '/page-dropdown.php';
552
+ }
553
+ ?>
554
+
555
 
556
  <script type='text/javascript'>
557
  var defaultMenu = <?php echo $editor_data['default_menu_js']; ?>;
includes/menu-editor-core.php CHANGED
@@ -17,6 +17,10 @@ require $thisDirectory . '/auto-versioning.php';
17
 
18
  class WPMenuEditor extends MenuEd_ShadowPluginFramework {
19
  const WPML_CONTEXT = 'admin-menu-editor menu texts';
 
 
 
 
20
 
21
  private $plugin_db_version = 140;
22
 
@@ -65,6 +69,7 @@ class WPMenuEditor extends MenuEd_ShadowPluginFramework {
65
  private $custom_wp_submenu = null;
66
 
67
  private $item_templates = array(); //A lookup list of default menu items, used as templates for the custom menu.
 
68
 
69
  private $cached_custom_menu = null; //Cached, non-merged version of the custom menu. Used by load_custom_menu().
70
  private $cached_virtual_caps = null;//List of virtual caps. Used by get_virtual_caps().
@@ -82,6 +87,11 @@ class WPMenuEditor extends MenuEd_ShadowPluginFramework {
82
  */
83
  private $cached_user_roles = array();
84
 
 
 
 
 
 
85
  function init(){
86
  $this->sitewide_options = true;
87
 
@@ -107,7 +117,7 @@ class WPMenuEditor extends MenuEd_ShadowPluginFramework {
107
  //The user who can see this plugin on the "Plugins" page. By default all admins can see it.
108
  'plugins_page_allowed_user_id' => null,
109
 
110
- 'show_deprecated_hide_button' => null,
111
  'dashboard_hiding_confirmation_enabled' => true,
112
 
113
  //When to show submenu icons.
@@ -116,17 +126,53 @@ class WPMenuEditor extends MenuEd_ShadowPluginFramework {
116
  //Menu editor UI colour scheme. "Classic" is the old blue/yellow scheme, and "wp-grey" is more WP-like.
117
  'ui_colour_scheme' => 'classic',
118
 
 
 
 
119
  //Enable/disable the admin notice that tells the user where the plugin settings menu is.
120
  'show_plugin_menu_notice' => true,
 
 
 
 
 
121
  );
122
  $this->serialize_with_json = false; //(Don't) store the options in JSON format
123
 
 
 
 
124
  $this->settings_link = 'options-general.php?page=menu_editor';
125
 
126
  $this->magic_hooks = true;
127
  //Run our hooks last (almost). Priority is less than PHP_INT_MAX mostly for defensive programming purposes.
128
  //Old PHP versions have known bugs related to large array keys, and WP might have undiscovered edge cases.
129
  $this->magic_hook_priority = PHP_INT_MAX - 10;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
130
 
131
  //AJAXify screen options
132
  add_action('wp_ajax_ws_ame_save_screen_options', array($this,'ajax_save_screen_options'));
@@ -138,6 +184,11 @@ class WPMenuEditor extends MenuEd_ShadowPluginFramework {
138
  array($this, 'ajax_disable_dashboard_hiding_confirmation')
139
  );
140
 
 
 
 
 
 
141
  //Make sure we have access to the original, un-mangled request data.
142
  //This is necessary because WordPress will stupidly apply "magic quotes"
143
  //to the request vars even if this PHP misfeature is disabled.
@@ -149,6 +200,9 @@ class WPMenuEditor extends MenuEd_ShadowPluginFramework {
149
  add_action('admin_enqueue_scripts', array($this, 'enqueue_helper_scripts'));
150
  add_action('admin_print_styles', array($this, 'enqueue_helper_styles'));
151
 
 
 
 
152
  //User survey
153
  add_action('admin_notices', array($this, 'display_survey_notice'));
154
 
@@ -261,9 +315,12 @@ class WPMenuEditor extends MenuEd_ShadowPluginFramework {
261
  array(&$this, 'page_menu_editor')
262
  );
263
  //Output our JS & CSS on that page only
264
- add_action("admin_print_scripts-$page", array($this, 'enqueue_scripts'));
265
  add_action("admin_print_styles-$page", array($this, 'enqueue_styles'));
266
 
 
 
 
267
  //Compatibility fix for All In One Event Calendar; see the callback for details.
268
  add_action("admin_print_scripts-$page", array($this, 'dequeue_ai1ec_scripts'));
269
 
@@ -293,7 +350,15 @@ class WPMenuEditor extends MenuEd_ShadowPluginFramework {
293
  }
294
 
295
  //Generate item templates from the default menu.
296
- $this->item_templates = $this->build_templates($this->default_wp_menu, $this->default_wp_submenu);
 
 
 
 
 
 
 
 
297
 
298
  //Add extra templates that are not part of the normal menu.
299
  $this->item_templates = $this->add_special_templates($this->item_templates);
@@ -494,6 +559,10 @@ class WPMenuEditor extends MenuEd_ShadowPluginFramework {
494
  * @return void
495
  */
496
  function enqueue_scripts(){
 
 
 
 
497
  //jQuery JSON plugin
498
  wp_register_auto_versioned_script('jquery-json', plugins_url('js/jquery.json.js', $this->plugin_file), array('jquery'));
499
  //jQuery sort plugin
@@ -505,19 +574,39 @@ class WPMenuEditor extends MenuEd_ShadowPluginFramework {
505
  //jQuery cookie plugin
506
  wp_register_auto_versioned_script('jquery-cookie', plugins_url('js/jquery.cookie.js', $this->plugin_file), array('jquery'));
507
 
 
 
 
 
 
 
 
 
 
 
 
 
 
508
  //Editor's scripts
 
 
 
 
 
 
509
  wp_register_auto_versioned_script(
510
  'menu-editor',
511
  plugins_url('js/menu-editor.js', $this->plugin_file),
512
- array(
513
- 'jquery', 'jquery-ui-sortable', 'jquery-ui-dialog',
514
- 'ame-jquery-form', 'jquery-ui-droppable', 'jquery-qtip',
515
- 'jquery-sort', 'jquery-json', 'jquery-cookie',
516
- 'wp-color-picker'
517
- )
518
  );
519
 
520
- //Add scripts to our editor page, but not the settings sub-section
 
 
 
 
 
 
521
  //that shares the same page slug. Some of the scripts would crash otherwise.
522
  if ( !$this->is_editor_page() ) {
523
  return;
@@ -548,24 +637,43 @@ class WPMenuEditor extends MenuEd_ShadowPluginFramework {
548
  $actors['special:super_admin'] = 'Super Admin';
549
  }
550
 
551
- //Known users. Right now, this is limited to the current user only.
552
  $users = array();
553
-
554
  $current_user = wp_get_current_user();
555
- $users[$current_user->get('user_login')] = array(
556
- 'user_login' => $current_user->get('user_login'),
557
- 'id' => $current_user->ID,
558
- 'roles' => array_values($this->get_user_roles($current_user)),
559
- 'capabilities' => $this->castValuesToBool($current_user->caps),
560
- 'is_super_admin' => is_multisite() && is_super_admin(),
561
- );
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
562
 
563
  $actors['user:' . $current_user->get('user_login')] = sprintf(
564
  'Current user (%s)',
565
  $current_user->get('user_login')
566
  );
567
- //Note: Users do NOT get added to the actor list because that feature
568
- //is not fully implemented.
569
 
570
  $showExtraIcons = (boolean)$this->options['show_extra_icons'];
571
  if ( isset($_COOKIE['ame-show-extra-icons']) && is_numeric($_COOKIE['ame-show-extra-icons']) ) {
@@ -573,48 +681,111 @@ class WPMenuEditor extends MenuEd_ShadowPluginFramework {
573
  }
574
 
575
  //The editor will need access to some of the plugin data and WP data.
576
- wp_localize_script(
577
- 'menu-editor',
578
- 'wsEditorData',
579
- array(
580
- 'imagesUrl' => plugins_url('images', $this->plugin_file),
581
- 'adminAjaxUrl' => admin_url('admin-ajax.php'),
582
- 'hideAdvancedSettings' => (boolean)$this->options['hide_advanced_settings'],
583
- 'showExtraIcons' => $showExtraIcons,
584
- 'submenuIconsEnabled' => $this->options['submenu_icons_enabled'],
585
-
586
- 'hideAdvancedSettingsNonce' => wp_create_nonce('ws_ame_save_screen_options'),
587
- 'dashiconsAvailable' => wp_style_is('dashicons', 'registered'),
588
- 'captionShowAdvanced' => 'Show advanced options',
589
- 'captionHideAdvanced' => 'Hide advanced options',
590
- 'wsMenuEditorPro' => false, //Will be overwritten if extras are loaded
591
- 'menuFormatName' => ameMenu::format_name,
592
- 'menuFormatVersion' => ameMenu::format_version,
593
-
594
- 'blankMenuItem' => ameMenuItem::blank_menu(),
595
- 'itemTemplates' => $this->item_templates,
596
- 'customItemTemplate' => array(
597
- 'name' => '< Custom >',
598
- 'defaults' => ameMenuItem::custom_item_defaults(),
599
- ),
600
-
601
- 'unclickableTemplateId' => ameMenuItem::unclickableTemplateId,
602
- 'unclickableTemplateClass' => ameMenuItem::unclickableTemplateClass,
603
-
604
- 'actors' => $actors,
605
- 'roles' => $roles,
606
- 'users' => $users,
607
- 'currentUserLogin' => $current_user->get('user_login'),
608
- 'selectedActor' => isset($this->get['selected_actor']) ? strval($this->get['selected_actor']) : null,
609
-
610
- 'showHints' => $this->get_hint_visibility(),
611
- 'dashboardHidingConfirmationEnabled' => $this->options['dashboard_hiding_confirmation_enabled'],
612
- 'disableDashboardConfirmationNonce' => wp_create_nonce('ws_ame_disable_dashboard_hiding_confirmation'),
613
-
614
- 'isDemoMode' => defined('IS_DEMO_MODE'),
615
- 'isMasterMode' => defined('IS_MASTER_MODE'),
616
- )
 
 
 
 
 
617
  );
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
618
  }
619
 
620
  /**
@@ -672,6 +843,11 @@ class WPMenuEditor extends MenuEd_ShadowPluginFramework {
672
  plugins_url('css/style-wp-grey.css', $this->plugin_file),
673
  array('menu-editor-base-style')
674
  );
 
 
 
 
 
675
 
676
  //WordPress introduced a new screen meta button style in WP 3.8.
677
  //We have two different stylesheets - one for 3.8+ and one for backwards compatibility.
@@ -722,7 +898,7 @@ class WPMenuEditor extends MenuEd_ShadowPluginFramework {
722
  *
723
  * @return array|null Either a menu in the internal format, or NULL if there is no custom menu available.
724
  */
725
- function load_custom_menu() {
726
  if ( $this->cached_custom_menu !== null ) {
727
  return $this->cached_custom_menu;
728
  }
@@ -805,65 +981,6 @@ class WPMenuEditor extends MenuEd_ShadowPluginFramework {
805
 
806
  return $admin_title;
807
  }
808
-
809
- /**
810
- * Populate a lookup array with default values (templates) from $menu and $submenu.
811
- * Used later to merge a custom menu with the native WordPress menu structure.
812
- *
813
- * @param array $menu
814
- * @param array $submenu
815
- * @return array An array of menu templates and their default values.
816
- */
817
- function build_templates($menu, $submenu){
818
- $templates = array();
819
-
820
- $name_lookup = array();
821
- foreach($menu as $pos => $item){
822
- $item = ameMenuItem::fromWpItem($item, $pos);
823
- if ($item['separator']) {
824
- continue;
825
- }
826
-
827
- $name = $this->sanitize_menu_title($item['menu_title']);
828
- $name_lookup[$item['file']] = $name;
829
-
830
- $templates[ameMenuItem::template_id($item)] = array(
831
- 'name' => $name,
832
- 'used' => false,
833
- 'defaults' => $item
834
- );
835
- }
836
-
837
- foreach($submenu as $parent => $items){
838
- //Skip sub-menus attached to non-existent parents. This should theoretically never happen,
839
- //but a buggy plugin can cause such a situation.
840
- if ( !isset($name_lookup[$parent]) ) {
841
- continue;
842
- }
843
-
844
- foreach($items as $pos => $item){
845
- $item = ameMenuItem::fromWpItem($item, $pos, $parent);
846
- $templates[ameMenuItem::template_id($item)] = array(
847
- 'name' => $name_lookup[$parent] . ' -> ' . $this->sanitize_menu_title($item['menu_title']),
848
- 'used' => false,
849
- 'defaults' => $item
850
- );
851
- }
852
- }
853
-
854
- return $templates;
855
- }
856
-
857
- /**
858
- * Sanitize a menu title for display.
859
- * Removes HTML tags and update notification bubbles.
860
- *
861
- * @param string $title
862
- * @return string
863
- */
864
- private function sanitize_menu_title($title) {
865
- return strip_tags( preg_replace('@<span[^>]*>.*</span>@i', '', $title) );
866
- }
867
 
868
  /**
869
  * Generate special menu templates and add them to the input template list.
@@ -889,6 +1006,20 @@ class WPMenuEditor extends MenuEd_ShadowPluginFramework {
889
  'defaults' => $unclickableDefaults,
890
  );
891
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
892
  if ( $this->is_pro_version() ) {
893
  //The Pro version has a [wp-logout-url] shortcode. Lets make it easier o use
894
  //by adding it to the "Target page" dropdown.
@@ -924,8 +1055,19 @@ class WPMenuEditor extends MenuEd_ShadowPluginFramework {
924
  //Iterate over all menus and submenus and look up default values
925
  //Also flag used and missing items.
926
  $orphans = array();
 
 
 
 
 
 
927
  foreach ($tree as &$topmenu){
928
 
 
 
 
 
 
929
  if ( !ameMenuItem::get($topmenu, 'custom') ) {
930
  $template_id = ameMenuItem::template_id($topmenu);
931
  //Is this menu present in the default WP menu?
@@ -934,6 +1076,9 @@ class WPMenuEditor extends MenuEd_ShadowPluginFramework {
934
  $topmenu['defaults'] = $this->item_templates[$template_id]['defaults'];
935
  //Note that the original item was used
936
  $this->item_templates[$template_id]['used'] = true;
 
 
 
937
  } else {
938
  //Record the menu as missing, unless it's a menu separator
939
  if ( empty($topmenu['separator']) ){
@@ -943,6 +1088,7 @@ class WPMenuEditor extends MenuEd_ShadowPluginFramework {
943
  $temp = $this->set_final_menu_capability($temp);
944
  $this->add_access_lookup($temp, 'menu', true);
945
  }
 
946
  }
947
  }
948
 
@@ -957,6 +1103,8 @@ class WPMenuEditor extends MenuEd_ShadowPluginFramework {
957
  //Yes, load defaults from that item
958
  $item['defaults'] = $this->item_templates[$template_id]['defaults'];
959
  $this->item_templates[$template_id]['used'] = true;
 
 
960
  //We must move orphaned items elsewhere. Use the default location if possible.
961
  if ( isset($topmenu['missing']) && $topmenu['missing'] ) {
962
  $orphans[] = $item;
@@ -986,6 +1134,7 @@ class WPMenuEditor extends MenuEd_ShadowPluginFramework {
986
  $tree = ameMenu::remove_missing_items($tree);
987
 
988
  //Lets merge in the unused items.
 
989
  foreach ($this->item_templates as $template_id => $template){
990
  //Skip used menus and separators
991
  if ( !empty($template['used']) || !empty($template['defaults']['separator'])) {
@@ -998,6 +1147,43 @@ class WPMenuEditor extends MenuEd_ShadowPluginFramework {
998
  $entry['defaults'] = $template['defaults'];
999
  $entry['unused'] = true; //Note that this item is unused
1000
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1001
  //Add the new entry to the menu tree
1002
  if ( !empty($template['defaults']['parent']) ) {
1003
  if (isset($tree[$template['defaults']['parent']])) {
@@ -1027,6 +1213,9 @@ class WPMenuEditor extends MenuEd_ShadowPluginFramework {
1027
  //Resort the tree to ensure the found items are in the right spots
1028
  $tree = ameMenu::sort_menu_tree($tree);
1029
 
 
 
 
1030
  return $tree;
1031
  }
1032
 
@@ -1100,11 +1289,6 @@ class WPMenuEditor extends MenuEd_ShadowPluginFramework {
1100
  $first_nonseparator_found = false;
1101
  foreach ($tree as $topmenu){
1102
 
1103
- //Skip missing and hidden menus.
1104
- if ( !empty($topmenu['missing']) || !empty($topmenu['hidden']) ) {
1105
- continue;
1106
- }
1107
-
1108
  //Skip leading menu separators. Fixes a superfluous separator showing up
1109
  //in WP 3.0 (multisite mode) when there's a custom menu and the current user
1110
  //can't access its first item ("Super Admin").
@@ -1121,34 +1305,19 @@ class WPMenuEditor extends MenuEd_ShadowPluginFramework {
1121
 
1122
  //Prepare the submenu of this menu
1123
  $new_items = array();
1124
- $has_submenu_icons = false;
1125
  if( !empty($topmenu['items']) ){
1126
  $items = $topmenu['items'];
1127
 
1128
  foreach ($items as $item) {
1129
- //Skip missing and hidden items
1130
- if ( !empty($item['missing']) || !empty($item['hidden']) ) {
1131
- continue;
1132
- }
1133
-
1134
- $item = $this->prepare_for_output($item, 'submenu', $topmenu['file']);
1135
  $new_items[] = $item;
1136
 
1137
  //Make a note of the page's correct title so we can fix it later if necessary.
1138
  $this->title_lookups[$item['file']] = !empty($item['page_title']) ? $item['page_title'] : $item['menu_title'];
1139
-
1140
- //Keep track of which menus have items with icons.
1141
- $has_submenu_icons = $has_submenu_icons || !empty($item['has_submenu_icon']);
1142
  }
1143
 
1144
  //Sort by position
1145
- uasort($new_items, 'ameMenuItem::compare_position');
1146
- }
1147
-
1148
- //The ame-has-submenu-icons class lets us change the appearance of all submenu items at once,
1149
- //without having to add classes/styles to each item individually.
1150
- if ( $has_submenu_icons && (strpos($topmenu['css_class'], 'ame-has-submenu-icons') === false) ) {
1151
- $topmenu['css_class'] .= ' ame-has-submenu-icons';
1152
  }
1153
 
1154
  $topmenu['items'] = $new_items;
@@ -1184,6 +1353,7 @@ class WPMenuEditor extends MenuEd_ShadowPluginFramework {
1184
  $this->reverse_item_lookup[$topmenu['url']] = $topmenu;
1185
  }
1186
 
 
1187
  foreach($topmenu['items'] as $item) {
1188
  $trueAccess = isset($this->page_access_lookup[$item['url']]) ? $this->page_access_lookup[$item['url']] : null;
1189
  if ( ($trueAccess === 'do_not_allow') && ($item['access_level'] !== $trueAccess) ) {
@@ -1200,9 +1370,30 @@ class WPMenuEditor extends MenuEd_ShadowPluginFramework {
1200
  }
1201
 
1202
  $this->reverse_item_lookup[$item['url']] = $item;
 
 
 
 
 
 
 
 
 
 
1203
  $new_submenu[$topmenu['file']][] = $this->convert_to_wp_format($item);
1204
  }
1205
 
 
 
 
 
 
 
 
 
 
 
 
1206
  $new_menu[] = $this->convert_to_wp_format($topmenu);
1207
  }
1208
 
@@ -1246,10 +1437,12 @@ class WPMenuEditor extends MenuEd_ShadowPluginFramework {
1246
  *
1247
  * @param array $item Menu item in the internal format.
1248
  * @param string $item_type Either 'menu' or 'submenu'.
1249
- * @param string $parent Optional. The parent of this sub-menu item. An empty string for top-level menus.
1250
  * @return array Menu item in the internal format.
1251
  */
1252
- private function prepare_for_output($item, $item_type = 'menu', $parent = '') {
 
 
1253
  // Special case : plugin pages that have been moved from a sub-menu to a different
1254
  // menu or the top level. We'll need to adjust the file field to point to the correct URL.
1255
  // This is required because WP identifies plugin pages using *both* the plugin file
@@ -1258,7 +1451,7 @@ class WPMenuEditor extends MenuEd_ShadowPluginFramework {
1258
  $template = $this->item_templates[$item['template_id']];
1259
  if ( $template['defaults']['is_plugin_page'] ) {
1260
  $default_parent = $template['defaults']['parent'];
1261
- if ( $parent != $default_parent ){
1262
  $item['file'] = $template['defaults']['url'];
1263
  }
1264
  }
@@ -1278,6 +1471,14 @@ class WPMenuEditor extends MenuEd_ShadowPluginFramework {
1278
  }
1279
  }
1280
 
 
 
 
 
 
 
 
 
1281
  //Menus that have both a custom icon URL and a "menu-icon-*" class will get two overlapping icons.
1282
  //Fix this by automatically removing the class. The user can set a custom class attr. to override.
1283
  $hasCustomIconUrl = !ameMenuItem::is_default($item, 'icon_url');
@@ -1295,9 +1496,9 @@ class WPMenuEditor extends MenuEd_ShadowPluginFramework {
1295
 
1296
  //Apply defaults & filters
1297
  $item = ameMenuItem::apply_defaults($item);
1298
- $item = ameMenuItem::apply_filters($item, $item_type, $parent); //may cause side-effects
1299
 
1300
- $item = $this->set_final_menu_capability($item);
1301
  if ( !$this->options['security_logging_enabled'] ) {
1302
  unset($item['access_check_log']); //Throw away the log to conserve memory.
1303
  }
@@ -1318,6 +1519,11 @@ class WPMenuEditor extends MenuEd_ShadowPluginFramework {
1318
  $item['css_class'] = 'menu-top ' . $item['css_class'];
1319
  }
1320
 
 
 
 
 
 
1321
  //Add submenu icons if necessary.
1322
  if ( ($item_type === 'submenu') && $hasIcon ) {
1323
  $item = apply_filters('admin_menu_editor-submenu_with_icon', $item, $hasCustomIconUrl);
@@ -1325,7 +1531,7 @@ class WPMenuEditor extends MenuEd_ShadowPluginFramework {
1325
 
1326
  //Used later to determine the current page based on URL.
1327
  if ( empty($item['url']) ) {
1328
- $original_parent = !empty($item['defaults']['parent']) ? $item['defaults']['parent'] : $parent;
1329
  $item['url'] = ameMenuItem::generate_url($item['file'], $original_parent);
1330
  }
1331
 
@@ -1364,14 +1570,38 @@ class WPMenuEditor extends MenuEd_ShadowPluginFramework {
1364
  * this menu.
1365
  *
1366
  * @param array $item Menu item (with defaults applied).
 
1367
  * @return array
1368
  */
1369
- private function set_final_menu_capability($item) {
1370
  $item['access_check_log'] = array(
1371
  str_repeat('=', 79),
1372
  'Figuring out what capability the user will need to access this item...'
1373
  );
1374
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1375
  $item = apply_filters('custom_admin_menu_capability', $item);
1376
 
1377
  $item['access_check_log'][] = '-----';
@@ -1506,9 +1736,24 @@ class WPMenuEditor extends MenuEd_ShadowPluginFramework {
1506
  //Sanitize menu item properties.
1507
  $menu['tree'] = ameMenu::sanitize($menu['tree']);
1508
 
 
 
 
 
 
 
1509
  //Save the custom menu
1510
  $this->set_custom_menu($menu);
1511
 
 
 
 
 
 
 
 
 
 
1512
  //Redirect back to the editor and display the success message.
1513
  //Also, automatically select the last selected actor (convenience feature).
1514
  $query = array('message' => 1);
@@ -1579,7 +1824,7 @@ class WPMenuEditor extends MenuEd_ShadowPluginFramework {
1579
 
1580
  //Menu editor colour scheme.
1581
  if ( !empty($this->post['ui_colour_scheme']) ) {
1582
- $valid_colour_schemes = array('classic', 'wp-grey');
1583
  $scheme = strval($this->post['ui_colour_scheme']);
1584
  if ( in_array($scheme, $valid_colour_schemes) ) {
1585
  $this->options['ui_colour_scheme'] = $scheme;
@@ -1595,6 +1840,16 @@ class WPMenuEditor extends MenuEd_ShadowPluginFramework {
1595
  }
1596
  }
1597
 
 
 
 
 
 
 
 
 
 
 
1598
  $this->save_options();
1599
  wp_redirect(add_query_arg('updated', 1, $this->get_settings_page_url()));
1600
  }
@@ -1613,7 +1868,7 @@ class WPMenuEditor extends MenuEd_ShadowPluginFramework {
1613
  );
1614
 
1615
  //Build a tree struct. for the default menu
1616
- $default_tree = ameMenu::wp2tree($this->default_wp_menu, $this->default_wp_submenu);
1617
  $default_menu = ameMenu::load_array($default_tree);
1618
 
1619
  //Is there a custom menu?
@@ -1731,6 +1986,16 @@ class WPMenuEditor extends MenuEd_ShadowPluginFramework {
1731
  return $caps;
1732
  }
1733
 
 
 
 
 
 
 
 
 
 
 
1734
  foreach($custom_menu['tree'] as $item) {
1735
  $caps = self::array_replace_recursive($caps, $this->get_virtual_caps_for($item));
1736
  }
@@ -1838,7 +2103,7 @@ class WPMenuEditor extends MenuEd_ShadowPluginFramework {
1838
  $defaults = array(
1839
  'ws_sidebar_pro_ad' => true,
1840
  'ws_whats_new_120' => false,
1841
- 'ws_hint_menu_permissions' => true,
1842
  );
1843
 
1844
  return array_merge($defaults, $show_hints);
@@ -1860,6 +2125,73 @@ class WPMenuEditor extends MenuEd_ShadowPluginFramework {
1860
  $this->save_options();
1861
  }
1862
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1863
  /**
1864
  * Enqueue a script that fixes a bug where pages moved to a different menu
1865
  * would not be highlighted properly when the user visits them.
@@ -2242,6 +2574,7 @@ class WPMenuEditor extends MenuEd_ShadowPluginFramework {
2242
  $this->post = $this->originalPost = $_POST;
2243
  $this->get = $_GET;
2244
 
 
2245
  if ( function_exists('get_magic_quotes_gpc') && get_magic_quotes_gpc() ) {
2246
  $this->post = stripslashes_deep($this->post);
2247
  $this->get = stripslashes_deep($this->get);
@@ -2263,7 +2596,9 @@ class WPMenuEditor extends MenuEd_ShadowPluginFramework {
2263
  'ame-helper-script',
2264
  'wsAmeCurrentMenuItem',
2265
  array(
2266
- 'customPageHeading' => $currentItem['page_heading']
 
 
2267
  )
2268
  );
2269
  }
@@ -2546,7 +2881,7 @@ class WPMenuEditor extends MenuEd_ShadowPluginFramework {
2546
  }
2547
 
2548
  /**
2549
- * Get the names of the roles that a user belongs to.
2550
  *
2551
  * "Why not just read the $user->roles array directly?", you may ask. Because some popular plugins have a really
2552
  * nasty bug where they inadvertently remove entries from that array. Specifically, they retrieve the first user
@@ -2557,7 +2892,7 @@ class WPMenuEditor extends MenuEd_ShadowPluginFramework {
2557
  * What some plugin developers fail to realize is that, in addition to returning the first entry, array_shift()
2558
  * also *removes* it from the array. As a result, $user->roles is now missing one of the user's roles. This bug
2559
  * doesn't cause major problems only because most plugins check capabilities and don't care about roles as such.
2560
- * AME needs to know to determine menu permissions for different roles.
2561
  *
2562
  * Known buggy plugins:
2563
  * - W3 Total Cache 0.9.4.1
@@ -2574,12 +2909,12 @@ class WPMenuEditor extends MenuEd_ShadowPluginFramework {
2574
  return array();
2575
  }
2576
  if ( !$user->exists() ) {
2577
- return $user->roles;
 
2578
  }
2579
 
2580
  if ( !isset($this->cached_user_roles[$user->ID]) ) {
2581
- //Note: In rare cases, WP_User::$roles can be false. For AME it's more convenient to have an empty list.
2582
- $this->cached_user_roles[$user->ID] = !empty($user->roles) ? $user->roles : array();
2583
  }
2584
  return $this->cached_user_roles[$user->ID];
2585
  }
@@ -2592,8 +2927,27 @@ class WPMenuEditor extends MenuEd_ShadowPluginFramework {
2592
  if ( empty($user) || !$user->exists() ) {
2593
  return;
2594
  }
 
 
2595
 
2596
- $this->cached_user_roles[$user->ID] = $user->roles;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2597
  }
2598
 
2599
  /**
@@ -2612,6 +2966,80 @@ class WPMenuEditor extends MenuEd_ShadowPluginFramework {
2612
  unset($this->cached_user_roles[$user_id]);
2613
  }
2614
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2615
  /**
2616
  * Tell new users how to access the plugin settings page.
2617
  */
@@ -2657,4 +3085,130 @@ class WPMenuEditor extends MenuEd_ShadowPluginFramework {
2657
  return apply_filters('admin_menu_editor_is_pro', false);
2658
  }
2659
 
2660
- } //class
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
17
 
18
  class WPMenuEditor extends MenuEd_ShadowPluginFramework {
19
  const WPML_CONTEXT = 'admin-menu-editor menu texts';
20
+ /**
21
+ * @var string The heading tag to use for admin pages.
22
+ */
23
+ public static $admin_heading_tag = 'h1';
24
 
25
  private $plugin_db_version = 140;
26
 
69
  private $custom_wp_submenu = null;
70
 
71
  private $item_templates = array(); //A lookup list of default menu items, used as templates for the custom menu.
72
+ private $relative_template_order = array();
73
 
74
  private $cached_custom_menu = null; //Cached, non-merged version of the custom menu. Used by load_custom_menu().
75
  private $cached_virtual_caps = null;//List of virtual caps. Used by get_virtual_caps().
87
  */
88
  private $cached_user_roles = array();
89
 
90
+ /**
91
+ * @var array An index of URLs relative to /wp-admin/. Any menus that match the index will be ignored.
92
+ */
93
+ private $menu_url_blacklist = array();
94
+
95
  function init(){
96
  $this->sitewide_options = true;
97
 
117
  //The user who can see this plugin on the "Plugins" page. By default all admins can see it.
118
  'plugins_page_allowed_user_id' => null,
119
 
120
+ 'show_deprecated_hide_button' => true, //Note: Un-deprecated as of 2015.10.01.
121
  'dashboard_hiding_confirmation_enabled' => true,
122
 
123
  //When to show submenu icons.
126
  //Menu editor UI colour scheme. "Classic" is the old blue/yellow scheme, and "wp-grey" is more WP-like.
127
  'ui_colour_scheme' => 'classic',
128
 
129
+ //User logins that will show up in the actor list at the top of the editor.
130
+ 'visible_users' => array(),
131
+
132
  //Enable/disable the admin notice that tells the user where the plugin settings menu is.
133
  'show_plugin_menu_notice' => true,
134
+
135
+ //Where to place menu items that are not part of the last saved menu configuration.
136
+ //This usually applies to new items added by other plugins and, in Multisite, items that exist on
137
+ //the current site but did not exist on the site where the user last edited the menu configuration.
138
+ 'unused_item_position' => 'relative', //"relative" or "bottom".
139
  );
140
  $this->serialize_with_json = false; //(Don't) store the options in JSON format
141
 
142
+ //WP 4.3+ uses H1 headings for admin pages. Older versions use H2 instead.
143
+ self::$admin_heading_tag = version_compare($GLOBALS['wp_version'], '4.3', '<') ? 'h2' : 'h1';
144
+
145
  $this->settings_link = 'options-general.php?page=menu_editor';
146
 
147
  $this->magic_hooks = true;
148
  //Run our hooks last (almost). Priority is less than PHP_INT_MAX mostly for defensive programming purposes.
149
  //Old PHP versions have known bugs related to large array keys, and WP might have undiscovered edge cases.
150
  $this->magic_hook_priority = PHP_INT_MAX - 10;
151
+
152
+ /*
153
+ * Menu blacklist. Any menu items that *exactly* match one of the URLs on this list will be ignored.
154
+ * They won't show up in the editor or the admin menu, but they will remain accessible (caps permitting).
155
+ *
156
+ * This is a workaround for plugins that add a menu item and then remove it. Most plugins do this
157
+ * to create "Welcome" or "What's New" pages that are accessible but don't appear in the admin menu.
158
+ *
159
+ * We can't automatically detect menus like that. Here's why:
160
+ * 1) Most plugins remove them too late, e.g. in admin_head. By that point, output has already started.
161
+ * We need the finalize the list of menu items and their permissions before that.
162
+ * 2) It's hard to automatically determine *why* a menu item was removed. We can't distinguish between
163
+ * cosmetic changes like the hidden "welcome" items and people removing menus to deny access.
164
+ */
165
+ $this->menu_url_blacklist = array(
166
+ //WP RSS Aggregator 4.7.7
167
+ 'index.php?page=wprss-welcome' => true,
168
+ //AffiliateWP 1.7.8
169
+ 'index.php?page=affwp-getting-started' => true,
170
+ 'index.php?page=affwp-what-is-new' => true,
171
+ 'index.php?page=affwp-credits' => true,
172
+ //BuddyPress 2.3.4
173
+ 'index.php?page=bp-about' => true,
174
+ 'index.php?page=bp-credits' => true,
175
+ );
176
 
177
  //AJAXify screen options
178
  add_action('wp_ajax_ws_ame_save_screen_options', array($this,'ajax_save_screen_options'));
184
  array($this, 'ajax_disable_dashboard_hiding_confirmation')
185
  );
186
 
187
+ //Retrieve a list of pages via AJAX.
188
+ add_action('wp_ajax_ws_ame_get_pages', array($this, 'ajax_get_pages'));
189
+ //Get details about a specific page via AJAX.
190
+ add_action('wp_ajax_ws_ame_get_page_details', array($this, 'ajax_get_page_details'));
191
+
192
  //Make sure we have access to the original, un-mangled request data.
193
  //This is necessary because WordPress will stupidly apply "magic quotes"
194
  //to the request vars even if this PHP misfeature is disabled.
200
  add_action('admin_enqueue_scripts', array($this, 'enqueue_helper_scripts'));
201
  add_action('admin_print_styles', array($this, 'enqueue_helper_styles'));
202
 
203
+ //Make sure our scripts load before other plugins' scripts.
204
+ add_action('admin_print_scripts', array($this, 'move_editor_scripts_to_top'));
205
+
206
  //User survey
207
  add_action('admin_notices', array($this, 'display_survey_notice'));
208
 
315
  array(&$this, 'page_menu_editor')
316
  );
317
  //Output our JS & CSS on that page only
318
+ add_action("admin_print_scripts-$page", array($this, 'enqueue_scripts'), 1);
319
  add_action("admin_print_styles-$page", array($this, 'enqueue_styles'));
320
 
321
+ //Make sure Lodash doesn't conflict with the copy of Underscore that's bundled with WordPress.
322
+ add_filter('script_loader_tag', array($this, 'lodash_noconflict'), 10, 2); //Filter exists since WP 4.1.
323
+
324
  //Compatibility fix for All In One Event Calendar; see the callback for details.
325
  add_action("admin_print_scripts-$page", array($this, 'dequeue_ai1ec_scripts'));
326
 
350
  }
351
 
352
  //Generate item templates from the default menu.
353
+ $templateBuilder = new AmeMenuTemplateBuilder();
354
+ $this->item_templates = $templateBuilder->build(
355
+ $this->default_wp_menu,
356
+ $this->default_wp_submenu,
357
+ $this->menu_url_blacklist
358
+ );
359
+
360
+ //Store the default order for later. It will be used when (re)inserting unused items into the menu.
361
+ $this->relative_template_order = $templateBuilder->getRelativeTemplateOrder();
362
 
363
  //Add extra templates that are not part of the normal menu.
364
  $this->item_templates = $this->add_special_templates($this->item_templates);
559
  * @return void
560
  */
561
  function enqueue_scripts(){
562
+ //Optimization: Remove wp-emoji.js from the plugin page. wpEmoji makes DOM manipulation slow because
563
+ //it tracks *all* DOM changes using MutationObserver.
564
+ remove_action('admin_print_scripts', 'print_emoji_detection_script');
565
+
566
  //jQuery JSON plugin
567
  wp_register_auto_versioned_script('jquery-json', plugins_url('js/jquery.json.js', $this->plugin_file), array('jquery'));
568
  //jQuery sort plugin
574
  //jQuery cookie plugin
575
  wp_register_auto_versioned_script('jquery-cookie', plugins_url('js/jquery.cookie.js', $this->plugin_file), array('jquery'));
576
 
577
+ //Lodash library
578
+ wp_register_auto_versioned_script('ame-lodash', plugins_url('js/lodash.min.js', $this->plugin_file));
579
+
580
+ //Modules
581
+ wp_register_auto_versioned_script(
582
+ 'ame-access-editor',
583
+ plugins_url('modules/access-editor/access-editor.js', $this->plugin_file),
584
+ array('jquery', 'ame-lodash')
585
+ );
586
+
587
+ //Let extras register their scripts.
588
+ do_action('admin_menu_editor-register_scripts');
589
+
590
  //Editor's scripts
591
+ $editor_dependencies = array(
592
+ 'jquery', 'jquery-ui-sortable', 'jquery-ui-dialog', 'jquery-ui-tabs',
593
+ 'ame-jquery-form', 'jquery-ui-droppable', 'jquery-qtip',
594
+ 'jquery-sort', 'jquery-json', 'jquery-cookie',
595
+ 'wp-color-picker', 'ame-lodash', 'ame-access-editor',
596
+ );
597
  wp_register_auto_versioned_script(
598
  'menu-editor',
599
  plugins_url('js/menu-editor.js', $this->plugin_file),
600
+ apply_filters('admin_menu_editor-editor_script_dependencies', $editor_dependencies)
 
 
 
 
 
601
  );
602
 
603
+ //Add only certain scripts to the settings sub-section.
604
+ if ( $this->is_settings_page() ) {
605
+ wp_enqueue_script('jquery-qtip');
606
+ return;
607
+ }
608
+
609
+ //Add all scripts to our editor page, but not the settings sub-section
610
  //that shares the same page slug. Some of the scripts would crash otherwise.
611
  if ( !$this->is_editor_page() ) {
612
  return;
637
  $actors['special:super_admin'] = 'Super Admin';
638
  }
639
 
640
+ //Known users.
641
  $users = array();
 
642
  $current_user = wp_get_current_user();
643
+
644
+ $visible_users = isset($this->options['visible_users']) ? $this->options['visible_users'] : array();
645
+ $logins_to_include = $visible_users;
646
+
647
+ //Always include the current user.
648
+ $logins_to_include[] = $current_user->get('user_login');
649
+ $logins_to_include = array_unique($logins_to_include);
650
+
651
+ //Load user details.
652
+ foreach($logins_to_include as $login) {
653
+ $user = get_user_by('login', $login);
654
+ if ( !empty($user) ) {
655
+ $users[$login] = array(
656
+ 'user_login' => $user->get('user_login'),
657
+ 'id' => $user->ID,
658
+ 'roles' => !empty($user->roles) ? (array)($user->roles) : array(),
659
+ 'capabilities' => $this->castValuesToBool($user->caps),
660
+ 'display_name' => $user->display_name,
661
+ 'is_super_admin' => is_multisite() && is_super_admin($user->ID),
662
+ );
663
+ }
664
+ }
665
+
666
+ //Compatibility workaround: Get the real roles of the current user even if other plugins corrupt the list.
667
+ $users[$current_user->get('user_login')]['roles'] = array_values($this->get_user_roles($current_user));
668
+
669
+ //Keep only those visible users that were successfully loaded.
670
+ //(array_values reindexes the array and gets rid of gaps.)
671
+ $visible_users = array_values(array_intersect($visible_users, array_keys($users)));
672
 
673
  $actors['user:' . $current_user->get('user_login')] = sprintf(
674
  'Current user (%s)',
675
  $current_user->get('user_login')
676
  );
 
 
677
 
678
  $showExtraIcons = (boolean)$this->options['show_extra_icons'];
679
  if ( isset($_COOKIE['ame-show-extra-icons']) && is_numeric($_COOKIE['ame-show-extra-icons']) ) {
681
  }
682
 
683
  //The editor will need access to some of the plugin data and WP data.
684
+ $script_data = array(
685
+ 'imagesUrl' => plugins_url('images', $this->plugin_file),
686
+ 'adminAjaxUrl' => admin_url('admin-ajax.php'),
687
+ 'hideAdvancedSettings' => (boolean)$this->options['hide_advanced_settings'],
688
+ 'showExtraIcons' => $showExtraIcons,
689
+ 'submenuIconsEnabled' => $this->options['submenu_icons_enabled'],
690
+
691
+ 'hideAdvancedSettingsNonce' => wp_create_nonce('ws_ame_save_screen_options'),
692
+ 'dashiconsAvailable' => wp_style_is('dashicons', 'registered'),
693
+ 'captionShowAdvanced' => 'Show advanced options',
694
+ 'captionHideAdvanced' => 'Hide advanced options',
695
+ 'wsMenuEditorPro' => false, //Will be overwritten if extras are loaded
696
+ 'menuFormatName' => ameMenu::format_name,
697
+ 'menuFormatVersion' => ameMenu::format_version,
698
+
699
+ 'blankMenuItem' => ameMenuItem::blank_menu(),
700
+ 'itemTemplates' => $this->item_templates,
701
+ 'customItemTemplate' => array(
702
+ 'name' => '< Custom >',
703
+ 'defaults' => ameMenuItem::custom_item_defaults(),
704
+ ),
705
+
706
+ 'unclickableTemplateId' => ameMenuItem::unclickableTemplateId,
707
+ 'unclickableTemplateClass' => ameMenuItem::unclickableTemplateClass,
708
+
709
+ 'embeddedPageTemplateId' => ameMenuItem::embeddedPageTemplateId,
710
+
711
+ 'actors' => $actors,
712
+ 'roles' => $roles,
713
+ 'users' => $users,
714
+ 'currentUserLogin' => $current_user->get('user_login'),
715
+ 'selectedActor' => isset($this->get['selected_actor']) ? strval($this->get['selected_actor']) : null,
716
+ 'visibleUsers' => $visible_users,
717
+
718
+ 'postTypes' => $this->get_post_type_details(),
719
+ 'taxonomies' => $this->get_taxonomy_details(),
720
+
721
+ 'showHints' => $this->get_hint_visibility(),
722
+ 'dashboardHidingConfirmationEnabled' => $this->options['dashboard_hiding_confirmation_enabled'],
723
+ 'disableDashboardConfirmationNonce' => wp_create_nonce('ws_ame_disable_dashboard_hiding_confirmation'),
724
+
725
+ 'getPagesNonce' => wp_create_nonce('ws_ame_get_pages'),
726
+ 'getPageDetailsNonce' => wp_create_nonce('ws_ame_get_page_details'),
727
+
728
+ 'isDemoMode' => defined('IS_DEMO_MODE'),
729
+ 'isMasterMode' => defined('IS_MASTER_MODE'),
730
  );
731
+ $script_data = apply_filters('admin_menu_editor-script_data', $script_data);
732
+ wp_localize_script('menu-editor', 'wsEditorData', $script_data);
733
+ }
734
+
735
+ /**
736
+ * Move editor scripts closer to the top of the script queue.
737
+ *
738
+ * This reduces the chances that JavaScript bugs in other plugins will crash the menu editor.
739
+ * For example, if another plugin's script loads first and crashes in a $(document).ready()
740
+ * handler, the editor's $(document).ready() handler will never be run. This will make the UI
741
+ * unusable because the menu list will not render, etc. Loading our scripts first makes that
742
+ * less likely.
743
+ */
744
+ public function move_editor_scripts_to_top() {
745
+ global $wp_scripts;
746
+ if ( !(isset($wp_scripts) && ($wp_scripts instanceof WP_Scripts)) ) {
747
+ $wp_scripts = new WP_Scripts();
748
+ }
749
+
750
+ //Sanity check. If the wp_scripts implementation has changed significantly, don't touch it.
751
+ if ( !isset($wp_scripts->queue) || (!is_array($wp_scripts->queue) || ($wp_scripts->queue instanceof Traversable)) ) {
752
+ return;
753
+ }
754
+
755
+ //We want to load our scripts *after* WordPress core scripts in case we depend on some core feature.
756
+ $common_key = array_search('common', $wp_scripts->queue);
757
+ $admin_bar_key = array_search('admin-bar', $wp_scripts->queue);
758
+ if ( ($common_key === false) && ($admin_bar_key === false) ) {
759
+ return;
760
+ }
761
+ $last_core_key = max($admin_bar_key, $common_key);
762
+
763
+ //Move only those scripts that are actually in the queue.
764
+ $handles_to_move = array();
765
+ foreach(array('menu-editor', 'ame-helper-script') as $handle) {
766
+ $key = array_search($handle, $wp_scripts->queue);
767
+ if ($key !== false) {
768
+ $handles_to_move[] = $handle;
769
+ unset($wp_scripts->queue[$key]); //Remove the script from its old position.
770
+ }
771
+ }
772
+
773
+ //Insert the scripts after core script(s).
774
+ array_splice($wp_scripts->queue, $last_core_key + 1, 0, $handles_to_move);
775
+ }
776
+
777
+ /**
778
+ * Revert the "_" variable to its original value and store Lodash in "wsAmeLodash" instead.
779
+ *
780
+ * @param string $tag
781
+ * @param string $script_handle
782
+ * @return string
783
+ */
784
+ public function lodash_noconflict($tag, $script_handle) {
785
+ if ($script_handle === 'ame-lodash') {
786
+ $tag .= '<script type="text/javascript">wsAmeLodash = _.noConflict();</script>';
787
+ }
788
+ return $tag;
789
  }
790
 
791
  /**
843
  plugins_url('css/style-wp-grey.css', $this->plugin_file),
844
  array('menu-editor-base-style')
845
  );
846
+ wp_register_auto_versioned_style(
847
+ 'menu-editor-colours-modern-one',
848
+ plugins_url('css/style-modern-one.css', $this->plugin_file),
849
+ array('menu-editor-base-style')
850
+ );
851
 
852
  //WordPress introduced a new screen meta button style in WP 3.8.
853
  //We have two different stylesheets - one for 3.8+ and one for backwards compatibility.
898
  *
899
  * @return array|null Either a menu in the internal format, or NULL if there is no custom menu available.
900
  */
901
+ public function load_custom_menu() {
902
  if ( $this->cached_custom_menu !== null ) {
903
  return $this->cached_custom_menu;
904
  }
981
 
982
  return $admin_title;
983
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
984
 
985
  /**
986
  * Generate special menu templates and add them to the input template list.
1006
  'defaults' => $unclickableDefaults,
1007
  );
1008
 
1009
+ $templates[ameMenuItem::embeddedPageTemplateId] = array(
1010
+ 'name' => '< Embed WP page >',
1011
+ 'used' => true,
1012
+ 'defaults' => array_merge(
1013
+ $itemDefaults,
1014
+ array(
1015
+ 'file' => '#automatically-generated',
1016
+ 'url' => '#automatically-generated',
1017
+ 'menu_title' => 'Embedded Page',
1018
+ 'page_heading' => ameMenuItem::embeddedPagePlaceholderHeading,
1019
+ )
1020
+ )
1021
+ );
1022
+
1023
  if ( $this->is_pro_version() ) {
1024
  //The Pro version has a [wp-logout-url] shortcode. Lets make it easier o use
1025
  //by adding it to the "Target page" dropdown.
1055
  //Iterate over all menus and submenus and look up default values
1056
  //Also flag used and missing items.
1057
  $orphans = array();
1058
+
1059
+ //Build an index of menu positions so that we can quickly pick the right position for new/unused items.
1060
+ $positions_by_template = array();
1061
+ $following_separator_position = array();
1062
+ $previous_default_top_menu = null;
1063
+
1064
  foreach ($tree as &$topmenu){
1065
 
1066
+ if ( !empty($topmenu['separator']) && isset($previous_default_top_menu) ) {
1067
+ $following_separator_position[$previous_default_top_menu] = ameMenuItem::get($topmenu, 'position', 0);
1068
+ }
1069
+ $previous_default_top_menu = null;
1070
+
1071
  if ( !ameMenuItem::get($topmenu, 'custom') ) {
1072
  $template_id = ameMenuItem::template_id($topmenu);
1073
  //Is this menu present in the default WP menu?
1076
  $topmenu['defaults'] = $this->item_templates[$template_id]['defaults'];
1077
  //Note that the original item was used
1078
  $this->item_templates[$template_id]['used'] = true;
1079
+ //Add valid, non-custom items to the position index.
1080
+ $positions_by_template[$template_id] = ameMenuItem::get($topmenu, 'position', 0);
1081
+ $previous_default_top_menu = $template_id;
1082
  } else {
1083
  //Record the menu as missing, unless it's a menu separator
1084
  if ( empty($topmenu['separator']) ){
1088
  $temp = $this->set_final_menu_capability($temp);
1089
  $this->add_access_lookup($temp, 'menu', true);
1090
  }
1091
+ //Don't add missing menus to the index because they won't show up anyway.
1092
  }
1093
  }
1094
 
1103
  //Yes, load defaults from that item
1104
  $item['defaults'] = $this->item_templates[$template_id]['defaults'];
1105
  $this->item_templates[$template_id]['used'] = true;
1106
+ //Add valid, non-custom items to the position index.
1107
+ $positions_by_template[$template_id] = ameMenuItem::get($item, 'position', 0);
1108
  //We must move orphaned items elsewhere. Use the default location if possible.
1109
  if ( isset($topmenu['missing']) && $topmenu['missing'] ) {
1110
  $orphans[] = $item;
1134
  $tree = ameMenu::remove_missing_items($tree);
1135
 
1136
  //Lets merge in the unused items.
1137
+ $max_menu_position = !empty($positions_by_template) ? max($positions_by_template) : 100;
1138
  foreach ($this->item_templates as $template_id => $template){
1139
  //Skip used menus and separators
1140
  if ( !empty($template['used']) || !empty($template['defaults']['separator'])) {
1147
  $entry['defaults'] = $template['defaults'];
1148
  $entry['unused'] = true; //Note that this item is unused
1149
 
1150
+ if ($this->options['unused_item_position'] === 'relative') {
1151
+
1152
+ //Attempt to maintain relative menu order.
1153
+ $previous_item = $was_separated = null;
1154
+ if ( isset($this->relative_template_order[$template_id]) ) {
1155
+ $previous_item = $this->relative_template_order[$template_id]['previous_item'];
1156
+ $was_separated = $this->relative_template_order[$template_id]['was_previous_item_separated'];
1157
+ }
1158
+
1159
+ if ( isset($previous_item, $positions_by_template[$previous_item]) ) {
1160
+ if ( $was_separated && isset($following_separator_position[$previous_item]) ) {
1161
+ //Desired order: previous item -> separator -> this item.
1162
+ $entry['position'] = $following_separator_position[$previous_item];
1163
+ } else {
1164
+ //Desired order: previous item -> this item.
1165
+ $entry['position'] = $positions_by_template[$previous_item];
1166
+ if ( isset($following_separator_position[$previous_item]) ) {
1167
+ //Now the separator is after this item, not the previous one.
1168
+ $following_separator_position[$template_id] = $following_separator_position[$previous_item];
1169
+ unset($following_separator_position[$previous_item]);
1170
+ }
1171
+ }
1172
+ $entry['position'] = $entry['position'] + 0.01;
1173
+ } else if ( $previous_item === '' ) {
1174
+ //Empty string = this was originally the first item.
1175
+ $entry['position'] = -1;
1176
+ } else {
1177
+ //Previous item is unknown or doesn't exist. Leave this item in its current, incorrect position.
1178
+ }
1179
+
1180
+ } else {
1181
+ //Move unused entries to the bottom.
1182
+ $max_menu_position = $max_menu_position + 1;
1183
+ $entry['position'] = $max_menu_position;
1184
+ }
1185
+ $positions_by_template[$template_id] = ameMenuItem::get($entry, 'position', 0);
1186
+
1187
  //Add the new entry to the menu tree
1188
  if ( !empty($template['defaults']['parent']) ) {
1189
  if (isset($tree[$template['defaults']['parent']])) {
1213
  //Resort the tree to ensure the found items are in the right spots
1214
  $tree = ameMenu::sort_menu_tree($tree);
1215
 
1216
+ //Order data is no longer necessary.
1217
+ $this->relative_template_order = null;
1218
+
1219
  return $tree;
1220
  }
1221
 
1289
  $first_nonseparator_found = false;
1290
  foreach ($tree as $topmenu){
1291
 
 
 
 
 
 
1292
  //Skip leading menu separators. Fixes a superfluous separator showing up
1293
  //in WP 3.0 (multisite mode) when there's a custom menu and the current user
1294
  //can't access its first item ("Super Admin").
1305
 
1306
  //Prepare the submenu of this menu
1307
  $new_items = array();
 
1308
  if( !empty($topmenu['items']) ){
1309
  $items = $topmenu['items'];
1310
 
1311
  foreach ($items as $item) {
1312
+ $item = $this->prepare_for_output($item, 'submenu', $topmenu);
 
 
 
 
 
1313
  $new_items[] = $item;
1314
 
1315
  //Make a note of the page's correct title so we can fix it later if necessary.
1316
  $this->title_lookups[$item['file']] = !empty($item['page_title']) ? $item['page_title'] : $item['menu_title'];
 
 
 
1317
  }
1318
 
1319
  //Sort by position
1320
+ usort($new_items, 'ameMenuItem::compare_position');
 
 
 
 
 
 
1321
  }
1322
 
1323
  $topmenu['items'] = $new_items;
1353
  $this->reverse_item_lookup[$topmenu['url']] = $topmenu;
1354
  }
1355
 
1356
+ $has_submenu_icons = false;
1357
  foreach($topmenu['items'] as $item) {
1358
  $trueAccess = isset($this->page_access_lookup[$item['url']]) ? $this->page_access_lookup[$item['url']] : null;
1359
  if ( ($trueAccess === 'do_not_allow') && ($item['access_level'] !== $trueAccess) ) {
1370
  }
1371
 
1372
  $this->reverse_item_lookup[$item['url']] = $item;
1373
+
1374
+ //Skip missing and hidden items
1375
+ if ( !empty($item['missing']) || !empty($item['hidden']) ) {
1376
+ continue;
1377
+ }
1378
+
1379
+ //Keep track of which menus have items with icons. Ignore hidden items.
1380
+ $has_submenu_icons = $has_submenu_icons
1381
+ || (!empty($item['has_submenu_icon']) && $item['access_level'] !== 'do_not_allow');
1382
+
1383
  $new_submenu[$topmenu['file']][] = $this->convert_to_wp_format($item);
1384
  }
1385
 
1386
+ //Skip missing and hidden menus.
1387
+ if ( !empty($topmenu['missing']) || !empty($topmenu['hidden']) ) {
1388
+ continue;
1389
+ }
1390
+
1391
+ //The ame-has-submenu-icons class lets us change the appearance of all submenu items at once,
1392
+ //without having to add classes/styles to each item individually.
1393
+ if ( $has_submenu_icons && (strpos($topmenu['css_class'], 'ame-has-submenu-icons') === false) ) {
1394
+ $topmenu['css_class'] .= ' ame-has-submenu-icons';
1395
+ }
1396
+
1397
  $new_menu[] = $this->convert_to_wp_format($topmenu);
1398
  }
1399
 
1437
  *
1438
  * @param array $item Menu item in the internal format.
1439
  * @param string $item_type Either 'menu' or 'submenu'.
1440
+ * @param array $parent Optional. The parent of this sub-menu item. Top level menus have no parent.
1441
  * @return array Menu item in the internal format.
1442
  */
1443
+ private function prepare_for_output($item, $item_type = 'menu', $parent = array()) {
1444
+ $parent_file = isset($parent['file']) ? $parent['file'] : '';
1445
+
1446
  // Special case : plugin pages that have been moved from a sub-menu to a different
1447
  // menu or the top level. We'll need to adjust the file field to point to the correct URL.
1448
  // This is required because WP identifies plugin pages using *both* the plugin file
1451
  $template = $this->item_templates[$item['template_id']];
1452
  if ( $template['defaults']['is_plugin_page'] ) {
1453
  $default_parent = $template['defaults']['parent'];
1454
+ if ( $parent_file != $default_parent ){
1455
  $item['file'] = $template['defaults']['url'];
1456
  }
1457
  }
1471
  }
1472
  }
1473
 
1474
+ //Make the default submenu icon the same as the parent icon.
1475
+ if ( !empty($parent) && isset($item['defaults']) ) {
1476
+ $parent_icon = ameMenuItem::get($parent, 'icon_url', '');
1477
+ if ( !empty($parent_icon) ) {
1478
+ $item['defaults']['icon_url'] = $parent_icon;
1479
+ }
1480
+ }
1481
+
1482
  //Menus that have both a custom icon URL and a "menu-icon-*" class will get two overlapping icons.
1483
  //Fix this by automatically removing the class. The user can set a custom class attr. to override.
1484
  $hasCustomIconUrl = !ameMenuItem::is_default($item, 'icon_url');
1496
 
1497
  //Apply defaults & filters
1498
  $item = ameMenuItem::apply_defaults($item);
1499
+ $item = ameMenuItem::apply_filters($item, $item_type, $parent_file); //may cause side-effects
1500
 
1501
+ $item = $this->set_final_menu_capability($item, $parent);
1502
  if ( !$this->options['security_logging_enabled'] ) {
1503
  unset($item['access_check_log']); //Throw away the log to conserve memory.
1504
  }
1519
  $item['css_class'] = 'menu-top ' . $item['css_class'];
1520
  }
1521
 
1522
+ //Add a flag to menus that will be kept open.
1523
+ if ( !empty($item['is_always_open']) && ($item_type === 'menu') && (!$item['separator']) ) {
1524
+ $item['css_class'] .= ' ws-ame-has-always-open-submenu';
1525
+ }
1526
+
1527
  //Add submenu icons if necessary.
1528
  if ( ($item_type === 'submenu') && $hasIcon ) {
1529
  $item = apply_filters('admin_menu_editor-submenu_with_icon', $item, $hasCustomIconUrl);
1531
 
1532
  //Used later to determine the current page based on URL.
1533
  if ( empty($item['url']) ) {
1534
+ $original_parent = !empty($item['defaults']['parent']) ? $item['defaults']['parent'] : $parent_file;
1535
  $item['url'] = ameMenuItem::generate_url($item['file'], $original_parent);
1536
  }
1537
 
1570
  * this menu.
1571
  *
1572
  * @param array $item Menu item (with defaults applied).
1573
+ * @param array $parent_item Parent menu item, if any.
1574
  * @return array
1575
  */
1576
+ private function set_final_menu_capability($item, $parent_item = null) {
1577
  $item['access_check_log'] = array(
1578
  str_repeat('=', 79),
1579
  'Figuring out what capability the user will need to access this item...'
1580
  );
1581
 
1582
+ //The user can configure the plugin to automatically hide all submenu items if the parent menu is hidden.
1583
+ //This is the opposite of how WordPress usually handles submenu permissions, so it's optional.
1584
+ $is_parent_denied = !empty($parent_item) && ($parent_item['access_level'] === 'do_not_allow');
1585
+ if ( $is_parent_denied && !empty($parent_item['restrict_access_to_items']) ) {
1586
+ $item['access_check_log'][] = '-----';
1587
+ $item['access_check_log'][] = 'WARNING: The parent menu overrides submenu permissions.';
1588
+ $item['access_check_log'][] = sprintf(
1589
+ 'The current user doesn\'t have access to the parent menu ("%s"). Because the "Hide all submenu items
1590
+ when this item is hidden" option is enabled, this item will also be hidden. Setting capability to
1591
+ "do_not_allow".',
1592
+ htmlentities($parent_item['menu_title'])
1593
+ );
1594
+
1595
+ $item['access_check_log'][] = str_repeat('=', 79);
1596
+ if ( !empty($parent_item['access_check_log']) ) {
1597
+ $item['access_check_log'][] = 'For reference, here\'s the log for the parent menu:';
1598
+ $item['access_check_log'] = array_merge($item['access_check_log'], $parent_item['access_check_log']);
1599
+ }
1600
+
1601
+ $item['access_level'] = 'do_not_allow';
1602
+ return $item;
1603
+ }
1604
+
1605
  $item = apply_filters('custom_admin_menu_capability', $item);
1606
 
1607
  $item['access_check_log'][] = '-----';
1736
  //Sanitize menu item properties.
1737
  $menu['tree'] = ameMenu::sanitize($menu['tree']);
1738
 
1739
+ //Discard capabilities that refer to unregistered post types or taxonomies.
1740
+ if ( !empty($menu['granted_capabilities']) ) {
1741
+ $capFilter = new ameGrantedCapabilityFilter();
1742
+ $menu['granted_capabilities'] = $capFilter->clean_up($menu['granted_capabilities']);
1743
+ }
1744
+
1745
  //Save the custom menu
1746
  $this->set_custom_menu($menu);
1747
 
1748
+ //Save the list of visible users.
1749
+ if ( isset($this->post['visible_users']) && is_string($this->post['visible_users']) ) {
1750
+ $visible_users = json_decode($this->post['visible_users']);
1751
+ if ( is_array($visible_users) ) {
1752
+ $this->options['visible_users'] = array_unique(array_map('strval', $visible_users));
1753
+ $this->save_options();
1754
+ }
1755
+ }
1756
+
1757
  //Redirect back to the editor and display the success message.
1758
  //Also, automatically select the last selected actor (convenience feature).
1759
  $query = array('message' => 1);
1824
 
1825
  //Menu editor colour scheme.
1826
  if ( !empty($this->post['ui_colour_scheme']) ) {
1827
+ $valid_colour_schemes = array('classic', 'wp-grey', 'modern-one');
1828
  $scheme = strval($this->post['ui_colour_scheme']);
1829
  if ( in_array($scheme, $valid_colour_schemes) ) {
1830
  $this->options['ui_colour_scheme'] = $scheme;
1840
  }
1841
  }
1842
 
1843
+ //Where to put new or unused menu items.
1844
+ if ( !empty($this->post['unused_item_position']) ) {
1845
+ $unused_item_position = strval($this->post['unused_item_position']);
1846
+ $valid_position_settings = array('relative', 'bottom');
1847
+ if ( in_array($unused_item_position, $valid_position_settings, true) ) {
1848
+ $this->options['unused_item_position'] = $unused_item_position;
1849
+ }
1850
+ }
1851
+
1852
+
1853
  $this->save_options();
1854
  wp_redirect(add_query_arg('updated', 1, $this->get_settings_page_url()));
1855
  }
1868
  );
1869
 
1870
  //Build a tree struct. for the default menu
1871
+ $default_tree = ameMenu::wp2tree($this->default_wp_menu, $this->default_wp_submenu, $this->menu_url_blacklist);
1872
  $default_menu = ameMenu::load_array($default_tree);
1873
 
1874
  //Is there a custom menu?
1986
  return $caps;
1987
  }
1988
 
1989
+ //Include directly granted capabilities.
1990
+ if ( !empty($custom_menu['granted_capabilities']) ) {
1991
+ foreach ($custom_menu['granted_capabilities'] as $actor => $capabilities) {
1992
+ foreach ($capabilities as $capability => $allow) {
1993
+ $caps[$actor][$capability] = (bool)(is_array($allow) ? $allow[0] : $allow);
1994
+ }
1995
+ }
1996
+ }
1997
+
1998
+ //grant_access settings on individual items have precedence.
1999
  foreach($custom_menu['tree'] as $item) {
2000
  $caps = self::array_replace_recursive($caps, $this->get_virtual_caps_for($item));
2001
  }
2103
  $defaults = array(
2104
  'ws_sidebar_pro_ad' => true,
2105
  'ws_whats_new_120' => false,
2106
+ 'ws_hint_menu_permissions' => false,
2107
  );
2108
 
2109
  return array_merge($defaults, $show_hints);
2125
  $this->save_options();
2126
  }
2127
 
2128
+ /**
2129
+ * Retrieve a list of recently modified pages.
2130
+ */
2131
+ public function ajax_get_pages() {
2132
+ if ( !check_ajax_referer('ws_ame_get_pages', false, false) ) {
2133
+ exit(json_encode(array('error' => 'Invalid nonce.')));
2134
+ } else if ( !$this->current_user_can_edit_menu() ) {
2135
+ exit(json_encode(array('error' => 'You don\'t have sufficient permissions to edit the admin menu.')));
2136
+ }
2137
+
2138
+ $pages = get_pages(array(
2139
+ 'sort_column' => 'post_modified',
2140
+ 'sort_order' => 'DESC',
2141
+ 'hierarchical' => false,
2142
+ 'number' => 50, //Semi-arbitrary. We do need a limit - some users could have thousands of pages.
2143
+ ));
2144
+ /** @var WP_Post[] $pages */
2145
+ $blog_id = get_current_blog_id();
2146
+
2147
+ $results = array();
2148
+ foreach($pages as $page) {
2149
+ $results[] = array(
2150
+ 'post_id' => $page->ID,
2151
+ 'blog_id' => $blog_id,
2152
+ 'post_title' => $page->post_title,
2153
+ 'post_modified' => $page->post_modified
2154
+ );
2155
+ }
2156
+
2157
+ exit(json_encode($results));
2158
+ }
2159
+
2160
+ /**
2161
+ * Get details about a specific page or post. CPTs also work.
2162
+ */
2163
+ public function ajax_get_page_details() {
2164
+ if ( !check_ajax_referer('ws_ame_get_page_details', false, false) ) {
2165
+ exit(json_encode(array('error' => 'Invalid nonce.')));
2166
+ } else if ( !$this->current_user_can_edit_menu() ) {
2167
+ exit(json_encode(array('error' => 'You don\'t have sufficient permissions to edit the admin menu.')));
2168
+ }
2169
+
2170
+ $post_id = intval($_GET['post_id']);
2171
+ $blog_id = intval($_GET['blog_id']);
2172
+ $should_switch = function_exists('get_current_blog_id') && ($blog_id !== get_current_blog_id());
2173
+
2174
+ if ( $should_switch ) {
2175
+ switch_to_blog($blog_id);
2176
+ }
2177
+
2178
+ $page = get_post($post_id);
2179
+ if ( !$page ) {
2180
+ exit(json_encode(array('error' => 'Not found')));
2181
+ }
2182
+
2183
+ if ( $should_switch ) {
2184
+ restore_current_blog();
2185
+ }
2186
+
2187
+ $response = array(
2188
+ 'post_id' => $page->ID,
2189
+ 'blog_id' => $blog_id,
2190
+ 'post_title' => $page->post_title,
2191
+ );
2192
+ exit(json_encode($response));
2193
+ }
2194
+
2195
  /**
2196
  * Enqueue a script that fixes a bug where pages moved to a different menu
2197
  * would not be highlighted properly when the user visits them.
2574
  $this->post = $this->originalPost = $_POST;
2575
  $this->get = $_GET;
2576
 
2577
+ /** @noinspection PhpDeprecationInspection */
2578
  if ( function_exists('get_magic_quotes_gpc') && get_magic_quotes_gpc() ) {
2579
  $this->post = stripslashes_deep($this->post);
2580
  $this->get = stripslashes_deep($this->get);
2596
  'ame-helper-script',
2597
  'wsAmeCurrentMenuItem',
2598
  array(
2599
+ 'customPageHeading' => $currentItem['page_heading'],
2600
+ 'pageHeadingSelector' =>
2601
+ version_compare(self::get_wp_version(), '4.3', '<') ? '.wrap > h2:first' : '.wrap > h1:first',
2602
  )
2603
  );
2604
  }
2881
  }
2882
 
2883
  /**
2884
+ * Get a user's roles.
2885
  *
2886
  * "Why not just read the $user->roles array directly?", you may ask. Because some popular plugins have a really
2887
  * nasty bug where they inadvertently remove entries from that array. Specifically, they retrieve the first user
2892
  * What some plugin developers fail to realize is that, in addition to returning the first entry, array_shift()
2893
  * also *removes* it from the array. As a result, $user->roles is now missing one of the user's roles. This bug
2894
  * doesn't cause major problems only because most plugins check capabilities and don't care about roles as such.
2895
+ * AME needs to know the roles because some menu permissions are set per role.
2896
  *
2897
  * Known buggy plugins:
2898
  * - W3 Total Cache 0.9.4.1
2909
  return array();
2910
  }
2911
  if ( !$user->exists() ) {
2912
+ //Note: In rare cases, WP_User::$roles can be false. For AME it's more convenient to have an empty list.
2913
+ return (!empty($user->roles) ? $user->roles : array());
2914
  }
2915
 
2916
  if ( !isset($this->cached_user_roles[$user->ID]) ) {
2917
+ $this->cached_user_roles[$user->ID] = $this->extract_user_roles($user);
 
2918
  }
2919
  return $this->cached_user_roles[$user->ID];
2920
  }
2927
  if ( empty($user) || !$user->exists() ) {
2928
  return;
2929
  }
2930
+ $this->cached_user_roles[$user->ID] = $this->extract_user_roles($user);
2931
+ }
2932
 
2933
+ /**
2934
+ * Get user roles by parsing their capabilities.
2935
+ *
2936
+ * This method is reliable because it determines user roles the same way that WordPress does. However, it's also
2937
+ * relatively "slow" (~ 25 microseconds on my dev. system). Don't call it directly. Use get_user_roles() instead -
2938
+ * it caches results.
2939
+ *
2940
+ * @see WP_User::get_role_caps
2941
+ *
2942
+ * @param WP_User $user
2943
+ * @return array
2944
+ */
2945
+ private function extract_user_roles($user) {
2946
+ if ( empty($user->caps) || !is_array($user->caps) ) {
2947
+ return (!empty($user->roles) ? $user->roles : array());
2948
+ }
2949
+ $wp_roles = ameRoleUtils::get_roles();
2950
+ return array_filter(array_keys($user->caps), array($wp_roles, 'is_role'));
2951
  }
2952
 
2953
  /**
2966
  unset($this->cached_user_roles[$user_id]);
2967
  }
2968
 
2969
+ /**
2970
+ * Get registered public post types.
2971
+ * @return array
2972
+ */
2973
+ private function get_post_type_details() {
2974
+ $results = array();
2975
+
2976
+ $post_types = get_post_types(array('public' => true, 'show_ui' => true), 'objects', 'or');
2977
+ $meta_caps = array('edit_post', 'read_post', 'delete_post');
2978
+
2979
+ foreach($post_types as $id => $post_type) {
2980
+ $title = $id;
2981
+ if (isset($post_type->labels, $post_type->labels->name) && !empty($post_type->labels->name)) {
2982
+ $title = $post_type->labels->name;
2983
+ }
2984
+
2985
+ $capabilities = array();
2986
+ foreach((array)$post_type->cap as $cap_type => $capability) {
2987
+ //Skip meta caps.
2988
+ if ($post_type->map_meta_cap && in_array($cap_type, $meta_caps)) {
2989
+ continue;
2990
+ }
2991
+
2992
+ //Skip the "read" cap. It's redundant - most CPTs use it, and all roles have it by default.
2993
+ if (($cap_type === 'read') && ($capability === 'read')) {
2994
+ continue;
2995
+ }
2996
+
2997
+ $capabilities[$cap_type] = $capability;
2998
+ }
2999
+
3000
+ $results[$id] = array(
3001
+ 'id' => $id,
3002
+ 'title' => $title,
3003
+ 'capabilities' => $capabilities,
3004
+ );
3005
+ }
3006
+
3007
+ return $results;
3008
+ }
3009
+
3010
+ /**
3011
+ * Get registered taxonomies.
3012
+ * @return array
3013
+ */
3014
+ private function get_taxonomy_details() {
3015
+ $results = array();
3016
+ $taxonomies = get_taxonomies(array('public' => true, 'show_ui' => true), 'objects', 'or');
3017
+
3018
+ foreach($taxonomies as $id => $taxonomy) {
3019
+ $title = $id;
3020
+ if (isset($taxonomy->labels, $taxonomy->labels->name) && !empty($taxonomy->labels->name)) {
3021
+ $title = $taxonomy->labels->name;
3022
+ }
3023
+
3024
+ $capabilities = array();
3025
+ foreach((array)$taxonomy->cap as $cap_type => $capability) {
3026
+ //Skip the "read" cap. It's redundant - most CPTs use it, and all roles have it by default.
3027
+ if (($cap_type === 'read') && ($capability === 'read')) {
3028
+ continue;
3029
+ }
3030
+ $capabilities[$cap_type] = $capability;
3031
+ }
3032
+
3033
+ $results[$id] = array(
3034
+ 'id' => $id,
3035
+ 'title' => $title,
3036
+ 'capabilities' => $capabilities,
3037
+ );
3038
+ }
3039
+
3040
+ return $results;
3041
+ }
3042
+
3043
  /**
3044
  * Tell new users how to access the plugin settings page.
3045
  */
3085
  return apply_filters('admin_menu_editor_is_pro', false);
3086
  }
3087
 
3088
+ /**
3089
+ * Get the WordPress version number.
3090
+ *
3091
+ * Warning: Some plugins change the WordPress version number to hide the installed version from visitors.
3092
+ * It's a security-by-obscurity technique. This means you can't rely on the number being correct.
3093
+ *
3094
+ * @return string Either the version number or an empty string.
3095
+ */
3096
+ private static function get_wp_version() {
3097
+ if ( isset($GLOBALS['wp_version']) ) {
3098
+ return $GLOBALS['wp_version'];
3099
+ }
3100
+ return '';
3101
+ }
3102
+
3103
+ } //class
3104
+
3105
+
3106
+ class ameMenuTemplateBuilder {
3107
+ private $templates = array();
3108
+
3109
+ private $parentNames = array();
3110
+ private $blacklist = array();
3111
+
3112
+ private $templateOrder = array();
3113
+ private $previousItemId = '';
3114
+ private $wasPreviousItemSeparated = false;
3115
+
3116
+ /**
3117
+ * Populate a lookup array with default values (templates) from $menu and $submenu.
3118
+ * Used later to merge a custom menu with the native WordPress menu structure.
3119
+ *
3120
+ * @param array $menu
3121
+ * @param array $submenu
3122
+ * @param array $blacklist
3123
+ * @return array An array of menu templates and their default values.
3124
+ */
3125
+ public function build($menu, $submenu, $blacklist = array()){
3126
+ $this->templates = array();
3127
+ $this->blacklist = $blacklist;
3128
+
3129
+ //At this point, the menu might not be sorted yet, especially if other plugins have made changes to it.
3130
+ //We need to know the relative order of menus to insert new items in the right place.
3131
+ ksort($menu, SORT_NUMERIC);
3132
+
3133
+ foreach($menu as $pos => $item){
3134
+ $this->addItem($item, $pos);
3135
+ }
3136
+
3137
+ foreach($submenu as $parent => $items){
3138
+ //Skip sub-menus attached to non-existent parents. This should theoretically never happen,
3139
+ //but a buggy plugin can cause such a situation.
3140
+ if ( !isset($this->parentNames[$parent]) ) {
3141
+ continue;
3142
+ }
3143
+
3144
+ ksort($items, SORT_NUMERIC);
3145
+ $this->previousItemId = '';
3146
+ $this->wasPreviousItemSeparated = false;
3147
+
3148
+ foreach($items as $pos => $item) {
3149
+ $this->addItem($item, $pos, $parent);
3150
+ }
3151
+ }
3152
+
3153
+ return $this->templates;
3154
+ }
3155
+
3156
+ /**
3157
+ * Add a menu item as a template.
3158
+ *
3159
+ * @param array $wpItem
3160
+ * @param int $position
3161
+ * @param string $parent
3162
+ */
3163
+ private function addItem($wpItem, $position, $parent = '') {
3164
+ $item = ameMenuItem::fromWpItem($wpItem, $position, $parent);
3165
+
3166
+ //Skip separators.
3167
+ if ( $item['separator'] ) {
3168
+ $this->wasPreviousItemSeparated = true;
3169
+ return;
3170
+ }
3171
+
3172
+ //Skip blacklisted menus.
3173
+ if ( isset($item['url'], $this->blacklist[$item['url']]) ) {
3174
+ return;
3175
+ }
3176
+
3177
+ $name = $this->sanitizeMenuTitle($item['menu_title']);
3178
+ if ( empty($parent) ) {
3179
+ $this->parentNames[$item['file']] = $name;
3180
+ } else {
3181
+ $name = $this->parentNames[$parent] . ' -> ' . $name;
3182
+ }
3183
+
3184
+ $templateId = ameMenuItem::template_id($item);
3185
+ $this->templates[$templateId] = array(
3186
+ 'name' => $name,
3187
+ 'used' => false,
3188
+ 'defaults' => $item,
3189
+ );
3190
+
3191
+ //Remember the relative order of menu items. It's a bit like a linked list.
3192
+ $this->templateOrder[$templateId] = array(
3193
+ 'previous_item' => $this->previousItemId,
3194
+ 'was_previous_item_separated' => $this->wasPreviousItemSeparated,
3195
+ );
3196
+ $this->previousItemId = $templateId;
3197
+ $this->wasPreviousItemSeparated = false;
3198
+ }
3199
+
3200
+ /**
3201
+ * Sanitize a menu title for display.
3202
+ * Removes HTML tags and update notification bubbles.
3203
+ *
3204
+ * @param string $title
3205
+ * @return string
3206
+ */
3207
+ private function sanitizeMenuTitle($title) {
3208
+ return strip_tags( preg_replace('@<span[^>]*>.*</span>@i', '', $title) );
3209
+ }
3210
+
3211
+ public function getRelativeTemplateOrder() {
3212
+ return $this->templateOrder;
3213
+ }
3214
+ }
includes/menu-item.php CHANGED
@@ -11,6 +11,9 @@ abstract class ameMenuItem {
11
  const unclickableTemplateId = '>special:none';
12
  const unclickableTemplateClass = 'ame-unclickable-menu-item';
13
 
 
 
 
14
  /**
15
  * @var array A partial list of files in /wp-admin/. Correct as of WP 3.8-RC1, 2013.12.04.
16
  * When trying to determine if a menu links to one of the default WP admin pages, it's faster
@@ -39,13 +42,13 @@ abstract class ameMenuItem {
39
  static $separator_count = 0;
40
  $default_css_class = empty($parent) ? 'menu-top' : '';
41
  $item = array(
42
- 'menu_title' => $item[0],
43
- 'access_level' => $item[1], //= required capability
44
  'file' => $item[2],
45
- 'page_title' => (isset($item[3]) ? $item[3] : ''),
46
- 'css_class' => (isset($item[4]) ? $item[4] : $default_css_class),
47
- 'hookname' => (isset($item[5]) ? $item[5] : ''), //Used as the ID attr. of the generated HTML tag.
48
- 'icon_url' => (isset($item[6]) ? $item[6] : 'dashicons-admin-generic'),
49
  'position' => $position,
50
  'parent' => $parent,
51
  );
@@ -56,7 +59,7 @@ abstract class ameMenuItem {
56
  }
57
 
58
  if ( empty($parent) ) {
59
- $item['separator'] = empty($item['file']) || empty($item['menu_title']) || (strpos($item['css_class'], 'wp-menu-separator') !== false);
60
  //WP 3.0 in multisite mode has two separators with the same filename. Fix by reindexing separators.
61
  if ( $item['separator'] ) {
62
  $item['file'] = 'separator_' . ($separator_count++);
@@ -101,13 +104,17 @@ abstract class ameMenuItem {
101
  'icon_url' => 'dashicons-admin-generic',
102
  'separator' => false,
103
  'colors' => false,
 
104
 
105
  //Internal fields that may not map directly to WP menu structures.
106
  'open_in' => 'same_window', //'new_window', 'iframe' or 'same_window' (the default)
 
107
  'template_id' => '', //The default menu item that this item is based on.
108
  'is_plugin_page' => false,
109
  'custom' => false,
110
  'url' => '',
 
 
111
  );
112
 
113
  return $basic_defaults;
@@ -125,13 +132,19 @@ abstract class ameMenuItem {
125
  'items' => array(), //List of sub-menu items.
126
  'grant_access' => array(), //Per-role and per-user access. Supersedes role_access.
127
  'colors' => null,
 
128
 
129
  'custom' => false, //True if item is made-from-scratch and has no template.
130
  'missing' => false, //True if our template is no longer present in the default admin menu. Note: Stored values will be ignored. Set upon merging.
131
  'unused' => false, //True if this item was generated from an unused default menu. Note: Stored values will be ignored. Set upon merging.
132
  'hidden' => false, //Hide/show the item. Hiding is purely cosmetic, the item remains accessible.
 
133
  'separator' => false, //True if the item is a menu separator.
134
 
 
 
 
 
135
  'defaults' => self::basic_defaults(),
136
  ));
137
  return $blank_menu;
@@ -147,9 +160,13 @@ abstract class ameMenuItem {
147
  'hookname' => '',
148
  'icon_url' => 'dashicons-admin-generic',
149
  'open_in' => 'same_window',
 
150
  'is_plugin_page' => false,
151
  'page_heading' => '',
152
  'colors' => false,
 
 
 
153
  );
154
  }
155
 
@@ -279,7 +296,10 @@ abstract class ameMenuItem {
279
  */
280
  public static function normalize($item) {
281
  if ( isset($item['defaults']) ) {
282
- $item['defaults'] = array_merge(self::basic_defaults(), $item['defaults']);
 
 
 
283
  }
284
  $item = array_merge(self::blank_menu(), $item);
285
 
@@ -433,7 +453,14 @@ abstract class ameMenuItem {
433
  * @return int
434
  */
435
  public static function compare_position($a, $b){
436
- return self::get($a, 'position', 0) - self::get($b, 'position', 0);
 
 
 
 
 
 
 
437
  }
438
 
439
  /**
@@ -447,10 +474,10 @@ abstract class ameMenuItem {
447
  $menu_url = is_array($item_slug) ? self::get($item_slug, 'file') : $item_slug;
448
  $parent_url = !empty($parent_slug) ? $parent_slug : 'admin.php';
449
 
450
- //Workaround for WooCommerce 2.1.12: For some reason, it uses "&amp;" instead of a plain "&" to separate
451
- //query parameters. We need a plain URL, not a HTML-entity-encoded one.
452
- //It is theoretically possible that another plugin might want to use a literal "&amp;", but its very unlikely.
453
- $menu_url = str_replace('&amp;', '&', $menu_url);
454
 
455
  if ( strpos($menu_url, '://') !== false ) {
456
  return $menu_url;
11
  const unclickableTemplateId = '>special:none';
12
  const unclickableTemplateClass = 'ame-unclickable-menu-item';
13
 
14
+ const embeddedPageTemplateId = '>special:embed_page';
15
+ const embeddedPagePlaceholderHeading = '[Same as menu title]';
16
+
17
  /**
18
  * @var array A partial list of files in /wp-admin/. Correct as of WP 3.8-RC1, 2013.12.04.
19
  * When trying to determine if a menu links to one of the default WP admin pages, it's faster
42
  static $separator_count = 0;
43
  $default_css_class = empty($parent) ? 'menu-top' : '';
44
  $item = array(
45
+ 'menu_title' => strval($item[0]),
46
+ 'access_level' => strval($item[1]), //= required capability
47
  'file' => $item[2],
48
+ 'page_title' => (isset($item[3]) ? strval($item[3]) : ''),
49
+ 'css_class' => (isset($item[4]) ? strval($item[4]) : $default_css_class),
50
+ 'hookname' => (isset($item[5]) ? strval($item[5]) : ''), //Used as the ID attr. of the generated HTML tag.
51
+ 'icon_url' => (isset($item[6]) ? strval($item[6]) : 'dashicons-admin-generic'),
52
  'position' => $position,
53
  'parent' => $parent,
54
  );
59
  }
60
 
61
  if ( empty($parent) ) {
62
+ $item['separator'] = empty($item['file']) || (strpos($item['css_class'], 'wp-menu-separator') !== false);
63
  //WP 3.0 in multisite mode has two separators with the same filename. Fix by reindexing separators.
64
  if ( $item['separator'] ) {
65
  $item['file'] = 'separator_' . ($separator_count++);
104
  'icon_url' => 'dashicons-admin-generic',
105
  'separator' => false,
106
  'colors' => false,
107
+ 'is_always_open' => false,
108
 
109
  //Internal fields that may not map directly to WP menu structures.
110
  'open_in' => 'same_window', //'new_window', 'iframe' or 'same_window' (the default)
111
+ 'iframe_height' => 0,
112
  'template_id' => '', //The default menu item that this item is based on.
113
  'is_plugin_page' => false,
114
  'custom' => false,
115
  'url' => '',
116
+ 'embedded_page_id' => 0,
117
+ 'embedded_page_blog_id' => function_exists('get_current_blog_id') ? get_current_blog_id() : 1,
118
  );
119
 
120
  return $basic_defaults;
132
  'items' => array(), //List of sub-menu items.
133
  'grant_access' => array(), //Per-role and per-user access. Supersedes role_access.
134
  'colors' => null,
135
+ 'is_always_open' => null,
136
 
137
  'custom' => false, //True if item is made-from-scratch and has no template.
138
  'missing' => false, //True if our template is no longer present in the default admin menu. Note: Stored values will be ignored. Set upon merging.
139
  'unused' => false, //True if this item was generated from an unused default menu. Note: Stored values will be ignored. Set upon merging.
140
  'hidden' => false, //Hide/show the item. Hiding is purely cosmetic, the item remains accessible.
141
+ 'hidden_from_actor' => array(), //Like the "hidden" flag, but per-role. Lets the user hide an item without changing its permissions.
142
  'separator' => false, //True if the item is a menu separator.
143
 
144
+ 'restrict_access_to_items' => false, //True = Deny access to all submenu items if the user doesn't have access to this item.
145
+
146
+ 'had_access_before_hiding' => null, //Roles who had access to this item before the user clicked the "hide" button. Usually empty.
147
+
148
  'defaults' => self::basic_defaults(),
149
  ));
150
  return $blank_menu;
160
  'hookname' => '',
161
  'icon_url' => 'dashicons-admin-generic',
162
  'open_in' => 'same_window',
163
+ 'iframe_height' => 0,
164
  'is_plugin_page' => false,
165
  'page_heading' => '',
166
  'colors' => false,
167
+ 'embedded_page_id' => 0,
168
+ 'embedded_page_blog_id' => function_exists('get_current_blog_id') ? get_current_blog_id() : 1,
169
+ 'is_always_open' => false,
170
  );
171
  }
172
 
296
  */
297
  public static function normalize($item) {
298
  if ( isset($item['defaults']) ) {
299
+ $item['defaults'] = array_merge(
300
+ empty($item['custom']) ? self::basic_defaults() : self::custom_item_defaults(),
301
+ $item['defaults']
302
+ );
303
  }
304
  $item = array_merge(self::blank_menu(), $item);
305
 
453
  * @return int
454
  */
455
  public static function compare_position($a, $b){
456
+ $result = self::get($a, 'position', 0) - self::get($b, 'position', 0);
457
+ //Support for non-integer positions.
458
+ if ($result > 0) {
459
+ return 1;
460
+ } else if ($result < 0) {
461
+ return -1;
462
+ }
463
+ return 0;
464
  }
465
 
466
  /**
474
  $menu_url = is_array($item_slug) ? self::get($item_slug, 'file') : $item_slug;
475
  $parent_url = !empty($parent_slug) ? $parent_slug : 'admin.php';
476
 
477
+ //Workaround for components that HTML-entity-encode menu URLs. Most plugins and themes don't do that, but there's
478
+ //at least one plugin that does (WooCommerce). WP core also encodes some menu URLs (e.g. Appearance -> Header).
479
+ //We need a raw URL here.
480
+ $menu_url = html_entity_decode($menu_url);
481
 
482
  if ( strpos($menu_url, '://') !== false ) {
483
  return $menu_url;
includes/menu.php CHANGED
@@ -1,7 +1,7 @@
1
  <?php
2
  abstract class ameMenu {
3
  const format_name = 'Admin Menu Editor menu';
4
- const format_version = '6.0';
5
 
6
  /**
7
  * Load an admin menu from a JSON string.
@@ -79,6 +79,48 @@ abstract class ameMenu {
79
  $menu['color_css_modified'] = isset($arr['color_css_modified']) ? intval($arr['color_css_modified']) : 0;
80
  }
81
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
82
  return $menu;
83
  }
84
 
@@ -141,21 +183,22 @@ abstract class ameMenu {
141
  //Resort all submenus as well
142
  foreach ($tree as &$topmenu){
143
  if (!empty($topmenu['items'])){
144
- uasort($topmenu['items'], 'ameMenuItem::compare_position');
145
  }
146
  }
147
 
148
  return $tree;
149
  }
150
 
151
- /**
152
- * Convert the WP menu structure to the internal representation. All properties set as defaults.
153
- *
154
- * @param array $menu
155
- * @param array $submenu
156
- * @return array Menu in the internal tree format.
157
- */
158
- public static function wp2tree($menu, $submenu){
 
159
  $tree = array();
160
  foreach ($menu as $pos => $item){
161
 
@@ -167,13 +210,28 @@ abstract class ameMenu {
167
  $parent = $tree_item['defaults']['file'];
168
  if ( isset($submenu[$parent]) ){
169
  foreach($submenu[$parent] as $position => $subitem){
 
 
 
 
 
 
 
170
  $tree_item['items'][] = array_merge(
171
  ameMenuItem::blank_menu(),
172
- array('defaults' => ameMenuItem::fromWpItem($subitem, $position, $parent))
173
  );
174
  }
175
  }
176
 
 
 
 
 
 
 
 
 
177
  $tree[$parent] = $tree_item;
178
  }
179
 
@@ -380,5 +438,44 @@ abstract class ameMenu {
380
  }
381
  }
382
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
383
 
384
  class InvalidMenuException extends Exception {}
1
  <?php
2
  abstract class ameMenu {
3
  const format_name = 'Admin Menu Editor menu';
4
+ const format_version = '6.3';
5
 
6
  /**
7
  * Load an admin menu from a JSON string.
79
  $menu['color_css_modified'] = isset($arr['color_css_modified']) ? intval($arr['color_css_modified']) : 0;
80
  }
81
 
82
+ //Sanitize color presets.
83
+ if ( isset($arr['color_presets']) && is_array($arr['color_presets']) ) {
84
+ $color_presets = array();
85
+
86
+ foreach($arr['color_presets'] as $name => $preset) {
87
+ $name = substr(trim(strip_tags(strval($name))), 0, 250);
88
+ if ( empty($name) || !is_array($preset) ) {
89
+ continue;
90
+ }
91
+
92
+ //Each color must be a hexadecimal HTML color code. For example: "#12456"
93
+ $is_valid_preset = true;
94
+ foreach($preset as $property => $color) {
95
+ //Note: It would good to check $property against a list of known color names.
96
+ if ( !is_string($property) || !is_string($color) || !preg_match('/^\#[0-9a-f]{6}$/i', $color) ) {
97
+ $is_valid_preset = false;
98
+ break;
99
+ }
100
+ }
101
+
102
+ if ( $is_valid_preset ) {
103
+ $color_presets[$name] = $preset;
104
+ }
105
+ }
106
+
107
+ $menu['color_presets'] = $color_presets;
108
+ }
109
+
110
+ //Copy directly granted capabilities.
111
+ if ( isset($arr['granted_capabilities']) && is_array($arr['granted_capabilities']) ) {
112
+ $granted_capabilities = array();
113
+ foreach($arr['granted_capabilities'] as $actor => $capabilities) {
114
+ //Skip empty lists to avoid problems with {} => [] and to save space.
115
+ if ( !empty($capabilities) ) {
116
+ $granted_capabilities[strval($actor)] = $capabilities;
117
+ }
118
+ }
119
+ if (!empty($granted_capabilities)) {
120
+ $menu['granted_capabilities'] = $granted_capabilities;
121
+ }
122
+ }
123
+
124
  return $menu;
125
  }
126
 
183
  //Resort all submenus as well
184
  foreach ($tree as &$topmenu){
185
  if (!empty($topmenu['items'])){
186
+ usort($topmenu['items'], 'ameMenuItem::compare_position');
187
  }
188
  }
189
 
190
  return $tree;
191
  }
192
 
193
+ /**
194
+ * Convert the WP menu structure to the internal representation. All properties set as defaults.
195
+ *
196
+ * @param array $menu
197
+ * @param array $submenu
198
+ * @param array $blacklist
199
+ * @return array Menu in the internal tree format.
200
+ */
201
+ public static function wp2tree($menu, $submenu, $blacklist = array()){
202
  $tree = array();
203
  foreach ($menu as $pos => $item){
204
 
210
  $parent = $tree_item['defaults']['file'];
211
  if ( isset($submenu[$parent]) ){
212
  foreach($submenu[$parent] as $position => $subitem){
213
+ $defaults = ameMenuItem::fromWpItem($subitem, $position, $parent);
214
+
215
+ //Skip blacklisted items.
216
+ if ( isset($defaults['url'], $blacklist[$defaults['url']]) ) {
217
+ continue;
218
+ }
219
+
220
  $tree_item['items'][] = array_merge(
221
  ameMenuItem::blank_menu(),
222
+ array('defaults' => $defaults)
223
  );
224
  }
225
  }
226
 
227
+ //Skip blacklisted top level menus (only if they have no submenus).
228
+ if (
229
+ empty($tree_item['items'])
230
+ && isset($tree_item['defaults']['url'], $blacklist[$tree_item['defaults']['url']])
231
+ ) {
232
+ continue;
233
+ }
234
+
235
  $tree[$parent] = $tree_item;
236
  }
237
 
438
  }
439
  }
440
 
441
+ class ameGrantedCapabilityFilter {
442
+ private $post_types = array();
443
+ private $taxonomies = array();
444
+
445
+ public function __construct() {
446
+ $this->post_types = get_post_types(array('public' => true, 'show_ui' => true), 'names', 'or');
447
+ $this->taxonomies = get_taxonomies(array('public' => true, 'show_ui' => true), 'names', 'or');
448
+ }
449
+
450
+ /**
451
+ * Remove capabilities that refer to unregistered post types or taxonomies.
452
+ *
453
+ * @param array $granted_capabilities
454
+ * @return array
455
+ */
456
+ public function clean_up($granted_capabilities) {
457
+ $clean = array();
458
+ foreach($granted_capabilities as $actor => $capabilities) {
459
+ $clean[$actor] = array_filter($capabilities, array($this, 'is_registered_source'));
460
+ }
461
+ return $clean;
462
+ }
463
+
464
+ private function is_registered_source($grant) {
465
+ if ( !is_array($grant) || !isset($grant[1]) ) {
466
+ return true;
467
+ }
468
+
469
+ if ( isset($grant[2]) ) {
470
+ if ( $grant[1] === 'post_type' ) {
471
+ return array_key_exists($grant[2], $this->post_types);
472
+ } else if ( $grant[1] === 'taxonomy' ) {
473
+ return array_key_exists($grant[2], $this->taxonomies);
474
+ }
475
+ }
476
+ return false;
477
+ }
478
+ }
479
+
480
 
481
  class InvalidMenuException extends Exception {}
includes/role-utils.php CHANGED
@@ -47,7 +47,7 @@ class ameRoleUtils {
47
  }
48
 
49
  /**
50
- * Get all defined WordPress roles.
51
  *
52
  * @global WP_Roles $wp_roles
53
  * @return WP_Roles
@@ -57,7 +57,6 @@ class ameRoleUtils {
57
  if ( !isset($wp_roles) ) {
58
  $wp_roles = new WP_Roles();
59
  }
60
- //TODO: Do something about Super Admin
61
  return $wp_roles;
62
  }
63
  }
47
  }
48
 
49
  /**
50
+ * Get the global WP_Roles instance.
51
  *
52
  * @global WP_Roles $wp_roles
53
  * @return WP_Roles
57
  if ( !isset($wp_roles) ) {
58
  $wp_roles = new WP_Roles();
59
  }
 
60
  return $wp_roles;
61
  }
62
  }
includes/settings-page.php CHANGED
@@ -16,12 +16,11 @@ $isProVersion = apply_filters('admin_menu_editor_is_pro', false);
16
  ?>
17
 
18
  <div class="wrap">
19
- <?php screen_icon(); ?>
20
- <h2>
21
  <?php echo apply_filters('admin_menu_editor-self_page_title', 'Menu Editor'); ?> Settings
22
  <a href="<?php echo esc_attr($editor_page_url); ?>" class="add-new-h2"
23
  title="Back to Admin Menu Editor">Editor</a>
24
- </h2>
25
 
26
  <form method="post" action="<?php echo esc_attr($formActionUrl); ?>" id="ws_plugin_settings_form">
27
 
@@ -138,10 +137,10 @@ $isProVersion = apply_filters('admin_menu_editor_is_pro', false);
138
  <label>
139
  <input type="checkbox" name="show_deprecated_hide_button"
140
  <?php checked($settings['show_deprecated_hide_button']); ?>>
141
- Enable the "Show/Hide" toolbar button (not recommended)
142
  </label>
143
  <br><span class="description">
144
- This feature is deprecated and is only kept for backwards compatibility purposes.
145
  </span>
146
  </p>
147
  <?php endif; ?>
@@ -160,6 +159,14 @@ $isProVersion = apply_filters('admin_menu_editor_is_pro', false);
160
  </label>
161
  </p>
162
 
 
 
 
 
 
 
 
 
163
  <p>
164
  <label>
165
  <input type="radio" name="ui_colour_scheme" value="wp-grey"
@@ -204,6 +211,50 @@ $isProVersion = apply_filters('admin_menu_editor_is_pro', false);
204
  </tr>
205
  <?php endif; ?>
206
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
207
  <tr>
208
  <th scope="row">Debugging</th>
209
  <td>
@@ -231,4 +282,15 @@ $isProVersion = apply_filters('admin_menu_editor_is_pro', false);
231
  ?>
232
  </form>
233
 
234
- </div>
 
 
 
 
 
 
 
 
 
 
 
16
  ?>
17
 
18
  <div class="wrap">
19
+ <<?php echo WPMenuEditor::$admin_heading_tag; ?>>
 
20
  <?php echo apply_filters('admin_menu_editor-self_page_title', 'Menu Editor'); ?> Settings
21
  <a href="<?php echo esc_attr($editor_page_url); ?>" class="add-new-h2"
22
  title="Back to Admin Menu Editor">Editor</a>
23
+ </<?php echo WPMenuEditor::$admin_heading_tag; ?>>
24
 
25
  <form method="post" action="<?php echo esc_attr($formActionUrl); ?>" id="ws_plugin_settings_form">
26
 
137
  <label>
138
  <input type="checkbox" name="show_deprecated_hide_button"
139
  <?php checked($settings['show_deprecated_hide_button']); ?>>
140
+ Enable the "Hide (cosmetic)" toolbar button
141
  </label>
142
  <br><span class="description">
143
+ This button hides the selected menu item without making it inaccessible.
144
  </span>
145
  </p>
146
  <?php endif; ?>
159
  </label>
160
  </p>
161
 
162
+ <p>
163
+ <label>
164
+ <input type="radio" name="ui_colour_scheme" value="modern-one"
165
+ <?php checked('modern-one', $settings['ui_colour_scheme']); ?>>
166
+ Modern
167
+ </label>
168
+ </p>
169
+
170
  <p>
171
  <label>
172
  <input type="radio" name="ui_colour_scheme" value="wp-grey"
211
  </tr>
212
  <?php endif; ?>
213
 
214
+ <tr>
215
+ <th scope="row">
216
+ New menu position
217
+ <a class="ws_tooltip_trigger"
218
+ title="This setting controls the position of menu items that are not present in the last saved menu
219
+ configuration.
220
+ &lt;br&gt;&lt;br&gt;
221
+ This includes new menus added by plugins and themes.
222
+ In Multisite, it also applies to menus that exist only on certain sites but not on all sites.
223
+ It doesn't affect menu items that you add through the Admin Menu Editor interface.">
224
+ <div class="dashicons dashicons-info"></div>
225
+ </a>
226
+ </th>
227
+ <td>
228
+ <fieldset>
229
+ <p>
230
+ <label>
231
+ <input type="radio" name="unused_item_position" value="relative"
232
+ <?php checked('relative', $settings['unused_item_position']); ?>>
233
+ Maintain relative order
234
+
235
+ <br><span class="description">
236
+ Attempts to put new items in the same relative positions
237
+ as they would be in in the default admin menu.
238
+ </span>
239
+ </label>
240
+ </p>
241
+
242
+ <p>
243
+ <label>
244
+ <input type="radio" name="unused_item_position" value="bottom"
245
+ <?php checked('bottom', $settings['unused_item_position']); ?>>
246
+ Bottom
247
+
248
+ <br><span class="description">
249
+ Puts new items at the bottom of the admin menu.
250
+ </span>
251
+ </label>
252
+ </p>
253
+
254
+ </fieldset>
255
+ </td>
256
+ </tr>
257
+
258
  <tr>
259
  <th scope="row">Debugging</th>
260
  <td>
282
  ?>
283
  </form>
284
 
285
+ </div>
286
+
287
+ <script type="text/javascript">
288
+ jQuery(function($) {
289
+ //Set up tooltips
290
+ $('.ws_tooltip_trigger').qtip({
291
+ style: {
292
+ classes: 'qtip qtip-rounded ws_tooltip_node ws_wide_tooltip'
293
+ }
294
+ });
295
+ });
296
+ </script>
js/admin-helpers.js CHANGED
@@ -5,11 +5,12 @@
5
 
6
  //The page heading is typically hardcoded and/or not configurable, so we need to use JS to change it.
7
  var customPageHeading = currentItem.hasOwnProperty('customPageHeading') ? currentItem['customPageHeading'] : null;
 
8
  var ameHideHeading = null;
9
  if ( customPageHeading ) {
10
  //Temporarily hide the heading to prevent the original text from showing up briefly
11
  //before being replaced when the DOM is ready (see below).
12
- ameHideHeading = $('<style type="text/css">.wrap > h2:first-child { visibility: hidden; }</style>')
13
  .appendTo('head');
14
  }
15
 
@@ -24,19 +25,33 @@
24
  })
25
  .closest('li').addClass('ws-submenu-separator-wrap');
26
 
27
- //Menus with the target "< None >" also shouldn't be clickable.
28
  adminMenu
29
  .find(
30
  'a.menu-top.ame-unclickable-menu-item, ul.wp-submenu > li > a[href^="#ame-unclickable-menu-item"]'
31
  )
32
  .click(function() {
 
 
 
 
 
 
 
 
33
  return false;
34
  });
35
 
 
 
 
 
 
 
36
  //Replace the original page heading with the custom heading.
37
  if ( customPageHeading ) {
38
  function replaceAdminPageHeading(newText) {
39
- var headingText = $('.wrap > h2:first')
40
  .contents()
41
  .filter(function() {
42
  //Find text nodes.
5
 
6
  //The page heading is typically hardcoded and/or not configurable, so we need to use JS to change it.
7
  var customPageHeading = currentItem.hasOwnProperty('customPageHeading') ? currentItem['customPageHeading'] : null;
8
+ var pageHeadingSelector = currentItem.hasOwnProperty('pageHeadingSelector') ? currentItem['pageHeadingSelector'] : '.wrap > h2:first';
9
  var ameHideHeading = null;
10
  if ( customPageHeading ) {
11
  //Temporarily hide the heading to prevent the original text from showing up briefly
12
  //before being replaced when the DOM is ready (see below).
13
+ ameHideHeading = $('<style type="text/css">.wrap > h2:first-child,.wrap > h1:first-child { visibility: hidden; }</style>')
14
  .appendTo('head');
15
  }
16
 
25
  })
26
  .closest('li').addClass('ws-submenu-separator-wrap');
27
 
28
+ //Menus with the target "< None >" also shouldn't be clickable.
29
  adminMenu
30
  .find(
31
  'a.menu-top.ame-unclickable-menu-item, ul.wp-submenu > li > a[href^="#ame-unclickable-menu-item"]'
32
  )
33
  .click(function() {
34
+ //Exception: At small viewport sizes, WordPress changes how top level menus work: clicking a menu
35
+ //now expands its submenu. We must not ignore that click or it will be impossible to expand the menu.
36
+ var viewportWidth = Math.max(document.documentElement.clientWidth, window.innerWidth || 0);
37
+ if ((viewportWidth <= 782) && $(this).closest('li').hasClass('wp-has-submenu')) {
38
+ return;
39
+ }
40
+
41
+ //Ignore the click.
42
  return false;
43
  });
44
 
45
+ //Open submenus that have the "always open" flag.
46
+ adminMenu
47
+ .find('li.ws-ame-has-always-open-submenu.wp-has-submenu')
48
+ .addClass('wp-has-current-submenu')
49
+ .removeClass('wp-not-current-submenu');
50
+
51
  //Replace the original page heading with the custom heading.
52
  if ( customPageHeading ) {
53
  function replaceAdminPageHeading(newText) {
54
+ var headingText = $(pageHeadingSelector)
55
  .contents()
56
  .filter(function() {
57
  //Find text nodes.
js/lodash.js ADDED
@@ -0,0 +1,12557 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /**
2
+ * @license
3
+ * lodash 3.10.1 (Custom Build) <https://lodash.com/>
4
+ * Build: `lodash compat -o ./lodash.js`
5
+ * Copyright 2012-2015 The Dojo Foundation <http://dojofoundation.org/>
6
+ * Based on Underscore.js 1.8.3 <http://underscorejs.org/LICENSE>
7
+ * Copyright 2009-2015 Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors
8
+ * Available under MIT license <https://lodash.com/license>
9
+ */
10
+ ;(function() {
11
+
12
+ /** Used as a safe reference for `undefined` in pre-ES5 environments. */
13
+ var undefined;
14
+
15
+ /** Used as the semantic version number. */
16
+ var VERSION = '3.10.1';
17
+
18
+ /** Used to compose bitmasks for wrapper metadata. */
19
+ var BIND_FLAG = 1,
20
+ BIND_KEY_FLAG = 2,
21
+ CURRY_BOUND_FLAG = 4,
22
+ CURRY_FLAG = 8,
23
+ CURRY_RIGHT_FLAG = 16,
24
+ PARTIAL_FLAG = 32,
25
+ PARTIAL_RIGHT_FLAG = 64,
26
+ ARY_FLAG = 128,
27
+ REARG_FLAG = 256;
28
+
29
+ /** Used as default options for `_.trunc`. */
30
+ var DEFAULT_TRUNC_LENGTH = 30,
31
+ DEFAULT_TRUNC_OMISSION = '...';
32
+
33
+ /** Used to detect when a function becomes hot. */
34
+ var HOT_COUNT = 150,
35
+ HOT_SPAN = 16;
36
+
37
+ /** Used as the size to enable large array optimizations. */
38
+ var LARGE_ARRAY_SIZE = 200;
39
+
40
+ /** Used to indicate the type of lazy iteratees. */
41
+ var LAZY_FILTER_FLAG = 1,
42
+ LAZY_MAP_FLAG = 2;
43
+
44
+ /** Used as the `TypeError` message for "Functions" methods. */
45
+ var FUNC_ERROR_TEXT = 'Expected a function';
46
+
47
+ /** Used as the internal argument placeholder. */
48
+ var PLACEHOLDER = '__lodash_placeholder__';
49
+
50
+ /** `Object#toString` result references. */
51
+ var argsTag = '[object Arguments]',
52
+ arrayTag = '[object Array]',
53
+ boolTag = '[object Boolean]',
54
+ dateTag = '[object Date]',
55
+ errorTag = '[object Error]',
56
+ funcTag = '[object Function]',
57
+ mapTag = '[object Map]',
58
+ numberTag = '[object Number]',
59
+ objectTag = '[object Object]',
60
+ regexpTag = '[object RegExp]',
61
+ setTag = '[object Set]',
62
+ stringTag = '[object String]',
63
+ weakMapTag = '[object WeakMap]';
64
+
65
+ var arrayBufferTag = '[object ArrayBuffer]',
66
+ float32Tag = '[object Float32Array]',
67
+ float64Tag = '[object Float64Array]',
68
+ int8Tag = '[object Int8Array]',
69
+ int16Tag = '[object Int16Array]',
70
+ int32Tag = '[object Int32Array]',
71
+ uint8Tag = '[object Uint8Array]',
72
+ uint8ClampedTag = '[object Uint8ClampedArray]',
73
+ uint16Tag = '[object Uint16Array]',
74
+ uint32Tag = '[object Uint32Array]';
75
+
76
+ /** Used to match empty string literals in compiled template source. */
77
+ var reEmptyStringLeading = /\b__p \+= '';/g,
78
+ reEmptyStringMiddle = /\b(__p \+=) '' \+/g,
79
+ reEmptyStringTrailing = /(__e\(.*?\)|\b__t\)) \+\n'';/g;
80
+
81
+ /** Used to match HTML entities and HTML characters. */
82
+ var reEscapedHtml = /&(?:amp|lt|gt|quot|#39|#96);/g,
83
+ reUnescapedHtml = /[&<>"'`]/g,
84
+ reHasEscapedHtml = RegExp(reEscapedHtml.source),
85
+ reHasUnescapedHtml = RegExp(reUnescapedHtml.source);
86
+
87
+ /** Used to match template delimiters. */
88
+ var reEscape = /<%-([\s\S]+?)%>/g,
89
+ reEvaluate = /<%([\s\S]+?)%>/g,
90
+ reInterpolate = /<%=([\s\S]+?)%>/g;
91
+
92
+ /** Used to match property names within property paths. */
93
+ var reIsDeepProp = /\.|\[(?:[^[\]]*|(["'])(?:(?!\1)[^\n\\]|\\.)*?\1)\]/,
94
+ reIsPlainProp = /^\w*$/,
95
+ rePropName = /[^.[\]]+|\[(?:(-?\d+(?:\.\d+)?)|(["'])((?:(?!\2)[^\n\\]|\\.)*?)\2)\]/g;
96
+
97
+ /**
98
+ * Used to match `RegExp` [syntax characters](http://ecma-international.org/ecma-262/6.0/#sec-patterns)
99
+ * and those outlined by [`EscapeRegExpPattern`](http://ecma-international.org/ecma-262/6.0/#sec-escaperegexppattern).
100
+ */
101
+ var reRegExpChars = /^[:!,]|[\\^$.*+?()[\]{}|\/]|(^[0-9a-fA-Fnrtuvx])|([\n\r\u2028\u2029])/g,
102
+ reHasRegExpChars = RegExp(reRegExpChars.source);
103
+
104
+ /** Used to match [combining diacritical marks](https://en.wikipedia.org/wiki/Combining_Diacritical_Marks). */
105
+ var reComboMark = /[\u0300-\u036f\ufe20-\ufe23]/g;
106
+
107
+ /** Used to match backslashes in property paths. */
108
+ var reEscapeChar = /\\(\\)?/g;
109
+
110
+ /** Used to match [ES template delimiters](http://ecma-international.org/ecma-262/6.0/#sec-template-literal-lexical-components). */
111
+ var reEsTemplate = /\$\{([^\\}]*(?:\\.[^\\}]*)*)\}/g;
112
+
113
+ /** Used to match `RegExp` flags from their coerced string values. */
114
+ var reFlags = /\w*$/;
115
+
116
+ /** Used to detect hexadecimal string values. */
117
+ var reHasHexPrefix = /^0[xX]/;
118
+
119
+ /** Used to detect host constructors (Safari > 5). */
120
+ var reIsHostCtor = /^\[object .+?Constructor\]$/;
121
+
122
+ /** Used to detect unsigned integer values. */
123
+ var reIsUint = /^\d+$/;
124
+
125
+ /** Used to match latin-1 supplementary letters (excluding mathematical operators). */
126
+ var reLatin1 = /[\xc0-\xd6\xd8-\xde\xdf-\xf6\xf8-\xff]/g;
127
+
128
+ /** Used to ensure capturing order of template delimiters. */
129
+ var reNoMatch = /($^)/;
130
+
131
+ /** Used to match unescaped characters in compiled string literals. */
132
+ var reUnescapedString = /['\n\r\u2028\u2029\\]/g;
133
+
134
+ /** Used to match words to create compound words. */
135
+ var reWords = (function() {
136
+ var upper = '[A-Z\\xc0-\\xd6\\xd8-\\xde]',
137
+ lower = '[a-z\\xdf-\\xf6\\xf8-\\xff]+';
138
+
139
+ return RegExp(upper + '+(?=' + upper + lower + ')|' + upper + '?' + lower + '|' + upper + '+|[0-9]+', 'g');
140
+ }());
141
+
142
+ /** Used to assign default `context` object properties. */
143
+ var contextProps = [
144
+ 'Array', 'ArrayBuffer', 'Date', 'Error', 'Float32Array', 'Float64Array',
145
+ 'Function', 'Int8Array', 'Int16Array', 'Int32Array', 'Math', 'Number',
146
+ 'Object', 'RegExp', 'Set', 'String', '_', 'clearTimeout', 'isFinite',
147
+ 'parseFloat', 'parseInt', 'setTimeout', 'TypeError', 'Uint8Array',
148
+ 'Uint8ClampedArray', 'Uint16Array', 'Uint32Array', 'WeakMap'
149
+ ];
150
+
151
+ /** Used to fix the JScript `[[DontEnum]]` bug. */
152
+ var shadowProps = [
153
+ 'constructor', 'hasOwnProperty', 'isPrototypeOf', 'propertyIsEnumerable',
154
+ 'toLocaleString', 'toString', 'valueOf'
155
+ ];
156
+
157
+ /** Used to make template sourceURLs easier to identify. */
158
+ var templateCounter = -1;
159
+
160
+ /** Used to identify `toStringTag` values of typed arrays. */
161
+ var typedArrayTags = {};
162
+ typedArrayTags[float32Tag] = typedArrayTags[float64Tag] =
163
+ typedArrayTags[int8Tag] = typedArrayTags[int16Tag] =
164
+ typedArrayTags[int32Tag] = typedArrayTags[uint8Tag] =
165
+ typedArrayTags[uint8ClampedTag] = typedArrayTags[uint16Tag] =
166
+ typedArrayTags[uint32Tag] = true;
167
+ typedArrayTags[argsTag] = typedArrayTags[arrayTag] =
168
+ typedArrayTags[arrayBufferTag] = typedArrayTags[boolTag] =
169
+ typedArrayTags[dateTag] = typedArrayTags[errorTag] =
170
+ typedArrayTags[funcTag] = typedArrayTags[mapTag] =
171
+ typedArrayTags[numberTag] = typedArrayTags[objectTag] =
172
+ typedArrayTags[regexpTag] = typedArrayTags[setTag] =
173
+ typedArrayTags[stringTag] = typedArrayTags[weakMapTag] = false;
174
+
175
+ /** Used to identify `toStringTag` values supported by `_.clone`. */
176
+ var cloneableTags = {};
177
+ cloneableTags[argsTag] = cloneableTags[arrayTag] =
178
+ cloneableTags[arrayBufferTag] = cloneableTags[boolTag] =
179
+ cloneableTags[dateTag] = cloneableTags[float32Tag] =
180
+ cloneableTags[float64Tag] = cloneableTags[int8Tag] =
181
+ cloneableTags[int16Tag] = cloneableTags[int32Tag] =
182
+ cloneableTags[numberTag] = cloneableTags[objectTag] =
183
+ cloneableTags[regexpTag] = cloneableTags[stringTag] =
184
+ cloneableTags[uint8Tag] = cloneableTags[uint8ClampedTag] =
185
+ cloneableTags[uint16Tag] = cloneableTags[uint32Tag] = true;
186
+ cloneableTags[errorTag] = cloneableTags[funcTag] =
187
+ cloneableTags[mapTag] = cloneableTags[setTag] =
188
+ cloneableTags[weakMapTag] = false;
189
+
190
+ /** Used to map latin-1 supplementary letters to basic latin letters. */
191
+ var deburredLetters = {
192
+ '\xc0': 'A', '\xc1': 'A', '\xc2': 'A', '\xc3': 'A', '\xc4': 'A', '\xc5': 'A',
193
+ '\xe0': 'a', '\xe1': 'a', '\xe2': 'a', '\xe3': 'a', '\xe4': 'a', '\xe5': 'a',
194
+ '\xc7': 'C', '\xe7': 'c',
195
+ '\xd0': 'D', '\xf0': 'd',
196
+ '\xc8': 'E', '\xc9': 'E', '\xca': 'E', '\xcb': 'E',
197
+ '\xe8': 'e', '\xe9': 'e', '\xea': 'e', '\xeb': 'e',
198
+ '\xcC': 'I', '\xcd': 'I', '\xce': 'I', '\xcf': 'I',
199
+ '\xeC': 'i', '\xed': 'i', '\xee': 'i', '\xef': 'i',
200
+ '\xd1': 'N', '\xf1': 'n',
201
+ '\xd2': 'O', '\xd3': 'O', '\xd4': 'O', '\xd5': 'O', '\xd6': 'O', '\xd8': 'O',
202
+ '\xf2': 'o', '\xf3': 'o', '\xf4': 'o', '\xf5': 'o', '\xf6': 'o', '\xf8': 'o',
203
+ '\xd9': 'U', '\xda': 'U', '\xdb': 'U', '\xdc': 'U',
204
+ '\xf9': 'u', '\xfa': 'u', '\xfb': 'u', '\xfc': 'u',
205
+ '\xdd': 'Y', '\xfd': 'y', '\xff': 'y',
206
+ '\xc6': 'Ae', '\xe6': 'ae',
207
+ '\xde': 'Th', '\xfe': 'th',
208
+ '\xdf': 'ss'
209
+ };
210
+
211
+ /** Used to map characters to HTML entities. */
212
+ var htmlEscapes = {
213
+ '&': '&amp;',
214
+ '<': '&lt;',
215
+ '>': '&gt;',
216
+ '"': '&quot;',
217
+ "'": '&#39;',
218
+ '`': '&#96;'
219
+ };
220
+
221
+ /** Used to map HTML entities to characters. */
222
+ var htmlUnescapes = {
223
+ '&amp;': '&',
224
+ '&lt;': '<',
225
+ '&gt;': '>',
226
+ '&quot;': '"',
227
+ '&#39;': "'",
228
+ '&#96;': '`'
229
+ };
230
+
231
+ /** Used to determine if values are of the language type `Object`. */
232
+ var objectTypes = {
233
+ 'function': true,
234
+ 'object': true
235
+ };
236
+
237
+ /** Used to escape characters for inclusion in compiled regexes. */
238
+ var regexpEscapes = {
239
+ '0': 'x30', '1': 'x31', '2': 'x32', '3': 'x33', '4': 'x34',
240
+ '5': 'x35', '6': 'x36', '7': 'x37', '8': 'x38', '9': 'x39',
241
+ 'A': 'x41', 'B': 'x42', 'C': 'x43', 'D': 'x44', 'E': 'x45', 'F': 'x46',
242
+ 'a': 'x61', 'b': 'x62', 'c': 'x63', 'd': 'x64', 'e': 'x65', 'f': 'x66',
243
+ 'n': 'x6e', 'r': 'x72', 't': 'x74', 'u': 'x75', 'v': 'x76', 'x': 'x78'
244
+ };
245
+
246
+ /** Used to escape characters for inclusion in compiled string literals. */
247
+ var stringEscapes = {
248
+ '\\': '\\',
249
+ "'": "'",
250
+ '\n': 'n',
251
+ '\r': 'r',
252
+ '\u2028': 'u2028',
253
+ '\u2029': 'u2029'
254
+ };
255
+
256
+ /** Detect free variable `exports`. */
257
+ var freeExports = objectTypes[typeof exports] && exports && !exports.nodeType && exports;
258
+
259
+ /** Detect free variable `module`. */
260
+ var freeModule = objectTypes[typeof module] && module && !module.nodeType && module;
261
+
262
+ /** Detect free variable `global` from Node.js. */
263
+ var freeGlobal = freeExports && freeModule && typeof global == 'object' && global && global.Object && global;
264
+
265
+ /** Detect free variable `self`. */
266
+ var freeSelf = objectTypes[typeof self] && self && self.Object && self;
267
+
268
+ /** Detect free variable `window`. */
269
+ var freeWindow = objectTypes[typeof window] && window && window.Object && window;
270
+
271
+ /** Detect the popular CommonJS extension `module.exports`. */
272
+ var moduleExports = freeModule && freeModule.exports === freeExports && freeExports;
273
+
274
+ /**
275
+ * Used as a reference to the global object.
276
+ *
277
+ * The `this` value is used if it's the global object to avoid Greasemonkey's
278
+ * restricted `window` object, otherwise the `window` object is used.
279
+ */
280
+ var root = freeGlobal || ((freeWindow !== (this && this.window)) && freeWindow) || freeSelf || this;
281
+
282
+ /*--------------------------------------------------------------------------*/
283
+
284
+ /**
285
+ * The base implementation of `compareAscending` which compares values and
286
+ * sorts them in ascending order without guaranteeing a stable sort.
287
+ *
288
+ * @private
289
+ * @param {*} value The value to compare.
290
+ * @param {*} other The other value to compare.
291
+ * @returns {number} Returns the sort order indicator for `value`.
292
+ */
293
+ function baseCompareAscending(value, other) {
294
+ if (value !== other) {
295
+ var valIsNull = value === null,
296
+ valIsUndef = value === undefined,
297
+ valIsReflexive = value === value;
298
+
299
+ var othIsNull = other === null,
300
+ othIsUndef = other === undefined,
301
+ othIsReflexive = other === other;
302
+
303
+ if ((value > other && !othIsNull) || !valIsReflexive ||
304
+ (valIsNull && !othIsUndef && othIsReflexive) ||
305
+ (valIsUndef && othIsReflexive)) {
306
+ return 1;
307
+ }
308
+ if ((value < other && !valIsNull) || !othIsReflexive ||
309
+ (othIsNull && !valIsUndef && valIsReflexive) ||
310
+ (othIsUndef && valIsReflexive)) {
311
+ return -1;
312
+ }
313
+ }
314
+ return 0;
315
+ }
316
+
317
+ /**
318
+ * The base implementation of `_.findIndex` and `_.findLastIndex` without
319
+ * support for callback shorthands and `this` binding.
320
+ *
321
+ * @private
322
+ * @param {Array} array The array to search.
323
+ * @param {Function} predicate The function invoked per iteration.
324
+ * @param {boolean} [fromRight] Specify iterating from right to left.
325
+ * @returns {number} Returns the index of the matched value, else `-1`.
326
+ */
327
+ function baseFindIndex(array, predicate, fromRight) {
328
+ var length = array.length,
329
+ index = fromRight ? length : -1;
330
+
331
+ while ((fromRight ? index-- : ++index < length)) {
332
+ if (predicate(array[index], index, array)) {
333
+ return index;
334
+ }
335
+ }
336
+ return -1;
337
+ }
338
+
339
+ /**
340
+ * The base implementation of `_.indexOf` without support for binary searches.
341
+ *
342
+ * @private
343
+ * @param {Array} array The array to search.
344
+ * @param {*} value The value to search for.
345
+ * @param {number} fromIndex The index to search from.
346
+ * @returns {number} Returns the index of the matched value, else `-1`.
347
+ */
348
+ function baseIndexOf(array, value, fromIndex) {
349
+ if (value !== value) {
350
+ return indexOfNaN(array, fromIndex);
351
+ }
352
+ var index = fromIndex - 1,
353
+ length = array.length;
354
+
355
+ while (++index < length) {
356
+ if (array[index] === value) {
357
+ return index;
358
+ }
359
+ }
360
+ return -1;
361
+ }
362
+
363
+ /**
364
+ * The base implementation of `_.isFunction` without support for environments
365
+ * with incorrect `typeof` results.
366
+ *
367
+ * @private
368
+ * @param {*} value The value to check.
369
+ * @returns {boolean} Returns `true` if `value` is correctly classified, else `false`.
370
+ */
371
+ function baseIsFunction(value) {
372
+ // Avoid a Chakra JIT bug in compatibility modes of IE 11.
373
+ // See https://github.com/jashkenas/underscore/issues/1621 for more details.
374
+ return typeof value == 'function' || false;
375
+ }
376
+
377
+ /**
378
+ * Converts `value` to a string if it's not one. An empty string is returned
379
+ * for `null` or `undefined` values.
380
+ *
381
+ * @private
382
+ * @param {*} value The value to process.
383
+ * @returns {string} Returns the string.
384
+ */
385
+ function baseToString(value) {
386
+ return value == null ? '' : (value + '');
387
+ }
388
+
389
+ /**
390
+ * Used by `_.trim` and `_.trimLeft` to get the index of the first character
391
+ * of `string` that is not found in `chars`.
392
+ *
393
+ * @private
394
+ * @param {string} string The string to inspect.
395
+ * @param {string} chars The characters to find.
396
+ * @returns {number} Returns the index of the first character not found in `chars`.
397
+ */
398
+ function charsLeftIndex(string, chars) {
399
+ var index = -1,
400
+ length = string.length;
401
+
402
+ while (++index < length && chars.indexOf(string.charAt(index)) > -1) {}
403
+ return index;
404
+ }
405
+
406
+ /**
407
+ * Used by `_.trim` and `_.trimRight` to get the index of the last character
408
+ * of `string` that is not found in `chars`.
409
+ *
410
+ * @private
411
+ * @param {string} string The string to inspect.
412
+ * @param {string} chars The characters to find.
413
+ * @returns {number} Returns the index of the last character not found in `chars`.
414
+ */
415
+ function charsRightIndex(string, chars) {
416
+ var index = string.length;
417
+
418
+ while (index-- && chars.indexOf(string.charAt(index)) > -1) {}
419
+ return index;
420
+ }
421
+
422
+ /**
423
+ * Used by `_.sortBy` to compare transformed elements of a collection and stable
424
+ * sort them in ascending order.
425
+ *
426
+ * @private
427
+ * @param {Object} object The object to compare.
428
+ * @param {Object} other The other object to compare.
429
+ * @returns {number} Returns the sort order indicator for `object`.
430
+ */
431
+ function compareAscending(object, other) {
432
+ return baseCompareAscending(object.criteria, other.criteria) || (object.index - other.index);
433
+ }
434
+
435
+ /**
436
+ * Used by `_.sortByOrder` to compare multiple properties of a value to another
437
+ * and stable sort them.
438
+ *
439
+ * If `orders` is unspecified, all valuess are sorted in ascending order. Otherwise,
440
+ * a value is sorted in ascending order if its corresponding order is "asc", and
441
+ * descending if "desc".
442
+ *
443
+ * @private
444
+ * @param {Object} object The object to compare.
445
+ * @param {Object} other The other object to compare.
446
+ * @param {boolean[]} orders The order to sort by for each property.
447
+ * @returns {number} Returns the sort order indicator for `object`.
448
+ */
449
+ function compareMultiple(object, other, orders) {
450
+ var index = -1,
451
+ objCriteria = object.criteria,
452
+ othCriteria = other.criteria,
453
+ length = objCriteria.length,
454
+ ordersLength = orders.length;
455
+
456
+ while (++index < length) {
457
+ var result = baseCompareAscending(objCriteria[index], othCriteria[index]);
458
+ if (result) {
459
+ if (index >= ordersLength) {
460
+ return result;
461
+ }
462
+ var order = orders[index];
463
+ return result * ((order === 'asc' || order === true) ? 1 : -1);
464
+ }
465
+ }
466
+ // Fixes an `Array#sort` bug in the JS engine embedded in Adobe applications
467
+ // that causes it, under certain circumstances, to provide the same value for
468
+ // `object` and `other`. See https://github.com/jashkenas/underscore/pull/1247
469
+ // for more details.
470
+ //
471
+ // This also ensures a stable sort in V8 and other engines.
472
+ // See https://code.google.com/p/v8/issues/detail?id=90 for more details.
473
+ return object.index - other.index;
474
+ }
475
+
476
+ /**
477
+ * Used by `_.deburr` to convert latin-1 supplementary letters to basic latin letters.
478
+ *
479
+ * @private
480
+ * @param {string} letter The matched letter to deburr.
481
+ * @returns {string} Returns the deburred letter.
482
+ */
483
+ function deburrLetter(letter) {
484
+ return deburredLetters[letter];
485
+ }
486
+
487
+ /**
488
+ * Used by `_.escape` to convert characters to HTML entities.
489
+ *
490
+ * @private
491
+ * @param {string} chr The matched character to escape.
492
+ * @returns {string} Returns the escaped character.
493
+ */
494
+ function escapeHtmlChar(chr) {
495
+ return htmlEscapes[chr];
496
+ }
497
+
498
+ /**
499
+ * Used by `_.escapeRegExp` to escape characters for inclusion in compiled regexes.
500
+ *
501
+ * @private
502
+ * @param {string} chr The matched character to escape.
503
+ * @param {string} leadingChar The capture group for a leading character.
504
+ * @param {string} whitespaceChar The capture group for a whitespace character.
505
+ * @returns {string} Returns the escaped character.
506
+ */
507
+ function escapeRegExpChar(chr, leadingChar, whitespaceChar) {
508
+ if (leadingChar) {
509
+ chr = regexpEscapes[chr];
510
+ } else if (whitespaceChar) {
511
+ chr = stringEscapes[chr];
512
+ }
513
+ return '\\' + chr;
514
+ }
515
+
516
+ /**
517
+ * Used by `_.template` to escape characters for inclusion in compiled string literals.
518
+ *
519
+ * @private
520
+ * @param {string} chr The matched character to escape.
521
+ * @returns {string} Returns the escaped character.
522
+ */
523
+ function escapeStringChar(chr) {
524
+ return '\\' + stringEscapes[chr];
525
+ }
526
+
527
+ /**
528
+ * Gets the index at which the first occurrence of `NaN` is found in `array`.
529
+ *
530
+ * @private
531
+ * @param {Array} array The array to search.
532
+ * @param {number} fromIndex The index to search from.
533
+ * @param {boolean} [fromRight] Specify iterating from right to left.
534
+ * @returns {number} Returns the index of the matched `NaN`, else `-1`.
535
+ */
536
+ function indexOfNaN(array, fromIndex, fromRight) {
537
+ var length = array.length,
538
+ index = fromIndex + (fromRight ? 0 : -1);
539
+
540
+ while ((fromRight ? index-- : ++index < length)) {
541
+ var other = array[index];
542
+ if (other !== other) {
543
+ return index;
544
+ }
545
+ }
546
+ return -1;
547
+ }
548
+
549
+ /**
550
+ * Checks if `value` is a host object in IE < 9.
551
+ *
552
+ * @private
553
+ * @param {*} value The value to check.
554
+ * @returns {boolean} Returns `true` if `value` is a host object, else `false`.
555
+ */
556
+ var isHostObject = (function() {
557
+ try {
558
+ Object({ 'toString': 0 } + '');
559
+ } catch(e) {
560
+ return function() { return false; };
561
+ }
562
+ return function(value) {
563
+ // IE < 9 presents many host objects as `Object` objects that can coerce
564
+ // to strings despite having improperly defined `toString` methods.
565
+ return typeof value.toString != 'function' && typeof (value + '') == 'string';
566
+ };
567
+ }());
568
+
569
+ /**
570
+ * Checks if `value` is object-like.
571
+ *
572
+ * @private
573
+ * @param {*} value The value to check.
574
+ * @returns {boolean} Returns `true` if `value` is object-like, else `false`.
575
+ */
576
+ function isObjectLike(value) {
577
+ return !!value && typeof value == 'object';
578
+ }
579
+
580
+ /**
581
+ * Used by `trimmedLeftIndex` and `trimmedRightIndex` to determine if a
582
+ * character code is whitespace.
583
+ *
584
+ * @private
585
+ * @param {number} charCode The character code to inspect.
586
+ * @returns {boolean} Returns `true` if `charCode` is whitespace, else `false`.
587
+ */
588
+ function isSpace(charCode) {
589
+ return ((charCode <= 160 && (charCode >= 9 && charCode <= 13) || charCode == 32 || charCode == 160) || charCode == 5760 || charCode == 6158 ||
590
+ (charCode >= 8192 && (charCode <= 8202 || charCode == 8232 || charCode == 8233 || charCode == 8239 || charCode == 8287 || charCode == 12288 || charCode == 65279)));
591
+ }
592
+
593
+ /**
594
+ * Replaces all `placeholder` elements in `array` with an internal placeholder
595
+ * and returns an array of their indexes.
596
+ *
597
+ * @private
598
+ * @param {Array} array The array to modify.
599
+ * @param {*} placeholder The placeholder to replace.
600
+ * @returns {Array} Returns the new array of placeholder indexes.
601
+ */
602
+ function replaceHolders(array, placeholder) {
603
+ var index = -1,
604
+ length = array.length,
605
+ resIndex = -1,
606
+ result = [];
607
+
608
+ while (++index < length) {
609
+ if (array[index] === placeholder) {
610
+ array[index] = PLACEHOLDER;
611
+ result[++resIndex] = index;
612
+ }
613
+ }
614
+ return result;
615
+ }
616
+
617
+ /**
618
+ * An implementation of `_.uniq` optimized for sorted arrays without support
619
+ * for callback shorthands and `this` binding.
620
+ *
621
+ * @private
622
+ * @param {Array} array The array to inspect.
623
+ * @param {Function} [iteratee] The function invoked per iteration.
624
+ * @returns {Array} Returns the new duplicate free array.
625
+ */
626
+ function sortedUniq(array, iteratee) {
627
+ var seen,
628
+ index = -1,
629
+ length = array.length,
630
+ resIndex = -1,
631
+ result = [];
632
+
633
+ while (++index < length) {
634
+ var value = array[index],
635
+ computed = iteratee ? iteratee(value, index, array) : value;
636
+
637
+ if (!index || seen !== computed) {
638
+ seen = computed;
639
+ result[++resIndex] = value;
640
+ }
641
+ }
642
+ return result;
643
+ }
644
+
645
+ /**
646
+ * Used by `_.trim` and `_.trimLeft` to get the index of the first non-whitespace
647
+ * character of `string`.
648
+ *
649
+ * @private
650
+ * @param {string} string The string to inspect.
651
+ * @returns {number} Returns the index of the first non-whitespace character.
652
+ */
653
+ function trimmedLeftIndex(string) {
654
+ var index = -1,
655
+ length = string.length;
656
+
657
+ while (++index < length && isSpace(string.charCodeAt(index))) {}
658
+ return index;
659
+ }
660
+
661
+ /**
662
+ * Used by `_.trim` and `_.trimRight` to get the index of the last non-whitespace
663
+ * character of `string`.
664
+ *
665
+ * @private
666
+ * @param {string} string The string to inspect.
667
+ * @returns {number} Returns the index of the last non-whitespace character.
668
+ */
669
+ function trimmedRightIndex(string) {
670
+ var index = string.length;
671
+
672
+ while (index-- && isSpace(string.charCodeAt(index))) {}
673
+ return index;
674
+ }
675
+
676
+ /**
677
+ * Used by `_.unescape` to convert HTML entities to characters.
678
+ *
679
+ * @private
680
+ * @param {string} chr The matched character to unescape.
681
+ * @returns {string} Returns the unescaped character.
682
+ */
683
+ function unescapeHtmlChar(chr) {
684
+ return htmlUnescapes[chr];
685
+ }
686
+
687
+ /*--------------------------------------------------------------------------*/
688
+
689
+ /**
690
+ * Create a new pristine `lodash` function using the given `context` object.
691
+ *
692
+ * @static
693
+ * @memberOf _
694
+ * @category Utility
695
+ * @param {Object} [context=root] The context object.
696
+ * @returns {Function} Returns a new `lodash` function.
697
+ * @example
698
+ *
699
+ * _.mixin({ 'foo': _.constant('foo') });
700
+ *
701
+ * var lodash = _.runInContext();
702
+ * lodash.mixin({ 'bar': lodash.constant('bar') });
703
+ *
704
+ * _.isFunction(_.foo);
705
+ * // => true
706
+ * _.isFunction(_.bar);
707
+ * // => false
708
+ *
709
+ * lodash.isFunction(lodash.foo);
710
+ * // => false
711
+ * lodash.isFunction(lodash.bar);
712
+ * // => true
713
+ *
714
+ * // using `context` to mock `Date#getTime` use in `_.now`
715
+ * var mock = _.runInContext({
716
+ * 'Date': function() {
717
+ * return { 'getTime': getTimeMock };
718
+ * }
719
+ * });
720
+ *
721
+ * // or creating a suped-up `defer` in Node.js
722
+ * var defer = _.runInContext({ 'setTimeout': setImmediate }).defer;
723
+ */
724
+ function runInContext(context) {
725
+ // Avoid issues with some ES3 environments that attempt to use values, named
726
+ // after built-in constructors like `Object`, for the creation of literals.
727
+ // ES5 clears this up by stating that literals must use built-in constructors.
728
+ // See https://es5.github.io/#x11.1.5 for more details.
729
+ context = context ? _.defaults(root.Object(), context, _.pick(root, contextProps)) : root;
730
+
731
+ /** Native constructor references. */
732
+ var Array = context.Array,
733
+ Date = context.Date,
734
+ Error = context.Error,
735
+ Function = context.Function,
736
+ Math = context.Math,
737
+ Number = context.Number,
738
+ Object = context.Object,
739
+ RegExp = context.RegExp,
740
+ String = context.String,
741
+ TypeError = context.TypeError;
742
+
743
+ /** Used for native method references. */
744
+ var arrayProto = Array.prototype,
745
+ errorProto = Error.prototype,
746
+ objectProto = Object.prototype,
747
+ stringProto = String.prototype;
748
+
749
+ /** Used to resolve the decompiled source of functions. */
750
+ var fnToString = Function.prototype.toString;
751
+
752
+ /** Used to check objects for own properties. */
753
+ var hasOwnProperty = objectProto.hasOwnProperty;
754
+
755
+ /** Used to generate unique IDs. */
756
+ var idCounter = 0;
757
+
758
+ /**
759
+ * Used to resolve the [`toStringTag`](http://ecma-international.org/ecma-262/6.0/#sec-object.prototype.tostring)
760
+ * of values.
761
+ */
762
+ var objToString = objectProto.toString;
763
+
764
+ /** Used to restore the original `_` reference in `_.noConflict`. */
765
+ var oldDash = root._;
766
+
767
+ /** Used to detect if a method is native. */
768
+ var reIsNative = RegExp('^' +
769
+ fnToString.call(hasOwnProperty).replace(/[\\^$.*+?()[\]{}|]/g, '\\$&')
770
+ .replace(/hasOwnProperty|(function).*?(?=\\\()| for .+?(?=\\\])/g, '$1.*?') + '$'
771
+ );
772
+
773
+ /** Native method references. */
774
+ var ArrayBuffer = context.ArrayBuffer,
775
+ clearTimeout = context.clearTimeout,
776
+ parseFloat = context.parseFloat,
777
+ pow = Math.pow,
778
+ propertyIsEnumerable = objectProto.propertyIsEnumerable,
779
+ Set = getNative(context, 'Set'),
780
+ setTimeout = context.setTimeout,
781
+ splice = arrayProto.splice,
782
+ Uint8Array = context.Uint8Array,
783
+ WeakMap = getNative(context, 'WeakMap');
784
+
785
+ /* Native method references for those with the same name as other `lodash` methods. */
786
+ var nativeCeil = Math.ceil,
787
+ nativeCreate = getNative(Object, 'create'),
788
+ nativeFloor = Math.floor,
789
+ nativeIsArray = getNative(Array, 'isArray'),
790
+ nativeIsFinite = context.isFinite,
791
+ nativeKeys = getNative(Object, 'keys'),
792
+ nativeMax = Math.max,
793
+ nativeMin = Math.min,
794
+ nativeNow = getNative(Date, 'now'),
795
+ nativeParseInt = context.parseInt,
796
+ nativeRandom = Math.random;
797
+
798
+ /** Used as references for `-Infinity` and `Infinity`. */
799
+ var NEGATIVE_INFINITY = Number.NEGATIVE_INFINITY,
800
+ POSITIVE_INFINITY = Number.POSITIVE_INFINITY;
801
+
802
+ /** Used as references for the maximum length and index of an array. */
803
+ var MAX_ARRAY_LENGTH = 4294967295,
804
+ MAX_ARRAY_INDEX = MAX_ARRAY_LENGTH - 1,
805
+ HALF_MAX_ARRAY_LENGTH = MAX_ARRAY_LENGTH >>> 1;
806
+
807
+ /**
808
+ * Used as the [maximum length](http://ecma-international.org/ecma-262/6.0/#sec-number.max_safe_integer)
809
+ * of an array-like value.
810
+ */
811
+ var MAX_SAFE_INTEGER = 9007199254740991;
812
+
813
+ /** Used to store function metadata. */
814
+ var metaMap = WeakMap && new WeakMap;
815
+
816
+ /** Used to lookup unminified function names. */
817
+ var realNames = {};
818
+
819
+ /** Used to lookup a type array constructors by `toStringTag`. */
820
+ var ctorByTag = {};
821
+ ctorByTag[float32Tag] = context.Float32Array;
822
+ ctorByTag[float64Tag] = context.Float64Array;
823
+ ctorByTag[int8Tag] = context.Int8Array;
824
+ ctorByTag[int16Tag] = context.Int16Array;
825
+ ctorByTag[int32Tag] = context.Int32Array;
826
+ ctorByTag[uint8Tag] = Uint8Array;
827
+ ctorByTag[uint8ClampedTag] = context.Uint8ClampedArray;
828
+ ctorByTag[uint16Tag] = context.Uint16Array;
829
+ ctorByTag[uint32Tag] = context.Uint32Array;
830
+
831
+ /** Used to avoid iterating over non-enumerable properties in IE < 9. */
832
+ var nonEnumProps = {};
833
+ nonEnumProps[arrayTag] = nonEnumProps[dateTag] = nonEnumProps[numberTag] = { 'constructor': true, 'toLocaleString': true, 'toString': true, 'valueOf': true };
834
+ nonEnumProps[boolTag] = nonEnumProps[stringTag] = { 'constructor': true, 'toString': true, 'valueOf': true };
835
+ nonEnumProps[errorTag] = nonEnumProps[funcTag] = nonEnumProps[regexpTag] = { 'constructor': true, 'toString': true };
836
+ nonEnumProps[objectTag] = { 'constructor': true };
837
+
838
+ arrayEach(shadowProps, function(key) {
839
+ for (var tag in nonEnumProps) {
840
+ if (hasOwnProperty.call(nonEnumProps, tag)) {
841
+ var props = nonEnumProps[tag];
842
+ props[key] = hasOwnProperty.call(props, key);
843
+ }
844
+ }
845
+ });
846
+
847
+ /*------------------------------------------------------------------------*/
848
+
849
+ /**
850
+ * Creates a `lodash` object which wraps `value` to enable implicit chaining.
851
+ * Methods that operate on and return arrays, collections, and functions can
852
+ * be chained together. Methods that retrieve a single value or may return a
853
+ * primitive value will automatically end the chain returning the unwrapped
854
+ * value. Explicit chaining may be enabled using `_.chain`. The execution of
855
+ * chained methods is lazy, that is, execution is deferred until `_#value`
856
+ * is implicitly or explicitly called.
857
+ *
858
+ * Lazy evaluation allows several methods to support shortcut fusion. Shortcut
859
+ * fusion is an optimization strategy which merge iteratee calls; this can help
860
+ * to avoid the creation of intermediate data structures and greatly reduce the
861
+ * number of iteratee executions.
862
+ *
863
+ * Chaining is supported in custom builds as long as the `_#value` method is
864
+ * directly or indirectly included in the build.
865
+ *
866
+ * In addition to lodash methods, wrappers have `Array` and `String` methods.
867
+ *
868
+ * The wrapper `Array` methods are:
869
+ * `concat`, `join`, `pop`, `push`, `reverse`, `shift`, `slice`, `sort`,
870
+ * `splice`, and `unshift`
871
+ *
872
+ * The wrapper `String` methods are:
873
+ * `replace` and `split`
874
+ *
875
+ * The wrapper methods that support shortcut fusion are:
876
+ * `compact`, `drop`, `dropRight`, `dropRightWhile`, `dropWhile`, `filter`,
877
+ * `first`, `initial`, `last`, `map`, `pluck`, `reject`, `rest`, `reverse`,
878
+ * `slice`, `take`, `takeRight`, `takeRightWhile`, `takeWhile`, `toArray`,
879
+ * and `where`
880
+ *
881
+ * The chainable wrapper methods are:
882
+ * `after`, `ary`, `assign`, `at`, `before`, `bind`, `bindAll`, `bindKey`,
883
+ * `callback`, `chain`, `chunk`, `commit`, `compact`, `concat`, `constant`,
884
+ * `countBy`, `create`, `curry`, `debounce`, `defaults`, `defaultsDeep`,
885
+ * `defer`, `delay`, `difference`, `drop`, `dropRight`, `dropRightWhile`,
886
+ * `dropWhile`, `fill`, `filter`, `flatten`, `flattenDeep`, `flow`, `flowRight`,
887
+ * `forEach`, `forEachRight`, `forIn`, `forInRight`, `forOwn`, `forOwnRight`,
888
+ * `functions`, `groupBy`, `indexBy`, `initial`, `intersection`, `invert`,
889
+ * `invoke`, `keys`, `keysIn`, `map`, `mapKeys`, `mapValues`, `matches`,
890
+ * `matchesProperty`, `memoize`, `merge`, `method`, `methodOf`, `mixin`,
891
+ * `modArgs`, `negate`, `omit`, `once`, `pairs`, `partial`, `partialRight`,
892
+ * `partition`, `pick`, `plant`, `pluck`, `property`, `propertyOf`, `pull`,
893
+ * `pullAt`, `push`, `range`, `rearg`, `reject`, `remove`, `rest`, `restParam`,
894
+ * `reverse`, `set`, `shuffle`, `slice`, `sort`, `sortBy`, `sortByAll`,
895
+ * `sortByOrder`, `splice`, `spread`, `take`, `takeRight`, `takeRightWhile`,
896
+ * `takeWhile`, `tap`, `throttle`, `thru`, `times`, `toArray`, `toPlainObject`,
897
+ * `transform`, `union`, `uniq`, `unshift`, `unzip`, `unzipWith`, `values`,
898
+ * `valuesIn`, `where`, `without`, `wrap`, `xor`, `zip`, `zipObject`, `zipWith`
899
+ *
900
+ * The wrapper methods that are **not** chainable by default are:
901
+ * `add`, `attempt`, `camelCase`, `capitalize`, `ceil`, `clone`, `cloneDeep`,
902
+ * `deburr`, `endsWith`, `escape`, `escapeRegExp`, `every`, `find`, `findIndex`,
903
+ * `findKey`, `findLast`, `findLastIndex`, `findLastKey`, `findWhere`, `first`,
904
+ * `floor`, `get`, `gt`, `gte`, `has`, `identity`, `includes`, `indexOf`,
905
+ * `inRange`, `isArguments`, `isArray`, `isBoolean`, `isDate`, `isElement`,
906
+ * `isEmpty`, `isEqual`, `isError`, `isFinite` `isFunction`, `isMatch`,
907
+ * `isNative`, `isNaN`, `isNull`, `isNumber`, `isObject`, `isPlainObject`,
908
+ * `isRegExp`, `isString`, `isUndefined`, `isTypedArray`, `join`, `kebabCase`,
909
+ * `last`, `lastIndexOf`, `lt`, `lte`, `max`, `min`, `noConflict`, `noop`,
910
+ * `now`, `pad`, `padLeft`, `padRight`, `parseInt`, `pop`, `random`, `reduce`,
911
+ * `reduceRight`, `repeat`, `result`, `round`, `runInContext`, `shift`, `size`,
912
+ * `snakeCase`, `some`, `sortedIndex`, `sortedLastIndex`, `startCase`,
913
+ * `startsWith`, `sum`, `template`, `trim`, `trimLeft`, `trimRight`, `trunc`,
914
+ * `unescape`, `uniqueId`, `value`, and `words`
915
+ *
916
+ * The wrapper method `sample` will return a wrapped value when `n` is provided,
917
+ * otherwise an unwrapped value is returned.
918
+ *
919
+ * @name _
920
+ * @constructor
921
+ * @category Chain
922
+ * @param {*} value The value to wrap in a `lodash` instance.
923
+ * @returns {Object} Returns the new `lodash` wrapper instance.
924
+ * @example
925
+ *
926
+ * var wrapped = _([1, 2, 3]);
927
+ *
928
+ * // returns an unwrapped value
929
+ * wrapped.reduce(function(total, n) {
930
+ * return total + n;
931
+ * });
932
+ * // => 6
933
+ *
934
+ * // returns a wrapped value
935
+ * var squares = wrapped.map(function(n) {
936
+ * return n * n;
937
+ * });
938
+ *
939
+ * _.isArray(squares);
940
+ * // => false
941
+ *
942
+ * _.isArray(squares.value());
943
+ * // => true
944
+ */
945
+ function lodash(value) {
946
+ if (isObjectLike(value) && !isArray(value) && !(value instanceof LazyWrapper)) {
947
+ if (value instanceof LodashWrapper) {
948
+ return value;
949
+ }
950
+ if (hasOwnProperty.call(value, '__chain__') && hasOwnProperty.call(value, '__wrapped__')) {
951
+ return wrapperClone(value);
952
+ }
953
+ }
954
+ return new LodashWrapper(value);
955
+ }
956
+
957
+ /**
958
+ * The function whose prototype all chaining wrappers inherit from.
959
+ *
960
+ * @private
961
+ */
962
+ function baseLodash() {
963
+ // No operation performed.
964
+ }
965
+
966
+ /**
967
+ * The base constructor for creating `lodash` wrapper objects.
968
+ *
969
+ * @private
970
+ * @param {*} value The value to wrap.
971
+ * @param {boolean} [chainAll] Enable chaining for all wrapper methods.
972
+ * @param {Array} [actions=[]] Actions to peform to resolve the unwrapped value.
973
+ */
974
+ function LodashWrapper(value, chainAll, actions) {
975
+ this.__wrapped__ = value;
976
+ this.__actions__ = actions || [];
977
+ this.__chain__ = !!chainAll;
978
+ }
979
+
980
+ /**
981
+ * An object environment feature flags.
982
+ *
983
+ * @static
984
+ * @memberOf _
985
+ * @type Object
986
+ */
987
+ var support = lodash.support = {};
988
+
989
+ (function(x) {
990
+ var Ctor = function() { this.x = x; },
991
+ object = { '0': x, 'length': x },
992
+ props = [];
993
+
994
+ Ctor.prototype = { 'valueOf': x, 'y': x };
995
+ for (var key in new Ctor) { props.push(key); }
996
+
997
+ /**
998
+ * Detect if `name` or `message` properties of `Error.prototype` are
999
+ * enumerable by default (IE < 9, Safari < 5.1).
1000
+ *
1001
+ * @memberOf _.support
1002
+ * @type boolean
1003
+ */
1004
+ support.enumErrorProps = propertyIsEnumerable.call(errorProto, 'message') ||
1005
+ propertyIsEnumerable.call(errorProto, 'name');
1006
+
1007
+ /**
1008
+ * Detect if `prototype` properties are enumerable by default.
1009
+ *
1010
+ * Firefox < 3.6, Opera > 9.50 - Opera < 11.60, and Safari < 5.1
1011
+ * (if the prototype or a property on the prototype has been set)
1012
+ * incorrectly set the `[[Enumerable]]` value of a function's `prototype`
1013
+ * property to `true`.
1014
+ *
1015
+ * @memberOf _.support
1016
+ * @type boolean
1017
+ */
1018
+ support.enumPrototypes = propertyIsEnumerable.call(Ctor, 'prototype');
1019
+
1020
+ /**
1021
+ * Detect if properties shadowing those on `Object.prototype` are non-enumerable.
1022
+ *
1023
+ * In IE < 9 an object's own properties, shadowing non-enumerable ones,
1024
+ * are made non-enumerable as well (a.k.a the JScript `[[DontEnum]]` bug).
1025
+ *
1026
+ * @memberOf _.support
1027
+ * @type boolean
1028
+ */
1029
+ support.nonEnumShadows = !/valueOf/.test(props);
1030
+
1031
+ /**
1032
+ * Detect if own properties are iterated after inherited properties (IE < 9).
1033
+ *
1034
+ * @memberOf _.support
1035
+ * @type boolean
1036
+ */
1037
+ support.ownLast = props[0] != 'x';
1038
+
1039
+ /**
1040
+ * Detect if `Array#shift` and `Array#splice` augment array-like objects
1041
+ * correctly.
1042
+ *
1043
+ * Firefox < 10, compatibility modes of IE 8, and IE < 9 have buggy Array
1044
+ * `shift()` and `splice()` functions that fail to remove the last element,
1045
+ * `value[0]`, of array-like objects even though the "length" property is
1046
+ * set to `0`. The `shift()` method is buggy in compatibility modes of IE 8,
1047
+ * while `splice()` is buggy regardless of mode in IE < 9.
1048
+ *
1049
+ * @memberOf _.support
1050
+ * @type boolean
1051
+ */
1052
+ support.spliceObjects = (splice.call(object, 0, 1), !object[0]);
1053
+
1054
+ /**
1055
+ * Detect lack of support for accessing string characters by index.
1056
+ *
1057
+ * IE < 8 can't access characters by index. IE 8 can only access characters
1058
+ * by index on string literals, not string objects.
1059
+ *
1060
+ * @memberOf _.support
1061
+ * @type boolean
1062
+ */
1063
+ support.unindexedChars = ('x'[0] + Object('x')[0]) != 'xx';
1064
+ }(1, 0));
1065
+
1066
+ /**
1067
+ * By default, the template delimiters used by lodash are like those in
1068
+ * embedded Ruby (ERB). Change the following template settings to use
1069
+ * alternative delimiters.
1070
+ *
1071
+ * @static
1072
+ * @memberOf _
1073
+ * @type Object
1074
+ */
1075
+ lodash.templateSettings = {
1076
+
1077
+ /**
1078
+ * Used to detect `data` property values to be HTML-escaped.
1079
+ *
1080
+ * @memberOf _.templateSettings
1081
+ * @type RegExp
1082
+ */
1083
+ 'escape': reEscape,
1084
+
1085
+ /**
1086
+ * Used to detect code to be evaluated.
1087
+ *
1088
+ * @memberOf _.templateSettings
1089
+ * @type RegExp
1090
+ */
1091
+ 'evaluate': reEvaluate,
1092
+
1093
+ /**
1094
+ * Used to detect `data` property values to inject.
1095
+ *
1096
+ * @memberOf _.templateSettings
1097
+ * @type RegExp
1098
+ */
1099
+ 'interpolate': reInterpolate,
1100
+
1101
+ /**
1102
+ * Used to reference the data object in the template text.
1103
+ *
1104
+ * @memberOf _.templateSettings
1105
+ * @type string
1106
+ */
1107
+ 'variable': '',
1108
+
1109
+ /**
1110
+ * Used to import variables into the compiled template.
1111
+ *
1112
+ * @memberOf _.templateSettings
1113
+ * @type Object
1114
+ */
1115
+ 'imports': {
1116
+
1117
+ /**
1118
+ * A reference to the `lodash` function.
1119
+ *
1120
+ * @memberOf _.templateSettings.imports
1121
+ * @type Function
1122
+ */
1123
+ '_': lodash
1124
+ }
1125
+ };
1126
+
1127
+ /*------------------------------------------------------------------------*/
1128
+
1129
+ /**
1130
+ * Creates a lazy wrapper object which wraps `value` to enable lazy evaluation.
1131
+ *
1132
+ * @private
1133
+ * @param {*} value The value to wrap.
1134
+ */
1135
+ function LazyWrapper(value) {
1136
+ this.__wrapped__ = value;
1137
+ this.__actions__ = [];
1138
+ this.__dir__ = 1;
1139
+ this.__filtered__ = false;
1140
+ this.__iteratees__ = [];
1141
+ this.__takeCount__ = POSITIVE_INFINITY;
1142
+ this.__views__ = [];
1143
+ }
1144
+
1145
+ /**
1146
+ * Creates a clone of the lazy wrapper object.
1147
+ *
1148
+ * @private
1149
+ * @name clone
1150
+ * @memberOf LazyWrapper
1151
+ * @returns {Object} Returns the cloned `LazyWrapper` object.
1152
+ */
1153
+ function lazyClone() {
1154
+ var result = new LazyWrapper(this.__wrapped__);
1155
+ result.__actions__ = arrayCopy(this.__actions__);
1156
+ result.__dir__ = this.__dir__;
1157
+ result.__filtered__ = this.__filtered__;
1158
+ result.__iteratees__ = arrayCopy(this.__iteratees__);
1159
+ result.__takeCount__ = this.__takeCount__;
1160
+ result.__views__ = arrayCopy(this.__views__);
1161
+ return result;
1162
+ }
1163
+
1164
+ /**
1165
+ * Reverses the direction of lazy iteration.
1166
+ *
1167
+ * @private
1168
+ * @name reverse
1169
+ * @memberOf LazyWrapper
1170
+ * @returns {Object} Returns the new reversed `LazyWrapper` object.
1171
+ */
1172
+ function lazyReverse() {
1173
+ if (this.__filtered__) {
1174
+ var result = new LazyWrapper(this);
1175
+ result.__dir__ = -1;
1176
+ result.__filtered__ = true;
1177
+ } else {
1178
+ result = this.clone();
1179
+ result.__dir__ *= -1;
1180
+ }
1181
+ return result;
1182
+ }
1183
+
1184
+ /**
1185
+ * Extracts the unwrapped value from its lazy wrapper.
1186
+ *
1187
+ * @private
1188
+ * @name value
1189
+ * @memberOf LazyWrapper
1190
+ * @returns {*} Returns the unwrapped value.
1191
+ */
1192
+ function lazyValue() {
1193
+ var array = this.__wrapped__.value(),
1194
+ dir = this.__dir__,
1195
+ isArr = isArray(array),
1196
+ isRight = dir < 0,
1197
+ arrLength = isArr ? array.length : 0,
1198
+ view = getView(0, arrLength, this.__views__),
1199
+ start = view.start,
1200
+ end = view.end,
1201
+ length = end - start,
1202
+ index = isRight ? end : (start - 1),
1203
+ iteratees = this.__iteratees__,
1204
+ iterLength = iteratees.length,
1205
+ resIndex = 0,
1206
+ takeCount = nativeMin(length, this.__takeCount__);
1207
+
1208
+ if (!isArr || arrLength < LARGE_ARRAY_SIZE || (arrLength == length && takeCount == length)) {
1209
+ return baseWrapperValue(array, this.__actions__);
1210
+ }
1211
+ var result = [];
1212
+
1213
+ outer:
1214
+ while (length-- && resIndex < takeCount) {
1215
+ index += dir;
1216
+
1217
+ var iterIndex = -1,
1218
+ value = array[index];
1219
+
1220
+ while (++iterIndex < iterLength) {
1221
+ var data = iteratees[iterIndex],
1222
+ iteratee = data.iteratee,
1223
+ type = data.type,
1224
+ computed = iteratee(value);
1225
+
1226
+ if (type == LAZY_MAP_FLAG) {
1227
+ value = computed;
1228
+ } else if (!computed) {
1229
+ if (type == LAZY_FILTER_FLAG) {
1230
+ continue outer;
1231
+ } else {
1232
+ break outer;
1233
+ }
1234
+ }
1235
+ }
1236
+ result[resIndex++] = value;
1237
+ }
1238
+ return result;
1239
+ }
1240
+
1241
+ /*------------------------------------------------------------------------*/
1242
+
1243
+ /**
1244
+ * Creates a cache object to store key/value pairs.
1245
+ *
1246
+ * @private
1247
+ * @static
1248
+ * @name Cache
1249
+ * @memberOf _.memoize
1250
+ */
1251
+ function MapCache() {
1252
+ this.__data__ = {};
1253
+ }
1254
+
1255
+ /**
1256
+ * Removes `key` and its value from the cache.
1257
+ *
1258
+ * @private
1259
+ * @name delete
1260
+ * @memberOf _.memoize.Cache
1261
+ * @param {string} key The key of the value to remove.
1262
+ * @returns {boolean} Returns `true` if the entry was removed successfully, else `false`.
1263
+ */
1264
+ function mapDelete(key) {
1265
+ return this.has(key) && delete this.__data__[key];
1266
+ }
1267
+
1268
+ /**
1269
+ * Gets the cached value for `key`.
1270
+ *
1271
+ * @private
1272
+ * @name get
1273
+ * @memberOf _.memoize.Cache
1274
+ * @param {string} key The key of the value to get.
1275
+ * @returns {*} Returns the cached value.
1276
+ */
1277
+ function mapGet(key) {
1278
+ return key == '__proto__' ? undefined : this.__data__[key];
1279
+ }
1280
+
1281
+ /**
1282
+ * Checks if a cached value for `key` exists.
1283
+ *
1284
+ * @private
1285
+ * @name has
1286
+ * @memberOf _.memoize.Cache
1287
+ * @param {string} key The key of the entry to check.
1288
+ * @returns {boolean} Returns `true` if an entry for `key` exists, else `false`.
1289
+ */
1290
+ function mapHas(key) {
1291
+ return key != '__proto__' && hasOwnProperty.call(this.__data__, key);
1292
+ }
1293
+
1294
+ /**
1295
+ * Sets `value` to `key` of the cache.
1296
+ *
1297
+ * @private
1298
+ * @name set
1299
+ * @memberOf _.memoize.Cache
1300
+ * @param {string} key The key of the value to cache.
1301
+ * @param {*} value The value to cache.
1302
+ * @returns {Object} Returns the cache object.
1303
+ */
1304
+ function mapSet(key, value) {
1305
+ if (key != '__proto__') {
1306
+ this.__data__[key] = value;
1307
+ }
1308
+ return this;
1309
+ }
1310
+
1311
+ /*------------------------------------------------------------------------*/
1312
+
1313
+ /**
1314
+ *
1315
+ * Creates a cache object to store unique values.
1316
+ *
1317
+ * @private
1318
+ * @param {Array} [values] The values to cache.
1319
+ */
1320
+ function SetCache(values) {
1321
+ var length = values ? values.length : 0;
1322
+
1323
+ this.data = { 'hash': nativeCreate(null), 'set': new Set };
1324
+ while (length--) {
1325
+ this.push(values[length]);
1326
+ }
1327
+ }
1328
+
1329
+ /**
1330
+ * Checks if `value` is in `cache` mimicking the return signature of
1331
+ * `_.indexOf` by returning `0` if the value is found, else `-1`.
1332
+ *
1333
+ * @private
1334
+ * @param {Object} cache The cache to search.
1335
+ * @param {*} value The value to search for.
1336
+ * @returns {number} Returns `0` if `value` is found, else `-1`.
1337
+ */
1338
+ function cacheIndexOf(cache, value) {
1339
+ var data = cache.data,
1340
+ result = (typeof value == 'string' || isObject(value)) ? data.set.has(value) : data.hash[value];
1341
+
1342
+ return result ? 0 : -1;
1343
+ }
1344
+
1345
+ /**
1346
+ * Adds `value` to the cache.
1347
+ *
1348
+ * @private
1349
+ * @name push
1350
+ * @memberOf SetCache
1351
+ * @param {*} value The value to cache.
1352
+ */
1353
+ function cachePush(value) {
1354
+ var data = this.data;
1355
+ if (typeof value == 'string' || isObject(value)) {
1356
+ data.set.add(value);
1357
+ } else {
1358
+ data.hash[value] = true;
1359
+ }
1360
+ }
1361
+
1362
+ /*------------------------------------------------------------------------*/
1363
+
1364
+ /**
1365
+ * Creates a new array joining `array` with `other`.
1366
+ *
1367
+ * @private
1368
+ * @param {Array} array The array to join.
1369
+ * @param {Array} other The other array to join.
1370
+ * @returns {Array} Returns the new concatenated array.
1371
+ */
1372
+ function arrayConcat(array, other) {
1373
+ var index = -1,
1374
+ length = array.length,
1375
+ othIndex = -1,
1376
+ othLength = other.length,
1377
+ result = Array(length + othLength);
1378
+
1379
+ while (++index < length) {
1380
+ result[index] = array[index];
1381
+ }
1382
+ while (++othIndex < othLength) {
1383
+ result[index++] = other[othIndex];
1384
+ }
1385
+ return result;
1386
+ }
1387
+
1388
+ /**
1389
+ * Copies the values of `source` to `array`.
1390
+ *
1391
+ * @private
1392
+ * @param {Array} source The array to copy values from.
1393
+ * @param {Array} [array=[]] The array to copy values to.
1394
+ * @returns {Array} Returns `array`.
1395
+ */
1396
+ function arrayCopy(source, array) {
1397
+ var index = -1,
1398
+ length = source.length;
1399
+
1400
+ array || (array = Array(length));
1401
+ while (++index < length) {
1402
+ array[index] = source[index];
1403
+ }
1404
+ return array;
1405
+ }
1406
+
1407
+ /**
1408
+ * A specialized version of `_.forEach` for arrays without support for callback
1409
+ * shorthands and `this` binding.
1410
+ *
1411
+ * @private
1412
+ * @param {Array} array The array to iterate over.
1413
+ * @param {Function} iteratee The function invoked per iteration.
1414
+ * @returns {Array} Returns `array`.
1415
+ */
1416
+ function arrayEach(array, iteratee) {
1417
+ var index = -1,
1418
+ length = array.length;
1419
+
1420
+ while (++index < length) {
1421
+ if (iteratee(array[index], index, array) === false) {
1422
+ break;
1423
+ }
1424
+ }
1425
+ return array;
1426
+ }
1427
+
1428
+ /**
1429
+ * A specialized version of `_.forEachRight` for arrays without support for
1430
+ * callback shorthands and `this` binding.
1431
+ *
1432
+ * @private
1433
+ * @param {Array} array The array to iterate over.
1434
+ * @param {Function} iteratee The function invoked per iteration.
1435
+ * @returns {Array} Returns `array`.
1436
+ */
1437
+ function arrayEachRight(array, iteratee) {
1438
+ var length = array.length;
1439
+
1440
+ while (length--) {
1441
+ if (iteratee(array[length], length, array) === false) {
1442
+ break;
1443
+ }
1444
+ }
1445
+ return array;
1446
+ }
1447
+
1448
+ /**
1449
+ * A specialized version of `_.every` for arrays without support for callback
1450
+ * shorthands and `this` binding.
1451
+ *
1452
+ * @private
1453
+ * @param {Array} array The array to iterate over.
1454
+ * @param {Function} predicate The function invoked per iteration.
1455
+ * @returns {boolean} Returns `true` if all elements pass the predicate check,
1456
+ * else `false`.
1457
+ */
1458
+ function arrayEvery(array, predicate) {
1459
+ var index = -1,
1460
+ length = array.length;
1461
+
1462
+ while (++index < length) {
1463
+ if (!predicate(array[index], index, array)) {
1464
+ return false;
1465
+ }
1466
+ }
1467
+ return true;
1468
+ }
1469
+
1470
+ /**
1471
+ * A specialized version of `baseExtremum` for arrays which invokes `iteratee`
1472
+ * with one argument: (value).
1473
+ *
1474
+ * @private
1475
+ * @param {Array} array The array to iterate over.
1476
+ * @param {Function} iteratee The function invoked per iteration.
1477
+ * @param {Function} comparator The function used to compare values.
1478
+ * @param {*} exValue The initial extremum value.
1479
+ * @returns {*} Returns the extremum value.
1480
+ */
1481
+ function arrayExtremum(array, iteratee, comparator, exValue) {
1482
+ var index = -1,
1483
+ length = array.length,
1484
+ computed = exValue,
1485
+ result = computed;
1486
+
1487
+ while (++index < length) {
1488
+ var value = array[index],
1489
+ current = +iteratee(value);
1490
+
1491
+ if (comparator(current, computed)) {
1492
+ computed = current;
1493
+ result = value;
1494
+ }
1495
+ }
1496
+ return result;
1497
+ }
1498
+
1499
+ /**
1500
+ * A specialized version of `_.filter` for arrays without support for callback
1501
+ * shorthands and `this` binding.
1502
+ *
1503
+ * @private
1504
+ * @param {Array} array The array to iterate over.
1505
+ * @param {Function} predicate The function invoked per iteration.
1506
+ * @returns {Array} Returns the new filtered array.
1507
+ */
1508
+ function arrayFilter(array, predicate) {
1509
+ var index = -1,
1510
+ length = array.length,
1511
+ resIndex = -1,
1512
+ result = [];
1513
+
1514
+ while (++index < length) {
1515
+ var value = array[index];
1516
+ if (predicate(value, index, array)) {
1517
+ result[++resIndex] = value;
1518
+ }
1519
+ }
1520
+ return result;
1521
+ }
1522
+
1523
+ /**
1524
+ * A specialized version of `_.map` for arrays without support for callback
1525
+ * shorthands and `this` binding.
1526
+ *
1527
+ * @private
1528
+ * @param {Array} array The array to iterate over.
1529
+ * @param {Function} iteratee The function invoked per iteration.
1530
+ * @returns {Array} Returns the new mapped array.
1531
+ */
1532
+ function arrayMap(array, iteratee) {
1533
+ var index = -1,
1534
+ length = array.length,
1535
+ result = Array(length);
1536
+
1537
+ while (++index < length) {
1538
+ result[index] = iteratee(array[index], index, array);
1539
+ }
1540
+ return result;
1541
+ }
1542
+
1543
+ /**
1544
+ * Appends the elements of `values` to `array`.
1545
+ *
1546
+ * @private
1547
+ * @param {Array} array The array to modify.
1548
+ * @param {Array} values The values to append.
1549
+ * @returns {Array} Returns `array`.
1550
+ */
1551
+ function arrayPush(array, values) {
1552
+ var index = -1,
1553
+ length = values.length,
1554
+ offset = array.length;
1555
+
1556
+ while (++index < length) {
1557
+ array[offset + index] = values[index];
1558
+ }
1559
+ return array;
1560
+ }
1561
+
1562
+ /**
1563
+ * A specialized version of `_.reduce` for arrays without support for callback
1564
+ * shorthands and `this` binding.
1565
+ *
1566
+ * @private
1567
+ * @param {Array} array The array to iterate over.
1568
+ * @param {Function} iteratee The function invoked per iteration.
1569
+ * @param {*} [accumulator] The initial value.
1570
+ * @param {boolean} [initFromArray] Specify using the first element of `array`
1571
+ * as the initial value.
1572
+ * @returns {*} Returns the accumulated value.
1573
+ */
1574
+ function arrayReduce(array, iteratee, accumulator, initFromArray) {
1575
+ var index = -1,
1576
+ length = array.length;
1577
+
1578
+ if (initFromArray && length) {
1579
+ accumulator = array[++index];
1580
+ }
1581
+ while (++index < length) {
1582
+ accumulator = iteratee(accumulator, array[index], index, array);
1583
+ }
1584
+ return accumulator;
1585
+ }
1586
+
1587
+ /**
1588
+ * A specialized version of `_.reduceRight` for arrays without support for
1589
+ * callback shorthands and `this` binding.
1590
+ *
1591
+ * @private
1592
+ * @param {Array} array The array to iterate over.
1593
+ * @param {Function} iteratee The function invoked per iteration.
1594
+ * @param {*} [accumulator] The initial value.
1595
+ * @param {boolean} [initFromArray] Specify using the last element of `array`
1596
+ * as the initial value.
1597
+ * @returns {*} Returns the accumulated value.
1598
+ */
1599
+ function arrayReduceRight(array, iteratee, accumulator, initFromArray) {
1600
+ var length = array.length;
1601
+ if (initFromArray && length) {
1602
+ accumulator = array[--length];
1603
+ }
1604
+ while (length--) {
1605
+ accumulator = iteratee(accumulator, array[length], length, array);
1606
+ }
1607
+ return accumulator;
1608
+ }
1609
+
1610
+ /**
1611
+ * A specialized version of `_.some` for arrays without support for callback
1612
+ * shorthands and `this` binding.
1613
+ *
1614
+ * @private
1615
+ * @param {Array} array The array to iterate over.
1616
+ * @param {Function} predicate The function invoked per iteration.
1617
+ * @returns {boolean} Returns `true` if any element passes the predicate check,
1618
+ * else `false`.
1619
+ */
1620
+ function arraySome(array, predicate) {
1621
+ var index = -1,
1622
+ length = array.length;
1623
+
1624
+ while (++index < length) {
1625
+ if (predicate(array[index], index, array)) {
1626
+ return true;
1627
+ }
1628
+ }
1629
+ return false;
1630
+ }
1631
+
1632
+ /**
1633
+ * A specialized version of `_.sum` for arrays without support for callback
1634
+ * shorthands and `this` binding..
1635
+ *
1636
+ * @private
1637
+ * @param {Array} array The array to iterate over.
1638
+ * @param {Function} iteratee The function invoked per iteration.
1639
+ * @returns {number} Returns the sum.
1640
+ */
1641
+ function arraySum(array, iteratee) {
1642
+ var length = array.length,
1643
+ result = 0;
1644
+
1645
+ while (length--) {
1646
+ result += +iteratee(array[length]) || 0;
1647
+ }
1648
+ return result;
1649
+ }
1650
+
1651
+ /**
1652
+ * Used by `_.defaults` to customize its `_.assign` use.
1653
+ *
1654
+ * @private
1655
+ * @param {*} objectValue The destination object property value.
1656
+ * @param {*} sourceValue The source object property value.
1657
+ * @returns {*} Returns the value to assign to the destination object.
1658
+ */
1659
+ function assignDefaults(objectValue, sourceValue) {
1660
+ return objectValue === undefined ? sourceValue : objectValue;
1661
+ }
1662
+
1663
+ /**
1664
+ * Used by `_.template` to customize its `_.assign` use.
1665
+ *
1666
+ * **Note:** This function is like `assignDefaults` except that it ignores
1667
+ * inherited property values when checking if a property is `undefined`.
1668
+ *
1669
+ * @private
1670
+ * @param {*} objectValue The destination object property value.
1671
+ * @param {*} sourceValue The source object property value.
1672
+ * @param {string} key The key associated with the object and source values.
1673
+ * @param {Object} object The destination object.
1674
+ * @returns {*} Returns the value to assign to the destination object.
1675
+ */
1676
+ function assignOwnDefaults(objectValue, sourceValue, key, object) {
1677
+ return (objectValue === undefined || !hasOwnProperty.call(object, key))
1678
+ ? sourceValue
1679
+ : objectValue;
1680
+ }
1681
+
1682
+ /**
1683
+ * A specialized version of `_.assign` for customizing assigned values without
1684
+ * support for argument juggling, multiple sources, and `this` binding `customizer`
1685
+ * functions.
1686
+ *
1687
+ * @private
1688
+ * @param {Object} object The destination object.
1689
+ * @param {Object} source The source object.
1690
+ * @param {Function} customizer The function to customize assigned values.
1691
+ * @returns {Object} Returns `object`.
1692
+ */
1693
+ function assignWith(object, source, customizer) {
1694
+ var index = -1,
1695
+ props = keys(source),
1696
+ length = props.length;
1697
+
1698
+ while (++index < length) {
1699
+ var key = props[index],
1700
+ value = object[key],
1701
+ result = customizer(value, source[key], key, object, source);
1702
+
1703
+ if ((result === result ? (result !== value) : (value === value)) ||
1704
+ (value === undefined && !(key in object))) {
1705
+ object[key] = result;
1706
+ }
1707
+ }
1708
+ return object;
1709
+ }
1710
+
1711
+ /**
1712
+ * The base implementation of `_.assign` without support for argument juggling,
1713
+ * multiple sources, and `customizer` functions.
1714
+ *
1715
+ * @private
1716
+ * @param {Object} object The destination object.
1717
+ * @param {Object} source The source object.
1718
+ * @returns {Object} Returns `object`.
1719
+ */
1720
+ function baseAssign(object, source) {
1721
+ return source == null
1722
+ ? object
1723
+ : baseCopy(source, keys(source), object);
1724
+ }
1725
+
1726
+ /**
1727
+ * The base implementation of `_.at` without support for string collections
1728
+ * and individual key arguments.
1729
+ *
1730
+ * @private
1731
+ * @param {Array|Object} collection The collection to iterate over.
1732
+ * @param {number[]|string[]} props The property names or indexes of elements to pick.
1733
+ * @returns {Array} Returns the new array of picked elements.
1734
+ */
1735
+ function baseAt(collection, props) {
1736
+ var index = -1,
1737
+ isNil = collection == null,
1738
+ isArr = !isNil && isArrayLike(collection),
1739
+ length = isArr ? collection.length : 0,
1740
+ propsLength = props.length,
1741
+ result = Array(propsLength);
1742
+
1743
+ while(++index < propsLength) {
1744
+ var key = props[index];
1745
+ if (isArr) {
1746
+ result[index] = isIndex(key, length) ? collection[key] : undefined;
1747
+ } else {
1748
+ result[index] = isNil ? undefined : collection[key];
1749
+ }
1750
+ }
1751
+ return result;
1752
+ }
1753
+
1754
+ /**
1755
+ * Copies properties of `source` to `object`.
1756
+ *
1757
+ * @private
1758
+ * @param {Object} source The object to copy properties from.
1759
+ * @param {Array} props The property names to copy.
1760
+ * @param {Object} [object={}] The object to copy properties to.
1761
+ * @returns {Object} Returns `object`.
1762
+ */
1763
+ function baseCopy(source, props, object) {
1764
+ object || (object = {});
1765
+
1766
+ var index = -1,
1767
+ length = props.length;
1768
+
1769
+ while (++index < length) {
1770
+ var key = props[index];
1771
+ object[key] = source[key];
1772
+ }
1773
+ return object;
1774
+ }
1775
+
1776
+ /**
1777
+ * The base implementation of `_.callback` which supports specifying the
1778
+ * number of arguments to provide to `func`.
1779
+ *
1780
+ * @private
1781
+ * @param {*} [func=_.identity] The value to convert to a callback.
1782
+ * @param {*} [thisArg] The `this` binding of `func`.
1783
+ * @param {number} [argCount] The number of arguments to provide to `func`.
1784
+ * @returns {Function} Returns the callback.
1785
+ */
1786
+ function baseCallback(func, thisArg, argCount) {
1787
+ var type = typeof func;
1788
+ if (type == 'function') {
1789
+ return thisArg === undefined
1790
+ ? func
1791
+ : bindCallback(func, thisArg, argCount);
1792
+ }
1793
+ if (func == null) {
1794
+ return identity;
1795
+ }
1796
+ if (type == 'object') {
1797
+ return baseMatches(func);
1798
+ }
1799
+ return thisArg === undefined
1800
+ ? property(func)
1801
+ : baseMatchesProperty(func, thisArg);
1802
+ }
1803
+
1804
+ /**
1805
+ * The base implementation of `_.clone` without support for argument juggling
1806
+ * and `this` binding `customizer` functions.
1807
+ *
1808
+ * @private
1809
+ * @param {*} value The value to clone.
1810
+ * @param {boolean} [isDeep] Specify a deep clone.
1811
+ * @param {Function} [customizer] The function to customize cloning values.
1812
+ * @param {string} [key] The key of `value`.
1813
+ * @param {Object} [object] The object `value` belongs to.
1814
+ * @param {Array} [stackA=[]] Tracks traversed source objects.
1815
+ * @param {Array} [stackB=[]] Associates clones with source counterparts.
1816
+ * @returns {*} Returns the cloned value.
1817
+ */
1818
+ function baseClone(value, isDeep, customizer, key, object, stackA, stackB) {
1819
+ var result;
1820
+ if (customizer) {
1821
+ result = object ? customizer(value, key, object) : customizer(value);
1822
+ }
1823
+ if (result !== undefined) {
1824
+ return result;
1825
+ }
1826
+ if (!isObject(value)) {
1827
+ return value;
1828
+ }
1829
+ var isArr = isArray(value);
1830
+ if (isArr) {
1831
+ result = initCloneArray(value);
1832
+ if (!isDeep) {
1833
+ return arrayCopy(value, result);
1834
+ }
1835
+ } else {
1836
+ var tag = objToString.call(value),
1837
+ isFunc = tag == funcTag;
1838
+
1839
+ if (tag == objectTag || tag == argsTag || (isFunc && !object)) {
1840
+ if (isHostObject(value)) {
1841
+ return object ? value : {};
1842
+ }
1843
+ result = initCloneObject(isFunc ? {} : value);
1844
+ if (!isDeep) {
1845
+ return baseAssign(result, value);
1846
+ }
1847
+ } else {
1848
+ return cloneableTags[tag]
1849
+ ? initCloneByTag(value, tag, isDeep)
1850
+ : (object ? value : {});
1851
+ }
1852
+ }
1853
+ // Check for circular references and return its corresponding clone.
1854
+ stackA || (stackA = []);
1855
+ stackB || (stackB = []);
1856
+
1857
+ var length = stackA.length;
1858
+ while (length--) {
1859
+ if (stackA[length] == value) {
1860
+ return stackB[length];
1861
+ }
1862
+ }
1863
+ // Add the source value to the stack of traversed objects and associate it with its clone.
1864
+ stackA.push(value);
1865
+ stackB.push(result);
1866
+
1867
+ // Recursively populate clone (susceptible to call stack limits).
1868
+ (isArr ? arrayEach : baseForOwn)(value, function(subValue, key) {
1869
+ result[key] = baseClone(subValue, isDeep, customizer, key, value, stackA, stackB);
1870
+ });
1871
+ return result;
1872
+ }
1873
+
1874
+ /**
1875
+ * The base implementation of `_.create` without support for assigning
1876
+ * properties to the created object.
1877
+ *
1878
+ * @private
1879
+ * @param {Object} prototype The object to inherit from.
1880
+ * @returns {Object} Returns the new object.
1881
+ */
1882
+ var baseCreate = (function() {
1883
+ function object() {}
1884
+ return function(prototype) {
1885
+ if (isObject(prototype)) {
1886
+ object.prototype = prototype;
1887
+ var result = new object;
1888
+ object.prototype = undefined;
1889
+ }
1890
+ return result || {};
1891
+ };
1892
+ }());
1893
+
1894
+ /**
1895
+ * The base implementation of `_.delay` and `_.defer` which accepts an index
1896
+ * of where to slice the arguments to provide to `func`.
1897
+ *
1898
+ * @private
1899
+ * @param {Function} func The function to delay.
1900
+ * @param {number} wait The number of milliseconds to delay invocation.
1901
+ * @param {Object} args The arguments provide to `func`.
1902
+ * @returns {number} Returns the timer id.
1903
+ */
1904
+ function baseDelay(func, wait, args) {
1905
+ if (typeof func != 'function') {
1906
+ throw new TypeError(FUNC_ERROR_TEXT);
1907
+ }
1908
+ return setTimeout(function() { func.apply(undefined, args); }, wait);
1909
+ }
1910
+
1911
+ /**
1912
+ * The base implementation of `_.difference` which accepts a single array
1913
+ * of values to exclude.
1914
+ *
1915
+ * @private
1916
+ * @param {Array} array The array to inspect.
1917
+ * @param {Array} values The values to exclude.
1918
+ * @returns {Array} Returns the new array of filtered values.
1919
+ */
1920
+ function baseDifference(array, values) {
1921
+ var length = array ? array.length : 0,
1922
+ result = [];
1923
+
1924
+ if (!length) {
1925
+ return result;
1926
+ }
1927
+ var index = -1,
1928
+ indexOf = getIndexOf(),
1929
+ isCommon = indexOf === baseIndexOf,
1930
+ cache = (isCommon && values.length >= LARGE_ARRAY_SIZE) ? createCache(values) : null,
1931
+ valuesLength = values.length;
1932
+
1933
+ if (cache) {
1934
+ indexOf = cacheIndexOf;
1935
+ isCommon = false;
1936
+ values = cache;
1937
+ }
1938
+ outer:
1939
+ while (++index < length) {
1940
+ var value = array[index];
1941
+
1942
+ if (isCommon && value === value) {
1943
+ var valuesIndex = valuesLength;
1944
+ while (valuesIndex--) {
1945
+ if (values[valuesIndex] === value) {
1946
+ continue outer;
1947
+ }
1948
+ }
1949
+ result.push(value);
1950
+ }
1951
+ else if (indexOf(values, value, 0) < 0) {
1952
+ result.push(value);
1953
+ }
1954
+ }
1955
+ return result;
1956
+ }
1957
+
1958
+ /**
1959
+ * The base implementation of `_.forEach` without support for callback
1960
+ * shorthands and `this` binding.
1961
+ *
1962
+ * @private
1963
+ * @param {Array|Object|string} collection The collection to iterate over.
1964
+ * @param {Function} iteratee The function invoked per iteration.
1965
+ * @returns {Array|Object|string} Returns `collection`.
1966
+ */
1967
+ var baseEach = createBaseEach(baseForOwn);
1968
+
1969
+ /**
1970
+ * The base implementation of `_.forEachRight` without support for callback
1971
+ * shorthands and `this` binding.
1972
+ *
1973
+ * @private
1974
+ * @param {Array|Object|string} collection The collection to iterate over.
1975
+ * @param {Function} iteratee The function invoked per iteration.
1976
+ * @returns {Array|Object|string} Returns `collection`.
1977
+ */
1978
+ var baseEachRight = createBaseEach(baseForOwnRight, true);
1979
+
1980
+ /**
1981
+ * The base implementation of `_.every` without support for callback
1982
+ * shorthands and `this` binding.
1983
+ *
1984
+ * @private
1985
+ * @param {Array|Object|string} collection The collection to iterate over.
1986
+ * @param {Function} predicate The function invoked per iteration.
1987
+ * @returns {boolean} Returns `true` if all elements pass the predicate check,
1988
+ * else `false`
1989
+ */
1990
+ function baseEvery(collection, predicate) {
1991
+ var result = true;
1992
+ baseEach(collection, function(value, index, collection) {
1993
+ result = !!predicate(value, index, collection);
1994
+ return result;
1995
+ });
1996
+ return result;
1997
+ }
1998
+
1999
+ /**
2000
+ * Gets the extremum value of `collection` invoking `iteratee` for each value
2001
+ * in `collection` to generate the criterion by which the value is ranked.
2002
+ * The `iteratee` is invoked with three arguments: (value, index|key, collection).
2003
+ *
2004
+ * @private
2005
+ * @param {Array|Object|string} collection The collection to iterate over.
2006
+ * @param {Function} iteratee The function invoked per iteration.
2007
+ * @param {Function} comparator The function used to compare values.
2008
+ * @param {*} exValue The initial extremum value.
2009
+ * @returns {*} Returns the extremum value.
2010
+ */
2011
+ function baseExtremum(collection, iteratee, comparator, exValue) {
2012
+ var computed = exValue,
2013
+ result = computed;
2014
+
2015
+ baseEach(collection, function(value, index, collection) {
2016
+ var current = +iteratee(value, index, collection);
2017
+ if (comparator(current, computed) || (current === exValue && current === result)) {
2018
+ computed = current;
2019
+ result = value;
2020
+ }
2021
+ });
2022
+ return result;
2023
+ }
2024
+
2025
+ /**
2026
+ * The base implementation of `_.fill` without an iteratee call guard.
2027
+ *
2028
+ * @private
2029
+ * @param {Array} array The array to fill.
2030
+ * @param {*} value The value to fill `array` with.
2031
+ * @param {number} [start=0] The start position.
2032
+ * @param {number} [end=array.length] The end position.
2033
+ * @returns {Array} Returns `array`.
2034
+ */
2035
+ function baseFill(array, value, start, end) {
2036
+ var length = array.length;
2037
+
2038
+ start = start == null ? 0 : (+start || 0);
2039
+ if (start < 0) {
2040
+ start = -start > length ? 0 : (length + start);
2041
+ }
2042
+ end = (end === undefined || end > length) ? length : (+end || 0);
2043
+ if (end < 0) {
2044
+ end += length;
2045
+ }
2046
+ length = start > end ? 0 : (end >>> 0);
2047
+ start >>>= 0;
2048
+
2049
+ while (start < length) {
2050
+ array[start++] = value;
2051
+ }
2052
+ return array;
2053
+ }
2054
+
2055
+ /**
2056
+ * The base implementation of `_.filter` without support for callback
2057
+ * shorthands and `this` binding.
2058
+ *
2059
+ * @private
2060
+ * @param {Array|Object|string} collection The collection to iterate over.
2061
+ * @param {Function} predicate The function invoked per iteration.
2062
+ * @returns {Array} Returns the new filtered array.
2063
+ */
2064
+ function baseFilter(collection, predicate) {
2065
+ var result = [];
2066
+ baseEach(collection, function(value, index, collection) {
2067
+ if (predicate(value, index, collection)) {
2068
+ result.push(value);
2069
+ }
2070
+ });
2071
+ return result;
2072
+ }
2073
+
2074
+ /**
2075
+ * The base implementation of `_.find`, `_.findLast`, `_.findKey`, and `_.findLastKey`,
2076
+ * without support for callback shorthands and `this` binding, which iterates
2077
+ * over `collection` using the provided `eachFunc`.
2078
+ *
2079
+ * @private
2080
+ * @param {Array|Object|string} collection The collection to search.
2081
+ * @param {Function} predicate The function invoked per iteration.
2082
+ * @param {Function} eachFunc The function to iterate over `collection`.
2083
+ * @param {boolean} [retKey] Specify returning the key of the found element
2084
+ * instead of the element itself.
2085
+ * @returns {*} Returns the found element or its key, else `undefined`.
2086
+ */
2087
+ function baseFind(collection, predicate, eachFunc, retKey) {
2088
+ var result;
2089
+ eachFunc(collection, function(value, key, collection) {
2090
+ if (predicate(value, key, collection)) {
2091
+ result = retKey ? key : value;
2092
+ return false;
2093
+ }
2094
+ });
2095
+ return result;
2096
+ }
2097
+
2098
+ /**
2099
+ * The base implementation of `_.flatten` with added support for restricting
2100
+ * flattening and specifying the start index.
2101
+ *
2102
+ * @private
2103
+ * @param {Array} array The array to flatten.
2104
+ * @param {boolean} [isDeep] Specify a deep flatten.
2105
+ * @param {boolean} [isStrict] Restrict flattening to arrays-like objects.
2106
+ * @param {Array} [result=[]] The initial result value.
2107
+ * @returns {Array} Returns the new flattened array.
2108
+ */
2109
+ function baseFlatten(array, isDeep, isStrict, result) {
2110
+ result || (result = []);
2111
+
2112
+ var index = -1,
2113
+ length = array.length;
2114
+
2115
+ while (++index < length) {
2116
+ var value = array[index];
2117
+ if (isObjectLike(value) && isArrayLike(value) &&
2118
+ (isStrict || isArray(value) || isArguments(value))) {
2119
+ if (isDeep) {
2120
+ // Recursively flatten arrays (susceptible to call stack limits).
2121
+ baseFlatten(value, isDeep, isStrict, result);
2122
+ } else {
2123
+ arrayPush(result, value);
2124
+ }
2125
+ } else if (!isStrict) {
2126
+ result[result.length] = value;
2127
+ }
2128
+ }
2129
+ return result;
2130
+ }
2131
+
2132
+ /**
2133
+ * The base implementation of `baseForIn` and `baseForOwn` which iterates
2134
+ * over `object` properties returned by `keysFunc` invoking `iteratee` for
2135
+ * each property. Iteratee functions may exit iteration early by explicitly
2136
+ * returning `false`.
2137
+ *
2138
+ * @private
2139
+ * @param {Object} object The object to iterate over.
2140
+ * @param {Function} iteratee The function invoked per iteration.
2141
+ * @param {Function} keysFunc The function to get the keys of `object`.
2142
+ * @returns {Object} Returns `object`.
2143
+ */
2144
+ var baseFor = createBaseFor();
2145
+
2146
+ /**
2147
+ * This function is like `baseFor` except that it iterates over properties
2148
+ * in the opposite order.
2149
+ *
2150
+ * @private
2151
+ * @param {Object} object The object to iterate over.
2152
+ * @param {Function} iteratee The function invoked per iteration.
2153
+ * @param {Function} keysFunc The function to get the keys of `object`.
2154
+ * @returns {Object} Returns `object`.
2155
+ */
2156
+ var baseForRight = createBaseFor(true);
2157
+
2158
+ /**
2159
+ * The base implementation of `_.forIn` without support for callback
2160
+ * shorthands and `this` binding.
2161
+ *
2162
+ * @private
2163
+ * @param {Object} object The object to iterate over.
2164
+ * @param {Function} iteratee The function invoked per iteration.
2165
+ * @returns {Object} Returns `object`.
2166
+ */
2167
+ function baseForIn(object, iteratee) {
2168
+ return baseFor(object, iteratee, keysIn);
2169
+ }
2170
+
2171
+ /**
2172
+ * The base implementation of `_.forOwn` without support for callback
2173
+ * shorthands and `this` binding.
2174
+ *
2175
+ * @private
2176
+ * @param {Object} object The object to iterate over.
2177
+ * @param {Function} iteratee The function invoked per iteration.
2178
+ * @returns {Object} Returns `object`.
2179
+ */
2180
+ function baseForOwn(object, iteratee) {
2181
+ return baseFor(object, iteratee, keys);
2182
+ }
2183
+
2184
+ /**
2185
+ * The base implementation of `_.forOwnRight` without support for callback
2186
+ * shorthands and `this` binding.
2187
+ *
2188
+ * @private
2189
+ * @param {Object} object The object to iterate over.
2190
+ * @param {Function} iteratee The function invoked per iteration.
2191
+ * @returns {Object} Returns `object`.
2192
+ */
2193
+ function baseForOwnRight(object, iteratee) {
2194
+ return baseForRight(object, iteratee, keys);
2195
+ }
2196
+
2197
+ /**
2198
+ * The base implementation of `_.functions` which creates an array of
2199
+ * `object` function property names filtered from those provided.
2200
+ *
2201
+ * @private
2202
+ * @param {Object} object The object to inspect.
2203
+ * @param {Array} props The property names to filter.
2204
+ * @returns {Array} Returns the new array of filtered property names.
2205
+ */
2206
+ function baseFunctions(object, props) {
2207
+ var index = -1,
2208
+ length = props.length,
2209
+ resIndex = -1,
2210
+ result = [];
2211
+
2212
+ while (++index < length) {
2213
+ var key = props[index];
2214
+ if (isFunction(object[key])) {
2215
+ result[++resIndex] = key;
2216
+ }
2217
+ }
2218
+ return result;
2219
+ }
2220
+
2221
+ /**
2222
+ * The base implementation of `get` without support for string paths
2223
+ * and default values.
2224
+ *
2225
+ * @private
2226
+ * @param {Object} object The object to query.
2227
+ * @param {Array} path The path of the property to get.
2228
+ * @param {string} [pathKey] The key representation of path.
2229
+ * @returns {*} Returns the resolved value.
2230
+ */
2231
+ function baseGet(object, path, pathKey) {
2232
+ if (object == null) {
2233
+ return;
2234
+ }
2235
+ object = toObject(object);
2236
+ if (pathKey !== undefined && pathKey in object) {
2237
+ path = [pathKey];
2238
+ }
2239
+ var index = 0,
2240
+ length = path.length;
2241
+
2242
+ while (object != null && index < length) {
2243
+ object = toObject(object)[path[index++]];
2244
+ }
2245
+ return (index && index == length) ? object : undefined;
2246
+ }
2247
+
2248
+ /**
2249
+ * The base implementation of `_.isEqual` without support for `this` binding
2250
+ * `customizer` functions.
2251
+ *
2252
+ * @private
2253
+ * @param {*} value The value to compare.
2254
+ * @param {*} other The other value to compare.
2255
+ * @param {Function} [customizer] The function to customize comparing values.
2256
+ * @param {boolean} [isLoose] Specify performing partial comparisons.
2257
+ * @param {Array} [stackA] Tracks traversed `value` objects.
2258
+ * @param {Array} [stackB] Tracks traversed `other` objects.
2259
+ * @returns {boolean} Returns `true` if the values are equivalent, else `false`.
2260
+ */
2261
+ function baseIsEqual(value, other, customizer, isLoose, stackA, stackB) {
2262
+ if (value === other) {
2263
+ return true;
2264
+ }
2265
+ if (value == null || other == null || (!isObject(value) && !isObjectLike(other))) {
2266
+ return value !== value && other !== other;
2267
+ }
2268
+ return baseIsEqualDeep(value, other, baseIsEqual, customizer, isLoose, stackA, stackB);
2269
+ }
2270
+
2271
+ /**
2272
+ * A specialized version of `baseIsEqual` for arrays and objects which performs
2273
+ * deep comparisons and tracks traversed objects enabling objects with circular
2274
+ * references to be compared.
2275
+ *
2276
+ * @private
2277
+ * @param {Object} object The object to compare.
2278
+ * @param {Object} other The other object to compare.
2279
+ * @param {Function} equalFunc The function to determine equivalents of values.
2280
+ * @param {Function} [customizer] The function to customize comparing objects.
2281
+ * @param {boolean} [isLoose] Specify performing partial comparisons.
2282
+ * @param {Array} [stackA=[]] Tracks traversed `value` objects.
2283
+ * @param {Array} [stackB=[]] Tracks traversed `other` objects.
2284
+ * @returns {boolean} Returns `true` if the objects are equivalent, else `false`.
2285
+ */
2286
+ function baseIsEqualDeep(object, other, equalFunc, customizer, isLoose, stackA, stackB) {
2287
+ var objIsArr = isArray(object),
2288
+ othIsArr = isArray(other),
2289
+ objTag = arrayTag,
2290
+ othTag = arrayTag;
2291
+
2292
+ if (!objIsArr) {
2293
+ objTag = objToString.call(object);
2294
+ if (objTag == argsTag) {
2295
+ objTag = objectTag;
2296
+ } else if (objTag != objectTag) {
2297
+ objIsArr = isTypedArray(object);
2298
+ }
2299
+ }
2300
+ if (!othIsArr) {
2301
+ othTag = objToString.call(other);
2302
+ if (othTag == argsTag) {
2303
+ othTag = objectTag;
2304
+ } else if (othTag != objectTag) {
2305
+ othIsArr = isTypedArray(other);
2306
+ }
2307
+ }
2308
+ var objIsObj = objTag == objectTag && !isHostObject(object),
2309
+ othIsObj = othTag == objectTag && !isHostObject(other),
2310
+ isSameTag = objTag == othTag;
2311
+
2312
+ if (isSameTag && !(objIsArr || objIsObj)) {
2313
+ return equalByTag(object, other, objTag);
2314
+ }
2315
+ if (!isLoose) {
2316
+ var objIsWrapped = objIsObj && hasOwnProperty.call(object, '__wrapped__'),
2317
+ othIsWrapped = othIsObj && hasOwnProperty.call(other, '__wrapped__');
2318
+
2319
+ if (objIsWrapped || othIsWrapped) {
2320
+ return equalFunc(objIsWrapped ? object.value() : object, othIsWrapped ? other.value() : other, customizer, isLoose, stackA, stackB);
2321
+ }
2322
+ }
2323
+ if (!isSameTag) {
2324
+ return false;
2325
+ }
2326
+ // Assume cyclic values are equal.
2327
+ // For more information on detecting circular references see https://es5.github.io/#JO.
2328
+ stackA || (stackA = []);
2329
+ stackB || (stackB = []);
2330
+
2331
+ var length = stackA.length;
2332
+ while (length--) {
2333
+ if (stackA[length] == object) {
2334
+ return stackB[length] == other;
2335
+ }
2336
+ }
2337
+ // Add `object` and `other` to the stack of traversed objects.
2338
+ stackA.push(object);
2339
+ stackB.push(other);
2340
+
2341
+ var result = (objIsArr ? equalArrays : equalObjects)(object, other, equalFunc, customizer, isLoose, stackA, stackB);
2342
+
2343
+ stackA.pop();
2344
+ stackB.pop();
2345
+
2346
+ return result;
2347
+ }
2348
+
2349
+ /**
2350
+ * The base implementation of `_.isMatch` without support for callback
2351
+ * shorthands and `this` binding.
2352
+ *
2353
+ * @private
2354
+ * @param {Object} object The object to inspect.
2355
+ * @param {Array} matchData The propery names, values, and compare flags to match.
2356
+ * @param {Function} [customizer] The function to customize comparing objects.
2357
+ * @returns {boolean} Returns `true` if `object` is a match, else `false`.
2358
+ */
2359
+ function baseIsMatch(object, matchData, customizer) {
2360
+ var index = matchData.length,
2361
+ length = index,
2362
+ noCustomizer = !customizer;
2363
+
2364
+ if (object == null) {
2365
+ return !length;
2366
+ }
2367
+ object = toObject(object);
2368
+ while (index--) {
2369
+ var data = matchData[index];
2370
+ if ((noCustomizer && data[2])
2371
+ ? data[1] !== object[data[0]]
2372
+ : !(data[0] in object)
2373
+ ) {
2374
+ return false;
2375
+ }
2376
+ }
2377
+ while (++index < length) {
2378
+ data = matchData[index];
2379
+ var key = data[0],
2380
+ objValue = object[key],
2381
+ srcValue = data[1];
2382
+
2383
+ if (noCustomizer && data[2]) {
2384
+ if (objValue === undefined && !(key in object)) {
2385
+ return false;
2386
+ }
2387
+ } else {
2388
+ var result = customizer ? customizer(objValue, srcValue, key) : undefined;
2389
+ if (!(result === undefined ? baseIsEqual(srcValue, objValue, customizer, true) : result)) {
2390
+ return false;
2391
+ }
2392
+ }
2393
+ }
2394
+ return true;
2395
+ }
2396
+
2397
+ /**
2398
+ * The base implementation of `_.map` without support for callback shorthands
2399
+ * and `this` binding.
2400
+ *
2401
+ * @private
2402
+ * @param {Array|Object|string} collection The collection to iterate over.
2403
+ * @param {Function} iteratee The function invoked per iteration.
2404
+ * @returns {Array} Returns the new mapped array.
2405
+ */
2406
+ function baseMap(collection, iteratee) {
2407
+ var index = -1,
2408
+ result = isArrayLike(collection) ? Array(collection.length) : [];
2409
+
2410
+ baseEach(collection, function(value, key, collection) {
2411
+ result[++index] = iteratee(value, key, collection);
2412
+ });
2413
+ return result;
2414
+ }
2415
+
2416
+ /**
2417
+ * The base implementation of `_.matches` which does not clone `source`.
2418
+ *
2419
+ * @private
2420
+ * @param {Object} source The object of property values to match.
2421
+ * @returns {Function} Returns the new function.
2422
+ */
2423
+ function baseMatches(source) {
2424
+ var matchData = getMatchData(source);
2425
+ if (matchData.length == 1 && matchData[0][2]) {
2426
+ var key = matchData[0][0],
2427
+ value = matchData[0][1];
2428
+
2429
+ return function(object) {
2430
+ if (object == null) {
2431
+ return false;
2432
+ }
2433
+ object = toObject(object);
2434
+ return object[key] === value && (value !== undefined || (key in object));
2435
+ };
2436
+ }
2437
+ return function(object) {
2438
+ return baseIsMatch(object, matchData);
2439
+ };
2440
+ }
2441
+
2442
+ /**
2443
+ * The base implementation of `_.matchesProperty` which does not clone `srcValue`.
2444
+ *
2445
+ * @private
2446
+ * @param {string} path The path of the property to get.
2447
+ * @param {*} srcValue The value to compare.
2448
+ * @returns {Function} Returns the new function.
2449
+ */
2450
+ function baseMatchesProperty(path, srcValue) {
2451
+ var isArr = isArray(path),
2452
+ isCommon = isKey(path) && isStrictComparable(srcValue),
2453
+ pathKey = (path + '');
2454
+
2455
+ path = toPath(path);
2456
+ return function(object) {
2457
+ if (object == null) {
2458
+ return false;
2459
+ }
2460
+ var key = pathKey;
2461
+ object = toObject(object);
2462
+ if ((isArr || !isCommon) && !(key in object)) {
2463
+ object = path.length == 1 ? object : baseGet(object, baseSlice(path, 0, -1));
2464
+ if (object == null) {
2465
+ return false;
2466
+ }
2467
+ key = last(path);
2468
+ object = toObject(object);
2469
+ }
2470
+ return object[key] === srcValue
2471
+ ? (srcValue !== undefined || (key in object))
2472
+ : baseIsEqual(srcValue, object[key], undefined, true);
2473
+ };
2474
+ }
2475
+
2476
+ /**
2477
+ * The base implementation of `_.merge` without support for argument juggling,
2478
+ * multiple sources, and `this` binding `customizer` functions.
2479
+ *
2480
+ * @private
2481
+ * @param {Object} object The destination object.
2482
+ * @param {Object} source The source object.
2483
+ * @param {Function} [customizer] The function to customize merged values.
2484
+ * @param {Array} [stackA=[]] Tracks traversed source objects.
2485
+ * @param {Array} [stackB=[]] Associates values with source counterparts.
2486
+ * @returns {Object} Returns `object`.
2487
+ */
2488
+ function baseMerge(object, source, customizer, stackA, stackB) {
2489
+ if (!isObject(object)) {
2490
+ return object;
2491
+ }
2492
+ var isSrcArr = isArrayLike(source) && (isArray(source) || isTypedArray(source)),
2493
+ props = isSrcArr ? undefined : keys(source);
2494
+
2495
+ arrayEach(props || source, function(srcValue, key) {
2496
+ if (props) {
2497
+ key = srcValue;
2498
+ srcValue = source[key];
2499
+ }
2500
+ if (isObjectLike(srcValue)) {
2501
+ stackA || (stackA = []);
2502
+ stackB || (stackB = []);
2503
+ baseMergeDeep(object, source, key, baseMerge, customizer, stackA, stackB);
2504
+ }
2505
+ else {
2506
+ var value = object[key],
2507
+ result = customizer ? customizer(value, srcValue, key, object, source) : undefined,
2508
+ isCommon = result === undefined;
2509
+
2510
+ if (isCommon) {
2511
+ result = srcValue;
2512
+ }
2513
+ if ((result !== undefined || (isSrcArr && !(key in object))) &&
2514
+ (isCommon || (result === result ? (result !== value) : (value === value)))) {
2515
+ object[key] = result;
2516
+ }
2517
+ }
2518
+ });
2519
+ return object;
2520
+ }
2521
+
2522
+ /**
2523
+ * A specialized version of `baseMerge` for arrays and objects which performs
2524
+ * deep merges and tracks traversed objects enabling objects with circular
2525
+ * references to be merged.
2526
+ *
2527
+ * @private
2528
+ * @param {Object} object The destination object.
2529
+ * @param {Object} source The source object.
2530
+ * @param {string} key The key of the value to merge.
2531
+ * @param {Function} mergeFunc The function to merge values.
2532
+ * @param {Function} [customizer] The function to customize merged values.
2533
+ * @param {Array} [stackA=[]] Tracks traversed source objects.
2534
+ * @param {Array} [stackB=[]] Associates values with source counterparts.
2535
+ * @returns {boolean} Returns `true` if the objects are equivalent, else `false`.
2536
+ */
2537
+ function baseMergeDeep(object, source, key, mergeFunc, customizer, stackA, stackB) {
2538
+ var length = stackA.length,
2539
+ srcValue = source[key];
2540
+
2541
+ while (length--) {
2542
+ if (stackA[length] == srcValue) {
2543
+ object[key] = stackB[length];
2544
+ return;
2545
+ }
2546
+ }
2547
+ var value = object[key],
2548
+ result = customizer ? customizer(value, srcValue, key, object, source) : undefined,
2549
+ isCommon = result === undefined;
2550
+
2551
+ if (isCommon) {
2552
+ result = srcValue;
2553
+ if (isArrayLike(srcValue) && (isArray(srcValue) || isTypedArray(srcValue))) {
2554
+ result = isArray(value)
2555
+ ? value
2556
+ : (isArrayLike(value) ? arrayCopy(value) : []);
2557
+ }
2558
+ else if (isPlainObject(srcValue) || isArguments(srcValue)) {
2559
+ result = isArguments(value)
2560
+ ? toPlainObject(value)
2561
+ : (isPlainObject(value) ? value : {});
2562
+ }
2563
+ else {
2564
+ isCommon = false;
2565
+ }
2566
+ }
2567
+ // Add the source value to the stack of traversed objects and associate
2568
+ // it with its merged value.
2569
+ stackA.push(srcValue);
2570
+ stackB.push(result);
2571
+
2572
+ if (isCommon) {
2573
+ // Recursively merge objects and arrays (susceptible to call stack limits).
2574
+ object[key] = mergeFunc(result, srcValue, customizer, stackA, stackB);
2575
+ } else if (result === result ? (result !== value) : (value === value)) {
2576
+ object[key] = result;
2577
+ }
2578
+ }
2579
+
2580
+ /**
2581
+ * The base implementation of `_.property` without support for deep paths.
2582
+ *
2583
+ * @private
2584
+ * @param {string} key The key of the property to get.
2585
+ * @returns {Function} Returns the new function.
2586
+ */
2587
+ function baseProperty(key) {
2588
+ return function(object) {
2589
+ return object == null ? undefined : toObject(object)[key];
2590
+ };
2591
+ }
2592
+
2593
+ /**
2594
+ * A specialized version of `baseProperty` which supports deep paths.
2595
+ *
2596
+ * @private
2597
+ * @param {Array|string} path The path of the property to get.
2598
+ * @returns {Function} Returns the new function.
2599
+ */
2600
+ function basePropertyDeep(path) {
2601
+ var pathKey = (path + '');
2602
+ path = toPath(path);
2603
+ return function(object) {
2604
+ return baseGet(object, path, pathKey);
2605
+ };
2606
+ }
2607
+
2608
+ /**
2609
+ * The base implementation of `_.pullAt` without support for individual
2610
+ * index arguments and capturing the removed elements.
2611
+ *
2612
+ * @private
2613
+ * @param {Array} array The array to modify.
2614
+ * @param {number[]} indexes The indexes of elements to remove.
2615
+ * @returns {Array} Returns `array`.
2616
+ */
2617
+ function basePullAt(array, indexes) {
2618
+ var length = array ? indexes.length : 0;
2619
+ while (length--) {
2620
+ var index = indexes[length];
2621
+ if (index != previous && isIndex(index)) {
2622
+ var previous = index;
2623
+ splice.call(array, index, 1);
2624
+ }
2625
+ }
2626
+ return array;
2627
+ }
2628
+
2629
+ /**
2630
+ * The base implementation of `_.random` without support for argument juggling
2631
+ * and returning floating-point numbers.
2632
+ *
2633
+ * @private
2634
+ * @param {number} min The minimum possible value.
2635
+ * @param {number} max The maximum possible value.
2636
+ * @returns {number} Returns the random number.
2637
+ */
2638
+ function baseRandom(min, max) {
2639
+ return min + nativeFloor(nativeRandom() * (max - min + 1));
2640
+ }
2641
+
2642
+ /**
2643
+ * The base implementation of `_.reduce` and `_.reduceRight` without support
2644
+ * for callback shorthands and `this` binding, which iterates over `collection`
2645
+ * using the provided `eachFunc`.
2646
+ *
2647
+ * @private
2648
+ * @param {Array|Object|string} collection The collection to iterate over.
2649
+ * @param {Function} iteratee The function invoked per iteration.
2650
+ * @param {*} accumulator The initial value.
2651
+ * @param {boolean} initFromCollection Specify using the first or last element
2652
+ * of `collection` as the initial value.
2653
+ * @param {Function} eachFunc The function to iterate over `collection`.
2654
+ * @returns {*} Returns the accumulated value.
2655
+ */
2656
+ function baseReduce(collection, iteratee, accumulator, initFromCollection, eachFunc) {
2657
+ eachFunc(collection, function(value, index, collection) {
2658
+ accumulator = initFromCollection
2659
+ ? (initFromCollection = false, value)
2660
+ : iteratee(accumulator, value, index, collection);
2661
+ });
2662
+ return accumulator;
2663
+ }
2664
+
2665
+ /**
2666
+ * The base implementation of `setData` without support for hot loop detection.
2667
+ *
2668
+ * @private
2669
+ * @param {Function} func The function to associate metadata with.
2670
+ * @param {*} data The metadata.
2671
+ * @returns {Function} Returns `func`.
2672
+ */
2673
+ var baseSetData = !metaMap ? identity : function(func, data) {
2674
+ metaMap.set(func, data);
2675
+ return func;
2676
+ };
2677
+
2678
+ /**
2679
+ * The base implementation of `_.slice` without an iteratee call guard.
2680
+ *
2681
+ * @private
2682
+ * @param {Array} array The array to slice.
2683
+ * @param {number} [start=0] The start position.
2684
+ * @param {number} [end=array.length] The end position.
2685
+ * @returns {Array} Returns the slice of `array`.
2686
+ */
2687
+ function baseSlice(array, start, end) {
2688
+ var index = -1,
2689
+ length = array.length;
2690
+
2691
+ start = start == null ? 0 : (+start || 0);
2692
+ if (start < 0) {
2693
+ start = -start > length ? 0 : (length + start);
2694
+ }
2695
+ end = (end === undefined || end > length) ? length : (+end || 0);
2696
+ if (end < 0) {
2697
+ end += length;
2698
+ }
2699
+ length = start > end ? 0 : ((end - start) >>> 0);
2700
+ start >>>= 0;
2701
+
2702
+ var result = Array(length);
2703
+ while (++index < length) {
2704
+ result[index] = array[index + start];
2705
+ }
2706
+ return result;
2707
+ }
2708
+
2709
+ /**
2710
+ * The base implementation of `_.some` without support for callback shorthands
2711
+ * and `this` binding.
2712
+ *
2713
+ * @private
2714
+ * @param {Array|Object|string} collection The collection to iterate over.
2715
+ * @param {Function} predicate The function invoked per iteration.
2716
+ * @returns {boolean} Returns `true` if any element passes the predicate check,
2717
+ * else `false`.
2718
+ */
2719
+ function baseSome(collection, predicate) {
2720
+ var result;
2721
+
2722
+ baseEach(collection, function(value, index, collection) {
2723
+ result = predicate(value, index, collection);
2724
+ return !result;
2725
+ });
2726
+ return !!result;
2727
+ }
2728
+
2729
+ /**
2730
+ * The base implementation of `_.sortBy` which uses `comparer` to define
2731
+ * the sort order of `array` and replaces criteria objects with their
2732
+ * corresponding values.
2733
+ *
2734
+ * @private
2735
+ * @param {Array} array The array to sort.
2736
+ * @param {Function} comparer The function to define sort order.
2737
+ * @returns {Array} Returns `array`.
2738
+ */
2739
+ function baseSortBy(array, comparer) {
2740
+ var length = array.length;
2741
+
2742
+ array.sort(comparer);
2743
+ while (length--) {
2744
+ array[length] = array[length].value;
2745
+ }
2746
+ return array;
2747
+ }
2748
+
2749
+ /**
2750
+ * The base implementation of `_.sortByOrder` without param guards.
2751
+ *
2752
+ * @private
2753
+ * @param {Array|Object|string} collection The collection to iterate over.
2754
+ * @param {Function[]|Object[]|string[]} iteratees The iteratees to sort by.
2755
+ * @param {boolean[]} orders The sort orders of `iteratees`.
2756
+ * @returns {Array} Returns the new sorted array.
2757
+ */
2758
+ function baseSortByOrder(collection, iteratees, orders) {
2759
+ var callback = getCallback(),
2760
+ index = -1;
2761
+
2762
+ iteratees = arrayMap(iteratees, function(iteratee) { return callback(iteratee); });
2763
+
2764
+ var result = baseMap(collection, function(value) {
2765
+ var criteria = arrayMap(iteratees, function(iteratee) { return iteratee(value); });
2766
+ return { 'criteria': criteria, 'index': ++index, 'value': value };
2767
+ });
2768
+
2769
+ return baseSortBy(result, function(object, other) {
2770
+ return compareMultiple(object, other, orders);
2771
+ });
2772
+ }
2773
+
2774
+ /**
2775
+ * The base implementation of `_.sum` without support for callback shorthands
2776
+ * and `this` binding.
2777
+ *
2778
+ * @private
2779
+ * @param {Array|Object|string} collection The collection to iterate over.
2780
+ * @param {Function} iteratee The function invoked per iteration.
2781
+ * @returns {number} Returns the sum.
2782
+ */
2783
+ function baseSum(collection, iteratee) {
2784
+ var result = 0;
2785
+ baseEach(collection, function(value, index, collection) {
2786
+ result += +iteratee(value, index, collection) || 0;
2787
+ });
2788
+ return result;
2789
+ }
2790
+
2791
+ /**
2792
+ * The base implementation of `_.uniq` without support for callback shorthands
2793
+ * and `this` binding.
2794
+ *
2795
+ * @private
2796
+ * @param {Array} array The array to inspect.
2797
+ * @param {Function} [iteratee] The function invoked per iteration.
2798
+ * @returns {Array} Returns the new duplicate free array.
2799
+ */
2800
+ function baseUniq(array, iteratee) {
2801
+ var index = -1,
2802
+ indexOf = getIndexOf(),
2803
+ length = array.length,
2804
+ isCommon = indexOf === baseIndexOf,
2805
+ isLarge = isCommon && length >= LARGE_ARRAY_SIZE,
2806
+ seen = isLarge ? createCache() : null,
2807
+ result = [];
2808
+
2809
+ if (seen) {
2810
+ indexOf = cacheIndexOf;
2811
+ isCommon = false;
2812
+ } else {
2813
+ isLarge = false;
2814
+ seen = iteratee ? [] : result;
2815
+ }
2816
+ outer:
2817
+ while (++index < length) {
2818
+ var value = array[index],
2819
+ computed = iteratee ? iteratee(value, index, array) : value;
2820
+
2821
+ if (isCommon && value === value) {
2822
+ var seenIndex = seen.length;
2823
+ while (seenIndex--) {
2824
+ if (seen[seenIndex] === computed) {
2825
+ continue outer;
2826
+ }
2827
+ }
2828
+ if (iteratee) {
2829
+ seen.push(computed);
2830
+ }
2831
+ result.push(value);
2832
+ }
2833
+ else if (indexOf(seen, computed, 0) < 0) {
2834
+ if (iteratee || isLarge) {
2835
+ seen.push(computed);
2836
+ }
2837
+ result.push(value);
2838
+ }
2839
+ }
2840
+ return result;
2841
+ }
2842
+
2843
+ /**
2844
+ * The base implementation of `_.values` and `_.valuesIn` which creates an
2845
+ * array of `object` property values corresponding to the property names
2846
+ * of `props`.
2847
+ *
2848
+ * @private
2849
+ * @param {Object} object The object to query.
2850
+ * @param {Array} props The property names to get values for.
2851
+ * @returns {Object} Returns the array of property values.
2852
+ */
2853
+ function baseValues(object, props) {
2854
+ var index = -1,
2855
+ length = props.length,
2856
+ result = Array(length);
2857
+
2858
+ while (++index < length) {
2859
+ result[index] = object[props[index]];
2860
+ }
2861
+ return result;
2862
+ }
2863
+
2864
+ /**
2865
+ * The base implementation of `_.dropRightWhile`, `_.dropWhile`, `_.takeRightWhile`,
2866
+ * and `_.takeWhile` without support for callback shorthands and `this` binding.
2867
+ *
2868
+ * @private
2869
+ * @param {Array} array The array to query.
2870
+ * @param {Function} predicate The function invoked per iteration.
2871
+ * @param {boolean} [isDrop] Specify dropping elements instead of taking them.
2872
+ * @param {boolean} [fromRight] Specify iterating from right to left.
2873
+ * @returns {Array} Returns the slice of `array`.
2874
+ */
2875
+ function baseWhile(array, predicate, isDrop, fromRight) {
2876
+ var length = array.length,
2877
+ index = fromRight ? length : -1;
2878
+
2879
+ while ((fromRight ? index-- : ++index < length) && predicate(array[index], index, array)) {}
2880
+ return isDrop
2881
+ ? baseSlice(array, (fromRight ? 0 : index), (fromRight ? index + 1 : length))
2882
+ : baseSlice(array, (fromRight ? index + 1 : 0), (fromRight ? length : index));
2883
+ }
2884
+
2885
+ /**
2886
+ * The base implementation of `wrapperValue` which returns the result of
2887
+ * performing a sequence of actions on the unwrapped `value`, where each
2888
+ * successive action is supplied the return value of the previous.
2889
+ *
2890
+ * @private
2891
+ * @param {*} value The unwrapped value.
2892
+ * @param {Array} actions Actions to peform to resolve the unwrapped value.
2893
+ * @returns {*} Returns the resolved value.
2894
+ */
2895
+ function baseWrapperValue(value, actions) {
2896
+ var result = value;
2897
+ if (result instanceof LazyWrapper) {
2898
+ result = result.value();
2899
+ }
2900
+ var index = -1,
2901
+ length = actions.length;
2902
+
2903
+ while (++index < length) {
2904
+ var action = actions[index];
2905
+ result = action.func.apply(action.thisArg, arrayPush([result], action.args));
2906
+ }
2907
+ return result;
2908
+ }
2909
+
2910
+ /**
2911
+ * Performs a binary search of `array` to determine the index at which `value`
2912
+ * should be inserted into `array` in order to maintain its sort order.
2913
+ *
2914
+ * @private
2915
+ * @param {Array} array The sorted array to inspect.
2916
+ * @param {*} value The value to evaluate.
2917
+ * @param {boolean} [retHighest] Specify returning the highest qualified index.
2918
+ * @returns {number} Returns the index at which `value` should be inserted
2919
+ * into `array`.
2920
+ */
2921
+ function binaryIndex(array, value, retHighest) {
2922
+ var low = 0,
2923
+ high = array ? array.length : low;
2924
+
2925
+ if (typeof value == 'number' && value === value && high <= HALF_MAX_ARRAY_LENGTH) {
2926
+ while (low < high) {
2927
+ var mid = (low + high) >>> 1,
2928
+ computed = array[mid];
2929
+
2930
+ if ((retHighest ? (computed <= value) : (computed < value)) && computed !== null) {
2931
+ low = mid + 1;
2932
+ } else {
2933
+ high = mid;
2934
+ }
2935
+ }
2936
+ return high;
2937
+ }
2938
+ return binaryIndexBy(array, value, identity, retHighest);
2939
+ }
2940
+
2941
+ /**
2942
+ * This function is like `binaryIndex` except that it invokes `iteratee` for
2943
+ * `value` and each element of `array` to compute their sort ranking. The
2944
+ * iteratee is invoked with one argument; (value).
2945
+ *
2946
+ * @private
2947
+ * @param {Array} array The sorted array to inspect.
2948
+ * @param {*} value The value to evaluate.
2949
+ * @param {Function} iteratee The function invoked per iteration.
2950
+ * @param {boolean} [retHighest] Specify returning the highest qualified index.
2951
+ * @returns {number} Returns the index at which `value` should be inserted
2952
+ * into `array`.
2953
+ */
2954
+ function binaryIndexBy(array, value, iteratee, retHighest) {
2955
+ value = iteratee(value);
2956
+
2957
+ var low = 0,
2958
+ high = array ? array.length : 0,
2959
+ valIsNaN = value !== value,
2960
+ valIsNull = value === null,
2961
+ valIsUndef = value === undefined;
2962
+
2963
+ while (low < high) {
2964
+ var mid = nativeFloor((low + high) / 2),
2965
+ computed = iteratee(array[mid]),
2966
+ isDef = computed !== undefined,
2967
+ isReflexive = computed === computed;
2968
+
2969
+ if (valIsNaN) {
2970
+ var setLow = isReflexive || retHighest;
2971
+ } else if (valIsNull) {
2972
+ setLow = isReflexive && isDef && (retHighest || computed != null);
2973
+ } else if (valIsUndef) {
2974
+ setLow = isReflexive && (retHighest || isDef);
2975
+ } else if (computed == null) {
2976
+ setLow = false;
2977
+ } else {
2978
+ setLow = retHighest ? (computed <= value) : (computed < value);
2979
+ }
2980
+ if (setLow) {
2981
+ low = mid + 1;
2982
+ } else {
2983
+ high = mid;
2984
+ }
2985
+ }
2986
+ return nativeMin(high, MAX_ARRAY_INDEX);
2987
+ }
2988
+
2989
+ /**
2990
+ * A specialized version of `baseCallback` which only supports `this` binding
2991
+ * and specifying the number of arguments to provide to `func`.
2992
+ *
2993
+ * @private
2994
+ * @param {Function} func The function to bind.
2995
+ * @param {*} thisArg The `this` binding of `func`.
2996
+ * @param {number} [argCount] The number of arguments to provide to `func`.
2997
+ * @returns {Function} Returns the callback.
2998
+ */
2999
+ function bindCallback(func, thisArg, argCount) {
3000
+ if (typeof func != 'function') {
3001
+ return identity;
3002
+ }
3003
+ if (thisArg === undefined) {
3004
+ return func;
3005
+ }
3006
+ switch (argCount) {
3007
+ case 1: return function(value) {
3008
+ return func.call(thisArg, value);
3009
+ };
3010
+ case 3: return function(value, index, collection) {
3011
+ return func.call(thisArg, value, index, collection);
3012
+ };
3013
+ case 4: return function(accumulator, value, index, collection) {
3014
+ return func.call(thisArg, accumulator, value, index, collection);
3015
+ };
3016
+ case 5: return function(value, other, key, object, source) {
3017
+ return func.call(thisArg, value, other, key, object, source);
3018
+ };
3019
+ }
3020
+ return function() {
3021
+ return func.apply(thisArg, arguments);
3022
+ };
3023
+ }
3024
+
3025
+ /**
3026
+ * Creates a clone of the given array buffer.
3027
+ *
3028
+ * @private
3029
+ * @param {ArrayBuffer} buffer The array buffer to clone.
3030
+ * @returns {ArrayBuffer} Returns the cloned array buffer.
3031
+ */
3032
+ function bufferClone(buffer) {
3033
+ var result = new ArrayBuffer(buffer.byteLength),
3034
+ view = new Uint8Array(result);
3035
+
3036
+ view.set(new Uint8Array(buffer));
3037
+ return result;
3038
+ }
3039
+
3040
+ /**
3041
+ * Creates an array that is the composition of partially applied arguments,
3042
+ * placeholders, and provided arguments into a single array of arguments.
3043
+ *
3044
+ * @private
3045
+ * @param {Array|Object} args The provided arguments.
3046
+ * @param {Array} partials The arguments to prepend to those provided.
3047
+ * @param {Array} holders The `partials` placeholder indexes.
3048
+ * @returns {Array} Returns the new array of composed arguments.
3049
+ */
3050
+ function composeArgs(args, partials, holders) {
3051
+ var holdersLength = holders.length,
3052
+ argsIndex = -1,
3053
+ argsLength = nativeMax(args.length - holdersLength, 0),
3054
+ leftIndex = -1,
3055
+ leftLength = partials.length,
3056
+ result = Array(leftLength + argsLength);
3057
+
3058
+ while (++leftIndex < leftLength) {
3059
+ result[leftIndex] = partials[leftIndex];
3060
+ }
3061
+ while (++argsIndex < holdersLength) {
3062
+ result[holders[argsIndex]] = args[argsIndex];
3063
+ }
3064
+ while (argsLength--) {
3065
+ result[leftIndex++] = args[argsIndex++];
3066
+ }
3067
+ return result;
3068
+ }
3069
+
3070
+ /**
3071
+ * This function is like `composeArgs` except that the arguments composition
3072
+ * is tailored for `_.partialRight`.
3073
+ *
3074
+ * @private
3075
+ * @param {Array|Object} args The provided arguments.
3076
+ * @param {Array} partials The arguments to append to those provided.
3077
+ * @param {Array} holders The `partials` placeholder indexes.
3078
+ * @returns {Array} Returns the new array of composed arguments.
3079
+ */
3080
+ function composeArgsRight(args, partials, holders) {
3081
+ var holdersIndex = -1,
3082
+ holdersLength = holders.length,
3083
+ argsIndex = -1,
3084
+ argsLength = nativeMax(args.length - holdersLength, 0),
3085
+ rightIndex = -1,
3086
+ rightLength = partials.length,
3087
+ result = Array(argsLength + rightLength);
3088
+
3089
+ while (++argsIndex < argsLength) {
3090
+ result[argsIndex] = args[argsIndex];
3091
+ }
3092
+ var offset = argsIndex;
3093
+ while (++rightIndex < rightLength) {
3094
+ result[offset + rightIndex] = partials[rightIndex];
3095
+ }
3096
+ while (++holdersIndex < holdersLength) {
3097
+ result[offset + holders[holdersIndex]] = args[argsIndex++];
3098
+ }
3099
+ return result;
3100
+ }
3101
+
3102
+ /**
3103
+ * Creates a `_.countBy`, `_.groupBy`, `_.indexBy`, or `_.partition` function.
3104
+ *
3105
+ * @private
3106
+ * @param {Function} setter The function to set keys and values of the accumulator object.
3107
+ * @param {Function} [initializer] The function to initialize the accumulator object.
3108
+ * @returns {Function} Returns the new aggregator function.
3109
+ */
3110
+ function createAggregator(setter, initializer) {
3111
+ return function(collection, iteratee, thisArg) {
3112
+ var result = initializer ? initializer() : {};
3113
+ iteratee = getCallback(iteratee, thisArg, 3);
3114
+
3115
+ if (isArray(collection)) {
3116
+ var index = -1,
3117
+ length = collection.length;
3118
+
3119
+ while (++index < length) {
3120
+ var value = collection[index];
3121
+ setter(result, value, iteratee(value, index, collection), collection);
3122
+ }
3123
+ } else {
3124
+ baseEach(collection, function(value, key, collection) {
3125
+ setter(result, value, iteratee(value, key, collection), collection);
3126
+ });
3127
+ }
3128
+ return result;
3129
+ };
3130
+ }
3131
+
3132
+ /**
3133
+ * Creates a `_.assign`, `_.defaults`, or `_.merge` function.
3134
+ *
3135
+ * @private
3136
+ * @param {Function} assigner The function to assign values.
3137
+ * @returns {Function} Returns the new assigner function.
3138
+ */
3139
+ function createAssigner(assigner) {
3140
+ return restParam(function(object, sources) {
3141
+ var index = -1,
3142
+ length = object == null ? 0 : sources.length,
3143
+ customizer = length > 2 ? sources[length - 2] : undefined,
3144
+ guard = length > 2 ? sources[2] : undefined,
3145
+ thisArg = length > 1 ? sources[length - 1] : undefined;
3146
+
3147
+ if (typeof customizer == 'function') {
3148
+ customizer = bindCallback(customizer, thisArg, 5);
3149
+ length -= 2;
3150
+ } else {
3151
+ customizer = typeof thisArg == 'function' ? thisArg : undefined;
3152
+ length -= (customizer ? 1 : 0);
3153
+ }
3154
+ if (guard && isIterateeCall(sources[0], sources[1], guard)) {
3155
+ customizer = length < 3 ? undefined : customizer;
3156
+ length = 1;
3157
+ }
3158
+ while (++index < length) {
3159
+ var source = sources[index];
3160
+ if (source) {
3161
+ assigner(object, source, customizer);
3162
+ }
3163
+ }
3164
+ return object;
3165
+ });
3166
+ }
3167
+
3168
+ /**
3169
+ * Creates a `baseEach` or `baseEachRight` function.
3170
+ *
3171
+ * @private
3172
+ * @param {Function} eachFunc The function to iterate over a collection.
3173
+ * @param {boolean} [fromRight] Specify iterating from right to left.
3174
+ * @returns {Function} Returns the new base function.
3175
+ */
3176
+ function createBaseEach(eachFunc, fromRight) {
3177
+ return function(collection, iteratee) {
3178
+ var length = collection ? getLength(collection) : 0;
3179
+ if (!isLength(length)) {
3180
+ return eachFunc(collection, iteratee);
3181
+ }
3182
+ var index = fromRight ? length : -1,
3183
+ iterable = toObject(collection);
3184
+
3185
+ while ((fromRight ? index-- : ++index < length)) {
3186
+ if (iteratee(iterable[index], index, iterable) === false) {
3187
+ break;
3188
+ }
3189
+ }
3190
+ return collection;
3191
+ };
3192
+ }
3193
+
3194
+ /**
3195
+ * Creates a base function for `_.forIn` or `_.forInRight`.
3196
+ *
3197
+ * @private
3198
+ * @param {boolean} [fromRight] Specify iterating from right to left.
3199
+ * @returns {Function} Returns the new base function.
3200
+ */
3201
+ function createBaseFor(fromRight) {
3202
+ return function(object, iteratee, keysFunc) {
3203
+ var iterable = toObject(object),
3204
+ props = keysFunc(object),
3205
+ length = props.length,
3206
+ index = fromRight ? length : -1;
3207
+
3208
+ while ((fromRight ? index-- : ++index < length)) {
3209
+ var key = props[index];
3210
+ if (iteratee(iterable[key], key, iterable) === false) {
3211
+ break;
3212
+ }
3213
+ }
3214
+ return object;
3215
+ };
3216
+ }
3217
+
3218
+ /**
3219
+ * Creates a function that wraps `func` and invokes it with the `this`
3220
+ * binding of `thisArg`.
3221
+ *
3222
+ * @private
3223
+ * @param {Function} func The function to bind.
3224
+ * @param {*} [thisArg] The `this` binding of `func`.
3225
+ * @returns {Function} Returns the new bound function.
3226
+ */
3227
+ function createBindWrapper(func, thisArg) {
3228
+ var Ctor = createCtorWrapper(func);
3229
+
3230
+ function wrapper() {
3231
+ var fn = (this && this !== root && this instanceof wrapper) ? Ctor : func;
3232
+ return fn.apply(thisArg, arguments);
3233
+ }
3234
+ return wrapper;
3235
+ }
3236
+
3237
+ /**
3238
+ * Creates a `Set` cache object to optimize linear searches of large arrays.
3239
+ *
3240
+ * @private
3241
+ * @param {Array} [values] The values to cache.
3242
+ * @returns {null|Object} Returns the new cache object if `Set` is supported, else `null`.
3243
+ */
3244
+ function createCache(values) {
3245
+ return (nativeCreate && Set) ? new SetCache(values) : null;
3246
+ }
3247
+
3248
+ /**
3249
+ * Creates a function that produces compound words out of the words in a
3250
+ * given string.
3251
+ *
3252
+ * @private
3253
+ * @param {Function} callback The function to combine each word.
3254
+ * @returns {Function} Returns the new compounder function.
3255
+ */
3256
+ function createCompounder(callback) {
3257
+ return function(string) {
3258
+ var index = -1,
3259
+ array = words(deburr(string)),
3260
+ length = array.length,
3261
+ result = '';
3262
+
3263
+ while (++index < length) {
3264
+ result = callback(result, array[index], index);
3265
+ }
3266
+ return result;
3267
+ };
3268
+ }
3269
+
3270
+ /**
3271
+ * Creates a function that produces an instance of `Ctor` regardless of
3272
+ * whether it was invoked as part of a `new` expression or by `call` or `apply`.
3273
+ *
3274
+ * @private
3275
+ * @param {Function} Ctor The constructor to wrap.
3276
+ * @returns {Function} Returns the new wrapped function.
3277
+ */
3278
+ function createCtorWrapper(Ctor) {
3279
+ return function() {
3280
+ // Use a `switch` statement to work with class constructors.
3281
+ // See http://ecma-international.org/ecma-262/6.0/#sec-ecmascript-function-objects-call-thisargument-argumentslist
3282
+ // for more details.
3283
+ var args = arguments;
3284
+ switch (args.length) {
3285
+ case 0: return new Ctor;
3286
+ case 1: return new Ctor(args[0]);
3287
+ case 2: return new Ctor(args[0], args[1]);
3288
+ case 3: return new Ctor(args[0], args[1], args[2]);
3289
+ case 4: return new Ctor(args[0], args[1], args[2], args[3]);
3290
+ case 5: return new Ctor(args[0], args[1], args[2], args[3], args[4]);
3291
+ case 6: return new Ctor(args[0], args[1], args[2], args[3], args[4], args[5]);
3292
+ case 7: return new Ctor(args[0], args[1], args[2], args[3], args[4], args[5], args[6]);
3293
+ }
3294
+ var thisBinding = baseCreate(Ctor.prototype),
3295
+ result = Ctor.apply(thisBinding, args);
3296
+
3297
+ // Mimic the constructor's `return` behavior.
3298
+ // See https://es5.github.io/#x13.2.2 for more details.
3299
+ return isObject(result) ? result : thisBinding;
3300
+ };
3301
+ }
3302
+
3303
+ /**
3304
+ * Creates a `_.curry` or `_.curryRight` function.
3305
+ *
3306
+ * @private
3307
+ * @param {boolean} flag The curry bit flag.
3308
+ * @returns {Function} Returns the new curry function.
3309
+ */
3310
+ function createCurry(flag) {
3311
+ function curryFunc(func, arity, guard) {
3312
+ if (guard && isIterateeCall(func, arity, guard)) {
3313
+ arity = undefined;
3314
+ }
3315
+ var result = createWrapper(func, flag, undefined, undefined, undefined, undefined, undefined, arity);
3316
+ result.placeholder = curryFunc.placeholder;
3317
+ return result;
3318
+ }
3319
+ return curryFunc;
3320
+ }
3321
+
3322
+ /**
3323
+ * Creates a `_.defaults` or `_.defaultsDeep` function.
3324
+ *
3325
+ * @private
3326
+ * @param {Function} assigner The function to assign values.
3327
+ * @param {Function} customizer The function to customize assigned values.
3328
+ * @returns {Function} Returns the new defaults function.
3329
+ */
3330
+ function createDefaults(assigner, customizer) {
3331
+ return restParam(function(args) {
3332
+ var object = args[0];
3333
+ if (object == null) {
3334
+ return object;
3335
+ }
3336
+ args.push(customizer);
3337
+ return assigner.apply(undefined, args);
3338
+ });
3339
+ }
3340
+
3341
+ /**
3342
+ * Creates a `_.max` or `_.min` function.
3343
+ *
3344
+ * @private
3345
+ * @param {Function} comparator The function used to compare values.
3346
+ * @param {*} exValue The initial extremum value.
3347
+ * @returns {Function} Returns the new extremum function.
3348
+ */
3349
+ function createExtremum(comparator, exValue) {
3350
+ return function(collection, iteratee, thisArg) {
3351
+ if (thisArg && isIterateeCall(collection, iteratee, thisArg)) {
3352
+ iteratee = undefined;
3353
+ }
3354
+ iteratee = getCallback(iteratee, thisArg, 3);
3355
+ if (iteratee.length == 1) {
3356
+ collection = isArray(collection) ? collection : toIterable(collection);
3357
+ var result = arrayExtremum(collection, iteratee, comparator, exValue);
3358
+ if (!(collection.length && result === exValue)) {
3359
+ return result;
3360
+ }
3361
+ }
3362
+ return baseExtremum(collection, iteratee, comparator, exValue);
3363
+ };
3364
+ }
3365
+
3366
+ /**
3367
+ * Creates a `_.find` or `_.findLast` function.
3368
+ *
3369
+ * @private
3370
+ * @param {Function} eachFunc The function to iterate over a collection.
3371
+ * @param {boolean} [fromRight] Specify iterating from right to left.
3372
+ * @returns {Function} Returns the new find function.
3373
+ */
3374
+ function createFind(eachFunc, fromRight) {
3375
+ return function(collection, predicate, thisArg) {
3376
+ predicate = getCallback(predicate, thisArg, 3);
3377
+ if (isArray(collection)) {
3378
+ var index = baseFindIndex(collection, predicate, fromRight);
3379
+ return index > -1 ? collection[index] : undefined;
3380
+ }
3381
+ return baseFind(collection, predicate, eachFunc);
3382
+ };
3383
+ }
3384
+
3385
+ /**
3386
+ * Creates a `_.findIndex` or `_.findLastIndex` function.
3387
+ *
3388
+ * @private
3389
+ * @param {boolean} [fromRight] Specify iterating from right to left.
3390
+ * @returns {Function} Returns the new find function.
3391
+ */
3392
+ function createFindIndex(fromRight) {
3393
+ return function(array, predicate, thisArg) {
3394
+ if (!(array && array.length)) {
3395
+ return -1;
3396
+ }
3397
+ predicate = getCallback(predicate, thisArg, 3);
3398
+ return baseFindIndex(array, predicate, fromRight);
3399
+ };
3400
+ }
3401
+
3402
+ /**
3403
+ * Creates a `_.findKey` or `_.findLastKey` function.
3404
+ *
3405
+ * @private
3406
+ * @param {Function} objectFunc The function to iterate over an object.
3407
+ * @returns {Function} Returns the new find function.
3408
+ */
3409
+ function createFindKey(objectFunc) {
3410
+ return function(object, predicate, thisArg) {
3411
+ predicate = getCallback(predicate, thisArg, 3);
3412
+ return baseFind(object, predicate, objectFunc, true);
3413
+ };
3414
+ }
3415
+
3416
+ /**
3417
+ * Creates a `_.flow` or `_.flowRight` function.
3418
+ *
3419
+ * @private
3420
+ * @param {boolean} [fromRight] Specify iterating from right to left.
3421
+ * @returns {Function} Returns the new flow function.
3422
+ */
3423
+ function createFlow(fromRight) {
3424
+ return function() {
3425
+ var wrapper,
3426
+ length = arguments.length,
3427
+ index = fromRight ? length : -1,
3428
+ leftIndex = 0,
3429
+ funcs = Array(length);
3430
+
3431
+ while ((fromRight ? index-- : ++index < length)) {
3432
+ var func = funcs[leftIndex++] = arguments[index];
3433
+ if (typeof func != 'function') {
3434
+ throw new TypeError(FUNC_ERROR_TEXT);
3435
+ }
3436
+ if (!wrapper && LodashWrapper.prototype.thru && getFuncName(func) == 'wrapper') {
3437
+ wrapper = new LodashWrapper([], true);
3438
+ }
3439
+ }
3440
+ index = wrapper ? -1 : length;
3441
+ while (++index < length) {
3442
+ func = funcs[index];
3443
+
3444
+ var funcName = getFuncName(func),
3445
+ data = funcName == 'wrapper' ? getData(func) : undefined;
3446
+
3447
+ if (data && isLaziable(data[0]) && data[1] == (ARY_FLAG | CURRY_FLAG | PARTIAL_FLAG | REARG_FLAG) && !data[4].length && data[9] == 1) {
3448
+ wrapper = wrapper[getFuncName(data[0])].apply(wrapper, data[3]);
3449
+ } else {
3450
+ wrapper = (func.length == 1 && isLaziable(func)) ? wrapper[funcName]() : wrapper.thru(func);
3451
+ }
3452
+ }
3453
+ return function() {
3454
+ var args = arguments,
3455
+ value = args[0];
3456
+
3457
+ if (wrapper && args.length == 1 && isArray(value) && value.length >= LARGE_ARRAY_SIZE) {
3458
+ return wrapper.plant(value).value();
3459
+ }
3460
+ var index = 0,
3461
+ result = length ? funcs[index].apply(this, args) : value;
3462
+
3463
+ while (++index < length) {
3464
+ result = funcs[index].call(this, result);
3465
+ }
3466
+ return result;
3467
+ };
3468
+ };
3469
+ }
3470
+
3471
+ /**
3472
+ * Creates a function for `_.forEach` or `_.forEachRight`.
3473
+ *
3474
+ * @private
3475
+ * @param {Function} arrayFunc The function to iterate over an array.
3476
+ * @param {Function} eachFunc The function to iterate over a collection.
3477
+ * @returns {Function} Returns the new each function.
3478
+ */
3479
+ function createForEach(arrayFunc, eachFunc) {
3480
+ return function(collection, iteratee, thisArg) {
3481
+ return (typeof iteratee == 'function' && thisArg === undefined && isArray(collection))
3482
+ ? arrayFunc(collection, iteratee)
3483
+ : eachFunc(collection, bindCallback(iteratee, thisArg, 3));
3484
+ };
3485
+ }
3486
+
3487
+ /**
3488
+ * Creates a function for `_.forIn` or `_.forInRight`.
3489
+ *
3490
+ * @private
3491
+ * @param {Function} objectFunc The function to iterate over an object.
3492
+ * @returns {Function} Returns the new each function.
3493
+ */
3494
+ function createForIn(objectFunc) {
3495
+ return function(object, iteratee, thisArg) {
3496
+ if (typeof iteratee != 'function' || thisArg !== undefined) {
3497
+ iteratee = bindCallback(iteratee, thisArg, 3);
3498
+ }
3499
+ return objectFunc(object, iteratee, keysIn);
3500
+ };
3501
+ }
3502
+
3503
+ /**
3504
+ * Creates a function for `_.forOwn` or `_.forOwnRight`.
3505
+ *
3506
+ * @private
3507
+ * @param {Function} objectFunc The function to iterate over an object.
3508
+ * @returns {Function} Returns the new each function.
3509
+ */
3510
+ function createForOwn(objectFunc) {
3511
+ return function(object, iteratee, thisArg) {
3512
+ if (typeof iteratee != 'function' || thisArg !== undefined) {
3513
+ iteratee = bindCallback(iteratee, thisArg, 3);
3514
+ }
3515
+ return objectFunc(object, iteratee);
3516
+ };
3517
+ }
3518
+
3519
+ /**
3520
+ * Creates a function for `_.mapKeys` or `_.mapValues`.
3521
+ *
3522
+ * @private
3523
+ * @param {boolean} [isMapKeys] Specify mapping keys instead of values.
3524
+ * @returns {Function} Returns the new map function.
3525
+ */
3526
+ function createObjectMapper(isMapKeys) {
3527
+ return function(object, iteratee, thisArg) {
3528
+ var result = {};
3529
+ iteratee = getCallback(iteratee, thisArg, 3);
3530
+
3531
+ baseForOwn(object, function(value, key, object) {
3532
+ var mapped = iteratee(value, key, object);
3533
+ key = isMapKeys ? mapped : key;
3534
+ value = isMapKeys ? value : mapped;
3535
+ result[key] = value;
3536
+ });
3537
+ return result;
3538
+ };
3539
+ }
3540
+
3541
+ /**
3542
+ * Creates a function for `_.padLeft` or `_.padRight`.
3543
+ *
3544
+ * @private
3545
+ * @param {boolean} [fromRight] Specify padding from the right.
3546
+ * @returns {Function} Returns the new pad function.
3547
+ */
3548
+ function createPadDir(fromRight) {
3549
+ return function(string, length, chars) {
3550
+ string = baseToString(string);
3551
+ return (fromRight ? string : '') + createPadding(string, length, chars) + (fromRight ? '' : string);
3552
+ };
3553
+ }
3554
+
3555
+ /**
3556
+ * Creates a `_.partial` or `_.partialRight` function.
3557
+ *
3558
+ * @private
3559
+ * @param {boolean} flag The partial bit flag.
3560
+ * @returns {Function} Returns the new partial function.
3561
+ */
3562
+ function createPartial(flag) {
3563
+ var partialFunc = restParam(function(func, partials) {
3564
+ var holders = replaceHolders(partials, partialFunc.placeholder);
3565
+ return createWrapper(func, flag, undefined, partials, holders);
3566
+ });
3567
+ return partialFunc;
3568
+ }
3569
+
3570
+ /**
3571
+ * Creates a function for `_.reduce` or `_.reduceRight`.
3572
+ *
3573
+ * @private
3574
+ * @param {Function} arrayFunc The function to iterate over an array.
3575
+ * @param {Function} eachFunc The function to iterate over a collection.
3576
+ * @returns {Function} Returns the new each function.
3577
+ */
3578
+ function createReduce(arrayFunc, eachFunc) {
3579
+ return function(collection, iteratee, accumulator, thisArg) {
3580
+ var initFromArray = arguments.length < 3;
3581
+ return (typeof iteratee == 'function' && thisArg === undefined && isArray(collection))
3582
+ ? arrayFunc(collection, iteratee, accumulator, initFromArray)
3583
+ : baseReduce(collection, getCallback(iteratee, thisArg, 4), accumulator, initFromArray, eachFunc);
3584
+ };
3585
+ }
3586
+
3587
+ /**
3588
+ * Creates a function that wraps `func` and invokes it with optional `this`
3589
+ * binding of, partial application, and currying.
3590
+ *
3591
+ * @private
3592
+ * @param {Function|string} func The function or method name to reference.
3593
+ * @param {number} bitmask The bitmask of flags. See `createWrapper` for more details.
3594
+ * @param {*} [thisArg] The `this` binding of `func`.
3595
+ * @param {Array} [partials] The arguments to prepend to those provided to the new function.
3596
+ * @param {Array} [holders] The `partials` placeholder indexes.
3597
+ * @param {Array} [partialsRight] The arguments to append to those provided to the new function.
3598
+ * @param {Array} [holdersRight] The `partialsRight` placeholder indexes.
3599
+ * @param {Array} [argPos] The argument positions of the new function.
3600
+ * @param {number} [ary] The arity cap of `func`.
3601
+ * @param {number} [arity] The arity of `func`.
3602
+ * @returns {Function} Returns the new wrapped function.
3603
+ */
3604
+ function createHybridWrapper(func, bitmask, thisArg, partials, holders, partialsRight, holdersRight, argPos, ary, arity) {
3605
+ var isAry = bitmask & ARY_FLAG,
3606
+ isBind = bitmask & BIND_FLAG,
3607
+ isBindKey = bitmask & BIND_KEY_FLAG,
3608
+ isCurry = bitmask & CURRY_FLAG,
3609
+ isCurryBound = bitmask & CURRY_BOUND_FLAG,
3610
+ isCurryRight = bitmask & CURRY_RIGHT_FLAG,
3611
+ Ctor = isBindKey ? undefined : createCtorWrapper(func);
3612
+
3613
+ function wrapper() {
3614
+ // Avoid `arguments` object use disqualifying optimizations by
3615
+ // converting it to an array before providing it to other functions.
3616
+ var length = arguments.length,
3617
+ index = length,
3618
+ args = Array(length);
3619
+
3620
+ while (index--) {
3621
+ args[index] = arguments[index];
3622
+ }
3623
+ if (partials) {
3624
+ args = composeArgs(args, partials, holders);
3625
+ }
3626
+ if (partialsRight) {
3627
+ args = composeArgsRight(args, partialsRight, holdersRight);
3628
+ }
3629
+ if (isCurry || isCurryRight) {
3630
+ var placeholder = wrapper.placeholder,
3631
+ argsHolders = replaceHolders(args, placeholder);
3632
+
3633
+ length -= argsHolders.length;
3634
+ if (length < arity) {
3635
+ var newArgPos = argPos ? arrayCopy(argPos) : undefined,
3636
+ newArity = nativeMax(arity - length, 0),
3637
+ newsHolders = isCurry ? argsHolders : undefined,
3638
+ newHoldersRight = isCurry ? undefined : argsHolders,
3639
+ newPartials = isCurry ? args : undefined,
3640
+ newPartialsRight = isCurry ? undefined : args;
3641
+
3642
+ bitmask |= (isCurry ? PARTIAL_FLAG : PARTIAL_RIGHT_FLAG);
3643
+ bitmask &= ~(isCurry ? PARTIAL_RIGHT_FLAG : PARTIAL_FLAG);
3644
+
3645
+ if (!isCurryBound) {
3646
+ bitmask &= ~(BIND_FLAG | BIND_KEY_FLAG);
3647
+ }
3648
+ var newData = [func, bitmask, thisArg, newPartials, newsHolders, newPartialsRight, newHoldersRight, newArgPos, ary, newArity],
3649
+ result = createHybridWrapper.apply(undefined, newData);
3650
+
3651
+ if (isLaziable(func)) {
3652
+ setData(result, newData);
3653
+ }
3654
+ result.placeholder = placeholder;
3655
+ return result;
3656
+ }
3657
+ }
3658
+ var thisBinding = isBind ? thisArg : this,
3659
+ fn = isBindKey ? thisBinding[func] : func;
3660
+
3661
+ if (argPos) {
3662
+ args = reorder(args, argPos);
3663
+ }
3664
+ if (isAry && ary < args.length) {
3665
+ args.length = ary;
3666
+ }
3667
+ if (this && this !== root && this instanceof wrapper) {
3668
+ fn = Ctor || createCtorWrapper(func);
3669
+ }
3670
+ return fn.apply(thisBinding, args);
3671
+ }
3672
+ return wrapper;
3673
+ }
3674
+
3675
+ /**
3676
+ * Creates the padding required for `string` based on the given `length`.
3677
+ * The `chars` string is truncated if the number of characters exceeds `length`.
3678
+ *
3679
+ * @private
3680
+ * @param {string} string The string to create padding for.
3681
+ * @param {number} [length=0] The padding length.
3682
+ * @param {string} [chars=' '] The string used as padding.
3683
+ * @returns {string} Returns the pad for `string`.
3684
+ */
3685
+ function createPadding(string, length, chars) {
3686
+ var strLength = string.length;
3687
+ length = +length;
3688
+
3689
+ if (strLength >= length || !nativeIsFinite(length)) {
3690
+ return '';
3691
+ }
3692
+ var padLength = length - strLength;
3693
+ chars = chars == null ? ' ' : (chars + '');
3694
+ return repeat(chars, nativeCeil(padLength / chars.length)).slice(0, padLength);
3695
+ }
3696
+
3697
+ /**
3698
+ * Creates a function that wraps `func` and invokes it with the optional `this`
3699
+ * binding of `thisArg` and the `partials` prepended to those provided to
3700
+ * the wrapper.
3701
+ *
3702
+ * @private
3703
+ * @param {Function} func The function to partially apply arguments to.
3704
+ * @param {number} bitmask The bitmask of flags. See `createWrapper` for more details.
3705
+ * @param {*} thisArg The `this` binding of `func`.
3706
+ * @param {Array} partials The arguments to prepend to those provided to the new function.
3707
+ * @returns {Function} Returns the new bound function.
3708
+ */
3709
+ function createPartialWrapper(func, bitmask, thisArg, partials) {
3710
+ var isBind = bitmask & BIND_FLAG,
3711
+ Ctor = createCtorWrapper(func);
3712
+
3713
+ function wrapper() {
3714
+ // Avoid `arguments` object use disqualifying optimizations by
3715
+ // converting it to an array before providing it `func`.
3716
+ var argsIndex = -1,
3717
+ argsLength = arguments.length,
3718
+ leftIndex = -1,
3719
+ leftLength = partials.length,
3720
+ args = Array(leftLength + argsLength);
3721
+
3722
+ while (++leftIndex < leftLength) {
3723
+ args[leftIndex] = partials[leftIndex];
3724
+ }
3725
+ while (argsLength--) {
3726
+ args[leftIndex++] = arguments[++argsIndex];
3727
+ }
3728
+ var fn = (this && this !== root && this instanceof wrapper) ? Ctor : func;
3729
+ return fn.apply(isBind ? thisArg : this, args);
3730
+ }
3731
+ return wrapper;
3732
+ }
3733
+
3734
+ /**
3735
+ * Creates a `_.ceil`, `_.floor`, or `_.round` function.
3736
+ *
3737
+ * @private
3738
+ * @param {string} methodName The name of the `Math` method to use when rounding.
3739
+ * @returns {Function} Returns the new round function.
3740
+ */
3741
+ function createRound(methodName) {
3742
+ var func = Math[methodName];
3743
+ return function(number, precision) {
3744
+ precision = precision === undefined ? 0 : (+precision || 0);
3745
+ if (precision) {
3746
+ precision = pow(10, precision);
3747
+ return func(number * precision) / precision;
3748
+ }
3749
+ return func(number);
3750
+ };
3751
+ }
3752
+
3753
+ /**
3754
+ * Creates a `_.sortedIndex` or `_.sortedLastIndex` function.
3755
+ *
3756
+ * @private
3757
+ * @param {boolean} [retHighest] Specify returning the highest qualified index.
3758
+ * @returns {Function} Returns the new index function.
3759
+ */
3760
+ function createSortedIndex(retHighest) {
3761
+ return function(array, value, iteratee, thisArg) {
3762
+ var callback = getCallback(iteratee);
3763
+ return (iteratee == null && callback === baseCallback)
3764
+ ? binaryIndex(array, value, retHighest)
3765
+ : binaryIndexBy(array, value, callback(iteratee, thisArg, 1), retHighest);
3766
+ };
3767
+ }
3768
+
3769
+ /**
3770
+ * Creates a function that either curries or invokes `func` with optional
3771
+ * `this` binding and partially applied arguments.
3772
+ *
3773
+ * @private
3774
+ * @param {Function|string} func The function or method name to reference.
3775
+ * @param {number} bitmask The bitmask of flags.
3776
+ * The bitmask may be composed of the following flags:
3777
+ * 1 - `_.bind`
3778
+ * 2 - `_.bindKey`
3779
+ * 4 - `_.curry` or `_.curryRight` of a bound function
3780
+ * 8 - `_.curry`
3781
+ * 16 - `_.curryRight`
3782
+ * 32 - `_.partial`
3783
+ * 64 - `_.partialRight`
3784
+ * 128 - `_.rearg`
3785
+ * 256 - `_.ary`
3786
+ * @param {*} [thisArg] The `this` binding of `func`.
3787
+ * @param {Array} [partials] The arguments to be partially applied.
3788
+ * @param {Array} [holders] The `partials` placeholder indexes.
3789
+ * @param {Array} [argPos] The argument positions of the new function.
3790
+ * @param {number} [ary] The arity cap of `func`.
3791
+ * @param {number} [arity] The arity of `func`.
3792
+ * @returns {Function} Returns the new wrapped function.
3793
+ */
3794
+ function createWrapper(func, bitmask, thisArg, partials, holders, argPos, ary, arity) {
3795
+ var isBindKey = bitmask & BIND_KEY_FLAG;
3796
+ if (!isBindKey && typeof func != 'function') {
3797
+ throw new TypeError(FUNC_ERROR_TEXT);
3798
+ }
3799
+ var length = partials ? partials.length : 0;
3800
+ if (!length) {
3801
+ bitmask &= ~(PARTIAL_FLAG | PARTIAL_RIGHT_FLAG);
3802
+ partials = holders = undefined;
3803
+ }
3804
+ length -= (holders ? holders.length : 0);
3805
+ if (bitmask & PARTIAL_RIGHT_FLAG) {
3806
+ var partialsRight = partials,
3807
+ holdersRight = holders;
3808
+
3809
+ partials = holders = undefined;
3810
+ }
3811
+ var data = isBindKey ? undefined : getData(func),
3812
+ newData = [func, bitmask, thisArg, partials, holders, partialsRight, holdersRight, argPos, ary, arity];
3813
+
3814
+ if (data) {
3815
+ mergeData(newData, data);
3816
+ bitmask = newData[1];
3817
+ arity = newData[9];
3818
+ }
3819
+ newData[9] = arity == null
3820
+ ? (isBindKey ? 0 : func.length)
3821
+ : (nativeMax(arity - length, 0) || 0);
3822
+
3823
+ if (bitmask == BIND_FLAG) {
3824
+ var result = createBindWrapper(newData[0], newData[2]);
3825
+ } else if ((bitmask == PARTIAL_FLAG || bitmask == (BIND_FLAG | PARTIAL_FLAG)) && !newData[4].length) {
3826
+ result = createPartialWrapper.apply(undefined, newData);
3827
+ } else {
3828
+ result = createHybridWrapper.apply(undefined, newData);
3829
+ }
3830
+ var setter = data ? baseSetData : setData;
3831
+ return setter(result, newData);
3832
+ }
3833
+
3834
+ /**
3835
+ * A specialized version of `baseIsEqualDeep` for arrays with support for
3836
+ * partial deep comparisons.
3837
+ *
3838
+ * @private
3839
+ * @param {Array} array The array to compare.
3840
+ * @param {Array} other The other array to compare.
3841
+ * @param {Function} equalFunc The function to determine equivalents of values.
3842
+ * @param {Function} [customizer] The function to customize comparing arrays.
3843
+ * @param {boolean} [isLoose] Specify performing partial comparisons.
3844
+ * @param {Array} [stackA] Tracks traversed `value` objects.
3845
+ * @param {Array} [stackB] Tracks traversed `other` objects.
3846
+ * @returns {boolean} Returns `true` if the arrays are equivalent, else `false`.
3847
+ */
3848
+ function equalArrays(array, other, equalFunc, customizer, isLoose, stackA, stackB) {
3849
+ var index = -1,
3850
+ arrLength = array.length,
3851
+ othLength = other.length;
3852
+
3853
+ if (arrLength != othLength && !(isLoose && othLength > arrLength)) {
3854
+ return false;
3855
+ }
3856
+ // Ignore non-index properties.
3857
+ while (++index < arrLength) {
3858
+ var arrValue = array[index],
3859
+ othValue = other[index],
3860
+ result = customizer ? customizer(isLoose ? othValue : arrValue, isLoose ? arrValue : othValue, index) : undefined;
3861
+
3862
+ if (result !== undefined) {
3863
+ if (result) {
3864
+ continue;
3865
+ }
3866
+ return false;
3867
+ }
3868
+ // Recursively compare arrays (susceptible to call stack limits).
3869
+ if (isLoose) {
3870
+ if (!arraySome(other, function(othValue) {
3871
+ return arrValue === othValue || equalFunc(arrValue, othValue, customizer, isLoose, stackA, stackB);
3872
+ })) {
3873
+ return false;
3874
+ }
3875
+ } else if (!(arrValue === othValue || equalFunc(arrValue, othValue, customizer, isLoose, stackA, stackB))) {
3876
+ return false;
3877
+ }
3878
+ }
3879
+ return true;
3880
+ }
3881
+
3882
+ /**
3883
+ * A specialized version of `baseIsEqualDeep` for comparing objects of
3884
+ * the same `toStringTag`.
3885
+ *
3886
+ * **Note:** This function only supports comparing values with tags of
3887
+ * `Boolean`, `Date`, `Error`, `Number`, `RegExp`, or `String`.
3888
+ *
3889
+ * @private
3890
+ * @param {Object} object The object to compare.
3891
+ * @param {Object} other The other object to compare.
3892
+ * @param {string} tag The `toStringTag` of the objects to compare.
3893
+ * @returns {boolean} Returns `true` if the objects are equivalent, else `false`.
3894
+ */
3895
+ function equalByTag(object, other, tag) {
3896
+ switch (tag) {
3897
+ case boolTag:
3898
+ case dateTag:
3899
+ // Coerce dates and booleans to numbers, dates to milliseconds and booleans
3900
+ // to `1` or `0` treating invalid dates coerced to `NaN` as not equal.
3901
+ return +object == +other;
3902
+
3903
+ case errorTag:
3904
+ return object.name == other.name && object.message == other.message;
3905
+
3906
+ case numberTag:
3907
+ // Treat `NaN` vs. `NaN` as equal.
3908
+ return (object != +object)
3909
+ ? other != +other
3910
+ : object == +other;
3911
+
3912
+ case regexpTag:
3913
+ case stringTag:
3914
+ // Coerce regexes to strings and treat strings primitives and string
3915
+ // objects as equal. See https://es5.github.io/#x15.10.6.4 for more details.
3916
+ return object == (other + '');
3917
+ }
3918
+ return false;
3919
+ }
3920
+
3921
+ /**
3922
+ * A specialized version of `baseIsEqualDeep` for objects with support for
3923
+ * partial deep comparisons.
3924
+ *
3925
+ * @private
3926
+ * @param {Object} object The object to compare.
3927
+ * @param {Object} other The other object to compare.
3928
+ * @param {Function} equalFunc The function to determine equivalents of values.
3929
+ * @param {Function} [customizer] The function to customize comparing values.
3930
+ * @param {boolean} [isLoose] Specify performing partial comparisons.
3931
+ * @param {Array} [stackA] Tracks traversed `value` objects.
3932
+ * @param {Array} [stackB] Tracks traversed `other` objects.
3933
+ * @returns {boolean} Returns `true` if the objects are equivalent, else `false`.
3934
+ */
3935
+ function equalObjects(object, other, equalFunc, customizer, isLoose, stackA, stackB) {
3936
+ var objProps = keys(object),
3937
+ objLength = objProps.length,
3938
+ othProps = keys(other),
3939
+ othLength = othProps.length;
3940
+
3941
+ if (objLength != othLength && !isLoose) {
3942
+ return false;
3943
+ }
3944
+ var index = objLength;
3945
+ while (index--) {
3946
+ var key = objProps[index];
3947
+ if (!(isLoose ? key in other : hasOwnProperty.call(other, key))) {
3948
+ return false;
3949
+ }
3950
+ }
3951
+ var skipCtor = isLoose;
3952
+ while (++index < objLength) {
3953
+ key = objProps[index];
3954
+ var objValue = object[key],
3955
+ othValue = other[key],
3956
+ result = customizer ? customizer(isLoose ? othValue : objValue, isLoose? objValue : othValue, key) : undefined;
3957
+
3958
+ // Recursively compare objects (susceptible to call stack limits).
3959
+ if (!(result === undefined ? equalFunc(objValue, othValue, customizer, isLoose, stackA, stackB) : result)) {
3960
+ return false;
3961
+ }
3962
+ skipCtor || (skipCtor = key == 'constructor');
3963
+ }
3964
+ if (!skipCtor) {
3965
+ var objCtor = object.constructor,
3966
+ othCtor = other.constructor;
3967
+
3968
+ // Non `Object` object instances with different constructors are not equal.
3969
+ if (objCtor != othCtor &&
3970
+ ('constructor' in object && 'constructor' in other) &&
3971
+ !(typeof objCtor == 'function' && objCtor instanceof objCtor &&
3972
+ typeof othCtor == 'function' && othCtor instanceof othCtor)) {
3973
+ return false;
3974
+ }
3975
+ }
3976
+ return true;
3977
+ }
3978
+
3979
+ /**
3980
+ * Gets the appropriate "callback" function. If the `_.callback` method is
3981
+ * customized this function returns the custom method, otherwise it returns
3982
+ * the `baseCallback` function. If arguments are provided the chosen function
3983
+ * is invoked with them and its result is returned.
3984
+ *
3985
+ * @private
3986
+ * @returns {Function} Returns the chosen function or its result.
3987
+ */
3988
+ function getCallback(func, thisArg, argCount) {
3989
+ var result = lodash.callback || callback;
3990
+ result = result === callback ? baseCallback : result;
3991
+ return argCount ? result(func, thisArg, argCount) : result;
3992
+ }
3993
+
3994
+ /**
3995
+ * Gets metadata for `func`.
3996
+ *
3997
+ * @private
3998
+ * @param {Function} func The function to query.
3999
+ * @returns {*} Returns the metadata for `func`.
4000
+ */
4001
+ var getData = !metaMap ? noop : function(func) {
4002
+ return metaMap.get(func);
4003
+ };
4004
+
4005
+ /**
4006
+ * Gets the name of `func`.
4007
+ *
4008
+ * @private
4009
+ * @param {Function} func The function to query.
4010
+ * @returns {string} Returns the function name.
4011
+ */
4012
+ function getFuncName(func) {
4013
+ var result = (func.name + ''),
4014
+ array = realNames[result],
4015
+ length = array ? array.length : 0;
4016
+
4017
+ while (length--) {
4018
+ var data = array[length],
4019
+ otherFunc = data.func;
4020
+ if (otherFunc == null || otherFunc == func) {
4021
+ return data.name;
4022
+ }
4023
+ }
4024
+ return result;
4025
+ }
4026
+
4027
+ /**
4028
+ * Gets the appropriate "indexOf" function. If the `_.indexOf` method is
4029
+ * customized this function returns the custom method, otherwise it returns
4030
+ * the `baseIndexOf` function. If arguments are provided the chosen function
4031
+ * is invoked with them and its result is returned.
4032
+ *
4033
+ * @private
4034
+ * @returns {Function|number} Returns the chosen function or its result.
4035
+ */
4036
+ function getIndexOf(collection, target, fromIndex) {
4037
+ var result = lodash.indexOf || indexOf;
4038
+ result = result === indexOf ? baseIndexOf : result;
4039
+ return collection ? result(collection, target, fromIndex) : result;
4040
+ }
4041
+
4042
+ /**
4043
+ * Gets the "length" property value of `object`.
4044
+ *
4045
+ * **Note:** This function is used to avoid a [JIT bug](https://bugs.webkit.org/show_bug.cgi?id=142792)
4046
+ * that affects Safari on at least iOS 8.1-8.3 ARM64.
4047
+ *
4048
+ * @private
4049
+ * @param {Object} object The object to query.
4050
+ * @returns {*} Returns the "length" value.
4051
+ */
4052
+ var getLength = baseProperty('length');
4053
+
4054
+ /**
4055
+ * Gets the propery names, values, and compare flags of `object`.
4056
+ *
4057
+ * @private
4058
+ * @param {Object} object The object to query.
4059
+ * @returns {Array} Returns the match data of `object`.
4060
+ */
4061
+ function getMatchData(object) {
4062
+ var result = pairs(object),
4063
+ length = result.length;
4064
+
4065
+ while (length--) {
4066
+ result[length][2] = isStrictComparable(result[length][1]);
4067
+ }
4068
+ return result;
4069
+ }
4070
+
4071
+ /**
4072
+ * Gets the native function at `key` of `object`.
4073
+ *
4074
+ * @private
4075
+ * @param {Object} object The object to query.
4076
+ * @param {string} key The key of the method to get.
4077
+ * @returns {*} Returns the function if it's native, else `undefined`.
4078
+ */
4079
+ function getNative(object, key) {
4080
+ var value = object == null ? undefined : object[key];
4081
+ return isNative(value) ? value : undefined;
4082
+ }
4083
+
4084
+ /**
4085
+ * Gets the view, applying any `transforms` to the `start` and `end` positions.
4086
+ *
4087
+ * @private
4088
+ * @param {number} start The start of the view.
4089
+ * @param {number} end The end of the view.
4090
+ * @param {Array} transforms The transformations to apply to the view.
4091
+ * @returns {Object} Returns an object containing the `start` and `end`
4092
+ * positions of the view.
4093
+ */
4094
+ function getView(start, end, transforms) {
4095
+ var index = -1,
4096
+ length = transforms.length;
4097
+
4098
+ while (++index < length) {
4099
+ var data = transforms[index],
4100
+ size = data.size;
4101
+
4102
+ switch (data.type) {
4103
+ case 'drop': start += size; break;
4104
+ case 'dropRight': end -= size; break;
4105
+ case 'take': end = nativeMin(end, start + size); break;
4106
+ case 'takeRight': start = nativeMax(start, end - size); break;
4107
+ }
4108
+ }
4109
+ return { 'start': start, 'end': end };
4110
+ }
4111
+
4112
+ /**
4113
+ * Initializes an array clone.
4114
+ *
4115
+ * @private
4116
+ * @param {Array} array The array to clone.
4117
+ * @returns {Array} Returns the initialized clone.
4118
+ */
4119
+ function initCloneArray(array) {
4120
+ var length = array.length,
4121
+ result = new array.constructor(length);
4122
+
4123
+ // Add array properties assigned by `RegExp#exec`.
4124
+ if (length && typeof array[0] == 'string' && hasOwnProperty.call(array, 'index')) {
4125
+ result.index = array.index;
4126
+ result.input = array.input;
4127
+ }
4128
+ return result;
4129
+ }
4130
+
4131
+ /**
4132
+ * Initializes an object clone.
4133
+ *
4134
+ * @private
4135
+ * @param {Object} object The object to clone.
4136
+ * @returns {Object} Returns the initialized clone.
4137
+ */
4138
+ function initCloneObject(object) {
4139
+ var Ctor = object.constructor;
4140
+ if (!(typeof Ctor == 'function' && Ctor instanceof Ctor)) {
4141
+ Ctor = Object;
4142
+ }
4143
+ return new Ctor;
4144
+ }
4145
+
4146
+ /**
4147
+ * Initializes an object clone based on its `toStringTag`.
4148
+ *
4149
+ * **Note:** This function only supports cloning values with tags of
4150
+ * `Boolean`, `Date`, `Error`, `Number`, `RegExp`, or `String`.
4151
+ *
4152
+ * @private
4153
+ * @param {Object} object The object to clone.
4154
+ * @param {string} tag The `toStringTag` of the object to clone.
4155
+ * @param {boolean} [isDeep] Specify a deep clone.
4156
+ * @returns {Object} Returns the initialized clone.
4157
+ */
4158
+ function initCloneByTag(object, tag, isDeep) {
4159
+ var Ctor = object.constructor;
4160
+ switch (tag) {
4161
+ case arrayBufferTag:
4162
+ return bufferClone(object);
4163
+
4164
+ case boolTag:
4165
+ case dateTag:
4166
+ return new Ctor(+object);
4167
+
4168
+ case float32Tag: case float64Tag:
4169
+ case int8Tag: case int16Tag: case int32Tag:
4170
+ case uint8Tag: case uint8ClampedTag: case uint16Tag: case uint32Tag:
4171
+ // Safari 5 mobile incorrectly has `Object` as the constructor of typed arrays.
4172
+ if (Ctor instanceof Ctor) {
4173
+ Ctor = ctorByTag[tag];
4174
+ }
4175
+ var buffer = object.buffer;
4176
+ return new Ctor(isDeep ? bufferClone(buffer) : buffer, object.byteOffset, object.length);
4177
+
4178
+ case numberTag:
4179
+ case stringTag:
4180
+ return new Ctor(object);
4181
+
4182
+ case regexpTag:
4183
+ var result = new Ctor(object.source, reFlags.exec(object));
4184
+ result.lastIndex = object.lastIndex;
4185
+ }
4186
+ return result;
4187
+ }
4188
+
4189
+ /**
4190
+ * Invokes the method at `path` on `object`.
4191
+ *
4192
+ * @private
4193
+ * @param {Object} object The object to query.
4194
+ * @param {Array|string} path The path of the method to invoke.
4195
+ * @param {Array} args The arguments to invoke the method with.
4196
+ * @returns {*} Returns the result of the invoked method.
4197
+ */
4198
+ function invokePath(object, path, args) {
4199
+ if (object != null && !isKey(path, object)) {
4200
+ path = toPath(path);
4201
+ object = path.length == 1 ? object : baseGet(object, baseSlice(path, 0, -1));
4202
+ path = last(path);
4203
+ }
4204
+ var func = object == null ? object : object[path];
4205
+ return func == null ? undefined : func.apply(object, args);
4206
+ }
4207
+
4208
+ /**
4209
+ * Checks if `value` is array-like.
4210
+ *
4211
+ * @private
4212
+ * @param {*} value The value to check.
4213
+ * @returns {boolean} Returns `true` if `value` is array-like, else `false`.
4214
+ */
4215
+ function isArrayLike(value) {
4216
+ return value != null && isLength(getLength(value));
4217
+ }
4218
+
4219
+ /**
4220
+ * Checks if `value` is a valid array-like index.
4221
+ *
4222
+ * @private
4223
+ * @param {*} value The value to check.
4224
+ * @param {number} [length=MAX_SAFE_INTEGER] The upper bounds of a valid index.
4225
+ * @returns {boolean} Returns `true` if `value` is a valid index, else `false`.
4226
+ */
4227
+ function isIndex(value, length) {
4228
+ value = (typeof value == 'number' || reIsUint.test(value)) ? +value : -1;
4229
+ length = length == null ? MAX_SAFE_INTEGER : length;
4230
+ return value > -1 && value % 1 == 0 && value < length;
4231
+ }
4232
+
4233
+ /**
4234
+ * Checks if the provided arguments are from an iteratee call.
4235
+ *
4236
+ * @private
4237
+ * @param {*} value The potential iteratee value argument.
4238
+ * @param {*} index The potential iteratee index or key argument.
4239
+ * @param {*} object The potential iteratee object argument.
4240
+ * @returns {boolean} Returns `true` if the arguments are from an iteratee call, else `false`.
4241
+ */
4242
+ function isIterateeCall(value, index, object) {
4243
+ if (!isObject(object)) {
4244
+ return false;
4245
+ }
4246
+ var type = typeof index;
4247
+ if (type == 'number'
4248
+ ? (isArrayLike(object) && isIndex(index, object.length))
4249
+ : (type == 'string' && index in object)) {
4250
+ var other = object[index];
4251
+ return value === value ? (value === other) : (other !== other);
4252
+ }
4253
+ return false;
4254
+ }
4255
+
4256
+ /**
4257
+ * Checks if `value` is a property name and not a property path.
4258
+ *
4259
+ * @private
4260
+ * @param {*} value The value to check.
4261
+ * @param {Object} [object] The object to query keys on.
4262
+ * @returns {boolean} Returns `true` if `value` is a property name, else `false`.
4263
+ */
4264
+ function isKey(value, object) {
4265
+ var type = typeof value;
4266
+ if ((type == 'string' && reIsPlainProp.test(value)) || type == 'number') {
4267
+ return true;
4268
+ }
4269
+ if (isArray(value)) {
4270
+ return false;
4271
+ }
4272
+ var result = !reIsDeepProp.test(value);
4273
+ return result || (object != null && value in toObject(object));
4274
+ }
4275
+
4276
+ /**
4277
+ * Checks if `func` has a lazy counterpart.
4278
+ *
4279
+ * @private
4280
+ * @param {Function} func The function to check.
4281
+ * @returns {boolean} Returns `true` if `func` has a lazy counterpart, else `false`.
4282
+ */
4283
+ function isLaziable(func) {
4284
+ var funcName = getFuncName(func),
4285
+ other = lodash[funcName];
4286
+
4287
+ if (typeof other != 'function' || !(funcName in LazyWrapper.prototype)) {
4288
+ return false;
4289
+ }
4290
+ if (func === other) {
4291
+ return true;
4292
+ }
4293
+ var data = getData(other);
4294
+ return !!data && func === data[0];
4295
+ }
4296
+
4297
+ /**
4298
+ * Checks if `value` is a valid array-like length.
4299
+ *
4300
+ * **Note:** This function is based on [`ToLength`](http://ecma-international.org/ecma-262/6.0/#sec-tolength).
4301
+ *
4302
+ * @private
4303
+ * @param {*} value The value to check.
4304
+ * @returns {boolean} Returns `true` if `value` is a valid length, else `false`.
4305
+ */
4306
+ function isLength(value) {
4307
+ return typeof value == 'number' && value > -1 && value % 1 == 0 && value <= MAX_SAFE_INTEGER;
4308
+ }
4309
+
4310
+ /**
4311
+ * Checks if `value` is suitable for strict equality comparisons, i.e. `===`.
4312
+ *
4313
+ * @private
4314
+ * @param {*} value The value to check.
4315
+ * @returns {boolean} Returns `true` if `value` if suitable for strict
4316
+ * equality comparisons, else `false`.
4317
+ */
4318
+ function isStrictComparable(value) {
4319
+ return value === value && !isObject(value);
4320
+ }
4321
+
4322
+ /**
4323
+ * Merges the function metadata of `source` into `data`.
4324
+ *
4325
+ * Merging metadata reduces the number of wrappers required to invoke a function.
4326
+ * This is possible because methods like `_.bind`, `_.curry`, and `_.partial`
4327
+ * may be applied regardless of execution order. Methods like `_.ary` and `_.rearg`
4328
+ * augment function arguments, making the order in which they are executed important,
4329
+ * preventing the merging of metadata. However, we make an exception for a safe
4330
+ * common case where curried functions have `_.ary` and or `_.rearg` applied.
4331
+ *
4332
+ * @private
4333
+ * @param {Array} data The destination metadata.
4334
+ * @param {Array} source The source metadata.
4335
+ * @returns {Array} Returns `data`.
4336
+ */
4337
+ function mergeData(data, source) {
4338
+ var bitmask = data[1],
4339
+ srcBitmask = source[1],
4340
+ newBitmask = bitmask | srcBitmask,
4341
+ isCommon = newBitmask < ARY_FLAG;
4342
+
4343
+ var isCombo =
4344
+ (srcBitmask == ARY_FLAG && bitmask == CURRY_FLAG) ||
4345
+ (srcBitmask == ARY_FLAG && bitmask == REARG_FLAG && data[7].length <= source[8]) ||
4346
+ (srcBitmask == (ARY_FLAG | REARG_FLAG) && bitmask == CURRY_FLAG);
4347
+
4348
+ // Exit early if metadata can't be merged.
4349
+ if (!(isCommon || isCombo)) {
4350
+ return data;
4351
+ }
4352
+ // Use source `thisArg` if available.
4353
+ if (srcBitmask & BIND_FLAG) {
4354
+ data[2] = source[2];
4355
+ // Set when currying a bound function.
4356
+ newBitmask |= (bitmask & BIND_FLAG) ? 0 : CURRY_BOUND_FLAG;
4357
+ }
4358
+ // Compose partial arguments.
4359
+ var value = source[3];
4360
+ if (value) {
4361
+ var partials = data[3];
4362
+ data[3] = partials ? composeArgs(partials, value, source[4]) : arrayCopy(value);
4363
+ data[4] = partials ? replaceHolders(data[3], PLACEHOLDER) : arrayCopy(source[4]);
4364
+ }
4365
+ // Compose partial right arguments.
4366
+ value = source[5];
4367
+ if (value) {
4368
+ partials = data[5];
4369
+ data[5] = partials ? composeArgsRight(partials, value, source[6]) : arrayCopy(value);
4370
+ data[6] = partials ? replaceHolders(data[5], PLACEHOLDER) : arrayCopy(source[6]);
4371
+ }
4372
+ // Use source `argPos` if available.
4373
+ value = source[7];
4374
+ if (value) {
4375
+ data[7] = arrayCopy(value);
4376
+ }
4377
+ // Use source `ary` if it's smaller.
4378
+ if (srcBitmask & ARY_FLAG) {
4379
+ data[8] = data[8] == null ? source[8] : nativeMin(data[8], source[8]);
4380
+ }
4381
+ // Use source `arity` if one is not provided.
4382
+ if (data[9] == null) {
4383
+ data[9] = source[9];
4384
+ }
4385
+ // Use source `func` and merge bitmasks.
4386
+ data[0] = source[0];
4387
+ data[1] = newBitmask;
4388
+
4389
+ return data;
4390
+ }
4391
+
4392
+ /**
4393
+ * Used by `_.defaultsDeep` to customize its `_.merge` use.
4394
+ *
4395
+ * @private
4396
+ * @param {*} objectValue The destination object property value.
4397
+ * @param {*} sourceValue The source object property value.
4398
+ * @returns {*} Returns the value to assign to the destination object.
4399
+ */
4400
+ function mergeDefaults(objectValue, sourceValue) {
4401
+ return objectValue === undefined ? sourceValue : merge(objectValue, sourceValue, mergeDefaults);
4402
+ }
4403
+
4404
+ /**
4405
+ * A specialized version of `_.pick` which picks `object` properties specified
4406
+ * by `props`.
4407
+ *
4408
+ * @private
4409
+ * @param {Object} object The source object.
4410
+ * @param {string[]} props The property names to pick.
4411
+ * @returns {Object} Returns the new object.
4412
+ */
4413
+ function pickByArray(object, props) {
4414
+ object = toObject(object);
4415
+
4416
+ var index = -1,
4417
+ length = props.length,
4418
+ result = {};
4419
+
4420
+ while (++index < length) {
4421
+ var key = props[index];
4422
+ if (key in object) {
4423
+ result[key] = object[key];
4424
+ }
4425
+ }
4426
+ return result;
4427
+ }
4428
+
4429
+ /**
4430
+ * A specialized version of `_.pick` which picks `object` properties `predicate`
4431
+ * returns truthy for.
4432
+ *
4433
+ * @private
4434
+ * @param {Object} object The source object.
4435
+ * @param {Function} predicate The function invoked per iteration.
4436
+ * @returns {Object} Returns the new object.
4437
+ */
4438
+ function pickByCallback(object, predicate) {
4439
+ var result = {};
4440
+ baseForIn(object, function(value, key, object) {
4441
+ if (predicate(value, key, object)) {
4442
+ result[key] = value;
4443
+ }
4444
+ });
4445
+ return result;
4446
+ }
4447
+
4448
+ /**
4449
+ * Reorder `array` according to the specified indexes where the element at
4450
+ * the first index is assigned as the first element, the element at
4451
+ * the second index is assigned as the second element, and so on.
4452
+ *
4453
+ * @private
4454
+ * @param {Array} array The array to reorder.
4455
+ * @param {Array} indexes The arranged array indexes.
4456
+ * @returns {Array} Returns `array`.
4457
+ */
4458
+ function reorder(array, indexes) {
4459
+ var arrLength = array.length,
4460
+ length = nativeMin(indexes.length, arrLength),
4461
+ oldArray = arrayCopy(array);
4462
+
4463
+ while (length--) {
4464
+ var index = indexes[length];
4465
+ array[length] = isIndex(index, arrLength) ? oldArray[index] : undefined;
4466
+ }
4467
+ return array;
4468
+ }
4469
+
4470
+ /**
4471
+ * Sets metadata for `func`.
4472
+ *
4473
+ * **Note:** If this function becomes hot, i.e. is invoked a lot in a short
4474
+ * period of time, it will trip its breaker and transition to an identity function
4475
+ * to avoid garbage collection pauses in V8. See [V8 issue 2070](https://code.google.com/p/v8/issues/detail?id=2070)
4476
+ * for more details.
4477
+ *
4478
+ * @private
4479
+ * @param {Function} func The function to associate metadata with.
4480
+ * @param {*} data The metadata.
4481
+ * @returns {Function} Returns `func`.
4482
+ */
4483
+ var setData = (function() {
4484
+ var count = 0,
4485
+ lastCalled = 0;
4486
+
4487
+ return function(key, value) {
4488
+ var stamp = now(),
4489
+ remaining = HOT_SPAN - (stamp - lastCalled);
4490
+
4491
+ lastCalled = stamp;
4492
+ if (remaining > 0) {
4493
+ if (++count >= HOT_COUNT) {
4494
+ return key;
4495
+ }
4496
+ } else {
4497
+ count = 0;
4498
+ }
4499
+ return baseSetData(key, value);
4500
+ };
4501
+ }());
4502
+
4503
+ /**
4504
+ * A fallback implementation of `Object.keys` which creates an array of the
4505
+ * own enumerable property names of `object`.
4506
+ *
4507
+ * @private
4508
+ * @param {Object} object The object to query.
4509
+ * @returns {Array} Returns the array of property names.
4510
+ */
4511
+ function shimKeys(object) {
4512
+ var props = keysIn(object),
4513
+ propsLength = props.length,
4514
+ length = propsLength && object.length;
4515
+
4516
+ var allowIndexes = !!length && isLength(length) &&
4517
+ (isArray(object) || isArguments(object) || isString(object));
4518
+
4519
+ var index = -1,
4520
+ result = [];
4521
+
4522
+ while (++index < propsLength) {
4523
+ var key = props[index];
4524
+ if ((allowIndexes && isIndex(key, length)) || hasOwnProperty.call(object, key)) {
4525
+ result.push(key);
4526
+ }
4527
+ }
4528
+ return result;
4529
+ }
4530
+
4531
+ /**
4532
+ * Converts `value` to an array-like object if it's not one.
4533
+ *
4534
+ * @private
4535
+ * @param {*} value The value to process.
4536
+ * @returns {Array|Object} Returns the array-like object.
4537
+ */
4538
+ function toIterable(value) {
4539
+ if (value == null) {
4540
+ return [];
4541
+ }
4542
+ if (!isArrayLike(value)) {
4543
+ return values(value);
4544
+ }
4545
+ if (lodash.support.unindexedChars && isString(value)) {
4546
+ return value.split('');
4547
+ }
4548
+ return isObject(value) ? value : Object(value);
4549
+ }
4550
+
4551
+ /**
4552
+ * Converts `value` to an object if it's not one.
4553
+ *
4554
+ * @private
4555
+ * @param {*} value The value to process.
4556
+ * @returns {Object} Returns the object.
4557
+ */
4558
+ function toObject(value) {
4559
+ if (lodash.support.unindexedChars && isString(value)) {
4560
+ var index = -1,
4561
+ length = value.length,
4562
+ result = Object(value);
4563
+
4564
+ while (++index < length) {
4565
+ result[index] = value.charAt(index);
4566
+ }
4567
+ return result;
4568
+ }
4569
+ return isObject(value) ? value : Object(value);
4570
+ }
4571
+
4572
+ /**
4573
+ * Converts `value` to property path array if it's not one.
4574
+ *
4575
+ * @private
4576
+ * @param {*} value The value to process.
4577
+ * @returns {Array} Returns the property path array.
4578
+ */
4579
+ function toPath(value) {
4580
+ if (isArray(value)) {
4581
+ return value;
4582
+ }
4583
+ var result = [];
4584
+ baseToString(value).replace(rePropName, function(match, number, quote, string) {
4585
+ result.push(quote ? string.replace(reEscapeChar, '$1') : (number || match));
4586
+ });
4587
+ return result;
4588
+ }
4589
+
4590
+ /**
4591
+ * Creates a clone of `wrapper`.
4592
+ *
4593
+ * @private
4594
+ * @param {Object} wrapper The wrapper to clone.
4595
+ * @returns {Object} Returns the cloned wrapper.
4596
+ */
4597
+ function wrapperClone(wrapper) {
4598
+ return wrapper instanceof LazyWrapper
4599
+ ? wrapper.clone()
4600
+ : new LodashWrapper(wrapper.__wrapped__, wrapper.__chain__, arrayCopy(wrapper.__actions__));
4601
+ }
4602
+
4603
+ /*------------------------------------------------------------------------*/
4604
+
4605
+ /**
4606
+ * Creates an array of elements split into groups the length of `size`.
4607
+ * If `collection` can't be split evenly, the final chunk will be the remaining
4608
+ * elements.
4609
+ *
4610
+ * @static
4611
+ * @memberOf _
4612
+ * @category Array
4613
+ * @param {Array} array The array to process.
4614
+ * @param {number} [size=1] The length of each chunk.
4615
+ * @param- {Object} [guard] Enables use as a callback for functions like `_.map`.
4616
+ * @returns {Array} Returns the new array containing chunks.
4617
+ * @example
4618
+ *
4619
+ * _.chunk(['a', 'b', 'c', 'd'], 2);
4620
+ * // => [['a', 'b'], ['c', 'd']]
4621
+ *
4622
+ * _.chunk(['a', 'b', 'c', 'd'], 3);
4623
+ * // => [['a', 'b', 'c'], ['d']]
4624
+ */
4625
+ function chunk(array, size, guard) {
4626
+ if (guard ? isIterateeCall(array, size, guard) : size == null) {
4627
+ size = 1;
4628
+ } else {
4629
+ size = nativeMax(nativeFloor(size) || 1, 1);
4630
+ }
4631
+ var index = 0,
4632
+ length = array ? array.length : 0,
4633
+ resIndex = -1,
4634
+ result = Array(nativeCeil(length / size));
4635
+
4636
+ while (index < length) {
4637
+ result[++resIndex] = baseSlice(array, index, (index += size));
4638
+ }
4639
+ return result;
4640
+ }
4641
+
4642
+ /**
4643
+ * Creates an array with all falsey values removed. The values `false`, `null`,
4644
+ * `0`, `""`, `undefined`, and `NaN` are falsey.
4645
+ *
4646
+ * @static
4647
+ * @memberOf _
4648
+ * @category Array
4649
+ * @param {Array} array The array to compact.
4650
+ * @returns {Array} Returns the new array of filtered values.
4651
+ * @example
4652
+ *
4653
+ * _.compact([0, 1, false, 2, '', 3]);
4654
+ * // => [1, 2, 3]
4655
+ */
4656
+ function compact(array) {
4657
+ var index = -1,
4658
+ length = array ? array.length : 0,
4659
+ resIndex = -1,
4660
+ result = [];
4661
+
4662
+ while (++index < length) {
4663
+ var value = array[index];
4664
+ if (value) {
4665
+ result[++resIndex] = value;
4666
+ }
4667
+ }
4668
+ return result;
4669
+ }
4670
+
4671
+ /**
4672
+ * Creates an array of unique `array` values not included in the other
4673
+ * provided arrays using [`SameValueZero`](http://ecma-international.org/ecma-262/6.0/#sec-samevaluezero)
4674
+ * for equality comparisons.
4675
+ *
4676
+ * @static
4677
+ * @memberOf _
4678
+ * @category Array
4679
+ * @param {Array} array The array to inspect.
4680
+ * @param {...Array} [values] The arrays of values to exclude.
4681
+ * @returns {Array} Returns the new array of filtered values.
4682
+ * @example
4683
+ *
4684
+ * _.difference([1, 2, 3], [4, 2]);
4685
+ * // => [1, 3]
4686
+ */
4687
+ var difference = restParam(function(array, values) {
4688
+ return (isObjectLike(array) && isArrayLike(array))
4689
+ ? baseDifference(array, baseFlatten(values, false, true))
4690
+ : [];
4691
+ });
4692
+
4693
+ /**
4694
+ * Creates a slice of `array` with `n` elements dropped from the beginning.
4695
+ *
4696
+ * @static
4697
+ * @memberOf _
4698
+ * @category Array
4699
+ * @param {Array} array The array to query.
4700
+ * @param {number} [n=1] The number of elements to drop.
4701
+ * @param- {Object} [guard] Enables use as a callback for functions like `_.map`.
4702
+ * @returns {Array} Returns the slice of `array`.
4703
+ * @example
4704
+ *
4705
+ * _.drop([1, 2, 3]);
4706
+ * // => [2, 3]
4707
+ *
4708
+ * _.drop([1, 2, 3], 2);
4709
+ * // => [3]
4710
+ *
4711
+ * _.drop([1, 2, 3], 5);
4712
+ * // => []
4713
+ *
4714
+ * _.drop([1, 2, 3], 0);
4715
+ * // => [1, 2, 3]
4716
+ */
4717
+ function drop(array, n, guard) {
4718
+ var length = array ? array.length : 0;
4719
+ if (!length) {
4720
+ return [];
4721
+ }
4722
+ if (guard ? isIterateeCall(array, n, guard) : n == null) {
4723
+ n = 1;
4724
+ }
4725
+ return baseSlice(array, n < 0 ? 0 : n);
4726
+ }
4727
+
4728
+ /**
4729
+ * Creates a slice of `array` with `n` elements dropped from the end.
4730
+ *
4731
+ * @static
4732
+ * @memberOf _
4733
+ * @category Array
4734
+ * @param {Array} array The array to query.
4735
+ * @param {number} [n=1] The number of elements to drop.
4736
+ * @param- {Object} [guard] Enables use as a callback for functions like `_.map`.
4737
+ * @returns {Array} Returns the slice of `array`.
4738
+ * @example
4739
+ *
4740
+ * _.dropRight([1, 2, 3]);
4741
+ * // => [1, 2]
4742
+ *
4743
+ * _.dropRight([1, 2, 3], 2);
4744
+ * // => [1]
4745
+ *
4746
+ * _.dropRight([1, 2, 3], 5);
4747
+ * // => []
4748
+ *
4749
+ * _.dropRight([1, 2, 3], 0);
4750
+ * // => [1, 2, 3]
4751
+ */
4752
+ function dropRight(array, n, guard) {
4753
+ var length = array ? array.length : 0;
4754
+ if (!length) {
4755
+ return [];
4756
+ }
4757
+ if (guard ? isIterateeCall(array, n, guard) : n == null) {
4758
+ n = 1;
4759
+ }
4760
+ n = length - (+n || 0);
4761
+ return baseSlice(array, 0, n < 0 ? 0 : n);
4762
+ }
4763
+
4764
+ /**
4765
+ * Creates a slice of `array` excluding elements dropped from the end.
4766
+ * Elements are dropped until `predicate` returns falsey. The predicate is
4767
+ * bound to `thisArg` and invoked with three arguments: (value, index, array).
4768
+ *
4769
+ * If a property name is provided for `predicate` the created `_.property`
4770
+ * style callback returns the property value of the given element.
4771
+ *
4772
+ * If a value is also provided for `thisArg` the created `_.matchesProperty`
4773
+ * style callback returns `true` for elements that have a matching property
4774
+ * value, else `false`.
4775
+ *
4776
+ * If an object is provided for `predicate` the created `_.matches` style
4777
+ * callback returns `true` for elements that match the properties of the given
4778
+ * object, else `false`.
4779
+ *
4780
+ * @static
4781
+ * @memberOf _
4782
+ * @category Array
4783
+ * @param {Array} array The array to query.
4784
+ * @param {Function|Object|string} [predicate=_.identity] The function invoked
4785
+ * per iteration.
4786
+ * @param {*} [thisArg] The `this` binding of `predicate`.
4787
+ * @returns {Array} Returns the slice of `array`.
4788
+ * @example
4789
+ *
4790
+ * _.dropRightWhile([1, 2, 3], function(n) {
4791
+ * return n > 1;
4792
+ * });
4793
+ * // => [1]
4794
+ *
4795
+ * var users = [
4796
+ * { 'user': 'barney', 'active': true },
4797
+ * { 'user': 'fred', 'active': false },
4798
+ * { 'user': 'pebbles', 'active': false }
4799
+ * ];
4800
+ *
4801
+ * // using the `_.matches` callback shorthand
4802
+ * _.pluck(_.dropRightWhile(users, { 'user': 'pebbles', 'active': false }), 'user');
4803
+ * // => ['barney', 'fred']
4804
+ *
4805
+ * // using the `_.matchesProperty` callback shorthand
4806
+ * _.pluck(_.dropRightWhile(users, 'active', false), 'user');
4807
+ * // => ['barney']
4808
+ *
4809
+ * // using the `_.property` callback shorthand
4810
+ * _.pluck(_.dropRightWhile(users, 'active'), 'user');
4811
+ * // => ['barney', 'fred', 'pebbles']
4812
+ */
4813
+ function dropRightWhile(array, predicate, thisArg) {
4814
+ return (array && array.length)
4815
+ ? baseWhile(array, getCallback(predicate, thisArg, 3), true, true)
4816
+ : [];
4817
+ }
4818
+
4819
+ /**
4820
+ * Creates a slice of `array` excluding elements dropped from the beginning.
4821
+ * Elements are dropped until `predicate` returns falsey. The predicate is
4822
+ * bound to `thisArg` and invoked with three arguments: (value, index, array).
4823
+ *
4824
+ * If a property name is provided for `predicate` the created `_.property`
4825
+ * style callback returns the property value of the given element.
4826
+ *
4827
+ * If a value is also provided for `thisArg` the created `_.matchesProperty`
4828
+ * style callback returns `true` for elements that have a matching property
4829
+ * value, else `false`.
4830
+ *
4831
+ * If an object is provided for `predicate` the created `_.matches` style
4832
+ * callback returns `true` for elements that have the properties of the given
4833
+ * object, else `false`.
4834
+ *
4835
+ * @static
4836
+ * @memberOf _
4837
+ * @category Array
4838
+ * @param {Array} array The array to query.
4839
+ * @param {Function|Object|string} [predicate=_.identity] The function invoked
4840
+ * per iteration.
4841
+ * @param {*} [thisArg] The `this` binding of `predicate`.
4842
+ * @returns {Array} Returns the slice of `array`.
4843
+ * @example
4844
+ *
4845
+ * _.dropWhile([1, 2, 3], function(n) {
4846
+ * return n < 3;
4847
+ * });
4848
+ * // => [3]
4849
+ *
4850
+ * var users = [
4851
+ * { 'user': 'barney', 'active': false },
4852
+ * { 'user': 'fred', 'active': false },
4853
+ * { 'user': 'pebbles', 'active': true }
4854
+ * ];
4855
+ *
4856
+ * // using the `_.matches` callback shorthand
4857
+ * _.pluck(_.dropWhile(users, { 'user': 'barney', 'active': false }), 'user');
4858
+ * // => ['fred', 'pebbles']
4859
+ *
4860
+ * // using the `_.matchesProperty` callback shorthand
4861
+ * _.pluck(_.dropWhile(users, 'active', false), 'user');
4862
+ * // => ['pebbles']
4863
+ *
4864
+ * // using the `_.property` callback shorthand
4865
+ * _.pluck(_.dropWhile(users, 'active'), 'user');
4866
+ * // => ['barney', 'fred', 'pebbles']
4867
+ */
4868
+ function dropWhile(array, predicate, thisArg) {
4869
+ return (array && array.length)
4870
+ ? baseWhile(array, getCallback(predicate, thisArg, 3), true)
4871
+ : [];
4872
+ }
4873
+
4874
+ /**
4875
+ * Fills elements of `array` with `value` from `start` up to, but not
4876
+ * including, `end`.
4877
+ *
4878
+ * **Note:** This method mutates `array`.
4879
+ *
4880
+ * @static
4881
+ * @memberOf _
4882
+ * @category Array
4883
+ * @param {Array} array The array to fill.
4884
+ * @param {*} value The value to fill `array` with.
4885
+ * @param {number} [start=0] The start position.
4886
+ * @param {number} [end=array.length] The end position.
4887
+ * @returns {Array} Returns `array`.
4888
+ * @example
4889
+ *
4890
+ * var array = [1, 2, 3];
4891
+ *
4892
+ * _.fill(array, 'a');
4893
+ * console.log(array);
4894
+ * // => ['a', 'a', 'a']
4895
+ *
4896
+ * _.fill(Array(3), 2);
4897
+ * // => [2, 2, 2]
4898
+ *
4899
+ * _.fill([4, 6, 8], '*', 1, 2);
4900
+ * // => [4, '*', 8]
4901
+ */
4902
+ function fill(array, value, start, end) {
4903
+ var length = array ? array.length : 0;
4904
+ if (!length) {
4905
+ return [];
4906
+ }
4907
+ if (start && typeof start != 'number' && isIterateeCall(array, value, start)) {
4908
+ start = 0;
4909
+ end = length;
4910
+ }
4911
+ return baseFill(array, value, start, end);
4912
+ }
4913
+
4914
+ /**
4915
+ * This method is like `_.find` except that it returns the index of the first
4916
+ * element `predicate` returns truthy for instead of the element itself.
4917
+ *
4918
+ * If a property name is provided for `predicate` the created `_.property`
4919
+ * style callback returns the property value of the given element.
4920
+ *
4921
+ * If a value is also provided for `thisArg` the created `_.matchesProperty`
4922
+ * style callback returns `true` for elements that have a matching property
4923
+ * value, else `false`.
4924
+ *
4925
+ * If an object is provided for `predicate` the created `_.matches` style
4926
+ * callback returns `true` for elements that have the properties of the given
4927
+ * object, else `false`.
4928
+ *
4929
+ * @static
4930
+ * @memberOf _
4931
+ * @category Array
4932
+ * @param {Array} array The array to search.
4933
+ * @param {Function|Object|string} [predicate=_.identity] The function invoked
4934
+ * per iteration.
4935
+ * @param {*} [thisArg] The `this` binding of `predicate`.
4936
+ * @returns {number} Returns the index of the found element, else `-1`.
4937
+ * @example
4938
+ *
4939
+ * var users = [
4940
+ * { 'user': 'barney', 'active': false },
4941
+ * { 'user': 'fred', 'active': false },
4942
+ * { 'user': 'pebbles', 'active': true }
4943
+ * ];
4944
+ *
4945
+ * _.findIndex(users, function(chr) {
4946
+ * return chr.user == 'barney';
4947
+ * });
4948
+ * // => 0
4949
+ *
4950
+ * // using the `_.matches` callback shorthand
4951
+ * _.findIndex(users, { 'user': 'fred', 'active': false });
4952
+ * // => 1
4953
+ *
4954
+ * // using the `_.matchesProperty` callback shorthand
4955
+ * _.findIndex(users, 'active', false);
4956
+ * // => 0
4957
+ *
4958
+ * // using the `_.property` callback shorthand
4959
+ * _.findIndex(users, 'active');
4960
+ * // => 2
4961
+ */
4962
+ var findIndex = createFindIndex();
4963
+
4964
+ /**
4965
+ * This method is like `_.findIndex` except that it iterates over elements
4966
+ * of `collection` from right to left.
4967
+ *
4968
+ * If a property name is provided for `predicate` the created `_.property`
4969
+ * style callback returns the property value of the given element.
4970
+ *
4971
+ * If a value is also provided for `thisArg` the created `_.matchesProperty`
4972
+ * style callback returns `true` for elements that have a matching property
4973
+ * value, else `false`.
4974
+ *
4975
+ * If an object is provided for `predicate` the created `_.matches` style
4976
+ * callback returns `true` for elements that have the properties of the given
4977
+ * object, else `false`.
4978
+ *
4979
+ * @static
4980
+ * @memberOf _
4981
+ * @category Array
4982
+ * @param {Array} array The array to search.
4983
+ * @param {Function|Object|string} [predicate=_.identity] The function invoked
4984
+ * per iteration.
4985
+ * @param {*} [thisArg] The `this` binding of `predicate`.
4986
+ * @returns {number} Returns the index of the found element, else `-1`.
4987
+ * @example
4988
+ *
4989
+ * var users = [
4990
+ * { 'user': 'barney', 'active': true },
4991
+ * { 'user': 'fred', 'active': false },
4992
+ * { 'user': 'pebbles', 'active': false }
4993
+ * ];
4994
+ *
4995
+ * _.findLastIndex(users, function(chr) {
4996
+ * return chr.user == 'pebbles';
4997
+ * });
4998
+ * // => 2
4999
+ *
5000
+ * // using the `_.matches` callback shorthand
5001
+ * _.findLastIndex(users, { 'user': 'barney', 'active': true });
5002
+ * // => 0
5003
+ *
5004
+ * // using the `_.matchesProperty` callback shorthand
5005
+ * _.findLastIndex(users, 'active', false);
5006
+ * // => 2
5007
+ *
5008
+ * // using the `_.property` callback shorthand
5009
+ * _.findLastIndex(users, 'active');
5010
+ * // => 0
5011
+ */
5012
+ var findLastIndex = createFindIndex(true);
5013
+
5014
+ /**
5015
+ * Gets the first element of `array`.
5016
+ *
5017
+ * @static
5018
+ * @memberOf _
5019
+ * @alias head
5020
+ * @category Array
5021
+ * @param {Array} array The array to query.
5022
+ * @returns {*} Returns the first element of `array`.
5023
+ * @example
5024
+ *
5025
+ * _.first([1, 2, 3]);
5026
+ * // => 1
5027
+ *
5028
+ * _.first([]);
5029
+ * // => undefined
5030
+ */
5031
+ function first(array) {
5032
+ return array ? array[0] : undefined;
5033
+ }
5034
+
5035
+ /**
5036
+ * Flattens a nested array. If `isDeep` is `true` the array is recursively
5037
+ * flattened, otherwise it's only flattened a single level.
5038
+ *
5039
+ * @static
5040
+ * @memberOf _
5041
+ * @category Array
5042
+ * @param {Array} array The array to flatten.
5043
+ * @param {boolean} [isDeep] Specify a deep flatten.
5044
+ * @param- {Object} [guard] Enables use as a callback for functions like `_.map`.
5045
+ * @returns {Array} Returns the new flattened array.
5046
+ * @example
5047
+ *
5048
+ * _.flatten([1, [2, 3, [4]]]);
5049
+ * // => [1, 2, 3, [4]]
5050
+ *
5051
+ * // using `isDeep`
5052
+ * _.flatten([1, [2, 3, [4]]], true);
5053
+ * // => [1, 2, 3, 4]
5054
+ */
5055
+ function flatten(array, isDeep, guard) {
5056
+ var length = array ? array.length : 0;
5057
+ if (guard && isIterateeCall(array, isDeep, guard)) {
5058
+ isDeep = false;
5059
+ }
5060
+ return length ? baseFlatten(array, isDeep) : [];
5061
+ }
5062
+
5063
+ /**
5064
+ * Recursively flattens a nested array.
5065
+ *
5066
+ * @static
5067
+ * @memberOf _
5068
+ * @category Array
5069
+ * @param {Array} array The array to recursively flatten.
5070
+ * @returns {Array} Returns the new flattened array.
5071
+ * @example
5072
+ *
5073
+ * _.flattenDeep([1, [2, 3, [4]]]);
5074
+ * // => [1, 2, 3, 4]
5075
+ */
5076
+ function flattenDeep(array) {
5077
+ var length = array ? array.length : 0;
5078
+ return length ? baseFlatten(array, true) : [];
5079
+ }
5080
+
5081
+ /**
5082
+ * Gets the index at which the first occurrence of `value` is found in `array`
5083
+ * using [`SameValueZero`](http://ecma-international.org/ecma-262/6.0/#sec-samevaluezero)
5084
+ * for equality comparisons. If `fromIndex` is negative, it's used as the offset
5085
+ * from the end of `array`. If `array` is sorted providing `true` for `fromIndex`
5086
+ * performs a faster binary search.
5087
+ *
5088
+ * @static
5089
+ * @memberOf _
5090
+ * @category Array
5091
+ * @param {Array} array The array to search.
5092
+ * @param {*} value The value to search for.
5093
+ * @param {boolean|number} [fromIndex=0] The index to search from or `true`
5094
+ * to perform a binary search on a sorted array.
5095
+ * @returns {number} Returns the index of the matched value, else `-1`.
5096
+ * @example
5097
+ *
5098
+ * _.indexOf([1, 2, 1, 2], 2);
5099
+ * // => 1
5100
+ *
5101
+ * // using `fromIndex`
5102
+ * _.indexOf([1, 2, 1, 2], 2, 2);
5103
+ * // => 3
5104
+ *
5105
+ * // performing a binary search
5106
+ * _.indexOf([1, 1, 2, 2], 2, true);
5107
+ * // => 2
5108
+ */
5109
+ function indexOf(array, value, fromIndex) {
5110
+ var length = array ? array.length : 0;
5111
+ if (!length) {
5112
+ return -1;
5113
+ }
5114
+ if (typeof fromIndex == 'number') {
5115
+ fromIndex = fromIndex < 0 ? nativeMax(length + fromIndex, 0) : fromIndex;
5116
+ } else if (fromIndex) {
5117
+ var index = binaryIndex(array, value);
5118
+ if (index < length &&
5119
+ (value === value ? (value === array[index]) : (array[index] !== array[index]))) {
5120
+ return index;
5121
+ }
5122
+ return -1;
5123
+ }
5124
+ return baseIndexOf(array, value, fromIndex || 0);
5125
+ }
5126
+
5127
+ /**
5128
+ * Gets all but the last element of `array`.
5129
+ *
5130
+ * @static
5131
+ * @memberOf _
5132
+ * @category Array
5133
+ * @param {Array} array The array to query.
5134
+ * @returns {Array} Returns the slice of `array`.
5135
+ * @example
5136
+ *
5137
+ * _.initial([1, 2, 3]);
5138
+ * // => [1, 2]
5139
+ */
5140
+ function initial(array) {
5141
+ return dropRight(array, 1);
5142
+ }
5143
+
5144
+ /**
5145
+ * Creates an array of unique values that are included in all of the provided
5146
+ * arrays using [`SameValueZero`](http://ecma-international.org/ecma-262/6.0/#sec-samevaluezero)
5147
+ * for equality comparisons.
5148
+ *
5149
+ * @static
5150
+ * @memberOf _
5151
+ * @category Array
5152
+ * @param {...Array} [arrays] The arrays to inspect.
5153
+ * @returns {Array} Returns the new array of shared values.
5154
+ * @example
5155
+ * _.intersection([1, 2], [4, 2], [2, 1]);
5156
+ * // => [2]
5157
+ */
5158
+ var intersection = restParam(function(arrays) {
5159
+ var othLength = arrays.length,
5160
+ othIndex = othLength,
5161
+ caches = Array(length),
5162
+ indexOf = getIndexOf(),
5163
+ isCommon = indexOf === baseIndexOf,
5164
+ result = [];
5165
+
5166
+ while (othIndex--) {
5167
+ var value = arrays[othIndex] = isArrayLike(value = arrays[othIndex]) ? value : [];
5168
+ caches[othIndex] = (isCommon && value.length >= 120) ? createCache(othIndex && value) : null;
5169
+ }
5170
+ var array = arrays[0],
5171
+ index = -1,
5172
+ length = array ? array.length : 0,
5173
+ seen = caches[0];
5174
+
5175
+ outer:
5176
+ while (++index < length) {
5177
+ value = array[index];
5178
+ if ((seen ? cacheIndexOf(seen, value) : indexOf(result, value, 0)) < 0) {
5179
+ var othIndex = othLength;
5180
+ while (--othIndex) {
5181
+ var cache = caches[othIndex];
5182
+ if ((cache ? cacheIndexOf(cache, value) : indexOf(arrays[othIndex], value, 0)) < 0) {
5183
+ continue outer;
5184
+ }
5185
+ }
5186
+ if (seen) {
5187
+ seen.push(value);
5188
+ }
5189
+ result.push(value);
5190
+ }
5191
+ }
5192
+ return result;
5193
+ });
5194
+
5195
+ /**
5196
+ * Gets the last element of `array`.
5197
+ *
5198
+ * @static
5199
+ * @memberOf _
5200
+ * @category Array
5201
+ * @param {Array} array The array to query.
5202
+ * @returns {*} Returns the last element of `array`.
5203
+ * @example
5204
+ *
5205
+ * _.last([1, 2, 3]);
5206
+ * // => 3
5207
+ */
5208
+ function last(array) {
5209
+ var length = array ? array.length : 0;
5210
+ return length ? array[length - 1] : undefined;
5211
+ }
5212
+
5213
+ /**
5214
+ * This method is like `_.indexOf` except that it iterates over elements of
5215
+ * `array` from right to left.
5216
+ *
5217
+ * @static
5218
+ * @memberOf _
5219
+ * @category Array
5220
+ * @param {Array} array The array to search.
5221
+ * @param {*} value The value to search for.
5222
+ * @param {boolean|number} [fromIndex=array.length-1] The index to search from
5223
+ * or `true` to perform a binary search on a sorted array.
5224
+ * @returns {number} Returns the index of the matched value, else `-1`.
5225
+ * @example
5226
+ *
5227
+ * _.lastIndexOf([1, 2, 1, 2], 2);
5228
+ * // => 3
5229
+ *
5230
+ * // using `fromIndex`
5231
+ * _.lastIndexOf([1, 2, 1, 2], 2, 2);
5232
+ * // => 1
5233
+ *
5234
+ * // performing a binary search
5235
+ * _.lastIndexOf([1, 1, 2, 2], 2, true);
5236
+ * // => 3
5237
+ */
5238
+ function lastIndexOf(array, value, fromIndex) {
5239
+ var length = array ? array.length : 0;
5240
+ if (!length) {
5241
+ return -1;
5242
+ }
5243
+ var index = length;
5244
+ if (typeof fromIndex == 'number') {
5245
+ index = (fromIndex < 0 ? nativeMax(length + fromIndex, 0) : nativeMin(fromIndex || 0, length - 1)) + 1;
5246
+ } else if (fromIndex) {
5247
+ index = binaryIndex(array, value, true) - 1;
5248
+ var other = array[index];
5249
+ if (value === value ? (value === other) : (other !== other)) {
5250
+ return index;
5251
+ }
5252
+ return -1;
5253
+ }
5254
+ if (value !== value) {
5255
+ return indexOfNaN(array, index, true);
5256
+ }
5257
+ while (index--) {
5258
+ if (array[index] === value) {
5259
+ return index;
5260
+ }
5261
+ }
5262
+ return -1;
5263
+ }
5264
+
5265
+ /**
5266
+ * Removes all provided values from `array` using
5267
+ * [`SameValueZero`](http://ecma-international.org/ecma-262/6.0/#sec-samevaluezero)
5268
+ * for equality comparisons.
5269
+ *
5270
+ * **Note:** Unlike `_.without`, this method mutates `array`.
5271
+ *
5272
+ * @static
5273
+ * @memberOf _
5274
+ * @category Array
5275
+ * @param {Array} array The array to modify.
5276
+ * @param {...*} [values] The values to remove.
5277
+ * @returns {Array} Returns `array`.
5278
+ * @example
5279
+ *
5280
+ * var array = [1, 2, 3, 1, 2, 3];
5281
+ *
5282
+ * _.pull(array, 2, 3);
5283
+ * console.log(array);
5284
+ * // => [1, 1]
5285
+ */
5286
+ function pull() {
5287
+ var args = arguments,
5288
+ array = args[0];
5289
+
5290
+ if (!(array && array.length)) {
5291
+ return array;
5292
+ }
5293
+ var index = 0,
5294
+ indexOf = getIndexOf(),
5295
+ length = args.length;
5296
+
5297
+ while (++index < length) {
5298
+ var fromIndex = 0,
5299
+ value = args[index];
5300
+
5301
+ while ((fromIndex = indexOf(array, value, fromIndex)) > -1) {
5302
+ splice.call(array, fromIndex, 1);
5303
+ }
5304
+ }
5305
+ return array;
5306
+ }
5307
+
5308
+ /**
5309
+ * Removes elements from `array` corresponding to the given indexes and returns
5310
+ * an array of the removed elements. Indexes may be specified as an array of
5311
+ * indexes or as individual arguments.
5312
+ *
5313
+ * **Note:** Unlike `_.at`, this method mutates `array`.
5314
+ *
5315
+ * @static
5316
+ * @memberOf _
5317
+ * @category Array
5318
+ * @param {Array} array The array to modify.
5319
+ * @param {...(number|number[])} [indexes] The indexes of elements to remove,
5320
+ * specified as individual indexes or arrays of indexes.
5321
+ * @returns {Array} Returns the new array of removed elements.
5322
+ * @example
5323
+ *
5324
+ * var array = [5, 10, 15, 20];
5325
+ * var evens = _.pullAt(array, 1, 3);
5326
+ *
5327
+ * console.log(array);
5328
+ * // => [5, 15]
5329
+ *
5330
+ * console.log(evens);
5331
+ * // => [10, 20]
5332
+ */
5333
+ var pullAt = restParam(function(array, indexes) {
5334
+ indexes = baseFlatten(indexes);
5335
+
5336
+ var result = baseAt(array, indexes);
5337
+ basePullAt(array, indexes.sort(baseCompareAscending));
5338
+ return result;
5339
+ });
5340
+
5341
+ /**
5342
+ * Removes all elements from `array` that `predicate` returns truthy for
5343
+ * and returns an array of the removed elements. The predicate is bound to
5344
+ * `thisArg` and invoked with three arguments: (value, index, array).
5345
+ *
5346
+ * If a property name is provided for `predicate` the created `_.property`
5347
+ * style callback returns the property value of the given element.
5348
+ *
5349
+ * If a value is also provided for `thisArg` the created `_.matchesProperty`
5350
+ * style callback returns `true` for elements that have a matching property
5351
+ * value, else `false`.
5352
+ *
5353
+ * If an object is provided for `predicate` the created `_.matches` style
5354
+ * callback returns `true` for elements that have the properties of the given
5355
+ * object, else `false`.
5356
+ *
5357
+ * **Note:** Unlike `_.filter`, this method mutates `array`.
5358
+ *
5359
+ * @static
5360
+ * @memberOf _
5361
+ * @category Array
5362
+ * @param {Array} array The array to modify.
5363
+ * @param {Function|Object|string} [predicate=_.identity] The function invoked
5364
+ * per iteration.
5365
+ * @param {*} [thisArg] The `this` binding of `predicate`.
5366
+ * @returns {Array} Returns the new array of removed elements.
5367
+ * @example
5368
+ *
5369
+ * var array = [1, 2, 3, 4];
5370
+ * var evens = _.remove(array, function(n) {
5371
+ * return n % 2 == 0;
5372
+ * });
5373
+ *
5374
+ * console.log(array);
5375
+ * // => [1, 3]
5376
+ *
5377
+ * console.log(evens);
5378
+ * // => [2, 4]
5379
+ */
5380
+ function remove(array, predicate, thisArg) {
5381
+ var result = [];
5382
+ if (!(array && array.length)) {
5383
+ return result;
5384
+ }
5385
+ var index = -1,
5386
+ indexes = [],
5387
+ length = array.length;
5388
+
5389
+ predicate = getCallback(predicate, thisArg, 3);
5390
+ while (++index < length) {
5391
+ var value = array[index];
5392
+ if (predicate(value, index, array)) {
5393
+ result.push(value);
5394
+ indexes.push(index);
5395
+ }
5396
+ }
5397
+ basePullAt(array, indexes);
5398
+ return result;
5399
+ }
5400
+
5401
+ /**
5402
+ * Gets all but the first element of `array`.
5403
+ *
5404
+ * @static
5405
+ * @memberOf _
5406
+ * @alias tail
5407
+ * @category Array
5408
+ * @param {Array} array The array to query.
5409
+ * @returns {Array} Returns the slice of `array`.
5410
+ * @example
5411
+ *
5412
+ * _.rest([1, 2, 3]);
5413
+ * // => [2, 3]
5414
+ */
5415
+ function rest(array) {
5416
+ return drop(array, 1);
5417
+ }
5418
+
5419
+ /**
5420
+ * Creates a slice of `array` from `start` up to, but not including, `end`.
5421
+ *
5422
+ * **Note:** This method is used instead of `Array#slice` to support node
5423
+ * lists in IE < 9 and to ensure dense arrays are returned.
5424
+ *
5425
+ * @static
5426
+ * @memberOf _
5427
+ * @category Array
5428
+ * @param {Array} array The array to slice.
5429
+ * @param {number} [start=0] The start position.
5430
+ * @param {number} [end=array.length] The end position.
5431
+ * @returns {Array} Returns the slice of `array`.
5432
+ */
5433
+ function slice(array, start, end) {
5434
+ var length = array ? array.length : 0;
5435
+ if (!length) {
5436
+ return [];
5437
+ }
5438
+ if (end && typeof end != 'number' && isIterateeCall(array, start, end)) {
5439
+ start = 0;
5440
+ end = length;
5441
+ }
5442
+ return baseSlice(array, start, end);
5443
+ }
5444
+
5445
+ /**
5446
+ * Uses a binary search to determine the lowest index at which `value` should
5447
+ * be inserted into `array` in order to maintain its sort order. If an iteratee
5448
+ * function is provided it's invoked for `value` and each element of `array`
5449
+ * to compute their sort ranking. The iteratee is bound to `thisArg` and
5450
+ * invoked with one argument; (value).
5451
+ *
5452
+ * If a property name is provided for `iteratee` the created `_.property`
5453
+ * style callback returns the property value of the given element.
5454
+ *
5455
+ * If a value is also provided for `thisArg` the created `_.matchesProperty`
5456
+ * style callback returns `true` for elements that have a matching property
5457
+ * value, else `false`.
5458
+ *
5459
+ * If an object is provided for `iteratee` the created `_.matches` style
5460
+ * callback returns `true` for elements that have the properties of the given
5461
+ * object, else `false`.
5462
+ *
5463
+ * @static
5464
+ * @memberOf _
5465
+ * @category Array
5466
+ * @param {Array} array The sorted array to inspect.
5467
+ * @param {*} value The value to evaluate.
5468
+ * @param {Function|Object|string} [iteratee=_.identity] The function invoked
5469
+ * per iteration.
5470
+ * @param {*} [thisArg] The `this` binding of `iteratee`.
5471
+ * @returns {number} Returns the index at which `value` should be inserted
5472
+ * into `array`.
5473
+ * @example
5474
+ *
5475
+ * _.sortedIndex([30, 50], 40);
5476
+ * // => 1
5477
+ *
5478
+ * _.sortedIndex([4, 4, 5, 5], 5);
5479
+ * // => 2
5480
+ *
5481
+ * var dict = { 'data': { 'thirty': 30, 'forty': 40, 'fifty': 50 } };
5482
+ *
5483
+ * // using an iteratee function
5484
+ * _.sortedIndex(['thirty', 'fifty'], 'forty', function(word) {
5485
+ * return this.data[word];
5486
+ * }, dict);
5487
+ * // => 1
5488
+ *
5489
+ * // using the `_.property` callback shorthand
5490
+ * _.sortedIndex([{ 'x': 30 }, { 'x': 50 }], { 'x': 40 }, 'x');
5491
+ * // => 1
5492
+ */
5493
+ var sortedIndex = createSortedIndex();
5494
+
5495
+ /**
5496
+ * This method is like `_.sortedIndex` except that it returns the highest
5497
+ * index at which `value` should be inserted into `array` in order to
5498
+ * maintain its sort order.
5499
+ *
5500
+ * @static
5501
+ * @memberOf _
5502
+ * @category Array
5503
+ * @param {Array} array The sorted array to inspect.
5504
+ * @param {*} value The value to evaluate.
5505
+ * @param {Function|Object|string} [iteratee=_.identity] The function invoked
5506
+ * per iteration.
5507
+ * @param {*} [thisArg] The `this` binding of `iteratee`.
5508
+ * @returns {number} Returns the index at which `value` should be inserted
5509
+ * into `array`.
5510
+ * @example
5511
+ *
5512
+ * _.sortedLastIndex([4, 4, 5, 5], 5);
5513
+ * // => 4
5514
+ */
5515
+ var sortedLastIndex = createSortedIndex(true);
5516
+
5517
+ /**
5518
+ * Creates a slice of `array` with `n` elements taken from the beginning.
5519
+ *
5520
+ * @static
5521
+ * @memberOf _
5522
+ * @category Array
5523
+ * @param {Array} array The array to query.
5524
+ * @param {number} [n=1] The number of elements to take.
5525
+ * @param- {Object} [guard] Enables use as a callback for functions like `_.map`.
5526
+ * @returns {Array} Returns the slice of `array`.
5527
+ * @example
5528
+ *
5529
+ * _.take([1, 2, 3]);
5530
+ * // => [1]
5531
+ *
5532
+ * _.take([1, 2, 3], 2);
5533
+ * // => [1, 2]
5534
+ *
5535
+ * _.take([1, 2, 3], 5);
5536
+ * // => [1, 2, 3]
5537
+ *
5538
+ * _.take([1, 2, 3], 0);
5539
+ * // => []
5540
+ */
5541
+ function take(array, n, guard) {
5542
+ var length = array ? array.length : 0;
5543
+ if (!length) {
5544
+ return [];
5545
+ }
5546
+ if (guard ? isIterateeCall(array, n, guard) : n == null) {
5547
+ n = 1;
5548
+ }
5549
+ return baseSlice(array, 0, n < 0 ? 0 : n);
5550
+ }
5551
+
5552
+ /**
5553
+ * Creates a slice of `array` with `n` elements taken from the end.
5554
+ *
5555
+ * @static
5556
+ * @memberOf _
5557
+ * @category Array
5558
+ * @param {Array} array The array to query.
5559
+ * @param {number} [n=1] The number of elements to take.
5560
+ * @param- {Object} [guard] Enables use as a callback for functions like `_.map`.
5561
+ * @returns {Array} Returns the slice of `array`.
5562
+ * @example
5563
+ *
5564
+ * _.takeRight([1, 2, 3]);
5565
+ * // => [3]
5566
+ *
5567
+ * _.takeRight([1, 2, 3], 2);
5568
+ * // => [2, 3]
5569
+ *
5570
+ * _.takeRight([1, 2, 3], 5);
5571
+ * // => [1, 2, 3]
5572
+ *
5573
+ * _.takeRight([1, 2, 3], 0);
5574
+ * // => []
5575
+ */
5576
+ function takeRight(array, n, guard) {
5577
+ var length = array ? array.length : 0;
5578
+ if (!length) {
5579
+ return [];
5580
+ }
5581
+ if (guard ? isIterateeCall(array, n, guard) : n == null) {
5582
+ n = 1;
5583
+ }
5584
+ n = length - (+n || 0);
5585
+ return baseSlice(array, n < 0 ? 0 : n);
5586
+ }
5587
+
5588
+ /**
5589
+ * Creates a slice of `array` with elements taken from the end. Elements are
5590
+ * taken until `predicate` returns falsey. The predicate is bound to `thisArg`
5591
+ * and invoked with three arguments: (value, index, array).
5592
+ *
5593
+ * If a property name is provided for `predicate` the created `_.property`
5594
+ * style callback returns the property value of the given element.
5595
+ *
5596
+ * If a value is also provided for `thisArg` the created `_.matchesProperty`
5597
+ * style callback returns `true` for elements that have a matching property
5598
+ * value, else `false`.
5599
+ *
5600
+ * If an object is provided for `predicate` the created `_.matches` style
5601
+ * callback returns `true` for elements that have the properties of the given
5602
+ * object, else `false`.
5603
+ *
5604
+ * @static
5605
+ * @memberOf _
5606
+ * @category Array
5607
+ * @param {Array} array The array to query.
5608
+ * @param {Function|Object|string} [predicate=_.identity] The function invoked
5609
+ * per iteration.
5610
+ * @param {*} [thisArg] The `this` binding of `predicate`.
5611
+ * @returns {Array} Returns the slice of `array`.
5612
+ * @example
5613
+ *
5614
+ * _.takeRightWhile([1, 2, 3], function(n) {
5615
+ * return n > 1;
5616
+ * });
5617
+ * // => [2, 3]
5618
+ *
5619
+ * var users = [
5620
+ * { 'user': 'barney', 'active': true },
5621
+ * { 'user': 'fred', 'active': false },
5622
+ * { 'user': 'pebbles', 'active': false }
5623
+ * ];
5624
+ *
5625
+ * // using the `_.matches` callback shorthand
5626
+ * _.pluck(_.takeRightWhile(users, { 'user': 'pebbles', 'active': false }), 'user');
5627
+ * // => ['pebbles']
5628
+ *
5629
+ * // using the `_.matchesProperty` callback shorthand
5630
+ * _.pluck(_.takeRightWhile(users, 'active', false), 'user');
5631
+ * // => ['fred', 'pebbles']
5632
+ *
5633
+ * // using the `_.property` callback shorthand
5634
+ * _.pluck(_.takeRightWhile(users, 'active'), 'user');
5635
+ * // => []
5636
+ */
5637
+ function takeRightWhile(array, predicate, thisArg) {
5638
+ return (array && array.length)
5639
+ ? baseWhile(array, getCallback(predicate, thisArg, 3), false, true)
5640
+ : [];
5641
+ }
5642
+
5643
+ /**
5644
+ * Creates a slice of `array` with elements taken from the beginning. Elements
5645
+ * are taken until `predicate` returns falsey. The predicate is bound to
5646
+ * `thisArg` and invoked with three arguments: (value, index, array).
5647
+ *
5648
+ * If a property name is provided for `predicate` the created `_.property`
5649
+ * style callback returns the property value of the given element.
5650
+ *
5651
+ * If a value is also provided for `thisArg` the created `_.matchesProperty`
5652
+ * style callback returns `true` for elements that have a matching property
5653
+ * value, else `false`.
5654
+ *
5655
+ * If an object is provided for `predicate` the created `_.matches` style
5656
+ * callback returns `true` for elements that have the properties of the given
5657
+ * object, else `false`.
5658
+ *
5659
+ * @static
5660
+ * @memberOf _
5661
+ * @category Array
5662
+ * @param {Array} array The array to query.
5663
+ * @param {Function|Object|string} [predicate=_.identity] The function invoked
5664
+ * per iteration.
5665
+ * @param {*} [thisArg] The `this` binding of `predicate`.
5666
+ * @returns {Array} Returns the slice of `array`.
5667
+ * @example
5668
+ *
5669
+ * _.takeWhile([1, 2, 3], function(n) {
5670
+ * return n < 3;
5671
+ * });
5672
+ * // => [1, 2]
5673
+ *
5674
+ * var users = [
5675
+ * { 'user': 'barney', 'active': false },
5676
+ * { 'user': 'fred', 'active': false},
5677
+ * { 'user': 'pebbles', 'active': true }
5678
+ * ];
5679
+ *
5680
+ * // using the `_.matches` callback shorthand
5681
+ * _.pluck(_.takeWhile(users, { 'user': 'barney', 'active': false }), 'user');
5682
+ * // => ['barney']
5683
+ *
5684
+ * // using the `_.matchesProperty` callback shorthand
5685
+ * _.pluck(_.takeWhile(users, 'active', false), 'user');
5686
+ * // => ['barney', 'fred']
5687
+ *
5688
+ * // using the `_.property` callback shorthand
5689
+ * _.pluck(_.takeWhile(users, 'active'), 'user');
5690
+ * // => []
5691
+ */
5692
+ function takeWhile(array, predicate, thisArg) {
5693
+ return (array && array.length)
5694
+ ? baseWhile(array, getCallback(predicate, thisArg, 3))
5695
+ : [];
5696
+ }
5697
+
5698
+ /**
5699
+ * Creates an array of unique values, in order, from all of the provided arrays
5700
+ * using [`SameValueZero`](http://ecma-international.org/ecma-262/6.0/#sec-samevaluezero)
5701
+ * for equality comparisons.
5702
+ *
5703
+ * @static
5704
+ * @memberOf _
5705
+ * @category Array
5706
+ * @param {...Array} [arrays] The arrays to inspect.
5707
+ * @returns {Array} Returns the new array of combined values.
5708
+ * @example
5709
+ *
5710
+ * _.union([1, 2], [4, 2], [2, 1]);
5711
+ * // => [1, 2, 4]
5712
+ */
5713
+ var union = restParam(function(arrays) {
5714
+ return baseUniq(baseFlatten(arrays, false, true));
5715
+ });
5716
+
5717
+ /**
5718
+ * Creates a duplicate-free version of an array, using
5719
+ * [`SameValueZero`](http://ecma-international.org/ecma-262/6.0/#sec-samevaluezero)
5720
+ * for equality comparisons, in which only the first occurence of each element
5721
+ * is kept. Providing `true` for `isSorted` performs a faster search algorithm
5722
+ * for sorted arrays. If an iteratee function is provided it's invoked for
5723
+ * each element in the array to generate the criterion by which uniqueness
5724
+ * is computed. The `iteratee` is bound to `thisArg` and invoked with three
5725
+ * arguments: (value, index, array).
5726
+ *
5727
+ * If a property name is provided for `iteratee` the created `_.property`
5728
+ * style callback returns the property value of the given element.
5729
+ *
5730
+ * If a value is also provided for `thisArg` the created `_.matchesProperty`
5731
+ * style callback returns `true` for elements that have a matching property
5732
+ * value, else `false`.
5733
+ *
5734
+ * If an object is provided for `iteratee` the created `_.matches` style
5735
+ * callback returns `true` for elements that have the properties of the given
5736
+ * object, else `false`.
5737
+ *
5738
+ * @static
5739
+ * @memberOf _
5740
+ * @alias unique
5741
+ * @category Array
5742
+ * @param {Array} array The array to inspect.
5743
+ * @param {boolean} [isSorted] Specify the array is sorted.
5744
+ * @param {Function|Object|string} [iteratee] The function invoked per iteration.
5745
+ * @param {*} [thisArg] The `this` binding of `iteratee`.
5746
+ * @returns {Array} Returns the new duplicate-value-free array.
5747
+ * @example
5748
+ *
5749
+ * _.uniq([2, 1, 2]);
5750
+ * // => [2, 1]
5751
+ *
5752
+ * // using `isSorted`
5753
+ * _.uniq([1, 1, 2], true);
5754
+ * // => [1, 2]
5755
+ *
5756
+ * // using an iteratee function
5757
+ * _.uniq([1, 2.5, 1.5, 2], function(n) {
5758
+ * return this.floor(n);
5759
+ * }, Math);
5760
+ * // => [1, 2.5]
5761
+ *
5762
+ * // using the `_.property` callback shorthand
5763
+ * _.uniq([{ 'x': 1 }, { 'x': 2 }, { 'x': 1 }], 'x');
5764
+ * // => [{ 'x': 1 }, { 'x': 2 }]
5765
+ */
5766
+ function uniq(array, isSorted, iteratee, thisArg) {
5767
+ var length = array ? array.length : 0;
5768
+ if (!length) {
5769
+ return [];
5770
+ }
5771
+ if (isSorted != null && typeof isSorted != 'boolean') {
5772
+ thisArg = iteratee;
5773
+ iteratee = isIterateeCall(array, isSorted, thisArg) ? undefined : isSorted;
5774
+ isSorted = false;
5775
+ }
5776
+ var callback = getCallback();
5777
+ if (!(iteratee == null && callback === baseCallback)) {
5778
+ iteratee = callback(iteratee, thisArg, 3);
5779
+ }
5780
+ return (isSorted && getIndexOf() === baseIndexOf)
5781
+ ? sortedUniq(array, iteratee)
5782
+ : baseUniq(array, iteratee);
5783
+ }
5784
+
5785
+ /**
5786
+ * This method is like `_.zip` except that it accepts an array of grouped
5787
+ * elements and creates an array regrouping the elements to their pre-zip
5788
+ * configuration.
5789
+ *
5790
+ * @static
5791
+ * @memberOf _
5792
+ * @category Array
5793
+ * @param {Array} array The array of grouped elements to process.
5794
+ * @returns {Array} Returns the new array of regrouped elements.
5795
+ * @example
5796
+ *
5797
+ * var zipped = _.zip(['fred', 'barney'], [30, 40], [true, false]);
5798
+ * // => [['fred', 30, true], ['barney', 40, false]]
5799
+ *
5800
+ * _.unzip(zipped);
5801
+ * // => [['fred', 'barney'], [30, 40], [true, false]]
5802
+ */
5803
+ function unzip(array) {
5804
+ if (!(array && array.length)) {
5805
+ return [];
5806
+ }
5807
+ var index = -1,
5808
+ length = 0;
5809
+
5810
+ array = arrayFilter(array, function(group) {
5811
+ if (isArrayLike(group)) {
5812
+ length = nativeMax(group.length, length);
5813
+ return true;
5814
+ }
5815
+ });
5816
+ var result = Array(length);
5817
+ while (++index < length) {
5818
+ result[index] = arrayMap(array, baseProperty(index));
5819
+ }
5820
+ return result;
5821
+ }
5822
+
5823
+ /**
5824
+ * This method is like `_.unzip` except that it accepts an iteratee to specify
5825
+ * how regrouped values should be combined. The `iteratee` is bound to `thisArg`
5826
+ * and invoked with four arguments: (accumulator, value, index, group).
5827
+ *
5828
+ * @static
5829
+ * @memberOf _
5830
+ * @category Array
5831
+ * @param {Array} array The array of grouped elements to process.
5832
+ * @param {Function} [iteratee] The function to combine regrouped values.
5833
+ * @param {*} [thisArg] The `this` binding of `iteratee`.
5834
+ * @returns {Array} Returns the new array of regrouped elements.
5835
+ * @example
5836
+ *
5837
+ * var zipped = _.zip([1, 2], [10, 20], [100, 200]);
5838
+ * // => [[1, 10, 100], [2, 20, 200]]
5839
+ *
5840
+ * _.unzipWith(zipped, _.add);
5841
+ * // => [3, 30, 300]
5842
+ */
5843
+ function unzipWith(array, iteratee, thisArg) {
5844
+ var length = array ? array.length : 0;
5845
+ if (!length) {
5846
+ return [];
5847
+ }
5848
+ var result = unzip(array);
5849
+ if (iteratee == null) {
5850
+ return result;
5851
+ }
5852
+ iteratee = bindCallback(iteratee, thisArg, 4);
5853
+ return arrayMap(result, function(group) {
5854
+ return arrayReduce(group, iteratee, undefined, true);
5855
+ });
5856
+ }
5857
+
5858
+ /**
5859
+ * Creates an array excluding all provided values using
5860
+ * [`SameValueZero`](http://ecma-international.org/ecma-262/6.0/#sec-samevaluezero)
5861
+ * for equality comparisons.
5862
+ *
5863
+ * @static
5864
+ * @memberOf _
5865
+ * @category Array
5866
+ * @param {Array} array The array to filter.
5867
+ * @param {...*} [values] The values to exclude.
5868
+ * @returns {Array} Returns the new array of filtered values.
5869
+ * @example
5870
+ *
5871
+ * _.without([1, 2, 1, 3], 1, 2);
5872
+ * // => [3]
5873
+ */
5874
+ var without = restParam(function(array, values) {
5875
+ return isArrayLike(array)
5876
+ ? baseDifference(array, values)
5877
+ : [];
5878
+ });
5879
+
5880
+ /**
5881
+ * Creates an array of unique values that is the [symmetric difference](https://en.wikipedia.org/wiki/Symmetric_difference)
5882
+ * of the provided arrays.
5883
+ *
5884
+ * @static
5885
+ * @memberOf _
5886
+ * @category Array
5887
+ * @param {...Array} [arrays] The arrays to inspect.
5888
+ * @returns {Array} Returns the new array of values.
5889
+ * @example
5890
+ *
5891
+ * _.xor([1, 2], [4, 2]);
5892
+ * // => [1, 4]
5893
+ */
5894
+ function xor() {
5895
+ var index = -1,
5896
+ length = arguments.length;
5897
+
5898
+ while (++index < length) {
5899
+ var array = arguments[index];
5900
+ if (isArrayLike(array)) {
5901
+ var result = result
5902
+ ? arrayPush(baseDifference(result, array), baseDifference(array, result))
5903
+ : array;
5904
+ }
5905
+ }
5906
+ return result ? baseUniq(result) : [];
5907
+ }
5908
+
5909
+ /**
5910
+ * Creates an array of grouped elements, the first of which contains the first
5911
+ * elements of the given arrays, the second of which contains the second elements
5912
+ * of the given arrays, and so on.
5913
+ *
5914
+ * @static
5915
+ * @memberOf _
5916
+ * @category Array
5917
+ * @param {...Array} [arrays] The arrays to process.
5918
+ * @returns {Array} Returns the new array of grouped elements.
5919
+ * @example
5920
+ *
5921
+ * _.zip(['fred', 'barney'], [30, 40], [true, false]);
5922
+ * // => [['fred', 30, true], ['barney', 40, false]]
5923
+ */
5924
+ var zip = restParam(unzip);
5925
+
5926
+ /**
5927
+ * The inverse of `_.pairs`; this method returns an object composed from arrays
5928
+ * of property names and values. Provide either a single two dimensional array,
5929
+ * e.g. `[[key1, value1], [key2, value2]]` or two arrays, one of property names
5930
+ * and one of corresponding values.
5931
+ *
5932
+ * @static
5933
+ * @memberOf _
5934
+ * @alias object
5935
+ * @category Array
5936
+ * @param {Array} props The property names.
5937
+ * @param {Array} [values=[]] The property values.
5938
+ * @returns {Object} Returns the new object.
5939
+ * @example
5940
+ *
5941
+ * _.zipObject([['fred', 30], ['barney', 40]]);
5942
+ * // => { 'fred': 30, 'barney': 40 }
5943
+ *
5944
+ * _.zipObject(['fred', 'barney'], [30, 40]);
5945
+ * // => { 'fred': 30, 'barney': 40 }
5946
+ */
5947
+ function zipObject(props, values) {
5948
+ var index = -1,
5949
+ length = props ? props.length : 0,
5950
+ result = {};
5951
+
5952
+ if (length && !values && !isArray(props[0])) {
5953
+ values = [];
5954
+ }
5955
+ while (++index < length) {
5956
+ var key = props[index];
5957
+ if (values) {
5958
+ result[key] = values[index];
5959
+ } else if (key) {
5960
+ result[key[0]] = key[1];
5961
+ }
5962
+ }
5963
+ return result;
5964
+ }
5965
+
5966
+ /**
5967
+ * This method is like `_.zip` except that it accepts an iteratee to specify
5968
+ * how grouped values should be combined. The `iteratee` is bound to `thisArg`
5969
+ * and invoked with four arguments: (accumulator, value, index, group).
5970
+ *
5971
+ * @static
5972
+ * @memberOf _
5973
+ * @category Array
5974
+ * @param {...Array} [arrays] The arrays to process.
5975
+ * @param {Function} [iteratee] The function to combine grouped values.
5976
+ * @param {*} [thisArg] The `this` binding of `iteratee`.
5977
+ * @returns {Array} Returns the new array of grouped elements.
5978
+ * @example
5979
+ *
5980
+ * _.zipWith([1, 2], [10, 20], [100, 200], _.add);
5981
+ * // => [111, 222]
5982
+ */
5983
+ var zipWith = restParam(function(arrays) {
5984
+ var length = arrays.length,
5985
+ iteratee = length > 2 ? arrays[length - 2] : undefined,
5986
+ thisArg = length > 1 ? arrays[length - 1] : undefined;
5987
+
5988
+ if (length > 2 && typeof iteratee == 'function') {
5989
+ length -= 2;
5990
+ } else {
5991
+ iteratee = (length > 1 && typeof thisArg == 'function') ? (--length, thisArg) : undefined;
5992
+ thisArg = undefined;
5993
+ }
5994
+ arrays.length = length;
5995
+ return unzipWith(arrays, iteratee, thisArg);
5996
+ });
5997
+
5998
+ /*------------------------------------------------------------------------*/
5999
+
6000
+ /**
6001
+ * Creates a `lodash` object that wraps `value` with explicit method
6002
+ * chaining enabled.
6003
+ *
6004
+ * @static
6005
+ * @memberOf _
6006
+ * @category Chain
6007
+ * @param {*} value The value to wrap.
6008
+ * @returns {Object} Returns the new `lodash` wrapper instance.
6009
+ * @example
6010
+ *
6011
+ * var users = [
6012
+ * { 'user': 'barney', 'age': 36 },
6013
+ * { 'user': 'fred', 'age': 40 },
6014
+ * { 'user': 'pebbles', 'age': 1 }
6015
+ * ];
6016
+ *
6017
+ * var youngest = _.chain(users)
6018
+ * .sortBy('age')
6019
+ * .map(function(chr) {
6020
+ * return chr.user + ' is ' + chr.age;
6021
+ * })
6022
+ * .first()
6023
+ * .value();
6024
+ * // => 'pebbles is 1'
6025
+ */
6026
+ function chain(value) {
6027
+ var result = lodash(value);
6028
+ result.__chain__ = true;
6029
+ return result;
6030
+ }
6031
+
6032
+ /**
6033
+ * This method invokes `interceptor` and returns `value`. The interceptor is
6034
+ * bound to `thisArg` and invoked with one argument; (value). The purpose of
6035
+ * this method is to "tap into" a method chain in order to perform operations
6036
+ * on intermediate results within the chain.
6037
+ *
6038
+ * @static
6039
+ * @memberOf _
6040
+ * @category Chain
6041
+ * @param {*} value The value to provide to `interceptor`.
6042
+ * @param {Function} interceptor The function to invoke.
6043
+ * @param {*} [thisArg] The `this` binding of `interceptor`.
6044
+ * @returns {*} Returns `value`.
6045
+ * @example
6046
+ *
6047
+ * _([1, 2, 3])
6048
+ * .tap(function(array) {
6049
+ * array.pop();
6050
+ * })
6051
+ * .reverse()
6052
+ * .value();
6053
+ * // => [2, 1]
6054
+ */
6055
+ function tap(value, interceptor, thisArg) {
6056
+ interceptor.call(thisArg, value);
6057
+ return value;
6058
+ }
6059
+
6060
+ /**
6061
+ * This method is like `_.tap` except that it returns the result of `interceptor`.
6062
+ *
6063
+ * @static
6064
+ * @memberOf _
6065
+ * @category Chain
6066
+ * @param {*} value The value to provide to `interceptor`.
6067
+ * @param {Function} interceptor The function to invoke.
6068
+ * @param {*} [thisArg] The `this` binding of `interceptor`.
6069
+ * @returns {*} Returns the result of `interceptor`.
6070
+ * @example
6071
+ *
6072
+ * _(' abc ')
6073
+ * .chain()
6074
+ * .trim()
6075
+ * .thru(function(value) {
6076
+ * return [value];
6077
+ * })
6078
+ * .value();
6079
+ * // => ['abc']
6080
+ */
6081
+ function thru(value, interceptor, thisArg) {
6082
+ return interceptor.call(thisArg, value);
6083
+ }
6084
+
6085
+ /**
6086
+ * Enables explicit method chaining on the wrapper object.
6087
+ *
6088
+ * @name chain
6089
+ * @memberOf _
6090
+ * @category Chain
6091
+ * @returns {Object} Returns the new `lodash` wrapper instance.
6092
+ * @example
6093
+ *
6094
+ * var users = [
6095
+ * { 'user': 'barney', 'age': 36 },
6096
+ * { 'user': 'fred', 'age': 40 }
6097
+ * ];
6098
+ *
6099
+ * // without explicit chaining
6100
+ * _(users).first();
6101
+ * // => { 'user': 'barney', 'age': 36 }
6102
+ *
6103
+ * // with explicit chaining
6104
+ * _(users).chain()
6105
+ * .first()
6106
+ * .pick('user')
6107
+ * .value();
6108
+ * // => { 'user': 'barney' }
6109
+ */
6110
+ function wrapperChain() {
6111
+ return chain(this);
6112
+ }
6113
+
6114
+ /**
6115
+ * Executes the chained sequence and returns the wrapped result.
6116
+ *
6117
+ * @name commit
6118
+ * @memberOf _
6119
+ * @category Chain
6120
+ * @returns {Object} Returns the new `lodash` wrapper instance.
6121
+ * @example
6122
+ *
6123
+ * var array = [1, 2];
6124
+ * var wrapped = _(array).push(3);
6125
+ *
6126
+ * console.log(array);
6127
+ * // => [1, 2]
6128
+ *
6129
+ * wrapped = wrapped.commit();
6130
+ * console.log(array);
6131
+ * // => [1, 2, 3]
6132
+ *
6133
+ * wrapped.last();
6134
+ * // => 3
6135
+ *
6136
+ * console.log(array);
6137
+ * // => [1, 2, 3]
6138
+ */
6139
+ function wrapperCommit() {
6140
+ return new LodashWrapper(this.value(), this.__chain__);
6141
+ }
6142
+
6143
+ /**
6144
+ * Creates a new array joining a wrapped array with any additional arrays
6145
+ * and/or values.
6146
+ *
6147
+ * @name concat
6148
+ * @memberOf _
6149
+ * @category Chain
6150
+ * @param {...*} [values] The values to concatenate.
6151
+ * @returns {Array} Returns the new concatenated array.
6152
+ * @example
6153
+ *
6154
+ * var array = [1];
6155
+ * var wrapped = _(array).concat(2, [3], [[4]]);
6156
+ *
6157
+ * console.log(wrapped.value());
6158
+ * // => [1, 2, 3, [4]]
6159
+ *
6160
+ * console.log(array);
6161
+ * // => [1]
6162
+ */
6163
+ var wrapperConcat = restParam(function(values) {
6164
+ values = baseFlatten(values);
6165
+ return this.thru(function(array) {
6166
+ return arrayConcat(isArray(array) ? array : [toObject(array)], values);
6167
+ });
6168
+ });
6169
+
6170
+ /**
6171
+ * Creates a clone of the chained sequence planting `value` as the wrapped value.
6172
+ *
6173
+ * @name plant
6174
+ * @memberOf _
6175
+ * @category Chain
6176
+ * @returns {Object} Returns the new `lodash` wrapper instance.
6177
+ * @example
6178
+ *
6179
+ * var array = [1, 2];
6180
+ * var wrapped = _(array).map(function(value) {
6181
+ * return Math.pow(value, 2);
6182
+ * });
6183
+ *
6184
+ * var other = [3, 4];
6185
+ * var otherWrapped = wrapped.plant(other);
6186
+ *
6187
+ * otherWrapped.value();
6188
+ * // => [9, 16]
6189
+ *
6190
+ * wrapped.value();
6191
+ * // => [1, 4]
6192
+ */
6193
+ function wrapperPlant(value) {
6194
+ var result,
6195
+ parent = this;
6196
+
6197
+ while (parent instanceof baseLodash) {
6198
+ var clone = wrapperClone(parent);
6199
+ if (result) {
6200
+ previous.__wrapped__ = clone;
6201
+ } else {
6202
+ result = clone;
6203
+ }
6204
+ var previous = clone;
6205
+ parent = parent.__wrapped__;
6206
+ }
6207
+ previous.__wrapped__ = value;
6208
+ return result;
6209
+ }
6210
+
6211
+ /**
6212
+ * Reverses the wrapped array so the first element becomes the last, the
6213
+ * second element becomes the second to last, and so on.
6214
+ *
6215
+ * **Note:** This method mutates the wrapped array.
6216
+ *
6217
+ * @name reverse
6218
+ * @memberOf _
6219
+ * @category Chain
6220
+ * @returns {Object} Returns the new reversed `lodash` wrapper instance.
6221
+ * @example
6222
+ *
6223
+ * var array = [1, 2, 3];
6224
+ *
6225
+ * _(array).reverse().value()
6226
+ * // => [3, 2, 1]
6227
+ *
6228
+ * console.log(array);
6229
+ * // => [3, 2, 1]
6230
+ */
6231
+ function wrapperReverse() {
6232
+ var value = this.__wrapped__;
6233
+
6234
+ var interceptor = function(value) {
6235
+ return value.reverse();
6236
+ };
6237
+ if (value instanceof LazyWrapper) {
6238
+ var wrapped = value;
6239
+ if (this.__actions__.length) {
6240
+ wrapped = new LazyWrapper(this);
6241
+ }
6242
+ wrapped = wrapped.reverse();
6243
+ wrapped.__actions__.push({ 'func': thru, 'args': [interceptor], 'thisArg': undefined });
6244
+ return new LodashWrapper(wrapped, this.__chain__);
6245
+ }
6246
+ return this.thru(interceptor);
6247
+ }
6248
+
6249
+ /**
6250
+ * Produces the result of coercing the unwrapped value to a string.
6251
+ *
6252
+ * @name toString
6253
+ * @memberOf _
6254
+ * @category Chain
6255
+ * @returns {string} Returns the coerced string value.
6256
+ * @example
6257
+ *
6258
+ * _([1, 2, 3]).toString();
6259
+ * // => '1,2,3'
6260
+ */
6261
+ function wrapperToString() {
6262
+ return (this.value() + '');
6263
+ }
6264
+
6265
+ /**
6266
+ * Executes the chained sequence to extract the unwrapped value.
6267
+ *
6268
+ * @name value
6269
+ * @memberOf _
6270
+ * @alias run, toJSON, valueOf
6271
+ * @category Chain
6272
+ * @returns {*} Returns the resolved unwrapped value.
6273
+ * @example
6274
+ *
6275
+ * _([1, 2, 3]).value();
6276
+ * // => [1, 2, 3]
6277
+ */
6278
+ function wrapperValue() {
6279
+ return baseWrapperValue(this.__wrapped__, this.__actions__);
6280
+ }
6281
+
6282
+ /*------------------------------------------------------------------------*/
6283
+
6284
+ /**
6285
+ * Creates an array of elements corresponding to the given keys, or indexes,
6286
+ * of `collection`. Keys may be specified as individual arguments or as arrays
6287
+ * of keys.
6288
+ *
6289
+ * @static
6290
+ * @memberOf _
6291
+ * @category Collection
6292
+ * @param {Array|Object|string} collection The collection to iterate over.
6293
+ * @param {...(number|number[]|string|string[])} [props] The property names
6294
+ * or indexes of elements to pick, specified individually or in arrays.
6295
+ * @returns {Array} Returns the new array of picked elements.
6296
+ * @example
6297
+ *
6298
+ * _.at(['a', 'b', 'c'], [0, 2]);
6299
+ * // => ['a', 'c']
6300
+ *
6301
+ * _.at(['barney', 'fred', 'pebbles'], 0, 2);
6302
+ * // => ['barney', 'pebbles']
6303
+ */
6304
+ var at = restParam(function(collection, props) {
6305
+ if (isArrayLike(collection)) {
6306
+ collection = toIterable(collection);
6307
+ }
6308
+ return baseAt(collection, baseFlatten(props));
6309
+ });
6310
+
6311
+ /**
6312
+ * Creates an object composed of keys generated from the results of running
6313
+ * each element of `collection` through `iteratee`. The corresponding value
6314
+ * of each key is the number of times the key was returned by `iteratee`.
6315
+ * The `iteratee` is bound to `thisArg` and invoked with three arguments:
6316
+ * (value, index|key, collection).
6317
+ *
6318
+ * If a property name is provided for `iteratee` the created `_.property`
6319
+ * style callback returns the property value of the given element.
6320
+ *
6321
+ * If a value is also provided for `thisArg` the created `_.matchesProperty`
6322
+ * style callback returns `true` for elements that have a matching property
6323
+ * value, else `false`.
6324
+ *
6325
+ * If an object is provided for `iteratee` the created `_.matches` style
6326
+ * callback returns `true` for elements that have the properties of the given
6327
+ * object, else `false`.
6328
+ *
6329
+ * @static
6330
+ * @memberOf _
6331
+ * @category Collection
6332
+ * @param {Array|Object|string} collection The collection to iterate over.
6333
+ * @param {Function|Object|string} [iteratee=_.identity] The function invoked
6334
+ * per iteration.
6335
+ * @param {*} [thisArg] The `this` binding of `iteratee`.
6336
+ * @returns {Object} Returns the composed aggregate object.
6337
+ * @example
6338
+ *
6339
+ * _.countBy([4.3, 6.1, 6.4], function(n) {
6340
+ * return Math.floor(n);
6341
+ * });
6342
+ * // => { '4': 1, '6': 2 }
6343
+ *
6344
+ * _.countBy([4.3, 6.1, 6.4], function(n) {
6345
+ * return this.floor(n);
6346
+ * }, Math);
6347
+ * // => { '4': 1, '6': 2 }
6348
+ *
6349
+ * _.countBy(['one', 'two', 'three'], 'length');
6350
+ * // => { '3': 2, '5': 1 }
6351
+ */
6352
+ var countBy = createAggregator(function(result, value, key) {
6353
+ hasOwnProperty.call(result, key) ? ++result[key] : (result[key] = 1);
6354
+ });
6355
+
6356
+ /**
6357
+ * Checks if `predicate` returns truthy for **all** elements of `collection`.
6358
+ * The predicate is bound to `thisArg` and invoked with three arguments:
6359
+ * (value, index|key, collection).
6360
+ *
6361
+ * If a property name is provided for `predicate` the created `_.property`
6362
+ * style callback returns the property value of the given element.
6363
+ *
6364
+ * If a value is also provided for `thisArg` the created `_.matchesProperty`
6365
+ * style callback returns `true` for elements that have a matching property
6366
+ * value, else `false`.
6367
+ *
6368
+ * If an object is provided for `predicate` the created `_.matches` style
6369
+ * callback returns `true` for elements that have the properties of the given
6370
+ * object, else `false`.
6371
+ *
6372
+ * @static
6373
+ * @memberOf _
6374
+ * @alias all
6375
+ * @category Collection
6376
+ * @param {Array|Object|string} collection The collection to iterate over.
6377
+ * @param {Function|Object|string} [predicate=_.identity] The function invoked
6378
+ * per iteration.
6379
+ * @param {*} [thisArg] The `this` binding of `predicate`.
6380
+ * @returns {boolean} Returns `true` if all elements pass the predicate check,
6381
+ * else `false`.
6382
+ * @example
6383
+ *
6384
+ * _.every([true, 1, null, 'yes'], Boolean);
6385
+ * // => false
6386
+ *
6387
+ * var users = [
6388
+ * { 'user': 'barney', 'active': false },
6389
+ * { 'user': 'fred', 'active': false }
6390
+ * ];
6391
+ *
6392
+ * // using the `_.matches` callback shorthand
6393
+ * _.every(users, { 'user': 'barney', 'active': false });
6394
+ * // => false
6395
+ *
6396
+ * // using the `_.matchesProperty` callback shorthand
6397
+ * _.every(users, 'active', false);
6398
+ * // => true
6399
+ *
6400
+ * // using the `_.property` callback shorthand
6401
+ * _.every(users, 'active');
6402
+ * // => false
6403
+ */
6404
+ function every(collection, predicate, thisArg) {
6405
+ var func = isArray(collection) ? arrayEvery : baseEvery;
6406
+ if (thisArg && isIterateeCall(collection, predicate, thisArg)) {
6407
+ predicate = undefined;
6408
+ }
6409
+ if (typeof predicate != 'function' || thisArg !== undefined) {
6410
+ predicate = getCallback(predicate, thisArg, 3);
6411
+ }
6412
+ return func(collection, predicate);
6413
+ }
6414
+
6415
+ /**
6416
+ * Iterates over elements of `collection`, returning an array of all elements
6417
+ * `predicate` returns truthy for. The predicate is bound to `thisArg` and
6418
+ * invoked with three arguments: (value, index|key, collection).
6419
+ *
6420
+ * If a property name is provided for `predicate` the created `_.property`
6421
+ * style callback returns the property value of the given element.
6422
+ *
6423
+ * If a value is also provided for `thisArg` the created `_.matchesProperty`
6424
+ * style callback returns `true` for elements that have a matching property
6425
+ * value, else `false`.
6426
+ *
6427
+ * If an object is provided for `predicate` the created `_.matches` style
6428
+ * callback returns `true` for elements that have the properties of the given
6429
+ * object, else `false`.
6430
+ *
6431
+ * @static
6432
+ * @memberOf _
6433
+ * @alias select
6434
+ * @category Collection
6435
+ * @param {Array|Object|string} collection The collection to iterate over.
6436
+ * @param {Function|Object|string} [predicate=_.identity] The function invoked
6437
+ * per iteration.
6438
+ * @param {*} [thisArg] The `this` binding of `predicate`.
6439
+ * @returns {Array} Returns the new filtered array.
6440
+ * @example
6441
+ *
6442
+ * _.filter([4, 5, 6], function(n) {
6443
+ * return n % 2 == 0;
6444
+ * });
6445
+ * // => [4, 6]
6446
+ *
6447
+ * var users = [
6448
+ * { 'user': 'barney', 'age': 36, 'active': true },
6449
+ * { 'user': 'fred', 'age': 40, 'active': false }
6450
+ * ];
6451
+ *
6452
+ * // using the `_.matches` callback shorthand
6453
+ * _.pluck(_.filter(users, { 'age': 36, 'active': true }), 'user');
6454
+ * // => ['barney']
6455
+ *
6456
+ * // using the `_.matchesProperty` callback shorthand
6457
+ * _.pluck(_.filter(users, 'active', false), 'user');
6458
+ * // => ['fred']
6459
+ *
6460
+ * // using the `_.property` callback shorthand
6461
+ * _.pluck(_.filter(users, 'active'), 'user');
6462
+ * // => ['barney']
6463
+ */
6464
+ function filter(collection, predicate, thisArg) {
6465
+ var func = isArray(collection) ? arrayFilter : baseFilter;
6466
+ predicate = getCallback(predicate, thisArg, 3);
6467
+ return func(collection, predicate);
6468
+ }
6469
+
6470
+ /**
6471
+ * Iterates over elements of `collection`, returning the first element
6472
+ * `predicate` returns truthy for. The predicate is bound to `thisArg` and
6473
+ * invoked with three arguments: (value, index|key, collection).
6474
+ *
6475
+ * If a property name is provided for `predicate` the created `_.property`
6476
+ * style callback returns the property value of the given element.
6477
+ *
6478
+ * If a value is also provided for `thisArg` the created `_.matchesProperty`
6479
+ * style callback returns `true` for elements that have a matching property
6480
+ * value, else `false`.
6481
+ *
6482
+ * If an object is provided for `predicate` the created `_.matches` style
6483
+ * callback returns `true` for elements that have the properties of the given
6484
+ * object, else `false`.
6485
+ *
6486
+ * @static
6487
+ * @memberOf _
6488
+ * @alias detect
6489
+ * @category Collection
6490
+ * @param {Array|Object|string} collection The collection to search.
6491
+ * @param {Function|Object|string} [predicate=_.identity] The function invoked
6492
+ * per iteration.
6493
+ * @param {*} [thisArg] The `this` binding of `predicate`.
6494
+ * @returns {*} Returns the matched element, else `undefined`.
6495
+ * @example
6496
+ *
6497
+ * var users = [
6498
+ * { 'user': 'barney', 'age': 36, 'active': true },
6499
+ * { 'user': 'fred', 'age': 40, 'active': false },
6500
+ * { 'user': 'pebbles', 'age': 1, 'active': true }
6501
+ * ];
6502
+ *
6503
+ * _.result(_.find(users, function(chr) {
6504
+ * return chr.age < 40;
6505
+ * }), 'user');
6506
+ * // => 'barney'
6507
+ *
6508
+ * // using the `_.matches` callback shorthand
6509
+ * _.result(_.find(users, { 'age': 1, 'active': true }), 'user');
6510
+ * // => 'pebbles'
6511
+ *
6512
+ * // using the `_.matchesProperty` callback shorthand
6513
+ * _.result(_.find(users, 'active', false), 'user');
6514
+ * // => 'fred'
6515
+ *
6516
+ * // using the `_.property` callback shorthand
6517
+ * _.result(_.find(users, 'active'), 'user');
6518
+ * // => 'barney'
6519
+ */
6520
+ var find = createFind(baseEach);
6521
+
6522
+ /**
6523
+ * This method is like `_.find` except that it iterates over elements of
6524
+ * `collection` from right to left.
6525
+ *
6526
+ * @static
6527
+ * @memberOf _
6528
+ * @category Collection
6529
+ * @param {Array|Object|string} collection The collection to search.
6530
+ * @param {Function|Object|string} [predicate=_.identity] The function invoked
6531
+ * per iteration.
6532
+ * @param {*} [thisArg] The `this` binding of `predicate`.
6533
+ * @returns {*} Returns the matched element, else `undefined`.
6534
+ * @example
6535
+ *
6536
+ * _.findLast([1, 2, 3, 4], function(n) {
6537
+ * return n % 2 == 1;
6538
+ * });
6539
+ * // => 3
6540
+ */
6541
+ var findLast = createFind(baseEachRight, true);
6542
+
6543
+ /**
6544
+ * Performs a deep comparison between each element in `collection` and the
6545
+ * source object, returning the first element that has equivalent property
6546
+ * values.
6547
+ *
6548
+ * **Note:** This method supports comparing arrays, booleans, `Date` objects,
6549
+ * numbers, `Object` objects, regexes, and strings. Objects are compared by
6550
+ * their own, not inherited, enumerable properties. For comparing a single
6551
+ * own or inherited property value see `_.matchesProperty`.
6552
+ *
6553
+ * @static
6554
+ * @memberOf _
6555
+ * @category Collection
6556
+ * @param {Array|Object|string} collection The collection to search.
6557
+ * @param {Object} source The object of property values to match.
6558
+ * @returns {*} Returns the matched element, else `undefined`.
6559
+ * @example
6560
+ *
6561
+ * var users = [
6562
+ * { 'user': 'barney', 'age': 36, 'active': true },
6563
+ * { 'user': 'fred', 'age': 40, 'active': false }
6564
+ * ];
6565
+ *
6566
+ * _.result(_.findWhere(users, { 'age': 36, 'active': true }), 'user');
6567
+ * // => 'barney'
6568
+ *
6569
+ * _.result(_.findWhere(users, { 'age': 40, 'active': false }), 'user');
6570
+ * // => 'fred'
6571
+ */
6572
+ function findWhere(collection, source) {
6573
+ return find(collection, baseMatches(source));
6574
+ }
6575
+
6576
+ /**
6577
+ * Iterates over elements of `collection` invoking `iteratee` for each element.
6578
+ * The `iteratee` is bound to `thisArg` and invoked with three arguments:
6579
+ * (value, index|key, collection). Iteratee functions may exit iteration early
6580
+ * by explicitly returning `false`.
6581
+ *
6582
+ * **Note:** As with other "Collections" methods, objects with a "length" property
6583
+ * are iterated like arrays. To avoid this behavior `_.forIn` or `_.forOwn`
6584
+ * may be used for object iteration.
6585
+ *
6586
+ * @static
6587
+ * @memberOf _
6588
+ * @alias each
6589
+ * @category Collection
6590
+ * @param {Array|Object|string} collection The collection to iterate over.
6591
+ * @param {Function} [iteratee=_.identity] The function invoked per iteration.
6592
+ * @param {*} [thisArg] The `this` binding of `iteratee`.
6593
+ * @returns {Array|Object|string} Returns `collection`.
6594
+ * @example
6595
+ *
6596
+ * _([1, 2]).forEach(function(n) {
6597
+ * console.log(n);
6598
+ * }).value();
6599
+ * // => logs each value from left to right and returns the array
6600
+ *
6601
+ * _.forEach({ 'a': 1, 'b': 2 }, function(n, key) {
6602
+ * console.log(n, key);
6603
+ * });
6604
+ * // => logs each value-key pair and returns the object (iteration order is not guaranteed)
6605
+ */
6606
+ var forEach = createForEach(arrayEach, baseEach);
6607
+
6608
+ /**
6609
+ * This method is like `_.forEach` except that it iterates over elements of
6610
+ * `collection` from right to left.
6611
+ *
6612
+ * @static
6613
+ * @memberOf _
6614
+ * @alias eachRight
6615
+ * @category Collection
6616
+ * @param {Array|Object|string} collection The collection to iterate over.
6617
+ * @param {Function} [iteratee=_.identity] The function invoked per iteration.
6618
+ * @param {*} [thisArg] The `this` binding of `iteratee`.
6619
+ * @returns {Array|Object|string} Returns `collection`.
6620
+ * @example
6621
+ *
6622
+ * _([1, 2]).forEachRight(function(n) {
6623
+ * console.log(n);
6624
+ * }).value();
6625
+ * // => logs each value from right to left and returns the array
6626
+ */
6627
+ var forEachRight = createForEach(arrayEachRight, baseEachRight);
6628
+
6629
+ /**
6630
+ * Creates an object composed of keys generated from the results of running
6631
+ * each element of `collection` through `iteratee`. The corresponding value
6632
+ * of each key is an array of the elements responsible for generating the key.
6633
+ * The `iteratee` is bound to `thisArg` and invoked with three arguments:
6634
+ * (value, index|key, collection).
6635
+ *
6636
+ * If a property name is provided for `iteratee` the created `_.property`
6637
+ * style callback returns the property value of the given element.
6638
+ *
6639
+ * If a value is also provided for `thisArg` the created `_.matchesProperty`
6640
+ * style callback returns `true` for elements that have a matching property
6641
+ * value, else `false`.
6642
+ *
6643
+ * If an object is provided for `iteratee` the created `_.matches` style
6644
+ * callback returns `true` for elements that have the properties of the given
6645
+ * object, else `false`.
6646
+ *
6647
+ * @static
6648
+ * @memberOf _
6649
+ * @category Collection
6650
+ * @param {Array|Object|string} collection The collection to iterate over.
6651
+ * @param {Function|Object|string} [iteratee=_.identity] The function invoked
6652
+ * per iteration.
6653
+ * @param {*} [thisArg] The `this` binding of `iteratee`.
6654
+ * @returns {Object} Returns the composed aggregate object.
6655
+ * @example
6656
+ *
6657
+ * _.groupBy([4.2, 6.1, 6.4], function(n) {
6658
+ * return Math.floor(n);
6659
+ * });
6660
+ * // => { '4': [4.2], '6': [6.1, 6.4] }
6661
+ *
6662
+ * _.groupBy([4.2, 6.1, 6.4], function(n) {
6663
+ * return this.floor(n);
6664
+ * }, Math);
6665
+ * // => { '4': [4.2], '6': [6.1, 6.4] }
6666
+ *
6667
+ * // using the `_.property` callback shorthand
6668
+ * _.groupBy(['one', 'two', 'three'], 'length');
6669
+ * // => { '3': ['one', 'two'], '5': ['three'] }
6670
+ */
6671
+ var groupBy = createAggregator(function(result, value, key) {
6672
+ if (hasOwnProperty.call(result, key)) {
6673
+ result[key].push(value);
6674
+ } else {
6675
+ result[key] = [value];
6676
+ }
6677
+ });
6678
+
6679
+ /**
6680
+ * Checks if `target` is in `collection` using
6681
+ * [`SameValueZero`](http://ecma-international.org/ecma-262/6.0/#sec-samevaluezero)
6682
+ * for equality comparisons. If `fromIndex` is negative, it's used as the offset
6683
+ * from the end of `collection`.
6684
+ *
6685
+ * @static
6686
+ * @memberOf _
6687
+ * @alias contains, include
6688
+ * @category Collection
6689
+ * @param {Array|Object|string} collection The collection to search.
6690
+ * @param {*} target The value to search for.
6691
+ * @param {number} [fromIndex=0] The index to search from.
6692
+ * @param- {Object} [guard] Enables use as a callback for functions like `_.reduce`.
6693
+ * @returns {boolean} Returns `true` if a matching element is found, else `false`.
6694
+ * @example
6695
+ *
6696
+ * _.includes([1, 2, 3], 1);
6697
+ * // => true
6698
+ *
6699
+ * _.includes([1, 2, 3], 1, 2);
6700
+ * // => false
6701
+ *
6702
+ * _.includes({ 'user': 'fred', 'age': 40 }, 'fred');
6703
+ * // => true
6704
+ *
6705
+ * _.includes('pebbles', 'eb');
6706
+ * // => true
6707
+ */
6708
+ function includes(collection, target, fromIndex, guard) {
6709
+ var length = collection ? getLength(collection) : 0;
6710
+ if (!isLength(length)) {
6711
+ collection = values(collection);
6712
+ length = collection.length;
6713
+ }
6714
+ if (typeof fromIndex != 'number' || (guard && isIterateeCall(target, fromIndex, guard))) {
6715
+ fromIndex = 0;
6716
+ } else {
6717
+ fromIndex = fromIndex < 0 ? nativeMax(length + fromIndex, 0) : (fromIndex || 0);
6718
+ }
6719
+ return (typeof collection == 'string' || !isArray(collection) && isString(collection))
6720
+ ? (fromIndex <= length && collection.indexOf(target, fromIndex) > -1)
6721
+ : (!!length && getIndexOf(collection, target, fromIndex) > -1);
6722
+ }
6723
+
6724
+ /**
6725
+ * Creates an object composed of keys generated from the results of running
6726
+ * each element of `collection` through `iteratee`. The corresponding value
6727
+ * of each key is the last element responsible for generating the key. The
6728
+ * iteratee function is bound to `thisArg` and invoked with three arguments:
6729
+ * (value, index|key, collection).
6730
+ *
6731
+ * If a property name is provided for `iteratee` the created `_.property`
6732
+ * style callback returns the property value of the given element.
6733
+ *
6734
+ * If a value is also provided for `thisArg` the created `_.matchesProperty`
6735
+ * style callback returns `true` for elements that have a matching property
6736
+ * value, else `false`.
6737
+ *
6738
+ * If an object is provided for `iteratee` the created `_.matches` style
6739
+ * callback returns `true` for elements that have the properties of the given
6740
+ * object, else `false`.
6741
+ *
6742
+ * @static
6743
+ * @memberOf _
6744
+ * @category Collection
6745
+ * @param {Array|Object|string} collection The collection to iterate over.
6746
+ * @param {Function|Object|string} [iteratee=_.identity] The function invoked
6747
+ * per iteration.
6748
+ * @param {*} [thisArg] The `this` binding of `iteratee`.
6749
+ * @returns {Object} Returns the composed aggregate object.
6750
+ * @example
6751
+ *
6752
+ * var keyData = [
6753
+ * { 'dir': 'left', 'code': 97 },
6754
+ * { 'dir': 'right', 'code': 100 }
6755
+ * ];
6756
+ *
6757
+ * _.indexBy(keyData, 'dir');
6758
+ * // => { 'left': { 'dir': 'left', 'code': 97 }, 'right': { 'dir': 'right', 'code': 100 } }
6759
+ *
6760
+ * _.indexBy(keyData, function(object) {
6761
+ * return String.fromCharCode(object.code);
6762
+ * });
6763
+ * // => { 'a': { 'dir': 'left', 'code': 97 }, 'd': { 'dir': 'right', 'code': 100 } }
6764
+ *
6765
+ * _.indexBy(keyData, function(object) {
6766
+ * return this.fromCharCode(object.code);
6767
+ * }, String);
6768
+ * // => { 'a': { 'dir': 'left', 'code': 97 }, 'd': { 'dir': 'right', 'code': 100 } }
6769
+ */
6770
+ var indexBy = createAggregator(function(result, value, key) {
6771
+ result[key] = value;
6772
+ });
6773
+
6774
+ /**
6775
+ * Invokes the method at `path` of each element in `collection`, returning
6776
+ * an array of the results of each invoked method. Any additional arguments
6777
+ * are provided to each invoked method. If `methodName` is a function it's
6778
+ * invoked for, and `this` bound to, each element in `collection`.
6779
+ *
6780
+ * @static
6781
+ * @memberOf _
6782
+ * @category Collection
6783
+ * @param {Array|Object|string} collection The collection to iterate over.
6784
+ * @param {Array|Function|string} path The path of the method to invoke or
6785
+ * the function invoked per iteration.
6786
+ * @param {...*} [args] The arguments to invoke the method with.
6787
+ * @returns {Array} Returns the array of results.
6788
+ * @example
6789
+ *
6790
+ * _.invoke([[5, 1, 7], [3, 2, 1]], 'sort');
6791
+ * // => [[1, 5, 7], [1, 2, 3]]
6792
+ *
6793
+ * _.invoke([123, 456], String.prototype.split, '');
6794
+ * // => [['1', '2', '3'], ['4', '5', '6']]
6795
+ */
6796
+ var invoke = restParam(function(collection, path, args) {
6797
+ var index = -1,
6798
+ isFunc = typeof path == 'function',
6799
+ isProp = isKey(path),
6800
+ result = isArrayLike(collection) ? Array(collection.length) : [];
6801
+
6802
+ baseEach(collection, function(value) {
6803
+ var func = isFunc ? path : ((isProp && value != null) ? value[path] : undefined);
6804
+ result[++index] = func ? func.apply(value, args) : invokePath(value, path, args);
6805
+ });
6806
+ return result;
6807
+ });
6808
+
6809
+ /**
6810
+ * Creates an array of values by running each element in `collection` through
6811
+ * `iteratee`. The `iteratee` is bound to `thisArg` and invoked with three
6812
+ * arguments: (value, index|key, collection).
6813
+ *
6814
+ * If a property name is provided for `iteratee` the created `_.property`
6815
+ * style callback returns the property value of the given element.
6816
+ *
6817
+ * If a value is also provided for `thisArg` the created `_.matchesProperty`
6818
+ * style callback returns `true` for elements that have a matching property
6819
+ * value, else `false`.
6820
+ *
6821
+ * If an object is provided for `iteratee` the created `_.matches` style
6822
+ * callback returns `true` for elements that have the properties of the given
6823
+ * object, else `false`.
6824
+ *
6825
+ * Many lodash methods are guarded to work as iteratees for methods like
6826
+ * `_.every`, `_.filter`, `_.map`, `_.mapValues`, `_.reject`, and `_.some`.
6827
+ *
6828
+ * The guarded methods are:
6829
+ * `ary`, `callback`, `chunk`, `clone`, `create`, `curry`, `curryRight`,
6830
+ * `drop`, `dropRight`, `every`, `fill`, `flatten`, `invert`, `max`, `min`,
6831
+ * `parseInt`, `slice`, `sortBy`, `take`, `takeRight`, `template`, `trim`,
6832
+ * `trimLeft`, `trimRight`, `trunc`, `random`, `range`, `sample`, `some`,
6833
+ * `sum`, `uniq`, and `words`
6834
+ *
6835
+ * @static
6836
+ * @memberOf _
6837
+ * @alias collect
6838
+ * @category Collection
6839
+ * @param {Array|Object|string} collection The collection to iterate over.
6840
+ * @param {Function|Object|string} [iteratee=_.identity] The function invoked
6841
+ * per iteration.
6842
+ * @param {*} [thisArg] The `this` binding of `iteratee`.
6843
+ * @returns {Array} Returns the new mapped array.
6844
+ * @example
6845
+ *
6846
+ * function timesThree(n) {
6847
+ * return n * 3;
6848
+ * }
6849
+ *
6850
+ * _.map([1, 2], timesThree);
6851
+ * // => [3, 6]
6852
+ *
6853
+ * _.map({ 'a': 1, 'b': 2 }, timesThree);
6854
+ * // => [3, 6] (iteration order is not guaranteed)
6855
+ *
6856
+ * var users = [
6857
+ * { 'user': 'barney' },
6858
+ * { 'user': 'fred' }
6859
+ * ];
6860
+ *
6861
+ * // using the `_.property` callback shorthand
6862
+ * _.map(users, 'user');
6863
+ * // => ['barney', 'fred']
6864
+ */
6865
+ function map(collection, iteratee, thisArg) {
6866
+ var func = isArray(collection) ? arrayMap : baseMap;
6867
+ iteratee = getCallback(iteratee, thisArg, 3);
6868
+ return func(collection, iteratee);
6869
+ }
6870
+
6871
+ /**
6872
+ * Creates an array of elements split into two groups, the first of which
6873
+ * contains elements `predicate` returns truthy for, while the second of which
6874
+ * contains elements `predicate` returns falsey for. The predicate is bound
6875
+ * to `thisArg` and invoked with three arguments: (value, index|key, collection).
6876
+ *
6877
+ * If a property name is provided for `predicate` the created `_.property`
6878
+ * style callback returns the property value of the given element.
6879
+ *
6880
+ * If a value is also provided for `thisArg` the created `_.matchesProperty`
6881
+ * style callback returns `true` for elements that have a matching property
6882
+ * value, else `false`.
6883
+ *
6884
+ * If an object is provided for `predicate` the created `_.matches` style
6885
+ * callback returns `true` for elements that have the properties of the given
6886
+ * object, else `false`.
6887
+ *
6888
+ * @static
6889
+ * @memberOf _
6890
+ * @category Collection
6891
+ * @param {Array|Object|string} collection The collection to iterate over.
6892
+ * @param {Function|Object|string} [predicate=_.identity] The function invoked
6893
+ * per iteration.
6894
+ * @param {*} [thisArg] The `this` binding of `predicate`.
6895
+ * @returns {Array} Returns the array of grouped elements.
6896
+ * @example
6897
+ *
6898
+ * _.partition([1, 2, 3], function(n) {
6899
+ * return n % 2;
6900
+ * });
6901
+ * // => [[1, 3], [2]]
6902
+ *
6903
+ * _.partition([1.2, 2.3, 3.4], function(n) {
6904
+ * return this.floor(n) % 2;
6905
+ * }, Math);
6906
+ * // => [[1.2, 3.4], [2.3]]
6907
+ *
6908
+ * var users = [
6909
+ * { 'user': 'barney', 'age': 36, 'active': false },
6910
+ * { 'user': 'fred', 'age': 40, 'active': true },
6911
+ * { 'user': 'pebbles', 'age': 1, 'active': false }
6912
+ * ];
6913
+ *
6914
+ * var mapper = function(array) {
6915
+ * return _.pluck(array, 'user');
6916
+ * };
6917
+ *
6918
+ * // using the `_.matches` callback shorthand
6919
+ * _.map(_.partition(users, { 'age': 1, 'active': false }), mapper);
6920
+ * // => [['pebbles'], ['barney', 'fred']]
6921
+ *
6922
+ * // using the `_.matchesProperty` callback shorthand
6923
+ * _.map(_.partition(users, 'active', false), mapper);
6924
+ * // => [['barney', 'pebbles'], ['fred']]
6925
+ *
6926
+ * // using the `_.property` callback shorthand
6927
+ * _.map(_.partition(users, 'active'), mapper);
6928
+ * // => [['fred'], ['barney', 'pebbles']]
6929
+ */
6930
+ var partition = createAggregator(function(result, value, key) {
6931
+ result[key ? 0 : 1].push(value);
6932
+ }, function() { return [[], []]; });
6933
+
6934
+ /**
6935
+ * Gets the property value of `path` from all elements in `collection`.
6936
+ *
6937
+ * @static
6938
+ * @memberOf _
6939
+ * @category Collection
6940
+ * @param {Array|Object|string} collection The collection to iterate over.
6941
+ * @param {Array|string} path The path of the property to pluck.
6942
+ * @returns {Array} Returns the property values.
6943
+ * @example
6944
+ *
6945
+ * var users = [
6946
+ * { 'user': 'barney', 'age': 36 },
6947
+ * { 'user': 'fred', 'age': 40 }
6948
+ * ];
6949
+ *
6950
+ * _.pluck(users, 'user');
6951
+ * // => ['barney', 'fred']
6952
+ *
6953
+ * var userIndex = _.indexBy(users, 'user');
6954
+ * _.pluck(userIndex, 'age');
6955
+ * // => [36, 40] (iteration order is not guaranteed)
6956
+ */
6957
+ function pluck(collection, path) {
6958
+ return map(collection, property(path));
6959
+ }
6960
+
6961
+ /**
6962
+ * Reduces `collection` to a value which is the accumulated result of running
6963
+ * each element in `collection` through `iteratee`, where each successive
6964
+ * invocation is supplied the return value of the previous. If `accumulator`
6965
+ * is not provided the first element of `collection` is used as the initial
6966
+ * value. The `iteratee` is bound to `thisArg` and invoked with four arguments:
6967
+ * (accumulator, value, index|key, collection).
6968
+ *
6969
+ * Many lodash methods are guarded to work as iteratees for methods like
6970
+ * `_.reduce`, `_.reduceRight`, and `_.transform`.
6971
+ *
6972
+ * The guarded methods are:
6973
+ * `assign`, `defaults`, `defaultsDeep`, `includes`, `merge`, `sortByAll`,
6974
+ * and `sortByOrder`
6975
+ *
6976
+ * @static
6977
+ * @memberOf _
6978
+ * @alias foldl, inject
6979
+ * @category Collection
6980
+ * @param {Array|Object|string} collection The collection to iterate over.
6981
+ * @param {Function} [iteratee=_.identity] The function invoked per iteration.
6982
+ * @param {*} [accumulator] The initial value.
6983
+ * @param {*} [thisArg] The `this` binding of `iteratee`.
6984
+ * @returns {*} Returns the accumulated value.
6985
+ * @example
6986
+ *
6987
+ * _.reduce([1, 2], function(total, n) {
6988
+ * return total + n;
6989
+ * });
6990
+ * // => 3
6991
+ *
6992
+ * _.reduce({ 'a': 1, 'b': 2 }, function(result, n, key) {
6993
+ * result[key] = n * 3;
6994
+ * return result;
6995
+ * }, {});
6996
+ * // => { 'a': 3, 'b': 6 } (iteration order is not guaranteed)
6997
+ */
6998
+ var reduce = createReduce(arrayReduce, baseEach);
6999
+
7000
+ /**
7001
+ * This method is like `_.reduce` except that it iterates over elements of
7002
+ * `collection` from right to left.
7003
+ *
7004
+ * @static
7005
+ * @memberOf _
7006
+ * @alias foldr
7007
+ * @category Collection
7008
+ * @param {Array|Object|string} collection The collection to iterate over.
7009
+ * @param {Function} [iteratee=_.identity] The function invoked per iteration.
7010
+ * @param {*} [accumulator] The initial value.
7011
+ * @param {*} [thisArg] The `this` binding of `iteratee`.
7012
+ * @returns {*} Returns the accumulated value.
7013
+ * @example
7014
+ *
7015
+ * var array = [[0, 1], [2, 3], [4, 5]];
7016
+ *
7017
+ * _.reduceRight(array, function(flattened, other) {
7018
+ * return flattened.concat(other);
7019
+ * }, []);
7020
+ * // => [4, 5, 2, 3, 0, 1]
7021
+ */
7022
+ var reduceRight = createReduce(arrayReduceRight, baseEachRight);
7023
+
7024
+ /**
7025
+ * The opposite of `_.filter`; this method returns the elements of `collection`
7026
+ * that `predicate` does **not** return truthy for.
7027
+ *
7028
+ * @static
7029
+ * @memberOf _
7030
+ * @category Collection
7031
+ * @param {Array|Object|string} collection The collection to iterate over.
7032
+ * @param {Function|Object|string} [predicate=_.identity] The function invoked
7033
+ * per iteration.
7034
+ * @param {*} [thisArg] The `this` binding of `predicate`.
7035
+ * @returns {Array} Returns the new filtered array.
7036
+ * @example
7037
+ *
7038
+ * _.reject([1, 2, 3, 4], function(n) {
7039
+ * return n % 2 == 0;
7040
+ * });
7041
+ * // => [1, 3]
7042
+ *
7043
+ * var users = [
7044
+ * { 'user': 'barney', 'age': 36, 'active': false },
7045
+ * { 'user': 'fred', 'age': 40, 'active': true }
7046
+ * ];
7047
+ *
7048
+ * // using the `_.matches` callback shorthand
7049
+ * _.pluck(_.reject(users, { 'age': 40, 'active': true }), 'user');
7050
+ * // => ['barney']
7051
+ *
7052
+ * // using the `_.matchesProperty` callback shorthand
7053
+ * _.pluck(_.reject(users, 'active', false), 'user');
7054
+ * // => ['fred']
7055
+ *
7056
+ * // using the `_.property` callback shorthand
7057
+ * _.pluck(_.reject(users, 'active'), 'user');
7058
+ * // => ['barney']
7059
+ */
7060
+ function reject(collection, predicate, thisArg) {
7061
+ var func = isArray(collection) ? arrayFilter : baseFilter;
7062
+ predicate = getCallback(predicate, thisArg, 3);
7063
+ return func(collection, function(value, index, collection) {
7064
+ return !predicate(value, index, collection);
7065
+ });
7066
+ }
7067
+
7068
+ /**
7069
+ * Gets a random element or `n` random elements from a collection.
7070
+ *
7071
+ * @static
7072
+ * @memberOf _
7073
+ * @category Collection
7074
+ * @param {Array|Object|string} collection The collection to sample.
7075
+ * @param {number} [n] The number of elements to sample.
7076
+ * @param- {Object} [guard] Enables use as a callback for functions like `_.map`.
7077
+ * @returns {*} Returns the random sample(s).
7078
+ * @example
7079
+ *
7080
+ * _.sample([1, 2, 3, 4]);
7081
+ * // => 2
7082
+ *
7083
+ * _.sample([1, 2, 3, 4], 2);
7084
+ * // => [3, 1]
7085
+ */
7086
+ function sample(collection, n, guard) {
7087
+ if (guard ? isIterateeCall(collection, n, guard) : n == null) {
7088
+ collection = toIterable(collection);
7089
+ var length = collection.length;
7090
+ return length > 0 ? collection[baseRandom(0, length - 1)] : undefined;
7091
+ }
7092
+ var index = -1,
7093
+ result = toArray(collection),
7094
+ length = result.length,
7095
+ lastIndex = length - 1;
7096
+
7097
+ n = nativeMin(n < 0 ? 0 : (+n || 0), length);
7098
+ while (++index < n) {
7099
+ var rand = baseRandom(index, lastIndex),
7100
+ value = result[rand];
7101
+
7102
+ result[rand] = result[index];
7103
+ result[index] = value;
7104
+ }
7105
+ result.length = n;
7106
+ return result;
7107
+ }
7108
+
7109
+ /**
7110
+ * Creates an array of shuffled values, using a version of the
7111
+ * [Fisher-Yates shuffle](https://en.wikipedia.org/wiki/Fisher-Yates_shuffle).
7112
+ *
7113
+ * @static
7114
+ * @memberOf _
7115
+ * @category Collection
7116
+ * @param {Array|Object|string} collection The collection to shuffle.
7117
+ * @returns {Array} Returns the new shuffled array.
7118
+ * @example
7119
+ *
7120
+ * _.shuffle([1, 2, 3, 4]);
7121
+ * // => [4, 1, 3, 2]
7122
+ */
7123
+ function shuffle(collection) {
7124
+ return sample(collection, POSITIVE_INFINITY);
7125
+ }
7126
+
7127
+ /**
7128
+ * Gets the size of `collection` by returning its length for array-like
7129
+ * values or the number of own enumerable properties for objects.
7130
+ *
7131
+ * @static
7132
+ * @memberOf _
7133
+ * @category Collection
7134
+ * @param {Array|Object|string} collection The collection to inspect.
7135
+ * @returns {number} Returns the size of `collection`.
7136
+ * @example
7137
+ *
7138
+ * _.size([1, 2, 3]);
7139
+ * // => 3
7140
+ *
7141
+ * _.size({ 'a': 1, 'b': 2 });
7142
+ * // => 2
7143
+ *
7144
+ * _.size('pebbles');
7145
+ * // => 7
7146
+ */
7147
+ function size(collection) {
7148
+ var length = collection ? getLength(collection) : 0;
7149
+ return isLength(length) ? length : keys(collection).length;
7150
+ }
7151
+
7152
+ /**
7153
+ * Checks if `predicate` returns truthy for **any** element of `collection`.
7154
+ * The function returns as soon as it finds a passing value and does not iterate
7155
+ * over the entire collection. The predicate is bound to `thisArg` and invoked
7156
+ * with three arguments: (value, index|key, collection).
7157
+ *
7158
+ * If a property name is provided for `predicate` the created `_.property`
7159
+ * style callback returns the property value of the given element.
7160
+ *
7161
+ * If a value is also provided for `thisArg` the created `_.matchesProperty`
7162
+ * style callback returns `true` for elements that have a matching property
7163
+ * value, else `false`.
7164
+ *
7165
+ * If an object is provided for `predicate` the created `_.matches` style
7166
+ * callback returns `true` for elements that have the properties of the given
7167
+ * object, else `false`.
7168
+ *
7169
+ * @static
7170
+ * @memberOf _
7171
+ * @alias any
7172
+ * @category Collection
7173
+ * @param {Array|Object|string} collection The collection to iterate over.
7174
+ * @param {Function|Object|string} [predicate=_.identity] The function invoked
7175
+ * per iteration.
7176
+ * @param {*} [thisArg] The `this` binding of `predicate`.
7177
+ * @returns {boolean} Returns `true` if any element passes the predicate check,
7178
+ * else `false`.
7179
+ * @example
7180
+ *
7181
+ * _.some([null, 0, 'yes', false], Boolean);
7182
+ * // => true
7183
+ *
7184
+ * var users = [
7185
+ * { 'user': 'barney', 'active': true },
7186
+ * { 'user': 'fred', 'active': false }
7187
+ * ];
7188
+ *
7189
+ * // using the `_.matches` callback shorthand
7190
+ * _.some(users, { 'user': 'barney', 'active': false });
7191
+ * // => false
7192
+ *
7193
+ * // using the `_.matchesProperty` callback shorthand
7194
+ * _.some(users, 'active', false);
7195
+ * // => true
7196
+ *
7197
+ * // using the `_.property` callback shorthand
7198
+ * _.some(users, 'active');
7199
+ * // => true
7200
+ */
7201
+ function some(collection, predicate, thisArg) {
7202
+ var func = isArray(collection) ? arraySome : baseSome;
7203
+ if (thisArg && isIterateeCall(collection, predicate, thisArg)) {
7204
+ predicate = undefined;
7205
+ }
7206
+ if (typeof predicate != 'function' || thisArg !== undefined) {
7207
+ predicate = getCallback(predicate, thisArg, 3);
7208
+ }
7209
+ return func(collection, predicate);
7210
+ }
7211
+
7212
+ /**
7213
+ * Creates an array of elements, sorted in ascending order by the results of
7214
+ * running each element in a collection through `iteratee`. This method performs
7215
+ * a stable sort, that is, it preserves the original sort order of equal elements.
7216
+ * The `iteratee` is bound to `thisArg` and invoked with three arguments:
7217
+ * (value, index|key, collection).
7218
+ *
7219
+ * If a property name is provided for `iteratee` the created `_.property`
7220
+ * style callback returns the property value of the given element.
7221
+ *
7222
+ * If a value is also provided for `thisArg` the created `_.matchesProperty`
7223
+ * style callback returns `true` for elements that have a matching property
7224
+ * value, else `false`.
7225
+ *
7226
+ * If an object is provided for `iteratee` the created `_.matches` style
7227
+ * callback returns `true` for elements that have the properties of the given
7228
+ * object, else `false`.
7229
+ *
7230
+ * @static
7231
+ * @memberOf _
7232
+ * @category Collection
7233
+ * @param {Array|Object|string} collection The collection to iterate over.
7234
+ * @param {Function|Object|string} [iteratee=_.identity] The function invoked
7235
+ * per iteration.
7236
+ * @param {*} [thisArg] The `this` binding of `iteratee`.
7237
+ * @returns {Array} Returns the new sorted array.
7238
+ * @example
7239
+ *
7240
+ * _.sortBy([1, 2, 3], function(n) {
7241
+ * return Math.sin(n);
7242
+ * });
7243
+ * // => [3, 1, 2]
7244
+ *
7245
+ * _.sortBy([1, 2, 3], function(n) {
7246
+ * return this.sin(n);
7247
+ * }, Math);
7248
+ * // => [3, 1, 2]
7249
+ *
7250
+ * var users = [
7251
+ * { 'user': 'fred' },
7252
+ * { 'user': 'pebbles' },
7253
+ * { 'user': 'barney' }
7254
+ * ];
7255
+ *
7256
+ * // using the `_.property` callback shorthand
7257
+ * _.pluck(_.sortBy(users, 'user'), 'user');
7258
+ * // => ['barney', 'fred', 'pebbles']
7259
+ */
7260
+ function sortBy(collection, iteratee, thisArg) {
7261
+ if (collection == null) {
7262
+ return [];
7263
+ }
7264
+ if (thisArg && isIterateeCall(collection, iteratee, thisArg)) {
7265
+ iteratee = undefined;
7266
+ }
7267
+ var index = -1;
7268
+ iteratee = getCallback(iteratee, thisArg, 3);
7269
+
7270
+ var result = baseMap(collection, function(value, key, collection) {
7271
+ return { 'criteria': iteratee(value, key, collection), 'index': ++index, 'value': value };
7272
+ });
7273
+ return baseSortBy(result, compareAscending);
7274
+ }
7275
+
7276
+ /**
7277
+ * This method is like `_.sortBy` except that it can sort by multiple iteratees
7278
+ * or property names.
7279
+ *
7280
+ * If a property name is provided for an iteratee the created `_.property`
7281
+ * style callback returns the property value of the given element.
7282
+ *
7283
+ * If an object is provided for an iteratee the created `_.matches` style
7284
+ * callback returns `true` for elements that have the properties of the given
7285
+ * object, else `false`.
7286
+ *
7287
+ * @static
7288
+ * @memberOf _
7289
+ * @category Collection
7290
+ * @param {Array|Object|string} collection The collection to iterate over.
7291
+ * @param {...(Function|Function[]|Object|Object[]|string|string[])} iteratees
7292
+ * The iteratees to sort by, specified as individual values or arrays of values.
7293
+ * @returns {Array} Returns the new sorted array.
7294
+ * @example
7295
+ *
7296
+ * var users = [
7297
+ * { 'user': 'fred', 'age': 48 },
7298
+ * { 'user': 'barney', 'age': 36 },
7299
+ * { 'user': 'fred', 'age': 42 },
7300
+ * { 'user': 'barney', 'age': 34 }
7301
+ * ];
7302
+ *
7303
+ * _.map(_.sortByAll(users, ['user', 'age']), _.values);
7304
+ * // => [['barney', 34], ['barney', 36], ['fred', 42], ['fred', 48]]
7305
+ *
7306
+ * _.map(_.sortByAll(users, 'user', function(chr) {
7307
+ * return Math.floor(chr.age / 10);
7308
+ * }), _.values);
7309
+ * // => [['barney', 36], ['barney', 34], ['fred', 48], ['fred', 42]]
7310
+ */
7311
+ var sortByAll = restParam(function(collection, iteratees) {
7312
+ if (collection == null) {
7313
+ return [];
7314
+ }
7315
+ var guard = iteratees[2];
7316
+ if (guard && isIterateeCall(iteratees[0], iteratees[1], guard)) {
7317
+ iteratees.length = 1;
7318
+ }
7319
+ return baseSortByOrder(collection, baseFlatten(iteratees), []);
7320
+ });
7321
+
7322
+ /**
7323
+ * This method is like `_.sortByAll` except that it allows specifying the
7324
+ * sort orders of the iteratees to sort by. If `orders` is unspecified, all
7325
+ * values are sorted in ascending order. Otherwise, a value is sorted in
7326
+ * ascending order if its corresponding order is "asc", and descending if "desc".
7327
+ *
7328
+ * If a property name is provided for an iteratee the created `_.property`
7329
+ * style callback returns the property value of the given element.
7330
+ *
7331
+ * If an object is provided for an iteratee the created `_.matches` style
7332
+ * callback returns `true` for elements that have the properties of the given
7333
+ * object, else `false`.
7334
+ *
7335
+ * @static
7336
+ * @memberOf _
7337
+ * @category Collection
7338
+ * @param {Array|Object|string} collection The collection to iterate over.
7339
+ * @param {Function[]|Object[]|string[]} iteratees The iteratees to sort by.
7340
+ * @param {boolean[]} [orders] The sort orders of `iteratees`.
7341
+ * @param- {Object} [guard] Enables use as a callback for functions like `_.reduce`.
7342
+ * @returns {Array} Returns the new sorted array.
7343
+ * @example
7344
+ *
7345
+ * var users = [
7346
+ * { 'user': 'fred', 'age': 48 },
7347
+ * { 'user': 'barney', 'age': 34 },
7348
+ * { 'user': 'fred', 'age': 42 },
7349
+ * { 'user': 'barney', 'age': 36 }
7350
+ * ];
7351
+ *
7352
+ * // sort by `user` in ascending order and by `age` in descending order
7353
+ * _.map(_.sortByOrder(users, ['user', 'age'], ['asc', 'desc']), _.values);
7354
+ * // => [['barney', 36], ['barney', 34], ['fred', 48], ['fred', 42]]
7355
+ */
7356
+ function sortByOrder(collection, iteratees, orders, guard) {
7357
+ if (collection == null) {
7358
+ return [];
7359
+ }
7360
+ if (guard && isIterateeCall(iteratees, orders, guard)) {
7361
+ orders = undefined;
7362
+ }
7363
+ if (!isArray(iteratees)) {
7364
+ iteratees = iteratees == null ? [] : [iteratees];
7365
+ }
7366
+ if (!isArray(orders)) {
7367
+ orders = orders == null ? [] : [orders];
7368
+ }
7369
+ return baseSortByOrder(collection, iteratees, orders);
7370
+ }
7371
+
7372
+ /**
7373
+ * Performs a deep comparison between each element in `collection` and the
7374
+ * source object, returning an array of all elements that have equivalent
7375
+ * property values.
7376
+ *
7377
+ * **Note:** This method supports comparing arrays, booleans, `Date` objects,
7378
+ * numbers, `Object` objects, regexes, and strings. Objects are compared by
7379
+ * their own, not inherited, enumerable properties. For comparing a single
7380
+ * own or inherited property value see `_.matchesProperty`.
7381
+ *
7382
+ * @static
7383
+ * @memberOf _
7384
+ * @category Collection
7385
+ * @param {Array|Object|string} collection The collection to search.
7386
+ * @param {Object} source The object of property values to match.
7387
+ * @returns {Array} Returns the new filtered array.
7388
+ * @example
7389
+ *
7390
+ * var users = [
7391
+ * { 'user': 'barney', 'age': 36, 'active': false, 'pets': ['hoppy'] },
7392
+ * { 'user': 'fred', 'age': 40, 'active': true, 'pets': ['baby puss', 'dino'] }
7393
+ * ];
7394
+ *
7395
+ * _.pluck(_.where(users, { 'age': 36, 'active': false }), 'user');
7396
+ * // => ['barney']
7397
+ *
7398
+ * _.pluck(_.where(users, { 'pets': ['dino'] }), 'user');
7399
+ * // => ['fred']
7400
+ */
7401
+ function where(collection, source) {
7402
+ return filter(collection, baseMatches(source));
7403
+ }
7404
+
7405
+ /*------------------------------------------------------------------------*/
7406
+
7407
+ /**
7408
+ * Gets the number of milliseconds that have elapsed since the Unix epoch
7409
+ * (1 January 1970 00:00:00 UTC).
7410
+ *
7411
+ * @static
7412
+ * @memberOf _
7413
+ * @category Date
7414
+ * @example
7415
+ *
7416
+ * _.defer(function(stamp) {
7417
+ * console.log(_.now() - stamp);
7418
+ * }, _.now());
7419
+ * // => logs the number of milliseconds it took for the deferred function to be invoked
7420
+ */
7421
+ var now = nativeNow || function() {
7422
+ return new Date().getTime();
7423
+ };
7424
+
7425
+ /*------------------------------------------------------------------------*/
7426
+
7427
+ /**
7428
+ * The opposite of `_.before`; this method creates a function that invokes
7429
+ * `func` once it's called `n` or more times.
7430
+ *
7431
+ * @static
7432
+ * @memberOf _
7433
+ * @category Function
7434
+ * @param {number} n The number of calls before `func` is invoked.
7435
+ * @param {Function} func The function to restrict.
7436
+ * @returns {Function} Returns the new restricted function.
7437
+ * @example
7438
+ *
7439
+ * var saves = ['profile', 'settings'];
7440
+ *
7441
+ * var done = _.after(saves.length, function() {
7442
+ * console.log('done saving!');
7443
+ * });
7444
+ *
7445
+ * _.forEach(saves, function(type) {
7446
+ * asyncSave({ 'type': type, 'complete': done });
7447
+ * });
7448
+ * // => logs 'done saving!' after the two async saves have completed
7449
+ */
7450
+ function after(n, func) {
7451
+ if (typeof func != 'function') {
7452
+ if (typeof n == 'function') {
7453
+ var temp = n;
7454
+ n = func;
7455
+ func = temp;
7456
+ } else {
7457
+ throw new TypeError(FUNC_ERROR_TEXT);
7458
+ }
7459
+ }
7460
+ n = nativeIsFinite(n = +n) ? n : 0;
7461
+ return function() {
7462
+ if (--n < 1) {
7463
+ return func.apply(this, arguments);
7464
+ }
7465
+ };
7466
+ }
7467
+
7468
+ /**
7469
+ * Creates a function that accepts up to `n` arguments ignoring any
7470
+ * additional arguments.
7471
+ *
7472
+ * @static
7473
+ * @memberOf _
7474
+ * @category Function
7475
+ * @param {Function} func The function to cap arguments for.
7476
+ * @param {number} [n=func.length] The arity cap.
7477
+ * @param- {Object} [guard] Enables use as a callback for functions like `_.map`.
7478
+ * @returns {Function} Returns the new function.
7479
+ * @example
7480
+ *
7481
+ * _.map(['6', '8', '10'], _.ary(parseInt, 1));
7482
+ * // => [6, 8, 10]
7483
+ */
7484
+ function ary(func, n, guard) {
7485
+ if (guard && isIterateeCall(func, n, guard)) {
7486
+ n = undefined;
7487
+ }
7488
+ n = (func && n == null) ? func.length : nativeMax(+n || 0, 0);
7489
+ return createWrapper(func, ARY_FLAG, undefined, undefined, undefined, undefined, n);
7490
+ }
7491
+
7492
+ /**
7493
+ * Creates a function that invokes `func`, with the `this` binding and arguments
7494
+ * of the created function, while it's called less than `n` times. Subsequent
7495
+ * calls to the created function return the result of the last `func` invocation.
7496
+ *
7497
+ * @static
7498
+ * @memberOf _
7499
+ * @category Function
7500
+ * @param {number} n The number of calls at which `func` is no longer invoked.
7501
+ * @param {Function} func The function to restrict.
7502
+ * @returns {Function} Returns the new restricted function.
7503
+ * @example
7504
+ *
7505
+ * jQuery('#add').on('click', _.before(5, addContactToList));
7506
+ * // => allows adding up to 4 contacts to the list
7507
+ */
7508
+ function before(n, func) {
7509
+ var result;
7510
+ if (typeof func != 'function') {
7511
+ if (typeof n == 'function') {
7512
+ var temp = n;
7513
+ n = func;
7514
+ func = temp;
7515
+ } else {
7516
+ throw new TypeError(FUNC_ERROR_TEXT);
7517
+ }
7518
+ }
7519
+ return function() {
7520
+ if (--n > 0) {
7521
+ result = func.apply(this, arguments);
7522
+ }
7523
+ if (n <= 1) {
7524
+ func = undefined;
7525
+ }
7526
+ return result;
7527
+ };
7528
+ }
7529
+
7530
+ /**
7531
+ * Creates a function that invokes `func` with the `this` binding of `thisArg`
7532
+ * and prepends any additional `_.bind` arguments to those provided to the
7533
+ * bound function.
7534
+ *
7535
+ * The `_.bind.placeholder` value, which defaults to `_` in monolithic builds,
7536
+ * may be used as a placeholder for partially applied arguments.
7537
+ *
7538
+ * **Note:** Unlike native `Function#bind` this method does not set the "length"
7539
+ * property of bound functions.
7540
+ *
7541
+ * @static
7542
+ * @memberOf _
7543
+ * @category Function
7544
+ * @param {Function} func The function to bind.
7545
+ * @param {*} thisArg The `this` binding of `func`.
7546
+ * @param {...*} [partials] The arguments to be partially applied.
7547
+ * @returns {Function} Returns the new bound function.
7548
+ * @example
7549
+ *
7550
+ * var greet = function(greeting, punctuation) {
7551
+ * return greeting + ' ' + this.user + punctuation;
7552
+ * };
7553
+ *
7554
+ * var object = { 'user': 'fred' };
7555
+ *
7556
+ * var bound = _.bind(greet, object, 'hi');
7557
+ * bound('!');
7558
+ * // => 'hi fred!'
7559
+ *
7560
+ * // using placeholders
7561
+ * var bound = _.bind(greet, object, _, '!');
7562
+ * bound('hi');
7563
+ * // => 'hi fred!'
7564
+ */
7565
+ var bind = restParam(function(func, thisArg, partials) {
7566
+ var bitmask = BIND_FLAG;
7567
+ if (partials.length) {
7568
+ var holders = replaceHolders(partials, bind.placeholder);
7569
+ bitmask |= PARTIAL_FLAG;
7570
+ }
7571
+ return createWrapper(func, bitmask, thisArg, partials, holders);
7572
+ });
7573
+
7574
+ /**
7575
+ * Binds methods of an object to the object itself, overwriting the existing
7576
+ * method. Method names may be specified as individual arguments or as arrays
7577
+ * of method names. If no method names are provided all enumerable function
7578
+ * properties, own and inherited, of `object` are bound.
7579
+ *
7580
+ * **Note:** This method does not set the "length" property of bound functions.
7581
+ *
7582
+ * @static
7583
+ * @memberOf _
7584
+ * @category Function
7585
+ * @param {Object} object The object to bind and assign the bound methods to.
7586
+ * @param {...(string|string[])} [methodNames] The object method names to bind,
7587
+ * specified as individual method names or arrays of method names.
7588
+ * @returns {Object} Returns `object`.
7589
+ * @example
7590
+ *
7591
+ * var view = {
7592
+ * 'label': 'docs',
7593
+ * 'onClick': function() {
7594
+ * console.log('clicked ' + this.label);
7595
+ * }
7596
+ * };
7597
+ *
7598
+ * _.bindAll(view);
7599
+ * jQuery('#docs').on('click', view.onClick);
7600
+ * // => logs 'clicked docs' when the element is clicked
7601
+ */
7602
+ var bindAll = restParam(function(object, methodNames) {
7603
+ methodNames = methodNames.length ? baseFlatten(methodNames) : functions(object);
7604
+
7605
+ var index = -1,
7606
+ length = methodNames.length;
7607
+
7608
+ while (++index < length) {
7609
+ var key = methodNames[index];
7610
+ object[key] = createWrapper(object[key], BIND_FLAG, object);
7611
+ }
7612
+ return object;
7613
+ });
7614
+
7615
+ /**
7616
+ * Creates a function that invokes the method at `object[key]` and prepends
7617
+ * any additional `_.bindKey` arguments to those provided to the bound function.
7618
+ *
7619
+ * This method differs from `_.bind` by allowing bound functions to reference
7620
+ * methods that may be redefined or don't yet exist.
7621
+ * See [Peter Michaux's article](http://peter.michaux.ca/articles/lazy-function-definition-pattern)
7622
+ * for more details.
7623
+ *
7624
+ * The `_.bindKey.placeholder` value, which defaults to `_` in monolithic
7625
+ * builds, may be used as a placeholder for partially applied arguments.
7626
+ *
7627
+ * @static
7628
+ * @memberOf _
7629
+ * @category Function
7630
+ * @param {Object} object The object the method belongs to.
7631
+ * @param {string} key The key of the method.
7632
+ * @param {...*} [partials] The arguments to be partially applied.
7633
+ * @returns {Function} Returns the new bound function.
7634
+ * @example
7635
+ *
7636
+ * var object = {
7637
+ * 'user': 'fred',
7638
+ * 'greet': function(greeting, punctuation) {
7639
+ * return greeting + ' ' + this.user + punctuation;
7640
+ * }
7641
+ * };
7642
+ *
7643
+ * var bound = _.bindKey(object, 'greet', 'hi');
7644
+ * bound('!');
7645
+ * // => 'hi fred!'
7646
+ *
7647
+ * object.greet = function(greeting, punctuation) {
7648
+ * return greeting + 'ya ' + this.user + punctuation;
7649
+ * };
7650
+ *
7651
+ * bound('!');
7652
+ * // => 'hiya fred!'
7653
+ *
7654
+ * // using placeholders
7655
+ * var bound = _.bindKey(object, 'greet', _, '!');
7656
+ * bound('hi');
7657
+ * // => 'hiya fred!'
7658
+ */
7659
+ var bindKey = restParam(function(object, key, partials) {
7660
+ var bitmask = BIND_FLAG | BIND_KEY_FLAG;
7661
+ if (partials.length) {
7662
+ var holders = replaceHolders(partials, bindKey.placeholder);
7663
+ bitmask |= PARTIAL_FLAG;
7664
+ }
7665
+ return createWrapper(key, bitmask, object, partials, holders);
7666
+ });
7667
+
7668
+ /**
7669
+ * Creates a function that accepts one or more arguments of `func` that when
7670
+ * called either invokes `func` returning its result, if all `func` arguments
7671
+ * have been provided, or returns a function that accepts one or more of the
7672
+ * remaining `func` arguments, and so on. The arity of `func` may be specified
7673
+ * if `func.length` is not sufficient.
7674
+ *
7675
+ * The `_.curry.placeholder` value, which defaults to `_` in monolithic builds,
7676
+ * may be used as a placeholder for provided arguments.
7677
+ *
7678
+ * **Note:** This method does not set the "length" property of curried functions.
7679
+ *
7680
+ * @static
7681
+ * @memberOf _
7682
+ * @category Function
7683
+ * @param {Function} func The function to curry.
7684
+ * @param {number} [arity=func.length] The arity of `func`.
7685
+ * @param- {Object} [guard] Enables use as a callback for functions like `_.map`.
7686
+ * @returns {Function} Returns the new curried function.
7687
+ * @example
7688
+ *
7689
+ * var abc = function(a, b, c) {
7690
+ * return [a, b, c];
7691
+ * };
7692
+ *
7693
+ * var curried = _.curry(abc);
7694
+ *
7695
+ * curried(1)(2)(3);
7696
+ * // => [1, 2, 3]
7697
+ *
7698
+ * curried(1, 2)(3);
7699
+ * // => [1, 2, 3]
7700
+ *
7701
+ * curried(1, 2, 3);
7702
+ * // => [1, 2, 3]
7703
+ *
7704
+ * // using placeholders
7705
+ * curried(1)(_, 3)(2);
7706
+ * // => [1, 2, 3]
7707
+ */
7708
+ var curry = createCurry(CURRY_FLAG);
7709
+
7710
+ /**
7711
+ * This method is like `_.curry` except that arguments are applied to `func`
7712
+ * in the manner of `_.partialRight` instead of `_.partial`.
7713
+ *
7714
+ * The `_.curryRight.placeholder` value, which defaults to `_` in monolithic
7715
+ * builds, may be used as a placeholder for provided arguments.
7716
+ *
7717
+ * **Note:** This method does not set the "length" property of curried functions.
7718
+ *
7719
+ * @static
7720
+ * @memberOf _
7721
+ * @category Function
7722
+ * @param {Function} func The function to curry.
7723
+ * @param {number} [arity=func.length] The arity of `func`.
7724
+ * @param- {Object} [guard] Enables use as a callback for functions like `_.map`.
7725
+ * @returns {Function} Returns the new curried function.
7726
+ * @example
7727
+ *
7728
+ * var abc = function(a, b, c) {
7729
+ * return [a, b, c];
7730
+ * };
7731
+ *
7732
+ * var curried = _.curryRight(abc);
7733
+ *
7734
+ * curried(3)(2)(1);
7735
+ * // => [1, 2, 3]
7736
+ *
7737
+ * curried(2, 3)(1);
7738
+ * // => [1, 2, 3]
7739
+ *
7740
+ * curried(1, 2, 3);
7741
+ * // => [1, 2, 3]
7742
+ *
7743
+ * // using placeholders
7744
+ * curried(3)(1, _)(2);
7745
+ * // => [1, 2, 3]
7746
+ */
7747
+ var curryRight = createCurry(CURRY_RIGHT_FLAG);
7748
+
7749
+ /**
7750
+ * Creates a debounced function that delays invoking `func` until after `wait`
7751
+ * milliseconds have elapsed since the last time the debounced function was
7752
+ * invoked. The debounced function comes with a `cancel` method to cancel
7753
+ * delayed invocations. Provide an options object to indicate that `func`
7754
+ * should be invoked on the leading and/or trailing edge of the `wait` timeout.
7755
+ * Subsequent calls to the debounced function return the result of the last
7756
+ * `func` invocation.
7757
+ *
7758
+ * **Note:** If `leading` and `trailing` options are `true`, `func` is invoked
7759
+ * on the trailing edge of the timeout only if the the debounced function is
7760
+ * invoked more than once during the `wait` timeout.
7761
+ *
7762
+ * See [David Corbacho's article](http://drupalmotion.com/article/debounce-and-throttle-visual-explanation)
7763
+ * for details over the differences between `_.debounce` and `_.throttle`.
7764
+ *
7765
+ * @static
7766
+ * @memberOf _
7767
+ * @category Function
7768
+ * @param {Function} func The function to debounce.
7769
+ * @param {number} [wait=0] The number of milliseconds to delay.
7770
+ * @param {Object} [options] The options object.
7771
+ * @param {boolean} [options.leading=false] Specify invoking on the leading
7772
+ * edge of the timeout.
7773
+ * @param {number} [options.maxWait] The maximum time `func` is allowed to be
7774
+ * delayed before it's invoked.
7775
+ * @param {boolean} [options.trailing=true] Specify invoking on the trailing
7776
+ * edge of the timeout.
7777
+ * @returns {Function} Returns the new debounced function.
7778
+ * @example
7779
+ *
7780
+ * // avoid costly calculations while the window size is in flux
7781
+ * jQuery(window).on('resize', _.debounce(calculateLayout, 150));
7782
+ *
7783
+ * // invoke `sendMail` when the click event is fired, debouncing subsequent calls
7784
+ * jQuery('#postbox').on('click', _.debounce(sendMail, 300, {
7785
+ * 'leading': true,
7786
+ * 'trailing': false
7787
+ * }));
7788
+ *
7789
+ * // ensure `batchLog` is invoked once after 1 second of debounced calls
7790
+ * var source = new EventSource('/stream');
7791
+ * jQuery(source).on('message', _.debounce(batchLog, 250, {
7792
+ * 'maxWait': 1000
7793
+ * }));
7794
+ *
7795
+ * // cancel a debounced call
7796
+ * var todoChanges = _.debounce(batchLog, 1000);
7797
+ * Object.observe(models.todo, todoChanges);
7798
+ *
7799
+ * Object.observe(models, function(changes) {
7800
+ * if (_.find(changes, { 'user': 'todo', 'type': 'delete'})) {
7801
+ * todoChanges.cancel();
7802
+ * }
7803
+ * }, ['delete']);
7804
+ *
7805
+ * // ...at some point `models.todo` is changed
7806
+ * models.todo.completed = true;
7807
+ *
7808
+ * // ...before 1 second has passed `models.todo` is deleted
7809
+ * // which cancels the debounced `todoChanges` call
7810
+ * delete models.todo;
7811
+ */
7812
+ function debounce(func, wait, options) {
7813
+ var args,
7814
+ maxTimeoutId,
7815
+ result,
7816
+ stamp,
7817
+ thisArg,
7818
+ timeoutId,
7819
+ trailingCall,
7820
+ lastCalled = 0,
7821
+ maxWait = false,
7822
+ trailing = true;
7823
+
7824
+ if (typeof func != 'function') {
7825
+ throw new TypeError(FUNC_ERROR_TEXT);
7826
+ }
7827
+ wait = wait < 0 ? 0 : (+wait || 0);
7828
+ if (options === true) {
7829
+ var leading = true;
7830
+ trailing = false;
7831
+ } else if (isObject(options)) {
7832
+ leading = !!options.leading;
7833
+ maxWait = 'maxWait' in options && nativeMax(+options.maxWait || 0, wait);
7834
+ trailing = 'trailing' in options ? !!options.trailing : trailing;
7835
+ }
7836
+
7837
+ function cancel() {
7838
+ if (timeoutId) {
7839
+ clearTimeout(timeoutId);
7840
+ }
7841
+ if (maxTimeoutId) {
7842
+ clearTimeout(maxTimeoutId);
7843
+ }
7844
+ lastCalled = 0;
7845
+ maxTimeoutId = timeoutId = trailingCall = undefined;
7846
+ }
7847
+
7848
+ function complete(isCalled, id) {
7849
+ if (id) {
7850
+ clearTimeout(id);
7851
+ }
7852
+ maxTimeoutId = timeoutId = trailingCall = undefined;
7853
+ if (isCalled) {
7854
+ lastCalled = now();
7855
+ result = func.apply(thisArg, args);
7856
+ if (!timeoutId && !maxTimeoutId) {
7857
+ args = thisArg = undefined;
7858
+ }
7859
+ }
7860
+ }
7861
+
7862
+ function delayed() {
7863
+ var remaining = wait - (now() - stamp);
7864
+ if (remaining <= 0 || remaining > wait) {
7865
+ complete(trailingCall, maxTimeoutId);
7866
+ } else {
7867
+ timeoutId = setTimeout(delayed, remaining);
7868
+ }
7869
+ }
7870
+
7871
+ function maxDelayed() {
7872
+ complete(trailing, timeoutId);
7873
+ }
7874
+
7875
+ function debounced() {
7876
+ args = arguments;
7877
+ stamp = now();
7878
+ thisArg = this;
7879
+ trailingCall = trailing && (timeoutId || !leading);
7880
+
7881
+ if (maxWait === false) {
7882
+ var leadingCall = leading && !timeoutId;
7883
+ } else {
7884
+ if (!maxTimeoutId && !leading) {
7885
+ lastCalled = stamp;
7886
+ }
7887
+ var remaining = maxWait - (stamp - lastCalled),
7888
+ isCalled = remaining <= 0 || remaining > maxWait;
7889
+
7890
+ if (isCalled) {
7891
+ if (maxTimeoutId) {
7892
+ maxTimeoutId = clearTimeout(maxTimeoutId);
7893
+ }
7894
+ lastCalled = stamp;
7895
+ result = func.apply(thisArg, args);
7896
+ }
7897
+ else if (!maxTimeoutId) {
7898
+ maxTimeoutId = setTimeout(maxDelayed, remaining);
7899
+ }
7900
+ }
7901
+ if (isCalled && timeoutId) {
7902
+ timeoutId = clearTimeout(timeoutId);
7903
+ }
7904
+ else if (!timeoutId && wait !== maxWait) {
7905
+ timeoutId = setTimeout(delayed, wait);
7906
+ }
7907
+ if (leadingCall) {
7908
+ isCalled = true;
7909
+ result = func.apply(thisArg, args);
7910
+ }
7911
+ if (isCalled && !timeoutId && !maxTimeoutId) {
7912
+ args = thisArg = undefined;
7913
+ }
7914
+ return result;
7915
+ }
7916
+ debounced.cancel = cancel;
7917
+ return debounced;
7918
+ }
7919
+
7920
+ /**
7921
+ * Defers invoking the `func` until the current call stack has cleared. Any
7922
+ * additional arguments are provided to `func` when it's invoked.
7923
+ *
7924
+ * @static
7925
+ * @memberOf _
7926
+ * @category Function
7927
+ * @param {Function} func The function to defer.
7928
+ * @param {...*} [args] The arguments to invoke the function with.
7929
+ * @returns {number} Returns the timer id.
7930
+ * @example
7931
+ *
7932
+ * _.defer(function(text) {
7933
+ * console.log(text);
7934
+ * }, 'deferred');
7935
+ * // logs 'deferred' after one or more milliseconds
7936
+ */
7937
+ var defer = restParam(function(func, args) {
7938
+ return baseDelay(func, 1, args);
7939
+ });
7940
+
7941
+ /**
7942
+ * Invokes `func` after `wait` milliseconds. Any additional arguments are
7943
+ * provided to `func` when it's invoked.
7944
+ *
7945
+ * @static
7946
+ * @memberOf _
7947
+ * @category Function
7948
+ * @param {Function} func The function to delay.
7949
+ * @param {number} wait The number of milliseconds to delay invocation.
7950
+ * @param {...*} [args] The arguments to invoke the function with.
7951
+ * @returns {number} Returns the timer id.
7952
+ * @example
7953
+ *
7954
+ * _.delay(function(text) {
7955
+ * console.log(text);
7956
+ * }, 1000, 'later');
7957
+ * // => logs 'later' after one second
7958
+ */
7959
+ var delay = restParam(function(func, wait, args) {
7960
+ return baseDelay(func, wait, args);
7961
+ });
7962
+
7963
+ /**
7964
+ * Creates a function that returns the result of invoking the provided
7965
+ * functions with the `this` binding of the created function, where each
7966
+ * successive invocation is supplied the return value of the previous.
7967
+ *
7968
+ * @static
7969
+ * @memberOf _
7970
+ * @category Function
7971
+ * @param {...Function} [funcs] Functions to invoke.
7972
+ * @returns {Function} Returns the new function.
7973
+ * @example
7974
+ *
7975
+ * function square(n) {
7976
+ * return n * n;
7977
+ * }
7978
+ *
7979
+ * var addSquare = _.flow(_.add, square);
7980
+ * addSquare(1, 2);
7981
+ * // => 9
7982
+ */
7983
+ var flow = createFlow();
7984
+
7985
+ /**
7986
+ * This method is like `_.flow` except that it creates a function that
7987
+ * invokes the provided functions from right to left.
7988
+ *
7989
+ * @static
7990
+ * @memberOf _
7991
+ * @alias backflow, compose
7992
+ * @category Function
7993
+ * @param {...Function} [funcs] Functions to invoke.
7994
+ * @returns {Function} Returns the new function.
7995
+ * @example
7996
+ *
7997
+ * function square(n) {
7998
+ * return n * n;
7999
+ * }
8000
+ *
8001
+ * var addSquare = _.flowRight(square, _.add);
8002
+ * addSquare(1, 2);
8003
+ * // => 9
8004
+ */
8005
+ var flowRight = createFlow(true);
8006
+
8007
+ /**
8008
+ * Creates a function that memoizes the result of `func`. If `resolver` is
8009
+ * provided it determines the cache key for storing the result based on the
8010
+ * arguments provided to the memoized function. By default, the first argument
8011
+ * provided to the memoized function is coerced to a string and used as the
8012
+ * cache key. The `func` is invoked with the `this` binding of the memoized
8013
+ * function.
8014
+ *
8015
+ * **Note:** The cache is exposed as the `cache` property on the memoized
8016
+ * function. Its creation may be customized by replacing the `_.memoize.Cache`
8017
+ * constructor with one whose instances implement the [`Map`](http://ecma-international.org/ecma-262/6.0/#sec-properties-of-the-map-prototype-object)
8018
+ * method interface of `get`, `has`, and `set`.
8019
+ *
8020
+ * @static
8021
+ * @memberOf _
8022
+ * @category Function
8023
+ * @param {Function} func The function to have its output memoized.
8024
+ * @param {Function} [resolver] The function to resolve the cache key.
8025
+ * @returns {Function} Returns the new memoizing function.
8026
+ * @example
8027
+ *
8028
+ * var upperCase = _.memoize(function(string) {
8029
+ * return string.toUpperCase();
8030
+ * });
8031
+ *
8032
+ * upperCase('fred');
8033
+ * // => 'FRED'
8034
+ *
8035
+ * // modifying the result cache
8036
+ * upperCase.cache.set('fred', 'BARNEY');
8037
+ * upperCase('fred');
8038
+ * // => 'BARNEY'
8039
+ *
8040
+ * // replacing `_.memoize.Cache`
8041
+ * var object = { 'user': 'fred' };
8042
+ * var other = { 'user': 'barney' };
8043
+ * var identity = _.memoize(_.identity);
8044
+ *
8045
+ * identity(object);
8046
+ * // => { 'user': 'fred' }
8047
+ * identity(other);
8048
+ * // => { 'user': 'fred' }
8049
+ *
8050
+ * _.memoize.Cache = WeakMap;
8051
+ * var identity = _.memoize(_.identity);
8052
+ *
8053
+ * identity(object);
8054
+ * // => { 'user': 'fred' }
8055
+ * identity(other);
8056
+ * // => { 'user': 'barney' }
8057
+ */
8058
+ function memoize(func, resolver) {
8059
+ if (typeof func != 'function' || (resolver && typeof resolver != 'function')) {
8060
+ throw new TypeError(FUNC_ERROR_TEXT);
8061
+ }
8062
+ var memoized = function() {
8063
+ var args = arguments,
8064
+ key = resolver ? resolver.apply(this, args) : args[0],
8065
+ cache = memoized.cache;
8066
+
8067
+ if (cache.has(key)) {
8068
+ return cache.get(key);
8069
+ }
8070
+ var result = func.apply(this, args);
8071
+ memoized.cache = cache.set(key, result);
8072
+ return result;
8073
+ };
8074
+ memoized.cache = new memoize.Cache;
8075
+ return memoized;
8076
+ }
8077
+
8078
+ /**
8079
+ * Creates a function that runs each argument through a corresponding
8080
+ * transform function.
8081
+ *
8082
+ * @static
8083
+ * @memberOf _
8084
+ * @category Function
8085
+ * @param {Function} func The function to wrap.
8086
+ * @param {...(Function|Function[])} [transforms] The functions to transform
8087
+ * arguments, specified as individual functions or arrays of functions.
8088
+ * @returns {Function} Returns the new function.
8089
+ * @example
8090
+ *
8091
+ * function doubled(n) {
8092
+ * return n * 2;
8093
+ * }
8094
+ *
8095
+ * function square(n) {
8096
+ * return n * n;
8097
+ * }
8098
+ *
8099
+ * var modded = _.modArgs(function(x, y) {
8100
+ * return [x, y];
8101
+ * }, square, doubled);
8102
+ *
8103
+ * modded(1, 2);
8104
+ * // => [1, 4]
8105
+ *
8106
+ * modded(5, 10);
8107
+ * // => [25, 20]
8108
+ */
8109
+ var modArgs = restParam(function(func, transforms) {
8110
+ transforms = baseFlatten(transforms);
8111
+ if (typeof func != 'function' || !arrayEvery(transforms, baseIsFunction)) {
8112
+ throw new TypeError(FUNC_ERROR_TEXT);
8113
+ }
8114
+ var length = transforms.length;
8115
+ return restParam(function(args) {
8116
+ var index = nativeMin(args.length, length);
8117
+ while (index--) {
8118
+ args[index] = transforms[index](args[index]);
8119
+ }
8120
+ return func.apply(this, args);
8121
+ });
8122
+ });
8123
+
8124
+ /**
8125
+ * Creates a function that negates the result of the predicate `func`. The
8126
+ * `func` predicate is invoked with the `this` binding and arguments of the
8127
+ * created function.
8128
+ *
8129
+ * @static
8130
+ * @memberOf _
8131
+ * @category Function
8132
+ * @param {Function} predicate The predicate to negate.
8133
+ * @returns {Function} Returns the new function.
8134
+ * @example
8135
+ *
8136
+ * function isEven(n) {
8137
+ * return n % 2 == 0;
8138
+ * }
8139
+ *
8140
+ * _.filter([1, 2, 3, 4, 5, 6], _.negate(isEven));
8141
+ * // => [1, 3, 5]
8142
+ */
8143
+ function negate(predicate) {
8144
+ if (typeof predicate != 'function') {
8145
+ throw new TypeError(FUNC_ERROR_TEXT);
8146
+ }
8147
+ return function() {
8148
+ return !predicate.apply(this, arguments);
8149
+ };
8150
+ }
8151
+
8152
+ /**
8153
+ * Creates a function that is restricted to invoking `func` once. Repeat calls
8154
+ * to the function return the value of the first call. The `func` is invoked
8155
+ * with the `this` binding and arguments of the created function.
8156
+ *
8157
+ * @static
8158
+ * @memberOf _
8159
+ * @category Function
8160
+ * @param {Function} func The function to restrict.
8161
+ * @returns {Function} Returns the new restricted function.
8162
+ * @example
8163
+ *
8164
+ * var initialize = _.once(createApplication);
8165
+ * initialize();
8166
+ * initialize();
8167
+ * // `initialize` invokes `createApplication` once
8168
+ */
8169
+ function once(func) {
8170
+ return before(2, func);
8171
+ }
8172
+
8173
+ /**
8174
+ * Creates a function that invokes `func` with `partial` arguments prepended
8175
+ * to those provided to the new function. This method is like `_.bind` except
8176
+ * it does **not** alter the `this` binding.
8177
+ *
8178
+ * The `_.partial.placeholder` value, which defaults to `_` in monolithic
8179
+ * builds, may be used as a placeholder for partially applied arguments.
8180
+ *
8181
+ * **Note:** This method does not set the "length" property of partially
8182
+ * applied functions.
8183
+ *
8184
+ * @static
8185
+ * @memberOf _
8186
+ * @category Function
8187
+ * @param {Function} func The function to partially apply arguments to.
8188
+ * @param {...*} [partials] The arguments to be partially applied.
8189
+ * @returns {Function} Returns the new partially applied function.
8190
+ * @example
8191
+ *
8192
+ * var greet = function(greeting, name) {
8193
+ * return greeting + ' ' + name;
8194
+ * };
8195
+ *
8196
+ * var sayHelloTo = _.partial(greet, 'hello');
8197
+ * sayHelloTo('fred');
8198
+ * // => 'hello fred'
8199
+ *
8200
+ * // using placeholders
8201
+ * var greetFred = _.partial(greet, _, 'fred');
8202
+ * greetFred('hi');
8203
+ * // => 'hi fred'
8204
+ */
8205
+ var partial = createPartial(PARTIAL_FLAG);
8206
+
8207
+ /**
8208
+ * This method is like `_.partial` except that partially applied arguments
8209
+ * are appended to those provided to the new function.
8210
+ *
8211
+ * The `_.partialRight.placeholder` value, which defaults to `_` in monolithic
8212
+ * builds, may be used as a placeholder for partially applied arguments.
8213
+ *
8214
+ * **Note:** This method does not set the "length" property of partially
8215
+ * applied functions.
8216
+ *
8217
+ * @static
8218
+ * @memberOf _
8219
+ * @category Function
8220
+ * @param {Function} func The function to partially apply arguments to.
8221
+ * @param {...*} [partials] The arguments to be partially applied.
8222
+ * @returns {Function} Returns the new partially applied function.
8223
+ * @example
8224
+ *
8225
+ * var greet = function(greeting, name) {
8226
+ * return greeting + ' ' + name;
8227
+ * };
8228
+ *
8229
+ * var greetFred = _.partialRight(greet, 'fred');
8230
+ * greetFred('hi');
8231
+ * // => 'hi fred'
8232
+ *
8233
+ * // using placeholders
8234
+ * var sayHelloTo = _.partialRight(greet, 'hello', _);
8235
+ * sayHelloTo('fred');
8236
+ * // => 'hello fred'
8237
+ */
8238
+ var partialRight = createPartial(PARTIAL_RIGHT_FLAG);
8239
+
8240
+ /**
8241
+ * Creates a function that invokes `func` with arguments arranged according
8242
+ * to the specified indexes where the argument value at the first index is
8243
+ * provided as the first argument, the argument value at the second index is
8244
+ * provided as the second argument, and so on.
8245
+ *
8246
+ * @static
8247
+ * @memberOf _
8248
+ * @category Function
8249
+ * @param {Function} func The function to rearrange arguments for.
8250
+ * @param {...(number|number[])} indexes The arranged argument indexes,
8251
+ * specified as individual indexes or arrays of indexes.
8252
+ * @returns {Function} Returns the new function.
8253
+ * @example
8254
+ *
8255
+ * var rearged = _.rearg(function(a, b, c) {
8256
+ * return [a, b, c];
8257
+ * }, 2, 0, 1);
8258
+ *
8259
+ * rearged('b', 'c', 'a')
8260
+ * // => ['a', 'b', 'c']
8261
+ *
8262
+ * var map = _.rearg(_.map, [1, 0]);
8263
+ * map(function(n) {
8264
+ * return n * 3;
8265
+ * }, [1, 2, 3]);
8266
+ * // => [3, 6, 9]
8267
+ */
8268
+ var rearg = restParam(function(func, indexes) {
8269
+ return createWrapper(func, REARG_FLAG, undefined, undefined, undefined, baseFlatten(indexes));
8270
+ });
8271
+
8272
+ /**
8273
+ * Creates a function that invokes `func` with the `this` binding of the
8274
+ * created function and arguments from `start` and beyond provided as an array.
8275
+ *
8276
+ * **Note:** This method is based on the [rest parameter](https://developer.mozilla.org/Web/JavaScript/Reference/Functions/rest_parameters).
8277
+ *
8278
+ * @static
8279
+ * @memberOf _
8280
+ * @category Function
8281
+ * @param {Function} func The function to apply a rest parameter to.
8282
+ * @param {number} [start=func.length-1] The start position of the rest parameter.
8283
+ * @returns {Function} Returns the new function.
8284
+ * @example
8285
+ *
8286
+ * var say = _.restParam(function(what, names) {
8287
+ * return what + ' ' + _.initial(names).join(', ') +
8288
+ * (_.size(names) > 1 ? ', & ' : '') + _.last(names);
8289
+ * });
8290
+ *
8291
+ * say('hello', 'fred', 'barney', 'pebbles');
8292
+ * // => 'hello fred, barney, & pebbles'
8293
+ */
8294
+ function restParam(func, start) {
8295
+ if (typeof func != 'function') {
8296
+ throw new TypeError(FUNC_ERROR_TEXT);
8297
+ }
8298
+ start = nativeMax(start === undefined ? (func.length - 1) : (+start || 0), 0);
8299
+ return function() {
8300
+ var args = arguments,
8301
+ index = -1,
8302
+ length = nativeMax(args.length - start, 0),
8303
+ rest = Array(length);
8304
+
8305
+ while (++index < length) {
8306
+ rest[index] = args[start + index];
8307
+ }
8308
+ switch (start) {
8309
+ case 0: return func.call(this, rest);
8310
+ case 1: return func.call(this, args[0], rest);
8311
+ case 2: return func.call(this, args[0], args[1], rest);
8312
+ }
8313
+ var otherArgs = Array(start + 1);
8314
+ index = -1;
8315
+ while (++index < start) {
8316
+ otherArgs[index] = args[index];
8317
+ }
8318
+ otherArgs[start] = rest;
8319
+ return func.apply(this, otherArgs);
8320
+ };
8321
+ }
8322
+
8323
+ /**
8324
+ * Creates a function that invokes `func` with the `this` binding of the created
8325
+ * function and an array of arguments much like [`Function#apply`](https://es5.github.io/#x15.3.4.3).
8326
+ *
8327
+ * **Note:** This method is based on the [spread operator](https://developer.mozilla.org/Web/JavaScript/Reference/Operators/Spread_operator).
8328
+ *
8329
+ * @static
8330
+ * @memberOf _
8331
+ * @category Function
8332
+ * @param {Function} func The function to spread arguments over.
8333
+ * @returns {Function} Returns the new function.
8334
+ * @example
8335
+ *
8336
+ * var say = _.spread(function(who, what) {
8337
+ * return who + ' says ' + what;
8338
+ * });
8339
+ *
8340
+ * say(['fred', 'hello']);
8341
+ * // => 'fred says hello'
8342
+ *
8343
+ * // with a Promise
8344
+ * var numbers = Promise.all([
8345
+ * Promise.resolve(40),
8346
+ * Promise.resolve(36)
8347
+ * ]);
8348
+ *
8349
+ * numbers.then(_.spread(function(x, y) {
8350
+ * return x + y;
8351
+ * }));
8352
+ * // => a Promise of 76
8353
+ */
8354
+ function spread(func) {
8355
+ if (typeof func != 'function') {
8356
+ throw new TypeError(FUNC_ERROR_TEXT);
8357
+ }
8358
+ return function(array) {
8359
+ return func.apply(this, array);
8360
+ };
8361
+ }
8362
+
8363
+ /**
8364
+ * Creates a throttled function that only invokes `func` at most once per
8365
+ * every `wait` milliseconds. The throttled function comes with a `cancel`
8366
+ * method to cancel delayed invocations. Provide an options object to indicate
8367
+ * that `func` should be invoked on the leading and/or trailing edge of the
8368
+ * `wait` timeout. Subsequent calls to the throttled function return the
8369
+ * result of the last `func` call.
8370
+ *
8371
+ * **Note:** If `leading` and `trailing` options are `true`, `func` is invoked
8372
+ * on the trailing edge of the timeout only if the the throttled function is
8373
+ * invoked more than once during the `wait` timeout.
8374
+ *
8375
+ * See [David Corbacho's article](http://drupalmotion.com/article/debounce-and-throttle-visual-explanation)
8376
+ * for details over the differences between `_.throttle` and `_.debounce`.
8377
+ *
8378
+ * @static
8379
+ * @memberOf _
8380
+ * @category Function
8381
+ * @param {Function} func The function to throttle.
8382
+ * @param {number} [wait=0] The number of milliseconds to throttle invocations to.
8383
+ * @param {Object} [options] The options object.
8384
+ * @param {boolean} [options.leading=true] Specify invoking on the leading
8385
+ * edge of the timeout.
8386
+ * @param {boolean} [options.trailing=true] Specify invoking on the trailing
8387
+ * edge of the timeout.
8388
+ * @returns {Function} Returns the new throttled function.
8389
+ * @example
8390
+ *
8391
+ * // avoid excessively updating the position while scrolling
8392
+ * jQuery(window).on('scroll', _.throttle(updatePosition, 100));
8393
+ *
8394
+ * // invoke `renewToken` when the click event is fired, but not more than once every 5 minutes
8395
+ * jQuery('.interactive').on('click', _.throttle(renewToken, 300000, {
8396
+ * 'trailing': false
8397
+ * }));
8398
+ *
8399
+ * // cancel a trailing throttled call
8400
+ * jQuery(window).on('popstate', throttled.cancel);
8401
+ */
8402
+ function throttle(func, wait, options) {
8403
+ var leading = true,
8404
+ trailing = true;
8405
+
8406
+ if (typeof func != 'function') {
8407
+ throw new TypeError(FUNC_ERROR_TEXT);
8408
+ }
8409
+ if (options === false) {
8410
+ leading = false;
8411
+ } else if (isObject(options)) {
8412
+ leading = 'leading' in options ? !!options.leading : leading;
8413
+ trailing = 'trailing' in options ? !!options.trailing : trailing;
8414
+ }
8415
+ return debounce(func, wait, { 'leading': leading, 'maxWait': +wait, 'trailing': trailing });
8416
+ }
8417
+
8418
+ /**
8419
+ * Creates a function that provides `value` to the wrapper function as its
8420
+ * first argument. Any additional arguments provided to the function are
8421
+ * appended to those provided to the wrapper function. The wrapper is invoked
8422
+ * with the `this` binding of the created function.
8423
+ *
8424
+ * @static
8425
+ * @memberOf _
8426
+ * @category Function
8427
+ * @param {*} value The value to wrap.
8428
+ * @param {Function} wrapper The wrapper function.
8429
+ * @returns {Function} Returns the new function.
8430
+ * @example
8431
+ *
8432
+ * var p = _.wrap(_.escape, function(func, text) {
8433
+ * return '<p>' + func(text) + '</p>';
8434
+ * });
8435
+ *
8436
+ * p('fred, barney, & pebbles');
8437
+ * // => '<p>fred, barney, &amp; pebbles</p>'
8438
+ */
8439
+ function wrap(value, wrapper) {
8440
+ wrapper = wrapper == null ? identity : wrapper;
8441
+ return createWrapper(wrapper, PARTIAL_FLAG, undefined, [value], []);
8442
+ }
8443
+
8444
+ /*------------------------------------------------------------------------*/
8445
+
8446
+ /**
8447
+ * Creates a clone of `value`. If `isDeep` is `true` nested objects are cloned,
8448
+ * otherwise they are assigned by reference. If `customizer` is provided it's
8449
+ * invoked to produce the cloned values. If `customizer` returns `undefined`
8450
+ * cloning is handled by the method instead. The `customizer` is bound to
8451
+ * `thisArg` and invoked with up to three argument; (value [, index|key, object]).
8452
+ *
8453
+ * **Note:** This method is loosely based on the
8454
+ * [structured clone algorithm](http://www.w3.org/TR/html5/infrastructure.html#internal-structured-cloning-algorithm).
8455
+ * The enumerable properties of `arguments` objects and objects created by
8456
+ * constructors other than `Object` are cloned to plain `Object` objects. An
8457
+ * empty object is returned for uncloneable values such as functions, DOM nodes,
8458
+ * Maps, Sets, and WeakMaps.
8459
+ *
8460
+ * @static
8461
+ * @memberOf _
8462
+ * @category Lang
8463
+ * @param {*} value The value to clone.
8464
+ * @param {boolean} [isDeep] Specify a deep clone.
8465
+ * @param {Function} [customizer] The function to customize cloning values.
8466
+ * @param {*} [thisArg] The `this` binding of `customizer`.
8467
+ * @returns {*} Returns the cloned value.
8468
+ * @example
8469
+ *
8470
+ * var users = [
8471
+ * { 'user': 'barney' },
8472
+ * { 'user': 'fred' }
8473
+ * ];
8474
+ *
8475
+ * var shallow = _.clone(users);
8476
+ * shallow[0] === users[0];
8477
+ * // => true
8478
+ *
8479
+ * var deep = _.clone(users, true);
8480
+ * deep[0] === users[0];
8481
+ * // => false
8482
+ *
8483
+ * // using a customizer callback
8484
+ * var el = _.clone(document.body, function(value) {
8485
+ * if (_.isElement(value)) {
8486
+ * return value.cloneNode(false);
8487
+ * }
8488
+ * });
8489
+ *
8490
+ * el === document.body
8491
+ * // => false
8492
+ * el.nodeName
8493
+ * // => BODY
8494
+ * el.childNodes.length;
8495
+ * // => 0
8496
+ */
8497
+ function clone(value, isDeep, customizer, thisArg) {
8498
+ if (isDeep && typeof isDeep != 'boolean' && isIterateeCall(value, isDeep, customizer)) {
8499
+ isDeep = false;
8500
+ }
8501
+ else if (typeof isDeep == 'function') {
8502
+ thisArg = customizer;
8503
+ customizer = isDeep;
8504
+ isDeep = false;
8505
+ }
8506
+ return typeof customizer == 'function'
8507
+ ? baseClone(value, isDeep, bindCallback(customizer, thisArg, 3))
8508
+ : baseClone(value, isDeep);
8509
+ }
8510
+
8511
+ /**
8512
+ * Creates a deep clone of `value`. If `customizer` is provided it's invoked
8513
+ * to produce the cloned values. If `customizer` returns `undefined` cloning
8514
+ * is handled by the method instead. The `customizer` is bound to `thisArg`
8515
+ * and invoked with up to three argument; (value [, index|key, object]).
8516
+ *
8517
+ * **Note:** This method is loosely based on the
8518
+ * [structured clone algorithm](http://www.w3.org/TR/html5/infrastructure.html#internal-structured-cloning-algorithm).
8519
+ * The enumerable properties of `arguments` objects and objects created by
8520
+ * constructors other than `Object` are cloned to plain `Object` objects. An
8521
+ * empty object is returned for uncloneable values such as functions, DOM nodes,
8522
+ * Maps, Sets, and WeakMaps.
8523
+ *
8524
+ * @static
8525
+ * @memberOf _
8526
+ * @category Lang
8527
+ * @param {*} value The value to deep clone.
8528
+ * @param {Function} [customizer] The function to customize cloning values.
8529
+ * @param {*} [thisArg] The `this` binding of `customizer`.
8530
+ * @returns {*} Returns the deep cloned value.
8531
+ * @example
8532
+ *
8533
+ * var users = [
8534
+ * { 'user': 'barney' },
8535
+ * { 'user': 'fred' }
8536
+ * ];
8537
+ *
8538
+ * var deep = _.cloneDeep(users);
8539
+ * deep[0] === users[0];
8540
+ * // => false
8541
+ *
8542
+ * // using a customizer callback
8543
+ * var el = _.cloneDeep(document.body, function(value) {
8544
+ * if (_.isElement(value)) {
8545
+ * return value.cloneNode(true);
8546
+ * }
8547
+ * });
8548
+ *
8549
+ * el === document.body
8550
+ * // => false
8551
+ * el.nodeName
8552
+ * // => BODY
8553
+ * el.childNodes.length;
8554
+ * // => 20
8555
+ */
8556
+ function cloneDeep(value, customizer, thisArg) {
8557
+ return typeof customizer == 'function'
8558
+ ? baseClone(value, true, bindCallback(customizer, thisArg, 3))
8559
+ : baseClone(value, true);
8560
+ }
8561
+
8562
+ /**
8563
+ * Checks if `value` is greater than `other`.
8564
+ *
8565
+ * @static
8566
+ * @memberOf _
8567
+ * @category Lang
8568
+ * @param {*} value The value to compare.
8569
+ * @param {*} other The other value to compare.
8570
+ * @returns {boolean} Returns `true` if `value` is greater than `other`, else `false`.
8571
+ * @example
8572
+ *
8573
+ * _.gt(3, 1);
8574
+ * // => true
8575
+ *
8576
+ * _.gt(3, 3);
8577
+ * // => false
8578
+ *
8579
+ * _.gt(1, 3);
8580
+ * // => false
8581
+ */
8582
+ function gt(value, other) {
8583
+ return value > other;
8584
+ }
8585
+
8586
+ /**
8587
+ * Checks if `value` is greater than or equal to `other`.
8588
+ *
8589
+ * @static
8590
+ * @memberOf _
8591
+ * @category Lang
8592
+ * @param {*} value The value to compare.
8593
+ * @param {*} other The other value to compare.
8594
+ * @returns {boolean} Returns `true` if `value` is greater than or equal to `other`, else `false`.
8595
+ * @example
8596
+ *
8597
+ * _.gte(3, 1);
8598
+ * // => true
8599
+ *
8600
+ * _.gte(3, 3);
8601
+ * // => true
8602
+ *
8603
+ * _.gte(1, 3);
8604
+ * // => false
8605
+ */
8606
+ function gte(value, other) {
8607
+ return value >= other;
8608
+ }
8609
+
8610
+ /**
8611
+ * Checks if `value` is classified as an `arguments` object.
8612
+ *
8613
+ * @static
8614
+ * @memberOf _
8615
+ * @category Lang
8616
+ * @param {*} value The value to check.
8617
+ * @returns {boolean} Returns `true` if `value` is correctly classified, else `false`.
8618
+ * @example
8619
+ *
8620
+ * _.isArguments(function() { return arguments; }());
8621
+ * // => true
8622
+ *
8623
+ * _.isArguments([1, 2, 3]);
8624
+ * // => false
8625
+ */
8626
+ function isArguments(value) {
8627
+ return isObjectLike(value) && isArrayLike(value) &&
8628
+ hasOwnProperty.call(value, 'callee') && !propertyIsEnumerable.call(value, 'callee');
8629
+ }
8630
+
8631
+ /**
8632
+ * Checks if `value` is classified as an `Array` object.
8633
+ *
8634
+ * @static
8635
+ * @memberOf _
8636
+ * @category Lang
8637
+ * @param {*} value The value to check.
8638
+ * @returns {boolean} Returns `true` if `value` is correctly classified, else `false`.
8639
+ * @example
8640
+ *
8641
+ * _.isArray([1, 2, 3]);
8642
+ * // => true
8643
+ *
8644
+ * _.isArray(function() { return arguments; }());
8645
+ * // => false
8646
+ */
8647
+ var isArray = nativeIsArray || function(value) {
8648
+ return isObjectLike(value) && isLength(value.length) && objToString.call(value) == arrayTag;
8649
+ };
8650
+
8651
+ /**
8652
+ * Checks if `value` is classified as a boolean primitive or object.
8653
+ *
8654
+ * @static
8655
+ * @memberOf _
8656
+ * @category Lang
8657
+ * @param {*} value The value to check.
8658
+ * @returns {boolean} Returns `true` if `value` is correctly classified, else `false`.
8659
+ * @example
8660
+ *
8661
+ * _.isBoolean(false);
8662
+ * // => true
8663
+ *
8664
+ * _.isBoolean(null);
8665
+ * // => false
8666
+ */
8667
+ function isBoolean(value) {
8668
+ return value === true || value === false || (isObjectLike(value) && objToString.call(value) == boolTag);
8669
+ }
8670
+
8671
+ /**
8672
+ * Checks if `value` is classified as a `Date` object.
8673
+ *
8674
+ * @static
8675
+ * @memberOf _
8676
+ * @category Lang
8677
+ * @param {*} value The value to check.
8678
+ * @returns {boolean} Returns `true` if `value` is correctly classified, else `false`.
8679
+ * @example
8680
+ *
8681
+ * _.isDate(new Date);
8682
+ * // => true
8683
+ *
8684
+ * _.isDate('Mon April 23 2012');
8685
+ * // => false
8686
+ */
8687
+ function isDate(value) {
8688
+ return isObjectLike(value) && objToString.call(value) == dateTag;
8689
+ }
8690
+
8691
+ /**
8692
+ * Checks if `value` is a DOM element.
8693
+ *
8694
+ * @static
8695
+ * @memberOf _
8696
+ * @category Lang
8697
+ * @param {*} value The value to check.
8698
+ * @returns {boolean} Returns `true` if `value` is a DOM element, else `false`.
8699
+ * @example
8700
+ *
8701
+ * _.isElement(document.body);
8702
+ * // => true
8703
+ *
8704
+ * _.isElement('<body>');
8705
+ * // => false
8706
+ */
8707
+ function isElement(value) {
8708
+ return !!value && value.nodeType === 1 && isObjectLike(value) && !isPlainObject(value);
8709
+ }
8710
+
8711
+ /**
8712
+ * Checks if `value` is empty. A value is considered empty unless it's an
8713
+ * `arguments` object, array, string, or jQuery-like collection with a length
8714
+ * greater than `0` or an object with own enumerable properties.
8715
+ *
8716
+ * @static
8717
+ * @memberOf _
8718
+ * @category Lang
8719
+ * @param {Array|Object|string} value The value to inspect.
8720
+ * @returns {boolean} Returns `true` if `value` is empty, else `false`.
8721
+ * @example
8722
+ *
8723
+ * _.isEmpty(null);
8724
+ * // => true
8725
+ *
8726
+ * _.isEmpty(true);
8727
+ * // => true
8728
+ *
8729
+ * _.isEmpty(1);
8730
+ * // => true
8731
+ *
8732
+ * _.isEmpty([1, 2, 3]);
8733
+ * // => false
8734
+ *
8735
+ * _.isEmpty({ 'a': 1 });
8736
+ * // => false
8737
+ */
8738
+ function isEmpty(value) {
8739
+ if (value == null) {
8740
+ return true;
8741
+ }
8742
+ if (isArrayLike(value) && (isArray(value) || isString(value) || isArguments(value) ||
8743
+ (isObjectLike(value) && isFunction(value.splice)))) {
8744
+ return !value.length;
8745
+ }
8746
+ return !keys(value).length;
8747
+ }
8748
+
8749
+ /**
8750
+ * Performs a deep comparison between two values to determine if they are
8751
+ * equivalent. If `customizer` is provided it's invoked to compare values.
8752
+ * If `customizer` returns `undefined` comparisons are handled by the method
8753
+ * instead. The `customizer` is bound to `thisArg` and invoked with up to
8754
+ * three arguments: (value, other [, index|key]).
8755
+ *
8756
+ * **Note:** This method supports comparing arrays, booleans, `Date` objects,
8757
+ * numbers, `Object` objects, regexes, and strings. Objects are compared by
8758
+ * their own, not inherited, enumerable properties. Functions and DOM nodes
8759
+ * are **not** supported. Provide a customizer function to extend support
8760
+ * for comparing other values.
8761
+ *
8762
+ * @static
8763
+ * @memberOf _
8764
+ * @alias eq
8765
+ * @category Lang
8766
+ * @param {*} value The value to compare.
8767
+ * @param {*} other The other value to compare.
8768
+ * @param {Function} [customizer] The function to customize value comparisons.
8769
+ * @param {*} [thisArg] The `this` binding of `customizer`.
8770
+ * @returns {boolean} Returns `true` if the values are equivalent, else `false`.
8771
+ * @example
8772
+ *
8773
+ * var object = { 'user': 'fred' };
8774
+ * var other = { 'user': 'fred' };
8775
+ *
8776
+ * object == other;
8777
+ * // => false
8778
+ *
8779
+ * _.isEqual(object, other);
8780
+ * // => true
8781
+ *
8782
+ * // using a customizer callback
8783
+ * var array = ['hello', 'goodbye'];
8784
+ * var other = ['hi', 'goodbye'];
8785
+ *
8786
+ * _.isEqual(array, other, function(value, other) {
8787
+ * if (_.every([value, other], RegExp.prototype.test, /^h(?:i|ello)$/)) {
8788
+ * return true;
8789
+ * }
8790
+ * });
8791
+ * // => true
8792
+ */
8793
+ function isEqual(value, other, customizer, thisArg) {
8794
+ customizer = typeof customizer == 'function' ? bindCallback(customizer, thisArg, 3) : undefined;
8795
+ var result = customizer ? customizer(value, other) : undefined;
8796
+ return result === undefined ? baseIsEqual(value, other, customizer) : !!result;
8797
+ }
8798
+
8799
+ /**
8800
+ * Checks if `value` is an `Error`, `EvalError`, `RangeError`, `ReferenceError`,
8801
+ * `SyntaxError`, `TypeError`, or `URIError` object.
8802
+ *
8803
+ * @static
8804
+ * @memberOf _
8805
+ * @category Lang
8806
+ * @param {*} value The value to check.
8807
+ * @returns {boolean} Returns `true` if `value` is an error object, else `false`.
8808
+ * @example
8809
+ *
8810
+ * _.isError(new Error);
8811
+ * // => true
8812
+ *
8813
+ * _.isError(Error);
8814
+ * // => false
8815
+ */
8816
+ function isError(value) {
8817
+ return isObjectLike(value) && typeof value.message == 'string' && objToString.call(value) == errorTag;
8818
+ }
8819
+
8820
+ /**
8821
+ * Checks if `value` is a finite primitive number.
8822
+ *
8823
+ * **Note:** This method is based on [`Number.isFinite`](http://ecma-international.org/ecma-262/6.0/#sec-number.isfinite).
8824
+ *
8825
+ * @static
8826
+ * @memberOf _
8827
+ * @category Lang
8828
+ * @param {*} value The value to check.
8829
+ * @returns {boolean} Returns `true` if `value` is a finite number, else `false`.
8830
+ * @example
8831
+ *
8832
+ * _.isFinite(10);
8833
+ * // => true
8834
+ *
8835
+ * _.isFinite('10');
8836
+ * // => false
8837
+ *
8838
+ * _.isFinite(true);
8839
+ * // => false
8840
+ *
8841
+ * _.isFinite(Object(10));
8842
+ * // => false
8843
+ *
8844
+ * _.isFinite(Infinity);
8845
+ * // => false
8846
+ */
8847
+ function isFinite(value) {
8848
+ return typeof value == 'number' && nativeIsFinite(value);
8849
+ }
8850
+
8851
+ /**
8852
+ * Checks if `value` is classified as a `Function` object.
8853
+ *
8854
+ * @static
8855
+ * @memberOf _
8856
+ * @category Lang
8857
+ * @param {*} value The value to check.
8858
+ * @returns {boolean} Returns `true` if `value` is correctly classified, else `false`.
8859
+ * @example
8860
+ *
8861
+ * _.isFunction(_);
8862
+ * // => true
8863
+ *
8864
+ * _.isFunction(/abc/);
8865
+ * // => false
8866
+ */
8867
+ function isFunction(value) {
8868
+ // The use of `Object#toString` avoids issues with the `typeof` operator
8869
+ // in older versions of Chrome and Safari which return 'function' for regexes
8870
+ // and Safari 8 which returns 'object' for typed array constructors.
8871
+ return isObject(value) && objToString.call(value) == funcTag;
8872
+ }
8873
+
8874
+ /**
8875
+ * Checks if `value` is the [language type](https://es5.github.io/#x8) of `Object`.
8876
+ * (e.g. arrays, functions, objects, regexes, `new Number(0)`, and `new String('')`)
8877
+ *
8878
+ * @static
8879
+ * @memberOf _
8880
+ * @category Lang
8881
+ * @param {*} value The value to check.
8882
+ * @returns {boolean} Returns `true` if `value` is an object, else `false`.
8883
+ * @example
8884
+ *
8885
+ * _.isObject({});
8886
+ * // => true
8887
+ *
8888
+ * _.isObject([1, 2, 3]);
8889
+ * // => true
8890
+ *
8891
+ * _.isObject(1);
8892
+ * // => false
8893
+ */
8894
+ function isObject(value) {
8895
+ // Avoid a V8 JIT bug in Chrome 19-20.
8896
+ // See https://code.google.com/p/v8/issues/detail?id=2291 for more details.
8897
+ var type = typeof value;
8898
+ return !!value && (type == 'object' || type == 'function');
8899
+ }
8900
+
8901
+ /**
8902
+ * Performs a deep comparison between `object` and `source` to determine if
8903
+ * `object` contains equivalent property values. If `customizer` is provided
8904
+ * it's invoked to compare values. If `customizer` returns `undefined`
8905
+ * comparisons are handled by the method instead. The `customizer` is bound
8906
+ * to `thisArg` and invoked with three arguments: (value, other, index|key).
8907
+ *
8908
+ * **Note:** This method supports comparing properties of arrays, booleans,
8909
+ * `Date` objects, numbers, `Object` objects, regexes, and strings. Functions
8910
+ * and DOM nodes are **not** supported. Provide a customizer function to extend
8911
+ * support for comparing other values.
8912
+ *
8913
+ * @static
8914
+ * @memberOf _
8915
+ * @category Lang
8916
+ * @param {Object} object The object to inspect.
8917
+ * @param {Object} source The object of property values to match.
8918
+ * @param {Function} [customizer] The function to customize value comparisons.
8919
+ * @param {*} [thisArg] The `this` binding of `customizer`.
8920
+ * @returns {boolean} Returns `true` if `object` is a match, else `false`.
8921
+ * @example
8922
+ *
8923
+ * var object = { 'user': 'fred', 'age': 40 };
8924
+ *
8925
+ * _.isMatch(object, { 'age': 40 });
8926
+ * // => true
8927
+ *
8928
+ * _.isMatch(object, { 'age': 36 });
8929
+ * // => false
8930
+ *
8931
+ * // using a customizer callback
8932
+ * var object = { 'greeting': 'hello' };
8933
+ * var source = { 'greeting': 'hi' };
8934
+ *
8935
+ * _.isMatch(object, source, function(value, other) {
8936
+ * return _.every([value, other], RegExp.prototype.test, /^h(?:i|ello)$/) || undefined;
8937
+ * });
8938
+ * // => true
8939
+ */
8940
+ function isMatch(object, source, customizer, thisArg) {
8941
+ customizer = typeof customizer == 'function' ? bindCallback(customizer, thisArg, 3) : undefined;
8942
+ return baseIsMatch(object, getMatchData(source), customizer);
8943
+ }
8944
+
8945
+ /**
8946
+ * Checks if `value` is `NaN`.
8947
+ *
8948
+ * **Note:** This method is not the same as [`isNaN`](https://es5.github.io/#x15.1.2.4)
8949
+ * which returns `true` for `undefined` and other non-numeric values.
8950
+ *
8951
+ * @static
8952
+ * @memberOf _
8953
+ * @category Lang
8954
+ * @param {*} value The value to check.
8955
+ * @returns {boolean} Returns `true` if `value` is `NaN`, else `false`.
8956
+ * @example
8957
+ *
8958
+ * _.isNaN(NaN);
8959
+ * // => true
8960
+ *
8961
+ * _.isNaN(new Number(NaN));
8962
+ * // => true
8963
+ *
8964
+ * isNaN(undefined);
8965
+ * // => true
8966
+ *
8967
+ * _.isNaN(undefined);
8968
+ * // => false
8969
+ */
8970
+ function isNaN(value) {
8971
+ // An `NaN` primitive is the only value that is not equal to itself.
8972
+ // Perform the `toStringTag` check first to avoid errors with some host objects in IE.
8973
+ return isNumber(value) && value != +value;
8974
+ }
8975
+
8976
+ /**
8977
+ * Checks if `value` is a native function.
8978
+ *
8979
+ * @static
8980
+ * @memberOf _
8981
+ * @category Lang
8982
+ * @param {*} value The value to check.
8983
+ * @returns {boolean} Returns `true` if `value` is a native function, else `false`.
8984
+ * @example
8985
+ *
8986
+ * _.isNative(Array.prototype.push);
8987
+ * // => true
8988
+ *
8989
+ * _.isNative(_);
8990
+ * // => false
8991
+ */
8992
+ function isNative(value) {
8993
+ if (value == null) {
8994
+ return false;
8995
+ }
8996
+ if (isFunction(value)) {
8997
+ return reIsNative.test(fnToString.call(value));
8998
+ }
8999
+ return isObjectLike(value) && (isHostObject(value) ? reIsNative : reIsHostCtor).test(value);
9000
+ }
9001
+
9002
+ /**
9003
+ * Checks if `value` is `null`.
9004
+ *
9005
+ * @static
9006
+ * @memberOf _
9007
+ * @category Lang
9008
+ * @param {*} value The value to check.
9009
+ * @returns {boolean} Returns `true` if `value` is `null`, else `false`.
9010
+ * @example
9011
+ *
9012
+ * _.isNull(null);
9013
+ * // => true
9014
+ *
9015
+ * _.isNull(void 0);
9016
+ * // => false
9017
+ */
9018
+ function isNull(value) {
9019
+ return value === null;
9020
+ }
9021
+
9022
+ /**
9023
+ * Checks if `value` is classified as a `Number` primitive or object.
9024
+ *
9025
+ * **Note:** To exclude `Infinity`, `-Infinity`, and `NaN`, which are classified
9026
+ * as numbers, use the `_.isFinite` method.
9027
+ *
9028
+ * @static
9029
+ * @memberOf _
9030
+ * @category Lang
9031
+ * @param {*} value The value to check.
9032
+ * @returns {boolean} Returns `true` if `value` is correctly classified, else `false`.
9033
+ * @example
9034
+ *
9035
+ * _.isNumber(8.4);
9036
+ * // => true
9037
+ *
9038
+ * _.isNumber(NaN);
9039
+ * // => true
9040
+ *
9041
+ * _.isNumber('8.4');
9042
+ * // => false
9043
+ */
9044
+ function isNumber(value) {
9045
+ return typeof value == 'number' || (isObjectLike(value) && objToString.call(value) == numberTag);
9046
+ }
9047
+
9048
+ /**
9049
+ * Checks if `value` is a plain object, that is, an object created by the
9050
+ * `Object` constructor or one with a `[[Prototype]]` of `null`.
9051
+ *
9052
+ * **Note:** This method assumes objects created by the `Object` constructor
9053
+ * have no inherited enumerable properties.
9054
+ *
9055
+ * @static
9056
+ * @memberOf _
9057
+ * @category Lang
9058
+ * @param {*} value The value to check.
9059
+ * @returns {boolean} Returns `true` if `value` is a plain object, else `false`.
9060
+ * @example
9061
+ *
9062
+ * function Foo() {
9063
+ * this.a = 1;
9064
+ * }
9065
+ *
9066
+ * _.isPlainObject(new Foo);
9067
+ * // => false
9068
+ *
9069
+ * _.isPlainObject([1, 2, 3]);
9070
+ * // => false
9071
+ *
9072
+ * _.isPlainObject({ 'x': 0, 'y': 0 });
9073
+ * // => true
9074
+ *
9075
+ * _.isPlainObject(Object.create(null));
9076
+ * // => true
9077
+ */
9078
+ function isPlainObject(value) {
9079
+ var Ctor;
9080
+
9081
+ // Exit early for non `Object` objects.
9082
+ if (!(isObjectLike(value) && objToString.call(value) == objectTag && !isHostObject(value) && !isArguments(value)) ||
9083
+ (!hasOwnProperty.call(value, 'constructor') && (Ctor = value.constructor, typeof Ctor == 'function' && !(Ctor instanceof Ctor)))) {
9084
+ return false;
9085
+ }
9086
+ // IE < 9 iterates inherited properties before own properties. If the first
9087
+ // iterated property is an object's own property then there are no inherited
9088
+ // enumerable properties.
9089
+ var result;
9090
+ if (lodash.support.ownLast) {
9091
+ baseForIn(value, function(subValue, key, object) {
9092
+ result = hasOwnProperty.call(object, key);
9093
+ return false;
9094
+ });
9095
+ return result !== false;
9096
+ }
9097
+ // In most environments an object's own properties are iterated before
9098
+ // its inherited properties. If the last iterated property is an object's
9099
+ // own property then there are no inherited enumerable properties.
9100
+ baseForIn(value, function(subValue, key) {
9101
+ result = key;
9102
+ });
9103
+ return result === undefined || hasOwnProperty.call(value, result);
9104
+ }
9105
+
9106
+ /**
9107
+ * Checks if `value` is classified as a `RegExp` object.
9108
+ *
9109
+ * @static
9110
+ * @memberOf _
9111
+ * @category Lang
9112
+ * @param {*} value The value to check.
9113
+ * @returns {boolean} Returns `true` if `value` is correctly classified, else `false`.
9114
+ * @example
9115
+ *
9116
+ * _.isRegExp(/abc/);
9117
+ * // => true
9118
+ *
9119
+ * _.isRegExp('/abc/');
9120
+ * // => false
9121
+ */
9122
+ function isRegExp(value) {
9123
+ return isObject(value) && objToString.call(value) == regexpTag;
9124
+ }
9125
+
9126
+ /**
9127
+ * Checks if `value` is classified as a `String` primitive or object.
9128
+ *
9129
+ * @static
9130
+ * @memberOf _
9131
+ * @category Lang
9132
+ * @param {*} value The value to check.
9133
+ * @returns {boolean} Returns `true` if `value` is correctly classified, else `false`.
9134
+ * @example
9135
+ *
9136
+ * _.isString('abc');
9137
+ * // => true
9138
+ *
9139
+ * _.isString(1);
9140
+ * // => false
9141
+ */
9142
+ function isString(value) {
9143
+ return typeof value == 'string' || (isObjectLike(value) && objToString.call(value) == stringTag);
9144
+ }
9145
+
9146
+ /**
9147
+ * Checks if `value` is classified as a typed array.
9148
+ *
9149
+ * @static
9150
+ * @memberOf _
9151
+ * @category Lang
9152
+ * @param {*} value The value to check.
9153
+ * @returns {boolean} Returns `true` if `value` is correctly classified, else `false`.
9154
+ * @example
9155
+ *
9156
+ * _.isTypedArray(new Uint8Array);
9157
+ * // => true
9158
+ *
9159
+ * _.isTypedArray([]);
9160
+ * // => false
9161
+ */
9162
+ function isTypedArray(value) {
9163
+ return isObjectLike(value) && isLength(value.length) && !!typedArrayTags[objToString.call(value)];
9164
+ }
9165
+
9166
+ /**
9167
+ * Checks if `value` is `undefined`.
9168
+ *
9169
+ * @static
9170
+ * @memberOf _
9171
+ * @category Lang
9172
+ * @param {*} value The value to check.
9173
+ * @returns {boolean} Returns `true` if `value` is `undefined`, else `false`.
9174
+ * @example
9175
+ *
9176
+ * _.isUndefined(void 0);
9177
+ * // => true
9178
+ *
9179
+ * _.isUndefined(null);
9180
+ * // => false
9181
+ */
9182
+ function isUndefined(value) {
9183
+ return value === undefined;
9184
+ }
9185
+
9186
+ /**
9187
+ * Checks if `value` is less than `other`.
9188
+ *
9189
+ * @static
9190
+ * @memberOf _
9191
+ * @category Lang
9192
+ * @param {*} value The value to compare.
9193
+ * @param {*} other The other value to compare.
9194
+ * @returns {boolean} Returns `true` if `value` is less than `other`, else `false`.
9195
+ * @example
9196
+ *
9197
+ * _.lt(1, 3);
9198
+ * // => true
9199
+ *
9200
+ * _.lt(3, 3);
9201
+ * // => false
9202
+ *
9203
+ * _.lt(3, 1);
9204
+ * // => false
9205
+ */
9206
+ function lt(value, other) {
9207
+ return value < other;
9208
+ }
9209
+
9210
+ /**
9211
+ * Checks if `value` is less than or equal to `other`.
9212
+ *
9213
+ * @static
9214
+ * @memberOf _
9215
+ * @category Lang
9216
+ * @param {*} value The value to compare.
9217
+ * @param {*} other The other value to compare.
9218
+ * @returns {boolean} Returns `true` if `value` is less than or equal to `other`, else `false`.
9219
+ * @example
9220
+ *
9221
+ * _.lte(1, 3);
9222
+ * // => true
9223
+ *
9224
+ * _.lte(3, 3);
9225
+ * // => true
9226
+ *
9227
+ * _.lte(3, 1);
9228
+ * // => false
9229
+ */
9230
+ function lte(value, other) {
9231
+ return value <= other;
9232
+ }
9233
+
9234
+ /**
9235
+ * Converts `value` to an array.
9236
+ *
9237
+ * @static
9238
+ * @memberOf _
9239
+ * @category Lang
9240
+ * @param {*} value The value to convert.
9241
+ * @returns {Array} Returns the converted array.
9242
+ * @example
9243
+ *
9244
+ * (function() {
9245
+ * return _.toArray(arguments).slice(1);
9246
+ * }(1, 2, 3));
9247
+ * // => [2, 3]
9248
+ */
9249
+ function toArray(value) {
9250
+ var length = value ? getLength(value) : 0;
9251
+ if (!isLength(length)) {
9252
+ return values(value);
9253
+ }
9254
+ if (!length) {
9255
+ return [];
9256
+ }
9257
+ return (lodash.support.unindexedChars && isString(value))
9258
+ ? value.split('')
9259
+ : arrayCopy(value);
9260
+ }
9261
+
9262
+ /**
9263
+ * Converts `value` to a plain object flattening inherited enumerable
9264
+ * properties of `value` to own properties of the plain object.
9265
+ *
9266
+ * @static
9267
+ * @memberOf _
9268
+ * @category Lang
9269
+ * @param {*} value The value to convert.
9270
+ * @returns {Object} Returns the converted plain object.
9271
+ * @example
9272
+ *
9273
+ * function Foo() {
9274
+ * this.b = 2;
9275
+ * }
9276
+ *
9277
+ * Foo.prototype.c = 3;
9278
+ *
9279
+ * _.assign({ 'a': 1 }, new Foo);
9280
+ * // => { 'a': 1, 'b': 2 }
9281
+ *
9282
+ * _.assign({ 'a': 1 }, _.toPlainObject(new Foo));
9283
+ * // => { 'a': 1, 'b': 2, 'c': 3 }
9284
+ */
9285
+ function toPlainObject(value) {
9286
+ return baseCopy(value, keysIn(value));
9287
+ }
9288
+
9289
+ /*------------------------------------------------------------------------*/
9290
+
9291
+ /**
9292
+ * Recursively merges own enumerable properties of the source object(s), that
9293
+ * don't resolve to `undefined` into the destination object. Subsequent sources
9294
+ * overwrite property assignments of previous sources. If `customizer` is
9295
+ * provided it's invoked to produce the merged values of the destination and
9296
+ * source properties. If `customizer` returns `undefined` merging is handled
9297
+ * by the method instead. The `customizer` is bound to `thisArg` and invoked
9298
+ * with five arguments: (objectValue, sourceValue, key, object, source).
9299
+ *
9300
+ * @static
9301
+ * @memberOf _
9302
+ * @category Object
9303
+ * @param {Object} object The destination object.
9304
+ * @param {...Object} [sources] The source objects.
9305
+ * @param {Function} [customizer] The function to customize assigned values.
9306
+ * @param {*} [thisArg] The `this` binding of `customizer`.
9307
+ * @returns {Object} Returns `object`.
9308
+ * @example
9309
+ *
9310
+ * var users = {
9311
+ * 'data': [{ 'user': 'barney' }, { 'user': 'fred' }]
9312
+ * };
9313
+ *
9314
+ * var ages = {
9315
+ * 'data': [{ 'age': 36 }, { 'age': 40 }]
9316
+ * };
9317
+ *
9318
+ * _.merge(users, ages);
9319
+ * // => { 'data': [{ 'user': 'barney', 'age': 36 }, { 'user': 'fred', 'age': 40 }] }
9320
+ *
9321
+ * // using a customizer callback
9322
+ * var object = {
9323
+ * 'fruits': ['apple'],
9324
+ * 'vegetables': ['beet']
9325
+ * };
9326
+ *
9327
+ * var other = {
9328
+ * 'fruits': ['banana'],
9329
+ * 'vegetables': ['carrot']
9330
+ * };
9331
+ *
9332
+ * _.merge(object, other, function(a, b) {
9333
+ * if (_.isArray(a)) {
9334
+ * return a.concat(b);
9335
+ * }
9336
+ * });
9337
+ * // => { 'fruits': ['apple', 'banana'], 'vegetables': ['beet', 'carrot'] }
9338
+ */
9339
+ var merge = createAssigner(baseMerge);
9340
+
9341
+ /**
9342
+ * Assigns own enumerable properties of source object(s) to the destination
9343
+ * object. Subsequent sources overwrite property assignments of previous sources.
9344
+ * If `customizer` is provided it's invoked to produce the assigned values.
9345
+ * The `customizer` is bound to `thisArg` and invoked with five arguments:
9346
+ * (objectValue, sourceValue, key, object, source).
9347
+ *
9348
+ * **Note:** This method mutates `object` and is based on
9349
+ * [`Object.assign`](http://ecma-international.org/ecma-262/6.0/#sec-object.assign).
9350
+ *
9351
+ * @static
9352
+ * @memberOf _
9353
+ * @alias extend
9354
+ * @category Object
9355
+ * @param {Object} object The destination object.
9356
+ * @param {...Object} [sources] The source objects.
9357
+ * @param {Function} [customizer] The function to customize assigned values.
9358
+ * @param {*} [thisArg] The `this` binding of `customizer`.
9359
+ * @returns {Object} Returns `object`.
9360
+ * @example
9361
+ *
9362
+ * _.assign({ 'user': 'barney' }, { 'age': 40 }, { 'user': 'fred' });
9363
+ * // => { 'user': 'fred', 'age': 40 }
9364
+ *
9365
+ * // using a customizer callback
9366
+ * var defaults = _.partialRight(_.assign, function(value, other) {
9367
+ * return _.isUndefined(value) ? other : value;
9368
+ * });
9369
+ *
9370
+ * defaults({ 'user': 'barney' }, { 'age': 36 }, { 'user': 'fred' });
9371
+ * // => { 'user': 'barney', 'age': 36 }
9372
+ */
9373
+ var assign = createAssigner(function(object, source, customizer) {
9374
+ return customizer
9375
+ ? assignWith(object, source, customizer)
9376
+ : baseAssign(object, source);
9377
+ });
9378
+
9379
+ /**
9380
+ * Creates an object that inherits from the given `prototype` object. If a
9381
+ * `properties` object is provided its own enumerable properties are assigned
9382
+ * to the created object.
9383
+ *
9384
+ * @static
9385
+ * @memberOf _
9386
+ * @category Object
9387
+ * @param {Object} prototype The object to inherit from.
9388
+ * @param {Object} [properties] The properties to assign to the object.
9389
+ * @param- {Object} [guard] Enables use as a callback for functions like `_.map`.
9390
+ * @returns {Object} Returns the new object.
9391
+ * @example
9392
+ *
9393
+ * function Shape() {
9394
+ * this.x = 0;
9395
+ * this.y = 0;
9396
+ * }
9397
+ *
9398
+ * function Circle() {
9399
+ * Shape.call(this);
9400
+ * }
9401
+ *
9402
+ * Circle.prototype = _.create(Shape.prototype, {
9403
+ * 'constructor': Circle
9404
+ * });
9405
+ *
9406
+ * var circle = new Circle;
9407
+ * circle instanceof Circle;
9408
+ * // => true
9409
+ *
9410
+ * circle instanceof Shape;
9411
+ * // => true
9412
+ */
9413
+ function create(prototype, properties, guard) {
9414
+ var result = baseCreate(prototype);
9415
+ if (guard && isIterateeCall(prototype, properties, guard)) {
9416
+ properties = undefined;
9417
+ }
9418
+ return properties ? baseAssign(result, properties) : result;
9419
+ }
9420
+
9421
+ /**
9422
+ * Assigns own enumerable properties of source object(s) to the destination
9423
+ * object for all destination properties that resolve to `undefined`. Once a
9424
+ * property is set, additional values of the same property are ignored.
9425
+ *
9426
+ * **Note:** This method mutates `object`.
9427
+ *
9428
+ * @static
9429
+ * @memberOf _
9430
+ * @category Object
9431
+ * @param {Object} object The destination object.
9432
+ * @param {...Object} [sources] The source objects.
9433
+ * @returns {Object} Returns `object`.
9434
+ * @example
9435
+ *
9436
+ * _.defaults({ 'user': 'barney' }, { 'age': 36 }, { 'user': 'fred' });
9437
+ * // => { 'user': 'barney', 'age': 36 }
9438
+ */
9439
+ var defaults = createDefaults(assign, assignDefaults);
9440
+
9441
+ /**
9442
+ * This method is like `_.defaults` except that it recursively assigns
9443
+ * default properties.
9444
+ *
9445
+ * **Note:** This method mutates `object`.
9446
+ *
9447
+ * @static
9448
+ * @memberOf _
9449
+ * @category Object
9450
+ * @param {Object} object The destination object.
9451
+ * @param {...Object} [sources] The source objects.
9452
+ * @returns {Object} Returns `object`.
9453
+ * @example
9454
+ *
9455
+ * _.defaultsDeep({ 'user': { 'name': 'barney' } }, { 'user': { 'name': 'fred', 'age': 36 } });
9456
+ * // => { 'user': { 'name': 'barney', 'age': 36 } }
9457
+ *
9458
+ */
9459
+ var defaultsDeep = createDefaults(merge, mergeDefaults);
9460
+
9461
+ /**
9462
+ * This method is like `_.find` except that it returns the key of the first
9463
+ * element `predicate` returns truthy for instead of the element itself.
9464
+ *
9465
+ * If a property name is provided for `predicate` the created `_.property`
9466
+ * style callback returns the property value of the given element.
9467
+ *
9468
+ * If a value is also provided for `thisArg` the created `_.matchesProperty`
9469
+ * style callback returns `true` for elements that have a matching property
9470
+ * value, else `false`.
9471
+ *
9472
+ * If an object is provided for `predicate` the created `_.matches` style
9473
+ * callback returns `true` for elements that have the properties of the given
9474
+ * object, else `false`.
9475
+ *
9476
+ * @static
9477
+ * @memberOf _
9478
+ * @category Object
9479
+ * @param {Object} object The object to search.
9480
+ * @param {Function|Object|string} [predicate=_.identity] The function invoked
9481
+ * per iteration.
9482
+ * @param {*} [thisArg] The `this` binding of `predicate`.
9483
+ * @returns {string|undefined} Returns the key of the matched element, else `undefined`.
9484
+ * @example
9485
+ *
9486
+ * var users = {
9487
+ * 'barney': { 'age': 36, 'active': true },
9488
+ * 'fred': { 'age': 40, 'active': false },
9489
+ * 'pebbles': { 'age': 1, 'active': true }
9490
+ * };
9491
+ *
9492
+ * _.findKey(users, function(chr) {
9493
+ * return chr.age < 40;
9494
+ * });
9495
+ * // => 'barney' (iteration order is not guaranteed)
9496
+ *
9497
+ * // using the `_.matches` callback shorthand
9498
+ * _.findKey(users, { 'age': 1, 'active': true });
9499
+ * // => 'pebbles'
9500
+ *
9501
+ * // using the `_.matchesProperty` callback shorthand
9502
+ * _.findKey(users, 'active', false);
9503
+ * // => 'fred'
9504
+ *
9505
+ * // using the `_.property` callback shorthand
9506
+ * _.findKey(users, 'active');
9507
+ * // => 'barney'
9508
+ */
9509
+ var findKey = createFindKey(baseForOwn);
9510
+
9511
+ /**
9512
+ * This method is like `_.findKey` except that it iterates over elements of
9513
+ * a collection in the opposite order.
9514
+ *
9515
+ * If a property name is provided for `predicate` the created `_.property`
9516
+ * style callback returns the property value of the given element.
9517
+ *
9518
+ * If a value is also provided for `thisArg` the created `_.matchesProperty`
9519
+ * style callback returns `true` for elements that have a matching property
9520
+ * value, else `false`.
9521
+ *
9522
+ * If an object is provided for `predicate` the created `_.matches` style
9523
+ * callback returns `true` for elements that have the properties of the given
9524
+ * object, else `false`.
9525
+ *
9526
+ * @static
9527
+ * @memberOf _
9528
+ * @category Object
9529
+ * @param {Object} object The object to search.
9530
+ * @param {Function|Object|string} [predicate=_.identity] The function invoked
9531
+ * per iteration.
9532
+ * @param {*} [thisArg] The `this` binding of `predicate`.
9533
+ * @returns {string|undefined} Returns the key of the matched element, else `undefined`.
9534
+ * @example
9535
+ *
9536
+ * var users = {
9537
+ * 'barney': { 'age': 36, 'active': true },
9538
+ * 'fred': { 'age': 40, 'active': false },
9539
+ * 'pebbles': { 'age': 1, 'active': true }
9540
+ * };
9541
+ *
9542
+ * _.findLastKey(users, function(chr) {
9543
+ * return chr.age < 40;
9544
+ * });
9545
+ * // => returns `pebbles` assuming `_.findKey` returns `barney`
9546
+ *
9547
+ * // using the `_.matches` callback shorthand
9548
+ * _.findLastKey(users, { 'age': 36, 'active': true });
9549
+ * // => 'barney'
9550
+ *
9551
+ * // using the `_.matchesProperty` callback shorthand
9552
+ * _.findLastKey(users, 'active', false);
9553
+ * // => 'fred'
9554
+ *
9555
+ * // using the `_.property` callback shorthand
9556
+ * _.findLastKey(users, 'active');
9557
+ * // => 'pebbles'
9558
+ */
9559
+ var findLastKey = createFindKey(baseForOwnRight);
9560
+
9561
+ /**
9562
+ * Iterates over own and inherited enumerable properties of an object invoking
9563
+ * `iteratee` for each property. The `iteratee` is bound to `thisArg` and invoked
9564
+ * with three arguments: (value, key, object). Iteratee functions may exit
9565
+ * iteration early by explicitly returning `false`.
9566
+ *
9567
+ * @static
9568
+ * @memberOf _
9569
+ * @category Object
9570
+ * @param {Object} object The object to iterate over.
9571
+ * @param {Function} [iteratee=_.identity] The function invoked per iteration.
9572
+ * @param {*} [thisArg] The `this` binding of `iteratee`.
9573
+ * @returns {Object} Returns `object`.
9574
+ * @example
9575
+ *
9576
+ * function Foo() {
9577
+ * this.a = 1;
9578
+ * this.b = 2;
9579
+ * }
9580
+ *
9581
+ * Foo.prototype.c = 3;
9582
+ *
9583
+ * _.forIn(new Foo, function(value, key) {
9584
+ * console.log(key);
9585
+ * });
9586
+ * // => logs 'a', 'b', and 'c' (iteration order is not guaranteed)
9587
+ */
9588
+ var forIn = createForIn(baseFor);
9589
+
9590
+ /**
9591
+ * This method is like `_.forIn` except that it iterates over properties of
9592
+ * `object` in the opposite order.
9593
+ *
9594
+ * @static
9595
+ * @memberOf _
9596
+ * @category Object
9597
+ * @param {Object} object The object to iterate over.
9598
+ * @param {Function} [iteratee=_.identity] The function invoked per iteration.
9599
+ * @param {*} [thisArg] The `this` binding of `iteratee`.
9600
+ * @returns {Object} Returns `object`.
9601
+ * @example
9602
+ *
9603
+ * function Foo() {
9604
+ * this.a = 1;
9605
+ * this.b = 2;
9606
+ * }
9607
+ *
9608
+ * Foo.prototype.c = 3;
9609
+ *
9610
+ * _.forInRight(new Foo, function(value, key) {
9611
+ * console.log(key);
9612
+ * });
9613
+ * // => logs 'c', 'b', and 'a' assuming `_.forIn ` logs 'a', 'b', and 'c'
9614
+ */
9615
+ var forInRight = createForIn(baseForRight);
9616
+
9617
+ /**
9618
+ * Iterates over own enumerable properties of an object invoking `iteratee`
9619
+ * for each property. The `iteratee` is bound to `thisArg` and invoked with
9620
+ * three arguments: (value, key, object). Iteratee functions may exit iteration
9621
+ * early by explicitly returning `false`.
9622
+ *
9623
+ * @static
9624
+ * @memberOf _
9625
+ * @category Object
9626
+ * @param {Object} object The object to iterate over.
9627
+ * @param {Function} [iteratee=_.identity] The function invoked per iteration.
9628
+ * @param {*} [thisArg] The `this` binding of `iteratee`.
9629
+ * @returns {Object} Returns `object`.
9630
+ * @example
9631
+ *
9632
+ * function Foo() {
9633
+ * this.a = 1;
9634
+ * this.b = 2;
9635
+ * }
9636
+ *
9637
+ * Foo.prototype.c = 3;
9638
+ *
9639
+ * _.forOwn(new Foo, function(value, key) {
9640
+ * console.log(key);
9641
+ * });
9642
+ * // => logs 'a' and 'b' (iteration order is not guaranteed)
9643
+ */
9644
+ var forOwn = createForOwn(baseForOwn);
9645
+
9646
+ /**
9647
+ * This method is like `_.forOwn` except that it iterates over properties of
9648
+ * `object` in the opposite order.
9649
+ *
9650
+ * @static
9651
+ * @memberOf _
9652
+ * @category Object
9653
+ * @param {Object} object The object to iterate over.
9654
+ * @param {Function} [iteratee=_.identity] The function invoked per iteration.
9655
+ * @param {*} [thisArg] The `this` binding of `iteratee`.
9656
+ * @returns {Object} Returns `object`.
9657
+ * @example
9658
+ *
9659
+ * function Foo() {
9660
+ * this.a = 1;
9661
+ * this.b = 2;
9662
+ * }
9663
+ *
9664
+ * Foo.prototype.c = 3;
9665
+ *
9666
+ * _.forOwnRight(new Foo, function(value, key) {
9667
+ * console.log(key);
9668
+ * });
9669
+ * // => logs 'b' and 'a' assuming `_.forOwn` logs 'a' and 'b'
9670
+ */
9671
+ var forOwnRight = createForOwn(baseForOwnRight);
9672
+
9673
+ /**
9674
+ * Creates an array of function property names from all enumerable properties,
9675
+ * own and inherited, of `object`.
9676
+ *
9677
+ * @static
9678
+ * @memberOf _
9679
+ * @alias methods
9680
+ * @category Object
9681
+ * @param {Object} object The object to inspect.
9682
+ * @returns {Array} Returns the new array of property names.
9683
+ * @example
9684
+ *
9685
+ * _.functions(_);
9686
+ * // => ['after', 'ary', 'assign', ...]
9687
+ */
9688
+ function functions(object) {
9689
+ return baseFunctions(object, keysIn(object));
9690
+ }
9691
+
9692
+ /**
9693
+ * Gets the property value at `path` of `object`. If the resolved value is
9694
+ * `undefined` the `defaultValue` is used in its place.
9695
+ *
9696
+ * @static
9697
+ * @memberOf _
9698
+ * @category Object
9699
+ * @param {Object} object The object to query.
9700
+ * @param {Array|string} path The path of the property to get.
9701
+ * @param {*} [defaultValue] The value returned if the resolved value is `undefined`.
9702
+ * @returns {*} Returns the resolved value.
9703
+ * @example
9704
+ *
9705
+ * var object = { 'a': [{ 'b': { 'c': 3 } }] };
9706
+ *
9707
+ * _.get(object, 'a[0].b.c');
9708
+ * // => 3
9709
+ *
9710
+ * _.get(object, ['a', '0', 'b', 'c']);
9711
+ * // => 3
9712
+ *
9713
+ * _.get(object, 'a.b.c', 'default');
9714
+ * // => 'default'
9715
+ */
9716
+ function get(object, path, defaultValue) {
9717
+ var result = object == null ? undefined : baseGet(object, toPath(path), (path + ''));
9718
+ return result === undefined ? defaultValue : result;
9719
+ }
9720
+
9721
+ /**
9722
+ * Checks if `path` is a direct property.
9723
+ *
9724
+ * @static
9725
+ * @memberOf _
9726
+ * @category Object
9727
+ * @param {Object} object The object to query.
9728
+ * @param {Array|string} path The path to check.
9729
+ * @returns {boolean} Returns `true` if `path` is a direct property, else `false`.
9730
+ * @example
9731
+ *
9732
+ * var object = { 'a': { 'b': { 'c': 3 } } };
9733
+ *
9734
+ * _.has(object, 'a');
9735
+ * // => true
9736
+ *
9737
+ * _.has(object, 'a.b.c');
9738
+ * // => true
9739
+ *
9740
+ * _.has(object, ['a', 'b', 'c']);
9741
+ * // => true
9742
+ */
9743
+ function has(object, path) {
9744
+ if (object == null) {
9745
+ return false;
9746
+ }
9747
+ var result = hasOwnProperty.call(object, path);
9748
+ if (!result && !isKey(path)) {
9749
+ path = toPath(path);
9750
+ object = path.length == 1 ? object : baseGet(object, baseSlice(path, 0, -1));
9751
+ if (object == null) {
9752
+ return false;
9753
+ }
9754
+ path = last(path);
9755
+ result = hasOwnProperty.call(object, path);
9756
+ }
9757
+ return result || (isLength(object.length) && isIndex(path, object.length) &&
9758
+ (isArray(object) || isArguments(object) || isString(object)));
9759
+ }
9760
+
9761
+ /**
9762
+ * Creates an object composed of the inverted keys and values of `object`.
9763
+ * If `object` contains duplicate values, subsequent values overwrite property
9764
+ * assignments of previous values unless `multiValue` is `true`.
9765
+ *
9766
+ * @static
9767
+ * @memberOf _
9768
+ * @category Object
9769
+ * @param {Object} object The object to invert.
9770
+ * @param {boolean} [multiValue] Allow multiple values per key.
9771
+ * @param- {Object} [guard] Enables use as a callback for functions like `_.map`.
9772
+ * @returns {Object} Returns the new inverted object.
9773
+ * @example
9774
+ *
9775
+ * var object = { 'a': 1, 'b': 2, 'c': 1 };
9776
+ *
9777
+ * _.invert(object);
9778
+ * // => { '1': 'c', '2': 'b' }
9779
+ *
9780
+ * // with `multiValue`
9781
+ * _.invert(object, true);
9782
+ * // => { '1': ['a', 'c'], '2': ['b'] }
9783
+ */
9784
+ function invert(object, multiValue, guard) {
9785
+ if (guard && isIterateeCall(object, multiValue, guard)) {
9786
+ multiValue = undefined;
9787
+ }
9788
+ var index = -1,
9789
+ props = keys(object),
9790
+ length = props.length,
9791
+ result = {};
9792
+
9793
+ while (++index < length) {
9794
+ var key = props[index],
9795
+ value = object[key];
9796
+
9797
+ if (multiValue) {
9798
+ if (hasOwnProperty.call(result, value)) {
9799
+ result[value].push(key);
9800
+ } else {
9801
+ result[value] = [key];
9802
+ }
9803
+ }
9804
+ else {
9805
+ result[value] = key;
9806
+ }
9807
+ }
9808
+ return result;
9809
+ }
9810
+
9811
+ /**
9812
+ * Creates an array of the own enumerable property names of `object`.
9813
+ *
9814
+ * **Note:** Non-object values are coerced to objects. See the
9815
+ * [ES spec](http://ecma-international.org/ecma-262/6.0/#sec-object.keys)
9816
+ * for more details.
9817
+ *
9818
+ * @static
9819
+ * @memberOf _
9820
+ * @category Object
9821
+ * @param {Object} object The object to query.
9822
+ * @returns {Array} Returns the array of property names.
9823
+ * @example
9824
+ *
9825
+ * function Foo() {
9826
+ * this.a = 1;
9827
+ * this.b = 2;
9828
+ * }
9829
+ *
9830
+ * Foo.prototype.c = 3;
9831
+ *
9832
+ * _.keys(new Foo);
9833
+ * // => ['a', 'b'] (iteration order is not guaranteed)
9834
+ *
9835
+ * _.keys('hi');
9836
+ * // => ['0', '1']
9837
+ */
9838
+ var keys = !nativeKeys ? shimKeys : function(object) {
9839
+ var Ctor = object == null ? undefined : object.constructor;
9840
+ if ((typeof Ctor == 'function' && Ctor.prototype === object) ||
9841
+ (typeof object == 'function' ? lodash.support.enumPrototypes : isArrayLike(object))) {
9842
+ return shimKeys(object);
9843
+ }
9844
+ return isObject(object) ? nativeKeys(object) : [];
9845
+ };
9846
+
9847
+ /**
9848
+ * Creates an array of the own and inherited enumerable property names of `object`.
9849
+ *
9850
+ * **Note:** Non-object values are coerced to objects.
9851
+ *
9852
+ * @static
9853
+ * @memberOf _
9854
+ * @category Object
9855
+ * @param {Object} object The object to query.
9856
+ * @returns {Array} Returns the array of property names.
9857
+ * @example
9858
+ *
9859
+ * function Foo() {
9860
+ * this.a = 1;
9861
+ * this.b = 2;
9862
+ * }
9863
+ *
9864
+ * Foo.prototype.c = 3;
9865
+ *
9866
+ * _.keysIn(new Foo);
9867
+ * // => ['a', 'b', 'c'] (iteration order is not guaranteed)
9868
+ */
9869
+ function keysIn(object) {
9870
+ if (object == null) {
9871
+ return [];
9872
+ }
9873
+ if (!isObject(object)) {
9874
+ object = Object(object);
9875
+ }
9876
+ var length = object.length,
9877
+ support = lodash.support;
9878
+
9879
+ length = (length && isLength(length) &&
9880
+ (isArray(object) || isArguments(object) || isString(object)) && length) || 0;
9881
+
9882
+ var Ctor = object.constructor,
9883
+ index = -1,
9884
+ proto = (isFunction(Ctor) && Ctor.prototype) || objectProto,
9885
+ isProto = proto === object,
9886
+ result = Array(length),
9887
+ skipIndexes = length > 0,
9888
+ skipErrorProps = support.enumErrorProps && (object === errorProto || object instanceof Error),
9889
+ skipProto = support.enumPrototypes && isFunction(object);
9890
+
9891
+ while (++index < length) {
9892
+ result[index] = (index + '');
9893
+ }
9894
+ // lodash skips the `constructor` property when it infers it's iterating
9895
+ // over a `prototype` object because IE < 9 can't set the `[[Enumerable]]`
9896
+ // attribute of an existing property and the `constructor` property of a
9897
+ // prototype defaults to non-enumerable.
9898
+ for (var key in object) {
9899
+ if (!(skipProto && key == 'prototype') &&
9900
+ !(skipErrorProps && (key == 'message' || key == 'name')) &&
9901
+ !(skipIndexes && isIndex(key, length)) &&
9902
+ !(key == 'constructor' && (isProto || !hasOwnProperty.call(object, key)))) {
9903
+ result.push(key);
9904
+ }
9905
+ }
9906
+ if (support.nonEnumShadows && object !== objectProto) {
9907
+ var tag = object === stringProto ? stringTag : (object === errorProto ? errorTag : objToString.call(object)),
9908
+ nonEnums = nonEnumProps[tag] || nonEnumProps[objectTag];
9909
+
9910
+ if (tag == objectTag) {
9911
+ proto = objectProto;
9912
+ }
9913
+ length = shadowProps.length;
9914
+ while (length--) {
9915
+ key = shadowProps[length];
9916
+ var nonEnum = nonEnums[key];
9917
+ if (!(isProto && nonEnum) &&
9918
+ (nonEnum ? hasOwnProperty.call(object, key) : object[key] !== proto[key])) {
9919
+ result.push(key);
9920
+ }
9921
+ }
9922
+ }
9923
+ return result;
9924
+ }
9925
+
9926
+ /**
9927
+ * The opposite of `_.mapValues`; this method creates an object with the
9928
+ * same values as `object` and keys generated by running each own enumerable
9929
+ * property of `object` through `iteratee`.
9930
+ *
9931
+ * @static
9932
+ * @memberOf _
9933
+ * @category Object
9934
+ * @param {Object} object The object to iterate over.
9935
+ * @param {Function|Object|string} [iteratee=_.identity] The function invoked
9936
+ * per iteration.
9937
+ * @param {*} [thisArg] The `this` binding of `iteratee`.
9938
+ * @returns {Object} Returns the new mapped object.
9939
+ * @example
9940
+ *
9941
+ * _.mapKeys({ 'a': 1, 'b': 2 }, function(value, key) {
9942
+ * return key + value;
9943
+ * });
9944
+ * // => { 'a1': 1, 'b2': 2 }
9945
+ */
9946
+ var mapKeys = createObjectMapper(true);
9947
+
9948
+ /**
9949
+ * Creates an object with the same keys as `object` and values generated by
9950
+ * running each own enumerable property of `object` through `iteratee`. The
9951
+ * iteratee function is bound to `thisArg` and invoked with three arguments:
9952
+ * (value, key, object).
9953
+ *
9954
+ * If a property name is provided for `iteratee` the created `_.property`
9955
+ * style callback returns the property value of the given element.
9956
+ *
9957
+ * If a value is also provided for `thisArg` the created `_.matchesProperty`
9958
+ * style callback returns `true` for elements that have a matching property
9959
+ * value, else `false`.
9960
+ *
9961
+ * If an object is provided for `iteratee` the created `_.matches` style
9962
+ * callback returns `true` for elements that have the properties of the given
9963
+ * object, else `false`.
9964
+ *
9965
+ * @static
9966
+ * @memberOf _
9967
+ * @category Object
9968
+ * @param {Object} object The object to iterate over.
9969
+ * @param {Function|Object|string} [iteratee=_.identity] The function invoked
9970
+ * per iteration.
9971
+ * @param {*} [thisArg] The `this` binding of `iteratee`.
9972
+ * @returns {Object} Returns the new mapped object.
9973
+ * @example
9974
+ *
9975
+ * _.mapValues({ 'a': 1, 'b': 2 }, function(n) {
9976
+ * return n * 3;
9977
+ * });
9978
+ * // => { 'a': 3, 'b': 6 }
9979
+ *
9980
+ * var users = {
9981
+ * 'fred': { 'user': 'fred', 'age': 40 },
9982
+ * 'pebbles': { 'user': 'pebbles', 'age': 1 }
9983
+ * };
9984
+ *
9985
+ * // using the `_.property` callback shorthand
9986
+ * _.mapValues(users, 'age');
9987
+ * // => { 'fred': 40, 'pebbles': 1 } (iteration order is not guaranteed)
9988
+ */
9989
+ var mapValues = createObjectMapper();
9990
+
9991
+ /**
9992
+ * The opposite of `_.pick`; this method creates an object composed of the
9993
+ * own and inherited enumerable properties of `object` that are not omitted.
9994
+ *
9995
+ * @static
9996
+ * @memberOf _
9997
+ * @category Object
9998
+ * @param {Object} object The source object.
9999
+ * @param {Function|...(string|string[])} [predicate] The function invoked per
10000
+ * iteration or property names to omit, specified as individual property
10001
+ * names or arrays of property names.
10002
+ * @param {*} [thisArg] The `this` binding of `predicate`.
10003
+ * @returns {Object} Returns the new object.
10004
+ * @example
10005
+ *
10006
+ * var object = { 'user': 'fred', 'age': 40 };
10007
+ *
10008
+ * _.omit(object, 'age');
10009
+ * // => { 'user': 'fred' }
10010
+ *
10011
+ * _.omit(object, _.isNumber);
10012
+ * // => { 'user': 'fred' }
10013
+ */
10014
+ var omit = restParam(function(object, props) {
10015
+ if (object == null) {
10016
+ return {};
10017
+ }
10018
+ if (typeof props[0] != 'function') {
10019
+ var props = arrayMap(baseFlatten(props), String);
10020
+ return pickByArray(object, baseDifference(keysIn(object), props));
10021
+ }
10022
+ var predicate = bindCallback(props[0], props[1], 3);
10023
+ return pickByCallback(object, function(value, key, object) {
10024
+ return !predicate(value, key, object);
10025
+ });
10026
+ });
10027
+
10028
+ /**
10029
+ * Creates a two dimensional array of the key-value pairs for `object`,
10030
+ * e.g. `[[key1, value1], [key2, value2]]`.
10031
+ *
10032
+ * @static
10033
+ * @memberOf _
10034
+ * @category Object
10035
+ * @param {Object} object The object to query.
10036
+ * @returns {Array} Returns the new array of key-value pairs.
10037
+ * @example
10038
+ *
10039
+ * _.pairs({ 'barney': 36, 'fred': 40 });
10040
+ * // => [['barney', 36], ['fred', 40]] (iteration order is not guaranteed)
10041
+ */
10042
+ function pairs(object) {
10043
+ object = toObject(object);
10044
+
10045
+ var index = -1,
10046
+ props = keys(object),
10047
+ length = props.length,
10048
+ result = Array(length);
10049
+
10050
+ while (++index < length) {
10051
+ var key = props[index];
10052
+ result[index] = [key, object[key]];
10053
+ }
10054
+ return result;
10055
+ }
10056
+
10057
+ /**
10058
+ * Creates an object composed of the picked `object` properties. Property
10059
+ * names may be specified as individual arguments or as arrays of property
10060
+ * names. If `predicate` is provided it's invoked for each property of `object`
10061
+ * picking the properties `predicate` returns truthy for. The predicate is
10062
+ * bound to `thisArg` and invoked with three arguments: (value, key, object).
10063
+ *
10064
+ * @static
10065
+ * @memberOf _
10066
+ * @category Object
10067
+ * @param {Object} object The source object.
10068
+ * @param {Function|...(string|string[])} [predicate] The function invoked per
10069
+ * iteration or property names to pick, specified as individual property
10070
+ * names or arrays of property names.
10071
+ * @param {*} [thisArg] The `this` binding of `predicate`.
10072
+ * @returns {Object} Returns the new object.
10073
+ * @example
10074
+ *
10075
+ * var object = { 'user': 'fred', 'age': 40 };
10076
+ *
10077
+ * _.pick(object, 'user');
10078
+ * // => { 'user': 'fred' }
10079
+ *
10080
+ * _.pick(object, _.isString);
10081
+ * // => { 'user': 'fred' }
10082
+ */
10083
+ var pick = restParam(function(object, props) {
10084
+ if (object == null) {
10085
+ return {};
10086
+ }
10087
+ return typeof props[0] == 'function'
10088
+ ? pickByCallback(object, bindCallback(props[0], props[1], 3))
10089
+ : pickByArray(object, baseFlatten(props));
10090
+ });
10091
+
10092
+ /**
10093
+ * This method is like `_.get` except that if the resolved value is a function
10094
+ * it's invoked with the `this` binding of its parent object and its result
10095
+ * is returned.
10096
+ *
10097
+ * @static
10098
+ * @memberOf _
10099
+ * @category Object
10100
+ * @param {Object} object The object to query.
10101
+ * @param {Array|string} path The path of the property to resolve.
10102
+ * @param {*} [defaultValue] The value returned if the resolved value is `undefined`.
10103
+ * @returns {*} Returns the resolved value.
10104
+ * @example
10105
+ *
10106
+ * var object = { 'a': [{ 'b': { 'c1': 3, 'c2': _.constant(4) } }] };
10107
+ *
10108
+ * _.result(object, 'a[0].b.c1');
10109
+ * // => 3
10110
+ *
10111
+ * _.result(object, 'a[0].b.c2');
10112
+ * // => 4
10113
+ *
10114
+ * _.result(object, 'a.b.c', 'default');
10115
+ * // => 'default'
10116
+ *
10117
+ * _.result(object, 'a.b.c', _.constant('default'));
10118
+ * // => 'default'
10119
+ */
10120
+ function result(object, path, defaultValue) {
10121
+ var result = object == null ? undefined : toObject(object)[path];
10122
+ if (result === undefined) {
10123
+ if (object != null && !isKey(path, object)) {
10124
+ path = toPath(path);
10125
+ object = path.length == 1 ? object : baseGet(object, baseSlice(path, 0, -1));
10126
+ result = object == null ? undefined : toObject(object)[last(path)];
10127
+ }
10128
+ result = result === undefined ? defaultValue : result;
10129
+ }
10130
+ return isFunction(result) ? result.call(object) : result;
10131
+ }
10132
+
10133
+ /**
10134
+ * Sets the property value of `path` on `object`. If a portion of `path`
10135
+ * does not exist it's created.
10136
+ *
10137
+ * @static
10138
+ * @memberOf _
10139
+ * @category Object
10140
+ * @param {Object} object The object to augment.
10141
+ * @param {Array|string} path The path of the property to set.
10142
+ * @param {*} value The value to set.
10143
+ * @returns {Object} Returns `object`.
10144
+ * @example
10145
+ *
10146
+ * var object = { 'a': [{ 'b': { 'c': 3 } }] };
10147
+ *
10148
+ * _.set(object, 'a[0].b.c', 4);
10149
+ * console.log(object.a[0].b.c);
10150
+ * // => 4
10151
+ *
10152
+ * _.set(object, 'x[0].y.z', 5);
10153
+ * console.log(object.x[0].y.z);
10154
+ * // => 5
10155
+ */
10156
+ function set(object, path, value) {
10157
+ if (object == null) {
10158
+ return object;
10159
+ }
10160
+ var pathKey = (path + '');
10161
+ path = (object[pathKey] != null || isKey(path, object)) ? [pathKey] : toPath(path);
10162
+
10163
+ var index = -1,
10164
+ length = path.length,
10165
+ lastIndex = length - 1,
10166
+ nested = object;
10167
+
10168
+ while (nested != null && ++index < length) {
10169
+ var key = path[index];
10170
+ if (isObject(nested)) {
10171
+ if (index == lastIndex) {
10172
+ nested[key] = value;
10173
+ } else if (nested[key] == null) {
10174
+ nested[key] = isIndex(path[index + 1]) ? [] : {};
10175
+ }
10176
+ }
10177
+ nested = nested[key];
10178
+ }
10179
+ return object;
10180
+ }
10181
+
10182
+ /**
10183
+ * An alternative to `_.reduce`; this method transforms `object` to a new
10184
+ * `accumulator` object which is the result of running each of its own enumerable
10185
+ * properties through `iteratee`, with each invocation potentially mutating
10186
+ * the `accumulator` object. The `iteratee` is bound to `thisArg` and invoked
10187
+ * with four arguments: (accumulator, value, key, object). Iteratee functions
10188
+ * may exit iteration early by explicitly returning `false`.
10189
+ *
10190
+ * @static
10191
+ * @memberOf _
10192
+ * @category Object
10193
+ * @param {Array|Object} object The object to iterate over.
10194
+ * @param {Function} [iteratee=_.identity] The function invoked per iteration.
10195
+ * @param {*} [accumulator] The custom accumulator value.
10196
+ * @param {*} [thisArg] The `this` binding of `iteratee`.
10197
+ * @returns {*} Returns the accumulated value.
10198
+ * @example
10199
+ *
10200
+ * _.transform([2, 3, 4], function(result, n) {
10201
+ * result.push(n *= n);
10202
+ * return n % 2 == 0;
10203
+ * });
10204
+ * // => [4, 9]
10205
+ *
10206
+ * _.transform({ 'a': 1, 'b': 2 }, function(result, n, key) {
10207
+ * result[key] = n * 3;
10208
+ * });
10209
+ * // => { 'a': 3, 'b': 6 }
10210
+ */
10211
+ function transform(object, iteratee, accumulator, thisArg) {
10212
+ var isArr = isArray(object) || isTypedArray(object);
10213
+ iteratee = getCallback(iteratee, thisArg, 4);
10214
+
10215
+ if (accumulator == null) {
10216
+ if (isArr || isObject(object)) {
10217
+ var Ctor = object.constructor;
10218
+ if (isArr) {
10219
+ accumulator = isArray(object) ? new Ctor : [];
10220
+ } else {
10221
+ accumulator = baseCreate(isFunction(Ctor) ? Ctor.prototype : undefined);
10222
+ }
10223
+ } else {
10224
+ accumulator = {};
10225
+ }
10226
+ }
10227
+ (isArr ? arrayEach : baseForOwn)(object, function(value, index, object) {
10228
+ return iteratee(accumulator, value, index, object);
10229
+ });
10230
+ return accumulator;
10231
+ }
10232
+
10233
+ /**
10234
+ * Creates an array of the own enumerable property values of `object`.
10235
+ *
10236
+ * **Note:** Non-object values are coerced to objects.
10237
+ *
10238
+ * @static
10239
+ * @memberOf _
10240
+ * @category Object
10241
+ * @param {Object} object The object to query.
10242
+ * @returns {Array} Returns the array of property values.
10243
+ * @example
10244
+ *
10245
+ * function Foo() {
10246
+ * this.a = 1;
10247
+ * this.b = 2;
10248
+ * }
10249
+ *
10250
+ * Foo.prototype.c = 3;
10251
+ *
10252
+ * _.values(new Foo);
10253
+ * // => [1, 2] (iteration order is not guaranteed)
10254
+ *
10255
+ * _.values('hi');
10256
+ * // => ['h', 'i']
10257
+ */
10258
+ function values(object) {
10259
+ return baseValues(object, keys(object));
10260
+ }
10261
+
10262
+ /**
10263
+ * Creates an array of the own and inherited enumerable property values
10264
+ * of `object`.
10265
+ *
10266
+ * **Note:** Non-object values are coerced to objects.
10267
+ *
10268
+ * @static
10269
+ * @memberOf _
10270
+ * @category Object
10271
+ * @param {Object} object The object to query.
10272
+ * @returns {Array} Returns the array of property values.
10273
+ * @example
10274
+ *
10275
+ * function Foo() {
10276
+ * this.a = 1;
10277
+ * this.b = 2;
10278
+ * }
10279
+ *
10280
+ * Foo.prototype.c = 3;
10281
+ *
10282
+ * _.valuesIn(new Foo);
10283
+ * // => [1, 2, 3] (iteration order is not guaranteed)
10284
+ */
10285
+ function valuesIn(object) {
10286
+ return baseValues(object, keysIn(object));
10287
+ }
10288
+
10289
+ /*------------------------------------------------------------------------*/
10290
+
10291
+ /**
10292
+ * Checks if `n` is between `start` and up to but not including, `end`. If
10293
+ * `end` is not specified it's set to `start` with `start` then set to `0`.
10294
+ *
10295
+ * @static
10296
+ * @memberOf _
10297
+ * @category Number
10298
+ * @param {number} n The number to check.
10299
+ * @param {number} [start=0] The start of the range.
10300
+ * @param {number} end The end of the range.
10301
+ * @returns {boolean} Returns `true` if `n` is in the range, else `false`.
10302
+ * @example
10303
+ *
10304
+ * _.inRange(3, 2, 4);
10305
+ * // => true
10306
+ *
10307
+ * _.inRange(4, 8);
10308
+ * // => true
10309
+ *
10310
+ * _.inRange(4, 2);
10311
+ * // => false
10312
+ *
10313
+ * _.inRange(2, 2);
10314
+ * // => false
10315
+ *
10316
+ * _.inRange(1.2, 2);
10317
+ * // => true
10318
+ *
10319
+ * _.inRange(5.2, 4);
10320
+ * // => false
10321
+ */
10322
+ function inRange(value, start, end) {
10323
+ start = +start || 0;
10324
+ if (end === undefined) {
10325
+ end = start;
10326
+ start = 0;
10327
+ } else {
10328
+ end = +end || 0;
10329
+ }
10330
+ return value >= nativeMin(start, end) && value < nativeMax(start, end);
10331
+ }
10332
+
10333
+ /**
10334
+ * Produces a random number between `min` and `max` (inclusive). If only one
10335
+ * argument is provided a number between `0` and the given number is returned.
10336
+ * If `floating` is `true`, or either `min` or `max` are floats, a floating-point
10337
+ * number is returned instead of an integer.
10338
+ *
10339
+ * @static
10340
+ * @memberOf _
10341
+ * @category Number
10342
+ * @param {number} [min=0] The minimum possible value.
10343
+ * @param {number} [max=1] The maximum possible value.
10344
+ * @param {boolean} [floating] Specify returning a floating-point number.
10345
+ * @returns {number} Returns the random number.
10346
+ * @example
10347
+ *
10348
+ * _.random(0, 5);
10349
+ * // => an integer between 0 and 5
10350
+ *
10351
+ * _.random(5);
10352
+ * // => also an integer between 0 and 5
10353
+ *
10354
+ * _.random(5, true);
10355
+ * // => a floating-point number between 0 and 5
10356
+ *
10357
+ * _.random(1.2, 5.2);
10358
+ * // => a floating-point number between 1.2 and 5.2
10359
+ */
10360
+ function random(min, max, floating) {
10361
+ if (floating && isIterateeCall(min, max, floating)) {
10362
+ max = floating = undefined;
10363
+ }
10364
+ var noMin = min == null,
10365
+ noMax = max == null;
10366
+
10367
+ if (floating == null) {
10368
+ if (noMax && typeof min == 'boolean') {
10369
+ floating = min;
10370
+ min = 1;
10371
+ }
10372
+ else if (typeof max == 'boolean') {
10373
+ floating = max;
10374
+ noMax = true;
10375
+ }
10376
+ }
10377
+ if (noMin && noMax) {
10378
+ max = 1;
10379
+ noMax = false;
10380
+ }
10381
+ min = +min || 0;
10382
+ if (noMax) {
10383
+ max = min;
10384
+ min = 0;
10385
+ } else {
10386
+ max = +max || 0;
10387
+ }
10388
+ if (floating || min % 1 || max % 1) {
10389
+ var rand = nativeRandom();
10390
+ return nativeMin(min + (rand * (max - min + parseFloat('1e-' + ((rand + '').length - 1)))), max);
10391
+ }
10392
+ return baseRandom(min, max);
10393
+ }
10394
+
10395
+ /*------------------------------------------------------------------------*/
10396
+
10397
+ /**
10398
+ * Converts `string` to [camel case](https://en.wikipedia.org/wiki/CamelCase).
10399
+ *
10400
+ * @static
10401
+ * @memberOf _
10402
+ * @category String
10403
+ * @param {string} [string=''] The string to convert.
10404
+ * @returns {string} Returns the camel cased string.
10405
+ * @example
10406
+ *
10407
+ * _.camelCase('Foo Bar');
10408
+ * // => 'fooBar'
10409
+ *
10410
+ * _.camelCase('--foo-bar');
10411
+ * // => 'fooBar'
10412
+ *
10413
+ * _.camelCase('__foo_bar__');
10414
+ * // => 'fooBar'
10415
+ */
10416
+ var camelCase = createCompounder(function(result, word, index) {
10417
+ word = word.toLowerCase();
10418
+ return result + (index ? (word.charAt(0).toUpperCase() + word.slice(1)) : word);
10419
+ });
10420
+
10421
+ /**
10422
+ * Capitalizes the first character of `string`.
10423
+ *
10424
+ * @static
10425
+ * @memberOf _
10426
+ * @category String
10427
+ * @param {string} [string=''] The string to capitalize.
10428
+ * @returns {string} Returns the capitalized string.
10429
+ * @example
10430
+ *
10431
+ * _.capitalize('fred');
10432
+ * // => 'Fred'
10433
+ */
10434
+ function capitalize(string) {
10435
+ string = baseToString(string);
10436
+ return string && (string.charAt(0).toUpperCase() + string.slice(1));
10437
+ }
10438
+
10439
+ /**
10440
+ * Deburrs `string` by converting [latin-1 supplementary letters](https://en.wikipedia.org/wiki/Latin-1_Supplement_(Unicode_block)#Character_table)
10441
+ * to basic latin letters and removing [combining diacritical marks](https://en.wikipedia.org/wiki/Combining_Diacritical_Marks).
10442
+ *
10443
+ * @static
10444
+ * @memberOf _
10445
+ * @category String
10446
+ * @param {string} [string=''] The string to deburr.
10447
+ * @returns {string} Returns the deburred string.
10448
+ * @example
10449
+ *
10450
+ * _.deburr('déjà vu');
10451
+ * // => 'deja vu'
10452
+ */
10453
+ function deburr(string) {
10454
+ string = baseToString(string);
10455
+ return string && string.replace(reLatin1, deburrLetter).replace(reComboMark, '');
10456
+ }
10457
+
10458
+ /**
10459
+ * Checks if `string` ends with the given target string.
10460
+ *
10461
+ * @static
10462
+ * @memberOf _
10463
+ * @category String
10464
+ * @param {string} [string=''] The string to search.
10465
+ * @param {string} [target] The string to search for.
10466
+ * @param {number} [position=string.length] The position to search from.
10467
+ * @returns {boolean} Returns `true` if `string` ends with `target`, else `false`.
10468
+ * @example
10469
+ *
10470
+ * _.endsWith('abc', 'c');
10471
+ * // => true
10472
+ *
10473
+ * _.endsWith('abc', 'b');
10474
+ * // => false
10475
+ *
10476
+ * _.endsWith('abc', 'b', 2);
10477
+ * // => true
10478
+ */
10479
+ function endsWith(string, target, position) {
10480
+ string = baseToString(string);
10481
+ target = (target + '');
10482
+
10483
+ var length = string.length;
10484
+ position = position === undefined
10485
+ ? length
10486
+ : nativeMin(position < 0 ? 0 : (+position || 0), length);
10487
+
10488
+ position -= target.length;
10489
+ return position >= 0 && string.indexOf(target, position) == position;
10490
+ }
10491
+
10492
+ /**
10493
+ * Converts the characters "&", "<", ">", '"', "'", and "\`", in `string` to
10494
+ * their corresponding HTML entities.
10495
+ *
10496
+ * **Note:** No other characters are escaped. To escape additional characters
10497
+ * use a third-party library like [_he_](https://mths.be/he).
10498
+ *
10499
+ * Though the ">" character is escaped for symmetry, characters like
10500
+ * ">" and "/" don't need escaping in HTML and have no special meaning
10501
+ * unless they're part of a tag or unquoted attribute value.
10502
+ * See [Mathias Bynens's article](https://mathiasbynens.be/notes/ambiguous-ampersands)
10503
+ * (under "semi-related fun fact") for more details.
10504
+ *
10505
+ * Backticks are escaped because in Internet Explorer < 9, they can break out
10506
+ * of attribute values or HTML comments. See [#59](https://html5sec.org/#59),
10507
+ * [#102](https://html5sec.org/#102), [#108](https://html5sec.org/#108), and
10508
+ * [#133](https://html5sec.org/#133) of the [HTML5 Security Cheatsheet](https://html5sec.org/)
10509
+ * for more details.
10510
+ *
10511
+ * When working with HTML you should always [quote attribute values](http://wonko.com/post/html-escaping)
10512
+ * to reduce XSS vectors.
10513
+ *
10514
+ * @static
10515
+ * @memberOf _
10516
+ * @category String
10517
+ * @param {string} [string=''] The string to escape.
10518
+ * @returns {string} Returns the escaped string.
10519
+ * @example
10520
+ *
10521
+ * _.escape('fred, barney, & pebbles');
10522
+ * // => 'fred, barney, &amp; pebbles'
10523
+ */
10524
+ function escape(string) {
10525
+ // Reset `lastIndex` because in IE < 9 `String#replace` does not.
10526
+ string = baseToString(string);
10527
+ return (string && reHasUnescapedHtml.test(string))
10528
+ ? string.replace(reUnescapedHtml, escapeHtmlChar)
10529
+ : string;
10530
+ }
10531
+
10532
+ /**
10533
+ * Escapes the `RegExp` special characters "\", "/", "^", "$", ".", "|", "?",
10534
+ * "*", "+", "(", ")", "[", "]", "{" and "}" in `string`.
10535
+ *
10536
+ * @static
10537
+ * @memberOf _
10538
+ * @category String
10539
+ * @param {string} [string=''] The string to escape.
10540
+ * @returns {string} Returns the escaped string.
10541
+ * @example
10542
+ *
10543
+ * _.escapeRegExp('[lodash](https://lodash.com/)');
10544
+ * // => '\[lodash\]\(https:\/\/lodash\.com\/\)'
10545
+ */
10546
+ function escapeRegExp(string) {
10547
+ string = baseToString(string);
10548
+ return (string && reHasRegExpChars.test(string))
10549
+ ? string.replace(reRegExpChars, escapeRegExpChar)
10550
+ : (string || '(?:)');
10551
+ }
10552
+
10553
+ /**
10554
+ * Converts `string` to [kebab case](https://en.wikipedia.org/wiki/Letter_case#Special_case_styles).
10555
+ *
10556
+ * @static
10557
+ * @memberOf _
10558
+ * @category String
10559
+ * @param {string} [string=''] The string to convert.
10560
+ * @returns {string} Returns the kebab cased string.
10561
+ * @example
10562
+ *
10563
+ * _.kebabCase('Foo Bar');
10564
+ * // => 'foo-bar'
10565
+ *
10566
+ * _.kebabCase('fooBar');
10567
+ * // => 'foo-bar'
10568
+ *
10569
+ * _.kebabCase('__foo_bar__');
10570
+ * // => 'foo-bar'
10571
+ */
10572
+ var kebabCase = createCompounder(function(result, word, index) {
10573
+ return result + (index ? '-' : '') + word.toLowerCase();
10574
+ });
10575
+
10576
+ /**
10577
+ * Pads `string` on the left and right sides if it's shorter than `length`.
10578
+ * Padding characters are truncated if they can't be evenly divided by `length`.
10579
+ *
10580
+ * @static
10581
+ * @memberOf _
10582
+ * @category String
10583
+ * @param {string} [string=''] The string to pad.
10584
+ * @param {number} [length=0] The padding length.
10585
+ * @param {string} [chars=' '] The string used as padding.
10586
+ * @returns {string} Returns the padded string.
10587
+ * @example
10588
+ *
10589
+ * _.pad('abc', 8);
10590
+ * // => ' abc '
10591
+ *
10592
+ * _.pad('abc', 8, '_-');
10593
+ * // => '_-abc_-_'
10594
+ *
10595
+ * _.pad('abc', 3);
10596
+ * // => 'abc'
10597
+ */
10598
+ function pad(string, length, chars) {
10599
+ string = baseToString(string);
10600
+ length = +length;
10601
+
10602
+ var strLength = string.length;
10603
+ if (strLength >= length || !nativeIsFinite(length)) {
10604
+ return string;
10605
+ }
10606
+ var mid = (length - strLength) / 2,
10607
+ leftLength = nativeFloor(mid),
10608
+ rightLength = nativeCeil(mid);
10609
+
10610
+ chars = createPadding('', rightLength, chars);
10611
+ return chars.slice(0, leftLength) + string + chars;
10612
+ }
10613
+
10614
+ /**
10615
+ * Pads `string` on the left side if it's shorter than `length`. Padding
10616
+ * characters are truncated if they exceed `length`.
10617
+ *
10618
+ * @static
10619
+ * @memberOf _
10620
+ * @category String
10621
+ * @param {string} [string=''] The string to pad.
10622
+ * @param {number} [length=0] The padding length.
10623
+ * @param {string} [chars=' '] The string used as padding.
10624
+ * @returns {string} Returns the padded string.
10625
+ * @example
10626
+ *
10627
+ * _.padLeft('abc', 6);
10628
+ * // => ' abc'
10629
+ *
10630
+ * _.padLeft('abc', 6, '_-');
10631
+ * // => '_-_abc'
10632
+ *
10633
+ * _.padLeft('abc', 3);
10634
+ * // => 'abc'
10635
+ */
10636
+ var padLeft = createPadDir();
10637
+
10638
+ /**
10639
+ * Pads `string` on the right side if it's shorter than `length`. Padding
10640
+ * characters are truncated if they exceed `length`.
10641
+ *
10642
+ * @static
10643
+ * @memberOf _
10644
+ * @category String
10645
+ * @param {string} [string=''] The string to pad.
10646
+ * @param {number} [length=0] The padding length.
10647
+ * @param {string} [chars=' '] The string used as padding.
10648
+ * @returns {string} Returns the padded string.
10649
+ * @example
10650
+ *
10651
+ * _.padRight('abc', 6);
10652
+ * // => 'abc '
10653
+ *
10654
+ * _.padRight('abc', 6, '_-');
10655
+ * // => 'abc_-_'
10656
+ *
10657
+ * _.padRight('abc', 3);
10658
+ * // => 'abc'
10659
+ */
10660
+ var padRight = createPadDir(true);
10661
+
10662
+ /**
10663
+ * Converts `string` to an integer of the specified radix. If `radix` is
10664
+ * `undefined` or `0`, a `radix` of `10` is used unless `value` is a hexadecimal,
10665
+ * in which case a `radix` of `16` is used.
10666
+ *
10667
+ * **Note:** This method aligns with the [ES5 implementation](https://es5.github.io/#E)
10668
+ * of `parseInt`.
10669
+ *
10670
+ * @static
10671
+ * @memberOf _
10672
+ * @category String
10673
+ * @param {string} string The string to convert.
10674
+ * @param {number} [radix] The radix to interpret `value` by.
10675
+ * @param- {Object} [guard] Enables use as a callback for functions like `_.map`.
10676
+ * @returns {number} Returns the converted integer.
10677
+ * @example
10678
+ *
10679
+ * _.parseInt('08');
10680
+ * // => 8
10681
+ *
10682
+ * _.map(['6', '08', '10'], _.parseInt);
10683
+ * // => [6, 8, 10]
10684
+ */
10685
+ function parseInt(string, radix, guard) {
10686
+ // Firefox < 21 and Opera < 15 follow ES3 for `parseInt`.
10687
+ // Chrome fails to trim leading <BOM> whitespace characters.
10688
+ // See https://code.google.com/p/v8/issues/detail?id=3109 for more details.
10689
+ if (guard ? isIterateeCall(string, radix, guard) : radix == null) {
10690
+ radix = 0;
10691
+ } else if (radix) {
10692
+ radix = +radix;
10693
+ }
10694
+ string = trim(string);
10695
+ return nativeParseInt(string, radix || (reHasHexPrefix.test(string) ? 16 : 10));
10696
+ }
10697
+
10698
+ /**
10699
+ * Repeats the given string `n` times.
10700
+ *
10701
+ * @static
10702
+ * @memberOf _
10703
+ * @category String
10704
+ * @param {string} [string=''] The string to repeat.
10705
+ * @param {number} [n=0] The number of times to repeat the string.
10706
+ * @returns {string} Returns the repeated string.
10707
+ * @example
10708
+ *
10709
+ * _.repeat('*', 3);
10710
+ * // => '***'
10711
+ *
10712
+ * _.repeat('abc', 2);
10713
+ * // => 'abcabc'
10714
+ *
10715
+ * _.repeat('abc', 0);
10716
+ * // => ''
10717
+ */
10718
+ function repeat(string, n) {
10719
+ var result = '';
10720
+ string = baseToString(string);
10721
+ n = +n;
10722
+ if (n < 1 || !string || !nativeIsFinite(n)) {
10723
+ return result;
10724
+ }
10725
+ // Leverage the exponentiation by squaring algorithm for a faster repeat.
10726
+ // See https://en.wikipedia.org/wiki/Exponentiation_by_squaring for more details.
10727
+ do {
10728
+ if (n % 2) {
10729
+ result += string;
10730
+ }
10731
+ n = nativeFloor(n / 2);
10732
+ string += string;
10733
+ } while (n);
10734
+
10735
+ return result;
10736
+ }
10737
+
10738
+ /**
10739
+ * Converts `string` to [snake case](https://en.wikipedia.org/wiki/Snake_case).
10740
+ *
10741
+ * @static
10742
+ * @memberOf _
10743
+ * @category String
10744
+ * @param {string} [string=''] The string to convert.
10745
+ * @returns {string} Returns the snake cased string.
10746
+ * @example
10747
+ *
10748
+ * _.snakeCase('Foo Bar');
10749
+ * // => 'foo_bar'
10750
+ *
10751
+ * _.snakeCase('fooBar');
10752
+ * // => 'foo_bar'
10753
+ *
10754
+ * _.snakeCase('--foo-bar');
10755
+ * // => 'foo_bar'
10756
+ */
10757
+ var snakeCase = createCompounder(function(result, word, index) {
10758
+ return result + (index ? '_' : '') + word.toLowerCase();
10759
+ });
10760
+
10761
+ /**
10762
+ * Converts `string` to [start case](https://en.wikipedia.org/wiki/Letter_case#Stylistic_or_specialised_usage).
10763
+ *
10764
+ * @static
10765
+ * @memberOf _
10766
+ * @category String
10767
+ * @param {string} [string=''] The string to convert.
10768
+ * @returns {string} Returns the start cased string.
10769
+ * @example
10770
+ *
10771
+ * _.startCase('--foo-bar');
10772
+ * // => 'Foo Bar'
10773
+ *
10774
+ * _.startCase('fooBar');
10775
+ * // => 'Foo Bar'
10776
+ *
10777
+ * _.startCase('__foo_bar__');
10778
+ * // => 'Foo Bar'
10779
+ */
10780
+ var startCase = createCompounder(function(result, word, index) {
10781
+ return result + (index ? ' ' : '') + (word.charAt(0).toUpperCase() + word.slice(1));
10782
+ });
10783
+
10784
+ /**
10785
+ * Checks if `string` starts with the given target string.
10786
+ *
10787
+ * @static
10788
+ * @memberOf _
10789
+ * @category String
10790
+ * @param {string} [string=''] The string to search.
10791
+ * @param {string} [target] The string to search for.
10792
+ * @param {number} [position=0] The position to search from.
10793
+ * @returns {boolean} Returns `true` if `string` starts with `target`, else `false`.
10794
+ * @example
10795
+ *
10796
+ * _.startsWith('abc', 'a');
10797
+ * // => true
10798
+ *
10799
+ * _.startsWith('abc', 'b');
10800
+ * // => false
10801
+ *
10802
+ * _.startsWith('abc', 'b', 1);
10803
+ * // => true
10804
+ */
10805
+ function startsWith(string, target, position) {
10806
+ string = baseToString(string);
10807
+ position = position == null
10808
+ ? 0
10809
+ : nativeMin(position < 0 ? 0 : (+position || 0), string.length);
10810
+
10811
+ return string.lastIndexOf(target, position) == position;
10812
+ }
10813
+
10814
+ /**
10815
+ * Creates a compiled template function that can interpolate data properties
10816
+ * in "interpolate" delimiters, HTML-escape interpolated data properties in
10817
+ * "escape" delimiters, and execute JavaScript in "evaluate" delimiters. Data
10818
+ * properties may be accessed as free variables in the template. If a setting
10819
+ * object is provided it takes precedence over `_.templateSettings` values.
10820
+ *
10821
+ * **Note:** In the development build `_.template` utilizes
10822
+ * [sourceURLs](http://www.html5rocks.com/en/tutorials/developertools/sourcemaps/#toc-sourceurl)
10823
+ * for easier debugging.
10824
+ *
10825
+ * For more information on precompiling templates see
10826
+ * [lodash's custom builds documentation](https://lodash.com/custom-builds).
10827
+ *
10828
+ * For more information on Chrome extension sandboxes see
10829
+ * [Chrome's extensions documentation](https://developer.chrome.com/extensions/sandboxingEval).
10830
+ *
10831
+ * @static
10832
+ * @memberOf _
10833
+ * @category String
10834
+ * @param {string} [string=''] The template string.
10835
+ * @param {Object} [options] The options object.
10836
+ * @param {RegExp} [options.escape] The HTML "escape" delimiter.
10837
+ * @param {RegExp} [options.evaluate] The "evaluate" delimiter.
10838
+ * @param {Object} [options.imports] An object to import into the template as free variables.
10839
+ * @param {RegExp} [options.interpolate] The "interpolate" delimiter.
10840
+ * @param {string} [options.sourceURL] The sourceURL of the template's compiled source.
10841
+ * @param {string} [options.variable] The data object variable name.
10842
+ * @param- {Object} [otherOptions] Enables the legacy `options` param signature.
10843
+ * @returns {Function} Returns the compiled template function.
10844
+ * @example
10845
+ *
10846
+ * // using the "interpolate" delimiter to create a compiled template
10847
+ * var compiled = _.template('hello <%= user %>!');
10848
+ * compiled({ 'user': 'fred' });
10849
+ * // => 'hello fred!'
10850
+ *
10851
+ * // using the HTML "escape" delimiter to escape data property values
10852
+ * var compiled = _.template('<b><%- value %></b>');
10853
+ * compiled({ 'value': '<script>' });
10854
+ * // => '<b>&lt;script&gt;</b>'
10855
+ *
10856
+ * // using the "evaluate" delimiter to execute JavaScript and generate HTML
10857
+ * var compiled = _.template('<% _.forEach(users, function(user) { %><li><%- user %></li><% }); %>');
10858
+ * compiled({ 'users': ['fred', 'barney'] });
10859
+ * // => '<li>fred</li><li>barney</li>'
10860
+ *
10861
+ * // using the internal `print` function in "evaluate" delimiters
10862
+ * var compiled = _.template('<% print("hello " + user); %>!');
10863
+ * compiled({ 'user': 'barney' });
10864
+ * // => 'hello barney!'
10865
+ *
10866
+ * // using the ES delimiter as an alternative to the default "interpolate" delimiter
10867
+ * var compiled = _.template('hello ${ user }!');
10868
+ * compiled({ 'user': 'pebbles' });
10869
+ * // => 'hello pebbles!'
10870
+ *
10871
+ * // using custom template delimiters
10872
+ * _.templateSettings.interpolate = /{{([\s\S]+?)}}/g;
10873
+ * var compiled = _.template('hello {{ user }}!');
10874
+ * compiled({ 'user': 'mustache' });
10875
+ * // => 'hello mustache!'
10876
+ *
10877
+ * // using backslashes to treat delimiters as plain text
10878
+ * var compiled = _.template('<%= "\\<%- value %\\>" %>');
10879
+ * compiled({ 'value': 'ignored' });
10880
+ * // => '<%- value %>'
10881
+ *
10882
+ * // using the `imports` option to import `jQuery` as `jq`
10883
+ * var text = '<% jq.each(users, function(user) { %><li><%- user %></li><% }); %>';
10884
+ * var compiled = _.template(text, { 'imports': { 'jq': jQuery } });
10885
+ * compiled({ 'users': ['fred', 'barney'] });
10886
+ * // => '<li>fred</li><li>barney</li>'
10887
+ *
10888
+ * // using the `sourceURL` option to specify a custom sourceURL for the template
10889
+ * var compiled = _.template('hello <%= user %>!', { 'sourceURL': '/basic/greeting.jst' });
10890
+ * compiled(data);
10891
+ * // => find the source of "greeting.jst" under the Sources tab or Resources panel of the web inspector
10892
+ *
10893
+ * // using the `variable` option to ensure a with-statement isn't used in the compiled template
10894
+ * var compiled = _.template('hi <%= data.user %>!', { 'variable': 'data' });
10895
+ * compiled.source;
10896
+ * // => function(data) {
10897
+ * // var __t, __p = '';
10898
+ * // __p += 'hi ' + ((__t = ( data.user )) == null ? '' : __t) + '!';
10899
+ * // return __p;
10900
+ * // }
10901
+ *
10902
+ * // using the `source` property to inline compiled templates for meaningful
10903
+ * // line numbers in error messages and a stack trace
10904
+ * fs.writeFileSync(path.join(cwd, 'jst.js'), '\
10905
+ * var JST = {\
10906
+ * "main": ' + _.template(mainText).source + '\
10907
+ * };\
10908
+ * ');
10909
+ */
10910
+ function template(string, options, otherOptions) {
10911
+ // Based on John Resig's `tmpl` implementation (http://ejohn.org/blog/javascript-micro-templating/)
10912
+ // and Laura Doktorova's doT.js (https://github.com/olado/doT).
10913
+ var settings = lodash.templateSettings;
10914
+
10915
+ if (otherOptions && isIterateeCall(string, options, otherOptions)) {
10916
+ options = otherOptions = undefined;
10917
+ }
10918
+ string = baseToString(string);
10919
+ options = assignWith(baseAssign({}, otherOptions || options), settings, assignOwnDefaults);
10920
+
10921
+ var imports = assignWith(baseAssign({}, options.imports), settings.imports, assignOwnDefaults),
10922
+ importsKeys = keys(imports),
10923
+ importsValues = baseValues(imports, importsKeys);
10924
+
10925
+ var isEscaping,
10926
+ isEvaluating,
10927
+ index = 0,
10928
+ interpolate = options.interpolate || reNoMatch,
10929
+ source = "__p += '";
10930
+
10931
+ // Compile the regexp to match each delimiter.
10932
+ var reDelimiters = RegExp(
10933
+ (options.escape || reNoMatch).source + '|' +
10934
+ interpolate.source + '|' +
10935
+ (interpolate === reInterpolate ? reEsTemplate : reNoMatch).source + '|' +
10936
+ (options.evaluate || reNoMatch).source + '|$'
10937
+ , 'g');
10938
+
10939
+ // Use a sourceURL for easier debugging.
10940
+ var sourceURL = '//# sourceURL=' +
10941
+ ('sourceURL' in options
10942
+ ? options.sourceURL
10943
+ : ('lodash.templateSources[' + (++templateCounter) + ']')
10944
+ ) + '\n';
10945
+
10946
+ string.replace(reDelimiters, function(match, escapeValue, interpolateValue, esTemplateValue, evaluateValue, offset) {
10947
+ interpolateValue || (interpolateValue = esTemplateValue);
10948
+
10949
+ // Escape characters that can't be included in string literals.
10950
+ source += string.slice(index, offset).replace(reUnescapedString, escapeStringChar);
10951
+
10952
+ // Replace delimiters with snippets.
10953
+ if (escapeValue) {
10954
+ isEscaping = true;
10955
+ source += "' +\n__e(" + escapeValue + ") +\n'";
10956
+ }
10957
+ if (evaluateValue) {
10958
+ isEvaluating = true;
10959
+ source += "';\n" + evaluateValue + ";\n__p += '";
10960
+ }
10961
+ if (interpolateValue) {
10962
+ source += "' +\n((__t = (" + interpolateValue + ")) == null ? '' : __t) +\n'";
10963
+ }
10964
+ index = offset + match.length;
10965
+
10966
+ // The JS engine embedded in Adobe products requires returning the `match`
10967
+ // string in order to produce the correct `offset` value.
10968
+ return match;
10969
+ });
10970
+
10971
+ source += "';\n";
10972
+
10973
+ // If `variable` is not specified wrap a with-statement around the generated
10974
+ // code to add the data object to the top of the scope chain.
10975
+ var variable = options.variable;
10976
+ if (!variable) {
10977
+ source = 'with (obj) {\n' + source + '\n}\n';
10978
+ }
10979
+ // Cleanup code by stripping empty strings.
10980
+ source = (isEvaluating ? source.replace(reEmptyStringLeading, '') : source)
10981
+ .replace(reEmptyStringMiddle, '$1')
10982
+ .replace(reEmptyStringTrailing, '$1;');
10983
+
10984
+ // Frame code as the function body.
10985
+ source = 'function(' + (variable || 'obj') + ') {\n' +
10986
+ (variable
10987
+ ? ''
10988
+ : 'obj || (obj = {});\n'
10989
+ ) +
10990
+ "var __t, __p = ''" +
10991
+ (isEscaping
10992
+ ? ', __e = _.escape'
10993
+ : ''
10994
+ ) +
10995
+ (isEvaluating
10996
+ ? ', __j = Array.prototype.join;\n' +
10997
+ "function print() { __p += __j.call(arguments, '') }\n"
10998
+ : ';\n'
10999
+ ) +
11000
+ source +
11001
+ 'return __p\n}';
11002
+
11003
+ var result = attempt(function() {
11004
+ return Function(importsKeys, sourceURL + 'return ' + source).apply(undefined, importsValues);
11005
+ });
11006
+
11007
+ // Provide the compiled function's source by its `toString` method or
11008
+ // the `source` property as a convenience for inlining compiled templates.
11009
+ result.source = source;
11010
+ if (isError(result)) {
11011
+ throw result;
11012
+ }
11013
+ return result;
11014
+ }
11015
+
11016
+ /**
11017
+ * Removes leading and trailing whitespace or specified characters from `string`.
11018
+ *
11019
+ * @static
11020
+ * @memberOf _
11021
+ * @category String
11022
+ * @param {string} [string=''] The string to trim.
11023
+ * @param {string} [chars=whitespace] The characters to trim.
11024
+ * @param- {Object} [guard] Enables use as a callback for functions like `_.map`.
11025
+ * @returns {string} Returns the trimmed string.
11026
+ * @example
11027
+ *
11028
+ * _.trim(' abc ');
11029
+ * // => 'abc'
11030
+ *
11031
+ * _.trim('-_-abc-_-', '_-');
11032
+ * // => 'abc'
11033
+ *
11034
+ * _.map([' foo ', ' bar '], _.trim);
11035
+ * // => ['foo', 'bar']
11036
+ */
11037
+ function trim(string, chars, guard) {
11038
+ var value = string;
11039
+ string = baseToString(string);
11040
+ if (!string) {
11041
+ return string;
11042
+ }
11043
+ if (guard ? isIterateeCall(value, chars, guard) : chars == null) {
11044
+ return string.slice(trimmedLeftIndex(string), trimmedRightIndex(string) + 1);
11045
+ }
11046
+ chars = (chars + '');
11047
+ return string.slice(charsLeftIndex(string, chars), charsRightIndex(string, chars) + 1);
11048
+ }
11049
+
11050
+ /**
11051
+ * Removes leading whitespace or specified characters from `string`.
11052
+ *
11053
+ * @static
11054
+ * @memberOf _
11055
+ * @category String
11056
+ * @param {string} [string=''] The string to trim.
11057
+ * @param {string} [chars=whitespace] The characters to trim.
11058
+ * @param- {Object} [guard] Enables use as a callback for functions like `_.map`.
11059
+ * @returns {string} Returns the trimmed string.
11060
+ * @example
11061
+ *
11062
+ * _.trimLeft(' abc ');
11063
+ * // => 'abc '
11064
+ *
11065
+ * _.trimLeft('-_-abc-_-', '_-');
11066
+ * // => 'abc-_-'
11067
+ */
11068
+ function trimLeft(string, chars, guard) {
11069
+ var value = string;
11070
+ string = baseToString(string);
11071
+ if (!string) {
11072
+ return string;
11073
+ }
11074
+ if (guard ? isIterateeCall(value, chars, guard) : chars == null) {
11075
+ return string.slice(trimmedLeftIndex(string));
11076
+ }
11077
+ return string.slice(charsLeftIndex(string, (chars + '')));
11078
+ }
11079
+
11080
+ /**
11081
+ * Removes trailing whitespace or specified characters from `string`.
11082
+ *
11083
+ * @static
11084
+ * @memberOf _
11085
+ * @category String
11086
+ * @param {string} [string=''] The string to trim.
11087
+ * @param {string} [chars=whitespace] The characters to trim.
11088
+ * @param- {Object} [guard] Enables use as a callback for functions like `_.map`.
11089
+ * @returns {string} Returns the trimmed string.
11090
+ * @example
11091
+ *
11092
+ * _.trimRight(' abc ');
11093
+ * // => ' abc'
11094
+ *
11095
+ * _.trimRight('-_-abc-_-', '_-');
11096
+ * // => '-_-abc'
11097
+ */
11098
+ function trimRight(string, chars, guard) {
11099
+ var value = string;
11100
+ string = baseToString(string);
11101
+ if (!string) {
11102
+ return string;
11103
+ }
11104
+ if (guard ? isIterateeCall(value, chars, guard) : chars == null) {
11105
+ return string.slice(0, trimmedRightIndex(string) + 1);
11106
+ }
11107
+ return string.slice(0, charsRightIndex(string, (chars + '')) + 1);
11108
+ }
11109
+
11110
+ /**
11111
+ * Truncates `string` if it's longer than the given maximum string length.
11112
+ * The last characters of the truncated string are replaced with the omission
11113
+ * string which defaults to "...".
11114
+ *
11115
+ * @static
11116
+ * @memberOf _
11117
+ * @category String
11118
+ * @param {string} [string=''] The string to truncate.
11119
+ * @param {Object|number} [options] The options object or maximum string length.
11120
+ * @param {number} [options.length=30] The maximum string length.
11121
+ * @param {string} [options.omission='...'] The string to indicate text is omitted.
11122
+ * @param {RegExp|string} [options.separator] The separator pattern to truncate to.
11123
+ * @param- {Object} [guard] Enables use as a callback for functions like `_.map`.
11124
+ * @returns {string} Returns the truncated string.
11125
+ * @example
11126
+ *
11127
+ * _.trunc('hi-diddly-ho there, neighborino');
11128
+ * // => 'hi-diddly-ho there, neighbo...'
11129
+ *
11130
+ * _.trunc('hi-diddly-ho there, neighborino', 24);
11131
+ * // => 'hi-diddly-ho there, n...'
11132
+ *
11133
+ * _.trunc('hi-diddly-ho there, neighborino', {
11134
+ * 'length': 24,
11135
+ * 'separator': ' '
11136
+ * });
11137
+ * // => 'hi-diddly-ho there,...'
11138
+ *
11139
+ * _.trunc('hi-diddly-ho there, neighborino', {
11140
+ * 'length': 24,
11141
+ * 'separator': /,? +/
11142
+ * });
11143
+ * // => 'hi-diddly-ho there...'
11144
+ *
11145
+ * _.trunc('hi-diddly-ho there, neighborino', {
11146
+ * 'omission': ' [...]'
11147
+ * });
11148
+ * // => 'hi-diddly-ho there, neig [...]'
11149
+ */
11150
+ function trunc(string, options, guard) {
11151
+ if (guard && isIterateeCall(string, options, guard)) {
11152
+ options = undefined;
11153
+ }
11154
+ var length = DEFAULT_TRUNC_LENGTH,
11155
+ omission = DEFAULT_TRUNC_OMISSION;
11156
+
11157
+ if (options != null) {
11158
+ if (isObject(options)) {
11159
+ var separator = 'separator' in options ? options.separator : separator;
11160
+ length = 'length' in options ? (+options.length || 0) : length;
11161
+ omission = 'omission' in options ? baseToString(options.omission) : omission;
11162
+ } else {
11163
+ length = +options || 0;
11164
+ }
11165
+ }
11166
+ string = baseToString(string);
11167
+ if (length >= string.length) {
11168
+ return string;
11169
+ }
11170
+ var end = length - omission.length;
11171
+ if (end < 1) {
11172
+ return omission;
11173
+ }
11174
+ var result = string.slice(0, end);
11175
+ if (separator == null) {
11176
+ return result + omission;
11177
+ }
11178
+ if (isRegExp(separator)) {
11179
+ if (string.slice(end).search(separator)) {
11180
+ var match,
11181
+ newEnd,
11182
+ substring = string.slice(0, end);
11183
+
11184
+ if (!separator.global) {
11185
+ separator = RegExp(separator.source, (reFlags.exec(separator) || '') + 'g');
11186
+ }
11187
+ separator.lastIndex = 0;
11188
+ while ((match = separator.exec(substring))) {
11189
+ newEnd = match.index;
11190
+ }
11191
+ result = result.slice(0, newEnd == null ? end : newEnd);
11192
+ }
11193
+ } else if (string.indexOf(separator, end) != end) {
11194
+ var index = result.lastIndexOf(separator);
11195
+ if (index > -1) {
11196
+ result = result.slice(0, index);
11197
+ }
11198
+ }
11199
+ return result + omission;
11200
+ }
11201
+
11202
+ /**
11203
+ * The inverse of `_.escape`; this method converts the HTML entities
11204
+ * `&amp;`, `&lt;`, `&gt;`, `&quot;`, `&#39;`, and `&#96;` in `string` to their
11205
+ * corresponding characters.
11206
+ *
11207
+ * **Note:** No other HTML entities are unescaped. To unescape additional HTML
11208
+ * entities use a third-party library like [_he_](https://mths.be/he).
11209
+ *
11210
+ * @static
11211
+ * @memberOf _
11212
+ * @category String
11213
+ * @param {string} [string=''] The string to unescape.
11214
+ * @returns {string} Returns the unescaped string.
11215
+ * @example
11216
+ *
11217
+ * _.unescape('fred, barney, &amp; pebbles');
11218
+ * // => 'fred, barney, & pebbles'
11219
+ */
11220
+ function unescape(string) {
11221
+ string = baseToString(string);
11222
+ return (string && reHasEscapedHtml.test(string))
11223
+ ? string.replace(reEscapedHtml, unescapeHtmlChar)
11224
+ : string;
11225
+ }
11226
+
11227
+ /**
11228
+ * Splits `string` into an array of its words.
11229
+ *
11230
+ * @static
11231
+ * @memberOf _
11232
+ * @category String
11233
+ * @param {string} [string=''] The string to inspect.
11234
+ * @param {RegExp|string} [pattern] The pattern to match words.
11235
+ * @param- {Object} [guard] Enables use as a callback for functions like `_.map`.
11236
+ * @returns {Array} Returns the words of `string`.
11237
+ * @example
11238
+ *
11239
+ * _.words('fred, barney, & pebbles');
11240
+ * // => ['fred', 'barney', 'pebbles']
11241
+ *
11242
+ * _.words('fred, barney, & pebbles', /[^, ]+/g);
11243
+ * // => ['fred', 'barney', '&', 'pebbles']
11244
+ */
11245
+ function words(string, pattern, guard) {
11246
+ if (guard && isIterateeCall(string, pattern, guard)) {
11247
+ pattern = undefined;
11248
+ }
11249
+ string = baseToString(string);
11250
+ return string.match(pattern || reWords) || [];
11251
+ }
11252
+
11253
+ /*------------------------------------------------------------------------*/
11254
+
11255
+ /**
11256
+ * Attempts to invoke `func`, returning either the result or the caught error
11257
+ * object. Any additional arguments are provided to `func` when it's invoked.
11258
+ *
11259
+ * @static
11260
+ * @memberOf _
11261
+ * @category Utility
11262
+ * @param {Function} func The function to attempt.
11263
+ * @returns {*} Returns the `func` result or error object.
11264
+ * @example
11265
+ *
11266
+ * // avoid throwing errors for invalid selectors
11267
+ * var elements = _.attempt(function(selector) {
11268
+ * return document.querySelectorAll(selector);
11269
+ * }, '>_>');
11270
+ *
11271
+ * if (_.isError(elements)) {
11272
+ * elements = [];
11273
+ * }
11274
+ */
11275
+ var attempt = restParam(function(func, args) {
11276
+ try {
11277
+ return func.apply(undefined, args);
11278
+ } catch(e) {
11279
+ return isError(e) ? e : new Error(e);
11280
+ }
11281
+ });
11282
+
11283
+ /**
11284
+ * Creates a function that invokes `func` with the `this` binding of `thisArg`
11285
+ * and arguments of the created function. If `func` is a property name the
11286
+ * created callback returns the property value for a given element. If `func`
11287
+ * is an object the created callback returns `true` for elements that contain
11288
+ * the equivalent object properties, otherwise it returns `false`.
11289
+ *
11290
+ * @static
11291
+ * @memberOf _
11292
+ * @alias iteratee
11293
+ * @category Utility
11294
+ * @param {*} [func=_.identity] The value to convert to a callback.
11295
+ * @param {*} [thisArg] The `this` binding of `func`.
11296
+ * @param- {Object} [guard] Enables use as a callback for functions like `_.map`.
11297
+ * @returns {Function} Returns the callback.
11298
+ * @example
11299
+ *
11300
+ * var users = [
11301
+ * { 'user': 'barney', 'age': 36 },
11302
+ * { 'user': 'fred', 'age': 40 }
11303
+ * ];
11304
+ *
11305
+ * // wrap to create custom callback shorthands
11306
+ * _.callback = _.wrap(_.callback, function(callback, func, thisArg) {
11307
+ * var match = /^(.+?)__([gl]t)(.+)$/.exec(func);
11308
+ * if (!match) {
11309
+ * return callback(func, thisArg);
11310
+ * }
11311
+ * return function(object) {
11312
+ * return match[2] == 'gt'
11313
+ * ? object[match[1]] > match[3]
11314
+ * : object[match[1]] < match[3];
11315
+ * };
11316
+ * });
11317
+ *
11318
+ * _.filter(users, 'age__gt36');
11319
+ * // => [{ 'user': 'fred', 'age': 40 }]
11320
+ */
11321
+ function callback(func, thisArg, guard) {
11322
+ if (guard && isIterateeCall(func, thisArg, guard)) {
11323
+ thisArg = undefined;
11324
+ }
11325
+ return isObjectLike(func)
11326
+ ? matches(func)
11327
+ : baseCallback(func, thisArg);
11328
+ }
11329
+
11330
+ /**
11331
+ * Creates a function that returns `value`.
11332
+ *
11333
+ * @static
11334
+ * @memberOf _
11335
+ * @category Utility
11336
+ * @param {*} value The value to return from the new function.
11337
+ * @returns {Function} Returns the new function.
11338
+ * @example
11339
+ *
11340
+ * var object = { 'user': 'fred' };
11341
+ * var getter = _.constant(object);
11342
+ *
11343
+ * getter() === object;
11344
+ * // => true
11345
+ */
11346
+ function constant(value) {
11347
+ return function() {
11348
+ return value;
11349
+ };
11350
+ }
11351
+
11352
+ /**
11353
+ * This method returns the first argument provided to it.
11354
+ *
11355
+ * @static
11356
+ * @memberOf _
11357
+ * @category Utility
11358
+ * @param {*} value Any value.
11359
+ * @returns {*} Returns `value`.
11360
+ * @example
11361
+ *
11362
+ * var object = { 'user': 'fred' };
11363
+ *
11364
+ * _.identity(object) === object;
11365
+ * // => true
11366
+ */
11367
+ function identity(value) {
11368
+ return value;
11369
+ }
11370
+
11371
+ /**
11372
+ * Creates a function that performs a deep comparison between a given object
11373
+ * and `source`, returning `true` if the given object has equivalent property
11374
+ * values, else `false`.
11375
+ *
11376
+ * **Note:** This method supports comparing arrays, booleans, `Date` objects,
11377
+ * numbers, `Object` objects, regexes, and strings. Objects are compared by
11378
+ * their own, not inherited, enumerable properties. For comparing a single
11379
+ * own or inherited property value see `_.matchesProperty`.
11380
+ *
11381
+ * @static
11382
+ * @memberOf _
11383
+ * @category Utility
11384
+ * @param {Object} source The object of property values to match.
11385
+ * @returns {Function} Returns the new function.
11386
+ * @example
11387
+ *
11388
+ * var users = [
11389
+ * { 'user': 'barney', 'age': 36, 'active': true },
11390
+ * { 'user': 'fred', 'age': 40, 'active': false }
11391
+ * ];
11392
+ *
11393
+ * _.filter(users, _.matches({ 'age': 40, 'active': false }));
11394
+ * // => [{ 'user': 'fred', 'age': 40, 'active': false }]
11395
+ */
11396
+ function matches(source) {
11397
+ return baseMatches(baseClone(source, true));
11398
+ }
11399
+
11400
+ /**
11401
+ * Creates a function that compares the property value of `path` on a given
11402
+ * object to `value`.
11403
+ *
11404
+ * **Note:** This method supports comparing arrays, booleans, `Date` objects,
11405
+ * numbers, `Object` objects, regexes, and strings. Objects are compared by
11406
+ * their own, not inherited, enumerable properties.
11407
+ *
11408
+ * @static
11409
+ * @memberOf _
11410
+ * @category Utility
11411
+ * @param {Array|string} path The path of the property to get.
11412
+ * @param {*} srcValue The value to match.
11413
+ * @returns {Function} Returns the new function.
11414
+ * @example
11415
+ *
11416
+ * var users = [
11417
+ * { 'user': 'barney' },
11418
+ * { 'user': 'fred' }
11419
+ * ];
11420
+ *
11421
+ * _.find(users, _.matchesProperty('user', 'fred'));
11422
+ * // => { 'user': 'fred' }
11423
+ */
11424
+ function matchesProperty(path, srcValue) {
11425
+ return baseMatchesProperty(path, baseClone(srcValue, true));
11426
+ }
11427
+
11428
+ /**
11429
+ * Creates a function that invokes the method at `path` on a given object.
11430
+ * Any additional arguments are provided to the invoked method.
11431
+ *
11432
+ * @static
11433
+ * @memberOf _
11434
+ * @category Utility
11435
+ * @param {Array|string} path The path of the method to invoke.
11436
+ * @param {...*} [args] The arguments to invoke the method with.
11437
+ * @returns {Function} Returns the new function.
11438
+ * @example
11439
+ *
11440
+ * var objects = [
11441
+ * { 'a': { 'b': { 'c': _.constant(2) } } },
11442
+ * { 'a': { 'b': { 'c': _.constant(1) } } }
11443
+ * ];
11444
+ *
11445
+ * _.map(objects, _.method('a.b.c'));
11446
+ * // => [2, 1]
11447
+ *
11448
+ * _.invoke(_.sortBy(objects, _.method(['a', 'b', 'c'])), 'a.b.c');
11449
+ * // => [1, 2]
11450
+ */
11451
+ var method = restParam(function(path, args) {
11452
+ return function(object) {
11453
+ return invokePath(object, path, args);
11454
+ };
11455
+ });
11456
+
11457
+ /**
11458
+ * The opposite of `_.method`; this method creates a function that invokes
11459
+ * the method at a given path on `object`. Any additional arguments are
11460
+ * provided to the invoked method.
11461
+ *
11462
+ * @static
11463
+ * @memberOf _
11464
+ * @category Utility
11465
+ * @param {Object} object The object to query.
11466
+ * @param {...*} [args] The arguments to invoke the method with.
11467
+ * @returns {Function} Returns the new function.
11468
+ * @example
11469
+ *
11470
+ * var array = _.times(3, _.constant),
11471
+ * object = { 'a': array, 'b': array, 'c': array };
11472
+ *
11473
+ * _.map(['a[2]', 'c[0]'], _.methodOf(object));
11474
+ * // => [2, 0]
11475
+ *
11476
+ * _.map([['a', '2'], ['c', '0']], _.methodOf(object));
11477
+ * // => [2, 0]
11478
+ */
11479
+ var methodOf = restParam(function(object, args) {
11480
+ return function(path) {
11481
+ return invokePath(object, path, args);
11482
+ };
11483
+ });
11484
+
11485
+ /**
11486
+ * Adds all own enumerable function properties of a source object to the
11487
+ * destination object. If `object` is a function then methods are added to
11488
+ * its prototype as well.
11489
+ *
11490
+ * **Note:** Use `_.runInContext` to create a pristine `lodash` function to
11491
+ * avoid conflicts caused by modifying the original.
11492
+ *
11493
+ * @static
11494
+ * @memberOf _
11495
+ * @category Utility
11496
+ * @param {Function|Object} [object=lodash] The destination object.
11497
+ * @param {Object} source The object of functions to add.
11498
+ * @param {Object} [options] The options object.
11499
+ * @param {boolean} [options.chain=true] Specify whether the functions added
11500
+ * are chainable.
11501
+ * @returns {Function|Object} Returns `object`.
11502
+ * @example
11503
+ *
11504
+ * function vowels(string) {
11505
+ * return _.filter(string, function(v) {
11506
+ * return /[aeiou]/i.test(v);
11507
+ * });
11508
+ * }
11509
+ *
11510
+ * _.mixin({ 'vowels': vowels });
11511
+ * _.vowels('fred');
11512
+ * // => ['e']
11513
+ *
11514
+ * _('fred').vowels().value();
11515
+ * // => ['e']
11516
+ *
11517
+ * _.mixin({ 'vowels': vowels }, { 'chain': false });
11518
+ * _('fred').vowels();
11519
+ * // => ['e']
11520
+ */
11521
+ function mixin(object, source, options) {
11522
+ if (options == null) {
11523
+ var isObj = isObject(source),
11524
+ props = isObj ? keys(source) : undefined,
11525
+ methodNames = (props && props.length) ? baseFunctions(source, props) : undefined;
11526
+
11527
+ if (!(methodNames ? methodNames.length : isObj)) {
11528
+ methodNames = false;
11529
+ options = source;
11530
+ source = object;
11531
+ object = this;
11532
+ }
11533
+ }
11534
+ if (!methodNames) {
11535
+ methodNames = baseFunctions(source, keys(source));
11536
+ }
11537
+ var chain = true,
11538
+ index = -1,
11539
+ isFunc = isFunction(object),
11540
+ length = methodNames.length;
11541
+
11542
+ if (options === false) {
11543
+ chain = false;
11544
+ } else if (isObject(options) && 'chain' in options) {
11545
+ chain = options.chain;
11546
+ }
11547
+ while (++index < length) {
11548
+ var methodName = methodNames[index],
11549
+ func = source[methodName];
11550
+
11551
+ object[methodName] = func;
11552
+ if (isFunc) {
11553
+ object.prototype[methodName] = (function(func) {
11554
+ return function() {
11555
+ var chainAll = this.__chain__;
11556
+ if (chain || chainAll) {
11557
+ var result = object(this.__wrapped__),
11558
+ actions = result.__actions__ = arrayCopy(this.__actions__);
11559
+
11560
+ actions.push({ 'func': func, 'args': arguments, 'thisArg': object });
11561
+ result.__chain__ = chainAll;
11562
+ return result;
11563
+ }
11564
+ return func.apply(object, arrayPush([this.value()], arguments));
11565
+ };
11566
+ }(func));
11567
+ }
11568
+ }
11569
+ return object;
11570
+ }
11571
+
11572
+ /**
11573
+ * Reverts the `_` variable to its previous value and returns a reference to
11574
+ * the `lodash` function.
11575
+ *
11576
+ * @static
11577
+ * @memberOf _
11578
+ * @category Utility
11579
+ * @returns {Function} Returns the `lodash` function.
11580
+ * @example
11581
+ *
11582
+ * var lodash = _.noConflict();
11583
+ */
11584
+ function noConflict() {
11585
+ root._ = oldDash;
11586
+ return this;
11587
+ }
11588
+
11589
+ /**
11590
+ * A no-operation function that returns `undefined` regardless of the
11591
+ * arguments it receives.
11592
+ *
11593
+ * @static
11594
+ * @memberOf _
11595
+ * @category Utility
11596
+ * @example
11597
+ *
11598
+ * var object = { 'user': 'fred' };
11599
+ *
11600
+ * _.noop(object) === undefined;
11601
+ * // => true
11602
+ */
11603
+ function noop() {
11604
+ // No operation performed.
11605
+ }
11606
+
11607
+ /**
11608
+ * Creates a function that returns the property value at `path` on a
11609
+ * given object.
11610
+ *
11611
+ * @static
11612
+ * @memberOf _
11613
+ * @category Utility
11614
+ * @param {Array|string} path The path of the property to get.
11615
+ * @returns {Function} Returns the new function.
11616
+ * @example
11617
+ *
11618
+ * var objects = [
11619
+ * { 'a': { 'b': { 'c': 2 } } },
11620
+ * { 'a': { 'b': { 'c': 1 } } }
11621
+ * ];
11622
+ *
11623
+ * _.map(objects, _.property('a.b.c'));
11624
+ * // => [2, 1]
11625
+ *
11626
+ * _.pluck(_.sortBy(objects, _.property(['a', 'b', 'c'])), 'a.b.c');
11627
+ * // => [1, 2]
11628
+ */
11629
+ function property(path) {
11630
+ return isKey(path) ? baseProperty(path) : basePropertyDeep(path);
11631
+ }
11632
+
11633
+ /**
11634
+ * The opposite of `_.property`; this method creates a function that returns
11635
+ * the property value at a given path on `object`.
11636
+ *
11637
+ * @static
11638
+ * @memberOf _
11639
+ * @category Utility
11640
+ * @param {Object} object The object to query.
11641
+ * @returns {Function} Returns the new function.
11642
+ * @example
11643
+ *
11644
+ * var array = [0, 1, 2],
11645
+ * object = { 'a': array, 'b': array, 'c': array };
11646
+ *
11647
+ * _.map(['a[2]', 'c[0]'], _.propertyOf(object));
11648
+ * // => [2, 0]
11649
+ *
11650
+ * _.map([['a', '2'], ['c', '0']], _.propertyOf(object));
11651
+ * // => [2, 0]
11652
+ */
11653
+ function propertyOf(object) {
11654
+ return function(path) {
11655
+ return baseGet(object, toPath(path), (path + ''));
11656
+ };
11657
+ }
11658
+
11659
+ /**
11660
+ * Creates an array of numbers (positive and/or negative) progressing from
11661
+ * `start` up to, but not including, `end`. If `end` is not specified it's
11662
+ * set to `start` with `start` then set to `0`. If `end` is less than `start`
11663
+ * a zero-length range is created unless a negative `step` is specified.
11664
+ *
11665
+ * @static
11666
+ * @memberOf _
11667
+ * @category Utility
11668
+ * @param {number} [start=0] The start of the range.
11669
+ * @param {number} end The end of the range.
11670
+ * @param {number} [step=1] The value to increment or decrement by.
11671
+ * @returns {Array} Returns the new array of numbers.
11672
+ * @example
11673
+ *
11674
+ * _.range(4);
11675
+ * // => [0, 1, 2, 3]
11676
+ *
11677
+ * _.range(1, 5);
11678
+ * // => [1, 2, 3, 4]
11679
+ *
11680
+ * _.range(0, 20, 5);
11681
+ * // => [0, 5, 10, 15]
11682
+ *
11683
+ * _.range(0, -4, -1);
11684
+ * // => [0, -1, -2, -3]
11685
+ *
11686
+ * _.range(1, 4, 0);
11687
+ * // => [1, 1, 1]
11688
+ *
11689
+ * _.range(0);
11690
+ * // => []
11691
+ */
11692
+ function range(start, end, step) {
11693
+ if (step && isIterateeCall(start, end, step)) {
11694
+ end = step = undefined;
11695
+ }
11696
+ start = +start || 0;
11697
+ step = step == null ? 1 : (+step || 0);
11698
+
11699
+ if (end == null) {
11700
+ end = start;
11701
+ start = 0;
11702
+ } else {
11703
+ end = +end || 0;
11704
+ }
11705
+ // Use `Array(length)` so engines like Chakra and V8 avoid slower modes.
11706
+ // See https://youtu.be/XAqIpGU8ZZk#t=17m25s for more details.
11707
+ var index = -1,
11708
+ length = nativeMax(nativeCeil((end - start) / (step || 1)), 0),
11709
+ result = Array(length);
11710
+
11711
+ while (++index < length) {
11712
+ result[index] = start;
11713
+ start += step;
11714
+ }
11715
+ return result;
11716
+ }
11717
+
11718
+ /**
11719
+ * Invokes the iteratee function `n` times, returning an array of the results
11720
+ * of each invocation. The `iteratee` is bound to `thisArg` and invoked with
11721
+ * one argument; (index).
11722
+ *
11723
+ * @static
11724
+ * @memberOf _
11725
+ * @category Utility
11726
+ * @param {number} n The number of times to invoke `iteratee`.
11727
+ * @param {Function} [iteratee=_.identity] The function invoked per iteration.
11728
+ * @param {*} [thisArg] The `this` binding of `iteratee`.
11729
+ * @returns {Array} Returns the array of results.
11730
+ * @example
11731
+ *
11732
+ * var diceRolls = _.times(3, _.partial(_.random, 1, 6, false));
11733
+ * // => [3, 6, 4]
11734
+ *
11735
+ * _.times(3, function(n) {
11736
+ * mage.castSpell(n);
11737
+ * });
11738
+ * // => invokes `mage.castSpell(n)` three times with `n` of `0`, `1`, and `2`
11739
+ *
11740
+ * _.times(3, function(n) {
11741
+ * this.cast(n);
11742
+ * }, mage);
11743
+ * // => also invokes `mage.castSpell(n)` three times
11744
+ */
11745
+ function times(n, iteratee, thisArg) {
11746
+ n = nativeFloor(n);
11747
+
11748
+ // Exit early to avoid a JSC JIT bug in Safari 8
11749
+ // where `Array(0)` is treated as `Array(1)`.
11750
+ if (n < 1 || !nativeIsFinite(n)) {
11751
+ return [];
11752
+ }
11753
+ var index = -1,
11754
+ result = Array(nativeMin(n, MAX_ARRAY_LENGTH));
11755
+
11756
+ iteratee = bindCallback(iteratee, thisArg, 1);
11757
+ while (++index < n) {
11758
+ if (index < MAX_ARRAY_LENGTH) {
11759
+ result[index] = iteratee(index);
11760
+ } else {
11761
+ iteratee(index);
11762
+ }
11763
+ }
11764
+ return result;
11765
+ }
11766
+
11767
+ /**
11768
+ * Generates a unique ID. If `prefix` is provided the ID is appended to it.
11769
+ *
11770
+ * @static
11771
+ * @memberOf _
11772
+ * @category Utility
11773
+ * @param {string} [prefix] The value to prefix the ID with.
11774
+ * @returns {string} Returns the unique ID.
11775
+ * @example
11776
+ *
11777
+ * _.uniqueId('contact_');
11778
+ * // => 'contact_104'
11779
+ *
11780
+ * _.uniqueId();
11781
+ * // => '105'
11782
+ */
11783
+ function uniqueId(prefix) {
11784
+ var id = ++idCounter;
11785
+ return baseToString(prefix) + id;
11786
+ }
11787
+
11788
+ /*------------------------------------------------------------------------*/
11789
+
11790
+ /**
11791
+ * Adds two numbers.
11792
+ *
11793
+ * @static
11794
+ * @memberOf _
11795
+ * @category Math
11796
+ * @param {number} augend The first number to add.
11797
+ * @param {number} addend The second number to add.
11798
+ * @returns {number} Returns the sum.
11799
+ * @example
11800
+ *
11801
+ * _.add(6, 4);
11802
+ * // => 10
11803
+ */
11804
+ function add(augend, addend) {
11805
+ return (+augend || 0) + (+addend || 0);
11806
+ }
11807
+
11808
+ /**
11809
+ * Calculates `n` rounded up to `precision`.
11810
+ *
11811
+ * @static
11812
+ * @memberOf _
11813
+ * @category Math
11814
+ * @param {number} n The number to round up.
11815
+ * @param {number} [precision=0] The precision to round up to.
11816
+ * @returns {number} Returns the rounded up number.
11817
+ * @example
11818
+ *
11819
+ * _.ceil(4.006);
11820
+ * // => 5
11821
+ *
11822
+ * _.ceil(6.004, 2);
11823
+ * // => 6.01
11824
+ *
11825
+ * _.ceil(6040, -2);
11826
+ * // => 6100
11827
+ */
11828
+ var ceil = createRound('ceil');
11829
+
11830
+ /**
11831
+ * Calculates `n` rounded down to `precision`.
11832
+ *
11833
+ * @static
11834
+ * @memberOf _
11835
+ * @category Math
11836
+ * @param {number} n The number to round down.
11837
+ * @param {number} [precision=0] The precision to round down to.
11838
+ * @returns {number} Returns the rounded down number.
11839
+ * @example
11840
+ *
11841
+ * _.floor(4.006);
11842
+ * // => 4
11843
+ *
11844
+ * _.floor(0.046, 2);
11845
+ * // => 0.04
11846
+ *
11847
+ * _.floor(4060, -2);
11848
+ * // => 4000
11849
+ */
11850
+ var floor = createRound('floor');
11851
+
11852
+ /**
11853
+ * Gets the maximum value of `collection`. If `collection` is empty or falsey
11854
+ * `-Infinity` is returned. If an iteratee function is provided it's invoked
11855
+ * for each value in `collection` to generate the criterion by which the value
11856
+ * is ranked. The `iteratee` is bound to `thisArg` and invoked with three
11857
+ * arguments: (value, index, collection).
11858
+ *
11859
+ * If a property name is provided for `iteratee` the created `_.property`
11860
+ * style callback returns the property value of the given element.
11861
+ *
11862
+ * If a value is also provided for `thisArg` the created `_.matchesProperty`
11863
+ * style callback returns `true` for elements that have a matching property
11864
+ * value, else `false`.
11865
+ *
11866
+ * If an object is provided for `iteratee` the created `_.matches` style
11867
+ * callback returns `true` for elements that have the properties of the given
11868
+ * object, else `false`.
11869
+ *
11870
+ * @static
11871
+ * @memberOf _
11872
+ * @category Math
11873
+ * @param {Array|Object|string} collection The collection to iterate over.
11874
+ * @param {Function|Object|string} [iteratee] The function invoked per iteration.
11875
+ * @param {*} [thisArg] The `this` binding of `iteratee`.
11876
+ * @returns {*} Returns the maximum value.
11877
+ * @example
11878
+ *
11879
+ * _.max([4, 2, 8, 6]);
11880
+ * // => 8
11881
+ *
11882
+ * _.max([]);
11883
+ * // => -Infinity
11884
+ *
11885
+ * var users = [
11886
+ * { 'user': 'barney', 'age': 36 },
11887
+ * { 'user': 'fred', 'age': 40 }
11888
+ * ];
11889
+ *
11890
+ * _.max(users, function(chr) {
11891
+ * return chr.age;
11892
+ * });
11893
+ * // => { 'user': 'fred', 'age': 40 }
11894
+ *
11895
+ * // using the `_.property` callback shorthand
11896
+ * _.max(users, 'age');
11897
+ * // => { 'user': 'fred', 'age': 40 }
11898
+ */
11899
+ var max = createExtremum(gt, NEGATIVE_INFINITY);
11900
+
11901
+ /**
11902
+ * Gets the minimum value of `collection`. If `collection` is empty or falsey
11903
+ * `Infinity` is returned. If an iteratee function is provided it's invoked
11904
+ * for each value in `collection` to generate the criterion by which the value
11905
+ * is ranked. The `iteratee` is bound to `thisArg` and invoked with three
11906
+ * arguments: (value, index, collection).
11907
+ *
11908
+ * If a property name is provided for `iteratee` the created `_.property`
11909
+ * style callback returns the property value of the given element.
11910
+ *
11911
+ * If a value is also provided for `thisArg` the created `_.matchesProperty`
11912
+ * style callback returns `true` for elements that have a matching property
11913
+ * value, else `false`.
11914
+ *
11915
+ * If an object is provided for `iteratee` the created `_.matches` style
11916
+ * callback returns `true` for elements that have the properties of the given
11917
+ * object, else `false`.
11918
+ *
11919
+ * @static
11920
+ * @memberOf _
11921
+ * @category Math
11922
+ * @param {Array|Object|string} collection The collection to iterate over.
11923
+ * @param {Function|Object|string} [iteratee] The function invoked per iteration.
11924
+ * @param {*} [thisArg] The `this` binding of `iteratee`.
11925
+ * @returns {*} Returns the minimum value.
11926
+ * @example
11927
+ *
11928
+ * _.min([4, 2, 8, 6]);
11929
+ * // => 2
11930
+ *
11931
+ * _.min([]);
11932
+ * // => Infinity
11933
+ *
11934
+ * var users = [
11935
+ * { 'user': 'barney', 'age': 36 },
11936
+ * { 'user': 'fred', 'age': 40 }
11937
+ * ];
11938
+ *
11939
+ * _.min(users, function(chr) {
11940
+ * return chr.age;
11941
+ * });
11942
+ * // => { 'user': 'barney', 'age': 36 }
11943
+ *
11944
+ * // using the `_.property` callback shorthand
11945
+ * _.min(users, 'age');
11946
+ * // => { 'user': 'barney', 'age': 36 }
11947
+ */
11948
+ var min = createExtremum(lt, POSITIVE_INFINITY);
11949
+
11950
+ /**
11951
+ * Calculates `n` rounded to `precision`.
11952
+ *
11953
+ * @static
11954
+ * @memberOf _
11955
+ * @category Math
11956
+ * @param {number} n The number to round.
11957
+ * @param {number} [precision=0] The precision to round to.
11958
+ * @returns {number} Returns the rounded number.
11959
+ * @example
11960
+ *
11961
+ * _.round(4.006);
11962
+ * // => 4
11963
+ *
11964
+ * _.round(4.006, 2);
11965
+ * // => 4.01
11966
+ *
11967
+ * _.round(4060, -2);
11968
+ * // => 4100
11969
+ */
11970
+ var round = createRound('round');
11971
+
11972
+ /**
11973
+ * Gets the sum of the values in `collection`.
11974
+ *
11975
+ * @static
11976
+ * @memberOf _
11977
+ * @category Math
11978
+ * @param {Array|Object|string} collection The collection to iterate over.
11979
+ * @param {Function|Object|string} [iteratee] The function invoked per iteration.
11980
+ * @param {*} [thisArg] The `this` binding of `iteratee`.
11981
+ * @returns {number} Returns the sum.
11982
+ * @example
11983
+ *
11984
+ * _.sum([4, 6]);
11985
+ * // => 10
11986
+ *
11987
+ * _.sum({ 'a': 4, 'b': 6 });
11988
+ * // => 10
11989
+ *
11990
+ * var objects = [
11991
+ * { 'n': 4 },
11992
+ * { 'n': 6 }
11993
+ * ];
11994
+ *
11995
+ * _.sum(objects, function(object) {
11996
+ * return object.n;
11997
+ * });
11998
+ * // => 10
11999
+ *
12000
+ * // using the `_.property` callback shorthand
12001
+ * _.sum(objects, 'n');
12002
+ * // => 10
12003
+ */
12004
+ function sum(collection, iteratee, thisArg) {
12005
+ if (thisArg && isIterateeCall(collection, iteratee, thisArg)) {
12006
+ iteratee = undefined;
12007
+ }
12008
+ iteratee = getCallback(iteratee, thisArg, 3);
12009
+ return iteratee.length == 1
12010
+ ? arraySum(isArray(collection) ? collection : toIterable(collection), iteratee)
12011
+ : baseSum(collection, iteratee);
12012
+ }
12013
+
12014
+ /*------------------------------------------------------------------------*/
12015
+
12016
+ // Ensure wrappers are instances of `baseLodash`.
12017
+ lodash.prototype = baseLodash.prototype;
12018
+
12019
+ LodashWrapper.prototype = baseCreate(baseLodash.prototype);
12020
+ LodashWrapper.prototype.constructor = LodashWrapper;
12021
+
12022
+ LazyWrapper.prototype = baseCreate(baseLodash.prototype);
12023
+ LazyWrapper.prototype.constructor = LazyWrapper;
12024
+
12025
+ // Add functions to the `Map` cache.
12026
+ MapCache.prototype['delete'] = mapDelete;
12027
+ MapCache.prototype.get = mapGet;
12028
+ MapCache.prototype.has = mapHas;
12029
+ MapCache.prototype.set = mapSet;
12030
+
12031
+ // Add functions to the `Set` cache.
12032
+ SetCache.prototype.push = cachePush;
12033
+
12034
+ // Assign cache to `_.memoize`.
12035
+ memoize.Cache = MapCache;
12036
+
12037
+ // Add functions that return wrapped values when chaining.
12038
+ lodash.after = after;
12039
+ lodash.ary = ary;
12040
+ lodash.assign = assign;
12041
+ lodash.at = at;
12042
+ lodash.before = before;
12043
+ lodash.bind = bind;
12044
+ lodash.bindAll = bindAll;
12045
+ lodash.bindKey = bindKey;
12046
+ lodash.callback = callback;
12047
+ lodash.chain = chain;
12048
+ lodash.chunk = chunk;
12049
+ lodash.compact = compact;
12050
+ lodash.constant = constant;
12051
+ lodash.countBy = countBy;
12052
+ lodash.create = create;
12053
+ lodash.curry = curry;
12054
+ lodash.curryRight = curryRight;
12055
+ lodash.debounce = debounce;
12056
+ lodash.defaults = defaults;
12057
+ lodash.defaultsDeep = defaultsDeep;
12058
+ lodash.defer = defer;
12059
+ lodash.delay = delay;
12060
+ lodash.difference = difference;
12061
+ lodash.drop = drop;
12062
+ lodash.dropRight = dropRight;
12063
+ lodash.dropRightWhile = dropRightWhile;
12064
+ lodash.dropWhile = dropWhile;
12065
+ lodash.fill = fill;
12066
+ lodash.filter = filter;
12067
+ lodash.flatten = flatten;
12068
+ lodash.flattenDeep = flattenDeep;
12069
+ lodash.flow = flow;
12070
+ lodash.flowRight = flowRight;
12071
+ lodash.forEach = forEach;
12072
+ lodash.forEachRight = forEachRight;
12073
+ lodash.forIn = forIn;
12074
+ lodash.forInRight = forInRight;
12075
+ lodash.forOwn = forOwn;
12076
+ lodash.forOwnRight = forOwnRight;
12077
+ lodash.functions = functions;
12078
+ lodash.groupBy = groupBy;
12079
+ lodash.indexBy = indexBy;
12080
+ lodash.initial = initial;
12081
+ lodash.intersection = intersection;
12082
+ lodash.invert = invert;
12083
+ lodash.invoke = invoke;
12084
+ lodash.keys = keys;
12085
+ lodash.keysIn = keysIn;
12086
+ lodash.map = map;
12087
+ lodash.mapKeys = mapKeys;
12088
+ lodash.mapValues = mapValues;
12089
+ lodash.matches = matches;
12090
+ lodash.matchesProperty = matchesProperty;
12091
+ lodash.memoize = memoize;
12092
+ lodash.merge = merge;
12093
+ lodash.method = method;
12094
+ lodash.methodOf = methodOf;
12095
+ lodash.mixin = mixin;
12096
+ lodash.modArgs = modArgs;
12097
+ lodash.negate = negate;
12098
+ lodash.omit = omit;
12099
+ lodash.once = once;
12100
+ lodash.pairs = pairs;
12101
+ lodash.partial = partial;
12102
+ lodash.partialRight = partialRight;
12103
+ lodash.partition = partition;
12104
+ lodash.pick = pick;
12105
+ lodash.pluck = pluck;
12106
+ lodash.property = property;
12107
+ lodash.propertyOf = propertyOf;
12108
+ lodash.pull = pull;
12109
+ lodash.pullAt = pullAt;
12110
+ lodash.range = range;
12111
+ lodash.rearg = rearg;
12112
+ lodash.reject = reject;
12113
+ lodash.remove = remove;
12114
+ lodash.rest = rest;
12115
+ lodash.restParam = restParam;
12116
+ lodash.set = set;
12117
+ lodash.shuffle = shuffle;
12118
+ lodash.slice = slice;
12119
+ lodash.sortBy = sortBy;
12120
+ lodash.sortByAll = sortByAll;
12121
+ lodash.sortByOrder = sortByOrder;
12122
+ lodash.spread = spread;
12123
+ lodash.take = take;
12124
+ lodash.takeRight = takeRight;
12125
+ lodash.takeRightWhile = takeRightWhile;
12126
+ lodash.takeWhile = takeWhile;
12127
+ lodash.tap = tap;
12128
+ lodash.throttle = throttle;
12129
+ lodash.thru = thru;
12130
+ lodash.times = times;
12131
+ lodash.toArray = toArray;
12132
+ lodash.toPlainObject = toPlainObject;
12133
+ lodash.transform = transform;
12134
+ lodash.union = union;
12135
+ lodash.uniq = uniq;
12136
+ lodash.unzip = unzip;
12137
+ lodash.unzipWith = unzipWith;
12138
+ lodash.values = values;
12139
+ lodash.valuesIn = valuesIn;
12140
+ lodash.where = where;
12141
+ lodash.without = without;
12142
+ lodash.wrap = wrap;
12143
+ lodash.xor = xor;
12144
+ lodash.zip = zip;
12145
+ lodash.zipObject = zipObject;
12146
+ lodash.zipWith = zipWith;
12147
+
12148
+ // Add aliases.
12149
+ lodash.backflow = flowRight;
12150
+ lodash.collect = map;
12151
+ lodash.compose = flowRight;
12152
+ lodash.each = forEach;
12153
+ lodash.eachRight = forEachRight;
12154
+ lodash.extend = assign;
12155
+ lodash.iteratee = callback;
12156
+ lodash.methods = functions;
12157
+ lodash.object = zipObject;
12158
+ lodash.select = filter;
12159
+ lodash.tail = rest;
12160
+ lodash.unique = uniq;
12161
+
12162
+ // Add functions to `lodash.prototype`.
12163
+ mixin(lodash, lodash);
12164
+
12165
+ /*------------------------------------------------------------------------*/
12166
+
12167
+ // Add functions that return unwrapped values when chaining.
12168
+ lodash.add = add;
12169
+ lodash.attempt = attempt;
12170
+ lodash.camelCase = camelCase;
12171
+ lodash.capitalize = capitalize;
12172
+ lodash.ceil = ceil;
12173
+ lodash.clone = clone;
12174
+ lodash.cloneDeep = cloneDeep;
12175
+ lodash.deburr = deburr;
12176
+ lodash.endsWith = endsWith;
12177
+ lodash.escape = escape;
12178
+ lodash.escapeRegExp = escapeRegExp;
12179
+ lodash.every = every;
12180
+ lodash.find = find;
12181
+ lodash.findIndex = findIndex;
12182
+ lodash.findKey = findKey;
12183
+ lodash.findLast = findLast;
12184
+ lodash.findLastIndex = findLastIndex;
12185
+ lodash.findLastKey = findLastKey;
12186
+ lodash.findWhere = findWhere;
12187
+ lodash.first = first;
12188
+ lodash.floor = floor;
12189
+ lodash.get = get;
12190
+ lodash.gt = gt;
12191
+ lodash.gte = gte;
12192
+ lodash.has = has;
12193
+ lodash.identity = identity;
12194
+ lodash.includes = includes;
12195
+ lodash.indexOf = indexOf;
12196
+ lodash.inRange = inRange;
12197
+ lodash.isArguments = isArguments;
12198
+ lodash.isArray = isArray;
12199
+ lodash.isBoolean = isBoolean;
12200
+ lodash.isDate = isDate;
12201
+ lodash.isElement = isElement;
12202
+ lodash.isEmpty = isEmpty;
12203
+ lodash.isEqual = isEqual;
12204
+ lodash.isError = isError;
12205
+ lodash.isFinite = isFinite;
12206
+ lodash.isFunction = isFunction;
12207
+ lodash.isMatch = isMatch;
12208
+ lodash.isNaN = isNaN;
12209
+ lodash.isNative = isNative;
12210
+ lodash.isNull = isNull;
12211
+ lodash.isNumber = isNumber;
12212
+ lodash.isObject = isObject;
12213
+ lodash.isPlainObject = isPlainObject;
12214
+ lodash.isRegExp = isRegExp;
12215
+ lodash.isString = isString;
12216
+ lodash.isTypedArray = isTypedArray;
12217
+ lodash.isUndefined = isUndefined;
12218
+ lodash.kebabCase = kebabCase;
12219
+ lodash.last = last;
12220
+ lodash.lastIndexOf = lastIndexOf;
12221
+ lodash.lt = lt;
12222
+ lodash.lte = lte;
12223
+ lodash.max = max;
12224
+ lodash.min = min;
12225
+ lodash.noConflict = noConflict;
12226
+ lodash.noop = noop;
12227
+ lodash.now = now;
12228
+ lodash.pad = pad;
12229
+ lodash.padLeft = padLeft;
12230
+ lodash.padRight = padRight;
12231
+ lodash.parseInt = parseInt;
12232
+ lodash.random = random;
12233
+ lodash.reduce = reduce;
12234
+ lodash.reduceRight = reduceRight;
12235
+ lodash.repeat = repeat;
12236
+ lodash.result = result;
12237
+ lodash.round = round;
12238
+ lodash.runInContext = runInContext;
12239
+ lodash.size = size;
12240
+ lodash.snakeCase = snakeCase;
12241
+ lodash.some = some;
12242
+ lodash.sortedIndex = sortedIndex;
12243
+ lodash.sortedLastIndex = sortedLastIndex;
12244
+ lodash.startCase = startCase;
12245
+ lodash.startsWith = startsWith;
12246
+ lodash.sum = sum;
12247
+ lodash.template = template;
12248
+ lodash.trim = trim;
12249
+ lodash.trimLeft = trimLeft;
12250
+ lodash.trimRight = trimRight;
12251
+ lodash.trunc = trunc;
12252
+ lodash.unescape = unescape;
12253
+ lodash.uniqueId = uniqueId;
12254
+ lodash.words = words;
12255
+
12256
+ // Add aliases.
12257
+ lodash.all = every;
12258
+ lodash.any = some;
12259
+ lodash.contains = includes;
12260
+ lodash.eq = isEqual;
12261
+ lodash.detect = find;
12262
+ lodash.foldl = reduce;
12263
+ lodash.foldr = reduceRight;
12264
+ lodash.head = first;
12265
+ lodash.include = includes;
12266
+ lodash.inject = reduce;
12267
+
12268
+ mixin(lodash, (function() {
12269
+ var source = {};
12270
+ baseForOwn(lodash, function(func, methodName) {
12271
+ if (!lodash.prototype[methodName]) {
12272
+ source[methodName] = func;
12273
+ }
12274
+ });
12275
+ return source;
12276
+ }()), false);
12277
+
12278
+ /*------------------------------------------------------------------------*/
12279
+
12280
+ // Add functions capable of returning wrapped and unwrapped values when chaining.
12281
+ lodash.sample = sample;
12282
+
12283
+ lodash.prototype.sample = function(n) {
12284
+ if (!this.__chain__ && n == null) {
12285
+ return sample(this.value());
12286
+ }
12287
+ return this.thru(function(value) {
12288
+ return sample(value, n);
12289
+ });
12290
+ };
12291
+
12292
+ /*------------------------------------------------------------------------*/
12293
+
12294
+ /**
12295
+ * The semantic version number.
12296
+ *
12297
+ * @static
12298
+ * @memberOf _
12299
+ * @type string
12300
+ */
12301
+ lodash.VERSION = VERSION;
12302
+
12303
+ // Assign default placeholders.
12304
+ arrayEach(['bind', 'bindKey', 'curry', 'curryRight', 'partial', 'partialRight'], function(methodName) {
12305
+ lodash[methodName].placeholder = lodash;
12306
+ });
12307
+
12308
+ // Add `LazyWrapper` methods for `_.drop` and `_.take` variants.
12309
+ arrayEach(['drop', 'take'], function(methodName, index) {
12310
+ LazyWrapper.prototype[methodName] = function(n) {
12311
+ var filtered = this.__filtered__;
12312
+ if (filtered && !index) {
12313
+ return new LazyWrapper(this);
12314
+ }
12315
+ n = n == null ? 1 : nativeMax(nativeFloor(n) || 0, 0);
12316
+
12317
+ var result = this.clone();
12318
+ if (filtered) {
12319
+ result.__takeCount__ = nativeMin(result.__takeCount__, n);
12320
+ } else {
12321
+ result.__views__.push({ 'size': n, 'type': methodName + (result.__dir__ < 0 ? 'Right' : '') });
12322
+ }
12323
+ return result;
12324
+ };
12325
+
12326
+ LazyWrapper.prototype[methodName + 'Right'] = function(n) {
12327
+ return this.reverse()[methodName](n).reverse();
12328
+ };
12329
+ });
12330
+
12331
+ // Add `LazyWrapper` methods that accept an `iteratee` value.
12332
+ arrayEach(['filter', 'map', 'takeWhile'], function(methodName, index) {
12333
+ var type = index + 1,
12334
+ isFilter = type != LAZY_MAP_FLAG;
12335
+
12336
+ LazyWrapper.prototype[methodName] = function(iteratee, thisArg) {
12337
+ var result = this.clone();
12338
+ result.__iteratees__.push({ 'iteratee': getCallback(iteratee, thisArg, 1), 'type': type });
12339
+ result.__filtered__ = result.__filtered__ || isFilter;
12340
+ return result;
12341
+ };
12342
+ });
12343
+
12344
+ // Add `LazyWrapper` methods for `_.first` and `_.last`.
12345
+ arrayEach(['first', 'last'], function(methodName, index) {
12346
+ var takeName = 'take' + (index ? 'Right' : '');
12347
+
12348
+ LazyWrapper.prototype[methodName] = function() {
12349
+ return this[takeName](1).value()[0];
12350
+ };
12351
+ });
12352
+
12353
+ // Add `LazyWrapper` methods for `_.initial` and `_.rest`.
12354
+ arrayEach(['initial', 'rest'], function(methodName, index) {
12355
+ var dropName = 'drop' + (index ? '' : 'Right');
12356
+
12357
+ LazyWrapper.prototype[methodName] = function() {
12358
+ return this.__filtered__ ? new LazyWrapper(this) : this[dropName](1);
12359
+ };
12360
+ });
12361
+
12362
+ // Add `LazyWrapper` methods for `_.pluck` and `_.where`.
12363
+ arrayEach(['pluck', 'where'], function(methodName, index) {
12364
+ var operationName = index ? 'filter' : 'map',
12365
+ createCallback = index ? baseMatches : property;
12366
+
12367
+ LazyWrapper.prototype[methodName] = function(value) {
12368
+ return this[operationName](createCallback(value));
12369
+ };
12370
+ });
12371
+
12372
+ LazyWrapper.prototype.compact = function() {
12373
+ return this.filter(identity);
12374
+ };
12375
+
12376
+ LazyWrapper.prototype.reject = function(predicate, thisArg) {
12377
+ predicate = getCallback(predicate, thisArg, 1);
12378
+ return this.filter(function(value) {
12379
+ return !predicate(value);
12380
+ });
12381
+ };
12382
+
12383
+ LazyWrapper.prototype.slice = function(start, end) {
12384
+ start = start == null ? 0 : (+start || 0);
12385
+
12386
+ var result = this;
12387
+ if (result.__filtered__ && (start > 0 || end < 0)) {
12388
+ return new LazyWrapper(result);
12389
+ }
12390
+ if (start < 0) {
12391
+ result = result.takeRight(-start);
12392
+ } else if (start) {
12393
+ result = result.drop(start);
12394
+ }
12395
+ if (end !== undefined) {
12396
+ end = (+end || 0);
12397
+ result = end < 0 ? result.dropRight(-end) : result.take(end - start);
12398
+ }
12399
+ return result;
12400
+ };
12401
+
12402
+ LazyWrapper.prototype.takeRightWhile = function(predicate, thisArg) {
12403
+ return this.reverse().takeWhile(predicate, thisArg).reverse();
12404
+ };
12405
+
12406
+ LazyWrapper.prototype.toArray = function() {
12407
+ return this.take(POSITIVE_INFINITY);
12408
+ };
12409
+
12410
+ // Add `LazyWrapper` methods to `lodash.prototype`.
12411
+ baseForOwn(LazyWrapper.prototype, function(func, methodName) {
12412
+ var checkIteratee = /^(?:filter|map|reject)|While$/.test(methodName),
12413
+ retUnwrapped = /^(?:first|last)$/.test(methodName),
12414
+ lodashFunc = lodash[retUnwrapped ? ('take' + (methodName == 'last' ? 'Right' : '')) : methodName];
12415
+
12416
+ if (!lodashFunc) {
12417
+ return;
12418
+ }
12419
+ lodash.prototype[methodName] = function() {
12420
+ var args = retUnwrapped ? [1] : arguments,
12421
+ chainAll = this.__chain__,
12422
+ value = this.__wrapped__,
12423
+ isHybrid = !!this.__actions__.length,
12424
+ isLazy = value instanceof LazyWrapper,
12425
+ iteratee = args[0],
12426
+ useLazy = isLazy || isArray(value);
12427
+
12428
+ if (useLazy && checkIteratee && typeof iteratee == 'function' && iteratee.length != 1) {
12429
+ // Avoid lazy use if the iteratee has a "length" value other than `1`.
12430
+ isLazy = useLazy = false;
12431
+ }
12432
+ var interceptor = function(value) {
12433
+ return (retUnwrapped && chainAll)
12434
+ ? lodashFunc(value, 1)[0]
12435
+ : lodashFunc.apply(undefined, arrayPush([value], args));
12436
+ };
12437
+
12438
+ var action = { 'func': thru, 'args': [interceptor], 'thisArg': undefined },
12439
+ onlyLazy = isLazy && !isHybrid;
12440
+
12441
+ if (retUnwrapped && !chainAll) {
12442
+ if (onlyLazy) {
12443
+ value = value.clone();
12444
+ value.__actions__.push(action);
12445
+ return func.call(value);
12446
+ }
12447
+ return lodashFunc.call(undefined, this.value())[0];
12448
+ }
12449
+ if (!retUnwrapped && useLazy) {
12450
+ value = onlyLazy ? value : new LazyWrapper(this);
12451
+ var result = func.apply(value, args);
12452
+ result.__actions__.push(action);
12453
+ return new LodashWrapper(result, chainAll);
12454
+ }
12455
+ return this.thru(interceptor);
12456
+ };
12457
+ });
12458
+
12459
+ // Add `Array` and `String` methods to `lodash.prototype`.
12460
+ arrayEach(['join', 'pop', 'push', 'replace', 'shift', 'sort', 'splice', 'split', 'unshift'], function(methodName) {
12461
+ var protoFunc = (/^(?:replace|split)$/.test(methodName) ? stringProto : arrayProto)[methodName],
12462
+ chainName = /^(?:push|sort|unshift)$/.test(methodName) ? 'tap' : 'thru',
12463
+ fixObjects = !support.spliceObjects && /^(?:pop|shift|splice)$/.test(methodName),
12464
+ retUnwrapped = /^(?:join|pop|replace|shift)$/.test(methodName);
12465
+
12466
+ // Avoid array-like object bugs with `Array#shift` and `Array#splice` in
12467
+ // IE < 9, Firefox < 10, and RingoJS.
12468
+ var func = !fixObjects ? protoFunc : function() {
12469
+ var result = protoFunc.apply(this, arguments);
12470
+ if (this.length === 0) {
12471
+ delete this[0];
12472
+ }
12473
+ return result;
12474
+ };
12475
+
12476
+ lodash.prototype[methodName] = function() {
12477
+ var args = arguments;
12478
+ if (retUnwrapped && !this.__chain__) {
12479
+ return func.apply(this.value(), args);
12480
+ }
12481
+ return this[chainName](function(value) {
12482
+ return func.apply(value, args);
12483
+ });
12484
+ };
12485
+ });
12486
+
12487
+ // Map minified function names to their real names.
12488
+ baseForOwn(LazyWrapper.prototype, function(func, methodName) {
12489
+ var lodashFunc = lodash[methodName];
12490
+ if (lodashFunc) {
12491
+ var key = (lodashFunc.name + ''),
12492
+ names = realNames[key] || (realNames[key] = []);
12493
+
12494
+ names.push({ 'name': methodName, 'func': lodashFunc });
12495
+ }
12496
+ });
12497
+
12498
+ realNames[createHybridWrapper(undefined, BIND_KEY_FLAG).name] = [{ 'name': 'wrapper', 'func': undefined }];
12499
+
12500
+ // Add functions to the lazy wrapper.
12501
+ LazyWrapper.prototype.clone = lazyClone;
12502
+ LazyWrapper.prototype.reverse = lazyReverse;
12503
+ LazyWrapper.prototype.value = lazyValue;
12504
+
12505
+ // Add chaining functions to the `lodash` wrapper.
12506
+ lodash.prototype.chain = wrapperChain;
12507
+ lodash.prototype.commit = wrapperCommit;
12508
+ lodash.prototype.concat = wrapperConcat;
12509
+ lodash.prototype.plant = wrapperPlant;
12510
+ lodash.prototype.reverse = wrapperReverse;
12511
+ lodash.prototype.toString = wrapperToString;
12512
+ lodash.prototype.run = lodash.prototype.toJSON = lodash.prototype.valueOf = lodash.prototype.value = wrapperValue;
12513
+
12514
+ // Add function aliases to the `lodash` wrapper.
12515
+ lodash.prototype.collect = lodash.prototype.map;
12516
+ lodash.prototype.head = lodash.prototype.first;
12517
+ lodash.prototype.select = lodash.prototype.filter;
12518
+ lodash.prototype.tail = lodash.prototype.rest;
12519
+
12520
+ return lodash;
12521
+ }
12522
+
12523
+ /*--------------------------------------------------------------------------*/
12524
+
12525
+ // Export lodash.
12526
+ var _ = runInContext();
12527
+
12528
+ // Some AMD build optimizers like r.js check for condition patterns like the following:
12529
+ if (typeof define == 'function' && typeof define.amd == 'object' && define.amd) {
12530
+ // Expose lodash to the global object when an AMD loader is present to avoid
12531
+ // errors in cases where lodash is loaded by a script tag and not intended
12532
+ // as an AMD module. See http://requirejs.org/docs/errors.html#mismatch for
12533
+ // more details.
12534
+ root._ = _;
12535
+
12536
+ // Define as an anonymous module so, through path mapping, it can be
12537
+ // referenced as the "underscore" module.
12538
+ define(function() {
12539
+ return _;
12540
+ });
12541
+ }
12542
+ // Check for `exports` after `define` in case a build optimizer adds an `exports` object.
12543
+ else if (freeExports && freeModule) {
12544
+ // Export for Node.js or RingoJS.
12545
+ if (moduleExports) {
12546
+ (freeModule.exports = _)._ = _;
12547
+ }
12548
+ // Export for Rhino with CommonJS support.
12549
+ else {
12550
+ freeExports._ = _;
12551
+ }
12552
+ }
12553
+ else {
12554
+ // Export for a browser or Rhino.
12555
+ root._ = _;
12556
+ }
12557
+ }.call(this));
js/lodash.min.js ADDED
@@ -0,0 +1,102 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /**
2
+ * @license
3
+ * lodash 3.10.1 (Custom Build) lodash.com/license | Underscore.js 1.8.3 underscorejs.org/LICENSE
4
+ * Build: `lodash compat -o ./lodash.js`
5
+ */
6
+ ;(function(){function n(n,t){if(n!==t){var r=null===n,e=n===w,u=n===n,o=null===t,i=t===w,f=t===t;if(n>t&&!o||!u||r&&!i&&f||e&&f)return 1;if(n<t&&!r||!f||o&&!e&&u||i&&u)return-1}return 0}function t(n,t,r){for(var e=n.length,u=r?e:-1;r?u--:++u<e;)if(t(n[u],u,n))return u;return-1}function r(n,t,r){if(t!==t)return p(n,r);r-=1;for(var e=n.length;++r<e;)if(n[r]===t)return r;return-1}function e(n){return typeof n=="function"||false}function u(n){return null==n?"":n+""}function o(n,t){for(var r=-1,e=n.length;++r<e&&-1<t.indexOf(n.charAt(r)););
7
+ return r}function i(n,t){for(var r=n.length;r--&&-1<t.indexOf(n.charAt(r)););return r}function f(t,r){return n(t.a,r.a)||t.b-r.b}function a(n){return Nn[n]}function c(n){return Tn[n]}function l(n,t,r){return t?n=Bn[n]:r&&(n=Dn[n]),"\\"+n}function s(n){return"\\"+Dn[n]}function p(n,t,r){var e=n.length;for(t+=r?0:-1;r?t--:++t<e;){var u=n[t];if(u!==u)return t}return-1}function h(n){return!!n&&typeof n=="object"}function _(n){return 160>=n&&9<=n&&13>=n||32==n||160==n||5760==n||6158==n||8192<=n&&(8202>=n||8232==n||8233==n||8239==n||8287==n||12288==n||65279==n);
8
+ }function v(n,t){for(var r=-1,e=n.length,u=-1,o=[];++r<e;)n[r]===t&&(n[r]=P,o[++u]=r);return o}function g(n){for(var t=-1,r=n.length;++t<r&&_(n.charCodeAt(t)););return t}function y(n){for(var t=n.length;t--&&_(n.charCodeAt(t)););return t}function d(n){return Pn[n]}function m(_){function Nn(n){if(h(n)&&!(Wo(n)||n instanceof zn)){if(n instanceof Pn)return n;if(eu.call(n,"__chain__")&&eu.call(n,"__wrapped__"))return qr(n)}return new Pn(n)}function Tn(){}function Pn(n,t,r){this.__wrapped__=n,this.__actions__=r||[],
9
+ this.__chain__=!!t}function zn(n){this.__wrapped__=n,this.__actions__=[],this.__dir__=1,this.__filtered__=false,this.__iteratees__=[],this.__takeCount__=Cu,this.__views__=[]}function Bn(){this.__data__={}}function Dn(n){var t=n?n.length:0;for(this.data={hash:mu(null),set:new hu};t--;)this.push(n[t])}function Mn(n,t){var r=n.data;return(typeof t=="string"||de(t)?r.set.has(t):r.hash[t])?0:-1}function qn(n,t){var r=-1,e=n.length;for(t||(t=De(e));++r<e;)t[r]=n[r];return t}function Kn(n,t){for(var r=-1,e=n.length;++r<e&&false!==t(n[r],r,n););
10
+ return n}function Vn(n,t){for(var r=-1,e=n.length;++r<e;)if(!t(n[r],r,n))return false;return true}function Zn(n,t){for(var r=-1,e=n.length,u=-1,o=[];++r<e;){var i=n[r];t(i,r,n)&&(o[++u]=i)}return o}function Xn(n,t){for(var r=-1,e=n.length,u=De(e);++r<e;)u[r]=t(n[r],r,n);return u}function Hn(n,t){for(var r=-1,e=t.length,u=n.length;++r<e;)n[u+r]=t[r];return n}function Qn(n,t,r,e){var u=-1,o=n.length;for(e&&o&&(r=n[++u]);++u<o;)r=t(r,n[u],u,n);return r}function nt(n,t){for(var r=-1,e=n.length;++r<e;)if(t(n[r],r,n))return true;
11
+ return false}function tt(n,t,r,e){return n!==w&&eu.call(e,r)?n:t}function rt(n,t,r){for(var e=-1,u=Ko(t),o=u.length;++e<o;){var i=u[e],f=n[i],a=r(f,t[i],i,n,t);(a===a?a===f:f!==f)&&(f!==w||i in n)||(n[i]=a)}return n}function et(n,t){return null==t?n:ot(t,Ko(t),n)}function ut(n,t){for(var r=-1,e=null==n,u=!e&&Sr(n),o=u?n.length:0,i=t.length,f=De(i);++r<i;){var a=t[r];f[r]=u?Ur(a,o)?n[a]:w:e?w:n[a]}return f}function ot(n,t,r){r||(r={});for(var e=-1,u=t.length;++e<u;){var o=t[e];r[o]=n[o]}return r}function it(n,t,r){
12
+ var e=typeof n;return"function"==e?t===w?n:Dt(n,t,r):null==n?Ne:"object"==e?At(n):t===w?Be(n):jt(n,t)}function ft(n,t,r,e,u,o,i){var f;if(r&&(f=u?r(n,e,u):r(n)),f!==w)return f;if(!de(n))return n;if(e=Wo(n)){if(f=Ir(n),!t)return qn(n,f)}else{var a=ou.call(n),c=a==K;if(a!=Z&&a!=z&&(!c||u))return Ln[a]?Er(n,a,t):u?n:{};if(Gn(n))return u?n:{};if(f=Rr(c?{}:n),!t)return et(f,n)}for(o||(o=[]),i||(i=[]),u=o.length;u--;)if(o[u]==n)return i[u];return o.push(n),i.push(f),(e?Kn:gt)(n,function(e,u){f[u]=ft(e,t,r,u,n,o,i);
13
+ }),f}function at(n,t,r){if(typeof n!="function")throw new Xe(T);return _u(function(){n.apply(w,r)},t)}function ct(n,t){var e=n?n.length:0,u=[];if(!e)return u;var o=-1,i=jr(),f=i===r,a=f&&t.length>=F&&mu&&hu?new Dn(t):null,c=t.length;a&&(i=Mn,f=false,t=a);n:for(;++o<e;)if(a=n[o],f&&a===a){for(var l=c;l--;)if(t[l]===a)continue n;u.push(a)}else 0>i(t,a,0)&&u.push(a);return u}function lt(n,t){var r=true;return zu(n,function(n,e,u){return r=!!t(n,e,u)}),r}function st(n,t,r,e){var u=e,o=u;return zu(n,function(n,i,f){
14
+ i=+t(n,i,f),(r(i,u)||i===e&&i===o)&&(u=i,o=n)}),o}function pt(n,t){var r=[];return zu(n,function(n,e,u){t(n,e,u)&&r.push(n)}),r}function ht(n,t,r,e){var u;return r(n,function(n,r,o){return t(n,r,o)?(u=e?r:n,false):void 0}),u}function _t(n,t,r,e){e||(e=[]);for(var u=-1,o=n.length;++u<o;){var i=n[u];h(i)&&Sr(i)&&(r||Wo(i)||_e(i))?t?_t(i,t,r,e):Hn(e,i):r||(e[e.length]=i)}return e}function vt(n,t){return Du(n,t,Ee)}function gt(n,t){return Du(n,t,Ko)}function yt(n,t){return Mu(n,t,Ko)}function dt(n,t){for(var r=-1,e=t.length,u=-1,o=[];++r<e;){
15
+ var i=t[r];ye(n[i])&&(o[++u]=i)}return o}function mt(n,t,r){if(null!=n){n=Dr(n),r!==w&&r in n&&(t=[r]),r=0;for(var e=t.length;null!=n&&r<e;)n=Dr(n)[t[r++]];return r&&r==e?n:w}}function wt(n,t,r,e,u,o){if(n===t)return true;if(null==n||null==t||!de(n)&&!h(t))return n!==n&&t!==t;n:{var i=wt,f=Wo(n),a=Wo(t),c=B,l=B;f||(c=ou.call(n),c==z?c=Z:c!=Z&&(f=je(n))),a||(l=ou.call(t),l==z?l=Z:l!=Z&&je(t));var s=c==Z&&!Gn(n),a=l==Z&&!Gn(t),l=c==l;if(!l||f||s){if(!e&&(c=s&&eu.call(n,"__wrapped__"),a=a&&eu.call(t,"__wrapped__"),
16
+ c||a)){n=i(c?n.value():n,a?t.value():t,r,e,u,o);break n}if(l){for(u||(u=[]),o||(o=[]),c=u.length;c--;)if(u[c]==n){n=o[c]==t;break n}u.push(n),o.push(t),n=(f?mr:xr)(n,t,i,r,e,u,o),u.pop(),o.pop()}else n=false}else n=wr(n,t,c)}return n}function xt(n,t,r){var e=t.length,u=e,o=!r;if(null==n)return!u;for(n=Dr(n);e--;){var i=t[e];if(o&&i[2]?i[1]!==n[i[0]]:!(i[0]in n))return false}for(;++e<u;){var i=t[e],f=i[0],a=n[f],c=i[1];if(o&&i[2]){if(a===w&&!(f in n))return false}else if(i=r?r(a,c,f):w,i===w?!wt(c,a,r,true):!i)return false;
17
+ }return true}function bt(n,t){var r=-1,e=Sr(n)?De(n.length):[];return zu(n,function(n,u,o){e[++r]=t(n,u,o)}),e}function At(n){var t=kr(n);if(1==t.length&&t[0][2]){var r=t[0][0],e=t[0][1];return function(n){return null==n?false:(n=Dr(n),n[r]===e&&(e!==w||r in n))}}return function(n){return xt(n,t)}}function jt(n,t){var r=Wo(n),e=Wr(n)&&t===t&&!de(t),u=n+"";return n=Mr(n),function(o){if(null==o)return false;var i=u;if(o=Dr(o),!(!r&&e||i in o)){if(o=1==n.length?o:mt(o,St(n,0,-1)),null==o)return false;i=Gr(n),o=Dr(o);
18
+ }return o[i]===t?t!==w||i in o:wt(t,o[i],w,true)}}function kt(n,t,r,e,u){if(!de(n))return n;var o=Sr(t)&&(Wo(t)||je(t)),i=o?w:Ko(t);return Kn(i||t,function(f,a){if(i&&(a=f,f=t[a]),h(f)){e||(e=[]),u||(u=[]);n:{for(var c=a,l=e,s=u,p=l.length,_=t[c];p--;)if(l[p]==_){n[c]=s[p];break n}var p=n[c],v=r?r(p,_,c,n,t):w,g=v===w;g&&(v=_,Sr(_)&&(Wo(_)||je(_))?v=Wo(p)?p:Sr(p)?qn(p):[]:xe(_)||_e(_)?v=_e(p)?Ie(p):xe(p)?p:{}:g=false),l.push(_),s.push(v),g?n[c]=kt(v,_,r,l,s):(v===v?v!==p:p===p)&&(n[c]=v)}}else c=n[a],
19
+ l=r?r(c,f,a,n,t):w,(s=l===w)&&(l=f),l===w&&(!o||a in n)||!s&&(l===l?l===c:c!==c)||(n[a]=l)}),n}function Ot(n){return function(t){return null==t?w:Dr(t)[n]}}function It(n){var t=n+"";return n=Mr(n),function(r){return mt(r,n,t)}}function Rt(n,t){for(var r=n?t.length:0;r--;){var e=t[r];if(e!=u&&Ur(e)){var u=e;vu.call(n,e,1)}}return n}function Et(n,t){return n+wu(Ru()*(t-n+1))}function Ct(n,t,r,e,u){return u(n,function(n,u,o){r=e?(e=false,n):t(r,n,u,o)}),r}function St(n,t,r){var e=-1,u=n.length;for(t=null==t?0:+t||0,
20
+ 0>t&&(t=-t>u?0:u+t),r=r===w||r>u?u:+r||0,0>r&&(r+=u),u=t>r?0:r-t>>>0,t>>>=0,r=De(u);++e<u;)r[e]=n[e+t];return r}function Ut(n,t){var r;return zu(n,function(n,e,u){return r=t(n,e,u),!r}),!!r}function $t(n,t){var r=n.length;for(n.sort(t);r--;)n[r]=n[r].c;return n}function Wt(t,r,e){var u=br(),o=-1;return r=Xn(r,function(n){return u(n)}),t=bt(t,function(n){return{a:Xn(r,function(t){return t(n)}),b:++o,c:n}}),$t(t,function(t,r){var u;n:{for(var o=-1,i=t.a,f=r.a,a=i.length,c=e.length;++o<a;)if(u=n(i[o],f[o])){
21
+ if(o>=c)break n;o=e[o],u*="asc"===o||true===o?1:-1;break n}u=t.b-r.b}return u})}function Ft(n,t){var r=0;return zu(n,function(n,e,u){r+=+t(n,e,u)||0}),r}function Lt(n,t){var e=-1,u=jr(),o=n.length,i=u===r,f=i&&o>=F,a=f&&mu&&hu?new Dn(void 0):null,c=[];a?(u=Mn,i=false):(f=false,a=t?[]:c);n:for(;++e<o;){var l=n[e],s=t?t(l,e,n):l;if(i&&l===l){for(var p=a.length;p--;)if(a[p]===s)continue n;t&&a.push(s),c.push(l)}else 0>u(a,s,0)&&((t||f)&&a.push(s),c.push(l))}return c}function Nt(n,t){for(var r=-1,e=t.length,u=De(e);++r<e;)u[r]=n[t[r]];
22
+ return u}function Tt(n,t,r,e){for(var u=n.length,o=e?u:-1;(e?o--:++o<u)&&t(n[o],o,n););return r?St(n,e?0:o,e?o+1:u):St(n,e?o+1:0,e?u:o)}function Pt(n,t){var r=n;r instanceof zn&&(r=r.value());for(var e=-1,u=t.length;++e<u;)var o=t[e],r=o.func.apply(o.thisArg,Hn([r],o.args));return r}function zt(n,t,r){var e=0,u=n?n.length:e;if(typeof t=="number"&&t===t&&u<=Uu){for(;e<u;){var o=e+u>>>1,i=n[o];(r?i<=t:i<t)&&null!==i?e=o+1:u=o}return u}return Bt(n,t,Ne,r)}function Bt(n,t,r,e){t=r(t);for(var u=0,o=n?n.length:0,i=t!==t,f=null===t,a=t===w;u<o;){
23
+ var c=wu((u+o)/2),l=r(n[c]),s=l!==w,p=l===l;(i?p||e:f?p&&s&&(e||null!=l):a?p&&(e||s):null==l?0:e?l<=t:l<t)?u=c+1:o=c}return ku(o,Su)}function Dt(n,t,r){if(typeof n!="function")return Ne;if(t===w)return n;switch(r){case 1:return function(r){return n.call(t,r)};case 3:return function(r,e,u){return n.call(t,r,e,u)};case 4:return function(r,e,u,o){return n.call(t,r,e,u,o)};case 5:return function(r,e,u,o,i){return n.call(t,r,e,u,o,i)}}return function(){return n.apply(t,arguments)}}function Mt(n){var t=new au(n.byteLength);
24
+ return new gu(t).set(new gu(n)),t}function qt(n,t,r){for(var e=r.length,u=-1,o=ju(n.length-e,0),i=-1,f=t.length,a=De(f+o);++i<f;)a[i]=t[i];for(;++u<e;)a[r[u]]=n[u];for(;o--;)a[i++]=n[u++];return a}function Kt(n,t,r){for(var e=-1,u=r.length,o=-1,i=ju(n.length-u,0),f=-1,a=t.length,c=De(i+a);++o<i;)c[o]=n[o];for(i=o;++f<a;)c[i+f]=t[f];for(;++e<u;)c[i+r[e]]=n[o++];return c}function Vt(n,t){return function(r,e,u){var o=t?t():{};if(e=br(e,u,3),Wo(r)){u=-1;for(var i=r.length;++u<i;){var f=r[u];n(o,f,e(f,u,r),r);
25
+ }}else zu(r,function(t,r,u){n(o,t,e(t,r,u),u)});return o}}function Zt(n){return pe(function(t,r){var e=-1,u=null==t?0:r.length,o=2<u?r[u-2]:w,i=2<u?r[2]:w,f=1<u?r[u-1]:w;for(typeof o=="function"?(o=Dt(o,f,5),u-=2):(o=typeof f=="function"?f:w,u-=o?1:0),i&&$r(r[0],r[1],i)&&(o=3>u?w:o,u=1);++e<u;)(i=r[e])&&n(t,i,o);return t})}function Yt(n,t){return function(r,e){var u=r?Vu(r):0;if(!Lr(u))return n(r,e);for(var o=t?u:-1,i=Dr(r);(t?o--:++o<u)&&false!==e(i[o],o,i););return r}}function Gt(n){return function(t,r,e){
26
+ var u=Dr(t);e=e(t);for(var o=e.length,i=n?o:-1;n?i--:++i<o;){var f=e[i];if(false===r(u[f],f,u))break}return t}}function Jt(n,t){function r(){return(this&&this!==Yn&&this instanceof r?e:n).apply(t,arguments)}var e=Ht(n);return r}function Xt(n){return function(t){var r=-1;t=Fe(Ue(t));for(var e=t.length,u="";++r<e;)u=n(u,t[r],r);return u}}function Ht(n){return function(){var t=arguments;switch(t.length){case 0:return new n;case 1:return new n(t[0]);case 2:return new n(t[0],t[1]);case 3:return new n(t[0],t[1],t[2]);
27
+ case 4:return new n(t[0],t[1],t[2],t[3]);case 5:return new n(t[0],t[1],t[2],t[3],t[4]);case 6:return new n(t[0],t[1],t[2],t[3],t[4],t[5]);case 7:return new n(t[0],t[1],t[2],t[3],t[4],t[5],t[6])}var r=Pu(n.prototype),t=n.apply(r,t);return de(t)?t:r}}function Qt(n){function t(r,e,u){return u&&$r(r,e,u)&&(e=w),r=dr(r,n,w,w,w,w,w,e),r.placeholder=t.placeholder,r}return t}function nr(n,t){return pe(function(r){var e=r[0];return null==e?e:(r.push(t),n.apply(w,r))})}function tr(n,t){return function(r,e,u){
28
+ if(u&&$r(r,e,u)&&(e=w),e=br(e,u,3),1==e.length){u=r=Wo(r)?r:Br(r);for(var o=e,i=-1,f=u.length,a=t,c=a;++i<f;){var l=u[i],s=+o(l);n(s,a)&&(a=s,c=l)}if(u=c,!r.length||u!==t)return u}return st(r,e,n,t)}}function rr(n,r){return function(e,u,o){return u=br(u,o,3),Wo(e)?(u=t(e,u,r),-1<u?e[u]:w):ht(e,u,n)}}function er(n){return function(r,e,u){return r&&r.length?(e=br(e,u,3),t(r,e,n)):-1}}function ur(n){return function(t,r,e){return r=br(r,e,3),ht(t,r,n,true)}}function or(n){return function(){for(var t,r=arguments.length,e=n?r:-1,u=0,o=De(r);n?e--:++e<r;){
29
+ var i=o[u++]=arguments[e];if(typeof i!="function")throw new Xe(T);!t&&Pn.prototype.thru&&"wrapper"==Ar(i)&&(t=new Pn([],true))}for(e=t?-1:r;++e<r;){var i=o[e],u=Ar(i),f="wrapper"==u?Ku(i):w;t=f&&Fr(f[0])&&f[1]==(E|k|I|C)&&!f[4].length&&1==f[9]?t[Ar(f[0])].apply(t,f[3]):1==i.length&&Fr(i)?t[u]():t.thru(i)}return function(){var n=arguments,e=n[0];if(t&&1==n.length&&Wo(e)&&e.length>=F)return t.plant(e).value();for(var u=0,n=r?o[u].apply(this,n):e;++u<r;)n=o[u].call(this,n);return n}}}function ir(n,t){
30
+ return function(r,e,u){return typeof e=="function"&&u===w&&Wo(r)?n(r,e):t(r,Dt(e,u,3))}}function fr(n){return function(t,r,e){return(typeof r!="function"||e!==w)&&(r=Dt(r,e,3)),n(t,r,Ee)}}function ar(n){return function(t,r,e){return(typeof r!="function"||e!==w)&&(r=Dt(r,e,3)),n(t,r)}}function cr(n){return function(t,r,e){var u={};return r=br(r,e,3),gt(t,function(t,e,o){o=r(t,e,o),e=n?o:e,t=n?t:o,u[e]=t}),u}}function lr(n){return function(t,r,e){return t=u(t),(n?t:"")+_r(t,r,e)+(n?"":t)}}function sr(n){
31
+ var t=pe(function(r,e){var u=v(e,t.placeholder);return dr(r,n,w,e,u)});return t}function pr(n,t){return function(r,e,u,o){var i=3>arguments.length;return typeof e=="function"&&o===w&&Wo(r)?n(r,e,u,i):Ct(r,br(e,o,4),u,i,t)}}function hr(n,t,r,e,u,o,i,f,a,c){function l(){for(var m=arguments.length,x=m,j=De(m);x--;)j[x]=arguments[x];if(e&&(j=qt(j,e,u)),o&&(j=Kt(j,o,i)),_||y){var x=l.placeholder,k=v(j,x),m=m-k.length;if(m<c){var O=f?qn(f):w,m=ju(c-m,0),E=_?k:w,k=_?w:k,C=_?j:w,j=_?w:j;return t|=_?I:R,t&=~(_?R:I),
32
+ g||(t&=~(b|A)),j=[n,t,r,C,E,j,k,O,a,m],O=hr.apply(w,j),Fr(n)&&Zu(O,j),O.placeholder=x,O}}if(x=p?r:this,O=h?x[n]:n,f)for(m=j.length,E=ku(f.length,m),k=qn(j);E--;)C=f[E],j[E]=Ur(C,m)?k[C]:w;return s&&a<j.length&&(j.length=a),this&&this!==Yn&&this instanceof l&&(O=d||Ht(n)),O.apply(x,j)}var s=t&E,p=t&b,h=t&A,_=t&k,g=t&j,y=t&O,d=h?w:Ht(n);return l}function _r(n,t,r){return n=n.length,t=+t,n<t&&bu(t)?(t-=n,r=null==r?" ":r+"",$e(r,du(t/r.length)).slice(0,t)):""}function vr(n,t,r,e){function u(){for(var t=-1,f=arguments.length,a=-1,c=e.length,l=De(c+f);++a<c;)l[a]=e[a];
33
+ for(;f--;)l[a++]=arguments[++t];return(this&&this!==Yn&&this instanceof u?i:n).apply(o?r:this,l)}var o=t&b,i=Ht(n);return u}function gr(n){var t=Ve[n];return function(n,r){return(r=r===w?0:+r||0)?(r=su(10,r),t(n*r)/r):t(n)}}function yr(n){return function(t,r,e,u){var o=br(e);return null==e&&o===it?zt(t,r,n):Bt(t,r,o(e,u,1),n)}}function dr(n,t,r,e,u,o,i,f){var a=t&A;if(!a&&typeof n!="function")throw new Xe(T);var c=e?e.length:0;if(c||(t&=~(I|R),e=u=w),c-=u?u.length:0,t&R){var l=e,s=u;e=u=w}var p=a?w:Ku(n);
34
+ return r=[n,t,r,e,u,l,s,o,i,f],p&&(e=r[1],t=p[1],f=e|t,u=t==E&&e==k||t==E&&e==C&&r[7].length<=p[8]||t==(E|C)&&e==k,(f<E||u)&&(t&b&&(r[2]=p[2],f|=e&b?0:j),(e=p[3])&&(u=r[3],r[3]=u?qt(u,e,p[4]):qn(e),r[4]=u?v(r[3],P):qn(p[4])),(e=p[5])&&(u=r[5],r[5]=u?Kt(u,e,p[6]):qn(e),r[6]=u?v(r[5],P):qn(p[6])),(e=p[7])&&(r[7]=qn(e)),t&E&&(r[8]=null==r[8]?p[8]:ku(r[8],p[8])),null==r[9]&&(r[9]=p[9]),r[0]=p[0],r[1]=f),t=r[1],f=r[9]),r[9]=null==f?a?0:n.length:ju(f-c,0)||0,n=t==b?Jt(r[0],r[2]):t!=I&&t!=(b|I)||r[4].length?hr.apply(w,r):vr.apply(w,r),
35
+ (p?qu:Zu)(n,r)}function mr(n,t,r,e,u,o,i){var f=-1,a=n.length,c=t.length;if(a!=c&&(!u||c<=a))return false;for(;++f<a;){var l=n[f],c=t[f],s=e?e(u?c:l,u?l:c,f):w;if(s!==w){if(s)continue;return false}if(u){if(!nt(t,function(n){return l===n||r(l,n,e,u,o,i)}))return false}else if(l!==c&&!r(l,c,e,u,o,i))return false}return true}function wr(n,t,r){switch(r){case D:case M:return+n==+t;case q:return n.name==t.name&&n.message==t.message;case V:return n!=+n?t!=+t:n==+t;case Y:case G:return n==t+""}return false}function xr(n,t,r,e,u,o,i){
36
+ var f=Ko(n),a=f.length,c=Ko(t).length;if(a!=c&&!u)return false;for(c=a;c--;){var l=f[c];if(!(u?l in t:eu.call(t,l)))return false}for(var s=u;++c<a;){var l=f[c],p=n[l],h=t[l],_=e?e(u?h:p,u?p:h,l):w;if(_===w?!r(p,h,e,u,o,i):!_)return false;s||(s="constructor"==l)}return s||(r=n.constructor,e=t.constructor,!(r!=e&&"constructor"in n&&"constructor"in t)||typeof r=="function"&&r instanceof r&&typeof e=="function"&&e instanceof e)?true:false}function br(n,t,r){var e=Nn.callback||Le,e=e===Le?it:e;return r?e(n,t,r):e}function Ar(n){
37
+ for(var t=n.name+"",r=Fu[t],e=r?r.length:0;e--;){var u=r[e],o=u.func;if(null==o||o==n)return u.name}return t}function jr(n,t,e){var u=Nn.indexOf||Yr,u=u===Yr?r:u;return n?u(n,t,e):u}function kr(n){n=Ce(n);for(var t=n.length;t--;){var r,e=n[t];r=n[t][1],r=r===r&&!de(r),e[2]=r}return n}function Or(n,t){var r=null==n?w:n[t];return me(r)?r:w}function Ir(n){var t=n.length,r=new n.constructor(t);return t&&"string"==typeof n[0]&&eu.call(n,"index")&&(r.index=n.index,r.input=n.input),r}function Rr(n){return n=n.constructor,
38
+ typeof n=="function"&&n instanceof n||(n=Ye),new n}function Er(n,t,r){var e=n.constructor;switch(t){case J:return Mt(n);case D:case M:return new e(+n);case X:case H:case Q:case nn:case tn:case rn:case en:case un:case on:return e instanceof e&&(e=Lu[t]),t=n.buffer,new e(r?Mt(t):t,n.byteOffset,n.length);case V:case G:return new e(n);case Y:var u=new e(n.source,kn.exec(n));u.lastIndex=n.lastIndex}return u}function Cr(n,t,r){return null==n||Wr(t,n)||(t=Mr(t),n=1==t.length?n:mt(n,St(t,0,-1)),t=Gr(t)),
39
+ t=null==n?n:n[t],null==t?w:t.apply(n,r)}function Sr(n){return null!=n&&Lr(Vu(n))}function Ur(n,t){return n=typeof n=="number"||Rn.test(n)?+n:-1,t=null==t?$u:t,-1<n&&0==n%1&&n<t}function $r(n,t,r){if(!de(r))return false;var e=typeof t;return("number"==e?Sr(r)&&Ur(t,r.length):"string"==e&&t in r)?(t=r[t],n===n?n===t:t!==t):false}function Wr(n,t){var r=typeof n;return"string"==r&&dn.test(n)||"number"==r?true:Wo(n)?false:!yn.test(n)||null!=t&&n in Dr(t)}function Fr(n){var t=Ar(n),r=Nn[t];return typeof r=="function"&&t in zn.prototype?n===r?true:(t=Ku(r),
40
+ !!t&&n===t[0]):false}function Lr(n){return typeof n=="number"&&-1<n&&0==n%1&&n<=$u}function Nr(n,t){return n===w?t:Fo(n,t,Nr)}function Tr(n,t){n=Dr(n);for(var r=-1,e=t.length,u={};++r<e;){var o=t[r];o in n&&(u[o]=n[o])}return u}function Pr(n,t){var r={};return vt(n,function(n,e,u){t(n,e,u)&&(r[e]=n)}),r}function zr(n){for(var t=Ee(n),r=t.length,e=r&&n.length,u=!!e&&Lr(e)&&(Wo(n)||_e(n)||Ae(n)),o=-1,i=[];++o<r;){var f=t[o];(u&&Ur(f,e)||eu.call(n,f))&&i.push(f)}return i}function Br(n){return null==n?[]:Sr(n)?Nn.support.unindexedChars&&Ae(n)?n.split(""):de(n)?n:Ye(n):Se(n);
41
+ }function Dr(n){if(Nn.support.unindexedChars&&Ae(n)){for(var t=-1,r=n.length,e=Ye(n);++t<r;)e[t]=n.charAt(t);return e}return de(n)?n:Ye(n)}function Mr(n){if(Wo(n))return n;var t=[];return u(n).replace(mn,function(n,r,e,u){t.push(e?u.replace(An,"$1"):r||n)}),t}function qr(n){return n instanceof zn?n.clone():new Pn(n.__wrapped__,n.__chain__,qn(n.__actions__))}function Kr(n,t,r){return n&&n.length?((r?$r(n,t,r):null==t)&&(t=1),St(n,0>t?0:t)):[]}function Vr(n,t,r){var e=n?n.length:0;return e?((r?$r(n,t,r):null==t)&&(t=1),
42
+ t=e-(+t||0),St(n,0,0>t?0:t)):[]}function Zr(n){return n?n[0]:w}function Yr(n,t,e){var u=n?n.length:0;if(!u)return-1;if(typeof e=="number")e=0>e?ju(u+e,0):e;else if(e)return e=zt(n,t),e<u&&(t===t?t===n[e]:n[e]!==n[e])?e:-1;return r(n,t,e||0)}function Gr(n){var t=n?n.length:0;return t?n[t-1]:w}function Jr(n){return Kr(n,1)}function Xr(n,t,e,u){if(!n||!n.length)return[];null!=t&&typeof t!="boolean"&&(u=e,e=$r(n,t,u)?w:t,t=false);var o=br();if((null!=e||o!==it)&&(e=o(e,u,3)),t&&jr()===r){t=e;var i;e=-1,
43
+ u=n.length;for(var o=-1,f=[];++e<u;){var a=n[e],c=t?t(a,e,n):a;e&&i===c||(i=c,f[++o]=a)}n=f}else n=Lt(n,e);return n}function Hr(n){if(!n||!n.length)return[];var t=-1,r=0;n=Zn(n,function(n){return Sr(n)?(r=ju(n.length,r),true):void 0});for(var e=De(r);++t<r;)e[t]=Xn(n,Ot(t));return e}function Qr(n,t,r){return n&&n.length?(n=Hr(n),null==t?n:(t=Dt(t,r,4),Xn(n,function(n){return Qn(n,t,w,true)}))):[]}function ne(n,t){var r=-1,e=n?n.length:0,u={};for(!e||t||Wo(n[0])||(t=[]);++r<e;){var o=n[r];t?u[o]=t[r]:o&&(u[o[0]]=o[1]);
44
+ }return u}function te(n){return n=Nn(n),n.__chain__=true,n}function re(n,t,r){return t.call(r,n)}function ee(n,t,r){var e=Wo(n)?Vn:lt;return r&&$r(n,t,r)&&(t=w),(typeof t!="function"||r!==w)&&(t=br(t,r,3)),e(n,t)}function ue(n,t,r){var e=Wo(n)?Zn:pt;return t=br(t,r,3),e(n,t)}function oe(n,t,r,e){var u=n?Vu(n):0;return Lr(u)||(n=Se(n),u=n.length),r=typeof r!="number"||e&&$r(t,r,e)?0:0>r?ju(u+r,0):r||0,typeof n=="string"||!Wo(n)&&Ae(n)?r<=u&&-1<n.indexOf(t,r):!!u&&-1<jr(n,t,r)}function ie(n,t,r){var e=Wo(n)?Xn:bt;
45
+ return t=br(t,r,3),e(n,t)}function fe(n,t,r){if(r?$r(n,t,r):null==t){n=Br(n);var e=n.length;return 0<e?n[Et(0,e-1)]:w}r=-1,n=Oe(n);var e=n.length,u=e-1;for(t=ku(0>t?0:+t||0,e);++r<t;){var e=Et(r,u),o=n[e];n[e]=n[r],n[r]=o}return n.length=t,n}function ae(n,t,r){var e=Wo(n)?nt:Ut;return r&&$r(n,t,r)&&(t=w),(typeof t!="function"||r!==w)&&(t=br(t,r,3)),e(n,t)}function ce(n,t){var r;if(typeof t!="function"){if(typeof n!="function")throw new Xe(T);var e=n;n=t,t=e}return function(){return 0<--n&&(r=t.apply(this,arguments)),
46
+ 1>=n&&(t=w),r}}function le(n,t,r){function e(t,r){r&&cu(r),a=p=h=w,t&&(_=wo(),c=n.apply(s,f),p||a||(f=s=w))}function u(){var n=t-(wo()-l);0>=n||n>t?e(h,a):p=_u(u,n)}function o(){e(g,p)}function i(){if(f=arguments,l=wo(),s=this,h=g&&(p||!y),false===v)var r=y&&!p;else{a||y||(_=l);var e=v-(l-_),i=0>=e||e>v;i?(a&&(a=cu(a)),_=l,c=n.apply(s,f)):a||(a=_u(o,e))}return i&&p?p=cu(p):p||t===v||(p=_u(u,t)),r&&(i=true,c=n.apply(s,f)),!i||p||a||(f=s=w),c}var f,a,c,l,s,p,h,_=0,v=false,g=true;if(typeof n!="function")throw new Xe(T);
47
+ if(t=0>t?0:+t||0,true===r)var y=true,g=false;else de(r)&&(y=!!r.leading,v="maxWait"in r&&ju(+r.maxWait||0,t),g="trailing"in r?!!r.trailing:g);return i.cancel=function(){p&&cu(p),a&&cu(a),_=0,a=p=h=w},i}function se(n,t){if(typeof n!="function"||t&&typeof t!="function")throw new Xe(T);var r=function(){var e=arguments,u=t?t.apply(this,e):e[0],o=r.cache;return o.has(u)?o.get(u):(e=n.apply(this,e),r.cache=o.set(u,e),e)};return r.cache=new se.Cache,r}function pe(n,t){if(typeof n!="function")throw new Xe(T);return t=ju(t===w?n.length-1:+t||0,0),
48
+ function(){for(var r=arguments,e=-1,u=ju(r.length-t,0),o=De(u);++e<u;)o[e]=r[t+e];switch(t){case 0:return n.call(this,o);case 1:return n.call(this,r[0],o);case 2:return n.call(this,r[0],r[1],o)}for(u=De(t+1),e=-1;++e<t;)u[e]=r[e];return u[t]=o,n.apply(this,u)}}function he(n,t){return n>t}function _e(n){return h(n)&&Sr(n)&&eu.call(n,"callee")&&!pu.call(n,"callee")}function ve(n,t,r,e){return e=(r=typeof r=="function"?Dt(r,e,3):w)?r(n,t):w,e===w?wt(n,t,r):!!e}function ge(n){return h(n)&&typeof n.message=="string"&&ou.call(n)==q;
49
+ }function ye(n){return de(n)&&ou.call(n)==K}function de(n){var t=typeof n;return!!n&&("object"==t||"function"==t)}function me(n){return null==n?false:ye(n)?fu.test(ru.call(n)):h(n)&&(Gn(n)?fu:In).test(n)}function we(n){return typeof n=="number"||h(n)&&ou.call(n)==V}function xe(n){var t;if(!h(n)||ou.call(n)!=Z||Gn(n)||_e(n)||!(eu.call(n,"constructor")||(t=n.constructor,typeof t!="function"||t instanceof t)))return false;var r;return Nn.support.ownLast?(vt(n,function(n,t,e){return r=eu.call(e,t),false}),false!==r):(vt(n,function(n,t){
50
+ r=t}),r===w||eu.call(n,r))}function be(n){return de(n)&&ou.call(n)==Y}function Ae(n){return typeof n=="string"||h(n)&&ou.call(n)==G}function je(n){return h(n)&&Lr(n.length)&&!!Fn[ou.call(n)]}function ke(n,t){return n<t}function Oe(n){var t=n?Vu(n):0;return Lr(t)?t?Nn.support.unindexedChars&&Ae(n)?n.split(""):qn(n):[]:Se(n)}function Ie(n){return ot(n,Ee(n))}function Re(n){return dt(n,Ee(n))}function Ee(n){if(null==n)return[];de(n)||(n=Ye(n));for(var t=n.length,r=Nn.support,t=t&&Lr(t)&&(Wo(n)||_e(n)||Ae(n))&&t||0,e=n.constructor,u=-1,e=ye(e)&&e.prototype||nu,o=e===n,i=De(t),f=0<t,a=r.enumErrorProps&&(n===Qe||n instanceof qe),c=r.enumPrototypes&&ye(n);++u<t;)i[u]=u+"";
51
+ for(var l in n)c&&"prototype"==l||a&&("message"==l||"name"==l)||f&&Ur(l,t)||"constructor"==l&&(o||!eu.call(n,l))||i.push(l);if(r.nonEnumShadows&&n!==nu)for(t=n===tu?G:n===Qe?q:ou.call(n),r=Nu[t]||Nu[Z],t==Z&&(e=nu),t=Wn.length;t--;)l=Wn[t],u=r[l],o&&u||(u?!eu.call(n,l):n[l]===e[l])||i.push(l);return i}function Ce(n){n=Dr(n);for(var t=-1,r=Ko(n),e=r.length,u=De(e);++t<e;){var o=r[t];u[t]=[o,n[o]]}return u}function Se(n){return Nt(n,Ko(n))}function Ue(n){return(n=u(n))&&n.replace(En,a).replace(bn,"");
52
+ }function $e(n,t){var r="";if(n=u(n),t=+t,1>t||!n||!bu(t))return r;do t%2&&(r+=n),t=wu(t/2),n+=n;while(t);return r}function We(n,t,r){var e=n;return(n=u(n))?(r?$r(e,t,r):null==t)?n.slice(g(n),y(n)+1):(t+="",n.slice(o(n,t),i(n,t)+1)):n}function Fe(n,t,r){return r&&$r(n,t,r)&&(t=w),n=u(n),n.match(t||Un)||[]}function Le(n,t,r){return r&&$r(n,t,r)&&(t=w),h(n)?Te(n):it(n,t)}function Ne(n){return n}function Te(n){return At(ft(n,true))}function Pe(n,t,r){if(null==r){var e=de(t),u=e?Ko(t):w;((u=u&&u.length?dt(t,u):w)?u.length:e)||(u=false,
53
+ r=t,t=n,n=this)}u||(u=dt(t,Ko(t)));var o=true,e=-1,i=ye(n),f=u.length;false===r?o=false:de(r)&&"chain"in r&&(o=r.chain);for(;++e<f;){r=u[e];var a=t[r];n[r]=a,i&&(n.prototype[r]=function(t){return function(){var r=this.__chain__;if(o||r){var e=n(this.__wrapped__);return(e.__actions__=qn(this.__actions__)).push({func:t,args:arguments,thisArg:n}),e.__chain__=r,e}return t.apply(n,Hn([this.value()],arguments))}}(a))}return n}function ze(){}function Be(n){return Wr(n)?Ot(n):It(n)}_=_?Jn.defaults(Yn.Object(),_,Jn.pick(Yn,$n)):Yn;
54
+ var De=_.Array,Me=_.Date,qe=_.Error,Ke=_.Function,Ve=_.Math,Ze=_.Number,Ye=_.Object,Ge=_.RegExp,Je=_.String,Xe=_.TypeError,He=De.prototype,Qe=qe.prototype,nu=Ye.prototype,tu=Je.prototype,ru=Ke.prototype.toString,eu=nu.hasOwnProperty,uu=0,ou=nu.toString,iu=Yn._,fu=Ge("^"+ru.call(eu).replace(/[\\^$.*+?()[\]{}|]/g,"\\$&").replace(/hasOwnProperty|(function).*?(?=\\\()| for .+?(?=\\\])/g,"$1.*?")+"$"),au=_.ArrayBuffer,cu=_.clearTimeout,lu=_.parseFloat,su=Ve.pow,pu=nu.propertyIsEnumerable,hu=Or(_,"Set"),_u=_.setTimeout,vu=He.splice,gu=_.Uint8Array,yu=Or(_,"WeakMap"),du=Ve.ceil,mu=Or(Ye,"create"),wu=Ve.floor,xu=Or(De,"isArray"),bu=_.isFinite,Au=Or(Ye,"keys"),ju=Ve.max,ku=Ve.min,Ou=Or(Me,"now"),Iu=_.parseInt,Ru=Ve.random,Eu=Ze.NEGATIVE_INFINITY,Cu=Ze.POSITIVE_INFINITY,Su=4294967294,Uu=2147483647,$u=9007199254740991,Wu=yu&&new yu,Fu={},Lu={};
55
+ Lu[X]=_.Float32Array,Lu[H]=_.Float64Array,Lu[Q]=_.Int8Array,Lu[nn]=_.Int16Array,Lu[tn]=_.Int32Array,Lu[rn]=gu,Lu[en]=_.Uint8ClampedArray,Lu[un]=_.Uint16Array,Lu[on]=_.Uint32Array;var Nu={};Nu[B]=Nu[M]=Nu[V]={constructor:true,toLocaleString:true,toString:true,valueOf:true},Nu[D]=Nu[G]={constructor:true,toString:true,valueOf:true},Nu[q]=Nu[K]=Nu[Y]={constructor:true,toString:true},Nu[Z]={constructor:true},Kn(Wn,function(n){for(var t in Nu)if(eu.call(Nu,t)){var r=Nu[t];r[n]=eu.call(r,n)}});var Tu=Nn.support={};!function(n){
56
+ var t=function(){this.x=n},r={0:n,length:n},e=[];t.prototype={valueOf:n,y:n};for(var u in new t)e.push(u);Tu.enumErrorProps=pu.call(Qe,"message")||pu.call(Qe,"name"),Tu.enumPrototypes=pu.call(t,"prototype"),Tu.nonEnumShadows=!/valueOf/.test(e),Tu.ownLast="x"!=e[0],Tu.spliceObjects=(vu.call(r,0,1),!r[0]),Tu.unindexedChars="xx"!="x"[0]+Ye("x")[0]}(1,0),Nn.templateSettings={escape:_n,evaluate:vn,interpolate:gn,variable:"",imports:{_:Nn}};var Pu=function(){function n(){}return function(t){if(de(t)){n.prototype=t;
57
+ var r=new n;n.prototype=w}return r||{}}}(),zu=Yt(gt),Bu=Yt(yt,true),Du=Gt(),Mu=Gt(true),qu=Wu?function(n,t){return Wu.set(n,t),n}:Ne,Ku=Wu?function(n){return Wu.get(n)}:ze,Vu=Ot("length"),Zu=function(){var n=0,t=0;return function(r,e){var u=wo(),o=W-(u-t);if(t=u,0<o){if(++n>=$)return r}else n=0;return qu(r,e)}}(),Yu=pe(function(n,t){return h(n)&&Sr(n)?ct(n,_t(t,false,true)):[]}),Gu=er(),Ju=er(true),Xu=pe(function(n){for(var t=n.length,e=t,u=De(l),o=jr(),i=o===r,f=[];e--;){var a=n[e]=Sr(a=n[e])?a:[];u[e]=i&&120<=a.length&&mu&&hu?new Dn(e&&a):null;
58
+ }var i=n[0],c=-1,l=i?i.length:0,s=u[0];n:for(;++c<l;)if(a=i[c],0>(s?Mn(s,a):o(f,a,0))){for(e=t;--e;){var p=u[e];if(0>(p?Mn(p,a):o(n[e],a,0)))continue n}s&&s.push(a),f.push(a)}return f}),Hu=pe(function(t,r){r=_t(r);var e=ut(t,r);return Rt(t,r.sort(n)),e}),Qu=yr(),no=yr(true),to=pe(function(n){return Lt(_t(n,false,true))}),ro=pe(function(n,t){return Sr(n)?ct(n,t):[]}),eo=pe(Hr),uo=pe(function(n){var t=n.length,r=2<t?n[t-2]:w,e=1<t?n[t-1]:w;return 2<t&&typeof r=="function"?t-=2:(r=1<t&&typeof e=="function"?(--t,
59
+ e):w,e=w),n.length=t,Qr(n,r,e)}),oo=pe(function(n){return n=_t(n),this.thru(function(t){t=Wo(t)?t:[Dr(t)];for(var r=n,e=-1,u=t.length,o=-1,i=r.length,f=De(u+i);++e<u;)f[e]=t[e];for(;++o<i;)f[e++]=r[o];return f})}),io=pe(function(n,t){return Sr(n)&&(n=Br(n)),ut(n,_t(t))}),fo=Vt(function(n,t,r){eu.call(n,r)?++n[r]:n[r]=1}),ao=rr(zu),co=rr(Bu,true),lo=ir(Kn,zu),so=ir(function(n,t){for(var r=n.length;r--&&false!==t(n[r],r,n););return n},Bu),po=Vt(function(n,t,r){eu.call(n,r)?n[r].push(t):n[r]=[t]}),ho=Vt(function(n,t,r){
60
+ n[r]=t}),_o=pe(function(n,t,r){var e=-1,u=typeof t=="function",o=Wr(t),i=Sr(n)?De(n.length):[];return zu(n,function(n){var f=u?t:o&&null!=n?n[t]:w;i[++e]=f?f.apply(n,r):Cr(n,t,r)}),i}),vo=Vt(function(n,t,r){n[r?0:1].push(t)},function(){return[[],[]]}),go=pr(Qn,zu),yo=pr(function(n,t,r,e){var u=n.length;for(e&&u&&(r=n[--u]);u--;)r=t(r,n[u],u,n);return r},Bu),mo=pe(function(n,t){if(null==n)return[];var r=t[2];return r&&$r(t[0],t[1],r)&&(t.length=1),Wt(n,_t(t),[])}),wo=Ou||function(){return(new Me).getTime();
61
+ },xo=pe(function(n,t,r){var e=b;if(r.length)var u=v(r,xo.placeholder),e=e|I;return dr(n,e,t,r,u)}),bo=pe(function(n,t){t=t.length?_t(t):Re(n);for(var r=-1,e=t.length;++r<e;){var u=t[r];n[u]=dr(n[u],b,n)}return n}),Ao=pe(function(n,t,r){var e=b|A;if(r.length)var u=v(r,Ao.placeholder),e=e|I;return dr(t,e,n,r,u)}),jo=Qt(k),ko=Qt(O),Oo=pe(function(n,t){return at(n,1,t)}),Io=pe(function(n,t,r){return at(n,t,r)}),Ro=or(),Eo=or(true),Co=pe(function(n,t){if(t=_t(t),typeof n!="function"||!Vn(t,e))throw new Xe(T);
62
+ var r=t.length;return pe(function(e){for(var u=ku(e.length,r);u--;)e[u]=t[u](e[u]);return n.apply(this,e)})}),So=sr(I),Uo=sr(R),$o=pe(function(n,t){return dr(n,C,w,w,w,_t(t))}),Wo=xu||function(n){return h(n)&&Lr(n.length)&&ou.call(n)==B},Fo=Zt(kt),Lo=Zt(function(n,t,r){return r?rt(n,t,r):et(n,t)}),No=nr(Lo,function(n,t){return n===w?t:n}),To=nr(Fo,Nr),Po=ur(gt),zo=ur(yt),Bo=fr(Du),Do=fr(Mu),Mo=ar(gt),qo=ar(yt),Ko=Au?function(n){var t=null==n?w:n.constructor;return typeof t=="function"&&t.prototype===n||(typeof n=="function"?Nn.support.enumPrototypes:Sr(n))?zr(n):de(n)?Au(n):[];
63
+ }:zr,Vo=cr(true),Zo=cr(),Yo=pe(function(n,t){if(null==n)return{};if("function"!=typeof t[0])return t=Xn(_t(t),Je),Tr(n,ct(Ee(n),t));var r=Dt(t[0],t[1],3);return Pr(n,function(n,t,e){return!r(n,t,e)})}),Go=pe(function(n,t){return null==n?{}:"function"==typeof t[0]?Pr(n,Dt(t[0],t[1],3)):Tr(n,_t(t))}),Jo=Xt(function(n,t,r){return t=t.toLowerCase(),n+(r?t.charAt(0).toUpperCase()+t.slice(1):t)}),Xo=Xt(function(n,t,r){return n+(r?"-":"")+t.toLowerCase()}),Ho=lr(),Qo=lr(true),ni=Xt(function(n,t,r){return n+(r?"_":"")+t.toLowerCase();
64
+ }),ti=Xt(function(n,t,r){return n+(r?" ":"")+(t.charAt(0).toUpperCase()+t.slice(1))}),ri=pe(function(n,t){try{return n.apply(w,t)}catch(r){return ge(r)?r:new qe(r)}}),ei=pe(function(n,t){return function(r){return Cr(r,n,t)}}),ui=pe(function(n,t){return function(r){return Cr(n,r,t)}}),oi=gr("ceil"),ii=gr("floor"),fi=tr(he,Eu),ai=tr(ke,Cu),ci=gr("round");return Nn.prototype=Tn.prototype,Pn.prototype=Pu(Tn.prototype),Pn.prototype.constructor=Pn,zn.prototype=Pu(Tn.prototype),zn.prototype.constructor=zn,
65
+ Bn.prototype["delete"]=function(n){return this.has(n)&&delete this.__data__[n]},Bn.prototype.get=function(n){return"__proto__"==n?w:this.__data__[n]},Bn.prototype.has=function(n){return"__proto__"!=n&&eu.call(this.__data__,n)},Bn.prototype.set=function(n,t){return"__proto__"!=n&&(this.__data__[n]=t),this},Dn.prototype.push=function(n){var t=this.data;typeof n=="string"||de(n)?t.set.add(n):t.hash[n]=true},se.Cache=Bn,Nn.after=function(n,t){if(typeof t!="function"){if(typeof n!="function")throw new Xe(T);
66
+ var r=n;n=t,t=r}return n=bu(n=+n)?n:0,function(){return 1>--n?t.apply(this,arguments):void 0}},Nn.ary=function(n,t,r){return r&&$r(n,t,r)&&(t=w),t=n&&null==t?n.length:ju(+t||0,0),dr(n,E,w,w,w,w,t)},Nn.assign=Lo,Nn.at=io,Nn.before=ce,Nn.bind=xo,Nn.bindAll=bo,Nn.bindKey=Ao,Nn.callback=Le,Nn.chain=te,Nn.chunk=function(n,t,r){t=(r?$r(n,t,r):null==t)?1:ju(wu(t)||1,1),r=0;for(var e=n?n.length:0,u=-1,o=De(du(e/t));r<e;)o[++u]=St(n,r,r+=t);return o},Nn.compact=function(n){for(var t=-1,r=n?n.length:0,e=-1,u=[];++t<r;){
67
+ var o=n[t];o&&(u[++e]=o)}return u},Nn.constant=function(n){return function(){return n}},Nn.countBy=fo,Nn.create=function(n,t,r){var e=Pu(n);return r&&$r(n,t,r)&&(t=w),t?et(e,t):e},Nn.curry=jo,Nn.curryRight=ko,Nn.debounce=le,Nn.defaults=No,Nn.defaultsDeep=To,Nn.defer=Oo,Nn.delay=Io,Nn.difference=Yu,Nn.drop=Kr,Nn.dropRight=Vr,Nn.dropRightWhile=function(n,t,r){return n&&n.length?Tt(n,br(t,r,3),true,true):[]},Nn.dropWhile=function(n,t,r){return n&&n.length?Tt(n,br(t,r,3),true):[]},Nn.fill=function(n,t,r,e){
68
+ var u=n?n.length:0;if(!u)return[];for(r&&typeof r!="number"&&$r(n,t,r)&&(r=0,e=u),u=n.length,r=null==r?0:+r||0,0>r&&(r=-r>u?0:u+r),e=e===w||e>u?u:+e||0,0>e&&(e+=u),u=r>e?0:e>>>0,r>>>=0;r<u;)n[r++]=t;return n},Nn.filter=ue,Nn.flatten=function(n,t,r){var e=n?n.length:0;return r&&$r(n,t,r)&&(t=false),e?_t(n,t):[]},Nn.flattenDeep=function(n){return n&&n.length?_t(n,true):[]},Nn.flow=Ro,Nn.flowRight=Eo,Nn.forEach=lo,Nn.forEachRight=so,Nn.forIn=Bo,Nn.forInRight=Do,Nn.forOwn=Mo,Nn.forOwnRight=qo,Nn.functions=Re,
69
+ Nn.groupBy=po,Nn.indexBy=ho,Nn.initial=function(n){return Vr(n,1)},Nn.intersection=Xu,Nn.invert=function(n,t,r){r&&$r(n,t,r)&&(t=w),r=-1;for(var e=Ko(n),u=e.length,o={};++r<u;){var i=e[r],f=n[i];t?eu.call(o,f)?o[f].push(i):o[f]=[i]:o[f]=i}return o},Nn.invoke=_o,Nn.keys=Ko,Nn.keysIn=Ee,Nn.map=ie,Nn.mapKeys=Vo,Nn.mapValues=Zo,Nn.matches=Te,Nn.matchesProperty=function(n,t){return jt(n,ft(t,true))},Nn.memoize=se,Nn.merge=Fo,Nn.method=ei,Nn.methodOf=ui,Nn.mixin=Pe,Nn.modArgs=Co,Nn.negate=function(n){if(typeof n!="function")throw new Xe(T);
70
+ return function(){return!n.apply(this,arguments)}},Nn.omit=Yo,Nn.once=function(n){return ce(2,n)},Nn.pairs=Ce,Nn.partial=So,Nn.partialRight=Uo,Nn.partition=vo,Nn.pick=Go,Nn.pluck=function(n,t){return ie(n,Be(t))},Nn.property=Be,Nn.propertyOf=function(n){return function(t){return mt(n,Mr(t),t+"")}},Nn.pull=function(){var n=arguments,t=n[0];if(!t||!t.length)return t;for(var r=0,e=jr(),u=n.length;++r<u;)for(var o=0,i=n[r];-1<(o=e(t,i,o));)vu.call(t,o,1);return t},Nn.pullAt=Hu,Nn.range=function(n,t,r){
71
+ r&&$r(n,t,r)&&(t=r=w),n=+n||0,r=null==r?1:+r||0,null==t?(t=n,n=0):t=+t||0;var e=-1;t=ju(du((t-n)/(r||1)),0);for(var u=De(t);++e<t;)u[e]=n,n+=r;return u},Nn.rearg=$o,Nn.reject=function(n,t,r){var e=Wo(n)?Zn:pt;return t=br(t,r,3),e(n,function(n,r,e){return!t(n,r,e)})},Nn.remove=function(n,t,r){var e=[];if(!n||!n.length)return e;var u=-1,o=[],i=n.length;for(t=br(t,r,3);++u<i;)r=n[u],t(r,u,n)&&(e.push(r),o.push(u));return Rt(n,o),e},Nn.rest=Jr,Nn.restParam=pe,Nn.set=function(n,t,r){if(null==n)return n;
72
+ var e=t+"";t=null!=n[e]||Wr(t,n)?[e]:Mr(t);for(var e=-1,u=t.length,o=u-1,i=n;null!=i&&++e<u;){var f=t[e];de(i)&&(e==o?i[f]=r:null==i[f]&&(i[f]=Ur(t[e+1])?[]:{})),i=i[f]}return n},Nn.shuffle=function(n){return fe(n,Cu)},Nn.slice=function(n,t,r){var e=n?n.length:0;return e?(r&&typeof r!="number"&&$r(n,t,r)&&(t=0,r=e),St(n,t,r)):[]},Nn.sortBy=function(n,t,r){if(null==n)return[];r&&$r(n,t,r)&&(t=w);var e=-1;return t=br(t,r,3),n=bt(n,function(n,r,u){return{a:t(n,r,u),b:++e,c:n}}),$t(n,f)},Nn.sortByAll=mo,
73
+ Nn.sortByOrder=function(n,t,r,e){return null==n?[]:(e&&$r(t,r,e)&&(r=w),Wo(t)||(t=null==t?[]:[t]),Wo(r)||(r=null==r?[]:[r]),Wt(n,t,r))},Nn.spread=function(n){if(typeof n!="function")throw new Xe(T);return function(t){return n.apply(this,t)}},Nn.take=function(n,t,r){return n&&n.length?((r?$r(n,t,r):null==t)&&(t=1),St(n,0,0>t?0:t)):[]},Nn.takeRight=function(n,t,r){var e=n?n.length:0;return e?((r?$r(n,t,r):null==t)&&(t=1),t=e-(+t||0),St(n,0>t?0:t)):[]},Nn.takeRightWhile=function(n,t,r){return n&&n.length?Tt(n,br(t,r,3),false,true):[];
74
+ },Nn.takeWhile=function(n,t,r){return n&&n.length?Tt(n,br(t,r,3)):[]},Nn.tap=function(n,t,r){return t.call(r,n),n},Nn.throttle=function(n,t,r){var e=true,u=true;if(typeof n!="function")throw new Xe(T);return false===r?e=false:de(r)&&(e="leading"in r?!!r.leading:e,u="trailing"in r?!!r.trailing:u),le(n,t,{leading:e,maxWait:+t,trailing:u})},Nn.thru=re,Nn.times=function(n,t,r){if(n=wu(n),1>n||!bu(n))return[];var e=-1,u=De(ku(n,4294967295));for(t=Dt(t,r,1);++e<n;)4294967295>e?u[e]=t(e):t(e);return u},Nn.toArray=Oe,
75
+ Nn.toPlainObject=Ie,Nn.transform=function(n,t,r,e){var u=Wo(n)||je(n);return t=br(t,e,4),null==r&&(u||de(n)?(e=n.constructor,r=u?Wo(n)?new e:[]:Pu(ye(e)?e.prototype:w)):r={}),(u?Kn:gt)(n,function(n,e,u){return t(r,n,e,u)}),r},Nn.union=to,Nn.uniq=Xr,Nn.unzip=Hr,Nn.unzipWith=Qr,Nn.values=Se,Nn.valuesIn=function(n){return Nt(n,Ee(n))},Nn.where=function(n,t){return ue(n,At(t))},Nn.without=ro,Nn.wrap=function(n,t){return t=null==t?Ne:t,dr(t,I,w,[n],[])},Nn.xor=function(){for(var n=-1,t=arguments.length;++n<t;){
76
+ var r=arguments[n];if(Sr(r))var e=e?Hn(ct(e,r),ct(r,e)):r}return e?Lt(e):[]},Nn.zip=eo,Nn.zipObject=ne,Nn.zipWith=uo,Nn.backflow=Eo,Nn.collect=ie,Nn.compose=Eo,Nn.each=lo,Nn.eachRight=so,Nn.extend=Lo,Nn.iteratee=Le,Nn.methods=Re,Nn.object=ne,Nn.select=ue,Nn.tail=Jr,Nn.unique=Xr,Pe(Nn,Nn),Nn.add=function(n,t){return(+n||0)+(+t||0)},Nn.attempt=ri,Nn.camelCase=Jo,Nn.capitalize=function(n){return(n=u(n))&&n.charAt(0).toUpperCase()+n.slice(1)},Nn.ceil=oi,Nn.clone=function(n,t,r,e){return t&&typeof t!="boolean"&&$r(n,t,r)?t=false:typeof t=="function"&&(e=r,
77
+ r=t,t=false),typeof r=="function"?ft(n,t,Dt(r,e,3)):ft(n,t)},Nn.cloneDeep=function(n,t,r){return typeof t=="function"?ft(n,true,Dt(t,r,3)):ft(n,true)},Nn.deburr=Ue,Nn.endsWith=function(n,t,r){n=u(n),t+="";var e=n.length;return r=r===w?e:ku(0>r?0:+r||0,e),r-=t.length,0<=r&&n.indexOf(t,r)==r},Nn.escape=function(n){return(n=u(n))&&hn.test(n)?n.replace(sn,c):n},Nn.escapeRegExp=function(n){return(n=u(n))&&xn.test(n)?n.replace(wn,l):n||"(?:)"},Nn.every=ee,Nn.find=ao,Nn.findIndex=Gu,Nn.findKey=Po,Nn.findLast=co,
78
+ Nn.findLastIndex=Ju,Nn.findLastKey=zo,Nn.findWhere=function(n,t){return ao(n,At(t))},Nn.first=Zr,Nn.floor=ii,Nn.get=function(n,t,r){return n=null==n?w:mt(n,Mr(t),t+""),n===w?r:n},Nn.gt=he,Nn.gte=function(n,t){return n>=t},Nn.has=function(n,t){if(null==n)return false;var r=eu.call(n,t);if(!r&&!Wr(t)){if(t=Mr(t),n=1==t.length?n:mt(n,St(t,0,-1)),null==n)return false;t=Gr(t),r=eu.call(n,t)}return r||Lr(n.length)&&Ur(t,n.length)&&(Wo(n)||_e(n)||Ae(n))},Nn.identity=Ne,Nn.includes=oe,Nn.indexOf=Yr,Nn.inRange=function(n,t,r){
79
+ return t=+t||0,r===w?(r=t,t=0):r=+r||0,n>=ku(t,r)&&n<ju(t,r)},Nn.isArguments=_e,Nn.isArray=Wo,Nn.isBoolean=function(n){return true===n||false===n||h(n)&&ou.call(n)==D},Nn.isDate=function(n){return h(n)&&ou.call(n)==M},Nn.isElement=function(n){return!!n&&1===n.nodeType&&h(n)&&!xe(n)},Nn.isEmpty=function(n){return null==n?true:Sr(n)&&(Wo(n)||Ae(n)||_e(n)||h(n)&&ye(n.splice))?!n.length:!Ko(n).length},Nn.isEqual=ve,Nn.isError=ge,Nn.isFinite=function(n){return typeof n=="number"&&bu(n)},Nn.isFunction=ye,Nn.isMatch=function(n,t,r,e){
80
+ return r=typeof r=="function"?Dt(r,e,3):w,xt(n,kr(t),r)},Nn.isNaN=function(n){return we(n)&&n!=+n},Nn.isNative=me,Nn.isNull=function(n){return null===n},Nn.isNumber=we,Nn.isObject=de,Nn.isPlainObject=xe,Nn.isRegExp=be,Nn.isString=Ae,Nn.isTypedArray=je,Nn.isUndefined=function(n){return n===w},Nn.kebabCase=Xo,Nn.last=Gr,Nn.lastIndexOf=function(n,t,r){var e=n?n.length:0;if(!e)return-1;var u=e;if(typeof r=="number")u=(0>r?ju(e+r,0):ku(r||0,e-1))+1;else if(r)return u=zt(n,t,true)-1,n=n[u],(t===t?t===n:n!==n)?u:-1;
81
+ if(t!==t)return p(n,u,true);for(;u--;)if(n[u]===t)return u;return-1},Nn.lt=ke,Nn.lte=function(n,t){return n<=t},Nn.max=fi,Nn.min=ai,Nn.noConflict=function(){return Yn._=iu,this},Nn.noop=ze,Nn.now=wo,Nn.pad=function(n,t,r){n=u(n),t=+t;var e=n.length;return e<t&&bu(t)?(e=(t-e)/2,t=wu(e),e=du(e),r=_r("",e,r),r.slice(0,t)+n+r):n},Nn.padLeft=Ho,Nn.padRight=Qo,Nn.parseInt=function(n,t,r){return(r?$r(n,t,r):null==t)?t=0:t&&(t=+t),n=We(n),Iu(n,t||(On.test(n)?16:10))},Nn.random=function(n,t,r){r&&$r(n,t,r)&&(t=r=w);
82
+ var e=null==n,u=null==t;return null==r&&(u&&typeof n=="boolean"?(r=n,n=1):typeof t=="boolean"&&(r=t,u=true)),e&&u&&(t=1,u=false),n=+n||0,u?(t=n,n=0):t=+t||0,r||n%1||t%1?(r=Ru(),ku(n+r*(t-n+lu("1e-"+((r+"").length-1))),t)):Et(n,t)},Nn.reduce=go,Nn.reduceRight=yo,Nn.repeat=$e,Nn.result=function(n,t,r){var e=null==n?w:Dr(n)[t];return e===w&&(null==n||Wr(t,n)||(t=Mr(t),n=1==t.length?n:mt(n,St(t,0,-1)),e=null==n?w:Dr(n)[Gr(t)]),e=e===w?r:e),ye(e)?e.call(n):e},Nn.round=ci,Nn.runInContext=m,Nn.size=function(n){
83
+ var t=n?Vu(n):0;return Lr(t)?t:Ko(n).length},Nn.snakeCase=ni,Nn.some=ae,Nn.sortedIndex=Qu,Nn.sortedLastIndex=no,Nn.startCase=ti,Nn.startsWith=function(n,t,r){return n=u(n),r=null==r?0:ku(0>r?0:+r||0,n.length),n.lastIndexOf(t,r)==r},Nn.sum=function(n,t,r){if(r&&$r(n,t,r)&&(t=w),t=br(t,r,3),1==t.length){n=Wo(n)?n:Br(n),r=n.length;for(var e=0;r--;)e+=+t(n[r])||0;n=e}else n=Ft(n,t);return n},Nn.template=function(n,t,r){var e=Nn.templateSettings;r&&$r(n,t,r)&&(t=r=w),n=u(n),t=rt(et({},r||t),e,tt),r=rt(et({},t.imports),e.imports,tt);
84
+ var o,i,f=Ko(r),a=Nt(r,f),c=0;r=t.interpolate||Cn;var l="__p+='";r=Ge((t.escape||Cn).source+"|"+r.source+"|"+(r===gn?jn:Cn).source+"|"+(t.evaluate||Cn).source+"|$","g");var p="sourceURL"in t?"//# sourceURL="+t.sourceURL+"\n":"";if(n.replace(r,function(t,r,e,u,f,a){return e||(e=u),l+=n.slice(c,a).replace(Sn,s),r&&(o=true,l+="'+__e("+r+")+'"),f&&(i=true,l+="';"+f+";\n__p+='"),e&&(l+="'+((__t=("+e+"))==null?'':__t)+'"),c=a+t.length,t}),l+="';",(t=t.variable)||(l="with(obj){"+l+"}"),l=(i?l.replace(fn,""):l).replace(an,"$1").replace(cn,"$1;"),
85
+ l="function("+(t||"obj")+"){"+(t?"":"obj||(obj={});")+"var __t,__p=''"+(o?",__e=_.escape":"")+(i?",__j=Array.prototype.join;function print(){__p+=__j.call(arguments,'')}":";")+l+"return __p}",t=ri(function(){return Ke(f,p+"return "+l).apply(w,a)}),t.source=l,ge(t))throw t;return t},Nn.trim=We,Nn.trimLeft=function(n,t,r){var e=n;return(n=u(n))?n.slice((r?$r(e,t,r):null==t)?g(n):o(n,t+"")):n},Nn.trimRight=function(n,t,r){var e=n;return(n=u(n))?(r?$r(e,t,r):null==t)?n.slice(0,y(n)+1):n.slice(0,i(n,t+"")+1):n;
86
+ },Nn.trunc=function(n,t,r){r&&$r(n,t,r)&&(t=w);var e=S;if(r=U,null!=t)if(de(t)){var o="separator"in t?t.separator:o,e="length"in t?+t.length||0:e;r="omission"in t?u(t.omission):r}else e=+t||0;if(n=u(n),e>=n.length)return n;if(e-=r.length,1>e)return r;if(t=n.slice(0,e),null==o)return t+r;if(be(o)){if(n.slice(e).search(o)){var i,f=n.slice(0,e);for(o.global||(o=Ge(o.source,(kn.exec(o)||"")+"g")),o.lastIndex=0;n=o.exec(f);)i=n.index;t=t.slice(0,null==i?e:i)}}else n.indexOf(o,e)!=e&&(o=t.lastIndexOf(o),
87
+ -1<o&&(t=t.slice(0,o)));return t+r},Nn.unescape=function(n){return(n=u(n))&&pn.test(n)?n.replace(ln,d):n},Nn.uniqueId=function(n){var t=++uu;return u(n)+t},Nn.words=Fe,Nn.all=ee,Nn.any=ae,Nn.contains=oe,Nn.eq=ve,Nn.detect=ao,Nn.foldl=go,Nn.foldr=yo,Nn.head=Zr,Nn.include=oe,Nn.inject=go,Pe(Nn,function(){var n={};return gt(Nn,function(t,r){Nn.prototype[r]||(n[r]=t)}),n}(),false),Nn.sample=fe,Nn.prototype.sample=function(n){return this.__chain__||null!=n?this.thru(function(t){return fe(t,n)}):fe(this.value());
88
+ },Nn.VERSION=x,Kn("bind bindKey curry curryRight partial partialRight".split(" "),function(n){Nn[n].placeholder=Nn}),Kn(["drop","take"],function(n,t){zn.prototype[n]=function(r){var e=this.__filtered__;if(e&&!t)return new zn(this);r=null==r?1:ju(wu(r)||0,0);var u=this.clone();return e?u.__takeCount__=ku(u.__takeCount__,r):u.__views__.push({size:r,type:n+(0>u.__dir__?"Right":"")}),u},zn.prototype[n+"Right"]=function(t){return this.reverse()[n](t).reverse()}}),Kn(["filter","map","takeWhile"],function(n,t){
89
+ var r=t+1,e=r!=N;zn.prototype[n]=function(n,t){var u=this.clone();return u.__iteratees__.push({iteratee:br(n,t,1),type:r}),u.__filtered__=u.__filtered__||e,u}}),Kn(["first","last"],function(n,t){var r="take"+(t?"Right":"");zn.prototype[n]=function(){return this[r](1).value()[0]}}),Kn(["initial","rest"],function(n,t){var r="drop"+(t?"":"Right");zn.prototype[n]=function(){return this.__filtered__?new zn(this):this[r](1)}}),Kn(["pluck","where"],function(n,t){var r=t?"filter":"map",e=t?At:Be;zn.prototype[n]=function(n){
90
+ return this[r](e(n))}}),zn.prototype.compact=function(){return this.filter(Ne)},zn.prototype.reject=function(n,t){return n=br(n,t,1),this.filter(function(t){return!n(t)})},zn.prototype.slice=function(n,t){n=null==n?0:+n||0;var r=this;return r.__filtered__&&(0<n||0>t)?new zn(r):(0>n?r=r.takeRight(-n):n&&(r=r.drop(n)),t!==w&&(t=+t||0,r=0>t?r.dropRight(-t):r.take(t-n)),r)},zn.prototype.takeRightWhile=function(n,t){return this.reverse().takeWhile(n,t).reverse()},zn.prototype.toArray=function(){return this.take(Cu);
91
+ },gt(zn.prototype,function(n,t){var r=/^(?:filter|map|reject)|While$/.test(t),e=/^(?:first|last)$/.test(t),u=Nn[e?"take"+("last"==t?"Right":""):t];u&&(Nn.prototype[t]=function(){var t=e?[1]:arguments,o=this.__chain__,i=this.__wrapped__,f=!!this.__actions__.length,a=i instanceof zn,c=t[0],l=a||Wo(i);l&&r&&typeof c=="function"&&1!=c.length&&(a=l=false);var s=function(n){return e&&o?u(n,1)[0]:u.apply(w,Hn([n],t))},c={func:re,args:[s],thisArg:w},f=a&&!f;return e&&!o?f?(i=i.clone(),i.__actions__.push(c),
92
+ n.call(i)):u.call(w,this.value())[0]:!e&&l?(i=f?i:new zn(this),i=n.apply(i,t),i.__actions__.push(c),new Pn(i,o)):this.thru(s)})}),Kn("join pop push replace shift sort splice split unshift".split(" "),function(n){var t=(/^(?:replace|split)$/.test(n)?tu:He)[n],r=/^(?:push|sort|unshift)$/.test(n)?"tap":"thru",e=!Tu.spliceObjects&&/^(?:pop|shift|splice)$/.test(n),u=/^(?:join|pop|replace|shift)$/.test(n),o=e?function(){var n=t.apply(this,arguments);return 0===this.length&&delete this[0],n}:t;Nn.prototype[n]=function(){
93
+ var n=arguments;return u&&!this.__chain__?o.apply(this.value(),n):this[r](function(t){return o.apply(t,n)})}}),gt(zn.prototype,function(n,t){var r=Nn[t];if(r){var e=r.name+"";(Fu[e]||(Fu[e]=[])).push({name:t,func:r})}}),Fu[hr(w,A).name]=[{name:"wrapper",func:w}],zn.prototype.clone=function(){var n=new zn(this.__wrapped__);return n.__actions__=qn(this.__actions__),n.__dir__=this.__dir__,n.__filtered__=this.__filtered__,n.__iteratees__=qn(this.__iteratees__),n.__takeCount__=this.__takeCount__,n.__views__=qn(this.__views__),
94
+ n},zn.prototype.reverse=function(){if(this.__filtered__){var n=new zn(this);n.__dir__=-1,n.__filtered__=true}else n=this.clone(),n.__dir__*=-1;return n},zn.prototype.value=function(){var n,t=this.__wrapped__.value(),r=this.__dir__,e=Wo(t),u=0>r,o=e?t.length:0;n=0;for(var i=o,f=this.__views__,a=-1,c=f.length;++a<c;){var l=f[a],s=l.size;switch(l.type){case"drop":n+=s;break;case"dropRight":i-=s;break;case"take":i=ku(i,n+s);break;case"takeRight":n=ju(n,i-s)}}if(n={start:n,end:i},i=n.start,f=n.end,n=f-i,
95
+ u=u?f:i-1,i=this.__iteratees__,f=i.length,a=0,c=ku(n,this.__takeCount__),!e||o<F||o==n&&c==n)return Pt(t,this.__actions__);e=[];n:for(;n--&&a<c;){for(u+=r,o=-1,l=t[u];++o<f;){var p=i[o],s=p.type,p=p.iteratee(l);if(s==N)l=p;else if(!p){if(s==L)continue n;break n}}e[a++]=l}return e},Nn.prototype.chain=function(){return te(this)},Nn.prototype.commit=function(){return new Pn(this.value(),this.__chain__)},Nn.prototype.concat=oo,Nn.prototype.plant=function(n){for(var t,r=this;r instanceof Tn;){var e=qr(r);
96
+ t?u.__wrapped__=e:t=e;var u=e,r=r.__wrapped__}return u.__wrapped__=n,t},Nn.prototype.reverse=function(){var n=this.__wrapped__,t=function(n){return n.reverse()};return n instanceof zn?(this.__actions__.length&&(n=new zn(this)),n=n.reverse(),n.__actions__.push({func:re,args:[t],thisArg:w}),new Pn(n,this.__chain__)):this.thru(t)},Nn.prototype.toString=function(){return this.value()+""},Nn.prototype.run=Nn.prototype.toJSON=Nn.prototype.valueOf=Nn.prototype.value=function(){return Pt(this.__wrapped__,this.__actions__);
97
+ },Nn.prototype.collect=Nn.prototype.map,Nn.prototype.head=Nn.prototype.first,Nn.prototype.select=Nn.prototype.filter,Nn.prototype.tail=Nn.prototype.rest,Nn}var w,x="3.10.1",b=1,A=2,j=4,k=8,O=16,I=32,R=64,E=128,C=256,S=30,U="...",$=150,W=16,F=200,L=1,N=2,T="Expected a function",P="__lodash_placeholder__",z="[object Arguments]",B="[object Array]",D="[object Boolean]",M="[object Date]",q="[object Error]",K="[object Function]",V="[object Number]",Z="[object Object]",Y="[object RegExp]",G="[object String]",J="[object ArrayBuffer]",X="[object Float32Array]",H="[object Float64Array]",Q="[object Int8Array]",nn="[object Int16Array]",tn="[object Int32Array]",rn="[object Uint8Array]",en="[object Uint8ClampedArray]",un="[object Uint16Array]",on="[object Uint32Array]",fn=/\b__p\+='';/g,an=/\b(__p\+=)''\+/g,cn=/(__e\(.*?\)|\b__t\))\+'';/g,ln=/&(?:amp|lt|gt|quot|#39|#96);/g,sn=/[&<>"'`]/g,pn=RegExp(ln.source),hn=RegExp(sn.source),_n=/<%-([\s\S]+?)%>/g,vn=/<%([\s\S]+?)%>/g,gn=/<%=([\s\S]+?)%>/g,yn=/\.|\[(?:[^[\]]*|(["'])(?:(?!\1)[^\n\\]|\\.)*?\1)\]/,dn=/^\w*$/,mn=/[^.[\]]+|\[(?:(-?\d+(?:\.\d+)?)|(["'])((?:(?!\2)[^\n\\]|\\.)*?)\2)\]/g,wn=/^[:!,]|[\\^$.*+?()[\]{}|\/]|(^[0-9a-fA-Fnrtuvx])|([\n\r\u2028\u2029])/g,xn=RegExp(wn.source),bn=/[\u0300-\u036f\ufe20-\ufe23]/g,An=/\\(\\)?/g,jn=/\$\{([^\\}]*(?:\\.[^\\}]*)*)\}/g,kn=/\w*$/,On=/^0[xX]/,In=/^\[object .+?Constructor\]$/,Rn=/^\d+$/,En=/[\xc0-\xd6\xd8-\xde\xdf-\xf6\xf8-\xff]/g,Cn=/($^)/,Sn=/['\n\r\u2028\u2029\\]/g,Un=RegExp("[A-Z\\xc0-\\xd6\\xd8-\\xde]+(?=[A-Z\\xc0-\\xd6\\xd8-\\xde][a-z\\xdf-\\xf6\\xf8-\\xff]+)|[A-Z\\xc0-\\xd6\\xd8-\\xde]?[a-z\\xdf-\\xf6\\xf8-\\xff]+|[A-Z\\xc0-\\xd6\\xd8-\\xde]+|[0-9]+","g"),$n="Array ArrayBuffer Date Error Float32Array Float64Array Function Int8Array Int16Array Int32Array Math Number Object RegExp Set String _ clearTimeout isFinite parseFloat parseInt setTimeout TypeError Uint8Array Uint8ClampedArray Uint16Array Uint32Array WeakMap".split(" "),Wn="constructor hasOwnProperty isPrototypeOf propertyIsEnumerable toLocaleString toString valueOf".split(" "),Fn={};
98
+ Fn[X]=Fn[H]=Fn[Q]=Fn[nn]=Fn[tn]=Fn[rn]=Fn[en]=Fn[un]=Fn[on]=true,Fn[z]=Fn[B]=Fn[J]=Fn[D]=Fn[M]=Fn[q]=Fn[K]=Fn["[object Map]"]=Fn[V]=Fn[Z]=Fn[Y]=Fn["[object Set]"]=Fn[G]=Fn["[object WeakMap]"]=false;var Ln={};Ln[z]=Ln[B]=Ln[J]=Ln[D]=Ln[M]=Ln[X]=Ln[H]=Ln[Q]=Ln[nn]=Ln[tn]=Ln[V]=Ln[Z]=Ln[Y]=Ln[G]=Ln[rn]=Ln[en]=Ln[un]=Ln[on]=true,Ln[q]=Ln[K]=Ln["[object Map]"]=Ln["[object Set]"]=Ln["[object WeakMap]"]=false;var Nn={"\xc0":"A","\xc1":"A","\xc2":"A","\xc3":"A","\xc4":"A","\xc5":"A","\xe0":"a","\xe1":"a","\xe2":"a",
99
+ "\xe3":"a","\xe4":"a","\xe5":"a","\xc7":"C","\xe7":"c","\xd0":"D","\xf0":"d","\xc8":"E","\xc9":"E","\xca":"E","\xcb":"E","\xe8":"e","\xe9":"e","\xea":"e","\xeb":"e","\xcc":"I","\xcd":"I","\xce":"I","\xcf":"I","\xec":"i","\xed":"i","\xee":"i","\xef":"i","\xd1":"N","\xf1":"n","\xd2":"O","\xd3":"O","\xd4":"O","\xd5":"O","\xd6":"O","\xd8":"O","\xf2":"o","\xf3":"o","\xf4":"o","\xf5":"o","\xf6":"o","\xf8":"o","\xd9":"U","\xda":"U","\xdb":"U","\xdc":"U","\xf9":"u","\xfa":"u","\xfb":"u","\xfc":"u","\xdd":"Y",
100
+ "\xfd":"y","\xff":"y","\xc6":"Ae","\xe6":"ae","\xde":"Th","\xfe":"th","\xdf":"ss"},Tn={"&":"&amp;","<":"&lt;",">":"&gt;",'"':"&quot;","'":"&#39;","`":"&#96;"},Pn={"&amp;":"&","&lt;":"<","&gt;":">","&quot;":'"',"&#39;":"'","&#96;":"`"},zn={"function":true,object:true},Bn={0:"x30",1:"x31",2:"x32",3:"x33",4:"x34",5:"x35",6:"x36",7:"x37",8:"x38",9:"x39",A:"x41",B:"x42",C:"x43",D:"x44",E:"x45",F:"x46",a:"x61",b:"x62",c:"x63",d:"x64",e:"x65",f:"x66",n:"x6e",r:"x72",t:"x74",u:"x75",v:"x76",x:"x78"},Dn={"\\":"\\",
101
+ "'":"'","\n":"n","\r":"r","\u2028":"u2028","\u2029":"u2029"},Mn=zn[typeof exports]&&exports&&!exports.nodeType&&exports,qn=zn[typeof module]&&module&&!module.nodeType&&module,Kn=zn[typeof self]&&self&&self.Object&&self,Vn=zn[typeof window]&&window&&window.Object&&window,Zn=qn&&qn.exports===Mn&&Mn,Yn=Mn&&qn&&typeof global=="object"&&global&&global.Object&&global||Vn!==(this&&this.window)&&Vn||Kn||this,Gn=function(){try{Object({toString:0}+"")}catch(n){return function(){return false}}return function(n){
102
+ return typeof n.toString!="function"&&typeof(n+"")=="string"}}(),Jn=m();typeof define=="function"&&typeof define.amd=="object"&&define.amd?(Yn._=Jn, define(function(){return Jn})):Mn&&qn?Zn?(qn.exports=Jn)._=Jn:Mn._=Jn:Yn._=Jn}).call(this);
js/menu-editor.js CHANGED
@@ -1,117 +1,250 @@
1
  //(c) W-Shadow
2
 
3
- /*global wsEditorData, defaultMenu, customMenu */
4
- /** @namespace wsEditorData */
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
5
 
6
  wsEditorData.wsMenuEditorPro = !!wsEditorData.wsMenuEditorPro; //Cast to boolean.
7
  var wsIdCounter = 0;
8
 
9
- var AmeCapabilityManager = (function(roles, users) {
10
- var me = {};
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
11
  users = users || {};
12
 
13
- function parseActorString(actor) {
14
- var separator = actor.indexOf(':');
15
- if (separator == -1) {
16
- throw {
17
- name: 'InvalidActorException',
18
- message: "Actor string does not contain a colon.",
19
- value: actor
20
- };
21
- }
22
 
23
- return {
24
- 'type' : actor.substring(0, separator),
25
- 'id' : actor.substring(separator + 1)
26
- }
27
- }
28
 
29
- me.hasCap = function(actor, capability, context) {
30
- context = context || {};
31
- var actorData = parseActorString(actor);
 
 
 
32
 
33
- //Super admins have access to everything, unless specifically denied.
34
- if ( actor == 'special:super_admin' ) {
35
- return (capability != 'do_not_allow');
36
- }
 
 
37
 
38
- if (actorData.type == 'role') {
39
- return me.roleHasCap(actorData.id, capability);
40
- } else if (actorData.type == 'user') {
41
- return me.userHasCap(actorData.id, capability, context);
42
- }
43
 
44
- throw {
45
- name: 'InvalidActorTypeException',
46
- message: "The specified actor type is not supported",
47
- value: actor,
48
- 'actorType': actorData.type
49
- };
50
- };
51
 
52
- me.roleHasCap = function(roleId, capability) {
53
- if (!roles.hasOwnProperty(roleId)) {
 
54
  throw {
55
- name: 'UnknownRoleException',
56
- message: 'Can not check capabilities for an unknown role',
57
- value: roleId,
58
- requireCapability: capability
59
  };
60
  }
61
 
62
- var role = roles[roleId];
63
- if ( role.capabilities.hasOwnProperty(capability) ) {
64
- return role.capabilities[capability];
65
- } else if (roleId == capability) {
66
- return true;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
67
  }
68
- return false;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
69
  };
70
 
71
- me.userHasCap = function(login, capability, context) {
72
- context = context || {};
 
 
 
 
 
73
  if (!users.hasOwnProperty(login)) {
74
  throw {
75
  name: 'UnknownUserException',
76
- message: 'Can not check capabilities for an unknown user',
77
- value: login,
78
- requireCapability: capability
79
  };
80
  }
81
 
 
82
  var user = users[login];
83
- if ( user.capabilities.hasOwnProperty(capability) ) {
84
- return user.capabilities[capability];
85
- } else {
86
- //Super Admins have all capabilities, except those explicitly denied.
87
- //We also need to check if the Super Admin actor is allowed in this context.
88
- if (user.is_super_admin ) {
89
- if (context.hasOwnProperty('special:super_admin')) {
90
- return context['special:super_admin'];
91
- }
92
- return (capability != 'do_not_allow');
93
- }
94
 
95
- //Check if any of the user's roles have the capability.
96
- for(var index = 0; index < user.roles.length; index++) {
97
- var roleId = user.roles[index];
 
 
 
 
 
 
 
98
 
99
- //Skip roles that are disabled in this context (i.e. via grant_access).
100
- if (context.hasOwnProperty('role:' + roleId) && !context['role:' + roleId]) {
101
- continue;
102
- }
103
 
104
- if (me.roleHasCap(roleId, capability)) {
105
- return true;
106
- }
107
- }
108
- }
109
 
110
- return false;
 
 
 
 
 
 
 
 
111
  };
112
 
113
  me.roleExists = function(roleId) {
114
- return roles.hasOwnProperty(roleId);
115
  };
116
 
117
  /**
@@ -151,14 +284,158 @@ var AmeCapabilityManager = (function(roles, users) {
151
  return specificity;
152
  };
153
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
154
 
155
  return me;
156
- })(wsEditorData.roles, wsEditorData.users);
157
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
158
 
159
  var AmeEditorApi = {};
 
160
 
161
- (function ($){
 
 
162
 
163
  var selectedActor = null;
164
 
@@ -168,7 +445,7 @@ var itemTemplates = {
168
  getTemplateById: function(templateId) {
169
  if (wsEditorData.itemTemplates.hasOwnProperty(templateId)) {
170
  return wsEditorData.itemTemplates[templateId];
171
- } else if ((templateId == '') || (templateId == 'custom')) {
172
  return wsEditorData.customItemTemplate;
173
  }
174
  return null;
@@ -184,12 +461,12 @@ var itemTemplates = {
184
  },
185
 
186
  getDefaultValue: function (templateId, fieldName) {
187
- if (fieldName == 'template_id') {
188
  return null;
189
  }
190
 
191
  var defaults = this.getDefaults(templateId);
192
- if (defaults && (typeof defaults[fieldName] != 'undefined')) {
193
  return defaults[fieldName];
194
  }
195
  return null;
@@ -208,7 +485,7 @@ var itemTemplates = {
208
  * @param value
209
  */
210
  function setInputValue(input, value) {
211
- if (input.attr('type') == 'checkbox'){
212
  input.prop('checked', value);
213
  } else {
214
  input.val(value);
@@ -223,7 +500,7 @@ function setInputValue(input, value) {
223
  * @return {*}
224
  */
225
  function getInputValue(input) {
226
- if (input.attr('type') == 'checkbox'){
227
  return input.is(':checked');
228
  }
229
  return input.val();
@@ -235,8 +512,8 @@ function getInputValue(input) {
235
  * Rationale: Simpler than atomically auto-incrementing or globally unique IDs.
236
  */
237
  function randomMenuId(prefix, size){
238
- prefix = (typeof prefix == 'undefined') ? 'custom_item_' : prefix;
239
- size = (typeof size == 'undefined') ? 5 : size;
240
 
241
  var suffix = "";
242
  var possible = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
@@ -270,6 +547,49 @@ function outputWpMenu(menu){
270
  menuBox.find('.ws_menu:first').click();
271
  }
272
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
273
  /*
274
  * Create edit widgets for a top-level menu and its submenus and append them all to the DOM.
275
  *
@@ -281,23 +601,24 @@ function outputWpMenu(menu){
281
  * Object with two fields - 'menu' and 'submenu' - containing the DOM nodes of the created widgets.
282
  */
283
  function outputTopMenu(menu, afterNode){
284
- //Create a container for menu items, even if there are none
285
- var submenu = buildSubmenu(menu.items);
286
-
287
  //Create the menu widget
288
  var menu_obj = buildMenuItem(menu, true);
289
- menu_obj.data('submenu_id', submenu.attr('id'));
290
- submenu.data('parent_menu_id', menu_obj.attr('id'));
291
 
292
- //Display
293
- submenu.appendTo('#ws_submenu_box');
294
- updateItemEditor(menu_obj);
295
- if ( (typeof afterNode != 'undefined') && (afterNode != null) ){
296
  $(afterNode).after(menu_obj);
297
  } else {
298
  menu_obj.appendTo('#ws_menu_box');
299
  }
300
 
 
 
 
 
 
 
 
 
 
301
  return {
302
  'menu' : menu_obj,
303
  'submenu' : submenu
@@ -307,11 +628,15 @@ function outputTopMenu(menu, afterNode){
307
  /*
308
  * Create and populate a submenu container.
309
  */
310
- function buildSubmenu(items){
311
  //Create a container for menu items, even if there are none
312
  var submenu = $('<div class="ws_submenu" style="display:none;"></div>');
313
  submenu.attr('id', 'ws-submenu-'+(wsIdCounter++));
314
 
 
 
 
 
315
  //Only show menus that have items.
316
  //Skip arrays (with a length) because filled menus are encoded as custom objects.
317
  var entry = null;
@@ -319,8 +644,8 @@ function buildSubmenu(items){
319
  $.each(items, function(index, item) {
320
  entry = buildMenuItem(item, false);
321
  if ( entry ){
322
- updateItemEditor(entry);
323
  submenu.append(entry);
 
324
  }
325
  });
326
  }
@@ -339,7 +664,7 @@ function buildSubmenu(items){
339
  * @return {*} The created widget as a jQuery object.
340
  */
341
  function buildMenuItem(itemData, isTopLevel) {
342
- isTopLevel = (typeof isTopLevel == 'undefined') ? false : isTopLevel;
343
 
344
  //Create the menu HTML
345
  var item = $('<div></div>')
@@ -357,7 +682,7 @@ function buildMenuItem(itemData, isTopLevel) {
357
  //the editors themselves are created later, when the user tries to access them
358
  //for the first time).
359
  var contents = [];
360
- var menuTitle = ((itemData.menu_title != null) ? itemData.menu_title : itemData.defaults.menu_title);
361
  if (menuTitle === '') {
362
  menuTitle = '&nbsp;';
363
  }
@@ -418,6 +743,9 @@ function jsTrim(str){
418
  return str.replace(/^\s+|\s+$/g, "");
419
  }
420
 
 
 
 
421
  function stripAllTags(input) {
422
  //Based on: http://phpjs.org/functions/strip_tags/
423
  var tags = /<\/?([a-z][a-z0-9]*)\b[^>]*>/gi,
@@ -507,7 +835,7 @@ var knownMenuFields = {
507
 
508
  menuItem.template_id = value;
509
  menuItem.defaults = itemTemplates.getDefaults(menuItem.template_id);
510
- menuItem.custom = (menuItem.template_id == '');
511
 
512
  // The file/URL of non-custom items is read-only and equal to the default
513
  // value. Rationale: simplifies menu generation, prevents some user mistakes.
@@ -534,6 +862,52 @@ var knownMenuFields = {
534
  }
535
  }),
536
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
537
  'file' : $.extend({}, baseField, {
538
  caption: 'URL',
539
  display: function(menuItem, displayValue, input) {
@@ -650,6 +1024,41 @@ var knownMenuFields = {
650
  visible: false
651
  }),
652
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
653
  'css_class' : $.extend({}, baseField, {
654
  caption: 'CSS classes',
655
  advanced : true,
@@ -666,11 +1075,12 @@ var knownMenuFields = {
666
  display: function(menuItem, displayValue, input, containerNode) {
667
  //Display the current icon in the selector.
668
  var cssClass = getFieldValue(menuItem, 'css_class', '');
669
- var iconUrl = getFieldValue(menuItem, 'icon_url', '');
 
670
 
671
  //When submenu icon visibility is set to "only if manually selected",
672
  //don't show the default submenu icons.
673
- var isDefault = (typeof menuItem['icon_url'] === 'undefined') || (menuItem['icon_url'] === null);
674
  if (isDefault && (wsEditorData.submenuIconsEnabled === 'if_custom') && containerNode.hasClass('ws_item')) {
675
  iconUrl = 'none';
676
  cssClass = '';
@@ -757,14 +1167,33 @@ var knownMenuFields = {
757
  caption: 'Hook name',
758
  advanced : true,
759
  onlyForTopMenus: true
 
 
 
 
 
 
 
 
760
  })
761
  };
762
 
 
 
 
 
 
 
 
 
 
 
 
763
  /*
764
  * Create editors for the visible fields of a menu entry and append them to the specified node.
765
  */
766
  function buildEditboxFields(fieldContainer, entry, isTopLevel){
767
- isTopLevel = (typeof isTopLevel == 'undefined') ? false : isTopLevel;
768
 
769
  var basicFields = $('<div class="ws_edit_panel ws_basic"></div>').appendTo(fieldContainer);
770
  var advancedFields = $('<div class="ws_edit_panel ws_advanced"></div>').appendTo(fieldContainer);
@@ -796,7 +1225,7 @@ function buildEditboxFields(fieldContainer, entry, isTopLevel){
796
  //Add a link that shows/hides advanced fields
797
  fieldContainer.append(
798
  '<div class="ws_toggle_container"><a href="#" class="ws_toggle_advanced_fields"'+
799
- (wsEditorData.hideAdvancedSettings ? '' : ' style="display:none;"')+'>'+
800
  (wsEditorData.hideAdvancedSettings ? wsEditorData.captionShowAdvanced : wsEditorData.captionHideAdvanced)
801
  +'</a></div>'
802
  );
@@ -827,8 +1256,8 @@ function buildEditboxField(entry, field_name, field_settings){
827
  break;
828
 
829
  case 'checkbox':
830
- inputBox = $('<label><input type="checkbox" class="ws_field_value"> '+
831
- field_settings.caption+'</label>'
832
  );
833
  break;
834
 
@@ -838,6 +1267,7 @@ function buildEditboxField(entry, field_name, field_settings){
838
  break;
839
 
840
  case 'icon_selector':
 
841
  inputBox = $(basicTextField)
842
  .add('<button class="button ws_select_icon" title="Select icon"><div class="icon16 icon-settings"></div><img src="" style="display:none;"></button>');
843
  break;
@@ -847,7 +1277,8 @@ function buildEditboxField(entry, field_name, field_settings){
847
  .add('<input type="button" class="button ws_open_color_editor" value="Edit...">');
848
  break;
849
 
850
- case 'text': //Intentional fall-through.
 
851
  default:
852
  inputBox = $(basicTextField);
853
  }
@@ -857,8 +1288,15 @@ function buildEditboxField(entry, field_name, field_settings){
857
  if (field_settings.addDropdown){
858
  className += ' ws_has_dropdown';
859
  }
 
 
 
860
 
861
- var editField = $('<div>' + (field_settings.standardCaption ? (field_settings.caption+'<br>') : '') + '</div>')
 
 
 
 
862
  .attr('class', className)
863
  .append(inputBox);
864
 
@@ -867,23 +1305,76 @@ function buildEditboxField(entry, field_name, field_settings){
867
  var dropdownId = field_settings.addDropdown;
868
  editField.append(
869
  $('<input type="button" value="&#9660;">')
870
- .addClass('button ws_dropdown_button')
871
  .attr('tabindex', '-1')
872
  .data('dropdownId', dropdownId)
873
  );
874
  }
875
 
876
  editField
877
- .append('<img src="' + wsEditorData.imagesUrl + '/transparent16.png" class="ws_reset_button" title="Reset to default value">&nbsp;</img>')
878
- .data('field_name', field_name);
879
-
880
- if ( !field_settings.visible ){
 
 
 
 
 
 
 
 
881
  editField.css('display', 'none');
882
  }
883
 
884
  return editField;
885
  }
886
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
887
  /**
888
  * Update the UI elements that that indicate whether the currently selected
889
  * actor can access a menu item.
@@ -892,20 +1383,38 @@ function buildEditboxField(entry, field_name, field_settings){
892
  */
893
  function updateActorAccessUi(containerNode) {
894
  //Update the permissions checkbox & UI
895
- if (selectedActor != null) {
896
- var menuItem = containerNode.data('menu_item');
897
  var hasAccess = actorCanAccessMenu(menuItem, selectedActor);
898
  var hasCustomPermissions = actorHasCustomPermissions(menuItem, selectedActor);
899
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
900
  var checkbox = containerNode.find('.ws_actor_access_checkbox');
901
  checkbox.prop('checked', hasAccess);
902
 
903
  //Display the checkbox differently if some items of this menu are hidden and some are visible,
904
  //or if their permissions don't match this menu's permissions.
905
- var submenuId = containerNode.data('submenu_id');
906
- var submenuItems = submenuId ? $('#' + submenuId).children('.ws_container') : [];
907
- if (!submenuId || submenuItems.length === 0) {
908
- //This menu doesn't contain any items.
909
  checkbox.prop('indeterminate', false);
910
  } else {
911
  var differentPermissions = false;
@@ -927,11 +1436,26 @@ function updateActorAccessUi(containerNode) {
927
 
928
  containerNode.toggleClass('ws_is_hidden_for_actor', !hasAccess);
929
  containerNode.toggleClass('ws_has_custom_permissions_for_actor', hasCustomPermissions);
930
- setMenuFlag(containerNode, 'custom_actor_permissions', hasCustomPermissions)
 
931
  } else {
932
  containerNode.removeClass('ws_is_hidden_for_actor ws_has_custom_permissions_for_actor');
933
  setMenuFlag(containerNode, 'custom_actor_permissions', false);
 
 
 
 
 
 
 
 
 
 
 
934
  }
 
 
 
935
  }
936
 
937
  /**
@@ -979,10 +1503,12 @@ function updateItemEditor(containerNode) {
979
 
980
  var hasADefaultValue = itemTemplates.hasDefaultValue(menuItem.template_id, fieldName);
981
  var defaultValue = itemTemplates.getDefaultValue(menuItem.template_id, fieldName);
982
- var isDefault = hasADefaultValue && (menuItem[fieldName] === null);
983
 
984
- if (fieldName == 'access_level') {
985
- isDefault = (getFieldValue(menuItem, 'extra_capability', '') === '') && isEmptyObject(menuItem.grant_access);
 
 
986
  }
987
 
988
  field.toggleClass('ws_has_no_default', !hasADefaultValue);
@@ -994,6 +1520,15 @@ function updateItemEditor(containerNode) {
994
  }
995
 
996
  setInputValue(input, displayValue);
 
 
 
 
 
 
 
 
 
997
  });
998
  }
999
 
@@ -1006,25 +1541,44 @@ function isEmptyObject(obj) {
1006
  return true;
1007
  }
1008
 
1009
- /*
1010
  * Get the current value of a single menu field.
1011
  *
1012
  * If the specified field is not set, this function will attempt to retrieve it
1013
  * from the "defaults" property of the menu object. If *that* fails, it will return
1014
  * the value of the optional third argument defaultValue.
 
 
 
 
 
 
1015
  */
1016
- function getFieldValue(entry, fieldName, defaultValue){
1017
  if ( (typeof entry[fieldName] === 'undefined') || (entry[fieldName] === null) ) {
1018
- if ( (typeof entry['defaults'] === 'undefined') || (typeof entry['defaults'][fieldName] === 'undefined') ){
1019
- return defaultValue;
1020
- } else {
 
 
 
 
 
 
 
 
 
1021
  return entry.defaults[fieldName];
 
 
1022
  }
1023
  } else {
1024
  return entry[fieldName];
1025
  }
1026
  }
1027
 
 
 
1028
  /*
1029
  * Make a menu container sortable
1030
  */
@@ -1034,10 +1588,45 @@ function makeBoxSortable(menuBox){
1034
  items: '> .ws_container',
1035
  cursor: 'move',
1036
  dropOnEmpty: true,
1037
- cancel : '.ws_editbox, .ws_edit_link'
 
 
 
 
 
 
 
 
 
 
 
 
 
1038
  });
1039
  }
1040
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1041
  /***************************************************************************
1042
  Parsing & encoding menu inputs
1043
  ***************************************************************************/
@@ -1048,7 +1637,7 @@ function makeBoxSortable(menuBox){
1048
  * @return {String} A JSON-encoded string representing the current menu tree loaded in the editor.
1049
  */
1050
  function encodeMenuAsJSON(tree){
1051
- if (typeof tree == 'undefined' || !tree) {
1052
  tree = readMenuTreeState();
1053
  }
1054
  tree.format = {
@@ -1075,6 +1664,9 @@ function readMenuTreeState(){
1075
  if (menu.template_id === wsEditorData.unclickableTemplateId) {
1076
  ws_paste_count++;
1077
  filename = '#' + wsEditorData.unclickableTemplateClass + '-' + ws_paste_count;
 
 
 
1078
  }
1079
 
1080
  //Prevent the user from saving top level items with duplicate URLs.
@@ -1085,19 +1677,24 @@ function readMenuTreeState(){
1085
  code: 'duplicate_top_level_url',
1086
  message: 'Error: Found a duplicate URL! All top level menus must have unique URLs.',
1087
  duplicates: [itemsByFilename[filename], containerNode]
1088
- }
1089
  }
1090
 
1091
  tree[filename] = menu;
1092
  itemsByFilename[filename] = containerNode;
1093
  });
1094
 
 
 
1095
  return {
1096
- tree: tree
 
 
1097
  };
1098
  }
1099
 
1100
  AmeEditorApi.readMenuTreeState = readMenuTreeState;
 
1101
 
1102
  /**
1103
  * Extract the current menu item settings from its editor widget.
@@ -1107,7 +1704,7 @@ AmeEditorApi.readMenuTreeState = readMenuTreeState;
1107
  * @return {Object} A menu object in the tree format.
1108
  */
1109
  function readItemState(itemDiv, position){
1110
- position = (typeof position == 'undefined') ? 0 : position;
1111
 
1112
  itemDiv = $(itemDiv);
1113
  var item = $.extend({}, wsEditorData.blankMenuItem, itemDiv.data('menu_item'), readAllFields(itemDiv));
@@ -1119,7 +1716,6 @@ function readItemState(itemDiv, position){
1119
  item.defaults.position = position; //The real default value will later overwrite this
1120
 
1121
  item.separator = itemDiv.hasClass('ws_menu_separator');
1122
- item.hidden = menuHasFlag(itemDiv, 'hidden');
1123
  item.custom = menuHasFlag(itemDiv, 'custom');
1124
 
1125
  //Gather the menu's sub-items, if any
@@ -1164,6 +1760,12 @@ function readAllFields(container){
1164
  return true;
1165
  }
1166
 
 
 
 
 
 
 
1167
  //Find the field (usually an input or select element).
1168
  var input_box = field.find('.ws_field_value');
1169
 
@@ -1178,7 +1780,7 @@ function readAllFields(container){
1178
 
1179
  //Permission settings are not stored in the visible access_level field (that's just for show),
1180
  //so do not attempt to read them from there.
1181
- state['access_level'] = null;
1182
 
1183
  return state;
1184
  }
@@ -1191,11 +1793,13 @@ function readAllFields(container){
1191
  var item_flags = {
1192
  'custom':'This is a custom menu item',
1193
  'unused':'This item was automatically recreated. You cannot delete a non-custom item, but you could hide it.',
1194
- 'hidden':'This item is hidden from ALL roles and users',
1195
- 'custom_actor_permissions' : "The selected role has custom permissions for this item."
 
1196
  };
1197
 
1198
- function setMenuFlag(item, flag, state) {
 
1199
  item = $(item);
1200
 
1201
  var item_class = 'ws_' + flag;
@@ -1203,11 +1807,14 @@ function setMenuFlag(item, flag, state) {
1203
 
1204
  item.toggleClass(item_class, state);
1205
  if (state) {
1206
- //Add the flag image,
1207
  var flag_container = item.find('.ws_flag_container');
1208
- if ( flag_container.find('.' + img_class).length == 0 ){
1209
- flag_container.append('<div class="ws_flag '+img_class+'" title="'+item_flags[flag]+'"></div>');
 
 
1210
  }
 
1211
  } else {
1212
  //Remove the flag image.
1213
  item.find('.' + img_class).remove();
@@ -1218,6 +1825,108 @@ function menuHasFlag(item, flag){
1218
  return $(item).hasClass('ws_'+flag);
1219
  }
1220
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1221
  /***********************************************************
1222
  Capability manipulation
1223
  ************************************************************/
@@ -1248,6 +1957,11 @@ function actorHasCustomPermissions(menuItem, actor) {
1248
  return false;
1249
  }
1250
 
 
 
 
 
 
1251
  function setActorAccess(containerNode, actor, allowAccess) {
1252
  var menuItem = containerNode.data('menu_item');
1253
 
@@ -1257,7 +1971,11 @@ function setActorAccess(containerNode, actor, allowAccess) {
1257
  menuItem.grant_access = {};
1258
  }
1259
 
1260
- menuItem.grant_access[actor] = allowAccess;
 
 
 
 
1261
  }
1262
 
1263
  function setSelectedActor(actor) {
@@ -1276,7 +1994,7 @@ function setSelectedActor(actor) {
1276
  var actorSelector = $('#ws_actor_selector');
1277
  $('.current', actorSelector).removeClass('current');
1278
 
1279
- if (selectedActor == null) {
1280
  $('a.ws_no_actor').addClass('current');
1281
  } else {
1282
  newSelectedItem.addClass('current');
@@ -1284,7 +2002,7 @@ function setSelectedActor(actor) {
1284
 
1285
  //There are some UI elements that can be visible or hidden depending on whether an actor is selected.
1286
  var editorNode = $('#ws_menu_editor');
1287
- editorNode.toggleClass('ws_is_actor_view', (selectedActor != null));
1288
 
1289
  //Update the menu item states to indicate whether they're accessible.
1290
  editorNode.find('.ws_container').each(function() {
@@ -1331,17 +2049,21 @@ function denyAccessForAllExcept(menuItem, actor) {
1331
  var menu_in_clipboard = null;
1332
  var ws_paste_count = 0;
1333
 
 
 
 
 
1334
  $(document).ready(function(){
1335
  //Some editor elements are only available in the Pro version.
1336
  if (wsEditorData.wsMenuEditorPro) {
1337
- knownMenuFields['open_in'].visible = true;
1338
- knownMenuFields['access_level'].visible = true;
1339
- knownMenuFields['page_heading'].visible = true;
1340
- knownMenuFields['colors'].visible = true;
1341
- knownMenuFields['extra_capability'].visible = false; //Superseded by the "access_level" field.
1342
 
1343
  //The Pro version supports submenu icons, but they can be disabled by the user.
1344
- knownMenuFields['icon_url'].onlyForTopMenus = (wsEditorData.submenuIconsEnabled == 'never');
1345
 
1346
  $('.ws_hide_if_pro').hide();
1347
  }
@@ -1356,39 +2078,99 @@ $(document).ready(function(){
1356
  /***************************************************************************
1357
  Event handlers for editor widgets
1358
  ***************************************************************************/
1359
- var menuEditorNode = $('#ws_menu_editor');
 
 
1360
 
1361
  //Highlight the clicked menu item and show it's submenu
1362
  var currentVisibleSubmenu = null;
1363
- menuEditorNode.on('click', '.ws_container', (function () {
1364
  var container = $(this);
1365
- if ( container.hasClass('ws_active') ){
1366
  return;
1367
  }
1368
 
1369
  //Highlight the active item and un-highlight the previous one
1370
  container.addClass('ws_active');
1371
  container.siblings('.ws_active').removeClass('ws_active');
1372
- if ( container.hasClass('ws_menu') ){
1373
  //Show/hide the appropriate submenu
1374
  if ( currentVisibleSubmenu ){
1375
  currentVisibleSubmenu.hide();
1376
  }
1377
- currentVisibleSubmenu = $('#'+container.data('submenu_id')).show();
 
 
 
 
 
 
1378
  }
 
 
 
 
 
1379
  }));
1380
 
1381
- //Show/hide a menu's properties
1382
- menuEditorNode.on('click', '.ws_edit_link', (function () {
1383
- var container = $(this).parents('.ws_container').first();
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1384
  var box = container.find('.ws_editbox');
1385
 
1386
  //For performance, the property editors for each menu are only created
1387
  //when the user tries to access access them for the first time.
1388
  if ( !container.data('field_editors_created') ){
1389
- buildEditboxFields(box, container.data('menu_item'), container.hasClass('ws_menu'));
 
1390
  container.data('field_editors_created', true);
1391
  updateItemEditor(container);
 
1392
  }
1393
 
1394
  $(this).toggleClass('ws_edit_link_expanded');
@@ -1413,10 +2195,12 @@ $(document).ready(function(){
1413
  var containerNode = field.closest('.ws_container');
1414
  var menuItem = containerNode.data('menu_item');
1415
 
1416
- if (fieldName == 'access_level') {
1417
  //This is a pretty nasty hack.
1418
  menuItem.grant_access = {};
1419
  menuItem.extra_capability = null;
 
 
1420
  }
1421
 
1422
  if (itemTemplates.hasDefaultValue(menuItem.template_id, fieldName)) {
@@ -1429,12 +2213,13 @@ $(document).ready(function(){
1429
 
1430
  //When a field is edited, change it's appearance if it's contents don't match the default value.
1431
  function fieldValueChange(){
 
1432
  var input = $(this);
1433
  var field = input.parents('.ws_edit_field').first();
1434
  var fieldName = field.data('field_name');
1435
 
1436
- if (fieldName == 'access_level') {
1437
- //This field is read-only and can never be directly edited by the user.
1438
  //Ignore spurious change events.
1439
  return;
1440
  }
@@ -1470,7 +2255,9 @@ $(document).ready(function(){
1470
  }
1471
 
1472
  updateItemEditor(containerNode);
1473
- updateParentAccessUi(containerNode)
 
 
1474
  }
1475
  menuEditorNode.on('click change', '.ws_field_value', fieldValueChange);
1476
 
@@ -1492,7 +2279,7 @@ $(document).ready(function(){
1492
 
1493
  //Allow/forbid items in actor-specific views
1494
  menuEditorNode.on('click', 'input.ws_actor_access_checkbox', function() {
1495
- if (selectedActor == null) {
1496
  return;
1497
  }
1498
 
@@ -1501,7 +2288,7 @@ $(document).ready(function(){
1501
 
1502
  var menu = containerNode.data('menu_item');
1503
  //Ask for confirmation if the user tries to hide Dashboard -> Home.
1504
- if ( !checked && ((menu.template_id == 'index.php>index.php') || (menu.template_id == '>index.php')) ) {
1505
  updateItemEditor(containerNode); //Resets the checkbox back to the old value.
1506
  confirmDashboardHiding(function(ok) {
1507
  if (ok) {
@@ -1520,8 +2307,8 @@ $(document).ready(function(){
1520
  * (And it violates SRP in a particularly egregious manner.)
1521
  *
1522
  * @param containerNode
1523
- * @param {String} actor
1524
- * @param {Boolean} allowAccess
1525
  */
1526
  function setActorAccessForTreeAndUpdateUi(containerNode, actor, allowAccess) {
1527
  setActorAccess(containerNode, actor, allowAccess);
@@ -1596,110 +2383,74 @@ $(document).ready(function(){
1596
  Access editor dialog
1597
  *************************************************************************/
1598
 
1599
- var accessEditorState = {
1600
- containerNode : null,
1601
- menuItem: null,
1602
- rowPrefix: 'access_settings_for-'
1603
- };
1604
-
1605
- $('#ws_menu_access_editor').dialog({
1606
- autoOpen: false,
1607
- closeText: ' ',
1608
- modal: true,
1609
- minHeight: 100,
1610
- draggable: false
1611
- });
1612
-
1613
- menuEditorNode.on('click', '.ws_launch_access_editor', function() {
1614
- var containerNode = $(this).parents('.ws_container').first();
1615
- var menuItem = containerNode.data('menu_item');
1616
-
1617
- //Write the values of this item to the editor fields.
1618
- var editor = $('#ws_menu_access_editor');
1619
-
1620
- var requiredCap = getFieldValue(menuItem, 'access_level', '< Error: access_level is missing! >');
1621
- var requiredCapField = editor.find('#ws_required_capability').empty();
1622
- if (menuItem.template_id === '') {
1623
- //Custom items have no required caps, only what users set.
1624
- requiredCapField.empty().append('<em>None</em>');
1625
- } else {
1626
- requiredCapField.text(requiredCap);
1627
- }
1628
-
1629
- editor.find('#ws_extra_capability').val(getFieldValue(menuItem, 'extra_capability', ''));
1630
-
1631
- //Generate the actor list.
1632
- var table = editor.find('.ws_role_table_body tbody').empty();
1633
- var alternate = '';
1634
- for(var actor in wsEditorData.actors) {
1635
- if (!wsEditorData.actors.hasOwnProperty(actor)) {
1636
- continue;
1637
- }
1638
- var actorName = wsEditorData.actors[actor];
1639
-
1640
- var checkboxId = 'allow_' + actor.replace(/[^a-zA-Z0-9_]/g, '_');
1641
- var checkbox = $('<input type="checkbox">').addClass('ws_role_access').attr('id', checkboxId);
1642
-
1643
- var actorHasAccess = actorCanAccessMenu(menuItem, actor);
1644
- if (actorHasAccess) {
1645
- checkbox.prop('checked', true);
1646
- }
1647
-
1648
- alternate = (alternate == '') ? 'alternate' : '';
1649
 
1650
- var cell = '<td>';
1651
- var row = $('<tr>').data('actor', actor).attr('class', alternate).append(
1652
- $(cell).addClass('ws_column_role post-title').append(
1653
- $('<label>').attr('for', checkboxId).append(
1654
- $('<strong>').text(actorName)
1655
- )
1656
- ),
1657
- $(cell).addClass('ws_column_access').append(checkbox)
1658
- );
1659
 
1660
- table.append(row);
1661
- }
 
 
 
 
 
 
1662
 
1663
- accessEditorState.containerNode = containerNode;
1664
- accessEditorState.menuItem = menuItem;
 
 
 
 
 
 
 
 
 
 
 
1665
 
1666
- //Show/hide the hint about sub menus overriding menu permissions.
1667
- var itemHasSubmenus = !!(containerNode.data('submenu_id')) &&
1668
- $('#' + containerNode.data('submenu_id')).find('.ws_item').length > 0;
1669
- var hintIsEnabled = !wsEditorData.showHints.hasOwnProperty('ws_hint_menu_permissions') || wsEditorData.showHints['ws_hint_menu_permissions'];
1670
- $('#ws_hint_menu_permissions').toggle(hintIsEnabled && itemHasSubmenus);
1671
 
1672
- //Warn the user if the required capability == role. Can't make it less restrictive.
1673
- var roleError = $('#ws_hardcoded_role_error');
1674
- if (requiredCap && AmeCapabilityManager.roleExists(requiredCap)) {
1675
- roleError.show();
1676
- $('#ws_hardcoded_role_name').text(requiredCap);
1677
- } else {
1678
- roleError.hide();
1679
  }
1680
-
1681
- editor.dialog('open');
1682
  });
1683
 
1684
- $('#ws_save_access_settings').click(function() {
1685
- //Save the new settings.
1686
- var extraCapability = jsTrim($('#ws_extra_capability').val());
1687
- accessEditorState.menuItem.extra_capability = (extraCapability === '') ? null : extraCapability;
1688
 
1689
- var grantAccess = accessEditorState.menuItem.grant_access;
1690
- if (!$.isPlainObject(grantAccess)) {
1691
- grantAccess = {};
1692
- }
1693
- var editor = $('#ws_menu_access_editor');
1694
- editor.find('.ws_role_table_body tbody tr').each(function() {
1695
- var row = $(this);
1696
- var actor = row.data('actor');
1697
- grantAccess[actor] = row.find('input.ws_role_access').is(':checked');
1698
  });
1699
- accessEditorState.menuItem.grant_access = grantAccess;
1700
-
1701
- updateItemEditor(accessEditorState.containerNode);
1702
- editor.dialog('close');
1703
  });
1704
 
1705
  /***************************************************************************
@@ -1721,14 +2472,15 @@ $(document).ready(function(){
1721
 
1722
  //Show/hide the capability drop-down list when the trigger button is clicked
1723
  $('#ws_trigger_capability_dropdown').on('mousedown click', onDropdownTriggerClicked);
1724
- menuEditorNode.on('mousedown click', '.ws_dropdown_button', onDropdownTriggerClicked);
1725
 
1726
  function onDropdownTriggerClicked(event){
 
1727
  var inputBox = null;
1728
  var button = $(this);
1729
 
1730
  //Find the input associated with the button that was clicked.
1731
- if ( button.attr('id') == 'ws_trigger_capability_dropdown' ) {
1732
  inputBox = $('#ws_extra_capability');
1733
  } else {
1734
  inputBox = button.closest('.ws_edit_field').find('.ws_field_value').first();
@@ -1736,7 +2488,7 @@ $(document).ready(function(){
1736
 
1737
  //If the user clicks the same button again while the dropdown is already visible,
1738
  //ignore the click. The dropdown will be hidden by its "blur" handler.
1739
- if (event.type == 'mousedown') {
1740
  if ( capSelectorDropdown.is(':visible') && inputBox.is(currentDropdownOwner) ) {
1741
  isDropdownBeingHidden = true;
1742
  }
@@ -1750,7 +2502,7 @@ $(document).ready(function(){
1750
  //the dropdown to be properly focused when displaying it in a dialog, we must make it
1751
  //a child of the dialog's DOM node (and vice versa when it's not in a dialog).
1752
  var parentContainer = $(this).closest('.ui-dialog, #ws_menu_editor');
1753
- if ((parentContainer.length > 0) && (capSelectorDropdown.closest(parentContainer).length == 0)) {
1754
  var oldHeight = capSelectorDropdown.height(); //Height seems to reset when moving to a new parent.
1755
  capSelectorDropdown.detach().appendTo(parentContainer).height(oldHeight);
1756
  }
@@ -1777,7 +2529,7 @@ $(document).ready(function(){
1777
 
1778
  //Also show it when the user presses the down arrow in the input field (doesn't work in Opera).
1779
  $('#ws_extra_capability').bind('keyup', function(event){
1780
- if ( event.which == 40 ){
1781
  $('#ws_trigger_capability_dropdown').click();
1782
  }
1783
  });
@@ -1786,22 +2538,21 @@ $(document).ready(function(){
1786
  var dropdownNodes = $('.ws_dropdown');
1787
 
1788
  // Hide capability drop-down when it loses focus.
1789
- dropdownNodes.blur(function(event){
1790
- console.log('Hiding dropdown because it lost focus.', event);
1791
  capSelectorDropdown.hide();
1792
  });
1793
 
1794
  dropdownNodes.keydown(function(event){
1795
 
1796
  //Hide it when the user presses Esc
1797
- if ( event.which == 27 ){
1798
  capSelectorDropdown.hide();
1799
  if (currentDropdownOwner) {
1800
  currentDropdownOwner.focus();
1801
  }
1802
 
1803
  //Select an item & hide the list when the user presses Enter or Tab
1804
- } else if ( (event.which == 13) || (event.which == 9) ){
1805
  capSelectorDropdown.hide();
1806
 
1807
  if (currentDropdownOwner) {
@@ -1817,7 +2568,7 @@ $(document).ready(function(){
1817
 
1818
  //Eat Tab keys to prevent focus theft. Required to make the "select item on Tab" thing work.
1819
  dropdownNodes.keyup(function(event){
1820
- if ( event.which == 9 ){
1821
  event.preventDefault();
1822
  }
1823
  });
@@ -1840,7 +2591,7 @@ $(document).ready(function(){
1840
  }
1841
 
1842
  var option = event.target;
1843
- if ( (typeof option['selected'] !== 'undefined') && !option.selected && option.value ){
1844
  option.selected = true;
1845
  }
1846
  });
@@ -1894,9 +2645,10 @@ $(document).ready(function(){
1894
 
1895
  currentIconButton = button;
1896
 
1897
- var menuItem = currentIconButton.closest('.ws_container').data('menu_item');
 
1898
  var cssClass = getFieldValue(menuItem, 'css_class', '');
1899
- var iconUrl = getFieldValue(menuItem, 'icon_url', '');
1900
 
1901
  var customImageOption = iconSelector.find('.ws_custom_image_icon').hide();
1902
 
@@ -1978,7 +2730,7 @@ $(document).ready(function(){
1978
  frame.on( 'select', function() {
1979
  //Grab the selected attachment.
1980
  var attachment = frame.state().get('selection').first();
1981
- //TODO: Warn the user if the image exceeds 16x16 pixels.
1982
 
1983
  //Set the menu icon to the attachment URL.
1984
  if (currentIconButton) {
@@ -2027,7 +2779,7 @@ $(document).ready(function(){
2027
  if (
2028
  !iconSelector.is(event.target)
2029
  && iconSelector.has(event.target).length === 0
2030
- && $(event.target).closest('.ws_select_icon').length == 0
2031
  ) {
2032
  iconSelector.hide();
2033
  currentIconButton = null;
@@ -2035,6 +2787,198 @@ $(document).ready(function(){
2035
  });
2036
 
2037
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2038
  /*************************************************************************
2039
  Color picker
2040
  *************************************************************************/
@@ -2080,6 +3024,10 @@ $(document).ready(function(){
2080
  'menu-bubble-current-background'
2081
  ];
2082
 
 
 
 
 
2083
  //Show only the primary color settings by default.
2084
  var showAdvancedColors = false;
2085
  $('#ws-ame-show-advanced-colors').click(function() {
@@ -2093,7 +3041,11 @@ $(document).ready(function(){
2093
  menuEditorNode.on('click', '.ws_open_color_editor, .ws_color_scheme_display', function() {
2094
  //Initializing the color pickers takes a while, so we only do it when needed instead of on document ready.
2095
  if ( !colorPickersInitialized ) {
2096
- menuColorDialog.find('.ame-color-picker').wpColorPicker();
 
 
 
 
2097
  colorPickersInitialized = true;
2098
  }
2099
 
@@ -2104,34 +3056,69 @@ $(document).ready(function(){
2104
  colorDialogState.menuItem = menuItem;
2105
 
2106
  var colors = getFieldValue(menuItem, 'colors', {}) || {};
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2107
  var customColorCount = 0;
 
2108
  for (var i = 0; i < menuColorVariables.length; i++) {
2109
  var name = menuColorVariables[i];
2110
  var value = colors.hasOwnProperty(name) ? colors[name] : false;
2111
 
2112
  if ( value ) {
2113
  $('#ame-color-' + name).wpColorPicker('color', value);
2114
- customColorCount++;
2115
- } else {
2116
- $('#ame-color-' + name).closest('.wp-picker-container').find('.wp-picker-clear').click();
2117
- }
2118
- }
2119
-
2120
- if ( customColorCount > 0 ) {
2121
- menuItem.colors = colors;
2122
- } else {
2123
- menuItem.colors = null;
2124
  }
2125
 
2126
- //Add menu title to the dialog caption.
2127
- var title = getFieldValue(menuItem, 'menu_title', null);
2128
- menuColorDialog.dialog(
2129
- 'option',
2130
- 'title',
2131
- title ? ('Colors: ' + title.substring(0, 30)) : 'Colors'
2132
- );
2133
- menuColorDialog.dialog('open');
2134
- });
2135
 
2136
  //The "Save Changes" button in the color dialog.
2137
  $('#ws-ame-save-menu-colors').click(function() {
@@ -2140,24 +3127,122 @@ $(document).ready(function(){
2140
  return;
2141
  }
2142
  var menuItem = colorDialogState.menuItem;
2143
- var colors = {}, colorCount = 0;
 
2144
 
2145
- for (var i = 0; i < menuColorVariables.length; i++) {
2146
- var name = menuColorVariables[i];
2147
- var value = $('#ame-color-' + name).val();
2148
- if (value) {
2149
- colors[name] = value;
2150
- colorCount++;
2151
- }
 
2152
  }
2153
 
2154
- menuItem.colors = colorCount > 0 ? colors : null;
2155
- updateItemEditor(colorDialogState.containerNode);
 
 
 
 
 
 
 
2156
 
 
2157
  colorDialogState.containerNode = null;
2158
  colorDialogState.menuItem = null;
2159
  });
2160
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2161
  /*************************************************************************
2162
  Menu toolbar buttons
2163
  *************************************************************************/
@@ -2166,22 +3251,82 @@ $(document).ready(function(){
2166
  }
2167
 
2168
  //Show/Hide menu
2169
- $('#ws_hide_menu').click(function () {
 
 
2170
  //Get the selected menu
2171
  var selection = getSelectedMenu();
2172
- if (!selection.length) return;
 
 
2173
 
2174
- //Mark the menu as hidden/visible
2175
- var menuItem = selection.data('menu_item');
2176
- menuItem.hidden = !menuItem.hidden;
2177
- setMenuFlag(selection, 'hidden', menuItem.hidden);
2178
-
2179
- //Also mark all of it's submenus as hidden/visible
2180
- $('#' + selection.data('submenu_id') + ' .ws_item').each(function(){
2181
- var submenuItem = $(this).data('menu_item');
2182
- submenuItem.hidden = menuItem.hidden;
2183
- setMenuFlag(this, 'hidden', submenuItem.hidden);
2184
- });
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2185
  });
2186
 
2187
  //Delete error dialog. It shows up when the user tries to delete one of the default menus.
@@ -2235,7 +3380,7 @@ $(document).ready(function(){
2235
  applyCallbackRecursively(selection, function(menuItem) {
2236
  menuItem.extra_capability = adminOnlyCap;
2237
  });
2238
- alert('The "required capability" field was set to "' + adminOnlyCap + '".')
2239
  }
2240
  };
2241
 
@@ -2254,26 +3399,29 @@ $(document).ready(function(){
2254
  });
2255
 
2256
  /**
2257
- * Attempt to delete a menu item. Will check if the item can actually be deleted and ask the user for confirmation.
2258
- * UI callback.
2259
  *
2260
- * @param selection The selected menu item (DOM node).
 
2261
  */
2262
- function tryDeleteItem(selection) {
2263
- var menuItem = selection.data('menu_item');
 
 
 
 
2264
  var isDefaultItem =
2265
  ( menuItem.template_id !== '')
2266
- && ( menuItem.template_id !== wsEditorData.unclickableTemplateId)
2267
- && (!menuItem.separator);
 
2268
 
2269
  var otherCopiesExist = false;
2270
- var shouldDelete = false;
2271
-
2272
  if (isDefaultItem) {
2273
  //Check if there are any other menus with the same template ID.
2274
  $('#ws_menu_editor').find('.ws_container').each(function() {
2275
  var otherItem = $(this).data('menu_item');
2276
- if ((menuItem != otherItem) && (menuItem.template_id == otherItem.template_id)) {
2277
  otherCopiesExist = true;
2278
  return false;
2279
  }
@@ -2281,13 +3429,26 @@ $(document).ready(function(){
2281
  });
2282
  }
2283
 
2284
- if (!isDefaultItem || otherCopiesExist) {
 
 
 
 
 
 
 
 
 
 
 
 
 
2285
  //Custom and duplicate items can be deleted normally.
2286
  shouldDelete = confirm('Delete this menu?');
2287
  } else {
2288
  //Non-custom items can not be deleted, but they can be hidden. Ask the user if they want to do that.
2289
  menuDeletionDialog.find('#ws-ame-menu-type-desc').text(
2290
- menuItem.defaults.is_plugin_page ? 'an item added by another plugin' : 'a built-in menu item'
2291
  );
2292
  menuDeletionDialog.data('selected_menu', selection);
2293
 
@@ -2321,29 +3482,41 @@ $(document).ready(function(){
2321
  }
2322
 
2323
  //Delete menu
2324
- $('#ws_delete_menu').click(function () {
 
 
2325
  //Get the selected menu
2326
  var selection = getSelectedMenu();
2327
- if (!selection.length) return;
 
 
2328
 
2329
  tryDeleteItem(selection);
2330
  });
2331
 
2332
  //Copy menu
2333
- $('#ws_copy_menu').click(function () {
 
 
2334
  //Get the selected menu
2335
  var selection = $('#ws_menu_box').find('.ws_active');
2336
- if (!selection.length) return;
 
 
2337
 
2338
  //Store a copy of the current menu state in clipboard
2339
  menu_in_clipboard = readItemState(selection);
2340
  });
2341
 
2342
  //Cut menu
2343
- $('#ws_cut_menu').click(function () {
 
 
2344
  //Get the selected menu
2345
  var selection = $('#ws_menu_box').find('.ws_active');
2346
- if (!selection.length) return;
 
 
2347
 
2348
  //Store a copy of the current menu state in clipboard
2349
  menu_in_clipboard = readItemState(selection);
@@ -2380,9 +3553,13 @@ $(document).ready(function(){
2380
  }
2381
  }
2382
 
2383
- $('#ws_paste_menu').click(function () {
 
 
2384
  //Check if anything has been copied/cut
2385
- if (!menu_in_clipboard) return;
 
 
2386
 
2387
  var menu = $.extend(true, {}, menu_in_clipboard);
2388
 
@@ -2393,7 +3570,9 @@ $(document).ready(function(){
2393
  });
2394
 
2395
  //New menu
2396
- $('#ws_new_menu').click(function () {
 
 
2397
  ws_paste_count++;
2398
 
2399
  //The new menu starts out rather bare
@@ -2408,7 +3587,7 @@ $(document).ready(function(){
2408
  });
2409
 
2410
  //Make it accessible only to the current actor if one is selected.
2411
- if (selectedActor != null) {
2412
  denyAccessForAllExcept(menu, selectedActor);
2413
  }
2414
 
@@ -2421,7 +3600,9 @@ $(document).ready(function(){
2421
  });
2422
 
2423
  //New separator
2424
- $('#ws_new_separator, #ws_new_submenu_separator').click(function () {
 
 
2425
  ws_paste_count++;
2426
 
2427
  //The new menu starts out rather bare
@@ -2439,7 +3620,7 @@ $(document).ready(function(){
2439
  }
2440
  });
2441
 
2442
- if ( $(this).attr('id').indexOf('submenu') == -1 ) {
2443
  //Insert in the top-level menu.
2444
  var selection = $('#ws_menu_box').find('.ws_active');
2445
  outputTopMenu(menu, (selection.length > 0) ? selection : null);
@@ -2450,8 +3631,10 @@ $(document).ready(function(){
2450
  });
2451
 
2452
  //Toggle all menus for the currently selected actor
2453
- $('#ws_toggle_all_menus').click(function() {
2454
- if ( selectedActor == null ) {
 
 
2455
  alert("This button enables/disables all menus for the selected role. To use it, click a role and then click this button again.");
2456
  return;
2457
  }
@@ -2474,20 +3657,36 @@ $(document).ready(function(){
2474
  draggable: false
2475
  });
2476
 
2477
- //Populate source/destination lists.
2478
  var sourceActorList = $('#ame-copy-source-actor'), destinationActorList = $('#ame-copy-destination-actor');
2479
- $.each(wsEditorData.actors, function(actor, name) {
2480
- var option = $('<option>', {val: actor, text: name});
2481
- sourceActorList.append(option);
2482
- destinationActorList.append(option.clone());
2483
- });
2484
 
2485
  //The "Copy permissions" toolbar button.
2486
- $('#ws_copy_role_permissions').click(function() {
 
 
 
 
 
 
 
 
 
 
 
 
 
2487
  //Pre-select the current actor as the destination.
2488
  if (selectedActor !== null) {
2489
  destinationActorList.val(selectedActor);
2490
  }
 
 
 
 
 
 
 
 
 
2491
  copyPermissionsDialog.dialog('open');
2492
  });
2493
 
@@ -2503,7 +3702,7 @@ $(document).ready(function(){
2503
  }
2504
 
2505
  //Iterate over all menu items and copy the permissions from one actor to the other.
2506
- var allMenuNodes = $('.ws_menu', '#ws_menu_box').add('.ws_item', '#ws_submenu_box');
2507
  allMenuNodes.each(function() {
2508
  var node = $(this);
2509
  var menuItem = node.data('menu_item');
@@ -2541,6 +3740,70 @@ $(document).ready(function(){
2541
  copyConfirmationButton.prop('disabled', !validInputs);
2542
  });
2543
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2544
 
2545
  /*************************************************************************
2546
  Item toolbar buttons
@@ -2550,40 +3813,54 @@ $(document).ready(function(){
2550
  }
2551
 
2552
  //Show/Hide item
2553
- $('#ws_hide_item').click(function () {
 
 
2554
  //Get the selected item
2555
  var selection = getSelectedSubmenuItem();
2556
- if (!selection.length) return;
 
 
2557
 
2558
  //Mark the item as hidden/visible
2559
- var menuItem = selection.data('menu_item');
2560
- menuItem.hidden = !menuItem.hidden;
2561
- setMenuFlag(selection, 'hidden', menuItem.hidden);
2562
  });
2563
 
2564
  //Delete item
2565
- $('#ws_delete_item').click(function () {
 
 
2566
  var selection = getSelectedSubmenuItem();
2567
- if (!selection.length) return;
 
 
2568
 
2569
  tryDeleteItem(selection);
2570
  });
2571
 
2572
  //Copy item
2573
- $('#ws_copy_item').click(function () {
 
 
2574
  //Get the selected item
2575
  var selection = getSelectedSubmenuItem();
2576
- if (!selection.length) return;
 
 
2577
 
2578
  //Store a copy of item state in the clipboard
2579
  menu_in_clipboard = readItemState(selection);
2580
  });
2581
 
2582
  //Cut item
2583
- $('#ws_cut_item').click(function () {
 
 
2584
  //Get the selected item
2585
  var selection = getSelectedSubmenuItem();
2586
- if (!selection.length) return;
 
 
2587
 
2588
  //Store a copy of item state in the clipboard
2589
  menu_in_clipboard = readItemState(selection);
@@ -2627,9 +3904,13 @@ $(document).ready(function(){
2627
  updateParentAccessUi(visibleSubmenu);
2628
  }
2629
 
2630
- $('#ws_paste_item').click(function () {
 
 
2631
  //Check if anything has been copied/cut
2632
- if (!menu_in_clipboard) return;
 
 
2633
 
2634
  //You can only add separators to submenus in the Pro version.
2635
  if ( menu_in_clipboard.separator && !wsEditorData.wsMenuEditorPro ) {
@@ -2642,7 +3923,9 @@ $(document).ready(function(){
2642
  });
2643
 
2644
  //New item
2645
- $('#ws_new_item').click(function () {
 
 
2646
  if ($('.ws_submenu:visible').length < 1) {
2647
  return; //Abort if no submenu visible
2648
  }
@@ -2659,12 +3942,11 @@ $(document).ready(function(){
2659
  });
2660
 
2661
  //Make it accessible to only the currently selected actor.
2662
- if (selectedActor != null) {
2663
  denyAccessForAllExcept(entry, selectedActor);
2664
  }
2665
 
2666
  var menu = buildMenuItem(entry);
2667
- updateItemEditor(menu);
2668
 
2669
  //Insert the item into the currently open submenu.
2670
  var visibleSubmenu = $('#ws_submenu_box').find('.ws_submenu:visible');
@@ -2674,6 +3956,7 @@ $(document).ready(function(){
2674
  } else {
2675
  visibleSubmenu.append(menu);
2676
  }
 
2677
 
2678
  //The items's editbox is always open
2679
  menu.find('.ws_edit_link').click();
@@ -2681,38 +3964,6 @@ $(document).ready(function(){
2681
  updateParentAccessUi(menu);
2682
  });
2683
 
2684
- function compareMenus(a, b){
2685
- var aTitle = jsTrim( $(a).find('.ws_item_title').text() );
2686
- var bTitle = jsTrim( $(b).find('.ws_item_title').text() );
2687
-
2688
- aTitle = aTitle.toLowerCase();
2689
- bTitle = bTitle.toLowerCase();
2690
-
2691
- return aTitle > bTitle ? 1 : -1;
2692
- }
2693
-
2694
- //Sort items in ascending order
2695
- $('#ws_sort_ascending').click(function () {
2696
- var submenu = $('#ws_submenu_box').find('.ws_submenu:visible');
2697
- if (submenu.length < 1) {
2698
- return; //Abort if no submenu visible
2699
- }
2700
-
2701
- submenu.find('.ws_container').sort(compareMenus);
2702
- });
2703
-
2704
- //Sort items in descending order
2705
- $('#ws_sort_descending').click(function () {
2706
- var submenu = $('#ws_submenu_box').find('.ws_submenu:visible');
2707
- if (submenu.length < 1) {
2708
- return; //Abort if no submenu visible
2709
- }
2710
-
2711
- submenu.find('.ws_container').sort((function(a, b){
2712
- return -compareMenus(a, b);
2713
- }));
2714
- });
2715
-
2716
  //==============================================
2717
  // Main buttons
2718
  //==============================================
@@ -2741,13 +3992,13 @@ $(document).ready(function(){
2741
  var foundItem = null;
2742
 
2743
  $.each(items, function(index, item) {
2744
- if (item.template_id == templateId) {
2745
  foundItem = item;
2746
  return false;
2747
  }
2748
  if (item.hasOwnProperty('items') && (item.items.length > 0)) {
2749
  foundItem = findItemByTemplateId(item.items, templateId);
2750
- if (foundItem != null) {
2751
  return false;
2752
  }
2753
  }
@@ -2760,7 +4011,7 @@ $(document).ready(function(){
2760
  //Abort the save if it would make the editor inaccessible.
2761
  if (wsEditorData.wsMenuEditorPro) {
2762
  var myMenuItem = findItemByTemplateId(tree.tree, 'options-general.php>menu_editor');
2763
- if (myMenuItem == null) {
2764
  //This is OK - the missing menu item will be re-inserted automatically.
2765
  } else if (!actorCanAccessMenu(myMenuItem, 'user:' + wsEditorData.currentUserLogin)) {
2766
  alert(
@@ -2776,20 +4027,35 @@ $(document).ready(function(){
2776
  $('#ws_data').val(data);
2777
  $('#ws_data_length').val(data.length);
2778
  $('#ws_selected_actor').val(selectedActor === null ? '' : selectedActor);
 
2779
  $('#ws_main_form').submit();
2780
  });
2781
 
2782
  //Load default menu - load the default WordPress menu
2783
  $('#ws_load_menu').click(function () {
2784
  if (confirm('Are you sure you want to load the default WordPress menu?')){
2785
- outputWpMenu(defaultMenu.tree);
2786
  }
2787
  });
2788
 
2789
  //Reset menu - re-load the custom menu. Discards any changes made by user.
2790
  $('#ws_reset_menu').click(function () {
2791
  if (confirm('Undo all changes made in the current editing session?')){
2792
- outputWpMenu(customMenu.tree);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2793
  }
2794
  });
2795
 
@@ -2831,18 +4097,21 @@ $(document).ready(function(){
2831
  'action' : 'export_custom_menu',
2832
  '_ajax_nonce' : wsEditorData.exportMenuNonce
2833
  },
 
 
 
2834
  function(data){
2835
  button.val('Export');
2836
  button.prop('disabled', false);
2837
 
2838
- if ( typeof data['error'] != 'undefined' ){
2839
  exportDialog.dialog('close');
2840
  alert(data.error);
2841
  }
2842
 
2843
- if ( (typeof data['download_url'] != 'undefined') && data.download_url ){
2844
  //window.location = data.download_url;
2845
- $('#download_menu_button').attr('href', data.download_url).data('filesize', data.filesize);
2846
  $('#export_progress_notice').hide();
2847
  $('#export_complete_notice, #download_menu_button').show();
2848
  }
@@ -2908,8 +4177,8 @@ $(document).ready(function(){
2908
 
2909
  //Check if the user has selected a file
2910
  for(var i = 0; i < formData.length; i++){
2911
- if ( formData[i].name == 'menu' ){
2912
- if ( (typeof formData[i]['value'] == 'undefined') || !formData[i]['value']){
2913
  alert('Select a file first!');
2914
  return false;
2915
  }
@@ -2937,18 +4206,18 @@ $(document).ready(function(){
2937
  return;
2938
  }
2939
 
2940
- if ( typeof data['error'] != 'undefined' ){
2941
  alert(data.error);
2942
  //Let the user try again
2943
  $('#import_menu_form').resetForm();
2944
  importDialog.find('.hide-when-uploading').show();
2945
  }
2946
 
2947
- if ( (typeof data['tree'] != 'undefined') && data.tree ){
2948
  //Whee, we got back a (seemingly) valid menu. A veritable miracle!
2949
  //Lets load it into the editor.
2950
  var progressNotice = $('#import_progress_notice2').show();
2951
- outputWpMenu(data.tree);
2952
  progressNotice.hide();
2953
  //Display a success notice, then automatically close the window after a few moments
2954
  $('#import_complete_notice').show();
@@ -2988,7 +4257,7 @@ $(document).ready(function(){
2988
  });
2989
 
2990
  //...and to drag top level menus to a sub-menu.
2991
- $('#ws_submenu_box').closest('.ws_main_container').droppable({
2992
  'hoverClass' : 'ws_top_to_submenu_drop_hover',
2993
 
2994
  'accept' : (function(thing){
@@ -2998,7 +4267,7 @@ $(document).ready(function(){
2998
  thing.hasClass('ws_menu') &&
2999
 
3000
  //Prevent users from dropping a menu on its own sub-menu.
3001
- (visibleSubmenu.attr('id') != thing.data('submenu_id'))
3002
  );
3003
  }),
3004
 
@@ -3014,7 +4283,55 @@ $(document).ready(function(){
3014
 
3015
 
3016
  //Set up tooltips
3017
- $('.ws_tooltip_trigger').qtip();
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
3018
 
3019
  //Flag closed hints as hidden by sending the appropriate AJAX request to the backend.
3020
  $('.ws_hint_close').click(function() {
@@ -3035,11 +4352,50 @@ $(document).ready(function(){
3035
  Actor views
3036
  ******************************************************************/
3037
 
3038
- //Build the list of available actors
3039
- var actorSelector = $('#ws_actor_selector').empty();
3040
- actorSelector.append('<li><a href="#" class="current ws_no_actor">All</a></li>');
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
3041
 
3042
- if (wsEditorData.wsMenuEditorPro) {
3043
  for(var actor in wsEditorData.actors) {
3044
  if (!wsEditorData.actors.hasOwnProperty(actor)) {
3045
  continue;
@@ -3048,41 +4404,82 @@ $(document).ready(function(){
3048
  $('<li></li>').append(
3049
  $('<a></a>')
3050
  .attr('href', '#' + actor)
 
3051
  .text(wsEditorData.actors[actor])
 
3052
  )
3053
  );
3054
  }
 
 
 
 
 
 
 
 
 
 
 
 
3055
  actorSelector.show();
3056
 
3057
- if ( wsEditorData.hasOwnProperty('selectedActor') && wsEditorData.selectedActor ) {
 
 
 
 
 
 
 
 
 
 
 
3058
  setSelectedActor(wsEditorData.selectedActor);
3059
  } else {
3060
  setSelectedActor(null);
3061
  }
3062
  }
3063
 
3064
- $('li a', actorSelector).click(function(event) {
3065
  var actor = $(this).attr('href').substring(1);
3066
- if (actor == '') {
3067
  actor = null;
3068
  }
3069
 
3070
  setSelectedActor(actor);
 
 
3071
 
 
3072
  event.preventDefault();
 
 
 
 
 
 
 
 
 
 
 
3073
  });
3074
 
3075
  //Finally, show the menu
3076
- outputWpMenu(customMenu.tree);
3077
  });
3078
 
3079
- })(jQuery);
3080
 
3081
  //==============================================
3082
  // Screen options
3083
  //==============================================
3084
 
3085
  jQuery(function($){
 
 
3086
  var screenOptions = $('#ws-ame-screen-meta-contents');
3087
  var hideSettingsCheckbox = screenOptions.find('#ws-hide-advanced-settings');
3088
  var extraIconsCheckbox = screenOptions.find('#ws-show-extra-icons');
1
  //(c) W-Shadow
2
 
3
+ /*global wsEditorData, defaultMenu, customMenu, _:false */
4
+
5
+ /**
6
+ * @property wsEditorData
7
+ * @property {boolean} wsEditorData.wsMenuEditorPro
8
+ *
9
+ * @property {object} wsEditorData.blankMenuItem
10
+ * @property {object} wsEditorData.itemTemplates
11
+ * @property {object} wsEditorData.customItemTemplate
12
+ *
13
+ * @property {string} wsEditorData.adminAjaxUrl
14
+ * @property {string} wsEditorData.imagesUrl
15
+ *
16
+ * @property {string} wsEditorData.menuFormatName
17
+ * @property {string} wsEditorData.menuFormatVersion
18
+ *
19
+ * @property {boolean} wsEditorData.hideAdvancedSettings
20
+ * @property {boolean} wsEditorData.showExtraIcons
21
+ * @property {boolean} wsEditorData.dashiconsAvailable
22
+ * @property {string} wsEditorData.submenuIconsEnabled
23
+ * @property {Object} wsEditorData.showHints
24
+ *
25
+ * @property {string} wsEditorData.hideAdvancedSettingsNonce
26
+ * @property {string} wsEditorData.getPagesNonce
27
+ * @property {string} wsEditorData.getPageDetailsNonce
28
+ * @property {string} wsEditorData.disableDashboardConfirmationNonce
29
+ *
30
+ * @property {string} wsEditorData.captionShowAdvanced
31
+ * @property {string} wsEditorData.captionHideAdvanced
32
+ *
33
+ * @property {string} wsEditorData.unclickableTemplateId
34
+ * @property {string} wsEditorData.unclickableTemplateClass
35
+ * @property {string} wsEditorData.embeddedPageTemplateId
36
+ *
37
+ * @property {string} wsEditorData.currentUserLogin
38
+ * @property {string|null} wsEditorData.selectedActor
39
+ *
40
+ * @property {object} wsEditorData.actors
41
+ * @property {object} wsEditorData.roles
42
+ * @property {object} wsEditorData.users
43
+ * @property {string[]} wsEditorData.visibleUsers
44
+ *
45
+ * @property {object} wsEditorData.postTypes
46
+ * @property {object} wsEditorData.taxonomies
47
+ *
48
+ * @property {boolean} wsEditorData.isDemoMode
49
+ * @property {boolean} wsEditorData.isMasterMode
50
+ */
51
 
52
  wsEditorData.wsMenuEditorPro = !!wsEditorData.wsMenuEditorPro; //Cast to boolean.
53
  var wsIdCounter = 0;
54
 
55
+ //A bit of black magic/hack to convince my IDE that wsAmeLodash is an alias for lodash.
56
+ window.wsAmeLodash = (function() {
57
+ 'use strict';
58
+ if (typeof wsAmeLodash !== 'undefined') {
59
+ return wsAmeLodash;
60
+ }
61
+ return _.noConflict();
62
+ })();
63
+
64
+ //These two properties must be objects, not arrays.
65
+ jQuery.each(['grant_access', 'hidden_from_actor'], function(unused, key) {
66
+ 'use strict';
67
+ if (wsEditorData.blankMenuItem.hasOwnProperty(key) && !jQuery.isPlainObject(wsEditorData.blankMenuItem[key])) {
68
+ wsEditorData.blankMenuItem[key] = {};
69
+ }
70
+ });
71
+
72
+ var AmeCapabilityManager = (function(roles, users, _) {
73
+ 'use strict';
74
+
75
+ /**
76
+ * A user.
77
+ *
78
+ * @typedef {Object} AmeUserActor
79
+ *
80
+ * @property {string} user_login
81
+ * @property {string} display_name
82
+ *
83
+ * @property {Object} capabilities A dictionary of ["capability" => boolean].
84
+ * @property {string[]} roles
85
+ * @property {boolean} is_super_admin
86
+ */
87
+
88
+ var me = {};
89
+ /**
90
+ * @type {Object.<String, AmeUserActor>}
91
+ */
92
  users = users || {};
93
 
94
+ var defaultCapabilities = {},
95
+ grantedCapabilities = {},
 
 
 
 
 
 
 
96
 
97
+ emptyObject = {},
98
+ cachedContextList = [emptyObject, grantedCapabilities, defaultCapabilities];
 
 
 
99
 
100
+ me.setRoles = function(newRoles) {
101
+ roles = newRoles;
102
+ _.forEach(roles, function(role, name) {
103
+ defaultCapabilities['role:' + name] = role.capabilities;
104
+ });
105
+ };
106
 
107
+ me.addUsers = function(newUsers) {
108
+ _.forEach(newUsers, function(user) {
109
+ users[user.user_login] = user;
110
+ defaultCapabilities['user:' + user.user_login] = user.capabilities;
111
+ });
112
+ };
113
 
114
+ me.getUsers = function() {
115
+ return users;
116
+ };
 
 
117
 
118
+ me.setRoles(roles);
119
+ me.addUsers(users);
 
 
 
 
 
120
 
121
+ function parseActorString(actor) {
122
+ var separator = actor.indexOf(':');
123
+ if (separator === -1) {
124
  throw {
125
+ name: 'InvalidActorException',
126
+ message: "Actor string does not contain a colon.",
127
+ value: actor
 
128
  };
129
  }
130
 
131
+ return {
132
+ 'type': actor.substring(0, separator),
133
+ 'id': actor.substring(separator + 1)
134
+ };
135
+ }
136
+
137
+ function actorHasCap(actor, capability, contextList) {
138
+ //Check for explicit settings first.
139
+ var result = null, actorValue, len = contextList.length;
140
+ for (var i = 0; i < len; i++) {
141
+ if (contextList[i].hasOwnProperty(actor)) {
142
+ actorValue = contextList[i][actor];
143
+ if (typeof actorValue === 'boolean') {
144
+ return actorValue;
145
+ } else if (actorValue.hasOwnProperty(capability)) {
146
+ result = actorValue[capability];
147
+ return (typeof result === 'boolean') ? result : result[0];
148
+ }
149
+ }
150
  }
151
+
152
+ //Super admins have access to everything by default, unless specifically denied.
153
+ if (actor === 'special:super_admin') {
154
+ return (capability !== 'do_not_allow');
155
+ }
156
+
157
+ //Roles only have the capabilities that they actually have.
158
+ if (actor.lastIndexOf('role:', 0) === 0) {
159
+ return false;
160
+ }
161
+
162
+ //Users can have a capability through their roles or the "super admin" flag.
163
+ if (actor.lastIndexOf('user:', 0) === 0) {
164
+ var user = users[actor.substr('user:'.length)];
165
+ if (user.is_super_admin) {
166
+ return actorHasCap('special:super_admin', capability, contextList);
167
+ }
168
+
169
+ //Check if any of the user's roles have the capability.
170
+ result = false;
171
+ for(var index = 0; index < user.roles.length; index++) {
172
+ result = result || actorHasCap('role:' + user.roles[index], capability, contextList);
173
+ }
174
+ return result;
175
+
176
+ } else {
177
+ throw {
178
+ name: 'InvalidActorTypeException',
179
+ message: "The specified actor type is not supported",
180
+ value: actor
181
+ };
182
+ }
183
+ }
184
+
185
+ me.hasCap = function(actor, capability, context) {
186
+ cachedContextList[0] = context || emptyObject;
187
+ return actorHasCap(actor, capability, cachedContextList);
188
+ };
189
+
190
+ me.hasCapByDefault = function(actor, capability) {
191
+ return actorHasCap(actor, capability, [defaultCapabilities]);
192
  };
193
 
194
+ /**
195
+ *
196
+ * @param {string} login
197
+ * @param {boolean} skipLoginActor
198
+ * @returns {Array} Caution: Do not modify the returned array. Returns a reference to an internal array.
199
+ */
200
+ me.getUserActors = function(login, skipLoginActor) {
201
  if (!users.hasOwnProperty(login)) {
202
  throw {
203
  name: 'UnknownUserException',
204
+ message: 'Can not get actors of an unknown user',
205
+ value: login
 
206
  };
207
  }
208
 
209
+ //Check the cache first.
210
  var user = users[login];
211
+ if (skipLoginActor && user.hasOwnProperty('actorsWithoutSelf')) {
212
+ return user.actorsWithoutSelf;
213
+ }
214
+ if (!skipLoginActor && user.hasOwnProperty('actors')) {
215
+ return user.actors;
216
+ }
 
 
 
 
 
217
 
218
+ //Generate the list and cache it.
219
+ var actors = [], actorsWithoutSelf = [];
220
+ actors.push('user:' + login);
221
+ if (user.is_super_admin) {
222
+ actorsWithoutSelf.push('special:super_admin');
223
+ }
224
+ for (var i = 0; i < user.roles.length; i++) {
225
+ actorsWithoutSelf.push('role:' + user.roles[i]);
226
+ }
227
+ actors = actors.concat(actorsWithoutSelf);
228
 
229
+ user.actors = actors;
230
+ user.actorsWithoutSelf = actorsWithoutSelf;
 
 
231
 
232
+ return skipLoginActor ? actorsWithoutSelf : actors;
233
+ };
 
 
 
234
 
235
+ me.getUser = function(login) {
236
+ if (!users.hasOwnProperty(login)) {
237
+ throw {
238
+ name: 'UnknownUserException',
239
+ message: 'User not found',
240
+ value: login
241
+ };
242
+ }
243
+ return users[login];
244
  };
245
 
246
  me.roleExists = function(roleId) {
247
+ return (typeof roleId === 'string') && roles.hasOwnProperty(roleId);
248
  };
249
 
250
  /**
284
  return specificity;
285
  };
286
 
287
+ me.setCap = function(actor, capability, hasCap, sourceType, sourceName) {
288
+ me.setCapInContext(grantedCapabilities, actor, capability, hasCap, sourceType, sourceName);
289
+ };
290
+
291
+ /**
292
+ * Grant or deny a capability to an actor.
293
+ *
294
+ * @param {Object} context
295
+ * @param {string} actor
296
+ * @param {string} capability
297
+ * @param {boolean} hasCap
298
+ * @param {string} [sourceType]
299
+ * @param {string} [sourceName]
300
+ */
301
+ me.setCapInContext = function(context, actor, capability, hasCap, sourceType, sourceName) {
302
+ var grant = sourceType ? [hasCap, sourceType, sourceName || null] : hasCap;
303
+ _.set(context, [actor, capability], grant);
304
+ };
305
+
306
+ me.resetCap = function(actor, capability) {
307
+ me.resetCapInContext(grantedCapabilities, actor, capability);
308
+ };
309
+
310
+ me.resetCapInContext = function(context, actor, capability) {
311
+ if (_.has(context, [actor, capability])) {
312
+ delete context[actor][capability];
313
+ }
314
+ };
315
+
316
+ me.setGrantedCapabilities = function(newGrants) {
317
+ grantedCapabilities = _.cloneDeep(newGrants);
318
+ cachedContextList[1] = grantedCapabilities;
319
+ };
320
+
321
+ me.getGrantedCapabilities = function() {
322
+ return grantedCapabilities;
323
+ };
324
+
325
+ /**
326
+ * Remove redundant granted capabilities.
327
+ *
328
+ * For example, if user "jane" has been granted the "edit_posts" capability both directly and via the Editor role,
329
+ * the direct grant is redundant. We can remove it. Jane will still have "edit_posts" because she's an editor.
330
+ */
331
+ me.pruneGrantedCapabilities = function(actorType) {
332
+ actorType = actorType || null;
333
+ var pruned = _.cloneDeep(grantedCapabilities),
334
+ context = [pruned, defaultCapabilities];
335
+
336
+ var actorKeys = _(pruned).keys().filter(function(actor) {
337
+ var parsed = parseActorString(actor);
338
+ //Skip users that are not loaded.
339
+ if (parsed.type === 'user' && !users.hasOwnProperty(actor.id)) {
340
+ return false;
341
+ }
342
+ return !(actorType && parsed.type !== actorType);
343
+ }).value();
344
+
345
+ _.forEach(actorKeys, function(actor) {
346
+ _.forEach(_.keys(pruned[actor]), function(capability) {
347
+ var grant = pruned[actor][capability];
348
+ delete pruned[actor][capability];
349
+
350
+ var hasCap = _.isArray(grant) ? grant[0] : grant,
351
+ hasCapWhenPruned = actorHasCap(actor, capability, context);
352
+
353
+ if (hasCap !== hasCapWhenPruned) {
354
+ pruned[actor][capability] = grant; //Restore.
355
+ }
356
+ });
357
+ });
358
+
359
+ me.setGrantedCapabilities(pruned);
360
+ return pruned;
361
+ };
362
 
363
  return me;
364
+ })(wsEditorData.roles, wsEditorData.users, wsAmeLodash);
365
 
366
+ /**
367
+ * A utility for retrieving post and page titles.
368
+ */
369
+ var AmePageTitles = (function($) {
370
+ 'use strict';
371
+
372
+ var me = {}, cache = {};
373
+
374
+ function getCacheKey(pageId, blogId) {
375
+ return blogId + '_' + pageId;
376
+ }
377
+
378
+ /**
379
+ * Add a page title to the cache.
380
+ *
381
+ * @param {Number} pageId Post or page ID.
382
+ * @param {Number} blogId Blog ID.
383
+ * @param {String} title The title of the post or page.
384
+ */
385
+ me.add = function(pageId, blogId, title) {
386
+ cache[getCacheKey(pageId, blogId)] = title;
387
+ };
388
+
389
+ /**
390
+ * Get page title.
391
+ *
392
+ * Note: This method does not return the title. Instead, it calls the provided callback with the title
393
+ * as the first argument. The callback will be executed asynchronously if the title hasn't been cached yet.
394
+ *
395
+ * @param {Number} pageId
396
+ * @param {Number} blogId
397
+ * @param {Function} callback
398
+ */
399
+ me.get = function(pageId, blogId, callback) {
400
+ var key = getCacheKey(pageId, blogId);
401
+ if (typeof cache[key] !== 'undefined') {
402
+ callback(cache[key], pageId, blogId);
403
+ return;
404
+ }
405
+
406
+ $.getJSON(
407
+ wsEditorData.adminAjaxUrl,
408
+ {
409
+ 'action' : 'ws_ame_get_page_details',
410
+ '_ajax_nonce' : wsEditorData.getPageDetailsNonce,
411
+ 'post_id' : pageId,
412
+ 'blog_id' : blogId
413
+ },
414
+ function(details) {
415
+ var title;
416
+ if (typeof details.error !== 'undefined'){
417
+ title = details.error;
418
+ } else if ((typeof details !== 'object') || (typeof details.post_title === 'undefined')) {
419
+ title = '< Server error >';
420
+ } else {
421
+ title = details.post_title;
422
+ }
423
+ cache[key] = title;
424
+
425
+ callback(cache[key], pageId, blogId);
426
+ }
427
+ );
428
+ };
429
+
430
+ return me;
431
+ })(jQuery);
432
 
433
  var AmeEditorApi = {};
434
+ window.AmeEditorApi = AmeEditorApi;
435
 
436
+
437
+ (function ($, _){
438
+ 'use strict';
439
 
440
  var selectedActor = null;
441
 
445
  getTemplateById: function(templateId) {
446
  if (wsEditorData.itemTemplates.hasOwnProperty(templateId)) {
447
  return wsEditorData.itemTemplates[templateId];
448
+ } else if ((templateId === '') || (templateId === 'custom')) {
449
  return wsEditorData.customItemTemplate;
450
  }
451
  return null;
461
  },
462
 
463
  getDefaultValue: function (templateId, fieldName) {
464
+ if (fieldName === 'template_id') {
465
  return null;
466
  }
467
 
468
  var defaults = this.getDefaults(templateId);
469
+ if (defaults && (typeof defaults[fieldName] !== 'undefined')) {
470
  return defaults[fieldName];
471
  }
472
  return null;
485
  * @param value
486
  */
487
  function setInputValue(input, value) {
488
+ if (input.attr('type') === 'checkbox'){
489
  input.prop('checked', value);
490
  } else {
491
  input.val(value);
500
  * @return {*}
501
  */
502
  function getInputValue(input) {
503
+ if (input.attr('type') === 'checkbox'){
504
  return input.is(':checked');
505
  }
506
  return input.val();
512
  * Rationale: Simpler than atomically auto-incrementing or globally unique IDs.
513
  */
514
  function randomMenuId(prefix, size){
515
+ prefix = (typeof prefix === 'undefined') ? 'custom_item_' : prefix;
516
+ size = (typeof size === 'undefined') ? 5 : size;
517
 
518
  var suffix = "";
519
  var possible = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
547
  menuBox.find('.ws_menu:first').click();
548
  }
549
 
550
+ /**
551
+ * Load a menu configuration in the editor.
552
+ * Note: All previous settings will be discarded without warning. Unsaved changes will be lost.
553
+ *
554
+ * @param {Object} adminMenu The menu structure to load.
555
+ */
556
+ function loadMenuConfiguration(adminMenu) {
557
+ //There are some menu properties that need to be objects, but PHP JSON-encodes empty associative
558
+ //arrays as numeric arrays. We want them to be empty objects instead.
559
+ if (adminMenu.hasOwnProperty('color_presets') && !$.isPlainObject(adminMenu.color_presets)) {
560
+ adminMenu.colorPresets = {};
561
+ }
562
+
563
+ var objectProperties = ['grant_access', 'hidden_from_actor'];
564
+ //noinspection JSUnusedLocalSymbols
565
+ function fixEmptyObjects(unused, menuItem) {
566
+ for (var i = 0; i < objectProperties.length; i++) {
567
+ var key = objectProperties[i];
568
+ if (menuItem.hasOwnProperty(key) && !$.isPlainObject(menuItem[key])) {
569
+ menuItem[key] = {};
570
+ }
571
+ }
572
+ if (menuItem.hasOwnProperty('items')) {
573
+ $.each(menuItem.items, fixEmptyObjects);
574
+ }
575
+ }
576
+ $.each(adminMenu.tree, fixEmptyObjects);
577
+
578
+ //Load color presets from the new configuration.
579
+ if (typeof adminMenu.color_presets === 'object') {
580
+ colorPresets = $.extend(true, {}, adminMenu.color_presets);
581
+ } else {
582
+ colorPresets = {};
583
+ }
584
+ wasPresetDropdownPopulated = false;
585
+
586
+ //Load capabilities.
587
+ AmeCapabilityManager.setGrantedCapabilities(_.get(adminMenu, 'granted_capabilities', {}));
588
+
589
+ //Display the new admin menu.
590
+ outputWpMenu(adminMenu.tree);
591
+ }
592
+
593
  /*
594
  * Create edit widgets for a top-level menu and its submenus and append them all to the DOM.
595
  *
601
  * Object with two fields - 'menu' and 'submenu' - containing the DOM nodes of the created widgets.
602
  */
603
  function outputTopMenu(menu, afterNode){
 
 
 
604
  //Create the menu widget
605
  var menu_obj = buildMenuItem(menu, true);
 
 
606
 
607
+ if ( (typeof afterNode !== 'undefined') && (afterNode !== null) ){
 
 
 
608
  $(afterNode).after(menu_obj);
609
  } else {
610
  menu_obj.appendTo('#ws_menu_box');
611
  }
612
 
613
+ //Create a container for menu items, even if there are none
614
+ var submenu = buildSubmenu(menu.items, menu_obj.attr('id'));
615
+ submenu.appendTo('#ws_submenu_box');
616
+ menu_obj.data('submenu_id', submenu.attr('id'));
617
+
618
+ //Note: Update the menu only after its children are ready. It needs the submenu items to decide whether to display
619
+ //the access checkbox as checked or indeterminate.
620
+ updateItemEditor(menu_obj);
621
+
622
  return {
623
  'menu' : menu_obj,
624
  'submenu' : submenu
628
  /*
629
  * Create and populate a submenu container.
630
  */
631
+ function buildSubmenu(items, parentMenuId){
632
  //Create a container for menu items, even if there are none
633
  var submenu = $('<div class="ws_submenu" style="display:none;"></div>');
634
  submenu.attr('id', 'ws-submenu-'+(wsIdCounter++));
635
 
636
+ if (parentMenuId) {
637
+ submenu.data('parent_menu_id', parentMenuId);
638
+ }
639
+
640
  //Only show menus that have items.
641
  //Skip arrays (with a length) because filled menus are encoded as custom objects.
642
  var entry = null;
644
  $.each(items, function(index, item) {
645
  entry = buildMenuItem(item, false);
646
  if ( entry ){
 
647
  submenu.append(entry);
648
+ updateItemEditor(entry);
649
  }
650
  });
651
  }
664
  * @return {*} The created widget as a jQuery object.
665
  */
666
  function buildMenuItem(itemData, isTopLevel) {
667
+ isTopLevel = (typeof isTopLevel === 'undefined') ? false : isTopLevel;
668
 
669
  //Create the menu HTML
670
  var item = $('<div></div>')
682
  //the editors themselves are created later, when the user tries to access them
683
  //for the first time).
684
  var contents = [];
685
+ var menuTitle = ((itemData.menu_title !== null) ? itemData.menu_title : itemData.defaults.menu_title);
686
  if (menuTitle === '') {
687
  menuTitle = '&nbsp;';
688
  }
743
  return str.replace(/^\s+|\s+$/g, "");
744
  }
745
 
746
+ //Expose this handy tool to our other scripts.
747
+ AmeEditorApi.jsTrim = jsTrim;
748
+
749
  function stripAllTags(input) {
750
  //Based on: http://phpjs.org/functions/strip_tags/
751
  var tags = /<\/?([a-z][a-z0-9]*)\b[^>]*>/gi,
835
 
836
  menuItem.template_id = value;
837
  menuItem.defaults = itemTemplates.getDefaults(menuItem.template_id);
838
+ menuItem.custom = (menuItem.template_id === '');
839
 
840
  // The file/URL of non-custom items is read-only and equal to the default
841
  // value. Rationale: simplifies menu generation, prevents some user mistakes.
862
  }
863
  }),
864
 
865
+ 'embedded_page_id' : $.extend({}, baseField, {
866
+ caption: 'Embedded page ID',
867
+ defaultValue: 'Select page to display',
868
+ type: 'text',
869
+ visible: false, //Displayed on-demand.
870
+ addDropdown: 'ws_embedded_page_selector',
871
+
872
+ display: function(menuItem, displayValue, input) {
873
+ //Only show this field if the "Embed WP page" template is selected.
874
+ input.closest('.ws_edit_field').toggle(menuItem.template_id === wsEditorData.embeddedPageTemplateId);
875
+
876
+ input.prop('readonly', true);
877
+ var pageId = parseInt(getFieldValue(menuItem, 'embedded_page_id', 0), 10),
878
+ blogId = parseInt(getFieldValue(menuItem, 'embedded_page_blog_id', 1), 10),
879
+ formattedId = 'ID: ' + pageId;
880
+
881
+ if (pageId <= 0) {
882
+ return 'Select page =>';
883
+ }
884
+
885
+ if (blogId !== 1) {
886
+ formattedId = formattedId + ', blog ID: ' + blogId;
887
+ }
888
+ displayValue = formattedId;
889
+
890
+ AmePageTitles.get(pageId, blogId, function(title) {
891
+ //If we retrieved the title via AJAX, the user might have selected a different page in the meantime.
892
+ //Make sure it's still the same page before displaying the title.
893
+ var currentPageId = parseInt(getFieldValue(menuItem, 'embedded_page_id', 0), 10),
894
+ currentBlogId = parseInt(getFieldValue(menuItem, 'embedded_page_blog_id', 1), 10);
895
+ if ((currentPageId !== pageId) || (currentBlogId !== blogId)) {
896
+ return;
897
+ }
898
+
899
+ displayValue = title + ' (' + formattedId + ')';
900
+ input.val(displayValue);
901
+ });
902
+
903
+ return displayValue;
904
+ },
905
+
906
+ write: function() {
907
+ //The user cannot directly edit this field. We deliberately ignore writes.
908
+ }
909
+ }),
910
+
911
  'file' : $.extend({}, baseField, {
912
  caption: 'URL',
913
  display: function(menuItem, displayValue, input) {
1024
  visible: false
1025
  }),
1026
 
1027
+ 'iframe_height' : $.extend({}, baseField, {
1028
+ caption: 'Frame height (pixels)',
1029
+ advanced : true,
1030
+ visible: function(menuItem) {
1031
+ return wsEditorData.wsMenuEditorPro && (getFieldValue(menuItem, 'open_in') === 'iframe');
1032
+ },
1033
+
1034
+ display: function(menuItem, displayValue, input) {
1035
+ input.prop('placeholder', 'Auto');
1036
+ if (displayValue === 0 || displayValue === '0') {
1037
+ displayValue = '';
1038
+ }
1039
+ return displayValue;
1040
+ },
1041
+
1042
+ write: function(menuItem, value) {
1043
+ value = parseInt(value, 10);
1044
+ if (isNaN(value) || (value < 0)) {
1045
+ value = 0;
1046
+ }
1047
+ value = Math.round(value);
1048
+
1049
+ if (value > 10000) {
1050
+ value = 10000;
1051
+ }
1052
+
1053
+ if (value === 0) {
1054
+ menuItem.iframe_height = null;
1055
+ } else {
1056
+ menuItem.iframe_height = value;
1057
+ }
1058
+
1059
+ }
1060
+ }),
1061
+
1062
  'css_class' : $.extend({}, baseField, {
1063
  caption: 'CSS classes',
1064
  advanced : true,
1075
  display: function(menuItem, displayValue, input, containerNode) {
1076
  //Display the current icon in the selector.
1077
  var cssClass = getFieldValue(menuItem, 'css_class', '');
1078
+ var iconUrl = getFieldValue(menuItem, 'icon_url', '', containerNode);
1079
+ displayValue = iconUrl;
1080
 
1081
  //When submenu icon visibility is set to "only if manually selected",
1082
  //don't show the default submenu icons.
1083
+ var isDefault = (typeof menuItem.icon_url === 'undefined') || (menuItem.icon_url === null);
1084
  if (isDefault && (wsEditorData.submenuIconsEnabled === 'if_custom') && containerNode.hasClass('ws_item')) {
1085
  iconUrl = 'none';
1086
  cssClass = '';
1167
  caption: 'Hook name',
1168
  advanced : true,
1169
  onlyForTopMenus: true
1170
+ }),
1171
+
1172
+ 'is_always_open' : $.extend({}, baseField, {
1173
+ caption: 'Keep this menu open',
1174
+ advanced : true,
1175
+ onlyForTopMenus: true,
1176
+ type: 'checkbox',
1177
+ standardCaption: false
1178
  })
1179
  };
1180
 
1181
+ AmeEditorApi.getItemDisplayUrl = function(menuItem) {
1182
+ var url = getFieldValue(menuItem, 'file', '');
1183
+ if (menuItem.template_id !== '') {
1184
+ var defaultUrl = itemTemplates.getDefaultValue(menuItem.template_id, 'url');
1185
+ if (defaultUrl) {
1186
+ url = defaultUrl;
1187
+ }
1188
+ }
1189
+ return url;
1190
+ };
1191
+
1192
  /*
1193
  * Create editors for the visible fields of a menu entry and append them to the specified node.
1194
  */
1195
  function buildEditboxFields(fieldContainer, entry, isTopLevel){
1196
+ isTopLevel = (typeof isTopLevel === 'undefined') ? false : isTopLevel;
1197
 
1198
  var basicFields = $('<div class="ws_edit_panel ws_basic"></div>').appendTo(fieldContainer);
1199
  var advancedFields = $('<div class="ws_edit_panel ws_advanced"></div>').appendTo(fieldContainer);
1225
  //Add a link that shows/hides advanced fields
1226
  fieldContainer.append(
1227
  '<div class="ws_toggle_container"><a href="#" class="ws_toggle_advanced_fields"'+
1228
+ (wsEditorData.hideAdvancedSettings ? '' : ' style="display:none;" ' )+'>'+
1229
  (wsEditorData.hideAdvancedSettings ? wsEditorData.captionShowAdvanced : wsEditorData.captionHideAdvanced)
1230
  +'</a></div>'
1231
  );
1256
  break;
1257
 
1258
  case 'checkbox':
1259
+ inputBox = $('<label><input type="checkbox" class="ws_field_value"> <span class="ws_field_label_text">'+
1260
+ field_settings.caption + '</span></label>'
1261
  );
1262
  break;
1263
 
1267
  break;
1268
 
1269
  case 'icon_selector':
1270
+ //noinspection HtmlUnknownTag
1271
  inputBox = $(basicTextField)
1272
  .add('<button class="button ws_select_icon" title="Select icon"><div class="icon16 icon-settings"></div><img src="" style="display:none;"></button>');
1273
  break;
1277
  .add('<input type="button" class="button ws_open_color_editor" value="Edit...">');
1278
  break;
1279
 
1280
+ case 'text':
1281
+ /* falls through */
1282
  default:
1283
  inputBox = $(basicTextField);
1284
  }
1288
  if (field_settings.addDropdown){
1289
  className += ' ws_has_dropdown';
1290
  }
1291
+ if (!field_settings.standardCaption) {
1292
+ className += ' ws_no_field_caption';
1293
+ }
1294
 
1295
+ var caption = '';
1296
+ if (field_settings.standardCaption) {
1297
+ caption = '<span class="ws_field_label_text">' + field_settings.caption + '</span><br>';
1298
+ }
1299
+ var editField = $('<div>' + caption + '</div>')
1300
  .attr('class', className)
1301
  .append(inputBox);
1302
 
1305
  var dropdownId = field_settings.addDropdown;
1306
  editField.append(
1307
  $('<input type="button" value="&#9660;">')
1308
+ .addClass('button ws_dropdown_button ' + dropdownId + '_trigger')
1309
  .attr('tabindex', '-1')
1310
  .data('dropdownId', dropdownId)
1311
  );
1312
  }
1313
 
1314
  editField
1315
+ .append(
1316
+ $('<img class="ws_reset_button" title="Reset to default value">')
1317
+ .attr('src', wsEditorData.imagesUrl + '/transparent16.png')
1318
+ ).data('field_name', field_name);
1319
+
1320
+ var visible = true;
1321
+ if (typeof field_settings.visible === 'function') {
1322
+ visible = field_settings.visible(entry, field_name);
1323
+ } else {
1324
+ visible = field_settings.visible;
1325
+ }
1326
+ if (!visible) {
1327
  editField.css('display', 'none');
1328
  }
1329
 
1330
  return editField;
1331
  }
1332
 
1333
+ /**
1334
+ * Get the parent menu of a menu item.
1335
+ *
1336
+ * @param containerNode A DOM element as a jQuery object.
1337
+ * @return {jQuery} Parent container node, or an empty jQuery set.
1338
+ */
1339
+ function getParentMenuNode(containerNode) {
1340
+ var submenu = containerNode.closest('.ws_submenu', '#ws_menu_editor'),
1341
+ parentId = submenu.data('parent_menu_id');
1342
+ if (parentId) {
1343
+ return $('#' + parentId);
1344
+ } else {
1345
+ return $([]);
1346
+ }
1347
+ }
1348
+
1349
+ /**
1350
+ * Get all submenu items of a menu item.
1351
+ *
1352
+ * @param {jQuery} containerNode
1353
+ * @return {jQuery} A list of submenu item container nodes, or an empty set.
1354
+ */
1355
+ function getSubmenuItemNodes(containerNode) {
1356
+ var subMenuId = containerNode.data('submenu_id');
1357
+ if (subMenuId) {
1358
+ return $('#' + subMenuId).find('.ws_container');
1359
+ } else {
1360
+ return $([]);
1361
+ }
1362
+ }
1363
+
1364
+ /**
1365
+ * Apply a callback recursively to a menu item and all of its children, in depth-first order.
1366
+ * The callback will be invoked with two arguments: (containerNode, menuItem).
1367
+ *
1368
+ * @param containerNode
1369
+ * @param {Function} callback
1370
+ */
1371
+ function walkMenuTree(containerNode, callback) {
1372
+ getSubmenuItemNodes(containerNode).each(function() {
1373
+ walkMenuTree($(this), callback);
1374
+ });
1375
+ callback(containerNode, containerNode.data('menu_item'));
1376
+ }
1377
+
1378
  /**
1379
  * Update the UI elements that that indicate whether the currently selected
1380
  * actor can access a menu item.
1383
  */
1384
  function updateActorAccessUi(containerNode) {
1385
  //Update the permissions checkbox & UI
1386
+ var menuItem = containerNode.data('menu_item');
1387
+ if (selectedActor !== null) {
1388
  var hasAccess = actorCanAccessMenu(menuItem, selectedActor);
1389
  var hasCustomPermissions = actorHasCustomPermissions(menuItem, selectedActor);
1390
 
1391
+ var isOverrideActive = !hasAccess && getFieldValue(menuItem, 'restrict_access_to_items', false);
1392
+
1393
+ //Check if the parent menu has the "hide all submenus if this is hidden" override in effect.
1394
+ var currentChild = containerNode, parentNode, parentItem;
1395
+ do {
1396
+ parentNode = getParentMenuNode(currentChild);
1397
+ parentItem = parentNode.data('menu_item');
1398
+ if (
1399
+ parentItem
1400
+ && getFieldValue(parentItem, 'restrict_access_to_items', false)
1401
+ && !actorCanAccessMenu(parentItem, selectedActor)
1402
+ ) {
1403
+ hasAccess = false;
1404
+ isOverrideActive = true;
1405
+ break;
1406
+ }
1407
+ currentChild = parentNode;
1408
+ } while (parentNode.length > 0);
1409
+
1410
  var checkbox = containerNode.find('.ws_actor_access_checkbox');
1411
  checkbox.prop('checked', hasAccess);
1412
 
1413
  //Display the checkbox differently if some items of this menu are hidden and some are visible,
1414
  //or if their permissions don't match this menu's permissions.
1415
+ var submenuItems = getSubmenuItemNodes(containerNode);
1416
+ if ((submenuItems.length === 0) || isOverrideActive) {
1417
+ //Either this menu doesn't contain any items, or their permissions don't matter because they're overridden.
 
1418
  checkbox.prop('indeterminate', false);
1419
  } else {
1420
  var differentPermissions = false;
1436
 
1437
  containerNode.toggleClass('ws_is_hidden_for_actor', !hasAccess);
1438
  containerNode.toggleClass('ws_has_custom_permissions_for_actor', hasCustomPermissions);
1439
+ setMenuFlag(containerNode, 'custom_actor_permissions', hasCustomPermissions);
1440
+ setMenuFlag(containerNode, 'hidden_from_others', false);
1441
  } else {
1442
  containerNode.removeClass('ws_is_hidden_for_actor ws_has_custom_permissions_for_actor');
1443
  setMenuFlag(containerNode, 'custom_actor_permissions', false);
1444
+
1445
+ var currentUserActor = 'user:' + wsEditorData.currentUserLogin;
1446
+ var otherActors = _(wsEditorData.actors).keys().without(currentUserActor, 'special:super_admin').value(),
1447
+ hiddenFromCurrentUser = ! actorCanAccessMenu(menuItem, currentUserActor),
1448
+ hiddenFromOthers = ! _.some(otherActors, _.curry(actorCanAccessMenu, 2)(menuItem));
1449
+ setMenuFlag(
1450
+ containerNode,
1451
+ 'hidden_from_others',
1452
+ hiddenFromOthers,
1453
+ hiddenFromCurrentUser ? 'Hidden from everyone' : 'Hidden from everyone except you'
1454
+ );
1455
  }
1456
+
1457
+ //Update the "hidden" flag.
1458
+ setMenuFlag(containerNode, 'hidden', itemHasHiddenFlag(menuItem, selectedActor));
1459
  }
1460
 
1461
  /**
1503
 
1504
  var hasADefaultValue = itemTemplates.hasDefaultValue(menuItem.template_id, fieldName);
1505
  var defaultValue = itemTemplates.getDefaultValue(menuItem.template_id, fieldName);
1506
+ var isDefault = hasADefaultValue && ((typeof menuItem[fieldName] === 'undefined') || (menuItem[fieldName] === null));
1507
 
1508
+ if (fieldName === 'access_level') {
1509
+ isDefault = (getFieldValue(menuItem, 'extra_capability', '') === '')
1510
+ && isEmptyObject(menuItem.grant_access)
1511
+ && (!getFieldValue(menuItem, 'restrict_access_to_items', false));
1512
  }
1513
 
1514
  field.toggleClass('ws_has_no_default', !hasADefaultValue);
1520
  }
1521
 
1522
  setInputValue(input, displayValue);
1523
+
1524
+ if (typeof (knownMenuFields[fieldName].visible) === 'function') {
1525
+ var isFieldVisible = knownMenuFields[fieldName].visible(menuItem, fieldName);
1526
+ if (isFieldVisible) {
1527
+ field.css('display', '');
1528
+ } else {
1529
+ field.css('display', 'none');
1530
+ }
1531
+ }
1532
  });
1533
  }
1534
 
1541
  return true;
1542
  }
1543
 
1544
+ /**
1545
  * Get the current value of a single menu field.
1546
  *
1547
  * If the specified field is not set, this function will attempt to retrieve it
1548
  * from the "defaults" property of the menu object. If *that* fails, it will return
1549
  * the value of the optional third argument defaultValue.
1550
+ *
1551
+ * @param {Object} entry
1552
+ * @param {string} fieldName
1553
+ * @param {*} [defaultValue]
1554
+ * @param {jQuery} [containerNode]
1555
+ * @return {*}
1556
  */
1557
+ function getFieldValue(entry, fieldName, defaultValue, containerNode){
1558
  if ( (typeof entry[fieldName] === 'undefined') || (entry[fieldName] === null) ) {
1559
+
1560
+ //By default, a submenu item has the same icon as its parent.
1561
+ if ((fieldName === 'icon_url') && containerNode && (wsEditorData.submenuIconsEnabled !== 'never')) {
1562
+ var parentContainerNode = getParentMenuNode(containerNode),
1563
+ parentMenuItem = parentContainerNode.data('menu_item');
1564
+ if (parentMenuItem) {
1565
+ return getFieldValue(parentMenuItem, fieldName, defaultValue, parentContainerNode);
1566
+ }
1567
+ }
1568
+
1569
+ var hasDefault = (typeof entry.defaults !== 'undefined') && (typeof entry.defaults[fieldName] !== 'undefined');
1570
+ if (hasDefault){
1571
  return entry.defaults[fieldName];
1572
+ } else {
1573
+ return defaultValue;
1574
  }
1575
  } else {
1576
  return entry[fieldName];
1577
  }
1578
  }
1579
 
1580
+ AmeEditorApi.getFieldValue = getFieldValue;
1581
+
1582
  /*
1583
  * Make a menu container sortable
1584
  */
1588
  items: '> .ws_container',
1589
  cursor: 'move',
1590
  dropOnEmpty: true,
1591
+ cancel : '.ws_editbox, .ws_edit_link',
1592
+
1593
+ placeholder: 'ws_container ws_sortable_placeholder',
1594
+ forcePlaceholderSize: true,
1595
+
1596
+ stop: function(even, ui) {
1597
+ //Fix incorrect item overlap caused by jQuery.sortable applying the initial z-index as an inline style.
1598
+ ui.item.css('z-index', '');
1599
+
1600
+ //Fix submenu container height. It should be tall enough to reach the selected parent menu.
1601
+ if (ui.item.hasClass('ws_menu') && ui.item.hasClass('ws_active')) {
1602
+ AmeEditorApi.updateSubmenuBoxHeight(ui.item);
1603
+ }
1604
+ }
1605
  });
1606
  }
1607
 
1608
+ /**
1609
+ * Iterates over all menu items invoking a callback for each item.
1610
+ *
1611
+ * The callback will be passed two arguments: the menu item and its UI container node (a jQuery object).
1612
+ * You can stop iteration by returning false from the callback.
1613
+ *
1614
+ * @param {Function} callback
1615
+ * @param {boolean} [skipSeparators] Defaults to true. Set to false to include separators in the iteration.
1616
+ */
1617
+ AmeEditorApi.forEachMenuItem = function(callback, skipSeparators) {
1618
+ if (typeof skipSeparators === 'undefined') {
1619
+ skipSeparators = true;
1620
+ }
1621
+
1622
+ $('#ws_menu_editor').find('.ws_container').each(function() {
1623
+ var containerNode = $(this);
1624
+ if ( !(skipSeparators && containerNode.hasClass('ws_menu_separator')) ) {
1625
+ return callback(containerNode.data('menu_item'), containerNode);
1626
+ }
1627
+ });
1628
+ };
1629
+
1630
  /***************************************************************************
1631
  Parsing & encoding menu inputs
1632
  ***************************************************************************/
1637
  * @return {String} A JSON-encoded string representing the current menu tree loaded in the editor.
1638
  */
1639
  function encodeMenuAsJSON(tree){
1640
+ if (typeof tree === 'undefined' || !tree) {
1641
  tree = readMenuTreeState();
1642
  }
1643
  tree.format = {
1664
  if (menu.template_id === wsEditorData.unclickableTemplateId) {
1665
  ws_paste_count++;
1666
  filename = '#' + wsEditorData.unclickableTemplateClass + '-' + ws_paste_count;
1667
+ } else if (menu.template_id === wsEditorData.embeddedPageTemplateId) {
1668
+ ws_paste_count++;
1669
+ filename = '#embedded-page-' + ws_paste_count;
1670
  }
1671
 
1672
  //Prevent the user from saving top level items with duplicate URLs.
1677
  code: 'duplicate_top_level_url',
1678
  message: 'Error: Found a duplicate URL! All top level menus must have unique URLs.',
1679
  duplicates: [itemsByFilename[filename], containerNode]
1680
+ };
1681
  }
1682
 
1683
  tree[filename] = menu;
1684
  itemsByFilename[filename] = containerNode;
1685
  });
1686
 
1687
+ AmeCapabilityManager.pruneGrantedCapabilities('user');
1688
+
1689
  return {
1690
+ tree: tree,
1691
+ color_presets: $.extend(true, {}, colorPresets),
1692
+ granted_capabilities: AmeCapabilityManager.getGrantedCapabilities()
1693
  };
1694
  }
1695
 
1696
  AmeEditorApi.readMenuTreeState = readMenuTreeState;
1697
+ AmeEditorApi.encodeMenuAsJson = encodeMenuAsJSON;
1698
 
1699
  /**
1700
  * Extract the current menu item settings from its editor widget.
1704
  * @return {Object} A menu object in the tree format.
1705
  */
1706
  function readItemState(itemDiv, position){
1707
+ position = (typeof position === 'undefined') ? 0 : position;
1708
 
1709
  itemDiv = $(itemDiv);
1710
  var item = $.extend({}, wsEditorData.blankMenuItem, itemDiv.data('menu_item'), readAllFields(itemDiv));
1716
  item.defaults.position = position; //The real default value will later overwrite this
1717
 
1718
  item.separator = itemDiv.hasClass('ws_menu_separator');
 
1719
  item.custom = menuHasFlag(itemDiv, 'custom');
1720
 
1721
  //Gather the menu's sub-items, if any
1760
  return true;
1761
  }
1762
 
1763
+ //Hackety-hack. The "Page" input is for display purposes and contains more than just the ID. Skip it.
1764
+ //Eventually we'll need a better way to handle this.
1765
+ if (field_name === 'embedded_page_id') {
1766
+ return true;
1767
+ }
1768
+
1769
  //Find the field (usually an input or select element).
1770
  var input_box = field.find('.ws_field_value');
1771
 
1780
 
1781
  //Permission settings are not stored in the visible access_level field (that's just for show),
1782
  //so do not attempt to read them from there.
1783
+ state.access_level = null;
1784
 
1785
  return state;
1786
  }
1793
  var item_flags = {
1794
  'custom':'This is a custom menu item',
1795
  'unused':'This item was automatically recreated. You cannot delete a non-custom item, but you could hide it.',
1796
+ 'hidden':'Cosmetically hidden',
1797
+ 'custom_actor_permissions' : "The selected role has custom permissions for this item.",
1798
+ 'hidden_from_others' : 'Hidden from everyone except you.'
1799
  };
1800
 
1801
+ function setMenuFlag(item, flag, state, title) {
1802
+ title = title || item_flags[flag];
1803
  item = $(item);
1804
 
1805
  var item_class = 'ws_' + flag;
1807
 
1808
  item.toggleClass(item_class, state);
1809
  if (state) {
1810
+ //Add the flag image.
1811
  var flag_container = item.find('.ws_flag_container');
1812
+ var image = flag_container.find('.' + img_class);
1813
+ if (image.length === 0) {
1814
+ image = $('<div></div>').addClass('ws_flag').addClass(img_class);
1815
+ flag_container.append(image);
1816
  }
1817
+ image.attr('title', title);
1818
  } else {
1819
  //Remove the flag image.
1820
  item.find('.' + img_class).remove();
1825
  return $(item).hasClass('ws_'+flag);
1826
  }
1827
 
1828
+ //The "hidden" flag is special. There's both a global version and one that's actor-specific.
1829
+
1830
+ /**
1831
+ * Check if a menu item is hidden from an actor.
1832
+ * This function only checks the "hidden" and "hidden_from_actor" flags, not permissions.
1833
+ *
1834
+ * @param {Object} menuItem
1835
+ * @param {string|null} actor
1836
+ * @returns {boolean}
1837
+ */
1838
+ function itemHasHiddenFlag(menuItem, actor) {
1839
+ var isHidden = false,
1840
+ userActors,
1841
+ userPrefix = 'user:',
1842
+ userLogin;
1843
+
1844
+ //(Only) A globally hidden item is hidden from everyone.
1845
+ if ((actor === null) || menuItem.hidden) {
1846
+ return menuItem.hidden;
1847
+ }
1848
+
1849
+ if (actor.substr(0, userPrefix.length) === userPrefix) {
1850
+ //You can set an exception for a specific user. It takes precedence.
1851
+ if (menuItem.hidden_from_actor.hasOwnProperty(actor)) {
1852
+ isHidden = menuItem.hidden_from_actor[actor];
1853
+ } else {
1854
+ //Otherwise the item is hidden only if it is hidden from all of the user's roles.
1855
+ userLogin = selectedActor.substr(userPrefix.length);
1856
+ userActors = AmeCapabilityManager.getUserActors(userLogin, true);
1857
+ for (var i = 0; i < userActors.length; i++) {
1858
+ if (menuItem.hidden_from_actor.hasOwnProperty(userActors[i]) && menuItem.hidden_from_actor[userActors[i]]) {
1859
+ isHidden = true;
1860
+ } else {
1861
+ isHidden = false;
1862
+ break;
1863
+ }
1864
+ }
1865
+ }
1866
+ } else {
1867
+ //Roles and the super admin are straightforward.
1868
+ isHidden = menuItem.hidden_from_actor.hasOwnProperty(actor) && menuItem.hidden_from_actor[actor];
1869
+ }
1870
+
1871
+ return isHidden;
1872
+ }
1873
+
1874
+ /**
1875
+ * Toggle menu visibility without changing its permissions.
1876
+ *
1877
+ * Applies to the selected actor, or all actors if no actor is selected.
1878
+ *
1879
+ * @param {jQuery} selection A menu container node.
1880
+ * @param {boolean} [isHidden] Optional. True = hide the menu, false = show the menu.
1881
+ */
1882
+ function toggleItemHiddenFlag(selection, isHidden) {
1883
+ var menuItem = selection.data('menu_item');
1884
+
1885
+ //By default, invert the current state.
1886
+ if (typeof isHidden === 'undefined') {
1887
+ isHidden = !itemHasHiddenFlag(menuItem, selectedActor);
1888
+ }
1889
+
1890
+ //Mark the menu as hidden/visible
1891
+ if (selectedActor === null) {
1892
+ //For ALL roles and users.
1893
+ menuItem.hidden = isHidden;
1894
+ menuItem.hidden_from_actor = {};
1895
+ } else {
1896
+ //Just for the current role.
1897
+ if (isHidden) {
1898
+ menuItem.hidden_from_actor[selectedActor] = true;
1899
+ } else {
1900
+ if (selectedActor.indexOf('user:') === 0) {
1901
+ //User-specific exception. Lets you can hide a menu from all admins but leave it visible to yourself.
1902
+ menuItem.hidden_from_actor[selectedActor] = false;
1903
+ } else {
1904
+ delete menuItem.hidden_from_actor[selectedActor];
1905
+ }
1906
+ }
1907
+
1908
+ //When the user un-hides a menu that was globally hidden via the "hidden" flag, we must remove
1909
+ //that flag but also make sure the menu stays hidden from other roles.
1910
+ if (!isHidden && menuItem.hidden) {
1911
+ menuItem.hidden = false;
1912
+ $.each(wsEditorData.actors, function(otherActor) {
1913
+ if (otherActor !== selectedActor) {
1914
+ menuItem.hidden_from_actor[otherActor] = true;
1915
+ }
1916
+ });
1917
+ }
1918
+ }
1919
+ setMenuFlag(selection, 'hidden', isHidden);
1920
+
1921
+ //Also mark all of it's submenus as hidden/visible
1922
+ var submenuId = selection.data('submenu_id');
1923
+ if (submenuId) {
1924
+ $('#' + submenuId + ' .ws_item').each(function(){
1925
+ toggleItemHiddenFlag($(this), isHidden);
1926
+ });
1927
+ }
1928
+ }
1929
+
1930
  /***********************************************************
1931
  Capability manipulation
1932
  ************************************************************/
1957
  return false;
1958
  }
1959
 
1960
+ /**
1961
+ * @param containerNode
1962
+ * @param {string|Object.<string, boolean>} actor
1963
+ * @param {boolean} [allowAccess]
1964
+ */
1965
  function setActorAccess(containerNode, actor, allowAccess) {
1966
  var menuItem = containerNode.data('menu_item');
1967
 
1971
  menuItem.grant_access = {};
1972
  }
1973
 
1974
+ if (typeof actor === 'string') {
1975
+ menuItem.grant_access[actor] = !!allowAccess;
1976
+ } else {
1977
+ _.assign(menuItem.grant_access, actor);
1978
+ }
1979
  }
1980
 
1981
  function setSelectedActor(actor) {
1994
  var actorSelector = $('#ws_actor_selector');
1995
  $('.current', actorSelector).removeClass('current');
1996
 
1997
+ if (selectedActor === null) {
1998
  $('a.ws_no_actor').addClass('current');
1999
  } else {
2000
  newSelectedItem.addClass('current');
2002
 
2003
  //There are some UI elements that can be visible or hidden depending on whether an actor is selected.
2004
  var editorNode = $('#ws_menu_editor');
2005
+ editorNode.toggleClass('ws_is_actor_view', (selectedActor !== null));
2006
 
2007
  //Update the menu item states to indicate whether they're accessible.
2008
  editorNode.find('.ws_container').each(function() {
2049
  var menu_in_clipboard = null;
2050
  var ws_paste_count = 0;
2051
 
2052
+ //Color preset stuff.
2053
+ var colorPresets = {},
2054
+ wasPresetDropdownPopulated = false;
2055
+
2056
  $(document).ready(function(){
2057
  //Some editor elements are only available in the Pro version.
2058
  if (wsEditorData.wsMenuEditorPro) {
2059
+ knownMenuFields.open_in.visible = true;
2060
+ knownMenuFields.access_level.visible = true;
2061
+ knownMenuFields.page_heading.visible = true;
2062
+ knownMenuFields.colors.visible = true;
2063
+ knownMenuFields.extra_capability.visible = false; //Superseded by the "access_level" field.
2064
 
2065
  //The Pro version supports submenu icons, but they can be disabled by the user.
2066
+ knownMenuFields.icon_url.onlyForTopMenus = (wsEditorData.submenuIconsEnabled === 'never');
2067
 
2068
  $('.ws_hide_if_pro').hide();
2069
  }
2078
  /***************************************************************************
2079
  Event handlers for editor widgets
2080
  ***************************************************************************/
2081
+ var menuEditorNode = $('#ws_menu_editor'),
2082
+ submenuBox = $('#ws_submenu_box'),
2083
+ submenuDropZone = submenuBox.closest('.ws_main_container').find('.ws_dropzone');
2084
 
2085
  //Highlight the clicked menu item and show it's submenu
2086
  var currentVisibleSubmenu = null;
2087
+ menuEditorNode.on('click', '.ws_container', (function () {
2088
  var container = $(this);
2089
+ if (container.hasClass('ws_active')) {
2090
  return;
2091
  }
2092
 
2093
  //Highlight the active item and un-highlight the previous one
2094
  container.addClass('ws_active');
2095
  container.siblings('.ws_active').removeClass('ws_active');
2096
+ if (container.hasClass('ws_menu')) {
2097
  //Show/hide the appropriate submenu
2098
  if ( currentVisibleSubmenu ){
2099
  currentVisibleSubmenu.hide();
2100
  }
2101
+ currentVisibleSubmenu = $('#' + container.data('submenu_id')).show();
2102
+
2103
+ updateSubmenuBoxHeight(container);
2104
+
2105
+ currentVisibleSubmenu.closest('.ws_main_container')
2106
+ .find('.ws_toolbar .ws_delete_menu_button')
2107
+ .toggleClass('ws_button_disabled', !canDeleteItem(getSelectedSubmenuItem()));
2108
  }
2109
+
2110
+ //Make the "delete" button appear disabled if you can't delete this item.
2111
+ container.closest('.ws_main_container')
2112
+ .find('.ws_toolbar .ws_delete_menu_button')
2113
+ .toggleClass('ws_button_disabled', !canDeleteItem(container));
2114
  }));
2115
 
2116
+ function updateSubmenuBoxHeight(selectedMenu) {
2117
+ //Make the submenu box tall enough to reach the selected item.
2118
+ //This prevents the menu tip (if any) from floating in empty space.
2119
+ if (selectedMenu.hasClass('ws_menu_separator')) {
2120
+ submenuBox.css('min-height', '');
2121
+ } else {
2122
+ var menuTipHeight = 30,
2123
+ empiricalExtraHeight = 4,
2124
+ verticalBoxOffset = (submenuBox.offset().top - mainMenuBox.offset().top),
2125
+ minSubmenuHeight = (selectedMenu.offset().top - mainMenuBox.offset().top)
2126
+ - verticalBoxOffset
2127
+ + menuTipHeight - submenuDropZone.outerHeight() + empiricalExtraHeight;
2128
+ minSubmenuHeight = Math.max(minSubmenuHeight, 0);
2129
+ submenuBox.css('min-height', minSubmenuHeight);
2130
+ }
2131
+ }
2132
+
2133
+ AmeEditorApi.updateSubmenuBoxHeight = updateSubmenuBoxHeight;
2134
+
2135
+ //Show a notification icon next to the "Permissions" field when the menu item supports extended permissions.
2136
+ function updateExtPermissionsIndicator(container, menuItem) {
2137
+ var extPermissions = AmeItemAccessEditor.detectExtPermissions(AmeEditorApi.getItemDisplayUrl(menuItem)),
2138
+ fieldTitle = container.find('.ws_edit_field-access_level .ws_field_label_text'),
2139
+ indicator = fieldTitle.find('.ws_ext_permissions_indicator');
2140
+
2141
+ if (wsEditorData.wsMenuEditorPro && (extPermissions !== null)) {
2142
+ if (indicator.length < 1) {
2143
+ indicator = $('<div class="dashicons dashicons-info ws_ext_permissions_indicator"></div>');
2144
+ fieldTitle.append(" ").append(indicator);
2145
+ }
2146
+ //Idea: Change the icon based on the kind of permissions available (post type, tags, etc).
2147
+ indicator.show().data('ext_permissions', extPermissions);
2148
+ } else {
2149
+ indicator.hide();
2150
+ }
2151
+ }
2152
+
2153
+ menuEditorNode.on('adminMenuEditor:fieldChange', function(event, menuItem, fieldName) {
2154
+ if ((fieldName === 'template_id') || (fieldName === 'file')) {
2155
+ updateExtPermissionsIndicator($(event.target), menuItem);
2156
+ }
2157
+ });
2158
+
2159
+ //Show/hide a menu's properties
2160
+ menuEditorNode.on('click', '.ws_edit_link', (function (event) {
2161
+ event.preventDefault();
2162
+
2163
+ var container = $(this).parents('.ws_container').first();
2164
  var box = container.find('.ws_editbox');
2165
 
2166
  //For performance, the property editors for each menu are only created
2167
  //when the user tries to access access them for the first time.
2168
  if ( !container.data('field_editors_created') ){
2169
+ var menuItem = container.data('menu_item');
2170
+ buildEditboxFields(box, menuItem, container.hasClass('ws_menu'));
2171
  container.data('field_editors_created', true);
2172
  updateItemEditor(container);
2173
+ updateExtPermissionsIndicator(container, menuItem);
2174
  }
2175
 
2176
  $(this).toggleClass('ws_edit_link_expanded');
2195
  var containerNode = field.closest('.ws_container');
2196
  var menuItem = containerNode.data('menu_item');
2197
 
2198
+ if (fieldName === 'access_level') {
2199
  //This is a pretty nasty hack.
2200
  menuItem.grant_access = {};
2201
  menuItem.extra_capability = null;
2202
+ menuItem.restrict_access_to_items = false;
2203
+ delete menuItem.had_access_before_hiding;
2204
  }
2205
 
2206
  if (itemTemplates.hasDefaultValue(menuItem.template_id, fieldName)) {
2213
 
2214
  //When a field is edited, change it's appearance if it's contents don't match the default value.
2215
  function fieldValueChange(){
2216
+ /* jshint validthis:true */
2217
  var input = $(this);
2218
  var field = input.parents('.ws_edit_field').first();
2219
  var fieldName = field.data('field_name');
2220
 
2221
+ if ((fieldName === 'access_level') || (fieldName === 'embedded_page_id')) {
2222
+ //These fields are read-only and can never be directly edited by the user.
2223
  //Ignore spurious change events.
2224
  return;
2225
  }
2255
  }
2256
 
2257
  updateItemEditor(containerNode);
2258
+ updateParentAccessUi(containerNode);
2259
+
2260
+ containerNode.trigger('adminMenuEditor:fieldChange', [menuItem, fieldName]);
2261
  }
2262
  menuEditorNode.on('click change', '.ws_field_value', fieldValueChange);
2263
 
2279
 
2280
  //Allow/forbid items in actor-specific views
2281
  menuEditorNode.on('click', 'input.ws_actor_access_checkbox', function() {
2282
+ if (selectedActor === null) {
2283
  return;
2284
  }
2285
 
2288
 
2289
  var menu = containerNode.data('menu_item');
2290
  //Ask for confirmation if the user tries to hide Dashboard -> Home.
2291
+ if ( !checked && ((menu.template_id === 'index.php>index.php') || (menu.template_id === '>index.php')) ) {
2292
  updateItemEditor(containerNode); //Resets the checkbox back to the old value.
2293
  confirmDashboardHiding(function(ok) {
2294
  if (ok) {
2307
  * (And it violates SRP in a particularly egregious manner.)
2308
  *
2309
  * @param containerNode
2310
+ * @param {String|Object.<String, Boolean>} actor
2311
+ * @param {Boolean} [allowAccess]
2312
  */
2313
  function setActorAccessForTreeAndUpdateUi(containerNode, actor, allowAccess) {
2314
  setActorAccess(containerNode, actor, allowAccess);
2383
  Access editor dialog
2384
  *************************************************************************/
2385
 
2386
+ AmeItemAccessEditor.setup({
2387
+ api: AmeEditorApi,
2388
+ actors: wsEditorData.actors,
2389
+ postTypes: wsEditorData.postTypes,
2390
+ taxonomies: wsEditorData.taxonomies,
2391
+ lodash: _,
2392
+ isPro: wsEditorData.wsMenuEditorPro,
2393
+
2394
+ save: function(menuItem, containerNode, settings) {
2395
+ //Save the new settings.
2396
+ menuItem.extra_capability = settings.extraCapability;
2397
+ menuItem.grant_access = settings.grantAccess;
2398
+ menuItem.restrict_access_to_items = settings.restrictAccessToItems;
2399
+
2400
+ //Save granted capabilities.
2401
+ var newlyDisabledCaps = {};
2402
+ _.forEach(settings.grantedCapabilities, function(capabilities, actor) {
2403
+ _.forEach(capabilities, function(grant, capability) {
2404
+ if (!_.isArray(grant)) {
2405
+ grant = [grant, null, null];
2406
+ }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2407
 
2408
+ AmeCapabilityManager.setCap(actor, capability, grant[0], grant[1], grant[2]);
 
 
 
 
 
 
 
 
2409
 
2410
+ if (!grant[0]) {
2411
+ if (!newlyDisabledCaps.hasOwnProperty(capability)) {
2412
+ newlyDisabledCaps[capability] = [];
2413
+ }
2414
+ newlyDisabledCaps[capability].push(actor);
2415
+ }
2416
+ });
2417
+ });
2418
 
2419
+ AmeEditorApi.forEachMenuItem(function(menuItem, containerNode) {
2420
+ //When the user unchecks a capability, uncheck ALL menu items associated with that capability.
2421
+ //Anything less won't actually get rid of the capability as enabled menus auto-grant req. caps.
2422
+ var requiredCap = getFieldValue(menuItem, 'access_level');
2423
+ if (newlyDisabledCaps.hasOwnProperty(requiredCap)) {
2424
+ //It's enough to remove custom "allow" settings. The rest happens automatically - items that
2425
+ //have no custom per-role settings use capability checks.
2426
+ _.forEach(newlyDisabledCaps[requiredCap], function(actor) {
2427
+ if (_.get(menuItem.grant_access, actor) === true) {
2428
+ delete menuItem.grant_access[actor];
2429
+ }
2430
+ });
2431
+ }
2432
 
2433
+ //Due to changed caps and cascading submenu overrides, changes to one item's permissions
2434
+ //can affect other items. Lets just update all items.
2435
+ updateActorAccessUi(containerNode);
2436
+ });
 
2437
 
2438
+ //Refresh the UI.
2439
+ updateItemEditor(containerNode);
 
 
 
 
 
2440
  }
 
 
2441
  });
2442
 
2443
+ menuEditorNode.on('click', '.ws_launch_access_editor', function() {
2444
+ var containerNode = $(this).parents('.ws_container').first();
2445
+ var menuItem = containerNode.data('menu_item');
 
2446
 
2447
+ AmeItemAccessEditor.open({
2448
+ menuItem: menuItem,
2449
+ containerNode: containerNode,
2450
+ selectedActor: selectedActor,
2451
+ itemHasSubmenus: (!!(containerNode.data('submenu_id')) &&
2452
+ $('#' + containerNode.data('submenu_id')).find('.ws_item').length > 0)
 
 
 
2453
  });
 
 
 
 
2454
  });
2455
 
2456
  /***************************************************************************
2472
 
2473
  //Show/hide the capability drop-down list when the trigger button is clicked
2474
  $('#ws_trigger_capability_dropdown').on('mousedown click', onDropdownTriggerClicked);
2475
+ menuEditorNode.on('mousedown click', '.ws_cap_selector_trigger', onDropdownTriggerClicked);
2476
 
2477
  function onDropdownTriggerClicked(event){
2478
+ /* jshint validthis:true */
2479
  var inputBox = null;
2480
  var button = $(this);
2481
 
2482
  //Find the input associated with the button that was clicked.
2483
+ if ( button.attr('id') === 'ws_trigger_capability_dropdown' ) {
2484
  inputBox = $('#ws_extra_capability');
2485
  } else {
2486
  inputBox = button.closest('.ws_edit_field').find('.ws_field_value').first();
2488
 
2489
  //If the user clicks the same button again while the dropdown is already visible,
2490
  //ignore the click. The dropdown will be hidden by its "blur" handler.
2491
+ if (event.type === 'mousedown') {
2492
  if ( capSelectorDropdown.is(':visible') && inputBox.is(currentDropdownOwner) ) {
2493
  isDropdownBeingHidden = true;
2494
  }
2502
  //the dropdown to be properly focused when displaying it in a dialog, we must make it
2503
  //a child of the dialog's DOM node (and vice versa when it's not in a dialog).
2504
  var parentContainer = $(this).closest('.ui-dialog, #ws_menu_editor');
2505
+ if ((parentContainer.length > 0) && (capSelectorDropdown.closest(parentContainer).length === 0)) {
2506
  var oldHeight = capSelectorDropdown.height(); //Height seems to reset when moving to a new parent.
2507
  capSelectorDropdown.detach().appendTo(parentContainer).height(oldHeight);
2508
  }
2529
 
2530
  //Also show it when the user presses the down arrow in the input field (doesn't work in Opera).
2531
  $('#ws_extra_capability').bind('keyup', function(event){
2532
+ if ( event.which === 40 ){
2533
  $('#ws_trigger_capability_dropdown').click();
2534
  }
2535
  });
2538
  var dropdownNodes = $('.ws_dropdown');
2539
 
2540
  // Hide capability drop-down when it loses focus.
2541
+ dropdownNodes.blur(function(){
 
2542
  capSelectorDropdown.hide();
2543
  });
2544
 
2545
  dropdownNodes.keydown(function(event){
2546
 
2547
  //Hide it when the user presses Esc
2548
+ if ( event.which === 27 ){
2549
  capSelectorDropdown.hide();
2550
  if (currentDropdownOwner) {
2551
  currentDropdownOwner.focus();
2552
  }
2553
 
2554
  //Select an item & hide the list when the user presses Enter or Tab
2555
+ } else if ( (event.which === 13) || (event.which === 9) ){
2556
  capSelectorDropdown.hide();
2557
 
2558
  if (currentDropdownOwner) {
2568
 
2569
  //Eat Tab keys to prevent focus theft. Required to make the "select item on Tab" thing work.
2570
  dropdownNodes.keyup(function(event){
2571
+ if ( event.which === 9 ){
2572
  event.preventDefault();
2573
  }
2574
  });
2591
  }
2592
 
2593
  var option = event.target;
2594
+ if ( (typeof option.selected !== 'undefined') && !option.selected && option.value ){
2595
  option.selected = true;
2596
  }
2597
  });
2645
 
2646
  currentIconButton = button;
2647
 
2648
+ var containerNode = currentIconButton.closest('.ws_container');
2649
+ var menuItem = containerNode.data('menu_item');
2650
  var cssClass = getFieldValue(menuItem, 'css_class', '');
2651
+ var iconUrl = getFieldValue(menuItem, 'icon_url', '', containerNode);
2652
 
2653
  var customImageOption = iconSelector.find('.ws_custom_image_icon').hide();
2654
 
2730
  frame.on( 'select', function() {
2731
  //Grab the selected attachment.
2732
  var attachment = frame.state().get('selection').first();
2733
+ //TODO: Warn the user if the image exceeds 20x20 pixels.
2734
 
2735
  //Set the menu icon to the attachment URL.
2736
  if (currentIconButton) {
2779
  if (
2780
  !iconSelector.is(event.target)
2781
  && iconSelector.has(event.target).length === 0
2782
+ && $(event.target).closest('.ws_select_icon').length === 0
2783
  ) {
2784
  iconSelector.hide();
2785
  currentIconButton = null;
2787
  });
2788
 
2789
 
2790
+ /*************************************************************************
2791
+ Embedded page selector
2792
+ *************************************************************************/
2793
+
2794
+ var pageSelector = $('#ws_embedded_page_selector'),
2795
+ pageListBox = pageSelector.find('#ws_current_site_pages'),
2796
+ currentPageSelectorButton = null, //The last page dropdown button that was clicked.
2797
+ isPageListPopulated = false,
2798
+ isPageRequestInProgress = false;
2799
+
2800
+ pageSelector.tabs({
2801
+ heightStyle: 'auto',
2802
+ hide: false,
2803
+ show: false
2804
+ });
2805
+ //Hack. The selector needs to be hidden by default, but it can't start out as "display: none" because that makes
2806
+ //jQuery miscalculate tab heights. So we put it in a hidden container, then hide it on load and move it elsewhere.
2807
+ pageSelector.hide().appendTo(menuEditorNode);
2808
+
2809
+ /**
2810
+ * Update the page selector with the current menu item's settings.
2811
+ */
2812
+ function updatePageSelector() {
2813
+ var menuItem, selectedPageId = 0, selectedBlogId = 1;
2814
+ if ( currentPageSelectorButton ) {
2815
+ menuItem = currentPageSelectorButton.closest('.ws_container').data('menu_item');
2816
+ selectedPageId = parseInt(getFieldValue(menuItem, 'embedded_page_id', 0), 10);
2817
+ selectedBlogId = parseInt(getFieldValue(menuItem, 'embedded_page_blog_id', 1), 10);
2818
+ }
2819
+
2820
+ if (selectedPageId === 0) {
2821
+ pageListBox.val(null);
2822
+ } else {
2823
+ var optionValue = selectedBlogId + '_' + selectedPageId;
2824
+ pageListBox.val(optionValue);
2825
+ if ( pageListBox.val() !== optionValue ) {
2826
+ pageListBox.val('custom');
2827
+ }
2828
+ }
2829
+
2830
+ pageSelector.find('#ws_embedded_page_id').val(selectedPageId);
2831
+ pageSelector.find('#ws_embedded_page_blog_id').val(selectedBlogId);
2832
+ }
2833
+
2834
+ menuEditorNode.on('click', '.ws_embedded_page_selector_trigger', function(event) {
2835
+ var thisButton = $(this),
2836
+ thisInput = thisButton.closest('.ws_edit_field').find('input.ws_field_value:first');
2837
+
2838
+ //Clicking the same button a second time hides the page selector.
2839
+ if (thisButton.is(currentPageSelectorButton) && pageSelector.is(':visible')) {
2840
+ pageSelector.hide();
2841
+ //noinspection JSUnusedAssignment
2842
+ currentPageSelectorButton = null;
2843
+ return;
2844
+ }
2845
+
2846
+ currentPageSelectorButton = thisButton;
2847
+ pageSelector.show();
2848
+ pageSelector.position({
2849
+ my: 'left top',
2850
+ at: 'left bottom',
2851
+ of: thisInput
2852
+ });
2853
+
2854
+ event.stopPropagation();
2855
+
2856
+ if (!isPageListPopulated && !isPageRequestInProgress) {
2857
+ isPageRequestInProgress = true;
2858
+
2859
+ var pageList = pageSelector.find('#ws_current_site_pages');
2860
+ pageList.prop('readonly', true);
2861
+
2862
+ $.getJSON(
2863
+ wsEditorData.adminAjaxUrl,
2864
+ {
2865
+ 'action' : 'ws_ame_get_pages',
2866
+ '_ajax_nonce' : wsEditorData.getPagesNonce
2867
+ },
2868
+ function(data){
2869
+ isPageRequestInProgress = false;
2870
+ pageList.prop('readonly', false);
2871
+
2872
+ if (typeof data.error !== 'undefined'){
2873
+ alert(data.error);
2874
+ return;
2875
+ } else if ((typeof data !== 'object') || (typeof data.length === 'undefined')) {
2876
+ alert('Error: Could not retrieve a list of pages. Unexpected response from the server.');
2877
+ return;
2878
+ }
2879
+
2880
+ //An alphabetised list is easier to scan visually.
2881
+ var pages = data.sort(function(a, b) {
2882
+ return a.post_title.localeCompare(b.post_title);
2883
+ });
2884
+
2885
+ //Populate the select box.
2886
+ pageList.empty();
2887
+ $.each(pages, function(index, page) {
2888
+ pageList.append($('<option>', {
2889
+ val: page.blog_id + '_' + page.post_id,
2890
+ text: page.post_title
2891
+ }));
2892
+ });
2893
+
2894
+ //Add a "custom" option. Select it when the current setting doesn't match any of the listed pages.
2895
+ pageList.prepend($('<option>', {
2896
+ val: 'custom',
2897
+ text: '< Custom >'
2898
+ }));
2899
+
2900
+ updatePageSelector();
2901
+ isPageListPopulated = true;
2902
+ },
2903
+ 'json'
2904
+ );
2905
+
2906
+ }
2907
+
2908
+ updatePageSelector();
2909
+
2910
+ //Open the "Pages" tab by default, or the "Custom" tab if that's what's selected in the list box.
2911
+ //The updatePageSelector call above sets the pageListBox value.
2912
+ pageSelector.tabs('option', 'active', (pageListBox.val() === 'custom') ? 1 : 0);
2913
+ });
2914
+
2915
+ //Hide the page selector if the user clicks outside of it and outside the current button.
2916
+ $(document).on('mouseup', function(event) {
2917
+ if ( !pageSelector.is(':visible') ) {
2918
+ return;
2919
+ }
2920
+
2921
+ var target = $(event.target);
2922
+ var isOutsideSelector = target.closest(pageSelector).length === 0;
2923
+ var isOutsideButton = currentPageSelectorButton && (target.closest(currentPageSelectorButton).length === 0);
2924
+
2925
+ if (isOutsideSelector && isOutsideButton) {
2926
+ pageSelector.hide();
2927
+ currentPageSelectorButton = null;
2928
+ }
2929
+ });
2930
+
2931
+ function setEmbeddedPageForCurrentItem(newPageId, newBlogId, title) {
2932
+ if ( currentPageSelectorButton ) {
2933
+ var containerNode = currentPageSelectorButton.closest('.ws_container'),
2934
+ menuItem = containerNode.data('menu_item');
2935
+
2936
+ menuItem.embedded_page_id = newPageId;
2937
+ menuItem.embedded_page_blog_id = newBlogId;
2938
+
2939
+ if (typeof title === 'string') {
2940
+ //Store the page title for later. It will be displayed in the text box.
2941
+ AmePageTitles.add(newPageId, newBlogId, title);
2942
+ }
2943
+
2944
+ updateItemEditor(containerNode);
2945
+ }
2946
+ }
2947
+
2948
+ //When the user chooses a page from the list, update the menu item and hide the dropdown.
2949
+ pageListBox.on('change', function() {
2950
+ var selection = pageListBox.val();
2951
+ if (selection === 'custom') { // jshint ignore:line
2952
+ //Do nothing. Presumably, the user will now switch to the "Custom" tab and enter new settings.
2953
+ //If they don't do that and just close the dropdown, we keep the previous settings.
2954
+ } else if ( currentPageSelectorButton ) {
2955
+ //Set the new page and blog IDs. The expected value format is "blogid_postid".
2956
+ var parts = selection.split('_'),
2957
+ newBlogId = parseInt(parts[0], 10),
2958
+ newPageId = parseInt(parts[1], 10);
2959
+
2960
+ pageSelector.hide();
2961
+ setEmbeddedPageForCurrentItem(newPageId, newBlogId, pageListBox.children(':selected').text());
2962
+ }
2963
+ });
2964
+
2965
+ pageSelector.find('#ws_custom_embedded_page_tab form').on('submit', function(event) {
2966
+ event.preventDefault();
2967
+
2968
+ var newPageId = parseInt(pageSelector.find('#ws_embedded_page_id').val(), 10),
2969
+ newBlogId = parseInt(pageSelector.find('#ws_embedded_page_blog_id').val(), 10);
2970
+
2971
+ if (isNaN(newPageId) || (newPageId < 0)) {
2972
+ alert('Error: Invalid post ID');
2973
+ } else if (isNaN(newBlogId) || (newBlogId < 0)) {
2974
+ alert('Error: Invalid blog ID');
2975
+ } else if ( currentPageSelectorButton ) {
2976
+ pageSelector.hide();
2977
+ setEmbeddedPageForCurrentItem(newPageId, newBlogId);
2978
+ }
2979
+ });
2980
+
2981
+
2982
  /*************************************************************************
2983
  Color picker
2984
  *************************************************************************/
3024
  'menu-bubble-current-background'
3025
  ];
3026
 
3027
+ var colorPresetDropdown = $('#ame-menu-color-presets'),
3028
+ colorPresetDeleteButton = $("#ws-ame-delete-color-preset"),
3029
+ areColorChangesIgnored = false;
3030
+
3031
  //Show only the primary color settings by default.
3032
  var showAdvancedColors = false;
3033
  $('#ws-ame-show-advanced-colors').click(function() {
3041
  menuEditorNode.on('click', '.ws_open_color_editor, .ws_color_scheme_display', function() {
3042
  //Initializing the color pickers takes a while, so we only do it when needed instead of on document ready.
3043
  if ( !colorPickersInitialized ) {
3044
+ menuColorDialog.find('.ame-color-picker').wpColorPicker({
3045
+ //Deselect the current preset when the user changes any of the color options.
3046
+ change: deselectPresetOnColorChange,
3047
+ clear: deselectPresetOnColorChange
3048
+ });
3049
  colorPickersInitialized = true;
3050
  }
3051
 
3056
  colorDialogState.menuItem = menuItem;
3057
 
3058
  var colors = getFieldValue(menuItem, 'colors', {}) || {};
3059
+ var customColorCount = displayColorSettingsInDialog(colors);
3060
+ if ( customColorCount > 0 ) {
3061
+ menuItem.colors = colors;
3062
+ } else {
3063
+ menuItem.colors = null;
3064
+ }
3065
+
3066
+ //Populate presets and deselect the previously selected option.
3067
+ colorPresetDropdown.val('');
3068
+ if (!wasPresetDropdownPopulated) {
3069
+ populatePresetDropdown();
3070
+ wasPresetDropdownPopulated = true;
3071
+ }
3072
+
3073
+ //Add menu title to the dialog caption.
3074
+ var title = getFieldValue(menuItem, 'menu_title', null);
3075
+ menuColorDialog.dialog(
3076
+ 'option',
3077
+ 'title',
3078
+ title ? ('Colors: ' + title.substring(0, 30)) : 'Colors'
3079
+ );
3080
+ menuColorDialog.dialog('open');
3081
+ });
3082
+
3083
+ function getColorSettingsFromDialog() {
3084
+ var colors = {}, colorCount = 0;
3085
+
3086
+ for (var i = 0; i < menuColorVariables.length; i++) {
3087
+ var name = menuColorVariables[i];
3088
+ var value = $('#ame-color-' + name).val();
3089
+ if (value) {
3090
+ colors[name] = value;
3091
+ colorCount++;
3092
+ }
3093
+ }
3094
+
3095
+ if (colorCount > 0) {
3096
+ return colors;
3097
+ } else {
3098
+ return null;
3099
+ }
3100
+ }
3101
+
3102
+ function displayColorSettingsInDialog(colors) {
3103
+ //noinspection JSUnusedAssignment
3104
+ areColorChangesIgnored = true;
3105
  var customColorCount = 0;
3106
+
3107
  for (var i = 0; i < menuColorVariables.length; i++) {
3108
  var name = menuColorVariables[i];
3109
  var value = colors.hasOwnProperty(name) ? colors[name] : false;
3110
 
3111
  if ( value ) {
3112
  $('#ame-color-' + name).wpColorPicker('color', value);
3113
+ customColorCount++;
3114
+ } else {
3115
+ $('#ame-color-' + name).closest('.wp-picker-container').find('.wp-picker-clear').click();
3116
+ }
 
 
 
 
 
 
3117
  }
3118
 
3119
+ areColorChangesIgnored = false;
3120
+ return customColorCount;
3121
+ }
 
 
 
 
 
 
3122
 
3123
  //The "Save Changes" button in the color dialog.
3124
  $('#ws-ame-save-menu-colors').click(function() {
3127
  return;
3128
  }
3129
  var menuItem = colorDialogState.menuItem;
3130
+ menuItem.colors = getColorSettingsFromDialog();
3131
+ updateItemEditor(colorDialogState.containerNode);
3132
 
3133
+ colorDialogState.containerNode = null;
3134
+ colorDialogState.menuItem = null;
3135
+ });
3136
+
3137
+ //The "Apply to All" button in the same dialog.
3138
+ $('#ws-ame-apply-colors-to-all').click(function() {
3139
+ if (!confirm('Apply these color settings to ALL top level menus?')) {
3140
+ return;
3141
  }
3142
 
3143
+ var newColors = getColorSettingsFromDialog();
3144
+ $('#ws_menu_box').find('.ws_menu').each(function() {
3145
+ var containerNode = $(this),
3146
+ menuItem = containerNode.data('menu_item');
3147
+ if (!menuItem.separator) {
3148
+ menuItem.colors = newColors;
3149
+ updateItemEditor(containerNode);
3150
+ }
3151
+ });
3152
 
3153
+ menuColorDialog.dialog('close');
3154
  colorDialogState.containerNode = null;
3155
  colorDialogState.menuItem = null;
3156
  });
3157
 
3158
+ function addColorPreset(name, colors) {
3159
+ colorPresets[name] = colors;
3160
+ populatePresetDropdown();
3161
+ colorPresetDropdown.val(name);
3162
+ colorPresetDeleteButton.removeClass('hidden');
3163
+ }
3164
+
3165
+ function deleteColorPreset(name) {
3166
+ delete colorPresets[name];
3167
+ populatePresetDropdown();
3168
+ colorPresetDropdown.val('');
3169
+ colorPresetDeleteButton.addClass('hidden');
3170
+ }
3171
+
3172
+ function populatePresetDropdown() {
3173
+ var separator = colorPresetDropdown.find('#ame-color-preset-separator');
3174
+
3175
+ //Delete the old options, but keep the "save preset" option and so on.
3176
+ colorPresetDropdown.find('option').not('.ame-meta-option').remove();
3177
+
3178
+ //Sort presets alphabetically.
3179
+ var presetNames = $.map(colorPresets, function(unused, name) {
3180
+ return name;
3181
+ }).sort(function(a, b) {
3182
+ return a.localeCompare(b);
3183
+ });
3184
+
3185
+ //Add them all to the dropdown.
3186
+ var newOptions = jQuery([]);
3187
+ $.each(presetNames, function(unused, name) {
3188
+ newOptions = newOptions.add($('<option>', {
3189
+ val: name,
3190
+ text: name
3191
+ }));
3192
+ });
3193
+ newOptions.insertBefore(separator);
3194
+ }
3195
+
3196
+ function deselectPresetOnColorChange() {
3197
+ //Most jQuery widgets don't trigger change events when you update them via JavaScript,
3198
+ //but apparently wpColorPicker does. We want to ignore those superfluous events.
3199
+ if (!areColorChangesIgnored && (colorPresetDropdown.val() !== '')) {
3200
+ colorPresetDropdown.val('');
3201
+ }
3202
+ }
3203
+
3204
+ colorPresetDropdown.change(function() {
3205
+ var dropdown = $(this),
3206
+ presetName = dropdown.val();
3207
+
3208
+ colorPresetDeleteButton.toggleClass('hidden', (presetName === '') || (presetName === '[save_preset]'));
3209
+
3210
+ if ((presetName === '[save_preset]') && menuColorDialog.dialog('isOpen')) {
3211
+ //Create a new preset.
3212
+ var colors = getColorSettingsFromDialog();
3213
+ if (colors === null) {
3214
+ dropdown.val('');
3215
+ alert('Error: No colors selected');
3216
+ return;
3217
+ }
3218
+
3219
+ var newPresetName = window.prompt('New preset name:', '');
3220
+ if ((newPresetName === null) || (jsTrim(newPresetName) === '')) {
3221
+ dropdown.val('');
3222
+ return;
3223
+ }
3224
+
3225
+ addColorPreset(newPresetName, colors);
3226
+ } else if (presetName !== '') {
3227
+ //Apply the selected preset.
3228
+ var preset = colorPresets[presetName];
3229
+ displayColorSettingsInDialog(preset);
3230
+ }
3231
+ });
3232
+
3233
+ colorPresetDeleteButton.click(function() {
3234
+ var presetName = $('#ame-menu-color-presets').val();
3235
+ if ((presetName === '[save_preset]') || (presetName === '') || (presetName === null)) {
3236
+ return false;
3237
+ }
3238
+ if (!confirm('Are you sure you want to delete the preset "' + presetName + '"?')) {
3239
+ return false;
3240
+ }
3241
+
3242
+ deleteColorPreset(presetName);
3243
+ return false;
3244
+ });
3245
+
3246
  /*************************************************************************
3247
  Menu toolbar buttons
3248
  *************************************************************************/
3251
  }
3252
 
3253
  //Show/Hide menu
3254
+ $('#ws_hide_menu').click(function (event) {
3255
+ event.preventDefault();
3256
+
3257
  //Get the selected menu
3258
  var selection = getSelectedMenu();
3259
+ if (!selection.length) {
3260
+ return;
3261
+ }
3262
 
3263
+ toggleItemHiddenFlag(selection);
3264
+ });
3265
+
3266
+ //Hide a menu and deny access.
3267
+ menuEditorNode.find('.ws_toolbar').on('click', '.ws_hide_and_deny_button', function() {
3268
+ var $box = $(this).closest('.ws_main_container').find('.ws_box'),
3269
+ selection = $box.is('#ws_menu_box') ? getSelectedMenu() : getSelectedSubmenuItem();
3270
+ if (selection.length < 1) {
3271
+ return;
3272
+ }
3273
+
3274
+ function objectFillKeys(keys, value) {
3275
+ var result = {};
3276
+ _.forEach(keys, function(key) {
3277
+ result[key] = value;
3278
+ });
3279
+ return result;
3280
+ }
3281
+
3282
+ if (selectedActor === null) {
3283
+ //Hide from everyone except Super Admin and the current user.
3284
+ var menuItem = selection.data('menu_item'),
3285
+ validActors = _.keys(wsEditorData.actors),
3286
+ alwaysAllowedActors = _.intersection(
3287
+ ['special:super_admin', 'user:' + wsEditorData.currentUserLogin],
3288
+ validActors
3289
+ ),
3290
+ victims = _.difference(validActors, alwaysAllowedActors),
3291
+ shouldHide;
3292
+
3293
+ //First, lets check who has access. Maybe this item is already hidden from the victims.
3294
+ shouldHide = _.some(victims, _.curry(actorCanAccessMenu, 2)(menuItem));
3295
+
3296
+ var keepEnabled = objectFillKeys(alwaysAllowedActors, true),
3297
+ hideAllExceptAllowed = _.assign(objectFillKeys(victims, false), keepEnabled);
3298
+
3299
+ walkMenuTree(selection, function(container, item) {
3300
+ var newAccess;
3301
+ if (shouldHide) {
3302
+ //Yay, hide it now!
3303
+ newAccess = hideAllExceptAllowed;
3304
+ //Only update had_access_before_hiding if this item isn't hidden yet or the field is missing.
3305
+ //We don't want to double-hide an item.
3306
+ var actorsWithAccess = _.filter(victims, function(actor) {
3307
+ return actorCanAccessMenu(item, actor);
3308
+ });
3309
+ if ((actorsWithAccess.length) > 0 || _.isEmpty(_.get(item, 'had_access_before_hiding', null))) {
3310
+ item.had_access_before_hiding = actorsWithAccess;
3311
+ }
3312
+ } else {
3313
+ //Give back access to the roles and users who previously had access.
3314
+ //Careful, don't give access to roles that no longer exist.
3315
+ var actorsWhoHadAccess = _.get(item, 'had_access_before_hiding', []) || [];
3316
+ actorsWhoHadAccess = _.intersection(actorsWhoHadAccess, validActors);
3317
+
3318
+ newAccess = _.assign(objectFillKeys(actorsWhoHadAccess, true), keepEnabled);
3319
+ delete item.had_access_before_hiding;
3320
+ }
3321
+
3322
+ setActorAccess(container, newAccess);
3323
+ updateItemEditor(container);
3324
+ });
3325
+
3326
+ } else {
3327
+ //Just toggle the checkbox.
3328
+ selection.find('input.ws_actor_access_checkbox').click();
3329
+ }
3330
  });
3331
 
3332
  //Delete error dialog. It shows up when the user tries to delete one of the default menus.
3380
  applyCallbackRecursively(selection, function(menuItem) {
3381
  menuItem.extra_capability = adminOnlyCap;
3382
  });
3383
+ alert('The "required capability" field was set to "' + adminOnlyCap + '".');
3384
  }
3385
  };
3386
 
3399
  });
3400
 
3401
  /**
3402
+ * Check if it's possible to delete a menu item.
 
3403
  *
3404
+ * @param {jQuery} containerNode
3405
+ * @returns {boolean}
3406
  */
3407
+ function canDeleteItem(containerNode) {
3408
+ if (!containerNode || (containerNode.length < 1)) {
3409
+ return false;
3410
+ }
3411
+
3412
+ var menuItem = containerNode.data('menu_item');
3413
  var isDefaultItem =
3414
  ( menuItem.template_id !== '')
3415
+ && ( menuItem.template_id !== wsEditorData.unclickableTemplateId)
3416
+ && ( menuItem.template_id !== wsEditorData.embeddedPageTemplateId)
3417
+ && (!menuItem.separator);
3418
 
3419
  var otherCopiesExist = false;
 
 
3420
  if (isDefaultItem) {
3421
  //Check if there are any other menus with the same template ID.
3422
  $('#ws_menu_editor').find('.ws_container').each(function() {
3423
  var otherItem = $(this).data('menu_item');
3424
+ if ((menuItem !== otherItem) && (menuItem.template_id === otherItem.template_id)) {
3425
  otherCopiesExist = true;
3426
  return false;
3427
  }
3429
  });
3430
  }
3431
 
3432
+ return (!isDefaultItem || otherCopiesExist);
3433
+ }
3434
+
3435
+ /**
3436
+ * Attempt to delete a menu item. Will check if the item can actually be deleted and ask the user for confirmation.
3437
+ * UI callback.
3438
+ *
3439
+ * @param {jQuery} selection The selected menu item (DOM node).
3440
+ */
3441
+ function tryDeleteItem(selection) {
3442
+ var menuItem = selection.data('menu_item');
3443
+ var shouldDelete = false;
3444
+
3445
+ if (canDeleteItem(selection)) {
3446
  //Custom and duplicate items can be deleted normally.
3447
  shouldDelete = confirm('Delete this menu?');
3448
  } else {
3449
  //Non-custom items can not be deleted, but they can be hidden. Ask the user if they want to do that.
3450
  menuDeletionDialog.find('#ws-ame-menu-type-desc').text(
3451
+ _.get(menuItem.defaults, 'is_plugin_page') ? 'an item added by another plugin' : 'a built-in menu item'
3452
  );
3453
  menuDeletionDialog.data('selected_menu', selection);
3454
 
3482
  }
3483
 
3484
  //Delete menu
3485
+ $('#ws_delete_menu').click(function (event) {
3486
+ event.preventDefault();
3487
+
3488
  //Get the selected menu
3489
  var selection = getSelectedMenu();
3490
+ if (!selection.length) {
3491
+ return;
3492
+ }
3493
 
3494
  tryDeleteItem(selection);
3495
  });
3496
 
3497
  //Copy menu
3498
+ $('#ws_copy_menu').click(function (event) {
3499
+ event.preventDefault();
3500
+
3501
  //Get the selected menu
3502
  var selection = $('#ws_menu_box').find('.ws_active');
3503
+ if (!selection.length) {
3504
+ return;
3505
+ }
3506
 
3507
  //Store a copy of the current menu state in clipboard
3508
  menu_in_clipboard = readItemState(selection);
3509
  });
3510
 
3511
  //Cut menu
3512
+ $('#ws_cut_menu').click(function (event) {
3513
+ event.preventDefault();
3514
+
3515
  //Get the selected menu
3516
  var selection = $('#ws_menu_box').find('.ws_active');
3517
+ if (!selection.length) {
3518
+ return;
3519
+ }
3520
 
3521
  //Store a copy of the current menu state in clipboard
3522
  menu_in_clipboard = readItemState(selection);
3553
  }
3554
  }
3555
 
3556
+ $('#ws_paste_menu').click(function (event) {
3557
+ event.preventDefault();
3558
+
3559
  //Check if anything has been copied/cut
3560
+ if (!menu_in_clipboard) {
3561
+ return;
3562
+ }
3563
 
3564
  var menu = $.extend(true, {}, menu_in_clipboard);
3565
 
3570
  });
3571
 
3572
  //New menu
3573
+ $('#ws_new_menu').click(function (event) {
3574
+ event.preventDefault();
3575
+
3576
  ws_paste_count++;
3577
 
3578
  //The new menu starts out rather bare
3587
  });
3588
 
3589
  //Make it accessible only to the current actor if one is selected.
3590
+ if (selectedActor !== null) {
3591
  denyAccessForAllExcept(menu, selectedActor);
3592
  }
3593
 
3600
  });
3601
 
3602
  //New separator
3603
+ $('#ws_new_separator, #ws_new_submenu_separator').click(function (event) {
3604
+ event.preventDefault();
3605
+
3606
  ws_paste_count++;
3607
 
3608
  //The new menu starts out rather bare
3620
  }
3621
  });
3622
 
3623
+ if ( $(this).attr('id').indexOf('submenu') === -1 ) {
3624
  //Insert in the top-level menu.
3625
  var selection = $('#ws_menu_box').find('.ws_active');
3626
  outputTopMenu(menu, (selection.length > 0) ? selection : null);
3631
  });
3632
 
3633
  //Toggle all menus for the currently selected actor
3634
+ $('#ws_toggle_all_menus').click(function(event) {
3635
+ event.preventDefault();
3636
+
3637
+ if ( selectedActor === null ) {
3638
  alert("This button enables/disables all menus for the selected role. To use it, click a role and then click this button again.");
3639
  return;
3640
  }
3657
  draggable: false
3658
  });
3659
 
 
3660
  var sourceActorList = $('#ame-copy-source-actor'), destinationActorList = $('#ame-copy-destination-actor');
 
 
 
 
 
3661
 
3662
  //The "Copy permissions" toolbar button.
3663
+ $('#ws_copy_role_permissions').click(function(event) {
3664
+ event.preventDefault();
3665
+
3666
+ var previousSource = sourceActorList.val();
3667
+
3668
+ //Populate source/destination lists.
3669
+ sourceActorList.find('option').not('[disabled]').remove();
3670
+ destinationActorList.find('option').not('[disabled]').remove();
3671
+ $.each(wsEditorData.actors, function(actor, name) {
3672
+ var option = $('<option>', {val: actor, text: name});
3673
+ sourceActorList.append(option);
3674
+ destinationActorList.append(option.clone());
3675
+ });
3676
+
3677
  //Pre-select the current actor as the destination.
3678
  if (selectedActor !== null) {
3679
  destinationActorList.val(selectedActor);
3680
  }
3681
+
3682
+ //Restore the previous source selection.
3683
+ if (previousSource) {
3684
+ sourceActorList.val(previousSource);
3685
+ }
3686
+ if (!sourceActorList.val()) {
3687
+ sourceActorList.find('option').first().prop('selected', true); //Fallback.
3688
+ }
3689
+
3690
  copyPermissionsDialog.dialog('open');
3691
  });
3692
 
3702
  }
3703
 
3704
  //Iterate over all menu items and copy the permissions from one actor to the other.
3705
+ var allMenuNodes = $('.ws_menu', '#ws_menu_box').add('.ws_item', submenuBox);
3706
  allMenuNodes.each(function() {
3707
  var node = $(this);
3708
  var menuItem = node.data('menu_item');
3740
  copyConfirmationButton.prop('disabled', !validInputs);
3741
  });
3742
 
3743
+ //Sort menus in ascending or descending order.
3744
+ menuEditorNode.find('.ws_toolbar').on('click', '.ws_sort_menus_button', function(event) {
3745
+ event.preventDefault();
3746
+
3747
+ var button = $(this),
3748
+ menuBox = $(this).closest('.ws_main_container').find('.ws_box').first();
3749
+
3750
+ if (menuBox.is('#ws_submenu_box')) {
3751
+ menuBox = menuBox.find('.ws_submenu:visible').first();
3752
+ }
3753
+
3754
+ if (menuBox.length > 0) {
3755
+ sortMenuItems(menuBox, button.data('sort-direction') || 'asc');
3756
+ }
3757
+ });
3758
+
3759
+ /**
3760
+ * Sort menu items by title.
3761
+ *
3762
+ * @param $menuBox A DOM node that contains multiple menu items.
3763
+ * @param {string} direction 'asc' or 'desc'
3764
+ */
3765
+ function sortMenuItems($menuBox, direction) {
3766
+ var multiplier = (direction === 'desc') ? -1 : 1,
3767
+ items = $menuBox.find('.ws_container');
3768
+
3769
+ //Separators don't have a title, but we don't want them to end up at the top of the list.
3770
+ //Instead, lets keep their position the same relative to the previous item.
3771
+ var prevItemTitle = '';
3772
+ items.each((function(){
3773
+ var item = $(this), sortValue;
3774
+ if (item.is('.ws_menu_separator')) {
3775
+ sortValue = prevItemTitle;
3776
+ } else {
3777
+ sortValue = jsTrim(item.find('.ws_item_title').text());
3778
+ prevItemTitle = sortValue;
3779
+ }
3780
+ item.data('ame-sort-value', sortValue);
3781
+ }));
3782
+
3783
+ function compareMenus(a, b){
3784
+ var aTitle = jsTrim($(a).find('.ws_item_title').text()),
3785
+ bTitle = jsTrim($(b).find('.ws_item_title').text());
3786
+
3787
+ aTitle = aTitle.toLowerCase();
3788
+ bTitle = bTitle.toLowerCase();
3789
+
3790
+ if (aTitle > bTitle) {
3791
+ return multiplier;
3792
+ } else if (aTitle < bTitle) {
3793
+ return -multiplier;
3794
+ }
3795
+ return 0;
3796
+ }
3797
+
3798
+ items.sort(compareMenus);
3799
+ }
3800
+
3801
+ //Toggle the second row of toolbar buttons.
3802
+ $('#ws_toggle_toolbar').click(function() {
3803
+ var visible = menuEditorNode.find('.ws_second_toolbar_row').toggle().is(':visible');
3804
+ $.cookie('ame-show-second-toolbar', visible ? '1' : '0', {expires: 90});
3805
+ });
3806
+
3807
 
3808
  /*************************************************************************
3809
  Item toolbar buttons
3813
  }
3814
 
3815
  //Show/Hide item
3816
+ $('#ws_hide_item').click(function (event) {
3817
+ event.preventDefault();
3818
+
3819
  //Get the selected item
3820
  var selection = getSelectedSubmenuItem();
3821
+ if (!selection.length) {
3822
+ return;
3823
+ }
3824
 
3825
  //Mark the item as hidden/visible
3826
+ toggleItemHiddenFlag(selection);
 
 
3827
  });
3828
 
3829
  //Delete item
3830
+ $('#ws_delete_item').click(function (event) {
3831
+ event.preventDefault();
3832
+
3833
  var selection = getSelectedSubmenuItem();
3834
+ if (!selection.length) {
3835
+ return;
3836
+ }
3837
 
3838
  tryDeleteItem(selection);
3839
  });
3840
 
3841
  //Copy item
3842
+ $('#ws_copy_item').click(function (event) {
3843
+ event.preventDefault();
3844
+
3845
  //Get the selected item
3846
  var selection = getSelectedSubmenuItem();
3847
+ if (!selection.length) {
3848
+ return;
3849
+ }
3850
 
3851
  //Store a copy of item state in the clipboard
3852
  menu_in_clipboard = readItemState(selection);
3853
  });
3854
 
3855
  //Cut item
3856
+ $('#ws_cut_item').click(function (event) {
3857
+ event.preventDefault();
3858
+
3859
  //Get the selected item
3860
  var selection = getSelectedSubmenuItem();
3861
+ if (!selection.length) {
3862
+ return;
3863
+ }
3864
 
3865
  //Store a copy of item state in the clipboard
3866
  menu_in_clipboard = readItemState(selection);
3904
  updateParentAccessUi(visibleSubmenu);
3905
  }
3906
 
3907
+ $('#ws_paste_item').click(function (event) {
3908
+ event.preventDefault();
3909
+
3910
  //Check if anything has been copied/cut
3911
+ if (!menu_in_clipboard) {
3912
+ return;
3913
+ }
3914
 
3915
  //You can only add separators to submenus in the Pro version.
3916
  if ( menu_in_clipboard.separator && !wsEditorData.wsMenuEditorPro ) {
3923
  });
3924
 
3925
  //New item
3926
+ $('#ws_new_item').click(function (event) {
3927
+ event.preventDefault();
3928
+
3929
  if ($('.ws_submenu:visible').length < 1) {
3930
  return; //Abort if no submenu visible
3931
  }
3942
  });
3943
 
3944
  //Make it accessible to only the currently selected actor.
3945
+ if (selectedActor !== null) {
3946
  denyAccessForAllExcept(entry, selectedActor);
3947
  }
3948
 
3949
  var menu = buildMenuItem(entry);
 
3950
 
3951
  //Insert the item into the currently open submenu.
3952
  var visibleSubmenu = $('#ws_submenu_box').find('.ws_submenu:visible');
3956
  } else {
3957
  visibleSubmenu.append(menu);
3958
  }
3959
+ updateItemEditor(menu);
3960
 
3961
  //The items's editbox is always open
3962
  menu.find('.ws_edit_link').click();
3964
  updateParentAccessUi(menu);
3965
  });
3966
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
3967
  //==============================================
3968
  // Main buttons
3969
  //==============================================
3992
  var foundItem = null;
3993
 
3994
  $.each(items, function(index, item) {
3995
+ if (item.template_id === templateId) {
3996
  foundItem = item;
3997
  return false;
3998
  }
3999
  if (item.hasOwnProperty('items') && (item.items.length > 0)) {
4000
  foundItem = findItemByTemplateId(item.items, templateId);
4001
+ if (foundItem !== null) {
4002
  return false;
4003
  }
4004
  }
4011
  //Abort the save if it would make the editor inaccessible.
4012
  if (wsEditorData.wsMenuEditorPro) {
4013
  var myMenuItem = findItemByTemplateId(tree.tree, 'options-general.php>menu_editor');
4014
+ if (myMenuItem === null) { // jshint ignore:line
4015
  //This is OK - the missing menu item will be re-inserted automatically.
4016
  } else if (!actorCanAccessMenu(myMenuItem, 'user:' + wsEditorData.currentUserLogin)) {
4017
  alert(
4027
  $('#ws_data').val(data);
4028
  $('#ws_data_length').val(data.length);
4029
  $('#ws_selected_actor').val(selectedActor === null ? '' : selectedActor);
4030
+ $('#ws_visible_users_json').val($.toJSON(wsEditorData.visibleUsers || []));
4031
  $('#ws_main_form').submit();
4032
  });
4033
 
4034
  //Load default menu - load the default WordPress menu
4035
  $('#ws_load_menu').click(function () {
4036
  if (confirm('Are you sure you want to load the default WordPress menu?')){
4037
+ loadMenuConfiguration(defaultMenu);
4038
  }
4039
  });
4040
 
4041
  //Reset menu - re-load the custom menu. Discards any changes made by user.
4042
  $('#ws_reset_menu').click(function () {
4043
  if (confirm('Undo all changes made in the current editing session?')){
4044
+ loadMenuConfiguration(customMenu);
4045
+ }
4046
+ });
4047
+
4048
+ $('#ws_toggle_editor_layout').click(function () {
4049
+ var isCompactLayoutEnabled = menuEditorNode.toggleClass('ws_compact_layout').hasClass('ws_compact_layout');
4050
+ $.cookie('ame-compact-layout', isCompactLayoutEnabled ? '1' : '0', {expires: 90});
4051
+
4052
+ var button = $(this);
4053
+ if (button.is('input')) {
4054
+ var checkMark = '\u2713';
4055
+ button.val(button.val().replace(checkMark, ''));
4056
+ if (isCompactLayoutEnabled) {
4057
+ button.val(checkMark + ' ' + button.val());
4058
+ }
4059
  }
4060
  });
4061
 
4097
  'action' : 'export_custom_menu',
4098
  '_ajax_nonce' : wsEditorData.exportMenuNonce
4099
  },
4100
+ /**
4101
+ * @param {Object} data
4102
+ */
4103
  function(data){
4104
  button.val('Export');
4105
  button.prop('disabled', false);
4106
 
4107
+ if ( typeof data.error !== 'undefined' ){
4108
  exportDialog.dialog('close');
4109
  alert(data.error);
4110
  }
4111
 
4112
+ if ( _.has(data, 'download_url') ){
4113
  //window.location = data.download_url;
4114
+ $('#download_menu_button').attr('href', _.get(data, 'download_url')).data('filesize', _.get(data, 'filesize'));
4115
  $('#export_progress_notice').hide();
4116
  $('#export_complete_notice, #download_menu_button').show();
4117
  }
4177
 
4178
  //Check if the user has selected a file
4179
  for(var i = 0; i < formData.length; i++){
4180
+ if ( formData[i].name === 'menu' ){
4181
+ if ( (typeof formData[i].value === 'undefined') || !formData[i].value){
4182
  alert('Select a file first!');
4183
  return false;
4184
  }
4206
  return;
4207
  }
4208
 
4209
+ if ( typeof data.error !== 'undefined' ){
4210
  alert(data.error);
4211
  //Let the user try again
4212
  $('#import_menu_form').resetForm();
4213
  importDialog.find('.hide-when-uploading').show();
4214
  }
4215
 
4216
+ if ( (typeof data.tree !== 'undefined') && data.tree ){
4217
  //Whee, we got back a (seemingly) valid menu. A veritable miracle!
4218
  //Lets load it into the editor.
4219
  var progressNotice = $('#import_progress_notice2').show();
4220
+ loadMenuConfiguration(data);
4221
  progressNotice.hide();
4222
  //Display a success notice, then automatically close the window after a few moments
4223
  $('#import_complete_notice').show();
4257
  });
4258
 
4259
  //...and to drag top level menus to a sub-menu.
4260
+ submenuBox.closest('.ws_main_container').droppable({
4261
  'hoverClass' : 'ws_top_to_submenu_drop_hover',
4262
 
4263
  'accept' : (function(thing){
4267
  thing.hasClass('ws_menu') &&
4268
 
4269
  //Prevent users from dropping a menu on its own sub-menu.
4270
+ (visibleSubmenu.attr('id') !== thing.data('submenu_id'))
4271
  );
4272
  }),
4273
 
4283
 
4284
 
4285
  //Set up tooltips
4286
+ $('.ws_tooltip_trigger').qtip({
4287
+ style: {
4288
+ classes: 'qtip qtip-rounded ws_tooltip_node'
4289
+ },
4290
+ hide: {
4291
+ fixed: true,
4292
+ delay: 300
4293
+ }
4294
+ });
4295
+
4296
+ //Set up the "additional permissions are available" tooltips.
4297
+ menuEditorNode.on('mouseenter click', '.ws_ext_permissions_indicator', function() {
4298
+ var $indicator = $(this);
4299
+ $indicator.qtip({
4300
+ overwrite: false,
4301
+ content: {
4302
+ text: function() {
4303
+ var indicator = $(this),
4304
+ extPermissions = indicator.data('ext_permissions'),
4305
+ text = 'Additional permission settings are available. Click "Edit..." to change them.',
4306
+ heading = '';
4307
+
4308
+ if (extPermissions && extPermissions.hasOwnProperty('title')) {
4309
+ heading = extPermissions.title;
4310
+ if (extPermissions.hasOwnProperty('type')) {
4311
+ heading = _.capitalize(_.startCase(extPermissions.type).toLowerCase()) + ': ' + heading;
4312
+ }
4313
+ text = '<strong>' + heading + '</strong><br>' + text;
4314
+ }
4315
+
4316
+ return text;
4317
+ }
4318
+ },
4319
+ show: {
4320
+ ready: true //Show immediately.
4321
+ },
4322
+ style: {
4323
+ classes: 'qtip qtip-rounded ws_tooltip_node'
4324
+ },
4325
+ hide: {
4326
+ fixed: true,
4327
+ delay: 300
4328
+ },
4329
+ position: {
4330
+ my: 'bottom center',
4331
+ at: 'top center'
4332
+ }
4333
+ });
4334
+ });
4335
 
4336
  //Flag closed hints as hidden by sending the appropriate AJAX request to the backend.
4337
  $('.ws_hint_close').click(function() {
4352
  Actor views
4353
  ******************************************************************/
4354
 
4355
+ var actorSelector = $('#ws_actor_selector');
4356
+
4357
+ function rebuildActorIndex() {
4358
+ var actors = {};
4359
+ //Include all roles.
4360
+ _.forEach(wsEditorData.roles, function(role, id) {
4361
+ actors['role:' + id] = role.name;
4362
+ });
4363
+ //Include the Super Admin (multisite only).
4364
+ if (wsEditorData.users[wsEditorData.currentUserLogin].is_super_admin) {
4365
+ actors['special:super_admin'] = 'Super Admin';
4366
+ }
4367
+ //Include the current user.
4368
+ actors['user:' + wsEditorData.currentUserLogin] = 'Current user (' + wsEditorData.currentUserLogin + ')';
4369
+
4370
+ //Include other visible users.
4371
+ _(_.get(wsEditorData, 'visibleUsers', []))
4372
+ .without(wsEditorData.currentUserLogin)
4373
+ .sortBy()
4374
+ .forEach(function(login) {
4375
+ var user = AmeCapabilityManager.getUser(login);
4376
+ actors['user:' + login] = user.display_name + ' (' + login + ')';
4377
+ })
4378
+ .value();
4379
+
4380
+ //Keep the same object, but replace all keys/values.
4381
+ _.forEach(_.keys(wsEditorData.actors), function(oldActor) {
4382
+ delete wsEditorData.actors[oldActor];
4383
+ });
4384
+ _.assign(wsEditorData.actors, actors);
4385
+ }
4386
+
4387
+
4388
+ function populateActorSelector() {
4389
+ if (!wsEditorData.wsMenuEditorPro) {
4390
+ return;
4391
+ }
4392
+
4393
+ rebuildActorIndex();
4394
+
4395
+ //Build the list of available actors.
4396
+ actorSelector.empty();
4397
+ actorSelector.append('<li><a href="#" class="current ws_actor_option ws_no_actor" data-text="All">All</a></li>');
4398
 
 
4399
  for(var actor in wsEditorData.actors) {
4400
  if (!wsEditorData.actors.hasOwnProperty(actor)) {
4401
  continue;
4404
  $('<li></li>').append(
4405
  $('<a></a>')
4406
  .attr('href', '#' + actor)
4407
+ .attr('data-text', wsEditorData.actors[actor])
4408
  .text(wsEditorData.actors[actor])
4409
+ .addClass('ws_actor_option')
4410
  )
4411
  );
4412
  }
4413
+
4414
+ var moreUsersText = 'Choose users\u2026';
4415
+ actorSelector.append(
4416
+ $('<li>').append(
4417
+ $('<a></a>')
4418
+ .attr('id', 'ws_show_more_users')
4419
+ .attr('href', '#more-users')
4420
+ .attr('data-text', moreUsersText)
4421
+ .text(moreUsersText)
4422
+ )
4423
+ );
4424
+
4425
  actorSelector.show();
4426
 
4427
+ if (selectedActor && !wsEditorData.actors.hasOwnProperty(selectedActor)) {
4428
+ selectedActor = null;
4429
+ }
4430
+ setSelectedActor(selectedActor);
4431
+ }
4432
+
4433
+ AmeEditorApi.populateActorSelector = populateActorSelector;
4434
+
4435
+ if (wsEditorData.wsMenuEditorPro) {
4436
+ populateActorSelector();
4437
+
4438
+ if (wsEditorData.hasOwnProperty('selectedActor') && wsEditorData.selectedActor) {
4439
  setSelectedActor(wsEditorData.selectedActor);
4440
  } else {
4441
  setSelectedActor(null);
4442
  }
4443
  }
4444
 
4445
+ actorSelector.on('click', 'li a.ws_actor_option', function(event) {
4446
  var actor = $(this).attr('href').substring(1);
4447
+ if (actor === '') {
4448
  actor = null;
4449
  }
4450
 
4451
  setSelectedActor(actor);
4452
+ event.preventDefault();
4453
+ });
4454
 
4455
+ actorSelector.on('click', '#ws_show_more_users', function(event) {
4456
  event.preventDefault();
4457
+ AmeVisibleUserDialog.open({
4458
+ currentUserLogin : wsEditorData.currentUserLogin,
4459
+ users : AmeCapabilityManager.getUsers(),
4460
+ visibleUsers : _.get(wsEditorData, 'visibleUsers', []),
4461
+
4462
+ save: function(userDetails, selectedUsers) {
4463
+ AmeCapabilityManager.addUsers(userDetails);
4464
+ wsEditorData.visibleUsers = selectedUsers;
4465
+ populateActorSelector();
4466
+ }
4467
+ });
4468
  });
4469
 
4470
  //Finally, show the menu
4471
+ loadMenuConfiguration(customMenu);
4472
  });
4473
 
4474
+ })(jQuery, wsAmeLodash);
4475
 
4476
  //==============================================
4477
  // Screen options
4478
  //==============================================
4479
 
4480
  jQuery(function($){
4481
+ 'use strict';
4482
+
4483
  var screenOptions = $('#ws-ame-screen-meta-contents');
4484
  var hideSettingsCheckbox = screenOptions.find('#ws-hide-advanced-settings');
4485
  var extraIconsCheckbox = screenOptions.find('#ws-show-extra-icons');
js/menu-highlight-fix.js CHANGED
@@ -183,7 +183,9 @@ jQuery(function($) {
183
  var parentMenu = bestMatchLink.closest('li.menu-top');
184
  //console.log('Best match is: ', bestMatchLink);
185
 
186
- var otherHighlightedMenus = $('li.wp-has-current-submenu, li.menu-top.current', '#adminmenu').not(parentMenu);
 
 
187
 
188
  var isWrongItemHighlighted = !bestMatchLink.hasClass('current');
189
  var isWrongMenuHighlighted = !parentMenu.is('.wp-has-current-submenu, .current') ||
183
  var parentMenu = bestMatchLink.closest('li.menu-top');
184
  //console.log('Best match is: ', bestMatchLink);
185
 
186
+ var otherHighlightedMenus = $('li.wp-has-current-submenu, li.menu-top.current', '#adminmenu')
187
+ .not(parentMenu)
188
+ .not('.ws-ame-has-always-open-submenu');
189
 
190
  var isWrongItemHighlighted = !bestMatchLink.hasClass('current');
191
  var isWrongMenuHighlighted = !parentMenu.is('.wp-has-current-submenu, .current') ||
menu-editor.php CHANGED
@@ -3,7 +3,7 @@
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.4.5
7
  Author: Janis Elsts
8
  Author URI: http://w-shadow.com/blog/
9
  */
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.5
7
  Author: Janis Elsts
8
  Author URI: http://w-shadow.com/blog/
9
  */
modules/access-editor/access-editor-template.php ADDED
@@ -0,0 +1,201 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <div id="ws_menu_access_editor" title="Permissions">
2
+
3
+ <div class="ws_dialog_panel">
4
+ <div class="error inline" id="ws_hardcoded_role_error">
5
+ <p>
6
+ <strong>Note:</strong>
7
+ Only users with the "<span id="ws_hardcoded_role_name">[role]</span>" role
8
+ can access this menu. This restriction is hard&shy;coded in the plugin that
9
+ created the menu.
10
+ </p>
11
+ </div>
12
+
13
+ <div id="ws_role_access_container" class="ws_dialog_subpanel ws_has_extended_permissions">
14
+ <div style="float: left; min-width: 352px;">
15
+ <strong>Grant access</strong>
16
+ <a class="ws_tooltip_trigger" title="
17
+ &#x2611; = give access and show the menu.
18
+ &lt;br&gt;
19
+ &#x2610; = prevent access and hide the menu.
20
+
21
+ &lt;br&gt;&lt;br&gt;
22
+ Checking a box will also give that role the required capability (see below).
23
+ ">[?]</a>
24
+ <br>
25
+
26
+ <div id="ws_role_table_body_container">
27
+ <div id="ws_role_access_overlay" class="ws_hide_if_pro"></div>
28
+ <div id="ws_role_access_overlay_content" class="ws_hide_if_pro">
29
+ Pro only feature.
30
+ Use capabilities (below) instead.
31
+ </div>
32
+
33
+ <table class="widefat ws_role_table_body">
34
+ <tbody>
35
+ <!-- Table contents will be generated by JavaScript. -->
36
+ </tbody>
37
+ </table>
38
+ </div>
39
+ </div>
40
+
41
+ <div id="ws_ext_permissions_container" class="ws_ext_readable_names_enabled">
42
+ <div id="ws_ext_permissions_container_caption">
43
+ <strong>
44
+ <span class="ws_aed_selected_actor_name">Role name</span>
45
+ <span class="ws_ame_breadcrumb_separator">&#187;</span>
46
+ <span id="ws_ext_selected_object_type_name">Post type or taxonomy</span>
47
+ </strong>
48
+ <span id="ws_ext_toggle_capability_names" class="dashicons dashicons-editor-code"
49
+ title="Toggle capability names"></span>
50
+ <br>
51
+ </div>
52
+
53
+ <?php
54
+ $cpt_actions = array(
55
+ 'General' => array(
56
+ 'edit_posts' => 'Edit',
57
+ 'publish_posts' => 'Publish',
58
+ 'delete_posts' => 'Delete',
59
+ 'create_posts' => 'Create',
60
+ ),
61
+
62
+ 'Published' => array(
63
+ 'edit_published_posts' => 'Edit published',
64
+ 'delete_published_posts' => 'Delete published',
65
+ ),
66
+
67
+ 'Others' => array(
68
+ 'edit_others_posts' => 'Edit others',
69
+ 'delete_others_posts' => 'Delete others',
70
+ ),
71
+
72
+ 'Private' => array(
73
+ 'edit_private_posts' => 'Edit private',
74
+ 'delete_private_posts' => 'Delete private',
75
+ 'read_private_posts' => 'Read private',
76
+ ),
77
+ );
78
+ ?>
79
+
80
+ <table class="widefat ws_ext_permissions_table" id="ws_post_type_permissions_table">
81
+ <?php foreach($cpt_actions as $group => $actions): ?>
82
+ <tr>
83
+ <td class="ws_ext_group_title" colspan="2"><?php echo $group; ?></td>
84
+ </tr>
85
+ <?php foreach($actions as $action => $readable_name): ?>
86
+ <?php $checkbox_id = 'ws_cpt_action-' . $action; ?>
87
+ <tr class="ws_ext_action-<?php echo esc_attr($action); ?>">
88
+ <td class="ws_ext_action_check_column">
89
+ <input
90
+ type="checkbox"
91
+ id="<?php echo esc_attr($checkbox_id); ?>"
92
+ class="ws_ext_action_allowed"
93
+ data-ext_action="<?php echo esc_attr($action); ?>">
94
+ </td>
95
+ <td class="ws_ext_action_name_column">
96
+ <label for="<?php echo esc_attr($checkbox_id); ?>" class="ws_ext_action_name">
97
+ <?php echo $readable_name; ?>
98
+ </label>
99
+ </td>
100
+ </tr>
101
+ <?php endforeach; ?>
102
+ <?php endforeach; ?>
103
+
104
+ <tr class="ws_ext_padding_row"><td colspan="2"></td></tr>
105
+ </table>
106
+
107
+ <?php
108
+ $taxonomy_actions = array(
109
+ 'manage_terms' => 'Manage',
110
+ 'edit_terms' => 'Edit',
111
+ 'delete_terms' => 'Delete',
112
+ 'assign_terms' => 'Assign',
113
+ );
114
+ ?>
115
+
116
+ <table class="widefat ws_ext_permissions_table" id="ws_taxonomy_permissions_table">
117
+ <?php foreach($taxonomy_actions as $action => $readable_name): ?>
118
+ <?php $checkbox_id = 'ws_taxonomy_action-' . $action; ?>
119
+ <tr class="ws_ext_action-<?php echo esc_attr($action); ?>">
120
+ <td class="ws_ext_action_check_column">
121
+ <input
122
+ type="checkbox"
123
+ id="<?php echo esc_attr($checkbox_id); ?>"
124
+ class="ws_ext_action_allowed"
125
+ data-ext_action="<?php echo esc_attr($action); ?>">
126
+ </td>
127
+ <td class="ws_ext_action_name_column">
128
+ <label for="<?php echo esc_attr($checkbox_id); ?>" class="ws_ext_action_name">
129
+ <?php echo $readable_name; ?>
130
+ </label>
131
+ </td>
132
+ </tr>
133
+ <?php endforeach; ?>
134
+
135
+ <tr class="ws_ext_padding_row"><td colspan="2"></td></tr>
136
+ </table>
137
+ </div>
138
+ </div>
139
+ <div class="clear"></div>
140
+
141
+
142
+ <div id="ws_required_cap_container" class="ws_dialog_subpanel">
143
+ <strong>Required capability</strong>
144
+ <a class="ws_tooltip_trigger" title="
145
+ This capability check is hard-coded in WordPress or the plugin that created the menu.
146
+
147
+ &lt;ul class=&quot;ws_tooltip_content_list&quot;&gt;
148
+ &lt;li&gt;
149
+ Only roles with this capability will be able to access this menu.
150
+ &lt;li&gt;
151
+ Admin Menu Editor will automatically grant the required capability to
152
+ all roles you check in the &quot;Roles&quot; list.
153
+ &lt;li&gt;
154
+ Custom menus have no hard-coded capability requirements.
155
+ &lt;/ul&gt;
156
+ ">[?]</a>
157
+ <br>
158
+ <span id="ws_required_capability">capability_here</span>
159
+ </div>
160
+
161
+ <div id="ws_extra_cap_container" class="ws_dialog_subpanel">
162
+ <label for="ws_extra_capability">
163
+ <strong>Extra capability</strong>
164
+ </label>
165
+ <a class="ws_tooltip_trigger" title="
166
+ Optional. An additional capability check that will be applied on top of
167
+ the &quot;Roles&quot; and &quot;Required capability&quot; settings.
168
+ Leave empty to disable.
169
+ ">[?]</a>
170
+ <br>
171
+ <input type="text" id="ws_extra_capability" class="ws_has_dropdown" value=""><input type="button" id="ws_trigger_capability_dropdown" value="&#9660;"
172
+ class="button ws_dropdown_button" tabindex="-1">
173
+ </div>
174
+
175
+ <div id="ws_item_restriction_container" class="ws_dialog_subpanel">
176
+ <strong>Override submenus</strong>
177
+ <a class="ws_tooltip_trigger" title="
178
+ &#x2611; = when this menu is hidden from a role or user, all of its submenu items will also be hidden
179
+ from that role or user.
180
+ &lt;br&gt;&lt;br&gt;
181
+ &#x2610; = this menu will stay visible as long as it has at least one visible submenu item.
182
+
183
+ &lt;br&gt;&lt;br&gt;
184
+
185
+ In WordPress, submenu item permissions usually have precedence. Check the box to give this menu
186
+ precedence over its submenus (but only when it is hidden).
187
+ ">[?]</a>
188
+ <br>
189
+ <label>
190
+ <input type="checkbox" id="ws_restrict_access_to_items">
191
+ Hide all submenu items when this item is hidden
192
+ </label>
193
+ </div>
194
+ </div>
195
+
196
+ <div class="ws_dialog_buttons">
197
+ <input type="button" class="button-primary" value="Save Changes" id="ws_save_access_settings">
198
+ <input type="button" class="button ws_close_dialog" value="Cancel">
199
+ </div>
200
+
201
+ </div>
modules/access-editor/access-editor.js ADDED
@@ -0,0 +1,491 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /* globals AmeCapabilityManager, jQuery */
2
+
3
+ window.AmeItemAccessEditor = (function ($) {
4
+ 'use strict';
5
+
6
+ /**
7
+ * A group of related permissions that can be displayed in the extended permissions panel.
8
+ *
9
+ * @typedef {Object} ExtPermissionsGroup
10
+ * @property {string} title A descriptive title, e.g. the name of the post type.
11
+ * @property {Object} capabilities A dictionary of ["readable name" => "capability"].
12
+ * @property {string} type What kind of group this is. Examples: "post_type", "taxonomy".
13
+ * @property {string|null} objectKey Post type or taxonomy key. Examples: "page", "category".
14
+ */
15
+
16
+ var _,
17
+ api,
18
+ isProVersion = false,
19
+ actors,
20
+ postTypes,
21
+ taxonomies,
22
+
23
+ $editor,
24
+ $actorTableRows = null,
25
+ wasDialogCreated = false,
26
+ saveCallback,
27
+
28
+ menuItem,
29
+ itemRequiredCap = '',
30
+ containerNode = null,
31
+ selectedActor = null,
32
+ readableNamesEnabled = true,
33
+
34
+ /**
35
+ * @type {ExtPermissionsGroup}
36
+ */
37
+ extPermissions = null,
38
+ /**
39
+ * @type {jQuery}
40
+ */
41
+ $currentExtTable = null,
42
+
43
+ hasExtendedPermissions = false,
44
+ unsavedCapabilities = {};
45
+
46
+ var defaultDialogWidth = 390,
47
+ extendedDialogWidth = 755;
48
+
49
+ function createEditorDialog() {
50
+ $editor = $editor || $('#ws_menu_access_editor');
51
+ $editor.dialog({
52
+ autoOpen: false,
53
+ closeText: ' ',
54
+ modal: true,
55
+ minHeight: 100,
56
+ width: defaultDialogWidth,
57
+ draggable: false
58
+ });
59
+
60
+ $editor.find('label.ws_ext_action_name')
61
+ .wrapInner('<span class="ws_ext_readable_name"></span>')
62
+ .append('<span class="ws_ext_capability"></span>');
63
+
64
+ $editor.find('#ws_ext_permissions_container')
65
+ .toggleClass('ws_ext_readable_names_enabled', readableNamesEnabled);
66
+
67
+ wasDialogCreated = true;
68
+ }
69
+
70
+ function setSelectedActor(actor) {
71
+ selectedActor = actor || null;
72
+
73
+ //Deselect the previously selected actor.
74
+ $actorTableRows.removeClass('ws_cpt_selected_role');
75
+
76
+ //Select the new one.
77
+ if (selectedActor) {
78
+ $actorTableRows.filter(function() {
79
+ return $(this).data('actor') === selectedActor;
80
+ }).addClass('ws_cpt_selected_role');
81
+
82
+ $editor.find('.ws_aed_selected_actor_name').text(actors[selectedActor]);
83
+ }
84
+
85
+ if (hasExtendedPermissions) {
86
+ refreshExtPermissionsTable();
87
+ }
88
+ }
89
+
90
+ /**
91
+ * Get a row from the role/actor table by actor ID.
92
+ *
93
+ * @param {string} actor
94
+ * @returns {jQuery}
95
+ */
96
+ function getActorRow(actor) {
97
+ return $actorTableRows.filter(function() {
98
+ return $(this).data('actor') === actor;
99
+ });
100
+ }
101
+
102
+ function refreshExtPermissionsTable() {
103
+ //Show what permissions the actor has for this CPT or taxonomy.
104
+ if (!hasExtendedPermissions) {
105
+ return;
106
+ }
107
+
108
+ var actions = $currentExtTable.find('tr td.ws_ext_action_check_column input[type="checkbox"]');
109
+ actions.each(function() {
110
+ var actionCheckbox = $(this),
111
+ requiredCapability = extPermissions.capabilities[actionCheckbox.data('ext_action')],
112
+ hasCap = AmeCapabilityManager.hasCap(selectedActor, requiredCapability, unsavedCapabilities),
113
+ hasCapByDefault = AmeCapabilityManager.hasCapByDefault(selectedActor, requiredCapability);
114
+ actionCheckbox.prop('checked', hasCap);
115
+
116
+ //Flag settings that don't match the default. This can help find problems.
117
+ actionCheckbox.closest('tr').toggleClass('ws_ext_has_custom_setting', hasCap !== hasCapByDefault);
118
+ });
119
+ }
120
+
121
+ /**
122
+ * Get the available permission settings for the post type or taxonomy that a URL refers to.
123
+ *
124
+ * Taxonomy has precedence over post type because it's less common in admin menus, and thus more notable.
125
+ * If a URL mentions both, this function only returns the taxonomy. Returns null if the URL isn't related to
126
+ * any CPTs or taxonomies.
127
+ *
128
+ * @param {string} url
129
+ * @returns {ExtPermissionsGroup|null}
130
+ */
131
+ function detectExtPermissions(url) {
132
+ url = url || '';
133
+ //To ease parsing, convert "something.php" to "/wp-admin/something.php". Otherwise the parser will think
134
+ //"something.php" is a domain name.
135
+ if (/^[\w\-]+?\.php/.test(url)) {
136
+ url = '/wp-admin/' + url;
137
+ }
138
+
139
+ var parsed = parseUri(url);
140
+ if (_.includes(['edit.php', 'post-new.php', 'edit-tags.php'], parsed.file)) {
141
+ var taxonomy = _.get(parsed, 'queryKey.taxonomy', null),
142
+ postType = _.get(parsed, 'queryKey.post_type', null);
143
+
144
+ if (taxonomy && taxonomies.hasOwnProperty(taxonomy)) {
145
+ return _.assign({}, taxonomies[taxonomy], {type: 'taxonomy', objectKey: taxonomy});
146
+ } else if (postType && postTypes.hasOwnProperty(postType)) {
147
+ return _.assign({}, postTypes[postType], {type: 'post_type', objectKey: postType});
148
+ } else if ((parsed.file === 'edit-tags.php') && (taxonomies.hasOwnProperty('category'))) {
149
+ return _.assign({}, _.get(taxonomies, 'category'), {type: 'taxonomy', objectKey: 'category'});
150
+ } else if (postTypes.hasOwnProperty('post')) {
151
+ return _.assign({}, _.get(postTypes, 'post'), {type: 'post_type', objectKey: 'post'});
152
+ }
153
+ }
154
+ return null;
155
+ }
156
+
157
+ // parseUri 1.2.2
158
+ // (c) Steven Levithan [http://stevenlevithan.com]
159
+ // MIT License
160
+ // Modified: Added partial URL-decoding support.
161
+
162
+ function parseUri(str) {
163
+ var o = parseUri.options,
164
+ m = o.parser[o.strictMode ? "strict" : "loose"].exec(str),
165
+ uri = {},
166
+ i = 14;
167
+
168
+ while (i--) { uri[o.key[i]] = m[i] || ""; }
169
+
170
+ uri[o.q.name] = {};
171
+ uri[o.key[12]].replace(o.q.parser, function ($0, $1, $2) {
172
+ if ($1) {
173
+ //Decode percent-encoded query parameters.
174
+ if (o.q.name === 'queryKey') {
175
+ $1 = decodeURIComponent($1);
176
+ $2 = decodeURIComponent($2);
177
+ }
178
+ uri[o.q.name][$1] = $2;
179
+ }
180
+ });
181
+
182
+ return uri;
183
+ }
184
+
185
+ parseUri.options = {
186
+ strictMode: false,
187
+ key: ["source","protocol","authority","userInfo","user","password","host","port","relative","path","directory","file","query","anchor"],
188
+ q: {
189
+ name: "queryKey",
190
+ parser: /(?:^|&)([^&=]*)=?([^&]*)/g
191
+ },
192
+ parser: {
193
+ strict: /^(?:([^:\/?#]+):)?(?:\/\/((?:(([^:@]*)(?::([^:@]*))?)?@)?([^:\/?#]*)(?::(\d*))?))?((((?:[^?#\/]*\/)*)([^?#]*))(?:\?([^#]*))?(?:#(.*))?)/,
194
+ loose: /^(?:(?![^:@]+:[^:@\/]*@)([^:\/?#.]+):)?(?:\/\/)?((?:(([^:@]*)(?::([^:@]*))?)?@)?([^:\/?#]*)(?::(\d*))?)(((\/(?:[^?#](?![^?#\/]*\.[^?#\/.]+(?:[?#]|$)))*\/?)?([^?#\/]*))(?:\?([^#]*))?(?:#(.*))?)/
195
+ }
196
+ };
197
+
198
+ // --- parseUri ends ---
199
+
200
+ //Set up dialog event handlers.
201
+ $(document).ready(function() {
202
+ $editor = $('#ws_menu_access_editor');
203
+
204
+ //Select a role or user on click.
205
+ $editor.on('click', '.ws_role_table_body tr', function() {
206
+ if (hasExtendedPermissions) {
207
+ setSelectedActor($(this).closest('tr').data('actor'));
208
+ }
209
+ });
210
+
211
+ //Toggle readable names vs capabilities.
212
+ $editor.on('click', '#ws_ext_toggle_capability_names', function() {
213
+ readableNamesEnabled = !readableNamesEnabled;
214
+ $('#ws_ext_permissions_container').toggleClass('ws_ext_readable_names_enabled', readableNamesEnabled);
215
+
216
+ //Remember the user's choice.
217
+ $.cookie('ame-readable-capability-names', readableNamesEnabled ? '1' : '0', {expires: 90});
218
+ });
219
+
220
+ //Prevent the user from accidentally changing menu permissions when selecting a role.
221
+ $editor.on('click', '.ws_column_role label', function(event) {
222
+ if (hasExtendedPermissions) {
223
+ //Usually, clicking the role label would toggle the access checkbox. This prevents that.
224
+ event.preventDefault();
225
+ }
226
+ });
227
+
228
+ //Store changes made by the user in a temporary location.
229
+ $editor.find('.ws_ext_permissions_table').on(
230
+ 'change',
231
+ 'input.ws_ext_action_allowed',
232
+ function() {
233
+ if (!hasExtendedPermissions) {
234
+ return;
235
+ }
236
+
237
+ var checkbox = $(this),
238
+ isAllowed = checkbox.prop('checked'),
239
+ capability = extPermissions.capabilities[checkbox.data('ext_action')],
240
+ hasCapWhenReset;
241
+
242
+ //Don't create custom settings unless necessary.
243
+ AmeCapabilityManager.resetCapInContext(unsavedCapabilities, selectedActor, capability);
244
+ hasCapWhenReset = AmeCapabilityManager.hasCap(selectedActor, capability, unsavedCapabilities);
245
+ if (isAllowed !== hasCapWhenReset) {
246
+ AmeCapabilityManager.setCapInContext(
247
+ unsavedCapabilities,
248
+ selectedActor,
249
+ capability,
250
+ isAllowed,
251
+ extPermissions.type,
252
+ extPermissions.objectKey
253
+ );
254
+ }
255
+
256
+ //If this is also the cap that's required to access the menu item, update the actor checkbox.
257
+ if (capability === itemRequiredCap) {
258
+ getActorRow(selectedActor).find('input.ws_role_access').prop('checked', isAllowed);
259
+ }
260
+
261
+ refreshExtPermissionsTable();
262
+ }
263
+ );
264
+
265
+ //Checking a role also gives it the required capability. However, that happens later, on the server side,
266
+ //and we don't want to give the role access to other menus associated with that capability. That means we only
267
+ //grant them that capability HERE if they already had it. Yep, that's not confusing at all.
268
+ $editor.find('.ws_role_table_body').on('change', 'input.ws_role_access', function() {
269
+ if (!hasExtendedPermissions || !itemRequiredCap) {
270
+ return;
271
+ }
272
+
273
+ var isAllowed = $(this).prop('checked'),
274
+ hasCap = AmeCapabilityManager.hasCap(selectedActor, itemRequiredCap, unsavedCapabilities),
275
+ hasCapByDefault = AmeCapabilityManager.hasCapByDefault(selectedActor, itemRequiredCap);
276
+
277
+ if (isAllowed && hasCapByDefault && !hasCap) {
278
+ AmeCapabilityManager.setCapInContext(
279
+ unsavedCapabilities,
280
+ selectedActor,
281
+ itemRequiredCap,
282
+ true,
283
+ extPermissions.type,
284
+ extPermissions.objectKey
285
+ );
286
+ refreshExtPermissionsTable();
287
+ }
288
+ });
289
+
290
+ //The "Save Changes" button.
291
+ $editor.find('#ws_save_access_settings').click(function() {
292
+ //Read the new settings from the form.
293
+ var extraCapability, restrictAccessToItems, grantAccess;
294
+
295
+ extraCapability = api.jsTrim($('#ws_extra_capability').val()) || null;
296
+ restrictAccessToItems = $('#ws_restrict_access_to_items').prop('checked');
297
+
298
+ grantAccess = $.extend({}, menuItem.grant_access);
299
+ $actorTableRows.each(function() {
300
+ var row = $(this);
301
+ grantAccess[row.data('actor')] = row.find('input.ws_role_access').prop('checked');
302
+ });
303
+
304
+ //Notify the editor. It will then update the menu item with the new values and refresh the UI.
305
+ if (saveCallback) {
306
+ saveCallback(
307
+ menuItem,
308
+ containerNode,
309
+ {
310
+ extraCapability : extraCapability,
311
+ grantAccess : grantAccess,
312
+ restrictAccessToItems : restrictAccessToItems,
313
+ grantedCapabilities : unsavedCapabilities
314
+ }
315
+ );
316
+ }
317
+
318
+ $editor.dialog('close');
319
+ });
320
+ });
321
+
322
+ return {
323
+ /**
324
+ * @param {AmeEditorApi} config.api
325
+ * @param {Object} config.actors
326
+ * @param {Object} config.postTypes
327
+ * @param {Object} config.taxonomies
328
+ * @param {lodash} config.lodash
329
+ * @param {Function} config.save
330
+ * @param {boolean} [config.isPro]
331
+ *
332
+ * @param config
333
+ */
334
+ setup: function(config) {
335
+ _ = config.lodash;
336
+ api = config.api;
337
+ actors = config.actors; //Note: This can change on the fly if the user changes visible users.
338
+
339
+ postTypes = config.postTypes;
340
+ taxonomies = config.taxonomies;
341
+
342
+ saveCallback = config.save || null;
343
+ isProVersion = _.get(config, 'isPro', false);
344
+
345
+ //Read settings from cookies.
346
+ readableNamesEnabled = $.cookie('ame-readable-capability-names');
347
+ if (typeof readableNamesEnabled === 'undefined') {
348
+ readableNamesEnabled = true;
349
+ } else {
350
+ readableNamesEnabled = (readableNamesEnabled === '1'); //Expected: "1" or "0".
351
+ }
352
+ },
353
+
354
+ open: function(state) {
355
+ menuItem = state.menuItem;
356
+ containerNode = state.containerNode;
357
+ unsavedCapabilities = {};
358
+ if (!wasDialogCreated) {
359
+ createEditorDialog();
360
+ }
361
+
362
+ //Write the values of this item to the editor fields.
363
+ itemRequiredCap = api.getFieldValue(menuItem, 'access_level', 'Error: access_level is missing!');
364
+ var requiredCapField = $editor.find('#ws_required_capability').empty();
365
+ if (menuItem.template_id === '') {
366
+ //Custom items have no required caps, only what users set.
367
+ requiredCapField.append('<em>None</em>');
368
+ } else {
369
+ requiredCapField.text(itemRequiredCap);
370
+ }
371
+
372
+ $editor.find('#ws_extra_capability').val(api.getFieldValue(menuItem, 'extra_capability', ''));
373
+ $editor.find('#ws_restrict_access_to_items').prop(
374
+ 'checked',
375
+ api.getFieldValue(menuItem, 'restrict_access_to_items', false)
376
+ );
377
+
378
+ //Generate the actor list.
379
+ var table = $editor.find('.ws_role_table_body tbody').empty(),
380
+ alternate = '';
381
+ for(var actor in actors) {
382
+ if (!actors.hasOwnProperty(actor)) {
383
+ continue;
384
+ }
385
+
386
+ var checkboxId = 'allow_' + actor.replace(/[^a-zA-Z0-9_]/g, '_');
387
+ var checkbox = $('<input type="checkbox">').addClass('ws_role_access').attr('id', checkboxId);
388
+
389
+ var actorHasAccess = api.actorCanAccessMenu(menuItem, actor);
390
+ checkbox.prop('checked', actorHasAccess);
391
+
392
+ alternate = (alternate === '') ? 'alternate' : '';
393
+
394
+ var cell = '<td>';
395
+ var row = $('<tr>').data('actor', actor).attr('class', alternate).append(
396
+ $(cell).addClass('ws_column_access').append(checkbox),
397
+ $(cell).addClass('ws_column_role post-title').append(
398
+ $('<label>').attr('for', checkboxId).append(
399
+ $('<span>').text(actors[actor])
400
+ )
401
+ ),
402
+ $(cell).addClass('ws_column_selected_role_tip').append(
403
+ $('<div></div>').addClass('ws_cpt_selected_role_tip')
404
+ )
405
+ );
406
+
407
+ table.append(row);
408
+ }
409
+
410
+ //We'll need these rows when dealing with CPTs and taxonomies.
411
+ $actorTableRows = table.find('tr');
412
+
413
+ //Hide the submenu override checkbox when the item doesn't have submenus.
414
+ $editor.find('#ws_item_restriction_container').toggle(state.itemHasSubmenus);
415
+
416
+ //Warn the user if the required capability == role. Can't make it less restrictive.
417
+ $('#ws_hardcoded_role_error').toggle(AmeCapabilityManager.roleExists(itemRequiredCap));
418
+ $('#ws_hardcoded_role_name').text(itemRequiredCap);
419
+
420
+ //Detect post type or taxonomy.
421
+ extPermissions = isProVersion ? detectExtPermissions(api.getItemDisplayUrl(menuItem)) : null;
422
+ hasExtendedPermissions = !!extPermissions;
423
+
424
+ var panelNode = $('#ws_ext_permissions_container');
425
+ if (hasExtendedPermissions) {
426
+ //Display the extended panel.
427
+ panelNode.show();
428
+
429
+ //Display only one of the tables.
430
+ $editor.find('.ws_ext_permissions_table').hide();
431
+ $currentExtTable = $editor.find('#ws_' + extPermissions.type + '_permissions_table').show();
432
+
433
+ //Show the group title.
434
+ panelNode.find('#ws_ext_selected_object_type_name').text(extPermissions.title);
435
+
436
+ //Often, the "create" option is redundant because it uses the same capability as "edit".
437
+ if (extPermissions.type === 'post_type') {
438
+ var createCap = _.get(extPermissions, 'capabilities.create_posts'),
439
+ editCap = _.get(extPermissions, 'capabilities.edit_posts');
440
+ $('#ws_cpt_action-create_posts').closest('tr').toggle(createCap !== editCap);
441
+ }
442
+
443
+ //Set caps. This way the user can mouse over an action to see the corresponding capability.
444
+ _.forEach(_.get(extPermissions, 'capabilities', []), function(capability, action) {
445
+ var title = capability;
446
+ if (capability === itemRequiredCap) {
447
+ title = title + ' (The required capability for this menu item.)';
448
+ }
449
+ $currentExtTable
450
+ .find('tr.ws_ext_action-' + action + ' label.ws_ext_action_name')
451
+ .attr('title', title)
452
+ .toggleClass('ws_same_as_required_cap', capability === itemRequiredCap)
453
+ .find('.ws_ext_capability').text(capability);
454
+ });
455
+ } else {
456
+ panelNode.hide();
457
+ $currentExtTable = null;
458
+ }
459
+
460
+ $editor.dialog('open');
461
+
462
+ //Make the dialog wider/narrower to accommodate the ext. permissions panel.
463
+ var desiredWidth = hasExtendedPermissions ? extendedDialogWidth : defaultDialogWidth;
464
+ if ($editor.dialog('option', 'width') !== desiredWidth) {
465
+ $editor.dialog('option', 'width', desiredWidth);
466
+ }
467
+
468
+ if (hasExtendedPermissions) {
469
+ //Select either the currently selected actor, or just the first one.
470
+ setSelectedActor(state.selectedActor || _.keys(actors)[0] || null);
471
+
472
+ //The permission table must be at least as tall as the actor list or the selected row won't look right.
473
+ var roleTable = $editor.find('.ws_role_table_body'),
474
+ heightDifference = roleTable.height() - $currentExtTable.height(),
475
+ paddingCell = $currentExtTable.find('.ws_ext_padding_row td'),
476
+ paddingCellHeight = paddingCell.height();
477
+
478
+ if (Math.abs(heightDifference) >= 1) {
479
+ paddingCell.height(Math.max(heightDifference + paddingCellHeight, 1));
480
+ }
481
+ } else {
482
+ setSelectedActor(null);
483
+ }
484
+
485
+ //Enable role hover and selection effects if there is a CPT or taxonomy to display.
486
+ $('#ws_role_access_container').toggleClass('ws_has_extended_permissions', hasExtendedPermissions);
487
+ },
488
+
489
+ detectExtPermissions: detectExtPermissions
490
+ };
491
+ })(jQuery);
readme.txt CHANGED
@@ -2,9 +2,9 @@
2
  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: 3.8
6
- Tested up to: 4.3
7
- Stable tag: 1.4.5
8
 
9
  Lets you edit the WordPress admin menu. You can re-order, hide or rename menus, add custom menus and more.
10
 
@@ -63,6 +63,34 @@ Plugins installed in the `mu-plugins` directory are treated as "always on", so y
63
 
64
  == Changelog ==
65
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
66
  = 1.4.5 =
67
  * Fixed a `TypeError: invalid 'in' operand a` error that caused compatibility issues with WordPress 4.3.
68
  * Fixed a bug where the current menu item wouldn't get highlighted if its URL included %-encoded query parameters.
2
  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: 4.4
7
+ Stable tag: 1.5
8
 
9
  Lets you edit the WordPress admin menu. You can re-order, hide or rename menus, add custom menus and more.
10
 
63
 
64
  == Changelog ==
65
 
66
+ = 1.5 =
67
+ * Added "Keep this menu open" checkbox. This setting keeps a top level menu expanded even if it is not the current menu.
68
+ * Added sort buttons to the top level menu toolbar.
69
+ * Added an arrow that points from the current submenu to the currently selected parent menu. This might help new users understand that the left column shows top level menus and the right column shows the corresponding submenu(s).
70
+ * Added a new editor colour scheme that makes the menu editor look more like other WordPress admin pages (e.g. Appearance -> Menus). You can enable it through the plugin settings page.
71
+ * New and unused menu items will now show up in the same relative position as they would be in the default admin menu. Alternatively, they can be displayed at the bottom of the menu. You can configure this in plugin settings.
72
+ * Fixed a rare bug where the menu editor would crash if one of the menu items had a `null` menu title. Technically, it's not valid to set the title to `null`, but it turns out that some plugins do that anyway.
73
+ * Top level menus that have an empty title ("", an empty string) are no longer treated as separators.
74
+ * Made all text fields and dropdowns the same height and gave them consistent margins.
75
+ * Fixed a number of layout bugs that could cause field labels to show up in the wrong place or get wrapped/broken in half when another plugin changed the default font or input size.
76
+ * Fixed a minor layout bug that caused the "expand menu properties" arrow to move down slightly when holding down the mouse button.
77
+ * Fixed a minor bug that could cause toolbar buttons to change size or position if another plugin happens to override the default link and image CSS.
78
+ * Added a workaround for plugins that create "Welcome", "What's New" or "Getting Started" menu items and then hide those items in a non-standard way. Now (some of) these items will no longer show up unnecessarily. If you find menus like that which still show up when not needed, please report them.
79
+ * Fixed a few other layout inconsistencies.
80
+ * Improved compatibility with buggy plugins that unintentionally corrupt the list of users' roles by misusing `array_shift`.
81
+ * Fixed a URL parsing bug that caused AME to mix up the "Customize", "Header" and "Background" menu items in some configurations.
82
+ * Fixed a layout issue where starting to drag one menu item would cause some other items to move around or change size very slightly.
83
+ * Fixed JavaScript error "_.empty is not a function".
84
+ * Increased minimum required WordPress version to 4.1.
85
+ * Renamed the "Show/Hide" button to "Hide without preventing access". Changed the icon from a grey puzzle piece to a rectangle with a dashed border.
86
+ * Made the plugin more resilient to JavaScript crashes caused by other plugins.
87
+ * Use `<h1>` headings for admin pages in WordPress 4.2 and above.
88
+ * Made the "delete" button appear disabled when the selected menu item can't be deleted.
89
+ * Moved the "new separator" button so that it's next to the "new menu" button.
90
+ * Changed the close icon of plugin dialogs to a plain white "X".
91
+ * Increased tooltip text size.
92
+ * Improved compatibility with IP Geo Block.
93
+
94
  = 1.4.5 =
95
  * Fixed a `TypeError: invalid 'in' operand a` error that caused compatibility issues with WordPress 4.3.
96
  * Fixed a bug where the current menu item wouldn't get highlighted if its URL included %-encoded query parameters.