@joewinke/jatui 0.1.11 → 0.1.19
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/README.md +123 -0
- package/package.json +2 -1
- package/src/lib/actions/railNav.ts +473 -0
- package/src/lib/components/AnnotationLayer.svelte +108 -0
- package/src/lib/components/AnnotationPanel.svelte +319 -0
- package/src/lib/components/AudioWaveform.svelte +9 -5
- package/src/lib/components/AvailabilityModal.svelte +7 -3
- package/src/lib/components/AvatarUpload.svelte +27 -4
- package/src/lib/components/BookingForm.svelte +11 -9
- package/src/lib/components/BurndownChart.svelte +778 -0
- package/src/lib/components/Button.svelte +10 -1
- package/src/lib/components/CalendarPicker.svelte +3 -3
- package/src/lib/components/Card.svelte +2 -2
- package/src/lib/components/ChipInput.svelte +21 -15
- package/src/lib/components/ColorSelector.svelte +17 -13
- package/src/lib/components/CommentThread.svelte +773 -0
- package/src/lib/components/ConfirmDialog.svelte +348 -0
- package/src/lib/components/ConfirmModal.svelte +78 -11
- package/src/lib/components/ContextMenu.svelte +59 -19
- package/src/lib/components/CountdownTimer.svelte +1 -1
- package/src/lib/components/DateRangePicker.svelte +6 -4
- package/src/lib/components/Drawer.svelte +36 -3
- package/src/lib/components/EntityPreviewCard.svelte +104 -0
- package/src/lib/components/FileDropzone.svelte +493 -0
- package/src/lib/components/FilePicker.svelte +83 -14
- package/src/lib/components/FileThumbnail.svelte +80 -0
- package/src/lib/components/FilterDropdown.svelte +11 -11
- package/src/lib/components/HunkDiffView.svelte +348 -0
- package/src/lib/components/ImageLightbox.svelte +274 -0
- package/src/lib/components/ImageUpload.svelte +58 -9
- package/src/lib/components/InlineEdit.svelte +15 -9
- package/src/lib/components/InputDialog.svelte +327 -0
- package/src/lib/components/LazyImage.svelte +1 -0
- package/src/lib/components/LinkShortener.svelte +1 -1
- package/src/lib/components/LoadingSpinner.svelte +6 -2
- package/src/lib/components/MarkupEditor.svelte +485 -0
- package/src/lib/components/MarkupOverlay.svelte +55 -0
- package/src/lib/components/MediaWorkbench.svelte +871 -0
- package/src/lib/components/MilestoneCard.svelte +1 -1
- package/src/lib/components/MilestoneTimeline.svelte +1 -1
- package/src/lib/components/Modal.svelte +39 -4
- package/src/lib/components/PDFViewer.svelte +105 -0
- package/src/lib/components/PdfThumbnail.svelte +3 -1
- package/src/lib/components/PhoneInput.svelte +1 -1
- package/src/lib/components/ResizablePanel.svelte +4 -4
- package/src/lib/components/SearchDropdown.svelte +26 -13
- package/src/lib/components/SelectInput.svelte +26 -4
- package/src/lib/components/SidebarUserFooter.svelte +1 -1
- package/src/lib/components/SignaturePad.svelte +8 -4
- package/src/lib/components/SmartImageEditor.svelte +720 -0
- package/src/lib/components/SortDropdown.svelte +9 -3
- package/src/lib/components/Sparkline.svelte +9 -0
- package/src/lib/components/StatusBadge.svelte +20 -18
- package/src/lib/components/TextArea.svelte +24 -5
- package/src/lib/components/TextInput.svelte +29 -6
- package/src/lib/components/ThemeSelector.svelte +15 -4
- package/src/lib/components/TimeSlotPicker.svelte +7 -7
- package/src/lib/components/UserAvatar.svelte +14 -1
- package/src/lib/components/VariablePicker.svelte +170 -0
- package/src/lib/components/VoicePlayer.svelte +4 -3
- package/src/lib/components/markup.ts +287 -0
- package/src/lib/components/messaging/ChannelInfoModal.svelte +9 -9
- package/src/lib/components/messaging/ChannelList.svelte +1 -1
- package/src/lib/components/messaging/ChannelMembersModal.svelte +1 -1
- package/src/lib/components/messaging/CreateChannelModal.svelte +1 -1
- package/src/lib/components/messaging/DirectMessageList.svelte +1 -1
- package/src/lib/components/messaging/EmojiSelector.svelte +2 -1
- package/src/lib/components/messaging/MentionAutocomplete.svelte +1 -1
- package/src/lib/components/messaging/MessageAttachment.svelte +3 -3
- package/src/lib/components/messaging/MessageAttachmentUpload.svelte +3 -3
- package/src/lib/components/messaging/MessageInput.svelte +1 -1
- package/src/lib/components/messaging/MessageItem.svelte +6 -3
- package/src/lib/components/messaging/NotificationSettingsModal.svelte +1 -1
- package/src/lib/components/messaging/QuotedMessageDisplay.svelte +6 -1
- package/src/lib/components/messaging/StartDMModal.svelte +1 -1
- package/src/lib/components/pipeline/Pipeline.svelte +4 -4
- package/src/lib/components/pipeline/PipelineCard.svelte +1 -1
- package/src/lib/components/pipeline/PipelineColumn.svelte +8 -3
- package/src/lib/index.ts +91 -0
- package/src/lib/stores/confirmDialog.svelte.ts +48 -0
- package/src/lib/stores/inputDialog.svelte.ts +51 -0
- package/src/lib/styles/rail.css +63 -0
- package/src/lib/types/annotation.ts +38 -0
- package/src/lib/types/comments.ts +97 -0
- package/src/lib/types/entityPreview.ts +45 -0
- package/src/lib/types/filePicker.ts +2 -0
- package/src/lib/types/smartImageEditor.ts +39 -0
- package/src/lib/types/templateVars.ts +36 -0
- package/src/lib/utils/dateFormatters.ts +12 -10
- package/src/lib/utils/taskUtils.ts +21 -7
package/src/lib/index.ts
CHANGED
|
@@ -1,9 +1,19 @@
|
|
|
1
|
+
// Actions
|
|
2
|
+
export { railNav, createRailNav, cycle } from './actions/railNav';
|
|
3
|
+
export type { RailNavOptions, RailNavController } from './actions/railNav';
|
|
4
|
+
|
|
1
5
|
// Components — Universal
|
|
2
6
|
export { default as UserAvatar } from './components/UserAvatar.svelte';
|
|
3
7
|
export { default as SidebarUserFooter } from './components/SidebarUserFooter.svelte';
|
|
4
8
|
export { default as TaskTypeIcon } from './components/TaskTypeIcon.svelte';
|
|
5
9
|
|
|
6
10
|
// Components — from JAT IDE
|
|
11
|
+
export { default as ConfirmDialog } from './components/ConfirmDialog.svelte';
|
|
12
|
+
export { showConfirm, confirmDialog, cancelDialog, getPending } from './stores/confirmDialog.svelte';
|
|
13
|
+
export type { ConfirmOptions } from './stores/confirmDialog.svelte';
|
|
14
|
+
export { default as InputDialog } from './components/InputDialog.svelte';
|
|
15
|
+
export { showInput, submitInput, cancelInput, getPendingInput } from './stores/inputDialog.svelte';
|
|
16
|
+
export type { InputOptions } from './stores/inputDialog.svelte';
|
|
7
17
|
export { default as SearchDropdown } from './components/SearchDropdown.svelte';
|
|
8
18
|
export { default as FilterDropdown } from './components/FilterDropdown.svelte';
|
|
9
19
|
export { default as ContextMenu } from './components/ContextMenu.svelte';
|
|
@@ -13,6 +23,7 @@ export { default as DateRangePicker } from './components/DateRangePicker.svelte'
|
|
|
13
23
|
export { default as SortDropdown } from './components/SortDropdown.svelte';
|
|
14
24
|
export { default as InlineEdit } from './components/InlineEdit.svelte';
|
|
15
25
|
export { default as FloatingActionBar } from './components/FloatingActionBar.svelte';
|
|
26
|
+
export { default as HunkDiffView } from './components/HunkDiffView.svelte';
|
|
16
27
|
|
|
17
28
|
// Components — from Flush
|
|
18
29
|
export { default as Button } from './components/Button.svelte';
|
|
@@ -34,6 +45,7 @@ export { default as ColorSelector } from './components/ColorSelector.svelte';
|
|
|
34
45
|
export { default as ConfirmModal } from './components/ConfirmModal.svelte';
|
|
35
46
|
export { default as CountdownTimer } from './components/CountdownTimer.svelte';
|
|
36
47
|
export { default as ImageUpload } from './components/ImageUpload.svelte';
|
|
48
|
+
export { default as SmartImageEditor } from './components/SmartImageEditor.svelte';
|
|
37
49
|
export { default as LazyImage } from './components/LazyImage.svelte';
|
|
38
50
|
export { default as ResizablePanel } from './components/ResizablePanel.svelte';
|
|
39
51
|
export { default as VoicePlayer } from './components/VoicePlayer.svelte';
|
|
@@ -42,15 +54,31 @@ export { default as SpeechForm } from './components/SpeechForm.svelte';
|
|
|
42
54
|
// Components — from Headcount
|
|
43
55
|
export { default as ThemeSelector } from './components/ThemeSelector.svelte';
|
|
44
56
|
export { default as Sparkline } from './components/Sparkline.svelte';
|
|
57
|
+
export { default as BurndownChart } from './components/BurndownChart.svelte';
|
|
45
58
|
|
|
46
59
|
// Components — from JST
|
|
47
60
|
export { default as AvatarUpload } from './components/AvatarUpload.svelte';
|
|
48
61
|
export { default as PdfThumbnail } from './components/PdfThumbnail.svelte';
|
|
49
62
|
export { default as FilePicker } from './components/FilePicker.svelte';
|
|
63
|
+
export { default as FileDropzone } from './components/FileDropzone.svelte';
|
|
64
|
+
export { default as FileThumbnail } from './components/FileThumbnail.svelte';
|
|
65
|
+
export { default as EntityPreviewCard } from './components/EntityPreviewCard.svelte';
|
|
66
|
+
|
|
67
|
+
// EntityPreviewCard types
|
|
68
|
+
export type { EntityPreviewData, EntityPreviewStat, EntityPreviewBadge } from './types/entityPreview';
|
|
50
69
|
|
|
51
70
|
// FilePicker types
|
|
52
71
|
export type { FilePickerFile, FilePickerSelection } from './types/filePicker';
|
|
53
72
|
|
|
73
|
+
// FileDropzone types
|
|
74
|
+
export type { FileDropzoneFile } from './components/FileDropzone.svelte';
|
|
75
|
+
|
|
76
|
+
// Components — Document Templates (JST)
|
|
77
|
+
export { default as VariablePicker } from './components/VariablePicker.svelte';
|
|
78
|
+
|
|
79
|
+
// Template variable types
|
|
80
|
+
export type { TemplateVar, TemplateVarGroup } from './types/templateVars';
|
|
81
|
+
|
|
54
82
|
// Components — Emoji
|
|
55
83
|
export { default as EmojiPicker } from './components/EmojiPicker.svelte';
|
|
56
84
|
|
|
@@ -61,6 +89,9 @@ export type { EmojiEntry, EmojiGroup } from './data/emojis';
|
|
|
61
89
|
// Components — from Meadow
|
|
62
90
|
export { default as LinkShortener } from './components/LinkShortener.svelte';
|
|
63
91
|
|
|
92
|
+
// Components — Media
|
|
93
|
+
export { default as MediaWorkbench } from './components/MediaWorkbench.svelte';
|
|
94
|
+
|
|
64
95
|
// Components — from Marduk
|
|
65
96
|
export { default as SignaturePad } from './components/SignaturePad.svelte';
|
|
66
97
|
export { default as MilestoneCard } from './components/MilestoneCard.svelte';
|
|
@@ -110,6 +141,14 @@ export {
|
|
|
110
141
|
LOCATION_ICONS
|
|
111
142
|
} from './types/booking';
|
|
112
143
|
|
|
144
|
+
// SmartImageEditor types
|
|
145
|
+
export type {
|
|
146
|
+
ImageVariantSpec,
|
|
147
|
+
ImageProcessingSpec,
|
|
148
|
+
VariantResult,
|
|
149
|
+
VariantState,
|
|
150
|
+
} from './types/smartImageEditor';
|
|
151
|
+
|
|
113
152
|
// Utilities — date formatting
|
|
114
153
|
export {
|
|
115
154
|
normalizeTimestamp,
|
|
@@ -262,3 +301,55 @@ export {
|
|
|
262
301
|
countMentions,
|
|
263
302
|
setMentionUrlGenerator
|
|
264
303
|
} from './utils/mentionParser';
|
|
304
|
+
|
|
305
|
+
// Components — Comments (universal threaded comments)
|
|
306
|
+
export { default as CommentThread } from './components/CommentThread.svelte';
|
|
307
|
+
|
|
308
|
+
// Comment types
|
|
309
|
+
export type {
|
|
310
|
+
Comment,
|
|
311
|
+
CommentEntityType,
|
|
312
|
+
CommentVisibility,
|
|
313
|
+
CommentCreateInput,
|
|
314
|
+
CommentCallbacks
|
|
315
|
+
} from './types/comments';
|
|
316
|
+
export { COMMENT_ENTITY_TYPES, COMMENT_VISIBILITIES } from './types/comments';
|
|
317
|
+
|
|
318
|
+
// Components — PDF Viewer (server-side PDFium WASM rendering, no client pdf.js)
|
|
319
|
+
export { default as PDFViewer } from './components/PDFViewer.svelte';
|
|
320
|
+
|
|
321
|
+
// Components — Document annotation suite (from Steelbridge FileManager)
|
|
322
|
+
export { default as MarkupEditor } from './components/MarkupEditor.svelte';
|
|
323
|
+
export { default as MarkupOverlay } from './components/MarkupOverlay.svelte';
|
|
324
|
+
export { default as ImageLightbox } from './components/ImageLightbox.svelte';
|
|
325
|
+
export { default as AnnotationLayer } from './components/AnnotationLayer.svelte';
|
|
326
|
+
export { default as AnnotationPanel } from './components/AnnotationPanel.svelte';
|
|
327
|
+
|
|
328
|
+
// Markup drawing utilities
|
|
329
|
+
export {
|
|
330
|
+
MARKUP_COLORS,
|
|
331
|
+
DEFAULT_STROKE_WIDTH,
|
|
332
|
+
renderShape,
|
|
333
|
+
renderAllShapes,
|
|
334
|
+
nextShapeId,
|
|
335
|
+
} from './components/markup';
|
|
336
|
+
|
|
337
|
+
// Markup + annotation types
|
|
338
|
+
export type {
|
|
339
|
+
MarkupTool,
|
|
340
|
+
Point,
|
|
341
|
+
MarkupShape,
|
|
342
|
+
ArrowShape,
|
|
343
|
+
RectangleShape,
|
|
344
|
+
EllipseShape,
|
|
345
|
+
CloudShape,
|
|
346
|
+
FreehandShape,
|
|
347
|
+
TextShape,
|
|
348
|
+
} from './components/markup';
|
|
349
|
+
|
|
350
|
+
export type {
|
|
351
|
+
Annotation,
|
|
352
|
+
AnnotationProfile,
|
|
353
|
+
AnnotationThread,
|
|
354
|
+
AnnotationCallbacks,
|
|
355
|
+
} from './types/annotation';
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Global promise-based confirm dialog.
|
|
3
|
+
* Usage: const ok = await showConfirm({ title: '...', body: '...' })
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
export type ConfirmOptions = {
|
|
7
|
+
title: string;
|
|
8
|
+
body?: string;
|
|
9
|
+
confirmLabel?: string;
|
|
10
|
+
cancelLabel?: string;
|
|
11
|
+
danger?: boolean;
|
|
12
|
+
timeoutMs?: number; // auto-cancel after this ms; 0 = no timeout (default 8000)
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
type Pending = {
|
|
16
|
+
options: ConfirmOptions;
|
|
17
|
+
resolve: (result: boolean) => void;
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
let pending = $state<Pending | null>(null);
|
|
21
|
+
|
|
22
|
+
export function getPending(): Pending | null {
|
|
23
|
+
return pending;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export function showConfirm(options: ConfirmOptions): Promise<boolean> {
|
|
27
|
+
// If a dialog is already open, cancel it first.
|
|
28
|
+
if (pending) {
|
|
29
|
+
pending.resolve(false);
|
|
30
|
+
}
|
|
31
|
+
return new Promise<boolean>((resolve) => {
|
|
32
|
+
pending = { options, resolve };
|
|
33
|
+
});
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export function confirmDialog(): void {
|
|
37
|
+
if (!pending) return;
|
|
38
|
+
const p = pending;
|
|
39
|
+
pending = null;
|
|
40
|
+
p.resolve(true);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export function cancelDialog(): void {
|
|
44
|
+
if (!pending) return;
|
|
45
|
+
const p = pending;
|
|
46
|
+
pending = null;
|
|
47
|
+
p.resolve(false);
|
|
48
|
+
}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Global promise-based input dialog.
|
|
3
|
+
* Usage: const value = await showInput({ title: '...', placeholder: '...' })
|
|
4
|
+
* Returns the typed string if confirmed, null if cancelled.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
export type InputOptions = {
|
|
8
|
+
title: string;
|
|
9
|
+
body?: string;
|
|
10
|
+
placeholder?: string;
|
|
11
|
+
confirmLabel?: string;
|
|
12
|
+
cancelLabel?: string;
|
|
13
|
+
timeoutMs?: number; // reserved; InputDialog never auto-cancels (user is typing)
|
|
14
|
+
validate?: (value: string) => string | null; // null = valid, string = error message
|
|
15
|
+
maxLength?: number;
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
type Pending = {
|
|
19
|
+
options: InputOptions;
|
|
20
|
+
resolve: (result: string | null) => void;
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
let pending = $state<Pending | null>(null);
|
|
24
|
+
|
|
25
|
+
export function getPendingInput(): Pending | null {
|
|
26
|
+
return pending;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export function showInput(options: InputOptions): Promise<string | null> {
|
|
30
|
+
// If a dialog is already open, cancel it first.
|
|
31
|
+
if (pending) {
|
|
32
|
+
pending.resolve(null);
|
|
33
|
+
}
|
|
34
|
+
return new Promise<string | null>((resolve) => {
|
|
35
|
+
pending = { options, resolve };
|
|
36
|
+
});
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export function submitInput(value: string): void {
|
|
40
|
+
if (!pending) return;
|
|
41
|
+
const p = pending;
|
|
42
|
+
pending = null;
|
|
43
|
+
p.resolve(value);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export function cancelInput(): void {
|
|
47
|
+
if (!pending) return;
|
|
48
|
+
const p = pending;
|
|
49
|
+
pending = null;
|
|
50
|
+
p.resolve(null);
|
|
51
|
+
}
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* rail.css — keyboard rail-navigation primitives
|
|
3
|
+
*
|
|
4
|
+
* Provides focus-ring and value-change-pulse treatments for rail-nav
|
|
5
|
+
* components (vertical or horizontal keyboard-navigable lists where each
|
|
6
|
+
* rung carries a [data-rail-id] attribute).
|
|
7
|
+
*
|
|
8
|
+
* Usage: import once in your app's root layout or global stylesheet:
|
|
9
|
+
* @import '@joewinke/jatui/src/lib/styles/rail.css';
|
|
10
|
+
*
|
|
11
|
+
* Token: var(--color-primary) — DaisyUI v5 primary (resolves to the
|
|
12
|
+
* consuming app's active theme primary hue).
|
|
13
|
+
*
|
|
14
|
+
* Naming contract (must match the companion railNav Svelte action):
|
|
15
|
+
* - Active-rung focus treatment: [data-rail-id]:focus-visible selector
|
|
16
|
+
* AND .rail-focus utility class (both route to the same ring + tint).
|
|
17
|
+
* - Value-change one-shot pulse: .rail-cycled (added by the action,
|
|
18
|
+
* removed after the animation fires).
|
|
19
|
+
*
|
|
20
|
+
* Design rules enforced:
|
|
21
|
+
* - Ring + tint only — NO colored side-stripe (§6 Don't: border-l/r > 1px).
|
|
22
|
+
* - No box-shadow at rest; shadow only during the pulse keyframe.
|
|
23
|
+
* - Respects prefers-reduced-motion.
|
|
24
|
+
*/
|
|
25
|
+
|
|
26
|
+
/* ─── Active-rung focus ring ───────────────────────────────────────────────
|
|
27
|
+
Applied via :focus-visible on the rung element (attribute-based, works
|
|
28
|
+
without adding a class) AND via .rail-focus (explicit class, same visual).
|
|
29
|
+
The ring is primary-tinted at 65% opacity; border-radius matches DaisyUI's
|
|
30
|
+
field radius token (falls back to 0.25rem). No side-stripe. No shadow at
|
|
31
|
+
rest (Flat-by-Default rule). ─────────────────────────────────────────── */
|
|
32
|
+
|
|
33
|
+
[data-rail-id]:focus-visible,
|
|
34
|
+
[data-rail-id]:has(:focus-visible),
|
|
35
|
+
.rail-focus {
|
|
36
|
+
outline: 2px solid color-mix(in oklch, var(--color-primary) 65%, transparent);
|
|
37
|
+
outline-offset: 2px;
|
|
38
|
+
border-radius: var(--radius-field, 0.25rem);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/* ─── Value-change pulse ────────────────────────────────────────────────────
|
|
42
|
+
One-shot animation applied by the railNav action when the rung's bound
|
|
43
|
+
value changes. Scales up 2.5% and blooms a brief primary-hued glow ring,
|
|
44
|
+
then returns to rest. Duration 260ms — fast enough to feel snappy,
|
|
45
|
+
long enough to register as intentional feedback.
|
|
46
|
+
Box-shadow (not outline) is used here so it can animate smoothly;
|
|
47
|
+
outline transitions are not interpolated by CSS. ─────────────────────── */
|
|
48
|
+
|
|
49
|
+
@keyframes rail-cycled {
|
|
50
|
+
0% { transform: scale(1); box-shadow: 0 0 0 0 color-mix(in oklch, var(--color-primary) 0%, transparent); }
|
|
51
|
+
35% { transform: scale(1.025); box-shadow: 0 0 0 3px color-mix(in oklch, var(--color-primary) 32%, transparent); }
|
|
52
|
+
100% { transform: scale(1); box-shadow: 0 0 0 0 color-mix(in oklch, var(--color-primary) 0%, transparent); }
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
.rail-cycled {
|
|
56
|
+
animation: rail-cycled 0.26s cubic-bezier(0.22, 1, 0.36, 1);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
@media (prefers-reduced-motion: reduce) {
|
|
60
|
+
.rail-cycled {
|
|
61
|
+
animation: none;
|
|
62
|
+
}
|
|
63
|
+
}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
// Annotation types for document/image annotation suite
|
|
2
|
+
// Used by AnnotationLayer, AnnotationPanel, ImageLightbox
|
|
3
|
+
|
|
4
|
+
export interface Annotation {
|
|
5
|
+
id: string
|
|
6
|
+
user_id: string
|
|
7
|
+
x_percent: number
|
|
8
|
+
y_percent: number
|
|
9
|
+
content: string
|
|
10
|
+
parent_annotation_id: string | null
|
|
11
|
+
resolved: boolean
|
|
12
|
+
resolved_by: string | null
|
|
13
|
+
resolved_at: string | null
|
|
14
|
+
created_at: string
|
|
15
|
+
updated_at: string
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export interface AnnotationProfile {
|
|
19
|
+
full_name: string | null
|
|
20
|
+
avatar_url: string | null
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export interface AnnotationThread {
|
|
24
|
+
root: Annotation
|
|
25
|
+
replies: Annotation[]
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/** Callbacks for AnnotationPanel async operations */
|
|
29
|
+
export interface AnnotationCallbacks {
|
|
30
|
+
/** Post a reply to a root annotation. Must return the new annotation or throw. */
|
|
31
|
+
onAddReply: (parentId: string, content: string) => Promise<void>
|
|
32
|
+
/** Toggle resolved state. */
|
|
33
|
+
onResolve: (annotationId: string, resolved: boolean) => Promise<void>
|
|
34
|
+
/** Delete an annotation (and its replies). */
|
|
35
|
+
onDelete: (annotationId: string) => Promise<void>
|
|
36
|
+
/** Edit annotation content. */
|
|
37
|
+
onEdit: (annotationId: string, content: string) => Promise<void>
|
|
38
|
+
}
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
// Threaded Comments Types
|
|
2
|
+
// Generic types for the CommentThread UI component — no framework or backend
|
|
3
|
+
// dependencies. Mirrors the JST `$lib/types/comments` projection so the
|
|
4
|
+
// component is a drop-in for any app whose server speaks the same shape.
|
|
5
|
+
//
|
|
6
|
+
// Threading is ONE level deep on the data side: a comment is either
|
|
7
|
+
// top-level (`parent_id === null`) or a single-level reply. The UI renders
|
|
8
|
+
// this as two visual levels (a comment and its replies).
|
|
9
|
+
|
|
10
|
+
/** Entity kinds a comment thread can hang off — mirrors the host DB CHECK. */
|
|
11
|
+
export type CommentEntityType =
|
|
12
|
+
| 'project'
|
|
13
|
+
| 'invoice'
|
|
14
|
+
| 'contract'
|
|
15
|
+
| 'asset'
|
|
16
|
+
| 'task'
|
|
17
|
+
| 'form_submission'
|
|
18
|
+
| 'milestone'
|
|
19
|
+
| 'contract_term'
|
|
20
|
+
|
|
21
|
+
export const COMMENT_ENTITY_TYPES: readonly CommentEntityType[] = [
|
|
22
|
+
'project',
|
|
23
|
+
'invoice',
|
|
24
|
+
'contract',
|
|
25
|
+
'asset',
|
|
26
|
+
'task',
|
|
27
|
+
'form_submission',
|
|
28
|
+
'milestone',
|
|
29
|
+
'contract_term'
|
|
30
|
+
] as const
|
|
31
|
+
|
|
32
|
+
/** Internal = staff-only. Client = also visible in the customer portal. */
|
|
33
|
+
export type CommentVisibility = 'internal' | 'client'
|
|
34
|
+
|
|
35
|
+
export const COMMENT_VISIBILITIES: readonly CommentVisibility[] = [
|
|
36
|
+
'internal',
|
|
37
|
+
'client'
|
|
38
|
+
] as const
|
|
39
|
+
|
|
40
|
+
export interface Comment {
|
|
41
|
+
id: string
|
|
42
|
+
team_id: string
|
|
43
|
+
entity_type: CommentEntityType
|
|
44
|
+
entity_id: string
|
|
45
|
+
author_id: string
|
|
46
|
+
/** Joined author display name (null if the profile is gone). */
|
|
47
|
+
author_name: string | null
|
|
48
|
+
/** Joined author avatar URL. */
|
|
49
|
+
author_avatar_url: string | null
|
|
50
|
+
body: string
|
|
51
|
+
/** null = top-level; otherwise the id of the top-level comment it replies to. */
|
|
52
|
+
parent_id: string | null
|
|
53
|
+
visibility: CommentVisibility
|
|
54
|
+
/** Set the first time the body is edited. */
|
|
55
|
+
edited_at: string | null
|
|
56
|
+
/** Set on soft-delete; body is replaced with "[deleted]". */
|
|
57
|
+
deleted_at: string | null
|
|
58
|
+
created_at: string
|
|
59
|
+
/** Number of replies (top-level rows only; always 0 for replies). */
|
|
60
|
+
reply_count: number
|
|
61
|
+
/**
|
|
62
|
+
* Replies are loaded on demand; this is [] in the paginated top-level
|
|
63
|
+
* listing and populated by the component when a thread is expanded.
|
|
64
|
+
*/
|
|
65
|
+
replies: Comment[]
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
export interface CommentCreateInput {
|
|
69
|
+
entityType: CommentEntityType
|
|
70
|
+
entityId: string
|
|
71
|
+
body: string
|
|
72
|
+
/** When set, this comment is a reply to the given TOP-LEVEL comment. */
|
|
73
|
+
parentId?: string | null
|
|
74
|
+
/** Defaults to "internal". Clamped to the parent's visibility for replies. */
|
|
75
|
+
visibility?: CommentVisibility
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* Transport-agnostic data layer for `<CommentThread>`. The consuming app
|
|
80
|
+
* wires these to a server load / form action / fetch endpoint. The
|
|
81
|
+
* component never imports server code or knows how data is fetched.
|
|
82
|
+
*/
|
|
83
|
+
export interface CommentCallbacks {
|
|
84
|
+
/** Persist a new comment (top-level or reply) and return the created row. */
|
|
85
|
+
submitComment: (input: CommentCreateInput) => Promise<Comment>
|
|
86
|
+
/** Edit an existing comment body; returns the updated row. */
|
|
87
|
+
editComment: (id: string, body: string) => Promise<Comment>
|
|
88
|
+
/** Soft-delete a comment. The row stays so replies remain anchored. */
|
|
89
|
+
deleteComment: (id: string) => Promise<void>
|
|
90
|
+
/** Fetch the replies for a top-level comment (oldest-first). */
|
|
91
|
+
loadReplies: (parentId: string) => Promise<Comment[]>
|
|
92
|
+
/**
|
|
93
|
+
* Optional: fetch the next page of top-level comments. When omitted, the
|
|
94
|
+
* "Load more" affordance is hidden.
|
|
95
|
+
*/
|
|
96
|
+
loadMore?: (offset: number) => Promise<{ comments: Comment[]; total: number }>
|
|
97
|
+
}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Entity preview card — the uniform shape an EntityPreviewCard renders and that
|
|
3
|
+
* a server-side resolver returns for any domain entity. One shape for every
|
|
4
|
+
* entity type so the card and hover-action stay generic and the same card backs
|
|
5
|
+
* hover-previews, @-mention unfurls, and link previews.
|
|
6
|
+
*
|
|
7
|
+
* Pure types — safe to import from both server (the resolver) and client (the
|
|
8
|
+
* card + action).
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
/** A small labelled stat shown in the card's footer grid (e.g. "Submissions" → 42). */
|
|
12
|
+
export interface EntityPreviewStat {
|
|
13
|
+
label: string
|
|
14
|
+
value: string | number
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/** A coloured pill (status, type, …). `tone` maps to a DaisyUI semantic token. */
|
|
18
|
+
export interface EntityPreviewBadge {
|
|
19
|
+
label: string
|
|
20
|
+
tone?: "neutral" | "primary" | "info" | "success" | "warning" | "error"
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export interface EntityPreviewData {
|
|
24
|
+
/** Canonical entity type slug (e.g. "base", "vendor", "form", "task"). */
|
|
25
|
+
type: string
|
|
26
|
+
id: string
|
|
27
|
+
/** Human label for the type, shown as the card's kicker (e.g. "Knowledge base"). */
|
|
28
|
+
typeLabel: string
|
|
29
|
+
/** Main title. */
|
|
30
|
+
title: string
|
|
31
|
+
/** Optional one-line subtitle under the title (e.g. a vendor's company). */
|
|
32
|
+
subtitle?: string | null
|
|
33
|
+
/** Optional longer summary / description (clamped in the card). */
|
|
34
|
+
summary?: string | null
|
|
35
|
+
/** og:image / avatar / thumbnail URL, when the entity has one. */
|
|
36
|
+
imageUrl?: string | null
|
|
37
|
+
/** A short emoji/text glyph fallback when there's no image. */
|
|
38
|
+
icon?: string | null
|
|
39
|
+
/** Up to ~4 labelled stats. */
|
|
40
|
+
stats?: EntityPreviewStat[]
|
|
41
|
+
/** Up to ~3 status/type pills. */
|
|
42
|
+
badges?: EntityPreviewBadge[]
|
|
43
|
+
/** The admin detail route for the entity (the card links here). */
|
|
44
|
+
href?: string | null
|
|
45
|
+
}
|
|
@@ -5,6 +5,8 @@ export interface FilePickerFile {
|
|
|
5
5
|
updated_at: string
|
|
6
6
|
metadata: Record<string, any>
|
|
7
7
|
scope: "personal" | "team"
|
|
8
|
+
/** Publicly accessible URL (no auth required). When present, used instead of the /api/files proxy URL. */
|
|
9
|
+
publicUrl?: string
|
|
8
10
|
}
|
|
9
11
|
|
|
10
12
|
export interface FilePickerSelection {
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
export interface ImageVariantSpec {
|
|
2
|
+
name: string
|
|
3
|
+
width: number
|
|
4
|
+
height: number
|
|
5
|
+
fit?: "contain" | "cover" | "fill"
|
|
6
|
+
/** CSS hex color or "transparent". Transparent background when omitted. */
|
|
7
|
+
background?: string
|
|
8
|
+
/**
|
|
9
|
+
* Optional Gemini edit instruction run on the (background-removed) source
|
|
10
|
+
* before Sharp's deterministic resize. Used to intelligently re-frame the
|
|
11
|
+
* variant — e.g. crop a logo down to just the brand mark, or compose an OG
|
|
12
|
+
* card. Falls back to Sharp-only if the Gemini call fails.
|
|
13
|
+
*/
|
|
14
|
+
aiPrompt?: string
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export interface ImageProcessingSpec {
|
|
18
|
+
removeBackground?: boolean
|
|
19
|
+
aspectRatio?: string
|
|
20
|
+
variants: ImageVariantSpec[]
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export interface VariantResult {
|
|
24
|
+
name: string
|
|
25
|
+
url: string
|
|
26
|
+
width: number
|
|
27
|
+
height: number
|
|
28
|
+
backgroundRemovalFailed?: boolean
|
|
29
|
+
/** True when this variant had an aiPrompt but the Gemini edit call failed
|
|
30
|
+
* and we fell back to Sharp-only. The result is still uploaded; this is a
|
|
31
|
+
* non-blocking signal that the smart re-frame didn't happen. */
|
|
32
|
+
aiEditFailed?: boolean
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export type VariantState =
|
|
36
|
+
| { status: "idle" }
|
|
37
|
+
| { status: "loading" }
|
|
38
|
+
| { status: "done"; result: VariantResult }
|
|
39
|
+
| { status: "error"; message: string }
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Template variable types — shared shape between the JST server-side registry
|
|
3
|
+
* (`src/lib/server/template-vars.ts`, `getVarsForCategory`) and the
|
|
4
|
+
* VariablePicker component.
|
|
5
|
+
*
|
|
6
|
+
* jatui cannot import the host app's `$lib/server` module (server-only), so
|
|
7
|
+
* these types are mirrored here. They are kept structurally identical to the
|
|
8
|
+
* registry's output so `getVarsForCategory(category, availableVars)` can be
|
|
9
|
+
* passed straight into `<VariablePicker groups={...} />` with no mapping —
|
|
10
|
+
* same jst↔jatui structural-compatibility pattern used by CommentThread.
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
/** A single insertable merge variable. */
|
|
14
|
+
export interface TemplateVar {
|
|
15
|
+
/**
|
|
16
|
+
* Dotted variable path inserted into the template as `{{name}}`.
|
|
17
|
+
* e.g. `contact.name`, `org.logo_url`, `total`.
|
|
18
|
+
*/
|
|
19
|
+
name: string
|
|
20
|
+
/** Human label shown in the picker, e.g. "Contact name". */
|
|
21
|
+
label: string
|
|
22
|
+
/** Optional one-line help text describing the value. */
|
|
23
|
+
description?: string
|
|
24
|
+
/** Optional sample value shown as a hint, e.g. "Acme Inc.". */
|
|
25
|
+
example?: string
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/** Variable context group, e.g. all client/org/financial/custom vars. */
|
|
29
|
+
export interface TemplateVarGroup {
|
|
30
|
+
/** Stable group id — matches VarGroupId in the host app's template-vars registry. */
|
|
31
|
+
id: string
|
|
32
|
+
/** Group heading shown in the picker, e.g. "Client". */
|
|
33
|
+
label: string
|
|
34
|
+
/** Variables in this group (never empty — empty groups are omitted). */
|
|
35
|
+
vars: TemplateVar[]
|
|
36
|
+
}
|
|
@@ -24,9 +24,11 @@ export function normalizeTimestamp(timestamp: string): string {
|
|
|
24
24
|
|
|
25
25
|
/**
|
|
26
26
|
* Parse a potentially non-standard timestamp into a Date object.
|
|
27
|
+
* Accepts a Date (returned by postgres.js), ISO string, or null/undefined.
|
|
27
28
|
*/
|
|
28
|
-
export function parseTimestamp(timestamp: string | null | undefined): Date | null {
|
|
29
|
+
export function parseTimestamp(timestamp: Date | string | null | undefined): Date | null {
|
|
29
30
|
if (!timestamp) return null;
|
|
31
|
+
if (timestamp instanceof Date) return isNaN(timestamp.getTime()) ? null : timestamp;
|
|
30
32
|
|
|
31
33
|
const normalized = normalizeTimestamp(timestamp);
|
|
32
34
|
const date = new Date(normalized);
|
|
@@ -38,7 +40,7 @@ export function parseTimestamp(timestamp: string | null | undefined): Date | nul
|
|
|
38
40
|
* Format relative time (e.g., "2d", "3mo", "1y").
|
|
39
41
|
* Compact format suitable for tables and compact UIs.
|
|
40
42
|
*/
|
|
41
|
-
export function formatRelativeTime(dateStr: string | null | undefined): string {
|
|
43
|
+
export function formatRelativeTime(dateStr: Date | string | null | undefined): string {
|
|
42
44
|
if (!dateStr) return '-';
|
|
43
45
|
|
|
44
46
|
const date = parseTimestamp(dateStr);
|
|
@@ -65,7 +67,7 @@ export function formatRelativeTime(dateStr: string | null | undefined): string {
|
|
|
65
67
|
/**
|
|
66
68
|
* Format full date and time for tooltips and detailed views.
|
|
67
69
|
*/
|
|
68
|
-
export function formatFullDate(dateStr: string | null | undefined): string {
|
|
70
|
+
export function formatFullDate(dateStr: Date | string | null | undefined): string {
|
|
69
71
|
if (!dateStr) return '';
|
|
70
72
|
|
|
71
73
|
const date = parseTimestamp(dateStr);
|
|
@@ -83,7 +85,7 @@ export function formatFullDate(dateStr: string | null | undefined): string {
|
|
|
83
85
|
/**
|
|
84
86
|
* Format short date for compact displays (e.g., date range picker).
|
|
85
87
|
*/
|
|
86
|
-
export function formatShortDate(dateStr: string | null | undefined): string {
|
|
88
|
+
export function formatShortDate(dateStr: Date | string | null | undefined): string {
|
|
87
89
|
if (!dateStr) return '';
|
|
88
90
|
|
|
89
91
|
const date = parseTimestamp(dateStr);
|
|
@@ -96,7 +98,7 @@ export function formatShortDate(dateStr: string | null | undefined): string {
|
|
|
96
98
|
* Format last activity time for agent cards.
|
|
97
99
|
* Shows "Just now", "Xm ago", "Xh ago", or "Xd ago".
|
|
98
100
|
*/
|
|
99
|
-
export function formatLastActivity(timestamp: string | null | undefined): string {
|
|
101
|
+
export function formatLastActivity(timestamp: Date | string | null | undefined): string {
|
|
100
102
|
if (!timestamp) return 'Never';
|
|
101
103
|
|
|
102
104
|
const date = parseTimestamp(timestamp);
|
|
@@ -117,7 +119,7 @@ export function formatLastActivity(timestamp: string | null | undefined): string
|
|
|
117
119
|
/**
|
|
118
120
|
* Format date for display (simple locale string).
|
|
119
121
|
*/
|
|
120
|
-
export function formatDate(dateString: string | null | undefined): string {
|
|
122
|
+
export function formatDate(dateString: Date | string | null | undefined): string {
|
|
121
123
|
if (!dateString) return 'N/A';
|
|
122
124
|
|
|
123
125
|
const date = parseTimestamp(dateString);
|
|
@@ -129,7 +131,7 @@ export function formatDate(dateString: string | null | undefined): string {
|
|
|
129
131
|
/**
|
|
130
132
|
* Get milliseconds since timestamp (for calculations).
|
|
131
133
|
*/
|
|
132
|
-
export function getTimeSinceMs(timestamp: string | null | undefined): number {
|
|
134
|
+
export function getTimeSinceMs(timestamp: Date | string | null | undefined): number {
|
|
133
135
|
if (!timestamp) return Infinity;
|
|
134
136
|
|
|
135
137
|
const date = parseTimestamp(timestamp);
|
|
@@ -141,14 +143,14 @@ export function getTimeSinceMs(timestamp: string | null | undefined): number {
|
|
|
141
143
|
/**
|
|
142
144
|
* Get minutes since timestamp.
|
|
143
145
|
*/
|
|
144
|
-
export function getTimeSinceMinutes(timestamp: string | null | undefined): number {
|
|
146
|
+
export function getTimeSinceMinutes(timestamp: Date | string | null | undefined): number {
|
|
145
147
|
return getTimeSinceMs(timestamp) / 60000;
|
|
146
148
|
}
|
|
147
149
|
|
|
148
150
|
/**
|
|
149
151
|
* Check if timestamp is within a given number of minutes.
|
|
150
152
|
*/
|
|
151
|
-
export function isWithinMinutes(timestamp: string | null | undefined, minutes: number): boolean {
|
|
153
|
+
export function isWithinMinutes(timestamp: Date | string | null | undefined, minutes: number): boolean {
|
|
152
154
|
return getTimeSinceMinutes(timestamp) < minutes;
|
|
153
155
|
}
|
|
154
156
|
|
|
@@ -156,7 +158,7 @@ export function isWithinMinutes(timestamp: string | null | undefined, minutes: n
|
|
|
156
158
|
* Format date + time for detail views (e.g. "Jan 5, 2:30 PM").
|
|
157
159
|
* No year — use formatFullDate when year is needed.
|
|
158
160
|
*/
|
|
159
|
-
export function formatDateTime(dateStr: string | null | undefined): string {
|
|
161
|
+
export function formatDateTime(dateStr: Date | string | null | undefined): string {
|
|
160
162
|
if (!dateStr) return '';
|
|
161
163
|
const date = parseTimestamp(dateStr);
|
|
162
164
|
if (!date) return '';
|