287 lines
11 KiB
HTML
287 lines
11 KiB
HTML
<!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>
|
|
/*
|
|
This is licensed under the Samuel Public License. See the repo for details.
|
|
*/
|
|
|
|
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>
|