@bendyline/squisq-editor-react 1.1.1 → 1.2.1
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.d.ts +1 -1
- package/dist/PreviewControls.d.ts.map +1 -1
- package/dist/PreviewControls.js +36 -17
- package/dist/PreviewControls.js.map +1 -1
- package/dist/RawEditor.d.ts.map +1 -1
- package/dist/RawEditor.js +10 -1
- package/dist/RawEditor.js.map +1 -1
- package/dist/Toolbar.d.ts.map +1 -1
- package/dist/Toolbar.js +155 -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 +23 -23
- package/src/PreviewControls.tsx +116 -87
- package/src/RawEditor.tsx +10 -1
- package/src/Toolbar.tsx +444 -14
- package/src/__tests__/tiptapBridge.test.ts +290 -0
- package/src/styles/editor.css +286 -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,98 @@
|
|
|
155
170
|
color: #1d4ed8;
|
|
156
171
|
}
|
|
157
172
|
|
|
173
|
+
/* ─── Toolbar: narrow screen adjustments ───────────────── */
|
|
174
|
+
|
|
175
|
+
@media (max-width: 768px) {
|
|
176
|
+
.squisq-toolbar-view-tabs {
|
|
177
|
+
padding: 0 8px;
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
/* ─── Toolbar Overflow Menu ──────────────────────────── */
|
|
182
|
+
|
|
183
|
+
.squisq-toolbar-overflow {
|
|
184
|
+
position: relative;
|
|
185
|
+
flex-shrink: 0;
|
|
186
|
+
margin-left: 2px;
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
.squisq-toolbar-overflow-trigger {
|
|
190
|
+
font-size: 16px;
|
|
191
|
+
letter-spacing: 1px;
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
.squisq-toolbar-overflow-menu {
|
|
195
|
+
position: absolute;
|
|
196
|
+
top: 100%;
|
|
197
|
+
right: 0;
|
|
198
|
+
z-index: 100;
|
|
199
|
+
min-width: 180px;
|
|
200
|
+
padding: 4px 0;
|
|
201
|
+
margin-top: 4px;
|
|
202
|
+
background: #fff;
|
|
203
|
+
border: 1px solid #e5e7eb;
|
|
204
|
+
border-radius: 6px;
|
|
205
|
+
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.12);
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
.squisq-toolbar-overflow-item {
|
|
209
|
+
display: flex;
|
|
210
|
+
align-items: center;
|
|
211
|
+
gap: 8px;
|
|
212
|
+
width: 100%;
|
|
213
|
+
padding: 6px 12px;
|
|
214
|
+
border: none;
|
|
215
|
+
background: transparent;
|
|
216
|
+
color: #374151;
|
|
217
|
+
font-size: 13px;
|
|
218
|
+
cursor: pointer;
|
|
219
|
+
text-align: left;
|
|
220
|
+
white-space: nowrap;
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
.squisq-toolbar-overflow-item:hover {
|
|
224
|
+
background: #f3f4f6;
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
.squisq-toolbar-overflow-item:disabled {
|
|
228
|
+
opacity: 0.4;
|
|
229
|
+
cursor: default;
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
.squisq-toolbar-overflow-item--active {
|
|
233
|
+
color: #2563eb;
|
|
234
|
+
background: #eff6ff;
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
.squisq-toolbar-overflow-icon {
|
|
238
|
+
display: inline-flex;
|
|
239
|
+
align-items: center;
|
|
240
|
+
justify-content: center;
|
|
241
|
+
width: 18px;
|
|
242
|
+
font-size: 14px;
|
|
243
|
+
font-weight: 600;
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
.squisq-toolbar-overflow-item--danger {
|
|
247
|
+
color: #dc2626;
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
.squisq-toolbar-overflow-item--danger:hover {
|
|
251
|
+
background: #fef2f2;
|
|
252
|
+
color: #b91c1c;
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
.squisq-toolbar-overflow-template {
|
|
256
|
+
gap: 6px;
|
|
257
|
+
padding: 6px 12px;
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
.squisq-toolbar-overflow-template select {
|
|
261
|
+
flex: 1;
|
|
262
|
+
min-width: 0;
|
|
263
|
+
}
|
|
264
|
+
|
|
158
265
|
/* ─── Template Picker (toolbar) ──────────────────────── */
|
|
159
266
|
|
|
160
267
|
.squisq-template-picker {
|
|
@@ -351,6 +458,130 @@
|
|
|
351
458
|
font-weight: 600;
|
|
352
459
|
}
|
|
353
460
|
|
|
461
|
+
/* Tiptap table interaction — resize handles, selection, wrapper */
|
|
462
|
+
|
|
463
|
+
.squisq-wysiwyg-editor th,
|
|
464
|
+
.squisq-wysiwyg-editor td {
|
|
465
|
+
position: relative;
|
|
466
|
+
}
|
|
467
|
+
|
|
468
|
+
.squisq-wysiwyg-editor .tableWrapper {
|
|
469
|
+
overflow-x: auto;
|
|
470
|
+
margin: 1em 0;
|
|
471
|
+
}
|
|
472
|
+
|
|
473
|
+
.squisq-wysiwyg-editor .column-resize-handle {
|
|
474
|
+
position: absolute;
|
|
475
|
+
right: -2px;
|
|
476
|
+
top: 0;
|
|
477
|
+
bottom: 0;
|
|
478
|
+
width: 4px;
|
|
479
|
+
background: #3b82f6;
|
|
480
|
+
pointer-events: none;
|
|
481
|
+
}
|
|
482
|
+
|
|
483
|
+
.squisq-wysiwyg-editor.resize-cursor {
|
|
484
|
+
cursor: col-resize;
|
|
485
|
+
}
|
|
486
|
+
|
|
487
|
+
.squisq-wysiwyg-editor .selectedCell::after {
|
|
488
|
+
content: '';
|
|
489
|
+
position: absolute;
|
|
490
|
+
left: 0;
|
|
491
|
+
right: 0;
|
|
492
|
+
top: 0;
|
|
493
|
+
bottom: 0;
|
|
494
|
+
background: rgba(59, 130, 246, 0.12);
|
|
495
|
+
pointer-events: none;
|
|
496
|
+
z-index: 2;
|
|
497
|
+
}
|
|
498
|
+
|
|
499
|
+
/* ─── Table Controls (toolbar) ───────────────────────── */
|
|
500
|
+
|
|
501
|
+
.squisq-table-controls {
|
|
502
|
+
display: flex;
|
|
503
|
+
align-items: center;
|
|
504
|
+
gap: 2px;
|
|
505
|
+
}
|
|
506
|
+
|
|
507
|
+
.squisq-table-controls-label {
|
|
508
|
+
font-size: 11px;
|
|
509
|
+
color: #6b7280;
|
|
510
|
+
font-weight: 500;
|
|
511
|
+
margin-right: 4px;
|
|
512
|
+
white-space: nowrap;
|
|
513
|
+
}
|
|
514
|
+
|
|
515
|
+
.squisq-table-controls .squisq-toolbar-button {
|
|
516
|
+
display: inline-flex;
|
|
517
|
+
align-items: center;
|
|
518
|
+
justify-content: center;
|
|
519
|
+
padding: 3px 5px;
|
|
520
|
+
}
|
|
521
|
+
|
|
522
|
+
.squisq-table-controls .squisq-toolbar-button svg {
|
|
523
|
+
display: block;
|
|
524
|
+
flex-shrink: 0;
|
|
525
|
+
}
|
|
526
|
+
|
|
527
|
+
.squisq-toolbar-button--danger {
|
|
528
|
+
color: #dc2626;
|
|
529
|
+
}
|
|
530
|
+
|
|
531
|
+
.squisq-toolbar-button--danger:hover {
|
|
532
|
+
background: #fef2f2;
|
|
533
|
+
color: #b91c1c;
|
|
534
|
+
}
|
|
535
|
+
|
|
536
|
+
/* ─── Preview Controls ──────────────────────────────── */
|
|
537
|
+
|
|
538
|
+
.squisq-preview-controls-inline {
|
|
539
|
+
display: flex;
|
|
540
|
+
align-items: center;
|
|
541
|
+
gap: 6px;
|
|
542
|
+
flex-wrap: wrap;
|
|
543
|
+
padding: 2px 0 2px 9px;
|
|
544
|
+
}
|
|
545
|
+
|
|
546
|
+
.squisq-preview-controls-inline .squisq-preview-control {
|
|
547
|
+
display: flex;
|
|
548
|
+
align-items: center;
|
|
549
|
+
gap: 4px;
|
|
550
|
+
}
|
|
551
|
+
|
|
552
|
+
.squisq-preview-controls-compact {
|
|
553
|
+
position: relative;
|
|
554
|
+
}
|
|
555
|
+
|
|
556
|
+
.squisq-preview-controls-popover {
|
|
557
|
+
position: absolute;
|
|
558
|
+
top: 100%;
|
|
559
|
+
right: 0;
|
|
560
|
+
z-index: 80;
|
|
561
|
+
background: var(--squisq-bg, #fff);
|
|
562
|
+
border: 1px solid var(--squisq-border, #d1d5db);
|
|
563
|
+
border-radius: 6px;
|
|
564
|
+
box-shadow: 0 4px 16px rgba(0, 0, 0, 0.15);
|
|
565
|
+
padding: 8px 12px;
|
|
566
|
+
margin-top: 4px;
|
|
567
|
+
display: flex;
|
|
568
|
+
flex-direction: column;
|
|
569
|
+
gap: 8px;
|
|
570
|
+
min-width: 180px;
|
|
571
|
+
}
|
|
572
|
+
|
|
573
|
+
.squisq-preview-control--compact {
|
|
574
|
+
display: flex;
|
|
575
|
+
align-items: center;
|
|
576
|
+
justify-content: space-between;
|
|
577
|
+
gap: 8px;
|
|
578
|
+
}
|
|
579
|
+
|
|
580
|
+
.squisq-preview-control--compact select {
|
|
581
|
+
flex: 1;
|
|
582
|
+
min-width: 0;
|
|
583
|
+
}
|
|
584
|
+
|
|
354
585
|
.squisq-wysiwyg-editor a {
|
|
355
586
|
color: #2563eb;
|
|
356
587
|
text-decoration: underline;
|
|
@@ -446,11 +677,11 @@
|
|
|
446
677
|
|
|
447
678
|
/* Toolbar */
|
|
448
679
|
.squisq-editor-shell[data-theme='dark'] .squisq-toolbar {
|
|
449
|
-
|
|
680
|
+
background: rgba(255, 255, 255, 0.08);
|
|
450
681
|
}
|
|
451
682
|
|
|
452
683
|
.squisq-editor-shell[data-theme='dark'] .squisq-toolbar-view-tabs {
|
|
453
|
-
background:
|
|
684
|
+
background: #111827;
|
|
454
685
|
border-right-color: rgba(255, 255, 255, 0.15);
|
|
455
686
|
}
|
|
456
687
|
|
|
@@ -495,6 +726,35 @@
|
|
|
495
726
|
background: #4b5563;
|
|
496
727
|
}
|
|
497
728
|
|
|
729
|
+
/* Overflow menu (dark) */
|
|
730
|
+
.squisq-editor-shell[data-theme='dark'] .squisq-toolbar-overflow-menu {
|
|
731
|
+
background: #1f2937;
|
|
732
|
+
border-color: #374151;
|
|
733
|
+
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.4);
|
|
734
|
+
}
|
|
735
|
+
|
|
736
|
+
.squisq-editor-shell[data-theme='dark'] .squisq-toolbar-overflow-item {
|
|
737
|
+
color: #d1d5db;
|
|
738
|
+
}
|
|
739
|
+
|
|
740
|
+
.squisq-editor-shell[data-theme='dark'] .squisq-toolbar-overflow-item:hover {
|
|
741
|
+
background: #374151;
|
|
742
|
+
}
|
|
743
|
+
|
|
744
|
+
.squisq-editor-shell[data-theme='dark'] .squisq-toolbar-overflow-item--active {
|
|
745
|
+
color: #60a5fa;
|
|
746
|
+
background: #1e3a5f;
|
|
747
|
+
}
|
|
748
|
+
|
|
749
|
+
.squisq-editor-shell[data-theme='dark'] .squisq-toolbar-overflow-item--danger {
|
|
750
|
+
color: #f87171;
|
|
751
|
+
}
|
|
752
|
+
|
|
753
|
+
.squisq-editor-shell[data-theme='dark'] .squisq-toolbar-overflow-item--danger:hover {
|
|
754
|
+
background: #450a0a;
|
|
755
|
+
color: #fca5a5;
|
|
756
|
+
}
|
|
757
|
+
|
|
498
758
|
/* Template picker (dark) */
|
|
499
759
|
.squisq-editor-shell[data-theme='dark'] .squisq-template-picker-label {
|
|
500
760
|
color: #9ca3af;
|
|
@@ -575,6 +835,27 @@
|
|
|
575
835
|
background: #1f2937;
|
|
576
836
|
}
|
|
577
837
|
|
|
838
|
+
.squisq-editor-shell[data-theme='dark'] .squisq-wysiwyg-editor .column-resize-handle {
|
|
839
|
+
background: #60a5fa;
|
|
840
|
+
}
|
|
841
|
+
|
|
842
|
+
.squisq-editor-shell[data-theme='dark'] .squisq-wysiwyg-editor .selectedCell::after {
|
|
843
|
+
background: rgba(96, 165, 250, 0.15);
|
|
844
|
+
}
|
|
845
|
+
|
|
846
|
+
.squisq-editor-shell[data-theme='dark'] .squisq-table-controls-label {
|
|
847
|
+
color: #9ca3af;
|
|
848
|
+
}
|
|
849
|
+
|
|
850
|
+
.squisq-editor-shell[data-theme='dark'] .squisq-toolbar-button--danger {
|
|
851
|
+
color: #f87171;
|
|
852
|
+
}
|
|
853
|
+
|
|
854
|
+
.squisq-editor-shell[data-theme='dark'] .squisq-toolbar-button--danger:hover {
|
|
855
|
+
background: #450a0a;
|
|
856
|
+
color: #fca5a5;
|
|
857
|
+
}
|
|
858
|
+
|
|
578
859
|
.squisq-editor-shell[data-theme='dark'] .squisq-wysiwyg-editor a {
|
|
579
860
|
color: #60a5fa;
|
|
580
861
|
}
|
|
@@ -629,12 +910,6 @@
|
|
|
629
910
|
.squisq-toolbar-view-tabs {
|
|
630
911
|
margin-right: 0;
|
|
631
912
|
}
|
|
632
|
-
|
|
633
|
-
.squisq-toolbar-actions {
|
|
634
|
-
flex-basis: 100%;
|
|
635
|
-
flex-wrap: wrap;
|
|
636
|
-
padding-top: 2px;
|
|
637
|
-
}
|
|
638
913
|
}
|
|
639
914
|
|
|
640
915
|
/* ─── Media Bin ──────────────────────────────────────── */
|