discord-color-text/index.html

288 lines
11 KiB
HTML
Raw Normal View History

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">&nbsp;</button>
<button data-ansi="31" class="button style-button ansi-31-bg">&nbsp;</button>
<button data-ansi="32" class="button style-button ansi-32-bg">&nbsp;</button>
<button data-ansi="33" class="button style-button ansi-33-bg">&nbsp;</button>
<button data-ansi="34" class="button style-button ansi-34-bg">&nbsp;</button>
<button data-ansi="35" class="button style-button ansi-35-bg">&nbsp;</button>
<button data-ansi="36" class="button style-button ansi-36-bg">&nbsp;</button>
<button data-ansi="37" class="button style-button ansi-37-bg">&nbsp;</button>
<br><br>
<strong>BG</strong>
<button data-ansi="40" class="button style-button ansi-40">&nbsp;</button>
<button data-ansi="41" class="button style-button ansi-41">&nbsp;</button>
<button data-ansi="42" class="button style-button ansi-42">&nbsp;</button>
<button data-ansi="43" class="button style-button ansi-43">&nbsp;</button>
<button data-ansi="44" class="button style-button ansi-44">&nbsp;</button>
<button data-ansi="45" class="button style-button ansi-45">&nbsp;</button>
<button data-ansi="46" class="button style-button ansi-46">&nbsp;</button>
<button data-ansi="47" class="button style-button ansi-47">&nbsp;</button>
<br><br>
<div class="flex"><div id="textarea" contenteditable="true">Welcome to&nbsp;<span class="ansi-33">Rebane</span>'s <span class="ansi-45"><span class="ansi-37">Discord</span></span>&nbsp;<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>&nbsp;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>