cp.scripts.js 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649
  1. // https://github.com/jackmoore/autosize
  2. (function (global, factory) {
  3. if (typeof define === "function" && define.amd) {
  4. define(['module', 'exports'], factory);
  5. } else if (typeof exports !== "undefined") {
  6. factory(module, exports);
  7. } else {
  8. var mod = {
  9. exports: {}
  10. };
  11. factory(mod, mod.exports);
  12. global.autosize = mod.exports;
  13. }
  14. })(this, function (module, exports) {
  15. 'use strict';
  16. var map = typeof Map === "function" ? new Map() : function () {
  17. var keys = [];
  18. var values = [];
  19. return {
  20. has: function has(key) {
  21. return keys.indexOf(key) > -1;
  22. },
  23. get: function get(key) {
  24. return values[keys.indexOf(key)];
  25. },
  26. set: function set(key, value) {
  27. if (keys.indexOf(key) === -1) {
  28. keys.push(key);
  29. values.push(value);
  30. }
  31. },
  32. delete: function _delete(key) {
  33. var index = keys.indexOf(key);
  34. if (index > -1) {
  35. keys.splice(index, 1);
  36. values.splice(index, 1);
  37. }
  38. }
  39. };
  40. }();
  41. var createEvent = function createEvent(name) {
  42. return new Event(name, { bubbles: true });
  43. };
  44. try {
  45. new Event('test');
  46. } catch (e) {
  47. // IE does not support 'new Event()'
  48. createEvent = function createEvent(name) {
  49. var evt = document.createEvent('Event');
  50. evt.initEvent(name, true, false);
  51. return evt;
  52. };
  53. }
  54. function assign(ta) {
  55. if (!ta || !ta.nodeName || ta.nodeName !== 'TEXTAREA' || map.has(ta)) return;
  56. var heightOffset = null;
  57. var clientWidth = null;
  58. var cachedHeight = null;
  59. function init() {
  60. var style = window.getComputedStyle(ta, null);
  61. if (style.resize === 'vertical') {
  62. ta.style.resize = 'none';
  63. } else if (style.resize === 'both') {
  64. ta.style.resize = 'horizontal';
  65. }
  66. if (style.boxSizing === 'content-box') {
  67. heightOffset = -(parseFloat(style.paddingTop) + parseFloat(style.paddingBottom));
  68. } else {
  69. heightOffset = parseFloat(style.borderTopWidth) + parseFloat(style.borderBottomWidth);
  70. }
  71. // Fix when a textarea is not on document body and heightOffset is Not a Number
  72. if (isNaN(heightOffset)) {
  73. heightOffset = 0;
  74. }
  75. update();
  76. }
  77. function changeOverflow(value) {
  78. {
  79. // Chrome/Safari-specific fix:
  80. // When the textarea y-overflow is hidden, Chrome/Safari do not reflow the text to account for the space
  81. // made available by removing the scrollbar. The following forces the necessary text reflow.
  82. var width = ta.style.width;
  83. ta.style.width = '0px';
  84. // Force reflow:
  85. /* jshint ignore:start */
  86. ta.offsetWidth;
  87. /* jshint ignore:end */
  88. ta.style.width = width;
  89. }
  90. ta.style.overflowY = value;
  91. }
  92. function getParentOverflows(el) {
  93. var arr = [];
  94. while (el && el.parentNode && el.parentNode instanceof Element) {
  95. if (el.parentNode.scrollTop) {
  96. arr.push({
  97. node: el.parentNode,
  98. scrollTop: el.parentNode.scrollTop
  99. });
  100. }
  101. el = el.parentNode;
  102. }
  103. return arr;
  104. }
  105. function resize() {
  106. if (ta.scrollHeight === 0) {
  107. // If the scrollHeight is 0, then the element probably has display:none or is detached from the DOM.
  108. return;
  109. }
  110. var overflows = getParentOverflows(ta);
  111. var docTop = document.documentElement && document.documentElement.scrollTop; // Needed for Mobile IE (ticket #240)
  112. ta.style.height = '';
  113. ta.style.height = ta.scrollHeight + heightOffset + 'px';
  114. // used to check if an update is actually necessary on window.resize
  115. clientWidth = ta.clientWidth;
  116. // prevents scroll-position jumping
  117. overflows.forEach(function (el) {
  118. el.node.scrollTop = el.scrollTop;
  119. });
  120. if (docTop) {
  121. document.documentElement.scrollTop = docTop;
  122. }
  123. }
  124. function update() {
  125. resize();
  126. var styleHeight = Math.round(parseFloat(ta.style.height));
  127. var computed = window.getComputedStyle(ta, null);
  128. // Using offsetHeight as a replacement for computed.height in IE, because IE does not account use of border-box
  129. var actualHeight = computed.boxSizing === 'content-box' ? Math.round(parseFloat(computed.height)) : ta.offsetHeight;
  130. // The actual height not matching the style height (set via the resize method) indicates that
  131. // the max-height has been exceeded, in which case the overflow should be allowed.
  132. if (actualHeight < styleHeight) {
  133. if (computed.overflowY === 'hidden') {
  134. changeOverflow('scroll');
  135. resize();
  136. actualHeight = computed.boxSizing === 'content-box' ? Math.round(parseFloat(window.getComputedStyle(ta, null).height)) : ta.offsetHeight;
  137. }
  138. } else {
  139. // Normally keep overflow set to hidden, to avoid flash of scrollbar as the textarea expands.
  140. if (computed.overflowY !== 'hidden') {
  141. changeOverflow('hidden');
  142. resize();
  143. actualHeight = computed.boxSizing === 'content-box' ? Math.round(parseFloat(window.getComputedStyle(ta, null).height)) : ta.offsetHeight;
  144. }
  145. }
  146. if (cachedHeight !== actualHeight) {
  147. cachedHeight = actualHeight;
  148. var evt = createEvent('autosize:resized');
  149. try {
  150. ta.dispatchEvent(evt);
  151. } catch (err) {
  152. // Firefox will throw an error on dispatchEvent for a detached element
  153. // https://bugzilla.mozilla.org/show_bug.cgi?id=889376
  154. }
  155. }
  156. }
  157. var pageResize = function pageResize() {
  158. if (ta.clientWidth !== clientWidth) {
  159. update();
  160. }
  161. };
  162. var destroy = function (style) {
  163. window.removeEventListener('resize', pageResize, false);
  164. ta.removeEventListener('input', update, false);
  165. ta.removeEventListener('keyup', update, false);
  166. ta.removeEventListener('autosize:destroy', destroy, false);
  167. ta.removeEventListener('autosize:update', update, false);
  168. Object.keys(style).forEach(function (key) {
  169. ta.style[key] = style[key];
  170. });
  171. map.delete(ta);
  172. }.bind(ta, {
  173. height: ta.style.height,
  174. resize: ta.style.resize,
  175. overflowY: ta.style.overflowY,
  176. overflowX: ta.style.overflowX,
  177. wordWrap: ta.style.wordWrap
  178. });
  179. ta.addEventListener('autosize:destroy', destroy, false);
  180. // IE9 does not fire onpropertychange or oninput for deletions,
  181. // so binding to onkeyup to catch most of those events.
  182. // There is no way that I know of to detect something like 'cut' in IE9.
  183. if ('onpropertychange' in ta && 'oninput' in ta) {
  184. ta.addEventListener('keyup', update, false);
  185. }
  186. window.addEventListener('resize', pageResize, false);
  187. ta.addEventListener('input', update, false);
  188. ta.addEventListener('autosize:update', update, false);
  189. ta.style.overflowX = 'hidden';
  190. ta.style.wordWrap = 'break-word';
  191. map.set(ta, {
  192. destroy: destroy,
  193. update: update
  194. });
  195. init();
  196. }
  197. function destroy(ta) {
  198. var methods = map.get(ta);
  199. if (methods) {
  200. methods.destroy();
  201. }
  202. }
  203. function update(ta) {
  204. var methods = map.get(ta);
  205. if (methods) {
  206. methods.update();
  207. }
  208. }
  209. var autosize = null;
  210. // Do nothing in Node.js environment and IE8 (or lower)
  211. if (typeof window === 'undefined' || typeof window.getComputedStyle !== 'function') {
  212. autosize = function autosize(el) {
  213. return el;
  214. };
  215. autosize.destroy = function (el) {
  216. return el;
  217. };
  218. autosize.update = function (el) {
  219. return el;
  220. };
  221. } else {
  222. autosize = function autosize(el, options) {
  223. if (el) {
  224. Array.prototype.forEach.call(el.length ? el : [el], function (x) {
  225. return assign(x, options);
  226. });
  227. }
  228. return el;
  229. };
  230. autosize.destroy = function (el) {
  231. if (el) {
  232. Array.prototype.forEach.call(el.length ? el : [el], destroy);
  233. }
  234. return el;
  235. };
  236. autosize.update = function (el) {
  237. if (el) {
  238. Array.prototype.forEach.call(el.length ? el : [el], update);
  239. }
  240. return el;
  241. };
  242. }
  243. exports.default = autosize;
  244. module.exports = exports['default'];
  245. });
  246. // ---
  247. (function(window, $) {
  248. var fave = function(window, $) {
  249. // Private
  250. var FormDataWasChanged = false;
  251. function GetModalAlertTmpl(title, message, error) {
  252. return '<div class="alert alert-' + (!error?'success':'danger') + ' alert-dismissible fade show" role="alert"><strong>' + title + '</strong> ' + message + '<button type="button" class="close" data-dismiss="alert" aria-label="Close"><span aria-hidden="true">&times;</span></button></div>';
  253. };
  254. function ShowSystemMsg(title, message, error) {
  255. var modal_alert_place = $('.modal.show .sys-messages');
  256. if(!modal_alert_place.length) {
  257. modal_alert_place = $('form.alert-here .sys-messages');
  258. }
  259. if(modal_alert_place.length) {
  260. modal_alert_place.html(GetModalAlertTmpl(title, message, error));
  261. }
  262. };
  263. function AjaxDone(data) {
  264. try {
  265. eval(data);
  266. } catch(e) {
  267. if(e instanceof SyntaxError) {
  268. console.log(data);
  269. console.log('Error: JavaScript code eval error', e.message)
  270. }
  271. }
  272. };
  273. function AjaxFail(data, status, error) {
  274. console.log('Error: data sending error, page will be reloaded', data, status, error);
  275. setTimeout(function() {
  276. window.location.reload(false);
  277. }, 1000);
  278. };
  279. function FormToAjax(form) {
  280. form.submit(function(e) {
  281. if(form.hasClass('loading')) {
  282. e.preventDefault();
  283. return;
  284. }
  285. // Block send button
  286. form.addClass('loading').addClass('alert-here');
  287. var button = form.find('button[type=submit]');
  288. button.addClass('progress-bar-striped')
  289. .addClass('progress-bar-animated');
  290. // Another button
  291. if(button.attr('data-target') != '') {
  292. $('#' + button.attr('data-target')).addClass('progress-bar-striped')
  293. .addClass('progress-bar-animated');
  294. }
  295. // Clear form messages
  296. form.find('.sys-messages').html('');
  297. $.ajax({
  298. type: "POST",
  299. url: form.attr('action'),
  300. data: form.serialize()
  301. }).done(function(data) {
  302. FormDataWasChanged = false;
  303. AjaxDone(data)
  304. }).fail(function(xhr, status, error) {
  305. AjaxFail(xhr.responseText, status, error);
  306. }).always(function() {
  307. // Add delay for one second
  308. setTimeout(function() {
  309. form.removeClass('loading').removeClass('alert-here');
  310. button.removeClass('progress-bar-striped')
  311. .removeClass('progress-bar-animated');
  312. // Another button
  313. if(button.attr('data-target') != '') {
  314. $('#' + button.attr('data-target'))
  315. .removeClass('progress-bar-striped')
  316. .removeClass('progress-bar-animated');
  317. }
  318. }, 100);
  319. });
  320. // Prevent submit action
  321. e.preventDefault();
  322. });
  323. // Bind to another button
  324. var button = form.find('button[type=submit]');
  325. if(button.attr('data-target') != '') {
  326. $('#' + button.attr('data-target')).click(function() {
  327. button.click();
  328. });
  329. }
  330. // Mark body if any data in form was changed
  331. if(form.hasClass('prev-data-lost')) {
  332. form.find('input, textarea, select').on('input', function() {
  333. if(!FormDataWasChanged) {
  334. FormDataWasChanged = true;
  335. }
  336. });
  337. }
  338. };
  339. function HtmlDecode(value) {
  340. var doc = new DOMParser().parseFromString(value, "text/html");
  341. return doc.documentElement.textContent;
  342. };
  343. function HtmlFixEditorHtml(value) {
  344. newValue = value;
  345. newValue = newValue.replace(/&nbsp;/gi, '');
  346. return newValue;
  347. };
  348. function AllFormsToAjax() {
  349. $('form').each(function() {
  350. FormToAjax($(this));
  351. });
  352. };
  353. function BindWindowBeforeUnload() {
  354. // Prevent page reload if data was changed
  355. $(window).bind('beforeunload', function(){
  356. if(FormDataWasChanged) {
  357. return 'Some data was changed and not saved. Are you sure want to leave page?';
  358. }
  359. });
  360. };
  361. function MakeTextAreasAutoSized() {
  362. autosize($('textarea.autosize'));
  363. };
  364. function MakeTextAreasWysiwyg() {
  365. $('textarea.wysiwyg').each(function() {
  366. var area = $(this)[0];
  367. var area_id = area.id;
  368. var area_name = area.name;
  369. var area_html = area.innerHTML;
  370. // Wrap editro by additional DIV and remove target textarea
  371. $(area).wrap('<div id="' + area_id + '_wysiwyg" class="form-control wysiwyg" style="height:auto;padding:0px"></div>').remove();
  372. var wysiwyg = document.getElementById(area_id + '_wysiwyg');
  373. wysiwyg.id = area_id;
  374. // Create and init editor
  375. var editor = window.pell.init({
  376. element: wysiwyg,
  377. onChange: function(html) {
  378. area.innerHTML = HtmlFixEditorHtml(html);
  379. $(area).val(HtmlFixEditorHtml(html));
  380. if(!FormDataWasChanged) {
  381. FormDataWasChanged = true;
  382. }
  383. },
  384. defaultParagraphSeparator: 'p',
  385. styleWithCSS: false,
  386. actions: [
  387. 'paragraph',
  388. 'heading1',
  389. 'heading2',
  390. 'bold',
  391. 'italic',
  392. 'underline',
  393. 'strikethrough',
  394. 'ulist',
  395. 'olist',
  396. 'link',
  397. {
  398. name: 'htmlcode',
  399. icon: 'HTML',
  400. title: 'HTML Source',
  401. result: function(edt, ctn, btn) {
  402. var jedt = $(edt);
  403. var jctn = $(ctn);
  404. var jbtn = $(btn);
  405. if(!jbtn.hasClass('pell-button-html-pressed')) {
  406. jbtn.addClass('pell-button-html-pressed');
  407. jedt.addClass('pell-html-mode');
  408. jedt.find('.pell-actionbar .pell-button').prop('disabled', true);
  409. jbtn.prop('disabled', false);
  410. setTimeout(function() {
  411. jedt.find('textarea.form-control').focus();
  412. }, 0);
  413. } else {
  414. jbtn.removeClass('pell-button-html-pressed');
  415. jedt.removeClass('pell-html-mode');
  416. jedt.find('.pell-actionbar .pell-button').prop('disabled', false);
  417. var srcValue = jedt.find('textarea.form-control').val();
  418. ctn.innerHTML = HtmlFixEditorHtml(srcValue);
  419. $(ctn).val(HtmlFixEditorHtml(srcValue));
  420. setTimeout(function() {
  421. jctn.focus();
  422. }, 0);
  423. }
  424. },
  425. },
  426. ],
  427. classes: {
  428. actionbar: 'pell-actionbar',
  429. button: 'pell-button',
  430. content: 'pell-content',
  431. selected: 'pell-button-selected'
  432. }
  433. });
  434. editor.onfocusin = function() {
  435. $(wysiwyg).addClass('focused');
  436. };
  437. editor.onfocusout = function() {
  438. $(wysiwyg).find('.pell-actionbar button.pell-button-selected').removeClass('pell-button-selected');
  439. $(wysiwyg).removeClass('focused');
  440. };
  441. // Re-add textarea
  442. $(wysiwyg).append('<textarea class="form-control" id="' + area_id + '_wysiwyg' + '" name="' + area_name + '" style="display:none"></textarea>');
  443. area = document.getElementById(area_id + '_wysiwyg');
  444. // Prevent data lost if HTML was changed
  445. $(area).on('input', function() {
  446. if(!FormDataWasChanged) {
  447. FormDataWasChanged = true;
  448. }
  449. });
  450. // Copy HTML to textarea and editor
  451. area.innerHTML = HtmlFixEditorHtml(HtmlDecode(area_html));
  452. $(area).val(HtmlFixEditorHtml(HtmlDecode(area_html)));
  453. editor.content.innerHTML = HtmlFixEditorHtml(HtmlDecode(area_html));
  454. });
  455. };
  456. function Initialize() {
  457. // Check if jQuery was loaded
  458. if(typeof $ == 'function') {
  459. AllFormsToAjax();
  460. BindWindowBeforeUnload();
  461. MakeTextAreasAutoSized();
  462. MakeTextAreasWysiwyg();
  463. } else {
  464. console.log('Error: jQuery is not loaded!');
  465. }
  466. };
  467. // Initialize
  468. if(window.addEventListener) {
  469. // W3C standard
  470. window.addEventListener('load', Initialize, false);
  471. } else if(window.attachEvent) {
  472. // Microsoft
  473. window.attachEvent('onload', Initialize);
  474. };
  475. // Public
  476. return {
  477. ShowMsgSuccess: function(title, message) {
  478. ShowSystemMsg(title, message, false);
  479. },
  480. ShowMsgError: function(title, message) {
  481. ShowSystemMsg(title, message, true);
  482. },
  483. ModalUserProfile: function() {
  484. var html = '<div class="modal fade" id="sys-modal-user-settings" tabindex="-1" role="dialog" aria-labelledby="sysModalUserSettingsLabel" aria-hidden="true"> \
  485. <div class="modal-dialog modal-dialog-centered" role="document"> \
  486. <div class="modal-content"> \
  487. <form class="form-user-settings" action="/cp/" method="post" autocomplete="off"> \
  488. <input type="hidden" name="action" value="index-user-update-profile"> \
  489. <div class="modal-header"> \
  490. <h5 class="modal-title" id="sysModalUserSettingsLabel">My profile</h5> \
  491. <button type="button" class="close" data-dismiss="modal" aria-label="Close"> \
  492. <span aria-hidden="true">&times;</span> \
  493. </button> \
  494. </div> \
  495. <div class="modal-body text-left"> \
  496. <div class="form-group"> \
  497. <label for="first_name">First name</label> \
  498. <input type="text" class="form-control" id="first_name" name="first_name" value="' + window.CurrentUserProfileData.first_name + '" placeholder="User first name" autocomplete="off"> \
  499. </div> \
  500. <div class="form-group"> \
  501. <label for="last_name">Last name</label> \
  502. <input type="text" class="form-control" id="last_name" name="last_name" value="' + window.CurrentUserProfileData.last_name + '" placeholder="User last name" autocomplete="off"> \
  503. </div> \
  504. <div class="form-group"> \
  505. <label for="email">Email</label> \
  506. <input type="email" class="form-control" id="email" name="email" value="' + window.CurrentUserProfileData.email + '" placeholder="User email" autocomplete="off" required> \
  507. </div> \
  508. <div class="form-group"> \
  509. <label for="password">New password</label> \
  510. <input type="password" class="form-control" id="password" aria-describedby="passwordHelp" name="password" value="" placeholder="User new password" autocomplete="off"> \
  511. <small id="passwordHelp" class="form-text text-muted">Leave this field empty if you don\'t want change your password</small> \
  512. </div> \
  513. <div class="sys-messages"></div> \
  514. </div> \
  515. <div class="modal-footer"> \
  516. <button type="submit" class="btn btn-primary">Save</button> \
  517. <button type="button" class="btn btn-secondary" data-dismiss="modal">Cancel</button> \
  518. </div> \
  519. </form> \
  520. </div> \
  521. </div> \
  522. </div>';
  523. $('#sys-modal-user-settings-placeholder').html(html);
  524. $("#sys-modal-user-settings").modal({
  525. backdrop: 'static',
  526. keyboard: false,
  527. show: false,
  528. });
  529. $('#sys-modal-user-settings').on('hidden.bs.modal', function(e) {
  530. $('#sys-modal-user-settings-placeholder').html('');
  531. });
  532. FormToAjax($('#sys-modal-user-settings form'));
  533. $("#sys-modal-user-settings").modal('show');
  534. },
  535. ActionLogout: function(message) {
  536. if(confirm(message)) {
  537. $.ajax({
  538. type: "POST",
  539. url: '/cp/',
  540. data: {
  541. action: 'index-user-logout',
  542. }
  543. }).done(function(data) {
  544. AjaxDone(data)
  545. }).fail(function(xhr, status, error) {
  546. AjaxFail(xhr.responseText, status, error);
  547. });
  548. }
  549. },
  550. ActionDataTableDelete: function(object, action, id, message) {
  551. if(confirm(message)) {
  552. $.ajax({
  553. type: "POST",
  554. url: '/cp/',
  555. data: {
  556. action: action,
  557. id: id,
  558. }
  559. }).done(function(data) {
  560. AjaxDone(data)
  561. }).fail(function(xhr, status, error) {
  562. AjaxFail(xhr.responseText, status, error);
  563. });
  564. }
  565. },
  566. };
  567. }(window, $);
  568. // Make it public
  569. window.fave = fave;
  570. }(window, jQuery));