Skip to content
Snippets Groups Projects
Commit aa707763 authored by Konrad Mohrfeldt's avatar Konrad Mohrfeldt :koala:
Browse files

feat: add ability to edit note contributors

refs #146
parent 57d928fd
No related branches found
No related tags found
No related merge requests found
Pipeline #3496 passed
......@@ -47,6 +47,34 @@
</template>
</FormGroup>
<FormGroup :label="t('noteEditor.contributors')" :errors="contributorErrors">
<ComboBox
v-model="contributors"
:choices="relevantHosts"
:close-on-select="false"
multiple
input-container-class="tw-flex tw-flex-wrap tw-gap-2 tw-w-full form-control tw-h-auto tw-min-h-[46px]"
input-class="tw-border-none tw-min-w-[150px] focus:tw-outline-none"
@search="hostSearch = $event"
>
<template #default="{ choice, ...attrs }">
<li v-bind="attrs">
{{ choice.name }}
</li>
</template>
<template #selected="{ items, deselect }">
<Tag
v-for="(host, index) in items"
:key="index"
:label="host.name"
removable
@remove="deselect(host)"
/>
</template>
</ComboBox>
</FormGroup>
<FormGroup :label="t('noteEditor.tags')" :errors="tagsErrors">
<TagInput v-model="tags" />
</FormGroup>
......@@ -74,38 +102,64 @@
<script lang="ts" setup>
import { computed, ref, watchEffect } from 'vue'
import { useStore } from 'vuex'
import { useTextareaAutosize } from '@vueuse/core'
import { useAPIObject, useServerFieldErrors } from '@/api'
import { useI18n } from '@/i18n'
import { useUpdatableState } from '@/util'
import { slugify } from '@/mixins/slugify'
import { Host, Show, TimeSlot } from '@/types'
import { useHostStore } from '@/stores/hosts'
import { newNote, NewNote, Note, useNoteStore } from '@/stores/notes'
import { slugify } from '@/mixins/slugify'
import { asyncWritableComputed, useUpdatableState } from '@/util'
import ADialog from '@/components/generic/ADialog.vue'
import FormGroup from '@/components/generic/FormGroup.vue'
import FormTable from '@/components/generic/FormTable.vue'
import ImagePicker from '@/components/images/ImagePicker.vue'
import ServerErrors from '@/components/ServerErrors.vue'
import TagInput from '@/components/generic/TagInput.vue'
import ComboBox from '@/components/ComboBox.vue'
import Tag from '@/components/generic/Tag.vue'
defineOptions({ compatConfig: { MODE: 3 } })
const props = defineProps<{
modelValue: null | number
timeslotId: number
timeslot: TimeSlot
isOpen: boolean
}>()
const emit = defineEmits<{
(e: 'update:modelValue', value: number | null): void
(e: 'show', value: boolean): void
}>()
const noteId = computed(() => props.modelValue)
const { t } = useI18n()
const store = useStore()
const shows = computed<Show[]>(() => store.state.shows.shows)
const show = computed(() => shows.value.find((show) => show.id === props.timeslot.showId))
const noteId = computed(() => props.modelValue)
const noteStore = useNoteStore()
const { obj: storedNote } = useAPIObject(noteStore, noteId)
const noteData = ref<Note | NewNote>(newNote(props.timeslotId))
const noteData = ref<Note | NewNote>(newNote(props.timeslot.id, show.value))
const { items: hosts, retrieve: retrieveHost } = useHostStore()
const hostSearch = ref('')
const relevantHosts = computed(() => {
const sanitize = (s: string) => s.replace(/\s+/g, '').toLowerCase()
const searchString = sanitize(hostSearch.value)
return hosts.filter(
({ id, name }) =>
!noteData.value.contributorIds.includes(id) && sanitize(name).includes(searchString),
)
})
const error = ref<Error>()
const [titleErrors, summaryErrors, contentErrors, imageErrors, tagsErrors, remainingErrors] =
useServerFieldErrors(error, 'title', 'summary', 'content', 'tags', 'imageId')
const [
titleErrors,
summaryErrors,
contentErrors,
imageErrors,
tagsErrors,
contributorErrors,
remainingErrors,
] = useServerFieldErrors(error, 'title', 'summary', 'content', 'tags', 'contributorIds', 'imageId')
const tags = computed({
get() {
return noteData.value.tags
......@@ -117,6 +171,17 @@ const tags = computed({
noteData.value.tags = value.join(', ')
},
})
const contributors = asyncWritableComputed<Host[]>([], {
async get() {
const data = await Promise.all(
noteData.value.contributorIds.map((id) => retrieveHost(id, undefined, { useCached: true })),
)
return data.filter((obj) => obj !== null) as Host[]
},
set(value: Host[]) {
noteData.value.contributorIds = value.map(({ id }) => id)
},
})
watchEffect(() => {
if (storedNote.value) {
......
......@@ -69,7 +69,7 @@
<NoteEditorModal
v-model="localNoteId"
:is-open="showNoteEditor"
:timeslot-id="timeslot.id"
:timeslot="timeslot"
@show="showNoteEditor = $event"
/>
</Teleport>
......
......@@ -337,6 +337,7 @@ export default {
contentPlaceholder: 'Beschreibe den Inhalt der Sendung',
image: 'Bild',
tags: 'Schlagwörter',
contributors: 'Mitwirkende',
save: 'Sendungsbeschreibung speichern',
},
......
......@@ -329,6 +329,7 @@ export default {
contentPlaceholder: 'Describe the content of the show',
image: 'Image',
tags: 'Tags',
contributors: 'Contributors',
save: 'Save Note',
},
......
......@@ -10,6 +10,7 @@ import {
createExtendableAPI,
createSteeringURL,
} from '@/api'
import { Show } from '@/types'
export type Note = {
id: number
......@@ -35,7 +36,7 @@ export type NewNote = Omit<Note, ReadonlyAttrs>
type NoteCreateData = Omit<Note, ReadonlyAttrs>
type NoteUpdateData = Partial<Omit<Note, ReadonlyAttrs>>
export function newNote(timeslotId: number): NewNote {
export function newNote(timeslotId: number, show?: Show): NewNote {
return {
title: '',
slug: '',
......@@ -43,7 +44,7 @@ export function newNote(timeslotId: number): NewNote {
content: '',
imageId: null,
cbaId: null,
contributorIds: [],
contributorIds: show?.hostIds ?? [],
links: [],
timeslotId,
tags: '',
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment