This is an automated email from the ASF dual-hosted git repository.

shuai pushed a commit to branch test
in repository https://gitbox.apache.org/repos/asf/answer.git

commit b5ae2d0351118a6274616987ce1d3c5283cf6d93
Author: shuai <[email protected]>
AuthorDate: Fri Dec 19 14:09:38 2025 +0800

    fix:  admin/themes add layout config
---
 cmd/wire_gen.go                             | 24 ++-----------------
 docs/docs.go                                | 19 ---------------
 docs/swagger.yaml                           | 17 --------------
 i18n/en_US.yaml                             |  4 ++++
 internal/service/mock/siteinfo_repo_mock.go | 19 ---------------
 ui/src/common/interface.ts                  |  1 +
 ui/src/components/Customize/index.tsx       | 36 +++++++++++++++++++++++++++++
 ui/src/components/Header/index.tsx          |  8 +++++--
 ui/src/pages/{ => 404}/403/index.tsx        |  0
 ui/src/pages/Admin/Themes/index.tsx         | 11 +++++++++
 ui/src/pages/Layout/index.tsx               | 11 +++++++--
 ui/src/pages/SideNavLayout/index.tsx        |  2 +-
 ui/src/stores/themeSetting.ts               |  2 ++
 13 files changed, 72 insertions(+), 82 deletions(-)

diff --git a/cmd/wire_gen.go b/cmd/wire_gen.go
index aae1c6af..dbfd640d 100644
--- a/cmd/wire_gen.go
+++ b/cmd/wire_gen.go
@@ -1,28 +1,8 @@
-//go:build !wireinject
-// +build !wireinject
-
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *   http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
 // Code generated by Wire. DO NOT EDIT.
 
 //go:generate go run github.com/google/wire/cmd/wire
+//go:build !wireinject
+// +build !wireinject
 
 package answercmd
 
diff --git a/docs/docs.go b/docs/docs.go
index 5e9d5b39..fc4d9909 100644
--- a/docs/docs.go
+++ b/docs/docs.go
@@ -1,22 +1,3 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *   http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
 // Package docs Code generated by swaggo/swag. DO NOT EDIT
 package docs
 
diff --git a/docs/swagger.yaml b/docs/swagger.yaml
index e0244083..42df5cbf 100644
--- a/docs/swagger.yaml
+++ b/docs/swagger.yaml
@@ -1,20 +1,3 @@
-# Licensed to the Apache Software Foundation (ASF) under one
-# or more contributor license agreements.  See the NOTICE file
-# distributed with this work for additional information
-# regarding copyright ownership.  The ASF licenses this file
-# to you under the Apache License, Version 2.0 (the
-# "License"); you may not use this file except in compliance
-# with the License.  You may obtain a copy of the License at
-#
-#   http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing,
-# software distributed under the License is distributed on an
-# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
-# KIND, either express or implied.  See the License for the
-# specific language governing permissions and limitations
-# under the License.
-
 basePath: /
 definitions:
   constant.NotificationChannelKey:
diff --git a/i18n/en_US.yaml b/i18n/en_US.yaml
index eac322da..4ab6b34b 100644
--- a/i18n/en_US.yaml
+++ b/i18n/en_US.yaml
@@ -2186,6 +2186,10 @@ ui:
       primary_color:
         label: Primary color
         text: Modify the colors used by your themes
+      layout:
+        label: Layout
+        full_width: Full-width
+        fixed_width: Fixed-width
     css_and_html:
       page_title: CSS and HTML
       custom_css:
diff --git a/internal/service/mock/siteinfo_repo_mock.go 
b/internal/service/mock/siteinfo_repo_mock.go
index a98ceb68..0a1b31e8 100644
--- a/internal/service/mock/siteinfo_repo_mock.go
+++ b/internal/service/mock/siteinfo_repo_mock.go
@@ -1,22 +1,3 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *   http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
 // Code generated by MockGen. DO NOT EDIT.
 // Source: ./siteinfo_service.go
 //
diff --git a/ui/src/common/interface.ts b/ui/src/common/interface.ts
index f2901908..3a77047e 100644
--- a/ui/src/common/interface.ts
+++ b/ui/src/common/interface.ts
@@ -469,6 +469,7 @@ export type themeConfig = {
 export interface AdminSettingsTheme {
   theme: string;
   color_scheme: string;
+  layout: string;
   theme_options?: { label: string; value: string }[];
   theme_config: Record<string, themeConfig>;
 }
diff --git a/ui/src/components/Customize/index.tsx 
b/ui/src/components/Customize/index.tsx
index c02db675..52cc651b 100644
--- a/ui/src/components/Customize/index.tsx
+++ b/ui/src/components/Customize/index.tsx
@@ -18,6 +18,7 @@
  */
 
 import { FC, memo, useEffect } from 'react';
+import { useLocation } from 'react-router-dom';
 
 import { customizeStore } from '@/stores';
 
@@ -117,6 +118,8 @@ const Index: FC = () => {
   const { custom_head, custom_header, custom_footer } = customizeStore(
     (state) => state,
   );
+  const { pathname } = useLocation();
+
   useEffect(() => {
     const isSeo = document.querySelector('meta[name="go-template"]');
     if (!isSeo) {
@@ -125,8 +128,41 @@ const Index: FC = () => {
       }, 1000);
       handleCustomHeader(custom_header);
       handleCustomFooter(custom_footer);
+    } else {
+      isSeo.remove();
     }
   }, [custom_head, custom_header, custom_footer]);
+
+  useEffect(() => {
+    /**
+     * description:  Activate scripts with data-client attribute when route 
changes
+     */
+    const allScript = document.body.querySelectorAll('script[data-client]');
+    console.log('allScript', allScript);
+    allScript.forEach((scriptNode) => {
+      const script = document.createElement('script');
+      script.setAttribute('data-client', 'true');
+      // If the script is already wrapped in an IIFE, use it directly; 
otherwise, wrap it in an IIFE
+      if (
+        /^\s*\(\s*function\s*\(\s*\)\s*{/.test(
+          (scriptNode as HTMLScriptElement).text,
+        ) ||
+        /^\s*\(\s*\(\s*\)\s*=>\s*{/.test((scriptNode as 
HTMLScriptElement).text)
+      ) {
+        script.text = (scriptNode as HTMLScriptElement).text;
+      } else {
+        script.text = `(() => {${(scriptNode as HTMLScriptElement).text}})();`;
+      }
+      for (let i = 0; i < scriptNode.attributes.length; i += 1) {
+        const attr = scriptNode.attributes[i];
+        if (attr.name !== 'data-client') {
+          script.setAttribute(attr.name, attr.value);
+        }
+      }
+      scriptNode.parentElement?.replaceChild(script, scriptNode);
+    });
+  }, [pathname]);
+
   return null;
 };
 
diff --git a/ui/src/components/Header/index.tsx 
b/ui/src/components/Header/index.tsx
index 2d151b88..4e9c8189 100644
--- a/ui/src/components/Header/index.tsx
+++ b/ui/src/components/Header/index.tsx
@@ -83,7 +83,7 @@ const Header: FC = () => {
 
   let navbarStyle = 'theme-light';
   let themeMode = 'light';
-  const { theme, theme_config } = themeSettingStore((_) => _);
+  const { theme, theme_config, layout } = themeSettingStore((_) => _);
   if (theme_config?.[theme]?.navbar_style) {
     // const color = theme_config[theme].navbar_style.startsWith('#')
     themeMode = isLight(theme_config[theme].navbar_style) ? 'light' : 'dark';
@@ -113,7 +113,11 @@ const Header: FC = () => {
         backgroundColor: theme_config[theme].navbar_style,
       }}
       id="header">
-      <div className="w-100 d-flex align-items-center px-3">
+      <div
+        className={classnames(
+          'w-100 d-flex align-items-center px-3',
+          layout === 'Fixed-width' ? 'container-xxl' : '',
+        )}>
         <Navbar.Toggle
           className="answer-navBar me-2"
           onClick={() => {
diff --git a/ui/src/pages/403/index.tsx b/ui/src/pages/404/403/index.tsx
similarity index 100%
rename from ui/src/pages/403/index.tsx
rename to ui/src/pages/404/403/index.tsx
diff --git a/ui/src/pages/Admin/Themes/index.tsx 
b/ui/src/pages/Admin/Themes/index.tsx
index 94eccbca..db2a13da 100644
--- a/ui/src/pages/Admin/Themes/index.tsx
+++ b/ui/src/pages/Admin/Themes/index.tsx
@@ -46,6 +46,13 @@ const Index: FC = () => {
         enumNames: themeSetting?.theme_options?.map((_) => _.label),
         default: themeSetting?.theme_options?.[0]?.value,
       },
+      layout: {
+        type: 'string',
+        title: t('layout.label'),
+        enum: ['Full-width', 'Fixed-width'],
+        enumNames: [t('layout.full_width'), t('layout.fixed_width')],
+        default: themeSetting?.layout,
+      },
       color_scheme: {
         type: 'string',
         title: t('color_scheme.label'),
@@ -77,6 +84,9 @@ const Index: FC = () => {
     color_scheme: {
       'ui:widget': 'select',
     },
+    layout: {
+      'ui:widget': 'select',
+    },
     navbar_style: {
       'ui:widget': 'input_group',
       'ui:options': {
@@ -131,6 +141,7 @@ const Index: FC = () => {
     const reqParams: Type.AdminSettingsTheme = {
       theme: themeName,
       color_scheme: formData.color_scheme.value,
+      layout: formData.layout.value,
       theme_config: {
         [themeName]: {
           navbar_style: formData.navbar_style.value,
diff --git a/ui/src/pages/Layout/index.tsx b/ui/src/pages/Layout/index.tsx
index 984e13f4..bd229165 100644
--- a/ui/src/pages/Layout/index.tsx
+++ b/ui/src/pages/Layout/index.tsx
@@ -22,12 +22,14 @@ import { Outlet, useLocation, ScrollRestoration } from 
'react-router-dom';
 import { HelmetProvider } from 'react-helmet-async';
 
 import { SWRConfig } from 'swr';
+import classnames from 'classnames';
 
 import {
   toastStore,
   loginToContinueStore,
   errorCodeStore,
   siteLealStore,
+  themeSettingStore,
 } from '@/stores';
 import {
   Header,
@@ -56,7 +58,8 @@ const Layout: FC = () => {
   const { code: httpStatusCode, reset: httpStatusReset } = errorCodeStore();
   const { show: showLoginToContinueModal } = loginToContinueStore();
   const { data: notificationData } = useQueryNotificationStatus();
-
+  const layout = themeSettingStore((state) => state.layout);
+  console.log(layout);
   useEffect(() => {
     // handle footnote links
     const fixFootnoteLinks = () => {
@@ -209,7 +212,11 @@ const Layout: FC = () => {
           revalidateOnFocus: false,
         }}>
         <Header />
-        <div className="position-relative page-wrap d-flex flex-column 
flex-fill">
+        <div
+          className={classnames(
+            'position-relative page-wrap d-flex flex-column flex-fill',
+            layout === 'Fixed-width' ? 'container-xxl' : '',
+          )}>
           {httpStatusCode ? (
             <HttpErrorContent httpCode={httpStatusCode} />
           ) : (
diff --git a/ui/src/pages/SideNavLayout/index.tsx 
b/ui/src/pages/SideNavLayout/index.tsx
index 907b9b28..b9bc38b9 100644
--- a/ui/src/pages/SideNavLayout/index.tsx
+++ b/ui/src/pages/SideNavLayout/index.tsx
@@ -38,7 +38,7 @@ const Index: FC = () => {
             <Outlet />
           </div>
         </div>
-        <div className="d-flex justify-content-center">
+        <div className="d-flex justify-content-center px-0 px-md-4">
           <div className="main-mx-with">
             <Footer />
           </div>
diff --git a/ui/src/stores/themeSetting.ts b/ui/src/stores/themeSetting.ts
index 4b13d818..2f1b1edb 100644
--- a/ui/src/stores/themeSetting.ts
+++ b/ui/src/stores/themeSetting.ts
@@ -27,6 +27,7 @@ interface IType {
   theme_config: AdminSettingsTheme['theme_config'];
   theme_options: AdminSettingsTheme['theme_options'];
   color_scheme: AdminSettingsTheme['color_scheme'];
+  layout: AdminSettingsTheme['layout'];
   update: (params: AdminSettingsTheme) => void;
 }
 
@@ -40,6 +41,7 @@ const store = create<IType>((set) => ({
       primary_color: DEFAULT_THEME_COLOR,
     },
   },
+  layout: 'full',
   update: (params) =>
     set((state) => {
       // Compatibility default value is colored or light before v1.5.1

Reply via email to