<template> <MkStickyContainer> <template #header><MkPageHeader :actions="headerActions" :tabs="headerTabs"/></template> <MkSpacer :contentMax="700"> <div v-if="channelId == null || channel != null" class="_gaps_m"> <MkInput v-model="name"> <template #label>{{ i18n.ts.name }}</template> </MkInput> <MkTextarea v-model="description"> <template #label>{{ i18n.ts.description }}</template> </MkTextarea> <MkColorInput v-model="color"> <template #label>{{ i18n.ts.color }}</template> </MkColorInput> <div> <MkButton v-if="bannerId == null" @click="setBannerImage"><i class="ti ti-plus"></i> {{ i18n.ts._channel.setBanner }}</MkButton> <div v-else-if="bannerUrl"> <img :src="bannerUrl" style="width: 100%;"/> <MkButton @click="removeBannerImage()"><i class="ti ti-trash"></i> {{ i18n.ts._channel.removeBanner }}</MkButton> </div> </div> <MkFolder :defaultOpen="true"> <template #label>{{ i18n.ts.pinnedNotes }}</template> <div class="_gaps"> <MkButton primary rounded @click="addPinnedNote()"><i class="ti ti-plus"></i></MkButton> <Sortable v-model="pinnedNotes" itemKey="id" :handle="'.' + $style.pinnedNoteHandle" :animation="150" > <template #item="{element,index}"> <div :class="$style.pinnedNote"> <button class="_button" :class="$style.pinnedNoteHandle"><i class="ti ti-menu"></i></button> {{ element.id }} <button class="_button" :class="$style.pinnedNoteRemove" @click="removePinnedNote(index)"><i class="ti ti-x"></i></button> </div> </template> </Sortable> </div> </MkFolder> <div class="_buttons"> <MkButton primary @click="save()"><i class="ti ti-device-floppy"></i> {{ channelId ? i18n.ts.save : i18n.ts.create }}</MkButton> <MkButton v-if="channelId" danger @click="archive()"><i class="ti ti-trash"></i> {{ i18n.ts.archive }}</MkButton> </div> </div> </MkSpacer> </MkStickyContainer> </template> <script lang="ts" setup> import { computed, ref, watch, defineAsyncComponent } from 'vue'; import MkTextarea from '@/components/MkTextarea.vue'; import MkButton from '@/components/MkButton.vue'; import MkInput from '@/components/MkInput.vue'; import MkColorInput from '@/components/MkColorInput.vue'; import { selectFile } from '@/scripts/select-file'; import * as os from '@/os'; import { useRouter } from '@/router'; import { definePageMetadata } from '@/scripts/page-metadata'; import { i18n } from '@/i18n'; import MkFolder from '@/components/MkFolder.vue'; const Sortable = defineAsyncComponent(() => import('vuedraggable').then(x => x.default)); const router = useRouter(); const props = defineProps<{ channelId?: string; }>(); let channel = $ref(null); let name = $ref(null); let description = $ref(null); let bannerUrl = $ref<string | null>(null); let bannerId = $ref<string | null>(null); let color = $ref('#000'); const pinnedNotes = ref([]); watch(() => bannerId, async () => { if (bannerId == null) { bannerUrl = null; } else { bannerUrl = (await os.api('drive/files/show', { fileId: bannerId, })).url; } }); async function fetchChannel() { if (props.channelId == null) return; channel = await os.api('channels/show', { channelId: props.channelId, }); name = channel.name; description = channel.description; bannerId = channel.bannerId; bannerUrl = channel.bannerUrl; pinnedNotes.value = channel.pinnedNoteIds.map(id => ({ id, })); color = channel.color; } fetchChannel(); async function addPinnedNote() { const { canceled, result: value } = await os.inputText({ title: i18n.ts.noteIdOrUrl, }); if (canceled) return; const note = await os.apiWithDialog('notes/show', { noteId: value.includes('/') ? value.split('/').pop() : value, }); pinnedNotes.value = [{ id: note.id, }, ...pinnedNotes.value]; } function removePinnedNote(index: number) { pinnedNotes.value.splice(index, 1); } function save() { const params = { name: name, description: description, bannerId: bannerId, pinnedNoteIds: pinnedNotes.value.map(x => x.id), color: color, }; if (props.channelId) { params.channelId = props.channelId; os.api('channels/update', params).then(() => { os.success(); }); } else { os.api('channels/create', params).then(created => { os.success(); router.push(`/channels/${created.id}`); }); } } async function archive() { const { canceled } = await os.confirm({ type: 'warning', title: i18n.t('channelArchiveConfirmTitle', { name: name }), text: i18n.ts.channelArchiveConfirmDescription, }); if (canceled) return; os.api('channels/update', { channelId: props.channelId, isArchived: true, }).then(() => { os.success(); }); } function setBannerImage(evt) { selectFile(evt.currentTarget ?? evt.target, null).then(file => { bannerId = file.id; }); } function removeBannerImage() { bannerId = null; } const headerActions = $computed(() => []); const headerTabs = $computed(() => []); definePageMetadata(computed(() => props.channelId ? { title: i18n.ts._channel.edit, icon: 'ti ti-device-tv', } : { title: i18n.ts._channel.create, icon: 'ti ti-device-tv', })); </script> <style lang="scss" module> .pinnedNote { position: relative; display: block; line-height: 2.85rem; text-overflow: ellipsis; overflow: hidden; white-space: nowrap; color: var(--navFg); } .pinnedNoteRemove { position: absolute; z-index: 10000; width: 32px; height: 32px; color: #ff2a2a; right: 8px; opacity: 0.8; } .pinnedNoteHandle { cursor: move; width: 32px; height: 32px; margin: 0 8px; opacity: 0.5; } </style>