Checkpoint current workspace
This commit is contained in:
@@ -311,26 +311,93 @@ pub(crate) fn render_editor_island_adapter_script() -> &'static str {
|
||||
const type = typeof block?.type === 'string' ? block.type : 'paragraph';
|
||||
const text = flattenText(block?.content);
|
||||
const content = text ? [{ type: 'text', text }] : [];
|
||||
const textAlign = typeof block?.props?.textAlign === 'string'
|
||||
? block.props.textAlign
|
||||
: typeof block?.props?.text_align === 'string'
|
||||
? block.props.text_align
|
||||
: undefined;
|
||||
const withTextAlign = (attrs = {}) => textAlign ? { ...attrs, textAlign } : attrs;
|
||||
const nestedChildren = Array.isArray(block?.children)
|
||||
? block.children.map(legacyBlockToTiptap).filter(Boolean)
|
||||
: [];
|
||||
const withListChildren = (itemType, listType, attrs = {}) => ({
|
||||
type: listType,
|
||||
content: [{
|
||||
type: itemType,
|
||||
attrs,
|
||||
content: [
|
||||
{ type: 'paragraph', content },
|
||||
...nestedChildren,
|
||||
],
|
||||
}],
|
||||
});
|
||||
if (type === 'heading') {
|
||||
const level = Number(block?.props?.level || block?.level || 1) || 1;
|
||||
return { type: 'heading', attrs: { level: Math.max(1, Math.min(6, level)) }, content };
|
||||
const collapsed = typeof block?.props?.collapsed === 'boolean' ? { collapsed: block.props.collapsed } : {};
|
||||
return { type: 'heading', attrs: withTextAlign({ level: Math.max(1, Math.min(6, level)), ...collapsed }), content };
|
||||
}
|
||||
if (type === 'bulletListItem' || type === 'bullet_list_item') {
|
||||
return { type: 'bulletList', content: [{ type: 'listItem', content: [{ type: 'paragraph', content }] }] };
|
||||
return withListChildren('listItem', 'bulletList');
|
||||
}
|
||||
if (type === 'numberedListItem' || type === 'numbered_list_item') {
|
||||
return { type: 'orderedList', content: [{ type: 'listItem', content: [{ type: 'paragraph', content }] }] };
|
||||
return withListChildren('listItem', 'orderedList');
|
||||
}
|
||||
if (type === 'checkListItem' || type === 'advancedTodo' || type === 'todo') {
|
||||
return { type: 'taskList', content: [{ type: 'taskItem', attrs: { checked: Boolean(block?.props?.checked) }, content: [{ type: 'paragraph', content }] }] };
|
||||
return withListChildren('taskItem', 'taskList', { checked: Boolean(block?.props?.checked) });
|
||||
}
|
||||
if (type === 'quote' || type === 'blockquote') {
|
||||
return { type: 'blockquote', content: [{ type: 'paragraph', content }] };
|
||||
return { type: 'blockquote', attrs: withTextAlign(), content: [{ type: 'paragraph', attrs: withTextAlign(), content }] };
|
||||
}
|
||||
if (type === 'codeBlock') {
|
||||
return { type: 'codeBlock', attrs: { language: block?.props?.language || null }, content };
|
||||
if (type === 'codeBlock' || type === 'code_block') {
|
||||
return { type: 'codeBlock', attrs: withTextAlign({ language: block?.props?.language || null }), content };
|
||||
}
|
||||
return { type: 'paragraph', content };
|
||||
if (type === 'divider' || type === 'horizontalRule' || type === 'horizontal_rule') {
|
||||
return { type: 'horizontalRule' };
|
||||
}
|
||||
if (type === 'table') {
|
||||
const tableSnapshot = block?.props?.tiptapTable;
|
||||
if (tableSnapshot && typeof tableSnapshot === 'object' && tableSnapshot.type === 'table') {
|
||||
return tableSnapshot;
|
||||
}
|
||||
return {
|
||||
type: 'table',
|
||||
content: [{
|
||||
type: 'tableRow',
|
||||
content: [{
|
||||
type: 'tableCell',
|
||||
attrs: { colspan: 1, rowspan: 1, colwidth: null },
|
||||
content: [{ type: 'paragraph', content }],
|
||||
}],
|
||||
}],
|
||||
};
|
||||
}
|
||||
if (type === 'toc' || type === 'tocNode' || type === 'toc_node') {
|
||||
const tocSnapshot = block?.props?.tiptapTocNode || block?.props?.tiptapToc;
|
||||
if (tocSnapshot && typeof tocSnapshot === 'object' && tocSnapshot.type === 'tocNode') {
|
||||
return tocSnapshot;
|
||||
}
|
||||
return {
|
||||
type: 'tocNode',
|
||||
attrs: {
|
||||
topOffset: Number(block?.props?.topOffset || block?.props?.top_offset || 0) || 0,
|
||||
maxShowCount: Number(block?.props?.maxShowCount || block?.props?.max_show_count || 20) || 20,
|
||||
showTitle: block?.props?.showTitle !== false,
|
||||
},
|
||||
};
|
||||
}
|
||||
if (type === 'image') {
|
||||
const imageSnapshot = block?.props?.tiptapImage;
|
||||
if (imageSnapshot && typeof imageSnapshot === 'object' && imageSnapshot.type === 'image') {
|
||||
return imageSnapshot;
|
||||
}
|
||||
const attrs = {
|
||||
src: String(block?.props?.src || block?.src || ''),
|
||||
alt: block?.props?.alt || block?.alt || null,
|
||||
title: block?.props?.title || block?.title || null,
|
||||
};
|
||||
return attrs.src ? { type: 'image', attrs } : null;
|
||||
}
|
||||
return { type: 'paragraph', attrs: withTextAlign(), content };
|
||||
};
|
||||
|
||||
const textToTiptapDocument = (text) => ({
|
||||
@@ -371,9 +438,20 @@ pub(crate) fn render_editor_island_adapter_script() -> &'static str {
|
||||
|
||||
const pageBody = aggregate.body || {};
|
||||
const permissions = aggregate.head?.permissions || {};
|
||||
const conflictDetectionKey = typeof pageBody.conflictDetectionKey === 'string'
|
||||
? pageBody.conflictDetectionKey
|
||||
: typeof pageBody.conflict_detection_key === 'string'
|
||||
? pageBody.conflict_detection_key
|
||||
: null;
|
||||
const revisionFromConflictKey = (value) => {
|
||||
const match = String(value || '').match(/:(\d+)$/);
|
||||
return match ? Number(match[1]) : null;
|
||||
};
|
||||
const pageBodyRevision = Number.isInteger(pageBody.revision) ? pageBody.revision : null;
|
||||
const keyRevision = revisionFromConflictKey(conflictDetectionKey);
|
||||
const editorMeta = {
|
||||
revision: Number.isInteger(pageBody.revision) ? pageBody.revision : null,
|
||||
conflictDetectionKey: typeof pageBody.conflictDetectionKey === 'string' ? pageBody.conflictDetectionKey : null,
|
||||
revision: pageBodyRevision && pageBodyRevision > 0 ? pageBodyRevision : keyRevision,
|
||||
conflictDetectionKey,
|
||||
};
|
||||
const mountOptions = {
|
||||
documentId: bootstrap.documentId,
|
||||
@@ -389,8 +467,26 @@ pub(crate) fn render_editor_island_adapter_script() -> &'static str {
|
||||
|
||||
let saveTimer = 0;
|
||||
let lastSavedSerialized = '';
|
||||
const normalizeBridgeValue = (value) => {
|
||||
if (value instanceof Map) {
|
||||
const out = {};
|
||||
for (const [key, item] of value.entries()) {
|
||||
out[key] = normalizeBridgeValue(item);
|
||||
}
|
||||
return out;
|
||||
}
|
||||
if (Array.isArray(value)) {
|
||||
return value.map(normalizeBridgeValue);
|
||||
}
|
||||
if (value && typeof value === 'object') {
|
||||
return Object.fromEntries(
|
||||
Object.entries(value).map(([key, item]) => [key, normalizeBridgeValue(item)]),
|
||||
);
|
||||
}
|
||||
return value;
|
||||
};
|
||||
const normalizeEnvelopePayload = (event) => {
|
||||
const detail = event?.detail;
|
||||
const detail = normalizeBridgeValue(event?.detail);
|
||||
if (!detail || typeof detail !== 'object') return null;
|
||||
const payload = detail.payload && typeof detail.payload === 'object' ? detail.payload : detail;
|
||||
return payload && typeof payload === 'object' ? payload : null;
|
||||
@@ -512,6 +608,24 @@ fn runtime_asset_content_type(asset_path: &str) -> &'static str {
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn editor_image_placeholder_asset() -> Response {
|
||||
const SVG: &str = r##"<svg xmlns="http://www.w3.org/2000/svg" width="640" height="360" viewBox="0 0 640 360" role="img" aria-label="E24 image placeholder">
|
||||
<rect width="640" height="360" rx="18" fill="#f3f4f6"/>
|
||||
<rect x="42" y="42" width="556" height="276" rx="14" fill="#ffffff" stroke="#d1d5db" stroke-width="2"/>
|
||||
<circle cx="168" cy="132" r="34" fill="#93c5fd"/>
|
||||
<path d="M98 278 246 174 340 242 410 192 542 278Z" fill="#86efac"/>
|
||||
<path d="M98 278 246 174 340 242 410 192 542 278" fill="none" stroke="#16a34a" stroke-width="8" stroke-linejoin="round"/>
|
||||
<text x="320" y="322" text-anchor="middle" font-family="Arial, sans-serif" font-size="22" fill="#374151">E24 Image</text>
|
||||
</svg>"##;
|
||||
Response::builder()
|
||||
.status(StatusCode::OK)
|
||||
.header(header::CONTENT_TYPE, "image/svg+xml; charset=utf-8")
|
||||
.header(header::CACHE_CONTROL, "no-store")
|
||||
.header(HEADER_MNOTE_WEB_OWNER, "mnote-web")
|
||||
.body(Body::from(SVG))
|
||||
.unwrap_or_else(|_| Response::new(Body::empty()))
|
||||
}
|
||||
|
||||
pub async fn leptos_tiptap_manifest() -> Response {
|
||||
let manifest = json!({
|
||||
"entryAssetPath": "mnote-leptos-tiptap-spike-island.js",
|
||||
|
||||
Reference in New Issue
Block a user