Parsel: a Mint Plug-in for detecting language, now updated · 29 words posted 01/31/2007 04:08 PM

Parsel is a plug-in for Mint, designed to detect your visitors’ browser settings. It’s updated it to work with Mint 1.29 and higher.
You can download it on google code.
* * *
How to Write a Pepper Plug-in for Mint, Part II: Preferences and Secondary Table Rows · 2101 words posted 09/30/2005 11:26 AM
Developers! Developers! Developers! Steve Ballmer and Shaun Inman both know that if you want programmers to embrace your platform, you make the API as accessible as possible. In Part I of this tutorial, we saw how easy it is to write a plug-in for Mint, Shaun Inman’s site statistics package. Now we’ll take a look at building upon our original plug-in to add preferences.
This tutorial builds on Part I. I won’t cover the same ground again, so if you’re building your first Pepper plug-in it’s best to read Part I first. Also, this is not a how-to-write PHP tutorial, but you should be able to follow along if you have a basic understanding of arrays and functions. Finally, you code at your own risk. I can’t help you fix your Mint installation if you break it.
To recap: in Part I we built Parsel, a plug-in to show the languages our site visitors use. It was nice, but it wasn’t very feature-rich. Users consistently requested two changes:
- Many users wanted to see languages grouped by family. This means we’ll want to generate a sub-list under each language family; and
- Many users wanted to see aggregates as a percent, not as a count. But we don’t want to force all users to change, so we’ll add a preference to show as count or percent.
Also, we have two house-keeping measures that don’t affect end users:
- We need to update some of the code. Since Parsel was first released, Mint has changed the way it retrieves configuration data; and
- In the original version, I included my list of languages in a separate file—keeping data separate from application logic, best practices, blah blah blah. It turned out to be overkill, so we’ll move the language list into our class file.
Now view the source and let’s get started! As with Part I, I’ve included line numbers so you can follow along.
Updating the Code to Comply with Mint 1.1
As I noted above, Mint 1.1 and above now retrieves config values differently, so we need to update the way we grab them. Shaun has added the function getCfgValue().
The old way:
$this->Mint->cfg['preferences'];
The new way:
$this->Mint->getCfgValue('preferences');
Moving the Language List into the Class File
You can make the case that best practices dictate that you keep your data separate from your controller, and thus we kept a list of languages in a separate file which we loaded via include_once(). You can also make the case that best practices are a pain in the ass for simple applications and add needless complexity.
Having persuaded myself of the latter, I deleted the old languages.php file and added a new function: get_Languages().
The old way:
include_once("{$this->Mint->siteroot}{$this->Mint->cfg['install_dir']}pepper/{$this->info['src']}languages.php");
The new way:
$languages = $this->get_Languages();
Our new get_Languages() function (line 523) loads the list of languages and abbreviations into an array and returns the array. How simple is that?
Now that we’ve taken care of housekeeping, let’s get to the fun stuff: adding new features. As with Part I, I’ll walk through the class function by function, revisiting what has changed and pointing out what’s new.
function install()
As we have seen, install() does just what it sounds like: it installs the plug-in and makes it available to Mint. But we need to make two major changes to our previous install function:
- Remember that we’ve just updated the way we retrieve configuration values from
cfgtogetCfgValue(). This means if a user tries to install Parsel to an older version of Mint, Parsel (and most likely Mint too) will break! We need to make sure they’re installing to a current version of Mint. - Now that we want to offer preferences to our users, we should set default values during the installation.
First, we’ll use code written by Colby Makowsky to check whether Mint is version 1.1 or later, and if not, abandon the installation and throw an error. (See lines 75-97). I won’t go into detail about the code here: if you’re following along you can see it’s clever and hacky, but is likely to change with future versions of Mint. In brief, if we detect an older version of Mint we build an array $errMsgs and pass that array to getHTML_PermissionErrors($errMsgs) (line 477), which outputs the error to the screen.
Next, we set our preferences (lines 100-104). I’ll focus on show_as_percent—you can figure out what show_raw_string does on your own.
First, we create a new preference: show_as_percent and we set the value to 1, which means by default our users will see aggregate values as percentages, not as counts.
$prefs['show_as_percent'] = 1;
Second, have have to save that preference:
$this->Mint->savePluginPreferences($this->plugin_id,$prefs);
function onDisplay()
This is largely the same as last time: onDisplay() is fired when a Mint viewer loads our pane. As mentioned above, we now load our language list from a new function instead of an external file.
function onDisplayPreferences()
The code in this function is entirely new to our Pepper: last time we didn’t have any preferences, so there wasn’t anything to display. But this time we have two preferences: show_as_percent and show_raw_string (as with the install() function, I’ll describe show_as_percent and let you figure out show_raw_string on your own). This function expects you to return an array of HTML; Mint automagically outputs the contents of the array for you.

First, let’s look at the finished preference pane.
By default, we want to show percentages, but we also want the user to be able to turn this feature off. The best way to handle this is with a checkbox. So in onDisplayPreferences() we need to handle the following tasks:
- Grab our preferences for this plugin;
- Build an HTML table to show the preferences in a user-friendly fashion; and
- Be sure to reflect the proper state of the preferences in the table.
Grab the preferences and stuff them into an array (line 213):
$prefs = $this->Mint->getPluginPreferences($this->plugin_id);
Get the show_as_percent value and set the checked value if necessary:
$show_as_percent = ( $prefs['show_as_percent'] ? ' checked="checked"' : '' );
Finally, we build a $preferences array with our HTML and return the array to finish the function.
function onSavePreferences()
This function (line 244) allows the user to save any changes she’s made to preferences (the very prefs we learned to display in the previous function). The function is automatically fired when the user clicks done; since the preference pane is actually a form, we simply check to see which values were posted and put them in the prefs array. The built-in function savePluginPreferences() does the heavy lifting of actually saving our updated preferences to the database.
Let’s skip over onCustom(), since we haven’t called it yet. We’ll return to it shortly.
getHTML_LanguagesRecent() and getHTML_LanguagesCommon()
These are the functions we wrote from scratch in the first part of the tutorial. Instead of native Mint functions they’re our own. Let’s take a closer look at the changes in getHTML_LanguagesCommon().
Recall that we now give the user the option to show language groups as percent instead of as counts. Since we’re saving this option as a plug-in preference, we need to retrieve the preference. First, we set the plug-in preferences into a $prefs array, and then we grab the show_as_percent flag (lines 350-351). Next, we set the label for our output table based on the pref we just retrieved (line 352).
Also recall that we want to show sub-data: not only the language group, but also the languages within the group. Fortunately, Mint has a built-in flag for its tableData array:
$tableData['hasFolders'] = true;

When we describe our language column in the thead array, we give it a CSS class of stacked-rows. This combination of hasFolders and stacked-rows tells Mint how to display the rows and sub-rows—notice the small arrow that appears on the left side of each row, indicating that the row can be expanded. Again, we’re just building on pre-set descriptions already present in the Mint API.
Based on the value of our $show_as_percent flag, we calculate percentages or counts accordingly. But the next big change comes along when its time to assemble our table data. It’s worth comparing the old and new versions:
Old:
$tableData['tbody'][] = array(
$language_row_final,
$r['total']
);
In the old version, we simply build an array with the name of the language and the total count. Think of it as simply column A and column B.
New:
$tableData['tbody'][] = array(
$language_row_readable,
$num_output,
'folderargs'=>array(
'action'=>'getranges',
'lang'=>$r['str_language'],
'total'=>$total
);
(Ignore the slightly different variable names). In the new version, we also assign values for the first and second columns, but we add a new element to the array: folderArgs. The first element, action, is required, and tells Mint which action to pass to onCustom() when the row is clicked—in this case, getranges. The second and third arguments, lang and total, are the arguments Mint will pass to the getranges function in the _POST scope.

Here’s the beauty of AJAX (and I promise that’s the only time I’ll use that word in this tutorial): if you were building Mint the old-fashioned way, you might assemble a massive, multi-dimensional array in PHP and pass it to the browser. You’d show and hide table nodes onClick(), but such a large amount of data would make the page load slowly. Instead, by using the XmlHttpRequest object Mint ignores the secondary data when the page loads, and only retrieves the language sub-rows when the user requests them. Faster page loads, and data as needed at your fingertips.
Schweet.
function onCustom()
Let’s jump back up to line 254 in the class file. Function onCustom() is fired when the user clicks a language family row to expand the specific language variant sub-rows beneath. onCustom() simply acts as a handler to catch the event and pass the data in the _POST scope to a new custom function: getHTML_Ranges().
function getHTML_Ranges()
Just like getHTML_LanguagesCommon(), this function (beginning line 413) is a custom function written by the developer—it’s not included in the Mint API, and it could have been named anything (assuming it’s consistent with PHP function naming rules). getHTML_Ranges() simply takes the two-letter abbreviation for the selected language and looks for matching regional variants in the database. As with our other getHTML functions, we simply assemble a $tableData array, use Mint’s built-in generateTableRows() function and pass it back as an $html variable.
A presentation note: with getHTML_Ranges() and getHTML_LanguagesCommon() we use formatPercents(), another built-in Mint function that formats our percents values to make them easy to read (line 457).
Confused? Me too. Let’s review how we stepped through all of those display functions:
onDisplay(): This built-in function fires when the pane loads. Aswitch-casechecks which tab is selected; let’s assume it’s “Most Common,” in which case we call…getHTML_LanguagesCommon(): This function populates our table with a list of language families, and shows percent or count based on user preferences. This is not a built-in function.onCustom(): This built-in function is called when a user clicks an expandable row in the Languages table.onCustom()simply listens for the click, checks for theactionand any other variables in the_POSTscope, and fires the requested function, in this casegetHTML_Ranges().getHTML_Ranges(): This function populates our sub-rows with a region specific list of languages. It is not a built-in Mint function.
That’s it. Writing a Pepper plug-in for Mint mostly depends on understanding when functions are called, and distinguishing between built-in API functions and those you have to write yourself. Let me know in the comments if you don’t understand a portion of this tutorial.
P.S. If you want to view the most current version of the Parsel source, you can always snoop around the svn repository. As you can see, Parsel still contains some inelegant hacks. For example, I only select the left two characters to determine the language family (line 365). Doing so renders incorrect results for outliers like AST (Asturia) and the query can surely by improved by using MySQL REGEX. So if you’d like to contribute to development and you’re familiar with subversion, leave a note in the comments and I’ll give you commit permission.
* * *
Triple Thick: A Mint Plug-in to Back Up Your Configuration · 80 words posted 09/27/2005 09:19 AM
Introducing Triple Thick, a Pepper Mint Plug-in to back up your Mint configuration. Unlike most Mint plug-ins, you don’t actually have to install Triple Thick to use it. If you develop plug-ins, or if you frequently test beta plug-ins, Triple Thick is for you.
Be sure to read the READ ME file included in the zip for instructions on configuring Triple Thick. The best time to use Triple Thick is before (before != after) installing a new plug-in.
* * *
Sparks! Updated to Show Stats · 74 words posted 09/14/2005 09:30 PM

We’ve updated “Sparks!”—the plug-in for Mint—to show high and low ranges, thus providing the scale of measurement in each Sparkline. Plus we’ve fixed a bux that caused the plug-in to generate blank graphs on some installations of Mint.
Thanks for all the feedback. If you come across bugs or have suggestions for improvement, email me or post them in the comments below.
You can grab the current copy of Sparks! on Colby’s site.
Comment [4]
* * *
Sparks! A Mint Plug-in to Generate Sparklines · 195 words posted 09/13/2005 07:24 PM
Edward Tufte calls Sparklines:
small, high-resolution graphics embedded in a context of words, numbers, images. Sparklines are data-intense, design-simple, word-sized graphics.
Colby Makowsky and I have written a Sparklines Pepper plug-in for
Mint, Shaun Inman’s web statistics package. With four small images you can now get a snapshot of your site’s activity trends.

Sparks! is built on top of James Byers’s Sparkline PHP Graphing Library.
Right now, the implementation is very simple: you can choose between a bar chart and a line graph to show data over four time periods (hours, days, weeks, and months). There aren’t any data points listed in each sparkline yet, but we can add them based on your feedback.
Download Sparks! from Colby’s site, kick the tires, and let us know what you think.
UPDATE 09.14.05 4:00PM EST
One user has reported that he cannot generate the Sparklines on his server. The Sparklines library requires that you have the GD library installed as a PHP module. I’ve included a utility page in the Sparks! download to help you check whether your server meets the requirements; please see “Troubleshooting Sparks! Pepper” in the READ ME file in the download for help troubleshooting.
Comment [3]
* * *
Parsel Posted: An Updated Mint Plug-in to Detect Language · 105 words posted 09/12/2005 09:38 AM
IMPORTANT UPDATE 01/26/2007: Parsel now works with Mint 1.29. You can grab the new version on google code.
I’ve updated Parsel to version 0.0.8 and added the two most frequent user requests:
- Parsel now shows only the primary browser language, and
- Parsel now aggregates the most common hits by root language, with an expandable pane showing regional variations (see image below).

Please check the included READ ME file for installation or update instructions. If you find any missing or incorrect language codes, please email me or post a comment here; I’ll update accordingly.
(Parsel is a Pepper plug-in for Shaun Inman’s Mint, a program for tracking site statistics).
Comment [7]
* * *
How To Write a Pepper Plug-in for Mint · 1678 words posted 09/08/2005 10:47 AM
UPDATE: The Pepper API has changed since I wrote this article. Please see Shaun Inman’s Intro to Pepper Development.
One of the coolest things about Shaun Inman’s web stats package Mint is its extensibility API. Mint plug-ins are called “Pepper,” and add features that aren’t in the core installation. There are already four or five plug-ins available, including my plug-in Parsel.
The API isn’t documented yet, but it’s extremely easy to learn by looking at exisiting plug-in examples. Here’s a quick tutorial for writing your own plug-ins.
DISCLAIMER: I was not on the Mint beta and I don’t have any inside information about the Pepper API. Shaun has stated that the Pepper API is not stable. I am not responsible if you follow my instructions and end up with a plug-in that breaks your Mint installation or otherwise diminishes your self esteem. Also, to make the most of this tutorial you need to have a basic understanding of simple PHP and MySQL.
Finally, I strongly recommend that you back up your mint_visit table in your Mint database before trying to write a plug-in. Maybe that’s a good barrier to entry for using this tutorial: if you know enough MySQL to backup a table, you’re probably safe writing your own plug-ins. If not, spend some time learning MySQL and PHP and then come back here.
To follow along, grab the source code for Parsel (currently at version 0.0.8). I’ve added line numbers for reference. You can save the copy locally as class.php and change my code to suit your needs.
Goals for the Plug-in
The default installation of Mint doesn’t track visitors’ browser language settings. We want to accomplish the following tasks:
- Track the visitors’ language in the Mint database
- Output the data in a user-friendly format, showing most recent data and aggregate (most common) data.
To get Mint to accomplish those tasks, we need to tell it:
- What to do on install
- What to do on uninstall
- What to do when a visitor hits any page on the site
- What to do when we want to look at our stats
Step by Step through the Plug-in
Pepper plug-ins are a PHP class with a series of functions. You’ll want to give your plug-in its own namespace, in other words, a name that doesn’t clash with a name that another developer might choose. For example, my name space is since1968. At lines 39 and 40, change the $install_plugin and class values to your namespace and the name of your plugin. My namespace and plugin name combined are since1968_Parsel.
Now let’s step through the functions. I’ll put line numbers in parentheses:
function since1968_Parsel()
This function (line 51) is the class constructor. That’s a fancy way of saying that the since1968_Parsel() function connects the plug-in to Mint and sets up basic information about the plug-in. Most of the fields are self-explanatory, but pay attention to the src and class values. The src tells Mint where to look for your plug-in. You’ll want to set up a folder structure like this:
/mint/pepper/yournamespace/yourpluginname
For example, Parsel is in the following folder:
/mint/pepper/since1968/parsel
For the src value, enter yournamespace/yourpluginname

Also, we need to specify which panes will show up in the Mint display. For example, at line 69 we tell Mint to display a pane called “Languages” with tabs called “Most Common” and “Most Recent” (shown to the left).
function install()
This function (line 80) runs when your user installs your plug-in. Mint stores its visitor data in a table called mint_visit, and by default it doesn’t track your visitor’s language. We need to add a field to the mint_visit table, and we do so with an ALTER TABLE command at line 81. Mint doesn’t protect the default fields in the mint_visit table; you want to create a field that won’t accidentally clash with other developers’ plug-ins. For example, I called my field since1968_language, consistent with my namespace.
function uninstall()
This function (line 101) runs when you uninstall your plug-in. Since we’ve added a field to the database during the installation, we need to clean up after ourselves during uninstall. At lines 102-103, we drop the since1968_language field from the mint_visit table.
function onRecord()
This function is fired whenever Mint inserts a new record in the database. We need to accomplish two things in this function:
- We need to grab the user’s language. We do so at line 121 via
HTTP_ACCEPT_LANGUAGE. - We need to tell Mint which value to insert into which field. At line 122, we return an array that tells Mint to insert the user’s language into the field
since1968_language—this is the new field we created infunction install().
It’s a good idea to keep this function as light and fast as possible: onRecord() fires EVERY TIME a user visits your site. If you need to clean up data, consider doing so when you pull it out of the database and not when you insert it. This will become clearer in the display functions, below.
Finally, Mint expects onRecord() to return an array every time—even if you’re not actually inserting anything into the database. Thus, at a minimum your onRecord() function should contain:
return array();
If you notice a “blank insert” problem while you’re developing your plug-in, make sure you’re returning an array!
function onJavaScript()
We don’t use this function with Parsel.
function onDisplay()
This function (line 144) sets up the display pane for your plug-in. We want to accomplish two tasks in this function:
- First, we load an array of language abbreviations and full names (line 148). We could include this array in its own function within the Parsel class, but I include a separate file called
languages.php(view its source). I assume I’ve missed some languages and it will be easier to update an external file instead of my Parsel class. - Second, we want to tell Mint how to output our data. In the
switch casestatement (line 151) we use the values we created in the class constructor. In plain English, the switch case statement reads “If the user wants to show the ‘Most Common’ tab, calle the getHTML_LanguagesCommon function and pass it the array of languages. If the user wants to show the ‘Most Recent’ tab, call the getHTML_LanguagesRecent fucntion and pass it the array of languages.”
The getHTML functions will create the actual output of the pane and return it to the onDisplay() function.
We skip the next four functions, since we don’t use a Widget and we don’t set preferences with the Parsel plug-in.
function getHTML_LanguagesRecent()
Mint displays its data in tables. This function needs to accomplish the following tasks:
- Set up an array to populate the table
- Query the MySQL database for the language data
- Massage the data as needed to make it human-friendly
- Pass the array to the
generateTable()function.
Let’s look at each step.
Lines 210-215 set up our table structure: we want a table with column headers called “Languages” and “When.”
Next, we query the database (line 217). For recent languages, we simply grab the language string and the time it was inserted into the database. We order by time descending, and make sure we don’t grab any blank entries.
Assuming we have a result, we loop through the query at line 225. Remember, we only want to output the time and the language.
Lines 235-254 aren’t truly necessary. If we skipped them, our language output would look like es-ar. But that’s not very friendly, is it? Instead, we look up each value in the $since1968_languages array and grab the human-readable language name. es-ar becomes Spanish (Argentina).
Here, we revisit the point I made earlier about doing the heavy lifting when outputting Mint data instead of when inserting it. In one sense, lines 235-254 represent bad coding practice. Why should we clean up output every single time we want to see it? But I think it’s better to slow down Mint marginally by massaging output than to slow down your site by massaging input every time you get a visitor. This might not make a different for low traffic sites but I’m certain the difference would appear under load.
At line 256, we take our cleaned up language name along with the time posted and add it to the $tableData array.
Finally, we call the built-in function generateTable() and pass our $tableData array to it to output the table HTML (line 263).
getHTML_LanguagesCommon()
This function (line 273) works pretty much like getHTML_LanguagesRecent. The only difference is that we want to group the output with our SQL query and show how often each language appears. As with the previous function, we build a $tableData array and pass it to the generateTable() function to output the HTML.
And that’s it! Now you can deploy your plug-in. To do so, create a folder with your namespace in the /mint/pepper folder. Then create a new folder with the name of your plug-in and save the class file there. The full path would be:
/mint/pepper/yournamespace/yourplugin/class.php
Upload the file and use the Mint interface to install the plug-in.
Reiterating Best Practices
Again, two important best practices to consider:
- Be respectful of other people’s work. This means that you should use your namespace not only with your plug-in class but also with any new fields you create in the database. That way you won’t accidentally collide with another developer’s new fields.
- Do the heavy lifting on output, not on input. Remember: you and you alone look at your Mint stats, but every single visitor to your site fires the
onRecord()function. If you need to clean up data for Mint presentation, consider doing it each time you look at it and not on insert—even if that might seem inelegant in other contexts.
I wrote this Pepper Mint tutorial in quick and dirty fashion, so if something sound uncommonly foolish or wrong please let me know in the comments.
Next week, I’ll post a tutorial for writing plug-ins that use preferences. Happy coding.
Comment [4]
* * *
Parsel: A Mint Plug-in to Detect Language · 323 words posted 09/07/2005 10:24 AM

IMPORTANT UPDATE 01/26/2007: I’ve updated Parsel to support Mint 1.29. You can grab the new version on google code.
Do you ever wonder which language your site visitors use? Meet Parsel, a plug-in for Mint, Shaun Inman’s web statistics application.
What Parsel Does:
Parsel grabs HTTP_ACCEPT_LANGUAGE from the browser’s user agent string and saves it in the Mint database. You can view languages by most recent and most common.
How to Install Parsel
- Download the zip.
- Unzip the file to your desktop and follow the instructions in the READ ME file.
Limitations
I have tested Parsel with Mint v100, v102, v104, v106, v107, v108, v109.The current version of Parsel requires Mint v111-v114..Parsel outputs the raw value of HTTP_ACCEPT_LANGUAGE.Parsel now outputs human friendly language listings. Also, Parsel doesn’t attempt to reconcile slightly different strings. For example, Parsel doesn’t recognize “en-us” as the same as “en-us,en;q.”- I’ve tested this plug-in, but you install it at your own risk. If you’re a PHP programmer, please feel free to look at the source code before installing.
If you have any suggestions or find any bugs, please post them in this thread. I’ll post a tutorial later today for writing your own simple plug-in.
UPDATE 7:23PM EST

I’ve updated Parsel to v0.0.2. Parsel now displays “human friendly” languages like “Spanish-Argentina” instead of “es-ar.” Grab the zip file and unzip it to your desktop. The files include a READ ME that gives quick and easy steps for installing from scratch or updating.
UPDATE 09/09/05 Noon EST
I’ve updated Parsel to v0.0.5. I’ve added to the list of supported languages and tweaked the database to prohibit nulls. If you’re upgrading from an older version of Parsle, please uninstall first.
UPDATE 09/12/05 9:43AM EST
I’ve updated Parsel to v0.0.8.
UPDATE 10/04/05 7:05PM EST
Parsel updated to support showing aggregates as percents.
If you encounter any problems, feel free to post them here in the comments. Thanks.
Comment [17]
* * *
How Old is Jeffrey Zeldman? · 107 words posted 09/06/2005 04:38 PM
I’ve kept a close eye on my search string referrals since installing Shaun Inman’s Mint 24 hours ago. At the moment, most of them are related to last week’s comparison of Katrina and the Flood of 1927.
This afternoon I noticed, bizarrely if delightfully, the following search string from Google UK: how old is Jeffrey Zeldman. My short answer for the searcher: I don’t know. Sorry.
But I can tell you this: when I interviewed Mr. Zeldman in 2003 he sent a picture to accompany his bio. I lost the image in a subsequent site move, but I recall its file name: since1955.
Good luck to the seeker of knowledge.
* * *
Configuring Shaun Inman's Mint · 323 words posted 09/05/2005 09:27 PM
At first, I was going to title this post “I Love You For the Little Things,” but if you’re a webmaster you’ll soon be drowning in enough cute references to Shuan Inman’s stats package Mint to make you puke your lungs out. (Tip: wait a few days and google mint ajaxy goodness.) Hence the boring title of this post.
But what little things! While installing Mint I encountered two details that should be a part of every app configuration process:
- First, Shaun provides a php file called mint_compatibility.php that you can run on your server to make sure Mint will work with your configuration. You don’t have to hunt for the file; it’s prominently featured on the requirements page. If your server is compatible, Mint lets you know. But if your server isn’t compatible, the file points out the specific problem and suggests that you contact your host if you are still interested in using Mint. I assume Shaun runs a one-man shop. Imagine the number of support emails he eliminates with this one user-friendly page.
- Second, Mint asks you to set the time when you first load the configuration page. I’ve always been baffled by the GMT-x format: why should I care what time it is in England? Instead, Mint simply asks you to select your time from a dropdown list (see screenshot). It’s a relatively common feature in desktop applications, but usually absent from php-based programs.
It’s small, telling details like easy installation and configuration that make an app a pleasure to work with. Great job Shaun. Now if I could just do something about my stats…
Update 09/07/2005: I had to tweak my settings a couple times before I could get the Local Searches working correctly. If you use a default installation of Textpattern set your local search path to ”/” (without the quotes) and set your query to “q” (again, without the quotes).
Update 10/27/05: See also mint ajax goodness.
* * *

