Jump to content
View in the app

A better way to browse. Learn more.

Invision Community

A full-screen app on your home screen with push notifications, badges and more.

To install this app on iOS and iPadOS
  1. Tap the Share icon in Safari
  2. Scroll the menu and tap Add to Home Screen.
  3. Tap Add in the top-right corner.
To install this app on Android
  1. Tap the 3-dot menu (⋮) in the top-right corner of the browser.
  2. Tap Add to Home screen or Install app.
  3. Confirm by tapping Install.

True Spoilers with Mouseover and OnClick - A Solution

Featured Replies

Folks, please try this solution. I got this to work on my forum. It allows you to use BBCode to transform highlighted text to black text with black background, then you can mouseover and it will reappear. It will also provide a larger block spoiler solution.

[s] [/s] for simple in-line spoilers

[spoiler] [/spoiler] for larger block type spoilers

image.png

.spoiler-tag {
    background-color: #333;
    color: #333;
    padding: 2px 4px;
    border-radius: 3px;
    cursor: pointer;
    transition: all 0.3s ease;
    user-select: none;
    position: relative;
    display: inline-block;
}

.spoiler-tag:hover {
    background-color: #555;
}

.spoiler-tag.revealed {
    background-color: transparent;
    color: inherit;
}

.spoiler-tag::after {
    content: " ";
    font-size: 12px;
    opacity: 0.7;
}

.spoiler-tag.revealed::after {
    display: none;
}

.spoiler-block {
    background-color: #2c2c2c;
    color: #2c2c2c;
    padding: 15px;
    border-radius: 5px;
    cursor: pointer;
    margin: 10px 0;
    transition: all 0.3s ease;
    position: relative;
    border: 2px solid #444;
}

.spoiler-block:hover {
    border-color: #666;
}

.spoiler-block.revealed {
    background-color: #f8f9fa;
    color: inherit;
    border-color: #dee2e6;
}

.spoiler-block::before {
    content: "Spoiler Content - Click to Reveal";
    position: absolute;
    top: 50%;
    left: 50%;
    transform: translate(-50%, -50%);
    font-weight: bold;
    color: #999;
    pointer-events: none;
}

.spoiler-block.revealed::before {
    display: none;
}
(function() {
    'use strict';
    
    function initSpoilers() {
        // Find all post content areas
        const contentAreas = document.querySelectorAll('[data-role="commentContent"], .cPost_contentWrap, .ipsType_richText, .ipsContained');
        
        contentAreas.forEach(function(area) {
            if (area.hasAttribute('data-spoiler-processed')) return;
            
            let html = area.innerHTML;
            let modified = false;
            
            // Process block spoilers [spoiler]...[/spoiler]
            html = html.replace(/\[spoiler\]([\s\S]*?)\[\/spoiler\]/gi, function(match, content) {
                modified = true;
                return '<div class="spoiler-block" onclick="toggleSpoiler(this)">' + content.trim() + '</div>';
            });
            
            // Process inline spoilers [s]...[/s]
            html = html.replace(/\[s\](.*?)\[\/s\]/gi, function(match, content) {
                modified = true;
                return '<span class="spoiler-tag" onclick="toggleSpoiler(this)">' + content + '</span>';
            });
            
            if (modified) {
                area.innerHTML = html;
            }
            
            area.setAttribute('data-spoiler-processed', 'true');
        });
    }
    
    // Global function for spoiler toggling
    window.toggleSpoiler = function(element) {
        element.classList.toggle('revealed');
    };
    
    // Initialize on page load
    document.addEventListener('DOMContentLoaded', initSpoilers);
    
    // Re-initialize when new content loads (AJAX)
    const observer = new MutationObserver(function(mutations) {
        let shouldProcess = false;
        mutations.forEach(function(mutation) {
            if (mutation.addedNodes.length > 0) {
                mutation.addedNodes.forEach(function(node) {
                    if (node.nodeType === 1 && (
                        node.matches && (
                            node.matches('[data-role="commentContent"]') ||
                            node.matches('.cPost_contentWrap') ||
                            node.querySelector('[data-role="commentContent"]') ||
                            node.querySelector('.cPost_contentWrap')
                        )
                    )) {
                        shouldProcess = true;
                    }
                });
            }
        });
        
        if (shouldProcess) {
            setTimeout(initSpoilers, 100);
        }
    });
    
    observer.observe(document.body, {
        childList: true,
        subtree: true
    });
})();

  • Author

Edit: this was generated by Claude.AI. Sometimes you just have to play around to get stuff done.

So does that create a spoiler button members can use?

  • Author

Working on that next.

  • Author
1 hour ago, Cruizin said:

So does that create a spoiler button members can use?

Yes, here is the updated Javascript and CSS that is needed to add spoilers with buttons to your forum. I can't say it's perfect of course because I don't know how to write code, but the computer did a decent job of making functional spoilers and even created a diagnostic script that allowed us to determine the correct toolbar identifiers.

Javascript & CSS to add True Spoilers to your forum.

Add this Javascript to your theme:

(function() {
'use strict';

function initSpoilers() {
const contentAreas = document.querySelectorAll('[data-role="commentContent"], .cPost_contentWrap, .ipsType_richText, .ipsContained');

contentAreas.forEach(function(area) {
if (area.hasAttribute('data-spoiler-processed')) return;

let html = area.innerHTML;
let modified = false;

html = html.replace(/\[spoiler\]([\s\S]*?)\[\/spoiler\]/gi, function(match, content) {
modified = true;
return '<div class="spoiler-block" onclick="toggleSpoiler(this)">' + content.trim() + '</div>';
});

html = html.replace(/\[s\](.*?)\[\/s\]/gi, function(match, content) {
modified = true;
return '<span class="spoiler-tag" onclick="toggleSpoiler(this)">' + content + '</span>';
});

if (modified) {
area.innerHTML = html;
}

area.setAttribute('data-spoiler-processed', 'true');
});
}

window.toggleSpoiler = function(element) {
element.classList.toggle('revealed');
};

// Add spoiler buttons to the IC5 toolbar
function addSpoilerButtons() {
console.log('Looking for IC5 toolbars...');

// Target the specific IC5 toolbar structure you have
const toolbars = document.querySelectorAll('[data-role="toolbar"].ipsEditor__toolbar--main:not([data-spoiler-buttons-added])');

toolbars.forEach(function(toolbar) {
console.log('Found IC5 main toolbar:', toolbar);

// Create spoiler button items in IC5 style
const inlineButtonWrap = createIC5ToolbarItem('spoiler-inline', '', 'Inline Spoiler', function() {
insertSpoilerTags('inline', toolbar);
});

const blockButtonWrap = createIC5ToolbarItem('spoiler-block', '', 'Block Spoiler', function() {
insertSpoilerTags('block', toolbar);
});

// Find the last toolbar item to insert after
const lastItem = toolbar.querySelector('li.ipsEditor__toolbar-item-wrap:last-child');

if (lastItem) {
// Insert spoiler buttons at the end of the toolbar
lastItem.parentNode.insertBefore(inlineButtonWrap, lastItem.nextSibling);
lastItem.parentNode.insertBefore(blockButtonWrap, lastItem.nextSibling);
console.log('Added spoiler buttons to IC5 toolbar');
} else {
// Fallback: append to toolbar
toolbar.appendChild(inlineButtonWrap);
toolbar.appendChild(blockButtonWrap);
console.log('Added spoiler buttons (fallback method)');
}

toolbar.setAttribute('data-spoiler-buttons-added', 'true');
});
}

// Create a toolbar item that matches IC5's structure
function createIC5ToolbarItem(toolbarItem, icon, title, clickHandler) {
const li = document.createElement('li');
li.className = 'ipsEditor__toolbar-item-wrap';
li.setAttribute('data-toolbar-item', toolbarItem);

const button = document.createElement('button');
button.type = 'button';
button.setAttribute('data-toolbar-item', toolbarItem);
button.title = title;
button.className = 'ipsEditor__toolbar-item--inactive ipsEditor__toolbar-item';

button.innerHTML = `
<i class="spoiler-icon">${icon}</i>
<span class="ipsEditor__toolbar-label">${title}</span>
`;

button.addEventListener('click', function(e) {
e.preventDefault();
clickHandler();
});

li.appendChild(button);
return li;
}

function insertSpoilerTags(type, toolbar) {
console.log('Inserting spoiler tags:', type);

// Find the TipTap editor associated with this toolbar
const editorContainer = toolbar.closest('.ipsEditor');
const editor = editorContainer ? editorContainer.querySelector('.tiptap.ProseMirror') : null;

if (!editor) {
console.error('Could not find TipTap editor');
alert('Please click in the editor first, then use the spoiler button.');
return;
}

// Focus the editor
editor.focus();

const selection = window.getSelection();
const selectedText = selection.toString();

let startTag, endTag;
if (type === 'inline') {
startTag = '[s]';
endTag = '[/s]';
} else {
startTag = '[spoiler]';
endTag = '[/spoiler]';
}

try {
if (selectedText) {
// Replace selected text with spoiler tags
const spoilerText = startTag + selectedText + endTag;

// Use document.execCommand for best compatibility with TipTap
if (document.execCommand('insertText', false, spoilerText)) {
console.log('Successfully inserted spoiler tags around selection');
} else {
throw new Error('execCommand failed');
}
} else {
// No selection, insert template
const template = startTag + 'spoiler text' + endTag;

if (document.execCommand('insertText', false, template)) {
// Select the placeholder text for easy replacement
setTimeout(function() {
const content = editor.textContent;
const startIndex = content.lastIndexOf(startTag) + startTag.length;
const endIndex = startIndex + 12; // 'spoiler text'.length

const textNode = findTextNode(editor, startIndex);
if (textNode) {
const range = document.createRange();
range.setStart(textNode, startIndex - getTextOffset(editor, textNode));
range.setEnd(textNode, endIndex - getTextOffset(editor, textNode));
selection.removeAllRanges();
selection.addRange(range);
}
}, 50);

console.log('Successfully inserted spoiler template');
} else {
throw new Error('execCommand failed');
}
}

// Dispatch input event to notify TipTap of changes
editor.dispatchEvent(new Event('input', { bubbles: true }));

} catch (error) {
console.error('Error inserting spoiler tags:', error);

// Fallback: show prompt with tags to copy
const tags = startTag + (selectedText || 'your spoiler text') + endTag;
const result = prompt('Copy these spoiler tags and paste them in the editor:', tags);
if (result !== null) {
editor.focus();
}
}
}

// Helper functions for text selection
function findTextNode(element, targetOffset) {
let currentOffset = 0;
const walker = document.createTreeWalker(
element,
NodeFilter.SHOW_TEXT,
null,
false
);

let node;
while (node = walker.nextNode()) {
const nodeLength = node.textContent.length;
if (currentOffset + nodeLength >= targetOffset) {
return node;
}
currentOffset += nodeLength;
}
return null;
}

function getTextOffset(element, targetNode) {
let offset = 0;
const walker = document.createTreeWalker(
element,
NodeFilter.SHOW_TEXT,
null,
false
);

let node;
while (node = walker.nextNode()) {
if (node === targetNode) {
break;
}
offset += node.textContent.length;
}
return offset;
}

// Initialize on page load
document.addEventListener('DOMContentLoaded', function() {
console.log('DOM loaded, initializing spoiler system...');
initSpoilers();
setTimeout(addSpoilerButtons, 1500); // Slightly longer delay for IC5
});

// Watch for dynamically loaded editors
const observer = new MutationObserver(function(mutations) {
let shouldProcessSpoilers = false;
let shouldAddButtons = false;

mutations.forEach(function(mutation) {
if (mutation.addedNodes.length > 0) {
mutation.addedNodes.forEach(function(node) {
if (node.nodeType === 1) {
// Check for new content that needs spoiler processing
if (node.matches && (
node.matches('[data-role="commentContent"]') ||
node.matches('.cPost_contentWrap') ||
node.querySelector('[data-role="commentContent"]') ||
node.querySelector('.cPost_contentWrap')
)) {
shouldProcessSpoilers = true;
}

// Check for new toolbars
if (node.matches && (
node.matches('[data-role="toolbar"].ipsEditor__toolbar--main') ||
node.querySelector('[data-role="toolbar"].ipsEditor__toolbar--main')
)) {
shouldAddButtons = true;
}
}
});
}
});

if (shouldProcessSpoilers) {
setTimeout(initSpoilers, 100);
}
if (shouldAddButtons) {
setTimeout(addSpoilerButtons, 800);
}
});

observer.observe(document.body, {
childList: true,
subtree: true
});

console.log('IC5 Spoiler system with toolbar buttons initialized');
})();

Add this CSS to your theme: (you can obviously adjust these parameters to your theme style and liking)

/* Spoiler Styles */
.spoiler-tag {
background-color: #333;
color: #333;
padding: 2px 4px;
border-radius: 3px;
cursor: pointer;
transition: all 0.3s ease;
user-select: none;
position: relative;
display: inline-block;
}

.spoiler-tag:hover {
background-color: #555;
}

.spoiler-tag.revealed {
background-color: transparent;
color: inherit;
}

.spoiler-tag::after {
content: " ";
font-size: 12px;
opacity: 0.7;
}

.spoiler-tag.revealed::after {
display: none;
}

.spoiler-block {
background-color: #2c2c2c;
color: #2c2c2c;
padding: 15px;
border-radius: 5px;
cursor: pointer;
margin: 10px 0;
transition: all 0.3s ease;
position: relative;
border: 2px solid #444;
}

.spoiler-block:hover {
border-color: #666;
}

.spoiler-block.revealed {
background-color: #f8f9fa;
color: inherit;
border-color: #dee2e6;
}

.spoiler-block::before {
content: " Spoiler Content - Click to Reveal";
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
font-weight: bold;
color: #999;
pointer-events: none;
}

.spoiler-block.revealed::before {
display: none;
}
/* IC5 Spoiler Button Styling */
.ipsEditor__toolbar-item-wrap[data-toolbar-item="spoiler-inline"] .spoiler-icon,
.ipsEditor__toolbar-item-wrap[data-toolbar-item="spoiler-block"] .spoiler-icon {
font-style: normal;
font-size: 14px;
line-height: 1;
}

/* Make sure spoiler buttons match the toolbar style */
.ipsEditor__toolbar-item-wrap[data-toolbar-item="spoiler-inline"] .ipsEditor__toolbar-item,
.ipsEditor__toolbar-item-wrap[data-toolbar-item="spoiler-block"] .ipsEditor__toolbar-item {
background: transparent;
border: none;
color: inherit;
padding: 8px;
border-radius: 4px;
transition: background-color 0.2s ease;
}

.ipsEditor__toolbar-item-wrap[data-toolbar-item="spoiler-inline"] .ipsEditor__toolbar-item:hover,
.ipsEditor__toolbar-item-wrap[data-toolbar-item="spoiler-block"] .ipsEditor__toolbar-item:hover {
background-color: rgba(0,0,0,0.05);
}

.ipsEditor__toolbar-item-wrap[data-toolbar-item="spoiler-inline"] .ipsEditor__toolbar-item:active,
.ipsEditor__toolbar-item-wrap[data-toolbar-item="spoiler-block"] .ipsEditor__toolbar-item:active {
background-color: rgba(0,0,0,0.1);
}

/* Dark theme support */
.ipsTheme_dark .ipsEditor__toolbar-item-wrap[data-toolbar-item="spoiler-inline"] .ipsEditor__toolbar-item:hover,
.ipsTheme_dark .ipsEditor__toolbar-item-wrap[data-toolbar-item="spoiler-block"] .ipsEditor__toolbar-item:hover {
background-color: rgba(255,255,255,0.1);
}

.ipsTheme_dark .ipsEditor__toolbar-item-wrap[data-toolbar-item="spoiler-inline"] .ipsEditor__toolbar-item:active,
.ipsTheme_dark .ipsEditor__toolbar-item-wrap[data-toolbar-item="spoiler-block"] .ipsEditor__toolbar-item:active {
background-color: rgba(255,255,255,0.15);

Again, this is just barely 'vibe-coding' (not) AI generated nonsense, but it works. Feel free to adjust and adapt and provide your thoughts.

Edited by Kyle_H

  • Author

For those who need to know where to add these two bits of code:

In the Admin Control Panel, head to Customization > Themes > ['Your Theme Name'] > Navigate to Theme Designer: Core CSS & JS.

  • Author

Folks, just discovered that images, gifs, videos, and hyperlinks were not being hidden in the block spoilers. I see this is going to be a work in progress so maybe follow this thread for updates, or take the code and contribute to it if you see that it's missing something. Here is the updated CSS that will make everything beyond text hidden as well. You might need to adjust some of the coloring to work with your particular themes (especially block background).

/* Enhanced Spoiler Styles - Hides ALL content types */
.spoiler-tag {
background-color: #333;
color: #333;
padding: 2px 4px;
border-radius: 0px;
cursor: pointer;
transition: all 0.3s ease;
user-select: none;
position: relative;
display: inline-block;
overflow: hidden;
}

.spoiler-tag:hover {
background-color: #555;
}

.spoiler-tag.revealed {
background-color: transparent;
color: inherit;
}

.spoiler-tag::after {
content: " ";
font-size: 12px;
opacity: 0.7;
}

.spoiler-tag.revealed::after {
display: none;
}

/* Block Spoiler - Enhanced to hide ALL content */
.spoiler-block {
background-color: #2c2c2c;
color: #2c2c2c;
padding: 15px;
border-radius: 5px;
cursor: pointer;
margin: 10px 0;
transition: all 0.3s ease;
position: relative;
border: 2px solid #444;
overflow: hidden;
min-height: 60px;

/* Force hide ALL child content when not revealed */
}

.spoiler-block:hover {
border-color: #666;
}

.spoiler-block.revealed {
background-color: #262626;
color: inherit;
border-color: #000000;
}

/* Hide ALL content inside spoiler blocks when not revealed */
.spoiler-block:not(.revealed) * {
visibility: hidden !important;
opacity: 0 !important;
color: transparent !important;
background-color: transparent !important;
border-color: transparent !important;
box-shadow: none !important;
}

/* Hide images, videos, iframes, and embedded content */
.spoiler-block:not(.revealed) img,
.spoiler-block:not(.revealed) video,
.spoiler-block:not(.revealed) iframe,
.spoiler-block:not(.revealed) embed,
.spoiler-block:not(.revealed) object,
.spoiler-block:not(.revealed) svg,
.spoiler-block:not(.revealed) canvas {
visibility: hidden !important;
opacity: 0 !important;
width: 0 !important;
height: 0 !important;
max-width: 0 !important;
max-height: 0 !important;
overflow: hidden !important;
display: none !important;
}

/* Hide links and their content */
.spoiler-block:not(.revealed) a {
visibility: hidden !important;
opacity: 0 !important;
color: transparent !important;
text-decoration: none !important;
pointer-events: none !important;
}

/* Hide any forum-specific content classes */
.spoiler-block:not(.revealed) .ipsImage,
.spoiler-block:not(.revealed) .ipsAttachment,
.spoiler-block:not(.revealed) .ipsEmbeddedVideo,
.spoiler-block:not(.revealed) .ipsQuote,
.spoiler-block:not(.revealed) .ipsCode,
.spoiler-block:not(.revealed) .ipsSpoiler,
.spoiler-block:not(.revealed) .ipsEmbed {
visibility: hidden !important;
opacity: 0 !important;
display: none !important;
}

/* Hide any user-uploaded content */
.spoiler-block:not(.revealed) [data-fileid],
.spoiler-block:not(.revealed) [data-role="attachmentLink"],
.spoiler-block:not(.revealed) .cAttachment {
visibility: hidden !important;
opacity: 0 !important;
display: none !important;
}

/* Show the spoiler reveal message */
.spoiler-block::before {
content: " Spoiler Content - Click to Reveal";
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
font-weight: bold;
color: #999;
pointer-events: none;
visibility: visible !important;
opacity: 1 !important;
z-index: 10;
background: rgba(44, 44, 44, 0.9);
padding: 8px 16px;
border-radius: 4px;
font-size: 14px;
white-space: nowrap;
}

.spoiler-block.revealed::before {
display: none;
}

/* Ensure revealed content shows properly */
.spoiler-block.revealed * {
visibility: visible !important;
opacity: 1 !important;
color: inherit !important;
display: initial !important;
width: auto !important;
height: auto !important;
max-width: 100% !important;
max-height: none !important;
pointer-events: auto !important;
}

.spoiler-block.revealed img,
.spoiler-block.revealed video,
.spoiler-block.revealed iframe,
.spoiler-block.revealed embed,
.spoiler-block.revealed object,
.spoiler-block.revealed svg,
.spoiler-block.revealed canvas {
display: initial !important;
visibility: visible !important;
opacity: 1 !important;
width: auto !important;
height: auto !important;
max-width: 100% !important;
max-height: none !important;
}

/* Dark theme support */
.ipsTheme_dark .spoiler-block {
background-color: #1a1a1a;
border-color: #333;
}

.ipsTheme_dark .spoiler-block:hover {
border-color: #555;
}

.ipsTheme_dark .spoiler-block.revealed {
background-color: #2c2c2c;
border-color: #555;
}

.ipsTheme_dark .spoiler-block::before {
color: #ccc;
background: rgba(26, 26, 26, 0.9);
}

/* IC5 Spoiler Button Styling (keep existing) */
.ipsEditor__toolbar-item-wrap[data-toolbar-item="spoiler-inline"] .spoiler-icon,
.ipsEditor__toolbar-item-wrap[data-toolbar-item="spoiler-block"] .spoiler-icon {
font-style: normal;
font-size: 14px;
line-height: 1;
}

.ipsEditor__toolbar-item-wrap[data-toolbar-item="spoiler-inline"] .ipsEditor__toolbar-item,
.ipsEditor__toolbar-item-wrap[data-toolbar-item="spoiler-block"] .ipsEditor__toolbar-item {
background: transparent;
border: none;
color: inherit;
padding: 8px;
border-radius: 4px;
transition: background-color 0.2s ease;
}

.ipsEditor__toolbar-item-wrap[data-toolbar-item="spoiler-inline"] .ipsEditor__toolbar-item:hover,
.ipsEditor__toolbar-item-wrap[data-toolbar-item="spoiler-block"] .ipsEditor__toolbar-item:hover {
background-color: rgba(0,0,0,0.05);
}

.ipsEditor__toolbar-item-wrap[data-toolbar-item="spoiler-inline"] .ipsEditor__toolbar-item:active,
.ipsEditor__toolbar-item-wrap[data-toolbar-item="spoiler-block"] .ipsEditor__toolbar-item:active {
background-color: rgba(0,0,0,0.1);
}

.ipsTheme_dark .ipsEditor__toolbar-item-wrap[data-toolbar-item="spoiler-inline"] .ipsEditor__toolbar-item:hover,
.ipsTheme_dark .ipsEditor__toolbar-item-wrap[data-toolbar-item="spoiler-block"] .ipsEditor__toolbar-item:hover {
background-color: rgba(255,255,255,0.1);
}

.ipsTheme_dark .ipsEditor__toolbar-item-wrap[data-toolbar-item="spoiler-inline"] .ipsEditor__toolbar-item:active,
.ipsTheme_dark .ipsEditor__toolbar-item-wrap[data-toolbar-item="spoiler-block"] .ipsEditor__toolbar-item:active {
background-color: rgba(255,255,255,0.15);
}

Recently Browsing 0

  • No registered users viewing this page.

Account

Navigation

Search

Search

Configure browser push notifications

Chrome (Android)
  1. Tap the lock icon next to the address bar.
  2. Tap Permissions → Notifications.
  3. Adjust your preference.
Chrome (Desktop)
  1. Click the padlock icon in the address bar.
  2. Select Site settings.
  3. Find Notifications and adjust your preference.