feat: url cleaner exceptions keep-all and defaults reset

This commit is contained in:
2026-02-27 06:48:39 +00:00
parent 4c2d423715
commit a0346a64f0
4 changed files with 640 additions and 13 deletions

View File

@@ -1,9 +1,10 @@
<script setup>
import { ref, watch, onUnmounted } from 'vue'
import { Copy, Trash2, ExternalLink, Power, Zap, X } from 'lucide-vue-next'
import { Copy, Trash2, ExternalLink, Power, Zap, X, Settings } from 'lucide-vue-next'
import { useExtension } from '../../composables/useExtension'
import { useLocalStorage } from '../../composables/useLocalStorage'
import ExtensionStatus from './common/ExtensionStatus.vue'
import UrlCleanerExceptionsModal from './UrlCleanerExceptionsModal.vue'
// Extension integration
const { isExtensionReady, isListening, lastClipboardText, startListening, stopListening, writeClipboard } = useExtension()
@@ -13,6 +14,21 @@ const inputUrl = ref('')
const cleanedHistory = useLocalStorage('url-cleaner-history', [])
const isWatchEnabled = useLocalStorage('url-cleaner-watch-enabled', false)
// Exceptions management
const showExceptionsModal = ref(false)
const defaultExceptions = [
{ id: 'yt', domainPattern: '*.youtube.com', keepParams: ['v', 't'], keepHash: false, keepAllParams: false, isEnabled: true, isDefault: true },
{ id: 'yt-short', domainPattern: 'youtu.be', keepParams: ['t'], keepHash: false, keepAllParams: false, isEnabled: true, isDefault: true }
]
const exceptions = useLocalStorage('url-cleaner-exceptions', defaultExceptions)
// Helper to match domain with glob pattern
const matchDomain = (pattern, domain) => {
// Escape regex chars except *
const regexString = '^' + pattern.replace(/[.+^${}()|[\]\\]/g, '\\$&').replace(/\*/g, '.*') + '$'
return new RegExp(regexString, 'i').test(domain)
}
// Watch for clipboard changes from extension
watch(lastClipboardText, (newText) => {
if (isWatchEnabled.value && newText) {
@@ -62,13 +78,41 @@ const processUrl = (text, autoClipboard = false) => {
try {
const urlObj = new URL(text)
// Remove query params and hash
if (urlObj.search || urlObj.hash) {
urlObj.search = ''
urlObj.hash = ''
cleanedUrl = urlObj.toString()
// Remove trailing slash if it wasn't there before? usually keep it standard
const hostname = urlObj.hostname
// Check for exceptions
const matchedRule = exceptions.value.find(rule =>
rule.isEnabled && matchDomain(rule.domainPattern, hostname)
)
if (matchedRule) {
if (!matchedRule.keepAllParams) {
// Exception logic: keep specific params
const params = new URLSearchParams(urlObj.search)
const keys = Array.from(params.keys())
for (const key of keys) {
if (!matchedRule.keepParams.includes(key)) {
params.delete(key)
}
}
urlObj.search = params.toString()
}
if (!matchedRule.keepHash) {
urlObj.hash = ''
}
} else {
// Default behavior: remove all query params and hash
if (urlObj.search || urlObj.hash) {
urlObj.search = ''
urlObj.hash = ''
}
}
cleanedUrl = urlObj.toString()
// Remove trailing slash if it wasn't there before? usually keep it standard
} catch (e) {
// Invalid URL format
if (!autoClipboard) {
@@ -135,7 +179,12 @@ onUnmounted(() => {
<div class="tool-panel">
<div class="panel-header">
<h2 class="tool-title">URL Cleaner</h2>
<ExtensionStatus :isReady="isExtensionReady" />
<div class="header-actions">
<button class="icon-btn settings-btn" @click="showExceptionsModal = true" title="Cleaning Exceptions">
<Settings size="20" />
</button>
<ExtensionStatus :isReady="isExtensionReady" />
</div>
</div>
<div class="input-section">
@@ -205,6 +254,14 @@ onUnmounted(() => {
<div class="empty-state" v-else>
<p>Paste a URL above or enable "Watch Clipboard" to automatically clean links.</p>
</div>
<UrlCleanerExceptionsModal
:isOpen="showExceptionsModal"
:exceptions="exceptions"
:defaultRules="defaultExceptions"
@update:exceptions="exceptions = $event"
@close="showExceptionsModal = false"
/>
</div>
</div>
</template>
@@ -422,4 +479,40 @@ onUnmounted(() => {
text-align: center;
padding: 2rem;
}
.header-actions {
display: flex;
align-items: center;
gap: 0.8rem;
position: absolute;
right: 0;
top: 50%;
transform: translateY(-50%);
}
.settings-btn {
background: rgba(255, 255, 255, 0.1);
width: 32px;
height: 32px;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
padding: 0;
color: var(--text-secondary);
transition: all 0.3s ease;
}
.settings-btn:hover {
background: rgba(255, 255, 255, 0.2);
color: var(--primary-accent);
}
:global(:root[data-theme="light"]) .settings-btn {
background: rgba(0, 0, 0, 0.05);
}
:global(:root[data-theme="light"]) .settings-btn:hover {
background: rgba(0, 0, 0, 0.1);
}
</style>