function PleromaModLoader () { this.config = { "modDirectory": "/instance/pleroma-mods/", "mods": [] }; this.loadConfig(); this.loadedMods = {}; this.classes = {}; } [ function loadMods () { for (const mod of this.config.mods) { const modObject = new PleromaMod(mod); modObject.enabled = true; if (localStorage.getItem("pleroma_mod_" + mod + "_enabled") === "false") { modObject.enabled = false; } localStorage.setItem("pleroma_mod_" + mod + "_enabled", modObject.enabled); modObject.include(); this.loadedMods[mod] = modObject; } }, function loadConfig () { window.fetch("/instance/pleroma-mod-config.json").then((response) => { if (response.ok) { response.json().then((json) => { for (const key in json) { this.config[key] = json[key]; } this.loadMods(); }).catch((error) => { console.error("can't parse loader config"); console.error(error); }); } }).catch((error) => { console.warn("can't load mod loader config"); console.warn(error); }); }, function registerClass (className, object) { this.classes[className] = object; }, function waitUntilReady () { const postPanel = document.getElementsByClassName("post-status-form"); const loginPanel = document.getElementsByClassName("login-form"); if (postPanel.length > 0 || loginPanel.length > 0) { for (var modName in this.loadedMods) { const settings = document.querySelector(".settings div[label]:first-child"); if (settings) { if (!settings.querySelector(".mod-settings")) { this.appendModSettings(settings); } } const mod = this.loadedMods[modName]; if (mod.enabled && mod.instance) { if (mod.instance.onReady) { mod.instance.onReady(); } } } this.createObserver(); } else { window.setTimeout(() => { this.waitUntilReady(); }, 1000); } }, function createCheckbox (label, mod) { const labelElement = document.createElement("label"); labelElement.classList.add("checkbox"); const input = document.createElement("input"); input.setAttribute("type", "checkbox"); input.checked = mod.enabled; input.addEventListener("change", (event) => { if (event.target.checked) { mod.enable(); } else { mod.disable(); } }); labelElement.appendChild(input); const fakeCheckbox = document.createElement("i"); fakeCheckbox.classList.add("checkbox-indicator"); labelElement.appendChild(fakeCheckbox); const text = document.createElement("span"); text.classList.add("label"); text.innerText = label; labelElement.appendChild(text); return labelElement; }, function appendModSettings (element) { const container = document.createElement("div"); container.classList.add("setting-item"); container.classList.add("mod-settings"); const title = document.createElement("h2"); title.innerText = "Mods"; container.appendChild(title); const optionList = document.createElement("ul"); optionList.classList.add("setting-list"); const modNames = Object.keys(this.loadedMods).sort(); for (const mod of modNames) { const li = document.createElement("li"); const enable = this.createCheckbox("enable " + mod, this.loadedMods[mod]); li.appendChild(enable); optionList.appendChild(li); } container.appendChild(optionList); element.appendChild(container); }, function createObserver () { this.containers = { main: document.querySelector(".main"), notifications: document.querySelector(".notifications"), userPanel: document.querySelector(".user-panel"), settingsModal: document.querySelector(".settings-modal") }; const observerConfig = { subtree: true, childList: true }; this.observer = new MutationObserver((mutations, observer) => { if ( mutations.length > 0 && mutations[0].addedNodes.length > 0 && mutations[0].addedNodes[0].classList && mutations[0].addedNodes[0].classList.contains("settings_tab-switcher") ) { this.appendModSettings(mutations[0].addedNodes[0].querySelector("div[label]:first-child")); } for (var modName in this.loadedMods) { const mod = this.loadedMods[modName]; if (mod.instance && mod.enabled) { if (mod.instance.onMutation) { for (const mutation of mutations) { let filter = null; if (mod.instance.config.filter) { filter = new RegExp(mod.instance.config.filter.join("|")); } if (!filter || filter.test(mutation.target.className)) { mod.instance.onMutation(mutation, observer); } } } } } }); this.observer.observe(this.containers.main, observerConfig); if (this.containers.notifications) { this.observer.observe(this.containers.notifications, observerConfig); } if (this.containers.userPanel) { this.observer.observe(this.containers.userPanel, observerConfig); } if (this.containers.settingsModal) { this.observer.observe(this.containers.settingsModal, observerConfig); } } ].forEach((fn) => { PleromaModLoader.prototype[fn.name] = fn; }); [ function registerMod (mod) { window.__pleromaModLoader.registerClass(mod.name, mod); }, function includeModScript (src) { return PleromaModLoader.includeScript(window.__pleromaModLoader.config.modDirectory + src); }, function includeModCss (src) { return PleromaModLoader.includeCss(window.__pleromaModLoader.config.modDirectory + src); }, function excludeModScript (src) { return PleromaModLoader.excludeScript(window.__pleromaModLoader.config.modDirectory + src); }, function excludeModCss (src) { return PleromaModLoader.excludeCss(window.__pleromaModLoader.config.modDirectory + src); }, function includeScript (src) { return new Promise((resolve) => { const body = document.getElementsByTagName("body")[0]; const script = document.createElement("script"); script.setAttribute("src", src); script.setAttribute("type", "text/javascript"); script.onload = () => { resolve(); }; body.appendChild(script); }); }, function getVueScope (element) { if (!element) { return null; } if (element.__vue__) { return element.__vue__; } if (element.parentNode) { return PleromaModLoader.getVueScope(element.parentNode); } return null; }, function getRootVueScope () { return document.querySelector("#app").__vue__; }, function includeCss (src) { return new Promise((resolve) => { const head = document.getElementsByTagName("head")[0]; const link = document.createElement("link"); link.setAttribute("href", src); link.setAttribute("rel", "stylesheet"); link.setAttribute("type", "text/css"); link.onload = () => { resolve(); }; head.appendChild(link); }); }, function excludeScript (src) { return new Promise((resolve) => { const script = document.querySelector('script[src="' + src + '"]'); if(script) { script.remove(); } resolve(); }); }, function excludeCss (src) { return new Promise((resolve) => { const link = document.querySelector('link[href="' + src + '"]'); if(link) { link.remove(); } resolve(); }); }, function getToken () { return PleromaModLoader.getRootVueScope().$store._vm.getUserToken(); }, function getModDir () { return window.__pleromaModLoader.config.modDirectory; } ].forEach((fn) => { PleromaModLoader[fn.name] = fn; }); function PleromaMod (name) { this.name = name; this.instance = null; this.enabled = localStorage.getItem("pleroma_mod_" + this.name + "_enabled") !== "false"; } [ function getClassName () { let className = "PleromaMod"; const nameParts = this.name.split("-"); for (const namePart of nameParts) { className += namePart.substring(0, 1).toUpperCase(); className += namePart.substring(1); } return className; }, function enable () { this.enabled = true; this.modLoaded(); if (this.instance.onReady) { this.instance.onReady(); } localStorage.setItem("pleroma_mod_" + this.name + "_enabled", this.enabled); }, function disable () { this.enabled = false; if (this.instance.onDestroy) { this.instance.onDestroy(); } console.log(this.name + " unloaded"); this.instance = null; localStorage.setItem("pleroma_mod_" + this.name + "_enabled", this.enabled); }, function include () { console.log("loading " + this.name); PleromaModLoader.includeScript( PleromaModLoader.getModDir() + "pleroma-mod-" + this.name + "/mod.js" ).then(() => { if (this.enabled) { this.modLoaded(); } }); }, function modLoaded () { console.log(this.name + " loaded"); this.instance = new window.__pleromaModLoader.classes[this.getClassName()](); this.run(); }, function run () { if (this.instance) { this.instance.run(); } } ].forEach((fn) => { PleromaMod.prototype[fn.name] = fn; }); window.__pleromaModLoader = new PleromaModLoader(); window.__pleromaModLoader.waitUntilReady();