@bendyline/squisq-editor-react 1.1.1 → 1.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/PreviewControls.js +1 -1
- package/dist/PreviewControls.js.map +1 -1
- package/dist/Toolbar.d.ts.map +1 -1
- package/dist/Toolbar.js +143 -10
- package/dist/Toolbar.js.map +1 -1
- package/dist/__tests__/tiptapBridge.test.d.ts +2 -0
- package/dist/__tests__/tiptapBridge.test.d.ts.map +1 -0
- package/dist/__tests__/tiptapBridge.test.js +241 -0
- package/dist/__tests__/tiptapBridge.test.js.map +1 -0
- package/dist/tiptapBridge.d.ts.map +1 -1
- package/dist/tiptapBridge.js +142 -0
- package/dist/tiptapBridge.js.map +1 -1
- package/package.json +4 -4
- package/src/PreviewControls.tsx +1 -1
- package/src/Toolbar.tsx +427 -12
- package/src/__tests__/tiptapBridge.test.ts +290 -0
- package/src/styles/editor.css +229 -11
- package/src/tiptapBridge.ts +159 -0
|
@@ -0,0 +1,290 @@
|
|
|
1
|
+
import { describe, it, expect } from 'vitest';
|
|
2
|
+
import { markdownToTiptap, tiptapToMarkdown } from '../tiptapBridge';
|
|
3
|
+
|
|
4
|
+
// ---------------------------------------------------------------------------
|
|
5
|
+
// markdownToTiptap
|
|
6
|
+
// ---------------------------------------------------------------------------
|
|
7
|
+
|
|
8
|
+
describe('markdownToTiptap', () => {
|
|
9
|
+
it('returns empty paragraph for empty input', () => {
|
|
10
|
+
expect(markdownToTiptap('')).toBe('<p></p>');
|
|
11
|
+
expect(markdownToTiptap(' ')).toBe('<p></p>');
|
|
12
|
+
});
|
|
13
|
+
|
|
14
|
+
it('converts a plain paragraph', () => {
|
|
15
|
+
const html = markdownToTiptap('Hello world');
|
|
16
|
+
expect(html).toContain('<p>');
|
|
17
|
+
expect(html).toContain('Hello world');
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
it('converts headings h1-h3', () => {
|
|
21
|
+
expect(markdownToTiptap('# Title')).toContain('<h1');
|
|
22
|
+
expect(markdownToTiptap('## Subtitle')).toContain('<h2');
|
|
23
|
+
expect(markdownToTiptap('### Section')).toContain('<h3');
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
it('converts bold text', () => {
|
|
27
|
+
const html = markdownToTiptap('This is **bold** text');
|
|
28
|
+
expect(html).toContain('<strong>bold</strong>');
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
it('converts italic text', () => {
|
|
32
|
+
const html = markdownToTiptap('This is *italic* text');
|
|
33
|
+
expect(html).toContain('<em>italic</em>');
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
it('converts strikethrough text', () => {
|
|
37
|
+
const html = markdownToTiptap('This is ~~deleted~~ text');
|
|
38
|
+
expect(html).toContain('<s>deleted</s>');
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
it('converts inline code', () => {
|
|
42
|
+
const html = markdownToTiptap('Use `console.log` here');
|
|
43
|
+
expect(html).toContain('<code>console.log</code>');
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
it('converts links', () => {
|
|
47
|
+
const html = markdownToTiptap('Visit [Example](https://example.com)');
|
|
48
|
+
expect(html).toContain('href="https://example.com"');
|
|
49
|
+
expect(html).toContain('Example');
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
it('converts images', () => {
|
|
53
|
+
const html = markdownToTiptap('');
|
|
54
|
+
expect(html).toContain('alt="Logo"');
|
|
55
|
+
expect(html).toContain('src="logo.png"');
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
it('converts fenced code blocks', () => {
|
|
59
|
+
const md = '```javascript\nconst x = 1;\n```';
|
|
60
|
+
const html = markdownToTiptap(md);
|
|
61
|
+
expect(html).toContain('<pre>');
|
|
62
|
+
expect(html).toContain('<code');
|
|
63
|
+
expect(html).toContain('language-javascript');
|
|
64
|
+
expect(html).toContain('const x = 1;');
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
it('converts unordered lists', () => {
|
|
68
|
+
const md = '- Item one\n- Item two\n- Item three';
|
|
69
|
+
const html = markdownToTiptap(md);
|
|
70
|
+
expect(html).toContain('<ul>');
|
|
71
|
+
expect(html).toContain('<li>');
|
|
72
|
+
expect(html).toContain('Item one');
|
|
73
|
+
expect(html).toContain('Item two');
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
it('converts ordered lists', () => {
|
|
77
|
+
const md = '1. First\n2. Second\n3. Third';
|
|
78
|
+
const html = markdownToTiptap(md);
|
|
79
|
+
expect(html).toContain('<ol>');
|
|
80
|
+
expect(html).toContain('<li>');
|
|
81
|
+
expect(html).toContain('First');
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
it('converts blockquotes', () => {
|
|
85
|
+
const md = '> This is a quote';
|
|
86
|
+
const html = markdownToTiptap(md);
|
|
87
|
+
expect(html).toContain('<blockquote>');
|
|
88
|
+
expect(html).toContain('This is a quote');
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
it('converts horizontal rules', () => {
|
|
92
|
+
const md = 'Before\n\n---\n\nAfter';
|
|
93
|
+
const html = markdownToTiptap(md);
|
|
94
|
+
expect(html).toContain('<hr');
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
it('converts markdown tables', () => {
|
|
98
|
+
const md = '| A | B |\n| --- | --- |\n| 1 | 2 |';
|
|
99
|
+
const html = markdownToTiptap(md);
|
|
100
|
+
expect(html).toContain('<table>');
|
|
101
|
+
expect(html).toContain('<thead>');
|
|
102
|
+
expect(html).toContain('<th>');
|
|
103
|
+
expect(html).toContain('<td>');
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
it('handles table column alignment', () => {
|
|
107
|
+
const md = '| Left | Center | Right |\n| :--- | :---: | ---: |\n| a | b | c |';
|
|
108
|
+
const html = markdownToTiptap(md);
|
|
109
|
+
expect(html).toContain('text-align: left');
|
|
110
|
+
expect(html).toContain('text-align: center');
|
|
111
|
+
expect(html).toContain('text-align: right');
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
it('escapes HTML special characters in text', () => {
|
|
115
|
+
const html = markdownToTiptap('Use <div> & "quotes"');
|
|
116
|
+
expect(html).toContain('<div>');
|
|
117
|
+
expect(html).toContain('&');
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
it('normalizes \\r\\n line endings', () => {
|
|
121
|
+
const html = markdownToTiptap('Line one\r\nLine two');
|
|
122
|
+
// Should not contain raw \r
|
|
123
|
+
expect(html).not.toContain('\r');
|
|
124
|
+
});
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
// ---------------------------------------------------------------------------
|
|
128
|
+
// tiptapToMarkdown
|
|
129
|
+
// ---------------------------------------------------------------------------
|
|
130
|
+
|
|
131
|
+
describe('tiptapToMarkdown', () => {
|
|
132
|
+
it('returns empty string for empty paragraph', () => {
|
|
133
|
+
expect(tiptapToMarkdown('')).toBe('');
|
|
134
|
+
expect(tiptapToMarkdown('<p></p>')).toBe('');
|
|
135
|
+
});
|
|
136
|
+
|
|
137
|
+
it('converts a paragraph to plain text', () => {
|
|
138
|
+
const md = tiptapToMarkdown('<p>Hello world</p>');
|
|
139
|
+
expect(md).toContain('Hello world');
|
|
140
|
+
});
|
|
141
|
+
|
|
142
|
+
it('converts headings', () => {
|
|
143
|
+
expect(tiptapToMarkdown('<h1>Title</h1>')).toContain('# Title');
|
|
144
|
+
expect(tiptapToMarkdown('<h2>Sub</h2>')).toContain('## Sub');
|
|
145
|
+
expect(tiptapToMarkdown('<h3>Section</h3>')).toContain('### Section');
|
|
146
|
+
});
|
|
147
|
+
|
|
148
|
+
it('converts strong tags to bold markdown', () => {
|
|
149
|
+
const md = tiptapToMarkdown('<p>This is <strong>bold</strong> text</p>');
|
|
150
|
+
expect(md).toContain('**bold**');
|
|
151
|
+
});
|
|
152
|
+
|
|
153
|
+
it('converts em tags to italic markdown', () => {
|
|
154
|
+
const md = tiptapToMarkdown('<p>This is <em>italic</em> text</p>');
|
|
155
|
+
expect(md).toContain('*italic*');
|
|
156
|
+
});
|
|
157
|
+
|
|
158
|
+
it('converts s/del tags to strikethrough', () => {
|
|
159
|
+
const md = tiptapToMarkdown('<p>This is <s>deleted</s> text</p>');
|
|
160
|
+
expect(md).toContain('~~deleted~~');
|
|
161
|
+
});
|
|
162
|
+
|
|
163
|
+
it('converts code tags to inline code', () => {
|
|
164
|
+
const md = tiptapToMarkdown('<p>Use <code>foo</code> here</p>');
|
|
165
|
+
expect(md).toContain('`foo`');
|
|
166
|
+
});
|
|
167
|
+
|
|
168
|
+
it('converts links', () => {
|
|
169
|
+
const md = tiptapToMarkdown('<p><a href="https://example.com">Example</a></p>');
|
|
170
|
+
expect(md).toContain('[Example](https://example.com)');
|
|
171
|
+
});
|
|
172
|
+
|
|
173
|
+
it('converts code blocks with language', () => {
|
|
174
|
+
const md = tiptapToMarkdown('<pre><code class="language-js">const x = 1;</code></pre>');
|
|
175
|
+
expect(md).toContain('```js');
|
|
176
|
+
expect(md).toContain('const x = 1;');
|
|
177
|
+
expect(md).toContain('```');
|
|
178
|
+
});
|
|
179
|
+
|
|
180
|
+
it('converts code blocks without language', () => {
|
|
181
|
+
const md = tiptapToMarkdown('<pre><code>plain code</code></pre>');
|
|
182
|
+
expect(md).toContain('```');
|
|
183
|
+
expect(md).toContain('plain code');
|
|
184
|
+
});
|
|
185
|
+
|
|
186
|
+
it('converts blockquotes', () => {
|
|
187
|
+
const md = tiptapToMarkdown('<blockquote><p>A wise quote</p></blockquote>');
|
|
188
|
+
expect(md).toContain('> ');
|
|
189
|
+
expect(md).toContain('A wise quote');
|
|
190
|
+
});
|
|
191
|
+
|
|
192
|
+
it('converts horizontal rules', () => {
|
|
193
|
+
const md = tiptapToMarkdown('<p>Before</p><hr><p>After</p>');
|
|
194
|
+
expect(md).toContain('---');
|
|
195
|
+
});
|
|
196
|
+
|
|
197
|
+
it('converts unordered lists', () => {
|
|
198
|
+
const md = tiptapToMarkdown('<ul><li><p>Alpha</p></li><li><p>Beta</p></li></ul>');
|
|
199
|
+
expect(md).toContain('- Alpha');
|
|
200
|
+
expect(md).toContain('- Beta');
|
|
201
|
+
});
|
|
202
|
+
|
|
203
|
+
it('converts ordered lists', () => {
|
|
204
|
+
const md = tiptapToMarkdown('<ol><li><p>First</p></li><li><p>Second</p></li></ol>');
|
|
205
|
+
expect(md).toContain('1. First');
|
|
206
|
+
expect(md).toContain('2. Second');
|
|
207
|
+
});
|
|
208
|
+
|
|
209
|
+
it('converts tables', () => {
|
|
210
|
+
const html =
|
|
211
|
+
'<table><thead><tr><th>Name</th><th>Age</th></tr></thead>' +
|
|
212
|
+
'<tbody><tr><td>Alice</td><td>30</td></tr></tbody></table>';
|
|
213
|
+
const md = tiptapToMarkdown(html);
|
|
214
|
+
expect(md).toContain('| Name | Age |');
|
|
215
|
+
expect(md).toContain('| --- | --- |');
|
|
216
|
+
expect(md).toContain('| Alice | 30 |');
|
|
217
|
+
});
|
|
218
|
+
|
|
219
|
+
it('preserves template annotations in headings', () => {
|
|
220
|
+
const html = '<h2 data-template="statHighlight" data-template-params="stat=42%">Stats</h2>';
|
|
221
|
+
const md = tiptapToMarkdown(html);
|
|
222
|
+
expect(md).toContain('## Stats');
|
|
223
|
+
expect(md).toContain('{[statHighlight');
|
|
224
|
+
expect(md).toContain('stat=42%');
|
|
225
|
+
});
|
|
226
|
+
|
|
227
|
+
it('unescapes HTML entities', () => {
|
|
228
|
+
const md = tiptapToMarkdown('<pre><code><div> & "test"</code></pre>');
|
|
229
|
+
expect(md).toContain('<div>');
|
|
230
|
+
expect(md).toContain('&');
|
|
231
|
+
expect(md).toContain('"test"');
|
|
232
|
+
});
|
|
233
|
+
});
|
|
234
|
+
|
|
235
|
+
// ---------------------------------------------------------------------------
|
|
236
|
+
// Round-trip tests
|
|
237
|
+
// ---------------------------------------------------------------------------
|
|
238
|
+
|
|
239
|
+
describe('round-trip: markdownToTiptap → tiptapToMarkdown', () => {
|
|
240
|
+
const roundTrip = (md: string) => tiptapToMarkdown(markdownToTiptap(md));
|
|
241
|
+
|
|
242
|
+
it('preserves plain text', () => {
|
|
243
|
+
expect(roundTrip('Hello world')).toContain('Hello world');
|
|
244
|
+
});
|
|
245
|
+
|
|
246
|
+
it('preserves headings', () => {
|
|
247
|
+
const result = roundTrip('## My Heading');
|
|
248
|
+
expect(result).toContain('## My Heading');
|
|
249
|
+
});
|
|
250
|
+
|
|
251
|
+
it('preserves bold text', () => {
|
|
252
|
+
expect(roundTrip('Some **bold** here')).toContain('**bold**');
|
|
253
|
+
});
|
|
254
|
+
|
|
255
|
+
it('preserves italic text', () => {
|
|
256
|
+
expect(roundTrip('Some *italic* here')).toContain('*italic*');
|
|
257
|
+
});
|
|
258
|
+
|
|
259
|
+
it('preserves inline code', () => {
|
|
260
|
+
expect(roundTrip('Use `code` here')).toContain('`code`');
|
|
261
|
+
});
|
|
262
|
+
|
|
263
|
+
it('preserves code blocks', () => {
|
|
264
|
+
const md = '```js\nconst x = 1;\n```';
|
|
265
|
+
const result = roundTrip(md);
|
|
266
|
+
expect(result).toContain('```js');
|
|
267
|
+
expect(result).toContain('const x = 1;');
|
|
268
|
+
});
|
|
269
|
+
|
|
270
|
+
it('preserves links', () => {
|
|
271
|
+
const result = roundTrip('Click [here](https://example.com)');
|
|
272
|
+
expect(result).toContain('[here](https://example.com)');
|
|
273
|
+
});
|
|
274
|
+
|
|
275
|
+
it('preserves blockquotes', () => {
|
|
276
|
+
expect(roundTrip('> Important note')).toContain('> Important note');
|
|
277
|
+
});
|
|
278
|
+
|
|
279
|
+
it('preserves unordered lists', () => {
|
|
280
|
+
const result = roundTrip('- Alpha\n- Beta');
|
|
281
|
+
expect(result).toContain('- Alpha');
|
|
282
|
+
expect(result).toContain('- Beta');
|
|
283
|
+
});
|
|
284
|
+
|
|
285
|
+
it('preserves ordered lists', () => {
|
|
286
|
+
const result = roundTrip('1. First\n2. Second');
|
|
287
|
+
expect(result).toContain('1. First');
|
|
288
|
+
expect(result).toContain('2. Second');
|
|
289
|
+
});
|
|
290
|
+
});
|
package/src/styles/editor.css
CHANGED
|
@@ -31,6 +31,8 @@
|
|
|
31
31
|
|
|
32
32
|
.squisq-view-tab {
|
|
33
33
|
padding: 6px 16px;
|
|
34
|
+
min-width: 72px;
|
|
35
|
+
text-align: center;
|
|
34
36
|
border: none;
|
|
35
37
|
background: transparent;
|
|
36
38
|
color: #6b7280;
|
|
@@ -57,9 +59,10 @@
|
|
|
57
59
|
.squisq-toolbar {
|
|
58
60
|
display: flex;
|
|
59
61
|
align-items: center;
|
|
60
|
-
flex-wrap:
|
|
62
|
+
flex-wrap: nowrap;
|
|
61
63
|
padding: 0 12px 0 0;
|
|
62
64
|
gap: 2px;
|
|
65
|
+
background: rgba(0, 0, 0, 0.07);
|
|
63
66
|
}
|
|
64
67
|
|
|
65
68
|
/* ─── View Tabs (inside toolbar) ─────────────────────── */
|
|
@@ -67,9 +70,9 @@
|
|
|
67
70
|
.squisq-toolbar-view-tabs {
|
|
68
71
|
display: flex;
|
|
69
72
|
gap: 0;
|
|
70
|
-
margin-right:
|
|
73
|
+
margin-right: 0;
|
|
71
74
|
padding: 0 16px 0 12px;
|
|
72
|
-
background:
|
|
75
|
+
background: #ffffff;
|
|
73
76
|
border-right: 1px solid rgba(0, 0, 0, 0.12);
|
|
74
77
|
align-self: stretch;
|
|
75
78
|
align-items: center;
|
|
@@ -79,6 +82,9 @@
|
|
|
79
82
|
display: flex;
|
|
80
83
|
align-items: center;
|
|
81
84
|
gap: 2px;
|
|
85
|
+
overflow: hidden;
|
|
86
|
+
flex: 1;
|
|
87
|
+
min-width: 0;
|
|
82
88
|
}
|
|
83
89
|
|
|
84
90
|
.squisq-toolbar-view-tab {
|
|
@@ -95,6 +101,15 @@
|
|
|
95
101
|
border-color 0.15s;
|
|
96
102
|
}
|
|
97
103
|
|
|
104
|
+
.squisq-toolbar-view-tab::after {
|
|
105
|
+
content: attr(data-label);
|
|
106
|
+
display: block;
|
|
107
|
+
font-weight: 600;
|
|
108
|
+
height: 0;
|
|
109
|
+
overflow: hidden;
|
|
110
|
+
visibility: hidden;
|
|
111
|
+
}
|
|
112
|
+
|
|
98
113
|
.squisq-toolbar-view-tab:hover {
|
|
99
114
|
color: #111827;
|
|
100
115
|
}
|
|
@@ -155,6 +170,90 @@
|
|
|
155
170
|
color: #1d4ed8;
|
|
156
171
|
}
|
|
157
172
|
|
|
173
|
+
/* ─── Toolbar Overflow Menu ──────────────────────────── */
|
|
174
|
+
|
|
175
|
+
.squisq-toolbar-overflow {
|
|
176
|
+
position: relative;
|
|
177
|
+
flex-shrink: 0;
|
|
178
|
+
margin-left: 2px;
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
.squisq-toolbar-overflow-trigger {
|
|
182
|
+
font-size: 16px;
|
|
183
|
+
letter-spacing: 1px;
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
.squisq-toolbar-overflow-menu {
|
|
187
|
+
position: absolute;
|
|
188
|
+
top: 100%;
|
|
189
|
+
right: 0;
|
|
190
|
+
z-index: 100;
|
|
191
|
+
min-width: 180px;
|
|
192
|
+
padding: 4px 0;
|
|
193
|
+
margin-top: 4px;
|
|
194
|
+
background: #fff;
|
|
195
|
+
border: 1px solid #e5e7eb;
|
|
196
|
+
border-radius: 6px;
|
|
197
|
+
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.12);
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
.squisq-toolbar-overflow-item {
|
|
201
|
+
display: flex;
|
|
202
|
+
align-items: center;
|
|
203
|
+
gap: 8px;
|
|
204
|
+
width: 100%;
|
|
205
|
+
padding: 6px 12px;
|
|
206
|
+
border: none;
|
|
207
|
+
background: transparent;
|
|
208
|
+
color: #374151;
|
|
209
|
+
font-size: 13px;
|
|
210
|
+
cursor: pointer;
|
|
211
|
+
text-align: left;
|
|
212
|
+
white-space: nowrap;
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
.squisq-toolbar-overflow-item:hover {
|
|
216
|
+
background: #f3f4f6;
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
.squisq-toolbar-overflow-item:disabled {
|
|
220
|
+
opacity: 0.4;
|
|
221
|
+
cursor: default;
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
.squisq-toolbar-overflow-item--active {
|
|
225
|
+
color: #2563eb;
|
|
226
|
+
background: #eff6ff;
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
.squisq-toolbar-overflow-icon {
|
|
230
|
+
display: inline-flex;
|
|
231
|
+
align-items: center;
|
|
232
|
+
justify-content: center;
|
|
233
|
+
width: 18px;
|
|
234
|
+
font-size: 14px;
|
|
235
|
+
font-weight: 600;
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
.squisq-toolbar-overflow-item--danger {
|
|
239
|
+
color: #dc2626;
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
.squisq-toolbar-overflow-item--danger:hover {
|
|
243
|
+
background: #fef2f2;
|
|
244
|
+
color: #b91c1c;
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
.squisq-toolbar-overflow-template {
|
|
248
|
+
gap: 6px;
|
|
249
|
+
padding: 6px 12px;
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
.squisq-toolbar-overflow-template select {
|
|
253
|
+
flex: 1;
|
|
254
|
+
min-width: 0;
|
|
255
|
+
}
|
|
256
|
+
|
|
158
257
|
/* ─── Template Picker (toolbar) ──────────────────────── */
|
|
159
258
|
|
|
160
259
|
.squisq-template-picker {
|
|
@@ -351,6 +450,81 @@
|
|
|
351
450
|
font-weight: 600;
|
|
352
451
|
}
|
|
353
452
|
|
|
453
|
+
/* Tiptap table interaction — resize handles, selection, wrapper */
|
|
454
|
+
|
|
455
|
+
.squisq-wysiwyg-editor th,
|
|
456
|
+
.squisq-wysiwyg-editor td {
|
|
457
|
+
position: relative;
|
|
458
|
+
}
|
|
459
|
+
|
|
460
|
+
.squisq-wysiwyg-editor .tableWrapper {
|
|
461
|
+
overflow-x: auto;
|
|
462
|
+
margin: 1em 0;
|
|
463
|
+
}
|
|
464
|
+
|
|
465
|
+
.squisq-wysiwyg-editor .column-resize-handle {
|
|
466
|
+
position: absolute;
|
|
467
|
+
right: -2px;
|
|
468
|
+
top: 0;
|
|
469
|
+
bottom: 0;
|
|
470
|
+
width: 4px;
|
|
471
|
+
background: #3b82f6;
|
|
472
|
+
pointer-events: none;
|
|
473
|
+
}
|
|
474
|
+
|
|
475
|
+
.squisq-wysiwyg-editor.resize-cursor {
|
|
476
|
+
cursor: col-resize;
|
|
477
|
+
}
|
|
478
|
+
|
|
479
|
+
.squisq-wysiwyg-editor .selectedCell::after {
|
|
480
|
+
content: '';
|
|
481
|
+
position: absolute;
|
|
482
|
+
left: 0;
|
|
483
|
+
right: 0;
|
|
484
|
+
top: 0;
|
|
485
|
+
bottom: 0;
|
|
486
|
+
background: rgba(59, 130, 246, 0.12);
|
|
487
|
+
pointer-events: none;
|
|
488
|
+
z-index: 2;
|
|
489
|
+
}
|
|
490
|
+
|
|
491
|
+
/* ─── Table Controls (toolbar) ───────────────────────── */
|
|
492
|
+
|
|
493
|
+
.squisq-table-controls {
|
|
494
|
+
display: flex;
|
|
495
|
+
align-items: center;
|
|
496
|
+
gap: 2px;
|
|
497
|
+
}
|
|
498
|
+
|
|
499
|
+
.squisq-table-controls-label {
|
|
500
|
+
font-size: 11px;
|
|
501
|
+
color: #6b7280;
|
|
502
|
+
font-weight: 500;
|
|
503
|
+
margin-right: 4px;
|
|
504
|
+
white-space: nowrap;
|
|
505
|
+
}
|
|
506
|
+
|
|
507
|
+
.squisq-table-controls .squisq-toolbar-button {
|
|
508
|
+
display: inline-flex;
|
|
509
|
+
align-items: center;
|
|
510
|
+
justify-content: center;
|
|
511
|
+
padding: 3px 5px;
|
|
512
|
+
}
|
|
513
|
+
|
|
514
|
+
.squisq-table-controls .squisq-toolbar-button svg {
|
|
515
|
+
display: block;
|
|
516
|
+
flex-shrink: 0;
|
|
517
|
+
}
|
|
518
|
+
|
|
519
|
+
.squisq-toolbar-button--danger {
|
|
520
|
+
color: #dc2626;
|
|
521
|
+
}
|
|
522
|
+
|
|
523
|
+
.squisq-toolbar-button--danger:hover {
|
|
524
|
+
background: #fef2f2;
|
|
525
|
+
color: #b91c1c;
|
|
526
|
+
}
|
|
527
|
+
|
|
354
528
|
.squisq-wysiwyg-editor a {
|
|
355
529
|
color: #2563eb;
|
|
356
530
|
text-decoration: underline;
|
|
@@ -446,11 +620,11 @@
|
|
|
446
620
|
|
|
447
621
|
/* Toolbar */
|
|
448
622
|
.squisq-editor-shell[data-theme='dark'] .squisq-toolbar {
|
|
449
|
-
|
|
623
|
+
background: rgba(255, 255, 255, 0.08);
|
|
450
624
|
}
|
|
451
625
|
|
|
452
626
|
.squisq-editor-shell[data-theme='dark'] .squisq-toolbar-view-tabs {
|
|
453
|
-
background:
|
|
627
|
+
background: #111827;
|
|
454
628
|
border-right-color: rgba(255, 255, 255, 0.15);
|
|
455
629
|
}
|
|
456
630
|
|
|
@@ -495,6 +669,35 @@
|
|
|
495
669
|
background: #4b5563;
|
|
496
670
|
}
|
|
497
671
|
|
|
672
|
+
/* Overflow menu (dark) */
|
|
673
|
+
.squisq-editor-shell[data-theme='dark'] .squisq-toolbar-overflow-menu {
|
|
674
|
+
background: #1f2937;
|
|
675
|
+
border-color: #374151;
|
|
676
|
+
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.4);
|
|
677
|
+
}
|
|
678
|
+
|
|
679
|
+
.squisq-editor-shell[data-theme='dark'] .squisq-toolbar-overflow-item {
|
|
680
|
+
color: #d1d5db;
|
|
681
|
+
}
|
|
682
|
+
|
|
683
|
+
.squisq-editor-shell[data-theme='dark'] .squisq-toolbar-overflow-item:hover {
|
|
684
|
+
background: #374151;
|
|
685
|
+
}
|
|
686
|
+
|
|
687
|
+
.squisq-editor-shell[data-theme='dark'] .squisq-toolbar-overflow-item--active {
|
|
688
|
+
color: #60a5fa;
|
|
689
|
+
background: #1e3a5f;
|
|
690
|
+
}
|
|
691
|
+
|
|
692
|
+
.squisq-editor-shell[data-theme='dark'] .squisq-toolbar-overflow-item--danger {
|
|
693
|
+
color: #f87171;
|
|
694
|
+
}
|
|
695
|
+
|
|
696
|
+
.squisq-editor-shell[data-theme='dark'] .squisq-toolbar-overflow-item--danger:hover {
|
|
697
|
+
background: #450a0a;
|
|
698
|
+
color: #fca5a5;
|
|
699
|
+
}
|
|
700
|
+
|
|
498
701
|
/* Template picker (dark) */
|
|
499
702
|
.squisq-editor-shell[data-theme='dark'] .squisq-template-picker-label {
|
|
500
703
|
color: #9ca3af;
|
|
@@ -575,6 +778,27 @@
|
|
|
575
778
|
background: #1f2937;
|
|
576
779
|
}
|
|
577
780
|
|
|
781
|
+
.squisq-editor-shell[data-theme='dark'] .squisq-wysiwyg-editor .column-resize-handle {
|
|
782
|
+
background: #60a5fa;
|
|
783
|
+
}
|
|
784
|
+
|
|
785
|
+
.squisq-editor-shell[data-theme='dark'] .squisq-wysiwyg-editor .selectedCell::after {
|
|
786
|
+
background: rgba(96, 165, 250, 0.15);
|
|
787
|
+
}
|
|
788
|
+
|
|
789
|
+
.squisq-editor-shell[data-theme='dark'] .squisq-table-controls-label {
|
|
790
|
+
color: #9ca3af;
|
|
791
|
+
}
|
|
792
|
+
|
|
793
|
+
.squisq-editor-shell[data-theme='dark'] .squisq-toolbar-button--danger {
|
|
794
|
+
color: #f87171;
|
|
795
|
+
}
|
|
796
|
+
|
|
797
|
+
.squisq-editor-shell[data-theme='dark'] .squisq-toolbar-button--danger:hover {
|
|
798
|
+
background: #450a0a;
|
|
799
|
+
color: #fca5a5;
|
|
800
|
+
}
|
|
801
|
+
|
|
578
802
|
.squisq-editor-shell[data-theme='dark'] .squisq-wysiwyg-editor a {
|
|
579
803
|
color: #60a5fa;
|
|
580
804
|
}
|
|
@@ -629,12 +853,6 @@
|
|
|
629
853
|
.squisq-toolbar-view-tabs {
|
|
630
854
|
margin-right: 0;
|
|
631
855
|
}
|
|
632
|
-
|
|
633
|
-
.squisq-toolbar-actions {
|
|
634
|
-
flex-basis: 100%;
|
|
635
|
-
flex-wrap: wrap;
|
|
636
|
-
padding-top: 2px;
|
|
637
|
-
}
|
|
638
856
|
}
|
|
639
857
|
|
|
640
858
|
/* ─── Media Bin ──────────────────────────────────────── */
|