diff --git a/app/electron/main.js b/app/electron/main.js
index 7fb790190..f97a934bb 100644
--- a/app/electron/main.js
+++ b/app/electron/main.js
@@ -484,7 +484,7 @@ const initKernel = (workspace, port, lang) => {
errorWindowId = showErrorWindow("⚠️ 初始化工作空间失败 Failed to create workspace directory", "
初始化工作空间失败。
Failed to init workspace.
");
break;
case 26:
- errorWindowId = showErrorWindow("⚠️ 文件系统读写错误 File system access error", "请检查文件系统权限,并确保没有其他程序正在读写文件; 请勿使用第三方同步盘进行数据同步,否则数据会被损坏(iCloud/OneDrive/Dropbox/Google Drive/坚果云/百度网盘/腾讯微云等) 解决方案:请将工作空间移动到其他路径后再打开
Please check file system permissions and make sure no other programs are reading or writing to the file; Do not use a third-party sync disk for data sync, otherwise the data will be damaged (OneDrive/Dropbox/Google Drive/Nutstore/Baidu Netdisk/Tencent Weiyun, etc.) Solution: Please move the workspace to another path before opening it
");
+ errorWindowId = showErrorWindow("⚠️ 文件系统读写错误 File system access error", "1. 请检查文件系统权限,并确保没有其他程序正在读写文件 2. 请勿使用第三方同步盘进行数据同步,否则数据会被损坏(iCloud/OneDrive/Dropbox/Google Drive/坚果云/百度网盘/腾讯微云等) 解决方案:请将工作空间移动到其他路径后再打开
1. Please check file system permissions and make sure no other programs are reading or writing to the file 2. Do not use a third-party sync disk for data sync, otherwise the data will be damaged (OneDrive/Dropbox/Google Drive/Nutstore/Baidu Netdisk/Tencent Weiyun, etc.) Solution: Please move the workspace to another path before opening it
");
break;
case 0:
break;
diff --git a/app/guide/20210808180117-6v0mkxr/20200923234011-ieuun1p/20210808180303-xaduj2o/20210615213222-vs5tzbd.sy b/app/guide/20210808180117-6v0mkxr/20200923234011-ieuun1p/20210808180303-xaduj2o/20210615213222-vs5tzbd.sy
index 92d8c9cf8..485e02c12 100644
--- a/app/guide/20210808180117-6v0mkxr/20200923234011-ieuun1p/20210808180303-xaduj2o/20210615213222-vs5tzbd.sy
+++ b/app/guide/20210808180117-6v0mkxr/20200923234011-ieuun1p/20210808180303-xaduj2o/20210615213222-vs5tzbd.sy
@@ -5,7 +5,7 @@
"Properties": {
"id": "20210615213222-vs5tzbd",
"title": "Data history",
- "updated": "20220831210614"
+ "updated": "20230403114455"
},
"Children": [
{
@@ -183,7 +183,7 @@
"ListData": {},
"Properties": {
"id": "20220501135308-30uwxvd",
- "updated": "20220831210614"
+ "updated": "20230403114455"
},
"Children": [
{
@@ -217,7 +217,7 @@
},
{
"Type": "NodeText",
- "Data": " - "
+ "Data": " - "
},
{
"Type": "NodeTextMark",
@@ -226,7 +226,7 @@
},
{
"Type": "NodeText",
- "Data": " - "
+ "Data": " - "
},
{
"Type": "NodeTextMark",
@@ -235,12 +235,16 @@
},
{
"Type": "NodeText",
- "Data": " make adjustments), and the suffix of the history folder is "
+ "Data": " make adjustments), and the suffix of the history folder is "
},
{
"Type": "NodeTextMark",
"TextMarkType": "code",
"TextMarkTextContent": "-update"
+ },
+ {
+ "Type": "NodeText",
+ "Data": ""
}
]
}
@@ -274,6 +278,10 @@
"Type": "NodeTextMark",
"TextMarkType": "code",
"TextMarkTextContent": "-sync"
+ },
+ {
+ "Type": "NodeText",
+ "Data": ""
}
]
}
@@ -307,6 +315,10 @@
"Type": "NodeTextMark",
"TextMarkType": "code",
"TextMarkTextContent": "-delete"
+ },
+ {
+ "Type": "NodeText",
+ "Data": ""
}
]
}
@@ -351,6 +363,10 @@
"Type": "NodeTextMark",
"TextMarkType": "code",
"TextMarkTextContent": "-clean"
+ },
+ {
+ "Type": "NodeText",
+ "Data": ""
}
]
}
@@ -365,7 +381,7 @@
},
"Properties": {
"id": "20220501135308-wzazz01",
- "updated": "20220628203717"
+ "updated": "20230403114455"
},
"Children": [
{
@@ -373,7 +389,7 @@
"Type": "NodeParagraph",
"Properties": {
"id": "20220501135308-xldpw4q",
- "updated": "20220628203717"
+ "updated": "20230403114455"
},
"Children": [
{
@@ -382,17 +398,69 @@
},
{
"Type": "NodeTextMark",
- "TextMarkType": "kbd",
+ "TextMarkType": "block-ref",
+ "TextMarkBlockRefID": "20220628204454-hhxohv5",
+ "TextMarkBlockRefSubtype": "s",
"TextMarkTextContent": "Optimize typography"
},
{
"Type": "NodeText",
- "Data": " function, a history will be generated, and the suffix of the history folder is "
+ "Data": ", a history will be generated, and the suffix of the history folder is "
},
{
"Type": "NodeTextMark",
"TextMarkType": "code",
"TextMarkTextContent": "-format"
+ },
+ {
+ "Type": "NodeText",
+ "Data": ""
+ }
+ ]
+ }
+ ]
+ },
+ {
+ "ID": "20230403114321-4rwhj6j",
+ "Type": "NodeListItem",
+ "ListData": {
+ "BulletChar": 42,
+ "Marker": "Kg=="
+ },
+ "Properties": {
+ "id": "20230403114321-4rwhj6j",
+ "updated": "20230403114424"
+ },
+ "Children": [
+ {
+ "ID": "20230403114321-tou7etc",
+ "Type": "NodeParagraph",
+ "Properties": {
+ "id": "20230403114321-tou7etc",
+ "updated": "20230403114424"
+ },
+ "Children": [
+ {
+ "Type": "NodeText",
+ "Data": "When using "
+ },
+ {
+ "Type": "NodeTextMark",
+ "TextMarkType": "kbd",
+ "TextMarkTextContent": "Search replace"
+ },
+ {
+ "Type": "NodeText",
+ "Data": ", a history will be generated, and the suffix of the history folder "
+ },
+ {
+ "Type": "NodeTextMark",
+ "TextMarkType": "code",
+ "TextMarkTextContent": "-replace"
+ },
+ {
+ "Type": "NodeText",
+ "Data": ""
}
]
}
diff --git a/app/guide/20210808180117-czj9bvb/20200812220555-lj3enxa/20210808180321-hbvl5c2/20210615211733-v6rzowm.sy b/app/guide/20210808180117-czj9bvb/20200812220555-lj3enxa/20210808180321-hbvl5c2/20210615211733-v6rzowm.sy
index f614333d6..16e284eaf 100644
--- a/app/guide/20210808180117-czj9bvb/20200812220555-lj3enxa/20210808180321-hbvl5c2/20210615211733-v6rzowm.sy
+++ b/app/guide/20210808180117-czj9bvb/20200812220555-lj3enxa/20210808180321-hbvl5c2/20210615211733-v6rzowm.sy
@@ -5,7 +5,7 @@
"Properties": {
"id": "20210615211733-v6rzowm",
"title": "数据历史",
- "updated": "20220831210638"
+ "updated": "20230403114310"
},
"Children": [
{
@@ -14,7 +14,7 @@
"HeadingLevel": 2,
"Properties": {
"id": "20220501134357-003dftr",
- "updated": "20220501134417"
+ "updated": "20230403113939"
},
"Children": [
{
@@ -183,7 +183,7 @@
"ListData": {},
"Properties": {
"id": "20210403160319-ufy7jta",
- "updated": "20220831210638"
+ "updated": "20230403114310"
},
"Children": [
{
@@ -217,7 +217,7 @@
},
{
"Type": "NodeText",
- "Data": " - "
+ "Data": " - "
},
{
"Type": "NodeTextMark",
@@ -226,7 +226,7 @@
},
{
"Type": "NodeText",
- "Data": " - "
+ "Data": " - "
},
{
"Type": "NodeTextMark",
@@ -235,12 +235,16 @@
},
{
"Type": "NodeText",
- "Data": " 进行调整),历史文件夹后缀为 "
+ "Data": " 进行调整),历史文件夹后缀为 "
},
{
"Type": "NodeTextMark",
"TextMarkType": "code",
"TextMarkTextContent": "-update"
+ },
+ {
+ "Type": "NodeText",
+ "Data": ""
}
]
}
@@ -268,24 +272,16 @@
"Children": [
{
"Type": "NodeText",
- "Data": "云端"
- },
- {
- "Type": "NodeText",
- "Data": "同步时,本地被"
- },
- {
- "Type": "NodeText",
- "Data": "云端"
- },
- {
- "Type": "NodeText",
- "Data": "覆盖的数据会生成历史,历史文件夹后缀为 "
+ "Data": "云端同步时,本地被云端覆盖的数据会生成历史,历史文件夹后缀为 "
},
{
"Type": "NodeTextMark",
"TextMarkType": "code",
"TextMarkTextContent": "-sync"
+ },
+ {
+ "Type": "NodeText",
+ "Data": ""
}
]
}
@@ -313,20 +309,16 @@
"Children": [
{
"Type": "NodeText",
- "Data": "手动删除笔记本、文档和"
- },
- {
- "Type": "NodeText",
- "Data": "资源文件"
- },
- {
- "Type": "NodeText",
- "Data": "时会生成历史,历史文件夹后缀为 "
+ "Data": "手动删除笔记本、文档和资源文件时会生成历史,历史文件夹后缀为 "
},
{
"Type": "NodeTextMark",
"TextMarkType": "code",
"TextMarkTextContent": "-delete"
+ },
+ {
+ "Type": "NodeText",
+ "Data": ""
}
]
}
@@ -371,6 +363,10 @@
"Type": "NodeTextMark",
"TextMarkType": "code",
"TextMarkTextContent": "-clean"
+ },
+ {
+ "Type": "NodeText",
+ "Data": ""
}
]
}
@@ -385,7 +381,7 @@
},
"Properties": {
"id": "20220501134920-qb6wh0b",
- "updated": "20220628203857"
+ "updated": "20230403114300"
},
"Children": [
{
@@ -393,7 +389,55 @@
"Type": "NodeParagraph",
"Properties": {
"id": "20220501134920-k403n1r",
- "updated": "20220628203857"
+ "updated": "20230403114300"
+ },
+ "Children": [
+ {
+ "Type": "NodeText",
+ "Data": "使用"
+ },
+ {
+ "Type": "NodeTextMark",
+ "TextMarkType": "block-ref",
+ "TextMarkBlockRefID": "20220628204444-9n0y9h2",
+ "TextMarkBlockRefSubtype": "s",
+ "TextMarkTextContent": "优化排版"
+ },
+ {
+ "Type": "NodeText",
+ "Data": "时会生成历史,历史文件夹后缀为 "
+ },
+ {
+ "Type": "NodeTextMark",
+ "TextMarkType": "code",
+ "TextMarkTextContent": "-format"
+ },
+ {
+ "Type": "NodeText",
+ "Data": ""
+ }
+ ]
+ }
+ ]
+ },
+ {
+ "ID": "20230403113929-44xkaoz",
+ "Type": "NodeListItem",
+ "ListData": {
+ "BulletChar": 42,
+ "Marker": "Kg=="
+ },
+ "Properties": {
+ "id": "20230403113929-44xkaoz",
+ "updated": "20230403114310"
+ },
+ "Children": [
+ {
+ "ID": "20230403113929-ud36ubv",
+ "Type": "NodeParagraph",
+ "Properties": {
+ "id": "20230403113929-ud36ubv",
+ "updated": "20230403114310"
},
"Children": [
{
@@ -403,16 +447,20 @@
{
"Type": "NodeTextMark",
"TextMarkType": "kbd",
- "TextMarkTextContent": "优化排版"
+ "TextMarkTextContent": "搜索替换"
},
{
"Type": "NodeText",
- "Data": " 功能时会生成历史,历史文件夹后缀为 "
+ "Data": " 时会生成历史,历史文件夹后缀为 "
},
{
"Type": "NodeTextMark",
"TextMarkType": "code",
- "TextMarkTextContent": "-format"
+ "TextMarkTextContent": "-replace"
+ },
+ {
+ "Type": "NodeText",
+ "Data": ""
}
]
}
diff --git a/app/guide/20211226090932-5lcq56f/20211226115423-d5z1joq/20211226121203-rjjngpz/20211226122707-8cr09co.sy b/app/guide/20211226090932-5lcq56f/20211226115423-d5z1joq/20211226121203-rjjngpz/20211226122707-8cr09co.sy
index 6a16c9709..11c2c0cb4 100644
--- a/app/guide/20211226090932-5lcq56f/20211226115423-d5z1joq/20211226121203-rjjngpz/20211226122707-8cr09co.sy
+++ b/app/guide/20211226090932-5lcq56f/20211226115423-d5z1joq/20211226121203-rjjngpz/20211226122707-8cr09co.sy
@@ -5,7 +5,7 @@
"Properties": {
"id": "20211226122707-8cr09co",
"title": "數據歷史",
- "updated": "20220831210559"
+ "updated": "20230403114139"
},
"Children": [
{
@@ -183,7 +183,7 @@
"ListData": {},
"Properties": {
"id": "20220501135134-p6jpw7s",
- "updated": "20220831210559"
+ "updated": "20230403114139"
},
"Children": [
{
@@ -217,7 +217,7 @@
},
{
"Type": "NodeText",
- "Data": " - "
+ "Data": " - "
},
{
"Type": "NodeTextMark",
@@ -226,7 +226,7 @@
},
{
"Type": "NodeText",
- "Data": " - "
+ "Data": " - "
},
{
"Type": "NodeTextMark",
@@ -235,12 +235,16 @@
},
{
"Type": "NodeText",
- "Data": " 進行調整),歷史文件夾後綴為 "
+ "Data": " 進行調整),歷史文件夾後綴為 "
},
{
"Type": "NodeTextMark",
"TextMarkType": "code",
"TextMarkTextContent": "-update"
+ },
+ {
+ "Type": "NodeText",
+ "Data": ""
}
]
}
@@ -274,6 +278,10 @@
"Type": "NodeTextMark",
"TextMarkType": "code",
"TextMarkTextContent": "-sync"
+ },
+ {
+ "Type": "NodeText",
+ "Data": ""
}
]
}
@@ -307,6 +315,10 @@
"Type": "NodeTextMark",
"TextMarkType": "code",
"TextMarkTextContent": "-delete"
+ },
+ {
+ "Type": "NodeText",
+ "Data": ""
}
]
}
@@ -351,6 +363,10 @@
"Type": "NodeTextMark",
"TextMarkType": "code",
"TextMarkTextContent": "-clean"
+ },
+ {
+ "Type": "NodeText",
+ "Data": ""
}
]
}
@@ -365,7 +381,7 @@
},
"Properties": {
"id": "20220501135134-6k05zq3",
- "updated": "20220628203918"
+ "updated": "20230403114139"
},
"Children": [
{
@@ -373,7 +389,55 @@
"Type": "NodeParagraph",
"Properties": {
"id": "20220501135134-o33eejl",
- "updated": "20220628203918"
+ "updated": "20230403114139"
+ },
+ "Children": [
+ {
+ "Type": "NodeText",
+ "Data": "使用"
+ },
+ {
+ "Type": "NodeTextMark",
+ "TextMarkType": "block-ref",
+ "TextMarkBlockRefID": "20220628204420-ui79vkt",
+ "TextMarkBlockRefSubtype": "s",
+ "TextMarkTextContent": "優化排版"
+ },
+ {
+ "Type": "NodeText",
+ "Data": "時會生成歷史,歷史文件夾後綴為 "
+ },
+ {
+ "Type": "NodeTextMark",
+ "TextMarkType": "code",
+ "TextMarkTextContent": "-format"
+ },
+ {
+ "Type": "NodeText",
+ "Data": ""
+ }
+ ]
+ }
+ ]
+ },
+ {
+ "ID": "20230403114032-wxsdrdw",
+ "Type": "NodeListItem",
+ "ListData": {
+ "BulletChar": 42,
+ "Marker": "Kg=="
+ },
+ "Properties": {
+ "id": "20230403114032-wxsdrdw",
+ "updated": "20230403114109"
+ },
+ "Children": [
+ {
+ "ID": "20230403114032-r5mnisa",
+ "Type": "NodeParagraph",
+ "Properties": {
+ "id": "20230403114032-r5mnisa",
+ "updated": "20230403114109"
},
"Children": [
{
@@ -383,16 +447,20 @@
{
"Type": "NodeTextMark",
"TextMarkType": "kbd",
- "TextMarkTextContent": "優化排版"
+ "TextMarkTextContent": "搜索替換"
},
{
"Type": "NodeText",
- "Data": " 功能時會生成歷史,歷史文件夾後綴為 "
+ "Data": " 時會生成歷史,歷史文件夾後綴為 "
},
{
"Type": "NodeTextMark",
"TextMarkType": "code",
- "TextMarkTextContent": "-format"
+ "TextMarkTextContent": "-replace"
+ },
+ {
+ "Type": "NodeText",
+ "Data": ""
}
]
}
diff --git a/app/src/history/history.ts b/app/src/history/history.ts
index 134d3a65e..2f2c76c5d 100644
--- a/app/src/history/history.ts
+++ b/app/src/history/history.ts
@@ -266,6 +266,7 @@ export const openHistory = () => {
delete
format
sync
+ replace
diff --git a/kernel/model/history.go b/kernel/model/history.go
index 520cd3e9a..b6cebd444 100644
--- a/kernel/model/history.go
+++ b/kernel/model/history.go
@@ -547,15 +547,20 @@ func (box *Box) recentModifiedDocs() (ret []string) {
}
const (
- HistoryOpClean = "clean"
- HistoryOpUpdate = "update"
- HistoryOpDelete = "delete"
- HistoryOpFormat = "format"
- HistoryOpSync = "sync"
+ HistoryOpClean = "clean"
+ HistoryOpUpdate = "update"
+ HistoryOpDelete = "delete"
+ HistoryOpFormat = "format"
+ HistoryOpSync = "sync"
+ HistoryOpReplace = "replace"
)
func GetHistoryDir(suffix string) (ret string, err error) {
- ret = filepath.Join(util.HistoryDir, time.Now().Format("2006-01-02-150405")+"-"+suffix)
+ return getHistoryDir(suffix, time.Now())
+}
+
+func getHistoryDir(suffix string, t time.Time) (ret string, err error) {
+ ret = filepath.Join(util.HistoryDir, t.Format("2006-01-02-150405")+"-"+suffix)
if err = os.MkdirAll(ret, 0755); nil != err {
logging.LogErrorf("make history dir failed: %s", err)
return
@@ -584,7 +589,7 @@ func ReindexHistory() (err error) {
return
}
-var validOps = []string{HistoryOpClean, HistoryOpUpdate, HistoryOpDelete, HistoryOpFormat, HistoryOpSync}
+var validOps = []string{HistoryOpClean, HistoryOpUpdate, HistoryOpDelete, HistoryOpFormat, HistoryOpSync, HistoryOpReplace}
const (
HistoryTypeDocName = 0
diff --git a/kernel/model/search.go b/kernel/model/search.go
index 1e7b56b00..d397a8bdf 100644
--- a/kernel/model/search.go
+++ b/kernel/model/search.go
@@ -20,8 +20,9 @@ import (
"bytes"
"errors"
"fmt"
- "github.com/siyuan-note/siyuan/kernel/task"
+ "os"
"path"
+ "path/filepath"
"regexp"
"sort"
"strconv"
@@ -35,10 +36,12 @@ import (
"github.com/88250/lute/lex"
"github.com/88250/lute/parse"
"github.com/jinzhu/copier"
+ "github.com/siyuan-note/filelock"
"github.com/siyuan-note/logging"
"github.com/siyuan-note/siyuan/kernel/conf"
"github.com/siyuan-note/siyuan/kernel/search"
"github.com/siyuan-note/siyuan/kernel/sql"
+ "github.com/siyuan-note/siyuan/kernel/task"
"github.com/siyuan-note/siyuan/kernel/treenode"
"github.com/siyuan-note/siyuan/kernel/util"
"github.com/xrash/smetrics"
@@ -215,16 +218,65 @@ func FindReplace(keyword, replacement string, ids []string, method int) (err err
ids = gulu.Str.RemoveDuplicatedElem(ids)
var renameRoots []*ast.Node
renameRootTitles := map[string]string{}
+ cachedTrees := map[string]*parse.Tree{}
+
+ historyDir, err := getHistoryDir(HistoryOpReplace, time.Now())
+ if nil != err {
+ logging.LogErrorf("get history dir failed: %s", err)
+ return
+ }
+
for _, id := range ids {
- var tree *parse.Tree
- tree, err = loadTreeByBlockID(id)
- if nil != err {
+ bt := treenode.GetBlockTree(id)
+ if nil == bt {
+ continue
+ }
+
+ tree := cachedTrees[bt.RootID]
+ if nil != tree {
+ continue
+ }
+
+ tree, _ = loadTreeByBlockID(id)
+ if nil == tree {
+ continue
+ }
+
+ historyPath := filepath.Join(historyDir, tree.Box, tree.Path)
+ if err = os.MkdirAll(filepath.Dir(historyPath), 0755); nil != err {
+ logging.LogErrorf("generate history failed: %s", err)
return
}
+ var data []byte
+ if data, err = filelock.ReadFile(filepath.Join(util.DataDir, tree.Box, tree.Path)); err != nil {
+ logging.LogErrorf("generate history failed: %s", err)
+ return
+ }
+
+ if err = gulu.File.WriteFileSafer(historyPath, data, 0644); err != nil {
+ logging.LogErrorf("generate history failed: %s", err)
+ return
+ }
+
+ cachedTrees[bt.RootID] = tree
+ }
+ indexHistoryDir(filepath.Base(historyDir), util.NewLute())
+
+ for _, id := range ids {
+ bt := treenode.GetBlockTree(id)
+ if nil == bt {
+ continue
+ }
+
+ tree := cachedTrees[bt.RootID]
+ if nil == tree {
+ continue
+ }
+
node := treenode.GetNodeInTree(tree, id)
if nil == node {
- return
+ continue
}
ast.Walk(node, func(n *ast.Node, entering bool) ast.WalkStatus {