.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;
diff --git a/src/components/app/relationships/OfflineFriends.vue b/src/components/app/relationships/OfflineFriends.vue
index 7f3efb3..2f96bda 100644
--- a/src/components/app/relationships/OfflineFriends.vue
+++ b/src/components/app/relationships/OfflineFriends.vue
@@ -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{
diff --git a/src/components/app/relationships/OnlineFriends.vue b/src/components/app/relationships/OnlineFriends.vue
index a63b983..7b96a06 100644
--- a/src/components/app/relationships/OnlineFriends.vue
+++ b/src/components/app/relationships/OnlineFriends.vue
@@ -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{
diff --git a/src/components/app/relationships/RecentFriends.vue b/src/components/app/relationships/RecentFriends.vue
index 52ef257..33dd0c6 100644
--- a/src/components/app/relationships/RecentFriends.vue
+++ b/src/components/app/relationships/RecentFriends.vue
@@ -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{
diff --git a/src/components/app/relationships/Tab.vue b/src/components/app/relationships/Tab.vue
index 8fe83e9..cedc5e7 100644
--- a/src/components/app/relationships/Tab.vue
+++ b/src/components/app/relationships/Tab.vue
@@ -20,6 +20,7 @@ export default {
.tab{
display: flex;
color: white;
+ cursor: pointer;
}
.tab-name {
padding-top: 3px;
diff --git a/src/components/app/statusList.vue b/src/components/app/statusList.vue
index f7864c6..4959359 100644
--- a/src/components/app/statusList.vue
+++ b/src/components/app/statusList.vue
@@ -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{
diff --git a/src/store/index.js b/src/store/index.js
index d57c20e..fef8e3e 100644
--- a/src/store/index.js
+++ b/src/store/index.js
@@ -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;
+ }
+ },
})
\ No newline at end of file
diff --git a/src/store/modules/channelModule.js b/src/store/modules/channelModule.js
index 554cb7f..11afe83 100644
--- a/src/store/modules/channelModule.js
+++ b/src/store/modules/channelModule.js
@@ -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;
},
diff --git a/src/store/modules/messageModule.js b/src/store/modules/messageModule.js
index 81fc4cb..639919b 100644
--- a/src/store/modules/messageModule.js
+++ b/src/store/modules/messageModule.js
@@ -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);
diff --git a/src/store/modules/popoutsModule/popoutsModule.js b/src/store/modules/popoutsModule/popoutsModule.js
index 497e656..3553e34 100644
--- a/src/store/modules/popoutsModule/popoutsModule.js
+++ b/src/store/modules/popoutsModule/popoutsModule.js
@@ -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);
},
diff --git a/src/store/modules/settingsModule.js b/src/store/modules/settingsModule.js
index 7ca104e..993d1de 100644
--- a/src/store/modules/settingsModule.js
+++ b/src/store/modules/settingsModule.js
@@ -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')) || [];
diff --git a/src/store/modules/socketIOModule.js b/src/store/modules/socketIOModule.js
index 4be7716..b370de9 100644
--- a/src/store/modules/socketIOModule.js
+++ b/src/store/modules/socketIOModule.js
@@ -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];
diff --git a/src/utils/changelog.js b/src/utils/changelog.js
index b78caa4..76f17e6 100644
--- a/src/utils/changelog.js
+++ b/src/utils/changelog.js
@@ -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.',
'👻👻👻',
diff --git a/src/utils/markdown-it-plugins/customEmoji.js b/src/utils/markdown-it-plugins/customEmoji.js
index 3c8aa6e..e2ce535 100644
--- a/src/utils/markdown-it-plugins/customEmoji.js
+++ b/src/utils/markdown-it-plugins/customEmoji.js
@@ -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} />`
}
diff --git a/src/utils/markdown-it-plugins/normalizeFence.js b/src/utils/markdown-it-plugins/normalizeFence.js
new file mode 100644
index 0000000..d031985
--- /dev/null
+++ b/src/utils/markdown-it-plugins/normalizeFence.js
@@ -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)
+}
\ No newline at end of file
diff --git a/src/utils/messageFormatter.js b/src/utils/messageFormatter.js
index 0694d54..4edcd69 100644
--- a/src/utils/messageFormatter.js
+++ b/src/utils/messageFormatter.js
@@ -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 '
' + markdown.utils.escapeHtml(str) + '
';
}
-}).use(chatPlugin)
+})
+ .use(normalizeFence)
+ .use(chatPlugin)
.use(customEmoji)
.use(formatLink)
.use(formatCode);
diff --git a/src/utils/notificationSound.js b/src/utils/notificationSound.js
index fc6e1c5..fe52628 100644
--- a/src/utils/notificationSound.js
+++ b/src/utils/notificationSound.js
@@ -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;
}
\ No newline at end of file
diff --git a/src/utils/simpleMarkdownRules/CustomEmoji.js b/src/utils/simpleMarkdownRules/CustomEmoji.js
new file mode 100644
index 0000000..201ee38
--- /dev/null
+++ b/src/utils/simpleMarkdownRules/CustomEmoji.js
@@ -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 '
 + ')
'
+ }
+}
\ No newline at end of file
diff --git a/src/utils/simpleMarkdownRules/linkify.js b/src/utils/simpleMarkdownRules/linkify.js
new file mode 100644
index 0000000..2349cb8
--- /dev/null
+++ b/src/utils/simpleMarkdownRules/linkify.js
@@ -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'
+ }
+}
\ No newline at end of file
diff --git a/src/views/App.vue b/src/views/App.vue
index f06099f..458ab3e 100644
--- a/src/views/App.vue
+++ b/src/views/App.vue
@@ -1,42 +1,14 @@
-
+
-
-
-
-
- explore
- Explore
-
-
-
- chat
- Direct Message
-
-
-
- forum
- Servers
-
-
-
- list_alt
- Changelog
-
-
-
-
+
@@ -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;
diff --git a/src/views/HomePage - Legacy.vue b/src/views/HomePage - Legacy.vue
deleted file mode 100644
index 21eeaba..0000000
--- a/src/views/HomePage - Legacy.vue
+++ /dev/null
@@ -1,508 +0,0 @@
-
-
-
-
-
-
-
-
-
- The best chat client that wont restrict you from important and fun features.
-
-

-
-
- Change log Click for details
-
-
-
-
- {{ change.shortTitle }}
-
-
- {{ change.date }}
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-