AppContainer.js 6.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258
  1. import { Container } from 'unstated';
  2. import InterceptorManager from '~/services/interceptor-manager';
  3. import GrowiRenderer from '../util/GrowiRenderer';
  4. import emojiStrategy from '../util/emojione/emoji_strategy_shrinked.json';
  5. import { i18nFactory } from '../util/i18n';
  6. /**
  7. * Service container related to options for Application
  8. * @extends {Container} unstated Container
  9. */
  10. export default class AppContainer extends Container {
  11. constructor() {
  12. super();
  13. // get csrf token from body element
  14. // DO NOT REMOVE: uploading attachment data requires appContainer.csrfToken
  15. const body = document.querySelector('body');
  16. this.csrfToken = body.dataset.csrftoken;
  17. this.config = JSON.parse(document.getElementById('growi-context-hydrate').textContent || '{}');
  18. const userAgent = window.navigator.userAgent.toLowerCase();
  19. this.isMobile = /iphone|ipad|android/.test(userAgent);
  20. const currentUserElem = document.getElementById('growi-current-user');
  21. if (currentUserElem != null) {
  22. this.currentUser = JSON.parse(currentUserElem.textContent);
  23. }
  24. const isSharedPageElem = document.getElementById('is-shared-page');
  25. // check what kind of user
  26. this.isGuestUser = this.currentUser == null;
  27. this.isSharedUser = isSharedPageElem != null && this.currentUser == null;
  28. const userLocaleId = this.currentUser?.lang;
  29. this.i18n = i18nFactory(userLocaleId);
  30. this.containerInstances = {};
  31. this.componentInstances = {};
  32. this.rendererInstances = {};
  33. }
  34. /**
  35. * Workaround for the mangling in production build to break constructor.name
  36. */
  37. static getClassName() {
  38. return 'AppContainer';
  39. }
  40. initApp() {
  41. this.injectToWindow();
  42. }
  43. initContents() {
  44. const body = document.querySelector('body');
  45. this.isAdmin = body.dataset.isAdmin === 'true';
  46. this.isDocSaved = true;
  47. this.originRenderer = new GrowiRenderer(this);
  48. this.interceptorManager = new InterceptorManager();
  49. if (this.currentUser != null) {
  50. // remove old user cache
  51. this.removeOldUserCache();
  52. }
  53. const isPluginEnabled = body.dataset.pluginEnabled === 'true';
  54. if (isPluginEnabled) {
  55. this.initPlugins();
  56. }
  57. this.injectToWindow();
  58. }
  59. initPlugins() {
  60. const growiPlugin = window.growiPlugin;
  61. growiPlugin.installAll(this, this.originRenderer);
  62. }
  63. injectToWindow() {
  64. window.appContainer = this;
  65. const originRenderer = this.getOriginRenderer();
  66. window.growiRenderer = originRenderer;
  67. // backward compatibility
  68. window.crowi = this;
  69. window.crowiRenderer = originRenderer;
  70. window.crowiPlugin = window.growiPlugin;
  71. }
  72. get currentUserId() {
  73. if (this.currentUser == null) {
  74. return null;
  75. }
  76. return this.currentUser._id;
  77. }
  78. get currentUsername() {
  79. if (this.currentUser == null) {
  80. return null;
  81. }
  82. return this.currentUser.username;
  83. }
  84. /**
  85. * @return {Object} window.Crowi (js/legacy/crowi.js)
  86. */
  87. getCrowiForJquery() {
  88. return window.Crowi;
  89. }
  90. getConfig() {
  91. return this.config;
  92. }
  93. /**
  94. * Register unstated container instance
  95. * @param {object} instance unstated container instance
  96. */
  97. registerContainer(instance) {
  98. if (instance == null) {
  99. throw new Error('The specified instance must not be null');
  100. }
  101. const className = instance.constructor.getClassName();
  102. if (this.containerInstances[className] != null) {
  103. throw new Error('The specified instance couldn\'t register because the same type object has already been registered');
  104. }
  105. this.containerInstances[className] = instance;
  106. }
  107. /**
  108. * Get registered unstated container instance
  109. * !! THIS METHOD SHOULD ONLY BE USED FROM unstated CONTAINERS !!
  110. * !! From component instances, inject containers with `import { Subscribe } from 'unstated'` !!
  111. *
  112. * @param {string} className
  113. */
  114. getContainer(className) {
  115. return this.containerInstances[className];
  116. }
  117. /**
  118. * Register React component instance
  119. * @param {string} id
  120. * @param {object} instance React component instance
  121. */
  122. registerComponentInstance(id, instance) {
  123. if (instance == null) {
  124. throw new Error('The specified instance must not be null');
  125. }
  126. if (this.componentInstances[id] != null) {
  127. throw new Error('The specified instance couldn\'t register because the same id has already been registered');
  128. }
  129. this.componentInstances[id] = instance;
  130. }
  131. /**
  132. * Get registered React component instance
  133. * @param {string} id
  134. */
  135. getComponentInstance(id) {
  136. return this.componentInstances[id];
  137. }
  138. /**
  139. *
  140. * @param {string} breakpoint id of breakpoint
  141. * @param {function} handler event handler for media query
  142. * @param {boolean} invokeOnInit invoke handler after the initialization if true
  143. */
  144. addBreakpointListener(breakpoint, handler, invokeOnInit = false) {
  145. document.addEventListener('DOMContentLoaded', () => {
  146. // get the value of '--breakpoint-*'
  147. const breakpointPixel = parseInt(window.getComputedStyle(document.documentElement).getPropertyValue(`--breakpoint-${breakpoint}`), 10);
  148. const mediaQuery = window.matchMedia(`(min-width: ${breakpointPixel}px)`);
  149. // add event listener
  150. mediaQuery.addListener(handler);
  151. // initialize
  152. if (invokeOnInit) {
  153. handler(mediaQuery);
  154. }
  155. });
  156. }
  157. getOriginRenderer() {
  158. return this.originRenderer;
  159. }
  160. /**
  161. * factory method
  162. */
  163. getRenderer(mode) {
  164. if (this.rendererInstances[mode] != null) {
  165. return this.rendererInstances[mode];
  166. }
  167. const renderer = new GrowiRenderer(this, this.originRenderer);
  168. // setup
  169. renderer.initMarkdownItConfigurers(mode);
  170. renderer.setup(mode);
  171. // register
  172. this.rendererInstances[mode] = renderer;
  173. return renderer;
  174. }
  175. getEmojiStrategy() {
  176. return emojiStrategy;
  177. }
  178. removeOldUserCache() {
  179. if (window.localStorage.userByName == null) {
  180. return;
  181. }
  182. const keys = ['userByName', 'userById', 'users', 'lastFetched'];
  183. keys.forEach((key) => {
  184. window.localStorage.removeItem(key);
  185. });
  186. }
  187. launchHandsontableModal(componentKind, beginLineNumber, endLineNumber) {
  188. let targetComponent;
  189. switch (componentKind) {
  190. case 'page':
  191. targetComponent = this.getComponentInstance('Page');
  192. break;
  193. }
  194. targetComponent.launchHandsontableModal(beginLineNumber, endLineNumber);
  195. }
  196. launchDrawioModal(componentKind, beginLineNumber, endLineNumber) {
  197. let targetComponent;
  198. switch (componentKind) {
  199. case 'page':
  200. targetComponent = this.getComponentInstance('Page');
  201. break;
  202. }
  203. targetComponent.launchDrawioModal(beginLineNumber, endLineNumber);
  204. }
  205. }