Private
Public Access
1
0

Add 17 features: My Work view, card copy/numbering, calendar, bulk ops, custom fields, automations, webhooks, invites, import/export, and more

Features implemented across 6 batches:
- Batch A: Fix ColorPicker gradient sync, tenant custom logo, unarchive cards, board backgrounds
- Batch B: My Work view, card copy/duplicate, card numbering (BOARD-123)
- Batch C: Due date reminders (cron), card watching with notifications
- Batch D: Bulk card operations, column sorting, calendar view
- Batch E: Invite by link, board import/export (Pounce + Trello JSON)
- Batch F: Webhooks with HMAC signing, custom fields, automation rules engine

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Catherine Renelle
2026-02-27 08:50:15 -05:00
parent 3a11ed01e9
commit 8c41b33313
54 changed files with 3656 additions and 82 deletions
+100
View File
@@ -0,0 +1,100 @@
<script lang="ts">
import { api } from '$lib/utils/api.js';
import { toast } from '$lib/stores/toasts.svelte.js';
type Props = {
boardId: string;
selectedCardIds: Set<string>;
columns: any[];
onaction: () => void;
oncancel: () => void;
};
let { boardId, selectedCardIds, columns, onaction, oncancel }: Props = $props();
let processing = $state(false);
let confirmDelete = $state(false);
async function bulkAction(action: string, extra: Record<string, any> = {}) {
processing = true;
const { ok, data } = await api(`/api/boards/${boardId}/cards/bulk`, {
method: 'POST',
body: JSON.stringify({
action,
cardIds: Array.from(selectedCardIds),
...extra
})
});
if (ok) {
toast.success(`${data.count} card(s) updated`);
onaction();
}
processing = false;
confirmDelete = false;
}
</script>
<div class="fixed bottom-4 left-1/2 -translate-x-1/2 z-50 bg-white dark:bg-gray-800 rounded-xl shadow-2xl border border-gray-200 dark:border-gray-700 px-4 py-3 flex items-center gap-3 flex-wrap">
<span class="text-sm font-medium text-gray-700 dark:text-gray-300">
{selectedCardIds.size} selected
</span>
<div class="h-5 w-px bg-gray-200 dark:bg-gray-700"></div>
<!-- Move to column -->
<select
onchange={(e) => {
const val = e.currentTarget.value;
if (val) bulkAction('move', { columnId: val });
e.currentTarget.value = '';
}}
disabled={processing}
class="text-sm rounded border border-gray-300 dark:border-gray-600 dark:bg-gray-700 dark:text-gray-200 px-2 py-1"
>
<option value="">Move to...</option>
{#each columns as col}
<option value={col.id}>{col.title}</option>
{/each}
</select>
<button
onclick={() => bulkAction('archive')}
disabled={processing}
class="text-sm rounded bg-gray-100 dark:bg-gray-700 px-3 py-1.5 text-gray-700 dark:text-gray-300 hover:bg-gray-200 dark:hover:bg-gray-600 transition disabled:opacity-50"
>
Archive
</button>
{#if confirmDelete}
<button
onclick={() => bulkAction('delete')}
disabled={processing}
class="text-sm rounded bg-[var(--color-danger)] px-3 py-1.5 text-white hover:opacity-90 transition disabled:opacity-50"
>
Confirm Delete
</button>
<button
onclick={() => (confirmDelete = false)}
class="text-sm text-gray-500 dark:text-gray-400 hover:text-gray-700 dark:hover:text-gray-200"
>
Cancel
</button>
{:else}
<button
onclick={() => (confirmDelete = true)}
disabled={processing}
class="text-sm rounded px-3 py-1.5 text-[var(--color-danger)] hover:bg-red-50 dark:hover:bg-red-900/30 transition disabled:opacity-50"
>
Delete
</button>
{/if}
<div class="h-5 w-px bg-gray-200 dark:bg-gray-700"></div>
<button
onclick={oncancel}
class="text-sm text-gray-500 dark:text-gray-400 hover:text-gray-700 dark:hover:text-gray-200 transition"
>
Done
</button>
</div>