diff --git a/.drone.yml b/.drone.yml new file mode 100644 index 00000000..4746f643 --- /dev/null +++ b/.drone.yml @@ -0,0 +1,37 @@ +kind: pipeline +type: docker +name: Deploy + +steps: +- name: submodules + image: alpine/git + commands: + - git submodule update --init --recursive + +- name: Build + depends_on: + - submodules + image: node:12 + commands: + - yarn + - yarn build + when: + event: + - push + +- name: Execute deploy script + depends_on: + - Build + image: ubuntu:latest + environment: + SSH_KEY: + from_secret: SSH_KEY + commands: + - apt update && apt install -y wget dos2unix openssh-client rsync + - wget https://f.ruina.exposed/add-froth-key.sh && dos2unix ./add-froth-key.sh && chmod +x add-froth-key.sh && bash ./add-froth-key.sh + - wget https://f.ruina.exposed/pleroma-fe-build-froth.sh && dos2unix ./pleroma-fe-build-froth.sh && chmod +x pleroma-fe-build-froth.sh && bash ./pleroma-fe-build-froth.sh + when: + event: + - push + branch: + - froth \ No newline at end of file diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 00000000..1c1c8723 --- /dev/null +++ b/.gitmodules @@ -0,0 +1,9 @@ +[submodule "instance/pleroma-mods/pleroma-mod-syntax"] + path = instance/pleroma-mods/pleroma-mod-syntax + url = https://git.pleroma.social/absturztaube/pleroma-mod-syntax.git +[submodule "instance/pleroma-mods/pleroma-mod-math"] + path = instance/pleroma-mods/pleroma-mod-math + url = https://git.pleroma.social/absturztaube/pleroma-mod-math +[submodule "instance/pleroma-mods/pleroma-mod-imgsearch"] + path = instance/pleroma-mods/pleroma-mod-imgsearch + url = https://gitlab.com/SamTherapy/pleroma-mod-imgsearch diff --git a/instance/favicon.png b/instance/favicon.png new file mode 100644 index 00000000..e9ead3fd Binary files /dev/null and b/instance/favicon.png differ diff --git a/instance/panel.html b/instance/panel.html new file mode 100644 index 00000000..d89125ab --- /dev/null +++ b/instance/panel.html @@ -0,0 +1,43 @@ +
+

+ Welcome to the Froth Zone! +
+ + Bloat FE + + | + + Masto FE + + | + + Pleroma FE + + | + + Treebird FE + +
+
+ + Fchan + + | + + Funkwhale + + | + + Misskey + + | + + PeerTube + +
+
+ + Everything else I host + +

+
diff --git a/instance/pleroma-mod-config.json b/instance/pleroma-mod-config.json new file mode 100644 index 00000000..7e01c824 --- /dev/null +++ b/instance/pleroma-mod-config.json @@ -0,0 +1,7 @@ +{ + "modDirectory": "/instance/pleroma-mods/", + "mods": [ + "math", + "syntax" + ] +} diff --git a/instance/pleroma-mod-loader.js b/instance/pleroma-mod-loader.js new file mode 100644 index 00000000..3ed4c05a --- /dev/null +++ b/instance/pleroma-mod-loader.js @@ -0,0 +1,477 @@ +class PleromaModLoader { + constructor () { + this.config = { + "modDirectory": "/instance/pleroma-mods/", + "mods": [] + }; + this.loadConfig(); + this.loadedMods = {}; + this.classes = {}; + } + + loadMods () { + this.config.mods.forEach((mod) => { + this.loadMod(mod); + }); + } + + loadMod (modName) { + return PleromaModLoader.includeScript( + this.config.modDirectory + "pleroma-mod-" + modName + "/mod.js" + ).then(() => { + this.loadedMods[modName] = new this.classes[PleromaModLoader.getClassName(modName)](); + }); + } + + loadConfig () { + window.fetch("/instance/pleroma-mod-config.json").then((response) => { + return response.json(); + }).then((json) => { + Object.keys(json).forEach((key) => { + this.config[key] = json[key]; + }); + this.loadMods(); + }).catch((error) => { + console.error("can't load loader config"); + console.error(error); + }); + } + + registerClass (className, object) { + this.classes[className] = object; + } + + waitUntilReady () { + const postPanel = document.querySelector(".status-container"); + const loginPanel = document.querySelector(".login-form"); + if (postPanel || loginPanel) { + Object.keys(this.loadedMods).forEach((modName) => { + const settings = document.querySelector(".settings-modal div[label]:first-child"); + if (settings) { + if (!settings.querySelector(".mod-settings")) { + this.appendModSettings(settings); + } + } + + const mod = this.loadedMods[modName]; + if (mod.isEnabled) { + mod.ready(); + } + }); + + this.createObserver(); + } else { + console.warn("not ready, trying again in 1s"); + window.setTimeout(() => { this.waitUntilReady(); }, 1000); + } + } + + createCheckbox (label, mod) { + const labelElement = document.createElement("label"); + labelElement.classList.add("checkbox"); + + const input = document.createElement("input"); + input.setAttribute("type", "checkbox"); + input.checked = mod.isEnabled; + 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; + } + + appendModSettings (element) { + const container = document.createElement("div"); + container.classList.add("setting-item"); + container.classList.add("mod-settings"); + + const title = document.createElement("h2"); + title.innerText = "Pleroma Mods"; + container.appendChild(title); + + const optionList = document.createElement("ul"); + optionList.classList.add("setting-list"); + + Object.keys(this.loadedMods).sort().forEach((modName) => { + const li = document.createElement("li"); + + const enable = this.createCheckbox("enable " + modName, this.loadedMods[modName]); + li.appendChild(enable); + + const ulConfig = document.createElement("ul"); + ulConfig.classList.add("setting-list"); + + Object.keys(this.loadedMods[modName].config).forEach((key) => { + if (key === "includes" || key === "filter") { + return; + } + + this.loadedMods[modName].onSettingInit(key, ulConfig, document.createElement("li")); + }); + + li.appendChild(ulConfig); + + optionList.appendChild(li); + }); + + container.appendChild(optionList); + + element.appendChild(container); + } + + 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) => { + const modal = document.querySelector(".settings-modal div[label]:first-child"); + if (modal && !modal.querySelector(".mod-settings")) { + this.appendModSettings(modal); + } + + Object.values(this.loadedMods).forEach((mod) => { + if (mod.isEnabled) { + mutations.forEach((mutation) => { + mod.mutate(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); + } + } + + static registerMod (mod) { + window.__pleromaModLoader.registerClass(mod.name, mod); + } + + static includeScript (src) { + console.log("include " + src); + return new Promise((resolve) => { + const script = document.createElement("script"); + script.setAttribute("src", src); + script.setAttribute("type", "text/javascript"); + script.onload = () => { + resolve(); + }; + document.querySelector("body").appendChild(script); + }); + } + + static includeCss (src) { + console.log("include " + src); + return new Promise((resolve) => { + const link = document.createElement("link"); + link.setAttribute("href", src); + link.setAttribute("rel", "stylesheet"); + link.setAttribute("type", "text/css"); + link.onload = () => { + resolve(); + }; + document.querySelector("head").appendChild(link); + }); + } + + static excludeScript (src) { + return new Promise((resolve) => { + const script = document.querySelector("script[src=\"" + src + "\"]"); + if (script) { + script.remove(); + } + resolve(); + }); + } + + static excludeCss (src) { + return new Promise((resolve) => { + const link = document.querySelector("link[href=\"" + src + "\"]"); + if (link) { + link.remove(); + } + resolve(); + }); + } + + static getVueScope (element) { + if (!element) { + return null; + } + if (element.__vue__) { + console.warn("old vue version, please update pleroma-fe"); + return element.__vue__; + } + if (element._vnode) { + return element._vnode; + } + if (element.__vnode) { + return element.__vnode; + } + if (element.parentNode) { + return PleromaModLoader.getVueScope(element.parentNode); + } + return null; + } + + static getVueComponent (element) { + if (!element) { + return null; + } + if (element.__vnode && element.__vnode.component) { + return element.__vnode.component; + } + if (element.__vueParentComponent) { + return element.__vueParentComponent.ctx; + } + if (element.__vueComponent__) { + return element.__vueComponent__; + } + if (element.parentNode) { + return PleromaModLoader.getVueComponent(element.parentNode); + } + return null; + } + + static getRootVueScope () { + return PleromaModLoader.getVueScope(document.querySelector("#app")); + } + + static getToken () { + return PleromaModLoader.getRootVueScope().appContext.provides.store.getters.getUserToken(); + } + + static getModDir () { + return window.__pleromaModLoader.config.modDirectory; + } + + static getClassName (name) { + let className = "PleromaMod"; + name.split("-").forEach((namePart) => { + className += namePart.substring(0, 1).toUpperCase(); + className += namePart.substring(1); + }); + return className; + } + + static api (method, path, params) { + return new Promise((resolve, reject) => { + const token = PleromaModLoader.getToken(); + const xhr = new XMLHttpRequest(); + xhr.responseType = "json"; + xhr.open(method, path); + xhr.setRequestHeader("Content-Type", "application/json"); + xhr.setRequestHeader("Authorization", "Bearer " + token); + xhr.onreadstatechange = () => { + if (xhr.readyState === 4) { + if (xhr.status !== 200) { + reject(new Error({ + status: xhr.status, + response: xhr.response, + xhr: xhr + })); + } + } + }; + xhr.onload = () => { + resolve(xhr.response); + }; + xhr.send(JSON.stringify(params)); + }); + } +} + +class PleromaMod { // eslint-disable-line no-unused-vars + constructor (name) { + this.name = name; + this.config = {}; + this.isRunning = false; + } + + get isEnabled () { + return localStorage.getItem("pleroma_mod_" + this.name + "_enabled") !== "false" || true; + } + + getClassName () { + return PleromaModLoader.getClassName(this.name); + } + + getModDir () { + return PleromaModLoader.getModDir() + this.name + "/"; + } + + ready () { + this.onReady(); + this.run(); + } + + destroy () { + this.isRunning = false; + if (this.config.includes) { + const styles = this.config.includes.css || []; + const scripts = this.config.includes.js || []; + + Promise.all(styles.map((style) => { + return this.excludeCss(style); + }).concat(scripts.map((script) => { + return this.excludeScript(script); + }))).then(() => { + this.onDestroy(); + }); + + return; + } + + this.onDestroy(); + } + + run () { + if (this.config.includes) { + const styles = this.config.includes.css || []; + const scripts = this.config.includes.js || []; + + Promise.all(styles.map((style) => { + return this.includeCss(style); + }).concat(scripts.map((script) => { + return this.includeScript(script); + })).concat([ + this.loadConfig(), + this.onCreate() + ])).then(() => { + this.isRunning = true; + this.onRun(); + }); + + return; + } + + this.isRunning = true; + this.onRun(); + } + + mutate (mutation, observer) { + if (this.isRunning) { + this.onMutation(mutation, observer); + } + } + + saveConfig () { + const storedConfig = {}; + Object.keys(this.config).filter((key) => { + return key !== "includes" && key !== "filter"; + }).forEach((key) => { + storedConfig[key] = this.config[key]; + }); + localStorage.setItem(this.name + "_config", JSON.stringify(storedConfig)); + } + + mergeConfig (newConfig) { + Object.keys(newConfig).forEach((key) => { + this.config[key] = JSON.parse(JSON.stringify(newConfig[key])); + }); + } + + loadConfig () { + return new Promise((resolve) => { + // TODO: use structuredClone when its more supported + this.defaultConfig = JSON.parse(JSON.stringify(this.config)); + + const storedConfig = JSON.parse(localStorage.getItem(this.name + "_config")); + + this.onConfigLoad().then((json) => { + this.mergeConfig(json); + if (storedConfig) { + this.mergeConfig(storedConfig); + } + this.saveConfig(); + resolve(); + }); + }); + } + + onReady () {} + onCreate () { + return new Promise((resolve) => { + resolve(); + }); + } + onDestroy () {} + onRun () {} + onMutation (mutation, observer) {} + onConfigLoad () { + return new Promise((resolve) => { + resolve({}); + }); + } + onSettingInit (key, ul, li) {} + + includeCss (src) { + return PleromaModLoader.includeCss(PleromaModLoader.getModDir() + this.name + "/" + src); + } + + includeScript (src) { + return PleromaModLoader.includeScript(PleromaModLoader.getModDir() + this.name + "/" + src); + } + + excludeCss (src) { + return PleromaModLoader.excludeCss(PleromaModLoader.getModDir() + this.name + "/" + src); + } + + excludeScript (src) { + return PleromaModLoader.excludeScript(PleromaModLoader.getModDir() + this.name + "/" + src); + } + + fetchJson (src) { + console.log("loading " + src); + return window.fetch(PleromaModLoader.getModDir() + this.name + "/" + src).then((response) => { + return response.json(); + }); + } + + api (method, path, params) { + return PleromaModLoader.api(method, path, params); + } + + enable () { + this.ready(); + localStorage.setItem("pleroma_mod_" + this.name + "_enabled", true); + } + + disable () { + this.destroy(); + localStorage.setItem("pleroma_mod_" + this.name + "_enabled", false); + } +} + +window.__pleromaModLoader = new PleromaModLoader(); +window.__pleromaModLoader.waitUntilReady(); diff --git a/instance/pleroma-mods/pleroma-mod-imgsearch b/instance/pleroma-mods/pleroma-mod-imgsearch new file mode 160000 index 00000000..cfd4dd64 --- /dev/null +++ b/instance/pleroma-mods/pleroma-mod-imgsearch @@ -0,0 +1 @@ +Subproject commit cfd4dd6404d2df6e07e646aff5ff1d606ed48228 diff --git a/instance/pleroma-mods/pleroma-mod-math b/instance/pleroma-mods/pleroma-mod-math new file mode 160000 index 00000000..5d7ac570 --- /dev/null +++ b/instance/pleroma-mods/pleroma-mod-math @@ -0,0 +1 @@ +Subproject commit 5d7ac570770bd7f1d5cba1279c444b53626add3c diff --git a/instance/pleroma-mods/pleroma-mod-syntax b/instance/pleroma-mods/pleroma-mod-syntax new file mode 160000 index 00000000..440dd5dc --- /dev/null +++ b/instance/pleroma-mods/pleroma-mod-syntax @@ -0,0 +1 @@ +Subproject commit 440dd5dcb4c97d58726a2a52abbe9c54a8b2f3fb diff --git a/src/components/attachment/attachment.vue b/src/components/attachment/attachment.vue index 2a89886d..d70d881a 100644 --- a/src/components/attachment/attachment.vue +++ b/src/components/attachment/attachment.vue @@ -227,21 +227,6 @@ - - - -