Action

KPIs (you get what you practice)

Posted by @jsamlarose, Last update about 2 years ago - Unlisted

UPDATES

about 2 years ago

Bug fix and minor cosmetic adjustments

Requires Matt Gemmell’s MGCheckListPrompt action. https://actions.getdrafts.com/g/10X

A basic habit tracker. Searches your drafts for keywords and phrases surrounded by @(…) syntax and displays a log of the most recent instances of those keywords/phrases with a flag of how long ago they were last logged.

Steps

  • includeAction

    name
    MGCheckListPrompt Library
  • 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 = "Logged " 
    	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 || elapsed < msPerHour || elapsed < msPerDay) {
            output = 'within the last 24 hours';
        }
    
        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
    }
    
    // sort the array
    
    function compare(a, b) {
    
      const varA = a.forSort;
      const varB = b.forSort; 
      
      let comparison = 0;
      if (varA > varB) {
        comparison = -1;
      } else if (varA < varB) {
        comparison = 1;
      }
      return comparison;
    }
  • script

    var ignoreResultsTaggedWith = "filter output"
    var fullDraftsAsContext = false
    var searchScope = "inbox" // Options: "inbox", "archive", "flagged", "trash","all".
    var modOrCreated = "created" // Options: "modified", "created"
    var mocProp = modOrCreated + "At"
    var i = 0;
    var  instances = []
    var myTitle = ""
    var myInfo = ""
    var searchStr = "@("
    var searchReg = /@\(.+?\)/g
    var queries = ""
    
    	let drafts = Draft.query(searchStr, searchScope,[],[ignoreResultsTaggedWith],modOrCreated,false)
    	while(drafts[i]){
    
        		hits = drafts[i].lines.filter(element => element.match(searchReg) )
        		var n=0
        		while(hits[n]){
        			descr = 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(", ") + ", " : ""
    
    				myInfo = "**" + timeDifference(drafts[i][mocProp]) + "** " + inlineTags + drafts[i].tags.join(", ") // + separator 
    			
    			var j=0
    			mlHits = hits[n].match(searchReg)
    			while(mlHits[j]){
    			     instances.push({
    						title: mlHits[j].replace("@(","").replace(")",""),
    						description: descr.replace(/\t/g,""),
    						info: myInfo,
    						metadata: hits[n],
    						uuid: drafts[i].uuid,
    						forSort: drafts[i].createdAt.toISOString().substring(0,10)
            			});
            			j++
            			}
        		n++;
        		}
    	i++;
    	}
    
    var newArray = [];   
               
                let uniqueObject = {};         
                for (let i in  instances) {        // Loop for the array elements
                    objTitle =  instances[i]['title']; // Extract the title
                    uniqueObject[objTitle] =  instances[i] // Use the title as the index
                }
                  
                for (i in uniqueObject) {          // Loop to push unique object into array
                    newArray.push(uniqueObject[i]);
                }
    
    newArray.sort(compare);
    
    // Create an MGCheckListPrompt.
    var prompt = new MGCheckListPrompt();
    prompt.message = "Query: " + searchStr;
    prompt.addItems(newArray)
    prompt.allowsTypeToFilter = true
    prompt.singleSelectionMode = true
    prompt.selectsImmediately = true
    prompt.processMarkdown = true
    prompt.includedContent = `
    <style>
    .item-description {
    	font-size: 90%;
    	display: inline;
    }
    .item-info {
    	font-size: 70%;
    	display: inline;
    }
    </style>
    `
    
    // Show the prompt.
    var selectedItems = prompt.show();
    
    // Report the result.
    if (prompt.didShow) {
    	if (selectedItems != null) {
    		source = Draft.find(newArray[selectedItems[0]].uuid);
    		editor.load(source)
    		fullText = editor.getText()
    		anchor = newArray[selectedItems[0]].metadata
    		pos = fullText.indexOf(anchor)
    		// find position of selected line
    		// pos = fullText.search("/"+ escapeRegex(anchor) +"/i")
    		pos = fullText.indexOf(newArray[selectedItems[0]].title, pos) // this to select the search term
    		editor.setSelectedRange(pos,newArray[selectedItems[0]].title.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
Items available in the Drafts Directory are uploaded by community members. Use appropriate caution reviewing downloaded items before use.