收口 MNote P0 P1 P2 审查尾项

- 归档 OnlyOffice live bridge、Page AI、mindmap、design governance 与相关 bug 条目
- 补齐 MinerU OCR 后端 runtime 合同与 smoke/test 基线
- 收口 ChatOnly/Doubao、ObjectIdentity、Page Aggregate compat 与 runtime owner 文档口径

验证:
- cargo test --manifest-path rust/Cargo.toml -p mnote-web local_ocr -- --test-threads=1
- cargo test --manifest-path rust/Cargo.toml -p mnote-web onlyoffice_bridge -- --test-threads=1
- git diff --check
- git diff --cached --check
- codegraph index . --force && codegraph status .
- codegraph sync . && codegraph status .
This commit is contained in:
lix-2026
2026-06-01 09:29:12 +08:00
parent 49a0545148
commit 1882db7681
143 changed files with 29810 additions and 3228 deletions
@@ -8,8 +8,8 @@ use crate::page_aggregate::{
use crate::routes::local_markdown_parser::{
file_stem_title, parse_markdown_attachment_refs, parse_markdown_page, split_frontmatter,
};
use crate::routes::local_search_index;
use crate::routes::snapshot_support::ProjectionSnapshot;
use crate::routes::{local_ocr, local_search_index};
use axum::extract::{Extension, Multipart, Path as AxumPath, Query, State};
use axum::http::{header, HeaderMap, HeaderValue, StatusCode};
use axum::Json;
@@ -4076,8 +4076,13 @@ fn local_mindmap_file_name(mindmap_id: &str) -> String {
fn is_local_mindmap_file_name(file_name: &str) -> bool {
let trimmed = file_name.trim();
let lower = trimmed.to_ascii_lowercase();
if !lower.ends_with(".json") {
return false;
}
lower.ends_with(".mindmap.json")
|| (trimmed.starts_with("思维导图") && lower.ends_with(".json"))
|| lower.starts_with("mindmap-")
|| lower.starts_with("mindmap_")
|| trimmed.starts_with("思维导图")
}
fn resolve_local_mindmap_path(
@@ -7395,6 +7400,9 @@ fn scan_markdown_page_tree(
continue;
}
if is_markdown_file(&entry.file_name) {
if local_ocr::is_local_ocr_sidecar_relative_path(&entry.relative_path) {
continue;
}
let markdown = fs::read_to_string(&entry.path).unwrap_or_default();
let parsed = parse_markdown_page(&markdown, &entry.file_name);
let page_id = local_markdown_path_page_id(&entry.relative_path);
@@ -8426,7 +8434,6 @@ fn editor_blocks_to_markdown_with_rewrite(
lines.push(text);
} else {
let src = rewrite_local_open_url_to_markdown_relative(src, local_file_context)
.map(|value| markdown_href_for_relative_path(&value))
.unwrap_or_else(|| src.to_string());
lines.push(format!(
"![{}]({})",
@@ -10424,6 +10431,61 @@ fn main() {}
let _ = std::fs::remove_dir_all(&root);
}
#[test]
fn local_markdown_generated_mindmap_id_roundtrips_as_mindmap_block() {
let root = temp_root("mnote-local-markdown-generated-mindmap-roundtrip");
init_workspace(&root);
std::fs::create_dir_all(root.join("Page")).expect("create page dir");
std::fs::write(root.join("Page").join("Page.md"), "").expect("write md");
let root_uri = format!("file://{}", root.display());
write_local_mindmap_data(
&root_uri,
"local-md:Page~2FPage.md",
"mindmap-123456",
serde_json::json!({"data":{"text":"中心主题"},"children":[]}),
false,
)
.expect("write generated mindmap");
save_local_markdown_page(
&root_uri,
"local-md:Page~2FPage.md",
None,
&serde_json::json!([
{
"type": "mindmap",
"props": {
"name": "思维导图",
"mindmapId": "mindmap-123456",
"rootNodeId": "root"
}
}
]),
)
.expect("save");
let saved = std::fs::read_to_string(root.join("Page").join("Page.md")).expect("read md");
assert!(saved.contains("[思维导图](mindmap-123456.json)"));
let aggregate = resolve_local_markdown_page_aggregate(&root_uri, "local-md:Page~2FPage.md")
.expect("aggregate");
let mindmap = aggregate.body.content.as_array().expect("blocks")[0].clone();
assert_eq!(mindmap["type"], "mindmap");
assert_eq!(mindmap["props"]["sourcePath"], "mindmap-123456.json");
assert_eq!(mindmap["props"]["mindmapId"], "mindmap-123456.json");
assert_eq!(
aggregate.body.block_document["blocks"][0]["attrs"]["mindmapId"],
serde_json::json!("mindmap-123456.json")
);
assert_eq!(
aggregate.body.block_document["blocks"][0]["attrs"]["sourcePath"],
serde_json::json!("mindmap-123456.json")
);
let _ = std::fs::remove_dir_all(&root);
}
#[test]
fn local_workspace_access_rejects_owner_mismatch() {
let root = temp_root("mnote-local-workspace-owner-mismatch");
@@ -12656,6 +12718,8 @@ fn main() {}
std::fs::create_dir_all(root.join("Page")).expect("create page dir");
std::fs::write(root.join("Page").join("Page.md"), "").expect("write md");
std::fs::write(root.join("Page").join("思维导图123456.json"), "{}").expect("write mindmap");
std::fs::write(root.join("Page").join("mindmap-123456.json"), "{}")
.expect("write generated mindmap");
std::fs::write(root.join("Page").join("report.docx"), b"docx").expect("write docx");
std::fs::write(root.join("Page").join("sheet.xlsx"), b"xlsx").expect("write xlsx");
std::fs::write(root.join("Page").join("slides.pptx"), b"pptx").expect("write pptx");
@@ -12687,6 +12751,17 @@ fn main() {}
mindmap["resourceMeta"]["workspacePath"]["objectIdentity"]["assetId"].as_str(),
Some("local-file:Page/思维导图123456.json")
);
let generated_mindmap = items
.iter()
.find(|item| item["title"].as_str() == Some("mindmap-123456.json"))
.expect("generated mindmap row");
assert_eq!(generated_mindmap["rowKind"].as_str(), Some("asset"));
assert_eq!(generated_mindmap["iconHint"].as_str(), Some("mindmap"));
assert_eq!(
generated_mindmap["resourceMeta"]["workspacePath"]["objectIdentity"]["objectKind"]
.as_str(),
Some("mindmap")
);
let office = items
.iter()
@@ -13239,6 +13314,41 @@ fn main() {}
let _ = std::fs::remove_dir_all(&root);
}
#[test]
fn local_ocr_sidecar_is_filetree_resource_not_page_tree_document() {
let root = temp_root("mnote-local-ocr-sidecar-page-tree");
init_workspace(&root);
let root_uri = format!("file://{}", root.display());
std::fs::create_dir_all(root.join("docs").join("Page.ocr")).expect("ocr dir");
std::fs::write(root.join("docs").join("Page.md"), "# Page\n").expect("page");
std::fs::write(
root.join("docs").join("Page.ocr").join("photo.png.ocr.md"),
"---\nmnote_ocr_version: 1\nprovider: mock\nmodel_version: vlm\nowner_document: ../Page.md\nsource_path: ./Page.assets/photo.png\nsource_root_relative_path: docs/Page.assets/photo.png\nsource_size: 3\nsource_mtime_ms: 1\nstatus: done\ncreated_at: 1\nupdated_at: 1\n---\n\nOCR-only-token\n",
)
.expect("ocr markdown");
let file_tree = load_local_folder_file_tree_children_snapshot(&root_uri, "docs/Page.ocr")
.expect("file tree");
let file_items = file_tree.projection["items"]
.as_array()
.expect("file items");
assert!(file_items
.iter()
.any(|item| item["title"].as_str() == Some("photo.png.ocr.md")));
let page_tree = load_local_folder_page_tree_snapshot(&root_uri).expect("page tree");
let page_items = page_tree.projection["items"]
.as_array()
.expect("page items");
assert!(page_items
.iter()
.any(|item| item["documentId"].as_str() == Some("local-md:docs~2FPage.md")));
assert!(!page_items.iter().any(|item| item["documentId"].as_str()
== Some("local-md:docs~2FPage.ocr~2Fphoto.png.ocr.md")));
let _ = std::fs::remove_dir_all(&root);
}
#[test]
fn local_rename_markdown_page_renames_nested_bundle() {
let root = temp_root("mnote-local-rename-nested-bundle");