Conditional Tag styles


#1

Is it possible to style tags based on the tag value?

E.g.,

@due(2014-09-01) has one style, but @due(2014-08-01) has another (e.g., red, because it is overdue)?

I guess this is really two questions:

  1. can a tag be styled differently based on value?
  2. can a tag value be evaluated, so that FT would know that @due(2014-08-01) is in the past, and should therefore have the other LESS rule applied to it?

Further, would it be possible to apply different LESS files to different FT files? Maybe a plugin that allowed one to choose from different themes, then some kind of persistent memory that remembered which theme was last used with any particular file?


#2

Yes, if you add the tag @hello(world) to your document then the PRE containing the line gets a new cm-hello attribute like this:

<pre cm-hello="world">

You can then target the line with that tag using attribute selectors; something like:

pre[cm-done=world] {
// style
}

See this plugin for lots of other individual tag styling techniques.

Jesse


Adding a note to a task, scripting Taskpaper
#3

So I don’t know really anything about javascript and less, but I’m trying to learn. Would something like the solution described in this stack exchange discussion work with FoldingText and its LESS implementation? Hoping to discover any “gotchas” before I start tinkering with it trial-and-error style.


#4

I don’t think that approach would work well. It would work to highlight tags that were overdue when your document first loaded. But once LESS gets compiled at appellation launch it’s static and so the “current time” would never get updated. Things that became due after you opened your documented wouldn’t show up unless your closed and reopened your document.

For time based highlighting (as opposed to highlighting that only depends on document content) you’ll need to write a plugin. It would in general “tick” and figure out which nodes are overdue and add the styles rules as appropriate.

The code to write this sort of plugin isn’t to complex, but touches a number of different parts of FoldingText, so would probably be hard to write as a first project. Give me a little bit and I’ll see if I can create a quick demo version.


#5

Thanks Jesse! I’m sure there’s other folks interested in this kind of thing. Even if the plugin had to run in order to enact the color changes, that would be fine with me. I’m not looking for minute-to-minute updates–daily is fine for my purposes.


#6

Colour me interested. I seem to remember asking about something similar elsewhere. Glad to hear it’s possible.


#7

This solution isn’t very efficient, but illustrates a basic approach:

define(function (require, exports, module) {
  'use strict';

  var Extensions = require('ft/core/extensions').Extensions,
    NodeSet = require('ft/core/nodeset').NodeSet,
    DateUtils = require('ft/util/date'),
    dueNodes = new NodeSet();

  /*jshint multistr:true */
  // undocumented extension point… just allows inserting some Less styles into app
  // another approach would have been to just add this rule to your user.less file.
  Extensions.add('com.foldingtext.editor.styles',
    '.cm-overdue {\
      background: red;\
    }'
  );
  /*jshint multistr:false */

  Extensions.addRenderNodeElementStyles(function (editor, node, elementRenderer) {
    if (dueNodes.containsNode(node)) {
      elementRenderer.addWrapClass('cm-overdue');
    }
  });

  // another undocumented extension point (I've added documentation for 
  // the next release) this extension point allows you to get a callback on every
  // clock tick, which happens once per second. This is better then setting up 
  // your own timer, because it means everything that updates on a clock will
  // update at same time, more efficient and more aesthetically pleasing this way!
  Extensions.add('com.foldingtext.editor.clockTick', function (editor, clockDate) {
    var nodePath = '//@due < "' + clockDate.format('isoDate') + '"',
      newDueNodes = NodeSet.setFromArray(editor.tree().evaluateNodePath(nodePath)),
      removedFromDue = NodeSet.minusSets(dueNodes, newDueNodes),
      addedToDue = NodeSet.minusSets(newDueNodes, dueNodes);

    removedFromDue.forEachNodeInSet(function(each) {
      editor.setNeedsRender(each);
    });

    addedToDue.forEachNodeInSet(function(each) {
      editor.setNeedsRender(each);
    });

    dueNodes = newDueNodes;    
  });
});

The key points are:

  1. Override com.foldingtext.editor.clockTick extension point to get “ticks” every 1 second.
  2. Do some calculation in each tick, and if a nodes visual representation should change (i.e. it becomes overdue, or is no longer overdue) then schedule a new editor.renderNode() for each such node.
  3. Override the Extensions.addRenderNodeElementStyles extension point to add an cm-overdue wrapper class for nodes that are overdue.

That’s the basic strategy, the parts that need to be improved are all in efficiency and feedback. To make changes update immediately when you are typing (instead of waiting for a second) you would need to listen for tree changed events and redo your ‘overdue’ check there. Also instead evaluating the node path over the entire tree on each step it would be more efficient to just maintain a collection of all nodes that have a due tag… and just compare agains them in the check phase… But anyway I think this approach should work fine for the moment.


#8

Yep–works for me. Thanks Jesse!


#9

Apologies for reviving an old thread, but this plugin seems to be broken by recent updates.

DateUtils = require('ft/util/date'),
produces the error: 'Failed to load resource: The requested URL was not found on this server.'
I’d managed to get the plugin up and running by commenting this line out, but since the last FT update, I’m now getting an error on:

clockDate.format
[Error] TypeError: clockDate.format is not a function. (In ‘clockDate.format(‘isoDate’)’, ‘clockDate.format’ is undefined)

Can anyone please suggest an update? Or if anyone has an alternative solution to highlighting due/overdue items…