diff --git a/packages/plugins/@nocobase/plugin-field-markdown-vditor/src/client/components/Edit.tsx b/packages/plugins/@nocobase/plugin-field-markdown-vditor/src/client/components/Edit.tsx index 51adb9b37b7..4791f92a138 100644 --- a/packages/plugins/@nocobase/plugin-field-markdown-vditor/src/client/components/Edit.tsx +++ b/packages/plugins/@nocobase/plugin-field-markdown-vditor/src/client/components/Edit.tsx @@ -7,10 +7,12 @@ * For more information, please refer to: https://www.nocobase.com/agreement. */ -import { useAPIClient, useApp, withDynamicSchemaProps } from '@nocobase/client'; +import { useAPIClient, useCompile, usePlugin, withDynamicSchemaProps } from '@nocobase/client'; import React, { useEffect, useLayoutEffect, useMemo, useRef, useState } from 'react'; +import { useTranslation } from 'react-i18next'; import Vditor from 'vditor'; import { defaultToolbar } from '../interfaces/markdown-vditor'; +import { NAMESPACE } from '../locale'; import { useCDN } from './const'; import useStyle from './style'; @@ -24,11 +26,15 @@ export const Edit = withDynamicSchemaProps((props) => { const vdFullscreen = useRef(false); const containerRef = useRef(null); const containerParentRef = useRef(null); - const app = useApp(); const apiClient = useAPIClient(); const cdn = useCDN(); const { wrapSSR, hashId, componentCls: containerClassName } = useStyle(); const locale = apiClient.auth.locale || 'en-US'; + const fileManagerPlugin: any = usePlugin('@nocobase/plugin-file-manager'); + const compile = useCompile(); + const compileRef = useRef(compile); + compileRef.current = compile; + const { t } = useTranslation(); const lang: any = useMemo(() => { const currentLang = locale.replace(/-/g, '_'); @@ -41,7 +47,6 @@ export const Edit = withDynamicSchemaProps((props) => { useEffect(() => { if (!containerRef.current) return; - const uploadFileCollection = fileCollection || 'attachments'; const toolbarConfig = toolbar ?? defaultToolbar; const vditor = new Vditor(containerRef.current, { @@ -68,24 +73,63 @@ export const Edit = withDynamicSchemaProps((props) => { onChange(value); }, upload: { - url: app.getApiUrl(`${uploadFileCollection}:create`), - headers: apiClient.getHeaders(), multiple: false, fieldName: 'file', - max: 1024 * 1024 * 1024, // 1G - format(files, responseText) { - const response = JSON.parse(responseText); - const formatResponse = { - msg: '', - code: 0, - data: { - errFiles: [], - succMap: { - [response.data.filename]: response.data.url, - }, - }, - }; - return JSON.stringify(formatResponse); + async handler(files: File[]) { + const file = files[0]; + + // Need to ensure focus is in the current input box before uploading + vditor.focus(); + + const { data: checkData } = await apiClient.resource('vditor').check({ + fileCollectionName: fileCollection, + }); + + if (!checkData?.data?.isSupportToUploadFiles) { + vditor.tip( + t('vditor.uploadError.message', { ns: NAMESPACE, storageTitle: checkData.data.storage?.title }), + 0, + ); + return; + } + + vditor.tip(t('uploading'), 0); + const { data, errorMessage } = await fileManagerPlugin.uploadFile({ + file, + fileCollectionName: fileCollection, + storageId: checkData?.data?.storage?.id, + storageType: checkData?.data?.storage?.type, + storageRules: checkData?.data?.storage?.rules, + }); + + if (errorMessage) { + vditor.tip(compileRef.current(errorMessage), 3000); + return; + } + + if (!data) { + vditor.tip(t('Response data is empty', { ns: NAMESPACE }), 3000); + return; + } + + const fileName = data.filename; + const fileUrl = data.url; + + // Check if the uploaded file is an image + const isImage = file.type.startsWith('image/'); + + if (isImage) { + // Insert as an image - will be displayed in the editor + vditor.insertValue(`![${fileName}](${fileUrl})`); + } else { + // For non-image files, insert as a download link + vditor.insertValue(`[${fileName}](${fileUrl})`); + } + + // hide the tip + vditor.tip(t(''), 10); + + return null; }, }, }); @@ -94,7 +138,8 @@ export const Edit = withDynamicSchemaProps((props) => { vdRef.current?.destroy(); vdRef.current = undefined; }; - }, [fileCollection, toolbar?.join(',')]); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [toolbar?.join(',')]); useEffect(() => { if (editorReady && vdRef.current) { @@ -155,7 +200,7 @@ export const Edit = withDynamicSchemaProps((props) => { return wrapSSR(
-
+
, ); }); diff --git a/packages/plugins/@nocobase/plugin-field-markdown-vditor/src/client/interfaces/markdown-vditor.tsx b/packages/plugins/@nocobase/plugin-field-markdown-vditor/src/client/interfaces/markdown-vditor.tsx index b5b8b54b7e7..608ca0ed08c 100644 --- a/packages/plugins/@nocobase/plugin-field-markdown-vditor/src/client/interfaces/markdown-vditor.tsx +++ b/packages/plugins/@nocobase/plugin-field-markdown-vditor/src/client/interfaces/markdown-vditor.tsx @@ -7,9 +7,9 @@ * For more information, please refer to: https://www.nocobase.com/agreement. */ +import { ISchema } from '@formily/react'; import { CollectionFieldInterface, interfacesProperties } from '@nocobase/client'; import { generateNTemplate } from '../locale'; -import { ISchema } from '@formily/react'; const { defaultProps, operators } = interfacesProperties; @@ -58,7 +58,9 @@ export class MarkdownVditorFieldInterface extends CollectionFieldInterface { 'x-reactions': { fulfill: { schema: { - description: generateNTemplate('Used to store files uploaded in the Markdown editor'), + description: generateNTemplate( + 'Used to store files uploaded in the Markdown editor (default: attachments)', + ), }, }, }, diff --git a/packages/plugins/@nocobase/plugin-field-markdown-vditor/src/client/locale/index.ts b/packages/plugins/@nocobase/plugin-field-markdown-vditor/src/client/locale/index.ts index 5c461f7c060..e2f3cbb485a 100644 --- a/packages/plugins/@nocobase/plugin-field-markdown-vditor/src/client/locale/index.ts +++ b/packages/plugins/@nocobase/plugin-field-markdown-vditor/src/client/locale/index.ts @@ -9,7 +9,7 @@ import { tval } from '@nocobase/utils/client'; -const NAMESPACE = 'field-markdown-vditor'; +export const NAMESPACE = 'field-markdown-vditor'; export function generateNTemplate(key: string) { return tval(key, { ns: NAMESPACE }) diff --git a/packages/plugins/@nocobase/plugin-field-markdown-vditor/src/locale/de-DE.json b/packages/plugins/@nocobase/plugin-field-markdown-vditor/src/locale/de-DE.json index ba814c43877..4b25ad4cd2d 100644 --- a/packages/plugins/@nocobase/plugin-field-markdown-vditor/src/locale/de-DE.json +++ b/packages/plugins/@nocobase/plugin-field-markdown-vditor/src/locale/de-DE.json @@ -1,7 +1,7 @@ { "Vditor": "Markdown(Vditor)", "File collection": "Dateisammlung", - "Used to store files uploaded in the Markdown editor": "Wird verwendet, um Dateien zu speichern, die im Markdown-Editor hochgeladen wurden", + "Used to store files uploaded in the Markdown editor (default: attachments)": "Dateisammlung, die in den Markdown-Editor hochgeladen wurde (Standard: attachments)", "Toolbar": "Editor-Symbolleistenkonfiguration", "Emoji": "Emoji", "Headings": "Überschriften", @@ -29,5 +29,10 @@ "Both": "Editor & Vorschau", "Preview": "Vorschau", "Fullscreen": "Vollbild umschalten", - "Outline": "Gliederung" + "Outline": "Gliederung", + "uploading": "Hochladen...", + "upload failed": "Hochladen fehlgeschlagen", + "vditor.uploadError.message": "Dateien können nicht in den aktuellen Speicher hochgeladen werden. Sie versuchen, Dateien in den Markdown-Editor hochzuladen, aber die aktuelle Speicher-Konfiguration unterstützt diese Operation nicht. Um die Upload-Funktionalität zu aktivieren, schließen Sie bitte die folgenden Einstellungen ab: 1. Gehen Sie zu \"Dateimanager\". 2. Wählen Sie den aktuell verwendeten Speicher ({{storageTitle}}) aus. 3. Legen Sie die \"Basis-URL\" fest und aktivieren Sie die Option \"Öffentlicher Zugriff\".", + "Storage configuration not found. Please configure a storage provider first.": "Speicherkonfiguration nicht gefunden. Bitte konfigurieren Sie zuerst einen Speicheranbieter.", + "Response data is empty": "Antwortdaten sind leer" } diff --git a/packages/plugins/@nocobase/plugin-field-markdown-vditor/src/locale/en-US.json b/packages/plugins/@nocobase/plugin-field-markdown-vditor/src/locale/en-US.json index 2c718740696..98743a02e22 100644 --- a/packages/plugins/@nocobase/plugin-field-markdown-vditor/src/locale/en-US.json +++ b/packages/plugins/@nocobase/plugin-field-markdown-vditor/src/locale/en-US.json @@ -1,7 +1,7 @@ { "Vditor": "Markdown(Vditor)", "File collection": "File collection", - "Used to store files uploaded in the Markdown editor": "Used to store files uploaded in the Markdown editor", + "Used to store files uploaded in the Markdown editor (default: attachments)": "Used to store files uploaded in the Markdown editor (default: attachments)", "Toolbar": "Editor toolbar configuration", "Emoji": "Emoji", "Headings": "Headings", @@ -29,5 +29,10 @@ "Both": "Editor & Preview", "Preview": "Preview", "Fullscreen": "Toggle Fullscreen", - "Outline": "Outline" -} \ No newline at end of file + "Outline": "Outline", + "uploading": "Uploading...", + "upload failed": "upload failed", + "vditor.uploadError.message": "Unable to upload files to the current storage. You are trying to upload files to the Markdown editor, but the current storage configuration does not support this operation. To enable upload functionality, please complete the following settings: 1. Go to \"File Manager\". 2. Select the storage currently in use ({{storageTitle}}). 3. Set \"Base URL\" and enable the \"Public access\" option.", + "Storage configuration not found. Please configure a storage provider first.": "Storage configuration not found. Please configure a storage provider first.", + "Response data is empty": "Response data is empty" +} diff --git a/packages/plugins/@nocobase/plugin-field-markdown-vditor/src/locale/it-IT.json b/packages/plugins/@nocobase/plugin-field-markdown-vditor/src/locale/it-IT.json index e9cb61630a3..19ae1799787 100644 --- a/packages/plugins/@nocobase/plugin-field-markdown-vditor/src/locale/it-IT.json +++ b/packages/plugins/@nocobase/plugin-field-markdown-vditor/src/locale/it-IT.json @@ -1,7 +1,7 @@ -{ + { "Vditor": "Markdown(Vditor)", "File collection": "Raccolta file", - "Used to store files uploaded in the Markdown editor": "Utilizzato per archiviare i file caricati nell'editor Markdown", + "Used to store files uploaded in the Markdown editor (default: attachments)": "Utilizzato per archiviare i file caricati nell'editor Markdown (default: attachments)", "Toolbar": "Configurazione barra degli strumenti dell'editor", "Emoji": "Emoji", "Headings": "Intestazioni", @@ -29,5 +29,10 @@ "Both": "Editor e Anteprima", "Preview": "Anteprima", "Fullscreen": "Attiva/disattiva schermo intero", - "Outline": "Struttura" + "Outline": "Struttura", + "uploading": "Uploading...", + "upload failed": "upload failed", + "vditor.uploadError.message": "Impossibile caricare file nello storage corrente. Stai tentando di caricare file nell'editor Markdown, ma la configurazione di archiviazione corrente non supporta questa operazione. Per abilitare la funzionalità di caricamento, completa le seguenti impostazioni: 1. Vai a \"Gestione file\". 2. Seleziona lo storage attualmente in uso ({{storageTitle}}). 3. Imposta \"URL di base\" e abilita l'opzione \"Accesso pubblico\".", + "Storage configuration not found. Please configure a storage provider first.": "Configurazione dello storage non trovata. Configura prima un provider di storage.", + "Response data is empty": "I dati di risposta sono vuoti" } diff --git a/packages/plugins/@nocobase/plugin-field-markdown-vditor/src/locale/ja-JP.json b/packages/plugins/@nocobase/plugin-field-markdown-vditor/src/locale/ja-JP.json index e41532b6705..cec3dc563b5 100644 --- a/packages/plugins/@nocobase/plugin-field-markdown-vditor/src/locale/ja-JP.json +++ b/packages/plugins/@nocobase/plugin-field-markdown-vditor/src/locale/ja-JP.json @@ -1,7 +1,7 @@ { "Vditor": "Markdown(Vditor)", "File collection": "ファイルコレクション", - "Used to store files uploaded in the Markdown editor": "Markdownエディタでアップロードされたファイルを保存するために使用", + "Used to store files uploaded in the Markdown editor (default: attachments)": "Markdownエディタでアップロードされたファイルを保存するために使用 (デフォルト: attachments)", "Toolbar": "ツールバー", "Emoji": "絵文字", "Headings": "見出し", @@ -29,5 +29,10 @@ "Both": "編集とプレビュー", "Preview": "プレビュー", "Fullscreen": "全画面表示の切り替え", - "Outline": "アウトライン" -} \ No newline at end of file + "Outline": "アウトライン", + "uploading": "Uploading...", + "upload failed": "upload failed", + "vditor.uploadError.message": "現在のストレージにファイルをアップロードできません。Markdownエディタにファイルをアップロードしようとしていますが、現在のストレージ設定ではこの操作はサポートされていません。アップロード機能を有効にするには、次の設定を完了してください:1.「ファイルマネージャー」に移動します。2.現在使用しているストレージ({{storageTitle}})を選択します。3.「ベースURL」を設定し、「パブリックアクセス」オプションを有効にします。", + "Storage configuration not found. Please configure a storage provider first.": "ストレージ構成が見つかりません。最初にストレージプロバイダーを構成してください。", + "Response data is empty": "応答データが空です" +} diff --git a/packages/plugins/@nocobase/plugin-field-markdown-vditor/src/locale/ko-KR.json b/packages/plugins/@nocobase/plugin-field-markdown-vditor/src/locale/ko-KR.json index ebae584d996..8d3ce5628ff 100644 --- a/packages/plugins/@nocobase/plugin-field-markdown-vditor/src/locale/ko-KR.json +++ b/packages/plugins/@nocobase/plugin-field-markdown-vditor/src/locale/ko-KR.json @@ -1,7 +1,7 @@ { "Vditor": "Markdown(Vditor)", "File collection": "파일 데이터 테이블", - "Used to store files uploaded in the Markdown editor": "Markdown 편집기에 업로드된 파일을 저장하는 데 사용됩니다", + "Used to store files uploaded in the Markdown editor (default: attachments)": "Markdown 편집기에 업로드된 파일을 저장하는 데 사용됩니다 (default: attachments)", "Toolbar": "편집기 도구 모음 구성", "Emoji": "이모지", "Headings": "제목크기", @@ -29,5 +29,10 @@ "Both": "에디터 & 미리보기", "Preview": "미리보기", "Fullscreen": "전체화면", - "Outline": "개요" -} \ No newline at end of file + "Outline": "개요", + "uploading": "Uploading...", + "upload failed": "upload failed", + "vditor.uploadError.message": "현재 스토리지에 파일을 업로드할 수 없습니다. Markdown 편집기에 파일을 업로드하려고 하지만 현재 스토리지 구성에서는 이 작업을 지원하지 않습니다. 업로드 기능을 활성화하려면 다음 설정을 완료하십시오: 1. \"파일 관리\"로 이동합니다. 2. 현재 사용 중인 스토리지({{storageTitle}})를 선택합니다. 3. \"기본 URL\"을 설정하고 \"공개 액세스\" 옵션을 활성화합니다.", + "Storage configuration not found. Please configure a storage provider first.": "저장소 구성을 찾을 수 없습니다. 먼저 저장소 공급자를 구성하십시오.", + "Response data is empty": "응답 데이터가 비어 있습니다" +} diff --git a/packages/plugins/@nocobase/plugin-field-markdown-vditor/src/locale/zh-CN.json b/packages/plugins/@nocobase/plugin-field-markdown-vditor/src/locale/zh-CN.json index d2bc2eb6b16..93d23cc8593 100644 --- a/packages/plugins/@nocobase/plugin-field-markdown-vditor/src/locale/zh-CN.json +++ b/packages/plugins/@nocobase/plugin-field-markdown-vditor/src/locale/zh-CN.json @@ -1,7 +1,7 @@ { "Vditor": "Markdown(Vditor)", "File collection": "文件数据表", - "Used to store files uploaded in the Markdown editor": "用于存储在Markdown编辑器中上传的文件", + "Used to store files uploaded in the Markdown editor (default: attachments)": "用于存储在Markdown编辑器中上传的文件 (默认: attachments)", "Toolbar": "编辑器工具栏配置", "Emoji": "表情", "Headings": "标题", @@ -29,5 +29,10 @@ "Both": "编辑 & 预览", "Preview": "预览", "Fullscreen": "全屏切换", - "Outline": "大纲" -} \ No newline at end of file + "Outline": "大纲", + "uploading": "上传中...", + "upload failed": "上传失败", + "vditor.uploadError.message": "无法上传文件到当前存储。您正在尝试上传文件到 Markdown 编辑器,但当前的存储配置不支持此操作。要启用上传功能,请完成以下设置:1. 前往「文件管理器」。2. 选择当前正在使用的存储({{storageTitle}})。3. 设置「基础 URL」并启用「公开访问」选项。", + "Storage configuration not found. Please configure a storage provider first.": "存储配置未找到。请先配置存储提供程序。", + "Response data is empty": "接口返回的数据为空" +} diff --git a/packages/plugins/@nocobase/plugin-field-markdown-vditor/src/server/__tests__/vditor-check.test.ts b/packages/plugins/@nocobase/plugin-field-markdown-vditor/src/server/__tests__/vditor-check.test.ts new file mode 100644 index 00000000000..b5ed87ad931 --- /dev/null +++ b/packages/plugins/@nocobase/plugin-field-markdown-vditor/src/server/__tests__/vditor-check.test.ts @@ -0,0 +1,282 @@ +/** + * This file is part of the NocoBase (R) project. + * Copyright (c) 2020-2024 NocoBase Co., Ltd. + * Authors: NocoBase Team. + * + * This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License. + * For more information, please refer to: https://www.nocobase.com/agreement. + */ + +import { mockServer } from '@nocobase/test'; +import { afterEach, beforeEach, describe, expect, it } from 'vitest'; + +describe('vditor:check', () => { + let app; + let db; + let agent; + + beforeEach(async () => { + app = mockServer({ + cors: { + origin: '*', + }, + plugins: ['field-sort', 'users', 'auth', 'file-manager', 'field-markdown-vditor'], + }); + + await app.load(); + + db = app.db; + agent = app.agent(); + + // 确保集合已同步 + await db.sync(); + + // 清除数据并准备测试数据 + await db.clean({ drop: true }); + + // 确保集合重新同步 + await db.sync(); + + // 创建测试所需的存储和文件集合 + await db.getRepository('storages').create({ + values: { + name: 'default-storage', + type: 'local', + default: true, + }, + }); + + await db.getRepository('storages').create({ + values: { + name: 's3-storage', + type: 's3-compatible', + default: false, + }, + }); + + await db.getRepository('storages').create({ + values: { + name: 's3-storage-with-baseurl', + type: 's3-compatible', + default: false, + options: { + baseUrl: 'https://example.com', + public: true, + }, + }, + }); + + // 创建测试文件集合 + await db.collection({ + name: 'file-collection-default', + fields: [ + { + type: 'string', + name: 'title', + }, + ], + }); + + await db.collection({ + name: 'file-collection-s3', + fields: [ + { + type: 'string', + name: 'title', + }, + ], + storage: 's3-storage', + }); + + await db.collection({ + name: 'file-collection-s3-with-baseurl', + fields: [ + { + type: 'string', + name: 'title', + }, + ], + storage: 's3-storage-with-baseurl', + }); + + await db.sync(); + }); + + afterEach(async () => { + await app.destroy(); + }); + + it('should return default storage when no fileCollectionName provided', async () => { + const response = await agent.resource('vditor').check(); + + expect(response.status).toBe(200); + expect(response.body.data.isSupportToUploadFiles).toBe(true); + expect(response.body.data.storage).toMatchInlineSnapshot(` + { + "id": 1, + "name": "default-storage", + "rules": {}, + "title": null, + "type": "local", + } + `); + }); + + it('should return default storage when fileCollectionName without storage is provided', async () => { + const response = await agent.resource('vditor').check({ + fileCollectionName: 'file-collection-default', + }); + + expect(response.status).toBe(200); + expect(response.body.data.isSupportToUploadFiles).toBe(true); + expect(response.body.data.storage).toMatchInlineSnapshot(` + { + "id": 1, + "name": "default-storage", + "rules": {}, + "title": null, + "type": "local", + } + `); + }); + + it('should return s3 storage and not support upload when s3 collection without baseUrl is provided', async () => { + const response = await agent.resource('vditor').check({ + fileCollectionName: 'file-collection-s3', + }); + + expect(response.status).toBe(200); + expect(response.body.data.isSupportToUploadFiles).toBe(false); + expect(response.body.data.storage).toMatchInlineSnapshot(` + { + "id": 2, + "name": "s3-storage", + "rules": {}, + "title": null, + "type": "s3-compatible", + } + `); + }); + + it('should return s3 storage and support upload when s3 collection with baseUrl is provided', async () => { + const response = await agent.resource('vditor').check({ + fileCollectionName: 'file-collection-s3-with-baseurl', + }); + + expect(response.status).toBe(200); + expect(response.body.data.isSupportToUploadFiles).toBe(true); + expect(response.body.data.storage).toMatchInlineSnapshot(` + { + "id": 3, + "name": "s3-storage-with-baseurl", + "rules": {}, + "title": null, + "type": "s3-compatible", + } + `); + }); + + it('should handle non-existent fileCollectionName gracefully', async () => { + const response = await agent.resource('vditor').check({ + fileCollectionName: 'non-existent-collection', + }); + + expect(response.status).toBe(200); + expect(response.body.data.isSupportToUploadFiles).toBe(true); + expect(response.body.data.storage).toMatchInlineSnapshot(` + { + "id": 1, + "name": "default-storage", + "rules": {}, + "title": null, + "type": "local", + } + `); + }); + + it('should handle s3 storage with baseUrl but not public attribute', async () => { + // 创建测试所需的非公开 s3 存储 + await db.getRepository('storages').create({ + values: { + name: 's3-storage-with-baseurl-not-public', + type: 's3-compatible', + default: false, + options: { + baseUrl: 'https://example.com', + public: false, + }, + }, + }); + + // 创建关联的文件集合 + await db.collection({ + name: 'file-collection-s3-baseurl-not-public', + fields: [ + { + type: 'string', + name: 'title', + }, + ], + storage: 's3-storage-with-baseurl-not-public', + }); + + await db.sync(); + + const response = await agent.resource('vditor').check({ + fileCollectionName: 'file-collection-s3-baseurl-not-public', + }); + + expect(response.status).toBe(200); + expect(response.body.data.isSupportToUploadFiles).toBe(false); + expect(response.body.data.storage).toMatchInlineSnapshot(` + { + "id": 4, + "name": "s3-storage-with-baseurl-not-public", + "rules": {}, + "title": null, + "type": "s3-compatible", + } + `); + }); + + it('should handle non-s3 storage types correctly', async () => { + // 创建一个非 s3 类型的存储 + await db.getRepository('storages').create({ + values: { + name: 'other-storage-type', + type: 'oss', + default: false, + }, + }); + + // 创建关联的文件集合 + await db.collection({ + name: 'file-collection-oss', + fields: [ + { + type: 'string', + name: 'title', + }, + ], + storage: 'other-storage-type', + }); + + await db.sync(); + + const response = await agent.resource('vditor').check({ + fileCollectionName: 'file-collection-oss', + }); + + expect(response.status).toBe(200); + expect(response.body.data.isSupportToUploadFiles).toBe(true); + expect(response.body.data.storage).toMatchInlineSnapshot(` + { + "id": 4, + "name": "other-storage-type", + "rules": {}, + "title": null, + "type": "oss", + } + `); + }); +}); diff --git a/packages/plugins/@nocobase/plugin-field-markdown-vditor/src/server/plugin.ts b/packages/plugins/@nocobase/plugin-field-markdown-vditor/src/server/plugin.ts index 2338e56f90d..53cbba69212 100644 --- a/packages/plugins/@nocobase/plugin-field-markdown-vditor/src/server/plugin.ts +++ b/packages/plugins/@nocobase/plugin-field-markdown-vditor/src/server/plugin.ts @@ -11,6 +11,11 @@ import { Plugin } from '@nocobase/server'; import fs from 'fs-extra'; import path from 'path'; +// @ts-ignore +import pkg from '../../package.json'; + +const namespace = pkg.name; + export class PluginFieldMarkdownVditorServer extends Plugin { async afterAdd() {} @@ -18,6 +23,63 @@ export class PluginFieldMarkdownVditorServer extends Plugin { async load() { await this.copyVditorDist(); + this.setResource(); + this.app.acl.allow('vditor', 'check', 'loggedIn'); + } + + setResource() { + this.app.resourceManager.define({ + name: 'vditor', + actions: { + check: async (context, next) => { + const { fileCollectionName } = context.action.params; + let storage; + + const fileCollection = this.db.getCollection(fileCollectionName || 'attachments'); + const storageName = fileCollection?.options?.storage; + if (storageName) { + storage = await this.db.getRepository('storages').findOne({ + where: { + name: storageName, + }, + }); + } else { + storage = await this.db.getRepository('storages').findOne({ + where: { + default: true, + }, + }); + } + + if (!storage) { + context.throw( + 400, + context.t('Storage configuration not found. Please configure a storage provider first.', { + ns: namespace, + }), + ); + } + + const isSupportToUploadFiles = + storage.type !== 's3-compatible' || (storage.options?.baseUrl && storage.options?.public); + + const storageInfo = { + id: storage.id, + title: storage.title, + name: storage.name, + type: storage.type, + rules: storage.rules, + }; + + context.body = { + isSupportToUploadFiles: !!isSupportToUploadFiles, + storage: storageInfo, + }; + + await next(); + }, + }, + }); } async copyVditorDist() { diff --git a/packages/plugins/@nocobase/plugin-file-manager/src/client/hooks/useStorageUploadProps.ts b/packages/plugins/@nocobase/plugin-file-manager/src/client/hooks/useStorageUploadProps.ts index 75a1d270712..e7bd924f282 100644 --- a/packages/plugins/@nocobase/plugin-file-manager/src/client/hooks/useStorageUploadProps.ts +++ b/packages/plugins/@nocobase/plugin-file-manager/src/client/hooks/useStorageUploadProps.ts @@ -7,19 +7,8 @@ * For more information, please refer to: https://www.nocobase.com/agreement. */ -import { - Input, - Upload, - useCollection, - useCollectionField, - useCollectionManager, - useCollectionRecordData, - usePlugin, - useRequest, - withDynamicSchemaProps, -} from '@nocobase/client'; -import React, { useEffect } from 'react'; -import { connect, mapProps, mapReadPretty, useField } from '@formily/react'; +import { useCollection, useCollectionField, useCollectionManager, usePlugin, useRequest } from '@nocobase/client'; +import { useEffect } from 'react'; import FileManagerPlugin from '../'; export function useStorage(storage) { diff --git a/packages/plugins/@nocobase/plugin-file-manager/src/client/index.tsx b/packages/plugins/@nocobase/plugin-file-manager/src/client/index.tsx index e7c4025a0b3..6a923d0dfa4 100644 --- a/packages/plugins/@nocobase/plugin-file-manager/src/client/index.tsx +++ b/packages/plugins/@nocobase/plugin-file-manager/src/client/index.tsx @@ -8,15 +8,16 @@ */ import { Plugin, useCollection } from '@nocobase/client'; +import { STORAGE_TYPE_ALI_OSS, STORAGE_TYPE_LOCAL, STORAGE_TYPE_S3, STORAGE_TYPE_TX_COS } from '../constants'; import { FileManagerProvider } from './FileManagerProvider'; +import { FileSizeField } from './FileSizeField'; import { FileStoragePane } from './FileStorage'; +import { useAttachmentFieldProps, useFileCollectionStorageRules } from './hooks'; +import { useStorageCfg } from './hooks/useStorageUploadProps'; +import { AttachmentFieldInterface } from './interfaces/attachment'; import { NAMESPACE } from './locale'; import { storageTypes } from './schemas/storageTypes'; -import { AttachmentFieldInterface } from './interfaces/attachment'; import { FileCollectionTemplate } from './templates'; -import { useAttachmentFieldProps, useFileCollectionStorageRules } from './hooks'; -import { FileSizeField } from './FileSizeField'; -import { STORAGE_TYPE_ALI_OSS, STORAGE_TYPE_LOCAL, STORAGE_TYPE_S3, STORAGE_TYPE_TX_COS } from '../constants'; export class PluginFileManagerClient extends Plugin { // refer by plugin-field-attachment-url @@ -58,6 +59,7 @@ export class PluginFileManagerClient extends Plugin { this.app.addScopes({ useAttachmentFieldProps, useFileCollectionStorageRules, + useStorageCfg, }); this.app.addComponents({ @@ -72,6 +74,49 @@ export class PluginFileManagerClient extends Plugin { getStorageType(name: string) { return this.storageTypes.get(name); } + + async uploadFile(options?: { + file: File; + fileCollectionName?: string; + storageType?: string; + /** 后面可能会废弃这个参数 */ + storageId?: number; + storageRules?: { + size: number; + }; + }): Promise<{ errorMessage?: string; data?: any }> { + const storageTypeObj = this.getStorageType(options?.storageType); + if (storageTypeObj?.upload) { + // 1. If storageType is provided, call the upload method directly + return await storageTypeObj.upload({ + file: options.file, + apiClient: this.app.apiClient, + storageType: options.storageType, + storageId: options.storageId, + storageRules: options.storageRules, + fileCollectionName: options.fileCollectionName, + }); + } + + // 2. If storageType is not provided, use the default upload method + try { + const formData = new FormData(); + formData.append('file', options.file); + const res = await this.app.apiClient.request({ + url: `${options.fileCollectionName || 'attachments'}:create`, + method: 'post', + data: formData, + }); + + return { + data: res.data?.data, + }; + } catch (error) { + return { + errorMessage: error.message, + }; + } + } } export default PluginFileManagerClient; diff --git a/packages/plugins/@nocobase/plugin-file-manager/src/client/schemas/storage.ts b/packages/plugins/@nocobase/plugin-file-manager/src/client/schemas/storage.ts index bad767884e0..16b45268365 100644 --- a/packages/plugins/@nocobase/plugin-file-manager/src/client/schemas/storage.ts +++ b/packages/plugins/@nocobase/plugin-file-manager/src/client/schemas/storage.ts @@ -53,7 +53,7 @@ const collection = { name: 'baseUrl', interface: 'input', uiSchema: { - title: `{{t("Access base URL", { ns: "${NAMESPACE}" })}}`, + title: `{{t("Base URL", { ns: "${NAMESPACE}" })}}`, type: 'string', 'x-component': 'TextAreaWithGlobalScope', required: true, diff --git a/packages/plugins/@nocobase/plugin-file-manager/src/client/schemas/storageTypes/ali-oss.tsx b/packages/plugins/@nocobase/plugin-file-manager/src/client/schemas/storageTypes/ali-oss.tsx index dcd868160ff..96383fc282e 100644 --- a/packages/plugins/@nocobase/plugin-file-manager/src/client/schemas/storageTypes/ali-oss.tsx +++ b/packages/plugins/@nocobase/plugin-file-manager/src/client/schemas/storageTypes/ali-oss.tsx @@ -7,11 +7,8 @@ * For more information, please refer to: https://www.nocobase.com/agreement. */ -import React from 'react'; - import { NAMESPACE } from '../../locale'; import common from './common'; -import { useTranslation } from 'react-i18next'; export default { title: `{{t("Aliyun OSS", { ns: "${NAMESPACE}" })}}`, diff --git a/packages/plugins/@nocobase/plugin-file-manager/src/client/schemas/storageTypes/tx-cos.tsx b/packages/plugins/@nocobase/plugin-file-manager/src/client/schemas/storageTypes/tx-cos.tsx index b86dc0c499c..8c0f1f6ccf4 100644 --- a/packages/plugins/@nocobase/plugin-file-manager/src/client/schemas/storageTypes/tx-cos.tsx +++ b/packages/plugins/@nocobase/plugin-file-manager/src/client/schemas/storageTypes/tx-cos.tsx @@ -7,10 +7,8 @@ * For more information, please refer to: https://www.nocobase.com/agreement. */ -import { useTranslation } from 'react-i18next'; import { NAMESPACE } from '../../locale'; import common from './common'; -import React from 'react'; export default { title: `{{t("Tencent COS", { ns: "${NAMESPACE}" })}}`, diff --git a/packages/plugins/@nocobase/plugin-file-manager/src/locale/de-DE.json b/packages/plugins/@nocobase/plugin-file-manager/src/locale/de-DE.json index 42a9c9da428..41d5215a860 100644 --- a/packages/plugins/@nocobase/plugin-file-manager/src/locale/de-DE.json +++ b/packages/plugins/@nocobase/plugin-file-manager/src/locale/de-DE.json @@ -20,7 +20,7 @@ "Storage type": "Speichertyp", "Default storage": "Standardspeicher", "Storage base URL": "Basis-URL des Speichers", - "Access base URL": "Basis-URL für den Zugriff", + "Base URL": "Basis-URL", "Base URL for file access, could be your CDN base URL. For example: \"https://cdn.nocobase.com\".": "Basis-URL für den Dateizugriff, könnte Ihre CDN-Basis-URL sein. Zum Beispiel: \"https://cdn.nocobase.com\".", "Destination": "Ziel", "Use the built-in static file server": "Den integrierten statischen Dateiserver verwenden", diff --git a/packages/plugins/@nocobase/plugin-file-manager/src/locale/it-IT.json b/packages/plugins/@nocobase/plugin-file-manager/src/locale/it-IT.json index 778734b98a9..01b1e9bf9b5 100644 --- a/packages/plugins/@nocobase/plugin-file-manager/src/locale/it-IT.json +++ b/packages/plugins/@nocobase/plugin-file-manager/src/locale/it-IT.json @@ -30,7 +30,7 @@ "Allow uploading multiple files": "Consenti caricamento di più file", "Storage": "Spazio di archiviazione", "Storages": "Spazi di archiviazione", - "Access base URL": "URL base accesso", + "Base URL": "URL base", "Base URL for file access, could be your CDN base URL. For example: \"https://cdn.nocobase.com\".": "URL base per l'accesso ai file, potrebbe essere l'URL base del tuo CDN. Ad esempio: \"https://cdn.nocobase.com\".", "Relative path the file will be saved to. Left blank as root path. The leading and trailing slashes \"/\" will be ignored. For example: \"user/avatar\".": "Percorso relativo in cui verrà salvato il file. Lasciare vuoto per il percorso radice. Le barre iniziali e finali \"/\" verranno ignorate. Ad esempio: \"user/avatar\".", "Default storage will be used when not selected": "Se non selezionato verrà utilizzato lo spazio di archiviazione predefinito", diff --git a/packages/plugins/@nocobase/plugin-file-manager/src/locale/ja-JP.json b/packages/plugins/@nocobase/plugin-file-manager/src/locale/ja-JP.json index aadd212423e..afb39b45b92 100644 --- a/packages/plugins/@nocobase/plugin-file-manager/src/locale/ja-JP.json +++ b/packages/plugins/@nocobase/plugin-file-manager/src/locale/ja-JP.json @@ -32,7 +32,7 @@ "Allow uploading multiple files": "複数ファイルのアップロードを許可する", "Storage": "ストレージ", "Storages": "ストレージ", - "Access base URL": "アクセスURLのベース", + "Base URL": "ベースURL", "Base URL for file access, could be your CDN base URL. For example: \"https://cdn.nocobase.com\".": "术语调整,“基礎URL”改为“ベースURL”", "Relative path the file will be saved to. Left blank as root path. The leading and trailing slashes \"/\" will be ignored. For example: \"user/avatar\".": "更清晰的表达方式,符合技术描述", "Default storage will be used when not selected": "空欄の場合はデフォルトのストレージが使用されます。", diff --git a/packages/plugins/@nocobase/plugin-file-manager/src/locale/zh-CN.json b/packages/plugins/@nocobase/plugin-file-manager/src/locale/zh-CN.json index d08ac66fb09..7873a4dbcad 100644 --- a/packages/plugins/@nocobase/plugin-file-manager/src/locale/zh-CN.json +++ b/packages/plugins/@nocobase/plugin-file-manager/src/locale/zh-CN.json @@ -18,7 +18,7 @@ "Storage name": "存储空间标识", "Storage type": "存储类型", "Default storage": "默认存储空间", - "Access base URL": "访问 URL 基础", + "Base URL": "基础 URL", "Base URL for file access, could be your CDN base URL. For example: \"https://cdn.nocobase.com\".": "文件访问的基础 URL,可以是你的 CDN 基础 URL。例如:\"https://cdn.nocobase.com\"。", "Destination": "上传目标文件夹", "Use the built-in static file server": "使用内置静态文件服务", diff --git a/packages/plugins/@nocobase/plugin-file-manager/src/server/actions/storages.ts b/packages/plugins/@nocobase/plugin-file-manager/src/server/actions/storages.ts index b1f4c218e3c..abcafd51596 100644 --- a/packages/plugins/@nocobase/plugin-file-manager/src/server/actions/storages.ts +++ b/packages/plugins/@nocobase/plugin-file-manager/src/server/actions/storages.ts @@ -30,6 +30,8 @@ export async function getBasicInfo(context, next) { name: result.name, type: result.type, rules: result.rules, + baseUrl: result.options?.baseUrl, + public: result.options?.public, }; next(); diff --git a/packages/plugins/@nocobase/plugin-file-manager/src/server/server.ts b/packages/plugins/@nocobase/plugin-file-manager/src/server/server.ts index aaef4c1e797..d581ba8772f 100644 --- a/packages/plugins/@nocobase/plugin-file-manager/src/server/server.ts +++ b/packages/plugins/@nocobase/plugin-file-manager/src/server/server.ts @@ -7,12 +7,12 @@ * For more information, please refer to: https://www.nocobase.com/agreement. */ -import { basename } from 'path'; import fs from 'fs'; +import { basename } from 'path'; -import { Plugin } from '@nocobase/server'; -import { isURL, Registry } from '@nocobase/utils'; import { Collection, Model, Transactionable } from '@nocobase/database'; +import { Plugin } from '@nocobase/server'; +import { Registry } from '@nocobase/utils'; import { STORAGE_TYPE_ALI_OSS, STORAGE_TYPE_LOCAL, STORAGE_TYPE_S3, STORAGE_TYPE_TX_COS } from '../constants'; import initActions from './actions'; import { AttachmentInterface } from './interfaces/attachment-interface'; diff --git a/packages/plugins/@nocobase/plugin-public-forms/src/server/plugin.ts b/packages/plugins/@nocobase/plugin-public-forms/src/server/plugin.ts index f3c45f4487c..54bf5bcff42 100644 --- a/packages/plugins/@nocobase/plugin-public-forms/src/server/plugin.ts +++ b/packages/plugins/@nocobase/plugin-public-forms/src/server/plugin.ts @@ -14,8 +14,9 @@ import { parseAssociationNames } from './hook'; class PasswordError extends Error {} export class PluginPublicFormsServer extends Plugin { - async parseCollectionData(formCollection, appends) { - const collection = this.db.getCollection(formCollection); + async parseCollectionData(dataSourceKey, formCollection, appends) { + const dataSource = this.app.dataSourceManager.dataSources.get(dataSourceKey); + const collection = dataSource.collectionManager.getCollection(formCollection); const collections = [ { name: collection.name, @@ -77,7 +78,7 @@ export class PluginPublicFormsServer extends Plugin { const schema = await uiSchema.getJsonSchema(filterByTk); const { getAssociationAppends } = parseAssociationNames(dataSourceKey, collectionName, this.app, schema); const { appends } = getAssociationAppends(); - const collections = await this.parseCollectionData(collectionName, appends); + const collections = await this.parseCollectionData(dataSourceKey, collectionName, appends); return { dataSource: { key: dataSourceKey, @@ -172,8 +173,9 @@ export class PluginPublicFormsServer extends Plugin { }; } else if ( (actionName === 'list' && ctx.PublicForm['targetCollections'].includes(resourceName)) || - (collection.options.template === 'file' && actionName === 'create') || - (resourceName === 'storages' && actionName === 'getBasicInfo') || + (collection?.options.template === 'file' && actionName === 'create') || + (resourceName === 'storages' && ['getBasicInfo', 'createPresignedUrl'].includes(actionName)) || + (resourceName === 'vditor' && ['check'].includes(actionName)) || (resourceName === 'map-configuration' && actionName === 'get') ) { ctx.permission = {