Yuki Takei 3 лет назад
Родитель
Сommit
35e94dd7d1

+ 3 - 3
packages/app/src/components/Admin/Customize/CustomizeThemeOptions.tsx

@@ -9,7 +9,7 @@ import { ThemeColorBox } from './ThemeColorBox';
 type Props = {
 type Props = {
   availableThemes: GrowiThemeMetadata[],
   availableThemes: GrowiThemeMetadata[],
   selectedTheme?: string,
   selectedTheme?: string,
-  onSelected?: (themeName: string, schemeType: GrowiThemeSchemeType) => void,
+  onSelected?: (themeName: string) => void,
 };
 };
 
 
 const CustomizeThemeOptions = (props: Props): JSX.Element => {
 const CustomizeThemeOptions = (props: Props): JSX.Element => {
@@ -36,7 +36,7 @@ const CustomizeThemeOptions = (props: Props): JSX.Element => {
                 key={theme.name}
                 key={theme.name}
                 isSelected={selectedTheme != null && selectedTheme === theme.name}
                 isSelected={selectedTheme != null && selectedTheme === theme.name}
                 metadata={theme}
                 metadata={theme}
-                onSelected={() => onSelected?.(theme.name, theme.schemeType)}
+                onSelected={() => onSelected?.(theme.name)}
               />
               />
             );
             );
           })}
           })}
@@ -52,7 +52,7 @@ const CustomizeThemeOptions = (props: Props): JSX.Element => {
                 key={theme.name}
                 key={theme.name}
                 isSelected={selectedTheme != null && selectedTheme === theme.name}
                 isSelected={selectedTheme != null && selectedTheme === theme.name}
                 metadata={theme}
                 metadata={theme}
-                onSelected={() => onSelected?.(theme.name, theme.schemeType)}
+                onSelected={() => onSelected?.(theme.name)}
               />
               />
             );
             );
           })}
           })}

+ 2 - 6
packages/app/src/components/Admin/Customize/CustomizeThemeSetting.tsx

@@ -1,6 +1,5 @@
 import React, { useCallback, useEffect, useState } from 'react';
 import React, { useCallback, useEffect, useState } from 'react';
 
 
-import { GrowiThemeSchemeType } from '@growi/core';
 import { PresetThemes, PresetThemesMetadatas } from '@growi/preset-themes';
 import { PresetThemes, PresetThemesMetadatas } from '@growi/preset-themes';
 import { useTranslation } from 'next-i18next';
 import { useTranslation } from 'next-i18next';
 
 
@@ -22,15 +21,13 @@ const CustomizeThemeSetting = (props: Props): JSX.Element => {
 
 
   const { data, error, update } = useSWRxGrowiThemeSetting();
   const { data, error, update } = useSWRxGrowiThemeSetting();
   const [currentTheme, setCurrentTheme] = useState(data?.currentTheme);
   const [currentTheme, setCurrentTheme] = useState(data?.currentTheme);
-  const [currentForcedColorScheme, setCurrentForcedColorScheme] = useState(data?.currentForcedColorScheme ?? null);
 
 
   useEffect(() => {
   useEffect(() => {
     setCurrentTheme(data?.currentTheme);
     setCurrentTheme(data?.currentTheme);
   }, [data?.currentTheme]);
   }, [data?.currentTheme]);
 
 
-  const selectedHandler = useCallback((themeName: string, schemeType: GrowiThemeSchemeType) => {
+  const selectedHandler = useCallback((themeName: string) => {
     setCurrentTheme(themeName);
     setCurrentTheme(themeName);
-    setCurrentForcedColorScheme(schemeType === GrowiThemeSchemeType.BOTH ? null : schemeType);
   }, []);
   }, []);
 
 
   const submitHandler = useCallback(async() => {
   const submitHandler = useCallback(async() => {
@@ -42,7 +39,6 @@ const CustomizeThemeSetting = (props: Props): JSX.Element => {
     try {
     try {
       await update({
       await update({
         theme: currentTheme,
         theme: currentTheme,
-        forcedColorScheme: currentForcedColorScheme,
       });
       });
 
 
       toastSuccess(t('toaster.update_successed', { target: t('admin:customize_settings.theme'), ns: 'commons' }));
       toastSuccess(t('toaster.update_successed', { target: t('admin:customize_settings.theme'), ns: 'commons' }));
@@ -50,7 +46,7 @@ const CustomizeThemeSetting = (props: Props): JSX.Element => {
     catch (err) {
     catch (err) {
       toastError(err);
       toastError(err);
     }
     }
-  }, [currentForcedColorScheme, currentTheme, t, update]);
+  }, [currentTheme, t, update]);
 
 
   const availableThemes = data?.pluginThemesMetadatas == null
   const availableThemes = data?.pluginThemesMetadatas == null
     ? PresetThemesMetadatas
     ? PresetThemesMetadatas

+ 1 - 2
packages/app/src/interfaces/customize.ts

@@ -1,4 +1,4 @@
-import type { ColorScheme, GrowiThemeMetadata } from '@growi/core';
+import type { GrowiThemeMetadata } from '@growi/core';
 
 
 export type IResLayoutSetting = {
 export type IResLayoutSetting = {
   isContainerFluid: boolean,
   isContainerFluid: boolean,
@@ -6,6 +6,5 @@ export type IResLayoutSetting = {
 
 
 export type IResGrowiTheme = {
 export type IResGrowiTheme = {
   currentTheme: string,
   currentTheme: string,
-  currentForcedColorScheme: ColorScheme,
   pluginThemesMetadatas: GrowiThemeMetadata[],
   pluginThemesMetadatas: GrowiThemeMetadata[],
 }
 }

+ 6 - 51
packages/app/src/pages/_document.page.tsx

@@ -1,8 +1,6 @@
 /* eslint-disable @next/next/google-font-display */
 /* eslint-disable @next/next/google-font-display */
 import React from 'react';
 import React from 'react';
 
 
-import type { ViteManifest } from '@growi/core';
-import { DefaultThemeMetadata, PresetThemesMetadatas } from '@growi/preset-themes';
 import Document, {
 import Document, {
   DocumentContext, DocumentInitialProps,
   DocumentContext, DocumentInitialProps,
   Html, Head, Main, NextScript,
   Html, Head, Main, NextScript,
@@ -14,40 +12,6 @@ import loggerFactory from '~/utils/logger';
 
 
 const logger = loggerFactory('growi:page:_document');
 const logger = loggerFactory('growi:page:_document');
 
 
-type HeadersForThemesProps = {
-  theme: string,
-  presetThemesManifest: ViteManifest,
-  pluginThemeHref: string | undefined,
-}
-const HeadersForThemes = (props: HeadersForThemesProps): JSX.Element => {
-  const {
-    theme, presetThemesManifest, pluginThemeHref,
-  } = props;
-
-  const elements: JSX.Element[] = [];
-
-  // when plugin theme is specified
-  if (pluginThemeHref != null) {
-    elements.push(
-      <link rel="stylesheet" key={`link_custom-themes-${theme}`} href={pluginThemeHref} />,
-    );
-  }
-  // preset theme
-  else {
-    const themeMetadata = PresetThemesMetadatas.find(p => p.name === theme);
-    const manifestKey = themeMetadata?.manifestKey ?? DefaultThemeMetadata.manifestKey;
-    if (themeMetadata == null || !(themeMetadata.manifestKey in presetThemesManifest)) {
-      logger.warn(`Use default theme because the key for '${theme} does not exist in preset-themes manifest`);
-    }
-    const href = `/static/preset-themes/${presetThemesManifest[manifestKey].file}`; // configured by express.static
-    elements.push(
-      <link rel="stylesheet" key={`link_preset-themes-${theme}`} href={href} />,
-    );
-  }
-
-  return <>{elements}</>;
-};
-
 type HeadersForGrowiPluginProps = {
 type HeadersForGrowiPluginProps = {
   pluginResourceEntries: GrowiPluginResourceEntries;
   pluginResourceEntries: GrowiPluginResourceEntries;
 }
 }
@@ -72,12 +36,10 @@ const HeadersForGrowiPlugin = (props: HeadersForGrowiPluginProps): JSX.Element =
 };
 };
 
 
 interface GrowiDocumentProps {
 interface GrowiDocumentProps {
-  theme: string,
+  themeHref: string,
   customScript: string | null,
   customScript: string | null,
   customCss: string | null,
   customCss: string | null,
   customNoscript: string | null,
   customNoscript: string | null,
-  presetThemesManifest: ViteManifest,
-  pluginThemeHref: string | undefined,
   pluginResourceEntries: GrowiPluginResourceEntries;
   pluginResourceEntries: GrowiPluginResourceEntries;
 }
 }
 declare type GrowiDocumentInitialProps = DocumentInitialProps & GrowiDocumentProps;
 declare type GrowiDocumentInitialProps = DocumentInitialProps & GrowiDocumentProps;
@@ -87,28 +49,22 @@ class GrowiDocument extends Document<GrowiDocumentInitialProps> {
   static override async getInitialProps(ctx: DocumentContext): Promise<GrowiDocumentInitialProps> {
   static override async getInitialProps(ctx: DocumentContext): Promise<GrowiDocumentInitialProps> {
     const initialProps: DocumentInitialProps = await Document.getInitialProps(ctx);
     const initialProps: DocumentInitialProps = await Document.getInitialProps(ctx);
     const { crowi } = ctx.req as CrowiRequest<any>;
     const { crowi } = ctx.req as CrowiRequest<any>;
-    const { configManager, customizeService, pluginService } = crowi;
+    const { customizeService, pluginService } = crowi;
 
 
-    const theme = configManager.getConfig('crowi', 'customize:theme');
+    const { themeHref } = customizeService;
     const customScript: string | null = customizeService.getCustomScript();
     const customScript: string | null = customizeService.getCustomScript();
     const customCss: string | null = customizeService.getCustomCss();
     const customCss: string | null = customizeService.getCustomCss();
     const customNoscript: string | null = customizeService.getCustomNoscript();
     const customNoscript: string | null = customizeService.getCustomNoscript();
 
 
-    // import preset-themes manifest
-    const presetThemesManifest = await import('@growi/preset-themes/dist/themes/manifest.json').then(imported => imported.default);
-
     // retrieve plugin manifests
     // retrieve plugin manifests
     const pluginResourceEntries = await (pluginService as IPluginService).retrieveAllPluginResourceEntries();
     const pluginResourceEntries = await (pluginService as IPluginService).retrieveAllPluginResourceEntries();
-    const pluginThemeHref = await (pluginService as IPluginService).retrieveThemeHref(theme);
 
 
     return {
     return {
       ...initialProps,
       ...initialProps,
-      theme,
+      themeHref,
       customScript,
       customScript,
       customCss,
       customCss,
       customNoscript,
       customNoscript,
-      presetThemesManifest,
-      pluginThemeHref,
       pluginResourceEntries,
       pluginResourceEntries,
     };
     };
   }
   }
@@ -137,7 +93,7 @@ class GrowiDocument extends Document<GrowiDocumentInitialProps> {
   override render(): JSX.Element {
   override render(): JSX.Element {
     const {
     const {
       customCss, customScript, customNoscript,
       customCss, customScript, customNoscript,
-      theme, presetThemesManifest, pluginThemeHref, pluginResourceEntries,
+      themeHref, pluginResourceEntries,
     } = this.props;
     } = this.props;
 
 
     return (
     return (
@@ -150,8 +106,7 @@ class GrowiDocument extends Document<GrowiDocumentInitialProps> {
           <link rel='preload' href="/static/fonts/Lato-Regular-latin-ext.woff2" as="font" type="font/woff2" />
           <link rel='preload' href="/static/fonts/Lato-Regular-latin-ext.woff2" as="font" type="font/woff2" />
           <link rel='preload' href="/static/fonts/Lato-Bold-latin.woff2" as="font" type="font/woff2" />
           <link rel='preload' href="/static/fonts/Lato-Bold-latin.woff2" as="font" type="font/woff2" />
           <link rel='preload' href="/static/fonts/Lato-Bold-latin-ext.woff2" as="font" type="font/woff2" />
           <link rel='preload' href="/static/fonts/Lato-Bold-latin-ext.woff2" as="font" type="font/woff2" />
-          <HeadersForThemes theme={theme}
-            presetThemesManifest={presetThemesManifest} pluginThemeHref={pluginThemeHref} />
+          <link rel="stylesheet" key="link-theme" href={themeHref} />
           <HeadersForGrowiPlugin pluginResourceEntries={pluginResourceEntries} />
           <HeadersForGrowiPlugin pluginResourceEntries={pluginResourceEntries} />
           {this.renderCustomCss(customCss)}
           {this.renderCustomCss(customCss)}
         </Head>
         </Head>

+ 1 - 1
packages/app/src/pages/utils/commons.ts

@@ -49,7 +49,7 @@ export const getServerSideCommonProps: GetServerSideProps<CommonProps> = async(c
   const redirectDestination = !isMaintenanceMode && currentPathname === '/maintenance' ? '/' : isMaintenanceMode && !currentPathname.match('/admin/*') && !(currentPathname === '/maintenance') ? '/maintenance' : null;
   const redirectDestination = !isMaintenanceMode && currentPathname === '/maintenance' ? '/' : isMaintenanceMode && !currentPathname.match('/admin/*') && !(currentPathname === '/maintenance') ? '/maintenance' : null;
   const isCustomizedLogoUploaded = await attachmentService.isBrandLogoExist();
   const isCustomizedLogoUploaded = await attachmentService.isBrandLogoExist();
   const isDefaultLogo = crowi.configManager.getConfig('crowi', 'customize:isDefaultLogo') || !isCustomizedLogoUploaded;
   const isDefaultLogo = crowi.configManager.getConfig('crowi', 'customize:isDefaultLogo') || !isCustomizedLogoUploaded;
-  const forcedColorScheme = crowi.configManager.getConfig('crowi', 'customize:theme:forcedColorScheme');
+  const forcedColorScheme = crowi.customizeService.forcedColorScheme;
 
 
   const props: CommonProps = {
   const props: CommonProps = {
     namespacesRequired: ['translation'],
     namespacesRequired: ['translation'],

+ 2 - 1
packages/app/src/server/crowi/index.js

@@ -130,7 +130,6 @@ Crowi.prototype.init = async function() {
     this.setUpFileUploaderSwitchService(),
     this.setUpFileUploaderSwitchService(),
     this.setupAttachmentService(),
     this.setupAttachmentService(),
     this.setUpAcl(),
     this.setUpAcl(),
-    this.setUpCustomize(),
     this.setUpRestQiitaAPI(),
     this.setUpRestQiitaAPI(),
     this.setupUserGroupService(),
     this.setupUserGroupService(),
     this.setupExport(),
     this.setupExport(),
@@ -141,6 +140,7 @@ Crowi.prototype.init = async function() {
     this.setupActivityService(),
     this.setupActivityService(),
     this.setupCommentService(),
     this.setupCommentService(),
     this.setupSyncPageStatusService(),
     this.setupSyncPageStatusService(),
+    this.setUpCustomize(), // depends on pluginService
   ]);
   ]);
 
 
   // globalNotification depends on slack and mailer
   // globalNotification depends on slack and mailer
@@ -604,6 +604,7 @@ Crowi.prototype.setUpCustomize = async function() {
     this.customizeService = new CustomizeService(this);
     this.customizeService = new CustomizeService(this);
     this.customizeService.initCustomCss();
     this.customizeService.initCustomCss();
     this.customizeService.initCustomTitle();
     this.customizeService.initCustomTitle();
+    this.customizeService.initGrowiTheme();
 
 
     // add as a message handler
     // add as a message handler
     if (this.s2sMessagingService != null) {
     if (this.s2sMessagingService != null) {

+ 2 - 5
packages/app/src/server/routes/apiv3/customize-setting.js

@@ -112,7 +112,6 @@ module.exports = (crowi) => {
     ],
     ],
     theme: [
     theme: [
       body('theme').isString(),
       body('theme').isString(),
-      body('forcedColorScheme').isString().optional({ nullable: true }),
     ],
     ],
     sidebar: [
     sidebar: [
       body('isSidebarDrawerMode').isBoolean(),
       body('isSidebarDrawerMode').isBoolean(),
@@ -275,7 +274,6 @@ module.exports = (crowi) => {
 
 
     try {
     try {
       const currentTheme = await crowi.configManager.getConfig('crowi', 'customize:theme');
       const currentTheme = await crowi.configManager.getConfig('crowi', 'customize:theme');
-      const currentForcedColorScheme = await crowi.configManager.getConfig('crowi', 'customize:theme:forcedColorScheme');
 
 
       // retrieve plugin manifests
       // retrieve plugin manifests
       const GrowiPluginModel = mongoose.model('GrowiPlugin');
       const GrowiPluginModel = mongoose.model('GrowiPlugin');
@@ -285,7 +283,7 @@ module.exports = (crowi) => {
         .map(themePlugin => themePlugin.meta.themes)
         .map(themePlugin => themePlugin.meta.themes)
         .flat();
         .flat();
 
 
-      return res.apiv3({ currentTheme, currentForcedColorScheme, pluginThemesMetadatas });
+      return res.apiv3({ currentTheme, pluginThemesMetadatas });
     }
     }
     catch (err) {
     catch (err) {
       const msg = 'Error occurred in getting theme';
       const msg = 'Error occurred in getting theme';
@@ -320,15 +318,14 @@ module.exports = (crowi) => {
   router.put('/theme', loginRequiredStrictly, adminRequired, addActivity, validator.theme, apiV3FormValidator, async(req, res) => {
   router.put('/theme', loginRequiredStrictly, adminRequired, addActivity, validator.theme, apiV3FormValidator, async(req, res) => {
     const requestParams = {
     const requestParams = {
       'customize:theme': req.body.theme,
       'customize:theme': req.body.theme,
-      'customize:theme:forcedColorScheme': req.body.forcedColorScheme,
     };
     };
 
 
     try {
     try {
       await crowi.configManager.updateConfigsInTheSameNamespace('crowi', requestParams);
       await crowi.configManager.updateConfigsInTheSameNamespace('crowi', requestParams);
       const customizedParams = {
       const customizedParams = {
         theme: await crowi.configManager.getConfig('crowi', 'customize:theme'),
         theme: await crowi.configManager.getConfig('crowi', 'customize:theme'),
-        forcedColorScheme: await crowi.configManager.getConfig('crowi', 'customize:theme:forcedColorScheme'),
       };
       };
+      customizeService.initGrowiTheme();
       const parameters = { action: SupportedAction.ACTION_ADMIN_THEME_UPDATE };
       const parameters = { action: SupportedAction.ACTION_ADMIN_THEME_UPDATE };
       activityEvent.emit('update', res.locals.activity._id, parameters);
       activityEvent.emit('update', res.locals.activity._id, parameters);
       return res.apiv3({ customizedParams });
       return res.apiv3({ customizedParams });

+ 40 - 1
packages/app/src/server/service/customize.ts

@@ -1,5 +1,6 @@
 // eslint-disable-next-line no-unused-vars
 // eslint-disable-next-line no-unused-vars
-import { DevidedPagePath } from '@growi/core';
+import { ColorScheme, DevidedPagePath, getForcedColorScheme } from '@growi/core';
+import { DefaultThemeMetadata, PresetThemesMetadatas } from '@growi/preset-themes';
 import uglifycss from 'uglifycss';
 import uglifycss from 'uglifycss';
 
 
 import loggerFactory from '~/utils/logger';
 import loggerFactory from '~/utils/logger';
@@ -7,8 +8,10 @@ import loggerFactory from '~/utils/logger';
 import S2sMessage from '../models/vo/s2s-message';
 import S2sMessage from '../models/vo/s2s-message';
 
 
 import ConfigManager from './config-manager';
 import ConfigManager from './config-manager';
+import type { IPluginService } from './plugin';
 import { S2sMessageHandlable } from './s2s-messaging/handlable';
 import { S2sMessageHandlable } from './s2s-messaging/handlable';
 
 
+
 const logger = loggerFactory('growi:service:CustomizeService');
 const logger = loggerFactory('growi:service:CustomizeService');
 
 
 
 
@@ -25,17 +28,26 @@ class CustomizeService implements S2sMessageHandlable {
 
 
   xssService: any;
   xssService: any;
 
 
+  pluginService: IPluginService;
+
   lastLoadedAt?: Date;
   lastLoadedAt?: Date;
 
 
   customCss?: string;
   customCss?: string;
 
 
   customTitleTemplate!: string;
   customTitleTemplate!: string;
 
 
+  theme: string;
+
+  themeHref: string;
+
+  forcedColorScheme?: ColorScheme;
+
   constructor(crowi) {
   constructor(crowi) {
     this.configManager = crowi.configManager;
     this.configManager = crowi.configManager;
     this.s2sMessagingService = crowi.s2sMessagingService;
     this.s2sMessagingService = crowi.s2sMessagingService;
     this.appService = crowi.appService;
     this.appService = crowi.appService;
     this.xssService = crowi.xssService;
     this.xssService = crowi.xssService;
+    this.pluginService = crowi.pluginService;
   }
   }
 
 
   /**
   /**
@@ -137,6 +149,33 @@ class CustomizeService implements S2sMessageHandlable {
     return this.xssService.process(customTitle);
     return this.xssService.process(customTitle);
   }
   }
 
 
+  async initGrowiTheme(): Promise<void> {
+    const theme = this.configManager.getConfig('crowi', 'customize:theme');
+
+    this.theme = theme;
+
+    const resultForThemePlugin = await this.pluginService.findThemePlugin(theme);
+
+    if (resultForThemePlugin != null) {
+      this.forcedColorScheme = getForcedColorScheme(resultForThemePlugin.themeMetadata.schemeType);
+      this.themeHref = resultForThemePlugin.themeHref;
+    }
+    // retrieve preset theme
+    else {
+      // import preset-themes manifest
+      const presetThemesManifest = await import('@growi/preset-themes/dist/themes/manifest.json').then(imported => imported.default);
+
+      const themeMetadata = PresetThemesMetadatas.find(p => p.name === theme);
+      this.forcedColorScheme = getForcedColorScheme(themeMetadata?.schemeType);
+
+      const manifestKey = themeMetadata?.manifestKey ?? DefaultThemeMetadata.manifestKey;
+      if (themeMetadata == null || !(themeMetadata.manifestKey in presetThemesManifest)) {
+        logger.warn(`Use default theme because the key for '${theme} does not exist in preset-themes manifest`);
+      }
+      this.themeHref = `/static/preset-themes/${presetThemesManifest[manifestKey].file}`; // configured by express.static
+    }
+
+  }
 
 
 }
 }
 
 

+ 18 - 7
packages/app/src/server/service/plugin.ts

@@ -33,9 +33,16 @@ function retrievePluginManifest(growiPlugin: GrowiPlugin): ViteManifest {
   return JSON.parse(manifestStr);
   return JSON.parse(manifestStr);
 }
 }
 
 
+
+type FindThemePluginResult = {
+  growiPlugin: GrowiPlugin,
+  themeMetadata: GrowiThemeMetadata,
+  themeHref: string,
+}
+
 export interface IPluginService {
 export interface IPluginService {
   install(origin: GrowiPluginOrigin): Promise<string>
   install(origin: GrowiPluginOrigin): Promise<string>
-  retrieveThemeHref(theme: string): Promise<string | undefined>
+  findThemePlugin(theme: string): Promise<FindThemePluginResult | null>
   retrieveAllPluginResourceEntries(): Promise<GrowiPluginResourceEntries>
   retrieveAllPluginResourceEntries(): Promise<GrowiPluginResourceEntries>
   downloadNotExistPluginRepositories(): Promise<void>
   downloadNotExistPluginRepositories(): Promise<void>
 }
 }
@@ -322,8 +329,7 @@ export class PluginService implements IPluginService {
     return growiPlugins.meta.name;
     return growiPlugins.meta.name;
   }
   }
 
 
-  async retrieveThemeHref(theme: string): Promise<string | undefined> {
-
+  async findThemePlugin(theme: string): Promise<FindThemePluginResult | null> {
     const GrowiPlugin = mongoose.model('GrowiPlugin') as GrowiPluginModel;
     const GrowiPlugin = mongoose.model('GrowiPlugin') as GrowiPluginModel;
 
 
     let matchedPlugin: GrowiPlugin | undefined;
     let matchedPlugin: GrowiPlugin | undefined;
@@ -349,15 +355,20 @@ export class PluginService implements IPluginService {
       logger.error(`Could not find the theme '${theme}' from GrowiPlugin documents.`, e);
       logger.error(`Could not find the theme '${theme}' from GrowiPlugin documents.`, e);
     }
     }
 
 
+    if (matchedPlugin == null || matchedThemeMetadata == null) {
+      return null;
+    }
+
+    let themeHref;
     try {
     try {
-      if (matchedPlugin != null && matchedThemeMetadata != null) {
-        const manifest = await retrievePluginManifest(matchedPlugin);
-        return `${PLUGINS_STATIC_DIR}/${matchedPlugin.installedPath}/dist/${manifest[matchedThemeMetadata.manifestKey].file}`;
-      }
+      const manifest = retrievePluginManifest(matchedPlugin);
+      themeHref = `${PLUGINS_STATIC_DIR}/${matchedPlugin.installedPath}/dist/${manifest[matchedThemeMetadata.manifestKey].file}`;
     }
     }
     catch (e) {
     catch (e) {
       logger.error(`Could not read manifest file for the theme '${theme}'`, e);
       logger.error(`Could not read manifest file for the theme '${theme}'`, e);
     }
     }
+
+    return { growiPlugin: matchedPlugin, themeMetadata: matchedThemeMetadata, themeHref };
   }
   }
 
 
   async retrieveAllPluginResourceEntries(): Promise<GrowiPluginResourceEntries> {
   async retrieveAllPluginResourceEntries(): Promise<GrowiPluginResourceEntries> {

+ 2 - 4
packages/app/src/stores/admin/customize.tsx

@@ -1,6 +1,5 @@
 import { useCallback } from 'react';
 import { useCallback } from 'react';
 
 
-import { ColorScheme } from '@growi/core';
 import useSWR, { SWRResponse } from 'swr';
 import useSWR, { SWRResponse } from 'swr';
 import useSWRImmutable from 'swr/immutable';
 import useSWRImmutable from 'swr/immutable';
 
 
@@ -30,7 +29,6 @@ export const useSWRxLayoutSetting = (): SWRResponse<IResLayoutSetting, Error> &
 
 
 type UpdateThemeArgs = {
 type UpdateThemeArgs = {
   theme: string,
   theme: string,
-  forcedColorScheme: ColorScheme | null,
 }
 }
 export const useSWRxGrowiThemeSetting = (): SWRResponse<IResGrowiTheme, Error> & updateConfigMethodForAdmin<UpdateThemeArgs> => {
 export const useSWRxGrowiThemeSetting = (): SWRResponse<IResGrowiTheme, Error> & updateConfigMethodForAdmin<UpdateThemeArgs> => {
 
 
@@ -41,9 +39,9 @@ export const useSWRxGrowiThemeSetting = (): SWRResponse<IResGrowiTheme, Error> &
 
 
   const swrResponse = useSWR('/customize-setting/theme', fetcher);
   const swrResponse = useSWR('/customize-setting/theme', fetcher);
 
 
-  const update = async({ theme, forcedColorScheme }: UpdateThemeArgs) => {
+  const update = async({ theme }: UpdateThemeArgs) => {
 
 
-    await apiv3Put('/customize-setting/theme', { theme, forcedColorScheme });
+    await apiv3Put('/customize-setting/theme', { theme });
 
 
     if (swrResponse.data == null) {
     if (swrResponse.data == null) {
       swrResponse.mutate();
       swrResponse.mutate();

+ 1 - 0
packages/core/src/index.ts

@@ -32,4 +32,5 @@ export * from './models/vo/error-apiv3';
 export * from './service/localstorage-manager';
 export * from './service/localstorage-manager';
 export * from './utils/basic-interceptor';
 export * from './utils/basic-interceptor';
 export * from './utils/browser-utils';
 export * from './utils/browser-utils';
+export * from './utils/growi-theme-metadata';
 export * from './utils/with-utils';
 export * from './utils/with-utils';

+ 8 - 0
packages/core/src/utils/growi-theme-metadata.ts

@@ -0,0 +1,8 @@
+import type { ColorScheme } from '../interfaces/color-scheme';
+import { GrowiThemeSchemeType } from '../interfaces/growi-theme-metadata';
+
+export const getForcedColorScheme = (growiThemeSchemeType?: GrowiThemeSchemeType): ColorScheme | undefined => {
+  return growiThemeSchemeType == null || growiThemeSchemeType === GrowiThemeSchemeType.BOTH
+    ? undefined
+    : growiThemeSchemeType as ColorScheme;
+};