From 2b44435c2a3f8792d7574980ebd01496b14b22ae Mon Sep 17 00:00:00 2001 From: lsy Date: Thu, 14 Nov 2024 01:44:26 +0800 Subject: [PATCH] =?UTF-8?q?=E5=89=8D=E7=AB=AF=EF=BC=9A=E5=AE=9E=E7=8E=B0ex?= =?UTF-8?q?tension=E6=8E=A5=E5=8F=A3=EF=BC=8C=E5=90=8E=E7=AB=AF=EF=BC=9A?= =?UTF-8?q?=E4=BF=AE=E5=A4=8D=E6=95=B0=E6=8D=AE=E5=BA=93=E5=AD=97=E6=AE=B5?= =?UTF-8?q?=E9=94=99=E8=AF=AF?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../database/relational/postgresql/init.sql | 12 +-- frontend/app/env.d.ts | 2 +- frontend/app/root.tsx | 47 ++++------- frontend/hooks/themeContext.tsx | 23 +++++ frontend/service/extensionService.ts | 84 +++++++++++++++++++ frontend/service/pluginService.ts | 24 ++++++ frontend/service/themeService.ts | 52 ++++++++++++ frontend/theme/default/theme.config.ts | 25 ++++++ frontend/tsconfig.json | 2 + frontend/types/extensionType.ts | 20 +++++ frontend/types/plugin.ts | 41 --------- frontend/types/pluginType.ts | 48 +++++++++++ frontend/types/templateType.ts | 34 ++++++++ frontend/types/{theme.ts => themeType.ts} | 28 ++++--- 14 files changed, 352 insertions(+), 90 deletions(-) create mode 100644 frontend/hooks/themeContext.tsx create mode 100644 frontend/service/extensionService.ts create mode 100644 frontend/service/pluginService.ts create mode 100644 frontend/service/themeService.ts create mode 100644 frontend/theme/default/theme.config.ts create mode 100644 frontend/types/extensionType.ts delete mode 100644 frontend/types/plugin.ts create mode 100644 frontend/types/pluginType.ts create mode 100644 frontend/types/templateType.ts rename frontend/types/{theme.ts => themeType.ts} (63%) diff --git a/backend/src/database/relational/postgresql/init.sql b/backend/src/database/relational/postgresql/init.sql index 7a98a95..5ad6285 100644 --- a/backend/src/database/relational/postgresql/init.sql +++ b/backend/src/database/relational/postgresql/init.sql @@ -7,11 +7,11 @@ CREATE EXTENSION IF NOT EXISTS pgcrypto; --- 用户权限枚举 CREATE TYPE privilege_level AS ENUM ('visitor', 'contributor', 'administrators'); --- 用户信息表 -CREATE TABLE person +CREATE TABLE persons ( person_name VARCHAR(100) PRIMARY KEY, --- 用户名 person_email VARCHAR(255) UNIQUE NOT NULL, --- 用户邮箱 - person_picture VARCHAR(255), --- 用户头像 + person_icon VARCHAR(255), --- 用户头像 person_password VARCHAR(255) NOT NULL, --- 用户密码 person_created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, --- 用户创建时间 person_updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, --- 用户更新时间 @@ -28,7 +28,7 @@ CREATE TABLE pages page_id SERIAL PRIMARY KEY, --- 独立页面唯一id主键 page_meta_keywords VARCHAR(255) NOT NULL, ---页面mata关键字 page_meta_description VARCHAR(255) NOT NULL, ---页面mata描述 - post_title VARCHAR(255) NOT NULL, --- 文章标题 + page_title VARCHAR(255) NOT NULL, --- 文章标题 page_content TEXT NOT NULL, --- 独立页面内容 page_mould VARCHAR(50), --- 独立页面模板名称 page_fields JSON, --- 自定义字段 @@ -47,8 +47,8 @@ CREATE TABLE posts post_content TEXT NOT NULL, --- 文章内容 post_status publication_status DEFAULT 'draft', --- 文章状态 post_editor BOOLEAN DEFAULT FALSE, --- 文章是否编辑未保存 - posts_unsaved_content TEXT, --- 未保存的文章 - posts_path VARCHAR(255), --- 文章路径 + post_unsaved_content TEXT, --- 未保存的文章 + post_path VARCHAR(255), --- 文章路径 post_created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, --- 文章创建时间 post_updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, --- 文章更新时间 post_published_at TIMESTAMP, --- 文章发布时间 @@ -58,7 +58,7 @@ CREATE TABLE posts CREATE TABLE tags ( tag_name VARCHAR(50) PRIMARY KEY CHECK (LOWER(tag_name) = tag_name), --- 标签名称主键 - tag_picture VARCHAR(255) --- 标签图标 + tag_icon VARCHAR(255) --- 标签图标 ); --- 文章与标签的关系表 CREATE TABLE post_tags diff --git a/frontend/app/env.d.ts b/frontend/app/env.d.ts index 66cb48d..4553dcc 100644 --- a/frontend/app/env.d.ts +++ b/frontend/app/env.d.ts @@ -7,10 +7,10 @@ /// interface ImportMetaEnv { - readonly VITE_APP_TYPE: boolean; //用于判断是动态博客还是静态博客 readonly VITE_APP_API: string; // 用于访问API的基础URL readonly VITE_THEME_PATH: string; // 存储主题文件的目录路径 readonly VITE_CONTENT_PATH: string; //mark文章存储的位置 + readonly VITE_CONTENT_STATIC_PATH: string; //导出文章静态存储的位置 readonly VITE_PLUGINS_PATH: string; // 存储插件文件的目录路径 readonly VITE_ASSETS_PATH: string; // 存储静态资源的目录路径 } diff --git a/frontend/app/root.tsx b/frontend/app/root.tsx index 217994e..8913fdb 100644 --- a/frontend/app/root.tsx +++ b/frontend/app/root.tsx @@ -1,40 +1,25 @@ -import { - Links, - Meta, - Outlet, - Scripts, - ScrollRestoration, -} from "@remix-run/react"; -import type { LinksFunction } from "@remix-run/node"; - +import { Meta, Outlet, Scripts, ScrollRestoration } from "@remix-run/react"; +import type { LoaderFunction } from "@remix-run/node"; +import { useLoaderData } from "@remix-run/react"; +import { ThemeProvider } from "hooks/themeContext"; +import { createContext, useContext, ReactNode } from 'react'; import "./tailwind.css"; -export const links: LinksFunction = () => [ - { rel: "preconnect", href: "https://fonts.googleapis.com" }, - { - rel: "preconnect", - href: "https://fonts.gstatic.com", - crossOrigin: "anonymous", - }, - { - rel: "stylesheet", - href: "https://fonts.googleapis.com/css2?family=Inter:ital,opsz,wght@0,14..32,100..900;1,14..32,100..900&display=swap", - }, -]; - export function Layout({ children }: { children: React.ReactNode }) { - console.log(import.meta.env.VITE_THEME_NAME); - return ( - + - - {children} + + {children} + @@ -43,5 +28,9 @@ export function Layout({ children }: { children: React.ReactNode }) { } export default function App() { - return ; -} + return ( + + + + ); +} \ No newline at end of file diff --git a/frontend/hooks/themeContext.tsx b/frontend/hooks/themeContext.tsx new file mode 100644 index 0000000..004567f --- /dev/null +++ b/frontend/hooks/themeContext.tsx @@ -0,0 +1,23 @@ +// service/theme/themeContext.tsx +import { createContext, useContext, ReactNode } from 'react'; +import { ThemeService } from 'service/themeService'; + +const ThemeContext = createContext(undefined); + +export function ThemeProvider({ children }: { children: ReactNode }) { + const themeService = ThemeService.getInstance(); + + return ( + + {children} + + ); +} + +export function useTheme(): ThemeService { + const context = useContext(ThemeContext); + if (context === undefined) { + throw new Error('useTheme must be used within a ThemeProvider'); + } + return context; +} \ No newline at end of file diff --git a/frontend/service/extensionService.ts b/frontend/service/extensionService.ts new file mode 100644 index 0000000..e060e6e --- /dev/null +++ b/frontend/service/extensionService.ts @@ -0,0 +1,84 @@ +// File path: service/extensionService.ts + +/** + * ExtensionManage 是一个单例类,用于管理扩展的实例。 + * 提供注册、触发和移除插件扩展的功能。 + */ +import { ExtensionType } from "types/extensionType"; +import React from "react"; + +class ExtensionManage { + /** 存储扩展的映射,键为扩展名称,值为插件名称和扩展的集合 */ + private extensions: Map> = new Map(); + /** ExtensionManage 的唯一实例 */ + private static instance: ExtensionManage; + + /** 私有构造函数,防止外部实例化 */ + private constructor() {} + + /** + * 获取 ExtensionManage 的唯一实例。 + * @returns {ExtensionManage} 返回 ExtensionManage 的唯一实例。 + */ + public static getInstance(): ExtensionManage { + if (!this.instance) { + this.instance = new ExtensionManage(); + } + return this.instance; + } + + /** 注册扩展 */ + private register(extensionName: string, pluginName: string, extension: ExtensionType) { + const handlers = this.extensions.get(extensionName) || new Set(); + handlers.add({ pluginName, extension }); + this.extensions.set(extensionName, handlers); + } + + /** 执行扩展方法 */ + private executeExtensionMethod(extensionName: string, method: keyof ExtensionType, ...args: any[]): Set { + const result = new Set(); + const handlers = this.extensions.get(extensionName); + + if (handlers) { + handlers.forEach(({ extension }) => { + const methodFunction = extension[method]; + if (methodFunction) { + try { + const value = methodFunction(...args); + if (value && (typeof value === 'string' || React.isValidElement(value))) { + result.add(value as T); + } + } catch (error) { + console.error(`Error executing hook ${extensionName}:`, error); + } + } + }); + } + return result; + } + + /** 触发扩展的动作 */ + private triggerAction(extensionName: string, ...args: any[]): void { + this.executeExtensionMethod(extensionName, 'action', ...args); + } + + /** 触发扩展的组件 */ + private triggerComponent(extensionName: string, ...args: any[]): Set { + return this.executeExtensionMethod(extensionName, 'component', ...args); + } + + /** 触发扩展的文本 */ + private triggerText(extensionName: string, ...args: any[]): Set { + return this.executeExtensionMethod(extensionName, 'text', ...args); + } + + /** 移除指定插件的扩展 */ + private removePluginExtensions(pluginName: string) { + this.extensions.forEach((handlers, extensionName) => { + const newHandlers = new Set( + Array.from(handlers).filter(handler => handler.pluginName !== pluginName) + ); + this.extensions.set(extensionName, newHandlers); + }); + } +} diff --git a/frontend/service/pluginService.ts b/frontend/service/pluginService.ts new file mode 100644 index 0000000..742fb84 --- /dev/null +++ b/frontend/service/pluginService.ts @@ -0,0 +1,24 @@ +import { PluginConfig ,PluginType ,PluginConfiguration} from "types/pluginType"; + + + +export class PluginService { + private static pluginInstance: PluginService | null = null; // 单例实例 + private pluginComponents: Map> = new Map(); // 插件组件缓存 + private constructor (){}; + + public static getInstance(): PluginService { + if (!this.pluginInstance) { + this.pluginInstance = new PluginService(); + } + return this.pluginInstance; + } + + +} \ No newline at end of file diff --git a/frontend/service/themeService.ts b/frontend/service/themeService.ts new file mode 100644 index 0000000..e684b2f --- /dev/null +++ b/frontend/service/themeService.ts @@ -0,0 +1,52 @@ +// service/theme/themeService.ts +import type { ThemeConfig } from 'types/themeType'; + +export class ThemeService { + private static themeInstance: ThemeService; // 单例实例 + private themeConfig: ThemeConfig | null = null; // 当前主题配置 + private themeComponents: Map = new Map(); // 主题组件缓存 + + private constructor() { } // 私有构造函数,防止外部实例化 + + // 获取单例实例 + public static getInstance(): ThemeService { + if (!ThemeService.themeInstance) { + ThemeService.themeInstance = new ThemeService(); + } + return ThemeService.themeInstance; + } + + // 加载主题 + async loadTheme(themeConfig: ThemeConfig): Promise { + this.themeConfig = themeConfig; // 设置当前主题 + await this.loadThemeComponents(themeConfig); // 加载主题组件 + } + + // 加载主题组件 + private async loadThemeComponents(config:ThemeConfig): Promise { + // 清除现有组件缓存 + this.themeComponents.clear(); + + // 动态导入主题入口组件 + const entryComponent = await import(config.entry); + this.themeComponents.set('entry', entryComponent.default); // 缓存入口路径 + + // 加载所有模板组件 + for (const [key, template] of config.templates.entries()) { + const component = await import(template.path); + this.themeComponents.set(key, component.default); // 缓存模板组件 + } + } + + // 获取指定模板名称的组件 + getComponent(templateName: string): React.ComponentType | null { + return this.themeComponents.get(templateName) || null; // 返回组件或null + } + + // 获取当前主题配置 + getCurrentTheme(): ThemeConfig | null { + return this.currentTheme; // 返回当前主题配置 + } +} + + diff --git a/frontend/theme/default/theme.config.ts b/frontend/theme/default/theme.config.ts new file mode 100644 index 0000000..d8be258 --- /dev/null +++ b/frontend/theme/default/theme.config.ts @@ -0,0 +1,25 @@ +import { ThemeConfig } from "types/themeType"; + +export const themeConfig: ThemeConfig = { + name: 'default', + displayName: '默认主题', + version: '1.0.0', + description: '一个简约风格的博客主题', + author: 'lsy', + entry: './index.tsx', + templates: new Map([ + ['page', { + path: './templates/page', + name: '文章列表模板', + description: '博客首页展示模板' + }], + ]), + + settingsSchema: undefined, + routes: { + post: "", + tag: "", + category: "", + page: "" + } +} \ No newline at end of file diff --git a/frontend/tsconfig.json b/frontend/tsconfig.json index 37f8077..7d099cd 100644 --- a/frontend/tsconfig.json +++ b/frontend/tsconfig.json @@ -25,6 +25,8 @@ "paths": { "theme/*":["./theme/*"], "types/*":["./types/*"], + "service/*":["./service/*"], + "hooks/*":["./hooks/*"], }, // Vite takes care of building everything, not tsc. diff --git a/frontend/types/extensionType.ts b/frontend/types/extensionType.ts new file mode 100644 index 0000000..3f2a3ae --- /dev/null +++ b/frontend/types/extensionType.ts @@ -0,0 +1,20 @@ +/** + * File path: types/extensionType.ts + * + * 该文件定义了扩展类型接口 ExtensionType,包含可选的操作、组件和文本生成函数。 + * + * 接口属性说明: + * - action: 可选的操作函数,接受任意参数并返回 void。 + * - component: 可选的组件函数,接受任意参数并返回一个 React 组件。 + * - text: 可选的文本生成函数,接受任意参数并返回一个字符串。 + */ +export interface ExtensionType { + /** 可选的操作函数,接受任意参数并返回 void */ + action?: (...args: any[]) => void; + + /** 可选的组件函数,接受任意参数并返回一个 React 组件 */ + component?: (...args: any[]) => React.FC; + + /** 可选的文本生成函数,接受任意参数并返回一个字符串 */ + text?: (...args: any[]) => string; +} diff --git a/frontend/types/plugin.ts b/frontend/types/plugin.ts deleted file mode 100644 index 3fcd4b4..0000000 --- a/frontend/types/plugin.ts +++ /dev/null @@ -1,41 +0,0 @@ -// File path: types/plugin.ts - -/** - * 插件配置接口 - * - * 该接口定义了插件的基本配置,包括插件的名称、版本、描述、作者等信息。 - * 还包括插件的生命周期钩子和依赖项的配置。 - */ -export interface PluginConfig { - name: string; // 插件名称 - version: string; // 插件版本 - displayName: string; // 插件显示名称 - description?: string; // 插件描述(可选) - author?: string; // 插件作者(可选) - enabled: boolean; // 插件是否启用 - icon?: string; // 插件图标URL(可选) - managePath?: string; // 插件管理页面路径(可选) - entry: string; // 插件入口组件路径 - // 主题配置 - settingsSchema?: { - type: string; // 配置类型 - properties: Record; - }; - // 依赖 - dependencies?: { - plugins?: string[]; // 依赖的插件列表(可选) - themes?: string[]; // 依赖的主题列表(可选) - }; - // 插件生命周期钩子 - hooks?: { - onInstall?: string; // 安装时调用的钩子(可选) - onUninstall?: string; // 卸载时调用的钩子(可选) - onEnable?: string; // 启用时调用的钩子(可选) - onDisable?: string; // 禁用时调用的钩子(可选) - }; -} \ No newline at end of file diff --git a/frontend/types/pluginType.ts b/frontend/types/pluginType.ts new file mode 100644 index 0000000..c64e537 --- /dev/null +++ b/frontend/types/pluginType.ts @@ -0,0 +1,48 @@ +// File path: types/pluginType.ts + +/** + * 插件配置接口 + * + * 该接口定义了插件的基本配置,包括插件的名称、版本、描述、作者等信息。 + * 还包括插件的生命周期钩子和依赖项的配置。 + */ +export interface PluginConfig { + name: string; // 插件名称 + version: string; // 插件版本 + displayName: string; // 插件显示名称 + description?: string; // 插件描述(可选) + author?: string; // 插件作者(可选) + enabled: boolean; // 插件是否启用 + icon?: string; // 插件图标URL(可选) + managePath?: string; // 插件管理页面路径(可选) + configuration?: PluginConfiguration; // 插件配置 + dependencies?: { + plugins?: string[]; // 依赖的插件列表(可选) + themes?: string[]; // 依赖的主题列表(可选) + }; + hooks?: { + onInstall?: (context: any) => {}; // 安装时调用的钩子(可选) + onUninstall?: (context: any) => {}; // 卸载时调用的钩子(可选) + onEnable?: (context: any) => {}; // 启用时调用的钩子(可选) + onDisable?: (context: any) => {}; // 禁用时调用的钩子(可选) + }; + routs: Set<{ + description?: string; // 路由描述(可选) + path: string; // 路由路径 + }>; +} + +/** + * 插件配置接口 + * + * 该接口定义了插件的配置类型及其属性。 + */ +export interface PluginConfiguration { + type: string; // 配置类型 + properties: Record; +} \ No newline at end of file diff --git a/frontend/types/templateType.ts b/frontend/types/templateType.ts new file mode 100644 index 0000000..9c71c2e --- /dev/null +++ b/frontend/types/templateType.ts @@ -0,0 +1,34 @@ +// File path: types/templateType.ts +/** + * 插件配置接口 + * + * 该接口定义了模板的基本配置,包括依赖项、钩子和页面渲染函数。 + */ +import React from "react"; +import { ExtensionType } from "types/extensionType"; + +export interface TemplateConfig { + /** + * 依赖项配置 + * + * 记录每个依赖字段的名称、描述和是否必填。 + */ + dependencies: Record; + + + extensions?: Record; + + /** + * 页面渲染函数 + * + * 接受参数并返回一个 React 组件。 + */ + page(params: Map): React.FC; +} diff --git a/frontend/types/theme.ts b/frontend/types/themeType.ts similarity index 63% rename from frontend/types/theme.ts rename to frontend/types/themeType.ts index 2a06256..5a661e4 100644 --- a/frontend/types/theme.ts +++ b/frontend/types/themeType.ts @@ -1,4 +1,4 @@ -// types/theme.ts +// File path: types/themeType.ts /** * 主题配置和模板接口定义文件 * 该文件包含主题配置接口和主题模板接口的定义,用于主题管理和渲染。 @@ -6,17 +6,18 @@ /** * 主题配置接口 + * 定义主题的基本信息、模板、全局配置、依赖、钩子和路由。 */ export interface ThemeConfig { - name: string; // 主题的唯一标识符 + name: string; // 主题的名称 displayName: string; // 主题的显示名称 version: string; // 主题的版本号 description?: string; // 主题的描述信息 author?: string; // 主题的作者信息 - entry: string; // 主题的入口组件路径 + entry?: string; // 主题的入口路径 templates: Map; // 主题模板的映射表 /** 主题全局配置 */ - globalSettings: { + globalSettings?: { layout?: string; // 主题的布局配置 css?: string; // 主题的CSS配置 }; @@ -34,22 +35,23 @@ export interface ThemeConfig { }; /** 钩子 */ hooks?: { - beforeRender?: string; // 渲染前执行的钩子 - afterRender?: string; // 渲染后执行的钩子 - onActivate?: string; // 主题激活时执行的钩子 - onDeactivate?: string; // 主题停用时执行的钩子 + onActivate?: () => {}; // 主题激活时执行的钩子 + onDeactivate?: () => {}; // 主题停用时执行的钩子 }; /** 路由 */ - routes:{ - post:string; // 文章使用的模板 - tag:string; // 标签使用的模板 - category:string; // 分类使用的模板 - page:string; // 独立页面模板路径 + routes: { + index: string; // 首页使用的模板 + post: string; // 文章使用的模板 + tag: string; // 标签使用的模板 + category: string; // 分类使用的模板 + error: string; // 错误页面用的模板 + page: Map; // 独立页面模板 } } /** * 主题模板接口 + * 定义主题模板的基本信息,包括路径、名称和描述。 */ export interface ThemeTemplate { path: string; // 模板文件的路径