Skip to content

Commit 76dd6bf

Browse files
authored
1 parent 45f80ca commit 76dd6bf

1 file changed

Lines changed: 121 additions & 0 deletions

File tree

rtf-to-html.html

Lines changed: 121 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,121 @@
1+
<!DOCTYPE html>
2+
<html lang="en">
3+
<head>
4+
<meta charset="UTF-8">
5+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
6+
<title>Clipboard RTF to HTML Inspector</title>
7+
<style>
8+
body { font-family: Arial, sans-serif; margin: 20px; }
9+
h1 { font-size: 1.5em; }
10+
#controls { margin-bottom: 10px; }
11+
#output { margin-top: 20px; }
12+
.format-block { border: 1px solid #ccc; padding: 10px; margin-bottom: 10px; }
13+
.format-label { font-weight: bold; margin-bottom: 5px; }
14+
.preview { white-space: pre-wrap; word-wrap: break-word; background: #f9f9f9; padding: 10px; font-family: 'Courier New', monospace; }
15+
textarea { width: 100%; box-sizing: border-box; font-family: 'Courier New', monospace; resize: vertical; }
16+
/* Dark mode styles */
17+
.dark .preview { background: #000; color: #fff; }
18+
.dark textarea { background: #000; color: #fff; }
19+
.dark .format-block { border-color: #555; }
20+
</style>
21+
</head>
22+
<body>
23+
<h1>Clipboard RTF to HTML Inspector</h1>
24+
<div id="controls">
25+
<label><input type="checkbox" id="dark-mode"> Black background</label>
26+
<p>Press <strong>Cmd+V</strong> (Mac) or <strong>Ctrl+V</strong> (Windows) to paste and convert RTF to colorized HTML.</p>
27+
</div>
28+
<div id="output"></div>
29+
30+
<script>
31+
const darkModeCheckbox = document.getElementById('dark-mode');
32+
darkModeCheckbox.addEventListener('change', () => {
33+
document.body.classList.toggle('dark', darkModeCheckbox.checked);
34+
});
35+
36+
document.addEventListener('paste', function(event) {
37+
event.preventDefault();
38+
const output = document.getElementById('output');
39+
output.innerHTML = '';
40+
41+
const clipboardData = event.clipboardData || window.clipboardData;
42+
const rtf = clipboardData.getData('text/rtf');
43+
if (!rtf) {
44+
output.textContent = 'No RTF data available in clipboard.';
45+
return;
46+
}
47+
48+
// Normalize line breaks
49+
let body = rtf.replace(/\r\n|\r/g, '\n');
50+
51+
// Parse color table (colors[1] is first color)
52+
const colorTableMatch = /\\colortbl;([^}]*)}/.exec(body);
53+
const colors = [null];
54+
if (colorTableMatch) {
55+
const defs = colorTableMatch[1].split(';');
56+
defs.forEach(def => {
57+
const m = /\\red(\d+)\\green(\d+)\\blue(\d+)/.exec(def);
58+
if (m) {
59+
const r = +m[1], g = +m[2], b = +m[3];
60+
const hex = '#' + ((1 << 24) + (r << 16) + (g << 8) + b).toString(16).slice(1);
61+
colors.push(hex);
62+
} else {
63+
colors.push(null);
64+
}
65+
});
66+
}
67+
68+
// Mark paragraphs & remove controls
69+
body = body.replace(/\\pard[d]?/g, '\n')
70+
.replace(/\\cf(\d+)/g, '[CF$1]')
71+
.replace(/\\[^ \n]+ ?/g, '')
72+
.replace(/[{}]/g, '')
73+
.replace(/\\/g, '');
74+
75+
// Wrap color runs
76+
const parts = body.split(/\[CF(\d+)\]/);
77+
let currentColor = null;
78+
let html = '';
79+
parts.forEach((seg, i) => {
80+
if (i % 2 === 1) {
81+
currentColor = +seg;
82+
} else {
83+
const text = seg.replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;');
84+
if (currentColor && colors[currentColor]) {
85+
html += `<span style="color:${colors[currentColor]}">${text}</span>`;
86+
} else {
87+
html += text;
88+
}
89+
}
90+
});
91+
92+
// Newlines to <br>
93+
const htmlWithBreaks = html.split('\n').join('<br>');
94+
95+
// Preview
96+
const previewBlock = document.createElement('div');
97+
previewBlock.className = 'format-block';
98+
const previewLabel = document.createElement('div');
99+
previewLabel.className = 'format-label';
100+
previewLabel.textContent = 'Preview';
101+
const previewDiv = document.createElement('div');
102+
previewDiv.className = 'preview';
103+
previewDiv.innerHTML = htmlWithBreaks;
104+
previewBlock.append(previewLabel, previewDiv);
105+
output.appendChild(previewBlock);
106+
107+
// HTML markup
108+
const htmlBlock = document.createElement('div');
109+
htmlBlock.className = 'format-block';
110+
const htmlLabel = document.createElement('div');
111+
htmlLabel.className = 'format-label';
112+
htmlLabel.textContent = 'HTML Markup';
113+
const textarea = document.createElement('textarea');
114+
textarea.rows = 10;
115+
textarea.value = htmlWithBreaks;
116+
htmlBlock.append(htmlLabel, textarea);
117+
output.appendChild(htmlBlock);
118+
});
119+
</script>
120+
</body>
121+
</html>

0 commit comments

Comments
 (0)