Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- <!DOCTYPE html>
- <html lang="en">
- <head>
- <meta charset="UTF-8" />
- <meta name="viewport" content="width=device-width, initial-scale=1.0" />
- <title>Roblox Rich Text Editor</title>
- <link href="https://fonts.googleapis.com/css2?family=DM+Mono:ital,wght@0,400;0,500;1,400&family=DM+Sans:wght@400;500;600&display=swap" rel="stylesheet" />
- <style>
- *, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
- :root {
- --bg: #0e0f13;
- --surface: #16181f;
- --surface2: #1e2029;
- --border: #2a2d3a;
- --border2: #363a4d;
- --accent: #e8624a;
- --accent2: #f0855f;
- --text: #e4e6f0;
- --muted: #7a7f9a;
- --success: #4caf7d;
- --mono: 'DM Mono', monospace;
- --sans: 'DM Sans', system-ui, sans-serif;
- --radius: 8px;
- --radius-lg: 12px;
- }
- body {
- font-family: var(--sans);
- background: var(--bg);
- color: var(--text);
- min-height: 100vh;
- padding: 32px 24px 48px;
- }
- header {
- display: flex;
- align-items: baseline;
- gap: 12px;
- margin-bottom: 24px;
- }
- header h1 {
- font-size: 18px;
- font-weight: 600;
- letter-spacing: -0.01em;
- color: var(--text);
- }
- header span {
- font-size: 12px;
- color: var(--muted);
- font-family: var(--mono);
- background: var(--surface2);
- border: 1px solid var(--border);
- border-radius: 4px;
- padding: 2px 8px;
- }
- #app {
- max-width: 960px;
- margin: 0 auto;
- display: flex;
- flex-direction: column;
- gap: 14px;
- }
- /* ── Toolbar ── */
- .toolbar {
- display: flex;
- flex-wrap: wrap;
- gap: 6px;
- align-items: center;
- padding: 10px 14px;
- background: var(--surface);
- border: 1px solid var(--border);
- border-radius: var(--radius-lg);
- }
- .toolbar-group {
- display: flex;
- gap: 4px;
- align-items: center;
- }
- .sep {
- width: 1px;
- align-self: stretch;
- background: var(--border);
- margin: 0 6px;
- opacity: 0.6;
- }
- .tb-btn {
- border: 1px solid var(--border2);
- background: var(--surface2);
- color: var(--text);
- border-radius: var(--radius);
- padding: 5px 11px;
- font-size: 13px;
- font-family: var(--sans);
- cursor: pointer;
- transition: background 0.12s, border-color 0.12s, color 0.12s;
- line-height: 1;
- }
- .tb-btn:hover {
- background: #272a36;
- border-color: var(--accent);
- color: var(--accent2);
- }
- .tb-btn:active { transform: scale(0.96); }
- .tb-btn.b { font-weight: 700; }
- .tb-btn.i { font-style: italic; }
- .tb-btn.u { text-decoration: underline; }
- .tb-btn.s { text-decoration: line-through; }
- .tb-btn.uc { text-transform: uppercase; font-size: 11px; letter-spacing: 0.05em; }
- .tb-btn.sc { font-variant: small-caps; }
- .color-input-wrap {
- display: flex;
- align-items: center;
- gap: 7px;
- }
- .color-input-wrap label {
- font-size: 12px;
- color: var(--muted);
- font-family: var(--mono);
- white-space: nowrap;
- }
- input[type=color] {
- width: 28px;
- height: 28px;
- border-radius: 6px;
- border: 1px solid var(--border2);
- padding: 2px;
- cursor: pointer;
- background: var(--surface2);
- }
- select.tb-select {
- border: 1px solid var(--border2);
- background: var(--surface2);
- color: var(--text);
- border-radius: var(--radius);
- padding: 5px 8px;
- font-size: 13px;
- cursor: pointer;
- font-family: var(--sans);
- outline: none;
- transition: border-color 0.12s;
- }
- select.tb-select:hover { border-color: var(--accent); }
- /* ── Panels ── */
- .panels {
- display: grid;
- grid-template-columns: 1fr 1fr;
- gap: 14px;
- }
- .panel-label {
- font-size: 10px;
- font-weight: 600;
- color: var(--muted);
- text-transform: uppercase;
- letter-spacing: 0.1em;
- margin-bottom: 7px;
- font-family: var(--mono);
- }
- textarea#editor {
- width: 100%;
- min-height: 200px;
- border: 1px solid var(--border);
- border-radius: var(--radius-lg);
- padding: 14px;
- font-family: var(--mono);
- font-size: 13px;
- color: var(--text);
- background: var(--surface);
- resize: vertical;
- line-height: 1.65;
- outline: none;
- transition: border-color 0.15s;
- caret-color: var(--accent);
- }
- textarea#editor::placeholder { color: #3d4158; }
- textarea#editor:focus { border-color: var(--accent); }
- .preview-box {
- min-height: 200px;
- border: 1px solid var(--border);
- border-radius: var(--radius-lg);
- padding: 14px;
- background: var(--surface);
- font-size: 15px;
- line-height: 1.75;
- color: var(--text);
- word-break: break-word;
- position: relative;
- }
- .preview-box:empty::before {
- content: 'Preview appears here…';
- color: #3d4158;
- font-size: 14px;
- font-family: var(--mono);
- }
- /* ── Actions ── */
- .actions {
- display: flex;
- gap: 8px;
- flex-wrap: wrap;
- align-items: center;
- }
- .btn-copy {
- border: 1px solid var(--accent);
- background: var(--accent);
- color: #fff;
- border-radius: var(--radius);
- padding: 7px 16px;
- font-size: 13px;
- font-weight: 600;
- cursor: pointer;
- font-family: var(--sans);
- transition: background 0.12s, transform 0.1s;
- }
- .btn-copy:hover { background: var(--accent2); border-color: var(--accent2); }
- .btn-copy:active { transform: scale(0.97); }
- .btn-clear {
- border: 1px solid var(--border2);
- background: transparent;
- color: var(--muted);
- border-radius: var(--radius);
- padding: 7px 16px;
- font-size: 13px;
- cursor: pointer;
- font-family: var(--sans);
- transition: color 0.12s, border-color 0.12s;
- }
- .btn-clear:hover { color: var(--text); border-color: var(--border2); }
- .copy-msg {
- font-size: 13px;
- color: var(--success);
- font-family: var(--mono);
- opacity: 0;
- transition: opacity 0.2s;
- }
- .copy-msg.show { opacity: 1; }
- .char-count {
- font-size: 12px;
- color: var(--muted);
- font-family: var(--mono);
- margin-left: auto;
- }
- /* ── Tag reference ── */
- .tag-ref {
- background: var(--surface);
- border: 1px solid var(--border);
- border-radius: var(--radius-lg);
- padding: 10px 14px;
- display: flex;
- flex-wrap: wrap;
- gap: 6px;
- align-items: center;
- }
- .tag-ref-label {
- font-size: 11px;
- color: var(--muted);
- font-family: var(--mono);
- margin-right: 4px;
- text-transform: uppercase;
- letter-spacing: 0.06em;
- }
- .tag-pill {
- font-size: 12px;
- font-family: var(--mono);
- background: var(--surface2);
- border: 1px solid var(--border);
- border-radius: 999px;
- padding: 3px 10px;
- color: var(--muted);
- transition: color 0.12s, border-color 0.12s;
- cursor: default;
- }
- .tag-pill:hover { color: var(--accent2); border-color: var(--accent); }
- /* ── Accent line at top ── */
- body::before {
- content: '';
- display: block;
- height: 3px;
- background: linear-gradient(90deg, var(--accent), #b060f8 60%, transparent);
- position: fixed;
- top: 0; left: 0; right: 0;
- z-index: 10;
- }
- @media (max-width: 640px) {
- .panels { grid-template-columns: 1fr; }
- }
- </style>
- </head>
- <body>
- <div id="app">
- <header>
- <h1>Roblox Rich Text Editor</h1>
- <span>RichText = true</span>
- </header>
- <div class="toolbar">
- <div class="toolbar-group">
- <button class="tb-btn b" onclick="wrap('<b>','</b>')">B</button>
- <button class="tb-btn i" onclick="wrap('<i>','</i>')">I</button>
- <button class="tb-btn u" onclick="wrap('<u>','</u>')">U</button>
- <button class="tb-btn s" onclick="wrap('<s>','</s>')">S</button>
- </div>
- <div class="sep"></div>
- <div class="toolbar-group">
- <button class="tb-btn uc" onclick="wrap('<uc>','</uc>')">ABC</button>
- <button class="tb-btn sc" onclick="wrap('<sc>','</sc>')">Sc</button>
- </div>
- <div class="sep"></div>
- <div class="toolbar-group color-input-wrap">
- <label>color</label>
- <input type="color" id="color-pick" value="#FF7800" />
- <button class="tb-btn" onclick="wrapColor()">Apply</button>
- </div>
- <div class="sep"></div>
- <div class="toolbar-group color-input-wrap">
- <label>size</label>
- <select class="tb-select" id="size-pick">
- <option value="12">12</option>
- <option value="16">16</option>
- <option value="20" selected>20</option>
- <option value="24">24</option>
- <option value="28">28</option>
- <option value="32">32</option>
- <option value="40">40</option>
- <option value="48">48</option>
- </select>
- <button class="tb-btn" onclick="wrapSize()">Apply</button>
- </div>
- <div class="sep"></div>
- <div class="toolbar-group">
- <button class="tb-btn" onclick="wrap('<mark color="#009966" transparency="0">','</mark>')">Mark</button>
- <button class="tb-btn" onclick="insertTag('<br/>')">BR</button>
- <button class="tb-btn" onclick="wrap('<stroke color="#00A2FF" thickness="2">','</stroke>')">Stroke</button>
- </div>
- <div class="sep"></div>
- <div class="toolbar-group color-input-wrap">
- <label>face</label>
- <select class="tb-select" id="face-pick">
- <option>Michroma</option>
- <option>GothamSSm</option>
- <option>Roboto</option>
- <option>SourceSansPro</option>
- <option>Oswald</option>
- <option>Creepster</option>
- <option>Bangers</option>
- </select>
- <button class="tb-btn" onclick="wrapFace()">Apply</button>
- </div>
- </div>
- <div class="panels">
- <div>
- <div class="panel-label">Rich text markup</div>
- <textarea id="editor" spellcheck="false" placeholder="Type or paste your rich text string here...
- Example: Hello <b>world</b>, this is <font color="#FF7800">orange</font>!"></textarea>
- </div>
- <div>
- <div class="panel-label">Rendered preview</div>
- <div class="preview-box" id="preview"></div>
- </div>
- </div>
- <div class="actions">
- <button class="btn-copy" onclick="copyText()">Copy markup</button>
- <button class="btn-clear" onclick="clearAll()">Clear</button>
- <span class="copy-msg" id="copy-msg">✓ Copied!</span>
- <span class="char-count" id="char-count">0 chars</span>
- </div>
- <div class="tag-ref">
- <span class="tag-ref-label">Tags</span>
- <span class="tag-pill"><b></span>
- <span class="tag-pill"><i></span>
- <span class="tag-pill"><u></span>
- <span class="tag-pill"><s></span>
- <span class="tag-pill"><uc></span>
- <span class="tag-pill"><sc></span>
- <span class="tag-pill"><br/></span>
- <span class="tag-pill"><font color=""></span>
- <span class="tag-pill"><font size=""></span>
- <span class="tag-pill"><font face=""></span>
- <span class="tag-pill"><font weight=""></span>
- <span class="tag-pill"><font transparency=""></span>
- <span class="tag-pill"><mark></span>
- <span class="tag-pill"><stroke></span>
- </div>
- </div>
- <script>
- const editor = document.getElementById('editor');
- const preview = document.getElementById('preview');
- const charCount = document.getElementById('char-count');
- function richToHtml(str) {
- return str
- .replace(/&/g, '&')
- .replace(/</g, '<TEMP_LT>')
- .replace(/>/g, '<TEMP_GT>')
- .replace(/"/g, '"')
- .replace(/'/g, "'")
- .replace(/<b>([\s\S]*?)<\/b>/gi, '<strong>$1</strong>')
- .replace(/<i>([\s\S]*?)<\/i>/gi, '<em>$1</em>')
- .replace(/<u>([\s\S]*?)<\/u>/gi, '<span style="text-decoration:underline">$1</span>')
- .replace(/<s>([\s\S]*?)<\/s>/gi, '<span style="text-decoration:line-through">$1</span>')
- .replace(/<uppercase>([\s\S]*?)<\/uppercase>/gi, '<span style="text-transform:uppercase">$1</span>')
- .replace(/<uc>([\s\S]*?)<\/uc>/gi, '<span style="text-transform:uppercase">$1</span>')
- .replace(/<smallcaps>([\s\S]*?)<\/smallcaps>/gi, '<span style="font-variant:small-caps">$1</span>')
- .replace(/<sc>([\s\S]*?)<\/sc>/gi, '<span style="font-variant:small-caps">$1</span>')
- .replace(/<br\/>/gi, '<br>')
- .replace(/<font([^>]*)>([\s\S]*?)<\/font>/gi, (m, attrs, content) => {
- let style = '';
- const color = attrs.match(/color\s*=\s*["']([^"']+)["']/i);
- const size = attrs.match(/size\s*=\s*["']([^"']+)["']/i);
- const face = attrs.match(/face\s*=\s*["']([^"']+)["']/i);
- const family = attrs.match(/family\s*=\s*["']([^"']+)["']/i);
- const weight = attrs.match(/weight\s*=\s*["']([^"']+)["']/i);
- const trans = attrs.match(/transparency\s*=\s*["']([^"']+)["']/i);
- if (color) style += `color:${color[1]};`;
- if (size) style += `font-size:${size[1]}px;`;
- if (face) style += `font-family:${face[1]},sans-serif;`;
- if (family) style += `font-family:${family[1]},sans-serif;`;
- if (weight) {
- const wmap = {thin:'100',extralight:'200',light:'300',regular:'400',medium:'500',semibold:'600',bold:'700',extrabold:'800',heavy:'900'};
- style += `font-weight:${wmap[weight[1].toLowerCase()] || weight[1]};`;
- }
- if (trans) style += `opacity:${1 - parseFloat(trans[1])};`;
- return `<span style="${style}">${content}</span>`;
- })
- .replace(/<mark([^>]*)>([\s\S]*?)<\/mark>/gi, (m, attrs, content) => {
- const color = (attrs.match(/color\s*=\s*["']([^"']+)["']/i)||[])[1] || '#FFD700';
- const trans = (attrs.match(/transparency\s*=\s*["']([^"']+)["']/i)||[])[1] || '0';
- return `<mark style="background:${color};opacity:${1-parseFloat(trans)};padding:0 2px;border-radius:2px;">${content}</mark>`;
- })
- .replace(/<stroke([^>]*)>([\s\S]*?)<\/stroke>/gi, (m, attrs, content) => {
- const color = (attrs.match(/color\s*=\s*["']([^"']+)["']/i)||[])[1] || '#000';
- const thick = (attrs.match(/thickness\s*=\s*["']([^"']+)["']/i)||[])[1] || '1';
- return `<span style="-webkit-text-stroke:${thick}px ${color}">${content}</span>`;
- })
- .replace(/<!--[\s\S]*?-->/g, '')
- .replace(/<TEMP_LT>/g, '<')
- .replace(/<TEMP_GT>/g, '>');
- }
- function updatePreview() {
- const val = editor.value;
- charCount.textContent = val.length + ' chars';
- try { preview.innerHTML = richToHtml(val); }
- catch(e) { preview.textContent = val; }
- }
- editor.addEventListener('input', updatePreview);
- function getSelection() {
- return { start: editor.selectionStart, end: editor.selectionEnd, text: editor.value.substring(editor.selectionStart, editor.selectionEnd) };
- }
- function wrap(open, close) {
- const { start, end, text } = getSelection();
- const newVal = editor.value.substring(0, start) + open + text + close + editor.value.substring(end);
- editor.value = newVal;
- editor.focus();
- editor.setSelectionRange(start + open.length + text.length + close.length, start + open.length + text.length + close.length);
- updatePreview();
- }
- function insertTag(tag) {
- const s = editor.selectionStart;
- editor.value = editor.value.substring(0, s) + tag + editor.value.substring(s);
- editor.focus();
- editor.setSelectionRange(s + tag.length, s + tag.length);
- updatePreview();
- }
- function wrapColor() { wrap(`<font color="${document.getElementById('color-pick').value}">`, '</font>'); }
- function wrapSize() { wrap(`<font size="${document.getElementById('size-pick').value}">`, '</font>'); }
- function wrapFace() { wrap(`<font face="${document.getElementById('face-pick').value}">`, '</font>'); }
- function copyText() {
- navigator.clipboard.writeText(editor.value).then(() => {
- const msg = document.getElementById('copy-msg');
- msg.classList.add('show');
- setTimeout(() => msg.classList.remove('show'), 1600);
- });
- }
- function clearAll() { editor.value = ''; updatePreview(); }
- updatePreview();
- </script>
- </body>
- </html>
Advertisement
Add Comment
Please, Sign In to add comment