2023-10-07 20:22:41 +00:00
|
|
|
<script>
|
|
|
|
export default {
|
|
|
|
props: {
|
|
|
|
visible: Boolean,
|
|
|
|
jwt: String, // caller's JWT token for authorization
|
|
|
|
user: Object, // the user we are viewing
|
|
|
|
username: String, // the local user
|
|
|
|
websiteUrl: String,
|
|
|
|
isDnd: Boolean,
|
|
|
|
isMuted: Boolean,
|
|
|
|
isBooted: Boolean,
|
|
|
|
profileWebhookEnabled: Boolean,
|
|
|
|
vipConfig: Object, // VIP config settings for BareRTC
|
|
|
|
},
|
|
|
|
data() {
|
|
|
|
return {
|
|
|
|
busy: false,
|
|
|
|
|
|
|
|
// Profile data
|
|
|
|
profileFields: [],
|
|
|
|
|
|
|
|
// Error messaging from backend
|
|
|
|
error: null,
|
|
|
|
};
|
|
|
|
},
|
|
|
|
watch: {
|
|
|
|
visible() {
|
|
|
|
if (this.visible) {
|
|
|
|
this.refresh();
|
|
|
|
} else {
|
|
|
|
this.profileFields = [];
|
|
|
|
this.error = null;
|
|
|
|
this.busy = false;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
},
|
|
|
|
computed: {
|
|
|
|
profileURL() {
|
|
|
|
if (this.user.profileURL) {
|
|
|
|
return this.urlFor(this.user.profileURL);
|
|
|
|
}
|
|
|
|
return null;
|
|
|
|
},
|
|
|
|
avatarURL() {
|
|
|
|
if (this.user.avatar) {
|
|
|
|
return this.urlFor(this.user.avatar);
|
|
|
|
}
|
|
|
|
return null;
|
|
|
|
},
|
|
|
|
nickname() {
|
|
|
|
if (this.user.nickname) {
|
|
|
|
return this.user.nickname;
|
|
|
|
}
|
|
|
|
return this.user.username;
|
|
|
|
},
|
|
|
|
},
|
|
|
|
methods: {
|
|
|
|
refresh() {
|
|
|
|
if (!this.profileWebhookEnabled) return;
|
|
|
|
if (!this.user || !this.user?.username) return;
|
|
|
|
this.busy = true;
|
|
|
|
return fetch("/api/profile", {
|
|
|
|
method: "POST",
|
|
|
|
mode: "same-origin",
|
|
|
|
cache: "no-cache",
|
|
|
|
credentials: "same-origin",
|
|
|
|
headers: {
|
|
|
|
"Content-Type": "application/json",
|
|
|
|
},
|
|
|
|
body: JSON.stringify({
|
|
|
|
"JWTToken": this.jwt,
|
|
|
|
"Username": this.user?.username,
|
|
|
|
}),
|
|
|
|
})
|
|
|
|
.then((response) => response.json())
|
|
|
|
.then((data) => {
|
|
|
|
if (data.Error) {
|
|
|
|
this.error = data.Error;
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (data.ProfileFields != undefined) {
|
|
|
|
this.profileFields = data.ProfileFields;
|
|
|
|
}
|
|
|
|
}).catch(resp => {
|
|
|
|
this.error = resp;
|
|
|
|
}).finally(() => {
|
|
|
|
this.busy = false;
|
|
|
|
})
|
|
|
|
},
|
|
|
|
|
|
|
|
cancel() {
|
|
|
|
this.$emit("cancel");
|
|
|
|
},
|
|
|
|
|
|
|
|
openProfile() {
|
|
|
|
let url = this.profileURL;
|
|
|
|
if (url) {
|
|
|
|
window.open(url);
|
|
|
|
}
|
|
|
|
},
|
|
|
|
|
|
|
|
openDMs() {
|
|
|
|
this.cancel();
|
|
|
|
this.$emit('send-dm', {
|
|
|
|
username: this.user.username,
|
|
|
|
});
|
|
|
|
},
|
|
|
|
|
|
|
|
muteUser() {
|
|
|
|
this.$emit('mute-user', this.user.username);
|
|
|
|
},
|
|
|
|
|
|
|
|
bootUser() {
|
|
|
|
this.$emit('boot-user', this.user.username);
|
|
|
|
},
|
|
|
|
|
|
|
|
urlFor(url) {
|
|
|
|
// Prepend the base websiteUrl if the given URL is relative.
|
|
|
|
if (url.match(/^https?:/i)) {
|
|
|
|
return url;
|
|
|
|
}
|
|
|
|
return this.websiteUrl.replace(/\/+$/, "") + url;
|
|
|
|
},
|
|
|
|
},
|
|
|
|
}
|
|
|
|
</script>
|
|
|
|
|
|
|
|
<template>
|
|
|
|
<!-- Profile Card Modal -->
|
|
|
|
<div class="modal" :class="{ 'is-active': visible }">
|
|
|
|
<div class="modal-background" @click="cancel()"></div>
|
|
|
|
<div class="modal-content">
|
|
|
|
<div class="card">
|
|
|
|
<header class="card-header has-background-success">
|
|
|
|
<p class="card-header-title">Profile Card</p>
|
|
|
|
</header>
|
|
|
|
<div class="card-content">
|
|
|
|
|
|
|
|
<!-- Avatar and name/username media -->
|
|
|
|
<div class="media mb-0">
|
|
|
|
<div class="media-left">
|
|
|
|
<a :href="profileURL"
|
|
|
|
@click.prevent="openProfile()"
|
|
|
|
:class="{ 'cursor-default': !profileURL }">
|
2023-10-08 18:24:44 +00:00
|
|
|
<figure class="image is-96x96">
|
2023-10-07 20:22:41 +00:00
|
|
|
<img v-if="avatarURL"
|
|
|
|
:src="avatarURL">
|
|
|
|
<img v-else src="/static/img/shy.png">
|
|
|
|
</figure>
|
|
|
|
</a>
|
|
|
|
</div>
|
|
|
|
<div class="media-content">
|
|
|
|
<strong>
|
|
|
|
<!-- User nickname/display name -->
|
|
|
|
{{ nickname }}
|
|
|
|
</strong>
|
|
|
|
<div>
|
|
|
|
<small>
|
|
|
|
<a v-if="profileURL"
|
|
|
|
:href="profileURL" target="_blank"
|
|
|
|
class="has-text-grey">
|
|
|
|
@{{ user.username }}
|
|
|
|
</a>
|
|
|
|
<span v-else class="has-text-grey">@{{ user.username }}</span>
|
|
|
|
</small>
|
|
|
|
</div>
|
|
|
|
|
2023-10-08 18:24:44 +00:00
|
|
|
<!-- User badges -->
|
2023-10-08 19:24:19 +00:00
|
|
|
<div v-if="user.op || user.vip || user.emoji" class="mt-4">
|
2023-10-08 19:23:11 +00:00
|
|
|
<!-- Emoji icon -->
|
|
|
|
<span v-if="user.emoji" class="mr-2">
|
|
|
|
{{ user.emoji }}
|
|
|
|
</span>
|
|
|
|
|
2023-10-08 18:24:44 +00:00
|
|
|
<!-- Operator? -->
|
|
|
|
<span v-if="user.op" class="tag is-warning is-light mr-2">
|
|
|
|
<i class="fa fa-peace mr-1"></i> Operator
|
|
|
|
</span>
|
2023-10-07 20:22:41 +00:00
|
|
|
|
2023-10-08 18:24:44 +00:00
|
|
|
<!-- VIP? -->
|
|
|
|
<span v-if="vipConfig && user.vip" class="tag is-success is-light mr-2"
|
|
|
|
:title="vipConfig.Name">
|
|
|
|
<i class="mr-1" :class="vipConfig.Icon"></i>
|
|
|
|
{{ vipConfig.Name }}
|
|
|
|
</span>
|
|
|
|
</div>
|
|
|
|
</div>
|
2023-10-07 20:22:41 +00:00
|
|
|
</div>
|
|
|
|
|
|
|
|
<!-- Action buttons -->
|
|
|
|
<div v-if="user.username !== username" class="mt-4">
|
|
|
|
<!-- DMs button -->
|
|
|
|
<button type="button"
|
|
|
|
class="button is-grey is-outlined is-small px-2 mb-1"
|
|
|
|
@click="openDMs()"
|
|
|
|
:title="isDnd ? 'This person is not accepting new DMs' : 'Open a Direct Message (DM) thread'"
|
|
|
|
:disabled="isDnd">
|
|
|
|
<i class="fa mr-1" :class="{'fa-comment': !isDnd, 'fa-comment-slash': isDnd}"></i>
|
|
|
|
Direct Message
|
|
|
|
</button>
|
|
|
|
|
|
|
|
<!-- Mute button -->
|
|
|
|
<button type="button"
|
|
|
|
class="button is-grey is-outlined is-small px-2 ml-1 mb-1"
|
|
|
|
@click="muteUser()" title="Mute user">
|
|
|
|
<i class="fa fa-comment-slash mr-1" :class="{
|
|
|
|
'has-text-success': isMuted,
|
|
|
|
'has-text-danger': !isMuted
|
|
|
|
}"></i>
|
|
|
|
{{ isMuted ? "Unmute" : "Mute" }} Messages
|
|
|
|
</button>
|
|
|
|
|
|
|
|
<!-- Boot button -->
|
|
|
|
<button type="button"
|
|
|
|
class="button is-grey is-outlined is-small px-2 ml-1 mb-1"
|
|
|
|
@click="bootUser()" title="Boot user off your webcam">
|
|
|
|
<i class="fa fa-user-xmark mr-1" :class="{
|
|
|
|
'has-text-danger': !isBooted,
|
|
|
|
'has-text-success': isBooted,
|
|
|
|
}"></i>
|
|
|
|
{{ isBooted ? 'Allow to watch my webcam' : "Don't allow to watch my webcam" }}
|
|
|
|
</button>
|
|
|
|
</div>
|
|
|
|
|
|
|
|
<!-- Profile Fields spinner/error -->
|
|
|
|
<div class="notification is-info is-light p-2 my-2" v-if="busy">
|
|
|
|
<i class="fa fa-spinner fa-spin mr-2"></i>
|
|
|
|
Loading profile details...
|
|
|
|
</div>
|
|
|
|
<div class="notification is-danger is-light p-2 my-2" v-else-if="error">
|
|
|
|
<i class="fa fa-exclamation-triangle mr-2"></i>
|
|
|
|
Error loading profile details:
|
|
|
|
{{ error }}
|
|
|
|
</div>
|
|
|
|
|
|
|
|
<!-- Profile Fields -->
|
|
|
|
<div class="columns is-multiline is-mobile mt-3"
|
|
|
|
v-else-if="profileFields.length > 0">
|
|
|
|
<div class="column py-1"
|
|
|
|
v-for="(field, i) in profileFields"
|
|
|
|
v-bind:key="field.Name"
|
|
|
|
:class="{'is-half': i < profileFields.length-1}">
|
|
|
|
<strong>{{ field.Name }}:</strong>
|
|
|
|
{{ field.Value }}
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
|
|
|
|
</div>
|
|
|
|
<footer class="card-footer">
|
|
|
|
<a :href="profileURL" target="_blank"
|
2023-10-08 18:24:44 +00:00
|
|
|
v-if="profileURL" class="card-footer-item"
|
|
|
|
@click="cancel()">
|
2023-10-07 20:22:41 +00:00
|
|
|
Full profile <i class="fa fa-external-link ml-2"></i>
|
|
|
|
</a>
|
|
|
|
<a href="#" @click.prevent="cancel()" class="card-footer-item">
|
|
|
|
Close
|
|
|
|
</a>
|
|
|
|
</footer>
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
</template>
|
|
|
|
|
|
|
|
<style scoped>
|
|
|
|
</style>
|