diff --git a/app/src/protyle/util/insertHTML.ts b/app/src/protyle/util/insertHTML.ts
index fc64cf4b3..9a4a3a2a9 100644
--- a/app/src/protyle/util/insertHTML.ts
+++ b/app/src/protyle/util/insertHTML.ts
@@ -4,10 +4,12 @@ import {transaction, updateTransaction} from "../wysiwyg/transaction";
import {getContenteditableElement} from "../wysiwyg/getBlock";
import {
fixTableRange,
- focusBlock, focusByRange,
+ focusBlock,
+ focusByRange,
focusByWbr,
getEditorRange,
- getSelectionOffset, setLastNodeRange,
+ getSelectionOffset,
+ setLastNodeRange,
} from "./selection";
import {Constants} from "../../constants";
import {highlightRender} from "../render/highlightRender";
@@ -257,7 +259,9 @@ export const insertHTML = (html: string, protyle: IProtyle, isBlock = false,
// 移动端插入嵌入块时,获取到的 range 为旧值
useProtyleRange = false,
// 在开头粘贴块则插入上方
- insertByCursor = false) => {
+ insertByCursor = false,
+ // 是否需要再次 spin
+ spin = true) => {
if (html === "") {
return;
}
@@ -364,7 +368,7 @@ export const insertHTML = (html: string, protyle: IProtyle, isBlock = false,
}
let innerHTML = unSpinHTML || // 在 table 中插入需要使用转换好的行内元素 https://github.com/siyuan-note/siyuan/issues/9358
- protyle.lute.SpinBlockDOM(html) || // 需要再 spin 一次 https://github.com/siyuan-note/siyuan/issues/7118
+ (spin && protyle.lute.SpinBlockDOM(html)) || // 需要再 spin 一次 https://github.com/siyuan-note/siyuan/issues/7118
html; // 空格会被 Spin 不再,需要使用原文
// 粘贴纯文本时会进行内部转义,这里需要进行反转义 https://github.com/siyuan-note/siyuan/issues/10620
innerHTML = innerHTML.replace(/;;;lt;;;/g, "<").replace(/;;;gt;;;/g, ">");
diff --git a/app/src/protyle/util/paste.ts b/app/src/protyle/util/paste.ts
index 26fc192b5..10181aed6 100644
--- a/app/src/protyle/util/paste.ts
+++ b/app/src/protyle/util/paste.ts
@@ -591,7 +591,7 @@ export const paste = async (protyle: IProtyle, event: (ClipboardEvent | DragEven
textPlain = textPlain.replace(/\u200D```/g, "```");
const textPlainDom = protyle.lute.Md2BlockDOM(textPlain);
- insertHTML(textPlainDom, protyle, false, false, true);
+ insertHTML(textPlainDom, protyle, false, false, true, false /* 不再次 spin 以提升性能 https://github.com/siyuan-note/siyuan/issues/15306 */);
}
blockRender(protyle, protyle.wysiwyg.element);
processRender(protyle.wysiwyg.element);
diff --git a/kernel/model/file.go b/kernel/model/file.go
index 2cd5cf816..654f46ee2 100644
--- a/kernel/model/file.go
+++ b/kernel/model/file.go
@@ -764,6 +764,11 @@ func loadNodesByStartEnd(tree *parse.Tree, startID, endID string) (nodes []*ast.
}
break
}
+
+ if len(nodes) >= Conf.Editor.DynamicLoadBlocks {
+ // 如果加载到指定数量的块则停止加载
+ break
+ }
}
return
}
diff --git a/kernel/model/transaction.go b/kernel/model/transaction.go
index a9aee1db6..9f1867ca3 100644
--- a/kernel/model/transaction.go
+++ b/kernel/model/transaction.go
@@ -167,153 +167,156 @@ func performTx(tx *Transaction) (ret *TxErr) {
}
}()
- for _, op := range tx.DoOperations {
- switch op.Action {
- case "create":
- ret = tx.doCreate(op)
- case "update":
- ret = tx.doUpdate(op)
- case "insert":
- ret = tx.doInsert(op)
- case "delete":
- ret = tx.doDelete(op)
- case "move":
- ret = tx.doMove(op)
- case "moveOutlineHeading":
- ret = tx.doMoveOutlineHeading(op)
- case "append":
- ret = tx.doAppend(op)
- case "appendInsert":
- ret = tx.doAppendInsert(op)
- case "prependInsert":
- ret = tx.doPrependInsert(op)
- case "foldHeading":
- ret = tx.doFoldHeading(op)
- case "unfoldHeading":
- ret = tx.doUnfoldHeading(op)
- case "setAttrs":
- ret = tx.doSetAttrs(op)
- case "doUpdateUpdated":
- ret = tx.doUpdateUpdated(op)
- case "addFlashcards":
- ret = tx.doAddFlashcards(op)
- case "removeFlashcards":
- ret = tx.doRemoveFlashcards(op)
- case "setAttrViewName":
- ret = tx.doSetAttrViewName(op)
- case "setAttrViewFilters":
- ret = tx.doSetAttrViewFilters(op)
- case "setAttrViewSorts":
- ret = tx.doSetAttrViewSorts(op)
- case "setAttrViewPageSize":
- ret = tx.doSetAttrViewPageSize(op)
- case "setAttrViewColWidth":
- ret = tx.doSetAttrViewColumnWidth(op)
- case "setAttrViewColWrap":
- ret = tx.doSetAttrViewColumnWrap(op)
- case "setAttrViewColHidden":
- ret = tx.doSetAttrViewColumnHidden(op)
- case "setAttrViewColPin":
- ret = tx.doSetAttrViewColumnPin(op)
- case "setAttrViewColIcon":
- ret = tx.doSetAttrViewColumnIcon(op)
- case "setAttrViewColDesc":
- ret = tx.doSetAttrViewColumnDesc(op)
- case "insertAttrViewBlock":
- ret = tx.doInsertAttrViewBlock(op)
- case "removeAttrViewBlock":
- ret = tx.doRemoveAttrViewBlock(op)
- case "addAttrViewCol":
- ret = tx.doAddAttrViewColumn(op)
- case "updateAttrViewCol":
- ret = tx.doUpdateAttrViewColumn(op)
- case "removeAttrViewCol":
- ret = tx.doRemoveAttrViewColumn(op)
- case "sortAttrViewRow":
- ret = tx.doSortAttrViewRow(op)
- case "sortAttrViewCol":
- ret = tx.doSortAttrViewColumn(op)
- case "sortAttrViewKey":
- ret = tx.doSortAttrViewKey(op)
- case "updateAttrViewCell":
- ret = tx.doUpdateAttrViewCell(op)
- case "updateAttrViewColOptions":
- ret = tx.doUpdateAttrViewColOptions(op)
- case "removeAttrViewColOption":
- ret = tx.doRemoveAttrViewColOption(op)
- case "updateAttrViewColOption":
- ret = tx.doUpdateAttrViewColOption(op)
- case "setAttrViewColOptionDesc":
- ret = tx.doSetAttrViewColOptionDesc(op)
- case "setAttrViewColCalc":
- ret = tx.doSetAttrViewColCalc(op)
- case "updateAttrViewColNumberFormat":
- ret = tx.doUpdateAttrViewColNumberFormat(op)
- case "replaceAttrViewBlock":
- ret = tx.doReplaceAttrViewBlock(op)
- case "updateAttrViewColTemplate":
- ret = tx.doUpdateAttrViewColTemplate(op)
- case "addAttrViewView":
- ret = tx.doAddAttrViewView(op)
- case "removeAttrViewView":
- ret = tx.doRemoveAttrViewView(op)
- case "setAttrViewViewName":
- ret = tx.doSetAttrViewViewName(op)
- case "setAttrViewViewIcon":
- ret = tx.doSetAttrViewViewIcon(op)
- case "setAttrViewViewDesc":
- ret = tx.doSetAttrViewViewDesc(op)
- case "duplicateAttrViewView":
- ret = tx.doDuplicateAttrViewView(op)
- case "sortAttrViewView":
- ret = tx.doSortAttrViewView(op)
- case "updateAttrViewColRelation":
- ret = tx.doUpdateAttrViewColRelation(op)
- case "updateAttrViewColRollup":
- ret = tx.doUpdateAttrViewColRollup(op)
- case "hideAttrViewName":
- ret = tx.doHideAttrViewName(op)
- case "setAttrViewColDate":
- ret = tx.doSetAttrViewColDate(op)
- case "unbindAttrViewBlock":
- ret = tx.doUnbindAttrViewBlock(op)
- case "duplicateAttrViewKey":
- ret = tx.doDuplicateAttrViewKey(op)
- case "setAttrViewCoverFrom":
- ret = tx.doSetAttrViewCoverFrom(op)
- case "setAttrViewCoverFromAssetKeyID":
- ret = tx.doSetAttrViewCoverFromAssetKeyID(op)
- case "setAttrViewCardSize":
- ret = tx.doSetAttrViewCardSize(op)
- case "setAttrViewFitImage":
- ret = tx.doSetAttrViewFitImage(op)
- case "setAttrViewShowIcon":
- ret = tx.doSetAttrViewShowIcon(op)
- case "setAttrViewWrapField":
- ret = tx.doSetAttrViewWrapField(op)
- case "changeAttrViewLayout":
- ret = tx.doChangeAttrViewLayout(op)
- case "setAttrViewBlockView":
- ret = tx.doSetAttrViewBlockView(op)
- case "setAttrViewCardAspectRatio":
- ret = tx.doSetAttrViewCardAspectRatio(op)
- case "setAttrViewGroup":
- ret = tx.doSetAttrViewGroup(op)
- case "hideAttrViewGroup":
- ret = tx.doHideAttrViewGroup(op)
- case "foldAttrViewGroup":
- ret = tx.doFoldAttrViewGroup(op)
- case "syncAttrViewTableColWidth":
- ret = tx.doSyncAttrViewTableColWidth(op)
- case "removeAttrViewGroup":
- ret = tx.doRemoveAttrViewGroup(op)
- case "sortAttrViewGroup":
- ret = tx.doSortAttrViewGroup(op)
- }
+ isLargeInsert := tx.processLargeInsert()
+ if !isLargeInsert {
+ for _, op := range tx.DoOperations {
+ switch op.Action {
+ case "create":
+ ret = tx.doCreate(op)
+ case "update":
+ ret = tx.doUpdate(op)
+ case "insert":
+ ret = tx.doInsert(op)
+ case "delete":
+ ret = tx.doDelete(op)
+ case "move":
+ ret = tx.doMove(op)
+ case "moveOutlineHeading":
+ ret = tx.doMoveOutlineHeading(op)
+ case "append":
+ ret = tx.doAppend(op)
+ case "appendInsert":
+ ret = tx.doAppendInsert(op)
+ case "prependInsert":
+ ret = tx.doPrependInsert(op)
+ case "foldHeading":
+ ret = tx.doFoldHeading(op)
+ case "unfoldHeading":
+ ret = tx.doUnfoldHeading(op)
+ case "setAttrs":
+ ret = tx.doSetAttrs(op)
+ case "doUpdateUpdated":
+ ret = tx.doUpdateUpdated(op)
+ case "addFlashcards":
+ ret = tx.doAddFlashcards(op)
+ case "removeFlashcards":
+ ret = tx.doRemoveFlashcards(op)
+ case "setAttrViewName":
+ ret = tx.doSetAttrViewName(op)
+ case "setAttrViewFilters":
+ ret = tx.doSetAttrViewFilters(op)
+ case "setAttrViewSorts":
+ ret = tx.doSetAttrViewSorts(op)
+ case "setAttrViewPageSize":
+ ret = tx.doSetAttrViewPageSize(op)
+ case "setAttrViewColWidth":
+ ret = tx.doSetAttrViewColumnWidth(op)
+ case "setAttrViewColWrap":
+ ret = tx.doSetAttrViewColumnWrap(op)
+ case "setAttrViewColHidden":
+ ret = tx.doSetAttrViewColumnHidden(op)
+ case "setAttrViewColPin":
+ ret = tx.doSetAttrViewColumnPin(op)
+ case "setAttrViewColIcon":
+ ret = tx.doSetAttrViewColumnIcon(op)
+ case "setAttrViewColDesc":
+ ret = tx.doSetAttrViewColumnDesc(op)
+ case "insertAttrViewBlock":
+ ret = tx.doInsertAttrViewBlock(op)
+ case "removeAttrViewBlock":
+ ret = tx.doRemoveAttrViewBlock(op)
+ case "addAttrViewCol":
+ ret = tx.doAddAttrViewColumn(op)
+ case "updateAttrViewCol":
+ ret = tx.doUpdateAttrViewColumn(op)
+ case "removeAttrViewCol":
+ ret = tx.doRemoveAttrViewColumn(op)
+ case "sortAttrViewRow":
+ ret = tx.doSortAttrViewRow(op)
+ case "sortAttrViewCol":
+ ret = tx.doSortAttrViewColumn(op)
+ case "sortAttrViewKey":
+ ret = tx.doSortAttrViewKey(op)
+ case "updateAttrViewCell":
+ ret = tx.doUpdateAttrViewCell(op)
+ case "updateAttrViewColOptions":
+ ret = tx.doUpdateAttrViewColOptions(op)
+ case "removeAttrViewColOption":
+ ret = tx.doRemoveAttrViewColOption(op)
+ case "updateAttrViewColOption":
+ ret = tx.doUpdateAttrViewColOption(op)
+ case "setAttrViewColOptionDesc":
+ ret = tx.doSetAttrViewColOptionDesc(op)
+ case "setAttrViewColCalc":
+ ret = tx.doSetAttrViewColCalc(op)
+ case "updateAttrViewColNumberFormat":
+ ret = tx.doUpdateAttrViewColNumberFormat(op)
+ case "replaceAttrViewBlock":
+ ret = tx.doReplaceAttrViewBlock(op)
+ case "updateAttrViewColTemplate":
+ ret = tx.doUpdateAttrViewColTemplate(op)
+ case "addAttrViewView":
+ ret = tx.doAddAttrViewView(op)
+ case "removeAttrViewView":
+ ret = tx.doRemoveAttrViewView(op)
+ case "setAttrViewViewName":
+ ret = tx.doSetAttrViewViewName(op)
+ case "setAttrViewViewIcon":
+ ret = tx.doSetAttrViewViewIcon(op)
+ case "setAttrViewViewDesc":
+ ret = tx.doSetAttrViewViewDesc(op)
+ case "duplicateAttrViewView":
+ ret = tx.doDuplicateAttrViewView(op)
+ case "sortAttrViewView":
+ ret = tx.doSortAttrViewView(op)
+ case "updateAttrViewColRelation":
+ ret = tx.doUpdateAttrViewColRelation(op)
+ case "updateAttrViewColRollup":
+ ret = tx.doUpdateAttrViewColRollup(op)
+ case "hideAttrViewName":
+ ret = tx.doHideAttrViewName(op)
+ case "setAttrViewColDate":
+ ret = tx.doSetAttrViewColDate(op)
+ case "unbindAttrViewBlock":
+ ret = tx.doUnbindAttrViewBlock(op)
+ case "duplicateAttrViewKey":
+ ret = tx.doDuplicateAttrViewKey(op)
+ case "setAttrViewCoverFrom":
+ ret = tx.doSetAttrViewCoverFrom(op)
+ case "setAttrViewCoverFromAssetKeyID":
+ ret = tx.doSetAttrViewCoverFromAssetKeyID(op)
+ case "setAttrViewCardSize":
+ ret = tx.doSetAttrViewCardSize(op)
+ case "setAttrViewFitImage":
+ ret = tx.doSetAttrViewFitImage(op)
+ case "setAttrViewShowIcon":
+ ret = tx.doSetAttrViewShowIcon(op)
+ case "setAttrViewWrapField":
+ ret = tx.doSetAttrViewWrapField(op)
+ case "changeAttrViewLayout":
+ ret = tx.doChangeAttrViewLayout(op)
+ case "setAttrViewBlockView":
+ ret = tx.doSetAttrViewBlockView(op)
+ case "setAttrViewCardAspectRatio":
+ ret = tx.doSetAttrViewCardAspectRatio(op)
+ case "setAttrViewGroup":
+ ret = tx.doSetAttrViewGroup(op)
+ case "hideAttrViewGroup":
+ ret = tx.doHideAttrViewGroup(op)
+ case "foldAttrViewGroup":
+ ret = tx.doFoldAttrViewGroup(op)
+ case "syncAttrViewTableColWidth":
+ ret = tx.doSyncAttrViewTableColWidth(op)
+ case "removeAttrViewGroup":
+ ret = tx.doRemoveAttrViewGroup(op)
+ case "sortAttrViewGroup":
+ ret = tx.doSortAttrViewGroup(op)
+ }
- if nil != ret {
- tx.rollback()
- return
+ if nil != ret {
+ tx.rollback()
+ return
+ }
}
}
@@ -324,6 +327,42 @@ func performTx(tx *Transaction) (ret *TxErr) {
return
}
+func (tx *Transaction) processLargeInsert() bool {
+ opSize := len(tx.DoOperations)
+ isLargeInsert := 128 < opSize
+ if isLargeInsert {
+ var previousID string
+ for i, op := range tx.DoOperations {
+ if i == opSize-1 {
+ if "delete" != op.Action {
+ // 最后一个是 delete
+ isLargeInsert = false
+ }
+ break
+ }
+ if "insert" != op.Action {
+ isLargeInsert = false
+ break
+ }
+
+ if "" == op.PreviousID {
+ isLargeInsert = false
+ break
+ }
+ if "" == previousID {
+ previousID = op.PreviousID
+ } else if previousID != op.PreviousID {
+ isLargeInsert = false
+ break
+ }
+ }
+ if isLargeInsert {
+ tx.doLargeInsert(previousID)
+ }
+ }
+ return isLargeInsert
+}
+
func (tx *Transaction) doMove(operation *Operation) (ret *TxErr) {
var err error
id := operation.ID
@@ -980,6 +1019,127 @@ func syncDelete2AttributeView(node *ast.Node) (changedAvIDs []string) {
return
}
+func (tx *Transaction) doLargeInsert(previousID string) (ret *TxErr) {
+ tree, err := tx.loadTree(previousID)
+ if nil != err {
+ logging.LogErrorf("load tree [%s] failed: %s", previousID, err)
+ return &TxErr{code: TxErrCodeBlockNotFound, id: previousID}
+ }
+
+ for _, operation := range tx.DoOperations {
+ if "insert" != operation.Action {
+ break
+ }
+
+ data := strings.ReplaceAll(operation.Data.(string), editor.FrontEndCaret, "")
+ subTree := tx.luteEngine.BlockDOM2Tree(data)
+ tx.processGlobalAssets(subTree)
+
+ insertedNode := subTree.Root.FirstChild
+ if nil == insertedNode {
+ return &TxErr{code: TxErrCodeBlockNotFound, msg: "invalid data tree", id: tree.ID}
+ }
+ var remains []*ast.Node
+ for remain := insertedNode.Next; nil != remain; remain = remain.Next {
+ if ast.NodeKramdownBlockIAL != remain.Type {
+ if "" == remain.ID {
+ remain.ID = ast.NewNodeID()
+ remain.SetIALAttr("id", remain.ID)
+ }
+ remains = append(remains, remain)
+ }
+ }
+ if "" == insertedNode.ID {
+ insertedNode.ID = ast.NewNodeID()
+ insertedNode.SetIALAttr("id", insertedNode.ID)
+ }
+
+ node := treenode.GetNodeInTree(tree, previousID)
+ if nil == node {
+ logging.LogErrorf("get node [%s] in tree [%s] failed", previousID, tree.Root.ID)
+ return &TxErr{code: TxErrCodeBlockNotFound, id: previousID}
+ }
+
+ if ast.NodeHeading == node.Type && "1" == node.IALAttr("fold") {
+ children := treenode.HeadingChildren(node)
+ if l := len(children); 0 < l {
+ node = children[l-1]
+ }
+ }
+ if ast.NodeList == insertedNode.Type && nil != node.Parent && ast.NodeList == node.Parent.Type {
+ insertedNode = insertedNode.FirstChild
+ }
+ for i := len(remains) - 1; 0 <= i; i-- {
+ remain := remains[i]
+ node.InsertAfter(remain)
+ }
+ node.InsertAfter(insertedNode)
+
+ createdUpdated(insertedNode)
+ tx.nodes[insertedNode.ID] = insertedNode
+ tx.trees[tree.ID] = tree
+
+ // 收集引用的定义块 ID
+ refDefIDs := getRefDefIDs(insertedNode)
+ // 推送定义节点引用计数
+ for _, defID := range refDefIDs {
+ task.AppendAsyncTaskWithDelay(task.SetDefRefCount, util.SQLFlushInterval, refreshRefCount, defID)
+ }
+
+ upsertAvBlockRel(insertedNode)
+
+ // 复制为副本时将该副本块插入到数据库中 https://github.com/siyuan-note/siyuan/issues/11959
+ avs := insertedNode.IALAttr(av.NodeAttrNameAvs)
+ for _, avID := range strings.Split(avs, ",") {
+ if !ast.IsNodeIDPattern(avID) {
+ continue
+ }
+
+ AddAttributeViewBlock(tx, []map[string]interface{}{{
+ "id": insertedNode.ID,
+ "isDetached": false,
+ }}, avID, "", previousID, false)
+ ReloadAttrView(avID)
+ }
+
+ if ast.NodeAttributeView == insertedNode.Type {
+ // 插入数据库块时需要重新绑定其中已经存在的块
+ // 比如剪切操作时,会先进行 delete 数据库解绑块,这里需要重新绑定 https://github.com/siyuan-note/siyuan/issues/13031
+ attrView, parseErr := av.ParseAttributeView(insertedNode.AttributeViewID)
+ if nil == parseErr {
+ trees, toBindNodes := tx.getAttrViewBoundNodes(attrView)
+ for _, toBindNode := range toBindNodes {
+ t := trees[toBindNode.ID]
+ bindBlockAv0(tx, insertedNode.AttributeViewID, toBindNode, t)
+ }
+
+ // 设置视图 https://github.com/siyuan-note/siyuan/issues/15279
+ v := attrView.GetView(attrView.ViewID)
+ if nil != v {
+ insertedNode.AttributeViewType = string(v.LayoutType)
+ attrs := parse.IAL2Map(insertedNode.KramdownIAL)
+ if "" == attrs[av.NodeAttrView] {
+ attrs[av.NodeAttrView] = v.ID
+ err = setNodeAttrs(insertedNode, tree, attrs)
+ if err != nil {
+ logging.LogWarnf("set node [%s] attrs failed: %s", operation.BlockID, err)
+ return
+ }
+ }
+ }
+ }
+ }
+
+ operation.ID = insertedNode.ID
+ operation.ParentID = insertedNode.Parent.ID
+ }
+
+ if err = tx.writeTree(tree); nil != err {
+ return &TxErr{code: TxErrCodeWriteTree, msg: err.Error(), id: tree.ID}
+ }
+ return tx.doDelete(tx.DoOperations[len(tx.DoOperations)-1])
+}
+
func (tx *Transaction) doInsert(operation *Operation) (ret *TxErr) {
var bt *treenode.BlockTree
bts := treenode.GetBlockTrees([]string{operation.ParentID, operation.PreviousID, operation.NextID})
@@ -1005,42 +1165,7 @@ func (tx *Transaction) doInsert(operation *Operation) (ret *TxErr) {
data := strings.ReplaceAll(operation.Data.(string), editor.FrontEndCaret, "")
subTree := tx.luteEngine.BlockDOM2Tree(data)
-
- if !tx.isGlobalAssetsInit {
- tx.assetsDir = getAssetsDir(filepath.Join(util.DataDir, bt.BoxID), filepath.Dir(filepath.Join(util.DataDir, bt.BoxID, bt.Path)))
- tx.isGlobalAssets = strings.HasPrefix(tx.assetsDir, filepath.Join(util.DataDir, "assets"))
- tx.isGlobalAssetsInit = true
- }
- if !tx.isGlobalAssets {
- // 本地资源文件需要移动到用户手动建立的 assets 下 https://github.com/siyuan-note/siyuan/issues/2410
- ast.Walk(subTree.Root, func(n *ast.Node, entering bool) ast.WalkStatus {
- if !entering {
- return ast.WalkContinue
- }
-
- if ast.NodeLinkDest == n.Type && bytes.HasPrefix(n.Tokens, []byte("assets/")) {
- assetP := gulu.Str.FromBytes(n.Tokens)
- assetPath, e := GetAssetAbsPath(assetP)
- if nil != e {
- logging.LogErrorf("get path of asset [%s] failed: %s", assetP, err)
- return ast.WalkContinue
- }
-
- if !strings.HasPrefix(assetPath, filepath.Join(util.DataDir, "assets")) {
- // 非全局 assets 则跳过
- return ast.WalkContinue
- }
-
- // 只有全局 assets 才移动到相对 assets
- targetP := filepath.Join(tx.assetsDir, filepath.Base(assetPath))
- if e = filelock.Rename(assetPath, targetP); err != nil {
- logging.LogErrorf("copy path of asset from [%s] to [%s] failed: %s", assetPath, targetP, err)
- return ast.WalkContinue
- }
- }
- return ast.WalkContinue
- })
- }
+ tx.processGlobalAssets(subTree)
insertedNode := subTree.Root.FirstChild
if nil == insertedNode {
@@ -1189,6 +1314,47 @@ func (tx *Transaction) doInsert(operation *Operation) (ret *TxErr) {
return
}
+func (tx *Transaction) processGlobalAssets(tree *parse.Tree) {
+ if !tx.isGlobalAssetsInit {
+ tx.assetsDir = getAssetsDir(filepath.Join(util.DataDir, tree.Box), filepath.Dir(filepath.Join(util.DataDir, tree.Box, tree.Path)))
+ tx.isGlobalAssets = strings.HasPrefix(tx.assetsDir, filepath.Join(util.DataDir, "assets"))
+ tx.isGlobalAssetsInit = true
+ }
+
+ if tx.isGlobalAssets {
+ return
+ }
+
+ // 本地资源文件需要移动到用户手动建立的 assets 下 https://github.com/siyuan-note/siyuan/issues/2410
+ ast.Walk(tree.Root, func(n *ast.Node, entering bool) ast.WalkStatus {
+ if !entering {
+ return ast.WalkContinue
+ }
+
+ if ast.NodeLinkDest == n.Type && bytes.HasPrefix(n.Tokens, []byte("assets/")) {
+ assetP := gulu.Str.FromBytes(n.Tokens)
+ assetPath, e := GetAssetAbsPath(assetP)
+ if nil != e {
+ logging.LogErrorf("get path of asset [%s] failed: %s", assetP, e)
+ return ast.WalkContinue
+ }
+
+ if !strings.HasPrefix(assetPath, filepath.Join(util.DataDir, "assets")) {
+ // 非全局 assets 则跳过
+ return ast.WalkContinue
+ }
+
+ // 只有全局 assets 才移动到相对 assets
+ targetP := filepath.Join(tx.assetsDir, filepath.Base(assetPath))
+ if e = filelock.Rename(assetPath, targetP); e != nil {
+ logging.LogErrorf("copy path of asset from [%s] to [%s] failed: %s", assetPath, targetP, e)
+ return ast.WalkContinue
+ }
+ }
+ return ast.WalkContinue
+ })
+}
+
func (tx *Transaction) doUpdate(operation *Operation) (ret *TxErr) {
id := operation.ID
tree, err := tx.loadTree(id)