LazyEditor — Technical Reference
LazyEditor — Technical Reference
Overview
LazyEditor is the custom WYSIWYG rich-text editor built specifically for the LazyCodet platform (Nuxt 3 / TypeScript). It is a contentEditable-based editor that avoids deprecated document.execCommand in favour of direct DOM manipulation. It supports two modes: WYSIWYG (class LazyEditor) and raw Markdown (class EditorMarkdown), both extending the abstract BaseEditor.
Source location: nuxt-lazycodet/assets/lib/editor/
Class Hierarchy
BaseEditor (abstract)
├── LazyEditor — WYSIWYG HTML editor (main)
└── EditorMarkdown — Raw markdown textarea editor
BaseEditor
Abstract base with shared reactive state:
isShowHelpPopup,isShowSidebarOptions,isShowRefPopup,isShowLinkPopup,isShowCaptionPopup— popup visibility refsdeletingImages,uploadingImages— image operation queues- Abstract methods:
setContent(),getContent(),moveCaretToFirstLine(),waitReadyStyleCodeBlock()
LazyEditor
The main editor class. Instantiated directly from the Vue component components/z/editor.vue.
Constructor signature:
new LazyEditor(
id_div: string, // ID of the container <div>
id_post: string | null, // Post ID for image upload routing
editor: BaseEditorModal, // Editor model (page-specific logic)
callback_autosave: (() => void) | null,
height: number | undefined | null, // Editor height in px (default: auto)
disablePlugins: Plugin[], // Plugins to disable
$t: (key: string) => any, // i18n translation function
waitReadyStyleCodeBlock: () => Promise<void>,
redirectMarkdownEditor?: string // Route to redirect to markdown mode
)
EditorMarkdown
Wraps a plain <textarea> for markdown editing. Implements the same BaseEditor interface so it can be swapped in transparently.
Sub-Controllers
Each controller is instantiated inside LazyEditor and holds a reference back to the parent editor instance.
| Controller | File | Responsibility |
|---|---|---|
CodeBlock | code-block/code-block.ts | PrismJS code blocks — create, focus, paste, language detection |
Caret | caret.ts | Caret tracking, offset calculations, move-to-node |
CaretMovingController | caret-moving-controller.ts | Directional caret movement across elements |
BoldController | bold-controller.ts | Bold toggling without execCommand |
Title | title.ts | Heading management (H1–H6 conversions) |
SelectionController | selection.ts | Selection read/write, direction detection, cut/remove |
Table | table.ts | Table navigation, caret movement inside cells |
List | list.ts | OL/UL creation, nesting, LI orphan handling |
HTML | html.ts | HTML DOM utilities, obstacle-overcome traversal |
StateManager | state-manager.ts | Undo/redo state stack with debounce |
ImageService | image-service.ts | Image upload to server, base64 preview, delete |
OnPasteEditor | onPasteEditor.ts | Paste handler for HTML, plain text, and images |
KeyDownEvent | keydown-event.ts | Per-key keyboard behaviour (arrow, enter, delete, …) |
ShortcutManager | shortcut-manager.ts | Keyboard shortcut registry (Windows + macOS) |
DOM Structure
<div class="container-editor">
<div class="arrow-note-line" /> <!-- Visual line indicator -->
<div class="dot-note-line" />
<div class="caret-tracking" /> <!-- Debug caret overlay -->
<div class="editor-body">
<!-- Toolbar is injected here -->
<div class="note-editable" <!-- The actual contentEditable div -->
contenteditable="true"
id="{id_editor}">
<p>...</p>
<div class="prism-live">...</div> <!-- Code blocks -->
<table>...</table>
</div>
</div>
</div>
Key CSS class names (defined in editor-enums.ts):
EditorEnum.className="note-editable"— the editable areaEditorEnum.bodyEditorClassName="editor-body"— scroll containerEditorEnum.prismLiveClassName="prism-live"— code block element
Content Model
The editor works with three formats:
| Format | Description |
|---|---|
html | Inner HTML of .note-editable — primary storage format |
plaintext | Stripped text content |
markdown | Converted markdown output (via @nguyentantaitcag2000/lazycodet) |
getContent() returns { html, plaintext, markdown }.setContent(html | EditorState) replaces the editor's content.
EditorState (state snapshot for undo/redo)
interface EditorState {
id: number;
html: string;
actionMetadata: EditorActionMetadata; // action type: insert | delete | backspace
isTextHaveCodeBlock?: boolean;
idElementFocusing?: string;
scrollPosition: number;
caretPosition: number;
selectionState?: SelectionState;
}
State Management (Undo / Redo)
StateManager maintains two stacks: undoStack and redoStack.
saveState(args?, autoSave?)— debounced (200 ms default) push toundoStack. Callscallback_autosaveifautoSave = true.immediate()— bypass debounce for the next save.- Redo stack is cleared whenever a new state is saved.
- Automatically integrates with image delete/restore operations to sync server-side state.
Code Block
Code blocks use PrismJS + custom PrismLive (from @nguyentantaitcag2000/lazycodet) rendered into a <textarea> for live editing.
Supported languages (25+):c, php, python, java, markup, html, xml, javascript, typescript, css, less, sass, scss, shell, csharp, sql, json, jsx, tsx, bash, yaml, docker, ahk, and more.
Code block creation:
- Shortcut:
Ctrl+Space(Windows) /Ctrl+Command(macOS) - Each code block has a language selector dropdown.
- Auto-detect language is supported via
LanguageDetector. - Tab key indents inside the block;
Ctrl+Backspacedrops (removes) the block.
Image Handling
Managed by ImageService:
- User pastes or drops an image.
- Image is compressed via
ImageHelper.compressImage()(max 2048px, quality 0.6, output WebP). - Base64 preview is immediately inserted into the editor with a loading indicator.
- Async upload to
ENV.server2 + /{editorModel.name}/editor/upload-imagevia multipart form. - On success, base64
srcis swapped to the server URL (preloaded first to avoid flicker). - Undo of an image paste triggers server deletion; redo re-uploads.
Paste Handling
OnPasteEditor.handle() dispatches based on clipboard content:
- Image — delegates to
ImageService.saveImage(). - Plain text paste (
Shift+V) — strips all HTML. - HTML — sanitised via
LazyFixHTMLandHTMLFixer, then inserted at the caret. - Code block paste — passed directly to
CodeBlock.onPaste(). - When text is selected, selection is removed before insertion.
Keyboard Shortcuts
Windows
| Shortcut | Action |
|---|---|
Ctrl+Space | Create new code block |
Ctrl+Z | Undo |
Ctrl+Y | Redo |
Ctrl+B | Bold |
Ctrl+I | Italic |
Ctrl+U | Underline |
Alt+0 | Format as paragraph <p> |
Alt+1 – Alt+6 | Format as H1–H6 |
Alt+7 | Ordered list |
Alt+8 | Unordered list |
Alt+B | Blockquote |
Alt+C | Inline code |
Alt+T | Insert table |
Alt+X | Checkbox |
Alt+E | Erase format |
Alt+↑ / Alt+↓ | Move current line up / down |
Alt+L | Create hyperlink |
Alt+− | Insert horizontal rule |
Alt+Shift+5 | Strikethrough |
Ctrl+Shift+L/R/E | Align left / right / center |
Ctrl+[ / Ctrl+] | Decrease / Increase indent |
Ctrl+Backspace | Drop (remove) code block |
macOS equivalents
Replace Ctrl → Command, Alt → Option, Ctrl+Space → Control+Command.
Plugin System
Certain editor features can be disabled via the disablePlugins constructor parameter:
Plugin enum value | Feature |
|---|---|
Plugin.title | Heading (H1–H6) formatting |
Plugin.alignment | Text alignment (center, right) |
Plugin.sign | Special characters / symbol popup |
Vue Integration
The editor is used via components/z/editor.vue (<ZEditor>):
<ZEditor
:id="'my-editor-div'"
:editorModel="editorModel"
:autoSave="true"
:toolbarFixed="true"
:height="650"
:disablePlugins="[]"
:cacheKey="'my-key'"
:redirectMarkdownEditor="'/markdown-route'"
/>
Props:
| Prop | Type | Description |
|---|---|---|
id | string | ID of the internal container <div> |
editorModel | BaseEditorModal | Page-level model controlling save/load logic |
autoSave | boolean | Enable auto-save callback |
toolbarFixed | boolean | Stick toolbar to top of viewport |
height | number | Fixed editor height (px). Omit for auto |
disablePlugins | Plugin[] | Features to disable |
cacheKey | string | Cache key for editor store |
redirectMarkdownEditor | string | Route name to redirect to markdown mode |
Associated popups (auto-rendered inside <ZEditor>):
EditorHelpPopup— keyboard shortcut helpEditorLinkPopup— hyperlink creation dialogEditorRefPopup— reference / citation dialogEditorCaptionPopup— image / table caption editorEditorSidebarLeft— left sidebar (sign groups, navigation)
External Dependencies
| Package | Used For |
|---|---|
jquery | DOM queries throughout the editor |
prismjs | Syntax highlighting engine |
@nguyentantaitcag2000/lazycodet | PrismLive (live code editing), Markdown converter, LanguageDetector, Codet (code formatter), LazyFixHTML (HTML sanitiser), list-normalizer |
Key Methods Reference
| Method | Description |
| -------------------------------------------------- | ------------------------------------------------------------------ | --------- | -------------------------------- |
| getContent() | Returns { html, plaintext, markdown } |
| setContent(arg) | Accepts HTML string or EditorState |
| setEmpty() | Clears the editor |
| pasteHTML(html, selection?) | Inserts HTML at caret position |
| pasteHTMLSpecial(el) | Inserts block elements (tables, HR, code) with trailing empty line |
| saveCaretPosition() / restoreCaretPosition() | Saves/restores caret before toolbar interactions |
| undo() / redo() | Async undo/redo with code block support |
| insertLineBefore(line) / insertLineAfter(line) | DOM line insertion helpers |
| getLine(element) | Walks up DOM to find the nearest direct child of the editor |
| GetElementInCursor() | Returns the element currently focused by the caret |
| isSelectingText() | True if user has an active text selection |
| align('left' | 'center' | 'right') | Aligns the current block element |
| setHeader(HeaderName) | Changes the current line's tag (P, H1–H6) |
| caption() | Opens caption editor for the selected image or table |
| unprotected(callback) | Temporarily suspends onDOMChange observer |
| static fixContent(html) | Normalises raw HTML before loading into editor |
Supported Block Element Types
Inside the .note-editable container, valid top-level elements are:
<p>— paragraph (default)<h1>–<h6>— headings<ul>/<ol>containing<li>— lists (supports nesting)<div class="prism-live">— code block container<table>— table<hr>— horizontal rule<blockquote>— blockquote
Orphan <div> elements are automatically converted to <p> via convertLine_DIVtoP().
Important Behaviours
- Zero-width spaces (
\u200B) are used as placeholder characters inside empty inline elements to preserve caret positioning. onDOMChange(MutationObserver) fires on every content change to update image lists, table lists, and trigger autosave.- Line heights are calculated once at construction time (
heightOfSingleLine) for scroll-to-caret logic. - Auto-scroll (
autoScrollDown/autoScrollUp) keeps the caret visible as the user types. - Triple-click selection is handled manually via
onTripleClickto ensure consistent cross-browser behaviour. - Drag-and-drop of text/images inside the editor is disabled to prevent unintended moves.