Resolve "[Epic] New landing page"

environments/review-front-927-m6zslj/deployments/2768
Eliot Berriot 2019-09-23 11:14:54 +02:00
rodzic f7a05bda03
commit 297c8f2b67
18 zmienionych plików z 996 dodań i 563 usunięć

Wyświetl plik

@ -0,0 +1 @@
Redesign of the landing and about pages (#872)

Wyświetl plik

@ -62,6 +62,39 @@ For more information about this feature, please check out our documentation:
- `User documentation <https://docs.funkwhale.audio/users/account.html>`_
Landing and about page redesign [Manual action suggested]
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
In this release, we've completely redesigned the landing and about page, by making it more useful and adapted to your pod
configuration. Among other things, the landing page will now include:
- your pod and an excerpt from your pod's description
- your pod banner image, if any
- your contact email, if any
- the login form
- the signup form (if registrations are open on your pod)
- some basic statistics about your pod
- a widget including recently uploaded albums, if anonymous access is enabled
The landing page will still include some information about Funkwhale, but in a less intrusive and proeminent way than before.
Additionally, the about page now includes:
- your pod name, description, rules and terms
- your pod banner image, if any
- your contact email, if any
- comprehensive statistics about your pod
- some info about your pod configuration, such as registration and federation status or the default upload quota for new users
With this redesign, we've added a handful of additional pod settings:
- Pod banner image
- Contact email
- Rules
- Terms of service
We recommend taking a few moments to fill these accordingly to your needs, by visiting ``/manage/settings``.
Allow-list to restrict federation to trusted domains
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

Wyświetl plik

@ -62,13 +62,12 @@ export default {
data () {
return {
bridge: null,
nodeinfo: null,
instanceUrl: null,
showShortcutsModal: false,
showSetInstanceModal: false,
}
},
created () {
async created () {
this.openWebsocket()
let self = this
if (!this.$store.state.ui.selectedLanguage) {
@ -78,7 +77,12 @@ export default {
// used to redraw ago dates every minute
self.$store.commit('ui/computeLastDate')
}, 1000 * 60)
if (!this.$store.state.instance.instanceUrl) {
const urlParams = new URLSearchParams(window.location.search);
const serverUrl = urlParams.get('_server')
if (serverUrl) {
this.$store.commit('instance/instanceUrl', serverUrl)
}
else if (!this.$store.state.instance.instanceUrl) {
// we have several way to guess the API server url. By order of precedence:
// 1. use the url provided in settings.json, if any
// 2. use the url specified when building via VUE_APP_INSTANCE_URL
@ -89,9 +93,9 @@ export default {
// needed to trigger initialization of axios
this.$store.commit('instance/instanceUrl', this.$store.state.instance.instanceUrl)
}
await this.fetchNodeInfo()
this.$store.dispatch('auth/check')
this.$store.dispatch('instance/fetchSettings')
this.fetchNodeInfo()
this.$store.commit('ui/addWebsocketEventHandler', {
eventName: 'inbox.item_added',
id: 'sidebarCount',
@ -152,14 +156,11 @@ export default {
this.$store.commit('ui/incrementNotifications', {type: 'pendingReviewEdits', value: event.pending_review_count})
},
incrementPendingReviewReportsCountInSidebar (event) {
console.log('HELLO', event)
this.$store.commit('ui/incrementNotifications', {type: 'pendingReviewReports', value: event.unresolved_count})
},
fetchNodeInfo () {
let self = this
axios.get('instance/nodeinfo/2.0/').then(response => {
self.nodeinfo = response.data
})
async fetchNodeInfo () {
let response = await axios.get('instance/nodeinfo/2.0/')
this.$store.commit('instance/nodeinfo', response.data)
},
autodetectLanguage () {
let userLanguage = navigator.language || navigator.userLanguage
@ -235,7 +236,8 @@ export default {
},
computed: {
...mapState({
messages: state => state.ui.messages
messages: state => state.ui.messages,
nodeinfo: state => state.instance.nodeinfo,
}),
...mapGetters({
currentTrack: 'queue/currentTrack'

Plik binarny nie jest wyświetlany.

Po

Szerokość:  |  Wysokość:  |  Rozmiar: 118 KiB

Wyświetl plik

@ -1,38 +1,197 @@
<template>
<main class="main pusher" v-title="labels.title">
<section class="ui vertical center aligned stripe segment">
<div class="ui text container">
<h1 class="ui huge header">
<span v-translate="{instance: instance.name.value}" translate-context="Content/About/Title/Short, Noun" v-if="instance.name.value" :translate-params="{instance: instance.name.value}">
About %{ instance }
</span>
<translate translate-context="Content/About/Title" v-else>About this instance</translate>
<main class="main pusher">
<section :class="['ui', 'head', {'with-background': banner}, 'vertical', 'center', 'aligned', 'stripe', 'segment']" :style="headerStyle">
<div class="segment-content">
<h1 class="ui center aligned large header">
<translate translate-context="Content/Home/Header"
:translate-params="{podName: podName}">
About %{ podName }
</translate>
<div v-if="shortDescription" class="sub header">
{{ shortDescription }}
</div>
</h1>
<stats></stats>
</div>
</section>
<section class="ui vertical stripe segment">
<div
class="ui middle aligned stackable text container">
<p
v-if="!instance.short_description.value && !instance.long_description.value"><translate translate-context="Content/About/Paragraph">Unfortunately, the owners of this instance did not yet take the time to complete this page.</translate></p>
<router-link
class="ui button"
v-if="$store.state.auth.availablePermissions['settings']"
:to="{path: '/manage/settings', hash: 'instance'}">
<i class="pencil icon"></i><translate translate-context="Content/Settings/Button.Label/Verb">Edit instance info</translate>
</router-link>
<div class="ui hidden divider"></div>
</div>
<div
v-if="instance.short_description.value"
class="ui middle aligned stackable text container">
<p>{{ instance.short_description.value }}</p>
</div>
<div
v-if="markdown && instance.long_description.value"
class="ui middle aligned stackable text container"
v-html="markdown.makeHtml(instance.long_description.value)">
<div class="ui container">
<div class="ui mobile reversed stackable grid">
<div class="ten wide column">
<div class="ui text container">
<h3 class="ui header" id="description">
<translate translate-context="Content/About/Header">About this pod</translate>
</h3>
<div v-html="markdown.makeHtml(longDescription)" v-if="longDescription"></div>
<p v-else>
<translate translate-context="Content/Home/Paragraph">No description available.</translate>
</p>
<h3 class="ui header" id="rules">
<translate translate-context="Content/About/Header">Rules</translate>
</h3>
<div v-html="markdown.makeHtml(rules)" v-if="rules"></div>
<p v-else>
<translate translate-context="Content/Home/Paragraph">No rules available.</translate>
</p>
<h3 class="ui header" id="terms">
<translate translate-context="Content/About/Header">Terms and privacy policy</translate>
</h3>
<div v-html="markdown.makeHtml(terms)" v-if="terms"></div>
<p v-else>
<translate translate-context="Content/Home/Paragraph">No terms available.</translate>
</p>
</div>
</div>
<div class="six wide column">
<div class="ui raised segment">
<h3 class="ui header">
<translate translate-context="Content/About/Header">Contents</translate>
</h3>
<div class="ui list">
<div class="ui item">
<a href="#description">
<translate translate-context="Content/About/Header">About this pod</translate>
</a>
</div>
<div class="ui item">
<a href="#rules">
<translate translate-context="Content/About/Header">Rules</translate>
</a>
</div>
<div class="ui item">
<a href="#terms">
<translate translate-context="Content/About/Header">Terms and privacy policy</translate>
</a>
</div>
</div>
<template v-if="contactEmail">
<h3 class="header">
<translate translate-context="Content/Home/Header/Name">Contact</translate>
</h3>
<a :href="`mailto:${contactEmail}`">{{ contactEmail }}</a>
</template>
<h3 class="header">
<translate translate-context="Content/About/Header/Name">Pod configuration</translate>
</h3>
<table class="ui very basic table">
<tbody>
<tr v-if="version">
<td>
<translate translate-context="*/*/*">Funkwhale version</translate>
</td>
<td>
{{ version }}
</td>
</tr>
<tr>
<td>
<translate translate-context="*/*/*">Registrations</translate>
</td>
<td v-if="openRegistrations">
<i class="check icon"></i>
<translate translate-context="*/*/*/State of registrations">Open</translate>
</td>
<td v-else>
<i class="x icon"></i>
<translate translate-context="*/*/*/State of registrations">Closed</translate>
</td>
</tr>
<tr>
<td>
<translate translate-context="*/*/*">Upload quota</translate>
</td>
<td v-if="defaultUploadQuota">
{{ defaultUploadQuota * 1000 * 1000 | humanSize }}
</td>
<td v-else>
<translate translate-context="*/*/*">N/A</translate>
</td>
</tr>
<tr>
<td>
<translate translate-context="*/*/*">Federation</translate>
</td>
<td v-if="federationEnabled">
<i class="check icon"></i>
<translate translate-context="*/*/*/State of feature">Enabled</translate>
</td>
<td v-else>
<i class="x icon"></i>
<translate translate-context="*/*/*/State of feature">Disabled</translate>
</td>
</tr>
<tr>
<td>
<translate translate-context="*/*/*">Anonymous access</translate>
</td>
<td v-if="anonymousCanListen">
<i class="check icon"></i>
<translate translate-context="*/*/*/State of feature">Enabled</translate>
</td>
<td v-else>
<i class="x icon"></i>
<translate translate-context="*/*/*/State of feature">Disabled</translate>
</td>
</tr>
<tr>
<td>
<translate translate-context="*/*/*">Allow-list</translate>
</td>
<td v-if="allowListEnabled">
<i class="check icon"></i>
<translate translate-context="*/*/*/State of feature">Enabled</translate>
</td>
<td v-else>
<i class="x icon"></i>
<translate translate-context="*/*/*/State of feature">Disabled</translate>
</td>
</tr>
<tr v-if="allowListDomains">
<td>
<translate translate-context="*/*/*">Allowed domains</translate>
</td>
<td>
<translate :translate-n="allowListDomains.length" translate-plural="%{ count } allowed domains" :translate-params="{count: allowListDomains.length}" translate-context="*/*/*">%{ count } allowed domains</translate>
<br>
<a @click.prevent="showAllowedDomains = !showAllowedDomains">
<translate v-if="showAllowedDomains" key="1" translate-context="*/*/*/Verb">Hide</translate>
<translate v-else key="2" translate-context="*/*/*/Verb">Show</translate>
</a>
<ul class="ui list" v-if="showAllowedDomains">
<li v-for="domain in allowListDomains" :key="domain">
<a :href="`https://${domain}`" target="_blank" rel="noopener">{{ domain }}</a>
</li>
</ul>
</td>
</tr>
</tbody>
</table>
<template v-if="stats">
<h3 class="header">
<translate translate-context="Content/Home/Header">Statistics</translate>
</h3>
<p>
<i class="user grey icon"></i><translate translate-context="Content/Home/Stat" :translate-params="{count: stats.users.toLocaleString($store.state.ui.momentLocale) }" :translate-n="stats.users" translate-plural="%{ count } active users">%{ count } active user</translate>
</p>
<p>
<i class="music grey icon"></i><translate translate-context="Content/Home/Stat" :translate-params="{count: parseInt(stats.hours).toLocaleString($store.state.ui.momentLocale)}" :translate-n="parseInt(stats.hours)" translate-plural="%{ count } hours of music">%{ count } hour of music</translate>
</p>
<p v-if="stats.artists">
<i class="users grey icon"></i><translate translate-context="Content/Home/Stat" :translate-params="{count: stats.artists.toLocaleString($store.state.ui.momentLocale) }" :translate-n="stats.artists" translate-plural="%{ count } artists">%{ count } artists</translate>
</p>
<p v-if="stats.albums">
<i class="headphones grey icon"></i><translate translate-context="Content/Home/Stat" :translate-params="{count: stats.albums.toLocaleString($store.state.ui.momentLocale) }" :translate-n="stats.albums" translate-plural="%{ count } albums">%{ count } albums</translate>
</p>
<p v-if="stats.tracks">
<i class="file grey icon"></i><translate translate-context="Content/Home/Stat" :translate-params="{count: stats.tracks.toLocaleString($store.state.ui.momentLocale) }" :translate-n="stats.tracks" translate-plural="%{ count } tracks">%{ count } tracks</translate>
</p>
<p v-if="stats.listenings">
<i class="play grey icon"></i><translate translate-context="Content/Home/Stat" :translate-params="{count: stats.listenings.toLocaleString($store.state.ui.momentLocale) }" :translate-n="stats.listenings" translate-plural="%{ count } listenings">%{ count } listenings</translate>
</p>
</template>
</div>
</div>
</div>
</div>
</section>
</main>
@ -40,37 +199,122 @@
<script>
import { mapState } from "vuex"
import Stats from "@/components/instance/Stats"
import _ from '@/lodash'
import showdown from 'showdown'
export default {
components: {
Stats
},
data () {
return {
markdown: null
markdown: new showdown.Converter(),
showAllowedDomains: false,
}
},
created () {
this.$store.dispatch("instance/fetchSettings")
let self = this
import('showdown').then(module => {
self.markdown = new module.default.Converter()
})
},
computed: {
...mapState({
instance: state => state.instance.settings.instance
...mapState({
nodeinfo: state => state.instance.nodeinfo,
}),
labels() {
return {
title: this.$pgettext('Content/About/Title', "About this instance")
podName() {
return _.get(this.nodeinfo, 'metadata.nodeName') || "Funkwhale"
},
banner () {
return _.get(this.nodeinfo, 'metadata.banner')
},
shortDescription () {
return _.get(this.nodeinfo, 'metadata.shortDescription')
},
longDescription () {
return _.get(this.nodeinfo, 'metadata.longDescription')
},
rules () {
return _.get(this.nodeinfo, 'metadata.rules')
},
terms () {
return _.get(this.nodeinfo, 'metadata.terms')
},
stats () {
let data = {
users: _.get(this.nodeinfo, 'usage.users.activeMonth', null),
hours: _.get(this.nodeinfo, 'metadata.library.music.hours', null),
artists: _.get(this.nodeinfo, 'metadata.library.artists.total', null),
albums: _.get(this.nodeinfo, 'metadata.library.albums.total', null),
tracks: _.get(this.nodeinfo, 'metadata.library.tracks.total', null),
listenings: _.get(this.nodeinfo, 'metadata.usage.listenings.total', null),
}
}
if (data.users === null || data.artists === null) {
return
}
return data
},
contactEmail () {
return _.get(this.nodeinfo, 'metadata.contactEmail')
},
anonymousCanListen () {
return _.get(this.nodeinfo, 'metadata.library.anonymousCanListen')
},
allowListEnabled () {
return _.get(this.nodeinfo, 'metadata.allowList.enabled')
},
allowListDomains () {
return _.get(this.nodeinfo, 'metadata.allowList.domains')
},
version () {
return _.get(this.nodeinfo, 'software.version')
},
openRegistrations () {
return _.get(this.nodeinfo, 'openRegistrations')
},
defaultUploadQuota () {
return _.get(this.nodeinfo, 'metadata.defaultUploadQuota')
},
federationEnabled () {
return _.get(this.nodeinfo, 'metadata.library.federationEnabled')
},
headerStyle() {
if (!this.banner) {
return ""
}
return (
"background-image: url(" +
this.$store.getters["instance/absoluteUrl"](this.banner) +
")"
)
},
}
}
</script>
<!-- Add "scoped" attribute to limit CSS to this component only -->
<style scoped>
<!-- Add "scoped" attribute to limit CSS to this component only -->
<style scoped lang="scss">
.ui.list .list.icon {
padding: 0;
}
h1.header, h1 .sub.header {
text-shadow: 0 2px 0 rgba(0,0,0,.8);
color: #fff !important;
}
h1.ui.header {
font-size: 3em;
}
h1.ui.header .sub.header {
font-size: 0.8em;
}
.main.pusher {
margin-top: 0;
min-height: 10em;
}
section.segment.head {
padding: 8em 3em;
background: linear-gradient(90deg, rgba(40,88,125,1) 0%, rgba(64,130,180,1) 100%);
background-repeat: no-repeat;
background-size: cover;
}
#pod {
font-size: 110%;
display: block;
}
</style>

Wyświetl plik

@ -1,152 +1,276 @@
<template>
<main class="main pusher" v-title="labels.title">
<section class="ui vertical center aligned stripe segment">
<div class="ui text container">
<h1 class="ui huge header">
<translate translate-context="Content/Home/Title/Verb">Welcome to Funkwhale</translate>
<section :class="['ui', 'head', {'with-background': banner}, 'vertical', 'center', 'aligned', 'stripe', 'segment']" :style="headerStyle">
<div class="segment-content">
<h1 class="ui center aligned large header">
<translate translate-context="Content/Home/Header"
:translate-params="{podName: podName}">
Welcome to %{ podName }!
</translate>
<div v-if="shortDescription" class="sub header">
{{ shortDescription }}
</div>
</h1>
<p><translate translate-context="Content/Home/Title">We think listening to music should be simple.</translate></p>
<router-link class="ui icon button" to="/about">
<i class="info icon"></i>
<translate translate-context="Content/Home/Button.Label/Verb">Learn more about this instance</translate>
</router-link>
<router-link class="ui icon teal button" to="/library">
<translate translate-context="Content/Home/Button.Label/Verb">Get me to the library</translate>
<i class="right arrow icon"></i>
</router-link>
</div>
</section>
<section class="ui vertical stripe segment">
<div class="ui middle aligned stackable text container">
<div class="ui grid">
<div class="row">
<div class="eight wide left floated column">
<h2 class="ui header">
<translate translate-context="Content/Home/Title">Why funkwhale?</translate>
</h2>
<p><translate translate-context="Content/Home/Paragraph">That's simple: we loved Grooveshark and we want to build something even better.</translate></p>
<div class="ui stackable grid">
<div class="ten wide column">
<h3 class="header">
<translate translate-context="Content/Home/Header">About this Funkwhale pod</translate>
</h3>
<div class="ui raised segment" id="pod">
<div class="ui stackable grid">
<div class="eight wide column">
<p v-if="!truncatedDescription">
<translate translate-context="Content/Home/Paragraph">No description available.</translate>
</p>
<template v-if="truncatedDescription || rules">
<div v-if="truncatedDescription" v-html="truncatedDescription"></div>
<div v-if="truncatedDescription" class="ui hidden divider"></div>
<div class="ui relaxed list">
<div class="item" v-if="truncatedDescription">
<i class="arrow right grey icon"></i>
<div class="content">
<router-link class="ui link" :to="{name: 'about'}">
<translate translate-context="Content/Home/Link">Learn more</translate>
</router-link>
</div>
</div>
<div class="item" v-if="rules">
<i class="book open grey icon"></i>
<div class="content">
<router-link class="ui link" v-if="rules" :to="{name: 'about', hash: '#rules'}">
<translate translate-context="Content/Home/Link">Server rules</translate>
</router-link>
</div>
</div>
</div>
</template>
</div>
<div class="eight wide column">
<template v-if="stats">
<h3 class="sub header">
<translate translate-context="Content/Home/Header">Statistics</translate>
</h3>
<p>
<i class="user grey icon"></i><translate translate-context="Content/Home/Stat" :translate-params="{count: stats.users.toLocaleString($store.state.ui.momentLocale) }" :translate-n="stats.users" translate-plural="%{ count } active users">%{ count } active user</translate>
</p>
<p>
<i class="music grey icon"></i><translate translate-context="Content/Home/Stat" :translate-params="{count: parseInt(stats.hours).toLocaleString($store.state.ui.momentLocale)}" :translate-n="parseInt(stats.hours)" translate-plural="%{ count } hours of music">%{ count } hour of music</translate>
</p>
</template>
<template v-if="contactEmail">
<h3 class="sub header">
<translate translate-context="Content/Home/Header/Name">Contact</translate>
</h3>
<i class="at grey icon"></i>
<a :href="`mailto:${contactEmail}`">{{ contactEmail }}</a>
</template>
</div>
</div>
<div class="four wide left floated column">
<img class="ui medium image" src="../assets/logo/logo.png" />
</div>
</div>
</div>
</div>
<div class="ui middle aligned stackable text container">
<div class="ui hidden divider"></div>
<h2 class="ui header">
<translate translate-context="Content/Home/Title">Unlimited music</translate>
</h2>
<p><translate translate-context="Content/Home/Paragraph">Funkwhale is designed to make it easy to listen to music you like, or to discover new artists.</translate></p>
<div class="ui list">
<div class="item">
<i class="sound icon"></i>
<div class="content">
<translate translate-context="Content/Home/List item/Verb">Click once, listen for hours using built-in radios</translate>
</div>
</div>
<div class="item">
<i class="heart icon"></i>
<div class="content">
<translate translate-context="Content/Home/List item/Verb">Keep track of your favorite songs</translate>
</div>
</div>
<div class="item">
<i class="list icon"></i>
<div class="content">
<translate translate-context="Content/Home/List item">Playlists? We've got them</translate>
</div>
</div>
</div>
</div>
<div class="ui middle aligned stackable text container">
<div class="ui hidden divider"></div>
<h2 class="ui header">
<translate translate-context="Content/Home/Title">A clean library</translate>
</h2>
<p><translate translate-context="Content/Home/Paragraph">Funkwhale takes care of handling your music</translate>.</p>
<div class="ui list">
<div class="item">
<i class="tag icon"></i>
<div class="content" v-html="musicbrainzItem"></div>
</div>
<div class="item">
<i class="plus icon"></i>
<div class="content">
<translate translate-context="Content/Home/List item">Covers, lyrics... our goal is to have them all ;)</translate>
</div>
</div>
</div>
</div>
<div class="ui middle aligned stackable text container">
<div class="ui hidden divider"></div>
<h2 class="ui header">
<translate translate-context="Content/Home/Title">Easy to use</translate>
</h2>
<p><translate translate-context="Content/Home/Paragraph">Funkwhale is dead simple to use.</translate></p>
<div class="ui list">
<div class="item">
<i class="book icon"></i>
<div class="content">
<translate translate-context="Content/Home/List item">No add-ons, no plugins... you only need a web library</translate>
</div>
</div>
<div class="item">
<i class="wizard icon"></i>
<div class="content">
<translate translate-context="Content/Home/List item">Access your music from a clean interface that focuses on what really matters</translate>
</div>
</div>
</div>
</div>
<div class="ui middle aligned stackable text container">
<div class="ui hidden divider"></div>
<h2 class="ui header">
<translate translate-context="Content/Home/Title">Your music, your way</translate>
</h2>
<p><translate translate-context="Content/Home/Paragraph">Funkwhale is free and gives you control over your music.</translate></p>
<div class="ui list">
<div class="item">
<i class="smile icon"></i>
<div class="content">
<translate translate-context="Content/Home/List item">The platform is free and open-source, you can install it and modify it without worries</translate>
</div>
</div>
<div class="item">
<i class="protect icon"></i>
<div class="content">
<translate translate-context="Content/Home/List item">We do not track you or bother you with ads</translate>
</div>
</div>
<div class="item">
<i class="users icon"></i>
<div class="content">
<translate translate-context="Content/Home/List item">You can invite friends and family to your instance so they can enjoy your music</translate>
</div>
</div>
<div class="six wide column">
<img class="ui image" src="../assets/network.png" />
</div>
</div>
<div class="ui hidden divider"></div>
<div class="ui hidden divider"></div>
<div class="ui stackable grid">
<div class="four wide column">
<h3 class="header">
<translate translate-context="Content/Home/Header">About Funkwhale</translate>
</h3>
<p v-translate translate-context="Content/Home/Paragraph">This pod runs Funkwhale, a community-driven project that lets you listen and share music and audio within a decentralized, open network.</p>
<p v-translate translate-context="Content/Home/Paragraph">Funkwhale is free and developped by a friendly community of volunteers.</p>
<a target="_blank" rel="noopener" href="https://funkwhale.audio">
<i class="external alternate icon"></i>
<translate translate-context="Content/Home/Link">Visit funkwhale.audio</translate>
</a>
</div>
<div class="four wide column">
<h3 class="header">
<translate translate-context="Head/Login/Title">Log In</translate>
</h3>
<login-form button-classes="basic green" :show-signup="false"></login-form>
<div class="ui hidden clearing divider"></div>
</div>
<div class="four wide column">
<h3 class="header">
<translate translate-context="*/Signup/Title">Sign up</translate>
</h3>
<template v-if="openRegistrations">
<p>
<translate translate-context="Content/Home/Paragraph">Sign up now to keep a track of your favorites, create playlists, discover new content and much more!</translate>
</p>
<p v-if="defaultUploadQuota">
<translate translate-context="Content/Home/Paragraph" :translate-params="{quota: humanSize(defaultUploadQuota * 1000 * 1000)}">Users on this pod also get %{ quota } of free storage to upload their own content!</translate>
</p>
<signup-form button-classes="basic green" :show-login="false"></signup-form>
</template>
<div v-else>
<p translate-context="Content/Home/Paragraph">Registrations are closed on this pod. You can signup on another pod using the link below.</p>
<a target="_blank" rel="noopener" href="https://funkwhale.audio/#get-started">
<i class="external alternate icon"></i>
<translate translate-context="Content/Home/Link">Find another pod</translate>
</a>
</div>
</div>
<div class="four wide column">
<h3 class="header">
<translate translate-context="Content/Home/Header">Useful links</translate>
</h3>
<div class="ui relaxed list">
<div class="item">
<i class="headphones icon"></i>
<div class="content">
<router-link v-if="anonymousCanListen" class="header" to="/library">
<translate translate-context="Content/Home/Link">Browse public content</translate>
</router-link>
<div class="description">
<translate translate-context="Content/Home/Link">Listen to public albums and playlists shared on this pod</translate>
</div>
</div>
</div>
<div class="item">
<i class="mobile alternate icon"></i>
<div class="content">
<a class="header" href="https://funkwhale.audio/apps" target="_blank" rel="noopener">
<translate translate-context="Content/Home/Link">Mobile apps</translate>
</a>
<div class="description">
<translate translate-context="Content/Home/Link">Use Funkwhale on other devices with our apps</translate>
</div>
</div>
</div>
<div class="item">
<i class="book icon"></i>
<div class="content">
<a class="header" href="https://docs.funkwhale.audio/users/index.html" target="_blank" rel="noopener">
<translate translate-context="Content/Home/Link">User guides</translate>
</a>
<div class="description">
<translate translate-context="Content/Home/Link">Discover everything you need to know about Funkwhale and its features</translate>
</div>
</div>
</div>
</div>
</div>
</div>
</section>
<section v-if="anonymousCanListen" class="ui vertical stripe segment">
<album-widget :filters="{playable: true, ordering: '-creation_date'}" :limit="10">
<template slot="title"><translate translate-context="Content/Home/Title">Recently added albums</translate></template>
<router-link to="/library">
<translate translate-context="Content/Home/Link">View more</translate>
<div class="ui hidden divider"></div>
</router-link>
</album-widget>
</section>
</main>
</template>
<script>
import $ from 'jquery'
import _ from '@/lodash'
import {mapState} from 'vuex'
import showdown from 'showdown'
import AlbumWidget from "@/components/audio/album/Widget"
import LoginForm from "@/components/auth/LoginForm"
import SignupForm from "@/components/auth/SignupForm"
import {humanSize } from '@/filters'
export default {
data() {
components: {
AlbumWidget,
LoginForm,
SignupForm,
},
data () {
return {
musicbrainzUrl: "https://musicbrainz.org/"
markdown: new showdown.Converter(),
excerptLength: 2, // html nodes,
humanSize
}
},
computed: {
...mapState({
nodeinfo: state => state.instance.nodeinfo,
}),
labels() {
return {
title: this.$pgettext('Head/Home/Title', "Welcome")
}
},
musicbrainzItem () {
let msg = this.$pgettext('Content/Home/List item/Verb', 'Get quality metadata about your music thanks to <a href="%{ url }" target="_blank">MusicBrainz</a>')
return this.$gettextInterpolate(msg, {url: this.musicbrainzUrl})
}
podName() {
return _.get(this.nodeinfo, 'metadata.nodeName') || "Funkwhale"
},
banner () {
return _.get(this.nodeinfo, 'metadata.banner')
},
shortDescription () {
return _.get(this.nodeinfo, 'metadata.shortDescription')
},
longDescription () {
return _.get(this.nodeinfo, 'metadata.longDescription')
},
rules () {
return _.get(this.nodeinfo, 'metadata.rules')
},
truncatedDescription () {
if (!this.longDescription) {
return
}
let doc = this.markdown.makeHtml(this.longDescription)
let nodes = $.parseHTML(doc)
let excerptParts = []
let handled = 0
nodes.forEach((n) => {
let content = n.innerHTML || n.nodeValue
if (handled < this.excerptLength && content.trim()) {
excerptParts.push(n)
handled += 1
}
})
return excerptParts.map((p) => { return p.outerHTML }).join('')
},
stats () {
let data = {
users: _.get(this.nodeinfo, 'usage.users.activeMonth', null),
hours: _.get(this.nodeinfo, 'metadata.library.music.hours', null),
}
if (data.users === null || data.artists === null) {
return
}
return data
},
contactEmail () {
return _.get(this.nodeinfo, 'metadata.contactEmail')
},
defaultUploadQuota () {
return _.get(this.nodeinfo, 'metadata.defaultUploadQuota')
},
anonymousCanListen () {
return _.get(this.nodeinfo, 'metadata.library.anonymousCanListen')
},
openRegistrations () {
return _.get(this.nodeinfo, 'openRegistrations')
},
headerStyle() {
if (!this.banner) {
return ""
}
return (
"background-image: url(" +
this.$store.getters["instance/absoluteUrl"](this.banner) +
")"
)
},
},
watch: {
'$store.state.auth.authenticated': {
@ -164,11 +288,34 @@ export default {
</script>
<!-- Add "scoped" attribute to limit CSS to this component only -->
<style scoped>
.stripe p {
font-size: 120%;
}
<style scoped lang="scss">
.ui.list .list.icon {
padding: 0;
}
h1.header, h1 .sub.header {
text-shadow: 0 2px 0 rgba(0,0,0,.8);
color: #fff !important;
}
h1.ui.header {
font-size: 3em;
}
h1.ui.header .sub.header {
font-size: 0.8em;
}
.main.pusher {
margin-top: 0;
min-height: 10em;
}
section.segment.head {
padding: 8em 3em;
background: linear-gradient(90deg, rgba(40,88,125,1) 0%, rgba(64,130,180,1) 100%);
background-repeat: no-repeat;
background-size: cover;
}
#pod {
font-size: 110%;
display: block;
}
</style>

Wyświetl plik

@ -4,6 +4,7 @@
<slot name="title"></slot>
<span v-if="showCount" class="ui tiny circular label">{{ count }}</span>
</h3>
<slot></slot>
<button v-if="controls" :disabled="!previousPage" @click="fetchData(previousPage)" :class="['ui', {disabled: !previousPage}, 'circular', 'icon', 'basic', 'button']"><i :class="['ui', 'angle left', 'icon']"></i></button>
<button v-if="controls" :disabled="!nextPage" @click="fetchData(nextPage)" :class="['ui', {disabled: !nextPage}, 'circular', 'icon', 'basic', 'button']"><i :class="['ui', 'angle right', 'icon']"></i></button>
<button v-if="controls" @click="fetchData('albums/')" :class="['ui', 'circular', 'icon', 'basic', 'button']"><i :class="['ui', 'refresh', 'icon']"></i></button>
@ -48,6 +49,7 @@ export default {
filters: {type: Object, required: true},
controls: {type: Boolean, default: true},
showCount: {type: Boolean, default: false},
limit: {type: Number, default: 12},
},
components: {
PlayButton
@ -55,7 +57,6 @@ export default {
data () {
return {
albums: [],
limit: 12,
count: 0,
isLoading: false,
errors: null,

Wyświetl plik

@ -1,122 +0,0 @@
<template>
<main class="main pusher" v-title="labels.title">
<section class="ui vertical stripe segment">
<div class="ui small text container">
<h2><translate translate-context="Content/Login/Title/Verb">Log in to your Funkwhale account</translate></h2>
<form class="ui form" @submit.prevent="submit()">
<div v-if="error" class="ui negative message">
<div class="header"><translate translate-context="Content/Login/Error message.Title">We cannot log you in</translate></div>
<ul class="list">
<li v-if="error == 'invalid_credentials'"><translate translate-context="Content/Login/Error message.List item/Call to action">Please double-check your username/password couple is correct</translate></li>
<li v-else>{{ error }}</li>
</ul>
</div>
<div class="field">
<label>
<translate translate-context="Content/Login/Input.Label/Noun">Username or email</translate> |
<router-link :to="{path: '/signup'}">
<translate translate-context="*/Signup/Link/Verb">Create an account</translate>
</router-link>
</label>
<input
ref="username"
tabindex="1"
required
name="username"
type="text"
autofocus
:placeholder="labels.usernamePlaceholder"
v-model="credentials.username"
>
</div>
<div class="field">
<label>
<translate translate-context="Content/*/Input.Label">Password</translate> |
<router-link :to="{name: 'auth.password-reset', query: {email: credentials.username}}">
<translate translate-context="*/Login/*/Verb">Reset your password</translate>
</router-link>
</label>
<password-input :index="2" required v-model="credentials.password" />
</div>
<button tabindex="3" :class="['ui', {'loading': isLoading}, 'right', 'floated', 'green', 'button']" type="submit">
<translate translate-context="*/Login/*/Verb">Login</translate>
</button>
</form>
</div>
</section>
</main>
</template>
<script>
import PasswordInput from "@/components/forms/PasswordInput"
export default {
props: {
next: { type: String, default: "/library" }
},
components: {
PasswordInput
},
data() {
return {
// We need to initialize the component with any
// properties that will be used in it
credentials: {
username: "",
password: ""
},
error: "",
isLoading: false
}
},
created () {
if (this.$store.state.auth.authenticated) {
this.$router.push(this.next)
}
},
mounted() {
this.$refs.username.focus()
},
computed: {
labels() {
let usernamePlaceholder = this.$pgettext('Content/Login/Input.Placeholder', "Enter your username or email")
let title = this.$pgettext('Head/Login/Title', "Log In")
return {
usernamePlaceholder,
title
}
}
},
methods: {
submit() {
var self = this
self.isLoading = true
this.error = ""
var credentials = {
username: this.credentials.username,
password: this.credentials.password
}
this.$store
.dispatch("auth/login", {
credentials,
next: this.next,
onError: error => {
if (error.response.status === 400) {
self.error = "invalid_credentials"
} else {
self.error = error.backendErrors[0]
}
}
})
.then(e => {
self.isLoading = false
})
}
}
}
</script>
<!-- Add "scoped" attribute to limit CSS to this component only -->
<style scoped>
</style>

Wyświetl plik

@ -0,0 +1,118 @@
<template>
<form class="ui form" @submit.prevent="submit()">
<div v-if="error" class="ui negative message">
<div class="header"><translate translate-context="Content/Login/Error message.Title">We cannot log you in</translate></div>
<ul class="list">
<li v-if="error == 'invalid_credentials'"><translate translate-context="Content/Login/Error message.List item/Call to action">Please double-check your username/password couple is correct</translate></li>
<li v-else>{{ error }}</li>
</ul>
</div>
<div class="field">
<label>
<translate translate-context="Content/Login/Input.Label/Noun">Username or email</translate>
<template v-if="showSignup">
|
<router-link :to="{path: '/signup'}">
<translate translate-context="*/Signup/Link/Verb">Create an account</translate>
</router-link>
</template>
</label>
<input
ref="username"
tabindex="1"
required
name="username"
type="text"
autofocus
:placeholder="labels.usernamePlaceholder"
v-model="credentials.username"
>
</div>
<div class="field">
<label>
<translate translate-context="Content/*/Input.Label">Password</translate> |
<router-link :to="{name: 'auth.password-reset', query: {email: credentials.username}}">
<translate translate-context="*/Login/*/Verb">Reset your password</translate>
</router-link>
</label>
<password-input :index="2" required v-model="credentials.password" />
</div>
<button tabindex="3" :class="['ui', {'loading': isLoading}, 'right', 'floated', buttonClasses, 'button']" type="submit">
<translate translate-context="*/Login/*/Verb">Login</translate>
</button>
</form>
</template>
<script>
import PasswordInput from "@/components/forms/PasswordInput"
export default {
props: {
next: { type: String, default: "/library" },
buttonClasses: { type: String, default: "green" },
showSignup: { type: Boolean, default: true},
},
components: {
PasswordInput
},
data() {
return {
// We need to initialize the component with any
// properties that will be used in it
credentials: {
username: "",
password: ""
},
error: "",
isLoading: false
}
},
created () {
if (this.$store.state.auth.authenticated) {
this.$router.push(this.next)
}
},
mounted() {
this.$refs.username.focus()
},
computed: {
labels() {
let usernamePlaceholder = this.$pgettext('Content/Login/Input.Placeholder', "Enter your username or email")
return {
usernamePlaceholder,
}
}
},
methods: {
submit() {
var self = this
self.isLoading = true
this.error = ""
var credentials = {
username: this.credentials.username,
password: this.credentials.password
}
this.$store
.dispatch("auth/login", {
credentials,
next: this.next,
onError: error => {
if (error.response.status === 400) {
self.error = "invalid_credentials"
} else {
self.error = error.backendErrors[0]
}
}
})
.then(e => {
self.isLoading = false
})
}
}
}
</script>
<!-- Add "scoped" attribute to limit CSS to this component only -->
<style scoped>
</style>

Wyświetl plik

@ -1,146 +0,0 @@
<template>
<main class="main pusher" v-title="labels.title">
<section class="ui vertical stripe segment">
<div class="ui small text container">
<h2><translate translate-context="Content/Signup/Title">Create a funkwhale account</translate></h2>
<form
:class="['ui', {'loading': isLoadingInstanceSetting}, 'form']"
@submit.prevent="submit()">
<p class="ui message" v-if="!$store.state.instance.settings.users.registration_enabled.value">
<translate translate-context="Content/Signup/Form/Paragraph">Public registrations are not possible on this instance. You will need an invitation code to sign up.</translate>
</p>
<div v-if="errors.length > 0" class="ui negative message">
<div class="header"><translate translate-context="Content/Signup/Form/Paragraph">Your account cannot be created.</translate></div>
<ul class="list">
<li v-for="error in errors">{{ error }}</li>
</ul>
</div>
<div class="field">
<label><translate translate-context="Content/*/*">Username</translate></label>
<input
ref="username"
name="username"
required
type="text"
autofocus
:placeholder="labels.usernamePlaceholder"
v-model="username">
</div>
<div class="field">
<label><translate translate-context="Content/*/*/Noun">Email</translate></label>
<input
ref="email"
name="email"
required
type="email"
:placeholder="labels.emailPlaceholder"
v-model="email">
</div>
<div class="field">
<label><translate translate-context="Content/*/Input.Label">Password</translate></label>
<password-input v-model="password" />
</div>
<div class="field" v-if="!$store.state.instance.settings.users.registration_enabled.value">
<label><translate translate-context="Content/*/Input.Label">Invitation code</translate></label>
<input
required
type="text"
name="invitation"
:placeholder="labels.placeholder"
v-model="invitation">
</div>
<button :class="['ui', 'green', {'loading': isLoading}, 'button']" type="submit">
<translate translate-context="Content/Signup/Button.Label">Create my account</translate>
</button>
</form>
</div>
</section>
</main>
</template>
<script>
import axios from "axios"
import logger from "@/logging"
import PasswordInput from "@/components/forms/PasswordInput"
export default {
props: {
defaultInvitation: { type: String, required: false, default: null },
next: { type: String, default: "/" }
},
components: {
PasswordInput
},
data() {
return {
username: "",
email: "",
password: "",
isLoadingInstanceSetting: true,
errors: [],
isLoading: false,
invitation: this.defaultInvitation
}
},
created() {
let self = this
this.$store.dispatch("instance/fetchSettings", {
callback: function() {
self.isLoadingInstanceSetting = false
}
})
},
computed: {
labels() {
let title = this.$pgettext("*/Signup/Title", "Sign Up")
let placeholder = this.$pgettext(
"Content/Signup/Form/Placeholder",
"Enter your invitation code (case insensitive)"
)
let usernamePlaceholder = this.$pgettext("Content/Signup/Form/Placeholder", "Enter your username")
let emailPlaceholder = this.$pgettext("Content/Signup/Form/Placeholder", "Enter your email")
return {
title,
usernamePlaceholder,
emailPlaceholder,
placeholder
}
}
},
methods: {
submit() {
var self = this
self.isLoading = true
this.errors = []
var payload = {
username: this.username,
password1: this.password,
password2: this.password,
email: this.email,
invitation: this.invitation
}
return axios.post("auth/registration/", payload).then(
response => {
logger.default.info("Successfully created account")
self.$router.push({
name: "profile",
params: {
username: this.username
}
})
},
error => {
self.errors = error.backendErrors
self.isLoading = false
}
)
}
}
}
</script>
<!-- Add "scoped" attribute to limit CSS to this component only -->
<style scoped>
</style>

Wyświetl plik

@ -0,0 +1,138 @@
<template>
<form
:class="['ui', {'loading': isLoadingInstanceSetting}, 'form']"
@submit.prevent="submit()">
<p class="ui message" v-if="!$store.state.instance.settings.users.registration_enabled.value">
<translate translate-context="Content/Signup/Form/Paragraph">Public registrations are not possible on this instance. You will need an invitation code to sign up.</translate>
</p>
<div v-if="errors.length > 0" class="ui negative message">
<div class="header"><translate translate-context="Content/Signup/Form/Paragraph">Your account cannot be created.</translate></div>
<ul class="list">
<li v-for="error in errors">{{ error }}</li>
</ul>
</div>
<div class="field">
<label><translate translate-context="Content/*/*">Username</translate></label>
<input
ref="username"
name="username"
required
type="text"
autofocus
:placeholder="labels.usernamePlaceholder"
v-model="username">
</div>
<div class="field">
<label><translate translate-context="Content/*/*/Noun">Email</translate></label>
<input
ref="email"
name="email"
required
type="email"
:placeholder="labels.emailPlaceholder"
v-model="email">
</div>
<div class="field">
<label><translate translate-context="Content/*/Input.Label">Password</translate></label>
<password-input v-model="password" />
</div>
<div class="field" v-if="!$store.state.instance.settings.users.registration_enabled.value">
<label><translate translate-context="Content/*/Input.Label">Invitation code</translate></label>
<input
required
type="text"
name="invitation"
:placeholder="labels.placeholder"
v-model="invitation">
</div>
<button :class="['ui', buttonClasses, {'loading': isLoading}, ' right floated button']" type="submit">
<translate translate-context="Content/Signup/Button.Label">Create my account</translate>
</button>
</form>
</template>
<script>
import axios from "axios"
import logger from "@/logging"
import PasswordInput from "@/components/forms/PasswordInput"
export default {
props: {
defaultInvitation: { type: String, required: false, default: null },
next: { type: String, default: "/" },
buttonClasses: { type: String, default: "green" },
},
components: {
PasswordInput
},
data() {
return {
username: "",
email: "",
password: "",
isLoadingInstanceSetting: true,
errors: [],
isLoading: false,
invitation: this.defaultInvitation
}
},
created() {
let self = this
this.$store.dispatch("instance/fetchSettings", {
callback: function() {
self.isLoadingInstanceSetting = false
}
})
},
computed: {
labels() {
let placeholder = this.$pgettext(
"Content/Signup/Form/Placeholder",
"Enter your invitation code (case insensitive)"
)
let usernamePlaceholder = this.$pgettext("Content/Signup/Form/Placeholder", "Enter your username")
let emailPlaceholder = this.$pgettext("Content/Signup/Form/Placeholder", "Enter your email")
return {
usernamePlaceholder,
emailPlaceholder,
placeholder
}
}
},
methods: {
submit() {
var self = this
self.isLoading = true
this.errors = []
var payload = {
username: this.username,
password1: this.password,
password2: this.password,
email: this.email,
invitation: this.invitation
}
return axios.post("auth/registration/", payload).then(
response => {
logger.default.info("Successfully created account")
self.$router.push({
name: "profile",
params: {
username: this.username
}
})
},
error => {
self.errors = error.backendErrors
self.isLoading = false
}
)
}
}
}
</script>
<!-- Add "scoped" attribute to limit CSS to this component only -->
<style scoped>
</style>

Wyświetl plik

@ -1,101 +0,0 @@
<template>
<div>
<div v-if="stats && stats.trackFavorites !== undefined" class="ui stackable two column grid">
<div class="column">
<h3 class="ui left aligned header">
<translate translate-context="Content/About/Title/Noun">User activity</translate>
</h3>
<div v-if="stats" class="ui mini horizontal statistics">
<div class="statistic">
<div class="value">
<i class="green user icon"></i>
{{ stats.users.toLocaleString($store.state.ui.momentLocale) }}
</div>
<div class="label"><translate translate-context="Content/About/Paragraph/Unit">users</translate></div>
</div>
<div class="statistic">
<div class="value">
<i class="orange sound icon"></i> {{ stats.listenings.toLocaleString($store.state.ui.momentLocale) }}
</div>
<div class="label"><translate translate-context="Content/About/Paragraph/Unit">tracks listened</translate></div>
</div>
<div class="statistic">
<div class="value">
<i class="pink heart icon"></i> {{ stats.trackFavorites.toLocaleString($store.state.ui.momentLocale) }}
</div>
<div class="label"><translate translate-context="Content/About/Paragraph/Unit">Tracks favorited</translate></div>
</div>
</div>
</div>
<div class="column">
<h3 class="ui left aligned header"><translate translate-context="*/*/*">Library</translate></h3>
<div class="ui mini horizontal statistics">
<div class="statistic">
<div class="value">
{{ stats.musicDuration.toLocaleString($store.state.ui.momentLocale) }}
</div>
<div class="label"><translate translate-context="Content/About/Paragraph/Unit">Hours of music</translate></div>
</div>
<div class="statistic">
<div class="value">
{{ stats.artists.toLocaleString($store.state.ui.momentLocale) }}
</div>
<div class="label"><translate translate-context="*/*/*/Noun">Artists</translate></div>
</div>
<div class="statistic">
<div class="value">
{{ stats.albums.toLocaleString($store.state.ui.momentLocale) }}
</div>
<div class="label"><translate translate-context="*/*/*">Albums</translate></div>
</div>
<div class="statistic">
<div class="value">
{{ stats.tracks.toLocaleString($store.state.ui.momentLocale) }}
</div>
<div class="label"><translate translate-context="*/*/*/Noun">Tracks</translate></div>
</div>
</div>
</div>
</div>
</div>
</template>
<script>
import _ from '@/lodash'
import axios from 'axios'
import logger from '@/logging'
export default {
data () {
return {
stats: null
}
},
created () {
this.fetchData()
},
methods: {
fetchData () {
var self = this
this.isLoading = true
logger.default.debug('Fetching instance stats...')
axios.get('instance/nodeinfo/2.0/').then((response) => {
let d = response.data
self.stats = {}
self.stats.users = _.get(d, 'usage.users.total')
self.stats.listenings = _.get(d, 'metadata.usage.listenings.total')
self.stats.trackFavorites = _.get(d, 'metadata.usage.favorites.tracks.total')
self.stats.musicDuration = Math.round(_.get(d, 'metadata.library.music.hours'))
self.stats.artists = _.get(d, 'metadata.library.artists.total')
self.stats.albums = _.get(d, 'metadata.library.albums.total')
self.stats.tracks = _.get(d, 'metadata.library.tracks.total')
self.isLoading = false
})
},
}
}
</script>
<!-- Add "scoped" attribute to limit CSS to this component only -->
<style scoped>
</style>

Wyświetl plik

@ -8,6 +8,17 @@ export default new Router({
mode: "history",
linkActiveClass: "active",
base: process.env.VUE_APP_ROUTER_BASE_URL || "/",
scrollBehavior(to, from, savedPosition) {
return new Promise(resolve => {
setTimeout(() => {
if (to.hash) {
resolve({ selector: to.hash });
}
let pos = savedPosition || { x: 0, y: 0 };
resolve(pos);
}, 100);
});
},
routes: [
{
path: "/",
@ -18,7 +29,10 @@ export default new Router({
{
path: "/front",
name: "front",
redirect: "/"
redirect: to => {
const { hash, params, query } = to
return { name: 'index', hash, query }
}
},
{
path: "/about",
@ -30,7 +44,7 @@ export default new Router({
path: "/login",
name: "login",
component: () =>
import(/* webpackChunkName: "core" */ "@/components/auth/Login"),
import(/* webpackChunkName: "core" */ "@/views/auth/Login"),
props: route => ({ next: route.query.next || "/library" })
},
{
@ -87,7 +101,7 @@ export default new Router({
path: "/signup",
name: "signup",
component: () =>
import(/* webpackChunkName: "core" */ "@/components/auth/Signup"),
import(/* webpackChunkName: "core" */ "@/views/auth/Signup"),
props: route => ({
defaultInvitation: route.query.invitation
})

Wyświetl plik

@ -17,6 +17,7 @@ export default {
instanceUrl: process.env.VUE_APP_INSTANCE_URL,
events: [],
knownInstances: [],
nodeinfo: null,
settings: {
instance: {
name: {
@ -41,7 +42,7 @@ export default {
enabled: {
value: true
}
}
},
}
},
mutations: {
@ -57,6 +58,9 @@ export default {
events: (state, value) => {
state.events = value
},
nodeinfo: (state, value) => {
state.nodeinfo = value
},
frontSettings: (state, value) => {
state.frontSettings = value
},

Wyświetl plik

@ -388,6 +388,9 @@ input + .help {
padding-left: 0;
padding-right: 0;
}
.column .ui.text.container {
max-width: 100% !important;
}
@import "./themes/_light.scss";
@import "./themes/_dark.scss";

Wyświetl plik

@ -123,6 +123,9 @@ $link-color: rgb(255, 144, 0);
.ui.segment:not(.basic) {
background-color: $light-background-color;
}
.link {
color: $link-color;
}
.ui.list,
.ui.dropdown {
.item,
@ -136,6 +139,9 @@ $link-color: rgb(255, 144, 0);
color: $background-color;
}
}
.segment .ui.list .item {
background-color: transparent;
}
.ui.divided.items > .item:not(:first-child) {
border-top: 1px solid $border-color;
}
@ -251,9 +257,12 @@ $link-color: rgb(255, 144, 0);
}
}
}
.ui.list > .item .description {
color: $text-color;
}
.ui.link.list.list a.item:hover,
.ui.link.list.list .item a:not(.ui):not(.button):hover {
color: $link-color;
.ui.link.list.list .item a:not(.ui):not(.button):hover, .ui.list > .item a.header {
color: $link-color !important;
}
[data-tooltip]::after {
background-color: $light-background-color;

Wyświetl plik

@ -0,0 +1,40 @@
<template>
<main class="main pusher" v-title="labels.title">
<section class="ui vertical stripe segment">
<div class="ui small text container">
<h2><translate translate-context="Content/Login/Title/Verb">Log in to your Funkwhale account</translate></h2>
<login-form :next="next"></login-form>
</div>
</section>
</main>
</template>
<script>
import LoginForm from "@/components/auth/LoginForm"
export default {
props: {
next: { type: String, default: "/library" }
},
components: {
LoginForm
},
created () {
if (this.$store.state.auth.authenticated) {
this.$router.push(this.next)
}
},
computed: {
labels() {
let title = this.$pgettext('Head/Login/Title', "Log In")
return {
title
}
}
}
}
</script>
<!-- Add "scoped" attribute to limit CSS to this component only -->
<style scoped>
</style>

Wyświetl plik

@ -0,0 +1,48 @@
<template>
<main class="main pusher" v-title="labels.title">
<section class="ui vertical stripe segment">
<div class="ui small text container">
<h2><translate translate-context="Content/Signup/Title">Create a funkwhale account</translate></h2>
<signup-form :default-invitation="defaultInvitation" :next="next"></signup-form>
</div>
</section>
</main>
</template>
<script>
import SignupForm from "@/components/auth/SignupForm"
export default {
props: {
defaultInvitation: { type: String, required: false, default: null },
next: { type: String, default: "/" }
},
components: {
SignupForm
},
data() {
return {
username: "",
email: "",
password: "",
isLoadingInstanceSetting: true,
errors: [],
isLoading: false,
invitation: this.defaultInvitation
}
},
computed: {
labels() {
let title = this.$pgettext("*/Signup/Title", "Sign Up")
return {
title
}
}
},
}
</script>
<!-- Add "scoped" attribute to limit CSS to this component only -->
<style scoped>
</style>