app.js 22 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693
  1. /* eslint-disable max-len */
  2. import React from 'react';
  3. import ReactDOM from 'react-dom';
  4. import { Provider } from 'unstated';
  5. import { I18nextProvider } from 'react-i18next';
  6. import * as toastr from 'toastr';
  7. import loggerFactory from '@alias/logger';
  8. import Xss from '@commons/service/xss';
  9. import * as entities from 'entities';
  10. import i18nFactory from './i18n';
  11. import GrowiRenderer from './util/GrowiRenderer';
  12. import HeaderSearchBox from './components/HeaderSearchBox';
  13. import SearchPage from './components/SearchPage';
  14. import TagsList from './components/TagsList';
  15. import PageEditor from './components/PageEditor';
  16. // eslint-disable-next-line import/no-duplicates
  17. import OptionsSelector from './components/PageEditor/OptionsSelector';
  18. // eslint-disable-next-line import/no-duplicates
  19. import { defaultEditorOptions, defaultPreviewOptions } from './components/PageEditor/OptionsSelector';
  20. import SavePageControls from './components/SavePageControls';
  21. import PageEditorByHackmd from './components/PageEditorByHackmd';
  22. import Page from './components/Page';
  23. import PageHistory from './components/PageHistory';
  24. import PageComments from './components/PageComments';
  25. import CommentEditorLazyRenderer from './components/PageComment/CommentEditorLazyRenderer';
  26. import PageAttachment from './components/PageAttachment';
  27. import PageStatusAlert from './components/PageStatusAlert';
  28. import RevisionPath from './components/Page/RevisionPath';
  29. import TagLabels from './components/Page/TagLabels';
  30. import BookmarkButton from './components/BookmarkButton';
  31. import LikeButton from './components/LikeButton';
  32. import PagePathAutoComplete from './components/PagePathAutoComplete';
  33. import RecentCreated from './components/RecentCreated/RecentCreated';
  34. import MyDraftList from './components/MyDraftList/MyDraftList';
  35. import UserPictureList from './components/User/UserPictureList';
  36. import CustomCssEditor from './components/Admin/CustomCssEditor';
  37. import CustomScriptEditor from './components/Admin/CustomScriptEditor';
  38. import CustomHeaderEditor from './components/Admin/CustomHeaderEditor';
  39. import AdminRebuildSearch from './components/Admin/AdminRebuildSearch';
  40. import GroupDeleteModal from './components/GroupDeleteModal/GroupDeleteModal';
  41. import AppContainer from './services/AppContainer';
  42. import PageContainer from './services/PageContainer';
  43. import CommentContainer from './components/PageComment/CommentContainer';
  44. import EditorContainer from './services/EditorContainer';
  45. const logger = loggerFactory('growi:app');
  46. if (!window) {
  47. window = {};
  48. }
  49. const userlang = $('body').data('userlang');
  50. const i18n = i18nFactory(userlang);
  51. // setup xss library
  52. const xss = new Xss();
  53. window.xss = xss;
  54. const mainContent = document.querySelector('#content-main');
  55. let pageId = null;
  56. let pageRevisionId = null;
  57. let pageRevisionCreatedAt = null;
  58. let pageRevisionIdHackmdSynced = null;
  59. let hasDraftOnHackmd = false;
  60. let pageIdOnHackmd = null;
  61. let pagePath;
  62. let pageContent = '';
  63. let markdown = '';
  64. let slackChannels;
  65. let pageTags = [];
  66. let templateTagData = '';
  67. if (mainContent !== null) {
  68. pageId = mainContent.getAttribute('data-page-id') || null;
  69. pageRevisionId = mainContent.getAttribute('data-page-revision-id');
  70. pageRevisionCreatedAt = +mainContent.getAttribute('data-page-revision-created');
  71. pageRevisionIdHackmdSynced = mainContent.getAttribute('data-page-revision-id-hackmd-synced') || null;
  72. pageIdOnHackmd = mainContent.getAttribute('data-page-id-on-hackmd') || null;
  73. hasDraftOnHackmd = !!mainContent.getAttribute('data-page-has-draft-on-hackmd');
  74. pagePath = mainContent.attributes['data-path'].value;
  75. slackChannels = mainContent.getAttribute('data-slack-channels') || '';
  76. templateTagData = mainContent.getAttribute('data-template-tags') || '';
  77. const rawText = document.getElementById('raw-text-original');
  78. if (rawText) {
  79. pageContent = rawText.innerHTML;
  80. }
  81. markdown = entities.decodeHTML(pageContent);
  82. }
  83. const isLoggedin = document.querySelector('.main-container.nologin') == null;
  84. const appContainer = new AppContainer();
  85. // backward compatibility
  86. const crowi = appContainer;
  87. window.crowi = appContainer;
  88. if (isLoggedin) {
  89. appContainer.fetchUsers();
  90. }
  91. const socket = appContainer.getWebSocket();
  92. const socketClientId = appContainer.getSocketClientId();
  93. const crowiRenderer = new GrowiRenderer(crowi, null, {
  94. mode: 'page',
  95. isAutoSetup: false, // manually setup because plugins may configure it
  96. renderToc: appContainer.getCrowiForJquery().renderTocContent, // function for rendering Table Of Contents
  97. });
  98. window.crowiRenderer = crowiRenderer;
  99. // create unstated container instance
  100. const pageContainer = new PageContainer();
  101. const commentContainer = new CommentContainer(crowi, pageContainer);
  102. const editorContainer = new EditorContainer(defaultEditorOptions, defaultPreviewOptions);
  103. // FIXME
  104. const isEnabledPlugins = $('body').data('plugin-enabled');
  105. if (isEnabledPlugins) {
  106. const crowiPlugin = window.crowiPlugin;
  107. crowiPlugin.installAll(crowi, crowiRenderer);
  108. }
  109. /**
  110. * receive tags from PageTagForm
  111. * @param {Array} tagData new tags
  112. */
  113. const setTagData = function(tagData) {
  114. pageTags = tagData;
  115. };
  116. /**
  117. * component store
  118. */
  119. const componentInstances = {};
  120. /**
  121. * save success handler when reloading is not needed
  122. * @param {object} page Page instance
  123. */
  124. const saveWithShortcutSuccessHandler = function(page) {
  125. const editorMode = appContainer.getCrowiForJquery().getCurrentEditorMode();
  126. // show toastr
  127. toastr.success(undefined, 'Saved successfully', {
  128. closeButton: true,
  129. progressBar: true,
  130. newestOnTop: false,
  131. showDuration: '100',
  132. hideDuration: '100',
  133. timeOut: '1200',
  134. extendedTimeOut: '150',
  135. });
  136. pageId = page._id;
  137. pageRevisionId = page.revision._id;
  138. pageRevisionIdHackmdSynced = page.revisionHackmdSynced;
  139. // set page id to SavePageControls
  140. pageContainer.setState({ pageId });
  141. // Page component
  142. if (componentInstances.page != null) {
  143. componentInstances.page.setMarkdown(page.revision.body);
  144. }
  145. // PageEditor component
  146. if (componentInstances.pageEditor != null) {
  147. const updateEditorValue = (editorMode !== 'builtin');
  148. componentInstances.pageEditor.setMarkdown(page.revision.body, updateEditorValue);
  149. }
  150. // PageEditorByHackmd component
  151. if (componentInstances.pageEditorByHackmd != null) {
  152. // clear state of PageEditorByHackmd
  153. componentInstances.pageEditorByHackmd.clearRevisionStatus(pageRevisionId, pageRevisionIdHackmdSynced);
  154. // reset
  155. if (editorMode !== 'hackmd') {
  156. componentInstances.pageEditorByHackmd.setMarkdown(page.revision.body, false);
  157. componentInstances.pageEditorByHackmd.reset();
  158. }
  159. }
  160. // PageStatusAlert component
  161. const pageStatusAlert = componentInstances.pageStatusAlert;
  162. // clear state of PageStatusAlert
  163. if (componentInstances.pageStatusAlert != null) {
  164. pageStatusAlert.clearRevisionStatus(pageRevisionId, pageRevisionIdHackmdSynced);
  165. }
  166. // hidden input
  167. $('input[name="revision_id"]').val(pageRevisionId);
  168. };
  169. const errorHandler = function(error) {
  170. toastr.error(error.message, 'Error occured', {
  171. closeButton: true,
  172. progressBar: true,
  173. newestOnTop: false,
  174. showDuration: '100',
  175. hideDuration: '100',
  176. timeOut: '3000',
  177. });
  178. };
  179. const saveWithShortcut = function(markdown) {
  180. const editorMode = appContainer.getCrowiForJquery().getCurrentEditorMode();
  181. let revisionId = pageRevisionId;
  182. // get options
  183. const options = pageContainer.getCurrentOptionsToSave();
  184. options.socketClientId = socketClientId;
  185. options.pageTags = pageTags;
  186. if (editorMode === 'hackmd') {
  187. // set option to sync
  188. options.isSyncRevisionToHackmd = true;
  189. // use revisionId of PageEditorByHackmd
  190. revisionId = componentInstances.pageEditorByHackmd.getRevisionIdHackmdSynced();
  191. }
  192. let promise;
  193. if (pageId == null) {
  194. promise = appContainer.createPage(pagePath, markdown, options);
  195. }
  196. else {
  197. promise = appContainer.updatePage(pageId, revisionId, markdown, options);
  198. }
  199. promise
  200. .then(saveWithShortcutSuccessHandler)
  201. .catch(errorHandler);
  202. };
  203. const saveWithSubmitButtonSuccessHandler = function() {
  204. appContainer.clearDraft(pagePath);
  205. window.location.href = pagePath;
  206. };
  207. const saveWithSubmitButton = function(submitOpts) {
  208. const editorMode = appContainer.getCrowiForJquery().getCurrentEditorMode();
  209. if (editorMode == null) {
  210. // do nothing
  211. return;
  212. }
  213. let revisionId = pageRevisionId;
  214. // get options
  215. const options = pageContainer.getCurrentOptionsToSave();
  216. options.socketClientId = socketClientId;
  217. options.pageTags = pageTags;
  218. // set 'submitOpts.overwriteScopesOfDescendants' to options
  219. options.overwriteScopesOfDescendants = submitOpts ? !!submitOpts.overwriteScopesOfDescendants : false;
  220. let promise;
  221. if (editorMode === 'hackmd') {
  222. // get markdown
  223. promise = componentInstances.pageEditorByHackmd.getMarkdown();
  224. // use revisionId of PageEditorByHackmd
  225. revisionId = componentInstances.pageEditorByHackmd.getRevisionIdHackmdSynced();
  226. // set option to sync
  227. options.isSyncRevisionToHackmd = true;
  228. }
  229. else {
  230. // get markdown
  231. promise = Promise.resolve(componentInstances.pageEditor.getMarkdown());
  232. }
  233. // create or update
  234. if (pageId == null) {
  235. promise = promise.then((markdown) => {
  236. return appContainer.createPage(pagePath, markdown, options);
  237. });
  238. }
  239. else {
  240. promise = promise.then((markdown) => {
  241. return appContainer.updatePage(pageId, revisionId, markdown, options);
  242. });
  243. }
  244. promise
  245. .then(saveWithSubmitButtonSuccessHandler)
  246. .catch(errorHandler);
  247. };
  248. // setup renderer after plugins are installed
  249. crowiRenderer.setup();
  250. // restore draft when the first time to edit
  251. const draft = appContainer.findDraft(pagePath);
  252. if (!pageRevisionId && draft != null) {
  253. markdown = draft;
  254. }
  255. /**
  256. * define components
  257. * key: id of element
  258. * value: React Element
  259. */
  260. const componentMappings = {
  261. 'search-top': <I18nextProvider i18n={i18n}><HeaderSearchBox crowi={crowi} /></I18nextProvider>,
  262. 'search-sidebar': <I18nextProvider i18n={i18n}><HeaderSearchBox crowi={crowi} /></I18nextProvider>,
  263. 'search-page': <I18nextProvider i18n={i18n}><SearchPage crowi={crowi} crowiRenderer={crowiRenderer} /></I18nextProvider>,
  264. // 'revision-history': <PageHistory pageId={pageId} />,
  265. 'bookmark-button': <BookmarkButton pageId={pageId} crowi={crowi} />,
  266. 'bookmark-button-lg': <BookmarkButton pageId={pageId} crowi={crowi} size="lg" />,
  267. 'tags-page': <I18nextProvider i18n={i18n}><TagsList crowi={crowi} /></I18nextProvider>,
  268. 'create-page-name-input': <PagePathAutoComplete crowi={crowi} initializedPath={pagePath} addTrailingSlash />,
  269. 'rename-page-name-input': <PagePathAutoComplete crowi={crowi} initializedPath={pagePath} />,
  270. 'duplicate-page-name-input': <PagePathAutoComplete crowi={crowi} initializedPath={pagePath} />,
  271. };
  272. // additional definitions if data exists
  273. let pageComments = null;
  274. if (pageId) {
  275. componentMappings['page-comments-list'] = (
  276. <I18nextProvider i18n={i18n}>
  277. <Provider inject={[commentContainer, editorContainer]}>
  278. <PageComments
  279. ref={(elem) => {
  280. if (pageComments == null) {
  281. pageComments = elem;
  282. }
  283. }}
  284. revisionCreatedAt={pageRevisionCreatedAt}
  285. pageId={pageId}
  286. pagePath={pagePath}
  287. slackChannels={slackChannels}
  288. crowi={crowi}
  289. crowiOriginRenderer={crowiRenderer}
  290. revisionId={pageRevisionId}
  291. />
  292. </Provider>
  293. </I18nextProvider>
  294. );
  295. componentMappings['page-attachment'] = <PageAttachment pageId={pageId} markdown={markdown} crowi={crowi} />;
  296. }
  297. if (pagePath) {
  298. componentMappings.page = <Page crowi={crowi} crowiRenderer={crowiRenderer} markdown={markdown} pagePath={pagePath} onSaveWithShortcut={saveWithShortcut} />;
  299. componentMappings['revision-path'] = <I18nextProvider i18n={i18n}><RevisionPath pageId={pageId} pagePath={pagePath} crowi={crowi} /></I18nextProvider>;
  300. componentMappings['tag-label'] = <I18nextProvider i18n={i18n}><TagLabels crowi={crowi} pageId={pageId} sendTagData={setTagData} templateTagData={templateTagData} /></I18nextProvider>;
  301. }
  302. Object.keys(componentMappings).forEach((key) => {
  303. const elem = document.getElementById(key);
  304. if (elem) {
  305. componentInstances[key] = ReactDOM.render(componentMappings[key], elem);
  306. }
  307. });
  308. // set page if exists
  309. if (componentInstances.page != null) {
  310. appContainer.setPage(componentInstances.page);
  311. }
  312. // render LikeButton
  313. const likeButtonElem = document.getElementById('like-button');
  314. if (likeButtonElem) {
  315. const isLiked = likeButtonElem.dataset.liked === 'true';
  316. ReactDOM.render(
  317. <LikeButton crowi={crowi} pageId={pageId} isLiked={isLiked} />,
  318. likeButtonElem,
  319. );
  320. }
  321. // render UserPictureList for seen-user-list
  322. const seenUserListElem = document.getElementById('seen-user-list');
  323. if (seenUserListElem) {
  324. const userIdsStr = seenUserListElem.dataset.userIds;
  325. const userIds = userIdsStr.split(',');
  326. ReactDOM.render(
  327. <UserPictureList crowi={crowi} userIds={userIds} />,
  328. seenUserListElem,
  329. );
  330. }
  331. // render UserPictureList for liker-list
  332. const likerListElem = document.getElementById('liker-list');
  333. if (likerListElem) {
  334. const userIdsStr = likerListElem.dataset.userIds;
  335. const userIds = userIdsStr.split(',');
  336. ReactDOM.render(
  337. <UserPictureList crowi={crowi} userIds={userIds} />,
  338. likerListElem,
  339. );
  340. }
  341. // render SavePageControls
  342. let savePageControls = null;
  343. const savePageControlsElem = document.getElementById('save-page-controls');
  344. if (savePageControlsElem) {
  345. ReactDOM.render(
  346. <I18nextProvider i18n={i18n}>
  347. <Provider inject={[appContainer, pageContainer]}>
  348. <SavePageControls
  349. onSubmit={saveWithSubmitButton}
  350. ref={(elem) => {
  351. if (savePageControls == null) {
  352. savePageControls = elem;
  353. }
  354. }}
  355. />
  356. </Provider>
  357. </I18nextProvider>,
  358. savePageControlsElem,
  359. );
  360. componentInstances.savePageControls = savePageControls;
  361. }
  362. const recentCreatedControlsElem = document.getElementById('user-created-list');
  363. if (recentCreatedControlsElem) {
  364. let limit = appContainer.getConfig().recentCreatedLimit;
  365. if (limit == null) {
  366. limit = 10;
  367. }
  368. ReactDOM.render(
  369. <RecentCreated crowi={crowi} pageId={pageId} limit={limit}>
  370. </RecentCreated>, document.getElementById('user-created-list'),
  371. );
  372. }
  373. const myDraftControlsElem = document.getElementById('user-draft-list');
  374. if (myDraftControlsElem) {
  375. let limit = appContainer.getConfig().recentCreatedLimit;
  376. if (limit == null) {
  377. limit = 10;
  378. }
  379. ReactDOM.render(
  380. <I18nextProvider i18n={i18n}>
  381. <MyDraftList
  382. limit={limit}
  383. crowi={crowi}
  384. crowiOriginRenderer={crowiRenderer}
  385. />
  386. </I18nextProvider>,
  387. myDraftControlsElem,
  388. );
  389. }
  390. /*
  391. * HackMD Editor
  392. */
  393. // render PageEditorWithHackmd
  394. let pageEditorByHackmd = null;
  395. const pageEditorWithHackmdElem = document.getElementById('page-editor-with-hackmd');
  396. if (pageEditorWithHackmdElem) {
  397. pageEditorByHackmd = ReactDOM.render(
  398. <PageEditorByHackmd
  399. crowi={crowi}
  400. pageId={pageId}
  401. revisionId={pageRevisionId}
  402. pageIdOnHackmd={pageIdOnHackmd}
  403. revisionIdHackmdSynced={pageRevisionIdHackmdSynced}
  404. hasDraftOnHackmd={hasDraftOnHackmd}
  405. markdown={markdown}
  406. onSaveWithShortcut={saveWithShortcut}
  407. />,
  408. pageEditorWithHackmdElem,
  409. );
  410. componentInstances.pageEditorByHackmd = pageEditorByHackmd;
  411. }
  412. /*
  413. * PageEditor
  414. */
  415. let pageEditor = null;
  416. // render PageEditor
  417. const pageEditorElem = document.getElementById('page-editor');
  418. if (pageEditorElem) {
  419. ReactDOM.render(
  420. <I18nextProvider i18n={i18n}>
  421. <Provider inject={[editorContainer]}>
  422. <PageEditor
  423. ref={(elem) => {
  424. if (pageEditor == null) {
  425. pageEditor = elem;
  426. }
  427. }}
  428. crowi={crowi}
  429. crowiRenderer={crowiRenderer}
  430. pageId={pageId}
  431. revisionId={pageRevisionId}
  432. pagePath={pagePath}
  433. markdown={markdown}
  434. onSaveWithShortcut={saveWithShortcut}
  435. />
  436. </Provider>
  437. </I18nextProvider>,
  438. pageEditorElem,
  439. );
  440. componentInstances.pageEditor = pageEditor;
  441. // set refs for setCaretLine/forceToFocus when tab is changed
  442. appContainer.setPageEditor(pageEditor);
  443. }
  444. // render comment form
  445. const writeCommentElem = document.getElementById('page-comment-write');
  446. if (writeCommentElem) {
  447. ReactDOM.render(
  448. <Provider inject={[commentContainer, editorContainer]}>
  449. <I18nextProvider i18n={i18n}>
  450. <CommentEditorLazyRenderer
  451. crowi={crowi}
  452. crowiOriginRenderer={crowiRenderer}
  453. slackChannels={slackChannels}
  454. >
  455. </CommentEditorLazyRenderer>
  456. </I18nextProvider>
  457. </Provider>,
  458. writeCommentElem,
  459. );
  460. }
  461. // render OptionsSelector
  462. const pageEditorOptionsSelectorElem = document.getElementById('page-editor-options-selector');
  463. if (pageEditorOptionsSelectorElem) {
  464. ReactDOM.render(
  465. <I18nextProvider i18n={i18n}>
  466. <Provider inject={[editorContainer]}>
  467. <OptionsSelector crowi={crowi} />
  468. </Provider>
  469. </I18nextProvider>,
  470. pageEditorOptionsSelectorElem,
  471. );
  472. }
  473. // render PageStatusAlert
  474. let pageStatusAlert = null;
  475. const pageStatusAlertElem = document.getElementById('page-status-alert');
  476. if (pageStatusAlertElem) {
  477. ReactDOM.render(
  478. <I18nextProvider i18n={i18n}>
  479. <PageStatusAlert
  480. ref={(elem) => {
  481. if (pageStatusAlert == null) {
  482. pageStatusAlert = elem;
  483. }
  484. }}
  485. revisionId={pageRevisionId}
  486. revisionIdHackmdSynced={pageRevisionIdHackmdSynced}
  487. hasDraftOnHackmd={hasDraftOnHackmd}
  488. />
  489. </I18nextProvider>,
  490. pageStatusAlertElem,
  491. );
  492. componentInstances.pageStatusAlert = pageStatusAlert;
  493. }
  494. // render for admin
  495. const customCssEditorElem = document.getElementById('custom-css-editor');
  496. if (customCssEditorElem != null) {
  497. // get input[type=hidden] element
  498. const customCssInputElem = document.getElementById('inputCustomCss');
  499. ReactDOM.render(
  500. <CustomCssEditor inputElem={customCssInputElem} />,
  501. customCssEditorElem,
  502. );
  503. }
  504. const customScriptEditorElem = document.getElementById('custom-script-editor');
  505. if (customScriptEditorElem != null) {
  506. // get input[type=hidden] element
  507. const customScriptInputElem = document.getElementById('inputCustomScript');
  508. ReactDOM.render(
  509. <CustomScriptEditor inputElem={customScriptInputElem} />,
  510. customScriptEditorElem,
  511. );
  512. }
  513. const customHeaderEditorElem = document.getElementById('custom-header-editor');
  514. if (customHeaderEditorElem != null) {
  515. // get input[type=hidden] element
  516. const customHeaderInputElem = document.getElementById('inputCustomHeader');
  517. ReactDOM.render(
  518. <CustomHeaderEditor inputElem={customHeaderInputElem} />,
  519. customHeaderEditorElem,
  520. );
  521. }
  522. const adminRebuildSearchElem = document.getElementById('admin-rebuild-search');
  523. if (adminRebuildSearchElem != null) {
  524. ReactDOM.render(
  525. <AdminRebuildSearch crowi={crowi} />,
  526. adminRebuildSearchElem,
  527. );
  528. }
  529. const adminGrantSelectorElem = document.getElementById('admin-delete-user-group-modal');
  530. if (adminGrantSelectorElem != null) {
  531. ReactDOM.render(
  532. <I18nextProvider i18n={i18n}>
  533. <GroupDeleteModal
  534. crowi={crowi}
  535. />
  536. </I18nextProvider>,
  537. adminGrantSelectorElem,
  538. );
  539. }
  540. // notification from websocket
  541. function updatePageStatusAlert(page, user) {
  542. const pageStatusAlert = componentInstances.pageStatusAlert;
  543. if (pageStatusAlert != null) {
  544. const revisionId = page.revision._id;
  545. const revisionIdHackmdSynced = page.revisionHackmdSynced;
  546. pageStatusAlert.setRevisionId(revisionId, revisionIdHackmdSynced);
  547. pageStatusAlert.setLastUpdateUsername(user.name);
  548. }
  549. }
  550. socket.on('page:create', (data) => {
  551. // skip if triggered myself
  552. if (data.socketClientId != null && data.socketClientId === socketClientId) {
  553. return;
  554. }
  555. logger.debug({ obj: data }, `websocket on 'page:create'`); // eslint-disable-line quotes
  556. // update PageStatusAlert
  557. if (data.page.path === pagePath) {
  558. updatePageStatusAlert(data.page, data.user);
  559. }
  560. });
  561. socket.on('page:update', (data) => {
  562. // skip if triggered myself
  563. if (data.socketClientId != null && data.socketClientId === socketClientId) {
  564. return;
  565. }
  566. logger.debug({ obj: data }, `websocket on 'page:update'`); // eslint-disable-line quotes
  567. if (data.page.path === pagePath) {
  568. // update PageStatusAlert
  569. updatePageStatusAlert(data.page, data.user);
  570. // update PageEditorByHackmd
  571. const pageEditorByHackmd = componentInstances.pageEditorByHackmd;
  572. if (pageEditorByHackmd != null) {
  573. const page = data.page;
  574. pageEditorByHackmd.setRevisionId(page.revision._id, page.revisionHackmdSynced);
  575. pageEditorByHackmd.setHasDraftOnHackmd(data.page.hasDraftOnHackmd);
  576. }
  577. }
  578. });
  579. socket.on('page:delete', (data) => {
  580. // skip if triggered myself
  581. if (data.socketClientId != null && data.socketClientId === socketClientId) {
  582. return;
  583. }
  584. logger.debug({ obj: data }, `websocket on 'page:delete'`); // eslint-disable-line quotes
  585. // update PageStatusAlert
  586. if (data.page.path === pagePath) {
  587. updatePageStatusAlert(data.page, data.user);
  588. }
  589. });
  590. socket.on('page:editingWithHackmd', (data) => {
  591. // skip if triggered myself
  592. if (data.socketClientId != null && data.socketClientId === socketClientId) {
  593. return;
  594. }
  595. logger.debug({ obj: data }, `websocket on 'page:editingWithHackmd'`); // eslint-disable-line quotes
  596. if (data.page.path === pagePath) {
  597. // update PageStatusAlert
  598. const pageStatusAlert = componentInstances.pageStatusAlert;
  599. if (pageStatusAlert != null) {
  600. pageStatusAlert.setHasDraftOnHackmd(data.page.hasDraftOnHackmd);
  601. }
  602. // update PageEditorByHackmd
  603. const pageEditorByHackmd = componentInstances.pageEditorByHackmd;
  604. if (pageEditorByHackmd != null) {
  605. pageEditorByHackmd.setHasDraftOnHackmd(data.page.hasDraftOnHackmd);
  606. }
  607. }
  608. });
  609. // うわーもうー (commented by Crowi team -- 2018.03.23 Yuki Takei)
  610. $('a[data-toggle="tab"][href="#revision-history"]').on('show.bs.tab', () => {
  611. ReactDOM.render(
  612. <I18nextProvider i18n={i18n}>
  613. <PageHistory pageId={pageId} crowi={crowi} />
  614. </I18nextProvider>, document.getElementById('revision-history'),
  615. );
  616. });