A filter list where you can set Keywords/phrases to automatically hide stories that match from view.
// ==UserScript==
// @name Fictionmania Filter
// @namespace http://tampermonkey.net/
// @version 2026-09-17
// @description A filter list where you can set Keywords/phrases to automatically hide stories that match from view.
// @author Silent J
// @license MIT
// @match *://*.fictionmania.tv/searchdisplay*
// @icon data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==
// @grant none
// ==/UserScript==
// This takes the first story on the page and surrounds it with a p tag so the filter below works for all stories on the page
(function() {
'use strict';
// Find only the first <hr> tag on the page
const firstHr = document.querySelector('hr');
// Exit early if no <hr> exists on the page
if (!firstHr) return;
let currentNode = firstHr.nextSibling;
const nodesToWrap = [];
// Gather all sibling nodes until we hit a <p> tag or run out of siblings
while (currentNode) {
// Stop if we encounter a P tag
if (currentNode.nodeType === Node.ELEMENT_NODE && currentNode.tagName.toLowerCase() === 'p') {
break;
}
// Also stop if we hit another HR tag
if (currentNode.nodeType === Node.ELEMENT_NODE && currentNode.tagName.toLowerCase() === 'hr') {
break;
}
nodesToWrap.push(currentNode);
currentNode = currentNode.nextSibling;
}
// Only proceed if we actually found nodes to wrap
if (nodesToWrap.length > 0) {
// Create the wrapper <p> element
const newParagraph = document.createElement('p');
// Insert the new <p> right after the first <hr>
firstHr.parentNode.insertBefore(newParagraph, firstHr.nextSibling);
// Move the gathered nodes inside the new <p>
nodesToWrap.forEach(node => {
newParagraph.appendChild(node);
});
}
})();
(function() {
'use strict';
// 1. Initialize keywords list from localStorage (defaults to ['unwanted text'] if empty)
const STORAGE_KEY = 'suppressor_forbidden_keywords';
let forbiddenKeywords = JSON.parse(localStorage.getItem(STORAGE_KEY)) || ["unwanted text here"];
let suppressedParagraphs = [];
let isHidden = true;
// 2. Main function to scan and hide paragraphs based on current keywords
function scanAndFilter() {
// Reset display and styling of previously handled paragraphs
suppressedParagraphs.forEach(p => {
p.style.display = '';
p.style.outline = '';
p.style.backgroundColor = '';
});
suppressedParagraphs = [];
// If no keywords exist, stop filtering
if (forbiddenKeywords.length === 0) {
updateBannerText();
return;
}
const paragraphs = document.querySelectorAll('p');
paragraphs.forEach(p => {
const text = p.textContent.toLowerCase();
// Check if paragraph contains ANY of the keywords (case-insensitive)
const matches = forbiddenKeywords.some(keyword => text.includes(keyword.toLowerCase()));
if (matches) {
suppressedParagraphs.push(p);
applyParagraphState(p);
}
});
updateBannerText();
}
// Helper to manage visibility and highlighting styles based on toggle state
function applyParagraphState(p) {
if (isHidden) {
p.style.display = 'none';
p.style.outline = '';
p.style.backgroundColor = '';
} else {
p.style.display = '';
// Apply distinct highlight styling when items are revealed
p.style.outline = '2px dashed #ff4d4d';
p.style.outlineOffset = '2px';
p.style.backgroundColor = 'rgba(255, 77, 77, 0.15)';
}
}
// 3. UI Construction: Main Floating Container
const container = document.createElement('div');
container.style.position = 'fixed';
container.style.top = '10px';
container.style.right = '10px';
container.style.backgroundColor = '#222';
container.style.color = '#fff';
container.style.fontSize = '12px';
container.style.fontFamily = 'sans-serif';
container.style.borderRadius = '6px';
container.style.boxShadow = '0 4px 12px rgba(0,0,0,0.4)';
container.style.zIndex = '99999';
container.style.width = '240px';
container.style.overflow = 'hidden';
document.body.appendChild(container);
// 4. Banner Component (Clickable Toggle header)
const banner = document.createElement('div');
banner.style.padding = '10px';
banner.style.cursor = 'pointer';
banner.style.userSelect = 'none';
banner.style.fontWeight = 'bold';
banner.style.textAlign = 'center';
banner.style.transition = 'background-color 0.2s';
container.appendChild(banner);
// 5. Dashboard Component (Hidden by default, reveals on hover)
const dashboard = document.createElement('div');
dashboard.style.padding = '0px 10px';
dashboard.style.borderTop = '0px solid #444';
dashboard.style.backgroundColor = '#2d2d2d';
dashboard.style.maxHeight = '0px';
dashboard.style.opacity = '0';
dashboard.style.overflow = 'hidden';
dashboard.style.transition = 'max-height 0.25s ease-out, opacity 0.2s ease-out, padding 0.25s ease-out';
container.appendChild(dashboard);
// Form structure inside dashboard
dashboard.innerHTML = `
<div style="margin-top: 8px; margin-bottom: 8px; font-weight: bold; color: #aaa;">Manage Keywords:</div>
<div id="keyword-list" style="max-height: 100px; overflow-y: auto; margin-bottom: 8px; padding-right: 5px;"></div>
<div style="display: flex; gap: 4px; margin-bottom: 8px;">
<input type="text" id="new-keyword-input" placeholder="Add keyword..." style="flex: 1; padding: 4px; border: 1px solid #555; background: #444; color: #fff; font-size: 11px; border-radius: 3px;">
<button id="add-keyword-btn" style="padding: 4px 8px; background: #0066cc; color: white; border: none; border-radius: 3px; cursor: pointer; font-size: 11px;">Add</button>
</div>
`;
const keywordListContainer = dashboard.querySelector('#keyword-list');
const inputField = dashboard.querySelector('#new-keyword-input');
const addBtn = dashboard.querySelector('#add-keyword-btn');
// Helper to refresh the textual summary inside the banner header
function updateBannerText() {
const count = suppressedParagraphs.length;
if (isHidden) {
banner.textContent = `Hidden Stories: ${count} 🙈`;
banner.style.backgroundColor = '#333';
} else {
banner.textContent = `UN-Hidden Stories: ${count} 👁️`;
banner.style.backgroundColor = '#0066cc';
}
}
// Helper to render the interactive list of terms in the dashboard
function renderKeywordList() {
keywordListContainer.innerHTML = '';
if (forbiddenKeywords.length === 0) {
keywordListContainer.innerHTML = '<div style="color: #777; font-style: italic;">No active filters.</div>';
return;
}
forbiddenKeywords.forEach((keyword, index) => {
const item = document.createElement('div');
item.style.display = 'flex';
item.style.justifyContent = 'between';
item.style.alignItems = 'center';
item.style.marginBottom = '4px';
item.style.background = '#3c3c3c';
item.style.padding = '3px 6px';
item.style.borderRadius = '3px';
item.innerHTML = `
<span style="flex: 1; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;" title="${keyword}">${keyword}</span>
<span class="del-btn" data-index="${index}" style="color: #ff4d4d; cursor: pointer; font-weight: bold; margin-left: 6px; padding: 0 2px;">×</span>
`;
keywordListContainer.appendChild(item);
});
}
// 6. Hover Mechanics (Show/Hide Dashboard)
container.addEventListener('mouseenter', () => {
dashboard.style.maxHeight = '200px';
dashboard.style.opacity = '1';
dashboard.style.padding = '4px 10px';
dashboard.style.borderTop = '1px solid #444';
});
container.addEventListener('mouseleave', () => {
if (document.activeElement !== inputField) {
dashboard.style.maxHeight = '0px';
dashboard.style.opacity = '0';
dashboard.style.padding = '0px 10px';
dashboard.style.borderTop = '0px solid #444';
}
});
inputField.addEventListener('blur', () => {
setTimeout(() => {
if (!container.matches(':hover')) {
dashboard.style.maxHeight = '0px';
dashboard.style.opacity = '0';
dashboard.style.padding = '0px 10px';
dashboard.style.borderTop = '0px solid #444';
}
}, 100);
});
// 7. Interactive Logic and Event Handlers
// Toggle content visibility and highlighting styles when clicking the banner
banner.addEventListener('click', () => {
isHidden = !isHidden;
suppressedParagraphs.forEach(p => applyParagraphState(p));
updateBannerText();
});
// Add a new keyword action
function handleAddKeyword() {
const val = inputField.value.trim();
if (val && !forbiddenKeywords.includes(val)) {
forbiddenKeywords.push(val);
localStorage.setItem(STORAGE_KEY, JSON.stringify(forbiddenKeywords));
inputField.value = '';
renderKeywordList();
scanAndFilter();
}
}
addBtn.addEventListener('click', handleAddKeyword);
inputField.addEventListener('keypress', (e) => { e.key === 'Enter' && handleAddKeyword(); });
// Remove keyword action
keywordListContainer.addEventListener('click', (e) => {
if (e.target.classList.contains('del-btn')) {
const idx = parseInt(e.target.getAttribute('data-index'), 10);
forbiddenKeywords.splice(idx, 1);
localStorage.setItem(STORAGE_KEY, JSON.stringify(forbiddenKeywords));
renderKeywordList();
scanAndFilter();
}
});
// Initial Engine Kickoff
renderKeywordList();
scanAndFilter();
})();