Action

Mustache Prompt

Posted by @pdavisonreiber, Last update 4 months ago

Mustache Prompt works with templates written in the Mustache template format. It will automatically detect and prompt you for the values of all the variables in your template, and then create a new draft using the data you entered.

It supports type annotations for dates, booleans, and arrays of strings. Try running the action on a draft containing the example text below to see how it works. For more information, read my blog post on this action.

Here is a basic {{text_variable}}.

{{bool:#boolean_variable}}
Here is a paragraph that will only display if boolean_variable is true.
{{/boolean_variable}}

{{boolean_variable}}
Here is a paragraph that will only display if boolean_variable is false.
{{/boolean_variable}}

{{#comma_separated_strings}}
Here is a paragraph that is repeated for each item in the array comma_separated_strings. The current item is {{.}}.
{{/comma_separated_strings}}

Here is a date: {{date:date_variable}}.
Here is the same date three days later: {{date_variable+3d}}
Here is the same date one week ago: {{date_variable-1w}}
Here is the same date in seven months’ time: {{date_variable+7m}}
Here is another date that is offset without the original date being shown: {{date:another_date+3w}}

Steps

  • script

    function mustachePrompt(text, dateFormat) {
      dateFormat = (dateFormat === undefined) ? "%Y-%m-%d" : dateFormat
      
      const variableRegex = /{{(?:(date|bool):)?(#|^)?(\w+)\??([+|-]\d+[d|w|m])?}}/g
      const variableMatches = text.matchAll(variableRegex)
      
      variables = {}
      
      for (match of variableMatches) {
        let instance = new Object()
        instance.string = match[0]
        instance.type = match[1]
        instance.modifier = match[2]
        instance.name = match[3]
        instance.offset = match[4]
        
        if (!variables.hasOwnProperty(instance.name)) {
          let variable = new Object()
          variable.type = instance.type
          variable.modifier = instance.modifier
          variable.instances = [instance]
          variables[instance.name] = variable
        } else {
          variables[instance.name].instances.push(instance)
          if (!variables[instance.name].type) {
            variables[instance.name].type = instance.type
          }
          if (!variables[instance.name].modifier) {
            variables[instance.name].modifier = instance.modifier
          }
        }
      }
      
      //alert(JSON.stringify(variables))
      
      let p = Prompt.create()
      
      for (name in variables) {
        let variable = variables[name]
        
        if (!variable.type) {
          p.addTextField(name, name, "")
        } else if (variable.type == "date") {
          p.addDatePicker(name, name, new Date(), {mode: "date"})
        } else if (variable.type == "bool") {
          p.addSwitch(name, name, false)
        }
      }
      
      p.addButton("OK")
      data = {}
      
      let cancel = true
      if (Object.keys(variables).length !== 0) {
        cancel = !p.show()
      }
      
      if (!cancel) {
        for (key in p.fieldValues) {
          let fieldValue = p.fieldValues[key]
          if (fieldValue instanceof Date) {
            data[key] = strftime(fieldValue, dateFormat)
          } else if (typeof fieldValue == "string") {
            if (fieldValue.includes(",") && variables[key].modifier == "#") {
              data[key] = fieldValue.split(",").map(s => s.trim())
            } else {
              data[key] = fieldValue
            }
          } else {
            data[key] = fieldValue
          }
          
          for (instance of variables[key].instances) {
            if (!instance.type && !instance.offset) {
              continue
            }
            
            if (instance.offset) {
              text = text.replace(instance.string, instance.string.replace(instance.type + ":", "").replace(instance.offset, snakify(instance.offset)))
              data[key + snakify(instance.offset)] = offsetDate(fieldValue, instance.offset, dateFormat)
            } else {
              text = text.replace(instance.string, instance.string.replace(instance.type + ":", ""))
            }
          }
        }
        //alert(JSON.stringify(data, 2))
        
        let template = MustacheTemplate.createWithTemplate(text)
        let result = template.render(data)
        return result
        
      } else {
        context.cancel()
      }  
    }
    
    function offsetDate(date, string, dateFormat) {
      let d = new Date(date)
      let offsetRegex = /(\+|-)(\d+)(d|w|m)/
      let match = string.match(offsetRegex)
      let multiplier = (match[1] == "+" ? 1 : -1)
      
      if (match[3] == "d") {
        d.setDate(d.getDate() + multiplier * parseInt(match[2]))
      } else if (match[3] == "w") {
        d.setDate(d.getDate() + multiplier * 7 * parseInt(match[2]))
      } else if (match[3] == "m") {
        d = addMonths(d, multiplier * parseInt(match[2]))
      }
      
      return strftime(d, dateFormat)
    }
    
    function addMonths(date, months) {
      var d = date.getDate();
      date.setMonth(date.getMonth() + +months);
      if (date.getDate() != d) {
        date.setDate(0);
      }
      return date;
    }
    
    function snakify(offset) {
      return offset.replace("+", "_offset_forward_").replace("-", "_offset_backwards_")
    }
  • script

    let result = mustachePrompt(draft.content)
    
    if (result) {
    	draft.setTemplateTag("mustache", result)
    } else {
    	app.displayWarningMessage("Mustache Error: not a valid Mustache template")
    	context.fail()
    }
    
  • script

    let mustache = draft.getTemplateTag("mustache")
    
    if (mustache) {
    	let d = new Draft()
    	d.content = mustache
    	d.update()
    	editor.load(d)
    }
    

Options

  • After Success Default
    Notification Info
    Log Level Info
Items available in the Drafts Directory are uploaded by community members. Use appropriate caution reviewing downloaded items before use.