Errata

#

This last weekend I wrapped up a new customization to this site, listing new comments (“notes”) right alongside new posts. This happens both in the blog and in the RSS feed. You can see some details of how I got this working on this Textpattern forum thread, but I offer more complete details here, specifically regarding the RSS feed. This may not make sense to people who don’t use Textpattern, but it was enough trouble to get it working that I hope maybe this will help someone working on a similar problem.


Textpattern generates its own RSS feeds, and these are not customizeable; you can only change it by replacing it comepletely. You do this by creating a special page template, and a new section (‘feed’ in my case) set to use that page. The final results of this can be seen at the feed’s new permanent address.

Below is the complete page template I am using. First of all I’m sticking with the Atom 1.0 standard as my RSS format, because I just like it better. The page first uses the aks_header plugin to set the HTTP header to application/atom+xml, since the HTML default would be inappropriate for an XML document such as an RSS feed. Next I use a SQL query inside the etc_query plugin to find the date of the most recent post or comment for use in the feed’s updated element.

Finally, using the approach offered in that forum thread, I use the etc_query plugin to pull a list of the 25 most recent posts and comments using forms that output in RSS (Atom 1.0) format — each item inside an entry element. An XSLT stylesheet is then applied to this output to sort the entry elements based on their child published elements.

<txp:aks_header name="Content-Type" value="application/atom+xml; charset=UTF-8" strip="0" gzip="1" nodebug="1" /><?xml version="1.0" encoding="UTF-8"?>
<feed xml:lang="en-us" xmlns="http://www.w3.org/2005/Atom">
<txp:etc_query data="
     SELECT UNIX_TIMESTAMP(MAX(PubDate)) AS CombinedPubDate 
     FROM (
         SELECT MAX(LastMod) AS PubDate FROM `textpattern` 
         WHERE status = 4 AND Section = 'article' 
         UNION
         SELECT MAX(posted) AS PubDate FROM `txp_discuss` WHERE visible = 1) tmp">
            <txp:variable name="pubdate" value="<txp:php>echo date('c',{CombinedPubDate?});</txp:php>" /></txp:etc_query>
  <title>The Local Yarn</title>
  <subtitle type="text">Joel's Improved Personal Website</subtitle>
  <link rel="self" href="http://thelocalyarn.com/feed/" />
  <link rel="alternate" type="text/html" href="http://thelocalyarn.com/" />
  <!-- ID manually copied from original Textpattern-generated feed -->
  <id>tag:thelocalyarn.com,2005:2a51d7d61935e24519dcd97008d51b63</id>
  <generator uri="http://textpattern.com/">Textpattern (custom feed)</generator>
  <updated><txp:variable name="pubdate" /></updated>
  <author>
    <name>Joel Dueck</name>
    <email>&#106;&#111;&#101;&#108;&#64;&#106;&#100;&#117;&#101;&#99;&#107;&#46;&#110;&#101;&#116;</email>
    <uri>http://thelocalyarn.com/</uri>
  </author>

<txp:etc_query globals="_GET"
     data="SELECT GROUP_CONCAT(ID SEPARATOR ',') artids, GROUP_CONCAT(discussid SEPARATOR ',') dscids, COUNT(discussid) dscount FROM 
    (SELECT NULL ID, discussid, d.posted Posted FROM `txp_discuss` d 
    JOIN `textpattern` txp ON d.parentid = txp.ID WHERE d.visible > 0 AND txp.Status = 4 AND txp.Section='article' 
    UNION ALL
    SELECT ID, NULL discussid, Posted FROM `textpattern` WHERE Status = 4 AND Section = 'article' 
    ORDER BY Posted DESC LIMIT {?page|0|intval.-1.*25},25) tmp">
<txp:variable name="artids" value="{artids?}" />
<txp:variable name="dscids" value="{dscids?}" />
<txp:variable name="dscount" value="{dscount?}" /></txp:etc_query>

<txp:etc_query markup="xml" 
  data='<body>
<txp:if_variable name="artids" value=""><txp:else />
<txp:article_custom id=''<txp:variable name="artids" />'' form="rss_article" /></txp:if_variable>
<txp:if_variable name="dscids" value=""><txp:else />
<txp:recent_comments break="" sort=''FIELD(discussid, <txp:variable name="dscids" />) DESC'' limit=''<txp:variable name="dscount" />'' form="rss_comment" /> </txp:if_variable></body>'>

<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" version="1.0" encoding="utf-8" omit-xml-declaration="yes" cdata-section-elements="content summary" />
<xsl:template match="body">
    <xsl:for-each select="entry">
        <xsl:sort select="published" order="descending" />
        <xsl:copy-of select="." />
    </xsl:for-each>
</xsl:template></xsl:stylesheet></txp:etc_query>
</feed>

There are a couple of slight but crucial differences between the above and what you would use in a normal HTML web page: in the final etc_query tag, notice the parameter markup="xml", and in the xsl:output tag notice method="xml" and cdata-section-elements="content summary" — this ensures that the output is not HTMLized, and that the <![CDATA[ sections in the content and summary elements are passed through properly.

Here is the rss_article form used to output the articles:

  <entry>
    <author>
      <name><txp:author link="0" /></name>
    </author>
    <published><txp:posted format="iso8601" /></published>
    <updated><txp:etc_query data='SELECT UNIX_TIMESTAMP(LastMod) AS ModDate FROM `textpattern` WHERE ID=<txp:article_id />'><txp:php>echo date('c',{ModDate?});</txp:php></txp:etc_query></updated>
    <title type="html"><txp:title no_widow="0" /></title>
    <link rel="alternate" type="text/html" href="<txp:permlink />" />
    <id>tag:thelocalyarn.com,<txp:posted format="%Y-%m-%d" />:2a51d7d61935e24519dcd97008d51b63/<txp:etc_query data='SELECT uid FROM `textpattern` WHERE ID=<txp:article_id />'>{uid?}</txp:etc_query></id>
    <content type="html"><![CDATA[
      <txp:body />
    ]]></content>
    <summary type="html"><![CDATA[
      <txp:excerpt />
    ]]></summary>
  </entry>

There are two cases here where etc_query is needed, to get at columns that Textpattern for which have any of its own tags: the last-modified date for the current article, and the auo-generated uid for the current article (which Textpattern generates specifically for use in RSS feeds).

I followed Textpattern’s format for the <id> tag, which is tag:[sitename],YYYY-MM-DD:[sitewide-uid]/[article-uid]. This ensures that when I switch to the new feed, the items that were already being served in RSS will have the exact same ID, avoiding a bunch of duplicate posts showing up for current RSS subscribers.

And finally, here is the rss_comments form used to include comments in the feed. This includes some conditional code to insert certain text if the comment author is not the article author.

<txp:etc_query data='SELECT email FROM `txp_users` WHERE name=(SELECT AuthorID FROM `textpattern` WHERE ID=<txp:article_id />)' name="etc_author_email" />
<txp:variable name="this_comment" value='<txp:comment_email />' />
<txp:etc_query data='<txp:comment_permlink>#</txp:comment_permlink>' query="//a"><txp:variable name="commentURI" value="{@href?}" /></txp:etc_query>  <entry>
    <author>
      <name><txp:comment_name link="0" /></name>
    </author>
    <published><txp:comment_time format="iso8601" /></published>
    <updated><txp:comment_time format="iso8601" /></updated>
<txp:if_variable name="this_comment" value='<txp:variable name="etc_author_email" />'>
    <title type="html">Re: <txp:title /></title>
<txp:else />
    <title type="html">Note from <txp:comment_name link="0" /> — Re: <txp:title /></title>
</txp:if_variable>
    <link rel="alternate" type="text/html" href="<txp:variable name='commentURI' />" />
    <id><txp:variable name="commentURI" /></id>
    <content type="html"><![CDATA[
      <txp:comment_message />
<txp:if_variable name="this_comment" value='<txp:variable name="etc_author_email" />'><txp:else /><p>— <txp:comment_name link="1" /></txp:if_variable> 
    <p><em>[The above is a note added to <txp:permlink title='<txp:title no_widow="0" />'>an earlier post…</txp:permlink>]
    </em></p>]]></content>
  </entry>

In creating this form I stumbled on a Textpattern deficiency: txp:comment_permlink has no way of providing just the URI of the comment instead of a full <a href> link. Once again etc_query steps in, providing a way to parse out the URI from the resulting link.

The very last thing I did, once I ensured that the feed was validating, was to update the RewriteRules in my site’s .htaccess file to redirect all feed requests using old URIs back to the new feed address. (This part will be different for you if you use a web server other than Apache.)

Implementing all of this has certainly been a futzy adventure.

#

Earlier this year, the site was effectively moved from its long-time home at jdueck.net to thelocalyarn.com. All links to the old domain now redirect to the new one. Switching domains like this is pretty simple — here’s how to do it without breaking anything, if you use Apache and have direct access to the virtual host files for your sites:

  1. Turn off any IFTTT or PourOver rules that cross-post from your site to Twitter, Facebook, ADN, etc using your site’s RSS feed. (I neglected to do this, so when the switch took effect, my followers got a rude spray of links for all recent posts.)
  2. Create the virtual hosts file for the new domain. It can point at exactly the same DocumentRoot as the old domain, so you don’t have to move or copy any files.
  3. Enable the new site with sudo a2ensite newdomain.com and sudo service apache2 reload
  4. Set up the A records in your DNS for the new domain to point to your web server’s IP address. You will want to try out the new domain in your browser at this point, to see if it loads at all (although it will be a little broken until you’re finished with these steps).
  5. In the virtual hosts file for the old domain, add the following line: Redirect / http://newdomain.com/ — this way, any links to pages or images at the old domain will be seamlessly redirected to the same path at the new domain.
  6. Update the config settings in your site’s CMS to specify the new domain. For example, in Textpattern you’ll want to update under Admin $rarr; Preferences → Site URL.
  7. Update any other services your site uses that care about the site’s domain. In my case, I had to update my settings at Typekit to recognize the new URL. Your traffic tracking service (i.e. Google Analytics or Piwik) will also want to know about the new domain name, and you may need to update the tracking code in all your pages.

Note that these instructions are web-only and do not affect email addresses at the old or new domains.

Side effects may include duplicating all the recent items in your RSS feed.

#

Since the 2011 redesign, it was the intent that comments would not only be heavily filtered and curated, but that by surviving that process, comments would earn a place of equal standing with the main body text.

We’ve finally gotten around to incorporating this intent into the way comments are displayed: removing the visual break between the main body and the comments, and removing any formatting differences between the two as well.

Finally, I’ve switched the formatting syntax in the comment processor from Textile to the more well-known Markdown, specifically Michel Fortin’s MarkdownExtra. This means readers have access to the full range of MarkdownExtra — footnotes, definition lists, tables, etc. — but also HTML, since the Markdown spec allows for HTML markup.

Fortin’s recommended method of installing MarkdownExtra for Textpattern is to rename markdown.php to classTextile.php and drop this file in place of Textpattern’s old one. This works well, but it brings a big security hazard: Markdown syntax explicitly allows the use of plain HTML, but Fortin’s class does no filtering for malicious content (unlike Textile, which escapes all HTML). This means that if you don’t do any extra work, you leave yourself pretty wide open to XSS attacks.

My solution was to bolt on the excellent HTML Purifier package. After installing it on the server, edit the new classTextile.php, and add this line just above the definition for class Textile:

require_once '/path/to/web/plugins/htmlpurifier/HTMLPurifier.standalone.php';

Then change the TextileRestricted function to read as follows:

function TextileRestricted($text, $lite='', $noimage='') {
    $purifier = new HTMLPurifier();
    $text = $this->TextileThis($text, $lite);
    return $purifier->purify($text);
}

Update: The code above has been corrected so that Markdown is applied first, then the purifier. If you do it the other way around, the purifier will break certain Markdown features (such as using > for blockquotes).

#

With the relaunching of the site as The Local Yarn have come a number of changes to past content, with more on the way.

  • New series have been created, and some articles have been retroactively re-categorised under the new series.
  • A couple of articles in the ‘Journal’ series have been retired, because they comprised unstructured reactions to current events, and lacked both voice and substance: June 6, 2004, regarding the death of Pres. Reagan, and November 5, 2007 about a trip to Chicago.
  • Another three articles in the ‘Journal’ series – Spring 007, August 6, 2010 and October 1, 2010 – have been retired because they are only brief re-posts of content from elsewhere, and we have Tumblr for that sort of thing now.
  • Yet two more articles in the ‘Journal’ series, July 6, 2007 and July 21, 2007, have been retired because they are comprised mostly of quotations and/or links, with little added substance.
  • The article Faith from the ‘Journal’ series has been retired due to structural problems.
  • The written policy for comments/footnotes has been softened somewhat, although the intent remains the same.

#

In the course of writing and researching for this week’s podcast, it was discovered we have been, for many years, misattributing the authorship of a poem long featured on the About section of this website:

Here no state chambers in long lines unfold,
Bright with broad mirrors, rough with fretted gold,
Yet modest ornament with use combined
Attracts the eye to excercise the mind.

The poem turns out to be an excerpt from Epistle to a Friend by Samuel Rogers, first published in 1798. We first encountered these lines unattributed in the 1842 book Cottage Residences by A.J. Downing, which was why we for many years believed he had written them.

#

I’ve begun implementing a feature we’ve been thinking about for years (and never seen anywhere else), a book-style index of all posts. One of the difficulties of the site’s layout has been making older writings accessible and and enjoyable to browse through, and hopefully this will provide a new angle for people to peruse through past posts.

We’ve been faithfully typing in keywords for every post for years, mainly for use in meta tags to help out the search engines. All we needed was some code to access the complete list of all keywords ever used. I found this in the tru_tags plugin for Textpattern. Since tru_tags cannot use custom fields (where I had been storing my keywords) I copied them into the keywords field proper with a SQL command:

UPDATE `textpattern` SET `Keywords`=concat(`Keywords`, ',', `custom_1`)

I then set up my “tag archive page” according to the tru_tag usage instructions, altering the format to achieve a book-like effect (Textpattern article IDs work nicely as page numbers). I was able to break out the keywords by first letter, just as in book indices, by adding a custom function to the tru_tags plugin to return only the first letter of the current keyword.

The index page would ordinarily be dynamically generated every time the page is accessed, but as this was found to present a noticeable performance penalty, I set up full-site caching using the asy_jpcache plugin, version 0.9.8, which worked flawlessly out of the box on our Textpattern 4.2.0 installation. The page is now served as a static, cached version (very snappy) which is updated once every 12 hours (a plenty small-enough window for this site).

Many of our posts’ keywords still need cleaning up and updating; this being the first time they have really seen the light of day on the website, numerous errors and omissions have inevitably popped up.

Typographically, two limitations remain. First, the problem of preserving title case in keywords (i.e., in proper names and book titles) — tru_tags prefers to fold everything into lowercase. Second, the problem of the added space before as well as after each comma - a byproblem of the way Textpattern evaluates conditional code in forms.

#

An improved chart has been created for the article This Is Your Life. Although the post is a year and a half old, we felt the corny Excel-isms stuck out poorly for the site, and we believe in continual improvement of our product. The old chart can be seen here for comparison.

#

  • The masthead is now just my name. That makes sense, I think. Only the about page retains reference to the site’s full and original title.
  • Last fall the link text was changed to a bright red from the staid old navy color it had been for years. This was a reflection of the fact that I’d begun using red ink in my journals. But the red text for hovered links was too bright, so today I changed them again so that the text darkens to pure black instead.
  • Fixed the “print” styles for printing hard copies.
  • Added an audio icon to podcast posts. More of those in the future at some point. The icon itself is just a letter from the Webdings font followed by two sizes of right-parentheses.
  • Added twitter contact info in a couple places.

#

New design principles have been learned, and changes introduced in the past six months:

  • The header now reduces in size and importance after you move past the front page. Principle: don’t restate the obvious.
  • Type is larger and now based on ems rather than pixels.
  • Headings are now in p22 Mayflower using sIFR. If you don’t have Flash enabled you’ll see everything in Georgia, which is fine.
  • All posts are now in one large pool, rather than having separate pools for poems, articles, and journal entries as before. Posts related to one another are strung together in ‘series’. This enables easy creation of new series, as opposed to the old system, where new structural and visual design solutions were needed every single time we wanted to add a new “type” of content.
  • Poetry is now set in tables, centered on the page based on the longest line.
  • The front page now displays the four most recent posts, instead of only the last most recent. I have moved towards shorter and shorter posts since I began writing online, and it makes less sense now to limit the front page in that way.

#

Page-end quotations have finally been added to the bottom Article and Contra Versey index pages. Also, interestingly, we have had to correct the regrettable and consistent solecism of printing the word “errata” with two T’s throughout the site. What’s another word for ironic?

#

The article What the Well-Spoken Man Is Saying (dated Mar 1, 2005) has been permanently retired. We believe we can do better.

#

We receive so much traffic from illegitimate .info domains that we finally decided to reject all traffic listing a .info site as the referrer.

So if you are one of the two people in the world who use a .info site for something ‘normal’ (I actually only know of one), you will not be able to link to this site. You might consider getting a different domain; .info has been abandoned to the underworld.

#

We thought we might clarify something about the Good Night, Irene pieces. The label “In 256 characters or less” is somewhat facetious. In fact, all of the pieces have exactly 255 or 256 characters, including spaces and punctuation.

#

The article Textpattern Semantics has now been translated into three languages: Dutch, Chinese, and French. Thank you to Marvin Vek, Jenny, and Etienne Depaulis (respectively) for volunteering to do these translations.

#

I have moved the site over to a new host, TextDrive. It’s run by people I trust, and who are committed to web publishing. What clinched the move, though, was a very good pecuniary arrangement, an offer of lifetime hosting for a one-time fee. I also have significant additional capacity that comes with this move; I will be considering how to use it judiciously.

#

The following items will be receiving attention:

  • Update RSS tutorial to use Atom, best practices
  • Picture on “Papers” page to be changedDone
  • Find a better way to present the Volumes in the Articles page, possibly involving javascriptdone (for now)
  • Find out why ISSN application is going nowherethey need 30 working days. Thank you, Ms Houston.
  • Polish search resultsDone

#

As the regulars have no doubt noticed, things look a little different around here. The site is now powered by Textpattern rather than being hand-typed XHTML. I’m hoping I’ll be able to focus on writing more by automating things. Using the computer to its best advantage. Textpattern cranks out standards-compliant pages and while not ultimately as flexible as the Old Way, does give a few features such as site search that weren’t available before. Feedback is welcome.

#

Volume I, Issue II (the ever-popular Altoids article) has been updated, mainly with typographic changes, such as complete elimination of the FONT tag, proper use of double & single primes, quote marks, and ellipses, as well as line spacing. Two of the more stupid items in the list have been changed, but the total number remains 94. The image linked in item 47 has been changed to reflect this website’s new address (the .org version was underhandedly taken during my absence in 2001-2002).

#

Changes to I Slept, and Dreamed a Dream: revised dialog for clarity. Also, the keywords and description in archived issues’ HEAD section have been updated to match their actual content (vs. being a copy of the index page’s keywords.)

#

Images altered in various issues.

In attempting to be more consistent in the use of our sparse accents, some of the spot illustrations have been switched out:

  • The candle/book/glasses image is preferred for articles of a grand or thoughtful nature
  • The editor-as-archer image for caustic and spirited offense
  • The dancing quaker for light humour

The affected issues are Volume II, Issue IV, and Volume III, Issue I.