Message click actions (#655)

* feat(MessageClickAction): add modifier configuration for individual actions

* feat(MessageClickAction): add click to copy action

* refactor(MessageClickActions): input agnostic actions - Also adds middle click as an input

* refactor(MessageClickActions): clean up plugin settings

* fix(MessageClickActions): header

* feat(MessageClickActions): remove middleClickAction until I fix it

* refactor(MessageClickActions): add back useful settings (doubleClickOthersAction, quote, pin, etc)

* fix fallback for guild_id

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

* fix fallback for guild_id

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

* fix(MessageClickActions): add missing EDIT_REPLY case to executeAction

* fix(MessageClickActions): resolve Copilot review comments (guild_id fallback, focusChanged, double-click condition, permissions, etc.)

* add bro back (sorry)

* sorry thororen-senpai

---------

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
This commit is contained in:
Key 2026-01-19 17:50:03 +00:00 • committed by GitHub
parent 2fa4ad813c
commit 7d228aeafc
No known key found for this signature in database
GPG key ID: B5690EEEBB952194

View file

@ -11,118 +11,141 @@ import { Devs, EquicordDevs } from "@utils/constants";
import { copyWithToast, insertTextIntoChatInputBox } from "@utils/discord";
import { Logger } from "@utils/Logger";
import definePlugin, { OptionType } from "@utils/types";
import { Channel, Message } from "@vencord/discord-types";
import type { Message } from "@vencord/discord-types";
import { ApplicationIntegrationType, MessageFlags } from "@vencord/discord-types/enums";
import { AuthenticationStore, Constants, EditMessageStore, FluxDispatcher, MessageActions, MessageTypeSets, PermissionsBits, PermissionStore, PinActions, RestAPI, Toasts, WindowStore } from "@webpack/common";
import { AuthenticationStore, Constants, EditMessageStore, FluxDispatcher, MessageActions, MessageTypeSets, PermissionsBits, PermissionStore, PinActions, RestAPI, showToast, Toasts, WindowStore } from "@webpack/common";
let isKeyPressed = false;
type Modifier = "NONE" | "SHIFT" | "CTRL" | "ALT" | "BACKSPACE";
type ClickAction = "NONE" | "DELETE" | "COPY_LINK" | "COPY_ID" | "COPY_CONTENT" | "COPY_USER_ID" | "EDIT" | "REPLY" | "REACT" | "OPEN_THREAD" | "OPEN_TAB" | "EDIT_REPLY" | "QUOTE" | "PIN";
const actions: { label: string; value: ClickAction; }[] = [
{ label: "None", value: "NONE" },
{ label: "Delete", value: "DELETE" },
{ label: "Copy Link", value: "COPY_LINK" },
{ label: "Copy ID", value: "COPY_ID" },
{ label: "Copy Content", value: "COPY_CONTENT" },
{ label: "Copy User ID", value: "COPY_USER_ID" },
{ label: "Edit", value: "EDIT" },
{ label: "Reply", value: "REPLY" },
{ label: "React", value: "REACT" },
{ label: "Open Thread", value: "OPEN_THREAD" },
{ label: "Open Tab", value: "OPEN_TAB" }
];
const doubleClickOwnActions: { label: string; value: ClickAction; }[] = [
{ label: "None", value: "NONE" },
{ label: "Edit", value: "EDIT" },
{ label: "Quote", value: "QUOTE" },
{ label: "Copy Content", value: "COPY_CONTENT" },
{ label: "Copy Link", value: "COPY_LINK" },
{ label: "Copy ID", value: "COPY_ID" },
{ label: "Copy User ID", value: "COPY_USER_ID" },
{ label: "React", value: "REACT" },
{ label: "Pin", value: "PIN" }
];
const doubleClickOthersActions: { label: string; value: ClickAction; }[] = [
{ label: "None", value: "NONE" },
{ label: "Reply", value: "REPLY" },
{ label: "Quote", value: "QUOTE" },
{ label: "Copy Content", value: "COPY_CONTENT" },
{ label: "Copy Link", value: "COPY_LINK" },
{ label: "Copy ID", value: "COPY_ID" },
{ label: "Copy User ID", value: "COPY_USER_ID" },
{ label: "React", value: "REACT" },
{ label: "Pin", value: "PIN" }
];
const modifiers: { label: string; value: Modifier; }[] = [
{ label: "None", value: "NONE" },
{ label: "Shift", value: "SHIFT" },
{ label: "Ctrl", value: "CTRL" },
{ label: "Alt", value: "ALT" }
];
const singleClickModifiers: { label: string; value: Modifier; }[] = [
{ label: "Backspace", value: "BACKSPACE" },
...modifiers
];
const pressedModifiers = new Set<Modifier>();
const keydown = (e: KeyboardEvent) => {
const key = settings.store.keySelection === "backspace" ? "Backspace" : "Delete";
if (e.key === key) {
isKeyPressed = true;
}
const mod = modifierFromKey(e);
if (mod) pressedModifiers.add(mod);
if (e.key === "Backspace") pressedModifiers.add("BACKSPACE");
};
const keyup = (e: KeyboardEvent) => {
const key = settings.store.keySelection === "backspace" ? "Backspace" : "Delete";
if (e.key === key) {
isKeyPressed = false;
const mod = modifierFromKey(e);
if (mod) pressedModifiers.delete(mod);
if (e.key === "Backspace") pressedModifiers.delete("BACKSPACE");
};
const focusChanged = () => {
if (!WindowStore.isFocused()) {
pressedModifiers.clear();
}
};
const focusChanged = () => !WindowStore.isFocused() && (isKeyPressed = false);
function modifierFromKey(e: KeyboardEvent): Modifier | null {
if (e.key === "Shift") return "SHIFT";
if (e.key === "Control") return "CTRL";
if (e.key === "Alt") return "ALT";
return null;
}
function isModifierPressed(modifier: Modifier): boolean {
return modifier === "NONE" || pressedModifiers.has(modifier);
}
let doubleClickTimeout: ReturnType<typeof setTimeout> | null = null;
let pendingDoubleClickAction: (() => void) | null = null;
enum ClickAction {
NONE = "none",
DELETE = "delete",
EDIT = "edit",
REPLY = "reply",
COPY_CONTENT = "copy_content",
COPY_LINK = "copy_link",
COPY_MESSAGE_ID = "copy_message_id",
COPY_USER_ID = "copy_user_id",
QUOTE = "quote",
REACT = "react",
PIN = "pin"
}
const settings = definePluginSettings({
backspaceClickAction: {
type: OptionType.SELECT,
description: "Action when holding Backspace and clicking a message",
options: [
{ label: "Delete Message", value: ClickAction.DELETE, default: true },
{ label: "Copy Content", value: ClickAction.COPY_CONTENT },
{ label: "Copy Link", value: ClickAction.COPY_LINK },
{ label: "Copy Message ID", value: ClickAction.COPY_MESSAGE_ID },
{ label: "Copy User ID", value: ClickAction.COPY_USER_ID },
{ label: "None (Disabled)", value: ClickAction.NONE }
]
reactEmoji: {
type: OptionType.STRING,
description: "Emoji to use for react actions",
default: "💀"
},
keySelection: {
singleClickAction: {
type: OptionType.SELECT,
description: "Key to use for click actions (Backspace or Delete)",
options: [
{ label: "Backspace", value: "backspace", default: true },
{ label: "Delete", value: "delete" }
]
description: "Action on single click with modifier",
options: actions,
default: "DELETE"
},
singleClickModifier: {
type: OptionType.SELECT,
description: "Modifier required for single click action",
options: singleClickModifiers,
default: "BACKSPACE"
},
doubleClickAction: {
type: OptionType.SELECT,
description: "Action on double-click (on your messages)",
options: [
{ label: "Edit Message", value: ClickAction.EDIT, default: true },
{ label: "Quote", value: ClickAction.QUOTE },
{ label: "Copy Content", value: ClickAction.COPY_CONTENT },
{ label: "Copy Link", value: ClickAction.COPY_LINK },
{ label: "Copy Message ID", value: ClickAction.COPY_MESSAGE_ID },
{ label: "Copy User ID", value: ClickAction.COPY_USER_ID },
{ label: "React", value: ClickAction.REACT },
{ label: "Pin Message", value: ClickAction.PIN },
{ label: "None (Disabled)", value: ClickAction.NONE }
]
description: "Action on double-click (your messages)",
options: doubleClickOwnActions,
default: "EDIT"
},
doubleClickOthersAction: {
type: OptionType.SELECT,
description: "Action on double-click (on others' messages)",
options: [
{ label: "Reply", value: ClickAction.REPLY, default: true },
{ label: "Quote", value: ClickAction.QUOTE },
{ label: "Copy Content", value: ClickAction.COPY_CONTENT },
{ label: "Copy Link", value: ClickAction.COPY_LINK },
{ label: "Copy Message ID", value: ClickAction.COPY_MESSAGE_ID },
{ label: "Copy User ID", value: ClickAction.COPY_USER_ID },
{ label: "React", value: ClickAction.REACT },
{ label: "Pin Message", value: ClickAction.PIN },
{ label: "None (Disabled)", value: ClickAction.NONE }
]
description: "Action on double-click (others' messages)",
options: doubleClickOthersActions,
default: "REPLY"
},
doubleClickModifier: {
type: OptionType.SELECT,
description: "Modifier required for double-click action",
options: modifiers,
default: "NONE"
},
tripleClickAction: {
type: OptionType.SELECT,
description: "Action on triple-click",
options: [
{ label: "React", value: ClickAction.REACT, default: true },
{ label: "Edit Message", value: ClickAction.EDIT },
{ label: "Reply", value: ClickAction.REPLY },
{ label: "Quote", value: ClickAction.QUOTE },
{ label: "Copy Content", value: ClickAction.COPY_CONTENT },
{ label: "Copy Link", value: ClickAction.COPY_LINK },
{ label: "Copy Message ID", value: ClickAction.COPY_MESSAGE_ID },
{ label: "Copy User ID", value: ClickAction.COPY_USER_ID },
{ label: "Pin Message", value: ClickAction.PIN },
{ label: "None (Disabled)", value: ClickAction.NONE }
]
options: actions,
default: "REACT"
},
reactEmoji: {
type: OptionType.STRING,
description: "Emoji to use for react actions (e.g. 💀 or pepe:123456789)",
default: "💀"
},
requireModifier: {
type: OptionType.BOOLEAN,
description: "Only perform click actions when shift or ctrl is held",
default: false
tripleClickModifier: {
type: OptionType.SELECT,
description: "Modifier required for triple-click action",
options: modifiers,
default: "NONE"
},
disableInDms: {
type: OptionType.BOOLEAN,
@ -131,12 +154,12 @@ const settings = definePluginSettings({
},
disableInSystemDms: {
type: OptionType.BOOLEAN,
description: "Disable all click actions in system DMs (e.g. Clyde, Discord)",
description: "Disable all click actions in system DMs",
default: true
},
clickTimeout: {
type: OptionType.NUMBER,
description: "Timeout in milliseconds to distinguish double/triple clicks",
description: "Timeout to distinguish double/triple clicks (ms)",
default: 300
},
quoteWithReply: {
@ -146,7 +169,7 @@ const settings = definePluginSettings({
},
useSelectionForQuote: {
type: OptionType.BOOLEAN,
description: "When quoting, use selected text if available (otherwise quotes full message)",
description: "When quoting, use selected text if available",
default: false
}
});
@ -162,29 +185,11 @@ function showWarning(message: string) {
});
}
function executeCommonAction(action: ClickAction, channel: Channel, msg: Message): boolean {
switch (action) {
case ClickAction.COPY_CONTENT:
copyWithToast(msg.content || "", "Message content copied!");
return true;
case ClickAction.COPY_LINK:
copyWithToast(`https://discord.com/channels/${channel.guild_id ?? "@me"}/${channel.id}/${msg.id}`, "Message link copied!");
return true;
case ClickAction.COPY_MESSAGE_ID:
copyWithToast(msg.id, "Message ID copied!");
return true;
case ClickAction.COPY_USER_ID:
copyWithToast(msg.author.id, "User ID copied!");
return true;
}
return false;
}
function isMessageReplyable(msg: Message) {
return MessageTypeSets.REPLYABLE.has(msg.type) && !msg.hasFlag(MessageFlags.EPHEMERAL);
}
async function toggleReaction(channelId: string, messageId: string, emoji: string, channel: Channel, msg: Message) {
async function toggleReaction(channelId: string, messageId: string, emoji: string, channel: { id: string; }, msg: Message) {
const trimmed = emoji.trim();
if (!trimmed) return;
@ -220,7 +225,37 @@ async function toggleReaction(channelId: string, messageId: string, emoji: strin
}
}
function togglePin(channel: Channel, msg: Message) {
async function copyMessageLink(msg: Message, channel: { id: string; guild_id?: string | null; }) {
const guildId = channel.guild_id ?? "@me";
const link = `https://discord.com/channels/${guildId}/${channel.id}/${msg.id}`;
try {
await navigator.clipboard.writeText(link);
showToast("Message link copied", Toasts.Type.SUCCESS);
} catch (e) {
new Logger("MessageClickActions").error("Failed to copy link:", e);
}
}
async function copyMessageId(msg: Message) {
try {
await navigator.clipboard.writeText(msg.id);
showToast("Message ID copied", Toasts.Type.SUCCESS);
} catch (e) {
new Logger("MessageClickActions").error("Failed to copy message ID:", e);
}
}
async function copyUserId(msg: Message) {
try {
await navigator.clipboard.writeText(msg.author.id);
showToast("User ID copied", Toasts.Type.SUCCESS);
} catch (e) {
new Logger("MessageClickActions").error("Failed to copy user ID:", e);
}
}
function togglePin(channel: { id: string; }, msg: Message) {
if (!PermissionStore.can(PermissionsBits.MANAGE_MESSAGES, channel)) {
showWarning("Cannot pin: Missing permissions");
return;
@ -233,7 +268,7 @@ function togglePin(channel: Channel, msg: Message) {
}
}
function quoteMessage(channel: Channel, msg: Message) {
function quoteMessage(channel: { id: string; guild_id?: string | null; isPrivate?: () => boolean; }, msg: Message) {
if (!isMessageReplyable(msg)) {
showWarning("Cannot quote this message type");
return;
@ -258,14 +293,154 @@ function quoteMessage(channel: Channel, msg: Message) {
channel,
message: msg,
shouldMention: false,
showMentionToggle: !channel.isPrivate()
showMentionToggle: !channel.isPrivate?.()
});
}
}
function openInNewTab(msg: Message, channel: { id: string; guild_id?: string | null; }) {
const guildId = channel.guild_id ?? "@me";
const link = `https://discord.com/channels/${guildId}/${channel.id}/${msg.id}`;
VencordNative.native.openExternal(link);
}
function openInThread(msg: Message, channel: { id: string; }) {
FluxDispatcher.dispatch({
type: "OPEN_THREAD_FLOW_MODAL",
channelId: channel.id,
messageId: msg.id
});
}
async function executeAction(
action: ClickAction,
msg: Message,
channel: { id: string; guild_id?: string | null; isDM?: () => boolean; isSystemDM?: () => boolean; isPrivate?: () => boolean; },
event: MouseEvent
) {
const myId = AuthenticationStore.getId();
const isMe = msg.author.id === myId;
const isSelfInvokedUserApp = msg.interactionMetadata?.authorizing_integration_owners?.[ApplicationIntegrationType.USER_INSTALL] === myId;
switch (action) {
case "DELETE":
if (!(isMe || PermissionStore.can(PermissionsBits.MANAGE_MESSAGES, channel) || isSelfInvokedUserApp)) return;
if (msg.deleted) {
FluxDispatcher.dispatch({
type: "MESSAGE_DELETE",
channelId: channel.id,
id: msg.id,
mlDeleted: true
});
} else {
MessageActions.deleteMessage(channel.id, msg.id);
}
event.preventDefault();
break;
case "COPY_LINK":
await copyMessageLink(msg, channel);
event.preventDefault();
break;
case "COPY_ID":
await copyMessageId(msg);
event.preventDefault();
break;
case "COPY_CONTENT":
copyWithToast(msg.content || "", "Message content copied!");
event.preventDefault();
break;
case "COPY_USER_ID":
await copyUserId(msg);
event.preventDefault();
break;
case "EDIT":
if (!isMe) return;
if (EditMessageStore.isEditing(channel.id, msg.id) || msg.state !== "SENT") return;
MessageActions.startEditMessage(channel.id, msg.id, msg.content);
event.preventDefault();
break;
case "REPLY":
if (!MessageTypeSets.REPLYABLE.has(msg.type) || msg.hasFlag(MessageFlags.EPHEMERAL)) return;
if (channel.guild_id && !PermissionStore.can(PermissionsBits.SEND_MESSAGES, channel)) return;
const isShiftPress = event.shiftKey;
const shouldMention = isPluginEnabled(NoReplyMentionPlugin.name)
? NoReplyMentionPlugin.shouldMention(msg, isShiftPress)
: !isShiftPress;
FluxDispatcher.dispatch({
type: "CREATE_PENDING_REPLY",
channel,
message: msg,
shouldMention,
showMentionToggle: channel.guild_id !== null
});
event.preventDefault();
break;
case "EDIT_REPLY":
if (isMe) {
if (EditMessageStore.isEditing(channel.id, msg.id) || msg.state !== "SENT") return;
MessageActions.startEditMessage(channel.id, msg.id, msg.content);
} else {
if (!MessageTypeSets.REPLYABLE.has(msg.type) || msg.hasFlag(MessageFlags.EPHEMERAL)) return;
if (channel.guild_id && !PermissionStore.can(PermissionsBits.SEND_MESSAGES, channel)) return;
const shouldMentionReply = isPluginEnabled(NoReplyMentionPlugin.name)
? NoReplyMentionPlugin.shouldMention(msg, false)
: true;
FluxDispatcher.dispatch({
type: "CREATE_PENDING_REPLY",
channel,
message: msg,
shouldMention: shouldMentionReply,
showMentionToggle: channel.guild_id !== null
});
}
event.preventDefault();
break;
case "QUOTE":
quoteMessage(channel, msg);
event.preventDefault();
break;
case "PIN":
togglePin(channel, msg);
event.preventDefault();
break;
case "REACT":
await toggleReaction(channel.id, msg.id, settings.store.reactEmoji, channel, msg);
event.preventDefault();
break;
case "OPEN_THREAD":
openInThread(msg, channel);
event.preventDefault();
break;
case "OPEN_TAB":
openInNewTab(msg, channel);
event.preventDefault();
break;
case "NONE":
break;
}
}
export default definePlugin({
name: "MessageClickActions",
description: "Customize message click actions - choose what happens when you click, double-click, or hold backspace",
description: "Customize click actions on messages.",
authors: [Devs.Ven, EquicordDevs.keyages, EquicordDevs.ZcraftElite],
isModified: true,
@ -292,131 +467,56 @@ export default definePlugin({
onMessageClick(msg, channel, event) {
const myId = AuthenticationStore.getId();
const isMe = msg.author.id === myId;
const isSelfInvokedUserApp = msg.interactionMetadata?.authorizing_integration_owners[ApplicationIntegrationType.USER_INSTALL] === myId;
const isDM = channel.isDM();
const isSystemDM = channel.isSystemDM();
if ((settings.store.disableInDms && isDM) || (settings.store.disableInSystemDms && isSystemDM)) return;
if (isKeyPressed) {
const action = settings.store.backspaceClickAction;
if (action === ClickAction.NONE) return;
const singleClickAction = settings.store.singleClickAction as ClickAction;
const doubleClickAction = isMe
? (settings.store.doubleClickAction as ClickAction)
: (settings.store.doubleClickOthersAction as ClickAction);
const tripleClickAction = settings.store.tripleClickAction as ClickAction;
if (action === ClickAction.DELETE) {
if (!(isMe || PermissionStore.can(PermissionsBits.MANAGE_MESSAGES, channel) || isSelfInvokedUserApp)) {
showWarning("Cannot delete: Missing permissions");
return;
}
const singleClickModifier = settings.store.singleClickModifier as Modifier;
const doubleClickModifier = settings.store.doubleClickModifier as Modifier;
const tripleClickModifier = settings.store.tripleClickModifier as Modifier;
if (msg.deleted) {
FluxDispatcher.dispatch({
type: "MESSAGE_DELETE",
channelId: channel.id,
id: msg.id,
mlDeleted: true
});
} else {
MessageActions.deleteMessage(channel.id, msg.id);
}
} else if (executeCommonAction(action, channel, msg)) {
event.preventDefault();
return;
}
}
const isSingleClick = event.detail === 1 && event.button === 0;
const isDoubleClick = event.detail === 2;
const isTripleClick = event.detail === 3;
if (event.detail === 3) {
if (isTripleClick) {
if (doubleClickTimeout) {
clearTimeout(doubleClickTimeout);
doubleClickTimeout = null;
pendingDoubleClickAction = null;
}
const action = settings.store.tripleClickAction;
if (action === ClickAction.NONE) return;
if (action === ClickAction.REACT) {
toggleReaction(channel.id, msg.id, settings.store.reactEmoji, channel, msg);
} else if (action === ClickAction.EDIT) {
if (!isMe) return;
if (EditMessageStore.isEditing(channel.id, msg.id) || msg.state !== "SENT") return;
MessageActions.startEditMessage(channel.id, msg.id, msg.content);
} else if (action === ClickAction.REPLY) {
if (channel.guild_id && !PermissionStore.can(PermissionsBits.SEND_MESSAGES, channel)) {
showWarning("Cannot reply: Missing permissions");
return;
}
if (!isMessageReplyable(msg)) {
showWarning("Cannot reply to this message type");
return;
}
FluxDispatcher.dispatch({
type: "CREATE_PENDING_REPLY",
channel,
message: msg,
shouldMention: !isMe,
showMentionToggle: !channel.isPrivate()
});
} else if (executeCommonAction(action, channel, msg)) {
event.preventDefault();
return;
} else if (action === ClickAction.QUOTE) {
quoteMessage(channel, msg);
} else if (action === ClickAction.PIN) {
togglePin(channel, msg);
if (isModifierPressed(tripleClickModifier) && tripleClickAction !== "NONE") {
executeAction(tripleClickAction, msg, channel, event);
}
event.preventDefault();
return;
}
if (event.detail !== 2) return;
if (settings.store.requireModifier && !event.ctrlKey && !event.shiftKey) return;
if (msg.deleted === true) return;
if (!isDoubleClick) {
if (isSingleClick && isModifierPressed(singleClickModifier) && singleClickAction !== "NONE") {
executeAction(singleClickAction, msg, channel, event);
return;
}
}
const executeDoubleClick = () => {
const action = isMe ? settings.store.doubleClickAction : settings.store.doubleClickOthersAction;
if (action === ClickAction.NONE) return;
if (action === ClickAction.EDIT) {
if (!isMe) return;
if (EditMessageStore.isEditing(channel.id, msg.id) || msg.state !== "SENT") return;
MessageActions.startEditMessage(channel.id, msg.id, msg.content);
} else if (action === ClickAction.REPLY) {
if (isMe) return;
if (channel.guild_id && !PermissionStore.can(PermissionsBits.SEND_MESSAGES, channel)) {
showWarning("Cannot reply: Missing permissions");
return;
}
if (!isMessageReplyable(msg)) {
showWarning("Cannot reply to this message type");
return;
}
const isShiftPress = event.shiftKey && !settings.store.requireModifier;
const shouldMention = isPluginEnabled(NoReplyMentionPlugin.name)
? NoReplyMentionPlugin.shouldMention(msg, isShiftPress)
: !isShiftPress;
FluxDispatcher.dispatch({
type: "CREATE_PENDING_REPLY",
channel,
message: msg,
shouldMention,
showMentionToggle: !channel.isPrivate()
});
} else if (executeCommonAction(action, channel, msg)) {
event.preventDefault();
return;
} else if (action === ClickAction.QUOTE) {
quoteMessage(channel, msg);
} else if (action === ClickAction.REACT) {
toggleReaction(channel.id, msg.id, settings.store.reactEmoji, channel, msg);
} else if (action === ClickAction.PIN) {
togglePin(channel, msg);
if (channel.guild_id && !PermissionStore.can(PermissionsBits.SEND_MESSAGES, channel)) return;
if (msg.deleted === true) return;
if (doubleClickAction !== "NONE") {
executeAction(doubleClickAction, msg, channel, event);
}
};
if (settings.store.tripleClickAction !== ClickAction.NONE) {
const canTripleClick = isModifierPressed(tripleClickModifier) && tripleClickAction !== "NONE";
if (canTripleClick) {
if (doubleClickTimeout) {
clearTimeout(doubleClickTimeout);
}
@ -427,7 +527,7 @@ export default definePlugin({
doubleClickTimeout = null;
}, settings.store.clickTimeout);
event.preventDefault();
} else {
} else if (isDoubleClick && (isModifierPressed(doubleClickModifier) || doubleClickModifier === "NONE")) {
executeDoubleClick();
event.preventDefault();
}