From 8b05c2d576fd2452cc885ff42f5808bb7ab717d6 Mon Sep 17 00:00:00 2001 From: fantasticit Date: Tue, 11 Feb 2025 23:07:18 +0800 Subject: [PATCH 1/2] =?UTF-8?q?feat:=20=E9=83=A8=E5=88=86=E9=85=8D?= =?UTF-8?q?=E7=BD=AE=E5=8A=A8=E6=80=81=E5=8C=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- config/dev.yaml | 44 ++++++++++++------- config/docker-prod-sample.yaml | 13 ++++++ packages/client/next.config.js | 4 +- packages/client/src/components/author.tsx | 22 +++++++--- packages/client/src/components/logo/index.tsx | 43 ++++++++++-------- packages/client/src/components/seo.tsx | 18 +++++--- .../client/src/hooks/use-runtime-config.tsx | 21 +++++++++ packages/client/src/pages/_app.tsx | 44 +++++++++++-------- packages/client/src/pages/api/config.ts | 7 +++ packages/client/src/pages/index.tsx | 23 +++++----- .../collaboration/collaboration/editor.tsx | 7 +-- packages/config/lib/index.d.ts | 1 + packages/config/lib/index.js | 16 ++++++- packages/config/src/index.ts | 20 ++++++++- 14 files changed, 197 insertions(+), 86 deletions(-) create mode 100644 packages/client/src/hooks/use-runtime-config.tsx create mode 100644 packages/client/src/pages/api/config.ts 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; +} From a216038f4752f472173a08c59bed5ecbe5fb8176 Mon Sep 17 00:00:00 2001 From: fantasticit Date: Sun, 16 Feb 2025 12:50:32 +0800 Subject: [PATCH 2/2] =?UTF-8?q?feat:=20=E5=A2=9E=E5=8A=A0=E5=B9=BF?= =?UTF-8?q?=E5=91=8A=E9=85=8D=E7=BD=AE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- config/dev.yaml | 13 -- config/docker-prod-sample.yaml | 13 -- dynamic.yaml | 12 ++ .../admin/system-config/ad/index.tsx | 141 +++++++++++++++ .../components/admin/system-config/index.tsx | 6 + .../admin/system-config/mail/index.tsx | 2 + .../admin/system-config/system/index.tsx | 2 + .../document/reader/public/index.module.scss | 18 ++ .../document/reader/public/index.tsx | 167 +++++++++++++----- packages/client/src/data/ad.ts | 42 +++++ .../collaboration/collaboration/editor.tsx | 2 + packages/config/lib/index.js | 4 +- packages/config/package.json | 5 +- packages/config/src/index.ts | 4 +- packages/constants/package.json | 4 +- packages/domains/lib/api/ad.d.ts | 23 +++ packages/domains/lib/api/ad.js | 26 +++ packages/domains/lib/api/index.d.ts | 1 + packages/domains/lib/api/index.js | 7 +- packages/domains/lib/index.js | 6 +- packages/domains/lib/models/ad.d.ts | 10 ++ packages/domains/lib/models/ad.js | 8 + packages/domains/lib/models/index.d.ts | 1 + packages/domains/lib/models/index.js | 7 +- packages/domains/package.json | 4 +- packages/domains/src/api/ad.ts | 25 +++ packages/domains/src/api/index.ts | 1 + packages/domains/src/models/ad.ts | 11 ++ packages/domains/src/models/index.ts | 1 + packages/server/src/app.module.ts | 4 + .../server/src/controllers/ad.controller.ts | 48 +++++ packages/server/src/dtos/ad.dto.ts | 17 ++ packages/server/src/entities/ad.entity.ts | 37 ++++ packages/server/src/modules/ad.module.ts | 16 ++ packages/server/src/services/ad.service.ts | 47 +++++ pnpm-lock.yaml | 12 +- 36 files changed, 660 insertions(+), 87 deletions(-) create mode 100644 dynamic.yaml create mode 100644 packages/client/src/components/admin/system-config/ad/index.tsx create mode 100644 packages/client/src/data/ad.ts create mode 100644 packages/domains/lib/api/ad.d.ts create mode 100644 packages/domains/lib/api/ad.js create mode 100644 packages/domains/lib/models/ad.d.ts create mode 100644 packages/domains/lib/models/ad.js create mode 100644 packages/domains/src/api/ad.ts create mode 100644 packages/domains/src/models/ad.ts create mode 100644 packages/server/src/controllers/ad.controller.ts create mode 100644 packages/server/src/dtos/ad.dto.ts create mode 100644 packages/server/src/entities/ad.entity.ts create mode 100644 packages/server/src/modules/ad.module.ts create mode 100644 packages/server/src/services/ad.service.ts diff --git a/config/dev.yaml b/config/dev.yaml index 66572641..b0e3900a 100644 --- a/config/dev.yaml +++ b/config/dev.yaml @@ -1,16 +1,3 @@ -# 以下为前台动态渲染的配置,修改后,无需打包,重启服务即可 -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/config/docker-prod-sample.yaml b/config/docker-prod-sample.yaml index bbe45e47..e60c2c9b 100644 --- a/config/docker-prod-sample.yaml +++ b/config/docker-prod-sample.yaml @@ -1,16 +1,3 @@ -# 以下为前台动态渲染的配置,修改后,无需打包,重启服务即可 -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/dynamic.yaml b/dynamic.yaml new file mode 100644 index 00000000..d3d34347 --- /dev/null +++ b/dynamic.yaml @@ -0,0 +1,12 @@ +# 以下为前台动态渲染的配置,修改后,无需打包,重启服务即可 +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' diff --git a/packages/client/src/components/admin/system-config/ad/index.tsx b/packages/client/src/components/admin/system-config/ad/index.tsx new file mode 100644 index 00000000..d5434bf4 --- /dev/null +++ b/packages/client/src/components/admin/system-config/ad/index.tsx @@ -0,0 +1,141 @@ +import React, { useCallback, useRef, useState } from 'react'; + +import { Avatar, Button, Form, Image, Modal, Space, Table } from '@douyinfe/semi-ui'; +import { FormApi } from '@douyinfe/semi-ui/lib/es/form'; + +import { AdType } from '@think/domains'; + +import { DataRender } from 'components/data-render'; +import { Upload } from 'components/upload'; +import { useAd } from 'data/ad'; + +const { Option } = Form.Select; + +const AdTypeTextMap = { + [AdType.shareDocCover]: '分享文档主图位', + [AdType.shareDocAside]: '分享文档侧边位', +}; + +const columns = [ + { + title: '类型', + dataIndex: 'type', + render: (type) => { + return AdTypeTextMap[type]; + }, + }, + { + title: '广告图', + dataIndex: 'cover', + render: (url) => { + return ( +
+ +
+ ); + }, + }, + { + title: '广告链接', + dataIndex: 'url', + }, +]; + +export const Ad = () => { + const { data, loading, error, createAd, deleteAd } = useAd(); + const [visible, toggleVisible] = useState(false); + const $form = useRef(); + const [currentCover, setCurrentCover] = useState(''); + + const setCover = useCallback((url) => { + $form.current.setValue('cover', url); + setCurrentCover(url); + }, []); + + const handleOk = useCallback(() => { + $form.current.validate().then((values) => { + if (!values.email) { + delete values.email; + } + createAd(values).then(() => { + $form.current.reset(); + setCover(''); + toggleVisible(false); + }); + }); + }, [createAd, setCover, toggleVisible]); + + return ( + ( +
+ + { + toggleVisible(false); + }} + style={{ maxWidth: '96vw' }} + onOk={handleOk} + > +
($form.current = formApi)} + labelPosition="left" + > + + + + + + + + + + + + + +
+
+ { + return ( + + ); + }, + }, + ]} + dataSource={data} + pagination={false} + /> + + )} + /> + ); +}; diff --git a/packages/client/src/components/admin/system-config/index.tsx b/packages/client/src/components/admin/system-config/index.tsx index 8cc4228b..11b7b816 100644 --- a/packages/client/src/components/admin/system-config/index.tsx +++ b/packages/client/src/components/admin/system-config/index.tsx @@ -2,6 +2,7 @@ import React from 'react'; import { TabPane, Tabs } from '@douyinfe/semi-ui'; +import { Ad } from './ad'; import { Mail } from './mail'; import { System } from './system'; @@ -13,6 +14,7 @@ interface IProps { const TitleMap = { base: '系统管理', mail: '邮箱服务', + ad: '广告管理', }; export const SystemConfig: React.FC = ({ tab, onNavigate }) => { @@ -25,6 +27,10 @@ export const SystemConfig: React.FC = ({ tab, onNavigate }) => { + + + + ); }; diff --git a/packages/client/src/components/admin/system-config/mail/index.tsx b/packages/client/src/components/admin/system-config/mail/index.tsx index 968dae5d..dc0a9d00 100644 --- a/packages/client/src/components/admin/system-config/mail/index.tsx +++ b/packages/client/src/components/admin/system-config/mail/index.tsx @@ -35,6 +35,8 @@ export const Mail = () => { closeIcon={null} /> + {/* + // @ts-ignore */}
{ error={error} normalContent={() => (
+ {/* + // @ts-ignore */} div { + height: 100%; + } + } + .contentWrap { flex: 1; overflow: hidden; diff --git a/packages/client/src/components/document/reader/public/index.tsx b/packages/client/src/components/document/reader/public/index.tsx index 945aa199..6ba12a55 100644 --- a/packages/client/src/components/document/reader/public/index.tsx +++ b/packages/client/src/components/document/reader/public/index.tsx @@ -1,28 +1,40 @@ -import { Breadcrumb, Button, Form, Layout, Nav, Skeleton, Space, Tooltip, Typography } from '@douyinfe/semi-ui'; -import { IconRoute } from '@douyinfe/semi-icons'; -import { FormApi } from '@douyinfe/semi-ui/lib/es/form'; -import { DataRender } from 'components/data-render'; -import { Divider } from 'components/divider'; -import { DocumentStyle } from 'components/document/style'; -import { LogoImage, LogoText } from 'components/logo'; -import { Seo } from 'components/seo'; -import { Theme } from 'components/theme'; -import { User } from 'components/user'; -import { usePublicDocumentDetail } from 'data/document'; -import { useMount } from 'hooks/use-mount'; -import { IsOnMobile } from 'hooks/use-on-mobile'; -import { SecureDocumentIllustration } from 'illustrations/secure-document'; -import Link from 'next/link'; -import React, { useCallback, useMemo, useRef } from 'react'; -import { createPortal } from 'react-dom'; -import { CollaborationEditor } from 'tiptap/editor'; +import { + Breadcrumb, + Button, + Form, + Layout, + Nav, + Skeleton, + Space, + Tooltip, + Typography, + Carousel, +} from "@douyinfe/semi-ui"; +import { IconRoute } from "@douyinfe/semi-icons"; +import { FormApi } from "@douyinfe/semi-ui/lib/es/form"; +import { DataRender } from "components/data-render"; +import { Divider } from "components/divider"; +import { DocumentStyle } from "components/document/style"; +import { LogoImage, LogoText } from "components/logo"; +import { Seo } from "components/seo"; +import { Theme } from "components/theme"; +import { User } from "components/user"; +import { usePublicDocumentDetail } from "data/document"; +import { useMount } from "hooks/use-mount"; +import { IsOnMobile } from "hooks/use-on-mobile"; +import { SecureDocumentIllustration } from "illustrations/secure-document"; +import Link from "next/link"; +import React, { useCallback, useMemo, useRef } from "react"; +import { createPortal } from "react-dom"; +import { CollaborationEditor } from "tiptap/editor"; -import { Author } from '../author'; -import styles from './index.module.scss'; -import Router from 'next/router'; -import { useRouterQuery } from 'hooks/use-router-query'; -import { IDocument, IWiki } from '@think/domains'; -import { DocumentFullscreen } from 'components/document/fullscreen'; +import { Author } from "../author"; +import styles from "./index.module.scss"; +import Router from "next/router"; +import { useRouterQuery } from "hooks/use-router-query"; +import { AdType, IDocument, IWiki } from "@think/domains"; +import { DocumentFullscreen } from "components/document/fullscreen"; +import { useAd } from "data/ad"; const { Header } = Layout; const { Text } = Typography; @@ -32,18 +44,25 @@ interface IProps { hideLogo?: boolean; } -export const DocumentPublicReader: React.FC = ({ documentId, hideLogo = false }) => { +export const DocumentPublicReader: React.FC = ({ + documentId, + hideLogo = false, +}) => { const $form = useRef(); const mounted = useMount(); - const { wikiId: currentWikiId } = useRouterQuery<{ wikiId: IWiki['id']; documentId: IDocument['id'] }>(); + const { wikiId: currentWikiId } = useRouterQuery<{ + wikiId: IWiki["id"]; + documentId: IDocument["id"]; + }>(); const { data, loading, error, query } = usePublicDocumentDetail(documentId); + const { data: ads } = useAd(); const { isMobile } = IsOnMobile.useHook(); - + const renderAuthor = useCallback( (element) => { if (!document) return null; - const target = element && element.querySelector('.ProseMirror .title'); + const target = element && element.querySelector(".ProseMirror .title"); if (target) { return createPortal(, target); @@ -62,27 +81,42 @@ export const DocumentPublicReader: React.FC = ({ documentId, hideLogo = const toPublicWikiOrDocumentURL = useCallback(() => { Router.push({ - pathname: currentWikiId ? '/share/document/[documentId]' : '/share/wiki/[wikiId]/document/[documentId]', - query: currentWikiId ? { documentId } : { wikiId: data.wikiId, documentId }, + pathname: currentWikiId + ? "/share/document/[documentId]" + : "/share/wiki/[wikiId]/document/[documentId]", + query: currentWikiId + ? { documentId } + : { wikiId: data.wikiId, documentId }, }); }, [currentWikiId, documentId, data]); + console.log(ads || []); + const content = useMemo(() => { if (error) { // @ts-ignore if (error.statusCode === 400) { return (
- + ($form.current = formApi)} labelPosition="left" onSubmit={handleOk} layout="horizontal" > - + @@ -94,16 +128,16 @@ export const DocumentPublicReader: React.FC = ({ documentId, hideLogo = return (
- {(error && (error as Error).message) || '未知错误'} + {(error && (error as Error).message) || "未知错误"}
); @@ -144,9 +178,14 @@ export const DocumentPublicReader: React.FC = ({ documentId, hideLogo = } footer={ - {!isMobile && data && } - -