290 lines
10 KiB
JavaScript
290 lines
10 KiB
JavaScript
/**
|
|
* @name Double Click To Edit
|
|
* @author Farcrada, original idea by Jiiks
|
|
* @version 9.4.7
|
|
* @description Double click a message you wrote to quickly edit it.
|
|
*
|
|
* @invite qH6UWCwfTu
|
|
* @website https://github.com/Farcrada/DiscordPlugins/
|
|
* @source https://github.com/Farcrada/DiscordPlugins/blob/master/Double-click-to-edit/DoubleClickToEdit.plugin.js
|
|
* @updateUrl https://raw.githubusercontent.com/Farcrada/DiscordPlugins/master/Double-click-to-edit/DoubleClickToEdit.plugin.js
|
|
*/
|
|
|
|
/** @type {typeof import("react")} */
|
|
const React = BdApi.React,
|
|
|
|
{ Webpack, Webpack: { Filters }, Data, Utils, ReactUtils } = BdApi,
|
|
|
|
config = {
|
|
info: {
|
|
name: "Double Click To Edit",
|
|
id: "DoubleClickToEdit",
|
|
description: "Double click a message you wrote to quickly edit it",
|
|
version: "9.4.7",
|
|
author: "Farcrada",
|
|
updateUrl: "https://raw.githubusercontent.com/Farcrada/DiscordPlugins/master/Double-click-to-edit/DoubleClickToEdit.plugin.js"
|
|
}
|
|
},
|
|
|
|
ignore = [
|
|
//Object
|
|
"video",
|
|
"emoji",
|
|
//Classes
|
|
"content",
|
|
"reactionInner"
|
|
],
|
|
walkable = [
|
|
"child",
|
|
"memoizedProps",
|
|
"sibling"
|
|
];
|
|
|
|
|
|
module.exports = class DoubleClickToEdit {
|
|
|
|
|
|
load() {
|
|
try { global.ZeresPluginLibrary.PluginUpdater.checkForUpdate(config.info.name, config.info.version, config.info.updateUrl); }
|
|
catch (err) { console.error(config.info.name, "Failed to reach the ZeresPluginLibrary for Plugin Updater.", err); }
|
|
}
|
|
|
|
start() {
|
|
try {
|
|
//Classes
|
|
this.selectedClass = Webpack.getModule(Filters.byKeys("message", "selected")).selected;
|
|
this.messagesWrapper = Webpack.getModule(Filters.byKeys("empty", "messagesWrapper")).messagesWrapper;
|
|
|
|
//Copy to clipboard
|
|
this.copyToClipboard = Webpack.getModule(Filters.byKeys("clipboard", "app")).clipboard.copy;
|
|
|
|
//Reply functions
|
|
this.replyToMessage = Webpack.getModule(m => m?.toString?.()?.replace('\n', '')?.search(/(channel:[\w|\w],message:[\w|\w],shouldMention:!)/) > -1, { searchExports: true })
|
|
this.getChannel = Webpack.getModule(Filters.byKeys("getChannel", "getDMFromUserId")).getChannel;
|
|
|
|
//Stores
|
|
this.MessageStore = Webpack.getModule(Filters.byKeys("receiveMessage", "editMessage"));
|
|
this.CurrentUserStore = Webpack.getModule(Filters.byKeys("getCurrentUser"));
|
|
|
|
//Settings
|
|
this.UIModule = Webpack.getModule(m => m.FormItem && m.RadioGroup);
|
|
|
|
//Events
|
|
global.document.addEventListener('dblclick', this.doubleclickFunc);
|
|
|
|
//Load settings
|
|
//Edit
|
|
this.doubleClickToEditModifier = Data.load(config.info.id, "doubleClickToEditModifier") ?? false;
|
|
this.editModifier = Data.load(config.info.id, "editModifier") ?? "shift";
|
|
//Reply
|
|
this.doubleClickToReply = Data.load(config.info.id, "doubleClickToReply") ?? false;
|
|
this.doubleClickToReplyModifier = Data.load(config.info.id, "doubleClickToReplyModifier") ?? false;
|
|
this.replyModifier = Data.load(config.info.id, "replyModifier") ?? "shift";
|
|
//Copy
|
|
this.doubleClickToCopy = Data.load(config.info.id, "doubleClickToCopy") ?? false;
|
|
this.copyModifier = Data.load(config.info.id, "copyModifier") ?? "shift";
|
|
|
|
}
|
|
catch (err) {
|
|
try {
|
|
console.error("Attempting to stop after starting error...", err);
|
|
this.stop();
|
|
}
|
|
catch (err) {
|
|
console.error(config.info.name + ".stop()", err);
|
|
}
|
|
}
|
|
}
|
|
|
|
//By doing this we make sure we're able to remove our event
|
|
//otherwise it gets stuck on the page and never actually unloads.
|
|
doubleclickFunc = (e) => this.handler(e);
|
|
|
|
stop = () => document.removeEventListener('dblclick', this.doubleclickFunc);
|
|
|
|
getSettingsPanel() {
|
|
//Anonymous function to preserve the this scope,
|
|
//which also makes it an anonymous functional component;
|
|
//Pretty neat.
|
|
return () => {
|
|
//Edit
|
|
const [editEnableModifier, setEditEnableModifier] = React.useState(this.doubleClickToEditModifier),
|
|
[editModifier, setEditModifier] = React.useState(this.editModifier),
|
|
//Reply
|
|
[reply, setReply] = React.useState(this.doubleClickToReply),
|
|
[replyEnableModifier, setReplyEnableModifier] = React.useState(this.doubleClickToReplyModifier),
|
|
[replyModifier, setReplyModifier] = React.useState(this.replyModifier),
|
|
//Copy
|
|
[copy, setCopy] = React.useState(this.doubleClickToCopy),
|
|
[copyModifier, setCopyModifier] = React.useState(this.copyModifier);
|
|
|
|
return [
|
|
//Edit
|
|
React.createElement(this.UIModule.FormSwitch, {
|
|
//The state that is loaded with the default value
|
|
value: editEnableModifier,
|
|
note: "Enable modifier for double clicking to edit",
|
|
//Since onChange passes the current state we can simply invoke it as such
|
|
onChange: (newState) => {
|
|
//Saving the new state
|
|
this.doubleClickToEditModifier = newState;
|
|
Data.save(config.info.id, "doubleClickToEditModifier", newState);
|
|
setEditEnableModifier(newState);
|
|
}
|
|
//Discord Is One Of Those
|
|
}, "Enable Edit Modifier"),
|
|
React.createElement(this.UIModule.FormItem, {
|
|
disabled: !editEnableModifier,
|
|
title: "Modifer to hold to edit a message"
|
|
},
|
|
React.createElement(this.UIModule.RadioGroup, {
|
|
disabled: !editEnableModifier,
|
|
value: editModifier,
|
|
options: [
|
|
{ name: "Shift", value: "shift" },
|
|
{ name: "Ctrl", value: "ctrl" },
|
|
{ name: "Alt", value: "alt" }
|
|
],
|
|
onChange: (newState) => {
|
|
this.editModifier = newState.value;
|
|
Data.save(config.info.id, "editModifier", newState.value);
|
|
setEditModifier(newState.value);
|
|
}
|
|
})),
|
|
|
|
//Reply
|
|
React.createElement(this.UIModule.FormSwitch, {
|
|
value: reply,
|
|
note: "Double click another's message and start replying.",
|
|
onChange: (newState) => {
|
|
this.doubleClickToReply = newState;
|
|
Data.save(config.info.id, "doubleClickToReply", newState);
|
|
setReply(newState);
|
|
}
|
|
}, "Enable Replying"),
|
|
React.createElement(this.UIModule.FormSwitch, {
|
|
disabled: !reply,
|
|
value: replyEnableModifier,
|
|
note: "Enable modifier for double clicking to reply",
|
|
onChange: (newState) => {
|
|
this.doubleClickToReplyModifier = newState;
|
|
Data.save(config.info.id, "doubleClickToReplyModifier", newState);
|
|
setReplyEnableModifier(newState);
|
|
}
|
|
}, "Enable Reply Modifier"),
|
|
React.createElement(this.UIModule.FormItem, {
|
|
disabled: (!reply || !replyEnableModifier),
|
|
title: "Modifier to hold when replying to a message"
|
|
},
|
|
React.createElement(this.UIModule.RadioGroup, {
|
|
disabled: (!reply || !replyEnableModifier),
|
|
value: replyModifier,
|
|
options: [
|
|
{ name: "Shift", value: "shift" },
|
|
{ name: "Ctrl", value: "ctrl" },
|
|
{ name: "Alt", value: "alt" }
|
|
],
|
|
onChange: (newState) => {
|
|
this.replyModifier = newState.value;
|
|
Data.save(config.info.id, "replyModifier", newState.value);
|
|
setReplyModifier(newState.value);
|
|
}
|
|
})),
|
|
|
|
//Copy
|
|
React.createElement(this.UIModule.FormSwitch, {
|
|
value: copy,
|
|
note: "Copy selection before entering edit-mode.",
|
|
onChange: (newState) => {
|
|
this.doubleClickToCopy = newState;
|
|
Data.save(config.info.id, "doubleClickToCopy", newState);
|
|
setCopy(newState);
|
|
}
|
|
}, "Enable Copying"),
|
|
React.createElement(this.UIModule.FormItem, {
|
|
disabled: !copy,
|
|
title: "Modifier to hold before copying text"
|
|
},
|
|
React.createElement(this.UIModule.RadioGroup, {
|
|
disabled: !copy,
|
|
value: copyModifier,
|
|
options: [
|
|
{ name: "Shift", value: "shift" },
|
|
{ name: "Ctrl", value: "ctrl" },
|
|
{ name: "Alt", value: "alt" }
|
|
],
|
|
onChange: (newState) => {
|
|
this.copyModifier = newState.value;
|
|
Data.save(config.info.id, "copyModifier", newState.value);
|
|
setCopyModifier(newState.value);
|
|
}
|
|
}))
|
|
];
|
|
}
|
|
}
|
|
|
|
handler(e) {
|
|
//Check if we're not double clicking
|
|
if (typeof (e?.target?.className) !== typeof ("") ||
|
|
ignore.some(nameOfClass => e?.target?.className?.indexOf?.(nameOfClass) > -1))
|
|
return;
|
|
|
|
//Target the message
|
|
const messageDiv = e.target.closest('li > [class^=message]');
|
|
|
|
//If it finds nothing, null it.
|
|
if (!messageDiv)
|
|
return;
|
|
//Make sure we're not resetting when the message is already in edit-mode.
|
|
if (messageDiv.classList.contains(this.selectedClass))
|
|
return;
|
|
|
|
//Basically make a HTMLElement/Node interactable with it's React components.
|
|
const instance = ReactUtils.getInternalInstance(messageDiv);
|
|
//Mandatory nullcheck
|
|
if (!instance)
|
|
return;
|
|
|
|
//When selecting text it might be useful to copy.
|
|
const copyKeyHeld = this.checkForModifier(this.doubleClickToCopy, this.copyModifier, e);
|
|
if (copyKeyHeld)
|
|
this.copyToClipboard(document.getSelection().toString());
|
|
|
|
//The message instance is filled top to bottom, as it is in view.
|
|
//As a result, "baseMessage" will be the actual message you want to address. And "message" will be the reply.
|
|
//Maybe the message has a reply, so check if "baseMessage" exists and otherwise fallback on "message".
|
|
const message = Utils.findInTree(instance, m => m?.baseMessage, { walkable: walkable })?.baseMessage ??
|
|
Utils.findInTree(instance, m => m?.message, { walkable: walkable })?.message;
|
|
|
|
if (!message)
|
|
return;
|
|
|
|
//Now we do the same thing with the edit and reply modifier
|
|
const editKeyHeld = this.checkForModifier(this.doubleClickToEditModifier, this.editModifier, e),
|
|
replyKeyHeld = this.checkForModifier(this.doubleClickToReplyModifier, this.replyModifier, e);
|
|
|
|
//If a modifier is enabled, check if the key is held, otherwise ignore.
|
|
if ((this.doubleClickToEditModifier ? editKeyHeld : true) && message.author.id === this.CurrentUserStore.getCurrentUser().id)
|
|
this.MessageStore.startEditMessage(message.channel_id, message.id, message.content);
|
|
else if ((this.doubleClickToReplyModifier ? replyKeyHeld : true) && this.doubleClickToReply)
|
|
this.replyToMessage(this.getChannel(message.channel_id), message, e);
|
|
}
|
|
|
|
/**
|
|
*
|
|
* @param {boolean} enabled Is the modifier enabled
|
|
* @param {string} modifier Modifier key to be checked for
|
|
* @param {Event} event The event checked against
|
|
* @returns {boolean} Whether the modifier is enabled and the modifier is pressed
|
|
*/
|
|
checkForModifier(enabled, modifier, event) {
|
|
if (enabled)
|
|
switch (modifier) {
|
|
case "shift": return event.shiftKey;
|
|
case "ctrl": return event.ctrlKey;
|
|
case "alt": return event.altKey;
|
|
}
|
|
return false;
|
|
}
|
|
}
|