EditorContainer.js 5.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228
  1. import { Container } from 'unstated';
  2. import loggerFactory from '~/utils/logger';
  3. const logger = loggerFactory('growi:services:EditorContainer');
  4. /**
  5. * Service container related to options for Editor/Preview
  6. * @extends {Container} unstated Container
  7. */
  8. export default class EditorContainer extends Container {
  9. constructor(appContainer, defaultEditorOptions, defaultPreviewOptions) {
  10. super();
  11. this.appContainer = appContainer;
  12. this.appContainer.registerContainer(this);
  13. this.retrieveEditorSettings = this.retrieveEditorSettings.bind(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. this.state = {
  20. tags: null,
  21. isSlackEnabled: false,
  22. slackChannels: mainContent.getAttribute('data-slack-channels') || '',
  23. grant: 1, // default: public
  24. grantGroupId: null,
  25. grantGroupName: null,
  26. editorOptions: {},
  27. previewOptions: {},
  28. // Defaults to null to show modal when not in DB
  29. isTextlintEnabled: null,
  30. textlintRules: [],
  31. indentSize: this.appContainer.config.adminPreferredIndentSize || 4,
  32. };
  33. this.isSetBeforeunloadEventHandler = false;
  34. this.initStateGrant();
  35. this.initDrafts();
  36. this.initEditorOptions('editorOptions', 'editorOptions', defaultEditorOptions);
  37. this.initEditorOptions('previewOptions', 'previewOptions', defaultPreviewOptions);
  38. }
  39. /**
  40. * Workaround for the mangling in production build to break constructor.name
  41. */
  42. static getClassName() {
  43. return 'EditorContainer';
  44. }
  45. /**
  46. * initialize state for page permission
  47. */
  48. initStateGrant() {
  49. const mainContent = document.getElementById('content-main');
  50. if (mainContent == null) {
  51. logger.debug('#content-main element is not exists');
  52. return;
  53. }
  54. this.state.grant = +mainContent.getAttribute('data-page-grant');
  55. const grantGroupId = mainContent.getAttribute('data-page-grant-group');
  56. if (grantGroupId != null && grantGroupId.length > 0) {
  57. this.state.grantGroupId = grantGroupId;
  58. this.state.grantGroupName = mainContent.getAttribute('data-page-grant-group-name');
  59. }
  60. }
  61. /**
  62. * initialize state for drafts
  63. */
  64. initDrafts() {
  65. this.drafts = {};
  66. // restore data from localStorage
  67. const contents = window.localStorage.drafts;
  68. if (contents != null) {
  69. try {
  70. this.drafts = JSON.parse(contents);
  71. }
  72. catch (e) {
  73. window.localStorage.removeItem('drafts');
  74. }
  75. }
  76. if (this.state.pageId == null) {
  77. const draft = this.findDraft(this.state.path);
  78. if (draft != null) {
  79. this.state.markdown = draft;
  80. }
  81. }
  82. }
  83. initEditorOptions(stateKey, localStorageKey, defaultOptions) {
  84. // load from localStorage
  85. const optsStr = window.localStorage[localStorageKey];
  86. let loadedOpts = {};
  87. // JSON.parseparse
  88. if (optsStr != null) {
  89. try {
  90. loadedOpts = JSON.parse(optsStr);
  91. }
  92. catch (e) {
  93. this.localStorage.removeItem(localStorageKey);
  94. }
  95. }
  96. // set to state obj
  97. this.state[stateKey] = Object.assign(defaultOptions, loadedOpts);
  98. }
  99. saveOptsToLocalStorage() {
  100. window.localStorage.setItem('editorOptions', JSON.stringify(this.state.editorOptions));
  101. window.localStorage.setItem('previewOptions', JSON.stringify(this.state.previewOptions));
  102. }
  103. setCaretLine(line) {
  104. const pageEditor = this.appContainer.getComponentInstance('PageEditor');
  105. if (pageEditor != null) {
  106. pageEditor.setCaretLine(line);
  107. }
  108. }
  109. focusToEditor() {
  110. const pageEditor = this.appContainer.getComponentInstance('PageEditor');
  111. if (pageEditor != null) {
  112. pageEditor.focusToEditor();
  113. }
  114. }
  115. getCurrentOptionsToSave() {
  116. const opt = {
  117. isSlackEnabled: this.state.isSlackEnabled,
  118. slackChannels: this.state.slackChannels,
  119. grant: this.state.grant,
  120. pageTags: this.state.tags,
  121. };
  122. if (this.state.grantGroupId != null) {
  123. opt.grantUserGroupId = this.state.grantGroupId;
  124. }
  125. return opt;
  126. }
  127. // See https://developer.mozilla.org/en-US/docs/Web/API/WindowEventHandlers/onbeforeunload#example
  128. showUnsavedWarning(e) {
  129. // Cancel the event
  130. e.preventDefault();
  131. // display browser default message
  132. e.returnValue = '';
  133. return '';
  134. }
  135. disableUnsavedWarning() {
  136. window.removeEventListener('beforeunload', this.showUnsavedWarning);
  137. this.isSetBeforeunloadEventHandler = false;
  138. }
  139. enableUnsavedWarning() {
  140. if (!this.isSetBeforeunloadEventHandler) {
  141. window.addEventListener('beforeunload', this.showUnsavedWarning);
  142. this.isSetBeforeunloadEventHandler = true;
  143. }
  144. }
  145. clearDraft(path) {
  146. delete this.drafts[path];
  147. window.localStorage.setItem('drafts', JSON.stringify(this.drafts));
  148. }
  149. clearAllDrafts() {
  150. window.localStorage.removeItem('drafts');
  151. }
  152. saveDraft(path, body) {
  153. this.drafts[path] = body;
  154. window.localStorage.setItem('drafts', JSON.stringify(this.drafts));
  155. }
  156. findDraft(path) {
  157. if (this.drafts != null && this.drafts[path]) {
  158. return this.drafts[path];
  159. }
  160. return null;
  161. }
  162. /**
  163. * Retrieve Editor Settings
  164. */
  165. async retrieveEditorSettings() {
  166. if (this.appContainer.isGuestUser) {
  167. return;
  168. }
  169. const { data } = await this.appContainer.apiv3Get('/personal-setting/editor-settings');
  170. if (data?.textlintSettings == null) {
  171. return;
  172. }
  173. // Defaults to null to show modal when not in DB
  174. const { isTextlintEnabled = null, textlintRules = [] } = data.textlintSettings;
  175. this.setState({
  176. isTextlintEnabled,
  177. textlintRules,
  178. });
  179. }
  180. }