New design, a lot of changes.

This commit is contained in:
supertiger1234 2019-10-29 20:02:56 +00:00
parent 41db687eaa
commit 154d6147a9
51 changed files with 1599 additions and 1731 deletions

5
package-lock.json generated
View file

@ -10296,6 +10296,11 @@
"integrity": "sha1-tf3AjxKH6hF4Yo5BXiUTK3NkbG0=",
"dev": true
},
"simple-markdown": {
"version": "0.6.1",
"resolved": "https://registry.npmjs.org/simple-markdown/-/simple-markdown-0.6.1.tgz",
"integrity": "sha512-02HKXvM9J7pJWf74fuWthcgof5jF81Yndt+XcXtWnEtpp8QaX9dUAJpdAA6KWrB/rSGzrOi0PRAVy9/0bJiIZw=="
},
"simple-swizzle": {
"version": "0.2.2",
"resolved": "https://registry.npmjs.org/simple-swizzle/-/simple-swizzle-0.2.2.tgz",

View file

@ -16,6 +16,7 @@
"markdown-it-chat-formatter": "^0.1.1",
"match-sorter": "^2.3.0",
"particles.js": "^2.0.0",
"simple-markdown": "^0.6.1",
"socket.io": "^2.2.0",
"socket.io-client": "^2.2.0",
"twemoji": "^11.3.0",

View file

@ -1,182 +0,0 @@
<template>
<div class="change-log">
<div class="inner">
<div
class="close-button"
@click="close"
>
<i class="material-icons">
close
</i>
</div>
<div class="change-title">
Change Log <div class="changelog-icon">
<i class="material-icons ">update</i>
</div>
</div>
<div class="change-list">
<div
v-for="(change, index) in changelog"
:key="index"
class="change"
>
<div class="date">
{{ change.date }}
</div>
<div class="changes-title">
{{ change.title }}
</div>
<div class="information">
<div v-if="change.new">
<strong>What's new?</strong><br>
<ul>
<li
v-for="(wnew, index) in change.new"
:key="index"
>
{{ wnew }}
</li>
</ul>
</div>
<div v-if="change.fix">
<strong>Issues fixed</strong><br>
<ul>
<li
v-for="(wfix, index) in change.fix"
:key="index"
>
{{ wfix }}
</li>
</ul>
</div>
<div v-if="change.next">
<strong>Up next</strong><br>
<ul>
<li
v-for="(wnext, index) in change.next"
:key="index"
>
{{ wnext }}
</li>
</ul>
</div>
<div v-if="change.msg">
{{ change.msg }}
</div>
</div>
</div>
</div>
</div>
</div>
</template>
<script>
import {bus} from './../main.js'
import changelog from '@/utils/changelog.js'
export default {
data() {
return {
changelog
}
},
methods: {
close() {
bus.$emit('closeChangeLog')
}
}
}
</script>
<style scoped>
.change-log{
position: absolute;
background: rgba(0, 0, 0, 0.342);
top: 0;
bottom: 0;
left: 0;
right: 0;
display: flex;
color: white;
}
.inner{
background-color: rgba(0, 0, 0, 0.664);
margin: auto;
width: 600px;
height: 700px;
display: flex;
flex-direction: column;
}
.close-button{
margin: auto;
margin-top: 10px;
margin-right: 10px;
user-select: none;
cursor: default;
color: grey;
transition: .3s;
}
.close-button:hover{
color: white;
}
.close-button .material-icons {
font-size: 30px;
}
.change-title{
color: white;
font-size: 30px;
margin: auto;
margin-top: 10px;
user-select: none;
display: flex;
padding-bottom: 15px;
flex-shrink: 0;
}
.changelog-icon{
margin-top: 2px;
margin-left: 10px
}
.changelog-icon .material-icons {
font-size: 40px;
}
.change-list {
height: 100%;
width: 100%;
overflow: auto;
border-top: 1px solid white;
}
.change{
margin: 5px;
min-height: 100px;
border-bottom: solid 1px white;
padding-bottom: 15px;
}
.change:last-child{
border-bottom: none;
}
.date{
text-align: right;
padding-top: 10px;
padding-right: 10px;
color: grey;
}
.changes-title {
font-size: 25px;
padding-left: 15px;
color: rgba(255, 255, 255, 0.795);
}
.information {
margin: 10px;
padding-left: 20px;
}
@media (max-height: 700px) {
.inner {
height: 100%;
}
}
</style>

View file

@ -54,7 +54,7 @@ export default {
right: 0;
left: 0;
z-index: 9999999999999999;
height: 39px;
height: 25px;
-webkit-app-region: drag;
justify-content: flex-end;
}
@ -66,8 +66,7 @@ export default {
display: flex;
flex-shrink: 0;
height: 100%;
width: 60px;
border-radius: 2px;
width: 30px;
color: white;
align-items: center;
justify-content: center;
@ -82,9 +81,9 @@ export default {
background: rgba(255, 0, 0, 0.595);
}
.frame-buttons .minimize:hover {
background: rgba(255, 81, 0, 0.595);
background: rgba(46, 46, 46, 0.595);
}
.frame-buttons .res-max:hover {
background: rgba(0, 162, 255, 0.595);
background: rgba(46, 46, 46, 0.595);
}
</style>

View file

@ -1,88 +1,78 @@
<template>
<div class="left-panel">
<MyMiniInformation />
<div class="tabs">
<div :class="{selector: true, right: currentTab === 1}" />
<div
class="tab"
:class="{notifyAnimation: friendRequestExists}"
@click="currentTab = 0"
>
Friends
<navigation />
<div class="content">
<div class="tabs">
<div :class="{selector: true, right: currentTab === 1}" />
<div
class="tab"
:class="{notifyAnimation: friendRequestExists}"
@click="currentTab = 0"
>Friends</div>
<div class="tab" :class="{notifyAnimation: DMNotification}" @click="currentTab = 1">Recents</div>
</div>
<div
class="tab"
:class="{notifyAnimation: DMNotification}"
@click="currentTab = 1"
>
Recents
<div v-if="currentTab === 0" class="list">
<pending-friends />
<online-friends />
<offline-friends />
</div>
<div v-else class="list">
<recent-friends />
</div>
<MyMiniInformation />
</div>
<div
v-if="currentTab === 0"
class="list"
>
<pending-friends />
<online-friends />
<offline-friends />
</div>
<div
v-else
class="list"
>
<recent-friends />
</div>
<AddFriendPanel />
</div>
</template>
<script>
import MyMiniInformation from '../../components/app/MyMiniInformation.vue'
import PendingFriends from './relationships/PendingFriends.vue'
import AddFriendPanel from './relationships/AddFriendPanel.vue'
import OnlineFriends from './relationships/OnlineFriends.vue'
import OfflineFriends from './relationships/OfflineFriends.vue'
import RecentFriends from './relationships/RecentFriends.vue'
import MyMiniInformation from "../../components/app/MyMiniInformation.vue";
import PendingFriends from "./relationships/PendingFriends.vue";
import OnlineFriends from "./relationships/OnlineFriends.vue";
import OfflineFriends from "./relationships/OfflineFriends.vue";
import RecentFriends from "./relationships/RecentFriends.vue";
import Navigation from "@/components/app/Navigation";
export default {
components: {
MyMiniInformation,
PendingFriends,
AddFriendPanel,
OnlineFriends,
OfflineFriends,
RecentFriends
RecentFriends,
Navigation
},
data() {
return {
currentTab: 0
}
currentTab: 0
};
},
watch: {
currentTab(tab) {
localStorage.setItem('friendsListTab', tab)
localStorage.setItem("friendsListTab", tab);
}
},
mounted() {
const tab = localStorage.getItem('friendsListTab');
const tab = localStorage.getItem("friendsListTab");
if (tab) {
this.currentTab = parseInt(tab)
this.currentTab = parseInt(tab);
}
},
computed: {
DMNotification() {
const notifications = this.$store.getters.notifications;
const channels = this.$store.getters.channels
const channels = this.$store.getters.channels;
const notification = notifications.find(e => {
return channels[e.channelID] && !channels[e.channelID].server_id && e.channelID !== this.$store.getters.selectedChannelID
})
return (
channels[e.channelID] &&
!channels[e.channelID].server_id &&
e.channelID !== this.$store.getters.selectedChannelID
);
});
// unopened dm
if (!notification) {
return notifications.find(e => {
return !channels[e.channelID]
})
return !channels[e.channelID];
});
}
return notification;
},
@ -93,106 +83,112 @@ export default {
});
return result.find(friend => friend.status === 1);
}
}
}
}
};
</script>
<style scoped>
.left-panel {
height: 100%;
background-color: rgba(0, 0, 0, 0.671);
background: rgba(0, 0, 0, 0.6);
width: 300px;
flex-shrink: 0;
display: flex;
flex-direction: column;
flex-direction: row;
z-index: 1;
}
.list{
margin: 2px;
margin-left: 5px;
margin-right: 5px;
.content {
display: flex;
flex-direction: column;
flex-shrink: 0;
flex: 1;
overflow: hidden;
}
.list {
flex: 1;
overflow: auto;
}
.tabs{
.tabs {
display: flex;
color: white;
flex-shrink: 0;
margin-top: 10px;
position: relative;
}
.tab{
.tab {
display: flex;
align-items: center;
align-content: center;
justify-content: center;
flex: 1;
text-align: center;
margin: auto;
flex-shrink: 0;
user-select: none;
cursor: default;
padding: 10px;
height: 50px;
background: rgba(0, 0, 0, 0.171);
margin-left: 1px;
margin-right: 1px;
border-radius: 5px;
transition: 0.3s;
cursor: pointer;
}
.tab:hover{
.tab:hover {
background: rgba(255, 255, 255, 0.096);
}
.selector {
background: rgba(255, 255, 255, 0.322);
width: 148px;
height: 39px;
width: 118px;
height: 50px;
top: 0;
left: 1px;
position: absolute;
z-index: -1;
transition: 0.3s;
border-radius: 5px;
}
.right{
transform: translateX(150px);
.right {
transform: translateX(120px);
}
/* ------- SCROLL BAR -------*/
/* width */
.list::-webkit-scrollbar {
width: 3px;
width: 3px;
}
/* Track */
.list::-webkit-scrollbar-track {
background: #8080806b;
background: #8080806b;
}
/* Handle */
.list::-webkit-scrollbar-thumb {
background: #f5f5f559;
background: #f5f5f559;
}
/* Handle on hover */
.list::-webkit-scrollbar-thumb:hover {
background: #f5f5f59e;
background: #f5f5f59e;
}
.notifyAnimation{
.notifyAnimation {
animation: notifyAnime;
animation-duration: 1s;
animation-iteration-count: infinite;
animation-fill-mode: forwards;
}
@keyframes notifyAnime {
0%{
0% {
background: rgba(121, 3, 3, 0.541);
}
40%{
40% {
background: rgba(255, 0, 0, 0.568);
}
60%{
60% {
background: rgba(255, 0, 0, 0.568);
}
100%{
100% {
background: rgba(121, 3, 3, 0.541);
}
}

View file

@ -71,18 +71,18 @@ export default {
.member {
display: flex;
padding: 3px;
margin: 3px 5px;
align-items: center;
align-content: center;
border-radius: 5px;
transition: 0.3s;
cursor: pointer;
user-select: none;
overflow: hidden;
}
.member:hover {
background: rgba(0, 0, 0, 0.301);
}
.information {
margin-left: 5px;
overflow: hidden;

View file

@ -82,7 +82,7 @@ export default {
color: white;
display: flex;
flex-direction: column;
background: rgba(0, 0, 0, 0.671);
background: rgba(0, 0, 0, 0.6);
width: 300px;
height: 100%;
}
@ -105,8 +105,6 @@ export default {
.tab {
background: rgba(0, 0, 0, 0.308);
padding: 5px;
border-radius: 5px;
margin: 5px;
user-select: none;
cursor: default;
color: rgb(200, 200, 200);

View file

@ -636,7 +636,7 @@ export default {
.right-panel {
height: 100%;
width: 100%;
background-color: rgba(0, 0, 0, 0.507);
background-color: rgba(0, 0, 0, 0.650);
flex: 1;
display: flex;
flex-direction: column;

View file

@ -2,7 +2,7 @@
<div class="container" @mouseover="hover = true" @mouseleave="hover = false">
<div
v-if="!type || type === 0"
:class="{message: true, ownMessage: user.uniqueID === $props.uniqueID, halloween: user.uniqueID === $props.uniqueID, ownMessageLeft: user.uniqueID === $props.uniqueID && (apperance && apperance.own_message_right === true)} "
:class="{message: true, ownMessage: user.uniqueID === $props.uniqueID, ownMessageLeft: user.uniqueID === $props.uniqueID && (apperance && apperance.own_message_right === true)} "
>
<div class="avatar">
<profile-picture
@ -18,80 +18,71 @@
</div>
<div class="content" @dblclick="contentDoubleClickEvent">
<div class="user-info">
<div class="username halloween-color"
<div
class="username"
@click="openUserInformation"
>
{{ this.$props.username }}
</div>
<div class="date">
{{ getDate }}
</div>
>{{ this.$props.username }}</div>
<div class="date">{{ getDate }}</div>
</div>
<div class="content-message"
v-html="formatMessage"
/>
<div class="content-message" v-html="formatMessage" />
<div class="file-content"
v-if="getFile"
>
<div class="file-content" v-if="getFile">
<div class="icon">
<i class="material-icons">insert_drive_file</i>
</div>
<div class="information">
<div class="info"> {{ getFile.fileName }}</div>
<a
:href="getFile.url"
target="_blank"
>
<div class="info">{{ getFile.fileName }}</div>
<a :href="getFile.url" target="_blank">
<div class="download-button">Download</div>
</a>
</div>
</div>
<div class="image-content" ref="image" v-if="getImage">
<img
:src="getImage"
@click="imageClicked"
>
<img :src="getImage" @click="imageClicked" />
</div>
<message-embed-template v-if="embed && Object.keys(embed).length" :embed="embed"/>
<message-embed-template v-if="embed && Object.keys(embed).length" :embed="embed" />
</div>
<div class="other-information">
<div class="drop-down-button" ref="drop-down-button" @click="openContextMenu"><i class="material-icons">more_vert</i></div>
<div class="sending-status" v-if="timeEdited && (status === undefined || status === 1)" :title="`Edited ${getEditedDate}`"><i class="material-icons">edit</i></div>
<div class="sending-status" v-else-if="status === 0"><i class="material-icons">hourglass_full</i></div>
<div class="sending-status" v-else-if="status === 1"><i class="material-icons">done</i></div>
<div class="sending-status" v-else-if="status === 2"><i class="material-icons">close</i> Failed</div>
<div class="drop-down-button" ref="drop-down-button" @click="openContextMenu">
<i class="material-icons">more_vert</i>
</div>
<div
class="sending-status"
v-if="timeEdited && (status === undefined || status === 1)"
:title="`Edited ${getEditedDate}`"
>
<i class="material-icons">edit</i>
</div>
<div class="sending-status" v-else-if="status === 0">
<i class="material-icons">hourglass_full</i>
</div>
<div class="sending-status" v-else-if="status === 1">
<i class="material-icons">done</i>
</div>
<div class="sending-status" v-else-if="status === 2">
<i class="material-icons">close</i> Failed
</div>
</div>
</div>
<div
v-if="type && (type === 1 || type === 2 || type === 3 || type === 4)"
class="presence-message"
:class="{join: type === 1, leave: type === 2, kick: type === 3, ban: type === 4}"
>
<span>
<span class="username"
@click="openUserInformation"
>{{ this.$props.username }}</span>
<span
v-if="type === 1"
class="text"
>has joined the server!</span>
<span
v-if="type === 2"
class="text"
>has left the server.</span>
<span
v-if="type === 3"
class="text"
>has been kicked.</span>
<span
v-if="type === 4"
class="text"
>has been banned.</span>
<span class="date">{{ getDate }}</span>
</span>
<div class="presense-contain">
<span>
<span class="username" @click="openUserInformation">{{ this.$props.username }}</span>
<span v-if="type === 1" class="text">has joined the server!</span>
<span v-if="type === 2" class="text">has left the server.</span>
<span v-if="type === 3" class="text">has been kicked.</span>
<span v-if="type === 4" class="text">has been banned.</span>
<span class="date">{{ getDate }}</span>
</span>
</div>
<div class="drop-down-button" ref="drop-down-button" @click="openContextMenu">
<i class="material-icons">more_vert</i>
</div>
</div>
</div>
</template>
@ -104,10 +95,10 @@ import messageFormatter from "@/utils/messageFormatter.js";
import config from "@/config.js";
import friendlyDate from "@/utils/date";
import path from "path";
import windowProperties from '@/utils/windowProperties';
import windowProperties from "@/utils/windowProperties";
import { mapState } from "vuex";
import messagesService from '../../services/messagesService';
import messagesService from "../../services/messagesService";
export default {
props: [
@ -132,20 +123,19 @@ export default {
data() {
return {
hover: false
}
};
},
methods: {
openContextMenu(event) {
const x = event.clientX;
const y = event.clientY;
this.$store.dispatch('setMessageContext', {
this.$store.dispatch("setMessageContext", {
x,
y,
channelID: this.channelID,
messageID: this.messageID,
message: this.message,
uniqueID: this.uniqueID
});
},
openUserInformation() {
@ -157,27 +147,35 @@ export default {
editMessage() {
if (this.uniqueID !== this.user.uniqueID) return;
this.dropDownVisable = false;
this.$store.dispatch("setEditMessage", {channelID: this.channelID, messageID: this.messageID, message: this.message});
this.$store.dispatch("setEditMessage", {
channelID: this.channelID,
messageID: this.messageID,
message: this.message
});
},
contentDoubleClickEvent(event){
if (event.target.classList.contains("content") || event.target.closest('.user-info')) this.editMessage();
contentDoubleClickEvent(event) {
if (
event.target.classList.contains("content") ||
event.target.closest(".user-info")
)
this.editMessage();
},
clamp(num, min, max) {
return num <= min ? min : num >= max ? max : num;
},
calculateAspectRatioFit(srcWidth, srcHeight, maxWidth, maxHeight) {
let ratio = Math.min(maxWidth / srcWidth, maxHeight / srcHeight);
return { width: srcWidth*ratio, height: srcHeight*ratio };
return { width: srcWidth * ratio, height: srcHeight * ratio };
},
imageSize() {
const files = this.$props.files;
if (!files || files.length === 0 || !files[0].dimensions)
return undefined;
const messageLog = document.querySelector('.message-logs');
const messageLog = document.querySelector(".message-logs");
const w = messageLog.offsetWidth;
const h = messageLog.offsetHeight;
let minWidth = w / 4;
let minHeight = h / 4;
if (w <= 800) {
@ -185,34 +183,39 @@ export default {
minHeight = h / 1.7;
}
const dimensions = this.$props.files[0].dimensions
const dimensions = this.$props.files[0].dimensions;
const srcWidth = dimensions.width;
const srcHeight = dimensions.height;
const newDimentions = this.calculateAspectRatioFit(srcWidth, srcHeight, minWidth, minHeight);
const newDimentions = this.calculateAspectRatioFit(
srcWidth,
srcHeight,
minWidth,
minHeight
);
const imageTag = this.$refs['image'];
const imageTag = this.$refs["image"];
imageTag.firstChild.style.width = "100%"
imageTag.firstChild.style.height = "100%"
imageTag.firstChild.style.width = "100%";
imageTag.firstChild.style.height = "100%";
imageTag.style.width = this.clamp(newDimentions.width, 0, srcWidth) + "px"
imageTag.style.height = this.clamp(newDimentions.height, 0, srcHeight) + "px"
imageTag.style.width =
this.clamp(newDimentions.width, 0, srcWidth) + "px";
imageTag.style.height =
this.clamp(newDimentions.height, 0, srcHeight) + "px";
},
onResize(dimentions) {
this.imageSize();
},
}
},
watch: {
getWindowWidth(dimentions) {
this.onResize(dimentions)
this.onResize(dimentions);
}
},
mounted() {
const files = this.files;
if (!files || files.length === 0 || !files[0].dimensions)
return undefined;
if (!files || files.length === 0 || !files[0].dimensions) return undefined;
this.imageSize();
},
computed: {
@ -254,33 +257,40 @@ export default {
return this.$store.getters.user;
},
getWindowWidth() {
return {width: windowProperties.resizeWidth, height: windowProperties.resizeHeight};
},
return {
width: windowProperties.resizeWidth,
height: windowProperties.resizeHeight
};
}
}
};
</script>
<style scoped>
<style scoped lang="scss">
.container {
position: relative;
z-index: 1;
}
.drop-down-button{
.drop-down-button {
opacity: 0;
transition: 0.2s;
position: relative;
z-index: 1;
}
.container:hover .drop-down-button{
.container:hover .drop-down-button {
opacity: 1;
}
.presence-message {
margin: 10px;
display: flex;
color: white;
overflow: hidden;
}
.presense-contain {
padding: 10px;
display: table;
color: white;
@ -289,24 +299,25 @@ export default {
background: rgba(0, 0, 0, 0.356);
}
.presence-message .text {
margin-left: 5px;
font-size: 15px;
}
.presence-message .username {
font-size: 15px;
font-weight: bold
font-weight: bold;
}
.presence-message.join {
color: #29BF12;
.presence-message.join .text {
color: #29bf12;
}
.presence-message.leave {
.presence-message.leave .text {
color: rgb(150, 139, 139);
}
.presence-message.kick {
color: #FF9914;
.presence-message.kick .text {
color: #ff9914;
}
.presence-message.ban {
.presence-message.ban .text {
color: #d92121;
}
@ -318,6 +329,8 @@ export default {
border-left: 8px solid rgba(184, 184, 184, 0.219);
border-right: none !important;
}
.ownMessageLeft .avatar {
margin-right: 0px;
margin-left: 5px;
@ -338,12 +351,8 @@ export default {
.ownMessage .content {
background: rgba(184, 184, 184, 0.219);
}
.ownMessage.halloween .content {
background: rgba(255, 135, 31, 0.43);
}
.ownMessage.halloween .triangle-inner {
background: rgba(255, 135, 31, 0.43);
}
.ownMessage .date {
color: rgb(209, 209, 209);
}
@ -397,11 +406,11 @@ export default {
}
.triangle-inner {
width: 0;
height: 0;
border-top: 9px solid transparent;
border-bottom: 0px solid transparent;
border-right: 8px solid rgba(0, 0, 0, 0.301);
width: 0;
height: 0;
border-top: 9px solid transparent;
border-bottom: 0px solid transparent;
border-right: 8px solid rgba(0, 0, 0, 0.301);
}
.content {
@ -433,7 +442,6 @@ export default {
width: 100px;
object-fit: contain;
height: 100px;
}
.image-content:hover img {
filter: brightness(70%);
@ -453,9 +461,6 @@ export default {
color: rgb(199, 199, 199);
text-decoration: underline;
}
.username.halloween-color {
color: orange
}
.date {
color: rgb(177, 177, 177);
font-size: 10px;
@ -489,8 +494,6 @@ export default {
align-content: center;
}
.message .other-information div .material-icons {
font-size: 15px;
color: rgb(306, 306, 306);
@ -505,14 +508,11 @@ export default {
}
@media (max-width: 468px) {
}
</style>
<style>
.code-inline{
.code-inline {
background: rgba(0, 0, 0, 0.322);
}
.msg-link {

View file

@ -1,7 +1,7 @@
<template>
<div
class="my-mini-information"
:style="{backgroundColor: getStatusColor}"
@mouseover="hover = true" @mouseleave="hover = false"
>
@ -9,7 +9,7 @@
<profile-picture
:url="`${avatar}${hover ? '' : '?type=png'}`"
:admin="user.admin"
size="50px"
size="35px"
:hover="true"
@click.native="openUserInformation"
/>
@ -30,7 +30,7 @@
class="current-status"
:src="getStatus"
>
<i class="material-icons expand-status-icon">expand_more</i>
<i class="material-icons expand-status-icon">expand_less</i>
<transition name="show-status-list">
<statusList
v-if="status.isPoppedOut"
@ -49,12 +49,6 @@
<i class="material-icons">error</i>
</div>
</div>
<div
class="setting-icon"
@click="openSettings"
>
<i class="material-icons">settings</i>
</div>
</div>
</template>
@ -123,12 +117,7 @@ export default {
this.$store.dispatch("changeStatus", result.data.set);
}
},
openSettings() {
this.$store.dispatch("setPopoutVisibility", {
name: "settings",
visibility: true
});
}
}
};
</script>
@ -139,8 +128,9 @@ export default {
.profile-pic-outer{
z-index:9999;
display: flex;
margin-left: 10px;
margin-left: 10px;
margin-right: 10px;
cursor: pointer;
}
.survay-button {
padding: 10px;
@ -160,6 +150,7 @@ export default {
width: 24px;
font-size: 30px;
color: cyan;
cursor: pointer;
}
.show-status-list-enter-active,
@ -169,7 +160,7 @@ export default {
.show-status-list-enter,
.show-status-list-leave-to {
opacity: 0;
transform: translateY(-5px);
transform: translateY(20px);
}
.fade-enter-active,
@ -181,19 +172,19 @@ export default {
}
.my-mini-information {
border-radius: 10px;
margin: 5px;
height: 80px;
position: relative;
background: rgba(0, 0, 0, 0.3);;
height: 50px;
display: flex;
align-items: center;
margin-top: 10px;
transition: 0.3s;
flex-shrink: 0;
}
.information {
color: white;
margin-top: -7px;
margin-top: -4px;
flex: 1;
width: 100%;
overflow: hidden;
@ -205,9 +196,9 @@ export default {
margin-right: 15px;
padding: 5px;
border-radius: 50%;
cursor: default;
user-select: none;
transition: 0.3s;
cursor: pointer;
}
.setting-icon:hover {
background: rgba(0, 0, 0, 0.294);
@ -221,10 +212,11 @@ export default {
padding-top: 1px;
padding-left: 5px;
margin-left: 10px;
margin-top: -2px;
margin-top: -10px;
transition: 0.3s;
user-select: none;
border-radius: 10px;
cursor: pointer;
}
.status:hover {
@ -251,14 +243,15 @@ export default {
margin-top: 10px;
text-overflow: ellipsis;
width: 100%;
font-size: 14px;
overflow: hidden;
}
.tag {
color: rgb(199, 199, 199);
font-size: 13px;
font-size: 11px;
display: inline-block;
vertical-align: top;
margin-top: 5px;
margin-top: 1px;
}
</style>

View file

@ -0,0 +1,373 @@
<template>
<div class="navigation" ref="navigation">
<div
class="tool-tip"
ref="toolTip"
:style="{top: toolTipTopPosition + 'px'}"
v-if="toolTipShown"
>{{toolTipLocalName || servers[toolTipServerID].name}}</div>
<div class="container" @mouseleave="mouseLeaveEvent">
<div class="scrollable">
<div class="navigation-items">
<div
class="item material-icons"
:class="{selected: currentTab == 0}"
@click="switchTab(0)"
@mouseenter="localToolTipEvent('Explore', $event)"
>explore</div>
<div
class="item material-icons"
:class="{selected: currentTab == 1, notifyAnimation: DMNotification || friendRequestExists}"
@click="switchTab(1)"
@mouseenter="localToolTipEvent('Direct Message', $event)"
>chat</div>
<div
class="item material-icons"
:class="{selected: currentTab == 2, notifyAnimation: serverNotification}"
@click="switchTab(2)"
@mouseenter="localToolTipEvent('Servers', $event)"
>forum</div>
<div
class="item material-icons"
:class="{selected: currentTab == 3}"
@click="switchTab(3)"
@mouseenter="localToolTipEvent('Changelog', $event)"
>list_alt</div>
</div>
<div class="seperater" />
<div class="server-items" v-if="currentTab === 2">
<server-template
v-for="(data, index) in serversArr"
:serverData="data"
@click.native="openServer(data.server_id)"
:key="index.server_id"
/>
</div>
</div>
<div class="seperater" />
<div
class="item material-icons"
v-if="currentTab === 1"
@click="addFriend"
@mouseenter="localToolTipEvent('Add Friend', $event)"
>person_add</div>
<div
class="item material-icons"
v-if="currentTab === 2"
@click="addServer"
@mouseenter="localToolTipEvent('Add Server', $event)"
>add</div>
<div
class="item material-icons"
@click="openSettings"
@mouseenter="localToolTipEvent('Settings', $event)"
>settings</div>
</div>
</div>
</template>
<script>
import { bus } from "@/main.js";
import config from "@/config.js";
import ServerTemplate from "@/components/app/ServerTemplate/ServerTemplate";
export default {
components: { ServerTemplate },
data() {
return {
avatarDomain: config.domain + "/avatars",
toolTipShown: false,
toolTipTopPosition: 0,
toolTipServerID: null,
toolTipLocalName: null
};
},
methods: {
dismissNotification(channelID) {
const notifications = this.$store.getters.notifications.find(function(e) {
return e.channelID === channelID;
});
if (notifications && notifications.count >= 1 && document.hasFocus()) {
this.$socket.emit("notification:dismiss", { channelID });
}
},
openServer(serverID) {
const server = this.servers[serverID];
const lastSelectedChannel = JSON.parse(
localStorage.getItem("selectedChannels") || "{}"
)[serverID];
const defaultChannelID = server.default_channel_id;
const channels = this.$store.getters.channels;
const channel = channels[lastSelectedChannel || defaultChannelID];
this.dismissNotification(channel.channelID);
this.$store.dispatch("servers/setSelectedServerID", serverID);
this.$store.dispatch("openChannel", channel);
},
switchChannel(isServer) {
const serverChannelID = this.$store.state.channelModule.serverChannelID;
const DMChannelID = this.$store.state.channelModule.DMChannelID;
if (isServer) {
this.$store.dispatch("selectedChannelID", serverChannelID);
const channel = this.$store.state.channelModule.channels[
serverChannelID
];
this.$store.dispatch("setChannelName", channel ? channel.name : "");
this.dismissNotification(serverChannelID);
} else {
const channel = this.$store.state.channelModule.channels[DMChannelID];
this.$store.dispatch(
"setChannelName",
channel ? channel.recipients[0].username : ""
);
this.$store.dispatch("selectedChannelID", DMChannelID);
this.dismissNotification(DMChannelID);
}
},
switchTab(index) {
localStorage.setItem("currentTab", index);
this.$store.dispatch("setCurrentTab", index);
if (index == 1) {
//1: direct message tab.
this.switchChannel(false);
} else if (index === 2) {
//2: server tab
this.switchChannel(true);
}
},
openSettings() {
this.$store.dispatch("setPopoutVisibility", {
name: "settings",
visibility: true
});
},
localToolTipEvent(name, event) {
const rect = event.target.getBoundingClientRect();
this.toolTipLocalName = name;
this.toolTipTopPosition = rect.top - this.getTopHeight() + 16;
this.toolTipShown = true;
},
serverToolTipEvent({ serverID, top }) {
this.toolTipLocalName = null;
this.toolTipServerID = serverID;
this.toolTipTopPosition = top - this.getTopHeight() + 16;
this.toolTipShown = true;
},
mouseLeaveEvent() {
this.toolTipShown = false;
this.toolTipServerID = null;
this.toolTipLocalName = null;
},
getTopHeight() {
return window.innerHeight - this.$refs["navigation"].offsetHeight;
},
addServer() {
this.$store.dispatch("setPopoutVisibility", {
name: "addServer",
visibility: true
});
},
addFriend() {
this.$store.dispatch('setAllPopout', {
show: true,
type: "ADD_FRIEND",
})
}
},
computed: {
currentTab() {
return this.$store.getters.currentTab;
},
servers() {
return this.$store.getters["servers/servers"];
},
serversArr() {
const data = this.servers;
return Object.keys(data)
.map(key => {
return data[key];
})
.slice()
.reverse();
},
selectedServerID() {
return this.$store.getters["servers/selectedServerID"];
},
serverNotification() {
const notifications = this.$store.getters.notifications;
const channels = this.$store.getters.channels;
const notification = notifications.find(e => {
return (
channels[e.channelID] &&
channels[e.channelID].server_id &&
e.channelID !== this.$store.getters.selectedChannelID
);
});
return notification;
},
DMNotification() {
const notifications = this.$store.getters.notifications;
const channels = this.$store.getters.channels;
const notification = notifications.find(e => {
return (
channels[e.channelID] &&
!channels[e.channelID].server_id &&
e.channelID !== this.$store.getters.selectedChannelID
);
});
// unopened dm
if (!notification) {
return notifications.find(e => {
return !channels[e.channelID];
});
}
return notification;
},
friendRequestExists() {
const allFriend = this.$store.getters.user.friends;
const result = Object.keys(allFriend).map(function(key) {
return allFriend[key];
});
return result.find(friend => friend.status === 1);
}
},
mounted() {
bus.$on("server-tool-tip", this.serverToolTipEvent);
},
destroyed() {
bus.$off("server-tool-tip", this.serverToolTipEvent);
}
};
</script>
<style lang="scss" scoped>
.navigation {
display: flex;
flex-direction: column;
flex-shrink: 0;
height: 100%;
width: 60px;
background-color: rgba(0, 0, 0, 0.5);
}
.container {
display: flex;
flex-direction: column;
flex-shrink: 0;
height: 100%;
width: 60px;
}
.navigation-items {
display: flex;
flex-direction: column;
width: 100%;
align-self: flex-start;
align-content: center;
flex-shrink: 0;
}
.scrollable {
display: flex;
flex-direction: column;
overflow: hidden;
overflow-y: auto;
scrollbar-width: none;
height: 100%;
align-self: center;
&::-webkit-scrollbar {
width: 0px;
}
}
.item {
position: relative;
display: flex;
flex-shrink: 0;
justify-content: center;
align-content: center;
align-items: center;
color: white;
font-size: 30px;
align-self: center;
width: 60px;
height: 60px;
cursor: pointer;
user-select: none;
opacity: 0.8;
transition: 0.2s;
&:hover {
background: rgba(0, 0, 0, 0.3);
}
&.selected {
background: rgba(0, 0, 0, 0.4);
opacity: 1;
}
}
.server-items {
display: flex;
flex-direction: column;
}
.seperater {
background-color: #d8d8d8;
flex-shrink: 0;
align-self: center;
width: calc(100% - 10px);
height: 2px;
}
.notifyAnimation:before {
content: "!";
color: white;
display: flex;
flex-direction: column;
align-items: center;
align-content: center;
font-size: 15px;
position: absolute;
z-index: 115651;
top: 5px;
right: 5px;
width: 20px;
height: 20px;
animation: notifyAnime;
animation-duration: 1s;
animation-iteration-count: infinite;
animation-fill-mode: forwards;
border-radius: 50%;
background: rgba(255, 23, 23, 0.753);
flex-shrink: 0;
}
@keyframes notifyAnime {
0% {
opacity: 0.2;
}
40% {
opacity: 1;
}
60% {
opacity: 1;
}
100% {
opacity: 0.2;
}
}
.tool-tip {
color: white;
position: absolute;
background: rgba(0, 0, 0, 0.7);
backdrop-filter: blur(3px);
padding: 5px;
max-width: 150px;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
left: 60px;
z-index: 99999;
user-select: none;
cursor: default;
transition: 0.2s;
}
</style>

View file

@ -14,6 +14,8 @@
<GenericPopout key="gp" v-if="popouts.genericMessage"/>
<message-context-menu key="mcm" v-if="popouts.messageContextMenu.messageID"/>
<server-member-context key="smc" v-if="popouts.serverMemberContext.uniqueID"/>
<server-context key="sc" v-if="popouts.allPopout.type === 'SERVER' && popouts.allPopout.show"/>
<add-friend key="af" v-if="popouts.allPopout.type === 'ADD_FRIEND' && popouts.allPopout.show"/>
</transition-group>
</div>
</template>
@ -26,9 +28,11 @@
// context menus
const messageContextMenu = () => import('./Popouts/messageContextMenu');
const ServerMemberContext = () => import('./Popouts/ServerMemberContext');
const ServerContext = () => import('./Popouts/ServerContextMenu.vue');
const AddServer = () => import('./Popouts/AddServer.vue');
const AddFriend = () => import('./Popouts/AddFriend');
const Settings = () => import('./Popouts/Settings.vue');
const TakeSurveyPopout = () => import('./Popouts/TakeSurveyPopout.vue');
const uploadDialog = () => import('./Popouts/uploadDialog.vue');
@ -55,7 +59,10 @@ export default {
ServerSettings,
GenericPopout,
messageContextMenu,
ServerMemberContext
ServerMemberContext,
ServerContext,
AddFriend
},
data() {
return {

View file

@ -0,0 +1,174 @@
<template>
<div class="dark-background" @mousedown="backgroundClick">
<div class="add-friend">
<div class="header">
<div class="icon material-icons">person_add</div>
<div class="name">Add Friend</div>
</div>
<div class="content">
<div class="container">
<div class="description">Add friends by using their username and tag.</div>
<input class="tag" v-model="input" type="text" placeholder="pancake@time" />
<input class="button" @click="addButton" type="button" :value="requestSent ? 'Adding...' :'Add Friend'" />
</div>
</div>
<div class="alerts" :class="{success, error}" v-if="error || success">{{error || success || ""}}</div>
</div>
</div>
</template>
<script>
import RelationshipService from '@/services/RelationshipService.js'
export default {
data() {
return {
input: "",
requestSent: false,
error: null,
success: null,
};
},
computed: {},
methods: {
closeMenu() {
this.$store.dispatch("setAllPopout", {
show: false,
type: "ADD_FRIEND"
});
},
backgroundClick(e) {
if (e.target.classList.contains("dark-background")) {
this.closeMenu();
}
},
async addButton(e) {
if (this.requestSent) return;
this.error = null;
this.success = null;
this.requestSent = true;
const split = this.input.trim().split("@");
// validation
if ( split.length <2 || split.length >2 || split[1] === "" || split[1].length !== 4){
this.requestSent = false;
this.error = "Invalid username or tag.";
return;
}
const username = split[0];
const tag = split[1];
const {ok, error, result} = await RelationshipService.post({username, tag})
this.requestSent = false;
if ( ok ) {
this.success = result.data.message
} else {
if (error.response === undefined) {
this.error = "Can't connect to server.";
return
}
this.error = error.response.data.errors[0].msg;
}
}
}
};
</script>
<style scoped lang="scss">
.dark-background {
position: absolute;
top: 0;
bottom: 0;
left: 0;
right: 0;
background: rgba(0, 0, 0, 0.541);
z-index: 111111;
display: flex;
}
.add-friend {
height: 300px;
width: 400px;
background: rgba(24, 24, 24, 0.9);
backdrop-filter: blur(10px);
display: flex;
flex-direction: column;
position: relative;
margin: auto;
overflow: hidden;
}
.header {
display: flex;
flex-shrink: 0;
background-color: rgba(41, 41, 41, 0.801);
color: white;
height: 50px;
align-content: center;
align-items: center;
justify-content: center;
.icon {
margin-right: 5px;
}
}
.content {
display: flex;
position: relative;
flex-direction: column;
height: 100%;
width: 100%;
color: rgb(255, 255, 255);
}
.container {
display: flex;
flex-direction: column;
margin: auto;
align-items: center;
align-content: center;
margin-top: 40px;
}
.tag {
color: white;
background: transparent;
border: solid 1px #afafaf;
}
.description {
margin-bottom: 30px;
}
.button {
align-self: center;
color: white;
width: initial;
background: #1998ff;
cursor: pointer;
&:hover {
background: #157fd6;
}
}
.alerts {
display: flex;
flex-direction: column;
position: absolute;
bottom: 0;
left: 0;
right: 0;
height: 30px;
color: white;
align-items: center;
align-content: center;
justify-content: center;
&.success {
background: #008a00;
}
&.error {
background: #8a0000;
}
}
</style>

View file

@ -0,0 +1,102 @@
<template>
<div class="drop-down-menu" v-click-outside="outsideClick">
<div class="item" @click="createInvite" >Manage Invites</div>
<div class="item" v-if="checkServerCreator" @click="showSettings" >Server Settings</div>
<div class="item warn" v-if="!checkServerCreator" @click="leaveServer">Leave Server</div>
</div>
</template>
<script>
import messagesService from '@/services/messagesService';
import ServerService from '../../../../services/ServerService';
export default {
data() {
return {
};
},
methods: {
closeMenu() {
this.$store.dispatch('setAllPopout', {
show: false,
type: null,
})
},
outsideClick(event) {
if (event.target.classList.contains('options-button')) return;
this.closeMenu();
},
setPosition() {
const y = this.contextDetails.y;
const x = this.contextDetails.x;
this.$el.style.top = y + "px";
this.$el.style.left = x + "px";
},
showSettings() {
this.closeMenu()
this.$store.dispatch('setServerSettings', {serverID: this.contextDetails.serverID})
},
createInvite(serverID) {
this.closeMenu()
this.$store.dispatch("setPopoutVisibility", {
name: "showServerInviteMenu",
visibility: true
});
},
async leaveServer() {
this.closeMenu()
const {ok, error, result} = await ServerService.leaveServer(this.contextDetails.serverID);
}
},
mounted() {
this.setPosition();
},
watch: {
contextDetails() {
this.setPosition();
}
},
computed: {
contextDetails() {
return this.$store.getters.popouts.allPopout
},
user() {
return this.$store.getters.user;
},
checkServerCreator() {
return this.contextDetails.creatorUniqueID === this.user.uniqueID
}
},
};
</script>
<style lang="scss" scoped>
.drop-down-menu {
position: absolute;
top: 0;
left: 0;
background: rgba(0, 0, 0, 0.7);
backdrop-filter: blur(5px);
z-index: 99999;
user-select: none;
color: white;
}
.item {
padding: 10px;
transition: 0.3s;
font-size: 13px;
cursor: pointer;
&:hover {
background: rgba(46, 46, 46, 0.651);
}
&.warn {
color: rgb(255, 59, 59);
}
}
</style>

View file

@ -39,7 +39,6 @@ import config from "@/config.js";
import { bus } from "@/main";
import Spinner from "@/components/Spinner.vue";
import ServerService from "@/services/ServerService";
import { mapState } from "vuex";
export default {
components: {Spinner},
@ -84,9 +83,9 @@ export default {
}
},
computed: {
...mapState({
serverID: state => state.popoutsModule.serverIDContextMenu
})
serverID() {
return this.$store.getters.popouts.allPopout.serverID
},
}
};
</script>

View file

@ -106,23 +106,21 @@ export default {
position: absolute;
top: 0;
left: 0;
background: rgba(31, 31, 31, 0.995);
background: rgba(0, 0, 0, 0.7);
backdrop-filter: blur(5px);
z-index: 99999;
padding: 5px;
border-radius: 5px;
user-select: none;
color: white;
}
.item {
padding: 5px;
margin: 2px;
padding: 10px;
border-radius: 5px;
transition: 0.3s;
font-size: 13px;
cursor: pointer;
&:hover {
background: rgb(46, 46, 46);
background: rgba(46, 46, 46, 0.651);
}
&.warn {
color: rgb(255, 59, 59);

View file

@ -2,6 +2,13 @@
<div class="my-profile-panel">
<div class="switches">
<div class="checkbox"
@click="toggleNotificationSounds">
<div class="checkbox-box" :class="{selected: !notificationSettings.disableNotificationSound}" />
<div class="checkbox-name">
Notification Sounds
</div>
</div>
<div class="checkbox"
@click="toggleNotification()">
<div class="checkbox-box" :class="{selected: !notificationSettings.disableDesktopNotification}" />
@ -23,7 +30,8 @@ import SettingsService from '@/services/settingsService.js';
export default {
data() {
return {
isElectron: window && window.process && window.process.type
isElectron: window && window.process && window.process.type,
};
},
methods: {
@ -39,7 +47,7 @@ export default {
const setting = this.notificationSettings['disableDesktopNotification'];
if (setting && setting === true && !this.isElectron) {
if (Notification.permission === "denied") {
alert("Notifications blocked. Please enable them in your browser.");
alert("Notifications permission is blocked. Allow notifications from this website in your browsers permission settings.");
}
Notification.requestPermission().then(function(result) {
if (result === 'denied' || result === 'default') return;
@ -49,6 +57,12 @@ export default {
}
this.toggleSetting('disableDesktopNotification');
},
toggleNotificationSounds(){
console.log("test")
const setting = this.notificationSettings['disableNotificationSound'];
console.log(!!!setting)
this.$store.dispatch('settingsModule/updateNotification', {disableNotificationSound: !setting})
}
},
mounted() {
if (!this.isElectron && this.notificationSettings.disableDesktopNotification === undefined) {
@ -69,18 +83,19 @@ export default {
.switches {
display: flex;
flex-direction: column;
margin: 20px;
user-select: none;
}
.checkbox {
display: flex;
margin-top: 10px;
}
.checkbox-box {
background: rgba(88, 88, 88, 0.74);
height: 20px;
width: 20px;
margin: auto;
margin-right: 10px;
transition: 0.3s;
border-radius: 5px;

View file

@ -106,23 +106,20 @@ export default {
position: absolute;
top: 0;
left: 0;
background: rgba(31, 31, 31, 0.895);
background: rgba(0, 0, 0, 0.7);
backdrop-filter: blur(5px);
z-index: 99999;
padding: 5px;
border-radius: 5px;
user-select: none;
color: white;
}
.item {
padding: 5px;
margin: 2px;
border-radius: 5px;
padding: 10px;
transition: 0.3s;
font-size: 13px;
cursor: pointer;
&:hover {
background: rgb(46, 46, 46);
background: rgba(46, 46, 46, 0.651);
}
&.warn {
color: rgb(255, 59, 59);

View file

@ -105,7 +105,7 @@ export default {
);
},
openChat() {
bus.$emit('changeTab', 1)
this.$store.dispatch('setCurrentTab', 1)
this.$store.dispatch("openChat", {
uniqueID: this.uniqueID,
channelName: this.user.username

View file

@ -1,24 +1,18 @@
<template>
<div class="left-panel">
<MyMiniInformation />
<div class="actions">
<div class="action" @click="openAddServer">
<div class="material-icons">add</div>
<div class="text">Add Server</div>
<navigation />
<div class="right">
<div class="server-banner" v-if="selectedServerID">
<div class="banner-image"></div>
<div class="sub-banner" >
<div class="text" :title="servers[selectedServerID].name">{{servers[selectedServerID].name}}</div>
<div class="options-button material-icons" @click="openServerContext">more_vert</div>
</div>
</div>
<div class="action" @click="openExploreTab">
<div class="material-icons">explore</div>
<div class="text">Explore</div>
<div class="channels-list">
<channels-list v-if="selectedServerID" :server-i-d="selectedServerID" />
</div>
</div>
<div class="list">
<server
v-for="(data, index) in servers"
:key="index.server_id"
:server-data="data"
:open-channel="selectedServerID && selectedServerID === data.server_id"
@click.native="toggleChannel(data.server_id, $event)"
/>
<MyMiniInformation />
</div>
</div>
</template>
@ -26,12 +20,16 @@
<script>
import MyMiniInformation from "@/components/app/MyMiniInformation.vue";
import Server from "@/components/app/ServerTemplate/ServerTemplate.vue";
import {bus} from '@/main'
import ChannelsList from "@/components/app/ServerTemplate/ChannelsList.vue";
import Navigation from "@/components/app/Navigation.vue";
import { bus } from "@/main";
export default {
components: {
MyMiniInformation,
Server
ChannelsList,
Server,
Navigation
},
data() {
return {
@ -45,98 +43,147 @@ export default {
visibility: true
});
},
toggleChannel(serverID, event) {
if (!event.target.closest('.small-view') || event.target.closest('.options-context-button') || event.target.closest('.options-context-menu')) return;
if (this.openedServer === serverID) {
this.openedServer = null;
this.$store.dispatch('servers/setSelectedServerID', null)
}
else{
this.openedServer = serverID;
this.$store.dispatch('servers/setSelectedServerID', serverID)
}
clickServer(serverID, event) {
this.openedServer = serverID;
this.$store.dispatch("servers/setSelectedServerID", serverID);
},
openExploreTab() {
bus.$emit('changeTab', 0)
this.$store.dispatch('setCurrentTab', 0)
},
openServerContext(event) {
const rect = event.target.getBoundingClientRect();
if (this.checkServerContextOpened) {
this.$store.dispatch('setAllPopout', {
show: false,
type: null
})
return;
}
this.$store.dispatch('setAllPopout', {
show: true,
type: 'SERVER',
serverID: this.servers[this.selectedServerID].server_id,
creatorUniqueID: this.servers[this.selectedServerID].creator.uniqueID,
x: rect.left - 30,
y: rect.top + 35,
})
}
},
computed: {
servers() {
const data = this.$store.getters['servers/servers'];
return Object.keys(data).map(key => {
return data[key];
}).slice().reverse()
return this.$store.getters["servers/servers"];
},
serversArr() {
const data = this.servers;
return Object.keys(data)
.map(key => {
return data[key];
})
.slice()
.reverse();
},
selectedServerID() {
return this.$store.getters['servers/selectedServerID'];
return this.$store.getters["servers/selectedServerID"];
},
checkServerContextOpened() {
const contextDetail = this.$store.getters.popouts.allPopout;
return contextDetail.show && contextDetail.type === "SERVER";
}
}
};
</script>
<style scoped lang="scss" >
.left-panel {
background-color: rgba(0, 0, 0, 0.6);
height: 100%;
background-color: rgba(0, 0, 0, 0.671);
width: 300px;
flex-shrink: 0;
display: flex;
flex-direction: column;
flex-direction: row;
z-index: 1;
}
.list {
margin: 2px;
margin-left: 5px;
margin-right: 5px;
flex: 1;
overflow: auto;
user-select: none;
}
/* ------- SCROLL BAR -------*/
/* width */
.list::-webkit-scrollbar {
width: 3px;
}
/* Track */
.list::-webkit-scrollbar-track {
background: #8080806b;
}
/* Handle */
.list::-webkit-scrollbar-thumb {
background: #f5f5f559;
}
/* Handle on hover */
.list::-webkit-scrollbar-thumb:hover {
background: #f5f5f59e;
}
.actions {
color: white;
.channels-list {
display: flex;
justify-content: center;
flex: 1;
height: 100%;
}
.left {
display: flex;
align-content: center;
align-items: center;
flex-direction: column;
flex-shrink: 0;
background: rgba(29, 29, 29, 0.37);
width: 70px;
overflow-x: auto;
}
.right {
display: flex;
flex-direction: column;
flex: 1;
width: 100%;
height: 100%;
overflow: hidden;
}
.server-banner {
display: flex;
overflow: hidden;
position: relative;
flex-direction: row;
background-color: rgb(32, 32, 32);
height: 150px;
}
.banner-image {
position: absolute;
background-image: url("../../assets/background.jpg");
background-position: center;
background-size: cover;
height: 100%;
z-index: 2;
width: 100%;
}
.sub-banner {
display: flex;
color: white;
background-color: rgba(0, 0, 0, 0.507);
align-self: flex-end;
height: 35px;
width: 100%;
align-items: center;
padding-left: 10px;
position: relative;
backdrop-filter: blur(15px);
z-index: 2;
user-select: none;
.action {
display: flex;
padding: 5px;
margin: 2px;
align-items: center;
align-content: center;
cursor: pointer;
color: rgb(223, 223, 223);
border-radius: 5px;
transition: 0.2s;
.material-icons {
color: white;
margin-right: 5px;
}
&:hover {
background: rgba(0, 0, 0, 0.24);
}
overflow: hidden;
cursor: default;
.text {
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
width: 100%;
}
}
.options-button {
display: flex;
align-items: center;
align-content: center;
justify-content: center;
flex-shrink: 0;
height: 35px;
width: 35px;
transition: 0.2s;
cursor: pointer;
user-select: none;
font-size: 20px;
&:hover {
background: rgba(0, 0, 0, 0.322);
}
}
</style>

View file

@ -54,22 +54,26 @@ export default {
.channel {
display: flex;
align-items: center;
margin: 5px;
margin-top: 3px;
margin-bottom: 3px;
padding: 5px;
border-radius: 5px;
transition: 0.3s;
font-size: 14px;
cursor: pointer;
color: white;
user-select: none;
overflow: hidden;
padding-right: 10px;
padding-left: 10px;
}
.channel:hover {
background: rgba(139, 139, 139, 0.288);
background: rgba(0, 0, 0, 0.192);
}
.selected {
background: rgba(139, 139, 139, 0.288);
.channel.selected {
background: rgba(0, 0, 0, 0.288);
}
.channel-name {
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
margin-left: 5px;

View file

@ -65,6 +65,11 @@ export default {
},
methods: {
openChannel(channel) {
// add to local storage
const selectedChannels = JSON.parse(localStorage.getItem('selectedChannels') || '{}')
selectedChannels[this.serverID] = channel.channelID;
localStorage.setItem('selectedChannels', JSON.stringify(selectedChannels));
const notificationExists = this.$store.getters.notifications.find(n => n.channelID === channel.channelID)
if (notificationExists && document.hasFocus()) {
@ -79,11 +84,21 @@ export default {
</script>
<style scoped>
.channel-list {
background: rgba(0, 0, 0, 0.288);
.channels-list {
height: 100%;
width: 100%;
display: flex;
flex: 1;
overflow: hidden;
overflow-y: auto;
flex-direction: column;
}
/* ------- SCROLL BAR -------*/
/* width */
.channels-list::-webkit-scrollbar {
width: 3px;
}
</style>

View file

@ -1,243 +1,126 @@
<template>
<div :class="{server: true, 'add-server': mode === 'ADD_SERVER'}">
<div :class="{'small-view': true, notifyAnimation: notification}">
<profile-picture
v-if="!mode"
size="50px"
:url="`${avatarDomain}/${serverData.avatar}`"
/>
<div
v-if="mode === 'ADD_SERVER'"
class="add-icon"
>
<i class="material-icons">add</i>
</div>
<div class="server-name">
{{ mode === 'ADD_SERVER'? 'Create / Join Server' : serverData.name }}
</div>
<div
v-if="mode !== 'ADD_SERVER'"
ref="contextMenuButton"
class="options-context-button"
@click="showContextMenu = !showContextMenu"
>
<i class="material-icons">more_vert</i>
</div>
<div
v-if="showContextMenu"
v-click-outside="closeContextMenu"
class="options-context-menu"
>
<div
class="menu-button"
@click="createInvite(serverData.server_id)"
>
Manage Invites
</div>
<div
v-if="serverData.creator.uniqueID !== user.uniqueID"
class="menu-button warn"
@click="leaveServer(serverData.server_id)"
>
Leave Server
</div>
<div
v-if="serverData.creator.uniqueID === user.uniqueID"
class="menu-button"
@click="showSettings()"
>
Server Settings
</div>
</div>
</div>
<div ref="container">
<channels-list
v-if="openChannel"
:server-i-d="serverData.server_id"
/>
</div>
<div class="server" :class="{selected: selectedServerID === serverData.server_id, notifyAnimation: notification }" @contextmenu.prevent="contextEvent" @mouseenter="hoverEvent" @mouseover="hover = true" @mouseleave="hover = false">
<profile-picture size="45px" :url="`${avatarDomain}/${serverData.avatar}${hover ? '' : '?type=png'}`" />
</div>
</template>
<script>
import {bus} from "../../../main.js"
import config from "@/config.js";
import ChannelsList from "@/components/app/ServerTemplate/ChannelsList.vue";
import ProfilePicture from "@/components/ProfilePictureTemplate.vue";
import ServerService from "@/services/ServerService";
import smoothReflow from "vue-smooth-reflow";
export default {
components: { ProfilePicture, ChannelsList },
mixins: [smoothReflow],
props: ["serverData", "openChannel", "mode"],
components: { ProfilePicture },
props: ["serverData"],
data() {
return {
showContextMenu: false,
showChannels: false,
avatarDomain: config.domain + "/avatars"
avatarDomain: config.domain + "/avatars",
hover: false
};
},
computed: {
user() {
return this.$store.getters.user;
},
selectedServerID() {
return this.$store.getters["servers/selectedServerID"];
},
notification() {
const notifications = this.$store.getters.notifications;
const channels = this.$store.getters.channels
const channels = this.$store.getters.channels;
const notification = notifications.find(e => {
return channels[e.channelID] && channels[e.channelID].server_id && this.serverData && channels[e.channelID].server_id === this.serverData.server_id
})
return (
channels[e.channelID] &&
channels[e.channelID].server_id &&
this.serverData &&
channels[e.channelID].server_id === this.serverData.server_id
);
});
return notification;
}
},
mounted() {
this.$smoothReflow({
el: this.$refs.container
});
},
methods: {
showSettings() {
this.showContextMenu = false;
this.$store.dispatch('setServerSettings', {serverID: this.serverData.server_id})
hoverEvent(event) {
const rect = event.target.getBoundingClientRect();
//let centerX = targetNode.offsetLeft + targetNode.offsetWidth / 2;
//let centerY = targetNode.offsetTop + targetNode.offsetHeight / 2;
bus.$emit('server-tool-tip', {serverID: this.serverData.server_id, top: rect.top})
},
createInvite(serverID) {
this.showContextMenu = false;
this.$store.dispatch("setServerIDContextMenu", serverID);
this.$store.dispatch("setPopoutVisibility", {
name: "showServerInviteMenu",
visibility: true
});
},
closeContextMenu(event) {
if (
event.target.closest(".options-context-button") ===
this.$refs.contextMenuButton
)
return;
this.showContextMenu = false;
},
async leaveServer(serverID) {
this.showContextMenu = false;
const {ok, error, result} = await ServerService.leaveServer(serverID);
contextEvent(event) {
this.$store.dispatch('setAllPopout', {
show: true,
type: 'SERVER',
serverID: this.serverData.server_id,
creatorUniqueID: this.serverData.creator.uniqueID,
x: event.clientX,
y: event.clientY
})
}
}
},
};
</script>
<style scoped>
<style scoped lang="scss">
.server {
position: relative;
z-index: 1;
display: flex;
align-self: center;
width: 60px;
height: 60px;
flex-shrink: 0;
justify-content: center;
align-content: center;
align-items: center;
user-select: none;
transition: 0.2s;
cursor: pointer;
&:hover {
background: rgba(0, 0, 0, 0.3);
}
&.selected {
background: rgba(0, 0, 0, 0.4);
opacity: 1;
}
}
.notifyAnimation:before{
content: '';
.notifyAnimation:before {
content: "!";
color: white;
display: flex;
flex-direction: column;
align-items: center;
align-content: center;
justify-content: center;
font-size: 15px;
position: absolute;
z-index: -1;
top: 0;
left: 0;
right: 0;
bottom: 0;
z-index: 115651;
top: 5px;
right: 5px;
width: 20px;
height: 20px;
animation: notifyAnime;
animation-duration: 1s;
animation-iteration-count: infinite;
animation-fill-mode: forwards;
border-radius: 5px;
border-radius: 50%;
background: rgba(255, 23, 23, 0.753);
}
@keyframes notifyAnime {
0%{
background: rgba(255, 0, 0, 0.198);
0% {
opacity: 1;
}
40%{
background: rgba(255, 0, 0, 0.411);
40% {
opacity: 0.9;
}
60%{
background: rgba(255, 0, 0, 0.411);
60% {
opacity: 1;
}
100%{
background: rgba(255, 0, 0, 0.198);
100% {
opacity: 0.2;
}
}
.server {
color: white;
display: flex;
flex-direction: column;
background-color: rgba(0, 0, 0, 0.137);
border-radius: 5px;
margin: 5px;
transition: 0.3s;
}
.server:hover {
background: rgba(0, 0, 0, 0.288);
}
.material-icons {
transition: 0.3s;
}
.add-server:hover .material-icons {
color: rgba(20, 255, 39, 0.726);
}
.small-view {
padding-right: 0;
display: flex;
transition: 0.3s;
position: relative;
align-items: center;
padding: 5px;
cursor: pointer;
}
.server-name {
overflow: hidden;
text-overflow: ellipsis;
margin-left: 5px;
flex: 1;
white-space: nowrap;
}
.add-icon {
height: 56px;
display: flex;
align-items: center;
margin-right: 5px;
margin-left: 5px;
}
.add-icon .material-icons {
font-size: 40px;
}
.options-context-button {
display: flex;
align-items: center;
margin-right: 5px;
color: rgba(255, 255, 255, 0.623);
border-radius: 50%;
transition: 0.3s;
}
.options-context-button .material-icons:hover {
color: white;
}
.options-context-menu {
position: absolute;
background: rgba(0, 0, 0, 0.692);
border-radius: 10px;
z-index: 9999;
padding: 5px;
right: 40px;
top: 20px;
}
.menu-button {
padding: 5px;
margin: 2px;
border-radius: 5px;
transition: 0.3s;
}
.menu-button:hover {
background: rgb(47, 47, 47);
}
.warn {
color: red;
}
</style>

View file

@ -67,10 +67,10 @@ export default {
@media (max-width: 600px) {
.left-panel {
position: absolute;
background-color: rgba(39, 39, 39, 0.97);
bottom: 0;
height: calc(100% - 50px);
z-index: 2;
backdrop-filter: blur(15px);
}
}
</style>

View file

@ -1,74 +1,80 @@
<template>
<div class="explore-tab">
<transition name="slidein">
<div class="left-panel"
<div
class="left-panel"
v-show="$mq === 'mobile' && showLeftPanel || ($mq !== 'mobile')"
v-click-outside="hideLeftPanel">
<div class="header">
<div class="icon">
<i class="material-icons">explore</i>
</div>
<div class="details">
<div class="title">Explore</div>
<div class="description">Find new servers, Emojis and more!</div>
</div>
</div>
<div class="items">
<div class="item halloween"
v-for="(tab, index) in tabs"
:key="index"
:class="{selected: selectedTab === index}"
@click="selectedTab = index">
<i class="material-icons">{{tab.icon}}</i>
{{tab.name}}
v-click-outside="hideLeftPanel"
>
<navigation />
<div class="content">
<div class="header">
<div class="icon">
<i class="material-icons">explore</i>
</div>
<div class="details">
<div class="title">Explore</div>
<div class="description">Find new servers, Emojis and more!</div>
</div>
</div>
<div class="items">
<div
class="item"
v-for="(tab, index) in tabs"
:key="index"
:class="{selected: selectedTab === index}"
@click="selectedTab = index"
>
<i class="material-icons">{{tab.icon}}</i>
{{tab.name}}
</div>
<div class="card self-promo" v-if="nertiviaServerHide !== true && !nertiviaServer">
<div class="material-icons close-btn" @click="hideSelfPromo">close</div>
<div class="logo" />
<div class="title">Join the official Nertivia server</div>
<div class="button" @click="joinNertiviaServer">Join</div>
</div>
<div class="card halloween" v-if="halloween">
<div class="pumpkin">🎃</div>
<div class="title">Happy Halloween!</div>
</div>
<div class="card self-promo" v-if="nertiviaServerHide !== true && !nertiviaServer">
<div class="material-icons close-btn" @click="hideSelfPromo">close</div>
<div class="logo"/>
<div class="title">Join the official Nertivia server</div>
<div class="button" @click="joinNertiviaServer">Join</div>
</div>
<div class="card donate-paypal" v-if="donateHide !== true">
<div class="material-icons close-btn" @click="hideDonatePaypal">close</div>
<div class="material-icons heart">favorite</div>
<div class="title">Support Nertivia by donating any amount of money. You will get a supporter badge and more features in the future.</div>
<div class="button" @click="donateButton">Donate With PayPal</div>
<div class="card donate-paypal" v-if="donateHide !== true">
<div class="material-icons close-btn" @click="hideDonatePaypal">close</div>
<div class="material-icons heart">favorite</div>
<div
class="title"
>Support Nertivia by donating any amount of money. You will get a supporter badge and more features in the future.</div>
<div class="button" @click="donateButton">Donate</div>
</div>
</div>
</div>
</div>
</transition>
<div class="right-panel">
<div class="header"> <div class="material-icons left-menu-show-button" @click="showLeftPanel = !showLeftPanel">view_list</div> <i class="material-icons">{{tabs[selectedTab].icon}}</i>{{tabs[selectedTab].name}}</div>
<div class="header">
<div
class="material-icons left-menu-show-button"
@click="showLeftPanel = !showLeftPanel"
>view_list</div>
<i class="material-icons">{{tabs[selectedTab].icon}}</i>
{{tabs[selectedTab].name}}
</div>
<div class="coming-soon" v-if="selectedTab > 0">
<div class="icon">
<i class="material-icons">explore</i>
</div>
<div class="text">Coming soon!</div>
</div>
<component :is="tabs[selectedTab].component" />
<component :is="tabs[selectedTab].component" />
</div>
</div>
</template>
<script>
import { bus } from "@/main";
import Servers from './Explore/servers'
import ServerService from '@/services/ServerService'
import Servers from "./Explore/servers";
import ServerService from "@/services/ServerService";
import Navigation from '@/components/app/Navigation';
export default {
components: { Servers },
components: { Servers, Navigation },
data() {
return {
showLeftPanel: false,
@ -80,11 +86,10 @@ export default {
{ icon: "brush", name: "Themes", component: "" },
{ icon: "message", name: "Message Styles", component: "" }
],
nertiviaServerID: '6572915451527958528',
nertiviaServerHide: localStorage.getItem('exploreTabNertiviaServerPromoHide') === 'true',
donateHide: localStorage.getItem('exploreTabDonateHide') === 'true',
halloween: new Date().getDate() === 31
nertiviaServerID: "6572915451527958528",
nertiviaServerHide:
localStorage.getItem("exploreTabNertiviaServerPromoHide") === "true",
donateHide: localStorage.getItem("exploreTabDonateHide") === "true",
};
},
@ -97,27 +102,29 @@ export default {
}
},
hideSelfPromo() {
localStorage.setItem('exploreTabNertiviaServerPromoHide', true)
localStorage.setItem("exploreTabNertiviaServerPromoHide", true);
this.nertiviaServerHide = true;
},
hideDonatePaypal() {
localStorage.setItem('exploreTabDonateHide', true)
localStorage.setItem("exploreTabDonateHide", true);
this.donateHide = true;
},
async joinNertiviaServer() {
const {ok, error, result} = await ServerService.joinServerById(this.nertiviaServerID, {
socketID: this.$socket.id
});
const { ok, error, result } = await ServerService.joinServerById(
this.nertiviaServerID,
{
socketID: this.$socket.id
}
);
},
donateButton() {
window.open('https://www.patreon.com/nertivia', '_blank');
window.open("https://www.patreon.com/nertivia", "_blank");
}
},
mounted() {
},
mounted() {},
computed: {
nertiviaServer() {
return this.$store.getters['servers/servers'][this.nertiviaServerID]
return this.$store.getters["servers/servers"][this.nertiviaServerID];
}
}
};
@ -125,26 +132,32 @@ export default {
<style lang="scss" scoped>
.explore-tab {
display: flex;
width: 100%;
height: 100%;
background: rgba(0, 0, 0, 0.459);
color: white;
position: relative;
}
.left-panel {
display: flex;
flex-direction: column;
background: rgba(0, 0, 0, 0.274);
flex-direction: row;
background: rgba(0, 0, 0, 0.6);
width: 300px;
flex-shrink: 0;
z-index: 2;
.content {
display: flex;
flex-direction: column;
height: 100%;
}
.items {
user-select: none;
height: 100%;
overflow: auto;
&::-webkit-scrollbar {
width: 3px;
}
.item {
.material-icons {
margin-right: 5px;
@ -152,22 +165,16 @@ export default {
display: flex;
align-content: center;
align-items: center;
background: rgba(0, 0, 0, 0.199);
margin: 5px;
padding: 10px;
border-radius: 5px;
cursor: pointer;
transition: 0.3s;
&:hover {
background: rgba(0, 0, 0, 0.452);
background: rgba(0, 0, 0, 0.2);
}
&.selected {
background: rgba(0, 0, 0, 0.452);
background: rgba(0, 0, 0, 0.4);
}
}
.item.halloween.selected {
background: rgba(255, 166, 0, 0.692);
}
}
.header {
display: flex;
@ -182,19 +189,19 @@ export default {
align-content: center;
justify-content: center;
flex-shrink: 0;
width: 100px;
width: 70px;
position: relative;
.material-icons {
font-size: 70px;
font-size: 50px;
}
&::after{
content: 'BETA';
&::after {
content: "BETA";
position: absolute;
background: #ff3333;
border-radius: 5px;
font-size: 10px;
font-size: 9px;
padding: 2px;
bottom: 15px;
bottom: 20px;
z-index: 999;
}
}
@ -228,28 +235,28 @@ export default {
.title {
text-align: center;
}
.button {
background-color: rgba(0, 0, 0, 0.200);
border-radius: 5px;
padding: 5px;
font-size: 20px;
margin-top: 15px;
cursor: pointer;
transition: 0.3s;
color: rgba(255, 255, 255, 0.924);
&:hover {
background-color: rgba(0, 0, 0, 0.300);
}
.button {
background-color: rgba(0, 0, 0, 0.2);
border-radius: 5px;
padding: 5px;
font-size: 15px;
margin-top: 15px;
cursor: pointer;
transition: 0.3s;
color: rgba(255, 255, 255, 0.924);
&:hover {
background-color: rgba(0, 0, 0, 0.3);
}
}
.close-btn {
position: absolute;
top: 15px;
right: 15px;
cursor: pointer;
}
&.self-promo{
&.self-promo {
.logo {
background-image: url('../../../assets/logo.png');
background-image: url("../../../assets/logo.png");
background-size: cover;
height: 100px;
width: 100px;
@ -269,15 +276,6 @@ export default {
font-size: 60px;
margin-bottom: 10px;
}
}
&.halloween {
.pumpkin {
font-size: 50px;
margin-bottom: 10px;
}
background: orange;
font-size: 30px;
}
}
.coming-soon {
@ -295,13 +293,16 @@ export default {
}
.right-panel {
background: rgba(0, 0, 0, 0.65);
.header {
background: rgba(0, 0, 0, 0.448);
padding: 10px;
display: flex;
align-items: center;
align-content: center;
.material-icons {margin-right: 5px;}
.material-icons {
margin-right: 5px;
}
user-select: none;
cursor: default;
}
@ -312,7 +313,6 @@ export default {
overflow: hidden;
}
.left-menu-show-button {
border-right: solid 1px rgb(158, 158, 158);
padding-right: 5px;
@ -320,14 +320,13 @@ export default {
cursor: pointer;
}
.slidein-enter-active,
.slidein-leave-active {
transition: 0.5s;
}
.slidein-enter, .slidein-leave-to /* .fade-leave-active below version 2.1.8 */ {
/* margin-left: -300px; */
transform: translateX(-300px)
transform: translateX(-300px);
}
@media (max-width: 600px) {
@ -336,11 +335,10 @@ export default {
}
.left-panel {
position: absolute;
background-color: rgba(39, 39, 39, 0.97);
bottom: 0;
height: calc(100% - 44px);
backdrop-filter: blur(15px);
z-index: 2;
}
}
</style>

View file

@ -5,7 +5,7 @@
<div class="title">Filter:</div>
<div class="filter-item">
<div
class="item halloween"
class="item"
v-for="(filter, index) in filters"
:class="{selected: filterSelected === index}"
:key="filter.name"
@ -17,7 +17,7 @@
<div class="title">Sort By:</div>
<div class="filter-item">
<div
class="item halloween"
class="item"
v-for="(sortBy, index) in sortBys"
:class="{selected: sortSelected === index}"
:key="sortBy.name"
@ -26,7 +26,7 @@
</div>
</div>
</div>
<div class="search-area">
<div class="search-area" v-if="false">
<input type="text" :placeholder="`Search for ${name}`" />
</div>
</div>
@ -136,9 +136,6 @@ input {
opacity: 0.8;
}
}
.item.halloween.selected {
color: orange;
}
}

View file

@ -1,6 +1,7 @@
<template>
<div class="item">
<div class="top">
<div class="background-dark"></div>
<profile-picture
size="90px"
:url="`${avatarDomain}/${server.server.avatar}`"
@ -8,7 +9,7 @@
<div class="name">
<div class="name-container">
<span class="inner-name">{{server.server.name}}</span>
<span class="material-icons halloween-icons" v-if="server.verified">check</span>
<span class="material-icons" v-if="server.verified">check</span>
</div>
</div>
</div>
@ -16,7 +17,7 @@
<div class="description">{{server.description}}</div>
<div class="buttons">
<div class="member-count"><div class="material-icons">account_box</div>{{server.total_members}}</div>
<div class="button halloween-button" :class="{selected: joined}" @click="joinButton">
<div class="button" :class="{selected: joined}" @click="joinButton">
<span v-if="joined">Joined</span>
<spinner v-else-if="joinClicked" :size="30"/>
<span v-else-if="!joinClicked">Join Server</span>
@ -70,6 +71,7 @@ export default {
<style lang="scss" scoped>
.item {
position: relative;
width: 250px;
height: 300px;
background: rgba(0, 0, 0, 0.479);
@ -94,6 +96,18 @@ export default {
align-content: center;
align-items: center;
flex-shrink: 0;
position: relative;
.background-dark {
position: absolute;
top: 0;
bottom: 0;
left: 0;
right: 0;
background: rgba(0, 0, 0, 0.6);
}
background-image: url('../../../../assets/background.jpg');
background-position: center;
background-size: cover;
.avatar {
background: rgb(26, 133, 255);
height: 80px;
@ -110,6 +124,7 @@ export default {
width: 100%;
text-align: center;
display: flex;
z-index: 999;
.name-container {
display: flex;
margin: auto;
@ -123,18 +138,16 @@ export default {
color: #66e0ff;
margin-left: 5px;
}
.halloween-icons {
color: orange;
}
}
}
.bottom {
display: flex;
flex-direction: column;
background: rgba(0, 0, 0, 0.194);
background: rgba(0, 0, 0, 0.541);
flex: 1;
height: 100%;
flex-shrink: 0;
.description {
margin: 10px;
flex: 1;
@ -146,6 +159,7 @@ export default {
overflow-wrap: anywhere;
flex-shrink: 0;
}
.buttons {
display: flex;
width: 100%;
@ -187,15 +201,7 @@ export default {
background: grey;
}
}
.halloween-button {
background: rgba(255, 166, 0, 0.8);
&:hover {
background: rgb(255, 166, 0);
}
&.selected {
background: grey;
}
}
}
}
</style>

View file

@ -1,62 +1,42 @@
<template>
<div class="news">
<div class="change-log">
<span class="news-title">Changes in this release</span>
<navigation />
<div class="changelog">
<div class="change-log">
<span class="news-title">Changes in this release</span>
<div
v-for="(change, index) in changelog"
:key="index"
class="change"
>
<div
class="heading"
:style="change.headColor ? `background-color: ${change.headColor}` : ``"
>
<div class="date">
{{ change.date }}
</div>
<div class="changes-title">
{{ change.title }}
</div>
</div>
<div class="information">
<div v-if="change.new">
<strong>What's new?</strong>
<br>
<ul>
<li
v-for="(wnew, index) in change.new"
:key="index"
v-html="wnew"
/>
</ul>
</div>
<div v-if="change.fix">
<strong>Issues fixed</strong>
<br>
<ul>
<li
v-for="(wfix, index) in change.fix"
:key="index"
v-html="wfix"
/>
</ul>
</div>
<div v-if="change.next">
<strong>Up next</strong>
<br>
<ul>
<li
v-for="(wnext, index) in change.next"
:key="index"
v-html="wnext"
/>
</ul>
</div>
<div v-for="(change, index) in changelog" :key="index" class="change">
<div
v-if="change.msg"
v-html="change.msg"
/>
class="heading"
:style="change.headColor ? `background-color: ${change.headColor}` : ``"
>
<div class="date">{{ change.date }}</div>
<div class="changes-title">{{ change.title }}</div>
</div>
<div class="information">
<div v-if="change.new">
<strong>What's new?</strong>
<br />
<ul>
<li v-for="(wnew, index) in change.new" :key="index" v-html="wnew" />
</ul>
</div>
<div v-if="change.fix">
<strong>Issues fixed</strong>
<br />
<ul>
<li v-for="(wfix, index) in change.fix" :key="index" v-html="wfix" />
</ul>
</div>
<div v-if="change.next">
<strong>Up next</strong>
<br />
<ul>
<li v-for="(wnext, index) in change.next" :key="index" v-html="wnext" />
</ul>
</div>
<div v-if="change.msg" v-html="change.msg" />
</div>
</div>
</div>
</div>
@ -65,12 +45,13 @@
<script>
import Spinner from "@/components/Spinner.vue";
import Navigation from "@/components/app/Navigation";
import changelog from "@/utils/changelog.js";
export default {
components: {},
components: { Navigation },
data() {
return {
changelog: changelog
changelog
};
}
};
@ -84,16 +65,19 @@ export default {
height: 100%;
color: white;
overflow: auto;
background: rgba(0, 0, 0, 0.486);
background: #0000005d;
position: relative;
}
.news-title {
display: inline-block;
margin-bottom: 10px;
font-size: 20px;
margin-left: 10px;
margin-top: 20px;
font-size: 21px;
color: white;
font-weight: bold;
padding-bottom: 10px;
border-bottom: solid 1px white;
}
.todo-list {
flex: 1;
@ -102,29 +86,35 @@ export default {
padding: 20px;
}
.change {
margin-bottom: 20px;
margin-bottom: 10px;
padding-bottom: 10px;
border-bottom: solid 1px white;
}
.heading{
.heading {
padding: 10px;
background: rgba(0, 0, 0, 0.555);
margin-bottom: 10px;
border-radius: 10px;
}
.information {
overflow-wrap: break-word;
margin: 10px;
}
.heading.latest {
background: rgba(38, 139, 255, 0.87);
}
.change-log {
background: rgba(0, 0, 0, 0.137);
padding: 20px;
background: rgba(0, 0, 0, 0.561);
overflow-y: auto;
max-width: 700px;
margin: auto;
}
.changelog {
display: flex;
width: 100%;
height: 100%;
overflow: hidden;
overflow: auto;
}
.plan-list {
color: white;
}
@ -142,5 +132,4 @@ export default {
margin-top: -5px;
margin-bottom: 10px;
}
</style>

View file

@ -103,21 +103,21 @@ export default {
@media (max-width: 949px) {
.members-panel {
position: absolute;
background-color: rgba(39, 39, 39, 0.97);
right: 0;
bottom: 0;
height: calc(100% - 50px);
z-index: 1;
backdrop-filter: blur(15px);
}
}
@media (max-width: 600px) {
.left-panel {
position: absolute;
background-color: rgba(39, 39, 39, 0.97);
bottom: 0;
height: calc(100% - 50px);
z-index: 2;
backdrop-filter: blur(15px);
}
}
</style>

View file

@ -1,205 +0,0 @@
<template>
<div class="add-friend-panel">
<div
class="panel-title"
@click="expanded = !expanded"
>
<div>
<i
v-if="!expanded"
class="material-icons"
>
person_add
</i>
<span>{{ expanded ? "Hide" : "Add friend" }}</span>
</div>
</div>
<transition
name="slide"
appear
>
<div
v-if="expanded"
class="add-friend"
>
<div class="title">
Add friend
</div>
<div class="info">
Type in your friends username and tag. eg: someone@jt4g
</div>
<div class="infoC">
Creators tag: Fishie@azK0
</div>
<form
action="#"
@submit.prevent="addFriend"
>
<input
v-model="input"
type="text"
placeholder="username@tag"
>
<loadingButton
:loading="currentButtonMessage == 1"
:message="buttonMessages[currentButtonMessage]"
/>
</form>
<div :class="{message: true, warning: errorMessage.isError}">
{{ errorMessage.message }}
</div>
</div>
</transition>
</div>
</template>
<script>
import RelationshipService from '@/services/RelationshipService.js'
import loadingButton from './../../Button.vue'
export default {
components: {
loadingButton
},
data() {
return {
expanded: false,
buttonMessages: [
"Add Friend",
"Adding..."
],
currentButtonMessage: 0,
input: "",
errorMessage:{
message: "",
isError: false
}
}
},
methods: {
async addFriend() {
this.$set(this.errorMessage, 'message', "")
this.currentButtonMessage = 1;
const split = this.input.trim().split("@");
// validation
if ( split.length <2 || split.length >2 || split[1] === "" || split[1].length !== 4){
this.$set(this.errorMessage, 'message', "Invalid username or tag.")
this.$set(this.errorMessage, 'isError', true)
this.currentButtonMessage = 0;
return;
}
const username = split[0];
const tag = split[1];
const {ok, error, result} = await RelationshipService.post({username, tag})
this.currentButtonMessage = 0;
if ( ok ) {
this.$set(this.errorMessage, 'message', result.data.message)
this.$set(this.errorMessage, 'isError', false)
} else {
if (error.response === undefined) {
this.$set(this.errorMessage, 'message', "Can't connect to server.")
this.$set(this.errorMessage, 'isError', true)
return
}
this.$set(this.errorMessage, 'message', error.response.data.errors[0].msg)
this.$set(this.errorMessage, 'isError', true)
}
}
}
}
</script>
<style scoped>
.slide-enter-active, .slide-leave-active {
transition: .3s;
}
.slide-enter, .slide-leave-to /* .fade-leave-active below version 2.1.8 */ {
margin-bottom: -250px;
opacity: 0;
}
.add-friend-panel{
width: 100%;
background: rgba(0, 0, 0, 0.123);
display: flex;
flex-direction: column;
border-radius: 5px;
}
.add-friend{
background: rgba(0, 0, 0, 0);
flex: 1;
height: 230px;
display: flex;
flex-direction: column;
color: white;
padding: 10px;
}
.title{
margin: auto;
margin-top: 10px;
margin-bottom: 0px;
font-size: 20px;
color: white;
user-select: none;
cursor: default;
}
.info{
text-align: center;
color: rgb(182, 182, 182);
font-size: 15px;
user-select: none;
cursor: default;
}
.infoC{
text-align: center;
color: rgb(255, 79, 79);
font-size: 15px;
}
form {
margin: auto;
margin-top: 5px;
margin-bottom: 0px;
}
.message{
margin: auto;
margin-top: 2px;
font-size: 15px;
color: green;
}
.warning {
color: red;
}
.panel-title{
background: rgba(0, 0, 0, 0.274);
width: 100%;
text-align: center;
padding-top: 5px;
padding-bottom: 5px;
color: white;
cursor: pointer;
user-select: none;
display: flex;
transition: 0.3s;
border-radius: 5px;
}
.panel-title:hover{
background: rgba(0, 0, 0, 0.445);
}
.panel-title .material-icons{
vertical-align: top;
margin-top: -2px;
}
.panel-title span{
margin-left: 5px;
}
.panel-title div{
margin: auto;
}
</style>

View file

@ -1,7 +1,8 @@
<template>
<div
:class="{friend: true, notifyAnimation: (notifications && notifications > 0) }"
class="friend"
:style="{background: status.status !== 0 ? status.bgColor : ''}"
:class="{selected: uniqueIDSelected, notifyAnimation: (notifications && notifications > 0) }"
@click="openChat"
@mouseover="hover = true"
@mouseleave="hover = false"
@ -80,6 +81,9 @@ export default {
statusColor: statuses[parseInt(status)].color,
bgColor: statuses[parseInt(status)].bgColor
}
},
uniqueIDSelected() {
return this.$store.getters.selectedUserUniqueID === this.recipient.uniqueID
}
},
methods: {
@ -108,20 +112,18 @@ export default {
<style scoped>
.username{
width: 201px;
width: 150px;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.friend {
color: white;
background-color: rgba(0, 0, 0, 0.100);
margin: 5px;
padding: 5px;
padding-right: 0;
padding-left: 10px;
padding-left: 10px;
display: flex;
transition: 0.3s;
border-radius: 5px;
position: relative;
overflow: hidden;
cursor: pointer;
@ -151,9 +153,11 @@ export default {
}
.friend:hover {
background-color: rgba(0, 0, 0, 0.246);
background: rgba(0, 0, 0, 0.3) !important;
}
.friend.selected {
background: rgba(0, 0, 0, 0.4) !important;
}
.profile-picture{
height: 30px;
width: 30px;

View file

@ -63,14 +63,12 @@ export default {
.friends{
background-color: rgba(0, 0, 0, 0);
margin: 5px;
user-select: none;
padding-bottom: 3px;
border-radius: 5px;
transition: 0.3s;
}
.tab{
border-radius: 5px;
transition: 0.3s;
}
.tab:hover{

View file

@ -79,14 +79,11 @@ export default {
.friends{
background-color: rgba(0, 0, 0, 0);
margin: 5px;
user-select: none;
padding-bottom: 3px;
border-radius: 5px;
transition: 0.3s;
}
.tab{
border-radius: 5px;
transition: 0.3s;
}
.tab:hover{

View file

@ -84,14 +84,12 @@ export default {
.recents{
background-color: rgba(0, 0, 0, 0);
margin: 5px;
user-select: none;
padding-bottom: 3px;
border-radius: 5px;
transition: 0.3s;
}
.tab{
border-radius: 5px;
transition: 0.3s;
}
.tab:hover{

View file

@ -20,6 +20,7 @@ export default {
.tab{
display: flex;
color: white;
cursor: pointer;
}
.tab-name {
padding-top: 3px;

View file

@ -67,17 +67,16 @@ export default {
.status-popout{
position: absolute;
background-color: rgba(26, 26, 26, 0.863);
border-radius: 10px;
padding: 5px;
bottom: 55px;
left: 30px;
background: rgba(0, 0, 0, 0.7);
backdrop-filter: blur(5px);
width: 180px;
z-index: 4;
}
.status-list {
padding: 5px;
padding: 10px;
transition: 0.3s;
border-radius: 5px;
margin: 5px;
display: flex;
align-items: center;
align-content: center;
@ -86,7 +85,7 @@ export default {
}
.status-list:hover {
background: rgba(0, 0, 0, 0.349);
background: rgba(46, 46, 46, 0.651);
}
.status-icon{

View file

@ -32,16 +32,22 @@ export const store = new Vuex.Store({
members: membersModule
},
state: {
currentTab: 0,
},
getters: {
},
mutations: {
currentTab(state) {
return state.currentTab;
}
},
actions: {
}
setCurrentTab({commit}, currentTab) {
localStorage.setItem("currentTab", currentTab);
commit('SET_CURRENT_TAB', currentTab)
}
},
mutations: {
SET_CURRENT_TAB(state, currentTab) {
state.currentTab = currentTab;
}
},
})

View file

@ -4,6 +4,7 @@ import Vue from "vue";
const state = {
selectedChannelID: null,
selectedUserUniqueID: null,
DMChannelID: null,
serverChannelID: null,
channelName: null,
@ -14,6 +15,9 @@ const getters = {
selectedChannelID(state) {
return state.selectedChannelID;
},
selectedUserUniqueID(state) {
return state.selectedUserUniqueID;
},
channels(state) {
return state.channels;
},
@ -43,6 +47,9 @@ const actions = {
selectedChannelID(context, channelID) {
context.commit("selectedChannelID", channelID);
},
selectedUserUniqueID(context, uniqueID) {
context.commit("selectedUserUniqueID", uniqueID);
},
setChannelName(context, name) {
context.commit("setChannelName", name);
},
@ -74,6 +81,9 @@ const mutations = {
selectedChannelID(state, channelID) {
state.selectedChannelID = channelID;
},
selectedUserUniqueID(state, uniqueID) {
state.selectedUserUniqueID = uniqueID;
},
setDMChannelID(state, channelID) {
state.DMChannelID = channelID;
},

View file

@ -25,6 +25,7 @@ const getters = {
const actions = {
// server channel
async openChannel(context, channel) {
context.commit("selectedChannelID", channel.channelID)
context.commit("setChannelName", channel.name);
const messages = context.state.messages[channel.channelID];
if (messages) {
@ -32,14 +33,12 @@ const actions = {
context.commit("setServerChannelID", channel.channelID);
return;
}
context.commit("selectedChannelID", "loading")
getMessages(context, channel.channelID, true);
},
//dm channel
async openChat(context, { uniqueID, channelID, channelName }) {
if (channelName) context.commit("setChannelName", channelName);
const channels = context.rootState.channelModule.channels;
channelID = Object.keys(channels).find(_channelID => {
return channels[_channelID].recipients && channels[_channelID].recipients.length && channels[_channelID].recipients[0].uniqueID === uniqueID
@ -47,6 +46,7 @@ const actions = {
const messages = context.state.messages[channelID];
const channel = channels[channelID];
context.commit('selectedUserUniqueID', uniqueID);
if (channelID) {
context.commit("setDMChannelID", channelID);
context.commit("selectedChannelID", channelID);

View file

@ -42,7 +42,18 @@ const state = {
uniqueID: null,
x: null,
y: null
}
},
// TODO: convert all above into one.
allPopout: {
show: false,
type: null,
serverID: null,
uniqueID: null,
creatorUniqueID: null,
x: null,
y: null,
}
}
@ -54,6 +65,9 @@ const getters = {
}
const actions = {
setAllPopout({commit, state}, data) {
commit('setAllPopout', {...state.allPopout, ...data})
},
setServerSettings({commit}, {serverID, index}){
commit('setServerSettings', {serverID, index})
},
@ -87,6 +101,9 @@ const actions = {
}
const mutations = {
setAllPopout(state, data) {
Vue.set(state, 'allPopout', data)
},
setServerMemberContext(state, data) {
Vue.set(state, 'serverMemberContext', data);
},

View file

@ -31,9 +31,9 @@ const actions = {
},
updateNotification(context, data) {
let notificationSettings = JSON.parse(localStorage.getItem('notificationSettings')) || {};
Object.assign(notificationSettings, data);
localStorage.setItem("notificationSettings", JSON.stringify(notificationSettings));
context.commit('updateNotification', notificationSettings)
const assigned = Object.assign({}, notificationSettings, data);
localStorage.setItem("notificationSettings", JSON.stringify(assigned));
context.commit('updateNotification', assigned)
},
addRecentEmoji(context, shortcode) {
const recentEmojis = JSON.parse(localStorage.getItem('recentEmojis')) || [];

View file

@ -270,13 +270,18 @@ const actions = {
if (!server.socketID) return;
if (this._vm.$socket.id !== server.socketID) return;
const defaultChannel = channels.find(c => c.channelID === server.default_channel_id)
bus.$emit('changeTab', 2)
context.dispatch('setCurrentTab', 2, {root: true})
context.dispatch('servers/setSelectedServerID', server.server_id, {root: true})
context.dispatch('openChannel', defaultChannel, {root: true})
},
['socket_server:leave'](context, {server_id}) {
const lastSelectedChannel = JSON.parse(localStorage.getItem('selectedChannels') || '{}')
if (lastSelectedChannel[server_id]) {
delete lastSelectedChannel[server_id];
localStorage.setItem('selectedChannels', JSON.stringify(lastSelectedChannel));
}
// check if server channel selected
const serverChannelIDs = context.rootState.servers.channelsIDs[server_id];

View file

@ -14,13 +14,25 @@
const config = [
{
version: 7.8,
title: "Redesigns!",
shortTitle: "",
date: "25/10/2019",
headColor: "rgba(25, 130, 255, 0.77)",
new: [
'Layout has been redesigned.',
'Added an option to mute notification sounds.',
'Last clicked channels should be remembered for each server.',
'You can now delete Join, Left and Ban messages from the chat.',
],
next: ['Custom server banners.'],
},
{
version: 7.6,
title: "👻Spooky bug fixes👻",
shortTitle: "",
date: "16/10/2019",
headColor: "rgb(255, 166, 0)", // halloween
//headColor: "rgba(25, 130, 255, 0.77)",
new: [
'Better handled Google Drive linking.',
'👻👻👻',

View file

@ -69,7 +69,7 @@ function replace_custom_emoji(state, silent) {
// console.log(nameEnd, parseUntil(state,nameStart,58))
// parser failed to find another ':', so it's not a valid emoji
if(nameEnd > max || nameEnd < 0 || nameEnd - nameStart <= 0) { return false; }
if((nameEnd+1) > max || nameEnd < 0 || nameEnd - nameStart <= 0) { return false; }
let emojiName = state.src.slice(nameStart, nameEnd)
@ -79,7 +79,9 @@ function replace_custom_emoji(state, silent) {
let idStart = pos
let idEnd = skipUntil(state, pos, 62);
if(idEnd > max || idEnd < 0 || idEnd - idStart <= 1) { return false; }
if((idEnd+1) > max || idEnd < 0 || idEnd - idStart <= 1) { return false; }
// console.log(idStart, idEnd)
let emojiID = state.src.slice(idStart, idEnd)
@ -88,7 +90,7 @@ function replace_custom_emoji(state, silent) {
state.posMax = idEnd
let token = state.push('custom_emoji', 'img', 0);
token.attrs = [[ 'src', `${config.domain}/media/${emojiID}` ], [ 'alt', emojiName ]]
token.attrs = [[ 'src', (`${config.domain}/media/${emojiID}`) ], [ 'alt', (emojiName) ]]
}
state.pos = idEnd + 1
@ -96,14 +98,17 @@ function replace_custom_emoji(state, silent) {
return true
}
const emojiRe = /<:[\w\d]+:[\w\d]+>/
export default function custom_emoji_plugin(md, opts) {
md.renderer.rules.custom_emoji = (tokens, idx) => {
let token = tokens[idx]
const srcIdx = token.attrIndex('src');
const altIdx = token.attrIndex('alt');
// todo: better escaping method,
// even if this is good and covers most cases, there may be edge cases where DOMPurify may be better
let src = md.utils.escapeHtml(token.attrs.find(([name]) => name === 'src')[1])
let alt = md.utils.escapeHtml(token.attrs.find(([name]) => name === 'alt')[1])
let src = encodeURI(md.utils.escapeHtml(token.attrs[srcIdx][1]))
let alt = md.utils.escapeHtml(token.attrs[altIdx][1])
return `<${md.utils.escapeHtml(token.tag)} class="emoji" title=${alt} alt=${alt} src=${src} />`
}

View file

@ -0,0 +1,83 @@
function fence(state, startLine, endLine, silent) {
var marker, len, params, nextLine, mem, token, markup,
haveEndMarker = false,
pos = state.bMarks[startLine] + state.tShift[startLine],
max = state.eMarks[startLine];
// if it's indented more than 3 spaces, it should be a code block
// if (state.sCount[startLine] - state.blkIndent >= 4) { return false; }
if (pos + 3 > max) { return false; }
marker = state.src.charCodeAt(pos);
if (marker !== 0x60 /* ` */) {
return false;
}
// scan marker length
mem = pos;
pos = state.skipChars(pos, marker);
len = pos - mem;
if (len < 3) { return false; }
markup = state.src.slice(mem, pos);
params = state.src.slice(pos, max);
if (marker === 0x60 /* ` */) {
if (params.indexOf(String.fromCharCode(marker)) >= 0) {
return false;
}
}
// Since start is found, we can report success here in validation mode
if (silent) { return true; }
// search end of block
nextLine = startLine;
for (;;) {
nextLine++;
if (nextLine >= endLine) {
return false
}
pos = mem = state.bMarks[nextLine] + state.tShift[nextLine];
max = state.eMarks[nextLine];
if (state.src.charCodeAt(pos) !== marker) { continue; }
pos = state.skipChars(pos, marker);
// closing code fence must be at least as long as the opening one
if (pos - mem < len) { continue; }
// make sure tail has spaces only
pos = state.skipSpaces(pos);
if (pos < max) { continue; }
haveEndMarker = true;
// found!
break;
}
// If a fence has heading spaces, they should be removed from its inner block
len = state.sCount[startLine];
state.line = nextLine + (haveEndMarker ? 1 : 0);
token = state.push('fence', 'code', 0);
token.info = params;
token.content = state.getLines(startLine + 1, nextLine, len, true);
token.markup = markup;
token.map = [ startLine, state.line ];
return true;
}
export default function normalizeFence(md, opts) {
md.block.ruler.at('fence', fence)
}

View file

@ -5,6 +5,7 @@ import config from "@/config.js";
import customEmoji from './markdown-it-plugins/customEmoji'
import formatLink from './markdown-it-plugins/formatLink'
import formatCode from './markdown-it-plugins/formatCode'
import normalizeFence from './markdown-it-plugins/normalizeFence'
import hljs from 'highlight.js'
@ -25,7 +26,9 @@ const markdown = new MarkdownIt({
return '<div class="codeblock"><code>' + markdown.utils.escapeHtml(str) + '</code></div>';
}
}).use(chatPlugin)
})
.use(normalizeFence)
.use(chatPlugin)
.use(customEmoji)
.use(formatLink)
.use(formatCode);

View file

@ -12,12 +12,12 @@ const newFriendAudio = new Audio(newFriendSound);
export default {
notification: () => {
if (isBusy()) return;
if (isBusy() || isNotificationDisabled()) return;
const audio = new Audio(notificationSound);
audio.play();
},
newFriend: () => {
if (isBusy()) return;
if (isBusy() || isNotificationDisabled()) return;
const audio = new Audio(newFriendSound);
audio.play();
}
@ -25,4 +25,7 @@ export default {
function isBusy(){
return store.getters.user.status == 3
}
function isNotificationDisabled(){
return !!store.getters['settingsModule/settings'].notification.disableNotificationSound;
}

View file

@ -0,0 +1,29 @@
import SimpleMarkdown from 'simple-markdown';
export default {
// Specify the order in which this rule is to be run
// This rule doesn't conflict with much else, so it should be fine to just put it before the
// general-case text rule:
order: SimpleMarkdown.defaultRules.text.order - 0.5,
// First we check whether a string matches
match: function(source) {
// // || followed by any character repeated [\s\S]+? followed by ||
// // Also see https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Regular_Expressions for more info on regexes
return /^\<\:([\S]+?)\:([\S]+?)>/.exec(source);
},
// Then parse this string into a syntax node
parse: function(capture, parse, state) {
return {
// capture[1] is the parenthesis group from the regex in `match`:
name: parse(capture[1], state),
id: parse(capture[2], state)
};
},
// Finally transform this syntax node into an html element:
html: function(node, output, state) {
return '<img class="emoji" title="' + output(node.name, state) + '" alt="' + output(node.name, state) + '" src="https://supertiger.tk/api/media/' + output(node.id, state) + '">'
}
}

View file

@ -0,0 +1,29 @@
import SimpleMarkdown from 'simple-markdown';
import linkify from 'linkify-it'
const linkifyInstance = linkify();
export default {
// Specify the order in which this rule is to be run
// This rule doesn't conflict with much else, so it should be fine to just put it before the
// general-case text rule:
order: SimpleMarkdown.defaultRules.link - 0.5,
// First we check whether a string matches
match: function(source) {
return linkifyInstance.match(source)
},
// Then parse this string into a syntax node
parse: function(capture, parse, state) {
console.log(capture)
return {
content: parse(capture[0], state),
};
},
// Finally transform this syntax node into an html element:
html: function(node, output, state) {
return 'owo'
}
}

View file

@ -1,42 +1,14 @@
<template>
<div id="app" ref="app">
<vue-headful :title="title" description="Nertivia Chat Client"/>
<div class="background-image halloween-background"></div>
<div class="background-image"></div>
<transition name="fade-between-two" appear>
<ConnectingScreen v-if="!loggedIn"/>
<div class="box" v-if="loggedIn">
<div class="frame">
<div class="tabs">
<div :class="`tab ${currentTab === 0 ? 'selected' : ''}`" @click="switchTab(0)">
<i class="material-icons">explore</i>
Explore
</div>
<div :class="{tab: true, selected: currentTab === 1, notifyAnimation: DMNotification || friendRequestExists}" @click="switchTab(1)">
<i class="material-icons">chat</i>
Direct Message
</div>
<div :class="{tab: true, selected: currentTab === 2, notifyAnimation: serverNotification}" @click="switchTab(2)">
<i class="material-icons">forum</i>
Servers
</div>
<div :class="`tab ${currentTab === 3 ? 'selected' : ''}`" @click="switchTab(3)">
<i class="material-icons">list_alt</i>
Changelog
</div>
<!-- <div :class="`tab ${currentTab === 4 ? 'selected' : ''}`" @click="switchTab(4)">
<i class="material-icons">info</i>
Ad
</div> -->
<div class="frame" v-if="isElectron">
<div class="window-buttons">
<electron-frame-buttons />
</div>
<div class="window-buttons" v-if="isElectron">
<electron-frame-buttons />
</div>
</div>
<div class="panel-layout">
@ -59,7 +31,6 @@ import changelog from "@/utils/changelog.js";
import ConnectingScreen from "./../components/app/ConnectingScreen.vue";
import Spinner from "./../components/Spinner.vue";
const ElectronFrameButtons = () =>
import("@/components/ElectronJS/FrameButtons.vue");
@ -87,7 +58,6 @@ const Explore = () => ({
export default {
name: "app",
components: {
DirectMessage,
Servers,
ConnectingScreen,
@ -98,7 +68,6 @@ export default {
},
data() {
return {
currentTab: 0,
title: "Nertivia",
isElectron: window && window.process && window.process.type
};
@ -133,7 +102,7 @@ export default {
},
switchTab(index) {
localStorage.setItem("currentTab", index);
this.currentTab = index;
this.$store.dispatch('setCurrentTab', index)
if (index == 1) { //1: direct message tab.
this.switchChannel(false)
} else if (index === 2) { //2: server tab
@ -156,25 +125,25 @@ export default {
const currentTab = localStorage.getItem("currentTab");
if(currentTab) {
this.currentTab = parseInt(currentTab);
this.$store.dispatch('setCurrentTab', parseInt(currentTab))
}
// check if changelog is updated
const seenVersion = localStorage.getItem("changelog-version-seen");
if (seenVersion && seenVersion < changelog[0].version) {
localStorage.setItem("currentTab", 3);
this.currentTab = 3;
this.$store.dispatch('setCurrentTab', 3)
}
localStorage.setItem("changelog-version-seen", changelog[0].version);
bus.$on("title:change", title => {
this.title = title;
});
bus.$on('changeTab', this.switchTab)
},
destroyed() {
bus.$off('changeTab', this.switchTab)
},
computed: {
currentTab() {
return this.$store.getters.currentTab;
},
loggedIn() {
return this.$store.getters.loggedIn;
},
@ -277,6 +246,8 @@ export default {
display: flex;
-webkit-app-region: drag;
flex-shrink: 0;
height: 25px;
background: #1089ff;
}
.window-buttons {
@ -287,6 +258,9 @@ export default {
.tabs {
display: flex;
flex-shrink: 0;
height: 40px;
overflow-y: hidden;
overflow-x: auto;
max-width: 500px;
@ -381,12 +355,6 @@ textarea {
filter: blur(15px);
transform: scale(1.1);
}
.halloween-background {
background: url(./../assets/halloween_background.jpg);
filter: blur(10px);
background-position: center;
}
.panel-layout {
display: flex;

View file

@ -1,508 +0,0 @@
<template>
<div id="app">
<vue-headful
title="Nertivia"
description="Nertivia Chat Client"
/>
<div class="background-image" />
<div class="layout">
<div class="small-view-nav-bar">
<div class="small-logo" />
<div class="small-title">
Nertivia
</div>
<div
class="show-menu-button"
@click="showMobileMenu = !showMobileMenu"
>
<i class="material-icons">
menu
</i>
</div>
</div>
<div class="panels">
<div class="left-panel">
<div class="title">
The best chat client that wont restrict you from important and fun features.
</div>
<img
src="../assets/graphics/HomeGraphics.png"
class="graphic-app"
>
<div
class="change-log-mini"
@click="showChangeLog = true"
>
<div class="change-title">
Change log <span style="font-size: 15px; color: rgba(211, 211, 211, 0.774);">Click for details</span>
</div>
<div class="change-list">
<div
v-for="change in changelogFiltered"
:key="change.title"
class="change"
>
<div class="notable-changes">
{{ change.shortTitle }}
</div>
<div class="change-date">
{{ change.date }}
</div>
</div>
</div>
</div>
<div class="twitter-outer">
<twitter class="twitter">
<div slot="loading">
loading .....
</div>
<a
class="twitter-timeline"
data-height="500"
data-theme="dark"
href="https://twitter.com/NertiviaApp?ref_src=twsrc%5Etfw"
>Tweets by NertiviaApp</a>
</twitter>
</div>
</div>
<RightPanel :class="{'show-menu-content': showMobileMenu }" />
</div>
</div>
<transition name="fade">
<ChangeLog v-if="showChangeLog" />
</transition>
</div>
</template>
<script>
import { twitter } from 'vue-twitter'
import {bus} from '../main';
import RightPanel from "./../components/homePage/RightPanel.vue"
import ChangeLog from "./../components/ChangeLog.vue"
import changelog from '@/utils/changelog.js'
export default {
components: {
RightPanel,
ChangeLog,
twitter
},
data() {
return {
loginSelected: true,
showMobileMenu: false,
showChangeLog: false,
changelog
}
},
computed: {
changelogFiltered() {
return this.changelog.slice(0, 3)
}
},
mounted() {
bus.$on('closeChangeLog', () => {
this.showChangeLog = false;
})
}
}
</script>
<style>
html {
height: 100%;
}
body {
margin: 0;
height: 100%;
overflow: hidden;
}
#app {
font-family: "Roboto", sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
color: #383838;
height: 100%;
}
</style>
<style scoped>
.twitter-outer{
margin: auto;
width: 600px;
opacity: 0.8;
transition: 0.3s;
}
.twitter-outer:hover{
opacity: 1;
}
.fade-enter-active, .fade-leave-active {
transition: opacity .2s;
}
.fade-enter, .fade-leave-to /* .fade-leave-active below version 2.1.8 */ {
opacity: 0;
}
#app {
font-family: 'Roboto', sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
color: #383838;
height: 100%;
}
button {
font-family: 'Roboto', sans-serif;
}
.spinner{
margin: auto;
padding: 30px;
}
.background-image {
background: url(./../assets/background.jpg);
position: absolute;
z-index: -1;
top: 0;
bottom: 0;
left: 0;
right: 0;
background-repeat: no-repeat;
background-position: bottom;
background-size: cover;
}
.layout{
display: flex;
height: 100%;
width:100%;
flex-direction: column;
}
.panels{
display: flex;
height: 100%;
width: 100%;
}
.left-panel {
flex: 1;
background: rgba(0, 0, 0, 0.253);
overflow: auto;
display: flex;
flex-direction: column;
}
.loader{
display: flex;
flex-direction: column;
}
.title-panel{
width: 100%;
height: 150px;
}
.graphics-panel{
flex: 1;
}
.graphic-app{
display: table;
margin: auto;
margin-top: 20px;
margin-bottom: 20px;
width: 900px;
height: auto;
user-select: none;
}
.title{
color: white;
font-size: 35px;
text-align: center;
margin-top: 120px;
}
.change-log-mini{
background: rgba(0, 0, 0, 0.322);
height: 150px;
width: 640px;
margin: auto;
margin-top: 20px;
color: white;
margin-bottom: 50px;
flex-shrink: 0;
}
.change-title {
font-size: 18px;
margin-top: 10px;
margin-bottom: 10px;
margin-left: 10px;
user-select: none;
}
.change-list{
display: flex;
}
.change {
background: rgba(0, 0, 0, 0.335);
width: 200px;
height: 90px;
margin-left: 10px;
border-radius: 5px;
transition: 0.3s;
position: relative
}
.change:hover {
background: rgba(0, 0, 0, 0.466);
}
.notable-changes{
margin: 10px;
cursor: default;
user-select: none;
}
.change-date{
position: absolute;
bottom: 10px;
right: 10px;
color: rgba(255, 255, 255, 0.753);
cursor: default;
user-select: none;
}
.small-view-nav-bar{
width: 100%;
height: 50px;
background: rgba(0, 0, 0, 0.411);
display: none;
align-items: center;
justify-content: center;
flex-shrink: 0;
}
.small-logo {
height: 30px;
width: 30px;
background: url(./../assets/logo.png);
background-size: 105%;
background-position: center;
border-radius: 50%;
box-shadow: 0px 0px 96px -4px rgba(69,212,255,1);
margin: auto;
margin-left: 10px;
flex-shrink: 0;
}
.small-title{
color: white;
font-size: 20px;
flex: 1;
margin-left: 10px;
}
.show-menu-button{
color: rgba(255, 255, 255, 0.698);
margin-right: 20px;
margin-top: 7px;
user-select: none;
transition: 0.3s
}
.show-menu-button:hover {
color: rgb(255, 255, 255);
}
.show-menu-content {
display: flex !important;
width: 400px !important;
opacity: 1 !important;
transform: scale(1) !important;
}
@media (max-width: 1051px) {
.change:nth-child(3){
display: none;
}
.change-log-mini{
width: 430px;
}
}
@media (max-width: 906px) {
.change:nth-child(3){
display: block;
}
.change-log-mini{
width: 640px;
}
}
@media (max-width: 649px) {
.twitter-outer{
margin-top: 20px;
margin-bottom: 50px;
width: initial;
}
.change-list{
flex-direction: column;
}
.change-log-mini{
height: initial;
width: calc(100% - 20px);
padding-bottom: 10px;
margin: auto;
}
.change{
margin-bottom: 5px;
margin-left: 5px;
margin-right: 0;
width: calc(100% - 10px);
}
}
@media (max-width: 1380px) {
.graphic-app{
width: calc(100% - 80px);
}
.title{
font-size: 30px;
margin-left: 20px;
margin-right: 20px;
}
}
@media (max-width: 906px) {
.right-panel-home {
position: absolute;
bottom: 0;
right: 0;
top: 50px;
display: flex;
margin-right: 0;
margin-top: 0;
height:calc(100% - 50px);
background-color: rgb(34, 34, 34);
width: 0;
overflow-x: hidden;
transition: 0.5s ease;
transform: scale(0.97);
opacity: 0;
}
.right-panel-inner{
width: 400px;
}
.small-view-nav-bar{
display: flex;
}
}
@media (max-width: 401px) {
.show-menu-content {
width: 100% !important;
}
.right-panel-inner{
width: 100%;
}
}
</style>
<!-- Used for forms !-->
<style>
@media (max-width: 1380px) {
.graphic-app{
width: calc(100% - 80px);
}
}
@media (max-width: 906px) {
.right-panel-home {
position: absolute;
bottom: 0;
right: 0;
top: 50px;
display: flex;
margin-right: 0;
margin-top: 0;
height:calc(100% - 50px);
background-color: rgba(34, 34, 34, 0.877);
width: 0;
overflow-x: hidden;
transition: 0.5s ease;
transform: scale(0.97);
opacity: 0;
}
.right-panel-inner{
width: 400px;
}
.small-view-nav-bar{
display: flex;
}
}
@media (max-width: 401px) {
.show-menu-content {
width: 100% !important;
}
.right-panel-inner{
width: 100%;
}
}
.form {
color: white;
margin: auto;
padding: 10px;
}
input{
padding: 10px;
background: rgba(0, 0, 0, 0.301);
outline: none;
border: none;
color: white;
margin-top: 5px;
margin-bottom: 10px;
width: 200px;
transition: 0.3s;
}
input:hover{
background: rgba(0, 0, 0, 0.452);
}
input:focus {
background: rgba(0, 0, 0, 0.603);
}
.input-title{
margin-top: 10px;
}
.form-button{
padding: 10px;
background: rgba(0, 0, 0, 0.226);
display: table;
transition: 0.5s;
margin: auto;
color: white;
border: none;
outline: none;
}
.form-button:hover{
background: rgba(0, 0, 0, 0.534)
}
.alert{
color: red;
font-size: 15px;
width: 220px;
}
</style>