@karbonjs/ui-svelte 0.2.2 → 0.2.4
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/package.json +2 -2
- package/src/editor/RichTextEditor.svelte +956 -233
- package/src/overlay/ImgBox.svelte +178 -41
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
2
|
import { onMount, tick } from 'svelte'
|
|
3
|
-
import type { MediaProvider } from '@karbonjs/ui-core'
|
|
3
|
+
import type { MediaProvider, MediaFile, EditorTheme } from '@karbonjs/ui-core'
|
|
4
4
|
|
|
5
5
|
interface Props {
|
|
6
6
|
value: string
|
|
7
7
|
placeholder?: string
|
|
8
8
|
media?: MediaProvider
|
|
9
|
+
theme?: EditorTheme
|
|
9
10
|
class?: string
|
|
10
11
|
}
|
|
11
12
|
|
|
@@ -13,6 +14,7 @@
|
|
|
13
14
|
value = $bindable(''),
|
|
14
15
|
placeholder = 'Rédigez votre contenu...',
|
|
15
16
|
media,
|
|
17
|
+
theme = 'default',
|
|
16
18
|
class: className = ''
|
|
17
19
|
}: Props = $props()
|
|
18
20
|
|
|
@@ -89,13 +91,28 @@
|
|
|
89
91
|
let elPropsTarget = $state<HTMLElement | null>(null)
|
|
90
92
|
|
|
91
93
|
// ── Media explorer state ──
|
|
92
|
-
let
|
|
94
|
+
let mediaCurrentPath = $state('')
|
|
95
|
+
let mediaEntries = $state<MediaFile[]>([])
|
|
93
96
|
let mediaLoading = $state(false)
|
|
94
|
-
let mediaSearch = $state('')
|
|
95
|
-
let mediaPage = $state(1)
|
|
96
|
-
let mediaTotal = $state(0)
|
|
97
|
-
let mediaSelected = $state<any>(null)
|
|
98
97
|
let mediaUploading = $state(false)
|
|
98
|
+
let mediaSearch = $state('')
|
|
99
|
+
let mediaSelected = $state<MediaFile | null>(null)
|
|
100
|
+
let mediaViewMode = $state<'grid' | 'list'>('grid')
|
|
101
|
+
let mediaShowNewFolder = $state(false)
|
|
102
|
+
let mediaNewFolderName = $state('')
|
|
103
|
+
let mediaDragOver = $state(false)
|
|
104
|
+
|
|
105
|
+
const mediaBreadcrumbs = $derived.by(() => {
|
|
106
|
+
const parts = mediaCurrentPath.split('/').filter(Boolean)
|
|
107
|
+
return parts.map((part, i) => ({
|
|
108
|
+
label: part,
|
|
109
|
+
path: parts.slice(0, i + 1).join('/')
|
|
110
|
+
}))
|
|
111
|
+
})
|
|
112
|
+
|
|
113
|
+
const mediaFiltered = $derived(
|
|
114
|
+
mediaSearch ? mediaEntries.filter(e => e.name.toLowerCase().includes(mediaSearch.toLowerCase())) : mediaEntries
|
|
115
|
+
)
|
|
99
116
|
|
|
100
117
|
const COLORS = [
|
|
101
118
|
'#000000', '#434343', '#666666', '#999999', '#b7b7b7', '#cccccc', '#d9d9d9', '#ffffff',
|
|
@@ -107,10 +124,10 @@
|
|
|
107
124
|
const FONT_SIZES = ['10px', '12px', '14px', '16px', '18px', '20px', '24px', '28px', '32px', '36px', '48px']
|
|
108
125
|
|
|
109
126
|
const HTML_RULES: [RegExp, string][] = [
|
|
110
|
-
[/(<!--[\s\S]*?-->)/g, '<span class="
|
|
111
|
-
[/(<\/?)([\w-]+)/g, '<span class="
|
|
112
|
-
[/([\w-]+)(=)("[^&]*"|'[^&]*')/g, '<span class="
|
|
113
|
-
[/(>)/g, '<span class="
|
|
127
|
+
[/(<!--[\s\S]*?-->)/g, '<span class="hl-comment">$1</span>'],
|
|
128
|
+
[/(<\/?)([\w-]+)/g, '<span class="hl-bracket">$1</span><span class="hl-tag">$2</span>'],
|
|
129
|
+
[/([\w-]+)(=)("[^&]*"|'[^&]*')/g, '<span class="hl-attr">$1</span><span class="hl-eq">$2</span><span class="hl-val">$3</span>'],
|
|
130
|
+
[/(>)/g, '<span class="hl-bracket">$1</span>'],
|
|
114
131
|
]
|
|
115
132
|
|
|
116
133
|
/** Escape string for safe HTML attribute insertion */
|
|
@@ -118,6 +135,17 @@
|
|
|
118
135
|
return str.replace(/&/g, '&').replace(/"/g, '"').replace(/'/g, ''').replace(/</g, '<').replace(/>/g, '>')
|
|
119
136
|
}
|
|
120
137
|
|
|
138
|
+
function isImage(entry: MediaFile): boolean {
|
|
139
|
+
return entry.mime?.startsWith('image/') ?? false
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
function formatSize(bytes: number): string {
|
|
143
|
+
if (bytes === 0) return '—'
|
|
144
|
+
if (bytes < 1024) return `${bytes} o`
|
|
145
|
+
if (bytes < 1048576) return `${(bytes / 1024).toFixed(1)} Ko`
|
|
146
|
+
return `${(bytes / 1048576).toFixed(1)} Mo`
|
|
147
|
+
}
|
|
148
|
+
|
|
121
149
|
onMount(() => {
|
|
122
150
|
if (value) editor.innerHTML = value
|
|
123
151
|
updateCounts()
|
|
@@ -269,7 +297,7 @@
|
|
|
269
297
|
if (!files?.length || !media?.upload) return
|
|
270
298
|
imageUploading = true
|
|
271
299
|
try {
|
|
272
|
-
const result = await media.upload(files[0])
|
|
300
|
+
const result = await media.upload(files[0], mediaCurrentPath)
|
|
273
301
|
imageUrl = result.url
|
|
274
302
|
imageAlt = files[0].name.replace(/\.[^/.]+$/, '')
|
|
275
303
|
} catch { /* */ } finally { imageUploading = false }
|
|
@@ -283,16 +311,31 @@
|
|
|
283
311
|
value = editor.innerHTML
|
|
284
312
|
}
|
|
285
313
|
|
|
286
|
-
function openMediaExplorerForImage() { mediaExplorerContext = 'imageModal'; showImageModal = false; showMediaExplorer = true; browseMedia() }
|
|
287
|
-
function openMediaExplorerInline() { saveSelection(); mediaExplorerContext = 'editor'; showMediaExplorer = true; browseMedia() }
|
|
314
|
+
function openMediaExplorerForImage() { mediaExplorerContext = 'imageModal'; showImageModal = false; showMediaExplorer = true; browseMedia(mediaCurrentPath) }
|
|
315
|
+
function openMediaExplorerInline() { saveSelection(); mediaExplorerContext = 'editor'; showMediaExplorer = true; browseMedia(mediaCurrentPath) }
|
|
288
316
|
|
|
289
|
-
|
|
317
|
+
// ── Media explorer ──
|
|
318
|
+
async function browseMedia(path: string) {
|
|
290
319
|
if (!media?.browse) return
|
|
291
320
|
mediaLoading = true
|
|
321
|
+
mediaSelected = null
|
|
292
322
|
try {
|
|
293
|
-
const result = await media.browse(
|
|
294
|
-
|
|
295
|
-
|
|
323
|
+
const result = await media.browse(path, mediaSearch || undefined)
|
|
324
|
+
mediaCurrentPath = result.path ?? path
|
|
325
|
+
mediaEntries = result.entries ?? []
|
|
326
|
+
} catch { mediaEntries = [] } finally { mediaLoading = false }
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
function mediaNavigateTo(path: string) { mediaCurrentPath = path; browseMedia(path) }
|
|
330
|
+
function mediaGoUp() { const parts = mediaCurrentPath.split('/').filter(Boolean); parts.pop(); mediaNavigateTo(parts.join('/')) }
|
|
331
|
+
|
|
332
|
+
function handleMediaEntryClick(entry: MediaFile) {
|
|
333
|
+
if (entry.is_dir) mediaNavigateTo(entry.path)
|
|
334
|
+
else mediaSelected = mediaSelected?.path === entry.path ? null : entry
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
function handleMediaEntryDblClick(entry: MediaFile) {
|
|
338
|
+
if (!entry.is_dir && entry.url) handleMediaSelect(entry.url)
|
|
296
339
|
}
|
|
297
340
|
|
|
298
341
|
function handleMediaSelect(url: string) {
|
|
@@ -307,6 +350,27 @@
|
|
|
307
350
|
}
|
|
308
351
|
}
|
|
309
352
|
|
|
353
|
+
async function mediaHandleUpload(files: FileList | null) {
|
|
354
|
+
if (!files?.length || !media?.upload) return
|
|
355
|
+
mediaUploading = true
|
|
356
|
+
try {
|
|
357
|
+
for (const file of files) await media.upload(file, mediaCurrentPath)
|
|
358
|
+
await browseMedia(mediaCurrentPath)
|
|
359
|
+
} catch { } finally { mediaUploading = false; mediaDragOver = false }
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
function mediaHandleDrop(e: DragEvent) { e.preventDefault(); mediaDragOver = false; mediaHandleUpload(e.dataTransfer?.files ?? null) }
|
|
363
|
+
|
|
364
|
+
async function mediaCreateFolder() {
|
|
365
|
+
if (!mediaNewFolderName.trim() || !media?.createFolder) return
|
|
366
|
+
try {
|
|
367
|
+
const path = mediaCurrentPath ? `${mediaCurrentPath}/${mediaNewFolderName}` : mediaNewFolderName
|
|
368
|
+
await media.createFolder(path)
|
|
369
|
+
mediaShowNewFolder = false; mediaNewFolderName = ''
|
|
370
|
+
await browseMedia(mediaCurrentPath)
|
|
371
|
+
} catch { }
|
|
372
|
+
}
|
|
373
|
+
|
|
310
374
|
// ── Table ──
|
|
311
375
|
function openTableModal() { saveSelection(); tableRows = 3; tableCols = 3; showTableModal = true }
|
|
312
376
|
|
|
@@ -353,10 +417,10 @@
|
|
|
353
417
|
if (!findText) return
|
|
354
418
|
const sel = window.getSelection(); const range = document.createRange()
|
|
355
419
|
const walker = document.createTreeWalker(editor, NodeFilter.SHOW_TEXT)
|
|
356
|
-
let node: Node | null
|
|
420
|
+
let node: Node | null
|
|
357
421
|
while ((node = walker.nextNode())) {
|
|
358
422
|
const idx = (node.textContent ?? '').toLowerCase().indexOf(findText.toLowerCase())
|
|
359
|
-
if (idx >= 0) { range.setStart(node, idx); range.setEnd(node, idx + findText.length); sel?.removeAllRanges(); sel?.addRange(range);
|
|
423
|
+
if (idx >= 0) { range.setStart(node, idx); range.setEnd(node, idx + findText.length); sel?.removeAllRanges(); sel?.addRange(range); break }
|
|
360
424
|
}
|
|
361
425
|
}
|
|
362
426
|
|
|
@@ -420,124 +484,137 @@
|
|
|
420
484
|
function handleSourceInput() { value = sourceCode; updateLineNumbers() }
|
|
421
485
|
function handleSourceScroll() { if (lineNumbers && sourceEl) lineNumbers.scrollTop = sourceEl.scrollTop }
|
|
422
486
|
function insertColor(color: string) { exec('foreColor', color); showColorPicker = false }
|
|
423
|
-
function isActive(cmd: string): string { return activeFormats.has(cmd) ? '
|
|
424
|
-
|
|
425
|
-
const btnClass = 'flex items-center justify-center min-w-[32px] h-8 rounded-md transition-all cursor-pointer border-none bg-transparent px-1'
|
|
426
|
-
const sepClass = 'w-px h-[22px] bg-[var(--karbon-border)] mx-0.5 shrink-0'
|
|
427
|
-
const modalOverlay = 'fixed inset-0 z-50 flex items-center justify-center bg-black/50 backdrop-blur-sm'
|
|
428
|
-
const modalBox = 'w-full rounded-xl border border-[var(--karbon-border)] bg-[var(--karbon-bg-card)] p-6 shadow-2xl'
|
|
429
|
-
const modalInput = 'block w-full rounded-lg border border-[var(--karbon-border-input)] bg-[var(--karbon-bg-input)] text-[var(--karbon-text)] px-3 py-2 text-sm outline-none focus:border-[var(--karbon-border-input-focus)] transition-colors'
|
|
430
|
-
const modalLabel = 'block text-xs font-medium text-[var(--karbon-text-4)]'
|
|
431
|
-
const modalBtnCancel = 'rounded-lg border border-[var(--karbon-border)] px-3 py-1.5 text-xs text-[var(--karbon-text-3)] hover:bg-[var(--karbon-bg-2)]'
|
|
432
|
-
const modalBtnPrimary = 'rounded-lg bg-violet-600 px-3 py-1.5 text-xs font-medium text-white hover:bg-violet-700 disabled:opacity-40'
|
|
487
|
+
function isActive(cmd: string): string { return activeFormats.has(cmd) ? 'rte-btn rte-btn-active' : 'rte-btn' }
|
|
433
488
|
</script>
|
|
434
|
-
|
|
489
|
+
|
|
490
|
+
<!-- svelte-ignore a11y_no_static_element_interactions -->
|
|
491
|
+
<div role="toolbar" aria-label="Éditeur de texte riche" class="rte-wrapper {fullscreen ? 'fixed inset-0 z-40 rounded-none flex flex-col' : ''} {className}">
|
|
435
492
|
|
|
436
493
|
<!-- ═══ TOOLBAR ═══ -->
|
|
437
|
-
<div class="
|
|
438
|
-
<button type="button" onclick={() => exec('undo')} class="
|
|
494
|
+
<div class="rte-toolbar">
|
|
495
|
+
<button type="button" onclick={() => exec('undo')} class="rte-btn" title="Annuler (Ctrl+Z)" aria-label="Annuler">
|
|
439
496
|
<svg xmlns="http://www.w3.org/2000/svg" width="15" height="15" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M3 7v6h6"/><path d="M21 17a9 9 0 0 0-9-9 9 9 0 0 0-6 2.3L3 13"/></svg>
|
|
440
497
|
</button>
|
|
441
|
-
<button type="button" onclick={() => exec('redo')} class="
|
|
498
|
+
<button type="button" onclick={() => exec('redo')} class="rte-btn" title="Rétablir" aria-label="Rétablir">
|
|
442
499
|
<svg xmlns="http://www.w3.org/2000/svg" width="15" height="15" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M21 7v6h-6"/><path d="M3 17a9 9 0 0 1 9-9 9 9 0 0 1 6 2.3L21 13"/></svg>
|
|
443
500
|
</button>
|
|
444
|
-
<span class=
|
|
501
|
+
<span class="rte-sep"></span>
|
|
445
502
|
|
|
446
|
-
<button type="button" onclick={() => exec('formatBlock', 'p')} class=
|
|
503
|
+
<button type="button" onclick={() => exec('formatBlock', 'p')} class={isActive('p')} title="Paragraphe" aria-label="Paragraphe">
|
|
447
504
|
<svg xmlns="http://www.w3.org/2000/svg" width="15" height="15" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M13 4v16"/><path d="M17 4v16"/><path d="M19 4H9.5a4.5 4.5 0 0 0 0 9H13"/></svg>
|
|
448
505
|
</button>
|
|
449
|
-
<button type="button" onclick={() => exec('formatBlock', 'h2')} class="{
|
|
450
|
-
<button type="button" onclick={() => exec('formatBlock', 'h3')} class="{
|
|
451
|
-
<button type="button" onclick={() => exec('formatBlock', 'h4')} class="{
|
|
452
|
-
<span class=
|
|
506
|
+
<button type="button" onclick={() => exec('formatBlock', 'h2')} class="{isActive('h2')} text-xs font-bold" title="Titre 2">H2</button>
|
|
507
|
+
<button type="button" onclick={() => exec('formatBlock', 'h3')} class="{isActive('h3')} text-xs font-bold" title="Titre 3">H3</button>
|
|
508
|
+
<button type="button" onclick={() => exec('formatBlock', 'h4')} class="{isActive('h4')} text-xs font-bold" title="Titre 4">H4</button>
|
|
509
|
+
<span class="rte-sep"></span>
|
|
453
510
|
|
|
454
|
-
<select onchange={(e) => setFontSize(e.currentTarget.value)} class="
|
|
511
|
+
<select onchange={(e) => setFontSize(e.currentTarget.value)} class="rte-select" title="Taille">
|
|
455
512
|
<option value="">Taille</option>
|
|
456
513
|
{#each FONT_SIZES as size}<option value={size}>{size}</option>{/each}
|
|
457
514
|
</select>
|
|
458
|
-
<span class=
|
|
515
|
+
<span class="rte-sep"></span>
|
|
459
516
|
|
|
460
|
-
<button type="button" onclick={() => exec('bold')} class=
|
|
517
|
+
<button type="button" onclick={() => exec('bold')} class={isActive('bold')} title="Gras (Ctrl+B)" aria-label="Gras">
|
|
461
518
|
<svg xmlns="http://www.w3.org/2000/svg" width="15" height="15" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M6 12h9a4 4 0 0 1 0 8H7a1 1 0 0 1-1-1V5a1 1 0 0 1 1-1h7a4 4 0 0 1 0 8"/></svg>
|
|
462
519
|
</button>
|
|
463
|
-
<button type="button" onclick={() => exec('italic')} class=
|
|
520
|
+
<button type="button" onclick={() => exec('italic')} class={isActive('italic')} title="Italique (Ctrl+I)" aria-label="Italique">
|
|
464
521
|
<svg xmlns="http://www.w3.org/2000/svg" width="15" height="15" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><line x1="19" x2="10" y1="4" y2="4"/><line x1="14" x2="5" y1="20" y2="20"/><line x1="15" x2="9" y1="4" y2="20"/></svg>
|
|
465
522
|
</button>
|
|
466
|
-
<button type="button" onclick={() => exec('underline')} class=
|
|
523
|
+
<button type="button" onclick={() => exec('underline')} class={isActive('underline')} title="Souligné (Ctrl+U)" aria-label="Souligné">
|
|
467
524
|
<svg xmlns="http://www.w3.org/2000/svg" width="15" height="15" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M6 4v6a6 6 0 0 0 12 0V4"/><line x1="4" x2="20" y1="20" y2="20"/></svg>
|
|
468
525
|
</button>
|
|
469
|
-
<button type="button" onclick={() => exec('strikeThrough')} class=
|
|
526
|
+
<button type="button" onclick={() => exec('strikeThrough')} class={isActive('strikeThrough')} title="Barré" aria-label="Barré">
|
|
470
527
|
<svg xmlns="http://www.w3.org/2000/svg" width="15" height="15" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M16 4H9a3 3 0 0 0-2.83 4"/><path d="M14 12a4 4 0 0 1 0 8H6"/><line x1="4" x2="20" y1="12" y2="12"/></svg>
|
|
471
528
|
</button>
|
|
472
|
-
<
|
|
529
|
+
<button type="button" onclick={() => exec('subscript')} class={isActive('subscript')} title="Indice" aria-label="Indice">
|
|
530
|
+
<svg xmlns="http://www.w3.org/2000/svg" width="15" height="15" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="m4 5 8 8"/><path d="m12 5-8 8"/><path d="M20 19h-4c0-1.5.44-2 1.5-2.5S20 15.33 20 14c0-.47-.17-.93-.48-1.29a2.11 2.11 0 0 0-2.62-.44c-.42.24-.74.62-.9 1.07"/></svg>
|
|
531
|
+
</button>
|
|
532
|
+
<button type="button" onclick={() => exec('superscript')} class={isActive('superscript')} title="Exposant" aria-label="Exposant">
|
|
533
|
+
<svg xmlns="http://www.w3.org/2000/svg" width="15" height="15" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="m4 19 8-8"/><path d="m12 19-8-8"/><path d="M20 9h-4c0-1.5.44-2 1.5-2.5S20 5.33 20 4c0-.47-.17-.93-.48-1.29a2.11 2.11 0 0 0-2.62-.44c-.42.24-.74.62-.9 1.07"/></svg>
|
|
534
|
+
</button>
|
|
535
|
+
<span class="rte-sep"></span>
|
|
473
536
|
|
|
537
|
+
<!-- Color -->
|
|
474
538
|
<div class="relative">
|
|
475
|
-
<button type="button" onclick={() => showColorPicker = !showColorPicker} class="
|
|
539
|
+
<button type="button" onclick={() => showColorPicker = !showColorPicker} class="rte-btn" title="Couleur" aria-label="Couleur du texte">
|
|
476
540
|
<svg xmlns="http://www.w3.org/2000/svg" width="15" height="15" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="m19 11-8-8-8.6 8.6a2 2 0 0 0 0 2.8l5.2 5.2c.8.8 2 .8 2.8 0L19 11Z"/><path d="m5 2 5 5"/><path d="M2 13h15"/><path d="M22 20a2 2 0 1 1-4 0c0-1.6 1.7-2.4 2-4 .3 1.6 2 2.4 2 4Z"/></svg>
|
|
477
541
|
</button>
|
|
478
542
|
{#if showColorPicker}
|
|
479
|
-
<div class="
|
|
480
|
-
{#each COLORS as color}<button type="button" onclick={() => insertColor(color)} class="
|
|
543
|
+
<div class="rte-color-grid">
|
|
544
|
+
{#each COLORS as color}<button type="button" onclick={() => insertColor(color)} class="rte-color-swatch" style="background-color: {color}" aria-label="Couleur {color}"></button>{/each}
|
|
481
545
|
</div>
|
|
482
546
|
{/if}
|
|
483
547
|
</div>
|
|
484
|
-
<button type="button" onclick={() => exec('removeFormat')} class="
|
|
548
|
+
<button type="button" onclick={() => exec('removeFormat')} class="rte-btn" title="Effacer formatage" aria-label="Effacer le formatage">
|
|
485
549
|
<svg xmlns="http://www.w3.org/2000/svg" width="15" height="15" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M7 21a4.6 4.6 0 0 1 0-9h10a4.6 4.6 0 1 1 0 9H7Z"/><path d="m3 3 18 18"/></svg>
|
|
486
550
|
</button>
|
|
487
|
-
<span class=
|
|
551
|
+
<span class="rte-sep"></span>
|
|
488
552
|
|
|
489
|
-
|
|
553
|
+
<!-- Alignment -->
|
|
554
|
+
<button type="button" onclick={() => exec('justifyLeft')} class={isActive('justifyLeft')} title="Gauche" aria-label="Aligner à gauche">
|
|
490
555
|
<svg xmlns="http://www.w3.org/2000/svg" width="15" height="15" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><line x1="21" x2="3" y1="6" y2="6"/><line x1="15" x2="3" y1="12" y2="12"/><line x1="17" x2="3" y1="18" y2="18"/></svg>
|
|
491
556
|
</button>
|
|
492
|
-
<button type="button" onclick={() => exec('justifyCenter')} class=
|
|
557
|
+
<button type="button" onclick={() => exec('justifyCenter')} class={isActive('justifyCenter')} title="Centre" aria-label="Centrer">
|
|
493
558
|
<svg xmlns="http://www.w3.org/2000/svg" width="15" height="15" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><line x1="21" x2="3" y1="6" y2="6"/><line x1="17" x2="7" y1="12" y2="12"/><line x1="19" x2="5" y1="18" y2="18"/></svg>
|
|
494
559
|
</button>
|
|
495
|
-
<button type="button" onclick={() => exec('justifyRight')} class=
|
|
560
|
+
<button type="button" onclick={() => exec('justifyRight')} class={isActive('justifyRight')} title="Droite" aria-label="Aligner à droite">
|
|
496
561
|
<svg xmlns="http://www.w3.org/2000/svg" width="15" height="15" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><line x1="21" x2="3" y1="6" y2="6"/><line x1="21" x2="9" y1="12" y2="12"/><line x1="21" x2="7" y1="18" y2="18"/></svg>
|
|
497
562
|
</button>
|
|
498
|
-
<
|
|
563
|
+
<button type="button" onclick={() => exec('justifyFull')} class={isActive('justifyFull')} title="Justifier" aria-label="Justifier">
|
|
564
|
+
<svg xmlns="http://www.w3.org/2000/svg" width="15" height="15" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><line x1="21" x2="3" y1="6" y2="6"/><line x1="21" x2="3" y1="12" y2="12"/><line x1="21" x2="3" y1="18" y2="18"/></svg>
|
|
565
|
+
</button>
|
|
566
|
+
<span class="rte-sep"></span>
|
|
499
567
|
|
|
500
|
-
|
|
568
|
+
<!-- Lists & blocks -->
|
|
569
|
+
<button type="button" onclick={() => exec('insertUnorderedList')} class={isActive('insertunorderedlist')} title="Puces" aria-label="Liste à puces">
|
|
501
570
|
<svg xmlns="http://www.w3.org/2000/svg" width="15" height="15" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><line x1="8" x2="21" y1="6" y2="6"/><line x1="8" x2="21" y1="12" y2="12"/><line x1="8" x2="21" y1="18" y2="18"/><line x1="3" x2="3.01" y1="6" y2="6"/><line x1="3" x2="3.01" y1="12" y2="12"/><line x1="3" x2="3.01" y1="18" y2="18"/></svg>
|
|
502
571
|
</button>
|
|
503
|
-
<button type="button" onclick={() => exec('insertOrderedList')} class=
|
|
572
|
+
<button type="button" onclick={() => exec('insertOrderedList')} class={isActive('insertorderedlist')} title="Numéros" aria-label="Liste numérotée">
|
|
504
573
|
<svg xmlns="http://www.w3.org/2000/svg" width="15" height="15" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><line x1="10" x2="21" y1="6" y2="6"/><line x1="10" x2="21" y1="12" y2="12"/><line x1="10" x2="21" y1="18" y2="18"/><path d="M4 6h1v4"/><path d="M4 10h2"/><path d="M6 18H4c0-1 2-2 2-3s-1-1.5-2-1"/></svg>
|
|
505
574
|
</button>
|
|
506
|
-
<button type="button" onclick={() => exec('
|
|
575
|
+
<button type="button" onclick={() => exec('indent')} class="rte-btn" title="Indenter" aria-label="Indenter">
|
|
576
|
+
<svg xmlns="http://www.w3.org/2000/svg" width="15" height="15" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polyline points="3 8 7 12 3 16"/><line x1="21" x2="11" y1="12" y2="12"/><line x1="21" x2="11" y1="6" y2="6"/><line x1="21" x2="11" y1="18" y2="18"/></svg>
|
|
577
|
+
</button>
|
|
578
|
+
<button type="button" onclick={() => exec('outdent')} class="rte-btn" title="Désindenter" aria-label="Désindenter">
|
|
579
|
+
<svg xmlns="http://www.w3.org/2000/svg" width="15" height="15" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polyline points="7 8 3 12 7 16"/><line x1="21" x2="11" y1="12" y2="12"/><line x1="21" x2="11" y1="6" y2="6"/><line x1="21" x2="11" y1="18" y2="18"/></svg>
|
|
580
|
+
</button>
|
|
581
|
+
<button type="button" onclick={() => exec('formatBlock', 'blockquote')} class={isActive('blockquote')} title="Citation" aria-label="Citation">
|
|
507
582
|
<svg xmlns="http://www.w3.org/2000/svg" width="15" height="15" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M3 21c3 0 7-1 7-8V5c0-1.25-.756-2.017-2-2H4c-1.25 0-2 .75-2 1.972V11c0 1.25.75 2 2 2 1 0 1 0 1 1v1c0 1-1 2-2 2s-1 .008-1 1.031V21z"/><path d="M15 21c3 0 7-1 7-8V5c0-1.25-.757-2.017-2-2h-4c-1.25 0-2 .75-2 1.972V11c0 1.25.75 2 2 2h.75c0 2.25.25 4-2.75 4v3z"/></svg>
|
|
508
583
|
</button>
|
|
509
|
-
<button type="button" onclick={() => exec('
|
|
584
|
+
<button type="button" onclick={() => exec('formatBlock', 'pre')} class="rte-btn" title="Bloc de code" aria-label="Bloc de code"><code class="text-[11px]"></></code></button>
|
|
585
|
+
<button type="button" onclick={() => exec('insertHTML', '<hr />')} class="rte-btn" title="Séparateur" aria-label="Séparateur">
|
|
510
586
|
<svg xmlns="http://www.w3.org/2000/svg" width="15" height="15" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M5 12h14"/></svg>
|
|
511
587
|
</button>
|
|
512
|
-
<span class=
|
|
588
|
+
<span class="rte-sep"></span>
|
|
513
589
|
|
|
514
|
-
|
|
590
|
+
<!-- Insert -->
|
|
591
|
+
<button type="button" onclick={openLinkModal} class="rte-btn" title="Lien (Ctrl+K)" aria-label="Insérer un lien">
|
|
515
592
|
<svg xmlns="http://www.w3.org/2000/svg" width="15" height="15" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71"/><path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71"/></svg>
|
|
516
593
|
</button>
|
|
517
|
-
<button type="button" onclick={openImageModal} class="
|
|
594
|
+
<button type="button" onclick={openImageModal} class="rte-btn" title="Image" aria-label="Insérer une image">
|
|
518
595
|
<svg xmlns="http://www.w3.org/2000/svg" width="15" height="15" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect width="18" height="18" x="3" y="3" rx="2" ry="2"/><circle cx="9" cy="9" r="2"/><path d="m21 15-3.086-3.086a2 2 0 0 0-2.828 0L6 21"/></svg>
|
|
519
596
|
</button>
|
|
520
597
|
{#if media}
|
|
521
|
-
<button type="button" onclick={openMediaExplorerInline} class="
|
|
598
|
+
<button type="button" onclick={openMediaExplorerInline} class="rte-btn" title="Médias" aria-label="Explorer les médias">
|
|
522
599
|
<svg xmlns="http://www.w3.org/2000/svg" width="15" height="15" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="m6 14 1.5-2.9A2 2 0 0 1 9.24 10H20a2 2 0 0 1 1.94 2.5l-1.54 6a2 2 0 0 1-1.95 1.5H4a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h3.9a2 2 0 0 1 1.69.9l.81 1.2a2 2 0 0 0 1.67.9H18a2 2 0 0 1 2 2v2"/></svg>
|
|
523
600
|
</button>
|
|
524
601
|
{/if}
|
|
525
|
-
<button type="button" onclick={openTableModal} class="
|
|
602
|
+
<button type="button" onclick={openTableModal} class="rte-btn" title="Tableau" aria-label="Insérer un tableau">
|
|
526
603
|
<svg xmlns="http://www.w3.org/2000/svg" width="15" height="15" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M12 3v18"/><rect width="18" height="18" x="3" y="3" rx="2"/><path d="M3 9h18"/><path d="M3 15h18"/></svg>
|
|
527
604
|
</button>
|
|
528
|
-
<button type="button" onclick={openEmbedModal} class="
|
|
605
|
+
<button type="button" onclick={openEmbedModal} class="rte-btn" title="Vidéo / Embed" aria-label="Insérer une vidéo">
|
|
529
606
|
<svg xmlns="http://www.w3.org/2000/svg" width="15" height="15" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M2.5 17a24.12 24.12 0 0 1 0-10 2 2 0 0 1 1.4-1.4 49.56 49.56 0 0 1 16.2 0A2 2 0 0 1 21.5 7a24.12 24.12 0 0 1 0 10 2 2 0 0 1-1.4 1.4 49.55 49.55 0 0 1-16.2 0A2 2 0 0 1 2.5 17"/><path d="m10 15 5-3-5-3z"/></svg>
|
|
530
607
|
</button>
|
|
531
608
|
|
|
532
609
|
<div class="flex-1"></div>
|
|
533
610
|
|
|
534
|
-
<button type="button" onclick={() => showFindReplace = !showFindReplace} class="{
|
|
611
|
+
<button type="button" onclick={() => showFindReplace = !showFindReplace} class="{showFindReplace ? 'rte-btn rte-btn-active' : 'rte-btn'}" title="Rechercher (Ctrl+H)" aria-label="Rechercher et remplacer">
|
|
535
612
|
<svg xmlns="http://www.w3.org/2000/svg" width="15" height="15" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="11" cy="11" r="8"/><path d="m21 21-4.3-4.3"/></svg>
|
|
536
613
|
</button>
|
|
537
|
-
<button type="button" onclick={toggleSource} class="{
|
|
614
|
+
<button type="button" onclick={toggleSource} class="{sourceMode ? 'rte-btn rte-btn-active' : 'rte-btn'}" title="Source" aria-label="Mode source HTML">
|
|
538
615
|
<svg xmlns="http://www.w3.org/2000/svg" width="15" height="15" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M15 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V7Z"/><path d="M14 2v4a2 2 0 0 0 2 2h4"/><path d="m10 13-2 2 2 2"/><path d="m14 17 2-2-2-2"/></svg>
|
|
539
616
|
</button>
|
|
540
|
-
<button type="button" onclick={() => fullscreen = !fullscreen} class="
|
|
617
|
+
<button type="button" onclick={() => fullscreen = !fullscreen} class="rte-btn" title="Plein écran" aria-label="Plein écran">
|
|
541
618
|
{#if fullscreen}
|
|
542
619
|
<svg xmlns="http://www.w3.org/2000/svg" width="15" height="15" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polyline points="4 14 10 14 10 20"/><polyline points="20 10 14 10 14 4"/><line x1="14" x2="21" y1="10" y2="3"/><line x1="3" x2="10" y1="21" y2="14"/></svg>
|
|
543
620
|
{:else}
|
|
@@ -548,13 +625,13 @@
|
|
|
548
625
|
|
|
549
626
|
<!-- ═══ FIND/REPLACE BAR ═══ -->
|
|
550
627
|
{#if showFindReplace}
|
|
551
|
-
<div class="
|
|
552
|
-
<input type="text" bind:value={findText} placeholder="Rechercher..." class="
|
|
553
|
-
<input type="text" bind:value={replaceText} placeholder="Remplacer..." class="
|
|
554
|
-
<button type="button" onclick={findNext} class="
|
|
555
|
-
<button type="button" onclick={replaceNext} class="
|
|
556
|
-
<button type="button" onclick={replaceAll} class="
|
|
557
|
-
<button type="button" onclick={() => showFindReplace = false} class="
|
|
628
|
+
<div class="rte-findbar">
|
|
629
|
+
<input type="text" bind:value={findText} placeholder="Rechercher..." class="rte-bar-input w-40" onkeydown={(e) => { if (e.key === 'Enter') findNext() }} />
|
|
630
|
+
<input type="text" bind:value={replaceText} placeholder="Remplacer..." class="rte-bar-input w-40" />
|
|
631
|
+
<button type="button" onclick={findNext} class="rte-bar-btn">Suivant</button>
|
|
632
|
+
<button type="button" onclick={replaceNext} class="rte-bar-btn">Remplacer</button>
|
|
633
|
+
<button type="button" onclick={replaceAll} class="rte-bar-btn">Tout</button>
|
|
634
|
+
<button type="button" onclick={() => showFindReplace = false} class="rte-btn" aria-label="Fermer">
|
|
558
635
|
<svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M18 6 6 18"/><path d="m6 6 12 12"/></svg>
|
|
559
636
|
</button>
|
|
560
637
|
</div>
|
|
@@ -562,33 +639,18 @@
|
|
|
562
639
|
|
|
563
640
|
<!-- ═══ EDITOR / SOURCE ═══ -->
|
|
564
641
|
{#if sourceMode}
|
|
565
|
-
<div class="
|
|
566
|
-
<div bind:this={lineNumbers} class="
|
|
567
|
-
<div class="
|
|
568
|
-
<textarea bind:this={sourceEl} bind:value={sourceCode} oninput={handleSourceInput} onscroll={handleSourceScroll} class="
|
|
642
|
+
<div class="rte-source-wrapper {fullscreen ? 'flex-1' : ''}" style="{fullscreen ? '' : 'height: 500px;'}">
|
|
643
|
+
<div bind:this={lineNumbers} class="rte-line-numbers"></div>
|
|
644
|
+
<div class="rte-highlight-layer" aria-hidden="true">{@html highlightHtml(sourceCode)}</div>
|
|
645
|
+
<textarea bind:this={sourceEl} bind:value={sourceCode} oninput={handleSourceInput} onscroll={handleSourceScroll} class="rte-source-textarea" spellcheck="false" wrap="off"></textarea>
|
|
569
646
|
</div>
|
|
570
647
|
{:else}
|
|
648
|
+
<!-- svelte-ignore a11y_interactive_supports_focus -->
|
|
571
649
|
<div
|
|
572
650
|
bind:this={editor} contenteditable="true"
|
|
573
|
-
class="outline-none
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
[&_h3]:text-[1.25em] [&_h3]:font-semibold [&_h3]:mt-3 [&_h3]:mb-1.5
|
|
577
|
-
[&_h4]:text-[1.1em] [&_h4]:font-semibold [&_h4]:mt-2.5 [&_h4]:mb-1
|
|
578
|
-
[&_p]:my-2 [&_a]:text-violet-500 [&_a]:underline
|
|
579
|
-
[&_blockquote]:border-l-[3px] [&_blockquote]:border-violet-500 [&_blockquote]:pl-4 [&_blockquote]:py-2 [&_blockquote]:my-4 [&_blockquote]:bg-violet-500/5 [&_blockquote]:rounded-r-lg [&_blockquote]:italic
|
|
580
|
-
[&_pre]:bg-[var(--karbon-bg-2)] [&_pre]:border [&_pre]:border-[var(--karbon-border)] [&_pre]:rounded-lg [&_pre]:p-4 [&_pre]:font-mono [&_pre]:text-[0.875em] [&_pre]:overflow-x-auto [&_pre]:my-4
|
|
581
|
-
[&_code]:bg-[var(--karbon-bg-2)] [&_code]:px-1.5 [&_code]:py-0.5 [&_code]:rounded [&_code]:text-[0.875em]
|
|
582
|
-
[&_img]:max-w-full [&_img]:h-auto [&_img]:rounded-lg [&_img]:my-4 [&_img]:cursor-pointer [&_img:hover]:outline [&_img:hover]:outline-2 [&_img:hover]:outline-violet-500/50 [&_img:hover]:outline-offset-2
|
|
583
|
-
[&_hr]:border-none [&_hr]:border-t [&_hr]:border-[var(--karbon-border)] [&_hr]:my-6
|
|
584
|
-
[&_ul]:pl-6 [&_ul]:my-2 [&_ol]:pl-6 [&_ol]:my-2 [&_li]:my-1
|
|
585
|
-
[&_table]:w-full [&_table]:border-collapse [&_table]:my-4
|
|
586
|
-
[&_th]:border [&_th]:border-[var(--karbon-border)] [&_th]:px-3 [&_th]:py-2 [&_th]:bg-[var(--karbon-bg-2)] [&_th]:font-semibold [&_th]:text-[0.875em]
|
|
587
|
-
[&_td]:border [&_td]:border-[var(--karbon-border)] [&_td]:px-3 [&_td]:py-2
|
|
588
|
-
[&_.embed-responsive]:relative [&_.embed-responsive]:pb-[56.25%] [&_.embed-responsive]:h-0 [&_.embed-responsive]:overflow-hidden [&_.embed-responsive]:my-4 [&_.embed-responsive]:rounded-lg
|
|
589
|
-
[&_.embed-responsive_iframe]:absolute [&_.embed-responsive_iframe]:inset-0 [&_.embed-responsive_iframe]:w-full [&_.embed-responsive_iframe]:h-full [&_.embed-responsive_iframe]:border-0
|
|
590
|
-
{fullscreen ? 'flex-1 overflow-y-auto' : 'min-h-[500px] max-h-[800px] overflow-y-auto'}"
|
|
591
|
-
data-placeholder={placeholder} role="textbox" tabindex="0" aria-multiline="true"
|
|
651
|
+
class="rte-content rte-{theme} outline-none {fullscreen ? 'flex-1 overflow-y-auto' : ''}"
|
|
652
|
+
style="{fullscreen ? '' : 'min-height: 500px; max-height: 800px; overflow-y: auto;'}"
|
|
653
|
+
data-placeholder={placeholder} role="textbox" aria-multiline="true"
|
|
592
654
|
oninput={handleInput} onkeydown={handleKeydown} onkeyup={updateActiveFormats}
|
|
593
655
|
onmouseup={updateActiveFormats} onpaste={handlePaste}
|
|
594
656
|
oncontextmenu={handleContextMenu} ondblclick={handleDblClick}
|
|
@@ -596,7 +658,7 @@
|
|
|
596
658
|
{/if}
|
|
597
659
|
|
|
598
660
|
<!-- ═══ STATUS BAR ═══ -->
|
|
599
|
-
<div class="
|
|
661
|
+
<div class="rte-statusbar">
|
|
600
662
|
<span>{wordCount} mot{wordCount !== 1 ? 's' : ''}</span>
|
|
601
663
|
<span>{charCount} caractère{charCount !== 1 ? 's' : ''}</span>
|
|
602
664
|
{#if sourceMode}<span class="text-violet-400">Mode source</span>{/if}
|
|
@@ -607,71 +669,72 @@
|
|
|
607
669
|
|
|
608
670
|
<!-- ═══ CONTEXT MENU ═══ -->
|
|
609
671
|
{#if showContextMenu}
|
|
672
|
+
<!-- svelte-ignore a11y_no_static_element_interactions -->
|
|
610
673
|
<div class="fixed inset-0 z-[60]" role="presentation" onclick={() => showContextMenu = false} onkeydown={(e) => { if (e.key === "Escape") showContextMenu = false }}></div>
|
|
611
|
-
<div class="
|
|
612
|
-
<div class="
|
|
613
|
-
<span
|
|
614
|
-
{contextType === 'image' ? 'Image' : contextType === 'link' ? 'Lien' : contextType === 'table' ? 'Tableau' : 'Élément'}
|
|
615
|
-
</span>
|
|
674
|
+
<div class="ctx-menu" style="left: {contextPos.x}px; top: {contextPos.y}px;">
|
|
675
|
+
<div class="ctx-header">
|
|
676
|
+
<span>{contextType === 'image' ? 'Image' : contextType === 'link' ? 'Lien' : contextType === 'table' ? 'Tableau' : 'Élément'}</span>
|
|
616
677
|
</div>
|
|
617
678
|
{#if contextType === 'image'}
|
|
618
679
|
<div class="py-1">
|
|
619
|
-
<button type="button" onclick={() => { if (targetImage) openImageProps(targetImage) }} class="
|
|
680
|
+
<button type="button" onclick={() => { if (targetImage) openImageProps(targetImage) }} class="ctx-item">Propriétés de l'image</button>
|
|
681
|
+
<button type="button" onclick={() => { showContextMenu = false; if (targetImage) { saveSelection(); linkUrl = ''; linkText = ''; showLinkModal = true } }} class="ctx-item">Ajouter un lien</button>
|
|
620
682
|
</div>
|
|
621
|
-
<div class="
|
|
622
|
-
<button type="button" onclick={deleteTargetImage} class="
|
|
683
|
+
<div class="ctx-divider">
|
|
684
|
+
<button type="button" onclick={deleteTargetImage} class="ctx-item ctx-danger">Supprimer l'image</button>
|
|
623
685
|
</div>
|
|
624
686
|
{:else if contextType === 'link'}
|
|
625
687
|
<div class="py-1">
|
|
626
|
-
<button type="button" onclick={() => { const a = contextTarget?.closest('a'); if (a) openLinkModalFromElement(a) }} class="
|
|
688
|
+
<button type="button" onclick={() => { const a = contextTarget?.closest('a'); if (a) openLinkModalFromElement(a) }} class="ctx-item">Modifier le lien</button>
|
|
627
689
|
</div>
|
|
628
|
-
<div class="
|
|
629
|
-
<button type="button" onclick={() => { showContextMenu = false; exec('unlink') }} class="
|
|
690
|
+
<div class="ctx-divider">
|
|
691
|
+
<button type="button" onclick={() => { showContextMenu = false; exec('unlink') }} class="ctx-item ctx-danger">Supprimer le lien</button>
|
|
630
692
|
</div>
|
|
631
693
|
{:else if contextType === 'table'}
|
|
632
694
|
<div class="py-1">
|
|
633
|
-
<div class="
|
|
634
|
-
<button type="button" onclick={() => tableAction('addRowAbove')} class="
|
|
635
|
-
<button type="button" onclick={() => tableAction('addRowBelow')} class="
|
|
695
|
+
<div class="ctx-section-title">Lignes</div>
|
|
696
|
+
<button type="button" onclick={() => tableAction('addRowAbove')} class="ctx-item">↑ Insérer au-dessus</button>
|
|
697
|
+
<button type="button" onclick={() => tableAction('addRowBelow')} class="ctx-item">↓ Insérer en-dessous</button>
|
|
636
698
|
</div>
|
|
637
|
-
<div class="
|
|
638
|
-
<div class="
|
|
639
|
-
<button type="button" onclick={() => tableAction('addColLeft')} class="
|
|
640
|
-
<button type="button" onclick={() => tableAction('addColRight')} class="
|
|
699
|
+
<div class="ctx-divider">
|
|
700
|
+
<div class="ctx-section-title">Colonnes</div>
|
|
701
|
+
<button type="button" onclick={() => tableAction('addColLeft')} class="ctx-item">← Insérer à gauche</button>
|
|
702
|
+
<button type="button" onclick={() => tableAction('addColRight')} class="ctx-item">→ Insérer à droite</button>
|
|
641
703
|
</div>
|
|
642
|
-
<div class="
|
|
643
|
-
<button type="button" onclick={() => tableAction('deleteRow')} class="
|
|
644
|
-
<button type="button" onclick={() => tableAction('deleteCol')} class="
|
|
645
|
-
<button type="button" onclick={() => tableAction('deleteTable')} class="
|
|
704
|
+
<div class="ctx-divider">
|
|
705
|
+
<button type="button" onclick={() => tableAction('deleteRow')} class="ctx-item ctx-warn">Supprimer la ligne</button>
|
|
706
|
+
<button type="button" onclick={() => tableAction('deleteCol')} class="ctx-item ctx-warn">Supprimer la colonne</button>
|
|
707
|
+
<button type="button" onclick={() => tableAction('deleteTable')} class="ctx-item ctx-danger">Supprimer le tableau</button>
|
|
646
708
|
</div>
|
|
647
709
|
{/if}
|
|
648
|
-
<div class="
|
|
649
|
-
<button type="button" onclick={() => openElementProps()} class="
|
|
650
|
-
<button type="button" onclick={() => { showContextMenu = false; exec('removeFormat') }} class="
|
|
710
|
+
<div class="ctx-divider">
|
|
711
|
+
<button type="button" onclick={() => openElementProps()} class="ctx-item">Propriétés HTML</button>
|
|
712
|
+
<button type="button" onclick={() => { showContextMenu = false; exec('removeFormat') }} class="ctx-item">Effacer le formatage</button>
|
|
651
713
|
</div>
|
|
652
714
|
</div>
|
|
653
715
|
{/if}
|
|
654
716
|
|
|
655
717
|
<!-- ═══ LINK MODAL ═══ -->
|
|
656
718
|
{#if showLinkModal}
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
<
|
|
719
|
+
<!-- svelte-ignore a11y_no_static_element_interactions -->
|
|
720
|
+
<div class="rte-overlay" role="presentation" onclick={() => showLinkModal = false} onkeydown={(e) => { if (e.key === "Escape") showLinkModal = false }}>
|
|
721
|
+
<div class="rte-modal max-w-md" role="dialog" tabindex="-1" onclick={(e) => e.stopPropagation()} onkeydown={(e) => e.stopPropagation()}>
|
|
722
|
+
<div class="rte-modal-header">
|
|
723
|
+
<h3>Insérer / Modifier un lien</h3>
|
|
724
|
+
<button type="button" onclick={() => showLinkModal = false} class="rte-modal-close" aria-label="Fermer"><svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M18 6 6 18"/><path d="m6 6 12 12"/></svg></button>
|
|
662
725
|
</div>
|
|
663
726
|
<div class="space-y-3">
|
|
664
|
-
<div class="space-y-1"><span class=
|
|
665
|
-
<div class="space-y-1"><span class=
|
|
666
|
-
<div class="space-y-1"><span class=
|
|
667
|
-
<div class="space-y-1"><span class=
|
|
668
|
-
<label class="flex items-center gap-2 cursor-pointer"><input type="checkbox" bind:checked={linkTarget} class="h-4 w-4 rounded" /><span class="
|
|
727
|
+
<div class="space-y-1"><span class="rte-label">URL</span><input type="url" bind:value={linkUrl} placeholder="https://..." class="rte-input" /></div>
|
|
728
|
+
<div class="space-y-1"><span class="rte-label">Texte</span><input type="text" bind:value={linkText} placeholder="Texte affiché" class="rte-input" /></div>
|
|
729
|
+
<div class="space-y-1"><span class="rte-label">Title</span><input type="text" bind:value={linkTitle} placeholder="Info-bulle au survol" class="rte-input" /></div>
|
|
730
|
+
<div class="space-y-1"><span class="rte-label">Classes CSS</span><input type="text" bind:value={linkClass} placeholder="ex: btn btn-primary" class="rte-input font-mono text-xs" /></div>
|
|
731
|
+
<label class="flex items-center gap-2 cursor-pointer"><input type="checkbox" bind:checked={linkTarget} class="h-4 w-4 rounded" /><span class="rte-label-inline">Ouvrir dans un nouvel onglet</span></label>
|
|
669
732
|
</div>
|
|
670
|
-
<div class="
|
|
671
|
-
<button type="button" onclick={() => { showLinkModal = false; restoreSelection(); exec('unlink') }} class="
|
|
733
|
+
<div class="rte-modal-footer justify-between">
|
|
734
|
+
<button type="button" onclick={() => { showLinkModal = false; restoreSelection(); exec('unlink') }} class="rte-btn-danger-text">Supprimer</button>
|
|
672
735
|
<div class="flex gap-2">
|
|
673
|
-
<button type="button" onclick={() => showLinkModal = false} class="
|
|
674
|
-
<button type="button" onclick={insertLink} class="
|
|
736
|
+
<button type="button" onclick={() => showLinkModal = false} class="rte-btn-cancel">Annuler</button>
|
|
737
|
+
<button type="button" onclick={insertLink} class="rte-btn-primary">Appliquer</button>
|
|
675
738
|
</div>
|
|
676
739
|
</div>
|
|
677
740
|
</div>
|
|
@@ -680,46 +743,47 @@
|
|
|
680
743
|
|
|
681
744
|
<!-- ═══ IMAGE INSERT MODAL ═══ -->
|
|
682
745
|
{#if showImageModal}
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
<
|
|
746
|
+
<!-- svelte-ignore a11y_no_static_element_interactions -->
|
|
747
|
+
<div class="rte-overlay" role="presentation" onclick={() => showImageModal = false} onkeydown={(e) => { if (e.key === "Escape") showImageModal = false }}>
|
|
748
|
+
<div class="rte-modal max-w-lg" role="dialog" tabindex="-1" onclick={(e) => e.stopPropagation()} onkeydown={(e) => e.stopPropagation()}>
|
|
749
|
+
<div class="rte-modal-header">
|
|
750
|
+
<h3>Insérer une image</h3>
|
|
751
|
+
<button type="button" onclick={() => showImageModal = false} class="rte-modal-close" aria-label="Fermer"><svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M18 6 6 18"/><path d="m6 6 12 12"/></svg></button>
|
|
688
752
|
</div>
|
|
689
753
|
<div class="space-y-4">
|
|
690
754
|
<div class="grid grid-cols-2 gap-3">
|
|
691
755
|
{#if media?.upload}
|
|
692
|
-
<div class="
|
|
756
|
+
<div class="rte-upload-zone">
|
|
693
757
|
{#if imageUploading}
|
|
694
758
|
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="animate-spin text-violet-400 mb-1"><path d="M21 12a9 9 0 1 1-6.219-8.56"/></svg>
|
|
695
|
-
<p class="text-[11px]
|
|
759
|
+
<p class="text-[11px] opacity-60">Upload...</p>
|
|
696
760
|
{:else}
|
|
697
|
-
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="
|
|
761
|
+
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="opacity-40 mb-1"><path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"/><polyline points="17 8 12 3 7 8"/><line x1="12" x2="12" y1="3" y2="15"/></svg>
|
|
698
762
|
<label class="cursor-pointer text-xs font-medium text-violet-400 hover:text-violet-300">Uploader<input type="file" accept="image/*" class="hidden" onchange={(e) => handleImageUpload(e.currentTarget.files)} /></label>
|
|
699
763
|
{/if}
|
|
700
764
|
</div>
|
|
701
765
|
{/if}
|
|
702
766
|
{#if media}
|
|
703
|
-
<button type="button" onclick={openMediaExplorerForImage} class="
|
|
704
|
-
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="
|
|
767
|
+
<button type="button" onclick={openMediaExplorerForImage} class="rte-upload-zone hover:border-violet-500/40 hover:bg-violet-500/5">
|
|
768
|
+
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="opacity-40 mb-1"><path d="m6 14 1.5-2.9A2 2 0 0 1 9.24 10H20a2 2 0 0 1 1.94 2.5l-1.54 6a2 2 0 0 1-1.95 1.5H4a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h3.9a2 2 0 0 1 1.69.9l.81 1.2a2 2 0 0 0 1.67.9H18a2 2 0 0 1 2 2v2"/></svg>
|
|
705
769
|
<span class="text-xs font-medium text-violet-400">Parcourir les médias</span>
|
|
706
770
|
</button>
|
|
707
771
|
{/if}
|
|
708
772
|
</div>
|
|
709
|
-
<div class="space-y-1"><span class=
|
|
773
|
+
<div class="space-y-1"><span class="rte-label">URL</span><input type="url" bind:value={imageUrl} placeholder="https://..." class="rte-input" /></div>
|
|
710
774
|
<div class="grid grid-cols-2 gap-3">
|
|
711
|
-
<div class="space-y-1"><span class=
|
|
712
|
-
<div class="space-y-1"><span class=
|
|
775
|
+
<div class="space-y-1"><span class="rte-label">Alt</span><input type="text" bind:value={imageAlt} class="rte-input" /></div>
|
|
776
|
+
<div class="space-y-1"><span class="rte-label">Title</span><input type="text" bind:value={imageTitle} class="rte-input" /></div>
|
|
713
777
|
</div>
|
|
714
778
|
<div class="grid grid-cols-2 gap-3">
|
|
715
|
-
<div class="space-y-1"><span class=
|
|
716
|
-
<div class="space-y-1"><span class=
|
|
779
|
+
<div class="space-y-1"><span class="rte-label">Largeur</span><input type="text" bind:value={imageWidth} placeholder="auto" class="rte-input" /></div>
|
|
780
|
+
<div class="space-y-1"><span class="rte-label">Classes CSS</span><input type="text" bind:value={imageClass} placeholder="rounded shadow" class="rte-input font-mono text-xs" /></div>
|
|
717
781
|
</div>
|
|
718
|
-
{#if imageUrl}<div class="
|
|
782
|
+
{#if imageUrl}<div class="rte-preview-box"><img src={imageUrl} alt={imageAlt} class="w-full max-h-40 object-contain" /></div>{/if}
|
|
719
783
|
</div>
|
|
720
|
-
<div class="
|
|
721
|
-
<button type="button" onclick={() => showImageModal = false} class="
|
|
722
|
-
<button type="button" onclick={insertImage} disabled={!imageUrl} class="
|
|
784
|
+
<div class="rte-modal-footer justify-end">
|
|
785
|
+
<button type="button" onclick={() => showImageModal = false} class="rte-btn-cancel">Annuler</button>
|
|
786
|
+
<button type="button" onclick={insertImage} disabled={!imageUrl} class="rte-btn-primary">Insérer</button>
|
|
723
787
|
</div>
|
|
724
788
|
</div>
|
|
725
789
|
</div>
|
|
@@ -727,36 +791,37 @@
|
|
|
727
791
|
|
|
728
792
|
<!-- ═══ IMAGE PROPS MODAL ═══ -->
|
|
729
793
|
{#if showImagePropsModal}
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
<
|
|
794
|
+
<!-- svelte-ignore a11y_no_static_element_interactions -->
|
|
795
|
+
<div class="rte-overlay" role="presentation" onclick={() => showImagePropsModal = false} onkeydown={(e) => { if (e.key === "Escape") showImagePropsModal = false }}>
|
|
796
|
+
<div class="rte-modal max-w-lg" role="dialog" tabindex="-1" onclick={(e) => e.stopPropagation()} onkeydown={(e) => e.stopPropagation()}>
|
|
797
|
+
<div class="rte-modal-header">
|
|
798
|
+
<h3>Propriétés de l'image</h3>
|
|
799
|
+
<button type="button" onclick={() => showImagePropsModal = false} class="rte-modal-close" aria-label="Fermer"><svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M18 6 6 18"/><path d="m6 6 12 12"/></svg></button>
|
|
735
800
|
</div>
|
|
736
|
-
{#if imageUrl}<div class="
|
|
801
|
+
{#if imageUrl}<div class="rte-preview-box mb-4"><img src={imageUrl} alt="" class="mx-auto max-h-32 object-contain" /></div>{/if}
|
|
737
802
|
<div class="space-y-3">
|
|
738
803
|
<div class="grid grid-cols-2 gap-3">
|
|
739
|
-
<div class="space-y-1"><span class=
|
|
740
|
-
<div class="space-y-1"><span class=
|
|
804
|
+
<div class="space-y-1"><span class="rte-label">Largeur</span><input type="text" bind:value={imageWidth} placeholder="auto" class="rte-input" /></div>
|
|
805
|
+
<div class="space-y-1"><span class="rte-label">Hauteur</span><input type="text" bind:value={imageHeight} placeholder="auto" class="rte-input" /></div>
|
|
741
806
|
</div>
|
|
742
|
-
<div class="space-y-1"><span class=
|
|
743
|
-
<div class="space-y-1"><span class=
|
|
807
|
+
<div class="space-y-1"><span class="rte-label">Texte alternatif</span><input type="text" bind:value={imageAlt} class="rte-input" /></div>
|
|
808
|
+
<div class="space-y-1"><span class="rte-label">Title</span><input type="text" bind:value={imageTitle} class="rte-input" /></div>
|
|
744
809
|
<div class="space-y-1">
|
|
745
|
-
<span class=
|
|
810
|
+
<span class="rte-label">Alignement</span>
|
|
746
811
|
<div class="flex gap-1">
|
|
747
812
|
{#each [{ v: '', l: 'Aucun' }, { v: 'left', l: 'Gauche' }, { v: 'center', l: 'Centre' }, { v: 'right', l: 'Droite' }] as opt}
|
|
748
|
-
<button type="button" onclick={() => imageAlign = opt.v} class="
|
|
813
|
+
<button type="button" onclick={() => imageAlign = opt.v} class="rte-align-btn {imageAlign === opt.v ? 'rte-align-btn-active' : ''}">{opt.l}</button>
|
|
749
814
|
{/each}
|
|
750
815
|
</div>
|
|
751
816
|
</div>
|
|
752
|
-
<div class="space-y-1"><span class=
|
|
753
|
-
<div class="space-y-1"><span class=
|
|
817
|
+
<div class="space-y-1"><span class="rte-label">Classes CSS</span><input type="text" bind:value={imageClass} placeholder="ex: rounded shadow-lg" class="rte-input" /></div>
|
|
818
|
+
<div class="space-y-1"><span class="rte-label">Style inline</span><input type="text" bind:value={imageStyle} placeholder="ex: border-radius: 8px" class="rte-input font-mono text-xs" /></div>
|
|
754
819
|
</div>
|
|
755
|
-
<div class="
|
|
756
|
-
<button type="button" onclick={deleteTargetImage} class="
|
|
820
|
+
<div class="rte-modal-footer justify-between">
|
|
821
|
+
<button type="button" onclick={deleteTargetImage} class="rte-btn-danger-text">Supprimer</button>
|
|
757
822
|
<div class="flex gap-2">
|
|
758
|
-
<button type="button" onclick={() => showImagePropsModal = false} class="
|
|
759
|
-
<button type="button" onclick={applyImageProps} class="
|
|
823
|
+
<button type="button" onclick={() => showImagePropsModal = false} class="rte-btn-cancel">Annuler</button>
|
|
824
|
+
<button type="button" onclick={applyImageProps} class="rte-btn-primary">Appliquer</button>
|
|
760
825
|
</div>
|
|
761
826
|
</div>
|
|
762
827
|
</div>
|
|
@@ -765,20 +830,21 @@
|
|
|
765
830
|
|
|
766
831
|
<!-- ═══ ELEMENT PROPS MODAL ═══ -->
|
|
767
832
|
{#if showElementProps}
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
<
|
|
833
|
+
<!-- svelte-ignore a11y_no_static_element_interactions -->
|
|
834
|
+
<div class="rte-overlay" role="presentation" onclick={() => showElementProps = false} onkeydown={(e) => { if (e.key === "Escape") showElementProps = false }}>
|
|
835
|
+
<div class="rte-modal max-w-md" role="dialog" tabindex="-1" onclick={(e) => e.stopPropagation()} onkeydown={(e) => e.stopPropagation()}>
|
|
836
|
+
<div class="rte-modal-header">
|
|
837
|
+
<h3>Propriétés : <code class="text-violet-400"><{elPropsTag}></code></h3>
|
|
838
|
+
<button type="button" onclick={() => showElementProps = false} class="rte-modal-close" aria-label="Fermer"><svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M18 6 6 18"/><path d="m6 6 12 12"/></svg></button>
|
|
773
839
|
</div>
|
|
774
840
|
<div class="space-y-3">
|
|
775
|
-
<div class="space-y-1"><span class=
|
|
776
|
-
<div class="space-y-1"><span class=
|
|
777
|
-
<div class="space-y-1"><span class=
|
|
841
|
+
<div class="space-y-1"><span class="rte-label">ID</span><input type="text" bind:value={elPropsId} placeholder="identifiant" class="rte-input font-mono text-xs" /></div>
|
|
842
|
+
<div class="space-y-1"><span class="rte-label">Classes CSS</span><input type="text" bind:value={elPropsClass} placeholder="class1 class2" class="rte-input font-mono text-xs" /></div>
|
|
843
|
+
<div class="space-y-1"><span class="rte-label">Style inline</span><textarea bind:value={elPropsStyle} rows="3" placeholder="color: red; font-size: 16px;" class="rte-input font-mono text-xs"></textarea></div>
|
|
778
844
|
</div>
|
|
779
|
-
<div class="
|
|
780
|
-
<button type="button" onclick={() => showElementProps = false} class="
|
|
781
|
-
<button type="button" onclick={applyElementProps} class="
|
|
845
|
+
<div class="rte-modal-footer justify-end">
|
|
846
|
+
<button type="button" onclick={() => showElementProps = false} class="rte-btn-cancel">Annuler</button>
|
|
847
|
+
<button type="button" onclick={applyElementProps} class="rte-btn-primary">Appliquer</button>
|
|
782
848
|
</div>
|
|
783
849
|
</div>
|
|
784
850
|
</div>
|
|
@@ -786,19 +852,20 @@
|
|
|
786
852
|
|
|
787
853
|
<!-- ═══ TABLE MODAL ═══ -->
|
|
788
854
|
{#if showTableModal}
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
<
|
|
855
|
+
<!-- svelte-ignore a11y_no_static_element_interactions -->
|
|
856
|
+
<div class="rte-overlay" role="presentation" onclick={() => showTableModal = false} onkeydown={(e) => { if (e.key === "Escape") showTableModal = false }}>
|
|
857
|
+
<div class="rte-modal max-w-sm" role="dialog" tabindex="-1" onclick={(e) => e.stopPropagation()} onkeydown={(e) => e.stopPropagation()}>
|
|
858
|
+
<div class="rte-modal-header">
|
|
859
|
+
<h3>Insérer un tableau</h3>
|
|
860
|
+
<button type="button" onclick={() => showTableModal = false} class="rte-modal-close" aria-label="Fermer"><svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M18 6 6 18"/><path d="m6 6 12 12"/></svg></button>
|
|
794
861
|
</div>
|
|
795
862
|
<div class="grid grid-cols-2 gap-3">
|
|
796
|
-
<div class="space-y-1"><span class=
|
|
797
|
-
<div class="space-y-1"><span class=
|
|
863
|
+
<div class="space-y-1"><span class="rte-label">Lignes</span><input type="number" bind:value={tableRows} min="1" max="20" class="rte-input" /></div>
|
|
864
|
+
<div class="space-y-1"><span class="rte-label">Colonnes</span><input type="number" bind:value={tableCols} min="1" max="10" class="rte-input" /></div>
|
|
798
865
|
</div>
|
|
799
|
-
<div class="
|
|
800
|
-
<button type="button" onclick={() => showTableModal = false} class="
|
|
801
|
-
<button type="button" onclick={insertTable} class="
|
|
866
|
+
<div class="rte-modal-footer justify-end">
|
|
867
|
+
<button type="button" onclick={() => showTableModal = false} class="rte-btn-cancel">Annuler</button>
|
|
868
|
+
<button type="button" onclick={insertTable} class="rte-btn-primary">Insérer</button>
|
|
802
869
|
</div>
|
|
803
870
|
</div>
|
|
804
871
|
</div>
|
|
@@ -806,77 +873,733 @@
|
|
|
806
873
|
|
|
807
874
|
<!-- ═══ EMBED MODAL ═══ -->
|
|
808
875
|
{#if showEmbedModal}
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
<
|
|
876
|
+
<!-- svelte-ignore a11y_no_static_element_interactions -->
|
|
877
|
+
<div class="rte-overlay" role="presentation" onclick={() => showEmbedModal = false} onkeydown={(e) => { if (e.key === "Escape") showEmbedModal = false }}>
|
|
878
|
+
<div class="rte-modal max-w-md" role="dialog" tabindex="-1" onclick={(e) => e.stopPropagation()} onkeydown={(e) => e.stopPropagation()}>
|
|
879
|
+
<div class="rte-modal-header">
|
|
880
|
+
<h3>Embed vidéo</h3>
|
|
881
|
+
<button type="button" onclick={() => showEmbedModal = false} class="rte-modal-close" aria-label="Fermer"><svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M18 6 6 18"/><path d="m6 6 12 12"/></svg></button>
|
|
814
882
|
</div>
|
|
815
|
-
<p class="text-xs
|
|
816
|
-
<input type="url" bind:value={embedUrl} placeholder="https://www.youtube.com/watch?v=..." class=
|
|
817
|
-
<div class="
|
|
818
|
-
<button type="button" onclick={() => showEmbedModal = false} class="
|
|
819
|
-
<button type="button" onclick={insertEmbed} disabled={!embedUrl} class="
|
|
883
|
+
<p class="text-xs opacity-50 mb-3">Collez une URL YouTube, Vimeo ou un lien embed.</p>
|
|
884
|
+
<input type="url" bind:value={embedUrl} placeholder="https://www.youtube.com/watch?v=..." class="rte-input" onkeydown={(e) => { if (e.key === 'Enter') insertEmbed() }} />
|
|
885
|
+
<div class="rte-modal-footer justify-end">
|
|
886
|
+
<button type="button" onclick={() => showEmbedModal = false} class="rte-btn-cancel">Annuler</button>
|
|
887
|
+
<button type="button" onclick={insertEmbed} disabled={!embedUrl} class="rte-btn-primary">Insérer</button>
|
|
820
888
|
</div>
|
|
821
889
|
</div>
|
|
822
890
|
</div>
|
|
823
891
|
{/if}
|
|
824
892
|
|
|
825
|
-
<!-- ═══ MEDIA EXPLORER
|
|
893
|
+
<!-- ═══ MEDIA EXPLORER ═══ -->
|
|
826
894
|
{#if showMediaExplorer && media}
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
895
|
+
<!-- svelte-ignore a11y_no_static_element_interactions -->
|
|
896
|
+
<div class="rte-overlay" role="presentation" onclick={() => showMediaExplorer = false} onkeydown={(e) => { if (e.key === "Escape") showMediaExplorer = false }}>
|
|
897
|
+
<div class="rte-media-explorer" role="dialog" tabindex="-1"
|
|
898
|
+
onclick={(e) => e.stopPropagation()} onkeydown={(e) => e.stopPropagation()}
|
|
899
|
+
ondragover={(e) => { e.preventDefault(); mediaDragOver = true }}
|
|
900
|
+
ondragleave={() => mediaDragOver = false}
|
|
901
|
+
ondrop={mediaHandleDrop}
|
|
902
|
+
>
|
|
903
|
+
<!-- Header -->
|
|
904
|
+
<div class="rte-media-header">
|
|
905
|
+
<svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="text-violet-400"><rect width="18" height="18" x="3" y="3" rx="2" ry="2"/><circle cx="9" cy="9" r="2"/><path d="m21 15-3.086-3.086a2 2 0 0 0-2.828 0L6 21"/></svg>
|
|
906
|
+
<h2 class="text-sm font-semibold">Explorateur de médias</h2>
|
|
831
907
|
<div class="flex-1"></div>
|
|
832
|
-
<
|
|
833
|
-
|
|
908
|
+
<div class="relative">
|
|
909
|
+
<svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="absolute left-2.5 top-1/2 -translate-y-1/2 opacity-40"><circle cx="11" cy="11" r="8"/><path d="m21 21-4.3-4.3"/></svg>
|
|
910
|
+
<input type="text" bind:value={mediaSearch} placeholder="Filtrer..." class="rte-input w-48 !py-1.5 !pl-8 !text-xs" onkeydown={(e) => { if (e.key === 'Enter') browseMedia(mediaCurrentPath) }} />
|
|
911
|
+
</div>
|
|
912
|
+
<!-- View toggle -->
|
|
913
|
+
<div class="rte-media-view-toggle">
|
|
914
|
+
<button type="button" onclick={() => mediaViewMode = 'grid'} class="p-1.5 {mediaViewMode === 'grid' ? 'bg-violet-500/15 text-violet-400' : 'opacity-40 hover:opacity-70'}">
|
|
915
|
+
<svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect width="7" height="7" x="3" y="3" rx="1"/><rect width="7" height="7" x="14" y="3" rx="1"/><rect width="7" height="7" x="14" y="14" rx="1"/><rect width="7" height="7" x="3" y="14" rx="1"/></svg>
|
|
916
|
+
</button>
|
|
917
|
+
<button type="button" onclick={() => mediaViewMode = 'list'} class="p-1.5 {mediaViewMode === 'list' ? 'bg-violet-500/15 text-violet-400' : 'opacity-40 hover:opacity-70'}">
|
|
918
|
+
<svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><line x1="8" x2="21" y1="6" y2="6"/><line x1="8" x2="21" y1="12" y2="12"/><line x1="8" x2="21" y1="18" y2="18"/><line x1="3" x2="3.01" y1="6" y2="6"/><line x1="3" x2="3.01" y1="12" y2="12"/><line x1="3" x2="3.01" y1="18" y2="18"/></svg>
|
|
919
|
+
</button>
|
|
920
|
+
</div>
|
|
921
|
+
<button type="button" onclick={() => showMediaExplorer = false} class="rte-modal-close" aria-label="Fermer">
|
|
834
922
|
<svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M18 6 6 18"/><path d="m6 6 12 12"/></svg>
|
|
835
923
|
</button>
|
|
836
924
|
</div>
|
|
837
925
|
|
|
838
|
-
|
|
926
|
+
<!-- Toolbar (nav + breadcrumbs + actions) -->
|
|
927
|
+
<div class="rte-media-toolbar">
|
|
928
|
+
<button type="button" onclick={mediaGoUp} disabled={!mediaCurrentPath} class="rte-btn disabled:opacity-30" aria-label="Remonter">
|
|
929
|
+
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="m12 19-7-7 7-7"/><path d="M19 12H5"/></svg>
|
|
930
|
+
</button>
|
|
931
|
+
<button type="button" onclick={() => mediaNavigateTo('')} class="rte-btn" aria-label="Racine">
|
|
932
|
+
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="m3 9 9-7 9 7v11a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2z"/><polyline points="9 22 9 12 15 12 15 22"/></svg>
|
|
933
|
+
</button>
|
|
934
|
+
<button type="button" onclick={() => browseMedia(mediaCurrentPath)} class="rte-btn" aria-label="Rafraîchir">
|
|
935
|
+
<svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M21 12a9 9 0 0 0-9-9 9.75 9.75 0 0 0-6.74 2.74L3 8"/><path d="M3 3v5h5"/><path d="M3 12a9 9 0 0 0 9 9 9.75 9.75 0 0 0 6.74-2.74L21 16"/><path d="M16 16h5v5"/></svg>
|
|
936
|
+
</button>
|
|
937
|
+
<!-- Breadcrumb -->
|
|
938
|
+
<div class="rte-media-breadcrumb">
|
|
939
|
+
<button type="button" onclick={() => mediaNavigateTo('')} class="hover:text-violet-400 transition-colors">uploads</button>
|
|
940
|
+
{#each mediaBreadcrumbs as crumb}
|
|
941
|
+
<svg xmlns="http://www.w3.org/2000/svg" width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="m9 18 6-6-6-6"/></svg>
|
|
942
|
+
<button type="button" onclick={() => mediaNavigateTo(crumb.path)} class="hover:text-violet-400 transition-colors">{crumb.label}</button>
|
|
943
|
+
{/each}
|
|
944
|
+
</div>
|
|
945
|
+
<div class="flex-1"></div>
|
|
946
|
+
{#if media.createFolder}
|
|
947
|
+
<button type="button" onclick={() => { mediaShowNewFolder = true; mediaNewFolderName = '' }} class="rte-media-action-btn">
|
|
948
|
+
<svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M12 10v6"/><path d="M9 13h6"/><path d="M20 20a2 2 0 0 0 2-2V8a2 2 0 0 0-2-2h-7.9a2 2 0 0 1-1.69-.9L9.6 3.9A2 2 0 0 0 7.93 3H4a2 2 0 0 0-2 2v13a2 2 0 0 0 2 2Z"/></svg>
|
|
949
|
+
Nouveau dossier
|
|
950
|
+
</button>
|
|
951
|
+
{/if}
|
|
952
|
+
{#if media.upload}
|
|
953
|
+
<label class="rte-media-upload-btn">
|
|
954
|
+
<svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"/><polyline points="17 8 12 3 7 8"/><line x1="12" x2="12" y1="3" y2="15"/></svg>
|
|
955
|
+
Uploader
|
|
956
|
+
<input type="file" accept="image/*" multiple class="hidden" onchange={(e) => mediaHandleUpload(e.currentTarget.files)} />
|
|
957
|
+
</label>
|
|
958
|
+
{/if}
|
|
959
|
+
</div>
|
|
960
|
+
|
|
961
|
+
<!-- New folder inline -->
|
|
962
|
+
{#if mediaShowNewFolder}
|
|
963
|
+
<div class="rte-media-newfolder">
|
|
964
|
+
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="text-violet-400"><path d="M12 10v6"/><path d="M9 13h6"/><path d="M20 20a2 2 0 0 0 2-2V8a2 2 0 0 0-2-2h-7.9a2 2 0 0 1-1.69-.9L9.6 3.9A2 2 0 0 0 7.93 3H4a2 2 0 0 0-2 2v13a2 2 0 0 0 2 2Z"/></svg>
|
|
965
|
+
<input type="text" bind:value={mediaNewFolderName} placeholder="Nom du dossier..." class="rte-input flex-1 !py-1.5 !text-xs"
|
|
966
|
+
onkeydown={(e) => { if (e.key === 'Enter') mediaCreateFolder(); if (e.key === 'Escape') mediaShowNewFolder = false }} />
|
|
967
|
+
<button type="button" onclick={mediaCreateFolder} class="rte-btn-primary !py-1.5 !px-3">
|
|
968
|
+
<svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M20 6 9 17l-5-5"/></svg>
|
|
969
|
+
</button>
|
|
970
|
+
<button type="button" onclick={() => mediaShowNewFolder = false} class="rte-modal-close">
|
|
971
|
+
<svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M18 6 6 18"/><path d="m6 6 12 12"/></svg>
|
|
972
|
+
</button>
|
|
973
|
+
</div>
|
|
974
|
+
{/if}
|
|
975
|
+
|
|
976
|
+
<!-- Content -->
|
|
977
|
+
<div class="rte-media-content {mediaDragOver ? 'ring-2 ring-inset ring-violet-500/50 bg-violet-500/5' : ''}">
|
|
978
|
+
{#if mediaUploading}
|
|
979
|
+
<div class="rte-media-overlay">
|
|
980
|
+
<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="animate-spin text-violet-400"><path d="M21 12a9 9 0 1 1-6.219-8.56"/></svg>
|
|
981
|
+
<p class="text-sm opacity-60 mt-2">Upload en cours...</p>
|
|
982
|
+
</div>
|
|
983
|
+
{/if}
|
|
984
|
+
|
|
839
985
|
{#if mediaLoading}
|
|
840
986
|
<div class="flex items-center justify-center py-20">
|
|
841
987
|
<svg xmlns="http://www.w3.org/2000/svg" width="28" height="28" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="animate-spin text-violet-400"><path d="M21 12a9 9 0 1 1-6.219-8.56"/></svg>
|
|
842
988
|
</div>
|
|
843
|
-
{:else if
|
|
844
|
-
<div class="flex flex-col items-center justify-center py-20
|
|
845
|
-
<
|
|
989
|
+
{:else if mediaFiltered.length === 0}
|
|
990
|
+
<div class="flex flex-col items-center justify-center py-20 opacity-40">
|
|
991
|
+
<svg xmlns="http://www.w3.org/2000/svg" width="48" height="48" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1" stroke-linecap="round" stroke-linejoin="round" class="mb-3 opacity-30"><path d="M20 20a2 2 0 0 0 2-2V8a2 2 0 0 0-2-2h-7.9a2 2 0 0 1-1.69-.9L9.6 3.9A2 2 0 0 0 7.93 3H4a2 2 0 0 0-2 2v13a2 2 0 0 0 2 2Z"/></svg>
|
|
992
|
+
<p class="text-sm">Dossier vide</p>
|
|
993
|
+
<p class="mt-1 text-xs">Glissez des fichiers ici ou cliquez sur Uploader</p>
|
|
846
994
|
</div>
|
|
847
|
-
{:else}
|
|
848
|
-
<div class="grid grid-cols-4 sm:grid-cols-5 md:grid-cols-6 gap-3">
|
|
849
|
-
{#each
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
995
|
+
{:else if mediaViewMode === 'grid'}
|
|
996
|
+
<div class="grid grid-cols-4 sm:grid-cols-5 md:grid-cols-6 lg:grid-cols-8 gap-3">
|
|
997
|
+
{#each mediaFiltered as entry}
|
|
998
|
+
<!-- svelte-ignore a11y_no_static_element_interactions -->
|
|
999
|
+
<!-- svelte-ignore a11y_click_events_have_key_events -->
|
|
1000
|
+
<div class="rte-media-item {mediaSelected?.path === entry.path ? 'rte-media-item-selected' : ''}"
|
|
1001
|
+
onclick={() => handleMediaEntryClick(entry)}
|
|
1002
|
+
ondblclick={() => handleMediaEntryDblClick(entry)}
|
|
854
1003
|
>
|
|
855
|
-
{#if
|
|
856
|
-
<div class="h-16 w-16
|
|
857
|
-
<
|
|
1004
|
+
{#if entry.is_dir}
|
|
1005
|
+
<div class="flex h-16 w-16 items-center justify-center">
|
|
1006
|
+
<svg xmlns="http://www.w3.org/2000/svg" width="40" height="40" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" class="text-amber-400/80"><path d="M20 20a2 2 0 0 0 2-2V8a2 2 0 0 0-2-2h-7.9a2 2 0 0 1-1.69-.9L9.6 3.9A2 2 0 0 0 7.93 3H4a2 2 0 0 0-2 2v13a2 2 0 0 0 2 2Z"/></svg>
|
|
858
1007
|
</div>
|
|
1008
|
+
{:else if isImage(entry) && entry.url}
|
|
1009
|
+
<div class="rte-media-thumb"><img src={entry.url} alt={entry.name} class="h-full w-full object-cover" loading="lazy" /></div>
|
|
859
1010
|
{:else}
|
|
860
1011
|
<div class="flex h-16 w-16 items-center justify-center">
|
|
861
|
-
<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="
|
|
1012
|
+
<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" class="opacity-40"><path d="M15 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V7Z"/><path d="M14 2v4a2 2 0 0 0 2 2h4"/></svg>
|
|
862
1013
|
</div>
|
|
863
1014
|
{/if}
|
|
864
|
-
<p class="mt-1.5 w-full truncate text-center text-[11px]
|
|
1015
|
+
<p class="mt-1.5 w-full truncate text-center text-[11px]">{entry.name}</p>
|
|
1016
|
+
<p class="text-[10px] opacity-40">{entry.is_dir ? 'Dossier' : formatSize(entry.size)}</p>
|
|
1017
|
+
</div>
|
|
1018
|
+
{/each}
|
|
1019
|
+
</div>
|
|
1020
|
+
{:else}
|
|
1021
|
+
<div class="rte-media-list">
|
|
1022
|
+
{#each mediaFiltered as entry, i}
|
|
1023
|
+
<!-- svelte-ignore a11y_no_static_element_interactions -->
|
|
1024
|
+
<!-- svelte-ignore a11y_click_events_have_key_events -->
|
|
1025
|
+
<div class="rte-media-list-item {i > 0 ? 'rte-media-list-divider' : ''} {mediaSelected?.path === entry.path ? 'bg-violet-500/10' : ''}"
|
|
1026
|
+
onclick={() => handleMediaEntryClick(entry)}
|
|
1027
|
+
ondblclick={() => handleMediaEntryDblClick(entry)}
|
|
1028
|
+
>
|
|
1029
|
+
{#if entry.is_dir}
|
|
1030
|
+
<svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" class="shrink-0 text-amber-400/80"><path d="M20 20a2 2 0 0 0 2-2V8a2 2 0 0 0-2-2h-7.9a2 2 0 0 1-1.69-.9L9.6 3.9A2 2 0 0 0 7.93 3H4a2 2 0 0 0-2 2v13a2 2 0 0 0 2 2Z"/></svg>
|
|
1031
|
+
{:else if isImage(entry) && entry.url}
|
|
1032
|
+
<img src={entry.url} alt="" class="rte-media-list-thumb" loading="lazy" />
|
|
1033
|
+
{:else}
|
|
1034
|
+
<svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" class="shrink-0 opacity-40"><path d="M15 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V7Z"/><path d="M14 2v4a2 2 0 0 0 2 2h4"/></svg>
|
|
1035
|
+
{/if}
|
|
1036
|
+
<span class="flex-1 truncate text-xs">{entry.name}</span>
|
|
1037
|
+
<span class="text-[11px] opacity-40">{entry.is_dir ? 'Dossier' : formatSize(entry.size)}</span>
|
|
1038
|
+
{#if entry.modified}<span class="hidden sm:block text-[11px] opacity-40">{entry.modified}</span>{/if}
|
|
865
1039
|
</div>
|
|
866
1040
|
{/each}
|
|
867
1041
|
</div>
|
|
868
1042
|
{/if}
|
|
869
1043
|
</div>
|
|
870
1044
|
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
1045
|
+
<!-- Footer -->
|
|
1046
|
+
<div class="rte-media-footer">
|
|
1047
|
+
{#if mediaSelected}
|
|
1048
|
+
<div class="flex items-center gap-3 flex-1 min-w-0">
|
|
1049
|
+
{#if isImage(mediaSelected) && mediaSelected.url}
|
|
1050
|
+
<img src={mediaSelected.url} alt="" class="h-10 w-10 rounded object-cover" />
|
|
1051
|
+
{/if}
|
|
1052
|
+
<div class="min-w-0">
|
|
1053
|
+
<p class="truncate text-xs font-medium">{mediaSelected.name}</p>
|
|
1054
|
+
<p class="text-[11px] opacity-40">{formatSize(mediaSelected.size)} · {mediaSelected.mime ?? 'Inconnu'}</p>
|
|
1055
|
+
</div>
|
|
1056
|
+
</div>
|
|
1057
|
+
{:else}
|
|
1058
|
+
<p class="flex-1 text-xs opacity-40">{mediaFiltered.length} élément{mediaFiltered.length !== 1 ? 's' : ''}</p>
|
|
1059
|
+
{/if}
|
|
1060
|
+
<button type="button" onclick={() => showMediaExplorer = false} class="rte-btn-cancel">Annuler</button>
|
|
1061
|
+
<button type="button" onclick={() => { if (mediaSelected?.url) handleMediaSelect(mediaSelected.url) }} disabled={!mediaSelected?.url} class="rte-btn-primary">Sélectionner</button>
|
|
875
1062
|
</div>
|
|
876
1063
|
</div>
|
|
877
1064
|
</div>
|
|
878
1065
|
{/if}
|
|
879
1066
|
|
|
1067
|
+
<!-- Color picker backdrop -->
|
|
880
1068
|
{#if showColorPicker}
|
|
1069
|
+
<!-- svelte-ignore a11y_no_static_element_interactions -->
|
|
881
1070
|
<div class="fixed inset-0 z-10" role="presentation" onclick={() => showColorPicker = false} onkeydown={(e) => { if (e.key === "Escape") showColorPicker = false }}></div>
|
|
882
1071
|
{/if}
|
|
1072
|
+
|
|
1073
|
+
<style>
|
|
1074
|
+
/* ═══════════════════════════════════════════════
|
|
1075
|
+
WRAPPER
|
|
1076
|
+
═══════════════════════════════════════════════ */
|
|
1077
|
+
.rte-wrapper {
|
|
1078
|
+
@apply rounded-xl shadow-sm overflow-hidden;
|
|
1079
|
+
border: 1px solid var(--karbon-border);
|
|
1080
|
+
background: var(--karbon-bg-card);
|
|
1081
|
+
}
|
|
1082
|
+
|
|
1083
|
+
/* ═══════════════════════════════════════════════
|
|
1084
|
+
TOOLBAR
|
|
1085
|
+
═══════════════════════════════════════════════ */
|
|
1086
|
+
.rte-toolbar {
|
|
1087
|
+
@apply flex flex-wrap items-center gap-1 px-3 py-2 select-none;
|
|
1088
|
+
border-bottom: 1px solid var(--karbon-border);
|
|
1089
|
+
background: color-mix(in srgb, var(--karbon-bg-2) 50%, transparent);
|
|
1090
|
+
}
|
|
1091
|
+
|
|
1092
|
+
.rte-btn {
|
|
1093
|
+
@apply flex items-center justify-center rounded-md transition-all cursor-pointer border-none bg-transparent px-1;
|
|
1094
|
+
min-width: 32px;
|
|
1095
|
+
height: 32px;
|
|
1096
|
+
color: var(--karbon-text-3);
|
|
1097
|
+
}
|
|
1098
|
+
|
|
1099
|
+
.rte-btn:hover {
|
|
1100
|
+
background: var(--karbon-bg-2);
|
|
1101
|
+
color: var(--karbon-text);
|
|
1102
|
+
}
|
|
1103
|
+
|
|
1104
|
+
.rte-btn-active {
|
|
1105
|
+
@apply bg-violet-500/15 text-violet-400;
|
|
1106
|
+
}
|
|
1107
|
+
|
|
1108
|
+
.rte-btn-active:hover {
|
|
1109
|
+
@apply bg-violet-500/20 text-violet-400;
|
|
1110
|
+
}
|
|
1111
|
+
|
|
1112
|
+
.rte-sep {
|
|
1113
|
+
@apply mx-0.5 shrink-0;
|
|
1114
|
+
width: 1px;
|
|
1115
|
+
height: 22px;
|
|
1116
|
+
background: var(--karbon-border);
|
|
1117
|
+
}
|
|
1118
|
+
|
|
1119
|
+
.rte-select {
|
|
1120
|
+
@apply rounded-[5px] px-1 py-0.5 text-[0.6875rem] outline-none cursor-pointer;
|
|
1121
|
+
background: var(--karbon-bg-input);
|
|
1122
|
+
border: 1px solid var(--karbon-border-input);
|
|
1123
|
+
color: var(--karbon-text-3);
|
|
1124
|
+
}
|
|
1125
|
+
|
|
1126
|
+
/* ═══════════════════════════════════════════════
|
|
1127
|
+
FIND/REPLACE BAR
|
|
1128
|
+
═══════════════════════════════════════════════ */
|
|
1129
|
+
.rte-findbar {
|
|
1130
|
+
@apply flex items-center gap-2 px-4 py-2;
|
|
1131
|
+
border-bottom: 1px solid var(--karbon-border);
|
|
1132
|
+
background: color-mix(in srgb, var(--karbon-bg-2) 30%, transparent);
|
|
1133
|
+
}
|
|
1134
|
+
|
|
1135
|
+
.rte-bar-input {
|
|
1136
|
+
@apply rounded-md px-2 py-1 text-xs outline-none;
|
|
1137
|
+
background: var(--karbon-bg-input);
|
|
1138
|
+
border: 1px solid var(--karbon-border-input);
|
|
1139
|
+
color: var(--karbon-text);
|
|
1140
|
+
}
|
|
1141
|
+
|
|
1142
|
+
.rte-bar-input:focus {
|
|
1143
|
+
border-color: var(--karbon-border-input-focus);
|
|
1144
|
+
}
|
|
1145
|
+
|
|
1146
|
+
.rte-bar-btn {
|
|
1147
|
+
@apply rounded-md px-2.5 py-1 text-[0.6875rem] cursor-pointer transition-all;
|
|
1148
|
+
background: var(--karbon-bg-2);
|
|
1149
|
+
border: 1px solid var(--karbon-border);
|
|
1150
|
+
color: var(--karbon-text-3);
|
|
1151
|
+
}
|
|
1152
|
+
|
|
1153
|
+
.rte-bar-btn:hover {
|
|
1154
|
+
background: var(--karbon-bg-card);
|
|
1155
|
+
color: var(--karbon-text);
|
|
1156
|
+
}
|
|
1157
|
+
|
|
1158
|
+
/* ═══════════════════════════════════════════════
|
|
1159
|
+
STATUS BAR
|
|
1160
|
+
═══════════════════════════════════════════════ */
|
|
1161
|
+
.rte-statusbar {
|
|
1162
|
+
@apply flex items-center gap-4 px-4 py-1.5 text-[11px];
|
|
1163
|
+
border-top: 1px solid var(--karbon-border);
|
|
1164
|
+
background: color-mix(in srgb, var(--karbon-bg-2) 30%, transparent);
|
|
1165
|
+
color: var(--karbon-text-4);
|
|
1166
|
+
}
|
|
1167
|
+
|
|
1168
|
+
/* ═══════════════════════════════════════════════
|
|
1169
|
+
CONTENT AREA — Default theme
|
|
1170
|
+
═══════════════════════════════════════════════ */
|
|
1171
|
+
.rte-content {
|
|
1172
|
+
padding: 1.25rem 1.5rem;
|
|
1173
|
+
color: var(--karbon-text);
|
|
1174
|
+
font-size: 0.9375rem;
|
|
1175
|
+
line-height: 1.75;
|
|
1176
|
+
}
|
|
1177
|
+
|
|
1178
|
+
.rte-content:empty:before {
|
|
1179
|
+
content: attr(data-placeholder);
|
|
1180
|
+
color: var(--karbon-text-4);
|
|
1181
|
+
pointer-events: none;
|
|
1182
|
+
}
|
|
1183
|
+
|
|
1184
|
+
.rte-content :global(h2) { @apply text-2xl font-bold mt-4 mb-2; }
|
|
1185
|
+
.rte-content :global(h3) { @apply text-xl font-semibold mt-3 mb-1.5; }
|
|
1186
|
+
.rte-content :global(h4) { @apply text-lg font-semibold mt-2.5 mb-1; }
|
|
1187
|
+
.rte-content :global(p) { @apply my-2; }
|
|
1188
|
+
.rte-content :global(a) { @apply text-violet-500 underline; }
|
|
1189
|
+
.rte-content :global(blockquote) {
|
|
1190
|
+
@apply border-l-[3px] border-violet-500 pl-4 py-2 my-4 rounded-r-lg italic;
|
|
1191
|
+
background: rgba(139, 92, 246, 0.05);
|
|
1192
|
+
}
|
|
1193
|
+
.rte-content :global(pre) {
|
|
1194
|
+
@apply rounded-lg p-4 font-mono text-sm overflow-x-auto my-4;
|
|
1195
|
+
background: var(--karbon-bg-2);
|
|
1196
|
+
border: 1px solid var(--karbon-border);
|
|
1197
|
+
}
|
|
1198
|
+
.rte-content :global(code) {
|
|
1199
|
+
@apply px-1.5 py-0.5 rounded text-sm;
|
|
1200
|
+
background: var(--karbon-bg-2);
|
|
1201
|
+
}
|
|
1202
|
+
.rte-content :global(img) {
|
|
1203
|
+
@apply max-w-full h-auto rounded-lg my-4 cursor-pointer;
|
|
1204
|
+
}
|
|
1205
|
+
.rte-content :global(img):hover {
|
|
1206
|
+
@apply outline outline-2 outline-violet-500/50 outline-offset-2;
|
|
1207
|
+
}
|
|
1208
|
+
.rte-content :global(hr) {
|
|
1209
|
+
@apply border-none my-6;
|
|
1210
|
+
border-top: 1px solid var(--karbon-border);
|
|
1211
|
+
}
|
|
1212
|
+
.rte-content :global(ul), .rte-content :global(ol) { @apply pl-6 my-2; }
|
|
1213
|
+
.rte-content :global(li) { @apply my-1; }
|
|
1214
|
+
.rte-content :global(table) { @apply w-full border-collapse my-4; }
|
|
1215
|
+
.rte-content :global(th) {
|
|
1216
|
+
@apply px-3 py-2 font-semibold text-sm;
|
|
1217
|
+
border: 1px solid var(--karbon-border);
|
|
1218
|
+
background: var(--karbon-bg-2);
|
|
1219
|
+
}
|
|
1220
|
+
.rte-content :global(td) {
|
|
1221
|
+
@apply px-3 py-2;
|
|
1222
|
+
border: 1px solid var(--karbon-border);
|
|
1223
|
+
}
|
|
1224
|
+
.rte-content :global(.embed-responsive) {
|
|
1225
|
+
@apply relative h-0 overflow-hidden my-4 rounded-lg;
|
|
1226
|
+
padding-bottom: 56.25%;
|
|
1227
|
+
}
|
|
1228
|
+
.rte-content :global(.embed-responsive iframe) {
|
|
1229
|
+
@apply absolute inset-0 w-full h-full border-0;
|
|
1230
|
+
}
|
|
1231
|
+
|
|
1232
|
+
/* ═══════════════════════════════════════════════
|
|
1233
|
+
CONTENT — Prose theme (article/blog)
|
|
1234
|
+
═══════════════════════════════════════════════ */
|
|
1235
|
+
.rte-prose {
|
|
1236
|
+
line-height: 1.9;
|
|
1237
|
+
padding: 2rem 2.5rem;
|
|
1238
|
+
}
|
|
1239
|
+
|
|
1240
|
+
.rte-prose :global(h2) { @apply text-3xl font-bold mt-8 mb-3; }
|
|
1241
|
+
.rte-prose :global(h3) { @apply text-2xl font-semibold mt-6 mb-2; }
|
|
1242
|
+
.rte-prose :global(h4) { @apply text-xl font-semibold mt-4 mb-1.5; }
|
|
1243
|
+
.rte-prose :global(p) { @apply my-4; }
|
|
1244
|
+
.rte-prose :global(blockquote) { @apply pl-6 py-3 my-6; }
|
|
1245
|
+
.rte-prose :global(img) { @apply my-6; }
|
|
1246
|
+
.rte-prose :global(hr) { @apply my-10; }
|
|
1247
|
+
|
|
1248
|
+
/* ═══════════════════════════════════════════════
|
|
1249
|
+
CONTENT — Compact theme (admin/dashboard)
|
|
1250
|
+
═══════════════════════════════════════════════ */
|
|
1251
|
+
.rte-compact {
|
|
1252
|
+
padding: 0.75rem 1rem;
|
|
1253
|
+
font-size: 0.8125rem;
|
|
1254
|
+
line-height: 1.6;
|
|
1255
|
+
}
|
|
1256
|
+
|
|
1257
|
+
.rte-compact :global(h2) { @apply text-lg font-semibold mt-2 mb-1; }
|
|
1258
|
+
.rte-compact :global(h3) { @apply text-base font-semibold mt-1.5 mb-0.5; }
|
|
1259
|
+
.rte-compact :global(h4) { @apply text-sm font-semibold mt-1 mb-0.5; }
|
|
1260
|
+
.rte-compact :global(p) { @apply my-1; }
|
|
1261
|
+
.rte-compact :global(blockquote) { @apply pl-3 py-1 my-2; }
|
|
1262
|
+
.rte-compact :global(img) { @apply my-2 rounded-md; }
|
|
1263
|
+
.rte-compact :global(hr) { @apply my-3; }
|
|
1264
|
+
|
|
1265
|
+
/* ═══════════════════════════════════════════════
|
|
1266
|
+
CONTENT — Minimal theme (neutral)
|
|
1267
|
+
═══════════════════════════════════════════════ */
|
|
1268
|
+
.rte-minimal {
|
|
1269
|
+
padding: 1rem 1.25rem;
|
|
1270
|
+
font-size: 0.875rem;
|
|
1271
|
+
line-height: 1.7;
|
|
1272
|
+
}
|
|
1273
|
+
|
|
1274
|
+
.rte-minimal :global(h2) { @apply text-xl font-semibold mt-3 mb-1.5; }
|
|
1275
|
+
.rte-minimal :global(h3) { @apply text-lg font-medium mt-2 mb-1; }
|
|
1276
|
+
.rte-minimal :global(h4) { @apply text-base font-medium mt-1.5 mb-0.5; }
|
|
1277
|
+
.rte-minimal :global(a) { @apply text-blue-500 underline; }
|
|
1278
|
+
.rte-minimal :global(blockquote) {
|
|
1279
|
+
@apply border-gray-400 bg-transparent;
|
|
1280
|
+
}
|
|
1281
|
+
|
|
1282
|
+
/* ═══════════════════════════════════════════════
|
|
1283
|
+
SOURCE MODE
|
|
1284
|
+
═══════════════════════════════════════════════ */
|
|
1285
|
+
.rte-source-wrapper {
|
|
1286
|
+
@apply relative flex overflow-hidden;
|
|
1287
|
+
background: #0d1117;
|
|
1288
|
+
}
|
|
1289
|
+
|
|
1290
|
+
.rte-line-numbers {
|
|
1291
|
+
@apply shrink-0 py-4 text-right select-none overflow-hidden;
|
|
1292
|
+
width: 48px;
|
|
1293
|
+
font-family: 'JetBrains Mono', 'Fira Code', monospace;
|
|
1294
|
+
font-size: 0.8125rem;
|
|
1295
|
+
line-height: 1.625;
|
|
1296
|
+
color: #484f58;
|
|
1297
|
+
border-right: 1px solid #21262d;
|
|
1298
|
+
}
|
|
1299
|
+
|
|
1300
|
+
.rte-line-numbers :global(div) { padding-right: 12px; }
|
|
1301
|
+
|
|
1302
|
+
.rte-highlight-layer {
|
|
1303
|
+
@apply absolute top-0 right-0 bottom-0 p-4 whitespace-pre overflow-hidden pointer-events-none;
|
|
1304
|
+
left: 48px;
|
|
1305
|
+
font-family: 'JetBrains Mono', 'Fira Code', monospace;
|
|
1306
|
+
font-size: 0.8125rem;
|
|
1307
|
+
line-height: 1.625;
|
|
1308
|
+
color: #c9d1d9;
|
|
1309
|
+
}
|
|
1310
|
+
|
|
1311
|
+
.rte-source-textarea {
|
|
1312
|
+
@apply absolute top-0 right-0 bottom-0 p-4 m-0 border-none outline-none resize-none whitespace-pre overflow-auto;
|
|
1313
|
+
left: 48px;
|
|
1314
|
+
font-family: 'JetBrains Mono', 'Fira Code', monospace;
|
|
1315
|
+
font-size: 0.8125rem;
|
|
1316
|
+
line-height: 1.625;
|
|
1317
|
+
color: transparent;
|
|
1318
|
+
caret-color: #c9d1d9;
|
|
1319
|
+
background: transparent;
|
|
1320
|
+
-webkit-text-fill-color: transparent;
|
|
1321
|
+
}
|
|
1322
|
+
|
|
1323
|
+
.rte-highlight-layer :global(.hl-tag) { color: #7ee787; }
|
|
1324
|
+
.rte-highlight-layer :global(.hl-bracket) { color: #8b949e; }
|
|
1325
|
+
.rte-highlight-layer :global(.hl-attr) { color: #79c0ff; }
|
|
1326
|
+
.rte-highlight-layer :global(.hl-eq) { color: #8b949e; }
|
|
1327
|
+
.rte-highlight-layer :global(.hl-val) { color: #a5d6ff; }
|
|
1328
|
+
.rte-highlight-layer :global(.hl-comment) { color: #484f58; font-style: italic; }
|
|
1329
|
+
|
|
1330
|
+
/* ═══════════════════════════════════════════════
|
|
1331
|
+
CONTEXT MENU
|
|
1332
|
+
═══════════════════════════════════════════════ */
|
|
1333
|
+
.ctx-menu {
|
|
1334
|
+
@apply fixed z-[61] w-56 rounded-xl shadow-2xl shadow-black/30 backdrop-blur-xl overflow-hidden;
|
|
1335
|
+
border: 1px solid var(--karbon-border);
|
|
1336
|
+
background: var(--karbon-bg-card);
|
|
1337
|
+
}
|
|
1338
|
+
|
|
1339
|
+
.ctx-header {
|
|
1340
|
+
@apply px-3 py-2 text-[11px] font-semibold uppercase tracking-wider;
|
|
1341
|
+
border-bottom: 1px solid var(--karbon-border);
|
|
1342
|
+
background: color-mix(in srgb, var(--karbon-bg-2) 50%, transparent);
|
|
1343
|
+
color: var(--karbon-text-4);
|
|
1344
|
+
}
|
|
1345
|
+
|
|
1346
|
+
.ctx-item {
|
|
1347
|
+
@apply flex items-center gap-2 w-full text-left px-3 py-[7px] text-xs bg-transparent border-none cursor-pointer transition-all;
|
|
1348
|
+
color: var(--karbon-text-2);
|
|
1349
|
+
}
|
|
1350
|
+
|
|
1351
|
+
.ctx-item:hover {
|
|
1352
|
+
background: rgba(139, 92, 246, 0.08);
|
|
1353
|
+
color: var(--karbon-text);
|
|
1354
|
+
}
|
|
1355
|
+
|
|
1356
|
+
.ctx-danger { @apply text-red-400; }
|
|
1357
|
+
.ctx-danger:hover { @apply bg-red-500/10 text-red-500; }
|
|
1358
|
+
.ctx-warn { @apply text-amber-400; }
|
|
1359
|
+
.ctx-warn:hover { @apply bg-amber-500/10 text-amber-500; }
|
|
1360
|
+
|
|
1361
|
+
.ctx-divider {
|
|
1362
|
+
@apply py-1;
|
|
1363
|
+
border-top: 1px solid var(--karbon-border);
|
|
1364
|
+
}
|
|
1365
|
+
|
|
1366
|
+
.ctx-section-title {
|
|
1367
|
+
@apply px-3 py-1 text-[10px] font-semibold uppercase tracking-wider;
|
|
1368
|
+
color: var(--karbon-text-4);
|
|
1369
|
+
}
|
|
1370
|
+
|
|
1371
|
+
/* ═══════════════════════════════════════════════
|
|
1372
|
+
MODALS
|
|
1373
|
+
═══════════════════════════════════════════════ */
|
|
1374
|
+
.rte-overlay {
|
|
1375
|
+
@apply fixed inset-0 z-50 flex items-center justify-center bg-black/50 backdrop-blur-sm;
|
|
1376
|
+
}
|
|
1377
|
+
|
|
1378
|
+
.rte-modal {
|
|
1379
|
+
@apply w-full rounded-xl p-6 shadow-2xl;
|
|
1380
|
+
border: 1px solid var(--karbon-border);
|
|
1381
|
+
background: var(--karbon-bg-card);
|
|
1382
|
+
}
|
|
1383
|
+
|
|
1384
|
+
.rte-modal-header {
|
|
1385
|
+
@apply flex items-center justify-between mb-4;
|
|
1386
|
+
}
|
|
1387
|
+
|
|
1388
|
+
.rte-modal-header h3 {
|
|
1389
|
+
@apply text-sm font-semibold;
|
|
1390
|
+
color: var(--karbon-text);
|
|
1391
|
+
}
|
|
1392
|
+
|
|
1393
|
+
.rte-modal-close {
|
|
1394
|
+
@apply cursor-pointer bg-transparent border-none;
|
|
1395
|
+
color: var(--karbon-text-4);
|
|
1396
|
+
}
|
|
1397
|
+
|
|
1398
|
+
.rte-modal-close:hover {
|
|
1399
|
+
color: var(--karbon-text);
|
|
1400
|
+
}
|
|
1401
|
+
|
|
1402
|
+
.rte-modal-footer {
|
|
1403
|
+
@apply mt-5 flex items-center gap-2;
|
|
1404
|
+
}
|
|
1405
|
+
|
|
1406
|
+
.rte-input {
|
|
1407
|
+
@apply block w-full rounded-lg px-3 py-2 text-sm outline-none transition-colors;
|
|
1408
|
+
border: 1px solid var(--karbon-border-input);
|
|
1409
|
+
background: var(--karbon-bg-input);
|
|
1410
|
+
color: var(--karbon-text);
|
|
1411
|
+
}
|
|
1412
|
+
|
|
1413
|
+
.rte-input:focus {
|
|
1414
|
+
border-color: var(--karbon-border-input-focus);
|
|
1415
|
+
}
|
|
1416
|
+
|
|
1417
|
+
.rte-label {
|
|
1418
|
+
@apply block text-xs font-medium;
|
|
1419
|
+
color: var(--karbon-text-4);
|
|
1420
|
+
}
|
|
1421
|
+
|
|
1422
|
+
.rte-label-inline {
|
|
1423
|
+
@apply text-sm;
|
|
1424
|
+
color: var(--karbon-text-2);
|
|
1425
|
+
}
|
|
1426
|
+
|
|
1427
|
+
.rte-btn-primary {
|
|
1428
|
+
@apply rounded-lg bg-violet-600 px-3 py-1.5 text-xs font-medium text-white cursor-pointer;
|
|
1429
|
+
}
|
|
1430
|
+
|
|
1431
|
+
.rte-btn-primary:hover { @apply bg-violet-700; }
|
|
1432
|
+
.rte-btn-primary:disabled { @apply opacity-40 cursor-not-allowed; }
|
|
1433
|
+
|
|
1434
|
+
.rte-btn-cancel {
|
|
1435
|
+
@apply rounded-lg px-3 py-1.5 text-xs cursor-pointer;
|
|
1436
|
+
border: 1px solid var(--karbon-border);
|
|
1437
|
+
color: var(--karbon-text-3);
|
|
1438
|
+
}
|
|
1439
|
+
|
|
1440
|
+
.rte-btn-cancel:hover {
|
|
1441
|
+
background: var(--karbon-bg-2);
|
|
1442
|
+
}
|
|
1443
|
+
|
|
1444
|
+
.rte-btn-danger-text {
|
|
1445
|
+
@apply text-xs text-red-400 cursor-pointer bg-transparent border-none;
|
|
1446
|
+
}
|
|
1447
|
+
|
|
1448
|
+
.rte-btn-danger-text:hover { @apply text-red-300; }
|
|
1449
|
+
|
|
1450
|
+
.rte-upload-zone {
|
|
1451
|
+
@apply flex flex-col items-center justify-center rounded-lg border-2 border-dashed p-4 transition-colors;
|
|
1452
|
+
border-color: var(--karbon-border);
|
|
1453
|
+
}
|
|
1454
|
+
|
|
1455
|
+
.rte-upload-zone:hover {
|
|
1456
|
+
border-color: var(--karbon-border-input);
|
|
1457
|
+
}
|
|
1458
|
+
|
|
1459
|
+
.rte-preview-box {
|
|
1460
|
+
@apply rounded-lg overflow-hidden;
|
|
1461
|
+
border: 1px solid var(--karbon-border);
|
|
1462
|
+
background: var(--karbon-bg-2);
|
|
1463
|
+
}
|
|
1464
|
+
|
|
1465
|
+
.rte-align-btn {
|
|
1466
|
+
@apply rounded-md px-3 py-1.5 text-xs transition-colors cursor-pointer;
|
|
1467
|
+
color: var(--karbon-text-3);
|
|
1468
|
+
}
|
|
1469
|
+
|
|
1470
|
+
.rte-align-btn:hover {
|
|
1471
|
+
background: var(--karbon-bg-2);
|
|
1472
|
+
}
|
|
1473
|
+
|
|
1474
|
+
.rte-align-btn-active {
|
|
1475
|
+
@apply bg-violet-500/15 text-violet-400 ring-1 ring-violet-500/30;
|
|
1476
|
+
}
|
|
1477
|
+
|
|
1478
|
+
/* ═══════════════════════════════════════════════
|
|
1479
|
+
COLOR PICKER
|
|
1480
|
+
═══════════════════════════════════════════════ */
|
|
1481
|
+
.rte-color-grid {
|
|
1482
|
+
@apply absolute left-0 top-full z-20 mt-1 grid grid-cols-8 gap-1 rounded-lg p-2 shadow-xl;
|
|
1483
|
+
border: 1px solid var(--karbon-border);
|
|
1484
|
+
background: var(--karbon-bg-card);
|
|
1485
|
+
}
|
|
1486
|
+
|
|
1487
|
+
.rte-color-swatch {
|
|
1488
|
+
@apply h-5 w-5 rounded cursor-pointer transition-transform;
|
|
1489
|
+
border: 1px solid var(--karbon-border);
|
|
1490
|
+
}
|
|
1491
|
+
|
|
1492
|
+
.rte-color-swatch:hover { @apply scale-125; }
|
|
1493
|
+
|
|
1494
|
+
/* ═══════════════════════════════════════════════
|
|
1495
|
+
MEDIA EXPLORER
|
|
1496
|
+
═══════════════════════════════════════════════ */
|
|
1497
|
+
.rte-media-explorer {
|
|
1498
|
+
@apply flex flex-col rounded-2xl shadow-2xl overflow-hidden;
|
|
1499
|
+
height: 85vh;
|
|
1500
|
+
width: 90vw;
|
|
1501
|
+
max-width: 64rem;
|
|
1502
|
+
border: 1px solid var(--karbon-border);
|
|
1503
|
+
background: var(--karbon-bg-card);
|
|
1504
|
+
}
|
|
1505
|
+
|
|
1506
|
+
.rte-media-header {
|
|
1507
|
+
@apply flex items-center gap-3 px-5 py-3;
|
|
1508
|
+
border-bottom: 1px solid var(--karbon-border);
|
|
1509
|
+
color: var(--karbon-text);
|
|
1510
|
+
}
|
|
1511
|
+
|
|
1512
|
+
.rte-media-toolbar {
|
|
1513
|
+
@apply flex items-center gap-2 px-5 py-2;
|
|
1514
|
+
border-bottom: 1px solid var(--karbon-border);
|
|
1515
|
+
background: color-mix(in srgb, var(--karbon-bg-2) 50%, transparent);
|
|
1516
|
+
}
|
|
1517
|
+
|
|
1518
|
+
.rte-media-breadcrumb {
|
|
1519
|
+
@apply flex items-center gap-1 text-xs ml-2;
|
|
1520
|
+
color: var(--karbon-text-4);
|
|
1521
|
+
}
|
|
1522
|
+
|
|
1523
|
+
.rte-media-action-btn {
|
|
1524
|
+
@apply flex items-center gap-1.5 rounded-md px-2.5 py-1.5 text-xs font-medium cursor-pointer bg-transparent border-none transition-colors;
|
|
1525
|
+
color: var(--karbon-text-3);
|
|
1526
|
+
}
|
|
1527
|
+
|
|
1528
|
+
.rte-media-action-btn:hover {
|
|
1529
|
+
background: var(--karbon-bg-2);
|
|
1530
|
+
}
|
|
1531
|
+
|
|
1532
|
+
.rte-media-upload-btn {
|
|
1533
|
+
@apply flex items-center gap-1.5 rounded-md px-2.5 py-1.5 text-xs font-medium text-violet-400 cursor-pointer transition-colors;
|
|
1534
|
+
background: rgba(139, 92, 246, 0.1);
|
|
1535
|
+
}
|
|
1536
|
+
|
|
1537
|
+
.rte-media-upload-btn:hover {
|
|
1538
|
+
background: rgba(139, 92, 246, 0.2);
|
|
1539
|
+
}
|
|
1540
|
+
|
|
1541
|
+
.rte-media-view-toggle {
|
|
1542
|
+
@apply flex items-center rounded-lg overflow-hidden;
|
|
1543
|
+
border: 1px solid var(--karbon-border-input);
|
|
1544
|
+
}
|
|
1545
|
+
|
|
1546
|
+
.rte-media-newfolder {
|
|
1547
|
+
@apply flex items-center gap-2 px-5 py-2;
|
|
1548
|
+
border-bottom: 1px solid var(--karbon-border);
|
|
1549
|
+
background: rgba(139, 92, 246, 0.05);
|
|
1550
|
+
}
|
|
1551
|
+
|
|
1552
|
+
.rte-media-content {
|
|
1553
|
+
@apply relative flex-1 overflow-y-auto p-4;
|
|
1554
|
+
}
|
|
1555
|
+
|
|
1556
|
+
.rte-media-overlay {
|
|
1557
|
+
@apply absolute inset-0 z-10 flex flex-col items-center justify-center backdrop-blur-sm;
|
|
1558
|
+
background: color-mix(in srgb, var(--karbon-bg-card) 80%, transparent);
|
|
1559
|
+
}
|
|
1560
|
+
|
|
1561
|
+
.rte-media-footer {
|
|
1562
|
+
@apply flex items-center gap-3 px-5 py-3;
|
|
1563
|
+
border-top: 1px solid var(--karbon-border);
|
|
1564
|
+
background: color-mix(in srgb, var(--karbon-bg-2) 50%, transparent);
|
|
1565
|
+
}
|
|
1566
|
+
|
|
1567
|
+
.rte-media-item {
|
|
1568
|
+
@apply flex flex-col items-center rounded-lg p-2 cursor-pointer transition-all;
|
|
1569
|
+
}
|
|
1570
|
+
|
|
1571
|
+
.rte-media-item:hover {
|
|
1572
|
+
background: var(--karbon-bg-2);
|
|
1573
|
+
}
|
|
1574
|
+
|
|
1575
|
+
.rte-media-item-selected {
|
|
1576
|
+
@apply bg-violet-500/15 ring-1 ring-violet-500/40;
|
|
1577
|
+
}
|
|
1578
|
+
|
|
1579
|
+
.rte-media-thumb {
|
|
1580
|
+
@apply h-16 w-16 overflow-hidden rounded-md;
|
|
1581
|
+
background: var(--karbon-bg-2);
|
|
1582
|
+
}
|
|
1583
|
+
|
|
1584
|
+
.rte-media-list {
|
|
1585
|
+
@apply rounded-lg overflow-hidden;
|
|
1586
|
+
border: 1px solid var(--karbon-border);
|
|
1587
|
+
}
|
|
1588
|
+
|
|
1589
|
+
.rte-media-list-item {
|
|
1590
|
+
@apply flex items-center gap-3 px-4 py-2 cursor-pointer transition-colors;
|
|
1591
|
+
}
|
|
1592
|
+
|
|
1593
|
+
.rte-media-list-item:hover {
|
|
1594
|
+
background: var(--karbon-bg-2);
|
|
1595
|
+
}
|
|
1596
|
+
|
|
1597
|
+
.rte-media-list-divider {
|
|
1598
|
+
border-top: 1px solid var(--karbon-border);
|
|
1599
|
+
}
|
|
1600
|
+
|
|
1601
|
+
.rte-media-list-thumb {
|
|
1602
|
+
@apply h-8 w-8 shrink-0 rounded object-cover;
|
|
1603
|
+
background: var(--karbon-bg-2);
|
|
1604
|
+
}
|
|
1605
|
+
</style>
|