trotl-texteditor 1.0.10 → 1.0.13

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/index.cjs CHANGED
@@ -87,6 +87,16 @@ function CodeBlock({
87
87
  }));
88
88
  }
89
89
 
90
+ const DEFAULT_HTML = `
91
+ <h1>Welcome</h1>
92
+ <p>This is a lightweight WYSIWYG editor built without external libraries.</p>
93
+ <ul>
94
+ <li>Edit text directly</li>
95
+ <li>Use the toolbar for formatting</li>
96
+ <li>Copy, paste, and export HTML</li>
97
+ </ul>
98
+ `;
99
+
90
100
  const COMMANDS = [{
91
101
  cmd: "bold",
92
102
  label: "Bold",
@@ -124,24 +134,287 @@ const COMMANDS = [{
124
134
  label: "Right",
125
135
  icon: "⟹"
126
136
  }];
137
+
138
+ const escapeHtml = value => value.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/\"/g, "&quot;").replace(/'/g, "&#039;");
139
+ const exec = (editorRef, cmd, value = null, setHtml, onChange, updateToolbarState) => {
140
+ editorRef.current?.focus();
141
+ document.execCommand(cmd, false, value);
142
+ const nextHtml = editorRef.current?.innerHTML || "";
143
+ setHtml(nextHtml);
144
+ onChange({
145
+ html: nextHtml,
146
+ text: editorRef.current?.innerText || ""
147
+ });
148
+ updateToolbarState();
149
+ };
150
+ const clearAll = (editorRef, setHtml, onChange, updateToolbarState) => {
151
+ if (!editorRef.current) return;
152
+ editorRef.current.innerHTML = "<p></p>";
153
+ setHtml(editorRef.current.innerHTML);
154
+ onChange({
155
+ html: editorRef.current.innerHTML,
156
+ text: editorRef.current.innerText || ""
157
+ });
158
+ updateToolbarState();
159
+ };
160
+ const exportHtml = async editorRef => {
161
+ const payload = editorRef.current?.innerHTML || "";
162
+ await navigator.clipboard.writeText(payload);
163
+ alert("HTML copied to clipboard.");
164
+ };
165
+
166
+ const applyHeading = (next, exec) => {
167
+ if (next === "p") {
168
+ exec("formatBlock", "P");
169
+ return;
170
+ }
171
+ if (next === "pre") {
172
+ exec("formatBlock", "PRE");
173
+ return;
174
+ }
175
+ exec("formatBlock", next.toUpperCase());
176
+ };
177
+ const applyFontSize = (next, exec, editorRef, setHtml, updateToolbarState) => {
178
+ const selection = window.getSelection();
179
+ if (!selection || selection.rangeCount === 0) return;
180
+ const range = selection.getRangeAt(0);
181
+ if (range.collapsed) return; // No selection
182
+
183
+ exec("fontSize", "7");
184
+ const fontElements = editorRef.current?.querySelectorAll("font[size='7']") || [];
185
+ fontElements.forEach(el => {
186
+ el.removeAttribute("size");
187
+ el.style.fontSize = `${next}px`;
188
+ });
189
+ setHtml(editorRef.current?.innerHTML || "");
190
+ updateToolbarState();
191
+ };
192
+ const applyCustomFontSize = (value, exec, editorRef, setHtml, updateToolbarState) => {
193
+ if (!value || isNaN(value)) return;
194
+ const selection = window.getSelection();
195
+ if (!selection || selection.rangeCount === 0) return;
196
+ const range = selection.getRangeAt(0);
197
+ if (range.collapsed) return; // No selection
198
+
199
+ exec("fontSize", "7");
200
+ const fontElements = editorRef.current?.querySelectorAll("font[size='7']") || [];
201
+ fontElements.forEach(el => {
202
+ el.removeAttribute("size");
203
+ el.style.fontSize = `${value}px`;
204
+ });
205
+ setHtml(editorRef.current?.innerHTML || "");
206
+ updateToolbarState();
207
+ };
208
+ const applyTextColor = (next, exec) => {
209
+ exec("foreColor", next);
210
+ };
211
+ const applyBackgroundColor = (next, exec) => {
212
+ exec("hiliteColor", next);
213
+ };
214
+ const resetColors = (theme, setTextColor, setBackgroundColor, exec) => {
215
+ const defaultText = theme === "dark" ? "#e5e7eb" : "#1f2937";
216
+ const defaultBg = "#ffff00";
217
+ setTextColor(defaultText);
218
+ setBackgroundColor(defaultBg);
219
+ exec("removeFormat");
220
+ };
221
+
222
+ const insertCodeBlock = (codeValue, codeLanguage, codeTheme, exec, setIsCodeModalOpen, setCodeValue) => {
223
+ const safeCode = escapeHtml(codeValue || "");
224
+ const languageClass = codeLanguage ? `language-${codeLanguage}` : "language-plain";
225
+ const htmlBlock = `<pre class="code-block-shell" data-theme="${codeTheme}"><code class="${languageClass}">${safeCode}</code></pre>`;
226
+ exec("insertHTML", htmlBlock);
227
+ setIsCodeModalOpen(false);
228
+ setCodeValue("");
229
+ };
230
+ const insertLink = exec => {
231
+ const url = window.prompt("Enter URL", "https://");
232
+ if (!url) return;
233
+ exec("createLink", url);
234
+ };
235
+ const insertTooltip = (editorRef, setHtml, onChange, updateToolbarState) => {
236
+ const selection = window.getSelection();
237
+ if (!selection || selection.rangeCount === 0) return;
238
+ const selectedText = selection.toString();
239
+ if (!selectedText) {
240
+ alert("Please select some text first");
241
+ return;
242
+ }
243
+ const tooltip = window.prompt("Enter tooltip/note text", "");
244
+ if (!tooltip) return;
245
+ const range = selection.getRangeAt(0);
246
+ const span = document.createElement("span");
247
+ span.className = "has-tooltip";
248
+ span.title = tooltip;
249
+ try {
250
+ range.surroundContents(span);
251
+ } catch {
252
+ // If surroundContents fails, use alternative method
253
+ span.appendChild(range.extractContents());
254
+ range.insertNode(span);
255
+ }
256
+ selection.removeAllRanges();
257
+ const nextHtml = editorRef.current?.innerHTML || "";
258
+ setHtml(nextHtml);
259
+ onChange({
260
+ html: nextHtml,
261
+ text: editorRef.current?.innerText || ""
262
+ });
263
+ updateToolbarState();
264
+ };
265
+ const insertAnchor = exec => {
266
+ const id = window.prompt("Enter anchor ID", "");
267
+ if (!id) return;
268
+ const sanitizedId = id.replace(/[^a-zA-Z0-9-_]/g, "");
269
+ const anchorHtml = `<span class="anchor-marker" id="${sanitizedId}" data-anchor="${sanitizedId}">⚓${sanitizedId}</span>`;
270
+ exec("insertHTML", anchorHtml);
271
+ };
272
+ const insertHr = exec => {
273
+ exec("insertHTML", "<hr>");
274
+ };
275
+ const insertImage = exec => {
276
+ const url = window.prompt("Enter image URL", "https://");
277
+ if (!url) return;
278
+ const alt = window.prompt("Enter image description (alt text)", "");
279
+ const imageHtml = `<img src="${url}" alt="${alt || ''}" style="max-width: 100%; height: auto;" />`;
280
+ exec("insertHTML", imageHtml);
281
+ };
282
+ const insertVideo = exec => {
283
+ const url = window.prompt("Enter video URL (YouTube, MP4, etc.)", "https://");
284
+ if (!url) return;
285
+
286
+ // Check if it's a YouTube URL
287
+ const youtubeMatch = url.match(/(?:youtube\.com\/watch\?v=|youtu\.be\/)([^&\s]+)/);
288
+ if (youtubeMatch) {
289
+ const videoId = youtubeMatch[1];
290
+ const embedHtml = `<iframe width="560" height="315" src="https://www.youtube.com/embed/${videoId}" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen style="max-width: 100%;"></iframe>`;
291
+ exec("insertHTML", embedHtml);
292
+ } else {
293
+ // Assume it's a direct video URL
294
+ const videoHtml = `<video controls style="max-width: 100%; height: auto;"><source src="${url}" type="video/mp4">Your browser does not support the video tag.</video>`;
295
+ exec("insertHTML", videoHtml);
296
+ }
297
+ };
298
+
299
+ const onInput = (editorRef, setHtml, onChange, updateToolbarState) => {
300
+ const nextHtml = editorRef.current?.innerHTML || "";
301
+ setHtml(nextHtml);
302
+ onChange({
303
+ html: nextHtml,
304
+ text: editorRef.current?.innerText || ""
305
+ });
306
+ updateToolbarState();
307
+ };
308
+ const onPaste = (event, plainPaste) => {
309
+ if (!plainPaste) return;
310
+ event.preventDefault();
311
+ const text = event.clipboardData.getData("text/plain");
312
+ document.execCommand("insertText", false, text);
313
+ };
314
+ const createUpdateToolbarState = (editorRef, setSelectionText, setActiveToolMap) => {
315
+ return () => {
316
+ const selection = document.getSelection();
317
+ if (!selection || selection.rangeCount === 0 || !editorRef.current) return;
318
+ const anchorNode = selection.anchorNode;
319
+ const isInsideEditor = anchorNode && editorRef.current.contains(anchorNode);
320
+ if (!isInsideEditor) {
321
+ setSelectionText("");
322
+ setActiveToolMap({});
323
+ return;
324
+ }
325
+ setSelectionText(selection.toString());
326
+ const nextActive = {};
327
+ COMMANDS.forEach(item => {
328
+ try {
329
+ nextActive[item.cmd] = document.queryCommandState(item.cmd);
330
+ } catch {
331
+ nextActive[item.cmd] = false;
332
+ }
333
+ });
334
+ setActiveToolMap(nextActive);
335
+ };
336
+ };
337
+
127
338
  function Editor({
128
339
  theme = "dark",
129
340
  tools = COMMANDS.map(item => item.cmd),
341
+ showDemoText = false,
130
342
  showHeading = true,
131
343
  showFontSize = true,
132
344
  showAlign = true,
133
345
  showLink = true,
346
+ showTooltip = true,
347
+ showAnchor = true,
348
+ showHr = true,
349
+ showMedia = true,
134
350
  showUndoRedo = true,
135
351
  showClear = true,
136
352
  showPlainPaste = true,
137
353
  showColor = true,
354
+ showFullscreen = true,
138
355
  codeBlock = false,
139
356
  codeBlockLanguages,
140
357
  showLiveHtml = true,
141
358
  showStats = true,
142
359
  showCopyHtml = true,
143
- onChange = () => {}
360
+ onChange = () => {},
361
+ initialHtml = "",
362
+ t
144
363
  }) {
364
+ let translate;
365
+ if (t) {
366
+ translate = t;
367
+ } else {
368
+ translate = key => {
369
+ const translations = {
370
+ sample: "Sample Text",
371
+ Undo: "Undo",
372
+ Redo: "Redo",
373
+ Format: "Format",
374
+ Paragraph: "Paragraph",
375
+ "Heading 1": "Heading 1",
376
+ "Heading 2": "Heading 2",
377
+ "Heading 3": "Heading 3",
378
+ "Heading 4": "Heading 4",
379
+ Blockquote: "Blockquote",
380
+ "Code Block": "Code Block",
381
+ Size: "Size",
382
+ Custom: "Custom",
383
+ Font: "Font",
384
+ "Text color": "Text color",
385
+ Background: "Background",
386
+ "Background color": "Background color",
387
+ "Reset colors": "Reset colors",
388
+ "Insert code block": "Insert code block",
389
+ Align: "Align",
390
+ "Align Left": "Align Left",
391
+ "Align Center": "Align Center",
392
+ "Align Right": "Align Right",
393
+ Justify: "Justify",
394
+ "Add tooltip/note to selected text": "Add tooltip/note to selected text",
395
+ "Insert anchor": "Insert anchor",
396
+ "Insert horizontal line": "Insert horizontal line",
397
+ "Insert picture": "Insert picture",
398
+ "Insert video": "Insert video",
399
+ "Clear format": "Clear format",
400
+ "Clear all": "Clear all",
401
+ "Paste as plain text": "Paste as plain text",
402
+ "Exit fullscreen": "Exit fullscreen",
403
+ "Enter fullscreen": "Enter fullscreen",
404
+ words: "words",
405
+ characters: "characters",
406
+ selected: "selected",
407
+ "No selection": "No selection",
408
+ "Copy HTML": "Copy HTML",
409
+ "Live HTML": "Live HTML",
410
+ "Insert Code Block": "Insert Code Block",
411
+ "Paste your code here": "Paste your code here",
412
+ Cancel: "Cancel",
413
+ Insert: "Insert"
414
+ };
415
+ return translations[key] || key;
416
+ };
417
+ }
145
418
  const editorRef = React.useRef(null);
146
419
  const [html, setHtml] = React.useState("");
147
420
  const [plainPaste, setPlainPaste] = React.useState(true);
@@ -152,6 +425,7 @@ function Editor({
152
425
  const [textColor, setTextColor] = React.useState("#1f2937");
153
426
  const [backgroundColor, setBackgroundColor] = React.useState("#ffff00");
154
427
  const [activeToolMap, setActiveToolMap] = React.useState({});
428
+ const [isFullscreen, setIsFullscreen] = React.useState(false);
155
429
  const [isCodeModalOpen, setIsCodeModalOpen] = React.useState(false);
156
430
  const [codeValue, setCodeValue] = React.useState("");
157
431
  const [codeLanguage, setCodeLanguage] = React.useState("js");
@@ -160,12 +434,8 @@ function Editor({
160
434
  setCodeTheme(theme);
161
435
  }, [theme]);
162
436
 
163
- // useEffect(() => {
164
- // if (editorRef.current) {
165
- // editorRef.current.innerHTML = DEFAULT_HTML;
166
- // setHtml(editorRef.current.innerHTML);
167
- // }
168
- // }, []);
437
+ // NOTE: initialization for `initialHtml` / demo text moved below `updateToolbarState` declaration to avoid referencing
438
+ // `updateToolbarState` before it is defined. See the effect inserted later in the file.
169
439
 
170
440
  const stats = React.useMemo(() => {
171
441
  const text = editorRef.current?.innerText || "";
@@ -175,144 +445,128 @@ function Editor({
175
445
  chars: text.length
176
446
  };
177
447
  }, [html]);
178
- const exec = (cmd, value = null) => {
179
- editorRef.current?.focus();
180
- document.execCommand(cmd, false, value);
181
- const nextHtml = editorRef.current?.innerHTML || "";
182
- setHtml(nextHtml);
183
- onChange({
184
- html: nextHtml,
185
- text: editorRef.current?.innerText || ""
186
- });
187
- updateToolbarState();
448
+ const exec$1 = (cmd, value = null) => {
449
+ exec(editorRef, cmd, value, setHtml, onChange, updateToolbarState);
188
450
  };
189
- const applyHeading = next => {
451
+ const applyHeading$1 = next => {
190
452
  setHeading(next);
191
- if (next === "p") {
192
- exec("formatBlock", "P");
193
- return;
194
- }
195
- if (next === "pre") {
196
- exec("formatBlock", "PRE");
197
- return;
198
- }
199
- exec("formatBlock", next.toUpperCase());
453
+ applyHeading(next, exec$1);
200
454
  };
201
- const applyFontSize = next => {
455
+ const applyFontSize$1 = next => {
202
456
  if (next === "custom") {
203
457
  setFontSize("custom");
204
458
  return;
205
459
  }
206
460
  setFontSize(next);
207
- exec("fontSize", "7");
208
- const fontElements = editorRef.current?.querySelectorAll("font[size='7']") || [];
209
- fontElements.forEach(el => {
210
- el.removeAttribute("size");
211
- el.style.fontSize = `${next}px`;
212
- });
213
- setHtml(editorRef.current?.innerHTML || "");
214
- updateToolbarState();
461
+ applyFontSize(next, exec$1, editorRef, setHtml, updateToolbarState);
215
462
  };
216
- const applyCustomFontSize = value => {
463
+ const applyCustomFontSize$1 = value => {
217
464
  setCustomSize(value);
218
- if (!value || isNaN(value)) return;
219
- exec("fontSize", "7");
220
- const fontElements = editorRef.current?.querySelectorAll("font[size='7']") || [];
221
- fontElements.forEach(el => {
222
- el.removeAttribute("size");
223
- el.style.fontSize = `${value}px`;
224
- });
225
- setHtml(editorRef.current?.innerHTML || "");
226
- updateToolbarState();
465
+ applyCustomFontSize(value, exec$1, editorRef, setHtml, updateToolbarState);
227
466
  };
228
- const applyTextColor = next => {
467
+ const applyTextColor$1 = next => {
229
468
  setTextColor(next);
230
- exec("foreColor", next);
469
+ applyTextColor(next, exec$1);
231
470
  };
232
- const applyBackgroundColor = next => {
471
+ const applyBackgroundColor$1 = next => {
233
472
  setBackgroundColor(next);
234
- exec("hiliteColor", next);
473
+ applyBackgroundColor(next, exec$1);
235
474
  };
236
- const resetColors = () => {
237
- const defaultText = theme === "dark" ? "#e5e7eb" : "#1f2937";
238
- const defaultBg = "#ffff00";
239
- setTextColor(defaultText);
240
- setBackgroundColor(defaultBg);
241
- exec("removeFormat");
475
+ const resetColors$1 = () => {
476
+ resetColors(theme, setTextColor, setBackgroundColor, exec$1);
242
477
  };
243
- const escapeHtml = value => value.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/\"/g, "&quot;").replace(/'/g, "&#039;");
244
- const insertCodeBlock = () => {
245
- const safeCode = escapeHtml(codeValue || "");
246
- const languageClass = codeLanguage ? `language-${codeLanguage}` : "language-plain";
247
- const htmlBlock = `<pre class="code-block-shell" data-theme="${codeTheme}"><code class="${languageClass}">${safeCode}</code></pre>`;
248
- exec("insertHTML", htmlBlock);
249
- setIsCodeModalOpen(false);
250
- setCodeValue("");
478
+ const insertCodeBlock$1 = () => {
479
+ insertCodeBlock(codeValue, codeLanguage, codeTheme, exec$1, setIsCodeModalOpen, setCodeValue);
251
480
  };
252
- const insertLink = () => {
253
- const url = window.prompt("Enter URL", "https://");
254
- if (!url) return;
255
- exec("createLink", url);
481
+ const insertLink$1 = () => {
482
+ insertLink(exec$1);
256
483
  };
257
- const removeFormat = () => exec("removeFormat");
258
- const clearAll = () => {
259
- if (!editorRef.current) return;
260
- editorRef.current.innerHTML = "<p></p>";
261
- setHtml(editorRef.current.innerHTML);
262
- onChange({
263
- html: editorRef.current.innerHTML,
264
- text: editorRef.current.innerText || ""
265
- });
266
- updateToolbarState();
484
+ const insertTooltip$1 = () => {
485
+ insertTooltip(editorRef, setHtml, onChange, updateToolbarState);
267
486
  };
268
- const onInput = () => {
269
- const nextHtml = editorRef.current?.innerHTML || "";
270
- setHtml(nextHtml);
271
- onChange({
272
- html: nextHtml,
273
- text: editorRef.current?.innerText || ""
274
- });
275
- updateToolbarState();
487
+ const insertAnchor$1 = () => {
488
+ insertAnchor(exec$1);
276
489
  };
277
- const onPaste = event => {
278
- if (!plainPaste) return;
279
- event.preventDefault();
280
- const text = event.clipboardData.getData("text/plain");
281
- document.execCommand("insertText", false, text);
490
+ const insertHr$1 = () => {
491
+ insertHr(exec$1);
282
492
  };
283
- const updateToolbarState = React.useCallback(() => {
284
- const selection = document.getSelection();
285
- if (!selection || selection.rangeCount === 0 || !editorRef.current) return;
286
- const anchorNode = selection.anchorNode;
287
- const isInsideEditor = anchorNode && editorRef.current.contains(anchorNode);
288
- if (!isInsideEditor) {
289
- setSelectionText("");
290
- setActiveToolMap({});
291
- return;
292
- }
293
- setSelectionText(selection.toString());
294
- const nextActive = {};
295
- COMMANDS.forEach(item => {
296
- try {
297
- nextActive[item.cmd] = document.queryCommandState(item.cmd);
298
- } catch {
299
- nextActive[item.cmd] = false;
493
+ const insertImage$1 = () => {
494
+ insertImage(exec$1);
495
+ };
496
+ const insertVideo$1 = () => {
497
+ insertVideo(exec$1);
498
+ };
499
+ const toggleFullscreen = () => {
500
+ setIsFullscreen(!isFullscreen);
501
+ };
502
+ const removeFormat = () => exec$1("removeFormat");
503
+ const clearAll$1 = () => {
504
+ clearAll(editorRef, setHtml, onChange, updateToolbarState);
505
+ };
506
+ const onInput$1 = () => {
507
+ onInput(editorRef, setHtml, onChange, updateToolbarState);
508
+ // Add key hint to all links
509
+ const links = editorRef.current?.querySelectorAll('a') || [];
510
+ links.forEach(link => {
511
+ if (!link.getAttribute('data-key-hint')) {
512
+ link.setAttribute('data-key-hint', 'Ctrl/Shift + Click to follow');
300
513
  }
301
514
  });
302
- setActiveToolMap(nextActive);
303
- }, []);
515
+ };
516
+ const onPaste$1 = event => {
517
+ onPaste(event, plainPaste);
518
+ };
519
+ const onEditorClick = event => {
520
+ if (event.target.tagName === 'A' && (event.ctrlKey || event.shiftKey || event.metaKey)) {
521
+ const href = event.target.getAttribute('href');
522
+ if (href) {
523
+ event.preventDefault();
524
+ window.open(href, '_blank');
525
+ }
526
+ }
527
+ };
528
+ const updateToolbarState = React.useCallback(createUpdateToolbarState(editorRef, setSelectionText, setActiveToolMap), []);
304
529
  React.useEffect(() => {
305
530
  document.addEventListener("selectionchange", updateToolbarState);
306
531
  return () => document.removeEventListener("selectionchange", updateToolbarState);
307
532
  }, [updateToolbarState]);
308
- const exportHtml = async () => {
309
- const payload = editorRef.current?.innerHTML || "";
310
- await navigator.clipboard.writeText(payload);
311
- alert("HTML copied to clipboard.");
533
+
534
+ // Initialize editor from `initialHtml` if provided; otherwise (when showDemoText)
535
+ // populate with demo content. Run after `updateToolbarState` is defined.
536
+ React.useEffect(() => {
537
+ if (initialHtml && editorRef.current) {
538
+ editorRef.current.innerHTML = initialHtml;
539
+ setHtml(initialHtml);
540
+ const links = editorRef.current?.querySelectorAll('a') || [];
541
+ links.forEach(link => {
542
+ if (!link.getAttribute('data-key-hint')) {
543
+ link.setAttribute('data-key-hint', 'Ctrl/Shift + Click to follow');
544
+ }
545
+ });
546
+ updateToolbarState();
547
+ try {
548
+ onChange(initialHtml);
549
+ } catch {/* noop */}
550
+ return;
551
+ }
552
+ if (showDemoText && editorRef.current) {
553
+ editorRef.current.innerHTML = DEFAULT_HTML;
554
+ setHtml(editorRef.current.innerHTML);
555
+ const links = editorRef.current?.querySelectorAll('a') || [];
556
+ links.forEach(link => {
557
+ if (!link.getAttribute('data-key-hint')) {
558
+ link.setAttribute('data-key-hint', 'Ctrl/Shift + Click to follow');
559
+ }
560
+ });
561
+ updateToolbarState();
562
+ }
563
+ }, [initialHtml, showDemoText, onChange, updateToolbarState]);
564
+ const exportHtml$1 = async () => {
565
+ await exportHtml(editorRef);
312
566
  };
313
567
  const activeCommands = React.useMemo(() => COMMANDS.filter(item => tools.includes(item.cmd)), [tools]);
314
568
  return /*#__PURE__*/React.createElement("section", {
315
- className: `editor-root theme-${theme}${showLiveHtml ? " show-preview" : " no-preview"}`,
569
+ className: `editor-root theme-${theme}${showLiveHtml ? " show-preview" : " no-preview"}${isFullscreen ? " fullscreen" : ""}`,
316
570
  "data-theme": theme
317
571
  }, /*#__PURE__*/React.createElement("div", {
318
572
  className: "toolbar"
@@ -321,13 +575,13 @@ function Editor({
321
575
  }, /*#__PURE__*/React.createElement("button", {
322
576
  type: "button",
323
577
  className: "toolbar-btn",
324
- onClick: () => exec("undo"),
325
- title: "Undo"
578
+ onClick: () => exec$1("undo"),
579
+ title: translate("Undo")
326
580
  }, "\u21B6"), /*#__PURE__*/React.createElement("button", {
327
581
  type: "button",
328
582
  className: "toolbar-btn",
329
- onClick: () => exec("redo"),
330
- title: "Redo"
583
+ onClick: () => exec$1("redo"),
584
+ title: translate("Redo")
331
585
  }, "\u21B7")), /*#__PURE__*/React.createElement("span", {
332
586
  className: "toolbar-separator"
333
587
  }, "|")), activeCommands.length > 0 && /*#__PURE__*/React.createElement(React.Fragment, null, /*#__PURE__*/React.createElement("div", {
@@ -337,8 +591,8 @@ function Editor({
337
591
  type: "button",
338
592
  className: `toolbar-btn${activeToolMap[item.cmd] ? " active" : ""}`,
339
593
  onMouseDown: event => event.preventDefault(),
340
- onClick: () => exec(item.cmd),
341
- title: item.label,
594
+ onClick: () => exec$1(item.cmd),
595
+ title: translate(item.label),
342
596
  "aria-pressed": activeToolMap[item.cmd] || false
343
597
  }, item.icon))), /*#__PURE__*/React.createElement("span", {
344
598
  className: "toolbar-separator"
@@ -346,29 +600,29 @@ function Editor({
346
600
  className: "toolbar-group"
347
601
  }, showHeading && /*#__PURE__*/React.createElement("label", {
348
602
  className: "toolbar-label"
349
- }, "Format", /*#__PURE__*/React.createElement("select", {
603
+ }, translate("Format"), /*#__PURE__*/React.createElement("select", {
350
604
  value: heading,
351
- onChange: event => applyHeading(event.target.value),
605
+ onChange: event => applyHeading$1(event.target.value),
352
606
  className: "toolbar-select"
353
607
  }, /*#__PURE__*/React.createElement("option", {
354
608
  value: "p"
355
- }, "Paragraph"), /*#__PURE__*/React.createElement("option", {
609
+ }, translate("Paragraph")), /*#__PURE__*/React.createElement("option", {
356
610
  value: "h1"
357
- }, "Heading 1"), /*#__PURE__*/React.createElement("option", {
611
+ }, translate("Heading 1")), /*#__PURE__*/React.createElement("option", {
358
612
  value: "h2"
359
- }, "Heading 2"), /*#__PURE__*/React.createElement("option", {
613
+ }, translate("Heading 2")), /*#__PURE__*/React.createElement("option", {
360
614
  value: "h3"
361
- }, "Heading 3"), /*#__PURE__*/React.createElement("option", {
615
+ }, translate("Heading 3")), /*#__PURE__*/React.createElement("option", {
362
616
  value: "h4"
363
- }, "Heading 4"), /*#__PURE__*/React.createElement("option", {
617
+ }, translate("Heading 4")), /*#__PURE__*/React.createElement("option", {
364
618
  value: "blockquote"
365
- }, "Blockquote"), codeBlock && /*#__PURE__*/React.createElement("option", {
619
+ }, translate("Blockquote")), codeBlock && /*#__PURE__*/React.createElement("option", {
366
620
  value: "pre"
367
- }, "Code Block"))), showFontSize && /*#__PURE__*/React.createElement("label", {
621
+ }, translate("Code Block")))), showFontSize && /*#__PURE__*/React.createElement("label", {
368
622
  className: "toolbar-label"
369
- }, "Size", /*#__PURE__*/React.createElement("select", {
623
+ }, translate("Size"), /*#__PURE__*/React.createElement("select", {
370
624
  value: fontSize,
371
- onChange: event => applyFontSize(event.target.value),
625
+ onChange: event => applyFontSize$1(event.target.value),
372
626
  className: "toolbar-select"
373
627
  }, /*#__PURE__*/React.createElement("option", {
374
628
  value: "10"
@@ -396,40 +650,40 @@ function Editor({
396
650
  value: "48"
397
651
  }, "48px"), /*#__PURE__*/React.createElement("option", {
398
652
  value: "custom"
399
- }, "Custom"))), showFontSize && fontSize === "custom" && /*#__PURE__*/React.createElement("label", {
653
+ }, translate("Custom")))), showFontSize && fontSize === "custom" && /*#__PURE__*/React.createElement("label", {
400
654
  className: "toolbar-label"
401
655
  }, /*#__PURE__*/React.createElement("input", {
402
656
  type: "number",
403
657
  min: "8",
404
658
  max: "200",
405
659
  value: customSize,
406
- onChange: event => applyCustomFontSize(event.target.value),
407
- placeholder: "Size",
660
+ onChange: event => applyCustomFontSize$1(event.target.value),
661
+ placeholder: translate("Size"),
408
662
  className: "toolbar-input",
409
663
  style: {
410
664
  width: "60px"
411
665
  }
412
666
  }), "px"), showColor && /*#__PURE__*/React.createElement(React.Fragment, null, /*#__PURE__*/React.createElement("label", {
413
667
  className: "toolbar-label color-label"
414
- }, "Font", /*#__PURE__*/React.createElement("input", {
668
+ }, translate("Font"), /*#__PURE__*/React.createElement("input", {
415
669
  type: "color",
416
670
  className: "toolbar-color",
417
671
  value: textColor,
418
- onChange: event => applyTextColor(event.target.value),
419
- "aria-label": "Text color"
672
+ onChange: event => applyTextColor$1(event.target.value),
673
+ "aria-label": translate("Text color")
420
674
  })), /*#__PURE__*/React.createElement("label", {
421
675
  className: "toolbar-label color-label"
422
- }, "Background", /*#__PURE__*/React.createElement("input", {
676
+ }, translate("Background"), /*#__PURE__*/React.createElement("input", {
423
677
  type: "color",
424
678
  className: "toolbar-color",
425
679
  value: backgroundColor,
426
- onChange: event => applyBackgroundColor(event.target.value),
427
- "aria-label": "Background color"
680
+ onChange: event => applyBackgroundColor$1(event.target.value),
681
+ "aria-label": translate("Background color")
428
682
  })), /*#__PURE__*/React.createElement("button", {
429
683
  type: "button",
430
684
  className: "toolbar-btn toolbar-btn-small",
431
- onClick: resetColors,
432
- title: "Reset colors"
685
+ onClick: resetColors$1,
686
+ title: translate("Reset colors")
433
687
  }, "\u21BA")), codeBlock && /*#__PURE__*/React.createElement("button", {
434
688
  type: "button",
435
689
  className: "toolbar-btn",
@@ -437,48 +691,73 @@ function Editor({
437
691
  setCodeTheme(theme);
438
692
  setIsCodeModalOpen(true);
439
693
  },
440
- title: "Insert code block"
441
- }, "Code Block"), showAlign && /*#__PURE__*/React.createElement("div", {
694
+ title: translate("Insert code block")
695
+ }, translate("Code Block")), showAlign && /*#__PURE__*/React.createElement("div", {
442
696
  className: "toolbar-label"
443
- }, "Align", /*#__PURE__*/React.createElement("div", {
697
+ }, translate("Align"), /*#__PURE__*/React.createElement("div", {
444
698
  className: "align-buttons"
445
699
  }, /*#__PURE__*/React.createElement("button", {
446
700
  type: "button",
447
701
  className: "toolbar-btn toolbar-btn-small",
448
- onClick: () => exec("justifyLeft"),
449
- title: "Align Left"
702
+ onClick: () => exec$1("justifyLeft"),
703
+ title: translate("Align Left")
450
704
  }, "\u27F8"), /*#__PURE__*/React.createElement("button", {
451
705
  type: "button",
452
706
  className: "toolbar-btn toolbar-btn-small",
453
- onClick: () => exec("justifyCenter"),
454
- title: "Align Center"
707
+ onClick: () => exec$1("justifyCenter"),
708
+ title: translate("Align Center")
455
709
  }, "\u21D4"), /*#__PURE__*/React.createElement("button", {
456
710
  type: "button",
457
711
  className: "toolbar-btn toolbar-btn-small",
458
- onClick: () => exec("justifyRight"),
459
- title: "Align Right"
712
+ onClick: () => exec$1("justifyRight"),
713
+ title: translate("Align Right")
460
714
  }, "\u27F9"), /*#__PURE__*/React.createElement("button", {
461
715
  type: "button",
462
716
  className: "toolbar-btn toolbar-btn-small",
463
- onClick: () => exec("justifyFull"),
464
- title: "Justify"
717
+ onClick: () => exec$1("justifyFull"),
718
+ title: translate("Justify")
465
719
  }, "\u21FF")))), /*#__PURE__*/React.createElement("span", {
466
720
  className: "toolbar-separator"
467
- }, "|")), (showLink || showClear) && /*#__PURE__*/React.createElement(React.Fragment, null, /*#__PURE__*/React.createElement("div", {
721
+ }, "|")), (showLink || showTooltip || showAnchor || showHr || showMedia || showClear) && /*#__PURE__*/React.createElement(React.Fragment, null, /*#__PURE__*/React.createElement("div", {
468
722
  className: "toolbar-group"
469
723
  }, showLink && /*#__PURE__*/React.createElement("button", {
470
724
  type: "button",
471
725
  className: "toolbar-btn",
472
- onClick: insertLink
473
- }, "\uD83D\uDD17"), showClear && /*#__PURE__*/React.createElement(React.Fragment, null, /*#__PURE__*/React.createElement("button", {
726
+ onClick: insertLink$1
727
+ }, "\uD83D\uDD17"), showTooltip && /*#__PURE__*/React.createElement("button", {
728
+ type: "button",
729
+ className: "toolbar-btn",
730
+ onClick: insertTooltip$1,
731
+ title: translate("Add tooltip/note to selected text")
732
+ }, "\uD83D\uDCAC"), showAnchor && /*#__PURE__*/React.createElement("button", {
733
+ type: "button",
734
+ className: "toolbar-btn",
735
+ onClick: insertAnchor$1,
736
+ title: translate("Insert anchor")
737
+ }, "\u2693"), showHr && /*#__PURE__*/React.createElement("button", {
738
+ type: "button",
739
+ className: "toolbar-btn",
740
+ onClick: insertHr$1,
741
+ title: translate("Insert horizontal line")
742
+ }, "\u2500"), showMedia && /*#__PURE__*/React.createElement(React.Fragment, null, /*#__PURE__*/React.createElement("button", {
743
+ type: "button",
744
+ className: "toolbar-btn",
745
+ onClick: insertImage$1,
746
+ title: translate("Insert picture")
747
+ }, "\uD83D\uDDBC\uFE0F"), /*#__PURE__*/React.createElement("button", {
748
+ type: "button",
749
+ className: "toolbar-btn",
750
+ onClick: insertVideo$1,
751
+ title: translate("Insert video")
752
+ }, "\uD83C\uDFAC")), showClear && /*#__PURE__*/React.createElement(React.Fragment, null, /*#__PURE__*/React.createElement("button", {
474
753
  type: "button",
475
754
  className: "toolbar-btn",
476
755
  onClick: removeFormat
477
- }, "Clear format"), /*#__PURE__*/React.createElement("button", {
756
+ }, translate("Clear format")), /*#__PURE__*/React.createElement("button", {
478
757
  type: "button",
479
758
  className: "toolbar-btn danger",
480
- onClick: clearAll
481
- }, "Clear all"))), /*#__PURE__*/React.createElement("span", {
759
+ onClick: clearAll$1
760
+ }, translate("Clear all")))), /*#__PURE__*/React.createElement("span", {
482
761
  className: "toolbar-separator"
483
762
  }, "|")), showPlainPaste && /*#__PURE__*/React.createElement("div", {
484
763
  className: "toolbar-group toggle"
@@ -488,14 +767,22 @@ function Editor({
488
767
  type: "checkbox",
489
768
  checked: plainPaste,
490
769
  onChange: event => setPlainPaste(event.target.checked)
491
- }), "Paste as plain text"))), /*#__PURE__*/React.createElement("div", {
770
+ }), translate("Paste as plain text"))), showFullscreen && /*#__PURE__*/React.createElement("div", {
771
+ className: "toolbar-group"
772
+ }, /*#__PURE__*/React.createElement("button", {
773
+ type: "button",
774
+ className: "toolbar-btn",
775
+ onClick: toggleFullscreen,
776
+ title: isFullscreen ? translate("Exit fullscreen") : translate("Enter fullscreen")
777
+ }, isFullscreen ? "⤓" : "⤢"))), /*#__PURE__*/React.createElement("div", {
492
778
  className: "editor-area"
493
779
  }, /*#__PURE__*/React.createElement("div", {
494
780
  ref: editorRef,
495
781
  className: "editor-surface",
496
782
  contentEditable: true,
497
- onInput: onInput,
498
- onPaste: onPaste,
783
+ onInput: onInput$1,
784
+ onPaste: onPaste$1,
785
+ onClick: onEditorClick,
499
786
  onKeyUp: updateToolbarState,
500
787
  onMouseUp: updateToolbarState,
501
788
  suppressContentEditableWarning: true,
@@ -504,19 +791,19 @@ function Editor({
504
791
  className: "editor-footer"
505
792
  }, showStats && /*#__PURE__*/React.createElement("div", {
506
793
  className: "footer-stats"
507
- }, /*#__PURE__*/React.createElement("span", null, stats.words, " words"), /*#__PURE__*/React.createElement("span", null, stats.chars, " characters"), /*#__PURE__*/React.createElement("span", null, selectionText ? `${selectionText.length} selected` : "No selection")), showCopyHtml && /*#__PURE__*/React.createElement("div", {
794
+ }, /*#__PURE__*/React.createElement("span", null, stats.words, " ", translate("words")), /*#__PURE__*/React.createElement("span", null, stats.chars, " ", translate("characters")), /*#__PURE__*/React.createElement("span", null, selectionText ? `${selectionText.length} ${translate("selected")}` : translate("No selection"))), showCopyHtml && /*#__PURE__*/React.createElement("div", {
508
795
  className: "footer-actions"
509
796
  }, /*#__PURE__*/React.createElement("button", {
510
797
  type: "button",
511
798
  className: "toolbar-btn",
512
- onClick: exportHtml
513
- }, "Copy HTML"))), showLiveHtml && /*#__PURE__*/React.createElement("aside", {
799
+ onClick: exportHtml$1
800
+ }, translate("Copy HTML")))), showLiveHtml && /*#__PURE__*/React.createElement("aside", {
514
801
  className: "editor-preview"
515
802
  }, /*#__PURE__*/React.createElement("pre", {
516
803
  className: "preview-code"
517
804
  }, html), /*#__PURE__*/React.createElement("div", {
518
805
  className: "preview-header"
519
- }, "Live HTML")), codeBlock && isCodeModalOpen && /*#__PURE__*/React.createElement("div", {
806
+ }, translate("Live HTML"))), codeBlock && isCodeModalOpen && /*#__PURE__*/React.createElement("div", {
520
807
  className: "modal-backdrop",
521
808
  role: "dialog",
522
809
  "aria-modal": "true"
@@ -524,7 +811,7 @@ function Editor({
524
811
  className: "modal"
525
812
  }, /*#__PURE__*/React.createElement("div", {
526
813
  className: "modal-header"
527
- }, "Insert Code Block"), /*#__PURE__*/React.createElement(CodeBlock, {
814
+ }, translate("Insert Code Block")), /*#__PURE__*/React.createElement(CodeBlock, {
528
815
  value: codeValue,
529
816
  language: codeLanguage,
530
817
  theme: codeTheme,
@@ -532,18 +819,18 @@ function Editor({
532
819
  onChange: setCodeValue,
533
820
  onLanguageChange: setCodeLanguage,
534
821
  onThemeChange: setCodeTheme,
535
- placeholder: "Paste your code here"
822
+ placeholder: translate("Paste your code here")
536
823
  }), /*#__PURE__*/React.createElement("div", {
537
824
  className: "modal-actions"
538
825
  }, /*#__PURE__*/React.createElement("button", {
539
826
  type: "button",
540
827
  className: "toolbar-btn",
541
828
  onClick: () => setIsCodeModalOpen(false)
542
- }, "Cancel"), /*#__PURE__*/React.createElement("button", {
829
+ }, translate("Cancel")), /*#__PURE__*/React.createElement("button", {
543
830
  type: "button",
544
831
  className: "toolbar-btn",
545
- onClick: insertCodeBlock
546
- }, "Insert")))));
832
+ onClick: insertCodeBlock$1
833
+ }, translate("Insert"))))));
547
834
  }
548
835
 
549
836
  // src/index.js - Library entry point