// 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 var activeFields, colNum = 1, num = 0, 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]; }, 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: "2020-12-08T13:37:35.401Z", 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 code blocks", inline: false } ] } }; window.onload = () => { document.querySelectorAll('img.clickable') .forEach(e => e.addEventListener('click', el => window.open(el.target.src))); let editorHolder = document.querySelector('.editorHolder'), guiParent = document.querySelector('.top'), gui = guiParent.querySelector('.gui:first-of-type'); window.editor = CodeMirror(elt => editorHolder.parentNode.replaceChild(elt, editorHolder), { value: JSON.stringify(json, null, 4), extraKeys: { Tab: cm => cm.replaceSelection(" ", "end") }, gutters: ["CodeMirror-foldgutter", "CodeMirror-lint-markers"], scrollbarStyle: "overlay", mode: "application/json", theme: 'material-darker', matchBrackets: true, foldGutter: true, lint: true, }); editor.focus(); let notif = document.querySelector('.notification'), url = (url) => /^(https?:)?\/\//g.exec(url) ? url : '//' + url, makeShort = (txt, length, mediaWidth) => { if (mediaWidth && window.matchMedia(`(max-width:${mediaWidth}px)`).matches) return txt.length > (length - 3) ? txt.substring(0, length - 3) + '...' : txt; return txt; }, error = (msg, time) => { 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 re = /"((icon_)?url")(: *)("(?!https?:\/\/).+?")/g.exec(editor.getValue()); if (re) return error(`URL should start with https:// or http:// on this line ${makeShort(re[0], 30, 600)}`); if (e.timestamp && new Date(e.timestamp).toString() === "Invalid Date") return error('Timestamp is invalid'); return true; }, markup = (txt, opts) => { txt = txt .replace(/<:[^:]+:(\d+)>/g, '') .replace(//g, '') .replace(/~~(.+?)~~/g, '$1') .replace(/\*\*\*(.+?)\*\*\*/g, '$1') .replace(/\*\*(.+?)\*\*/g, '$1') .replace(/__(.+?)__/g, '$1') .replace(/\*(.+?)\*/g, '$1') .replace(/_(.+?)_/g, '$1') if (opts.inlineBlock) txt = txt.replace(/\`([^\`]+?)\`|\`\`([^\`]+?)\`\`|\`\`\`((?:\n|.)+?)\`\`\`/g, (m, x, y, z) => x ? `${x}` : y ? `${y}` : z ? `${z}` : m); else txt = txt.replace(/\`\`\`(\w{1,15})?\n((?:\n|.)+?)\`\`\`|\`\`(.+?)\`\`(?!\`)|\`([^\`]+?)\`/g, (m, w, x, y, z) => w && x ? `
${x}
` : x ? `
${x}
` : y || z ? `${y || z}` : m); if (opts.inEmbed) txt = txt.replace(/\[([^\[\]]+)\]\((.+?)\)/g, `$1`); if (opts.replaceEmojis) txt = txt.replace(/(?[^>]+)(? x && emojis[x] ? emojis[x] : match); txt = txt .replace(/(?<=\n|^)\s*>\s+([^\n]+)/g, '
$1
') .replace(/\n/g, '
'); return txt; }, embedContent = document.querySelector('.messageContent'), 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'), embedThumbnail = document.querySelector('.embedThumbnail'), embedFields = embed.querySelector('.embedFields'), tstamp = stringISO => { let date = stringISO ? new Date(stringISO) : new Date(), dateArray = date.toLocaleString('en-US', { hour: 'numeric', hour12: true, 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) el.innerHTML = 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})`, toObj = jsonString => JSON.parse(jsonString.replace(/\\"|"(?:\\"|[^"])*"|(\/\/.*|\/\*[\s\S]*?\*\/)/g, (x, y) => y ? "" : x)); buildGui = (object, opts) => { gui.innerHTML = `

Message content

Author

Title

Description

Fields

Thumbnail

Image

`; let fieldsEditor = gui.querySelector('.fields ~ .edit'), addField = `

New Field

`; if (object.embed?.fields) fieldsEditor.innerHTML = object.embed.fields.filter(f => f && typeof f === 'object').map(f => `
Remove
`).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')) window.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) { input.focus(); input.selectionStart = input.selectionEnd = input.value.length; } else if (txt) txt.focus(); elm.classList.contains('fields') && elm.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'); fields = gui.querySelector('.fields ~ .edit'); document.querySelector('.addField').addEventListener('click', () => { !json.embed && (json.embed = {}); let arr = json.embed.fields || []; 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 => { let v = el.target.value, field = el.target.closest('.field'); if (field) { let jsonField = json.embed.fields[Array.from(fields.children).indexOf(field)]; if (jsonField) { if (el.target.type === 'text') jsonField.name = v; else if (el.target.type === 'textarea') jsonField.value = v; else jsonField.inline = el.target.checked; } else { let obj = {} if (el.target.type === 'text') obj.name = v; else if (el.target.type === 'textarea') obj.value = v; else obj.inline = el.target.checked; json.embed.fields.push(obj); } } else { json.embed ??= {}; switch (el.target) { case content: json.content = v; break; case title: json.embed.title = v; break; case authorName: json.embed.author ??= {}, json.embed.author.name = v; break; case authorLink: json.embed.author ??= {}, json.embed.author.icon_url = v, imgSrc(el.target.previousElementSibling, v); break; case desc: json.embed.description = v; break; case thumbLink: json.embed.thumbnail ??= {}, json.embed.thumbnail.url = v, imgSrc(el.target.closest('.editIcon').querySelector('.imgParent'), v); break; case imgLink: json.embed.image ??= {}, json.embed.image.url = v, imgSrc(el.target.closest('.editIcon').querySelector('.imgParent'), v); break; case footerText: json.embed.footer ??= {}, json.embed.footer.text = v; break; case footerLink: json.embed.footer ??= {}, json.embed.footer.icon_url = v, imgSrc(el.target.previousElementSibling, v); break; } } update(json); })) if (opts?.activate) { let elements = opts.activate; Array.from(elements).map(el => el.className).map(clss => '.' + clss.split(' ').slice(0, 2).join('.')) .forEach(clss => document.querySelectorAll(clss) .forEach(e => e.classList.add('active'))) } else['.item.author', '.item.description'].forEach(clss => document.querySelector(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 files = document.querySelectorAll('input[type="file"]'); files.forEach(f => f.addEventListener('change', e => { if (f.files) { e.target.nextElementSibling.click(); e.target.closest('.edit').querySelector('.browse').classList.add('loading'); } })) document.querySelectorAll('form').forEach(form => form.addEventListener('submit', e => { e.preventDefault(); let formData = new FormData(e.target); 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 = e.target.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(e.target.previousElementSibling.querySelector('.editIcon > .imgParent') || e.target.closest('.editIcon').querySelector('.imgParent'), res.link); let input = e.target.previousElementSibling.querySelector('.editIcon > input') || e.target.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 5 minutes. To delete it now got to ${res.link.replace('/files', '/del')} and enter this code: ${res.authkey}`); }).catch(err => `Request to tempfile.site failed with error: ${err}`) })) } buildGui(json); fields = gui.querySelector('.fields ~ .edit'); update = data => { try { embedContent.innerHTML = data.content ? markup(data.content, { replaceEmojis: true }) : ''; if (data.embed && Object.keys(data.embed).length) { let e = data.embed; if (!allGood(e)) return; if (e.title) display(embedTitle, markup(`${e.url ? '' + e.title + '' : e.title}`, { replaceEmojis: true, inlineBlock: true })); else hide(embedTitle); if (e.description) display(embedDescription, markup(e.description, { inEmbed: true, replaceEmojis: true })); else hide(embedDescription); if (e.color) embed.closest('.embed').style.borderColor = (typeof e.color === 'number' ? '#' + e.color.toString(16).padStart(6, "0") : e.color); else embed.closest('.embed').style.removeProperty('border-color'); if (e.author && e.author.name) display(embedAuthor, ` ${e.author.icon_url ? '' : ''} ${e.author.url ? '' + e.author.name + '' : '' + e.author.name + ''}`, 'flex'); else hide(embedAuthor); if (e.thumbnail && e.thumbnail.url) embedThumbnail.src = e.thumbnail.url, embedThumbnail.style.display = 'block'; else hide(embedThumbnail); if (e.image && e.image.url) embedImage.src = e.image.url, embedImage.style.display = 'block'; else hide(embedImage); if (e.footer && e.footer.text) display(embedFooter, ` ${e.footer.icon_url ? '' : ''} ${e.footer.text} ${e.timestamp ? '•' + tstamp(e.timestamp) : ''}`, 'flex'); else if (e.timestamp) display(embedFooter, `${tstamp(e.timestamp)}`, 'flex'); else hide(embedFooter); if (e.fields) { embedFields.innerHTML = ''; e.fields.forEach(f => { if (f.name && f.value) { if (!f.inline) { let el = embedFields.insertBefore(document.createElement('div'), null); el.outerHTML = `
${markup(f.name, { inEmbed: true, replaceEmojis: true, inlineBlock: true })}
${markup(f.value, { inEmbed: true, replaceEmojis: true })}
`; } else { el = embedFields.insertBefore(document.createElement('div'), null); el.outerHTML = `
${markup(f.name, { inEmbed: true, replaceEmojis: true, inlineBlock: true })}
${markup(f.value, { inEmbed: true, replaceEmojis: true })}
`; colNum = (colNum === 9 ? 1 : colNum + 4); num++; } } }); colNum = 1; let len = e.fields.filter(f => f.inline).length; if (len === 2 || (len > 3 && len % 2 !== 0)) { let children = Array.from(embedFields.children), arr = children.filter(x => x === children[len] || x === children[len - 1]); arr[0] && (arr[0].style.gridColumn = '1 / 7'); arr[1] && (arr[1].style.gridColumn = '7 / 13'); } display(embedFields, undefined, 'grid'); } else hide(embedFields); embed.classList.remove('empty'); document.querySelectorAll('.markup pre > code').forEach((block) => hljs.highlightBlock(block)); notif.animate({ opacity: '0', bottom: '-50px', offset: 1 }, { easing: 'ease', duration: 500 }).onfinish = () => notif.style.removeProperty('display'); twemoji.parse(msgEmbed); } else embed.classList.add('empty'); } catch (e) { error(e); } } editor.on('change', editor => { try { update(toObj(editor.getValue())); } catch (e) { if (editor.getValue()) return; embed.classList.add('empty'); embedContent.innerHTML = ''; } }); let picker = new CP(document.querySelector('.picker'), state = { parent: document.querySelector('.cTop') }); picker.fire('change', toRGB('#41f097')); let colRight = document.querySelector('.colRight'), removePicker = () => colRight.classList.remove('picking'); document.querySelector('.colBack').addEventListener('click', e => { picker.self.remove(); removePicker(); }) picker.on('enter', () => colRight.classList.add('picking')) picker.on('exit', removePicker); document.querySelectorAll('.colr').forEach(e => e.addEventListener('click', el => { el = el.target.closest('.colr') || 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'); })) 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); }) }, 1000) document.querySelector('.timeText').innerText = tstamp(); document.querySelectorAll('.markup pre > code').forEach((block) => hljs.highlightBlock(block)); !window.navigator.userAgent.match(/Firefox\/[\d\.]+$/g) && // Firefox pushes the text up a little document.querySelector('.botText').style.removeProperty('top'); document.querySelector('.opt.gui').addEventListener('click', () => { json = toObj(editor.getValue() || '{}'); buildGui(json, { activate: activeFields }); document.body.classList.add('gui'); activeFields = null; }) document.querySelector('.opt.json').addEventListener('click', () => { editor.setValue(JSON.stringify(json, null, 4)); editor.refresh(); document.body.classList.remove('gui'); editor.focus(); activeFields = document.querySelectorAll('.gui > .item.active'); }) document.querySelector('.clear').addEventListener('click', () => { json = {} embed.style.removeProperty('border-color'); picker.source.style.removeProperty('background'); update(json); buildGui(json); editor.setValue('{}'); document.querySelectorAll('.gui>.item').forEach(e => e.classList.add('active')); content.focus(); }) let colrs = document.querySelector('.colrs'); document.querySelector('.pickerToggle').addEventListener('click', () => colrs.classList.toggle('display')); update(json); };