Prompt With List... On Steroids! πŸ’ͺ🏼

Sorry for the questions. But is it possible to have a different prompt color for light and dark mode?
Note: Transparency works perfectly for me now. Thanks.

That could be quite complicated. We'd either need two colour values in an array or another set of colour variables.

Both these options are probably a bit much for the average user. I may be wrong, but I think most people stick to one or the other rather than change at certain times of day, so I figured it would be good to have a default look that works for either and a custom look that the user could configure.

However...

There's nothing stopping you from doing this:

PWLOS - Example Caller.kmmacros (38 KB)

Macro screenshot

Thanks that's a good workable solution for me. :+1:

1 Like

I've added this as an option in the Example Caller in the initial post.

I've also added custom Light/Dark mode colours as a set of prompt defaults in the submacro itself, so that you can commit to a particular set of colours for each, if you like.

Screenshot

1 Like

Don't forget, macOS has three modes: Light, Dark and Auto.

This is kind of moot though, no?

I asked Claude to modify the HTML to maybe further improve the matching as the list narrows. I'm not sure what redundancies, if any, it may have included but thought I should post in case it's of any interest. In my limited testing so far, it's working fairly well in terms of what I was looking for. Here are the parts of the HTML it altered/added:

  1. Enhanced filterList() function
// Enhanced filtering function with smart matching
function filterList() {
    var filter = document.querySelector('input').value.toLowerCase().trim();
    var terms = filter.split(' ').filter(function(term) { return term.length > 0; });
    var items = document.querySelectorAll('li');
    var visibleItems = [];
    var exactMatch = null;
    var bestMatch = null;
    var bestScore = -1;

    // First pass: filter items and find matches
    for (var i = 0; i < items.length; i++) {
        var itemText = items[i].textContent.toLowerCase();
        var isVisible = false;

        if (terms.length === 0) {
            // Show all items if no filter
            isVisible = true;
        } else if (terms.every(function(term) { return itemText.indexOf(term) > -1; })) {
            isVisible = true;
        }

        if (isVisible) {
            items[i].style.display = '';
            visibleItems.push(items[i]);

            // Check for exact match
            if (filter && itemText === filter) {
                exactMatch = items[i];
            }
            
            // Calculate match score for best match
            if (filter && !exactMatch) {
                var score = calculateMatchScore(itemText, filter, terms);
                if (score > bestScore) {
                    bestScore = score;
                    bestMatch = items[i];
                }
            }
        } else {
            items[i].style.display = 'none';
        }
        
        // Remove selection from all items
        items[i].classList.remove('selected');
    }

    // Second pass: select the best item
    var itemToSelect = null;
    
    if (exactMatch) {
        itemToSelect = exactMatch;
    } else if (bestMatch) {
        itemToSelect = bestMatch;
    } else if (visibleItems.length > 0) {
        itemToSelect = visibleItems[0];
    }

    if (itemToSelect) {
        itemToSelect.classList.add('selected');
        scrollSelectedItemIntoView();
    }
}
  1. New calculateMatchScore() function:
// Calculate match score for an item
function calculateMatchScore(itemText, filter, terms) {
    var score = 0;
    
    // Boost score if item starts with the filter
    if (itemText.startsWith(filter)) {
        score += 1000;
    }
    
    // Boost score if item contains the filter as a substring
    var filterIndex = itemText.indexOf(filter);
    if (filterIndex !== -1) {
        score += 500 - filterIndex; // Earlier matches get higher scores
    }
    
    // Score based on how many terms match and their positions
    terms.forEach(function(term) {
        var termIndex = itemText.indexOf(term);
        if (termIndex !== -1) {
            score += 100 - termIndex; // Earlier matches get higher scores
            
            // Bonus if term appears at word boundary
            if (termIndex === 0 || itemText[termIndex - 1] === ' ') {
                score += 50;
            }
        }
    });
    
    // Prefer shorter items (more specific matches)
    score += Math.max(0, 200 - itemText.length);
    
    return score;
}
  1. Updated scrollSelectedItemIntoView() function:
function scrollSelectedItemIntoView() {
    var selectedItem = document.querySelector('li.selected');
    if (!selectedItem) return;  // Added null check
    
    var list = document.querySelector('ul');

    var listRect = list.getBoundingClientRect();
    var itemRect = selectedItem.getBoundingClientRect();

    if (itemRect.top > listRect.top + listRect.height / 2) {
        list.scrollTop += itemRect.top - listRect.top - listRect.height / 2 + itemRect.height / 2;
    } else if (itemRect.bottom < listRect.top + listRect.height / 2) {
        list.scrollTop -= listRect.top + listRect.height / 2 - itemRect.bottom + itemRect.height / 2;
    }
}

I must have misunderstood what you meant as I thought I'd fixed that already. I got the impression you meant that the first result should be selected, ready to be marked and submitted when the list is refined with the search function. Is that not what you meant?

ahh I apologize if I wasn't clear enough. I didn't necessarily want the first result in the narrowing down of the list to always be selected, but the best possible match given what I've typed to be selected, insofar as this is possible. If I have an item named "downloads" in the list and I start to type "dow" then downloads would be selected even if there are other items in the list that may include that word...those other items would still be visible but the closest match would take priority.

Edit: Honestly, if this further clarifies things, the way the native prompt list action narrows and highlights matches as you type works exactly as I think I want it. Although admittedly, I don't know all that is going on under the hood or how complex that would be to implement.

You mean fuzzy searching? Or do you mean showing the match that starts with the typed string first?

hmm...I think something like fuzzy searching would probably be ideal. In searching the forum, I came across the post below, and I'm now thinking because the native prompt list uses machine learning it's remembering what I select most often and that's perhaps why I can get to it so quickly.

1 Like

Hello,
first of all, @noisneil, congratulations for the epic work!!!
A couple questions (please forgive me if they have already been answered, but I haven't found anything so far):

  • Is it possible to show multiple windows (with different sets of options of course) at the same time? And set each window's left corner value, in order to make them all visible?
  • Is it possible to start with all answers selected (manually deselecting what the users wants to deselect)?

Thank you so much!!
Jan

You can call two versions of the prompt using async submacros. I've added these to the initial macro download.

Trigger the first macro, which uses the second to call the main submacro. In order to make this work, I've had to make some adjustments to the main sub, which I'll detail in the next post.

I've added ⌘A to select all, which is more versatile than pre-selection.

3 Likes

Update:

  • Fixed an issue where click-drag selections weren't being returned properly.
  • Added a variable to set the xy position of the prompt by its top-left corner. If this variable is empty, the prompt will be centred, as was its previous behaviour.
  • Added ⌘A to select all list entries.
  • Click Away to Dismiss is now optional; enter anything in this field to enable it. This was necessary in order to enable calling multiple instances of the prompt, as requested by @freewind1974, two posts previously.
4 Likes