mirror of
https://github.com/danbulant/Nertivia-Client
synced 2026-07-06 03:20:55 +00:00
added mentioning
This commit is contained in:
parent
5c76f628d4
commit
c1509654d8
17 changed files with 450 additions and 74 deletions
|
|
@ -1,7 +1,7 @@
|
||||||
<template>
|
<template>
|
||||||
<div class="emoji-suggetions-list">
|
<div class="emoji-suggetions-list">
|
||||||
<div
|
<div
|
||||||
v-for="(emoji, index) in $props.emojiArray.slice(0, 10)"
|
v-for="(emoji, index) in $props.emojiArray"
|
||||||
:key="emoji.hexcode || emoji.emojiID"
|
:key="emoji.hexcode || emoji.emojiID"
|
||||||
:class="{ emojiItem: true, selected: index === emojiIndex }"
|
:class="{ emojiItem: true, selected: index === emojiIndex }"
|
||||||
@mouseenter="hoverEvent"
|
@mouseenter="hoverEvent"
|
||||||
|
|
@ -83,6 +83,7 @@ export default {
|
||||||
<style scoped>
|
<style scoped>
|
||||||
.selected {
|
.selected {
|
||||||
background: rgba(255, 255, 255, 0.2);
|
background: rgba(255, 255, 255, 0.2);
|
||||||
|
color: white;
|
||||||
}
|
}
|
||||||
.emoji-suggetions-list {
|
.emoji-suggetions-list {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
|
|
@ -92,9 +93,10 @@ export default {
|
||||||
overflow-y: auto;
|
overflow-y: auto;
|
||||||
z-index: 2;
|
z-index: 2;
|
||||||
background: rgba(0, 0, 0, 0.7);
|
background: rgba(0, 0, 0, 0.7);
|
||||||
|
box-shadow: 0px 0px 20px 5px #000000bd;
|
||||||
backdrop-filter: blur(5px);
|
backdrop-filter: blur(5px);
|
||||||
border-radius: 4px;
|
border-radius: 4px;
|
||||||
color: white;
|
color: rgba(255, 255, 255, 0.7);
|
||||||
transition: 0.3s;
|
transition: 0.3s;
|
||||||
user-select: none;
|
user-select: none;
|
||||||
cursor: default;
|
cursor: default;
|
||||||
|
|
|
||||||
|
|
@ -36,7 +36,8 @@
|
||||||
class="item material-icons"
|
class="item material-icons"
|
||||||
:class="{
|
:class="{
|
||||||
selected: currentTab == 2,
|
selected: currentTab == 2,
|
||||||
notifyAnimation: serverNotification
|
notifyAnimation: serverNotification.notification,
|
||||||
|
mentioned: serverNotification.mentioned
|
||||||
}"
|
}"
|
||||||
@click="switchTab(2)"
|
@click="switchTab(2)"
|
||||||
@mouseenter="localToolTipEvent('Servers', $event)"
|
@mouseenter="localToolTipEvent('Servers', $event)"
|
||||||
|
|
@ -197,7 +198,7 @@ export default {
|
||||||
serverNotification() {
|
serverNotification() {
|
||||||
const notifications = this.$store.getters.notifications;
|
const notifications = this.$store.getters.notifications;
|
||||||
const channels = this.$store.getters.channels;
|
const channels = this.$store.getters.channels;
|
||||||
const notification = notifications.find(e => {
|
const notificationsFiltered = notifications.filter(e => {
|
||||||
return (
|
return (
|
||||||
channels[e.channelID] &&
|
channels[e.channelID] &&
|
||||||
channels[e.channelID].server_id &&
|
channels[e.channelID].server_id &&
|
||||||
|
|
@ -206,7 +207,11 @@ export default {
|
||||||
this.currentTab !== 2)
|
this.currentTab !== 2)
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
return notification;
|
const mentioned = notifications.find(m => m.mentioned);
|
||||||
|
return {
|
||||||
|
notification: !!notificationsFiltered.length,
|
||||||
|
mentioned: !!mentioned
|
||||||
|
};
|
||||||
},
|
},
|
||||||
DMNotification() {
|
DMNotification() {
|
||||||
const notifications = this.$store.getters.notifications;
|
const notifications = this.$store.getters.notifications;
|
||||||
|
|
@ -307,7 +312,12 @@ export default {
|
||||||
background: #ee3e34;
|
background: #ee3e34;
|
||||||
flex-shrink: 0;
|
flex-shrink: 0;
|
||||||
}
|
}
|
||||||
|
.mentioned:before {
|
||||||
|
content: "@";
|
||||||
|
margin-bottom: 10px;
|
||||||
|
font-size: 13px;
|
||||||
|
background: #ff6947;
|
||||||
|
}
|
||||||
.tool-tip {
|
.tool-tip {
|
||||||
color: white;
|
color: white;
|
||||||
position: absolute;
|
position: absolute;
|
||||||
|
|
@ -325,4 +335,5 @@ export default {
|
||||||
cursor: default;
|
cursor: default;
|
||||||
transition: 0.2s;
|
transition: 0.2s;
|
||||||
}
|
}
|
||||||
|
|
||||||
</style>
|
</style>
|
||||||
|
|
|
||||||
|
|
@ -36,6 +36,7 @@
|
||||||
:timeEdited="msg.timeEdited"
|
:timeEdited="msg.timeEdited"
|
||||||
:color="msg.color"
|
:color="msg.color"
|
||||||
:isServer="isServer"
|
:isServer="isServer"
|
||||||
|
:mentions="msg.mentions"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<uploadsQueue v-if="uploadQueue !== undefined" :queue="uploadQueue" />
|
<uploadsQueue v-if="uploadQueue !== undefined" :queue="uploadQueue" />
|
||||||
|
|
|
||||||
|
|
@ -55,6 +55,7 @@
|
||||||
</div>
|
</div>
|
||||||
</transition>
|
</transition>
|
||||||
<emoji-suggestions v-if="emojiArray" :emojiArray="emojiArray" />
|
<emoji-suggestions v-if="emojiArray" :emojiArray="emojiArray" />
|
||||||
|
<mentions-popout v-if="mentionsArray" :list="mentionsArray" />
|
||||||
<emoji-panel v-if="showEmojiPanel" @close="showEmojiPanel = false" />
|
<emoji-panel v-if="showEmojiPanel" @close="showEmojiPanel = false" />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
@ -192,6 +193,7 @@ import { bus } from "../../main";
|
||||||
import Spinner from "@/components/Spinner.vue";
|
import Spinner from "@/components/Spinner.vue";
|
||||||
import heading from "@/components/app/MessagePanel/Heading.vue";
|
import heading from "@/components/app/MessagePanel/Heading.vue";
|
||||||
import emojiSuggestions from "@/components/app/EmojiPanels/emojiSuggestions.vue";
|
import emojiSuggestions from "@/components/app/EmojiPanels/emojiSuggestions.vue";
|
||||||
|
import mentionsPopout from "@/components/app/mentionsPopout.vue";
|
||||||
import MessageLogs from "@/components/app/MessageLogs.vue";
|
import MessageLogs from "@/components/app/MessageLogs.vue";
|
||||||
import emojiParser from "@/utils/emojiParser.js";
|
import emojiParser from "@/utils/emojiParser.js";
|
||||||
import windowProperties from "@/utils/windowProperties";
|
import windowProperties from "@/utils/windowProperties";
|
||||||
|
|
@ -211,7 +213,8 @@ export default {
|
||||||
heading,
|
heading,
|
||||||
EditPanel,
|
EditPanel,
|
||||||
MessageLogs,
|
MessageLogs,
|
||||||
TypingStatus
|
TypingStatus,
|
||||||
|
mentionsPopout
|
||||||
},
|
},
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
|
|
@ -283,6 +286,19 @@ export default {
|
||||||
|
|
||||||
return ("" + number).substring(add);
|
return ("" + number).substring(add);
|
||||||
},
|
},
|
||||||
|
replaceMentions(message) {
|
||||||
|
const regex = /@([\w\d\s_-]+):([\w\d_-]+)/g;
|
||||||
|
|
||||||
|
return message.replace(regex, word => {
|
||||||
|
const [username, tag] = word.split(":");
|
||||||
|
if (tag.length !== 4 || !username || !tag) return word;
|
||||||
|
const member = Object.values(this.members).find(
|
||||||
|
m => "@" + m.username === username && m.tag === tag
|
||||||
|
);
|
||||||
|
if (!member) return word;
|
||||||
|
return `<@${member.uniqueID}>`;
|
||||||
|
});
|
||||||
|
},
|
||||||
async sendMessage() {
|
async sendMessage() {
|
||||||
this.$refs["input-box"].focus();
|
this.$refs["input-box"].focus();
|
||||||
this.message = this.message.trim();
|
this.message = this.message.trim();
|
||||||
|
|
@ -293,7 +309,8 @@ export default {
|
||||||
clearInterval(this.postTimerID);
|
clearInterval(this.postTimerID);
|
||||||
this.postTimerID = null;
|
this.postTimerID = null;
|
||||||
|
|
||||||
const msg = emojiParser.replaceShortcode(this.message);
|
let msg = emojiParser.replaceShortcode(this.message);
|
||||||
|
msg = this.replaceMentions(msg);
|
||||||
|
|
||||||
const tempID = this.generateNum(25);
|
const tempID = this.generateNum(25);
|
||||||
|
|
||||||
|
|
@ -409,6 +426,19 @@ export default {
|
||||||
}
|
}
|
||||||
bus.$emit("scrollDown");
|
bus.$emit("scrollDown");
|
||||||
},
|
},
|
||||||
|
mentionsSwitchKey(event) {
|
||||||
|
if (!this.mentionsArray) return;
|
||||||
|
if (event.keyCode === 38) {
|
||||||
|
bus.$emit("mentions:key", "up");
|
||||||
|
event.preventDefault();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (event.keyCode === 40) {
|
||||||
|
bus.$emit("mentions:key", "down");
|
||||||
|
event.preventDefault();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
},
|
||||||
emojiSwitchKey(event) {
|
emojiSwitchKey(event) {
|
||||||
if (!this.emojiArray) return;
|
if (!this.emojiArray) return;
|
||||||
|
|
||||||
|
|
@ -438,10 +468,10 @@ export default {
|
||||||
},
|
},
|
||||||
showEmojiPopout(event) {
|
showEmojiPopout(event) {
|
||||||
if (event.keyCode == 38 || event.keyCode == 40) return; // up/down
|
if (event.keyCode == 38 || event.keyCode == 40) return; // up/down
|
||||||
|
const message = this.$refs["input-box"].value;
|
||||||
const cursorPosition = event.target.selectionStart;
|
const cursorPosition = event.target.selectionStart;
|
||||||
const cursorWord = this.ReturnWord(this.message, cursorPosition);
|
const cursorWord = this.ReturnWord(message, cursorPosition);
|
||||||
const cursorLetter = this.message.substring(
|
const cursorLetter = message.substring(
|
||||||
cursorPosition - 1,
|
cursorPosition - 1,
|
||||||
cursorPosition
|
cursorPosition
|
||||||
);
|
);
|
||||||
|
|
@ -452,11 +482,49 @@ export default {
|
||||||
if (!cursorWord.startsWith(":") || cursorWord.length <= 2)
|
if (!cursorWord.startsWith(":") || cursorWord.length <= 2)
|
||||||
return this.$store.dispatch("setEmojiArray", null);
|
return this.$store.dispatch("setEmojiArray", null);
|
||||||
|
|
||||||
const searchArr = emojiParser.searchEmoji(cursorWord.slice(1, -1));
|
const searchArr = emojiParser.searchEmoji(
|
||||||
|
cursorWord.slice(1, cursorWord.length)
|
||||||
|
);
|
||||||
if (searchArr.length <= 0)
|
if (searchArr.length <= 0)
|
||||||
return this.$store.dispatch("setEmojiArray", null);
|
return this.$store.dispatch("setEmojiArray", null);
|
||||||
|
|
||||||
this.$store.dispatch("setEmojiArray", searchArr);
|
this.$store.dispatch("setEmojiArray", searchArr.slice(0, 10));
|
||||||
|
},
|
||||||
|
showMentionsPopout(event) {
|
||||||
|
if (event.keyCode == 38 || event.keyCode == 40) return; // up/down
|
||||||
|
const message = this.$refs["input-box"].value;
|
||||||
|
const cursorPosition = event.target.selectionStart;
|
||||||
|
const cursorWord = this.ReturnWord(message, cursorPosition);
|
||||||
|
|
||||||
|
if (!cursorWord.startsWith("@")) {
|
||||||
|
this.$store.dispatch("mentionsListModule/setMentionsArray", null);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// word without @
|
||||||
|
const wordWithoutBegining = cursorWord
|
||||||
|
.slice(1, cursorWord.length)
|
||||||
|
.toLowerCase();
|
||||||
|
|
||||||
|
let searchedMembers = [];
|
||||||
|
|
||||||
|
for (let index = 0; index < this.serverMembers.length; index++) {
|
||||||
|
const serverMember = this.serverMembers[index];
|
||||||
|
if (serverMember.server_id != this.server.server_id) continue;
|
||||||
|
const member = this.members[serverMember.uniqueID];
|
||||||
|
if (
|
||||||
|
member.username
|
||||||
|
.toLowerCase()
|
||||||
|
.replace(/\s/g, "")
|
||||||
|
.includes(wordWithoutBegining)
|
||||||
|
) {
|
||||||
|
searchedMembers.push(member);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
searchedMembers = searchedMembers.slice(0, 9);
|
||||||
|
this.$store.dispatch(
|
||||||
|
"mentionsListModule/setMentionsArray",
|
||||||
|
searchedMembers
|
||||||
|
);
|
||||||
},
|
},
|
||||||
async onInput(event) {
|
async onInput(event) {
|
||||||
const value = event.target.value.trim();
|
const value = event.target.value.trim();
|
||||||
|
|
@ -466,7 +534,24 @@ export default {
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
keyUp(event) {
|
keyUp(event) {
|
||||||
this.showEmojiPopout(event);
|
setTimeout(() => {
|
||||||
|
this.showMentionsPopout(event);
|
||||||
|
this.showEmojiPopout(event);
|
||||||
|
}, 10);
|
||||||
|
},
|
||||||
|
enterMention() {
|
||||||
|
const member = this.mentionsArray[this.mentionsListIndex];
|
||||||
|
if (!member) return;
|
||||||
|
this.$store.dispatch("mentionsListModule/setMentionsArray", null);
|
||||||
|
const cursorPosition = this.$refs["input-box"].selectionStart;
|
||||||
|
const cursorWord = this.ReturnWord(this.message, cursorPosition);
|
||||||
|
const start = cursorPosition - cursorWord.length;
|
||||||
|
const end = cursorPosition;
|
||||||
|
|
||||||
|
this.message =
|
||||||
|
this.message.substring(0, start) +
|
||||||
|
`@${member.username}:${member.tag} ` +
|
||||||
|
this.message.substring(end);
|
||||||
},
|
},
|
||||||
enterEmojiSuggestion() {
|
enterEmojiSuggestion() {
|
||||||
const emoji = this.emojiArray[this.emojiIndex];
|
const emoji = this.emojiArray[this.emojiIndex];
|
||||||
|
|
@ -505,6 +590,7 @@ export default {
|
||||||
this.$store.dispatch("settingsModule/addRecentEmoji", shortcode);
|
this.$store.dispatch("settingsModule/addRecentEmoji", shortcode);
|
||||||
},
|
},
|
||||||
keyDown(event) {
|
keyDown(event) {
|
||||||
|
this.mentionsSwitchKey(event);
|
||||||
this.emojiSwitchKey(event);
|
this.emojiSwitchKey(event);
|
||||||
// when enter is press
|
// when enter is press
|
||||||
if (event.keyCode == 13) {
|
if (event.keyCode == 13) {
|
||||||
|
|
@ -518,6 +604,13 @@ export default {
|
||||||
this.enterEmojiSuggestion();
|
this.enterEmojiSuggestion();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
if (
|
||||||
|
this.mentionsArray &&
|
||||||
|
this.mentionsArray[this.mentionsListIndex]
|
||||||
|
) {
|
||||||
|
this.enterMention();
|
||||||
|
return;
|
||||||
|
}
|
||||||
if (this.editMessage) {
|
if (this.editMessage) {
|
||||||
return this.updateMessage();
|
return this.updateMessage();
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -662,6 +755,7 @@ export default {
|
||||||
|
|
||||||
bus.$on("newMessage", this.hideTypingStatus);
|
bus.$on("newMessage", this.hideTypingStatus);
|
||||||
bus.$on("emojiSuggestions:Selected", this.enterEmojiSuggestion);
|
bus.$on("emojiSuggestions:Selected", this.enterEmojiSuggestion);
|
||||||
|
bus.$on("mentions:Selected", this.enterMention);
|
||||||
bus.$on("emojiPanel:Selected", this.enterEmojiPanel);
|
bus.$on("emojiPanel:Selected", this.enterEmojiPanel);
|
||||||
|
|
||||||
bus.$on("scrolledDown", scrolledDown => {
|
bus.$on("scrolledDown", scrolledDown => {
|
||||||
|
|
@ -678,7 +772,8 @@ export default {
|
||||||
|
|
||||||
bus.$off("newMessage", this.hideTypingStatus);
|
bus.$off("newMessage", this.hideTypingStatus);
|
||||||
bus.$off("emojiSuggestions:Selected", this.enterEmojiSuggestion);
|
bus.$off("emojiSuggestions:Selected", this.enterEmojiSuggestion);
|
||||||
bus.$on("emojiPanel:Selected", this.enterEmojiPanel);
|
bus.$off("emojiPanel:Selected", this.enterEmojiPanel);
|
||||||
|
bus.$off("mentions:Selected", this.enterMention);
|
||||||
window.removeEventListener("focus", this.onFocus);
|
window.removeEventListener("focus", this.onFocus);
|
||||||
window.removeEventListener("blur", this.onBlur);
|
window.removeEventListener("blur", this.onBlur);
|
||||||
|
|
||||||
|
|
@ -722,6 +817,14 @@ export default {
|
||||||
undefined
|
undefined
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
|
members() {
|
||||||
|
const members = this.$store.getters["members/members"];
|
||||||
|
return members;
|
||||||
|
},
|
||||||
|
serverMembers() {
|
||||||
|
const serverMembers = this.$store.getters["servers/serverMembers"];
|
||||||
|
return serverMembers.reverse();
|
||||||
|
},
|
||||||
serverMember() {
|
serverMember() {
|
||||||
return this.$store.getters["servers/serverMembers"].find(
|
return this.$store.getters["servers/serverMembers"].find(
|
||||||
sm =>
|
sm =>
|
||||||
|
|
@ -783,9 +886,15 @@ export default {
|
||||||
emojiArray() {
|
emojiArray() {
|
||||||
return this.$store.getters.emojiArray;
|
return this.$store.getters.emojiArray;
|
||||||
},
|
},
|
||||||
|
mentionsArray() {
|
||||||
|
return this.$store.getters["mentionsListModule/mentionsArray"];
|
||||||
|
},
|
||||||
emojiIndex() {
|
emojiIndex() {
|
||||||
return this.$store.getters.getEmojiIndex;
|
return this.$store.getters.getEmojiIndex;
|
||||||
},
|
},
|
||||||
|
mentionsListIndex() {
|
||||||
|
return this.$store.getters["mentionsListModule/getMentionIndex"];
|
||||||
|
},
|
||||||
recipients() {
|
recipients() {
|
||||||
const selectedChannel = this.$store.getters.selectedChannelID;
|
const selectedChannel = this.$store.getters.selectedChannelID;
|
||||||
const channel = this.$store.getters.channels[selectedChannel];
|
const channel = this.$store.getters.channels[selectedChannel];
|
||||||
|
|
|
||||||
|
|
@ -37,6 +37,13 @@
|
||||||
{{ this.$props.username }}
|
{{ this.$props.username }}
|
||||||
</div>
|
</div>
|
||||||
<div class="date">{{ getDate }}</div>
|
<div class="date">{{ getDate }}</div>
|
||||||
|
<div
|
||||||
|
class="mentioned material-icons"
|
||||||
|
v-if="mentioned"
|
||||||
|
title="You were mentioned"
|
||||||
|
>
|
||||||
|
alternate_email
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<SimpleMarkdown
|
<SimpleMarkdown
|
||||||
class="content-message"
|
class="content-message"
|
||||||
|
|
@ -116,8 +123,8 @@
|
||||||
<span class="username" @click="openUserInformation">{{
|
<span class="username" @click="openUserInformation">{{
|
||||||
this.$props.username
|
this.$props.username
|
||||||
}}</span>
|
}}</span>
|
||||||
<span v-if="type === 1" class="text">has joined the server!</span>
|
<span v-if="type === 1" class="text">joined the server!</span>
|
||||||
<span v-if="type === 2" class="text">has left the server.</span>
|
<span v-if="type === 2" class="text">left the server.</span>
|
||||||
<span v-if="type === 3" class="text">has been kicked.</span>
|
<span v-if="type === 3" class="text">has been kicked.</span>
|
||||||
<span v-if="type === 4" class="text">has been banned.</span>
|
<span v-if="type === 4" class="text">has been banned.</span>
|
||||||
<span class="date">{{ getDate }}</span>
|
<span class="date">{{ getDate }}</span>
|
||||||
|
|
@ -161,7 +168,8 @@ export default {
|
||||||
"channelID",
|
"channelID",
|
||||||
"timeEdited",
|
"timeEdited",
|
||||||
"color",
|
"color",
|
||||||
"isServer"
|
"isServer",
|
||||||
|
"mentions"
|
||||||
],
|
],
|
||||||
components: {
|
components: {
|
||||||
ProfilePicture,
|
ProfilePicture,
|
||||||
|
|
@ -171,7 +179,8 @@ export default {
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
hover: false,
|
hover: false,
|
||||||
isGif: false
|
isGif: false,
|
||||||
|
mentioned: false
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
|
|
@ -263,14 +272,28 @@ export default {
|
||||||
},
|
},
|
||||||
onResize() {
|
onResize() {
|
||||||
this.imageSize();
|
this.imageSize();
|
||||||
|
},
|
||||||
|
checkMentioned() {
|
||||||
|
if (
|
||||||
|
this.mentions &&
|
||||||
|
this.mentions.find(u => u.uniqueID === this.user.uniqueID)
|
||||||
|
) {
|
||||||
|
this.mentioned = true;
|
||||||
|
} else {
|
||||||
|
this.mentioned = false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
watch: {
|
watch: {
|
||||||
getWindowWidth(dimentions) {
|
getWindowWidth(dimentions) {
|
||||||
this.onResize(dimentions);
|
this.onResize(dimentions);
|
||||||
|
},
|
||||||
|
mentions() {
|
||||||
|
this.checkMentioned();
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
mounted() {
|
mounted() {
|
||||||
|
this.checkMentioned();
|
||||||
this.isGif = this.userAvatar.endsWith(".gif");
|
this.isGif = this.userAvatar.endsWith(".gif");
|
||||||
const files = this.files;
|
const files = this.files;
|
||||||
if (!files || files.length === 0 || !files[0].dimensions) return undefined;
|
if (!files || files.length === 0 || !files[0].dimensions) return undefined;
|
||||||
|
|
@ -603,7 +626,24 @@ $message-color: rgba(0, 0, 0, 0.3);
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
margin-top: 5px;
|
margin-top: 5px;
|
||||||
}
|
}
|
||||||
|
.mentioned {
|
||||||
|
margin-left: 5px;
|
||||||
|
margin-right: 5px;
|
||||||
|
flex-shrink: 0;
|
||||||
|
color: white;
|
||||||
|
background: rgba(255, 59, 59, 0.9);
|
||||||
|
font-size: 13px;
|
||||||
|
display: flex;
|
||||||
|
align-content: center;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
margin-top: -2px;
|
||||||
|
padding: 2px;
|
||||||
|
height: 15px;
|
||||||
|
width: 15px;
|
||||||
|
border-radius: 50%;
|
||||||
|
cursor: default;
|
||||||
|
}
|
||||||
@media (max-width: 468px) {
|
@media (max-width: 468px) {
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
|
||||||
|
|
@ -194,48 +194,8 @@ export default {
|
||||||
},
|
},
|
||||||
selectedServerID() {
|
selectedServerID() {
|
||||||
return this.$store.getters["servers/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 ||
|
|
||||||
!document.hasFocus() ||
|
|
||||||
this.currentTab !== 2)
|
|
||||||
);
|
|
||||||
});
|
|
||||||
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 ||
|
|
||||||
!document.hasFocus() ||
|
|
||||||
this.currentTab !== 1)
|
|
||||||
);
|
|
||||||
});
|
|
||||||
// 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() {
|
mounted() {
|
||||||
bus.$on("server-tool-tip", this.serverToolTipEvent);
|
bus.$on("server-tool-tip", this.serverToolTipEvent);
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,8 @@
|
||||||
:data-servername="serverData.name"
|
:data-servername="serverData.name"
|
||||||
:class="{
|
:class="{
|
||||||
selected: selectedServerID === serverData.server_id,
|
selected: selectedServerID === serverData.server_id,
|
||||||
notifyAnimation: notification
|
notifyAnimation: notification.notification,
|
||||||
|
mentioned: notification.mentioned
|
||||||
}"
|
}"
|
||||||
@contextmenu.prevent="contextEvent"
|
@contextmenu.prevent="contextEvent"
|
||||||
@mouseenter="hoverEvent"
|
@mouseenter="hoverEvent"
|
||||||
|
|
@ -47,7 +48,7 @@ export default {
|
||||||
const notifications = this.$store.getters.notifications;
|
const notifications = this.$store.getters.notifications;
|
||||||
|
|
||||||
const channels = this.$store.getters.channels;
|
const channels = this.$store.getters.channels;
|
||||||
const notification = notifications.find(e => {
|
const filteredNotifications = notifications.filter(e => {
|
||||||
return (
|
return (
|
||||||
channels[e.channelID] &&
|
channels[e.channelID] &&
|
||||||
channels[e.channelID].server_id &&
|
channels[e.channelID].server_id &&
|
||||||
|
|
@ -56,7 +57,11 @@ export default {
|
||||||
(this.selectedChannelID !== e.channelID || !document.hasFocus())
|
(this.selectedChannelID !== e.channelID || !document.hasFocus())
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
return notification;
|
const mentioned = filteredNotifications.find(n => n.mentioned);
|
||||||
|
return {
|
||||||
|
mentioned: !!mentioned,
|
||||||
|
notification: !!filteredNotifications.length
|
||||||
|
};
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
|
|
@ -125,7 +130,6 @@ export default {
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
align-content: center;
|
align-content: center;
|
||||||
justify-content: center;
|
|
||||||
font-size: 15px;
|
font-size: 15px;
|
||||||
position: absolute;
|
position: absolute;
|
||||||
z-index: 115651;
|
z-index: 115651;
|
||||||
|
|
@ -136,4 +140,10 @@ export default {
|
||||||
border-radius: 50%;
|
border-radius: 50%;
|
||||||
background: #ee3e34;
|
background: #ee3e34;
|
||||||
}
|
}
|
||||||
|
.mentioned:after {
|
||||||
|
content: "@";
|
||||||
|
margin-bottom: 10px;
|
||||||
|
font-size: 13px;
|
||||||
|
background: #ff6947;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,10 @@
|
||||||
<template>
|
<template>
|
||||||
<div class="formatted-content" v-html="markdown"></div>
|
<div
|
||||||
|
class="formatted-content"
|
||||||
|
ref="content"
|
||||||
|
@click="textClicked"
|
||||||
|
v-html="markdown"
|
||||||
|
></div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
|
|
@ -9,6 +14,14 @@ export default {
|
||||||
props: {
|
props: {
|
||||||
message: String
|
message: String
|
||||||
},
|
},
|
||||||
|
methods: {
|
||||||
|
textClicked(event) {
|
||||||
|
if (event.target.classList[0] === "mention") {
|
||||||
|
const id = event.target.id.split("-")[1];
|
||||||
|
this.$store.dispatch("setUserInformationPopout", id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
computed: {
|
computed: {
|
||||||
markdown: function() {
|
markdown: function() {
|
||||||
return messageFormatter(this.message);
|
return messageFormatter(this.message);
|
||||||
|
|
@ -47,4 +60,16 @@ pre {
|
||||||
.link {
|
.link {
|
||||||
color: #68aaff;
|
color: #68aaff;
|
||||||
}
|
}
|
||||||
|
.mention {
|
||||||
|
background: rgba(0, 0, 0, 0.3);
|
||||||
|
color: rgb(94, 164, 255);
|
||||||
|
font-weight: bold;
|
||||||
|
border-radius: 7px;
|
||||||
|
padding: 3px;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: 0.2s;
|
||||||
|
}
|
||||||
|
.mention:hover {
|
||||||
|
background: rgba(0, 0, 0, 0.6);
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
|
||||||
118
src/components/app/mentionsPopout.vue
Normal file
118
src/components/app/mentionsPopout.vue
Normal file
|
|
@ -0,0 +1,118 @@
|
||||||
|
<template>
|
||||||
|
<div class="list">
|
||||||
|
<div
|
||||||
|
v-for="(member, index) in list"
|
||||||
|
:key="member.uniqueID"
|
||||||
|
:class="{ item: true, selected: index === mentionIndex }"
|
||||||
|
@mouseenter="hoverEvent"
|
||||||
|
@click="clickEvent"
|
||||||
|
>
|
||||||
|
<img class="avatar" :src="avatarPath + member.avatar + '?type=webp'" />
|
||||||
|
<div class="username">{{ member.username }}</div>
|
||||||
|
<div class="tag">:{{ member.tag }}</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import { bus } from "@/main";
|
||||||
|
import config from "@/config.js";
|
||||||
|
export default {
|
||||||
|
props: ["list"],
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
avatarPath: config.domain + "/avatars/"
|
||||||
|
};
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
mentionIndex() {
|
||||||
|
return this.$store.getters["mentionsListModule/getMentionIndex"];
|
||||||
|
}
|
||||||
|
},
|
||||||
|
watch: {
|
||||||
|
list() {
|
||||||
|
this.changeIndex(0);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
mounted() {
|
||||||
|
bus.$on("mentions:key", this.KeySwitch);
|
||||||
|
},
|
||||||
|
destroyed() {
|
||||||
|
bus.$off("mentions:key", this.KeySwitch);
|
||||||
|
this.$store.dispatch("mentionsListModule/setMentionsArray", null);
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
changeIndex(index) {
|
||||||
|
this.$store.dispatch("mentionsListModule/changeIndex", index);
|
||||||
|
},
|
||||||
|
hoverEvent(event) {
|
||||||
|
const mention = event.target.closest(".item");
|
||||||
|
const parent = event.target.parentElement.children;
|
||||||
|
if (!mention) return;
|
||||||
|
const index = [...parent].findIndex(el => el === mention);
|
||||||
|
if (index >= 0) this.changeIndex(index);
|
||||||
|
},
|
||||||
|
KeySwitch(key) {
|
||||||
|
if (key == "up") {
|
||||||
|
if (this.mentionIndex == 0)
|
||||||
|
return this.changeIndex(this.$props.list.length - 1);
|
||||||
|
|
||||||
|
this.changeIndex(this.mentionIndex - 1);
|
||||||
|
}
|
||||||
|
if (key == "down") {
|
||||||
|
if (this.mentionIndex == this.$props.list.length - 1)
|
||||||
|
return this.changeIndex(0);
|
||||||
|
this.changeIndex(this.mentionIndex + 1);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
clickEvent() {
|
||||||
|
bus.$emit("mentions:Selected");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.selected {
|
||||||
|
color: white;
|
||||||
|
background: rgba(255, 255, 255, 0.2);
|
||||||
|
}
|
||||||
|
.list {
|
||||||
|
position: absolute;
|
||||||
|
bottom: -35px;
|
||||||
|
left: 50px;
|
||||||
|
max-height: 400px;
|
||||||
|
overflow-y: auto;
|
||||||
|
z-index: 2;
|
||||||
|
background: rgba(0, 0, 0, 0.7);
|
||||||
|
box-shadow: 0px 0px 20px 5px #000000bd;
|
||||||
|
backdrop-filter: blur(5px);
|
||||||
|
border-radius: 4px;
|
||||||
|
color: rgba(255, 255, 255, 0.7);
|
||||||
|
transition: 0.3s;
|
||||||
|
user-select: none;
|
||||||
|
cursor: default;
|
||||||
|
}
|
||||||
|
.avatar {
|
||||||
|
width: 25px;
|
||||||
|
height: 25px;
|
||||||
|
margin-right: 5px;
|
||||||
|
border-radius: 50%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.item {
|
||||||
|
display: flex;
|
||||||
|
padding: 5px;
|
||||||
|
align-content: center;
|
||||||
|
align-items: center;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
.tag {
|
||||||
|
color: rgba(255, 255, 255, 0.3);
|
||||||
|
}
|
||||||
|
@media (max-height: 441px) {
|
||||||
|
.list {
|
||||||
|
max-height: 150px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
@ -9,6 +9,7 @@ import settingsModule from "./modules/settingsModule";
|
||||||
import uploadFilesModule from "./modules/uploadFilesModule";
|
import uploadFilesModule from "./modules/uploadFilesModule";
|
||||||
import popoutsModule from "./modules/popoutsModule/popoutsModule.js";
|
import popoutsModule from "./modules/popoutsModule/popoutsModule.js";
|
||||||
import emojiSuggestionModule from "./modules/emojiSuggestionModule";
|
import emojiSuggestionModule from "./modules/emojiSuggestionModule";
|
||||||
|
import mentionsListModule from "./modules/mentionsListModule";
|
||||||
import serversModule from "./modules/serversModule";
|
import serversModule from "./modules/serversModule";
|
||||||
import membersModule from "./modules/membersModule";
|
import membersModule from "./modules/membersModule";
|
||||||
Vue.use(Vuex);
|
Vue.use(Vuex);
|
||||||
|
|
@ -24,6 +25,7 @@ export const store = new Vuex.Store({
|
||||||
uploadFilesModule,
|
uploadFilesModule,
|
||||||
popoutsModule,
|
popoutsModule,
|
||||||
emojiSuggestionModule,
|
emojiSuggestionModule,
|
||||||
|
mentionsListModule,
|
||||||
servers: serversModule,
|
servers: serversModule,
|
||||||
members: membersModule
|
members: membersModule
|
||||||
},
|
},
|
||||||
|
|
|
||||||
41
src/store/modules/mentionsListModule.js
Normal file
41
src/store/modules/mentionsListModule.js
Normal file
|
|
@ -0,0 +1,41 @@
|
||||||
|
import Vue from "vue";
|
||||||
|
|
||||||
|
const state = {
|
||||||
|
array: null,
|
||||||
|
index: 0
|
||||||
|
};
|
||||||
|
|
||||||
|
const getters = {
|
||||||
|
mentionsArray(state) {
|
||||||
|
return state.array;
|
||||||
|
},
|
||||||
|
getMentionIndex(state) {
|
||||||
|
return state.index;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const actions = {
|
||||||
|
setMentionsArray(context, array) {
|
||||||
|
context.commit("setMentionsArray", array);
|
||||||
|
},
|
||||||
|
changeIndex(context, index) {
|
||||||
|
context.commit("changeIndex", index);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const mutations = {
|
||||||
|
setMentionsArray(state, array) {
|
||||||
|
Vue.set(state, "array", array);
|
||||||
|
},
|
||||||
|
changeIndex(state, index) {
|
||||||
|
Vue.set(state, "index", index);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export default {
|
||||||
|
namespaced: true,
|
||||||
|
state,
|
||||||
|
getters,
|
||||||
|
actions,
|
||||||
|
mutations
|
||||||
|
};
|
||||||
|
|
@ -16,7 +16,7 @@ const actions = {
|
||||||
context.commit("addAllNotifications", notifications);
|
context.commit("addAllNotifications", notifications);
|
||||||
},
|
},
|
||||||
messageCreatedNotification(context, notification) {
|
messageCreatedNotification(context, notification) {
|
||||||
const { channelID, lastMessageID, sender } = notification;
|
const { channelID, lastMessageID, sender, mentioned } = notification;
|
||||||
const currentTab = context.rootGetters.currentTab;
|
const currentTab = context.rootGetters.currentTab;
|
||||||
|
|
||||||
// dont display a notification if the channel is selected.
|
// dont display a notification if the channel is selected.
|
||||||
|
|
@ -33,12 +33,12 @@ const actions = {
|
||||||
if (find) {
|
if (find) {
|
||||||
return context.commit("messageCreatedNotification", {
|
return context.commit("messageCreatedNotification", {
|
||||||
exists: true,
|
exists: true,
|
||||||
notification: { channelID, lastMessageID, sender }
|
notification: { channelID, lastMessageID, sender, mentioned }
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
context.commit("messageCreatedNotification", {
|
context.commit("messageCreatedNotification", {
|
||||||
exists: false,
|
exists: false,
|
||||||
notification: { channelID, lastMessageID, sender, count: 1 }
|
notification: { channelID, lastMessageID, sender, count: 1, mentioned }
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
dismissNotification(context, channelID) {
|
dismissNotification(context, channelID) {
|
||||||
|
|
@ -66,6 +66,13 @@ const mutations = {
|
||||||
if (state.notifications[i].channelID === notification.channelID) {
|
if (state.notifications[i].channelID === notification.channelID) {
|
||||||
const count = state.notifications[i].count;
|
const count = state.notifications[i].count;
|
||||||
Vue.set(state.notifications[i], "count", count + 1);
|
Vue.set(state.notifications[i], "count", count + 1);
|
||||||
|
if (!state.notifications[i].mentioned) {
|
||||||
|
Vue.set(
|
||||||
|
state.notifications[i],
|
||||||
|
"mentioned",
|
||||||
|
notification.mentioned
|
||||||
|
);
|
||||||
|
}
|
||||||
Vue.set(
|
Vue.set(
|
||||||
state.notifications[i],
|
state.notifications[i],
|
||||||
"lastMessageID",
|
"lastMessageID",
|
||||||
|
|
|
||||||
|
|
@ -217,10 +217,14 @@ const actions = {
|
||||||
);
|
);
|
||||||
desktopNotification();
|
desktopNotification();
|
||||||
}
|
}
|
||||||
|
|
||||||
const notification = {
|
const notification = {
|
||||||
channelID: data.message.channelID,
|
channelID: data.message.channelID,
|
||||||
lastMessageID: data.message.messageID,
|
lastMessageID: data.message.messageID,
|
||||||
sender: data.message.creator
|
sender: data.message.creator,
|
||||||
|
mentioned: !!data.message.mentions.find(
|
||||||
|
m => m.uniqueID === context.rootState.user.user.uniqueID
|
||||||
|
)
|
||||||
};
|
};
|
||||||
context.dispatch("messageCreatedNotification", notification);
|
context.dispatch("messageCreatedNotification", notification);
|
||||||
function desktopNotification() {
|
function desktopNotification() {
|
||||||
|
|
@ -496,9 +500,16 @@ const actions = {
|
||||||
context.dispatch("servers/addMemberRole", { role_id, uniqueID, server_id });
|
context.dispatch("servers/addMemberRole", { role_id, uniqueID, server_id });
|
||||||
},
|
},
|
||||||
// eslint-disable-next-line prettier/prettier
|
// eslint-disable-next-line prettier/prettier
|
||||||
["socket_serverMember:removeRole"](context, { role_id, uniqueID, server_id }) {
|
["socket_serverMember:removeRole"](
|
||||||
|
context,
|
||||||
|
{ role_id, uniqueID, server_id }
|
||||||
|
) {
|
||||||
// eslint-disable-next-line prettier/prettier
|
// eslint-disable-next-line prettier/prettier
|
||||||
context.dispatch("servers/removeMemberRole", { role_id, uniqueID, server_id });
|
context.dispatch("servers/removeMemberRole", {
|
||||||
|
role_id,
|
||||||
|
uniqueID,
|
||||||
|
server_id
|
||||||
|
});
|
||||||
},
|
},
|
||||||
["socket_server:updateRoles"](context, { roles }) {
|
["socket_server:updateRoles"](context, { roles }) {
|
||||||
// eslint-disable-next-line prettier/prettier
|
// eslint-disable-next-line prettier/prettier
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,13 @@
|
||||||
const config = [
|
const config = [
|
||||||
|
{
|
||||||
|
version: 9.8,
|
||||||
|
title: "Wake up @Fishie!",
|
||||||
|
shortTitle: "",
|
||||||
|
date: "12/02/2020",
|
||||||
|
new: [
|
||||||
|
"Is someone not responding to you? Wake them up by mentioning them by typing '@' followed by their name in the message input box."
|
||||||
|
],
|
||||||
|
},
|
||||||
{
|
{
|
||||||
version: 9.7,
|
version: 9.7,
|
||||||
title: "You have 99 mails!",
|
title: "You have 99 mails!",
|
||||||
|
|
|
||||||
|
|
@ -54,10 +54,12 @@ export default {
|
||||||
const customEmojis = store.state["settingsModule"].customEmojis;
|
const customEmojis = store.state["settingsModule"].customEmojis;
|
||||||
return [
|
return [
|
||||||
...matchSorter(customEmojis, shortCode, {
|
...matchSorter(customEmojis, shortCode, {
|
||||||
keys: ["name"]
|
keys: ["name"],
|
||||||
|
threshold: matchSorter.rankings.CONTAINS
|
||||||
}),
|
}),
|
||||||
...matchSorter(emojis, shortCode, {
|
...matchSorter(emojis, shortCode, {
|
||||||
keys: ["shortcodes"]
|
keys: ["shortcodes"],
|
||||||
|
threshold: matchSorter.rankings.CONTAINS
|
||||||
})
|
})
|
||||||
];
|
];
|
||||||
},
|
},
|
||||||
|
|
|
||||||
26
src/utils/markdown-rules/mentions.js
Normal file
26
src/utils/markdown-rules/mentions.js
Normal file
|
|
@ -0,0 +1,26 @@
|
||||||
|
import * as SimpleMarkdown from "simple-markdown";
|
||||||
|
import { store } from "@/store/index";
|
||||||
|
|
||||||
|
export default order => {
|
||||||
|
return {
|
||||||
|
order: order++,
|
||||||
|
match: function(source) {
|
||||||
|
return /^<@([\d]+)>/.exec(source);
|
||||||
|
},
|
||||||
|
|
||||||
|
parse: function(capture) {
|
||||||
|
return {
|
||||||
|
id: capture[1],
|
||||||
|
orig: capture[0]
|
||||||
|
};
|
||||||
|
},
|
||||||
|
html: function(node) {
|
||||||
|
const member = store.getters["members/members"][node.id];
|
||||||
|
if (!member) return node.orig;
|
||||||
|
return SimpleMarkdown.htmlTag("span", "@" + member.username, {
|
||||||
|
class: "mention",
|
||||||
|
id: "mention-" + member.uniqueID
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
@ -8,6 +8,7 @@ import strikeout from "./markdown-rules/strikeout";
|
||||||
import inlineCodeblock from "./markdown-rules/inlineCodeblock";
|
import inlineCodeblock from "./markdown-rules/inlineCodeblock";
|
||||||
import link from "./markdown-rules/link";
|
import link from "./markdown-rules/link";
|
||||||
import customEmoji from "./markdown-rules/customEmoji";
|
import customEmoji from "./markdown-rules/customEmoji";
|
||||||
|
import mentions from "./markdown-rules/mentions";
|
||||||
|
|
||||||
let order = 0; // order the below rules as declared below rather than by the original defaultRules order:
|
let order = 0; // order the below rules as declared below rather than by the original defaultRules order:
|
||||||
|
|
||||||
|
|
@ -17,6 +18,7 @@ const rules = {
|
||||||
underline: underline(order++),
|
underline: underline(order++),
|
||||||
strikeout: strikeout(order++),
|
strikeout: strikeout(order++),
|
||||||
link: link(order++),
|
link: link(order++),
|
||||||
|
mentions: mentions(order++),
|
||||||
customEmoji: customEmoji(order++),
|
customEmoji: customEmoji(order++),
|
||||||
|
|
||||||
strong: Object.assign({}, SimpleMarkdown.defaultRules.strong, {
|
strong: Object.assign({}, SimpleMarkdown.defaultRules.strong, {
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue