diff --git a/app/appearance/langs/en_US.json b/app/appearance/langs/en_US.json index 7d43093d5..ce1ae764e 100644 --- a/app/appearance/langs/en_US.json +++ b/app/appearance/langs/en_US.json @@ -960,7 +960,7 @@ "129": "Cloud data has been corrupted, please refer to here to resolve the issue", "130": "Starting new version installer...", "131": "This operation is not supported, please go to the cloud storage provider management console to operate", - "132": "TODO", + "132": "The current search method does not support the replace operation, please use the [Text] or [Regex] search method", "133": "TODO", "134": "In order to prevent the newly restored data from being overwritten by synchronization, the data synchronization function has been automatically suspended", "135": "Unable to decrypt data. Make sure to use the same key on multiple devices, then create a new cloud directory to sync", diff --git a/app/appearance/langs/es_ES.json b/app/appearance/langs/es_ES.json index 6f978aa01..1dd576df1 100644 --- a/app/appearance/langs/es_ES.json +++ b/app/appearance/langs/es_ES.json @@ -1,6 +1,6 @@ { "searchMethod": "método de búsqueda", - "regex": "regex", + "regex": "Regex", "groupBy": "Agrupar por", "keywordsLimit": "Límite de palabras clave", "exportAsImage": "Exportar como imagen", @@ -960,7 +960,7 @@ "129": "Los datos de la nube se han dañado, consulte aquí para resolver el problema", "130": "Iniciando instalador de nueva versión...", "131": "Esta solicitud no es compatible, vaya a la consola de administración del proveedor de almacenamiento en la nube para operar", - "132": "TODO", + "132": "El método de búsqueda actual no admite la operación de reemplazo, utilice el método de búsqueda [Texto] o [Regex]", "133": "TODO", "134": "Para evitar que los datos recién restaurados sean sobrescritos por la sincronización, se ha suspendido automáticamente la función de sincronización de datos", "135": "No se pueden descifrar los datos. Asegúrese de usar la misma clave en varios dispositivos, luego cree un nuevo directorio en la nube para sincronizar", diff --git a/app/appearance/langs/fr_FR.json b/app/appearance/langs/fr_FR.json index de6ab073b..640a0574b 100644 --- a/app/appearance/langs/fr_FR.json +++ b/app/appearance/langs/fr_FR.json @@ -960,7 +960,7 @@ "129": "Les données cloud ont été corrompues, veuillez vous référer à ici pour résoudre le problème", "130": "Démarrage du programme d'installation de la nouvelle version...", "131": "Cette opération n'est pas prise en charge, veuillez vous rendre sur la console de gestion du fournisseur de stockage cloud pour opérer", - "132": "TODO", + "132": "La méthode de recherche actuelle ne prend pas en charge l'opération de remplacement, veuillez utiliser la méthode de recherche [Texte] ou [Regex]", "133": "TODO", "134": "Afin d'éviter que les données nouvellement restaurées ne soient écrasées par la synchronisation, la fonction de synchronisation des données a été automatiquement suspendue", "135": "Impossible de déchiffrer les données. Assurez-vous d'utiliser la même clé sur plusieurs appareils, puis créez un nouveau répertoire cloud à synchroniser", diff --git a/app/appearance/langs/zh_CHT.json b/app/appearance/langs/zh_CHT.json index 72d5bbcd1..b7a872ff9 100644 --- a/app/appearance/langs/zh_CHT.json +++ b/app/appearance/langs/zh_CHT.json @@ -960,7 +960,7 @@ "129": "雲端數據已經損壞,請參考這裡解決該問題", "130": "正在啟動新版本安裝程序...", "131": "不支持該操作,請到雲端存儲提供商管理控制台進行操作", - "132": "TODO", + "132": "當前搜索方式下不支持替換操作,請使用 [文本] 或 [正則表達式] 搜索方式", "133": "TODO", "134": "為避免剛恢復的數據被同步覆蓋,數據同步功能已被自動暫停", "135": "無法解密數據。請確保多個設備上使用相同的密鑰,然後創建新的雲端目錄進行同步", diff --git a/app/appearance/langs/zh_CN.json b/app/appearance/langs/zh_CN.json index 8e0dd2ab4..99025eaf9 100644 --- a/app/appearance/langs/zh_CN.json +++ b/app/appearance/langs/zh_CN.json @@ -960,7 +960,7 @@ "129": "云端数据已经损坏,请参考这里解决该问题", "130": "正在启动新版本安装程序...", "131": "不支持该操作,请到云端存储提供商管理控制台进行操作", - "132": "TODO", + "132": "当前搜索方式下不支持替换操作,请使用 [文本] 或 [正则表达式] 搜索方式", "133": "TODO", "134": "为避免刚恢复的数据被同步覆盖,数据同步功能已被自动暂停", "135": "无法解密数据。请确保多个设备上使用相同的密钥,然后创建新的云端目录进行同步", diff --git a/kernel/api/search.go b/kernel/api/search.go index e058d45bd..01798c252 100644 --- a/kernel/api/search.go +++ b/kernel/api/search.go @@ -38,12 +38,17 @@ func findReplace(c *gin.Context) { k := arg["k"].(string) r := arg["r"].(string) + methodArg := arg["method"] + var method int // 0:文本,1:查询语法,2:SQL,3:正则表达式 + if nil != methodArg { + method = int(methodArg.(float64)) + } idsArg := arg["ids"].([]interface{}) var ids []string for _, id := range idsArg { ids = append(ids, id.(string)) } - err := model.FindReplace(k, r, ids) + err := model.FindReplace(k, r, ids, method) if nil != err { ret.Code = -1 ret.Msg = err.Error() diff --git a/kernel/model/search.go b/kernel/model/search.go index ca9d7e1b4..0140eb985 100644 --- a/kernel/model/search.go +++ b/kernel/model/search.go @@ -19,6 +19,7 @@ package model import ( "bytes" "path" + "regexp" "strconv" "strings" "time" @@ -165,8 +166,13 @@ func SearchRefBlock(id, rootID, keyword string, beforeLen int) (ret []*Block, ne return } -func FindReplace(keyword, replacement string, ids []string) (err error) { - keyword = strings.Trim(keyword, "\"") // FTS 字符串需要去除双引号 +func FindReplace(keyword, replacement string, ids []string, method int) (err error) { + // method:0:文本,1:查询语法,2:SQL,3:正则表达式 + if 1 == method || 2 == method { + util.PushMsg(Conf.Language(132), 5000) + return + } + if keyword == replacement { return } @@ -194,40 +200,90 @@ func FindReplace(keyword, replacement string, ids []string) (err error) { switch n.Type { case ast.NodeDocument: title := n.IALAttr("title") - if strings.Contains(title, keyword) { - renameRootTitles[n.ID] = strings.ReplaceAll(title, keyword, replacement) - renameRoots = append(renameRoots, n) + if 0 == method { + if strings.Contains(title, keyword) { + renameRootTitles[n.ID] = strings.ReplaceAll(title, keyword, replacement) + renameRoots = append(renameRoots, n) + } + } else if 3 == method { + r, _ := regexp.Compile(keyword) + if nil != r && r.MatchString(title) { + renameRootTitles[n.ID] = r.ReplaceAllString(title, replacement) + renameRoots = append(renameRoots, n) + } } case ast.NodeText, ast.NodeLinkDest, ast.NodeLinkText, ast.NodeLinkTitle, ast.NodeCodeSpanContent, ast.NodeCodeBlockCode, ast.NodeInlineMathContent, ast.NodeMathBlockContent: - if bytes.Contains(n.Tokens, []byte(keyword)) { - n.Tokens = bytes.ReplaceAll(n.Tokens, []byte(keyword), []byte(replacement)) + if 0 == method { + if bytes.Contains(n.Tokens, []byte(keyword)) { + n.Tokens = bytes.ReplaceAll(n.Tokens, []byte(keyword), []byte(replacement)) + } + } else if 3 == method { + r, _ := regexp.Compile(keyword) + if nil != r && r.MatchString(string(n.Tokens)) { + n.Tokens = []byte(r.ReplaceAllString(string(n.Tokens), replacement)) + } } case ast.NodeTextMark: if n.IsTextMarkType("code") { escapedKey := html.EscapeString(keyword) - if strings.Contains(n.TextMarkTextContent, escapedKey) { - n.TextMarkTextContent = strings.ReplaceAll(n.TextMarkTextContent, escapedKey, replacement) + if 0 == method { + if strings.Contains(n.TextMarkTextContent, escapedKey) { + n.TextMarkTextContent = strings.ReplaceAll(n.TextMarkTextContent, escapedKey, replacement) + } + } else if 3 == method { + r, _ := regexp.Compile(escapedKey) + if nil != r && r.MatchString(n.TextMarkTextContent) { + n.TextMarkTextContent = r.ReplaceAllString(n.TextMarkTextContent, replacement) + } } } else { - if bytes.Contains(n.Tokens, []byte(keyword)) { - n.TextMarkTextContent = strings.ReplaceAll(n.TextMarkTextContent, keyword, replacement) + if 0 == method { + if bytes.Contains(n.Tokens, []byte(keyword)) { + n.TextMarkTextContent = strings.ReplaceAll(n.TextMarkTextContent, keyword, replacement) + } + } else if 3 == method { + r, _ := regexp.Compile(keyword) + if nil != r && r.MatchString(n.TextMarkTextContent) { + n.TextMarkTextContent = r.ReplaceAllString(n.TextMarkTextContent, replacement) + } } } - if strings.Contains(n.TextMarkTextContent, keyword) { - n.TextMarkTextContent = strings.ReplaceAll(n.TextMarkTextContent, keyword, replacement) - } - if strings.Contains(n.TextMarkInlineMathContent, keyword) { - n.TextMarkInlineMathContent = strings.ReplaceAll(n.TextMarkInlineMathContent, keyword, replacement) - } - if strings.Contains(n.TextMarkInlineMemoContent, keyword) { - n.TextMarkInlineMemoContent = strings.ReplaceAll(n.TextMarkInlineMemoContent, keyword, replacement) - } - if strings.Contains(n.TextMarkATitle, keyword) { - n.TextMarkATitle = strings.ReplaceAll(n.TextMarkATitle, keyword, replacement) - } - if strings.Contains(n.TextMarkAHref, keyword) { - n.TextMarkAHref = strings.ReplaceAll(n.TextMarkAHref, keyword, replacement) + if 0 == method { + if strings.Contains(n.TextMarkTextContent, keyword) { + n.TextMarkTextContent = strings.ReplaceAll(n.TextMarkTextContent, keyword, replacement) + } + if strings.Contains(n.TextMarkInlineMathContent, keyword) { + n.TextMarkInlineMathContent = strings.ReplaceAll(n.TextMarkInlineMathContent, keyword, replacement) + } + if strings.Contains(n.TextMarkInlineMemoContent, keyword) { + n.TextMarkInlineMemoContent = strings.ReplaceAll(n.TextMarkInlineMemoContent, keyword, replacement) + } + if strings.Contains(n.TextMarkATitle, keyword) { + n.TextMarkATitle = strings.ReplaceAll(n.TextMarkATitle, keyword, replacement) + } + if strings.Contains(n.TextMarkAHref, keyword) { + n.TextMarkAHref = strings.ReplaceAll(n.TextMarkAHref, keyword, replacement) + } + } else if 3 == method { + r, _ := regexp.Compile(keyword) + if nil != r { + if r.MatchString(n.TextMarkTextContent) { + n.TextMarkTextContent = r.ReplaceAllString(n.TextMarkTextContent, replacement) + } + if r.MatchString(n.TextMarkInlineMathContent) { + n.TextMarkInlineMathContent = r.ReplaceAllString(n.TextMarkInlineMathContent, replacement) + } + if r.MatchString(n.TextMarkInlineMemoContent) { + n.TextMarkInlineMemoContent = r.ReplaceAllString(n.TextMarkInlineMemoContent, replacement) + } + if r.MatchString(n.TextMarkATitle) { + n.TextMarkATitle = r.ReplaceAllString(n.TextMarkATitle, replacement) + } + if r.MatchString(n.TextMarkAHref) { + n.TextMarkAHref = r.ReplaceAllString(n.TextMarkAHref, replacement) + } + } } } return ast.WalkContinue