Action
Block level filter (MGCL/search)
UPDATES
about 3 years ago
Now supports inline hashtags. Also a few cosmetic adjustments.
about 3 years ago
Now supports inline hashtags. Also a few cosmetic adjustments.
about 3 years ago
Update to fix bug when starting search from another action (you can send text to this from another action or from a shortcut; prefix incoming text as “fromWikiLink:”) or from selected text. The first action step (preferences) now determines defaults for the way the action behaves, but can be overwritten by the first prompt if that setting is enabled.
about 3 years ago
Updates!
- Fixed display of modified/created label (https://forums.getdrafts.com/t/block-level-filtering-querying-aka-another-option-for-searching-for-things-in-drafts/11086/15?u=jsamlarose)
- Added option for sorting by modified or created date.
- If the search returns a draft with the search result in the title, the first non-empty line of the draft is now shown as context, rather than nothing (if you haven’t set your preferences to show the full content of the draft). These lines are clearly labelled to avoid any misinterpretation/confusion.
- Other small, cosmetic updates.
about 3 years ago
Now with added settings!
- The first action step allows you to control your preferences via the first action step (i.e. set it and forget it) or on the fly via the form. - If you manage your preferences in the action step you can set a few more options in the search scope (inbox, all, flagged, archive, trashed).
- If you choose to manage your preferences on the fly, the form displays two switches to control whether the full text of a draft is displayed, and whether you want to search in the “inbox” or “all” scope.
about 3 years ago
Minor fix
about 3 years ago
- added another option: you can now set the filter options to search within your inbox, archive, flagged drafts or even trash
- tidied up some sections of the code to make it easier to parse for people making their own adjustments (lots more could be done to tidy this up further!)
- trapped an error that could be triggered by running the filter on a line containing an empty wiki-link
about 3 years ago
Added an easy option to set whether or not a draft’s full text is displayed in instances where the term you’re searching for is found in that draft’s title. The option is determined by a variable in the first action step.
about 3 years ago
Better support for wiki-links/backlinks/(whatever you call them)! The initial prompt now offers options for search queries based on the current draft’s title— nods to @yvonnezed. Also updated the explanation of the kind of search this action does— nods to @cfritze.
about 3 years ago
Updated regex for detecting wiki-links in current line, and handling of [[s: … ]] syntax definition (as well as my own [[@: … ]] syntax).
about 3 years ago
If the search term is in the title, a search result will now show the content of the whole draft in the search item’s context field (description). Felt like a waste for the context to simply repeat the title, otherwise. If the search item is in the title AND some lines within the content, you’ll see those specific lines in the results as well. I might walk this back at some point in the future, but I’m feeling like “more is more” in this circumstance. We’ll see. I’ve also set the list to render HTML and markdown, which makes links a bit tidier, but might break the list in some circumstances. Again, might walk that back, but it’s working well for me for now…
about 3 years ago
Update to facilitate running this action from another action…
over 3 years ago
Minor update: initial prompt (if no text selected) gets focus when invoked.
over 3 years ago
Better handling of relative dates
over 3 years ago
Adjusted description.
Depends on Matt Gemmell’s MGCheckListPrompt action. This won’t work if you don’t have that installed (with apologies to Matt for my own less-than-elegant hackery around his brilliant work…;)
This action queries drafts for a search term…
- if there’s any text selected, that text will be used as a search.
- if no text is selected, you’ll be offered a prompt to enter a search term
- if no text is selected, and the cursor is in a line with a wiki-linked phrase, that wiki-linked phrase is offered as an option for the search— if there’s more than one, each will be offered as a separate option
Once your search term is set, hits are loaded into an interface (the Matt Gemmell Checklist Prompt), displaying the entirety of each line that the search term is found in for context.
Selecting any option from the list should highlight the search term in the draft.
NOTES:
- The action currently has one setting for easy adjustment: if you’d prefer that the list shows the full text of a draft if the term you’re searching for is found in the title of that draft, set the variable in the first action step to be true. Otherwise, leave it false.
- The search that this action performs is an “exact phrase” search, meaning that if you search for a phrase consisting of more than one word, you’ll only see results containing exactly that phrase, rather than results that contain each of the words in that phrase. To borrow an example from Drafts’ guide on search and filtering: “red parachute"will find a draft with the sentence “She used a red parachute when skydiving”, but not the draft “She used a red and blue parachute…” (…also, none of the other advanced query options detailed in that guide are enabled here!)
Steps
-
script
// PREFERENCES // META: Do you want to set preferences here or on the form itself? Doing it here keeps the form tidy and allows for a wider range of options in search scope. Doing it on the form allows you to change things on the fly. Quotation marks required. // Options: "here", "form". var preferences = "form" // Don't edit this next line... ;) // if (preferences == "here") { // If you've selected "form", any adjustments you make below will be ignored, except when starting a search from a wikilink or from selected text... // CONTEXT DISPLAY: How would you prefer the list to behave when the term you're searching for is found in the title of a draft? If it would be useful to see the whole draft in this instance, set this variable to true. Otherwise, if you have lots of long drafts, you may wish to keep this set as false, in which case the first non-empty line of the draft is returned. No quotation marks required. // Options: true, false var fullDraftsAsContext = false // SCOPE OF SEARCH: Where do you want your search to be run? Quotation marks required! // Options: "inbox", "archive", "flagged", "trash","all". var searchScope = "inbox" // SORT ORDER: How do you want your search results sorted? Quotation marks required! // Options: "modified", "created" var modOrCreated = "modified" // (Don't change this...) var mocProp = modOrCreated + "At" // }
-
includeAction
name MGCheckListPrompt Library
-
script
// INITIAL PROMPT TO DEFINE FILTER // the way the action is invoked determines whether this prompt is displayed... if (editor.getSelectedText().length > 1) { var searchStr = editor.getSelectedText() } else if (draft.content.startsWith("fromWikiLink:")) { var searchStr = draft.content.replace("fromWikiLink:","") } else { // no selection, and it wasn't invoked from a wikilink, so let's build the prompt... var searchStr = "" lSel = editor.getSelectedLineRange() sel = editor.getTextInRange(lSel[0],lSel[1]) var p = Prompt.create(); p.title = "Filter"; p.addTextField("searchTerm", "Search for", "", { "wantsFocus": true }); p.addLabel("label","ALTERNATIVELY, FILTER BY:", { "textSize": "caption" }); // options for backlinks... var linkRefs = ["(Linked references for this draft)","(Unlinked references for this draft)"]; // is there a wiki-link in the current line? Let's check, and if so, add to the list of potential filters... var buildArr = []; if (/\B#(\d*[A-Za-z_]+\w*)\b(?!;)/.test(sel)){ buildArr = [...new Set(buildArr.concat(sel.match(/\B#(\d*[A-Za-z_]+\w*)\b(?!;)/g)))]; } if (/\[\[[\#|\w|\s|@|\.|\?|»|\:|\(|\)|\/|\||\-|\+"]+\]\]/.test(sel)) { buildArr = [...new Set(buildArr.concat(sel.match(/\[\[[\#|\w|\s|@|\.|\?|»|\:|\(|\)|\/|\||\-|\+"]+\]\]/g)))]} var opts = buildArr.concat(linkRefs) p.addSelect("linkSel", "", opts, [], false); // show search settings if enabled in first action step... if (preferences == "form") { p.addSwitch("modOrCreated", "Sort by modified? (off = created)", false); p.addSwitch("context", "Show full draft if search term is in title?", false); p.addSwitch("searchScope", "Search all? (off = search inbox only)", false); } p.addButton("run"); var didSelect = p.show(); // set variables and settings in response to chosen/selected terms... if (p.buttonPressed == "run") { if (preferences == "form") { var fullDraftsAsContext = p.fieldValues["context"] var searchScope = p.fieldValues["searchScope"] ? "all" : "inbox" var modOrCreated = p.fieldValues["modOrCreated"] ? "modified" : "created" var mocProp = modOrCreated + "At" } if (p.fieldValues["searchTerm"].length > 0){ searchStr = p.fieldValues["searchTerm"] } else if (p.fieldValues["linkSel"].toString().startsWith("[[s:")||p.fieldValues["linkSel"].toString().startsWith("[[@:")){ // a syntax defined search should just be a search term... searchStr = p.fieldValues["linkSel"].toString().replace("[[s:","").replace("[[@:","").replace("]]","") } else if (p.fieldValues["linkSel"].toString().includes("(Linked references for this draft)")){ searchStr = p.fieldValues["linkSel"].toString() var queries = [ "[["+ draft.displayTitle +"]]", "[[# "+ draft.displayTitle +"]]", ] } else if (p.fieldValues["linkSel"].toString().includes("(Unlinked references for this draft)")){ var searchStr = draft.displayTitle; } else if (/\B#(\d*[A-Za-z_]+\w*)\b(?!;)/.test(p.fieldValues["linkSel"].toString())){ var searchStr = p.fieldValues["linkSel"].toString(); } else if (p.fieldValues["linkSel"].length > 0){ // searchStr = p.fieldValues["linkSel"].toString().replace("[[s:","[[") searchStr = p.fieldValues["linkSel"].toString() searchStr = searchStr.split('[[').pop().split(']]')[0].replace("# ","") // searchStr = "\"[[" + searchStr + "\" OR \"[[# " + searchStr + "\"" var queries = [ "[["+ searchStr +"]]", "[[# "+ searchStr +"]]", ]; } else { context.cancel() } } } searchStr == "" ? context.cancel() : searchStr
-
script
// FUNCTIONS // escape characters for regex function escapeRegex(string) { return string.replace(/[-\/\\^$*+?.()|[\]{}]/g, '\\$&'); } // relative time formatting: https://stackoverflow.com/a/37802747 function timeDifference(previous) { var prefix = "(" + modOrCreated + " " var suffix = ")" var output = "" var current = new Date() var msPerMinute = 60 * 1000; var msPerHour = msPerMinute * 60; var msPerDay = msPerHour * 24; var msPerMonth = msPerDay * 30; var msPerYear = msPerDay * 365; var elapsed = current - previous; if (elapsed < msPerMinute) { if (Math.round(elapsed/1000)==1) { output = 'a second ago'; } else { output = Math.round(elapsed/1000) + ' seconds ago'; } } else if (elapsed < msPerHour) { if (Math.round(elapsed/msPerMinute)==1) { output = 'a minute ago'; } else { output = Math.round(elapsed/msPerMinute) + ' minutes ago'; } } else if (elapsed < msPerDay ) { if (Math.round(elapsed/msPerHour)==1) { output = 'an hour ago'; } else { output = Math.round(elapsed/msPerHour) + ' hours ago'; } } else if (elapsed < msPerMonth) { if (Math.round(elapsed/msPerDay)==1) { output = 'yesterday'; } else { output = '~' + Math.round(elapsed/msPerDay) + ' days ago'; } } else if (elapsed < msPerYear) { if (Math.round(elapsed/msPerMonth)==1) { output = 'last month'; } else { output = '~' + Math.round(elapsed/msPerMonth) + ' months ago'; } } else { if (Math.round(elapsed/msPerYear)==1) { output = 'last year'; } else { output = '~' + Math.round(elapsed/msPerYear) + ' years ago'; } } return prefix + output + suffix } // https://stackoverflow.com/questions/3177836/how-to-format-time-since-xxx-e-g-4-minutes-ago-similar-to-stack-exchange-site /* function timeSince(date) { var seconds = Math.floor((new Date() - date) / 1000); var interval = seconds / 31536000; if (interval > 1) { return Math.floor(interval) + " years"; } interval = seconds / 2592000; if (interval > 1) { return Math.floor(interval) + " months"; } interval = seconds / 86400; if (interval > 1) { return Math.floor(interval) + " days"; } interval = seconds / 3600; if (interval > 1) { return Math.floor(interval) + " hours"; } interval = seconds / 60; if (interval > 1) { return Math.floor(interval) + " minutes"; } return Math.floor(seconds) + " seconds"; } */
-
script
// DISPLAY RESULTS AND NAVIGATE TO LOCATIONS WITHIN SELECTED DRAFTS var i = 0; var backlinks = [] if (queries) { // this, if we're searching for a linked reference let queriesDD = [...new Set(queries)]; var searchStr = "\"" + queriesDD.join("\" OR \"") + "\"" let drafts = Draft.query(searchStr, searchScope,[],[],modOrCreated,true) while(drafts[i]){ var q=0 while(queries[q]){ hits = drafts[i].lines.filter(element => element.toString().toUpperCase().includes(queries[q].toString().toUpperCase()) ) var n=0 while(hits[n]){ // descr = (typeof hits[n] == 'undefined') ? drafts[i].title : hits[n] let descr = hits[n] ? hits[n] : drafts[i].title let separator = drafts[i].tags.join() == "" ? "" : " ... " backlinks.push({ title: drafts[i].title, description: descr, // info: drafts[i].tags.join()+ timeDifference(drafts[i].createdAt), info: drafts[i].tags.join(", ") + separator + timeDifference(drafts[i][mocProp]), uuid: drafts[i].uuid, metadata: drafts[i].content, permalink: drafts[i].permalink }); n++; } q++; } i++; } const keys = ['title', 'description'], filtered = backlinks.filter( (s => o => (k => !s.has(k) && s.add(k)) (keys.map(k => o[k]).join('|')) ) (new Set) ); } else { // all other searches... let drafts = Draft.query(searchStr, searchScope,[],[],modOrCreated,true) while(drafts[i]){ hits = drafts[i].lines.filter(element => element.toString().toUpperCase().includes(searchStr.toString().toUpperCase()) ) var n=0 while(hits[n]){ if (fullDraftsAsContext == true) { descr = drafts[i].title == hits[n] ? drafts[i].processTemplate("[[body]]") : hits[n] } else { // descr = drafts[i].title == hits[n] ? "" : hits[n] descr = drafts[i].title == hits[n] ? draft.lines.filter((a) => a)[1] + "\n↳ _**first line of draft; search term in title**_" : hits[n] } let separator = drafts[i].tags.join() == "" ? "" : " ... " backlinks.push({ title: drafts[i].title, // description: hits[n], description: descr.replace(/\t/g,""), // info: drafts[i].tags.join()+ timeDifference(drafts[i][mocProp]), info: drafts[i].tags.join(", ") + separator + timeDifference(drafts[i][mocProp]), metadata: hits[n], uuid: drafts[i].uuid }); n++; } i++; } } // Create an MGCheckListPrompt. var prompt = new MGCheckListPrompt(); prompt.message = "Query: " + searchStr; prompt.addItems(backlinks) prompt.allowsTypeToFilter = true prompt.singleSelectionMode = true prompt.selectsImmediately = true prompt.processMarkdown = true prompt.includedContent = ` <style> .item-description { font-size: 90%; display: inline; } </style> ` // Show the prompt. var selectedItems = prompt.show(); // Report the result. if (prompt.didShow) { if (selectedItems != null) { source = Draft.find(backlinks[selectedItems[0]].uuid); editor.load(source) fullText = editor.getText() anchor = backlinks[selectedItems[0]].metadata pos = fullText.indexOf(anchor) // find position of selected line // pos = fullText.search("/"+ escapeRegex(anchor) +"/i") if (queries) {searchStr = queries[0].toString().substr(2).slice(0, -2)} pos = fullText.toUpperCase().indexOf(searchStr.toString().toUpperCase(), pos) // this to select the search term editor.setSelectedRange(pos,searchStr.toString().length) // anchor.length or searchStr.length, to select search term editor.activate(); } else { } } else { app.displayErrorMessage("Looks like your query returned no results."); context.cancel() }
Options
-
After Success Nothing Notification None Log Level None