RSS for Yandex Turbo - Version 1.27

Version Description

(06.04.2020) =

  • RSS- .
  • .
  • 64k .
  • ( ).
  • "yturbo_before_ads" - .
  • - WPCase: Turbo Ads.
Download this release

Release Info

Developer Flector
Plugin Icon 128x128 RSS for Yandex Turbo
Version 1.27
Comparing to
See all releases

Code changes from version 1.26 to 1.27

inc/AdminNotice.php ADDED
@@ -0,0 +1,609 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ namespace YTurboAdminNotices;
3
+
4
+ if (!class_exists(__NAMESPACE__ . '\\AdminNotice', false)) {
5
+
6
+ class AdminNotice {
7
+ const TYPE_SUCCESS = 'success';
8
+ const TYPE_INFO = 'info';
9
+ const TYPE_WARNING = 'warning';
10
+ const TYPE_ERROR = 'error';
11
+
12
+ const DISMISS_PER_USER = 'user';
13
+ const DISMISS_PER_SITE = 'site';
14
+ const DISMISS_PERMANENTLY = 3153600000; //100 years in seconds.
15
+ const DISMISS_ACTION_PREFIX = 'ye_v1_dismiss-';
16
+
17
+ const DISMISSED_OPTION_PREFIX = 'ye_is_dismissed-';
18
+ const DELAYED_NOTICE_OPTION = 'ye_delayed_notices';
19
+
20
+ protected $id = null;
21
+ protected $content = '';
22
+ protected $noticeType = 'success';
23
+ protected $customCssClasses = array();
24
+
25
+ protected $allowedScreens = array();
26
+ protected $requiredCapability = null;
27
+
28
+ protected $isDismissible = false;
29
+ protected $isPersistentlyDismissible = false;
30
+ protected $dismissionScope = self::DISMISS_PER_SITE;
31
+ protected $dismissalDuration = self::DISMISS_PERMANENTLY;
32
+
33
+ public function __construct($id = null) {
34
+ $this->id = $id;
35
+ }
36
+
37
+ /**
38
+ * Create a new notice.
39
+ *
40
+ * @param string $id
41
+ * @return AdminNotice
42
+ */
43
+ public static function create($id = null) {
44
+ return new static($id);
45
+ }
46
+
47
+ public function text($message) {
48
+ $this->content = '<p>' . esc_html($message) . '</p>';
49
+ return $this;
50
+ }
51
+
52
+ public function html($arbitraryHtml) {
53
+ $this->content = '<p>' . $arbitraryHtml . '</p>';
54
+ return $this;
55
+ }
56
+
57
+ public function rawHtml($arbitraryHtml) {
58
+ $this->content = $arbitraryHtml;
59
+ return $this;
60
+ }
61
+
62
+ public function getHtmlContent() {
63
+ return $this->content;
64
+ }
65
+
66
+ public function type($noticeType) {
67
+ $this->noticeType = $noticeType;
68
+ return $this;
69
+ }
70
+
71
+ public function success($messageHtml = null) {
72
+ return $this->setTypeAndMessage(self::TYPE_SUCCESS, $messageHtml);
73
+ }
74
+
75
+ public function info($messageHtml = null) {
76
+ return $this->setTypeAndMessage(self::TYPE_INFO, $messageHtml);
77
+ }
78
+
79
+ public function warning($messageHtml = null) {
80
+ return $this->setTypeAndMessage(self::TYPE_WARNING, $messageHtml);
81
+ }
82
+
83
+ public function error($messageHtml = null) {
84
+ return $this->setTypeAndMessage(self::TYPE_ERROR, $messageHtml);
85
+ }
86
+
87
+ protected function setTypeAndMessage($noticeType, $messageHtml = null) {
88
+ $this->noticeType = $noticeType;
89
+ if (isset($messageHtml)) {
90
+ $this->html($messageHtml);
91
+ }
92
+ return $this;
93
+ }
94
+
95
+ public function addClass($className) {
96
+ if (is_array($className)) {
97
+ $className = implode(' ', $className);
98
+ }
99
+ $this->customCssClasses[] = $className;
100
+ return $this;
101
+ }
102
+
103
+ /**
104
+ * Make the notice dismissible.
105
+ *
106
+ * @return $this
107
+ */
108
+ public function dismissible() {
109
+ $this->isDismissible = true;
110
+ return $this;
111
+ }
112
+
113
+ /**
114
+ * When the user dismisses the notice, remember that and don't show it again.
115
+ *
116
+ * @param string $scope
117
+ * @param int $duration How long (in seconds) the notice should be considered dismissed for.
118
+ * @return $this
119
+ */
120
+ public function persistentlyDismissible($scope = self::DISMISS_PER_SITE, $duration = self::DISMISS_PERMANENTLY) {
121
+ if (empty($this->id)) {
122
+ throw new \LogicException('Persistently dismissible notices must have a unique ID.');
123
+ }
124
+
125
+ $this->isDismissible = true;
126
+ $this->isPersistentlyDismissible = true;
127
+ $this->dismissionScope = $scope;
128
+ $this->dismissalDuration = $duration;
129
+
130
+ $ajaxCallback = array($this, 'ajaxDismiss');
131
+ if (has_action($this->getDismissActionName(), $ajaxCallback) === false) {
132
+ add_action('wp_ajax_' . $this->getDismissActionName(), $ajaxCallback);
133
+ }
134
+
135
+ return $this;
136
+ }
137
+
138
+ /**
139
+ * Only show the notice on the specified admin page(s).
140
+ *
141
+ * @link https://codex.wordpress.org/Plugin_API/Admin_Screen_Reference
142
+ *
143
+ * @param string|string[] $screenId
144
+ * @return $this
145
+ */
146
+ public function onPage($screenId) {
147
+ $this->allowedScreens = array_merge($this->allowedScreens, (array)$screenId);
148
+ return $this;
149
+ }
150
+
151
+ /**
152
+ * Get the current screen ID.
153
+ *
154
+ * @return null|string
155
+ */
156
+ private function getCurrentScreenId() {
157
+ if (!function_exists('get_current_screen')) {
158
+ return null;
159
+ }
160
+
161
+ $screen = \get_current_screen();
162
+ if ($screen === null) {
163
+ return null;
164
+ }
165
+ return $screen->id;
166
+ }
167
+
168
+ /**
169
+ * Only show this notice to users that have the specified capability.
170
+ *
171
+ * @param string|null $capability
172
+ * @return $this
173
+ */
174
+ public function requiredCap($capability) {
175
+ $this->requiredCapability = $capability;
176
+ return $this;
177
+ }
178
+
179
+ /**
180
+ * Show the notice on the current page when all preconditions are met.
181
+ */
182
+ public function show() {
183
+ if (did_action('admin_notices')) {
184
+ $this->maybeOutputNotice();
185
+ } else {
186
+ add_action('admin_notices', array($this, 'maybeOutputNotice'));
187
+ }
188
+ return $this;
189
+ }
190
+
191
+ /**
192
+ * Immediately output the notice unless it has been dismissed.
193
+ *
194
+ * @internal
195
+ */
196
+ public function maybeOutputNotice() {
197
+ if (isset($this->requiredCapability) && !current_user_can($this->requiredCapability)) {
198
+ return;
199
+ }
200
+
201
+ if (!empty($this->allowedScreens) && !in_array($this->getCurrentScreenId(), $this->allowedScreens)) {
202
+ return;
203
+ }
204
+
205
+ if ($this->isDismissed()) {
206
+ return;
207
+ }
208
+ $this->outputNotice();
209
+ }
210
+
211
+ /**
212
+ * Output the notice.
213
+ */
214
+ public function outputNotice() {
215
+ $classes = array_merge(
216
+ array('notice', 'notice-' . $this->noticeType),
217
+ $this->customCssClasses
218
+ );
219
+
220
+ if ($this->isDismissible) {
221
+ $classes[] = 'is-dismissible';
222
+ }
223
+
224
+ $attributes = array(
225
+ 'id' => $this->id,
226
+ 'class' => implode(' ', $classes),
227
+ );
228
+
229
+ if ($this->isPersistentlyDismissible) {
230
+ $attributes['data-ye-dismiss-nonce'] = wp_create_nonce($this->getDismissActionName());
231
+
232
+ $attributes['data-ye-notice-data'] = $this->toJson();
233
+ $attributes['data-ye-signature'] = wp_create_nonce(
234
+ $this->id . '|' . $attributes['data-ye-dismiss-nonce'] . '|' . $attributes['data-ye-notice-data']
235
+ );
236
+
237
+ $this->enqueueScriptOnce();
238
+ }
239
+
240
+ /** @noinspection HtmlUnknownAttribute */
241
+ printf(
242
+ '<div %1$s>%2$s</div>',
243
+ $this->formatTagAttributes($attributes),
244
+ $this->content
245
+ );
246
+ }
247
+
248
+ protected function enqueueScriptOnce() {
249
+ if (!wp_script_is('ye-dismiss-notice', 'registered')) {
250
+ //Note: Queueing a script also registers it.
251
+ wp_enqueue_script(
252
+ 'ye-dismiss-notice',
253
+ plugins_url('dismiss-notice.js', __FILE__),
254
+ array('jquery'),
255
+ '20170318',
256
+ true
257
+ );
258
+ }
259
+ }
260
+
261
+ protected function formatTagAttributes($attributes) {
262
+ $attributePairs = array();
263
+ foreach ($attributes as $name => $value) {
264
+ if (isset($value)) {
265
+ $attributePairs[] = $name . '="' . esc_attr($value) . '"';
266
+ }
267
+ }
268
+ return implode(' ', $attributePairs);
269
+ }
270
+
271
+ /**
272
+ * Show the notice on the next admin page that's visited by the current user.
273
+ * The notice will be shown only once.
274
+ *
275
+ * More accurately, this shows the notice the next time the admin_notices hook is called
276
+ * in the context of the current user, whether that happens during this page load or the next,
277
+ * or a week later. The intended use is for form handlers that redirect to another page, plugin
278
+ * activation hooks and other callbacks that can't display a notice in the usual way.
279
+ *
280
+ * @return self
281
+ */
282
+ public function showOnNextPage() {
283
+ if (!is_user_logged_in()) {
284
+ return $this;
285
+ }
286
+
287
+ //Schedule the notice to appear on the next page.
288
+ add_user_meta(
289
+ get_current_user_id(),
290
+ static::DELAYED_NOTICE_OPTION,
291
+ wp_slash($this->toJson()),
292
+ false
293
+ );
294
+
295
+ return $this;
296
+ }
297
+
298
+ /**
299
+ * Display delayed notices stored by showOnNextPage.
300
+ *
301
+ * @internal
302
+ */
303
+ public static function _showDelayedNotices() {
304
+ $userId = get_current_user_id();
305
+ $notices = get_user_meta($userId, static::DELAYED_NOTICE_OPTION, false);
306
+ if (empty($notices)) {
307
+ return;
308
+ }
309
+
310
+ foreach ($notices as $json) {
311
+ $notice = static::tryUnserializeNotice($json);
312
+ if (isset($notice)) {
313
+ $notice->show();
314
+
315
+ //Only show the notice once.
316
+ delete_user_meta($userId, static::DELAYED_NOTICE_OPTION, wp_slash($json));
317
+ }
318
+ }
319
+ }
320
+
321
+ /**
322
+ * Attempt to unserialize a notice from JSON.
323
+ *
324
+ * @internal
325
+ * @param string $json
326
+ * @return null|static
327
+ */
328
+ protected static function tryUnserializeNotice($json) {
329
+ $properties = json_decode($json, true);
330
+ if (!is_array($properties)) {
331
+ return null;
332
+ }
333
+
334
+ //Ignore notices created by other versions of this class.
335
+ if (self::getKey($properties, '_className') !== get_called_class()) {
336
+ return null;
337
+ }
338
+
339
+ return static::fromJson($json);
340
+ }
341
+
342
+ /**
343
+ * Serialize the notice as JSON.
344
+ *
345
+ * @return string
346
+ */
347
+ public function toJson() {
348
+ $data = array(
349
+ 'id' => $this->id,
350
+ 'content' => $this->content,
351
+ 'noticeType' => $this->noticeType,
352
+ 'isDismissible' => $this->isDismissible,
353
+ 'isPersistentlyDismissible' => $this->isPersistentlyDismissible,
354
+ 'dismissionScope' => $this->dismissionScope,
355
+ 'dismissalDuration' => $this->dismissalDuration,
356
+ 'customCssClasses' => $this->customCssClasses,
357
+ 'allowedScreens' => $this->allowedScreens,
358
+ 'requiredCapability' => $this->requiredCapability,
359
+ '_className' => get_class($this),
360
+ );
361
+
362
+ return json_encode($data);
363
+ }
364
+
365
+ /**
366
+ * Load a notice from JSON.
367
+ *
368
+ * @param string $json
369
+ * @return AdminNotice
370
+ */
371
+ public static function fromJson($json) {
372
+ $properties = json_decode($json, true);
373
+
374
+ $notice = new static($properties['id']);
375
+ $notice->rawHtml($properties['content']);
376
+ $notice->type($properties['noticeType']);
377
+ $notice->addClass($properties['customCssClasses']);
378
+ $notice->onPage(self::getKey($properties, 'allowedScreens', array()));
379
+ $notice->requiredCap(self::getKey($properties, 'requiredCapability'));
380
+
381
+ if ($properties['isDismissible']) {
382
+ $notice->dismissible();
383
+ }
384
+ if ($properties['isPersistentlyDismissible']) {
385
+ $notice->persistentlyDismissible(
386
+ self::getKey($properties, 'dismissionScope', self::DISMISS_PER_SITE),
387
+ self::getKey($properties, 'dismissalDuration', self::DISMISS_PERMANENTLY)
388
+ );
389
+ }
390
+
391
+ return $notice;
392
+ }
393
+
394
+ /**
395
+ * @param array $array
396
+ * @param string $key
397
+ * @param mixed $defaultValue
398
+ * @return mixed
399
+ */
400
+ protected static function getKey($array, $key, $defaultValue = null) {
401
+ if (array_key_exists($key, $array)) {
402
+ return $array[$key];
403
+ }
404
+ return $defaultValue;
405
+ }
406
+
407
+ /**
408
+ * Process an AJAX request to dismiss this notice.
409
+ *
410
+ * @internal
411
+ */
412
+ public function ajaxDismiss() {
413
+ check_ajax_referer($this->getDismissActionName());
414
+
415
+ if (!is_user_logged_in()) {
416
+ wp_die('Access denied. You need to be logged in to dismiss notices.');
417
+ return;
418
+ }
419
+
420
+ if (isset($this->requiredCapability) && !current_user_can($this->requiredCapability)) {
421
+ wp_die('Access denied. You don\'t have the required capability to dismiss this notice.');
422
+ return;
423
+ }
424
+
425
+ $this->dismiss();
426
+ exit('Notice dismissed');
427
+ }
428
+
429
+ /**
430
+ * Dismiss a notice.
431
+ *
432
+ * @param int $duration How long (in seconds) the notice should be considered dismissed for.
433
+ * By default, the duration is the same as the duration
434
+ * passed to {@see self::persistentlyDismissible()}.
435
+ * You can also pass {@see self::DISMISS_PERMANENTLY}.
436
+ * @return self
437
+ */
438
+ public function dismiss($duration = null) {
439
+ if (!$this->isPersistentlyDismissible) {
440
+ return $this;
441
+ }
442
+
443
+ $dismissal = json_encode(array(
444
+ 'dismissalTime' => time(),
445
+ 'dismissalDuration' => $duration,
446
+ ));
447
+
448
+ if ($this->dismissionScope === self::DISMISS_PER_SITE) {
449
+ update_option($this->getDismissOptionName(), $dismissal);
450
+ } else {
451
+ update_user_meta(get_current_user_id(), $this->getDismissOptionName(), $dismissal);
452
+ }
453
+
454
+ return $this;
455
+ }
456
+
457
+ public function undismiss($scope = self::DISMISS_PER_SITE) {
458
+ if (!$this->isPersistentlyDismissible) {
459
+ return $this;
460
+ }
461
+
462
+ if ($this->dismissionScope === self::DISMISS_PER_SITE) {
463
+ delete_option($this->getDismissOptionName());
464
+ } else {
465
+ if ($scope === self::DISMISS_PER_SITE) {
466
+ //Un-dismiss it for all users.
467
+ delete_metadata('user', 0, $this->getDismissOptionName(), '', true);
468
+ } else {
469
+ //Un-dismiss just for the current user.
470
+ delete_user_meta(get_current_user_id(), $this->getDismissOptionName());
471
+ }
472
+ }
473
+
474
+ return $this;
475
+ }
476
+
477
+ /**
478
+ * Delete all "dismissed" flags that have the specified prefix.
479
+ *
480
+ * @param string $prefix
481
+ */
482
+ public static function cleanUpDatabase($prefix) {
483
+ global $wpdb;
484
+ /** @var \wpdb $wpdb */
485
+ $escapedPrefix = esc_sql($wpdb->esc_like(static::DISMISSED_OPTION_PREFIX . $prefix) . '%');
486
+
487
+ if (!is_string($escapedPrefix) || (strlen($escapedPrefix) < 2)) {
488
+ throw new \LogicException('Prefix must not be empty.'); //This should never happen.
489
+ }
490
+
491
+ $wpdb->query(sprintf(
492
+ 'DELETE FROM %s WHERE option_name LIKE "%s"',
493
+ $wpdb->options,
494
+ $escapedPrefix
495
+ ));
496
+ $wpdb->query(sprintf(
497
+ 'DELETE FROM %s WHERE meta_key LIKE "%s"',
498
+ $wpdb->usermeta,
499
+ $escapedPrefix
500
+ ));
501
+ }
502
+
503
+ public function isDismissed() {
504
+ if (!$this->isPersistentlyDismissible) {
505
+ return false;
506
+ }
507
+
508
+ if ($this->dismissionScope === self::DISMISS_PER_SITE) {
509
+ $dismissal = get_option($this->getDismissOptionName());
510
+ } else {
511
+ $dismissal = get_user_meta(get_current_user_id(), $this->getDismissOptionName(), true);
512
+ }
513
+
514
+ $dismissal = is_string($dismissal) ? json_decode($dismissal, true) : false;
515
+
516
+ if (!$dismissal) {
517
+ return false;
518
+ }
519
+
520
+ // If the library has been updated from version 1.0, then the dismiss option
521
+ // will contain '1' instead of the dismissal time. The best we can do
522
+ // in this case is set the dismissal time to the current time so that
523
+ // the notice can at least eventually be undismissed.
524
+ if ($dismissal === 1) {
525
+ $this->dismiss();
526
+
527
+ return true;
528
+ }
529
+
530
+ return time() < $dismissal['dismissalTime'] + ($dismissal['dismissalDuration'] ?: $this->dismissalDuration);
531
+ }
532
+
533
+ protected function getDismissActionName() {
534
+ return self::DISMISS_ACTION_PREFIX . $this->id;
535
+ }
536
+
537
+ protected function getDismissOptionName() {
538
+ return static::DISMISSED_OPTION_PREFIX . $this->id;
539
+ }
540
+
541
+ /**
542
+ * @internal
543
+ */
544
+ public static function _ajaxAddDismissalHandler() {
545
+ if (!self::isUnhandledAjaxAction()) {
546
+ return;
547
+ }
548
+
549
+ $id = substr($_POST['action'], strlen(self::DISMISS_ACTION_PREFIX));
550
+ $ajaxNonce = strval($_POST['_ajax_nonce']);
551
+ $json = strval(wp_unslash($_POST['notice-data']));
552
+ if (!wp_verify_nonce($_POST['signature'], $id . '|' . $ajaxNonce . '|' . $json)) {
553
+ return;
554
+ }
555
+
556
+ //The notice will automatically set an AJAX hook when it gets unserialized.
557
+ self::tryUnserializeNotice($json);
558
+ }
559
+
560
+ /**
561
+ * Is this a "dismiss notice" AJAX request without a registered action callback?
562
+ *
563
+ * @internal
564
+ * @return bool
565
+ */
566
+ protected static function isUnhandledAjaxAction() {
567
+ $doingAjax = defined('DOING_AJAX') && constant('DOING_AJAX');
568
+ if (!$doingAjax) {
569
+ return false;
570
+ }
571
+
572
+ $requiredParams = array('action', 'notice-data', 'signature', '_ajax_nonce');
573
+ foreach($requiredParams as $param) {
574
+ if (empty($_POST[$param]) || !is_string($_POST[$param])) {
575
+ return false;
576
+ }
577
+ }
578
+
579
+ $action = $_POST['action'];
580
+ if (has_action('wp_ajax_' . $action) === true) {
581
+ return false;
582
+ }
583
+
584
+ $isDismissAction = self::stringStartsWith($action, self::DISMISS_ACTION_PREFIX)
585
+ && (strlen($action) > strlen(self::DISMISS_ACTION_PREFIX));
586
+ return $isDismissAction;
587
+ }
588
+
589
+ protected static function stringStartsWith($input, $prefix) {
590
+ return substr($input, 0, strlen($prefix)) === $prefix;
591
+ }
592
+ }
593
+
594
+ /**
595
+ * Create an admin notice.
596
+ *
597
+ * @param string $id
598
+ * @return AdminNotice
599
+ */
600
+ function easyAdminNotice($id = null) {
601
+ return new AdminNotice($id);
602
+ }
603
+
604
+ if (function_exists('add_action')) {
605
+ add_action('admin_notices', array(__NAMESPACE__ . '\\AdminNotice', '_showDelayedNotices'));
606
+ add_action('admin_init', array(__NAMESPACE__ . '\\AdminNotice', '_ajaxAddDismissalHandler'), 300);
607
+ }
608
+
609
+ } //class_exists
inc/{class-Kama_Contents.php → Contents.php} RENAMED
@@ -1,15 +1,17 @@
1
  <?php
2
 
 
3
  /**
4
  * Содержание (оглавление) для больших постов.
5
  *
 
6
  * @author: Kama
7
  * @info: http://wp-kama.ru/?p=1513
8
- * @version: 3.17
9
  *
10
  * @changelog: https://github.com/doiftrue/Kama_Contents/blob/master/CHANGELOG.md
11
  */
12
- class Kama_Contents {
13
 
14
  public $opt = [
15
  // Отступ слева у подразделов в px.
@@ -230,12 +232,10 @@ class Kama_Contents {
230
  }
231
  else {
232
  $contents =
233
- ( $_is_tit ? '<div class="kc__wrap"'. $ItemList .' >' : '' ) .
234
- ( $_is_tit ? '<span style="display:block;" class="kc-title kc__title" id="kcmenu"'. ($ItemList?' itemprop="name"':'') .'>'. $_tit .'</span>'. "\n" : '' ) .
235
- '<ul class="contents"'. ( (! $_tit || $embed) ? ' id="kcmenu"' : '' ) . ( ($ItemList && ! $_is_tit ) ? $ItemList : '' ) .'>'. "\n".
236
  implode('', $this->contents ) .
237
- '</ul>'."\n" .
238
- ( $_is_tit ? '</div>' : '' );
239
  }
240
 
241
  $contents =
@@ -300,7 +300,7 @@ class Kama_Contents {
300
  if( $level > 0 )
301
  $sub = ( $opt->margin ? ' style="margin-left:'. ($level*$opt->margin) .'px;"' : '') . ' class="sub sub_'. $level .'"';
302
  else
303
- $sub = ' class="top"';
304
 
305
  // collect contents
306
  // markup
@@ -317,7 +317,7 @@ class Kama_Contents {
317
  $this->contents[] = "\t".'
318
  <tr>
319
  <td '. ($_is_mark?' itemprop="itemListElement" itemscope itemtype="http://schema.org/ListItem"':'') .'>
320
- <a rel="nofollow"'. ($_is_mark?' itemprop="url"':'') .' href="'. $opt->page_url .'#'. $anchor .'">
321
  '.( $_is_mark ? '<span itemprop="name">'. $cont_elem_txt .'</span>' : $cont_elem_txt ).'
322
  </a>
323
  '.( $_is_mark ? ' <meta itemprop="position" content="'. $temp->counter .'" />':'' ).'
@@ -328,7 +328,7 @@ class Kama_Contents {
328
  else {
329
  $this->contents[] = "\t".'
330
  <li'. $sub . ($_is_mark?' itemprop="itemListElement" itemscope itemtype="http://schema.org/ListItem"':'') .'>
331
- <a rel="nofollow"'. ($_is_mark?' itemprop="url"':'') .' href="'. $opt->page_url .'#'. $anchor .'">
332
  '.( $_is_mark ? '<span itemprop="name">'. $cont_elem_txt .'</span>' : $cont_elem_txt ).'
333
  </a>
334
  '.( $_is_mark ? ' <meta itemprop="position" content="'. $temp->counter .'" />':'' ).'
@@ -336,18 +336,18 @@ class Kama_Contents {
336
  }
337
 
338
  if( $opt->anchor_link )
339
- $tag_txt = '<a rel="nofollow" class="kc__anchlink" href="#'. $anchor .'">'. $opt->anchor_link .'</a> ' . $tag_txt;
340
 
341
  // anchor type: 'a' or 'id'
342
  if( $opt->anchor_type === 'a' )
343
- $new_el = '<a class="kc__anchor" name="'. $anchor .'"></a>'."\n<$tag $attrs>$tag_txt</$tag>";
344
  else
345
- $new_el = "\n<$tag id=\"$anchor\" $attrs>$tag_txt</$tag>";
346
 
347
  $to_menu = '';
348
  if( $opt->to_menu ){
349
  // go to contents
350
- $to_menu = '<a rel="nofollow" class="kc-gotop kc__gotop" href="'. $opt->page_url .'#kcmenu">'. $opt->to_menu .'</a>';
351
 
352
  // remove '$to_menu' if simbols beatween $to_menu too small (< 300)
353
  $pos = strpos( $temp->content, $match[0] ); // mb_strpos( $temp->content, $match[0] ) - в 150 раз медленнее!
@@ -370,7 +370,9 @@ class Kama_Contents {
370
  function _sanitaze_anchor( $anch ){
371
  $anch = strip_tags( $anch );
372
 
373
- $iso9 = array(
 
 
374
  'А'=>'A', 'Б'=>'B', 'В'=>'V', 'Г'=>'G', 'Д'=>'D', 'Е'=>'E', 'Ё'=>'YO', 'Ж'=>'ZH',
375
  'З'=>'Z', 'И'=>'I', 'Й'=>'J', 'К'=>'K', 'Л'=>'L', 'М'=>'M', 'Н'=>'N', 'О'=>'O',
376
  'П'=>'P', 'Р'=>'R', 'С'=>'S', 'Т'=>'T', 'У'=>'U', 'Ф'=>'F', 'Х'=>'H', 'Ц'=>'TS',
@@ -383,35 +385,36 @@ class Kama_Contents {
383
  // other
384
  'Ѓ'=>'G', 'Ґ'=>'G', 'Є'=>'YE', 'Ѕ'=>'Z', 'Ј'=>'J', 'І'=>'I', 'Ї'=>'YI', 'Ќ'=>'K', 'Љ'=>'L', 'Њ'=>'N', 'Ў'=>'U', 'Џ'=>'DH',
385
  'ѓ'=>'g', 'ґ'=>'g', 'є'=>'ye', 'ѕ'=>'z', 'ј'=>'j', 'і'=>'i', 'ї'=>'yi', 'ќ'=>'k', 'љ'=>'l', 'њ'=>'n', 'ў'=>'u', 'џ'=>'dh'
386
- );
387
 
388
  $anch = strtr( $anch, $iso9 );
389
 
390
  $spec = preg_quote( $this->opt->spec );
391
- $anch = preg_replace("/[^a-zA-Z0-9_$spec\-]+/", '-', $anch ); // все ненужное на '-'
392
  $anch = strtolower( trim( $anch, '-') );
393
  $anch = substr( $anch, 0, 70 ); // shorten
394
- $anch = $this->_unique_anchor( $anch );
 
 
 
395
 
396
  return $anch;
397
  }
398
 
399
  ## adds number at the end if this anchor already exists
400
- function _unique_anchor( $anch ){
401
- $temp = & $this->temp;
402
 
403
  // check and unique anchor
404
- if( empty($temp->anchors) ){
405
- $temp->anchors = array( $anch => 1 );
406
- }
407
- elseif( isset($temp->anchors[ $anch ]) ){
408
  $lastnum = substr( $anch, -1 );
409
  $lastnum = is_numeric($lastnum) ? $lastnum + 1 : 2;
410
- return $this->_unique_anchor( "$anch-$lastnum" );
411
- }
412
- else {
413
- $temp->anchors[ $anch ] = 1;
414
  }
 
 
415
 
416
  return $anch;
417
  }
1
  <?php
2
 
3
+
4
  /**
5
  * Содержание (оглавление) для больших постов.
6
  *
7
+ * оригинал от камы, переименовано так как изменена разметка
8
  * @author: Kama
9
  * @info: http://wp-kama.ru/?p=1513
10
+ * @version: 3.18
11
  *
12
  * @changelog: https://github.com/doiftrue/Kama_Contents/blob/master/CHANGELOG.md
13
  */
14
+ class YTurbo_Contents {
15
 
16
  public $opt = [
17
  // Отступ слева у подразделов в px.
232
  }
233
  else {
234
  $contents =
235
+ ( $_is_tit ? '<h3>'. $_tit .'</h3>'. "\n" : '' ) .
236
+ '<ol>'. "\n".
 
237
  implode('', $this->contents ) .
238
+ '</ol>'."\n";
 
239
  }
240
 
241
  $contents =
300
  if( $level > 0 )
301
  $sub = ( $opt->margin ? ' style="margin-left:'. ($level*$opt->margin) .'px;"' : '') . ' class="sub sub_'. $level .'"';
302
  else
303
+ $sub = '';
304
 
305
  // collect contents
306
  // markup
317
  $this->contents[] = "\t".'
318
  <tr>
319
  <td '. ($_is_mark?' itemprop="itemListElement" itemscope itemtype="http://schema.org/ListItem"':'') .'>
320
+ <a '. ($_is_mark?' itemprop="url"':'') .' href="'. $opt->page_url .'#'. $anchor .'">
321
  '.( $_is_mark ? '<span itemprop="name">'. $cont_elem_txt .'</span>' : $cont_elem_txt ).'
322
  </a>
323
  '.( $_is_mark ? ' <meta itemprop="position" content="'. $temp->counter .'" />':'' ).'
328
  else {
329
  $this->contents[] = "\t".'
330
  <li'. $sub . ($_is_mark?' itemprop="itemListElement" itemscope itemtype="http://schema.org/ListItem"':'') .'>
331
+ <a'. ($_is_mark?' itemprop="url"':'') .' href="'. $opt->page_url .'#'. $anchor .'">
332
  '.( $_is_mark ? '<span itemprop="name">'. $cont_elem_txt .'</span>' : $cont_elem_txt ).'
333
  </a>
334
  '.( $_is_mark ? ' <meta itemprop="position" content="'. $temp->counter .'" />':'' ).'
336
  }
337
 
338
  if( $opt->anchor_link )
339
+ $tag_txt = '<a href="#'. $anchor .'">'. $opt->anchor_link .'</a> ' . $tag_txt;
340
 
341
  // anchor type: 'a' or 'id'
342
  if( $opt->anchor_type === 'a' )
343
+ $new_el = '<a name="'. $anchor .'"></a>'."\n<$tag $attrs>$tag_txt</$tag>";
344
  else
345
+ $new_el = "\n<$tag id=\"$anchor\"$attrs>$tag_txt</$tag>";
346
 
347
  $to_menu = '';
348
  if( $opt->to_menu ){
349
  // go to contents
350
+ $to_menu = '<a href="'. $opt->page_url .'#kcmenu">'. $opt->to_menu .'</a>';
351
 
352
  // remove '$to_menu' if simbols beatween $to_menu too small (< 300)
353
  $pos = strpos( $temp->content, $match[0] ); // mb_strpos( $temp->content, $match[0] ) - в 150 раз медленнее!
370
  function _sanitaze_anchor( $anch ){
371
  $anch = strip_tags( $anch );
372
 
373
+ $anch = apply_filters( 'kama_cont::sanitaze_anchor_before', $anch, $this );
374
+
375
+ $iso9 = [
376
  'А'=>'A', 'Б'=>'B', 'В'=>'V', 'Г'=>'G', 'Д'=>'D', 'Е'=>'E', 'Ё'=>'YO', 'Ж'=>'ZH',
377
  'З'=>'Z', 'И'=>'I', 'Й'=>'J', 'К'=>'K', 'Л'=>'L', 'М'=>'M', 'Н'=>'N', 'О'=>'O',
378
  'П'=>'P', 'Р'=>'R', 'С'=>'S', 'Т'=>'T', 'У'=>'U', 'Ф'=>'F', 'Х'=>'H', 'Ц'=>'TS',
385
  // other
386
  'Ѓ'=>'G', 'Ґ'=>'G', 'Є'=>'YE', 'Ѕ'=>'Z', 'Ј'=>'J', 'І'=>'I', 'Ї'=>'YI', 'Ќ'=>'K', 'Љ'=>'L', 'Њ'=>'N', 'Ў'=>'U', 'Џ'=>'DH',
387
  'ѓ'=>'g', 'ґ'=>'g', 'є'=>'ye', 'ѕ'=>'z', 'ј'=>'j', 'і'=>'i', 'ї'=>'yi', 'ќ'=>'k', 'љ'=>'l', 'њ'=>'n', 'ў'=>'u', 'џ'=>'dh'
388
+ ];
389
 
390
  $anch = strtr( $anch, $iso9 );
391
 
392
  $spec = preg_quote( $this->opt->spec );
393
+ $anch = preg_replace( "/[^a-zA-Z0-9_$spec\-]+/", '-', $anch ); // все ненужное на '-'
394
  $anch = strtolower( trim( $anch, '-') );
395
  $anch = substr( $anch, 0, 70 ); // shorten
396
+
397
+ $anch = apply_filters( 'kama_cont::sanitaze_anchor', $anch, $this );
398
+
399
+ $anch = self::_unique_anchor( $anch );
400
 
401
  return $anch;
402
  }
403
 
404
  ## adds number at the end if this anchor already exists
405
+ static function _unique_anchor( $anch ){
406
+ static $anchors = [];
407
 
408
  // check and unique anchor
409
+ if( isset($anchors[ $anch ]) ){
410
+
 
 
411
  $lastnum = substr( $anch, -1 );
412
  $lastnum = is_numeric($lastnum) ? $lastnum + 1 : 2;
413
+
414
+ return self::_unique_anchor( "$anch-$lastnum" );
 
 
415
  }
416
+ else
417
+ $anchors[ $anch ] = 1;
418
 
419
  return $anch;
420
  }
inc/animate.min.css DELETED
@@ -1,11 +0,0 @@
1
- @charset "UTF-8";
2
-
3
- /*!
4
- * animate.css -http://daneden.me/animate
5
- * Version - 3.7.0
6
- * Licensed under the MIT license - http://opensource.org/licenses/MIT
7
- *
8
- * Copyright (c) 2018 Daniel Eden
9
- */
10
-
11
- @-webkit-keyframes bounce{0%,20%,53%,80%,to{-webkit-animation-timing-function:cubic-bezier(.215,.61,.355,1);-webkit-transform:translateZ(0);animation-timing-function:cubic-bezier(.215,.61,.355,1);transform:translateZ(0)}40%,43%{-webkit-animation-timing-function:cubic-bezier(.755,.05,.855,.06);-webkit-transform:translate3d(0,-30px,0);animation-timing-function:cubic-bezier(.755,.05,.855,.06);transform:translate3d(0,-30px,0)}70%{-webkit-animation-timing-function:cubic-bezier(.755,.05,.855,.06);-webkit-transform:translate3d(0,-15px,0);animation-timing-function:cubic-bezier(.755,.05,.855,.06);transform:translate3d(0,-15px,0)}90%{-webkit-transform:translate3d(0,-4px,0);transform:translate3d(0,-4px,0)}}@keyframes bounce{0%,20%,53%,80%,to{-webkit-animation-timing-function:cubic-bezier(.215,.61,.355,1);-webkit-transform:translateZ(0);animation-timing-function:cubic-bezier(.215,.61,.355,1);transform:translateZ(0)}40%,43%{-webkit-animation-timing-function:cubic-bezier(.755,.05,.855,.06);-webkit-transform:translate3d(0,-30px,0);animation-timing-function:cubic-bezier(.755,.05,.855,.06);transform:translate3d(0,-30px,0)}70%{-webkit-animation-timing-function:cubic-bezier(.755,.05,.855,.06);-webkit-transform:translate3d(0,-15px,0);animation-timing-function:cubic-bezier(.755,.05,.855,.06);transform:translate3d(0,-15px,0)}90%{-webkit-transform:translate3d(0,-4px,0);transform:translate3d(0,-4px,0)}}.bounce{-webkit-animation-name:bounce;-webkit-transform-origin:center bottom;animation-name:bounce;transform-origin:center bottom}@-webkit-keyframes flash{0%,50%,to{opacity:1}25%,75%{opacity:0}}@keyframes flash{0%,50%,to{opacity:1}25%,75%{opacity:0}}.flash{-webkit-animation-name:flash;animation-name:flash}@-webkit-keyframes pulse{0%{-webkit-transform:scaleX(1);transform:scaleX(1)}50%{-webkit-transform:scale3d(1.05,1.05,1.05);transform:scale3d(1.05,1.05,1.05)}to{-webkit-transform:scaleX(1);transform:scaleX(1)}}@keyframes pulse{0%{-webkit-transform:scaleX(1);transform:scaleX(1)}50%{-webkit-transform:scale3d(1.05,1.05,1.05);transform:scale3d(1.05,1.05,1.05)}to{-webkit-transform:scaleX(1);transform:scaleX(1)}}.pulse{-webkit-animation-name:pulse;animation-name:pulse}@-webkit-keyframes rubberBand{0%{-webkit-transform:scaleX(1);transform:scaleX(1)}30%{-webkit-transform:scale3d(1.25,.75,1);transform:scale3d(1.25,.75,1)}40%{-webkit-transform:scale3d(.75,1.25,1);transform:scale3d(.75,1.25,1)}50%{-webkit-transform:scale3d(1.15,.85,1);transform:scale3d(1.15,.85,1)}65%{-webkit-transform:scale3d(.95,1.05,1);transform:scale3d(.95,1.05,1)}75%{-webkit-transform:scale3d(1.05,.95,1);transform:scale3d(1.05,.95,1)}to{-webkit-transform:scaleX(1);transform:scaleX(1)}}@keyframes rubberBand{0%{-webkit-transform:scaleX(1);transform:scaleX(1)}30%{-webkit-transform:scale3d(1.25,.75,1);transform:scale3d(1.25,.75,1)}40%{-webkit-transform:scale3d(.75,1.25,1);transform:scale3d(.75,1.25,1)}50%{-webkit-transform:scale3d(1.15,.85,1);transform:scale3d(1.15,.85,1)}65%{-webkit-transform:scale3d(.95,1.05,1);transform:scale3d(.95,1.05,1)}75%{-webkit-transform:scale3d(1.05,.95,1);transform:scale3d(1.05,.95,1)}to{-webkit-transform:scaleX(1);transform:scaleX(1)}}.rubberBand{-webkit-animation-name:rubberBand;animation-name:rubberBand}@-webkit-keyframes shake{0%,to{-webkit-transform:translateZ(0);transform:translateZ(0)}10%,30%,50%,70%,90%{-webkit-transform:translate3d(-10px,0,0);transform:translate3d(-10px,0,0)}20%,40%,60%,80%{-webkit-transform:translate3d(10px,0,0);transform:translate3d(10px,0,0)}}@keyframes shake{0%,to{-webkit-transform:translateZ(0);transform:translateZ(0)}10%,30%,50%,70%,90%{-webkit-transform:translate3d(-10px,0,0);transform:translate3d(-10px,0,0)}20%,40%,60%,80%{-webkit-transform:translate3d(10px,0,0);transform:translate3d(10px,0,0)}}.shake{-webkit-animation-name:shake;animation-name:shake}@-webkit-keyframes headShake{0%{-webkit-transform:translateX(0);transform:translateX(0)}6.5%{-webkit-transform:translateX(-6px) rotateY(-9deg);transform:translateX(-6px) rotateY(-9deg)}18.5%{-webkit-transform:translateX(5px) rotateY(7deg);transform:translateX(5px) rotateY(7deg)}31.5%{-webkit-transform:translateX(-3px) rotateY(-5deg);transform:translateX(-3px) rotateY(-5deg)}43.5%{-webkit-transform:translateX(2px) rotateY(3deg);transform:translateX(2px) rotateY(3deg)}50%{-webkit-transform:translateX(0);transform:translateX(0)}}@keyframes headShake{0%{-webkit-transform:translateX(0);transform:translateX(0)}6.5%{-webkit-transform:translateX(-6px) rotateY(-9deg);transform:translateX(-6px) rotateY(-9deg)}18.5%{-webkit-transform:translateX(5px) rotateY(7deg);transform:translateX(5px) rotateY(7deg)}31.5%{-webkit-transform:translateX(-3px) rotateY(-5deg);transform:translateX(-3px) rotateY(-5deg)}43.5%{-webkit-transform:translateX(2px) rotateY(3deg);transform:translateX(2px) rotateY(3deg)}50%{-webkit-transform:translateX(0);transform:translateX(0)}}.headShake{-webkit-animation-name:headShake;-webkit-animation-timing-function:ease-in-out;animation-name:headShake;animation-timing-function:ease-in-out}@-webkit-keyframes swing{20%{-webkit-transform:rotate(15deg);transform:rotate(15deg)}40%{-webkit-transform:rotate(-10deg);transform:rotate(-10deg)}60%{-webkit-transform:rotate(5deg);transform:rotate(5deg)}80%{-webkit-transform:rotate(-5deg);transform:rotate(-5deg)}to{-webkit-transform:rotate(0deg);transform:rotate(0deg)}}@keyframes swing{20%{-webkit-transform:rotate(15deg);transform:rotate(15deg)}40%{-webkit-transform:rotate(-10deg);transform:rotate(-10deg)}60%{-webkit-transform:rotate(5deg);transform:rotate(5deg)}80%{-webkit-transform:rotate(-5deg);transform:rotate(-5deg)}to{-webkit-transform:rotate(0deg);transform:rotate(0deg)}}.swing{-webkit-animation-name:swing;-webkit-transform-origin:top center;animation-name:swing;transform-origin:top center}@-webkit-keyframes tada{0%{-webkit-transform:scaleX(1);transform:scaleX(1)}10%,20%{-webkit-transform:scale3d(.9,.9,.9) rotate(-3deg);transform:scale3d(.9,.9,.9) rotate(-3deg)}30%,50%,70%,90%{-webkit-transform:scale3d(1.1,1.1,1.1) rotate(3deg);transform:scale3d(1.1,1.1,1.1) rotate(3deg)}40%,60%,80%{-webkit-transform:scale3d(1.1,1.1,1.1) rotate(-3deg);transform:scale3d(1.1,1.1,1.1) rotate(-3deg)}to{-webkit-transform:scaleX(1);transform:scaleX(1)}}@keyframes tada{0%{-webkit-transform:scaleX(1);transform:scaleX(1)}10%,20%{-webkit-transform:scale3d(.9,.9,.9) rotate(-3deg);transform:scale3d(.9,.9,.9) rotate(-3deg)}30%,50%,70%,90%{-webkit-transform:scale3d(1.1,1.1,1.1) rotate(3deg);transform:scale3d(1.1,1.1,1.1) rotate(3deg)}40%,60%,80%{-webkit-transform:scale3d(1.1,1.1,1.1) rotate(-3deg);transform:scale3d(1.1,1.1,1.1) rotate(-3deg)}to{-webkit-transform:scaleX(1);transform:scaleX(1)}}.tada{-webkit-animation-name:tada;animation-name:tada}@-webkit-keyframes wobble{0%{-webkit-transform:translateZ(0);transform:translateZ(0)}15%{-webkit-transform:translate3d(-25%,0,0) rotate(-5deg);transform:translate3d(-25%,0,0) rotate(-5deg)}30%{-webkit-transform:translate3d(20%,0,0) rotate(3deg);transform:translate3d(20%,0,0) rotate(3deg)}45%{-webkit-transform:translate3d(-15%,0,0) rotate(-3deg);transform:translate3d(-15%,0,0) rotate(-3deg)}60%{-webkit-transform:translate3d(10%,0,0) rotate(2deg);transform:translate3d(10%,0,0) rotate(2deg)}75%{-webkit-transform:translate3d(-5%,0,0) rotate(-1deg);transform:translate3d(-5%,0,0) rotate(-1deg)}to{-webkit-transform:translateZ(0);transform:translateZ(0)}}@keyframes wobble{0%{-webkit-transform:translateZ(0);transform:translateZ(0)}15%{-webkit-transform:translate3d(-25%,0,0) rotate(-5deg);transform:translate3d(-25%,0,0) rotate(-5deg)}30%{-webkit-transform:translate3d(20%,0,0) rotate(3deg);transform:translate3d(20%,0,0) rotate(3deg)}45%{-webkit-transform:translate3d(-15%,0,0) rotate(-3deg);transform:translate3d(-15%,0,0) rotate(-3deg)}60%{-webkit-transform:translate3d(10%,0,0) rotate(2deg);transform:translate3d(10%,0,0) rotate(2deg)}75%{-webkit-transform:translate3d(-5%,0,0) rotate(-1deg);transform:translate3d(-5%,0,0) rotate(-1deg)}to{-webkit-transform:translateZ(0);transform:translateZ(0)}}.wobble{-webkit-animation-name:wobble;animation-name:wobble}@-webkit-keyframes jello{0%,11.1%,to{-webkit-transform:translateZ(0);transform:translateZ(0)}22.2%{-webkit-transform:skewX(-12.5deg) skewY(-12.5deg);transform:skewX(-12.5deg) skewY(-12.5deg)}33.3%{-webkit-transform:skewX(6.25deg) skewY(6.25deg);transform:skewX(6.25deg) skewY(6.25deg)}44.4%{-webkit-transform:skewX(-3.125deg) skewY(-3.125deg);transform:skewX(-3.125deg) skewY(-3.125deg)}55.5%{-webkit-transform:skewX(1.5625deg) skewY(1.5625deg);transform:skewX(1.5625deg) skewY(1.5625deg)}66.6%{-webkit-transform:skewX(-.78125deg) skewY(-.78125deg);transform:skewX(-.78125deg) skewY(-.78125deg)}77.7%{-webkit-transform:skewX(.390625deg) skewY(.390625deg);transform:skewX(.390625deg) skewY(.390625deg)}88.8%{-webkit-transform:skewX(-.1953125deg) skewY(-.1953125deg);transform:skewX(-.1953125deg) skewY(-.1953125deg)}}@keyframes jello{0%,11.1%,to{-webkit-transform:translateZ(0);transform:translateZ(0)}22.2%{-webkit-transform:skewX(-12.5deg) skewY(-12.5deg);transform:skewX(-12.5deg) skewY(-12.5deg)}33.3%{-webkit-transform:skewX(6.25deg) skewY(6.25deg);transform:skewX(6.25deg) skewY(6.25deg)}44.4%{-webkit-transform:skewX(-3.125deg) skewY(-3.125deg);transform:skewX(-3.125deg) skewY(-3.125deg)}55.5%{-webkit-transform:skewX(1.5625deg) skewY(1.5625deg);transform:skewX(1.5625deg) skewY(1.5625deg)}66.6%{-webkit-transform:skewX(-.78125deg) skewY(-.78125deg);transform:skewX(-.78125deg) skewY(-.78125deg)}77.7%{-webkit-transform:skewX(.390625deg) skewY(.390625deg);transform:skewX(.390625deg) skewY(.390625deg)}88.8%{-webkit-transform:skewX(-.1953125deg) skewY(-.1953125deg);transform:skewX(-.1953125deg) skewY(-.1953125deg)}}.jello{-webkit-animation-name:jello;-webkit-transform-origin:center;animation-name:jello;transform-origin:center}@-webkit-keyframes heartBeat{0%{-webkit-transform:scale(1);transform:scale(1)}14%{-webkit-transform:scale(1.3);transform:scale(1.3)}28%{-webkit-transform:scale(1);transform:scale(1)}42%{-webkit-transform:scale(1.3);transform:scale(1.3)}70%{-webkit-transform:scale(1);transform:scale(1)}}@keyframes heartBeat{0%{-webkit-transform:scale(1);transform:scale(1)}14%{-webkit-transform:scale(1.3);transform:scale(1.3)}28%{-webkit-transform:scale(1);transform:scale(1)}42%{-webkit-transform:scale(1.3);transform:scale(1.3)}70%{-webkit-transform:scale(1);transform:scale(1)}}.heartBeat{-webkit-animation-duration:1.3s;-webkit-animation-name:heartBeat;-webkit-animation-timing-function:ease-in-out;animation-duration:1.3s;animation-name:heartBeat;animation-timing-function:ease-in-out}@-webkit-keyframes bounceIn{0%,20%,40%,60%,80%,to{-webkit-animation-timing-function:cubic-bezier(.215,.61,.355,1);animation-timing-function:cubic-bezier(.215,.61,.355,1)}0%{-webkit-transform:scale3d(.3,.3,.3);opacity:0;transform:scale3d(.3,.3,.3)}20%{-webkit-transform:scale3d(1.1,1.1,1.1);transform:scale3d(1.1,1.1,1.1)}40%{-webkit-transform:scale3d(.9,.9,.9);transform:scale3d(.9,.9,.9)}60%{-webkit-transform:scale3d(1.03,1.03,1.03);opacity:1;transform:scale3d(1.03,1.03,1.03)}80%{-webkit-transform:scale3d(.97,.97,.97);transform:scale3d(.97,.97,.97)}to{-webkit-transform:scaleX(1);opacity:1;transform:scaleX(1)}}@keyframes bounceIn{0%,20%,40%,60%,80%,to{-webkit-animation-timing-function:cubic-bezier(.215,.61,.355,1);animation-timing-function:cubic-bezier(.215,.61,.355,1)}0%{-webkit-transform:scale3d(.3,.3,.3);opacity:0;transform:scale3d(.3,.3,.3)}20%{-webkit-transform:scale3d(1.1,1.1,1.1);transform:scale3d(1.1,1.1,1.1)}40%{-webkit-transform:scale3d(.9,.9,.9);transform:scale3d(.9,.9,.9)}60%{-webkit-transform:scale3d(1.03,1.03,1.03);opacity:1;transform:scale3d(1.03,1.03,1.03)}80%{-webkit-transform:scale3d(.97,.97,.97);transform:scale3d(.97,.97,.97)}to{-webkit-transform:scaleX(1);opacity:1;transform:scaleX(1)}}.bounceIn{-webkit-animation-duration:.75s;-webkit-animation-name:bounceIn;animation-duration:.75s;animation-name:bounceIn}@-webkit-keyframes bounceInDown{0%,60%,75%,90%,to{-webkit-animation-timing-function:cubic-bezier(.215,.61,.355,1);animation-timing-function:cubic-bezier(.215,.61,.355,1)}0%{-webkit-transform:translate3d(0,-3000px,0);opacity:0;transform:translate3d(0,-3000px,0)}60%{-webkit-transform:translate3d(0,25px,0);opacity:1;transform:translate3d(0,25px,0)}75%{-webkit-transform:translate3d(0,-10px,0);transform:translate3d(0,-10px,0)}90%{-webkit-transform:translate3d(0,5px,0);transform:translate3d(0,5px,0)}to{-webkit-transform:translateZ(0);transform:translateZ(0)}}@keyframes bounceInDown{0%,60%,75%,90%,to{-webkit-animation-timing-function:cubic-bezier(.215,.61,.355,1);animation-timing-function:cubic-bezier(.215,.61,.355,1)}0%{-webkit-transform:translate3d(0,-3000px,0);opacity:0;transform:translate3d(0,-3000px,0)}60%{-webkit-transform:translate3d(0,25px,0);opacity:1;transform:translate3d(0,25px,0)}75%{-webkit-transform:translate3d(0,-10px,0);transform:translate3d(0,-10px,0)}90%{-webkit-transform:translate3d(0,5px,0);transform:translate3d(0,5px,0)}to{-webkit-transform:translateZ(0);transform:translateZ(0)}}.bounceInDown{-webkit-animation-name:bounceInDown;animation-name:bounceInDown}@-webkit-keyframes bounceInLeft{0%,60%,75%,90%,to{-webkit-animation-timing-function:cubic-bezier(.215,.61,.355,1);animation-timing-function:cubic-bezier(.215,.61,.355,1)}0%{-webkit-transform:translate3d(-3000px,0,0);opacity:0;transform:translate3d(-3000px,0,0)}60%{-webkit-transform:translate3d(25px,0,0);opacity:1;transform:translate3d(25px,0,0)}75%{-webkit-transform:translate3d(-10px,0,0);transform:translate3d(-10px,0,0)}90%{-webkit-transform:translate3d(5px,0,0);transform:translate3d(5px,0,0)}to{-webkit-transform:translateZ(0);transform:translateZ(0)}}@keyframes bounceInLeft{0%,60%,75%,90%,to{-webkit-animation-timing-function:cubic-bezier(.215,.61,.355,1);animation-timing-function:cubic-bezier(.215,.61,.355,1)}0%{-webkit-transform:translate3d(-3000px,0,0);opacity:0;transform:translate3d(-3000px,0,0)}60%{-webkit-transform:translate3d(25px,0,0);opacity:1;transform:translate3d(25px,0,0)}75%{-webkit-transform:translate3d(-10px,0,0);transform:translate3d(-10px,0,0)}90%{-webkit-transform:translate3d(5px,0,0);transform:translate3d(5px,0,0)}to{-webkit-transform:translateZ(0);transform:translateZ(0)}}.bounceInLeft{-webkit-animation-name:bounceInLeft;animation-name:bounceInLeft}@-webkit-keyframes bounceInRight{0%,60%,75%,90%,to{-webkit-animation-timing-function:cubic-bezier(.215,.61,.355,1);animation-timing-function:cubic-bezier(.215,.61,.355,1)}0%{-webkit-transform:translate3d(3000px,0,0);opacity:0;transform:translate3d(3000px,0,0)}60%{-webkit-transform:translate3d(-25px,0,0);opacity:1;transform:translate3d(-25px,0,0)}75%{-webkit-transform:translate3d(10px,0,0);transform:translate3d(10px,0,0)}90%{-webkit-transform:translate3d(-5px,0,0);transform:translate3d(-5px,0,0)}to{-webkit-transform:translateZ(0);transform:translateZ(0)}}@keyframes bounceInRight{0%,60%,75%,90%,to{-webkit-animation-timing-function:cubic-bezier(.215,.61,.355,1);animation-timing-function:cubic-bezier(.215,.61,.355,1)}0%{-webkit-transform:translate3d(3000px,0,0);opacity:0;transform:translate3d(3000px,0,0)}60%{-webkit-transform:translate3d(-25px,0,0);opacity:1;transform:translate3d(-25px,0,0)}75%{-webkit-transform:translate3d(10px,0,0);transform:translate3d(10px,0,0)}90%{-webkit-transform:translate3d(-5px,0,0);transform:translate3d(-5px,0,0)}to{-webkit-transform:translateZ(0);transform:translateZ(0)}}.bounceInRight{-webkit-animation-name:bounceInRight;animation-name:bounceInRight}@-webkit-keyframes bounceInUp{0%,60%,75%,90%,to{-webkit-animation-timing-function:cubic-bezier(.215,.61,.355,1);animation-timing-function:cubic-bezier(.215,.61,.355,1)}0%{-webkit-transform:translate3d(0,3000px,0);opacity:0;transform:translate3d(0,3000px,0)}60%{-webkit-transform:translate3d(0,-20px,0);opacity:1;transform:translate3d(0,-20px,0)}75%{-webkit-transform:translate3d(0,10px,0);transform:translate3d(0,10px,0)}90%{-webkit-transform:translate3d(0,-5px,0);transform:translate3d(0,-5px,0)}to{-webkit-transform:translateZ(0);transform:translateZ(0)}}@keyframes bounceInUp{0%,60%,75%,90%,to{-webkit-animation-timing-function:cubic-bezier(.215,.61,.355,1);animation-timing-function:cubic-bezier(.215,.61,.355,1)}0%{-webkit-transform:translate3d(0,3000px,0);opacity:0;transform:translate3d(0,3000px,0)}60%{-webkit-transform:translate3d(0,-20px,0);opacity:1;transform:translate3d(0,-20px,0)}75%{-webkit-transform:translate3d(0,10px,0);transform:translate3d(0,10px,0)}90%{-webkit-transform:translate3d(0,-5px,0);transform:translate3d(0,-5px,0)}to{-webkit-transform:translateZ(0);transform:translateZ(0)}}.bounceInUp{-webkit-animation-name:bounceInUp;animation-name:bounceInUp}@-webkit-keyframes bounceOut{20%{-webkit-transform:scale3d(.9,.9,.9);transform:scale3d(.9,.9,.9)}50%,55%{-webkit-transform:scale3d(1.1,1.1,1.1);opacity:1;transform:scale3d(1.1,1.1,1.1)}to{-webkit-transform:scale3d(.3,.3,.3);opacity:0;transform:scale3d(.3,.3,.3)}}@keyframes bounceOut{20%{-webkit-transform:scale3d(.9,.9,.9);transform:scale3d(.9,.9,.9)}50%,55%{-webkit-transform:scale3d(1.1,1.1,1.1);opacity:1;transform:scale3d(1.1,1.1,1.1)}to{-webkit-transform:scale3d(.3,.3,.3);opacity:0;transform:scale3d(.3,.3,.3)}}.bounceOut{-webkit-animation-duration:.75s;-webkit-animation-name:bounceOut;animation-duration:.75s;animation-name:bounceOut}@-webkit-keyframes bounceOutDown{20%{-webkit-transform:translate3d(0,10px,0);transform:translate3d(0,10px,0)}40%,45%{-webkit-transform:translate3d(0,-20px,0);opacity:1;transform:translate3d(0,-20px,0)}to{-webkit-transform:translate3d(0,2000px,0);opacity:0;transform:translate3d(0,2000px,0)}}@keyframes bounceOutDown{20%{-webkit-transform:translate3d(0,10px,0);transform:translate3d(0,10px,0)}40%,45%{-webkit-transform:translate3d(0,-20px,0);opacity:1;transform:translate3d(0,-20px,0)}to{-webkit-transform:translate3d(0,2000px,0);opacity:0;transform:translate3d(0,2000px,0)}}.bounceOutDown{-webkit-animation-name:bounceOutDown;animation-name:bounceOutDown}@-webkit-keyframes bounceOutLeft{20%{-webkit-transform:translate3d(20px,0,0);opacity:1;transform:translate3d(20px,0,0)}to{-webkit-transform:translate3d(-2000px,0,0);opacity:0;transform:translate3d(-2000px,0,0)}}@keyframes bounceOutLeft{20%{-webkit-transform:translate3d(20px,0,0);opacity:1;transform:translate3d(20px,0,0)}to{-webkit-transform:translate3d(-2000px,0,0);opacity:0;transform:translate3d(-2000px,0,0)}}.bounceOutLeft{-webkit-animation-name:bounceOutLeft;animation-name:bounceOutLeft}@-webkit-keyframes bounceOutRight{20%{-webkit-transform:translate3d(-20px,0,0);opacity:1;transform:translate3d(-20px,0,0)}to{-webkit-transform:translate3d(2000px,0,0);opacity:0;transform:translate3d(2000px,0,0)}}@keyframes bounceOutRight{20%{-webkit-transform:translate3d(-20px,0,0);opacity:1;transform:translate3d(-20px,0,0)}to{-webkit-transform:translate3d(2000px,0,0);opacity:0;transform:translate3d(2000px,0,0)}}.bounceOutRight{-webkit-animation-name:bounceOutRight;animation-name:bounceOutRight}@-webkit-keyframes bounceOutUp{20%{-webkit-transform:translate3d(0,-10px,0);transform:translate3d(0,-10px,0)}40%,45%{-webkit-transform:translate3d(0,20px,0);opacity:1;transform:translate3d(0,20px,0)}to{-webkit-transform:translate3d(0,-2000px,0);opacity:0;transform:translate3d(0,-2000px,0)}}@keyframes bounceOutUp{20%{-webkit-transform:translate3d(0,-10px,0);transform:translate3d(0,-10px,0)}40%,45%{-webkit-transform:translate3d(0,20px,0);opacity:1;transform:translate3d(0,20px,0)}to{-webkit-transform:translate3d(0,-2000px,0);opacity:0;transform:translate3d(0,-2000px,0)}}.bounceOutUp{-webkit-animation-name:bounceOutUp;animation-name:bounceOutUp}@-webkit-keyframes fadeIn{0%{opacity:0}to{opacity:1}}@keyframes fadeIn{0%{opacity:0}to{opacity:1}}.fadeIn{-webkit-animation-name:fadeIn;animation-name:fadeIn}@-webkit-keyframes fadeInDown{0%{-webkit-transform:translate3d(0,-100%,0);opacity:0;transform:translate3d(0,-100%,0)}to{-webkit-transform:translateZ(0);opacity:1;transform:translateZ(0)}}@keyframes fadeInDown{0%{-webkit-transform:translate3d(0,-100%,0);opacity:0;transform:translate3d(0,-100%,0)}to{-webkit-transform:translateZ(0);opacity:1;transform:translateZ(0)}}.fadeInDown{-webkit-animation-name:fadeInDown;animation-name:fadeInDown}@-webkit-keyframes fadeInDownBig{0%{-webkit-transform:translate3d(0,-2000px,0);opacity:0;transform:translate3d(0,-2000px,0)}to{-webkit-transform:translateZ(0);opacity:1;transform:translateZ(0)}}@keyframes fadeInDownBig{0%{-webkit-transform:translate3d(0,-2000px,0);opacity:0;transform:translate3d(0,-2000px,0)}to{-webkit-transform:translateZ(0);opacity:1;transform:translateZ(0)}}.fadeInDownBig{-webkit-animation-name:fadeInDownBig;animation-name:fadeInDownBig}@-webkit-keyframes fadeInLeft{0%{-webkit-transform:translate3d(-100%,0,0);opacity:0;transform:translate3d(-100%,0,0)}to{-webkit-transform:translateZ(0);opacity:1;transform:translateZ(0)}}@keyframes fadeInLeft{0%{-webkit-transform:translate3d(-100%,0,0);opacity:0;transform:translate3d(-100%,0,0)}to{-webkit-transform:translateZ(0);opacity:1;transform:translateZ(0)}}.fadeInLeft{-webkit-animation-name:fadeInLeft;animation-name:fadeInLeft}@-webkit-keyframes fadeInLeftBig{0%{-webkit-transform:translate3d(-2000px,0,0);opacity:0;transform:translate3d(-2000px,0,0)}to{-webkit-transform:translateZ(0);opacity:1;transform:translateZ(0)}}@keyframes fadeInLeftBig{0%{-webkit-transform:translate3d(-2000px,0,0);opacity:0;transform:translate3d(-2000px,0,0)}to{-webkit-transform:translateZ(0);opacity:1;transform:translateZ(0)}}.fadeInLeftBig{-webkit-animation-name:fadeInLeftBig;animation-name:fadeInLeftBig}@-webkit-keyframes fadeInRight{0%{-webkit-transform:translate3d(100%,0,0);opacity:0;transform:translate3d(100%,0,0)}to{-webkit-transform:translateZ(0);opacity:1;transform:translateZ(0)}}@keyframes fadeInRight{0%{-webkit-transform:translate3d(100%,0,0);opacity:0;transform:translate3d(100%,0,0)}to{-webkit-transform:translateZ(0);opacity:1;transform:translateZ(0)}}.fadeInRight{-webkit-animation-name:fadeInRight;animation-name:fadeInRight}@-webkit-keyframes fadeInRightBig{0%{-webkit-transform:translate3d(2000px,0,0);opacity:0;transform:translate3d(2000px,0,0)}to{-webkit-transform:translateZ(0);opacity:1;transform:translateZ(0)}}@keyframes fadeInRightBig{0%{-webkit-transform:translate3d(2000px,0,0);opacity:0;transform:translate3d(2000px,0,0)}to{-webkit-transform:translateZ(0);opacity:1;transform:translateZ(0)}}.fadeInRightBig{-webkit-animation-name:fadeInRightBig;animation-name:fadeInRightBig}@-webkit-keyframes fadeInUp{0%{-webkit-transform:translate3d(0,100%,0);opacity:0;transform:translate3d(0,100%,0)}to{-webkit-transform:translateZ(0);opacity:1;transform:translateZ(0)}}@keyframes fadeInUp{0%{-webkit-transform:translate3d(0,100%,0);opacity:0;transform:translate3d(0,100%,0)}to{-webkit-transform:translateZ(0);opacity:1;transform:translateZ(0)}}.fadeInUp{-webkit-animation-name:fadeInUp;animation-name:fadeInUp}@-webkit-keyframes fadeInUpBig{0%{-webkit-transform:translate3d(0,2000px,0);opacity:0;transform:translate3d(0,2000px,0)}to{-webkit-transform:translateZ(0);opacity:1;transform:translateZ(0)}}@keyframes fadeInUpBig{0%{-webkit-transform:translate3d(0,2000px,0);opacity:0;transform:translate3d(0,2000px,0)}to{-webkit-transform:translateZ(0);opacity:1;transform:translateZ(0)}}.fadeInUpBig{-webkit-animation-name:fadeInUpBig;animation-name:fadeInUpBig}@-webkit-keyframes fadeOut{0%{opacity:1}to{opacity:0}}@keyframes fadeOut{0%{opacity:1}to{opacity:0}}.fadeOut{-webkit-animation-name:fadeOut;animation-name:fadeOut}@-webkit-keyframes fadeOutDown{0%{opacity:1}to{-webkit-transform:translate3d(0,100%,0);opacity:0;transform:translate3d(0,100%,0)}}@keyframes fadeOutDown{0%{opacity:1}to{-webkit-transform:translate3d(0,100%,0);opacity:0;transform:translate3d(0,100%,0)}}.fadeOutDown{-webkit-animation-name:fadeOutDown;animation-name:fadeOutDown}@-webkit-keyframes fadeOutDownBig{0%{opacity:1}to{-webkit-transform:translate3d(0,2000px,0);opacity:0;transform:translate3d(0,2000px,0)}}@keyframes fadeOutDownBig{0%{opacity:1}to{-webkit-transform:translate3d(0,2000px,0);opacity:0;transform:translate3d(0,2000px,0)}}.fadeOutDownBig{-webkit-animation-name:fadeOutDownBig;animation-name:fadeOutDownBig}@-webkit-keyframes fadeOutLeft{0%{opacity:1}to{-webkit-transform:translate3d(-100%,0,0);opacity:0;transform:translate3d(-100%,0,0)}}@keyframes fadeOutLeft{0%{opacity:1}to{-webkit-transform:translate3d(-100%,0,0);opacity:0;transform:translate3d(-100%,0,0)}}.fadeOutLeft{-webkit-animation-name:fadeOutLeft;animation-name:fadeOutLeft}@-webkit-keyframes fadeOutLeftBig{0%{opacity:1}to{-webkit-transform:translate3d(-2000px,0,0);opacity:0;transform:translate3d(-2000px,0,0)}}@keyframes fadeOutLeftBig{0%{opacity:1}to{-webkit-transform:translate3d(-2000px,0,0);opacity:0;transform:translate3d(-2000px,0,0)}}.fadeOutLeftBig{-webkit-animation-name:fadeOutLeftBig;animation-name:fadeOutLeftBig}@-webkit-keyframes fadeOutRight{0%{opacity:1}to{-webkit-transform:translate3d(100%,0,0);opacity:0;transform:translate3d(100%,0,0)}}@keyframes fadeOutRight{0%{opacity:1}to{-webkit-transform:translate3d(100%,0,0);opacity:0;transform:translate3d(100%,0,0)}}.fadeOutRight{-webkit-animation-name:fadeOutRight;animation-name:fadeOutRight}@-webkit-keyframes fadeOutRightBig{0%{opacity:1}to{-webkit-transform:translate3d(2000px,0,0);opacity:0;transform:translate3d(2000px,0,0)}}@keyframes fadeOutRightBig{0%{opacity:1}to{-webkit-transform:translate3d(2000px,0,0);opacity:0;transform:translate3d(2000px,0,0)}}.fadeOutRightBig{-webkit-animation-name:fadeOutRightBig;animation-name:fadeOutRightBig}@-webkit-keyframes fadeOutUp{0%{opacity:1}to{-webkit-transform:translate3d(0,-100%,0);opacity:0;transform:translate3d(0,-100%,0)}}@keyframes fadeOutUp{0%{opacity:1}to{-webkit-transform:translate3d(0,-100%,0);opacity:0;transform:translate3d(0,-100%,0)}}.fadeOutUp{-webkit-animation-name:fadeOutUp;animation-name:fadeOutUp}@-webkit-keyframes fadeOutUpBig{0%{opacity:1}to{-webkit-transform:translate3d(0,-2000px,0);opacity:0;transform:translate3d(0,-2000px,0)}}@keyframes fadeOutUpBig{0%{opacity:1}to{-webkit-transform:translate3d(0,-2000px,0);opacity:0;transform:translate3d(0,-2000px,0)}}.fadeOutUpBig{-webkit-animation-name:fadeOutUpBig;animation-name:fadeOutUpBig}@-webkit-keyframes flip{0%{-webkit-animation-timing-function:ease-out;-webkit-transform:perspective(400px) scaleX(1) translateZ(0) rotateY(-1turn);animation-timing-function:ease-out;transform:perspective(400px) scaleX(1) translateZ(0) rotateY(-1turn)}40%{-webkit-animation-timing-function:ease-out;-webkit-transform:perspective(400px) scaleX(1) translateZ(150px) rotateY(-190deg);animation-timing-function:ease-out;transform:perspective(400px) scaleX(1) translateZ(150px) rotateY(-190deg)}50%{-webkit-animation-timing-function:ease-in;-webkit-transform:perspective(400px) scaleX(1) translateZ(150px) rotateY(-170deg);animation-timing-function:ease-in;transform:perspective(400px) scaleX(1) translateZ(150px) rotateY(-170deg)}80%{-webkit-animation-timing-function:ease-in;-webkit-transform:perspective(400px) scale3d(.95,.95,.95) translateZ(0) rotateY(0deg);animation-timing-function:ease-in;transform:perspective(400px) scale3d(.95,.95,.95) translateZ(0) rotateY(0deg)}to{-webkit-animation-timing-function:ease-in;-webkit-transform:perspective(400px) scaleX(1) translateZ(0) rotateY(0deg);animation-timing-function:ease-in;transform:perspective(400px) scaleX(1) translateZ(0) rotateY(0deg)}}@keyframes flip{0%{-webkit-animation-timing-function:ease-out;-webkit-transform:perspective(400px) scaleX(1) translateZ(0) rotateY(-1turn);animation-timing-function:ease-out;transform:perspective(400px) scaleX(1) translateZ(0) rotateY(-1turn)}40%{-webkit-animation-timing-function:ease-out;-webkit-transform:perspective(400px) scaleX(1) translateZ(150px) rotateY(-190deg);animation-timing-function:ease-out;transform:perspective(400px) scaleX(1) translateZ(150px) rotateY(-190deg)}50%{-webkit-animation-timing-function:ease-in;-webkit-transform:perspective(400px) scaleX(1) translateZ(150px) rotateY(-170deg);animation-timing-function:ease-in;transform:perspective(400px) scaleX(1) translateZ(150px) rotateY(-170deg)}80%{-webkit-animation-timing-function:ease-in;-webkit-transform:perspective(400px) scale3d(.95,.95,.95) translateZ(0) rotateY(0deg);animation-timing-function:ease-in;transform:perspective(400px) scale3d(.95,.95,.95) translateZ(0) rotateY(0deg)}to{-webkit-animation-timing-function:ease-in;-webkit-transform:perspective(400px) scaleX(1) translateZ(0) rotateY(0deg);animation-timing-function:ease-in;transform:perspective(400px) scaleX(1) translateZ(0) rotateY(0deg)}}.animated.flip{-webkit-animation-name:flip;-webkit-backface-visibility:visible;animation-name:flip;backface-visibility:visible}@-webkit-keyframes flipInX{0%{-webkit-animation-timing-function:ease-in;-webkit-transform:perspective(400px) rotateX(90deg);animation-timing-function:ease-in;opacity:0;transform:perspective(400px) rotateX(90deg)}40%{-webkit-animation-timing-function:ease-in;-webkit-transform:perspective(400px) rotateX(-20deg);animation-timing-function:ease-in;transform:perspective(400px) rotateX(-20deg)}60%{-webkit-transform:perspective(400px) rotateX(10deg);opacity:1;transform:perspective(400px) rotateX(10deg)}80%{-webkit-transform:perspective(400px) rotateX(-5deg);transform:perspective(400px) rotateX(-5deg)}to{-webkit-transform:perspective(400px);transform:perspective(400px)}}@keyframes flipInX{0%{-webkit-animation-timing-function:ease-in;-webkit-transform:perspective(400px) rotateX(90deg);animation-timing-function:ease-in;opacity:0;transform:perspective(400px) rotateX(90deg)}40%{-webkit-animation-timing-function:ease-in;-webkit-transform:perspective(400px) rotateX(-20deg);animation-timing-function:ease-in;transform:perspective(400px) rotateX(-20deg)}60%{-webkit-transform:perspective(400px) rotateX(10deg);opacity:1;transform:perspective(400px) rotateX(10deg)}80%{-webkit-transform:perspective(400px) rotateX(-5deg);transform:perspective(400px) rotateX(-5deg)}to{-webkit-transform:perspective(400px);transform:perspective(400px)}}.flipInX{-webkit-animation-name:flipInX;-webkit-backface-visibility:visible!important;animation-name:flipInX;backface-visibility:visible!important}@-webkit-keyframes flipInY{0%{-webkit-animation-timing-function:ease-in;-webkit-transform:perspective(400px) rotateY(90deg);animation-timing-function:ease-in;opacity:0;transform:perspective(400px) rotateY(90deg)}40%{-webkit-animation-timing-function:ease-in;-webkit-transform:perspective(400px) rotateY(-20deg);animation-timing-function:ease-in;transform:perspective(400px) rotateY(-20deg)}60%{-webkit-transform:perspective(400px) rotateY(10deg);opacity:1;transform:perspective(400px) rotateY(10deg)}80%{-webkit-transform:perspective(400px) rotateY(-5deg);transform:perspective(400px) rotateY(-5deg)}to{-webkit-transform:perspective(400px);transform:perspective(400px)}}@keyframes flipInY{0%{-webkit-animation-timing-function:ease-in;-webkit-transform:perspective(400px) rotateY(90deg);animation-timing-function:ease-in;opacity:0;transform:perspective(400px) rotateY(90deg)}40%{-webkit-animation-timing-function:ease-in;-webkit-transform:perspective(400px) rotateY(-20deg);animation-timing-function:ease-in;transform:perspective(400px) rotateY(-20deg)}60%{-webkit-transform:perspective(400px) rotateY(10deg);opacity:1;transform:perspective(400px) rotateY(10deg)}80%{-webkit-transform:perspective(400px) rotateY(-5deg);transform:perspective(400px) rotateY(-5deg)}to{-webkit-transform:perspective(400px);transform:perspective(400px)}}.flipInY{-webkit-animation-name:flipInY;-webkit-backface-visibility:visible!important;animation-name:flipInY;backface-visibility:visible!important}@-webkit-keyframes flipOutX{0%{-webkit-transform:perspective(400px);transform:perspective(400px)}30%{-webkit-transform:perspective(400px) rotateX(-20deg);opacity:1;transform:perspective(400px) rotateX(-20deg)}to{-webkit-transform:perspective(400px) rotateX(90deg);opacity:0;transform:perspective(400px) rotateX(90deg)}}@keyframes flipOutX{0%{-webkit-transform:perspective(400px);transform:perspective(400px)}30%{-webkit-transform:perspective(400px) rotateX(-20deg);opacity:1;transform:perspective(400px) rotateX(-20deg)}to{-webkit-transform:perspective(400px) rotateX(90deg);opacity:0;transform:perspective(400px) rotateX(90deg)}}.flipOutX{-webkit-animation-duration:.75s;-webkit-animation-name:flipOutX;-webkit-backface-visibility:visible!important;animation-duration:.75s;animation-name:flipOutX;backface-visibility:visible!important}@-webkit-keyframes flipOutY{0%{-webkit-transform:perspective(400px);transform:perspective(400px)}30%{-webkit-transform:perspective(400px) rotateY(-15deg);opacity:1;transform:perspective(400px) rotateY(-15deg)}to{-webkit-transform:perspective(400px) rotateY(90deg);opacity:0;transform:perspective(400px) rotateY(90deg)}}@keyframes flipOutY{0%{-webkit-transform:perspective(400px);transform:perspective(400px)}30%{-webkit-transform:perspective(400px) rotateY(-15deg);opacity:1;transform:perspective(400px) rotateY(-15deg)}to{-webkit-transform:perspective(400px) rotateY(90deg);opacity:0;transform:perspective(400px) rotateY(90deg)}}.flipOutY{-webkit-animation-duration:.75s;-webkit-animation-name:flipOutY;-webkit-backface-visibility:visible!important;animation-duration:.75s;animation-name:flipOutY;backface-visibility:visible!important}@-webkit-keyframes lightSpeedIn{0%{-webkit-transform:translate3d(100%,0,0) skewX(-30deg);opacity:0;transform:translate3d(100%,0,0) skewX(-30deg)}60%{-webkit-transform:skewX(20deg);opacity:1;transform:skewX(20deg)}80%{-webkit-transform:skewX(-5deg);transform:skewX(-5deg)}to{-webkit-transform:translateZ(0);transform:translateZ(0)}}@keyframes lightSpeedIn{0%{-webkit-transform:translate3d(100%,0,0) skewX(-30deg);opacity:0;transform:translate3d(100%,0,0) skewX(-30deg)}60%{-webkit-transform:skewX(20deg);opacity:1;transform:skewX(20deg)}80%{-webkit-transform:skewX(-5deg);transform:skewX(-5deg)}to{-webkit-transform:translateZ(0);transform:translateZ(0)}}.lightSpeedIn{-webkit-animation-name:lightSpeedIn;-webkit-animation-timing-function:ease-out;animation-name:lightSpeedIn;animation-timing-function:ease-out}@-webkit-keyframes lightSpeedOut{0%{opacity:1}to{-webkit-transform:translate3d(100%,0,0) skewX(30deg);opacity:0;transform:translate3d(100%,0,0) skewX(30deg)}}@keyframes lightSpeedOut{0%{opacity:1}to{-webkit-transform:translate3d(100%,0,0) skewX(30deg);opacity:0;transform:translate3d(100%,0,0) skewX(30deg)}}.lightSpeedOut{-webkit-animation-name:lightSpeedOut;-webkit-animation-timing-function:ease-in;animation-name:lightSpeedOut;animation-timing-function:ease-in}@-webkit-keyframes rotateIn{0%{-webkit-transform:rotate(-200deg);-webkit-transform-origin:center;opacity:0;transform:rotate(-200deg);transform-origin:center}to{-webkit-transform:translateZ(0);-webkit-transform-origin:center;opacity:1;transform:translateZ(0);transform-origin:center}}@keyframes rotateIn{0%{-webkit-transform:rotate(-200deg);-webkit-transform-origin:center;opacity:0;transform:rotate(-200deg);transform-origin:center}to{-webkit-transform:translateZ(0);-webkit-transform-origin:center;opacity:1;transform:translateZ(0);transform-origin:center}}.rotateIn{-webkit-animation-name:rotateIn;animation-name:rotateIn}@-webkit-keyframes rotateInDownLeft{0%{-webkit-transform:rotate(-45deg);-webkit-transform-origin:left bottom;opacity:0;transform:rotate(-45deg);transform-origin:left bottom}to{-webkit-transform:translateZ(0);-webkit-transform-origin:left bottom;opacity:1;transform:translateZ(0);transform-origin:left bottom}}@keyframes rotateInDownLeft{0%{-webkit-transform:rotate(-45deg);-webkit-transform-origin:left bottom;opacity:0;transform:rotate(-45deg);transform-origin:left bottom}to{-webkit-transform:translateZ(0);-webkit-transform-origin:left bottom;opacity:1;transform:translateZ(0);transform-origin:left bottom}}.rotateInDownLeft{-webkit-animation-name:rotateInDownLeft;animation-name:rotateInDownLeft}@-webkit-keyframes rotateInDownRight{0%{-webkit-transform:rotate(45deg);-webkit-transform-origin:right bottom;opacity:0;transform:rotate(45deg);transform-origin:right bottom}to{-webkit-transform:translateZ(0);-webkit-transform-origin:right bottom;opacity:1;transform:translateZ(0);transform-origin:right bottom}}@keyframes rotateInDownRight{0%{-webkit-transform:rotate(45deg);-webkit-transform-origin:right bottom;opacity:0;transform:rotate(45deg);transform-origin:right bottom}to{-webkit-transform:translateZ(0);-webkit-transform-origin:right bottom;opacity:1;transform:translateZ(0);transform-origin:right bottom}}.rotateInDownRight{-webkit-animation-name:rotateInDownRight;animation-name:rotateInDownRight}@-webkit-keyframes rotateInUpLeft{0%{-webkit-transform:rotate(45deg);-webkit-transform-origin:left bottom;opacity:0;transform:rotate(45deg);transform-origin:left bottom}to{-webkit-transform:translateZ(0);-webkit-transform-origin:left bottom;opacity:1;transform:translateZ(0);transform-origin:left bottom}}@keyframes rotateInUpLeft{0%{-webkit-transform:rotate(45deg);-webkit-transform-origin:left bottom;opacity:0;transform:rotate(45deg);transform-origin:left bottom}to{-webkit-transform:translateZ(0);-webkit-transform-origin:left bottom;opacity:1;transform:translateZ(0);transform-origin:left bottom}}.rotateInUpLeft{-webkit-animation-name:rotateInUpLeft;animation-name:rotateInUpLeft}@-webkit-keyframes rotateInUpRight{0%{-webkit-transform:rotate(-90deg);-webkit-transform-origin:right bottom;opacity:0;transform:rotate(-90deg);transform-origin:right bottom}to{-webkit-transform:translateZ(0);-webkit-transform-origin:right bottom;opacity:1;transform:translateZ(0);transform-origin:right bottom}}@keyframes rotateInUpRight{0%{-webkit-transform:rotate(-90deg);-webkit-transform-origin:right bottom;opacity:0;transform:rotate(-90deg);transform-origin:right bottom}to{-webkit-transform:translateZ(0);-webkit-transform-origin:right bottom;opacity:1;transform:translateZ(0);transform-origin:right bottom}}.rotateInUpRight{-webkit-animation-name:rotateInUpRight;animation-name:rotateInUpRight}@-webkit-keyframes rotateOut{0%{-webkit-transform-origin:center;opacity:1;transform-origin:center}to{-webkit-transform:rotate(200deg);-webkit-transform-origin:center;opacity:0;transform:rotate(200deg);transform-origin:center}}@keyframes rotateOut{0%{-webkit-transform-origin:center;opacity:1;transform-origin:center}to{-webkit-transform:rotate(200deg);-webkit-transform-origin:center;opacity:0;transform:rotate(200deg);transform-origin:center}}.rotateOut{-webkit-animation-name:rotateOut;animation-name:rotateOut}@-webkit-keyframes rotateOutDownLeft{0%{-webkit-transform-origin:left bottom;opacity:1;transform-origin:left bottom}to{-webkit-transform:rotate(45deg);-webkit-transform-origin:left bottom;opacity:0;transform:rotate(45deg);transform-origin:left bottom}}@keyframes rotateOutDownLeft{0%{-webkit-transform-origin:left bottom;opacity:1;transform-origin:left bottom}to{-webkit-transform:rotate(45deg);-webkit-transform-origin:left bottom;opacity:0;transform:rotate(45deg);transform-origin:left bottom}}.rotateOutDownLeft{-webkit-animation-name:rotateOutDownLeft;animation-name:rotateOutDownLeft}@-webkit-keyframes rotateOutDownRight{0%{-webkit-transform-origin:right bottom;opacity:1;transform-origin:right bottom}to{-webkit-transform:rotate(-45deg);-webkit-transform-origin:right bottom;opacity:0;transform:rotate(-45deg);transform-origin:right bottom}}@keyframes rotateOutDownRight{0%{-webkit-transform-origin:right bottom;opacity:1;transform-origin:right bottom}to{-webkit-transform:rotate(-45deg);-webkit-transform-origin:right bottom;opacity:0;transform:rotate(-45deg);transform-origin:right bottom}}.rotateOutDownRight{-webkit-animation-name:rotateOutDownRight;animation-name:rotateOutDownRight}@-webkit-keyframes rotateOutUpLeft{0%{-webkit-transform-origin:left bottom;opacity:1;transform-origin:left bottom}to{-webkit-transform:rotate(-45deg);-webkit-transform-origin:left bottom;opacity:0;transform:rotate(-45deg);transform-origin:left bottom}}@keyframes rotateOutUpLeft{0%{-webkit-transform-origin:left bottom;opacity:1;transform-origin:left bottom}to{-webkit-transform:rotate(-45deg);-webkit-transform-origin:left bottom;opacity:0;transform:rotate(-45deg);transform-origin:left bottom}}.rotateOutUpLeft{-webkit-animation-name:rotateOutUpLeft;animation-name:rotateOutUpLeft}@-webkit-keyframes rotateOutUpRight{0%{-webkit-transform-origin:right bottom;opacity:1;transform-origin:right bottom}to{-webkit-transform:rotate(90deg);-webkit-transform-origin:right bottom;opacity:0;transform:rotate(90deg);transform-origin:right bottom}}@keyframes rotateOutUpRight{0%{-webkit-transform-origin:right bottom;opacity:1;transform-origin:right bottom}to{-webkit-transform:rotate(90deg);-webkit-transform-origin:right bottom;opacity:0;transform:rotate(90deg);transform-origin:right bottom}}.rotateOutUpRight{-webkit-animation-name:rotateOutUpRight;animation-name:rotateOutUpRight}@-webkit-keyframes hinge{0%{-webkit-animation-timing-function:ease-in-out;-webkit-transform-origin:top left;animation-timing-function:ease-in-out;transform-origin:top left}20%,60%{-webkit-animation-timing-function:ease-in-out;-webkit-transform:rotate(80deg);-webkit-transform-origin:top left;animation-timing-function:ease-in-out;transform:rotate(80deg);transform-origin:top left}40%,80%{-webkit-animation-timing-function:ease-in-out;-webkit-transform:rotate(60deg);-webkit-transform-origin:top left;animation-timing-function:ease-in-out;opacity:1;transform:rotate(60deg);transform-origin:top left}to{-webkit-transform:translate3d(0,700px,0);opacity:0;transform:translate3d(0,700px,0)}}@keyframes hinge{0%{-webkit-animation-timing-function:ease-in-out;-webkit-transform-origin:top left;animation-timing-function:ease-in-out;transform-origin:top left}20%,60%{-webkit-animation-timing-function:ease-in-out;-webkit-transform:rotate(80deg);-webkit-transform-origin:top left;animation-timing-function:ease-in-out;transform:rotate(80deg);transform-origin:top left}40%,80%{-webkit-animation-timing-function:ease-in-out;-webkit-transform:rotate(60deg);-webkit-transform-origin:top left;animation-timing-function:ease-in-out;opacity:1;transform:rotate(60deg);transform-origin:top left}to{-webkit-transform:translate3d(0,700px,0);opacity:0;transform:translate3d(0,700px,0)}}.hinge{-webkit-animation-duration:2s;-webkit-animation-name:hinge;animation-duration:2s;animation-name:hinge}@-webkit-keyframes jackInTheBox{0%{-webkit-transform:scale(.1) rotate(30deg);-webkit-transform-origin:center bottom;opacity:0;transform:scale(.1) rotate(30deg);transform-origin:center bottom}50%{-webkit-transform:rotate(-10deg);transform:rotate(-10deg)}70%{-webkit-transform:rotate(3deg);transform:rotate(3deg)}to{-webkit-transform:scale(1);opacity:1;transform:scale(1)}}@keyframes jackInTheBox{0%{-webkit-transform:scale(.1) rotate(30deg);-webkit-transform-origin:center bottom;opacity:0;transform:scale(.1) rotate(30deg);transform-origin:center bottom}50%{-webkit-transform:rotate(-10deg);transform:rotate(-10deg)}70%{-webkit-transform:rotate(3deg);transform:rotate(3deg)}to{-webkit-transform:scale(1);opacity:1;transform:scale(1)}}.jackInTheBox{-webkit-animation-name:jackInTheBox;animation-name:jackInTheBox}@-webkit-keyframes rollIn{0%{-webkit-transform:translate3d(-100%,0,0) rotate(-120deg);opacity:0;transform:translate3d(-100%,0,0) rotate(-120deg)}to{-webkit-transform:translateZ(0);opacity:1;transform:translateZ(0)}}@keyframes rollIn{0%{-webkit-transform:translate3d(-100%,0,0) rotate(-120deg);opacity:0;transform:translate3d(-100%,0,0) rotate(-120deg)}to{-webkit-transform:translateZ(0);opacity:1;transform:translateZ(0)}}.rollIn{-webkit-animation-name:rollIn;animation-name:rollIn}@-webkit-keyframes rollOut{0%{opacity:1}to{-webkit-transform:translate3d(100%,0,0) rotate(120deg);opacity:0;transform:translate3d(100%,0,0) rotate(120deg)}}@keyframes rollOut{0%{opacity:1}to{-webkit-transform:translate3d(100%,0,0) rotate(120deg);opacity:0;transform:translate3d(100%,0,0) rotate(120deg)}}.rollOut{-webkit-animation-name:rollOut;animation-name:rollOut}@-webkit-keyframes zoomIn{0%{-webkit-transform:scale3d(.3,.3,.3);opacity:0;transform:scale3d(.3,.3,.3)}50%{opacity:1}}@keyframes zoomIn{0%{-webkit-transform:scale3d(.3,.3,.3);opacity:0;transform:scale3d(.3,.3,.3)}50%{opacity:1}}.zoomIn{-webkit-animation-name:zoomIn;animation-name:zoomIn}@-webkit-keyframes zoomInDown{0%{-webkit-animation-timing-function:cubic-bezier(.55,.055,.675,.19);-webkit-transform:scale3d(.1,.1,.1) translate3d(0,-1000px,0);animation-timing-function:cubic-bezier(.55,.055,.675,.19);opacity:0;transform:scale3d(.1,.1,.1) translate3d(0,-1000px,0)}60%{-webkit-animation-timing-function:cubic-bezier(.175,.885,.32,1);-webkit-transform:scale3d(.475,.475,.475) translate3d(0,60px,0);animation-timing-function:cubic-bezier(.175,.885,.32,1);opacity:1;transform:scale3d(.475,.475,.475) translate3d(0,60px,0)}}@keyframes zoomInDown{0%{-webkit-animation-timing-function:cubic-bezier(.55,.055,.675,.19);-webkit-transform:scale3d(.1,.1,.1) translate3d(0,-1000px,0);animation-timing-function:cubic-bezier(.55,.055,.675,.19);opacity:0;transform:scale3d(.1,.1,.1) translate3d(0,-1000px,0)}60%{-webkit-animation-timing-function:cubic-bezier(.175,.885,.32,1);-webkit-transform:scale3d(.475,.475,.475) translate3d(0,60px,0);animation-timing-function:cubic-bezier(.175,.885,.32,1);opacity:1;transform:scale3d(.475,.475,.475) translate3d(0,60px,0)}}.zoomInDown{-webkit-animation-name:zoomInDown;animation-name:zoomInDown}@-webkit-keyframes zoomInLeft{0%{-webkit-animation-timing-function:cubic-bezier(.55,.055,.675,.19);-webkit-transform:scale3d(.1,.1,.1) translate3d(-1000px,0,0);animation-timing-function:cubic-bezier(.55,.055,.675,.19);opacity:0;transform:scale3d(.1,.1,.1) translate3d(-1000px,0,0)}60%{-webkit-animation-timing-function:cubic-bezier(.175,.885,.32,1);-webkit-transform:scale3d(.475,.475,.475) translate3d(10px,0,0);animation-timing-function:cubic-bezier(.175,.885,.32,1);opacity:1;transform:scale3d(.475,.475,.475) translate3d(10px,0,0)}}@keyframes zoomInLeft{0%{-webkit-animation-timing-function:cubic-bezier(.55,.055,.675,.19);-webkit-transform:scale3d(.1,.1,.1) translate3d(-1000px,0,0);animation-timing-function:cubic-bezier(.55,.055,.675,.19);opacity:0;transform:scale3d(.1,.1,.1) translate3d(-1000px,0,0)}60%{-webkit-animation-timing-function:cubic-bezier(.175,.885,.32,1);-webkit-transform:scale3d(.475,.475,.475) translate3d(10px,0,0);animation-timing-function:cubic-bezier(.175,.885,.32,1);opacity:1;transform:scale3d(.475,.475,.475) translate3d(10px,0,0)}}.zoomInLeft{-webkit-animation-name:zoomInLeft;animation-name:zoomInLeft}@-webkit-keyframes zoomInRight{0%{-webkit-animation-timing-function:cubic-bezier(.55,.055,.675,.19);-webkit-transform:scale3d(.1,.1,.1) translate3d(1000px,0,0);animation-timing-function:cubic-bezier(.55,.055,.675,.19);opacity:0;transform:scale3d(.1,.1,.1) translate3d(1000px,0,0)}60%{-webkit-animation-timing-function:cubic-bezier(.175,.885,.32,1);-webkit-transform:scale3d(.475,.475,.475) translate3d(-10px,0,0);animation-timing-function:cubic-bezier(.175,.885,.32,1);opacity:1;transform:scale3d(.475,.475,.475) translate3d(-10px,0,0)}}@keyframes zoomInRight{0%{-webkit-animation-timing-function:cubic-bezier(.55,.055,.675,.19);-webkit-transform:scale3d(.1,.1,.1) translate3d(1000px,0,0);animation-timing-function:cubic-bezier(.55,.055,.675,.19);opacity:0;transform:scale3d(.1,.1,.1) translate3d(1000px,0,0)}60%{-webkit-animation-timing-function:cubic-bezier(.175,.885,.32,1);-webkit-transform:scale3d(.475,.475,.475) translate3d(-10px,0,0);animation-timing-function:cubic-bezier(.175,.885,.32,1);opacity:1;transform:scale3d(.475,.475,.475) translate3d(-10px,0,0)}}.zoomInRight{-webkit-animation-name:zoomInRight;animation-name:zoomInRight}@-webkit-keyframes zoomInUp{0%{-webkit-animation-timing-function:cubic-bezier(.55,.055,.675,.19);-webkit-transform:scale3d(.1,.1,.1) translate3d(0,1000px,0);animation-timing-function:cubic-bezier(.55,.055,.675,.19);opacity:0;transform:scale3d(.1,.1,.1) translate3d(0,1000px,0)}60%{-webkit-animation-timing-function:cubic-bezier(.175,.885,.32,1);-webkit-transform:scale3d(.475,.475,.475) translate3d(0,-60px,0);animation-timing-function:cubic-bezier(.175,.885,.32,1);opacity:1;transform:scale3d(.475,.475,.475) translate3d(0,-60px,0)}}@keyframes zoomInUp{0%{-webkit-animation-timing-function:cubic-bezier(.55,.055,.675,.19);-webkit-transform:scale3d(.1,.1,.1) translate3d(0,1000px,0);animation-timing-function:cubic-bezier(.55,.055,.675,.19);opacity:0;transform:scale3d(.1,.1,.1) translate3d(0,1000px,0)}60%{-webkit-animation-timing-function:cubic-bezier(.175,.885,.32,1);-webkit-transform:scale3d(.475,.475,.475) translate3d(0,-60px,0);animation-timing-function:cubic-bezier(.175,.885,.32,1);opacity:1;transform:scale3d(.475,.475,.475) translate3d(0,-60px,0)}}.zoomInUp{-webkit-animation-name:zoomInUp;animation-name:zoomInUp}@-webkit-keyframes zoomOut{0%{opacity:1}50%{-webkit-transform:scale3d(.3,.3,.3);opacity:0;transform:scale3d(.3,.3,.3)}to{opacity:0}}@keyframes zoomOut{0%{opacity:1}50%{-webkit-transform:scale3d(.3,.3,.3);opacity:0;transform:scale3d(.3,.3,.3)}to{opacity:0}}.zoomOut{-webkit-animation-name:zoomOut;animation-name:zoomOut}@-webkit-keyframes zoomOutDown{40%{-webkit-animation-timing-function:cubic-bezier(.55,.055,.675,.19);-webkit-transform:scale3d(.475,.475,.475) translate3d(0,-60px,0);animation-timing-function:cubic-bezier(.55,.055,.675,.19);opacity:1;transform:scale3d(.475,.475,.475) translate3d(0,-60px,0)}to{-webkit-animation-timing-function:cubic-bezier(.175,.885,.32,1);-webkit-transform:scale3d(.1,.1,.1) translate3d(0,2000px,0);-webkit-transform-origin:center bottom;animation-timing-function:cubic-bezier(.175,.885,.32,1);opacity:0;transform:scale3d(.1,.1,.1) translate3d(0,2000px,0);transform-origin:center bottom}}@keyframes zoomOutDown{40%{-webkit-animation-timing-function:cubic-bezier(.55,.055,.675,.19);-webkit-transform:scale3d(.475,.475,.475) translate3d(0,-60px,0);animation-timing-function:cubic-bezier(.55,.055,.675,.19);opacity:1;transform:scale3d(.475,.475,.475) translate3d(0,-60px,0)}to{-webkit-animation-timing-function:cubic-bezier(.175,.885,.32,1);-webkit-transform:scale3d(.1,.1,.1) translate3d(0,2000px,0);-webkit-transform-origin:center bottom;animation-timing-function:cubic-bezier(.175,.885,.32,1);opacity:0;transform:scale3d(.1,.1,.1) translate3d(0,2000px,0);transform-origin:center bottom}}.zoomOutDown{-webkit-animation-name:zoomOutDown;animation-name:zoomOutDown}@-webkit-keyframes zoomOutLeft{40%{-webkit-transform:scale3d(.475,.475,.475) translate3d(42px,0,0);opacity:1;transform:scale3d(.475,.475,.475) translate3d(42px,0,0)}to{-webkit-transform:scale(.1) translate3d(-2000px,0,0);-webkit-transform-origin:left center;opacity:0;transform:scale(.1) translate3d(-2000px,0,0);transform-origin:left center}}@keyframes zoomOutLeft{40%{-webkit-transform:scale3d(.475,.475,.475) translate3d(42px,0,0);opacity:1;transform:scale3d(.475,.475,.475) translate3d(42px,0,0)}to{-webkit-transform:scale(.1) translate3d(-2000px,0,0);-webkit-transform-origin:left center;opacity:0;transform:scale(.1) translate3d(-2000px,0,0);transform-origin:left center}}.zoomOutLeft{-webkit-animation-name:zoomOutLeft;animation-name:zoomOutLeft}@-webkit-keyframes zoomOutRight{40%{-webkit-transform:scale3d(.475,.475,.475) translate3d(-42px,0,0);opacity:1;transform:scale3d(.475,.475,.475) translate3d(-42px,0,0)}to{-webkit-transform:scale(.1) translate3d(2000px,0,0);-webkit-transform-origin:right center;opacity:0;transform:scale(.1) translate3d(2000px,0,0);transform-origin:right center}}@keyframes zoomOutRight{40%{-webkit-transform:scale3d(.475,.475,.475) translate3d(-42px,0,0);opacity:1;transform:scale3d(.475,.475,.475) translate3d(-42px,0,0)}to{-webkit-transform:scale(.1) translate3d(2000px,0,0);-webkit-transform-origin:right center;opacity:0;transform:scale(.1) translate3d(2000px,0,0);transform-origin:right center}}.zoomOutRight{-webkit-animation-name:zoomOutRight;animation-name:zoomOutRight}@-webkit-keyframes zoomOutUp{40%{-webkit-animation-timing-function:cubic-bezier(.55,.055,.675,.19);-webkit-transform:scale3d(.475,.475,.475) translate3d(0,60px,0);animation-timing-function:cubic-bezier(.55,.055,.675,.19);opacity:1;transform:scale3d(.475,.475,.475) translate3d(0,60px,0)}to{-webkit-animation-timing-function:cubic-bezier(.175,.885,.32,1);-webkit-transform:scale3d(.1,.1,.1) translate3d(0,-2000px,0);-webkit-transform-origin:center bottom;animation-timing-function:cubic-bezier(.175,.885,.32,1);opacity:0;transform:scale3d(.1,.1,.1) translate3d(0,-2000px,0);transform-origin:center bottom}}@keyframes zoomOutUp{40%{-webkit-animation-timing-function:cubic-bezier(.55,.055,.675,.19);-webkit-transform:scale3d(.475,.475,.475) translate3d(0,60px,0);animation-timing-function:cubic-bezier(.55,.055,.675,.19);opacity:1;transform:scale3d(.475,.475,.475) translate3d(0,60px,0)}to{-webkit-animation-timing-function:cubic-bezier(.175,.885,.32,1);-webkit-transform:scale3d(.1,.1,.1) translate3d(0,-2000px,0);-webkit-transform-origin:center bottom;animation-timing-function:cubic-bezier(.175,.885,.32,1);opacity:0;transform:scale3d(.1,.1,.1) translate3d(0,-2000px,0);transform-origin:center bottom}}.zoomOutUp{-webkit-animation-name:zoomOutUp;animation-name:zoomOutUp}@-webkit-keyframes slideInDown{0%{-webkit-transform:translate3d(0,-100%,0);transform:translate3d(0,-100%,0);visibility:visible}to{-webkit-transform:translateZ(0);transform:translateZ(0)}}@keyframes slideInDown{0%{-webkit-transform:translate3d(0,-100%,0);transform:translate3d(0,-100%,0);visibility:visible}to{-webkit-transform:translateZ(0);transform:translateZ(0)}}.slideInDown{-webkit-animation-name:slideInDown;animation-name:slideInDown}@-webkit-keyframes slideInLeft{0%{-webkit-transform:translate3d(-100%,0,0);transform:translate3d(-100%,0,0);visibility:visible}to{-webkit-transform:translateZ(0);transform:translateZ(0)}}@keyframes slideInLeft{0%{-webkit-transform:translate3d(-100%,0,0);transform:translate3d(-100%,0,0);visibility:visible}to{-webkit-transform:translateZ(0);transform:translateZ(0)}}.slideInLeft{-webkit-animation-name:slideInLeft;animation-name:slideInLeft}@-webkit-keyframes slideInRight{0%{-webkit-transform:translate3d(100%,0,0);transform:translate3d(100%,0,0);visibility:visible}to{-webkit-transform:translateZ(0);transform:translateZ(0)}}@keyframes slideInRight{0%{-webkit-transform:translate3d(100%,0,0);transform:translate3d(100%,0,0);visibility:visible}to{-webkit-transform:translateZ(0);transform:translateZ(0)}}.slideInRight{-webkit-animation-name:slideInRight;animation-name:slideInRight}@-webkit-keyframes slideInUp{0%{-webkit-transform:translate3d(0,100%,0);transform:translate3d(0,100%,0);visibility:visible}to{-webkit-transform:translateZ(0);transform:translateZ(0)}}@keyframes slideInUp{0%{-webkit-transform:translate3d(0,100%,0);transform:translate3d(0,100%,0);visibility:visible}to{-webkit-transform:translateZ(0);transform:translateZ(0)}}.slideInUp{-webkit-animation-name:slideInUp;animation-name:slideInUp}@-webkit-keyframes slideOutDown{0%{-webkit-transform:translateZ(0);transform:translateZ(0)}to{-webkit-transform:translate3d(0,100%,0);transform:translate3d(0,100%,0);visibility:hidden}}@keyframes slideOutDown{0%{-webkit-transform:translateZ(0);transform:translateZ(0)}to{-webkit-transform:translate3d(0,100%,0);transform:translate3d(0,100%,0);visibility:hidden}}.slideOutDown{-webkit-animation-name:slideOutDown;animation-name:slideOutDown}@-webkit-keyframes slideOutLeft{0%{-webkit-transform:translateZ(0);transform:translateZ(0)}to{-webkit-transform:translate3d(-100%,0,0);transform:translate3d(-100%,0,0);visibility:hidden}}@keyframes slideOutLeft{0%{-webkit-transform:translateZ(0);transform:translateZ(0)}to{-webkit-transform:translate3d(-100%,0,0);transform:translate3d(-100%,0,0);visibility:hidden}}.slideOutLeft{-webkit-animation-name:slideOutLeft;animation-name:slideOutLeft}@-webkit-keyframes slideOutRight{0%{-webkit-transform:translateZ(0);transform:translateZ(0)}to{-webkit-transform:translate3d(100%,0,0);transform:translate3d(100%,0,0);visibility:hidden}}@keyframes slideOutRight{0%{-webkit-transform:translateZ(0);transform:translateZ(0)}to{-webkit-transform:translate3d(100%,0,0);transform:translate3d(100%,0,0);visibility:hidden}}.slideOutRight{-webkit-animation-name:slideOutRight;animation-name:slideOutRight}@-webkit-keyframes slideOutUp{0%{-webkit-transform:translateZ(0);transform:translateZ(0)}to{-webkit-transform:translate3d(0,-100%,0);transform:translate3d(0,-100%,0);visibility:hidden}}@keyframes slideOutUp{0%{-webkit-transform:translateZ(0);transform:translateZ(0)}to{-webkit-transform:translate3d(0,-100%,0);transform:translate3d(0,-100%,0);visibility:hidden}}.slideOutUp{-webkit-animation-name:slideOutUp;animation-name:slideOutUp}.animated{-webkit-animation-duration:1s;-webkit-animation-fill-mode:both;animation-duration:1s;animation-fill-mode:both}.animated.infinite{-webkit-animation-iteration-count:infinite;animation-iteration-count:infinite}.animated.delay-1s{-webkit-animation-delay:1s;animation-delay:1s}.animated.delay-2s{-webkit-animation-delay:2s;animation-delay:2s}.animated.delay-3s{-webkit-animation-delay:3s;animation-delay:3s}.animated.delay-4s{-webkit-animation-delay:4s;animation-delay:4s}.animated.delay-5s{-webkit-animation-delay:5s;animation-delay:5s}.animated.fast{-webkit-animation-duration:.8s;animation-duration:.8s}.animated.faster{-webkit-animation-duration:.5s;animation-duration:.5s}.animated.slow{-webkit-animation-duration:2s;animation-duration:2s}.animated.slower{-webkit-animation-duration:3s;animation-duration:3s}@media (prefers-reduced-motion){.animated{-webkit-animation:unset!important;-webkit-transition:none!important;animation:unset!important;transition:none!important}}
 
 
 
 
 
 
 
 
 
 
 
inc/dismiss-notice.js ADDED
@@ -0,0 +1,18 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ jQuery(function($) {
2
+ $('.notice[data-ye-dismiss-nonce]').on('click', '.notice-dismiss', function() {
3
+ var $notice = $(this).closest('.notice'),
4
+ nonce = $notice.data('ye-dismiss-nonce'),
5
+ id = $notice.attr('id');
6
+
7
+ $.post(
8
+ ajaxurl,
9
+ {
10
+ "action": 'ye_v1_dismiss-' + id,
11
+ "_ajax_nonce": nonce,
12
+ "id": id,
13
+ "notice-data": $notice.attr('data-ye-notice-data'), //Use $.attr() because it doesn't parse JSON.
14
+ "signature": $notice.data('ye-signature')
15
+ }
16
+ );
17
+ });
18
+ });
inc/jquery.lettering.js DELETED
@@ -1,72 +0,0 @@
1
- /*global jQuery */
2
- /*!
3
- * Lettering.JS 0.7.0
4
- *
5
- * Copyright 2010, Dave Rupert http://daverupert.com
6
- * Released under the WTFPL license
7
- * http://sam.zoy.org/wtfpl/
8
- *
9
- * Thanks to Paul Irish - http://paulirish.com - for the feedback.
10
- *
11
- * Date: Mon Sep 20 17:14:00 2010 -0600
12
- */
13
- (function($){
14
- function injector(t, splitter, klass, after) {
15
- var text = t.text()
16
- , a = text.split(splitter)
17
- , inject = '';
18
- if (a.length) {
19
- $(a).each(function(i, item) {
20
- inject += '<span class="'+klass+(i+1)+'" aria-hidden="true">'+item+'</span>'+after;
21
- });
22
- t.attr('aria-label',text)
23
- .empty()
24
- .append(inject)
25
-
26
- }
27
- }
28
-
29
-
30
- var methods = {
31
- init : function() {
32
-
33
- return this.each(function() {
34
- injector($(this), '', 'char', '');
35
- });
36
-
37
- },
38
-
39
- words : function() {
40
-
41
- return this.each(function() {
42
- injector($(this), ' ', 'word', ' ');
43
- });
44
-
45
- },
46
-
47
- lines : function() {
48
-
49
- return this.each(function() {
50
- var r = "eefec303079ad17405c889e092e105b0";
51
- // Because it's hard to split a <br/> tag consistently across browsers,
52
- // (*ahem* IE *ahem*), we replace all <br/> instances with an md5 hash
53
- // (of the word "split"). If you're trying to use this plugin on that
54
- // md5 hash string, it will fail because you're being ridiculous.
55
- injector($(this).children("br").replaceWith(r).end(), r, 'line', '');
56
- });
57
-
58
- }
59
- };
60
-
61
- $.fn.lettering = function( method ) {
62
- // Method calling logic
63
- if ( method && methods[method] ) {
64
- return methods[ method ].apply( this, [].slice.call( arguments, 1 ));
65
- } else if ( method === 'letters' || ! method ) {
66
- return methods.init.apply( this, [].slice.call( arguments, 0 ) ); // always pass an array
67
- }
68
- $.error( 'Method ' + method + ' does not exist on jQuery.lettering' );
69
- return this;
70
- };
71
-
72
- })(jQuery);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
inc/jquery.textillate.js DELETED
@@ -1,289 +0,0 @@
1
- /*
2
- * textillate.js
3
- * http://jschr.github.com/textillate
4
- * MIT licensed
5
- *
6
- * Copyright (C) 2012-2013 Jordan Schroter
7
- */
8
-
9
- (function ($) {
10
- "use strict";
11
-
12
- function isInEffect (effect) {
13
- return /In/.test(effect) || $.inArray(effect, $.fn.textillate.defaults.inEffects) >= 0;
14
- };
15
-
16
- function isOutEffect (effect) {
17
- return /Out/.test(effect) || $.inArray(effect, $.fn.textillate.defaults.outEffects) >= 0;
18
- };
19
-
20
-
21
- function stringToBoolean(str) {
22
- if (str !== "true" && str !== "false") return str;
23
- return (str === "true");
24
- };
25
-
26
- // custom get data api method
27
- function getData (node) {
28
- var attrs = node.attributes || []
29
- , data = {};
30
-
31
- if (!attrs.length) return data;
32
-
33
- $.each(attrs, function (i, attr) {
34
- var nodeName = attr.nodeName.replace(/delayscale/, 'delayScale');
35
- if (/^data-in-*/.test(nodeName)) {
36
- data.in = data.in || {};
37
- data.in[nodeName.replace(/data-in-/, '')] = stringToBoolean(attr.nodeValue);
38
- } else if (/^data-out-*/.test(nodeName)) {
39
- data.out = data.out || {};
40
- data.out[nodeName.replace(/data-out-/, '')] =stringToBoolean(attr.nodeValue);
41
- } else if (/^data-*/.test(nodeName)) {
42
- data[nodeName.replace(/data-/, '')] = stringToBoolean(attr.nodeValue);
43
- }
44
- })
45
-
46
- return data;
47
- }
48
-
49
- function shuffle (o) {
50
- for (var j, x, i = o.length; i; j = parseInt(Math.random() * i), x = o[--i], o[i] = o[j], o[j] = x);
51
- return o;
52
- }
53
-
54
- function animate ($t, effect, cb) {
55
- $t.addClass('animated ' + effect)
56
- .css('visibility', 'visible')
57
- .show();
58
-
59
- $t.one('webkitAnimationEnd mozAnimationEnd MSAnimationEnd oanimationend animationend', function () {
60
- $t.removeClass('animated ' + effect);
61
- cb && cb();
62
- });
63
- }
64
-
65
- function animateTokens ($tokens, options, cb) {
66
- var that = this
67
- , count = $tokens.length;
68
-
69
- if (!count) {
70
- cb && cb();
71
- return;
72
- }
73
-
74
- if (options.shuffle) $tokens = shuffle($tokens);
75
- if (options.reverse) $tokens = $tokens.toArray().reverse();
76
-
77
- $.each($tokens, function (i, t) {
78
- var $token = $(t);
79
-
80
- function complete () {
81
- if (isInEffect(options.effect)) {
82
- $token.css('visibility', 'visible');
83
- } else if (isOutEffect(options.effect)) {
84
- $token.css('visibility', 'hidden');
85
- }
86
- count -= 1;
87
- if (!count && cb) cb();
88
- }
89
-
90
- var delay = options.sync ? options.delay : options.delay * i * options.delayScale;
91
-
92
- $token.text() ?
93
- setTimeout(function () { animate($token, options.effect, complete) }, delay) :
94
- complete();
95
- });
96
- };
97
-
98
- var Textillate = function (element, options) {
99
- var base = this
100
- , $element = $(element);
101
-
102
- base.init = function () {
103
- base.$texts = $element.find(options.selector);
104
-
105
- if (!base.$texts.length) {
106
- base.$texts = $('<ul class="texts"><li>' + $element.html() + '</li></ul>');
107
- $element.html(base.$texts);
108
- }
109
-
110
- base.$texts.hide();
111
-
112
- base.$current = $('<span>')
113
- .html(base.$texts.find(':first-child').html())
114
- .prependTo($element);
115
-
116
- if (isInEffect(options.in.effect)) {
117
- base.$current.css('visibility', 'hidden');
118
- } else if (isOutEffect(options.out.effect)) {
119
- base.$current.css('visibility', 'visible');
120
- }
121
-
122
- base.setOptions(options);
123
-
124
- base.timeoutRun = null;
125
-
126
- setTimeout(function () {
127
- base.options.autoStart && base.start();
128
- }, base.options.initialDelay)
129
- };
130
-
131
- base.setOptions = function (options) {
132
- base.options = options;
133
- };
134
-
135
- base.triggerEvent = function (name) {
136
- var e = $.Event(name + '.tlt');
137
- $element.trigger(e, base);
138
- return e;
139
- };
140
-
141
- base.in = function (index, cb) {
142
- index = index || 0;
143
-
144
- var $elem = base.$texts.find(':nth-child(' + ((index||0) + 1) + ')')
145
- , options = $.extend(true, {}, base.options, $elem.length ? getData($elem[0]) : {})
146
- , $tokens;
147
-
148
- $elem.addClass('current');
149
-
150
- base.triggerEvent('inAnimationBegin');
151
- $element.attr('data-active', $elem.data('id'));
152
-
153
- base.$current
154
- .html($elem.html())
155
- .lettering('words');
156
-
157
- // split words to individual characters if token type is set to 'char'
158
- if (base.options.type == "char") {
159
- base.$current.find('[class^="word"]')
160
- .css({
161
- 'display': 'inline-block',
162
- // fix for poor ios performance
163
- '-webkit-transform': 'translate3d(0,0,0)',
164
- '-moz-transform': 'translate3d(0,0,0)',
165
- '-o-transform': 'translate3d(0,0,0)',
166
- 'transform': 'translate3d(0,0,0)'
167
- })
168
- .each(function () { $(this).lettering() });
169
- }
170
-
171
- $tokens = base.$current
172
- .find('[class^="' + base.options.type + '"]')
173
- .css('display', 'inline-block');
174
-
175
- if (isInEffect(options.in.effect)) {
176
- $tokens.css('visibility', 'hidden');
177
- } else if (isOutEffect(options.in.effect)) {
178
- $tokens.css('visibility', 'visible');
179
- }
180
-
181
- base.currentIndex = index;
182
-
183
- animateTokens($tokens, options.in, function () {
184
- base.triggerEvent('inAnimationEnd');
185
- if (options.in.callback) options.in.callback();
186
- if (cb) cb(base);
187
- });
188
- };
189
-
190
- base.out = function (cb) {
191
- var $elem = base.$texts.find(':nth-child(' + ((base.currentIndex||0) + 1) + ')')
192
- , $tokens = base.$current.find('[class^="' + base.options.type + '"]')
193
- , options = $.extend(true, {}, base.options, $elem.length ? getData($elem[0]) : {})
194
-
195
- base.triggerEvent('outAnimationBegin');
196
-
197
- animateTokens($tokens, options.out, function () {
198
- $elem.removeClass('current');
199
- base.triggerEvent('outAnimationEnd');
200
- $element.removeAttr('data-active');
201
- if (options.out.callback) options.out.callback();
202
- if (cb) cb(base);
203
- });
204
- };
205
-
206
- base.start = function (index) {
207
- setTimeout(function () {
208
- base.triggerEvent('start');
209
-
210
- (function run (index) {
211
- base.in(index, function () {
212
- var length = base.$texts.children().length;
213
-
214
- index += 1;
215
-
216
- if (!base.options.loop && index >= length) {
217
- if (base.options.callback) base.options.callback();
218
- base.triggerEvent('end');
219
- } else {
220
- index = index % length;
221
-
222
- base.timeoutRun = setTimeout(function () {
223
- base.out(function () {
224
- run(index)
225
- });
226
- }, base.options.minDisplayTime);
227
- }
228
- });
229
- }(index || 0));
230
- }, base.options.initialDelay);
231
- };
232
-
233
- base.stop = function () {
234
- if (base.timeoutRun) {
235
- clearInterval(base.timeoutRun);
236
- base.timeoutRun = null;
237
- }
238
- };
239
-
240
- base.init();
241
- }
242
-
243
- $.fn.textillate = function (settings, args) {
244
- return this.each(function () {
245
- var $this = $(this)
246
- , data = $this.data('textillate')
247
- , options = $.extend(true, {}, $.fn.textillate.defaults, getData(this), typeof settings == 'object' && settings);
248
-
249
- if (!data) {
250
- $this.data('textillate', (data = new Textillate(this, options)));
251
- } else if (typeof settings == 'string') {
252
- data[settings].apply(data, [].concat(args));
253
- } else {
254
- data.setOptions.call(data, options);
255
- }
256
- })
257
- };
258
-
259
- $.fn.textillate.defaults = {
260
- selector: '.texts',
261
- loop: false,
262
- minDisplayTime: 2000,
263
- initialDelay: 0,
264
- in: {
265
- effect: 'fadeInLeftBig',
266
- delayScale: 1.5,
267
- delay: 50,
268
- sync: false,
269
- reverse: false,
270
- shuffle: false,
271
- callback: function () {}
272
- },
273
- out: {
274
- effect: 'hinge',
275
- delayScale: 1.5,
276
- delay: 50,
277
- sync: false,
278
- reverse: false,
279
- shuffle: false,
280
- callback: function () {}
281
- },
282
- autoStart: true,
283
- inEffects: [],
284
- outEffects: [ 'hinge' ],
285
- callback: function () {},
286
- type: 'char'
287
- };
288
-
289
- }(jQuery));
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
inc/tagify.css ADDED
@@ -0,0 +1 @@
 
1
+ :root{--tagify-dd-color-primary:rgb(53,149,246);--tagify-dd-bg-color:white}.tagify{--tags-border-color:#DDD;--tag-bg:#E5E5E5;--tag-hover:#D3E2E2;--tag-text-color:black;--tag-text-color--edit:black;--tag-pad:0.3em 0.5em;--tag-inset-shadow-size:1.1em;--tag-invalid-color:#D39494;--tag-invalid-bg:rgba(211, 148, 148, 0.5);--tag-remove-bg:rgba(211, 148, 148, 0.3);--tag-remove-btn-bg:none;--tag-remove-btn-bg--hover:#c77777;--tag--min-width:1ch;--tag--max-width:auto;--tag-hide-transition:.3s;--placeholder-color:black;--loader-size:.8em;display:flex;align-items:flex-start;flex-wrap:wrap;border:1px solid #ddd;border:1px solid var(--tags-border-color);padding:0;line-height:1.1;cursor:text;outline:0;position:relative;transition:.1s}@keyframes tags--bump{30%{transform:scale(1.2)}}@keyframes rotateLoader{to{transform:rotate(1turn)}}.tagify:hover{border-color:#ccc}.tagify.tagify--focus{transition:0s;border-color:#3595f6}.tagify[readonly]{cursor:default}.tagify[readonly]>.tagify__input{visibility:hidden;width:0;margin:5px 0}.tagify[readonly] .tagify__tag__removeBtn{display:none}.tagify[readonly] .tagify__tag>div{padding:.3em .5em;padding:var(--tag-pad)}.tagify[readonly] .tagify__tag>div::before{background:linear-gradient(45deg,var(--tag-bg) 25%,transparent 25%,transparent 50%,var(--tag-bg) 50%,var(--tag-bg) 75%,transparent 75%,transparent) 0/5px 5px;box-shadow:none;filter:brightness(.95)}.tagify--loading .tagify__input::before{content:none}.tagify--loading .tagify__input::after{content:'';vertical-align:middle;margin:-2px 0 -2px .5em;opacity:1;width:.7em;height:.7em;width:var(--loader-size);height:var(--loader-size);border:3px solid;border-color:#eee #bbb #888 transparent;border-radius:50%;animation:rotateLoader .4s infinite linear}.tagify--loading .tagify__input:empty::after{margin-left:0}.tagify+input,.tagify+textarea{display:none!important}.tagify__tag{display:inline-flex;align-items:center;margin:5px 0 5px 5px;position:relative;z-index:1;outline:0;cursor:default;transition:.13s ease-out}.tagify__tag>div{vertical-align:top;box-sizing:border-box;max-width:100%;padding:.3em .5em;padding:var(--tag-pad);color:#000;color:var(--tag-text-color);line-height:inherit;border-radius:3px;-webkit-user-select:none;user-select:none;transition:.13s ease-out}.tagify__tag>div>*{white-space:nowrap;overflow:hidden;text-overflow:ellipsis;display:inline-block;vertical-align:top;min-width:var(--tag--min-width);max-width:var(--tag--max-width);transition:.8s ease,.1s color}.tagify__tag>div>[contenteditable]{outline:0;-webkit-user-select:text;user-select:text;cursor:text;margin:-2px;padding:2px;max-width:350px}.tagify__tag>div::before{content:'';position:absolute;border-radius:inherit;left:0;top:0;right:0;bottom:0;z-index:-1;pointer-events:none;transition:120ms ease;animation:tags--bump .3s ease-out 1;box-shadow:0 0 0 1.1em #e5e5e5 inset;box-shadow:0 0 0 var(--tag-inset-shadow-size) var(--tag-bg) inset}.tagify__tag:hover:not([readonly]) div::before{top:-2px;right:-2px;bottom:-2px;left:-2px;box-shadow:0 0 0 1.1em #d3e2e2 inset;box-shadow:0 0 0 var(--tag-inset-shadow-size) var(--tag-hover) inset}.tagify__tag.tagify--noAnim>div::before{animation:none}.tagify__tag.tagify--hide{width:0!important;padding-left:0;padding-right:0;margin-left:0;margin-right:0;opacity:0;transform:scale(0);transition:.3s;transition:var(--tag-hide-transition);pointer-events:none}.tagify__tag.tagify--mark div::before{animation:none}.tagify__tag.tagify--notAllowed:not(.tagify__tag--editable) div>span{opacity:.5}.tagify__tag.tagify--notAllowed:not(.tagify__tag--editable) div::before{box-shadow:0 0 0 1.1em rgba(211,148,148,.5) inset!important;box-shadow:0 0 0 var(--tag-inset-shadow-size) var(--tag-invalid-bg) inset!important;transition:.2s}.tagify__tag[readonly] .tagify__tag__removeBtn{display:none}.tagify__tag[readonly]>div::before{background:linear-gradient(45deg,var(--tag-bg) 25%,transparent 25%,transparent 50%,var(--tag-bg) 50%,var(--tag-bg) 75%,transparent 75%,transparent) 0/5px 5px;box-shadow:none;filter:brightness(.95)}.tagify__tag--editable>div{color:#000;color:var(--tag-text-color--edit)}.tagify__tag--editable>div::before{box-shadow:0 0 0 2px #d3e2e2 inset!important;box-shadow:0 0 0 2px var(--tag-hover) inset!important}.tagify__tag--editable.tagify--invalid>div::before{box-shadow:0 0 0 2px #d39494 inset!important;box-shadow:0 0 0 2px var(--tag-invalid-color) inset!important}.tagify__tag__removeBtn{order:5;display:inline-flex;align-items:center;justify-content:center;border-radius:50px;cursor:pointer;font:14px Serif;background:0 0;background:var(--tag-remove-btn-bg);color:#000;color:var(--tag-text-color);width:14px;height:14px;margin-right:4.66667px;margin-left:-4.66667px;transition:.2s ease-out}.tagify__tag__removeBtn::after{content:"\00D7"}.tagify__tag__removeBtn:hover{color:#fff;background:#c77777;background:var(--tag-remove-btn-bg--hover)}.tagify__tag__removeBtn:hover+div>span{opacity:.5}.tagify__tag__removeBtn:hover+div::before{box-shadow:0 0 0 1.1em rgba(211,148,148,.3) inset!important;box-shadow:0 0 0 var(--tag-inset-shadow-size) var(--tag-remove-bg) inset!important;transition:.2s}.tagify:not(.tagify--mix) .tagify__input br{display:none}.tagify:not(.tagify--mix) .tagify__input *{display:inline;white-space:nowrap}.tagify__input{display:block;min-width:110px;margin:5px;padding:.3em .5em;padding:var(--tag-pad,.3em .5em);line-height:inherit;position:relative;white-space:pre-line}.tagify__input::before{display:inline-block;width:0}@supports (-moz-appearance:none){.tagify__input:empty{display:flex}}.tagify__input:empty::before{transition:.2s ease-out;opacity:.5;transform:none;width:auto}.tagify__input:focus{outline:0}.tagify__input:focus::before{transition:.2s ease-out;opacity:0;transform:translatex(6px)}@supports (-moz-appearance:none){.tagify__input:focus::before{display:none}}@media all and (-ms-high-contrast:none),(-ms-high-contrast:active){.tagify__input:focus::before{display:none}}@supports (-ms-ime-align:auto){.tagify__input:focus::before{display:none}}.tagify__input:focus:empty::before{transition:.2s ease-out;opacity:.3;transform:none}@supports (-moz-appearance:none){.tagify__input:focus:empty::before{display:inline-block}}.tagify__input::before{content:attr(data-placeholder);line-height:1.8;position:absolute;top:0;z-index:1;color:#000;color:var(--placeholder-color);white-space:nowrap;pointer-events:none;opacity:0}.tagify--mix .tagify__input::before{position:static;line-height:inherit}@supports (-moz-appearance:none){.tagify__input::before{line-height:inherit;position:relative}}.tagify__input::after{content:attr(data-suggest);display:inline-block;white-space:pre;color:#000;opacity:.3;pointer-events:none;max-width:100px}.tagify__input .tagify__tag{margin:0}.tagify__input .tagify__tag>div{padding-top:0;padding-bottom:0}.tagify--mix{line-height:1.7}.tagify--mix .tagify__input{padding:5px;margin:0;width:100%;height:100%;line-height:inherit}.tagify--mix .tagify__input::after{content:none}.tagify--select::after{content:'>';opacity:.5;position:absolute;top:50%;right:0;bottom:0;font:16px monospace;line-height:8px;height:8px;pointer-events:none;transform:translate(-150%,-50%) scaleX(1.2) rotate(90deg);transition:.2s ease-in-out}.tagify--select[aria-expanded=true]::after{transform:translate(-150%,-50%) rotate(270deg) scaleY(1.2)}.tagify--select .tagify__tag{position:absolute;top:0;right:1.8em;bottom:0}.tagify--select .tagify__tag div{display:none}.tagify--select .tagify__input{width:100%}.tagify--invalid{--tags-border-color:#D39494}.tagify__dropdown{position:absolute;z-index:9999;transform:translateY(1px);overflow:hidden}.tagify__dropdown[placement=top]{margin-top:0;transform:translateY(-2px)}.tagify__dropdown[placement=top] .tagify__dropdown__wrapper{border-top-width:1px;border-bottom-width:0}.tagify__dropdown--text{box-shadow:0 0 0 3px rgba(var(--tagify-dd-color-primary),.1);font-size:.9em}.tagify__dropdown--text .tagify__dropdown__wrapper{border-width:1px}.tagify__dropdown__wrapper{max-height:300px;overflow:hidden;background:#fff;background:var(--tagify-dd-bg-color);border:1px solid #3595f6;border-color:var(--tagify-dd-color-primary);border-top-width:0;box-shadow:0 2px 4px -2px rgba(0,0,0,.2);transition:.25s cubic-bezier(0,1,.5,1)}.tagify__dropdown__wrapper:hover{overflow:auto}.tagify__dropdown--initial .tagify__dropdown__wrapper{max-height:20px;transform:translateY(-1em)}.tagify__dropdown--initial[placement=top] .tagify__dropdown__wrapper{transform:translateY(2em)}.tagify__dropdown__item{box-sizing:inherit;padding:.3em .5em;margin:1px;cursor:pointer;border-radius:2px;position:relative;outline:0}.tagify__dropdown__item--active{background:#3595f6;background:var(--tagify-dd-color-primary);color:#fff}.tagify__dropdown__item:active{filter:brightness(105%)}.tagify__dropdown__createTagBtn{width:100%;background:#3595f6;background:var(--tagify-dd-color-primary);color:#fff;color:var(--tagify-dd-bg-color);border:none}
inc/tagify.js ADDED
@@ -0,0 +1,2193 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /**
2
+ * Tagify (v 3.6.3)- tags input component
3
+ * By Yair Even-Or
4
+ * Don't sell this code. (c)
5
+ * https://github.com/yairEO/tagify
6
+ */
7
+ ;(function(root, factory) {
8
+ if (typeof define === 'function' && define.amd) {
9
+ define([], factory);
10
+ } else if (typeof exports === 'object') {
11
+ module.exports = factory();
12
+ } else {
13
+ root.Tagify = factory();
14
+ }
15
+ }(this, function() {
16
+ "use strict";
17
+
18
+ function ownKeys(object, enumerableOnly) { var keys = Object.keys(object); if (Object.getOwnPropertySymbols) { var symbols = Object.getOwnPropertySymbols(object); if (enumerableOnly) symbols = symbols.filter(function (sym) { return Object.getOwnPropertyDescriptor(object, sym).enumerable; }); keys.push.apply(keys, symbols); } return keys; }
19
+
20
+ function _objectSpread(target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i] != null ? arguments[i] : {}; if (i % 2) { ownKeys(source, true).forEach(function (key) { _defineProperty(target, key, source[key]); }); } else if (Object.getOwnPropertyDescriptors) { Object.defineProperties(target, Object.getOwnPropertyDescriptors(source)); } else { ownKeys(source).forEach(function (key) { Object.defineProperty(target, key, Object.getOwnPropertyDescriptor(source, key)); }); } } return target; }
21
+
22
+ function _defineProperty(obj, key, value) { if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; }
23
+
24
+ function _toConsumableArray(arr) { return _arrayWithoutHoles(arr) || _iterableToArray(arr) || _nonIterableSpread(); }
25
+
26
+ function _nonIterableSpread() { throw new TypeError("Invalid attempt to spread non-iterable instance"); }
27
+
28
+ function _iterableToArray(iter) { if (Symbol.iterator in Object(iter) || Object.prototype.toString.call(iter) === "[object Arguments]") return Array.from(iter); }
29
+
30
+ function _arrayWithoutHoles(arr) { if (Array.isArray(arr)) { for (var i = 0, arr2 = new Array(arr.length); i < arr.length; i++) { arr2[i] = arr[i]; } return arr2; } }
31
+
32
+ var isFirefox = typeof InstallTrigger !== 'undefined';
33
+
34
+ var getUID = function getUID() {
35
+ return (new Date().getTime() + Math.floor(Math.random() * 10000 + 1)).toString(16);
36
+ };
37
+
38
+ var removeCollectionProp = function removeCollectionProp(collection, unwantedProp) {
39
+ return collection.map(function (v) {
40
+ var props = {};
41
+
42
+ for (var p in v) {
43
+ if (p != unwantedProp) props[p] = v[p];
44
+ }
45
+
46
+ return props;
47
+ });
48
+ };
49
+ /**
50
+ * Checks if an argument is a javascript Object
51
+ */
52
+
53
+
54
+ function isObject(obj) {
55
+ var type = Object.prototype.toString.call(obj).split(' ')[1].slice(0, -1);
56
+ return obj === Object(obj) && type != 'Array' && type != 'Function' && type != 'RegExp' && type != 'HTMLUnknownElement';
57
+ }
58
+
59
+ function decode(s) {
60
+ var el = document.createElement('div');
61
+ return s.replace(/\&#?[0-9a-z]+;/gi, function (enc) {
62
+ el.innerHTML = enc;
63
+ return el.innerText;
64
+ });
65
+ }
66
+ /**
67
+ * utility method
68
+ * https://stackoverflow.com/a/6234804/104380
69
+ */
70
+
71
+
72
+ function escapeHTML(s) {
73
+ return s.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;").replace(/'/g, "&#039;");
74
+ } // ☝☝☝ ALL THE ABOVE WILL BE MOVED INTO SEPARATE FILES ☝☝☝
75
+
76
+ /**
77
+ * @constructor
78
+ * @param {Object} input DOM element
79
+ * @param {Object} settings settings object
80
+ */
81
+
82
+
83
+ function Tagify(input, settings) {
84
+ // protection
85
+ if (!input) {
86
+ console.warn('Tagify: ', 'invalid input element ', input);
87
+ return this;
88
+ }
89
+
90
+ this.applySettings(input, settings || {});
91
+ this.state = {
92
+ editing: false,
93
+ actions: {},
94
+ // UI actions for state-locking
95
+ dropdown: {}
96
+ };
97
+ this.value = []; // tags' data
98
+ // events' callbacks references will be stores here, so events could be unbinded
99
+
100
+ this.listeners = {};
101
+ this.DOM = {}; // Store all relevant DOM elements in an Object
102
+
103
+ this.extend(this, new this.EventDispatcher(this));
104
+ this.build(input);
105
+ this.getCSSVars();
106
+ this.loadOriginalValues();
107
+ this.events.customBinding.call(this);
108
+ this.events.binding.call(this);
109
+ input.autofocus && this.DOM.input.focus();
110
+ }
111
+
112
+ Tagify.prototype = {
113
+ isIE: window.document.documentMode,
114
+ // https://developer.mozilla.org/en-US/docs/Web/API/Document/compatMode#Browser_compatibility
115
+ TEXTS: {
116
+ empty: "empty",
117
+ exceed: "number of tags exceeded",
118
+ pattern: "pattern mismatch",
119
+ duplicate: "already exists",
120
+ notAllowed: "not allowed"
121
+ },
122
+ DEFAULTS: {
123
+ delimiters: ",",
124
+ // [RegEx] split tags by any of these delimiters ("null" to cancel) Example: ",| |."
125
+ pattern: null,
126
+ // RegEx pattern to validate input by. Ex: /[1-9]/
127
+ maxTags: Infinity,
128
+ // Maximum number of tags
129
+ callbacks: {},
130
+ // Exposed callbacks object to be triggered on certain events
131
+ addTagOnBlur: true,
132
+ // Flag - automatically adds the text which was inputed as a tag when blur event happens
133
+ duplicates: false,
134
+ // Flag - allow tuplicate tags
135
+ whitelist: [],
136
+ // Array of tags to suggest as the user types (can be used along with "enforceWhitelist" setting)
137
+ blacklist: [],
138
+ // A list of non-allowed tags
139
+ enforceWhitelist: false,
140
+ // Flag - Only allow tags allowed in whitelist
141
+ keepInvalidTags: false,
142
+ // Flag - if true, do not remove tags which did not pass validation
143
+ mixTagsAllowedAfter: /,|\.|\:|\s/,
144
+ // RegEx - Define conditions in which mix-tags content is allowing a tag to be added after
145
+ mixTagsInterpolator: ['[[', ']]'],
146
+ // Interpolation for mix mode. Everything between this will becmoe a tag
147
+ backspace: true,
148
+ // false / true / "edit"
149
+ skipInvalid: false,
150
+ // If `true`, do not add invalid, temporary, tags before automatically removing them
151
+ editTags: 2,
152
+ // 1 or 2 clicks to edit a tag. false/null for not allowing editing
153
+ transformTag: function transformTag() {},
154
+ // Takes a tag input string as argument and returns a transformed value
155
+ autoComplete: {
156
+ enabled: true,
157
+ // Tries to suggest the input's value while typing (match from whitelist) by adding the rest of term as grayed-out text
158
+ rightKey: false // If `true`, when Right key is pressed, use the suggested value to create a tag, else just auto-completes the input. in mixed-mode this is set to "true"
159
+
160
+ },
161
+ dropdown: {
162
+ classname: '',
163
+ enabled: 2,
164
+ // minimum input characters needs to be typed for the dropdown to show
165
+ maxItems: 10,
166
+ searchKeys: [],
167
+ fuzzySearch: true,
168
+ highlightFirst: false,
169
+ // highlights first-matched item in the list
170
+ closeOnSelect: true,
171
+ // closes the dropdown after selecting an item, if `enabled:0` (which means always show dropdown)
172
+ position: 'all' // 'manual' / 'text' / 'all'
173
+
174
+ }
175
+ },
176
+ // Using ARIA & role attributes
177
+ // https://www.w3.org/TR/wai-aria-practices/examples/combobox/aria1.1pattern/listbox-combo.html
178
+ templates: {
179
+ wrapper: function wrapper(input, settings) {
180
+ return "<tags class=\"tagify ".concat(settings.mode ? "tagify--" + settings.mode : "", " ").concat(input.className, "\"\n ").concat(settings.readonly ? 'readonly aria-readonly="true"' : 'aria-haspopup="listbox" aria-expanded="false"', "\n role=\"tagslist\"\n tabIndex=\"-1\">\n <span contenteditable data-placeholder=\"").concat(settings.placeholder || '&#8203;', "\" aria-placeholder=\"").concat(settings.placeholder || '', "\"\n class=\"tagify__input\"\n role=\"textbox\"\n aria-autocomplete=\"both\"\n aria-multiline=\"").concat(settings.mode == 'mix' ? true : false, "\"></span>\n </tags>");
181
+ },
182
+ tag: function tag(value, tagData) {
183
+ return "<tag title=\"".concat(tagData.title || value, "\"\n contenteditable='false'\n spellcheck='false'\n tabIndex=\"-1\"\n class=\"tagify__tag ").concat(tagData["class"] ? tagData["class"] : "", "\"\n ").concat(this.getAttributes(tagData), ">\n <x title='' class='tagify__tag__removeBtn' role='button' aria-label='remove tag'></x>\n <div>\n <span class='tagify__tag-text'>").concat(value, "</span>\n </div>\n </tag>");
184
+ },
185
+ dropdownItem: function dropdownItem(item) {
186
+ var mapValueTo = this.settings.dropdown.mapValueTo,
187
+ value = (mapValueTo ? typeof mapValueTo == 'function' ? mapValueTo(item) : item[mapValueTo] : item.value) || item.value,
188
+ sanitizedValue = (value || item).replace(/`|'/g, "&#39;");
189
+ return "<div ".concat(this.getAttributes(item), "\n class='tagify__dropdown__item ").concat(item["class"] ? item["class"] : "", "'\n tabindex=\"0\"\n role=\"option\">").concat(sanitizedValue, "</div>");
190
+ }
191
+ },
192
+ customEventsList: ['add', 'remove', 'invalid', 'input', 'click', 'keydown', 'focus', 'blur', 'edit:input', 'edit:updated', 'edit:start', 'edit:keydown', 'dropdown:show', 'dropdown:hide', 'dropdown:select'],
193
+ applySettings: function applySettings(input, settings) {
194
+ var _this2 = this;
195
+
196
+ this.DEFAULTS.templates = this.templates;
197
+ this.settings = this.extend({}, this.DEFAULTS, settings);
198
+ this.settings.readonly = input.hasAttribute('readonly'); // if "readonly" do not include an "input" element inside the Tags component
199
+
200
+ this.settings.placeholder = input.getAttribute('placeholder') || this.settings.placeholder || "";
201
+ if (this.isIE) this.settings.autoComplete = false; // IE goes crazy if this isn't false
202
+
203
+ ["whitelist", "blacklist"].forEach(function (name) {
204
+ var attrVal = input.getAttribute('data-' + name);
205
+
206
+ if (attrVal) {
207
+ attrVal = attrVal.split(_this2.settings.delimiters);
208
+ if (attrVal instanceof Array) _this2.settings[name] = attrVal;
209
+ }
210
+ }); // backward-compatibility for old version of "autoComplete" setting:
211
+
212
+ if ("autoComplete" in settings && !isObject(settings.autoComplete)) {
213
+ this.settings.autoComplete = this.DEFAULTS.autoComplete;
214
+ this.settings.autoComplete.enabled = settings.autoComplete;
215
+ }
216
+
217
+ if (input.pattern) try {
218
+ this.settings.pattern = new RegExp(input.pattern);
219
+ } catch (e) {} // Convert the "delimiters" setting into a REGEX object
220
+
221
+ if (this.settings.delimiters) {
222
+ try {
223
+ this.settings.delimiters = new RegExp(this.settings.delimiters, "g");
224
+ } catch (e) {}
225
+ } // make sure the dropdown will be shown on "focus" and not only after typing something (in "select" mode)
226
+
227
+
228
+ if (this.settings.mode == 'select') this.settings.dropdown.enabled = 0;
229
+ if (this.settings.mode == 'mix') this.settings.autoComplete.rightKey = true;
230
+ },
231
+
232
+ /**
233
+ * Returns a string of HTML element attributes
234
+ * @param {Object} data [Tag data]
235
+ */
236
+ getAttributes: function getAttributes(data) {
237
+ // only items which are objects have properties which can be used as attributes
238
+ if (Object.prototype.toString.call(data) != "[object Object]") return '';
239
+ var keys = Object.keys(data),
240
+ s = "",
241
+ propName,
242
+ i;
243
+
244
+ for (i = keys.length; i--;) {
245
+ propName = keys[i];
246
+ if (propName != 'class' && data.hasOwnProperty(propName) && data[propName]) s += " " + propName + (data[propName] ? "=\"".concat(data[propName], "\"") : "");
247
+ }
248
+
249
+ return s;
250
+ },
251
+
252
+ /**
253
+ * utility method
254
+ * https://stackoverflow.com/a/35385518/104380
255
+ * @param {String} s [HTML string]
256
+ * @return {Object} [DOM node]
257
+ */
258
+ parseHTML: function parseHTML(s) {
259
+ var parser = new DOMParser(),
260
+ node = parser.parseFromString(s.trim(), "text/html");
261
+ return node.body.firstElementChild;
262
+ },
263
+
264
+ /**
265
+ * Get the caret position relative to the viewport
266
+ * https://stackoverflow.com/q/58985076/104380
267
+ *
268
+ * @returns {object} left, top distance in pixels
269
+ */
270
+ getCaretGlobalPosition: function getCaretGlobalPosition() {
271
+ var sel = document.getSelection();
272
+
273
+ if (sel.rangeCount) {
274
+ var r = sel.getRangeAt(0);
275
+ var node = r.startContainer;
276
+ var offset = r.startOffset;
277
+ var rect, r2;
278
+
279
+ if (offset > 0) {
280
+ r2 = document.createRange();
281
+ r2.setStart(node, offset - 1);
282
+ r2.setEnd(node, offset);
283
+ rect = r2.getBoundingClientRect();
284
+ return {
285
+ left: rect.right,
286
+ top: rect.top,
287
+ bottom: rect.bottom
288
+ };
289
+ }
290
+ }
291
+
292
+ return {
293
+ left: -9999,
294
+ top: -9999
295
+ };
296
+ },
297
+
298
+ /**
299
+ * Get specific CSS variables which are relevant to this script and parse them as needed.
300
+ * The result is saved on the instance in "this.CSSVars"
301
+ */
302
+ getCSSVars: function getCSSVars() {
303
+ var compStyle = getComputedStyle(this.DOM.scope, null);
304
+
305
+ var getProp = function getProp(name) {
306
+ return compStyle.getPropertyValue('--' + name);
307
+ };
308
+
309
+ function seprateUnitFromValue(a) {
310
+ if (!a) return {};
311
+ a = a.trim().split(' ')[0];
312
+ var unit = a.split(/\d+/g).filter(function (n) {
313
+ return n;
314
+ }).pop().trim(),
315
+ value = +a.split(unit).filter(function (n) {
316
+ return n;
317
+ })[0].trim();
318
+ return {
319
+ value: value,
320
+ unit: unit
321
+ };
322
+ }
323
+
324
+ this.CSSVars = {
325
+ tagHideTransition: function (_ref) {
326
+ var value = _ref.value,
327
+ unit = _ref.unit;
328
+ return unit == 's' ? value * 1000 : value;
329
+ }(seprateUnitFromValue(getProp('tag-hide-transition')))
330
+ };
331
+ },
332
+
333
+ /**
334
+ * builds the HTML of this component
335
+ * @param {Object} input [DOM element which would be "transformed" into "Tags"]
336
+ */
337
+ build: function build(input) {
338
+ var DOM = this.DOM,
339
+ template = this.settings.templates.wrapper(input, this.settings);
340
+ DOM.originalInput = input;
341
+ DOM.scope = this.parseHTML(template);
342
+ DOM.input = DOM.scope.querySelector('[contenteditable]');
343
+ input.parentNode.insertBefore(DOM.scope, input);
344
+
345
+ if (this.settings.dropdown.enabled >= 0) {
346
+ this.dropdown.init.call(this);
347
+ }
348
+ },
349
+
350
+ /**
351
+ * revert any changes made by this component
352
+ */
353
+ destroy: function destroy() {
354
+ this.DOM.scope.parentNode.removeChild(this.DOM.scope);
355
+ this.dropdown.hide.call(this, true);
356
+ clearTimeout(this.dropdownHide__bindEventsTimeout);
357
+ },
358
+
359
+ /**
360
+ * if the original input had any values, add them as tags
361
+ */
362
+ loadOriginalValues: function loadOriginalValues(value) {
363
+ value = value || this.DOM.originalInput.value; // if the original input already had any value (tags)
364
+
365
+ if (!value) return;
366
+ this.removeAllTags();
367
+ if (this.settings.mode == 'mix') this.parseMixTags(value.trim());else {
368
+ try {
369
+ if (typeof JSON.parse(value) !== 'string') value = JSON.parse(value);
370
+ } catch (err) {}
371
+
372
+ this.addTags(value).forEach(function (tag) {
373
+ return tag && tag.classList.add('tagify--noAnim');
374
+ });
375
+ }
376
+ },
377
+
378
+ /**
379
+ * merge objects into a single new one
380
+ * TEST: extend({}, {a:{foo:1}, b:[]}, {a:{bar:2}, b:[1], c:()=>{}})
381
+ */
382
+ extend: function extend(o, o1, o2) {
383
+ var that = this;
384
+ if (!(o instanceof Object)) o = {};
385
+ copy(o, o1);
386
+ if (o2) copy(o, o2);
387
+
388
+ function copy(a, b) {
389
+ // copy o2 to o
390
+ for (var key in b) {
391
+ if (b.hasOwnProperty(key)) {
392
+ if (isObject(b[key])) {
393
+ if (!isObject(a[key])) a[key] = Object.assign({}, b[key]);else copy(a[key], b[key]);
394
+ } else a[key] = b[key];
395
+ }
396
+ }
397
+ }
398
+
399
+ return o;
400
+ },
401
+ cloneEvent: function cloneEvent(e) {
402
+ var clonedEvent = {};
403
+
404
+ for (var v in e) {
405
+ clonedEvent[v] = e[v];
406
+ }
407
+
408
+ return clonedEvent;
409
+ },
410
+
411
+ /**
412
+ * A constructor for exposing events to the outside
413
+ */
414
+ EventDispatcher: function EventDispatcher(instance) {
415
+ // Create a DOM EventTarget object
416
+ var target = document.createTextNode('');
417
+
418
+ function addRemove(op, events, cb) {
419
+ if (cb) events.split(/\s+/g).forEach(function (name) {
420
+ return target[op + 'EventListener'].call(target, name, cb);
421
+ });
422
+ } // Pass EventTarget interface calls to DOM EventTarget object
423
+
424
+
425
+ this.off = function (events, cb) {
426
+ addRemove('remove', events, cb);
427
+ return this;
428
+ };
429
+
430
+ this.on = function (events, cb) {
431
+ if (cb && typeof cb == 'function') addRemove('add', events, cb);
432
+ return this;
433
+ };
434
+
435
+ this.trigger = function (eventName, data) {
436
+ var e;
437
+ if (!eventName) return;
438
+
439
+ if (instance.settings.isJQueryPlugin) {
440
+ if (eventName == 'remove') eventName = 'removeTag'; // issue #222
441
+
442
+ jQuery(instance.DOM.originalInput).triggerHandler(eventName, [data]);
443
+ } else {
444
+ try {
445
+ e = new CustomEvent(eventName, {
446
+ "detail": this.extend({}, data, {
447
+ tagify: this
448
+ })
449
+ });
450
+ } catch (err) {
451
+ console.warn(err);
452
+ }
453
+
454
+ target.dispatchEvent(e);
455
+ }
456
+ };
457
+ },
458
+
459
+ /**
460
+ * Toogle loading state on/off
461
+ * @param {Boolean} isLoading
462
+ */
463
+ loading: function loading(isLoading) {
464
+ // IE11 doesn't support toggle with second parameter
465
+ this.DOM.scope.classList[isLoading ? "add" : "remove"]('tagify--loading');
466
+ return this;
467
+ },
468
+ toggleFocusClass: function toggleFocusClass(force) {
469
+ this.DOM.scope.classList.toggle('tagify--focus', !!force);
470
+ },
471
+
472
+ /**
473
+ * DOM events listeners binding
474
+ */
475
+ events: {
476
+ // bind custom events which were passed in the settings
477
+ customBinding: function customBinding() {
478
+ var _this3 = this;
479
+
480
+ this.customEventsList.forEach(function (name) {
481
+ _this3.on(name, _this3.settings.callbacks[name]);
482
+ });
483
+ },
484
+ binding: function binding() {
485
+ var bindUnbind = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : true;
486
+
487
+ var _CB = this.events.callbacks,
488
+ _CBR,
489
+ action = bindUnbind ? 'addEventListener' : 'removeEventListener'; // do not allow the main events to be bound more than once
490
+
491
+
492
+ if (this.state.mainEvents && bindUnbind) return; // set the binding state of the main events, so they will not be bound more than once
493
+
494
+ this.state.mainEvents = bindUnbind;
495
+
496
+ if (bindUnbind && !this.listeners.main) {
497
+ // this event should never be unbinded:
498
+ // IE cannot register "input" events on contenteditable elements, so the "keydown" should be used instead..
499
+ this.DOM.input.addEventListener(this.isIE ? "keydown" : "input", _CB[this.isIE ? "onInputIE" : "onInput"].bind(this));
500
+ if (this.settings.isJQueryPlugin) jQuery(this.DOM.originalInput).on('tagify.removeAllTags', this.removeAllTags.bind(this));
501
+ } // setup callback references so events could be removed later
502
+
503
+
504
+ _CBR = this.listeners.main = this.listeners.main || {
505
+ focus: ['input', _CB.onFocusBlur.bind(this)],
506
+ blur: ['input', _CB.onFocusBlur.bind(this)],
507
+ keydown: ['input', _CB.onKeydown.bind(this)],
508
+ click: ['scope', _CB.onClickScope.bind(this)],
509
+ dblclick: ['scope', _CB.onDoubleClickScope.bind(this)]
510
+ };
511
+
512
+ for (var eventName in _CBR) {
513
+ // make sure the focus/blur event is always regesitered (and never more than once)
514
+ if (eventName == 'blur' && !bindUnbind) return;
515
+
516
+ this.DOM[_CBR[eventName][0]][action](eventName, _CBR[eventName][1]);
517
+ }
518
+ },
519
+
520
+ /**
521
+ * DOM events callbacks
522
+ */
523
+ callbacks: {
524
+ onFocusBlur: function onFocusBlur(e) {
525
+ var text = e.target ? e.target.textContent.trim() : '',
526
+ // a string
527
+ _s = this.settings,
528
+ type = e.type,
529
+ eventData = {
530
+ relatedTarget: e.relatedTarget
531
+ },
532
+ shouldAddTags; // goes into this scenario only on input "blur" and a tag was clicked
533
+
534
+ if (e.relatedTarget && e.relatedTarget.classList.contains('tagify__tag') && this.DOM.scope.contains(e.relatedTarget)) return;
535
+
536
+ if (type == 'blur' && e.relatedTarget === this.DOM.scope) {
537
+ this.dropdown.hide.call(this);
538
+ this.DOM.input.focus();
539
+ return;
540
+ }
541
+
542
+ if (this.state.actions.selectOption && (_s.dropdown.enabled || !_s.dropdown.closeOnSelect)) return;
543
+ this.state.hasFocus = type == "focus" ? +new Date() : false;
544
+ this.toggleFocusClass(this.state.hasFocus);
545
+ this.setRangeAtStartEnd(false);
546
+
547
+ if (_s.mode == 'mix') {
548
+ if (type == "focus") {
549
+ this.trigger("focus", eventData);
550
+ } else if (e.type == "blur") {
551
+ this.trigger("blur", eventData);
552
+ this.loading(false);
553
+ this.dropdown.hide.call(this);
554
+ }
555
+
556
+ return;
557
+ }
558
+
559
+ if (type == "focus") {
560
+ this.trigger("focus", eventData); // e.target.classList.remove('placeholder');
561
+
562
+ if (_s.dropdown.enabled === 0 && _s.mode != "select") {
563
+ this.dropdown.show.call(this);
564
+ }
565
+
566
+ return;
567
+ } else if (type == "blur") {
568
+ this.trigger("blur", eventData);
569
+ this.loading(false);
570
+ shouldAddTags = this.settings.mode == 'select' ? !this.value.length || this.value[0].value != text : text && !this.state.actions.selectOption && _s.addTagOnBlur; // do not add a tag if "selectOption" action was just fired (this means a tag was just added from the dropdown)
571
+
572
+ shouldAddTags && this.addTags(text, true);
573
+ }
574
+
575
+ this.DOM.input.removeAttribute('style');
576
+ this.dropdown.hide.call(this);
577
+ },
578
+ onKeydown: function onKeydown(e) {
579
+ var _this4 = this;
580
+
581
+ var s = e.target.textContent.trim();
582
+ this.trigger("keydown", {
583
+ originalEvent: this.cloneEvent(e)
584
+ });
585
+ /**
586
+ * ONLY FOR MIX-MODE:
587
+ */
588
+
589
+ if (this.settings.mode == 'mix') {
590
+ switch (e.key) {
591
+ case 'Left':
592
+ case 'ArrowLeft':
593
+ {
594
+ // when left arrow was pressed, raise a flag so when the dropdown is shown, right-arrow will be ignored
595
+ // because it seems likely the user wishes to use the arrows to move the caret
596
+ this.state.actions.ArrowLeft = true;
597
+ break;
598
+ }
599
+
600
+ case 'Delete':
601
+ case 'Backspace':
602
+ {
603
+ if (this.state.editing) return;
604
+ var selection = document.getSelection(),
605
+ lastInputValue = decode(this.DOM.input.innerHTML),
606
+ lastTagElems = this.getTagElms();
607
+ if (selection.anchorNode.nodeType == 3 && // node at caret location is a Text node
608
+ !selection.anchorNode.nodeValue && // has some text
609
+ selection.anchorNode.previousElementSibling) // text node has a Tag node before it
610
+ e.preventDefault(); // if( isFirefox && selection && selection.anchorOffset == 0 )
611
+ // this.removeTag(selection.anchorNode.previousSibling)
612
+ // a minimum delay is needed before the node actually gets ditached from the document (don't know why),
613
+ // to know exactly which tag was deleted. This is the easiest way of knowing besides using MutationObserver
614
+
615
+ setTimeout(function () {
616
+ // fixes #384, where the first and only tag will not get removed with backspace
617
+ if (decode(_this4.DOM.input.innerHTML).length >= lastInputValue.length) {
618
+ _this4.removeTag(selection.anchorNode.previousElementSibling); // the above "removeTag" methods removes the tag with a transition. Chrome adds a <br> element for some reason at this stage
619
+
620
+
621
+ if (_this4.DOM.input.children.length == 2 && _this4.DOM.input.children[1].tagName == "BR") {
622
+ _this4.DOM.input.innerHTML = "";
623
+ _this4.value.length = 0;
624
+ return true;
625
+ }
626
+ } // find out which tag(s) were deleted and trigger "remove" event
627
+ // iterate over the list of tags still in the document and then filter only those from the "this.value" collection
628
+
629
+
630
+ _this4.value = [].map.call(lastTagElems, function (node, nodeIdx) {
631
+ var tagData = node.__tagifyTagData;
632
+ if (node.parentNode) return tagData;else _this4.trigger('remove', {
633
+ tag: node,
634
+ index: nodeIdx,
635
+ data: tagData
636
+ });
637
+ }).filter(function (n) {
638
+ return n;
639
+ }); // remove empty items in the mapped array
640
+ }, 50); // Firefox needs this higher duration for some reason or things get buggy when to deleting text from the end
641
+
642
+ break;
643
+ }
644
+ // currently commented to allow new lines in mixed-mode
645
+ // case 'Enter' :
646
+ // e.preventDefault(); // solves Chrome bug - http://stackoverflow.com/a/20398191/104380
647
+ }
648
+
649
+ return true;
650
+ }
651
+
652
+ switch (e.key) {
653
+ case 'Backspace':
654
+ if (!this.state.dropdown.visible) {
655
+ if (s == "" || s.charCodeAt(0) == 8203) {
656
+ // 8203: ZERO WIDTH SPACE unicode
657
+ if (this.settings.backspace === true) this.removeTag();else if (this.settings.backspace == 'edit') setTimeout(this.editTag.bind(this), 0); // timeout reason: when edited tag gets focused and the caret is placed at the end, the last character gets deletec (because of backspace)
658
+ }
659
+ }
660
+
661
+ break;
662
+
663
+ case 'Esc':
664
+ case 'Escape':
665
+ if (this.state.dropdown.visible) return;
666
+ e.target.blur();
667
+ break;
668
+
669
+ case 'Down':
670
+ case 'ArrowDown':
671
+ // if( this.settings.mode == 'select' ) // issue #333
672
+ if (!this.state.dropdown.visible) this.dropdown.show.call(this);
673
+ break;
674
+
675
+ case 'ArrowRight':
676
+ {
677
+ var tagData = this.state.inputSuggestion || this.state.ddItemData;
678
+
679
+ if (tagData && this.settings.autoComplete.rightKey) {
680
+ this.addTags([tagData], true);
681
+ return;
682
+ }
683
+
684
+ break;
685
+ }
686
+
687
+ case 'Tab':
688
+ {
689
+ if (!s || this.settings.mode == 'select') return true;
690
+ }
691
+
692
+ case 'Enter':
693
+ e.preventDefault(); // solves Chrome bug - http://stackoverflow.com/a/20398191/104380
694
+ // because the main "keydown" event is bound before the dropdown events, this will fire first and will not *yet*
695
+ // know if an option was just selected from the dropdown menu. If an option was selected,
696
+ // the dropdown events should handle adding the tag
697
+
698
+ setTimeout(function () {
699
+ if (_this4.state.actions.selectOption) return;
700
+
701
+ _this4.addTags(s, true);
702
+ });
703
+ }
704
+ },
705
+ onInput: function onInput(e) {
706
+ var value = this.settings.mode == 'mix' ? this.DOM.input.textContent : this.input.normalize.call(this),
707
+ showSuggestions = value.length >= this.settings.dropdown.enabled,
708
+ eventData = {
709
+ value: value,
710
+ inputElm: this.DOM.input
711
+ };
712
+ if (this.settings.mode == 'mix') return this.events.callbacks.onMixTagsInput.call(this, e);
713
+ eventData.isValid = this.validateTag({
714
+ value: value
715
+ });
716
+ this.trigger('input', eventData); // "input" event must be triggered at this point, before the dropdown is shown
717
+
718
+ if (!value) {
719
+ this.input.set.call(this, '');
720
+ return;
721
+ }
722
+
723
+ if (this.input.value == value) return; // for IE; since IE doesn't have an "input" event so "keyDown" is used instead
724
+ // save the value on the input's State object
725
+
726
+ this.input.set.call(this, value, false); // update the input with the normalized value and run validations
727
+ // this.setRangeAtStartEnd(); // fix caret position
728
+
729
+ if (value.search(this.settings.delimiters) != -1) {
730
+ if (this.addTags(value)) {
731
+ this.input.set.call(this); // clear the input field's value
732
+ }
733
+ } else if (this.settings.dropdown.enabled >= 0) {
734
+ this.dropdown[showSuggestions ? "show" : "hide"].call(this, value);
735
+ }
736
+ },
737
+ onMixTagsInput: function onMixTagsInput(e) {
738
+ var _this5 = this;
739
+
740
+ var range,
741
+ split,
742
+ tag,
743
+ showSuggestions,
744
+ selection,
745
+ _s = this.settings,
746
+ lastTagsCount = this.value.length,
747
+ tagsCount = this.getTagElms().length; // check if ANY tags were magically added through browser redo/undo
748
+
749
+ if (tagsCount > lastTagsCount) {
750
+ this.value = [].map.call(this.getTagElms(), function (node) {
751
+ return node.__tagifyTagData;
752
+ });
753
+ this.update();
754
+ return;
755
+ }
756
+
757
+ if (this.hasMaxTags()) return true;
758
+
759
+ if (window.getSelection) {
760
+ selection = window.getSelection(); // only detect tags if selection is inside a textNode (not somehow on already-existing tag)
761
+
762
+ if (selection.rangeCount > 0 && selection.anchorNode.nodeType == 3) {
763
+ range = selection.getRangeAt(0).cloneRange();
764
+ range.collapse(true);
765
+ range.setStart(selection.focusNode, 0);
766
+ split = range.toString().split(_s.mixTagsAllowedAfter); // ["foo", "bar", "@a"]
767
+
768
+ tag = split[split.length - 1].match(_s.pattern); // tag = range.toString().match(_s.pattern) // allow spaces
769
+
770
+ if (tag) {
771
+ this.state.actions.ArrowLeft = false; // start fresh, assuming the user did not (yet) used any arrow to move the caret
772
+
773
+ this.state.tag = {
774
+ prefix: tag[0],
775
+ value: tag.input.split(tag[0])[1]
776
+ };
777
+ showSuggestions = this.state.tag.value.length >= _s.dropdown.enabled;
778
+ }
779
+ }
780
+ } // wait until the "this.value" has been updated (see "onKeydown" method for "mix-mode")
781
+ // the dropdown must be shown only after this event has been driggered, so an implementer could
782
+ // dynamically change the whitelist.
783
+
784
+
785
+ setTimeout(function () {
786
+ _this5.update();
787
+
788
+ _this5.trigger("input", _this5.extend({}, _this5.state.tag, {
789
+ textContent: _this5.DOM.input.textContent
790
+ }));
791
+
792
+ if (_this5.state.tag) _this5.dropdown[showSuggestions ? "show" : "hide"].call(_this5, _this5.state.tag.value);
793
+ }, 10);
794
+ },
795
+ onInputIE: function onInputIE(e) {
796
+ var _this = this; // for the "e.target.textContent" to be changed, the browser requires a small delay
797
+
798
+
799
+ setTimeout(function () {
800
+ _this.events.callbacks.onInput.call(_this, e);
801
+ });
802
+ },
803
+ onClickScope: function onClickScope(e) {
804
+ var tagElm = e.target.closest('.tagify__tag'),
805
+ _s = this.settings,
806
+ timeDiffFocus = +new Date() - this.state.hasFocus,
807
+ tagElmIdx;
808
+
809
+ if (e.target == this.DOM.scope) {
810
+ // if( !this.state.hasFocus )
811
+ // this.dropdown.hide.call(this)
812
+ this.DOM.input.focus();
813
+ return;
814
+ } else if (e.target.classList.contains("tagify__tag__removeBtn")) {
815
+ this.removeTag(e.target.parentNode);
816
+ return;
817
+ } else if (tagElm) {
818
+ tagElmIdx = this.getNodeIndex(tagElm);
819
+ this.trigger("click", {
820
+ tag: tagElm,
821
+ index: tagElmIdx,
822
+ data: this.value[tagElmIdx],
823
+ originalEvent: this.cloneEvent(e)
824
+ });
825
+ if (this.settings.editTags == 1) this.events.callbacks.onDoubleClickScope.call(this, e);
826
+ return;
827
+ } // when clicking on the input itself
828
+ else if (e.target == this.DOM.input && timeDiffFocus > 500) {
829
+ if (this.state.dropdown.visible) this.dropdown.hide.call(this);else if (_s.dropdown.enabled === 0 && _s.mode != 'mix') this.dropdown.show.call(this);
830
+ return;
831
+ }
832
+
833
+ if (_s.mode == 'select') !this.state.dropdown.visible && this.dropdown.show.call(this);
834
+ },
835
+ onEditTagInput: function onEditTagInput(editableElm, e) {
836
+ var tagElm = editableElm.closest('tag'),
837
+ tagElmIdx = this.getNodeIndex(tagElm),
838
+ value = this.input.normalize.call(this, editableElm),
839
+ isValid = this.validateTag({
840
+ value: value
841
+ }); // the value chould have been invalid in the first-place so make sure to re-validate it
842
+
843
+ tagElm.classList.toggle('tagify--invalid', isValid !== true);
844
+ tagElm.__tagifyTagData.__isValid = isValid; // show dropdown if typed text is equal or more than the "enabled" dropdown setting
845
+
846
+ if (value.length >= this.settings.dropdown.enabled) {
847
+ this.state.editing.value = value;
848
+ this.dropdown.show.call(this, value);
849
+ }
850
+
851
+ this.trigger("edit:input", {
852
+ tag: tagElm,
853
+ index: tagElmIdx,
854
+ data: this.extend({}, this.value[tagElmIdx], {
855
+ newValue: value
856
+ }),
857
+ originalEvent: this.cloneEvent(e)
858
+ });
859
+ },
860
+ onEditTagFocus: function onEditTagFocus(tagElm) {
861
+ this.state.editing = {
862
+ scope: tagElm,
863
+ input: tagElm.querySelector("[contenteditable]")
864
+ };
865
+ },
866
+ onEditTagBlur: function onEditTagBlur(editableElm) {
867
+ if (!this.state.hasFocus) this.toggleFocusClass();
868
+ if (!this.DOM.scope.contains(editableElm)) return;
869
+ var tagElm = editableElm.closest('.tagify__tag'),
870
+ currentValue = this.input.normalize.call(this, editableElm),
871
+ value = currentValue,
872
+ // || editableElm.originalValue,
873
+ hasChanged = value != editableElm.originalValue,
874
+ tagData = this.extend({}, tagElm.__tagifyTagData, {
875
+ value: value
876
+ }),
877
+ isValid = this.validateTag(tagData); // this.DOM.input.focus()
878
+
879
+ if (!currentValue) {
880
+ this.removeTag(tagElm);
881
+ this.onEditTagDone(null, tagData);
882
+ return;
883
+ }
884
+
885
+ if (hasChanged) {
886
+ this.settings.transformTag.call(this, tagData); // MUST re-validate after tag transformation
887
+
888
+ isValid = this.validateTag(tagData);
889
+ } else {
890
+ // tagData.__isValid = this.validateTag(tagData)
891
+ this.onEditTagDone(tagElm, tagData);
892
+ return;
893
+ }
894
+
895
+ if (isValid !== true) {
896
+ this.trigger("invalid", {
897
+ data: tagData,
898
+ tag: tagElm,
899
+ message: isValid
900
+ });
901
+ return;
902
+ }
903
+
904
+ this.onEditTagDone(tagElm, tagData);
905
+ },
906
+ onEditTagkeydown: function onEditTagkeydown(e, tagElm) {
907
+ this.trigger("edit:keydown", {
908
+ originalEvent: this.cloneEvent(e)
909
+ });
910
+
911
+ switch (e.key) {
912
+ case 'Esc':
913
+ case 'Escape':
914
+ e.target.textContent = e.target.originalValue; // revert back data as it was pre-edit
915
+
916
+ tagElm.__tagifyTagData = tagElm.__tagifyTagData.__originalData;
917
+
918
+ case 'Enter':
919
+ case 'Tab':
920
+ e.preventDefault();
921
+ e.target.blur();
922
+ }
923
+ },
924
+ onDoubleClickScope: function onDoubleClickScope(e) {
925
+ var tagElm = e.target.closest('tag'),
926
+ _s = this.settings,
927
+ isEditingTag,
928
+ isReadyOnlyTag;
929
+ if (!tagElm) return;
930
+ isEditingTag = tagElm.classList.contains('tagify__tag--editable');
931
+ isReadyOnlyTag = tagElm.hasAttribute('readonly');
932
+ if (_s.mode != 'select' && !_s.readonly && !isEditingTag && !isReadyOnlyTag && this.settings.editTags) this.editTag(tagElm);
933
+ this.toggleFocusClass(true);
934
+ }
935
+ }
936
+ },
937
+
938
+ /**
939
+ * Enters a tag into "edit" mode
940
+ * @param {Node} tagElm the tag element to edit. if nothing specified, use last last
941
+ */
942
+ editTag: function editTag(tagElm, opts) {
943
+ var _this6 = this;
944
+
945
+ tagElm = tagElm || this.getLastTag();
946
+ opts = opts || {};
947
+
948
+ var editableElm = tagElm.querySelector('.tagify__tag-text'),
949
+ tagIdx = this.getNodeIndex(tagElm),
950
+ tagData = tagElm.__tagifyTagData,
951
+ _CB = this.events.callbacks,
952
+ that = this,
953
+ isValid = true,
954
+ delayed_onEditTagBlur = function delayed_onEditTagBlur() {
955
+ that.state.editing = false;
956
+ setTimeout(_CB.onEditTagBlur.bind(that), 0, editableElm);
957
+ };
958
+
959
+ if (!editableElm) {
960
+ console.warn('Cannot find element in Tag template: ', '.tagify__tag-text');
961
+ return;
962
+ }
963
+
964
+ if (tagData instanceof Object && "editable" in tagData && !tagData.editable) return; // cache the original data, on the DOM node, before any modification ocurs, for possible revert
965
+
966
+ tagElm.__tagifyTagData.__originalData = this.extend({}, tagData);
967
+ tagElm.classList.add('tagify__tag--editable');
968
+ editableElm.originalValue = editableElm.textContent;
969
+ editableElm.setAttribute('contenteditable', true);
970
+ editableElm.addEventListener('focus', _CB.onEditTagFocus.bind(this, tagElm));
971
+ editableElm.addEventListener('blur', delayed_onEditTagBlur);
972
+ editableElm.addEventListener('input', _CB.onEditTagInput.bind(this, editableElm));
973
+ editableElm.addEventListener('keydown', function (e) {
974
+ return _CB.onEditTagkeydown.call(_this6, e, tagElm);
975
+ });
976
+ editableElm.focus();
977
+ this.setRangeAtStartEnd(false, editableElm);
978
+ if (!opts.skipValidation) isValid = this.editTagToggleValidity(tagElm, tagData.value);
979
+ this.trigger("edit:start", {
980
+ tag: tagElm,
981
+ index: tagIdx,
982
+ data: tagData,
983
+ isValid: isValid
984
+ });
985
+ return this;
986
+ },
987
+ editTagToggleValidity: function editTagToggleValidity(tagElm, value) {
988
+ var tagData = tagElm.__tagifyTagData,
989
+ toggleState;
990
+
991
+ if (!tagData) {
992
+ console.warn("tag has no data: ", tagElm, tagData);
993
+ return;
994
+ }
995
+
996
+ toggleState = !!(tagData.__isValid && tagData.__isValid != true); //this.validateTag(tagData);
997
+
998
+ tagElm.classList.toggle('tagify--invalid', toggleState);
999
+ return tagData.__isValid;
1000
+ },
1001
+ onEditTagDone: function onEditTagDone(tagElm, tagData) {
1002
+ tagData = tagData || {};
1003
+ var eventData = {
1004
+ tag: tagElm,
1005
+ index: this.getNodeIndex(tagElm),
1006
+ data: tagData
1007
+ };
1008
+ this.trigger("edit:beforeUpdate", eventData);
1009
+ delete tagData.__originalData;
1010
+
1011
+ if (tagElm) {
1012
+ this.editTagToggleValidity(tagElm);
1013
+ this.replaceTag(tagElm, tagData);
1014
+ }
1015
+
1016
+ this.trigger("edit:updated", eventData);
1017
+ this.dropdown.hide.call(this);
1018
+ },
1019
+
1020
+ /**
1021
+ * Replaces an exisitng tag with a new one and update the relevant state
1022
+ * @param {Object} tagElm [DOM node to replace]
1023
+ * @param {Object} tagData [data to create new tag from]
1024
+ */
1025
+ replaceTag: function replaceTag(tagElm, tagData) {
1026
+ var _this7 = this;
1027
+
1028
+ if (!tagData || !tagData.value) tagData = tagElm.__tagifyTagData; // if tag is invalid, make the according changes in the newly created element
1029
+
1030
+ if (tagData.__isValid && tagData.__isValid != true) this.extend(tagData, this.getInvaildTagParams(tagData, tagData.__isValid));
1031
+ var newTag = this.createTagElem(tagData); // when editing a tag and selecting a dropdown suggested item, the state should be "locked"
1032
+ // so "onEditTagBlur" won't run and change the tag also *after* it was just changed.
1033
+
1034
+ if (this.state.editing.locked) return;
1035
+ this.state.editing = {
1036
+ locked: true
1037
+ };
1038
+ setTimeout(function () {
1039
+ return delete _this7.state.editing.locked;
1040
+ }, 500); // update DOM
1041
+
1042
+ tagElm.parentNode.replaceChild(newTag, tagElm);
1043
+ this.updateValueByDOMTags();
1044
+ },
1045
+
1046
+ /**
1047
+ * update value by traversing all valid tags
1048
+ */
1049
+ updateValueByDOMTags: function updateValueByDOMTags() {
1050
+ var _this8 = this;
1051
+
1052
+ this.value = [];
1053
+ [].forEach.call(this.getTagElms(), function (node) {
1054
+ if (node.classList.contains('tagify--notAllowed')) return;
1055
+
1056
+ _this8.value.push(node.__tagifyTagData);
1057
+ });
1058
+ this.update();
1059
+ },
1060
+
1061
+ /** https://stackoverflow.com/a/59156872/104380
1062
+ * @param {Boolean} start indicating where to place it (start or end of the node)
1063
+ * @param {Object} node DOM node to place the caret at
1064
+ */
1065
+ setRangeAtStartEnd: function setRangeAtStartEnd(start, node) {
1066
+ node = node || this.DOM.input;
1067
+ node = node.lastChild || node;
1068
+ var sel = document.getSelection();
1069
+
1070
+ if (sel.rangeCount) {
1071
+ ['Start', 'End'].forEach(function (pos) {
1072
+ return sel.getRangeAt(0)["set" + pos](node, start ? 0 : node.length);
1073
+ });
1074
+ }
1075
+ },
1076
+
1077
+ /**
1078
+ * input bridge for accessing & setting
1079
+ * @type {Object}
1080
+ */
1081
+ input: {
1082
+ value: '',
1083
+ set: function set() {
1084
+ var s = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : '';
1085
+ var updateDOM = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : true;
1086
+ var hideDropdown = this.settings.dropdown.closeOnSelect;
1087
+ this.input.value = s;
1088
+ if (updateDOM) this.DOM.input.innerHTML = s;
1089
+ if (!s && hideDropdown) setTimeout(this.dropdown.hide.bind(this), 20); // setTimeout duration must be HIGER than the dropdown's item "onClick" method's "focus()" event, because the "hide" method re-binds the main events and it will catch the "blur" event and will cause
1090
+
1091
+ this.input.autocomplete.suggest.call(this);
1092
+ this.input.validate.call(this);
1093
+ },
1094
+
1095
+ /**
1096
+ * Marks the tagify's input as "invalid" if the value did not pass "validateTag()"
1097
+ */
1098
+ validate: function validate() {
1099
+ var isValid = !this.input.value || this.validateTag({
1100
+ value: this.input.value
1101
+ });
1102
+ if (this.settings.mode == 'select') this.DOM.scope.classList.toggle('tagify--invalid', isValid !== true);else this.DOM.input.classList.toggle('tagify__input--invalid', isValid !== true);
1103
+ },
1104
+ // remove any child DOM elements that aren't of type TEXT (like <br>)
1105
+ normalize: function normalize(node) {
1106
+ var clone = node || this.DOM.input,
1107
+ //.cloneNode(true),
1108
+ v = []; // when a text was pasted in FF, the "this.DOM.input" element will have <br> but no newline symbols (\n), and this will
1109
+ // result in tags no being properly created if one wishes to create a separate tag per newline.
1110
+
1111
+ clone.childNodes.forEach(function (n) {
1112
+ return n.nodeType == 3 && v.push(n.nodeValue);
1113
+ });
1114
+ v = v.join("\n");
1115
+
1116
+ try {
1117
+ // "delimiters" might be of a non-regex value, where this will fail ("Tags With Properties" example in demo page):
1118
+ v = v.replace(/(?:\r\n|\r|\n)/g, this.settings.delimiters.source.charAt(0));
1119
+ } catch (err) {}
1120
+
1121
+ v = v.replace(/\s/g, ' ') // replace NBSPs with spaces characters
1122
+ .replace(/^\s+/, ""); // trimLeft
1123
+
1124
+ return v;
1125
+ },
1126
+
1127
+ /**
1128
+ * suggest the rest of the input's value (via CSS "::after" using "content:attr(...)")
1129
+ * @param {String} s [description]
1130
+ */
1131
+ autocomplete: {
1132
+ suggest: function suggest(data) {
1133
+ if (!this.settings.autoComplete.enabled) return;
1134
+ data = data || {};
1135
+ if (typeof data == 'string') data = {
1136
+ value: data
1137
+ };
1138
+ var suggestedText = data.value || '',
1139
+ suggestionStart = suggestedText.substr(0, this.input.value.length).toLowerCase(),
1140
+ suggestionTrimmed = suggestedText.substring(this.input.value.length);
1141
+
1142
+ if (!suggestedText || !this.input.value || suggestionStart != this.input.value.toLowerCase()) {
1143
+ this.DOM.input.removeAttribute("data-suggest");
1144
+ delete this.state.inputSuggestion;
1145
+ } else {
1146
+ this.DOM.input.setAttribute("data-suggest", suggestionTrimmed);
1147
+ this.state.inputSuggestion = data;
1148
+ }
1149
+ },
1150
+
1151
+ /**
1152
+ * sets the suggested text as the input's value & cleanup the suggestion autocomplete.
1153
+ * @param {String} s [text]
1154
+ */
1155
+ set: function set(s) {
1156
+ var dataSuggest = this.DOM.input.getAttribute('data-suggest'),
1157
+ suggestion = s || (dataSuggest ? this.input.value + dataSuggest : null);
1158
+
1159
+ if (suggestion) {
1160
+ if (this.settings.mode == 'mix') {
1161
+ this.replaceTextWithNode(document.createTextNode(this.state.tag.prefix + suggestion));
1162
+ } else {
1163
+ this.input.set.call(this, suggestion);
1164
+ this.setRangeAtStartEnd();
1165
+ }
1166
+
1167
+ this.input.autocomplete.suggest.call(this);
1168
+ this.dropdown.hide.call(this);
1169
+ return true;
1170
+ }
1171
+
1172
+ return false;
1173
+ }
1174
+ }
1175
+ },
1176
+ getNodeIndex: function getNodeIndex(node) {
1177
+ var index = 0;
1178
+ if (node) while (node = node.previousElementSibling) {
1179
+ index++;
1180
+ }
1181
+ return index;
1182
+ },
1183
+ getTagElms: function getTagElms() {
1184
+ return this.DOM.scope.querySelectorAll('.tagify__tag');
1185
+ },
1186
+ getLastTag: function getLastTag() {
1187
+ var lastTag = this.DOM.scope.querySelectorAll('tag:not(.tagify--hide):not([readonly])');
1188
+ return lastTag[lastTag.length - 1];
1189
+ },
1190
+
1191
+ /**
1192
+ * Searches if any tag with a certain value already exis
1193
+ * @param {String/Object} v [text value / tag data object]
1194
+ * @return {Boolean}
1195
+ */
1196
+ isTagDuplicate: function isTagDuplicate(value) {
1197
+ var duplications; // duplications are irrelevant for this scenario
1198
+
1199
+ if (this.settings.mode == 'select') return false;
1200
+ duplications = this.value.reduce(function (acc, item) {
1201
+ return value.trim().toLowerCase() === item.value.toLowerCase() ? acc + 1 : acc;
1202
+ }, 0);
1203
+ return duplications; // this.value.some(item => value.trim().toLowerCase() === item.value.toLowerCase())
1204
+ },
1205
+ getTagIndexByValue: function getTagIndexByValue(value) {
1206
+ var result = [];
1207
+ this.getTagElms().forEach(function (tagElm, i) {
1208
+ if (tagElm.textContent.trim().toLowerCase() == value.toLowerCase()) result.push(i);
1209
+ });
1210
+ return result;
1211
+ },
1212
+ getTagElmByValue: function getTagElmByValue(value) {
1213
+ var tagIdx = this.getTagIndexByValue(value)[0];
1214
+ return this.getTagElms()[tagIdx];
1215
+ },
1216
+
1217
+ /**
1218
+ * Mark a tag element by its value
1219
+ * @param {String|Number} value [text value to search for]
1220
+ * @param {Object} tagElm [a specific "tag" element to compare to the other tag elements siblings]
1221
+ * @return {boolean} [found / not found]
1222
+ */
1223
+ markTagByValue: function markTagByValue(value, tagElm) {
1224
+ tagElm = tagElm || this.getTagElmByValue(value); // check AGAIN if "tagElm" is defined
1225
+
1226
+ if (tagElm) {
1227
+ tagElm.classList.add('tagify--mark');
1228
+ setTimeout(function () {
1229
+ tagElm.classList.remove('tagify--mark');
1230
+ }, 100);
1231
+ return tagElm;
1232
+ }
1233
+
1234
+ return false;
1235
+ },
1236
+
1237
+ /**
1238
+ * make sure the tag, or words in it, is not in the blacklist
1239
+ */
1240
+ isTagBlacklisted: function isTagBlacklisted(v) {
1241
+ v = v.toLowerCase().trim();
1242
+ return this.settings.blacklist.filter(function (x) {
1243
+ return v == x.toLowerCase();
1244
+ }).length;
1245
+ },
1246
+
1247
+ /**
1248
+ * make sure the tag, or words in it, is not in the blacklist
1249
+ */
1250
+ isTagWhitelisted: function isTagWhitelisted(v) {
1251
+ return this.settings.whitelist.some(function (item) {
1252
+ return typeof v == 'string' ? v.trim().toLowerCase() === (item.value || item).toLowerCase() : JSON.stringify(item).toLowerCase() === JSON.stringify(v).toLowerCase();
1253
+ });
1254
+ },
1255
+
1256
+ /**
1257
+ * validate a tag object BEFORE the actual tag will be created & appeneded
1258
+ * @param {String} s
1259
+ * @param {String} uid [unique ID, to not inclue own tag when cheking for duplicates]
1260
+ * @return {Boolean/String} ["true" if validation has passed, String for a fail]
1261
+ */
1262
+ validateTag: function validateTag(tagData) {
1263
+ var value = tagData.value.trim(),
1264
+ _s = this.settings,
1265
+ result = true; // check for empty value
1266
+
1267
+ if (!value) result = this.TEXTS.empty; // check if pattern should be used and if so, use it to test the value
1268
+ else if (_s.pattern && _s.pattern instanceof RegExp && !_s.pattern.test(value)) result = this.TEXTS.pattern; // if duplicates are not allowed and there is a duplicate
1269
+ else if (!_s.duplicates && this.isTagDuplicate(value)) result = this.TEXTS.duplicate;else if (this.isTagBlacklisted(value) || _s.enforceWhitelist && !this.isTagWhitelisted(value)) result = this.TEXTS.notAllowed;
1270
+ return result;
1271
+ },
1272
+ getInvaildTagParams: function getInvaildTagParams(tagData, validation) {
1273
+ return {
1274
+ "aria-invalid": true,
1275
+ "class": (tagData["class"] || '') + ' tagify--notAllowed',
1276
+ "title": validation
1277
+ };
1278
+ },
1279
+ hasMaxTags: function hasMaxTags() {
1280
+ if (this.value.length >= this.settings.maxTags) return this.TEXTS.exceed;
1281
+ return false;
1282
+ },
1283
+
1284
+ /**
1285
+ * pre-proccess the tagsItems, which can be a complex tagsItems like an Array of Objects or a string comprised of multiple words
1286
+ * so each item should be iterated on and a tag created for.
1287
+ * @return {Array} [Array of Objects]
1288
+ */
1289
+ normalizeTags: function normalizeTags(tagsItems) {
1290
+ var _this$settings = this.settings,
1291
+ whitelist = _this$settings.whitelist,
1292
+ delimiters = _this$settings.delimiters,
1293
+ mode = _this$settings.mode,
1294
+ whitelistWithProps = whitelist ? whitelist[0] instanceof Object : false,
1295
+ isArray = tagsItems instanceof Array,
1296
+ isCollection = isArray && tagsItems[0] instanceof Object && "value" in tagsItems[0],
1297
+ temp = [],
1298
+ mapStringToCollection = function mapStringToCollection(s) {
1299
+ return (s + "").split(delimiters).filter(function (n) {
1300
+ return n;
1301
+ }).map(function (v) {
1302
+ return {
1303
+ value: v.trim()
1304
+ };
1305
+ });
1306
+ }; // no need to continue if "tagsItems" is an Array of Objects
1307
+
1308
+
1309
+ if (isCollection) {
1310
+ var _ref2;
1311
+
1312
+ // iterate the collection items and check for values that can be splitted into multiple tags
1313
+ tagsItems = (_ref2 = []).concat.apply(_ref2, _toConsumableArray(tagsItems.map(function (item) {
1314
+ return mapStringToCollection(item.value).map(function (newItem) {
1315
+ return _objectSpread({}, item, {}, newItem);
1316
+ });
1317
+ })));
1318
+ return tagsItems;
1319
+ }
1320
+
1321
+ if (typeof tagsItems == 'number') tagsItems = tagsItems.toString(); // if the value is a "simple" String, ex: "aaa, bbb, ccc"
1322
+
1323
+ if (typeof tagsItems == 'string') {
1324
+ if (!tagsItems.trim()) return []; // go over each tag and add it (if there were multiple ones)
1325
+
1326
+ tagsItems = mapStringToCollection(tagsItems);
1327
+ } else if (isArray) {
1328
+ var _ref3;
1329
+
1330
+ tagsItems = (_ref3 = []).concat.apply(_ref3, _toConsumableArray(tagsItems.map(function (item) {
1331
+ return mapStringToCollection(item);
1332
+ })));
1333
+ } // search if the tag exists in the whitelist as an Object (has props),
1334
+ // to be able to use its properties
1335
+
1336
+
1337
+ if (whitelistWithProps) {
1338
+ tagsItems.forEach(function (item) {
1339
+ // the "value" prop should preferably be unique
1340
+ var matchObj = whitelist.filter(function (WL_item) {
1341
+ return WL_item.value.toLowerCase() == item.value.toLowerCase();
1342
+ });
1343
+
1344
+ if (matchObj[0]) {
1345
+ temp.push(matchObj[0]); // set the Array (with the found Object) as the new value
1346
+ } else if (mode != 'mix') temp.push(item);
1347
+ });
1348
+ tagsItems = temp;
1349
+ }
1350
+
1351
+ return tagsItems;
1352
+ },
1353
+
1354
+ /**
1355
+ * Used to parse the initial value of a textarea (or input) element and gemerate mixed text w/ tags
1356
+ * https://stackoverflow.com/a/57598892/104380
1357
+ * @param {String} s
1358
+ */
1359
+ parseMixTags: function parseMixTags(s) {
1360
+ var _this9 = this;
1361
+
1362
+ var _this$settings2 = this.settings,
1363
+ mixTagsInterpolator = _this$settings2.mixTagsInterpolator,
1364
+ duplicates = _this$settings2.duplicates,
1365
+ transformTag = _this$settings2.transformTag,
1366
+ enforceWhitelist = _this$settings2.enforceWhitelist,
1367
+ tagsDataSet = [];
1368
+ s = s.split(mixTagsInterpolator[0]).map(function (s1, i) {
1369
+ var s2 = s1.split(mixTagsInterpolator[1]),
1370
+ preInterpolated = s2[0],
1371
+ tagData,
1372
+ tagElm;
1373
+
1374
+ try {
1375
+ tagData = JSON.parse(preInterpolated);
1376
+ } catch (err) {
1377
+ tagData = _this9.normalizeTags(preInterpolated)[0]; //{value:preInterpolated}
1378
+ }
1379
+
1380
+ if (s2.length > 1 && (!enforceWhitelist || _this9.isTagWhitelisted(tagData.value)) && !(!duplicates && _this9.isTagDuplicate(tagData.value))) {
1381
+ transformTag.call(_this9, tagData);
1382
+ tagElm = _this9.createTagElem(tagData);
1383
+ tagsDataSet.push(tagData);
1384
+ tagElm.classList.add('tagify--noAnim');
1385
+ s2[0] = tagElm.outerHTML; //+ "&#8288;" // put a zero-space at the end so the caret won't jump back to the start (when the last input's child element is a tag)
1386
+
1387
+ _this9.value.push(tagData);
1388
+ } else if (s1) return i ? mixTagsInterpolator[0] + s1 : s1;
1389
+
1390
+ return s2.join('');
1391
+ }).join('');
1392
+ this.DOM.input.innerHTML = s;
1393
+ this.DOM.input.appendChild(document.createTextNode(''));
1394
+ this.DOM.input.normalize();
1395
+ this.getTagElms().forEach(function (elm, idx) {
1396
+ return elm.__tagifyTagData = tagsDataSet[idx];
1397
+ });
1398
+ this.update();
1399
+ return s;
1400
+ },
1401
+
1402
+ /**
1403
+ * For mixed-mode: replaces a text starting with a prefix with a wrapper element (tag or something)
1404
+ * First there *has* to be a "this.state.tag" which is a string that was just typed and is staring with a prefix
1405
+ */
1406
+ replaceTextWithNode: function replaceTextWithNode(wrapperElm, tagString) {
1407
+ if (!this.state.tag && !tagString) return;
1408
+ tagString = tagString || this.state.tag.prefix + this.state.tag.value;
1409
+ var idx,
1410
+ replacedNode,
1411
+ selection = this.state.selection || window.getSelection(),
1412
+ nodeAtCaret = selection.anchorNode; // ex. replace #ba with the tag "bart" where "|" is where the caret is:
1413
+ // start with: "#ba #ba| #ba"
1414
+ // split the text node at the index of the caret
1415
+
1416
+ nodeAtCaret.splitText(selection.anchorOffset); // "#ba #ba"
1417
+ // get index of last occurence of "#ba"
1418
+
1419
+ idx = nodeAtCaret.nodeValue.lastIndexOf(tagString);
1420
+ replacedNode = nodeAtCaret.splitText(idx); // clean up the tag's string and put tag element instead
1421
+
1422
+ replacedNode.nodeValue = replacedNode.nodeValue.replace(tagString, '');
1423
+ nodeAtCaret.parentNode.insertBefore(wrapperElm, replacedNode);
1424
+ this.DOM.input.normalize();
1425
+ return replacedNode;
1426
+ },
1427
+
1428
+ /**
1429
+ * For selecting a single option (not used for multiple tags)
1430
+ * @param {Object} tagElm Tag DOM node
1431
+ * @param {Object} tagData Tag data
1432
+ */
1433
+ selectTag: function selectTag(tagElm, tagData) {
1434
+ this.input.set.call(this, tagData.value, true); // place the caret at the end of the input, only if a dropdown option was selected (and not by manually typing another value and clicking "TAB")
1435
+
1436
+ if (this.state.actions.selectOption) setTimeout(this.setRangeAtStartEnd.bind(this));
1437
+ if (this.getLastTag()) this.replaceTag(this.getLastTag(), tagData);else this.appendTag(tagElm);
1438
+ this.value[0] = tagData;
1439
+ this.trigger('add', {
1440
+ tag: tagElm,
1441
+ data: tagData
1442
+ });
1443
+ this.update();
1444
+ return [tagElm];
1445
+ },
1446
+
1447
+ /**
1448
+ * add an empty "tag" element in an editable state
1449
+ */
1450
+ addEmptyTag: function addEmptyTag() {
1451
+ var tagData = {
1452
+ value: ""
1453
+ },
1454
+ tagElm = this.createTagElem(tagData); // must be assigned ASAP, before "validateTag" method below
1455
+
1456
+ tagElm.__tagifyTagData = tagData; // add the tag to the component's DOM
1457
+
1458
+ this.appendTag(tagElm);
1459
+ this.editTag(tagElm, {
1460
+ skipValidation: true
1461
+ });
1462
+ },
1463
+
1464
+ /**
1465
+ * add a "tag" element to the "tags" component
1466
+ * @param {String/Array} tagsItems [A string (single or multiple values with a delimiter), or an Array of Objects or just Array of Strings]
1467
+ * @param {Boolean} clearInput [flag if the input's value should be cleared after adding tags]
1468
+ * @param {Boolean} skipInvalid [do not add, mark & remove invalid tags]
1469
+ * @return {Array} Array of DOM elements (tags)
1470
+ */
1471
+ addTags: function addTags(tagsItems, clearInput) {
1472
+ var _this10 = this;
1473
+
1474
+ var skipInvalid = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : this.settings.skipInvalid;
1475
+ var tagElems = [],
1476
+ tagElm,
1477
+ _s = this.settings;
1478
+
1479
+ if (!tagsItems || tagsItems.length == 0) {
1480
+ // is mode is "select" clean all tags
1481
+ if (_s.mode == 'select') this.removeAllTags();
1482
+ return tagElems;
1483
+ } // converts Array/String/Object to an Array of Objects
1484
+
1485
+
1486
+ tagsItems = this.normalizeTags(tagsItems); // if in edit-mode, do not continue but instead replace the tag's text.
1487
+ // the scenario is that "addTags" was called from a dropdown suggested option selected while editing
1488
+
1489
+ if (this.state.editing.scope) {
1490
+ tagsItems[0].__isValid = true; // must be "true" at this point because it must have been coming from the dropdown sugegstions list
1491
+
1492
+ return this.onEditTagDone(this.state.editing.scope, tagsItems[0]);
1493
+ }
1494
+
1495
+ if (_s.mode == 'mix') {
1496
+ _s.transformTag.call(this, tagsItems[0]); // TODO: should check if the tag is valid
1497
+
1498
+
1499
+ tagElm = this.createTagElem(tagsItems[0]); // tries to replace a taged textNode with a tagElm, and if not able,
1500
+ // insert the new tag to the END if "addTags" was called from outside
1501
+
1502
+ if (!this.replaceTextWithNode(tagElm)) {
1503
+ this.DOM.input.appendChild(tagElm);
1504
+ } // fixes a firefox bug where if the last child of the input is a tag and not a text, the input cannot get focus (by Tab key)
1505
+
1506
+
1507
+ this.DOM.input.appendChild(document.createTextNode(''));
1508
+ setTimeout(function () {
1509
+ return tagElm.classList.add('tagify--noAnim');
1510
+ }, 300);
1511
+ tagsItems[0].prefix = tagsItems[0].prefix || this.state.tag ? this.state.tag.prefix : (_s.pattern.source || _s.pattern)[0];
1512
+ this.value.push(tagsItems[0]);
1513
+ this.update();
1514
+ this.state.tag = null;
1515
+ this.trigger('add', this.extend({}, {
1516
+ tag: tagElm
1517
+ }, {
1518
+ data: tagsItems[0]
1519
+ })); // fixes a firefox bug where if the last child of the input is a tag and not a text, the input cannot get focus (by Tab key)
1520
+
1521
+ this.DOM.input.appendChild(document.createTextNode(''));
1522
+ return tagElm;
1523
+ }
1524
+
1525
+ if (_s.mode == 'select') clearInput = false;
1526
+ this.DOM.input.removeAttribute('style');
1527
+ tagsItems.forEach(function (tagData) {
1528
+ var tagElm,
1529
+ tagElmParams = {}; // shallow-clone tagData so later modifications will not apply to the source
1530
+
1531
+ tagData = Object.assign({}, tagData);
1532
+
1533
+ _s.transformTag.call(_this10, tagData); ///////////////// ( validation )//////////////////////
1534
+
1535
+
1536
+ tagData.__isValid = _this10.hasMaxTags() || _this10.validateTag(tagData);
1537
+
1538
+ if (tagData.__isValid !== true) {
1539
+ if (skipInvalid) return;
1540
+
1541
+ _this10.extend(tagElmParams, _this10.getInvaildTagParams(tagData, tagData.__isValid)); // mark, for a brief moment, the tag THIS CURRENT tag is a duplcate of
1542
+
1543
+
1544
+ if (tagData.__isValid == _this10.TEXTS.duplicate) _this10.markTagByValue(tagData.value);
1545
+ } /////////////////////////////////////////////////////
1546
+ // add accessibility attributes
1547
+
1548
+
1549
+ tagElmParams.role = "tag";
1550
+ if (tagData.readonly) tagElmParams["aria-readonly"] = true; // Create tag HTML element
1551
+
1552
+ tagElm = _this10.createTagElem(_this10.extend({}, tagData, tagElmParams));
1553
+ tagElm.__tagifyTagData = tagData;
1554
+ tagElems.push(tagElm); // mode-select overrides
1555
+
1556
+ if (_s.mode == 'select') {
1557
+ return _this10.selectTag(tagElm, tagData);
1558
+ } // add the tag to the component's DOM
1559
+
1560
+
1561
+ _this10.appendTag(tagElm);
1562
+
1563
+ if (tagData.__isValid && tagData.__isValid === true) {
1564
+ // update state
1565
+ _this10.value.push(tagData);
1566
+
1567
+ _this10.update();
1568
+
1569
+ _this10.trigger('add', {
1570
+ tag: tagElm,
1571
+ index: _this10.value.length - 1,
1572
+ data: tagData
1573
+ });
1574
+ } else {
1575
+ _this10.trigger("invalid", {
1576
+ data: tagData,
1577
+ index: _this10.value.length,
1578
+ tag: tagElm,
1579
+ message: tagData.__isValid
1580
+ });
1581
+
1582
+ if (!_s.keepInvalidTags) // remove invalid tags (if "keepInvalidTags" is set to "false")
1583
+ setTimeout(function () {
1584
+ return _this10.removeTag(tagElm, true);
1585
+ }, 1000);
1586
+ }
1587
+
1588
+ _this10.dropdown.position.call(_this10); // reposition the dropdown because the just-added tag might cause a new-line
1589
+
1590
+ });
1591
+
1592
+ if (tagsItems.length && clearInput) {
1593
+ this.input.set.call(this);
1594
+ }
1595
+
1596
+ this.dropdown.refilter.call(this);
1597
+ return tagElems;
1598
+ },
1599
+
1600
+ /**
1601
+ * appened (validated) tag to the component's DOM scope
1602
+ */
1603
+ appendTag: function appendTag(tagElm) {
1604
+ var insertBeforeNode = this.DOM.scope.lastElementChild;
1605
+ if (insertBeforeNode === this.DOM.input) this.DOM.scope.insertBefore(tagElm, insertBeforeNode);else this.DOM.scope.appendChild(tagElm);
1606
+ },
1607
+
1608
+ /**
1609
+ * Removed new lines and irrelevant spaces which might affect layout, and are better gone
1610
+ * @param {string} s [HTML string]
1611
+ */
1612
+ minify: function minify(s) {
1613
+ return s ? s.replace(/\>[\r\n ]+\</g, "><").replace(/(<.*?>)|\s+/g, function (m, $1) {
1614
+ return $1 ? $1 : ' ';
1615
+ }) // https://stackoverflow.com/a/44841484/104380
1616
+ : "";
1617
+ },
1618
+
1619
+ /**
1620
+ * creates a DOM tag element and injects it into the component (this.DOM.scope)
1621
+ * @param {Object} tagData [text value & properties for the created tag]
1622
+ * @return {Object} [DOM element]
1623
+ */
1624
+ createTagElem: function createTagElem(tagData) {
1625
+ var tagElm,
1626
+ v = escapeHTML(tagData.value),
1627
+ template = this.settings.templates.tag.call(this, v, tagData);
1628
+ if (this.settings.readonly) tagData.readonly = true; // tagData.__uid = tagData.__uid || getUID()
1629
+
1630
+ template = this.minify(template);
1631
+ tagElm = this.parseHTML(template);
1632
+ tagElm.__tagifyTagData = tagData;
1633
+ return tagElm;
1634
+ },
1635
+ reCheckInvalidTags: function reCheckInvalidTags() {
1636
+ var _this11 = this;
1637
+
1638
+ // find all invalid tags and re-check them
1639
+ var tagElms = this.DOM.scope.querySelectorAll('.tagify__tag.tagify--notAllowed');
1640
+ [].forEach.call(tagElms, function (node) {
1641
+ var tagData = node.__tagifyTagData,
1642
+ wasNodeDuplicate = node.getAttribute('title') == _this11.TEXTS.duplicate,
1643
+ isNodeValid = _this11.validateTag(tagData) === true; // if this tag node was marked as a dulpicate, unmark it (it might have been marked as "notAllowed" for other reasons)
1644
+
1645
+
1646
+ if (wasNodeDuplicate && isNodeValid) {
1647
+ tagData.__isValid = true;
1648
+
1649
+ _this11.replaceTag(node, tagData);
1650
+ }
1651
+ });
1652
+ },
1653
+
1654
+ /**
1655
+ * Removes a tag
1656
+ * @param {Object|String} tagElm [DOM element or a String value. if undefined or null, remove last added tag]
1657
+ * @param {Boolean} silent [A flag, which when turned on, does not removes any value and does not update the original input value but simply removes the tag from tagify]
1658
+ * @param {Number} tranDuration [Transition duration in MS]
1659
+ */
1660
+ removeTag: function removeTag(tagElm, silent, tranDuration) {
1661
+ var tagData;
1662
+ tagElm = tagElm || this.getLastTag();
1663
+ tranDuration = tranDuration || this.CSSVars.tagHideTransition;
1664
+ if (typeof tagElm == 'string') tagElm = this.getTagElmByValue(tagElm);
1665
+ if (!(tagElm instanceof HTMLElement)) return;
1666
+ var that = this,
1667
+ tagIdx = this.getNodeIndex(tagElm); // this.getTagIndexByValue(tagElm.textContent)
1668
+
1669
+ if (this.settings.mode == 'select') {
1670
+ tranDuration = 0;
1671
+ this.input.set.call(this);
1672
+ }
1673
+
1674
+ if (tagElm.classList.contains('tagify--notAllowed')) silent = true;
1675
+
1676
+ function removeNode() {
1677
+ if (!tagElm.parentNode) return;
1678
+ tagData = tagElm.__tagifyTagData;
1679
+ tagElm.parentNode.removeChild(tagElm);
1680
+
1681
+ if (!silent) {
1682
+ if (tagIdx > -1) that.value.splice(tagIdx, 1); // that.removeValueById(tagData.__uid)
1683
+
1684
+ that.update(); // update the original input with the current value
1685
+
1686
+ that.trigger('remove', {
1687
+ tag: tagElm,
1688
+ index: tagIdx,
1689
+ data: tagData
1690
+ });
1691
+ that.dropdown.refilter.call(that);
1692
+ that.dropdown.position.call(that); // check if any of the current tags which might have been marked as "duplicate" should be now un-marked
1693
+
1694
+ if (that.settings.keepInvalidTags) that.reCheckInvalidTags();
1695
+ } else if (that.settings.keepInvalidTags) that.trigger('remove', {
1696
+ tag: tagElm,
1697
+ index: tagIdx
1698
+ });
1699
+ }
1700
+
1701
+ function animation() {
1702
+ tagElm.style.width = parseFloat(window.getComputedStyle(tagElm).width) + 'px';
1703
+ document.body.clientTop; // force repaint for the width to take affect before the "hide" class below
1704
+
1705
+ tagElm.classList.add('tagify--hide'); // manual timeout (hack, since transitionend cannot be used because of hover)
1706
+
1707
+ setTimeout(removeNode, tranDuration);
1708
+ }
1709
+
1710
+ if (tranDuration && tranDuration > 10) animation();else removeNode();
1711
+ },
1712
+ removeAllTags: function removeAllTags() {
1713
+ this.value = [];
1714
+ if (this.settings.mode == 'mix') this.DOM.input.innerHTML = '';else Array.prototype.slice.call(this.getTagElms()).forEach(function (elm) {
1715
+ return elm.parentNode.removeChild(elm);
1716
+ });
1717
+ this.dropdown.position.call(this);
1718
+ if (this.settings.mode == 'select') this.input.set.call(this);
1719
+ this.update();
1720
+ },
1721
+
1722
+ /**
1723
+ * Removes an item in "this.value" by its UID
1724
+ * @param {String} uid
1725
+ */
1726
+ removeValueById: function removeValueById(uid) {// this.value = this.value.filter(item => item.__tagifyTagData.__uid != uid)
1727
+ },
1728
+ preUpdate: function preUpdate() {
1729
+ this.DOM.scope.classList.toggle('tagify--hasMaxTags', this.value.length >= this.settings.maxTags);
1730
+ this.DOM.scope.classList.toggle('tagify--noTags', !this.value.length);
1731
+ },
1732
+
1733
+ /**
1734
+ * update the origianl (hidden) input field's value
1735
+ * see - https://stackoverflow.com/q/50957841/104380
1736
+ */
1737
+ update: function update() {
1738
+ this.preUpdate();
1739
+ var value = removeCollectionProp(this.value, "__isValid");
1740
+ this.DOM.originalInput.value = this.settings.mode == 'mix' ? this.getMixedTagsAsString(value) : this.value.length ? JSON.stringify(value) : "";
1741
+ this.DOM.originalInput.dispatchEvent(new CustomEvent('change'));
1742
+ },
1743
+ getMixedTagsAsString: function getMixedTagsAsString(value) {
1744
+ var result = "",
1745
+ i = 0,
1746
+ currentTags = value,
1747
+ _interpolator = this.settings.mixTagsInterpolator;
1748
+
1749
+ function iterateChildren(rootNode) {
1750
+ rootNode.childNodes.forEach(function (node) {
1751
+ if (node.nodeType == 1) {
1752
+ if (node.classList.contains("tagify__tag")) {
1753
+ result += _interpolator[0] + JSON.stringify(currentTags[i++]) + _interpolator[1];
1754
+ return;
1755
+ } // Chrome adds <div><br></div> for empty new lines, and FF only adds <br>
1756
+
1757
+
1758
+ if (isFirefox && node.tagName == 'BR') result += "\r\n";else if (node.tagName == 'DIV') {
1759
+ result += "\r\n";
1760
+ iterateChildren(node);
1761
+ }
1762
+ } else result += node.textContent;
1763
+ });
1764
+ }
1765
+
1766
+ iterateChildren(this.DOM.input);
1767
+ return result;
1768
+ },
1769
+
1770
+ /**
1771
+ * Meassures an element's height, which might yet have been added DOM
1772
+ * https://stackoverflow.com/q/5944038/104380
1773
+ * @param {DOM} node
1774
+ */
1775
+ getNodeHeight: function getNodeHeight(node) {
1776
+ var height,
1777
+ clone = node.cloneNode(true);
1778
+ clone.style.cssText = "position:fixed; top:-9999px; opacity:0";
1779
+ document.body.appendChild(clone);
1780
+ height = clone.clientHeight;
1781
+ clone.parentNode.removeChild(clone);
1782
+ return height;
1783
+ },
1784
+
1785
+ /**
1786
+ * Dropdown controller
1787
+ * @type {Object}
1788
+ */
1789
+ dropdown: {
1790
+ init: function init() {
1791
+ this.DOM.dropdown = this.dropdown.build.call(this);
1792
+ this.DOM.dropdown.content = this.DOM.dropdown.querySelector('.tagify__dropdown__wrapper');
1793
+ },
1794
+ build: function build() {
1795
+ var _this$settings$dropdo = this.settings.dropdown,
1796
+ position = _this$settings$dropdo.position,
1797
+ classname = _this$settings$dropdo.classname,
1798
+ _className = "".concat(position == 'manual' ? "" : "tagify__dropdown tagify__dropdown--".concat(position), " ").concat(classname).trim(),
1799
+ elm = this.parseHTML("<div class=\"".concat(_className, "\" role=\"listbox\" aria-labelledby=\"dropdown\">\n <div class=\"tagify__dropdown__wrapper\"></div>\n </div>"));
1800
+
1801
+ return elm;
1802
+ },
1803
+ show: function show(value) {
1804
+ var _this12 = this;
1805
+
1806
+ var HTMLContent,
1807
+ _s = this.settings,
1808
+ firstListItem,
1809
+ firstListItemValue,
1810
+ ddHeight,
1811
+ selection = window.getSelection(),
1812
+ allowNewTags = _s.mode == 'mix' && !_s.enforceWhitelist,
1813
+ noWhitelist = !_s.whitelist || !_s.whitelist.length,
1814
+ isManual = _s.dropdown.position == 'manual';
1815
+ if (noWhitelist && !allowNewTags || _s.dropdown.enable === false) return;
1816
+ clearTimeout(this.dropdownHide__bindEventsTimeout); // if no value was supplied, show all the "whitelist" items in the dropdown
1817
+ // @type [Array] listItems
1818
+ // TODO: add a Setting to control items' sort order for "listItems"
1819
+
1820
+ this.suggestedListItems = this.dropdown.filterListItems.call(this, value);
1821
+
1822
+ if (!this.suggestedListItems.length) {
1823
+ // in mix-mode, if the value isn't included in the whilelist & "enforceWhitelist" setting is "false",
1824
+ // then add a custom suggestion item to the dropdown
1825
+ if (allowNewTags && !this.state.editing.scope) {
1826
+ this.suggestedListItems = [{
1827
+ value: value
1828
+ }];
1829
+ } // hide suggestions list if no suggestions were matched & cleanup
1830
+ else {
1831
+ this.input.autocomplete.suggest.call(this);
1832
+ this.dropdown.hide.call(this);
1833
+ return;
1834
+ }
1835
+ }
1836
+
1837
+ firstListItem = this.suggestedListItems[0];
1838
+ firstListItemValue = firstListItem.value || firstListItem;
1839
+
1840
+ if (_s.autoComplete) {
1841
+ // only fill the sugegstion if the value of the first list item STARTS with the input value (regardless of "fuzzysearch" setting)
1842
+ if (firstListItemValue.indexOf(value) == 0) this.input.autocomplete.suggest.call(this, firstListItem);
1843
+ }
1844
+
1845
+ HTMLContent = this.dropdown.createListHTML.call(this, this.suggestedListItems);
1846
+ this.DOM.dropdown.content.innerHTML = this.minify(HTMLContent); // if "enforceWhitelist" is "true", highlight the first suggested item
1847
+
1848
+ if (_s.enforceWhitelist && !isManual || _s.dropdown.highlightFirst) this.dropdown.highlightOption.call(this, this.DOM.dropdown.content.children[0]);
1849
+ this.DOM.scope.setAttribute("aria-expanded", true);
1850
+ this.trigger("dropdown:show", this.DOM.dropdown); // set the dropdown visible state to be the same as the searched value.
1851
+ // MUST be set *before* position() is called
1852
+
1853
+ this.state.dropdown.visible = value || true;
1854
+ this.state.selection = {
1855
+ anchorOffset: selection.anchorOffset,
1856
+ anchorNode: selection.anchorNode
1857
+ };
1858
+ this.dropdown.position.call(this); // if the dropdown has yet to be appended to the document,
1859
+ // append the dropdown to the body element & handle events
1860
+
1861
+ if (!document.body.contains(this.DOM.dropdown)) {
1862
+ if (!isManual) {
1863
+ this.events.binding.call(this, false); // unbind the main events
1864
+ // let the element render in the DOM first to accurately measure it
1865
+ // this.DOM.dropdown.style.cssText = "left:-9999px; top:-9999px;";
1866
+
1867
+ ddHeight = this.getNodeHeight(this.DOM.dropdown);
1868
+ this.DOM.dropdown.classList.add('tagify__dropdown--initial');
1869
+ this.dropdown.position.call(this, ddHeight);
1870
+ document.body.appendChild(this.DOM.dropdown);
1871
+ setTimeout(function () {
1872
+ return _this12.DOM.dropdown.classList.remove('tagify__dropdown--initial');
1873
+ });
1874
+ } // timeout is needed for when pressing arrow down to show the dropdown,
1875
+ // so the key event won't get registered in the dropdown events listeners
1876
+
1877
+
1878
+ setTimeout(this.dropdown.events.binding.bind(this));
1879
+ }
1880
+ },
1881
+ hide: function hide(force) {
1882
+ var _this$DOM = this.DOM,
1883
+ scope = _this$DOM.scope,
1884
+ dropdown = _this$DOM.dropdown,
1885
+ isManual = this.settings.dropdown.position == 'manual' && !force;
1886
+ if (!dropdown || !document.body.contains(dropdown) || isManual) return;
1887
+ window.removeEventListener('resize', this.dropdown.position);
1888
+ this.dropdown.events.binding.call(this, false); // unbind all events
1889
+ // if the dropdown is open, and the input (scope) is clicked,
1890
+ // the dropdown should be now "closed", and the next click (on the scope)
1891
+ // should re-open it, and without a timeout, clicking to close will re-open immediately
1892
+
1893
+ clearTimeout(this.dropdownHide__bindEventsTimeout);
1894
+ this.dropdownHide__bindEventsTimeout = setTimeout(this.events.binding.bind(this), 250); // re-bind main events
1895
+
1896
+ scope.setAttribute("aria-expanded", false);
1897
+ dropdown.parentNode.removeChild(dropdown);
1898
+ this.state.dropdown.visible = false;
1899
+ this.state.ddItemData = this.state.ddItemElm = this.state.selection = null;
1900
+ this.trigger("dropdown:hide", dropdown);
1901
+ },
1902
+
1903
+ /**
1904
+ * fill data into the suggestions list (mainly used to update the list when removing tags, so they will be re-added to the list. not efficient)
1905
+ */
1906
+ refilter: function refilter() {
1907
+ this.suggestedListItems = this.dropdown.filterListItems.call(this, '');
1908
+ var listHTML = this.dropdown.createListHTML.call(this, this.suggestedListItems);
1909
+ this.DOM.dropdown.content.innerHTML = this.minify(listHTML);
1910
+ this.trigger("dropdown:updated", this.DOM.dropdown);
1911
+ },
1912
+ position: function position(ddHeight) {
1913
+ var isBelowViewport,
1914
+ rect,
1915
+ top,
1916
+ bottom,
1917
+ left,
1918
+ width,
1919
+ ddElm = this.DOM.dropdown;
1920
+ if (!this.state.dropdown.visible) return;
1921
+
1922
+ if (this.settings.dropdown.position == 'text') {
1923
+ rect = this.getCaretGlobalPosition();
1924
+ bottom = rect.bottom;
1925
+ top = rect.top;
1926
+ left = rect.left;
1927
+ width = 'auto';
1928
+ } else {
1929
+ rect = this.DOM.scope.getBoundingClientRect();
1930
+ top = rect.top;
1931
+ bottom = rect.bottom - 1;
1932
+ left = rect.left;
1933
+ width = rect.width + "px";
1934
+ }
1935
+
1936
+ top = Math.floor(top);
1937
+ bottom = Math.ceil(bottom);
1938
+ isBelowViewport = document.documentElement.clientHeight - bottom < (ddHeight || ddElm.clientHeight); // flip vertically if there is no space for the dropdown below the input
1939
+
1940
+ ddElm.style.cssText = "left:" + (left + window.pageXOffset) + "px; width:" + width + ";" + (isBelowViewport ? "bottom:" + (document.documentElement.clientHeight - top - window.pageYOffset - 2) + "px;" : "top: " + (bottom + window.pageYOffset) + "px");
1941
+ ddElm.setAttribute('placement', isBelowViewport ? "top" : "bottom");
1942
+ },
1943
+ events: {
1944
+ /**
1945
+ * Events should only be binded when the dropdown is rendered and removed when isn't
1946
+ * @param {Boolean} bindUnbind [optional. true when wanting to unbind all the events]
1947
+ */
1948
+ binding: function binding() {
1949
+ var bindUnbind = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : true;
1950
+
1951
+ // references to the ".bind()" methods must be saved so they could be unbinded later
1952
+ var _CB = this.dropdown.events.callbacks,
1953
+ _CBR = this.listeners.dropdown = this.listeners.dropdown || {
1954
+ position: this.dropdown.position.bind(this),
1955
+ onKeyDown: _CB.onKeyDown.bind(this),
1956
+ onMouseOver: _CB.onMouseOver.bind(this),
1957
+ onMouseLeave: _CB.onMouseLeave.bind(this),
1958
+ onClick: _CB.onClick.bind(this),
1959
+ onScroll: _CB.onScroll.bind(this)
1960
+ },
1961
+ action = bindUnbind ? 'addEventListener' : 'removeEventListener';
1962
+
1963
+ if (this.settings.dropdown.position != 'manual') {
1964
+ window[action]('resize', _CBR.position);
1965
+ window[action]('keydown', _CBR.onKeyDown);
1966
+ } // window[action]('mousedown', _CBR.onClick);
1967
+
1968
+
1969
+ this.DOM.dropdown[action]('mouseover', _CBR.onMouseOver);
1970
+ this.DOM.dropdown[action]('mouseleave', _CBR.onMouseLeave);
1971
+ this.DOM.dropdown[action]('mousedown', _CBR.onClick);
1972
+ this.DOM.dropdown.content[action]('scroll', _CBR.onScroll); // add back the main "click" event because it is needed for removing/clicking already-existing tags, even if dropdown is shown
1973
+
1974
+ this.DOM[this.listeners.main.click[0]][action]('click', this.listeners.main.click[1]);
1975
+ },
1976
+ callbacks: {
1977
+ onKeyDown: function onKeyDown(e) {
1978
+ // get the "active" element, and if there was none (yet) active, use first child
1979
+ var activeListElm = this.DOM.dropdown.querySelector("[class$='--active']"),
1980
+ selectedElm = activeListElm;
1981
+
1982
+ switch (e.key) {
1983
+ case 'ArrowDown':
1984
+ case 'ArrowUp':
1985
+ case 'Down': // >IE11
1986
+
1987
+ case 'Up':
1988
+ {
1989
+ // >IE11
1990
+ e.preventDefault();
1991
+ var dropdownItems;
1992
+ if (selectedElm) selectedElm = selectedElm[(e.key == 'ArrowUp' || e.key == 'Up' ? "previous" : "next") + "ElementSibling"]; // if no element was found, loop
1993
+
1994
+ if (!selectedElm) {
1995
+ dropdownItems = this.DOM.dropdown.content.children;
1996
+ selectedElm = dropdownItems[e.key == 'ArrowUp' || e.key == 'Up' ? dropdownItems.length - 1 : 0];
1997
+ }
1998
+
1999
+ this.dropdown.highlightOption.call(this, selectedElm, true);
2000
+ break;
2001
+ }
2002
+
2003
+ case 'Escape':
2004
+ case 'Esc':
2005
+ // IE11
2006
+ this.dropdown.hide.call(this);
2007
+ break;
2008
+
2009
+ case 'ArrowRight':
2010
+ if (this.state.actions.ArrowLeft) return;
2011
+
2012
+ case 'Tab':
2013
+ {
2014
+ // in mix-mode, treat arrowRight like Enter key, so a tag will be created
2015
+ if (this.settings.mode != 'mix' && !this.settings.autoComplete.rightKey) {
2016
+ try {
2017
+ var value = selectedElm ? selectedElm.textContent : this.suggestedListItems[0].value;
2018
+ this.input.autocomplete.set.call(this, value);
2019
+ } catch (err) {}
2020
+
2021
+ return false;
2022
+ }
2023
+ }
2024
+
2025
+ case 'Enter':
2026
+ {
2027
+ e.preventDefault();
2028
+ this.dropdown.selectOption.call(this, activeListElm);
2029
+ break;
2030
+ }
2031
+
2032
+ case 'Backspace':
2033
+ {
2034
+ if (this.settings.mode == 'mix' || this.state.editing.scope) return;
2035
+
2036
+ var _value = this.input.value.trim();
2037
+
2038
+ if (_value == "" || _value.charCodeAt(0) == 8203) {
2039
+ if (this.settings.backspace === true) this.removeTag();else if (this.settings.backspace == 'edit') setTimeout(this.editTag.bind(this), 0);
2040
+ }
2041
+ }
2042
+ }
2043
+ },
2044
+ onMouseOver: function onMouseOver(e) {
2045
+ var ddItem = e.target.closest('.tagify__dropdown__item'); // event delegation check
2046
+
2047
+ ddItem && this.dropdown.highlightOption.call(this, ddItem);
2048
+ },
2049
+ onMouseLeave: function onMouseLeave(e) {
2050
+ // de-highlight any previously highlighted option
2051
+ this.dropdown.highlightOption.call(this);
2052
+ },
2053
+ onClick: function onClick(e) {
2054
+ if (e.button != 0 || e.target == this.DOM.dropdown) return; // allow only mouse left-clicks
2055
+
2056
+ var listItemElm = e.target.closest(".tagify__dropdown__item");
2057
+ this.dropdown.selectOption.call(this, listItemElm);
2058
+ },
2059
+ onScroll: function onScroll(e) {
2060
+ var elm = e.target,
2061
+ pos = elm.scrollTop / (elm.scrollHeight - elm.parentNode.clientHeight) * 100;
2062
+ this.trigger("dropdown:scroll", {
2063
+ percentage: Math.round(pos)
2064
+ });
2065
+ }
2066
+ }
2067
+ },
2068
+
2069
+ /**
2070
+ * mark the currently active suggestion option
2071
+ * @param {Object} elm option DOM node
2072
+ * @param {Boolean} adjustScroll when navigation with keyboard arrows (up/down), aut-scroll to always show the highlighted element
2073
+ */
2074
+ highlightOption: function highlightOption(elm, adjustScroll) {
2075
+ var className = "tagify__dropdown__item--active",
2076
+ itemData; // focus casues a bug in Firefox with the placeholder been shown on the input element
2077
+ // if( this.settings.dropdown.position != 'manual' )
2078
+ // elm.focus();
2079
+
2080
+ if (this.state.ddItemElm) {
2081
+ this.state.ddItemElm.classList.remove(className);
2082
+ this.state.ddItemElm.removeAttribute("aria-selected");
2083
+ }
2084
+
2085
+ if (!elm) {
2086
+ this.state.ddItemData = null;
2087
+ this.state.ddItemElm = null;
2088
+ this.input.autocomplete.suggest.call(this);
2089
+ return;
2090
+ }
2091
+
2092
+ itemData = this.suggestedListItems[this.getNodeIndex(elm)];
2093
+ this.state.ddItemData = itemData;
2094
+ this.state.ddItemElm = elm; // this.DOM.dropdown.querySelectorAll("[class$='--active']").forEach(activeElm => activeElm.classList.remove(className));
2095
+
2096
+ elm.classList.add(className);
2097
+ elm.setAttribute("aria-selected", true);
2098
+ if (adjustScroll) elm.parentNode.scrollTop = elm.clientHeight + elm.offsetTop - elm.parentNode.clientHeight; // Try to autocomplete the typed value with the currently highlighted dropdown item
2099
+
2100
+ if (this.settings.autoComplete) {
2101
+ this.input.autocomplete.suggest.call(this, itemData);
2102
+ if (this.settings.dropdown.position != 'manual') this.dropdown.position.call(this); // suggestions might alter the height of the tagify wrapper because of unkown suggested term length that could drop to the next line
2103
+ }
2104
+ },
2105
+
2106
+ /**
2107
+ * Create a tag from the currently active suggestion option
2108
+ * @param {Object} elm DOM node to select
2109
+ */
2110
+ selectOption: function selectOption(elm) {
2111
+ var _this13 = this;
2112
+
2113
+ if (!elm) return; // temporary set the "actions" state to indicate to the main "blur" event it shouldn't run
2114
+
2115
+ this.state.actions.selectOption = true;
2116
+ setTimeout(function () {
2117
+ return _this13.state.actions.selectOption = false;
2118
+ }, 50);
2119
+ var hideDropdown = this.settings.dropdown.closeOnSelect,
2120
+ value = this.suggestedListItems[this.getNodeIndex(elm)] || this.input.value;
2121
+ this.trigger("dropdown:select", value);
2122
+ this.addTags([value], true); // Tagify instances should re-focus to the input element once an option was selected, to allow continuous typing
2123
+
2124
+ if (!this.state.editing) setTimeout(function () {
2125
+ _this13.DOM.input.focus();
2126
+
2127
+ _this13.toggleFocusClass(true);
2128
+ });
2129
+
2130
+ if (hideDropdown) {
2131
+ this.dropdown.hide.call(this); // setTimeout(() => this.events.callbacks.onFocusBlur.call(this, {type:"blur"}), 60)
2132
+ }
2133
+ },
2134
+
2135
+ /**
2136
+ * returns an HTML string of the suggestions' list items
2137
+ * @param {string} value string to filter the whitelist by
2138
+ * @return {Array} list of filtered whitelist items according to the settings provided and current value
2139
+ */
2140
+ filterListItems: function filterListItems(value) {
2141
+ var _this14 = this;
2142
+
2143
+ var _s = this.settings,
2144
+ list = [],
2145
+ whitelist = _s.whitelist,
2146
+ suggestionsCount = _s.dropdown.maxItems || Infinity,
2147
+ searchKeys = _s.dropdown.searchKeys.concat(["searchBy", "value"]),
2148
+ whitelistItem,
2149
+ valueIsInWhitelist,
2150
+ whitelistItemValueIndex,
2151
+ searchBy,
2152
+ isDuplicate,
2153
+ i = 0;
2154
+
2155
+ if (!value) {
2156
+ return (_s.duplicates ? whitelist : whitelist.filter(function (item) {
2157
+ return !_this14.isTagDuplicate(isObject(item) ? item.value : item);
2158
+ }) // don't include tags which have already been added.
2159
+ ).slice(0, suggestionsCount); // respect "maxItems" dropdown setting
2160
+ }
2161
+
2162
+ for (; i < whitelist.length; i++) {
2163
+ whitelistItem = whitelist[i] instanceof Object ? whitelist[i] : {
2164
+ value: whitelist[i]
2165
+ }; //normalize value as an Object
2166
+
2167
+ searchBy = searchKeys.reduce(function (values, k) {
2168
+ return values + " " + (whitelistItem[k] || "");
2169
+ }, "").toLowerCase();
2170
+ whitelistItemValueIndex = searchBy.indexOf(value.toLowerCase());
2171
+ valueIsInWhitelist = _s.dropdown.fuzzySearch ? whitelistItemValueIndex >= 0 : whitelistItemValueIndex == 0;
2172
+ isDuplicate = !_s.duplicates && this.isTagDuplicate(isObject(whitelistItem) ? whitelistItem.value : whitelistItem); // match for the value within each "whitelist" item
2173
+
2174
+ if (valueIsInWhitelist && !isDuplicate && suggestionsCount--) list.push(whitelistItem);
2175
+ if (suggestionsCount == 0) break;
2176
+ }
2177
+
2178
+ return list;
2179
+ },
2180
+
2181
+ /**
2182
+ * Creates the dropdown items' HTML
2183
+ * @param {Array} list [Array of Objects]
2184
+ * @return {String}
2185
+ */
2186
+ createListHTML: function createListHTML(optionsArr) {
2187
+ var template = this.settings.templates.dropdownItem.bind(this);
2188
+ return this.minify(optionsArr.map(template).join(""));
2189
+ }
2190
+ }
2191
+ };
2192
+ return Tagify;
2193
+ }));
inc/yturbo-css.css CHANGED
@@ -1,3 +1,5 @@
 
 
1
  tt {
2
  padding: 1px 5px 1px;
3
  margin: 0 1px;
@@ -134,6 +136,11 @@ td table {
134
  .hide {
135
  display:none;
136
  }
 
 
 
 
 
137
  .types, .shortcodes {
138
  margin-bottom: 6px;
139
  display: table;
@@ -232,10 +239,74 @@ td table {
232
  vertical-align: super;
233
  }
234
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
235
  /* блок восстановления css старого wordpress begin */
236
  .foptions .postbox {
237
  border: 1px solid #D8D8D8;
238
  }
 
239
  .foptions input[type=color],
240
  .foptions input[type=date],
241
  .foptions input[type=datetime-local],
@@ -260,6 +331,7 @@ td table {
260
  border: 1px solid #b4b9be;
261
  border-radius: 3px;
262
  }
 
263
  .foptions input[type=checkbox]:focus,
264
  .foptions input[type=color]:focus,
265
  .foptions input[type=date]:focus,
@@ -296,6 +368,7 @@ td table {
296
  .foptions input[type=week] {
297
  padding: 0 0 0 8px;
298
  }
 
299
  .foptions input:disabled,
300
  .foptions select:disabled,
301
  .foptions textarea:disabled {
1
+ @charset "utf-8";
2
+
3
  tt {
4
  padding: 1px 5px 1px;
5
  margin: 0 1px;
136
  .hide {
137
  display:none;
138
  }
139
+ .foptions #current-version {
140
+ font-size: 12px;
141
+ vertical-align: super;
142
+ font-weight: 600;
143
+ }
144
  .types, .shortcodes {
145
  margin-bottom: 6px;
146
  display: table;
239
  vertical-align: super;
240
  }
241
 
242
+ /* блок выбора тегов begin */
243
+ .tagify__dropdown__wrapper {
244
+ max-width: 484px;
245
+ padding: 2px 1px 3px 1px;
246
+ border: 1px solid #BDE0FF;
247
+ margin-top: 2px;
248
+ margin-bottom: 2px;
249
+ box-shadow: none;
250
+ background: aliceblue;
251
+ border-radius: 3px;
252
+ }
253
+ .ytexcludetagslist-input,
254
+ .ytexcludetagslist-input2 {
255
+ max-width: 484px;
256
+ padding: 2px 1px 3px 1px;
257
+ margin-bottom: 8px;
258
+ --tag-bg: #9d989838;
259
+ --tag-hover: #40a0ed63;
260
+ --tag-text-color: #23282d;
261
+ --tag-remove-bg: #c21c1c9e;
262
+ --tag-remove-btn-bg--hover: #940707c7;
263
+ }
264
+ .ytexcludetagslist-input .tagify__tag__removeBtn:hover+div>span {
265
+ opacity: 1;
266
+ color: #881010;
267
+ }
268
+ .ytexcludetagslist-input2 .tagify__tag__removeBtn:hover+div>span {
269
+ opacity: 1;
270
+ color: #881010;
271
+ }
272
+ .tagify__tag {
273
+ margin: 5px 1px 1px 5px;
274
+ }
275
+ .tagify__input {
276
+ min-width: 10px;
277
+ padding-left: 1px;
278
+ padding-right: 1px;
279
+ margin-top: 6px;
280
+ font-size: 12px;
281
+ }
282
+ .tags-look .tagify__dropdown__item {
283
+ display: inline-block;
284
+ border-radius: 3px;
285
+ padding: .3em .5em;
286
+ border: 1px solid #CCC;
287
+ background: #F3F3F3;
288
+ margin: 3px 0px 2px 5px;
289
+ font-size: .85em;
290
+ color: black;
291
+ transition: 0s;
292
+ }
293
+ .tags-look .tagify__dropdown__item--active {
294
+ color: black;
295
+ }
296
+ .tags-look .tagify__dropdown__item:hover {
297
+ background: lightyellow;
298
+ border-color: gold;
299
+ }
300
+ .tagify__tag>div>* {
301
+ line-height: 1.2;
302
+ }
303
+ /* блок выбора тегов end */
304
+
305
  /* блок восстановления css старого wordpress begin */
306
  .foptions .postbox {
307
  border: 1px solid #D8D8D8;
308
  }
309
+ .foptions .tagify,
310
  .foptions input[type=color],
311
  .foptions input[type=date],
312
  .foptions input[type=datetime-local],
331
  border: 1px solid #b4b9be;
332
  border-radius: 3px;
333
  }
334
+ .foptions .tagify--focus,
335
  .foptions input[type=checkbox]:focus,
336
  .foptions input[type=color]:focus,
337
  .foptions input[type=date]:focus,
368
  .foptions input[type=week] {
369
  padding: 0 0 0 8px;
370
  }
371
+ .foptions .tagify:disabled,
372
  .foptions input:disabled,
373
  .foptions select:disabled,
374
  .foptions textarea:disabled {
inc/yturbo-script.js CHANGED
@@ -1,33 +1,3 @@
1
- jQuery(document).ready(function($) {
2
- $('.tcode').textillate({
3
- loop: true,
4
- minDisplayTime: 5000,
5
- initialDelay: 800,
6
- autoStart: true,
7
- inEffects: [],
8
- outEffects: [],
9
- in: {
10
- effect: 'rollIn',
11
- delayScale: 1.5,
12
- delay: 50,
13
- sync: false,
14
- shuffle: true,
15
- reverse: false,
16
- callback: function() {}
17
- },
18
- out: {
19
- effect: 'fadeOut',
20
- delayScale: 1.5,
21
- delay: 50,
22
- sync: false,
23
- shuffle: true,
24
- reverse: false,
25
- callback: function() {}
26
- },
27
- callback: function() {}
28
- });
29
- })
30
-
31
  jQuery(document).ready(function($) {
32
  var thumb = jQuery('#ytthumbnail');
33
  var select = this.value;
@@ -300,6 +270,15 @@ jQuery(document).ready(function($) {
300
  }
301
  });
302
 
 
 
 
 
 
 
 
 
 
303
  var block1 = jQuery('#ytad1');
304
 
305
  if (jQuery('#ytad1').is(':checked')) {
@@ -1065,7 +1044,7 @@ jQuery(document).ready(function($){
1065
  });
1066
 
1067
  function setExpTime() {
1068
- var limit = 30 * 24 * 60 * 60 * 1000; // месяц
1069
  var time = localStorage.getItem('yt-time');
1070
  if (time === null) {
1071
  localStorage.setItem('yt-time', +new Date());
@@ -1078,7 +1057,7 @@ jQuery(document).ready(function($){
1078
  }
1079
 
1080
  function checkExpTime() {
1081
- var limit = 30 * 24 * 60 * 60 * 1000; // месяц
1082
  var time = localStorage.getItem('yt-time');
1083
  if (time === null) {
1084
 
@@ -1089,4 +1068,125 @@ jQuery(document).ready(function($){
1089
  }
1090
  }
1091
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1092
  });
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
  jQuery(document).ready(function($) {
2
  var thumb = jQuery('#ytthumbnail');
3
  var select = this.value;
270
  }
271
  });
272
 
273
+ var rurls = jQuery('#ytexcludeurls');
274
+ rurls.change(function() {
275
+ if (jQuery('#ytexcludeurls').is(':checked')) {
276
+ jQuery('.ytexcludeurlslisttr').fadeIn();
277
+ } else {
278
+ jQuery('.ytexcludeurlslisttr').hide();
279
+ }
280
+ });
281
+
282
  var block1 = jQuery('#ytad1');
283
 
284
  if (jQuery('#ytad1').is(':checked')) {
1044
  });
1045
 
1046
  function setExpTime() {
1047
+ var limit = 90 * 24 * 60 * 60 * 1000; // 3 месяца
1048
  var time = localStorage.getItem('yt-time');
1049
  if (time === null) {
1050
  localStorage.setItem('yt-time', +new Date());
1057
  }
1058
 
1059
  function checkExpTime() {
1060
+ var limit = 90 * 24 * 60 * 60 * 1000; // 3 месяца
1061
  var time = localStorage.getItem('yt-time');
1062
  if (time === null) {
1063
 
1068
  }
1069
  }
1070
 
1071
+ });
1072
+
1073
+ jQuery(document).ready(function($) {
1074
+
1075
+ var str = $('#tags-list').val();
1076
+ var whitelist = str.split(',');
1077
+
1078
+ var input = document.querySelector('input[name="ytexcludetagslist-input"]'),
1079
+ tagify = new Tagify(input, {
1080
+ whitelist: whitelist,
1081
+ enforceWhitelist: true,
1082
+ dropdown: {
1083
+ maxItems: 20,
1084
+ classname: 'tags-look',
1085
+ enabled: 0,
1086
+ closeOnSelect: false,
1087
+ }
1088
+ })
1089
+
1090
+ tagify
1091
+ .on('add', onAddTag)
1092
+ .on('remove', onRemoveTag)
1093
+ .on('invalid', onInvalidTag)
1094
+
1095
+ function onAddTag(e) {
1096
+
1097
+ $('tag').data('title', $('tag').attr('title')).removeAttr('title');
1098
+ var str = tagify.DOM.originalInput.value;
1099
+ var temp = str.replace(/{"value":"/g, '');
1100
+ temp = temp.replace(/"}/g, '');
1101
+ temp = temp.replace(/"}/g, '');
1102
+ temp = temp.replace(/\[/g, '');
1103
+ temp = temp.replace(/\]/g, '');
1104
+
1105
+ $('#ytexcludetagslist').val(temp);
1106
+ }
1107
+
1108
+ function onRemoveTag(e) {
1109
+ if ($('tags').hasClass('tagify--focus')) {
1110
+ tagify.dropdown.hide.call(tagify);
1111
+ $('tags').removeClass('tagify--focus');
1112
+ }
1113
+
1114
+ var str = tagify.DOM.originalInput.value;
1115
+ var temp = str.replace(/{"value":"/g, '');
1116
+ temp = temp.replace(/"}/g, '');
1117
+ temp = temp.replace(/"}/g, '');
1118
+ temp = temp.replace(/\[/g, '');
1119
+ temp = temp.replace(/\]/g, '');
1120
+
1121
+ $('#ytexcludetagslist').val(temp);
1122
+
1123
+ }
1124
+
1125
+ function onInvalidTag(e) {
1126
+ tagify.dropdown.show.call(tagify);
1127
+ }
1128
+
1129
+ $('tag').data('title', $('tag').attr('title')).removeAttr('title');
1130
+
1131
+ });
1132
+
1133
+
1134
+ jQuery(document).ready(function($) {
1135
+
1136
+ var str = $('#tags-list2').val();
1137
+ var whitelist = str.split(',');
1138
+
1139
+ var input = document.querySelector('input[name="ytexcludetagslist-input2"]'),
1140
+ tagify2 = new Tagify(input, {
1141
+ whitelist: whitelist,
1142
+ enforceWhitelist: true,
1143
+ dropdown: {
1144
+ maxItems: 20,
1145
+ classname: 'tags-look',
1146
+ enabled: 0,
1147
+ closeOnSelect: false,
1148
+ }
1149
+ })
1150
+
1151
+ tagify2
1152
+ .on('add', onAddTag2)
1153
+ .on('remove', onRemoveTag2)
1154
+ .on('invalid', onInvalidTag2)
1155
+
1156
+ function onAddTag2(e) {
1157
+
1158
+ $('tag').data('title', $('tag').attr('title')).removeAttr('title');
1159
+ var str = tagify2.DOM.originalInput.value;
1160
+ var temp = str.replace(/{"value":"/g, '');
1161
+ temp = temp.replace(/"}/g, '');
1162
+ temp = temp.replace(/"}/g, '');
1163
+ temp = temp.replace(/\[/g, '');
1164
+ temp = temp.replace(/\]/g, '');
1165
+
1166
+ $('#ytexcludetagslist2').val(temp);
1167
+ }
1168
+
1169
+ function onRemoveTag2(e) {
1170
+ if ($('tags').hasClass('tagify--focus')) {
1171
+ tagify2.dropdown.hide.call(tagify2);
1172
+ $('tags').removeClass('tagify--focus');
1173
+ }
1174
+
1175
+ var str = tagify2.DOM.originalInput.value;
1176
+ var temp = str.replace(/{"value":"/g, '');
1177
+ temp = temp.replace(/"}/g, '');
1178
+ temp = temp.replace(/"}/g, '');
1179
+ temp = temp.replace(/\[/g, '');
1180
+ temp = temp.replace(/\]/g, '');
1181
+
1182
+ $('#ytexcludetagslist2').val(temp);
1183
+
1184
+ }
1185
+
1186
+ function onInvalidTag2(e) {
1187
+ tagify2.dropdown.show.call(tagify2);
1188
+ }
1189
+
1190
+ $('tag').data('title', $('tag').attr('title')).removeAttr('title');
1191
+
1192
  });
readme.txt CHANGED
@@ -5,7 +5,7 @@ Tags: yandex, turbo, yandex turbo, rss, feed, турбо, яндекс турб
5
  Requires at least: 4.4
6
  Tested up to: 5.4
7
  Requires PHP: 5.3
8
- Stable tag: 1.26
9
 
10
  Создание RSS-ленты для сервиса Яндекс.Турбо.
11
 
@@ -19,6 +19,7 @@ Stable tag: 1.26
19
 
20
  Если вам понравился этот плагин, то, <strong>пожалуйста</strong>, поставьте ему 5 звезд.
21
 
 
22
 
23
  == Frequently Asked Questions ==
24
 
@@ -39,6 +40,10 @@ Stable tag: 1.26
39
 
40
  Четвертое. Не стесняйтесь писать Платонам и требовать от них четкого ответа, что именно не так с вашими турбо-страницами. Яндекс запустил проверку турбо-страниц, но работает она сейчас достаточно криво. Например, Яндекс ругается на отсутствие даты и дает для примера старую версию турбо-страницы и забанненую версию этой турбо-страницы. Самое удивительное - и там и там даты нет. Как можно ругаться на несоответствие дат, если и там и там их нет? Поэтому еще раз повторю, доставайте Платонов - пусть связываются с командой, работающей над турбо-страницами и узнают точную причину бана. В противном случае, велика вероятность, что Платоны на глазок определят проблему и в этом случае устранить бан турбо-страниц будет сложно.
41
 
 
 
 
 
42
  = Лента не проходит валидацию, что делать? =
43
 
44
  RSS-лента для Яндекс.Турбо никогда не сможет пройти обычную валидацию, так как технические требования Яндекс.Турбо несовместимы со стандартами обычного RSS.
@@ -339,7 +344,7 @@ function ct_get_steps() {
339
  <p>второе изображение</p>
340
  <p>контент записи</p>`
341
 
342
- Учтите, что результат вывода вашего шаблона будет потом обработан фильтрами плагина.
343
 
344
  = Какие шорткоды можно использовать в шаблонах? =
345
 
@@ -401,6 +406,23 @@ add_filter( 'yturbo_custom_title', 'my_custom_title_for_turbo' );`
401
 
402
  В этом случае будут выполнены все требования Яндекса к заголовкам записей.
403
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
404
  == Screenshots ==
405
 
406
  1. Пример добавленных RSS-лент в Яндекс.Вебмастере.
@@ -409,7 +431,16 @@ add_filter( 'yturbo_custom_title', 'my_custom_title_for_turbo' );`
409
 
410
  == Changelog ==
411
 
412
- = 1.26 =
 
 
 
 
 
 
 
 
 
413
 
414
  * изменен интерфейс удаления шорткодов из контента турбо-страниц.
415
  * удалена опция вывода заголовков из SEO-плагинов (Яндекс стал требовать соответствия заголовков).
5
  Requires at least: 4.4
6
  Tested up to: 5.4
7
  Requires PHP: 5.3
8
+ Stable tag: 1.27
9
 
10
  Создание RSS-ленты для сервиса Яндекс.Турбо.
11
 
19
 
20
  Если вам понравился этот плагин, то, <strong>пожалуйста</strong>, поставьте ему 5 звезд.
21
 
22
+ Для плагина есть премиум-дополнение [WPCase: Turbo Ads](https://wpcase.ru/wpcase-turbo-ads/) для неограниченной вставки рекламы на турбо-страницах.
23
 
24
  == Frequently Asked Questions ==
25
 
40
 
41
  Четвертое. Не стесняйтесь писать Платонам и требовать от них четкого ответа, что именно не так с вашими турбо-страницами. Яндекс запустил проверку турбо-страниц, но работает она сейчас достаточно криво. Например, Яндекс ругается на отсутствие даты и дает для примера старую версию турбо-страницы и забанненую версию этой турбо-страницы. Самое удивительное - и там и там даты нет. Как можно ругаться на несоответствие дат, если и там и там их нет? Поэтому еще раз повторю, доставайте Платонов - пусть связываются с командой, работающей над турбо-страницами и узнают точную причину бана. В противном случае, велика вероятность, что Платоны на глазок определят проблему и в этом случае устранить бан турбо-страниц будет сложно.
42
 
43
+ = Лента не меняется, что делать? =
44
+
45
+ Один из самых частых вопросов. Вы изменили настройки плагина, потом смотрите вашу RSS-ленту в браузере и не видите никаких изменений. Так происходит из-за того, что все браузеры кэшируют открытые RSS-ленты. Чтобы увидеть изменения надо произвести "жесткую" перезагрузку страницы. В браузерах это делается при нажатии клавиш <strong>Ctrl + F5</strong>.
46
+
47
  = Лента не проходит валидацию, что делать? =
48
 
49
  RSS-лента для Яндекс.Турбо никогда не сможет пройти обычную валидацию, так как технические требования Яндекс.Турбо несовместимы со стандартами обычного RSS.
344
  <p>второе изображение</p>
345
  <p>контент записи</p>`
346
 
347
+ Учтите, что результат вывода вашего шаблона будет потом обработан фильтрами плагина. Это может вызвать определенные проблемы, так как фильтры плагина удалят всю лишнюю разметку, которая вам может быть нужна. Например, если вы пытаетесь создать турбо-галерею, а плагин каждую вашу картинку оборачивает в тег `<figure>`, что портит разметку турбо-галереи. В таком случае вам нужно воспользоваться фильтром `yturbo_before_ads`, который срабатывает почти в самом конце обработки контента записи прямо перед вставкой рекламы. Однако, ваши переменные не должны быть обернуты символами <strong>%%</strong> (их содержимое будет удалено при обработке шаблона). Воспользуйтесь любыми другими символами, чтобы отфильтровать потом вашу переменную.
348
 
349
  = Какие шорткоды можно использовать в шаблонах? =
350
 
406
 
407
  В этом случае будут выполнены все требования Яндекса к заголовкам записей.
408
 
409
+ = Как переопределить список тегов для удаления? =
410
+
411
+ По умолчанию в плагине можно удалить теги только из предустановленного списка тегов. В этом списке нет важных тегов вроде `<p>` и `<ul>`, так как их неосторожное удаление может привести к фатальным последствиям. Однако, в плагине есть фильтр, которым можно расширить список тегов для удаления и включить в него нужные вам теги. Делается это так:
412
+
413
+ `function my_custom_tags_list( $tags ) {
414
+
415
+ //скобки и пробелы недопустимы
416
+ //не забываем запятую в начале
417
+ $tags .= ',p,table,ul';
418
+
419
+ return $tags;
420
+ }
421
+ add_filter( 'yturbo_tags_list', 'my_custom_tags_list' );`
422
+
423
+ В список можно включать только парные теги (имеющие тег закрытия). Самозакрывающиеся теги фильтром будут проигнорированы.
424
+
425
+
426
  == Screenshots ==
427
 
428
  1. Пример добавленных RSS-лент в Яндекс.Вебмастере.
431
 
432
  == Changelog ==
433
 
434
+ = 1.27 (06.04.2020) =
435
+
436
+ * добавлена опция формирования RSS-ленты из удаленных записей.
437
+ * изменен интерфейс фильтров удаления тегов.
438
+ * исправлена ошибка с исчезновением записей больше 64k символов.
439
+ * немного изменен цикл выборки записей (стал быстрее).
440
+ * добавлен фильтр "yturbo_before_ads" - выполняется перед вставкой рекламы.
441
+ * добавлена реклама плагина-дополнения [WPCase: Turbo Ads](https://wpcase.ru/wpcase-turbo-ads/).
442
+
443
+ = 1.26 (20.03.2020) =
444
 
445
  * изменен интерфейс удаления шорткодов из контента турбо-страниц.
446
  * удалена опция вывода заголовков из SEO-плагинов (Яндекс стал требовать соответствия заголовков).
rss-for-yandex-turbo.php CHANGED
@@ -3,17 +3,31 @@
3
  Plugin Name: RSS for Yandex Turbo
4
  Plugin URI: https://wordpress.org/plugins/rss-for-yandex-turbo/
5
  Description: Создание RSS-ленты для сервиса Яндекс.Турбо.
6
- Version: 1.26
7
  Author: Flector
8
  Author URI: https://profiles.wordpress.org/flector#content-plugins
9
  Text Domain: rss-for-yandex-turbo
10
  */
11
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
12
  //проверка версии плагина (запуск функции установки новых опций) begin
13
  function yturbo_check_version() {
14
  $yturbo_options = get_option('yturbo_options');
15
  if (!isset($yturbo_options['version'])){$yturbo_options['version']='1.00';update_option('yturbo_options',$yturbo_options);}
16
- if ( $yturbo_options['version'] != '1.26' ) {
17
  yturbo_set_new_options();
18
  }
19
  }
@@ -128,7 +142,28 @@ function yturbo_set_new_options() {
128
  $yturbo_options['ytdescription'] = esc_html(yturbo_remove_emoji(strip_tags($yturbo_options['ytdescription'])));
129
  if (!isset($yturbo_options['required'])) {$yturbo_options['required']='1.00';}
130
 
131
- $yturbo_options['version'] = '1.26';
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
132
  update_option('yturbo_options', $yturbo_options);
133
  }
134
  //функция установки новых опций при обновлении плагина у пользователей end
@@ -136,7 +171,7 @@ function yturbo_set_new_options() {
136
  //функция установки значений по умолчанию при активации плагина begin
137
  function yturbo_init() {
138
  $yturbo_options = array();
139
- $yturbo_options['version'] = '1.26';
140
  $yturbo_options['ytrssname'] = 'turbo';
141
  $yturbo_options['yttitle'] = esc_html(yturbo_remove_emoji(strip_tags(get_bloginfo_rss('title'))));
142
  $yturbo_options['ytlink'] = get_bloginfo_rss('url');
@@ -151,10 +186,10 @@ function yturbo_init() {
151
  $yturbo_options['ytauthor'] = '';
152
  $yturbo_options['ytthumbnail'] = 'enabled';
153
  $yturbo_options['ytselectthumb'] = 'large';
154
- $yturbo_options['ytexcludetags'] = 'disabled';
155
- $yturbo_options['ytexcludetagslist'] = '<div>';
156
- $yturbo_options['ytexcludetags2'] = 'disabled';
157
- $yturbo_options['ytexcludetagslist2'] = '<ins>,<style>,<object>';
158
  $yturbo_options['ytexcludecontent'] = 'disabled';
159
  $yturbo_options['ytexcludecontentlist'] = esc_textarea('<!--more-->\n<p></p>\n<p>&nbsp;</p>');
160
 
@@ -264,6 +299,10 @@ function yturbo_init() {
264
  $yturbo_options['ytturbocolumn'] = 'enabled';
265
  $yturbo_options['ytrelateddate'] = '12';
266
 
 
 
 
 
267
  $yturbo_options['required']='1.00';
268
 
269
  add_option('yturbo_options', $yturbo_options);
@@ -295,6 +334,7 @@ register_deactivation_hook( __FILE__, 'yturbo_on_deactivation' );
295
  function yturbo_on_uninstall() {
296
  if ( ! current_user_can('activate_plugins') ) return;
297
  delete_option('yturbo_options');
 
298
  }
299
  register_uninstall_hook( __FILE__, 'yturbo_on_uninstall' );
300
  //функция при удалении плагина end
@@ -316,18 +356,13 @@ add_filter( 'plugin_action_links_' . plugin_basename( __FILE__ ),'yturbo_actions
316
  //функция загрузки скриптов и стилей плагина только в админке и только на странице настроек плагина begin
317
  function yturbo_files_admin( $hook_suffix ) {
318
  $purl = plugins_url('', __FILE__);
 
319
  if ( $hook_suffix == 'settings_page_rss-for-yandex-turbo' ) {
320
- if(!wp_script_is('jquery')) {wp_enqueue_script('jquery');}
321
- wp_register_script('yturbo-lettering', $purl . '/inc/jquery.lettering.js');
322
- wp_enqueue_script('yturbo-lettering');
323
- wp_register_script('yturbo-textillate', $purl . '/inc/jquery.textillate.js');
324
- wp_enqueue_script('yturbo-textillate');
325
- wp_register_style('yturbo-animate', $purl . '/inc/animate.min.css');
326
- wp_enqueue_style('yturbo-animate');
327
- wp_register_script('yturbo-script', $purl . '/inc/yturbo-script.js', array(), '1.26');
328
- wp_enqueue_script('yturbo-script');
329
- wp_register_style('yturbo-css', $purl . '/inc/yturbo-css.css', array(), '1.26');
330
- wp_enqueue_style('yturbo-css');
331
  }
332
  }
333
  add_action( 'admin_enqueue_scripts', 'yturbo_files_admin' );
@@ -385,19 +420,18 @@ if ( ! wp_verify_nonce( $_POST['yturbo_nonce'], plugin_basename(__FILE__) ) || !
385
  $yturbo_options['ytselectthumb'] = sanitize_text_field($_POST['ytselectthumb']);
386
 
387
  if(isset($_POST['ytexcludetags'])){$yturbo_options['ytexcludetags'] = sanitize_text_field($_POST['ytexcludetags']);}else{$yturbo_options['ytexcludetags'] = 'disabled';}
388
- $ytexcludetagslist = preg_replace('/\s+/', '', $_POST['ytexcludetagslist']);
389
- $ytexcludetagslist = str_replace(array('[', ']', '"', '\'', '/'), '', $ytexcludetagslist);
390
- $yturbo_options['ytexcludetagslist'] = esc_textarea($ytexcludetagslist);
391
 
392
  if(isset($_POST['ytexcludetags2'])){$yturbo_options['ytexcludetags2'] = sanitize_text_field($_POST['ytexcludetags2']);}else{$yturbo_options['ytexcludetags2'] = 'disabled';}
393
- $ytexcludetagslist2 = preg_replace('/\s+/', '', $_POST['ytexcludetagslist2']);
394
- $ytexcludetagslist2 = str_replace(array('[', ']', '"', '\'', '/'), '', $ytexcludetagslist2);
395
- $yturbo_options['ytexcludetagslist2'] = esc_textarea($ytexcludetagslist2);
396
 
397
  if(isset($_POST['ytexcludecontent'])){$yturbo_options['ytexcludecontent'] = sanitize_text_field($_POST['ytexcludecontent']);}else{$yturbo_options['ytexcludecontent'] = 'disabled';}
398
- $yturbo_options['ytexcludecontentlist'] = esc_textarea($_POST['ytexcludecontentlist']);
 
399
 
400
- if(isset($_POST['ytad1'])){$yturbo_options['ytad1'] = sanitize_text_field($_POST['ytad1']);}else{$yturbo_options['ytad1'] = 'disabled';}
401
  $yturbo_options['ytad1set'] = sanitize_text_field($_POST['ytad1set']);
402
  $yturbo_options['ytad1rsa'] = sanitize_text_field($_POST['ytad1rsa']);
403
  $yturbo_options['ytadfox1'] = esc_html($_POST['ytadfox1']);
@@ -574,6 +608,11 @@ if ( ! wp_verify_nonce( $_POST['yturbo_nonce'], plugin_basename(__FILE__) ) || !
574
  $yturbo_options['ytrelateddate'] = sanitize_text_field($_POST['ytrelateddate']);
575
  }
576
 
 
 
 
 
 
577
  update_option('yturbo_options', $yturbo_options);
578
 
579
  yturbo_clear_transients();
@@ -592,7 +631,7 @@ if ( ! wp_verify_nonce( $_POST['yturbo_nonce'], plugin_basename(__FILE__) ) || !
592
  <?php endif; ?>
593
 
594
  <div class="wrap foptions">
595
- <h2><?php _e('Настройки плагина &#8220;Яндекс.Турбо&#8220;', 'rss-for-yandex-turbo'); ?><span id="restore-hide-blocks" class="dashicons dashicons-admin-generic hide" title="<?php _e('Восстановить скрытые блоки', 'rss-for-yandex-turbo'); ?>"></span></h2>
596
 
597
  <div class="metabox-holder" id="poststuff">
598
  <div class="meta-box-sortables">
@@ -605,7 +644,7 @@ if (closedonat == 'yes') {
605
  document.getElementById('restore-hide-blocks').className = 'dashicons dashicons-admin-generic';
606
  }
607
  </script>
608
- <h3 style="border-bottom: 1px solid #E1E1E1;background: #f7f7f7;"><span class="tcode"><?php _e('Вам нравится этот плагин ?', 'rss-for-yandex-turbo'); ?></span>
609
  <span id="close-donat" class="dashicons dashicons-no-alt" title="<?php _e('Скрыть блок', 'rss-for-yandex-turbo'); ?>"></span></h3>
610
  <div class="inside" style="display: block;margin-right: 12px;">
611
  <img src="<?php echo $purl . '/img/icon_coffee.png'; ?>" title="<?php _e('Купить мне чашку кофе :)', 'rss-for-yandex-turbo'); ?>" style=" margin: 5px; float:left;" />
@@ -685,7 +724,7 @@ if (closedonat == 'yes') {
685
  <br /><small><?php _e('Язык статей RSS-ленты в стандарте <a target="_blank" href="https://ru.wikipedia.org/wiki/%D0%9A%D0%BE%D0%B4%D1%8B_%D1%8F%D0%B7%D1%8B%D0%BA%D0%BE%D0%B2">ISO 639-1</a> (Россия - <strong>ru</strong>, Украина - <strong>uk</strong> и т.д.).', 'rss-for-yandex-turbo'); ?> </small>
686
  </td>
687
  </tr>
688
- <tr class="trbordertop">
689
  <th><?php _e('Количество записей:', 'rss-for-yandex-turbo'); ?></th>
690
  <td>
691
  <input style="max-width: 74px;" name="ytnumber" type="number" min="1" max="999999" step="1" value="<?php echo $yturbo_options['ytnumber']; ?>" />
@@ -714,10 +753,50 @@ if (closedonat == 'yes') {
714
  </small>
715
  </td>
716
  </tr>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
717
  <tr class="trbordertop">
718
- <th class="tdcheckbox"><?php _e('Отключение Турбо:', 'rss-for-yandex-turbo'); ?></th>
719
  <td>
720
- <label for="ytremoveturbo"><input type="checkbox" value="enabled" name="ytremoveturbo" id="ytremoveturbo" <?php if ($yturbo_options['ytremoveturbo'] == 'enabled') echo 'checked="checked"'; ?> /><?php _e('Отключить турбо-страницы', 'rss-for-yandex-turbo'); ?></label>
721
  <br /><small><?php _e('Эта опция добавит в RSS-ленту атрибут <tt>turbo="false"</tt> к тегу <tt>&lt;item></tt> для всех записей.', 'rss-for-yandex-turbo'); ?> <br />
722
  <?php _e('Это единственный способ заставить Яндекс отключить турбо-страницы для вашего сайта.', 'rss-for-yandex-turbo'); ?><br />
723
  <?php _e('Простое удаление плагина не поможет - необходимо, чтобы бот Яндекса "съел" ленту с <tt>turbo="false"</tt>.', 'rss-for-yandex-turbo'); ?><br />
@@ -1478,9 +1557,10 @@ if (closedonat == 'yes') {
1478
  <div class="xyztabs__content<?php if($yturbo_options['yttab']=='Реклама'){echo ' active';} ?>"><!-- begin tab -->
1479
 
1480
  <?php if ( yturbo_check_ads() == true ) echo '<div style="display:none;">'; ?>
1481
- <p><?php _e('Реклама, установленная в Яндекс.Вебмастере, распределяется равномерно по тексту страницы (примерно каждые 2-3 экрана).', 'rss-for-yandex-turbo'); ?><br />
1482
- <?php _e('Если у вас большие по размеру контента статьи, то имеет смысл использовать равномерное распределение рекламы от Яндекса.', 'rss-for-yandex-turbo'); ?><br />
1483
- <?php _e('В противном же случае, рекламные блоки лучше установить через плагин (будет выведено минимум 3 рекламных блока).', 'rss-for-yandex-turbo'); ?><br />
 
1484
  <?php _e('При проблемах с настройкой рекламной сети ADFOX ознакомьтесь со справочными материалами: <a target="_blank" href="https://sites.help.adfox.ru/page/225">статья</a>, <a target="_blank" href="https://webmaster.yandex.ru/blog/videourok-kak-razmeschat-reklamu-na-turbo-stranitsakh-cherez-adfox">видеоурок</a>.', 'rss-for-yandex-turbo'); ?><br />
1485
  </p>
1486
 
@@ -1794,7 +1874,7 @@ if (closedonat == 'yes') {
1794
 
1795
  <p><?php _e('В шаблоне можно использовать шорткоды (убедитесь, что их вывод не содержит скрипты или css-код).', 'rss-for-yandex-turbo'); ?><br />
1796
  <?php _e('В плагин встроено несколько собственных шорткодов, полный их список вы можете посмотреть <a target="_blank" href="https://ru.wordpress.org/plugins/rss-for-yandex-turbo/#%D0%BA%D0%B0%D0%BA%D0%B8%D0%B5%20%D1%88%D0%BE%D1%80%D1%82%D0%BA%D0%BE%D0%B4%D1%8B%20%D0%BC%D0%BE%D0%B6%D0%BD%D0%BE%20%D0%B8%D1%81%D0%BF%D0%BE%D0%BB%D1%8C%D0%B7%D0%BE%D0%B2%D0%B0%D1%82%D1%8C%20%D0%B2%20%D1%88%D0%B0%D0%B1%D0%BB%D0%BE%D0%BD%D0%B0%D1%85%3F">здесь</a>.', 'rss-for-yandex-turbo'); ?></p>
1797
- <p> <?php _e('<strong>Внимание!</strong> Произвольные поля плагина <strong>Advanced Custom Fields</strong> необходимо обрабатывать <a target="_blank" href="https://ru.wordpress.org/plugins/rss-for-yandex-turbo/#%D1%88%D0%B0%D0%B1%D0%BB%D0%BE%D0%BD%D1%8B%20%D0%B8%20%D0%BF%D0%BB%D0%B0%D0%B3%D0%B8%D0%BD%20advanced%20custom%20fields">фильтром.</a>', 'rss-for-yandex-turbo'); ?><br /><br />
1798
  </p>
1799
 
1800
  <table class="form-table">
@@ -1830,7 +1910,7 @@ if (closedonat == 'yes') {
1830
  'toolbar1' => 'undo,redo,formatselect,bold,italic,underline,strikethrough,superscript,subscript,hr,blockquote,link,unlink,bullist,numlist,table,yablocks,',
1831
  'toolbar2' => '',
1832
  'toolbar3' => '',
1833
- 'content_css' => $purl . '/inc/editor.css?ver=1.26'
1834
  ),
1835
  'quicktags' => array(
1836
  'id' => $editor_id,
@@ -1885,27 +1965,37 @@ if (closedonat == 'yes') {
1885
  $ytshortcodes = explode(",", $yturbo_options['ytexcludeshortcodeslist']);
1886
  $ytshortcodes = array_diff($ytshortcodes, array(''));
1887
 
1888
- foreach ( $result as $shortcode ) { ?>
 
 
1889
  <label class="shortcodes" for="<?php echo $shortcode; ?>"><input type="checkbox" value="<?php echo $shortcode; ?>" name="shortcodes[]" id="<?php echo $shortcode; ?>" <?php if (in_array($shortcode, $ytshortcodes)) echo 'checked="checked"'; ?> />[<?php echo $shortcode; ?>]</label>
1890
  <?php } ?>
1891
  <small><?php _e('В списке находятся все зарегистрированные на сайте шорткоды, кроме системных.', 'rss-for-yandex-turbo'); ?><br />
1892
  </small>
 
 
 
 
1893
  </td>
1894
  </tr>
1895
  <tr class="ytexcludetagstr trbordertop">
1896
  <th class="tdcheckbox"><?php _e('Фильтр тегов (без контента):', 'rss-for-yandex-turbo'); ?></th>
1897
  <td>
1898
  <label for="ytexcludetags"><input type="checkbox" value="enabled" name="ytexcludetags" id="ytexcludetags" <?php if ($yturbo_options['ytexcludetags'] == 'enabled') echo 'checked="checked"'; ?> /><?php _e('Удалить указанные html-теги', 'rss-for-yandex-turbo'); ?></label>
1899
- <br /><small><?php _e('Из контента записей будут удалены все указанные html-теги (<strong>сам контент этих тегов останется</strong>).', 'rss-for-yandex-turbo'); ?></small>
 
 
1900
  </td>
1901
  </tr>
1902
  <tr class="ytexcludetagslisttr" <?php if ($yturbo_options['ytexcludetags'] == 'disabled') echo 'style="display:none;"'; ?>>
1903
- <th><?php _e('Теги для удаления:', 'rss-for-yandex-turbo'); ?></th>
1904
- <td>
1905
- <textarea rows="4" cols="70" name="ytexcludetagslist" id="ytexcludetagslist"><?php echo stripslashes($yturbo_options['ytexcludetagslist']); ?></textarea>
1906
- <br /><small><?php _e('Список удаляемых html-тегов через запятую (пример: <tt>&lt;div>,&lt;span></tt>).', 'rss-for-yandex-turbo'); ?><br />
1907
- <?php _e('Указывать классы, идентификаторы и прочее нельзя, только сами теги.', 'rss-for-yandex-turbo'); ?><br />
1908
- <?php _e('Самозакрывающиеся теги вроде <tt>&lt;img src="..." /></tt> и <tt>&lt;br /></tt> удалить нельзя.', 'rss-for-yandex-turbo'); ?><br />
 
 
1909
  </small>
1910
  </td>
1911
  </tr>
@@ -1913,16 +2003,18 @@ if (closedonat == 'yes') {
1913
  <th class="tdcheckbox"><?php _e('Фильтр тегов (с контентом):', 'rss-for-yandex-turbo'); ?></th>
1914
  <td>
1915
  <label for="ytexcludetags2"><input type="checkbox" value="enabled" name="ytexcludetags2" id="ytexcludetags2" <?php if ($yturbo_options['ytexcludetags2'] == 'enabled') echo 'checked="checked"'; ?> /><?php _e('Удалить указанные html-теги', 'rss-for-yandex-turbo'); ?></label>
1916
- <br /><small><?php _e('Из контента записей будут удалены все указанные html-теги (<strong>включая сам контент этих тегов</strong>).', 'rss-for-yandex-turbo'); ?></small>
1917
  </td>
1918
  </tr>
1919
  <tr class="ytexcludetagslist2tr" <?php if ($yturbo_options['ytexcludetags2'] == 'disabled') echo 'style="display:none;"'; ?>>
1920
- <th><?php _e('Теги для удаления:', 'rss-for-yandex-turbo'); ?></th>
1921
- <td>
1922
- <textarea rows="4" cols="70" name="ytexcludetagslist2" id="ytexcludetagslist2"><?php echo stripslashes($yturbo_options['ytexcludetagslist2']); ?></textarea>
1923
- <br /><small><?php _e('Список удаляемых html-тегов через запятую (пример: <tt>&lt;style>,&lt;script></tt>).', 'rss-for-yandex-turbo'); ?><br />
1924
- <?php _e('Указывать классы, идентификаторы и прочее нельзя, только сами теги.', 'rss-for-yandex-turbo'); ?> <br />
1925
- <?php _e('Самозакрывающиеся теги вроде <tt>&lt;img src="..." /></tt> и <tt>&lt;br /></tt> удалить нельзя.', 'rss-for-yandex-turbo'); ?><br />
 
 
1926
  </small>
1927
  </td>
1928
  </tr>
@@ -1961,7 +2053,7 @@ if (closeabout == 'yes') {
1961
  document.getElementById('restore-hide-blocks').className = 'dashicons dashicons-admin-generic';
1962
  }
1963
  </script>
1964
- <h3 style="border-bottom: 1px solid #E1E1E1;background: #f7f7f7;"><span class="tcode"><?php _e('О плагине', 'rss-for-yandex-turbo'); ?></span>
1965
  <span id="close-about" class="dashicons dashicons-no-alt" title="<?php _e('Скрыть блок', 'rss-for-yandex-turbo'); ?>"></span></h3>
1966
  <div class="inside" style="padding-bottom:15px;display: block;">
1967
 
@@ -1977,7 +2069,7 @@ if (closeabout == 'yes') {
1977
  <li><a target="_blank" href="https://ru.wordpress.org/plugins/today-yesterday-dates/">Today-Yesterday Dates</a> - <?php _e('относительные даты для записей за сегодня и вчера.', 'rss-for-yandex-turbo'); ?> </li>
1978
  <li><a target="_blank" href="https://ru.wordpress.org/plugins/truncate-comments/">Truncate Comments</a> - <?php _e('плагин скрывает длинные комментарии js-скриптом (в стиле Яндекса или Амазона).', 'rss-for-yandex-turbo'); ?> </li>
1979
  <li><a target="_blank" href="https://ru.wordpress.org/plugins/easy-yandex-share/">Easy Yandex Share</a> - <?php _e('продвинутый вывод блока &#8220;Яндекс.Поделиться&#8221;.', 'rss-for-yandex-turbo'); ?></li>
1980
- <li><a target="_blank" href="https://wordpress.org/plugins/hide-my-dates/">Hide My Dates</a> - <?php _e('this plugin hides post and comment publishing dates from Google.', 'rss-for-yandex-turbo'); ?></li>
1981
  <li style="margin: 3px 0px 3px 35px;"><a target="_blank" href="https://ru.wordpress.org/plugins/html5-cumulus/">HTML5 Cumulus</a> <span class="new">new</span> - <?php _e('современная (HTML5) версия классического плагина &#8220;WP-Cumulus&#8221;.', 'rss-for-yandex-turbo'); ?></li>
1982
 
1983
  </ul>
@@ -1988,6 +2080,7 @@ if (closeabout == 'yes') {
1988
  </form>
1989
  </div>
1990
  </div>
 
1991
  <?php
1992
  }
1993
  //функция вывода страницы настроек плагина end
@@ -2241,7 +2334,7 @@ tt{padding: 1px 5px 1px;margin: 0 1px;background: #eaeaea;background: rgba(0, 0,
2241
  'toolbar1' => 'undo,redo,formatselect,bold,italic,underline,strikethrough,superscript,subscript,hr,blockquote,link,unlink,bullist,numlist,table,yablocks,',
2242
  'toolbar2' => '',
2243
  'toolbar3' => '',
2244
- 'content_css' => $purl . '/inc/editor.css?ver=1.26'
2245
  ),
2246
  'quicktags' => array(
2247
  'id' => 'customtemplate',
@@ -2324,9 +2417,9 @@ $ytad5rsa = $yturbo_options['ytad5rsa'];
2324
  $ytadfox5 = html_entity_decode(stripcslashes($yturbo_options['ytadfox5']),ENT_QUOTES);
2325
 
2326
  $ytexcludetags = $yturbo_options['ytexcludetags'];
2327
- $ytexcludetagslist = html_entity_decode($yturbo_options['ytexcludetagslist']);
2328
  $ytexcludetags2 = $yturbo_options['ytexcludetags2'];
2329
- $ytexcludetagslist2 = html_entity_decode($yturbo_options['ytexcludetagslist2']);
2330
  $ytexcludecontent = $yturbo_options['ytexcludecontent'];
2331
  $ytexcludecontentlist = html_entity_decode($yturbo_options['ytexcludecontentlist']);
2332
  $tax_query = array();
@@ -2411,25 +2504,37 @@ if ($ytrazb == 'enabled' && $ytrazbnumber) {
2411
  if (isset($_GET['paged'])) {
2412
  $paged = $_GET['paged'];
2413
  } else {
2414
- $paged = 0;
2415
- }
2416
- $offset = $ytrazbnumber * ($paged - 1);
2417
- if ($paged == 0) {
2418
  $paged = 1;
2419
- $offset = 0;
2420
  }
2421
- $temp = ceil($ytnumber / $ytrazbnumber);
2422
- if ($paged > $temp) {echo 'Не хватает записей для этой ленты, измените настройки плагина.'; return;}
2423
- $perpage = $ytrazbnumber * $paged;
2424
  } else {
2425
- $offset = 0;
2426
  $ytrazbnumber = $ytnumber;
2427
  }
2428
- if($yttype[0]==''){$yttype[0]='trulala';}//если в настройках не выбраны типы записей, то отключаем дефолтный post_type равный 'post'
2429
-
2430
- $args = array('offset'=> $offset,'ignore_sticky_posts' => 1, 'post_type' => $yttype, 'post_status' => 'publish', 'posts_per_page' => $ytrazbnumber,'tax_query' => $tax_query,
2431
- 'meta_query' => array('relation' => 'OR', array('key' => 'ytrssenabled_meta_value', 'compare' => 'NOT EXISTS',),
2432
- array('key' => 'ytrssenabled_meta_value', 'value' => 'yes', 'compare' => '!=',),));
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2433
  $query = new WP_Query( $args );
2434
 
2435
  header('Content-Type: ' . feed_content_type('rss2') . '; charset=' . get_option('blog_charset'), true);
@@ -2453,7 +2558,7 @@ echo '<?xml version="1.0" encoding="'.get_option('blog_charset').'"?'.'>'.PHP_EO
2453
  <?php if ($ytmediascope) { ?><turbo:analytics id="<?php echo $ytmediascope; ?>" type="Mediascope"></turbo:analytics><?php echo PHP_EOL; ?><?php } ?>
2454
  <?php do_action( 'yturbo_ads_header' ); echo yturbo_turbo_ads(); ?>
2455
  <language><?php echo $ytlanguage; ?></language>
2456
- <generator>RSS for Yandex Turbo v1.26 (https://wordpress.org/plugins/rss-for-yandex-turbo/)</generator>
2457
  <?php do_action( 'yturbo_generator' ); ?>
2458
  <?php while($query->have_posts()) : $query->the_post(); ?>
2459
  <?php $ytremove = get_post_meta(get_the_ID(), 'ytremove_meta_value', true); ?>
@@ -2665,7 +2770,8 @@ echo '<?xml version="1.0" encoding="'.get_option('blog_charset').'"?'.'>'.PHP_EO
2665
  <?php if ($yturbo_options['ytsearch'] != 'disabled' && $yturbo_options['ytsearchmesto'] == 'В начале записи') {echo yturbo_search_widget();} ?>
2666
  <?php if ($yturbo_options['ytfeedback'] != 'disabled' && $yturbo_options['ytfeedbackselect'] == 'false' && $yturbo_options['ytfeedbackselectmesto'] == 'В начале записи') {echo yturbo_widget_feedback();} ?>
2667
  <?php
2668
- $temp = apply_filters( 'yturbo_add_custom_ads', $content );
 
2669
  if ( $temp != $content ) {
2670
  echo $temp;
2671
  } else {
@@ -2806,6 +2912,15 @@ function yturbo_the_content_feed() {
2806
 
2807
  //функция удаления тегов вместе с их контентом begin
2808
  function yturbo_strip_tags_with_content( $text, $tags = '', $invert = FALSE ) {
 
 
 
 
 
 
 
 
 
2809
  preg_match_all( '/<(.+?)[\s]*\/?[\s]*>/si', trim( $tags ), $tags_array );
2810
  $tags_array = array_unique( $tags_array[1] );
2811
 
@@ -2835,6 +2950,14 @@ function yturbo_strip_tags_with_content( $text, $tags = '', $invert = FALSE ) {
2835
  //функция удаления тегов без их контента begin
2836
  function yturbo_strip_tags_without_content( $text, $tags = '' ) {
2837
 
 
 
 
 
 
 
 
 
2838
  preg_match_all('/<(.+?)[\s]*\/?[\s]*>/si', trim($tags), $tags);
2839
  $tags = array_unique($tags[1]);
2840
 
@@ -3031,7 +3154,7 @@ function yturbo_add_advert( $content ) {
3031
  $ads = PHP_EOL.'<figure data-turbo-ad-id="second_ad_place"></figure>';
3032
  }
3033
 
3034
- if (mb_strlen($tempcontent) > (int)$ytrazmer) {
3035
  $content = preg_replace('~[^^]{'. $num .'}.*?(?:\r?\n\r?\n|</p>|</figure>|</ul>|</pre>|</table>|</ol>|</blockquote>)~su', "\${0}$ads", trim( $content ), 1);
3036
  }
3037
 
@@ -3216,7 +3339,7 @@ function yturbo_comments( $comment, $args, $depth ) {
3216
  <?php if ($args['has_children'] && $ytcommentsdrevo=='enabled') { ?><?php echo '<div data-block="comments">'; ?><?php }
3217
  }
3218
 
3219
- function yturbo_comments_end($comment, $args, $depth) {
3220
  $yturbo_options = get_option('yturbo_options');
3221
  $ytcommentsdrevo = $yturbo_options['ytcommentsdrevo'];
3222
  ?>
@@ -3505,11 +3628,9 @@ function yturbo_toc( $content ) {
3505
  if ( ! in_array( get_post_type( get_the_ID() ), $types ) )
3506
  return $content;
3507
 
3508
- //подключение файла с классом Kama_Contents begin
3509
- if ( ! class_exists('Kama_Contents') ) {
3510
- require_once dirname( __FILE__ ) . '/inc/class-Kama_Contents.php';
3511
- }
3512
- //подключение файла с классом Kama_Contents end
3513
 
3514
  $selectors = array();
3515
  if ($yturbo_options['yttoch1']=='enabled'){array_push($selectors, 'h1');}
@@ -3529,19 +3650,11 @@ function yturbo_toc( $content ) {
3529
  'selectors' => $selectors,
3530
  );
3531
 
3532
- $contents = Kama_Contents::init( $args )->make_contents( $content );
3533
 
3534
  $contents = str_replace("\n", '', $contents);
3535
  $contents = trim(preg_replace('/\t+/', '', $contents));
3536
  $contents = wpautop($contents);
3537
- $contents = str_replace('<div class="kc__wrap" ><span style="display:block;" class="kc-title kc__title" id="kcmenu">', '<div><h3>', $contents);
3538
- $contents = str_replace('</span></p>', '</h3>', $contents);
3539
- $contents = str_replace(' class="contents"', '', $contents);
3540
- $contents = str_replace(' class="top"', '', $contents);
3541
- $contents = str_replace(' rel="nofollow"', '', $contents);
3542
- $contents = str_replace('<ul>', '<ol>', $contents);
3543
- $contents = str_replace('<ul id="kcmenu">', '<ol>', $contents);
3544
- $contents = str_replace('</ul>', '</ol>', $contents);
3545
 
3546
  if ( $yturbo_options['yttocmesto'] == 'В начале записи' ) {
3547
  return PHP_EOL . $contents . $content;
@@ -3623,9 +3736,10 @@ function yturbo_empty_title( $title ) {
3623
 
3624
  //добавляем плагины в визуальный редактор begin
3625
  function yturbo_add_plugins_tinymce( $plugins ) {
 
3626
  $purl = plugins_url('', __FILE__);
3627
- $plugins['yablocks'] = $purl . '/inc/yablocks.js?ver=1.26';
3628
- $plugins['table'] = $purl . '/inc/table.js?ver=1.26';
3629
  return $plugins;
3630
  }
3631
  add_filter( 'mce_external_plugins', 'yturbo_add_plugins_tinymce' );
@@ -3789,4 +3903,109 @@ function yturbo_hide_custom_fields( $protected, $meta_key ){
3789
  return $protected;
3790
  }
3791
  add_filter( 'is_protected_meta', 'yturbo_hide_custom_fields', 10, 2 );
3792
- //скрываем произвольные поля плагина end
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
3
  Plugin Name: RSS for Yandex Turbo
4
  Plugin URI: https://wordpress.org/plugins/rss-for-yandex-turbo/
5
  Description: Создание RSS-ленты для сервиса Яндекс.Турбо.
6
+ Version: 1.27
7
  Author: Flector
8
  Author URI: https://profiles.wordpress.org/flector#content-plugins
9
  Text Domain: rss-for-yandex-turbo
10
  */
11
 
12
+ //вывод admin notice с рекламкой (для админов) begin
13
+ require_once plugin_dir_path( __FILE__ ) . 'inc/AdminNotice.php';
14
+ use \YTurboAdminNotices\AdminNotice;
15
+ function yturbo_add_notice_ads() {
16
+ AdminNotice::create('yturbo-ads1')
17
+ ->requiredCap('administrator')
18
+ ->persistentlyDismissible(AdminNotice::DISMISS_PER_USER)
19
+ ->success()
20
+ ->rawHtml(__('<p>Для плагина <strong>RSS for Yandex Turbo</strong> появилось премиум-дополнение <strong><a target="_blank" href="https://wpcase.ru/wpcase-turbo-ads/">WPCase: Turbo Ads</a></strong>, которое позволит <br />вам добавить на турбо-страницы неограниченное количество рекламных блоков в нужных вам местах.</p>', 'rss-for-yandex-turbo'))
21
+ ->show();
22
+ }
23
+ add_action( 'admin_notices', 'yturbo_add_notice_ads' );
24
+ //вывод admin notice с рекламкой (для админов) end
25
+
26
  //проверка версии плагина (запуск функции установки новых опций) begin
27
  function yturbo_check_version() {
28
  $yturbo_options = get_option('yturbo_options');
29
  if (!isset($yturbo_options['version'])){$yturbo_options['version']='1.00';update_option('yturbo_options',$yturbo_options);}
30
+ if ( $yturbo_options['version'] != '1.27' ) {
31
  yturbo_set_new_options();
32
  }
33
  }
142
  $yturbo_options['ytdescription'] = esc_html(yturbo_remove_emoji(strip_tags($yturbo_options['ytdescription'])));
143
  if (!isset($yturbo_options['required'])) {$yturbo_options['required']='1.00';}
144
 
145
+ // новый формат хранения удаляемых тегов begin
146
+ $yturbo_options['ytexcludetagslist'] = preg_replace('/[^A-Za-z0-9,]/', '', html_entity_decode($yturbo_options['ytexcludetagslist']));
147
+ $yturbo_options['ytexcludetagslist'] = mb_strtolower($yturbo_options['ytexcludetagslist']);
148
+ $a = explode(",", $yturbo_options['ytexcludetagslist'] );
149
+ $a = array_diff($a, array(''));
150
+ $yturbo_options['ytexcludetagslist'] = implode(",", $a );
151
+ // новый формат хранения удаляемых тегов end
152
+
153
+ // новый формат хранения удаляемых тегов begin
154
+ $yturbo_options['ytexcludetagslist2'] = preg_replace('/[^A-Za-z0-9,]/', '', html_entity_decode($yturbo_options['ytexcludetagslist2']));
155
+ $yturbo_options['ytexcludetagslist2'] = mb_strtolower($yturbo_options['ytexcludetagslist2']);
156
+ $a = explode(",", $yturbo_options['ytexcludetagslist2'] );
157
+ $a = array_diff($a, array(''));
158
+ $yturbo_options['ytexcludetagslist2'] = implode(",", $a );
159
+ // новый формат хранения удаляемых тегов end
160
+
161
+ if (!isset($yturbo_options['ytexcludeurls'])) {$yturbo_options['ytexcludeurls']='disabled';}
162
+ if (!isset($yturbo_options['ytexcludeurlslist'])) {$yturbo_options['ytexcludeurlslist']='';}
163
+ if (!isset($yturbo_options['ytdeltracking'])) {$yturbo_options['ytdeltracking']='disabled';}
164
+
165
+
166
+ $yturbo_options['version'] = '1.27';
167
  update_option('yturbo_options', $yturbo_options);
168
  }
169
  //функция установки новых опций при обновлении плагина у пользователей end
171
  //функция установки значений по умолчанию при активации плагина begin
172
  function yturbo_init() {
173
  $yturbo_options = array();
174
+ $yturbo_options['version'] = '1.27';
175
  $yturbo_options['ytrssname'] = 'turbo';
176
  $yturbo_options['yttitle'] = esc_html(yturbo_remove_emoji(strip_tags(get_bloginfo_rss('title'))));
177
  $yturbo_options['ytlink'] = get_bloginfo_rss('url');
186
  $yturbo_options['ytauthor'] = '';
187
  $yturbo_options['ytthumbnail'] = 'enabled';
188
  $yturbo_options['ytselectthumb'] = 'large';
189
+ $yturbo_options['ytexcludetags'] = 'enabled';
190
+ $yturbo_options['ytexcludetagslist'] = 'span';
191
+ $yturbo_options['ytexcludetags2'] = 'enabled';
192
+ $yturbo_options['ytexcludetagslist2'] = 'script,style';
193
  $yturbo_options['ytexcludecontent'] = 'disabled';
194
  $yturbo_options['ytexcludecontentlist'] = esc_textarea('<!--more-->\n<p></p>\n<p>&nbsp;</p>');
195
 
299
  $yturbo_options['ytturbocolumn'] = 'enabled';
300
  $yturbo_options['ytrelateddate'] = '12';
301
 
302
+ $yturbo_options['ytexcludeurls'] = 'disabled';
303
+ $yturbo_options['ytexcludeurlslist'] = '';
304
+ $yturbo_options['ytdeltracking'] = 'disabled';
305
+
306
  $yturbo_options['required']='1.00';
307
 
308
  add_option('yturbo_options', $yturbo_options);
334
  function yturbo_on_uninstall() {
335
  if ( ! current_user_can('activate_plugins') ) return;
336
  delete_option('yturbo_options');
337
+ AdminNotice::cleanUpDatabase('yturbo-');
338
  }
339
  register_uninstall_hook( __FILE__, 'yturbo_on_uninstall' );
340
  //функция при удалении плагина end
356
  //функция загрузки скриптов и стилей плагина только в админке и только на странице настроек плагина begin
357
  function yturbo_files_admin( $hook_suffix ) {
358
  $purl = plugins_url('', __FILE__);
359
+ $yturbo_options = get_option('yturbo_options');
360
  if ( $hook_suffix == 'settings_page_rss-for-yandex-turbo' ) {
361
+ wp_enqueue_script('jquery');
362
+ wp_enqueue_script('yturbo-tagify-js', $purl . '/inc/tagify.js', array(), $yturbo_options['version']);
363
+ wp_enqueue_style('yturbo-tagify-css', $purl . '/inc/tagify.css', array(), $yturbo_options['version']);
364
+ wp_enqueue_script('yturbo-script', $purl . '/inc/yturbo-script.js', array(), $yturbo_options['version']);
365
+ wp_enqueue_style('yturbo-css', $purl . '/inc/yturbo-css.css', array(), $yturbo_options['version']);
 
 
 
 
 
 
366
  }
367
  }
368
  add_action( 'admin_enqueue_scripts', 'yturbo_files_admin' );
420
  $yturbo_options['ytselectthumb'] = sanitize_text_field($_POST['ytselectthumb']);
421
 
422
  if(isset($_POST['ytexcludetags'])){$yturbo_options['ytexcludetags'] = sanitize_text_field($_POST['ytexcludetags']);}else{$yturbo_options['ytexcludetags'] = 'disabled';}
423
+ $ytexcludetagslist = preg_replace('/[^A-Za-z0-9,]/', '', sanitize_text_field($_POST['ytexcludetagslist']));
424
+ $yturbo_options['ytexcludetagslist'] = $ytexcludetagslist;
 
425
 
426
  if(isset($_POST['ytexcludetags2'])){$yturbo_options['ytexcludetags2'] = sanitize_text_field($_POST['ytexcludetags2']);}else{$yturbo_options['ytexcludetags2'] = 'disabled';}
427
+ $ytexcludetagslist2 = preg_replace('/[^A-Za-z0-9,]/', '', sanitize_text_field($_POST['ytexcludetagslist2']));
428
+ $yturbo_options['ytexcludetagslist2'] = $ytexcludetagslist2;
 
429
 
430
  if(isset($_POST['ytexcludecontent'])){$yturbo_options['ytexcludecontent'] = sanitize_text_field($_POST['ytexcludecontent']);}else{$yturbo_options['ytexcludecontent'] = 'disabled';}
431
+ $lines = array_filter(explode("\n", trim(esc_textarea($_POST['ytexcludecontentlist']))));
432
+ $yturbo_options['ytexcludecontentlist'] = implode("\n", $lines);
433
 
434
+ if(isset($_POST['ytad1'])){$yturbo_options['ytad1'] = sanitize_text_field($_POST['ytad1']);}else{$yturbo_options['ytad1'] = 'disabled';}
435
  $yturbo_options['ytad1set'] = sanitize_text_field($_POST['ytad1set']);
436
  $yturbo_options['ytad1rsa'] = sanitize_text_field($_POST['ytad1rsa']);
437
  $yturbo_options['ytadfox1'] = esc_html($_POST['ytadfox1']);
608
  $yturbo_options['ytrelateddate'] = sanitize_text_field($_POST['ytrelateddate']);
609
  }
610
 
611
+ if(isset($_POST['ytexcludeurls'])){$yturbo_options['ytexcludeurls'] = sanitize_text_field($_POST['ytexcludeurls']);}else{$yturbo_options['ytexcludeurls'] = 'disabled';}
612
+ $lines = array_filter(explode("\n", trim(esc_textarea($_POST['ytexcludeurlslist']))));
613
+ $yturbo_options['ytexcludeurlslist'] = implode("\n", $lines);
614
+ if(isset($_POST['ytdeltracking'])){$yturbo_options['ytdeltracking'] = sanitize_text_field($_POST['ytdeltracking']);}else{$yturbo_options['ytdeltracking'] = 'disabled';}
615
+
616
  update_option('yturbo_options', $yturbo_options);
617
 
618
  yturbo_clear_transients();
631
  <?php endif; ?>
632
 
633
  <div class="wrap foptions">
634
+ <h2><?php _e('Настройки плагина &#8220;Яндекс.Турбо&#8220;', 'rss-for-yandex-turbo'); ?> <span id="current-version">v<?php echo $yturbo_options['version']; ?></span><span id="restore-hide-blocks" class="dashicons dashicons-admin-generic hide" title="<?php _e('Восстановить скрытые блоки', 'rss-for-yandex-turbo'); ?>"></span></h2>
635
 
636
  <div class="metabox-holder" id="poststuff">
637
  <div class="meta-box-sortables">
644
  document.getElementById('restore-hide-blocks').className = 'dashicons dashicons-admin-generic';
645
  }
646
  </script>
647
+ <h3 style="border-bottom: 1px solid #E1E1E1;background: #f7f7f7;"><?php _e('Вам нравится этот плагин ?', 'rss-for-yandex-turbo'); ?>
648
  <span id="close-donat" class="dashicons dashicons-no-alt" title="<?php _e('Скрыть блок', 'rss-for-yandex-turbo'); ?>"></span></h3>
649
  <div class="inside" style="display: block;margin-right: 12px;">
650
  <img src="<?php echo $purl . '/img/icon_coffee.png'; ?>" title="<?php _e('Купить мне чашку кофе :)', 'rss-for-yandex-turbo'); ?>" style=" margin: 5px; float:left;" />
724
  <br /><small><?php _e('Язык статей RSS-ленты в стандарте <a target="_blank" href="https://ru.wikipedia.org/wiki/%D0%9A%D0%BE%D0%B4%D1%8B_%D1%8F%D0%B7%D1%8B%D0%BA%D0%BE%D0%B2">ISO 639-1</a> (Россия - <strong>ru</strong>, Украина - <strong>uk</strong> и т.д.).', 'rss-for-yandex-turbo'); ?> </small>
725
  </td>
726
  </tr>
727
+ <tr class="trbordertop">
728
  <th><?php _e('Количество записей:', 'rss-for-yandex-turbo'); ?></th>
729
  <td>
730
  <input style="max-width: 74px;" name="ytnumber" type="number" min="1" max="999999" step="1" value="<?php echo $yturbo_options['ytnumber']; ?>" />
753
  </small>
754
  </td>
755
  </tr>
756
+ <tr class="ytexcludeurlstr trbordertop">
757
+ <th class="tdcheckbox"><?php _e('Выборочное отключение:', 'rss-for-yandex-turbo'); ?></th>
758
+ <td>
759
+ <label for="ytexcludeurls"><input type="checkbox" value="enabled" name="ytexcludeurls" id="ytexcludeurls" <?php if ($yturbo_options['ytexcludeurls'] == 'enabled') echo 'checked="checked"'; ?> /><?php _e('Удалить указанные турбо-страницы', 'rss-for-yandex-turbo'); ?></label>
760
+ <br /><small><?php _e('Если вы полностью удалили запись на сайте, то отключить ее турбо-страницу обычным способом не получится.', 'rss-for-yandex-turbo'); ?><br />
761
+ <?php _e('Эта опция позволит сформировать отдельную RSS-ленту с записями, которые Яндекс должен удалить.', 'rss-for-yandex-turbo'); ?><br />
762
+ </small>
763
+ </td>
764
+ </tr>
765
+ <tr class="ytexcludeurlslisttr" <?php if ($yturbo_options['ytexcludeurls'] == 'disabled') echo 'style="display:none;"'; ?>>
766
+ <th class="tdcheckbox"><?php _e('URL "мусорной" ленты:', 'rss-for-yandex-turbo'); ?></th>
767
+ <td>
768
+ <?php
769
+ if ( get_option('permalink_structure') ) {
770
+ echo '<a target="_blank" href="'.get_bloginfo("url").'/feed/'.$yturbo_options['ytrssname'].'/?lenta=trash'.'">'.get_bloginfo("url").'/feed/'.$yturbo_options['ytrssname'].'/?lenta=trash'.'</a>';
771
+ } else {
772
+ echo '<a target="_blank" href="'.get_bloginfo("url").'/?feed='.$yturbo_options['ytrssname'].'&lenta=trash">'.get_bloginfo("url").'/?feed='.$yturbo_options['ytrssname'].'&lenta=trash</a>';
773
+ }
774
+ ?>
775
+ <br /><small><?php _e('Добавьте эту RSS-ленту в Яндекс.Вебмастер как обычную ленту.', 'rss-for-yandex-turbo'); ?><br />
776
+ </small>
777
+ </td>
778
+ </tr>
779
+ <tr class="ytexcludeurlslisttr" <?php if ($yturbo_options['ytexcludeurls'] == 'disabled') echo 'style="display:none;"'; ?>>
780
+ <th class="tdcheckbox"><?php _e('Отслеживание:', 'rss-for-yandex-turbo'); ?></th>
781
+ <td>
782
+ <label for="ytdeltracking"><input type="checkbox" value="enabled" name="ytdeltracking" id="ytdeltracking" <?php if ($yturbo_options['ytdeltracking'] == 'enabled') echo 'checked="checked"'; ?> /><?php _e('Следить за удаляемыми записями', 'rss-for-yandex-turbo'); ?></label>
783
+ <br /><small><?php _e('Плагин будет автоматически добавлять в список ниже ссылки на удаленные записи.', 'rss-for-yandex-turbo'); ?><br />
784
+
785
+ </small>
786
+ </td>
787
+ </tr>
788
+ <tr class="ytexcludeurlslisttr" <?php if ($yturbo_options['ytexcludeurls'] == 'disabled') echo 'style="display:none;"'; ?>>
789
+ <th><?php _e('Список удаляемых ссылок:', 'rss-for-yandex-turbo'); ?></th>
790
+ <td>
791
+ <textarea rows="8" cols="70" name="ytexcludeurlslist" id="ytexcludeurlslist"><?php echo stripcslashes($yturbo_options['ytexcludeurlslist']); ?></textarea>
792
+ <br /><small><?php _e('Каждая новая ссылка для удаления должна начинаться с новой строки.', 'rss-for-yandex-turbo'); ?><br />
793
+ </small>
794
+ </td>
795
+ </tr>
796
  <tr class="trbordertop">
797
+ <th class="tdcheckbox"><?php _e('Полное отключение:', 'rss-for-yandex-turbo'); ?></th>
798
  <td>
799
+ <label for="ytremoveturbo"><input type="checkbox" value="enabled" name="ytremoveturbo" id="ytremoveturbo" <?php if ($yturbo_options['ytremoveturbo'] == 'enabled') echo 'checked="checked"'; ?> /><?php _e('Удалить все турбо-страницы', 'rss-for-yandex-turbo'); ?></label>
800
  <br /><small><?php _e('Эта опция добавит в RSS-ленту атрибут <tt>turbo="false"</tt> к тегу <tt>&lt;item></tt> для всех записей.', 'rss-for-yandex-turbo'); ?> <br />
801
  <?php _e('Это единственный способ заставить Яндекс отключить турбо-страницы для вашего сайта.', 'rss-for-yandex-turbo'); ?><br />
802
  <?php _e('Простое удаление плагина не поможет - необходимо, чтобы бот Яндекса "съел" ленту с <tt>turbo="false"</tt>.', 'rss-for-yandex-turbo'); ?><br />
1557
  <div class="xyztabs__content<?php if($yturbo_options['yttab']=='Реклама'){echo ' active';} ?>"><!-- begin tab -->
1558
 
1559
  <?php if ( yturbo_check_ads() == true ) echo '<div style="display:none;">'; ?>
1560
+ <p><?php _e('Реклама, установленная в Яндекс.Вебмастере, распределяется равномерно по тексту страницы (примерно каждые 2-3 экрана с общим ограничением в 10 рекламных блоков).', 'rss-for-yandex-turbo'); ?><br />
1561
+ <?php _e('Если у вас большие по размеру контента статьи или вас не устраивает частота, с которой Яндекс расставляет рекламу, то рекомендую попробовать плагин <a target="_blank" href="https://wpcase.ru/wpcase-turbo-ads/">WPCase: Turbo Ads</a>.', 'rss-for-yandex-turbo'); ?><br />
1562
+ <?php _e('В нем вы можете установить сколько угодно рекламных блоков и с той частотой, которая вам нужна (гибкие настройки вставки рекламных блоков).', 'rss-for-yandex-turbo'); ?><br /><br />
1563
+ <?php _e('Этот же плагин позволяет разместить максимально 5 рекламных блоков (только 3 в контенте статьи).', 'rss-for-yandex-turbo'); ?><br />
1564
  <?php _e('При проблемах с настройкой рекламной сети ADFOX ознакомьтесь со справочными материалами: <a target="_blank" href="https://sites.help.adfox.ru/page/225">статья</a>, <a target="_blank" href="https://webmaster.yandex.ru/blog/videourok-kak-razmeschat-reklamu-na-turbo-stranitsakh-cherez-adfox">видеоурок</a>.', 'rss-for-yandex-turbo'); ?><br />
1565
  </p>
1566
 
1874
 
1875
  <p><?php _e('В шаблоне можно использовать шорткоды (убедитесь, что их вывод не содержит скрипты или css-код).', 'rss-for-yandex-turbo'); ?><br />
1876
  <?php _e('В плагин встроено несколько собственных шорткодов, полный их список вы можете посмотреть <a target="_blank" href="https://ru.wordpress.org/plugins/rss-for-yandex-turbo/#%D0%BA%D0%B0%D0%BA%D0%B8%D0%B5%20%D1%88%D0%BE%D1%80%D1%82%D0%BA%D0%BE%D0%B4%D1%8B%20%D0%BC%D0%BE%D0%B6%D0%BD%D0%BE%20%D0%B8%D1%81%D0%BF%D0%BE%D0%BB%D1%8C%D0%B7%D0%BE%D0%B2%D0%B0%D1%82%D1%8C%20%D0%B2%20%D1%88%D0%B0%D0%B1%D0%BB%D0%BE%D0%BD%D0%B0%D1%85%3F">здесь</a>.', 'rss-for-yandex-turbo'); ?></p>
1877
+ <p> <?php _e('<strong>Внимание!</strong> Произвольные поля плагина <strong>Advanced Custom Fields</strong> необходимо обрабатывать <a target="_blank" href="https://ru.wordpress.org/plugins/rss-for-yandex-turbo/#%D1%88%D0%B0%D0%B1%D0%BB%D0%BE%D0%BD%D1%8B%20%D0%B8%20%D0%BF%D0%BB%D0%B0%D0%B3%D0%B8%D0%BD%20advanced%20custom%20fields">фильтром</a>.', 'rss-for-yandex-turbo'); ?><br /><br />
1878
  </p>
1879
 
1880
  <table class="form-table">
1910
  'toolbar1' => 'undo,redo,formatselect,bold,italic,underline,strikethrough,superscript,subscript,hr,blockquote,link,unlink,bullist,numlist,table,yablocks,',
1911
  'toolbar2' => '',
1912
  'toolbar3' => '',
1913
+ 'content_css' => $purl . '/inc/editor.css?ver=' . $yturbo_options['version'],
1914
  ),
1915
  'quicktags' => array(
1916
  'id' => $editor_id,
1965
  $ytshortcodes = explode(",", $yturbo_options['ytexcludeshortcodeslist']);
1966
  $ytshortcodes = array_diff($ytshortcodes, array(''));
1967
 
1968
+ if ( ! empty($result) ) :
1969
+
1970
+ foreach ( $result as $shortcode ) { ?>
1971
  <label class="shortcodes" for="<?php echo $shortcode; ?>"><input type="checkbox" value="<?php echo $shortcode; ?>" name="shortcodes[]" id="<?php echo $shortcode; ?>" <?php if (in_array($shortcode, $ytshortcodes)) echo 'checked="checked"'; ?> />[<?php echo $shortcode; ?>]</label>
1972
  <?php } ?>
1973
  <small><?php _e('В списке находятся все зарегистрированные на сайте шорткоды, кроме системных.', 'rss-for-yandex-turbo'); ?><br />
1974
  </small>
1975
+
1976
+ <?php else : ?>
1977
+ <p style="margin-top: -5px;"><?php _e('Сторонних шорткодов не найдено.', 'rss-for-yandex-turbo'); ?></p>
1978
+ <?php endif; ?>
1979
  </td>
1980
  </tr>
1981
  <tr class="ytexcludetagstr trbordertop">
1982
  <th class="tdcheckbox"><?php _e('Фильтр тегов (без контента):', 'rss-for-yandex-turbo'); ?></th>
1983
  <td>
1984
  <label for="ytexcludetags"><input type="checkbox" value="enabled" name="ytexcludetags" id="ytexcludetags" <?php if ($yturbo_options['ytexcludetags'] == 'enabled') echo 'checked="checked"'; ?> /><?php _e('Удалить указанные html-теги', 'rss-for-yandex-turbo'); ?></label>
1985
+ <br /><small><?php _e('Из контента записей будут удалены все указанные html-теги (<strong>без контента этих тегов</strong>).', 'rss-for-yandex-turbo'); ?></small>
1986
+
1987
+
1988
  </td>
1989
  </tr>
1990
  <tr class="ytexcludetagslisttr" <?php if ($yturbo_options['ytexcludetags'] == 'disabled') echo 'style="display:none;"'; ?>>
1991
+ <th style="padding-top: 5px;"><?php _e('Теги для удаления:', 'rss-for-yandex-turbo'); ?></th>
1992
+ <td style="padding-top: 5px;">
1993
+ <input style="display:none;" name="ytexcludetagslist-input" class="ytexcludetagslist-input" placeholder="" value="<?php echo stripslashes($yturbo_options['ytexcludetagslist']); ?>" />
1994
+ <input type="hidden" id="tags-list" value="<?php echo yturbo_tags_list(); ?>" />
1995
+ <input type="hidden" name="ytexcludetagslist" id="ytexcludetagslist" value="<?php echo stripslashes($yturbo_options['ytexcludetagslist']); ?>" />
1996
+ <small><?php _e('Список удаляемых html-тегов. Начните набирать нужный тег для подсказки.', 'rss-for-yandex-turbo'); ?><br />
1997
+ <?php _e('Самозакрывающиеся теги вроде <tt>&lt;br /></tt> этим фильтром удалить нельзя.', 'rss-for-yandex-turbo'); ?><br />
1998
+ <?php _e('Список возможных для удаления тегов можно <a target="_blank" href="https://ru.wordpress.org/plugins/rss-for-yandex-turbo/#%D0%BA%D0%B0%D0%BA%20%D0%BF%D0%B5%D1%80%D0%B5%D0%BE%D0%BF%D1%80%D0%B5%D0%B4%D0%B5%D0%BB%D0%B8%D1%82%D1%8C%20%D1%81%D0%BF%D0%B8%D1%81%D0%BE%D0%BA%20%D1%82%D0%B5%D0%B3%D0%BE%D0%B2%20%D0%B4%D0%BB%D1%8F%20%D1%83%D0%B4%D0%B0%D0%BB%D0%B5%D0%BD%D0%B8%D1%8F%3F">переопределить</a>.', 'rss-for-yandex-turbo'); ?><br />
1999
  </small>
2000
  </td>
2001
  </tr>
2003
  <th class="tdcheckbox"><?php _e('Фильтр тегов (с контентом):', 'rss-for-yandex-turbo'); ?></th>
2004
  <td>
2005
  <label for="ytexcludetags2"><input type="checkbox" value="enabled" name="ytexcludetags2" id="ytexcludetags2" <?php if ($yturbo_options['ytexcludetags2'] == 'enabled') echo 'checked="checked"'; ?> /><?php _e('Удалить указанные html-теги', 'rss-for-yandex-turbo'); ?></label>
2006
+ <br /><small><?php _e('Из контента записей будут удалены все указанные html-теги (<strong>включая контент этих тегов</strong>).', 'rss-for-yandex-turbo'); ?></small>
2007
  </td>
2008
  </tr>
2009
  <tr class="ytexcludetagslist2tr" <?php if ($yturbo_options['ytexcludetags2'] == 'disabled') echo 'style="display:none;"'; ?>>
2010
+ <th style="padding-top: 5px;"><?php _e('Теги для удаления:', 'rss-for-yandex-turbo'); ?></th>
2011
+ <td style="padding-top: 5px;">
2012
+ <input style="display:none;" name="ytexcludetagslist-input2" class="ytexcludetagslist-input2" placeholder="" value="<?php echo stripslashes($yturbo_options['ytexcludetagslist2']); ?>" />
2013
+ <input type="hidden" id="tags-list2" value="<?php echo yturbo_tags_list(); ?>" />
2014
+ <input type="hidden" name="ytexcludetagslist2" id="ytexcludetagslist2" value="<?php echo stripslashes($yturbo_options['ytexcludetagslist2']); ?>" />
2015
+ <small><?php _e('Список удаляемых html-тегов. Начните набирать нужный тег для подсказки.', 'rss-for-yandex-turbo'); ?><br />
2016
+ <?php _e('Самозакрывающиеся теги вроде <tt>&lt;br /></tt> этим фильтром удалить нельзя.', 'rss-for-yandex-turbo'); ?> <br />
2017
+ <?php _e('Список возможных для удаления тегов можно <a target="_blank" href="https://ru.wordpress.org/plugins/rss-for-yandex-turbo/#%D0%BA%D0%B0%D0%BA%20%D0%BF%D0%B5%D1%80%D0%B5%D0%BE%D0%BF%D1%80%D0%B5%D0%B4%D0%B5%D0%BB%D0%B8%D1%82%D1%8C%20%D1%81%D0%BF%D0%B8%D1%81%D0%BE%D0%BA%20%D1%82%D0%B5%D0%B3%D0%BE%D0%B2%20%D0%B4%D0%BB%D1%8F%20%D1%83%D0%B4%D0%B0%D0%BB%D0%B5%D0%BD%D0%B8%D1%8F%3F">переопределить</a>.', 'rss-for-yandex-turbo'); ?><br />
2018
  </small>
2019
  </td>
2020
  </tr>
2053
  document.getElementById('restore-hide-blocks').className = 'dashicons dashicons-admin-generic';
2054
  }
2055
  </script>
2056
+ <h3 style="border-bottom: 1px solid #E1E1E1;background: #f7f7f7;"><?php _e('О плагине', 'rss-for-yandex-turbo'); ?>
2057
  <span id="close-about" class="dashicons dashicons-no-alt" title="<?php _e('Скрыть блок', 'rss-for-yandex-turbo'); ?>"></span></h3>
2058
  <div class="inside" style="padding-bottom:15px;display: block;">
2059
 
2069
  <li><a target="_blank" href="https://ru.wordpress.org/plugins/today-yesterday-dates/">Today-Yesterday Dates</a> - <?php _e('относительные даты для записей за сегодня и вчера.', 'rss-for-yandex-turbo'); ?> </li>
2070
  <li><a target="_blank" href="https://ru.wordpress.org/plugins/truncate-comments/">Truncate Comments</a> - <?php _e('плагин скрывает длинные комментарии js-скриптом (в стиле Яндекса или Амазона).', 'rss-for-yandex-turbo'); ?> </li>
2071
  <li><a target="_blank" href="https://ru.wordpress.org/plugins/easy-yandex-share/">Easy Yandex Share</a> - <?php _e('продвинутый вывод блока &#8220;Яндекс.Поделиться&#8221;.', 'rss-for-yandex-turbo'); ?></li>
2072
+ <li><a target="_blank" href="https://ru.wordpress.org/plugins/hide-my-dates/">Hide My Dates</a> - <?php _e('плагин прячет от Гугла даты публикации записей и комментариев.', 'rss-for-yandex-turbo'); ?></li>
2073
  <li style="margin: 3px 0px 3px 35px;"><a target="_blank" href="https://ru.wordpress.org/plugins/html5-cumulus/">HTML5 Cumulus</a> <span class="new">new</span> - <?php _e('современная (HTML5) версия классического плагина &#8220;WP-Cumulus&#8221;.', 'rss-for-yandex-turbo'); ?></li>
2074
 
2075
  </ul>
2080
  </form>
2081
  </div>
2082
  </div>
2083
+ </div>
2084
  <?php
2085
  }
2086
  //функция вывода страницы настроек плагина end
2334
  'toolbar1' => 'undo,redo,formatselect,bold,italic,underline,strikethrough,superscript,subscript,hr,blockquote,link,unlink,bullist,numlist,table,yablocks,',
2335
  'toolbar2' => '',
2336
  'toolbar3' => '',
2337
+ 'content_css' => $purl . '/inc/editor.css?ver=' . $yturbo_options['version'],
2338
  ),
2339
  'quicktags' => array(
2340
  'id' => 'customtemplate',
2417
  $ytadfox5 = html_entity_decode(stripcslashes($yturbo_options['ytadfox5']),ENT_QUOTES);
2418
 
2419
  $ytexcludetags = $yturbo_options['ytexcludetags'];
2420
+ $ytexcludetagslist = $yturbo_options['ytexcludetagslist'];
2421
  $ytexcludetags2 = $yturbo_options['ytexcludetags2'];
2422
+ $ytexcludetagslist2 = $yturbo_options['ytexcludetagslist2'];
2423
  $ytexcludecontent = $yturbo_options['ytexcludecontent'];
2424
  $ytexcludecontentlist = html_entity_decode($yturbo_options['ytexcludecontentlist']);
2425
  $tax_query = array();
2504
  if (isset($_GET['paged'])) {
2505
  $paged = $_GET['paged'];
2506
  } else {
 
 
 
 
2507
  $paged = 1;
 
2508
  }
2509
+ if ($paged == 0) {$paged = 1;}
 
 
2510
  } else {
2511
+ $paged = 1;
2512
  $ytrazbnumber = $ytnumber;
2513
  }
2514
+ if ( isset($_GET['lenta']) && $_GET['lenta'] == 'trash' ) {
2515
+ if ( $yturbo_options['ytexcludeurls'] == 'enabled' ) {
2516
+ yturbo_lenta_trash();
2517
+ exit;
2518
+ }
2519
+ }
2520
+
2521
+ //если в настройках не выбраны типы записей, то отключаем дефолтный post_type равный 'post'
2522
+ if($yttype[0]==''){$yttype[0]='trulala';}
2523
+
2524
+ $args = array(
2525
+ 'paged' => $paged,
2526
+ 'ignore_sticky_posts' => 1,
2527
+ 'post_type' => $yttype,
2528
+ 'post_status' => 'publish',
2529
+ 'posts_per_page' => $ytrazbnumber,
2530
+ 'tax_query' => $tax_query,
2531
+ 'meta_query' => array(
2532
+ 'relation' => 'OR',
2533
+ array('key' => 'ytrssenabled_meta_value', 'compare' => 'NOT EXISTS',),
2534
+ array('key' => 'ytrssenabled_meta_value', 'value' => 'yes', 'compare' => '!=',),
2535
+ )
2536
+ );
2537
+ $args = apply_filters( 'yturbo_query_args', $args );
2538
  $query = new WP_Query( $args );
2539
 
2540
  header('Content-Type: ' . feed_content_type('rss2') . '; charset=' . get_option('blog_charset'), true);
2558
  <?php if ($ytmediascope) { ?><turbo:analytics id="<?php echo $ytmediascope; ?>" type="Mediascope"></turbo:analytics><?php echo PHP_EOL; ?><?php } ?>
2559
  <?php do_action( 'yturbo_ads_header' ); echo yturbo_turbo_ads(); ?>
2560
  <language><?php echo $ytlanguage; ?></language>
2561
+ <generator>RSS for Yandex Turbo v<?php echo $yturbo_options['version']; ?> (https://wordpress.org/plugins/rss-for-yandex-turbo/)</generator>
2562
  <?php do_action( 'yturbo_generator' ); ?>
2563
  <?php while($query->have_posts()) : $query->the_post(); ?>
2564
  <?php $ytremove = get_post_meta(get_the_ID(), 'ytremove_meta_value', true); ?>
2770
  <?php if ($yturbo_options['ytsearch'] != 'disabled' && $yturbo_options['ytsearchmesto'] == 'В начале записи') {echo yturbo_search_widget();} ?>
2771
  <?php if ($yturbo_options['ytfeedback'] != 'disabled' && $yturbo_options['ytfeedbackselect'] == 'false' && $yturbo_options['ytfeedbackselectmesto'] == 'В начале записи') {echo yturbo_widget_feedback();} ?>
2772
  <?php
2773
+ $content = apply_filters('yturbo_before_ads', $content);
2774
+ $temp = apply_filters('yturbo_add_custom_ads', $content);
2775
  if ( $temp != $content ) {
2776
  echo $temp;
2777
  } else {
2912
 
2913
  //функция удаления тегов вместе с их контентом begin
2914
  function yturbo_strip_tags_with_content( $text, $tags = '', $invert = FALSE ) {
2915
+
2916
+ // удаляем лишние символы, добавляем тегам символы <> begin
2917
+ $tags = preg_replace('/[^A-Za-z0-9,]/', '', $tags);
2918
+ $a = explode(",", $tags );
2919
+ $a = array_diff($a, array(''));
2920
+ array_walk($a, function(&$value, $key) { $value = '<'. $value . '>'; } );
2921
+ $tags = implode(",", $a );
2922
+ // удаляем лишние символы, добавляем тегам символы <> end
2923
+
2924
  preg_match_all( '/<(.+?)[\s]*\/?[\s]*>/si', trim( $tags ), $tags_array );
2925
  $tags_array = array_unique( $tags_array[1] );
2926
 
2950
  //функция удаления тегов без их контента begin
2951
  function yturbo_strip_tags_without_content( $text, $tags = '' ) {
2952
 
2953
+ // удаляем лишние символы, добавляем тегам символы <> begin
2954
+ $tags = preg_replace('/[^A-Za-z0-9,]/', '', $tags);
2955
+ $a = explode(",", $tags );
2956
+ $a = array_diff($a, array(''));
2957
+ array_walk($a, function(&$value, $key) { $value = '<'. $value . '>'; } );
2958
+ $tags = implode(",", $a );
2959
+ // удаляем лишние символы, добавляем тегам символы <> end
2960
+
2961
  preg_match_all('/<(.+?)[\s]*\/?[\s]*>/si', trim($tags), $tags);
2962
  $tags = array_unique($tags[1]);
2963
 
3154
  $ads = PHP_EOL.'<figure data-turbo-ad-id="second_ad_place"></figure>';
3155
  }
3156
 
3157
+ if (mb_strlen($tempcontent) > (int)$ytrazmer && mb_strlen($tempcontent) < 65000) {
3158
  $content = preg_replace('~[^^]{'. $num .'}.*?(?:\r?\n\r?\n|</p>|</figure>|</ul>|</pre>|</table>|</ol>|</blockquote>)~su', "\${0}$ads", trim( $content ), 1);
3159
  }
3160
 
3339
  <?php if ($args['has_children'] && $ytcommentsdrevo=='enabled') { ?><?php echo '<div data-block="comments">'; ?><?php }
3340
  }
3341
 
3342
+ function yturbo_comments_end( $comment, $args, $depth ) {
3343
  $yturbo_options = get_option('yturbo_options');
3344
  $ytcommentsdrevo = $yturbo_options['ytcommentsdrevo'];
3345
  ?>
3628
  if ( ! in_array( get_post_type( get_the_ID() ), $types ) )
3629
  return $content;
3630
 
3631
+ //подключение файла с классом YTurbo_Contents begin
3632
+ require_once dirname( __FILE__ ) . '/inc/Contents.php';
3633
+ //подключение файла с классом YTurbo_Contents end
 
 
3634
 
3635
  $selectors = array();
3636
  if ($yturbo_options['yttoch1']=='enabled'){array_push($selectors, 'h1');}
3650
  'selectors' => $selectors,
3651
  );
3652
 
3653
+ $contents = YTurbo_Contents::init( $args )->make_contents( $content );
3654
 
3655
  $contents = str_replace("\n", '', $contents);
3656
  $contents = trim(preg_replace('/\t+/', '', $contents));
3657
  $contents = wpautop($contents);
 
 
 
 
 
 
 
 
3658
 
3659
  if ( $yturbo_options['yttocmesto'] == 'В начале записи' ) {
3660
  return PHP_EOL . $contents . $content;
3736
 
3737
  //добавляем плагины в визуальный редактор begin
3738
  function yturbo_add_plugins_tinymce( $plugins ) {
3739
+ $yturbo_options = get_option('yturbo_options');
3740
  $purl = plugins_url('', __FILE__);
3741
+ $plugins['yablocks'] = $purl . '/inc/yablocks.js?ver=' . $yturbo_options['version'];
3742
+ $plugins['table'] = $purl . '/inc/table.js?ver=' . $yturbo_options['version'];
3743
  return $plugins;
3744
  }
3745
  add_filter( 'mce_external_plugins', 'yturbo_add_plugins_tinymce' );
3903
  return $protected;
3904
  }
3905
  add_filter( 'is_protected_meta', 'yturbo_hide_custom_fields', 10, 2 );
3906
+ //скрываем произвольные поля плагина end
3907
+
3908
+ //функция определения доступных для удаления тегов begin
3909
+ function yturbo_tags_list() {
3910
+
3911
+ $tags_list = 'abbr,acronym,address,applet,area,article,aside,audio,base,basefont,bb,bdo,big,body,button,canvas,caption,center,cite,code,col,colgroup,command,datagrid,datalist,dd,del,details,dfn,dialog,dir,div,dl,dt,embed,eventsource,fieldset,font,footer,form,frame,frameset,head,header,hgroup,html,ins,isindex,kbd,keygen,label,legend,main,map,mark,menu,meter,nav,noframes,noscript,object,optgroup,option,output,param,pre,progress,q,rp,rt,ruby,samp,script,section,svg,select,small,span,style,time,title,tt,var,wbr,sidebar';
3912
+ $tags_list = apply_filters( 'yturbo_tags_list', $tags_list );
3913
+
3914
+ return $tags_list;
3915
+ }
3916
+ //функция определения доступных для удаления тегов end
3917
+
3918
+ //функция вывода мусорной ленты begin
3919
+ function yturbo_lenta_trash() {
3920
+ $yturbo_options = get_option('yturbo_options');
3921
+
3922
+ header('Content-Type: ' . feed_content_type('rss2') . '; charset=' . get_option('blog_charset'), true);
3923
+ echo '<?xml version="1.0" encoding="'.get_option('blog_charset').'"?'.'>'.PHP_EOL;
3924
+ ?>
3925
+ <rss
3926
+ xmlns:yandex="http://news.yandex.ru"
3927
+ xmlns:media="http://search.yahoo.com/mrss/"
3928
+ xmlns:turbo="http://turbo.yandex.ru"
3929
+ version="2.0">
3930
+ <channel>
3931
+ <turbo:cms_plugin>C125AEEC6018B4A0EF9BF40E6615DD17</turbo:cms_plugin>
3932
+ <title><?php echo stripslashes($yturbo_options['yttitle']); ?></title>
3933
+ <link><?php echo esc_html($yturbo_options['ytlink']); ?></link>
3934
+ <description><?php echo stripslashes($yturbo_options['ytdescription']); ?></description>
3935
+ <language><?php echo $yturbo_options['ytlanguage']; ?></language>
3936
+ <generator>RSS for Yandex Turbo v<?php echo $yturbo_options['version']; ?> (https://wordpress.org/plugins/rss-for-yandex-turbo/)</generator>
3937
+ <?php if ( $yturbo_options['ytexcludeurlslist'] ) {
3938
+ $textAr = explode("\n", str_replace(array("\r\n", "\r"), "\n", $yturbo_options['ytexcludeurlslist']));
3939
+ $i = 0;
3940
+ foreach ($textAr as $line) {
3941
+ $line = stripcslashes($line);
3942
+ $line = '<item turbo="false"><link>' . $line . '</link></item>' . PHP_EOL;
3943
+ if ($i > 0) echo ' ';
3944
+ echo $line;
3945
+ $i++;
3946
+ }
3947
+ } else {
3948
+ //чтобы яндекс не ругался на пустую ленту, если на удалении нет записей
3949
+ echo '<item turbo="false"><link>' . get_bloginfo_rss('url') . '/musor-page/</link></item>' . PHP_EOL;
3950
+ }
3951
+ ?>
3952
+ </channel>
3953
+ </rss>
3954
+ <?php }
3955
+ //функция вывода мусорной ленты end
3956
+
3957
+ //функция отслеживания урлов удаляемых записей begin
3958
+ function yturbo_trash_tracking( $post_id ) {
3959
+
3960
+ $yturbo_options = get_option('yturbo_options');
3961
+
3962
+ if ( $yturbo_options['ytexcludeurls'] == 'disabled' )
3963
+ return;
3964
+
3965
+ if ( $yturbo_options['ytdeltracking'] == 'disabled' )
3966
+ return;
3967
+
3968
+ $yttype = explode(",", $yturbo_options['yttype']);
3969
+ $yttype = array_diff($yttype, array(''));
3970
+
3971
+ if ( ! in_array(get_post_type($post_id), $yttype) )
3972
+ return;
3973
+
3974
+ $delpermalink = PHP_EOL . esc_url( apply_filters( 'the_permalink_rss', get_permalink($post_id) ) );
3975
+ $yturbo_options['ytexcludeurlslist'] .= $delpermalink;
3976
+ $lines = array_filter(explode("\n", trim($yturbo_options['ytexcludeurlslist'])));
3977
+ $yturbo_options['ytexcludeurlslist'] = implode("\n", $lines);
3978
+
3979
+ update_option('yturbo_options', $yturbo_options);
3980
+ }
3981
+ add_action( 'wp_trash_post', 'yturbo_trash_tracking' );
3982
+ //функция отслеживания урлов удаляемых записей end
3983
+
3984
+ //функция отслеживания урлов восстанавливаемых записей begin
3985
+ function yturbo_untrash_tracking( $post_id ) {
3986
+
3987
+ $yturbo_options = get_option('yturbo_options');
3988
+
3989
+ if ( $yturbo_options['ytexcludeurls'] == 'disabled' )
3990
+ return;
3991
+
3992
+ if ( $yturbo_options['ytdeltracking'] == 'disabled' )
3993
+ return;
3994
+
3995
+ $yttype = explode(",", $yturbo_options['yttype']);
3996
+ $yttype = array_diff($yttype, array(''));
3997
+
3998
+ if ( ! in_array(get_post_type($post_id), $yttype) )
3999
+ return;
4000
+
4001
+ $restorepermalink = esc_url( apply_filters( 'the_permalink_rss', get_permalink($post_id) ) );
4002
+ $yturbo_options['ytexcludeurlslist'] = str_replace($restorepermalink, '', $yturbo_options['ytexcludeurlslist']);
4003
+ $lines = array_filter(explode("\n", trim($yturbo_options['ytexcludeurlslist'])));
4004
+ $yturbo_options['ytexcludeurlslist'] = implode("\n", $lines);
4005
+
4006
+ update_option('yturbo_options', $yturbo_options);
4007
+ }
4008
+ add_action( 'untrashed_post', 'yturbo_untrash_tracking' );
4009
+ //функция отслеживания урлов восстанавливаемых записей end
4010
+
4011
+