customize.ts 5.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183
  1. // eslint-disable-next-line no-unused-vars
  2. import { ColorScheme, DevidedPagePath, getForcedColorScheme } from '@growi/core';
  3. import { DefaultThemeMetadata, PresetThemesMetadatas } from '@growi/preset-themes';
  4. import uglifycss from 'uglifycss';
  5. import loggerFactory from '~/utils/logger';
  6. import S2sMessage from '../models/vo/s2s-message';
  7. import ConfigManager from './config-manager';
  8. import type { IPluginService } from './plugin';
  9. import { S2sMessageHandlable } from './s2s-messaging/handlable';
  10. const logger = loggerFactory('growi:service:CustomizeService');
  11. /**
  12. * the service class of CustomizeService
  13. */
  14. class CustomizeService implements S2sMessageHandlable {
  15. configManager: ConfigManager;
  16. s2sMessagingService: any;
  17. appService: any;
  18. xssService: any;
  19. pluginService: IPluginService;
  20. lastLoadedAt?: Date;
  21. customCss?: string;
  22. customTitleTemplate!: string;
  23. theme: string;
  24. themeHref: string;
  25. forcedColorScheme?: ColorScheme;
  26. constructor(crowi) {
  27. this.configManager = crowi.configManager;
  28. this.s2sMessagingService = crowi.s2sMessagingService;
  29. this.appService = crowi.appService;
  30. this.xssService = crowi.xssService;
  31. this.pluginService = crowi.pluginService;
  32. }
  33. /**
  34. * @inheritdoc
  35. */
  36. shouldHandleS2sMessage(s2sMessage) {
  37. const { eventName, updatedAt } = s2sMessage;
  38. if (eventName !== 'customizeServiceUpdated' || updatedAt == null) {
  39. return false;
  40. }
  41. return this.lastLoadedAt == null || this.lastLoadedAt < new Date(s2sMessage.updatedAt);
  42. }
  43. /**
  44. * @inheritdoc
  45. */
  46. async handleS2sMessage(s2sMessage) {
  47. const { configManager } = this;
  48. logger.info('Reset customized value by pubsub notification');
  49. await configManager.loadConfigs();
  50. this.initCustomCss();
  51. this.initCustomTitle();
  52. this.initGrowiTheme();
  53. }
  54. async publishUpdatedMessage() {
  55. const { s2sMessagingService } = this;
  56. if (s2sMessagingService != null) {
  57. const s2sMessage = new S2sMessage('customizeServiceUpdated', { updatedAt: new Date() });
  58. try {
  59. await s2sMessagingService.publish(s2sMessage);
  60. }
  61. catch (e) {
  62. logger.error('Failed to publish update message with S2sMessagingService: ', e.message);
  63. }
  64. }
  65. }
  66. /**
  67. * initialize custom css strings
  68. */
  69. initCustomCss() {
  70. const rawCss = this.configManager.getConfig('crowi', 'customize:css') || '';
  71. // uglify and store
  72. this.customCss = uglifycss.processString(rawCss);
  73. this.lastLoadedAt = new Date();
  74. }
  75. getCustomCss() {
  76. return this.customCss;
  77. }
  78. getCustomScript() {
  79. return this.configManager.getConfig('crowi', 'customize:script');
  80. }
  81. getCustomNoscript() {
  82. return this.configManager.getConfig('crowi', 'customize:noscript');
  83. }
  84. initCustomTitle() {
  85. let configValue = this.configManager.getConfig('crowi', 'customize:title');
  86. if (configValue == null || configValue.trim().length === 0) {
  87. configValue = '{{pagename}} - {{sitename}}';
  88. }
  89. this.customTitleTemplate = configValue;
  90. this.lastLoadedAt = new Date();
  91. }
  92. generateCustomTitle(pageOrPath) {
  93. const path = pageOrPath.path || pageOrPath;
  94. const dPagePath = new DevidedPagePath(path, true, true);
  95. const customTitle = this.customTitleTemplate
  96. .replace('{{sitename}}', this.appService.getAppTitle())
  97. .replace('{{pagepath}}', path)
  98. .replace('{{page}}', dPagePath.latter) // for backward compatibility
  99. .replace('{{pagename}}', dPagePath.latter);
  100. return this.xssService.process(customTitle);
  101. }
  102. generateCustomTitleForFixedPageName(title) {
  103. // replace
  104. const customTitle = this.customTitleTemplate
  105. .replace('{{sitename}}', this.appService.getAppTitle())
  106. .replace('{{page}}', title)
  107. .replace('{{pagepath}}', title)
  108. .replace('{{pagename}}', title);
  109. return this.xssService.process(customTitle);
  110. }
  111. async initGrowiTheme(): Promise<void> {
  112. const theme = this.configManager.getConfig('crowi', 'customize:theme');
  113. this.theme = theme;
  114. const resultForThemePlugin = await this.pluginService.findThemePlugin(theme);
  115. if (resultForThemePlugin != null) {
  116. this.forcedColorScheme = getForcedColorScheme(resultForThemePlugin.themeMetadata.schemeType);
  117. this.themeHref = resultForThemePlugin.themeHref;
  118. }
  119. // retrieve preset theme
  120. else {
  121. // import preset-themes manifest
  122. const presetThemesManifest = await import('@growi/preset-themes/dist/themes/manifest.json').then(imported => imported.default);
  123. const themeMetadata = PresetThemesMetadatas.find(p => p.name === theme);
  124. this.forcedColorScheme = getForcedColorScheme(themeMetadata?.schemeType);
  125. const manifestKey = themeMetadata?.manifestKey ?? DefaultThemeMetadata.manifestKey;
  126. if (themeMetadata == null || !(themeMetadata.manifestKey in presetThemesManifest)) {
  127. logger.warn(`Use default theme because the key for '${theme} does not exist in preset-themes manifest`);
  128. }
  129. this.themeHref = `/static/preset-themes/${presetThemesManifest[manifestKey].file}`; // configured by express.static
  130. }
  131. }
  132. }
  133. module.exports = CustomizeService;