@mdguggenbichler/slugbase-core 0.0.29 → 0.0.31
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/frontend/src/components/sharing/ShareResourceDialog.tsx +45 -40
- package/frontend/src/config/docs.ts +19 -0
- package/frontend/src/config/mode.ts +6 -4
- package/frontend/src/pages/Folders.tsx +0 -2
- package/frontend/src/pages/Profile.tsx +3 -2
- package/frontend/src/pages/Signup.tsx +3 -2
- package/frontend/src/pages/Tags.tsx +0 -2
- package/frontend/src/pages/admin/AdminLayout.tsx +2 -1
- package/package.json +1 -1
|
@@ -14,7 +14,7 @@ import { ScrollArea } from '../ui/scroll-area';
|
|
|
14
14
|
import Button from '../ui/Button';
|
|
15
15
|
import Tooltip from '../ui/Tooltip';
|
|
16
16
|
import { useToast } from '../ui/Toast';
|
|
17
|
-
import {
|
|
17
|
+
import { User, Users, X } from 'lucide-react';
|
|
18
18
|
import { cn } from '@/lib/utils';
|
|
19
19
|
|
|
20
20
|
interface SharedUser {
|
|
@@ -146,9 +146,10 @@ export default function ShareResourceDialog({
|
|
|
146
146
|
const msg = err.response?.data?.error || t('common.error');
|
|
147
147
|
setError(msg);
|
|
148
148
|
showToast(msg, 'error');
|
|
149
|
-
} finally {
|
|
150
149
|
setSaving(false);
|
|
150
|
+
throw err;
|
|
151
151
|
}
|
|
152
|
+
setSaving(false);
|
|
152
153
|
}
|
|
153
154
|
|
|
154
155
|
function handleRemoveUser(userId: string) {
|
|
@@ -162,11 +163,19 @@ export default function ShareResourceDialog({
|
|
|
162
163
|
}
|
|
163
164
|
|
|
164
165
|
function handleAddUser(userId: string) {
|
|
166
|
+
if (sharedUsers.some((u) => u.id === userId)) return;
|
|
167
|
+
const userToAdd = allUsers.find((u) => u.id === userId);
|
|
165
168
|
const newUserIds = [...sharedUsers.map((u) => u.id), userId];
|
|
166
|
-
if (
|
|
167
|
-
|
|
169
|
+
if (userToAdd) {
|
|
170
|
+
setSharedUsers((prev) => [...prev, userToAdd]);
|
|
171
|
+
}
|
|
168
172
|
setPeopleDropdownOpen(false);
|
|
169
173
|
setEmailInput('');
|
|
174
|
+
updateShares(newUserIds, sharedTeams.map((t) => t.id), false).catch(() => {
|
|
175
|
+
if (userToAdd) {
|
|
176
|
+
setSharedUsers((prev) => prev.filter((u) => u.id !== userId));
|
|
177
|
+
}
|
|
178
|
+
});
|
|
170
179
|
}
|
|
171
180
|
|
|
172
181
|
function handleAddUserByEmail() {
|
|
@@ -181,9 +190,17 @@ export default function ShareResourceDialog({
|
|
|
181
190
|
}
|
|
182
191
|
|
|
183
192
|
function handleAddTeam(teamId: string) {
|
|
193
|
+
if (sharedTeams.some((t) => t.id === teamId)) return;
|
|
194
|
+
const teamToAdd = teams.find((t) => t.id === teamId);
|
|
184
195
|
const newTeamIds = [...sharedTeams.map((t) => t.id), teamId];
|
|
185
|
-
if (
|
|
186
|
-
|
|
196
|
+
if (teamToAdd) {
|
|
197
|
+
setSharedTeams((prev) => [...prev, teamToAdd]);
|
|
198
|
+
}
|
|
199
|
+
updateShares(sharedUsers.map((u) => u.id), newTeamIds, false).catch(() => {
|
|
200
|
+
if (teamToAdd) {
|
|
201
|
+
setSharedTeams((prev) => prev.filter((t) => t.id !== teamId));
|
|
202
|
+
}
|
|
203
|
+
});
|
|
187
204
|
}
|
|
188
205
|
|
|
189
206
|
const searchQuery = emailInput.trim().toLowerCase();
|
|
@@ -326,41 +343,29 @@ export default function ShareResourceDialog({
|
|
|
326
343
|
{allowShareToUsers && (activeTab === 'people' || !allowShareToTeams) && (
|
|
327
344
|
<div className="space-y-2">
|
|
328
345
|
<div className="relative">
|
|
329
|
-
<
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
346
|
+
<Input
|
|
347
|
+
placeholder={t('admin.searchUsers')}
|
|
348
|
+
value={emailInput}
|
|
349
|
+
onChange={(e) => {
|
|
350
|
+
setEmailInput(e.target.value);
|
|
351
|
+
setPeopleDropdownOpen(true);
|
|
352
|
+
}}
|
|
353
|
+
onFocus={() => {
|
|
354
|
+
if (searchQuery.length >= MIN_CHARS_FOR_USER_DROPDOWN)
|
|
335
355
|
setPeopleDropdownOpen(true);
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
}}
|
|
350
|
-
className="flex-1"
|
|
351
|
-
autoComplete="off"
|
|
352
|
-
/>
|
|
353
|
-
<Button
|
|
354
|
-
type="button"
|
|
355
|
-
variant="outline"
|
|
356
|
-
size="sm"
|
|
357
|
-
disabled={saving}
|
|
358
|
-
onClick={() => handleAddUserByEmail()}
|
|
359
|
-
>
|
|
360
|
-
<UserPlus className="h-4 w-4" />
|
|
361
|
-
{t('sharing.add')}
|
|
362
|
-
</Button>
|
|
363
|
-
</div>
|
|
356
|
+
}}
|
|
357
|
+
onBlur={() => {
|
|
358
|
+
setTimeout(() => setPeopleDropdownOpen(false), 150);
|
|
359
|
+
}}
|
|
360
|
+
onKeyDown={(e) => {
|
|
361
|
+
if (e.key === 'Enter') {
|
|
362
|
+
e.preventDefault();
|
|
363
|
+
handleAddUserByEmail();
|
|
364
|
+
}
|
|
365
|
+
}}
|
|
366
|
+
className="w-full"
|
|
367
|
+
autoComplete="off"
|
|
368
|
+
/>
|
|
364
369
|
{peopleDropdownOpen && showUserDropdown && (
|
|
365
370
|
<div
|
|
366
371
|
className="absolute top-full left-0 right-0 z-10 mt-1 max-h-48 overflow-auto rounded-md border bg-popover shadow-md"
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Documentation URLs at docs.slugbase.app.
|
|
3
|
+
* Paths differ by mode: selfhosted (/selfhosted/...) vs cloud (/cloud/...).
|
|
4
|
+
*/
|
|
5
|
+
import { isCloud } from './mode';
|
|
6
|
+
|
|
7
|
+
const DOCS_BASE = 'https://docs.slugbase.app';
|
|
8
|
+
|
|
9
|
+
/** URL to the API Reference section (selfhosted or cloud). */
|
|
10
|
+
export function getDocsApiReferenceUrl(): string {
|
|
11
|
+
const path = isCloud ? 'cloud/api-reference' : 'selfhosted/api-reference';
|
|
12
|
+
return `${DOCS_BASE}/${path}`;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
/** URL to docs home/overview (selfhosted intro or cloud overview). */
|
|
16
|
+
export function getDocsBaseUrl(): string {
|
|
17
|
+
const path = isCloud ? 'cloud/overview' : 'selfhosted/intro';
|
|
18
|
+
return `${DOCS_BASE}/${path}`;
|
|
19
|
+
}
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* SlugBase frontend runtime mode
|
|
2
|
+
* SlugBase frontend runtime mode: self-hosted or cloud.
|
|
3
|
+
* Derived from VITE_SLUGBASE_MODE at build time (cloud builds set it to 'cloud').
|
|
3
4
|
*/
|
|
4
|
-
|
|
5
|
-
export const
|
|
6
|
-
export const
|
|
5
|
+
const buildMode = import.meta.env.VITE_SLUGBASE_MODE === 'cloud' ? 'cloud' : 'selfhosted';
|
|
6
|
+
export const mode: 'selfhosted' | 'cloud' = buildMode;
|
|
7
|
+
export const isCloud = buildMode === 'cloud';
|
|
8
|
+
export const isSelfhosted = !isCloud;
|
|
@@ -14,6 +14,7 @@ import { Card, CardHeader, CardTitle, CardDescription, CardContent, CardFooter }
|
|
|
14
14
|
import { Badge } from '../components/ui/badge';
|
|
15
15
|
import { Input } from '../components/ui/input';
|
|
16
16
|
import api from '../api/client';
|
|
17
|
+
import { getDocsApiReferenceUrl } from '../config/docs';
|
|
17
18
|
|
|
18
19
|
interface ApiToken {
|
|
19
20
|
id: string;
|
|
@@ -58,7 +59,7 @@ function SettingsRow({
|
|
|
58
59
|
|
|
59
60
|
export default function Profile() {
|
|
60
61
|
const { t } = useTranslation();
|
|
61
|
-
const { pathPrefixForLinks
|
|
62
|
+
const { pathPrefixForLinks } = useAppConfig();
|
|
62
63
|
const prefix = (pathPrefixForLinks || '').replace(/\/+/g, '/') || '';
|
|
63
64
|
const { user, updateUser, checkAuth } = useAuth();
|
|
64
65
|
const { showToast } = useToast();
|
|
@@ -510,7 +511,7 @@ export default function Profile() {
|
|
|
510
511
|
</p>
|
|
511
512
|
</div>
|
|
512
513
|
<a
|
|
513
|
-
href={
|
|
514
|
+
href={getDocsApiReferenceUrl()}
|
|
514
515
|
target="_blank"
|
|
515
516
|
rel="noopener noreferrer"
|
|
516
517
|
className="text-sm font-medium text-primary hover:text-primary/90 inline-block"
|
|
@@ -4,6 +4,7 @@ import { useTranslation } from 'react-i18next';
|
|
|
4
4
|
import api from '../api/client';
|
|
5
5
|
import { useAppConfig } from '../contexts/AppConfigContext';
|
|
6
6
|
import Button from '../components/ui/Button';
|
|
7
|
+
import { getDocsBaseUrl } from '../config/docs';
|
|
7
8
|
|
|
8
9
|
const MIN_PASSWORD_LENGTH = 8;
|
|
9
10
|
|
|
@@ -162,11 +163,11 @@ export default function Signup() {
|
|
|
162
163
|
/>
|
|
163
164
|
<label htmlFor="signup-accept-terms" className="text-sm text-gray-700 dark:text-gray-300">
|
|
164
165
|
{t('signup.acceptTermsPrefix')}
|
|
165
|
-
<a href=
|
|
166
|
+
<a href={getDocsBaseUrl()} target="_blank" rel="noopener noreferrer" className="text-blue-600 dark:text-blue-400 hover:underline">
|
|
166
167
|
{t('signup.acceptTermsTerms')}
|
|
167
168
|
</a>
|
|
168
169
|
{t('signup.acceptTermsAnd')}
|
|
169
|
-
<a href=
|
|
170
|
+
<a href={getDocsBaseUrl()} target="_blank" rel="noopener noreferrer" className="text-blue-600 dark:text-blue-400 hover:underline">
|
|
170
171
|
{t('signup.acceptTermsPrivacy')}
|
|
171
172
|
</a>
|
|
172
173
|
{t('signup.acceptTermsSuffix')}
|
|
@@ -287,8 +287,6 @@ export default function Tags() {
|
|
|
287
287
|
</h3>
|
|
288
288
|
</div>
|
|
289
289
|
</div>
|
|
290
|
-
{/* TODO: Add bookmark_count when backend supports it */}
|
|
291
|
-
<p className="text-xs text-muted-foreground">—</p>
|
|
292
290
|
</div>
|
|
293
291
|
</Link>
|
|
294
292
|
<div className={`flex gap-1.5 pt-2.5 mt-auto shrink-0 border-t border-border ${compactMode ? 'pt-2' : ''}`}>
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { useTranslation } from 'react-i18next';
|
|
2
2
|
import { Outlet } from 'react-router-dom';
|
|
3
3
|
import { ExternalLink } from 'lucide-react';
|
|
4
|
+
import { getDocsApiReferenceUrl } from '../../config/docs';
|
|
4
5
|
|
|
5
6
|
export default function AdminLayout() {
|
|
6
7
|
const { t } = useTranslation();
|
|
@@ -25,7 +26,7 @@ export default function AdminLayout() {
|
|
|
25
26
|
</p>
|
|
26
27
|
</div>
|
|
27
28
|
<a
|
|
28
|
-
href=
|
|
29
|
+
href={getDocsApiReferenceUrl()}
|
|
29
30
|
target="_blank"
|
|
30
31
|
rel="noopener noreferrer"
|
|
31
32
|
className="inline-flex items-center gap-2 px-4 py-2 text-sm font-medium text-primary hover:text-primary/90 transition-colors"
|