import { Editor } from '../../editor/Editor.js'; import { ContextMenuDirectory, ContextMenuReadOnlyEntry } from '../context-menu/context-menu.js'; class HtmlEditorPanel extends HTMLElement { currentPath: string | null = null; _pinned: boolean = false; _iframe: HTMLIFrameElement | null = null; _undoStack: string[] = []; _redoStack: string[] = []; _mutationObserver: MutationObserver | null = null; _initialized = false; _needsRestore = false; connectedCallback(): void { if ( this._initialized ) return; this._initialized = true; this.className = 'html-editor-panel'; this.innerHTML = `
Open an HTML file from the file tree
`; this._iframe = this.querySelector( 'iframe' ); this.querySelector( '.hep-pin' )!.addEventListener( 'click', () => this._togglePin() ); this.querySelector( '.hep-save' )!.addEventListener( 'click', () => this._save() ); this.querySelector( '.hep-undo' )!.addEventListener( 'click', () => this._undo() ); this.querySelector( '.hep-redo' )!.addEventListener( 'click', () => this._redo() ); this.querySelector( '.hep-init' )!.addEventListener( 'click', () => this._initTemplate() ); Editor.get().onDocumentOpened.addListener( ( e ) => { if ( ! this._pinned ) this._loadDocument( e.path, e.content ); } ); document.addEventListener( 'keydown', ( e: KeyboardEvent ) => { if ( ! this.currentPath ) { return; } if ( e.ctrlKey && e.key === 's' ) { e.preventDefault(); this._save(); } if ( e.ctrlKey && ! e.shiftKey && e.key === 'z' ) { e.preventDefault(); this._undo(); } if ( e.ctrlKey && ( e.key === 'y' || ( e.shiftKey && e.key === 'z' ) ) ) { e.preventDefault(); this._redo(); } } ); } disconnectedCallback(): void { if ( this._undoStack.length > 0 ) { this._needsRestore = true; } } _updateTabLabel( path: string ): void { const name = path ? path.slice( path.lastIndexOf( '/' ) + 1 ) : ''; this.dispatchEvent( new CustomEvent( 'panel:label-change', { bubbles: true, detail: { label: '📄 ' + name } } ) ); } _loadDocument( path: string, content: string ): void { this.currentPath = path; this._undoStack = [ content ]; this._redoStack = []; this._updateTabLabel( path ); this.querySelector( '.hep-empty' )!.setAttribute( 'style', 'display:none' ); this._iframe!.style.display = ''; this._renderContent( content ); this._updateButtons( false ); } _renderContent( html: string ): void { const iframe = this._iframe!; if ( this._mutationObserver ) { this._mutationObserver.disconnect(); this._mutationObserver = null; } iframe.srcdoc = html; iframe.onload = () => { if ( this._needsRestore ) { this._needsRestore = false; this._renderContent( this._undoStack[ this._undoStack.length - 1 ] ); return; } const doc = iframe.contentDocument!; const pc = doc.querySelector( 'page-content' ); if ( pc ) { ( pc as HTMLElement ).contentEditable = 'true'; ( pc as HTMLElement ).style.outline = 'none'; this._mutationObserver = new MutationObserver( () => this._onContentChanged() ); this._mutationObserver.observe( pc, { subtree: true, childList: true, characterData: true, attributes: true } ); } }; } _onContentChanged(): void { if ( ! this.currentPath || ! this._iframe?.contentDocument ) { return; } const html = '\n' + this._iframe.contentDocument.documentElement.outerHTML; const last = this._undoStack[ this._undoStack.length - 1 ]; if ( html === last ) { return; } this._undoStack.push( html ); if ( this._undoStack.length > 200 ) { this._undoStack.shift(); } this._redoStack = []; Editor.get().markDirty( this.currentPath, html ); this._updateButtons( true ); } _undo(): void { if ( this._undoStack.length < 2 ) { return; } const current = this._undoStack.pop()!; this._redoStack.push( current ); const prev = this._undoStack[ this._undoStack.length - 1 ]; Editor.get().markDirty( this.currentPath!, prev ); this._renderContent( prev ); this._updateButtons( true ); } _redo(): void { if ( ! this._redoStack.length ) { return; } const next = this._redoStack.pop()!; this._undoStack.push( next ); Editor.get().markDirty( this.currentPath!, next ); this._renderContent( next ); this._updateButtons( true ); } _togglePin(): void { this._pinned = ! this._pinned; this.querySelector( '.hep-pin' )!.classList.toggle( 'pinned', this._pinned ); } _initTemplate(): void { if ( ! this.currentPath ) return; const template = [ '', '', '', ' ', ' Hello World', '', '', ' ', '

Hello World

', '

Welcome.

', '
', '', '', ].join( '\n' ); this._undoStack.push( template ); this._redoStack = []; Editor.get().markDirty( this.currentPath, template ); this._renderContent( template ); this._updateButtons( true ); } async _save(): Promise { if ( ! this.currentPath ) { return; } await Editor.get().save( this.currentPath ); this._updateButtons( false ); } addContextMenuEntries( dir: ContextMenuDirectory ): void { if ( this.currentPath ) { dir.add( new ContextMenuReadOnlyEntry( dir, `Editing: ${this.currentPath}` ) ); } else { dir.add( new ContextMenuReadOnlyEntry( dir, 'No document open' ) ); } } _updateButtons( dirty: boolean ): void { ( this.querySelector( '.hep-save' ) as HTMLButtonElement ).disabled = ! dirty; ( this.querySelector( '.hep-undo' ) as HTMLButtonElement ).disabled = this._undoStack.length < 2; ( this.querySelector( '.hep-redo' ) as HTMLButtonElement ).disabled = this._redoStack.length === 0; ( this.querySelector( '.hep-init' ) as HTMLButtonElement ).disabled = ! this.currentPath; } } customElements.define( 'html-editor-panel', HtmlEditorPanel );