diff --git a/config/dev.yaml b/config/dev.yaml
index 41220bc8..66572641 100644
--- a/config/dev.yaml
+++ b/config/dev.yaml
@@ -1,18 +1,28 @@
+# 以下为前台动态渲染的配置,修改后,无需打包,重启服务即可
+dynamic:
+ # 输入 logo 的图片地址
+ logo: ''
+ appName: '云策文档'
+ appDescription: '云策文档是一款开源知识管理工具。通过独立的知识库空间,结构化地组织在线协作文档,实现知识的积累与沉淀,促进知识的复用与流通。'
+ appKeywords: '云策文档,协作,文档,fantasticit,https://github.com/fantasticit/think'
+ # 版权信息,可以是 HTML
+ copyrightInformation: '
Develop by fantasticit
'
+ # 外部跳转按钮配置
+ externalButtonText: 'Github'
+ externalButtonURL: 'https://github.com/fantasticit/think'
+
# 开发环境配置
client:
port: 5001
assetPrefix: '/'
apiUrl: 'http://localhost:5002/api'
collaborationUrl: 'ws://localhost:5003'
- # 以下为页面 meta 配置
- seoAppName: '云策文档'
- seoDescription: '云策文档是一款开源知识管理工具。通过独立的知识库空间,结构化地组织在线协作文档,实现知识的积累与沉淀,促进知识的复用与流通。'
- seoKeywords: '云策文档,协作,文档,fantasticit,https://github.com/fantasticit/think'
# 预先连接的来源,空格分割(比如图片存储服务器)
dnsPrefetch: '//wipi.oss-cn-shanghai.aliyuncs.com'
# 站点地址(如:https://think.codingit.cn/),一定要设置,否则会出现 cookie、跨域等问题
- siteUrl: 'http://localhost:5001'
- # 一定要设置,否则会出现 cookie、跨域等问题,注意如果网页部署在 https://think.codingit.cn,那这里的值应当为 .codingit.cn(没有 think)
+ siteUrl:
+ 'http://localhost:5001'
+ # 一定要设置,否则会出现 cookie、跨域等问题,注意如果网页部署在 https://think.codingit.cn,那这里的值应当为 .codingit.cn(没有 think)
siteDomain: ''
server:
@@ -58,18 +68,18 @@ oss:
# 线上更改为服务端地址(如:https://api.codingit.cn)
server: 'http://localhost:5002'
s3:
- enable: true
+ enable: false
config:
- # isp 提供商,可选 minio,s3,aliyun,tencent
- cloudisp: 'minio'
- accessKeyId: ''
- secretAccessKey: ''
- bucket: ''
- region: ''
- # 仅cloudisp 的值为 minio/s3 时配置,其它提供商为空
- forcePathStyle: false
- # 仅cloudisp 的值为 minio 时配置,其它提供商为空
- endpoint: ''
+ # isp 提供商,可选 minio,s3,aliyun,tencent
+ cloudisp: 'minio'
+ accessKeyId: ''
+ secretAccessKey: ''
+ bucket: ''
+ region: ''
+ # 仅cloudisp 的值为 minio/s3 时配置,其它提供商为空
+ forcePathStyle: false
+ # 仅cloudisp 的值为 minio 时配置,其它提供商为空
+ endpoint: ''
# jwt 配置
jwt:
diff --git a/config/docker-prod-sample.yaml b/config/docker-prod-sample.yaml
index e60c2c9b..bbe45e47 100644
--- a/config/docker-prod-sample.yaml
+++ b/config/docker-prod-sample.yaml
@@ -1,3 +1,16 @@
+# 以下为前台动态渲染的配置,修改后,无需打包,重启服务即可
+dynamic:
+ # 输入 logo 的图片地址
+ logo: ''
+ appName: '云策文档'
+ appDescription: '云策文档是一款开源知识管理工具。通过独立的知识库空间,结构化地组织在线协作文档,实现知识的积累与沉淀,促进知识的复用与流通。'
+ appKeywords: '云策文档,协作,文档,fantasticit,https://github.com/fantasticit/think'
+ # 版权信息,可以是 HTML
+ copyrightInformation: 'Develop by fantasticit
'
+ # 外部跳转按钮配置
+ externalButtonText: 'Github'
+ externalButtonURL: 'https://github.com/fantasticit/think'
+
# 开发环境配置
client:
port: 5001
diff --git a/packages/client/next.config.js b/packages/client/next.config.js
index e93f21a5..ea17d882 100644
--- a/packages/client/next.config.js
+++ b/packages/client/next.config.js
@@ -25,9 +25,7 @@ const nextConfig = semi({
COLLABORATION_API_URL: config.client.collaborationUrl,
ENABLE_OSS_S3: config.oss.s3.enable,
DNS_PREFETCH: (config.client.dnsPrefetch || '').split(' '),
- SEO_APPNAME: config.client.seoAppName,
- SEO_DESCRIPTION: config.client.seoDescription,
- SEO_KEYWORDS: config.client.seoKeywords,
+ SITE_URL: config.client.siteUrl,
},
webpack: (config, { dev, isServer }) => {
config.resolve.plugins.push(new TsconfigPathsPlugin());
diff --git a/packages/client/src/components/author.tsx b/packages/client/src/components/author.tsx
index 112515d9..ba1c3fd1 100644
--- a/packages/client/src/components/author.tsx
+++ b/packages/client/src/components/author.tsx
@@ -1,18 +1,26 @@
import { IconLikeHeart } from '@douyinfe/semi-icons';
import { Space, Typography } from '@douyinfe/semi-ui';
+import { RuntimeConfig } from 'hooks/use-runtime-config';
+
const { Text } = Typography;
export const Author = () => {
+ const config = RuntimeConfig.useHook();
+
return (
-
-
- Develop by
- fantasticit
- with
-
-
+ {config.copyrightInformation ? (
+
+ ) : (
+
+
+ Develop by
+ fantasticit
+ with
+
+
+ )}
);
};
diff --git a/packages/client/src/components/logo/index.tsx b/packages/client/src/components/logo/index.tsx
index 2cf5de17..2cc1d24a 100644
--- a/packages/client/src/components/logo/index.tsx
+++ b/packages/client/src/components/logo/index.tsx
@@ -1,43 +1,50 @@
import { Typography } from '@douyinfe/semi-ui';
+import { RuntimeConfig } from 'hooks/use-runtime-config';
import Link from 'next/link';
import styles from './index.module.scss';
const { Text } = Typography;
-export const LogoName = process.env.SEO_APPNAME;
-
export const LogoImage = () => {
+ const config = RuntimeConfig.useHook();
+
return (
-
+ )}
);
};
export const LogoText = () => {
+ const config = RuntimeConfig.useHook();
+
return (
- {LogoName}
+ {config?.appName}
);
diff --git a/packages/client/src/components/seo.tsx b/packages/client/src/components/seo.tsx
index 6db2c9fb..e36483b3 100644
--- a/packages/client/src/components/seo.tsx
+++ b/packages/client/src/components/seo.tsx
@@ -1,21 +1,27 @@
-import React, { useEffect } from 'react';
+import React, { useCallback, useEffect } from 'react';
import { Helmet } from 'react-helmet';
+import { RuntimeConfig } from 'hooks/use-runtime-config';
+
interface IProps {
title: string;
needTitleSuffix?: boolean;
}
-const buildTitle = (title) => `${title} - 云策文档`;
-
export const Seo: React.FC = ({ title, needTitleSuffix = true }) => {
+ const config = RuntimeConfig.useHook();
+
+ const buildTitle = useCallback(() => {
+ return `${title} - ${config?.appName}`;
+ }, [title, config?.appName]);
+
useEffect(() => {
- window.document.title = needTitleSuffix ? buildTitle(title) : title;
- }, [title, needTitleSuffix]);
+ window.document.title = needTitleSuffix ? buildTitle() : title;
+ }, [title, needTitleSuffix, buildTitle]);
return (
- {needTitleSuffix ? buildTitle(title) : title}
+ {needTitleSuffix ? buildTitle() : title}
);
};
diff --git a/packages/client/src/hooks/use-runtime-config.tsx b/packages/client/src/hooks/use-runtime-config.tsx
new file mode 100644
index 00000000..5c7a71c2
--- /dev/null
+++ b/packages/client/src/hooks/use-runtime-config.tsx
@@ -0,0 +1,21 @@
+import { useState } from 'react';
+
+import { createGlobalHook } from './create-global-hook';
+
+export type IRuntimeConfig = {
+ logo?: string;
+ appName?: string;
+ appDescription?: string;
+ appKeywords?: string;
+ copyrightInformation?: string;
+ externalButtonText?: string;
+ externalButtonURL?: string;
+};
+
+const useRuntimeConfig = (defaultConfig: IRuntimeConfig = {}) => {
+ const [config, setConfig] = useState(defaultConfig);
+
+ return config;
+};
+
+export const RuntimeConfig = createGlobalHook(useRuntimeConfig);
diff --git a/packages/client/src/pages/_app.tsx b/packages/client/src/pages/_app.tsx
index b3ff8308..444df2ef 100644
--- a/packages/client/src/pages/_app.tsx
+++ b/packages/client/src/pages/_app.tsx
@@ -11,6 +11,7 @@ import { preloadTiptapResources } from 'tiptap/preload';
import { isMobile } from 'helpers/env';
import { DocumentVersionControl } from 'hooks/use-document-version';
import { IsOnMobile } from 'hooks/use-on-mobile';
+import { IRuntimeConfig, RuntimeConfig } from 'hooks/use-runtime-config';
import { Theme } from 'hooks/use-theme';
import App from 'next/app';
import Head from 'next/head';
@@ -19,7 +20,7 @@ import 'viewerjs/dist/viewer.css';
import 'styles/globals.scss';
import 'thirtypart/array-prototype-at';
-class MyApp extends App<{ isMobile: boolean }> {
+class MyApp extends App<{ isMobile: boolean; runtimeConfig: IRuntimeConfig }> {
state = {
queryClient: new QueryClient({
defaultOptions: {
@@ -36,13 +37,19 @@ class MyApp extends App<{ isMobile: boolean }> {
static getInitialProps = async ({ Component, ctx }) => {
const request = ctx?.req;
const getPagePropsPromise = Component.getInitialProps ? Component.getInitialProps(ctx) : Promise.resolve({});
- const [pageProps] = await Promise.all([getPagePropsPromise]).catch((err) => {
- return [{}];
+ const [pageProps, runtimeConfig] = await Promise.all([
+ getPagePropsPromise,
+ fetch(`${process.env.SITE_URL}/api/config`).then((res) => {
+ return res.json();
+ }),
+ ]).catch((err) => {
+ return [{}, {}];
});
return {
pageProps,
isMobile: isMobile(request?.headers['user-agent']),
+ runtimeConfig,
};
};
@@ -57,7 +64,7 @@ class MyApp extends App<{ isMobile: boolean }> {
}
render() {
- const { Component, pageProps, isMobile } = this.props;
+ const { Component, pageProps, isMobile, runtimeConfig } = this.props;
const { queryClient } = this.state;
return (
@@ -68,24 +75,23 @@ class MyApp extends App<{ isMobile: boolean }> {
content="minimum-scale=1, initial-scale=1, width=device-width, shrink-to-fit=no, user-scalable=no, viewport-fit=cover"
/>
-
-
-
+
+
+
-
-
+
+
-
-
-
+
+
+
-
{((process.env.DNS_PREFETCH || []) as string[]).map((url) => (
))}
@@ -94,11 +100,13 @@ class MyApp extends App<{ isMobile: boolean }> {
-
-
-
-
-
+
+
+
+
+
+
+
diff --git a/packages/client/src/pages/api/config.ts b/packages/client/src/pages/api/config.ts
new file mode 100644
index 00000000..a281daa2
--- /dev/null
+++ b/packages/client/src/pages/api/config.ts
@@ -0,0 +1,7 @@
+import { getRuntimeConfig } from '@think/config';
+
+import type { NextApiRequest, NextApiResponse } from 'next';
+
+export default function handler(req: NextApiRequest, res: NextApiResponse>) {
+ res.status(200).json(getRuntimeConfig() as Record);
+}
diff --git a/packages/client/src/pages/index.tsx b/packages/client/src/pages/index.tsx
index 2c9fdb8e..a12e6e1c 100644
--- a/packages/client/src/pages/index.tsx
+++ b/packages/client/src/pages/index.tsx
@@ -4,6 +4,7 @@ import { Button, Typography } from '@douyinfe/semi-ui';
import { Seo } from 'components/seo';
import { toLogin, useUser } from 'data/user';
+import { RuntimeConfig } from 'hooks/use-runtime-config';
import { TeamWorkIllustration } from 'illustrations/team-work';
import { SingleColumnLayout } from 'layouts/single-column';
import type { NextPage } from 'next';
@@ -14,6 +15,8 @@ import styles from './index.module.scss';
const { Title, Paragraph } = Typography;
const Page: NextPage = () => {
+ const config = RuntimeConfig.useHook();
+
const { user } = useUser();
const start = useCallback(() => {
@@ -24,9 +27,9 @@ const Page: NextPage = () => {
}
}, [user]);
- const toGithub = useCallback(() => {
- window.open('https://github.com/fantasticit/think');
- }, []);
+ const toExternal = useCallback(() => {
+ window.open(config.externalButtonURL);
+ }, [config.externalButtonURL]);
return (
@@ -36,18 +39,18 @@ const Page: NextPage = () => {
-
云策文档
-
- 云策文档是一款开源知识管理工具。通过独立的知识库空间,结构化地组织在线协作文档,实现知识的积累与沉淀,促进知识的复用与流通。
-
+
{config?.appName}
+
{config?.appDescription}
-
+ {config.externalButtonText && (
+
+ )}
diff --git a/packages/client/src/tiptap/editor/collaboration/collaboration/editor.tsx b/packages/client/src/tiptap/editor/collaboration/collaboration/editor.tsx
index 2b4a30f3..c8e5d5a4 100644
--- a/packages/client/src/tiptap/editor/collaboration/collaboration/editor.tsx
+++ b/packages/client/src/tiptap/editor/collaboration/collaboration/editor.tsx
@@ -12,12 +12,12 @@ import cls from 'classnames';
import { Banner } from 'components/banner';
import { CommentEditor } from 'components/document/comments';
import { ImageViewer } from 'components/image-viewer';
-import { LogoName } from 'components/logo';
import { getRandomColor } from 'helpers/color';
import { isAndroid, isIOS } from 'helpers/env';
import { useDocumentStyle } from 'hooks/use-document-style';
import { useNetwork } from 'hooks/use-network';
import { IsOnMobile } from 'hooks/use-on-mobile';
+import { RuntimeConfig } from 'hooks/use-runtime-config';
import { useToggle } from 'hooks/use-toggle';
import { CollaborationKit } from '../kit';
@@ -47,6 +47,7 @@ export const EditorInstance = forwardRef((props: IProps, ref) => {
renderInEditorPortal,
onTitleUpdate,
} = props;
+ const config = RuntimeConfig.useHook();
const $headerContainer = useRef();
const $mainContainer = useRef();
const { isMobile } = IsOnMobile.useHook();
@@ -110,7 +111,7 @@ export const EditorInstance = forwardRef((props: IProps, ref) => {
const listener = (event: KeyboardEvent) => {
if ((event.ctrlKey || event.metaKey) && event.keyCode == 83) {
event.preventDefault();
- Toast.info(`${LogoName}会实时保存你的数据,无需手动保存。`);
+ Toast.info(`${config.appName}会实时保存你的数据,无需手动保存。`);
return false;
}
};
@@ -120,7 +121,7 @@ export const EditorInstance = forwardRef((props: IProps, ref) => {
return () => {
window.document.removeEventListener('keydown', listener);
};
- }, []);
+ }, [config.appName]);
// 监听键盘收起、打开
useEffect(() => {
diff --git a/packages/config/lib/index.d.ts b/packages/config/lib/index.d.ts
index 6389de57..80ef0330 100644
--- a/packages/config/lib/index.d.ts
+++ b/packages/config/lib/index.d.ts
@@ -1 +1,2 @@
export declare function getConfig(): Record;
+export declare function getRuntimeConfig(): Record;
diff --git a/packages/config/lib/index.js b/packages/config/lib/index.js
index 43a39270..2e0dec48 100644
--- a/packages/config/lib/index.js
+++ b/packages/config/lib/index.js
@@ -1,6 +1,6 @@
"use strict";
exports.__esModule = true;
-exports.getConfig = void 0;
+exports.getRuntimeConfig = exports.getConfig = void 0;
var fs = require("fs-extra");
var yaml = require("js-yaml");
var path = require("path");
@@ -9,8 +9,8 @@ var FILE_ENV_NAME = {
test: 'test',
production: 'prod'
};
-var env = process.env.NODE_ENV || 'development';
function getConfig() {
+ var env = process.env.NODE_ENV || 'development';
var filePath = path.join(__dirname, '../../../config', "".concat(FILE_ENV_NAME[env], ".yaml"));
if (!fs.existsSync(filePath)) {
throw new Error("Can not find config file: ".concat(filePath));
@@ -18,3 +18,15 @@ function getConfig() {
return yaml.load(fs.readFileSync(filePath, 'utf8'));
}
exports.getConfig = getConfig;
+function getRuntimeConfig() {
+ var env = process.env.NODE_ENV || 'development';
+ var filePath = path.join(__dirname, '../../../../../../config', "".concat(FILE_ENV_NAME[env], ".yaml"));
+ if (!fs.existsSync(filePath)) {
+ filePath = path.join(__dirname, '../../../config', "".concat(FILE_ENV_NAME[env], ".yaml"));
+ }
+ if (!fs.existsSync(filePath)) {
+ throw new Error("Can not find config file: ".concat(filePath));
+ }
+ return (yaml.load(fs.readFileSync(filePath, 'utf8')).dynamic || {});
+}
+exports.getRuntimeConfig = getRuntimeConfig;
diff --git a/packages/config/src/index.ts b/packages/config/src/index.ts
index 11016ed7..704c23a6 100644
--- a/packages/config/src/index.ts
+++ b/packages/config/src/index.ts
@@ -8,9 +8,9 @@ const FILE_ENV_NAME = {
production: 'prod',
};
-const env = process.env.NODE_ENV || 'development';
-
export function getConfig() {
+ const env = process.env.NODE_ENV || 'development';
+
const filePath = path.join(__dirname, '../../../config', `${FILE_ENV_NAME[env]}.yaml`);
if (!fs.existsSync(filePath)) {
@@ -19,3 +19,19 @@ export function getConfig() {
return yaml.load(fs.readFileSync(filePath, 'utf8')) as Record;
}
+
+export function getRuntimeConfig() {
+ const env = process.env.NODE_ENV || 'development';
+
+ let filePath = path.join(__dirname, '../../../../../../config', `${FILE_ENV_NAME[env]}.yaml`);
+
+ if (!fs.existsSync(filePath)) {
+ filePath = path.join(__dirname, '../../../config', `${FILE_ENV_NAME[env]}.yaml`);
+ }
+
+ if (!fs.existsSync(filePath)) {
+ throw new Error(`Can not find config file: ${filePath}`);
+ }
+
+ return (yaml.load(fs.readFileSync(filePath, 'utf8')).dynamic || {}) as Record;
+}