feat: restore Wolai workspace navigation and assets
This commit is contained in:
@@ -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',
|
||||
|
||||
Reference in New Issue
Block a user