gavaengine 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,2716 @@
1
+ "use client";
2
+ import {
3
+ useGavaActions,
4
+ useGavaConfig,
5
+ useSplash
6
+ } from "./chunk-GGD7I4JO.js";
7
+
8
+ // src/components/editor/ArticleEditor.tsx
9
+ import { useState as useState6, useEffect as useEffect4, useCallback as useCallback4, useRef as useRef3 } from "react";
10
+ import { useRouter } from "next/navigation";
11
+ import { useEditor, EditorContent } from "@tiptap/react";
12
+ import StarterKit from "@tiptap/starter-kit";
13
+
14
+ // src/components/editor/extensions/ResizableImageExtension.ts
15
+ import { Node, mergeAttributes } from "@tiptap/core";
16
+ import { ReactNodeViewRenderer } from "@tiptap/react";
17
+
18
+ // src/components/editor/extensions/ResizableImageView.tsx
19
+ import { NodeViewWrapper } from "@tiptap/react";
20
+ import { useCallback, useRef, useState } from "react";
21
+ import { AlignLeft, AlignCenter, AlignRight, Trash2 } from "lucide-react";
22
+ import { Fragment, jsx, jsxs } from "react/jsx-runtime";
23
+ function ResizableImageView({
24
+ node,
25
+ updateAttributes,
26
+ selected,
27
+ deleteNode
28
+ }) {
29
+ const { src, alt, width, textWrap } = node.attrs;
30
+ const wrapperRef = useRef(null);
31
+ const [resizing, setResizing] = useState(false);
32
+ const onResizeStart = useCallback(
33
+ (e, handle) => {
34
+ e.preventDefault();
35
+ e.stopPropagation();
36
+ setResizing(true);
37
+ const startX = e.clientX;
38
+ const container = wrapperRef.current?.closest(
39
+ ".article-content, .tiptap"
40
+ );
41
+ if (!container) return;
42
+ const containerWidth = container.getBoundingClientRect().width;
43
+ const startWidthPx = wrapperRef.current.getBoundingClientRect().width;
44
+ const isLeft = handle === "nw" || handle === "sw";
45
+ const onMove = (ev) => {
46
+ const rawDelta = ev.clientX - startX;
47
+ const delta = isLeft ? -rawDelta : rawDelta;
48
+ const newPx = Math.max(startWidthPx + delta, containerWidth * 0.1);
49
+ const pct = Math.min(newPx / containerWidth * 100, 100);
50
+ updateAttributes({ width: `${Math.round(pct)}%` });
51
+ };
52
+ const onUp = () => {
53
+ setResizing(false);
54
+ document.removeEventListener("mousemove", onMove);
55
+ document.removeEventListener("mouseup", onUp);
56
+ };
57
+ document.addEventListener("mousemove", onMove);
58
+ document.addEventListener("mouseup", onUp);
59
+ },
60
+ [updateAttributes]
61
+ );
62
+ const setWrap = useCallback(
63
+ (mode) => {
64
+ if (mode === "none") {
65
+ updateAttributes({ textWrap: "none", width: "100%" });
66
+ } else {
67
+ const pct = parseInt(width, 10);
68
+ updateAttributes({
69
+ textWrap: mode,
70
+ width: pct > 60 ? "40%" : width
71
+ });
72
+ }
73
+ },
74
+ [updateAttributes, width]
75
+ );
76
+ return /* @__PURE__ */ jsxs(
77
+ NodeViewWrapper,
78
+ {
79
+ className: ["image-node", selected && "selected", resizing && "resizing"].filter(Boolean).join(" "),
80
+ "data-text-wrap": textWrap,
81
+ style: { width },
82
+ ref: wrapperRef,
83
+ children: [
84
+ /* @__PURE__ */ jsx(
85
+ "img",
86
+ {
87
+ src,
88
+ alt: alt || "",
89
+ draggable: false,
90
+ "data-drag-handle": true,
91
+ className: "image-node__img"
92
+ }
93
+ ),
94
+ selected && /* @__PURE__ */ jsxs(Fragment, { children: [
95
+ ["nw", "ne", "sw", "se"].map((h) => /* @__PURE__ */ jsx(
96
+ "span",
97
+ {
98
+ className: `image-node__handle image-node__handle--${h}`,
99
+ onMouseDown: (e) => onResizeStart(e, h),
100
+ contentEditable: false
101
+ },
102
+ h
103
+ )),
104
+ !resizing && /* @__PURE__ */ jsxs(
105
+ "div",
106
+ {
107
+ className: "image-node__toolbar",
108
+ contentEditable: false,
109
+ onMouseDown: (e) => e.stopPropagation(),
110
+ children: [
111
+ /* @__PURE__ */ jsx(
112
+ "button",
113
+ {
114
+ type: "button",
115
+ onClick: () => setWrap("left"),
116
+ className: textWrap === "left" ? "active" : "",
117
+ title: "Text right",
118
+ children: /* @__PURE__ */ jsx(AlignLeft, { className: "w-4 h-4" })
119
+ }
120
+ ),
121
+ /* @__PURE__ */ jsx(
122
+ "button",
123
+ {
124
+ type: "button",
125
+ onClick: () => setWrap("none"),
126
+ className: textWrap === "none" ? "active" : "",
127
+ title: "Full width",
128
+ children: /* @__PURE__ */ jsx(AlignCenter, { className: "w-4 h-4" })
129
+ }
130
+ ),
131
+ /* @__PURE__ */ jsx(
132
+ "button",
133
+ {
134
+ type: "button",
135
+ onClick: () => setWrap("right"),
136
+ className: textWrap === "right" ? "active" : "",
137
+ title: "Text left",
138
+ children: /* @__PURE__ */ jsx(AlignRight, { className: "w-4 h-4" })
139
+ }
140
+ ),
141
+ /* @__PURE__ */ jsx("span", { className: "image-node__toolbar-sep" }),
142
+ /* @__PURE__ */ jsx(
143
+ "button",
144
+ {
145
+ type: "button",
146
+ onClick: () => deleteNode(),
147
+ className: "delete",
148
+ title: "Remove",
149
+ children: /* @__PURE__ */ jsx(Trash2, { className: "w-4 h-4" })
150
+ }
151
+ )
152
+ ]
153
+ }
154
+ )
155
+ ] })
156
+ ]
157
+ }
158
+ );
159
+ }
160
+
161
+ // src/components/editor/extensions/ResizableImageExtension.ts
162
+ function figureAlignToWrap(figure) {
163
+ if (figure.classList.contains("align-left")) return "left";
164
+ if (figure.classList.contains("align-right")) return "right";
165
+ return "none";
166
+ }
167
+ function figureWidth(figure) {
168
+ return figure.style.maxWidth || figure.style.width || "100%";
169
+ }
170
+ var ResizableImage = Node.create({
171
+ name: "image",
172
+ group: "block",
173
+ draggable: true,
174
+ atom: true,
175
+ addOptions() {
176
+ return { HTMLAttributes: {} };
177
+ },
178
+ addAttributes() {
179
+ return {
180
+ src: { default: null },
181
+ alt: { default: null },
182
+ title: { default: null },
183
+ width: {
184
+ default: "100%",
185
+ parseHTML: (el) => {
186
+ const figure = el.closest("figure");
187
+ if (figure) return figureWidth(figure);
188
+ return el.style.width || "100%";
189
+ },
190
+ renderHTML: (attrs) => ({
191
+ style: `width: ${attrs.width}`
192
+ })
193
+ },
194
+ textWrap: {
195
+ default: "none",
196
+ parseHTML: (el) => {
197
+ const figure = el.closest("figure");
198
+ if (figure) return figureAlignToWrap(figure);
199
+ return el.getAttribute("data-text-wrap") || el.getAttribute("data-float") || "none";
200
+ },
201
+ renderHTML: (attrs) => ({
202
+ "data-text-wrap": attrs.textWrap
203
+ })
204
+ }
205
+ };
206
+ },
207
+ parseHTML() {
208
+ return [
209
+ { tag: "figure img[src]" },
210
+ { tag: "img[src]" }
211
+ ];
212
+ },
213
+ renderHTML({ HTMLAttributes }) {
214
+ return [
215
+ "img",
216
+ mergeAttributes(this.options.HTMLAttributes, HTMLAttributes)
217
+ ];
218
+ },
219
+ addCommands() {
220
+ return {
221
+ setImage: (options) => ({ commands }) => commands.insertContent({ type: this.name, attrs: options })
222
+ };
223
+ },
224
+ addNodeView() {
225
+ return ReactNodeViewRenderer(ResizableImageView);
226
+ }
227
+ });
228
+
229
+ // src/components/editor/ArticleEditor.tsx
230
+ import LinkExt from "@tiptap/extension-link";
231
+
232
+ // src/components/editor/extensions/DraggableYoutubeExtension.ts
233
+ import Youtube from "@tiptap/extension-youtube";
234
+ import { ReactNodeViewRenderer as ReactNodeViewRenderer2 } from "@tiptap/react";
235
+
236
+ // src/components/editor/extensions/DraggableYoutubeView.tsx
237
+ import { NodeViewWrapper as NodeViewWrapper2 } from "@tiptap/react";
238
+ import { jsx as jsx2 } from "react/jsx-runtime";
239
+ function toEmbedUrl(url) {
240
+ if (!url) return "";
241
+ if (url.includes("/embed/")) return url;
242
+ const shortMatch = url.match(/youtu\.be\/([\w-]+)/);
243
+ if (shortMatch) return `https://www.youtube.com/embed/${shortMatch[1]}`;
244
+ const match = url.match(/(?:v=|shorts\/)([\w-]+)/);
245
+ if (match) return `https://www.youtube.com/embed/${match[1]}`;
246
+ return url;
247
+ }
248
+ function DraggableYoutubeView({ node, selected }) {
249
+ const { src, width, height } = node.attrs;
250
+ const embedUrl = toEmbedUrl(src ?? "");
251
+ if (!embedUrl) return /* @__PURE__ */ jsx2(NodeViewWrapper2, {});
252
+ return /* @__PURE__ */ jsx2(
253
+ NodeViewWrapper2,
254
+ {
255
+ className: `draggable-video-wrapper${selected ? " selected" : ""}`,
256
+ "data-drag-handle": true,
257
+ children: /* @__PURE__ */ jsx2(
258
+ "iframe",
259
+ {
260
+ src: embedUrl,
261
+ width: width || 640,
262
+ height: height || 360,
263
+ allowFullScreen: true,
264
+ allow: "accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture",
265
+ style: { pointerEvents: selected ? "none" : "auto" }
266
+ }
267
+ )
268
+ }
269
+ );
270
+ }
271
+
272
+ // src/components/editor/extensions/DraggableYoutubeExtension.ts
273
+ var DraggableYoutube = Youtube.extend({
274
+ draggable: true,
275
+ addNodeView() {
276
+ return ReactNodeViewRenderer2(DraggableYoutubeView);
277
+ }
278
+ });
279
+
280
+ // src/components/editor/ArticleEditor.tsx
281
+ import Placeholder from "@tiptap/extension-placeholder";
282
+
283
+ // src/components/editor/extensions/VideoExtension.ts
284
+ import { Node as Node2, mergeAttributes as mergeAttributes2 } from "@tiptap/core";
285
+ import { ReactNodeViewRenderer as ReactNodeViewRenderer3 } from "@tiptap/react";
286
+
287
+ // src/components/editor/extensions/VideoView.tsx
288
+ import { NodeViewWrapper as NodeViewWrapper3 } from "@tiptap/react";
289
+ import { jsx as jsx3 } from "react/jsx-runtime";
290
+ function VideoView({ node, selected }) {
291
+ const { src } = node.attrs;
292
+ if (!src) return /* @__PURE__ */ jsx3(NodeViewWrapper3, {});
293
+ return /* @__PURE__ */ jsx3(
294
+ NodeViewWrapper3,
295
+ {
296
+ className: `article-video draggable-video-wrapper${selected ? " selected" : ""}`,
297
+ "data-drag-handle": true,
298
+ children: /* @__PURE__ */ jsx3(
299
+ "video",
300
+ {
301
+ src,
302
+ controls: true,
303
+ style: { pointerEvents: selected ? "none" : "auto" }
304
+ }
305
+ )
306
+ }
307
+ );
308
+ }
309
+
310
+ // src/components/editor/extensions/VideoExtension.ts
311
+ var VideoExtension = Node2.create({
312
+ name: "videoBlock",
313
+ group: "block",
314
+ atom: true,
315
+ draggable: true,
316
+ addAttributes() {
317
+ return {
318
+ src: { default: null }
319
+ };
320
+ },
321
+ parseHTML() {
322
+ return [{ tag: "video[src]" }];
323
+ },
324
+ renderHTML({ HTMLAttributes }) {
325
+ return [
326
+ "div",
327
+ { class: "article-video" },
328
+ ["video", mergeAttributes2(HTMLAttributes, { controls: "true" })]
329
+ ];
330
+ },
331
+ addCommands() {
332
+ return {
333
+ setVideo: (options) => ({ commands }) => {
334
+ return commands.insertContent({
335
+ type: this.name,
336
+ attrs: options
337
+ });
338
+ }
339
+ };
340
+ },
341
+ addNodeView() {
342
+ return ReactNodeViewRenderer3(VideoView);
343
+ }
344
+ });
345
+
346
+ // src/components/editor/ArticleEditor.tsx
347
+ import UnderlineExt from "@tiptap/extension-underline";
348
+ import TextAlign from "@tiptap/extension-text-align";
349
+ import Highlight from "@tiptap/extension-highlight";
350
+ import { Color } from "@tiptap/extension-color";
351
+ import { TextStyle } from "@tiptap/extension-text-style";
352
+ import Superscript2 from "@tiptap/extension-superscript";
353
+ import Subscript2 from "@tiptap/extension-subscript";
354
+ import { Table as Table2 } from "@tiptap/extension-table";
355
+ import { TableRow } from "@tiptap/extension-table-row";
356
+ import { TableCell } from "@tiptap/extension-table-cell";
357
+ import { TableHeader } from "@tiptap/extension-table-header";
358
+ import {
359
+ ArrowLeft,
360
+ Check,
361
+ Loader2 as Loader22,
362
+ Settings,
363
+ ChevronRight,
364
+ Globe,
365
+ FileText,
366
+ History,
367
+ Eye
368
+ } from "lucide-react";
369
+
370
+ // src/components/editor/EditorToolbar.tsx
371
+ import {
372
+ Bold,
373
+ Italic,
374
+ Underline,
375
+ Strikethrough,
376
+ Superscript,
377
+ Subscript,
378
+ Heading2,
379
+ Heading3,
380
+ List,
381
+ ListOrdered,
382
+ Quote,
383
+ Minus,
384
+ Code,
385
+ AlignLeft as AlignLeft2,
386
+ AlignCenter as AlignCenter2,
387
+ AlignRight as AlignRight2,
388
+ Link,
389
+ Image as Image2,
390
+ Youtube as Youtube2,
391
+ Video,
392
+ Undo2,
393
+ Redo2,
394
+ Highlighter,
395
+ Palette,
396
+ Table,
397
+ Plus,
398
+ X as X2
399
+ } from "lucide-react";
400
+ import { useCallback as useCallback3, useState as useState3, useRef as useRef2, useEffect as useEffect2 } from "react";
401
+
402
+ // src/components/media/MediaPickerModal.tsx
403
+ import { useState as useState2, useEffect, useCallback as useCallback2 } from "react";
404
+ import { X, Search, Upload, Loader2, ImageIcon } from "lucide-react";
405
+ import { Fragment as Fragment2, jsx as jsx4, jsxs as jsxs2 } from "react/jsx-runtime";
406
+ function formatSize(bytes) {
407
+ if (bytes < 1024) return `${bytes} B`;
408
+ if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`;
409
+ return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;
410
+ }
411
+ function MediaPickerModal({
412
+ open,
413
+ onClose,
414
+ onSelect
415
+ }) {
416
+ const actions = useGavaActions();
417
+ const { strings, upload: uploadConfig } = useGavaConfig();
418
+ const [tab, setTab] = useState2("library");
419
+ const [media, setMedia] = useState2([]);
420
+ const [loading, setLoading] = useState2(false);
421
+ const [search, setSearch] = useState2("");
422
+ const [uploading, setUploading] = useState2(false);
423
+ const [dragOver, setDragOver] = useState2(false);
424
+ const loadMedia = useCallback2(async () => {
425
+ setLoading(true);
426
+ try {
427
+ const data = await actions.getMedia();
428
+ setMedia(data);
429
+ } finally {
430
+ setLoading(false);
431
+ }
432
+ }, [actions]);
433
+ useEffect(() => {
434
+ if (open) {
435
+ loadMedia();
436
+ setSearch("");
437
+ setTab("library");
438
+ }
439
+ }, [open, loadMedia]);
440
+ useEffect(() => {
441
+ if (!open) return;
442
+ const handleKey = (e) => {
443
+ if (e.key === "Escape") onClose();
444
+ };
445
+ document.addEventListener("keydown", handleKey);
446
+ return () => document.removeEventListener("keydown", handleKey);
447
+ }, [open, onClose]);
448
+ const uploadFile = useCallback2(
449
+ async (file) => {
450
+ setUploading(true);
451
+ try {
452
+ const formData = new FormData();
453
+ formData.append("file", file);
454
+ const res = await fetch(actions.uploadUrl, {
455
+ method: "POST",
456
+ body: formData
457
+ });
458
+ const data = await res.json();
459
+ if (data.url) {
460
+ onSelect(data.url);
461
+ }
462
+ } finally {
463
+ setUploading(false);
464
+ }
465
+ },
466
+ [onSelect, actions.uploadUrl]
467
+ );
468
+ const handleFileSelect = useCallback2(() => {
469
+ const input = document.createElement("input");
470
+ input.type = "file";
471
+ input.accept = uploadConfig.imageTypes.join(",");
472
+ input.onchange = () => {
473
+ const file = input.files?.[0];
474
+ if (file) uploadFile(file);
475
+ };
476
+ input.click();
477
+ }, [uploadFile, uploadConfig.imageTypes]);
478
+ const handleDrop = useCallback2(
479
+ (e) => {
480
+ e.preventDefault();
481
+ setDragOver(false);
482
+ const file = e.dataTransfer.files[0];
483
+ if (file && file.type.startsWith("image/")) {
484
+ uploadFile(file);
485
+ }
486
+ },
487
+ [uploadFile]
488
+ );
489
+ if (!open) return null;
490
+ const images = media.filter((m) => m.mimeType.startsWith("image/"));
491
+ const filtered = images.filter(
492
+ (m) => search ? m.filename.toLowerCase().includes(search.toLowerCase()) : true
493
+ );
494
+ return /* @__PURE__ */ jsxs2("div", { className: "fixed inset-0 z-50 flex items-center justify-center", children: [
495
+ /* @__PURE__ */ jsx4("div", { className: "absolute inset-0 bg-black/50", onClick: onClose }),
496
+ /* @__PURE__ */ jsxs2("div", { className: "relative w-full max-w-3xl max-h-[85vh] mx-4 bg-card border border-card-border rounded-2xl shadow-xl flex flex-col dash-scale-in", children: [
497
+ /* @__PURE__ */ jsxs2("div", { className: "flex items-center justify-between px-5 py-4 border-b border-card-border", children: [
498
+ /* @__PURE__ */ jsx4("h2", { className: "text-lg font-semibold text-foreground", children: strings.selectImage }),
499
+ /* @__PURE__ */ jsx4(
500
+ "button",
501
+ {
502
+ onClick: onClose,
503
+ className: "p-1.5 rounded-full text-muted hover:bg-card-border/50 hover:text-foreground transition-colors",
504
+ children: /* @__PURE__ */ jsx4(X, { className: "w-5 h-5" })
505
+ }
506
+ )
507
+ ] }),
508
+ /* @__PURE__ */ jsxs2("div", { className: "flex border-b border-card-border px-5", children: [
509
+ /* @__PURE__ */ jsx4(
510
+ "button",
511
+ {
512
+ onClick: () => setTab("library"),
513
+ className: `px-4 py-2.5 text-sm font-medium border-b-2 transition-colors -mb-px ${tab === "library" ? "border-accent text-accent" : "border-transparent text-muted hover:text-foreground"}`,
514
+ children: strings.mediaLibrary
515
+ }
516
+ ),
517
+ /* @__PURE__ */ jsx4(
518
+ "button",
519
+ {
520
+ onClick: () => setTab("upload"),
521
+ className: `px-4 py-2.5 text-sm font-medium border-b-2 transition-colors -mb-px ${tab === "upload" ? "border-accent text-accent" : "border-transparent text-muted hover:text-foreground"}`,
522
+ children: strings.uploadNew
523
+ }
524
+ )
525
+ ] }),
526
+ /* @__PURE__ */ jsx4("div", { className: "flex-1 overflow-y-auto p-5", children: tab === "library" ? /* @__PURE__ */ jsxs2(Fragment2, { children: [
527
+ /* @__PURE__ */ jsxs2("div", { className: "relative mb-4", children: [
528
+ /* @__PURE__ */ jsx4(Search, { className: "absolute left-3 top-1/2 -translate-y-1/2 w-4 h-4 text-muted" }),
529
+ /* @__PURE__ */ jsx4(
530
+ "input",
531
+ {
532
+ type: "text",
533
+ value: search,
534
+ onChange: (e) => setSearch(e.target.value),
535
+ placeholder: strings.searchFiles,
536
+ className: "w-full pl-9 pr-3 py-2 text-sm bg-background border border-card-border rounded-full outline-none focus:border-accent transition-colors"
537
+ }
538
+ )
539
+ ] }),
540
+ loading ? /* @__PURE__ */ jsx4("div", { className: "flex items-center justify-center py-16", children: /* @__PURE__ */ jsx4(Loader2, { className: "w-6 h-6 text-muted animate-spin" }) }) : filtered.length === 0 ? /* @__PURE__ */ jsxs2("div", { className: "text-center py-16", children: [
541
+ /* @__PURE__ */ jsx4(ImageIcon, { className: "w-10 h-10 text-muted mx-auto mb-3" }),
542
+ /* @__PURE__ */ jsx4("p", { className: "text-sm text-muted", children: search ? strings.noImagesSearch : strings.noImages })
543
+ ] }) : /* @__PURE__ */ jsx4("div", { className: "grid grid-cols-3 sm:grid-cols-4 md:grid-cols-5 gap-3", children: filtered.map((m) => /* @__PURE__ */ jsxs2(
544
+ "button",
545
+ {
546
+ type: "button",
547
+ onClick: () => onSelect(m.path),
548
+ className: "group relative aspect-square rounded-lg overflow-hidden border border-card-border hover:border-accent transition-colors focus:outline-none focus:ring-2 focus:ring-accent",
549
+ children: [
550
+ /* @__PURE__ */ jsx4(
551
+ "img",
552
+ {
553
+ src: m.path,
554
+ alt: m.filename,
555
+ className: "w-full h-full object-cover"
556
+ }
557
+ ),
558
+ /* @__PURE__ */ jsx4("div", { className: "absolute inset-0 bg-black/0 group-hover:bg-black/30 transition-colors" }),
559
+ /* @__PURE__ */ jsxs2("div", { className: "absolute bottom-0 left-0 right-0 bg-gradient-to-t from-black/70 to-transparent p-2 opacity-0 group-hover:opacity-100 transition-opacity", children: [
560
+ /* @__PURE__ */ jsx4("p", { className: "text-[11px] text-white truncate", children: m.filename }),
561
+ /* @__PURE__ */ jsx4("p", { className: "text-[10px] text-white/70", children: formatSize(m.size) })
562
+ ] })
563
+ ]
564
+ },
565
+ m.id
566
+ )) })
567
+ ] }) : /* @__PURE__ */ jsx4(
568
+ "div",
569
+ {
570
+ onDragOver: (e) => {
571
+ e.preventDefault();
572
+ setDragOver(true);
573
+ },
574
+ onDragLeave: () => setDragOver(false),
575
+ onDrop: handleDrop,
576
+ className: `flex flex-col items-center justify-center py-20 rounded-xl border-2 border-dashed transition-colors ${dragOver ? "border-accent bg-accent/5" : "border-card-border"}`,
577
+ children: uploading ? /* @__PURE__ */ jsx4(Loader2, { className: "w-10 h-10 text-muted animate-spin" }) : /* @__PURE__ */ jsxs2(Fragment2, { children: [
578
+ /* @__PURE__ */ jsx4(Upload, { className: "w-10 h-10 text-muted mb-4" }),
579
+ /* @__PURE__ */ jsx4("p", { className: "text-sm text-muted mb-4", children: strings.dragHint }),
580
+ /* @__PURE__ */ jsx4(
581
+ "button",
582
+ {
583
+ type: "button",
584
+ onClick: handleFileSelect,
585
+ className: "px-5 py-2 text-sm font-medium bg-accent text-white rounded-full hover:bg-accent/90 transition-colors",
586
+ children: strings.chooseFile
587
+ }
588
+ ),
589
+ /* @__PURE__ */ jsx4("p", { className: "text-xs text-muted mt-3", children: strings.uploadHint })
590
+ ] })
591
+ }
592
+ ) })
593
+ ] })
594
+ ] });
595
+ }
596
+
597
+ // src/components/editor/EditorToolbar.tsx
598
+ import { Fragment as Fragment3, jsx as jsx5, jsxs as jsxs3 } from "react/jsx-runtime";
599
+ var TEXT_COLORS = [
600
+ { label: "Black", value: "#171717" },
601
+ { label: "Red", value: "#dc2626" },
602
+ { label: "Orange", value: "#ea580c" },
603
+ { label: "Green", value: "#16a34a" },
604
+ { label: "Blue", value: "#2563eb" },
605
+ { label: "Purple", value: "#9333ea" },
606
+ { label: "Gray", value: "#6b7280" }
607
+ ];
608
+ var HIGHLIGHT_COLORS = [
609
+ { label: "Yellow", value: "#fef08a" },
610
+ { label: "Green", value: "#bbf7d0" },
611
+ { label: "Blue", value: "#bfdbfe" },
612
+ { label: "Pink", value: "#fbcfe8" },
613
+ { label: "Orange", value: "#fed7aa" },
614
+ { label: "Purple", value: "#e9d5ff" }
615
+ ];
616
+ function EditorToolbar({ editor }) {
617
+ const actions = useGavaActions();
618
+ const [showColorPicker, setShowColorPicker] = useState3(false);
619
+ const [showHighlightPicker, setShowHighlightPicker] = useState3(false);
620
+ const [showTableMenu, setShowTableMenu] = useState3(false);
621
+ const [mediaPickerOpen, setMediaPickerOpen] = useState3(false);
622
+ const colorRef = useRef2(null);
623
+ const highlightRef = useRef2(null);
624
+ const tableRef = useRef2(null);
625
+ useEffect2(() => {
626
+ const handleClick = (e) => {
627
+ if (colorRef.current && !colorRef.current.contains(e.target))
628
+ setShowColorPicker(false);
629
+ if (highlightRef.current && !highlightRef.current.contains(e.target))
630
+ setShowHighlightPicker(false);
631
+ if (tableRef.current && !tableRef.current.contains(e.target))
632
+ setShowTableMenu(false);
633
+ };
634
+ document.addEventListener("mousedown", handleClick);
635
+ return () => document.removeEventListener("mousedown", handleClick);
636
+ }, []);
637
+ const handleImageSelect = useCallback3(
638
+ (url) => {
639
+ if (!editor) return;
640
+ editor.chain().focus().setImage({ src: url }).run();
641
+ setMediaPickerOpen(false);
642
+ },
643
+ [editor]
644
+ );
645
+ const addLink = useCallback3(() => {
646
+ if (!editor) return;
647
+ const previousUrl = editor.getAttributes("link").href;
648
+ const url = window.prompt("URL:", previousUrl);
649
+ if (url === null) return;
650
+ if (url === "") {
651
+ editor.chain().focus().extendMarkRange("link").unsetLink().run();
652
+ return;
653
+ }
654
+ editor.chain().focus().extendMarkRange("link").setLink({ href: url }).run();
655
+ }, [editor]);
656
+ const addYoutube = useCallback3(() => {
657
+ if (!editor) return;
658
+ const url = window.prompt("YouTube URL:");
659
+ if (!url) return;
660
+ editor.commands.setYoutubeVideo({ src: url, width: 640, height: 360 });
661
+ }, [editor]);
662
+ const addVideo = useCallback3(() => {
663
+ if (!editor) return;
664
+ const input = document.createElement("input");
665
+ input.type = "file";
666
+ input.accept = "video/mp4,video/webm,video/quicktime";
667
+ input.onchange = async () => {
668
+ const file = input.files?.[0];
669
+ if (!file) return;
670
+ const formData = new FormData();
671
+ formData.append("file", file);
672
+ const res = await fetch(actions.uploadUrl, {
673
+ method: "POST",
674
+ body: formData
675
+ });
676
+ const data = await res.json();
677
+ if (data.url) {
678
+ editor.commands.setVideo({ src: data.url });
679
+ } else if (data.error) {
680
+ alert(data.error);
681
+ }
682
+ };
683
+ input.click();
684
+ }, [editor, actions.uploadUrl]);
685
+ if (!editor) return null;
686
+ const ToolButton = ({
687
+ onClick,
688
+ active,
689
+ children,
690
+ title
691
+ }) => /* @__PURE__ */ jsx5(
692
+ "button",
693
+ {
694
+ type: "button",
695
+ onClick,
696
+ title,
697
+ className: `p-1.5 rounded-full transition-colors ${active ? "bg-accent/15 text-accent" : "text-muted hover:bg-card-border/50 hover:text-foreground"}`,
698
+ children
699
+ }
700
+ );
701
+ const Divider = () => /* @__PURE__ */ jsx5("div", { className: "w-px h-6 bg-card-border mx-1" });
702
+ const DropdownItem = ({
703
+ onClick,
704
+ children,
705
+ danger
706
+ }) => /* @__PURE__ */ jsx5(
707
+ "button",
708
+ {
709
+ onClick,
710
+ className: `w-full text-left px-3 py-1.5 text-sm transition-colors ${danger ? "text-red-500 hover:bg-red-50" : "text-foreground hover:bg-card-border/50"}`,
711
+ children
712
+ }
713
+ );
714
+ return /* @__PURE__ */ jsxs3("div", { className: "inline-flex items-center flex-wrap gap-0.5 p-2 border border-card-border rounded-full bg-card", children: [
715
+ /* @__PURE__ */ jsx5(
716
+ ToolButton,
717
+ {
718
+ onClick: () => editor.chain().focus().toggleBold().run(),
719
+ active: editor.isActive("bold"),
720
+ title: "Bold",
721
+ children: /* @__PURE__ */ jsx5(Bold, { className: "w-4 h-4" })
722
+ }
723
+ ),
724
+ /* @__PURE__ */ jsx5(
725
+ ToolButton,
726
+ {
727
+ onClick: () => editor.chain().focus().toggleItalic().run(),
728
+ active: editor.isActive("italic"),
729
+ title: "Italic",
730
+ children: /* @__PURE__ */ jsx5(Italic, { className: "w-4 h-4" })
731
+ }
732
+ ),
733
+ /* @__PURE__ */ jsx5(
734
+ ToolButton,
735
+ {
736
+ onClick: () => editor.chain().focus().toggleUnderline().run(),
737
+ active: editor.isActive("underline"),
738
+ title: "Underline",
739
+ children: /* @__PURE__ */ jsx5(Underline, { className: "w-4 h-4" })
740
+ }
741
+ ),
742
+ /* @__PURE__ */ jsx5(
743
+ ToolButton,
744
+ {
745
+ onClick: () => editor.chain().focus().toggleStrike().run(),
746
+ active: editor.isActive("strike"),
747
+ title: "Strikethrough",
748
+ children: /* @__PURE__ */ jsx5(Strikethrough, { className: "w-4 h-4" })
749
+ }
750
+ ),
751
+ /* @__PURE__ */ jsx5(
752
+ ToolButton,
753
+ {
754
+ onClick: () => editor.chain().focus().toggleSuperscript().run(),
755
+ active: editor.isActive("superscript"),
756
+ title: "Superscript",
757
+ children: /* @__PURE__ */ jsx5(Superscript, { className: "w-4 h-4" })
758
+ }
759
+ ),
760
+ /* @__PURE__ */ jsx5(
761
+ ToolButton,
762
+ {
763
+ onClick: () => editor.chain().focus().toggleSubscript().run(),
764
+ active: editor.isActive("subscript"),
765
+ title: "Subscript",
766
+ children: /* @__PURE__ */ jsx5(Subscript, { className: "w-4 h-4" })
767
+ }
768
+ ),
769
+ /* @__PURE__ */ jsx5(Divider, {}),
770
+ /* @__PURE__ */ jsxs3("div", { className: "relative", ref: colorRef, children: [
771
+ /* @__PURE__ */ jsx5(
772
+ ToolButton,
773
+ {
774
+ onClick: () => {
775
+ setShowColorPicker(!showColorPicker);
776
+ setShowHighlightPicker(false);
777
+ setShowTableMenu(false);
778
+ },
779
+ active: showColorPicker,
780
+ title: "Text color",
781
+ children: /* @__PURE__ */ jsxs3("div", { className: "flex flex-col items-center gap-0.5", children: [
782
+ /* @__PURE__ */ jsx5(Palette, { className: "w-4 h-4" }),
783
+ /* @__PURE__ */ jsx5(
784
+ "div",
785
+ {
786
+ className: "w-3.5 h-0.5 rounded-full",
787
+ style: {
788
+ background: editor.getAttributes("textStyle").color || "currentColor"
789
+ }
790
+ }
791
+ )
792
+ ] })
793
+ }
794
+ ),
795
+ showColorPicker && /* @__PURE__ */ jsx5("div", { className: "absolute top-full left-0 mt-1 bg-card border border-card-border rounded-lg p-2 shadow-lg z-30", children: /* @__PURE__ */ jsxs3("div", { className: "grid grid-cols-4 gap-1.5 w-[132px]", children: [
796
+ /* @__PURE__ */ jsx5(
797
+ "button",
798
+ {
799
+ onClick: () => {
800
+ editor.chain().focus().unsetColor().run();
801
+ setShowColorPicker(false);
802
+ },
803
+ className: "w-7 h-7 rounded-md border border-card-border hover:scale-110 transition-transform flex items-center justify-center bg-background",
804
+ title: "Default",
805
+ children: /* @__PURE__ */ jsx5(X2, { className: "w-3 h-3 text-muted" })
806
+ }
807
+ ),
808
+ TEXT_COLORS.map((c) => /* @__PURE__ */ jsx5(
809
+ "button",
810
+ {
811
+ onClick: () => {
812
+ editor.chain().focus().setColor(c.value).run();
813
+ setShowColorPicker(false);
814
+ },
815
+ className: "w-7 h-7 rounded-md border border-card-border hover:scale-110 transition-transform",
816
+ style: { background: c.value },
817
+ title: c.label
818
+ },
819
+ c.value
820
+ ))
821
+ ] }) })
822
+ ] }),
823
+ /* @__PURE__ */ jsxs3("div", { className: "relative", ref: highlightRef, children: [
824
+ /* @__PURE__ */ jsx5(
825
+ ToolButton,
826
+ {
827
+ onClick: () => {
828
+ setShowHighlightPicker(!showHighlightPicker);
829
+ setShowColorPicker(false);
830
+ setShowTableMenu(false);
831
+ },
832
+ active: editor.isActive("highlight"),
833
+ title: "Highlight",
834
+ children: /* @__PURE__ */ jsx5(Highlighter, { className: "w-4 h-4" })
835
+ }
836
+ ),
837
+ showHighlightPicker && /* @__PURE__ */ jsx5("div", { className: "absolute top-full left-0 mt-1 bg-card border border-card-border rounded-lg p-2 shadow-lg z-30", children: /* @__PURE__ */ jsxs3("div", { className: "grid grid-cols-4 gap-1.5 w-[132px]", children: [
838
+ /* @__PURE__ */ jsx5(
839
+ "button",
840
+ {
841
+ onClick: () => {
842
+ editor.chain().focus().unsetHighlight().run();
843
+ setShowHighlightPicker(false);
844
+ },
845
+ className: "w-7 h-7 rounded-md border border-card-border hover:scale-110 transition-transform flex items-center justify-center bg-background",
846
+ title: "Remove",
847
+ children: /* @__PURE__ */ jsx5(X2, { className: "w-3 h-3 text-muted" })
848
+ }
849
+ ),
850
+ HIGHLIGHT_COLORS.map((c) => /* @__PURE__ */ jsx5(
851
+ "button",
852
+ {
853
+ onClick: () => {
854
+ editor.chain().focus().toggleHighlight({ color: c.value }).run();
855
+ setShowHighlightPicker(false);
856
+ },
857
+ className: "w-7 h-7 rounded-md border border-card-border hover:scale-110 transition-transform",
858
+ style: { background: c.value },
859
+ title: c.label
860
+ },
861
+ c.value
862
+ ))
863
+ ] }) })
864
+ ] }),
865
+ /* @__PURE__ */ jsx5(Divider, {}),
866
+ /* @__PURE__ */ jsx5(
867
+ ToolButton,
868
+ {
869
+ onClick: () => editor.chain().focus().toggleHeading({ level: 2 }).run(),
870
+ active: editor.isActive("heading", { level: 2 }),
871
+ title: "H2",
872
+ children: /* @__PURE__ */ jsx5(Heading2, { className: "w-4 h-4" })
873
+ }
874
+ ),
875
+ /* @__PURE__ */ jsx5(
876
+ ToolButton,
877
+ {
878
+ onClick: () => editor.chain().focus().toggleHeading({ level: 3 }).run(),
879
+ active: editor.isActive("heading", { level: 3 }),
880
+ title: "H3",
881
+ children: /* @__PURE__ */ jsx5(Heading3, { className: "w-4 h-4" })
882
+ }
883
+ ),
884
+ /* @__PURE__ */ jsx5(Divider, {}),
885
+ /* @__PURE__ */ jsx5(
886
+ ToolButton,
887
+ {
888
+ onClick: () => editor.chain().focus().toggleBulletList().run(),
889
+ active: editor.isActive("bulletList"),
890
+ title: "Bullet list",
891
+ children: /* @__PURE__ */ jsx5(List, { className: "w-4 h-4" })
892
+ }
893
+ ),
894
+ /* @__PURE__ */ jsx5(
895
+ ToolButton,
896
+ {
897
+ onClick: () => editor.chain().focus().toggleOrderedList().run(),
898
+ active: editor.isActive("orderedList"),
899
+ title: "Numbered list",
900
+ children: /* @__PURE__ */ jsx5(ListOrdered, { className: "w-4 h-4" })
901
+ }
902
+ ),
903
+ /* @__PURE__ */ jsx5(
904
+ ToolButton,
905
+ {
906
+ onClick: () => editor.chain().focus().toggleBlockquote().run(),
907
+ active: editor.isActive("blockquote"),
908
+ title: "Quote",
909
+ children: /* @__PURE__ */ jsx5(Quote, { className: "w-4 h-4" })
910
+ }
911
+ ),
912
+ /* @__PURE__ */ jsx5(
913
+ ToolButton,
914
+ {
915
+ onClick: () => editor.chain().focus().toggleCodeBlock().run(),
916
+ active: editor.isActive("codeBlock"),
917
+ title: "Code block",
918
+ children: /* @__PURE__ */ jsx5(Code, { className: "w-4 h-4" })
919
+ }
920
+ ),
921
+ /* @__PURE__ */ jsx5(
922
+ ToolButton,
923
+ {
924
+ onClick: () => editor.chain().focus().setHorizontalRule().run(),
925
+ title: "Divider",
926
+ children: /* @__PURE__ */ jsx5(Minus, { className: "w-4 h-4" })
927
+ }
928
+ ),
929
+ /* @__PURE__ */ jsx5(Divider, {}),
930
+ /* @__PURE__ */ jsx5(
931
+ ToolButton,
932
+ {
933
+ onClick: () => editor.chain().focus().setTextAlign("left").run(),
934
+ active: editor.isActive({ textAlign: "left" }),
935
+ title: "Align left",
936
+ children: /* @__PURE__ */ jsx5(AlignLeft2, { className: "w-4 h-4" })
937
+ }
938
+ ),
939
+ /* @__PURE__ */ jsx5(
940
+ ToolButton,
941
+ {
942
+ onClick: () => editor.chain().focus().setTextAlign("center").run(),
943
+ active: editor.isActive({ textAlign: "center" }),
944
+ title: "Align center",
945
+ children: /* @__PURE__ */ jsx5(AlignCenter2, { className: "w-4 h-4" })
946
+ }
947
+ ),
948
+ /* @__PURE__ */ jsx5(
949
+ ToolButton,
950
+ {
951
+ onClick: () => editor.chain().focus().setTextAlign("right").run(),
952
+ active: editor.isActive({ textAlign: "right" }),
953
+ title: "Align right",
954
+ children: /* @__PURE__ */ jsx5(AlignRight2, { className: "w-4 h-4" })
955
+ }
956
+ ),
957
+ /* @__PURE__ */ jsx5(Divider, {}),
958
+ /* @__PURE__ */ jsxs3("div", { className: "relative", ref: tableRef, children: [
959
+ /* @__PURE__ */ jsx5(
960
+ ToolButton,
961
+ {
962
+ onClick: () => {
963
+ setShowTableMenu(!showTableMenu);
964
+ setShowColorPicker(false);
965
+ setShowHighlightPicker(false);
966
+ },
967
+ active: editor.isActive("table"),
968
+ title: "Table",
969
+ children: /* @__PURE__ */ jsx5(Table, { className: "w-4 h-4" })
970
+ }
971
+ ),
972
+ showTableMenu && /* @__PURE__ */ jsx5("div", { className: "absolute top-full left-0 mt-1 bg-card border border-card-border rounded-lg py-1 shadow-lg z-30 w-52", children: !editor.isActive("table") ? /* @__PURE__ */ jsx5(
973
+ DropdownItem,
974
+ {
975
+ onClick: () => {
976
+ editor.chain().focus().insertTable({ rows: 3, cols: 3, withHeaderRow: true }).run();
977
+ setShowTableMenu(false);
978
+ },
979
+ children: /* @__PURE__ */ jsxs3("span", { className: "flex items-center gap-2", children: [
980
+ /* @__PURE__ */ jsx5(Plus, { className: "w-3.5 h-3.5" }),
981
+ "Insert 3x3 table"
982
+ ] })
983
+ }
984
+ ) : /* @__PURE__ */ jsxs3(Fragment3, { children: [
985
+ /* @__PURE__ */ jsx5(
986
+ DropdownItem,
987
+ {
988
+ onClick: () => {
989
+ editor.chain().focus().addRowBefore().run();
990
+ setShowTableMenu(false);
991
+ },
992
+ children: "Add row above"
993
+ }
994
+ ),
995
+ /* @__PURE__ */ jsx5(
996
+ DropdownItem,
997
+ {
998
+ onClick: () => {
999
+ editor.chain().focus().addRowAfter().run();
1000
+ setShowTableMenu(false);
1001
+ },
1002
+ children: "Add row below"
1003
+ }
1004
+ ),
1005
+ /* @__PURE__ */ jsx5(
1006
+ DropdownItem,
1007
+ {
1008
+ onClick: () => {
1009
+ editor.chain().focus().addColumnBefore().run();
1010
+ setShowTableMenu(false);
1011
+ },
1012
+ children: "Add column left"
1013
+ }
1014
+ ),
1015
+ /* @__PURE__ */ jsx5(
1016
+ DropdownItem,
1017
+ {
1018
+ onClick: () => {
1019
+ editor.chain().focus().addColumnAfter().run();
1020
+ setShowTableMenu(false);
1021
+ },
1022
+ children: "Add column right"
1023
+ }
1024
+ ),
1025
+ /* @__PURE__ */ jsx5("div", { className: "border-t border-card-border my-1" }),
1026
+ /* @__PURE__ */ jsx5(
1027
+ DropdownItem,
1028
+ {
1029
+ danger: true,
1030
+ onClick: () => {
1031
+ editor.chain().focus().deleteRow().run();
1032
+ setShowTableMenu(false);
1033
+ },
1034
+ children: "Delete row"
1035
+ }
1036
+ ),
1037
+ /* @__PURE__ */ jsx5(
1038
+ DropdownItem,
1039
+ {
1040
+ danger: true,
1041
+ onClick: () => {
1042
+ editor.chain().focus().deleteColumn().run();
1043
+ setShowTableMenu(false);
1044
+ },
1045
+ children: "Delete column"
1046
+ }
1047
+ ),
1048
+ /* @__PURE__ */ jsx5(
1049
+ DropdownItem,
1050
+ {
1051
+ danger: true,
1052
+ onClick: () => {
1053
+ editor.chain().focus().deleteTable().run();
1054
+ setShowTableMenu(false);
1055
+ },
1056
+ children: "Delete table"
1057
+ }
1058
+ )
1059
+ ] }) })
1060
+ ] }),
1061
+ /* @__PURE__ */ jsx5(Divider, {}),
1062
+ /* @__PURE__ */ jsx5(ToolButton, { onClick: addLink, active: editor.isActive("link"), title: "Link", children: /* @__PURE__ */ jsx5(Link, { className: "w-4 h-4" }) }),
1063
+ /* @__PURE__ */ jsx5(ToolButton, { onClick: () => setMediaPickerOpen(true), title: "Image", children: /* @__PURE__ */ jsx5(Image2, { className: "w-4 h-4" }) }),
1064
+ /* @__PURE__ */ jsx5(ToolButton, { onClick: addYoutube, title: "YouTube", children: /* @__PURE__ */ jsx5(Youtube2, { className: "w-4 h-4" }) }),
1065
+ /* @__PURE__ */ jsx5(ToolButton, { onClick: addVideo, title: "Video", children: /* @__PURE__ */ jsx5(Video, { className: "w-4 h-4" }) }),
1066
+ /* @__PURE__ */ jsx5(Divider, {}),
1067
+ /* @__PURE__ */ jsx5(
1068
+ ToolButton,
1069
+ {
1070
+ onClick: () => editor.chain().focus().undo().run(),
1071
+ title: "Undo",
1072
+ children: /* @__PURE__ */ jsx5(Undo2, { className: "w-4 h-4" })
1073
+ }
1074
+ ),
1075
+ /* @__PURE__ */ jsx5(
1076
+ ToolButton,
1077
+ {
1078
+ onClick: () => editor.chain().focus().redo().run(),
1079
+ title: "Redo",
1080
+ children: /* @__PURE__ */ jsx5(Redo2, { className: "w-4 h-4" })
1081
+ }
1082
+ ),
1083
+ /* @__PURE__ */ jsx5(
1084
+ MediaPickerModal,
1085
+ {
1086
+ open: mediaPickerOpen,
1087
+ onClose: () => setMediaPickerOpen(false),
1088
+ onSelect: handleImageSelect
1089
+ }
1090
+ )
1091
+ ] });
1092
+ }
1093
+
1094
+ // src/components/editor/CoverImageUpload.tsx
1095
+ import { useState as useState4 } from "react";
1096
+ import { ImagePlus, X as X3 } from "lucide-react";
1097
+ import { Fragment as Fragment4, jsx as jsx6, jsxs as jsxs4 } from "react/jsx-runtime";
1098
+ function CoverImageUpload({ value, onChange }) {
1099
+ const [pickerOpen, setPickerOpen] = useState4(false);
1100
+ const { strings } = useGavaConfig();
1101
+ const handleSelect = (url) => {
1102
+ onChange(url);
1103
+ setPickerOpen(false);
1104
+ };
1105
+ if (value) {
1106
+ return /* @__PURE__ */ jsxs4(Fragment4, { children: [
1107
+ /* @__PURE__ */ jsxs4("div", { className: "relative aspect-[2/1] w-full rounded-xl overflow-hidden group cursor-pointer", children: [
1108
+ /* @__PURE__ */ jsx6(
1109
+ "img",
1110
+ {
1111
+ src: value,
1112
+ alt: strings.coverImageLabel,
1113
+ className: "w-full h-full object-cover"
1114
+ }
1115
+ ),
1116
+ /* @__PURE__ */ jsx6("div", { className: "absolute inset-0 bg-black/0 group-hover:bg-black/30 transition-colors flex items-center justify-center", children: /* @__PURE__ */ jsxs4("div", { className: "opacity-0 group-hover:opacity-100 transition-opacity flex gap-2", children: [
1117
+ /* @__PURE__ */ jsx6(
1118
+ "button",
1119
+ {
1120
+ type: "button",
1121
+ onClick: (e) => {
1122
+ e.stopPropagation();
1123
+ setPickerOpen(true);
1124
+ },
1125
+ className: "bg-white text-gray-900 px-4 py-2 rounded-full text-sm font-medium hover:bg-gray-100 transition-colors",
1126
+ children: strings.change
1127
+ }
1128
+ ),
1129
+ /* @__PURE__ */ jsx6(
1130
+ "button",
1131
+ {
1132
+ type: "button",
1133
+ onClick: (e) => {
1134
+ e.stopPropagation();
1135
+ onChange("");
1136
+ },
1137
+ className: "bg-red-500 text-white p-2 rounded-full hover:bg-red-600 transition-colors",
1138
+ children: /* @__PURE__ */ jsx6(X3, { className: "w-4 h-4" })
1139
+ }
1140
+ )
1141
+ ] }) })
1142
+ ] }),
1143
+ /* @__PURE__ */ jsx6(
1144
+ MediaPickerModal,
1145
+ {
1146
+ open: pickerOpen,
1147
+ onClose: () => setPickerOpen(false),
1148
+ onSelect: handleSelect
1149
+ }
1150
+ )
1151
+ ] });
1152
+ }
1153
+ return /* @__PURE__ */ jsxs4(Fragment4, { children: [
1154
+ /* @__PURE__ */ jsxs4(
1155
+ "button",
1156
+ {
1157
+ type: "button",
1158
+ onClick: () => setPickerOpen(true),
1159
+ className: "w-full aspect-[2/1] rounded-xl border-2 border-dashed transition-colors flex flex-col items-center justify-center gap-3 border-card-border hover:border-muted",
1160
+ children: [
1161
+ /* @__PURE__ */ jsx6(ImagePlus, { className: "w-8 h-8 text-muted" }),
1162
+ /* @__PURE__ */ jsx6("div", { className: "text-sm text-muted", children: /* @__PURE__ */ jsx6("span", { className: "font-medium text-foreground", children: strings.coverImageLabel }) }),
1163
+ /* @__PURE__ */ jsx6("span", { className: "text-xs text-muted", children: strings.coverImageHint })
1164
+ ]
1165
+ }
1166
+ ),
1167
+ /* @__PURE__ */ jsx6(
1168
+ MediaPickerModal,
1169
+ {
1170
+ open: pickerOpen,
1171
+ onClose: () => setPickerOpen(false),
1172
+ onSelect: handleSelect
1173
+ }
1174
+ )
1175
+ ] });
1176
+ }
1177
+
1178
+ // src/components/editor/RevisionPanel.tsx
1179
+ import { useState as useState5, useEffect as useEffect3 } from "react";
1180
+ import { X as X4, RotateCcw, Clock } from "lucide-react";
1181
+ import { jsx as jsx7, jsxs as jsxs5 } from "react/jsx-runtime";
1182
+ function RevisionPanel({
1183
+ articleId,
1184
+ onClose,
1185
+ onRestore
1186
+ }) {
1187
+ const actions = useGavaActions();
1188
+ const { strings } = useGavaConfig();
1189
+ const [revisions, setRevisions] = useState5([]);
1190
+ const [loading, setLoading] = useState5(true);
1191
+ const [restoring, setRestoring] = useState5(null);
1192
+ useEffect3(() => {
1193
+ loadRevisions();
1194
+ }, [articleId]);
1195
+ async function loadRevisions() {
1196
+ setLoading(true);
1197
+ try {
1198
+ const data = await actions.getRevisions(articleId);
1199
+ setRevisions(data);
1200
+ } catch {
1201
+ } finally {
1202
+ setLoading(false);
1203
+ }
1204
+ }
1205
+ async function handleRestore(revisionId) {
1206
+ if (!confirm(strings.restoreConfirm)) return;
1207
+ setRestoring(revisionId);
1208
+ try {
1209
+ await actions.restoreRevision(articleId, revisionId);
1210
+ onRestore();
1211
+ } catch (err) {
1212
+ alert(err instanceof Error ? err.message : "Error restoring revision");
1213
+ } finally {
1214
+ setRestoring(null);
1215
+ }
1216
+ }
1217
+ function formatDate(date) {
1218
+ return new Date(date).toLocaleString("it-IT", {
1219
+ day: "numeric",
1220
+ month: "short",
1221
+ hour: "2-digit",
1222
+ minute: "2-digit"
1223
+ });
1224
+ }
1225
+ return /* @__PURE__ */ jsxs5("div", { className: "w-80 border-l border-card-border bg-card overflow-auto p-5 hidden lg:block", children: [
1226
+ /* @__PURE__ */ jsxs5("div", { className: "flex items-center justify-between mb-4", children: [
1227
+ /* @__PURE__ */ jsxs5("h3", { className: "font-semibold text-foreground flex items-center gap-2", children: [
1228
+ /* @__PURE__ */ jsx7(Clock, { className: "w-4 h-4" }),
1229
+ strings.revisions
1230
+ ] }),
1231
+ /* @__PURE__ */ jsx7(
1232
+ "button",
1233
+ {
1234
+ onClick: onClose,
1235
+ className: "p-1 rounded-full text-muted hover:text-foreground hover:bg-card-border/50 transition-colors",
1236
+ children: /* @__PURE__ */ jsx7(X4, { className: "w-4 h-4" })
1237
+ }
1238
+ )
1239
+ ] }),
1240
+ loading ? /* @__PURE__ */ jsx7("p", { className: "text-sm text-muted", children: strings.loading }) : revisions.length === 0 ? /* @__PURE__ */ jsx7("p", { className: "text-sm text-muted", children: strings.noRevisions }) : /* @__PURE__ */ jsx7("div", { className: "space-y-3", children: revisions.map((rev) => /* @__PURE__ */ jsx7(
1241
+ "div",
1242
+ {
1243
+ className: "p-3 rounded-lg border border-card-border hover:border-accent/30 transition-colors",
1244
+ children: /* @__PURE__ */ jsxs5("div", { className: "flex items-start justify-between gap-2", children: [
1245
+ /* @__PURE__ */ jsxs5("div", { className: "min-w-0 flex-1", children: [
1246
+ /* @__PURE__ */ jsx7("p", { className: "text-sm font-medium text-foreground truncate", children: rev.title || strings.untitled }),
1247
+ /* @__PURE__ */ jsxs5("p", { className: "text-xs text-muted mt-0.5", children: [
1248
+ formatDate(rev.createdAt),
1249
+ " \u2014 ",
1250
+ rev.editor.name
1251
+ ] }),
1252
+ rev.note && /* @__PURE__ */ jsx7("p", { className: "text-xs text-accent mt-1", children: rev.note })
1253
+ ] }),
1254
+ /* @__PURE__ */ jsx7(
1255
+ "button",
1256
+ {
1257
+ onClick: () => handleRestore(rev.id),
1258
+ disabled: restoring === rev.id,
1259
+ className: "p-1.5 rounded-full text-muted hover:text-accent hover:bg-accent/10 transition-colors flex-shrink-0 disabled:opacity-50",
1260
+ title: strings.restore,
1261
+ children: /* @__PURE__ */ jsx7(
1262
+ RotateCcw,
1263
+ {
1264
+ className: `w-3.5 h-3.5 ${restoring === rev.id ? "animate-spin" : ""}`
1265
+ }
1266
+ )
1267
+ }
1268
+ )
1269
+ ] })
1270
+ },
1271
+ rev.id
1272
+ )) })
1273
+ ] });
1274
+ }
1275
+
1276
+ // src/components/editor/ArticleEditor.tsx
1277
+ import { Fragment as Fragment5, jsx as jsx8, jsxs as jsxs6 } from "react/jsx-runtime";
1278
+ function slugify(text) {
1279
+ return text.toLowerCase().normalize("NFD").replace(/[\u0300-\u036f]/g, "").replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "");
1280
+ }
1281
+ function ArticleEditor({ article, canPublish: canPub }) {
1282
+ const router = useRouter();
1283
+ const actions = useGavaActions();
1284
+ const config = useGavaConfig();
1285
+ const { strings, routes, categories, editor: editorConfig } = config;
1286
+ const [title, setTitle] = useState6(article.title);
1287
+ const [slug, setSlug] = useState6(article.slug);
1288
+ const [excerpt, setExcerpt] = useState6(article.excerpt);
1289
+ const [coverImage, setCoverImage] = useState6(article.coverImage);
1290
+ const [category, setCategory] = useState6(article.category);
1291
+ const [status, setStatus] = useState6(article.status);
1292
+ const [authorName, setAuthorName] = useState6(article.authorName || "");
1293
+ const [saveStatus, setSaveStatus] = useState6("idle");
1294
+ const [showSettings, setShowSettings] = useState6(false);
1295
+ const [showHistory, setShowHistory] = useState6(false);
1296
+ const [autoSlug, setAutoSlug] = useState6(!article.slug);
1297
+ const saveTimeout = useRef3(null);
1298
+ const pendingUpdate = useRef3(false);
1299
+ const mountedRef = useRef3(false);
1300
+ const scrollRef = useRef3(null);
1301
+ const [scrolled, setScrolled] = useState6(false);
1302
+ const [hasSpace, setHasSpace] = useState6(false);
1303
+ const spread = scrolled && hasSpace;
1304
+ useEffect4(() => {
1305
+ document.documentElement.classList.remove("page-exit-to-editor");
1306
+ document.body.style.overflow = "hidden";
1307
+ return () => {
1308
+ document.body.style.overflow = "";
1309
+ };
1310
+ }, []);
1311
+ useEffect4(() => {
1312
+ const check = () => setHasSpace(window.innerWidth >= 1420);
1313
+ check();
1314
+ window.addEventListener("resize", check);
1315
+ return () => window.removeEventListener("resize", check);
1316
+ }, []);
1317
+ const editor = useEditor({
1318
+ immediatelyRender: false,
1319
+ extensions: [
1320
+ StarterKit.configure({
1321
+ heading: { levels: editorConfig.headingLevels }
1322
+ }),
1323
+ ResizableImage,
1324
+ LinkExt.configure({
1325
+ openOnClick: false,
1326
+ HTMLAttributes: { target: "_blank", rel: "noopener noreferrer" }
1327
+ }),
1328
+ DraggableYoutube.configure({
1329
+ HTMLAttributes: { class: "article-video-embed" },
1330
+ width: 640,
1331
+ height: 360
1332
+ }),
1333
+ Placeholder.configure({
1334
+ placeholder: editorConfig.placeholder
1335
+ }),
1336
+ UnderlineExt,
1337
+ TextAlign.configure({
1338
+ types: ["heading", "paragraph"]
1339
+ }),
1340
+ Highlight.configure({ multicolor: true }),
1341
+ Color,
1342
+ TextStyle,
1343
+ Superscript2,
1344
+ Subscript2,
1345
+ Table2.configure({ resizable: true }),
1346
+ TableRow,
1347
+ TableCell,
1348
+ TableHeader,
1349
+ VideoExtension
1350
+ ],
1351
+ content: article.content || "",
1352
+ editorProps: {
1353
+ attributes: {
1354
+ class: "article-content outline-none min-h-[300px]"
1355
+ }
1356
+ },
1357
+ onUpdate: () => {
1358
+ scheduleSave();
1359
+ }
1360
+ });
1361
+ const doSave = useCallback4(
1362
+ async (fields) => {
1363
+ if (!fields && !editor) return;
1364
+ if (pendingUpdate.current) return;
1365
+ pendingUpdate.current = true;
1366
+ setSaveStatus("saving");
1367
+ try {
1368
+ const data = fields ?? {
1369
+ title,
1370
+ slug,
1371
+ excerpt,
1372
+ coverImage,
1373
+ category,
1374
+ content: editor?.getHTML() ?? "",
1375
+ authorName
1376
+ };
1377
+ await actions.updateArticle(article.id, data);
1378
+ setSaveStatus("saved");
1379
+ setTimeout(() => setSaveStatus("idle"), 2e3);
1380
+ } catch {
1381
+ setSaveStatus("error");
1382
+ } finally {
1383
+ pendingUpdate.current = false;
1384
+ }
1385
+ },
1386
+ [article.id, title, slug, excerpt, coverImage, category, editor, authorName, actions]
1387
+ );
1388
+ const scheduleSave = useCallback4(() => {
1389
+ if (saveTimeout.current) clearTimeout(saveTimeout.current);
1390
+ saveTimeout.current = setTimeout(() => doSave(), editorConfig.autoSaveDelay);
1391
+ }, [doSave, editorConfig.autoSaveDelay]);
1392
+ useEffect4(() => {
1393
+ const handleKeyDown = (e) => {
1394
+ if ((e.ctrlKey || e.metaKey) && e.key === "s") {
1395
+ e.preventDefault();
1396
+ if (saveTimeout.current) clearTimeout(saveTimeout.current);
1397
+ doSave();
1398
+ }
1399
+ };
1400
+ window.addEventListener("keydown", handleKeyDown);
1401
+ return () => window.removeEventListener("keydown", handleKeyDown);
1402
+ }, [doSave]);
1403
+ useEffect4(() => {
1404
+ if (!mountedRef.current) {
1405
+ mountedRef.current = true;
1406
+ return;
1407
+ }
1408
+ scheduleSave();
1409
+ }, [title, slug, excerpt, coverImage, category, authorName]);
1410
+ useEffect4(() => {
1411
+ const el = scrollRef.current;
1412
+ if (!el) return;
1413
+ const onScroll = () => setScrolled(el.scrollTop > 50);
1414
+ onScroll();
1415
+ el.addEventListener("scroll", onScroll, { passive: true });
1416
+ return () => el.removeEventListener("scroll", onScroll);
1417
+ }, []);
1418
+ useEffect4(() => {
1419
+ if (autoSlug && title) {
1420
+ setSlug(slugify(title));
1421
+ }
1422
+ }, [title, autoSlug]);
1423
+ const handlePublish = async () => {
1424
+ if (saveTimeout.current) clearTimeout(saveTimeout.current);
1425
+ await doSave();
1426
+ try {
1427
+ await actions.publishArticle(article.id);
1428
+ setStatus("pubblicato");
1429
+ router.refresh();
1430
+ } catch (err) {
1431
+ alert(err instanceof Error ? err.message : "Error publishing");
1432
+ }
1433
+ };
1434
+ const handleUnpublish = async () => {
1435
+ try {
1436
+ await actions.unpublishArticle(article.id);
1437
+ setStatus("bozza");
1438
+ router.refresh();
1439
+ } catch (err) {
1440
+ alert(err instanceof Error ? err.message : "Error");
1441
+ }
1442
+ };
1443
+ const handleSaveDraft = async () => {
1444
+ if (saveTimeout.current) clearTimeout(saveTimeout.current);
1445
+ await doSave();
1446
+ };
1447
+ return /* @__PURE__ */ jsxs6("div", { className: "-mx-4 sm:-mx-6 -my-6 lg:-my-8 h-[calc(100dvh-4.5rem)] flex flex-col bg-background overflow-hidden", children: [
1448
+ /* @__PURE__ */ jsx8(
1449
+ "div",
1450
+ {
1451
+ className: `z-20 px-4 py-3 pointer-events-none transition-all duration-500 ease-out ${spread ? "absolute top-[4.5rem] left-0 right-0" : "flex-shrink-0 relative"}`,
1452
+ children: /* @__PURE__ */ jsxs6(
1453
+ "div",
1454
+ {
1455
+ className: "flex items-center justify-between mx-auto transition-all duration-500 ease-out",
1456
+ style: { maxWidth: spread ? "100%" : "64rem" },
1457
+ children: [
1458
+ /* @__PURE__ */ jsx8(
1459
+ "div",
1460
+ {
1461
+ className: `flex items-center pointer-events-auto transition-all duration-500 ease-out ${spread ? "bg-background/90 backdrop-blur-md shadow-lg border border-card-border/50 rounded-full px-3 py-1.5" : ""}`,
1462
+ children: /* @__PURE__ */ jsxs6(
1463
+ "button",
1464
+ {
1465
+ onClick: () => router.push(routes.articles),
1466
+ className: "flex items-center gap-2 text-sm text-muted hover:text-foreground transition-colors",
1467
+ children: [
1468
+ /* @__PURE__ */ jsx8(ArrowLeft, { className: "w-4 h-4" }),
1469
+ /* @__PURE__ */ jsx8("span", { className: "hidden sm:inline", children: strings.articles })
1470
+ ]
1471
+ }
1472
+ )
1473
+ }
1474
+ ),
1475
+ /* @__PURE__ */ jsxs6(
1476
+ "div",
1477
+ {
1478
+ className: `flex items-center gap-2 text-sm text-muted transition-all duration-500 ${spread ? "opacity-0 scale-90 pointer-events-none" : "opacity-100 scale-100"}`,
1479
+ children: [
1480
+ saveStatus === "saving" && /* @__PURE__ */ jsxs6(Fragment5, { children: [
1481
+ /* @__PURE__ */ jsx8(Loader22, { className: "w-3.5 h-3.5 animate-spin" }),
1482
+ /* @__PURE__ */ jsx8("span", { children: strings.saving })
1483
+ ] }),
1484
+ saveStatus === "saved" && /* @__PURE__ */ jsxs6(Fragment5, { children: [
1485
+ /* @__PURE__ */ jsx8(Check, { className: "w-3.5 h-3.5 text-green-500" }),
1486
+ /* @__PURE__ */ jsx8("span", { className: "text-green-500", children: strings.saved })
1487
+ ] }),
1488
+ saveStatus === "error" && /* @__PURE__ */ jsx8("span", { className: "text-red-500", children: strings.saveError })
1489
+ ]
1490
+ }
1491
+ ),
1492
+ /* @__PURE__ */ jsxs6(
1493
+ "div",
1494
+ {
1495
+ className: `flex items-center gap-2 pointer-events-auto transition-all duration-500 ease-out ${spread ? "bg-background/90 backdrop-blur-md shadow-lg border border-card-border/50 rounded-full px-2 py-1" : ""}`,
1496
+ children: [
1497
+ /* @__PURE__ */ jsxs6(
1498
+ "div",
1499
+ {
1500
+ className: `flex items-center gap-1.5 text-sm text-muted overflow-hidden transition-all duration-300 ${spread && saveStatus !== "idle" ? "max-w-[30px] opacity-100 pl-1" : "max-w-0 opacity-0"}`,
1501
+ children: [
1502
+ saveStatus === "saving" && /* @__PURE__ */ jsx8(Loader22, { className: "w-3.5 h-3.5 animate-spin flex-shrink-0" }),
1503
+ saveStatus === "saved" && /* @__PURE__ */ jsx8(Check, { className: "w-3.5 h-3.5 text-green-500 flex-shrink-0" }),
1504
+ saveStatus === "error" && /* @__PURE__ */ jsx8("span", { className: "w-2 h-2 rounded-full bg-red-500 flex-shrink-0" })
1505
+ ]
1506
+ }
1507
+ ),
1508
+ /* @__PURE__ */ jsx8(
1509
+ "button",
1510
+ {
1511
+ onClick: () => window.open(
1512
+ status === "pubblicato" && slug ? routes.articleView(slug) : routes.articlePreview(article.id),
1513
+ "_blank"
1514
+ ),
1515
+ className: "p-2 rounded-full text-muted hover:text-foreground hover:bg-card-border/50 transition-colors",
1516
+ title: status === "pubblicato" ? strings.viewArticle : strings.preview,
1517
+ children: /* @__PURE__ */ jsx8(Eye, { className: "w-4 h-4" })
1518
+ }
1519
+ ),
1520
+ /* @__PURE__ */ jsx8(
1521
+ "button",
1522
+ {
1523
+ onClick: () => {
1524
+ setShowHistory(!showHistory);
1525
+ if (!showHistory) setShowSettings(false);
1526
+ },
1527
+ className: `p-2 rounded-full transition-colors ${showHistory ? "bg-accent/10 text-accent" : "text-muted hover:text-foreground hover:bg-card-border/50"}`,
1528
+ title: strings.revisions,
1529
+ children: /* @__PURE__ */ jsx8(History, { className: "w-4 h-4" })
1530
+ }
1531
+ ),
1532
+ /* @__PURE__ */ jsx8(
1533
+ "button",
1534
+ {
1535
+ onClick: () => {
1536
+ setShowSettings(!showSettings);
1537
+ if (!showSettings) setShowHistory(false);
1538
+ },
1539
+ className: `p-2 rounded-full transition-colors ${showSettings ? "bg-accent/10 text-accent" : "text-muted hover:text-foreground hover:bg-card-border/50"}`,
1540
+ title: strings.settings,
1541
+ children: /* @__PURE__ */ jsx8(Settings, { className: "w-4 h-4" })
1542
+ }
1543
+ ),
1544
+ /* @__PURE__ */ jsx8(
1545
+ "button",
1546
+ {
1547
+ onClick: handleSaveDraft,
1548
+ className: "px-3 py-1.5 text-sm border border-card-border/50 rounded-full hover:bg-card-border/50 transition-colors",
1549
+ children: strings.saveDraft
1550
+ }
1551
+ ),
1552
+ canPub && status === "bozza" && /* @__PURE__ */ jsxs6(
1553
+ "button",
1554
+ {
1555
+ onClick: handlePublish,
1556
+ className: "px-4 py-1.5 text-sm bg-accent text-white rounded-full hover:bg-accent-hover transition-colors font-medium flex items-center gap-1.5",
1557
+ children: [
1558
+ /* @__PURE__ */ jsx8(Globe, { className: "w-3.5 h-3.5" }),
1559
+ strings.publish
1560
+ ]
1561
+ }
1562
+ ),
1563
+ canPub && status === "pubblicato" && /* @__PURE__ */ jsx8(
1564
+ "button",
1565
+ {
1566
+ onClick: handleUnpublish,
1567
+ className: "px-3 py-1.5 text-sm border border-amber-500 text-amber-600 rounded-full hover:bg-amber-50 transition-colors",
1568
+ children: strings.unpublish
1569
+ }
1570
+ )
1571
+ ]
1572
+ }
1573
+ )
1574
+ ]
1575
+ }
1576
+ )
1577
+ }
1578
+ ),
1579
+ /* @__PURE__ */ jsxs6("div", { className: "flex-1 flex min-h-0", children: [
1580
+ /* @__PURE__ */ jsxs6("div", { className: "flex-1 overflow-y-auto", ref: scrollRef, children: [
1581
+ /* @__PURE__ */ jsxs6("div", { className: "article-content-wrapper py-8", children: [
1582
+ /* @__PURE__ */ jsx8("div", { className: "flex items-center gap-2 mb-6", children: status === "pubblicato" ? /* @__PURE__ */ jsxs6("span", { className: "inline-flex items-center gap-1.5 text-xs font-medium text-green-600 bg-green-50 px-2.5 py-1 rounded-full", children: [
1583
+ /* @__PURE__ */ jsx8(Globe, { className: "w-3 h-3" }),
1584
+ strings.published
1585
+ ] }) : /* @__PURE__ */ jsxs6("span", { className: "inline-flex items-center gap-1.5 text-xs font-medium text-amber-600 bg-amber-50 px-2.5 py-1 rounded-full", children: [
1586
+ /* @__PURE__ */ jsx8(FileText, { className: "w-3 h-3" }),
1587
+ strings.draft
1588
+ ] }) }),
1589
+ /* @__PURE__ */ jsx8(CoverImageUpload, { value: coverImage, onChange: setCoverImage }),
1590
+ /* @__PURE__ */ jsx8(
1591
+ "textarea",
1592
+ {
1593
+ ref: (el) => {
1594
+ if (el) {
1595
+ el.style.height = "auto";
1596
+ el.style.height = el.scrollHeight + "px";
1597
+ }
1598
+ },
1599
+ value: title,
1600
+ onChange: (e) => {
1601
+ setTitle(e.target.value);
1602
+ const el = e.target;
1603
+ el.style.height = "auto";
1604
+ el.style.height = el.scrollHeight + "px";
1605
+ },
1606
+ rows: 1,
1607
+ placeholder: strings.titlePlaceholder,
1608
+ className: "w-full mt-8 mb-6 text-4xl md:text-[2.75rem] lg:text-5xl font-heading font-bold text-foreground leading-[1.08] tracking-[-0.02em] bg-transparent border-none outline-none placeholder:text-muted-foreground/40 resize-none overflow-hidden"
1609
+ }
1610
+ )
1611
+ ] }),
1612
+ /* @__PURE__ */ jsx8("div", { className: "sticky top-0 z-10 py-2 flex justify-center px-4", children: /* @__PURE__ */ jsx8(EditorToolbar, { editor }) }),
1613
+ /* @__PURE__ */ jsx8("div", { className: "article-content-wrapper pb-8", children: /* @__PURE__ */ jsx8(EditorContent, { editor }) })
1614
+ ] }),
1615
+ showHistory && /* @__PURE__ */ jsx8(
1616
+ RevisionPanel,
1617
+ {
1618
+ articleId: article.id,
1619
+ onClose: () => setShowHistory(false),
1620
+ onRestore: () => router.refresh()
1621
+ }
1622
+ ),
1623
+ showSettings && /* @__PURE__ */ jsxs6("div", { className: "w-80 border-l border-card-border bg-card overflow-auto p-5 hidden lg:block", children: [
1624
+ /* @__PURE__ */ jsxs6("h3", { className: "font-semibold text-foreground mb-4 flex items-center gap-2", children: [
1625
+ /* @__PURE__ */ jsx8(Settings, { className: "w-4 h-4" }),
1626
+ strings.settings
1627
+ ] }),
1628
+ /* @__PURE__ */ jsxs6("div", { className: "space-y-5", children: [
1629
+ /* @__PURE__ */ jsxs6("div", { children: [
1630
+ /* @__PURE__ */ jsx8("label", { className: "block text-sm font-medium text-foreground mb-1.5", children: strings.slugUrl }),
1631
+ /* @__PURE__ */ jsx8("div", { className: "flex items-center gap-2", children: /* @__PURE__ */ jsx8(
1632
+ "input",
1633
+ {
1634
+ type: "text",
1635
+ value: slug,
1636
+ onChange: (e) => {
1637
+ setAutoSlug(false);
1638
+ setSlug(e.target.value);
1639
+ },
1640
+ placeholder: strings.slugPlaceholder,
1641
+ className: "flex-1 px-3 py-2 text-sm bg-background border border-card-border rounded-lg outline-none focus:border-accent"
1642
+ }
1643
+ ) }),
1644
+ /* @__PURE__ */ jsx8("p", { className: "text-xs text-muted mt-1", children: routes.articleView(slug || "...") })
1645
+ ] }),
1646
+ /* @__PURE__ */ jsxs6("div", { children: [
1647
+ /* @__PURE__ */ jsx8("label", { className: "block text-sm font-medium text-foreground mb-1.5", children: strings.category }),
1648
+ /* @__PURE__ */ jsxs6(
1649
+ "select",
1650
+ {
1651
+ value: category,
1652
+ onChange: (e) => setCategory(e.target.value),
1653
+ className: "w-full px-3 py-2 text-sm bg-background border border-card-border rounded-lg outline-none focus:border-accent",
1654
+ children: [
1655
+ /* @__PURE__ */ jsx8("option", { value: "", children: strings.selectCategory }),
1656
+ categories.map((cat) => /* @__PURE__ */ jsx8("option", { value: cat, children: cat }, cat))
1657
+ ]
1658
+ }
1659
+ )
1660
+ ] }),
1661
+ /* @__PURE__ */ jsxs6("div", { children: [
1662
+ /* @__PURE__ */ jsx8("label", { className: "block text-sm font-medium text-foreground mb-1.5", children: strings.excerpt }),
1663
+ /* @__PURE__ */ jsx8(
1664
+ "textarea",
1665
+ {
1666
+ value: excerpt,
1667
+ onChange: (e) => setExcerpt(e.target.value),
1668
+ placeholder: strings.excerptPlaceholder,
1669
+ rows: 3,
1670
+ className: "w-full px-3 py-2 text-sm bg-background border border-card-border rounded-lg outline-none focus:border-accent resize-none"
1671
+ }
1672
+ )
1673
+ ] }),
1674
+ /* @__PURE__ */ jsx8("div", { className: "pt-4 border-t border-card-border", children: /* @__PURE__ */ jsxs6("div", { children: [
1675
+ /* @__PURE__ */ jsx8("label", { className: "block text-sm font-medium text-foreground mb-1.5", children: strings.author }),
1676
+ /* @__PURE__ */ jsx8(
1677
+ "input",
1678
+ {
1679
+ type: "text",
1680
+ value: authorName,
1681
+ onChange: (e) => setAuthorName(e.target.value),
1682
+ placeholder: strings.authorPlaceholder,
1683
+ className: "w-full px-3 py-2 text-sm bg-background border border-card-border rounded-lg outline-none focus:border-accent"
1684
+ }
1685
+ )
1686
+ ] }) }),
1687
+ status === "pubblicato" && slug && /* @__PURE__ */ jsx8("div", { children: /* @__PURE__ */ jsxs6(
1688
+ "a",
1689
+ {
1690
+ href: routes.articleView(slug),
1691
+ target: "_blank",
1692
+ rel: "noopener noreferrer",
1693
+ className: "flex items-center gap-2 text-sm text-accent hover:underline",
1694
+ children: [
1695
+ strings.viewArticle,
1696
+ /* @__PURE__ */ jsx8(ChevronRight, { className: "w-3.5 h-3.5" })
1697
+ ]
1698
+ }
1699
+ ) })
1700
+ ] })
1701
+ ] })
1702
+ ] })
1703
+ ] });
1704
+ }
1705
+
1706
+ // src/components/editor/ImageEditModal.tsx
1707
+ import { useState as useState7, useCallback as useCallback5 } from "react";
1708
+ import Cropper from "react-easy-crop";
1709
+ import {
1710
+ X as X5,
1711
+ ZoomIn,
1712
+ ZoomOut,
1713
+ Check as Check2,
1714
+ RectangleHorizontal,
1715
+ Square,
1716
+ Smartphone,
1717
+ Maximize,
1718
+ Loader2 as Loader23,
1719
+ RotateCcw as RotateCcw2
1720
+ } from "lucide-react";
1721
+ import { jsx as jsx9, jsxs as jsxs7 } from "react/jsx-runtime";
1722
+ var ASPECT_PRESETS = [
1723
+ { label: "Free", value: void 0, icon: Maximize },
1724
+ { label: "16:9", value: 16 / 9, icon: RectangleHorizontal },
1725
+ { label: "4:3", value: 4 / 3, icon: Smartphone },
1726
+ { label: "1:1", value: 1, icon: Square }
1727
+ ];
1728
+ async function loadImage(src) {
1729
+ const image = new Image();
1730
+ image.crossOrigin = "anonymous";
1731
+ image.src = src;
1732
+ await new Promise((resolve, reject) => {
1733
+ image.onload = () => resolve();
1734
+ image.onerror = reject;
1735
+ });
1736
+ return image;
1737
+ }
1738
+ async function cropImageWithBlurFill(imageSrc, pixelCrop) {
1739
+ const image = await loadImage(imageSrc);
1740
+ const canvas = document.createElement("canvas");
1741
+ canvas.width = pixelCrop.width;
1742
+ canvas.height = pixelCrop.height;
1743
+ const ctx = canvas.getContext("2d");
1744
+ const extendsLeft = pixelCrop.x < 0;
1745
+ const extendsTop = pixelCrop.y < 0;
1746
+ const extendsRight = pixelCrop.x + pixelCrop.width > image.naturalWidth;
1747
+ const extendsBottom = pixelCrop.y + pixelCrop.height > image.naturalHeight;
1748
+ const needsBlurFill = extendsLeft || extendsTop || extendsRight || extendsBottom;
1749
+ if (needsBlurFill) {
1750
+ const blurCanvas = document.createElement("canvas");
1751
+ blurCanvas.width = pixelCrop.width;
1752
+ blurCanvas.height = pixelCrop.height;
1753
+ const blurCtx = blurCanvas.getContext("2d");
1754
+ blurCtx.filter = "blur(30px) brightness(0.7)";
1755
+ blurCtx.drawImage(image, 0, 0, pixelCrop.width, pixelCrop.height);
1756
+ blurCtx.filter = "none";
1757
+ ctx.drawImage(blurCanvas, 0, 0);
1758
+ const imgX = -pixelCrop.x;
1759
+ const imgY = -pixelCrop.y;
1760
+ ctx.drawImage(image, imgX, imgY, image.naturalWidth, image.naturalHeight);
1761
+ } else {
1762
+ ctx.drawImage(
1763
+ image,
1764
+ pixelCrop.x,
1765
+ pixelCrop.y,
1766
+ pixelCrop.width,
1767
+ pixelCrop.height,
1768
+ 0,
1769
+ 0,
1770
+ pixelCrop.width,
1771
+ pixelCrop.height
1772
+ );
1773
+ }
1774
+ return new Promise((resolve) => {
1775
+ canvas.toBlob((blob) => resolve(blob), "image/jpeg", 0.92);
1776
+ });
1777
+ }
1778
+ function ImageEditModal({
1779
+ src,
1780
+ originalSrc,
1781
+ onSave,
1782
+ onRestore,
1783
+ onClose
1784
+ }) {
1785
+ const actions = useGavaActions();
1786
+ const { strings } = useGavaConfig();
1787
+ const [crop, setCrop] = useState7({ x: 0, y: 0 });
1788
+ const [zoom, setZoom] = useState7(1);
1789
+ const [aspect, setAspect] = useState7(void 0);
1790
+ const [croppedAreaPixels, setCroppedAreaPixels] = useState7(null);
1791
+ const [saving, setSaving] = useState7(false);
1792
+ const onCropComplete = useCallback5((_, pixels) => {
1793
+ setCroppedAreaPixels(pixels);
1794
+ }, []);
1795
+ const handleSave = async () => {
1796
+ if (!croppedAreaPixels) return;
1797
+ setSaving(true);
1798
+ try {
1799
+ const blob = await cropImageWithBlurFill(src, croppedAreaPixels);
1800
+ const formData = new FormData();
1801
+ formData.append("file", blob, "cropped.jpg");
1802
+ const res = await fetch(actions.uploadUrl, {
1803
+ method: "POST",
1804
+ body: formData
1805
+ });
1806
+ const data = await res.json();
1807
+ if (data.url) {
1808
+ onSave(data.url);
1809
+ }
1810
+ } catch {
1811
+ alert(strings.saveError);
1812
+ } finally {
1813
+ setSaving(false);
1814
+ }
1815
+ };
1816
+ return /* @__PURE__ */ jsxs7("div", { className: "fixed inset-0 z-50 flex flex-col bg-black/90", children: [
1817
+ /* @__PURE__ */ jsxs7("div", { className: "flex items-center justify-between px-4 py-3 bg-black/50", children: [
1818
+ /* @__PURE__ */ jsx9("h3", { className: "text-white font-medium text-sm", children: strings.editImage }),
1819
+ /* @__PURE__ */ jsxs7("div", { className: "flex items-center gap-2", children: [
1820
+ originalSrc && onRestore && /* @__PURE__ */ jsxs7(
1821
+ "button",
1822
+ {
1823
+ onClick: () => {
1824
+ onRestore();
1825
+ onClose();
1826
+ },
1827
+ className: "flex items-center gap-1.5 px-3 py-1.5 text-xs font-medium text-amber-400 hover:text-amber-300 border border-amber-400/30 rounded-full hover:bg-amber-400/10 transition-colors",
1828
+ children: [
1829
+ /* @__PURE__ */ jsx9(RotateCcw2, { className: "w-3.5 h-3.5" }),
1830
+ strings.restoreOriginal
1831
+ ]
1832
+ }
1833
+ ),
1834
+ /* @__PURE__ */ jsx9(
1835
+ "button",
1836
+ {
1837
+ onClick: onClose,
1838
+ className: "text-white/70 hover:text-white p-1 rounded-full hover:bg-white/10 transition-colors",
1839
+ children: /* @__PURE__ */ jsx9(X5, { className: "w-5 h-5" })
1840
+ }
1841
+ )
1842
+ ] })
1843
+ ] }),
1844
+ /* @__PURE__ */ jsx9("div", { className: "flex-1 relative", children: /* @__PURE__ */ jsx9(
1845
+ Cropper,
1846
+ {
1847
+ image: src,
1848
+ crop,
1849
+ zoom,
1850
+ minZoom: 0.3,
1851
+ maxZoom: 3,
1852
+ aspect,
1853
+ restrictPosition: false,
1854
+ onCropChange: setCrop,
1855
+ onZoomChange: setZoom,
1856
+ onCropComplete,
1857
+ showGrid: true,
1858
+ style: {
1859
+ containerStyle: { background: "transparent" }
1860
+ }
1861
+ }
1862
+ ) }),
1863
+ /* @__PURE__ */ jsxs7("div", { className: "bg-black/70 backdrop-blur-sm px-4 py-4 space-y-3", children: [
1864
+ /* @__PURE__ */ jsxs7("div", { className: "flex items-center gap-3 justify-center", children: [
1865
+ /* @__PURE__ */ jsx9(
1866
+ "button",
1867
+ {
1868
+ onClick: () => setZoom(Math.max(0.3, zoom - 0.2)),
1869
+ className: "text-white/70 hover:text-white p-1.5 rounded-full hover:bg-white/10 transition-colors",
1870
+ children: /* @__PURE__ */ jsx9(ZoomOut, { className: "w-4 h-4" })
1871
+ }
1872
+ ),
1873
+ /* @__PURE__ */ jsx9(
1874
+ "input",
1875
+ {
1876
+ type: "range",
1877
+ min: 0.3,
1878
+ max: 3,
1879
+ step: 0.05,
1880
+ value: zoom,
1881
+ onChange: (e) => setZoom(Number(e.target.value)),
1882
+ className: "w-48 accent-blue-500"
1883
+ }
1884
+ ),
1885
+ /* @__PURE__ */ jsx9(
1886
+ "button",
1887
+ {
1888
+ onClick: () => setZoom(Math.min(3, zoom + 0.2)),
1889
+ className: "text-white/70 hover:text-white p-1.5 rounded-full hover:bg-white/10 transition-colors",
1890
+ children: /* @__PURE__ */ jsx9(ZoomIn, { className: "w-4 h-4" })
1891
+ }
1892
+ ),
1893
+ /* @__PURE__ */ jsxs7("span", { className: "text-white/50 text-xs w-12 text-center", children: [
1894
+ Math.round(zoom * 100),
1895
+ "%"
1896
+ ] })
1897
+ ] }),
1898
+ /* @__PURE__ */ jsxs7("div", { className: "flex items-center justify-between", children: [
1899
+ /* @__PURE__ */ jsx9("div", { className: "flex gap-1", children: ASPECT_PRESETS.map((p) => {
1900
+ const Icon = p.icon;
1901
+ const isActive = aspect === p.value || aspect === void 0 && p.value === void 0;
1902
+ return /* @__PURE__ */ jsxs7(
1903
+ "button",
1904
+ {
1905
+ onClick: () => setAspect(p.value),
1906
+ className: `flex items-center gap-1.5 px-3 py-1.5 rounded-full text-xs font-medium transition-colors ${isActive ? "bg-blue-500 text-white" : "text-white/60 hover:text-white hover:bg-white/10"}`,
1907
+ children: [
1908
+ /* @__PURE__ */ jsx9(Icon, { className: "w-3.5 h-3.5" }),
1909
+ p.label
1910
+ ]
1911
+ },
1912
+ p.label
1913
+ );
1914
+ }) }),
1915
+ /* @__PURE__ */ jsxs7("div", { className: "flex gap-2", children: [
1916
+ /* @__PURE__ */ jsx9(
1917
+ "button",
1918
+ {
1919
+ onClick: onClose,
1920
+ className: "px-4 py-1.5 text-sm text-white/70 hover:text-white border border-white/20 rounded-full hover:bg-white/10 transition-colors",
1921
+ children: strings.cancel
1922
+ }
1923
+ ),
1924
+ /* @__PURE__ */ jsxs7(
1925
+ "button",
1926
+ {
1927
+ onClick: handleSave,
1928
+ disabled: saving,
1929
+ className: "px-4 py-1.5 text-sm bg-blue-500 text-white rounded-full hover:bg-blue-600 transition-colors font-medium flex items-center gap-1.5 disabled:opacity-50",
1930
+ children: [
1931
+ saving ? /* @__PURE__ */ jsx9(Loader23, { className: "w-3.5 h-3.5 animate-spin" }) : /* @__PURE__ */ jsx9(Check2, { className: "w-3.5 h-3.5" }),
1932
+ strings.apply
1933
+ ]
1934
+ }
1935
+ )
1936
+ ] })
1937
+ ] })
1938
+ ] })
1939
+ ] });
1940
+ }
1941
+
1942
+ // src/components/articles/ArticleList.tsx
1943
+ import { useState as useState8, useRef as useRef4, useEffect as useEffect5, useCallback as useCallback6 } from "react";
1944
+ import Link2 from "next/link";
1945
+ import { useRouter as useRouter2 } from "next/navigation";
1946
+ import {
1947
+ Plus as Plus2,
1948
+ FileText as FileText2,
1949
+ Globe as Globe2,
1950
+ Pencil,
1951
+ Trash2 as Trash22,
1952
+ Search as Search2,
1953
+ EyeOff
1954
+ } from "lucide-react";
1955
+ import { jsx as jsx10, jsxs as jsxs8 } from "react/jsx-runtime";
1956
+ function ArticleList({
1957
+ articles,
1958
+ canCreate,
1959
+ canEdit,
1960
+ canPublish: canPub
1961
+ }) {
1962
+ const router = useRouter2();
1963
+ const actions = useGavaActions();
1964
+ const { strings, routes } = useGavaConfig();
1965
+ const filterLabels = {
1966
+ tutti: strings.all,
1967
+ bozza: strings.drafts,
1968
+ pubblicato: strings.published
1969
+ };
1970
+ const [filter, setFilter] = useState8("tutti");
1971
+ const [search, setSearch] = useState8("");
1972
+ const filterRefs = useRef4({});
1973
+ const [indicator, setIndicator] = useState8({ left: 0, width: 0 });
1974
+ const filtered = articles.filter((a) => {
1975
+ if (filter !== "tutti" && a.status !== filter) return false;
1976
+ if (search && !a.title.toLowerCase().includes(search.toLowerCase()))
1977
+ return false;
1978
+ return true;
1979
+ });
1980
+ const counts = {
1981
+ tutti: articles.length,
1982
+ bozza: articles.filter((a) => a.status === "bozza").length,
1983
+ pubblicato: articles.filter((a) => a.status === "pubblicato").length
1984
+ };
1985
+ const updateIndicator = useCallback6(() => {
1986
+ const el = filterRefs.current[filter];
1987
+ if (el) {
1988
+ setIndicator({
1989
+ left: el.offsetLeft,
1990
+ width: el.offsetWidth
1991
+ });
1992
+ }
1993
+ }, [filter]);
1994
+ useEffect5(() => {
1995
+ updateIndicator();
1996
+ window.addEventListener("resize", updateIndicator);
1997
+ return () => window.removeEventListener("resize", updateIndicator);
1998
+ }, [updateIndicator]);
1999
+ const handleDelete = async (id, title) => {
2000
+ if (!confirm(strings.deleteConfirm(title))) return;
2001
+ await actions.deleteArticle(id);
2002
+ router.refresh();
2003
+ };
2004
+ const handleUnpublish = async (id, title) => {
2005
+ if (!confirm(strings.unpublishConfirm(title))) return;
2006
+ await actions.unpublishArticle(id);
2007
+ router.refresh();
2008
+ };
2009
+ return /* @__PURE__ */ jsxs8("div", { className: "dash-animate-in", children: [
2010
+ /* @__PURE__ */ jsxs8("div", { className: "flex flex-col sm:flex-row sm:items-center sm:justify-between gap-4 mb-6", children: [
2011
+ /* @__PURE__ */ jsxs8("div", { children: [
2012
+ /* @__PURE__ */ jsx10("h1", { className: "text-2xl font-bold text-foreground mb-1", children: strings.articles }),
2013
+ /* @__PURE__ */ jsx10("p", { className: "text-sm text-muted", children: strings.totalArticles(articles.length) })
2014
+ ] }),
2015
+ canCreate && /* @__PURE__ */ jsxs8(
2016
+ Link2,
2017
+ {
2018
+ href: routes.articleNew,
2019
+ className: "inline-flex items-center gap-2 px-4 py-2.5 bg-accent text-white rounded-full hover:bg-accent-hover transition-all duration-200 text-sm font-medium hover:shadow-md active:scale-[0.97]",
2020
+ children: [
2021
+ /* @__PURE__ */ jsx10(Plus2, { className: "w-4 h-4" }),
2022
+ strings.newArticle
2023
+ ]
2024
+ }
2025
+ )
2026
+ ] }),
2027
+ /* @__PURE__ */ jsxs8("div", { className: "flex flex-col sm:flex-row gap-3 mb-6", children: [
2028
+ /* @__PURE__ */ jsxs8("div", { className: "dash-filter-group flex bg-card border border-card-border rounded-full p-1 gap-1", children: [
2029
+ /* @__PURE__ */ jsx10(
2030
+ "div",
2031
+ {
2032
+ className: "dash-filter-indicator",
2033
+ style: { left: indicator.left, width: indicator.width }
2034
+ }
2035
+ ),
2036
+ ["tutti", "bozza", "pubblicato"].map((f) => /* @__PURE__ */ jsxs8(
2037
+ "button",
2038
+ {
2039
+ ref: (el) => {
2040
+ filterRefs.current[f] = el;
2041
+ },
2042
+ onClick: () => setFilter(f),
2043
+ className: `dash-filter-btn px-3 py-1.5 text-sm rounded-full capitalize ${filter === f ? "text-white" : "text-muted hover:text-foreground"}`,
2044
+ children: [
2045
+ filterLabels[f],
2046
+ " ",
2047
+ /* @__PURE__ */ jsxs8(
2048
+ "span",
2049
+ {
2050
+ className: `text-xs ${filter === f ? "opacity-80" : "opacity-50"}`,
2051
+ children: [
2052
+ "(",
2053
+ counts[f],
2054
+ ")"
2055
+ ]
2056
+ }
2057
+ )
2058
+ ]
2059
+ },
2060
+ f
2061
+ ))
2062
+ ] }),
2063
+ /* @__PURE__ */ jsxs8("div", { className: "relative flex-1 max-w-xs", children: [
2064
+ /* @__PURE__ */ jsx10(Search2, { className: "absolute left-3 top-1/2 -translate-y-1/2 w-4 h-4 text-muted" }),
2065
+ /* @__PURE__ */ jsx10(
2066
+ "input",
2067
+ {
2068
+ type: "text",
2069
+ value: search,
2070
+ onChange: (e) => setSearch(e.target.value),
2071
+ placeholder: strings.searchArticles,
2072
+ className: "dash-search w-full pl-9 pr-3 py-2 text-sm bg-card border border-card-border rounded-full outline-none focus:border-accent"
2073
+ }
2074
+ )
2075
+ ] })
2076
+ ] }),
2077
+ filtered.length === 0 ? /* @__PURE__ */ jsxs8("div", { className: "dash-scale-in bg-card border border-card-border rounded-xl p-12 text-center", children: [
2078
+ /* @__PURE__ */ jsx10(FileText2, { className: "dash-empty-icon w-10 h-10 text-muted mx-auto mb-3" }),
2079
+ /* @__PURE__ */ jsx10("p", { className: "text-muted text-sm", children: search ? strings.noArticlesSearch : strings.noArticles })
2080
+ ] }) : /* @__PURE__ */ jsx10("div", { className: "bg-card border border-card-border rounded-xl overflow-hidden", children: /* @__PURE__ */ jsx10("div", { className: "overflow-x-auto", children: /* @__PURE__ */ jsxs8("table", { className: "w-full text-sm", children: [
2081
+ /* @__PURE__ */ jsx10("thead", { children: /* @__PURE__ */ jsxs8("tr", { className: "border-b border-card-border text-left", children: [
2082
+ /* @__PURE__ */ jsx10("th", { className: "px-4 py-3 font-medium text-muted", children: strings.title }),
2083
+ /* @__PURE__ */ jsx10("th", { className: "px-4 py-3 font-medium text-muted hidden md:table-cell", children: strings.author }),
2084
+ /* @__PURE__ */ jsx10("th", { className: "px-4 py-3 font-medium text-muted hidden lg:table-cell", children: strings.category }),
2085
+ /* @__PURE__ */ jsx10("th", { className: "px-4 py-3 font-medium text-muted", children: strings.status }),
2086
+ /* @__PURE__ */ jsx10("th", { className: "px-4 py-3 font-medium text-muted hidden sm:table-cell", children: strings.updated }),
2087
+ canEdit && /* @__PURE__ */ jsx10("th", { className: "px-4 py-3 font-medium text-muted text-right", children: strings.actions })
2088
+ ] }) }),
2089
+ /* @__PURE__ */ jsx10("tbody", { children: filtered.map((article) => /* @__PURE__ */ jsxs8(
2090
+ "tr",
2091
+ {
2092
+ className: "border-b border-card-border/50 last:border-0 hover:bg-card-border/20 transition-colors duration-150",
2093
+ children: [
2094
+ /* @__PURE__ */ jsx10("td", { className: "px-4 py-3", children: /* @__PURE__ */ jsxs8("div", { className: "flex items-center gap-3", children: [
2095
+ article.coverImage ? /* @__PURE__ */ jsx10(
2096
+ "img",
2097
+ {
2098
+ src: article.coverImage,
2099
+ alt: "",
2100
+ className: "w-10 h-10 rounded-md object-cover flex-shrink-0 bg-card-border transition-transform duration-200 hover:scale-105"
2101
+ }
2102
+ ) : /* @__PURE__ */ jsx10("div", { className: "w-10 h-10 rounded-md bg-card-border flex items-center justify-center flex-shrink-0", children: /* @__PURE__ */ jsx10(FileText2, { className: "w-4 h-4 text-muted" }) }),
2103
+ /* @__PURE__ */ jsx10("span", { className: "font-medium text-foreground", children: article.title || /* @__PURE__ */ jsx10("span", { className: "italic text-muted", children: strings.untitled }) })
2104
+ ] }) }),
2105
+ /* @__PURE__ */ jsx10("td", { className: "px-4 py-3 text-muted hidden md:table-cell", children: article.authorName || "\u2014" }),
2106
+ /* @__PURE__ */ jsx10("td", { className: "px-4 py-3 text-muted hidden lg:table-cell", children: article.category || "\u2014" }),
2107
+ /* @__PURE__ */ jsx10("td", { className: "px-4 py-3", children: article.status === "pubblicato" ? /* @__PURE__ */ jsxs8("span", { className: "inline-flex items-center gap-1 text-xs font-medium text-green-600 bg-green-50 px-2 py-0.5 rounded-full", children: [
2108
+ /* @__PURE__ */ jsx10(Globe2, { className: "w-3 h-3" }),
2109
+ strings.published
2110
+ ] }) : /* @__PURE__ */ jsxs8("span", { className: "inline-flex items-center gap-1 text-xs font-medium text-amber-600 bg-amber-50 px-2 py-0.5 rounded-full", children: [
2111
+ /* @__PURE__ */ jsx10(FileText2, { className: "w-3 h-3" }),
2112
+ strings.draft
2113
+ ] }) }),
2114
+ /* @__PURE__ */ jsx10("td", { className: "px-4 py-3 text-muted text-sm hidden sm:table-cell", children: new Date(article.updatedAt).toLocaleDateString("it-IT", {
2115
+ day: "numeric",
2116
+ month: "short",
2117
+ year: "numeric"
2118
+ }) }),
2119
+ canEdit && /* @__PURE__ */ jsx10("td", { className: "px-4 py-3 text-right", children: /* @__PURE__ */ jsxs8("div", { className: "flex items-center justify-end gap-1", children: [
2120
+ canPub && article.status === "pubblicato" && /* @__PURE__ */ jsx10(
2121
+ "button",
2122
+ {
2123
+ onClick: () => handleUnpublish(article.id, article.title),
2124
+ className: "p-1.5 rounded-full text-muted hover:text-amber-600 hover:bg-amber-50 transition-all duration-200 active:scale-90",
2125
+ title: strings.unpublish,
2126
+ children: /* @__PURE__ */ jsx10(EyeOff, { className: "w-4 h-4" })
2127
+ }
2128
+ ),
2129
+ /* @__PURE__ */ jsx10(
2130
+ Link2,
2131
+ {
2132
+ href: routes.articleEdit(article.id),
2133
+ className: "p-1.5 rounded-full text-muted hover:text-foreground hover:bg-card-border/50 transition-all duration-200 active:scale-90",
2134
+ title: strings.edit,
2135
+ children: /* @__PURE__ */ jsx10(Pencil, { className: "w-4 h-4" })
2136
+ }
2137
+ ),
2138
+ /* @__PURE__ */ jsx10(
2139
+ "button",
2140
+ {
2141
+ onClick: () => handleDelete(article.id, article.title),
2142
+ className: "p-1.5 rounded-full text-muted hover:text-red-500 hover:bg-red-50 transition-all duration-200 active:scale-90",
2143
+ title: strings.delete,
2144
+ children: /* @__PURE__ */ jsx10(Trash22, { className: "w-4 h-4" })
2145
+ }
2146
+ )
2147
+ ] }) })
2148
+ ]
2149
+ },
2150
+ article.id
2151
+ )) })
2152
+ ] }) }) })
2153
+ ] });
2154
+ }
2155
+
2156
+ // src/components/users/UserTable.tsx
2157
+ import Link3 from "next/link";
2158
+ import { Pencil as Pencil2, Trash2 as Trash23 } from "lucide-react";
2159
+ import { jsx as jsx11, jsxs as jsxs9 } from "react/jsx-runtime";
2160
+ function UserTable({
2161
+ users,
2162
+ currentUserId
2163
+ }) {
2164
+ const actions = useGavaActions();
2165
+ const { strings, roles, routes } = useGavaConfig();
2166
+ async function handleDelete(id, name) {
2167
+ if (!confirm(strings.deleteUserConfirm(name))) return;
2168
+ const result = await actions.deleteUser(id);
2169
+ if (result && "error" in result && result.error) {
2170
+ alert(result.error);
2171
+ }
2172
+ }
2173
+ return /* @__PURE__ */ jsx11("div", { className: "overflow-x-auto", children: /* @__PURE__ */ jsxs9("table", { className: "w-full text-sm", children: [
2174
+ /* @__PURE__ */ jsx11("thead", { children: /* @__PURE__ */ jsxs9("tr", { className: "border-b border-card-border", children: [
2175
+ /* @__PURE__ */ jsx11("th", { className: "text-left py-3 px-4 font-semibold text-foreground", children: strings.name }),
2176
+ /* @__PURE__ */ jsx11("th", { className: "text-left py-3 px-4 font-semibold text-foreground", children: strings.email }),
2177
+ /* @__PURE__ */ jsx11("th", { className: "text-left py-3 px-4 font-semibold text-foreground", children: strings.role }),
2178
+ /* @__PURE__ */ jsx11("th", { className: "text-left py-3 px-4 font-semibold text-foreground", children: strings.createdAt }),
2179
+ /* @__PURE__ */ jsx11("th", { className: "text-right py-3 px-4 font-semibold text-foreground", children: strings.actions })
2180
+ ] }) }),
2181
+ /* @__PURE__ */ jsx11("tbody", { children: users.map((user) => /* @__PURE__ */ jsxs9(
2182
+ "tr",
2183
+ {
2184
+ className: "border-b border-card-border hover:bg-card/50",
2185
+ children: [
2186
+ /* @__PURE__ */ jsx11("td", { className: "py-3 px-4 text-foreground", children: user.name }),
2187
+ /* @__PURE__ */ jsx11("td", { className: "py-3 px-4 text-muted", children: user.email }),
2188
+ /* @__PURE__ */ jsx11("td", { className: "py-3 px-4", children: /* @__PURE__ */ jsx11("span", { className: "inline-block px-2 py-0.5 bg-accent/10 text-accent text-xs font-medium rounded-full", children: roles.labels[user.role] || user.role }) }),
2189
+ /* @__PURE__ */ jsx11("td", { className: "py-3 px-4 text-muted", children: new Date(user.createdAt).toLocaleDateString("it-IT") }),
2190
+ /* @__PURE__ */ jsx11("td", { className: "py-3 px-4 text-right", children: /* @__PURE__ */ jsxs9("div", { className: "flex items-center justify-end gap-2", children: [
2191
+ /* @__PURE__ */ jsx11(
2192
+ Link3,
2193
+ {
2194
+ href: routes.userEdit(user.id),
2195
+ className: "p-1.5 text-muted hover:text-accent transition-colors",
2196
+ title: strings.edit,
2197
+ children: /* @__PURE__ */ jsx11(Pencil2, { className: "w-4 h-4" })
2198
+ }
2199
+ ),
2200
+ user.id !== currentUserId && /* @__PURE__ */ jsx11(
2201
+ "button",
2202
+ {
2203
+ onClick: () => handleDelete(user.id, user.name),
2204
+ className: "p-1.5 text-muted hover:text-red-500 transition-colors",
2205
+ title: strings.delete,
2206
+ children: /* @__PURE__ */ jsx11(Trash23, { className: "w-4 h-4" })
2207
+ }
2208
+ )
2209
+ ] }) })
2210
+ ]
2211
+ },
2212
+ user.id
2213
+ )) })
2214
+ ] }) });
2215
+ }
2216
+
2217
+ // src/components/users/UserForm.tsx
2218
+ import { useState as useState9 } from "react";
2219
+ import { jsx as jsx12, jsxs as jsxs10 } from "react/jsx-runtime";
2220
+ function UserForm({ user }) {
2221
+ const actions = useGavaActions();
2222
+ const { strings, roles } = useGavaConfig();
2223
+ const [error, setError] = useState9("");
2224
+ const isEditing = !!user;
2225
+ async function handleSubmit(formData) {
2226
+ setError("");
2227
+ let result;
2228
+ if (isEditing) {
2229
+ result = await actions.updateUser(user.id, formData);
2230
+ } else {
2231
+ result = await actions.createUser(formData);
2232
+ }
2233
+ if (result && "error" in result && result.error) {
2234
+ setError(result.error);
2235
+ }
2236
+ }
2237
+ return /* @__PURE__ */ jsxs10("form", { action: handleSubmit, className: "space-y-4 max-w-md", children: [
2238
+ error && /* @__PURE__ */ jsx12("div", { className: "bg-red-50 dark:bg-red-900/20 border border-red-200 dark:border-red-800 text-red-700 dark:text-red-400 px-4 py-3 rounded-lg text-sm", children: error }),
2239
+ /* @__PURE__ */ jsxs10("div", { children: [
2240
+ /* @__PURE__ */ jsx12(
2241
+ "label",
2242
+ {
2243
+ htmlFor: "name",
2244
+ className: "block text-sm font-medium text-foreground mb-1",
2245
+ children: strings.name
2246
+ }
2247
+ ),
2248
+ /* @__PURE__ */ jsx12(
2249
+ "input",
2250
+ {
2251
+ id: "name",
2252
+ name: "name",
2253
+ type: "text",
2254
+ required: true,
2255
+ defaultValue: user?.name || "",
2256
+ className: "w-full px-3 py-2 bg-background border border-card-border rounded-lg text-foreground text-sm focus:outline-none focus:ring-2 focus:ring-accent focus:border-transparent"
2257
+ }
2258
+ )
2259
+ ] }),
2260
+ /* @__PURE__ */ jsxs10("div", { children: [
2261
+ /* @__PURE__ */ jsx12(
2262
+ "label",
2263
+ {
2264
+ htmlFor: "email",
2265
+ className: "block text-sm font-medium text-foreground mb-1",
2266
+ children: strings.email
2267
+ }
2268
+ ),
2269
+ /* @__PURE__ */ jsx12(
2270
+ "input",
2271
+ {
2272
+ id: "email",
2273
+ name: "email",
2274
+ type: "email",
2275
+ required: true,
2276
+ defaultValue: user?.email || "",
2277
+ className: "w-full px-3 py-2 bg-background border border-card-border rounded-lg text-foreground text-sm focus:outline-none focus:ring-2 focus:ring-accent focus:border-transparent"
2278
+ }
2279
+ )
2280
+ ] }),
2281
+ /* @__PURE__ */ jsxs10("div", { children: [
2282
+ /* @__PURE__ */ jsxs10(
2283
+ "label",
2284
+ {
2285
+ htmlFor: "password",
2286
+ className: "block text-sm font-medium text-foreground mb-1",
2287
+ children: [
2288
+ strings.password,
2289
+ isEditing && strings.passwordEditHint
2290
+ ]
2291
+ }
2292
+ ),
2293
+ /* @__PURE__ */ jsx12(
2294
+ "input",
2295
+ {
2296
+ id: "password",
2297
+ name: "password",
2298
+ type: "password",
2299
+ required: !isEditing,
2300
+ minLength: 6,
2301
+ className: "w-full px-3 py-2 bg-background border border-card-border rounded-lg text-foreground text-sm focus:outline-none focus:ring-2 focus:ring-accent focus:border-transparent",
2302
+ placeholder: isEditing ? "" : "Min 6 chars"
2303
+ }
2304
+ )
2305
+ ] }),
2306
+ /* @__PURE__ */ jsxs10("div", { children: [
2307
+ /* @__PURE__ */ jsx12(
2308
+ "label",
2309
+ {
2310
+ htmlFor: "role",
2311
+ className: "block text-sm font-medium text-foreground mb-1",
2312
+ children: strings.role
2313
+ }
2314
+ ),
2315
+ /* @__PURE__ */ jsx12(
2316
+ "select",
2317
+ {
2318
+ id: "role",
2319
+ name: "role",
2320
+ required: true,
2321
+ defaultValue: user?.role || roles.list[roles.list.length - 1],
2322
+ className: "w-full px-3 py-2 bg-background border border-card-border rounded-lg text-foreground text-sm focus:outline-none focus:ring-2 focus:ring-accent focus:border-transparent",
2323
+ children: roles.list.map((r) => /* @__PURE__ */ jsx12("option", { value: r, children: roles.labels[r] || r }, r))
2324
+ }
2325
+ )
2326
+ ] }),
2327
+ /* @__PURE__ */ jsx12(
2328
+ "button",
2329
+ {
2330
+ type: "submit",
2331
+ className: "px-6 py-2.5 bg-accent text-white rounded-full font-medium text-sm hover:bg-accent-hover transition-colors",
2332
+ children: isEditing ? strings.saveChanges : strings.createUser
2333
+ }
2334
+ )
2335
+ ] });
2336
+ }
2337
+
2338
+ // src/components/media/MediaGrid.tsx
2339
+ import { useState as useState10 } from "react";
2340
+ import { useRouter as useRouter3 } from "next/navigation";
2341
+ import { Search as Search3, Trash2 as Trash24, Copy, Check as Check3, ImageIcon as ImageIcon2 } from "lucide-react";
2342
+ import { jsx as jsx13, jsxs as jsxs11 } from "react/jsx-runtime";
2343
+ function formatSize2(bytes) {
2344
+ if (bytes < 1024) return `${bytes} B`;
2345
+ if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`;
2346
+ return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;
2347
+ }
2348
+ function MediaGrid({ media }) {
2349
+ const router = useRouter3();
2350
+ const actions = useGavaActions();
2351
+ const { strings } = useGavaConfig();
2352
+ const [search, setSearch] = useState10("");
2353
+ const [copiedId, setCopiedId] = useState10(null);
2354
+ const filtered = media.filter(
2355
+ (m) => search ? m.filename.toLowerCase().includes(search.toLowerCase()) : true
2356
+ );
2357
+ const handleCopy = async (path, id) => {
2358
+ await navigator.clipboard.writeText(path);
2359
+ setCopiedId(id);
2360
+ setTimeout(() => setCopiedId(null), 2e3);
2361
+ };
2362
+ const handleDelete = async (id, filename) => {
2363
+ if (!confirm(strings.deleteConfirm(filename))) return;
2364
+ try {
2365
+ await actions.deleteMedia(id);
2366
+ router.refresh();
2367
+ } catch (err) {
2368
+ alert(
2369
+ err instanceof Error ? err.message : "Error deleting file"
2370
+ );
2371
+ }
2372
+ };
2373
+ return /* @__PURE__ */ jsxs11("div", { className: "dash-animate-in", children: [
2374
+ /* @__PURE__ */ jsxs11("div", { className: "flex flex-col sm:flex-row sm:items-center sm:justify-between gap-4 mb-6", children: [
2375
+ /* @__PURE__ */ jsxs11("div", { children: [
2376
+ /* @__PURE__ */ jsx13("h1", { className: "text-2xl font-bold text-foreground mb-1", children: strings.media }),
2377
+ /* @__PURE__ */ jsx13("p", { className: "text-sm text-muted", children: strings.totalFiles(media.length) })
2378
+ ] }),
2379
+ /* @__PURE__ */ jsxs11("div", { className: "relative max-w-xs w-full", children: [
2380
+ /* @__PURE__ */ jsx13(Search3, { className: "absolute left-3 top-1/2 -translate-y-1/2 w-4 h-4 text-muted" }),
2381
+ /* @__PURE__ */ jsx13(
2382
+ "input",
2383
+ {
2384
+ type: "text",
2385
+ value: search,
2386
+ onChange: (e) => setSearch(e.target.value),
2387
+ placeholder: strings.searchFiles,
2388
+ className: "dash-search w-full pl-9 pr-3 py-2 text-sm bg-card border border-card-border rounded-full outline-none focus:border-accent"
2389
+ }
2390
+ )
2391
+ ] })
2392
+ ] }),
2393
+ filtered.length === 0 ? /* @__PURE__ */ jsxs11("div", { className: "dash-scale-in bg-card border border-card-border rounded-xl p-12 text-center", children: [
2394
+ /* @__PURE__ */ jsx13(ImageIcon2, { className: "dash-empty-icon w-10 h-10 text-muted mx-auto mb-3" }),
2395
+ /* @__PURE__ */ jsx13("p", { className: "text-muted text-sm", children: search ? strings.noMediaSearch : strings.noMedia })
2396
+ ] }) : /* @__PURE__ */ jsx13("div", { className: "grid grid-cols-2 sm:grid-cols-3 md:grid-cols-4 lg:grid-cols-5 gap-4", children: filtered.map((m, i) => /* @__PURE__ */ jsxs11(
2397
+ "div",
2398
+ {
2399
+ className: "dash-animate-in dash-stagger dash-card-hover bg-card border border-card-border rounded-xl overflow-hidden group",
2400
+ style: { "--i": i },
2401
+ children: [
2402
+ /* @__PURE__ */ jsxs11("div", { className: "aspect-square bg-card-border relative overflow-hidden", children: [
2403
+ m.mimeType.startsWith("image/") ? /* @__PURE__ */ jsx13(
2404
+ "img",
2405
+ {
2406
+ src: m.path,
2407
+ alt: m.filename,
2408
+ className: "dash-media-img w-full h-full object-cover"
2409
+ }
2410
+ ) : /* @__PURE__ */ jsx13("div", { className: "w-full h-full flex items-center justify-center", children: /* @__PURE__ */ jsx13(ImageIcon2, { className: "w-8 h-8 text-muted" }) }),
2411
+ /* @__PURE__ */ jsxs11("div", { className: "absolute inset-0 bg-black/50 opacity-0 group-hover:opacity-100 transition-opacity duration-200 flex items-center justify-center gap-2", children: [
2412
+ /* @__PURE__ */ jsx13(
2413
+ "button",
2414
+ {
2415
+ onClick: () => handleCopy(m.path, m.id),
2416
+ className: "p-2 rounded-full bg-white/90 text-foreground hover:bg-white transition-all duration-200 active:scale-90",
2417
+ title: strings.copyUrl,
2418
+ children: copiedId === m.id ? /* @__PURE__ */ jsx13(Check3, { className: "w-4 h-4 text-green-600" }) : /* @__PURE__ */ jsx13(Copy, { className: "w-4 h-4" })
2419
+ }
2420
+ ),
2421
+ /* @__PURE__ */ jsx13(
2422
+ "button",
2423
+ {
2424
+ onClick: () => handleDelete(m.id, m.filename),
2425
+ className: "p-2 rounded-full bg-white/90 text-red-500 hover:bg-white transition-all duration-200 active:scale-90",
2426
+ title: strings.delete,
2427
+ children: /* @__PURE__ */ jsx13(Trash24, { className: "w-4 h-4" })
2428
+ }
2429
+ )
2430
+ ] })
2431
+ ] }),
2432
+ /* @__PURE__ */ jsxs11("div", { className: "p-2.5", children: [
2433
+ /* @__PURE__ */ jsx13("p", { className: "text-xs font-medium text-foreground truncate", children: m.filename }),
2434
+ /* @__PURE__ */ jsxs11("p", { className: "text-[10px] text-muted", children: [
2435
+ formatSize2(m.size),
2436
+ " \u2014 ",
2437
+ m.uploader.name
2438
+ ] })
2439
+ ] })
2440
+ ]
2441
+ },
2442
+ m.id
2443
+ )) })
2444
+ ] });
2445
+ }
2446
+
2447
+ // src/components/dashboard/DashboardNavbar.tsx
2448
+ import Link4 from "next/link";
2449
+ import { usePathname } from "next/navigation";
2450
+ import { useSession, signOut } from "next-auth/react";
2451
+ import { FileText as FileText3, Users, LogOut, BarChart3, ImageIcon as ImageIcon3 } from "lucide-react";
2452
+ import { useState as useState11 } from "react";
2453
+ import { jsx as jsx14, jsxs as jsxs12 } from "react/jsx-runtime";
2454
+ function DashboardNavbar() {
2455
+ const pathname = usePathname();
2456
+ const { data: session } = useSession();
2457
+ const [mobileOpen, setMobileOpen] = useState11(false);
2458
+ const { branding, roles, strings, routes } = useGavaConfig();
2459
+ const role = session?.user?.role;
2460
+ const navigation = [
2461
+ { name: strings.articles, href: routes.articles, icon: FileText3 },
2462
+ ...roles.canEdit(role || "") ? [{ name: strings.media, href: routes.media, icon: ImageIcon3 }] : [],
2463
+ ...roles.canPublish(role || "") ? [
2464
+ {
2465
+ name: strings.statistics,
2466
+ href: routes.dashboard + "/statistiche",
2467
+ icon: BarChart3
2468
+ }
2469
+ ] : [],
2470
+ ...role === roles.adminRole ? [{ name: strings.users, href: routes.users, icon: Users }] : []
2471
+ ];
2472
+ const isActive = (href) => pathname.startsWith(href);
2473
+ return /* @__PURE__ */ jsx14("header", { className: "sticky top-0 z-50 px-4 pb-4", children: /* @__PURE__ */ jsx14("div", { className: "mx-auto max-w-7xl", children: /* @__PURE__ */ jsxs12("nav", { className: "bg-background/95 backdrop-blur-sm border border-t-transparent border-card-border rounded-b-xl", children: [
2474
+ /* @__PURE__ */ jsxs12("div", { className: "flex h-14 items-center justify-between px-6", children: [
2475
+ /* @__PURE__ */ jsxs12(Link4, { href: routes.home, className: "flex items-center gap-3", children: [
2476
+ branding.logo && /* @__PURE__ */ jsx14("span", { className: "relative z-[9999] -my-[100px] py-[100px] bg-white rounded-b-lg px-1.5 pb-1.5", children: /* @__PURE__ */ jsx14(
2477
+ "img",
2478
+ {
2479
+ src: branding.logo,
2480
+ alt: branding.name,
2481
+ width: 44,
2482
+ height: 44
2483
+ }
2484
+ ) }),
2485
+ /* @__PURE__ */ jsx14("span", { className: "font-bold text-lg text-foreground", children: branding.name })
2486
+ ] }),
2487
+ /* @__PURE__ */ jsx14("div", { className: "hidden md:flex md:items-center md:gap-6", children: navigation.map((item) => /* @__PURE__ */ jsxs12(
2488
+ Link4,
2489
+ {
2490
+ href: item.href,
2491
+ className: `flex items-center gap-2 text-sm font-medium transition-colors ${isActive(item.href) ? "text-accent" : "text-muted hover:text-foreground"}`,
2492
+ children: [
2493
+ /* @__PURE__ */ jsx14(item.icon, { className: "w-4 h-4" }),
2494
+ item.name
2495
+ ]
2496
+ },
2497
+ item.name
2498
+ )) }),
2499
+ /* @__PURE__ */ jsxs12("div", { className: "hidden md:flex md:items-center md:gap-3", children: [
2500
+ /* @__PURE__ */ jsxs12("div", { className: "text-right", children: [
2501
+ /* @__PURE__ */ jsx14("p", { className: "text-sm font-medium text-foreground leading-tight", children: session?.user?.name }),
2502
+ /* @__PURE__ */ jsx14("p", { className: "text-xs text-muted leading-tight", children: roles.labels[role || ""] || role })
2503
+ ] }),
2504
+ /* @__PURE__ */ jsx14(
2505
+ "button",
2506
+ {
2507
+ onClick: () => signOut({ callbackUrl: routes.login }),
2508
+ className: "p-2 rounded-full text-muted hover:text-foreground hover:bg-card transition-colors",
2509
+ title: strings.logout,
2510
+ children: /* @__PURE__ */ jsx14(LogOut, { className: "w-4 h-4" })
2511
+ }
2512
+ )
2513
+ ] }),
2514
+ /* @__PURE__ */ jsxs12(
2515
+ "button",
2516
+ {
2517
+ type: "button",
2518
+ onClick: () => setMobileOpen(!mobileOpen),
2519
+ className: "md:hidden p-2 rounded-full text-foreground flex flex-col justify-center items-center w-10 h-10 gap-1.5",
2520
+ "aria-label": "Menu",
2521
+ children: [
2522
+ /* @__PURE__ */ jsx14(
2523
+ "span",
2524
+ {
2525
+ className: `block h-0.5 w-5 bg-foreground rounded-full transition-all duration-300 ease-in-out ${mobileOpen ? "rotate-45 translate-y-2" : ""}`
2526
+ }
2527
+ ),
2528
+ /* @__PURE__ */ jsx14(
2529
+ "span",
2530
+ {
2531
+ className: `block h-0.5 w-5 bg-foreground rounded-full transition-all duration-300 ease-in-out ${mobileOpen ? "opacity-0 scale-0" : ""}`
2532
+ }
2533
+ ),
2534
+ /* @__PURE__ */ jsx14(
2535
+ "span",
2536
+ {
2537
+ className: `block h-0.5 w-5 bg-foreground rounded-full transition-all duration-300 ease-in-out ${mobileOpen ? "-rotate-45 -translate-y-2" : ""}`
2538
+ }
2539
+ )
2540
+ ]
2541
+ }
2542
+ )
2543
+ ] }),
2544
+ /* @__PURE__ */ jsx14(
2545
+ "div",
2546
+ {
2547
+ className: `md:hidden overflow-hidden transition-all duration-300 ease-in-out ${mobileOpen ? "max-h-80 opacity-100" : "max-h-0 opacity-0"}`,
2548
+ children: /* @__PURE__ */ jsxs12("div", { className: "py-4 border-t border-card-border mx-6 space-y-1", children: [
2549
+ navigation.map((item) => /* @__PURE__ */ jsxs12(
2550
+ Link4,
2551
+ {
2552
+ href: item.href,
2553
+ onClick: () => setMobileOpen(false),
2554
+ className: `flex items-center gap-3 px-3 py-2 rounded-full text-sm font-medium transition-colors ${isActive(item.href) ? "text-accent" : "text-muted hover:text-foreground"}`,
2555
+ children: [
2556
+ /* @__PURE__ */ jsx14(item.icon, { className: "w-4 h-4" }),
2557
+ item.name
2558
+ ]
2559
+ },
2560
+ item.name
2561
+ )),
2562
+ /* @__PURE__ */ jsxs12("div", { className: "pt-3 mt-2 border-t border-card-border", children: [
2563
+ /* @__PURE__ */ jsxs12("div", { className: "px-3 mb-2", children: [
2564
+ /* @__PURE__ */ jsx14("p", { className: "text-sm font-medium text-foreground", children: session?.user?.name }),
2565
+ /* @__PURE__ */ jsx14("p", { className: "text-xs text-muted", children: roles.labels[role || ""] || role })
2566
+ ] }),
2567
+ /* @__PURE__ */ jsxs12(
2568
+ "button",
2569
+ {
2570
+ onClick: () => signOut({ callbackUrl: routes.login }),
2571
+ className: "flex items-center gap-3 px-3 py-2 text-sm text-muted hover:text-foreground transition-colors w-full rounded-full hover:bg-card",
2572
+ children: [
2573
+ /* @__PURE__ */ jsx14(LogOut, { className: "w-4 h-4" }),
2574
+ strings.logout
2575
+ ]
2576
+ }
2577
+ )
2578
+ ] })
2579
+ ] })
2580
+ }
2581
+ )
2582
+ ] }) }) });
2583
+ }
2584
+
2585
+ // src/components/dashboard/StatCard.tsx
2586
+ import { jsx as jsx15, jsxs as jsxs13 } from "react/jsx-runtime";
2587
+ var accentStyles = {
2588
+ blue: {
2589
+ border: "border-l-blue-500",
2590
+ bg: "bg-blue-500/10",
2591
+ text: "text-blue-600"
2592
+ },
2593
+ green: {
2594
+ border: "border-l-green-500",
2595
+ bg: "bg-green-500/10",
2596
+ text: "text-green-600"
2597
+ },
2598
+ purple: {
2599
+ border: "border-l-purple-500",
2600
+ bg: "bg-purple-500/10",
2601
+ text: "text-purple-600"
2602
+ },
2603
+ amber: {
2604
+ border: "border-l-amber-500",
2605
+ bg: "bg-amber-500/10",
2606
+ text: "text-amber-600"
2607
+ }
2608
+ };
2609
+ function StatCard({
2610
+ label,
2611
+ value,
2612
+ icon: Icon,
2613
+ accent = "blue"
2614
+ }) {
2615
+ const s = accentStyles[accent];
2616
+ return /* @__PURE__ */ jsx15(
2617
+ "div",
2618
+ {
2619
+ className: `bg-card border border-card-border rounded-xl p-5 border-l-[3px] ${s.border} transition-all duration-200 hover:shadow-sm`,
2620
+ children: /* @__PURE__ */ jsxs13("div", { className: "flex items-start justify-between", children: [
2621
+ /* @__PURE__ */ jsxs13("div", { children: [
2622
+ /* @__PURE__ */ jsx15("p", { className: "text-xs font-medium text-muted uppercase tracking-wide mb-2", children: label }),
2623
+ /* @__PURE__ */ jsx15("p", { className: "text-3xl font-bold text-foreground tabular-nums", children: value })
2624
+ ] }),
2625
+ /* @__PURE__ */ jsx15("div", { className: `p-2 rounded-lg ${s.bg}`, children: /* @__PURE__ */ jsx15(Icon, { className: `w-5 h-5 ${s.text}` }) })
2626
+ ] })
2627
+ }
2628
+ );
2629
+ }
2630
+
2631
+ // src/components/splash/SplashScreen.tsx
2632
+ import { useMemo } from "react";
2633
+ import { jsx as jsx16, jsxs as jsxs14 } from "react/jsx-runtime";
2634
+ function pickRandom3(words) {
2635
+ const shuffled = [...words].sort(() => Math.random() - 0.5);
2636
+ return shuffled.slice(0, 3);
2637
+ }
2638
+ function SplashScreen() {
2639
+ const { phase } = useSplash();
2640
+ const { branding } = useGavaConfig();
2641
+ const words = useMemo(() => pickRandom3(branding.splashWords), [branding.splashWords]);
2642
+ if (phase === "idle") return null;
2643
+ const topClass = phase === "closing" ? "splash-enter-down" : phase === "opening" ? "splash-exit-up" : "";
2644
+ const bottomClass = phase === "closing" ? "splash-enter-up" : phase === "opening" ? "splash-exit-down" : "";
2645
+ return /* @__PURE__ */ jsxs14("div", { className: "fixed inset-0 z-[9999] pointer-events-none", children: [
2646
+ /* @__PURE__ */ jsx16(
2647
+ "div",
2648
+ {
2649
+ className: `splash-half splash-half-top absolute inset-x-0 top-0 h-1/2 bg-background overflow-hidden ${topClass}`,
2650
+ children: /* @__PURE__ */ jsx16("div", { className: "absolute inset-0 flex flex-col items-center justify-end pb-2", children: /* @__PURE__ */ jsx16("span", { className: "splash-title", children: "GAVA" }) })
2651
+ }
2652
+ ),
2653
+ /* @__PURE__ */ jsx16(
2654
+ "div",
2655
+ {
2656
+ className: `splash-half splash-half-bottom absolute inset-x-0 bottom-0 h-1/2 bg-background overflow-hidden ${bottomClass}`,
2657
+ children: /* @__PURE__ */ jsxs14("div", { className: "absolute inset-0 flex flex-col items-center justify-start pt-2", children: [
2658
+ /* @__PURE__ */ jsx16("span", { className: "splash-title", children: "ENGINE" }),
2659
+ /* @__PURE__ */ jsx16("div", { className: "flex items-center gap-3 mt-6", children: words.map((word, i) => /* @__PURE__ */ jsxs14(
2660
+ "span",
2661
+ {
2662
+ className: "splash-word text-xs sm:text-sm font-medium tracking-widest uppercase",
2663
+ style: { animationDelay: `${0.2 + i * 0.1}s` },
2664
+ children: [
2665
+ word,
2666
+ i < 2 && /* @__PURE__ */ jsx16("span", { className: "ml-3 text-muted-foreground", children: "\xB7" })
2667
+ ]
2668
+ },
2669
+ word
2670
+ )) })
2671
+ ] })
2672
+ }
2673
+ )
2674
+ ] });
2675
+ }
2676
+
2677
+ // src/components/splash/DashboardSplashTrigger.tsx
2678
+ import { useEffect as useEffect6 } from "react";
2679
+ function DashboardSplashTrigger() {
2680
+ const { openSplash } = useSplash();
2681
+ useEffect6(() => {
2682
+ if (typeof window.requestIdleCallback === "function") {
2683
+ const id = window.requestIdleCallback(() => openSplash());
2684
+ return () => window.cancelIdleCallback(id);
2685
+ }
2686
+ const timeout = setTimeout(() => {
2687
+ requestAnimationFrame(() => openSplash());
2688
+ }, 100);
2689
+ return () => clearTimeout(timeout);
2690
+ }, [openSplash]);
2691
+ return null;
2692
+ }
2693
+
2694
+ export {
2695
+ ResizableImageView,
2696
+ ResizableImage,
2697
+ DraggableYoutubeView,
2698
+ DraggableYoutube,
2699
+ VideoView,
2700
+ VideoExtension,
2701
+ MediaPickerModal,
2702
+ EditorToolbar,
2703
+ CoverImageUpload,
2704
+ RevisionPanel,
2705
+ ArticleEditor,
2706
+ ImageEditModal,
2707
+ ArticleList,
2708
+ UserTable,
2709
+ UserForm,
2710
+ MediaGrid,
2711
+ DashboardNavbar,
2712
+ StatCard,
2713
+ SplashScreen,
2714
+ DashboardSplashTrigger
2715
+ };
2716
+ //# sourceMappingURL=chunk-CE7E5MAB.js.map