customize.ts 4.2 KB

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