Include Tools

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

Tento skript by neměl být instalován přímo. Jedná se o knihovnu, kterou by měly jiné skripty využívat pomocí meta příkazu // @require https://update.greatest.deepsurf.us/scripts/379902/1071128/Include%20Tools.js

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