View difference between Paste ID: 03AGznkC and cRmQUFeC
SHOW: | | - or go back to the newest paste.
1
// ==UserScript==
2
// @name         Kara Keep Advanced Search Assistant
3
// @namespace    http://karakeep.app/
4-
// @version      1.1
4+
// @version      2.0
5-
// @description  Adds quick access to advanced search filters in Kara Keep
5+
// @description  Reliable search filter insertion for Kara Keep
6
// @author       KaraKeep Assistant
7
// @match        *://karakeep.app/*
8
// @match        *://try.karakeep.app/*
9
// @grant        none
10
// @run-at       document-end
11
// ==/UserScript==
12
13
(function() {
14
    'use strict';
15
16-
    // KaraKeep Advanced Search Assistant v1.5
16+
    // Configuration
17-
    const VERSION = "1.5";
17+
18
    const VERSION = "2.0";
19
    const RETRY_LIMIT = 10;
20
    const RETRY_DELAY = 300;
21
22
    // Setup logging
23
    if (!window.__karaKeepBookmarkletLog) window.__karaKeepBookmarkletLog = [];
24-
    // Set to false to disable space after filter insertion
24+
25
        window.__karaKeepBookmarkletLog.push({ time: Date.now(), msg });
26
        console.log("[🔍 KaraKeep Assistant] " + msg);
27
    };
28
29
    // Prevent duplicate initialization
30
    if (window.__karaKeepUIVersion === VERSION) {
31
        log("UI already active. Use 'removeKaraKeepFilterUI()' to remove");
32-
    let injectionAttempts = 0;
32+
33
    }
34-
    log(`Activating version ${VERSION}`);
34+
35
36
    log("Activated! Version " + VERSION);
37-
    // Create UI elements early to prevent DOM removal issues
37+
38
39
    // UI Elements (created once and reused)
40
    const btn = document.createElement("button");
41
    btn.id = "karaKeepFilterBtn";
42-
    btn.classList.add("kara-keep-filter-btn");
42+
43
    btn.type = "button";
44-
    // Menu creation
44+
    btn.style.cssText = `
45
        display: inline-flex;
46
        align-items: center;
47-
    
47+
        justify-content: center;
48-
    // Define FILTERS array
48+
        white-space: nowrap;
49
        border-radius: 0.375rem;
50
        padding: 0 1rem;
51
        height: 2.5rem;
52
        font-size: 0.875rem;
53
        font-weight: 500;
54
        cursor: pointer;
55
        background-color: transparent;
56
        border: none;
57
        color: hsl(var(--foreground));
58
        transition: background-color 0.2s ease;
59
        margin-left: 0.5rem;
60
    `;
61
62
    const menu = document.createElement("div");
63
    menu.id = "karaKeepFilterMenu";
64
    menu.style.cssText = `
65
        display: none;
66
        position: fixed;
67-
    // Initialize the UI components
67+
        z-index: 100000;
68
        background: #fff;
69-
        // Button styling
69+
        box-shadow: 0 8px 20px rgba(0,0,0,.18);
70-
        btn.style.cssText = `
70+
        border: 1px solid #ccc;
71-
            display: inline-flex;
71+
        border-radius: 6px;
72-
            align-items: center;
72+
        padding: 8px 0;
73-
            justify-content: center;
73+
        color: #222;
74-
            white-space: nowrap;
74+
        font-size: 15px;
75-
            border-radius: 0.375rem;
75+
        max-height: 260px;
76-
            padding: 0 1rem;
76+
        overflow-y: auto;
77-
            height: 2.5rem;
77+
    `;
78-
            font-size: 0.875rem;
78+
79-
            font-weight: 500;
79+
    // Filters list
80-
            cursor: pointer;
80+
81-
            background-color: transparent;
81+
82-
            border: none;
82+
83-
            color: hsl(var(--foreground));
83+
84-
            transition: background-color 0.2s ease;
84+
85-
            margin-left: 0.5rem;
85+
86
        { name: "Type: Text", value: "is:text", desc: "Type: text" },
87
        { name: "Type: Media", value: "is:media", desc: "Type: media" },
88-
        // Menu styling
88+
89-
        menu.style.cssText = `
89+
90-
            display: none;
90+
91-
            position: fixed;
91+
92-
            z-index: 100000;
92+
93-
            background: #fff;
93+
94-
            box-shadow: 0 8px 20px rgba(0,0,0,.18);
94+
95-
            border: 1px solid #ccc;
95+
96-
            border-radius: 6px;
96+
97-
            padding: 8px 0;
97+
98-
            color: #222;
98+
    // Initialize UI components
99-
            font-size: 15px;
99+
100-
            max-height: 260px;
100+
        // Create filter menu items
101-
            overflow-y: auto;
101+
102
            const item = document.createElement("button");
103
            item.textContent = f.name;
104-
        // Build filter menu
104+
105
            item.style.cssText = `
106
                display: block;
107
                width: 100%;
108
                background: none;
109
                border: none;
110
                text-align: left;
111
                padding: 6px 20px;
112
                cursor: pointer;
113
                font-size: 15px;
114
                color: #222;
115
                outline: none;
116
                transition: background 0.2s;
117
            `;
118
            item.addEventListener('mouseenter', () => item.style.background = "#e6f0fa");
119
            item.addEventListener('mouseleave', () => item.style.background = "none");
120
            
121
            item.addEventListener('click', e => {
122
                e.preventDefault();
123
                const searchInput = document.querySelector('input[placeholder="Search"]');
124
                if (!searchInput) return;
125
                
126
                searchInput.focus();
127
                let start = searchInput.selectionStart;
128
                let end = searchInput.selectionEnd;
129
                
130
                if (typeof start === "number") {
131
                    const orig = searchInput.value;
132
                    const insertText = f.value + (ADD_SPACE_AFTER_FILTER ? " " : "");
133
                    searchInput.value = orig.slice(0, start) + insertText + orig.slice(end);
134
                    searchInput.selectionStart = searchInput.selectionEnd = start + insertText.length;
135
                    
136
                    // Trigger input event for React
137
                    const inputEvent = new Event('input', { bubbles: true });
138
                    searchInput.dispatchEvent(inputEvent);
139
                } else {
140
                    searchInput.value += f.value + (ADD_SPACE_AFTER_FILTER ? " " : "");
141
                }
142
                
143-
                // Trigger input event to update search
143+
144-
                const inputEvent = new Event('input', { bubbles: true });
144+
145-
                searchInput.dispatchEvent(inputEvent);
145+
146
            menu.appendChild(item);
147
        });
148
149
        // Button hover effects
150
        btn.addEventListener('mouseenter', () => {
151
            btn.style.backgroundColor = 'hsl(var(--accent))';
152
        });
153-
        // Menu show/hide with positioning
153+
154
            btn.style.backgroundColor = 'transparent';
155
        });
156
157
        // Menu toggle
158-
            } else {
158+
159
            e.stopPropagation();
160
            menu.style.display = menu.style.display === "block" ? "none" : "block";
161
            if (menu.style.display === "block") {
162-
                menu.style.display = "block";
162+
163-
                if (!menu.parentNode) {
163+
164
                menu.style.left = rect.left + "px";
165
                if (!document.getElementById('karaKeepFilterMenu')) {
166
                    document.body.appendChild(menu);
167
                }
168
            }
169-
        // Click outside to close
169+
170
171-
            if (!btn.contains(e.target) && (!menu.contains(e.target) || e.target.tagName === 'BUTTON')) {
171+
        // Close menu when clicking outside
172
        document.addEventListener("mousedown", e => {
173
            if (!btn.contains(e.target) && !menu.contains(e.target)) {
174
                menu.style.display = "none";
175
            }
176-
        // Hover effects
176+
177
    }
178
179
    // Add styles to document
180
    function addStyles() {
181
        const style = document.createElement('style');
182
        style.id = 'kara-keep-assistant-styles';
183
        style.textContent = `
184
            .kara-keep-filter-btn {
185-
    // Injection logic with redundancy checks
185+
186-
    function injectButton() {
186+
187-
        if (injectionAttempts > 20) {
187+
188-
            log("Maximum injection attempts reached. Aborting.");
188+
189
            .kara-keep-filter-btn:hover {
190
                background-color: hsl(var(--accent)) !important;
191-
        injectionAttempts++;
191+
192
            #karaKeepFilterMenu {
193
                font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, sans-serif;
194
                z-index: 100000;
195-
            log("Buttons container not found. Retrying...");
195+
196-
            setTimeout(injectButton, 500);
196+
197
                background-color: #e6f0fa !important;
198
            }
199
        `;
200
        document.head.appendChild(style);
201
        return style;
202-
            log("Button already exists");
202+
203
204
    // Inject button into toolbar
205
    function injectButton(retryCount = 0) {
206
        if (retryCount > RETRY_LIMIT) {
207
            log("Failed to find button container after " + RETRY_LIMIT + " attempts");
208
            return;
209
        }
210
211
        const buttonsContainer = document.querySelector('.flex.min-w-max.flex-wrap.overflow-hidden');
212
        if (!buttonsContainer) {
213
            setTimeout(() => injectButton(retryCount + 1), RETRY_DELAY);
214
            return;
215
        }
216-
        // Add menu to DOM if not present
216+
217-
        if (!document.getElementById('karaKeepFilterMenu')) {
217+
218-
            document.body.appendChild(menu);
218+
219
            return;
220
        }
221
222-
    // Add CSS styles
222+
223
        const editButton = buttonsContainer.querySelector('button:not([disabled])');
224
        if (editButton) {
225
            buttonsContainer.insertBefore(btn, editButton);
226
            log("UI injected successfully!");
227
        } else {
228
            buttonsContainer.appendChild(btn);
229
            log("UI injected at end of buttons");
230
        }
231
    }
232
233
    // Setup DOM observer
234
    function setupObserver() {
235
        const observer = new MutationObserver(mutations => {
236
            // Check if our button was removed
237-
                z-index: 10000;
237+
            if (!document.contains(btn)) {
238
                log("Button was removed - reinjecting");
239
                injectButton();
240
            }
241
        });
242
243
        observer.observe(document.body, {
244
            childList: true,
245
            subtree: true
246-
    // MutationObserver to handle DOM changes
246+
247
    }
248-
        const observer = new MutationObserver((mutations) => {
248+
249-
            let headerChanged = false;
249+
    // Cleanup function
250-
            for (const mutation of mutations) {
250+
    function setupCleanup() {
251-
                if (mutation.type === 'childList') {
251+
252-
                    // Check if our button was removed
252+
253-
                    if (!document.getElementById('karaKeepFilterBtn')) {
253+
254-
                        headerChanged = true;
254+
255-
                        break;
255+
256-
                    }
256+
257-
                    // Check if buttons container changed
257+
258-
                    if (mutation.target.classList && mutation.target.classList.contains('flex')) {
258+
259-
                        headerChanged = true;
259+
260-
                        break;
260+
261-
                    }
261+
262
    function init() {
263
        log("Initializing UI components...");
264
        initUI();
265-
            if (headerChanged) {
265+
        const styles = addStyles();
266-
                log("Header changed - reinjecting button");
266+
267
        setupObserver();
268
        setupCleanup();
269
        
270
        // Add menu to DOM
271
        document.body.appendChild(menu);
272
        
273
        log("Assistant is fully initialized");
274
    }
275
276
    // Start initialization when DOM is ready
277
    if (document.readyState === 'loading') {
278
        document.addEventListener('DOMContentLoaded', init);
279-
        log("Initializing...");
279+
280
        setTimeout(init, 1000);
281-
        addStyles();
281+
282
})();
283