trotl-texteditor 1.0.6 → 1.0.9

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
@@ -1,6 +1,91 @@
1
1
  'use strict';
2
2
 
3
- var react = require('react');
3
+ Object.defineProperty(exports, '__esModule', { value: true });
4
+
5
+ var React = require('react');
6
+
7
+ const DEFAULT_LANGUAGES = [{
8
+ value: "plain",
9
+ label: "Plain"
10
+ }, {
11
+ value: "js",
12
+ label: "JavaScript"
13
+ }, {
14
+ value: "ts",
15
+ label: "TypeScript"
16
+ }, {
17
+ value: "java",
18
+ label: "Java"
19
+ }, {
20
+ value: "css",
21
+ label: "CSS"
22
+ }, {
23
+ value: "html",
24
+ label: "HTML"
25
+ }, {
26
+ value: "json",
27
+ label: "JSON"
28
+ }];
29
+ const DEFAULT_THEMES = [{
30
+ value: "dark",
31
+ label: "Dark"
32
+ }, {
33
+ value: "light",
34
+ label: "Light"
35
+ }];
36
+ function CodeBlock({
37
+ value = "",
38
+ language = "plain",
39
+ theme = "dark",
40
+ languages = DEFAULT_LANGUAGES,
41
+ themes = DEFAULT_THEMES,
42
+ onChange = () => {},
43
+ onLanguageChange = () => {},
44
+ onThemeChange = () => {},
45
+ placeholder = "Paste code here"
46
+ }) {
47
+ const normalizedLanguages = React.useMemo(() => {
48
+ if (!Array.isArray(languages) || languages.length === 0) return DEFAULT_LANGUAGES;
49
+ if (typeof languages[0] === "string") {
50
+ return languages.map(item => ({
51
+ value: item,
52
+ label: item
53
+ }));
54
+ }
55
+ return languages;
56
+ }, [languages]);
57
+ const currentLanguage = React.useMemo(() => normalizedLanguages.find(item => item.value === language) || normalizedLanguages[0], [language, normalizedLanguages]);
58
+ return /*#__PURE__*/React.createElement("section", {
59
+ className: `code-block code-block-${theme}`,
60
+ "data-language": currentLanguage?.value
61
+ }, /*#__PURE__*/React.createElement("header", {
62
+ className: "code-block-toolbar"
63
+ }, /*#__PURE__*/React.createElement("label", {
64
+ className: "code-block-label"
65
+ }, "Language", /*#__PURE__*/React.createElement("select", {
66
+ className: "code-block-select",
67
+ value: currentLanguage?.value,
68
+ onChange: event => onLanguageChange(event.target.value)
69
+ }, normalizedLanguages.map(item => /*#__PURE__*/React.createElement("option", {
70
+ key: item.value,
71
+ value: item.value
72
+ }, item.label)))), /*#__PURE__*/React.createElement("label", {
73
+ className: "code-block-label"
74
+ }, "Theme", /*#__PURE__*/React.createElement("select", {
75
+ className: "code-block-select",
76
+ value: theme,
77
+ onChange: event => onThemeChange(event.target.value)
78
+ }, themes.map(item => /*#__PURE__*/React.createElement("option", {
79
+ key: item.value,
80
+ value: item.value
81
+ }, item.label))))), /*#__PURE__*/React.createElement("textarea", {
82
+ className: "code-block-area",
83
+ value: value,
84
+ onChange: event => onChange(event.target.value),
85
+ placeholder: placeholder,
86
+ spellCheck: false
87
+ }));
88
+ }
4
89
 
5
90
  const COMMANDS = [{
6
91
  cmd: "bold",
@@ -44,24 +129,36 @@ function Editor({
44
129
  tools = COMMANDS.map(item => item.cmd),
45
130
  showHeading = true,
46
131
  showFontSize = true,
132
+ showAlign = true,
47
133
  showLink = true,
48
134
  showUndoRedo = true,
49
135
  showClear = true,
50
136
  showPlainPaste = true,
51
137
  showColor = true,
138
+ codeBlock = false,
139
+ codeBlockLanguages,
52
140
  showLiveHtml = true,
53
141
  showStats = true,
54
142
  showCopyHtml = true,
55
143
  onChange = () => {}
56
144
  }) {
57
- const editorRef = react.useRef(null);
58
- const [html, setHtml] = react.useState("");
59
- const [plainPaste, setPlainPaste] = react.useState(true);
60
- const [selectionText, setSelectionText] = react.useState("");
61
- const [heading, setHeading] = react.useState("p");
62
- const [fontSize, setFontSize] = react.useState("16");
63
- const [textColor, setTextColor] = react.useState("#1f2937");
64
- const [activeToolMap, setActiveToolMap] = react.useState({});
145
+ const editorRef = React.useRef(null);
146
+ const [html, setHtml] = React.useState("");
147
+ const [plainPaste, setPlainPaste] = React.useState(true);
148
+ const [selectionText, setSelectionText] = React.useState("");
149
+ const [heading, setHeading] = React.useState("p");
150
+ const [fontSize, setFontSize] = React.useState("16");
151
+ const [customSize, setCustomSize] = React.useState("");
152
+ const [textColor, setTextColor] = React.useState("#1f2937");
153
+ const [backgroundColor, setBackgroundColor] = React.useState("#ffff00");
154
+ const [activeToolMap, setActiveToolMap] = React.useState({});
155
+ const [isCodeModalOpen, setIsCodeModalOpen] = React.useState(false);
156
+ const [codeValue, setCodeValue] = React.useState("");
157
+ const [codeLanguage, setCodeLanguage] = React.useState("js");
158
+ const [codeTheme, setCodeTheme] = React.useState(theme);
159
+ React.useEffect(() => {
160
+ setCodeTheme(theme);
161
+ }, [theme]);
65
162
 
66
163
  // useEffect(() => {
67
164
  // if (editorRef.current) {
@@ -70,14 +167,14 @@ function Editor({
70
167
  // }
71
168
  // }, []);
72
169
 
73
- const stats = react.useMemo(() => {
170
+ const stats = React.useMemo(() => {
74
171
  const text = editorRef.current?.innerText || "";
75
172
  const words = text.trim() ? text.trim().split(/\s+/).length : 0;
76
173
  return {
77
174
  words,
78
175
  chars: text.length
79
176
  };
80
- }, []);
177
+ }, [html]);
81
178
  const exec = (cmd, value = null) => {
82
179
  editorRef.current?.focus();
83
180
  document.execCommand(cmd, false, value);
@@ -95,9 +192,17 @@ function Editor({
95
192
  exec("formatBlock", "P");
96
193
  return;
97
194
  }
195
+ if (next === "pre") {
196
+ exec("formatBlock", "PRE");
197
+ return;
198
+ }
98
199
  exec("formatBlock", next.toUpperCase());
99
200
  };
100
201
  const applyFontSize = next => {
202
+ if (next === "custom") {
203
+ setFontSize("custom");
204
+ return;
205
+ }
101
206
  setFontSize(next);
102
207
  exec("fontSize", "7");
103
208
  const fontElements = editorRef.current?.querySelectorAll("font[size='7']") || [];
@@ -108,10 +213,42 @@ function Editor({
108
213
  setHtml(editorRef.current?.innerHTML || "");
109
214
  updateToolbarState();
110
215
  };
216
+ const applyCustomFontSize = value => {
217
+ 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();
227
+ };
111
228
  const applyTextColor = next => {
112
229
  setTextColor(next);
113
230
  exec("foreColor", next);
114
231
  };
232
+ const applyBackgroundColor = next => {
233
+ setBackgroundColor(next);
234
+ exec("hiliteColor", next);
235
+ };
236
+ const resetColors = () => {
237
+ const defaultText = theme === "dark" ? "#e5e7eb" : "#1f2937";
238
+ const defaultBg = "#ffff00";
239
+ setTextColor(defaultText);
240
+ setBackgroundColor(defaultBg);
241
+ exec("removeFormat");
242
+ };
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("");
251
+ };
115
252
  const insertLink = () => {
116
253
  const url = window.prompt("Enter URL", "https://");
117
254
  if (!url) return;
@@ -143,7 +280,7 @@ function Editor({
143
280
  const text = event.clipboardData.getData("text/plain");
144
281
  document.execCommand("insertText", false, text);
145
282
  };
146
- const updateToolbarState = react.useCallback(() => {
283
+ const updateToolbarState = React.useCallback(() => {
147
284
  const selection = document.getSelection();
148
285
  if (!selection || selection.rangeCount === 0 || !editorRef.current) return;
149
286
  const anchorNode = selection.anchorNode;
@@ -164,7 +301,7 @@ function Editor({
164
301
  });
165
302
  setActiveToolMap(nextActive);
166
303
  }, []);
167
- react.useEffect(() => {
304
+ React.useEffect(() => {
168
305
  document.addEventListener("selectionchange", updateToolbarState);
169
306
  return () => document.removeEventListener("selectionchange", updateToolbarState);
170
307
  }, [updateToolbarState]);
@@ -173,13 +310,27 @@ function Editor({
173
310
  await navigator.clipboard.writeText(payload);
174
311
  alert("HTML copied to clipboard.");
175
312
  };
176
- const activeCommands = react.useMemo(() => COMMANDS.filter(item => tools.includes(item.cmd)), [tools]);
313
+ const activeCommands = React.useMemo(() => COMMANDS.filter(item => tools.includes(item.cmd)), [tools]);
177
314
  return /*#__PURE__*/React.createElement("section", {
178
- className: `editor-root theme-${theme}${showLiveHtml ? "" : " no-preview"}`,
315
+ className: `editor-root theme-${theme}${showLiveHtml ? " show-preview" : " no-preview"}`,
179
316
  "data-theme": theme
180
317
  }, /*#__PURE__*/React.createElement("div", {
181
318
  className: "toolbar"
182
- }, activeCommands.length > 0 && /*#__PURE__*/React.createElement("div", {
319
+ }, showUndoRedo && /*#__PURE__*/React.createElement(React.Fragment, null, /*#__PURE__*/React.createElement("div", {
320
+ className: "toolbar-group"
321
+ }, /*#__PURE__*/React.createElement("button", {
322
+ type: "button",
323
+ className: "toolbar-btn",
324
+ onClick: () => exec("undo"),
325
+ title: "Undo"
326
+ }, "\u21B6"), /*#__PURE__*/React.createElement("button", {
327
+ type: "button",
328
+ className: "toolbar-btn",
329
+ onClick: () => exec("redo"),
330
+ title: "Redo"
331
+ }, "\u21B7")), /*#__PURE__*/React.createElement("span", {
332
+ className: "toolbar-separator"
333
+ }, "|")), activeCommands.length > 0 && /*#__PURE__*/React.createElement(React.Fragment, null, /*#__PURE__*/React.createElement("div", {
183
334
  className: "toolbar-group"
184
335
  }, activeCommands.map(item => /*#__PURE__*/React.createElement("button", {
185
336
  key: item.cmd,
@@ -189,11 +340,13 @@ function Editor({
189
340
  onClick: () => exec(item.cmd),
190
341
  title: item.label,
191
342
  "aria-pressed": activeToolMap[item.cmd] || false
192
- }, item.icon))), (showHeading || showFontSize || showColor) && /*#__PURE__*/React.createElement("div", {
343
+ }, item.icon))), /*#__PURE__*/React.createElement("span", {
344
+ className: "toolbar-separator"
345
+ }, "|")), (showHeading || showFontSize || showColor) && /*#__PURE__*/React.createElement(React.Fragment, null, /*#__PURE__*/React.createElement("div", {
193
346
  className: "toolbar-group"
194
347
  }, showHeading && /*#__PURE__*/React.createElement("label", {
195
348
  className: "toolbar-label"
196
- }, "Heading", /*#__PURE__*/React.createElement("select", {
349
+ }, "Format", /*#__PURE__*/React.createElement("select", {
197
350
  value: heading,
198
351
  onChange: event => applyHeading(event.target.value),
199
352
  className: "toolbar-select"
@@ -205,45 +358,119 @@ function Editor({
205
358
  value: "h2"
206
359
  }, "Heading 2"), /*#__PURE__*/React.createElement("option", {
207
360
  value: "h3"
208
- }, "Heading 3"))), showFontSize && /*#__PURE__*/React.createElement("label", {
361
+ }, "Heading 3"), /*#__PURE__*/React.createElement("option", {
362
+ value: "h4"
363
+ }, "Heading 4"), /*#__PURE__*/React.createElement("option", {
364
+ value: "blockquote"
365
+ }, "Blockquote"), codeBlock && /*#__PURE__*/React.createElement("option", {
366
+ value: "pre"
367
+ }, "Code Block"))), showFontSize && /*#__PURE__*/React.createElement("label", {
209
368
  className: "toolbar-label"
210
369
  }, "Size", /*#__PURE__*/React.createElement("select", {
211
370
  value: fontSize,
212
371
  onChange: event => applyFontSize(event.target.value),
213
372
  className: "toolbar-select"
214
373
  }, /*#__PURE__*/React.createElement("option", {
374
+ value: "10"
375
+ }, "10px"), /*#__PURE__*/React.createElement("option", {
376
+ value: "12"
377
+ }, "12px"), /*#__PURE__*/React.createElement("option", {
215
378
  value: "14"
216
379
  }, "14px"), /*#__PURE__*/React.createElement("option", {
217
380
  value: "16"
218
381
  }, "16px"), /*#__PURE__*/React.createElement("option", {
219
382
  value: "18"
220
383
  }, "18px"), /*#__PURE__*/React.createElement("option", {
384
+ value: "20"
385
+ }, "20px"), /*#__PURE__*/React.createElement("option", {
221
386
  value: "22"
222
387
  }, "22px"), /*#__PURE__*/React.createElement("option", {
388
+ value: "24"
389
+ }, "24px"), /*#__PURE__*/React.createElement("option", {
223
390
  value: "28"
224
- }, "28px"))), showColor && /*#__PURE__*/React.createElement("label", {
391
+ }, "28px"), /*#__PURE__*/React.createElement("option", {
392
+ value: "32"
393
+ }, "32px"), /*#__PURE__*/React.createElement("option", {
394
+ value: "36"
395
+ }, "36px"), /*#__PURE__*/React.createElement("option", {
396
+ value: "48"
397
+ }, "48px"), /*#__PURE__*/React.createElement("option", {
398
+ value: "custom"
399
+ }, "Custom"))), showFontSize && fontSize === "custom" && /*#__PURE__*/React.createElement("label", {
400
+ className: "toolbar-label"
401
+ }, /*#__PURE__*/React.createElement("input", {
402
+ type: "number",
403
+ min: "8",
404
+ max: "200",
405
+ value: customSize,
406
+ onChange: event => applyCustomFontSize(event.target.value),
407
+ placeholder: "Size",
408
+ className: "toolbar-input",
409
+ style: {
410
+ width: "60px"
411
+ }
412
+ }), "px"), showColor && /*#__PURE__*/React.createElement(React.Fragment, null, /*#__PURE__*/React.createElement("label", {
225
413
  className: "toolbar-label color-label"
226
- }, "Color", /*#__PURE__*/React.createElement("input", {
414
+ }, "Font", /*#__PURE__*/React.createElement("input", {
227
415
  type: "color",
228
416
  className: "toolbar-color",
229
417
  value: textColor,
230
418
  onChange: event => applyTextColor(event.target.value),
231
419
  "aria-label": "Text color"
232
- }))), (showLink || showUndoRedo || showClear) && /*#__PURE__*/React.createElement("div", {
233
- className: "toolbar-group"
234
- }, showLink && /*#__PURE__*/React.createElement("button", {
420
+ })), /*#__PURE__*/React.createElement("label", {
421
+ className: "toolbar-label color-label"
422
+ }, "Background", /*#__PURE__*/React.createElement("input", {
423
+ type: "color",
424
+ className: "toolbar-color",
425
+ value: backgroundColor,
426
+ onChange: event => applyBackgroundColor(event.target.value),
427
+ "aria-label": "Background color"
428
+ })), /*#__PURE__*/React.createElement("button", {
235
429
  type: "button",
236
- className: "toolbar-btn",
237
- onClick: insertLink
238
- }, "Link"), showUndoRedo && /*#__PURE__*/React.createElement(React.Fragment, null, /*#__PURE__*/React.createElement("button", {
430
+ className: "toolbar-btn toolbar-btn-small",
431
+ onClick: resetColors,
432
+ title: "Reset colors"
433
+ }, "\u21BA")), codeBlock && /*#__PURE__*/React.createElement("button", {
239
434
  type: "button",
240
435
  className: "toolbar-btn",
241
- onClick: () => exec("undo")
242
- }, "Undo"), /*#__PURE__*/React.createElement("button", {
436
+ onClick: () => {
437
+ setCodeTheme(theme);
438
+ setIsCodeModalOpen(true);
439
+ },
440
+ title: "Insert code block"
441
+ }, "Code Block"), showAlign && /*#__PURE__*/React.createElement("div", {
442
+ className: "toolbar-label"
443
+ }, "Align", /*#__PURE__*/React.createElement("div", {
444
+ className: "align-buttons"
445
+ }, /*#__PURE__*/React.createElement("button", {
446
+ type: "button",
447
+ className: "toolbar-btn toolbar-btn-small",
448
+ onClick: () => exec("justifyLeft"),
449
+ title: "Align Left"
450
+ }, "\u27F8"), /*#__PURE__*/React.createElement("button", {
451
+ type: "button",
452
+ className: "toolbar-btn toolbar-btn-small",
453
+ onClick: () => exec("justifyCenter"),
454
+ title: "Align Center"
455
+ }, "\u21D4"), /*#__PURE__*/React.createElement("button", {
456
+ type: "button",
457
+ className: "toolbar-btn toolbar-btn-small",
458
+ onClick: () => exec("justifyRight"),
459
+ title: "Align Right"
460
+ }, "\u27F9"), /*#__PURE__*/React.createElement("button", {
461
+ type: "button",
462
+ className: "toolbar-btn toolbar-btn-small",
463
+ onClick: () => exec("justifyFull"),
464
+ title: "Justify"
465
+ }, "\u21FF")))), /*#__PURE__*/React.createElement("span", {
466
+ className: "toolbar-separator"
467
+ }, "|")), (showLink || showClear) && /*#__PURE__*/React.createElement(React.Fragment, null, /*#__PURE__*/React.createElement("div", {
468
+ className: "toolbar-group"
469
+ }, showLink && /*#__PURE__*/React.createElement("button", {
243
470
  type: "button",
244
471
  className: "toolbar-btn",
245
- onClick: () => exec("redo")
246
- }, "Redo")), showClear && /*#__PURE__*/React.createElement(React.Fragment, null, /*#__PURE__*/React.createElement("button", {
472
+ onClick: insertLink
473
+ }, "\uD83D\uDD17"), showClear && /*#__PURE__*/React.createElement(React.Fragment, null, /*#__PURE__*/React.createElement("button", {
247
474
  type: "button",
248
475
  className: "toolbar-btn",
249
476
  onClick: removeFormat
@@ -251,7 +478,9 @@ function Editor({
251
478
  type: "button",
252
479
  className: "toolbar-btn danger",
253
480
  onClick: clearAll
254
- }, "Clear all"))), showPlainPaste && /*#__PURE__*/React.createElement("div", {
481
+ }, "Clear all"))), /*#__PURE__*/React.createElement("span", {
482
+ className: "toolbar-separator"
483
+ }, "|")), showPlainPaste && /*#__PURE__*/React.createElement("div", {
255
484
  className: "toolbar-group toggle"
256
485
  }, /*#__PURE__*/React.createElement("label", {
257
486
  className: "toggle-row"
@@ -283,14 +512,42 @@ function Editor({
283
512
  onClick: exportHtml
284
513
  }, "Copy HTML"))), showLiveHtml && /*#__PURE__*/React.createElement("aside", {
285
514
  className: "editor-preview"
286
- }, /*#__PURE__*/React.createElement("div", {
287
- className: "preview-header"
288
- }, "Live HTML"), /*#__PURE__*/React.createElement("pre", {
515
+ }, /*#__PURE__*/React.createElement("pre", {
289
516
  className: "preview-code"
290
- }, html)));
517
+ }, html), /*#__PURE__*/React.createElement("div", {
518
+ className: "preview-header"
519
+ }, "Live HTML")), codeBlock && isCodeModalOpen && /*#__PURE__*/React.createElement("div", {
520
+ className: "modal-backdrop",
521
+ role: "dialog",
522
+ "aria-modal": "true"
523
+ }, /*#__PURE__*/React.createElement("div", {
524
+ className: "modal"
525
+ }, /*#__PURE__*/React.createElement("div", {
526
+ className: "modal-header"
527
+ }, "Insert Code Block"), /*#__PURE__*/React.createElement(CodeBlock, {
528
+ value: codeValue,
529
+ language: codeLanguage,
530
+ theme: codeTheme,
531
+ languages: codeBlockLanguages,
532
+ onChange: setCodeValue,
533
+ onLanguageChange: setCodeLanguage,
534
+ onThemeChange: setCodeTheme,
535
+ placeholder: "Paste your code here"
536
+ }), /*#__PURE__*/React.createElement("div", {
537
+ className: "modal-actions"
538
+ }, /*#__PURE__*/React.createElement("button", {
539
+ type: "button",
540
+ className: "toolbar-btn",
541
+ onClick: () => setIsCodeModalOpen(false)
542
+ }, "Cancel"), /*#__PURE__*/React.createElement("button", {
543
+ type: "button",
544
+ className: "toolbar-btn",
545
+ onClick: insertCodeBlock
546
+ }, "Insert")))));
291
547
  }
292
548
 
293
549
  // src/index.js - Library entry point
294
550
 
295
- module.exports = Editor;
551
+ exports.CodeBlock = CodeBlock;
552
+ exports.default = Editor;
296
553
  //# sourceMappingURL=index.cjs.map