@nks-hub/texy-editor 0.1.0

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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 NKS Hub
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,260 @@
1
+ # @nks-hub/texy-editor
2
+
3
+ [![Build Status](https://github.com/nks-hub/texy-ts-editor/actions/workflows/build.yml/badge.svg)](https://github.com/nks-hub/texy-ts-editor/actions)
4
+ [![npm version](https://img.shields.io/npm/v/@nks-hub/texy-editor.svg)](https://www.npmjs.com/package/@nks-hub/texy-editor)
5
+ [![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](LICENSE)
6
+
7
+ > Modern TypeScript Texy markup editor with configurable toolbar, client-side parser, live preview, theming, and plugin system. Zero jQuery dependency. Spiritual successor to [Texyla](https://github.com/niclem/texyla).
8
+
9
+ ---
10
+
11
+ ## Why Texy Editor?
12
+
13
+ [Texy](https://texy.info) is a powerful markup language created by [David Grudl](https://davidgrudl.com/) that goes beyond Markdown with underline headings, advanced image syntax, modifiers, typography rules, and more. The original [Texyla editor](https://github.com/niclem/texyla) provided a jQuery-based frontend for Texy, but hasn't been maintained since 2014.
14
+
15
+ This project is a **modern rewrite from scratch** — zero jQuery, pure TypeScript, with a client-side parser and extensible plugin system:
16
+
17
+ - **TypeScript-first** — Fully typed API, exported types for all options
18
+ - **Zero dependencies** — No jQuery, no external libraries
19
+ - **Client-side parser** — Live preview without server round-trips
20
+ - **Plugin system** — Extend with custom syntax (YouTube, smileys, BBCode, etc.)
21
+ - **Themeable** — Light/dark themes via CSS custom properties
22
+ - **Dual output** — ESM + CJS builds, tree-shakeable
23
+
24
+ ---
25
+
26
+ ## Quick Start
27
+
28
+ ### Installation
29
+
30
+ ```bash
31
+ npm install @nks-hub/texy-editor
32
+ ```
33
+
34
+ ### Attach to a Textarea
35
+
36
+ ```typescript
37
+ import { TexyEditor } from '@nks-hub/texy-editor';
38
+ import '@nks-hub/texy-editor/css';
39
+
40
+ const editor = new TexyEditor('#my-textarea', {
41
+ language: 'en',
42
+ theme: 'light',
43
+ livePreview: true,
44
+ });
45
+ ```
46
+
47
+ ### Standalone Parser (No UI)
48
+
49
+ ```typescript
50
+ import { TexyParser } from '@nks-hub/texy-editor';
51
+
52
+ const parser = new TexyParser();
53
+ const html = parser.parse('**bold** and *italic*');
54
+ // <p><strong>bold</strong> and <em>italic</em></p>
55
+ ```
56
+
57
+ ---
58
+
59
+ ## Features
60
+
61
+ | Feature | Description |
62
+ |---------|-------------|
63
+ | **Full Texy Syntax** | Bold, italic, headings, lists, tables, code blocks, images, links, blockquotes, modifiers |
64
+ | **Typography** | Smart dashes, ellipsis, arrows, symbols, Czech non-breaking spaces |
65
+ | **Live Preview** | Client-side rendering with configurable debounce |
66
+ | **Toolbar** | Configurable button groups, custom buttons, keyboard shortcuts |
67
+ | **Keyboard Shortcuts** | Ctrl+B, Ctrl+I, Tab indent, F11 fullscreen, customizable |
68
+ | **Undo/Redo** | Built-in history with configurable max steps |
69
+ | **i18n** | English and Czech built-in, extensible to any language |
70
+ | **Themes** | Light and dark presets, CSS custom properties for full control |
71
+ | **Plugin System** | Preprocess, inline, and postprocess hooks with placeholder protection |
72
+ | **Split View** | Side-by-side editor + preview mode |
73
+ | **Fullscreen** | Distraction-free editing mode |
74
+
75
+ ---
76
+
77
+ ## Supported Texy Syntax
78
+
79
+ ### Inline
80
+
81
+ | Syntax | Output |
82
+ |--------|--------|
83
+ | `**bold**` | **bold** |
84
+ | `*italic*` | *italic* |
85
+ | `` `code` `` | `code` |
86
+ | `--deleted--` | ~~deleted~~ |
87
+ | `++inserted++` | <u>inserted</u> |
88
+ | `^^super^^` | superscript |
89
+ | `__sub__` | subscript |
90
+ | `>>quoted<<` | quoted |
91
+ | `"text":https://url` | link |
92
+ | `[* image.jpg *]` | image |
93
+ | `word((title))` | abbreviation |
94
+ | `''noTexy zone''` | raw text |
95
+
96
+ ### Block
97
+
98
+ | Syntax | Output |
99
+ |--------|--------|
100
+ | Underline heading (`===`) | `<h1>`–`<h4>` |
101
+ | Surrounded heading (`=== text ===`) | `<h1>`–`<h4>` |
102
+ | `- item` | unordered list |
103
+ | `1) item` | ordered list |
104
+ | `> quote` | blockquote |
105
+ | `/--code lang` ... `\--` | code block |
106
+ | `/--html` ... `\--` | raw HTML |
107
+ | `/--div .class` ... `\--` | div with modifier |
108
+ | `\| cell \| cell \|` | table |
109
+ | `---` or `***` | horizontal rule |
110
+
111
+ ---
112
+
113
+ ## Plugins
114
+
115
+ Extend the parser with built-in or custom plugins.
116
+
117
+ ### Built-in Plugins
118
+
119
+ ```typescript
120
+ import {
121
+ TexyParser,
122
+ youtubePlugin,
123
+ smileyPlugin,
124
+ bbcodePlugin,
125
+ imageEmbedPlugin,
126
+ linkRedirectPlugin,
127
+ } from '@nks-hub/texy-editor';
128
+
129
+ const parser = new TexyParser({
130
+ plugins: [
131
+ youtubePlugin({ width: 560, height: 315 }),
132
+ smileyPlugin({ baseUrl: 'https://example.com/smileys' }),
133
+ bbcodePlugin(),
134
+ imageEmbedPlugin({ maxWidth: '400px' }),
135
+ linkRedirectPlugin({
136
+ redirectUrl: 'https://example.com/redirect',
137
+ excludeDomains: ['example.com'],
138
+ }),
139
+ ],
140
+ });
141
+ ```
142
+
143
+ | Plugin | Syntax | Description |
144
+ |--------|--------|-------------|
145
+ | `youtubePlugin` | `[* youtube:ID *]` | YouTube video embeds |
146
+ | `smileyPlugin` | `*123*` | Numeric smiley/emoticon images |
147
+ | `bbcodePlugin` | `[b]`, `[i]`, `[u]`, `[s]`, `[url=]`, `[color=]` | BBCode tag support |
148
+ | `imageEmbedPlugin` | `"[* img *]":URL` | Linked image embeds |
149
+ | `linkRedirectPlugin` | — (postprocess) | Rewrite external links through redirect service |
150
+
151
+ ### Custom Plugin
152
+
153
+ ```typescript
154
+ import type { TexyParserPlugin } from '@nks-hub/texy-editor';
155
+
156
+ const mentionPlugin: TexyParserPlugin = {
157
+ name: 'mentions',
158
+ processInline(text, placeholder) {
159
+ return text.replace(/@([a-zA-Z0-9_]+)/g, (_m, user) =>
160
+ placeholder(`<a href="/profile/${user}">@${user}</a>`)
161
+ );
162
+ },
163
+ };
164
+
165
+ const parser = new TexyParser({ plugins: [mentionPlugin] });
166
+ ```
167
+
168
+ **Plugin hooks:**
169
+
170
+ | Hook | Stage | Use case |
171
+ |------|-------|----------|
172
+ | `preprocess(text)` | Before parsing | Convert custom syntax to Texy or placeholders |
173
+ | `processInline(text, ph)` | During inline pass | Generate HTML with placeholder protection |
174
+ | `postprocess(html)` | After parsing | Transform final HTML output |
175
+
176
+ ---
177
+
178
+ ## Editor Options
179
+
180
+ ```typescript
181
+ const editor = new TexyEditor('#textarea', {
182
+ language: 'en', // 'en' | 'cs' | custom
183
+ theme: 'light', // 'light' | 'dark' | custom class
184
+ livePreview: true, // Client-side preview
185
+ livePreviewDelay: 300, // Debounce ms
186
+ splitView: true, // Side-by-side mode
187
+ fullscreen: true, // Enable F11 fullscreen
188
+ autoResize: true, // Auto-grow textarea
189
+ maxUndoSteps: 50, // Undo history limit
190
+ previewPath: '/api/preview', // Server-side preview URL
191
+ toolbar: ['bold', 'italic', null, 'link', 'image'],
192
+ shortcuts: { bold: 'Ctrl+B' },
193
+ plugins: [],
194
+ cssVars: { '--texy-accent': '#0066cc' },
195
+ });
196
+ ```
197
+
198
+ ---
199
+
200
+ ## Build Outputs
201
+
202
+ | File | Format | Size | Use Case |
203
+ |------|--------|------|----------|
204
+ | `dist/texy-editor.js` | ESM | ~66 KB | Modern bundlers (Vite, webpack, Rollup) |
205
+ | `dist/texy-editor.cjs` | CJS | ~47 KB | Node.js / `require()` |
206
+ | `dist/texy-editor.css` | CSS | ~10 KB | Base stylesheet with theme support |
207
+ | `dist/types/` | `.d.ts` | — | TypeScript type declarations |
208
+
209
+ ---
210
+
211
+ ## Development
212
+
213
+ ```bash
214
+ # Install dependencies
215
+ npm install
216
+
217
+ # Dev server with playground
218
+ npm run dev
219
+
220
+ # Run tests (229 tests)
221
+ npm test
222
+
223
+ # Type checking
224
+ npm run typecheck
225
+
226
+ # Production build
227
+ npm run build
228
+
229
+ # Watch mode
230
+ npm run test:watch
231
+ ```
232
+
233
+ ---
234
+
235
+ ## Requirements
236
+
237
+ - **Node.js**: 18+ recommended
238
+ - **TypeScript**: 5.0+ (for TypeScript projects)
239
+ - **Browsers**: Modern browsers with ES6 support
240
+
241
+ ---
242
+
243
+ ## License
244
+
245
+ MIT License — see [LICENSE](LICENSE) file for details.
246
+
247
+ ---
248
+
249
+ ## Links
250
+
251
+ - [Texy Markup Language](https://texy.info) — The original Texy PHP library by David Grudl
252
+ - [Texyla](https://github.com/niclem/texyla) — The original jQuery-based Texy editor (archived)
253
+ - [Nette Framework](https://nette.org) — PHP framework commonly used with Texy
254
+ - [GitHub Repository](https://github.com/nks-hub/texy-ts-editor)
255
+ - [npm Package](https://www.npmjs.com/package/@nks-hub/texy-editor)
256
+ - [Issue Tracker](https://github.com/nks-hub/texy-ts-editor/issues)
257
+
258
+ ---
259
+
260
+ **Built for Texy** — A modern successor to Texyla, built from scratch in TypeScript.
@@ -0,0 +1,42 @@
1
+ "use strict";var S=Object.defineProperty;var E=(a,t,e)=>t in a?S(a,t,{enumerable:!0,configurable:!0,writable:!0,value:e}):a[t]=e;var h=(a,t,e)=>E(a,typeof t!="symbol"?t+"":t,e);Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});class f{constructor(t){this.textarea=t}get lf(){const t=this.textarea.value;return t.includes(`\r
2
+ `)?`\r
3
+ `:t.includes("\r")?"\r":`
4
+ `}getState(){const{selectionStart:t,selectionEnd:e,value:i}=this.textarea;return{start:t,end:e,text:i.substring(t,e),before:i.substring(0,t),after:i.substring(e)}}text(){return this.getState().text}length(){return this.textarea.selectionEnd-this.textarea.selectionStart}isCursor(){return this.textarea.selectionStart===this.textarea.selectionEnd}select(t,e){this.textarea.focus(),this.textarea.setSelectionRange(t,t+e)}setCursor(t){this.select(t,0)}replace(t){const e=this.getState(),i=this.textarea.scrollTop;this.textarea.value=e.before+t+e.after,this.select(e.start,t.length),this.textarea.scrollTop=i,this.dispatchInput()}tag(t,e){const i=this.getState(),n=this.textarea.scrollTop,s=i.before+t+i.text+e+i.after;this.textarea.value=s,this.isCursorAt(i)?this.setCursor(i.start+t.length):this.select(i.start+t.length,i.text.length),this.textarea.scrollTop=n,this.dispatchInput()}phrase(t,e){this.trimSelect(),this.tag(t,e??t)}trimSelect(){const t=this.getState(),e=t.text.replace(/\s+$/,"");e.length<t.text.length&&this.select(t.start,e.length)}selectBlock(){const{value:t}=this.textarea;let e=this.textarea.selectionStart,i=this.textarea.selectionEnd;for(;e>0&&t[e-1]!==`
5
+ `;)e--;for(;i<t.length&&t[i]!==`
6
+ `;)i++;this.select(e,i-e)}getValue(){return this.textarea.value}setValue(t){this.textarea.value=t,this.dispatchInput()}focus(){this.textarea.focus()}getElement(){return this.textarea}isCursorAt(t){return t.start===t.end}dispatchInput(){this.textarea.dispatchEvent(new Event("input",{bubbles:!0}))}}class b{constructor(t){this.selection=t}bold(){this.selection.trimSelect();const t=this.selection.text();t.match(/^\*\*.*\*\*$/)?this.selection.replace(t.substring(2,t.length-2)):this.selection.tag("**","**")}italic(){this.selection.trimSelect();const t=this.selection.text();t.match(/^\*\*\*.*\*\*\*$/)||t.match(/^\*[^*]+\*$/)?this.selection.replace(t.substring(1,t.length-1)):this.selection.tag("*","*")}deleted(){this.toggleInline("--")}inserted(){this.toggleInline("++")}superscript(){this.toggleInline("^^")}subscript(){this.toggleInline("__")}inlineCode(){this.toggleInline("`")}noTexy(){this.selection.phrase("''","''")}quoted(){this.selection.phrase(">>","<<")}link(t,e){if(t)if(e)this.selection.replace(`"${e}":${t}`);else if(this.selection.isCursor()){this.selection.replace(`"":${t}`);const i=this.selection.getState();this.selection.setCursor(i.start+1)}else this.selection.phrase('"',`":${t}`)}acronym(t){if(!t)return;this.selection.text().match(/^[\p{L}\p{N}]{2,}$/u)?this.selection.tag("",`((${t}))`):this.selection.phrase('"',`"((${t}))`)}image(t,e,i,n){let s="";i==="<>"&&(s+=this.selection.lf+".<>"+this.selection.lf,i="*"),s+="[* "+t+" ",e&&(s+=`.( ${e}) `),s+=(i||"*")+"]",n&&(s+=" *** "+n),this.selection.replace(s)}heading(t){const i={1:"#",2:"*",3:"=",4:"-"}[t],n=this.selection.lf;if(this.selection.selectBlock(),this.selection.isCursor())return;const s=this.selection.text(),r=i.repeat(Math.max(3,s.length));this.selection.tag("",n+r)}headingWithPrompt(t,e){if(!e)return;const n={1:"#",2:"*",3:"=",4:"-"}[t],s=this.selection.lf,r=n.repeat(Math.max(3,e.length));this.selection.tag(e+s+r+s,"")}unorderedList(){this.applyList("ul")}orderedList(){this.applyList("ol")}orderedListRoman(){this.applyList("romans")}orderedListRomanSmall(){this.applyList("smallRomans")}orderedListAlpha(){this.applyList("bigAlphabet")}orderedListAlphaSmall(){this.applyList("smallAlphabet")}blockquote(){this.applyList("bq")}indent(){this.applyList("indent")}unindent(){this.selection.selectBlock();const t=this.selection.lf,i=this.selection.text().split(t).map(n=>{const s=n[0];return s===" "||s===" "?n.substring(1):n});this.selection.replace(i.join(t))}alignLeft(){this.insertAlignment("<")}alignRight(){this.insertAlignment(">")}alignCenter(){this.insertAlignment("<>")}alignJustify(){this.insertAlignment("=")}codeBlock(t){const e=this.selection.lf,i=t?" "+t:"";this.selection.tag(`/--code${i}${e}`,`${e}\\--`)}htmlBlock(){const t=this.selection.lf;this.selection.tag(`/--html${t}`,`${t}\\--`)}divBlock(t){const e=this.selection.lf,i=t?` ${t}`:"";this.selection.tag(`/--div${i}${e}`,`${e}\\--`)}textBlock(){const t=this.selection.lf;this.selection.tag(`/--text${t}`,`${t}\\--`)}commentBlock(){const t=this.selection.lf;this.selection.tag(`/--comment${t}`,`${t}\\--`)}horizontalRule(){const t=this.selection.lf,e=`${t}${t}-------------------${t}${t}`;this.selection.isCursor()?this.selection.tag(e,""):this.selection.replace(e)}table(t,e,i){const n=this.selection.lf;let s=n;for(let r=0;r<e;r++){if(i==="top"&&r===1){s+="|";for(let o=0;o<t;o++)s+="--------";s+=n}for(let o=0;o<t;o++)i==="left"&&o===0?s+="|* ":s+="| ";s+="|"+n}s+=n,this.selection.replace(s)}colorModifier(t){this.selection.phrase('"',`" .{color: ${t}}`)}classModifier(t){this.selection.phrase('"',`" .[${t}]`)}insertSymbol(t){this.selection.isCursor()?this.selection.tag(t,""):this.selection.replace(t)}toggleInline(t){this.selection.trimSelect();const e=this.selection.text(),i=t.replace(/[.*+?^${}()|[\]\\]/g,"\\$&");new RegExp(`^${i}.*${i}$`).test(e)?this.selection.replace(e.substring(t.length,e.length-t.length)):this.selection.tag(t,t)}applyList(t){this.selection.selectBlock();const e=this.selection.lf,i=this.selection.text().split(e),n=this.selection.isCursor()?3:i.length,s=[];for(let r=1;r<=n;r++){const o=this.getBullet(t,r),l=this.selection.isCursor()?"":i[r-1];s.push(o+" "+l)}if(this.selection.isCursor()){const r=s[0],o=s.slice(1).join(e);this.selection.tag(r,e+o)}else this.selection.replace(s.join(e))}getBullet(t,e){switch(t){case"ul":return"-";case"ol":return e+")";case"bq":return">";case"indent":return"";case"romans":return this.toRoman(e)+")";case"smallRomans":return this.toRoman(e).toLowerCase()+")";case"smallAlphabet":return this.toLetter(e)+")";case"bigAlphabet":return this.toLetter(e).toUpperCase()+")";default:return"-"}}toRoman(t){t=Math.min(t,5999);const e=["","M","MM","MMM","MMMM","MMMMM"],i=["","C","CC","CCC","CD","D","DC","DCC","DCCC","CM"],n=["","X","XX","XXX","XL","L","LX","LXX","LXXX","XC"],s=["","I","II","III","IV","V","VI","VII","VIII","IX"],r=Math.floor(t/1e3),o=Math.floor(t%1e3/100),l=Math.floor(t%100/10),c=t%10;return e[r]+i[o]+n[l]+s[c]}toLetter(t){return"abcdefghijklmnopqrstuvwxyz"[Math.max(0,Math.min(t,26)-1)]}insertAlignment(t){const e=this.selection.getValue(),i=this.selection.getState().start,n=this.selection.lf,s=n+n,r="."+t+n,o=e.substring(0,i).lastIndexOf(s);if(o===-1)this.selection.setValue(r+e),this.selection.setCursor(i+r.length);else{const l=o+s.length;this.selection.setValue(e.substring(0,l)+r+e.substring(l)),this.selection.setCursor(i+r.length)}}}class k{constructor(){h(this,"listeners",new Map)}on(t,e){this.listeners.has(t)||this.listeners.set(t,new Set),this.listeners.get(t).add(e)}off(t,e){var i;(i=this.listeners.get(t))==null||i.delete(e)}emit(t,e){var i;(i=this.listeners.get(t))==null||i.forEach(n=>{try{n(e)}catch(s){console.error(`[TexyEditor] Event handler error for "${t}":`,s)}})}removeAll(){this.listeners.clear()}}class v{constructor(t=100){h(this,"stack",[]);h(this,"pointer",-1);h(this,"maxSteps");this.maxSteps=t}push(t){this.stack=this.stack.slice(0,this.pointer+1),this.stack.push(t),this.stack.length>this.maxSteps&&this.stack.shift(),this.pointer=this.stack.length-1}undo(){return this.pointer<=0?null:(this.pointer--,this.stack[this.pointer])}redo(){return this.pointer>=this.stack.length-1?null:(this.pointer++,this.stack[this.pointer])}canUndo(){return this.pointer>0}canRedo(){return this.pointer<this.stack.length-1}clear(){this.stack=[],this.pointer=-1}}const A={bold:"Ctrl+B",italic:"Ctrl+I",link:"Ctrl+K",undo:"Ctrl+Z",redo:"Ctrl+Shift+Z",submit:"Ctrl+S",fullscreen:"F11",indent:"Tab",unindent:"Shift+Tab"};class w{constructor(t,e){h(this,"bindings",[]);h(this,"boundHandler",null);this.textarea=t,this.customShortcuts=e}register(t,e){var s;const i=((s=this.customShortcuts)==null?void 0:s[t])??A[t];if(!i)return;const n=this.parseShortcut(i);n&&(n.handler=e,this.bindings.push(n))}attach(){this.boundHandler=t=>this.handleKeydown(t),this.textarea.addEventListener("keydown",this.boundHandler)}detach(){this.boundHandler&&(this.textarea.removeEventListener("keydown",this.boundHandler),this.boundHandler=null),this.bindings=[]}handleKeydown(t){for(const e of this.bindings){const i=t.ctrlKey===e.ctrl||t.metaKey===e.ctrl,n=t.shiftKey===e.shift,s=t.altKey===e.alt,r=t.key.toLowerCase()===e.key.toLowerCase();if(i&&n&&s&&r){t.preventDefault(),t.stopPropagation(),e.handler();return}}}parseShortcut(t){const e=t.split("+").map(i=>i.trim());return e.length===0?null:{key:e[e.length-1],ctrl:e.some(i=>i.toLowerCase()==="ctrl"),shift:e.some(i=>i.toLowerCase()==="shift"),alt:e.some(i=>i.toLowerCase()==="alt"),handler:()=>{}}}}const B={bold:'<path d="M6 4h5a3 3 0 0 1 0 6H6zm0 6h6a3 3 0 0 1 0 6H6z" stroke="currentColor" stroke-width="2" fill="none"/>',italic:'<path d="M10 4h4M8 20h4M14 4l-4 16" stroke="currentColor" stroke-width="2" fill="none" stroke-linecap="round"/>',deleted:'<path d="M6 12h12M7 6h10a2 2 0 0 1 0 4H7M7 14h10a2 2 0 0 1 0 4H7" stroke="currentColor" stroke-width="1.5" fill="none"/>',inserted:'<path d="M6 18h12" stroke="currentColor" stroke-width="2" stroke-linecap="round"/><path d="M8 4v10M16 4v10" stroke="currentColor" stroke-width="1.5" fill="none"/>',superscript:'<path d="M4 18l6-10 6 10M15 4h4l-4 5h4" stroke="currentColor" stroke-width="1.5" fill="none" stroke-linecap="round"/>',subscript:'<path d="M4 4l6 10 6-10M15 16h4l-4 5h4" stroke="currentColor" stroke-width="1.5" fill="none" stroke-linecap="round"/>',code:'<path d="M8 7l-4 5 4 5M16 7l4 5-4 5" stroke="currentColor" stroke-width="2" fill="none" stroke-linecap="round" stroke-linejoin="round"/>',codeBlock:'<rect x="3" y="3" width="18" height="18" rx="2" stroke="currentColor" stroke-width="1.5" fill="none"/><path d="M8 9l-2 3 2 3M16 9l2 3-2 3" stroke="currentColor" stroke-width="1.5" fill="none" stroke-linecap="round"/>',heading1:'<path d="M4 6v12M12 6v12M4 12h8M17 8v10M15 8h4" stroke="currentColor" stroke-width="2" fill="none" stroke-linecap="round"/>',heading2:'<path d="M4 6v12M12 6v12M4 12h8" stroke="currentColor" stroke-width="2" fill="none" stroke-linecap="round"/><path d="M15 9a2 2 0 0 1 4 0c0 2-4 3-4 5h4" stroke="currentColor" stroke-width="1.5" fill="none" stroke-linecap="round"/>',heading3:'<path d="M4 6v12M12 6v12M4 12h8" stroke="currentColor" stroke-width="2" fill="none" stroke-linecap="round"/><path d="M15 8h3l-2 3 2 0a2 2 0 0 1-3 2" stroke="currentColor" stroke-width="1.5" fill="none" stroke-linecap="round"/>',heading4:'<path d="M4 6v12M12 6v12M4 12h8" stroke="currentColor" stroke-width="2" fill="none" stroke-linecap="round"/><path d="M15 8v5h4M19 8v8" stroke="currentColor" stroke-width="1.5" fill="none" stroke-linecap="round"/>',link:'<path d="M10 14a3.5 3.5 0 0 0 5-5l-1-1M14 10a3.5 3.5 0 0 0-5 5l1 1" stroke="currentColor" stroke-width="2" fill="none" stroke-linecap="round"/>',image:'<rect x="3" y="5" width="18" height="14" rx="2" stroke="currentColor" stroke-width="1.5" fill="none"/><circle cx="8.5" cy="10" r="1.5" fill="currentColor"/><path d="M21 15l-5-5L5 19" stroke="currentColor" stroke-width="1.5" fill="none"/>',ul:'<path d="M9 6h11M9 12h11M9 18h11" stroke="currentColor" stroke-width="2" stroke-linecap="round"/><circle cx="4.5" cy="6" r="1.5" fill="currentColor"/><circle cx="4.5" cy="12" r="1.5" fill="currentColor"/><circle cx="4.5" cy="18" r="1.5" fill="currentColor"/>',ol:'<path d="M10 6h10M10 12h10M10 18h10" stroke="currentColor" stroke-width="2" stroke-linecap="round"/><text x="4" y="8" font-size="8" fill="currentColor" font-family="sans-serif">1</text><text x="4" y="14" font-size="8" fill="currentColor" font-family="sans-serif">2</text><text x="4" y="20" font-size="8" fill="currentColor" font-family="sans-serif">3</text>',blockquote:'<path d="M6 10c0-3 2-5 5-5M13 10c0-3 2-5 5-5" stroke="currentColor" stroke-width="2" fill="none" stroke-linecap="round"/><path d="M6 14c0 3 2 5 5 5M13 14c0 3 2 5 5 5" stroke="currentColor" stroke-width="2" fill="none" stroke-linecap="round"/>',hr:'<path d="M3 12h18" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-dasharray="2 3"/>',table:'<rect x="3" y="4" width="18" height="16" rx="2" stroke="currentColor" stroke-width="1.5" fill="none"/><path d="M3 10h18M3 16h18M10 4v16" stroke="currentColor" stroke-width="1.5"/>',color:'<circle cx="12" cy="12" r="8" stroke="currentColor" stroke-width="1.5" fill="none"/><circle cx="12" cy="12" r="3" fill="currentColor"/>',symbol:'<text x="6" y="17" font-size="16" fill="currentColor" font-family="serif">&#937;</text>',acronym:'<path d="M4 16h4l2-6 2 6h4" stroke="currentColor" stroke-width="1.5" fill="none" stroke-linecap="round"/><path d="M6 13h4" stroke="currentColor" stroke-width="1.5"/><path d="M16 8a2 2 0 1 0 4 0 2 2 0 0 0-4 0" stroke="currentColor" stroke-width="1.5" fill="none"/>',alignLeft:'<path d="M4 6h16M4 10h10M4 14h16M4 18h10" stroke="currentColor" stroke-width="2" stroke-linecap="round"/>',alignRight:'<path d="M4 6h16M10 10h10M4 14h16M10 18h10" stroke="currentColor" stroke-width="2" stroke-linecap="round"/>',alignCenter:'<path d="M4 6h16M7 10h10M4 14h16M7 18h10" stroke="currentColor" stroke-width="2" stroke-linecap="round"/>',alignJustify:'<path d="M4 6h16M4 10h16M4 14h16M4 18h16" stroke="currentColor" stroke-width="2" stroke-linecap="round"/>',indent:'<path d="M12 6h8M12 12h8M12 18h8M4 8l4 4-4 4" stroke="currentColor" stroke-width="2" fill="none" stroke-linecap="round" stroke-linejoin="round"/>',unindent:'<path d="M12 6h8M12 12h8M12 18h8M8 8l-4 4 4 4" stroke="currentColor" stroke-width="2" fill="none" stroke-linecap="round" stroke-linejoin="round"/>',undo:'<path d="M4 8h12a4 4 0 0 1 0 8H8" stroke="currentColor" stroke-width="2" fill="none" stroke-linecap="round"/><path d="M7 5l-3 3 3 3" stroke="currentColor" stroke-width="2" fill="none" stroke-linecap="round" stroke-linejoin="round"/>',redo:'<path d="M20 8H8a4 4 0 0 0 0 8h8" stroke="currentColor" stroke-width="2" fill="none" stroke-linecap="round"/><path d="M17 5l3 3-3 3" stroke="currentColor" stroke-width="2" fill="none" stroke-linecap="round" stroke-linejoin="round"/>',fullscreen:'<path d="M4 9V4h5M15 4h5v5M20 15v5h-5M9 20H4v-5" stroke="currentColor" stroke-width="2" fill="none" stroke-linecap="round" stroke-linejoin="round"/>',preview:'<path d="M1 12s4-7 11-7 11 7 11 7-4 7-11 7S1 12 1 12z" stroke="currentColor" stroke-width="1.5" fill="none"/><circle cx="12" cy="12" r="3" stroke="currentColor" stroke-width="1.5" fill="none"/>',edit:'<path d="M14 3l3 3L7 16l-4 1 1-4L14 3z" stroke="currentColor" stroke-width="1.5" fill="none" stroke-linejoin="round"/>',splitView:'<rect x="3" y="4" width="18" height="16" rx="2" stroke="currentColor" stroke-width="1.5" fill="none"/><path d="M12 4v16" stroke="currentColor" stroke-width="1.5"/>',upload:'<path d="M12 16V4M8 8l4-4 4 4" stroke="currentColor" stroke-width="2" fill="none" stroke-linecap="round" stroke-linejoin="round"/><path d="M4 14v4a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2v-4" stroke="currentColor" stroke-width="2" fill="none" stroke-linecap="round"/>'};function H(a){const t=B[a];return t?`<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="18" height="18" class="te-icon">${t}</svg>`:""}class y{constructor(t,e){h(this,"toolbar");h(this,"bottomBar");this.strings=t,this.actionHandler=e}build(t){this.toolbar=document.createElement("div"),this.toolbar.className="te-toolbar",this.toolbar.setAttribute("role","toolbar"),this.toolbar.setAttribute("aria-label","Editor toolbar");for(const e of t){const i=this.renderItem(e);i&&this.toolbar.appendChild(i)}return this.toolbar}buildBottomBar(t,e,i){this.bottomBar=document.createElement("div"),this.bottomBar.className="te-bottom-bar";const n=document.createElement("div");n.className="te-bottom-left";for(const l of t)n.appendChild(this.createButton(l));this.bottomBar.appendChild(n);const s=document.createElement("div");s.className="te-bottom-right";const r=document.createElement("div");r.className="te-bottom-right-edit";for(const l of e)r.appendChild(this.createButton(l));s.appendChild(r);const o=document.createElement("div");o.className="te-bottom-right-preview",o.style.display="none";for(const l of i)o.appendChild(this.createButton(l));return s.appendChild(o),this.bottomBar.appendChild(s),this.bottomBar}renderItem(t){return t===null?this.createSeparator():typeof t=="string"?this.createButton(t):this.isGroup(t)?this.createDropdown(t):this.isCustomButton(t)?this.createCustomButton(t):null}createButton(t){const e=document.createElement("button");e.type="button",e.className=`te-btn te-btn-${t}`,e.setAttribute("data-action",t);const i=this.strings[t]??t;e.setAttribute("aria-label",i),e.setAttribute("title",i);const n=H(t);return n?e.innerHTML=n:e.textContent=i,e.addEventListener("click",s=>{s.preventDefault(),this.actionHandler(t)}),e}createCustomButton(t){const e=document.createElement("button");return e.type="button",e.className=`te-btn te-btn-custom te-btn-${t.name}`,e.setAttribute("aria-label",t.label),e.setAttribute("title",t.label),t.icon?e.innerHTML=t.icon:e.textContent=t.label,e.addEventListener("click",i=>{i.preventDefault(),this.actionHandler(t.name)}),e}createSeparator(){const t=document.createElement("span");return t.className="te-separator",t.setAttribute("role","separator"),t.setAttribute("aria-orientation","vertical"),t}createDropdown(t){const e=document.createElement("div");e.className="te-dropdown";const i=document.createElement("button");i.type="button",i.className="te-btn te-dropdown-trigger",i.setAttribute("aria-haspopup","true"),i.setAttribute("aria-expanded","false"),i.textContent=t.label??"...";const n=document.createElement("div");n.className="te-dropdown-menu",n.setAttribute("role","menu");for(const s of t.items){const r=this.createButton(s);r.setAttribute("role","menuitem"),n.appendChild(r)}return i.addEventListener("click",s=>{s.preventDefault(),s.stopPropagation();const r=e.classList.toggle("te-dropdown-open");i.setAttribute("aria-expanded",String(r))}),document.addEventListener("click",()=>{e.classList.remove("te-dropdown-open"),i.setAttribute("aria-expanded","false")}),e.appendChild(i),e.appendChild(n),e}isGroup(t){return typeof t=="object"&&t!==null&&"type"in t&&t.type==="group"}isCustomButton(t){return typeof t=="object"&&t!==null&&"type"in t&&t.type==="button"}}class C{constructor(t,e){h(this,"openDialogs",new Map);this.container=t,this.strings=e}open(t,e){this.close(t);const i=document.createElement("dialog");i.className="te-dialog",e.width&&(i.style.width=e.width+"px");const n=document.createElement("div");n.className="te-dialog-header";const s=document.createElement("h3");s.className="te-dialog-title",s.textContent=e.title,n.appendChild(s);const r=document.createElement("button");r.type="button",r.className="te-dialog-close",r.setAttribute("aria-label",this.strings.close),r.innerHTML="&times;",r.addEventListener("click",()=>this.close(t)),n.appendChild(r),i.appendChild(n);const o=document.createElement("div");o.className="te-dialog-body",o.appendChild(e.content),i.appendChild(o);const l=document.createElement("div");l.className="te-dialog-footer";const c=document.createElement("button");c.type="button",c.className="te-btn te-dialog-cancel",c.textContent=this.strings.cancel,c.addEventListener("click",()=>{var p;(p=e.onCancel)==null||p.call(e),this.close(t)}),l.appendChild(c);const u=document.createElement("button");u.type="button",u.className="te-btn te-dialog-submit",u.textContent=this.strings.ok,u.addEventListener("click",()=>{e.onSubmit(),this.close(t)}),l.appendChild(u),i.appendChild(l),i.addEventListener("cancel",p=>{var g;p.preventDefault(),(g=e.onCancel)==null||g.call(e),this.close(t)}),this.container.appendChild(i),this.openDialogs.set(t,i),i.showModal();const d=i.querySelector("input, select, textarea");return d==null||d.focus(),i}close(t){const e=this.openDialogs.get(t);e&&(e.close(),e.remove(),this.openDialogs.delete(t))}closeAll(){for(const[t]of this.openDialogs)this.close(t)}isOpen(t){return this.openDialogs.has(t)}}class M{constructor(t={}){h(this,"options");h(this,"plugins");this.options={rules:t.rules??[],plugins:t.plugins??[],enableTypography:t.enableTypography??!0,enableAutolinks:t.enableAutolinks??!0},this.plugins=this.options.plugins}addPlugin(t){this.plugins.push(t)}removePlugin(t){this.plugins=this.plugins.filter(e=>e.name!==t)}getPlugins(){return this.plugins}parse(t){if(!t.trim())return"";let e=t.replace(/\r\n/g,`
7
+ `).replace(/\r/g,`
8
+ `);for(const s of this.plugins)s.preprocess&&(e=s.preprocess(e));const i=this.options.rules.filter(s=>s.block).sort((s,r)=>s.priority-r.priority);for(const s of i)e=s.block(e);let n=this.parseBlocks(e);for(const s of this.plugins)s.postprocess&&(n=s.postprocess(n));return n}parseBlocks(t){const e=[],i=this.splitBlockSegments(t);for(const n of i){const s=n.trim();if(!s)continue;const r=this.tryCommentBlock(s)??this.tryCodeBlock(s)??this.tryHtmlBlock(s)??this.tryDivBlock(s)??this.tryTextBlock(s)??this.tryTable(s)??this.tryHeading(s)??this.tryHorizontalRule(s)??this.tryBlockquote(s)??this.tryList(s)??this.tryParagraph(s);r&&e.push(r)}return e.join(`
9
+ `)}splitBlockSegments(t){const e=[],i=t.split(`
10
+ `);let n=[],s=!1;for(const r of i)if(!s&&/^\/--/.test(r)){if(n.length>0){const o=n.join(`
11
+ `);e.push(...this.splitParagraphs(o)),n=[]}s=!0,n.push(r)}else s&&/^\\--/.test(r)?(n.push(r),e.push(n.join(`
12
+ `)),n=[],s=!1):n.push(r);if(n.length>0){const r=n.join(`
13
+ `);s?e.push(r):e.push(...this.splitParagraphs(r))}return e}splitParagraphs(t){return t.split(/\n{2,}/).filter(e=>e.trim())}tryCommentBlock(t){return t.match(/^\/--comment[ \t]*\n([\s\S]*?)(?:\n\\--|$)/)?"<!-- comment -->":null}tryCodeBlock(t){const e=t.match(/^\/--code[ \t]*(\S*)[ \t]*\n([\s\S]*?)(?:\n\\--|$)/);if(!e)return null;const i=e[1],n=this.escapeHtml(e[2]);return`<pre><code${i?` class="language-${this.escapeHtml(i)}"`:""}>${n}</code></pre>`}tryHtmlBlock(t){const e=t.match(/^\/--html[ \t]*\n([\s\S]*?)(?:\n\\--|$)/);return e?e[1]:null}tryDivBlock(t){const e=t.match(/^\/--div[ \t]*(.*?)[ \t]*\n([\s\S]*?)(?:\n\\--|$)/);if(!e)return null;const i=e[1],n=this.parseBlocks(e[2]);return`<div${i?this.parseModifierAttrs(i):""}>
14
+ ${n}
15
+ </div>`}tryTextBlock(t){const e=t.match(/^\/--text[ \t]*\n([\s\S]*?)(?:\n\\--|$)/);return e?`<pre>${this.escapeHtml(e[1])}</pre>`:null}tryTable(t){const e=t.split(`
16
+ `);if(!e[0].match(/^\|/))return null;const i=o=>/^\|/.test(o.trim())||/^\|[-=]+/.test(o.trim());if(!e.every(o=>!o.trim()||i(o)))return null;let n=`<table>
17
+ `,s=!1,r=!1;for(const o of e){const l=o.trim();if(!l)continue;if(/^\|[-=]+/.test(l)){s&&!r&&(n+=`</thead>
18
+ <tbody>
19
+ `,r=!0);continue}const c=this.parseTableRow(l);if(!c)continue;c.some(d=>d.isHeader)&&!r&&!s&&(n+=`<thead>
20
+ `,s=!0),n+="<tr>";for(const d of c){const p=d.isHeader?"th":"td",g=this.parseInline(d.content.trim());n+=`<${p}>${g}</${p}>`}n+=`</tr>
21
+ `}return s&&!r&&(n+=`</thead>
22
+ `),r&&(n+=`</tbody>
23
+ `),n+="</table>",n}parseTableRow(t){let e=t.trim();return e.startsWith("|")?(e=e.substring(1),e.endsWith("|")&&(e=e.substring(0,e.length-1)),e.split("|").map(n=>{const s=n.trim();return s.startsWith("*")?{content:s.substring(1).trim(),isHeader:!0}:{content:s,isHeader:!1}})):null}tryHeading(t){const e=t.split(`
24
+ `),i={"#":1,"*":2,"=":3,"-":4},n=e[0].match(/^(#{3,}|\*{3,}|={3,}|-{3,})\s+(.+?)\s+\1\s*$/);if(n){const s=n[1][0],r=i[s];if(r){const o=this.parseInline(n[2]),l=e.slice(1).join(`
25
+ `).trim();let c=`<h${r}>${o}</h${r}>`;return l&&(c+=`
26
+ `+this.parseBlocks(l)),c}}if(e.length>=2){const s=e[0],o=e[1].match(/^(#{3,}|\*{3,}|={3,}|-{3,})\s*$/);if(o&&s.trim()){const l=o[1][0],c=i[l];if(c){const u=this.parseInline(s.trim()),d=e.slice(2).join(`
27
+ `).trim();let p=`<h${c}>${u}</h${c}>`;return d&&(p+=`
28
+ `+this.parseBlocks(d)),p}}}return null}tryHorizontalRule(t){return/^-{3,}\s*$/.test(t.trim())&&!t.includes(`
29
+ `)||/^\*{3,}\s*$/.test(t.trim())&&!t.includes(`
30
+ `)?"<hr>":null}tryBlockquote(t){const e=t.split(`
31
+ `);if(!e[0].match(/^>\s/)||!e.every(r=>/^>\s?/.test(r)||!r.trim()))return null;const n=e.map(r=>r.replace(/^>\s?/,"")).join(`
32
+ `);return`<blockquote>
33
+ ${this.parseBlocks(n)}
34
+ </blockquote>`}tryList(t){const e=t.split(`
35
+ `);if(/^[-*+]\s/.test(e[0]))return this.parseListItems(e,/^[-*+]\s/,"ul");if(/^(\d+[).]|[a-z][).]|[A-Z][).]|[IVXLCDM]+[).]|[ivxlcdm]+[).])\s/.test(e[0])){const i=e[0];let n="";return/^\d/.test(i)?n="":/^[ivxlcdm]+\)/i.test(i)&&/^[IV]/.test(i)?n=' type="I"':/^[ivxlcdm]+\)/.test(i)?n=' type="i"':/^[A-Z]\)/.test(i)?n=' type="A"':/^[a-z]\)/.test(i)&&(n=' type="a"'),this.parseListItems(e,/^(?:\d+[).]|[a-zA-Z][).]|[IVXLCDM]+[).]|[ivxlcdm]+[).])\s/,"ol",n)}return null}parseListItems(t,e,i,n=""){const s=[];let r=[];for(const l of t)e.test(l)?(r.length>0&&s.push(r.join(`
36
+ `)),r=[l.replace(e,"")]):/^\s+/.test(l)&&r.length>0?r.push(l.replace(/^\s+/,"")):!l.trim()&&r.length>0?r.push(""):r.push(l);r.length>0&&s.push(r.join(`
37
+ `));let o=`<${i}${n}>
38
+ `;for(const l of s){const c=l.trim(),u=this.tryList(c);u&&c!==l.trim()?o+=`<li>${u}</li>
39
+ `:o+=`<li>${this.parseInline(c)}</li>
40
+ `}return o+=`</${i}>`,o}tryParagraph(t){const e=t.match(/^\.([\<\>\=]+)\n([\s\S]+)$/);if(e){const n=this.parseAlignModifier(e[1]),s=this.parseInline(e[2].trim());return`<p${n}>${s}</p>`}return`<p>${this.parseInline(t)}</p>`}parseAlignModifier(t){switch(t){case"<":return' style="text-align:left"';case">":return' style="text-align:right"';case"<>":return' style="text-align:center"';case"=":return' style="text-align:justify"';default:return""}}parseInline(t){const e=new Map;let i=0;const n=o=>{const l=`\0PH${i++}\0`;return e.set(l,o),l};let s=t;s=s.replace(/''(.+?)''/g,(o,l)=>n(l)),s=s.replace(/`([^`]+)`/g,(o,l)=>n(`<code>${this.escapeHtml(l)}</code>`)),s=s.replace(/\[\*\s+(\S+?)(?:\s+\.\(([^)]*)\))?\s*([<>*]?)\s*\]/g,(o,l,c,u)=>{let d="";u==="<"?d=' style="float:left"':u===">"&&(d=' style="float:right"');const p=c?` alt="${this.escapeHtml(c)}"`:' alt=""';return n(`<img src="${this.escapeHtml(l)}"${p}${d}>`)});const r=this.options.rules.filter(o=>o.inline).sort((o,l)=>o.priority-l.priority);for(const o of r)s=o.inline(s);for(const o of this.plugins)o.processInline&&(s=o.processInline(s,n));s=s.replace(/"([^"]+?)":((?:https?:\/\/|ftp:\/\/|mailto:)\S+)/g,(o,l,c)=>n(`<a href="${this.escapeHtml(c)}">${this.escapeHtml(l)}</a>`)),s=s.replace(/"([^"]+?)":(\S+)/g,(o,l,c)=>n(`<a href="${this.escapeHtml(c)}">${this.escapeHtml(l)}</a>`)),s=s.replace(/(\w+)\(\(([^)]+)\)\)/g,(o,l,c)=>n(`<abbr title="${this.escapeHtml(c)}">${this.escapeHtml(l)}</abbr>`)),s=s.replace(/"(.+?)"\s*\.\{([^}]+)\}/g,(o,l,c)=>n(`<span style="${this.escapeHtml(c)}">${this.escapeHtml(l)}</span>`)),s=s.replace(/"(.+?)"\s*\.\[([^\]]+)\]/g,(o,l,c)=>n(`<span class="${this.escapeHtml(c)}">${this.escapeHtml(l)}</span>`)),this.options.enableTypography&&(s=this.applyTypography(s)),s=this.escapeHtmlPreservingPlaceholders(s,e),s=s.replace(/\*\*(.+?)\*\*/g,"<strong>$1</strong>"),s=s.replace(new RegExp("(?<!\\*)\\*([^*]+?)\\*(?!\\*)","g"),"<em>$1</em>"),s=s.replace(/--(.+?)--/g,"<del>$1</del>"),s=s.replace(/\+\+(.+?)\+\+/g,"<ins>$1</ins>"),s=s.replace(/\^\^(.+?)\^\^/g,"<sup>$1</sup>"),s=s.replace(/__(.+?)__/g,"<sub>$1</sub>"),s=s.replace(/&gt;&gt;(.+?)&lt;&lt;/g,"<q>$1</q>"),this.options.enableAutolinks&&(s=s.replace(new RegExp('(?<![&"=\\/])(?:https?:\\/\\/|www\\.)[^\\s<>&]+',"g"),o=>`<a href="${o.startsWith("www.")?`https://${o}`:o}">${o}</a>`)),s=s.replace(/\n/g,`<br>
41
+ `);for(const[o,l]of e)s=s.replace(o,l);return s}escapeHtmlPreservingPlaceholders(t,e){const i=[];let n=t;for(;n.length>0;){const s=n.indexOf("\0PH");if(s===-1){i.push(this.escapeHtml(n));break}s>0&&i.push(this.escapeHtml(n.substring(0,s)));const r=n.indexOf("\0",s+1);if(r===-1){i.push(this.escapeHtml(n.substring(s)));break}const o=n.substring(s,r+1);e.has(o)?i.push(o):i.push(this.escapeHtml(n.substring(s,r+1))),n=n.substring(r+1)}return i.join("")}applyTypography(t){return t=t.replace(/<->/g,"↔"),t=t.replace(/<=>/g,"⇔"),t=t.replace(/-->/g,"→"),t=t.replace(/<--/g,"←"),t=t.replace(/==>/g,"⇒"),t=t.replace(/<== /g,"⇐ "),t=t.replace(/---/g,"—"),t=t.replace(/(\w)--(\w)/g,"$1–$2"),t=t.replace(/\s--\s/g," – "),t=t.replace(/\.\.\./g,"…"),t=t.replace(/\(tm\)/gi,"™"),t=t.replace(/\(c\)/gi,"©"),t=t.replace(/\(r\)/gi,"®"),t=t.replace(/(\d)\s*x\s*(\d)/g,"$1×$2"),t=t.replace(/\b([ksvzuoiaKSVZUOIA]) /g,"$1 "),t}parseModifierAttrs(t){const e=[],i=t.match(/\{([^}]+)\}/);i&&e.push(`style="${this.escapeHtml(i[1])}"`);const n=t.match(/\[([^\]]+)\]/);n&&e.push(`class="${this.escapeHtml(n[1])}"`);const s=t.match(/#([a-zA-Z][\w-]*)/);s&&e.push(`id="${this.escapeHtml(s[1])}"`);const r=t.match(/\(([^)]+)\)/);return r&&e.push(`title="${this.escapeHtml(r[1])}"`),e.length>0?" "+e.join(" "):""}escapeHtml(t){return t.replace(/&/g,"&amp;").replace(/</g,"&lt;").replace(/>/g,"&gt;").replace(/"/g,"&quot;")}}function T(a={}){const t=a.width??560,e=a.height??315,i=a.allowFullscreen!==!1,n=a.wrapperClass??"texy-youtube";return{name:"youtube",preprocess(s){return s=s.replace(/\[\*\s+youtube:([a-zA-Z0-9_-]+)\s*\*?\]/g,"{{youtube:$1}}"),s=s.replace(/\[\*\s+(https?:\/\/(?:www\.)?youtube\.com\/watch\?v=([a-zA-Z0-9_-]+)[^\s]*)\s*\*?\]/g,(r,o,l)=>`{{youtube:${l}}}`),s=s.replace(/\[\*\s+https?:\/\/(?:www\.)?youtube\.com\/shorts\/([a-zA-Z0-9_-]+)[^\s]*\s*\*?\]/g,"{{youtube:$1}}"),s=s.replace(/\[\*\s+https?:\/\/youtu\.be\/([a-zA-Z0-9_-]+)[^\s]*\s*\*?\]/g,"{{youtube:$1}}"),s},processInline(s,r){return s.replace(/\{\{youtube:([a-zA-Z0-9_-]+)\}\}/g,(o,l)=>{const u=`<div class="${n}"><iframe width="${t}" height="${e}" src="https://www.youtube.com/embed/${l}" frameborder="0"${i?" allowfullscreen":""}></iframe></div>`;return r(u)})}}}function I(a){const t=a.baseUrl.replace(/\/$/,""),e=a.format??"gif",i=a.ariaLabels??{},n=a.className??"smiley";return{name:"smiley",processInline(s,r){return s.replace(new RegExp("(?<!\\*)\\*(\\d{1,6})\\*(?!\\*)","g"),(o,l)=>{const c=i[l]??"smiley",u=`<img src="${t}/${l}.${e}" alt="${c}" class="${n}">`;return r(u)})}}}function P(a){const t=a.redirectUrl,e=a.excludeDomains??[],i=a.targetBlank!==!1,n=a.noopener!==!1;return{name:"link-redirect",postprocess(s){return s.replace(/<a\s+href="(https?:\/\/[^"]+)"/g,(r,o)=>{try{const u=new URL(o).hostname;if(e.some(d=>u===d||u.endsWith("."+d)))return r}catch{return r}const l=encodeURIComponent(o),c=[`href="${t}?url=${l}"`];return i&&c.push('target="_blank"'),n&&c.push('rel="noopener noreferrer"'),`<a ${c.join(" ")}`})}}}function D(){return{name:"bbcode",processInline(a,t){return a=a.replace(/\[b\]([\s\S]*?)\[\/b\]/gi,(e,i)=>t(`<strong>${i}</strong>`)),a=a.replace(/\[i\]([\s\S]*?)\[\/i\]/gi,(e,i)=>t(`<em>${i}</em>`)),a=a.replace(/\[u\]([\s\S]*?)\[\/u\]/gi,(e,i)=>t(`<u>${i}</u>`)),a=a.replace(/\[s\]([\s\S]*?)\[\/s\]/gi,(e,i)=>t(`<del>${i}</del>`)),a=a.replace(/\[url=([^\]]+)\]([\s\S]*?)\[\/url\]/gi,(e,i,n)=>t(`<a href="${i}">${n}</a>`)),a=a.replace(/\[color=([^\]]+)\]([\s\S]*?)\[\/color\]/gi,(e,i,n)=>t(`<span style="color:${i}">${n}</span>`)),a}}}function N(a={}){const t=a.maxWidth??"300px",e=a.className??"texy-image",i=a.enableLinkedImages!==!1;return{name:"image-embed",preprocess(n){return i&&(n=n.replace(/"\[\*\s+(\S+?)(?:\s+\.\(([^)]*)\))?\s*\*?\]"\s*:\s*(\S+)/g,(s,r,o,l)=>{const c=o?`|alt:${o}`:"";return`{{imglink:${r}${c}|${l}}}`})),n},processInline(n,s){return n=n.replace(/\{\{imglink:(\S+?)(?:\|alt:([^|]*))?\|(\S+)\}\}/g,(r,o,l,c)=>{const u=l?` alt="${l}"`:' alt=""',d=`<a href="${c}"><img src="${o}"${u} class="${e}" style="max-width:${t}"></a>`;return s(d)}),n}}}const $={bold:"Tučně",italic:"Kurzíva",deleted:"Přeškrtnuté",inserted:"Podtržené",superscript:"Horní index",subscript:"Dolní index",code:"Kód",codeBlock:"Blok kódu",heading1:"Nadpis 1",heading2:"Nadpis 2",heading3:"Nadpis 3",heading4:"Nadpis 4",link:"Odkaz",linkUrl:"URL odkazu",linkText:"Text odkazu",image:"Obrázek",imageUrl:"URL obrázku",imageAlt:"Popis obrázku",imageAlign:"Zarovnání",unorderedList:"Odrážky",orderedList:"Číslovaný seznam",blockquote:"Citace",horizontalRule:"Vodorovná čára",table:"Tabulka",tableRows:"Řádky",tableCols:"Sloupce",color:"Barva textu",symbol:"Speciální znak",acronym:"Zkratka",acronymTitle:"Význam zkratky",alignLeft:"Zarovnat vlevo",alignRight:"Zarovnat vpravo",alignCenter:"Zarovnat na střed",alignJustify:"Zarovnat do bloku",indent:"Odsadit",unindent:"Zrušit odsazení",fullscreen:"Celá obrazovka",preview:"Náhled",edit:"Upravit",splitView:"Rozdělený pohled",undo:"Zpět",redo:"Vpřed",upload:"Nahrát soubor",uploadDragDrop:"Přetáhněte soubor sem nebo klikněte",previewEmpty:"Zadejte text pro zobrazení náhledu.",previewLoading:"Načítání náhledu…",headingPrompt:"Text nadpisu",ok:"OK",cancel:"Zrušit",close:"Zavřít"},x={bold:"Bold",italic:"Italic",deleted:"Strikethrough",inserted:"Underline",superscript:"Superscript",subscript:"Subscript",code:"Inline code",codeBlock:"Code block",heading1:"Heading 1",heading2:"Heading 2",heading3:"Heading 3",heading4:"Heading 4",link:"Link",linkUrl:"Link URL",linkText:"Link text",image:"Image",imageUrl:"Image URL",imageAlt:"Image description",imageAlign:"Alignment",unorderedList:"Bullet list",orderedList:"Numbered list",blockquote:"Blockquote",horizontalRule:"Horizontal rule",table:"Table",tableRows:"Rows",tableCols:"Columns",color:"Text color",symbol:"Special character",acronym:"Acronym",acronymTitle:"Acronym meaning",alignLeft:"Align left",alignRight:"Align right",alignCenter:"Align center",alignJustify:"Justify",indent:"Indent",unindent:"Outdent",fullscreen:"Fullscreen",preview:"Preview",edit:"Edit",splitView:"Split view",undo:"Undo",redo:"Redo",upload:"Upload file",uploadDragDrop:"Drag & drop a file here or click",previewEmpty:"Enter some text to see preview.",previewLoading:"Loading preview…",headingPrompt:"Heading text",ok:"OK",cancel:"Cancel",close:"Close"},m={cs:$,en:x};function L(a){return m[a]??m.en}function R(a,t){m[a]=t}const V=["bold","italic",null,"ul","ol",null,"link","image",null,"blockquote","code","codeBlock",null,"heading1","heading2","heading3",null,"hr","table",null,"color","symbol"],z={language:"cs",defaultView:"edit",width:"100%",livePreview:!0,livePreviewDelay:400,theme:"light",splitView:!1,fullscreen:!1,autoResize:!0,maxUndoSteps:100,texyCfg:""};class U{constructor(t,e={}){h(this,"container");h(this,"textarea");h(this,"editDiv");h(this,"previewDiv");h(this,"previewContent");h(this,"selection");h(this,"formatter");h(this,"events");h(this,"undoManager");h(this,"keyboard");h(this,"toolbarBuilder");h(this,"dialogManager");h(this,"strings");h(this,"options");h(this,"currentView","edit");h(this,"isFullscreen",!1);h(this,"parser");h(this,"plugins",[]);h(this,"previewDebounceTimer",null);h(this,"lastPreviewedValue","");h(this,"destroyed",!1);h(this,"actions",{});if(typeof t=="string"){const i=document.querySelector(t);if(!i)throw new Error(`TexyEditor: textarea "${t}" not found`);this.textarea=i}else this.textarea=t;if(!this.textarea.dataset.texyEditor){if(this.textarea.dataset.texyEditor="true",this.options={...z,...e},this.strings=L(this.options.language??"cs"),this.selection=new f(this.textarea),this.formatter=new b(this.selection),this.events=new k,this.undoManager=new v(this.options.maxUndoSteps),this.keyboard=new w(this.textarea,this.options.shortcuts),this.parser=new M,this.registerActions(),this.toolbarBuilder=new y(this.strings,i=>this.execAction(i)),this.buildDOM(),this.applyTheme(),this.options.cssVars)for(const[i,n]of Object.entries(this.options.cssVars))this.container.style.setProperty(i,n);if(this.setupKeyboard(),this.options.autoResize&&this.setupAutoResize(),this.setupUndoTracking(),this.currentView=this.options.defaultView??"edit",this.options.plugins)for(const i of this.options.plugins)this.loadPlugin(i);this.undoManager.push({value:this.textarea.value,cursorStart:0,cursorEnd:0})}}getValue(){return this.textarea.value}setValue(t){this.textarea.value=t,this.textarea.dispatchEvent(new Event("input",{bubbles:!0}))}getSelection(){return this.selection.text()}replaceSelection(t){this.selection.replace(t)}wrapSelection(t,e){this.selection.tag(t,e)}insertAtCursor(t){this.selection.isCursor()?this.selection.tag(t,""):this.selection.replace(t)}focus(){this.textarea.focus()}setView(t){this.currentView=t,this.updateView(),this.events.emit("view:change",{mode:t})}getView(){return this.currentView}execAction(t){const e=this.actions[t];e&&(e(),this.events.emit("toolbar:action",{button:t}))}on(t,e){this.events.on(t,e)}off(t,e){this.events.off(t,e)}undo(){const t=this.undoManager.undo();t&&(this.textarea.value=t.value,this.selection.select(t.cursorStart,t.cursorEnd-t.cursorStart),this.events.emit("undo",void 0))}redo(){const t=this.undoManager.redo();t&&(this.textarea.value=t.value,this.selection.select(t.cursorStart,t.cursorEnd-t.cursorStart),this.events.emit("redo",void 0))}openWindow(t){}closeWindow(t){this.dialogManager.close(t)}toggleFullscreen(){this.isFullscreen=!this.isFullscreen,this.container.classList.toggle("te-fullscreen",this.isFullscreen),this.events.emit("fullscreen:toggle",{active:this.isFullscreen})}getTextarea(){return this.textarea}getContainer(){return this.container}getStrings(){return this.strings}getDialogManager(){return this.dialogManager}getFormatter(){return this.formatter}getSelectionManager(){return this.selection}destroy(){var e;if(this.destroyed)return;this.destroyed=!0;for(const i of this.plugins)(e=i.destroy)==null||e.call(i);this.keyboard.detach(),this.dialogManager.closeAll();const t=this.container.parentNode;t&&(t.insertBefore(this.textarea,this.container),t.removeChild(this.container)),delete this.textarea.dataset.texyEditor,this.events.emit("destroy",void 0),this.events.removeAll()}buildDOM(){var n;this.container=document.createElement("div"),this.container.className="te-editor",this.options.width&&(this.container.style.width=this.options.width),this.options.ariaLabel&&this.container.setAttribute("aria-label",this.options.ariaLabel);const t=this.options.toolbar??V,e=this.toolbarBuilder.build(t);this.container.appendChild(e),this.editDiv=document.createElement("div"),this.editDiv.className="te-edit-area",this.container.appendChild(this.editDiv),this.previewDiv=document.createElement("div"),this.previewDiv.className="te-preview-area",this.previewDiv.style.display="none",this.previewContent=document.createElement("div"),this.previewContent.className="te-preview-content",this.previewDiv.appendChild(this.previewContent),this.container.appendChild(this.previewDiv);const i=this.toolbarBuilder.buildBottomBar(this.options.bottomLeftToolbar??["edit","preview","splitView"],this.options.bottomRightEditToolbar??["undo","redo"],this.options.bottomRightPreviewToolbar??[]);this.container.appendChild(i),this.dialogManager=new C(this.container,this.strings),(n=this.textarea.parentNode)==null||n.insertBefore(this.container,this.textarea),this.editDiv.appendChild(this.textarea),this.textarea.classList.add("te-textarea")}applyTheme(){const t=this.options.theme??"light";this.container.classList.add(`te-theme-${t}`),this.container.setAttribute("data-te-theme",t)}updateView(){const t=this.currentView==="edit",e=this.currentView==="preview",i=this.currentView==="split";this.editDiv.style.display=t||i?"":"none",this.previewDiv.style.display=e||i?"":"none",this.container.classList.toggle("te-view-edit",t),this.container.classList.toggle("te-view-preview",e),this.container.classList.toggle("te-view-split",i),this.container.querySelectorAll(".te-bottom-left .te-btn").forEach(n=>{const s=n.dataset.action;n.classList.toggle("te-btn-active",s===this.currentView)}),t||this.renderPreview()}renderPreview(){const t=this.textarea.value;if(!t.trim()){this.previewContent.innerHTML=`<p class="te-preview-empty">${this.strings.previewEmpty}</p>`;return}t!==this.lastPreviewedValue&&(this.lastPreviewedValue=t,this.options.previewPath?(this.previewContent.innerHTML=`<p class="te-preview-loading">${this.strings.previewLoading}</p>`,this.fetchServerPreview(t)):this.options.livePreview&&(this.previewContent.innerHTML=`<div class="te-preview-rendered">${this.parser.parse(t)}</div>`))}async fetchServerPreview(t){try{const i=await(await fetch(this.options.previewPath,{method:"POST",headers:{"Content-Type":"application/x-www-form-urlencoded"},body:`texy=${encodeURIComponent(t)}&cfg=${encodeURIComponent(this.options.texyCfg??"")}`})).text();this.previewContent.innerHTML=i,this.events.emit("preview:render",{html:i})}catch{this.previewContent.innerHTML='<p class="te-preview-error">Preview loading failed.</p>'}}registerActions(){this.actions={bold:()=>this.formatter.bold(),italic:()=>this.formatter.italic(),deleted:()=>this.formatter.deleted(),inserted:()=>this.formatter.inserted(),superscript:()=>this.formatter.superscript(),subscript:()=>this.formatter.subscript(),code:()=>this.formatter.inlineCode(),codeBlock:()=>this.formatter.codeBlock(),heading1:()=>this.handleHeading(1),heading2:()=>this.handleHeading(2),heading3:()=>this.handleHeading(3),heading4:()=>this.handleHeading(4),link:()=>this.handleLink(),image:()=>this.handleImage(),ul:()=>this.formatter.unorderedList(),ol:()=>this.formatter.orderedList(),blockquote:()=>this.formatter.blockquote(),hr:()=>this.formatter.horizontalRule(),table:()=>this.handleTable(),color:()=>this.handleColor(),symbol:()=>this.handleSymbol(),acronym:()=>this.handleAcronym(),alignLeft:()=>this.formatter.alignLeft(),alignRight:()=>this.formatter.alignRight(),alignCenter:()=>this.formatter.alignCenter(),alignJustify:()=>this.formatter.alignJustify(),indent:()=>this.formatter.indent(),unindent:()=>this.formatter.unindent(),undo:()=>this.undo(),redo:()=>this.redo(),fullscreen:()=>this.toggleFullscreen(),edit:()=>this.setView("edit"),preview:()=>this.setView("preview"),splitView:()=>this.setView("split")}}handleHeading(t){if(this.selection.isCursor()){const e=prompt(this.strings.headingPrompt,"");e&&this.formatter.headingWithPrompt(t,e)}else this.formatter.heading(t)}handleLink(){const t=document.createElement("div");t.className="te-dialog-form";const e=this.createFormField(t,this.strings.linkUrl,"url","https://"),i=this.createFormField(t,this.strings.linkText,"text",this.selection.text());this.dialogManager.open("link",{title:this.strings.link,width:400,content:t,onSubmit:()=>{this.formatter.link(e.value,i.value||void 0),this.focus()}})}handleImage(){const t=document.createElement("div");t.className="te-dialog-form";const e=this.createFormField(t,this.strings.imageUrl,"url",""),i=this.createFormField(t,this.strings.imageAlt,"text",""),n=document.createElement("label");n.className="te-form-label",n.textContent=this.strings.imageAlign,t.appendChild(n);const s=document.createElement("select");s.className="te-form-input";for(const[r,o]of[["*","---"],["<","← "+this.strings.alignLeft],[">",this.strings.alignRight+" →"],["<>","↔ "+this.strings.alignCenter]]){const l=document.createElement("option");l.value=r,l.textContent=o,s.appendChild(l)}t.appendChild(s),this.dialogManager.open("image",{title:this.strings.image,width:420,content:t,onSubmit:()=>{const r=s.value;this.formatter.image(e.value,i.value||void 0,r),this.focus()}})}handleTable(){const t=document.createElement("div");t.className="te-dialog-form";const e=this.createFormField(t,this.strings.tableCols,"number","3"),i=this.createFormField(t,this.strings.tableRows,"number","3");this.dialogManager.open("table",{title:this.strings.table,width:320,content:t,onSubmit:()=>{const n=parseInt(e.value)||3,s=parseInt(i.value)||3;this.formatter.table(n,s,"top"),this.focus()}})}handleColor(){const t=["red","blue","green","orange","purple","brown","navy","teal","gray","black","#e74c3c","#3498db","#2ecc71","#f39c12","#9b59b6","#1abc9c","#e67e22","#95a5a6","#34495e","#d35400"],e=document.createElement("div");e.className="te-color-grid";for(const i of t){const n=document.createElement("button");n.type="button",n.className="te-color-swatch",n.style.backgroundColor=i,n.setAttribute("data-color",i),n.setAttribute("title",i),n.addEventListener("click",()=>{this.formatter.colorModifier(i),this.dialogManager.close("color"),this.focus()}),e.appendChild(n)}this.dialogManager.open("color",{title:this.strings.color,width:300,content:e,onSubmit:()=>{}})}handleSymbol(){const t=["&","@","§","©","®","™","°","±","×","÷","€","£","¥","¢","‰","†","‡","¶","•","…","←","→","↑","↓","↔","⇒","⇐","⇔","≈","≠","≤","≥","∞","∑","∏","∫","√","∂","∆","∇","α","β","γ","δ","ε","π","σ","τ","φ","ω","♠","♣","♥","♦","★","☆","✓","✗","♪","♫"],e=document.createElement("div");e.className="te-symbol-grid";for(const i of t){const n=document.createElement("button");n.type="button",n.className="te-symbol-btn",n.textContent=i,n.setAttribute("title",i),n.addEventListener("click",()=>{this.formatter.insertSymbol(i),this.dialogManager.close("symbol"),this.focus()}),e.appendChild(n)}this.dialogManager.open("symbol",{title:this.strings.symbol,width:400,content:e,onSubmit:()=>{}})}handleAcronym(){const t=prompt(this.strings.acronymTitle,"");t&&this.formatter.acronym(t)}setupKeyboard(){this.keyboard.register("bold",()=>this.execAction("bold")),this.keyboard.register("italic",()=>this.execAction("italic")),this.keyboard.register("link",()=>this.execAction("link")),this.keyboard.register("undo",()=>this.undo()),this.keyboard.register("redo",()=>this.redo()),this.keyboard.register("fullscreen",()=>this.toggleFullscreen()),this.keyboard.register("indent",()=>this.formatter.indent()),this.keyboard.register("unindent",()=>this.formatter.unindent()),this.keyboard.attach()}setupUndoTracking(){let t=null;this.textarea.addEventListener("input",()=>{t&&clearTimeout(t),t=setTimeout(()=>{this.undoManager.push({value:this.textarea.value,cursorStart:this.textarea.selectionStart,cursorEnd:this.textarea.selectionEnd})},300),this.events.emit("change",{value:this.textarea.value}),this.currentView!=="edit"&&this.options.livePreview&&(this.previewDebounceTimer&&clearTimeout(this.previewDebounceTimer),this.previewDebounceTimer=setTimeout(()=>this.renderPreview(),this.options.livePreviewDelay??400))})}setupAutoResize(){const t=()=>{this.textarea.style.height="auto",this.textarea.style.height=this.textarea.scrollHeight+"px"};this.textarea.addEventListener("input",t),requestAnimationFrame(t)}loadPlugin(t){t.init(this),this.plugins.push(t),this.events.emit("plugin:init",{name:t.name})}createFormField(t,e,i,n){const s=document.createElement("div");s.className="te-form-group";const r=document.createElement("label");r.className="te-form-label",r.textContent=e,s.appendChild(r);const o=document.createElement("input");return o.type=i,o.className="te-form-input",o.value=n,s.appendChild(o),t.appendChild(s),o}}exports.DialogManager=C;exports.EventBus=k;exports.KeyboardManager=w;exports.Selection=f;exports.TexyEditor=U;exports.TexyFormatter=b;exports.TexyParser=M;exports.ToolbarBuilder=y;exports.UndoManager=v;exports.bbcodePlugin=D;exports.cs=$;exports.en=x;exports.getStrings=L;exports.imageEmbedPlugin=N;exports.linkRedirectPlugin=P;exports.registerLanguage=R;exports.smileyPlugin=I;exports.youtubePlugin=T;
42
+ //# sourceMappingURL=texy-editor.cjs.map