tulih-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.
Files changed (122) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +331 -0
  3. package/dist/tulih-editor.css +1 -0
  4. package/dist/tulih-editor.es.js +3051 -0
  5. package/dist/tulih-editor.es.js.map +1 -0
  6. package/dist/tulih-editor.umd.js +8 -0
  7. package/dist/tulih-editor.umd.js.map +1 -0
  8. package/dist/types/core/Editor.d.ts +20 -0
  9. package/dist/types/core/PluginManager.d.ts +22 -0
  10. package/dist/types/core/helpers.d.ts +22 -0
  11. package/dist/types/index.d.ts +5 -0
  12. package/dist/types/plugins/align.d.ts +3 -0
  13. package/dist/types/plugins/autoLinkify.d.ts +3 -0
  14. package/dist/types/plugins/autosave.d.ts +3 -0
  15. package/dist/types/plugins/block.d.ts +3 -0
  16. package/dist/types/plugins/caseTransform.d.ts +3 -0
  17. package/dist/types/plugins/codeBlock.d.ts +3 -0
  18. package/dist/types/plugins/colors.d.ts +3 -0
  19. package/dist/types/plugins/darkMode.d.ts +3 -0
  20. package/dist/types/plugins/direction.d.ts +3 -0
  21. package/dist/types/plugins/dragDrop.d.ts +3 -0
  22. package/dist/types/plugins/emoji.d.ts +3 -0
  23. package/dist/types/plugins/emojiAutocomplete.d.ts +3 -0
  24. package/dist/types/plugins/findReplace.d.ts +3 -0
  25. package/dist/types/plugins/floatingToolbar.d.ts +3 -0
  26. package/dist/types/plugins/fontFamily.d.ts +3 -0
  27. package/dist/types/plugins/fontSize.d.ts +3 -0
  28. package/dist/types/plugins/fullscreen.d.ts +3 -0
  29. package/dist/types/plugins/history.d.ts +3 -0
  30. package/dist/types/plugins/hr.d.ts +3 -0
  31. package/dist/types/plugins/iframe.d.ts +3 -0
  32. package/dist/types/plugins/image.d.ts +3 -0
  33. package/dist/types/plugins/imageProps.d.ts +3 -0
  34. package/dist/types/plugins/imageTools.d.ts +3 -0
  35. package/dist/types/plugins/indent.d.ts +3 -0
  36. package/dist/types/plugins/index.d.ts +2 -0
  37. package/dist/types/plugins/inline.d.ts +3 -0
  38. package/dist/types/plugins/inlineCode.d.ts +3 -0
  39. package/dist/types/plugins/keyboardShortcuts.d.ts +3 -0
  40. package/dist/types/plugins/lineHeight.d.ts +3 -0
  41. package/dist/types/plugins/link.d.ts +3 -0
  42. package/dist/types/plugins/linkTooltip.d.ts +3 -0
  43. package/dist/types/plugins/list.d.ts +3 -0
  44. package/dist/types/plugins/markdown.d.ts +3 -0
  45. package/dist/types/plugins/mediaEmbed.d.ts +3 -0
  46. package/dist/types/plugins/pasteImage.d.ts +3 -0
  47. package/dist/types/plugins/pastePlain.d.ts +3 -0
  48. package/dist/types/plugins/pre.d.ts +3 -0
  49. package/dist/types/plugins/readOnly.d.ts +3 -0
  50. package/dist/types/plugins/shortcutCustomizer.d.ts +3 -0
  51. package/dist/types/plugins/shortcutsHelp.d.ts +3 -0
  52. package/dist/types/plugins/source.d.ts +3 -0
  53. package/dist/types/plugins/specialChars.d.ts +3 -0
  54. package/dist/types/plugins/statusBar.d.ts +3 -0
  55. package/dist/types/plugins/subSuper.d.ts +3 -0
  56. package/dist/types/plugins/table.d.ts +3 -0
  57. package/dist/types/plugins/tableBg.d.ts +3 -0
  58. package/dist/types/plugins/tableTools.d.ts +3 -0
  59. package/dist/types/plugins/toolbarCollapse.d.ts +3 -0
  60. package/dist/types/plugins/unlink.d.ts +3 -0
  61. package/dist/types/plugins/wordCount.d.ts +3 -0
  62. package/dist/types/types.d.ts +226 -0
  63. package/package.json +66 -0
  64. package/src/core/Editor.ts +460 -0
  65. package/src/core/PluginManager.ts +140 -0
  66. package/src/core/helpers.ts +209 -0
  67. package/src/css.d.ts +2 -0
  68. package/src/index.ts +87 -0
  69. package/src/plugins/align.ts +72 -0
  70. package/src/plugins/autoLinkify.ts +34 -0
  71. package/src/plugins/autosave.ts +69 -0
  72. package/src/plugins/block.ts +32 -0
  73. package/src/plugins/caseTransform.ts +54 -0
  74. package/src/plugins/codeBlock.ts +93 -0
  75. package/src/plugins/colors.ts +68 -0
  76. package/src/plugins/darkMode.ts +123 -0
  77. package/src/plugins/direction.ts +30 -0
  78. package/src/plugins/dragDrop.ts +68 -0
  79. package/src/plugins/emoji.ts +188 -0
  80. package/src/plugins/emojiAutocomplete.ts +183 -0
  81. package/src/plugins/findReplace.ts +229 -0
  82. package/src/plugins/floatingToolbar.ts +258 -0
  83. package/src/plugins/fontFamily.ts +41 -0
  84. package/src/plugins/fontSize.ts +32 -0
  85. package/src/plugins/fullscreen.ts +36 -0
  86. package/src/plugins/history.ts +14 -0
  87. package/src/plugins/hr.ts +118 -0
  88. package/src/plugins/iframe.ts +88 -0
  89. package/src/plugins/image.ts +107 -0
  90. package/src/plugins/imageProps.ts +119 -0
  91. package/src/plugins/imageTools.ts +344 -0
  92. package/src/plugins/indent.ts +29 -0
  93. package/src/plugins/index.ts +101 -0
  94. package/src/plugins/inline.ts +17 -0
  95. package/src/plugins/inlineCode.ts +21 -0
  96. package/src/plugins/keyboardShortcuts.ts +92 -0
  97. package/src/plugins/lineHeight.ts +40 -0
  98. package/src/plugins/link.ts +344 -0
  99. package/src/plugins/linkTooltip.ts +63 -0
  100. package/src/plugins/list.ts +141 -0
  101. package/src/plugins/markdown.ts +61 -0
  102. package/src/plugins/mediaEmbed.ts +44 -0
  103. package/src/plugins/pasteImage.ts +61 -0
  104. package/src/plugins/pastePlain.ts +43 -0
  105. package/src/plugins/pre.ts +11 -0
  106. package/src/plugins/readOnly.ts +46 -0
  107. package/src/plugins/shortcutCustomizer.ts +125 -0
  108. package/src/plugins/shortcutsHelp.ts +51 -0
  109. package/src/plugins/source.ts +77 -0
  110. package/src/plugins/specialChars.ts +64 -0
  111. package/src/plugins/statusBar.ts +85 -0
  112. package/src/plugins/subSuper.ts +20 -0
  113. package/src/plugins/table.ts +166 -0
  114. package/src/plugins/tableBg.ts +11 -0
  115. package/src/plugins/tableTools.ts +475 -0
  116. package/src/plugins/toolbarCollapse.ts +14 -0
  117. package/src/plugins/unlink.ts +29 -0
  118. package/src/plugins/wordCount.ts +34 -0
  119. package/src/styles/base.css +258 -0
  120. package/src/styles/editor.css +309 -0
  121. package/src/styles/index.css +6 -0
  122. package/src/types.ts +278 -0
@@ -0,0 +1,29 @@
1
+ import type { Plugin, PluginContext } from '../types';
2
+
3
+ const unlink: Plugin = {
4
+ name: 'unlink',
5
+ order: 58,
6
+ toolbarHTML: '<button type="button" class="btn btn-sm btn-light" title="Remove link" data-cmd="unlink"><i class="ti ti-x"></i></button>',
7
+ init(ctx: PluginContext): void {
8
+ if(!ctx.features.unlink) return;
9
+ var btn = ctx.wrapper.querySelector('[data-cmd="unlink"]');
10
+ if(!btn) return;
11
+ btn.addEventListener('click', function(){
12
+ var sel = window.getSelection();
13
+ if(!sel || sel.rangeCount === 0) return;
14
+ var node: Node | null = sel.anchorNode;
15
+ if(node && node.nodeType === 3) node = node.parentNode;
16
+ var link = node && (node as HTMLElement).closest && (node as HTMLElement).closest('a') as HTMLAnchorElement | null;
17
+ if(link){
18
+ var parent = link.parentNode!;
19
+ while(link.firstChild){ parent.insertBefore(link.firstChild, link); }
20
+ parent.removeChild(link);
21
+ } else {
22
+ document.execCommand('unlink', false, undefined);
23
+ }
24
+ ctx.editor.focus();
25
+ });
26
+ }
27
+ };
28
+
29
+ export default unlink;
@@ -0,0 +1,34 @@
1
+ import type { Plugin, PluginContext } from '../types';
2
+
3
+ const wordCount: Plugin = {
4
+ name: 'wordCount',
5
+ order: 200,
6
+ css: '' +
7
+ '.te-statusbar {' +
8
+ ' display: flex; justify-content: flex-end; gap: 1rem;' +
9
+ ' padding: .3rem .5rem; font-size: .75rem; color: #6c757d;' +
10
+ ' border: 1px solid #dee2e6; border-top: none;' +
11
+ ' border-radius: 0 0 .375rem .375rem;' +
12
+ ' background: #f8f9fa;' +
13
+ '}',
14
+ init(ctx: PluginContext): void {
15
+ if (!ctx.features.wordCount) return;
16
+
17
+ var bar = document.createElement('div');
18
+ bar.className = 'te-statusbar';
19
+ ctx.wrapper.appendChild(bar);
20
+
21
+ function update() {
22
+ var text = ctx.editor.textContent || '';
23
+ var chars = text.length;
24
+ var words = text.trim() ? text.trim().split(/\s+/).length : 0;
25
+ bar.innerHTML = '<span>Words: ' + words + '</span><span>Chars: ' + chars + '</span>';
26
+ }
27
+
28
+ ctx.editor.addEventListener('input', update);
29
+ ctx.editor.addEventListener('paste', function () { setTimeout(update, 50); });
30
+ setTimeout(update, 100);
31
+ }
32
+ };
33
+
34
+ export default wordCount;
@@ -0,0 +1,258 @@
1
+ /*
2
+ * TulihEditor base styles — a small, self-contained set of button/form/layout
3
+ * primitives so the editor looks correct WITHOUT Bootstrap on the page.
4
+ *
5
+ * Everything is scoped (via :where, specificity 0) to TulihEditor roots: the
6
+ * main `.te-container` plus the popups that get appended to <body>. This means:
7
+ * - styles never leak onto the host page, and
8
+ * - if the host DOES load Bootstrap, these low-specificity rules quietly
9
+ * defer to it.
10
+ * The themed look (colors) lives in editor.css and in each plugin's injected CSS.
11
+ */
12
+
13
+ /* ---- Buttons ---------------------------------------------------------- */
14
+ :where(
15
+ .te-container, .te-float-toolbar, .te-float-link-input, .te-link-tools,
16
+ .te-link-edit-popup, .te-image-tools, .te-table-tools, .te-hr-popup,
17
+ .te-fr-popup, .te-emoji-popup, .te-sc-popup, .te-codeblock-popup
18
+ ) .btn {
19
+ display: inline-flex;
20
+ align-items: center;
21
+ justify-content: center;
22
+ gap: 4px;
23
+ font-family: inherit;
24
+ font-size: 1rem;
25
+ font-weight: 400;
26
+ line-height: 1.5;
27
+ text-align: center;
28
+ text-decoration: none;
29
+ vertical-align: middle;
30
+ white-space: nowrap;
31
+ cursor: pointer;
32
+ user-select: none;
33
+ padding: 0.375rem 0.75rem;
34
+ border: 1px solid transparent;
35
+ border-radius: 0.375rem;
36
+ background-color: transparent;
37
+ color: #212529;
38
+ transition: color .15s ease-in-out, background-color .15s ease-in-out, border-color .15s ease-in-out;
39
+ }
40
+ :where(
41
+ .te-container, .te-float-toolbar, .te-float-link-input, .te-link-tools,
42
+ .te-link-edit-popup, .te-image-tools, .te-table-tools, .te-hr-popup,
43
+ .te-fr-popup, .te-emoji-popup, .te-sc-popup, .te-codeblock-popup
44
+ ) .btn:focus-visible {
45
+ outline: 2px solid rgba(26, 26, 26, .35);
46
+ outline-offset: 1px;
47
+ }
48
+ :where(
49
+ .te-container, .te-float-toolbar, .te-float-link-input, .te-link-tools,
50
+ .te-link-edit-popup, .te-image-tools, .te-table-tools, .te-hr-popup,
51
+ .te-fr-popup, .te-emoji-popup, .te-sc-popup, .te-codeblock-popup
52
+ ) .btn:disabled,
53
+ :where(
54
+ .te-container, .te-float-toolbar, .te-float-link-input, .te-link-tools,
55
+ .te-link-edit-popup, .te-image-tools, .te-table-tools, .te-hr-popup,
56
+ .te-fr-popup, .te-emoji-popup, .te-sc-popup, .te-codeblock-popup
57
+ ) .btn[disabled] {
58
+ opacity: .5;
59
+ pointer-events: none;
60
+ }
61
+ :where(
62
+ .te-container, .te-float-toolbar, .te-float-link-input, .te-link-tools,
63
+ .te-link-edit-popup, .te-image-tools, .te-table-tools, .te-hr-popup,
64
+ .te-fr-popup, .te-emoji-popup, .te-sc-popup, .te-codeblock-popup
65
+ ) .btn-sm {
66
+ padding: 0.25rem 0.5rem;
67
+ font-size: 0.875rem;
68
+ border-radius: 0.25rem;
69
+ }
70
+
71
+ /* Button color variants (used by toolbar and body-appended popups) */
72
+ :where(
73
+ .te-container, .te-float-toolbar, .te-link-tools, .te-link-edit-popup,
74
+ .te-image-tools, .te-table-tools, .te-hr-popup, .te-fr-popup,
75
+ .te-emoji-popup, .te-sc-popup, .te-codeblock-popup
76
+ ) .btn-light {
77
+ color: #3A3A3A;
78
+ background-color: #f8f9fa;
79
+ border-color: transparent;
80
+ }
81
+ :where(
82
+ .te-container, .te-float-toolbar, .te-link-tools, .te-link-edit-popup,
83
+ .te-image-tools, .te-table-tools, .te-hr-popup, .te-fr-popup,
84
+ .te-emoji-popup, .te-sc-popup, .te-codeblock-popup
85
+ ) .btn-light:hover {
86
+ background-color: #F2EDE2;
87
+ }
88
+ :where(.te-container, .te-link-tools, .te-link-edit-popup, .te-hr-popup, .te-fr-popup, .te-codeblock-popup, .te-table-tools) .btn-primary {
89
+ color: #fff;
90
+ background-color: #1A1A1A;
91
+ border-color: #1A1A1A;
92
+ }
93
+ :where(.te-container, .te-link-tools, .te-link-edit-popup, .te-hr-popup, .te-fr-popup, .te-codeblock-popup, .te-table-tools) .btn-primary:hover {
94
+ background-color: #333;
95
+ border-color: #333;
96
+ }
97
+ :where(.te-container, .te-link-tools, .te-link-edit-popup, .te-hr-popup, .te-fr-popup, .te-codeblock-popup, .te-table-tools) .btn-secondary {
98
+ color: #fff;
99
+ background-color: #6c757d;
100
+ border-color: #6c757d;
101
+ }
102
+ :where(.te-container, .te-link-tools, .te-link-edit-popup, .te-hr-popup, .te-fr-popup, .te-codeblock-popup, .te-table-tools) .btn-secondary:hover {
103
+ background-color: #5c636a;
104
+ border-color: #565e64;
105
+ }
106
+ :where(.te-container, .te-link-tools, .te-link-edit-popup, .te-image-tools, .te-table-tools, .te-hr-popup, .te-fr-popup, .te-codeblock-popup) .btn-outline-secondary {
107
+ color: #3A3A3A;
108
+ border-color: #dee2e6;
109
+ }
110
+ :where(.te-container, .te-link-tools, .te-link-edit-popup, .te-image-tools, .te-table-tools, .te-hr-popup, .te-fr-popup, .te-codeblock-popup) .btn-outline-secondary:hover {
111
+ background-color: #F2EDE2;
112
+ border-color: #ccc;
113
+ }
114
+ :where(.te-container, .te-link-tools, .te-link-edit-popup, .te-image-tools, .te-table-tools, .te-hr-popup, .te-fr-popup, .te-codeblock-popup) .btn-outline-danger {
115
+ color: #C1272D;
116
+ border-color: #C1272D;
117
+ }
118
+ :where(.te-container, .te-link-tools, .te-link-edit-popup, .te-image-tools, .te-table-tools, .te-hr-popup, .te-fr-popup, .te-codeblock-popup) .btn-outline-danger:hover {
119
+ background-color: #C1272D;
120
+ color: #fff;
121
+ }
122
+ :where(.te-container, .te-link-tools, .te-link-edit-popup, .te-image-tools, .te-table-tools, .te-hr-popup, .te-fr-popup, .te-codeblock-popup) .btn-outline-primary {
123
+ color: #E8A317;
124
+ border-color: #E8A317;
125
+ }
126
+ :where(.te-container, .te-link-tools, .te-link-edit-popup, .te-image-tools, .te-table-tools, .te-hr-popup, .te-fr-popup, .te-codeblock-popup) .btn-outline-primary:hover {
127
+ background-color: #E8A317;
128
+ color: #fff;
129
+ }
130
+
131
+ /* Close button (modals) */
132
+ :where(.te-container) .btn-close {
133
+ box-sizing: content-box;
134
+ width: 1em;
135
+ height: 1em;
136
+ padding: 0.25em;
137
+ border: 0;
138
+ border-radius: 0.375rem;
139
+ background: transparent url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3e%3cpath d='M.293.293a1 1 0 0 1 1.414 0L8 6.586 14.293.293a1 1 0 1 1 1.414 1.414L9.414 8l6.293 6.293a1 1 0 0 1-1.414 1.414L8 9.414l-6.293 6.293a1 1 0 0 1-1.414-1.414L6.586 8 .293 1.707a1 1 0 0 1 0-1.414z'/%3e%3c/svg%3e") center/1em auto no-repeat;
140
+ cursor: pointer;
141
+ opacity: .5;
142
+ }
143
+ :where(.te-container) .btn-close:hover {
144
+ opacity: .8;
145
+ }
146
+
147
+ /* ---- Form controls ---------------------------------------------------- */
148
+ :where(.te-container, .te-fr-popup, .te-hr-popup, .te-codeblock-popup, .te-float-link-input, .te-link-edit-popup) .form-control,
149
+ :where(.te-container, .te-fr-popup, .te-hr-popup, .te-codeblock-popup, .te-float-link-input, .te-link-edit-popup) .form-select {
150
+ display: block;
151
+ width: 100%;
152
+ padding: 0.375rem 0.75rem;
153
+ font-family: inherit;
154
+ font-size: 1rem;
155
+ font-weight: 400;
156
+ line-height: 1.5;
157
+ color: #212529;
158
+ background-color: #fff;
159
+ border: 1px solid #ced4da;
160
+ border-radius: 0.375rem;
161
+ appearance: none;
162
+ }
163
+ :where(.te-container, .te-fr-popup, .te-hr-popup, .te-codeblock-popup, .te-float-link-input, .te-link-edit-popup) .form-control:focus,
164
+ :where(.te-container, .te-fr-popup, .te-hr-popup, .te-codeblock-popup, .te-float-link-input, .te-link-edit-popup) .form-select:focus {
165
+ outline: 0;
166
+ border-color: #86b7fe;
167
+ box-shadow: 0 0 0 0.2rem rgba(13, 110, 253, .2);
168
+ }
169
+ :where(.te-container, .te-fr-popup, .te-hr-popup, .te-codeblock-popup) .form-select {
170
+ padding-right: 2.25rem;
171
+ background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3e%3cpath fill='none' stroke='%23343a40' stroke-linecap='round' stroke-linejoin='round' stroke-width='2' d='m2 5 6 6 6-6'/%3e%3c/svg%3e");
172
+ background-repeat: no-repeat;
173
+ background-position: right 0.75rem center;
174
+ background-size: 16px 12px;
175
+ }
176
+ :where(.te-container, .te-fr-popup, .te-hr-popup, .te-codeblock-popup) .form-control-sm,
177
+ :where(.te-container, .te-fr-popup, .te-hr-popup, .te-codeblock-popup) .form-select-sm {
178
+ min-height: calc(1.5em + 0.5rem + 2px);
179
+ padding: 0.25rem 0.5rem;
180
+ font-size: 0.875rem;
181
+ border-radius: 0.25rem;
182
+ }
183
+ :where(.te-container, .te-fr-popup, .te-hr-popup, .te-codeblock-popup) .form-select-sm {
184
+ padding-right: 2rem;
185
+ }
186
+ :where(.te-container, .te-hr-popup) .form-control-color {
187
+ width: 3rem;
188
+ height: calc(1.5em + 0.75rem + 2px);
189
+ padding: 0.25rem;
190
+ }
191
+ :where(.te-container) .form-label {
192
+ display: inline-block;
193
+ margin-bottom: 0.5rem;
194
+ font-size: 0.9rem;
195
+ font-weight: 500;
196
+ color: #212529;
197
+ }
198
+
199
+ /* Checkboxes */
200
+ :where(.te-container) .form-check {
201
+ display: block;
202
+ min-height: 1.5rem;
203
+ padding-left: 1.5em;
204
+ margin-bottom: 0.125rem;
205
+ }
206
+ :where(.te-container) .form-check-input {
207
+ width: 1em;
208
+ height: 1em;
209
+ margin-top: 0.25em;
210
+ margin-left: -1.5em;
211
+ vertical-align: top;
212
+ background-color: #fff;
213
+ border: 1px solid rgba(0, 0, 0, .25);
214
+ border-radius: 0.25em;
215
+ appearance: none;
216
+ cursor: pointer;
217
+ }
218
+ :where(.te-container) .form-check-input:checked {
219
+ background-color: #1A1A1A;
220
+ border-color: #1A1A1A;
221
+ background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 20 20'%3e%3cpath fill='none' stroke='%23fff' stroke-linecap='round' stroke-linejoin='round' stroke-width='3' d='m6 10 3 3 6-6'/%3e%3c/svg%3e");
222
+ background-repeat: no-repeat;
223
+ background-position: center;
224
+ background-size: contain;
225
+ }
226
+ :where(.te-container) .form-check-label {
227
+ cursor: pointer;
228
+ }
229
+
230
+ /* ---- Minimal grid + spacing utilities (used inside modals) ----------- */
231
+ :where(.te-container) .row {
232
+ display: flex;
233
+ flex-wrap: wrap;
234
+ margin-right: -0.5rem;
235
+ margin-left: -0.5rem;
236
+ }
237
+ :where(.te-container) .row > .col,
238
+ :where(.te-container) .row > [class^="col-"],
239
+ :where(.te-container) .row > [class*=" col-"] {
240
+ flex: 1 0 0%;
241
+ padding-right: 0.5rem;
242
+ padding-left: 0.5rem;
243
+ }
244
+ :where(.te-container) .col-6 { flex: 0 0 auto; width: 50%; }
245
+ :where(.te-container) .col-4 { flex: 0 0 auto; width: 33.3333%; }
246
+ :where(.te-container) .col-3 { flex: 0 0 auto; width: 25%; }
247
+ :where(.te-container) .g-2 { gap: 0.5rem 0; }
248
+
249
+ :where(.te-container) .mb-0 { margin-bottom: 0 !important; }
250
+ :where(.te-container) .mb-2 { margin-bottom: 0.5rem !important; }
251
+ :where(.te-container) .mb-3 { margin-bottom: 1rem !important; }
252
+ :where(.te-container) .mt-2 { margin-top: 0.5rem !important; }
253
+ :where(.te-container) .mt-3 { margin-top: 1rem !important; }
254
+ :where(.te-container) .small { font-size: 0.875em; }
255
+ :where(.te-container) .w-100 { width: 100% !important; }
256
+ :where(.te-container) .d-block { display: block !important; }
257
+ :where(.te-container) .d-flex { display: flex !important; }
258
+ :where(.te-container) .text-muted { color: #6c757d !important; }
@@ -0,0 +1,309 @@
1
+ .te-container {
2
+ max-width: 900px;
3
+ position: relative;
4
+ }
5
+
6
+ .te-toolbar {
7
+ display: flex;
8
+ flex-wrap: wrap;
9
+ gap: 6px;
10
+ border: 1px solid #dee2e6;
11
+ background: #FBF9F4;
12
+ padding: 8px;
13
+ border-bottom: none;
14
+ border-radius: .375rem .375rem 0 0;
15
+ }
16
+
17
+ .te-toolbar .btn,
18
+ .te-toolbar .btn-light {
19
+ color: #3A3A3A;
20
+ background: transparent;
21
+ border-color: transparent;
22
+ }
23
+
24
+ .te-toolbar .btn:hover,
25
+ .te-toolbar .btn-light:hover {
26
+ background: #F2EDE2;
27
+ }
28
+
29
+ .te-toolbar .btn.active,
30
+ .te-toolbar .btn-light.active {
31
+ background: #FBEAD6;
32
+ border-color: #FBEAD6;
33
+ }
34
+
35
+ .te-toolbar .btn-outline-secondary {
36
+ color: #3A3A3A;
37
+ border-color: #dee2e6;
38
+ }
39
+
40
+ .te-toolbar select {
41
+ touch-action: manipulation;
42
+ }
43
+
44
+ .te-dropdown-icon {
45
+ display: inline-flex;
46
+ align-items: center;
47
+ vertical-align: middle;
48
+ margin-right: 2px;
49
+ }
50
+
51
+ .te-dropdown-icon svg {
52
+ width: 18px;
53
+ height: 18px;
54
+ stroke-width: 2;
55
+ }
56
+
57
+ .te-dropdown-icon-text {
58
+ font-size: 13px;
59
+ font-weight: 600;
60
+ line-height: 1;
61
+ color: #3A3A3A;
62
+ }
63
+
64
+ .te-toolbar .btn-primary,
65
+ .te-toolbar .btn.btn-primary {
66
+ background: #1A1A1A;
67
+ border-color: #1A1A1A;
68
+ color: #fff;
69
+ }
70
+ .te-toolbar .btn-primary:hover,
71
+ .te-toolbar .btn.btn-primary:hover {
72
+ background: #333;
73
+ border-color: #333;
74
+ }
75
+
76
+ .te-toolbar .btn-outline-danger,
77
+ .te-toolbar .btn.btn-outline-danger {
78
+ color: #C1272D;
79
+ border-color: #C1272D;
80
+ }
81
+ .te-toolbar .btn-outline-danger:hover,
82
+ .te-toolbar .btn.btn-outline-danger:hover {
83
+ background: #C1272D;
84
+ color: #fff;
85
+ }
86
+
87
+ .te-toolbar .btn-outline-primary,
88
+ .te-toolbar .btn.btn-outline-primary {
89
+ color: #E8A317;
90
+ border-color: #E8A317;
91
+ }
92
+ .te-toolbar .btn-outline-primary:hover,
93
+ .te-toolbar .btn.btn-outline-primary:hover {
94
+ background: #E8A317;
95
+ color: #fff;
96
+ }
97
+
98
+ .te-divider {
99
+ width: 1px;
100
+ background: #dee2e6;
101
+ align-self: stretch;
102
+ margin: 0 4px;
103
+ }
104
+
105
+ .te-editor {
106
+ height: 300px;
107
+ overflow: auto;
108
+ border: 1px solid #dee2e6;
109
+ border-radius: 0;
110
+ padding: 12px;
111
+ background: #fff;
112
+ }
113
+
114
+ .te-editor:focus {
115
+ outline: none;
116
+ box-shadow: inset 0 0 0 2px rgba(13,110,253,.25);
117
+ }
118
+
119
+ .te-toolbar .btn .ti { pointer-events: none; }
120
+
121
+ .te-editor[placeholder]:empty:before {
122
+ content: attr(placeholder);
123
+ color: #6c757d;
124
+ }
125
+
126
+ .te-toolbar select.form-select {
127
+ width: auto;
128
+ padding-right: 2rem;
129
+ }
130
+
131
+ .te-status {
132
+ color: #6c757d;
133
+ font-size: .875rem;
134
+ }
135
+
136
+ /* ── Modal ── */
137
+ @keyframes te-modal-in {
138
+ from { opacity: 0; transform: scale(0.95) translateY(-10px); }
139
+ to { opacity: 1; transform: scale(1) translateY(0); }
140
+ }
141
+ @keyframes te-backdrop-in {
142
+ from { opacity: 0; }
143
+ to { opacity: 1; }
144
+ }
145
+
146
+ .te-modal {
147
+ position: absolute;
148
+ inset: 0;
149
+ display: none;
150
+ align-items: center;
151
+ justify-content: center;
152
+ z-index: 2500;
153
+ overflow-y: auto;
154
+ padding: 1rem;
155
+ }
156
+ .te-modal.is-open {
157
+ display: flex;
158
+ }
159
+ .te-modal.is-open .te-modal-dialog {
160
+ animation: te-modal-in .2s ease-out;
161
+ }
162
+ .te-modal.is-open .te-modal-backdrop {
163
+ animation: te-backdrop-in .2s ease-out;
164
+ }
165
+
166
+ .te-modal-backdrop {
167
+ position: absolute;
168
+ inset: 0;
169
+ background: rgba(0,0,0,.4);
170
+ }
171
+
172
+ .te-modal-dialog {
173
+ position: relative;
174
+ background: #fff;
175
+ border-radius: .75rem;
176
+ box-shadow: 0 20px 60px rgba(0,0,0,.18);
177
+ width: 540px;
178
+ max-width: 100%;
179
+ overflow: hidden;
180
+ z-index: 1;
181
+ }
182
+
183
+ .te-modal-header {
184
+ display: flex;
185
+ align-items: center;
186
+ justify-content: space-between;
187
+ padding: 1rem 1.25rem;
188
+ background: #FBF9F4;
189
+ border-bottom: 1px solid #eee;
190
+ }
191
+ .te-modal-header .te-modal-title {
192
+ font-size: 1.05rem;
193
+ font-weight: 600;
194
+ color: #1A1A1A;
195
+ }
196
+ .te-modal-header .btn-close {
197
+ font-size: .85rem;
198
+ opacity: .5;
199
+ transition: opacity .15s;
200
+ }
201
+ .te-modal-header .btn-close:hover {
202
+ opacity: .8;
203
+ }
204
+
205
+ .te-modal-body {
206
+ padding: 1.25rem;
207
+ color: #212529;
208
+ max-height: 60vh;
209
+ overflow-y: auto;
210
+ }
211
+
212
+ .te-modal-footer {
213
+ display: flex;
214
+ gap: .5rem;
215
+ justify-content: flex-end;
216
+ padding: 1rem 1.25rem;
217
+ background: #FBF9F4;
218
+ border-top: 1px solid #eee;
219
+ }
220
+
221
+ /* Modal buttons */
222
+ .te-modal .btn-primary {
223
+ background: #1A1A1A;
224
+ border-color: #1A1A1A;
225
+ color: #fff;
226
+ }
227
+ .te-modal .btn-primary:hover {
228
+ background: #333;
229
+ border-color: #333;
230
+ }
231
+
232
+ .te-modal .btn-outline-secondary {
233
+ color: #3A3A3A;
234
+ border-color: #dee2e6;
235
+ }
236
+ .te-modal .btn-outline-secondary:hover {
237
+ background: #F2EDE2;
238
+ border-color: #ccc;
239
+ }
240
+
241
+ .te-modal .btn-outline-danger {
242
+ color: #C1272D;
243
+ border-color: #C1272D;
244
+ }
245
+ .te-modal .btn-outline-danger:hover {
246
+ background: #C1272D;
247
+ color: #fff;
248
+ }
249
+
250
+ .te-modal .btn-outline-primary {
251
+ color: #E8A317;
252
+ border-color: #E8A317;
253
+ }
254
+ .te-modal .btn-outline-primary:hover {
255
+ background: #E8A317;
256
+ color: #fff;
257
+ }
258
+
259
+ .te-editor a {
260
+ text-decoration: none;
261
+ }
262
+
263
+ .te-editor .te-table {
264
+ width: 100%;
265
+ border-collapse: collapse;
266
+ }
267
+
268
+ .te-editor .te-table td,
269
+ .te-editor .te-table th {
270
+ border: 1px solid #dee2e6;
271
+ padding: 6px;
272
+ }
273
+
274
+ /* Mobile */
275
+ @media (max-width: 576px) {
276
+ .te-toolbar .btn {
277
+ padding: .35rem .5rem;
278
+ font-size: .8rem;
279
+ }
280
+ .te-toolbar select.form-select {
281
+ font-size: .8rem;
282
+ padding: .2rem 1.5rem .2rem .5rem;
283
+ }
284
+ .te-editor {
285
+ padding: 8px;
286
+ font-size: .95rem;
287
+ }
288
+ .te-container .te-toolbar {
289
+ gap: 3px;
290
+ padding: 4px;
291
+ }
292
+ .te-divider {
293
+ margin: 0 2px;
294
+ }
295
+ .te-modal-dialog {
296
+ border-radius: .5rem;
297
+ }
298
+ .te-modal-header,
299
+ .te-modal-body,
300
+ .te-modal-footer {
301
+ padding: .75rem 1rem;
302
+ }
303
+ .te-modal-body {
304
+ max-height: 50vh;
305
+ }
306
+ }
307
+
308
+ /* Minimal mode: hide toolbar */
309
+ .te-minimal .te-toolbar { display: none; }
@@ -0,0 +1,6 @@
1
+ /* TulihEditor stylesheet entry. Imported by src/index.ts so the bundler emits a
2
+ * single dist/tulih-editor.css. `base.css` provides framework-free primitives;
3
+ * `editor.css` is the editor's own theme/layout. Plugin-specific CSS is injected
4
+ * at runtime by each plugin. */
5
+ @import './base.css';
6
+ @import './editor.css';