fix(17): WR-07 open a singleton's record from its list route

A settings link to a singleton controller only worked on a plain left
click; a new tab landed on the list route, which answers 404, and every
non-singleton link paid an extra /singleton request. The settings index
now links like any controller link, and the list view opens the
controller's one record when its list answers 404 and the singleton
route answers an id.
This commit is contained in:
2026-10-12 21:43:07 +02:00
parent fb10b06b21
commit 9d7c21d800
5 changed files with 73 additions and 49 deletions

View File

@@ -1,9 +1,5 @@
<script setup lang="ts">
import { ref } from 'vue'
import { RouterLink, useRouter } from 'vue-router'
import { api } from '../api/client'
import type { SettingsEntry } from '../api/types'
import { parseControllerId } from '../app/controllerRoutes'
import { RouterLink } from 'vue-router'
import { t } from '../app/i18n'
import { iconFor } from '../app/icons'
import { settingsGroups, settingsPath } from '../state/useSettings'
@@ -11,37 +7,10 @@ import { settingsGroups, settingsPath } from '../state/useSettings'
// Settings index (D-21): every permitted settings page, grouped by category,
// with its icon, label and description. The server filters the list. A
// singleton entry opens its settings form; a link entry opens its controller.
// A link to a singleton controller (D-11) opens that controller's one record:
// GET …/singleton answers its id (a controller that is not a singleton
// answers 404 and the link opens its list).
const router = useRouter()
const opening = ref<string | null>(null)
async function open(event: MouseEvent, entry: SettingsEntry, navigate: (event: MouseEvent) => unknown): Promise<void> {
const params = entry.controller ? parseControllerId(entry.controller) : null
if (!params) {
void navigate(event)
return
}
if (event.button !== 0 || event.metaKey || event.ctrlKey || event.shiftKey || event.altKey) {
// A new tab or window opens the link as it is.
return
}
event.preventDefault()
if (opening.value !== null) {
return
}
opening.value = entry.code
const target = settingsPath(entry)
try {
const { data } = await api.GET('/{vendor}/{plugin}/{controller}/singleton', { params: { path: params } })
await router.push(data ? `${target}/${data.data.id}` : target)
} catch {
await router.push(target)
} finally {
opening.value = null
}
}
// A link to a singleton controller (D-11) points at the controller's list
// route like any other, so a click, a new tab and a bookmark behave the
// same; the list view opens the controller's one record when the list
// answers 404 (ListView openSingleton).
</script>
<template>
@@ -70,9 +39,8 @@ async function open(event: MouseEvent, entry: SettingsEntry, navigate: (event: M
<a
:href="href"
:data-settings="entry.code"
:aria-busy="opening === entry.code ? 'true' : undefined"
class="flex items-start gap-3.5 rounded-card border border-border bg-surface p-5 text-text no-underline shadow-card hover:bg-hover"
@click="open($event, entry, navigate)"
@click="navigate"
>
<span class="flex size-10 shrink-0 items-center justify-center rounded-inner bg-subtle text-muted">
<component :is="iconFor(entry.icon)" :size="20" aria-hidden="true" />