// Want to use or contribute to this? https://github.com/Glitchii/embedbuilder
// If you found an issue, please report it, make a P.R, or use the discussion page. Thanks
options = window.options || {};
inIframe = window.inIframe || top !== self;
currentURL = () => new URL(inIframe ? /(https?:\/\/(?:[\d\w]+\.)?[\d\w\.]+(?::\d+)?)/g.exec(document.referrer)?.[0] || location.href : location.href);
var params = currentURL().searchParams,
hasParam = param => params.get(param) !== null,
dataSpecified = options.dataSpecified || 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,
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,
activeFields, colNum = 1, num = 0, validationError,
toggleStored = item => {
const found = localStorage.getItem(item);
if (!found) return localStorage.setItem(item, true);
localStorage.removeItem(item);
return found;
},
jsonToBase64 = (jsonCode, withURL = false, redirect = false) => {
data = btoa(escape((JSON.stringify(typeof jsonCode === 'object' ? jsonCode : json))));
if (withURL) {
const url = currentURL();
url.searchParams.set('data', data);
if (redirect) window.top.location.href = url;
// Replace %3D ('=' url encoded) with '='
data = url.href.replace(/data=\w+(?:%3D)+/g, 'data=' + data);
}
return data;
},
base64ToJson = data => {
jsonData = unescape(atob(data || dataSpecified));
if (typeof jsonData === 'string')
jsonData = JSON.parse(jsonData);
return jsonData;
},
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];
},
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 isReversed = document.body.classList.toggle('reversed');
if (autoParams) isReversed ? urlOptions({ set: ['reverse', ''] }) : urlOptions({ remove: 'reverse' });
},
urlOptions = ({ remove, set }) => {
const url = currentURL();
if (remove) url.searchParams.delete(remove);
if (set) url.searchParams.set(set[0], set[1]);
try {
history.replaceState(null, null, url.href.replace(/=&|=$/g, x => x === '=' ? '' : '&'));
} catch (e) {
// Most likely embeded in iframe
console.message(`${e.name}: ${e.message}`, e);
// if (e.name === 'SecurityError')
// window.top.location.href = href;
}
},
mainKeys = ["author", "footer", "color", "thumbnail", "image", "fields", "title", "description", "url", "timestamp"],
jsonKeys = ["embed", "content", ...mainKeys],
json = window.json || {
content: "You can~~not~~ do `this`.```py\nAnd this.\nprint('Hi')```\n*italics* or _italics_ __*underline italics*__\n**bold** __**underline bold**__\n***bold italics*** __***underline bold italics***__\n__underline__ ~~Strikethrough~~",
embed: {
title: "Hello ~~people~~ world :wave:",
description: "You can use [links](https://discord.com) or emojis :smile: 😎\n```\nAnd also code blocks\n```",
color: 4321431,
timestamp: new Date().toISOString(),
url: "https://discord.com",
author: {
name: "Author name",
url: "https://discord.com",
icon_url: "https://unsplash.it/100"
},
thumbnail: {
url: "https://unsplash.it/200"
},
image: {
url: "https://unsplash.it/380/200"
},
footer: {
text: "Footer text",
icon_url: "https://unsplash.it/100"
},
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)
json = base64ToJson();
if (allowPlaceholders)
allowPlaceholders = params.get('placeholders') === 'errors' ? 1 : 2;
addEventListener('DOMContentLoaded', () => {
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');
else {
if (username) document.querySelector('.username').textContent = username;
if (avatar) document.querySelector('.avatar').src = avatar;
if (verified) document.querySelector('.msgEmbed > .contents').classList.add('verified');
}
if (reverseColumns || localStorage.getItem('reverseColumns'))
reverse();
if (autoUpdateURL)
document.body.classList.add('autoUpdateURL');
if (autoParams)
document.querySelector('.auto-params > input').checked = true;
if (inIframe)
// Remove menu options that don't work in iframe.
for (const e of document.querySelectorAll('.no-frame'))
e.remove();
if (hideMenu)
document.querySelector('.top-btn.menu').classList.add('hidden');
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;
}
document.querySelectorAll('.clickable > img')
.forEach(e => 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('.messageContent + .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: {
// Make tabs four spaces long instead of the default two.
Tab: cm => cm.replaceSelection(" ", "end"),
// Fill in indent spaces on a new line when enter (return) key is pressed.
Enter: _ => {
let cur = editor.getCursor(), end = editor.getLine(cur.line),
leadingSpaces = end.replace(/\S($|.)+/g, '') || ' \n', nextLine = editor.getLine(cur.line + 1);
if ((nextLine === undefined || !nextLine.trim()) && !end.substr(cur.ch).trim())
editor.replaceRange('\n', { line: cur.line, ch: cur.ch });
else
editor.replaceRange(`\n${end.endsWith('{') ? leadingSpaces + ' ' : leadingSpaces}`, {
line: cur.line,
ch: cur.ch
});
},
}
});
editor.focus();
const notif = document.querySelector('.notification'),
url = (url) => /^(https?:)?\/\//g.exec(url) ? url : '//' + url,
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;
}, error = (msg, time) => {
if (msg === false)
// Hide error element
return notif.animate({ opacity: '0', bottom: '-50px', offset: 1 }, { easing: 'ease', duration: 500 }).onfinish = () => notif.style.removeProperty('display');
notif.innerHTML = msg;
notif.style.display = 'block';
time && setTimeout(() => notif.animate({ opacity: '0', bottom: '-50px', offset: 1 }, { easing: 'ease', duration: 500 })
.onfinish = () => notif.style.removeProperty('display'), time);
return false;
}, allGood = e => {
let invalid, err, str = JSON.stringify(e, null, 4), re = /("(?:icon_)?url": *")((?!\w+?:\/\/).+)"/g.exec(str);
if (e.timestamp && new Date(e.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]}`;
update(JSON.parse(str.replace(re[0], `${re[1]}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, 5000);
}
return true;
}, innerHTML = (element, html) => {
// console.log(element, html);
element.innerHTML = html;
return element;
}, markup = (txt, opts) => {
if (opts.replaceEmojis)
txt = txt.replace(/(?[^>]+)(? p && emojis[p] ? emojis[p] : match);
txt = txt
/** Markdown */
.replace(/<:\w+:(\d{18})>/g, '
')
.replace(/<a:\w+:(\d{18})>/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, '')}
`;
})
/** Mentions */
.replace(/<#\d+>/g, () => `channel`)
.replace(/<@(?:&|!)?\d+>|@(?:everyone|here)/g, match => {
if (match.startsWith('@')) return `${match}`
else return `@${match.includes('&') ? 'role' : 'user'}`
})
if (opts.inlineBlock)
// Treat both inline code and code blocks as inline code
txt = txt.replace(/`([^`]+?)`|``([^`]+?)``|```((?:\n|.)+?)```/g, (m, x, y, z) => x ? `${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 (opts.inEmbed)
txt = txt.replace(/\[([^\[\]]+)\]\((.+?)\)/g, `$1`);
return txt;
},
embed = document.querySelector('.embedGrid'),
msgEmbed = document.querySelector('.msgEmbed'),
embedTitle = document.querySelector('.embedTitle'),
embedDescription = document.querySelector('.embedDescription'),
embedAuthor = document.querySelector('.embedAuthor'),
embedFooter = document.querySelector('.embedFooter'),
embedImage = document.querySelector('.embedImage > img'),
embedThumbnail = document.querySelector('.embedThumbnail > img'),
embedFields = embed.querySelector('.embedFields'),
smallerScreen = matchMedia('(max-width: 1015px)'),
encodeHTML = str => str.replace(/[\u00A0-\u9999<>\&]/g, i => '' + i.charCodeAt(0) + ';'),
tstamp = stringISO => {
let 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));
return today.toDateString() === date.toDateString() ? `Today at ${dateArray}` :
yesterday.toDateString() === date.toDateString() ? `Yesterday at ${dateArray}` :
`${String(date.getMonth() + 1).padStart(2, '0')}/${String(date.getDate()).padStart(2, '0')}/${date.getFullYear()}`;
}, display = (el, data, displayType) => {
if (data) innerHTML(el, data);
el.style.display = displayType || "unset";
}, hide = el => el.style.removeProperty('display'),
imgSrc = (elm, src, remove) => remove ? elm.style.removeProperty('content') : elm.style.content = `url(${src})`;
buildGui = (object, opts) => {
gui.innerHTML = `
`;
const fieldsEditor = gui.querySelector('.fields ~ .edit'), addField = `
`;
if (object.embed?.fields) fieldsEditor.innerHTML = object.embed.fields.filter(f => f && typeof f === 'object').map(f => `
`).join('\n') + addField;
else fieldsEditor.innerHTML = addField;
gui.querySelectorAll('.removeBtn').forEach(e => {
e.addEventListener('click', el => {
fields = gui.querySelector('.fields ~ .edit');
let field = el.target.closest('.field');
if (field) {
let i = Array.from(fields.children).indexOf(field), jsonField = object.embed.fields[i];
if (jsonField) {
object.embed.fields.splice(i, 1);
field.remove();
update(object);
}
}
})
})
document.querySelectorAll('.gui > .item').forEach(e => {
e.addEventListener('click', el => {
let elm = (el.target.closest('.top>.gui>.item') || el.target);
if (elm.classList.contains('active')) getSelection().anchorNode !== elm && elm.classList.remove('active');
else {
let inlineField = elm.closest('.inlineField'),
input = elm.nextElementSibling.querySelector('input[type="text"]'),
txt = elm.nextElementSibling.querySelector('textarea');
elm.classList.add('active');
if (inlineField) inlineField.querySelector('.ttle~input').focus();
else if (input) {
if (!smallerScreen.matches)
input.focus();
input.selectionStart = input.selectionEnd = input.value.length;
} else if (txt && !smallerScreen.matches)
txt.focus();
if (elm.classList.contains('fields')) {
if (reverseColumns && smallerScreen.matches)
// return elm.nextElementSibling.scrollIntoView({ behavior: 'smooth', block: "end" });
return elm.parentNode.scrollTop = elm.offsetTop;
elm.scrollIntoView({ behavior: "smooth", block: "center" });
}
}
})
})
// Scroll into view when tabs are opened in the GUI.
let lastTabs = Array.from(document.querySelectorAll('.footer.rows2, .image.largeImg')),
requiresView = matchMedia(`${smallerScreen.media}, (max-height: 845px)`);
document.querySelectorAll('.gui>.item:not(.fields)').forEach(e => e.addEventListener('click', () => {
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;
}
}));
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');
fields = gui.querySelector('.fields ~ .edit');
document.querySelector('.addField').addEventListener('click', () => {
!json.embed && (json.embed = {});
let arr = json.embed.fields || [];
if (arr.length >= 25) return error('Cannot have more than 25 fields', 5000);
arr.push({ name: "Field name", value: "Field value", inline: false });
json.embed.fields = arr;
update(json);
buildGui(json, { newField: true, activate: document.querySelectorAll('.gui > .item.active') });
})
gui.querySelectorAll('textarea, input').forEach(e => e.addEventListener('input', el => {
const value = el.target.value, field = el.target.closest('.field');
if (field) {
const jsonField = json.embed.fields[Array.from(fields.children).filter(e => e.className === 'field').indexOf(field)];
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;
} else {
const obj = {}
if (el.target.type === 'text') obj.name = value;
else if (el.target.type === 'textarea') obj.value = value;
else obj.inline = el.target.checked;
json.embed.fields.push(obj);
}
} else {
json.embed ??= {};
switch (el.target) {
case content: json.content = value; break;
case title: json.embed.title = value; break;
case authorName: json.embed.author ??= {}, json.embed.author.name = value; break;
case authorLink: json.embed.author ??= {}, json.embed.author.icon_url = value, imgSrc(el.target.previousElementSibling, value); break;
case desc: json.embed.description = value; break;
case thumbLink: json.embed.thumbnail ??= {}, json.embed.thumbnail.url = value, imgSrc(el.target.closest('.editIcon').querySelector('.imgParent'), value); break;
case imgLink: json.embed.image ??= {}, json.embed.image.url = value, imgSrc(el.target.closest('.editIcon').querySelector('.imgParent'), value); break;
case footerText: json.embed.footer ??= {}, json.embed.footer.text = value; break;
case footerLink: json.embed.footer ??= {}, json.embed.footer.icon_url = value, imgSrc(el.target.previousElementSibling, value); break;
}
}
update(json);
}))
if (opts?.guiTabs) {
let tabs = opts.guiTabs.split?.(/, */) || opts.guiTabs, bottomKeys = ['footer', 'image'], topKeys = ['author', 'content'];
document.querySelectorAll(`.${tabs.join(', .')}`).forEach(e => e.classList.add('active'));
// Autoscroll GUI to the bottom if necessary.
if (!tabs.some(item => topKeys.includes(item)) && tabs.some(item => bottomKeys.includes(item))) {
let gui2 = document.querySelector('.top .gui');
gui2.scrollTo({ top: gui2.scrollHeight });
}
} else if (opts?.activate) {
Array.from(opts.activate).map(el => el.className).map(clss => '.' + clss.split(' ').slice(0, 2).join('.'))
.forEach(clss => document.querySelectorAll(clss)
.forEach(e => e.classList.add('active')))
} else
document.querySelectorAll('.item.author, .item.description').forEach(clss => clss.classList.add('active'));
if (opts?.newField) {
let last = fields.children[fields.childElementCount - 2], el = last.querySelector('.designerFieldName > input');
el.setSelectionRange(el.value.length, el.value.length); el.focus();
last.scrollIntoView({ behavior: "smooth", block: "center" });
}
let upload = form => {
let formData = new FormData(form);
formData.append('file', files.files);
formData.append('datetime', '10m');
fetch('https://tempfile.site/api/files', {
method: 'POST',
body: formData,
})
.then(res => res.json())
.then(res => {
let browse = form.closest('.edit').querySelector('.browse');
browse.classList.remove('loading');
if (!res.ok) {
console.log(res.error);
browse.classList.add('error');
return setTimeout(() => browse.classList.remove('error'), 5000)
}
imgSrc(form.previousElementSibling.querySelector('.editIcon > .imgParent') || form.closest('.editIcon').querySelector('.imgParent'), res.link);
let input = form.previousElementSibling.querySelector('.editIcon > input') || form.previousElementSibling;
input.value = res.link;
if (input === authorLink) ((json.embed ??= {}).author ??= {}).icon_url = res.link;
else if (input === thumbLink) ((json.embed ??= {}).thumbnail ??= {}).url = res.link;
else if (input === imgLink) ((json.embed ??= {}).image ??= {}).url = res.link;
else ((json.embed ??= {}).footer ??= {}).icon_url = res.link;
update(json);
console.info(`Image (${res.link}) will be deleted in 10 minutes. To delete it now, go to ${res.link.replace('/files', '/del')} and enter this code: ${res.authkey}`);
}).catch(err => error(`Request to tempfile.site failed with error: ${err}`, 5000))
}
const files = document.querySelectorAll('input[type="file"]');
files.forEach(f => f.addEventListener('change', e => {
if (f.files) {
upload(e.target.parentElement);
e.target.closest('.edit').querySelector('.browse').classList.add('loading');
}
}))
}
buildGui(json, { guiTabs });
fields = gui.querySelector('.fields ~ .edit');
update = data => {
try {
if (!data.content) document.body.classList.add('emptyContent');
else {
innerHTML(embedContent, markup(encodeHTML(data.content), { replaceEmojis: true }));
document.body.classList.remove('emptyContent');
}
if (data.embed && Object.keys(data.embed).length) {
const emb = data.embed;
if (!allGood(emb)) return;
validationError = false;
if (emb.title) display(embedTitle, markup(`${emb.url ? '' + encodeHTML(emb.title) + '' : encodeHTML(emb.title)}`, { replaceEmojis: true, inlineBlock: true }));
else hide(embedTitle);
if (emb.description) display(embedDescription, markup(encodeHTML(emb.description), { inEmbed: true, replaceEmojis: true }));
else hide(embedDescription);
if (emb.color) embed.closest('.embed').style.borderColor = (typeof emb.color === 'number' ? '#' + emb.color.toString(16).padStart(6, "0") : emb.color);
else embed.closest('.embed').style.removeProperty('border-color');
if (emb.author?.name) display(embedAuthor, `
${emb.author.icon_url ? '
' : ''}
${emb.author.url ? '' + encodeHTML(emb.author.name) + '' : '' + encodeHTML(emb.author.name) + ''}`, 'flex');
else hide(embedAuthor);
const pre = embed.querySelector('.markup pre');
if (emb.thumbnail?.url) {
embedThumbnail.src = emb.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 (emb.image?.url)
embedImage.src = emb.image.url,
embedImage.parentElement.style.display = 'block';
else hide(embedImage.parentElement);
if (emb.footer?.text) display(embedFooter, `
${emb.footer.icon_url ? '' : ''}`, 'flex');
else if (emb.timestamp) display(embedFooter, ``, 'flex');
else hide(embedFooter);
if (emb.fields) {
innerHTML(embedFields, '');
let index, gridCol;
emb.fields.forEach((f, i) => {
if (f.name && f.value) {
let 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 its right are inline and -
if (emb.fields[i].inline && emb.fields[i + 1]?.inline &&
// it's the first field in the embed or -
((i === 0 && emb.fields[i + 2] && !emb.fields[i + 2].inline) || ((
// it's not the first field in the embed but the previous field is not inline or -
i > 0 && !emb.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 && emb.fields[i - 1].inline && emb.fields[i - 2].inline && emb.fields[i - 3].inline && (emb.fields[i - 4] ? !emb.fields[i - 4].inline : !emb.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 == emb.fields.length - 2 || !emb.fields[i + 2].inline))) || i % 3 === 0 && i == emb.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 = `
${markup(encodeHTML(f.name), { inEmbed: true, replaceEmojis: true, inlineBlock: true })}
${markup(encodeHTML(f.value), { inEmbed: true, replaceEmojis: true })}
`;
else {
if (i && !emb.fields[i - 1].inline) colNum = 1;
fieldElement.outerHTML = `
${markup(encodeHTML(f.name), { inEmbed: true, replaceEmojis: true, inlineBlock: true })}
${markup(encodeHTML(f.value), { inEmbed: true, replaceEmojis: true })}
`;
if (index !== i) gridCol = false;
}
colNum = (colNum === 9 ? 1 : colNum + 4);
num++;
}
});
document.querySelectorAll('.embedField[style="grid-column: 1 / 5;"]').forEach(e => {
if (!e.nextElementSibling || e.nextElementSibling.style.gridColumn === '1 / 13')
e.style.gridColumn = '1 / 13';
});
colNum = 1;
display(embedFields, undefined, 'grid');
} else hide(embedFields);
document.body.classList.remove('emptyEmbed');
for (block of document.querySelectorAll('.markup pre > code'))
hljs.highlightBlock(block);
error(false);
twemoji.parse(msgEmbed);
} else
document.body.classList.add('emptyEmbed');
// Make sure that the embed has no text or any visible images such as custom emojis before hiding.
if (!embedCont.innerText && !document.querySelector('.messageContent + .container .embedGrid > [style*=display] img'))
document.body.classList.add('emptyEmbed');
json = data;
if (autoUpdateURL)
urlOptions({ set: ['data', jsonToBase64(json)] })
} catch (e) {
console.error(e);
console.dir(e);
error(e);
}
}
editor.on('change', editor => {
// // Autofill when " key is typed on new line
// let line = editor.getCursor().line, text = editor.getLine(line)
// if (text.trim() === '"') {
// editor.replaceRange(text.trim() + ': ', { line, ch: line.length });
// editor.setCursor(line, text.length)
// }
let jsonData = JSON.parse(editor.getValue()), dataKeys = Object.keys(jsonData);
if (!dataKeys.includes('embed') && !dataKeys.includes('embed') && mainKeys.some(key => dataKeys.includes(key))) {
editor.setValue(JSON.stringify({ embed: jsonData }, null, 4));
editor.refresh();
}
try {
if (dataKeys.length && !jsonKeys.some(key => dataKeys.includes(key))) {
let usedKeys = dataKeys.filter(key => !jsonKeys.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."}`);
} else if (!validationError)
error(false);
update(jsonData);
}
catch (e) {
if (editor.getValue()) return;
document.body.classList.add('emptyEmbed');
innerHTML(embedContent, '');
}
});
let picker = new CP(document.querySelector('.picker'), state = { parent: document.querySelector('.cTop') });
picker.fire('change', toRGB('#41f097'));
let colors = document.querySelector('.colors'),
hexInput = colors.querySelector('.hex>div input'),
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', () => {
if (json?.embed?.color) {
hexInput.value = json.embed.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 => {
el = el.target.closest('.color') || el.target;
embed.closest('.embed').style.borderColor = el.style.backgroundColor;
json.embed && (json.embed.color = toRGB(el.style.backgroundColor, false, true));
picker.source.style.removeProperty('background');
}))
hexInput.addEventListener('focus', () => typingHex = true);
setTimeout(() => {
picker.on('change', function (r, g, b, a) {
embed.closest('.embed').style.borderColor = this.color(r, g, b);
json.embed && (json.embed.color = parseInt(this.color(r, g, b).slice(1), 16));
picker.source.style.background = this.color(r, g, b);
hexInput.value = json.embed.color.toString(16).padStart(6, '0');
})
}, 1000)
document.querySelector('.timeText').innerText = tstamp();
for (block of document.querySelectorAll('.markup pre > code'))
hljs.highlightBlock(block);
document.querySelector('.opt.gui').addEventListener('click', () => {
json = JSON.parse(editor.getValue() || '{}');
buildGui(json, { activate: activeFields });
document.body.classList.add('gui');
activeFields = null;
if (pickInGuiMode) {
pickInGuiMode = false;
togglePicker();
}
})
document.querySelector('.opt.json').addEventListener('click', () => {
let jsonData = JSON.stringify(json, null, 4);
editor.setValue(jsonData === '{}' ? '{\n\t\n}' : jsonData);
editor.refresh();
document.body.classList.remove('gui');
// if (!smallerScreen.matches)
editor.focus();
activeFields = document.querySelectorAll('.gui > .item.active');
if (document.querySelector('section.low'))
togglePicker(true);
})
document.querySelector('.clear').addEventListener('click', () => {
json = {};
embed.style.removeProperty('border-color');
picker.source.style.removeProperty('background');
update(json); buildGui(json); editor.setValue('{\n\t\n}');
document.querySelectorAll('.gui>.item').forEach(e => 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 = jsonToBase64(json, true);
// With long text inside a 'prompt' on Chromium based browsers, some text will but cut and replaced with '...'.
// So, for the Chromium users, we copy to clipboard instead of showing a prompt.
if (!window.chrome)
return prompt('Here\'s the current URL with base64 embed data:', data);
if (location.protocol === 'http:')
// Clipboard API only works on HTTPS protocol.
navigator.clipboard.writeText(data);
else {
const input = document.createElement('input');
input.value = data;
document.body.appendChild(input);
input.select();
document.execCommand('copy');
document.body.removeChild(input);
}
alert('Copied to clipboard.');
}
const input = e.target.closest('.item')?.querySelector('input');
if (input) input.checked = !input.checked;
if (e.target.closest('.item.auto')) {
console.log('Burv');
autoUpdateURL = document.body.classList.toggle('autoUpdateURL');
if (autoUpdateURL) localStorage.setItem('autoUpdateURL', true);
else localStorage.removeItem('autoUpdateURL');
update(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;
document.body.classList.toggle('no-user');
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);
}
}
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');
pickLater && (pickInGuiMode = true);
};
document.querySelector('.pickerToggle').addEventListener('click', togglePicker);
update(json);
document.body.addEventListener('click', e => {
if (e.target.classList.contains('low') || (e.target.classList.contains('top') && colors.classList.contains('display')))
togglePicker();
})
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');
json.embed.color = parseInt(inputValue, 16);
update(json);
})
if (onlyEmbed) document.querySelector('.side1')?.remove();
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 => alert('Could not copy to clipboard: ' + err.message))) {
const textarea = document.createElement('textarea');
textarea.value = jsonData;
document.body.appendChild(textarea);
textarea.select();
textarea.setSelectionRange(0, 50000);
document.execCommand('copy');
document.body.removeChild(textarea);
next();
}
});
});
console.__proto__.message = function (title, message, collapse = true) {
collapse && this.groupCollapsed(title) || this.group(title);
this.dir(message);
this.groupEnd();
}