Lazycodet Explanation

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 refs
  • deletingImages, 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.

ControllerFileResponsibility
CodeBlockcode-block/code-block.tsPrismJS code blocks — create, focus, paste, language detection
Caretcaret.tsCaret tracking, offset calculations, move-to-node
CaretMovingControllercaret-moving-controller.tsDirectional caret movement across elements
BoldControllerbold-controller.tsBold toggling without execCommand
Titletitle.tsHeading management (H1–H6 conversions)
SelectionControllerselection.tsSelection read/write, direction detection, cut/remove
Tabletable.tsTable navigation, caret movement inside cells
Listlist.tsOL/UL creation, nesting, LI orphan handling
HTMLhtml.tsHTML DOM utilities, obstacle-overcome traversal
StateManagerstate-manager.tsUndo/redo state stack with debounce
ImageServiceimage-service.tsImage upload to server, base64 preview, delete
OnPasteEditoronPasteEditor.tsPaste handler for HTML, plain text, and images
KeyDownEventkeydown-event.tsPer-key keyboard behaviour (arrow, enter, delete, …)
ShortcutManagershortcut-manager.tsKeyboard 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 area
  • EditorEnum.bodyEditorClassName = "editor-body" — scroll container
  • EditorEnum.prismLiveClassName = "prism-live" — code block element

Content Model

The editor works with three formats:

FormatDescription
htmlInner HTML of .note-editable — primary storage format
plaintextStripped text content
markdownConverted 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 to undoStack. Calls callback_autosave if autoSave = 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+Backspace drops (removes) the block.

Image Handling

Managed by ImageService:

  1. User pastes or drops an image.
  2. Image is compressed via ImageHelper.compressImage() (max 2048px, quality 0.6, output WebP).
  3. Base64 preview is immediately inserted into the editor with a loading indicator.
  4. Async upload to ENV.server2 + /{editorModel.name}/editor/upload-image via multipart form.
  5. On success, base64 src is swapped to the server URL (preloaded first to avoid flicker).
  6. 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 LazyFixHTML and HTMLFixer, 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

ShortcutAction
Ctrl+SpaceCreate new code block
Ctrl+ZUndo
Ctrl+YRedo
Ctrl+BBold
Ctrl+IItalic
Ctrl+UUnderline
Alt+0Format as paragraph <p>
Alt+1Alt+6Format as H1–H6
Alt+7Ordered list
Alt+8Unordered list
Alt+BBlockquote
Alt+CInline code
Alt+TInsert table
Alt+XCheckbox
Alt+EErase format
Alt+↑ / Alt+↓Move current line up / down
Alt+LCreate hyperlink
Alt+−Insert horizontal rule
Alt+Shift+5Strikethrough
Ctrl+Shift+L/R/EAlign left / right / center
Ctrl+[ / Ctrl+]Decrease / Increase indent
Ctrl+BackspaceDrop (remove) code block

macOS equivalents

Replace CtrlCommand, AltOption, Ctrl+SpaceControl+Command.


Plugin System

Certain editor features can be disabled via the disablePlugins constructor parameter:

Plugin enum valueFeature
Plugin.titleHeading (H1–H6) formatting
Plugin.alignmentText alignment (center, right)
Plugin.signSpecial 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:

PropTypeDescription
idstringID of the internal container <div>
editorModelBaseEditorModalPage-level model controlling save/load logic
autoSavebooleanEnable auto-save callback
toolbarFixedbooleanStick toolbar to top of viewport
heightnumberFixed editor height (px). Omit for auto
disablePluginsPlugin[]Features to disable
cacheKeystringCache key for editor store
redirectMarkdownEditorstringRoute name to redirect to markdown mode

Associated popups (auto-rendered inside <ZEditor>):

  • EditorHelpPopup — keyboard shortcut help
  • EditorLinkPopup — hyperlink creation dialog
  • EditorRefPopup — reference / citation dialog
  • EditorCaptionPopup — image / table caption editor
  • EditorSidebarLeft — left sidebar (sign groups, navigation)

External Dependencies

PackageUsed For
jqueryDOM queries throughout the editor
prismjsSyntax highlighting engine
@nguyentantaitcag2000/lazycodetPrismLive (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 onTripleClick to ensure consistent cross-browser behaviour.
  • Drag-and-drop of text/images inside the editor is disabled to prevent unintended moves.