Action
Autocomplete Cross-Link
Autocomplete [[cross-links]]
by Matt Gemmell - @mattgemmell - https://mattgemmell.com/
Type “[[” then at least 2 characters (by default) before invoking this action.
Requires Drafts 20.1 or later.
Steps
-
script
// Autocomplete [[cross-links]] // by Matt Gemmell - @mattgemmell - https://mattgemmell.com/ // // Type "[[" then at least 2 characters (by default) before invoking this action. // Requires Drafts 20.1 or later. // =========================== var chooseBestMatch = false; // If false, user is asked to choose between multiple matching titles. var minimumQueryLength = 2; // Must type at least this number of characters after the "[[" opening delimiter or we won't search. var excludeCurrentDraft = true; // If true, the draft currently being edited will be excluded as a candidate for autocompletion of cross-links. This avoids the situation where autocompleting a cross-link within the first line (i.e. the title) of a draft will always include the draft itself in the list of suggestions. // =========================== function minRange(theRange) { // Returns index of start of theRange. return theRange[0]; } function maxRange(theRange) { // Returns index immediately AFTER end of theRange. return theRange[0] + theRange[1]; } function editorTextInRange(editor, range) { // Returns editor's text in the given range. return editor.getTextInRange(range[0], range[1]); } function getTitle(theDraft) { return theDraft.displayTitle; } function getUUID(theDraft) { return theDraft.uuid; } // Grab entire line(s) of text which contain the selection. var selectedLineRange = editor.getSelectedLineRange(); var selectedLines = editorTextInRange(editor, selectedLineRange); // If selection spans more than one line, abort. var newLineIndex = selectedLines.indexOf("\n"); if (newLineIndex !== -1 && newLineIndex!== selectedLines.length - 1) { // i.e. Just a trailing newline. app.displayInfoMessage("Doesn't work with multi-line selections."); } else { var selRange = editor.getSelectedRange(); selRange[0] -= selectedLineRange[0]; // Normalise to be line-relative. var partialTitle = null; var partialTitleRange = null; var openingDelimiter = "[["; if (selRange[1] > 0) { // There's a selection. Assume it's the partial title to be autocompleted. partialTitle = editorTextInRange(selRange); partialTitleRange = selRange; if (partialTitle.indexOf(openingDelimiter) == 0) { // Trim delimiter. partialTitle = partialTitle.slice(openingDelimiter.length); partialTitleRange[0] += openingDelimiter.length; partialTitleRange[1] -= openingDelimiter.length; } } else { // No selection. Try to find opening link-delimiters "[[" to left of insertion point. // Limit the amount of text we're searching for the closest match to a reasonable length. var searchRadius = 250; // Num chars around selection/insertion to search. var searchRange = [0, selectedLines.length]; // Entire line. if (minRange(selRange) > searchRadius) { // Move beginning of searchRange to within searchRadius of selRange's start. searchRange[0] = minRange(selRange) - searchRadius; searchRange[1] -= searchRange[0]; } // Move end of searchRange to beginning of selRange (i.e. just behind the insertion point). var delta = maxRange(searchRange) - minRange(selRange); searchRange[1] -= delta; var searchText = selectedLines.substring(minRange(searchRange), maxRange(searchRange)); // Find rightmost occurrence of openingDelimiter within searchText. var foundIndex = searchText.lastIndexOf(openingDelimiter); if (foundIndex != -1) { partialTitle = searchText.slice(foundIndex + openingDelimiter.length); partialTitleRange = selRange; partialTitleRange[0] -= partialTitle.length; partialTitleRange[1] = partialTitle.length; } } if (partialTitle != null) { // Check that partialTitle is long enough to be worth searching for. if (partialTitle.length < minimumQueryLength) { app.displayInfoMessage("Please type at least " + minimumQueryLength + " characters for autocompletion."); } else { // Search for matching drafts. partialTitle = partialTitle.trim(); var chosenDraft = null; var foundDrafts = Draft.queryByTitle(partialTitle); // Eliminate current draft from results. if (excludeCurrentDraft && foundDrafts.length > 0) { var foundUUIDs = foundDrafts.map(getUUID); var index = foundUUIDs.indexOf(draft.uuid); if (index > -1) { foundDrafts.splice(index, 1); // modifies in place } } if (foundDrafts.length > 0) { // We found at least one match. if (foundDrafts.length == 1 || chooseBestMatch) { // Use the first/only match. chosenDraft = foundDrafts[0]; } else { // Choose one of the matching drafts. var draftTitles = foundDrafts.map(getTitle); var prompt = Prompt.create(); prompt.isCancellable = true; prompt.title = "Link to which draft?"; prompt.addButton("Insert Link"); prompt.addSelect("select", "", draftTitles, [draftTitles[0]], false); var didSelect = prompt.show(); var chosenDraftTitle = prompt.fieldValues["select"][0]; if (didSelect) { var draftIndex = draftTitles.indexOf(chosenDraftTitle); chosenDraft = foundDrafts[draftIndex]; } else { if (!editor.isActive) { editor.activate(); } } } if (chosenDraft != null) { // We don't insert the opening delimiter because it's already there. var linkToInsert = chosenDraft.displayTitle + "]]"; // Insert link, either replacing selection or relevant text before insertion point. partialTitleRange[0] += selectedLineRange[0]; // make document-relative editor.setSelectedRange(partialTitleRange[0], partialTitleRange[1]); editor.setSelectedText(linkToInsert); // avoid the setTextInRange() scrolling bug //editor.setTextInRange(partialTitleRange[0], partialTitleRange[1], linkToInsert); editor.setSelectedRange(minRange(partialTitleRange) + linkToInsert.length, 0); editor.save(); draft.update(); if (!editor.isActive) { editor.activate(); } } } else { app.displayInfoMessage("No matching drafts found."); } } } else { app.displayInfoMessage("Couldn't find start of link ( [[ )."); } }
Options
-
After Success Nothing Notification Info Log Level Info
Items available in the Drafts Directory are uploaded by community members. Use appropriate caution reviewing downloaded items before use.