fix local folder resource lifecycle
This commit is contained in:
@@ -322,12 +322,8 @@ pub async fn root_entry(
|
||||
.filter(|value| !value.is_empty())
|
||||
.unwrap_or("local-folder")
|
||||
.to_string();
|
||||
let requested_or_recent_page_id = choose_root_entry_active_page_id(
|
||||
requested_page_id.clone(),
|
||||
recent_page_id.clone(),
|
||||
None,
|
||||
None,
|
||||
);
|
||||
let requested_or_recent_page_id =
|
||||
choose_root_entry_active_page_id(requested_page_id.clone(), None, None, None);
|
||||
let workspace_projection = build_workspace_shell_projection(
|
||||
&snapshot.dataset,
|
||||
&workspace_id,
|
||||
@@ -336,7 +332,7 @@ pub async fn root_entry(
|
||||
);
|
||||
let selected_active_page_id = choose_root_entry_active_page_id(
|
||||
requested_page_id.clone(),
|
||||
recent_page_id.clone(),
|
||||
None,
|
||||
workspace_projection.active_page_id.as_deref(),
|
||||
workspace_projection
|
||||
.my_page_items
|
||||
@@ -494,7 +490,24 @@ pub async fn root_entry(
|
||||
})
|
||||
};
|
||||
let (html_title, content, body_extra) = if active_page_id.trim().is_empty() {
|
||||
("MNOTE".to_string(), render_workspace_entry(), String::new())
|
||||
let body_extra = if active_source_kind.as_deref() == Some("local_folder") {
|
||||
let panes_bootstrap_json = serde_json::to_string(&json!({
|
||||
"schema": "mnote.document_panes_bootstrap.v1",
|
||||
"secondaryRequested": false,
|
||||
"secondaryInvalid": false,
|
||||
"panes": [],
|
||||
}))
|
||||
.unwrap_or_else(|_| "{}".to_string());
|
||||
format!(
|
||||
r#"<script id="__MNOTE_DOCUMENT_PANES_BOOTSTRAP__" type="application/json">{}</script>
|
||||
{}"#,
|
||||
escape_script_json(&panes_bootstrap_json),
|
||||
render_editor_island_adapter_script(),
|
||||
)
|
||||
} else {
|
||||
String::new()
|
||||
};
|
||||
("MNOTE".to_string(), render_workspace_entry(), body_extra)
|
||||
} else {
|
||||
match build_page_aggregate_snapshot(
|
||||
&state,
|
||||
@@ -2505,8 +2518,11 @@ mod tests {
|
||||
let html = String::from_utf8(body.to_vec()).expect("utf8");
|
||||
assert!(!html.contains("初始化的新页面"));
|
||||
assert!(html.contains(r#"data-mnote-source-kind="local_folder""#));
|
||||
assert!(html.contains(r#"data-testid="mnote-create-default-local-workspace""#));
|
||||
assert!(html.contains(r#"data-testid="mnote-open-local-folder-empty""#));
|
||||
assert!(html.contains(r#"data-testid="mnote-document-workspace""#));
|
||||
assert!(html.contains(r#"data-mnote-resource-tab-host="true""#));
|
||||
assert!(html.contains("__MNOTE_DOCUMENT_PANES_BOOTSTRAP__"));
|
||||
assert!(!html.contains(r#"data-testid="mnote-create-default-local-workspace""#));
|
||||
assert!(!html.contains(r#"data-testid="mnote-open-local-folder-empty""#));
|
||||
assert!(!html.contains("workspaces:ensureDefaultWorkspace"));
|
||||
let _ = std::fs::remove_dir_all(&base);
|
||||
}
|
||||
@@ -2719,6 +2735,7 @@ mod tests {
|
||||
.uri(format!(
|
||||
"/?sourceKind=local_folder&rootUri={root_uri}&treeView=filetree"
|
||||
))
|
||||
.header("cookie", "mnote_recent_page_id=local-md:Other~2FOther.md")
|
||||
.header("x-mnote-actor-id", "user_real")
|
||||
.header("x-mnote-actor-type", "user")
|
||||
.body(Body::empty())
|
||||
@@ -2743,6 +2760,50 @@ mod tests {
|
||||
assert!(!html.contains(r#"href="/tree"#));
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn root_entry_local_folder_without_active_page_keeps_resource_tab_host() {
|
||||
let root = temp_root("mnote-root-local-folder-no-active-page");
|
||||
std::fs::create_dir_all(root.join("attachments")).expect("create attachments");
|
||||
std::fs::write(root.join("attachments").join("report-a.pdf"), b"%PDF-1.4\n")
|
||||
.expect("write pdf");
|
||||
|
||||
let root_uri = format!("file://{}", root.display());
|
||||
crate::routes::local_folder_source::initialize_local_workspace_for_actor(
|
||||
"user_real",
|
||||
&root_uri,
|
||||
)
|
||||
.expect("init local workspace");
|
||||
let response = app_with_config("http://127.0.0.1:3100".into(), false)
|
||||
.oneshot(
|
||||
Request::builder()
|
||||
.uri(format!(
|
||||
"/?sourceKind=local_folder&rootUri={root_uri}&treeView=filetree"
|
||||
))
|
||||
.header("cookie", "mnote_recent_page_id=local-md:Other~2FOther.md")
|
||||
.header("x-mnote-actor-id", "user_real")
|
||||
.header("x-mnote-actor-type", "user")
|
||||
.body(Body::empty())
|
||||
.expect("request"),
|
||||
)
|
||||
.await
|
||||
.expect("response");
|
||||
|
||||
let _ = std::fs::remove_dir_all(&root);
|
||||
|
||||
assert_eq!(response.status(), StatusCode::OK);
|
||||
let body = to_bytes(response.into_body(), usize::MAX)
|
||||
.await
|
||||
.expect("body");
|
||||
let html = String::from_utf8(body.to_vec()).expect("utf8");
|
||||
assert!(html.contains(r#"data-mnote-shell="workspace""#));
|
||||
assert!(html.contains(r#"data-row-id="local:asset:attachments/report-a.pdf""#));
|
||||
assert!(html.contains(r#"data-testid="mnote-document-workspace""#));
|
||||
assert!(html.contains(r#"data-mnote-resource-tab-host="true""#));
|
||||
assert!(html.contains("__MNOTE_DOCUMENT_PANES_BOOTSTRAP__"));
|
||||
assert!(html.contains("openResourceInActiveTab"));
|
||||
assert!(!html.contains(r#"<script id="__MNOTE_PAGE_AGGREGATE__""#));
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn root_entry_redirects_anonymous_viewer_to_auth() {
|
||||
let response = app_with_config("http://127.0.0.1:3100".into(), false)
|
||||
|
||||
@@ -10201,12 +10201,12 @@ fn main() {}
|
||||
})
|
||||
.expect("page row");
|
||||
|
||||
// 有 H1 且无 frontmatter title 时,标题来自 H1。
|
||||
assert_eq!(row["title"].as_str(), Some("Body Heading"));
|
||||
// 本地 Markdown 标题来自文件名,正文 H1 只作为正文内容。
|
||||
assert_eq!(row["title"].as_str(), Some("File Name"));
|
||||
|
||||
let aggregate = resolve_local_markdown_page_aggregate(&root_uri, "local-md:File~20Name.md")
|
||||
.expect("aggregate");
|
||||
assert_eq!(aggregate.title, "Body Heading");
|
||||
assert_eq!(aggregate.title, "File Name");
|
||||
|
||||
let _ = std::fs::remove_dir_all(&root);
|
||||
}
|
||||
@@ -11386,12 +11386,11 @@ fn main() {}
|
||||
let snapshot = load_local_folder_page_tree_snapshot(&root_uri).expect("load page tree");
|
||||
let items = snapshot.projection["items"].as_array().expect("items");
|
||||
|
||||
// 找到 Root 页面节点和 Child 页面节点。
|
||||
// 标题优先级:H1("Body Root" / "Body Child")高于文件名。
|
||||
// 找到 Root 页面节点和 Child 页面节点。标题来自文件名,不来自正文 H1。
|
||||
let root_node = items
|
||||
.iter()
|
||||
.find(|item| {
|
||||
item["title"].as_str() == Some("Body Root")
|
||||
item["title"].as_str() == Some("Root")
|
||||
&& item["resourceMeta"]["resourceKind"].as_str() == Some("document")
|
||||
})
|
||||
.expect("Root page node");
|
||||
@@ -11399,7 +11398,7 @@ fn main() {}
|
||||
let child_node = items
|
||||
.iter()
|
||||
.find(|item| {
|
||||
item["title"].as_str() == Some("Body Child")
|
||||
item["title"].as_str() == Some("Child")
|
||||
&& item["resourceMeta"]["resourceKind"].as_str() == Some("document")
|
||||
})
|
||||
.expect("Child page node");
|
||||
|
||||
@@ -79,61 +79,17 @@ struct MarkdownInlineStyles {
|
||||
}
|
||||
|
||||
pub fn parse_markdown_page(markdown: &str, file_name: &str) -> ParsedLocalMarkdownPage {
|
||||
let (frontmatter, body) = split_frontmatter(markdown);
|
||||
let (_frontmatter, body) = split_frontmatter(markdown);
|
||||
let body_owned = body.to_string();
|
||||
// 标题优先级:frontmatter title > 正文第一条 H1 > 文件名。
|
||||
let title = parse_frontmatter_title(frontmatter.as_deref())
|
||||
.or_else(|| find_first_h1(&body_owned))
|
||||
.unwrap_or_else(|| file_stem_title(file_name));
|
||||
// 本地 Markdown 的树标题和页头标题统一来自文件名;
|
||||
// frontmatter title 与正文 H1 都保留为正文/元数据内容,不作为资源标题真相。
|
||||
let title = file_stem_title(file_name);
|
||||
ParsedLocalMarkdownPage {
|
||||
title,
|
||||
body: body_owned,
|
||||
}
|
||||
}
|
||||
|
||||
/// 从 frontmatter 中提取简单 `title:` 字段。
|
||||
fn parse_frontmatter_title(frontmatter: Option<&str>) -> Option<String> {
|
||||
let text = frontmatter?;
|
||||
for line in text.lines() {
|
||||
let trimmed = line.trim();
|
||||
if let Some(value) = trimmed.strip_prefix("title:") {
|
||||
let raw = value.trim();
|
||||
if raw.is_empty() {
|
||||
continue;
|
||||
}
|
||||
let unquoted = if (raw.starts_with('"') && raw.ends_with('"'))
|
||||
|| (raw.starts_with('\'') && raw.ends_with('\''))
|
||||
{
|
||||
&raw[1..raw.len() - 1]
|
||||
} else {
|
||||
raw
|
||||
};
|
||||
let title = unquoted.trim().to_string();
|
||||
if !title.is_empty() {
|
||||
return Some(title);
|
||||
}
|
||||
}
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
/// 通过 AST 找正文第一条 H1,避免把代码块里的 `# ...` 误判为标题。
|
||||
fn find_first_h1(body: &str) -> Option<String> {
|
||||
let arena = Arena::new();
|
||||
let root = parse_document(&arena, body, &markdown_options());
|
||||
for node in root.descendants() {
|
||||
let value = node.data.borrow();
|
||||
if let NodeValue::Heading(heading) = &value.value {
|
||||
if heading.level != 1 {
|
||||
continue;
|
||||
}
|
||||
let title = collect_plain_text(node).trim().to_string();
|
||||
return (!title.is_empty()).then_some(title);
|
||||
}
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
pub fn markdown_to_blocks(markdown: &str) -> Value {
|
||||
markdown_to_blocks_with_attachment_paths(markdown, &BTreeSet::new())
|
||||
}
|
||||
@@ -920,65 +876,35 @@ mod tests {
|
||||
assert!(!d2_inline.is_empty(), "非空数据单元格不应为空");
|
||||
}
|
||||
|
||||
// 标题优先级:frontmatter title > H1 > 文件名。
|
||||
// 本地 Markdown 标题统一来自文件名,frontmatter title 和 H1 都只保留为正文/元数据。
|
||||
|
||||
#[test]
|
||||
fn parse_markdown_title_uses_frontmatter_title() {
|
||||
fn parse_markdown_title_uses_filename_over_frontmatter_and_h1() {
|
||||
let parsed = parse_markdown_page(
|
||||
"---\ntitle: Frontmatter Title\n---\n# H1 Heading\nbody\n",
|
||||
"file.md",
|
||||
"File Name.md",
|
||||
);
|
||||
assert_eq!(parsed.title, "Frontmatter Title");
|
||||
assert_eq!(parsed.title, "File Name");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parse_markdown_title_uses_first_h1_when_no_frontmatter_title() {
|
||||
let parsed = parse_markdown_page("# H1 Title\nbody\n## Not H1\n", "file.md");
|
||||
assert_eq!(parsed.title, "H1 Title");
|
||||
fn parse_markdown_title_uses_filename_when_h1_exists() {
|
||||
let parsed = parse_markdown_page("# H1 Title\nbody\n## Not H1\n", "File Name.md");
|
||||
assert_eq!(parsed.title, "File Name");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parse_markdown_title_falls_back_to_filename() {
|
||||
fn parse_markdown_title_uses_filename_when_body_has_no_h1() {
|
||||
let parsed = parse_markdown_page("plain text\nmore text\n", "My File.md");
|
||||
assert_eq!(parsed.title, "My File");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parse_markdown_title_prefers_frontmatter_over_h1() {
|
||||
let parsed = parse_markdown_page("---\ntitle: FM Title\n---\n# H1 here\n", "file.md");
|
||||
assert_eq!(parsed.title, "FM Title");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parse_markdown_title_handles_quoted_double() {
|
||||
let parsed = parse_markdown_page("---\ntitle: \"Quoted Double\"\n---\nbody\n", "file.md");
|
||||
assert_eq!(parsed.title, "Quoted Double");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parse_markdown_title_handles_quoted_single() {
|
||||
let parsed = parse_markdown_page("---\ntitle: 'Single Quoted'\n---\nbody\n", "file.md");
|
||||
assert_eq!(parsed.title, "Single Quoted");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parse_markdown_title_uses_h1_when_frontmatter_has_no_title() {
|
||||
let parsed = parse_markdown_page("---\nother: value\n---\n# From H1\n", "file.md");
|
||||
assert_eq!(parsed.title, "From H1");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parse_markdown_title_uses_filename_when_body_has_no_h1() {
|
||||
fn parse_markdown_title_ignores_frontmatter_without_title() {
|
||||
let parsed = parse_markdown_page("---\ntags: foo\n---\nplain text\n", "NoH1.md");
|
||||
assert_eq!(parsed.title, "NoH1");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parse_markdown_title_handles_atx_closing_markers() {
|
||||
let parsed = parse_markdown_page("# Heading With Closing #\n", "file.md");
|
||||
assert_eq!(parsed.title, "Heading With Closing");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parse_markdown_title_ignores_hash_inside_code_block() {
|
||||
let parsed = parse_markdown_page("```md\n# Not A Title\n```\nplain text\n", "Fallback.md");
|
||||
|
||||
@@ -222,6 +222,8 @@ pub async fn document_page_shell(
|
||||
secondary_workspace_id={secondary_aggregate.as_ref().map(|aggregate| aggregate.identity.workspace_id.clone()).unwrap_or_default()}
|
||||
secondary_page_subtree_json={secondary_page_subtree_json.unwrap_or_default()}
|
||||
secondary_page_options_json={secondary_page_options_json.unwrap_or_default()}
|
||||
primary_hide_title_header={is_local_folder}
|
||||
secondary_hide_title_header={secondary_source_kind == Some("local_folder")}
|
||||
show_admin_access_policy={is_local_access_policy_admin_context(&context)}
|
||||
/>
|
||||
});
|
||||
@@ -653,7 +655,7 @@ pub(crate) fn render_editor_island_adapter_script() -> &'static str {
|
||||
};
|
||||
|
||||
const panesBootstrap = parseJsonScript(PANES_BOOTSTRAP_ID);
|
||||
if (!panesBootstrap || !Array.isArray(panesBootstrap.panes) || panesBootstrap.panes.length === 0) return;
|
||||
if (!panesBootstrap || !Array.isArray(panesBootstrap.panes)) return;
|
||||
|
||||
const paneRouteConfig = {
|
||||
primary: {
|
||||
@@ -803,7 +805,6 @@ pub(crate) fn render_editor_island_adapter_script() -> &'static str {
|
||||
};
|
||||
|
||||
const paneRuntimes = panesBootstrap.panes.map(buildPaneRuntime).filter(Boolean);
|
||||
if (!paneRuntimes.length) return;
|
||||
|
||||
const loadRuntime = async () => {
|
||||
if (window.__mnoteLeptosTiptapRuntimePromise) {
|
||||
@@ -4298,9 +4299,11 @@ pub(crate) fn render_editor_island_adapter_script() -> &'static str {
|
||||
});
|
||||
}, { once: true });
|
||||
|
||||
Promise.all(paneRuntimes.map((paneRuntime) => mountPane(paneRuntime))).catch((error) => {
|
||||
console.error('mnote multi-pane editor mount failed', error);
|
||||
});
|
||||
if (paneRuntimes.length) {
|
||||
Promise.all(paneRuntimes.map((paneRuntime) => mountPane(paneRuntime))).catch((error) => {
|
||||
console.error('mnote multi-pane editor mount failed', error);
|
||||
});
|
||||
}
|
||||
})();
|
||||
</script>"#
|
||||
}
|
||||
@@ -5282,8 +5285,8 @@ mod tests {
|
||||
payload["result"]["identity"]["documentId"],
|
||||
"local-md:Local~20Aggregate~2FLocal~20Aggregate.md"
|
||||
);
|
||||
// H1 "Local Heading" 优先于文件名标题 "Local Aggregate"。
|
||||
assert_eq!(payload["result"]["head"]["title"], "Local Heading");
|
||||
// 本地 Markdown 标题来自文件名;正文 H1 只作为正文内容。
|
||||
assert_eq!(payload["result"]["head"]["title"], "Local Aggregate");
|
||||
assert_eq!(payload["result"]["head"]["permissions"]["readOnly"], false);
|
||||
assert_eq!(payload["result"]["body"]["revision"], 0);
|
||||
assert!(payload["result"]["body"]["content"]
|
||||
@@ -5423,8 +5426,8 @@ mod tests {
|
||||
assert!(html.contains("localFolderEventRegistry"));
|
||||
assert!(html
|
||||
.contains("if (!response.ok) {\n if (session.sourceKind === 'local_folder')"));
|
||||
assert!(html.contains("mayAffectMissingDocument"));
|
||||
assert!(html.contains("eventKind.includes('Remove') || eventKind.includes('Name')"));
|
||||
assert!(html.contains("shouldSuppressLocalFolderSelfChange"));
|
||||
assert!(html.contains("kind.includes('Modify(Name')"));
|
||||
assert!(html.contains("targetSession.views.size === 0"));
|
||||
assert!(html.contains("session.views.size === 0"));
|
||||
assert!(html.contains("targetSession.saving"));
|
||||
|
||||
@@ -21,6 +21,7 @@ pub struct DocumentPaneViewModel {
|
||||
pub has_page_subtree: bool,
|
||||
pub primary_legacy_ids: bool,
|
||||
pub visible: bool,
|
||||
pub hide_title_header: bool,
|
||||
}
|
||||
|
||||
#[component]
|
||||
@@ -76,7 +77,11 @@ fn DocumentPane(model: DocumentPaneViewModel) -> impl IntoView {
|
||||
data-page-font={model.page_font.clone()}
|
||||
data-page-show-heading-numbers={model.page_show_heading_numbers.to_string()}
|
||||
>
|
||||
<header class="document-shell-header">
|
||||
<header
|
||||
class="document-shell-header"
|
||||
data-page-title-hidden={model.hide_title_header.to_string()}
|
||||
hidden={model.hide_title_header}
|
||||
>
|
||||
<div class="document-page-icon" aria-hidden="true">
|
||||
<span class="material-symbols-outlined material-symbols-filled mnote-material-page-icon" data-icon="home"></span>
|
||||
</div>
|
||||
@@ -177,6 +182,12 @@ pub fn DocumentPage(
|
||||
/// 右侧页面选项 JSON(可选)
|
||||
#[prop(optional)]
|
||||
secondary_page_options_json: String,
|
||||
/// 是否隐藏主文档页头标题
|
||||
#[prop(optional)]
|
||||
primary_hide_title_header: bool,
|
||||
/// 是否隐藏右侧文档页头标题
|
||||
#[prop(optional)]
|
||||
secondary_hide_title_header: bool,
|
||||
/// 是否显示管理员授权入口
|
||||
#[prop(optional)]
|
||||
show_admin_access_policy: bool,
|
||||
@@ -260,6 +271,7 @@ pub fn DocumentPage(
|
||||
has_page_subtree,
|
||||
primary_legacy_ids: true,
|
||||
visible: true,
|
||||
hide_title_header: primary_hide_title_header,
|
||||
};
|
||||
let secondary_model = DocumentPaneViewModel {
|
||||
pane_role: "secondary",
|
||||
@@ -278,6 +290,7 @@ pub fn DocumentPage(
|
||||
has_page_subtree: secondary_has_page_subtree,
|
||||
primary_legacy_ids: false,
|
||||
visible: secondary_visible,
|
||||
hide_title_header: secondary_hide_title_header,
|
||||
};
|
||||
view! {
|
||||
<PageLayout current_nav="documents" 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={title.clone()} show_admin_access_policy={show_admin_access_policy} enable_tree_live={enable_tree_live}>
|
||||
|
||||
@@ -61,7 +61,54 @@ pub fn HomePage(
|
||||
</main>
|
||||
}.into_any()
|
||||
} else {
|
||||
view! {}.into_any()
|
||||
view! {
|
||||
<main
|
||||
class="document-workspace mnote-workspace-empty-editor-host"
|
||||
data-testid="mnote-document-workspace"
|
||||
data-has-secondary-pane="false"
|
||||
data-workspace-id={workspace_id.clone().unwrap_or_default()}
|
||||
>
|
||||
<section class="document-main-editor-group" data-testid="mnote-main-editor-tab-host" data-pane-role="primary">
|
||||
<div class="mnote-main-tab-strip" data-mnote-main-tab-strip="true" data-pane-role="primary" role="tablist" aria-label="主编辑区标签页">
|
||||
<button
|
||||
type="button"
|
||||
class="mnote-main-tab is-active"
|
||||
data-mnote-main-tab="page"
|
||||
data-mnote-tab-kind="page"
|
||||
data-pane-role="primary"
|
||||
data-workspace-id={workspace_id.clone().unwrap_or_default()}
|
||||
role="tab"
|
||||
aria-selected="true"
|
||||
tabindex="0"
|
||||
>
|
||||
<span class="mnote-main-tab-badge" aria-hidden="true"></span>
|
||||
<span class="mnote-main-tab-title">"文件夹"</span>
|
||||
</button>
|
||||
</div>
|
||||
<div class="mnote-main-tab-panels">
|
||||
<div data-mnote-page-tab-panel="true" data-pane-role="primary">
|
||||
<section
|
||||
class="document-pane mnote-workspace-empty-pane"
|
||||
data-document-pane="true"
|
||||
data-pane-role="primary"
|
||||
data-pane-workspace-id={workspace_id.clone().unwrap_or_default()}
|
||||
data-pane-visible="true"
|
||||
aria-label="主文档"
|
||||
></section>
|
||||
</div>
|
||||
<section
|
||||
class="mnote-resource-tab-host"
|
||||
data-mnote-resource-tab-host="true"
|
||||
data-pane-role="primary"
|
||||
data-testid="mnote-resource-tab-host"
|
||||
hidden=true
|
||||
>
|
||||
<div class="mnote-resource-tab-panel-root" data-mnote-resource-tab-panel-root="true" data-pane-role="primary"></div>
|
||||
</section>
|
||||
</div>
|
||||
</section>
|
||||
</main>
|
||||
}.into_any()
|
||||
}}
|
||||
</PageLayout>
|
||||
}
|
||||
|
||||
@@ -3138,8 +3138,7 @@ const SIDEBAR_TREE_JS: &str = r##"
|
||||
}
|
||||
|
||||
function shouldOpenLocalResourceInNewWindow(fileName) {
|
||||
var ext = attachmentExtensionFromFileName(fileName);
|
||||
return ext === 'pdf';
|
||||
return false;
|
||||
}
|
||||
|
||||
function isLocalUploadedAsset(asset) {
|
||||
@@ -10923,9 +10922,9 @@ mod tests {
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn sidebar_runtime_opens_local_pdf_assets_in_browser_tab_by_default() {
|
||||
fn sidebar_runtime_opens_local_pdf_assets_in_active_resource_tab_by_default() {
|
||||
assert!(SIDEBAR_TREE_JS.contains("function shouldOpenLocalResourceInNewWindow(fileName)"));
|
||||
assert!(SIDEBAR_TREE_JS.contains("return ext === 'pdf';"));
|
||||
assert!(SIDEBAR_TREE_JS.contains("return false;"));
|
||||
assert!(SIDEBAR_TREE_JS.contains("var shouldOpenInNewWindow = forceNewWindow || shouldOpenLocalResourceInNewWindow(localFileName);"));
|
||||
assert!(SIDEBAR_TREE_JS
|
||||
.contains("if (!shouldOpenInNewWindow && await openLocalResourceInActiveTab({"));
|
||||
|
||||
@@ -26,6 +26,6 @@ wasm-bindgen-futures = "0.4"
|
||||
web-sys = { version = "0.3.77", features = ["CustomEvent", "CustomEventInit", "DataTransfer", "Document", "DomRect", "DragEvent", "Element", "EventTarget", "HtmlElement", "MessageEvent", "MouseEvent", "Node", "Range", "RequestInit", "RequestMode", "Response", "Selection", "Storage", "Window"] }
|
||||
|
||||
[dependencies.leptos-tiptap]
|
||||
path = "../../../design/05-editor-mainline/reference-code/leptos-tiptap"
|
||||
path = "../../../reference-code/leptos-tiptap"
|
||||
default-features = false
|
||||
features = ["component", "full"]
|
||||
|
||||
+1
-1
@@ -68,7 +68,7 @@ export interface InitOutput {
|
||||
readonly wasm_bindgen__convert__closures_____invoke__h3a3182d847094e12: (a: number, b: number, c: any) => [number, number];
|
||||
readonly wasm_bindgen__convert__closures_____invoke__h0accad0f5d87788a: (a: number, b: number, c: any, d: any) => void;
|
||||
readonly wasm_bindgen__convert__closures_____invoke__h396914cf76a9e7a5: (a: number, b: number, c: any) => void;
|
||||
readonly wasm_bindgen__convert__closures_____invoke__hde8e7b4803478c08: (a: number, b: number, c: any) => void;
|
||||
readonly wasm_bindgen__convert__closures_____invoke__had771ddc65647798: (a: number, b: number, c: any) => void;
|
||||
readonly wasm_bindgen__convert__closures_____invoke__h0abd2b2fe4652e2a: (a: number, b: number, c: any) => void;
|
||||
readonly wasm_bindgen__convert__closures_____invoke__h396914cf76a9e7a5_4: (a: number, b: number, c: any) => void;
|
||||
readonly wasm_bindgen__convert__closures_____invoke__h794babeffd4f821f: (a: number, b: number) => void;
|
||||
|
||||
+19
-11
@@ -608,6 +608,14 @@ function __wbg_get_imports() {
|
||||
getDataViewMemory0().setInt32(arg0 + 4 * 1, len1, true);
|
||||
getDataViewMemory0().setInt32(arg0 + 4 * 0, ptr1, true);
|
||||
},
|
||||
__wbg_innerHeight_72e7bb88c4b9ede8: function() { return handleError(function (arg0) {
|
||||
const ret = arg0.innerHeight;
|
||||
return ret;
|
||||
}, arguments); },
|
||||
__wbg_innerWidth_c7446907ab672e41: function() { return handleError(function (arg0) {
|
||||
const ret = arg0.innerWidth;
|
||||
return ret;
|
||||
}, arguments); },
|
||||
__wbg_insertBefore_38c7d835a2dcac23: function() { return handleError(function (arg0, arg1, arg2) {
|
||||
const ret = arg0.insertBefore(arg1, arg2);
|
||||
return ret;
|
||||
@@ -1223,42 +1231,42 @@ function __wbg_get_imports() {
|
||||
}
|
||||
}, arguments); },
|
||||
__wbindgen_cast_0000000000000001: function(arg0, arg1) {
|
||||
// Cast intrinsic for `Closure(Closure { owned: true, function: Function { arguments: [Externref], shim_idx: 1129, ret: Unit, inner_ret: Some(Unit) }, mutable: true }) -> Externref`.
|
||||
// Cast intrinsic for `Closure(Closure { owned: true, function: Function { arguments: [Externref], shim_idx: 1128, ret: Unit, inner_ret: Some(Unit) }, mutable: true }) -> Externref`.
|
||||
const ret = makeMutClosure(arg0, arg1, wasm_bindgen__convert__closures_____invoke__h396914cf76a9e7a5);
|
||||
return ret;
|
||||
},
|
||||
__wbindgen_cast_0000000000000002: function(arg0, arg1) {
|
||||
// Cast intrinsic for `Closure(Closure { owned: true, function: Function { arguments: [Externref], shim_idx: 1181, ret: Result(Unit), inner_ret: Some(Result(Unit)) }, mutable: true }) -> Externref`.
|
||||
// Cast intrinsic for `Closure(Closure { owned: true, function: Function { arguments: [Externref], shim_idx: 1180, ret: Result(Unit), inner_ret: Some(Result(Unit)) }, mutable: true }) -> Externref`.
|
||||
const ret = makeMutClosure(arg0, arg1, wasm_bindgen__convert__closures_____invoke__h3a3182d847094e12);
|
||||
return ret;
|
||||
},
|
||||
__wbindgen_cast_0000000000000003: function(arg0, arg1) {
|
||||
// Cast intrinsic for `Closure(Closure { owned: true, function: Function { arguments: [Externref], shim_idx: 957, ret: Unit, inner_ret: Some(Unit) }, mutable: false }) -> Externref`.
|
||||
const ret = makeClosure(arg0, arg1, wasm_bindgen__convert__closures_____invoke__hde8e7b4803478c08);
|
||||
// Cast intrinsic for `Closure(Closure { owned: true, function: Function { arguments: [Externref], shim_idx: 956, ret: Unit, inner_ret: Some(Unit) }, mutable: false }) -> Externref`.
|
||||
const ret = makeClosure(arg0, arg1, wasm_bindgen__convert__closures_____invoke__had771ddc65647798);
|
||||
return ret;
|
||||
},
|
||||
__wbindgen_cast_0000000000000004: function(arg0, arg1) {
|
||||
// Cast intrinsic for `Closure(Closure { owned: true, function: Function { arguments: [NamedExternref("Event")], shim_idx: 1079, ret: Unit, inner_ret: Some(Unit) }, mutable: true }) -> Externref`.
|
||||
// Cast intrinsic for `Closure(Closure { owned: true, function: Function { arguments: [NamedExternref("Event")], shim_idx: 1078, ret: Unit, inner_ret: Some(Unit) }, mutable: true }) -> Externref`.
|
||||
const ret = makeMutClosure(arg0, arg1, wasm_bindgen__convert__closures_____invoke__h0abd2b2fe4652e2a);
|
||||
return ret;
|
||||
},
|
||||
__wbindgen_cast_0000000000000005: function(arg0, arg1) {
|
||||
// Cast intrinsic for `Closure(Closure { owned: true, function: Function { arguments: [NamedExternref("Event")], shim_idx: 1129, ret: Unit, inner_ret: Some(Unit) }, mutable: true }) -> Externref`.
|
||||
// Cast intrinsic for `Closure(Closure { owned: true, function: Function { arguments: [NamedExternref("Event")], shim_idx: 1128, ret: Unit, inner_ret: Some(Unit) }, mutable: true }) -> Externref`.
|
||||
const ret = makeMutClosure(arg0, arg1, wasm_bindgen__convert__closures_____invoke__h396914cf76a9e7a5_4);
|
||||
return ret;
|
||||
},
|
||||
__wbindgen_cast_0000000000000006: function(arg0, arg1) {
|
||||
// Cast intrinsic for `Closure(Closure { owned: true, function: Function { arguments: [], shim_idx: 1081, ret: Unit, inner_ret: Some(Unit) }, mutable: true }) -> Externref`.
|
||||
// Cast intrinsic for `Closure(Closure { owned: true, function: Function { arguments: [], shim_idx: 1080, ret: Unit, inner_ret: Some(Unit) }, mutable: true }) -> Externref`.
|
||||
const ret = makeMutClosure(arg0, arg1, wasm_bindgen__convert__closures_____invoke__h794babeffd4f821f);
|
||||
return ret;
|
||||
},
|
||||
__wbindgen_cast_0000000000000007: function(arg0, arg1) {
|
||||
// Cast intrinsic for `Closure(Closure { owned: true, function: Function { arguments: [], shim_idx: 1096, ret: Unit, inner_ret: Some(Unit) }, mutable: false }) -> Externref`.
|
||||
// Cast intrinsic for `Closure(Closure { owned: true, function: Function { arguments: [], shim_idx: 1095, ret: Unit, inner_ret: Some(Unit) }, mutable: false }) -> Externref`.
|
||||
const ret = makeClosure(arg0, arg1, wasm_bindgen__convert__closures_____invoke__h2b648e7ac8ec1fb5);
|
||||
return ret;
|
||||
},
|
||||
__wbindgen_cast_0000000000000008: function(arg0, arg1) {
|
||||
// Cast intrinsic for `Closure(Closure { owned: true, function: Function { arguments: [], shim_idx: 1132, ret: Unit, inner_ret: Some(Unit) }, mutable: true }) -> Externref`.
|
||||
// Cast intrinsic for `Closure(Closure { owned: true, function: Function { arguments: [], shim_idx: 1131, ret: Unit, inner_ret: Some(Unit) }, mutable: true }) -> Externref`.
|
||||
const ret = makeMutClosure(arg0, arg1, wasm_bindgen__convert__closures_____invoke__h0b7fb40a8610550c);
|
||||
return ret;
|
||||
},
|
||||
@@ -1318,8 +1326,8 @@ function wasm_bindgen__convert__closures_____invoke__h396914cf76a9e7a5(arg0, arg
|
||||
wasm.wasm_bindgen__convert__closures_____invoke__h396914cf76a9e7a5(arg0, arg1, arg2);
|
||||
}
|
||||
|
||||
function wasm_bindgen__convert__closures_____invoke__hde8e7b4803478c08(arg0, arg1, arg2) {
|
||||
wasm.wasm_bindgen__convert__closures_____invoke__hde8e7b4803478c08(arg0, arg1, arg2);
|
||||
function wasm_bindgen__convert__closures_____invoke__had771ddc65647798(arg0, arg1, arg2) {
|
||||
wasm.wasm_bindgen__convert__closures_____invoke__had771ddc65647798(arg0, arg1, arg2);
|
||||
}
|
||||
|
||||
function wasm_bindgen__convert__closures_____invoke__h0abd2b2fe4652e2a(arg0, arg1, arg2) {
|
||||
|
||||
BIN
Binary file not shown.
Vendored
+1
-1
@@ -21,7 +21,7 @@ export const intounderlyingsource_cancel: (a: number) => void;
|
||||
export const wasm_bindgen__convert__closures_____invoke__h3a3182d847094e12: (a: number, b: number, c: any) => [number, number];
|
||||
export const wasm_bindgen__convert__closures_____invoke__h0accad0f5d87788a: (a: number, b: number, c: any, d: any) => void;
|
||||
export const wasm_bindgen__convert__closures_____invoke__h396914cf76a9e7a5: (a: number, b: number, c: any) => void;
|
||||
export const wasm_bindgen__convert__closures_____invoke__hde8e7b4803478c08: (a: number, b: number, c: any) => void;
|
||||
export const wasm_bindgen__convert__closures_____invoke__had771ddc65647798: (a: number, b: number, c: any) => void;
|
||||
export const wasm_bindgen__convert__closures_____invoke__h0abd2b2fe4652e2a: (a: number, b: number, c: any) => void;
|
||||
export const wasm_bindgen__convert__closures_____invoke__h396914cf76a9e7a5_4: (a: number, b: number, c: any) => void;
|
||||
export const wasm_bindgen__convert__closures_____invoke__h794babeffd4f821f: (a: number, b: number) => void;
|
||||
|
||||
@@ -6363,11 +6363,23 @@ fn apply_html_update(
|
||||
editor: TiptapEditorHandle,
|
||||
persisted_identity: &PersistedDocumentIdentity,
|
||||
next_html: String,
|
||||
document_id: ReadSignal<Option<String>>,
|
||||
workspace_id: ReadSignal<Option<String>>,
|
||||
dirty_count: ReadSignal<u32>,
|
||||
set_dirty_count: WriteSignal<u32>,
|
||||
set_html_output: WriteSignal<String>,
|
||||
set_document_json: WriteSignal<Value>,
|
||||
set_json_output: WriteSignal<String>,
|
||||
title: ReadSignal<String>,
|
||||
hovered_block: ReadSignal<Option<HoveredBlockState>>,
|
||||
editor_focused: ReadSignal<bool>,
|
||||
slash_open: ReadSignal<bool>,
|
||||
turn_into_open: ReadSignal<bool>,
|
||||
color_menu_open: ReadSignal<bool>,
|
||||
more_menu_open: ReadSignal<bool>,
|
||||
revision: ReadSignal<Option<i64>>,
|
||||
conflict_detection_key: ReadSignal<Option<String>>,
|
||||
read_only: ReadSignal<bool>,
|
||||
set_command_feedback: WriteSignal<String>,
|
||||
success_message: impl Into<String>,
|
||||
) {
|
||||
@@ -6379,6 +6391,39 @@ fn apply_html_update(
|
||||
set_html_output.set(html.clone());
|
||||
set_document_json.set(snapshot.clone());
|
||||
set_json_output.set(json_text);
|
||||
dispatch_change_event(&ChangePayload {
|
||||
document_id: document_id.get_untracked(),
|
||||
workspace_id: workspace_id.get_untracked(),
|
||||
title: title.get_untracked(),
|
||||
content: snapshot.clone(),
|
||||
meta: ChangeMetaPayload {
|
||||
dirty_count: dirty_count.get_untracked(),
|
||||
editor_focused: editor_focused.get_untracked(),
|
||||
slash_open: slash_open.get_untracked(),
|
||||
toolbar_open: toolbar_overlay_locked(
|
||||
turn_into_open.get_untracked(),
|
||||
color_menu_open.get_untracked(),
|
||||
more_menu_open.get_untracked(),
|
||||
),
|
||||
selected_block_index: hovered_block.get_untracked().map(|block| block.index),
|
||||
revision: revision.get_untracked(),
|
||||
conflict_detection_key: conflict_detection_key.get_untracked(),
|
||||
read_only: read_only.get_untracked(),
|
||||
},
|
||||
});
|
||||
dispatch_runtime_state(
|
||||
document_id.get_untracked(),
|
||||
workspace_id.get_untracked(),
|
||||
title.get_untracked(),
|
||||
dirty_count.get_untracked(),
|
||||
hovered_block.get_untracked(),
|
||||
editor_focused.get_untracked(),
|
||||
slash_open.get_untracked(),
|
||||
turn_into_open.get_untracked(),
|
||||
color_menu_open.get_untracked(),
|
||||
more_menu_open.get_untracked(),
|
||||
read_only.get_untracked(),
|
||||
);
|
||||
match persist_document_state(
|
||||
persisted_identity,
|
||||
&title.get_untracked(),
|
||||
@@ -8487,11 +8532,23 @@ fn App(mount_options: MountOptions) -> impl IntoView {
|
||||
editor,
|
||||
&persisted_identity,
|
||||
next_html,
|
||||
document_id,
|
||||
workspace_id,
|
||||
dirty_count,
|
||||
set_dirty_count,
|
||||
set_html_output,
|
||||
set_document_json,
|
||||
set_json_output,
|
||||
title,
|
||||
hovered_block,
|
||||
editor_focused,
|
||||
slash_open,
|
||||
turn_into_open,
|
||||
color_menu_open,
|
||||
more_menu_open,
|
||||
revision,
|
||||
conflict_detection_key,
|
||||
read_only,
|
||||
set_command_feedback,
|
||||
"已通过块手柄拖拽重排",
|
||||
);
|
||||
@@ -9268,11 +9325,23 @@ fn App(mount_options: MountOptions) -> impl IntoView {
|
||||
editor,
|
||||
&runtime_persisted_identity(document_id, workspace_id),
|
||||
next_html,
|
||||
document_id,
|
||||
workspace_id,
|
||||
dirty_count,
|
||||
set_dirty_count,
|
||||
set_html_output,
|
||||
set_document_json,
|
||||
set_json_output,
|
||||
title,
|
||||
hovered_block,
|
||||
editor_focused,
|
||||
slash_open,
|
||||
turn_into_open,
|
||||
color_menu_open,
|
||||
more_menu_open,
|
||||
revision,
|
||||
conflict_detection_key,
|
||||
read_only,
|
||||
set_command_feedback,
|
||||
"已通过块手柄拖拽重排",
|
||||
);
|
||||
@@ -10423,11 +10492,23 @@ fn App(mount_options: MountOptions) -> impl IntoView {
|
||||
editor,
|
||||
&runtime_persisted_identity(document_id, workspace_id),
|
||||
next_html,
|
||||
document_id,
|
||||
workspace_id,
|
||||
dirty_count,
|
||||
set_dirty_count,
|
||||
set_html_output,
|
||||
set_document_json,
|
||||
set_json_output,
|
||||
title,
|
||||
hovered_block,
|
||||
editor_focused,
|
||||
slash_open,
|
||||
turn_into_open,
|
||||
color_menu_open,
|
||||
more_menu_open,
|
||||
revision,
|
||||
conflict_detection_key,
|
||||
read_only,
|
||||
set_command_feedback,
|
||||
format!("已复制 {}", duplicate_feedback_label.clone()),
|
||||
);
|
||||
@@ -10456,11 +10537,23 @@ fn App(mount_options: MountOptions) -> impl IntoView {
|
||||
editor,
|
||||
&runtime_persisted_identity(document_id, workspace_id),
|
||||
next_html,
|
||||
document_id,
|
||||
workspace_id,
|
||||
dirty_count,
|
||||
set_dirty_count,
|
||||
set_html_output,
|
||||
set_document_json,
|
||||
set_json_output,
|
||||
title,
|
||||
hovered_block,
|
||||
editor_focused,
|
||||
slash_open,
|
||||
turn_into_open,
|
||||
color_menu_open,
|
||||
more_menu_open,
|
||||
revision,
|
||||
conflict_detection_key,
|
||||
read_only,
|
||||
set_command_feedback,
|
||||
format!("已删除 {}", delete_feedback_label.clone()),
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user