Spaces:
Running
Running
| <html lang="en"> | |
| <head> | |
| <meta charset="UTF-8"> | |
| <meta name="viewport" content="width=device-width, initial-scale=1.0"> | |
| <title>Text Censorship App</title> | |
| <script src="https://cdn.tailwindcss.com"></script> | |
| <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css"> | |
| <style> | |
| .file-input-label { | |
| display: inline-block; | |
| padding: 0.5rem 1rem; | |
| border-radius: 0.375rem; | |
| background-color: #3b82f6; | |
| color: white; | |
| cursor: pointer; | |
| transition: background-color 0.2s; | |
| } | |
| .file-input-label:hover { | |
| background-color: #2563eb; | |
| } | |
| #fileInput { | |
| display: none; | |
| } | |
| .textarea-container { | |
| position: relative; | |
| } | |
| .word-count { | |
| position: absolute; | |
| bottom: 0.5rem; | |
| right: 0.5rem; | |
| background-color: rgba(255, 255, 255, 0.8); | |
| padding: 0.2rem 0.5rem; | |
| border-radius: 0.25rem; | |
| font-size: 0.75rem; | |
| color: #6b7280; | |
| } | |
| .censored { | |
| background-color: #fecaca; | |
| color: transparent; | |
| text-shadow: 0 0 8px #ef4444; | |
| border-radius: 0.25rem; | |
| } | |
| </style> | |
| </head> | |
| <body class="bg-gray-100 min-h-screen"> | |
| <div class="container mx-auto px-4 py-8 max-w-4xl"> | |
| <div class="bg-white rounded-xl shadow-lg overflow-hidden"> | |
| <!-- Header --> | |
| <div class="bg-blue-600 px-6 py-4"> | |
| <div class="flex items-center justify-between"> | |
| <h1 class="text-2xl font-bold text-white"> | |
| <i class="fas fa-shield-alt mr-2"></i> Text Censorship Tool | |
| </h1> | |
| <div class="flex items-center space-x-2"> | |
| <span id="statusBadge" class="px-3 py-1 rounded-full text-xs font-semibold bg-blue-500 text-white"> | |
| <i class="fas fa-circle-notch fa-spin mr-1"></i> Loading word list | |
| </span> | |
| </div> | |
| </div> | |
| </div> | |
| <!-- Main Content --> | |
| <div class="p-6"> | |
| <!-- File Input Section --> | |
| <div class="mb-6 bg-blue-50 p-4 rounded-lg"> | |
| <h2 class="text-lg font-semibold text-blue-800 mb-3"> | |
| <i class="fas fa-file-import mr-2"></i> Bad Words Dictionary | |
| </h2> | |
| <div class="flex flex-col sm:flex-row items-start sm:items-center gap-4"> | |
| <div> | |
| <label for="fileInput" class="file-input-label"> | |
| <i class="fas fa-upload mr-2"></i> Choose Word List File | |
| </label> | |
| <input type="file" id="fileInput" accept=".txt"> | |
| </div> | |
| <div class="text-sm text-gray-600"> | |
| <p>Or use our <button id="defaultWordsBtn" class="text-blue-600 hover:underline">default word list</button></p> | |
| </div> | |
| </div> | |
| <div id="fileInfo" class="mt-3 text-sm text-gray-600 hidden"> | |
| <i class="fas fa-file-alt mr-1"></i> <span id="fileName">No file selected</span> | |
| <span id="wordCount" class="ml-3"></span> | |
| </div> | |
| </div> | |
| <!-- Text Input Section --> | |
| <div class="mb-6"> | |
| <label for="inputText" class="block text-sm font-medium text-gray-700 mb-2"> | |
| <i class="fas fa-pencil-alt mr-2"></i> Enter your text: | |
| </label> | |
| <div class="textarea-container"> | |
| <textarea id="inputText" rows="8" class="w-full px-3 py-2 border border-gray-300 rounded-lg focus:ring-blue-500 focus:border-blue-500" placeholder="Type or paste your text here..."></textarea> | |
| <div class="word-count"> | |
| <span id="inputWordCount">0</span> words | |
| </div> | |
| </div> | |
| </div> | |
| <!-- Options Section --> | |
| <div class="mb-6 bg-gray-50 p-4 rounded-lg"> | |
| <h2 class="text-lg font-semibold text-gray-800 mb-3"> | |
| <i class="fas fa-cog mr-2"></i> Censorship Options | |
| </h2> | |
| <div class="grid grid-cols-1 sm:grid-cols-2 gap-4"> | |
| <div> | |
| <label class="inline-flex items-center"> | |
| <input type="checkbox" id="matchCase" class="rounded border-gray-300 text-blue-600 shadow-sm focus:border-blue-300 focus:ring focus:ring-blue-200 focus:ring-opacity-50"> | |
| <span class="ml-2 text-sm text-gray-700">Match case</span> | |
| </label> | |
| </div> | |
| <div> | |
| <label class="inline-flex items-center"> | |
| <input type="checkbox" id="matchPartial" checked class="rounded border-gray-300 text-blue-600 shadow-sm focus:border-blue-300 focus:ring focus:ring-blue-200 focus:ring-opacity-50"> | |
| <span class="ml-2 text-sm text-gray-700">Match partial words</span> | |
| </label> | |
| </div> | |
| <div> | |
| <label class="inline-flex items-center"> | |
| <input type="checkbox" id="highlightWords" checked class="rounded border-gray-300 text-blue-600 shadow-sm focus:border-blue-300 focus:ring focus:ring-blue-200 focus:ring-opacity-50"> | |
| <span class="ml-2 text-sm text-gray-700">Highlight censored words</span> | |
| </label> | |
| </div> | |
| <div> | |
| <label class="inline-flex items-center"> | |
| <input type="checkbox" id="replaceWithStars" checked class="rounded border-gray-300 text-blue-600 shadow-sm focus:border-blue-300 focus:ring focus:ring-blue-200 focus:ring-opacity-50"> | |
| <span class="ml-2 text-sm text-gray-700">Replace with asterisks</span> | |
| </label> | |
| </div> | |
| </div> | |
| </div> | |
| <!-- Action Button --> | |
| <div class="flex justify-center mb-6"> | |
| <button id="censorBtn" class="px-6 py-3 bg-blue-600 text-white font-medium rounded-lg hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-blue-500 focus:ring-offset-2 transition-colors"> | |
| <i class="fas fa-filter mr-2"></i> Censor Text | |
| </button> | |
| </div> | |
| <!-- Output Section --> | |
| <div> | |
| <label for="outputText" class="block text-sm font-medium text-gray-700 mb-2"> | |
| <i class="fas fa-shield-virus mr-2"></i> Censored text: | |
| </label> | |
| <div class="textarea-container"> | |
| <textarea id="outputText" rows="8" readonly class="w-full px-3 py-2 border border-gray-300 rounded-lg bg-gray-50" placeholder="Your censored text will appear here..."></textarea> | |
| <div class="word-count"> | |
| <span id="outputWordCount">0</span> words | <span id="censoredCount">0</span> censored | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| <!-- Footer --> | |
| <div class="bg-gray-100 px-6 py-3 text-center text-sm text-gray-600"> | |
| <p><i class="fas fa-info-circle mr-1"></i> This tool helps maintain respectful communication by filtering inappropriate language.</p> | |
| </div> | |
| </div> | |
| </div> | |
| <script> | |
| // Default word list (Russian swear words) | |
| const defaultWordList = [ | |
| "блядь", "бля", "блять", "блядина", "блядский", "блядство", "блядун", "блядюга", | |
| "ебанутый", "ебать", "ебал", "ебало", "ебанько", "ебаный", "ебарь", "ебатория", | |
| "ебашит", "ебись", "ебливый", "ебля", "ебнуть", "ебнутый", "ебун", "ебучий", | |
| "говно", "говнюк", "говняный", "говнецо", "говнище", "говнарь", | |
| "дерьмо", "дерьмовый", "дерьмище", "дерьмак", | |
| "дрочить", "дрочила", "дрочун", "дрочка", "дроченый", | |
| "залупа", "залупиться", "залупляться", "залупень", "залупский", | |
| "мудак", "мудила", "мудозвон", "мудоеб", "мудацкий", "муде", "мудень", | |
| "пизда", "пиздец", "пиздатый", "пиздеть", "пиздёж", "пиздища", "пиздобол", | |
| "пиздолиз", "пиздорванный", "пиздюк", "пиздюлина", "пиздюли", "пиздюля", | |
| "пиздятина", "пиздячий", "пиздят", "пиздящий", | |
| "срать", "срака", "сраный", "срань", "срач", "срачка", "срачник", | |
| "хуй", "хуев", "хуёвый", "хуёвина", "хуета", "хуила", "хуило", "хуище", | |
| "хуйло", "хуйня", "хуйрик", "хуита", "хуище", "хуяк", "хуярить", "хуякнуть", | |
| "шлюха", "шлюшка", "шлюшиный", "шлюшонок", | |
| "выебон", "выебываться", "взъебанный", "взъебать", "взъебка", | |
| "выебанный", "выебать", "выебениваться", "выебистика", "выебон", | |
| "доебаться", "доебываться", "долбоеб", "долбоёб", "долбоёбище", | |
| "заебал", "заебанец", "заебатый", "заебать", "заебенить", "заебись", | |
| "изъебнуться", "изъебать", "изъебанный", "изъебениться", | |
| "наебал", "наебалово", "наебать", "наебенить", "наебка", "наебнутый", | |
| "недоебень", "недоебыш", "недоёб", "недоёбище", | |
| "объебал", "объебать", "объебос", "объебанный", "объебать", | |
| "отъебался", "отъебать", "отъебись", "отъебон", "отъебанный", | |
| "переебанный", "переебать", "переёб", "переёбывать", "переёбочный", | |
| "поебень", "поеботина", "поебуха", "поебывать", "подъеб", "подъебать", | |
| "подъебка", "подъёбывать", "подъёбщик", | |
| "приебать", "приебаться", "приёб", "приёбывать", "приёбочный", | |
| "проебать", "проебал", "проебанный", "проёб", "проёбывать", | |
| "разъебай", "разъебать", "разъебанный", "разъёб", "разъёбывать", | |
| "съебать", "съебался", "съебанный", "съёбывать", "съёбочный", | |
| "уебать", "уебал", "уебанец", "уебанка", "уебанский", "уебище", | |
| "выебон", "выебываться", "взъебанный", "взъебать", "взъебка", | |
| "выебанный", "выебать", "выебениваться", "выебистика", "выебон", | |
| "доебаться", "доебываться", "долбоеб", "долбоёб", "долбоёбище", | |
| "заебал", "заебанец", "заебатый", "заебать", "заебенить", "заебись", | |
| "изъебнуться", "изъебать", "изъебанный", "изъебениться", | |
| "наебал", "наебалово", "наебать", "наебенить", "наебка", "наебнутый", | |
| "недоебень", "недоебыш", "недоёб", "недоёбище", | |
| "объебал", "объебать", "объебос", "объебанный", "объебать", | |
| "отъебался", "отъебать", "отъебись", "отъебон", "отъебанный", | |
| "переебанный", "переебать", "переёб", "переёбывать", "переёбочный", | |
| "поебень", "поеботина", "поебуха", "поебывать", "подъеб", "подъебать", | |
| "подъебка", "подъёбывать", "подъёбщик", | |
| "приебать", "приебаться", "приёб", "приёбывать", "приёбочный", | |
| "проебать", "проебал", "проебанный", "проёб", "проёбывать", | |
| "разъебай", "разъебать", "разъебанный", "разъёб", "разъёбывать", | |
| "съебать", "съебался", "съебанный", "съёбывать", "съёбочный", | |
| "уебать", "уебал", "уебанец", "уебанка", "уебанский", "уебище" | |
| ]; | |
| // Initialize variables | |
| let badWords = []; | |
| let statusBadge = document.getElementById('statusBadge'); | |
| let fileInput = document.getElementById('fileInput'); | |
| let fileName = document.getElementById('fileName'); | |
| let wordCount = document.getElementById('wordCount'); | |
| let fileInfo = document.getElementById('fileInfo'); | |
| let inputText = document.getElementById('inputText'); | |
| let outputText = document.getElementById('outputText'); | |
| let inputWordCount = document.getElementById('inputWordCount'); | |
| let outputWordCount = document.getElementById('outputWordCount'); | |
| let censoredCount = document.getElementById('censoredCount'); | |
| let censorBtn = document.getElementById('censorBtn'); | |
| let defaultWordsBtn = document.getElementById('defaultWordsBtn'); | |
| let matchCase = document.getElementById('matchCase'); | |
| let matchPartial = document.getElementById('matchPartial'); | |
| let highlightWords = document.getElementById('highlightWords'); | |
| let replaceWithStars = document.getElementById('replaceWithStars'); | |
| // Load default word list initially | |
| loadWordListFromArray(defaultWordList); | |
| statusBadge.innerHTML = '<i class="fas fa-check-circle mr-1"></i> Default word list loaded'; | |
| // Event listeners | |
| fileInput.addEventListener('change', handleFileSelect); | |
| defaultWordsBtn.addEventListener('click', loadDefaultWords); | |
| inputText.addEventListener('input', updateWordCount); | |
| censorBtn.addEventListener('click', censorText); | |
| // Functions | |
| function handleFileSelect(event) { | |
| const file = event.target.files[0]; | |
| if (!file) return; | |
| fileName.textContent = file.name; | |
| fileInfo.classList.remove('hidden'); | |
| const reader = new FileReader(); | |
| reader.onload = function(e) { | |
| const content = e.target.result; | |
| const words = content.split('\n') | |
| .map(word => word.trim()) | |
| .filter(word => word.length > 0); | |
| loadWordListFromArray(words); | |
| statusBadge.innerHTML = `<i class="fas fa-check-circle mr-1"></i> ${words.length} words loaded`; | |
| wordCount.textContent = `${words.length} words`; | |
| }; | |
| reader.readAsText(file); | |
| } | |
| function loadDefaultWords() { | |
| loadWordListFromArray(defaultWordList); | |
| statusBadge.innerHTML = '<i class="fas fa-check-circle mr-1"></i> Default word list loaded'; | |
| wordCount.textContent = `${defaultWordList.length} words`; | |
| fileInfo.classList.remove('hidden'); | |
| fileName.textContent = 'Default Russian word list'; | |
| fileInput.value = ''; // Clear file input | |
| } | |
| function loadWordListFromArray(words) { | |
| badWords = words; | |
| // Create regex patterns for all word forms | |
| badWords = badWords.map(word => { | |
| // Create pattern that matches all forms of the word (with different endings) | |
| return word.replace(/[а-я]/g, (char) => { | |
| return `[${char}${char.toUpperCase()}]`; | |
| }); | |
| }); | |
| } | |
| function updateWordCount() { | |
| const text = inputText.value; | |
| const count = text.trim() === '' ? 0 : text.split(/\s+/).length; | |
| inputWordCount.textContent = count; | |
| } | |
| function censorText() { | |
| const text = inputText.value; | |
| if (!text.trim()) { | |
| outputText.value = ''; | |
| outputWordCount.textContent = '0'; | |
| censoredCount.textContent = '0'; | |
| return; | |
| } | |
| let censoredText = text; | |
| let censoredWords = 0; | |
| const caseSensitive = matchCase.checked; | |
| const partialMatch = matchPartial.checked; | |
| const highlight = highlightWords.checked; | |
| const useStars = replaceWithStars.checked; | |
| // Create a single regex pattern for all bad words | |
| const pattern = new RegExp( | |
| badWords.map(word => | |
| partialMatch ? `(${word}[а-яa-z]*)` : `(\\b${word}\\b)` | |
| ).join('|'), | |
| caseSensitive ? 'g' : 'gi' | |
| ); | |
| censoredText = censoredText.replace(pattern, (match) => { | |
| censoredWords++; | |
| if (useStars) { | |
| const stars = '*'.repeat(match.length); | |
| if (highlight) { | |
| return `<span class="censored" title="Censored word">${stars}</span>`; | |
| } | |
| return stars; | |
| } else { | |
| if (highlight) { | |
| return `<span class="censored" title="Censored word">${match}</span>`; | |
| } | |
| return match; | |
| } | |
| }); | |
| // Update output | |
| outputText.innerHTML = censoredText; | |
| const outputCount = censoredText.trim() === '' ? 0 : censoredText.split(/\s+/).length; | |
| outputWordCount.textContent = outputCount; | |
| censoredCount.textContent = censoredWords; | |
| } | |
| </script> | |
| <p style="border-radius: 8px; text-align: center; font-size: 12px; color: #fff; margin-top: 16px;position: fixed; left: 8px; bottom: 8px; z-index: 10; background: rgba(0, 0, 0, 0.8); padding: 4px 8px;">Made with <img src="https://enzostvs-deepsite.hf.space/logo.svg" alt="DeepSite Logo" style="width: 16px; height: 16px; vertical-align: middle;display:inline-block;margin-right:3px;filter:brightness(0) invert(1);"><a href="https://enzostvs-deepsite.hf.space" style="color: #fff;text-decoration: underline;" target="_blank" >DeepSite</a> - 🧬 <a href="https://enzostvs-deepsite.hf.space?remix=l0ngAF/censor-text" style="color: #fff;text-decoration: underline;" target="_blank" >Remix</a></p></body> | |
| </html> |