feat: migrate Milkdown integration to local vendor bundle model

This commit is contained in:
2026-06-08 19:28:13 +02:00
parent c6ffe1993e
commit 56677556cd
5 changed files with 1177 additions and 15 deletions
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -1,20 +1,20 @@
// Map to keep track of active Crepe editor instances by elementId (container ID)
const editors = new Map();
const editorCache = new Map();
/**
* Asynchronously injects a stylesheet link tag into the document head
* and returns a Promise that resolves when the stylesheet is fully loaded.
*/
async function injectStylesheet(url) {
if (document.querySelector(`link[href="${url}"]`)) {
async function ensureStylesheet(href) {
if (document.querySelector(`link[href="${href}"]`)) {
return Promise.resolve();
}
return new Promise((resolve, reject) => {
const link = document.createElement('link');
link.rel = 'stylesheet';
link.href = url;
link.href = href;
link.onload = () => resolve();
link.onerror = (err) => reject(new Error(`Failed to load stylesheet: ${url}. ${err}`));
link.onerror = (err) => reject(new Error(`Failed to load stylesheet: ${href}. ${err}`));
document.head.appendChild(link);
});
}
@@ -31,13 +31,16 @@ export async function initEditor(elementId, dotNetHelper, initialMarkdown) {
try {
// Condition 2: Prevent FOUC by loading stylesheets before instantiating the editor
await Promise.all([
injectStylesheet('https://esm.sh/@milkdown/crepe/lib/theme/common/style.css'),
injectStylesheet('https://esm.sh/@milkdown/crepe/lib/theme/frame/style.css')
]);
await ensureStylesheet('/_content/NexusReader.UI.Shared/css/vendor/milkdown-crepe.css');
// Dynamically import the Crepe ESM module
const { Crepe } = await import('https://esm.sh/@milkdown/crepe');
// Dynamically import the local JS bundle
await import('/_content/NexusReader.UI.Shared/js/vendor/milkdown-crepe.js');
// Get Crepe constructor from the global window.milkdownCrepe namespace
const Crepe = window.milkdownCrepe?.Crepe;
if (!Crepe) {
throw new Error("Crepe constructor not found on window.milkdownCrepe");
}
// Initialize the Crepe editor instance with custom ImageBlock upload handler
const crepe = new Crepe({
@@ -61,7 +64,7 @@ export async function initEditor(elementId, dotNetHelper, initialMarkdown) {
});
// Store the editor instance in the map
editors.set(elementId, crepe);
editorCache.set(elementId, crepe);
// Create the editor view asynchronously
await crepe.create();
@@ -76,7 +79,7 @@ export async function initEditor(elementId, dotNetHelper, initialMarkdown) {
* Retrieves the current Markdown content from a specific editor instance.
*/
export function getMarkdownContent(elementId) {
const crepe = editors.get(elementId);
const crepe = editorCache.get(elementId);
if (!crepe) {
console.warn(`[Milkdown] No editor instance found for element: ${elementId}`);
return "";
@@ -88,7 +91,7 @@ export function getMarkdownContent(elementId) {
* Safely disposes of the editor instance to prevent memory leaks in WASM.
*/
export async function destroyEditor(elementId) {
const crepe = editors.get(elementId);
const crepe = editorCache.get(elementId);
if (crepe) {
try {
await crepe.destroy();
@@ -96,6 +99,6 @@ export async function destroyEditor(elementId) {
} catch (error) {
console.error(`[Milkdown] Error destroying editor for element "${elementId}":`, error);
}
editors.delete(elementId);
editorCache.delete(elementId);
}
}
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long