diff --git a/README.md b/README.md index ad40389..a6820a4 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@ Visualize embed or message content from JSON input or provided GUI editor. -This can be used for bot embed commands commands. Not everyone understands JSON, thus GUI. +This can be used for bot embed commands. Not everyone understands JSON, thus GUI. You can look into the [project boards](https://github.com/Glitchii/embedbuilder/projects/3) if you want to see what is being worked on or want to contribute.

diff --git a/assets/js/index.js b/assets/js/index.js index 6f72352..a57cac6 100644 --- a/assets/js/index.js +++ b/assets/js/index.js @@ -115,8 +115,7 @@ window.onload = () => { return error(`URL should have a protocol. Did you mean http://${makeShort(re[2], 30, 600).replace(' ', '')}?`); } return true; - }, - markup = (txt, opts) => { + }, markup = (txt, opts) => { txt = txt .replace(/\<:[^:]+:(\d+)\>/g, '') .replace(/\<a:[^:]+:(\d+)\>/g, '') @@ -158,381 +157,381 @@ window.onload = () => { 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

-
-
-
- - -
-
- -
+ 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

-
-
-
- -
- -
- - - -
-
+
+
+

Title

+ +
+

Description

+
+ +
+

Fields

+
+

Thumbnail

+
+
+
+ +
+ +
+ + + +
-

Image

-
-
-
- -
- -
- - - -
-
+
+

Image

+
+
+
+ +
+ +
+ + + +
- -
-
-
- - -
-
- -
+
+ +
+
+
+ +
-
- - -
`; + +

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

New Field

- + 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 => ` +
+
+
+
+ +
+
+ +
+
+
+ +
+
+ - - - - - - - - - - - - - - - - - - - - - - - - - + -
`; - 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; + 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 || []; - 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 => { - let value = el.target.value, field = el.target.closest('.field'); + gui.querySelectorAll('.removeBtn').forEach(e => { + e.addEventListener('click', el => { + fields = gui.querySelector('.fields ~ .edit'); + let field = el.target.closest('.field'); if (field) { - let jsonField = json.embed.fields[Array.from(fields.children).indexOf(field)]; + let i = Array.from(fields.children).indexOf(field), jsonField = object.embed.fields[i]; 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 { - let 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); + 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 || []; + 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 => { + let value = 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 = value; + else if (el.target.type === 'textarea') jsonField.value = value; + else jsonField.inline = el.target.checked; } 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; - } + let 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?.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" }); } + update(json); + })) - 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'); - } - })) + 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')); - 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 ??= {}).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)) - })) + 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 ??= {}).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)) + })) + } + buildGui(json); fields = gui.querySelector('.fields ~ .edit'); update = data => { diff --git a/assets/media/screenshot2.png b/assets/media/screenshot2.png new file mode 100644 index 0000000..8d75ccf Binary files /dev/null and b/assets/media/screenshot2.png differ diff --git a/comp.js b/comp.js new file mode 100644 index 0000000..a57cac6 --- /dev/null +++ b/comp.js @@ -0,0 +1,675 @@ + +// 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: 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 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'), + embedContent = document.querySelector('.messageContent'), + embedCont = document.querySelector('.messageContent + .container'), + 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 str = JSON.stringify(e, null, 4), re = /("(?:icon_)?url": *")((?!\w+?:\/\/).+)"/g.exec(str); + if (e.timestamp && new Date(e.timestamp).toString() === "Invalid Date") return error('Timestamp is invalid'); + 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) { + lastPos = activeInput.selectionStart + 7; + authorLink.value = `http://${re[2]}`; + update(JSON.parse(str.replace(re[0], `${re[1]}http://${re[2]}"`))); + activeInput.setSelectionRange(lastPos, lastPos) + return true; + } + } + return error(`URL should have a protocol. Did you mean http://${makeShort(re[2], 30, 600).replace(' ', '')}?`); + } + return true; + }, markup = (txt, opts) => { + txt = txt + .replace(/\<:[^:]+:(\d+)\>/g, '') + .replace(/\<a:[^:]+:(\d+)\>/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; + }, + 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'), + 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: 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 || []; + 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 => { + let value = 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 = value; + else if (el.target.type === 'textarea') jsonField.value = value; + else jsonField.inline = el.target.checked; + } else { + let 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?.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 ??= {}).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)) + })) + } + + buildGui(json); + fields = gui.querySelector('.fields ~ .edit'); + update = data => { + try { + if (!data.content) embedContent.classList.add('empty'); + else { + embedContent.innerHTML = markup(encodeHTML(data.content), { replaceEmojis: true }); + embedContent.classList.remove('empty'); + } + if (data.embed && Object.keys(data.embed).length) { + let e = data.embed; + if (!allGood(e)) return; + if (e.title) display(embedTitle, markup(`${e.url ? '' + encodeHTML(e.title) + '' : encodeHTML(e.title)}`, { replaceEmojis: true, inlineBlock: true })); + else hide(embedTitle); + if (e.description) display(embedDescription, markup(encodeHTML(e.description), { inEmbed: true, replaceEmojis: true })); + else hide(embedDescription); + if (e.color) embed.closest('.embed').style.borderColor = encodeHTML(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 ? '' + encodeHTML(e.author.name) + '' : '' + encodeHTML(e.author.name) + ''}`, 'flex'); + else hide(embedAuthor); + if (e.thumbnail && e.thumbnail.url) embedThumbnail.src = encodeHTML(e.thumbnail.url), embedThumbnail.style.display = 'block'; + else hide(embedThumbnail); + if (e.image && e.image.url) embedImage.src = encodeHTML(e.image.url), embedImage.style.display = 'block'; + else hide(embedImage); + if (e.footer && e.footer.text) display(embedFooter, ` + ${e.footer.icon_url ? '' : ''} + ${encodeHTML(e.footer.text)} + ${e.timestamp ? '•' + encodeHTML(tstamp(e.timestamp)) : ''}
`, 'flex'); + else if (e.timestamp) display(embedFooter, `${encodeHTML(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(encodeHTML(f.name), { inEmbed: true, replaceEmojis: true, inlineBlock: true })}
+
${markup(encodeHTML(f.value), { inEmbed: true, replaceEmojis: true })}
+
`; + } else { + el = embedFields.insertBefore(document.createElement('div'), null); + el.outerHTML = ` +
+
${markup(encodeHTML(f.name), { inEmbed: true, replaceEmojis: true, inlineBlock: true })}
+
${markup(encodeHTML(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); + embedCont.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 embedCont.classList.add('empty'); + } catch (e) { + error(e); + } + } + + editor.on('change', editor => { + try { update(toObj(editor.getValue())); } + catch (e) { + if (editor.getValue()) return; + embedCont.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(JSON.stringify(json, null, 4)); + 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); +}; diff --git a/index.html b/index.html index 890cd35..9d87d5a 100644 --- a/index.html +++ b/index.html @@ -3,6 +3,14 @@ + + + + + + + + Embed Builder