Complete workbench P0 resource tab cutover
This commit is contained in:
@@ -3184,6 +3184,19 @@ pub(crate) fn render_editor_island_adapter_script() -> &'static str {
|
||||
return 'file';
|
||||
};
|
||||
|
||||
const resolveResourceOpen = (input = {}) => {
|
||||
const kind = normalizeResourceTabKind(input);
|
||||
const badgeKind = resourceTabBadgeKind(input, kind);
|
||||
const rawTarget = String(input?.openTarget || '').trim().toLowerCase();
|
||||
const openTarget = rawTarget === 'new-window' || rawTarget === 'side' ? rawTarget : 'active-tab';
|
||||
const editable = kind === 'markdown' || kind === 'text' || kind === 'code';
|
||||
const defaultOpenMode = kind === 'office' && openTarget === 'active-tab' ? 'active-tab-iframe' : openTarget;
|
||||
const viewerUrl = kind === 'office'
|
||||
? String(input?.officeUrl || input?.href || '').trim()
|
||||
: String(input?.href || '').trim();
|
||||
return { editorKind: kind, badgeKind, defaultOpenMode, editable, viewerUrl, openTarget };
|
||||
};
|
||||
|
||||
const touchResourceTabMru = (key) => {
|
||||
const id = String(key || '').trim();
|
||||
if (!id) return;
|
||||
@@ -3193,6 +3206,11 @@ pub(crate) fn render_editor_island_adapter_script() -> &'static str {
|
||||
if (resourceTabMru.length > resourceTabMruMax) resourceTabMru.length = resourceTabMruMax;
|
||||
};
|
||||
|
||||
const setResourceTabLastActive = (key) => {
|
||||
const entry = resourceTabRegistry.get(String(key || '').trim());
|
||||
if (entry?.session) entry.session.lastActiveAt = Date.now();
|
||||
};
|
||||
|
||||
const removeFromResourceTabMru = (key) => {
|
||||
const index = resourceTabMru.indexOf(key);
|
||||
if (index >= 0) resourceTabMru.splice(index, 1);
|
||||
@@ -3239,7 +3257,10 @@ pub(crate) fn render_editor_island_adapter_script() -> &'static str {
|
||||
const activateMainEditorTab = (objectIdentity) => {
|
||||
const nodes = resourceTabHostNodes();
|
||||
const activeResource = String(objectIdentity || '').trim();
|
||||
if (activeResource) touchResourceTabMru(activeResource);
|
||||
if (activeResource) {
|
||||
touchResourceTabMru(activeResource);
|
||||
setResourceTabLastActive(activeResource);
|
||||
}
|
||||
if (nodes.pageTab instanceof HTMLElement) {
|
||||
const activePage = !activeResource;
|
||||
nodes.pageTab.classList.toggle('is-active', activePage);
|
||||
@@ -3289,6 +3310,20 @@ pub(crate) fn render_editor_island_adapter_script() -> &'static str {
|
||||
activateMainEditorTab(lastActiveResourceTabKey());
|
||||
};
|
||||
|
||||
const markResourceTabError = (entry) => {
|
||||
if (!entry) return;
|
||||
entry.kind = 'error';
|
||||
if (entry.tab instanceof HTMLElement) {
|
||||
entry.tab.setAttribute('data-mnote-tab-kind', 'error');
|
||||
entry.tab.setAttribute('data-mnote-tab-badge-kind', 'file');
|
||||
entry.tab.classList.add('is-error');
|
||||
}
|
||||
if (entry.panel instanceof HTMLElement) {
|
||||
entry.panel.setAttribute('data-resource-kind', 'error');
|
||||
entry.panel.innerHTML = '<div class="mnote-resource-tab-error" data-resource-tab-error="true"><div class="mnote-resource-tab-error-inner"><h1>资源打开失败</h1><p>无法加载此资源,请检查文件路径和访问权限。</p></div></div>';
|
||||
}
|
||||
};
|
||||
|
||||
const createResourceTabDom = (input) => {
|
||||
const nodes = resourceTabHostNodes();
|
||||
if (!(nodes.strip instanceof HTMLElement) || !(nodes.panelRoot instanceof HTMLElement)) return null;
|
||||
@@ -3356,6 +3391,7 @@ pub(crate) fn render_editor_island_adapter_script() -> &'static str {
|
||||
fileVersion: conflictDetectionKey,
|
||||
lastExternalConflictDetectionKey: conflictDetectionKey,
|
||||
readOnly: false,
|
||||
lastActiveAt: 0,
|
||||
dirty: false,
|
||||
saving: false,
|
||||
hasExternalConflict: false,
|
||||
@@ -3464,7 +3500,7 @@ pub(crate) fn render_editor_island_adapter_script() -> &'static str {
|
||||
return true;
|
||||
} catch (error) {
|
||||
console.warn('mnote resource tab 打开失败', error);
|
||||
entry.panel.innerHTML = '<div class="mnote-resource-tab-text-shell" data-resource-tab-error="true">资源打开失败</div>';
|
||||
markResourceTabError(entry);
|
||||
return false;
|
||||
}
|
||||
};
|
||||
@@ -3530,6 +3566,7 @@ pub(crate) fn render_editor_island_adapter_script() -> &'static str {
|
||||
if (url instanceof URL) replaceUrlState(url);
|
||||
return true;
|
||||
},
|
||||
resolveResourceOpen: (input) => resolveResourceOpen(input),
|
||||
openResourceInActiveTab: openResourceInActiveTab,
|
||||
closeSecondaryDocument: ({ url } = {}) => {
|
||||
closeSecondaryPane(url instanceof URL ? url : currentUrl());
|
||||
@@ -4246,6 +4283,15 @@ mod tests {
|
||||
"const tiptapDocument = pageBodyTiptapDocument(pageBody, '', runtimeDescriptor.bootstrap);"
|
||||
));
|
||||
assert!(!html.contains("mnote-web-document-shell"));
|
||||
|
||||
// Resource open resolver contract
|
||||
assert!(html.contains("normalizeResourceTabKind"));
|
||||
assert!(html.contains("data-resource-tab-error"));
|
||||
assert!(html.contains("resourceTabCloseGuardReason"));
|
||||
assert!(html.contains("closeResourceTab"));
|
||||
assert!(html.contains("resourceTabRegistry.delete(key)"));
|
||||
assert!(html.contains("activateMainEditorTab(lastActiveResourceTabKey())"));
|
||||
assert!(html.contains("resourceTabBadgeKind(input, kind)"));
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
|
||||
@@ -2043,7 +2043,13 @@ const SIDEBAR_TREE_JS: &str = r##"
|
||||
|
||||
function buildOnlyOfficeOpenUrl(input) {
|
||||
var target = new URL('/onlyoffice', window.location.origin);
|
||||
target.searchParams.set('fileUrl', input.fileUrl || '');
|
||||
var fileUrl = String(input.fileUrl || '').trim();
|
||||
if (fileUrl) {
|
||||
try {
|
||||
fileUrl = new URL(fileUrl, window.location.origin).toString();
|
||||
} catch (_error) {}
|
||||
}
|
||||
target.searchParams.set('fileUrl', fileUrl);
|
||||
target.searchParams.set('fileName', input.fileName || '未命名资源');
|
||||
target.searchParams.set('fileType', input.fileType || 'docx');
|
||||
if (input.assetId) target.searchParams.set('assetId', input.assetId);
|
||||
|
||||
@@ -2522,6 +2522,46 @@ body {
|
||||
padding: 34px 48px;
|
||||
}
|
||||
|
||||
.mnote-resource-tab-error {
|
||||
display: flex;
|
||||
place-items: center;
|
||||
justify-content: center;
|
||||
width: 100%;
|
||||
min-height: calc(100vh - 80px);
|
||||
background: #FFF;
|
||||
color: #73726e;
|
||||
font-size: 14px;
|
||||
line-height: 1.6;
|
||||
padding: 48px;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.mnote-resource-tab-error[data-resource-tab-error="true"] {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.mnote-resource-tab-error-inner {
|
||||
max-width: 420px;
|
||||
text-align: center;
|
||||
border: 1px solid var(--color-basic-200, #E3E3E0);
|
||||
border-radius: 6px;
|
||||
padding: 24px 28px;
|
||||
background: var(--color-basic-50, #F7F7F5);
|
||||
}
|
||||
|
||||
.mnote-resource-tab-error-inner h1 {
|
||||
margin: 0 0 8px;
|
||||
font-size: 15px;
|
||||
font-weight: 600;
|
||||
color: var(--color-basic-900, #37352F);
|
||||
}
|
||||
|
||||
.mnote-resource-tab-error-inner p {
|
||||
margin: 0;
|
||||
font-size: 13px;
|
||||
color: var(--color-basic-600, #9B9A97);
|
||||
}
|
||||
|
||||
.mnote-resource-tab-editor-root {
|
||||
min-height: calc(100vh - 112px);
|
||||
}
|
||||
|
||||
@@ -151,6 +151,18 @@ pub enum FileTreeOpenTarget {
|
||||
Asset {
|
||||
document_id: String,
|
||||
asset_id: String,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
resource_kind: Option<String>,
|
||||
},
|
||||
LocalFile {
|
||||
root_uri: String,
|
||||
path: String,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
resource_kind: Option<String>,
|
||||
},
|
||||
Directory {
|
||||
root_uri: String,
|
||||
path: String,
|
||||
},
|
||||
}
|
||||
|
||||
@@ -455,10 +467,27 @@ fn resolve_open_target(
|
||||
document_id: row.document_id.clone()?,
|
||||
asset_id: row.asset_id.clone()?,
|
||||
}),
|
||||
"local-file" | "local_file" => Some(FileTreeOpenTarget::LocalFile {
|
||||
root_uri: String::new(),
|
||||
path: row.asset_id.clone().unwrap_or_default(),
|
||||
resource_kind: None,
|
||||
}),
|
||||
"directory" => Some(FileTreeOpenTarget::Directory {
|
||||
root_uri: String::new(),
|
||||
path: row.asset_id.clone().unwrap_or_default(),
|
||||
}),
|
||||
"asset" => Some(FileTreeOpenTarget::Asset {
|
||||
document_id: row.document_id.clone()?,
|
||||
asset_id: row.asset_id.clone()?,
|
||||
resource_kind: None,
|
||||
}),
|
||||
"office" | "image" | "pdf" | "code" | "text" | "mindmap" | "table" => {
|
||||
Some(FileTreeOpenTarget::Asset {
|
||||
document_id: row.document_id.clone()?,
|
||||
asset_id: row.asset_id.clone()?,
|
||||
resource_kind: Some(row.row_kind.clone()),
|
||||
})
|
||||
}
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
@@ -647,6 +676,7 @@ mod tests {
|
||||
target: FileTreeOpenTarget::Asset {
|
||||
document_id: "root".into(),
|
||||
asset_id: "image".into(),
|
||||
resource_kind: None,
|
||||
},
|
||||
},
|
||||
)));
|
||||
@@ -786,4 +816,53 @@ mod tests {
|
||||
assert_eq!(escaped.state.drag_effect, None);
|
||||
assert_eq!(escaped.state.drop_target_row_id, None);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn filetree_open_target_serializes_resource_identity_variants() {
|
||||
let asset = serde_json::to_value(FileTreeOpenTarget::Asset {
|
||||
document_id: "doc:page".into(),
|
||||
asset_id: "local-file:Page/report.docx".into(),
|
||||
resource_kind: Some("office".into()),
|
||||
})
|
||||
.expect("serialize asset target");
|
||||
assert_eq!(
|
||||
asset,
|
||||
serde_json::json!({
|
||||
"kind": "asset",
|
||||
"documentId": "doc:page",
|
||||
"assetId": "local-file:Page/report.docx",
|
||||
"resourceKind": "office"
|
||||
})
|
||||
);
|
||||
|
||||
let local_file = serde_json::to_value(FileTreeOpenTarget::LocalFile {
|
||||
root_uri: "file:///tmp/mnote".into(),
|
||||
path: "Page/资源.ext".into(),
|
||||
resource_kind: Some("local_file".into()),
|
||||
})
|
||||
.expect("serialize local file target");
|
||||
assert_eq!(
|
||||
local_file,
|
||||
serde_json::json!({
|
||||
"kind": "localFile",
|
||||
"rootUri": "file:///tmp/mnote",
|
||||
"path": "Page/资源.ext",
|
||||
"resourceKind": "local_file"
|
||||
})
|
||||
);
|
||||
|
||||
let directory = serde_json::to_value(FileTreeOpenTarget::Directory {
|
||||
root_uri: "file:///tmp/mnote".into(),
|
||||
path: "Page/assets".into(),
|
||||
})
|
||||
.expect("serialize directory target");
|
||||
assert_eq!(
|
||||
directory,
|
||||
serde_json::json!({
|
||||
"kind": "directory",
|
||||
"rootUri": "file:///tmp/mnote",
|
||||
"path": "Page/assets"
|
||||
})
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user