UNPKG

@plusify/ngx-markdown-editor

Version:

A customizable Markdown editor for Angular with KaTeX support, syntax highlighting, and a modern toolbar.

1 lines 70.1 kB
{"version":3,"file":"plusify-ngx-markdown-editor.mjs","sources":["../../../../projects/ngx-markdown-editor/src/lib/animations/swing.animation.ts","../../../../projects/ngx-markdown-editor/src/lib/components/preview/preview.component.ts","../../../../projects/ngx-markdown-editor/src/lib/components/preview/preview.component.html","../../../../projects/ngx-markdown-editor/src/lib/components/textarea/markdown-textarea.component.ts","../../../../projects/ngx-markdown-editor/src/lib/components/textarea/markdown-textarea.component.html","../../../../projects/ngx-markdown-editor/src/lib/components/toolbar/components/table-size-selector/table-size-selector.component.ts","../../../../projects/ngx-markdown-editor/src/lib/components/toolbar/components/table-size-selector/table-size-selector.component.html","../../../../projects/ngx-markdown-editor/src/lib/components/toolbar/components/popover/popover.component.ts","../../../../projects/ngx-markdown-editor/src/lib/components/toolbar/components/popover/popover.component.html","../../../../projects/ngx-markdown-editor/src/lib/components/toolbar/actions/toolbar-actions.ts","../../../../projects/ngx-markdown-editor/src/lib/components/toolbar/toolbar-items.ts","../../../../projects/ngx-markdown-editor/src/lib/components/toolbar/markdown-toolbar.component.ts","../../../../projects/ngx-markdown-editor/src/lib/components/toolbar/markdown-toolbar.component.html","../../../../projects/ngx-markdown-editor/src/lib/config/default-editor-config.ts","../../../../projects/ngx-markdown-editor/src/lib/editor/markdown-editor.component.ts","../../../../projects/ngx-markdown-editor/src/lib/editor/markdown-editor.component.html","../../../../projects/ngx-markdown-editor/src/public-api.ts","../../../../projects/ngx-markdown-editor/src/plusify-ngx-markdown-editor.ts"],"sourcesContent":["import {\n animation,\n style,\n animate,\n trigger,\n transition,\n useAnimation,\n} from '@angular/animations';\n\n/**\n * Animation for swinging elements in from different directions.\n */\nexport const swingIn = animation(\n [\n style({\n opacity: 0,\n transform: '{{startTransform}}',\n transformOrigin: '{{origin}}',\n }),\n animate(\n '{{duration}} {{easing}}',\n style({\n opacity: 1,\n transform: 'translate(0, 0) scale(1) rotate(0)',\n })\n ),\n ],\n {\n params: {\n startTransform: 'translateY(50px) rotateX(-90deg)',\n origin: 'bottom',\n duration: '600ms',\n easing: 'cubic-bezier(.36,-0.64,.34,1.76)',\n },\n }\n);\n\n/**\n * Animation for swinging elements in from the bottom.\n */\nexport const swingFromBottom = trigger('swingFromBottom', [\n transition(':enter', [\n useAnimation(swingIn, {\n params: {\n startTransform: 'translateY(50px)',\n origin: 'bottom',\n },\n }),\n ]),\n transition(':leave', [\n animate(\n '400ms',\n style({\n opacity: 0,\n transform: 'translateY(50px)',\n })\n ),\n ]),\n]);\n\n/**\n * Animation for swinging elements in from the left.\n */\nexport const swingFromLeft = trigger('swingFromLeft', [\n transition(':enter', [\n useAnimation(swingIn, {\n params: {\n startTransform: 'translateX(-50px)',\n origin: 'left',\n },\n }),\n ]),\n transition(':leave', [\n animate(\n '400ms',\n style({\n opacity: 0,\n transform: 'translateX(-50px)',\n })\n ),\n ]),\n]);\n\n/**\n * Animation for swinging elements in from the right.\n */\nexport const swingFromRight = trigger('swingFromRight', [\n transition(':enter', [\n useAnimation(swingIn, {\n params: {\n startTransform: 'translateX(50px)',\n origin: 'right',\n },\n }),\n ]),\n transition(':leave', [\n animate(\n '400ms',\n style({\n opacity: 0,\n transform: 'translateX(50px)',\n })\n ),\n ]),\n]);\n\n/**\n * Duration for the swing scale center animation\n * Value is set to ms\n */\nexport const SWING_SCALE_CENTER_DURATION = 250;\n\n/**\n * Animation for swinging elements in from the center with scaling.\n */\nexport const swingScaleCenter = trigger('swingScaleCenter', [\n transition(':enter', [\n useAnimation(swingIn, {\n params: {\n startTransform: 'scale(0.5)',\n origin: 'center',\n easing: 'ease-out',\n },\n }),\n ]),\n transition(':leave', [\n animate(\n `${SWING_SCALE_CENTER_DURATION}ms`,\n style({\n opacity: 0,\n transform: 'scale(0.5)',\n })\n ),\n ]),\n]);\n","import { CommonModule } from '@angular/common';\nimport { Component, ElementRef, input, Input, InputSignal, output, ViewChild } from '@angular/core';\nimport { MarkdownComponent } from 'ngx-markdown';\nimport 'prismjs/components/prism-bash';\nimport 'prismjs/components/prism-json';\nimport 'prismjs/components/prism-markup';\nimport 'prismjs/components/prism-scss';\nimport 'prismjs/components/prism-typescript';\n\n/**\n * NgxMarkdownPreviewComponent\n *\n * Displays rendered markdown content using ngx-markdown.\n * Supports KaTeX math rendering and clipboard functionality.\n */\n@Component({\n selector: 'plusify-markdown-preview',\n templateUrl: './preview.component.html',\n styleUrls: ['./preview.component.scss', './preview-theme.scss'],\n imports: [CommonModule, MarkdownComponent],\n})\n/**\n * A component for rendering a markdown preview with optional scrolling and sanitizer control.\n * \n * @remarks\n * This component allows rendering markdown content in a preview area. It provides\n * functionality to handle scrolling events and control whether Angular's default DOM\n * sanitizer is disabled for rendering trusted raw HTML inside markdown.\n * \n * @example\n * ```html\n * <ngx-markdown-preview\n * [content]=\"markdownContent\"\n * [disableSanitizer]=\"true\"\n * (scrolled)=\"onScroll($event)\">\n * </ngx-markdown-preview>\n * ```\n * \n * @property content - The markdown content to be rendered in the preview.\n * @property disableSanitizer - A flag to disable Angular's default DOM sanitizer.\n * @property scrolled - An output signal that emits the scroll ratio of the preview area.\n * \n * @method onScroll - Handles the scroll event and emits the scroll ratio.\n * @method scrollTo - Scrolls the preview area to a specific ratio.\n * \n * @decorator @ViewChild('preview', { static: true }) - References the preview element in the DOM.\n */\nexport class NgxMarkdownPreviewComponent {\n /**\n * Markdown content to be rendered in the preview.\n */\n content: InputSignal<string> = input<string>('');\n\n /**\n * Whether to disable Angular’s default DOM sanitizer.\n * This can be useful for rendering trusted raw HTML inside markdown.\n */\n disableSanitizer: InputSignal<boolean> = input<boolean>(false);\n\n public scrolled = output<number>();\n\n @ViewChild('preview', { static: true })\n previewEl!: ElementRef<HTMLDivElement>;\n\n /**\n * Handles the scroll event on the preview element and calculates the scroll ratio.\n * Emits the calculated scroll ratio as a value between 0 and 1.\n *\n * The scroll ratio is determined by dividing the current scroll position (`scrollTop`)\n * by the difference between the total scrollable height (`scrollHeight`) and the visible height (`clientHeight`).\n *\n * @emits scrolled - Emits the scroll ratio as a number between 0 and 1.\n */\n onScroll(): void {\n const el = this.previewEl.nativeElement;\n const ratio = el.scrollTop / (el.scrollHeight - el.clientHeight);\n this.scrolled.emit(ratio);\n }\n\n /**\n * Scrolls the preview element to a specific position based on the provided ratio.\n * \n * @param ratio - A number between 0 and 1 representing the scroll position as a percentage.\n * For example, 0 scrolls to the top, 1 scrolls to the bottom, and 0.5 scrolls to the middle.\n */\n public scrollTo(ratio: number): void {\n const el = this.previewEl.nativeElement;\n el.scrollTop = ratio * (el.scrollHeight - el.clientHeight);\n }\n}\n","<!-- Rendered markdown preview -->\n<div #preview class=\"preview-content scrollable full-height\" (scroll)=\"onScroll()\">\n <markdown [data]=\"content()\" katex clipboard [disableSanitizer]=\"disableSanitizer()\"></markdown>\n</div>\n","import { CommonModule } from '@angular/common';\nimport {\n Component,\n ElementRef,\n EventEmitter,\n input,\n Input,\n InputSignal,\n output,\n Output,\n ViewChild,\n} from '@angular/core';\nimport { FormsModule } from '@angular/forms';\n\n/**\n * NgxMarkdownTextareaComponent\n *\n * Handles all text manipulation logic for the markdown editor.\n * Provides utility functions to insert, wrap, and read text at the cursor position.\n */\n@Component({\n selector: 'plusify-markdown-textarea',\n templateUrl: './markdown-textarea.component.html',\n styleUrls: ['./markdown-textarea.component.scss'],\n imports: [CommonModule, FormsModule],\n})\nexport class NgxMarkdownTextareaComponent {\n /**\n * Current textarea value.\n */\n @Input()\n value = '';\n\n /**\n * Whether the textarea is readonly.\n */\n readonly: InputSignal<boolean> = input<boolean>(false);\n\n /**\n * Emits whenever the value is updated programmatically or by the user.\n */\n @Output()\n valueChange = new EventEmitter<string>();\n\n /**\n * Reference to the native textarea element.\n */\n @ViewChild('textarea', { static: true })\n textareaEl!: ElementRef<HTMLTextAreaElement>;\n\n /**\n * Emits the current scroll position of the textarea as a number.\n * This can be used to track or synchronize scrolling behavior.\n */\n public scrolled = output<number>();\n\n /**\n * Inserts the given text at the current cursor position.\n * @param text The text to insert.\n */\n insertTextAtCursor(text: string) {\n const el = this.textareaEl.nativeElement;\n const start = el.selectionStart;\n const end = el.selectionEnd;\n const before = this.value.slice(0, start);\n const after = this.value.slice(end);\n\n this.value = before + text + after;\n this.valueChange.emit(this.value);\n\n // Restore focus and move cursor\n setTimeout(() => {\n el.focus();\n el.selectionStart = el.selectionEnd = start + text.length;\n });\n }\n\n /**\n * Wraps the currently selected text with the provided strings.\n * @param before Text to add before the selection.\n * @param after Text to add after the selection (defaults to 'before').\n */\n wrapSelection(before: string, after = before) {\n const el = this.textareaEl.nativeElement;\n const start = el.selectionStart;\n const end = el.selectionEnd;\n\n const selected = this.value.slice(start, end);\n const wrapped = `${before}${selected}${after}`;\n this.value = this.value.slice(0, start) + wrapped + this.value.slice(end);\n this.valueChange.emit(this.value);\n\n setTimeout(() => {\n el.focus();\n el.selectionStart = start + before.length;\n el.selectionEnd = end + before.length;\n });\n }\n\n /**\n * Returns the text currently selected by the user.\n */\n getSelectedText(): string {\n const el = this.textareaEl.nativeElement;\n return this.value.substring(el.selectionStart, el.selectionEnd);\n }\n\n /**\n * Handles the 'Tab' keypress to insert a tab character instead of focusing another element.\n * @param event The keyboard event.\n */\n onTabKeyPress(event: KeyboardEvent) {\n if (event.key === 'Tab') {\n event.preventDefault();\n this.insertTextAtCursor('\\t');\n }\n }\n\n /**\n * Copies the current value to the clipboard.\n */\n copyToClipboard() {\n navigator.clipboard.writeText(this.value);\n }\n\n /**\n * Synchronizes scroll position between editor and preview pane.\n * Calculates scroll ratio of source and applies it to target element.\n *\n * @param sourceTag - Either 'editor' or 'preview', indicating the source of the scroll\n */\n onScroll() {\n const el = this.textareaEl.nativeElement;\n const ratio = el.scrollTop / (el.scrollHeight - el.clientHeight);\n this.scrolled.emit(ratio);\n }\n\n /**\n * Scrolls the textarea element to a specific position based on the provided ratio.\n * The ratio determines the vertical scroll position as a fraction of the total scrollable height.\n *\n * @param ratio - A number between 0 and 1 representing the scroll position as a percentage\n * of the total scrollable height. For example, 0 scrolls to the top, 0.5 scrolls\n * to the middle, and 1 scrolls to the bottom.\n */\n public scrollTo(ratio: number) {\n const el = this.textareaEl.nativeElement;\n el.scrollTop = ratio * (el.scrollHeight - el.clientHeight);\n }\n}\n","<!-- Markdown textarea component -->\n<textarea\n #textarea\n class=\"markdown-textarea scrollable w-100\"\n [readonly]=\"readonly()\"\n [(ngModel)]=\"value\"\n (scroll)=\"onScroll()\"\n (ngModelChange)=\"valueChange.emit($event)\"\n placeholder=\"Type your markdown here...\"\n (keydown.tab)=\"onTabKeyPress($event)\"></textarea>\n","import { CommonModule } from '@angular/common';\nimport { Component, EventEmitter, Output } from '@angular/core';\n\n@Component({\n selector: 'app-table-size-selector',\n templateUrl: './table-size-selector.component.html',\n styleUrls: ['./table-size-selector.component.scss'],\n imports: [CommonModule],\n})\nexport class TableSizeSelectorComponent {\n @Output()\n tableSelected = new EventEmitter<string>();\n\n maxRows: number = 10; // Tamaño máximo de filas en la cuadrícula\n maxCols: number = 10; // Tamaño máximo de columnas en la cuadrícula\n\n rowsArray: number[] = Array(this.maxRows)\n .fill(0)\n .map((x, i) => i);\n colsArray: number[] = Array(this.maxCols)\n .fill(0)\n .map((x, i) => i);\n\n hoveredRow: number = -1;\n hoveredCol: number = -1;\n\n selectedRows: number = 1;\n selectedCols: number = 1;\n\n onMouseEnter(row: number, col: number) {\n this.hoveredRow = row;\n this.hoveredCol = col;\n this.selectedRows = row + 1;\n this.selectedCols = col + 1;\n }\n\n onMouseLeave() {\n // Puedes restablecer hoveredRow y hoveredCol si quieres que la selección visual\n // desaparezca cuando el ratón sale del popover, o mantener la última selección.\n // this.hoveredRow = -1;\n // this.hoveredCol = -1;\n }\n\n selectSize() {\n const content = this.generateTableMarkdown(this.selectedRows, this.selectedCols);\n this.tableSelected.emit(content);\n }\n\n /**\n * Generates markdown table syntax based on selected size.\n * @param rows Number of rows\n * @param cols Number of columns\n */\n private generateTableMarkdown(rows: number, cols: number): string {\n // Encabezados: | Col 1 | Col 2 | Col 3 |\n const headers = Array.from({ length: cols }, (_, i) => `Col ${i + 1}`).join(' | ');\n const headerLine = `| ${headers} |`;\n\n // Separador de encabezado: | --- | --- | --- |\n const separatorLine = `| ${Array(cols).fill('---').join(' | ')} |`;\n\n // Cuerpo: varias filas del tipo | Val 1 | Val 2 | Val 3 |\n const bodyLines = Array.from({ length: rows }, () => {\n const row = Array.from({ length: cols }, (_, i) => `Val ${i + 1}`).join(' | ');\n return `| ${row} |`;\n });\n\n return `\\n\\n${headerLine}\\n${separatorLine}\\n${bodyLines.join('\\n')}\\n`;\n }\n}\n","<div class=\"table-size-popover\" (mouseleave)=\"onMouseLeave()\">\n <div class=\"grid-container\">\n @for (row of rowsArray; track $index; let r = $index) {\n <div class=\"grid-row\">\n @for (col of colsArray; track $index; let c = $index) {\n <div\n class=\"grid-cell\"\n [class.selected]=\"r <= hoveredRow && c <= hoveredCol\"\n (mouseenter)=\"onMouseEnter(r, c)\"\n (click)=\"selectSize()\"\n (keydown)=\"(null)\"></div>\n }\n </div>\n }\n </div>\n <div class=\"size-display\">{{ selectedRows }} x {{ selectedCols }}</div>\n</div>\n","import { animate, state, style, transition, trigger } from '@angular/animations';\nimport { CommonModule } from '@angular/common';\nimport {\n AfterViewInit,\n Component,\n ElementRef,\n EventEmitter,\n HostListener,\n input,\n Input,\n InputSignal,\n Output,\n} from '@angular/core';\nimport { FormsModule } from '@angular/forms';\nimport { TableSizeSelectorComponent } from '../table-size-selector/table-size-selector.component';\n\nexport interface PopoverField {\n key: string;\n label: string;\n placeholder?: string;\n type?: 'text' | 'menu' | 'table';\n options?: { label: string; value: string }[];\n}\n\n/**\n * PopoverComponent\n *\n * A reusable popover component that displays a form with various input fields.\n * Supports dynamic fields, menu options, and emits events on submit or cancel.\n * It can be used for various purposes like inserting links, images, or headings in a markdown editor.\n */\n@Component({\n selector: 'app-popover',\n templateUrl: './popover.component.html',\n styleUrls: ['./popover.component.scss'],\n animations: [\n trigger('popoverFade', [\n state('void', style({ opacity: 0, transform: 'translateY(-10px) scale(0.95)' })),\n state('*', style({ opacity: 1, transform: 'translateY(0) scale(1)' })),\n transition('void => *', animate('200ms ease-out')),\n transition('* => void', animate('150ms ease-in')),\n ]),\n ],\n imports: [CommonModule, FormsModule, TableSizeSelectorComponent],\n})\nexport class PopoverComponent implements AfterViewInit {\n /**\n * Fields to render in the popover form.\n */\n fields: InputSignal<PopoverField[]> = input<PopoverField[]>([]);\n\n /**\n * Reference to the anchor element used for popover positioning.\n */\n @Input()\n anchor!: HTMLElement;\n\n @Input()\n type!: 'table' | 'image' | 'link' | 'heading';\n\n /**\n * Event emitted when the user submits the form.\n */\n @Output()\n submit = new EventEmitter<Record<string, string> | string>();\n\n /**\n * Event emitted when the popover is cancelled or dismissed.\n */\n @Output()\n cancel = new EventEmitter<void>();\n\n /**\n * Internal form data model.\n */\n formData: Record<string, string> = {};\n\n constructor(private readonly elementRef: ElementRef) {}\n\n ngAfterViewInit(): void {\n setTimeout(() => this.positionPopover(), 0);\n }\n\n /**\n * Positions the popover relative to the anchor element.\n */\n private positionPopover(): void {\n const rect = this.anchor.getBoundingClientRect();\n const popover = this.elementRef.nativeElement.querySelector('.plusify-popover') as HTMLElement;\n if (!popover) return;\n\n popover.style.top = `${rect.bottom + window.scrollY + 6}px`;\n popover.style.left = `${rect.left + window.scrollX}px`;\n }\n\n /**\n * Emits the current form data on submit.\n */\n onSubmit(): void {\n this.submit.emit(this.formData);\n }\n\n /**\n * Emits cancel event to close the popover.\n */\n onCancel(): void {\n this.cancel.emit();\n }\n\n /**\n * Emits a submit event immediately when a menu option is selected.\n */\n submitMenuSelection(key: string, value: string): void {\n const result: Record<string, string> = {};\n result[key] = value;\n this.submit.emit(result);\n }\n\n /**\n * Checks if all fields are of type \"menu\".\n */\n isMenuOnly(): boolean {\n return this.fields().every((f) => f.type === 'menu');\n }\n\n /**\n * Closes the popover if the user clicks outside of it and the anchor.\n */\n @HostListener('document:click', ['$event'])\n onClickOutside(event: MouseEvent): void {\n const popover = this.elementRef.nativeElement.querySelector('.plusify-popover');\n if (\n popover &&\n !popover.contains(event.target as Node) &&\n !this.anchor.contains(event.target as Node)\n ) {\n this.cancel.emit();\n }\n }\n\n onTableSizeSelected(content: string): void {\n this.submit.emit(content);\n }\n}\n","<div class=\"plusify-popover\" #popover @popoverFade [class.no-table-popover]=\"type !== 'table'\">\n @switch (type) {\n <!-- Table size selector -->\n @case ('table') {\n <app-table-size-selector\n (tableSelected)=\"onTableSizeSelected($event)\"></app-table-size-selector>\n }\n @default {\n <form (submit)=\"onSubmit(); $event.preventDefault()\">\n <div *ngFor=\"let field of fields()\" class=\"popover-field\">\n @if (!isMenuOnly()) {\n <label>{{ field.label }}</label>\n }\n\n <ng-container [ngSwitch]=\"field.type || 'text'\">\n <!-- Text input -->\n <input\n *ngSwitchCase=\"'text'\"\n type=\"text\"\n [placeholder]=\"field.placeholder\"\n [(ngModel)]=\"formData[field.key]\"\n name=\"{{ field.key }}\"\n required />\n\n <!-- Immediate menu -->\n <div *ngSwitchCase=\"'menu'\" class=\"menu-options\">\n <button\n *ngFor=\"let opt of field.options\"\n type=\"button\"\n (click)=\"submitMenuSelection(field.key, opt.value)\">\n <span [ngSwitch]=\"opt.value\">\n <h1 *ngSwitchCase=\"'1'\">Heading 1</h1>\n <h2 *ngSwitchCase=\"'2'\">Heading 2</h2>\n <h3 *ngSwitchCase=\"'3'\">Heading 3</h3>\n <h4 *ngSwitchCase=\"'4'\">Heading 4</h4>\n <h5 *ngSwitchCase=\"'5'\">Heading 5</h5>\n <h6 *ngSwitchCase=\"'6'\">Heading 6</h6>\n <span *ngSwitchDefault>{{ opt.label }}</span>\n </span>\n </button>\n </div>\n </ng-container>\n </div>\n <!-- Actions (only if not menu-only) -->\n <div class=\"popover-actions\" *ngIf=\"!isMenuOnly()\">\n <button type=\"button\" (click)=\"onCancel()\">Cancel</button>\n <button type=\"submit\">Insert</button>\n </div>\n </form>\n }\n }\n</div>\n","import { NgxMarkdownToolbarComponent } from '../markdown-toolbar.component';\n\n/**\n * Inserts a markdown image tag after collecting user input via popover.\n */\nexport async function insertImage(\n toolbarComponent: NgxMarkdownToolbarComponent,\n anchor?: HTMLElement,\n) {\n const result = await toolbarComponent.openPopover('image', anchor);\n if (!result) return;\n\n const { url, alt } = result as Record<string, string>;\n if (!url) return;\n\n toolbarComponent.textarea.insertTextAtCursor(`\\n![${alt || 'Alt Text'}](${url})`);\n}\n\n/**\n * Inserts a markdown link tag after collecting user input via popover.\n */\nexport async function insertLink(\n toolbarComponent: NgxMarkdownToolbarComponent,\n anchor?: HTMLElement,\n) {\n const result = await toolbarComponent.openPopover('link', anchor);\n if (!result) return;\n\n const { url, text } = result as Record<string, string>;\n if (!url) return;\n\n toolbarComponent.textarea.insertTextAtCursor(`\\n[${text || 'Text'}](${url})`);\n}\n\n/**\n * Inserts a markdown heading of selected level (h1-h6) after user selection via popover menu.\n */\nexport async function insertHeading(\n toolbarComponent: NgxMarkdownToolbarComponent,\n anchor?: HTMLElement,\n) {\n const result = (await toolbarComponent.openPopover('heading', anchor)) as Record<string, string>;\n if (!result) return;\n\n const level = parseInt(result.level, 10);\n if (isNaN(level) || level < 1 || level > 6) return;\n\n const prefix = '#'.repeat(level) + ' ';\n toolbarComponent.textarea.wrapSelection(`\\n\\n${prefix}`, 'title');\n}\n\nexport async function toggleTablePopover(\n toolbarComponent: NgxMarkdownToolbarComponent,\n anchor?: HTMLElement,\n) {\n const result = await toolbarComponent.openPopover('table', anchor);\n if (!result) return;\n\n const content = result as string;\n if (!content) return;\n\n toolbarComponent.textarea.insertTextAtCursor(content);\n}\n","import {\n insertHeading,\n insertImage,\n insertLink,\n toggleTablePopover,\n} from './actions/toolbar-actions';\nimport { NgxMarkdownToolbarComponent } from './markdown-toolbar.component';\n\nexport interface ToolbarItem {\n type: string;\n title: string;\n iconHtml?: string;\n iconFont?: string;\n action: (toolbar: NgxMarkdownToolbarComponent, anchor?: HTMLElement) => void | Promise<void>;\n}\n\nexport const DEFAULT_TOOLBAR_GROUPS: ToolbarItem[][] = [\n [\n {\n type: 'heading',\n title: 'Heading',\n iconHtml: 'H',\n action: insertHeading,\n },\n {\n type: 'bold',\n title: 'Bold',\n iconFont: 'format_bold',\n action: (tb) => tb.textarea.wrapSelection('**'),\n },\n {\n type: 'italic',\n title: 'Italic',\n iconFont: 'format_italic',\n action: (tb) => tb.textarea.wrapSelection('_'),\n },\n {\n type: 'strike',\n title: 'Strikethrough',\n iconFont: 'strikethrough_s',\n action: (tb) => tb.textarea.wrapSelection('~~'),\n },\n ],\n [\n {\n type: 'line',\n title: 'Line',\n iconHtml: '<strong>—</strong>',\n action: (tb) => tb.textarea.insertTextAtCursor('\\n---\\n'),\n },\n {\n type: 'blockquote',\n title: 'Quote',\n iconFont: 'format_quote',\n action: (tb) => tb.textarea.wrapSelection('> ', ''),\n },\n ],\n [\n {\n type: 'unordered-list',\n title: 'Unordered List',\n iconFont: 'format_list_bulleted',\n action: (tb) => tb.textarea.wrapSelection('- ', ''),\n },\n {\n type: 'ordered-list',\n title: 'Ordered List',\n iconFont: 'format_list_numbered',\n action: (tb) => tb.textarea.wrapSelection('1. ', ''),\n },\n {\n type: 'task-list',\n title: 'Task List',\n iconFont: 'check_box',\n action: (tb) => tb.textarea.wrapSelection('\\n- [ ] ', ''),\n },\n ],\n [\n {\n type: 'table',\n title: 'Table',\n iconFont: 'table',\n action: toggleTablePopover,\n },\n {\n type: 'image',\n title: 'Image',\n iconFont: 'image',\n action: insertImage,\n },\n {\n type: 'link',\n title: 'Link',\n iconFont: 'link',\n action: insertLink,\n },\n ],\n [\n {\n type: 'code',\n title: 'Inline Code',\n iconFont: 'code',\n action: (tb) => tb.textarea.wrapSelection('`'),\n },\n {\n type: 'codeblock',\n title: 'Code Block',\n iconFont: 'code_blocks',\n action: (tb) => tb.textarea.insertTextAtCursor('\\n```\\ncode\\n```\\n'),\n },\n ],\n];\n","import { CommonModule } from '@angular/common';\nimport {\n Component,\n EventEmitter,\n input,\n Input,\n InputSignal,\n Output,\n ViewChild,\n ViewContainerRef,\n} from '@angular/core';\nimport { MatIconModule } from '@angular/material/icon';\nimport { PopoverComponent, PopoverField } from './components/popover/popover.component';\nimport { DEFAULT_TOOLBAR_GROUPS, ToolbarItem } from './toolbar-items';\n\nexport interface MarkdownEditorTextarea {\n insertTextAtCursor(text: string): void;\n wrapSelection(before: string, after?: string): void;\n getSelectedText(): string;\n copyToClipboard(): void;\n}\n\n/**\n * NgxMarkdownToolbarComponent\n *\n * This component renders a set of markdown formatting tools as a toolbar.\n * It emits events to toggle scroll syncing and preview.\n * It accepts a reference to the NgxMarkdownTextareaComponent to apply formatting.\n */\n@Component({\n selector: 'plusify-markdown-toolbar',\n templateUrl: './markdown-toolbar.component.html',\n styleUrls: ['./markdown-toolbar.component.scss'],\n imports: [CommonModule, MatIconModule],\n})\nexport class NgxMarkdownToolbarComponent {\n /**\n * Groups of toolbar buttons to render.\n */\n itemGroups: ToolbarItem[][] = DEFAULT_TOOLBAR_GROUPS;\n\n /**\n * Reference to the textarea component.\n * It must implement insertTextAtCursor, wrapSelection, getSelectedText and copyToClipboard methods.\n */\n @Input() textarea!: MarkdownEditorTextarea;\n\n /**\n * Whether sync scroll is enabled.\n */\n syncScroll: InputSignal<boolean> = input<boolean>(true);\n\n /**\n * Whether preview panel is visible.\n */\n showPreview: InputSignal<boolean> = input<boolean>(true);\n\n /**\n * Emits when sync scroll is toggled.\n */\n @Output()\n toggleSyncScroll = new EventEmitter<boolean>();\n\n /**\n * Emits when preview panel is toggled.\n */\n @Output()\n togglePreview = new EventEmitter<boolean>();\n\n /**\n * Placeholder container for popovers (injected dynamically).\n */\n @ViewChild('popoverHost', { read: ViewContainerRef })\n popoverHost!: ViewContainerRef;\n\n /**\n * Handles click on any toolbar item.\n * @param item The toolbar item.\n * @param anchor The DOM element clicked.\n */\n onClick(item: ToolbarItem, anchor: HTMLElement) {\n if (item.action) {\n item.action(this, anchor);\n }\n }\n\n /**\n * Opens a popover with specific fields depending on the type.\n * @param type Type of popover (image, link, heading)\n * @param anchor DOM element to anchor the popover to\n * @returns A promise that resolves with the submitted fields\n */\n async openPopover(\n type: string,\n anchor: HTMLElement,\n ): Promise<Record<string, string> | string | null> {\n const fields = this.buildPopoverFields(type);\n\n return new Promise((resolve) => {\n this.popoverHost.clear();\n const ref = this.popoverHost.createComponent(PopoverComponent);\n\n ref.setInput('anchor', anchor);\n ref.setInput('type', type);\n ref.setInput('fields', fields);\n\n ref.instance.submit.subscribe((result) => {\n resolve(result);\n ref.destroy();\n });\n\n ref.instance.cancel.subscribe(() => {\n resolve(null);\n ref.destroy();\n });\n });\n }\n\n /**\n * Returns a list of fields to show in the popover, based on the type.\n */\n private buildPopoverFields(type: string): PopoverField[] {\n switch (type) {\n case 'image':\n return [\n { key: 'url', label: 'Image URL', placeholder: 'https://...' },\n { key: 'alt', label: 'Alt text', placeholder: 'Description' },\n ];\n case 'link':\n return [\n { key: 'url', label: 'Link URL', placeholder: 'https://...' },\n { key: 'text', label: 'Link text', placeholder: 'Text to display' },\n ];\n case 'heading':\n return [\n {\n key: 'level',\n label: 'Heading level',\n type: 'menu',\n options: Array.from({ length: 6 }, (_, i) => ({\n label: `Heading ${i + 1}`,\n value: String(i + 1),\n })),\n },\n ];\n default:\n return [];\n }\n }\n}\n","<div class=\"markdown-toolbar\">\n <!-- Render toolbar button groups -->\n @for (itemGroup of itemGroups; track $index) {\n <div class=\"toolbar-group\">\n @for (item of itemGroup; track item.type) {\n <button class=\"toolbar-button\" [title]=\"item.title\" #btnRef (click)=\"onClick(item, btnRef)\">\n @if (item.iconFont) {\n <span class=\"material-symbols-outlined material-symbols-rounded\">\n {{ item.iconFont }}\n </span>\n } @else if (item.iconHtml) {\n <span [innerHTML]=\"item.iconHtml\"></span>\n } @else {\n <span>\n {{ item.icon || item.type }}\n </span>\n }\n </button>\n }\n </div>\n }\n\n <!-- Preview & sync toggle -->\n <div class=\"toolbar-options\">\n <div class=\"toolbar-toggle\">\n <label for=\"syncScroll\">Scroll</label>\n <input\n id=\"syncScroll\"\n type=\"checkbox\"\n [checked]=\"syncScroll()\"\n (change)=\"toggleSyncScroll.emit($event.target.checked)\" />\n </div>\n <div class=\"toolbar-toggle\">\n <label for=\"showPreview\">Preview</label>\n <input\n id=\"showPreview\"\n type=\"checkbox\"\n [checked]=\"showPreview()\"\n (change)=\"togglePreview.emit($event.target.checked)\" />\n </div>\n </div>\n\n <!-- Popover container -->\n <ng-template #popoverHost></ng-template>\n</div>\n","import { MarkdownEditorConfig } from \"../types/config.interface\";\n\nexport const DEFAULT_EDITOR_CONFIG: MarkdownEditorConfig = {\n showPreview: true,\n showToolbar: true,\n syncScroll: true,\n value: '',\n readonly: false,\n disableSanitizer: false,\n};\n","import { CommonModule } from '@angular/common';\nimport {\n ChangeDetectionStrategy,\n Component,\n effect,\n EventEmitter,\n HostBinding,\n input,\n Input,\n InputSignal,\n output,\n Output,\n ViewChild,\n} from '@angular/core';\nimport { swingFromRight } from '../animations/swing.animation';\nimport { NgxMarkdownPreviewComponent } from '../components/preview/preview.component';\nimport { NgxMarkdownTextareaComponent } from '../components/textarea/markdown-textarea.component';\nimport { NgxMarkdownToolbarComponent } from '../components/toolbar/markdown-toolbar.component';\nimport { DEFAULT_EDITOR_CONFIG } from '../config/default-editor-config';\nimport { MarkdownEditorConfig } from '../types/config.interface';\n\n/**\n * NgxMarkdownEditorComponent\n *\n * A customizable markdown editor component with toolbar and live preview.\n * Allows theming via CSS variables and accepts an optional config input.\n */\n@Component({\n selector: 'plusify-markdown-editor',\n templateUrl: './markdown-editor.component.html',\n styleUrls: ['./markdown-editor.component.scss'],\n animations: [swingFromRight],\n imports: [\n CommonModule,\n NgxMarkdownToolbarComponent,\n NgxMarkdownTextareaComponent,\n NgxMarkdownPreviewComponent,\n ],\n standalone: true,\n changeDetection: ChangeDetectionStrategy.OnPush,\n})\n/**\n * The `NgxMarkdownEditorComponent` is a customizable Markdown editor component\n * designed for Angular applications. It provides features such as live preview,\n * toolbar controls, and configurable themes. The component allows for seamless\n * integration with Markdown content and offers flexibility through various inputs\n * and outputs.\n *\n * ### Features:\n * - **Markdown Content Management**: Bind and update Markdown content using the `value` input and `valueChange` output.\n * - **Theme Customization**: Apply custom CSS variables via the `customTheme` input.\n * - **Configurable Behavior**: Control editor settings like preview visibility, toolbar display, scroll synchronization, and sanitizer usage.\n * - **Dynamic Styling**: Automatically bind theme classes and styles to the host element.\n *\n * ### Inputs:\n * - `value`: The main Markdown content value.\n * - `customTheme`: A record of CSS variables for theme customization.\n * - `themeClass`: A class applied to the host element for theme styling.\n * - `config`: Configuration options for the editor, such as initial values and readonly mode.\n * - `showPreviewInput`: Controls the visibility of the preview pane.\n * - `showToolbarInput`: Controls the visibility of the toolbar.\n * - `syncScrollInput`: Enables or disables synchronized scrolling between the editor and preview.\n * - `disableSanitizerInput`: Disables the sanitizer for Markdown content.\n *\n * ### Outputs:\n * - `valueChange`: Emits the updated Markdown content whenever changes occur.\n *\n * ### Methods:\n * - `handleValueChange(value: string)`: Handles changes from the internal textarea and updates the `value` property.\n * - `handleTogglePreview(value: boolean)`: Toggles the visibility of the preview pane.\n * - `handleToggleSyncScroll(value: boolean)`: Toggles synchronized scrolling between the editor and preview.\n *\n * ### Host Bindings:\n * - `hostClass`: Dynamically binds the `themeClass` to the host element.\n * - `hostStyle`: Dynamically binds style variables for theme customization to the host element.\n *\n * ### Internal Behavior:\n * - Synchronizes configuration inputs with internal signals using reactive effects.\n * - Merges default configuration with user-provided settings.\n * - Provides default theme styles and allows overriding via `customTheme`.\n *\n * This component is ideal for applications requiring a rich Markdown editing experience\n * with customizable themes and behavior.\n */\nexport class NgxMarkdownEditorComponent {\n /**\n * Ref to the internal textarea component.\n */\n @ViewChild('textareaRef')\n textareaRef!: NgxMarkdownTextareaComponent;\n\n @ViewChild('markdownPreview')\n previewRef!: NgxMarkdownPreviewComponent;\n\n private isSyncing = false;\n\n /**\n * Main markdown content value.\n */\n @Input()\n value = '';\n\n @Output()\n valueChange = new EventEmitter<string>();\n\n /**\n * Configuration input (initialValue, readonly, etc.)\n */\n public config: InputSignal<MarkdownEditorConfig> = input<MarkdownEditorConfig>({});\n\n /**\n * Controls the visibility of the preview pane.\n * @default true\n */\n public showPreviewInput: InputSignal<boolean> = input<boolean>(true, { alias: 'showPreview' });\n\n /**\n * Output signal for preview visibility changes.\n * Emits a boolean indicating whether the preview is shown or hidden.\n */\n public showPreviewOutput = output<boolean>();\n\n /**\n * Controls the synchronization of scrolling between the editor and preview.\n * @default true\n */\n public syncScrollInput: InputSignal<boolean> = input<boolean>(true, { alias: 'syncScroll' });\n\n /**\n * Output signal for scroll synchronization changes.\n * Emits a boolean indicating whether scroll synchronization is enabled or disabled.\n */\n public syncScrollOutput = output<boolean>();\n\n /**\n * Controls the visibility of the toolbar.\n * @default true\n */\n public showToolbarInput: InputSignal<boolean> = input<boolean>(true, { alias: 'showToolbar' });\n\n\n /**\n * Theme customization input (CSS variables).\n */\n @Input()\n customTheme: Record<string, string> = {};\n\n /**\n * Host theme class.\n */\n @Input()\n themeClass = '';\n\n @HostBinding('class')\n get hostClass() {\n return this.themeClass;\n }\n\n /**\n * Style variables bound to host.\n */\n @HostBinding('style')\n get hostStyle(): Record<string, string> {\n const defaults = {\n '--color-primary': '#009b77',\n '--color-on-primary': '#ffffff',\n '--color-outline': '#e0e0e0',\n '--color-on-surface': '#222',\n '--color-on-surface-variant': '#666',\n '--color-primary-hover': '#007b5e',\n };\n return { ...defaults, ...this.customTheme };\n }\n\n /**\n * Internal config used after merging with defaults.\n */\n internalConfig: MarkdownEditorConfig = { ...DEFAULT_EDITOR_CONFIG };\n\n constructor() {\n // Sincronización de config e inputs\n effect(() => {\n this.internalConfig = {\n ...DEFAULT_EDITOR_CONFIG,\n showPreview: this.showPreviewInput() ?? true,\n syncScroll: this.syncScrollInput() ?? true,\n showToolbar: this.showToolbarInput() ?? true,\n ...this.config(),\n };\n\n if (this.internalConfig.value) {\n this.setValue(this.internalConfig.value);\n }\n });\n }\n\n /**\n * Sets the initial editor value.\n */\n private setValue(value: string) {\n this.value = value || '';\n }\n\n /**\n * Handles changes from internal textarea.\n */\n handleValueChange(value: string) {\n this.value = value;\n this.valueChange.emit(value);\n }\n\n /**\n * Handlers from toolbar events.\n */\n handleTogglePreview(value: boolean) {\n this.internalConfig.showPreview = value;\n this.showPreviewOutput.emit(value);\n }\n\n /**\n * Toggles the synchronization of scrolling between the editor and preview.\n *\n * @param value - A boolean indicating whether to enable or disable sync scrolling.\n */\n handleToggleSyncScroll(value: boolean) {\n this.internalConfig.syncScroll = value;\n this.syncScrollOutput.emit(value);\n }\n\n /**\n * Handles the scroll synchronization between the textarea and the preview pane.\n * If syncScroll is disabled or a synchronization is already in progress, the method exits early.\n *\n * @param ratio - The scroll ratio of the textarea, typically a value between 0 and 1.\n * This ratio is used to calculate the corresponding scroll position in the preview pane.\n */\n handleTextareaScroll(ratio: number) {\n if (!this.internalConfig.syncScroll || this.isSyncing) return;\n\n this.isSyncing = true;\n this.previewRef?.scrollTo(ratio);\n requestAnimationFrame(() => (this.isSyncing = false));\n }\n\n /**\n * Handles the synchronization of scrolling between the preview and the textarea.\n * If the `syncScroll` configuration is disabled or a scroll synchronization is already in progress,\n * the method exits early.\n *\n * @param ratio - The scroll ratio (a value between 0 and 1) representing the position\n * of the preview scroll relative to its total height.\n */\n handlePreviewScroll(ratio: number) {\n if (!this.internalConfig.syncScroll || this.isSyncing) return;\n\n this.isSyncing = true;\n this.textareaRef?.scrollTo(ratio);\n requestAnimationFrame(() => (this.isSyncing = false));\n }\n}\n","<div class=\"plusify-markdown-editor\">\n @if (this.internalConfig.showToolbar) {\n <plusify-markdown-toolbar\n [textarea]=\"textareaRef\"\n [showPreview]=\"this.internalConfig.showPreview\"\n [syncScroll]=\"this.internalConfig.showToolbar\"\n (toggleSyncScroll)=\"handleToggleSyncScroll($event)\"\n (togglePreview)=\"handleTogglePreview($event)\">\n </plusify-markdown-toolbar>\n }\n\n <div class=\"d-flex flex-row flex-1\">\n <div class=\"flex-1\">\n <plusify-markdown-textarea\n #textareaRef\n [(value)]=\"value\"\n [readonly]=\"internalConfig.readonly\"\n (valueChange)=\"handleValueChange($event)\"\n (scrolled)=\"handleTextareaScroll($event)\">\n </plusify-markdown-textarea>\n </div>\n\n @if (this.internalConfig.showPreview) {\n <div class=\"flex-1\" @swingFromRight>\n <plusify-markdown-preview\n #markdownPreview\n [content]=\"value\"\n [disableSanitizer]=\"this.internalConfig.disableSanitizer\"\n (scrolled)=\"handlePreviewScroll($event)\">\n </plusify-markdown-preview>\n </div>\n }\n </div>\n</div>\n","/*\n * Public API Surface of ngx-markdown-editor\n */\n\nexport * from './lib/editor/markdown-editor.component';\n","/**\n * Generated bundle index. Do not edit.\n */\n\nexport * from './public-api';\n"],"names":["i1"],"mappings":"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