@joewinke/jatui 0.1.10 → 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 +3 -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 +188 -0
- 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 +183 -63
- 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 +105 -1
- 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/phone.ts +80 -0
- package/src/lib/utils/taskUtils.ts +21 -7
|
@@ -0,0 +1,287 @@
|
|
|
1
|
+
// Markup drawing primitives for construction drawing redlining
|
|
2
|
+
// Adapted from JAT feedback annotation engine with cloud bubble tool added
|
|
3
|
+
|
|
4
|
+
export type MarkupTool =
|
|
5
|
+
| "arrow"
|
|
6
|
+
| "rectangle"
|
|
7
|
+
| "ellipse"
|
|
8
|
+
| "cloud"
|
|
9
|
+
| "freehand"
|
|
10
|
+
| "text"
|
|
11
|
+
|
|
12
|
+
export interface Point {
|
|
13
|
+
x: number
|
|
14
|
+
y: number
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export interface BaseShape {
|
|
18
|
+
id: number
|
|
19
|
+
type: MarkupTool
|
|
20
|
+
color: string
|
|
21
|
+
strokeWidth: number
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export interface ArrowShape extends BaseShape {
|
|
25
|
+
type: "arrow"
|
|
26
|
+
start: Point
|
|
27
|
+
end: Point
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export interface RectangleShape extends BaseShape {
|
|
31
|
+
type: "rectangle"
|
|
32
|
+
start: Point
|
|
33
|
+
end: Point
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export interface EllipseShape extends BaseShape {
|
|
37
|
+
type: "ellipse"
|
|
38
|
+
start: Point
|
|
39
|
+
end: Point
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export interface CloudShape extends BaseShape {
|
|
43
|
+
type: "cloud"
|
|
44
|
+
start: Point
|
|
45
|
+
end: Point
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export interface FreehandShape extends BaseShape {
|
|
49
|
+
type: "freehand"
|
|
50
|
+
points: Point[]
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
export interface TextShape extends BaseShape {
|
|
54
|
+
type: "text"
|
|
55
|
+
position: Point
|
|
56
|
+
content: string
|
|
57
|
+
fontSize: number
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
export type MarkupShape =
|
|
61
|
+
| ArrowShape
|
|
62
|
+
| RectangleShape
|
|
63
|
+
| EllipseShape
|
|
64
|
+
| CloudShape
|
|
65
|
+
| FreehandShape
|
|
66
|
+
| TextShape
|
|
67
|
+
|
|
68
|
+
export const MARKUP_COLORS = [
|
|
69
|
+
"#ef4444",
|
|
70
|
+
"#eab308",
|
|
71
|
+
"#22c55e",
|
|
72
|
+
"#3b82f6",
|
|
73
|
+
"#ffffff",
|
|
74
|
+
"#111827",
|
|
75
|
+
]
|
|
76
|
+
|
|
77
|
+
export const DEFAULT_STROKE_WIDTH = 3
|
|
78
|
+
|
|
79
|
+
let _nextId = 1
|
|
80
|
+
export function nextShapeId(): number {
|
|
81
|
+
return _nextId++
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
function drawArrow(ctx: CanvasRenderingContext2D, shape: ArrowShape) {
|
|
85
|
+
const { start, end, color, strokeWidth } = shape
|
|
86
|
+
ctx.strokeStyle = color
|
|
87
|
+
ctx.lineWidth = strokeWidth
|
|
88
|
+
ctx.lineCap = "round"
|
|
89
|
+
ctx.lineJoin = "round"
|
|
90
|
+
|
|
91
|
+
ctx.beginPath()
|
|
92
|
+
ctx.moveTo(start.x, start.y)
|
|
93
|
+
ctx.lineTo(end.x, end.y)
|
|
94
|
+
ctx.stroke()
|
|
95
|
+
|
|
96
|
+
// Arrowhead
|
|
97
|
+
const angle = Math.atan2(end.y - start.y, end.x - start.x)
|
|
98
|
+
const headLen = 14
|
|
99
|
+
const headAngle = Math.PI / 7
|
|
100
|
+
ctx.beginPath()
|
|
101
|
+
ctx.moveTo(end.x, end.y)
|
|
102
|
+
ctx.lineTo(
|
|
103
|
+
end.x - headLen * Math.cos(angle - headAngle),
|
|
104
|
+
end.y - headLen * Math.sin(angle - headAngle),
|
|
105
|
+
)
|
|
106
|
+
ctx.moveTo(end.x, end.y)
|
|
107
|
+
ctx.lineTo(
|
|
108
|
+
end.x - headLen * Math.cos(angle + headAngle),
|
|
109
|
+
end.y - headLen * Math.sin(angle + headAngle),
|
|
110
|
+
)
|
|
111
|
+
ctx.stroke()
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
function drawRectangle(ctx: CanvasRenderingContext2D, shape: RectangleShape) {
|
|
115
|
+
const { start, end, color, strokeWidth } = shape
|
|
116
|
+
ctx.strokeStyle = color
|
|
117
|
+
ctx.lineWidth = strokeWidth
|
|
118
|
+
ctx.lineJoin = "round"
|
|
119
|
+
const x = Math.min(start.x, end.x)
|
|
120
|
+
const y = Math.min(start.y, end.y)
|
|
121
|
+
const w = Math.abs(end.x - start.x)
|
|
122
|
+
const h = Math.abs(end.y - start.y)
|
|
123
|
+
ctx.strokeRect(x, y, w, h)
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
function drawEllipse(ctx: CanvasRenderingContext2D, shape: EllipseShape) {
|
|
127
|
+
const { start, end, color, strokeWidth } = shape
|
|
128
|
+
ctx.strokeStyle = color
|
|
129
|
+
ctx.lineWidth = strokeWidth
|
|
130
|
+
const cx = (start.x + end.x) / 2
|
|
131
|
+
const cy = (start.y + end.y) / 2
|
|
132
|
+
const rx = Math.abs(end.x - start.x) / 2
|
|
133
|
+
const ry = Math.abs(end.y - start.y) / 2
|
|
134
|
+
if (rx < 1 || ry < 1) return
|
|
135
|
+
ctx.beginPath()
|
|
136
|
+
ctx.ellipse(cx, cy, rx, ry, 0, 0, Math.PI * 2)
|
|
137
|
+
ctx.stroke()
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
/**
|
|
141
|
+
* Revision cloud — a series of small arcs along the perimeter of a rectangle.
|
|
142
|
+
* Standard construction markup tool for highlighting areas of change.
|
|
143
|
+
*/
|
|
144
|
+
function drawCloud(ctx: CanvasRenderingContext2D, shape: CloudShape) {
|
|
145
|
+
const { start, end, color, strokeWidth } = shape
|
|
146
|
+
ctx.strokeStyle = color
|
|
147
|
+
ctx.lineWidth = strokeWidth
|
|
148
|
+
ctx.lineCap = "round"
|
|
149
|
+
ctx.lineJoin = "round"
|
|
150
|
+
|
|
151
|
+
const x = Math.min(start.x, end.x)
|
|
152
|
+
const y = Math.min(start.y, end.y)
|
|
153
|
+
const w = Math.abs(end.x - start.x)
|
|
154
|
+
const h = Math.abs(end.y - start.y)
|
|
155
|
+
if (w < 4 || h < 4) return
|
|
156
|
+
|
|
157
|
+
// Generate perimeter points, then draw arcs between them
|
|
158
|
+
const arcSize = Math.min(w, h, 30) // arc diameter adapts to shape size
|
|
159
|
+
const perimeter = 2 * (w + h)
|
|
160
|
+
const numArcs = Math.max(8, Math.round(perimeter / arcSize))
|
|
161
|
+
|
|
162
|
+
// Collect points along the rectangle perimeter
|
|
163
|
+
const points: Point[] = []
|
|
164
|
+
const segLengths = [w, h, w, h]
|
|
165
|
+
const segDirs = [
|
|
166
|
+
{ dx: 1, dy: 0 },
|
|
167
|
+
{ dx: 0, dy: 1 },
|
|
168
|
+
{ dx: -1, dy: 0 },
|
|
169
|
+
{ dx: 0, dy: -1 },
|
|
170
|
+
]
|
|
171
|
+
const segStarts: Point[] = [
|
|
172
|
+
{ x, y },
|
|
173
|
+
{ x: x + w, y },
|
|
174
|
+
{ x: x + w, y: y + h },
|
|
175
|
+
{ x, y: y + h },
|
|
176
|
+
]
|
|
177
|
+
|
|
178
|
+
let totalLen = 0
|
|
179
|
+
for (const l of segLengths) totalLen += l
|
|
180
|
+
const step = totalLen / numArcs
|
|
181
|
+
|
|
182
|
+
let dist = 0
|
|
183
|
+
let segIdx = 0
|
|
184
|
+
let segDist = 0
|
|
185
|
+
|
|
186
|
+
for (let i = 0; i < numArcs; i++) {
|
|
187
|
+
const target = i * step
|
|
188
|
+
while (dist + segLengths[segIdx] - segDist < target && segIdx < 3) {
|
|
189
|
+
dist += segLengths[segIdx] - segDist
|
|
190
|
+
segIdx++
|
|
191
|
+
segDist = 0
|
|
192
|
+
}
|
|
193
|
+
const remaining = target - dist
|
|
194
|
+
points.push({
|
|
195
|
+
x: segStarts[segIdx].x + segDirs[segIdx].dx * (segDist + remaining),
|
|
196
|
+
y: segStarts[segIdx].y + segDirs[segIdx].dy * (segDist + remaining),
|
|
197
|
+
})
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
// Draw arcs between consecutive points
|
|
201
|
+
ctx.beginPath()
|
|
202
|
+
for (let i = 0; i < points.length; i++) {
|
|
203
|
+
const p1 = points[i]
|
|
204
|
+
const p2 = points[(i + 1) % points.length]
|
|
205
|
+
const mx = (p1.x + p2.x) / 2
|
|
206
|
+
const my = (p1.y + p2.y) / 2
|
|
207
|
+
// Bulge outward from center of rectangle
|
|
208
|
+
const cx = x + w / 2
|
|
209
|
+
const cy = y + h / 2
|
|
210
|
+
const dx = mx - cx
|
|
211
|
+
const dy = my - cy
|
|
212
|
+
const len = Math.sqrt(dx * dx + dy * dy) || 1
|
|
213
|
+
const bulge = arcSize * 0.35
|
|
214
|
+
const ctrlX = mx + (dx / len) * bulge
|
|
215
|
+
const ctrlY = my + (dy / len) * bulge
|
|
216
|
+
|
|
217
|
+
if (i === 0) {
|
|
218
|
+
ctx.moveTo(p1.x, p1.y)
|
|
219
|
+
}
|
|
220
|
+
ctx.quadraticCurveTo(ctrlX, ctrlY, p2.x, p2.y)
|
|
221
|
+
}
|
|
222
|
+
ctx.closePath()
|
|
223
|
+
ctx.stroke()
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
function drawFreehand(ctx: CanvasRenderingContext2D, shape: FreehandShape) {
|
|
227
|
+
const { points, color, strokeWidth } = shape
|
|
228
|
+
if (points.length < 2) return
|
|
229
|
+
ctx.strokeStyle = color
|
|
230
|
+
ctx.lineWidth = strokeWidth
|
|
231
|
+
ctx.lineCap = "round"
|
|
232
|
+
ctx.lineJoin = "round"
|
|
233
|
+
ctx.beginPath()
|
|
234
|
+
ctx.moveTo(points[0].x, points[0].y)
|
|
235
|
+
for (let i = 1; i < points.length; i++) {
|
|
236
|
+
ctx.lineTo(points[i].x, points[i].y)
|
|
237
|
+
}
|
|
238
|
+
ctx.stroke()
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
function drawText(ctx: CanvasRenderingContext2D, shape: TextShape) {
|
|
242
|
+
const { position, content, color, fontSize } = shape
|
|
243
|
+
if (!content) return
|
|
244
|
+
ctx.font = `bold ${fontSize}px sans-serif`
|
|
245
|
+
ctx.textBaseline = "top"
|
|
246
|
+
// Black outline for contrast on construction drawings
|
|
247
|
+
ctx.strokeStyle = "#000000"
|
|
248
|
+
ctx.lineWidth = 2
|
|
249
|
+
ctx.lineJoin = "round"
|
|
250
|
+
ctx.strokeText(content, position.x, position.y)
|
|
251
|
+
ctx.fillStyle = color
|
|
252
|
+
ctx.fillText(content, position.x, position.y)
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
export function renderShape(ctx: CanvasRenderingContext2D, shape: MarkupShape) {
|
|
256
|
+
ctx.save()
|
|
257
|
+
switch (shape.type) {
|
|
258
|
+
case "arrow":
|
|
259
|
+
drawArrow(ctx, shape)
|
|
260
|
+
break
|
|
261
|
+
case "rectangle":
|
|
262
|
+
drawRectangle(ctx, shape)
|
|
263
|
+
break
|
|
264
|
+
case "ellipse":
|
|
265
|
+
drawEllipse(ctx, shape)
|
|
266
|
+
break
|
|
267
|
+
case "cloud":
|
|
268
|
+
drawCloud(ctx, shape)
|
|
269
|
+
break
|
|
270
|
+
case "freehand":
|
|
271
|
+
drawFreehand(ctx, shape)
|
|
272
|
+
break
|
|
273
|
+
case "text":
|
|
274
|
+
drawText(ctx, shape)
|
|
275
|
+
break
|
|
276
|
+
}
|
|
277
|
+
ctx.restore()
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
export function renderAllShapes(
|
|
281
|
+
ctx: CanvasRenderingContext2D,
|
|
282
|
+
shapes: MarkupShape[],
|
|
283
|
+
) {
|
|
284
|
+
for (const shape of shapes) {
|
|
285
|
+
renderShape(ctx, shape)
|
|
286
|
+
}
|
|
287
|
+
}
|
|
@@ -83,7 +83,7 @@
|
|
|
83
83
|
<dialog class="modal modal-open">
|
|
84
84
|
<div class="modal-box max-w-md">
|
|
85
85
|
<div class="flex items-center justify-between mb-4">
|
|
86
|
-
<h3 class="font-
|
|
86
|
+
<h3 class="font-semibold">
|
|
87
87
|
{channel.emoji || '#'} {channel.name}
|
|
88
88
|
</h3>
|
|
89
89
|
<button class="btn btn-ghost btn-sm btn-circle" onclick={onclose}>✕</button>
|
|
@@ -96,11 +96,11 @@
|
|
|
96
96
|
{#if isEditing}
|
|
97
97
|
<div class="space-y-4">
|
|
98
98
|
<div>
|
|
99
|
-
<label for="channel-info-name" class="label"><span class="
|
|
99
|
+
<label for="channel-info-name" class="label"><span class="text-[0.8125rem] font-medium text-base-content/85">Name</span></label>
|
|
100
100
|
<input id="channel-info-name" type="text" bind:value={editName} class="input input-bordered w-full" maxlength="80" />
|
|
101
101
|
</div>
|
|
102
102
|
<div>
|
|
103
|
-
<label for="channel-info-description" class="label"><span class="
|
|
103
|
+
<label for="channel-info-description" class="label"><span class="text-[0.8125rem] font-medium text-base-content/85">Description</span></label>
|
|
104
104
|
<textarea id="channel-info-description" bind:value={editDescription} class="textarea textarea-bordered w-full" rows="3" maxlength="250"></textarea>
|
|
105
105
|
</div>
|
|
106
106
|
<div class="flex gap-2">
|
|
@@ -114,20 +114,20 @@
|
|
|
114
114
|
{:else}
|
|
115
115
|
<div class="space-y-4">
|
|
116
116
|
<div>
|
|
117
|
-
<span class="text-
|
|
118
|
-
<p class="text-
|
|
117
|
+
<span class="text-[0.8125rem] text-base-content/45 tracking-[0.005em]">Type</span>
|
|
118
|
+
<p class="text-[0.9375rem]">{channel.type === 'private' ? '🔒 Private' : '# Public'}</p>
|
|
119
119
|
</div>
|
|
120
120
|
|
|
121
121
|
{#if channel.description}
|
|
122
122
|
<div>
|
|
123
|
-
<span class="text-
|
|
124
|
-
<p class="text-
|
|
123
|
+
<span class="text-[0.8125rem] text-base-content/45 tracking-[0.005em]">Description</span>
|
|
124
|
+
<p class="text-[0.9375rem]">{channel.description}</p>
|
|
125
125
|
</div>
|
|
126
126
|
{/if}
|
|
127
127
|
|
|
128
128
|
<div>
|
|
129
|
-
<span class="text-
|
|
130
|
-
<p class="text-
|
|
129
|
+
<span class="text-[0.8125rem] text-base-content/45 tracking-[0.005em]">Created</span>
|
|
130
|
+
<p class="text-[0.9375rem]">{formatDate(channel.createdAt)}{channel.creatorName ? ` by ${channel.creatorName}` : ''}</p>
|
|
131
131
|
</div>
|
|
132
132
|
|
|
133
133
|
<div class="flex flex-wrap gap-2">
|
|
@@ -74,7 +74,7 @@
|
|
|
74
74
|
</span>
|
|
75
75
|
|
|
76
76
|
{#if channel.unreadCount && channel.unreadCount > 0}
|
|
77
|
-
<span class="badge badge-error badge-xs text-
|
|
77
|
+
<span class="badge badge-error badge-xs text-error-content">
|
|
78
78
|
{channel.unreadCount > 99 ? '99+' : channel.unreadCount}
|
|
79
79
|
</span>
|
|
80
80
|
{/if}
|
|
@@ -100,7 +100,7 @@
|
|
|
100
100
|
<dialog class="modal modal-open">
|
|
101
101
|
<div class="modal-box max-w-md">
|
|
102
102
|
<div class="flex items-center justify-between mb-4">
|
|
103
|
-
<h3 class="font-
|
|
103
|
+
<h3 class="font-semibold">Members of #{channelName}</h3>
|
|
104
104
|
<button class="btn btn-ghost btn-sm btn-circle" onclick={onclose}>✕</button>
|
|
105
105
|
</div>
|
|
106
106
|
|
|
@@ -109,7 +109,7 @@
|
|
|
109
109
|
<dialog class="modal modal-open">
|
|
110
110
|
<div class="modal-box max-w-lg">
|
|
111
111
|
<div class="flex items-center justify-between mb-4">
|
|
112
|
-
<h3 class="font-
|
|
112
|
+
<h3 class="font-semibold">Create Channel</h3>
|
|
113
113
|
<button class="btn btn-ghost btn-sm btn-circle" onclick={handleClose}>✕</button>
|
|
114
114
|
</div>
|
|
115
115
|
|
|
@@ -115,7 +115,7 @@
|
|
|
115
115
|
|
|
116
116
|
<div class="flex items-center gap-1">
|
|
117
117
|
{#if user.unreadCount > 0}
|
|
118
|
-
<span class="badge badge-error badge-xs text-
|
|
118
|
+
<span class="badge badge-error badge-xs text-error-content">
|
|
119
119
|
{user.unreadCount > 99 ? '99+' : user.unreadCount}
|
|
120
120
|
</span>
|
|
121
121
|
{/if}
|
|
@@ -46,6 +46,7 @@
|
|
|
46
46
|
|
|
47
47
|
let searchQuery = $state('')
|
|
48
48
|
let activeCategory: EmojiCategory = $state('smileys_people')
|
|
49
|
+
// svelte-ignore state_referenced_locally
|
|
49
50
|
let selectedSkinTone: SkinTone = $state(preferences?.skinTone || 'default')
|
|
50
51
|
let hoveredEmoji = $state<{ emoji: string; name: string; shortcode: string } | null>(null)
|
|
51
52
|
let loading = $state(false)
|
|
@@ -164,7 +165,7 @@
|
|
|
164
165
|
{#if visible}
|
|
165
166
|
<div
|
|
166
167
|
bind:this={selectorElement}
|
|
167
|
-
class="fixed z-50 bg-base-
|
|
168
|
+
class="fixed z-50 bg-base-200 border border-base-300 rounded-box w-88 flex flex-col"
|
|
168
169
|
style="left: {position.x}px; top: {position.y}px; max-height: 420px;"
|
|
169
170
|
>
|
|
170
171
|
<!-- Search -->
|
|
@@ -144,7 +144,7 @@
|
|
|
144
144
|
{#if visible}
|
|
145
145
|
<div
|
|
146
146
|
bind:this={dropdownElement}
|
|
147
|
-
class="bg-base-
|
|
147
|
+
class="bg-base-200 border border-base-300 rounded-box overflow-y-auto overflow-x-hidden flex flex-col"
|
|
148
148
|
style={dropdownStyle}
|
|
149
149
|
>
|
|
150
150
|
{#if loading}
|
|
@@ -196,7 +196,7 @@
|
|
|
196
196
|
{/if}
|
|
197
197
|
|
|
198
198
|
{#if !compact}
|
|
199
|
-
<button type="button" class="absolute top-1 right-1 btn btn-circle btn-xs bg-base-100/80
|
|
199
|
+
<button type="button" class="absolute top-1 right-1 btn btn-circle btn-xs bg-base-100/80 opacity-0 group-hover:opacity-100 transition-opacity" onclick={(e) => { e.stopPropagation(); handleContextMenu(e) }} title="More options">
|
|
200
200
|
⋮
|
|
201
201
|
</button>
|
|
202
202
|
{/if}
|
|
@@ -206,7 +206,7 @@
|
|
|
206
206
|
{#if showContextMenu}
|
|
207
207
|
<!-- svelte-ignore a11y_click_events_have_key_events -->
|
|
208
208
|
<!-- svelte-ignore a11y_no_static_element_interactions -->
|
|
209
|
-
<div class="fixed z-50 bg-base-
|
|
209
|
+
<div class="fixed z-50 bg-base-200 rounded-lg border border-base-300 py-1 min-w-[160px]" style="left: {contextMenuPosition.x}px; top: {contextMenuPosition.y}px;" onclick={(e) => e.stopPropagation()} role="menu" tabindex="-1">
|
|
210
210
|
<button class="w-full flex items-center gap-2 px-3 py-2 text-sm hover:bg-base-200 transition-colors" onclick={handleOpenInNewTab} role="menuitem">↗ Open in new tab</button>
|
|
211
211
|
<button class="w-full flex items-center gap-2 px-3 py-2 text-sm hover:bg-base-200 transition-colors" onclick={handleDownload} role="menuitem">⬇ Download</button>
|
|
212
212
|
<button class="w-full flex items-center gap-2 px-3 py-2 text-sm hover:bg-base-200 transition-colors" onclick={handleCopyLink} role="menuitem">📋 Copy link</button>
|
|
@@ -248,7 +248,7 @@
|
|
|
248
248
|
{#if showDetailsModal}
|
|
249
249
|
<dialog class="modal modal-open">
|
|
250
250
|
<div class="modal-box max-w-md">
|
|
251
|
-
<h3 class="font-
|
|
251
|
+
<h3 class="font-semibold mb-4">File Details</h3>
|
|
252
252
|
<div class="space-y-3 text-sm">
|
|
253
253
|
<div class="flex justify-between"><span class="text-base-content/60">Name</span><span class="font-medium truncate max-w-[200px]" title={attachment.file_name}>{attachment.file_name}</span></div>
|
|
254
254
|
<div class="flex justify-between"><span class="text-base-content/60">Type</span><span class="font-medium">{attachment.file_type}</span></div>
|
|
@@ -188,11 +188,11 @@
|
|
|
188
188
|
{#if isDragging}
|
|
189
189
|
<div
|
|
190
190
|
bind:this={dropZoneRef}
|
|
191
|
-
class="fixed inset-0 z-50 bg-primary/10
|
|
191
|
+
class="fixed inset-0 z-50 bg-primary/10 flex items-center justify-center"
|
|
192
192
|
ondragenter={handleDragEnter} ondragleave={handleDragLeave} ondragover={handleDragOver} ondrop={handleDrop}
|
|
193
193
|
role="region" aria-label="Drop zone for file uploads"
|
|
194
194
|
>
|
|
195
|
-
<div class="bg-base-100 rounded-
|
|
195
|
+
<div class="bg-base-100 rounded-md border-2 border-dashed border-primary p-8 text-center">
|
|
196
196
|
<div class="text-4xl mb-4">📎</div>
|
|
197
197
|
<p class="text-lg font-medium text-primary">Drop files to upload</p>
|
|
198
198
|
<p class="text-sm text-base-content/60 mt-2">Images, videos, and documents up to {maxSize}MB</p>
|
|
@@ -213,7 +213,7 @@
|
|
|
213
213
|
</div>
|
|
214
214
|
|
|
215
215
|
{#if att.uploading}
|
|
216
|
-
<div class="absolute inset-0 bg-
|
|
216
|
+
<div class="absolute inset-0 bg-[color-mix(in_oklch,#180042_55%,transparent)] flex items-center justify-center">
|
|
217
217
|
<span class="loading loading-spinner loading-sm text-white"></span>
|
|
218
218
|
</div>
|
|
219
219
|
{/if}
|
|
@@ -362,7 +362,7 @@
|
|
|
362
362
|
{/if}
|
|
363
363
|
<button type="button" class="absolute top-0.5 right-0.5 btn btn-circle btn-xs bg-base-100/80 hover:bg-error hover:text-error-content opacity-0 group-hover:opacity-100 transition-opacity" onclick={() => removeAttachment(att.id)} title="Remove">✕</button>
|
|
364
364
|
{#if att.uploading}
|
|
365
|
-
<div class="absolute inset-0 bg-
|
|
365
|
+
<div class="absolute inset-0 bg-[color-mix(in_oklch,#180042_55%,transparent)] flex items-center justify-center"><span class="loading loading-spinner loading-xs text-white"></span></div>
|
|
366
366
|
{/if}
|
|
367
367
|
</div>
|
|
368
368
|
{/each}
|
|
@@ -183,7 +183,7 @@
|
|
|
183
183
|
</script>
|
|
184
184
|
|
|
185
185
|
<div
|
|
186
|
-
class="group flex gap-3 px-2 py-1 rounded relative transition-colors w-full text-left cursor-pointer {message.isPinned ? 'bg-warning/5 hover:bg-warning/10
|
|
186
|
+
class="group flex gap-3 px-2 py-1 rounded relative transition-colors w-full text-left cursor-pointer {message.isPinned ? 'bg-warning/5 hover:bg-warning/10' : 'hover:bg-base-200'}"
|
|
187
187
|
class:pt-2={!isConsecutive}
|
|
188
188
|
data-message-id={message.id}
|
|
189
189
|
onclick={handleMessageClick}
|
|
@@ -205,7 +205,7 @@
|
|
|
205
205
|
<!-- Content -->
|
|
206
206
|
<div class="flex-1 min-w-0 relative">
|
|
207
207
|
<!-- Floating action buttons -->
|
|
208
|
-
<div class="absolute right-0 {!isConsecutive ? 'top-0' : 'top-1'} opacity-0 group-hover:opacity-100 transition-opacity bg-base-
|
|
208
|
+
<div class="absolute right-0 {!isConsecutive ? 'top-0' : 'top-1'} opacity-0 group-hover:opacity-100 transition-opacity bg-base-200 rounded-lg border border-base-300 p-1 z-10">
|
|
209
209
|
<div class="flex items-center">
|
|
210
210
|
{#if reactionCallbacks && currentUserId}
|
|
211
211
|
<div class="flex items-center gap-1 mr-1">
|
|
@@ -289,8 +289,11 @@
|
|
|
289
289
|
|
|
290
290
|
<!-- Thread indicator -->
|
|
291
291
|
{#if message.thread_reply_count && message.thread_reply_count > 0}
|
|
292
|
-
<button type="button" class="mt-2 p-2 bg-base-200/50 rounded-lg
|
|
292
|
+
<button type="button" class="mt-2 p-2 bg-base-200/50 rounded-lg hover:bg-base-300/50 transition-colors w-full text-left" onclick={(e) => { e.stopPropagation(); handleReply() }}>
|
|
293
293
|
<div class="flex items-center gap-2 text-xs">
|
|
294
|
+
<svg class="w-3.5 h-3.5 text-primary/50 flex-shrink-0" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">
|
|
295
|
+
<path d="M21 15a2 2 0 0 1-2 2H7l-4 4V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2z"></path>
|
|
296
|
+
</svg>
|
|
294
297
|
{#if message.thread_last_sender}
|
|
295
298
|
<Avatar name={message.thread_last_sender.full_name} avatarUrl={message.thread_last_sender.avatar_url} size="xs" />
|
|
296
299
|
{/if}
|
|
@@ -121,7 +121,7 @@
|
|
|
121
121
|
<dialog class="modal modal-open">
|
|
122
122
|
<div class="modal-box max-w-md">
|
|
123
123
|
<div class="flex items-center justify-between mb-4">
|
|
124
|
-
<h3 class="font-
|
|
124
|
+
<h3 class="font-semibold">
|
|
125
125
|
{type === 'global' ? 'Notification Settings' : `Notifications: #${channelName}`}
|
|
126
126
|
</h3>
|
|
127
127
|
<button class="btn btn-ghost btn-sm btn-circle" onclick={onclose}>✕</button>
|
|
@@ -54,7 +54,7 @@
|
|
|
54
54
|
|
|
55
55
|
<div class="quoted-message-display {mode} {className}">
|
|
56
56
|
<div
|
|
57
|
-
class="flex items-start gap-2 p-3 bg-base-200/50 rounded-lg
|
|
57
|
+
class="flex items-start gap-2 p-3 bg-base-200/50 rounded-lg relative"
|
|
58
58
|
>
|
|
59
59
|
{#if mode === 'preview' && onremove}
|
|
60
60
|
<button
|
|
@@ -66,6 +66,10 @@
|
|
|
66
66
|
</button>
|
|
67
67
|
{/if}
|
|
68
68
|
|
|
69
|
+
<svg class="w-3.5 h-3.5 text-primary/50 flex-shrink-0 mt-0.5" viewBox="0 0 24 24" fill="currentColor" aria-hidden="true">
|
|
70
|
+
<path d="M14.017 21v-7.391c0-5.704 3.731-9.57 8.983-10.609l.995 2.151c-2.432.917-3.995 3.293-3.995 5.849h4v10h-9.983zm-14.017 0v-7.391c0-5.704 3.748-9.57 9-10.609l.996 2.151c-2.433.917-3.996 3.293-3.996 5.849h3.983v10h-9.983z"/>
|
|
71
|
+
</svg>
|
|
72
|
+
|
|
69
73
|
<div class="flex-shrink-0">
|
|
70
74
|
<Avatar
|
|
71
75
|
name={quotedMessage.senderName}
|
|
@@ -95,6 +99,7 @@
|
|
|
95
99
|
.line-clamp-3 {
|
|
96
100
|
display: -webkit-box;
|
|
97
101
|
-webkit-line-clamp: 3;
|
|
102
|
+
line-clamp: 3;
|
|
98
103
|
-webkit-box-orient: vertical;
|
|
99
104
|
overflow: hidden;
|
|
100
105
|
}
|
|
@@ -50,7 +50,7 @@
|
|
|
50
50
|
<dialog class="modal modal-open">
|
|
51
51
|
<div class="modal-box max-w-md">
|
|
52
52
|
<div class="flex items-center justify-between mb-4">
|
|
53
|
-
<h3 class="font-
|
|
53
|
+
<h3 class="font-semibold">New Message</h3>
|
|
54
54
|
<button class="btn btn-ghost btn-sm btn-circle" onclick={handleClose}>✕</button>
|
|
55
55
|
</div>
|
|
56
56
|
|
|
@@ -196,8 +196,8 @@
|
|
|
196
196
|
const sortedTableItems = $derived.by(() => {
|
|
197
197
|
if (!sortKey) return allItems;
|
|
198
198
|
return [...allItems].sort((a, b) => {
|
|
199
|
-
const av = (a as Record<string, unknown>)[sortKey] ?? '';
|
|
200
|
-
const bv = (b as Record<string, unknown>)[sortKey] ?? '';
|
|
199
|
+
const av = (a as unknown as Record<string, unknown>)[sortKey] ?? '';
|
|
200
|
+
const bv = (b as unknown as Record<string, unknown>)[sortKey] ?? '';
|
|
201
201
|
const cmp =
|
|
202
202
|
typeof av === 'number' && typeof bv === 'number'
|
|
203
203
|
? av - bv
|
|
@@ -236,7 +236,7 @@
|
|
|
236
236
|
day: 'numeric'
|
|
237
237
|
});
|
|
238
238
|
}
|
|
239
|
-
return String((item as Record<string, unknown>)[key] ?? '');
|
|
239
|
+
return String((item as unknown as Record<string, unknown>)[key] ?? '');
|
|
240
240
|
}
|
|
241
241
|
</script>
|
|
242
242
|
|
|
@@ -245,7 +245,7 @@
|
|
|
245
245
|
{#if metrics && metrics.length > 0}
|
|
246
246
|
<div class="grid grid-cols-2 md:grid-cols-4 gap-3">
|
|
247
247
|
{#each metrics as m}
|
|
248
|
-
<div class="stat bg-base-100 rounded-
|
|
248
|
+
<div class="stat bg-base-100 rounded-md border border-base-300 p-3">
|
|
249
249
|
<div class="stat-title text-xs">{m.label}</div>
|
|
250
250
|
<div class="stat-value text-lg" style:color={m.color}>
|
|
251
251
|
{formatMetricValue(m)}
|
|
@@ -37,7 +37,7 @@
|
|
|
37
37
|
|
|
38
38
|
<button
|
|
39
39
|
type="button"
|
|
40
|
-
class="card card-compact bg-base-
|
|
40
|
+
class="card card-compact bg-base-200 border cursor-pointer hover:bg-base-300/80 transition-colors w-full text-left {isDragging ? 'opacity-50 rotate-2' : ''} {className}"
|
|
41
41
|
style:border-color={colors.border}
|
|
42
42
|
onclick={() => onclick?.(item)}
|
|
43
43
|
>
|
|
@@ -63,7 +63,7 @@
|
|
|
63
63
|
</script>
|
|
64
64
|
|
|
65
65
|
<div
|
|
66
|
-
class="flex flex-col rounded-
|
|
66
|
+
class="flex flex-col rounded-md border {className}"
|
|
67
67
|
style:border-color={colors.border}
|
|
68
68
|
style:background-color={colors.bg}
|
|
69
69
|
>
|
|
@@ -148,8 +148,13 @@
|
|
|
148
148
|
{#if emptyState}
|
|
149
149
|
{@render emptyState({ stage })}
|
|
150
150
|
{:else}
|
|
151
|
-
<div class="flex-1 flex items-center justify-center
|
|
152
|
-
|
|
151
|
+
<div class="flex-1 flex items-center justify-center p-4 my-1">
|
|
152
|
+
<div class="w-full rounded-lg border border-dashed border-base-content/15 py-6 flex flex-col items-center gap-1.5">
|
|
153
|
+
<svg class="w-5 h-5 text-base-content/20" fill="none" stroke="currentColor" viewBox="0 0 24 24" aria-hidden="true">
|
|
154
|
+
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5" d="M12 6v6m0 0v6m0-6h6m-6 0H6" />
|
|
155
|
+
</svg>
|
|
156
|
+
<span class="text-xs text-base-content/25">Empty</span>
|
|
157
|
+
</div>
|
|
153
158
|
</div>
|
|
154
159
|
{/if}
|
|
155
160
|
{/if}
|