feat: restore Wolai workspace navigation and assets

This commit is contained in:
Agent Board
2026-07-15 23:12:15 +08:00
parent 31a160fcc5
commit 6f9c7d3b58
48 changed files with 6734 additions and 517 deletions
@@ -197,9 +197,67 @@ export const legacyBlockToTiptap = (block, index = 0) => {
}),
};
}
if (type === 'page_reference' || type === 'pageReference' || type === 'pagereference') {
const sourcePath = firstNonEmptyText(
block?.props?.sourcePath,
block?.props?.source_path,
block?.props?.href,
block?.props?.url,
blockAttrs?.sourcePath,
blockAttrs?.source_path,
blockAttrs?.href,
blockAttrs?.url,
blockAttrs?.pageId,
blockAttrs?.page_id
);
const title = firstNonEmptyText(
block?.props?.title,
block?.props?.name,
blockAttrs?.title,
blockAttrs?.name,
content,
sourcePath
) || '页面';
return {
type: 'paragraph',
attrs: withTextAlign({ blockId }),
content: [{
type: 'text',
text: title,
marks: [{
type: 'link',
attrs: {
href: sourcePath || '#',
target: '_self',
rel: 'noopener noreferrer nofollow',
class: 'mnote-page-block-link',
},
}],
}],
};
}
if (type === 'media') {
const sourcePath = firstNonEmptyText(block?.props?.sourcePath, block?.props?.url, block?.props?.src);
const name = firstNonEmptyText(block?.props?.name, block?.props?.fileName, block?.props?.title, sourcePath);
const sourcePath = firstNonEmptyText(
block?.props?.sourcePath,
block?.props?.source_path,
block?.props?.url,
block?.props?.src,
blockAttrs?.sourcePath,
blockAttrs?.source_path,
blockAttrs?.url,
blockAttrs?.src
);
const name = firstNonEmptyText(
block?.props?.name,
block?.props?.fileName,
block?.props?.file_name,
block?.props?.title,
blockAttrs?.name,
blockAttrs?.fileName,
blockAttrs?.file_name,
blockAttrs?.title,
sourcePath
);
const mediaContent = name
? [{
type: 'text',
@@ -336,7 +394,7 @@ export const localMarkdownDocumentIdFromRelativePath = (relativePath) => {
if (!normalized) return '';
return 'local-md:' + normalized
.split('/')
.map((part) => part.replace(/ /g, '~20'))
.map((part) => encodeURIComponent(part).replace(/%/g, '~'))
.join('~2F');
};
@@ -377,6 +435,48 @@ export const normalizeLocalAssetRelativePath = (value, context) => {
.join('/');
};
export const normalizeLocalPageRelativePath = (value, context) => {
const text = unwrapMarkdownLinkTarget(value);
if (!text || text.startsWith('#')) return '';
if (text.startsWith('/documents/')) return text;
if (text.startsWith('/api/') || text.startsWith('http://') || text.startsWith('https://') || text.startsWith('mailto:')) return '';
const baseDir = text.startsWith('/') ? '' : localMarkdownDirectoryFromDocumentId(context?.documentId);
const parts = (baseDir ? `${baseDir}/${text}` : text)
.replace(/\\/g, '/')
.split('/');
const normalized = [];
for (const part of parts) {
if (!part || part === '.') continue;
if (part === '..') {
if (!normalized.length) return '';
normalized.pop();
continue;
}
normalized.push(part);
}
return normalized.join('/');
};
export const localPageOpenUrlForTiptap = (value, context) => {
const text = unwrapMarkdownLinkTarget(value);
if (!context || context.sourceKind !== 'local_folder' || !context.rootUri) return text || value;
if (text.startsWith('/documents/')) {
const url = new URL(text, window.location.origin);
if (!url.searchParams.get('sourceKind')) url.searchParams.set('sourceKind', 'local_folder');
if (!url.searchParams.get('rootUri')) url.searchParams.set('rootUri', context.rootUri);
return `${url.pathname}${url.search}`;
}
const relativePath = normalizeLocalPageRelativePath(text, context);
if (!relativePath) return text || value;
const documentId = localMarkdownDocumentIdFromRelativePath(relativePath);
if (!documentId) return text || value;
const url = new URL(`/documents/${documentId}`, window.location.origin);
url.searchParams.set('sourceKind', 'local_folder');
url.searchParams.set('rootUri', context.rootUri);
url.searchParams.set('treeView', 'filetree');
return `${url.pathname}${url.search}`;
};
export const localFileOpenUrlForTiptap = (value, context) => {
if (!context || context.sourceKind !== 'local_folder' || !context.rootUri) return value;
const relativePath = normalizeLocalAssetRelativePath(value, context);
@@ -463,6 +563,8 @@ const attachmentRefForTiptapHref = (href, context) => {
const withAttachmentProjectionAttrs = (attrs, attachmentRef) => {
if (!attachmentRef || typeof attachmentRef !== 'object') return attrs;
const next = { ...attrs };
const fileSize = attachmentFileSizeText(attachmentRef);
if (fileSize) next['data-file-size'] = fileSize;
if (attachmentRef.exists === false) {
next.class = mergeClassNames(next.class, 'mnote-uploaded-attachment-missing');
next['data-mnote-attachment-missing'] = 'true';
@@ -476,6 +578,14 @@ const withAttachmentProjectionAttrs = (attrs, attachmentRef) => {
return next;
};
export const attachmentFileSizeText = (attachmentRef) => {
const size = Number(attachmentRef && (attachmentRef.fileSize || attachmentRef.file_size) || 0);
if (!Number.isFinite(size) || size <= 0) return '';
if (size >= 1024 * 1024) return `${(size / 1024 / 1024).toFixed(size >= 10 * 1024 * 1024 ? 1 : 2)} MB`;
if (size >= 1024) return `${(size / 1024).toFixed(size >= 100 * 1024 ? 0 : 2)} KB`;
return `${Math.round(size)} B`;
};
export const localizeTiptapAssetUrls = (node, context) => {
if (!node || typeof node !== 'object') return node;
if (node.type === 'image' && node.attrs && typeof node.attrs.src === 'string') {
@@ -490,12 +600,26 @@ export const localizeTiptapAssetUrls = (node, context) => {
node.marks = node.marks.map((mark) => {
if (!mark || mark.type !== 'link' || !mark.attrs || typeof mark.attrs.href !== 'string') return mark;
const href = String(mark.attrs.href || '').trim();
const markClass = String(mark.attrs.class || '');
if (markClass.split(/\s+/).includes('mnote-page-block-link')) {
return {
...mark,
attrs: {
...mark.attrs,
href: localPageOpenUrlForTiptap(href, context),
target: mark.attrs.target || '_self',
rel: mark.attrs.rel || 'noopener noreferrer nofollow',
class: mergeClassNames(mark.attrs.class, 'mnote-page-block-link'),
},
};
}
const attachmentClass = localAttachmentClassForTiptapHref(href);
const attachmentRef = attachmentRefForTiptapHref(href, context);
const localizedHref = attachmentClass ? localFileOpenUrlForTiptap(href, context) : href;
const attrs = attachmentClass
? withAttachmentProjectionAttrs({
...mark.attrs,
href,
href: localizedHref,
class: mergeClassNames(mark.attrs.class, attachmentClass),
target: mark.attrs.target || '_blank',
rel: mark.attrs.rel || 'noopener noreferrer nofollow',