SHOW:
|
|
- or go back to the newest paste.
| 1 | javascript:(function() {
| |
| 2 | console.log('[Reddit BBCode Tool] Activated - Click copy buttons on posts/comments');
| |
| 3 | const USE_DATA_URL_FOR_IMAGES = false; | |
| 4 | ||
| 5 | function htmlToBBCode(html) {
| |
| 6 | const textarea = document.createElement('textarea');
| |
| 7 | textarea.innerHTML = html; | |
| 8 | const decoded = textarea.value; | |
| 9 | return decoded | |
| 10 | .replace(/<h1>([\s\S]+?)<\/h1>/g, '[HEADING=1][b]$1[/b][/HEADING]\n\n') | |
| 11 | .replace(/<h2>([\s\S]+?)<\/h2>/g, '[HEADING=2][b]$1[/b][/HEADING]\n\n') | |
| 12 | .replace(/<h3>([\s\S]+?)<\/h3>/g, '[HEADING=3][b]$1[/b][/HEADING]\n\n') | |
| 13 | .replace(/<a href="([^"]+)"[^>]*>([\s\S]+?)<\/a>/g, (m, url, text) => {
| |
| 14 | const cleanUrl = url.replace(/&/g, '&'); | |
| 15 | const cleanText = text.replace(/<(?!\/?(?:b|i|s|u)\b)[^>]*>/gi, ''); | |
| 16 | return `[u][url='${cleanUrl}']${cleanText}[/url][/u]`;
| |
| 17 | }) | |
| 18 | .replace(/<img src="([^"]+)"[^>]*>/g, '[img]$1[/img]') | |
| 19 | .replace(/<(b|strong)>([^<]+)<\/(b|strong)>/g, '[b]$2[/b]') | |
| 20 | .replace(/<(i|em)>([^<]+)<\/(i|em)>/g, '[i]$2[/i]') | |
| 21 | .replace(/<s>([^<]+)<\/s>/g, '[s]$1[/s]') | |
| 22 | .replace(/<blockquote>([\s\S]+?)<\/blockquote>/g, '[quote]$1[/quote]') | |
| 23 | .replace(/<pre>([\s\S]+?)<\/pre>/g, '[code]$1[/code]') | |
| 24 | .replace(/<code>([^<]+)<\/code>/g, '[icode]$1[/icode]') | |
| 25 | .replace(/<ol>([\s\S]+?)<\/ol>/g, (match, listContent) => '[LIST=1]\n' + listContent.replace(/<li>([\s\S]+?)<\/li>/g, '[*] $1\n') + '[/LIST]') | |
| 26 | .replace(/<ul>([\s\S]+?)<\/ul>/g, (match, listContent) => '[LIST]\n' + listContent.replace(/<li>([\s\S]+?)<\/li>/g, '[*] $1\n') + '[/LIST]') | |
| 27 | .replace(/<\/p>|<\s*br\s*\/?>/gi, '\n') | |
| 28 | .replace(/<\/?[^>]+>/g, '') | |
| 29 | .replace(/&/g, '&') | |
| 30 | .replace(/\n{3,}/g, '\n\n')
| |
| 31 | .replace(/^\s+|\s+$/g, ''); | |
| 32 | } | |
| 33 | ||
| 34 | function extractTextWithLinks(html) {
| |
| 35 | const parser = new DOMParser(); | |
| 36 | const doc = parser.parseFromString(html, 'text/html'); | |
| 37 | let text = doc.body.innerText.trim(); | |
| 38 | const links = Array.from(doc.body.querySelectorAll('a'));
| |
| 39 | const images = Array.from(doc.body.querySelectorAll('img'));
| |
| 40 | let reconstructedText = text; | |
| 41 | links.forEach(link => {
| |
| 42 | const linkText = link.textContent.trim(); | |
| 43 | const linkUrl = link.href; | |
| 44 | reconstructedText = reconstructedText.replace(linkText, `[u][url]${linkUrl}[/url][/u]`);
| |
| 45 | }); | |
| 46 | images.forEach(image => {
| |
| 47 | let src = image.src; | |
| 48 | if (USE_DATA_URL_FOR_IMAGES) {
| |
| 49 | src = image.getAttribute('data-url') || src.replace(/preview\\.redd\\.it/, 'i.redd.it');
| |
| 50 | } | |
| 51 | reconstructedText += `\n[img]${src}[/img]`;
| |
| 52 | }); | |
| 53 | return reconstructedText.trim(); | |
| 54 | } | |
| 55 | ||
| 56 | function extractPostUrl(element) {
| |
| 57 | return element.querySelector('a.bylink[href*="/comments/"]')?.href || element.querySelector('a.title[href*="/comments/"]')?.href || (element.hasAttribute('data-permalink') ? `https://www.reddit.com${element.getAttribute('data-permalink')}` : window.location.href);
| |
| 58 | } | |
| 59 | ||
| 60 | function getParentComments(commentElement, maxDepth = 10) {
| |
| 61 | - | const parentContainer = currentComment.parentElement.closest('.sitetable.listing, .child') || currentComment.parentElement.closest('shreddit-comment');
|
| 61 | + | |
| 62 | - | if (!parentContainer) break; |
| 62 | + | |
| 63 | - | const parentComment = parentContainer.parentElement.closest('.thing.comment, shreddit-comment');
|
| 63 | + | |
| 64 | - | if (!parentComment) break; |
| 64 | + | |
| 65 | - | const commentId = parentComment.id || parentComment.getAttribute('data-fullname');
|
| 65 | + | |
| 66 | - | if (seenIds.has(commentId)) break; |
| 66 | + | let parentId = null; |
| 67 | - | seenIds.add(commentId); |
| 67 | + | let parentCommentElement = null; |
| 68 | - | const datetime = parentComment.querySelector('time')?.getAttribute('title') || parentComment.querySelector('time')?.getAttribute('datetime') || 'Unknown';
|
| 68 | + | |
| 69 | - | const contentHtml = parentComment.querySelector('.md, div[slot="comment"]');
|
| 69 | + | |
| 70 | - | const permalink = parentComment.querySelector('a.bylink')?.href || (parentComment.getAttribute('permalink') ? `https://www.reddit.com${parentComment.getAttribute('permalink')}` : window.location.href);
|
| 70 | + | parentId = currentComment.getAttribute('parentid');
|
| 71 | if (parentId && parentId.startsWith('t1_')) {
| |
| 72 | parentCommentElement = document.querySelector(`shreddit-comment[thingid="${parentId}"]`);
| |
| 73 | } | |
| 74 | - | currentComment = parentComment; |
| 74 | + | |
| 75 | ||
| 76 | if (!parentCommentElement) {
| |
| 77 | const parentLink = currentComment.querySelector('a[data-parent-id]');
| |
| 78 | if (parentLink) {
| |
| 79 | parentId = parentLink.getAttribute('data-parent-id');
| |
| 80 | if (parentId.startsWith('t1_')) {
| |
| 81 | parentCommentElement = document.querySelector(`.thing.comment[data-fullname="${parentId}"]`);
| |
| 82 | } | |
| 83 | } else {
| |
| 84 | ||
| 85 | const parentThing = currentComment.parentElement.closest('.thing.comment');
| |
| 86 | if (parentThing) {
| |
| 87 | parentId = parentThing.getAttribute('data-fullname') || parentThing.id;
| |
| 88 | parentCommentElement = parentThing; | |
| 89 | } | |
| 90 | } | |
| 91 | } | |
| 92 | ||
| 93 | if (!parentCommentElement || !parentId || seenIds.has(parentId)) {
| |
| 94 | break; | |
| 95 | } | |
| 96 | seenIds.add(parentId); | |
| 97 | ||
| 98 | const datetime = parentCommentElement.querySelector('time')?.getAttribute('title') || parentCommentElement.querySelector('time')?.getAttribute('datetime') || 'Unknown';
| |
| 99 | const contentHtml = parentCommentElement.querySelector('.md, div[slot="comment"]');
| |
| 100 | const permalink = parentCommentElement.querySelector('a.bylink')?.href || (parentCommentElement.getAttribute('permalink') ? `https://www.reddit.com${parentCommentElement.getAttribute('permalink')}` : window.location.href);
| |
| 101 | const contentText = contentHtml ? extractTextWithLinks(contentHtml.innerHTML) : 'No content'; | |
| 102 | const contentBBCode = htmlToBBCode(contentText); | |
| 103 | ||
| 104 | parents.unshift({ permalink, spoilerContent: `[spoiler="text"]\nCommented on ${datetime}\n\n${contentBBCode}\n[/spoiler]` });
| |
| 105 | currentComment = parentCommentElement; | |
| 106 | } | |
| 107 | return parents; | |
| 108 | } | |
| 109 | ||
| 110 | function createDepthDropdown(maxDepth, callback, button) {
| |
| 111 | const existingDropdown = document.querySelector('.depth-dropdown');
| |
| 112 | if (existingDropdown) existingDropdown.remove(); | |
| 113 | const dropdown = document.createElement('select');
| |
| 114 | dropdown.className = 'depth-dropdown'; | |
| 115 | dropdown.style.position = 'absolute'; | |
| 116 | dropdown.style.marginLeft = '5px'; | |
| 117 | dropdown.style.padding = '2px'; | |
| 118 | dropdown.style.fontSize = '12px'; | |
| 119 | for (let i = 0; i <= maxDepth; i++) {
| |
| 120 | const option = document.createElement('option');
| |
| 121 | option.value = i; | |
| 122 | option.textContent = i === 0 ? 'Only this' : `${i} parent${i > 1 ? 's' : ''}`;
| |
| 123 | dropdown.appendChild(option); | |
| 124 | } | |
| 125 | dropdown.addEventListener('change', () => {
| |
| 126 | callback(parseInt(dropdown.value)); | |
| 127 | dropdown.remove(); | |
| 128 | }); | |
| 129 | dropdown.addEventListener('blur', () => dropdown.remove());
| |
| 130 | button.insertAdjacentElement('afterend', dropdown);
| |
| 131 | dropdown.focus(); | |
| 132 | } | |
| 133 | ||
| 134 | function createCopyButtons(element) {
| |
| 135 | - | const mediaContent = [ `Posted on ${datetime}`, ...(imgSection ? [imgSection] : []), ...(videoUrl ? [videoUrl] : []) ];
|
| 135 | + | |
| 136 | existingButtons.forEach(btn => btn.remove()); | |
| 137 | const copyButton = document.createElement('button');
| |
| 138 | copyButton.textContent = '📋'; | |
| 139 | copyButton.title = 'Copy content to BBCode'; | |
| 140 | copyButton.className = 'copy-btn'; | |
| 141 | copyButton.style.cssText = 'cursor:pointer;background:green;color:white;margin-left:5px;padding:2px 6px;border:none;border-radius:4px;'; | |
| 142 | const nestedButton = document.createElement('button');
| |
| 143 | nestedButton.textContent = '📋'; | |
| 144 | nestedButton.title = 'Copy content with nested parents'; | |
| 145 | nestedButton.className = 'nested-btn'; | |
| 146 | nestedButton.style.cssText = 'cursor:pointer;background:blue;color:white;margin-left:5px;padding:2px 6px;border:none;border-radius:4px;'; | |
| 147 | if (element.tagName.toLowerCase() === 'shreddit-post' || element.classList.contains('thing') && element.classList.contains('link')) {
| |
| 148 | const url = extractPostUrl(element); | |
| 149 | let header = '', body = '', images = new Set(); | |
| 150 | const title = element.querySelector('h1[slot="title"], .title > a')?.textContent.trim() || '';
| |
| 151 | const flair = element.querySelector('.linkflairlabel span')?.textContent.trim() || '';
| |
| 152 | const datetime = element.querySelector('time')?.getAttribute('title') || element.querySelector('time')?.getAttribute('datetime') || 'Unknown';
| |
| 153 | const videoUrl = element.dataset.url || element.getAttribute('data-url');
| |
| 154 | header = `${flair ? `[i][${flair}][/i] ` : ''}[b]${title}[/b]\n\n${url}\n\n`;
| |
| 155 | Array.from(element.querySelectorAll('gallery-carousel li img, .media-preview img'))
| |
| 156 | .forEach(img => {
| |
| 157 | let src = img.src; | |
| 158 | if (USE_DATA_URL_FOR_IMAGES) {
| |
| 159 | src = img.getAttribute('data-url') || src.replace(/preview\\.redd\\.it/, 'i.redd.it');
| |
| 160 | } | |
| 161 | if (!src.includes('redditstatic.com/video-') && !src.includes('old.reddit.com/static/checkmark.svg')) {
| |
| 162 | images.add(src); | |
| 163 | } | |
| 164 | }); | |
| 165 | const textBody = element.querySelector('div[slot="text-body"], .md');
| |
| 166 | if (textBody) body += htmlToBBCode(extractTextWithLinks(textBody.innerHTML)); | |
| 167 | const imgSection = Array.from(images).map(u => `[img]${u}[/img]`).join('\n');
| |
| 168 | const mediaContent = [`Posted on ${datetime}`, ...(imgSection ? [imgSection] : []), ...(videoUrl ? [videoUrl] : [])];
| |
| 169 | const fullPayload = `${header}[spoiler="text"]\n${mediaContent.join('\n\n')}\n\n${body.trim()}\n[/spoiler]`;
| |
| 170 | copyButton.addEventListener('click', (e) => {
| |
| 171 | e.preventDefault(); | |
| 172 | - | const items = [ ...parents.slice(0, depth), { permalink, spoilerContent } ];
|
| 172 | + | |
| 173 | navigator.clipboard.writeText(fullPayload.replace(/\n{3,}/g, '\n\n'))
| |
| 174 | .then(() => showToast('Post copied!'))
| |
| 175 | .catch(err => console.error('[Reddit BBCode Tool] Copy failed:', err));
| |
| 176 | }); | |
| 177 | nestedButton.style.display = 'none'; | |
| 178 | const target = element.querySelector('div[slot="credit-bar"], .tagline') || element;
| |
| 179 | target.appendChild(copyButton); | |
| 180 | target.appendChild(nestedButton); | |
| 181 | } else if (element.tagName.toLowerCase() === 'shreddit-comment' || element.classList.contains('comment')) {
| |
| 182 | const permalink = element.getAttribute('permalink') ? `https://www.reddit.com${element.getAttribute('permalink')}` : element.querySelector('a.bylink')?.href || window.location.href;
| |
| 183 | const contentHtml = element.querySelector('div[slot="comment"], .md');
| |
| 184 | if (!contentHtml) {
| |
| 185 | console.log('[Reddit BBCode Tool] No content found for comment');
| |
| 186 | return; | |
| 187 | } | |
| 188 | const datetime = element.querySelector('time')?.getAttribute('title') || element.querySelector('time')?.getAttribute('datetime') || 'Unknown';
| |
| 189 | const contentText = extractTextWithLinks(contentHtml.innerHTML); | |
| 190 | const contentBBCode = htmlToBBCode(contentText); | |
| 191 | const spoilerContent = `[spoiler="text"]\nCommented on ${datetime}\n\n${contentBBCode}\n[/spoiler]`;
| |
| 192 | copyButton.addEventListener('click', (e) => {
| |
| 193 | e.preventDefault(); | |
| 194 | e.stopPropagation(); | |
| 195 | navigator.clipboard.writeText(`${permalink}\n${spoilerContent}`.replace(/\n{3,}/g, '\n\n'))
| |
| 196 | .then(() => showToast('Comment copied!'))
| |
| 197 | .catch(err => console.error('[Reddit BBCode Tool] Copy failed:', err));
| |
| 198 | }); | |
| 199 | nestedButton.addEventListener('click', (e) => {
| |
| 200 | e.preventDefault(); | |
| 201 | e.stopPropagation(); | |
| 202 | const parents = getParentComments(element); | |
| 203 | const maxDepth = parents.length; | |
| 204 | createDepthDropdown(maxDepth, (depth) => {
| |
| 205 | const items = [...parents.slice(0, depth), { permalink, spoilerContent }];
| |
| 206 | const payload = items | |
| 207 | .map((item, index) => {
| |
| 208 | const indent = '│ '.repeat(index); | |
| 209 | const commentLines = `${item.permalink}\n${item.spoilerContent}`.split('\n');
| |
| 210 | return commentLines.map(line => `${indent}${line}`).join('\n');
| |
| 211 | }) | |
| 212 | .join('\n\n').replace(/\n{3,}/g, '\n\n');
| |
| 213 | navigator.clipboard.writeText(payload) | |
| 214 | .then(() => showToast(`Comment with ${depth} parent${depth === 1 ? '' : 's'} copied!`))
| |
| 215 | .catch(err => console.error('[Reddit BBCode Tool] Nested copy failed:', err));
| |
| 216 | }, nestedButton); | |
| 217 | }); | |
| 218 | const target = element.querySelector('div[slot="actionRow"], .tagline') || element;
| |
| 219 | target.appendChild(copyButton); | |
| 220 | target.appendChild(nestedButton); | |
| 221 | } | |
| 222 | } | |
| 223 | ||
| 224 | function showToast(message) {
| |
| 225 | const toast = document.createElement('div');
| |
| 226 | toast.textContent = message; | |
| 227 | toast.style.cssText = 'position:fixed;top:50%;left:50%;transform:translate(-50%,-50%);padding:10px;border-radius:5px;background:#fff;color:#000;z-index:1000;box-shadow:0 0 10px rgba(0,0,0,0.2);'; | |
| 228 | document.body.appendChild(toast); | |
| 229 | setTimeout(() => toast.remove(), 2000); | |
| 230 | } | |
| 231 | ||
| 232 | document.querySelectorAll('shreddit-post, shreddit-comment, .thing.link, .thing.comment').forEach(createCopyButtons);
| |
| 233 | })(); |