feat(services): introduce DeesServiceLibLoader to lazy-load heavy client libraries from CDN and update components to use it

This commit is contained in:
2026-01-01 20:25:05 +00:00
parent 2a6457e192
commit d7f3594dd4
12 changed files with 410 additions and 39 deletions

View File

@@ -14,12 +14,8 @@ import {
query,
} from '@design.estate/dees-element';
import { Editor } from '@tiptap/core';
import StarterKit from '@tiptap/starter-kit';
import Underline from '@tiptap/extension-underline';
import TextAlign from '@tiptap/extension-text-align';
import Link from '@tiptap/extension-link';
import Typography from '@tiptap/extension-typography';
import type { Editor } from '@tiptap/core';
import { DeesServiceLibLoader, type ITiptapBundle } from '../../../services/index.js';
declare global {
interface HTMLElementTagNameMap {
@@ -63,6 +59,7 @@ export class DeesInputRichtext extends DeesInputBase<string> {
private editorElement: HTMLElement;
private linkInputElement: HTMLInputElement;
private tiptapBundle: ITiptapBundle | null = null;
public editor: Editor;
@@ -233,13 +230,19 @@ export class DeesInputRichtext extends DeesInputBase<string> {
public async firstUpdated() {
await this.updateComplete;
// Load Tiptap from CDN
this.tiptapBundle = await DeesServiceLibLoader.getInstance().loadTiptap();
this.editorElement = this.shadowRoot.querySelector('.editor-content');
this.linkInputElement = this.shadowRoot.querySelector('.link-input input');
this.initializeEditor();
}
private initializeEditor(): void {
if (this.disabled) return;
if (this.disabled || !this.tiptapBundle) return;
const { Editor, StarterKit, Underline, TextAlign, Link, Typography } = this.tiptapBundle;
this.editor = new Editor({
element: this.editorElement,
@@ -249,7 +252,7 @@ export class DeesInputRichtext extends DeesInputBase<string> {
levels: [1, 2, 3],
},
}),
Underline,
Underline.configure({}),
TextAlign.configure({
types: ['heading', 'paragraph'],
}),
@@ -259,7 +262,7 @@ export class DeesInputRichtext extends DeesInputBase<string> {
class: 'editor-link',
},
}),
Typography,
Typography.configure({}),
],
content: this.value || (this.placeholder ? `<p>${this.placeholder}</p>` : ''),
onUpdate: ({ editor }) => {

View File

@@ -2,9 +2,10 @@ import { BaseBlockHandler, type IBlockEventHandlers } from '../block.base.js';
import type { IBlock } from '../../wysiwyg.types.js';
import { cssManager } from '@design.estate/dees-element';
import { WysiwygSelection } from '../../wysiwyg.selection.js';
import hlight from 'highlight.js';
import type { HLJSApi } from 'highlight.js';
import { cssGeistFontFamily, cssMonoFontFamily } from '../../../../00fonts.js';
import { PROGRAMMING_LANGUAGES } from '../../wysiwyg.constants.js';
import { DeesServiceLibLoader } from '../../../../../services/index.js';
/**
* CodeBlockHandler with improved architecture
@@ -18,8 +19,9 @@ import { PROGRAMMING_LANGUAGES } from '../../wysiwyg.constants.js';
*/
export class CodeBlockHandler extends BaseBlockHandler {
type = 'code';
private highlightTimer: any = null;
private highlightJs: HLJSApi | null = null;
render(block: IBlock, isSelected: boolean): string {
const language = block.metadata?.language || 'typescript';
@@ -306,28 +308,33 @@ export class CodeBlockHandler extends BaseBlockHandler {
return linesBeforeCursor.length - 1; // 0-indexed
}
private applyHighlighting(element: HTMLElement, block: IBlock): void {
private async applyHighlighting(element: HTMLElement, block: IBlock): Promise<void> {
const editor = element.querySelector('.code-editor') as HTMLElement;
if (!editor) return;
// Load highlight.js from CDN if not already loaded
if (!this.highlightJs) {
this.highlightJs = await DeesServiceLibLoader.getInstance().loadHighlightJs();
}
// Store cursor position
const cursorPos = this.getCursorPosition(element);
// Get plain text content
const content = editor.textContent || '';
const language = block.metadata?.language || 'typescript';
// Apply highlighting
try {
const result = hlight.highlight(content, {
const result = this.highlightJs.highlight(content, {
language: language,
ignoreIllegals: true
ignoreIllegals: true,
});
// Only update if we have valid highlighted content
if (result.value) {
editor.innerHTML = result.value;
// Restore cursor position if editor is focused
if (document.activeElement === editor && cursorPos !== null) {
requestAnimationFrame(() => {