{"record":{"id":"7fb883d73ca2d628","repo":"siyuan-note/siyuan","slug":"conf-language-341","errorCode":null,"errorMessage":"Conf.Language(341)","messagePattern":"Conf\\.Language\\(341\\)","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"kernel/model/heading.go","lineNumber":188,"sourceCode":"\t\t}()\n\t}\n\treturn\n}\n\nfunc Doc2Heading(srcID, targetID string, after bool) (srcTreeBox, srcTreePath string, err error) {\n\tif !ast.IsNodeIDPattern(srcID) || !ast.IsNodeIDPattern(targetID) {\n\t\treturn\n\t}\n\n\tFlushTxQueue()\n\n\tsrcTree, _ := LoadTreeByBlockID(srcID)\n\tif nil == srcTree {\n\t\terr = ErrBlockNotFound\n\t\treturn\n\t}\n\tif IsBoxDoc(srcTree.Box, srcTree.ID) {\n\t\terr = errors.New(Conf.Language(341))\n\t\treturn\n\t}\n\n\tsubDir := filepath.Join(util.DataDir, srcTree.Box, strings.TrimSuffix(srcTree.Path, \".sy\"))\n\tif gulu.File.IsDir(subDir) {\n\t\tif !util.IsEmptyDir(subDir) {\n\t\t\terr = errors.New(Conf.Language(20))\n\t\t\treturn\n\t\t}\n\n\t\tif removeErr := os.Remove(subDir); nil != removeErr { // 移除空文件夹不会有副作用\n\t\t\tlogging.LogWarnf(\"remove empty dir [%s] failed: %s\", subDir, removeErr)\n\t\t}\n\t}\n\n\tif nil == treenode.GetBlockTree(targetID) {\n\t\t// 目标块不存在时忽略处理\n\t\treturn","sourceCodeStart":170,"sourceCodeEnd":206,"githubUrl":"https://github.com/siyuan-note/siyuan/blob/8641553a1f07374001902d3ce773285db1292b2d/kernel/model/heading.go#L170-L206","documentation":"Doc2Heading converts a document into heading content merged under a target heading. Before doing anything it refuses when the source tree IS the notebook's own top-level document (IsBoxDoc): Conf.Language(341) — 'The top-level notebook document cannot be removed or moved'. The operation would delete/move the notebook's root document, which is not allowed.","triggerScenarios":"Calling Doc2Heading (API doc2HeadingHeadingTransaction) where srcID refers to the notebook's root/top-level document rather than a sub-document.","commonSituations":"Selecting the whole notebook root in a script or UI automation and invoking the merge-to-heading action; misidentifying the srcID by passing the doc that equals the notebook path root.","solutions":["Pass a child sub-document ID as srcID, not the notebook's root document","Check that srcTree path is not the root (.sy at notebook root) before calling","Choose a different organization (rename or move the notebook) instead of converting the root doc"],"exampleFix":"// before\nawait fetchPost('/api/filetree/doc2Heading', {srcID: rootDocID, targetID: headingID});\n// after\nif (rootDocID === srcID) throw new Error('top-level doc cannot be converted');\nawait fetchPost('/api/filetree/doc2Heading', {srcID: subDocID, targetID: headingID});","handlingStrategy":"validation","validationCode":"// root doc of a notebook has path == '/<rootDocID>.sy'; refuse conversion\nconst doc = getDocMeta(srcID);\nif (doc.path === `/${doc.rootID}.sy`) throw new Error('cannot convert top-level notebook document');","typeGuard":null,"tryCatchPattern":"try {\n  await fetchPost('/api/filetree/doc2Heading', {srcID, targetID});\n} catch (e) {\n  if (String(e.msg).includes('top-level notebook document')) {\n    alert('Choose a sub-document instead of the notebook root');\n  } else throw e;\n}","preventionTips":["Only offer the 'convert to heading' action on non-root documents in UI","Compare doc.path against the notebook root pattern before calling","Remember top-level docs also cannot be removed or moved"],"tags":["filetree","document-structure","protected-resource"],"backgroundTag":"unsupported-operation","analyzedSha":"8641553a1f07374001902d3ce773285db1292b2d","analyzedAt":"2026-09-11T16:08:28.414Z","contentChangedAt":"2026-09-11T16:08:28.414Z","schemaVersion":2},"datasetVersion":"2026-09-14T00:17:10.932Z"}