Soft auto-apply (retract + confirm, no self-training) + tagging UX (reject-rest, tag-input race, modal playlist) #197

Merged
bvandeusen merged 12 commits from dev into main 2026-07-06 21:03:30 -04:00
3 changed files with 38 additions and 34 deletions
Showing only changes of commit 3bf41ecac3 - Show all commits
+2 -2
View File
@@ -200,13 +200,13 @@ async function fullImageIds () {
}
async function openModal (imageId) {
modal.open(imageId, { postImageIds: await fullImageIds() })
modal.open(imageId, { playlistIds: await fullImageIds() })
}
async function openModalAtMore () {
const ids = await fullImageIds()
const first = ids[visibleCount.value] ?? ids[0]
if (first != null) modal.open(first, { postImageIds: ids })
if (first != null) modal.open(first, { playlistIds: ids })
}
// --- description "Show more" (text-only, in place, only when truncated) ----
+32 -31
View File
@@ -17,12 +17,13 @@ export const useModalStore = defineStore('modal', () => {
// chip rail. Audit 2026-06-02.
const inflight = useInflightToken()
// Post-scoped cycle. When set, prev/next cycles within this array
// (used by PostCard image clicks — the modal is scoped to that post's
// images). When null, prev/next falls back to current.value.neighbors
// (the gallery-store-driven /api/gallery/image/<id> neighbors).
const postImageIds = ref(null)
const postImageIndex = ref(0)
// Scoped playlist. When set, prev/next cycles within THIS ordered id array
// the current gallery filter (GalleryView) or a post's images (PostCard) — so
// the modal walks exactly what the user was looking at, not a global order.
// When null, prev/next falls back to current.value.neighbors (the
// /api/gallery/image/<id> global neighbours).
const playlistIds = ref(null)
const playlistIndex = ref(0)
async function open (id, opts = {}) {
// Cancel any in-flight tag mutation or reloadTags from the
@@ -32,13 +33,13 @@ export const useModalStore = defineStore('modal', () => {
current.value = null // cleared upfront so it stays null on error
// Update post-scoped state if caller passed it; otherwise clear so
// the next open() from gallery context uses neighbors mode.
if (opts.postImageIds != null) {
postImageIds.value = opts.postImageIds
postImageIndex.value = opts.postImageIds.indexOf(id)
if (postImageIndex.value < 0) postImageIndex.value = 0
} else if (opts.clearPostScope !== false && postImageIds.value != null) {
postImageIds.value = null
postImageIndex.value = 0
if (opts.playlistIds != null) {
playlistIds.value = opts.playlistIds
playlistIndex.value = opts.playlistIds.indexOf(id)
if (playlistIndex.value < 0) playlistIndex.value = 0
} else if (opts.clearPlaylist !== false && playlistIds.value != null) {
playlistIds.value = null
playlistIndex.value = 0
}
const t = inflight.claim()
await run(async () => {
@@ -53,17 +54,17 @@ export const useModalStore = defineStore('modal', () => {
currentImageId.value = null
current.value = null
error.value = null
postImageIds.value = null
postImageIndex.value = 0
playlistIds.value = null
playlistIndex.value = 0
}
async function goPrev () {
if (postImageIds.value != null) {
if (postImageIndex.value > 0) {
const newIdx = postImageIndex.value - 1
const newId = postImageIds.value[newIdx]
postImageIndex.value = newIdx
await open(newId, { postImageIds: postImageIds.value })
if (playlistIds.value != null) {
if (playlistIndex.value > 0) {
const newIdx = playlistIndex.value - 1
const newId = playlistIds.value[newIdx]
playlistIndex.value = newIdx
await open(newId, { playlistIds: playlistIds.value })
}
return
}
@@ -73,12 +74,12 @@ export const useModalStore = defineStore('modal', () => {
}
async function goNext () {
if (postImageIds.value != null) {
if (postImageIndex.value < postImageIds.value.length - 1) {
const newIdx = postImageIndex.value + 1
const newId = postImageIds.value[newIdx]
postImageIndex.value = newIdx
await open(newId, { postImageIds: postImageIds.value })
if (playlistIds.value != null) {
if (playlistIndex.value < playlistIds.value.length - 1) {
const newIdx = playlistIndex.value + 1
const newId = playlistIds.value[newIdx]
playlistIndex.value = newIdx
await open(newId, { playlistIds: playlistIds.value })
}
return
}
@@ -177,19 +178,19 @@ export const useModalStore = defineStore('modal', () => {
const isOpen = computed(() => currentImageId.value !== null)
const canPrev = computed(() => {
if (postImageIds.value != null) return postImageIndex.value > 0
if (playlistIds.value != null) return playlistIndex.value > 0
return current.value?.neighbors?.prev_id != null
})
const canNext = computed(() => {
if (postImageIds.value != null) {
return postImageIndex.value < (postImageIds.value.length - 1)
if (playlistIds.value != null) {
return playlistIndex.value < (playlistIds.value.length - 1)
}
return current.value?.neighbors?.next_id != null
})
return {
currentImageId, current, loading, error,
postImageIds, postImageIndex,
playlistIds, playlistIndex,
isOpen, canPrev, canNext,
open, close, goPrev, goNext,
reloadTags, removeTag, addExistingTag, createAndAdd,
+4 -1
View File
@@ -54,7 +54,10 @@ watch(() => route.query, (q) => {
})
function openImage(id) {
modal.open(id)
// Walk the current gallery filter in the modal (#1322) — prev/next moves
// through exactly the filtered set the operator is viewing, not global
// neighbours. Snapshot of the currently-loaded, filtered, ordered ids.
modal.open(id, { playlistIds: store.images.map((i) => i.id) })
}
</script>