Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- // ==UserScript==
- // @name Kara Keep Advanced Search Assistant
- // @namespace http://karakeep.app/
- // @version 2.0
- // @description Reliable search filter insertion for Kara Keep
- // @author KaraKeep Assistant
- // @match *://karakeep.app/*
- // @match *://try.karakeep.app/*
- // @grant none
- // @run-at document-end
- // ==/UserScript==
- (function() {
- 'use strict';
- // Configuration
- const ADD_SPACE_AFTER_FILTER = true;
- const VERSION = "2.0";
- const RETRY_LIMIT = 10;
- const RETRY_DELAY = 300;
- // Setup logging
- if (!window.__karaKeepBookmarkletLog) window.__karaKeepBookmarkletLog = [];
- const log = msg => {
- window.__karaKeepBookmarkletLog.push({ time: Date.now(), msg });
- console.log("[🔍 KaraKeep Assistant] " + msg);
- };
- // Prevent duplicate initialization
- if (window.__karaKeepUIVersion === VERSION) {
- log("UI already active. Use 'removeKaraKeepFilterUI()' to remove");
- return;
- }
- window.__karaKeepUIVersion = VERSION;
- log("Activated! Version " + VERSION);
- log(`Space after filters: ${ADD_SPACE_AFTER_FILTER ? "ENABLED" : "DISABLED"}`);
- // UI Elements (created once and reused)
- const btn = document.createElement("button");
- btn.id = "karaKeepFilterBtn";
- btn.textContent = "Search Filters";
- btn.type = "button";
- btn.style.cssText = `
- display: inline-flex;
- align-items: center;
- justify-content: center;
- white-space: nowrap;
- border-radius: 0.375rem;
- padding: 0 1rem;
- height: 2.5rem;
- font-size: 0.875rem;
- font-weight: 500;
- cursor: pointer;
- background-color: transparent;
- border: none;
- color: hsl(var(--foreground));
- transition: background-color 0.2s ease;
- margin-left: 0.5rem;
- `;
- const menu = document.createElement("div");
- menu.id = "karaKeepFilterMenu";
- menu.style.cssText = `
- display: none;
- position: fixed;
- z-index: 100000;
- background: #fff;
- box-shadow: 0 8px 20px rgba(0,0,0,.18);
- border: 1px solid #ccc;
- border-radius: 6px;
- padding: 8px 0;
- color: #222;
- font-size: 15px;
- max-height: 260px;
- overflow-y: auto;
- `;
- // Filters list
- const FILTERS = [
- { name: "Favorited", value: "is:fav", desc: "Favorited bookmarks" },
- { name: "Archived", value: "is:archived", desc: "Archived bookmarks" },
- { name: "Tagged", value: "is:tagged", desc: "Has one or more tags" },
- { name: "In List", value: "is:inlist", desc: "Is in at least one list" },
- { name: "Type: Link", value: "is:link", desc: "Type: link" },
- { name: "Type: Text", value: "is:text", desc: "Type: text" },
- { name: "Type: Media", value: "is:media", desc: "Type: media" },
- { name: "URL", value: "url:example.com", desc: "Match URL substring" },
- { name: "After Date", value: "after:2024-01-01", desc: "After date (YYYY-MM-DD)" },
- { name: "Before Date", value: "before:2025-01-01", desc: "Before date (YYYY-MM-DD)" },
- { name: "Tag", value: "#important", desc: "With tag" },
- { name: "List", value: 'list:"to review"', desc: "In specific list" },
- { name: "Feed", value: "feed:Hackernews", desc: "Imported from feed" },
- { name: "Age <1w", value: "age:<1w", desc: "Younger than 1 week" },
- { name: "Age >1y", value: "age:>1y", desc: "Older than 1 year" }
- ];
- // Initialize UI components
- function initUI() {
- // Create filter menu items
- FILTERS.forEach(f => {
- const item = document.createElement("button");
- item.textContent = f.name;
- item.title = f.desc;
- item.style.cssText = `
- display: block;
- width: 100%;
- background: none;
- border: none;
- text-align: left;
- padding: 6px 20px;
- cursor: pointer;
- font-size: 15px;
- color: #222;
- outline: none;
- transition: background 0.2s;
- `;
- item.addEventListener('mouseenter', () => item.style.background = "#e6f0fa");
- item.addEventListener('mouseleave', () => item.style.background = "none");
- item.addEventListener('click', e => {
- e.preventDefault();
- const searchInput = document.querySelector('input[placeholder="Search"]');
- if (!searchInput) return;
- searchInput.focus();
- let start = searchInput.selectionStart;
- let end = searchInput.selectionEnd;
- if (typeof start === "number") {
- const orig = searchInput.value;
- const insertText = f.value + (ADD_SPACE_AFTER_FILTER ? " " : "");
- searchInput.value = orig.slice(0, start) + insertText + orig.slice(end);
- searchInput.selectionStart = searchInput.selectionEnd = start + insertText.length;
- // Trigger input event for React
- const inputEvent = new Event('input', { bubbles: true });
- searchInput.dispatchEvent(inputEvent);
- } else {
- searchInput.value += f.value + (ADD_SPACE_AFTER_FILTER ? " " : "");
- }
- menu.style.display = "none";
- log(`Added filter: '${f.value}${ADD_SPACE_AFTER_FILTER ? ' ' : ''}'`);
- });
- menu.appendChild(item);
- });
- // Button hover effects
- btn.addEventListener('mouseenter', () => {
- btn.style.backgroundColor = 'hsl(var(--accent))';
- });
- btn.addEventListener('mouseleave', () => {
- btn.style.backgroundColor = 'transparent';
- });
- // Menu toggle
- btn.addEventListener('click', e => {
- e.stopPropagation();
- menu.style.display = menu.style.display === "block" ? "none" : "block";
- if (menu.style.display === "block") {
- const rect = btn.getBoundingClientRect();
- menu.style.top = (rect.bottom + 4) + "px";
- menu.style.left = rect.left + "px";
- if (!document.getElementById('karaKeepFilterMenu')) {
- document.body.appendChild(menu);
- }
- }
- });
- // Close menu when clicking outside
- document.addEventListener("mousedown", e => {
- if (!btn.contains(e.target) && !menu.contains(e.target)) {
- menu.style.display = "none";
- }
- });
- }
- // Add styles to document
- function addStyles() {
- const style = document.createElement('style');
- style.id = 'kara-keep-assistant-styles';
- style.textContent = `
- .kara-keep-filter-btn {
- background-color: transparent;
- transition: background-color 0.2s ease;
- margin-left: 0.5rem;
- }
- .kara-keep-filter-btn:hover {
- background-color: hsl(var(--accent)) !important;
- }
- #karaKeepFilterMenu {
- font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, sans-serif;
- z-index: 100000;
- }
- #karaKeepFilterMenu button:hover {
- background-color: #e6f0fa !important;
- }
- `;
- document.head.appendChild(style);
- return style;
- }
- // Inject button into toolbar
- function injectButton(retryCount = 0) {
- if (retryCount > RETRY_LIMIT) {
- log("Failed to find button container after " + RETRY_LIMIT + " attempts");
- return;
- }
- const buttonsContainer = document.querySelector('.flex.min-w-max.flex-wrap.overflow-hidden');
- if (!buttonsContainer) {
- setTimeout(() => injectButton(retryCount + 1), RETRY_DELAY);
- return;
- }
- // Check if button already exists
- if (document.getElementById('karaKeepFilterBtn')) {
- return;
- }
- // Find insertion point
- const editButton = buttonsContainer.querySelector('button:not([disabled])');
- if (editButton) {
- buttonsContainer.insertBefore(btn, editButton);
- log("UI injected successfully!");
- } else {
- buttonsContainer.appendChild(btn);
- log("UI injected at end of buttons");
- }
- }
- // Setup DOM observer
- function setupObserver() {
- const observer = new MutationObserver(mutations => {
- // Check if our button was removed
- if (!document.contains(btn)) {
- log("Button was removed - reinjecting");
- injectButton();
- }
- });
- observer.observe(document.body, {
- childList: true,
- subtree: true
- });
- }
- // Cleanup function
- function setupCleanup() {
- window.removeKaraKeepFilterUI = () => {
- btn.remove();
- menu.remove();
- const styles = document.getElementById('kara-keep-assistant-styles');
- if (styles) styles.remove();
- window.__karaKeepUIVersion = null;
- log("Assistant removed. Refresh page to restore original UI");
- };
- }
- // Main initialization
- function init() {
- log("Initializing UI components...");
- initUI();
- const styles = addStyles();
- injectButton();
- setupObserver();
- setupCleanup();
- // Add menu to DOM
- document.body.appendChild(menu);
- log("Assistant is fully initialized");
- }
- // Start initialization when DOM is ready
- if (document.readyState === 'loading') {
- document.addEventListener('DOMContentLoaded', init);
- } else {
- setTimeout(init, 1000);
- }
- })();
Advertisement
Add Comment
Please, Sign In to add comment