Include Tools

Общие инструменты для всех страничек

目前为 2022-07-15 提交的版本。查看 最新版本

此脚本不应直接安装,它是一个供其他脚本使用的外部库。如果您需要使用该库,请在脚本元属性加入:// @require https://update.cn-greasyfork.org/scripts/379902/1070349/Include%20Tools.js

  1. // ==UserScript==
  2. // @name Include Tools
  3. // @namespace scriptomatika
  4. // @author mouse-karaganda
  5. // @description Общие инструменты для всех страничек
  6. // @version 1.33
  7. // @grant none
  8. // ==/UserScript==
  9.  
  10. /* jshint esversion: 6 */
  11.  
  12. var paramWindow = (typeof unsafeWindow === 'object') ? unsafeWindow : window;
  13.  
  14. (function(unsafeWindow) {
  15. var console = unsafeWindow.console;
  16. var jQuery = unsafeWindow.jQuery;
  17.  
  18. unsafeWindow.__krokodil = {
  19. /**
  20. * Отрисовывает элемент
  21. */
  22. renderElement: function(config) {
  23. // Определяем параметры по умолчанию
  24. var newRenderType = this.setRenderType(config.renderType);
  25. var newConfig = {
  26. // ~~~ название тега ~~~ //
  27. tagName: config.tagName || 'div',
  28. // ~~~ атрибуты тега ~~~ //
  29. attr: config.attr || {},
  30. // ~~~ идентификатор ~~~ //
  31. id: config.id,
  32. // ~~~ имя класса ~~~ //
  33. cls: config.cls,
  34. // ~~~ встроенные стили тега ~~~ //
  35. style: config.style || {},
  36. // ~~~ встроенные данные ~~~ //
  37. dataset: config.dataset || {},
  38. // ~~~ содержимое элемента ~~~ //
  39. innerHTML: this.join(config.innerHTML || ''),
  40. // ~~~ обработчики событий элемента ~~~ //
  41. listeners: config.listeners || {},
  42. // ~~~ родительский элемент для нового ~~~ //
  43. renderTo: this.getIf(config.renderTo),
  44. // ~~~ способ отрисовки: append (по умолчанию), insertBefore, insertAfter, insertFirst, none ~~~ //
  45. renderType: newRenderType
  46. };
  47. var newElement;
  48. if (newConfig.tagName == 'text') {
  49. // Создаем текстовый узел
  50. newElement = document.createTextNode(newConfig.innerHTML);
  51. } else {
  52. // Создаем элемент
  53. newElement = document.createElement(newConfig.tagName);
  54. // Добавляем атрибуты
  55. this.attr(newElement, newConfig.attr);
  56. // Добавляем идентификатор, если указан
  57. if (newConfig.id) {
  58. this.attr(newElement, { 'id': newConfig.id });
  59. }
  60. //console.log('newElement == %o, config == %o, id == ', newElement, newConfig, newConfig.id);
  61. // Добавляем атрибут класса
  62. if (newConfig.cls) {
  63. this.attr(newElement, { 'class': newConfig.cls });
  64. }
  65. // Наполняем содержимым
  66. newElement.innerHTML = newConfig.innerHTML;
  67. // Задаем стиль
  68. this.css(newElement, newConfig.style);
  69. // Задаем встроенные данные
  70. this.extend(newElement.dataset, newConfig.dataset);
  71. // Навешиваем события
  72. var confListeners = newConfig.listeners;
  73. for (var ev in confListeners) {
  74. if (ev != 'scope') {
  75. //console.log('this.on(newElement == %o, ev == %o, newConfig.listeners[ev] == %o, newConfig.listeners.scope == %o)', newElement, ev, newConfig.listeners[ev], newConfig.listeners.scope);
  76. this.on(newElement, ev, newConfig.listeners[ev], newConfig.listeners.scope);
  77. }
  78. }
  79. //console.log('После: tag == %o, listeners == %o', newConfig.tagName, confListeners);
  80. }
  81. // Отрисовываем элемент
  82. var target, returnRender = true;
  83. while (returnRender) {
  84. switch (newConfig.renderType) {
  85. // Не отрисовывать, только создать
  86. case this.enumRenderType['none']: {
  87. returnRender = false;
  88. break;
  89. };
  90. // Вставить перед указанным
  91. case this.enumRenderType['insertBefore']: {
  92. target = newConfig.renderTo || document.body.firstChild;
  93. // если элемент не задан - вернемся к способу по умолчанию
  94. if (target) {
  95. target.parentNode.insertBefore(newElement, target);
  96. returnRender = false;
  97. } else {
  98. newConfig.renderType = this.enumRenderType['default'];
  99. }
  100. break;
  101. };
  102. // Вставить после указанного
  103. case this.enumRenderType['insertAfter']: {
  104. // если элемент не задан - вернемся к способу по умолчанию
  105. if (newConfig.renderTo && newConfig.renderTo.nextSibling) {
  106. target = newConfig.renderTo.nextSibling;
  107. target.parentNode.insertBefore(newElement, target);
  108. returnRender = false;
  109. } else {
  110. newConfig.renderType = this.enumRenderType['default'];
  111. }
  112. break;
  113. };
  114. // Вставить как первый дочерний
  115. case this.enumRenderType['prepend']: {
  116. // если элемент не задан - вернемся к способу по умолчанию
  117. if (newConfig.renderTo && newConfig.renderTo.firstChild) {
  118. target = newConfig.renderTo.firstChild;
  119. target.parentNode.insertBefore(newElement, target);
  120. returnRender = false;
  121. } else {
  122. newConfig.renderType = this.enumRenderType['default'];
  123. }
  124. break;
  125. };
  126. // Вставить как последний дочерний
  127. case this.enumRenderType['append']:
  128. default: {
  129. var parent = newConfig.renderTo || document.body;
  130. parent.appendChild(newElement);
  131. returnRender = false;
  132. };
  133. }
  134. }
  135. // Возвращаем элемент
  136. return newElement;
  137. },
  138. /**
  139. * Отрисовать несколько одинаковых элементов подряд
  140. */
  141. renderElements: function(count, config) {
  142. for (var k = 0; k < count; k++) {
  143. this.renderElement(config);
  144. }
  145. },
  146. /**
  147. * Отрисовать текстовый узел
  148. */
  149. renderText: function(config) {
  150. // Упрощенные настройки
  151. var newConfig = {
  152. tagName: 'text',
  153. innerHTML: config.text,
  154. renderTo: config.renderTo,
  155. renderType: config.renderType
  156. };
  157. var newElement = this.renderElement(newConfig);
  158. return newElement;
  159. },
  160. /**
  161. * Отрисовать элемент style
  162. * @param {String} text Любое количество строк через запятую
  163. */
  164. renderStyle: function(text) {
  165. var stringSet = arguments;
  166. var tag = this.renderElement({
  167. tagName: 'style',
  168. attr: { type: 'text/css' },
  169. innerHTML: this.format('\n\t{0}\n', this.join(stringSet, '\n\t'))
  170. });
  171. return tag;
  172. },
  173. /**
  174. * Возможные способы отрисовки
  175. */
  176. enumRenderType: {
  177. 'append': 0,
  178. 'prepend': 1,
  179. 'insertBefore': 2,
  180. 'insertAfter': 3,
  181. 'none': 4,
  182. 'default': 0
  183. },
  184. // Назначает способ отрисовки
  185. setRenderType: function(renderType) {
  186. if (typeof renderType != 'string') {
  187. return this.enumRenderType['default'];
  188. }
  189. if (this.enumRenderType[renderType] == undefined) {
  190. return this.enumRenderType['default'];
  191. }
  192. return this.enumRenderType[renderType];
  193. },
  194. /**
  195. * Карта кодов клавиш
  196. */
  197. keyMap: {
  198. // Клавиши со стрелками
  199. arrowLeft: 37,
  200. arrowUp: 38,
  201. arrowRight: 39,
  202. arrowDown: 40
  203. },
  204. /**
  205. * Карта кодов символов
  206. */
  207. charMap: {
  208. arrowLeft: 8592, // ←
  209. arrowRight: 8594 // →
  210. },
  211. /**
  212. * Ждём, пока отрисуется элемент, и выполняем действия
  213. * @param {String} selector css-селектор для поиска элемента (строго строка)
  214. * @param {Function} callback Функция, выполняющая действия над элементом. this внутри неё — искомый DOM-узел
  215. * @param {Number} maxIterCount Максимальное количество попыток найти элемент
  216. */
  217. missingElement: function(selector, callback, maxIterCount) {
  218. var setLog = false;
  219. // Итерации 5 раз в секунду
  220. var missingOne = 100;
  221. // Ограничим количество попыток разумными пределами
  222. var defaultCount = 3000;
  223. if (!this.isNumber(maxIterCount)) {
  224. maxIterCount = defaultCount;
  225. }
  226. if (0 > maxIterCount || maxIterCount > defaultCount) {
  227. maxIterCount = defaultCount;
  228. }
  229. // Запускаем таймер на поиск
  230. var iterCount = 0;
  231. var elementTimer = setInterval(this.createDelegate(function() {
  232. // Сообщение об ожидании
  233. var secondsMsg = this.numberWithCase(iterCount, 'секунду', 'секунды', 'секунд');
  234. if (iterCount % 10 == 0) {
  235. if (setLog) console.log('missing: Ждём [%o] %s', selector, secondsMsg);
  236. }
  237. var element = this.getAll(selector);
  238. // Определим, что вышел элемент
  239. var elementStop = this.isIterable(element) && (element.length > 0);
  240. // Определим, что кончилось количество попыток
  241. var iterStop = (iterCount >= maxIterCount);
  242. if (elementStop || iterStop) {
  243. clearInterval(elementTimer);
  244. var elementExists = true;
  245. // Если элемент так и не появился
  246. if (!elementStop && iterStop) {
  247. if (setLog) console.log('missing: Закончились попытки [%o]', selector);
  248. elementExists = false;
  249. return;
  250. }
  251. // Появился элемент - выполняем действия
  252. if (setLog) console.log('missing: Появился элемент [%o] == %o', selector, element);
  253. if (this.isFunction(callback)) {
  254. if (element.length == 1) {
  255. element = element[0];
  256. }
  257. if (setLog) console.log('missing: Запускаем обработчик [%o] == %o', elementExists, element);
  258. callback.call(element, elementExists);
  259. }
  260. }
  261. iterCount++;
  262. }, this), missingOne);
  263. },
  264. /**
  265. * Добавить свойства в объект
  266. */
  267. extend: function(target, newProperties) {
  268. if (typeof newProperties == 'object') {
  269. for (var i in newProperties) {
  270. target[i] = newProperties[i];
  271. }
  272. }
  273. return target;
  274. },
  275. /**
  276. * Создать класс-наследник от базового класса или объекта
  277. */
  278. inherit: function(base, newConfig) {
  279. var newProto = (typeof base == 'function') ? new base() : this.extend({}, base);
  280. this.extend(newProto, newConfig);
  281. return function() {
  282. var F = function() {};
  283. F.prototype = newProto;
  284. return new F();
  285. };
  286. },
  287. /**
  288. * Получить элемент по селектору
  289. */
  290. get: function(selector, parent) {
  291. parent = this.getIf(parent);
  292. return (parent || unsafeWindow.document).querySelector(selector);
  293. },
  294. /**
  295. * Получить массив элементов по селектору
  296. */
  297. getAll: function(selector, parent) {
  298. parent = this.getIf(parent);
  299. return (parent || unsafeWindow.document).querySelectorAll(selector);
  300. },
  301. /**
  302. * Получить элемент, если задан элемент или селектор
  303. */
  304. getIf: function(element) {
  305. return this.isString(element) ? this.get(element) : element;
  306. },
  307. /**
  308. * Получить массив элементов, если задан массив элементов или селектор
  309. */
  310. getIfAll: function(elements) {
  311. return this.isString(elements) ? this.getAll(elements) : this.toIterable(elements);
  312. },
  313. /**
  314. * Назначим атрибуты элементу или извлечем их
  315. */
  316. attr: function(element, attributes) {
  317. var nativeEl = this.getIf(element);
  318. if (typeof attributes == 'string') {
  319. // извлечем атрибут
  320. var result = '';
  321. if (nativeEl.getAttribute) {
  322. result = nativeEl.getAttribute(attributes);
  323. }
  324. if (!result) {
  325. result = '';
  326. }
  327. return result;
  328. } else if (typeof attributes == 'object') {
  329. // назначим атрибуты всем элементам по селектору
  330. nativeEl = this.getIfAll(element);
  331. for (var i = 0; i < nativeEl.length; i++) {
  332. // назначим атрибуты из списка
  333. for (var at in attributes) {
  334. try {
  335. if (attributes[at] == '') {
  336. // Удалим пустой атрибут
  337. nativeEl[i].removeAttribute(at);
  338. } else {
  339. // Запишем осмысленный атрибут
  340. nativeEl[i].setAttribute(at, attributes[at]);
  341. }
  342. } catch (e) {
  343. console.error(e);
  344. }
  345. }
  346. }
  347. }
  348. },
  349. /**
  350. * Назначим стили элементу или извлечем их
  351. */
  352. css: function(element, properties) {
  353. var nativeEl = this.getIf(element);
  354. if (typeof properties == 'string') {
  355. // извлечем стиль
  356. var result = '';
  357. if (nativeEl.style) {
  358. var calcStyle = window.getComputedStyle(nativeEl, null) || nativeEl.currentStyle;
  359. result = calcStyle[properties];
  360. }
  361. if (!result) {
  362. result = '';
  363. }
  364. return result;
  365. } else if (typeof properties == 'object') {
  366. // присвоим стили всем элементам по селектору
  367. nativeEl = this.getIfAll(element);
  368. try {
  369. for (var i = 0; i < nativeEl.length; i++) {
  370. // назначим стили из списка
  371. this.extend(nativeEl[i].style, properties);
  372. }
  373. } catch (e) {
  374. console.error(e);
  375. }
  376. }
  377. },
  378. /**
  379. * Показать элемент
  380. */
  381. show: function(element, inline) {
  382. var current = this.getIf(element);
  383. if (current) {
  384. var style = current.style;
  385. style.display = inline ? 'inline' : 'block';
  386. }
  387. },
  388. /**
  389. * Спрятать элемент
  390. */
  391. hide: function(element, soft) {
  392. var current = this.getIf(element);
  393. if (current) {
  394. if (!!soft) {
  395. current.style.visibility = 'hidden';
  396. } else {
  397. current.style.display = 'none';
  398. }
  399. }
  400. },
  401. /**
  402. * Спрятать элемент, убрав его за границу экрана
  403. */
  404. hideFixed: function(element) {
  405. var current = this.getIf(element);
  406. if (current) {
  407. this.css(current, {
  408. position: 'fixed',
  409. left: '-2000px',
  410. top: '-2000px'
  411. });
  412. }
  413. },
  414. /**
  415. * Удалить элемент
  416. */
  417. del: function(element) {
  418. var current = this.getIf(element);
  419. if (current && current.parentNode) {
  420. current.parentNode.removeChild(current);
  421. }
  422. },
  423. /**
  424. * Изменить видимость элемента
  425. */
  426. toggle: function(element, inline) {
  427. this.isVisible(element) ? this.hide(element) : this.show(element, inline);
  428. },
  429. /**
  430. * Проверить, виден ли элемент
  431. */
  432. isVisible: function(element) {
  433. return this.getIf(element).style.display != 'none';
  434. },
  435. /**
  436. * Навесить обработчик
  437. */
  438. on: function(element, eventType, handler, scope) {
  439. var elements;
  440. if (!element) {
  441. return false;
  442. }
  443. if (this.isString(element)) {
  444. element = this.getIfAll(element);
  445. if (!(element && this.isIterable(element)))
  446. return false;
  447. }
  448. if (!this.isIterable(element)) {
  449. element = this.toIterable(element);
  450. }
  451. var eventHandler = handler;
  452. if (scope) {
  453. eventHandler = this.createDelegate(handler, scope, handler.arguments);
  454. }
  455. this.each(element, function(currentEl) {
  456. if (currentEl.addEventListener) {
  457. currentEl.addEventListener(eventType, eventHandler, false);
  458. }
  459. else if (currentEl.attachEvent) {
  460. currentEl.attachEvent('on' + eventType, eventHandler);
  461. }
  462. }, this);
  463. },
  464. /**
  465. * Запустить событие
  466. */
  467. fireEvent: function(element, eventType, keys, bubbles, cancelable) {
  468. // Определим необходимые параметры
  469. var eventBubbles = this.isBoolean(bubbles) ? bubbles : true;
  470. var eventCancelable = this.isBoolean(cancelable) ? cancelable : true;
  471. // Для клика создадим MouseEvent
  472. var isMouse = /click|dblclick|mouseup|mousedown/i.test(eventType);
  473. // Приведем к нужному виду клавиши
  474. keys = keys || {};
  475. this.each(['ctrlKey', 'altKey', 'shiftKey', 'metaKey'], function(letter) {
  476. if (!keys[letter]) {
  477. keys[letter] = false;
  478. }
  479. });
  480. // запустим для всех элементов по селектору
  481. var nativeEl = this.getIfAll(element);
  482. this.each(nativeEl, function(elem) {
  483. var evt = document.createEvent(isMouse ? 'MouseEvents' : 'HTMLEvents');
  484. if (isMouse) {
  485. // Событие мыши
  486. // event.initMouseEvent(type, canBubble, cancelable, view, detail, screenX, screenY, clientX, clientY, ctrlKey, altKey, shiftKey, metaKey, button, relatedTarget);
  487. evt.initMouseEvent(eventType, eventBubbles, eventCancelable, window, 0, 0, 0, 0, 0, keys.ctrlKey, keys.altKey, keys.shiftKey, keys.metaKey, 0, null);
  488. } else {
  489. // Событие общего типа
  490. // event.initEvent(type, bubbles, cancelable);
  491. evt.initEvent(eventType, eventBubbles, eventCancelable);
  492. }
  493. //var evt = (isMouse ? new MouseEvent() : new UIEvent());
  494. elem.dispatchEvent(evt);
  495. //console.log('dispatchEvent elem == %o, event == %o', elem, evt);
  496. }, this);
  497. },
  498. /**
  499. * Остановить выполнение события
  500. */
  501. stopEvent: function(e) {
  502. var event = e || window.event;
  503. if (!event) {
  504. return false;
  505. }
  506. event.preventDefault = event.preventDefault || function() {
  507. this.returnValue = false;
  508. };
  509. event.stopPropagation = event.stopPropagation || function() {
  510. this.cancelBubble = true;
  511. };
  512. event.preventDefault();
  513. event.stopPropagation();
  514. return true;
  515. },
  516. /**
  517. * Выделить текст в поле ввода
  518. */
  519. selectText: function(element, start, end) {
  520. var current = this.getIf(element);
  521. if (!current) {
  522. return;
  523. }
  524. if (!end) {
  525. end = start;
  526. }
  527. // firefox
  528. if ('selectionStart' in element) {
  529. element.setSelectionRange(start, end);
  530. element.focus(); // to make behaviour consistent with IE
  531. }
  532. // ie win
  533. else if(document.selection) {
  534. var range = element.createTextRange();
  535. range.collapse(true);
  536. range.moveStart('character', start);
  537. range.moveEnd('character', end - start);
  538. range.select();
  539. }
  540. },
  541. /**
  542. * Определяет, является ли значение строкой
  543. */
  544. isString : function(v) {
  545. return typeof v === 'string';
  546. },
  547. /**
  548. * Определяет, является ли значение числом
  549. */
  550. isNumber: function(v) {
  551. return typeof v === 'number' && isFinite(v);
  552. },
  553. /**
  554. * Определяет, является ли значение булевым
  555. */
  556. isBoolean: function(v) {
  557. return typeof v === 'boolean';
  558. },
  559. /**
  560. * Определяет, является ли значение объектом
  561. */
  562. isObject : function(v) {
  563. return typeof v === 'object';
  564. },
  565. /**
  566. * Определяет, является ли значение функцией
  567. */
  568. isFunction: function(v) {
  569. return typeof v === 'function';
  570. },
  571. /**
  572. * Определяет, является ли значение датой
  573. */
  574. isDate: function(v) {
  575. var result = true;
  576. this.each([
  577. 'getDay',
  578. 'getMonth',
  579. 'getFullYear',
  580. 'getHours',
  581. 'getMinutes'
  582. ], function(property) {
  583. result == result && this.isFunction(v[property]);
  584. }, this);
  585. return result;
  586. },
  587. /**
  588. * Переведем число в удобочитаемый вид с пробелами
  589. */
  590. numberToString: function(v) {
  591. var partLen = 3;
  592. try {
  593. v = Number(v);
  594. } catch (e) {
  595. return v;
  596. }
  597. v = String(v);
  598. var pointPos;
  599. pointPos = (pointPos = v.indexOf('.')) > 0 ? (pointPos) : (v.length);
  600. var result = v.substring(pointPos);
  601. v = v.substr(0, pointPos);
  602. var firstPart = true;
  603. while (v.length > 0) {
  604. var startPos = v.length - partLen;
  605. if (startPos < 0) {
  606. startPos = 0;
  607. }
  608. if (!firstPart) {
  609. result = ' ' + result;
  610. }
  611. firstPart = false;
  612. result = v.substr(startPos, partLen) + result;
  613. v = v.substr(0, v.length - partLen);
  614. }
  615. return result;
  616. },
  617. /**
  618. * Число с текстом в нужном падеже
  619. * @param {Number} number Число, к которому нужно дописать текст
  620. * @param {String} textFor1 Текст для количества 1
  621. * @param {String} textFor2 Текст для количества 2
  622. * @param {String} textFor10 Текст для количества 10
  623. */
  624. numberWithCase: function(number, textFor1, textFor2, textFor10) {
  625. // Определяем, какой текст подставить, по последней цифре
  626. var lastDigit = number % 10;
  627. var result = {
  628. number: number,
  629. text: ''
  630. };
  631. // Текст для количества 1
  632. if (this.inArray(lastDigit, [ 1 ])) {
  633. result.text = textFor1;
  634. }
  635. // Текст для количества 2
  636. if (this.inArray(lastDigit, [ 2, 3, 4 ])) {
  637. result.text = textFor2;
  638. }
  639. // Текст для количества 10
  640. if (this.inArray(lastDigit, [ 5, 6, 7, 8, 9, 0 ])) {
  641. result.text = textFor10;
  642. }
  643. // Текст для количества от 11 до 19
  644. var twoLastDigits = number % 100;
  645. if (10 < twoLastDigits && twoLastDigits < 20) {
  646. result.text = textFor10;
  647. }
  648. return this.template('{number} {text}', result);
  649. },
  650. /**
  651. * Определить, является ли тип значения скалярным
  652. */
  653. isScalar: function(v) {
  654. return this.isString(v) || this.isNumber(v) || this.isBoolean(v);
  655. },
  656. /**
  657. * Определить, является ли тип значения перечислимым
  658. */
  659. isIterable: function(v) {
  660. var result = !!v;
  661. if (result) {
  662. result = result && this.isNumber(v.length);
  663. result = result && !this.isString(v);
  664. // У формы есть свойство length - пропускаем её
  665. result = result && !(v.tagName && v.tagName.toUpperCase() == 'FORM');
  666. }
  667. return result;
  668. },
  669. /**
  670. * Сделать значение перечислимым
  671. */
  672. toIterable: function(value) {
  673. if (!value) {
  674. return value;
  675. }
  676. return this.isIterable(value) ? value : [value];
  677. },
  678. /**
  679. * Задать область видимости (scope) для функции
  680. */
  681. createDelegate: function(func, scope, args) {
  682. var method = func;
  683. return function() {
  684. var callArgs = args || arguments;
  685. return method.apply(scope || window, callArgs);
  686. };
  687. },
  688. /**
  689. * Проверим, является ли значение элементом массива или объекта
  690. */
  691. inArray: function(value, array) {
  692. return this.each(array, function(key) {
  693. if (key === value) {
  694. return true;
  695. }
  696. }) !== true;
  697. },
  698. /**
  699. * Найдем значение в массиве и вернем индекс
  700. */
  701. findInArray: function(value, array) {
  702. var result = this.each(array, function(key) {
  703. if (key === value) {
  704. return true;
  705. }
  706. });
  707. return this.isNumber(result) ? result : -1;
  708. },
  709. /**
  710. * Запустить функцию для всех элементов массива или объекта
  711. * @param {Array} array Массив, в котором значения будут перебираться по индексу элемента
  712. * @param {Object} array Объект, в котором значения будут перебираться по имени поля
  713. * @returns {Number} Индекс элемента, на котором досрочно завершилось выполнение, если array - массив
  714. * @returns {String} Имя поля, на котором досрочно завершилось выполнение, если array - объект
  715. * @returns {Boolean} True, если выполнение не завершалось досрочно
  716. */
  717. each: function(array, fn, scope) {
  718. if (!array) {
  719. return;
  720. }
  721. if (this.isIterable(array)) {
  722. for (var i = 0, len = array.length; i < len; i++) {
  723. if (this.isBoolean( fn.call(scope || array[i], array[i], i, array) )) {
  724. return i;
  725. };
  726. }
  727. } else {
  728. for (var key in array) {
  729. if (this.isBoolean( fn.call(scope || array[key], array[key], key, array) )) {
  730. return key;
  731. };
  732. }
  733. }
  734. return true;
  735. },
  736. /**
  737. * Разбить строку, укоротив её и склеив части указанным разделителем
  738. * @param {String} original Исходная строка
  739. * @param {Number} maxLength Максимальная длина, до которой нужно усечь исходную строку
  740. * @param {Number} tailLength Длина второй короткой части
  741. * @param {String} glue Разделитель, который склеит две части укороченной строки
  742. */
  743. splitWithGlue: function(original, maxLength, tailLength, glue) {
  744. // Разделитель по умолчанию
  745. if (!this.isString(glue)) {
  746. glue = '...';
  747. }
  748. // По умолчанию строка завершается разделителем
  749. if (!this.isNumber(tailLength)) {
  750. tailLength = 0;
  751. }
  752. var result = original;
  753. if (result.length > maxLength) {
  754. result = this.template('{head}{glue}{tail}', {
  755. head: original.substring(0, maxLength - (tailLength + glue.length)),
  756. glue: glue,
  757. tail: original.substring(original.length - tailLength)
  758. });
  759. }
  760. return result;
  761. },
  762. /**
  763. * форматирование строки, используя объект
  764. */
  765. template: function(strTarget, objSource) {
  766. var s = arguments[0];
  767. for (var prop in objSource) {
  768. var reg = new RegExp("\\{" + prop + "\\}", "gm");
  769. s = s.replace(reg, objSource[prop]);
  770. }
  771. return s;
  772. },
  773. /**
  774. * форматирование строки, используя числовые индексы
  775. */
  776. format: function() {
  777. var original = arguments[0];
  778. this.each(arguments, function(sample, index) {
  779. if (index > 0) {
  780. var currentI = index - 1;
  781. var reg = new RegExp("\\{" + currentI + "\\}", "gm");
  782. original = original.replace(reg, sample);
  783. }
  784. });
  785. return original;
  786. },
  787. /**
  788. * Быстрый доступ к форматированию
  789. */
  790. fmt: function() {
  791. return this.format.apply(this, arguments);
  792. },
  793. /**
  794. * Выдать строку заданной длины с заполнением символом
  795. */
  796. leftPad: function (val, size, character) {
  797. var result = String(val);
  798. if (!character) {
  799. character = ' ';
  800. }
  801. while (result.length < size) {
  802. result = character + result;
  803. }
  804. return result;
  805. },
  806. /**
  807. * Определить, какая часть окна ушла вверх при прокрутке
  808. */
  809. getScrollOffset: function () {
  810. var d = unsafeWindow.top.document;
  811. return top.pageYOffset ? top.pageYOffset : (
  812. (d.documentElement && d.documentElement.scrollTop) ? (d.documentElement.scrollTop) : (d.body.scrollTop)
  813. );
  814. },
  815. /**
  816. * Определить размер окна
  817. */
  818. getWindowSize: function () {
  819. var d = unsafeWindow.top.document;
  820. return {
  821. width: /*top.innerWidth ? top.innerWidth :*/ (
  822. (d.documentElement.clientWidth) ? (d.documentElement.clientWidth) : (d.body.offsetWidth)
  823. ),
  824. height: /*top.innerHeight ? top.innerHeight :*/ (
  825. (d.documentElement.clientHeight) ? (d.documentElement.clientHeight) : (d.body.offsetHeight)
  826. )
  827. };
  828. },
  829. /**
  830. * Склеить строки
  831. */
  832. join: function(rows, glue) {
  833. return Array.prototype.slice.call(this.toIterable(rows), 0).join(glue || '');
  834. },
  835. /**
  836. * Вернем значение cookie
  837. */
  838. getCookie: function(name) {
  839. var value = null;
  840. // Проверим, есть ли кука с таким именем
  841. var cookie = unsafeWindow.document.cookie;
  842. var regKey = new RegExp(name.replace(/([\.$?*|{}\(\)\[\]\\\/\+^])/g, '\\$1') + '=(.*?)((; ?)|$)');
  843. var hasMatch = cookie.match(regKey);
  844. if (hasMatch && hasMatch[1]) {
  845. value = decodeURIComponent(hasMatch[1]);
  846. }
  847. return value;
  848. },
  849. /**
  850. * Установим значение cookie
  851. * @param {Object} options Объект с дополнительными значениями
  852. * - expires Срок действия куки: {Number} количество дней или {Data} дата окончания срока
  853. * - path Путь, отсчитывая от которого будет действовать кука
  854. * - domain Домен, в пределах которого будет действовать кука
  855. * - secure Кука для https-соединения
  856. */
  857. setCookie: function(name, value, options) {
  858. // Можно опустить значение куки, если нужно удалить
  859. if (!value) {
  860. value = '';
  861. }
  862. options = options || {};
  863. // Проверяем, задана дата или количество дней
  864. var expires = '';
  865. if (options.expires && (typeof options.expires == 'number' || options.expires.toUTCString)) {
  866. var date;
  867. if (typeof options.expires == 'number') {
  868. date = new Date();
  869. date.setTime(date.getTime() + (options.expires * 24 * 60 * 60 * 1000));
  870. } else {
  871. date = options.expires;
  872. }
  873. expires = '; expires=' + date.toUTCString();
  874. }
  875. // Проставляем другие опции
  876. var path = options.path ? '; path=' + (options.path) : '';
  877. var domain = options.domain ? '; domain=' + (options.domain) : '';
  878. var secure = (options.secure === true) ? '; secure' : '';
  879. unsafeWindow.document.cookie = [name, '=', encodeURIComponent(value), expires, path, domain, secure].join('');
  880. },
  881. /**
  882. * Картинка с большим пальцем
  883. */
  884. getThumbHand: function() {
  885. var thumbSource;
  886. thumbSource = ( // рука
  887. 'data:image/png;base64,\
  888. iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAA0UlEQVR42r3SPwsBYRzA8buSlMFi\
  889. MymDd+A1WEiSUDarwS6TwSyjgUkkfxZh4J0YpQwKk8L36R56uu5Rd1ee+izXPd/nN/xMw+cx/xXI\
  890. ooYxhm4DSbRRxAQ5N4EUmqjKKZ4YOAXmeCjfj1ICddyxwVVGxL0dep+AGK2gBA5oYPZjuoWYSheY\
  891. Iq+52EUMAWS8BHxNUJbfo9ij5XWCEl4Y6QIrpG2X4uggjIh84KQLnFHB2uH1kGHtglis7x5scVF+\
  892. uom6Ye3ByxYIoo+lGvB8fAfecvkwEbIZfswAAAAASUVORK5CYII=');
  893. thumbSource = ( // сообщение
  894. 'data:image/png;base64,\
  895. iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAABL0lEQVQ4y2P4//8/AyWYgWoGRLTv\
  896. EALipUD8E4j/48G7gFgFmwEbVx689f/3n7//8YEtJ++DDLkNxGxwA4AcHiD+8/ffv/8fvv37//LT\
  897. v//PPv77/+T9v/8P3/37f+/Nv/+3X/39f+cVxPDqBcdBhpghGyCXM/UAWPIFUOOzD//+PwZqfvD2\
  898. 3/+7UM3XX/z9f/UZxIDOVWdBBnhjNeApUPOjd1DNr//9v/USovkKUPPFJ7gNgHsB5Pz7QFvvvP77\
  899. /yZQ87Xnf/9fBmq+8ARkwB+wAbWLToAMsMQaiCBDkAHINRce/wUbjBaInLii8Q8syubuuAo36P3n\
  900. H2A+UPwy1mjEhoEK7zx/9xWm8TsQ1xKdEoGKe2duuwLS+AWIC0lKykANSkB8D4hT6JcXBswAAPeL\
  901. DyK+4moLAAAAAElFTkSuQmCC');
  902. return thumbSource;
  903. },
  904. /**
  905. * Отладка
  906. */
  907. thumb: function(text) {
  908. var bgImage = this.format('background: url("{0}") no-repeat;', this.getThumbHand());
  909. console.log('%c ', bgImage, text);
  910. },
  911. /**
  912. * Удалим значение cookie
  913. */
  914. removeCookie: function(name) {
  915. this.setCookie(name, null, { expires: -1 });
  916. },
  917. /**
  918. * Отладка
  919. */
  920. groupDir: function(name, object) {
  921. console.group(name);
  922. console.dir(object);
  923. console.groupEnd();
  924. },
  925. /**
  926. * Отладка: ошибка с пользовательским сообщением
  927. */
  928. errorist: function(error, text, parameters) {
  929. var params = Array.prototype.slice.call(arguments, 1);
  930. params.unshift('#FFEBEB');
  931. this.coloredLog(params);
  932. console.error(error);
  933. },
  934. /**
  935. * Отладка: вывод цветной строки
  936. */
  937. coloredLog: function(color, text) {
  938. var params = Array.prototype.slice.call(arguments, 2);
  939. params.unshift('background-color: ' + color + ';');
  940. params.unshift('%c' + text);
  941. console.log.apply(console, params);
  942. },
  943. /**
  944. * XPath-запрос
  945. */
  946. xpath: function(selector) {
  947. var nodes = document.evaluate(selector, document, null, XPathResult.ANY_TYPE, null);
  948. var thisNode = nodes.iterateNext();
  949. while (thisNode) {
  950. //console.log(thisNode.textContent);
  951. thisNode = nodes.iterateNext();
  952. }
  953. },
  954. /**
  955. * Упаковать для хранилища
  956. */
  957. packToStorage: function(objBox) {
  958. var clone = this.extend({}, objBox);
  959. this.each(clone, function(property, index) {
  960. if (typeof property == 'function') {
  961. clone[index] = property.toString();
  962. }
  963. if (typeof property == 'object') {
  964. clone[index] = this.packToStorage(property);
  965. }
  966. }, this);
  967. return JSON.stringify(clone);
  968. },
  969. /**
  970. * Распаковать из хранилища
  971. */
  972. unpackFromStorage: function(objBox) {
  973. var result = {};
  974. try {
  975. result = JSON.parse(objBox);
  976. } catch (e) {
  977. try {
  978. result = eval('(' + objBox + ')');
  979. } catch (e) {
  980. result = objBox;
  981. }
  982. }
  983. if (typeof result == 'object') {
  984. for (var property in result) {
  985. result[property] = this.unpackFromStorage(result[property]);
  986. }
  987. }
  988. return result;
  989. },
  990. /**
  991. * Проверить соответствие домена, как для Stylish
  992. */
  993. mozDocumentDomainIs: function() {
  994. let result = false;
  995. let domainList = Array.prototype.slice.call(arguments, 1);
  996. this.each(domainList, function(domainName, index) {
  997. let current = (document.domain == domainName) || (document.domain.substring(document.domain.indexOf(domainName) + 1) == domainName);
  998. result |= current;
  999. });
  1000. return result;
  1001. },
  1002. /**
  1003. * Проверить начало URL, как для Stylish
  1004. */
  1005. mozDocumentUrlPrefixIs: function() {
  1006. let result = false;
  1007. let prefixList = Array.prototype.slice.call(arguments, 1);
  1008. this.each(prefixList, function(prefix, index) {
  1009. let current = (document.location.href.indexOf(prefix) == 0);
  1010. result |= current;
  1011. });
  1012. return result;
  1013. }
  1014. };
  1015.  
  1016. // Добавляем обратный порядок в jQuery
  1017. if (typeof jQuery != 'undefined') {
  1018. if (typeof jQuery.fn.reverse != 'function') {
  1019. jQuery.fn.reverse = function() {
  1020. return jQuery(this.get().reverse());
  1021. };
  1022. }
  1023. if (typeof jQuery.fn.softHide != 'function') {
  1024. jQuery.fn.softHide = function() {
  1025. return jQuery(this).css({ visibility: 'hidden' });
  1026. };
  1027. }
  1028. }
  1029.  
  1030. unsafeWindow.NodeList.prototype.size = () => this.length;
  1031.  
  1032. // форматирование строки
  1033. unsafeWindow.String.prototype.format = unsafeWindow.__krokodil.format;
  1034.  
  1035. //отладка
  1036. unsafeWindow.console.groupDir = unsafeWindow.__krokodil.groupDir;
  1037. unsafeWindow.console.coloredLog = unsafeWindow.__krokodil.coloredLog;
  1038. unsafeWindow.console.errorist = unsafeWindow.__krokodil.errorist;
  1039.  
  1040. //unsafeWindow.__krokodil.thumb('Include Tools');
  1041. //console.coloredLog('#fffbd6', 'Include Tools');
  1042. //console.errorist('Include Tools');
  1043. console.log('Include Tools 💬 1.33');
  1044.  
  1045. })(paramWindow);