PageContainer.js 6.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235
  1. import { Container } from 'unstated';
  2. import loggerFactory from '@alias/logger';
  3. import * as entities from 'entities';
  4. const logger = loggerFactory('growi:services:PageContainer');
  5. /**
  6. * Service container related to Page
  7. * @extends {Container} unstated Container
  8. */
  9. export default class PageContainer extends Container {
  10. constructor(appContainer) {
  11. super();
  12. this.appContainer = appContainer;
  13. this.appContainer.registerContainer(this);
  14. const mainContent = document.querySelector('#content-main');
  15. if (mainContent == null) {
  16. logger.debug('#content-main element is not exists');
  17. return;
  18. }
  19. const revisionId = mainContent.getAttribute('data-page-revision-id');
  20. this.state = {
  21. // local page data
  22. markdown: null, // will be initialized after initStateMarkdown()
  23. pageId: mainContent.getAttribute('data-page-id'),
  24. revisionId,
  25. revisionCreatedAt: +mainContent.getAttribute('data-page-revision-created'),
  26. path: mainContent.getAttribute('data-path'),
  27. tags: [],
  28. templateTagData: mainContent.getAttribute('data-template-tags') || '',
  29. isSlackEnabled: false,
  30. slackChannels: mainContent.getAttribute('data-slack-channels') || '',
  31. grant: 1, // default: public
  32. grantGroupId: null,
  33. grantGroupName: null,
  34. // latest(on remote) information
  35. remoteRevisionId: revisionId,
  36. revisionIdHackmdSynced: mainContent.getAttribute('data-page-revision-id-hackmd-synced'),
  37. lastUpdateUsername: undefined,
  38. pageIdOnHackmd: mainContent.getAttribute('data-page-id-on-hackmd'),
  39. hasDraftOnHackmd: !!mainContent.getAttribute('data-page-has-draft-on-hackmd'),
  40. isHackmdDraftUpdatingInRealtime: false,
  41. };
  42. this.initStateMarkdown();
  43. this.initStateGrant();
  44. this.initDrafts();
  45. this.addWebSocketEventHandlers = this.addWebSocketEventHandlers.bind(this);
  46. this.addWebSocketEventHandlers();
  47. }
  48. /**
  49. * initialize state for markdown data
  50. */
  51. initStateMarkdown() {
  52. let pageContent = '';
  53. const rawText = document.getElementById('raw-text-original');
  54. if (rawText) {
  55. pageContent = rawText.innerHTML;
  56. }
  57. const markdown = entities.decodeHTML(pageContent);
  58. this.state.markdown = markdown;
  59. }
  60. /**
  61. * initialize state for page permission
  62. */
  63. initStateGrant() {
  64. const elem = document.getElementById('save-page-controls');
  65. if (elem) {
  66. this.state.grant = +elem.dataset.grant;
  67. const grantGroupId = elem.dataset.grantGroup;
  68. if (grantGroupId != null && grantGroupId.length > 0) {
  69. this.state.grantGroupId = grantGroupId;
  70. this.state.grantGroupName = elem.dataset.grantGroupName;
  71. }
  72. }
  73. }
  74. /**
  75. * initialize state for drafts
  76. */
  77. initDrafts() {
  78. this.drafts = {};
  79. // restore data from localStorage
  80. const contents = window.localStorage.drafts;
  81. if (contents != null) {
  82. try {
  83. this.drafts = JSON.parse(contents);
  84. }
  85. catch (e) {
  86. window.localStorage.removeItem('drafts');
  87. }
  88. }
  89. if (this.state.pageId == null) {
  90. const draft = this.findDraft(this.state.path);
  91. if (draft != null) {
  92. this.state.markdown = draft;
  93. }
  94. }
  95. }
  96. getCurrentOptionsToSave() {
  97. const opt = {
  98. isSlackEnabled: this.state.isSlackEnabled,
  99. slackChannels: this.state.slackChannels,
  100. grant: this.state.grant,
  101. };
  102. if (this.state.grantGroupId != null) {
  103. opt.grantUserGroupId = this.state.grantGroupId;
  104. }
  105. return opt;
  106. }
  107. setLatestRemotePageData(page, user) {
  108. this.setState({
  109. remoteRevisionId: page.revision._id,
  110. revisionIdHackmdSynced: page.revisionHackmdSynced,
  111. lastUpdateUsername: user.name,
  112. });
  113. }
  114. clearDraft(path) {
  115. delete this.drafts[path];
  116. window.localStorage.setItem('drafts', JSON.stringify(this.drafts));
  117. }
  118. clearAllDrafts() {
  119. window.localStorage.removeItem('drafts');
  120. }
  121. saveDraft(path, body) {
  122. this.drafts[path] = body;
  123. window.localStorage.setItem('drafts', JSON.stringify(this.drafts));
  124. }
  125. findDraft(path) {
  126. if (this.drafts != null && this.drafts[path]) {
  127. return this.drafts[path];
  128. }
  129. return null;
  130. }
  131. addWebSocketEventHandlers() {
  132. const pageContainer = this;
  133. const websocketContainer = this.appContainer.getContainer('WebsocketContainer');
  134. const socket = websocketContainer.getWebSocket();
  135. socket.on('page:create', (data) => {
  136. // skip if triggered myself
  137. if (data.socketClientId != null && data.socketClientId === websocketContainer.getCocketClientId()) {
  138. return;
  139. }
  140. logger.debug({ obj: data }, `websocket on 'page:create'`); // eslint-disable-line quotes
  141. // update PageStatusAlert
  142. if (data.page.path === pageContainer.state.path) {
  143. this.setLatestRemotePageData(data.page, data.user);
  144. }
  145. });
  146. socket.on('page:update', (data) => {
  147. // skip if triggered myself
  148. if (data.socketClientId != null && data.socketClientId === websocketContainer.getCocketClientId()) {
  149. return;
  150. }
  151. logger.debug({ obj: data }, `websocket on 'page:update'`); // eslint-disable-line quotes
  152. if (data.page.path === pageContainer.state.path) {
  153. // update PageStatusAlert
  154. pageContainer.setLatestRemotePageData(data.page, data.user);
  155. // update remote data
  156. const page = data.page;
  157. pageContainer.setState({
  158. remoteRevisionId: page.revision._id,
  159. revisionIdHackmdSynced: page.revisionHackmdSynced,
  160. hasDraftOnHackmd: page.hasDraftOnHackmd,
  161. });
  162. }
  163. });
  164. socket.on('page:delete', (data) => {
  165. // skip if triggered myself
  166. if (data.socketClientId != null && data.socketClientId === websocketContainer.getCocketClientId()) {
  167. return;
  168. }
  169. logger.debug({ obj: data }, `websocket on 'page:delete'`); // eslint-disable-line quotes
  170. // update PageStatusAlert
  171. if (data.page.path === pageContainer.state.path) {
  172. pageContainer.setLatestRemotePageData(data.page, data.user);
  173. }
  174. });
  175. socket.on('page:editingWithHackmd', (data) => {
  176. // skip if triggered myself
  177. if (data.socketClientId != null && data.socketClientId === websocketContainer.getCocketClientId()) {
  178. return;
  179. }
  180. logger.debug({ obj: data }, `websocket on 'page:editingWithHackmd'`); // eslint-disable-line quotes
  181. if (data.page.path === pageContainer.state.path) {
  182. pageContainer.setState({ isHackmdDraftUpdatingInRealtime: true });
  183. }
  184. });
  185. }
  186. }