Fix context menu generation

This commit is contained in:
Pavel Djundik 2019-11-23 16:26:20 +02:00
parent de76a86757
commit 2a6c57abaa
13 changed files with 97 additions and 124 deletions

View file

@ -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>

View file

@ -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,
});
}, },
}, },
}; };

View file

@ -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,
});
}, },
}, },
}; };

View file

@ -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>

View file

@ -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();

View file

@ -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" />&#32;<ParsedMessage
:user="message.from" :message="message"
dir="auto" />
:context-menu-callback="openUserContextMenu"
/>&#32;<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">&lt;</span> <span class="only-copy">&lt;</span>
<Username :user="message.from" :context-menu-callback="openUserContextMenu" /> <Username :user="message.from" />
<span class="only-copy">&gt; </span> <span class="only-copy">&gt; </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>

View file

@ -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
); );
}, },
}; };

View file

@ -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,
});
}, },
}, },
}; };

View file

@ -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);
}
}, },
}, },
}; };

View file

@ -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,

View file

@ -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);

View file

@ -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

View file

@ -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>",
}, },