Action

Switch Syntax

Posted by @jsamlarose, Last update 2 days ago

Simply syntax switcher using an HTML view, with a filter to quickly target desired syntax

Steps

  • script

    // ========================================
    // CONFIGURATION VARIABLES - Edit these to customise
    // ========================================
    
    // Display names for UI elements
    const displayNames = {
        singular: "syntax",     // e.g., "Insert Selected syntax"
        plural: "syntaxes",     // e.g., "Filter syntaxes..."
        createNew: "syntax"     // Not used for this script
    };
    
    // Output variable name for Drafts.send() - this should match what your second script expects
    const outputVariableName = "selected_syntax";
    
    // Placeholder text for search bar
    const searchPlaceholder = `Filter ${displayNames.plural}...`;
    
    // ========================================
    // No need to edit below this line
    // ========================================
    
    // Function to format date for display
    function formatDate(date) {
        const options = { year: 'numeric', month: 'short', day: 'numeric' };
        return date.toLocaleDateString(undefined, options);
    }
    
    // Get all available syntaxes
    const syntaxes = Syntax.getAll();
    const currentSyntax = draft.syntax;
    
    // Create items array from syntaxes
    let items = [];
    
    syntaxes.forEach((syntax, index) => {
        const isCurrentSyntax = syntax.name === currentSyntax.name;
        
        items.push({
            text: syntax.name,
            title: syntax.name,
            uuid: syntax.name, // Use syntax name as unique identifier
            isCurrent: isCurrentSyntax,
            syntaxObject: syntax
        });
    });
    
    // Sort alphabetically
    items.sort((a, b) => a.title.localeCompare(b.title));
    
    // Format items for menu display
    let listItems = items.map((item, index) => {
        return {
            text: item.text,
            title: item.title,
            uuid: item.uuid,
            display: `${item.text}`,
            isCurrent: item.isCurrent,
            syntaxObject: item.syntaxObject
        };
    });
    
    // HTML and JavaScript for the HTML view menu
    let html = `
    <!DOCTYPE html>
    <html>
    <head>
        <meta name="viewport" content="width=device-width, initial-scale=1">
        <style>
            body {
                font-family: 'Avenir Next', -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen', 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue', sans-serif;
                padding: 0;
                margin: 0;
                background-color: #1e1e1e;
                color: #e0e0e0;
            }
            #topBar {
                position: fixed;
                top: 0;
                left: 0;
                right: 0;
                display: flex;
                gap: 0.5em;
                background-color: #1e1e1e;
                padding: 0.5em;
                border-bottom: 1px solid #3d3d3d;
                z-index: 1000;
            }
            #searchBar {
                flex-grow: 1;
                padding: 0.5em;
                background-color: #2d2d2d;
                color: #e0e0e0;
                border: 1px solid #3d3d3d;
                border-radius: 4px;
            }
            #buttonContainer {
                display: flex;
                gap: 0.5em;
            }
            .actionButton {
                background-color: #2d2d2d;
                color: #e0e0e0;
                border: 1px solid #3d3d3d;
                border-radius: 4px;
                padding: 0.5em;
                cursor: pointer;
            }
            .actionButton:hover {
                background-color: #444444;
            }
            ul {
                list-style: none;
                padding: 0 1em;
                margin: 0;
                margin-top: 3.5em;
                outline: none;
            }
            li {
                padding: 0.5em;
                border-bottom: 1px solid #3d3d3d;
                cursor: pointer;
                display: flex;
                justify-content: space-between;
                align-items: center;
            }
            li.selected {
                background-color: #2a2a1e;
            }
            li.highlight {
                background-color: #333333;
            }
            li.current {
                background-color: #2d4a3d;
                border-left: 3px solid #66cc99;
            }
            li.current.highlight {
                background-color: #3a5a4a;
            }
            .randomColor1 { color: #ff6600; }
            .randomColor2 { color: #ff9900; }
            .randomColor3 { color: #ffcc00; }
            .randomColor4 { color: #99cc33; }
            .randomColor5 { color: #66cc99; }
            .randomColor6 { color: #9999cc; }
            .currentColor { color: #66cc99 !important; }
            .itemContent {
                flex-grow: 1;
                padding-right: 10px;
            }
            .statusLabel {
                font-size: 0.8em;
                opacity: 0.6;
                margin-top: 2px;
                font-style: italic;
            }
        </style>
    </head>
    <body>
        <div id="topBar">
            <input type="text" id="searchBar" placeholder="${searchPlaceholder}" autocomplete="off" autocorrect="off" autocapitalize="off" spellcheck="false">
            <div id="buttonContainer">
                <button id="cancelButton" class="actionButton" onclick="cancelSelection()">Cancel</button>
            </div>
        </div>
        
        <ul id="itemList" tabindex="0"></ul>
    
        <script>
        let listItems = ${JSON.stringify(listItems)};
        let displayNames = ${JSON.stringify(displayNames)};
        let outputVariableName = ${JSON.stringify(outputVariableName)};
        let selectedItems = new Set();
        let currentHighlightIndex = -1;
        let focusableElements = [];
        let currentFocusIndex = 0;
    
        function createListItems() {
            let itemList = document.getElementById('itemList');
            itemList.innerHTML = '';
            
            let filteredItems = filterItemsBySearch();
            
            filteredItems.forEach((item, index) => {
                let li = document.createElement('li');
                
                let contentDiv = document.createElement('div');
                contentDiv.className = 'itemContent';
                contentDiv.textContent = item.title;
                
                // Add status indicator if this is the current syntax
                if (item.isCurrent) {
                    let statusDiv = document.createElement('div');
                    statusDiv.textContent = 'Current syntax';
                    statusDiv.className = 'statusLabel';
                    contentDiv.appendChild(statusDiv);
                    li.classList.add('current');
                }
                
                li.appendChild(contentDiv);
                
                li.dataset.value = item.text;
                li.dataset.uuid = item.uuid;
                li.dataset.searchable = item.title.toLowerCase();
    
                // Color coding
                if (item.isCurrent) {
                    li.classList.add('currentColor');
                } else {
                    li.classList.add('randomColor' + ((index % 6) + 1));
                }
    
                // Auto-select current syntax
                if (item.isCurrent) {
                    selectedItems.add(item.uuid);
                    li.classList.add('selected');
                }
    
                li.onclick = function () {
                    const selectedSyntax = this.dataset.uuid;
                    const currentSyntaxItem = listItems.find(item => item.isCurrent);
                    
                    if (currentSyntaxItem && selectedSyntax === currentSyntaxItem.uuid) {
                        // Selected current syntax - cancel/do nothing
                        cancelSelection();
                    } else {
                        // Selected different syntax - switch immediately
                        switchSyntax(selectedSyntax);
                    }
                };
    
                itemList.appendChild(li);
            });
            
            // No need to update button since we switch immediately
        }
        
        function filterItemsBySearch() {
            let searchText = document.getElementById('searchBar').value.toLowerCase();
            
            if (searchText === '') {
                return listItems;
            }
            
            return listItems.filter(item => {
                return item.title.toLowerCase().includes(searchText);
            });
        }
    
        function filterItems() {
            let searchText = document.getElementById('searchBar').value.toLowerCase();
            let itemList = document.getElementById('itemList');
            
            Array.from(itemList.children).forEach(item => {
                let searchableText = item.dataset.searchable || item.textContent.toLowerCase();
                item.style.display = searchableText.includes(searchText) ? '' : 'none';
            });
        }
        
        function updateSwitchButton() {
            // Function removed - no longer needed since we switch immediately
        }
    
        function highlightItem(index, preventScroll = false) {
            let visibleItems = Array.from(itemList.children).filter(item => item.style.display !== 'none');
            visibleItems.forEach(item => item.classList.remove('highlight'));
            if (index >= 0 && index < visibleItems.length) {
                visibleItems[index].classList.add('highlight');
                
                if (preventScroll) return;
                
                const item = visibleItems[index];
                const itemRect = item.getBoundingClientRect();
                const headerHeight = 56;
                const viewportTop = headerHeight;
                const viewportBottom = window.innerHeight;
                
                if (itemRect.top < viewportTop) {
                    window.scrollTo({
                        top: window.scrollY + (itemRect.top - viewportTop - 10),
                        behavior: 'smooth'
                    });
                } else if (itemRect.bottom > viewportBottom) {
                    window.scrollTo({
                        top: window.scrollY + (itemRect.bottom - viewportBottom + 10),
                        behavior: 'smooth'
                    });
                }
            }
        }
    
        function handleKeyDown(event) {
            if (event.target.id === 'searchBar') {
                if (event.key === 'ArrowDown') {
                    let visibleItems = Array.from(itemList.children).filter(item => item.style.display !== 'none');
                    if (visibleItems.length > 0) {
                        currentHighlightIndex = 0;
                        highlightItem(currentHighlightIndex, true);
                        
                        const currentScrollY = window.scrollY;
                        document.getElementById('itemList').focus();
                        window.scrollTo(0, currentScrollY);
                        
                        setTimeout(() => {
                            window.scrollTo(0, currentScrollY);
                        }, 0);
                        
                        requestAnimationFrame(() => {
                            window.scrollTo(0, currentScrollY);
                        });
                        
                        event.preventDefault();
                    }
                    return;
                } else if (event.key === 'Enter') {
                    // Find highlighted item and switch immediately
                    let visibleItems = Array.from(itemList.children).filter(item => item.style.display !== 'none');
                    if (visibleItems.length > 0) {
                        // If nothing highlighted, highlight first item
                        if (currentHighlightIndex < 0) {
                            currentHighlightIndex = 0;
                        }
                        
                        if (currentHighlightIndex < visibleItems.length) {
                            const selectedSyntax = visibleItems[currentHighlightIndex].dataset.uuid;
                            const currentSyntaxItem = listItems.find(item => item.isCurrent);
                            
                            if (currentSyntaxItem && selectedSyntax === currentSyntaxItem.uuid) {
                                cancelSelection();
                            } else {
                                switchSyntax(selectedSyntax);
                            }
                        }
                    }
                    event.preventDefault();
                    return;
                } else if (event.key === 'Escape') {
                    cancelSelection();
                    event.preventDefault();
                    return;
                }
                return;
            }
            
            let visibleItems = Array.from(itemList.children).filter(item => item.style.display !== 'none');
    
            if (event.key === 'ArrowDown') {
                currentHighlightIndex = Math.min(currentHighlightIndex + 1, visibleItems.length - 1);
                highlightItem(currentHighlightIndex);
                event.preventDefault();
            } else if (event.key === 'ArrowUp') {
                currentHighlightIndex = Math.max(currentHighlightIndex - 1, 0);
                highlightItem(currentHighlightIndex);
                event.preventDefault();
            } else if (event.key === 'Enter') {
                // Switch to highlighted item immediately
                if (currentHighlightIndex >= 0 && currentHighlightIndex < visibleItems.length) {
                    let highlightedItem = visibleItems[currentHighlightIndex];
                    if (highlightedItem) {
                        const selectedSyntax = highlightedItem.dataset.uuid;
                        const currentSyntaxItem = listItems.find(item => item.isCurrent);
                        
                        if (currentSyntaxItem && selectedSyntax === currentSyntaxItem.uuid) {
                            cancelSelection();
                        } else {
                            switchSyntax(selectedSyntax);
                        }
                    }
                }
                event.preventDefault();
            } else if (event.key === ' ') {
                // Space also switches immediately
                if (currentHighlightIndex >= 0 && currentHighlightIndex < visibleItems.length) {
                    let highlightedItem = visibleItems[currentHighlightIndex];
                    if (highlightedItem) {
                        const selectedSyntax = highlightedItem.dataset.uuid;
                        const currentSyntaxItem = listItems.find(item => item.isCurrent);
                        
                        if (currentSyntaxItem && selectedSyntax === currentSyntaxItem.uuid) {
                            cancelSelection();
                        } else {
                            switchSyntax(selectedSyntax);
                        }
                    }
                }
                event.preventDefault();
            } else if (event.key === 'Escape') {
                cancelSelection();
                event.preventDefault();
            } else if (event.key === 'ArrowLeft' || event.key === 'ArrowRight') {
                handleArrowNavigation(event.key);
                event.preventDefault();
            }
        }
    
        function handleArrowNavigation(direction) {
            if (focusableElements.length === 0) {
                focusableElements = [
                    document.getElementById('searchBar'),
                    document.getElementById('cancelButton')
                ];
            }
    
            if (direction === 'ArrowRight') {
                currentFocusIndex = (currentFocusIndex + 1) % focusableElements.length;
            } else {
                currentFocusIndex = (currentFocusIndex - 1 + focusableElements.length) % focusableElements.length;
            }
    
            focusableElements[currentFocusIndex].focus();
        }
    
        function switchSyntax(syntaxName) {
            Drafts.send(outputVariableName, JSON.stringify({
                action: "switch",
                syntaxName: syntaxName
            }));
            Drafts.continue();
        }
    
        function cancelSelection() {
            Drafts.send(outputVariableName, JSON.stringify({
                action: "cancel"
            }));
            Drafts.continue();
        }
    
        // Initialise the interface
        createListItems();
        document.getElementById('searchBar').addEventListener('input', filterItems);
        document.addEventListener('keydown', handleKeyDown);
    
        // Set focus to the search bar after a slight delay to ensure it's ready
        setTimeout(() => {
            document.getElementById('searchBar').focus();
        }, 100);
    
        window.onload = () => {
            document.getElementById('searchBar').focus();
        };
    
        </script>
    </body>
    </html>
    `;
    
    // Show the HTML preview
    let preview = HTMLPreview.create();
    preview.show(html);
    
    // ========================================
    // Process the HTML view
    // ========================================
    
    // Configuration variables for the processor script
    const inputVariableName = "selected_syntax";
    
    // Function to handle syntax data sent from the HTML preview
    function processSelectedSyntax(data) {
        if (!data) {
            console.log("No data received");
            return;
        }
        
        try {
            let actionData = JSON.parse(data);
            
            if (actionData && actionData.action === "switch") {
                // Switch to the selected syntax
                switchToSyntax(actionData.syntaxName);
            } else if (actionData && actionData.action === "cancel") {
                // User cancelled - do nothing
                console.log("Syntax selection cancelled");
            } else {
                console.log("Unknown action or invalid data format");
            }
        } catch (e) {
            console.log("Error processing data: " + e.message);
        }
    }
    
    // Function to switch to the selected syntax
    function switchToSyntax(syntaxName) {
        if (!syntaxName || syntaxName.trim().length === 0) {
            alert(`No ${displayNames.singular} specified`);
            return;
        }
        
        // Get all syntaxes and find the selected one
        const syntaxes = Syntax.getAll();
        const targetSyntax = syntaxes.find(s => s.name === syntaxName);
        const currentSyntax = draft.syntax;
        
        if (!targetSyntax) {
            alert(`Could not find syntax: ${syntaxName}`);
            return;
        }
        
        // Check if we're switching to the same syntax
        if (currentSyntax.name === targetSyntax.name) {
            app.displayInfoMessage(`Already using ${targetSyntax.name} syntax`);
            return;
        }
        
        // Apply the new syntax
        draft.syntax = targetSyntax;
        
        // Add JavaScript tag if switching to JavaScript syntax
        if (targetSyntax.name === "JavaScript") {
            draft.addTag("javascript");
        }
        
        // Update the draft
        draft.update();
        
        // Show success message
        app.displaySuccessMessage(`Switched to ${targetSyntax.name}`);
        
        console.log(`Switched from ${currentSyntax.name} to ${targetSyntax.name}`);
    }
    
    // Get the syntax data from the context using the configured variable name
    let selectedSyntaxData = context.previewValues[inputVariableName];
    
    if (selectedSyntaxData) {
        processSelectedSyntax(selectedSyntaxData);
    } else {
        console.log(`No ${displayNames.singular} data received`);
    }

Options

  • After Success Nothing
    Notification None
    Log Level None
Items available in the Drafts Directory are uploaded by community members. Use appropriate caution reviewing downloaded items before use.