@duskmoon-dev/el-markdown-input 0.8.1 → 0.8.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cjs/index.js +1214 -0
- package/dist/cjs/index.js.map +16 -0
- package/dist/cjs/register.js +1184 -0
- package/dist/cjs/register.js.map +16 -0
- package/dist/esm/index.js +1189 -0
- package/dist/esm/index.js.map +16 -0
- package/dist/esm/register.js +1161 -0
- package/dist/esm/register.js.map +16 -0
- package/dist/tsconfig.tsbuildinfo +1 -0
- package/dist/types/autocomplete.d.ts +45 -0
- package/dist/types/autocomplete.d.ts.map +1 -0
- package/dist/types/css.d.ts +10 -0
- package/dist/types/css.d.ts.map +1 -0
- package/dist/types/element.d.ts +104 -0
- package/dist/types/element.d.ts.map +1 -0
- package/dist/types/highlight.d.ts +33 -0
- package/dist/types/highlight.d.ts.map +1 -0
- package/dist/types/index.d.ts +67 -0
- package/dist/types/index.d.ts.map +1 -0
- package/dist/types/register.d.ts +2 -0
- package/dist/types/register.d.ts.map +1 -0
- package/dist/types/status-bar.d.ts +25 -0
- package/dist/types/status-bar.d.ts.map +1 -0
- package/dist/types/types.d.ts +12 -0
- package/dist/types/types.d.ts.map +1 -0
- package/dist/types/upload.d.ts +26 -0
- package/dist/types/upload.d.ts.map +1 -0
- package/package.json +2 -2
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../src/css.ts", "../../src/highlight.ts", "../../src/upload.ts", "../../src/autocomplete.ts", "../../src/status-bar.ts", "../../src/element.ts", "../../src/index.ts"],
|
|
4
|
+
"sourcesContent": [
|
|
5
|
+
"import { css } from '@duskmoon-dev/el-base';\n\n/**\n * Shadow DOM stylesheet for el-dm-markdown-input.\n *\n * Exposes --md-* custom properties as the external theming API.\n * Each --md-* variable falls back to the corresponding --color-* token\n * from @duskmoon-dev/core so the element automatically adopts the\n * active design-system theme without any consumer configuration.\n */\nexport const elementStyles = css`\n /* ── Custom property defaults with design-system fallbacks ─────────── */\n :host {\n --md-border: var(--color-outline, #d0d7de);\n --md-border-focus: var(--color-primary, #0969da);\n --md-bg: var(--color-surface, #ffffff);\n --md-bg-toolbar: var(--color-surface-variant, #f6f8fa);\n --md-bg-hover: var(--color-surface-container, #eaeef2);\n --md-text: var(--color-on-surface, #1f2328);\n --md-text-muted: var(--color-on-surface-variant, #656d76);\n --md-accent: var(--color-primary, #0969da);\n --md-radius: 6px;\n --md-upload-bar: var(--color-primary, #0969da);\n --md-color-warning: var(--color-warning, #d97706);\n --md-color-error: var(--color-error, #dc2626);\n\n display: block;\n position: relative; /* establishes containing block for the ac-dropdown portal */\n font-family: inherit;\n }\n\n :host([hidden]) {\n display: none !important;\n }\n\n /* Dark-mode overrides (activated by [dark] attribute on host) */\n :host([dark]) {\n --md-border: #30363d;\n --md-border-focus: #58a6ff;\n --md-bg: #0d1117;\n --md-bg-toolbar: #161b22;\n --md-bg-hover: #21262d;\n --md-text: #e6edf3;\n --md-text-muted: #8b949e;\n --md-accent: #58a6ff;\n --md-upload-bar: #58a6ff;\n --md-color-warning: #f59e0b;\n --md-color-error: #fca5a5;\n }\n\n /* ── Editor chrome ──────────────────────────────────────────────────── */\n .editor {\n display: flex;\n flex-direction: column;\n border: 1px solid var(--md-border);\n border-radius: var(--md-radius);\n background: var(--md-bg);\n color: var(--md-text);\n overflow: hidden;\n }\n\n .editor:focus-within {\n border-color: var(--md-border-focus);\n outline: 2px solid var(--md-border-focus);\n outline-offset: -1px;\n }\n\n /* ── Toolbar / tab bar ──────────────────────────────────────────────── */\n .toolbar {\n display: flex;\n gap: 0;\n background: var(--md-bg-toolbar);\n border-bottom: 1px solid var(--md-border);\n padding: 0 0.5rem;\n }\n\n .tab-btn {\n padding: 0.5rem 0.875rem;\n border: none;\n background: transparent;\n color: var(--md-text-muted);\n font-family: inherit;\n font-size: 0.875rem;\n font-weight: 500;\n cursor: pointer;\n border-bottom: 2px solid transparent;\n margin-bottom: -1px;\n transition:\n color 150ms ease,\n border-color 150ms ease;\n }\n\n .tab-btn:hover {\n color: var(--md-text);\n background: var(--md-bg-hover);\n }\n\n .tab-btn[aria-selected='true'] {\n color: var(--md-text);\n border-bottom-color: var(--md-accent);\n }\n\n .tab-btn:focus-visible {\n outline: 2px solid var(--md-accent);\n outline-offset: -2px;\n border-radius: 3px;\n }\n\n /* ── Write area (backdrop + textarea overlay) ───────────────────────── */\n .write-area {\n position: relative;\n min-height: 12rem;\n flex: 1;\n }\n\n /*\n * Backdrop: renders syntax-highlighted HTML behind the transparent textarea.\n * Must share IDENTICAL font metrics with the textarea to stay pixel-aligned.\n */\n .backdrop {\n position: absolute;\n inset: 0;\n pointer-events: none;\n /*\n * Use overflow: auto (not overflow: hidden) so the backdrop reserves\n * the same scrollbar gutter as the textarea when content overflows.\n * Without this, the textarea scrollbar narrows its text area but the\n * backdrop stays full-width — lines wrap at different points — causing\n * the cursor to appear misaligned with the highlighted text.\n */\n overflow: auto;\n scrollbar-width: none; /* Firefox */\n border: none;\n background: transparent;\n /*\n * Do NOT put white-space: pre-wrap here. The backdrop div contains a\n * backdrop-content child, and the HTML template has whitespace text\n * nodes (newline + indent) between them. With pre-wrap on the parent\n * those text nodes render as a visible leading newline, shifting all\n * content down by one line and misaligning the cursor vertically.\n * pre-wrap lives on .backdrop-content instead.\n */\n\n font-family: ui-monospace, 'SFMono-Regular', Consolas, 'Liberation Mono', Menlo, monospace;\n font-size: 0.875rem;\n line-height: 1.6;\n padding: 0.75rem;\n color: var(--md-text);\n }\n\n .backdrop::-webkit-scrollbar {\n display: none; /* Chrome / Safari */\n }\n\n .backdrop-content {\n display: block;\n white-space: pre-wrap;\n word-wrap: break-word;\n overflow-wrap: break-word;\n /* Prism token colours are injected via a separate <style id=\"prism-theme\"> */\n }\n\n textarea {\n position: relative;\n display: block;\n width: 100%;\n min-height: 12rem;\n border: none;\n outline: none;\n resize: vertical;\n background: transparent;\n color: transparent;\n caret-color: var(--md-text);\n box-sizing: border-box;\n\n /* MUST match .backdrop exactly */\n font-family: ui-monospace, 'SFMono-Regular', Consolas, 'Liberation Mono', Menlo, monospace;\n font-size: 0.875rem;\n line-height: 1.6;\n padding: 0.75rem;\n white-space: pre-wrap;\n word-wrap: break-word;\n overflow-wrap: break-word;\n }\n\n textarea::placeholder {\n color: var(--md-text-muted);\n }\n\n textarea:disabled {\n cursor: not-allowed;\n opacity: 0.6;\n }\n\n /* ── Preview panel ──────────────────────────────────────────────────── */\n .preview-body {\n padding: 0.75rem;\n min-height: 12rem;\n overflow-y: auto;\n color: var(--md-text);\n /* .markdown-body styles come from @duskmoon-dev/core via the element */\n }\n\n /* ── Status bar ─────────────────────────────────────────────────────── */\n .status-bar {\n display: flex;\n align-items: center;\n justify-content: space-between;\n padding: 0.375rem 0.75rem;\n border-top: 1px solid var(--md-border);\n background: var(--md-bg-toolbar);\n font-size: 0.75rem;\n color: var(--md-text-muted);\n gap: 0.5rem;\n }\n\n .attach-btn {\n display: inline-flex;\n align-items: center;\n gap: 0.25rem;\n padding: 0.25rem 0.5rem;\n border: none;\n background: transparent;\n color: var(--md-text-muted);\n font-family: inherit;\n font-size: 0.75rem;\n cursor: pointer;\n border-radius: 4px;\n transition:\n color 150ms ease,\n background 150ms ease;\n }\n\n .attach-btn:hover {\n color: var(--md-text);\n background: var(--md-bg-hover);\n }\n\n .attach-btn:focus-visible {\n outline: 2px solid var(--md-accent);\n outline-offset: 1px;\n }\n\n .status-bar-count {\n margin-left: auto;\n white-space: nowrap;\n }\n\n .status-bar-count .warning {\n color: var(--md-color-warning);\n }\n\n .status-bar-count .error {\n color: var(--md-color-error);\n }\n\n .file-input {\n display: none;\n }\n\n /* ── Autocomplete dropdown ──────────────────────────────────────────── */\n /*\n * The dropdown is a direct child of :host (outside .editor) so it is not\n * clipped by .editor's overflow: hidden. :host has position: relative which\n * establishes the containing block for this absolute positioning.\n */\n .ac-dropdown {\n position: absolute;\n z-index: 100;\n left: 0.75rem;\n /* Align to bottom of the editor chrome; the editor fills 100% of :host height */\n bottom: calc(var(--md-status-bar-height, 2rem) + 4px);\n min-width: 16rem;\n max-width: 28rem;\n max-height: 16rem;\n overflow-y: auto;\n margin: 0;\n padding: 0.25rem 0;\n list-style: none;\n background: var(--md-bg);\n border: 1px solid var(--md-border);\n border-radius: var(--md-radius);\n box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);\n }\n\n .ac-item {\n display: flex;\n flex-direction: column;\n padding: 0.5rem 0.75rem;\n cursor: pointer;\n transition: background 100ms ease;\n }\n\n .ac-item:hover,\n .ac-item[aria-selected='true'] {\n background: var(--md-bg-hover);\n }\n\n .ac-item-label {\n font-size: 0.875rem;\n color: var(--md-text);\n font-weight: 500;\n }\n\n .ac-item-subtitle {\n font-size: 0.75rem;\n color: var(--md-text-muted);\n margin-top: 1px;\n }\n\n /* ── Upload progress rows ───────────────────────────────────────────── */\n .upload-list {\n display: flex;\n flex-direction: column;\n gap: 0;\n }\n\n .upload-row {\n display: flex;\n align-items: center;\n gap: 0.5rem;\n padding: 0.375rem 0.75rem;\n border-top: 1px solid var(--md-border);\n background: var(--md-bg-toolbar);\n font-size: 0.75rem;\n color: var(--md-text-muted);\n }\n\n .upload-filename {\n flex: 1;\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n }\n\n .upload-bar-track {\n width: 6rem;\n height: 3px;\n background: var(--md-border);\n border-radius: 2px;\n overflow: hidden;\n flex-shrink: 0;\n }\n\n .upload-bar {\n height: 100%;\n background: var(--md-upload-bar);\n border-radius: 2px;\n transition: width 150ms ease;\n }\n\n .upload-error-row {\n display: flex;\n align-items: center;\n gap: 0.5rem;\n padding: 0.375rem 0.75rem;\n border-top: 1px solid var(--md-border);\n background: oklch(97% 0.02 25);\n color: var(--md-color-error);\n font-size: 0.75rem;\n }\n\n :host([dark]) .upload-error-row {\n background: oklch(20% 0.03 25);\n }\n\n .upload-error-msg {\n flex: 1;\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n }\n`;\n",
|
|
6
|
+
"/**\n * Prism.js CDN loader + backdrop highlight utilities.\n *\n * Prism is loaded lazily as a UMD script from cdnjs. A module-level Promise\n * caches the load so multiple elements on the same page share one request.\n * If the CDN is unavailable the element degrades gracefully — text entry and\n * form submission continue to work, just without syntax colouring.\n */\n\ndeclare global {\n interface Window {\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n Prism?: any;\n }\n}\n\nconst PRISM_BASE = 'https://cdnjs.cloudflare.com/ajax/libs/prism/1.29.0';\nconst PRISM_CORE_URL = `${PRISM_BASE}/prism.min.js`;\nconst PRISM_AUTOLOADER_URL = `${PRISM_BASE}/plugins/autoloader/prism-autoloader.min.js`;\nconst PRISM_THEME_DARK_URL = `${PRISM_BASE}/themes/prism-tomorrow.min.css`;\nconst PRISM_THEME_LIGHT_URL = `${PRISM_BASE}/themes/prism-coy.min.css`;\n\n/** Cached load promise — shared across all instances on the page. */\nlet _prismReady: Promise<void> | null = null;\n\n/** Inject a script tag into document.head and resolve when loaded. */\nfunction _loadScript(src: string): Promise<void> {\n return new Promise((resolve) => {\n const script = document.createElement('script');\n script.src = src;\n script.onload = () => resolve();\n script.onerror = () => resolve(); // resolve even on error (graceful degrade)\n document.head.appendChild(script);\n });\n}\n\n/**\n * Ensure Prism is loaded and ready. Returns a cached Promise after the first call.\n * Safe to call multiple times — only one network request is ever made.\n */\nexport function ensurePrism(): Promise<void> {\n if (window.Prism) return Promise.resolve();\n if (_prismReady) return _prismReady;\n\n _prismReady = _loadScript(PRISM_CORE_URL).then(() => {\n if (!window.Prism) return;\n // Configure autoloader before loading it\n window.Prism.manual = true;\n return _loadScript(PRISM_AUTOLOADER_URL).then(() => {\n if (window.Prism?.plugins?.autoloader) {\n window.Prism.plugins.autoloader.languages_path = `${PRISM_BASE}/components/`;\n }\n });\n });\n\n return _prismReady;\n}\n\n/**\n * Escape HTML special characters in the given text.\n * Escapes & first to prevent double-escaping.\n */\nfunction escapeHtml(text: string): string {\n return text.replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>');\n}\n\n/**\n * Highlight markdown text using Prism and return an HTML string.\n * If Prism is not available, returns the HTML-escaped text unchanged.\n *\n * Appends a non-breaking space to prevent the backdrop div from collapsing\n * when the textarea value ends with a newline.\n */\nexport function highlightMarkdown(text: string): string {\n const escaped = escapeHtml(text);\n\n if (!window.Prism?.languages?.markdown) {\n // Prism not ready yet — return escaped plain text\n return escaped + '\\u00a0';\n }\n\n try {\n const highlighted = window.Prism.highlight(text, window.Prism.languages.markdown, 'markdown');\n return highlighted + '\\u00a0';\n } catch {\n return escaped + '\\u00a0';\n }\n}\n\n/**\n * Inject or update a Prism syntax theme inside the given shadow root.\n * Uses a <style id=\"prism-theme\"> element with an @import so the browser\n * caches the CDN stylesheet normally.\n */\nexport function applyPrismTheme(shadowRoot: ShadowRoot, dark: boolean): void {\n const themeUrl = dark ? PRISM_THEME_DARK_URL : PRISM_THEME_LIGHT_URL;\n let styleEl = shadowRoot.getElementById('prism-theme') as HTMLStyleElement | null;\n\n if (!styleEl) {\n styleEl = document.createElement('style');\n styleEl.id = 'prism-theme';\n shadowRoot.appendChild(styleEl);\n }\n\n const expected = `@import url(\"${themeUrl}\");`;\n if (styleEl.textContent !== expected) {\n styleEl.textContent = expected;\n }\n}\n",
|
|
7
|
+
"/**\n * File upload utilities for el-dm-markdown-input.\n *\n * Handles XHR-based multipart/form-data uploads, markdown snippet generation,\n * and accepted file type validation.\n */\n\n/** Accepted MIME types and extensions (aligned with PRD). */\nconst ACCEPTED_MIME_PREFIXES = ['image/'];\nconst ACCEPTED_MIME_EXACT = ['application/pdf'];\nconst ACCEPTED_EXTENSIONS = ['.zip', '.txt', '.csv', '.json', '.md'];\n\n/**\n * Returns true if the file's MIME type or name extension is accepted.\n */\nexport function isAcceptedType(file: File): boolean {\n const type = file.type.toLowerCase();\n if (ACCEPTED_MIME_PREFIXES.some((p) => type.startsWith(p))) return true;\n if (ACCEPTED_MIME_EXACT.includes(type)) return true;\n const name = file.name.toLowerCase();\n if (ACCEPTED_EXTENSIONS.some((ext) => name.endsWith(ext))) return true;\n return false;\n}\n\n/**\n * Generate the markdown insertion string for an uploaded file.\n * Images use ``, all other files use `[name](url)`.\n */\nexport function fileToMarkdown(file: File, url: string): string {\n if (file.type.startsWith('image/')) {\n return ``;\n }\n return `[${file.name}](${url})`;\n}\n\n/**\n * Upload a single file to the given URL via XHR POST multipart/form-data.\n *\n * @param file The file to upload\n * @param uploadUrl POST endpoint — must return `{ url: string }` on 2xx\n * @param onProgress Callback fired with progress 0–100 during upload\n * @returns Resolves with the URL from the server response\n * @throws Rejects with an error message string on failure\n */\nexport function uploadFile(\n file: File,\n uploadUrl: string,\n onProgress: (pct: number) => void,\n): Promise<string> {\n return new Promise((resolve, reject) => {\n const xhr = new XMLHttpRequest();\n const body = new FormData();\n body.append('file', file);\n\n xhr.upload.addEventListener('progress', (e) => {\n if (e.lengthComputable) {\n onProgress(Math.round((e.loaded / e.total) * 100));\n }\n });\n\n xhr.addEventListener('load', () => {\n if (xhr.status >= 200 && xhr.status < 300) {\n try {\n const data = JSON.parse(xhr.responseText) as { url?: string };\n if (data.url) {\n resolve(data.url);\n } else {\n reject('Upload response missing url field');\n }\n } catch {\n reject('Upload response is not valid JSON');\n }\n } else {\n reject(`Upload failed with status ${xhr.status}`);\n }\n });\n\n xhr.addEventListener('error', () => reject('Network error during upload'));\n xhr.addEventListener('abort', () => reject('Upload aborted'));\n\n xhr.open('POST', uploadUrl);\n xhr.send(body);\n });\n}\n",
|
|
8
|
+
"/**\n * Autocomplete utilities for @mention and #reference detection.\n */\n\nimport type { Suggestion } from './types.js';\n\n/**\n * Scan backward from cursorPos through value to detect an active @word or #word trigger.\n *\n * Rules:\n * - Scan character by character backward from cursor\n * - If a whitespace or newline is encountered before a trigger char → no trigger\n * - If @ or # is found → extract the query (text between trigger and cursor)\n * - The query must not contain any whitespace\n *\n * @returns Trigger info or null if no active trigger\n */\nexport function detectTrigger(\n value: string,\n cursorPos: number,\n): { trigger: '@' | '#'; query: string; triggerPos: number } | null {\n let i = cursorPos - 1;\n\n while (i >= 0) {\n const ch = value[i];\n\n if (ch === '@' || ch === '#') {\n const query = value.slice(i + 1, cursorPos);\n // Only activate if there's no whitespace in the query\n if (!/\\s/.test(query)) {\n // Make sure the char before the trigger is whitespace, start-of-string, or trigger is at position 0\n const before = i > 0 ? value[i - 1] : null;\n if (before === null || /[\\s\\n]/.test(before)) {\n return { trigger: ch as '@' | '#', query, triggerPos: i };\n }\n }\n return null;\n }\n\n if (/[\\s\\n]/.test(ch)) {\n // Hit whitespace before finding a trigger\n return null;\n }\n\n i--;\n }\n\n return null;\n}\n\n/**\n * Replace the trigger+query span in value with the confirmed replacement.\n *\n * For a mention: `@ali` → `@asmith` (trigger is preserved in replacement)\n * The replacement value should NOT include the trigger prefix — we add it.\n *\n * @param value Full textarea value\n * @param triggerPos Index of the @ or # character\n * @param cursorPos Current cursor position (end of query)\n * @param trigger The trigger character used\n * @param replacement The id from the selected suggestion\n * @returns New value and new cursor position\n */\nexport function confirmSuggestion(\n value: string,\n triggerPos: number,\n cursorPos: number,\n trigger: '@' | '#',\n replacement: string,\n): { newValue: string; newCursorPos: number } {\n const before = value.slice(0, triggerPos);\n const after = value.slice(cursorPos);\n const inserted = `${trigger}${replacement}`;\n const newValue = before + inserted + after;\n const newCursorPos = triggerPos + inserted.length;\n return { newValue, newCursorPos };\n}\n\n/**\n * Render the HTML for the autocomplete dropdown list.\n *\n * @param suggestions Current suggestion list\n * @param selectedIndex 0-based highlighted item (-1 = none)\n */\nexport function renderDropdown(suggestions: Suggestion[], selectedIndex: number): string {\n if (suggestions.length === 0) return '';\n\n const items = suggestions\n .map((s, i) => {\n const selected = i === selectedIndex ? ' aria-selected=\"true\"' : ' aria-selected=\"false\"';\n const subtitle = s.subtitle\n ? `<span class=\"ac-item-subtitle\">${escapeHtml(s.subtitle)}</span>`\n : '';\n return `<li id=\"ac-item-${i}\" class=\"ac-item\" role=\"option\" data-ac-index=\"${i}\"${selected}>\n <span class=\"ac-item-label\">${escapeHtml(s.label)}</span>${subtitle}\n </li>`;\n })\n .join('');\n\n return items;\n}\n\nfunction escapeHtml(text: string): string {\n return text.replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>');\n}\n",
|
|
9
|
+
"/**\n * Status bar utilities: word/character counting and colour thresholds.\n */\n\n/**\n * Count words in a string using the algorithm specified in the PRD:\n * trim, split on whitespace, filter empty strings.\n */\nexport function countWords(text: string): number {\n if (!text.trim()) return 0;\n return text.trim().split(/\\s+/).filter(Boolean).length;\n}\n\n/**\n * Determine the CSS colour class for the word count display.\n *\n * @param wordCount Current word count\n * @param maxWords The configured maximum, or null if uncapped\n * @returns 'normal' | 'warning' | 'error'\n */\nexport function countColour(\n wordCount: number,\n maxWords: number | null,\n): 'normal' | 'warning' | 'error' {\n if (!maxWords) return 'normal';\n const pct = (wordCount / maxWords) * 100;\n if (pct >= 100) return 'error';\n if (pct >= 90) return 'warning';\n return 'normal';\n}\n\n/**\n * Render the HTML content for the .status-bar-count span.\n *\n * @param wordCount Current word count\n * @param charCount Current character count\n * @param maxWords Configured cap, or null/undefined if uncapped\n */\nexport function renderStatusCount(\n wordCount: number,\n charCount: number,\n maxWords: number | null | undefined,\n): string {\n const cap = maxWords ?? null;\n const colour = countColour(wordCount, cap);\n const colourClass = colour !== 'normal' ? ` class=\"${colour}\"` : '';\n\n if (cap) {\n return `<span${colourClass}>${wordCount} / ${cap} words · ${charCount} chars</span>`;\n }\n return `<span>${wordCount} words · ${charCount} chars</span>`;\n}\n",
|
|
10
|
+
"/**\n * DuskMoon Markdown Input Element\n *\n * A form-associated custom element providing a markdown editor with:\n * - Write tab with syntax-highlighted backdrop (Prism.js CDN)\n * - Preview tab with rendered HTML (.markdown-body from @duskmoon-dev/core)\n * - File upload via drag-and-drop, clipboard paste, or file picker\n * - @mention / #reference autocomplete dropdown\n * - Live word / character count status bar\n * - Phoenix LiveView hook (MarkdownInputHook, exported from index.ts)\n *\n * @element el-dm-markdown-input\n *\n * @attr {string} name Form field name\n * @attr {string} value Initial markdown content\n * @attr {string} placeholder Textarea placeholder (default: \"Write markdown…\")\n * @attr {boolean} disabled Disables editing\n * @attr {boolean} readonly Makes the editor read-only (value still submitted)\n * @attr {string} upload-url POST endpoint for file uploads\n * @attr {number} max-words Soft word cap shown in status bar\n * @attr {boolean} dark Activates dark Prism theme + dark CSS variable defaults\n *\n * @fires change `{ value: string }` — on every input\n * @fires upload-start `{ file: File }` — when a file is accepted\n * @fires upload-done `{ file: File, url: string, markdown: string }` — on success\n * @fires upload-error `{ file: File, error: string }` — on failure\n * @fires mention-query `{ trigger: \"@\", query, resolve }` — on @word input\n * @fires reference-query `{ trigger: \"#\", query, resolve }` — on #word input\n */\n\nimport { BaseElement } from '@duskmoon-dev/el-base';\nimport { css as markdownBodyCSS } from '@duskmoon-dev/core/components/markdown-body';\n\nimport { elementStyles } from './css.js';\nimport { ensurePrism, highlightMarkdown, applyPrismTheme } from './highlight.js';\nimport { uploadFile, fileToMarkdown, isAcceptedType } from './upload.js';\nimport { detectTrigger, confirmSuggestion, renderDropdown } from './autocomplete.js';\nimport { countWords, renderStatusCount } from './status-bar.js';\nimport type { Suggestion } from './types.js';\n\n// Strip @layer wrapper for Shadow DOM compatibility\nconst coreMarkdownStyles = markdownBodyCSS\n .replace(/@layer\\s+components\\s*\\{/, '')\n .replace(/\\}\\s*$/, '');\n\n// Inject core markdown-body styles as a constructable stylesheet\nimport { css } from '@duskmoon-dev/el-base';\nconst markdownBodySheet = css`\n ${coreMarkdownStyles}\n`;\n\nexport class ElDmMarkdownInput extends BaseElement {\n static formAssociated = true as const;\n\n static properties = {\n name: { type: String, reflect: true, default: '' },\n value: { type: String, default: '' },\n placeholder: { type: String, reflect: true, default: 'Write markdown\\u2026' },\n disabled: { type: Boolean, reflect: true },\n readonly: { type: Boolean, reflect: true },\n uploadUrl: { type: String, reflect: true, attribute: 'upload-url' },\n maxWords: { type: Number, reflect: true, attribute: 'max-words' },\n dark: { type: Boolean, reflect: true },\n };\n\n declare name: string;\n declare value: string;\n declare placeholder: string;\n declare disabled: boolean;\n declare readonly: boolean;\n declare uploadUrl: string | undefined;\n declare maxWords: number | undefined;\n declare dark: boolean;\n\n // ── ElementInternals for form association ────────────────────────────\n #internals!: ElementInternals;\n\n // ── Rendering state ──────────────────────────────────────────────────\n /** True after the first full render has populated the shadow DOM. */\n #initialized = false;\n\n /** Which tab is currently active. */\n #activeTab: 'write' | 'preview' = 'write';\n\n // ── Debounce timers ──────────────────────────────────────────────────\n #highlightTimer: ReturnType<typeof setTimeout> | null = null;\n #statusTimer: ReturnType<typeof setTimeout> | null = null;\n\n // ── DOM element refs (set after first render) ────────────────────────\n #textarea: HTMLTextAreaElement | null = null;\n #backdrop: HTMLElement | null = null;\n #backdropContent: HTMLElement | null = null;\n #writeArea: HTMLElement | null = null;\n #previewBody: HTMLElement | null = null;\n #statusCount: HTMLElement | null = null;\n #acDropdown: HTMLElement | null = null;\n #uploadList: HTMLElement | null = null;\n #fileInput: HTMLInputElement | null = null;\n\n // ── Resize observer ──────────────────────────────────────────────────\n #resizeObserver: ResizeObserver | null = null;\n\n // ── Autocomplete state ───────────────────────────────────────────────\n #acSuggestions: Suggestion[] = [];\n #acSelectedIndex = -1;\n #acTriggerPos = -1;\n #acTrigger: '@' | '#' | null = null;\n\n // ── Upload state ─────────────────────────────────────────────────────\n #uploadIdCounter = 0;\n\n constructor() {\n super();\n // attachInternals() must be called in the constructor per HTML spec\n this.#internals = this.attachInternals();\n this.attachStyles([elementStyles, markdownBodySheet]);\n }\n\n // ════════════════════════════════════════════════════════════════════\n // Lifecycle\n // ════════════════════════════════════════════════════════════════════\n\n connectedCallback(): void {\n super.connectedCallback(); // triggers initial update() → render()\n\n // Set initial form value from the reactive `value` property\n const initial = (this as unknown as { value: string }).value ?? '';\n if (initial && this.#textarea) {\n this.#textarea.value = initial;\n this.#syncFormValue();\n }\n }\n\n disconnectedCallback(): void {\n this.#resizeObserver?.disconnect();\n super.disconnectedCallback();\n }\n\n // ════════════════════════════════════════════════════════════════════\n // Rendering — override update() to protect the textarea after init\n // ════════════════════════════════════════════════════════════════════\n\n protected override update(): void {\n if (!this.#initialized) {\n // Full initial render — creates all DOM nodes\n super.update(); // calls render() → shadowRoot.innerHTML = content\n this.#initialized = true;\n this.#cacheDOMRefs();\n this.#attachEventHandlers();\n this.#initHighlight();\n this.#updateStatusBarNow();\n\n // Restore initial value from reactive prop\n const initVal = (this as unknown as { value: string }).value ?? '';\n if (initVal && this.#textarea) {\n this.#textarea.value = initVal;\n this.#syncFormValue();\n this.#scheduleHighlight();\n }\n return;\n }\n\n // Incremental update — patch only specific DOM regions\n this.#patchDynamicRegions();\n }\n\n /**\n * Patch DOM regions that can change due to reactive property updates,\n * without replacing the textarea (which would lose state).\n */\n #patchDynamicRegions(): void {\n const ta = this.#textarea;\n if (!ta) return;\n\n // Sync simple attributes\n const placeholder =\n (this as unknown as { placeholder: string }).placeholder ?? 'Write markdown\\u2026';\n ta.placeholder = placeholder;\n ta.disabled = !!(this as unknown as { disabled: boolean }).disabled;\n ta.readOnly = !!(this as unknown as { readonly: boolean }).readonly;\n\n const attachBtn = this.shadowRoot.querySelector<HTMLButtonElement>('.attach-btn');\n if (attachBtn) {\n attachBtn.disabled = ta.disabled || ta.readOnly;\n }\n\n // Sync value if the reactive prop was updated externally (e.g. attributeChangedCallback)\n const propVal = (this as unknown as { value: string }).value ?? '';\n if (propVal !== ta.value) {\n ta.value = propVal;\n this.#syncFormValue();\n this.#scheduleHighlight();\n }\n\n // Update Prism theme when dark attribute changes\n const dark = !!(this as unknown as { dark: boolean }).dark;\n applyPrismTheme(this.shadowRoot, dark);\n\n // Re-render status bar (maxWords may have changed)\n this.#updateStatusBarNow();\n }\n\n protected override render(): string {\n const ph = (this as unknown as { placeholder: string }).placeholder ?? 'Write markdown\\u2026';\n const disabled = !!(this as unknown as { disabled: boolean }).disabled;\n const readonly = !!(this as unknown as { readonly: boolean }).readonly;\n\n return `\n <div class=\"editor\">\n <div class=\"toolbar\" role=\"tablist\" aria-label=\"Editor mode\">\n <button\n class=\"tab-btn\"\n data-tab=\"write\"\n role=\"tab\"\n aria-selected=\"true\"\n aria-controls=\"write-panel\"\n >Write</button>\n <button\n class=\"tab-btn\"\n data-tab=\"preview\"\n role=\"tab\"\n aria-selected=\"false\"\n aria-controls=\"preview-panel\"\n >Preview</button>\n </div>\n\n <div class=\"write-area\" id=\"write-panel\" role=\"tabpanel\" aria-label=\"Markdown editor\">\n <div class=\"backdrop\" aria-hidden=\"true\">\n <div class=\"backdrop-content\"></div>\n </div>\n <textarea\n aria-label=\"Markdown editor\"\n aria-haspopup=\"listbox\"\n aria-autocomplete=\"list\"\n aria-controls=\"ac-dropdown\"\n placeholder=\"${ph}\"\n ${disabled ? 'disabled' : ''}\n ${readonly ? 'readonly' : ''}\n spellcheck=\"false\"\n autocomplete=\"off\"\n autocorrect=\"off\"\n autocapitalize=\"off\"\n ></textarea>\n </div>\n\n <div\n class=\"preview-body markdown-body\"\n id=\"preview-panel\"\n role=\"tabpanel\"\n aria-label=\"Markdown preview\"\n hidden\n ></div>\n\n <div class=\"status-bar\">\n <button class=\"attach-btn\" type=\"button\" aria-label=\"Attach files\" ${disabled || readonly ? 'disabled' : ''}>\n 📎 Attach files\n </button>\n <span class=\"status-bar-count\" aria-live=\"polite\"></span>\n <input\n type=\"file\"\n class=\"file-input\"\n multiple\n accept=\"image/*,application/pdf,.zip,.txt,.csv,.json,.md\"\n aria-hidden=\"true\"\n tabindex=\"-1\"\n >\n </div>\n\n <div class=\"upload-list\"></div>\n </div>\n <ul id=\"ac-dropdown\" class=\"ac-dropdown\" role=\"listbox\" aria-label=\"Suggestions\" hidden></ul>\n `;\n }\n\n // ════════════════════════════════════════════════════════════════════\n // Post-render setup\n // ════════════════════════════════════════════════════════════════════\n\n #cacheDOMRefs(): void {\n this.#textarea = this.shadowRoot.querySelector('textarea');\n this.#backdrop = this.shadowRoot.querySelector('.backdrop');\n this.#backdropContent = this.shadowRoot.querySelector('.backdrop-content');\n this.#writeArea = this.shadowRoot.querySelector('.write-area');\n this.#previewBody = this.shadowRoot.querySelector('.preview-body');\n this.#statusCount = this.shadowRoot.querySelector('.status-bar-count');\n this.#acDropdown = this.shadowRoot.querySelector('.ac-dropdown');\n this.#uploadList = this.shadowRoot.querySelector('.upload-list');\n this.#fileInput = this.shadowRoot.querySelector('.file-input');\n }\n\n #attachEventHandlers(): void {\n const ta = this.#textarea;\n if (!ta) return;\n\n // ── Textarea input ─────────────────────────────────────────────\n ta.addEventListener('input', () => {\n this.#syncFormValue();\n this.emit('change', { value: ta.value });\n this.#scheduleHighlight();\n this.#scheduleStatusUpdate();\n this.#handleAutocompleteInput();\n });\n\n // ── Scroll sync (backdrop must follow textarea scroll) ─────────\n ta.addEventListener('scroll', () => {\n if (this.#backdrop) {\n this.#backdrop.scrollTop = ta.scrollTop;\n this.#backdrop.scrollLeft = ta.scrollLeft;\n }\n });\n\n // ── Close dropdown when focus leaves the textarea ──────────────\n ta.addEventListener('blur', () => {\n // Delay to allow pointer events on dropdown items to fire first\n setTimeout(() => {\n if (!this.shadowRoot?.activeElement) {\n this.#closeDropdown();\n }\n }, 150);\n });\n\n // ── Tab key for autocomplete (prevent default only when dropdown open) ──\n ta.addEventListener('keydown', (e) => {\n if (this.#acSuggestions.length > 0 && !this.#acDropdown?.hidden) {\n this.#handleDropdownKeydown(e);\n }\n // Ctrl+Shift+P → toggle preview (T023)\n if (e.ctrlKey && e.shiftKey && e.key === 'P') {\n e.preventDefault();\n this.#switchTab(this.#activeTab === 'write' ? 'preview' : 'write');\n }\n });\n\n // ── Drag and drop ──────────────────────────────────────────────\n const writeArea = this.#writeArea;\n if (writeArea) {\n writeArea.addEventListener('dragover', (e) => {\n e.preventDefault();\n writeArea.style.opacity = '0.8';\n });\n writeArea.addEventListener('dragleave', () => {\n writeArea.style.opacity = '';\n });\n writeArea.addEventListener('drop', (e) => {\n e.preventDefault();\n writeArea.style.opacity = '';\n if ((this as unknown as { readonly: boolean }).readonly) return;\n const files = Array.from(e.dataTransfer?.files ?? []).filter(isAcceptedType);\n files.forEach((f) => this.#startUpload(f));\n });\n }\n\n // ── Clipboard paste (images only) ─────────────────────────────\n ta.addEventListener('paste', (e) => {\n if ((this as unknown as { readonly: boolean }).readonly) return;\n const imageFiles = Array.from(e.clipboardData?.files ?? []).filter((f) =>\n f.type.startsWith('image/'),\n );\n if (imageFiles.length > 0) {\n e.preventDefault();\n imageFiles.forEach((f) => this.#startUpload(f));\n }\n });\n\n // ── Tab buttons ────────────────────────────────────────────────\n const toolbar = this.shadowRoot.querySelector('.toolbar');\n toolbar?.addEventListener('click', (e) => {\n const btn = (e.target as HTMLElement).closest<HTMLElement>('.tab-btn');\n const tab = btn?.dataset.tab as 'write' | 'preview' | undefined;\n if (tab) this.#switchTab(tab);\n });\n\n // ── Attach button ──────────────────────────────────────────────\n const attachBtn = this.shadowRoot.querySelector('.attach-btn');\n attachBtn?.addEventListener('click', () => this.#fileInput?.click());\n\n this.#fileInput?.addEventListener('change', () => {\n const files = Array.from(this.#fileInput?.files ?? []).filter(isAcceptedType);\n files.forEach((f) => this.#startUpload(f));\n if (this.#fileInput) this.#fileInput.value = '';\n });\n\n // ── Autocomplete dropdown click delegation ─────────────────────\n this.#acDropdown?.addEventListener('click', (e) => {\n const item = (e.target as HTMLElement).closest<HTMLElement>('[data-ac-index]');\n if (item) {\n const idx = parseInt(item.dataset.acIndex ?? '-1', 10);\n if (idx >= 0) {\n this.#acSelectedIndex = idx;\n this.#confirmAutocomplete();\n }\n }\n });\n\n // ── ResizeObserver: mirror textarea dimensions to backdrop ─────\n if (typeof ResizeObserver !== 'undefined') {\n this.#resizeObserver = new ResizeObserver(() => {\n if (this.#backdrop && this.#textarea) {\n this.#backdrop.style.height = `${this.#textarea.offsetHeight}px`;\n }\n });\n this.#resizeObserver.observe(ta);\n }\n }\n\n // ════════════════════════════════════════════════════════════════════\n // Highlight (Write tab backdrop)\n // ════════════════════════════════════════════════════════════════════\n\n #initHighlight(): void {\n const dark = !!(this as unknown as { dark: boolean }).dark;\n applyPrismTheme(this.shadowRoot, dark);\n ensurePrism().then(() => {\n // Highlight immediately once Prism is ready\n if (this.#textarea && this.#backdropContent) {\n this.#backdropContent.innerHTML = highlightMarkdown(this.#textarea.value);\n }\n });\n }\n\n #scheduleHighlight(): void {\n if (this.#highlightTimer !== null) clearTimeout(this.#highlightTimer);\n this.#highlightTimer = setTimeout(() => {\n this.#highlightTimer = null;\n if (this.#backdropContent && this.#textarea) {\n this.#backdropContent.innerHTML = highlightMarkdown(this.#textarea.value);\n }\n // Sync scroll after highlight (content size may change)\n if (this.#backdrop && this.#textarea) {\n this.#backdrop.scrollTop = this.#textarea.scrollTop;\n }\n }, 60);\n }\n\n // ════════════════════════════════════════════════════════════════════\n // Tab switching (Write ↔ Preview)\n // ════════════════════════════════════════════════════════════════════\n\n #switchTab(tab: 'write' | 'preview'): void {\n if (tab === this.#activeTab) return;\n this.#activeTab = tab;\n\n const writeBtns = this.shadowRoot.querySelectorAll<HTMLElement>('.tab-btn');\n writeBtns.forEach((btn) => {\n const isActive = btn.dataset.tab === tab;\n btn.setAttribute('aria-selected', String(isActive));\n });\n\n if (tab === 'preview') {\n this.#writeArea?.setAttribute('hidden', '');\n if (this.#previewBody) {\n this.#previewBody.removeAttribute('hidden');\n this.#previewBody.innerHTML = this.#renderMarkdown(this.#textarea?.value ?? '');\n }\n } else {\n this.#writeArea?.removeAttribute('hidden');\n this.#previewBody?.setAttribute('hidden', '');\n }\n }\n\n // ════════════════════════════════════════════════════════════════════\n // Minimal markdown renderer (preview tab)\n // ════════════════════════════════════════════════════════════════════\n\n #renderMarkdown(md: string): string {\n if (!md.trim()) return '<p></p>';\n\n // Step 1: Extract fenced code blocks\n // Use a placeholder string that won't appear in markdown content\n const CB_PH = '\\u2060CB';\n const IC_PH = '\\u2060IC';\n const codeBlocks: string[] = [];\n let html = md.replace(/```(\\w*)\\n?([\\s\\S]*?)```/g, (_, lang, code) => {\n const escaped = code.replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>');\n const idx =\n codeBlocks.push(`<pre><code class=\"language-${lang || 'text'}\">${escaped}</code></pre>`) -\n 1;\n return `${CB_PH}${idx}${CB_PH}`;\n });\n\n // Step 2: Extract inline code\n const inlineCodes: string[] = [];\n html = html.replace(/`([^`\\n]+)`/g, (_, code) => {\n const escaped = code.replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>');\n const idx = inlineCodes.push(`<code>${escaped}</code>`) - 1;\n return `${IC_PH}${idx}${IC_PH}`;\n });\n\n // Step 3: HTML-escape remaining content\n html = html.replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>');\n\n // Step 4: Markdown transformations\n // Images before links — sanitize URLs to block javascript: and other unsafe protocols\n html = html.replace(\n /!\\[([^\\]]*)\\]\\(([^)]+)\\)/g,\n (_, alt, url) => `<img src=\"${sanitizeUrl(url)}\" alt=\"${alt}\">`,\n );\n html = html.replace(\n /\\[([^\\]]+)\\]\\(([^)]+)\\)/g,\n (_, text, url) => `<a href=\"${sanitizeUrl(url)}\">${text}</a>`,\n );\n\n // Headings\n html = html\n .replace(/^###### (.+)$/gm, '<h6>$1</h6>')\n .replace(/^##### (.+)$/gm, '<h5>$1</h5>')\n .replace(/^#### (.+)$/gm, '<h4>$1</h4>')\n .replace(/^### (.+)$/gm, '<h3>$1</h3>')\n .replace(/^## (.+)$/gm, '<h2>$1</h2>')\n .replace(/^# (.+)$/gm, '<h1>$1</h1>');\n\n // Horizontal rules\n html = html.replace(/^[-*_]{3,}$/gm, '<hr>');\n\n // Blockquotes (note: > is escaped to > above)\n html = html.replace(/^> (.+)$/gm, '<blockquote>$1</blockquote>');\n\n // Bold + italic (order matters: *** before ** before *)\n html = html\n .replace(/\\*\\*\\*(.+?)\\*\\*\\*/g, '<strong><em>$1</em></strong>')\n .replace(/\\*\\*(.+?)\\*\\*/g, '<strong>$1</strong>')\n .replace(/\\*(.+?)\\*/g, '<em>$1</em>')\n .replace(/___(.+?)___/g, '<strong><em>$1</em></strong>')\n .replace(/__(.+?)__/g, '<strong>$1</strong>')\n .replace(/_(.+?)_/g, '<em>$1</em>');\n\n // Strikethrough\n html = html.replace(/~~(.+?)~~/g, '<del>$1</del>');\n\n // Lists (single-level)\n // Tag items with a type marker to distinguish ul from ol, then group consecutive runs\n html = html.replace(/^[ \\t]*[-*+] (.+)$/gm, '<li data-list=\"ul\">$1</li>');\n html = html.replace(/^[ \\t]*\\d+\\. (.+)$/gm, '<li data-list=\"ol\">$1</li>');\n // Group consecutive unordered items into <ul>\n html = html.replace(\n /(<li data-list=\"ul\">[^\\n]*<\\/li>(?:\\n<li data-list=\"ul\">[^\\n]*<\\/li>)*)/g,\n (match) => '<ul>' + match.replace(/ data-list=\"ul\"/g, '') + '</ul>',\n );\n // Group consecutive ordered items into <ol>\n html = html.replace(\n /(<li data-list=\"ol\">[^\\n]*<\\/li>(?:\\n<li data-list=\"ol\">[^\\n]*<\\/li>)*)/g,\n (match) => '<ol>' + match.replace(/ data-list=\"ol\"/g, '') + '</ol>',\n );\n\n // Paragraphs\n const lines = html.split('\\n\\n');\n html = lines\n .map((block) => {\n const t = block.trim();\n if (!t) return '';\n if (/^<(h[1-6]|ul|ol|li|blockquote|hr|pre|img)/.test(t) || t.startsWith(CB_PH)) return t;\n return `<p>${t.replace(/\\n/g, '<br>')}</p>`;\n })\n .filter(Boolean)\n .join('\\n');\n\n // Step 5: Restore placeholders\n html = html.replace(\n new RegExp(`${CB_PH}(\\\\d+)${CB_PH}`, 'g'),\n (_, i) => codeBlocks[parseInt(i, 10)] ?? '',\n );\n html = html.replace(\n new RegExp(`${IC_PH}(\\\\d+)${IC_PH}`, 'g'),\n (_, i) => inlineCodes[parseInt(i, 10)] ?? '',\n );\n\n return html;\n }\n\n // ════════════════════════════════════════════════════════════════════\n // Form association\n // ════════════════════════════════════════════════════════════════════\n\n #syncFormValue(): void {\n this.#internals?.setFormValue(this.#textarea?.value ?? '');\n }\n\n // ════════════════════════════════════════════════════════════════════\n // File upload\n // ════════════════════════════════════════════════════════════════════\n\n #startUpload(file: File): void {\n this.emit('upload-start', { file });\n\n const id = `upload-${++this.#uploadIdCounter}`;\n const uploadUrl = (this as unknown as { uploadUrl: string | undefined }).uploadUrl;\n\n if (!uploadUrl) {\n this.emit('upload-error', { file, error: 'no upload-url set' });\n this.#showUploadError(file, 'no upload-url set');\n return;\n }\n\n // Create progress row\n this.#addProgressRow(id, file.name);\n\n uploadFile(file, uploadUrl, (pct) => {\n this.#updateProgressRow(id, pct);\n })\n .then((url) => {\n this.#removeUploadRow(id);\n const markdown = fileToMarkdown(file, url);\n this.insertText(markdown);\n this.emit('upload-done', { file, url, markdown });\n })\n .catch((err: string) => {\n this.#removeUploadRow(id);\n const errorMsg = typeof err === 'string' ? err : 'Upload failed';\n this.emit('upload-error', { file, error: errorMsg });\n this.#showUploadError(file, errorMsg);\n });\n }\n\n #addProgressRow(id: string, filename: string): void {\n if (!this.#uploadList) return;\n const row = document.createElement('div');\n row.className = 'upload-row';\n row.id = id;\n row.innerHTML = `\n <span class=\"upload-filename\">${escapeHtmlStr(filename)}</span>\n <div class=\"upload-bar-track\">\n <div class=\"upload-bar\" style=\"width: 0%\"></div>\n </div>\n `;\n this.#uploadList.appendChild(row);\n }\n\n #updateProgressRow(id: string, pct: number): void {\n const bar = this.#uploadList?.querySelector<HTMLElement>(`#${id} .upload-bar`);\n if (bar) bar.style.width = `${pct}%`;\n }\n\n #removeUploadRow(id: string): void {\n this.#uploadList?.querySelector(`#${id}`)?.remove();\n }\n\n #showUploadError(file: File, message: string): void {\n if (!this.#uploadList) return;\n const row = document.createElement('div');\n row.className = 'upload-error-row';\n row.innerHTML = `\n <span class=\"upload-error-msg\">${escapeHtmlStr(file.name)}: ${escapeHtmlStr(message)}</span>\n `;\n this.#uploadList.appendChild(row);\n setTimeout(() => row.remove(), 4000);\n }\n\n // ════════════════════════════════════════════════════════════════════\n // Autocomplete\n // ════════════════════════════════════════════════════════════════════\n\n #handleAutocompleteInput(): void {\n const ta = this.#textarea;\n if (!ta) return;\n\n const result = detectTrigger(ta.value, ta.selectionStart ?? 0);\n\n if (!result) {\n this.#closeDropdown();\n return;\n }\n\n const { trigger, query, triggerPos } = result;\n this.#acTrigger = trigger;\n this.#acTriggerPos = triggerPos;\n\n const resolve = (list: Suggestion[]) => this.setSuggestions(list);\n\n if (trigger === '@') {\n this.emit('mention-query', { trigger, query, resolve });\n } else {\n this.emit('reference-query', { trigger, query, resolve });\n }\n }\n\n #handleDropdownKeydown(e: KeyboardEvent): void {\n const len = this.#acSuggestions.length;\n if (len === 0) return;\n\n switch (e.key) {\n case 'ArrowDown':\n e.preventDefault();\n this.#acSelectedIndex = (this.#acSelectedIndex + 1) % len;\n this.#updateDropdown();\n break;\n case 'ArrowUp':\n e.preventDefault();\n this.#acSelectedIndex = (this.#acSelectedIndex - 1 + len) % len;\n this.#updateDropdown();\n break;\n case 'Enter':\n case 'Tab':\n if (this.#acSelectedIndex >= 0) {\n e.preventDefault();\n this.#confirmAutocomplete();\n }\n break;\n case 'Escape':\n this.#closeDropdown();\n break;\n }\n }\n\n #confirmAutocomplete(): void {\n const ta = this.#textarea;\n if (!ta || this.#acSelectedIndex < 0 || !this.#acTrigger) return;\n\n const suggestion = this.#acSuggestions[this.#acSelectedIndex];\n if (!suggestion) return;\n\n const { newValue, newCursorPos } = confirmSuggestion(\n ta.value,\n this.#acTriggerPos,\n ta.selectionStart ?? ta.value.length,\n this.#acTrigger,\n suggestion.id,\n );\n\n ta.value = newValue;\n ta.setSelectionRange(newCursorPos, newCursorPos);\n this.#syncFormValue();\n this.emit('change', { value: ta.value });\n this.#scheduleHighlight();\n this.#scheduleStatusUpdate();\n this.#closeDropdown();\n }\n\n #closeDropdown(): void {\n this.#acSuggestions = [];\n this.#acSelectedIndex = -1;\n this.#acTrigger = null;\n this.#acTriggerPos = -1;\n if (this.#acDropdown) {\n this.#acDropdown.innerHTML = '';\n this.#acDropdown.hidden = true;\n }\n }\n\n #updateDropdown(): void {\n if (!this.#acDropdown) return;\n if (this.#acSuggestions.length === 0) {\n this.#acDropdown.hidden = true;\n this.#textarea?.removeAttribute('aria-activedescendant');\n return;\n }\n this.#acDropdown.innerHTML = renderDropdown(this.#acSuggestions, this.#acSelectedIndex);\n this.#acDropdown.hidden = false;\n // Update aria-activedescendant so screen readers announce the highlighted item\n if (this.#acSelectedIndex >= 0) {\n this.#textarea?.setAttribute('aria-activedescendant', `ac-item-${this.#acSelectedIndex}`);\n } else {\n this.#textarea?.removeAttribute('aria-activedescendant');\n }\n }\n\n // ════════════════════════════════════════════════════════════════════\n // Status bar\n // ════════════════════════════════════════════════════════════════════\n\n #scheduleStatusUpdate(): void {\n if (this.#statusTimer !== null) clearTimeout(this.#statusTimer);\n this.#statusTimer = setTimeout(() => {\n this.#statusTimer = null;\n this.#updateStatusBarNow();\n }, 100);\n }\n\n #updateStatusBarNow(): void {\n if (!this.#statusCount) return;\n const text = this.#textarea?.value ?? '';\n const words = countWords(text);\n const chars = text.length;\n const maxWords = (this as unknown as { maxWords: number | undefined }).maxWords ?? null;\n this.#statusCount.innerHTML = renderStatusCount(words, chars, maxWords);\n }\n\n // ════════════════════════════════════════════════════════════════════\n // Public API\n // ════════════════════════════════════════════════════════════════════\n\n /** Returns the current markdown content. */\n getValue(): string {\n return this.#textarea?.value ?? '';\n }\n\n /**\n * Set the editor content programmatically.\n * Does NOT fire a change event. Updates the form value.\n */\n setValue(str: string): void {\n if (this.#textarea) {\n this.#textarea.value = str;\n this.#syncFormValue();\n this.#scheduleHighlight();\n this.#updateStatusBarNow();\n } else {\n // Called before element is connected — store in reactive property\n (this as unknown as { value: string }).value = str;\n }\n }\n\n /**\n * Insert text at the current cursor position, replacing any selection.\n * Fires a change event after insertion.\n */\n insertText(str: string): void {\n const ta = this.#textarea;\n if (!ta) return;\n\n const start = ta.selectionStart ?? ta.value.length;\n const end = ta.selectionEnd ?? ta.value.length;\n ta.value = ta.value.slice(0, start) + str + ta.value.slice(end);\n const newPos = start + str.length;\n ta.setSelectionRange(newPos, newPos);\n // Dispatch 'input' — the textarea's input listener handles syncFormValue(),\n // emit('change'), scheduleHighlight(), scheduleStatusUpdate(), and autocomplete\n ta.dispatchEvent(new Event('input', { bubbles: true }));\n }\n\n /**\n * Feed suggestions into the autocomplete dropdown.\n * Pass an empty array to close the dropdown.\n */\n setSuggestions(list: Suggestion[]): void {\n this.#acSuggestions = list;\n this.#acSelectedIndex = list.length > 0 ? 0 : -1;\n this.#updateDropdown();\n }\n}\n\n/**\n * Sanitize a URL for use in rendered HTML (preview tab).\n * Only allows https://, http://, relative paths, and anchor fragments.\n * Rejects javascript:, data:, vbscript:, and other unsafe protocols.\n */\nfunction sanitizeUrl(url: string): string {\n const trimmed = url.trim();\n // Has no protocol → relative URL, safe\n if (!/^[a-z][a-z\\d+\\-.]*:/i.test(trimmed)) return trimmed;\n // Allow http and https only\n if (/^https?:/i.test(trimmed)) return trimmed;\n // All other protocols (javascript:, data:, vbscript:, …) → neutralise\n return '#';\n}\n\n/** HTML-escape a string for safe insertion into innerHTML. */\nfunction escapeHtmlStr(s: string): string {\n return s\n .replace(/&/g, '&')\n .replace(/</g, '<')\n .replace(/>/g, '>')\n .replace(/\"/g, '"');\n}\n",
|
|
11
|
+
"/**\n * @duskmoon-dev/el-markdown-input\n *\n * A form-associated custom element providing a markdown editor with:\n * - Syntax-highlighted write mode (Prism.js, loaded from CDN)\n * - Preview mode with rendered HTML\n * - File upload via drag-and-drop, clipboard paste, or file picker\n * - @mention / #reference autocomplete\n * - Live word / character count status bar\n * - Phoenix LiveView hook (MarkdownInputHook)\n *\n * @example\n * ```ts\n * import { register } from '@duskmoon-dev/el-markdown-input';\n * register();\n * ```\n *\n * @example LiveView\n * ```js\n * import { MarkdownInputHook } from '@duskmoon-dev/el-markdown-input';\n * let liveSocket = new LiveSocket('/live', Socket, {\n * hooks: { MarkdownInput: MarkdownInputHook }\n * });\n * ```\n */\n\nexport { ElDmMarkdownInput } from './element.js';\nexport type { Suggestion } from './types.js';\n\n/**\n * Register the <el-dm-markdown-input> custom element.\n * Safe to call multiple times — guards against double registration.\n */\nexport function register(): void {\n if (!customElements.get('el-dm-markdown-input')) {\n // Dynamic import to avoid circular reference at module evaluation time\n import('./element.js').then(({ ElDmMarkdownInput }) => {\n customElements.define('el-dm-markdown-input', ElDmMarkdownInput);\n });\n }\n}\n\n// ── Phoenix LiveView Hook ────────────────────────────────────────────────────\n\ntype MarkdownInputEl = HTMLElement & {\n getValue(): string;\n setValue(s: string): void;\n dataset: DOMStringMap;\n};\n\ninterface LiveViewHook {\n el: MarkdownInputEl;\n pushEvent(event: string, payload: Record<string, unknown>): void;\n mounted(): void;\n updated(): void;\n}\n\n/**\n * Phoenix LiveView hook that syncs the markdown editor value with the server.\n *\n * Usage:\n * ```js\n * import { MarkdownInputHook, register } from '@duskmoon-dev/el-markdown-input';\n * register();\n * let liveSocket = new LiveSocket('/live', Socket, {\n * hooks: { MarkdownInput: MarkdownInputHook }\n * });\n * ```\n *\n * Template:\n * ```heex\n * <el-dm-markdown-input\n * id=\"body-input\"\n * name=\"body\"\n * data-value={@content}\n * phx-hook=\"MarkdownInput\"\n * />\n * ```\n */\nexport const MarkdownInputHook: Pick<LiveViewHook, 'mounted' | 'updated'> = {\n mounted(this: LiveViewHook) {\n // Sync initial server value\n this.el.setValue(this.el.dataset.value ?? '');\n\n // Push editor changes to the LiveView process\n this.el.addEventListener('change', (e) => {\n const detail = (e as CustomEvent<{ value: string }>).detail;\n this.pushEvent('content_changed', { value: detail.value });\n });\n\n // Push upload-start events (server can handle server-side upload flow)\n this.el.addEventListener('upload-start', (e) => {\n const detail = (e as CustomEvent<{ file: File }>).detail;\n this.pushEvent('upload_file', { name: detail.file.name });\n });\n },\n\n updated(this: LiveViewHook) {\n // Server pushed a new value (e.g. after form reset or server-side update)\n const v = this.el.dataset.value;\n if (v !== undefined && v !== this.el.getValue()) {\n this.el.setValue(v);\n }\n },\n};\n"
|
|
12
|
+
],
|
|
13
|
+
"mappings": ";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;IAAA,gBAUa;AAAA;AAAA,EAVb;AAAA,EAUa,gBAAgB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AA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",
|
|
14
|
+
"debugId": "41BCD34F5B96974F64756E2164756E21",
|
|
15
|
+
"names": []
|
|
16
|
+
}
|