Fix context menu generation
This commit is contained in:
parent
de76a86757
commit
2a6c57abaa
@ -15,7 +15,6 @@
|
|||||||
import throttle from "lodash/throttle";
|
import throttle from "lodash/throttle";
|
||||||
import constants from "../js/constants";
|
import constants from "../js/constants";
|
||||||
import storage from "../js/localStorage";
|
import storage from "../js/localStorage";
|
||||||
import {generateUserContextMenu} from "../js/helpers/contextMenu";
|
|
||||||
|
|
||||||
import Sidebar from "./Sidebar.vue";
|
import Sidebar from "./Sidebar.vue";
|
||||||
import ImageViewer from "./ImageViewer.vue";
|
import ImageViewer from "./ImageViewer.vue";
|
||||||
@ -89,27 +88,6 @@ export default {
|
|||||||
|
|
||||||
this.$store.commit("userlistOpen", isUserlistOpen === "true");
|
this.$store.commit("userlistOpen", isUserlistOpen === "true");
|
||||||
},
|
},
|
||||||
openContextMenu(event, items) {
|
|
||||||
// TODO: maybe move this method to the store or some other more accessible place
|
|
||||||
this.$refs.contextMenu.open(event, items);
|
|
||||||
},
|
|
||||||
openContextMenuForMentionedNick(event, network, nick) {
|
|
||||||
// TODO: Find a better way to do this
|
|
||||||
|
|
||||||
const channel = this.$store.state.activeChannel.channel;
|
|
||||||
let user = channel.users.find((u) => u.nick === nick);
|
|
||||||
|
|
||||||
if (!user) {
|
|
||||||
user = {
|
|
||||||
nick: nick,
|
|
||||||
mode: "",
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
const items = generateUserContextMenu(this.$root, channel, network, user);
|
|
||||||
|
|
||||||
this.openContextMenu(event, items);
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
@ -25,7 +25,6 @@
|
|||||||
|
|
||||||
<script>
|
<script>
|
||||||
import socket from "../js/socket";
|
import socket from "../js/socket";
|
||||||
import {generateChannelContextMenu} from "../js/helpers/contextMenu.js";
|
|
||||||
import isChannelCollapsed from "../js/helpers/isChannelCollapsed";
|
import isChannelCollapsed from "../js/helpers/isChannelCollapsed";
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
@ -77,8 +76,11 @@ export default {
|
|||||||
this.$root.switchToChannel(this.channel);
|
this.$root.switchToChannel(this.channel);
|
||||||
},
|
},
|
||||||
openContextMenu(event) {
|
openContextMenu(event) {
|
||||||
const items = generateChannelContextMenu(this.$root, this.channel, this.network);
|
this.$root.$emit("contextmenu:channel", {
|
||||||
this.$root.$refs.app.openContextMenu(event, items);
|
event: event,
|
||||||
|
channel: this.channel,
|
||||||
|
network: this.network,
|
||||||
|
});
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
@ -95,7 +95,6 @@
|
|||||||
|
|
||||||
<script>
|
<script>
|
||||||
import socket from "../js/socket";
|
import socket from "../js/socket";
|
||||||
import {generateChannelContextMenu} from "../js/helpers/contextMenu.js";
|
|
||||||
import ParsedMessage from "./ParsedMessage.vue";
|
import ParsedMessage from "./ParsedMessage.vue";
|
||||||
import MessageList from "./MessageList.vue";
|
import MessageList from "./MessageList.vue";
|
||||||
import ChatInput from "./ChatInput.vue";
|
import ChatInput from "./ChatInput.vue";
|
||||||
@ -182,8 +181,11 @@ export default {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
openContextMenu(event) {
|
openContextMenu(event) {
|
||||||
const items = generateChannelContextMenu(this.$root, this.channel, this.network);
|
this.$root.$emit("contextmenu:channel", {
|
||||||
this.$root.$refs.app.openContextMenu(event, items);
|
event: event,
|
||||||
|
channel: this.channel,
|
||||||
|
network: this.network,
|
||||||
|
});
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
@ -32,7 +32,6 @@
|
|||||||
:on-hover="hoverUser"
|
:on-hover="hoverUser"
|
||||||
:active="user.original === activeUser"
|
:active="user.original === activeUser"
|
||||||
:user="user"
|
:user="user"
|
||||||
:context-menu-callback="openContextMenu"
|
|
||||||
/>
|
/>
|
||||||
</template>
|
</template>
|
||||||
<template v-else>
|
<template v-else>
|
||||||
@ -42,7 +41,6 @@
|
|||||||
:on-hover="hoverUser"
|
:on-hover="hoverUser"
|
||||||
:active="user === activeUser"
|
:active="user === activeUser"
|
||||||
:user="user"
|
:user="user"
|
||||||
:context-menu-callback="openContextMenu"
|
|
||||||
/>
|
/>
|
||||||
</template>
|
</template>
|
||||||
</div>
|
</div>
|
||||||
@ -54,7 +52,6 @@
|
|||||||
import {filter as fuzzyFilter} from "fuzzy";
|
import {filter as fuzzyFilter} from "fuzzy";
|
||||||
import Username from "./Username.vue";
|
import Username from "./Username.vue";
|
||||||
import UsernameFiltered from "./UsernameFiltered.vue";
|
import UsernameFiltered from "./UsernameFiltered.vue";
|
||||||
import {generateUserContextMenu} from "../js/helpers/contextMenu.js";
|
|
||||||
|
|
||||||
const modes = {
|
const modes = {
|
||||||
"~": "owner",
|
"~": "owner",
|
||||||
@ -197,11 +194,6 @@ export default {
|
|||||||
el.scrollIntoView({block: "nearest", inline: "nearest"});
|
el.scrollIntoView({block: "nearest", inline: "nearest"});
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
openContextMenu(event, user) {
|
|
||||||
const {network} = this.$store.getters.findChannel(this.channel.id);
|
|
||||||
const items = generateUserContextMenu(this.$root, this.channel, network, user);
|
|
||||||
this.$root.$refs.app.openContextMenu(event, items);
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
@ -35,6 +35,7 @@
|
|||||||
|
|
||||||
<script>
|
<script>
|
||||||
import Mousetrap from "mousetrap";
|
import Mousetrap from "mousetrap";
|
||||||
|
import {generateUserContextMenu, generateChannelContextMenu} from "../js/helpers/contextMenu.js";
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: "ContextMenu",
|
name: "ContextMenu",
|
||||||
@ -56,12 +57,33 @@ export default {
|
|||||||
mounted() {
|
mounted() {
|
||||||
Mousetrap.bind("esc", this.close);
|
Mousetrap.bind("esc", this.close);
|
||||||
Mousetrap.bind(["up", "down", "tab", "shift+tab"], this.navigateMenu);
|
Mousetrap.bind(["up", "down", "tab", "shift+tab"], this.navigateMenu);
|
||||||
|
|
||||||
|
this.$root.$on("contextmenu:user", this.openUserContextMenu);
|
||||||
|
this.$root.$on("contextmenu:channel", this.openChannelContextMenu);
|
||||||
},
|
},
|
||||||
destroyed() {
|
destroyed() {
|
||||||
Mousetrap.unbind("esc", this.close);
|
Mousetrap.unbind("esc", this.close);
|
||||||
Mousetrap.unbind(["up", "down", "tab", "shift+tab"], this.navigateMenu);
|
Mousetrap.unbind(["up", "down", "tab", "shift+tab"], this.navigateMenu);
|
||||||
|
|
||||||
|
this.$root.$off("contextmenu:user", this.openUserContextMenu);
|
||||||
|
this.$root.$off("contextmenu:channel", this.openChannelContextMenu);
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
|
openChannelContextMenu(data) {
|
||||||
|
const items = generateChannelContextMenu(this.$root, data.channel, data.network);
|
||||||
|
this.open(data.event, items);
|
||||||
|
},
|
||||||
|
openUserContextMenu(data) {
|
||||||
|
const {network, channel} = this.$store.state.activeChannel;
|
||||||
|
|
||||||
|
const items = generateUserContextMenu(
|
||||||
|
this.$root,
|
||||||
|
channel,
|
||||||
|
network,
|
||||||
|
channel.users.find((u) => u.nick === data.user.nick) || {nick: data.user.nick}
|
||||||
|
);
|
||||||
|
this.open(data.event, items);
|
||||||
|
},
|
||||||
open(event, items) {
|
open(event, items) {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
|
|
||||||
|
@ -20,11 +20,9 @@
|
|||||||
<template v-else-if="message.type === 'action'">
|
<template v-else-if="message.type === 'action'">
|
||||||
<span class="from"><span class="only-copy">* </span></span>
|
<span class="from"><span class="only-copy">* </span></span>
|
||||||
<span class="content" dir="auto">
|
<span class="content" dir="auto">
|
||||||
<Username
|
<Username :user="message.from" dir="auto" /> <ParsedMessage
|
||||||
:user="message.from"
|
:message="message"
|
||||||
dir="auto"
|
/>
|
||||||
:context-menu-callback="openUserContextMenu"
|
|
||||||
/> <ParsedMessage :network="network" :message="message" />
|
|
||||||
<LinkPreview
|
<LinkPreview
|
||||||
v-for="preview in message.previews"
|
v-for="preview in message.previews"
|
||||||
:key="preview.link"
|
:key="preview.link"
|
||||||
@ -37,7 +35,7 @@
|
|||||||
<span v-if="message.type === 'message'" class="from">
|
<span v-if="message.type === 'message'" class="from">
|
||||||
<template v-if="message.from && message.from.nick">
|
<template v-if="message.from && message.from.nick">
|
||||||
<span class="only-copy"><</span>
|
<span class="only-copy"><</span>
|
||||||
<Username :user="message.from" :context-menu-callback="openUserContextMenu" />
|
<Username :user="message.from" />
|
||||||
<span class="only-copy">> </span>
|
<span class="only-copy">> </span>
|
||||||
</template>
|
</template>
|
||||||
</span>
|
</span>
|
||||||
@ -51,7 +49,7 @@
|
|||||||
<span v-else class="from">
|
<span v-else class="from">
|
||||||
<template v-if="message.from && message.from.nick">
|
<template v-if="message.from && message.from.nick">
|
||||||
<span class="only-copy">-</span>
|
<span class="only-copy">-</span>
|
||||||
<Username :user="message.from" :context-menu-callback="openUserContextMenu" />
|
<Username :user="message.from" />
|
||||||
<span class="only-copy">- </span>
|
<span class="only-copy">- </span>
|
||||||
</template>
|
</template>
|
||||||
</span>
|
</span>
|
||||||
@ -74,7 +72,6 @@ import Username from "./Username.vue";
|
|||||||
import LinkPreview from "./LinkPreview.vue";
|
import LinkPreview from "./LinkPreview.vue";
|
||||||
import ParsedMessage from "./ParsedMessage.vue";
|
import ParsedMessage from "./ParsedMessage.vue";
|
||||||
import MessageTypes from "./MessageTypes";
|
import MessageTypes from "./MessageTypes";
|
||||||
import {generateUserContextMenu} from "../js/helpers/contextMenu.js";
|
|
||||||
import constants from "../js/constants";
|
import constants from "../js/constants";
|
||||||
|
|
||||||
MessageTypes.ParsedMessage = ParsedMessage;
|
MessageTypes.ParsedMessage = ParsedMessage;
|
||||||
@ -106,10 +103,6 @@ export default {
|
|||||||
isAction() {
|
isAction() {
|
||||||
return typeof MessageTypes["message-" + this.message.type] !== "undefined";
|
return typeof MessageTypes["message-" + this.message.type] !== "undefined";
|
||||||
},
|
},
|
||||||
openUserContextMenu(event, user) {
|
|
||||||
const items = generateUserContextMenu(this.$root, this.channel, this.network, user);
|
|
||||||
this.$root.$refs.app.openContextMenu(event, items);
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
@ -16,8 +16,7 @@ export default {
|
|||||||
? context.props.text
|
? context.props.text
|
||||||
: context.props.message.text,
|
: context.props.message.text,
|
||||||
context.props.message,
|
context.props.message,
|
||||||
context.props.network,
|
context.props.network
|
||||||
context.parent.$root
|
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
@ -4,8 +4,8 @@
|
|||||||
:data-name="user.nick"
|
:data-name="user.nick"
|
||||||
role="button"
|
role="button"
|
||||||
v-on="onHover ? {mouseover: hover} : {}"
|
v-on="onHover ? {mouseover: hover} : {}"
|
||||||
@click.prevent="rightClick($event)"
|
@click.prevent="openContextMenu"
|
||||||
@contextmenu.prevent="rightClick($event)"
|
@contextmenu.prevent="openContextMenu"
|
||||||
>{{ user.mode }}{{ user.nick }}</span
|
>{{ user.mode }}{{ user.nick }}</span
|
||||||
>
|
>
|
||||||
</template>
|
</template>
|
||||||
@ -19,7 +19,6 @@ export default {
|
|||||||
user: Object,
|
user: Object,
|
||||||
active: Boolean,
|
active: Boolean,
|
||||||
onHover: Function,
|
onHover: Function,
|
||||||
contextMenuCallback: Function,
|
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
nickColor() {
|
nickColor() {
|
||||||
@ -30,10 +29,11 @@ export default {
|
|||||||
hover() {
|
hover() {
|
||||||
return this.onHover(this.user);
|
return this.onHover(this.user);
|
||||||
},
|
},
|
||||||
rightClick($event) {
|
openContextMenu(event) {
|
||||||
if (this.contextMenuCallback) {
|
this.$root.$emit("contextmenu:user", {
|
||||||
this.contextMenuCallback($event, this.user);
|
event: event,
|
||||||
}
|
user: this.user,
|
||||||
|
});
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
@ -3,9 +3,9 @@
|
|||||||
:class="['user', nickColor, {active: active}]"
|
:class="['user', nickColor, {active: active}]"
|
||||||
:data-name="user.original.nick"
|
:data-name="user.original.nick"
|
||||||
role="button"
|
role="button"
|
||||||
@mouseover="hover"
|
@mouseover="onHover(user.original)"
|
||||||
@click.prevent="rightClick($event)"
|
@click.prevent="openContextMenu"
|
||||||
@contextmenu.prevent="rightClick($event)"
|
@contextmenu.prevent="openContextMenu"
|
||||||
v-html="user.original.mode + user.string"
|
v-html="user.original.mode + user.string"
|
||||||
/>
|
/>
|
||||||
</template>
|
</template>
|
||||||
@ -19,7 +19,6 @@ export default {
|
|||||||
user: Object,
|
user: Object,
|
||||||
active: Boolean,
|
active: Boolean,
|
||||||
onHover: Function,
|
onHover: Function,
|
||||||
contextMenuCallback: Function,
|
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
nickColor() {
|
nickColor() {
|
||||||
@ -27,13 +26,11 @@ export default {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
hover() {
|
openContextMenu(event) {
|
||||||
this.onHover ? this.onHover(this.user.original) : null;
|
this.$root.$emit("contextmenu:user", {
|
||||||
},
|
event: event,
|
||||||
rightClick($event) {
|
user: this.user.original,
|
||||||
if (this.contextMenuCallback) {
|
});
|
||||||
this.contextMenuCallback($event, this.user);
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
@ -2133,6 +2133,7 @@ part/quit messages where we don't load previews (adds a blank line otherwise) */
|
|||||||
line-height: 1.4;
|
line-height: 1.4;
|
||||||
transition: background-color 0.2s;
|
transition: background-color 0.2s;
|
||||||
border-radius: 3px;
|
border-radius: 3px;
|
||||||
|
white-space: nowrap;
|
||||||
}
|
}
|
||||||
|
|
||||||
.context-menu-item.active,
|
.context-menu-item.active,
|
||||||
|
@ -91,52 +91,46 @@ export function generateChannelContextMenu($root, channel, network) {
|
|||||||
|
|
||||||
// Add menu items for channels
|
// Add menu items for channels
|
||||||
if (channel.type === "channel") {
|
if (channel.type === "channel") {
|
||||||
items = [
|
items.push({
|
||||||
...items,
|
label: "Edit topic",
|
||||||
{
|
type: "item",
|
||||||
label: "Edit topic",
|
class: "edit",
|
||||||
type: "item",
|
action() {
|
||||||
class: "edit",
|
channel.editTopic = true;
|
||||||
action() {
|
$root.switchToChannel(channel);
|
||||||
channel.editTopic = true;
|
|
||||||
$root.switchToChannel(channel);
|
|
||||||
|
|
||||||
$root.$nextTick(() =>
|
$root.$nextTick(() =>
|
||||||
document.querySelector(`#chan-${channel.id} .topic-input`).focus()
|
document.querySelector(`#chan-${channel.id} .topic-input`).focus()
|
||||||
);
|
);
|
||||||
},
|
|
||||||
},
|
},
|
||||||
{
|
});
|
||||||
label: "List banned users",
|
items.push({
|
||||||
type: "item",
|
label: "List banned users",
|
||||||
class: "list",
|
type: "item",
|
||||||
action() {
|
class: "list",
|
||||||
socket.emit("input", {
|
action() {
|
||||||
target: channel.id,
|
socket.emit("input", {
|
||||||
text: "/banlist",
|
target: channel.id,
|
||||||
});
|
text: "/banlist",
|
||||||
},
|
});
|
||||||
},
|
},
|
||||||
];
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add menu items for queries
|
// Add menu items for queries
|
||||||
if (channel.type === "query") {
|
if (channel.type === "query") {
|
||||||
items = [
|
items.push({
|
||||||
...items,
|
label: "User information",
|
||||||
{
|
type: "item",
|
||||||
label: "User information",
|
class: "action-whois",
|
||||||
type: "item",
|
action() {
|
||||||
class: "action-whois",
|
$root.switchToChannel(channel);
|
||||||
action() {
|
socket.emit("input", {
|
||||||
$root.switchToChannel(channel);
|
target: channel.id,
|
||||||
socket.emit("input", {
|
text: "/whois " + channel.name,
|
||||||
target: $root.$store.state.activeChannel.channel.id,
|
});
|
||||||
text: "/whois " + channel.name,
|
|
||||||
});
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
];
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add close menu item
|
// Add close menu item
|
||||||
@ -159,7 +153,7 @@ export function generateChannelContextMenu($root, channel, network) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function generateUserContextMenu($root, channel, network, user) {
|
export function generateUserContextMenu($root, channel, network, user) {
|
||||||
const currentChannelUser = channel.users.filter((u) => u.nick === network.nick)[0];
|
const currentChannelUser = channel.users.find((u) => u.nick === network.nick) || {};
|
||||||
|
|
||||||
const whois = () => {
|
const whois = () => {
|
||||||
const chan = $root.$store.getters.findChannelOnCurrentNetwork(user.nick);
|
const chan = $root.$store.getters.findChannelOnCurrentNetwork(user.nick);
|
||||||
|
@ -6,11 +6,11 @@ import findLinks from "./ircmessageparser/findLinks";
|
|||||||
import findEmoji from "./ircmessageparser/findEmoji";
|
import findEmoji from "./ircmessageparser/findEmoji";
|
||||||
import findNames from "./ircmessageparser/findNames";
|
import findNames from "./ircmessageparser/findNames";
|
||||||
import merge from "./ircmessageparser/merge";
|
import merge from "./ircmessageparser/merge";
|
||||||
import colorClass from "./colorClass";
|
|
||||||
import emojiMap from "./fullnamemap.json";
|
import emojiMap from "./fullnamemap.json";
|
||||||
import LinkPreviewToggle from "../../components/LinkPreviewToggle.vue";
|
import LinkPreviewToggle from "../../components/LinkPreviewToggle.vue";
|
||||||
import LinkPreviewFileSize from "../../components/LinkPreviewFileSize.vue";
|
import LinkPreviewFileSize from "../../components/LinkPreviewFileSize.vue";
|
||||||
import InlineChannel from "../../components/InlineChannel.vue";
|
import InlineChannel from "../../components/InlineChannel.vue";
|
||||||
|
import Username from "../../components/Username.vue";
|
||||||
|
|
||||||
const emojiModifiersRegex = /[\u{1f3fb}-\u{1f3ff}]/gu;
|
const emojiModifiersRegex = /[\u{1f3fb}-\u{1f3ff}]/gu;
|
||||||
|
|
||||||
@ -70,7 +70,7 @@ function createFragment(fragment, createElement) {
|
|||||||
|
|
||||||
// Transform an IRC message potentially filled with styling control codes, URLs,
|
// Transform an IRC message potentially filled with styling control codes, URLs,
|
||||||
// nicknames, and channels into a string of HTML elements to display on the client.
|
// nicknames, and channels into a string of HTML elements to display on the client.
|
||||||
function parse(createElement, text, message = undefined, network = undefined, $root) {
|
function parse(createElement, text, message = undefined, network = undefined) {
|
||||||
// Extract the styling information and get the plain text version from it
|
// Extract the styling information and get the plain text version from it
|
||||||
const styleFragments = parseStyle(text);
|
const styleFragments = parseStyle(text);
|
||||||
const cleanText = styleFragments.map((fragment) => fragment.text).join("");
|
const cleanText = styleFragments.map((fragment) => fragment.text).join("");
|
||||||
@ -180,23 +180,16 @@ function parse(createElement, text, message = undefined, network = undefined, $r
|
|||||||
fragments
|
fragments
|
||||||
);
|
);
|
||||||
} else if (textPart.nick) {
|
} else if (textPart.nick) {
|
||||||
// TODO: This really does not belong here, find a better way
|
|
||||||
const openContextMenu = (event) => {
|
|
||||||
$root.$refs.app.openContextMenuForMentionedNick(event, network, textPart.nick);
|
|
||||||
};
|
|
||||||
|
|
||||||
return createElement(
|
return createElement(
|
||||||
"span",
|
Username,
|
||||||
{
|
{
|
||||||
class: ["user", colorClass(textPart.nick)],
|
props: {
|
||||||
attrs: {
|
user: {
|
||||||
role: "button",
|
nick: textPart.nick,
|
||||||
dir: "auto",
|
},
|
||||||
"data-name": textPart.nick,
|
|
||||||
},
|
},
|
||||||
on: {
|
attrs: {
|
||||||
contextmenu: openContextMenu,
|
dir: "auto",
|
||||||
click: openContextMenu,
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
fragments
|
fragments
|
||||||
|
@ -358,7 +358,7 @@ describe("IRC formatted message parser", () => {
|
|||||||
input: "test, MaxLeiter",
|
input: "test, MaxLeiter",
|
||||||
expected:
|
expected:
|
||||||
"test, " +
|
"test, " +
|
||||||
'<span role="button" dir="auto" data-name="MaxLeiter" class="user color-12">' +
|
'<span data-name="MaxLeiter" role="button" dir="auto" class="user color-12">' +
|
||||||
"MaxLeiter" +
|
"MaxLeiter" +
|
||||||
"</span>",
|
"</span>",
|
||||||
},
|
},
|
||||||
|
Loading…
Reference in New Issue
Block a user