Action
Block level filter to new draft
UPDATES
over 2 years ago
Added “ignoreResultsTaggedWith” preference to suppress results from drafts produced by this action (tagged with “filter output” by default).
Branched off from the original Block level filter to write filter results to a new draft. In response to this conversation.
Could do with some polish…
- The draft that’s produced by this action will skew the results. It’d be better to output to a fixed draft every time. That would allow users to work with filter results as a slide over or split screen panel on iOS. Alternatively, we’d need to hide the draft(s) produced by this action from the filter for future searches… Fixed this with a preferences update…
- As this action actually creates a draft (rather than presenting results in a menu), we’ll need a separate action to target the exact location of any specific block of context within its source draft.
- Including the UUID probably isn’t necessary. Edge case: might be useful if a title is updated before the filter output is refreshed?
- Ultimately, this could be just be folded into the original action (as an option from the initial prompt)
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" // The next setting is used to suppress results from Drafts with a specified tag. Currently works best if you focus on one tag only, although you can enter a list of tags separated by commas if you choose... var ignoreResultsTaggedWith = "filter output" // 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:","") // 20211026: if tag includes an underscore, search both as tag AND key phrase if (searchStr.includes("_")){ var queries = [ searchStr, searchStr.replaceAll("_"," ").replace("#",""), ]; } } 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, [],[ignoreResultsTaggedWith],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() == "" ? "" : " ... " let regex = /(\B#(\d*[A-Za-z_]+\w*)\b(?!;))/g let inlineTags = regex.test(drafts[i].content) ? drafts[i].content.match(regex).filter((v, i, a) => a.indexOf(v) === i).join(", ") + " ... " : "" backlinks.push({ title: drafts[i].title, description: descr.replace(/\t/g,""), // info: drafts[i].tags.join()+ timeDifference(drafts[i].createdAt), info: inlineTags + 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,[],[ignoreResultsTaggedWith],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() == "" ? "" : " ... " let regex = /(\B#(\d*[A-Za-z_]+\w*)\b(?!;))/g let inlineTags = regex.test(drafts[i].content) ? drafts[i].content.match(regex).filter((v, i, a) => a.indexOf(v) === i).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: inlineTags + drafts[i].tags.join(", ") + separator + timeDifference(drafts[i][mocProp]), metadata: hits[n], uuid: drafts[i].uuid }); n++; } i++; } } let output = "# FILTER OUTPUT RESULTS:" + searchStr + "\n" backlinks.forEach(function(item){ if ((item.description === undefined) || (item.description == null) || (item.description == "undefined")) { output += "\n[[" + item.title + "]]\n+ " + item.info + "\n[[u:" + item.uuid + "]]\n" } else { output += "\n[[" + item.title + "]]\n↳ " + item.description.replace("undefined\n↳","") + "\n\t+ " + item.info + "\n\t[[u:" + item.uuid + "]]\n" } }) let filterD = new Draft() filterD.content = output filterD.addTag(ignoreResultsTaggedWith) filterD.update() editor.load(filterD) // 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