Dark Mode

Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Commit 6f910f4

Browse files
authored
1 parent 0001aa0 commit 6f910f4

File tree

1 file changed

+379
-0
lines changed
  • paste-html-subset.html

1 file changed

+379
-0
lines changed

paste-html-subset.html

Lines changed: 379 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,379 @@
1+
>
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>Rich Paste to HTML Subsettitle>
7+
<style>
8+
* {
9+
box-sizing: border-box;
10+
}
11+
12+
body {
13+
font-family: Helvetica, Arial, sans-serif;
14+
max-width: 1000px;
15+
margin: 0 auto;
16+
padding: 20px;
17+
line-height: 1.5;
18+
}
19+
20+
h1, h2 {
21+
color: #333;
22+
}
23+
24+
#paste-area {
25+
width: 100%;
26+
min-height: 200px;
27+
padding: 15px;
28+
border: 2px dashed #ccc;
29+
border-radius: 4px;
30+
margin-bottom: 15px;
31+
font-size: 16px;
32+
font-family: Helvetica, Arial, sans-serif;
33+
}
34+
35+
#paste-area:focus {
36+
outline: none;
37+
border-color: #4a88e5;
38+
}
39+
40+
.info-text {
41+
color: #666;
42+
margin-bottom: 20px;
43+
font-style: italic;
44+
}
45+
46+
#output-container {
47+
display: none;
48+
margin-top: 30px;
49+
}
50+
51+
#html-output {
52+
width: 100%;
53+
height: 200px;
54+
padding: 10px;
55+
border: 1px solid #ddd;
56+
border-radius: 4px;
57+
font-family: monospace;
58+
font-size: 16px;
59+
margin-bottom: 15px;
60+
white-space: pre-wrap;
61+
}
62+
63+
.button {
64+
background-color: #33aa44;
65+
color: white;
66+
border: none;
67+
padding: 10px 15px;
68+
font-size: 16px;
69+
border-radius: 4px;
70+
cursor: pointer;
71+
margin-right: 10px;
72+
margin-bottom: 20px;
73+
}
74+
75+
.button:hover {
76+
background-color: #2a9038;
77+
}
78+
79+
#preview-container {
80+
margin-top: 20px;
81+
border: 1px solid #eee;
82+
padding: 20px;
83+
border-radius: 4px;
84+
}
85+
86+
#preview-content {
87+
font-family: Helvetica, Arial, sans-serif;
88+
}
89+
90+
.success-message {
91+
color: green;
92+
font-weight: bold;
93+
}
94+
95+
.tag-list {
96+
background-color: #f8f8f8;
97+
border-left: 4px solid #4a88e5;
98+
padding: 15px;
99+
margin-bottom: 20px;
100+
}
101+
102+
.tag-list ul {
103+
margin: 0;
104+
padding-left: 20px;
105+
}
106+
style>
107+
head>
108+
<body>
109+
<h1>Rich Paste to HTML Subseth1>
110+
111+
<div class="tag-list">
112+
<h3>Supported HTML elementsh3>
113+
<ul>
114+
<li><strong>pstrong> - Paragraphsli>
115+
<li><strong>strongstrong> - Bold textli>
116+
<li><strong>emstrong> - Italic textli>
117+
<li><strong>blockquotestrong> - Quoted textli>
118+
<li><strong>codestrong> - Code snippetsli>
119+
<li><strong>astrong> - Links (href attribute is preserved)li>
120+
<li><strong>h1-h6strong> - Headingsli>
121+
<li><strong>ulstrong> - Unordered listsli>
122+
<li><strong>olstrong> - Ordered listsli>
123+
<li><strong>listrong> - List itemsli>
124+
<li><strong>dlstrong> - Definition listsli>
125+
<li><strong>dtstrong> - Definition termsli>
126+
<li><strong>ddstrong> - Definition descriptionsli>
127+
ul>
128+
div>
129+
130+
<div id="paste-area" contenteditable="true">div>
131+
<p class="info-text">Paste using Ctrl+V or +V to capture the rich text HTML. On mobile, use "Paste as text with formatting" from your context menu.p>
132+
133+
<div id="output-container">
134+
<h2>Clean HTML codeh2>
135+
<textarea id="html-output" readonly>textarea>
136+
<button id="copy-button" class="button">Copy HTMLbutton>
137+
<button id="clear-button" class="button">Clear allbutton>
138+
139+
<div id="preview-container">
140+
<h2>Previewh2>
141+
<iframe id="preview-frame" style="width: 100%; height: 300px; border: 1px solid #ddd; border-radius: 4px;">iframe>
142+
div>
143+
div>
144+
145+
<script>
146+
// Function to escape HTML entities for display
147+
function escapeHtml(html) {
148+
return html
149+
.replace(/&/g, "&")
150+
.replace(/</g, "<")
151+
.replace(/>/g, ">")
152+
.replace(/"/g, """)
153+
.replace(/'/g, "'");
154+
}
155+
156+
// Function to sanitize HTML and keep only allowed tags and attributes
157+
function sanitizeHtml(html) {
158+
const allowedTags = [
159+
'p', 'strong', 'em', 'blockquote', 'code', 'a',
160+
'h1', 'h2', 'h3', 'h4', 'h5', 'h6',
161+
'ul', 'ol', 'li', 'dl', 'dt', 'dd'
162+
];
163+
164+
// Use a simpler approach that creates a new DOM tree
165+
const parser = new DOMParser();
166+
const doc = parser.parseFromString(html, 'text/html');
167+
const result = document.createElement('div');
168+
169+
// Function to process a node and its children
170+
function cleanNode(node) {
171+
// For text nodes, just create a copy
172+
if (node.nodeType === Node.TEXT_NODE) {
173+
return document.createTextNode(node.textContent);
174+
}
175+
176+
// Skip non-element nodes
177+
if (node.nodeType !== Node.ELEMENT_NODE) {
178+
return null;
179+
}
180+
181+
const tagName = node.tagName.toLowerCase();
182+
183+
// If it's not an allowed tag, just process its children
184+
if (!allowedTags.includes(tagName)) {
185+
const fragment = document.createDocumentFragment();
186+
187+
for (const child of node.childNodes) {
188+
const cleanedChild = cleanNode(child);
189+
if (cleanedChild) {
190+
fragment.appendChild(cleanedChild);
191+
}
192+
}
193+
194+
return fragment;
195+
}
196+
197+
// Create a new clean element
198+
const cleanElement = document.createElement(tagName);
199+
200+
// Only copy the href attribute for links
201+
if (tagName === 'a' && node.hasAttribute('href')) {
202+
cleanElement.setAttribute('href', node.getAttribute('href'));
203+
}
204+
205+
// Process all child nodes
206+
for (const child of node.childNodes) {
207+
const cleanedChild = cleanNode(child);
208+
if (cleanedChild) {
209+
cleanElement.appendChild(cleanedChild);
210+
}
211+
}
212+
213+
return cleanElement;
214+
}
215+
216+
// Process the body content
217+
const bodyContent = doc.body;
218+
for (const childNode of bodyContent.childNodes) {
219+
const cleanedNode = cleanNode(childNode);
220+
if (cleanedNode) {
221+
result.appendChild(cleanedNode);
222+
}
223+
}
224+
225+
return result.innerHTML;
226+
}
227+
228+
// Function to pretty format HTML with indentation
229+
function formatHtml(html) {
230+
let formatted = '';
231+
let indent = 0;
232+
233+
// Function to get indentation string
234+
function getIndent(level) {
235+
// Ensure level is non-negative to avoid RangeError
236+
const safeLevel = Math.max(0, level);
237+
return ' '.repeat(safeLevel);
238+
}
239+
240+
// Split HTML by tags
241+
const parts = html.split(/(<\/?[^>]+>)/);
242+
243+
for (let i = 0; i < parts.length; i++) {
244+
const part = parts[i].trim();
245+
if (!part) continue;
246+
247+
// Check if it's a tag
248+
if (part.startsWith('<')) {
249+
// Closing tag
250+
if (part.startsWith(')) {
251+
indent = Math.max(0, indent - 1); // Prevent negative indent
252+
formatted += getIndent(indent) + part + '\n';
253+
}
254+
// Self-closing tag
255+
else if (part.endsWith('/>')) {
256+
formatted += getIndent(indent) + part + '\n';
257+
}
258+
// Opening tag
259+
else {
260+
formatted += getIndent(indent) + part + '\n';
261+
// Don't increase indent for inline elements
262+
if (!part.match(/<(strong|em|code|a)[^>]*>/i)) {
263+
indent++;
264+
}
265+
}
266+
}
267+
// Content
268+
else if (part) {
269+
formatted += getIndent(indent) + part + '\n';
270+
}
271+
}
272+
273+
return formatted;
274+
}
275+
276+
// Get DOM elements
277+
const pasteArea = document.getElementById('paste-area');
278+
const outputContainer = document.getElementById('output-container');
279+
const htmlOutput = document.getElementById('html-output');
280+
const previewContent = document.getElementById('preview-content');
281+
const copyButton = document.getElementById('copy-button');
282+
const clearButton = document.getElementById('clear-button');
283+
284+
// Handle paste event
285+
pasteArea.addEventListener('paste', function(e) {
286+
// Let the paste happen normally first
287+
setTimeout(function() {
288+
// Get pasted content
289+
const pastedContent = pasteArea.innerHTML;
290+
291+
if (pastedContent) {
292+
// Sanitize the HTML to only keep allowed tags
293+
const sanitizedHtml = sanitizeHtml(pastedContent);
294+
295+
// Format the HTML nicely
296+
const formattedHtml = formatHtml(sanitizedHtml);
297+
298+
// Display the HTML code with escaped characters
299+
htmlOutput.value = formattedHtml;
300+
301+
// Show the output container
302+
outputContainer.style.display = 'block';
303+
304+
try {
305+
// Get the iframe element
306+
const previewFrame = document.getElementById('preview-frame');
307+
308+
// Create a blob with the HTML content
309+
const blob = new Blob([`
310+
311+
312+
313+
314+
315+
323+
324+
325+
${sanitizedHtml}
326+
327+
328+
`], {type: 'text/html'});
329+
330+
// Create a URL for the blob
331+
const blobURL = URL.createObjectURL(blob);
332+
333+
// Set the iframe source to the blob URL
334+
previewFrame.src = blobURL;
335+
336+
// Clean up the blob URL when the iframe loads
337+
previewFrame.onload = function() {
338+
URL.revokeObjectURL(blobURL);
339+
};
340+
} catch (error) {
341+
console.error('Error updating preview:', error);
342+
}
343+
344+
// Show success message
345+
pasteArea.innerHTML = '

Content pasted. HTML extracted and cleaned.

'
;
346+
}
347+
}, 10);
348+
});
349+
350+
// Reset paste area when clicked
351+
pasteArea.addEventListener('focus', function() {
352+
if (pasteArea.textContent.includes('Content pasted')) {
353+
pasteArea.innerHTML = '';
354+
}
355+
});
356+
357+
// Copy HTML to clipboard
358+
copyButton.addEventListener('click', function() {
359+
htmlOutput.select();
360+
document.execCommand('copy');
361+
362+
const originalText = copyButton.textContent;
363+
copyButton.textContent = 'Copied!';
364+
365+
setTimeout(function() {
366+
copyButton.textContent = originalText;
367+
}, 1500);
368+
});
369+
370+
// Clear all content
371+
clearButton.addEventListener('click', function() {
372+
pasteArea.innerHTML = '';
373+
htmlOutput.value = '';
374+
document.getElementById('preview-frame').src = 'about:blank';
375+
outputContainer.style.display = 'none';
376+
});
377+
script>
378+
body>
379+
html>

0 commit comments

Comments
(0)