feat: offer a merge that keeps the first book, without the workbench
This commit is contained in:
@@ -1,7 +1,5 @@
|
||||
<script lang="ts">
|
||||
import { untrack } from 'svelte';
|
||||
import { toast } from 'svelte-sonner';
|
||||
import { invalidate } from '$app/navigation';
|
||||
import { ArrowRight, GitMerge, Plus, Undo2 } from '@lucide/svelte';
|
||||
|
||||
import * as Dialog from '$lib/components/ui/dialog/index.js';
|
||||
@@ -11,9 +9,10 @@
|
||||
import { Badge } from '$lib/components/ui/badge/index.js';
|
||||
import BookImage from '$lib/components/view/book-image.svelte';
|
||||
import GeneratedCover from '$lib/components/view/generated-cover.svelte';
|
||||
import { mergeBooks } from '$lib/api/book.remote';
|
||||
import type { Book } from '$lib/schema';
|
||||
|
||||
import { mergeInto } from './merge';
|
||||
|
||||
import {
|
||||
MERGE_FIELDS,
|
||||
applyAction,
|
||||
@@ -105,26 +104,13 @@
|
||||
if (busy) return;
|
||||
busy = true;
|
||||
|
||||
try {
|
||||
await mergeBooks({
|
||||
library_id: libraryId,
|
||||
survivor_id: survivor.id,
|
||||
merged_ids: candidates.map((book) => book.id),
|
||||
metadata: changed.length ? draft : undefined
|
||||
});
|
||||
const merged = await mergeInto(libraryId, survivor, folded, changed.length ? draft : undefined);
|
||||
|
||||
// Both, because this dialog is opened from the duplicates review and from
|
||||
// the library's selection toolbar, and each page depends on a different one.
|
||||
await Promise.all([invalidate('app:books'), invalidate('app:duplicate-books')]);
|
||||
busy = false;
|
||||
|
||||
toast.success(`Merged into “${survivor.title}”`);
|
||||
if (merged) {
|
||||
open = false;
|
||||
onmerged?.(survivor, folded);
|
||||
} catch (error) {
|
||||
console.error('Failed to merge books', error);
|
||||
toast.error(error instanceof Error ? error.message : 'Could not merge these books');
|
||||
} finally {
|
||||
busy = false;
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
@@ -0,0 +1,44 @@
|
||||
import { invalidate } from '$app/navigation';
|
||||
import { toast } from 'svelte-sonner';
|
||||
|
||||
import { mergeBooks } from '$lib/api/book.remote';
|
||||
import type { Book } from '$lib/schema';
|
||||
import type { FieldValue } from './field-spec';
|
||||
|
||||
/**
|
||||
* Fold `folded` into `survivor`, and tell the reader how it went.
|
||||
*
|
||||
* Shared because a merge is reachable two ways — the workbench, where the reader
|
||||
* resolved the metadata field by field, and the quick action beside it, which
|
||||
* takes the survivor's metadata as it stands. Only the `metadata` argument
|
||||
* differs, and the invalidation and the wording should not.
|
||||
*
|
||||
* @returns whether the merge went through; the caller decides what to close or
|
||||
* clear, and has no toast of its own to write either way.
|
||||
*/
|
||||
export async function mergeInto(
|
||||
libraryId: number | string,
|
||||
survivor: Book,
|
||||
folded: Book[],
|
||||
metadata?: Record<string, FieldValue>
|
||||
): Promise<boolean> {
|
||||
try {
|
||||
await mergeBooks({
|
||||
library_id: libraryId,
|
||||
survivor_id: survivor.id,
|
||||
merged_ids: folded.map((book) => book.id),
|
||||
metadata
|
||||
});
|
||||
|
||||
// Both, because a merge is started from the duplicates review and from the
|
||||
// library's selection toolbar, and each page depends on a different one.
|
||||
await Promise.all([invalidate('app:books'), invalidate('app:duplicate-books')]);
|
||||
|
||||
toast.success(`Merged into “${survivor.title}”`);
|
||||
return true;
|
||||
} catch (error) {
|
||||
console.error('Failed to merge books', error);
|
||||
toast.error(error instanceof Error ? error.message : 'Could not merge these books');
|
||||
return false;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user