Web display of Taskpaper file
A simple text format is a joy forever. One of the nice things about a well-designed simple format like Taskpaper is that just about anything and anyone can read it. It makes it very easy to reposition the data in that format to somewhere else, like the web, using a scripting language like PHP.
Depending on what kind of notes you put in your task lists, you may wish to put a .htaccess file (or the equivalent if you aren’t using Apache) in the same directory as this web page, to limit access.
The web page
The web page itself is fairly simple. It has two sections: the section that displays tasks, and a footer that displays tags to make it easy to switch from tag to tag.
The section that displays tasks is also simple. There are only three kinds of lines in a Taskpaper file: a project name, a task, and a note. Notes belong to the task (or project, but I don’t implement that—all of my notes belong to a task) above them.
If the current page is a search for a project, this only displays tasks and notes within that project. If the current page is a search for tasks belonging to a tag, this only displays tasks tagged with that tag, and notes belonging to them. Project names are only displayed if they have a visible task on this page.
[toggle code]
-
<html>
-
<head>
- <title>My Tasks</title>
- <LINK REL="StyleSheet" HREF="tasks.css" TYPE="text/css" MEDIA="all" />
- </head>
-
<body>
- <h1>My Tasks</h1>
-
<?
- $tasks = file('/Users/me/Documents/Projects/Tasks.taskpaper');
-
FOREACH ($tasks as $task):
- $task = trim($task);
- $task = htmlspecialchars($task);
- //projects aren't displayed unless they have visible tasks
- //so just keep the project name until a task is displayed
-
IF (substr($task, -1) == ':'):
- $project = substr($task, 0, -1);
- //show tasks or notes only if all projects are displayed
- //or they are part of the displayed project
-
ELSEIF (!isset($_GET['project']) || $_GET['project'] == $project):
-
IF (substr($task, 0, 2) == '- '):
- $task = substr($task, 2);
- list($task, $classes, $tags) = parseTask($task);
-
IF (!isset($_GET['tag']) || in_array($_GET['tag'], $tags)):
- displayTask($task, $classes);
- ENDIF;
-
ELSEIF (!isset($_GET['tag']) || in_array($_GET['tag'], $tags)):
- displayNote($task, $classes);
- ENDIF;
-
IF (substr($task, 0, 2) == '- '):
- ENDIF;
- ENDFOREACH;
- closeNotes();
- closeTasks();
- //footer: display all tags in project or file
- //if we're focussed on one tag or project, include a "view all" link
-
IF (isset($_GET['tag']) || isset($_GET['project'])):
- $alltags[] = array('count'=>0, 'link'=>'<a href="./">View all tasks</a>');
- ENDIF;
-
IF ($alltags):
- uasort($alltags, 'sorttags');
- echo "\n\n", '<ul class="tags">';
-
FOREACH ($alltags as $tag):
- echo '<li>', $tag['link'], "</li>\n";
- ENDFOREACH;
- echo '</ul>';
- ENDIF;
- ?>
- </body>
-
<head>
- </html>
The functions
The hardest part about the Taskpaper format is that I want the tasks in each project to be part of a list and I want the notes for each task to be part of the same “pre” tag. Because tasks and notes span multiple lines, I need to keep track of whether or not I’m currently displaying a task or note. When displaying a new project, the old task list needs to be closed, and when displaying a new project or tag, any notes need to be closed. That’s what the various open and close functions are. I don’t use subtasks, so this script doesn’t support them.
Making URLs clickable inside tasks and notes is theoretically difficult, but I just used Nico Oelgart’s Make URLs clickable function.
The bulk of the work is handled by the parseTask() function. It separates each task into a task and a list of tags, and then attaches the tag list back to the task, but linked as a search to those tasks. It also returns a list of all tags, so that the main part of the script can check to see if this task is tagged with the desired tag.
[toggle code]
-
<?
- //parse a task into task, classes, and list of tags
-
function parseTask($line) {
- global $alltags;
- $taglist = explode(' @', $line);
- $task = array_shift($taglist);
- $task = parseURLs($task);
- $tags = array();
- $cleantaglist = array();
- $classes = "";
-
IF ($taglist):
-
FOREACH ($taglist as $tag):
- $rawtag = " @$tag";
- $tag = preg_replace('/\(.*/', '', $tag);
-
IF ($tag == 'project'):
- $tags[] = $rawtag;
-
ELSE:
- $encodedtag = urlencode($tag);
- $taglink = "<a class=\"tag\" href=\"./?tag=$encodedtag\">$rawtag</a>";
- $tags[] = $taglink;
-
IF ($alltags[$tag]):
- $alltags[$tag]['count']++;
-
ELSE:
- $alltags[$tag] = array('count'=>1, 'link'=>$taglink);
- ENDIF;
- $cleantaglist[] = $tag;
- ENDIF;
- ENDFOREACH;
- $task .= implode("", $tags);
-
IF ($cleantaglist):
- $classes = implode(" ", $cleantaglist);
- $classes = " class=\"$classes\"";
- ENDIF;
-
FOREACH ($taglist as $tag):
- ENDIF;
- return array($task, $classes, $cleantaglist);
- }
- // keep track of whether we're currently in a note or task
-
function openNotes($classes) {
- global $inNote;
-
IF (!$inNote):
- $inNote = true;
- echo "<pre$classes>";
-
ELSE:
- echo "\n";
- ENDIF;
- }
-
function closeNotes() {
- global $inNote;
-
IF ($inNote):
- echo "</pre>\n";
- $inNote = false;
- ENDIF;
- }
-
function displayNote($note, $classes) {
- openNotes($classes);
- echo parseURLs($note);
- }
-
function openTasks() {
- global $inTask;
-
IF (!$inTask):
- $inTask = true;
- echo "<ol>\n";
- ENDIF;
- }
-
function closeTasks() {
- global $inTask;
-
IF ($inTask):
- echo "</ol>\n";
- $inTask = false;
- ENDIF;
- }
-
function displayTask($task, $classes) {
- global $project, $currentproject;
- closeNotes();
-
IF ($project != $currentproject):
- closeTasks();
- $encodedproject = urlencode($project);
- echo "<h2><a href=\"./?project=$encodedproject\">$project</a></h2>\n";
- $currentproject = $project;
- ENDIF;
- openTasks();
- echo "<li$classes>$task</li>\n";
- }
- //link any URLs in notes or tasks
- //from http://www.bytemycode.com/snippets/snippet/602/
-
function parseURLs($text, $maxurl_len = 70, $target = '_self') {
-
IF (preg_match_all('/((ht|f)tps?:\/\/([\w\.]+\.)?[\w-]+(\.[a-zA-Z]{2,4})?[^\s\r\n\(\)"\'<>\,\!]+)/si', $text, $urls)):
- $offset1 = ceil(0.65 * $maxurl_len) - 2;
- $offset2 = ceil(0.30 * $maxurl_len) - 1;
-
FOREACH (array_unique($urls[1]) AS $url):
-
IF ($maxurl_len AND strlen($url) > $maxurl_len):
- $urltext = substr($url, 0, $offset1) . '...' . substr($url, -$offset2);
-
ELSE:
- $urltext = $url;
- ENDIF;
- $text = str_replace($url, '<a class="embedded" href="'. $url .'" target="'. $target .'" title="'. $url .'">'. $urltext .'</a>', $text);
-
IF ($maxurl_len AND strlen($url) > $maxurl_len):
- ENDFOREACH;
- ENDIF;
- return $text;
-
IF (preg_match_all('/((ht|f)tps?:\/\/([\w\.]+\.)?[\w-]+(\.[a-zA-Z]{2,4})?[^\s\r\n\(\)"\'<>\,\!]+)/si', $text, $urls)):
- }
- //sort tags by frequency
-
function sorttags($a, $b) {
-
IF ($a['count']<$b['count']):
- return true;
-
ELSEIF ($a['count']>$b['count']):
- return false;
-
ELSE:
- return 0;
- ENDIF;
-
IF ($a['count']<$b['count']):
- }
- ?>
The stylesheet
There are only a few elements to style. If you use a lot of tags, you can go wild giving each tag a different style. Just remember that the last style found is the one that takes precedence if a task is tagged with more than one tag.
[toggle code]
-
h1 {
- background-color: #12B355;
- color: White;
- text-align: center;
- }
-
h2 {
- border-color: #12B355;
- border-style: solid;
- border-width: .1em;
- border-left-style: none;
- border-right-style: none;
- }
- /* links should be the same color as the task they're part of */
-
a {
- color: inherit;
- }
- /* links embedded in tasks or notes should stand out from tag or project links */
-
a.embedded {
- text-decoration: underline;
- }
- /* by default, tasks are grey */
-
li, pre {
- color: #AAAAAA;
- }
- /* top tasks have a special color */
-
.top {
- color: #12B31F;
- }
- /* and when they're done, tasks have a special color and a line through them */
-
.done {
- text-decoration: line-through;
- color: #1AFF79;
- }
- /* the list of possible tags */
-
ul.tags {
- text-align: center;
- list-style-type: none;
- margin-top: 2em;
- border: .1em solid #12B355;
- padding: 0;
- }
-
ul.tags li {
- display: inline;
- }
-
ul.tags li+li:before {
- content: " | ";
- }
-
ul.tags a {
- color: #12B355;
- }
Enhancements
If you need something more complex, look at Taskpaper.web. If you want this script to be more complex, you might consider an in-memory SQLite database. If you specify “:memory:” as the “filename” for your SQLite database, the database will be entirely in memory; this will give you the power of SQL to manipulate your tasks, notes, and projects. PHP 5 has SQLite support built-in (as does Python 2.5).
- PHP - Make URLs clickable (And short down)
- “This function makes URLs in a given text clickable, and shorts down the link text if its length is over the specified one. (Like vBulletin, etc… does)”
- Taskpaper
- At first glance it doesn’t seem like you’d want to pay $20 for a task manager this simple—but a lot of people are. Sometimes, simplicity is worth paying for.
- Taskpaper.web
- “A web implementation of TaskPaper. Primarily designed as a lightweight viewer for documents you have created in TaskPaper. It also provides rudimentary editing facilities for quick edits on the move.”
More HTML
- Nisus “clean HTML” macro
- The Nisus macro language is Perl; this means we can use all of Perl’s strengths as a text filter scripting language in Nisus.
- Flash on iPhone not in anybody’s interest
- Flash on iPhone is not in the interest of people who buy iPhones. The only people who really want it are poor web designers who can’t get out of 1992.
- ELinks text-only web browser
- If you need a text browsers on Mac OS X, the ELinks browser compiles out of the box.
- iPhone development another FairPlay squeeze play?
- Why no iPhone-only applications? Is it short-sightedness on Apple’s part, or are they trying to encourage something big?
- Cascading style sheets and HTML
- You can use style sheets to simplify your web pages, making them readable across a wide variety of browsers and situations, without sacrificing presentation quality.
- Six more pages with the topic HTML, and other related pages
More PHP
- Auto-closing HTML tags in comments
- One of the biggest problems on blogs is that comments often get stuck with unclosed italics, bold, or links. You can automatically close them by transforming the HTML snippet into an XML document.
- Stable sorting of numerically indexed arrays in PHP
- From PHP 4.1, sorted arrays are no longer “stable”. That is, if they are resorted and two items are equal values, they no longer can be expected to maintain their order vis-a-vis each other.
- Override the Host: header when using PHP’s readfile
- It is possible to specify HTTP headers when using URLs with PHP’s file-oriented functions such as readfile.
- Add nodes to SimpleXMLElement
- If you want to add child nodes in PHP’s SimpleXML, the correct way to do it is to add the node first, then create it.
- New PHP Tutorial
- I’ve just uploaded a new version of my PHP tutorial, with a better MySQL section.
- Two more pages with the topic PHP, and other related pages
More TaskPaper
- GeekTool, TaskPaper, and XML
- A script to convert a TaskPaper file to XML so as to filter it for specific tags and display the results on the Desktop.
- SilverService and Taskpaper
- SilverService is a great little app if you commonly need to repetitiously modify text. Any application that supports services will support running selected text through command-line scripts via SilverService.
- Django and Taskpaper
- The Taskpaper format is a simple, structured text format. That means it’s easy to create using Django templates.