@liwe3/webcomponents 1.0.14 → 1.1.10

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.
Files changed (85) hide show
  1. package/dist/AIMarkdownEditor.d.ts +35 -0
  2. package/dist/AIMarkdownEditor.d.ts.map +1 -0
  3. package/dist/AIMarkdownEditor.js +412 -0
  4. package/dist/AIMarkdownEditor.js.map +1 -0
  5. package/dist/AITextEditor.d.ts +183 -0
  6. package/dist/AITextEditor.d.ts.map +1 -0
  7. package/dist/AITextEditor.js +63 -27
  8. package/dist/AITextEditor.js.map +1 -1
  9. package/dist/ButtonToolbar.d.ts +35 -0
  10. package/dist/ButtonToolbar.d.ts.map +1 -0
  11. package/dist/ButtonToolbar.js +220 -0
  12. package/dist/ButtonToolbar.js.map +1 -0
  13. package/dist/CheckList.d.ts +31 -0
  14. package/dist/CheckList.d.ts.map +1 -0
  15. package/dist/CheckList.js +336 -0
  16. package/dist/CheckList.js.map +1 -0
  17. package/dist/ChunkUploader.d.ts +125 -0
  18. package/dist/ChunkUploader.d.ts.map +1 -0
  19. package/dist/ChunkUploader.js +756 -0
  20. package/dist/ChunkUploader.js.map +1 -0
  21. package/dist/ComicBalloon.d.ts +82 -0
  22. package/dist/ComicBalloon.d.ts.map +1 -0
  23. package/dist/ComicBalloon.js +346 -0
  24. package/dist/ComicBalloon.js.map +1 -0
  25. package/dist/ContainerBox.d.ts +112 -0
  26. package/dist/ContainerBox.d.ts.map +1 -0
  27. package/dist/ContainerBox.js +359 -0
  28. package/dist/ContainerBox.js.map +1 -0
  29. package/dist/DateSelector.d.ts +103 -0
  30. package/dist/DateSelector.d.ts.map +1 -0
  31. package/dist/Dialog.d.ts +102 -0
  32. package/dist/Dialog.d.ts.map +1 -0
  33. package/dist/Dialog.js +299 -0
  34. package/dist/Dialog.js.map +1 -0
  35. package/dist/Drawer.d.ts +63 -0
  36. package/dist/Drawer.d.ts.map +1 -0
  37. package/dist/Drawer.js +340 -0
  38. package/dist/Drawer.js.map +1 -0
  39. package/dist/ImageView.d.ts +42 -0
  40. package/dist/ImageView.d.ts.map +1 -0
  41. package/dist/ImageView.js +209 -0
  42. package/dist/ImageView.js.map +1 -0
  43. package/dist/MarkdownPreview.d.ts +25 -0
  44. package/dist/MarkdownPreview.d.ts.map +1 -0
  45. package/dist/MarkdownPreview.js +147 -0
  46. package/dist/MarkdownPreview.js.map +1 -0
  47. package/dist/PopoverMenu.d.ts +103 -0
  48. package/dist/PopoverMenu.d.ts.map +1 -0
  49. package/dist/ResizableCropper.d.ts +158 -0
  50. package/dist/ResizableCropper.d.ts.map +1 -0
  51. package/dist/ResizableCropper.js +562 -0
  52. package/dist/ResizableCropper.js.map +1 -0
  53. package/dist/SmartSelect.d.ts +100 -0
  54. package/dist/SmartSelect.d.ts.map +1 -0
  55. package/dist/SmartSelect.js +45 -2
  56. package/dist/SmartSelect.js.map +1 -1
  57. package/dist/Toast.d.ts +127 -0
  58. package/dist/Toast.d.ts.map +1 -0
  59. package/dist/Toast.js +79 -49
  60. package/dist/Toast.js.map +1 -1
  61. package/dist/TreeView.d.ts +84 -0
  62. package/dist/TreeView.d.ts.map +1 -0
  63. package/dist/TreeView.js +478 -0
  64. package/dist/TreeView.js.map +1 -0
  65. package/dist/index.d.ts +23 -0
  66. package/dist/index.d.ts.map +1 -0
  67. package/dist/index.js +51 -14
  68. package/dist/index.js.map +1 -1
  69. package/package.json +60 -5
  70. package/src/AIMarkdownEditor.ts +568 -0
  71. package/src/AITextEditor.ts +97 -2
  72. package/src/ButtonToolbar.ts +302 -0
  73. package/src/CheckList.ts +438 -0
  74. package/src/ChunkUploader.ts +1135 -0
  75. package/src/ComicBalloon.ts +709 -0
  76. package/src/ContainerBox.ts +570 -0
  77. package/src/Dialog.ts +510 -0
  78. package/src/Drawer.ts +435 -0
  79. package/src/ImageView.ts +265 -0
  80. package/src/MarkdownPreview.ts +213 -0
  81. package/src/ResizableCropper.ts +1099 -0
  82. package/src/SmartSelect.ts +48 -2
  83. package/src/Toast.ts +96 -32
  84. package/src/TreeView.ts +673 -0
  85. package/src/index.ts +129 -27
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sources":["../src/index.ts"],"sourcesContent":["/**\n * @liwe3/webcomponents\n * A collection of reusable web components\n */\n\n// Export SmartSelect\nexport {\n SmartSelectElement,\n defineSmartSelect,\n type SelectOption\n} from './SmartSelect';\n\n// Export AITextEditor\nexport {\n AITextEditorElement,\n defineAITextEditor,\n type AITextEditorConfig\n} from './AITextEditor';\n\n// Export Toast\nexport {\n ToastElement,\n defineToast,\n toastAdd,\n type ToastType,\n type ToastButton,\n type ToastConfig\n} from './Toast';\n\n// Export PopoverMenu\nexport {\n PopoverMenuElement,\n definePopoverMenu,\n type PopoverMenuItem,\n type PopoverMenuConfig\n} from './PopoverMenu';\n\n// Export DateSelector\nexport {\n DateSelectorElement,\n defineDateSelector,\n type DateRange\n} from './DateSelector';\n\n// Convenience function to register all components at once\nexport const defineAllComponents = (): void => {\n if (typeof window !== 'undefined') {\n import('./SmartSelect').then(({ defineSmartSelect }) => defineSmartSelect());\n import('./AITextEditor').then(({ defineAITextEditor }) => defineAITextEditor());\n import('./Toast').then(({ defineToast }) => defineToast());\n import('./PopoverMenu').then(({ definePopoverMenu }) => definePopoverMenu());\n import('./DateSelector').then(({ defineDateSelector }) => defineDateSelector());\n }\n};\n"],"names":["defineAllComponents","defineSmartSelect","defineAITextEditor","defineToast","definePopoverMenu","defineDateSelector"],"mappings":";;;;;AA6CO,MAAMA,IAAsB,MAAY;AAC7C,EAAI,OAAO,SAAW,QACpB,OAAO,kBAAe,EAAE,KAAK,CAAC,EAAE,mBAAAC,EAAAA,MAAwBA,GAAmB,GAC3E,OAAO,mBAAgB,EAAE,KAAK,CAAC,EAAE,oBAAAC,EAAAA,MAAyBA,GAAoB,GAC9E,OAAO,YAAS,EAAE,KAAK,CAAC,EAAE,aAAAC,EAAAA,MAAkBA,GAAa,GACzD,OAAO,kBAAe,EAAE,KAAK,CAAC,EAAE,mBAAAC,EAAAA,MAAwBA,GAAmB,GAC3E,OAAO,mBAAgB,EAAE,KAAK,CAAC,EAAE,oBAAAC,EAAAA,MAAyBA,GAAoB;AAElF;"}
1
+ {"version":3,"file":"index.js","sources":["../src/index.ts"],"sourcesContent":["/**\n * @liwe3/webcomponents\n * A collection of reusable web components\n */\n\n// Export SmartSelect\nexport {\n\tSmartSelectElement,\n\tdefineSmartSelect,\n\ttype SelectOption,\n} from './SmartSelect';\n\n// Export AITextEditor\nexport {\n\tAITextEditorElement,\n\tdefineAITextEditor,\n\ttype AITextEditorConfig,\n} from './AITextEditor';\n\n// Export Toast\nexport {\n\tToastElement,\n\tdefineToast,\n\ttoastAdd,\n\ttype ToastType,\n\ttype ToastButton,\n\ttype ToastConfig,\n} from './Toast';\n\n// Export Dialog\nexport {\n\tDialogElement,\n\tdefineDialog,\n\tdialogAdd,\n\ttype DialogButton,\n\ttype DialogConfig,\n} from './Dialog';\n\n// Export PopoverMenu\nexport {\n\tPopoverMenuElement,\n\tdefinePopoverMenu,\n\ttype PopoverMenuItem,\n\ttype PopoverMenuConfig,\n} from './PopoverMenu';\n\n// Export DateSelector\nexport {\n\tDateSelectorElement,\n\tdefineDateSelector,\n\ttype DateRange,\n} from './DateSelector';\n\n// Export TreeView\nexport {\n\tTreeViewElement,\n\tdefineTreeView,\n\ttype TreeNode,\n} from './TreeView';\n\n// Export ContainerBox\nexport {\n\tContainerBoxElement,\n\tdefineContainerBox,\n\ttype MenuPosition,\n\ttype ContainerBoxConfig,\n} from './ContainerBox';\n\n// Export Drawer\nexport {\n\tDrawerElement,\n\tdefineDrawer,\n\ttype DrawerDirection,\n\ttype DrawerState,\n\ttype DrawerConfig,\n} from './Drawer';\n\n// Export ImageView\nexport * from './ImageView';\n\n// Export ChunkUploader\nexport {\n\tChunkUploaderElement,\n\tdefineChunkUploader,\n\ttype UploadedFile,\n\ttype ChunkUploaderConfig,\n} from './ChunkUploader';\n\n// Export CheckList\nexport {\n\tCheckListElement,\n\tdefineCheckList,\n\ttype CheckListItem,\n} from './CheckList';\n\n// Export ButtonToolbar\nexport {\n\tButtonToolbarElement,\n\tdefineButtonToolbar,\n\ttype ButtonToolbarItem,\n\ttype ButtonToolbarGroup,\n} from './ButtonToolbar';\n\n// Export AIMarkdownEditor\nexport {\n\tAIMarkdownEditorElement,\n\tdefineAIMarkdownEditor,\n} from './AIMarkdownEditor';\n\n// Export MarkdownPreview\nexport {\n\tMarkdownPreviewElement,\n\tdefineMarkdownPreview,\n} from './MarkdownPreview';\n\n// Export ResizableCropper\nexport {\n\tResizableCropperElement,\n\tdefineResizableCropper,\n\ttype ResizableCropperState,\n\ttype ResizableCropEventDetail,\n\ttype ResizableCropperValues,\n\ttype ResizableCropperComponentState,\n} from './ResizableCropper';\n\n// Export ComicBalloon\nexport {\n\tComicBalloonElement,\n\tdefineComicBalloon,\n\tBalloonType,\n\ttype HandlerPosition,\n\ttype ContentChangeEvent,\n\ttype HandlerMoveEvent,\n} from './ComicBalloon';\n\n// Convenience function to register all components at once\nexport const defineAllComponents = () : void => {\n\tif ( typeof window !== 'undefined' ) {\n\t\timport( './SmartSelect' ).then( ( { defineSmartSelect } ) => defineSmartSelect() );\n\t\timport( './AITextEditor' ).then( ( { defineAITextEditor } ) => defineAITextEditor() );\n\t\timport( './AIMarkdownEditor' ).then( ( { defineAIMarkdownEditor } ) => defineAIMarkdownEditor() );\n\t\timport( './MarkdownPreview' ).then( ( { defineMarkdownPreview } ) => defineMarkdownPreview() );\n\t\timport( './Toast' ).then( ( { defineToast } ) => defineToast() );\n\t\timport( './Dialog' ).then( ( { defineDialog } ) => defineDialog() );\n\t\timport( './PopoverMenu' ).then( ( { definePopoverMenu } ) => definePopoverMenu() );\n\t\timport( './DateSelector' ).then( ( { defineDateSelector } ) => defineDateSelector() );\n\t\timport( './TreeView' ).then( ( { defineTreeView } ) => defineTreeView() );\n\t\timport( './ContainerBox' ).then( ( { defineContainerBox } ) => defineContainerBox() );\n\t\timport( './Drawer' ).then( ( { defineDrawer } ) => defineDrawer() );\n\t\timport( './ChunkUploader' ).then( ( { defineChunkUploader } ) => defineChunkUploader() );\n\t\timport( './CheckList' ).then( ( { defineCheckList } ) => defineCheckList() );\n\t\timport( './ButtonToolbar' ).then( ( { defineButtonToolbar } ) => defineButtonToolbar() );\n\t\timport( './ResizableCropper' ).then( ( { defineResizableCropper } ) => defineResizableCropper() );\n\t\timport( './ComicBalloon' ).then( ( { defineComicBalloon } ) => defineComicBalloon() );\n\t}\n};\n"],"names":["defineAllComponents","defineSmartSelect","defineAITextEditor","defineAIMarkdownEditor","defineMarkdownPreview","defineToast","defineDialog","definePopoverMenu","defineDateSelector","defineTreeView","defineContainerBox","defineDrawer","defineChunkUploader","defineCheckList","defineButtonToolbar","defineResizableCropper","defineComicBalloon"],"mappings":";;;;;;;;;;;;;;;;;AAwIO,MAAMA,IAAsB,MAAa;AAC/C,EAAK,OAAO,SAAW,QACtB,OAAQ,kBAAgB,EAAE,KAAM,CAAE,EAAE,mBAAAC,EAAAA,MAAyBA,GAAoB,GACjF,OAAQ,mBAAiB,EAAE,KAAM,CAAE,EAAE,oBAAAC,EAAAA,MAA0BA,GAAqB,GACpF,OAAQ,uBAAqB,EAAE,KAAM,CAAE,EAAE,wBAAAC,EAAAA,MAA8BA,GAAyB,GAChG,OAAQ,sBAAoB,EAAE,KAAM,CAAE,EAAE,uBAAAC,EAAAA,MAA6BA,GAAwB,GAC7F,OAAQ,YAAU,EAAE,KAAM,CAAE,EAAE,aAAAC,EAAAA,MAAmBA,GAAc,GAC/D,OAAQ,aAAW,EAAE,KAAM,CAAE,EAAE,cAAAC,EAAAA,MAAoBA,GAAe,GAClE,OAAQ,kBAAgB,EAAE,KAAM,CAAE,EAAE,mBAAAC,EAAAA,MAAyBA,GAAoB,GACjF,OAAQ,mBAAiB,EAAE,KAAM,CAAE,EAAE,oBAAAC,EAAAA,MAA0BA,GAAqB,GACpF,OAAQ,eAAa,EAAE,KAAM,CAAE,EAAE,gBAAAC,EAAAA,MAAsBA,GAAiB,GACxE,OAAQ,mBAAiB,EAAE,KAAM,CAAE,EAAE,oBAAAC,EAAAA,MAA0BA,GAAqB,GACpF,OAAQ,aAAW,EAAE,KAAM,CAAE,EAAE,cAAAC,EAAAA,MAAoBA,GAAe,GAClE,OAAQ,oBAAkB,EAAE,KAAM,CAAE,EAAE,qBAAAC,EAAAA,MAA2BA,GAAsB,GACvF,OAAQ,gBAAc,EAAE,KAAM,CAAE,EAAE,iBAAAC,EAAAA,MAAuBA,GAAkB,GAC3E,OAAQ,oBAAkB,EAAE,KAAM,CAAE,EAAE,qBAAAC,EAAAA,MAA2BA,GAAsB,GACvF,OAAQ,uBAAqB,EAAE,KAAM,CAAE,EAAE,wBAAAC,EAAAA,MAA8BA,GAAyB,GAChG,OAAQ,mBAAiB,EAAE,KAAM,CAAE,EAAE,oBAAAC,EAAAA,MAA0BA,GAAqB;AAEtF;"}
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@liwe3/webcomponents",
3
- "version": "1.0.14",
4
- "description": "A collection of reusable web components including SmartSelect and AITextEditor",
3
+ "version": "1.1.10",
4
+ "description": "A collection of reusable web components including SmartSelect and AITextEditor, ChunkUploader and more",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
7
7
  "module": "./dist/index.js",
@@ -27,6 +27,11 @@
27
27
  "import": "./dist/Toast.js",
28
28
  "default": "./dist/Toast.js"
29
29
  },
30
+ "./dialog": {
31
+ "types": "./dist/Dialog.d.ts",
32
+ "import": "./dist/Dialog.js",
33
+ "default": "./dist/Dialog.js"
34
+ },
30
35
  "./popover-menu": {
31
36
  "types": "./dist/PopoverMenu.d.ts",
32
37
  "import": "./dist/PopoverMenu.js",
@@ -36,6 +41,56 @@
36
41
  "types": "./dist/DateSelector.d.ts",
37
42
  "import": "./dist/DateSelector.js",
38
43
  "default": "./dist/DateSelector.js"
44
+ },
45
+ "./tree-view": {
46
+ "types": "./dist/TreeView.d.ts",
47
+ "import": "./dist/TreeView.js",
48
+ "default": "./dist/TreeView.js"
49
+ },
50
+ "./container-box": {
51
+ "types": "./dist/ContainerBox.d.ts",
52
+ "import": "./dist/ContainerBox.js",
53
+ "default": "./dist/ContainerBox.js"
54
+ },
55
+ "./drawer": {
56
+ "types": "./dist/Drawer.d.ts",
57
+ "import": "./dist/Drawer.js",
58
+ "default": "./dist/Drawer.js"
59
+ },
60
+ "./image-view": {
61
+ "types": "./dist/ImageView.d.ts",
62
+ "import": "./dist/ImageView.js",
63
+ "default": "./dist/ImageView.js"
64
+ },
65
+ "./chunk-uploader": {
66
+ "types": "./dist/ChunkUploader.d.ts",
67
+ "import": "./dist/ChunkUploader.js",
68
+ "default": "./dist/ChunkUploader.js"
69
+ },
70
+ "./button-toolbar": {
71
+ "types": "./dist/ButtonToolbar.d.ts",
72
+ "import": "./dist/ButtonToolbar.js",
73
+ "default": "./dist/ButtonToolbar.js"
74
+ },
75
+ "./ai-markdown-editor": {
76
+ "types": "./dist/AIMarkdownEditor.d.ts",
77
+ "import": "./dist/AIMarkdownEditor.js",
78
+ "default": "./dist/AIMarkdownEditor.js"
79
+ },
80
+ "./markdown-preview": {
81
+ "types": "./dist/MarkdownPreview.d.ts",
82
+ "import": "./dist/MarkdownPreview.js",
83
+ "default": "./dist/MarkdownPreview.js"
84
+ },
85
+ "./checklist": {
86
+ "types": "./dist/CheckList.d.ts",
87
+ "import": "./dist/CheckList.js",
88
+ "default": "./dist/CheckList.js"
89
+ },
90
+ "./resizable-cropper": {
91
+ "types": "./dist/ResizableCropper.d.ts",
92
+ "import": "./dist/ResizableCropper.js",
93
+ "default": "./dist/ResizableCropper.js"
39
94
  }
40
95
  },
41
96
  "files": [
@@ -58,11 +113,11 @@
58
113
  "text-editor",
59
114
  "liwe3"
60
115
  ],
61
- "author": "Liwe3",
116
+ "author": "Fabio Rotondo",
62
117
  "license": "MIT",
63
118
  "repository": {
64
119
  "type": "git",
65
- "url": "https://github.com/liwe3/webcomponents.git"
120
+ "url": "https://github.com/fsoft72/liwe3-webcomponents.git"
66
121
  },
67
122
  "devDependencies": {
68
123
  "typescript": "^5.9.2",
@@ -72,4 +127,4 @@
72
127
  "access": "public"
73
128
  },
74
129
  "packageManager": "pnpm@10.16.0"
75
- }
130
+ }
@@ -0,0 +1,568 @@
1
+ import { AITextEditorElement, defineAITextEditor } from './AITextEditor';
2
+ import { ButtonToolbarElement, ButtonToolbarGroup, defineButtonToolbar } from './ButtonToolbar';
3
+ import { MarkdownPreviewElement, defineMarkdownPreview } from './MarkdownPreview';
4
+
5
+ export class AIMarkdownEditorElement extends HTMLElement {
6
+ declare shadowRoot: ShadowRoot;
7
+ private aiEditor!: AITextEditorElement;
8
+ private toolbar!: ButtonToolbarElement;
9
+ private preview!: MarkdownPreviewElement;
10
+ private editorStatus!: HTMLElement;
11
+ private loading!: HTMLElement;
12
+ private isPreviewMode: boolean = false;
13
+ private markdownLibUrl: string = 'https://cdn.jsdelivr.net/npm/marked@4.3.0/marked.min.js';
14
+
15
+ constructor() {
16
+ super();
17
+ this.attachShadow({ mode: 'open' });
18
+ }
19
+
20
+ connectedCallback(): void {
21
+ this.render();
22
+ this.init();
23
+ }
24
+
25
+ private render(): void {
26
+ this.shadowRoot.innerHTML = `
27
+ <style>
28
+ :host {
29
+ display: flex;
30
+ flex-direction: column;
31
+ width: 100%;
32
+ height: 100%;
33
+ gap: 0.5rem;
34
+ }
35
+
36
+ .editor-container {
37
+ flex: 1;
38
+ position: relative;
39
+ min-height: 0; /* Important for flexbox scrolling */
40
+ border: 2px solid #e1e5e9;
41
+ border-radius: 12px;
42
+ background: #fafbfc;
43
+ }
44
+
45
+ .editor-container:focus-within {
46
+ border-color: #4facfe;
47
+ background: white;
48
+ }
49
+
50
+ .editor-status {
51
+ position: absolute;
52
+ top: 5px;
53
+ left: 5px;
54
+ width: 10px;
55
+ height: 10px;
56
+ border-radius: 100%;
57
+ background: #777;
58
+ z-index: 10;
59
+ }
60
+
61
+ .loading {
62
+ position: absolute;
63
+ top: 5px;
64
+ right: 10px;
65
+ z-index: 10;
66
+ display: none;
67
+ }
68
+
69
+ .loading.show {
70
+ display: block;
71
+ }
72
+
73
+ .spinner {
74
+ width: 10px;
75
+ height: 10px;
76
+ border: 2px solid #e1e5e9;
77
+ border-top: 2px solid #4facfe;
78
+ border-radius: 50%;
79
+ animation: spin 1s linear infinite;
80
+ }
81
+
82
+ @keyframes spin {
83
+ 0% { transform: rotate(0deg); }
84
+ 100% { transform: rotate(360deg); }
85
+ }
86
+
87
+ liwe3-ai-text-editor {
88
+ width: 100%;
89
+ height: 100%;
90
+ }
91
+
92
+ .settings-modal {
93
+ position: absolute;
94
+ top: 50%;
95
+ left: 50%;
96
+ transform: translate(-50%, -50%);
97
+ background: white;
98
+ padding: 20px;
99
+ border-radius: 8px;
100
+ box-shadow: 0 4px 12px rgba(0,0,0,0.15);
101
+ z-index: 100;
102
+ width: 400px;
103
+ max-width: 90%;
104
+ display: none;
105
+ border: 1px solid #e5e7eb;
106
+ }
107
+
108
+ .settings-modal.open {
109
+ display: block;
110
+ }
111
+
112
+ .settings-modal h3 {
113
+ margin-top: 0;
114
+ margin-bottom: 15px;
115
+ font-size: 18px;
116
+ color: #1f2937;
117
+ }
118
+
119
+ .form-group {
120
+ margin-bottom: 15px;
121
+ }
122
+
123
+ .form-group label {
124
+ display: block;
125
+ margin-bottom: 5px;
126
+ font-size: 14px;
127
+ color: #4b5563;
128
+ }
129
+
130
+ .form-group input, .form-group textarea {
131
+ width: 100%;
132
+ padding: 8px;
133
+ border: 1px solid #d1d5db;
134
+ border-radius: 4px;
135
+ font-size: 14px;
136
+ box-sizing: border-box;
137
+ }
138
+
139
+ .form-group textarea {
140
+ resize: vertical;
141
+ min-height: 60px;
142
+ }
143
+
144
+ .modal-actions {
145
+ display: flex;
146
+ justify-content: flex-end;
147
+ gap: 10px;
148
+ margin-top: 20px;
149
+ }
150
+
151
+ .btn {
152
+ padding: 8px 16px;
153
+ border-radius: 4px;
154
+ border: none;
155
+ cursor: pointer;
156
+ font-size: 14px;
157
+ }
158
+
159
+ .btn-primary {
160
+ background: #3b82f6;
161
+ color: white;
162
+ }
163
+
164
+ .btn-secondary {
165
+ background: #e5e7eb;
166
+ color: #374151;
167
+ }
168
+
169
+ .overlay {
170
+ position: fixed;
171
+ top: 0;
172
+ left: 0;
173
+ right: 0;
174
+ bottom: 0;
175
+ background: rgba(0,0,0,0.5);
176
+ z-index: 99;
177
+ display: none;
178
+ }
179
+
180
+ .overlay.open {
181
+ display: block;
182
+ }
183
+ </style>
184
+ <liwe3-button-toolbar id="toolbar"></liwe3-button-toolbar>
185
+ <div class="editor-container">
186
+ <div class="editor-status"></div>
187
+ <div class="loading" id="loading">
188
+ <div class="spinner"></div>
189
+ </div>
190
+ <liwe3-ai-text-editor id="editor"></liwe3-ai-text-editor>
191
+ <liwe3-markdown-preview id="preview" style="display: none; width: 100%; height: 100%; overflow: auto;"></liwe3-markdown-preview>
192
+ </div>
193
+
194
+ <div class="overlay" id="overlay"></div>
195
+ <div class="settings-modal" id="settingsModal">
196
+ <h3>AI Settings</h3>
197
+ <div class="form-group">
198
+ <label for="apiKey">API Key</label>
199
+ <input type="password" id="apiKey" placeholder="sk-...">
200
+ </div>
201
+ <div class="form-group">
202
+ <label for="systemPrompt">System Prompt</label>
203
+ <textarea id="systemPrompt"></textarea>
204
+ </div>
205
+ <div class="form-group">
206
+ <label for="modelName">Model Name</label>
207
+ <input type="text" id="modelName" placeholder="gpt-3.5-turbo">
208
+ </div>
209
+ <div class="form-group">
210
+ <label for="apiEndpoint">API Endpoint</label>
211
+ <input type="text" id="apiEndpoint" placeholder="https://api.openai.com/v1/chat/completions">
212
+ </div>
213
+ <div class="form-group">
214
+ <label for="markdownLibUrl">Markdown Library URL</label>
215
+ <input type="text" id="markdownLibUrl" placeholder="https://cdn.jsdelivr.net/npm/marked@4.3.0/marked.min.js">
216
+ </div>
217
+ <div class="form-group">
218
+ <label for="suggestionDelay">Suggestion Delay (seconds)</label>
219
+ <input type="number" id="suggestionDelay" step="0.1" min="0.5">
220
+ </div>
221
+ <div class="modal-actions">
222
+ <button class="btn btn-secondary" id="cancelBtn">Cancel</button>
223
+ <button class="btn btn-primary" id="saveBtn">Save</button>
224
+ </div>
225
+ </div>
226
+ `;
227
+ }
228
+
229
+ private init(): void {
230
+ this.aiEditor = this.shadowRoot.getElementById('editor') as AITextEditorElement;
231
+ this.preview = this.shadowRoot.getElementById('preview') as MarkdownPreviewElement;
232
+ this.toolbar = this.shadowRoot.getElementById('toolbar') as ButtonToolbarElement;
233
+ this.editorStatus = this.shadowRoot.querySelector('.editor-status') as HTMLElement;
234
+ this.loading = this.shadowRoot.getElementById('loading') as HTMLElement;
235
+
236
+ // Configure the AITextEditor in embedded mode with callbacks
237
+ this.aiEditor.configure({
238
+ embedded: true,
239
+ onStatusChange: (hasApiKey: boolean) => {
240
+ this.editorStatus.style.backgroundColor = hasApiKey ? '#4caf50' : '#777';
241
+ },
242
+ onLoadingChange: (isLoading: boolean) => {
243
+ if (isLoading) {
244
+ this.loading.classList.add('show');
245
+ } else {
246
+ this.loading.classList.remove('show');
247
+ }
248
+ }
249
+ });
250
+
251
+ this.setupModal();
252
+ this.setupToolbar();
253
+
254
+ // Forward events from aiEditor
255
+ this.aiEditor.addEventListener('change', (e: Event) => {
256
+ this.dispatchEvent(new CustomEvent('change', {
257
+ detail: (e as CustomEvent).detail,
258
+ bubbles: true,
259
+ composed: true
260
+ }));
261
+ });
262
+
263
+ this.aiEditor.addEventListener('beforeSuggestion', (e: Event) => {
264
+ this.dispatchEvent(new CustomEvent('beforeSuggestion', {
265
+ detail: (e as CustomEvent).detail,
266
+ bubbles: true,
267
+ composed: true,
268
+ cancelable: true
269
+ }));
270
+ });
271
+ }
272
+
273
+ private setupModal(): void {
274
+ const modal = this.shadowRoot.getElementById('settingsModal') as HTMLElement;
275
+ const overlay = this.shadowRoot.getElementById('overlay') as HTMLElement;
276
+ const cancelBtn = this.shadowRoot.getElementById('cancelBtn') as HTMLButtonElement;
277
+ const saveBtn = this.shadowRoot.getElementById('saveBtn') as HTMLButtonElement;
278
+
279
+ const close = () => {
280
+ modal.classList.remove('open');
281
+ overlay.classList.remove('open');
282
+ };
283
+
284
+ cancelBtn.addEventListener('click', close);
285
+ overlay.addEventListener('click', close);
286
+
287
+ saveBtn.addEventListener('click', () => {
288
+ const apiKey = (this.shadowRoot.getElementById('apiKey') as HTMLInputElement).value;
289
+ const systemPrompt = (this.shadowRoot.getElementById('systemPrompt') as HTMLTextAreaElement).value;
290
+ const modelName = (this.shadowRoot.getElementById('modelName') as HTMLInputElement).value;
291
+ const apiEndpoint = (this.shadowRoot.getElementById('apiEndpoint') as HTMLInputElement).value;
292
+ const markdownLibUrl = (this.shadowRoot.getElementById('markdownLibUrl') as HTMLInputElement).value;
293
+ const suggestionDelay = parseFloat((this.shadowRoot.getElementById('suggestionDelay') as HTMLInputElement).value);
294
+
295
+ this.aiEditor.setApiKey(apiKey);
296
+ this.aiEditor.setSystemPrompt(systemPrompt);
297
+ this.aiEditor.setModelName(modelName);
298
+ this.aiEditor.setApiEndpoint(apiEndpoint);
299
+ this.markdownLibUrl = markdownLibUrl;
300
+ if (!isNaN(suggestionDelay)) {
301
+ this.aiEditor.setSuggestionDelay(suggestionDelay);
302
+ }
303
+
304
+ close();
305
+ });
306
+ }
307
+
308
+ private openSettings(): void {
309
+ const modal = this.shadowRoot.getElementById('settingsModal') as HTMLElement;
310
+ const overlay = this.shadowRoot.getElementById('overlay') as HTMLElement;
311
+
312
+ // Load current values
313
+ (this.shadowRoot.getElementById('apiKey') as HTMLInputElement).value = this.aiEditor.getApiKey();
314
+ (this.shadowRoot.getElementById('systemPrompt') as HTMLTextAreaElement).value = this.aiEditor.getSystemPrompt();
315
+ (this.shadowRoot.getElementById('modelName') as HTMLInputElement).value = this.aiEditor.getModelName();
316
+ (this.shadowRoot.getElementById('apiEndpoint') as HTMLInputElement).value = this.aiEditor.getApiEndpoint();
317
+ (this.shadowRoot.getElementById('markdownLibUrl') as HTMLInputElement).value = this.markdownLibUrl;
318
+ (this.shadowRoot.getElementById('suggestionDelay') as HTMLInputElement).value = this.aiEditor.getSuggestionDelay().toString();
319
+
320
+ modal.classList.add('open');
321
+ overlay.classList.add('open');
322
+ }
323
+
324
+ private setupToolbar(): void {
325
+ const groups: ButtonToolbarGroup[] = [
326
+ {
327
+ id: 'format',
328
+ items: [
329
+ {
330
+ id: 'bold',
331
+ label: 'B',
332
+ tooltip: 'Bold',
333
+ icon: '<svg viewBox="0 0 24 24"><path d="M8 11h4.5a2.5 2.5 0 0 0 0-5H8v5zm10 4.5a4.5 4.5 0 0 1-4.5 4.5H6V4h6.5a4.5 4.5 0 0 1 3.26 7.586A4.5 4.5 0 0 1 18 15.5zM8 13v5h5.5a2.5 2.5 0 0 0 0-5H8z"/></svg>'
334
+ },
335
+ {
336
+ id: 'italic',
337
+ label: 'I',
338
+ tooltip: 'Italic',
339
+ icon: '<svg viewBox="0 0 24 24"><path d="M10 4v3h2.21l-3.42 8H6v3h8v-3h-2.21l3.42-8H18V4z"/></svg>'
340
+ },
341
+ {
342
+ id: 'underline',
343
+ label: 'U',
344
+ tooltip: 'Underline',
345
+ icon: '<svg viewBox="0 0 24 24"><path d="M12 17c3.31 0 6-2.69 6-6V3h-2.5v8c0 1.93-1.57 3.5-3.5 3.5S8.5 12.93 8.5 11V3H6v8c0 3.31 2.69 6 6 6zm-7 2v2h14v-2H5z"/></svg>'
346
+ }
347
+ ]
348
+ },
349
+ {
350
+ id: 'lists',
351
+ items: [
352
+ {
353
+ id: 'ul',
354
+ label: 'UL',
355
+ tooltip: 'Unordered List',
356
+ icon: '<svg viewBox="0 0 24 24"><path d="M4 10.5c-.83 0-1.5.67-1.5 1.5s.67 1.5 1.5 1.5 1.5-.67 1.5-1.5-.67-1.5-1.5-1.5zm0-6c-.83 0-1.5.67-1.5 1.5S3.17 7.5 4 7.5 5.5 6.83 5.5 6 4.83 4.5 4 4.5zm0 12c-.83 0-1.5.67-1.5 1.5s.67 1.5 1.5 1.5 1.5-.67 1.5-1.5-.67-1.5-1.5-1.5zM7 19h14v-2H7v2zm0-6h14v-2H7v2zm0-8v2h14V5H7z"/></svg>'
357
+ },
358
+ {
359
+ id: 'ol',
360
+ label: 'OL',
361
+ tooltip: 'Numbered List',
362
+ icon: '<svg viewBox="0 0 24 24"><path d="M2 17h2v.5H3v1h1v.5H2v1h3v-4H2v1zm1-9h1V4H2v1h1v3zm-1 3h1.8L2 13.1v.9h3v-1H3.2L5 10.9V10H2v1zm5-6v2h14V5H7zm0 14h14v-2H7v2zm0-6h14v-2H7v2z"/></svg>'
363
+ }
364
+ ]
365
+ },
366
+ {
367
+ id: 'settings',
368
+ items: [
369
+ {
370
+ id: 'preview',
371
+ tooltip: 'Toggle Preview',
372
+ icon: '<svg viewBox="0 0 24 24"><path d="M12 4.5C7 4.5 2.73 7.61 1 12c1.73 4.39 6 7.5 11 7.5s9.27-3.11 11-7.5c-1.73-4.39-6-7.5-11-7.5zM12 17c-2.76 0-5-2.24-5-5s2.24-5 5-5 5 2.24 5 5-2.24 5-5 5zm0-8c-1.66 0-3 1.34-3 3s1.34 3 3 3 3-1.34 3-3-1.34-3-3-3z"/></svg>'
373
+ },
374
+ {
375
+ id: 'settings',
376
+ tooltip: 'AI Settings',
377
+ icon: '<svg viewBox="0 0 24 24"><path d="M19.14 12.94c.04-.3.06-.61.06-.94 0-.32-.02-.64-.07-.94l2.03-1.58c.18-.14.23-.41.12-.61l-1.92-3.32c-.12-.22-.37-.29-.59-.22l-2.39.96c-.5-.38-1.03-.7-1.62-.94l-.36-2.54c-.04-.24-.24-.41-.48-.41h-3.84c-.24 0-.43.17-.47.41l-.36 2.54c-.59.24-1.13.57-1.62.94l-2.39-.96c-.22-.08-.47 0-.59.22L4.16 8.87c-.12.21-.08.47.12.61l2.03 1.58c-.05.3-.09.63-.09.94s.02.64.07.94l-2.03 1.58c-.18.14-.23.41-.12.61l1.92 3.32c.12.22.37.29.59.22l2.39-.96c.5.38 1.03.7 1.62.94l.36 2.54c.04.24.24.41.48.41h3.84c.24 0 .43-.17.47-.41l.36-2.54c.59-.24 1.13-.57 1.62-.94l2.39.96c.22.08.47 0 .59-.22l1.92-3.32c.12-.22.08-.47-.12-.61l-2.01-1.58zM12 15.6c-1.98 0-3.6-1.62-3.6-3.6s1.62-3.6 3.6-3.6 3.6 1.62 3.6 3.6-1.62 3.6-3.6 3.6z"/></svg>'
378
+ }
379
+ ]
380
+ }
381
+ ];
382
+
383
+ this.toolbar.groups = groups;
384
+ this.toolbar.addEventListener('button-click', (e: Event) => {
385
+ const detail = (e as CustomEvent).detail;
386
+ this.handleToolbarAction(detail.id);
387
+ });
388
+ }
389
+
390
+ private togglePreview(): void {
391
+ this.isPreviewMode = !this.isPreviewMode;
392
+ if (this.isPreviewMode) {
393
+ this.preview.value = this.aiEditor.getText();
394
+ this.preview.libUrl = this.markdownLibUrl;
395
+ this.aiEditor.style.display = 'none';
396
+ this.preview.style.display = 'block';
397
+ } else {
398
+ this.aiEditor.style.display = 'block';
399
+ this.preview.style.display = 'none';
400
+ }
401
+ }
402
+
403
+ private handleToolbarAction(actionId: string): void {
404
+ if (actionId === 'preview') {
405
+ this.togglePreview();
406
+ return;
407
+ }
408
+
409
+ const textarea = this.aiEditor.shadowRoot?.getElementById('editor') as HTMLTextAreaElement;
410
+ if (!textarea) return;
411
+
412
+ const start = textarea.selectionStart;
413
+ const end = textarea.selectionEnd;
414
+ const text = textarea.value;
415
+ const selectedText = text.substring(start, end);
416
+
417
+ let newText = '';
418
+ let newCursorPos = end;
419
+
420
+ switch (actionId) {
421
+ case 'settings':
422
+ this.openSettings();
423
+ return; // Exit early as we don't need text manipulation
424
+ case 'bold':
425
+ newText = text.substring(0, start) + `**${selectedText}**` + text.substring(end);
426
+ newCursorPos = end + 4; // ** + **
427
+ if (start === end) newCursorPos = start + 2; // Position inside **
428
+ break;
429
+ case 'italic':
430
+ newText = text.substring(0, start) + `*${selectedText}*` + text.substring(end);
431
+ newCursorPos = end + 2; // * + *
432
+ if (start === end) newCursorPos = start + 1; // Position inside *
433
+ break;
434
+ case 'underline':
435
+ newText = text.substring(0, start) + `<u>${selectedText}</u>` + text.substring(end);
436
+ newCursorPos = end + 7; // <u> + </u>
437
+ if (start === end) newCursorPos = start + 3; // Position inside <u>
438
+ break;
439
+ case 'ul':
440
+ // For lists, we want to handle multiline selection
441
+ if (start !== end) {
442
+ const lines = selectedText.split('\n');
443
+ const listText = lines.map(line => `- ${line}`).join('\n');
444
+ newText = text.substring(0, start) + listText + text.substring(end);
445
+ newCursorPos = start + listText.length;
446
+ } else {
447
+ // Insert at beginning of line? Or just insert marker?
448
+ // Simple version: insert marker
449
+ newText = text.substring(0, start) + `- ` + text.substring(end);
450
+ newCursorPos = start + 2;
451
+ }
452
+ break;
453
+ case 'ol':
454
+ if (start !== end) {
455
+ const lines = selectedText.split('\n');
456
+ const listText = lines.map((line, i) => `${i + 1}. ${line}`).join('\n');
457
+ newText = text.substring(0, start) + listText + text.substring(end);
458
+ newCursorPos = start + listText.length;
459
+ } else {
460
+ newText = text.substring(0, start) + `1. ` + text.substring(end);
461
+ newCursorPos = start + 3;
462
+ }
463
+ break;
464
+ }
465
+
466
+ if (newText) {
467
+ textarea.value = newText;
468
+ textarea.focus();
469
+
470
+ // If we just inserted markers around empty selection, put cursor inside
471
+ if (start === end) {
472
+ if (actionId === 'bold') textarea.setSelectionRange(start + 2, start + 2);
473
+ else if (actionId === 'italic') textarea.setSelectionRange(start + 1, start + 1);
474
+ else if (actionId === 'underline') textarea.setSelectionRange(start + 3, start + 3);
475
+ else textarea.setSelectionRange(newCursorPos, newCursorPos);
476
+ } else {
477
+ // Select the modified text
478
+ // This is a bit complex to calculate exactly for all cases, so just putting cursor at end for now
479
+ textarea.setSelectionRange(newCursorPos, newCursorPos);
480
+ }
481
+
482
+ // Trigger input event so AITextEditor updates
483
+ textarea.dispatchEvent(new Event('input', { bubbles: true }));
484
+ }
485
+ }
486
+
487
+ // Proxy methods to AITextEditor
488
+ setText(text: string): void {
489
+ if (this.aiEditor) {
490
+ this.aiEditor.setText(text);
491
+ }
492
+ }
493
+
494
+ getText(): string {
495
+ return this.aiEditor ? this.aiEditor.getText() : '';
496
+ }
497
+
498
+ setApiKey(key: string): void {
499
+ if (this.aiEditor) {
500
+ this.aiEditor.setApiKey(key);
501
+ }
502
+ }
503
+
504
+ getApiKey(): string {
505
+ return this.aiEditor ? this.aiEditor.getApiKey() : '';
506
+ }
507
+
508
+ setSuggestionDelay(seconds: number): void {
509
+ if (this.aiEditor) {
510
+ this.aiEditor.setSuggestionDelay(seconds);
511
+ }
512
+ }
513
+
514
+ getSuggestionDelay(): number {
515
+ return this.aiEditor ? this.aiEditor.getSuggestionDelay() : 1;
516
+ }
517
+
518
+ setSystemPrompt(prompt: string): void {
519
+ if (this.aiEditor) {
520
+ this.aiEditor.setSystemPrompt(prompt);
521
+ }
522
+ }
523
+
524
+ getSystemPrompt(): string {
525
+ return this.aiEditor ? this.aiEditor.getSystemPrompt() : '';
526
+ }
527
+
528
+ setApiEndpoint(endpoint: string): void {
529
+ if (this.aiEditor) {
530
+ this.aiEditor.setApiEndpoint(endpoint);
531
+ }
532
+ }
533
+
534
+ getApiEndpoint(): string {
535
+ return this.aiEditor ? this.aiEditor.getApiEndpoint() : '';
536
+ }
537
+
538
+ setModelName(modelName: string): void {
539
+ if (this.aiEditor) {
540
+ this.aiEditor.setModelName(modelName);
541
+ }
542
+ }
543
+
544
+ getModelName(): string {
545
+ return this.aiEditor ? this.aiEditor.getModelName() : '';
546
+ }
547
+
548
+ setContext(context: string): void {
549
+ if (this.aiEditor) {
550
+ this.aiEditor.setContext(context);
551
+ }
552
+ }
553
+
554
+ getContext(): string {
555
+ return this.aiEditor ? this.aiEditor.getContext() : '';
556
+ }
557
+ }
558
+
559
+ export const defineAIMarkdownEditor = (tagName: string = 'liwe3-ai-markdown-editor'): void => {
560
+ if (typeof window !== 'undefined') {
561
+ defineAITextEditor();
562
+ defineButtonToolbar();
563
+ defineMarkdownPreview();
564
+ if (!customElements.get(tagName)) {
565
+ customElements.define(tagName, AIMarkdownEditorElement);
566
+ }
567
+ }
568
+ };