EditorContainer.js 5.6 KB

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