2024-03-23 14:30:47 -05:00
<!DOCTYPE html>
< html lang = "en" >
< head >
< link rel = "icon" type = "image/x-icon" href = "/favicon.ico" >
< title > Sneed's Discord Colored Text Generator< / title >
< meta charset = "UTF-8" >
< meta name = "description" content = "Sneed's Discord Colored Text Generator" >
< meta name = "author" content = "sam-sneed+rebane" >
< style >
/*
2024-03-23 14:32:30 -05:00
This is licensed under the Samuel Public License. See the repo for details.
2024-03-23 14:30:47 -05:00
*/
html {
font-family: sans-serif;
background-color: #36393F;
text-align: center;
color: #FFF;
}
.container {
max-width: 500px;
margin: auto;
}
.flex {
height: 100%;
display: flex;
align-items: center;
justify-content: center;
}
#textarea {
width: 600px;
height: 200px;
border-radius: 5px;
resize: both;
overflow: auto;
text-align: left;
font-family: monospace;
background-color: #2F3136;
color: #B9BBBE;
border: #202225 1px solid;
padding: 5px;
display: inline-block;
white-space: pre-wrap;
font-size: 0.875rem;
line-height: 1.125rem;
text-indent: 0;
}
.button {
min-height: 32px;
min-width: 32px;
border: none;
border-radius: 3px;
color: #fff;
background-color: #4f545c;
font-size: 14px;
padding: 2px 16px;
cursor: pointer;
transition: background-color 250ms linear;
}
a,a:visited {
color: #00AFF4
}
.tooltip {
display: none;
position: absolute;
background-color: #3BA55D;
border: none;
border-radius: 3px;
color: #fff;
font-size: 14px;
padding: 8px 16px;
top: 0;
}
.ansi-1 { font-weight:700; text-decoration:none; }
.ansi-4 { font-weight:500; text-decoration:underline; }
.ansi-30 { color: #4f545c; }
.ansi-31 { color: #dc322f; }
.ansi-32 { color: #859900; }
.ansi-33 { color: #b58900; }
.ansi-34 { color: #268bd2; }
.ansi-35 { color: #d33682; }
.ansi-36 { color: #2aa198; }
.ansi-37 { color: #ffffff; }
.ansi-30-bg { background-color: #4f545c; }
.ansi-31-bg { background-color: #dc322f; }
.ansi-32-bg { background-color: #859900; }
.ansi-33-bg { background-color: #b58900; }
.ansi-34-bg { background-color: #268bd2; }
.ansi-35-bg { background-color: #d33682; }
.ansi-36-bg { background-color: #2aa198; }
.ansi-37-bg { background-color: #ffffff; }
.ansi-40 { background-color: #002b36; }
.ansi-41 { background-color: #cb4b16; }
.ansi-42 { background-color: #586e75; }
.ansi-43 { background-color: #657b83; }
.ansi-44 { background-color: #839496; }
.ansi-45 { background-color: #6c71c4; }
.ansi-46 { background-color: #93a1a1; }
.ansi-47 { background-color: #fdf6e3; }
< / style >
< / head >
< body >
< h1 > Sam Sneed's Discord < span style = "color:#5865F2" > Colored< / span > Text Generator< / h1 >
< div class = "container" >
< h3 > About< / h3 >
< p > This is a simple app that creates colored Discord messages using the ANSI color codes available on the latest Discord desktop versions.< / p >
< p > To use this, write your text, select parts of it and assign colors to them, then copy it using the button below, and send in a Discord message.< / p >
< h3 > Source Code< / h3 >
< p > This app runs entirely in your browser and the source code is freely available on < a href = "https://github.com/sam-sneed/discord-color-text" > GitHub< / a > . Shout out to kkrypt0nn for < a href = "https://gist.github.com/kkrypt0nn/a02506f3712ff2d1c8ca7c9e0aed7c06" > this guide< / a > .< / p >
< / div >
< h2 > Create your text< / h2 >
< button data-ansi = "0" class = "button style-button" > Reset All< / button >
< button data-ansi = "1" class = "button style-button ansi-1" > Bold< / button >
< button data-ansi = "4" class = "button style-button ansi-4" > Line< / button >
< br > < br >
< strong > FG< / strong >
< button data-ansi = "30" class = "button style-button ansi-30-bg" > < / button >
< button data-ansi = "31" class = "button style-button ansi-31-bg" > < / button >
< button data-ansi = "32" class = "button style-button ansi-32-bg" > < / button >
< button data-ansi = "33" class = "button style-button ansi-33-bg" > < / button >
< button data-ansi = "34" class = "button style-button ansi-34-bg" > < / button >
< button data-ansi = "35" class = "button style-button ansi-35-bg" > < / button >
< button data-ansi = "36" class = "button style-button ansi-36-bg" > < / button >
< button data-ansi = "37" class = "button style-button ansi-37-bg" > < / button >
< br > < br >
< strong > BG< / strong >
< button data-ansi = "40" class = "button style-button ansi-40" > < / button >
< button data-ansi = "41" class = "button style-button ansi-41" > < / button >
< button data-ansi = "42" class = "button style-button ansi-42" > < / button >
< button data-ansi = "43" class = "button style-button ansi-43" > < / button >
< button data-ansi = "44" class = "button style-button ansi-44" > < / button >
< button data-ansi = "45" class = "button style-button ansi-45" > < / button >
< button data-ansi = "46" class = "button style-button ansi-46" > < / button >
< button data-ansi = "47" class = "button style-button ansi-47" > < / button >
< br > < br >
< div class = "flex" > < div id = "textarea" contenteditable = "true" > Welcome to < span class = "ansi-33" > Rebane< / span > 's < span class = "ansi-45" > < span class = "ansi-37" > Discord< / span > < / span > < span class = "ansi-31" > C< / span > < span class = "ansi-32" > o< / span > < span class = "ansi-33" > l< / span > < span class = "ansi-34" > o< / span > < span class = "ansi-35" > r< / span > < span class = "ansi-36" > e< / span > < span class = "ansi-37" > d< / span > Text Generator!< / div > < / div >
< br >
< button class = "button copy" > Copy text as Discord formatted< / button >
< br >
< br >
< small > This is an unofficial tool, it is not made or endorsed by Discord.< / small >
< div class = "tooltip" > Tooltip< / div >
< script type = "text/javascript" >
const textarea = document.querySelector("#textarea");
const copybtn = document.querySelector(".button.copy");
const tooltip = document.querySelector(".tooltip");
const tooltipTexts = {
// FG
"30": "Dark Gray (33%)",
"31": "Red",
"32": "Yellowish Green",
"33": "Gold",
"34": "Light Blue",
"35": "Pink",
"36": "Teal",
"37": "White",
// BG
"40": "Blueish Black",
"41": "Rust Brown",
"42": "Gray (40%)",
"43": "Gray (45%)",
"44": "Light Gray (55%)",
"45": "Blurple",
"46": "Light Gray (60%)",
"47": "Cream White",
};
// Some basic escaping of pasted HTML tags, not ideal but good enough for this situation.
textarea.oninput = () => {
const base = textarea.innerHTML.replace(/< (\/?(br|span|span class="ansi-[0-9]*"))>/g,"[$1]");
if (base.includes("< ") || base.includes(">")) textarea.innerHTML = base.replace(/< . * ? > /g,"").replace(/[< >]/g,"").replace(/\[(\/?(br|span|span class="ansi-[0-9]*"))\]/g,"< $1>");
};
// https://stackoverflow.com/a/61237402
document.addEventListener('keydown', event => {
if (event.key === 'Enter') {
document.execCommand('insertLineBreak')
event.preventDefault()
}
});
document.querySelectorAll(".style-button").forEach((btn) => {
btn.onclick = () => {
if (!btn.dataset.ansi) {
textarea.innerText = textarea.innerText;
return;
}
const selection = window.getSelection();
const text = window.getSelection().toString();
const span = document.createElement("span");
span.innerText = text;
span.classList.add(`ansi-${btn.dataset.ansi}`);
const range = selection.getRangeAt(0);
range.deleteContents();
range.insertNode(span);
range.selectNodeContents(span);
selection.removeAllRanges();
selection.addRange(range);
};
btn.onmouseenter = () => {
if (!(btn.dataset.ansi > 4)) return;
const rect = btn.getBoundingClientRect();
tooltip.style.display = "block";
tooltip.innerText = tooltipTexts[btn.dataset.ansi];
tooltip.style.top = `${rect.top - 36}px`;
tooltip.style.left = `${rect.left - tooltip.clientWidth/2 + btn.clientWidth/2}px`;
};
btn.onmouseleave = () => {
tooltip.style.display = "none";
};
});
function nodesToANSI(nodes, states) {
let text = ""
for (const node of nodes) {
if (node.nodeType === 3) {
text += node.textContent;
continue;
}
if (node.nodeName === "BR") {
text += "\n";
continue;
}
const ansiCode = +(node.className.split("-")[1]);
const newState = Object.assign({}, states.at(-1));
if (ansiCode < 30 ) newState . st = ansiCode;
if (ansiCode >= 30 & & ansiCode < 40 ) newState . fg = ansiCode;
if (ansiCode >= 40) newState.bg = ansiCode;
states.push(newState)
text += `\x1b[${newState.st};${(ansiCode >= 40) ? newState.bg : newState.fg}m`;
text += nodesToANSI(node.childNodes, states);
states.pop()
text += `\x1b[0m`;
if (states.at(-1).fg !== 2) text += `\x1b[${states.at(-1).st};${states.at(-1).fg}m`;
if (states.at(-1).bg !== 2) text += `\x1b[${states.at(-1).st};${states.at(-1).bg}m`;
}
return text;
}
let copyCount = 0;
let copyTimeout = null;
copybtn.onclick = () => {
const toCopy = "```ansi\n" + nodesToANSI(textarea.childNodes, [{ fg: 2, bg: 2, st:2 }]) + "\n```";
navigator.clipboard.writeText(toCopy).then(() => {
if (copyTimeout) clearTimeout(copyTimeout);
const funnyCopyMessages = copybtn.innerText = ["Copied!", "Double Copy!", "Triple Copy!", "Dominating!!", "Rampage!!", "Mega Copy!!", "Unstoppable!!", "Wicked Sick!!", "Monster Copy!!!", "GODLIKE!!!", "BEYOND GODLIKE!!!!", Array(16).fill(0).reduce(p => p + String.fromCharCode(Math.floor(Math.random() * 65535)),"")];
copybtn.style.backgroundColor = (copyCount < = 8) ? "#3BA55D" : "#ED4245";
copybtn.innerText = funnyCopyMessages[copyCount];
copyCount = Math.min(11, copyCount + 1);
copyTimeout = setTimeout(() => {
copyCount = 0;
copybtn.style.backgroundColor = null;
copybtn.innerText = "Copy text as Discord formatted";
}, 2000)
}, (err) => {
// We don't need to stop the users if they get a little too excited about the button
if (copyCount > 2) return;
alert("Copying failed for some reason, let's try showing an alert, maybe you can copy it instead.");
alert(toCopy);
});
}
< / script >
< / body >
< / html >