diff --git a/assets/js/script.js b/assets/js/script.js
index 2c31b4e..52adecf 100644
--- a/assets/js/script.js
+++ b/assets/js/script.js
@@ -6,1530 +6,1684 @@
window.options ??= {};
window.inIframe ??= top !== self;
-mainHost = "glitchii.github.io";
let params = new URLSearchParams(location.search),
- sendMessage = params.get('message') || null,
- hasParam = param => params.get(param) !== null,
- dataSpecified = options.data || params.get('data'),
- username = params.get('username') || options.username,
- avatar = params.get('avatar') || options.avatar,
- guiTabs = params.get('guitabs') || options.guiTabs,
- useJsonEditor = params.get('editor') === 'json' || options.useJsonEditor,
- verified = hasParam('verified') || options.verified,
- reverseColumns = hasParam('reverse') || options.reverseColumns,
- noUser = localStorage.getItem('noUser') || hasParam('nouser') || options.noUser,
- onlyEmbed = hasParam('embed') || options.onlyEmbed,
- allowPlaceholders = hasParam('placeholders') || options.allowPlaceholders,
- autoUpdateURL = localStorage.getItem('autoUpdateURL') || options.autoUpdateURL,
- noMultiEmbedsOption = localStorage.getItem('noMultiEmbedsOption') || hasParam('nomultiembedsoption') || options.noMultiEmbedsOption,
- single = noMultiEmbedsOption ? options.single ?? true : (localStorage.getItem('single') || hasParam('single') || options.single) ?? false,
- multiEmbeds = !single,
- autoParams = localStorage.getItem('autoParams') || hasParam('autoparams') || options.autoParams,
- hideEditor = localStorage.getItem('hideeditor') || hasParam('hideeditor') || options.hideEditor,
- hidePreview = localStorage.getItem('hidepreview') || hasParam('hidepreview') || options.hidePreview,
- hideMenu = localStorage.getItem('hideMenu') || hasParam('hidemenu') || options.hideMenu,
- sourceOption = localStorage.getItem('sourceOption') || hasParam('sourceoption') || options.sourceOption,
- // sourceInMenu = localStorage.getItem('sourceInMenu') || hasParam('sourceInMenu') || options.sourceInMenu || top.location.host === mainHost,
- validationError, activeFields, lastActiveGuiEmbedIndex = -1, lastGuiJson, colNum = 1, num = 0;
+ sendMessage = params.get("message") || null,
+ hasParam = param => params.get(param) !== null,
+ dataSpecified = options.data || params.get("data"),
+ username = params.get("username") || options.username,
+ avatar = params.get("avatar") || options.avatar,
+ guiTabs = params.get("guitabs") || options.guiTabs,
+ useJsonEditor = params.get("editor") === "json" || options.useJsonEditor,
+ verified = hasParam("verified") || options.verified,
+ reverseColumns = hasParam("reverse") || options.reverseColumns,
+ noUser = localStorage.getItem("noUser") || hasParam("nouser") || options.noUser,
+ onlyEmbed = hasParam("embed") || options.onlyEmbed,
+ allowPlaceholders = hasParam("placeholders") || options.allowPlaceholders,
+ autoUpdateURL = localStorage.getItem("autoUpdateURL") || options.autoUpdateURL,
+ noMultiEmbedsOption = localStorage.getItem("noMultiEmbedsOption") || hasParam("nomultiembedsoption") || options.noMultiEmbedsOption,
+ single = noMultiEmbedsOption ? options.single ?? true : (localStorage.getItem("single") || hasParam("single") || options.single) ?? false,
+ multiEmbeds = !single,
+ autoParams = localStorage.getItem("autoParams") || hasParam("autoparams") || options.autoParams,
+ hideEditor = localStorage.getItem("hideeditor") || hasParam("hideeditor") || options.hideEditor,
+ hidePreview = localStorage.getItem("hidepreview") || hasParam("hidepreview") || options.hidePreview,
+ hideMenu = localStorage.getItem("hideMenu") || hasParam("hidemenu") || options.hideMenu,
+ sourceOption = localStorage.getItem("sourceOption") || hasParam("sourceoption") || options.sourceOption,
+ validationError, activeFields, lastActiveGuiEmbedIndex = -1,
+ lastGuiJson, colNum = 1,
+ num = 0;
const guiEmbedIndex = guiEl => {
- const guiEmbed = guiEl?.closest('.guiEmbed');
- const gui = guiEmbed?.closest('.gui')
+ const guiEmbed = guiEl?.closest(".guiEmbed");
+ const gui = guiEmbed?.closest(".gui")
- return !gui ? -1 : Array.from(gui.querySelectorAll('.guiEmbed')).indexOf(guiEmbed)
+ return !gui ? -1 : Array.from(gui.querySelectorAll(".guiEmbed")).indexOf(guiEmbed)
}
const toggleStored = item => {
- const found = localStorage.getItem(item);
- if (!found)
- return localStorage.setItem(item, true);
+ const found = localStorage.getItem(item);
+ if (!found)
+ return localStorage.setItem(item, true);
- localStorage.removeItem(item);
- return found;
+ localStorage.removeItem(item);
+ return found;
};
const createElement = object => {
- let element;
- for (const tag in object) {
- element = document.createElement(tag);
+ let element;
+ for (const tag in object) {
+ element = document.createElement(tag);
- for (const attr in object[tag])
- if (attr !== 'children') element[attr] = object[tag][attr];
- else for (const child of object[tag][attr])
- element.appendChild(createElement(child));
+ for (const attr in object[tag])
+ if (attr !== "children") element[attr] = object[tag][attr];
+ else
+ for (const child of object[tag][attr])
+ element.appendChild(createElement(child));
- }
+ }
- return element;
+ return element;
}
const encodeJson = (jsonCode, withURL = false, redirect = false) => {
- let data = btoa(encodeURIComponent((JSON.stringify(typeof jsonCode === 'object' ? jsonCode : json))));
- let url = new URL(location.href);
+ let data = btoa(encodeURIComponent((JSON.stringify(typeof jsonCode === "object" ? jsonCode : json))));
+ let url = new URL(location.href);
- if (withURL) {
- url.searchParams.set('data', data);
- if (redirect)
- return top.location.href = url;
+ if (withURL) {
+ url.searchParams.set("data", data);
+ if (redirect)
+ return top.location.href = url;
- data = url.href
- // Replace %3D ('=' url encoded) with '='
- .replace(/data=\w+(?:%3D)+/g, 'data=' + data);
- }
+ data = url.href
+ // Replace %3D ("=" url encoded) with "="
+ .replace(/data=\w+(?:%3D)+/g, "data=" + data);
+ }
- return data;
+ return data;
};
const decodeJson = data => {
- const jsonData = decodeURIComponent(atob(data || dataSpecified));
- return typeof jsonData === 'string' ? JSON.parse(jsonData) : jsonData;
+ const jsonData = decodeURIComponent(atob(data || dataSpecified));
+ return typeof jsonData === "string" ? JSON.parse(jsonData) : jsonData;
};
-// IMPORTANT: jsonToBase64 and base64ToJson are subject to removal in the future.
-// Use encodeJson and decodeJson instead (they are aliases)
-let jsonToBase64 = encodeJson, base64ToJson = decodeJson;
-
-
const toRGB = (hex, reversed, integer) => {
- if (reversed) return '#' + hex.match(/\d+/g).map(x => parseInt(x).toString(16).padStart(2, '0')).join('');
- if (integer) return parseInt(hex.match(/\d+/g).map(x => parseInt(x).toString(16).padStart(2, '0')).join(''), 16);
- if (hex.includes(',')) return hex.match(/\d+/g);
- hex = hex.replace('#', '').match(/.{1,2}/g)
- return [parseInt(hex[0], 16), parseInt(hex[1], 16), parseInt(hex[2], 16), 1];
+ if (reversed) return "#" + hex.match(/\d+/g).map(x => parseInt(x).toString(16).padStart(2, "0")).join("");
+ if (integer) return parseInt(hex.match(/\d+/g).map(x => parseInt(x).toString(16).padStart(2, "0")).join(""), 16);
+ if (hex.includes(",")) return hex.match(/\d+/g);
+ hex = hex.replace("#", "").match(/.{1,2}/g)
+ return [parseInt(hex[0], 16), parseInt(hex[1], 16), parseInt(hex[2], 16), 1];
};
const reverse = (reversed, callback) => {
- const side = document.querySelector(reversed ? '.side2' : '.side1');
- if (side.nextElementSibling) side.parentElement.insertBefore(side.nextElementSibling, side);
- else side.parentElement.insertBefore(side, side.parentElement.firstElementChild);
+ const side = document.querySelector(reversed ? ".side2" : ".side1");
+ if (side.nextElementSibling) side.parentElement.insertBefore(side.nextElementSibling, side);
+ else side.parentElement.insertBefore(side, side.parentElement.firstElementChild);
- const isReversed = document.body.classList.toggle('reversed');
- if (autoParams) isReversed ? urlOptions({ set: ['reverse', ''] }) : urlOptions({ remove: 'reverse' });
+ const isReversed = document.body.classList.toggle("reversed");
+ if (autoParams) isReversed ? urlOptions({
+ set: ["reverse", ""]
+ }) : urlOptions({
+ remove: "reverse"
+ });
};
-const urlOptions = ({ remove, set }) => {
- const url = new URL(location.href);
- if (remove) url.searchParams.delete(remove);
- if (set) url.searchParams.set(set[0], set[1]);
-
- try {
- history.replaceState(null, null, url.href.replace(/(? x === '=' ? '' : '&'));
- } catch (e) {
- // 'SecurityError' when trying to change the url of a different origin
- // e.g. when trying to change the url of the parent window from an iframe
- console.info(e);
- }
+const urlOptions = ({
+ remove,
+ set
+}) => {
+ const url = new URL(location.href);
+ if (remove) url.searchParams.delete(remove);
+ if (set) url.searchParams.set(set[0], set[1]);
+
+ try {
+ history.replaceState(null, null, url.href.replace(/(? x === "=" ? "" : "&"));
+ } catch (e) {
+ // "SecurityError" when trying to change the url of a different origin
+ // e.g. when trying to change the url of the parent window from an iframe
+ console.info(e);
+ }
};
const animateGuiEmbedNameAt = (i, text) => {
- const guiEmbedName = document.querySelectorAll('.gui .guiEmbedName')?.[i];
- // Shake animation
- guiEmbedName?.animate(
- [{ transform: 'translate(0, 0)' },
- { transform: 'translate(10px, 0)' },
- { transform: 'translate(0, 0)' }],
- { duration: 100, iterations: 3 });
+ const guiEmbedName = document.querySelectorAll(".gui .guiEmbedName")?.[i];
+ // Shake animation
+ guiEmbedName?.animate(
+ [{
+ transform: "translate(0, 0)"
+ },
+ {
+ transform: "translate(10px, 0)"
+ },
+ {
+ transform: "translate(0, 0)"
+ }
+ ], {
+ duration: 100,
+ iterations: 3
+ });
- text && (guiEmbedName?.style.setProperty('--text', `"${text}"`));
+ text && (guiEmbedName?.style.setProperty("--text", `"${text}"`));
- guiEmbedName?.scrollIntoView({ behavior: "smooth", block: "center" });
- guiEmbedName?.classList.remove('empty');
- setTimeout(() => guiEmbedName?.classList.add('empty'), 10);
+ guiEmbedName?.scrollIntoView({
+ behavior: "smooth",
+ block: "center"
+ });
+ guiEmbedName?.classList.remove("empty");
+ setTimeout(() => guiEmbedName?.classList.add("empty"), 10);
}
const indexOfEmptyGuiEmbed = text => {
- for (const [i, element] of document.querySelectorAll('.msgEmbed>.container .embed').entries())
- if (element.classList.contains('emptyEmbed')) {
- text !== false && animateGuiEmbedNameAt(i, text);
- return i;
- }
+ for (const [i, element] of document.querySelectorAll(".msgEmbed>.container .embed").entries())
+ if (element.classList.contains("emptyEmbed")) {
+ text !== false && animateGuiEmbedNameAt(i, text);
+ return i;
+ }
- for (const [i, embedObj] of (json.embeds || []).entries())
- if (!(0 in Object.keys(embedObj))) {
- text !== false && animateGuiEmbedNameAt(i, text);
- return i;
- }
+ for (const [i, embedObj] of(json.embeds || []).entries())
+ if (!(0 in Object.keys(embedObj))) {
+ text !== false && animateGuiEmbedNameAt(i, text);
+ return i;
+ }
- return -1;
+ return -1;
}
const changeLastActiveGuiEmbed = index => {
- const pickerEmbedText = document.querySelector('.colors .cTop .embedText>span');
+ const pickerEmbedText = document.querySelector(".colors .cTop .embedText>span");
- if (index === -1) {
- lastActiveGuiEmbedIndex = -1;
- return pickerEmbedText.textContent = '';
- }
+ if (index === -1) {
+ lastActiveGuiEmbedIndex = -1;
+ return pickerEmbedText.textContent = "";
+ }
- lastActiveGuiEmbedIndex = index;
+ lastActiveGuiEmbedIndex = index;
- if (pickerEmbedText) {
- pickerEmbedText.textContent = index + 1;
+ if (pickerEmbedText) {
+ pickerEmbedText.textContent = index + 1;
- const guiEmbedNames = document.querySelectorAll('.gui .item.guiEmbedName');
- pickerEmbedText.onclick = () => {
- const newIndex = parseInt(prompt('Enter an embed number' + (guiEmbedNames.length > 1 ? `, 1 - ${guiEmbedNames.length}` : ''), index + 1));
- if (isNaN(newIndex)) return;
- if (newIndex < 1 || newIndex > guiEmbedNames.length)
- return error(guiEmbedNames.length === 1 ? `'${newIndex}' is not a valid embed number` : `'${newIndex}' doesn't seem like a number between 1 and ${guiEmbedNames.length}`);
+ const guiEmbedNames = document.querySelectorAll(".gui .item.guiEmbedName");
+ pickerEmbedText.onclick = () => {
+ const newIndex = parseInt(prompt("Enter an embed number" + (guiEmbedNames.length > 1 ? `, 1 - ${guiEmbedNames.length}` : ""), index + 1));
+ if (isNaN(newIndex)) return;
+ if (newIndex < 1 || newIndex > guiEmbedNames.length)
+ return error(guiEmbedNames.length === 1 ? `"${newIndex}" is not a valid embed number` : `"${newIndex}" doesn"t seem like a number between 1 and ${guiEmbedNames.length}`);
- changeLastActiveGuiEmbed(newIndex - 1);
- }
- }
+ changeLastActiveGuiEmbed(newIndex - 1);
+ }
+ }
}
// Called after building embed for extra work.
-const afterBuilding = () => autoUpdateURL && urlOptions({ set: ['data', encodeJson(json)] });
+const afterBuilding = () => autoUpdateURL && urlOptions({
+ set: ["data", encodeJson(json)]
+});
+
// Parses emojis to images and adds code highlighting.
-const externalParsing = ({ noEmojis, element } = {}) => {
- !noEmojis && twemoji.parse(element || document.querySelector('.msgEmbed'), { base: 'https://cdn.jsdelivr.net/gh/twitter/twemoji@14.0.2/assets/' });
- for (const block of document.querySelectorAll('.markup pre > code'))
- hljs.highlightBlock(block);
+const externalParsing = ({
+ noEmojis,
+ element
+} = {}) => {
+ !noEmojis && twemoji.parse(element || document.querySelector(".msgEmbed"), {
+ base: "https://cdn.jsdelivr.net/gh/twitter/twemoji@14.0.2/assets/"
+ });
+ for (const block of document.querySelectorAll(".markup pre > code"))
+ hljs.highlightBlock(block);
- const embed = element?.closest('.embed');
- if (embed?.innerText.trim())
- (multiEmbeds ? embed : document.body).classList.remove('emptyEmbed');
+ const embed = element?.closest(".embed");
+ if (embed?.innerText.trim())
+ (multiEmbeds ? embed : document.body).classList.remove("emptyEmbed");
- afterBuilding()
+ afterBuilding()
};
let embedKeys = ["author", "footer", "color", "thumbnail", "image", "fields", "title", "description", "url", "timestamp"];
let mainKeys = ["embed", "embeds", "content"];
let allJsonKeys = [...mainKeys, ...embedKeys];
-// 'jsonObject' is used internally, do not change it's value. Assign to 'json' instead.
-// 'json' is the object that is used to build the embed. Assigning to it also updates the editor.
+// "jsonObject" is used internally, do not change it"s value. Assign to "json" instead.
+// "json" is the object that is used to build the embed. Assigning to it also updates the editor.
let jsonObject = window.json || {
- content: "",
- embed: {
- title: "Hello ~~people~~ world :wave:",
- description: "You can use [links](https://discord.com) or emojis :smile: 😎\n```\nAnd also code blocks\n```",
- color: 0x41f097,
- timestamp: new Date().toISOString(),
- url: "https://discord.com",
- author: {
- name: "Author name",
- url: "https://discord.com",
- icon_url: "https://cdn.discordapp.com/embed/avatars/0.png"
- },
- thumbnail: {
- url: "https://cdn.discordapp.com/embed/avatars/0.png"
- },
- image: {
- url: "https://glitchii.github.io/embedbuilder/assets/media/banner.png"
- },
- footer: {
- text: "Footer text",
- icon_url: "https://cdn.discordapp.com/embed/avatars/0.png"
- },
- fields: [
- {
- name: "Field 1, *lorem* **ipsum**, ~~dolor~~",
- value: "Field value"
- },
- {
- name: "Field 2",
- value: "You can use custom emojis <:Kekwlaugh:722088222766923847>. <:GangstaBlob:742256196295065661>",
- inline: false
- },
- {
- name: "Inline field",
- value: "Fields can be inline",
- inline: true
- },
- {
- name: "Inline field",
- value: "*Lorem ipsum*",
- inline: true
- },
- {
- name: "Inline field",
- value: "value",
- inline: true
- },
- {
- name: "Another field",
- value: "> Nope, didn't forget about this",
- inline: false
- }
- ]
- }
+ content: "",
+ embed: {
+ title: "Hello ~~people~~ world :wave:",
+ description: "You can use [links](https://discord.com) or emojis :smile: 😎\n```\nAnd also code blocks\n```",
+ color: 0x41f097,
+ timestamp: new Date().toISOString(),
+ url: "https://discord.com",
+ author: {
+ name: "Author name",
+ url: "https://discord.com",
+ icon_url: "https://cdn.discordapp.com/embed/avatars/0.png"
+ },
+ thumbnail: {
+ url: "https://cdn.discordapp.com/embed/avatars/0.png"
+ },
+ image: {
+ url: "https://glitchii.github.io/embedbuilder/assets/media/banner.png"
+ },
+ footer: {
+ text: "Footer text",
+ icon_url: "https://cdn.discordapp.com/embed/avatars/0.png"
+ },
+ fields: [{
+ name: "Field 1, *lorem* **ipsum**, ~~dolor~~",
+ value: "Field value"
+ },
+ {
+ name: "Field 2",
+ value: "You can use custom emojis <:Kekwlaugh:722088222766923847>. <:GangstaBlob:742256196295065661>",
+ inline: false
+ },
+ {
+ name: "Inline field",
+ value: "Fields can be inline",
+ inline: true
+ },
+ {
+ name: "Inline field",
+ value: "*Lorem ipsum*",
+ inline: true
+ },
+ {
+ name: "Inline field",
+ value: "value",
+ inline: true
+ },
+ {
+ name: "Another field",
+ value: "> Nope, didn't forget about this",
+ inline: false
+ }
+ ]
+ }
}
if (dataSpecified)
- jsonObject = decodeJson();
+ jsonObject = decodeJson();
if (allowPlaceholders)
- allowPlaceholders = params.get('placeholders') === 'errors' ? 1 : 2;
+ allowPlaceholders = params.get("placeholders") === "errors" ? 1 : 2;
-// Even if not in multi-embed mode, 'jsonObject' should always have an array 'embeds'
-// To get the right json object that includes either 'embeds' or 'embed' if not in multi-embed mode,
-// print 'json' (global variable) instead of 'jsonObject', jsonObject is used internally, you shouldn't modify it.
+// Even if not in multi-embed mode, "jsonObject" should always have an array "embeds"
+// To get the right json object that includes either "embeds" or "embed" if not in multi-embed mode,
+// print "json" (global variable) instead of "jsonObject", jsonObject is used internally, you shouldn"t modify it.
if (multiEmbeds && !jsonObject.embeds?.length)
- jsonObject.embeds = jsonObject.embed ? [jsonObject.embed] : [];
+ jsonObject.embeds = jsonObject.embed ? [jsonObject.embed] : [];
else if (!multiEmbeds)
- jsonObject.embeds = jsonObject.embeds?.[0] ? [jsonObject.embeds[0]] : jsonObject.embed ? [jsonObject.embed] : [];
+ jsonObject.embeds = jsonObject.embeds?.[0] ? [jsonObject.embeds[0]] : jsonObject.embed ? [jsonObject.embed] : [];
delete jsonObject.embed;
-addEventListener('DOMContentLoaded', () => {
- if (reverseColumns || localStorage.getItem('reverseColumns'))
- reverse();
- if (autoParams)
- document.querySelector('.item.auto-params > input').checked = true;
- if (hideMenu)
- document.querySelector('.top-btn.menu')?.classList.add('hidden');
- if (noMultiEmbedsOption)
- document.querySelector('.box .item.multi')?.remove();
- if (inIframe)
- // Remove menu options that don't work in iframe.
- for (const e of document.querySelectorAll('.no-frame'))
- e.remove();
-
- if (autoUpdateURL) {
- document.body.classList.add('autoUpdateURL');
- document.querySelector('.item.auto > input').checked = true;
- }
-
- if (single) {
- document.body.classList.add('single');
- if (autoParams)
- single ? urlOptions({ set: ['single', ''] }) : urlOptions({ remove: 'single' });
- }
-
- if (hideEditor) {
- document.body.classList.add('no-editor');
- document.querySelector('.toggle .toggles .editor input').checked = false;
- }
-
- if (hidePreview) {
- document.body.classList.add('no-preview');
- document.querySelector('.toggle .toggles .preview input').checked = false;
- }
-
- if (onlyEmbed) document.body.classList.add('only-embed');
- else {
- document.querySelector('.side1.noDisplay')?.classList.remove('noDisplay');
- if (useJsonEditor)
- document.body.classList.remove('gui');
- }
-
- if (noUser) {
- document.body.classList.add('no-user');
- if (autoParams)
- noUser ? urlOptions({ set: ['nouser', ''] }) : urlOptions({ remove: 'nouser' });
- }
-
- else {
- if (username) document.querySelector('.username').textContent = username;
- if (avatar) document.querySelector('.avatar').src = avatar;
- if (verified) document.querySelector('.msgEmbed > .contents').classList.add('verified');
- }
-
- for (const e of document.querySelectorAll('.clickable > img'))
- e.parentElement.addEventListener('mouseup', el => window.open(el.target.src));
-
- const editorHolder = document.querySelector('.editorHolder'),
- guiParent = document.querySelector('.top'),
- embedContent = document.querySelector('.messageContent'),
- embedCont = document.querySelector('.msgEmbed>.container'),
- gui = guiParent.querySelector('.gui:first-of-type');
-
- editor = CodeMirror(elt => editorHolder.parentNode.replaceChild(elt, editorHolder), {
- value: JSON.stringify(json, null, 4),
- gutters: ["CodeMirror-foldgutter", "CodeMirror-lint-markers"],
- scrollbarStyle: "overlay",
- mode: "application/json",
- theme: 'material-darker',
- matchBrackets: true,
- foldGutter: true,
- lint: true,
- extraKeys: {
- // Fill in indent spaces on a new line when enter (return) key is pressed.
- Enter: _ => {
- const cursor = editor.getCursor();
- const end = editor.getLine(cursor.line);
- const leadingSpaces = end.replace(/\S($|.)+/g, '') || ' \n';
- const nextLine = editor.getLine(cursor.line + 1);
-
- if ((nextLine === undefined || !nextLine.trim()) && !end.substr(cursor.ch).trim())
- editor.replaceRange('\n', { line: cursor.line, ch: cursor.ch });
- else
- editor.replaceRange(`\n${end.endsWith('{') ? leadingSpaces + ' ' : leadingSpaces}`, {
- line: cursor.line,
- ch: cursor.ch
- });
- },
- }
- });
-
- editor.focus();
-
- const notif = document.querySelector('.notification');
-
- error = (msg, time = '5s') => {
- notif.innerHTML = msg;
- notif.style.removeProperty('--startY');
- notif.style.removeProperty('--startOpacity');
- notif.style.setProperty('--time', time);
- notif.onanimationend = () => notif.style.display = null;
-
- // If notification element is not already visible, (no other message is already displayed), display it.
- if (!notif.style.display)
- return notif.style.display = 'block', false;
-
- // If there's a message already displayed, update it and delay animating out.
- notif.style.setProperty('--startY', 0);
- notif.style.setProperty('--startOpacity', 1);
- notif.style.display = null;
- setTimeout(() => notif.style.display = 'block', .5);
-
- return false;
- };
-
- const url = (url) => /^(https?:)?\/\//g.exec(url) ? url : '//' + url;
-
- const makeShort = (txt, length, mediaWidth) => {
- if (mediaWidth && matchMedia(`(max-width:${mediaWidth}px)`).matches)
- return txt.length > (length - 3) ? txt.substring(0, length - 3) + '...' : txt;
- return txt;
- }
-
- const allGood = embedObj => {
- let invalid, err;
- let str = JSON.stringify(embedObj, null, 4)
- let re = /("(?:icon_)?url": *")((?!\w+?:\/\/).+)"/g.exec(str);
-
- if (embedObj.timestamp && new Date(embedObj.timestamp).toString() === "Invalid Date") {
- if (allowPlaceholders === 2) return true;
- if (!allowPlaceholders) invalid = true, err = 'Timestamp is invalid';
- } else if (re) { // If a URL is found without a protocol
- if (!/\w+:|\/\/|^\//g.exec(re[2]) && re[2].includes('.')) {
- let activeInput = document.querySelector('input[class$="link" i]:focus')
- if (activeInput && !allowPlaceholders) {
- lastPos = activeInput.selectionStart + 7;
- activeInput.value = `http://${re[2]}`;
- activeInput.setSelectionRange(lastPos, lastPos)
- return true;
- }
- }
- if (allowPlaceholders !== 2)
- invalid = true, err = (`URL should have a protocol. Did you mean http://${makeShort(re[2], 30, 600).replace(' ', '')}?`);
- }
-
- if (invalid) {
- validationError = true;
- return error(err);
- }
-
- return true;
- }
-
- const markup = (txt, { replaceEmojis, inlineBlock, inEmbed }) => {
- if (replaceEmojis)
- txt = txt.replace(/(?[^>]+)(? p && emojis[p] ? emojis[p] : match);
-
- txt = txt
- /** Markdown */
- .replace(/<:\w+:(\d{17,19})>/g, '')
- .replace(/<a:\w+:(\d{17,20})>/g, '')
- .replace(/~~(.+?)~~/g, '$1')
- .replace(/\*\*\*(.+?)\*\*\*/g, '$1')
- .replace(/\*\*(.+?)\*\*/g, '$1')
- .replace(/__(.+?)__/g, '$1')
- .replace(/\*(.+?)\*/g, '$1')
- .replace(/_(.+?)_/g, '$1')
- // Replace >>> and > with block-quotes. > is HTML code for >
- .replace(/^(?: *>>> ([\s\S]*))|(?:^ *>(?!>>) +.+\n)+(?:^ *>(?!>>) .+\n?)+|^(?: *>(?!>>) ([^\n]*))(\n?)/mg, (all, match1, match2, newLine) => {
- return `
${match1 || match2 || newLine ? match1 || match2 : all.replace(/^ *> /gm, '')}
${x}
` : y ? `${y}
` : z ? `${z}
` : m);
- else {
- // Code block
- txt = txt.replace(/```(?:([a-z0-9_+\-.]+?)\n)?\n*([^\n][^]*?)\n*```/ig, (m, w, x) => {
- if (w) return `${x.trim()}
`
- else return `${x.trim()}
`
- });
- // Inline code
- txt = txt.replace(/`([^`]+?)`|``([^`]+?)``/g, (m, x, y, z) => x ? `${x}
` : y ? `${y}
` : z ? `${z}
` : m)
- }
-
- if (inEmbed)
- txt = txt.replace(/\[([^\[\]]+)\]\((.+?)\)/g, `$1`);
-
- return txt;
- }
-
-
- const createEmbedFields = (fields, embedFields) => {
- embedFields.innerHTML = '';
- let index, gridCol;
-
- for (const [i, f] of fields.entries()) {
- if (f.name && f.value) {
- const fieldElement = embedFields.insertBefore(document.createElement('div'), null);
- // Figuring out if there are only two fields on a row to give them more space.
- // e.fields = json.embeds.fields.
-
- // if both the field of index 'i' and the next field on it's right are inline and -
- if (fields[i].inline && fields[i + 1]?.inline &&
- // it's the first field in the embed or -
- ((i === 0 && fields[i + 2] && !fields[i + 2].inline) || ((
- // it's not the first field in the embed but the previous field is not inline or -
- i > 0 && !fields[i - 1].inline ||
- // it has 3 or more fields behind it and 3 of those are inline except the 4th one back if it exists -
- i >= 3 && fields[i - 1].inline && fields[i - 2].inline && fields[i - 3].inline && (fields[i - 4] ? !fields[i - 4].inline : !fields[i - 4])
- // or it's the first field on the last row or the last field on the last row is not inline or it's the first field in a row and it's the last field on the last row.
- ) && (i == fields.length - 2 || !fields[i + 2].inline))) || i % 3 === 0 && i == fields.length - 2) {
- // then make the field halfway (and the next field will take the other half of the embed).
- index = i, gridCol = '1 / 7';
- }
- // The next field.
- if (index === i - 1)
- gridCol = '7 / 13';
-
- if (!f.inline)
- fieldElement.outerHTML = `
- `;
- else {
- if (i && !fields[i - 1].inline) colNum = 1;
-
- fieldElement.outerHTML = `
- `;
-
- if (index !== i) gridCol = false;
- }
-
- colNum = (colNum === 9 ? 1 : colNum + 4);
- num++;
- };
- };
-
-
- for (const e of document.querySelectorAll('.embedField[style="grid-column: 1 / 5;"]'))
- if (!e.nextElementSibling || e.nextElementSibling.style.gridColumn === '1 / 13')
- e.style.gridColumn = '1 / 13';
- colNum = 1;
-
- display(embedFields, undefined, 'grid');
- }
-
- const smallerScreen = matchMedia('(max-width: 1015px)');
-
- const encodeHTML = str => str.replace(/[\u00A0-\u9999<>\&]/g, i => '' + i.charCodeAt(0) + ';');
-
- const timestamp = stringISO => {
- const date = stringISO ? new Date(stringISO) : new Date(),
- dateArray = date.toLocaleString('en-US', { hour: 'numeric', hour12: false, minute: 'numeric' }),
- today = new Date(),
- yesterday = new Date(new Date().setDate(today.getDate() - 1)),
- tommorrow = new Date(new Date().setDate(today.getDate() + 1));
-
- return today.toDateString() === date.toDateString() ? `Today at ${dateArray}` :
- yesterday.toDateString() === date.toDateString() ? `Yesterday at ${dateArray}` :
- tommorrow.toDateString() === date.toDateString() ? `Tomorrow at ${dateArray}` :
- `${String(date.getMonth() + 1).padStart(2, '0')}/${String(date.getDate()).padStart(2, '0')}/${date.getFullYear()}`;
- }
-
- const display = (el, data, displayType) => {
- if (data) el.innerHTML = data;
- el.style.display = displayType || "unset";
- }
-
- const hide = el => el.style.removeProperty('display'),
- imgSrc = (elm, src, remove) => remove ? elm.style.removeProperty('content') : elm.style.content = `url(${src})`;
-
- const [guiFragment, fieldFragment, embedFragment, guiEmbedAddFragment] = Array.from({ length: 4 }, () => document.createDocumentFragment());
- embedFragment.appendChild(document.querySelector('.embed.markup').cloneNode(true));
- guiEmbedAddFragment.appendChild(document.querySelector('.guiEmbedAdd').cloneNode(true));
- fieldFragment.appendChild(document.querySelector('.edit>.fields>.field').cloneNode(true));
-
- document.querySelector('.embed.markup').remove();
- gui.querySelector('.edit>.fields>.field').remove();
-
- for (const child of gui.childNodes)
- guiFragment.appendChild(child.cloneNode(true));
-
- // Renders the GUI editor with json data from 'jsonObject'.
- buildGui = (object = jsonObject, opts) => {
- gui.innerHTML = '';
- gui.appendChild(guiEmbedAddFragment.firstChild.cloneNode(true))
- .addEventListener('click', () => {
- if (indexOfEmptyGuiEmbed('(empty embed)') !== -1) return;
- jsonObject.embeds.push({});
- buildGui();
- });
-
- for (const child of Array.from(guiFragment.childNodes)) {
- if (child.classList?.[1] === 'content')
- gui.insertBefore(gui.appendChild(child.cloneNode(true)), gui.appendChild(child.nextElementSibling.cloneNode(true))).nextElementSibling.firstElementChild.value = object.content || '';
- else if (child.classList?.[1] === 'guiEmbedName') {
- for (const [i, embed] of (object.embeds.length ? object.embeds : [{}]).entries()) {
- const guiEmbedName = gui.appendChild(child.cloneNode(true))
-
- guiEmbedName.querySelector('.text').innerHTML = `Embed ${i + 1}${embed.title ? `: ${embed.title}` : ''}`;
- guiEmbedName.querySelector('.icon').addEventListener('click', () => {
- object.embeds.splice(i, 1);
- buildGui();
- buildEmbed();
- });
-
- const guiEmbed = gui.appendChild(createElement({ 'div': { className: 'guiEmbed' } }));
- const guiEmbedTemplate = child.nextElementSibling;
-
- for (const child2 of Array.from(guiEmbedTemplate.children)) {
- if (!child2?.classList.contains('edit')) {
- const row = guiEmbed.appendChild(child2.cloneNode(true));
- const edit = child2.nextElementSibling?.cloneNode(true);
- edit?.classList.contains('edit') && guiEmbed.appendChild(edit);
-
- switch (child2.classList[1]) {
- case 'author':
- const authorURL = embed?.author?.icon_url || '';
- if (authorURL)
- edit.querySelector('.imgParent').style.content = 'url(' + encodeHTML(authorURL) + ')';
- edit.querySelector('.editAuthorLink').value = authorURL;
- edit.querySelector('.editAuthorName').value = embed?.author?.name || '';
- break;
- case 'title':
- row.querySelector('.editTitle').value = embed?.title || '';
- break;
- case 'description':
- edit.querySelector('.editDescription').value = embed?.description || '';
- break;
- case 'thumbnail':
- const thumbnailURL = embed?.thumbnail?.url || '';
- if (thumbnailURL)
- edit.querySelector('.imgParent').style.content = 'url(' + encodeHTML(thumbnailURL) + ')';
- edit.querySelector('.editThumbnailLink').value = thumbnailURL;
- break;
- case 'image':
- const imageURL = embed?.image?.url || '';
- if (imageURL)
- edit.querySelector('.imgParent').style.content = 'url(' + encodeHTML(imageURL) + ')';
- edit.querySelector('.editImageLink').value = imageURL;
- break;
- case 'footer':
- const footerURL = embed?.footer?.icon_url || '';
- if (footerURL)
- edit.querySelector('.imgParent').style.content = 'url(' + encodeHTML(footerURL) + ')';
- edit.querySelector('.editFooterLink').value = footerURL;
- edit.querySelector('.editFooterText').value = embed?.footer?.text || '';
- break;
- case 'fields':
- for (const f of embed?.fields || []) {
- const fields = edit.querySelector('.fields');
- const field = fields.appendChild(createElement({ 'div': { className: 'field' } }));
-
- for (const child of Array.from(fieldFragment.firstChild.children)) {
- const newChild = field.appendChild(child.cloneNode(true));
-
- if (child.classList.contains('inlineCheck'))
- newChild.querySelector('input').checked = !!f.inline;
-
- else if (f.value && child.classList?.contains('fieldInner'))
- newChild.querySelector('.designerFieldName input').value = f.name || '',
- newChild.querySelector('.designerFieldValue textarea').value = f.value || '';
- }
- }
- }
- }
- }
- }
- }
-
- // Expand last embed in GUI
- const names = gui.querySelectorAll('.guiEmbedName');
- names[names.length - 1]?.classList.add('active');
- }
-
- for (const e of document.querySelectorAll('.top>.gui .item'))
- e.addEventListener('click', el => {
- if (e?.classList.contains('active'))
- getSelection().anchorNode !== e && e.classList.remove('active');
- else if (e) {
- const inlineField = e.closest('.inlineField'),
- input = e.nextElementSibling?.querySelector('input[type="text"]'),
- txt = e.nextElementSibling?.querySelector('textarea');
-
- e.classList.add('active');
- if (e.classList.contains('guiEmbedName'))
- return changeLastActiveGuiEmbed(guiEmbedIndex(e));
-
- else if (inlineField)
- inlineField.querySelector('.ttle~input').focus();
-
- else if (e.classList.contains('footer')) {
- const date = new Date(jsonObject.embeds[guiEmbedIndex(e)]?.timestamp || new Date());
- const textElement = e.nextElementSibling.querySelector('svg>text');
- const dateInput = textElement.closest('.footerDate').querySelector('input');
-
- return (
- textElement.textContent = (date.getDate() + '').padStart(2, 0),
- dateInput.value = date.toISOString().substring(0, 19)
- );
- }
-
- else if (input) {
- !smallerScreen.matches && input.focus();
- input.selectionStart = input.selectionEnd = input.value.length;
- }
-
- else if (txt && !smallerScreen.matches)
- txt.focus();
-
- if (e.classList.contains('fields')) {
- if (reverseColumns && smallerScreen.matches)
- // return elm.nextElementSibling.scrollIntoView({ behavior: 'smooth', block: "end" });
- return e.parentNode.scrollTop = e.offsetTop;
-
- e.scrollIntoView({ behavior: "smooth", block: "center" });
- }
- }
- })
-
- content = gui.querySelector('.editContent');
- title = gui.querySelector('.editTitle');
- authorName = gui.querySelector('.editAuthorName');
- authorLink = gui.querySelector('.editAuthorLink');
- desc = gui.querySelector('.editDescription');
- thumbLink = gui.querySelector('.editThumbnailLink');
- imgLink = gui.querySelector('.editImageLink');
- footerText = gui.querySelector('.editFooterText');
- footerLink = gui.querySelector('.editFooterLink');
-
- // Scroll into view when tabs are opened in the GUI.
- const lastTabs = Array.from(document.querySelectorAll('.footer.rows2, .image.largeImg'));
- const requiresView = matchMedia(`${smallerScreen.media}, (max-height: 845px)`);
- const addGuiEventListeners = () => {
- for (const e of document.querySelectorAll('.gui .item:not(.fields)'))
- e.onclick = () => {
- if (lastTabs.includes(e) || requiresView.matches) {
- if (!reverseColumns || !smallerScreen.matches)
- e.scrollIntoView({ behavior: 'smooth', block: "center" });
- else if (e.nextElementSibling.classList.contains('edit') && e.classList.contains('active'))
- // e.nextElementSibling.scrollIntoView({ behavior: 'smooth', block: "end" });
- e.parentNode.scrollTop = e.offsetTop;
- }
- };
-
- for (const e of document.querySelectorAll('.addField'))
- e.onclick = () => {
- const guiEmbed = e.closest('.guiEmbed');
- const indexOfGuiEmbed = Array.from(gui.querySelectorAll('.guiEmbed')).indexOf(guiEmbed);
- if (indexOfGuiEmbed === -1) return error('Could not find the embed to add the field to.');
-
- const fieldsObj = (jsonObject.embeds[indexOfGuiEmbed] ??= {}).fields ??= [];
- if (fieldsObj.length >= 25) return error('Cannot have more than 25 fields');
- fieldsObj.push({ name: "Field name", value: "Field value", inline: false });
-
- const newField = guiEmbed?.querySelector('.item.fields+.edit>.fields')?.appendChild(fieldFragment.firstChild.cloneNode(true));
-
- buildEmbed();
- addGuiEventListeners();
-
- newField.scrollIntoView({ behavior: "smooth", block: "center" });
- if (!smallerScreen.matches) {
- const firstFieldInput = newField.querySelector('.designerFieldName input');
-
- firstFieldInput?.setSelectionRange(firstFieldInput.value.length, firstFieldInput.value.length);
- firstFieldInput?.focus();
- }
- };
-
- for (const e of document.querySelectorAll('.fields .field .removeBtn'))
- e.onclick = () => {
- const embedIndex = guiEmbedIndex(e);
- const fieldIndex = Array.from(e.closest('.fields').children).indexOf(e.closest('.field'));
-
- if (jsonObject.embeds[embedIndex]?.fields[fieldIndex] === -1)
- return error('Failed to find the index of the field to remove.');
-
- jsonObject.embeds[embedIndex].fields.splice(fieldIndex, 1);
-
- buildEmbed();
- e.closest('.field').remove();
- };
-
- for (const e of gui.querySelectorAll('textarea, input'))
- e.oninput = el => {
- const value = el.target.value;
- const index = guiEmbedIndex(el.target);
- const field = el.target.closest('.field');
- const fields = field?.closest('.fields');
- const embedObj = jsonObject.embeds[index] ??= {};
-
- if (field) {
- console.log(field)
- const fieldIndex = Array.from(fields.children).indexOf(field);
- const jsonField = embedObj.fields[fieldIndex];
- const embedFields = document.querySelectorAll('.container>.embed')[index]?.querySelector('.embedFields');
-
- if (jsonField) {
- if (el.target.type === 'text') jsonField.name = value;
- else if (el.target.type === 'textarea') jsonField.value = value;
- else jsonField.inline = el.target.checked;
- createEmbedFields(embedObj.fields, embedFields);
- }
- } else {
- switch (el.target.classList?.[0]) {
- case 'editContent':
- jsonObject.content = value;
- buildEmbed({ only: 'content' });
- break;
- case 'editTitle':
- embedObj.title = value;
- const guiEmbedName = el.target.closest('.guiEmbed')?.previousElementSibling;
- if (guiEmbedName?.classList.contains('guiEmbedName'))
- guiEmbedName.querySelector('.text').innerHTML = `${guiEmbedName.innerText.split(':')[0]}${value ? `: ${value}` : ''}`;
- buildEmbed({ only: 'embedTitle', index: guiEmbedIndex(el.target) });
- break;
- case 'editAuthorName':
- embedObj.author ??= {}, embedObj.author.name = value;
- buildEmbed({ only: 'embedAuthorName', index: guiEmbedIndex(el.target) });
- break;
- case 'editAuthorLink': embedObj.author ??= {}, embedObj.author.icon_url = value;
- imgSrc(el.target.previousElementSibling, value);
- buildEmbed({ only: 'embedAuthorLink', index: guiEmbedIndex(el.target) });
- break;
- case 'editDescription': embedObj.description = value;
- buildEmbed({ only: 'embedDescription', index: guiEmbedIndex(el.target) });
- break;
- case 'editThumbnailLink':
- embedObj.thumbnail ??= {}, embedObj.thumbnail.url = value;
- imgSrc(el.target.closest('.editIcon').querySelector('.imgParent'), value);
- buildEmbed({ only: 'embedThumbnail', index: guiEmbedIndex(el.target) });
- break;
- case 'editImageLink':
- embedObj.image ??= {}, embedObj.image.url = value;
- imgSrc(el.target.closest('.editIcon').querySelector('.imgParent'), value);
- buildEmbed({ only: 'embedImageLink', index: guiEmbedIndex(el.target) });
- break;
- case 'editFooterText':
- embedObj.footer ??= {}, embedObj.footer.text = value;
- buildEmbed({ only: 'embedFooterText', index: guiEmbedIndex(el.target) });
- break;
- case 'editFooterLink':
- embedObj.footer ??= {}, embedObj.footer.icon_url = value;
- imgSrc(el.target.previousElementSibling, value);
- buildEmbed({ only: 'embedFooterLink', index: guiEmbedIndex(el.target) });
- break;
- case 'embedFooterTimestamp':
- const date = new Date(value);
- if (isNaN(date.getTime())) return error('Invalid date');
-
- embedObj.timestamp = date;
- el.target.parentElement.querySelector('svg>text').textContent = (date.getDate() + '').padStart(2, 0);
- buildEmbed({ only: 'embedFooterTimestamp', index: guiEmbedIndex(el.target) });
- break;
- }
-
- // Find and filter out any empty objects ({}) in the embeds array as Discord doesn't like them.
- const nonEmptyEmbedObjects = json.embeds?.filter(o => 0 in Object.keys(o));
- if (nonEmptyEmbedObjects?.length)
- json.embeds = nonEmptyEmbedObjects;
- }
-
- // Display embed elements hidden due to not having content. '.msgEmbed>.container' is embed container.
- document.querySelectorAll('.msgEmbed>.container')[guiEmbedIndex(el.target)]?.querySelector('.emptyEmbed')?.classList.remove('emptyEmbed');
- }
-
- const uploadError = (message, browse, sleepTime) => {
- browse.classList.remove('loading');
- browse.classList.add('error');
-
- const p = browse.parentElement.querySelector('.browse.error>p')
- p.dataset.error = message;
-
- setTimeout(() => {
- browse.classList.remove('error');
- delete p.dataset.error;
- }, sleepTime ?? 7000);
- }
-
- for (const browse of document.querySelectorAll('.browse'))
- browse.onclick = e => {
- const formData = new FormData();
- const fileInput = createElement({ 'input': { type: 'file', accept: 'image/*' } });
- const edit = browse.closest('.edit');
- const expiration = 7 * 24 * 60 * 60;
-
- fileInput.onchange = el => {
- if (el.target.files[0].size > 32 * 1024 * 1024)
- return uploadError('File is too large. Maximum size is 32 MB.', browse, 5000);
-
- formData.append("expiration", expiration); // Expire after 7 days. Discord caches files.
- formData.append("key", options.uploadKey || "93385e22b0619db73a5525140b13491c"); // Add your own key through the uploadKey option.
- formData.append("image", el.target.files[0]);
- // formData.append("name", ""); // Uses original file name if no "name" is not specified.
-
- browse.classList.add('loading');
-
- fetch('https://api.imgbb.com/1/upload', { method: 'POST', body: formData })
- .then(res => res.json())
- .then(res => {
- browse.classList.remove('loading');
- if (!res.success) {
- console.log('Upload failed:', res.data?.error || res.error?.message || res);
- return uploadError(res.data?.error || res.error?.message || "Request failed. (Check dev-console)", browse);
- }
-
- imgSrc(edit.querySelector('.editIcon > .imgParent'), res.data.url);
- const linkInput = edit.querySelector('input[type=text]');
- const textInput = edit.querySelector('input[class$=Name], input[class$=Text]');
-
- linkInput.value = res.data.url;
- // focus on the next empty input if the field requires a name or text to display eg. footer or author.
- !textInput?.value && textInput?.focus();
-
- console.info(`${res.data.url} will be deleted in ${expiration / 60 / 60} hours. To delete it now, visit ${res.data.delete_url} and scroll down to find the delete button.`);
-
- linkInput.dispatchEvent(new Event('input'));
- }).catch(err => {
- browse.classList.remove('loading');
- error(`Request failed with error: ${err}`)
- })
- }
-
- fileInput.click();
- }
-
- if (multiEmbeds) {
- for (const e of document.querySelectorAll('.guiEmbed'))
- e.onclick = () => {
- const guiEmbed = e.closest('.guiEmbed');
- const indexOfGuiEmbed = Array.from(gui.querySelectorAll('.guiEmbed')).indexOf(guiEmbed);
- if (indexOfGuiEmbed === -1) return error('Could not find the embed to add the field to.');
-
- changeLastActiveGuiEmbed(indexOfGuiEmbed);
- };
-
-
- if (!jsonObject.embeds[lastActiveGuiEmbedIndex])
- changeLastActiveGuiEmbed(
- jsonObject.embeds[lastActiveGuiEmbedIndex - 1] ?
- lastActiveGuiEmbedIndex - 1 :
- jsonObject.embeds.length ? 0 : -1
- );
- } else {
- changeLastActiveGuiEmbed(-1);
- }
- }
-
- addGuiEventListeners();
-
- let activeGuiEmbed;
-
- if (opts?.guiEmbedIndex) {
- activeGuiEmbed = Array.from(document.querySelectorAll('.gui .item.guiEmbedName'))[opts.guiEmbedIndex];
- activeGuiEmbed?.classList.add('active');
- activeGuiEmbed = activeGuiEmbed?.nextElementSibling;
- }
-
-
- if (opts?.activateClassNames)
- for (const cName of opts.activateClassNames)
- for (const e of document.getElementsByClassName(cName))
- e.classList.add('active');
-
- else if (opts?.guiTabs) {
- const tabs = opts.guiTabs.split?.(/, */) || opts.guiTabs;
- const bottomKeys = ['footer', 'image'];
- const topKeys = ['author', 'content'];
-
-
- // Deactivate the default activated GUI fields
- for (const e of gui.querySelectorAll('.item:not(.guiEmbedName).active'))
- e.classList.remove('active');
-
- // Activate wanted GUI fields
- for (const e of document.querySelectorAll(`.${tabs.join(', .')}`))
- e.classList.add('active');
-
- // Autoscroll GUI to the bottom if necessary.
- if (!tabs.some(item => topKeys.includes(item)) && tabs.some(item => bottomKeys.includes(item))) {
- const gui2 = document.querySelector('.top .gui');
- gui2.scrollTo({ top: gui2.scrollHeight });
- }
- }
-
- else if (opts?.activate)
- for (const clss of Array.from(opts.activate).map(el => el.className).map(clss => '.' + clss.split(' ').slice(0, 2).join('.')))
- for (const e of document.querySelectorAll(clss))
- e.classList.add('active');
-
- else for (const clss of document.querySelectorAll('.item.author, .item.description'))
- clss.classList.add('active');
- }
-
- buildGui(jsonObject, { guiTabs });
- gui.classList.remove('hidden');
-
- fields = gui.querySelector('.fields ~ .edit .fields');
-
- // Renders embed and message content.
- buildEmbed = ({ jsonData, only, index = 0 } = {}) => {
- if (jsonData) json = jsonData;
- if (!jsonObject.embeds?.length) document.body.classList.add('emptyEmbed');
-
- try {
- // If there's no message content, hide the message content HTML element.
- if (!jsonObject.content) document.body.classList.add('emptyContent');
- else {
- // Update embed content in render
- embedContent.innerHTML = markup(encodeHTML(jsonObject.content), { replaceEmojis: true });
- document.body.classList.remove('emptyContent');
- }
-
- const embed = document.querySelectorAll('.container>.embed')[index];
- const embedObj = jsonObject.embeds[index];
-
- if (only && (!embed || !embedObj)) return buildEmbed();
-
- switch (only) {
- // If only updating the message content and nothing else, return here.
- case 'content': return externalParsing({ element: embedContent });
- case 'embedTitle':
- const embedTitle = embed?.querySelector('.embedTitle');
- if (!embedTitle) return buildEmbed();
- if (!embedObj.title) hide(embedTitle);
- else display(embedTitle, markup(`${embedObj.url ? '' + encodeHTML(embedObj.title) + '' : encodeHTML(embedObj.title)}`, { replaceEmojis: true, inlineBlock: true }));
-
- return externalParsing({ element: embedTitle });
- case 'embedAuthorName':
- case 'embedAuthorLink':
- const embedAuthor = embed?.querySelector('.embedAuthor');
- if (!embedAuthor) return buildEmbed();
- if (!embedObj.author?.name) hide(embedAuthor);
- else display(embedAuthor, `
- ${embedObj.author.icon_url ? '' : ''}
- ${embedObj.author.url ? '' + encodeHTML(embedObj.author.name) + '' : ' '}`, 'flex');
-
- return externalParsing({ element: embedAuthor });
- case 'embedDescription':
- const embedDescription = embed?.querySelector('.embedDescription');
- if (!embedDescription) return buildEmbed();
- if (!embedObj.description) hide(embedDescription);
- else display(embedDescription, markup(encodeHTML(embedObj.description), { inEmbed: true, replaceEmojis: true }));
-
- return externalParsing({ element: embedDescription });
- case 'embedThumbnail':
- const embedThumbnailLink = embed?.querySelector('.embedThumbnailLink');
- if (!embedThumbnailLink) return buildEmbed();
- const pre = embed.querySelector('.embedGrid .markup pre');
- if (embedObj.thumbnail?.url) {
- embedThumbnailLink.src = embedObj.thumbnail.url;
- embedThumbnailLink.parentElement.style.display = 'block';
- if (pre) pre.style.maxWidth = '90%';
- } else {
- hide(embedThumbnailLink.parentElement);
- pre?.style.removeProperty('max-width');
- }
-
- return afterBuilding();
- case 'embedImage':
- const embedImageLink = embed?.querySelector('.embedImageLink');
- if (!embedImageLink) return buildEmbed();
- if (!embedObj.image?.url) hide(embedImageLink.parentElement);
- else embedImageLink.src = embedObj.image.url,
- embedImageLink.parentElement.style.display = 'block';
-
- return afterBuilding();
- case 'embedFooterText':
- case 'embedFooterLink':
- case 'embedFooterTimestamp':
- const embedFooter = embed?.querySelector('.embedFooter');
- if (!embedFooter) return buildEmbed();
- if (!embedObj.footer?.text) hide(embedFooter);
- else display(embedFooter, `
- ${embedObj.footer.icon_url ? '' : ''} `, 'flex');
-
- return externalParsing({ element: embedFooter });
- }
-
- if (multiEmbeds) embedCont.innerHTML = '';
-
- for (const embedObj of jsonObject.embeds) {
- if (!allGood(embedObj)) continue;
- if (!multiEmbeds) embedCont.innerHTML = '';
-
- validationError = false;
-
- const embedElement = embedCont.appendChild(embedFragment.firstChild.cloneNode(true));
- const embedGrid = embedElement.querySelector('.embedGrid');
- const msgEmbed = embedElement.querySelector('.msgEmbed');
- const embedTitle = embedElement.querySelector('.embedTitle');
- const embedDescription = embedElement.querySelector('.embedDescription');
- const embedAuthor = embedElement.querySelector('.embedAuthor');
- const embedFooter = embedElement.querySelector('.embedFooter');
- const embedImage = embedElement.querySelector('.embedImage > img');
- const embedThumbnail = embedElement.querySelector('.embedThumbnail > img');
- const embedFields = embedElement.querySelector('.embedFields');
-
- if (embedObj.title) display(embedTitle, markup(`${embedObj.url ? '' + encodeHTML(embedObj.title) + '' : encodeHTML(embedObj.title)}`, { replaceEmojis: true, inlineBlock: true }));
- else hide(embedTitle);
-
- if (embedObj.description) display(embedDescription, markup(encodeHTML(embedObj.description), { inEmbed: true, replaceEmojis: true }));
- else hide(embedDescription);
-
- if (embedObj.color) embedGrid.closest('.embed').style.borderColor = (typeof embedObj.color === 'number' ? '#' + embedObj.color.toString(16).padStart(6, "0") : embedObj.color);
- else embedGrid.closest('.embed').style.removeProperty('border-color');
-
- if (embedObj.author?.name) display(embedAuthor, `
- ${embedObj.author.icon_url ? '' : ''}
- ${embedObj.author.url ? '' + encodeHTML(embedObj.author.name) + '' : ' '}`, 'flex');
- else hide(embedAuthor);
-
- const pre = embedGrid.querySelector('.markup pre');
- if (embedObj.thumbnail?.url) {
- embedThumbnail.src = embedObj.thumbnail.url;
- embedThumbnail.parentElement.style.display = 'block';
- if (pre) pre.style.maxWidth = '90%';
- } else {
- hide(embedThumbnail.parentElement);
- if (pre) pre.style.removeProperty('max-width');
- }
-
- if (embedObj.image?.url)
- embedImage.src = embedObj.image.url,
- embedImage.parentElement.style.display = 'block';
- else hide(embedImage.parentElement);
-
- if (embedObj.footer?.text) display(embedFooter, `
- ${embedObj.footer.icon_url ? '' : ''} `, 'flex');
- else if (embedObj.timestamp) display(embedFooter, ` `, 'flex');
- else hide(embedFooter);
-
- if (embedObj.fields) createEmbedFields(embedObj.fields, embedFields);
- else hide(embedFields);
-
- document.body.classList.remove('emptyEmbed');
- externalParsing();
-
- if (embedElement.innerText.trim() || embedElement.querySelector('.embedGrid > [style*=display] img'))
- embedElement.classList.remove('emptyEmbed');
- else
- embedElement.classList.add('emptyEmbed');
- }
-
- // Make sure that the embed has no text or any visible images such as custom emojis before hiding.
- if (!multiEmbeds && !embedCont.innerText.trim() && !embedCont.querySelector('.embedGrid > [style*=display] img'))
- document.body.classList.add('emptyEmbed');
-
- afterBuilding()
- } catch (e) {
- console.error(e);
- error(e);
- }
- window.parent.postMessage([sendMessage, json], '*');
- console.log("UPDATED")
- }
-
- editor.on('change', editor => {
- // If the editor value is not set by the user, return.
- if (JSON.stringify(json, null, 4) === editor.getValue()) return;
-
- try {
- // Autofill when " is typed on new line
- const line = editor.getCursor().line;
- const text = editor.getLine(line)
-
- if (text.trim() === '"') {
- editor.replaceRange(text.trim() + ':', { line, ch: line.length });
- editor.setCursor(line, text.length)
- }
-
- json = JSON.parse(editor.getValue());
- const dataKeys = Object.keys(json);
-
- if (dataKeys.length && !allJsonKeys.some(key => dataKeys.includes(key))) {
- const usedKeys = dataKeys.filter(key => !allJsonKeys.includes(key));
- if (usedKeys.length > 2)
- return error(`'${usedKeys[0] + "', '" + usedKeys.slice(1, usedKeys.length - 1).join("', '")}', and '${usedKeys[usedKeys.length - 1]}' are invalid keys.`);
- return error(`'${usedKeys.length == 2 ? usedKeys[0] + "' and '" + usedKeys[usedKeys.length - 1] + "' are invalid keys." : usedKeys[0] + "' is an invalid key."}`);
- }
-
- buildEmbed();
-
- } catch (e) {
- if (editor.getValue()) return;
- document.body.classList.add('emptyEmbed');
- embedContent.innerHTML = '';
- }
- });
-
- const picker = new CP(document.querySelector('.picker'), state = { parent: document.querySelector('.cTop') });
-
- picker.fire?.('change', toRGB('#41f097'));
-
- const colors = document.querySelector('.colors');
- const hexInput = colors?.querySelector('.hex>div input');
-
- let typingHex = true, exit = false;
-
- removePicker = () => {
- if (exit) return exit = false;
- if (typingHex) picker.enter();
- else {
- typingHex = false, exit = true;
- colors.classList.remove('picking');
- picker.exit();
- }
- }
-
- document.querySelector('.colBack')?.addEventListener('click', () => {
- picker.self.remove();
- typingHex = false;
- removePicker();
- })
-
- picker.on?.('exit', removePicker);
- picker.on?.('enter', () => {
- const embedIndex = multiEmbeds && lastActiveGuiEmbedIndex !== -1 ? lastActiveGuiEmbedIndex : 0;
- if (jsonObject?.embeds[embedIndex]?.color) {
- hexInput.value = jsonObject.embeds[embedIndex].color.toString(16).padStart(6, '0');
- document.querySelector('.hex.incorrect')?.classList.remove('incorrect');
- }
- colors.classList.add('picking')
- })
-
- document.querySelectorAll('.color').forEach(e => e.addEventListener('click', el => {
- const embedIndex = multiEmbeds && lastActiveGuiEmbedIndex !== -1 ? lastActiveGuiEmbedIndex : 0;
- const embed = document.querySelectorAll('.msgEmbed .container>.embed')[embedIndex];
- const embedObj = jsonObject.embeds[embedIndex] ??= {};
- const color = el.target.closest('.color');
-
- embedObj.color = toRGB(color.style.backgroundColor, false, true);
- embed && (embed.style.borderColor = color.style.backgroundColor);
- picker.source.style.removeProperty('background');
- }))
-
- hexInput?.addEventListener('focus', () => typingHex = true);
- setTimeout(() => {
- picker.on?.('change', function (r, g, b, a) {
- const embedIndex = multiEmbeds && lastActiveGuiEmbedIndex !== -1 ? lastActiveGuiEmbedIndex : 0;
- const embed = document.querySelectorAll('.msgEmbed .container>.embed')[embedIndex];
- const embedObj = jsonObject.embeds[embedIndex];
-
- picker.source.style.background = this.color(r, g, b);
- embedObj.color = parseInt(this.color(r, g, b).slice(1), 16);
- embed.style.borderColor = this.color(r, g, b);
- hexInput.value = embedObj.color.toString(16).padStart(6, '0');
- })
- }, 1000)
-
- document.querySelector('.timeText').innerText = timestamp();
-
- for (const block of document.querySelectorAll('.markup pre > code'))
- hljs.highlightBlock(block);
-
- document.querySelector('.opt.gui').addEventListener('click', () => {
- if (lastGuiJson && lastGuiJson !== JSON.stringify(json, null, 4))
- buildGui();
-
- lastGuiJson = false
- activeFields = null;
-
- document.body.classList.add('gui');
- if (pickInGuiMode) {
- pickInGuiMode = false;
- togglePicker();
- }
- })
-
- document.querySelector('.opt.json').addEventListener('click', () => {
- const emptyEmbedIndex = indexOfEmptyGuiEmbed(false);
- if (emptyEmbedIndex !== -1)
- // Clicked GUI tab while a blank embed is added from GUI.
- return error(gui.querySelectorAll('.item.guiEmbedName')[emptyEmbedIndex].innerText.split(':')[0] + ' should not be empty.', '3s');
-
- const jsonStr = JSON.stringify(json, null, 4);
- lastGuiJson = jsonStr;
-
- document.body.classList.remove('gui');
- editor.setValue(jsonStr === '{}' ? '{\n\t\n}' : jsonStr);
- editor.refresh();
- editor.focus();
-
- activeFields = document.querySelectorAll('.gui > .item.active');
- if (document.querySelector('section.side1.low'))
- togglePicker(true);
- })
-
- document.querySelector('.clear').addEventListener('click', () => {
- json = {};
-
- picker.source.style.removeProperty('background');
- document.querySelector('.msgEmbed .container>.embed')?.remove();
-
- buildEmbed();
- buildGui();
-
- const jsonStr = JSON.stringify(json, null, 4);
- editor.setValue(jsonStr === '{}' ? '{\n\t\n}' : jsonStr);
-
- for (const e of document.querySelectorAll('.gui .item'))
- e.classList.add('active');
-
- if (!smallerScreen.matches)
- content.focus();
- })
-
- document.querySelector('.top-btn.menu')?.addEventListener('click', e => {
- if (e.target.closest('.item.dataLink')) {
- const data = encodeJson(json, true).replace(/(? x === '=' ? '' : '&');
- if (!window.chrome)
- // With long text inside a 'prompt' on Chromium based browsers, some text will be trimmed off and replaced with '...'.
- return prompt('Here\'s the current URL with base64 embed data:', data);
-
- // So, for the Chromium users, we copy to clipboard instead of showing a prompt.
- try {
- // Clipboard API might only work on HTTPS protocol.
- navigator.clipboard.writeText(data);
- } catch {
- const input = document.body.appendChild(document.createElement('input'));
- input.value = data;
- input.select();
- document.setSelectionRange(0, 50000);
- document.execCommand('copy');
- document.body.removeChild(input);
- }
-
- return alert('Copied to clipboard.');
- }
-
- if (e.target.closest('.item.download'))
- return createElement({ a: { download: 'embed' + '.json', href: 'data:text/json;charset=utf-8,' + encodeURIComponent(JSON.stringify(json, null, 4)) } }).click();
-
- const input = e.target.closest('.item')?.querySelector('input');
- if (input) input.checked = !input.checked;
-
- if (e.target.closest('.item.auto')) {
- autoUpdateURL = document.body.classList.toggle('autoUpdateURL');
- if (autoUpdateURL) localStorage.setItem('autoUpdateURL', true);
- else localStorage.removeItem('autoUpdateURL');
- urlOptions({ set: ['data', encodeJson(json)] });
- } else if (e.target.closest('.item.reverse')) {
- reverse(reverseColumns);
- reverseColumns = !reverseColumns;
- toggleStored('reverseColumns');
- } else if (e.target.closest('.item.noUser')) {
- if (options.avatar) document.querySelector('img.avatar').src = options.avatar;
-
- const noUser = document.body.classList.toggle('no-user');
- if (autoParams) noUser ? urlOptions({ set: ['nouser', ''] }) : urlOptions({ remove: 'nouser' });
- toggleStored('noUser');
- } else if (e.target.closest('.item.auto-params')) {
- if (input.checked) localStorage.setItem('autoParams', true);
- else localStorage.removeItem('autoParams');
- autoParams = input.checked;
- } else if (e.target.closest('.toggles>.item')) {
- const win = input.closest('.item').classList[2];
-
- if (input.checked) {
- document.body.classList.remove(`no-${win}`);
- localStorage.removeItem(`hide${win}`);
- } else {
- document.body.classList.add(`no-${win}`);
- localStorage.setItem(`hide${win}`, true);
- }
- } else if (e.target.closest('.item.multi') && !noMultiEmbedsOption) {
- multiEmbeds = !document.body.classList.toggle('single');
- activeFields = document.querySelectorAll('.gui > .item.active');
-
- if (autoParams) !multiEmbeds ? urlOptions({ set: ['single', ''] }) : urlOptions({ remove: 'single' });
- if (multiEmbeds) localStorage.setItem('multiEmbeds', true);
- else {
- localStorage.removeItem('multiEmbeds');
- jsonObject.embeds = [jsonObject.embeds?.[0] || {}];
- }
-
- buildGui();
- buildEmbed();
- editor.setValue(JSON.stringify(json, null, 4));
- }
-
- e.target.closest('.top-btn')?.classList.toggle('active')
- })
-
- document.querySelectorAll('.img').forEach(e => {
- if (e.nextElementSibling?.classList.contains('spinner-container'))
- e.addEventListener('error', el => {
- el.target.style.removeProperty('display');
- el.target.nextElementSibling.style.display = 'block';
- })
- })
-
- let pickInGuiMode = false;
- togglePicker = pickLater => {
- colors.classList.toggle('display');
- document.querySelector('.side1').classList.toggle('low');
- if (pickLater) pickInGuiMode = true;
- };
-
- document.querySelector('.pickerToggle').addEventListener('click', () => togglePicker());
- buildEmbed();
-
- document.body.addEventListener('click', e => {
- if (e.target.classList.contains('low') || (e.target.classList.contains('top') && colors.classList.contains('display')))
- togglePicker();
- })
-
- // #0070ff, #5865f2
- document.querySelector('.colors .hex>div')?.addEventListener('input', e => {
- let inputValue = e.target.value;
-
- if (inputValue.startsWith('#'))
- e.target.value = inputValue.slice(1), inputValue = e.target.value;
- if (inputValue.length !== 6 || !/^[a-zA-Z0-9]{6}$/g.test(inputValue))
- return e.target.closest('.hex').classList.add('incorrect');
-
- e.target.closest('.hex').classList.remove('incorrect');
-
- const embedIndex = multiEmbeds && lastActiveGuiEmbedIndex !== -1 ? lastActiveGuiEmbedIndex : 0;
- jsonObject.embeds[embedIndex].color = parseInt(inputValue, 16);
- picker.fire?.('change', toRGB(inputValue));
-
- buildEmbed();
- })
-
- if (onlyEmbed) document.querySelector('.side1')?.remove();
-
- const menuMore = document.querySelector('.item.section .inner.more');
- const menuSource = menuMore?.querySelector('.source');
-
- if (!sourceOption) menuSource.remove();
- if (menuMore.childElementCount < 2) menuMore?.classList.add('invisible');
- if (menuMore.parentElement.childElementCount < 1) menuMore?.parentElement.classList.add('invisible');
-
- document.querySelector('.top-btn.copy').addEventListener('click', e => {
- const mark = e.target.closest('.top-btn.copy').querySelector('.mark'),
- jsonData = JSON.stringify(json, null, 4),
- next = () => {
- mark?.classList.remove('hidden');
- mark?.previousElementSibling?.classList.add('hidden');
-
- setTimeout(() => {
- mark?.classList.add('hidden');
- mark?.previousElementSibling?.classList.remove('hidden');
- }, 1500);
- }
-
- if (!navigator.clipboard?.writeText(jsonData).then(next).catch(err => console.log('Could not copy to clipboard: ' + err.message))) {
- const textarea = document.body.appendChild(document.createElement('textarea'));
-
- textarea.value = jsonData;
- textarea.select();
- textarea.setSelectionRange(0, 50000);
- document.execCommand('copy');
- document.body.removeChild(textarea);
- next();
- }
- });
+addEventListener("DOMContentLoaded", () => {
+ if (reverseColumns || localStorage.getItem("reverseColumns"))
+ reverse();
+ if (autoParams)
+ document.querySelector(".item.auto-params > input").checked = true;
+ if (hideMenu)
+ document.querySelector(".top-btn.menu")?.classList.add("hidden");
+ if (noMultiEmbedsOption)
+ document.querySelector(".box .item.multi")?.remove();
+ if (inIframe)
+ // Remove menu options that don"t work in iframe.
+ for (const e of document.querySelectorAll(".no-frame"))
+ e.remove();
+
+ if (autoUpdateURL) {
+ document.body.classList.add("autoUpdateURL");
+ document.querySelector(".item.auto > input").checked = true;
+ }
+
+ if (single) {
+ document.body.classList.add("single");
+ if (autoParams)
+ single ? urlOptions({
+ set: ["single", ""]
+ }) : urlOptions({
+ remove: "single"
+ });
+ }
+
+ if (hideEditor) {
+ document.body.classList.add("no-editor");
+ document.querySelector(".toggle .toggles .editor input").checked = false;
+ }
+
+ if (hidePreview) {
+ document.body.classList.add("no-preview");
+ document.querySelector(".toggle .toggles .preview input").checked = false;
+ }
+
+ if (onlyEmbed) document.body.classList.add("only-embed");
+ else {
+ document.querySelector(".side1.noDisplay")?.classList.remove("noDisplay");
+ if (useJsonEditor)
+ document.body.classList.remove("gui");
+ }
+
+ if (noUser) {
+ document.body.classList.add("no-user");
+ if (autoParams)
+ noUser ? urlOptions({
+ set: ["nouser", ""]
+ }) : urlOptions({
+ remove: "nouser"
+ });
+ } else {
+ if (username) document.querySelector(".username").textContent = username;
+ if (avatar) document.querySelector(".avatar").src = avatar;
+ if (verified) document.querySelector(".msgEmbed > .contents").classList.add("verified");
+ }
+
+ for (const e of document.querySelectorAll(".clickable > img"))
+ e.parentElement.addEventListener("mouseup", el => window.open(el.target.src));
+
+ const editorHolder = document.querySelector(".editorHolder"),
+ guiParent = document.querySelector(".top"),
+ embedContent = document.querySelector(".messageContent"),
+ embedCont = document.querySelector(".msgEmbed>.container"),
+ gui = guiParent.querySelector(".gui:first-of-type");
+
+ editor = CodeMirror(elt => editorHolder.parentNode.replaceChild(elt, editorHolder), {
+ value: JSON.stringify(json, null, 4),
+ gutters: ["CodeMirror-foldgutter", "CodeMirror-lint-markers"],
+ scrollbarStyle: "overlay",
+ mode: "application/json",
+ theme: "material-darker",
+ matchBrackets: true,
+ foldGutter: true,
+ lint: true,
+ extraKeys: {
+ // Fill in indent spaces on a new line when enter (return) key is pressed.
+ Enter: _ => {
+ const cursor = editor.getCursor();
+ const end = editor.getLine(cursor.line);
+ const leadingSpaces = end.replace(/\S($|.)+/g, "") || " \n";
+ const nextLine = editor.getLine(cursor.line + 1);
+
+ if ((nextLine === undefined || !nextLine.trim()) && !end.substr(cursor.ch).trim())
+ editor.replaceRange("\n", {
+ line: cursor.line,
+ ch: cursor.ch
+ });
+ else
+ editor.replaceRange(`\n${end.endsWith("{") ? leadingSpaces + " " : leadingSpaces}`, {
+ line: cursor.line,
+ ch: cursor.ch
+ });
+ },
+ }
+ });
+
+ editor.focus();
+
+ const notif = document.querySelector(".notification");
+
+ error = (msg, time = "5s") => {
+ notif.innerHTML = msg;
+ notif.style.removeProperty("--startY");
+ notif.style.removeProperty("--startOpacity");
+ notif.style.setProperty("--time", time);
+ notif.onanimationend = () => notif.style.display = null;
+
+ // If notification element is not already visible, (no other message is already displayed), display it.
+ if (!notif.style.display)
+ return notif.style.display = "block", false;
+
+ // If there"s a message already displayed, update it and delay animating out.
+ notif.style.setProperty("--startY", 0);
+ notif.style.setProperty("--startOpacity", 1);
+ notif.style.display = null;
+ setTimeout(() => notif.style.display = "block", .5);
+
+ return false;
+ };
+
+ const url = (url) => /^(https?:)?\/\//g.exec(url) ? url : "//" + url;
+
+ const makeShort = (txt, length, mediaWidth) => {
+ if (mediaWidth && matchMedia(`(max-width:${mediaWidth}px)`).matches)
+ return txt.length > (length - 3) ? txt.substring(0, length - 3) + "..." : txt;
+ return txt;
+ }
+
+ const allGood = embedObj => {
+ let invalid, err;
+ let str = JSON.stringify(embedObj, null, 4)
+ let re = /("(?:icon_)?url": *")((?!\w+?:\/\/).+)"/g.exec(str);
+
+ if (embedObj.timestamp && new Date(embedObj.timestamp).toString() === "Invalid Date") {
+ if (allowPlaceholders === 2) return true;
+ if (!allowPlaceholders) invalid = true, err = "Timestamp is invalid";
+ } else if (re) { // If a URL is found without a protocol
+ if (!/\w+:|\/\/|^\//g.exec(re[2]) && re[2].includes(".")) {
+ let activeInput = document.querySelector("input[class$='link' i]:focus")
+ if (activeInput && !allowPlaceholders) {
+ lastPos = activeInput.selectionStart + 7;
+ activeInput.value = `http://${re[2]}`;
+ activeInput.setSelectionRange(lastPos, lastPos)
+ return true;
+ }
+ }
+ if (allowPlaceholders !== 2)
+ invalid = true, err = (`URL should have a protocol. Did you mean http://${makeShort(re[2], 30, 600).replace(" ", "")}?`);
+ }
+
+ if (invalid) {
+ validationError = true;
+ return error(err);
+ }
+
+ return true;
+ }
+
+ const markup = (txt, {
+ replaceEmojis,
+ inlineBlock,
+ inEmbed
+ }) => {
+ if (replaceEmojis)
+ txt = txt.replace(/(?[^>]+)(? p && emojis[p] ? emojis[p] : match);
+
+ txt = txt
+ /** Markdown */
+ .replace(/<:\w+:(\d{17,19})>/g, "")
+ .replace(/<a:\w+:(\d{17,20})>/g, "")
+ .replace(/~~(.+?)~~/g, "${match1 || match2 || newLine ? match1 || match2 : all.replace(/^ *> /gm, "")}
${x}
` : y ? `${y}
` : z ? `${z}
` : m);
+ else {
+ // Code block
+ txt = txt.replace(/```(?:([a-z0-9_+\-.]+?)\n)?\n*([^\n][^]*?)\n*```/ig, (m, w, x) => {
+ if (w) return `${x.trim()}
`
+ else return `${x.trim()}
`
+ });
+ // Inline code
+ txt = txt.replace(/`([^`]+?)`|``([^`]+?)``/g, (m, x, y, z) => x ? `${x}
` : y ? `${y}
` : z ? `${z}
` : m)
+ }
+
+ if (inEmbed)
+ txt = txt.replace(/\[([^\[\]]+)\]\((.+?)\)/g, `$1`);
+
+ return txt;
+ }
+
+
+ const createEmbedFields = (fields, embedFields) => {
+ embedFields.innerHTML = "";
+ let index, gridCol;
+
+ for (const [i, f] of fields.entries()) {
+ if (f.name && f.value) {
+ const fieldElement = embedFields.insertBefore(document.createElement("div"), null);
+ // Figuring out if there are only two fields on a row to give them more space.
+ // e.fields = json.embeds.fields.
+
+ // if both the field of index "i" and the next field on it"s right are inline and -
+ if (fields[i].inline && fields[i + 1]?.inline &&
+ // it"s the first field in the embed or -
+ ((i === 0 && fields[i + 2] && !fields[i + 2].inline) || ((
+ // it"s not the first field in the embed but the previous field is not inline or -
+ i > 0 && !fields[i - 1].inline ||
+ // it has 3 or more fields behind it and 3 of those are inline except the 4th one back if it exists -
+ i >= 3 && fields[i - 1].inline && fields[i - 2].inline && fields[i - 3].inline && (fields[i - 4] ? !fields[i - 4].inline : !fields[i - 4])
+ // or it"s the first field on the last row or the last field on the last row is not inline or it"s the first field in a row and it"s the last field on the last row.
+ ) && (i == fields.length - 2 || !fields[i + 2].inline))) || i % 3 === 0 && i == fields.length - 2) {
+ // then make the field halfway (and the next field will take the other half of the embed).
+ index = i, gridCol = "1 / 7";
+ }
+ // The next field.
+ if (index === i - 1)
+ gridCol = "7 / 13";
+
+ if (!f.inline)
+ fieldElement.outerHTML = `
+ `;
+ else {
+ if (i && !fields[i - 1].inline) colNum = 1;
+
+ fieldElement.outerHTML = `
+ `;
+
+ if (index !== i) gridCol = false;
+ }
+
+ colNum = (colNum === 9 ? 1 : colNum + 4);
+ num++;
+ };
+ };
+
+
+ for (const e of document.querySelectorAll(".embedField[style=\"grid-column: 1 / 5;\"]"))
+ if (!e.nextElementSibling || e.nextElementSibling.style.gridColumn === "1 / 13")
+ e.style.gridColumn = "1 / 13";
+ colNum = 1;
+
+ display(embedFields, undefined, "grid");
+ }
+
+ const smallerScreen = matchMedia("(max-width: 1015px)");
+
+ const encodeHTML = str => str.replace(/[\u00A0-\u9999<>\&]/g, i => "" + i.charCodeAt(0) + ";");
+
+ const timestamp = stringISO => {
+ const date = stringISO ? new Date(stringISO) : new Date(),
+ dateArray = date.toLocaleString("en-US", {
+ hour: "numeric",
+ hour12: false,
+ minute: "numeric"
+ }),
+ today = new Date(),
+ yesterday = new Date(new Date().setDate(today.getDate() - 1)),
+ tommorrow = new Date(new Date().setDate(today.getDate() + 1));
+
+ return today.toDateString() === date.toDateString() ? `Today at ${dateArray}` :
+ yesterday.toDateString() === date.toDateString() ? `Yesterday at ${dateArray}` :
+ tommorrow.toDateString() === date.toDateString() ? `Tomorrow at ${dateArray}` :
+ `${String(date.getMonth() + 1).padStart(2, "0")}/${String(date.getDate()).padStart(2, "0")}/${date.getFullYear()}`;
+ }
+
+ const display = (el, data, displayType) => {
+ if (data) el.innerHTML = data;
+ el.style.display = displayType || "unset";
+ }
+
+ const hide = el => el.style.removeProperty("display"),
+ imgSrc = (elm, src, remove) => remove ? elm.style.removeProperty("content") : elm.style.content = `url(${src})`;
+
+ const [guiFragment, fieldFragment, embedFragment, guiEmbedAddFragment] = Array.from({
+ length: 4
+ }, () => document.createDocumentFragment());
+ embedFragment.appendChild(document.querySelector(".embed.markup").cloneNode(true));
+ guiEmbedAddFragment.appendChild(document.querySelector(".guiEmbedAdd").cloneNode(true));
+ fieldFragment.appendChild(document.querySelector(".edit>.fields>.field").cloneNode(true));
+
+ document.querySelector(".embed.markup").remove();
+ gui.querySelector(".edit>.fields>.field").remove();
+
+ for (const child of gui.childNodes)
+ guiFragment.appendChild(child.cloneNode(true));
+
+ // Renders the GUI editor with json data from "jsonObject".
+ buildGui = (object = jsonObject, opts) => {
+ gui.innerHTML = "";
+ gui.appendChild(guiEmbedAddFragment.firstChild.cloneNode(true))
+ .addEventListener("click", () => {
+ if (indexOfEmptyGuiEmbed("(empty embed)") !== -1) return;
+ jsonObject.embeds.push({});
+ buildGui();
+ });
+
+ for (const child of Array.from(guiFragment.childNodes)) {
+ if (child.classList?.[1] === "content")
+ gui.insertBefore(gui.appendChild(child.cloneNode(true)), gui.appendChild(child.nextElementSibling.cloneNode(true))).nextElementSibling.firstElementChild.value = object.content || "";
+ else if (child.classList?.[1] === "guiEmbedName") {
+ for (const [i, embed] of(object.embeds.length ? object.embeds : [{}]).entries()) {
+ const guiEmbedName = gui.appendChild(child.cloneNode(true))
+
+ guiEmbedName.querySelector(".text").innerHTML = `Embed ${i + 1}${embed.title ? `: ${embed.title}` : ""}`;
+ guiEmbedName.querySelector(".icon").addEventListener("click", () => {
+ object.embeds.splice(i, 1);
+ buildGui();
+ buildEmbed();
+ });
+
+ const guiEmbed = gui.appendChild(createElement({
+ "div": {
+ className: "guiEmbed"
+ }
+ }));
+ const guiEmbedTemplate = child.nextElementSibling;
+
+ for (const child2 of Array.from(guiEmbedTemplate.children)) {
+ if (!child2?.classList.contains("edit")) {
+ const row = guiEmbed.appendChild(child2.cloneNode(true));
+ const edit = child2.nextElementSibling?.cloneNode(true);
+ edit?.classList.contains("edit") && guiEmbed.appendChild(edit);
+
+ switch (child2.classList[1]) {
+ case "author":
+ const authorURL = embed?.author?.icon_url || "";
+ if (authorURL)
+ edit.querySelector(".imgParent").style.content = "url(" + encodeHTML(authorURL) + ")";
+ edit.querySelector(".editAuthorLink").value = authorURL;
+ edit.querySelector(".editAuthorName").value = embed?.author?.name || "";
+ break;
+ case "title":
+ row.querySelector(".editTitle").value = embed?.title || "";
+ break;
+ case "description":
+ edit.querySelector(".editDescription").value = embed?.description || "";
+ break;
+ case "thumbnail":
+ const thumbnailURL = embed?.thumbnail?.url || "";
+ if (thumbnailURL)
+ edit.querySelector(".imgParent").style.content = "url(" + encodeHTML(thumbnailURL) + ")";
+ edit.querySelector(".editThumbnailLink").value = thumbnailURL;
+ break;
+ case "image":
+ const imageURL = embed?.image?.url || "";
+ if (imageURL)
+ edit.querySelector(".imgParent").style.content = "url(" + encodeHTML(imageURL) + ")";
+ edit.querySelector(".editImageLink").value = imageURL;
+ break;
+ case "footer":
+ const footerURL = embed?.footer?.icon_url || "";
+ if (footerURL)
+ edit.querySelector(".imgParent").style.content = "url(" + encodeHTML(footerURL) + ")";
+ edit.querySelector(".editFooterLink").value = footerURL;
+ edit.querySelector(".editFooterText").value = embed?.footer?.text || "";
+ break;
+ case "fields":
+ for (const f of embed?.fields || []) {
+ const fields = edit.querySelector(".fields");
+ const field = fields.appendChild(createElement({
+ "div": {
+ className: "field"
+ }
+ }));
+
+ for (const child of Array.from(fieldFragment.firstChild.children)) {
+ const newChild = field.appendChild(child.cloneNode(true));
+
+ if (child.classList.contains("inlineCheck"))
+ newChild.querySelector("input").checked = !!f.inline;
+
+ else if (f.value && child.classList?.contains("fieldInner"))
+ newChild.querySelector(".designerFieldName input").value = f.name || "",
+ newChild.querySelector(".designerFieldValue textarea").value = f.value || "";
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+
+ // Expand last embed in GUI
+ const names = gui.querySelectorAll(".guiEmbedName");
+ names[names.length - 1]?.classList.add("active");
+ }
+
+ for (const e of document.querySelectorAll(".top>.gui .item"))
+ e.addEventListener("click", el => {
+ if (e?.classList.contains("active"))
+ getSelection().anchorNode !== e && e.classList.remove("active");
+ else if (e) {
+ const inlineField = e.closest(".inlineField"),
+ input = e.nextElementSibling?.querySelector("input[type=\"text\"]"),
+ txt = e.nextElementSibling?.querySelector("textarea");
+
+ e.classList.add("active");
+ if (e.classList.contains("guiEmbedName"))
+ return changeLastActiveGuiEmbed(guiEmbedIndex(e));
+
+ else if (inlineField)
+ inlineField.querySelector(".ttle~input").focus();
+
+ else if (e.classList.contains("footer")) {
+ const date = new Date(jsonObject.embeds[guiEmbedIndex(e)]?.timestamp || new Date());
+ const textElement = e.nextElementSibling.querySelector("svg>text");
+ const dateInput = textElement.closest(".footerDate").querySelector("input");
+
+ return (
+ textElement.textContent = (date.getDate() + "").padStart(2, 0),
+ dateInput.value = date.toISOString().substring(0, 19)
+ );
+ } else if (input) {
+ !smallerScreen.matches && input.focus();
+ input.selectionStart = input.selectionEnd = input.value.length;
+ } else if (txt && !smallerScreen.matches)
+ txt.focus();
+
+ if (e.classList.contains("fields")) {
+ if (reverseColumns && smallerScreen.matches)
+ // return elm.nextElementSibling.scrollIntoView({ behavior: "smooth", block: "end" });
+ return e.parentNode.scrollTop = e.offsetTop;
+
+ e.scrollIntoView({
+ behavior: "smooth",
+ block: "center"
+ });
+ }
+ }
+ })
+
+ content = gui.querySelector(".editContent");
+ title = gui.querySelector(".editTitle");
+ authorName = gui.querySelector(".editAuthorName");
+ authorLink = gui.querySelector(".editAuthorLink");
+ desc = gui.querySelector(".editDescription");
+ thumbLink = gui.querySelector(".editThumbnailLink");
+ imgLink = gui.querySelector(".editImageLink");
+ footerText = gui.querySelector(".editFooterText");
+ footerLink = gui.querySelector(".editFooterLink");
+
+ // Scroll into view when tabs are opened in the GUI.
+ const lastTabs = Array.from(document.querySelectorAll(".footer.rows2, .image.largeImg"));
+ const requiresView = matchMedia(`${smallerScreen.media}, (max-height: 845px)`);
+ const addGuiEventListeners = () => {
+ for (const e of document.querySelectorAll(".gui .item:not(.fields)"))
+ e.onclick = () => {
+ if (lastTabs.includes(e) || requiresView.matches) {
+ if (!reverseColumns || !smallerScreen.matches)
+ e.scrollIntoView({
+ behavior: "smooth",
+ block: "center"
+ });
+ else if (e.nextElementSibling.classList.contains("edit") && e.classList.contains("active"))
+ // e.nextElementSibling.scrollIntoView({ behavior: "smooth", block: "end" });
+ e.parentNode.scrollTop = e.offsetTop;
+ }
+ };
+
+ for (const e of document.querySelectorAll(".addField"))
+ e.onclick = () => {
+ const guiEmbed = e.closest(".guiEmbed");
+ const indexOfGuiEmbed = Array.from(gui.querySelectorAll(".guiEmbed")).indexOf(guiEmbed);
+ if (indexOfGuiEmbed === -1) return error("Could not find the embed to add the field to.");
+
+ const fieldsObj = (jsonObject.embeds[indexOfGuiEmbed] ??= {}).fields ??= [];
+ if (fieldsObj.length >= 25) return error("Cannot have more than 25 fields");
+ fieldsObj.push({
+ name: "Field name",
+ value: "Field value",
+ inline: false
+ });
+
+ const newField = guiEmbed?.querySelector(".item.fields+.edit>.fields")?.appendChild(fieldFragment.firstChild.cloneNode(true));
+
+ buildEmbed();
+ addGuiEventListeners();
+
+ newField.scrollIntoView({
+ behavior: "smooth",
+ block: "center"
+ });
+ if (!smallerScreen.matches) {
+ const firstFieldInput = newField.querySelector(".designerFieldName input");
+
+ firstFieldInput?.setSelectionRange(firstFieldInput.value.length, firstFieldInput.value.length);
+ firstFieldInput?.focus();
+ }
+ };
+
+ for (const e of document.querySelectorAll(".fields .field .removeBtn"))
+ e.onclick = () => {
+ const embedIndex = guiEmbedIndex(e);
+ const fieldIndex = Array.from(e.closest(".fields").children).indexOf(e.closest(".field"));
+
+ if (jsonObject.embeds[embedIndex]?.fields[fieldIndex] === -1)
+ return error("Failed to find the index of the field to remove.");
+
+ jsonObject.embeds[embedIndex].fields.splice(fieldIndex, 1);
+
+ buildEmbed();
+ e.closest(".field").remove();
+ };
+
+ for (const e of gui.querySelectorAll("textarea, input"))
+ e.oninput = el => {
+ const value = el.target.value;
+ const index = guiEmbedIndex(el.target);
+ const field = el.target.closest(".field");
+ const fields = field?.closest(".fields");
+ const embedObj = jsonObject.embeds[index] ??= {};
+
+ if (field) {
+ console.log(field)
+ const fieldIndex = Array.from(fields.children).indexOf(field);
+ const jsonField = embedObj.fields[fieldIndex];
+ const embedFields = document.querySelectorAll(".container>.embed")[index]?.querySelector(".embedFields");
+
+ if (jsonField) {
+ if (el.target.type === "text") jsonField.name = value;
+ else if (el.target.type === "textarea") jsonField.value = value;
+ else jsonField.inline = el.target.checked;
+ createEmbedFields(embedObj.fields, embedFields);
+ }
+ } else {
+ switch (el.target.classList?.[0]) {
+ case "editContent":
+ jsonObject.content = value;
+ buildEmbed({
+ only: "content"
+ });
+ break;
+ case "editTitle":
+ embedObj.title = value;
+ const guiEmbedName = el.target.closest(".guiEmbed")?.previousElementSibling;
+ if (guiEmbedName?.classList.contains("guiEmbedName"))
+ guiEmbedName.querySelector(".text").innerHTML = `${guiEmbedName.innerText.split(":")[0]}${value ? `: ${value}` : ""}`;
+ buildEmbed({
+ only: "embedTitle",
+ index: guiEmbedIndex(el.target)
+ });
+ break;
+ case "editAuthorName":
+ embedObj.author ??= {}, embedObj.author.name = value;
+ buildEmbed({
+ only: "embedAuthorName",
+ index: guiEmbedIndex(el.target)
+ });
+ break;
+ case "editAuthorLink":
+ embedObj.author ??= {}, embedObj.author.icon_url = value;
+ imgSrc(el.target.previousElementSibling, value);
+ buildEmbed({
+ only: "embedAuthorLink",
+ index: guiEmbedIndex(el.target)
+ });
+ break;
+ case "editDescription":
+ embedObj.description = value;
+ buildEmbed({
+ only: "embedDescription",
+ index: guiEmbedIndex(el.target)
+ });
+ break;
+ case "editThumbnailLink":
+ embedObj.thumbnail ??= {}, embedObj.thumbnail.url = value;
+ imgSrc(el.target.closest(".editIcon").querySelector(".imgParent"), value);
+ buildEmbed({
+ only: "embedThumbnail",
+ index: guiEmbedIndex(el.target)
+ });
+ break;
+ case "editImageLink":
+ embedObj.image ??= {}, embedObj.image.url = value;
+ imgSrc(el.target.closest(".editIcon").querySelector(".imgParent"), value);
+ buildEmbed({
+ only: "embedImageLink",
+ index: guiEmbedIndex(el.target)
+ });
+ break;
+ case "editFooterText":
+ embedObj.footer ??= {}, embedObj.footer.text = value;
+ buildEmbed({
+ only: "embedFooterText",
+ index: guiEmbedIndex(el.target)
+ });
+ break;
+ case "editFooterLink":
+ embedObj.footer ??= {}, embedObj.footer.icon_url = value;
+ imgSrc(el.target.previousElementSibling, value);
+ buildEmbed({
+ only: "embedFooterLink",
+ index: guiEmbedIndex(el.target)
+ });
+ break;
+ case "embedFooterTimestamp":
+ const date = new Date(value);
+ if (isNaN(date.getTime())) return error("Invalid date");
+
+ embedObj.timestamp = date;
+ el.target.parentElement.querySelector("svg>text").textContent = (date.getDate() + "").padStart(2, 0);
+ buildEmbed({
+ only: "embedFooterTimestamp",
+ index: guiEmbedIndex(el.target)
+ });
+ break;
+ }
+
+ // Find and filter out any empty objects ({}) in the embeds array as Discord doesn"t like them.
+ const nonEmptyEmbedObjects = json.embeds?.filter(o => 0 in Object.keys(o));
+ if (nonEmptyEmbedObjects?.length)
+ json.embeds = nonEmptyEmbedObjects;
+ }
+
+ // Display embed elements hidden due to not having content. ".msgEmbed>.container" is embed container.
+ document.querySelectorAll(".msgEmbed>.container")[guiEmbedIndex(el.target)]?.querySelector(".emptyEmbed")?.classList.remove("emptyEmbed");
+ }
+
+ const uploadError = (message, browse, sleepTime) => {
+ browse.classList.remove("loading");
+ browse.classList.add("error");
+
+ const p = browse.parentElement.querySelector(".browse.error>p")
+ p.dataset.error = message;
+
+ setTimeout(() => {
+ browse.classList.remove("error");
+ delete p.dataset.error;
+ }, sleepTime ?? 7000);
+ }
+
+ for (const browse of document.querySelectorAll(".browse"))
+ browse.onclick = e => {
+ const formData = new FormData();
+ const fileInput = createElement({
+ "input": {
+ type: "file",
+ accept: "image/*"
+ }
+ });
+ const edit = browse.closest(".edit");
+ const expiration = 7 * 24 * 60 * 60;
+
+ fileInput.onchange = el => {
+ if (el.target.files[0].size > 32 * 1024 * 1024)
+ return uploadError("File is too large. Maximum size is 32 MB.", browse, 5000);
+
+ formData.append("expiration", expiration); // Expire after 7 days. Discord caches files.
+ formData.append("key", options.uploadKey || "93385e22b0619db73a5525140b13491c"); // Add your own key through the uploadKey option.
+ formData.append("image", el.target.files[0]);
+ // formData.append("name", ""); // Uses original file name if no "name" is not specified.
+
+ browse.classList.add("loading");
+
+ fetch("https://api.imgbb.com/1/upload", {
+ method: "POST",
+ body: formData
+ })
+ .then(res => res.json())
+ .then(res => {
+ browse.classList.remove("loading");
+ if (!res.success) {
+ console.log("Upload failed:", res.data?.error || res.error?.message || res);
+ return uploadError(res.data?.error || res.error?.message || "Request failed. (Check dev-console)", browse);
+ }
+
+ imgSrc(edit.querySelector(".editIcon > .imgParent"), res.data.url);
+ const linkInput = edit.querySelector("input[type=text]");
+ const textInput = edit.querySelector("input[class$=Name], input[class$=Text]");
+
+ linkInput.value = res.data.url;
+ // focus on the next empty input if the field requires a name or text to display eg. footer or author.
+ !textInput?.value && textInput?.focus();
+
+ console.info(`${res.data.url} will be deleted in ${expiration / 60 / 60} hours. To delete it now, visit ${res.data.delete_url} and scroll down to find the delete button.`);
+
+ linkInput.dispatchEvent(new Event("input"));
+ }).catch(err => {
+ browse.classList.remove("loading");
+ error(`Request failed with error: ${err}`)
+ })
+ }
+
+ fileInput.click();
+ }
+
+ if (multiEmbeds) {
+ for (const e of document.querySelectorAll(".guiEmbed"))
+ e.onclick = () => {
+ const guiEmbed = e.closest(".guiEmbed");
+ const indexOfGuiEmbed = Array.from(gui.querySelectorAll(".guiEmbed")).indexOf(guiEmbed);
+ if (indexOfGuiEmbed === -1) return error("Could not find the embed to add the field to.");
+
+ changeLastActiveGuiEmbed(indexOfGuiEmbed);
+ };
+
+
+ if (!jsonObject.embeds[lastActiveGuiEmbedIndex])
+ changeLastActiveGuiEmbed(
+ jsonObject.embeds[lastActiveGuiEmbedIndex - 1] ?
+ lastActiveGuiEmbedIndex - 1 :
+ jsonObject.embeds.length ? 0 : -1
+ );
+ } else {
+ changeLastActiveGuiEmbed(-1);
+ }
+ }
+
+ addGuiEventListeners();
+
+ let activeGuiEmbed;
+
+ if (opts?.guiEmbedIndex) {
+ activeGuiEmbed = Array.from(document.querySelectorAll(".gui .item.guiEmbedName"))[opts.guiEmbedIndex];
+ activeGuiEmbed?.classList.add("active");
+ activeGuiEmbed = activeGuiEmbed?.nextElementSibling;
+ }
+
+
+ if (opts?.activateClassNames)
+ for (const cName of opts.activateClassNames)
+ for (const e of document.getElementsByClassName(cName))
+ e.classList.add("active");
+
+ else if (opts?.guiTabs) {
+ const tabs = opts.guiTabs.split?.(/, */) || opts.guiTabs;
+ const bottomKeys = ["footer", "image"];
+ const topKeys = ["author", "content"];
+
+
+ // Deactivate the default activated GUI fields
+ for (const e of gui.querySelectorAll(".item:not(.guiEmbedName).active"))
+ e.classList.remove("active");
+
+ // Activate wanted GUI fields
+ for (const e of document.querySelectorAll(`.${tabs.join(", .")}`))
+ e.classList.add("active");
+
+ // Autoscroll GUI to the bottom if necessary.
+ if (!tabs.some(item => topKeys.includes(item)) && tabs.some(item => bottomKeys.includes(item))) {
+ const gui2 = document.querySelector(".top .gui");
+ gui2.scrollTo({
+ top: gui2.scrollHeight
+ });
+ }
+ } else if (opts?.activate)
+ for (const clss of Array.from(opts.activate).map(el => el.className).map(clss => "." + clss.split(" ").slice(0, 2).join(".")))
+ for (const e of document.querySelectorAll(clss))
+ e.classList.add("active");
+
+ else
+ for (const clss of document.querySelectorAll(".item.author, .item.description"))
+ clss.classList.add("active");
+ }
+
+ buildGui(jsonObject, {
+ guiTabs
+ });
+ gui.classList.remove("hidden");
+
+ fields = gui.querySelector(".fields ~ .edit .fields");
+
+ // Renders embed and message content.
+ buildEmbed = ({
+ jsonData,
+ only,
+ index = 0
+ } = {}) => {
+ if (jsonData) json = jsonData;
+ if (!jsonObject.embeds?.length) document.body.classList.add("emptyEmbed");
+
+ try {
+ // If there"s no message content, hide the message content HTML element.
+ if (!jsonObject.content) document.body.classList.add("emptyContent");
+ else {
+ // Update embed content in render
+ embedContent.innerHTML = markup(encodeHTML(jsonObject.content), {
+ replaceEmojis: true
+ });
+ document.body.classList.remove("emptyContent");
+ }
+
+ const embed = document.querySelectorAll(".container>.embed")[index];
+ const embedObj = jsonObject.embeds[index];
+
+ if (only && (!embed || !embedObj)) return buildEmbed();
+
+ switch (only) {
+ // If only updating the message content and nothing else, return here.
+ case "content":
+ return externalParsing({
+ element: embedContent
+ });
+ case "embedTitle":
+ const embedTitle = embed?.querySelector(".embedTitle");
+ if (!embedTitle) return buildEmbed();
+ if (!embedObj.title) hide(embedTitle);
+ else display(embedTitle, markup(`${embedObj.url ? "" + encodeHTML(embedObj.title) + "" : encodeHTML(embedObj.title)}`, {
+ replaceEmojis: true,
+ inlineBlock: true
+ }));
+
+ return externalParsing({
+ element: embedTitle
+ });
+ case "embedAuthorName":
+ case "embedAuthorLink":
+ const embedAuthor = embed?.querySelector(".embedAuthor");
+ if (!embedAuthor) return buildEmbed();
+ if (!embedObj.author?.name) hide(embedAuthor);
+ else display(embedAuthor, `
+ ${embedObj.author.icon_url ? "" : ""}
+ ${embedObj.author.url ? "" + encodeHTML(embedObj.author.name) + "" : " "}`, "flex");
+
+ return externalParsing({
+ element: embedAuthor
+ });
+ case "embedDescription":
+ const embedDescription = embed?.querySelector(".embedDescription");
+ if (!embedDescription) return buildEmbed();
+ if (!embedObj.description) hide(embedDescription);
+ else display(embedDescription, markup(encodeHTML(embedObj.description), {
+ inEmbed: true,
+ replaceEmojis: true
+ }));
+
+ return externalParsing({
+ element: embedDescription
+ });
+ case "embedThumbnail":
+ const embedThumbnailLink = embed?.querySelector(".embedThumbnailLink");
+ if (!embedThumbnailLink) return buildEmbed();
+ const pre = embed.querySelector(".embedGrid .markup pre");
+ if (embedObj.thumbnail?.url) {
+ embedThumbnailLink.src = embedObj.thumbnail.url;
+ embedThumbnailLink.parentElement.style.display = "block";
+ if (pre) pre.style.maxWidth = "90%";
+ } else {
+ hide(embedThumbnailLink.parentElement);
+ pre?.style.removeProperty("max-width");
+ }
+
+ return afterBuilding();
+ case "embedImage":
+ const embedImageLink = embed?.querySelector(".embedImageLink");
+ if (!embedImageLink) return buildEmbed();
+ if (!embedObj.image?.url) hide(embedImageLink.parentElement);
+ else embedImageLink.src = embedObj.image.url,
+ embedImageLink.parentElement.style.display = "block";
+
+ return afterBuilding();
+ case "embedFooterText":
+ case "embedFooterLink":
+ case "embedFooterTimestamp":
+ const embedFooter = embed?.querySelector(".embedFooter");
+ if (!embedFooter) return buildEmbed();
+ if (!embedObj.footer?.text) hide(embedFooter);
+ else display(embedFooter, `
+ ${embedObj.footer.icon_url ? "" : ""} `, "flex");
+
+ return externalParsing({
+ element: embedFooter
+ });
+ }
+
+ if (multiEmbeds) embedCont.innerHTML = "";
+
+ for (const embedObj of jsonObject.embeds) {
+ if (!allGood(embedObj)) continue;
+ if (!multiEmbeds) embedCont.innerHTML = "";
+
+ validationError = false;
+
+ const embedElement = embedCont.appendChild(embedFragment.firstChild.cloneNode(true));
+ const embedGrid = embedElement.querySelector(".embedGrid");
+ const msgEmbed = embedElement.querySelector(".msgEmbed");
+ const embedTitle = embedElement.querySelector(".embedTitle");
+ const embedDescription = embedElement.querySelector(".embedDescription");
+ const embedAuthor = embedElement.querySelector(".embedAuthor");
+ const embedFooter = embedElement.querySelector(".embedFooter");
+ const embedImage = embedElement.querySelector(".embedImage > img");
+ const embedThumbnail = embedElement.querySelector(".embedThumbnail > img");
+ const embedFields = embedElement.querySelector(".embedFields");
+
+ if (embedObj.title) display(embedTitle, markup(`${embedObj.url ? "" + encodeHTML(embedObj.title) + "" : encodeHTML(embedObj.title)}`, {
+ replaceEmojis: true,
+ inlineBlock: true
+ }));
+ else hide(embedTitle);
+
+ if (embedObj.description) display(embedDescription, markup(encodeHTML(embedObj.description), {
+ inEmbed: true,
+ replaceEmojis: true
+ }));
+ else hide(embedDescription);
+
+ if (embedObj.color) embedGrid.closest(".embed").style.borderColor = (typeof embedObj.color === "number" ? "#" + embedObj.color.toString(16).padStart(6, "0") : embedObj.color);
+ else embedGrid.closest(".embed").style.removeProperty("border-color");
+
+ if (embedObj.author?.name) display(embedAuthor, `
+ ${embedObj.author.icon_url ? "" : ""}
+ ${embedObj.author.url ? "" + encodeHTML(embedObj.author.name) + "" : " "}`, "flex");
+ else hide(embedAuthor);
+
+ const pre = embedGrid.querySelector(".markup pre");
+ if (embedObj.thumbnail?.url) {
+ embedThumbnail.src = embedObj.thumbnail.url;
+ embedThumbnail.parentElement.style.display = "block";
+ if (pre) pre.style.maxWidth = "90%";
+ } else {
+ hide(embedThumbnail.parentElement);
+ if (pre) pre.style.removeProperty("max-width");
+ }
+
+ if (embedObj.image?.url)
+ embedImage.src = embedObj.image.url,
+ embedImage.parentElement.style.display = "block";
+ else hide(embedImage.parentElement);
+
+ if (embedObj.footer?.text) display(embedFooter, `
+ ${embedObj.footer.icon_url ? "" : ""} `, "flex");
+ else if (embedObj.timestamp) display(embedFooter, ` `, "flex");
+ else hide(embedFooter);
+
+ if (embedObj.fields) createEmbedFields(embedObj.fields, embedFields);
+ else hide(embedFields);
+
+ document.body.classList.remove("emptyEmbed");
+ externalParsing();
+
+ if (embedElement.innerText.trim() || embedElement.querySelector(".embedGrid > [style*=display] img"))
+ embedElement.classList.remove("emptyEmbed");
+ else
+ embedElement.classList.add("emptyEmbed");
+ }
+
+ // Make sure that the embed has no text or any visible images such as custom emojis before hiding.
+ if (!multiEmbeds && !embedCont.innerText.trim() && !embedCont.querySelector(".embedGrid > [style*=display] img"))
+ document.body.classList.add("emptyEmbed");
+
+ afterBuilding()
+ } catch (e) {
+ console.error(e);
+ error(e);
+ }
+ window.parent.postMessage([sendMessage, json], "*");
+ console.log("UPDATED")
+ }
+
+ editor.on("change", editor => {
+ // If the editor value is not set by the user, return.
+ if (JSON.stringify(json, null, 4) === editor.getValue()) return;
+
+ try {
+ // Autofill when " is typed on new line
+ const line = editor.getCursor().line;
+ const text = editor.getLine(line)
+
+ if (text.trim() === "\"") {
+ editor.replaceRange(text.trim() + ":", {
+ line,
+ ch: line.length
+ });
+ editor.setCursor(line, text.length)
+ }
+
+ json = JSON.parse(editor.getValue());
+ const dataKeys = Object.keys(json);
+
+ if (dataKeys.length && !allJsonKeys.some(key => dataKeys.includes(key))) {
+ const usedKeys = dataKeys.filter(key => !allJsonKeys.includes(key));
+ if (usedKeys.length > 2)
+ return error(`'${usedKeys[0] + "', '" + usedKeys.slice(1, usedKeys.length - 1).join("', '")}', and '${usedKeys[usedKeys.length - 1]}' are invalid keys.`);
+ return error(`'${usedKeys.length == 2 ? usedKeys[0] + "' and '" + usedKeys[usedKeys.length - 1] + "' are invalid keys." : usedKeys[0] + "' is an invalid key."}`);
+ }
+
+ buildEmbed();
+
+ } catch (e) {
+ if (editor.getValue()) return;
+ document.body.classList.add("emptyEmbed");
+ embedContent.innerHTML = "";
+ }
+ });
+
+ const picker = new CP(document.querySelector(".picker"), state = {
+ parent: document.querySelector(".cTop")
+ });
+
+ picker.fire?.("change", toRGB("#41f097"));
+
+ const colors = document.querySelector(".colors");
+ const hexInput = colors?.querySelector(".hex>div input");
+
+ let typingHex = true,
+ exit = false;
+
+ removePicker = () => {
+ if (exit) return exit = false;
+ if (typingHex) picker.enter();
+ else {
+ typingHex = false, exit = true;
+ colors.classList.remove("picking");
+ picker.exit();
+ }
+ }
+
+ document.querySelector(".colBack")?.addEventListener("click", () => {
+ picker.self.remove();
+ typingHex = false;
+ removePicker();
+ })
+
+ picker.on?.("exit", removePicker);
+ picker.on?.("enter", () => {
+ const embedIndex = multiEmbeds && lastActiveGuiEmbedIndex !== -1 ? lastActiveGuiEmbedIndex : 0;
+ if (jsonObject?.embeds[embedIndex]?.color) {
+ hexInput.value = jsonObject.embeds[embedIndex].color.toString(16).padStart(6, "0");
+ document.querySelector(".hex.incorrect")?.classList.remove("incorrect");
+ }
+ colors.classList.add("picking")
+ })
+
+ document.querySelectorAll(".color").forEach(e => e.addEventListener("click", el => {
+ const embedIndex = multiEmbeds && lastActiveGuiEmbedIndex !== -1 ? lastActiveGuiEmbedIndex : 0;
+ const embed = document.querySelectorAll(".msgEmbed .container>.embed")[embedIndex];
+ const embedObj = jsonObject.embeds[embedIndex] ??= {};
+ const color = el.target.closest(".color");
+
+ embedObj.color = toRGB(color.style.backgroundColor, false, true);
+ embed && (embed.style.borderColor = color.style.backgroundColor);
+ picker.source.style.removeProperty("background");
+ }))
+
+ hexInput?.addEventListener("focus", () => typingHex = true);
+ setTimeout(() => {
+ picker.on?.("change", function (r, g, b, a) {
+ const embedIndex = multiEmbeds && lastActiveGuiEmbedIndex !== -1 ? lastActiveGuiEmbedIndex : 0;
+ const embed = document.querySelectorAll(".msgEmbed .container>.embed")[embedIndex];
+ const embedObj = jsonObject.embeds[embedIndex];
+
+ picker.source.style.background = this.color(r, g, b);
+ embedObj.color = parseInt(this.color(r, g, b).slice(1), 16);
+ embed.style.borderColor = this.color(r, g, b);
+ hexInput.value = embedObj.color.toString(16).padStart(6, "0");
+ })
+ }, 1000)
+
+ document.querySelector(".timeText").innerText = timestamp();
+
+ for (const block of document.querySelectorAll(".markup pre > code"))
+ hljs.highlightBlock(block);
+
+ document.querySelector(".opt.gui").addEventListener("click", () => {
+ if (lastGuiJson && lastGuiJson !== JSON.stringify(json, null, 4))
+ buildGui();
+
+ lastGuiJson = false
+ activeFields = null;
+
+ document.body.classList.add("gui");
+ if (pickInGuiMode) {
+ pickInGuiMode = false;
+ togglePicker();
+ }
+ })
+
+ document.querySelector(".opt.json").addEventListener("click", () => {
+ const emptyEmbedIndex = indexOfEmptyGuiEmbed(false);
+ if (emptyEmbedIndex !== -1)
+ // Clicked GUI tab while a blank embed is added from GUI.
+ return error(gui.querySelectorAll(".item.guiEmbedName")[emptyEmbedIndex].innerText.split(":")[0] + " should not be empty.", "3s");
+
+ const jsonStr = JSON.stringify(json, null, 4);
+ lastGuiJson = jsonStr;
+
+ document.body.classList.remove("gui");
+ editor.setValue(jsonStr === "{}" ? "{\n\t\n}" : jsonStr);
+ editor.refresh();
+ editor.focus();
+
+ activeFields = document.querySelectorAll(".gui > .item.active");
+ if (document.querySelector("section.side1.low"))
+ togglePicker(true);
+ })
+
+ document.querySelector(".clear").addEventListener("click", () => {
+ json = {};
+
+ picker.source.style.removeProperty("background");
+ document.querySelector(".msgEmbed .container>.embed")?.remove();
+
+ buildEmbed();
+ buildGui();
+
+ const jsonStr = JSON.stringify(json, null, 4);
+ editor.setValue(jsonStr === "{}" ? "{\n\t\n}" : jsonStr);
+
+ for (const e of document.querySelectorAll(".gui .item"))
+ e.classList.add("active");
+
+ if (!smallerScreen.matches)
+ content.focus();
+ })
+
+ document.querySelector(".top-btn.menu")?.addEventListener("click", e => {
+ if (e.target.closest(".item.dataLink")) {
+ const data = encodeJson(json, true).replace(/(? x === "=" ? "" : "&");
+ if (!window.chrome)
+ // With long text inside a "prompt" on Chromium based browsers, some text will be trimmed off and replaced with "...".
+ return prompt("Here\"s the current URL with base64 embed data:", data);
+
+ // So, for the Chromium users, we copy to clipboard instead of showing a prompt.
+ try {
+ // Clipboard API might only work on HTTPS protocol.
+ navigator.clipboard.writeText(data);
+ } catch {
+ const input = document.body.appendChild(document.createElement("input"));
+ input.value = data;
+ input.select();
+ document.setSelectionRange(0, 50000);
+ document.execCommand("copy");
+ document.body.removeChild(input);
+ }
+
+ return alert("Copied to clipboard.");
+ }
+
+ if (e.target.closest(".item.download"))
+ return createElement({
+ a: {
+ download: "embed" + ".json",
+ href: "data:text/json;charset=utf-8," + encodeURIComponent(JSON.stringify(json, null, 4))
+ }
+ }).click();
+
+ const input = e.target.closest(".item")?.querySelector("input");
+ if (input) input.checked = !input.checked;
+
+ if (e.target.closest(".item.auto")) {
+ autoUpdateURL = document.body.classList.toggle("autoUpdateURL");
+ if (autoUpdateURL) localStorage.setItem("autoUpdateURL", true);
+ else localStorage.removeItem("autoUpdateURL");
+ urlOptions({
+ set: ["data", encodeJson(json)]
+ });
+ } else if (e.target.closest(".item.reverse")) {
+ reverse(reverseColumns);
+ reverseColumns = !reverseColumns;
+ toggleStored("reverseColumns");
+ } else if (e.target.closest(".item.noUser")) {
+ if (options.avatar) document.querySelector("img.avatar").src = options.avatar;
+
+ const noUser = document.body.classList.toggle("no-user");
+ if (autoParams) noUser ? urlOptions({
+ set: ["nouser", ""]
+ }) : urlOptions({
+ remove: "nouser"
+ });
+ toggleStored("noUser");
+ } else if (e.target.closest(".item.auto-params")) {
+ if (input.checked) localStorage.setItem("autoParams", true);
+ else localStorage.removeItem("autoParams");
+ autoParams = input.checked;
+ } else if (e.target.closest(".toggles>.item")) {
+ const win = input.closest(".item").classList[2];
+
+ if (input.checked) {
+ document.body.classList.remove(`no-${win}`);
+ localStorage.removeItem(`hide${win}`);
+ } else {
+ document.body.classList.add(`no-${win}`);
+ localStorage.setItem(`hide${win}`, true);
+ }
+ } else if (e.target.closest(".item.multi") && !noMultiEmbedsOption) {
+ multiEmbeds = !document.body.classList.toggle("single");
+ activeFields = document.querySelectorAll(".gui > .item.active");
+
+ if (autoParams) !multiEmbeds ? urlOptions({
+ set: ["single", ""]
+ }) : urlOptions({
+ remove: "single"
+ });
+ if (multiEmbeds) localStorage.setItem("multiEmbeds", true);
+ else {
+ localStorage.removeItem("multiEmbeds");
+ jsonObject.embeds = [jsonObject.embeds?.[0] || {}];
+ }
+
+ buildGui();
+ buildEmbed();
+ editor.setValue(JSON.stringify(json, null, 4));
+ }
+
+ e.target.closest(".top-btn")?.classList.toggle("active")
+ })
+
+ document.querySelectorAll(".img").forEach(e => {
+ if (e.nextElementSibling?.classList.contains("spinner-container"))
+ e.addEventListener("error", el => {
+ el.target.style.removeProperty("display");
+ el.target.nextElementSibling.style.display = "block";
+ })
+ })
+
+ let pickInGuiMode = false;
+ togglePicker = pickLater => {
+ colors.classList.toggle("display");
+ document.querySelector(".side1").classList.toggle("low");
+ if (pickLater) pickInGuiMode = true;
+ };
+
+ document.querySelector(".pickerToggle").addEventListener("click", () => togglePicker());
+ buildEmbed();
+
+ document.body.addEventListener("click", e => {
+ if (e.target.classList.contains("low") || (e.target.classList.contains("top") && colors.classList.contains("display")))
+ togglePicker();
+ })
+
+ // #0070ff, #5865f2
+ document.querySelector(".colors .hex>div")?.addEventListener("input", e => {
+ let inputValue = e.target.value;
+
+ if (inputValue.startsWith("#"))
+ e.target.value = inputValue.slice(1), inputValue = e.target.value;
+ if (inputValue.length !== 6 || !/^[a-zA-Z0-9]{6}$/g.test(inputValue))
+ return e.target.closest(".hex").classList.add("incorrect");
+
+ e.target.closest(".hex").classList.remove("incorrect");
+
+ const embedIndex = multiEmbeds && lastActiveGuiEmbedIndex !== -1 ? lastActiveGuiEmbedIndex : 0;
+ jsonObject.embeds[embedIndex].color = parseInt(inputValue, 16);
+ picker.fire?.("change", toRGB(inputValue));
+
+ buildEmbed();
+ })
+
+ if (onlyEmbed) document.querySelector(".side1")?.remove();
+
+ const menuMore = document.querySelector(".item.section .inner.more");
+ const menuSource = menuMore?.querySelector(".source");
+
+ if (!sourceOption) menuSource.remove();
+ if (menuMore.childElementCount < 2) menuMore?.classList.add("invisible");
+ if (menuMore.parentElement.childElementCount < 1) menuMore?.parentElement.classList.add("invisible");
+
+ document.querySelector(".top-btn.copy").addEventListener("click", e => {
+ const mark = e.target.closest(".top-btn.copy").querySelector(".mark"),
+ jsonData = JSON.stringify(json, null, 4),
+ next = () => {
+ mark?.classList.remove("hidden");
+ mark?.previousElementSibling?.classList.add("hidden");
+
+ setTimeout(() => {
+ mark?.classList.add("hidden");
+ mark?.previousElementSibling?.classList.remove("hidden");
+ }, 1500);
+ }
+
+ if (!navigator.clipboard?.writeText(jsonData).then(next).catch(err => console.log("Could not copy to clipboard: " + err.message))) {
+ const textarea = document.body.appendChild(document.createElement("textarea"));
+
+ textarea.value = jsonData;
+ textarea.select();
+ textarea.setSelectionRange(0, 50000);
+ document.execCommand("copy");
+ document.body.removeChild(textarea);
+ next();
+ }
+ });
});
-// Don't assign to 'jsonObject', assign to 'json' instead.
-// 'jsonObject' is used to store the final json object and used internally.
-// Below is the getter and setter for 'json' which formats the value properly into and out of 'jsonObject'.
-Object.defineProperty(window, 'json', {
- configurable: true,
- // Getter to format 'jsonObject' properly depending on options and other factors
- // eg. using 'embeds' or 'embed' in output depending on 'multiEmbeds' option.
- get() {
- const json = {};
+// Don"t assign to "jsonObject", assign to "json" instead.
+// "jsonObject" is used to store the final json object and used internally.
+// Below is the getter and setter for "json" which formats the value properly into and out of "jsonObject".
+Object.defineProperty(window, "json", {
+ configurable: true,
+ // Getter to format "jsonObject" properly depending on options and other factors
+ // eg. using "embeds" or "embed" in output depending on "multiEmbeds" option.
+ get() {
+ const json = {};
- if (jsonObject.content)
- json.content = jsonObject.content;
+ if (jsonObject.content)
+ json.content = jsonObject.content;
- // If 'jsonObject.embeds' array is set and has content. Empty braces ({}) will be filtered as not content.
- if (jsonObject.embeds?.length)
- if (multiEmbeds) json.embeds = jsonObject.embeds.map(cleanEmbed);
- else json.embed = cleanEmbed(jsonObject.embeds[0]);
+ // If "jsonObject.embeds" array is set and has content. Empty braces ({}) will be filtered as not content.
+ if (jsonObject.embeds?.length)
+ if (multiEmbeds) json.embeds = jsonObject.embeds.map(cleanEmbed);
+ else json.embed = cleanEmbed(jsonObject.embeds[0]);
- return json;
- },
+ return json;
+ },
- // Setter for 'json' which formats the value properly into 'jsonObject'.
- set(val) {
- // Filter out items which are not objects and not empty objects.
- const embedObjects = val.embeds?.filter(j => j.constructor === Object && 0 in Object.keys(j));
- // Convert 'embed' to 'embeds' and delete 'embed' or validate and use 'embeds' if provided.
- const embeds = val.embed ? [val.embed] : embedObjects?.length ? embedObjects : []
- // Convert objects used as values to string and trim whitespace.
- const content = val.content?.toString().trim();
+ // Setter for "json" which formats the value properly into "jsonObject".
+ set(val) {
+ // Filter out items which are not objects and not empty objects.
+ const embedObjects = val.embeds?.filter(j => j.constructor === Object && 0 in Object.keys(j));
+ // Convert "embed" to "embeds" and delete "embed" or validate and use "embeds" if provided.
+ const embeds = val.embed ? [val.embed] : embedObjects?.length ? embedObjects : []
+ // Convert objects used as values to string and trim whitespace.
+ const content = val.content?.toString().trim();
- jsonObject = {
- ...(content && { content }),
- embeds: embeds.map(cleanEmbed),
- };
+ jsonObject = {
+ ...(content && {
+ content
+ }),
+ embeds: embeds.map(cleanEmbed),
+ };
- buildEmbed();
- buildGui();
- },
+ buildEmbed();
+ buildGui();
+ },
});
// Props used to validate embed properties.
window.embedObjectsProps ??= {
- author: ["name", "url", "icon_url",],
- thumbnail: ["url", "proxy_url", "height", "width",],
- image: ["url", "proxy_url", "height", "width",],
- fields: { items: ["name", "value", "inline",], },
- footer: ["text", "icon_url",],
+ author: ["name", "url", "icon_url", ],
+ thumbnail: ["url", "proxy_url", "height", "width", ],
+ image: ["url", "proxy_url", "height", "width", ],
+ fields: {
+ items: ["name", "value", "inline", ],
+ },
+ footer: ["text", "icon_url", ],
}
function cleanEmbed(obj, recursing = false) {
- if (!recursing)
- // Remove all invalid properties from embed object.
- for (const key in obj)
- if (!embedKeys.includes(key))
- delete obj[key];
- else if (obj[key].constructor === Object) // Value is an object. eg. 'author'
- // Remove items that are not in the props of the current key.
- for (const item in obj[key])
- !embedObjectsProps[key].includes(item) && delete obj[key][item];
+ if (!recursing)
+ // Remove all invalid properties from embed object.
+ for (const key in obj)
+ if (!embedKeys.includes(key))
+ delete obj[key];
+ else if (obj[key].constructor === Object) // Value is an object. eg. "author"
+ // Remove items that are not in the props of the current key.
+ for (const item in obj[key])
+ !embedObjectsProps[key].includes(item) && delete obj[key][item];
- else if (obj[key].constructor === Array) // Value is an array. eg. 'fields'
- // Remove items that are not in the props of the current key.
- for (const item of obj[key])
- for (const i in item)
- !embedObjectsProps[key].items.includes(i) && delete item[i];
+ else if (obj[key].constructor === Array) // Value is an array. eg. "fields"
+ // Remove items that are not in the props of the current key.
+ for (const item of obj[key])
+ for (const i in item)
+ !embedObjectsProps[key].items.includes(i) && delete item[i];
- // Remove empty properties from embed object.
- for (const [key, val] of Object.entries(obj))
- if (val === undefined || val.trim?.() === "")
- // Remove the key if value is empty
- delete obj[key];
- else if (val.constructor === Object)
- // Remove object (val) if it has no keys or recursively remove empty keys from objects.
- (!Object.keys(val).length && delete obj[key]) || (obj[key] = cleanEmbed(val, true));
- else if (val.constructor === Array)
- // Remove array (val) if it has no keys or recursively remove empty keys from objects in array.
- !val.length && delete obj[key] || (obj[key] = val.map(k => cleanEmbed(k, true)));
- else
- // If object isn't a string, boolean, number, array or object, convert it to string.
- if (!['string', 'boolean', 'number'].includes(typeof val))
- obj[key] = val.toString();
+ // Remove empty properties from embed object.
+ for (const [key, val] of Object.entries(obj))
+ if (val === undefined || val.trim?.() === "")
+ // Remove the key if value is empty
+ delete obj[key];
+ else if (val.constructor === Object)
+ // Remove object (val) if it has no keys or recursively remove empty keys from objects.
+ (!Object.keys(val).length && delete obj[key]) || (obj[key] = cleanEmbed(val, true));
+ else if (val.constructor === Array)
+ // Remove array (val) if it has no keys or recursively remove empty keys from objects in array.
+ !val.length && delete obj[key] || (obj[key] = val.map(k => cleanEmbed(k, true)));
+ else
+ // If object isn"t a string, boolean, number, array or object, convert it to string.
+ if (!["string", "boolean", "number"].includes(typeof val))
+ obj[key] = val.toString();
- return obj;
+ return obj;
}
\ No newline at end of file
diff --git a/index.html b/index.html
index d59ab24..3da1636 100644
--- a/index.html
+++ b/index.html
@@ -3,7 +3,7 @@
-
+
@@ -21,11 +21,11 @@
-
-
-
-
-
+
+
+
+
+