style(project): format Go with gofmt, set up Prettier for the frontend
Run gofmt -w across the backend, normalising the manually-aligned := blocks to the gofmt standard. No code behaviour changes. Add Prettier (+ prettier-plugin-svelte) to the frontend with the SvelteKit default config (tabs, single quotes) so formatting is reproducible, then run it over the whole tree. Add format / format:check npm scripts and a .prettierignore (build output, generated schema.ts, static assets). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -33,8 +33,8 @@
|
||||
api.get<TagOffsetPage>('/tags?limit=200&sort=name&order=asc'),
|
||||
api.post<{ common_tag_ids: string[]; partial_tag_ids: string[] }>(
|
||||
'/files/bulk/common-tags',
|
||||
{ file_ids: fileIds },
|
||||
),
|
||||
{ file_ids: fileIds }
|
||||
)
|
||||
]);
|
||||
allTags = tagsRes.items ?? [];
|
||||
commonIds = new Set(commonRes.common_tag_ids ?? []);
|
||||
@@ -53,16 +53,16 @@
|
||||
allTags.filter(
|
||||
(t) =>
|
||||
assignedIds.has(t.id ?? '') &&
|
||||
(!search.trim() || t.name?.toLowerCase().includes(search.toLowerCase())),
|
||||
),
|
||||
(!search.trim() || t.name?.toLowerCase().includes(search.toLowerCase()))
|
||||
)
|
||||
);
|
||||
|
||||
let availableTags = $derived(
|
||||
allTags.filter(
|
||||
(t) =>
|
||||
!assignedIds.has(t.id ?? '') &&
|
||||
(!search.trim() || t.name?.toLowerCase().includes(search.toLowerCase())),
|
||||
),
|
||||
(!search.trim() || t.name?.toLowerCase().includes(search.toLowerCase()))
|
||||
)
|
||||
);
|
||||
|
||||
function tagStyle(tag: Tag) {
|
||||
@@ -132,8 +132,10 @@
|
||||
class="tag assigned"
|
||||
class:partial={isPartial}
|
||||
style={tagStyle(tag)}
|
||||
onclick={() => isPartial ? promotePartial(tag.id!) : remove(tag.id!)}
|
||||
title={isPartial ? 'Partial — click to add to all files' : 'Click to remove from all files'}
|
||||
onclick={() => (isPartial ? promotePartial(tag.id!) : remove(tag.id!))}
|
||||
title={isPartial
|
||||
? 'Partial — click to add to all files'
|
||||
: 'Click to remove from all files'}
|
||||
>
|
||||
{tag.name}
|
||||
{#if isPartial}
|
||||
@@ -159,7 +161,12 @@
|
||||
{#if search}
|
||||
<button class="search-clear" onclick={() => (search = '')} aria-label="Clear search">
|
||||
<svg width="14" height="14" viewBox="0 0 14 14" fill="none" aria-hidden="true">
|
||||
<path d="M2 2l10 10M12 2L2 12" stroke="currentColor" stroke-width="1.8" stroke-linecap="round"/>
|
||||
<path
|
||||
d="M2 2l10 10M12 2L2 12"
|
||||
stroke="currentColor"
|
||||
stroke-width="1.8"
|
||||
stroke-linecap="round"
|
||||
/>
|
||||
</svg>
|
||||
</button>
|
||||
{/if}
|
||||
@@ -347,4 +354,4 @@
|
||||
color: var(--color-text-muted);
|
||||
margin: 0;
|
||||
}
|
||||
</style>
|
||||
</style>
|
||||
|
||||
@@ -22,7 +22,7 @@
|
||||
selected = false,
|
||||
selectionMode = false,
|
||||
onTap,
|
||||
onLongPress,
|
||||
onLongPress
|
||||
}: Props = $props();
|
||||
|
||||
let imgSrc = $state<string | null>(null);
|
||||
@@ -34,7 +34,7 @@
|
||||
let cancelled = false;
|
||||
|
||||
fetch(`/api/v1/files/${file.id}/thumbnail`, {
|
||||
headers: token ? { Authorization: `Bearer ${token}` } : {},
|
||||
headers: token ? { Authorization: `Bearer ${token}` } : {}
|
||||
})
|
||||
.then((res) => (res.ok ? res.blob() : null))
|
||||
.then((blob) => {
|
||||
@@ -111,7 +111,10 @@
|
||||
data-file-index={index}
|
||||
onpointerdown={onPointerDown}
|
||||
onpointermove={onPointerMoveInternal}
|
||||
onpointerup={() => { cancelPress(); didLongPress = false; }}
|
||||
onpointerup={() => {
|
||||
cancelPress();
|
||||
didLongPress = false;
|
||||
}}
|
||||
onpointerleave={cancelPress}
|
||||
oncontextmenu={(e) => e.preventDefault()}
|
||||
onclick={onClick}
|
||||
@@ -128,14 +131,27 @@
|
||||
{#if selected}
|
||||
<div class="check" aria-hidden="true">
|
||||
<svg width="18" height="18" viewBox="0 0 18 18" fill="none">
|
||||
<circle cx="9" cy="9" r="8.5" fill="rgba(0,0,0,0.55)" stroke="white" stroke-width="1"/>
|
||||
<path d="M5 9l3 3 5-5" stroke="white" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<circle cx="9" cy="9" r="8.5" fill="rgba(0,0,0,0.55)" stroke="white" stroke-width="1" />
|
||||
<path
|
||||
d="M5 9l3 3 5-5"
|
||||
stroke="white"
|
||||
stroke-width="1.8"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
/>
|
||||
</svg>
|
||||
</div>
|
||||
{:else if selectionMode}
|
||||
<div class="check" aria-hidden="true">
|
||||
<svg width="18" height="18" viewBox="0 0 18 18" fill="none">
|
||||
<circle cx="9" cy="9" r="8.5" fill="rgba(0,0,0,0.35)" stroke="rgba(255,255,255,0.5)" stroke-width="1"/>
|
||||
<circle
|
||||
cx="9"
|
||||
cy="9"
|
||||
r="8.5"
|
||||
fill="rgba(0,0,0,0.35)"
|
||||
stroke="rgba(255,255,255,0.5)"
|
||||
stroke-width="1"
|
||||
/>
|
||||
</svg>
|
||||
</div>
|
||||
{/if}
|
||||
@@ -207,7 +223,11 @@
|
||||
}
|
||||
|
||||
@keyframes shimmer {
|
||||
0% { background-position: 200% 0; }
|
||||
100% { background-position: -200% 0; }
|
||||
0% {
|
||||
background-position: 200% 0;
|
||||
}
|
||||
100% {
|
||||
background-position: -200% 0;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
</style>
|
||||
|
||||
@@ -50,13 +50,11 @@
|
||||
id: uid(),
|
||||
name: f.name,
|
||||
progress: 0,
|
||||
status: 'uploading',
|
||||
status: 'uploading'
|
||||
}));
|
||||
queue = [...queue, ...items];
|
||||
|
||||
await Promise.all(
|
||||
files.map((file, i) => uploadOne(file, items[i].id)),
|
||||
);
|
||||
await Promise.all(files.map((file, i) => uploadOne(file, items[i].id)));
|
||||
}
|
||||
|
||||
async function uploadOne(file: globalThis.File, itemId: string) {
|
||||
@@ -64,10 +62,8 @@
|
||||
fd.append('file', file);
|
||||
|
||||
try {
|
||||
const result = await uploadWithProgress<ApiFile>(
|
||||
'/files',
|
||||
fd,
|
||||
(pct) => updateItem(itemId, { progress: pct }),
|
||||
const result = await uploadWithProgress<ApiFile>('/files', fd, (pct) =>
|
||||
updateItem(itemId, { progress: pct })
|
||||
);
|
||||
updateItem(itemId, { status: 'done', progress: 100 });
|
||||
onUploaded(result);
|
||||
@@ -144,8 +140,14 @@
|
||||
<div class="drop-overlay" aria-hidden="true">
|
||||
<div class="drop-label">
|
||||
<svg width="36" height="36" viewBox="0 0 36 36" fill="none" aria-hidden="true">
|
||||
<path d="M18 4v20M10 14l8-10 8 10" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M6 28h24" stroke="currentColor" stroke-width="2.5" stroke-linecap="round"/>
|
||||
<path
|
||||
d="M18 4v20M10 14l8-10 8 10"
|
||||
stroke="currentColor"
|
||||
stroke-width="2.5"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
/>
|
||||
<path d="M6 28h24" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" />
|
||||
</svg>
|
||||
Drop files to upload
|
||||
</div>
|
||||
@@ -171,7 +173,11 @@
|
||||
|
||||
<ul class="upload-list">
|
||||
{#each queue as item (item.id)}
|
||||
<li class="upload-item" class:done={item.status === 'done'} class:error={item.status === 'error'}>
|
||||
<li
|
||||
class="upload-item"
|
||||
class:done={item.status === 'done'}
|
||||
class:error={item.status === 'error'}
|
||||
>
|
||||
<span class="item-name" title={item.name}>{item.name}</span>
|
||||
<div class="item-right">
|
||||
{#if item.status === 'uploading'}
|
||||
@@ -180,8 +186,21 @@
|
||||
</div>
|
||||
<span class="pct">{item.progress}%</span>
|
||||
{:else if item.status === 'done'}
|
||||
<svg class="icon-ok" width="16" height="16" viewBox="0 0 16 16" fill="none" aria-label="Done">
|
||||
<path d="M3 8l4 4 6-6" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<svg
|
||||
class="icon-ok"
|
||||
width="16"
|
||||
height="16"
|
||||
viewBox="0 0 16 16"
|
||||
fill="none"
|
||||
aria-label="Done"
|
||||
>
|
||||
<path
|
||||
d="M3 8l4 4 6-6"
|
||||
stroke="currentColor"
|
||||
stroke-width="2"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
/>
|
||||
</svg>
|
||||
{:else}
|
||||
<span class="err-msg" title={item.error}>{item.error}</span>
|
||||
@@ -243,8 +262,14 @@
|
||||
}
|
||||
|
||||
@keyframes slide-up {
|
||||
from { transform: translateY(10px); opacity: 0; }
|
||||
to { transform: translateY(0); opacity: 1; }
|
||||
from {
|
||||
transform: translateY(10px);
|
||||
opacity: 0;
|
||||
}
|
||||
to {
|
||||
transform: translateY(0);
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
||||
.panel-header {
|
||||
@@ -348,4 +373,4 @@
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
</style>
|
||||
</style>
|
||||
|
||||
@@ -42,7 +42,7 @@
|
||||
let dirty = $state(false);
|
||||
|
||||
let exifEntries = $derived(
|
||||
file?.exif ? Object.entries(file.exif as Record<string, unknown>) : [],
|
||||
file?.exif ? Object.entries(file.exif as Record<string, unknown>) : []
|
||||
);
|
||||
|
||||
// ---- Load (re-runs whenever the file changes, i.e. paging) ----
|
||||
@@ -88,7 +88,7 @@
|
||||
const token = get(authStore).accessToken;
|
||||
try {
|
||||
const res = await fetch(`/api/v1/files/${id}/preview`, {
|
||||
headers: token ? { Authorization: `Bearer ${token}` } : {},
|
||||
headers: token ? { Authorization: `Bearer ${token}` } : {}
|
||||
});
|
||||
if (res.ok && fileId === id) {
|
||||
previewSrc = URL.createObjectURL(await res.blob());
|
||||
@@ -129,13 +129,13 @@
|
||||
(entries) => {
|
||||
tagsVisible = entries[0]?.isIntersecting ?? false;
|
||||
},
|
||||
{ rootMargin: '200px' },
|
||||
{ rootMargin: '200px' }
|
||||
);
|
||||
observer.observe(node);
|
||||
return {
|
||||
destroy() {
|
||||
observer.disconnect();
|
||||
},
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@@ -158,10 +158,8 @@
|
||||
try {
|
||||
const updated = await api.patch<File>(`/files/${file.id}`, {
|
||||
notes: notes.trim() || null,
|
||||
content_datetime: contentDatetime
|
||||
? new Date(contentDatetime).toISOString()
|
||||
: undefined,
|
||||
is_public: isPublic,
|
||||
content_datetime: contentDatetime ? new Date(contentDatetime).toISOString() : undefined,
|
||||
is_public: isPublic
|
||||
});
|
||||
file = updated;
|
||||
dirty = false;
|
||||
@@ -206,7 +204,13 @@
|
||||
<div class="top-bar">
|
||||
<button class="back-btn" onclick={onClose} aria-label="Back to files">
|
||||
<svg width="20" height="20" viewBox="0 0 20 20" fill="none" aria-hidden="true">
|
||||
<path d="M12 4L6 10L12 16" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path
|
||||
d="M12 4L6 10L12 16"
|
||||
stroke="currentColor"
|
||||
stroke-width="2"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
/>
|
||||
</svg>
|
||||
</button>
|
||||
<span class="filename">{file?.original_name ?? ''}</span>
|
||||
@@ -230,7 +234,13 @@
|
||||
aria-label="Previous file"
|
||||
>
|
||||
<svg width="18" height="18" viewBox="0 0 18 18" fill="none" aria-hidden="true">
|
||||
<path d="M11 3L5 9L11 15" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path
|
||||
d="M11 3L5 9L11 15"
|
||||
stroke="currentColor"
|
||||
stroke-width="2"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
/>
|
||||
</svg>
|
||||
</button>
|
||||
{/if}
|
||||
@@ -241,7 +251,13 @@
|
||||
aria-label="Next file"
|
||||
>
|
||||
<svg width="18" height="18" viewBox="0 0 18 18" fill="none" aria-hidden="true">
|
||||
<path d="M7 3L13 9L7 15" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path
|
||||
d="M7 3L13 9L7 15"
|
||||
stroke="currentColor"
|
||||
stroke-width="2"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
/>
|
||||
</svg>
|
||||
</button>
|
||||
{/if}
|
||||
@@ -290,7 +306,10 @@
|
||||
<button
|
||||
class="toggle"
|
||||
class:on={isPublic}
|
||||
onclick={() => { isPublic = !isPublic; dirty = true; }}
|
||||
onclick={() => {
|
||||
isPublic = !isPublic;
|
||||
dirty = true;
|
||||
}}
|
||||
role="switch"
|
||||
aria-checked={isPublic}
|
||||
aria-label="Public"
|
||||
@@ -299,11 +318,7 @@
|
||||
</button>
|
||||
</section>
|
||||
|
||||
<button
|
||||
class="save-btn"
|
||||
onclick={save}
|
||||
disabled={!dirty || saving}
|
||||
>
|
||||
<button class="save-btn" onclick={save} disabled={!dirty || saving}>
|
||||
{saving ? 'Saving…' : 'Save changes'}
|
||||
</button>
|
||||
|
||||
@@ -409,12 +424,7 @@
|
||||
}
|
||||
|
||||
.preview-placeholder.shimmer {
|
||||
background: linear-gradient(
|
||||
90deg,
|
||||
#111 25%,
|
||||
#222 50%,
|
||||
#111 75%
|
||||
);
|
||||
background: linear-gradient(90deg, #111 25%, #222 50%, #111 75%);
|
||||
background-size: 200% 100%;
|
||||
animation: shimmer 1.4s infinite;
|
||||
}
|
||||
@@ -445,8 +455,12 @@
|
||||
background-color: rgba(0, 0, 0, 0.8);
|
||||
}
|
||||
|
||||
.nav-prev { left: 10px; }
|
||||
.nav-next { right: 10px; }
|
||||
.nav-prev {
|
||||
left: 10px;
|
||||
}
|
||||
.nav-next {
|
||||
right: 10px;
|
||||
}
|
||||
|
||||
/* ---- Metadata panel ---- */
|
||||
.meta-panel {
|
||||
@@ -465,7 +479,9 @@
|
||||
padding-bottom: 10px;
|
||||
}
|
||||
|
||||
.sep { opacity: 0.4; }
|
||||
.sep {
|
||||
opacity: 0.4;
|
||||
}
|
||||
|
||||
.section {
|
||||
padding: 10px 0;
|
||||
@@ -577,7 +593,9 @@
|
||||
cursor: pointer;
|
||||
margin-top: 4px;
|
||||
margin-bottom: 4px;
|
||||
transition: background-color 0.15s, opacity 0.15s;
|
||||
transition:
|
||||
background-color 0.15s,
|
||||
opacity 0.15s;
|
||||
}
|
||||
|
||||
.save-btn:hover:not(:disabled) {
|
||||
@@ -632,7 +650,11 @@
|
||||
}
|
||||
|
||||
@keyframes shimmer {
|
||||
0% { background-position: 200% 0; }
|
||||
100% { background-position: -200% 0; }
|
||||
0% {
|
||||
background-position: 200% 0;
|
||||
}
|
||||
100% {
|
||||
background-position: -200% 0;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -18,11 +18,7 @@
|
||||
let search = $state('');
|
||||
let tokens = $state<string[]>(parseDslFilter(value));
|
||||
let tagNames = $derived(
|
||||
new Map(
|
||||
tags
|
||||
.filter((t) => t.id && t.name)
|
||||
.map((t) => [t.id as string, t.name as string]),
|
||||
),
|
||||
new Map(tags.filter((t) => t.id && t.name).map((t) => [t.id as string, t.name as string]))
|
||||
);
|
||||
|
||||
$effect(() => {
|
||||
@@ -36,9 +32,7 @@
|
||||
});
|
||||
|
||||
let filteredTags = $derived(
|
||||
search.trim()
|
||||
? tags.filter((t) => t.name?.toLowerCase().includes(search.toLowerCase()))
|
||||
: tags,
|
||||
search.trim() ? tags.filter((t) => t.name?.toLowerCase().includes(search.toLowerCase())) : tags
|
||||
);
|
||||
|
||||
function addToken(t: string) {
|
||||
@@ -143,7 +137,11 @@
|
||||
{#each filteredTags as tag (tag.id)}
|
||||
<button
|
||||
class="token tag-token"
|
||||
style="background-color: {tag.color ? '#' + tag.color : tag.category_color ? '#' + tag.category_color : 'var(--color-tag-default)'}"
|
||||
style="background-color: {tag.color
|
||||
? '#' + tag.color
|
||||
: tag.category_color
|
||||
? '#' + tag.category_color
|
||||
: 'var(--color-tag-default)'}"
|
||||
onclick={() => addToken(`t=${tag.id}`)}
|
||||
>
|
||||
{tag.name}
|
||||
@@ -214,7 +212,9 @@
|
||||
font-weight: 600;
|
||||
cursor: grab;
|
||||
user-select: none;
|
||||
transition: opacity 0.15s, outline 0.1s;
|
||||
transition:
|
||||
opacity 0.15s,
|
||||
outline 0.1s;
|
||||
outline: 2px solid transparent;
|
||||
}
|
||||
|
||||
@@ -326,4 +326,4 @@
|
||||
.btn-close:hover {
|
||||
color: var(--color-text-primary);
|
||||
}
|
||||
</style>
|
||||
</style>
|
||||
|
||||
@@ -26,14 +26,14 @@
|
||||
allTags.filter(
|
||||
(t) =>
|
||||
!assignedIds.has(t.id) &&
|
||||
(!search.trim() || t.name?.toLowerCase().includes(search.toLowerCase())),
|
||||
),
|
||||
(!search.trim() || t.name?.toLowerCase().includes(search.toLowerCase()))
|
||||
)
|
||||
);
|
||||
|
||||
let filteredAssigned = $derived(
|
||||
search.trim()
|
||||
? fileTags.filter((t) => t.name?.toLowerCase().includes(search.toLowerCase()))
|
||||
: fileTags,
|
||||
: fileTags
|
||||
);
|
||||
|
||||
async function handleAdd(tagId: string) {
|
||||
@@ -93,7 +93,12 @@
|
||||
{#if search}
|
||||
<button class="search-clear" onclick={() => (search = '')} aria-label="Clear search">
|
||||
<svg width="14" height="14" viewBox="0 0 14 14" fill="none" aria-hidden="true">
|
||||
<path d="M2 2l10 10M12 2L2 12" stroke="currentColor" stroke-width="1.8" stroke-linecap="round"/>
|
||||
<path
|
||||
d="M2 2l10 10M12 2L2 12"
|
||||
stroke="currentColor"
|
||||
stroke-width="1.8"
|
||||
stroke-linecap="round"
|
||||
/>
|
||||
</svg>
|
||||
</button>
|
||||
{/if}
|
||||
@@ -239,4 +244,4 @@
|
||||
color: var(--color-text-muted);
|
||||
margin: 0;
|
||||
}
|
||||
</style>
|
||||
</style>
|
||||
|
||||
Reference in New Issue
Block a user