Include Tools

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

当前为 2019-03-19 提交的版本,查看 最新版本

此脚本不应直接安装。它是供其他脚本使用的外部库,要使用该库请加入元指令 // @require https://update.cn-greasyfork.org/scripts/379902/681292/Include%20Tools.js

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