fix: align local markdown conflict and title behavior
This commit is contained in:
@@ -92,11 +92,7 @@ async fn build_document_events_stream(
|
||||
match subscription.receiver.recv().await {
|
||||
Ok(payload) => {
|
||||
if let Some(expected) = document_relative_path.as_deref() {
|
||||
let relative_path = payload
|
||||
.get("relativePath")
|
||||
.and_then(Value::as_str)
|
||||
.unwrap_or_default();
|
||||
if expected != relative_path {
|
||||
if !document_event_targets_relative_path(&payload, expected) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
@@ -239,6 +235,14 @@ fn local_markdown_relative_path_from_document_id(document_id: &str) -> Option<St
|
||||
decode_local_id_segment(encoded).ok()
|
||||
}
|
||||
|
||||
fn document_event_targets_relative_path(payload: &Value, expected: &str) -> bool {
|
||||
let relative_path = payload
|
||||
.get("relativePath")
|
||||
.and_then(Value::as_str)
|
||||
.unwrap_or_default();
|
||||
expected == relative_path
|
||||
}
|
||||
|
||||
fn system_time_ms(time: SystemTime) -> u128 {
|
||||
time.duration_since(UNIX_EPOCH)
|
||||
.map(|duration| duration.as_millis())
|
||||
@@ -368,6 +372,23 @@ mod tests {
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn document_event_filter_rejects_resource_only_changes() {
|
||||
let payload = json!({
|
||||
"sourceKind": "local_folder",
|
||||
"rootUri": "file:///test",
|
||||
"relativePath": "docs/README/image.png",
|
||||
"documentId": "",
|
||||
"eventKind": "Create(File)",
|
||||
"revision": 1,
|
||||
});
|
||||
|
||||
assert!(
|
||||
!document_event_targets_relative_path(&payload, "docs/README.md"),
|
||||
"正文事件流不能把资源文件变化当作 Markdown 正文变化"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn build_tree_snapshot_payload_has_required_fields() {
|
||||
let sidebar_projection = json!({
|
||||
|
||||
@@ -2780,7 +2780,10 @@ pub fn resolve_local_markdown_page_aggregate(
|
||||
.and_then(page_options_hide_title_header_value)
|
||||
.is_none()
|
||||
{
|
||||
page_options.hide_title_header = true;
|
||||
// 我的空间(有 workspace.json 的托管工作区,包括用户本人的及分享的)默认显示页头标题。
|
||||
// 普通本地文件夹(无托管清单)默认隐藏页头标题,避免文件名与正文 H1 重复。
|
||||
let is_managed = load_local_workspace_manifest(&canonical_root).is_ok();
|
||||
page_options.hide_title_header = !is_managed;
|
||||
}
|
||||
let character_count = parsed.body.chars().count() as u64;
|
||||
let word_count = parsed
|
||||
@@ -10344,6 +10347,23 @@ fn main() {}
|
||||
let aggregate = resolve_local_markdown_page_aggregate(&root_uri, "local-md:File~20Name.md")
|
||||
.expect("aggregate");
|
||||
assert_eq!(aggregate.title, "File Name");
|
||||
// init_workspace 创建的是托管工作区(我的空间),默认显示页头标题。
|
||||
assert!(!aggregate.layout.page_options.hide_title_header);
|
||||
|
||||
let _ = std::fs::remove_dir_all(&root);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn local_markdown_non_managed_folder_default_hides_title_header() {
|
||||
// 非托管本地文件夹:没有 .mnote/workspace.json,应该默认隐藏标题。
|
||||
let root = temp_root("mnote-local-non-managed-title-default");
|
||||
let _ = std::fs::create_dir_all(root.join(".mnote"));
|
||||
std::fs::write(root.join("Page.md"), "# Page Heading\n").expect("write md");
|
||||
let root_uri = format!("file://{}", root.display());
|
||||
|
||||
let aggregate = resolve_local_markdown_page_aggregate(&root_uri, "local-md:Page.md")
|
||||
.expect("aggregate");
|
||||
assert_eq!(aggregate.title, "Page");
|
||||
assert!(aggregate.layout.page_options.hide_title_header);
|
||||
|
||||
let _ = std::fs::remove_dir_all(&root);
|
||||
|
||||
@@ -1578,6 +1578,8 @@ pub(crate) fn render_editor_island_adapter_script() -> &'static str {
|
||||
|
||||
const sessionViews = (session) => Array.from(session.views.values());
|
||||
|
||||
const localFolderEventChannelKey = (session) => `${String(session?.rootUri || '').trim()}#${String(session?.documentId || '').trim()}`;
|
||||
|
||||
const detachSessionFromLocalFolderChannel = (session) => {
|
||||
const channel = session.localFolderChannel;
|
||||
if (!channel) return;
|
||||
@@ -1588,7 +1590,7 @@ pub(crate) fn render_editor_island_adapter_script() -> &'static str {
|
||||
} catch (_) {
|
||||
// noop
|
||||
}
|
||||
localFolderEventRegistry.delete(channel.rootUri);
|
||||
localFolderEventRegistry.delete(channel.key);
|
||||
}
|
||||
session.localFolderChannel = null;
|
||||
};
|
||||
@@ -2394,13 +2396,17 @@ pub(crate) fn render_editor_island_adapter_script() -> &'static str {
|
||||
if (session.sourceKind !== 'local_folder' || !session.rootUri || typeof window.EventSource !== 'function') {
|
||||
return;
|
||||
}
|
||||
let channel = localFolderEventRegistry.get(session.rootUri);
|
||||
const channelKey = localFolderEventChannelKey(session);
|
||||
let channel = localFolderEventRegistry.get(channelKey);
|
||||
if (!channel) {
|
||||
const url = new URL('/api/local-folder/events', window.location.origin);
|
||||
url.searchParams.set('rootUri', session.rootUri);
|
||||
url.searchParams.set('documentId', session.documentId);
|
||||
const eventSource = new EventSource(url.toString());
|
||||
channel = {
|
||||
key: channelKey,
|
||||
rootUri: session.rootUri,
|
||||
documentId: session.documentId,
|
||||
eventSource,
|
||||
sessions: new Map(),
|
||||
};
|
||||
@@ -2410,6 +2416,7 @@ pub(crate) fn render_editor_island_adapter_script() -> &'static str {
|
||||
Array.from(channel.sessions.values()).forEach((targetSession) => {
|
||||
if (!targetSession || targetSession.views.size === 0) return;
|
||||
const documentId = typeof payload.documentId === 'string' ? payload.documentId.trim() : '';
|
||||
if (!documentId) return;
|
||||
const eventKind = String(payload.eventKind || '');
|
||||
const targetsCurrentDocument = Boolean(documentId && documentId === targetSession.documentId);
|
||||
if (documentId && !targetsCurrentDocument) return;
|
||||
@@ -2435,7 +2442,7 @@ pub(crate) fn render_editor_island_adapter_script() -> &'static str {
|
||||
eventSource.onerror = () => {
|
||||
console.warn('mnote local folder 外部更新事件流中断,将等待浏览器自动重连');
|
||||
};
|
||||
localFolderEventRegistry.set(session.rootUri, channel);
|
||||
localFolderEventRegistry.set(channelKey, channel);
|
||||
}
|
||||
channel.sessions.set(session.key, session);
|
||||
session.localFolderChannel = channel;
|
||||
@@ -3103,6 +3110,7 @@ pub(crate) fn render_editor_island_adapter_script() -> &'static str {
|
||||
view.disconnectSlashObserver();
|
||||
view.disconnectSlashObserver = null;
|
||||
}
|
||||
clearSessionConflictSurface(view.session);
|
||||
if (view.mountId != null && typeof view.runtime?.unmount === 'function') {
|
||||
try {
|
||||
view.runtime.unmount(view.mountId);
|
||||
@@ -5422,6 +5430,9 @@ mod tests {
|
||||
assert!(html.contains("refreshMindmapRuntimesFromTreePayload"));
|
||||
assert!(html.contains("__MNOTE_LEPTOS_MINDMAP_BRIDGES__"));
|
||||
assert!(html.contains("/api/local-folder/events"));
|
||||
assert!(html.contains("localFolderEventChannelKey"));
|
||||
assert!(html.contains("url.searchParams.set('documentId', session.documentId);"));
|
||||
assert!(html.contains("if (!documentId) return;"));
|
||||
assert!(html.contains("new EventSource(url.toString())"));
|
||||
assert!(html.contains("localFolderEventRegistry"));
|
||||
assert!(html
|
||||
@@ -5432,6 +5443,7 @@ mod tests {
|
||||
assert!(html.contains("session.views.size === 0"));
|
||||
assert!(html.contains("targetSession.saving"));
|
||||
assert!(html.contains("lastSelfSaveSignalAt"));
|
||||
assert!(html.contains("clearSessionConflictSurface(view.session);"));
|
||||
assert!(html.contains("command: 'replaceContent'"));
|
||||
assert!(html.contains("external-change-conflict"));
|
||||
assert!(html.contains("mnote-editor-conflict-panel"));
|
||||
|
||||
Reference in New Issue
Block a user