crowi.js 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703
  1. /* eslint-disable react/jsx-filename-extension */
  2. import { pathUtils } from 'growi-commons';
  3. const entities = require('entities');
  4. const escapeStringRegexp = require('escape-string-regexp');
  5. require('jquery.cookie');
  6. require('./thirdparty-js/waves');
  7. const Crowi = {};
  8. if (!window) {
  9. window = {};
  10. }
  11. window.Crowi = Crowi;
  12. /**
  13. * set 'data-caret-line' attribute that will be processed when 'shown.bs.tab' event fired
  14. * @param {number} line
  15. */
  16. Crowi.setCaretLineData = function(line) {
  17. const pageEditorDom = document.querySelector('#page-editor');
  18. pageEditorDom.setAttribute('data-caret-line', line);
  19. };
  20. /**
  21. * invoked when;
  22. *
  23. * 1. 'shown.bs.tab' event fired
  24. */
  25. Crowi.setCaretLineAndFocusToEditor = function() {
  26. // get 'data-caret-line' attributes
  27. const pageEditorDom = document.querySelector('#page-editor');
  28. if (pageEditorDom == null) {
  29. return;
  30. }
  31. const { appContainer } = window;
  32. const editorContainer = appContainer.getContainer('EditorContainer');
  33. const line = pageEditorDom.getAttribute('data-caret-line') || 0;
  34. editorContainer.setCaretLine(+line);
  35. // reset data-caret-line attribute
  36. pageEditorDom.removeAttribute('data-caret-line');
  37. // focus
  38. editorContainer.focusToEditor();
  39. };
  40. // original: middleware.swigFilter
  41. Crowi.userPicture = function(user) {
  42. if (!user) {
  43. return '/images/icons/user.svg';
  44. }
  45. return user.image || '/images/icons/user.svg';
  46. };
  47. Crowi.modifyScrollTop = function() {
  48. const offset = 10;
  49. const hash = window.location.hash;
  50. if (hash === '') {
  51. return;
  52. }
  53. const pageHeader = document.querySelector('#page-header');
  54. if (!pageHeader) {
  55. return;
  56. }
  57. const pageHeaderRect = pageHeader.getBoundingClientRect();
  58. const sectionHeader = Crowi.findSectionHeader(hash);
  59. if (sectionHeader === null) {
  60. return;
  61. }
  62. let timeout = 0;
  63. if (window.scrollY === 0) {
  64. timeout = 200;
  65. }
  66. setTimeout(() => {
  67. const sectionHeaderRect = sectionHeader.getBoundingClientRect();
  68. if (sectionHeaderRect.top >= pageHeaderRect.bottom) {
  69. return;
  70. }
  71. window.scrollTo(0, (window.scrollY - pageHeaderRect.height - offset));
  72. }, timeout);
  73. };
  74. Crowi.handleKeyEHandler = (event) => {
  75. // ignore when dom that has 'modal in' classes exists
  76. if (document.getElementsByClassName('modal in').length > 0) {
  77. return;
  78. }
  79. // show editor
  80. $('a[data-toggle="tab"][href="#edit"]').tab('show');
  81. event.preventDefault();
  82. };
  83. Crowi.handleKeyCHandler = (event) => {
  84. // ignore when dom that has 'modal in' classes exists
  85. if (document.getElementsByClassName('modal in').length > 0) {
  86. return;
  87. }
  88. // show modal to create a page
  89. $('#create-page').modal();
  90. event.preventDefault();
  91. };
  92. Crowi.handleKeyCtrlSlashHandler = (event) => {
  93. // show modal to create a page
  94. $('#shortcuts-modal').modal('toggle');
  95. event.preventDefault();
  96. };
  97. Crowi.initClassesByOS = function() {
  98. // add classes to cmd-key by OS
  99. const platform = navigator.platform.toLowerCase();
  100. const isMac = (platform.indexOf('mac') > -1);
  101. document.querySelectorAll('.system-version .cmd-key').forEach((element) => {
  102. if (isMac) {
  103. element.classList.add('mac');
  104. }
  105. else {
  106. element.classList.add('win');
  107. }
  108. });
  109. document.querySelectorAll('#shortcuts-modal .cmd-key').forEach((element) => {
  110. if (isMac) {
  111. element.classList.add('mac');
  112. }
  113. else {
  114. element.classList.add('win', 'key-longer');
  115. }
  116. });
  117. };
  118. Crowi.findHashFromUrl = function(url) {
  119. let match;
  120. /* eslint-disable no-cond-assign */
  121. if (match = url.match(/#(.+)$/)) {
  122. return `#${match[1]}`;
  123. }
  124. /* eslint-enable no-cond-assign */
  125. return '';
  126. };
  127. Crowi.findSectionHeader = function(hash) {
  128. if (hash.length === 0) {
  129. return;
  130. }
  131. // omit '#'
  132. const id = hash.replace('#', '');
  133. // don't use jQuery and document.querySelector
  134. // because hash may containe Base64 encoded strings
  135. const elem = document.getElementById(id);
  136. if (elem != null && elem.tagName.match(/h\d+/i)) { // match h1, h2, h3...
  137. return elem;
  138. }
  139. return null;
  140. };
  141. Crowi.unhighlightSelectedSection = function(hash) {
  142. const elem = Crowi.findSectionHeader(hash);
  143. if (elem != null) {
  144. elem.classList.remove('highlighted');
  145. }
  146. };
  147. Crowi.highlightSelectedSection = function(hash) {
  148. const elem = Crowi.findSectionHeader(hash);
  149. if (elem != null) {
  150. elem.classList.add('highlighted');
  151. }
  152. };
  153. $(() => {
  154. const appContainer = window.appContainer;
  155. const websocketContainer = appContainer.getContainer('WebsocketContainer');
  156. const config = appContainer.getConfig();
  157. const pageId = $('#content-main').data('page-id');
  158. // const revisionId = $('#content-main').data('page-revision-id');
  159. // const revisionCreatedAt = $('#content-main').data('page-revision-created');
  160. // const currentUser = $('#content-main').data('current-user');
  161. const isSeen = $('#content-main').data('page-is-seen');
  162. const pagePath = $('#content-main').data('path');
  163. const isSavedStatesOfTabChanges = config.isSavedStatesOfTabChanges;
  164. $('[data-toggle="popover"]').popover();
  165. $('[data-toggle="tooltip"]').tooltip();
  166. $('[data-tooltip-stay]').tooltip('show');
  167. $('#toggle-crowi-sidebar').click((e) => {
  168. const $body = $('body');
  169. if ($body.hasClass('aside-hidden')) {
  170. $body.removeClass('aside-hidden');
  171. $.cookie('aside-hidden', 0, { expires: 30, path: '/' });
  172. }
  173. else {
  174. $body.addClass('aside-hidden');
  175. $.cookie('aside-hidden', 1, { expires: 30, path: '/' });
  176. }
  177. return false;
  178. });
  179. if ($.cookie('aside-hidden') === 1) {
  180. $('body').addClass('aside-hidden');
  181. }
  182. $('.copy-link').on('click', function() {
  183. $(this).select();
  184. });
  185. $('#create-page').on('shown.bs.modal', (e) => {
  186. // quick hack: replace from server side rendering "date" to client side "date"
  187. const today = new Date();
  188. const month = (`0${today.getMonth() + 1}`).slice(-2);
  189. const day = (`0${today.getDate()}`).slice(-2);
  190. const dateString = `${today.getFullYear()}/${month}/${day}`;
  191. $('#create-page-today .page-today-suffix').text(`/${dateString}/`);
  192. $('#create-page-today .page-today-input2').data('prefix', `/${dateString}/`);
  193. // focus
  194. $('#create-page-today .page-today-input2').eq(0).focus();
  195. });
  196. $('#create-page-today').submit(function(e) {
  197. let prefix1 = $('input.page-today-input1', this).data('prefix');
  198. let prefix2 = $('input.page-today-input2', this).data('prefix');
  199. const input1 = $('input.page-today-input1', this).val();
  200. const input2 = $('input.page-today-input2', this).val();
  201. if (input1 === '') {
  202. prefix1 = 'メモ';
  203. }
  204. if (input2 === '') {
  205. prefix2 = prefix2.slice(0, -1);
  206. }
  207. window.location.href = `${prefix1 + input1 + prefix2 + input2}#edit`;
  208. return false;
  209. });
  210. $('#create-page-under-tree').submit(function(e) {
  211. let name = $('input', this).val();
  212. if (!name.match(/^\//)) {
  213. name = `/${name}`;
  214. }
  215. if (name.match(/.+\/$/)) {
  216. name = name.substr(0, name.length - 1);
  217. }
  218. window.location.href = `${pathUtils.encodePagePath(name)}#edit`;
  219. return false;
  220. });
  221. // rename/unportalize
  222. $('#renamePage, #unportalize').on('shown.bs.modal', (e) => {
  223. $('#renamePage #newPageName').focus();
  224. $('#renamePage .msg, #unportalize .msg').hide();
  225. });
  226. $('#renamePageForm, #unportalize-form').submit(function(e) {
  227. // create name-value map
  228. const nameValueMap = {};
  229. $(this).serializeArray().forEach((obj) => {
  230. nameValueMap[obj.name] = obj.value; // nameValueMap.new_path is renamed page path
  231. });
  232. nameValueMap.socketClientId = websocketContainer.getSocketClientId();
  233. $.ajax({
  234. type: 'POST',
  235. url: '/_api/pages.rename',
  236. data: nameValueMap,
  237. dataType: 'json',
  238. })
  239. .done((res) => {
  240. // error
  241. if (!res.ok) {
  242. const linkPath = pathUtils.normalizePath(nameValueMap.new_path);
  243. $('#renamePage .msg, #unportalize .msg').hide();
  244. $(`#renamePage .msg-${res.code}, #unportalize .msg-${res.code}`).show();
  245. $('#renamePage #linkToNewPage, #unportalize #linkToNewPage').html(`
  246. <a href="${linkPath}">${linkPath} <i class="icon-login"></i></a>
  247. `);
  248. }
  249. else {
  250. const page = res.page;
  251. window.location.href = `${page.path}?renamed=${pagePath}`;
  252. }
  253. });
  254. return false;
  255. });
  256. // duplicate
  257. $('#duplicatePage').on('shown.bs.modal', (e) => {
  258. $('#duplicatePage #duplicatePageName').focus();
  259. $('#duplicatePage .msg').hide();
  260. });
  261. $('#duplicatePageForm, #unportalize-form').submit(function(e) {
  262. // create name-value map
  263. const nameValueMap = {};
  264. $(this).serializeArray().forEach((obj) => {
  265. nameValueMap[obj.name] = obj.value; // nameValueMap.new_path is duplicated page path
  266. });
  267. nameValueMap.socketClientId = websocketContainer.getSocketClientId();
  268. $.ajax({
  269. type: 'POST',
  270. url: '/_api/pages.duplicate',
  271. data: nameValueMap,
  272. dataType: 'json',
  273. }).done((res) => {
  274. // error
  275. if (!res.ok) {
  276. const linkPath = pathUtils.normalizePath(nameValueMap.new_path);
  277. $('#duplicatePage .msg').hide();
  278. $(`#duplicatePage .msg-${res.code}`).show();
  279. $('#duplicatePage #linkToNewPage').html(`
  280. <a href="${linkPath}">${linkPath} <i class="icon-login"></i></a>
  281. `);
  282. }
  283. else {
  284. const page = res.page;
  285. window.location.href = `${page.path}?duplicated=${pagePath}`;
  286. }
  287. });
  288. return false;
  289. });
  290. // delete
  291. $('#deletePage').on('shown.bs.modal', (e) => {
  292. $('#deletePage .msg').hide();
  293. });
  294. $('#delete-page-form').submit((e) => {
  295. // create name-value map
  296. const nameValueMap = {};
  297. $('#delete-page-form').serializeArray().forEach((obj) => {
  298. nameValueMap[obj.name] = obj.value;
  299. });
  300. nameValueMap.socketClientId = websocketContainer.getSocketClientId();
  301. $.ajax({
  302. type: 'POST',
  303. url: '/_api/pages.remove',
  304. data: nameValueMap,
  305. dataType: 'json',
  306. }).done((res) => {
  307. // error
  308. if (!res.ok) {
  309. $('#deletePage .msg').hide();
  310. $(`#deletePage .msg-${res.code}`).show();
  311. }
  312. else {
  313. const page = res.page;
  314. window.location.href = page.path;
  315. }
  316. });
  317. return false;
  318. });
  319. // Put Back
  320. $('#putBackPage').on('shown.bs.modal', (e) => {
  321. $('#putBackPage .msg').hide();
  322. });
  323. $('#revert-delete-page-form').submit((e) => {
  324. $.ajax({
  325. type: 'POST',
  326. url: '/_api/pages.revertRemove',
  327. data: $('#revert-delete-page-form').serialize(),
  328. dataType: 'json',
  329. }).done((res) => {
  330. // error
  331. if (!res.ok) {
  332. $('#putBackPage .msg').hide();
  333. $(`#putBackPage .msg-${res.code}`).show();
  334. }
  335. else {
  336. const page = res.page;
  337. window.location.href = page.path;
  338. }
  339. });
  340. return false;
  341. });
  342. $('#unlink-page-form').submit((e) => {
  343. $.ajax({
  344. type: 'POST',
  345. url: '/_api/pages.unlink',
  346. data: $('#unlink-page-form').serialize(),
  347. dataType: 'json',
  348. })
  349. .done((res) => {
  350. if (!res.ok) {
  351. $('#delete-errors').html(`<i class="fa fa-times-circle"></i> ${res.error}`);
  352. $('#delete-errors').addClass('alert-danger');
  353. }
  354. else {
  355. window.location.href = `${res.path}?unlinked=true`;
  356. }
  357. });
  358. return false;
  359. });
  360. $('#create-portal-button').on('click', (e) => {
  361. $('a[data-toggle="tab"][href="#edit"]').tab('show');
  362. $('body').addClass('on-edit');
  363. $('body').addClass('builtin-editor');
  364. const path = $('.content-main').data('path');
  365. if (path !== '/' && $('.content-main').data('page-id') === '') {
  366. const upperPage = path.substr(0, path.length - 1);
  367. $.get('/_api/pages.get', { path: upperPage }, (res) => {
  368. if (res.ok && res.page) {
  369. $('#portal-warning-modal').modal('show');
  370. }
  371. });
  372. }
  373. });
  374. $('#portal-form-close').on('click', (e) => {
  375. $('#edit').removeClass('active');
  376. $('body').removeClass('on-edit');
  377. $('body').removeClass('builtin-editor');
  378. window.location.hash = '#';
  379. });
  380. /*
  381. * wrap short path with <strong></strong>
  382. */
  383. $('#view-list .page-list-ul-flat .page-list-link').each(function() {
  384. const $link = $(this);
  385. /* eslint-disable-next-line no-unused-vars */
  386. const text = $link.text();
  387. let path = decodeURIComponent($link.data('path'));
  388. const shortPath = decodeURIComponent($link.data('short-path')); // convert to string
  389. if (path == null || shortPath == null) {
  390. // continue
  391. return;
  392. }
  393. path = entities.encodeHTML(path);
  394. const pattern = `${escapeStringRegexp(entities.encodeHTML(shortPath))}(/)?$`;
  395. $link.html(path.replace(new RegExp(pattern), `<strong>${shortPath}$1</strong>`));
  396. });
  397. if (pageId) {
  398. // for Crowi Template LangProcessor
  399. $('.template-create-button', $('#revision-body')).on('click', function() {
  400. const path = $(this).data('path');
  401. const templateId = $(this).data('template');
  402. const template = $(`#${templateId}`).html();
  403. const editorContainer = appContainer.getContainer('EditorContainer');
  404. editorContainer.saveDraft(path, template);
  405. window.location.href = `${path}#edit`;
  406. });
  407. if (!isSeen) {
  408. $.post('/_api/pages.seen', { page_id: pageId }, (res) => {
  409. // ignore unless response has error
  410. if (res.ok && res.seenUser) {
  411. $('#content-main').data('page-is-seen', 1);
  412. }
  413. });
  414. }
  415. // presentation
  416. let presentaionInitialized = false;
  417. const $b = $('body');
  418. $(document).on('click', '.toggle-presentation', function(e) {
  419. const $a = $(this);
  420. e.preventDefault();
  421. $b.toggleClass('overlay-on');
  422. if (!presentaionInitialized) {
  423. presentaionInitialized = true;
  424. $('<iframe />').attr({
  425. src: $a.attr('href'),
  426. }).appendTo($('#presentation-container'));
  427. }
  428. }).on('click', '.fullscreen-layer', () => {
  429. $b.toggleClass('overlay-on');
  430. });
  431. } // end if pageId
  432. // tab changing handling
  433. $('a[href="#revision-body"]').on('show.bs.tab', () => {
  434. appContainer.setState({ editorMode: null });
  435. });
  436. $('a[href="#edit"]').on('show.bs.tab', () => {
  437. appContainer.setState({ editorMode: 'builtin' });
  438. $('body').addClass('on-edit');
  439. $('body').addClass('builtin-editor');
  440. });
  441. $('a[href="#edit"]').on('hide.bs.tab', () => {
  442. $('body').removeClass('on-edit');
  443. $('body').removeClass('builtin-editor');
  444. });
  445. $('a[href="#hackmd"]').on('show.bs.tab', () => {
  446. appContainer.setState({ editorMode: 'hackmd' });
  447. $('body').addClass('on-edit');
  448. $('body').addClass('hackmd');
  449. });
  450. $('a[href="#hackmd"]').on('hide.bs.tab', () => {
  451. $('body').removeClass('on-edit');
  452. $('body').removeClass('hackmd');
  453. });
  454. // hash handling
  455. if (isSavedStatesOfTabChanges) {
  456. $('a[data-toggle="tab"][href="#revision-history"]').on('show.bs.tab', () => {
  457. window.location.hash = '#revision-history';
  458. window.history.replaceState('', 'History', '#revision-history');
  459. });
  460. $('a[data-toggle="tab"][href="#edit"]').on('show.bs.tab', () => {
  461. window.location.hash = '#edit';
  462. window.history.replaceState('', 'Edit', '#edit');
  463. });
  464. $('a[data-toggle="tab"][href="#hackmd"]').on('show.bs.tab', () => {
  465. window.location.hash = '#hackmd';
  466. window.history.replaceState('', 'HackMD', '#hackmd');
  467. });
  468. $('a[data-toggle="tab"][href="#revision-body"]').on('show.bs.tab', () => {
  469. // couln't solve https://github.com/weseek/crowi-plus/issues/119 completely -- 2017.07.03 Yuki Takei
  470. window.location.hash = '#';
  471. window.history.replaceState('', '', window.location.href);
  472. });
  473. }
  474. else {
  475. $('a[data-toggle="tab"][href="#revision-history"]').on('show.bs.tab', () => {
  476. window.history.replaceState('', 'History', '#revision-history');
  477. });
  478. $('a[data-toggle="tab"][href="#edit"]').on('show.bs.tab', () => {
  479. window.history.replaceState('', 'Edit', '#edit');
  480. });
  481. $('a[data-toggle="tab"][href="#hackmd"]').on('show.bs.tab', () => {
  482. window.history.replaceState('', 'HackMD', '#hackmd');
  483. });
  484. $('a[data-toggle="tab"][href="#revision-body"]').on('show.bs.tab', () => {
  485. window.history.replaceState('', '', window.location.href.replace(window.location.hash, ''));
  486. });
  487. // replace all href="#edit" link behaviors
  488. $(document).on('click', 'a[href="#edit"]', () => {
  489. window.location.replace('#edit');
  490. });
  491. }
  492. // focus to editor when 'shown.bs.tab' event fired
  493. $('a[href="#edit"]').on('shown.bs.tab', (e) => {
  494. Crowi.setCaretLineAndFocusToEditor();
  495. });
  496. });
  497. window.addEventListener('load', (e) => {
  498. const { appContainer } = window;
  499. // do nothing if user is guest
  500. if (appContainer.currentUser == null) {
  501. return;
  502. }
  503. // hash on page
  504. if (window.location.hash) {
  505. if ((window.location.hash === '#edit' || window.location.hash === '#edit-form') && $('.tab-pane#edit').length > 0) {
  506. appContainer.setState({ editorMode: 'builtin' });
  507. $('a[data-toggle="tab"][href="#edit"]').tab('show');
  508. $('body').addClass('on-edit');
  509. $('body').addClass('builtin-editor');
  510. // focus
  511. Crowi.setCaretLineAndFocusToEditor();
  512. }
  513. else if (window.location.hash === '#hackmd' && $('.tab-pane#hackmd').length > 0) {
  514. appContainer.setState({ editorMode: 'hackmd' });
  515. $('a[data-toggle="tab"][href="#hackmd"]').tab('show');
  516. $('body').addClass('on-edit');
  517. $('body').addClass('hackmd');
  518. }
  519. else if (window.location.hash === '#revision-history' && $('.tab-pane#revision-history').length > 0) {
  520. $('a[data-toggle="tab"][href="#revision-history"]').tab('show');
  521. }
  522. }
  523. });
  524. window.addEventListener('load', (e) => {
  525. const crowi = window.crowi;
  526. if (crowi && crowi.users && crowi.users.length !== 0) {
  527. const totalUsers = crowi.users.length;
  528. const $listLiker = $('.page-list-liker');
  529. $listLiker.each((i, liker) => {
  530. const count = $(liker).data('count') || 0;
  531. if (count / totalUsers > 0.05) {
  532. $(liker).addClass('popular-page-high');
  533. // 5%
  534. }
  535. else if (count / totalUsers > 0.02) {
  536. $(liker).addClass('popular-page-mid');
  537. // 2%
  538. }
  539. else if (count / totalUsers > 0.005) {
  540. $(liker).addClass('popular-page-low');
  541. // 0.5%
  542. }
  543. });
  544. const $listSeer = $('.page-list-seer');
  545. $listSeer.each((i, seer) => {
  546. const count = $(seer).data('count') || 0;
  547. if (count / totalUsers > 0.10) {
  548. // 10%
  549. $(seer).addClass('popular-page-high');
  550. }
  551. else if (count / totalUsers > 0.05) {
  552. // 5%
  553. $(seer).addClass('popular-page-mid');
  554. }
  555. else if (count / totalUsers > 0.02) {
  556. // 2%
  557. $(seer).addClass('popular-page-low');
  558. }
  559. });
  560. }
  561. Crowi.highlightSelectedSection(window.location.hash);
  562. Crowi.modifyScrollTop();
  563. Crowi.initClassesByOS();
  564. });
  565. window.addEventListener('hashchange', (e) => {
  566. Crowi.unhighlightSelectedSection(Crowi.findHashFromUrl(e.oldURL));
  567. Crowi.highlightSelectedSection(Crowi.findHashFromUrl(e.newURL));
  568. Crowi.modifyScrollTop();
  569. // hash on page
  570. if (window.location.hash) {
  571. if (window.location.hash === '#edit') {
  572. $('a[data-toggle="tab"][href="#edit"]').tab('show');
  573. }
  574. else if (window.location.hash === '#hackmd') {
  575. $('a[data-toggle="tab"][href="#hackmd"]').tab('show');
  576. }
  577. else if (window.location.hash === '#revision-history') {
  578. $('a[data-toggle="tab"][href="#revision-history"]').tab('show');
  579. }
  580. }
  581. else {
  582. $('a[data-toggle="tab"][href="#revision-body"]').tab('show');
  583. }
  584. });
  585. window.addEventListener('keydown', (event) => {
  586. const target = event.target;
  587. // ignore when target dom is input
  588. const inputPattern = /^input|textinput|textarea$/i;
  589. if (inputPattern.test(target.tagName) || target.isContentEditable) {
  590. return;
  591. }
  592. switch (event.key) {
  593. case 'e':
  594. if (!event.ctrlKey && !event.metaKey && !event.altKey && !event.shiftKey) {
  595. Crowi.handleKeyEHandler(event);
  596. }
  597. break;
  598. case 'c':
  599. if (!event.ctrlKey && !event.metaKey && !event.altKey && !event.shiftKey) {
  600. Crowi.handleKeyCHandler(event);
  601. }
  602. break;
  603. case '/':
  604. if (event.ctrlKey || event.metaKey) {
  605. Crowi.handleKeyCtrlSlashHandler(event);
  606. }
  607. break;
  608. default:
  609. }
  610. });
  611. // adjust min-height of page for print temporarily
  612. window.onbeforeprint = function() {
  613. $('#page-wrapper').css('min-height', '0px');
  614. };