customize.ts 4.0 KB

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