@jhits/plugin-content 0.0.15 → 0.0.16

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (41) hide show
  1. package/dist/api/files.d.ts +27 -0
  2. package/dist/api/files.d.ts.map +1 -0
  3. package/dist/api/files.js +159 -0
  4. package/dist/api/handler.d.ts +4 -0
  5. package/dist/api/handler.d.ts.map +1 -1
  6. package/dist/api/links.d.ts +23 -0
  7. package/dist/api/links.d.ts.map +1 -0
  8. package/dist/api/links.js +75 -0
  9. package/dist/api/router.d.ts +6 -0
  10. package/dist/api/router.d.ts.map +1 -1
  11. package/dist/api/router.js +32 -3
  12. package/dist/components/DynamicLink.d.ts +28 -0
  13. package/dist/components/DynamicLink.d.ts.map +1 -0
  14. package/dist/components/DynamicLink.js +62 -0
  15. package/dist/components/LinkSettingsModal.d.ts +22 -0
  16. package/dist/components/LinkSettingsModal.d.ts.map +1 -0
  17. package/dist/components/LinkSettingsModal.js +172 -0
  18. package/dist/hooks/useLinks.d.ts +23 -0
  19. package/dist/hooks/useLinks.d.ts.map +1 -0
  20. package/dist/hooks/useLinks.js +56 -0
  21. package/dist/index.d.ts +3 -1
  22. package/dist/index.d.ts.map +1 -1
  23. package/dist/index.js +4 -2
  24. package/dist/views/LinkManager/LinkManager.d.ts +9 -0
  25. package/dist/views/LinkManager/LinkManager.d.ts.map +1 -0
  26. package/dist/views/LinkManager/LinkManager.js +90 -0
  27. package/dist/views/MediaManager/MediaManager.d.ts +8 -0
  28. package/dist/views/MediaManager/MediaManager.d.ts.map +1 -0
  29. package/dist/views/MediaManager/MediaManager.js +93 -0
  30. package/dist/views/index.d.ts +10 -0
  31. package/dist/views/index.d.ts.map +1 -0
  32. package/dist/views/index.js +22 -0
  33. package/package.json +1 -1
  34. package/src/api/files.ts +192 -0
  35. package/src/api/handler.ts +2 -0
  36. package/src/api/links.ts +107 -0
  37. package/src/api/router.ts +37 -6
  38. package/src/components/DynamicLink.tsx +152 -0
  39. package/src/components/LinkSettingsModal.tsx +442 -0
  40. package/src/hooks/useLinks.ts +75 -0
  41. package/src/index.tsx +5 -3
@@ -0,0 +1,172 @@
1
+ /**
2
+ * Link Settings Modal
3
+ * Inline editor for dynamic links
4
+ */
5
+ 'use client';
6
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
7
+ import React, { useState, useEffect } from 'react';
8
+ import { createPortal } from 'react-dom';
9
+ import { X, Save, Upload, Link2, FileText, Globe, Loader2, Check, Download, Search, FolderOpen, Trash2 } from 'lucide-react';
10
+ import { motion } from 'framer-motion';
11
+ export function LinkSettingsModal({ isOpen, onClose, linkKey, siteId, locale, initialData, onSaveSuccess, apiBaseUrl = '/api/plugin-content' }) {
12
+ const [label, setLabel] = useState(initialData?.label || '');
13
+ const [target, setTarget] = useState(initialData?.target || '');
14
+ const [type, setType] = useState(initialData?.type || 'url');
15
+ const [isSaving, setIsSaving] = useState(false);
16
+ const [isUploading, setIsUploading] = useState(false);
17
+ const [mounted, setMounted] = useState(false);
18
+ // File browser state
19
+ const [isBrowsing, setIsBrowsing] = useState(false);
20
+ const [availableFiles, setAvailableFiles] = useState([]);
21
+ const [allLinks, setAllLinks] = useState([]); // To check for file usage
22
+ const [fileSearch, setFileSearch] = useState('');
23
+ const [isLoadingFiles, setIsLoadingFiles] = useState(false);
24
+ const downloadUrl = React.useMemo(() => {
25
+ if (type !== 'file' || !target)
26
+ return '';
27
+ return `${apiBaseUrl}/files/download?id=${target}&download=true`;
28
+ }, [type, target, apiBaseUrl]);
29
+ useEffect(() => {
30
+ setMounted(true);
31
+ }, []);
32
+ useEffect(() => {
33
+ if (isOpen && initialData) {
34
+ setLabel(initialData.label);
35
+ setTarget(initialData.target);
36
+ setType(initialData.type);
37
+ setIsBrowsing(false);
38
+ }
39
+ }, [isOpen, initialData]);
40
+ const fetchFilesAndLinks = async () => {
41
+ setIsLoadingFiles(true);
42
+ try {
43
+ const [filesRes, linksRes] = await Promise.all([
44
+ fetch(`${apiBaseUrl}/files?siteId=${siteId}`),
45
+ fetch(`${apiBaseUrl}/links?siteId=${siteId}`)
46
+ ]);
47
+ if (filesRes.ok) {
48
+ const data = await filesRes.json();
49
+ setAvailableFiles(data.files || []);
50
+ }
51
+ if (linksRes.ok) {
52
+ const data = await linksRes.json();
53
+ setAllLinks(data.links || []);
54
+ }
55
+ }
56
+ catch (err) {
57
+ console.error('Failed to fetch library data:', err);
58
+ }
59
+ finally {
60
+ setIsLoadingFiles(false);
61
+ }
62
+ };
63
+ // Check if a specific file is in use by any link in any language
64
+ const isFileInUse = (fileId) => {
65
+ return allLinks.some(link => Object.values(link.languages || {}).some((lang) => lang.type === 'file' && lang.target === fileId));
66
+ };
67
+ const handleDeleteFile = async (e, fileId) => {
68
+ e.stopPropagation();
69
+ if (isFileInUse(fileId)) {
70
+ alert('This file cannot be deleted because it is currently linked to a button.');
71
+ return;
72
+ }
73
+ if (!confirm('Are you sure you want to permanently delete this file?'))
74
+ return;
75
+ try {
76
+ const res = await fetch(`${apiBaseUrl}/files?siteId=${siteId}&id=${fileId}`, {
77
+ method: 'DELETE',
78
+ });
79
+ if (res.ok) {
80
+ setAvailableFiles(availableFiles.filter(f => f.id !== fileId));
81
+ if (target === fileId) {
82
+ setTarget('');
83
+ }
84
+ }
85
+ }
86
+ catch (err) {
87
+ console.error('Delete failed:', err);
88
+ }
89
+ };
90
+ const filteredFiles = React.useMemo(() => {
91
+ return availableFiles.filter(f => f.filename.toLowerCase().includes(fileSearch.toLowerCase()));
92
+ }, [availableFiles, fileSearch]);
93
+ const handleSave = async () => {
94
+ setIsSaving(true);
95
+ try {
96
+ // 1. First get existing link data to preserve other languages
97
+ const getRes = await fetch(`${apiBaseUrl}/links?siteId=${siteId}`);
98
+ let existingLink = { key: linkKey, languages: {} };
99
+ if (getRes.ok) {
100
+ const data = await getRes.json();
101
+ const found = data.links?.find((l) => l.key === linkKey);
102
+ if (found)
103
+ existingLink = found;
104
+ }
105
+ // 2. Update the specific language
106
+ const updatedLink = {
107
+ ...existingLink,
108
+ languages: {
109
+ ...existingLink.languages,
110
+ [locale]: { label, target, type }
111
+ }
112
+ };
113
+ const res = await fetch(`${apiBaseUrl}/links`, {
114
+ method: 'POST',
115
+ headers: { 'Content-Type': 'application/json' },
116
+ body: JSON.stringify({ siteId, link: updatedLink }),
117
+ });
118
+ if (res.ok) {
119
+ if (onSaveSuccess)
120
+ onSaveSuccess();
121
+ // Notify other components
122
+ window.dispatchEvent(new CustomEvent('link-updated'));
123
+ onClose();
124
+ }
125
+ }
126
+ catch (err) {
127
+ console.error('Failed to save link settings:', err);
128
+ }
129
+ finally {
130
+ setIsSaving(false);
131
+ }
132
+ };
133
+ const handleFileUpload = async (e) => {
134
+ const file = e.target.files?.[0];
135
+ if (!file)
136
+ return;
137
+ setIsUploading(true);
138
+ try {
139
+ const formData = new FormData();
140
+ formData.append('file', file);
141
+ formData.append('siteId', siteId);
142
+ const res = await fetch(`${apiBaseUrl}/files/upload`, {
143
+ method: 'POST',
144
+ body: formData,
145
+ });
146
+ if (res.ok) {
147
+ const data = await res.json();
148
+ setTarget(data.file.id);
149
+ setType('file');
150
+ }
151
+ }
152
+ catch (err) {
153
+ console.error('Upload failed:', err);
154
+ }
155
+ finally {
156
+ setIsUploading(false);
157
+ }
158
+ };
159
+ if (!mounted || !isOpen)
160
+ return null;
161
+ return createPortal(_jsx("div", { className: "fixed inset-0 z-[10000] flex items-center justify-center p-4 bg-black/60 backdrop-blur-sm", onClick: onClose, children: _jsx(motion.div, { initial: { opacity: 0, scale: 0.95, y: 20 }, animate: { opacity: 1, scale: 1, y: 0 }, className: "bg-white dark:bg-neutral-900 w-full max-w-lg rounded-[2.5rem] shadow-2xl border border-neutral-200 dark:border-neutral-800 overflow-hidden", onClick: (e) => e.stopPropagation(), children: _jsxs("div", { className: "p-8", children: [_jsxs("div", { className: "flex items-center justify-between mb-8", children: [_jsxs("div", { className: "flex items-center gap-3", children: [_jsx("div", { className: "p-3 bg-primary/10 rounded-2xl", children: _jsx(Link2, { className: "text-primary size-6" }) }), _jsxs("div", { children: [_jsx("h3", { className: "text-xl font-black uppercase tracking-tighter text-neutral-950 dark:text-white", children: "Link Settings" }), _jsxs("p", { className: "text-[10px] font-bold text-neutral-500 uppercase tracking-widest", children: ["Key: ", linkKey, " \u2022 ", locale.toUpperCase()] })] })] }), _jsx("button", { onClick: onClose, className: "p-2 hover:bg-neutral-100 dark:hover:bg-neutral-800 rounded-full transition-colors", children: _jsx(X, { size: 20 }) })] }), _jsxs("div", { className: "space-y-6", children: [_jsxs("div", { children: [_jsx("label", { className: "text-[10px] font-black uppercase tracking-widest text-neutral-400 block mb-2", children: "Display Label" }), _jsx("input", { type: "text", value: label, onChange: (e) => setLabel(e.target.value), className: "w-full bg-neutral-50 dark:bg-neutral-800 border border-neutral-200 dark:border-neutral-700 p-4 rounded-2xl text-sm font-bold outline-none focus:border-primary transition-all", placeholder: "Button text..." })] }), _jsxs("div", { className: "flex gap-2 p-1 bg-neutral-100 dark:bg-neutral-800 rounded-2xl", children: [_jsxs("button", { onClick: () => setType('url'), className: `flex-1 flex items-center justify-center gap-2 py-2.5 rounded-xl text-[10px] font-black uppercase tracking-widest transition-all ${type === 'url' ? 'bg-white dark:bg-neutral-700 shadow-sm text-primary' : 'text-neutral-500'}`, children: [_jsx(Globe, { size: 14 }), "URL / Route"] }), _jsxs("button", { onClick: () => setType('file'), className: `flex-1 flex items-center justify-center gap-2 py-2.5 rounded-xl text-[10px] font-black uppercase tracking-widest transition-all ${type === 'file' ? 'bg-white dark:bg-neutral-700 shadow-sm text-primary' : 'text-neutral-500'}`, children: [_jsx(FileText, { size: 14 }), "Download File"] })] }), _jsxs("div", { children: [_jsx("label", { className: "text-[10px] font-black uppercase tracking-widest text-neutral-400 block mb-2", children: type === 'url' ? 'Destination URL' : 'File Identifier' }), _jsxs("div", { className: "relative", children: [_jsx("input", { type: "text", value: target, onChange: (e) => setTarget(e.target.value), className: "w-full bg-neutral-50 dark:bg-neutral-800 border border-neutral-200 dark:border-neutral-700 p-4 rounded-2xl text-sm font-bold outline-none focus:border-primary transition-all", placeholder: type === 'url' ? "/about or https://..." : "Upload or select a file..." }), type === 'file' && target && (_jsxs("div", { className: "absolute right-4 top-1/2 -translate-y-1/2 flex items-center gap-2", children: [_jsx("a", { href: downloadUrl, target: "_blank", rel: "noopener noreferrer", className: "p-1.5 bg-white dark:bg-neutral-700 rounded-lg text-neutral-500 hover:text-primary transition-all shadow-sm", title: "Download File", children: _jsx(Download, { size: 14 }) }), _jsx("div", { className: "text-green-500", children: _jsx(Check, { size: 16 }) })] }))] })] }), type === 'file' && (_jsxs("div", { className: "space-y-4", children: [_jsxs("div", { className: "flex gap-2 p-1 bg-neutral-100 dark:bg-neutral-800 rounded-xl", children: [_jsxs("button", { onClick: () => setIsBrowsing(false), className: `flex-1 flex items-center justify-center gap-2 py-2 rounded-lg text-[9px] font-black uppercase tracking-widest transition-all ${!isBrowsing ? 'bg-white dark:bg-neutral-700 shadow-sm text-primary' : 'text-neutral-500'}`, children: [_jsx(Upload, { size: 12 }), "Upload New"] }), _jsxs("button", { onClick: () => {
162
+ setIsBrowsing(true);
163
+ fetchFilesAndLinks();
164
+ }, className: `flex-1 flex items-center justify-center gap-2 py-2 rounded-lg text-[9px] font-black uppercase tracking-widest transition-all ${isBrowsing ? 'bg-white dark:bg-neutral-700 shadow-sm text-primary' : 'text-neutral-500'}`, children: [_jsx(FolderOpen, { size: 12 }), "Browse Library"] })] }), isBrowsing ? (_jsxs("div", { className: "bg-neutral-50 dark:bg-neutral-800/50 rounded-2xl border border-neutral-200 dark:border-neutral-700 overflow-hidden", children: [_jsx("div", { className: "p-3 border-b border-neutral-200 dark:border-neutral-700 bg-white dark:bg-neutral-900", children: _jsxs("div", { className: "relative", children: [_jsx(Search, { className: "absolute left-3 top-1/2 -translate-y-1/2 text-neutral-400 size-3" }), _jsx("input", { type: "text", value: fileSearch, onChange: (e) => setFileSearch(e.target.value), placeholder: "Search files...", className: "w-full pl-9 pr-3 py-2 bg-neutral-50 dark:bg-neutral-800 border border-neutral-200 dark:border-neutral-700 rounded-lg text-xs outline-none focus:border-primary transition-all" })] }) }), _jsx("div", { className: "max-h-[200px] overflow-y-auto p-2 space-y-1 custom-scrollbar", children: isLoadingFiles ? (_jsx("div", { className: "flex justify-center py-8", children: _jsx(Loader2, { className: "animate-spin text-primary size-6" }) })) : filteredFiles.length === 0 ? (_jsx("p", { className: "text-center py-8 text-[10px] text-neutral-500 uppercase font-bold", children: "No files found" })) : (filteredFiles.map(file => {
165
+ const inUse = isFileInUse(file.id);
166
+ return (_jsxs("div", { className: "flex items-center gap-1 group/item", children: [_jsxs("button", { onClick: () => setTarget(file.id), className: `flex-1 flex items-center gap-3 p-3 rounded-xl transition-all text-left ${target === file.id
167
+ ? 'bg-primary/10 border border-primary/20'
168
+ : 'hover:bg-white dark:hover:bg-neutral-800 border border-transparent'}`, children: [_jsx("div", { className: "p-2 bg-white dark:bg-neutral-900 rounded-lg shadow-sm", children: _jsx(FileText, { size: 14, className: "text-primary" }) }), _jsxs("div", { className: "min-w-0 flex-1", children: [_jsx("p", { className: "text-xs font-bold text-neutral-950 dark:text-white truncate", children: file.filename }), _jsx("p", { className: "text-[9px] text-neutral-500 uppercase font-black", children: file.mimeType.split('/')[1] })] }), target === file.id && _jsx(Check, { size: 14, className: "text-primary" })] }), !inUse ? (_jsx("button", { onClick: (e) => handleDeleteFile(e, file.id), className: "p-3 text-neutral-400 hover:text-red-500 transition-colors opacity-0 group-hover/item:opacity-100", title: "Delete file", children: _jsx(Trash2, { size: 14 }) })) : (_jsx("div", { className: "p-3 text-neutral-300 cursor-not-allowed", title: "File is in use and cannot be deleted", children: _jsx(Link2, { size: 14 }) }))] }, file.id));
169
+ })) })] })) : (_jsx("div", { className: "pt-2", children: _jsxs("label", { className: `flex items-center justify-center gap-3 w-full p-4 rounded-2xl border-2 border-dashed transition-all cursor-pointer ${isUploading
170
+ ? 'border-neutral-300 bg-neutral-50 opacity-50 pointer-events-none'
171
+ : 'border-neutral-200 hover:border-primary hover:bg-primary/5'}`, children: [isUploading ? _jsx(Loader2, { className: "animate-spin text-primary" }) : _jsx(Upload, { className: "text-primary" }), _jsx("span", { className: "text-[10px] font-black uppercase tracking-widest text-neutral-600", children: isUploading ? 'Uploading...' : 'Upload PDF or Document' }), _jsx("input", { type: "file", className: "hidden", onChange: handleFileUpload, disabled: isUploading })] }) }))] }))] }), _jsxs("div", { className: "mt-10 flex gap-3", children: [_jsx("button", { onClick: onClose, className: "flex-1 px-6 py-4 rounded-full border-2 border-neutral-200 dark:border-neutral-700 text-[10px] font-black uppercase tracking-widest hover:bg-neutral-50 dark:hover:bg-neutral-800 transition-all", children: "Cancel" }), _jsxs("button", { onClick: handleSave, disabled: isSaving || !label, className: "flex-1 flex items-center justify-center gap-2 px-6 py-4 bg-primary text-white rounded-full text-[10px] font-black uppercase tracking-widest hover:bg-primary/90 transition-all shadow-lg shadow-primary/20 disabled:opacity-50 disabled:cursor-not-allowed", children: [isSaving ? _jsx(Loader2, { size: 14, className: "animate-spin" }) : _jsx(Save, { size: 14 }), isSaving ? 'Saving...' : 'Save Settings'] })] })] }) }) }), document.body);
172
+ }
@@ -0,0 +1,23 @@
1
+ /**
2
+ * useLink Hook
3
+ * React hook for fetching dynamic localized links
4
+ */
5
+ interface LocalizedLink {
6
+ label: string;
7
+ target: string;
8
+ type: 'url' | 'file';
9
+ }
10
+ interface GlobalLink {
11
+ key: string;
12
+ languages: {
13
+ [locale: string]: LocalizedLink;
14
+ };
15
+ }
16
+ export declare function useLinks(siteId?: string, apiBaseUrl?: string): {
17
+ links: GlobalLink[];
18
+ loading: boolean;
19
+ getLink: (key: string, locale: string) => LocalizedLink | null;
20
+ refetch: () => Promise<void>;
21
+ };
22
+ export {};
23
+ //# sourceMappingURL=useLinks.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"useLinks.d.ts","sourceRoot":"","sources":["../../src/hooks/useLinks.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAIH,UAAU,aAAa;IACnB,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,MAAM,CAAC;IACf,IAAI,EAAE,KAAK,GAAG,MAAM,CAAC;CACxB;AAED,UAAU,UAAU;IAChB,GAAG,EAAE,MAAM,CAAC;IACZ,SAAS,EAAE;QACP,CAAC,MAAM,EAAE,MAAM,GAAG,aAAa,CAAC;KACnC,CAAC;CACL;AAED,wBAAgB,QAAQ,CAAC,MAAM,GAAE,MAAkB,EAAE,UAAU,GAAE,MAA8B;;;mBA4BrE,MAAM,UAAU,MAAM;;EA0B/C"}
@@ -0,0 +1,56 @@
1
+ /**
2
+ * useLink Hook
3
+ * React hook for fetching dynamic localized links
4
+ */
5
+ import { useState, useEffect } from 'react';
6
+ export function useLinks(siteId = 'default', apiBaseUrl = '/api/plugin-content') {
7
+ const [links, setLinks] = useState([]);
8
+ const [loading, setLoading] = useState(true);
9
+ const fetchLinks = async () => {
10
+ try {
11
+ setLoading(true);
12
+ const res = await fetch(`${apiBaseUrl}/links?siteId=${siteId}`);
13
+ if (res.ok) {
14
+ const data = await res.json();
15
+ setLinks(data.links || []);
16
+ }
17
+ }
18
+ catch (err) {
19
+ console.error('[useLinks] Failed to fetch links:', err);
20
+ }
21
+ finally {
22
+ setLoading(false);
23
+ }
24
+ };
25
+ useEffect(() => {
26
+ fetchLinks();
27
+ // Listen for updates from other components (like the inline editor)
28
+ const handleUpdate = () => fetchLinks();
29
+ window.addEventListener('link-updated', handleUpdate);
30
+ return () => window.removeEventListener('link-updated', handleUpdate);
31
+ }, [siteId, apiBaseUrl]);
32
+ const getLink = (key, locale) => {
33
+ const link = links.find(l => l.key === key);
34
+ if (!link)
35
+ return null;
36
+ // Try exact locale match, fallback to first available or null
37
+ const localized = link.languages[locale] || Object.values(link.languages)[0] || null;
38
+ if (!localized)
39
+ return null;
40
+ // Resolve target if it's a file
41
+ if (localized.type === 'file' && localized.target && !localized.target.startsWith('http') && !localized.target.startsWith('/')) {
42
+ // Get base domain from apiBaseUrl if it exists
43
+ let baseUrl = '';
44
+ if (apiBaseUrl.startsWith('http')) {
45
+ const url = new URL(apiBaseUrl);
46
+ baseUrl = url.origin;
47
+ }
48
+ return {
49
+ ...localized,
50
+ target: `${baseUrl}/api/plugin-content/files/download?id=${localized.target}&download=true`
51
+ };
52
+ }
53
+ return localized;
54
+ };
55
+ return { links, loading, getLink, refetch: fetchLinks };
56
+ }
package/dist/index.d.ts CHANGED
@@ -14,17 +14,19 @@ export interface ContentPluginProps {
14
14
  * Content Plugin Component
15
15
  * Renders the translation editor for editing website content
16
16
  */
17
- export default function ContentPlugin({ enabled, locale, messages }: ContentPluginProps): import("react/jsx-runtime").JSX.Element | null;
17
+ export default function ContentPlugin(props: ContentPluginProps): import("react/jsx-runtime").JSX.Element | null;
18
18
  export { ContentPlugin };
19
19
  export { default as MultilineText } from './components/MultilineText';
20
20
  export type { MultilineTextProps } from './components/MultilineText';
21
21
  export { default as ParsedText } from './components/ParsedText';
22
22
  export type { ParsedTextProps } from './components/ParsedText';
23
+ export { DynamicLink } from './components/DynamicLink';
23
24
  export type { TranslationEditorProps } from './components/TranslationEditor';
24
25
  export { ParserConfigProvider, useParserConfig } from './context/ParserConfigContext';
25
26
  export type { ParserConfigProviderProps } from './context/ParserConfigContext';
26
27
  export { useParse } from './hooks/useParse';
27
28
  export { useLocaleSync } from './hooks/useLocaleSync';
29
+ export { useLinks } from './hooks/useLinks';
28
30
  export { parse } from './utils/parser';
29
31
  export { defaultParserConfig } from './utils/parser-config';
30
32
  export type { ParserConfig, FormatStyle } from './utils/parser-config';
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.tsx"],"names":[],"mappings":"AAAA;;;GAGG;AAQH,MAAM,WAAW,kBAAkB;IAC/B,iDAAiD;IACjD,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,+EAA+E;IAC/E,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,iFAAiF;IACjF,QAAQ,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC;CAClC;AAoBD;;;GAGG;AACH,MAAM,CAAC,OAAO,UAAU,aAAa,CAAC,EAAE,OAAc,EAAE,MAAM,EAAE,QAAQ,EAAE,EAAE,kBAAkB,kDAmB7F;AAkBD,OAAO,EAAE,aAAa,EAAE,CAAC;AAGzB,OAAO,EAAE,OAAO,IAAI,aAAa,EAAE,MAAM,4BAA4B,CAAC;AACtE,YAAY,EAAE,kBAAkB,EAAE,MAAM,4BAA4B,CAAC;AACrE,OAAO,EAAE,OAAO,IAAI,UAAU,EAAE,MAAM,yBAAyB,CAAC;AAChE,YAAY,EAAE,eAAe,EAAE,MAAM,yBAAyB,CAAC;AAC/D,YAAY,EAAE,sBAAsB,EAAE,MAAM,gCAAgC,CAAC;AAG7E,OAAO,EAAE,oBAAoB,EAAE,eAAe,EAAE,MAAM,+BAA+B,CAAC;AACtF,YAAY,EAAE,yBAAyB,EAAE,MAAM,+BAA+B,CAAC;AAG/E,OAAO,EAAE,QAAQ,EAAE,MAAM,kBAAkB,CAAC;AAC5C,OAAO,EAAE,aAAa,EAAE,MAAM,uBAAuB,CAAC;AAItD,OAAO,EAAE,KAAK,EAAE,MAAM,gBAAgB,CAAC;AAGvC,OAAO,EAAE,mBAAmB,EAAE,MAAM,uBAAuB,CAAC;AAC5D,YAAY,EAAE,YAAY,EAAE,WAAW,EAAE,MAAM,uBAAuB,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.tsx"],"names":[],"mappings":"AAAA;;;GAGG;AAQH,MAAM,WAAW,kBAAkB;IAC/B,iDAAiD;IACjD,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,+EAA+E;IAC/E,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,iFAAiF;IACjF,QAAQ,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC;CAClC;AAoBD;;;GAGG;AACH,MAAM,CAAC,OAAO,UAAU,aAAa,CAAC,KAAK,EAAE,kBAAkB,kDAoB9D;AAkBD,OAAO,EAAE,aAAa,EAAE,CAAC;AAGzB,OAAO,EAAE,OAAO,IAAI,aAAa,EAAE,MAAM,4BAA4B,CAAC;AACtE,YAAY,EAAE,kBAAkB,EAAE,MAAM,4BAA4B,CAAC;AACrE,OAAO,EAAE,OAAO,IAAI,UAAU,EAAE,MAAM,yBAAyB,CAAC;AAChE,YAAY,EAAE,eAAe,EAAE,MAAM,yBAAyB,CAAC;AAC/D,OAAO,EAAE,WAAW,EAAE,MAAM,0BAA0B,CAAC;AACvD,YAAY,EAAE,sBAAsB,EAAE,MAAM,gCAAgC,CAAC;AAG7E,OAAO,EAAE,oBAAoB,EAAE,eAAe,EAAE,MAAM,+BAA+B,CAAC;AACtF,YAAY,EAAE,yBAAyB,EAAE,MAAM,+BAA+B,CAAC;AAG/E,OAAO,EAAE,QAAQ,EAAE,MAAM,kBAAkB,CAAC;AAC5C,OAAO,EAAE,aAAa,EAAE,MAAM,uBAAuB,CAAC;AACtD,OAAO,EAAE,QAAQ,EAAE,MAAM,kBAAkB,CAAC;AAI5C,OAAO,EAAE,KAAK,EAAE,MAAM,gBAAgB,CAAC;AAGvC,OAAO,EAAE,mBAAmB,EAAE,MAAM,uBAAuB,CAAC;AAC5D,YAAY,EAAE,YAAY,EAAE,WAAW,EAAE,MAAM,uBAAuB,CAAC"}
package/dist/index.js CHANGED
@@ -20,7 +20,8 @@ function ClientOnly({ children }) {
20
20
  * Content Plugin Component
21
21
  * Renders the translation editor for editing website content
22
22
  */
23
- export default function ContentPlugin({ enabled = true, locale, messages }) {
23
+ export default function ContentPlugin(props) {
24
+ const { enabled = true, locale, messages } = props;
24
25
  if (!enabled)
25
26
  return null;
26
27
  // If locale and messages are provided as props, use them directly
@@ -28,7 +29,6 @@ export default function ContentPlugin({ enabled = true, locale, messages }) {
28
29
  return (_jsx(ClientOnly, { children: _jsx(ContentPluginWithIntl, { locale: locale, messages: messages }) }));
29
30
  }
30
31
  // Otherwise, try to use context (for backward compatibility)
31
- // This path might have issues with context not being found
32
32
  return (_jsx(ClientOnly, { children: _jsx(ContentPluginWithIntlFallback, {}) }));
33
33
  }
34
34
  function ContentPluginWithIntlFallback() {
@@ -43,11 +43,13 @@ export { ContentPlugin };
43
43
  // Export components
44
44
  export { default as MultilineText } from './components/MultilineText';
45
45
  export { default as ParsedText } from './components/ParsedText';
46
+ export { DynamicLink } from './components/DynamicLink';
46
47
  // Export context
47
48
  export { ParserConfigProvider, useParserConfig } from './context/ParserConfigContext';
48
49
  // Export hooks
49
50
  export { useParse } from './hooks/useParse';
50
51
  export { useLocaleSync } from './hooks/useLocaleSync';
52
+ export { useLinks } from './hooks/useLinks';
51
53
  // Export utilities
52
54
  // Note: parse() is a client-only function. Use ParsedText component in server components.
53
55
  export { parse } from './utils/parser';
@@ -0,0 +1,9 @@
1
+ /**
2
+ * Link Manager View
3
+ * Allows defining global, localized links and buttons
4
+ */
5
+ export declare function LinkManagerView({ siteId, locale: defaultLocale }: {
6
+ siteId: string;
7
+ locale: string;
8
+ }): import("react/jsx-runtime").JSX.Element;
9
+ //# sourceMappingURL=LinkManager.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"LinkManager.d.ts","sourceRoot":"","sources":["../../../src/views/LinkManager/LinkManager.tsx"],"names":[],"mappings":"AAAA;;;GAGG;AAsBH,wBAAgB,eAAe,CAAC,EAAE,MAAM,EAAE,MAAM,EAAE,aAAa,EAAE,EAAE;IAAE,MAAM,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,2CAuMpG"}
@@ -0,0 +1,90 @@
1
+ /**
2
+ * Link Manager View
3
+ * Allows defining global, localized links and buttons
4
+ */
5
+ 'use client';
6
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
7
+ import { useState, useEffect } from 'react';
8
+ import { Plus, Link2, Trash2, Globe, Save } from 'lucide-react';
9
+ export function LinkManagerView({ siteId, locale: defaultLocale }) {
10
+ const [links, setLinks] = useState([]);
11
+ const [loading, setLoading] = useState(true);
12
+ const [activeTab, setActiveTab] = useState('links');
13
+ useEffect(() => {
14
+ // Fetch links from API
15
+ const fetchLinks = async () => {
16
+ try {
17
+ const res = await fetch(`/api/plugin-content/links?siteId=${siteId}`);
18
+ if (res.ok) {
19
+ const data = await res.json();
20
+ setLinks(data.links || []);
21
+ }
22
+ }
23
+ catch (err) {
24
+ console.error('Failed to fetch links:', err);
25
+ }
26
+ finally {
27
+ setLoading(false);
28
+ }
29
+ };
30
+ fetchLinks();
31
+ }, [siteId]);
32
+ const handleAddLink = () => {
33
+ const newLink = {
34
+ key: `new-link-${Date.now()}`,
35
+ languages: {
36
+ [defaultLocale]: { label: 'New Link', target: '', type: 'url' }
37
+ }
38
+ };
39
+ setLinks([...links, newLink]);
40
+ };
41
+ const handleSaveLink = async (link) => {
42
+ try {
43
+ const res = await fetch('/api/plugin-content/links', {
44
+ method: 'POST',
45
+ headers: { 'Content-Type': 'application/json' },
46
+ body: JSON.stringify({ siteId, link }),
47
+ });
48
+ if (res.ok) {
49
+ // Refresh list or update local state
50
+ const result = await res.json();
51
+ setLinks(links.map(l => l.key === link.key ? result.link : l));
52
+ }
53
+ }
54
+ catch (err) {
55
+ console.error('Failed to save link:', err);
56
+ }
57
+ };
58
+ const handleDeleteLink = async (key) => {
59
+ if (!confirm('Are you sure you want to delete this link?'))
60
+ return;
61
+ try {
62
+ const res = await fetch(`/api/plugin-content/links?siteId=${siteId}&key=${key}`, {
63
+ method: 'DELETE',
64
+ });
65
+ if (res.ok) {
66
+ setLinks(links.filter(l => l.key !== key));
67
+ }
68
+ }
69
+ catch (err) {
70
+ console.error('Failed to delete link:', err);
71
+ }
72
+ };
73
+ return (_jsxs("div", { className: "p-8 bg-white dark:bg-neutral-900 rounded-[2.5rem] h-full overflow-y-auto", children: [_jsxs("div", { className: "flex items-center justify-between mb-8", children: [_jsxs("div", { children: [_jsx("h1", { className: "text-3xl font-black uppercase tracking-tighter text-neutral-950 dark:text-white", children: "Link & Button Manager" }), _jsx("p", { className: "text-sm text-neutral-500 dark:text-neutral-400", children: "Manage dynamic buttons and links across your application" })] }), _jsxs("button", { onClick: handleAddLink, className: "flex items-center gap-2 px-6 py-3 bg-primary text-white rounded-full text-[10px] font-black uppercase tracking-widest hover:bg-primary/90 transition-all shadow-lg shadow-primary/20", children: [_jsx(Plus, { size: 16 }), "Define New Link"] })] }), _jsx("div", { className: "space-y-6", children: links.length === 0 && !loading ? (_jsxs("div", { className: "text-center py-20 bg-neutral-50 dark:bg-neutral-800/50 rounded-[2rem] border-2 border-dashed border-neutral-200 dark:border-neutral-700", children: [_jsx(Link2, { className: "mx-auto size-12 text-neutral-300 dark:text-neutral-600 mb-4" }), _jsx("p", { className: "text-neutral-500", children: "No dynamic links defined yet." })] })) : (links.map((link) => (_jsxs("div", { className: "bg-neutral-50 dark:bg-neutral-800/50 p-6 rounded-[2rem] border border-neutral-200 dark:border-neutral-700", children: [_jsxs("div", { className: "flex items-start justify-between mb-6", children: [_jsxs("div", { className: "flex-1 max-w-md", children: [_jsx("label", { className: "text-[10px] font-black uppercase tracking-widest text-neutral-400 block mb-2", children: "System Key (Unique ID)" }), _jsx("input", { type: "text", value: link.key, onChange: (e) => setLinks(links.map(l => l.key === link.key ? { ...l, key: e.target.value } : l)), className: "w-full bg-white dark:bg-neutral-900 border border-neutral-200 dark:border-neutral-700 p-3 rounded-xl text-sm font-bold outline-none focus:border-primary transition-all", placeholder: "e.g. hero-primary-button" })] }), _jsxs("div", { className: "flex gap-2", children: [_jsx("button", { onClick: () => handleSaveLink(link), className: "p-3 bg-white dark:bg-neutral-900 text-primary border border-neutral-200 dark:border-neutral-700 rounded-xl hover:border-primary transition-all", title: "Save Link", children: _jsx(Save, { size: 18 }) }), _jsx("button", { onClick: () => handleDeleteLink(link.key), className: "p-3 bg-white dark:bg-neutral-900 text-red-500 border border-neutral-200 dark:border-neutral-700 rounded-xl hover:border-red-500 transition-all", title: "Delete Link", children: _jsx(Trash2, { size: 18 }) })] })] }), _jsxs("div", { className: "grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6", children: [Object.entries(link.languages).map(([locale, data]) => (_jsxs("div", { className: "bg-white dark:bg-neutral-900 p-5 rounded-2xl border border-neutral-100 dark:border-neutral-800", children: [_jsxs("div", { className: "flex items-center gap-2 mb-4", children: [_jsx(Globe, { size: 14, className: "text-primary" }), _jsxs("span", { className: "text-[10px] font-black uppercase tracking-widest text-neutral-500", children: [locale.toUpperCase(), " Translation"] })] }), _jsxs("div", { className: "space-y-4", children: [_jsxs("div", { children: [_jsx("label", { className: "text-[9px] font-bold uppercase text-neutral-400 block mb-1", children: "Label" }), _jsx("input", { type: "text", value: data.label, onChange: (e) => {
74
+ const newLangs = { ...link.languages, [locale]: { ...data, label: e.target.value } };
75
+ setLinks(links.map(l => l.key === link.key ? { ...l, languages: newLangs } : l));
76
+ }, className: "w-full bg-neutral-50 dark:bg-neutral-800 border border-neutral-200 dark:border-neutral-700 p-2 rounded-lg text-xs outline-none focus:border-primary transition-all" })] }), _jsxs("div", { children: [_jsx("label", { className: "text-[9px] font-bold uppercase text-neutral-400 block mb-1", children: "Target" }), _jsxs("div", { className: "flex gap-2", children: [_jsxs("select", { value: data.type, onChange: (e) => {
77
+ const newType = e.target.value;
78
+ const newLangs = { ...link.languages, [locale]: { ...data, type: newType } };
79
+ setLinks(links.map(l => l.key === link.key ? { ...l, languages: newLangs } : l));
80
+ }, className: "bg-neutral-50 dark:bg-neutral-800 border border-neutral-200 dark:border-neutral-700 p-2 rounded-lg text-[10px] outline-none", children: [_jsx("option", { value: "url", children: "URL" }), _jsx("option", { value: "file", children: "File" })] }), _jsx("input", { type: "text", value: data.target, onChange: (e) => {
81
+ const newLangs = { ...link.languages, [locale]: { ...data, target: e.target.value } };
82
+ setLinks(links.map(l => l.key === link.key ? { ...l, languages: newLangs } : l));
83
+ }, className: "flex-1 bg-neutral-50 dark:bg-neutral-800 border border-neutral-200 dark:border-neutral-700 p-2 rounded-lg text-xs outline-none focus:border-primary transition-all", placeholder: data.type === 'url' ? "/about or https://..." : "file-id-..." })] })] })] })] }, locale))), _jsxs("button", { onClick: () => {
84
+ const newLocale = prompt('Enter locale (e.g. en, nl, sv):');
85
+ if (newLocale) {
86
+ const newLangs = { ...link.languages, [newLocale]: { label: '', target: '', type: 'url' } };
87
+ setLinks(links.map(l => l.key === link.key ? { ...l, languages: newLangs } : l));
88
+ }
89
+ }, className: "flex flex-col items-center justify-center p-5 rounded-2xl border-2 border-dashed border-neutral-200 dark:border-neutral-700 hover:border-primary transition-all text-neutral-400 hover:text-primary", children: [_jsx(Plus, { size: 20 }), _jsx("span", { className: "text-[10px] font-black uppercase mt-2", children: "Add translation" })] })] })] }, link.key)))) })] }));
90
+ }
@@ -0,0 +1,8 @@
1
+ /**
2
+ * Media Manager View
3
+ * Allows uploading and managing general files (PDFs, docs, etc.)
4
+ */
5
+ export declare function MediaManagerView({ siteId }: {
6
+ siteId: string;
7
+ }): import("react/jsx-runtime").JSX.Element;
8
+ //# sourceMappingURL=MediaManager.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"MediaManager.d.ts","sourceRoot":"","sources":["../../../src/views/MediaManager/MediaManager.tsx"],"names":[],"mappings":"AAAA;;;GAGG;AAgBH,wBAAgB,gBAAgB,CAAC,EAAE,MAAM,EAAE,EAAE;IAAE,MAAM,EAAE,MAAM,CAAA;CAAE,2CAgL9D"}
@@ -0,0 +1,93 @@
1
+ /**
2
+ * Media Manager View
3
+ * Allows uploading and managing general files (PDFs, docs, etc.)
4
+ */
5
+ 'use client';
6
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
7
+ import { useState, useEffect, useCallback } from 'react';
8
+ import { Upload, File, Trash2, Search, Copy, Check, FileText, ExternalLink } from 'lucide-react';
9
+ export function MediaManagerView({ siteId }) {
10
+ const [files, setFiles] = useState([]);
11
+ const [loading, setLoading] = useState(true);
12
+ const [uploading, setUploading] = useState(false);
13
+ const [searchQuery, setSearchQuery] = useState('');
14
+ const [copiedId, setCopiedId] = useState(null);
15
+ const fetchFiles = useCallback(async () => {
16
+ try {
17
+ setLoading(true);
18
+ const res = await fetch(`/api/plugin-content/files?siteId=${siteId}`);
19
+ if (res.ok) {
20
+ const data = await res.json();
21
+ setFiles(data.files || []);
22
+ }
23
+ }
24
+ catch (err) {
25
+ console.error('Failed to fetch files:', err);
26
+ }
27
+ finally {
28
+ setLoading(false);
29
+ }
30
+ }, [siteId]);
31
+ useEffect(() => {
32
+ fetchFiles();
33
+ }, [fetchFiles]);
34
+ const handleUpload = async (e) => {
35
+ const file = e.target.files?.[0];
36
+ if (!file)
37
+ return;
38
+ try {
39
+ setUploading(true);
40
+ const formData = new FormData();
41
+ formData.append('file', file);
42
+ formData.append('siteId', siteId);
43
+ const res = await fetch('/api/plugin-content/files/upload', {
44
+ method: 'POST',
45
+ body: formData,
46
+ });
47
+ if (res.ok) {
48
+ fetchFiles();
49
+ }
50
+ else {
51
+ const error = await res.json();
52
+ alert(error.error || 'Upload failed');
53
+ }
54
+ }
55
+ catch (err) {
56
+ console.error('Upload error:', err);
57
+ alert('An error occurred during upload');
58
+ }
59
+ finally {
60
+ setUploading(false);
61
+ }
62
+ };
63
+ const handleDelete = async (id) => {
64
+ if (!confirm('Are you sure you want to delete this file?'))
65
+ return;
66
+ try {
67
+ const res = await fetch(`/api/plugin-content/files?siteId=${siteId}&id=${id}`, {
68
+ method: 'DELETE',
69
+ });
70
+ if (res.ok) {
71
+ setFiles(files.filter(f => f.id !== id));
72
+ }
73
+ }
74
+ catch (err) {
75
+ console.error('Delete error:', err);
76
+ }
77
+ };
78
+ const copyToClipboard = (text) => {
79
+ navigator.clipboard.writeText(text);
80
+ setCopiedId(text);
81
+ setTimeout(() => setCopiedId(null), 2000);
82
+ };
83
+ const filteredFiles = files.filter(f => f.filename.toLowerCase().includes(searchQuery.toLowerCase()));
84
+ const formatSize = (bytes) => {
85
+ if (bytes === 0)
86
+ return '0 Bytes';
87
+ const k = 1024;
88
+ const sizes = ['Bytes', 'KB', 'MB', 'GB'];
89
+ const i = Math.floor(Math.log(bytes) / Math.log(k));
90
+ return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i];
91
+ };
92
+ return (_jsxs("div", { className: "p-8 bg-white dark:bg-neutral-900 rounded-[2.5rem] h-full flex flex-col", children: [_jsxs("div", { className: "flex items-center justify-between mb-8", children: [_jsxs("div", { children: [_jsx("h1", { className: "text-3xl font-black uppercase tracking-tighter text-neutral-950 dark:text-white", children: "File Library" }), _jsx("p", { className: "text-sm text-neutral-500 dark:text-neutral-400", children: "Upload and manage documents, PDFs, and other assets" })] }), _jsxs("label", { className: `flex items-center gap-2 px-6 py-3 bg-primary text-white rounded-full text-[10px] font-black uppercase tracking-widest cursor-pointer hover:bg-primary/90 transition-all shadow-lg shadow-primary/20 ${uploading ? 'opacity-50 pointer-events-none' : ''}`, children: [_jsx(Upload, { size: 16 }), uploading ? 'Uploading...' : 'Upload File', _jsx("input", { type: "file", className: "hidden", onChange: handleUpload, disabled: uploading })] })] }), _jsxs("div", { className: "relative mb-6", children: [_jsx(Search, { className: "absolute left-4 top-1/2 -translate-y-1/2 text-neutral-400 size-4" }), _jsx("input", { type: "text", value: searchQuery, onChange: (e) => setSearchQuery(e.target.value), placeholder: "Search files by name...", className: "w-full pl-11 pr-4 py-3 bg-neutral-50 dark:bg-neutral-800 border border-neutral-200 dark:border-neutral-700 rounded-2xl text-sm outline-none focus:border-primary transition-all" })] }), _jsx("div", { className: "flex-1 overflow-y-auto custom-scrollbar", children: loading ? (_jsx("div", { className: "flex items-center justify-center py-20", children: _jsx("div", { className: "w-8 h-8 border-4 border-primary/20 border-t-primary rounded-full animate-spin" }) })) : filteredFiles.length === 0 ? (_jsxs("div", { className: "text-center py-20 bg-neutral-50 dark:bg-neutral-800/50 rounded-[2rem] border-2 border-dashed border-neutral-200 dark:border-neutral-700", children: [_jsx(FileText, { className: "mx-auto size-12 text-neutral-300 dark:text-neutral-600 mb-4" }), _jsx("p", { className: "text-neutral-500", children: searchQuery ? 'No files match your search.' : 'No files uploaded yet.' })] })) : (_jsx("div", { className: "grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 gap-6", children: filteredFiles.map((file) => (_jsxs("div", { className: "bg-neutral-50 dark:bg-neutral-800/50 p-5 rounded-[2rem] border border-neutral-200 dark:border-neutral-700 group transition-all hover:shadow-xl hover:border-primary/30", children: [_jsxs("div", { className: "flex items-center gap-4 mb-4", children: [_jsx("div", { className: "p-3 bg-white dark:bg-neutral-900 rounded-2xl shadow-sm group-hover:bg-primary/10 transition-colors", children: _jsx(File, { size: 24, className: "text-primary" }) }), _jsxs("div", { className: "min-w-0 flex-1", children: [_jsx("h3", { className: "font-bold text-sm text-neutral-950 dark:text-white truncate", title: file.filename, children: file.filename }), _jsxs("p", { className: "text-[10px] text-neutral-500 uppercase font-black tracking-tight", children: [formatSize(file.size), " \u2022 ", file.mimeType.split('/')[1].toUpperCase()] })] })] }), _jsxs("div", { className: "flex items-center gap-2 pt-4 border-t border-neutral-200 dark:border-neutral-700", children: [_jsxs("button", { onClick: () => copyToClipboard(file.id), className: "flex-1 flex items-center justify-center gap-2 py-2 bg-white dark:bg-neutral-900 rounded-xl text-[9px] font-black uppercase tracking-widest text-neutral-600 dark:text-neutral-400 hover:text-primary transition-all border border-neutral-200 dark:border-neutral-700", children: [copiedId === file.id ? _jsx(Check, { size: 12 }) : _jsx(Copy, { size: 12 }), copiedId === file.id ? 'Copied' : 'ID'] }), _jsx("a", { href: file.url, target: "_blank", rel: "noopener noreferrer", className: "p-2 bg-white dark:bg-neutral-900 rounded-xl text-neutral-600 dark:text-neutral-400 hover:text-blue-500 transition-all border border-neutral-200 dark:border-neutral-700", title: "View/Download", children: _jsx(ExternalLink, { size: 14 }) }), _jsx("button", { onClick: () => handleDelete(file.id), className: "p-2 bg-white dark:bg-neutral-900 rounded-xl text-neutral-600 dark:text-neutral-400 hover:text-red-500 transition-all border border-neutral-200 dark:border-neutral-700", title: "Delete", children: _jsx(Trash2, { size: 14 }) })] })] }, file.id))) })) })] }));
93
+ }
@@ -0,0 +1,10 @@
1
+ /**
2
+ * Asset Manager View
3
+ * Main entry point for managing links and files
4
+ */
5
+ export declare function AssetManagerView({ siteId, locale, subPath }: {
6
+ siteId: string;
7
+ locale: string;
8
+ subPath: string[];
9
+ }): import("react/jsx-runtime").JSX.Element;
10
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/views/index.tsx"],"names":[],"mappings":"AAAA;;;GAGG;AASH,wBAAgB,gBAAgB,CAAC,EAAE,MAAM,EAAE,MAAM,EAAE,OAAO,EAAE,EAAE;IAAE,MAAM,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,MAAM,EAAE,CAAA;CAAE,2CAkElH"}
@@ -0,0 +1,22 @@
1
+ /**
2
+ * Asset Manager View
3
+ * Main entry point for managing links and files
4
+ */
5
+ 'use client';
6
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
7
+ import { Link2, FileText, ChevronRight } from 'lucide-react';
8
+ import { LinkManagerView } from './LinkManager/LinkManager';
9
+ import { MediaManagerView } from './MediaManager/MediaManager';
10
+ export function AssetManagerView({ siteId, locale, subPath }) {
11
+ const route = subPath[1] || 'links';
12
+ const navigate = (path) => {
13
+ window.history.pushState(null, '', `/dashboard/content/${path}`);
14
+ // Force re-render by triggering popstate or similar if needed,
15
+ // but for now we'll just handle it via state if we were in a single view
16
+ };
17
+ return (_jsxs("div", { className: "flex h-full w-full overflow-hidden", children: [_jsxs("aside", { className: "w-64 border-r border-dashboard-border bg-dashboard-sidebar flex flex-col p-6", children: [_jsx("h2", { className: "text-[10px] font-black uppercase tracking-[0.2em] text-neutral-400 mb-8", children: "Content Assets" }), _jsxs("div", { className: "space-y-2", children: [_jsxs("button", { onClick: () => window.location.href = '/dashboard/content/links', className: `w-full flex items-center justify-between p-4 rounded-2xl transition-all ${route === 'links'
18
+ ? 'bg-primary text-white shadow-lg shadow-primary/20'
19
+ : 'text-neutral-500 hover:bg-neutral-100 dark:hover:bg-neutral-800'}`, children: [_jsxs("div", { className: "flex items-center gap-3", children: [_jsx(Link2, { size: 18 }), _jsx("span", { className: "text-sm font-bold", children: "Links & Buttons" })] }), _jsx(ChevronRight, { size: 14, className: route === 'links' ? 'opacity-100' : 'opacity-0' })] }), _jsxs("button", { onClick: () => window.location.href = '/dashboard/content/media', className: `w-full flex items-center justify-between p-4 rounded-2xl transition-all ${route === 'media'
20
+ ? 'bg-primary text-white shadow-lg shadow-primary/20'
21
+ : 'text-neutral-500 hover:bg-neutral-100 dark:hover:bg-neutral-800'}`, children: [_jsxs("div", { className: "flex items-center gap-3", children: [_jsx(FileText, { size: 18 }), _jsx("span", { className: "text-sm font-bold", children: "File Library" })] }), _jsx(ChevronRight, { size: 14, className: route === 'media' ? 'opacity-100' : 'opacity-0' })] })] }), _jsx("div", { className: "mt-auto p-4 bg-dashboard-bg rounded-2xl border border-dashboard-border", children: _jsx("p", { className: "text-[10px] text-neutral-500 leading-relaxed font-medium", children: "Use these assets to make your application buttons and downloads dynamic." }) })] }), _jsx("main", { className: "flex-1 min-w-0 overflow-hidden", children: route === 'links' ? (_jsx(LinkManagerView, { siteId: siteId, locale: locale })) : (_jsx(MediaManagerView, { siteId: siteId })) })] }));
22
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@jhits/plugin-content",
3
- "version": "0.0.15",
3
+ "version": "0.0.16",
4
4
  "description": "Content management and localization plugin for the JHITS ecosystem",
5
5
  "publishConfig": {
6
6
  "access": "public"