diff --git a/package.json b/package.json index 5276c79..b93782b 100644 --- a/package.json +++ b/package.json @@ -11,6 +11,7 @@ "scripts": { "build": "tsc", "build-dev": "tsc && for file in dist/*.js; do sed -i \"s/%RELEASE_TYPE%/ - Development/g\" \"$file\";done", + "build-dev-win": "tsc && sed -i \"s/%RELEASE_TYPE%/ - Development/g\" \"dist/*\" && del \"dist\\*.\"", "prettify": "prettier --tab-width 4 --write \"src/**/*.ts\"" }, "devDependencies": { diff --git a/src/EGO Forum Enhancement.ts b/src/EGO Forum Enhancement.ts index f86ff8c..6f9864f 100644 --- a/src/EGO Forum Enhancement.ts +++ b/src/EGO Forum Enhancement.ts @@ -3,7 +3,7 @@ // @namespace https://github.com/blankdvth/eGOScripts/blob/master/src/EGO%20Forum%20Enhancement.ts // @downloadURL %DOWNLOAD_URL% // @updateURL %DOWNLOAD_URL% -// @version 4.3.1 +// @version 4.4.0 // @description Add various enhancements & QOL additions to the EdgeGamers Forums that are beneficial for Leadership members. // @author blank_dvth, Skle, MSWS // @match https://www.edgegamers.com/* @@ -35,6 +35,11 @@ interface OnHold_Map { explain: string; } +interface CannedResponse { + name: string; + response: string; +} + declare var SteamIDConverter: any; const completedMap: Completed_Map[] = []; @@ -42,6 +47,7 @@ const signatureBlockList: string[] = []; const navbarURLs: NavbarURL_Map[] = []; const onHoldTemplates: OnHold_Map[] = []; const autoMentionForums: string[] = []; +const cannedResponses: { [category: string]: CannedResponse[] } = {}; const contestReportForums: string[] = ["1233", "1234", "1235", "1236"]; /** @@ -182,8 +188,7 @@ function setupForumsConfig() { }, "move-to-completed": { type: "hidden", - default: - "Contest a Ban ?$;1236\nReport a Player ?$;1235\nContact Leadership ?$;853", + default: "1234;1236\n1233;1235\n852;853", }, "signature-block-unchecked": { label: "Signature Block List", @@ -234,8 +239,8 @@ function setupForumsConfig() { "auto-mention-unchecked": { label: "Auto Mention (Subforum IDs)", section: [ - "Autofill", - "Autofill various content into the post editor.", + "Automention", + "Automatically mention the OP in the editor in certain forums", ], type: "textarea", save: false, @@ -251,10 +256,40 @@ function setupForumsConfig() { default: true, }, "auto-mention-focus": { - label: "Autofocus after mentioning (only on load mode)", + label: "Focus after mentioning (only on load mode)", type: "checkbox", default: false, }, + "canned-responses-unchecked": { + label: "Canned Responses", + section: [ + "Canned Responses", + "See this guide on how to format your canned responses.", + ], + type: "textarea", + save: false, + default: "", + }, + "canned-responses": { + type: "hidden", + default: "", + }, + "canned-response-min-width": { + label: "Minimum width of dropdown (in pixels)", + type: "int", + min: 0, + default: 125, + }, + "canned-response-focus": { + label: "Focus after inserting canned response", + type: "checkbox", + default: true, + }, + "canned-response-trigger-automention": { + label: "Attempt to trigger automention before inserting canned response", + type: "checkbox", + default: true, + }, }, events: { init: function () { @@ -275,6 +310,10 @@ function setupForumsConfig() { "auto-mention-unchecked", GM_config.get("auto-mention") ); + GM_config.set( + "canned-responses-unchecked", + GM_config.get("canned-responses") + ); }, open: function (doc) { GM_config.fields[ @@ -356,6 +395,25 @@ function setupForumsConfig() { ) GM_config.set("auto-mention", autoMention); }); + GM_config.fields[ + "canned-responses-unchecked" + ].node?.addEventListener("change", function () { + const cannedResponses = GM_config.get( + "canned-responses-unchecked", + true + ) as string; + // Check if entire config matches the regex by matching all and rejoining the matches, then comparing to the original + if ( + [ + ...cannedResponses.matchAll( + /(?:===\n|^)- (?.+)\n- (?.+)\n(?(?:.|\n)+?)\n===/gm + ), + ] + .map((i) => i[0]) + .join("\n") === cannedResponses + ) + GM_config.set("canned-responses", cannedResponses); + }); }, save: function (forgotten) { if ( @@ -390,6 +448,13 @@ function setupForumsConfig() { alert( "Invalid auto mention list. Ensure each ID is on it's own line and all IDs are numerical." ); + if ( + forgotten["canned-responses-unchecked"] !== + GM_config.get("canned-responses") + ) + alert( + "Invalid canned responses list. Ensure each response is in the proper format (see the wiki for more information)." + ); }, }, css: "textarea {width: 100%; height: 160px; resize: vertical;}", @@ -507,6 +572,25 @@ function loadAutoMentionList() { }); } +/** + * Loads the canned responses from config + */ +function loadCannedResponses() { + const cannedResponsesRaw = GM_config.get("canned-responses") as string; + [ + ...cannedResponsesRaw.matchAll( + /(?:===\n|^)- (?.+)\n- (?.+)\n(?(?:.|\n)+?)\n===/gm + ), + ].forEach((match) => { + const category = match.groups!.category; + if (!cannedResponses[category]) cannedResponses[category] = []; + cannedResponses[category].push({ + name: match.groups!.name, + response: match.groups!.response, + }); + }); +} + /** * Adds a MAUL profile button to the given div * @param {HTMLDivElement} div Div to add to @@ -754,6 +838,36 @@ function editPostBox(text: string, append: boolean = false) { } } +/** + * Get the ID of the current forum + * @returns {string} Forum ID + */ +function getForumId() { + return document + .getElementById("XF")! + .dataset.containerKey?.replace("node-", ""); +} + +/** + * Get the username of the current user + * @returns {string} Username of current user + */ +function getUsername() { + return ( + document.querySelector( + "a.p-navgroup-link--user > span.p-navgroup-linkText" + ) as HTMLSpanElement | null + )?.innerText; +} + +/** + * Get the username of the OP of the current thread + * @returns {string} Username of the OP + */ +function getOP() { + return document.querySelector("a.username") as HTMLAnchorElement | null; +} + /** * Generates large, transparent text (basically a watermark) * @param {string} top CSS Top Style @@ -775,13 +889,12 @@ function generateRedText(top: string, str: string = "Confidential") { } /** - * Get the ID of the current forum - * @returns {string} Forum ID + * Fills in placeholders in a canned response string with the proper data then returns it */ -function getForumId() { - return document - .getElementById("XF")! - .dataset.containerKey?.replace("node-", ""); +function generateResponseText(response: string) { + return response + .replaceAll("{{{username}}}", getUsername() ?? "") + .replaceAll("{{{op username}}}", getOP()?.innerText ?? ""); } /** @@ -948,26 +1061,6 @@ function handleGenericThread() { // Ban Contest or Report handleBanReportContest(); - if (autoMentionForums.includes(forumId)) { - const observer = new MutationObserver((mutations) => { - mutations.forEach((mutation) => { - if (!mutation.addedNodes) return; - - for (let i = 0; i < mutation.addedNodes.length; i++) { - const node = mutation.addedNodes[i]; - if (node.nodeName === "DIV") - handleAutoMention(observer); - } - }); - }); - observer.observe(document.body, { - childList: true, - subtree: true, - attributes: false, - characterData: false, - }); - } - const button_group = document.querySelector("div.buttonGroup"); for (var i = 0; i < completedMap.length; i++) { if (forumId == completedMap[i].originId) { @@ -993,6 +1086,29 @@ function handleGenericThread() { // LE Forums handleLeadership(); + const observer = new MutationObserver((mutations) => { + mutations.every((mutation) => { + // Using every so that we can return false to stop observing + if (!mutation.addedNodes) return true; + + for (let i = 0; i < mutation.addedNodes.length; i++) { + const node = mutation.addedNodes[i]; + if (node.nodeName === "DIV") { + handlePostBox(observer); + return false; + } + } + + return true; + }); + }); + observer.observe(document.body, { + childList: true, + subtree: true, + attributes: false, + characterData: false, + }); + blockSignatures(); } @@ -1140,7 +1256,7 @@ function handleLeadership() { * @param focus Whether to focus the post box after mentioning the user */ function autoMention(focus: boolean) { - const user = document.querySelector("a.username") as HTMLAnchorElement; + const user = getOP(); if (!user) return; const username = user.innerText; const userId = user.dataset.userId; @@ -1150,23 +1266,125 @@ function autoMention(focus: boolean) { } /** - * Handles adding auto mention functionality to the post box - * @param {MutationObserver} observer + * Handles operations that should be performed when the post box is loaded + * @param observer */ -function handleAutoMention(observer: MutationObserver) { +function handlePostBox(observer: MutationObserver) { const postBox = document.querySelector("div.fr-box") as HTMLDivElement; if (!postBox) return; + const forumId = getForumId(); observer.disconnect(); + + if (forumId && autoMentionForums.includes(forumId)) handleAutoMention(); + handleCannedResponses(); +} + +/** + * Handles adding auto mention functionality to the post box + */ +function handleAutoMention() { + const postBox = document.querySelector("div.fr-box") as HTMLDivElement; if (GM_config.get("auto-mention-onclick")) { - postBox.addEventListener("click", function () { + function autoMentionListener() { autoMention(true); - }); + postBox.removeEventListener("click", autoMentionListener); + } + postBox.addEventListener("click", autoMentionListener); } else { postBox.click(); autoMention(GM_config.get("auto-mention-focus") as boolean); } } +/** + * Handles adding canned responses to the post box + */ +function handleCannedResponses() { + const bar = document.querySelector( + "div.formButtonGroup-extra" + ) as HTMLDivElement; + if (!bar) { + console.warn("Could not find post box button bar"); + return; + } + Object.entries(cannedResponses).forEach((cannedResponse) => { + const [category, responses] = cannedResponse; + const dropdown = document.createElement("span"); + dropdown.classList.add("p-navEl-splitTrigger"); // Adds various styling for dropdowns, technically for the navbar but it works here + dropdown.style.float = "none"; // Class makes it float left, so we need to override it + dropdown.style.textAlign = "center"; + dropdown.style.lineHeight = bar.clientHeight + "px"; + dropdown.style.paddingLeft = "8px"; + dropdown.dataset.category = category; + dropdown.innerText = category; + + var dropdownMenu = document.createElement("div"); + dropdownMenu.classList.add( + "menu", + "menu--structural", + "menu--potentialFixed", + "menu--left" + ); + dropdownMenu.style.zIndex = "800"; + dropdownMenu.style.display = "none"; + dropdownMenu.style.position = "fixed"; + dropdownMenu.style.minWidth = + GM_config.get("canned-response-min-width") + "px"; + dropdownMenu.style.overflow = "auto"; + dropdownMenu.hidden = true; + + dropdown.append(dropdownMenu); + dropdown.addEventListener("mouseover", function () { + var rect = dropdown.getBoundingClientRect(); + dropdownMenu.hidden = false; + dropdownMenu.style.display = "block"; + dropdownMenu.style.top = rect.bottom + "px"; + dropdownMenu.style.left = rect.left + "px"; + dropdownMenu.style.maxHeight = + window.innerHeight - rect.top - 25 + "px"; + dropdownMenu.style.maxWidth = + window.innerWidth - rect.left - 25 + "px"; + dropdownContent.style.maxHeight = dropdownMenu.style.maxHeight; + dropdownMenu.classList.add("is-active"); + }); + dropdown.addEventListener("mouseout", function () { + dropdownMenu.hidden = true; + dropdownMenu.style.display = "none"; + dropdownMenu.classList.remove("is-active"); + }); + + const dropdownContent = document.createElement("div"); + dropdownContent.classList.add("menu-content"); + dropdownContent.style.overflow = "none"; + dropdownMenu.append(dropdownContent); + bar.append(dropdown); + + responses.forEach((response) => { + const btn = document.createElement("a"); + btn.classList.add("menu-linkRow", "u-indentDepth0"); + btn.innerText = response.name; + btn.style.cursor = "pointer"; + btn.style.paddingLeft = "4px"; + btn.style.paddingRight = "0px"; + btn.style.paddingTop = "1px"; + btn.style.paddingBottom = "1px"; + btn.addEventListener("click", function () { + const postBox = getPostBoxEl(); + if (GM_config.get("canned-response-trigger-automention")) { + const forumId = getForumId(); + if (forumId && autoMentionForums.includes(forumId)) + autoMention(false); + } + editPostBox(generateResponseText(response.response), true); + dropdown.dispatchEvent(new MouseEvent("mouseout")); + postBox.dispatchEvent(new Event("autosize:update")); + if (GM_config.get("canned-response-focus")) postBox.focus(); + }); + dropdownContent.append(btn); + }); + }); +} + /** * Changes the target of the application links to open in a new tab * @returns void @@ -1419,6 +1637,7 @@ function blockSignatures() { loadNavbarURLs(); loadOnHoldTemplates(); loadAutoMentionList(); + loadCannedResponses(); // Determine what page we're on const url = window.location.href;