@joewinke/jatui 0.1.25 → 0.1.27
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/actions/collapse.d.ts +80 -0
- package/dist/actions/collapse.js +134 -0
- package/dist/actions/railNav.d.ts +167 -0
- package/dist/actions/railNav.js +351 -0
- package/dist/components/AnnotationLayer.svelte +108 -0
- package/dist/components/AnnotationLayer.svelte.d.ts +12 -0
- package/dist/components/AnnotationPanel.svelte +319 -0
- package/dist/components/AnnotationPanel.svelte.d.ts +13 -0
- package/dist/components/AudioWaveform.svelte +701 -0
- package/dist/components/AudioWaveform.svelte.d.ts +19 -0
- package/dist/components/AvailabilityModal.svelte +177 -0
- package/dist/components/AvailabilityModal.svelte.d.ts +14 -0
- package/dist/components/AvatarUpload.svelte +234 -0
- package/dist/components/AvatarUpload.svelte.d.ts +23 -0
- package/dist/components/Badge.svelte +38 -0
- package/dist/components/Badge.svelte.d.ts +14 -0
- package/dist/components/BookingForm.svelte +278 -0
- package/dist/components/BookingForm.svelte.d.ts +37 -0
- package/dist/components/BurndownChart.svelte +778 -0
- package/dist/components/BurndownChart.svelte.d.ts +17 -0
- package/dist/components/Button.svelte +81 -0
- package/dist/components/Button.svelte.d.ts +21 -0
- package/dist/components/CalendarPicker.svelte +284 -0
- package/dist/components/CalendarPicker.svelte.d.ts +22 -0
- package/dist/components/Card.svelte +67 -0
- package/dist/components/Card.svelte.d.ts +17 -0
- package/dist/components/CharacterCounter.svelte +82 -0
- package/dist/components/CharacterCounter.svelte.d.ts +10 -0
- package/dist/components/ChipInput.svelte +609 -0
- package/dist/components/ChipInput.svelte.d.ts +55 -0
- package/dist/components/Collapse.svelte +91 -0
- package/dist/components/Collapse.svelte.d.ts +18 -0
- package/dist/components/ColorSelector.svelte +167 -0
- package/dist/components/ColorSelector.svelte.d.ts +9 -0
- package/dist/components/CommentThread.svelte +773 -0
- package/dist/components/CommentThread.svelte.d.ts +32 -0
- package/dist/components/ConfirmDialog.svelte +348 -0
- package/dist/components/ConfirmDialog.svelte.d.ts +3 -0
- package/dist/components/ConfirmModal.svelte +142 -0
- package/dist/components/ConfirmModal.svelte.d.ts +15 -0
- package/dist/components/ContextMenu.svelte +188 -0
- package/dist/components/ContextMenu.svelte.d.ts +31 -0
- package/dist/components/CountdownTimer.svelte +94 -0
- package/dist/components/CountdownTimer.svelte.d.ts +10 -0
- package/dist/components/DateRangePicker.svelte +194 -0
- package/dist/components/DateRangePicker.svelte.d.ts +10 -0
- package/dist/components/Drawer.svelte +143 -0
- package/dist/components/Drawer.svelte.d.ts +33 -0
- package/dist/components/EmojiPicker.svelte +159 -0
- package/dist/components/EmojiPicker.svelte.d.ts +8 -0
- package/dist/components/EntityPreviewCard.svelte +104 -0
- package/dist/components/EntityPreviewCard.svelte.d.ts +9 -0
- package/dist/components/FileDropzone.svelte +493 -0
- package/dist/components/FileDropzone.svelte.d.ts +40 -0
- package/dist/components/FilePicker.svelte +488 -0
- package/dist/components/FilePicker.svelte.d.ts +20 -0
- package/dist/components/FileThumbnail.svelte +80 -0
- package/dist/components/FileThumbnail.svelte.d.ts +12 -0
- package/dist/components/FilterDropdown.svelte +203 -0
- package/dist/components/FilterDropdown.svelte.d.ts +48 -0
- package/dist/components/FloatingActionBar.svelte +354 -0
- package/dist/components/FloatingActionBar.svelte.d.ts +51 -0
- package/dist/components/GPSTracker.svelte +202 -0
- package/dist/components/GPSTracker.svelte.d.ts +34 -0
- package/dist/components/HunkDiffView.svelte +348 -0
- package/dist/components/HunkDiffView.svelte.d.ts +9 -0
- package/dist/components/ImageLightbox.svelte +274 -0
- package/dist/components/ImageLightbox.svelte.d.ts +25 -0
- package/dist/components/ImageUpload.svelte +147 -0
- package/dist/components/ImageUpload.svelte.d.ts +10 -0
- package/dist/components/InlineEdit.svelte +291 -0
- package/dist/components/InlineEdit.svelte.d.ts +45 -0
- package/dist/components/InputDialog.svelte +327 -0
- package/dist/components/InputDialog.svelte.d.ts +3 -0
- package/dist/components/KeyboardShortcutsOverlay.svelte +296 -0
- package/dist/components/KeyboardShortcutsOverlay.svelte.d.ts +42 -0
- package/dist/components/LazyImage.svelte +123 -0
- package/dist/components/LazyImage.svelte.d.ts +14 -0
- package/dist/components/LinkShortener.svelte +274 -0
- package/dist/components/LinkShortener.svelte.d.ts +27 -0
- package/dist/components/LoadingSpinner.svelte +106 -0
- package/dist/components/LoadingSpinner.svelte.d.ts +21 -0
- package/dist/components/LocationMap.svelte +186 -0
- package/dist/components/LocationMap.svelte.d.ts +11 -0
- package/dist/components/MapView.svelte +341 -0
- package/dist/components/MapView.svelte.d.ts +20 -0
- package/dist/components/MarkupEditor.svelte +485 -0
- package/dist/components/MarkupEditor.svelte.d.ts +10 -0
- package/dist/components/MarkupOverlay.svelte +55 -0
- package/dist/components/MarkupOverlay.svelte.d.ts +8 -0
- package/dist/components/MediaWorkbench.svelte +871 -0
- package/dist/components/MediaWorkbench.svelte.d.ts +9 -0
- package/dist/components/MilestoneCard.svelte +98 -0
- package/dist/components/MilestoneCard.svelte.d.ts +16 -0
- package/dist/components/MilestoneTimeline.svelte +91 -0
- package/dist/components/MilestoneTimeline.svelte.d.ts +15 -0
- package/dist/components/Modal.svelte +244 -0
- package/dist/components/Modal.svelte.d.ts +35 -0
- package/dist/components/PDFViewer.svelte +105 -0
- package/dist/components/PDFViewer.svelte.d.ts +9 -0
- package/dist/components/PdfThumbnail.svelte +63 -0
- package/dist/components/PdfThumbnail.svelte.d.ts +9 -0
- package/dist/components/PhoneInput.svelte +212 -0
- package/dist/components/PhoneInput.svelte.d.ts +25 -0
- package/dist/components/ResizableDivider.svelte +307 -0
- package/dist/components/ResizableDivider.svelte.d.ts +26 -0
- package/dist/components/ResizablePanel.svelte +305 -0
- package/dist/components/ResizablePanel.svelte.d.ts +18 -0
- package/dist/components/SearchDropdown.svelte +686 -0
- package/dist/components/SearchDropdown.svelte.d.ts +40 -0
- package/dist/components/SelectInput.svelte +237 -0
- package/dist/components/SelectInput.svelte.d.ts +26 -0
- package/dist/components/SidebarUserFooter.svelte +34 -0
- package/dist/components/SidebarUserFooter.svelte.d.ts +16 -0
- package/dist/components/SignaturePad.svelte +175 -0
- package/dist/components/SignaturePad.svelte.d.ts +17 -0
- package/dist/components/SmartImageEditor.svelte +720 -0
- package/dist/components/SmartImageEditor.svelte.d.ts +24 -0
- package/dist/components/SortDropdown.svelte +156 -0
- package/dist/components/SortDropdown.svelte.d.ts +40 -0
- package/dist/components/Sparkline.svelte +116 -0
- package/dist/components/Sparkline.svelte.d.ts +12 -0
- package/dist/components/SpeechForm.svelte +114 -0
- package/dist/components/SpeechForm.svelte.d.ts +17 -0
- package/dist/components/StatusBadge.svelte +157 -0
- package/dist/components/StatusBadge.svelte.d.ts +24 -0
- package/dist/components/TaskTypeIcon.svelte +54 -0
- package/dist/components/TaskTypeIcon.svelte.d.ts +17 -0
- package/dist/components/TextArea.svelte +162 -0
- package/dist/components/TextArea.svelte.d.ts +30 -0
- package/dist/components/TextInput.svelte +131 -0
- package/dist/components/TextInput.svelte.d.ts +29 -0
- package/dist/components/ThemeSelector.svelte +206 -0
- package/dist/components/ThemeSelector.svelte.d.ts +14 -0
- package/dist/components/TimeSlotPicker.svelte +162 -0
- package/dist/components/TimeSlotPicker.svelte.d.ts +18 -0
- package/dist/components/UserAvatar.svelte +72 -0
- package/dist/components/UserAvatar.svelte.d.ts +11 -0
- package/dist/components/VariablePicker.svelte +170 -0
- package/dist/components/VariablePicker.svelte.d.ts +40 -0
- package/dist/components/VoicePlayer.svelte +422 -0
- package/dist/components/VoicePlayer.svelte.d.ts +18 -0
- package/dist/components/linked-columns/LinkedColumns.svelte +526 -0
- package/dist/components/linked-columns/LinkedColumns.svelte.d.ts +130 -0
- package/dist/components/markup.d.ts +47 -0
- package/dist/components/markup.js +201 -0
- package/dist/components/messaging/Avatar.svelte +81 -0
- package/dist/components/messaging/Avatar.svelte.d.ts +11 -0
- package/dist/components/messaging/ChannelInfoModal.svelte +163 -0
- package/dist/components/messaging/ChannelInfoModal.svelte.d.ts +18 -0
- package/dist/components/messaging/ChannelList.svelte +107 -0
- package/dist/components/messaging/ChannelList.svelte.d.ts +11 -0
- package/dist/components/messaging/ChannelMemberAvatarStack.svelte +69 -0
- package/dist/components/messaging/ChannelMemberAvatarStack.svelte.d.ts +11 -0
- package/dist/components/messaging/ChannelMembersModal.svelte +182 -0
- package/dist/components/messaging/ChannelMembersModal.svelte.d.ts +16 -0
- package/dist/components/messaging/CreateChannelModal.svelte +190 -0
- package/dist/components/messaging/CreateChannelModal.svelte.d.ts +14 -0
- package/dist/components/messaging/DirectMessageList.svelte +145 -0
- package/dist/components/messaging/DirectMessageList.svelte.d.ts +12 -0
- package/dist/components/messaging/EmojiSelector.svelte +261 -0
- package/dist/components/messaging/EmojiSelector.svelte.d.ts +19 -0
- package/dist/components/messaging/MentionAutocomplete.svelte +193 -0
- package/dist/components/messaging/MentionAutocomplete.svelte.d.ts +17 -0
- package/dist/components/messaging/MessageAttachment.svelte +273 -0
- package/dist/components/messaging/MessageAttachment.svelte.d.ts +13 -0
- package/dist/components/messaging/MessageAttachmentUpload.svelte +243 -0
- package/dist/components/messaging/MessageAttachmentUpload.svelte.d.ts +19 -0
- package/dist/components/messaging/MessageInput.svelte +452 -0
- package/dist/components/messaging/MessageInput.svelte.d.ts +28 -0
- package/dist/components/messaging/MessageItem.svelte +341 -0
- package/dist/components/messaging/MessageItem.svelte.d.ts +41 -0
- package/dist/components/messaging/MessageThread.svelte +306 -0
- package/dist/components/messaging/MessageThread.svelte.d.ts +30 -0
- package/dist/components/messaging/NotificationSettingsModal.svelte +234 -0
- package/dist/components/messaging/NotificationSettingsModal.svelte.d.ts +13 -0
- package/dist/components/messaging/QuotedMessageDisplay.svelte +123 -0
- package/dist/components/messaging/QuotedMessageDisplay.svelte.d.ts +10 -0
- package/dist/components/messaging/StartDMModal.svelte +100 -0
- package/dist/components/messaging/StartDMModal.svelte.d.ts +14 -0
- package/dist/components/messaging/ThreadPanel.svelte +153 -0
- package/dist/components/messaging/ThreadPanel.svelte.d.ts +16 -0
- package/dist/components/pipeline/DESIGN.md +361 -0
- package/dist/components/pipeline/Pipeline.svelte +391 -0
- package/dist/components/pipeline/Pipeline.svelte.d.ts +28 -0
- package/dist/components/pipeline/PipelineCard.svelte +94 -0
- package/dist/components/pipeline/PipelineCard.svelte.d.ts +13 -0
- package/dist/components/pipeline/PipelineColumn.svelte +163 -0
- package/dist/components/pipeline/PipelineColumn.svelte.d.ts +30 -0
- package/dist/components/replay/ChapterTimeline.svelte +326 -0
- package/dist/components/replay/ChapterTimeline.svelte.d.ts +39 -0
- package/dist/components/session-nav/transcriptModel.d.ts +132 -0
- package/dist/components/session-nav/transcriptModel.js +285 -0
- package/dist/data/emojis.d.ts +18 -0
- package/dist/data/emojis.js +1196 -0
- package/dist/index.d.ts +142 -0
- package/dist/index.js +142 -0
- package/dist/stores/confirmDialog.svelte.d.ts +21 -0
- package/dist/stores/confirmDialog.svelte.js +31 -0
- package/dist/stores/inputDialog.svelte.d.ts +24 -0
- package/dist/stores/inputDialog.svelte.js +32 -0
- package/dist/styles/rail.css +63 -0
- package/dist/types/annotation.d.ts +32 -0
- package/dist/types/annotation.js +3 -0
- package/dist/types/booking.d.ts +115 -0
- package/dist/types/booking.js +24 -0
- package/dist/types/comments.d.ts +65 -0
- package/dist/types/comments.js +22 -0
- package/dist/types/entityPreview.d.ts +42 -0
- package/dist/types/entityPreview.js +10 -0
- package/dist/types/filePicker.d.ts +15 -0
- package/dist/types/filePicker.js +1 -0
- package/dist/types/googleMaps.d.ts +51 -0
- package/dist/types/maps.d.ts +43 -0
- package/dist/types/maps.js +4 -0
- package/dist/types/messaging.d.ts +282 -0
- package/dist/types/messaging.js +100 -0
- package/dist/types/milestone.d.ts +20 -0
- package/dist/types/milestone.js +7 -0
- package/dist/types/pipeline.d.ts +115 -0
- package/dist/types/pipeline.js +15 -0
- package/dist/types/smartImageEditor.d.ts +42 -0
- package/dist/types/smartImageEditor.js +1 -0
- package/dist/types/templateVars.d.ts +34 -0
- package/dist/types/templateVars.js +12 -0
- package/dist/utils/currency.d.ts +8 -0
- package/dist/utils/currency.js +17 -0
- package/dist/utils/daisyuiColors.d.ts +37 -0
- package/dist/utils/daisyuiColors.js +152 -0
- package/dist/utils/dateFormatters.d.ts +69 -0
- package/dist/utils/dateFormatters.js +199 -0
- package/dist/utils/googleMapsLoader.d.ts +19 -0
- package/dist/utils/googleMapsLoader.js +121 -0
- package/dist/utils/mentionParser.d.ts +67 -0
- package/dist/utils/mentionParser.js +132 -0
- package/dist/utils/phone.d.ts +13 -0
- package/dist/utils/phone.js +67 -0
- package/dist/utils/phoneFormat.d.ts +26 -0
- package/dist/utils/phoneFormat.js +69 -0
- package/dist/utils/taskUtils.d.ts +37 -0
- package/dist/utils/taskUtils.js +71 -0
- package/package.json +3 -2
|
@@ -0,0 +1,274 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
export interface ShortenedLink {
|
|
3
|
+
id: string
|
|
4
|
+
slug: string
|
|
5
|
+
destination_url: string
|
|
6
|
+
title?: string | null
|
|
7
|
+
click_count: number
|
|
8
|
+
created_at: string
|
|
9
|
+
expires_at?: string | null
|
|
10
|
+
is_active: boolean
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
interface Props {
|
|
14
|
+
links: ShortenedLink[]
|
|
15
|
+
baseUrl?: string
|
|
16
|
+
showHeader?: boolean
|
|
17
|
+
onCreate?: (data: { title: string; destination_url: string; slug: string; expires_at: string | null }) => void
|
|
18
|
+
onDelete?: (id: string) => void
|
|
19
|
+
onToggle?: (id: string, is_active: boolean) => void
|
|
20
|
+
class?: string
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
let { links, baseUrl = '', showHeader = true, onCreate, onDelete, onToggle, class: className = '' }: Props = $props()
|
|
24
|
+
|
|
25
|
+
let showForm = $state(false)
|
|
26
|
+
let title = $state('')
|
|
27
|
+
let destinationUrl = $state('')
|
|
28
|
+
let customSlug = $state('')
|
|
29
|
+
let expiresAt = $state('')
|
|
30
|
+
let copiedId = $state<string | null>(null)
|
|
31
|
+
let submitting = $state(false)
|
|
32
|
+
|
|
33
|
+
function generateSlug(): string {
|
|
34
|
+
const chars = 'abcdefghijklmnopqrstuvwxyz0123456789'
|
|
35
|
+
let result = ''
|
|
36
|
+
for (let i = 0; i < 6; i++) {
|
|
37
|
+
result += chars.charAt(Math.floor(Math.random() * chars.length))
|
|
38
|
+
}
|
|
39
|
+
return result
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function handleSubmit(e: Event) {
|
|
43
|
+
e.preventDefault()
|
|
44
|
+
if (!destinationUrl || submitting) return
|
|
45
|
+
|
|
46
|
+
submitting = true
|
|
47
|
+
const slug = customSlug.trim() || generateSlug()
|
|
48
|
+
|
|
49
|
+
onCreate?.({
|
|
50
|
+
title: title.trim(),
|
|
51
|
+
destination_url: destinationUrl.trim(),
|
|
52
|
+
slug,
|
|
53
|
+
expires_at: expiresAt || null
|
|
54
|
+
})
|
|
55
|
+
|
|
56
|
+
title = ''
|
|
57
|
+
destinationUrl = ''
|
|
58
|
+
customSlug = ''
|
|
59
|
+
expiresAt = ''
|
|
60
|
+
showForm = false
|
|
61
|
+
submitting = false
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
async function copyUrl(link: ShortenedLink) {
|
|
65
|
+
const url = `${baseUrl}/l/${link.slug}`
|
|
66
|
+
await navigator.clipboard.writeText(url)
|
|
67
|
+
copiedId = link.id
|
|
68
|
+
setTimeout(() => { copiedId = null }, 2000)
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
function isExpired(link: ShortenedLink): boolean {
|
|
72
|
+
if (!link.expires_at) return false
|
|
73
|
+
return new Date(link.expires_at) < new Date()
|
|
74
|
+
}
|
|
75
|
+
</script>
|
|
76
|
+
|
|
77
|
+
<div class="space-y-4 {className}">
|
|
78
|
+
{#if showHeader}
|
|
79
|
+
<div class="flex items-center justify-between">
|
|
80
|
+
<h2 class="text-lg font-semibold">Short Links</h2>
|
|
81
|
+
{#if !showForm}
|
|
82
|
+
<button class="btn btn-primary btn-sm" onclick={() => showForm = true}>
|
|
83
|
+
<svg xmlns="http://www.w3.org/2000/svg" class="h-4 w-4" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
|
84
|
+
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 4v16m8-8H4" />
|
|
85
|
+
</svg>
|
|
86
|
+
New Link
|
|
87
|
+
</button>
|
|
88
|
+
{/if}
|
|
89
|
+
</div>
|
|
90
|
+
{:else if !showForm}
|
|
91
|
+
<div class="flex justify-end">
|
|
92
|
+
<button class="btn btn-primary btn-sm" onclick={() => showForm = true}>
|
|
93
|
+
<svg xmlns="http://www.w3.org/2000/svg" class="h-4 w-4" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
|
94
|
+
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 4v16m8-8H4" />
|
|
95
|
+
</svg>
|
|
96
|
+
New Link
|
|
97
|
+
</button>
|
|
98
|
+
</div>
|
|
99
|
+
{/if}
|
|
100
|
+
|
|
101
|
+
{#if showForm}
|
|
102
|
+
<form class="card bg-base-200" onsubmit={handleSubmit}>
|
|
103
|
+
<div class="card-body gap-4">
|
|
104
|
+
<div class="grid sm:grid-cols-2 gap-3">
|
|
105
|
+
<div class="flex flex-col gap-1.5">
|
|
106
|
+
<label class="text-sm font-medium" for="link-title">Title</label>
|
|
107
|
+
<input
|
|
108
|
+
id="link-title"
|
|
109
|
+
type="text"
|
|
110
|
+
class="input input-bordered input-sm w-full"
|
|
111
|
+
placeholder="My Campaign Link"
|
|
112
|
+
bind:value={title}
|
|
113
|
+
/>
|
|
114
|
+
</div>
|
|
115
|
+
<div class="flex flex-col gap-1.5">
|
|
116
|
+
<label class="text-sm font-medium" for="link-destination">
|
|
117
|
+
Destination URL <span class="text-error">*</span>
|
|
118
|
+
</label>
|
|
119
|
+
<input
|
|
120
|
+
id="link-destination"
|
|
121
|
+
type="url"
|
|
122
|
+
class="input input-bordered input-sm w-full"
|
|
123
|
+
placeholder="https://example.com/page"
|
|
124
|
+
bind:value={destinationUrl}
|
|
125
|
+
required
|
|
126
|
+
/>
|
|
127
|
+
</div>
|
|
128
|
+
</div>
|
|
129
|
+
|
|
130
|
+
<div class="grid sm:grid-cols-2 gap-3">
|
|
131
|
+
<div class="flex flex-col gap-1.5">
|
|
132
|
+
<label class="text-sm font-medium" for="link-slug">Custom Slug</label>
|
|
133
|
+
<div class="join w-full">
|
|
134
|
+
<span class="join-item flex items-center px-3 bg-base-300 text-xs text-base-content/50 border border-base-content/20">/l/</span>
|
|
135
|
+
<input
|
|
136
|
+
id="link-slug"
|
|
137
|
+
type="text"
|
|
138
|
+
class="input input-bordered input-sm join-item flex-1 min-w-0"
|
|
139
|
+
placeholder="auto-generated"
|
|
140
|
+
bind:value={customSlug}
|
|
141
|
+
pattern="[a-zA-Z0-9_-]+"
|
|
142
|
+
/>
|
|
143
|
+
</div>
|
|
144
|
+
</div>
|
|
145
|
+
<div class="flex flex-col gap-1.5">
|
|
146
|
+
<label class="text-sm font-medium" for="link-expires">Expires</label>
|
|
147
|
+
<input
|
|
148
|
+
id="link-expires"
|
|
149
|
+
type="date"
|
|
150
|
+
class="input input-bordered input-sm w-full"
|
|
151
|
+
bind:value={expiresAt}
|
|
152
|
+
/>
|
|
153
|
+
</div>
|
|
154
|
+
</div>
|
|
155
|
+
|
|
156
|
+
<div class="flex justify-end gap-2 pt-1">
|
|
157
|
+
<button type="button" class="btn btn-ghost btn-sm" onclick={() => showForm = false}>Cancel</button>
|
|
158
|
+
<button type="submit" class="btn btn-primary btn-sm" disabled={!destinationUrl || submitting}>
|
|
159
|
+
{#if submitting}
|
|
160
|
+
<span class="loading loading-spinner loading-xs"></span>
|
|
161
|
+
{/if}
|
|
162
|
+
Create Link
|
|
163
|
+
</button>
|
|
164
|
+
</div>
|
|
165
|
+
</div>
|
|
166
|
+
</form>
|
|
167
|
+
{/if}
|
|
168
|
+
|
|
169
|
+
{#if links.length === 0 && !showForm}
|
|
170
|
+
<div class="card bg-base-200">
|
|
171
|
+
<div class="card-body text-center py-8">
|
|
172
|
+
<p class="text-base-content/60">No short links yet. Create your first one above.</p>
|
|
173
|
+
</div>
|
|
174
|
+
</div>
|
|
175
|
+
{:else if links.length > 0}
|
|
176
|
+
<div class="overflow-x-auto">
|
|
177
|
+
<table class="table">
|
|
178
|
+
<thead>
|
|
179
|
+
<tr>
|
|
180
|
+
<th>Link</th>
|
|
181
|
+
<th>Destination</th>
|
|
182
|
+
<th class="text-center">Clicks</th>
|
|
183
|
+
<th class="text-center">Status</th>
|
|
184
|
+
<th class="text-right">Actions</th>
|
|
185
|
+
</tr>
|
|
186
|
+
</thead>
|
|
187
|
+
<tbody>
|
|
188
|
+
{#each links as link (link.id)}
|
|
189
|
+
<tr class="hover">
|
|
190
|
+
<td>
|
|
191
|
+
<div>
|
|
192
|
+
{#if link.title}
|
|
193
|
+
<div class="font-medium text-sm">{link.title}</div>
|
|
194
|
+
{/if}
|
|
195
|
+
<div class="text-primary text-sm font-mono">/l/{link.slug}</div>
|
|
196
|
+
</div>
|
|
197
|
+
</td>
|
|
198
|
+
<td>
|
|
199
|
+
<a
|
|
200
|
+
href={link.destination_url}
|
|
201
|
+
target="_blank"
|
|
202
|
+
rel="noopener noreferrer"
|
|
203
|
+
class="link link-hover text-sm max-w-xs truncate block"
|
|
204
|
+
title={link.destination_url}
|
|
205
|
+
>
|
|
206
|
+
{link.destination_url}
|
|
207
|
+
</a>
|
|
208
|
+
</td>
|
|
209
|
+
<td class="text-center">
|
|
210
|
+
<span class="badge badge-ghost badge-sm">{link.click_count}</span>
|
|
211
|
+
</td>
|
|
212
|
+
<td class="text-center">
|
|
213
|
+
{#if isExpired(link)}
|
|
214
|
+
<span class="badge badge-error badge-sm">Expired</span>
|
|
215
|
+
{:else if !link.is_active}
|
|
216
|
+
<span class="badge badge-ghost badge-sm">Inactive</span>
|
|
217
|
+
{:else}
|
|
218
|
+
<span class="badge badge-success badge-sm">Active</span>
|
|
219
|
+
{/if}
|
|
220
|
+
</td>
|
|
221
|
+
<td>
|
|
222
|
+
<div class="flex items-center justify-end gap-1">
|
|
223
|
+
<button
|
|
224
|
+
class="btn btn-ghost btn-xs"
|
|
225
|
+
title="Copy short URL"
|
|
226
|
+
onclick={() => copyUrl(link)}
|
|
227
|
+
>
|
|
228
|
+
{#if copiedId === link.id}
|
|
229
|
+
<svg xmlns="http://www.w3.org/2000/svg" class="h-4 w-4 text-success" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
|
230
|
+
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M5 13l4 4L19 7" />
|
|
231
|
+
</svg>
|
|
232
|
+
{:else}
|
|
233
|
+
<svg xmlns="http://www.w3.org/2000/svg" class="h-4 w-4" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
|
234
|
+
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8 16H6a2 2 0 01-2-2V6a2 2 0 012-2h8a2 2 0 012 2v2m-6 12h8a2 2 0 002-2v-8a2 2 0 00-2-2h-8a2 2 0 00-2 2v8a2 2 0 002 2z" />
|
|
235
|
+
</svg>
|
|
236
|
+
{/if}
|
|
237
|
+
</button>
|
|
238
|
+
{#if onToggle}
|
|
239
|
+
<button
|
|
240
|
+
class="btn btn-ghost btn-xs"
|
|
241
|
+
title={link.is_active ? 'Deactivate' : 'Activate'}
|
|
242
|
+
onclick={() => onToggle(link.id, !link.is_active)}
|
|
243
|
+
>
|
|
244
|
+
{#if link.is_active}
|
|
245
|
+
<svg xmlns="http://www.w3.org/2000/svg" class="h-4 w-4" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
|
246
|
+
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M18.364 18.364A9 9 0 005.636 5.636m12.728 12.728A9 9 0 015.636 5.636m12.728 12.728L5.636 5.636" />
|
|
247
|
+
</svg>
|
|
248
|
+
{:else}
|
|
249
|
+
<svg xmlns="http://www.w3.org/2000/svg" class="h-4 w-4" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
|
250
|
+
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z" />
|
|
251
|
+
</svg>
|
|
252
|
+
{/if}
|
|
253
|
+
</button>
|
|
254
|
+
{/if}
|
|
255
|
+
{#if onDelete}
|
|
256
|
+
<button
|
|
257
|
+
class="btn btn-ghost btn-xs text-error"
|
|
258
|
+
title="Delete link"
|
|
259
|
+
onclick={() => onDelete(link.id)}
|
|
260
|
+
>
|
|
261
|
+
<svg xmlns="http://www.w3.org/2000/svg" class="h-4 w-4" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
|
262
|
+
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 7l-.867 12.142A2 2 0 0116.138 21H7.862a2 2 0 01-1.995-1.858L5 7m5 4v6m4-6v6m1-10V4a1 1 0 00-1-1h-4a1 1 0 00-1 1v3M4 7h16" />
|
|
263
|
+
</svg>
|
|
264
|
+
</button>
|
|
265
|
+
{/if}
|
|
266
|
+
</div>
|
|
267
|
+
</td>
|
|
268
|
+
</tr>
|
|
269
|
+
{/each}
|
|
270
|
+
</tbody>
|
|
271
|
+
</table>
|
|
272
|
+
</div>
|
|
273
|
+
{/if}
|
|
274
|
+
</div>
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
export interface ShortenedLink {
|
|
2
|
+
id: string;
|
|
3
|
+
slug: string;
|
|
4
|
+
destination_url: string;
|
|
5
|
+
title?: string | null;
|
|
6
|
+
click_count: number;
|
|
7
|
+
created_at: string;
|
|
8
|
+
expires_at?: string | null;
|
|
9
|
+
is_active: boolean;
|
|
10
|
+
}
|
|
11
|
+
interface Props {
|
|
12
|
+
links: ShortenedLink[];
|
|
13
|
+
baseUrl?: string;
|
|
14
|
+
showHeader?: boolean;
|
|
15
|
+
onCreate?: (data: {
|
|
16
|
+
title: string;
|
|
17
|
+
destination_url: string;
|
|
18
|
+
slug: string;
|
|
19
|
+
expires_at: string | null;
|
|
20
|
+
}) => void;
|
|
21
|
+
onDelete?: (id: string) => void;
|
|
22
|
+
onToggle?: (id: string, is_active: boolean) => void;
|
|
23
|
+
class?: string;
|
|
24
|
+
}
|
|
25
|
+
declare const LinkShortener: import("svelte").Component<Props, {}, "">;
|
|
26
|
+
type LinkShortener = ReturnType<typeof LinkShortener>;
|
|
27
|
+
export default LinkShortener;
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
/**
|
|
3
|
+
* LoadingSpinner Component
|
|
4
|
+
*
|
|
5
|
+
* Customizable DaisyUI loading spinner with text, overlay, and speed options.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
interface Props {
|
|
9
|
+
type?: 'spinner' | 'dots' | 'ring' | 'ball' | 'bars' | 'infinity' | 'pulse';
|
|
10
|
+
size?: 'xs' | 'sm' | 'md' | 'lg' | 'xl';
|
|
11
|
+
text?: string;
|
|
12
|
+
textPosition?: 'top' | 'bottom' | 'left' | 'right';
|
|
13
|
+
overlay?: boolean;
|
|
14
|
+
centered?: boolean;
|
|
15
|
+
color?: string;
|
|
16
|
+
ariaLabel?: string;
|
|
17
|
+
visible?: boolean;
|
|
18
|
+
speed?: 'slow' | 'normal' | 'fast';
|
|
19
|
+
class?: string;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
let {
|
|
23
|
+
type = 'spinner',
|
|
24
|
+
size = 'md',
|
|
25
|
+
text = '',
|
|
26
|
+
textPosition = 'bottom',
|
|
27
|
+
overlay = false,
|
|
28
|
+
centered = false,
|
|
29
|
+
color = '',
|
|
30
|
+
ariaLabel = 'Loading',
|
|
31
|
+
visible = true,
|
|
32
|
+
speed = 'normal',
|
|
33
|
+
class: className = ''
|
|
34
|
+
}: Props = $props();
|
|
35
|
+
|
|
36
|
+
const sizeClass = $derived(
|
|
37
|
+
{ xs: 'loading-xs', sm: 'loading-sm', md: 'loading-md', lg: 'loading-lg', xl: 'loading-xl' }[size]
|
|
38
|
+
);
|
|
39
|
+
|
|
40
|
+
const typeClass = $derived(
|
|
41
|
+
{
|
|
42
|
+
spinner: 'loading-spinner',
|
|
43
|
+
dots: 'loading-dots',
|
|
44
|
+
ring: 'loading-ring',
|
|
45
|
+
ball: 'loading-ball',
|
|
46
|
+
bars: 'loading-bars',
|
|
47
|
+
infinity: 'loading-infinity',
|
|
48
|
+
pulse: 'loading-pulse'
|
|
49
|
+
}[type]
|
|
50
|
+
);
|
|
51
|
+
|
|
52
|
+
const speedClass = $derived(
|
|
53
|
+
{ slow: '[animation-duration:2s]', normal: '', fast: '[animation-duration:0.5s]' }[speed]
|
|
54
|
+
);
|
|
55
|
+
|
|
56
|
+
const spinnerClass = $derived(
|
|
57
|
+
['loading', typeClass, sizeClass, speedClass, color ? '' : 'text-primary', className]
|
|
58
|
+
.filter(Boolean)
|
|
59
|
+
.join(' ')
|
|
60
|
+
);
|
|
61
|
+
|
|
62
|
+
const containerClass = $derived(
|
|
63
|
+
[
|
|
64
|
+
overlay ? 'fixed inset-0 flex items-center justify-center z-50' : '',
|
|
65
|
+
centered && !overlay ? 'flex items-center justify-center' : '',
|
|
66
|
+
textPosition === 'left' || textPosition === 'right' ? 'flex items-center' : '',
|
|
67
|
+
textPosition === 'top' || textPosition === 'bottom' ? 'flex flex-col items-center' : ''
|
|
68
|
+
]
|
|
69
|
+
.filter(Boolean)
|
|
70
|
+
.join(' ')
|
|
71
|
+
);
|
|
72
|
+
|
|
73
|
+
const containerStyle = $derived(
|
|
74
|
+
overlay ? 'background: color-mix(in oklch, #180042 55%, transparent)' : ''
|
|
75
|
+
);
|
|
76
|
+
|
|
77
|
+
const textClass = $derived(
|
|
78
|
+
['text-sm text-base-content/70', { top: 'mb-2', bottom: 'mt-2', left: 'mr-2', right: 'ml-2' }[textPosition]]
|
|
79
|
+
.filter(Boolean)
|
|
80
|
+
.join(' ')
|
|
81
|
+
);
|
|
82
|
+
|
|
83
|
+
const spinnerStyle = $derived(color ? `color: ${color}` : '');
|
|
84
|
+
</script>
|
|
85
|
+
|
|
86
|
+
{#if visible}
|
|
87
|
+
<div class={containerClass} style={containerStyle} aria-label={ariaLabel} role="status" aria-live="polite">
|
|
88
|
+
{#if text && textPosition === 'top'}
|
|
89
|
+
<div class={textClass}>{text}</div>
|
|
90
|
+
{/if}
|
|
91
|
+
{#if text && textPosition === 'left'}
|
|
92
|
+
<div class={textClass}>{text}</div>
|
|
93
|
+
{/if}
|
|
94
|
+
|
|
95
|
+
<div class={spinnerClass} style={spinnerStyle}></div>
|
|
96
|
+
|
|
97
|
+
{#if text && textPosition === 'right'}
|
|
98
|
+
<div class={textClass}>{text}</div>
|
|
99
|
+
{/if}
|
|
100
|
+
{#if text && textPosition === 'bottom'}
|
|
101
|
+
<div class={textClass}>{text}</div>
|
|
102
|
+
{/if}
|
|
103
|
+
|
|
104
|
+
<span class="sr-only">{ariaLabel}</span>
|
|
105
|
+
</div>
|
|
106
|
+
{/if}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* LoadingSpinner Component
|
|
3
|
+
*
|
|
4
|
+
* Customizable DaisyUI loading spinner with text, overlay, and speed options.
|
|
5
|
+
*/
|
|
6
|
+
interface Props {
|
|
7
|
+
type?: 'spinner' | 'dots' | 'ring' | 'ball' | 'bars' | 'infinity' | 'pulse';
|
|
8
|
+
size?: 'xs' | 'sm' | 'md' | 'lg' | 'xl';
|
|
9
|
+
text?: string;
|
|
10
|
+
textPosition?: 'top' | 'bottom' | 'left' | 'right';
|
|
11
|
+
overlay?: boolean;
|
|
12
|
+
centered?: boolean;
|
|
13
|
+
color?: string;
|
|
14
|
+
ariaLabel?: string;
|
|
15
|
+
visible?: boolean;
|
|
16
|
+
speed?: 'slow' | 'normal' | 'fast';
|
|
17
|
+
class?: string;
|
|
18
|
+
}
|
|
19
|
+
declare const LoadingSpinner: import("svelte").Component<Props, {}, "">;
|
|
20
|
+
type LoadingSpinner = ReturnType<typeof LoadingSpinner>;
|
|
21
|
+
export default LoadingSpinner;
|
|
@@ -0,0 +1,186 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
/**
|
|
3
|
+
* LocationMap — single-location Google Maps embed with a pulsing dot marker.
|
|
4
|
+
*
|
|
5
|
+
* Shows the current GPS position on a map. While waiting for coords, renders
|
|
6
|
+
* a "Waiting for GPS signal…" placeholder.
|
|
7
|
+
*
|
|
8
|
+
* API key is passed explicitly — jatui has no $env dependency.
|
|
9
|
+
*
|
|
10
|
+
* Usage:
|
|
11
|
+
*
|
|
12
|
+
* <LocationMap
|
|
13
|
+
* apiKey={PUBLIC_GOOGLE_MAPS_API_KEY}
|
|
14
|
+
* latitude={coords.latitude}
|
|
15
|
+
* longitude={coords.longitude}
|
|
16
|
+
* accuracy={coords.accuracy}
|
|
17
|
+
* />
|
|
18
|
+
*
|
|
19
|
+
* Ported from flush technician/LocationMap (190L) — steelbridge fork capture (jst-e0d1u.4).
|
|
20
|
+
*/
|
|
21
|
+
import { onMount, onDestroy } from 'svelte';
|
|
22
|
+
import { loadGoogleMapsAPI, isGoogleMapsLoaded } from '../utils/googleMapsLoader';
|
|
23
|
+
|
|
24
|
+
interface Props {
|
|
25
|
+
apiKey: string;
|
|
26
|
+
latitude?: number;
|
|
27
|
+
longitude?: number;
|
|
28
|
+
accuracy?: number;
|
|
29
|
+
showAccuracyCircle?: boolean;
|
|
30
|
+
height?: string;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
let {
|
|
34
|
+
apiKey,
|
|
35
|
+
latitude,
|
|
36
|
+
longitude,
|
|
37
|
+
accuracy,
|
|
38
|
+
showAccuracyCircle = true,
|
|
39
|
+
height = '200px'
|
|
40
|
+
}: Props = $props();
|
|
41
|
+
|
|
42
|
+
let mapContainer: HTMLElement;
|
|
43
|
+
let map: google.maps.Map;
|
|
44
|
+
let marker: any;
|
|
45
|
+
let accuracyCircle: google.maps.Circle;
|
|
46
|
+
let AdvancedMarkerElement: any;
|
|
47
|
+
|
|
48
|
+
onMount(async () => {
|
|
49
|
+
if (!apiKey) {
|
|
50
|
+
console.warn('LocationMap: No Google Maps API key provided');
|
|
51
|
+
return;
|
|
52
|
+
}
|
|
53
|
+
try {
|
|
54
|
+
await loadGoogleMapsAPI({ apiKey, libraries: ['marker'] });
|
|
55
|
+
if (isGoogleMapsLoaded() && window.google?.maps?.marker) {
|
|
56
|
+
AdvancedMarkerElement = window.google.maps.marker.AdvancedMarkerElement;
|
|
57
|
+
initializeMap();
|
|
58
|
+
}
|
|
59
|
+
} catch (e) {
|
|
60
|
+
console.warn('LocationMap: Failed to load Google Maps API', e);
|
|
61
|
+
}
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
onDestroy(() => {
|
|
65
|
+
if (marker) marker.map = null;
|
|
66
|
+
if (accuracyCircle) accuracyCircle.setMap(null);
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
function initializeMap() {
|
|
70
|
+
const position = { lat: latitude ?? 26.3683, lng: longitude ?? -80.1289 };
|
|
71
|
+
|
|
72
|
+
map = new google.maps.Map(mapContainer, {
|
|
73
|
+
zoom: 16,
|
|
74
|
+
center: position,
|
|
75
|
+
mapId: 'jatui-location-map',
|
|
76
|
+
disableDefaultUI: true,
|
|
77
|
+
zoomControl: true,
|
|
78
|
+
mapTypeControl: false,
|
|
79
|
+
streetViewControl: false,
|
|
80
|
+
fullscreenControl: false
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
updateMarker();
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
function updateMarker() {
|
|
87
|
+
if (!map || !AdvancedMarkerElement) return;
|
|
88
|
+
|
|
89
|
+
const position = { lat: latitude ?? 26.3683, lng: longitude ?? -80.1289 };
|
|
90
|
+
|
|
91
|
+
if (marker) marker.map = null;
|
|
92
|
+
|
|
93
|
+
const el = document.createElement('div');
|
|
94
|
+
el.className = 'lm-current-location';
|
|
95
|
+
el.innerHTML = '<div class="lm-pulse-ring"></div><div class="lm-dot"></div>';
|
|
96
|
+
|
|
97
|
+
marker = new AdvancedMarkerElement({
|
|
98
|
+
position,
|
|
99
|
+
map,
|
|
100
|
+
content: el,
|
|
101
|
+
title: 'Current location'
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
if (accuracyCircle) accuracyCircle.setMap(null);
|
|
105
|
+
|
|
106
|
+
if (showAccuracyCircle && accuracy) {
|
|
107
|
+
accuracyCircle = new google.maps.Circle({
|
|
108
|
+
strokeColor: '#4285F4',
|
|
109
|
+
strokeOpacity: 0.8,
|
|
110
|
+
strokeWeight: 1,
|
|
111
|
+
fillColor: '#4285F4',
|
|
112
|
+
fillOpacity: 0.2,
|
|
113
|
+
map,
|
|
114
|
+
center: position,
|
|
115
|
+
radius: accuracy
|
|
116
|
+
});
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
map.setCenter(position);
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
$effect(() => {
|
|
123
|
+
if (map && latitude && longitude) updateMarker();
|
|
124
|
+
});
|
|
125
|
+
</script>
|
|
126
|
+
|
|
127
|
+
<div
|
|
128
|
+
class="relative rounded-lg overflow-hidden border border-base-300"
|
|
129
|
+
style="height: {height}"
|
|
130
|
+
>
|
|
131
|
+
<div bind:this={mapContainer} class="w-full h-full"></div>
|
|
132
|
+
|
|
133
|
+
{#if !latitude || !longitude}
|
|
134
|
+
<div class="absolute inset-0 flex items-center justify-center bg-base-200">
|
|
135
|
+
<div class="text-center">
|
|
136
|
+
<div class="loading loading-spinner loading-md text-primary"></div>
|
|
137
|
+
<p class="text-sm mt-2 text-base-content/70">Waiting for GPS signal…</p>
|
|
138
|
+
</div>
|
|
139
|
+
</div>
|
|
140
|
+
{/if}
|
|
141
|
+
</div>
|
|
142
|
+
|
|
143
|
+
<style>
|
|
144
|
+
:global(.lm-current-location) {
|
|
145
|
+
position: relative;
|
|
146
|
+
width: 20px;
|
|
147
|
+
height: 20px;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
:global(.lm-dot) {
|
|
151
|
+
position: absolute;
|
|
152
|
+
top: 50%;
|
|
153
|
+
left: 50%;
|
|
154
|
+
transform: translate(-50%, -50%);
|
|
155
|
+
width: 12px;
|
|
156
|
+
height: 12px;
|
|
157
|
+
background-color: #4285f4;
|
|
158
|
+
border: 2px solid white;
|
|
159
|
+
border-radius: 50%;
|
|
160
|
+
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.3);
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
:global(.lm-pulse-ring) {
|
|
164
|
+
position: absolute;
|
|
165
|
+
top: 50%;
|
|
166
|
+
left: 50%;
|
|
167
|
+
transform: translate(-50%, -50%);
|
|
168
|
+
width: 20px;
|
|
169
|
+
height: 20px;
|
|
170
|
+
background-color: #4285f4;
|
|
171
|
+
border-radius: 50%;
|
|
172
|
+
opacity: 0.3;
|
|
173
|
+
animation: lm-pulse 2s infinite;
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
@keyframes lm-pulse {
|
|
177
|
+
0% {
|
|
178
|
+
transform: translate(-50%, -50%) scale(1);
|
|
179
|
+
opacity: 0.3;
|
|
180
|
+
}
|
|
181
|
+
100% {
|
|
182
|
+
transform: translate(-50%, -50%) scale(3);
|
|
183
|
+
opacity: 0;
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
</style>
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
interface Props {
|
|
2
|
+
apiKey: string;
|
|
3
|
+
latitude?: number;
|
|
4
|
+
longitude?: number;
|
|
5
|
+
accuracy?: number;
|
|
6
|
+
showAccuracyCircle?: boolean;
|
|
7
|
+
height?: string;
|
|
8
|
+
}
|
|
9
|
+
declare const LocationMap: import("svelte").Component<Props, {}, "">;
|
|
10
|
+
type LocationMap = ReturnType<typeof LocationMap>;
|
|
11
|
+
export default LocationMap;
|