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.