fix(tags): rename-onto-existing in the image modal now merges, not errors
The image-modal tag kebab's rename dialog still showed a leftover stub
('Merging two tags into one lands in FC-2c') on a name collision, dead-ending
the operator. The merge machinery has existed for a while — the Tags view
already resolves rename collisions this way. Wire TagRenameDialog to it: on the
409 collision hint, show the same merge confirmation FandomSetDialog uses
(target name, image associations moved, alias kept) and POST /api/tags/<id>/merge
into the existing tag.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -2,25 +2,55 @@
|
|||||||
<v-card>
|
<v-card>
|
||||||
<v-card-title>Rename tag</v-card-title>
|
<v-card-title>Rename tag</v-card-title>
|
||||||
<v-card-text>
|
<v-card-text>
|
||||||
<v-text-field
|
<template v-if="!collision">
|
||||||
v-model="newName" label="New name" density="compact"
|
<v-text-field
|
||||||
autofocus @keydown.enter="submit"
|
v-model="newName" label="New name" density="compact"
|
||||||
/>
|
autofocus @keydown.enter="submit"
|
||||||
<v-alert v-if="errorMsg" type="error" variant="tonal" density="compact" class="mt-2">
|
/>
|
||||||
{{ errorMsg }}
|
<v-alert
|
||||||
<div v-if="isCollision" class="text-caption mt-1">
|
v-if="errorMsg" type="error" variant="tonal" density="compact"
|
||||||
Merging two tags into one lands in FC-2c.
|
class="mt-2"
|
||||||
</div>
|
>{{ errorMsg }}</v-alert>
|
||||||
</v-alert>
|
</template>
|
||||||
|
|
||||||
|
<template v-else>
|
||||||
|
<!-- A tag of this (kind, fandom) already has that name. Renaming onto it
|
||||||
|
is a merge, not a fork — same resolution the Tags view offers. -->
|
||||||
|
<v-alert type="warning" variant="tonal" density="compact" class="mb-3">
|
||||||
|
A {{ tag.kind }} tag named “{{ collision.target.name }}” already exists.
|
||||||
|
</v-alert>
|
||||||
|
<p class="text-body-2">
|
||||||
|
Merge “{{ tag.name }}” into “{{ collision.target.name }}”?
|
||||||
|
{{ collision.source_image_count }} image
|
||||||
|
association{{ collision.source_image_count === 1 ? '' : 's' }}
|
||||||
|
will move over and this tag will be deleted{{
|
||||||
|
collision.will_alias ? ' (its name kept as a tagger alias)' : '' }}.
|
||||||
|
</p>
|
||||||
|
<v-alert
|
||||||
|
v-if="errorMsg" type="error" variant="tonal" density="compact"
|
||||||
|
class="mt-3"
|
||||||
|
>{{ errorMsg }}</v-alert>
|
||||||
|
</template>
|
||||||
</v-card-text>
|
</v-card-text>
|
||||||
<v-card-actions>
|
<v-card-actions>
|
||||||
<v-spacer />
|
<v-spacer />
|
||||||
<v-btn @click="$emit('cancel')">Cancel</v-btn>
|
<template v-if="!collision">
|
||||||
<v-btn
|
<v-btn :disabled="busy" @click="$emit('cancel')">Cancel</v-btn>
|
||||||
color="primary" rounded="pill"
|
<v-btn
|
||||||
:disabled="!newName.trim() || newName === tag.name"
|
color="primary" rounded="pill"
|
||||||
:loading="busy" @click="submit"
|
:disabled="!newName.trim() || newName === tag.name"
|
||||||
>Rename</v-btn>
|
:loading="busy" @click="submit"
|
||||||
|
>Rename</v-btn>
|
||||||
|
</template>
|
||||||
|
<template v-else>
|
||||||
|
<v-btn :disabled="busy" @click="collision = null; errorMsg = null">
|
||||||
|
Back
|
||||||
|
</v-btn>
|
||||||
|
<v-btn
|
||||||
|
color="warning" variant="flat" rounded="pill"
|
||||||
|
:loading="busy" @click="onMerge"
|
||||||
|
>Merge</v-btn>
|
||||||
|
</template>
|
||||||
</v-card-actions>
|
</v-card-actions>
|
||||||
</v-card>
|
</v-card>
|
||||||
</template>
|
</template>
|
||||||
@@ -35,22 +65,41 @@ const emit = defineEmits(['renamed', 'cancel'])
|
|||||||
const api = useApi()
|
const api = useApi()
|
||||||
const newName = ref(props.tag.name)
|
const newName = ref(props.tag.name)
|
||||||
const errorMsg = ref(null)
|
const errorMsg = ref(null)
|
||||||
const isCollision = ref(false)
|
const collision = ref(null)
|
||||||
const busy = ref(false)
|
const busy = ref(false)
|
||||||
|
|
||||||
async function submit() {
|
async function submit() {
|
||||||
if (!newName.value.trim() || newName.value === props.tag.name) return
|
if (!newName.value.trim() || newName.value === props.tag.name) return
|
||||||
busy.value = true
|
busy.value = true
|
||||||
errorMsg.value = null
|
errorMsg.value = null
|
||||||
isCollision.value = false
|
|
||||||
try {
|
try {
|
||||||
const updated = await api.patch(`/api/tags/${props.tag.id}`, {
|
const updated = await api.patch(`/api/tags/${props.tag.id}`, {
|
||||||
body: { name: newName.value.trim() }
|
body: { name: newName.value.trim() }
|
||||||
})
|
})
|
||||||
emit('renamed', updated)
|
emit('renamed', updated)
|
||||||
|
} catch (e) {
|
||||||
|
// 409 → the new name collides with an existing tag of the same
|
||||||
|
// (kind, fandom). Offer to merge into it rather than dead-ending.
|
||||||
|
if (e.status === 409 && e.body && e.body.target) {
|
||||||
|
collision.value = e.body
|
||||||
|
} else {
|
||||||
|
errorMsg.value = e.message
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
busy.value = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function onMerge() {
|
||||||
|
busy.value = true
|
||||||
|
errorMsg.value = null
|
||||||
|
try {
|
||||||
|
await api.post(`/api/tags/${props.tag.id}/merge`, {
|
||||||
|
body: { target_id: collision.value.target.id }
|
||||||
|
})
|
||||||
|
emit('renamed', { id: collision.value.target.id, name: collision.value.target.name })
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
errorMsg.value = e.message
|
errorMsg.value = e.message
|
||||||
isCollision.value = e.status === 409
|
|
||||||
} finally {
|
} finally {
|
||||||
busy.value = false
|
busy.value = false
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user