From 11a16a663a53148b9c895c78b8660ff8182ed7e5 Mon Sep 17 00:00:00 2001 From: Thibaud Dauce Date: Tue, 2 Jun 2026 12:01:12 +0200 Subject: [PATCH 1/5] feat: add topic page --- pages/topics/[id].vue | 185 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 185 insertions(+) create mode 100644 pages/topics/[id].vue diff --git a/pages/topics/[id].vue b/pages/topics/[id].vue new file mode 100644 index 000000000..65cd5698a --- /dev/null +++ b/pages/topics/[id].vue @@ -0,0 +1,185 @@ + + + From 5d74167ffd48cf3c764170156300a87e6361a534 Mon Sep 17 00:00:00 2001 From: Thibaud Dauce Date: Tue, 2 Jun 2026 12:51:20 +0200 Subject: [PATCH 2/5] add discussions to topics page --- components/Buttons/EditButton.vue | 3 +- pages/topics/[id].vue | 125 ++++++++++-------------------- pages/topics/[id]/discussions.vue | 16 ++++ pages/topics/[id]/index.vue | 87 +++++++++++++++++++++ types/discussions.ts | 4 +- utils/discussions.ts | 8 ++ 6 files changed, 156 insertions(+), 87 deletions(-) create mode 100644 pages/topics/[id]/discussions.vue create mode 100644 pages/topics/[id]/index.vue diff --git a/components/Buttons/EditButton.vue b/components/Buttons/EditButton.vue index 201f9bdd2..45d4ad751 100644 --- a/components/Buttons/EditButton.vue +++ b/components/Buttons/EditButton.vue @@ -14,7 +14,7 @@ import { BrandedButton, throwOnNever } from '@datagouv/components-next' import { RiEdit2Line } from '@remixicon/vue' const props = defineProps<{ - type: 'organizations' | 'users' | 'posts' | 'reuses' | 'dataservices' | 'datasets' + type: 'organizations' | 'users' | 'posts' | 'reuses' | 'dataservices' | 'datasets' | 'topics' id: string }>() @@ -31,6 +31,7 @@ const link = computed(() => { case 'reuses': case 'dataservices': case 'datasets': + case 'topics': return base default: return throwOnNever(props.type as never, t('Aucun autre type défini')) diff --git a/pages/topics/[id].vue b/pages/topics/[id].vue index 65cd5698a..8bcaeb654 100644 --- a/pages/topics/[id].vue +++ b/pages/topics/[id].vue @@ -1,17 +1,24 @@ diff --git a/pages/topics/[id]/index.vue b/pages/topics/[id]/index.vue new file mode 100644 index 000000000..5041f1d0b --- /dev/null +++ b/pages/topics/[id]/index.vue @@ -0,0 +1,87 @@ + + + diff --git a/types/discussions.ts b/types/discussions.ts index b3899d4ad..17efcad20 100644 --- a/types/discussions.ts +++ b/types/discussions.ts @@ -1,4 +1,4 @@ -import type { Dataservice, DatasetV2, OrganizationReference, Reuse, UserReference } from '@datagouv/components-next' +import type { Dataservice, DatasetV2, OrganizationReference, Reuse, TopicV2, UserReference } from '@datagouv/components-next' import type { Post } from './posts' export type DiscussionSortedBy = 'title' | 'created' | 'closed' @@ -12,7 +12,7 @@ export type Subject = { class: string } -export type DiscussionSubjectTypes = Dataservice | DatasetV2 | Reuse | Post +export type DiscussionSubjectTypes = Dataservice | DatasetV2 | Reuse | Post | TopicV2 export type DiscussionSubject = { class: 'Dataservice' | 'Dataset' | 'Reuse' | 'Post' | 'Topic' | 'Organization' diff --git a/utils/discussions.ts b/utils/discussions.ts index 2d881c90b..b077106f7 100644 --- a/utils/discussions.ts +++ b/utils/discussions.ts @@ -23,6 +23,10 @@ export function getSubjectTitle(subject: DiscussionSubjectTypes) { if ('title' in subject) { return subject.title } + // Topics use `name` instead of `title`. + if ('name' in subject) { + return subject.name + } return throwOnNever(subject as never, `Unknown type ${subject}`) }; @@ -31,6 +35,10 @@ export function getSubjectPage(subject: DiscussionSubjectTypes) { if (subject === null) { return '' } + // Topics expose their front URL through `page` like other subjects. + if ('elements' in subject) { + return subject.page + } if ('page' in subject) { return subject.page } From 83236d38d54e540b329870bdb00f9983710c8d6e Mon Sep 17 00:00:00 2001 From: Thibaud Dauce Date: Tue, 2 Jun 2026 13:01:38 +0200 Subject: [PATCH 3/5] remove comments --- utils/discussions.ts | 2 -- 1 file changed, 2 deletions(-) diff --git a/utils/discussions.ts b/utils/discussions.ts index b077106f7..e120b0e5b 100644 --- a/utils/discussions.ts +++ b/utils/discussions.ts @@ -23,7 +23,6 @@ export function getSubjectTitle(subject: DiscussionSubjectTypes) { if ('title' in subject) { return subject.title } - // Topics use `name` instead of `title`. if ('name' in subject) { return subject.name } @@ -35,7 +34,6 @@ export function getSubjectPage(subject: DiscussionSubjectTypes) { if (subject === null) { return '' } - // Topics expose their front URL through `page` like other subjects. if ('elements' in subject) { return subject.page } From 03eefd32d46a7a9f9283ad52041350bd7c698e82 Mon Sep 17 00:00:00 2001 From: Thibaud Dauce Date: Wed, 3 Jun 2026 08:57:17 +0200 Subject: [PATCH 4/5] add comment --- utils/discussions.ts | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/utils/discussions.ts b/utils/discussions.ts index e120b0e5b..432b0717c 100644 --- a/utils/discussions.ts +++ b/utils/discussions.ts @@ -34,6 +34,10 @@ export function getSubjectPage(subject: DiscussionSubjectTypes) { if (subject === null) { return '' } + // TODO: remove once udata#3765 is merged. Until then the topic API doesn't + // return `page`, so the `'page' in subject` check below fails at runtime and + // we'd hit throwOnNever. Matching on `elements` (always present on topics) + // avoids the crash while the field is missing. if ('elements' in subject) { return subject.page } From 15216c641a223791ab59f2c583487613bcddbef2 Mon Sep 17 00:00:00 2001 From: Thibaud Dauce Date: Wed, 3 Jun 2026 09:05:21 +0200 Subject: [PATCH 5/5] add comment --- pages/topics/[id]/index.vue | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/pages/topics/[id]/index.vue b/pages/topics/[id]/index.vue index 5041f1d0b..e4c4d47c6 100644 --- a/pages/topics/[id]/index.vue +++ b/pages/topics/[id]/index.vue @@ -80,6 +80,12 @@ const reusesQuery = computed(() => ({ page_size: reusesPageSize.value, topic: props.topic.id, })) +// We use the search endpoint for reuses because there is no v2 reuses index +// filtered by topic yet. Once udata#3800 is merged we can switch to the faster +// v2 index, like we already do for datasets above. +// If we ever need the TopicElement data (title/description/extras attached to +// the element), we could instead query /api/2/topics/{id}/elements/ for both +// datasets and reuses. const { data: reuses } = await useAPI>('/api/2/reuses/search/', { headers: { 'X-Fields': reusesXFields }, query: reusesQuery,