+
-
-
`;
- 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 => `
+
+
+
+
+
+
+
+ ${encodeHTML(f.value)}
+
+
+
+
+
+ Inline
+
+
+
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
-
`;
- if (object.embed?.fields) fieldsEditor.innerHTML = object.embed.fields.filter(f => f && typeof f === 'object').map(f => `
-
-
-
-
-
-
-
- ${encodeHTML(f.value)}
-
-
-
-
-
- Inline
-
-
-
-
`).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, '
')
+ .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 = `
+
+
+ ${encodeHTML(object.content || '')}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+