重构:Wails升级/mermaid主题切换/代码高亮修复/文件系统UI重构
- Wails v2.12.0升级(App绑定新增API、runtime类型扩展) - 修复mermaid暗色主题切换渲染失败(SVG textContent污染→data-mermaid-src保存源码) - 修复代码高亮全语言失效(languageMap静态白名单替代运行时hljs检查) - 文件系统:FileListPanel重写、FileItemRow合并删除、Toolbar简化 - 新增剪贴板图片粘贴(Ctrl+V粘贴图片到当前目录) - 死代码清理:DeviceTest/errorHandler/useLocalStorage移除 - MarkdownEditor优化、theme store增强、CodeMirror加载器精简
This commit is contained in:
@@ -41,8 +41,14 @@ var (
|
||||
es6ImportFromRegex = regexp.MustCompile(`import\s+([\s\S]*?)\s+from\s+["']([^"']+)["']`)
|
||||
es6DynamicImport = regexp.MustCompile(`import\s*\(\s*["']([^"']+)["']\s*\)`)
|
||||
es6BareImport = regexp.MustCompile(`(?m)^\s*import\s+["']([^"']+)["']`)
|
||||
|
||||
// HTML 预览路径修复
|
||||
locationPathRegex = regexp.MustCompile(`\blocation\.pathname\b`)
|
||||
)
|
||||
|
||||
// HTML 属性正则缓存(避免 replaceHtmlTagAttribute 中重复编译)
|
||||
var attrRegexCache sync.Map // map[string]*regexp.Regexp
|
||||
|
||||
// LocalFileServer 本地文件服务器(独立的 HTTP 服务器)
|
||||
type LocalFileServer struct {
|
||||
server *http.Server
|
||||
@@ -501,6 +507,11 @@ func handleHtmlPreviewRequest(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
// 解析参数
|
||||
filePath := r.URL.Query().Get("path")
|
||||
var err error
|
||||
if filePath, err = url.QueryUnescape(filePath); err != nil {
|
||||
http.Error(w, "Invalid path encoding", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
theme := r.URL.Query().Get("theme")
|
||||
if theme == "" {
|
||||
theme = "light"
|
||||
@@ -536,6 +547,12 @@ func handleHtmlPreviewRequest(w http.ResponseWriter, r *http.Request) {
|
||||
// 转换资源路径(将相对路径和绝对路径都转换为完整的本地文件服务器 URL)
|
||||
processedContent := transformHtmlResourcePaths(string(content), baseDir)
|
||||
|
||||
// 修复 JS 中基于 location.pathname 的相对路径计算
|
||||
// 预览模式下 location.pathname = "/localfs/html-preview",与实际文件路径不一致
|
||||
// ⚠️ 会替换所有出现位置(含JS字符串内),HTML预览场景下可接受
|
||||
correctPathname := `"/localfs/` + strings.ReplaceAll(baseDir, "\\", "/") + `/`
|
||||
processedContent = locationPathRegex.ReplaceAllString(processedContent, correctPathname)
|
||||
|
||||
// 注入链接点击拦截脚本
|
||||
finalContent := injectLinkInterceptor(processedContent)
|
||||
|
||||
@@ -616,8 +633,14 @@ func transformHtmlResourcePaths(htmlContent string, baseDir string) string {
|
||||
// replaceHtmlTagAttribute 替换 HTML 标签中的属性路径
|
||||
func replaceHtmlTagAttribute(html string, pattern *regexp.Regexp, attrName string, baseDir string) string {
|
||||
return pattern.ReplaceAllStringFunc(html, func(match string) string {
|
||||
// 提取属性值
|
||||
attrRegex := regexp.MustCompile(fmt.Sprintf(`%s=["']([^"']+)["']`, attrName))
|
||||
// 提取属性值(使用缓存的正则)
|
||||
var attrRegex *regexp.Regexp
|
||||
if v, ok := attrRegexCache.Load(attrName); ok {
|
||||
attrRegex = v.(*regexp.Regexp)
|
||||
} else {
|
||||
attrRegex = regexp.MustCompile(fmt.Sprintf(`%s=["']([^"']+)["']`, attrName))
|
||||
attrRegexCache.Store(attrName, attrRegex)
|
||||
}
|
||||
attrMatch := attrRegex.FindStringSubmatch(match)
|
||||
if attrMatch == nil {
|
||||
return match
|
||||
|
||||
@@ -6,6 +6,7 @@ import (
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"strings"
|
||||
"sync"
|
||||
)
|
||||
|
||||
// PathValidator 路径验证器接口
|
||||
@@ -180,16 +181,25 @@ func (v *DefaultPathValidator) isSensitivePath(path string) bool {
|
||||
return false
|
||||
}
|
||||
|
||||
// 默认路径验证器(缓存,避免每次调用重复初始化)
|
||||
var (
|
||||
defaultValidatorOnce sync.Once
|
||||
defaultValidator PathValidator
|
||||
)
|
||||
|
||||
func getDefaultValidator() PathValidator {
|
||||
defaultValidatorOnce.Do(func() {
|
||||
defaultValidator = NewPathValidator(DefaultConfig())
|
||||
})
|
||||
return defaultValidator
|
||||
}
|
||||
|
||||
// isSafePath 兼容函数:保持向后兼容
|
||||
// 使用默认配置的路径验证器
|
||||
func isSafePath(path string) bool {
|
||||
validator := NewPathValidator(DefaultConfig())
|
||||
return validator.IsSafe(path)
|
||||
return getDefaultValidator().IsSafe(path)
|
||||
}
|
||||
|
||||
// isSensitivePath 兼容函数:保持向后兼容
|
||||
// 使用默认配置检查敏感路径
|
||||
func isSensitivePath(path string) bool {
|
||||
validator := NewPathValidator(DefaultConfig())
|
||||
return validator.IsSensitive(path)
|
||||
return getDefaultValidator().IsSensitive(path)
|
||||
}
|
||||
|
||||
@@ -2,17 +2,22 @@ package filesystem
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/base64"
|
||||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"u-desk/internal/common"
|
||||
)
|
||||
|
||||
const maxReadWriteSize = 10 * 1024 * 1024 // 10MB 读写上限
|
||||
|
||||
// FileOperationResult 文件操作结果
|
||||
type FileOperationResult struct {
|
||||
Path string `json:"path"`
|
||||
@@ -131,9 +136,8 @@ func (s *FileSystemService) ReadFile(path string) (string, error) {
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("获取文件信息失败: %v", err)
|
||||
}
|
||||
const maxReadSize = 10 * 1024 * 1024 // 10MB
|
||||
if info.Size() > maxReadSize {
|
||||
return "", fmt.Errorf("文件过大 (%.1f MB),超过读取上限 (%d MB)", float64(info.Size())/1024/1024, maxReadSize/1024/1024)
|
||||
if info.Size() > maxReadWriteSize {
|
||||
return "", fmt.Errorf("文件过大 (%.1f MB),超过读取上限 (%d MB)", float64(info.Size())/1024/1024, maxReadWriteSize/1024/1024)
|
||||
}
|
||||
|
||||
// 读取文件
|
||||
@@ -151,30 +155,43 @@ func (s *FileSystemService) Write(path, content string) error {
|
||||
return s.WriteFile(path, content)
|
||||
}
|
||||
|
||||
// WriteFile 写入文件
|
||||
func (s *FileSystemService) WriteFile(path, content string) error {
|
||||
// 路径验证
|
||||
// writeFile 内部写入实现(路径验证+大小检查+写入+日志)
|
||||
func (s *FileSystemService) writeFileWithLog(path string, data []byte) error {
|
||||
if err := s.validatePath(path); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// 创建目录
|
||||
dir := filepath.Dir(path)
|
||||
if err := os.MkdirAll(dir, DefaultDirPermissions); err != nil {
|
||||
return fmt.Errorf("创建目录失败: %v", err)
|
||||
}
|
||||
|
||||
// 写入文件
|
||||
data := []byte(content)
|
||||
if len(data) > maxReadWriteSize {
|
||||
return fmt.Errorf("文件过大 (%.1f MB),超过写入上限 (%d MB)", float64(len(data))/1024/1024, maxReadWriteSize/1024/1024)
|
||||
}
|
||||
if err := os.WriteFile(path, data, DefaultFilePermissions); err != nil {
|
||||
s.logWrite(path, int64(len(data)), err)
|
||||
return fmt.Errorf("写入文件失败: %v", err)
|
||||
}
|
||||
|
||||
s.logWrite(path, int64(len(data)), nil)
|
||||
return nil
|
||||
}
|
||||
|
||||
// WriteFile 写入文件
|
||||
func (s *FileSystemService) WriteFile(path, content string) error {
|
||||
return s.writeFileWithLog(path, []byte(content))
|
||||
}
|
||||
|
||||
// SaveBase64File 将 base64 编码内容解码后写入二进制文件
|
||||
func (s *FileSystemService) SaveBase64File(path, base64Content string) error {
|
||||
if strings.TrimSpace(base64Content) == "" {
|
||||
return errors.New("base64 内容不能为空")
|
||||
}
|
||||
data, err := base64.StdEncoding.DecodeString(base64Content)
|
||||
if err != nil {
|
||||
return fmt.Errorf("base64 解码失败: %v", err)
|
||||
}
|
||||
return s.writeFileWithLog(path, data)
|
||||
}
|
||||
|
||||
// List 列出目录内容(实现 FileService 接口)
|
||||
func (s *FileSystemService) List(path string) ([]map[string]interface{}, error) {
|
||||
return s.ListDir(path)
|
||||
|
||||
Reference in New Issue
Block a user