This commit is contained in:
Troplo 2022-08-02 21:15:16 +10:00
parent 75df196dae
commit 5ea4d835bd
12 changed files with 351 additions and 428 deletions

34
.github/workflows/desktop.yml vendored Normal file
View file

@ -0,0 +1,34 @@
name: Build/release
on: push
jobs:
release:
runs-on: ${{ matrix.os }}
strategy:
matrix:
os: [macos-latest, ubuntu-latest, windows-latest]
steps:
- name: Check out Git repository
uses: actions/checkout@v1
- name: Install Node.js, NPM and Yarn
uses: actions/setup-node@v1
with:
node-version: 10
- name: Build/release Electron app
uses: samuelmeuli/action-electron-builder@v1
with:
# GitHub token, automatically provided to the action
# (No need to define this secret in the repo settings)
github_token: ${{ secrets.github_token }}
# If the commit is tagged with a version (e.g. "v1.0.0"),
# release the app after building
release: ${{ startsWith(github.ref, 'refs/tags/v') }}
package_root: "./frontend",
use_vue_cli: true,
build_script_name: "electron:build"

View file

@ -0,0 +1,17 @@
"use strict"
module.exports = {
async up(queryInterface, Sequelize) {
await queryInterface.removeColumn("Users", "guidedWizard")
await queryInterface.removeColumn("Users", "emailDirectLogin")
},
async down(queryInterface, Sequelize) {
/**
* Add reverting commands here.
*
* Example:
* await queryInterface.dropTable('users');
*/
}
}

View file

@ -64,11 +64,6 @@ module.exports = (sequelize, DataTypes) => {
allowNull: true,
defaultValue: null
},
guidedWizard: {
type: DataTypes.BOOLEAN,
allowNull: false,
defaultValue: true
},
privacy: {
type: DataTypes.JSON,
defaultValue: {
@ -99,11 +94,6 @@ module.exports = (sequelize, DataTypes) => {
allowNull: false,
defaultValue: false
},
emailDirectLogin: {
type: DataTypes.BOOLEAN,
allowNull: false,
defaultValue: false
},
email: {
type: DataTypes.STRING,
allowNull: true,

View file

@ -1,6 +1,6 @@
{
"name": "colubrina",
"version": "1.0.11",
"version": "1.0.12",
"private": true,
"author": "Troplo <troplo@troplo.com>",
"scripts": {

View file

@ -1,3 +1,13 @@
.offset-message {
padding-left: 47px;
}
.message-action-card {
position: absolute;
top: 0;
right: 0;
margin-top: 5px;
margin-right: 5px;
}
/* large codeblock */
code {
background-color: var(--v-bg-base) !important;

View file

@ -19,7 +19,11 @@
</v-btn>
</v-toolbar>
<v-textarea
style="margin-bottom: 5px"
:style="
!$vuetify.breakpoint.mobile
? 'margin-bottom: 3px'
: 'margin-bottom: -17px'
"
autofocus
label="Type a message"
placeholder="Keep it civil"
@ -193,10 +197,11 @@ export default {
},
handlePaste(data) {
if (data.clipboardData.items.length) {
const item = data.clipboardData.items[0]
if (item.kind === "file") {
this.file = item.getAsFile()
this.getURLForImage()
for (const item of data.clipboardData.items) {
if (item.kind === "file") {
this.file = item.getAsFile()
this.getURLForImage()
}
}
}
},

View file

@ -1,7 +1,7 @@
<template>
<div>
<v-hover v-slot="{ hover }">
<div>
<span>
<template v-if="!message.type">
<v-toolbar
@click="jumpToMessage(message.replyId)"
@ -40,8 +40,9 @@
:class="{ 'message-hover': hover }"
:id="'message-' + index"
@contextmenu="show($event, 'message', message)"
:style="lastMessage ? 'margin-bottom: -5px; margin-top: -5px;' : ''"
>
<v-avatar size="48" class="mr-2">
<v-avatar size="40" class="mr-2" v-if="!lastMessage">
<v-img
:src="
$store.state.baseURL + '/usercontent/' + message.user.avatar
@ -51,8 +52,15 @@
/>
<v-icon v-else class="elevation-1"> mdi-account </v-icon>
</v-avatar>
<v-list-item-content>
<v-list-item-subtitle>
<small
style="font-size: 9px; position: absolute"
class="grey--text"
v-if="lastMessage && hover"
>
{{ $date(message.createdAt).format("hh:mm A") }}
</small>
<v-list-item-content :class="{ 'offset-message': lastMessage }">
<v-list-item-subtitle v-if="!lastMessage">
{{ getName(message.user) }}
<v-chip
v-if="message.user.bot"
@ -87,13 +95,27 @@
</span>
</v-tooltip>
</v-list-item-subtitle>
<p
<span
v-if="edit.id !== message.id"
v-markdown
style="overflow-wrap: anywhere"
>
{{ message.content }}
</p>
<span v-markdown>
{{ message.content }}
</span>
<v-tooltip top v-if="message.edited && lastMessage">
<template v-slot:activator="{ on }">
<v-icon color="grey" small v-on="on" style="">
mdi-pencil
</v-icon>
</template>
<span>
{{
$date(message.editedAt).format("DD/MM/YYYY hh:mm:ss A")
}}
</span>
</v-tooltip>
</span>
<template v-if="edit.id !== message.id">
<v-row
v-for="(embed, index) in message.embeds"
@ -339,7 +361,14 @@
{{ attachment.name }}
</span>
</v-card-text>
<v-card-actions>
<v-card-actions
v-if="
attachment.extension !== 'jpg' &&
attachment.extension !== 'png' &&
attachment.extension !== 'jpeg' &&
attachment.extension !== 'gif'
"
>
{{ attachment.name }} -
{{ friendlySize(attachment.size) }}
<v-spacer />
@ -366,64 +395,108 @@
v-if="edit.id === message.id"
></CommsInput>
</v-list-item-content>
<v-list-item-action v-if="!$vuetify.breakpoint.mobile && hover">
<v-list-item-subtitle>
<v-btn
icon
v-if="message.userId === $store.state.user.id"
@click="deleteMessage(message)"
>
<v-icon> mdi-delete </v-icon>
</v-btn>
<v-btn
icon
@click="
edit.content = message.content
edit.editing = true
edit.id = message.id
"
v-if="
message.userId === $store.state.user.id &&
edit.id !== message.id
"
>
<v-icon> mdi-pencil </v-icon>
</v-btn>
<v-btn
icon
@click="
edit.content = ''
edit.editing = false
edit.id = null
"
v-if="
message.userId === $store.state.user.id &&
edit.id === message.id
"
>
<v-icon> mdi-close </v-icon>
</v-btn>
<v-btn
icon
@click="
replying(message)
focusInput()
"
>
<v-icon> mdi-reply </v-icon>
</v-btn>
<v-btn
icon
v-if="chat.rank === 'admin' || chat.chat.type === 'direct'"
@click="
pinMessage()
focusInput()
"
>
<v-icon> mdi-pin </v-icon>
</v-btn>
</v-list-item-subtitle>
</v-list-item-action>
<v-card
elevation="3"
class="message-action-card"
v-if="!$vuetify.breakpoint.mobile && hover"
>
<v-tooltip top>
<template v-slot:activator="{ on }">
<span v-on="on">
<v-btn
icon
v-on="on"
v-if="message.userId === $store.state.user.id"
@click="deleteMessage(message)"
>
<v-icon> mdi-delete </v-icon>
</v-btn>
</span>
</template>
<span> Delete </span>
</v-tooltip>
<v-tooltip top>
<template v-slot:activator="{ on }">
<span v-on="on">
<v-btn
icon
v-on="on"
@click="
edit.content = message.content
edit.editing = true
edit.id = message.id
"
v-if="
message.userId === $store.state.user.id &&
edit.id !== message.id
"
>
<v-icon> mdi-pencil </v-icon>
</v-btn>
</span>
</template>
<span> Edit </span>
</v-tooltip>
<v-tooltip top>
<template v-slot:activator="{ on }">
<span v-on="on">
<v-btn
v-on="on"
icon
@click="
edit.content = ''
edit.editing = false
edit.id = null
"
v-if="
message.userId === $store.state.user.id &&
edit.id === message.id
"
>
<v-icon> mdi-close </v-icon>
</v-btn>
</span>
</template>
<span> Discard Edits </span>
</v-tooltip>
<v-tooltip top>
<template v-slot:activator="{ on }">
<span v-on="on">
<v-btn
icon
@click="
replying(message)
focusInput()
"
v-on="on"
>
<v-icon> mdi-reply </v-icon>
</v-btn>
</span>
</template>
<span> Reply </span>
</v-tooltip>
<v-tooltip top>
<template v-slot:activator="{ on }">
<span v-on="on">
<v-btn
v-on="on"
icon
v-if="
chat.rank === 'admin' || chat.chat.type === 'direct'
"
@click="
pinMessage()
focusInput()
"
>
<v-icon> mdi-pin </v-icon>
</v-btn>
</span>
</template>
<span> Pin to Chat </span>
</v-tooltip>
</v-card>
</v-list-item>
</template>
<template v-else-if="message.type === 'leave'">
@ -721,7 +794,7 @@
</v-list-item-action>
</v-list-item>
</template>
</div>
</span>
</v-hover>
</div>
</template>
@ -764,7 +837,8 @@ export default {
"index",
"show",
"setImagePreview",
"deleteMessage"
"deleteMessage",
"lastMessage"
],
components: {
CommsInput,

View file

@ -18,26 +18,13 @@ import "highlight.js/styles/github.css"
const md = require("markdown-it")({
html: false, // Enable HTML tags in source
xhtmlOut: false, // Use '/' to close single tags (<br />).
// This is only for full CommonMark compatibility.
breaks: true, // Convert '\n' in paragraphs into <br>
langPrefix: "language-", // CSS language prefix for fenced blocks. Can be
// useful for external highlighters.
linkify: true, // Autoconvert URL-like text to links
// Enable some language-neutral replacement + quotes beautification
// For the full list of replacements, see https://github.com/markdown-it/markdown-it/blob/master/lib/rules_core/replacements.js
typographer: false,
// Double + single quotes replacement pairs, when typographer enabled,
// and smartquotes on. Could be either a String or an Array.
//
// For example, you can use '«»„“' for Russian, '„“‚‘' for German,
// and ['«\xA0', '\xA0»', '\xA0', '\xA0'] for French (including nbsp).
quotes: "“”‘’",
// Highlighter function. Should return escaped HTML,
// or '' if the source string is not changed and should be escaped externally.
// If result starts with <pre... internal wrapper is skipped.
highlight: function (/*str, lang*/) {
return ""
}
@ -95,7 +82,11 @@ Vue.use(VueAxios, axios)
Vue.use(Toast)
Vue.directive("markdown", {
inserted(el) {
el.innerHTML = md.render(el.innerHTML)
el.innerHTML = md
.render(el.innerHTML)
.replaceAll("&amp;", "&")
.replaceAll("<p>", "<span>")
.replaceAll("</p>", "</span>")
}
})
if (process.env.NODE_ENV === "development") {

View file

@ -1,5 +1,5 @@
<template>
<div id="communications-chat" @dragover.prevent @drop.prevent>
<div id="communications-chat" @dragover.prevent @drop.prevent="handleDrag">
<v-menu
:position-x="$store.state.context.pins.x"
:position-y="60"
@ -83,8 +83,7 @@
<v-dialog
v-model="preview.dialog"
elevation="0"
:width="preview.width"
:height="preview.height"
:min-height="300"
:max-width="1000"
:max-height="600"
content-class="rounded-0"
@ -94,12 +93,20 @@
:src="preview.src"
:max-width="1000"
:max-height="600"
:min-height="300"
aspect-ratio="16/9"
contain
></v-img>
<v-container>
<a :href="preview.src" style="text-decoration: none" target="_blank">
<small> Open Externally </small>
</a>
<small
class="float-end"
style="text-decoration: none; color: inherit"
>
{{ preview.name }}
</small>
</v-container>
</v-card>
</v-dialog>
@ -160,7 +167,7 @@
</v-list>
</v-navigation-drawer>
<v-row v-if="!loading" @drop="handleDrag" no-gutters>
<v-col class="flex-grow-1 flex-shrink-1" id="chat-col">
<v-col class="flex-grow-1 flex-shrink-1 pb-0" id="chat-col">
<v-card
class="d-flex flex-column fill-height rounded-xl"
style="overflow: auto; height: calc(100vh - 24px - 40px - 40px)"
@ -211,6 +218,14 @@
:show="show"
:set-image-preview="setImagePreview"
:delete-message="deleteMessage"
:last-message="
messages[index - 1]?.userId === message?.userId &&
$date(message.createdAt).diff(
messages[index - 1]?.createdAt,
'minute'
) < 10 &&
!message.replyId
"
></Message>
</div>
<div
@ -335,7 +350,6 @@
style="
border-radius: 20px 20px 0 0;
cursor: pointer;
z-index: 50;
position: relative;
top: -30px;
margin-bottom: -27px;
@ -350,6 +364,30 @@
</div>
</v-toolbar>
</v-fade-transition>
<v-fade-transition
v-model="usersTyping.length"
v-if="$vuetify.breakpoint.mobile"
>
<v-toolbar
height="22"
color="toolbar"
elevation="0"
style="
border-radius: 20px 20px 0 0;
cursor: pointer;
position: relative;
top: -30px;
margin-bottom: -27px;
"
width="100%"
v-if="usersTyping.length"
>
<div>
{{ usersTyping.map((user) => getName(user)).join(", ") }}
{{ usersTyping.length > 1 ? " are" : " is" }} typing...
</div>
</v-toolbar>
</v-fade-transition>
<CommsInput
:chat="chat"
:replying="replying"
@ -357,26 +395,22 @@
:autoScroll="autoScroll"
:endSend="endSend"
></CommsInput>
<v-fade-transition v-model="usersTyping.length">
<v-toolbar
height="22"
elevation="0"
<v-fade-transition
v-model="usersTyping.length"
v-if="!$vuetify.breakpoint.mobile"
>
<div
style="
border-radius: 0 0 20px 20px;
position: relative;
margin-bottom: -2px;
margin-top: -20px;
bottom: -14px;
margin-top: -22px;
bottom: -16px;
"
width="100%"
color="toolbar"
v-if="usersTyping.length"
>
<div style="overflow: hidden">
{{ usersTyping.map((user) => getName(user)).join(", ") }}
{{ usersTyping.length > 1 ? " are" : " is" }} typing...
</div>
</v-toolbar>
{{ usersTyping.map((user) => getName(user)).join(", ") }}
{{ usersTyping.length > 1 ? " are" : " is" }} typing...
</div>
</v-fade-transition>
</v-card-text>
</v-card>
@ -421,314 +455,28 @@
></v-text-field>
<v-list two-line color="card" ref="message-list-search">
<template v-for="(message, index) in search.results">
<v-toolbar
@click="jumpToMessage(message.replyId)"
:key="message.keyId + '-reply-toolbar'"
elevation="0"
outlined
height="40"
color="card"
v-if="message.reply"
style="cursor: pointer"
>
<v-icon class="mr-2">mdi-reply</v-icon>
<v-avatar size="24" class="mr-2">
<v-img
:src="
$store.state.baseURL +
'/usercontent/' +
message.reply.user.avatar
"
v-if="message.reply.user.avatar"
class="elevation-1"
/>
<v-icon v-else class="elevation-1"> mdi-account </v-icon>
</v-avatar>
<template v-if="message.reply.attachments.length">
<v-icon class="mr-2">mdi-file-image</v-icon>
</template>
<template
v-if="
!message.reply.content && message.reply.attachments.length
"
>
Click to view attachment
</template>
{{ message.reply.content.substring(0, 100) }}
</v-toolbar>
<v-list-item
style="cursor: pointer"
<div
@click="jumpToMessage(message.id)"
:key="message.keyId"
:class="{
'text-xs-right': message.userId === $store.state.user.id,
'text-xs-left': message.userId !== $store.state.user.id
}"
:id="'message-' + index"
style="cursor: pointer"
>
<v-avatar size="48" class="mr-2">
<v-img
:src="
$store.state.baseURL +
'/usercontent/' +
message.user.avatar
"
v-if="message.user.avatar"
class="elevation-1"
/>
<v-icon v-else class="elevation-1"> mdi-account </v-icon>
</v-avatar>
<v-list-item-content>
<v-list-item-subtitle>
{{ getName(message.user) }}
<small>
{{
$date(message.createdAt).format("DD/MM/YYYY hh:mm A")
}}</small
>
<v-tooltip top v-if="message.edited">
<template v-slot:activator="{ on, attrs }">
<span v-on="on" v-bind="attrs">
<v-icon
color="grey"
small
style="
margin-bottom: 2px;
margin-left: 4px;
position: absolute;
"
>
mdi-pencil
</v-icon>
</span>
</template>
<span>
{{
$date(message.editedAt).format(
"DD/MM/YYYY hh:mm:ss A"
)
}}
</span>
</v-tooltip>
</v-list-item-subtitle>
<p
v-if="edit.id !== message.id"
v-markdown
style="overflow-wrap: anywhere"
>
{{ message.content }}
</p>
<template v-if="edit.id !== message.id">
<v-row
v-for="(embed, index) in message.embeds"
:key="index"
:id="'embed-' + index"
>
<v-card
elevaion="0"
color="card"
max-width="25%"
width="25%"
class="ml-3"
>
<v-container>
<v-row v-if="embed.type === 'openGraph'">
<v-col
cols="12"
class="text-xs-center"
v-if="embed.openGraph.ogImage"
>
<v-img
:src="
embed.openGraph.ogImage?.url ||
embed.openGraph.ogImage[0]?.url
"
class="elevation-1"
contain
:aspect-ratio="16 / 9"
>
<template v-slot:placeholder>
<v-row
class="fill-height ma-0"
align="center"
justify="center"
>
<v-progress-circular
indeterminate
color="grey lighten-5"
></v-progress-circular>
</v-row>
</template>
</v-img>
</v-col>
<v-col cols="12" class="text-xs-center">
<h4>
{{ embed.openGraph.ogSiteName }}
</h4>
<a
:href="embed.link"
target="_blank"
style="text-decoration: none"
>
<h3>
{{ embed.openGraph.ogTitle }}
</h3>
</a>
<p v-if="embed.openGraph.ogDescription">
{{ embed.openGraph.ogDescription }}
</p>
</v-col>
</v-row>
<template v-else-if="embed.type === 'image'">
<v-hover v-slot="{ hover }">
<div>
<v-img
@click="setImagePreview(embed)"
contain
:aspect-ratio="16 / 9"
:src="embed.mediaProxyLink"
>
<template v-slot:placeholder>
<v-row
class="fill-height ma-0"
align="center"
justify="center"
>
<v-progress-circular
indeterminate
color="grey lighten-5"
></v-progress-circular>
</v-row>
</template>
<template v-slot:default>
<v-fade-transition v-if="hover">
<v-overlay absolute>
<v-icon large
>mdi-arrow-expand-all</v-icon
>
</v-overlay>
</v-fade-transition>
</template>
</v-img>
</div>
</v-hover>
<v-card-actions>
MediaProxy Image
<v-spacer />
<v-btn
text
icon
:href="embed.url"
target="_blank"
>
<v-icon> mdi-download </v-icon>
</v-btn>
</v-card-actions>
</template>
</v-container>
</v-card>
</v-row>
</template>
<template v-if="edit.id !== message.id">
<v-card
v-for="(attachment, index) in message.attachments"
:key="attachment.id"
:id="'attachment-' + index"
max-width="40%"
elevaion="0"
color="card"
>
<v-hover
v-slot="{ hover }"
v-if="
attachment.extension === 'jpg' ||
attachment.extension === 'png' ||
attachment.extension === 'jpeg' ||
attachment.extension === 'gif'
"
>
<div>
<v-img
@click="setImagePreview(attachment)"
contain
:aspect-ratio="16 / 9"
:src="
$store.state.baseURL +
'/usercontent/' +
attachment.attachment
"
>
<template v-slot:placeholder>
<v-row
class="fill-height ma-0"
align="center"
justify="center"
>
<v-progress-circular
indeterminate
color="grey lighten-5"
></v-progress-circular>
</v-row>
</template>
<template v-slot:default>
<v-fade-transition v-if="hover">
<v-overlay absolute>
<v-icon large>mdi-arrow-expand-all</v-icon>
</v-overlay>
</v-fade-transition>
</template>
</v-img>
</div>
</v-hover>
<v-card-text v-else>
<v-icon class="mr-2" :size="48">
{{ fileTypes[attachment.extension] || "mdi-file" }}
</v-icon>
<span>
{{ attachment.name }}
</span>
</v-card-text>
<v-card-actions>
{{ attachment.name }} -
{{ friendlySize(attachment.size) }}
<v-spacer />
<v-btn
text
icon
:href="
$store.state.baseURL +
'/usercontent/' +
attachment.attachment
"
target="_blank"
>
<v-icon> mdi-download </v-icon>
</v-btn>
</v-card-actions>
</v-card>
</template>
<v-text-field
v-model="edit.content"
v-if="edit.editing && edit.id === message.id"
autofocus
:value="message.content"
label="Type a message"
placeholder="Type a message"
type="text"
ref="edit-input"
outlined
append-outer-icon="mdi-send"
@keyup.enter="editMessage(message)"
@keydown.esc="
edit.content = ''
edit.editing = false
edit.id = null
focusInput()
"
@click:append-outer="editMessage(message)"
/>
</v-list-item-content>
</v-list-item>
<Message
:message="message"
:jump-to-message="jumpToMessage"
:edit="edit"
:focus-input="focusInput"
:replying="setReply"
:get-name="getName"
:end-edit="endEdit"
:auto-scroll="autoScroll"
:chat="chat"
:index="index"
:show="show"
:set-image-preview="setImagePreview"
:delete-message="deleteMessage"
:last-message="false"
></Message>
</div>
</template>
</v-list>
</v-card-text>
@ -912,7 +660,8 @@ export default {
dialog: false,
src: "",
height: 0,
width: 0
width: 0,
name: ""
},
fileTypes: {
png: "mdi-file-image",
@ -1207,6 +956,7 @@ export default {
this.preview.height = img.height
this.preview.width = img.width
this.preview.dialog = true
this.preview.name = attachment.name
}
img.src = link
},

View file

@ -159,6 +159,10 @@ export default {
this.loading = false
this.$socket.disconnect()
this.$socket.connect()
if (this.isElectron()) {
this.axios.defaults.baseURL = this.instance
localStorage.setItem("instance", this.instance)
}
if (
this.$store.state.site.emailVerification &&
!this.$store.state.user.emailVerified
@ -191,7 +195,6 @@ export default {
.then((res) => {
this.instanceString = res.data.name + " v" + res.data.latestVersion
this.axios.defaults.baseURL = this.instance
localStorage.setItem("instance", this.instance)
this.$store.dispatch("getState")
})
.catch(() => {

View file

@ -7,10 +7,23 @@
<v-container>
<v-form ref="form" class="pa-4 pt-6">
<p class="text-center text-h4">
Login to
Register to
<span class="troplo-title">{{ $store.state.site.name }}</span
><small style="font-size: 15px">beta</small>
</p>
<v-text-field
@keyup.enter="doRegister()"
class="rounded-xl"
v-model="instance"
v-if="isElectron()"
label="Instance URL"
placeholder="https://colubrina.troplo.com"
type="email"
></v-text-field>
<small style="float: right" v-if="isElectron()">{{
instanceString
}}</small
><br v-if="isElectron()" />
<v-text-field
@keyup.enter="doRegister()"
class="rounded-xl"
@ -82,10 +95,29 @@ export default {
email: "",
totp: "",
totpDialog: false,
loading: false
loading: false,
instance: "https://colubrina.troplo.com",
instanceString: ""
}
},
methods: {
isElectron() {
return process.env.IS_ELECTRON
},
testInstance() {
if (this.isElectron()) {
this.axios
.get(this.instance + "/api/v1/state")
.then((res) => {
this.instanceString = res.data.name + " v" + res.data.latestVersion
this.axios.defaults.baseURL = this.instance
this.$store.dispatch("getState")
})
.catch(() => {
this.instanceString = "Error connecting to instance"
})
}
},
viewport() {
return window.innerHeight
},
@ -106,6 +138,10 @@ export default {
this.loading = false
this.$socket.disconnect()
this.$socket.connect()
if (this.isElectron()) {
this.axios.defaults.baseURL = this.instance
localStorage.setItem("instance", this.instance)
}
if (
this.$store.state.site.emailVerification &&
!this.$store.state.user.emailVerified
@ -114,6 +150,9 @@ export default {
} else {
this.$router.push("/")
}
if (this.isElectron()) {
window.location.reload()
}
})
.catch((e) => {
if (
@ -133,6 +172,12 @@ export default {
if (this.$store.state.user?.id) {
this.$router.push("/")
}
this.testInstance()
},
watch: {
instance() {
this.testInstance()
}
}
}
</script>

View file

@ -26,7 +26,11 @@
</v-overlay>
</v-fade-transition>
<v-img
:src="'/usercontent/' + $store.state.user.avatar"
:src="
$store.state.baseURL +
'/usercontent/' +
$store.state.user.avatar
"
v-if="$store.state.user.avatar"
class="elevation-1"
/>