fix local markdown attachment regressions

This commit is contained in:
lix-2026
2026-05-29 11:13:05 +08:00
parent 1109e3c0d8
commit cbe789e034
63 changed files with 3249 additions and 1502 deletions
+3 -1
View File
@@ -33,6 +33,9 @@ pub fn HomePage(
/// 是否显示管理员授权入口
#[prop(optional)]
show_admin_access_policy: bool,
/// 是否启用树实时流
#[prop(optional, default = true)]
enable_tree_live: bool,
) -> impl IntoView {
let active_page_id = active_page_id.unwrap_or_default();
let active_page_title = active_page_title
@@ -50,7 +53,6 @@ pub fn HomePage(
.as_deref()
.map(|workspace_id| format!("/documents/{active_page_id}?workspaceId={workspace_id}"))
.unwrap_or_else(|| format!("/documents/{active_page_id}"));
let enable_tree_live = false;
view! {
<PageLayout current_nav="home" sidebar_tree_html={sidebar_tree_html.unwrap_or_default()} workspace_name={workspace_name.unwrap_or_default()} workspace_sidebar_html={workspace_sidebar_html.unwrap_or_default()} topbar_title={active_page_title.clone()} show_admin_access_policy={show_admin_access_policy} enable_tree_live={enable_tree_live}>
{move || if has_active_page {
+55 -12
View File
@@ -24,6 +24,15 @@ const SIDEBAR_TREE_JS: &str = r##"
"##;
fn browser_runtime_src(asset: &str) -> String {
let base = format!("/api/mnote-browser-runtime/{asset}");
if let Some(cache_buster) = crate::routes::dev_hot::dev_hot_cache_buster() {
format!("{base}?devHot={cache_buster}")
} else {
base
}
}
/// MNOTE Wolai 风格页面布局
///
/// 包含左侧栏 + 内容区的双栏布局。
@@ -152,17 +161,17 @@ pub fn PageLayout(
<div hidden data-testid="mnote-admin-access-policy-template-admin" inner_html={admin_access_policy_template}></div>
<div hidden data-testid="mnote-admin-access-policy-template-user" inner_html={user_access_policy_template}></div>
<script inner_html={crate::ssr::pages::admin::ADMIN_POLICY_SCRIPT.to_string()}></script>
<script type="module" src="/api/mnote-browser-runtime/resource-open-runtime.js"></script>
<script type="module" src="/api/mnote-browser-runtime/local-upload-runtime.js"></script>
<script type="module" src="/api/mnote-browser-runtime/filetree-runtime.js"></script>
<script type="module" src="/api/mnote-browser-runtime/filetree-selection-runtime.js"></script>
<script type="module" src="/api/mnote-browser-runtime/filetree-context-menu-runtime.js"></script>
<script type="module" src="/api/mnote-browser-runtime/filetree-dnd-runtime.js"></script>
<script type="module" src="/api/mnote-browser-runtime/filetree-keyboard-runtime.js"></script>
<script type="module" src="/api/mnote-browser-runtime/sidebar-shell-runtime.js"></script>
<script type="module" src="/api/mnote-browser-runtime/sidebar-tree-runtime.js"></script>
<script type="module" src={browser_runtime_src("resource-open-runtime.js")}></script>
<script type="module" src={browser_runtime_src("local-upload-runtime.js")}></script>
<script type="module" src={browser_runtime_src("filetree-runtime.js")}></script>
<script type="module" src={browser_runtime_src("filetree-selection-runtime.js")}></script>
<script type="module" src={browser_runtime_src("filetree-context-menu-runtime.js")}></script>
<script type="module" src={browser_runtime_src("filetree-dnd-runtime.js")}></script>
<script type="module" src={browser_runtime_src("filetree-keyboard-runtime.js")}></script>
<script type="module" src={browser_runtime_src("sidebar-shell-runtime.js")}></script>
<script type="module" src={browser_runtime_src("sidebar-tree-runtime.js")}></script>
<script inner_html={SIDEBAR_TREE_JS.to_string()}></script>
<script type="module" src="/api/mnote-browser-runtime/tree-live-controller.js"></script>
<script type="module" src={browser_runtime_src("tree-live-controller.js")}></script>
</aside>
<div class="mnote-sidebar-resizer" data-mnote-sidebar-resizer="true" role="separator" aria-orientation="vertical" aria-label="调整侧栏宽度"></div>
<div class="mnote-main">
@@ -385,6 +394,29 @@ mod tests {
assert!(html.contains(r#"data-mnote-shortcut-kind="page""#));
}
#[test]
fn page_layout_adds_dev_hot_cache_buster_to_browser_runtime_scripts() {
let _guard = crate::test_support::hermes_env_lock()
.lock()
.expect("env lock");
std::env::set_var("MNOTE_WEB_DEV_HOT_RELOAD", "1");
let html = crate::ssr::render_view(leptos::view! {
<super::PageLayout current_nav="documents" topbar_title={"个人".to_string()}>
<main>"正文"</main>
</super::PageLayout>
});
std::env::remove_var("MNOTE_WEB_DEV_HOT_RELOAD");
assert!(
html.contains("/api/mnote-browser-runtime/tree-live-controller.js?devHot="),
"dev:hot 下 tree live controller URL 必须带 cache buster,避免浏览器继续执行旧 WS/SSE 逻辑"
);
assert!(
html.contains("/api/mnote-browser-runtime/sidebar-tree-runtime.js?devHot="),
"dev:hot 下 sidebar runtime URL 也必须带 cache buster"
);
}
#[test]
fn sidebar_runtime_supports_shortcuts_and_scoped_filetree() {
assert!(SIDEBAR_TREE_RUNTIME_JS.contains("/api/sidebar/shortcuts"));
@@ -587,12 +619,11 @@ mod tests {
}
#[test]
fn sidebar_tree_runtime_polls_local_folder_without_browser_reload() {
fn sidebar_tree_runtime_uses_local_folder_events_without_browser_reload() {
assert!(SIDEBAR_TREE_RUNTIME_JS.contains("startLocalFolderSidebarWatch"));
assert!(SIDEBAR_TREE_RUNTIME_JS.contains("refreshLocalFolderSidebarSnapshot"));
assert!(SIDEBAR_WORKSPACE_RUNTIME_JS.contains("function currentWorkspaceId()"));
assert!(SIDEBAR_TREE_LIVE_APPLY_RUNTIME_JS.contains("var rootUri = currentRootUri();"));
assert!(SIDEBAR_TREE_LIVE_APPLY_RUNTIME_JS.contains("/api/tree/local-folder-watch"));
assert!(SIDEBAR_TREE_LIVE_APPLY_RUNTIME_JS.contains("/api/tree/projections/sidebar"));
assert!(SIDEBAR_TREE_LIVE_APPLY_RUNTIME_JS.contains("/api/tree/projections/file"));
assert!(SIDEBAR_TREE_LIVE_APPLY_RUNTIME_JS
@@ -604,6 +635,18 @@ mod tests {
assert!(
SIDEBAR_TREE_LIVE_APPLY_RUNTIME_JS.contains("data-mnote-local-folder-watch-applied")
);
assert!(
SIDEBAR_TREE_LIVE_APPLY_RUNTIME_JS.contains("data-mnote-local-folder-watch-disabled")
);
assert!(
TREE_LIVE_CONTROLLER_JS.contains("body.getAttribute('data-mnote-source-kind')"),
"根入口由 SSR body 暴露 local_folder 时,tree live 不能误走 retired Convex WS/SSE"
);
assert!(
TREE_LIVE_CONTROLLER_JS.contains("bootstrap.transport === 'local-folder-events'"),
"本地文件夹 bootstrap transport 应直接选择 /api/local-folder/events"
);
assert!(!SIDEBAR_TREE_LIVE_APPLY_RUNTIME_JS.contains("/api/tree/local-folder-watch"));
assert!(!SIDEBAR_TREE_LIVE_APPLY_RUNTIME_JS
.contains("fetch(window.location.href, { headers: { accept: 'text/html' } })"));
assert!(!SIDEBAR_TREE_LIVE_APPLY_RUNTIME_JS
+16 -2
View File
@@ -2011,12 +2011,20 @@ body {
text-decoration: none !important;
}
.document-shell .editor-surface .ProseMirror a[data-mnote-attachment-missing="true"] {
.document-shell .editor-surface .ProseMirror a[data-mnote-attachment-missing="true"],
.document-shell .editor-surface .ProseMirror a.mnote-uploaded-attachment-missing {
color: #9f1239 !important;
background: #fff1f2 !important;
box-shadow: inset 0 0 0 1px #fecdd3 !important;
}
.document-shell .editor-surface .ProseMirror a[data-mnote-attachment-unauthorized="true"],
.document-shell .editor-surface .ProseMirror a.mnote-uploaded-attachment-unauthorized {
color: #92400e !important;
background: #fffbeb !important;
box-shadow: inset 0 0 0 1px #fde68a !important;
}
.document-shell .editor-surface .ProseMirror a.mnote-uploaded-attachment-row::before,
.document-shell .editor-surface .ProseMirror a[href*="/api/local-folder/files/open"]::before {
content: "" !important;
@@ -2029,10 +2037,16 @@ body {
box-shadow: inset 0 0 0 1px rgba(15, 23, 42, 0.12) !important;
}
.document-shell .editor-surface .ProseMirror a[data-mnote-attachment-missing="true"]::before {
.document-shell .editor-surface .ProseMirror a[data-mnote-attachment-missing="true"]::before,
.document-shell .editor-surface .ProseMirror a.mnote-uploaded-attachment-missing::before {
background: #e11d48 !important;
}
.document-shell .editor-surface .ProseMirror a[data-mnote-attachment-unauthorized="true"]::before,
.document-shell .editor-surface .ProseMirror a.mnote-uploaded-attachment-unauthorized::before {
background: #d97706 !important;
}
.document-shell .editor-surface .ProseMirror a.mnote-uploaded-attachment-word::before {
background: #4f7df3;
}