kopia lustrzana https://dev.funkwhale.audio/funkwhale/funkwhale
environments/review-front-deve-otr6gc/deployments/13419
rodzic
bbdb3dcb9d
commit
b8f5f5d6ef
|
@ -38,6 +38,7 @@
|
||||||
"sass": "1.53.0",
|
"sass": "1.53.0",
|
||||||
"showdown": "2.1.0",
|
"showdown": "2.1.0",
|
||||||
"text-clipper": "2.2.0",
|
"text-clipper": "2.2.0",
|
||||||
|
"transliteration": "^2.3.5",
|
||||||
"vue": "3.2.37",
|
"vue": "3.2.37",
|
||||||
"vue-gettext": "2.1.12",
|
"vue-gettext": "2.1.12",
|
||||||
"vue-plyr": "7.0.0",
|
"vue-plyr": "7.0.0",
|
||||||
|
|
|
@ -122,16 +122,11 @@ const showUserModal = ref(false)
|
||||||
const showLanguageModal = ref(false)
|
const showLanguageModal = ref(false)
|
||||||
const showThemeModal = ref(false)
|
const showThemeModal = ref(false)
|
||||||
|
|
||||||
// TODO (wvffle): Use current language this.$language.current
|
const gettext = useGettext()
|
||||||
const languageSelection = undefined
|
const languageSelection = ref(gettext.current)
|
||||||
// export default {
|
watch(languageSelection, (v) => {
|
||||||
// watch: {
|
store.dispatch('ui/currentLanguage', v)
|
||||||
// languageSelection: function (v) {
|
})
|
||||||
// this.$store.dispatch('ui/currentLanguage', v)
|
|
||||||
// this.$refs.languageModal.closeModal()
|
|
||||||
// }
|
|
||||||
// },
|
|
||||||
// }
|
|
||||||
|
|
||||||
watch(() => store.state.auth.authenticated, (authenticated) => {
|
watch(() => store.state.auth.authenticated, (authenticated) => {
|
||||||
if (authenticated) {
|
if (authenticated) {
|
||||||
|
|
|
@ -1,3 +1,145 @@
|
||||||
|
<script setup lang="ts">
|
||||||
|
import type { Channel, BackendError } from '~/types'
|
||||||
|
|
||||||
|
import { slugify } from 'transliteration'
|
||||||
|
import { reactive, computed, ref, watchEffect, watch } from 'vue'
|
||||||
|
import { useGettext } from 'vue3-gettext'
|
||||||
|
|
||||||
|
import axios from 'axios'
|
||||||
|
import AttachmentInput from '~/components/common/AttachmentInput.vue'
|
||||||
|
import TagsSelector from '~/components/library/TagsSelector.vue'
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
object?: Channel | null
|
||||||
|
step: number
|
||||||
|
}
|
||||||
|
|
||||||
|
const emit = defineEmits(['category', 'submittable', 'loading', 'errored', 'created', 'updated'])
|
||||||
|
const props = withDefaults(defineProps<Props>(), {
|
||||||
|
object: null,
|
||||||
|
step: 1
|
||||||
|
})
|
||||||
|
|
||||||
|
const { $pgettext } = useGettext()
|
||||||
|
|
||||||
|
const newValues = reactive({
|
||||||
|
name: props.object?.artist?.name ?? '',
|
||||||
|
username: props.object?.actor.preferred_username ?? '',
|
||||||
|
tags: props.object?.artist?.tags ?? [],
|
||||||
|
description: props.object?.artist?.description?.text ?? '',
|
||||||
|
cover: props.object?.artist?.cover?.uuid ?? null,
|
||||||
|
content_category: props.object?.artist?.content_category ?? 'podcast',
|
||||||
|
metadata: { ...(props.object?.metadata ?? {}) }
|
||||||
|
})
|
||||||
|
|
||||||
|
const creating = computed(() => props.object === null)
|
||||||
|
const categoryChoices = computed(() => [
|
||||||
|
{
|
||||||
|
value: 'podcast',
|
||||||
|
label: $pgettext('*/*/*', 'Podcasts'),
|
||||||
|
helpText: $pgettext('Content/Channels/Help', 'Host your episodes and keep your community updated.')
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: 'music',
|
||||||
|
label: $pgettext('*/*/*', 'Artist discography'),
|
||||||
|
helpText: $pgettext('Content/Channels/Help', 'Publish music you make as a nice discography of albums and singles.')
|
||||||
|
}
|
||||||
|
])
|
||||||
|
|
||||||
|
interface ITunesCategory {
|
||||||
|
value: string
|
||||||
|
label: string
|
||||||
|
children: []
|
||||||
|
}
|
||||||
|
|
||||||
|
interface MetadataChoices {
|
||||||
|
itunes_category?: ITunesCategory[] | null
|
||||||
|
language: {
|
||||||
|
value: string
|
||||||
|
label: string
|
||||||
|
}[]
|
||||||
|
}
|
||||||
|
|
||||||
|
const metadataChoices = ref({ itunes_category: null } as MetadataChoices)
|
||||||
|
const itunesSubcategories = computed(() => {
|
||||||
|
for (const element of metadataChoices.value.itunes_category ?? []) {
|
||||||
|
if (element.value === newValues.metadata.itunes_category) {
|
||||||
|
return element.children ?? []
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return []
|
||||||
|
})
|
||||||
|
|
||||||
|
const labels = computed(() => ({
|
||||||
|
namePlaceholder: $pgettext('Content/Channel/Form.Field.Placeholder', 'Awesome channel name'),
|
||||||
|
usernamePlaceholder: $pgettext('Content/Channel/Form.Field.Placeholder', 'awesomechannelname')
|
||||||
|
}))
|
||||||
|
|
||||||
|
const submittable = computed(() =>
|
||||||
|
newValues.content_category === 'podcast'
|
||||||
|
? newValues.name && newValues.username && newValues.metadata.itunes_category && newValues.metadata.language
|
||||||
|
: newValues.name && newValues.username
|
||||||
|
)
|
||||||
|
|
||||||
|
watch(() => newValues.name, (name) => {
|
||||||
|
if (creating.value) {
|
||||||
|
newValues.username = slugify(name)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
watch(() => newValues.metadata.itunes_category, () => {
|
||||||
|
newValues.metadata.itunes_subcategory = null
|
||||||
|
})
|
||||||
|
|
||||||
|
const isLoading = ref(false)
|
||||||
|
const errors = ref([] as string[])
|
||||||
|
|
||||||
|
watchEffect(() => emit('category', newValues.content_category))
|
||||||
|
watchEffect(() => emit('loading', isLoading.value))
|
||||||
|
watchEffect(() => emit('submittable', submittable.value))
|
||||||
|
|
||||||
|
// TODO (wvffle): Add loader / Use Suspense
|
||||||
|
const fetchMetadataChoices = async () => {
|
||||||
|
try {
|
||||||
|
const response = await axios.get('channels/metadata-choices')
|
||||||
|
metadataChoices.value = response.data
|
||||||
|
} catch (error) {
|
||||||
|
errors.value = (error as BackendError).backendErrors
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fetchMetadataChoices()
|
||||||
|
|
||||||
|
const submit = async () => {
|
||||||
|
isLoading.value = true
|
||||||
|
|
||||||
|
const payload = {
|
||||||
|
...newValues,
|
||||||
|
description: newValues.description
|
||||||
|
? {
|
||||||
|
content_type: 'text/markdown',
|
||||||
|
text: newValues.description
|
||||||
|
}
|
||||||
|
: null
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const request = () => creating.value
|
||||||
|
? axios.post('channels/', payload)
|
||||||
|
: axios.patch(`channels/${props.object?.uuid}`, payload)
|
||||||
|
|
||||||
|
const response = await request()
|
||||||
|
emit(creating.value ? 'created' : 'updated', response.data)
|
||||||
|
} catch (error) {
|
||||||
|
errors.value = (error as BackendError).backendErrors
|
||||||
|
emit('errored', errors.value)
|
||||||
|
}
|
||||||
|
|
||||||
|
isLoading.value = false
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<form
|
<form
|
||||||
class="ui form"
|
class="ui form"
|
||||||
|
@ -78,8 +220,8 @@
|
||||||
<input
|
<input
|
||||||
v-model="newValues.username"
|
v-model="newValues.username"
|
||||||
type="text"
|
type="text"
|
||||||
:required="creating || null"
|
:required="creating"
|
||||||
:disabled="!creating || null"
|
:disabled="!creating"
|
||||||
:placeholder="labels.usernamePlaceholder"
|
:placeholder="labels.usernamePlaceholder"
|
||||||
>
|
>
|
||||||
</div>
|
</div>
|
||||||
|
@ -182,7 +324,7 @@
|
||||||
id="itunes-category"
|
id="itunes-category"
|
||||||
v-model="newValues.metadata.itunes_subcategory"
|
v-model="newValues.metadata.itunes_subcategory"
|
||||||
name="itunes-category"
|
name="itunes-category"
|
||||||
:disabled="!newValues.metadata.itunes_category || null"
|
:disabled="!newValues.metadata.itunes_category"
|
||||||
class="ui dropdown"
|
class="ui dropdown"
|
||||||
>
|
>
|
||||||
<option
|
<option
|
||||||
|
@ -241,171 +383,3 @@
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
|
||||||
import axios from 'axios'
|
|
||||||
|
|
||||||
import AttachmentInput from '~/components/common/AttachmentInput.vue'
|
|
||||||
import TagsSelector from '~/components/library/TagsSelector.vue'
|
|
||||||
|
|
||||||
function slugify (text) {
|
|
||||||
return text.toString().toLowerCase()
|
|
||||||
.replace(/\s+/g, '') // Remove spaces
|
|
||||||
.replace(/[^\w]+/g, '') // Remove all non-word chars
|
|
||||||
}
|
|
||||||
|
|
||||||
export default {
|
|
||||||
components: {
|
|
||||||
AttachmentInput,
|
|
||||||
TagsSelector
|
|
||||||
},
|
|
||||||
props: {
|
|
||||||
object: { type: Object, required: false, default: null },
|
|
||||||
step: { type: Number, required: false, default: 1 }
|
|
||||||
},
|
|
||||||
data () {
|
|
||||||
const oldValues = {}
|
|
||||||
if (this.object) {
|
|
||||||
oldValues.metadata = { ...(this.object.metadata || {}) }
|
|
||||||
oldValues.name = this.object.artist.name
|
|
||||||
oldValues.description = this.object.artist.description
|
|
||||||
oldValues.cover = this.object.artist.cover
|
|
||||||
oldValues.tags = this.object.artist.tags
|
|
||||||
oldValues.content_category = this.object.artist.content_category
|
|
||||||
oldValues.username = this.object.actor.preferred_username
|
|
||||||
}
|
|
||||||
return {
|
|
||||||
isLoading: false,
|
|
||||||
errors: [],
|
|
||||||
metadataChoices: null,
|
|
||||||
newValues: {
|
|
||||||
name: oldValues.name || '',
|
|
||||||
username: oldValues.username || '',
|
|
||||||
tags: oldValues.tags || [],
|
|
||||||
description: (oldValues.description || {}).text || '',
|
|
||||||
cover: (oldValues.cover || {}).uuid || null,
|
|
||||||
content_category: oldValues.content_category || 'podcast',
|
|
||||||
metadata: oldValues.metadata || {}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
computed: {
|
|
||||||
creating () {
|
|
||||||
return this.object === null
|
|
||||||
},
|
|
||||||
categoryChoices () {
|
|
||||||
return [
|
|
||||||
{
|
|
||||||
value: 'podcast',
|
|
||||||
label: this.$pgettext('*/*/*', 'Podcasts'),
|
|
||||||
helpText: this.$pgettext('Content/Channels/Help', 'Host your episodes and keep your community updated.')
|
|
||||||
},
|
|
||||||
{
|
|
||||||
value: 'music',
|
|
||||||
label: this.$pgettext('*/*/*', 'Artist discography'),
|
|
||||||
helpText: this.$pgettext('Content/Channels/Help', 'Publish music you make as a nice discography of albums and singles.')
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
itunesSubcategories () {
|
|
||||||
for (let index = 0; index < this.metadataChoices.itunes_category.length; index++) {
|
|
||||||
const element = this.metadataChoices.itunes_category[index]
|
|
||||||
if (element.value === this.newValues.metadata.itunes_category) {
|
|
||||||
return element.children || []
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return []
|
|
||||||
},
|
|
||||||
labels () {
|
|
||||||
return {
|
|
||||||
namePlaceholder: this.$pgettext('Content/Channel/Form.Field.Placeholder', 'Awesome channel name'),
|
|
||||||
usernamePlaceholder: this.$pgettext('Content/Channel/Form.Field.Placeholder', 'awesomechannelname')
|
|
||||||
}
|
|
||||||
},
|
|
||||||
submittable () {
|
|
||||||
let v = this.newValues.name && this.newValues.username
|
|
||||||
if (this.newValues.content_category === 'podcast') {
|
|
||||||
v = v && this.newValues.metadata.itunes_category && this.newValues.metadata.language
|
|
||||||
}
|
|
||||||
return !!v
|
|
||||||
}
|
|
||||||
},
|
|
||||||
watch: {
|
|
||||||
'newValues.name' (v) {
|
|
||||||
if (this.creating) {
|
|
||||||
this.newValues.username = slugify(v)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
'newValues.metadata.itunes_category' (v) {
|
|
||||||
this.newValues.metadata.itunes_subcategory = null
|
|
||||||
},
|
|
||||||
'newValues.content_category': {
|
|
||||||
handler (v) {
|
|
||||||
this.$emit('category', v)
|
|
||||||
},
|
|
||||||
immediate: true
|
|
||||||
},
|
|
||||||
isLoading: {
|
|
||||||
handler (v) {
|
|
||||||
this.$emit('loading', v)
|
|
||||||
},
|
|
||||||
immediate: true
|
|
||||||
},
|
|
||||||
submittable: {
|
|
||||||
handler (v) {
|
|
||||||
this.$emit('submittable', v)
|
|
||||||
},
|
|
||||||
immediate: true
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
created () {
|
|
||||||
this.fetchMetadataChoices()
|
|
||||||
},
|
|
||||||
methods: {
|
|
||||||
fetchMetadataChoices () {
|
|
||||||
const self = this
|
|
||||||
axios.get('channels/metadata-choices').then((response) => {
|
|
||||||
self.metadataChoices = response.data
|
|
||||||
}, error => {
|
|
||||||
self.errors = error.backendErrors
|
|
||||||
})
|
|
||||||
},
|
|
||||||
submit () {
|
|
||||||
this.isLoading = true
|
|
||||||
const self = this
|
|
||||||
const handler = this.creating ? axios.post : axios.patch
|
|
||||||
const url = this.creating ? 'channels/' : `channels/${this.object.uuid}`
|
|
||||||
const payload = {
|
|
||||||
name: this.newValues.name,
|
|
||||||
username: this.newValues.username,
|
|
||||||
tags: this.newValues.tags,
|
|
||||||
content_category: this.newValues.content_category,
|
|
||||||
cover: this.newValues.cover,
|
|
||||||
metadata: this.newValues.metadata
|
|
||||||
}
|
|
||||||
if (this.newValues.description) {
|
|
||||||
payload.description = {
|
|
||||||
content_type: 'text/markdown',
|
|
||||||
text: this.newValues.description
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
payload.description = null
|
|
||||||
}
|
|
||||||
|
|
||||||
handler(url, payload).then((response) => {
|
|
||||||
self.isLoading = false
|
|
||||||
if (self.creating) {
|
|
||||||
self.$emit('created', response.data)
|
|
||||||
} else {
|
|
||||||
self.$emit('updated', response.data)
|
|
||||||
}
|
|
||||||
}, error => {
|
|
||||||
self.isLoading = false
|
|
||||||
self.errors = error.backendErrors
|
|
||||||
self.$emit('errored', self.errors)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
|
|
|
@ -113,6 +113,14 @@ export interface Channel {
|
||||||
subscriptions_count: number
|
subscriptions_count: number
|
||||||
downloads_count: number
|
downloads_count: number
|
||||||
content_category: ContentCategory
|
content_category: ContentCategory
|
||||||
|
|
||||||
|
metadata?: {
|
||||||
|
itunes_category?: unknown
|
||||||
|
itunes_subcategory?: unknown
|
||||||
|
language?: string
|
||||||
|
owner_name: string
|
||||||
|
owner_email: string
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export type PrivacyLevel = 'everyone' | 'instance' | 'me'
|
export type PrivacyLevel = 'everyone' | 'instance' | 'me'
|
||||||
|
|
|
@ -6237,6 +6237,13 @@ tr46@^1.0.1:
|
||||||
dependencies:
|
dependencies:
|
||||||
punycode "^2.1.0"
|
punycode "^2.1.0"
|
||||||
|
|
||||||
|
transliteration@^2.3.5:
|
||||||
|
version "2.3.5"
|
||||||
|
resolved "https://registry.yarnpkg.com/transliteration/-/transliteration-2.3.5.tgz#8f92309575f69e4a8a525dab4ff705ebcf961c45"
|
||||||
|
integrity sha512-HAGI4Lq4Q9dZ3Utu2phaWgtm3vB6PkLUFqWAScg/UW+1eZ/Tg6Exo4oC0/3VUol/w4BlefLhUUSVBr/9/ZGQOw==
|
||||||
|
dependencies:
|
||||||
|
yargs "^17.5.1"
|
||||||
|
|
||||||
ts-jest@28.0.5:
|
ts-jest@28.0.5:
|
||||||
version "28.0.5"
|
version "28.0.5"
|
||||||
resolved "https://registry.yarnpkg.com/ts-jest/-/ts-jest-28.0.5.tgz#31776f768fba6dfc8c061d488840ed0c8eeac8b9"
|
resolved "https://registry.yarnpkg.com/ts-jest/-/ts-jest-28.0.5.tgz#31776f768fba6dfc8c061d488840ed0c8eeac8b9"
|
||||||
|
@ -6895,7 +6902,7 @@ yargs-parser@^21.0.0, yargs-parser@^21.0.1:
|
||||||
resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-21.1.1.tgz#9096bceebf990d21bb31fa9516e0ede294a77d35"
|
resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-21.1.1.tgz#9096bceebf990d21bb31fa9516e0ede294a77d35"
|
||||||
integrity sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==
|
integrity sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==
|
||||||
|
|
||||||
yargs@^17.3.1:
|
yargs@^17.3.1, yargs@^17.5.1:
|
||||||
version "17.5.1"
|
version "17.5.1"
|
||||||
resolved "https://registry.yarnpkg.com/yargs/-/yargs-17.5.1.tgz#e109900cab6fcb7fd44b1d8249166feb0b36e58e"
|
resolved "https://registry.yarnpkg.com/yargs/-/yargs-17.5.1.tgz#e109900cab6fcb7fd44b1d8249166feb0b36e58e"
|
||||||
integrity sha512-t6YAJcxDkNX7NFYiVtKvWUz8l+PaKTLiL63mJYWR2GnHq2gjEWISzsLp9wg3aY36dY1j+gfIEL3pIF+XlJJfbA==
|
integrity sha512-t6YAJcxDkNX7NFYiVtKvWUz8l+PaKTLiL63mJYWR2GnHq2gjEWISzsLp9wg3aY36dY1j+gfIEL3pIF+XlJJfbA==
|
||||||
|
|
Ładowanie…
Reference in New Issue