
diff --git a/.gitignore b/.gitignore
index 5b7147b..82f2264 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1 +1,2 @@
-testing/
\ No newline at end of file
+test*
+.vscode
\ No newline at end of file
diff --git a/README.md b/README.md
index 924a1a7..4129862 100644
--- a/README.md
+++ b/README.md
@@ -4,12 +4,49 @@ Visualize embed or message content from JSON input or provided GUI editor.
This can be used for discord bot embed commands. It can also be intergreted into your Discord bot's website.
-Aside the JSON editor, it also includes a GUI editor which converts to JSON for simplicity.
-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.
-
-
+Aside the JSON editor, it also includes a GUI editor which converts to JSON for simplicity.
+
 
-Used at https://troybot.xyz/embed
-[To Do](https://github.com/Glitchii/embedbuilder/projects/3) | [Discussions](https://github.com/Glitchii/embedbuilder/discussions/1)
+# Supported URL Parameters
+
+## Editor param (/?editor=json)
+The GUI editor is used by default. If 'editor' parameter is set with the editor set to "json", the website will use the JSON editor by default instead when the website loads. Setting the value to anything else other than 'json' will be ignored.
+
+Example URL: https://glitchii.github.io/embedbuilder/?editor=json
+
+
+## Data param (/?data=...)
+A data param is used to specify the dafault json data to be used when the website loads. Note that value of the data param should be URL encoded first, then base64 encoded last. Calling the `jsonToBase64` fuction in /assets/script.js does that and returns the encoded JSON data.
+
+Example URL:
+https://glitchii.github.io/embedbuilder/?data=JTdCJTIyZW1iZWQlMjIlM0ElN0IlMjJ0aXRsZSUyMiUzQSUyMkxvcmVtJTIwaXBzdW0lMjIlMkMlMjJkZXNjcmlwdGlvbiUyMiUzQSUyMkRvbG9yJTIwc2l0JTIwYW1ldC4uLiUyMiUyQyUyMmNvbG9yJTIyJTNBMzkxMjklN0QlN0Q=
+
+## Other parameters
+
+```
+Parameter Description
+--------- -----------
+username= Used to set the deafult name of the bot.
+avatar= If a valid URL is given, that will be the avatar or icon of the bot.
+verified= Displays a verified badge on the bot tag when set to true.
+reversed= Reverse the preview and editors position.
+guitabs= Specify what gui tabs to display comma seperated.
+ Example: `guitabs=author` or `guitabs=image,footer`
+```
+### Example URL with all parameters:
+https://glitchii.github.io/embedbuilder/?username=Troy&verified=true&reversed=true&guitabs=image,footer&avatar=https://cdn.discordapp.com/avatars/663074487335649292/576eb5f13519b9da10ba7807bdd83fab.webp?size=128
+
+>## Intergretting into your website
+>You are free to use this in your website. Intergretting into your websites allows sending the embed to Discord with a few changes, and using 'formmaters' eg. '{ server_name }' or '{ user_name }' etc. A not so bad downside would be that you'd probably have to keep up with fixes and updates.
+If all you want is to have an embed builder in your website with no additional features and maybe using your own bot name and avatar, etc., you could iframe https://glitchii.github.io/embedbuilder into your website with a few of the parameters above if needed instead.
+
+
+
+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.
+
+ Used at https://troybot.xyz/embed
+[To Do](https://github.com/Glitchii/embedbuilder/projects/3) | [Discussions](https://github.com/Glitchii/embedbuilder/discussions/1)
\ No newline at end of file
diff --git a/assets/css/index.css b/assets/css/index.css
index 96a9239..385b26b 100644
--- a/assets/css/index.css
+++ b/assets/css/index.css
@@ -1,3 +1,43 @@
+@font-face {
+ font-family: DsWhitney;
+ src: local("Whitney"), url("../fonts/whitney-300.woff2"), url("../fonts/whitney-300.woff");
+ font-style: normal;
+ font-weight: 300;
+ font-display: swap;
+}
+
+@font-face {
+ font-family: DsWhitney;
+ src: local("Whitney"), url("../fonts/whitney-400.woff2"), url("../fonts/whitney-400.woff");
+ font-style: normal;
+ font-weight: 400;
+ font-display: swap;
+}
+
+@font-face {
+ font-family: DsWhitney;
+ src: local("Whitney"), url("../fonts/whitney-500.woff2"), url("../fonts/whitney-500.woff");
+ font-style: normal;
+ font-weight: 500;
+ font-display: swap;
+}
+
+@font-face {
+ font-family: DsWhitney;
+ src: local("Whitney"), url("../fonts/whitney-600.woff2"), url("../fonts/whitney-600.woff");
+ font-style: normal;
+ font-weight: 600;
+ font-display: swap;
+}
+
+@font-face {
+ font-family: DsWhitney;
+ src: local("Whitney"), url("../fonts/whitney-700.woff2"), url("../fonts/whitney-700.woff");
+ font-style: normal;
+ font-weight: 700;
+ font-display: swap;
+}
+
* {
outline: none;
box-sizing: border-box;
@@ -21,7 +61,7 @@ body {
line-height: 1;
margin: 0px;
padding: 0px;
- font-family: Whitney, "Helvetica Neue", Helvetica, Arial, sans-serif;
+ font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
overflow: hidden;
background-color: var(--fullEmbedBackground);
color: #fff;
@@ -113,6 +153,7 @@ body.gui .side1 .item.top {
overflow: auto;
scrollbar-color: #26272d #36393f;
scrollbar-width: thin;
+ font-family: DsWhitney, "Helvetica Neue", Helvetica, Arial, sans-serif;
}
::-webkit-scrollbar {
@@ -198,12 +239,12 @@ a img {
}
::-webkit-input-placeholder, body, button, input, select, textarea {
- font-family: Whitney, "Helvetica Neue", Helvetica, Arial, sans-serif;
+ font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
text-rendering: optimizelegibility;
}
::placeholder, body, button, input, select, textarea {
- font-family: Whitney, "Helvetica Neue", Helvetica, Arial, sans-serif;
+ font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
text-rendering: optimizelegibility;
}
@@ -233,6 +274,10 @@ img[alt] {
line-height: 1.375rem;
}
+.botTagVerified {
+ display: none;
+}
+
.contents {
position: static;
margin-left: 0px;
@@ -240,6 +285,14 @@ img[alt] {
text-indent: 0px;
}
+.contents.verified .botTag {
+ padding-left: 0;
+}
+
+.contents.verified .botTagVerified {
+ display: block;
+}
+
.avatar {
position: absolute;
left: 36px;
@@ -318,7 +371,9 @@ img[alt] {
}
.imageWrapper img {
- position: absolute;
+ /* position: absolute; */
+ width: 100%;
+ height: 100%;
}
.clickable {
@@ -355,7 +410,7 @@ img[alt] {
.embedGrid {
overflow: hidden;
- max-width: 516px;
+ max-width: 520px;
padding: 0.5rem 1rem 1rem 0.75rem;
display: inline-grid;
grid-template-columns: auto;
@@ -415,7 +470,7 @@ img[alt] {
max-width: 100%;
}
-.embedImage, .embedThumbnail {
+/* .embedImage, .embedThumbnail {
display: block;
object-fit: fill;
}
@@ -438,6 +493,24 @@ img[alt] {
justify-self: end;
width: 80px;
height: 80px;
+} */
+
+.embedImage img, .embedThumbnail img {
+ display: block;
+ object-fit: fill;
+ border-radius: 4px;
+}
+
+.embedThumbnail {
+ grid-area: 1 / 2 / 8 / 2;
+ margin-left: 16px;
+ margin-top: 8px;
+ flex-shrink: 0;
+ justify-self: end;
+ /* <> */
+ width: 80px;
+ height: 80px;
+ /* <> */
}
.embedFooter {
@@ -648,10 +721,79 @@ u {
white-space: pre-wrap;
}
-.markup pre, .markup>blockquote {
+.markup>blockquote, .markup pre {
max-width: 90%;
}
+.embed {
+ max-width: 100%;
+}
+
+/* Spinner */
+
+.spinner-container {
+ position: relative;
+ width: 80px;
+ height: 80px;
+ background-color: #2d2f34;
+ transform: translate(-50%, -50%);
+ top: -50%;
+ left: 50%;
+ display: none;
+}
+
+.embedImage > .spinner-container {
+ background: none;
+}
+
+.spinner-container .spinner {
+ display: -webkit-box;
+ display: flex;
+ justify-content: center;
+ align-items: center;
+ position: absolute;
+ left: 50%;
+ top: 50%;
+ transform: translate(-50%, -50%);
+}
+
+.spinner-container .spinner .inner {
+ position: relative;
+ display: inline-block;
+ width: 32px;
+ height: 32px;
+ contain: paint;
+}
+
+@keyframes spinner-wandering-cubes {
+ 25% {
+ transform: translateX(22px) rotate(-90deg) scale(.5);
+ }
+ 50% {
+ transform: translateX(22px) translateY(22px) rotate(-180deg);
+ }
+ 75% {
+ transform: translateX(0) translateY(22px) rotate(-270deg) scale(.5);
+ }
+ to {
+ transform: rotate(-1turn);
+ }
+}
+
+.spinner-container .spinner .inner .wanderingCubesItem {
+ background-color: #7981f0;
+ width: 10px;
+ height: 10px;
+ position: absolute;
+ top: 0;
+ left: 0;
+ animation: spinner-wandering-cubes 1.8s ease-in-out infinite;
+}
+
+.spinner-container .spinner .inner span:last-child {
+ animation-delay: -.9s;
+}
+
.markup pre {
border-radius: 4px;
padding: 0;
@@ -1728,6 +1870,10 @@ body.emptyEmbed.emptyContent .emptyTxt {
animation: colrsAn .1s ease-out;
box-shadow: 0px 5px 15px 0px #0000004f;
}
+ body.reversed .bottom .colrs {
+ left: 50%;
+ transform: translateX(-50%);
+ }
body.gui .side1 .item.top {
height: 85%;
max-width: 100%;
diff --git a/assets/js/script.js b/assets/js/script.js
index 2758928..e1e5e03 100644
--- a/assets/js/script.js
+++ b/assets/js/script.js
@@ -2,14 +2,45 @@
// 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,
+
+var params = new URL(location).searchParams,
+ dataSpecified = params.get('data'),
+ botName = params.get('username'),
+ botIcon = params.get('avatar'),
+ guiTabs = params.get('guitabs'),
+ useJsonEditor = params.get('editor') === 'json',
+ botVerified = params.get('verified') === 'true',
+ reverseColmns = params.get('reversed') === 'true',
+ activeFields, colNum = 1, num = 0, validationError,
+ jsonToBase64 = (jsonCode, withURL, redirect) => {
+ let data = jsonCode || json;
+ if (typeof data === 'object')
+ data = JSON.stringify(data);
+ data = btoa(escape(data));
+ if (withURL) {
+ let currentURL = new URL(location);
+ currentURL.searchParams.append('data', data);
+ redirect && (window.location = currentURL)
+ data = currentURL.href;
+ }
+ 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];
- }, json = {
+ },
+ mainKeys = ["author", "footer", "color", "thumbnail", "image", "fields", "title", "description", "url", "timestamp"],
+ jsonKeys = ["embed", "content", ...mainKeys],
+ 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:",
@@ -64,16 +95,31 @@ var activeFields, colNum = 1, num = 0,
}
]
}
- };
+ }
+
+if (dataSpecified)
+ window.json = base64ToJson();
window.onload = () => {
- document.querySelectorAll('img.clickable')
- .forEach(e => e.addEventListener('click', el => window.open(el.target.src)));
+ if (useJsonEditor) document.body.classList.remove('gui');
+ if (botName) document.querySelector('.username').textContent = botName;
+ if (botIcon) document.querySelector('.avatar').src = botIcon;
+ if (botVerified) document.querySelector('.msgEmbed > .contents').classList.add('verified');
+ if (reverseColmns) {
+ let side1 = document.querySelector('.side1');
+ side1.parentElement.insertBefore(side1.nextElementSibling, side1);
+ document.body.classList.add('reversed');
+ };
+
+ document.querySelectorAll('.clickable > img')
+ .forEach(e => e.parentElement.addEventListener('mouseup', 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),
gutters: ["CodeMirror-foldgutter", "CodeMirror-lint-markers"],
@@ -109,14 +155,18 @@ window.onload = () => {
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 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
+ 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")
+ 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) {
@@ -127,7 +177,11 @@ window.onload = () => {
return true;
}
}
- return error(`URL should have a protocol. Did you mean http://${makeShort(re[2], 30, 600).replace(' ', '')}?`);
+ invalid = true, err = (`URL should have a protocol. Did you mean http://${makeShort(re[2], 30, 600).replace(' ', '')}?`);
+ }
+ if (invalid) {
+ validationError = true;
+ return error(err);
}
return true;
}, markup = (txt, opts) => {
@@ -141,12 +195,12 @@ window.onload = () => {
.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);
+ else txt = txt.replace(/\`\`\`(\w{1,15})?\n((?:\n|.)+?)\`\`\`|\`\`(.+?)\`\`(?!\`)|\`([^\`]+?)\`/g, (m, w, x, y, z) => w && x ? `${x.trim()}
` : x ? `${x.trim()}
` : 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
${match.replace(/> /g, '')}