Action
special characters
Posted by @jsamlarose,
Last update
4 days ago
Menu to insert special characters. The source list is in the first action step— update for your own purposes…
Steps
-
script
// FUNCTIONS // escape characters for regex function escapeRegex(string) { return string.replace(/[-\/\\^$*+?.()|[\]{}]/g, '\\$&'); } var currentLineText = editor.getTextInRange(editor.getSelectedLineRange()[0], editor.getSelectedLineRange()[1]); // CONFIGURATION: Add new special characters here and reorder as needed // Just cut/paste entries to reorder them - no IDs needed! const specialCharacters = [ { name: 'Arrow (Long)', label: 'arrow_long', character: ' --» ' }, { name: 'Arrow (Short)', label: 'arrow_short', character: '»' }, { name: 'Apostrophe (Typographic)', label: 'apostrophe', character: "’" }, { name: 'Back tick', label: 'back tick', character: "`" }, { name: 'Tilde', label: 'tilde', character: "~" }, { name: 'Ellipsis', label: 'ellipsis', character: '…' }, { name: 'Bullet Point', label: 'bullet', character: '•' }, { name: 'Degree Symbol', label: 'degree', character: '°' }, { name: 'Copyright Symbol', label: 'copyright', character: '©' }, { name: 'Registered Trademark', label: 'registered', character: '®' }, { name: 'Trademark', label: 'trademark', character: '™' } // Add new characters anywhere in this array - just insert them where you want them to appear! ]; // Convert specialCharacters array to JSON string for embedding in HTML let charactersArray = JSON.stringify(specialCharacters); let currentLineTextJSON = JSON.stringify(currentLineText); // HTML and JavaScript for the web preview 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: 1em; background-color: #1e1e1e; color: #e0e0e0; } #searchBar { width: 100%; padding: 0.5em; margin-bottom: 1em; background-color: #2d2d2d; color: #e0e0e0; border: 1px solid #3d3d3d; border-radius: 4px; } ul { list-style: none; padding-left: 0; } li { padding: 0.5em 0; border-bottom: 1px solid #3d3d3d; cursor: pointer; display: flex; align-items: center; } li:hover { background-color: #2d2d2d; } li.selected { background-color: #444; } li.highlight { background-color: #555; } .character-preview { font-weight: bold; font-size: 1.2em; margin-right: 15px; min-width: 40px; text-align: center; background-color: #2d2d2d; padding: 5px; border-radius: 3px; } .character-info { flex: 1; } .character-name { font-weight: bold; margin-bottom: 2px; } .character-label { color: #999; font-size: 0.9em; } .h1 { border-left: 3px solid #ff6600; padding-left: 10px; } .h2 { border-left: 3px solid #ff9900; padding-left: 10px; } .h3 { border-left: 3px solid #ffcc00; padding-left: 10px; } .h4 { border-left: 3px solid #99cc33; padding-left: 10px; } .h5 { border-left: 3px solid #66cc99; padding-left: 10px; } .h6 { border-left: 3px solid #9999cc; padding-left: 10px; } </style> </head> <body> <input type="text" id="searchBar" placeholder="Search special characters..."> <ul id="itemList"></ul> <script> let itemList = document.getElementById('itemList'); let searchBar = document.getElementById('searchBar'); let characters = ${charactersArray}; let selectedCharacter = null; let currentIndex = 0; function createListItems() { itemList.innerHTML = ''; characters.forEach((char, index) => { let li = document.createElement('li'); // Create character preview span let previewSpan = document.createElement('span'); previewSpan.className = 'character-preview'; previewSpan.textContent = char.character; // Create info container let infoDiv = document.createElement('div'); infoDiv.className = 'character-info'; // Create name span let nameSpan = document.createElement('div'); nameSpan.className = 'character-name'; nameSpan.textContent = char.name; // Create label span let labelSpan = document.createElement('div'); labelSpan.className = 'character-label'; labelSpan.textContent = char.label; // Add elements to info div infoDiv.appendChild(nameSpan); infoDiv.appendChild(labelSpan); // Add both spans to the list item li.appendChild(previewSpan); li.appendChild(infoDiv); li.dataset.index = index; li.dataset.label = char.label; li.dataset.character = char.character; li.classList.add('h' + ((index % 6) + 1)); li.onclick = function() { Array.from(itemList.children).forEach(item => item.classList.remove('selected')); this.classList.add('selected'); selectedCharacter = { label: this.dataset.label, character: this.dataset.character }; submitSelection(); }; itemList.appendChild(li); }); if (itemList.children.length > 0) { itemList.children[0].classList.add('selected'); let firstItem = itemList.children[0]; selectedCharacter = { label: firstItem.dataset.label, character: firstItem.dataset.character }; } } function filterItems() { let searchText = searchBar.value.toLowerCase(); let visibleItems = 0; Array.from(itemList.children).forEach((item) => { let characterName = item.querySelector('.character-name').textContent.toLowerCase(); let characterLabel = item.querySelector('.character-label').textContent.toLowerCase(); let shouldShow = characterName.includes(searchText) || characterLabel.includes(searchText); item.style.display = shouldShow ? '' : 'none'; if (shouldShow) { visibleItems++; } }); currentIndex = 0; if (visibleItems > 0) { Array.from(itemList.children).forEach((item) => item.classList.remove('selected')); let firstVisibleItem = Array.from(itemList.children).find(item => item.style.display !== 'none'); if (firstVisibleItem) { firstVisibleItem.classList.add('selected'); selectedCharacter = { label: firstVisibleItem.dataset.label, character: firstVisibleItem.dataset.character }; } } return visibleItems; } function selectItem(index) { Array.from(itemList.children).forEach(item => { item.classList.remove('highlight'); item.classList.remove('selected'); }); let visibleItems = Array.from(itemList.children).filter(item => item.style.display !== 'none'); if (visibleItems.length > 0) { let item = visibleItems[index]; item.classList.add('highlight'); item.classList.add('selected'); item.scrollIntoView({ behavior: 'smooth', block: 'nearest' }); selectedCharacter = { label: item.dataset.label, character: item.dataset.character }; currentIndex = index; } } function submitSelection() { if (selectedCharacter) { // Send the selected character Drafts.send("special_character", selectedCharacter.character); Drafts.continue(); } else { alert("Please select a character."); } } document.addEventListener('keydown', function(event) { let visibleItems = Array.from(itemList.children).filter(item => item.style.display !== 'none'); if (event.key === 'ArrowDown') { currentIndex = (currentIndex + 1) % visibleItems.length; selectItem(currentIndex); event.preventDefault(); } else if (event.key === 'ArrowUp') { currentIndex = (currentIndex - 1 + visibleItems.length) % visibleItems.length; selectItem(currentIndex); event.preventDefault(); } else if (event.key === 'ArrowRight' || event.key === 'ArrowLeft') { searchBar.focus(); event.preventDefault(); } else if (event.key === 'Enter') { submitSelection(); event.preventDefault(); } }); createListItems(); searchBar.addEventListener('input', filterItems); window.onload = function() { searchBar.focus(); }; </script> </body> </html> `; let preview = HTMLPreview.create(); if (preview.show(html)) { } else { context.cancel(); }
-
script
// Get the formatted datetime value passed from the previous action let special_character = context.previewValues["special_character"]; // If we received a value, insert it at the current cursor position if (special_character !== undefined) { // Insert the formatted date/time at the current cursor position editor.setSelectedText(special_character + " "); // Get the current selection range end position let currentPosition = editor.getSelectedRange()[0] + editor.getSelectedRange()[1]; // Set the cursor position to be after the inserted text plus one space editor.setSelectedRange(currentPosition, 0); // Activate the editor to ensure focus returns to it editor.activate(); } else { console.log("No special character was received"); }
Options
-
After Success Default Notification Error Log Level Info
Items available in the Drafts Directory are uploaded by community members. Use appropriate caution reviewing downloaded items before use.