Welcome to TiddlyWiki created by Jeremy Ruston, Copyright © 2007 UnaMesa Association
[[Ben Gillies|BenGillies]]
This is a Python plugin for TiddlyWeb that restricts HTML tags and attributes to a whitelist. It is available on GitHub at [[html_validator.py|http://github.com/bengillies/TiddlyWeb-Plugins/blob/1f76489fe677e813bfcd682bf9b745dc2c9683ac/validators/html_validator.py]].
The code is republished below for reference purposes (though any future updates will only appear on GitHub):
{{{
from tiddlyweb.web.validator import TIDDLER_VALIDATORS
from tiddlyweb.model.tiddler import Tiddler
import logging
from BeautifulSoup import BeautifulSoup, Comment
import re
"""
sanitise any input that has unauthorised javascript/html tags in it
Let through only tags and attributes in the whitelists ALLOWED_TAGS and ALLOWED_ATTRIBUTES
"""
ALLOWED_TAGS=[
'html',
'p',
'i',
'strong',
'b',
'u',
'a',
'h1',
'h2',
'h3',
'h4',
'h5',
'h6',
'pre',
'br',
'img',
'span',
'em',
'strike',
'sub',
'sup',
'address',
'font',
'table',
'tbody',
'tr',
'td',
'ol',
'ul',
'li',
'div'
]
ALLOWED_ATTRIBUTES=[
'href',
'src',
'alt',
'title'
]
def sanitise_html(value):
#match dangerous attribute values (eg javascript:) with regex
r = re.compile(r'[\s]*(&#x.{1,7})?'.join(list('javascript:')))
#turn the input into a BeautifulSoup parse tree
soup = BeautifulSoup(value)
#get all HTML comments (<!-- -->) and remove them
for comment in soup.findAll(text=lambda text: isinstance(text, Comment)):
comment.extract()
#Check all tags against the allowed set and remove any that are not found
for tag in soup.findAll(True):
if tag.name not in ALLOWED_TAGS:
tag.hidden = True
#check that attribute/value pairs against the allowed list and regex and cut any that are not allowed
tag.attrs = [(attr, r.sub('', val)) for attr, val in tag.attrs
if attr in ALLOWED_ATTRIBUTES]
return soup.renderContents().decode('utf8')
def sanitise_xss(tiddler,environ):
for field,value in tiddler.fields.iteritems():
tiddler.fields[field] = sanitise_html(value)
tiddler.text = sanitise_html(tiddler.text)
tiddler.tags = map(sanitise_html,tiddler.tags)
tiddler.title = sanitise_html(tiddler.title)
def init(config_in):
"""
init function
"""
TIDDLER_VALIDATORS.append(sanitise_xss)
}}}
For a while now, Osmosoft has been working on a project called [[TiddlySpace|http://tiddlyspace.com]], which is a collaborative environment built on top of TiddlyWeb and TiddlyWebWiki. Before we go any further, you should go and [[try it out now|http://tiddlyspace.com]] as it's really cool. I'll just wait here till you get back...
One of the more interesting aspects of [[TiddlySpace|http://tiddlyspace.com]] is the way you can add new functionality to your space by including other spaces within it. With that in mind, I've created a new space at http://bookmarks.tiddlyspace.com that lets you bookmark other websites (in a delicious sort of way) and store information about them inside your own space. The benefit of this of course, is that all your sites get stored inside a TiddlyWiki, where you can operate on them further, link to them, tag them (or even transclude parts of them if you like) instead of locking them away inside a service that you don't control (you can of course download your TiddlyWiki and take it with you).
This is achieved through a [[bookmarklet|http://en.wikipedia.org/wiki/Bookmarklet]] that loads up your space inside an iframe directly on the website of your choosing, and then uses some funky [[HTML5|http://ajaxian.com/archives/using-html-5-postmessage]] cross document messaging (with postMessage) to send over some important details (url, description, title, etc).
The really cool thing about this is that [[TiddlySpace|http://tiddlyspace.com]] means you can get it straight away: all you need to do is include the {{{bookmarksplugin}}} space inside your own, and then call the {{{<<bookmarklet>>}}} macro somewhere to generate your bookmarklet.
All this of course means that I can keep my post really short, and let the space itself do the talking.
If you're interested in the source code, you can find it in my contributors directory at http://svn.tiddlywiki.org/Trunk/contributors/BenGillies/TiddlyWeb/Plugins/Bookmarklet
(NB - I should point out here that this will work in any default TiddlyWeb install, though not in standalone TiddlyWiki).
{{{
// hijack function to handle 412 edit conflict response
config.extensions.ServerSideSavingPlugin.saveTiddlerCallback = function(context, userParams) {
var tiddler = context.tiddler;
if(context.status || context.httpStatus == 1223) {
handle204(context);
} else {
if(context.httpStatus == 412) {
handle412(context);
} else {
displayMessage(config.extensions.ServerSideSavingPlugin.locale.saveError.format([tiddler.title, context.statusText]));
}
}
};
function handle204(context) {
var tiddler = context.tiddler;
if(tiddler.fields.changecount == context.changecount) { //# check for changes since save was triggered
tiddler.clearChangeCount();
} else if(tiddler.fields.changecount > 0) {
tiddler.fields.changecount -= context.changecount;
}
displayMessage(config.extensions.ServerSideSavingPlugin.locale.saved.format([tiddler.title]));
store.setDirty(false);
// from here is a custom bit for the copyPlugin; you'd want to handle via a custom 204 handler passed into the ServerSideSavingPlugin
if (tiddler.fields["sourceworkspace"]) {
newWorkspace = tiddler.fields['server.workspace'];
originalworkspace = tiddler.fields["sourceworkspace"];
tiddler.fields["server.workspace"] = originalworkspace;
tiddler.fields["server.page.revision"] = tiddler.fields["revisionsidinsource"];
if (tiddler.fields["publishlevel"] == 'move') {
store.removeTiddler(tiddler.title);
autoSaveChanges();
}
delete tiddler.fields['sourceworkspace'];
delete tiddler.fields["revisionsidinsource"];
}
}
function handle412(context) {
var tiddler = context.tiddler;
try {
var adaptor = config.extensions.ServerSideSavingPlugin.getTiddlerServerAdaptor(tiddler);
} catch(ex) {
return false;
}
if (!adaptor.host){
adaptor.host = context.host;
}
var context = {workspace: tiddler.fields["server.workspace"]};
var req = adaptor.getTiddler(tiddler.title, context, {}, onGetTiddler);
return req ? tiddler : false;
}
function onGetTiddler(context) {
var destTiddler = context.tiddler;
var sourceTiddler = store.getTiddler(destTiddler.title);
sourceTiddler.fields['server.page.revision'] = destTiddler.fields['server.page.revision'];
sourceTiddler.fields['server.workspace'] = destTiddler.fields['server.workspace'];
store.saveTiddler(sourceTiddler.title);
autoSaveChanges(false);
}
}}}
Following on with my current theme of making TiddlyWeb easier to use, especially without Javascript, I thought I'd introduce another little plugin that I've written. To sum it up in a sentence, it adds POST support to TiddlyWeb.
What this means in practise, is that you can put an HTML form in your page (TiddlyWebPages is a good place to put it), and when somebody clicks submit, the data they entered is POSTed to TiddlyWeb, which then stores it, all with no JavaScript whatsoever. Sounds simple right? Well yes, that's kind of the point.
To use it properly, you can POST to the following urls:
*/bags/<bag_name>/tiddlers/<tiddler_name>
*/recipes/<recipe_name>/tiddlers/<tiddler_name>
in the same way that you can PUT to them in normal TiddlyWeb. However, this also lets you POST direct to the bag (or recipe), allowing users to specify their own title right there in the input box. To do this, you can post to:
*/bags/<bag_name>/tiddlers
*/recipes/<recipe_name>/tiddlers
and just include a "title" field in your HTML form. Sometimes though, the title doesn't matter (my comments section is a good example of this), so if you don't include one, TiddlyWeb will now generate a random title for you, saving you the bother.
!!Fields and Values
Essentially, you could POST any set of values you like to the above URLs, and they'd get put in the right bag as tiddlers, but some names are reserved for specific tiddler attributes. They are as follows:
*Title - this specifies the tiddler title
*Text - this specifies the text (or content) of the tiddler
*tags - this specifies the tags that you want the tiddler to have (specify these as a TiddlyWiki style space/double square bracket delimited list)
Other values will simply be set as tiddler fields.
!!Binary Files
There is one exception to this though - binary files. If you include a file input box and give it a name of "file", then TiddlyWeb will use that instead, and put that file into the correct bag (or recipe), as a tiddler. You can then both navigate to and view it in the usual way.
!!Example
The following would be a simple example of how to use this plugin. It assumes that the plugin has been installed already (ie - added to tiddlywebconfig.py)
{{{
<form method="POST" action="/bags/foo/tiddlers">
<input type="text" name="title" value="Insert title here" /><br>
<textarea rows="5" cols="40" name="text">Insert text here</textarea><br>
<input type="tags" name="tags" value="tags" /><br>
<input type="submit" value="Send to TiddlyWeb" />
</form>
--- or for binary files (like images) ---
<form method="POST" action="/bags/foo/tiddlers" enctype="multipart/form-data">
<input type="file" name="file" /><br>
<input type="submit" value="Send to TiddlyWeb" />
</form>
}}}
Its that simple. If you want this plugin, its on GitHub at http://github.com/bengillies/TiddlyWeb-Plugins/tree/master/form/
You can test it out by leaving me a comment. :)
<<recentByTagLink "Home" "blog" 2 1>>
[[About Me|BenGillies]]
<<recentByTagLink "TiddlyWeb" "tiddlyweb">>
<<recentByTagLink "Plugins" "plugins">>
Since my last post, I've been hard at work on a new plugin, much larger in scope than my previous TiddlyWeb plugins. Before I start, you'll notice that my website has changed in look and feel quite considerably. Don't worry, the old site still exists at http://bengillies.net/.a/recipes/oldsite/tiddlers should you wish to see it.
So, onto the new plugin
!!What new plugin?
It's called TiddlyWebPages (or tiddly-webpages).
!!What's with the new site layout?
Well, the new layout is built on new plugin. Makes sense right?
!!What's it all about?
TiddlyWeb is a great server side, and with TiddlyWebWiki installed, it allows you to create some great websites pretty quickly and easily. In fact, once everything's set up, you don't even need to log in to your server. You can create the site right there from the comfort of your browser. The problem comes when you don't want to use TiddlyWebWiki for your entire site. Maybe you don't want to use it at all, or maybe you just want a site that doesn't use Javascript, or a site that functions more like a normal one. Well, that's where TiddlyWebPages comes in. It allows you to rapidly create new HTML templates, and store them in tiddlers. These templates then fit together however you like, reusing different templates wherever and whenever you like, to create your site. You can then define custom URLs (again within Tiddlers) and tell TiddlyWebPages which template and which recipe you want to assign to which URL. You can even tell it to load different templates depending on which bag you're viewing.
!!That sounds pretty interesting
Thanks
!!But what if I want to include templates within other templates?
You can do that too. You just tell it which recipe you want that template to use, and include it in your other template wherever you want. Doesn't need a recipe? Uses the same recipe as the other template? That's not a problem either.
!!Ok, so how do these sub templates differ to normal templates?
They don't. All templates are the same, they all use the same syntax, and you can view all of them on their own should you wish to. Likewise, you can include any template inside any other template too. In fact, this website makes heavy use of that very aspect. You can even reuse other people's templates as they're all automatically available on the web. Just copy them over into your TiddlyWeb instance.
!!Sounds pretty cool. You mentioned something about URLs?
I was just getting to that bit. You can create your own URLs much like you can create your own templates. URLs can be entered in a pattern format, allowing you to match with any bag or tiddler, and associate that with a recipe (yes, that means you can carry over said bag/tiddler into the recipe to create a dynamic recipe) and load all of it up in a template of your choosing. It even works with standard TiddlyWeb filters.
!!Excellent, so I could have /stuff/bag_name/tiddler_name?
Yes.
!!You also mentioned something about default templates for bags and recipes?
I did, quite right. You can assign a custom template to load up for each bag or recipe. So if one recipe is your blog and one showcases your project work, you can use a different template as the default for each. Don't worry though, each template has an extension associated with it. You can still view any tiddler/bag/recipe using any template you like, just put the correct extension on the end.
!!So what about extensions provided by other plugins?
Yes they work too. Remember the link to my old site (http://bengillies.net/.a/recipes/oldsite/tiddlers)? Notice the lack of extension on the end? Yes, that's because I told TiddlyWebPages to load up that recipe using TiddlyWebWiki. Likewise, you could set up a custom URL to use a different plugin, or even add a different plugin inside one of your templates should you so choose.
!!Sounds great. Where can I get it?
It's on GitHub. You can find it [[here|http://github.com/bengillies/TiddlyWeb-Plugins/tree/master/tw_pages]] together with a readme detailing how to use it properly.
Finally, if you have any problems, please let me know. I'm on [[Twitter|http://www.twitter.com/bengillies]] [[@bengillies|http://www.twitter.com/bengillies]]. I also read the TiddlyWeb [[Google group|http://groups.google.com/group/TiddlyWeb]] so if you post something there, I'll happily reply to it.
I frequently read the TiddlyWeb, TiddlyWiki, and TiddlyWikiDev Google Groups. Find them at:
http://groups.google.com/group/TiddlyWeb
http://groups.google.com/group/TiddlyWiki/
http://groups.google.com/group/TiddlyWikiDev/
This is a Python plugin for TiddlyWeb that filters tiddlers, returning all tiddlers that have the specified string somewhere in the specified field (aka - partial matching on fields). It is available on GitHub at [[like.py|http://github.com/bengillies/TiddlyWeb-Plugins/blob/f7d1bc798726a2bf74fa849e80baa730b6d5d4c4/filters/like.py]].
The code is republished below for reference purposes (though any future updates will only appear on GitHub):
{{{
"""
Compare the given string to the supplied field and return everything that partially matches:
eg:
/bags/foo/tiddlers?like=title:bar
will return all tiddlers where bar is contained somewhere within the title
"""
from tiddlyweb.filters import FILTER_PARSERS
from tiddlyweb.filters.select import select_parse
def compare_text(source, test, negate=False):
if source.lower() in test.lower():
return True != negate
return False != negate
def compare_tags(source, test, negate=False):
count = 0
for tag in test:
if source.lower() in tag.lower():
return True != negate
return False != negate
def compare_fields(source, test, attribute, negate=False):
try:
if type(test[attribute]) == text:
return compare_text(source[attribute], test[attribute])
except KeyError:
return False != negate
return False != negate
ATTRIBUTE_SELECTOR={
'tags': compare_tags,
}
def like(attribute, args, tiddlers, negate=False):
for tiddler in tiddlers:
try:
test = getattr(tiddler, attribute)
test_func = ATTRIBUTE_SELECTOR.get(attribute, compare_text)
found = test_func(args, test, negate)
except AttributeError:
found = compare_fields(args, tiddler.fields, attribute, negate)
if found:
yield tiddler
return
def like_parse(command):
attribute, args = command.split(':', 1)
if args.startswith('!'):
args = args.replace('!', '', 1)
def selector(tiddlers):
return like(attribute, args, tiddlers, negate=True)
else:
def selector(tiddlers):
return like(attribute, args, tiddlers)
return selector
FILTER_PARSERS['like'] = like_parse
def init(config):
pass
}}}
Following on from my [[previous post|Validating TiddlyWeb]] about validators, I thought I'd talk about filters in TiddlyWeb and give a couple of examples about how they work.
Filters take a list of tiddlers, filter that list in some way, and return the filtered list. How it works behind the scenes is slightly more complex, but essentially the end result is that you can apply them to any list of tiddlers and you'll get a filtered list back in return, so whether that's a recipe, or a bag of tiddlers (or even a list of tiddlers in a custom python script) it doesn't matter: they can all be filtered.
Writing a custom filter, as you may expect with TiddlyWeb, is trivially easy: all it requires is a function that takes the filter itself, and returns another function that in turn, does the filtering on a list of tiddlers. If you're thinking about writing a filter yourself, this means that you'll need at least two functions, one to parse the filter into a usable format, and another to take that format, along with a list of tiddlers (technically in Python, this is a generator object) and return the filtered list.
<<setCollapseHeightHere>>
So, onto my filters: One filter matches the tiddlers in a given list with a named tiddler, returning all those tiddlers that are related, ranked in order of most-relatedness; The other filter does partial matching on any field that you specify, returning all tiddlers that have a field that partially matches the given text.
You can use them as follows:
!!Related tiddlers:
{{{
/recipes/foo/tiddlers?related=bar:title,tags,bag
}}}
The above filter would take all tiddlers in the recipe "foo", and return all those related to the tiddler "bar" (where "bar" is in the same recipe). The related-ness in this example would be based on three fields: title; tags; and bag.
!!Like tiddlers:
{{{
/recipes/foo/tiddlers?like=tags:tiddly
}}}
This filter would take all tiddlers in the recipe "foo" and return all those where "tiddly" appears somewhere in one of the tags. In this particular example, this would be useful if you're looking for tiddlers about either TiddlyWiki, or TiddlyWeb.
Both of these are available on GitHub, where you'll find the most up to date version. I have also reproduced the code (as it is at the moment) on my blog. If you're interested, take a look:
[[Related.py]] - find all tiddlers related to the given tiddler
[[Like.py]] - compare the given string to the supplied field and return everything that partially matches
I have an account on GitHub where I publish all of my TiddlyWeb plugins.
http://github.com/bengillies/
//{{{
config.commands.saveTiddler.old_handler = config.commands.saveTiddler.handler;
config.commands.saveTiddler.handler = function (event, src, title) {
var stored_tiddler = store.getTiddler(title);
if(stored_tiddler&&stored_tiddler.fields && stored_tiddler.fields['sourceworkspace']){
delete stored_tiddler.fields['sourceworkspace'];
}
config.commands.saveTiddler.old_handler(event,src,title);
};
config.commands.publishtiddler = {
text: "publish",
tooltip: "publish this so everyone can see it",
confirmMsg: "Are you sure you want to publish this? If so, it will become visible to everybody and you will no longer be able to edit it",
saveFirstMsg: "Please save this first!",
handler: function(event,src,title){
var t = store.getTiddler(title);
if(!t){
alert(this.saveFirstMsg);
}
if (!confirm(this.confirmMsg)) {
return false;
}
var fields = store.getTiddler(title).fields;
//********************CHANGE THIS BIT TO YOUR PREFERRED BAG***************
fields['publishtobag'] = "blog";
fields['publishlevel'] = "copy";
//********************************************************************************************
var publishToBag = fields.publishtobag;
var newWorkspace = "bags/"+publishToBag;
var publishLevel = fields.publishlevel;
if(publishLevel) {
fields['sourceworkspace'] = fields['server.workspace'];
fields['revisionsidinsource'] = fields['server.page.revision'];
fields['server.workspace'] = newWorkspace;
store.saveTiddler(title);
autoSaveChanges();
} else {
alert("no publish level set!");
}
}
};
//}}}
TiddlyWeb is a server side version of Tiddlywiki.
More information can be found at the following sites:
http://tiddlywiki.org/wiki/TiddlyWeb
http://tiddlyweb.peermore.com/wiki/
That's me, Ben Gillies. I'm a software developer currently working on the open source Tiddlywiki platform at [[Osmosoft|http://osmosoft.com]]. For those who don't know what Tiddlywiki is, I would recommend you check out http://tiddlywiki.com and have a play with it yourself, but essentially, its a portable wiki built in a single file that allows you to add and edit wiki entries and then take your wiki around with you, or email it to your boss, if you feel that way inclined. This site itself is built on a server side version of Tiddlywiki, called [[TiddlyWeb|http://tiddlyweb.peermore.com/wiki/]], which I also recommend you check out.
<div class='header' macro='gradient vert #FFF #FFF '>
<div class='gradient'>
<div class='titleLine' >
<span class='searchBar' macro='search'></span>
<span class='siteTitle' refresh='content' tiddler='SiteTitle'></span>
<span class='siteSubtitle' refresh='content' tiddler='SiteSubtitle'></span>
</div>
<div id='topMenu' refresh='content' tiddler='MainMenu'></div>
</div>
</div>
<div id='bodywrapper'>
<div id='sidebar'>
<div id='sidebarOptions' refresh='content' tiddler='SideBarOptions'></div>
<div id='sidebarTabs' refresh='content' force='true' tiddler='SideBarTabs'></div>
</div>
<div id='displayArea'>
<div id='messageArea'></div>
<div id='tiddlerDisplay'></div>
</div>
<div id='displayFooter'></div>
</div>
In a previous post on ~TiddlyWebPages, I mentioned URL handling functionality, and promised a later post detailing how to use it. Well, since then, I've done a considerable amount of extra work on it, simplifying the way URL's are stored, and adding a couple of extra features, which I'll detail here.
First, and most importantly though, it's no longer part of ~TiddlyWebPages. Instead, it has been packaged up as [[tiddlywebplugins.urls|http://github.com/bengillies/tiddlywebplugins.urls]] and can be installed by doing
{{{
pip install -U tiddlywebplugins.urls
}}}
Then you'll need to put 'tiddlywebplugins.urls' into both the 'server_plugins' and 'twanager_plugins' sections of tiddlywebconfig.py.
Once you've done that, you'll be able to add URLs with twanager. For example, loading up the "default" recipe as a TiddlyWiki at /mywiki, you'd write:
{{{
twanager url /mywiki /recipes/default/tiddlers.wiki
}}}
Where /mywiki is the url path you want to use, and /recipes/default/tiddlers.wiki is what you want to appear there.
If you wanted to load up any recipe as a wiki, and serve them up at /wikis/<recipe_name> then you could write:
{{{
twanager url /wikis/{recipe:segment} "/recipes/{{ recipe }}/tiddlers.wiki"
}}}
In this example, {recipe:segment} specifies a variable within the URL called "recipe". The {{ recipe }} then get's replaced with whatever URL you eventually go to. You could then arrive at the /mywiki example above by pointing your browser at /wikis/default.
You can find further instructions about syntax for the URL path you are adding at http://lukearno.com/projects/selector/ though note that any variables are always embedded within the destination URL with double braces (eg - /recipes/{{ recipe }}/tiddlers.wiki).
!Redirection
tiddlywebplugins.urls also supports URL redirection. This can be used either internally to your site, or with any external link instead.
To use internally, just add --redirect to the twanager command (eg - twanager url --redirect /s3Rw /bags/common/tiddlers/MyTiddler would redirect anyone going to /s3Rw to /bags/common/tiddlers/MyTiddler).
To use with external sites, you just need to use the full URL. For example, you could redirect to Google with:
{{{
twanager url /google http://www.google.com
}}}
!URL storage and Modification
These URLs are all stored in the TiddlyWeb store with the Title being the URL you want to create, and the text being the URL you want to map/redirect to. The --redirect option in the twanager command, relates directly to tagging the tiddler "redirect".
By default, this will be stored in a bag called urls, with a strict policy to stop other users modifying your URLs. This means that, with sufficient permissions, you could manage all your URLs from within TiddlyWebWiki itself.
Just one final note to finish off with, Currently, this only supports GET requests. This means that you cannot send PUT, POST or DELETE requests to these URLs. Saying that though, TiddlyWebWiki should still work regardless, just bear in mind that you should currently be PUTting to the standard set of URLs if you're writing any custom Javascript yourself.
!Useful Links
''Source code'': http://github.com/bengillies/tiddlywebplugins.urls
''Package'': http://pypi.python.org/pypi/tiddlywebplugins.urls
Guide to ''Selector'': http://lukearno.com/projects/selector/
Ok, so we got TiddlyWeb up and running in Part 1. The view that you get from this is fairly plain though, and definitely doesn't look anything like this site. So the next couple of steps involve customising and finalising the install. Most of this is pretty much the same as for any other version of TiddlyWeb, so I'm only going to cover it briefly as information already exists [[elsewhere|TiddlyWeb]].
!Step three: Customising
The first thing we'll probably want to do is make a new bag and a couple of recipes. You can do this using twanager and running the following commands from inside your instance directory:
<<setCollapseHeightHere>>
{{{
twanager bag <bag_name>
ctrl-D
twanager recipe <recipe_name>
/bags/system/tiddlers
/bags/<bag_name>/tiddlers
ctrl-D
}}}
You'll probably want to stop guests from editing your site too. For this, you'll need to create a user account with twanager. You can then set access policies from within each bag to ensure that you're the only person that can write to them. You'll also want to ensure that your web server can write to the store. You can do this using the chmod function. For more details on how it works, type:
{{{
man chmod
}}}
into a command line.
Once you've done this, the final step before you get into TiddlyWiki itself involves the .htaccess file in the root of your web directory. Open this up and ensure that put and delete are both enabled. Then type in the following, or ensure that something similar exists:
{{{
RedirectMatch ^/$ http://<your_domain_name.com>/index.cgi/
SetEnv PATH_INFO /
}}}
The RedirectMatch command ensures that whenever somebody types in your URL and hits enter, they get redirected to your TiddlyWeb install. The SetEnv command simply places a forward-slash into the PATH_INFO variable and is necessary in case anybody misses out the slash at the end of index.cgi, which would otherwise result in a 404.
!Finalising
Now that your setup is customised and you have some recipes, bags and your own user name, we'll add a few plugins to get TiddlyWeb to load a TiddlyWiki by default.
Plugins in TiddlyWeb can be in two forms - server-side, or client side. Server-side plugins are wirtten in Python, and included by modifying tiddlywebconfig.py. Client side plugins are written in javascript and take the form of standard TiddlyWiki plugins, of which there are [[loads|http://tiddlyvault.tiddlyspot.com/]].
So first things first, let's add a server-side plugin to display one of two recipes (in the form of a TiddlyWiki) depending on whether the site visitor is logged in as an admin, or not. This plugin is called cachinghoster and can be found [[here|http://tiddlyweb.peermore.com/wiki/#cachinghoster]]. Drop it in the same place as tiddlywebconfig.py and point the links inside the file to the appropriate recipes. Ideally, one of these should allow you to update your blog and one should not. Then open tiddlywebconfig.py and add the following command:
{{{
'system_plugins': ['cachinghoster'],
}}}
Then just run "twanager server" in order to install it, and manually exit again. Now try opening your site again. You should get a TiddlyWiki loading up by default. With any luck, this should change to a different TiddlyWiki depending upon whether you are logged in or not. This will be primarily to ensure that your site is read only when people visit.
Finally, install the [[BlogLayout]] plugin by simply copying the contents across into a new tiddler, and tagging with systemConfig. It should save automatically (you will need to be logged in though) and will present you with a default view of al tiddlers, tagged with "blog", listed in date order with the most recent entry first. By default, it will also summarise these and add a read more link to the bottom. This functionality can be altered, or added as links manually in other areas too (see the links at the top of this site for an example) and more details can be found out in the [[BlogLayout]] plugin itself.
Well, that's it. Other than that, you might like to install a [[theme|http://tiddlythemes.com/]], or create your own, but you should now have a fully functioning blog created in TiddlyWeb.
Happy Blogging....
A while ago, I sat down with [[Michael Mahemoff|http://softwareas.com/]] and [[Jonathan Lister|http://jaybyjayfresh.com/]] and between us we came up with a method for copying tiddlers in a TiddlyWiki/TiddlyWeb environment from one bag to another. The purpose for this was to allow a user to draft out an article (or a document of any sort really), and save it to allow work on it later, without worrying about somebody seeing their half finished work. As it turned out, this was relatively easy to accomplish within TiddlyWiki and involved modifying the {{{server.workspace}}} field of the tiddler. There was some cleanup to do afterwards of course, involving sorting out potential revision id clashes, and setting the {{{server.workspace}}} field back to the original bag, but the core of the process involved simply changing one field, and then saving the tiddler again.
Since then I have refined this process, fixing a couple of bugs and adding move functionality (copying the tiddler and then deleting the original) and now feel it is in a state ready to blog about.
<<setCollapseHeightHere>>
!Usage
To use this, you will need the PublishCommand tiddler, and the ZPublishCleanup tiddler. Add the Publish command button to a toolbar (I add mine in the "more" section of ViewToolbar and you should be good to go. All you'll need to do is set the custom fields in PublishCommand to the bag you want, and whether you want to move or copy:
{{{
fields['publishtobag'] = "blog" //set this to your desired bag
fields['publishlevel'] = "copy" //this value can be either "copy" or "move"
}}}
Then to use it, just click publish. on a tiddler that you want to perform the action on.
In practise, you may want a more dynamic version, allowing you to set the bag and publish level depending on what it is you're copying/moving. If you need to accomplish this, I would suggest using [[Jon Robson's|http://jonrobson.me.uk]] [[AdvancedEditTemplate|http://svn.tiddlywiki.org/Trunk/contributors/JonRobson/plugins/AdvancedEditTemplate/]] Plugin and setting dropdown inputs to allow users to set the bag and copy/move as required. To see this in action, save the [[index.html|http://svn.tiddlywiki.org/Trunk/contributors/JonRobson/plugins/AdvancedEditTemplate/index.html]] file locally and then load it up in your browser. Then click edit on the open tiddler to get a demonstration.
To integrate these two plugins successfully, you'll want one dropdown called "publishtobag" with a list of all bag names, and one called "publishlevel" with a choice of "copy" or "move".
I personally found it very useful to proof read this post before I made it live, but I'm sure there are many other uses that I have not yet considered, so feel free to modify it as much as you need for your purpose (I know I have already).
As I've just finished putting this site together, I thought I'd give you a brief overview of TiddlyWeb (the platform this site is built on) and how I went about implementing it.
First things first, there's quite a lot of documentation about TiddlyWeb already (see [[here|http://tiddlyweb.peermore.com/wiki/]]) so I'm not going to explain the basics about it, or try to detail the underlying structure. All I'm going to do here is provide a set of instructions for installing and running TiddlyWeb on a setup similar to mine.
So what's my setup?
<<setCollapseHeightHere>>
I'm running this site on Apache and Linux. I'm running Python through CGI, and I don't have root access to the server. If this sounds like you, then you've probably got one of the many hosting companies around providing you with your own web space on one of their servers. I would imagine this is a fairly common situation and unfortunately, requires jumping through a few extra hoops than the other install methods available, but here we go anyway.
!Step One: Installing TiddlyWeb
The first hurdle to jump over is actually installing TiddlyWeb itself. Now, TiddlyWeb is usually installed with the Python tool "easy_install" but, if you don't have easy_install you're going to have to install it. The main problem with this however is the lack of root access. So to start, we're going to install Virtual Python. This is a tool that creates a virtual install of Python in your home directory and lets you install other libraries on top of that. So go [[here|http://peak.telecommunity.com/DevCenter/EasyInstall#creating-a-virtual-python]] to install Virtual Python. Then go [[here|http://peak.telecommunity.com/dist/ez_setup.py]] and install easy_install from this script. From here, normal instructions can be followed for installation, so do:
//{{{
easy_install -U tiddlyweb
//}}}
!Step Two: Setting up an Instance
Now that TiddlyWeb's installed, we need to create an instance in the directory you publish websties in (most likely public_html). So, go into this directory and run:
//{{{
twanager instance <instance_name>
//}}}
Where <instance_name> is the name you want to give your wiki. This will then set things up for a default install, creating the store (where your content is stored) and creating a default config file called "tiddlywebconfig.py". However, we don't want a default install, so go and grab the index.cgi file from the [[svn repository|http://svn.tiddlywiki.org/Trunk/association/serversides/tiddlyweb/core/]] and drop it in the root of your web directory (e.g. public_html). Now open it up in a text editor (vi, nano, etc) and do the following:
Change the path at the top of the file to be your virtual python install path
then set the following:
//{{{
file_store_location = <path_to_your_store>
//most likely <instance_name>/store
//}}}
Then check that the following lines are either commented out or do not exist (note that these changes may be implemented in a future version of TiddlyWeb, thus saving you the bother):
//{{{
#sys.path.append('/home/cdent/src/TiddlyWeb')
#web_server_base = '/tiddlyweb/index.cgi'
# hostname = os.environ['HTTP_HOST']
# if ':' in hostname:
# hostname, port = hostname.split(':')
# config['server_prefix'] = web_server_base
//}}}
and finally make sure the line
//{{{
app = serve.load_app(hostname, port, config['urls_map'])
//}}}
looks more like this:
//{{{
app = serve.load_app()
//}}}
That's it for now, so save and quit and load up tiddlywebconfig.py in a text editor. Add the following lines:
//{{{
'server_prefix': '<instance_name>/index.cgi',
'server_host': {
'scheme': 'http',
'host': '<domain_name>',
'port': '80',
},
//}}}
Save and quit. That should be it, TiddlyWeb should now work. Open up your browser and point it to http://your_domain_name/<instance_name/index.cgi/ to browse through bags, recipes, etc. Note that you will need the forward slash after the index.cgi, or you'll probably get an error.
Coming in Part 2 - Configuration and finalisation
!!Update
The index.cgi file has now changed. Please see [[CGI Install Update]] for more information.
TiddlyWiki is a wiki in a single HTML file, driven by javascript. It is very extensible with plugins, macros, and all sorts of interesting customizations.
* [[Home TiddlyWiki Web Site|http://www.tiddlywiki.com]]
* [[TiddlyWiki Documentation|http://www.tiddlywiki.org]]
I have recently updated my BlogLayout plugin, extending functionality and adding a few new features to make it more powerful so that you can use it in the way that you want to. As a result, I thought I'd go over it in a bit more detail here so that you can get a feel for how to use it.
!Features
Getting one of the major questions out of the way first and foremost, the core of this blog runs on TiddlyWeb, however the plugin itself is pure TiddlyWiki. If you're running on something else, you should still be able to use it without any problems - it was designed and tested on TiddlyWiki so should work as such. I envisage TiddlyWeb specific plugins at some point in the future, but I'll save those for a later post.
<<setCollapseHeightHere>>
So what can you do?
The first and primary feature of any blog is that it displays posts most recent first. This plugin is no different and by default will load in any posts tagged with a tag of your choosing. I chose "blog" personally, but feel free to pick something else.
The next, and possibly most obvious feature, is collapsing posts to provide a summary. This lets visitors see more of your posts, so lets them get to the one they're interested in sooner. This one has a few different options to help make it more powerful so I'll discuss it later on in more detail.
The third feature is limiting post counts. This means that if you only want to display 5 posts by default, but want all the others equally as available then you can, and any visitor to your site only has to click the "More Posts..." button to access them. This is fairly simple behaviour to use, so just set the post display count to whatever value you want, and this will happen by default. The post display count, along with other default options can be found in the BlogLayout plugin itself, or accessed via config.macros.BlogLayout.
The final feature regards links. Most blogs have links within their site separating content out into different themes. As a blog, I would expect these pages to also be laid out as a blog, possibly even in the same form as the homepage. This can also be achieved by calling {{{recentByTagLink}}} in place of an ordinary link. Simply supply some text for your link and a tag to link to to get started. Optional extras include specifying how many posts per page, whether to auto summarise those posts, and what the default height of each post needs to be before it is summarised.
!Auto Summary
I mentioned earlier that there were a few different ways of using this depending upon how you feel it should work. by default, it will summarise all posts on the home page, and any that are linked to as in the above method. Any posts opened directly will not be summarised, and posts will not be summarised if opening via a different means. This is deliberate as when someone clicks a link, it is generally with the intention of reading the contents. However, you may want all posts to appear summarised wherever they were opened from. To do this you will need to add the following to the ViewTemplate tiddler, right below the ".viewer" div.
{{{
<div macro="collapseThisTiddler"></div>
}}}
This will run the macro on every tiddler by default. You may notice also that this means there is a macro to run this manually. This macro can also be placed at the bottom of any tiddler content and will run on that tiddler only, every time it is called. The macro itself can also be called with a default height for the tiddler, which will be used in preference to the default height supplied in config.macros.BlogLayout. Specifying -1 instead, will ensure that the post is never summarised, which is particularly useful for images.
Moving on to the default height, this can be also be specified on a per tiddler basis, by putting {{{<<setCollapseHeightHere>>}}} at the position you want summarised. This will then always be used in preference to any other default height, and should ensure that your posts always summarise at the height of your choosing should you so wish.
Finally, I'll just mention that there is a stylesheet allowing you to customise the "Read More" and "More Posts" links at StylesheetBlogLayout.
If you have any thoughts on this or suggestions for improvement, I'm on Twitter [[@bengillies|http://www.twitter.com/bengillies]] so drop me a line.
Thanks
My OpenID is bengillies.net. It is hosted on my server, meaning that I own my own identity. OpenID is described as following:
<<<
OpenID eliminates the need for multiple usernames across different websites, simplifying your online experience.
You get to choose the OpenID Provider that best meets your needs and most importantly that you trust. At the same time, your OpenID can stay with you, no matter which Provider you move to. And best of all, the OpenID technology is not proprietary and is completely free.
<<<
If you want any more information, see http://openid.net/
I want to take this opportunity to talk about the current state of binary tiddlers within TiddlyWeb, how you can use them, and to introduce some new stuff that I've been working on.
There are currently a few ways of dealing with binary tiddlers in TiddlyWeb/TiddlyWiki. If we first take a look at TiddlyWiki, we can see that binary tiddlers can be loaded in the form of base64 encoded data: URIs (see [[TiddlyPictoWiki|http://groups.google.com/group/tiddlywiki/browse_thread/thread/a3177baf4604834f]] for a good example of this). Indeed, the upcoming [[TiddlyWiki5|http://www.tiddlywiki.com/tiddlywiki5/]] fully supports all of this natively (among other things).
Of course TiddlyWeb also supports binary tiddlers (setting the tiddler.type attribute determines this), with the difficulty being how to get the data into the store in the first place. For this, natively, your only real option is is to PUT it using curl and the RESTful API (more on another native option below). This works for the most part, though I wouldn't call it particularly user friendly. There is also my form plugin (see [[POSTing to TiddlyWeb|http://bengillies.net/.a/recipes/sitecontent/tiddlers/POSTing%20to%20TiddlyWeb]]), which I'll go into greater detail about later on.
Finally, you can use twanager, and a small plugin for importing binary content that I wrote called tiddlywebplugins.bimport. I should note first that I wrote this a few months ago and some of the functionality has since been integrated with TiddlyWeb core (specifically, the twanager command twimport) so most of this also applies to that command. The main benefit of using bimport however, is that you can guarantee that the file you're importing will always be stored as binary (javascript files or TiddlyWiki files might be a good use case). So, to install:
{{{
sudo pip install -U tiddlywebplugins.bimport
}}}
and then add {{{'tiddlywebplugins.bimport'}}} to you tiddlywebconfig.py file. You can then import binary files with:
{{{
twanager bimport <bag_name> <tiddler_name> <URI>
}}}
where the URI can be anything (eg - a file: URI). Alternatively, the twimport command can be run as:
{{{
twanager twimport <bag_name> <URI>
}}}
with anything not recognised as a valid type for TiddlyWeb to use being stored as binary content. What this means is that an image will import fine, but a Javascript file, will get tagged "systemConfig" and lose its mime type. If you don't want this to happen, you should use bimport. Otherwise, twimport is probably easier (as you don't have to install an extra plugin).
!Uploading via the Web
A while ago I wrote a blog entry entitled [[POSTing to TiddlyWeb|http://bengillies.net/.a/recipes/sitecontent/tiddlers/POSTing%20to%20TiddlyWeb]] which mentioned that you could use it to upload files to a TiddlyWeb bag or recipe. I've recently updated this to support tagging binary tiddlers, and have released it to pypi. You can install it by running:
{{{
sudo pip install -U tiddlywebplugins.form
}}}
from a command line. If you were using the old version, you should probably update as the new version supports better handling of tiddler titles, in that you can now override the title of a binary file being uploaded, and the aforementioned tagging of binary content (so that you can tag for example, an image, or a pdf).
I've also created a TiddlyWiki plugin to use with it. You can find it in my SVN repository at http://svn.tiddlywiki.org/Trunk/contributors/BenGillies/TiddlyWeb/Plugins/Binary/tiddlers/BinaryUploadPlugin.js, and can import it into your TiddlyWeb store with:
{{{
twanager twimport <bag_name> http://svn.tiddlywiki.org/Trunk/contributors/BenGillies/TiddlyWeb/Plugins/Binary/tiddlers/split.recipe
}}}
To use it, simply add:
{{{
<<binaryUpload bag:bag_name edit:tags edit:title tags:default_tags>>
}}}
where bag:bag_name is optional and allows you to specify a different bag to the one you're currently in, edit:tags and edit:title are also optional, and allow you to add a title or tags, and tags:default_tags allows you to set some default tags if you have the edit:tags option on.
I believe that binary tiddlers now have a fairly good story in TiddlyWeb, and am quite looking forward to seeing potential applications that surface.
This is a Python plugin for TiddlyWeb that filters out specific tiddlers base on titles matched with a blacklist. It also removes the systemConfig tag, if present and is designed to be used speficially to validate TiddlyWebWiki. It is available on GitHub at [[tiddlywiki_validator.py|http://github.com/bengillies/TiddlyWeb-Plugins/blob/1f76489fe677e813bfcd682bf9b745dc2c9683ac/validators/tiddlywiki_validator.py]].
The code is republished below for reference purposes (though any future updates will only appear on GitHub):
{{{
from tiddlyweb.web.validator import TIDDLER_VALIDATORS
from tiddlyweb.model.tiddler import Tiddler
"""
sanitise all tiddlywiki input by dissallowing reserved names, and clearing systemConfig tags.
Any tiddler in RESERVED_TITLES will be dissallowed.
"""
RESERVED_TITLES=[
'MarkupPreHead',
'MarkupPostBody'
]
def validate_tiddlywiki(tiddler,environ):
if tiddler.title in RESERVED_TITLES:
raise Exception('Reserved name')
if 'systemConfig' in tiddler.tags:
tiddler.tags.remove('systemConfig')
def init(config_in):
"""
init function
"""
TIDDLER_VALIDATORS.append(validate_tiddlywiki)
}}}
I've been looking at the TiddlyWeb log in system recently, specifically its support for OpenID, and my thinking went something like the following: "TiddlyWeb supports log in via OpenID (if you have one), and this is a blog that one person (that's me) needs to be able to log in to. What would be cool, would be if I could host my own OpenID server and use that to log into TiddlyWeb".
So after a bit of digging around, I came across a solution in the form of [[phpMyID|http://siege.org/projects/phpMyID/]] which is an OpenID server serving one user only. First off, if you're not sure what OpenID is, the OpenID website describes it as follows:
<<<
OpenID eliminates the need for multiple usernames across different websites, simplifying your online experience.
You get to choose the OpenID Provider that best meets your needs and most importantly that you trust. At the same time, your OpenID can stay with you, no matter which Provider you move to. And best of all, the OpenID technology is not proprietary and is completely free.
<<<
<<setCollapseHeightHere>>
All that looks very well and good so what's the downside you ask? Well unfortunately, support for OpenID isn't ''that'' great. A number of sites support it, but that support is hardly universal across the whole web.
Anyway, back to phpMyID. As the name suggests, it is written in php, so isn't really that compatible with TiddlyWeb (being written in Python), though being completely open, that doesn't really matter. The instructions on [[the site|https://www.siege.org/svn/oss/phpMyID/trunk/README]] are fairly comprehensive, so to install it yourself, just follow them. As you do though, bear in mind the following points:
*You'll probably want to install the server in a separate folder. mine is at http://bengillies.net/openid or http://openid.bengillies.net
*If your setup is anything like mine, you will need to implement one of the options in the [[htaccess|https://www.siege.org/svn/oss/phpMyID/trunk/htaccess]] file in their [[subversion repository|https://www.siege.org/svn/oss/phpMyID/trunk/]]. The first one worked really well for me.
*You can test your installation at [[openidenabled.com|http://www.openidenabled.com/resources/openid-test/]] by following the steps in the [[phpMyID forum|https://www.siege.org/forum/viewtopic.php?pid=85#p85]].
Finally, to integrate with TiddlyWeb/TiddlyWiki (it should work with both as long as you can put phpMyID on a server somewhere), enter the following into MarkupPreHead:
{{{
<link href="http://openid.bengillies.net/" rel="openid.server"/>
<link href="http://openid.bengillies.net/" rel="openid.delegate"/>
}}}
Where http://openid.bengillies.net/ is the path to your OpenID Server (e.g. - MyID.config.php - read the install instructions, ad you'll know which file I mean).
Since I enabled comments on my site, I've been getting quite a lot of spam. Now, this is a problem that's existed all over the web now for quite a long time. What's more, it's been solved quite often before, usually requiring log in, or some sort of [[CAPTCHA|http://en.wikipedia.org/wiki/CAPTCHA]] validation to ensure that whoever is posting a comment is actually a human being.
While TiddlyWeb supports log in off the bat, I'm not a huge fan of requiring it just to post a comment, as logging in seems, to me at least, a bit unnecessary when all you want to do is leave one comment on a site.
With that in mind, I've gone down the CAPTCHA route, and implemented a [[validator|http://tiddlyweb.peermore.com/wiki/recipes/docs/tiddlers/validator]] that supports the popular [[reCAPTCHA|http://recaptcha.net/]] validator that you've probably seen all over the rest of the web.
To use it, you'll need to grab it from my GitHub repository [[here|http://github.com/bengillies/TiddlyWeb-Plugins/blob/master/validators/recaptcha.py]]. Then you can add it to system_plugins in your tiddlywebconfig.py file. As its an external service, you'll then need to sign up to it at http://recaptcha.net/api/getkey in order to get the requisite private and public keys.
Once you've signed up, add your private key to the tiddlywebconfig.py file like so:
{{{
'recaptcha_private_key': '<private_key>'
}}}
Then, wherever you need to use it, add the following HTML:
{{{
<script type="text/javascript"
src="http://api.recaptcha.net/challenge?k=<your_public_key>">
</script>
<noscript>
<iframe src="http://api.recaptcha.net/noscript?k=<your_public_key>"
height="300" width="500" frameborder="0"></iframe><br>
<textarea name="recaptcha_challenge_field" rows="3" cols="40">
</textarea>
<input type="hidden" name="recaptcha_response_field"
value="manual_challenge">
</noscript>
}}}
Making sure to substitute <your_public_key> for the public key you got when you signed up (its in there twice).
Finally, it's a validator, so don't forget to set your accept policy.
[[Running on TiddlyWeb, Part One]]
This is a Python plugin for TiddlyWeb that filters tiddlers, returning all related tiddlers, ranked in order of related-ness. It is available on GitHub at [[related.py|http://github.com/bengillies/TiddlyWeb-Plugins/blob/f7d1bc798726a2bf74fa849e80baa730b6d5d4c4/filters/related.py]].
The code is republished below for reference purposes (though any future updates will only appear on GitHub):
{{{
"""
Compare the given tiddler with other tiddlers in the bag and return
anything that is related byt the supplied fields, sorted in order with most related first
eg:
/bags/foo/tiddlers?related=bar:title,tags
will return all tiddlers related (by title and tags) to the tiddler "bar", ranked in most related first order
"""
from tiddlyweb.filters import FILTER_PARSERS, parse_for_filters, recursive_filter
import logging
import re
def compare_text(source, test):
source_words = re.split('\W',source)
count = 0
for word in source_words:
if word.lower() in test.lower():
count += 1
return count
def compare_tags(source, test):
count = 0
for tag in source:
if tag in test:
count += 1
return count
def compare_fields(source, test, match):
count = 0
try:
if type(source[match]) == text:
count = compare_text(source[match], test[match])
except KeyError:
pass
return count
ATTRIBUTE_SELECTOR={
'tags': compare_tags,
}
def match_related_articles(title, matches, tiddlers):
def empty_generator(): return ;yield 'never'
tiddlers = [tiddler for tiddler in tiddlers]
try:
source_tiddler = recursive_filter(parse_for_filters('select=title:%s' % title)[0], tiddlers).next()
except StopIteration:
#nothing to match on, so return an empty generator
return empty_generator()
sort_set = []
for tiddler in tiddlers:
count = 0
for match in matches:
try:
source = getattr(source_tiddler, match)
test = getattr(tiddler, match)
test_func = ATTRIBUTE_SELECTOR.get(match, compare_text)
count += test_func(source, test)
except AttributeError:
count += compare_fields(source_tiddler.fields, tiddler.fields, match)
if count > 0 and source_tiddler.title != tiddler.title:
sort_set.append([tiddler,count])
def sort_function(a,b): return cmp(b[1],a[1])
sort_set.sort(sort_function)
result = (tiddler_set[0] for tiddler_set in sort_set)
return result
def related_parse(command):
attribute, args = command.split(':', 1)
args = args.split(',')
def relator(tiddlers):
return match_related_articles(attribute, args, tiddlers)
return relator
FILTER_PARSERS['related'] = related_parse
def init(config):
pass
}}}
I post on Twitter. If you don't like RSS/Atom, it's a good way of finding out when I post something, or even when I find something interesting.
[[@bengillies|http://twitter.com/bengillies]]
/***
|''Name:''|BlogLayout|
|''Description:''|adds a blog like view and tiddler summary view to TiddlyWiki|
|''Author''|BenGillies|
|''CodeRepository:''|http://svn.tiddlywiki.org/Trunk/contributors/BenGillies/plugins/BlogLayout.js |
|''Version:''|1.0|
|''Comments:''|Please make comments at http://groups.google.co.uk/group/TiddlyWikiDev |
|''License''|[[BSD License|http://www.opensource.org/licenses/bsd-license.php]] |
|''~CoreVersion:''|2.5|
<<setCollapseHeightHere>>
! Usage
Set POST_TAG_NAME to the tag that you want to load by default. This will then automatically order all tags most recent first.
All posts longer than MAX_HEIGHT will be shortened and a "Read More..." link appended to the bottom.
You can additionally call
{{{<<collapseThisTiddler default_height>>}}}
At the end of any tiddler to provide a similarly shortened view with a "Read More..." link at the bottom. default_height is optional and provides a default to set the height to if setCollapseHeightHere has not been called within the tiddler (see below). Set default_height to -1 to set a default of not shortening tiddlers. This can be placed in the ViewTemplate tiddler (AUTO_SUMMARISE_FRONT_PAGE should be turned off if you are doing this) right after the .viewer div, as follows:
{{{<div macro="collapseThisTiddler default_height"></div>;}}}
You can also set a custom height from within the tiddler. If you do this, it will take precedent over all other default height settings and is the recommended method of setting height as it allows you to fine tune how short each tiddler can be. To use, call:
{{{<<setCollapseHeightHere turn_off>>}}}
This will set the height of the shortened tiddler to wherever you place the macro. turn_off should be -1 if you wish the tiddler to always appear in full. Otherwise, leave blank.
You can link to a blog-like page/layout (as per the page ouy get on first load) by putting:
{{{<<recentByTagLink link_name tag_name max_posts collapse_posts default_height>>}}}
in place of any link, where:
link_name = the text you want the link to read
tag_name = the name of the tag you want to filter by (aka POST_TAG_NAME)
max_posts = the maximum number of posts to display
collapse_posts = this can be 1 or 0. If 1 it will shorten posts, adding the Read More link. Default is AUTO_SUMMARISE_FRONT_PAGE.
default_height = the default height of shortened posts. Set to -1 to turn off by default.
Note - It is assumed that when a user clicks on a link specifically, they want to read the whole tiddler. If you want tiddlers to appear shortened when they are clicked on, you will need to edit the ViewTemplate tiddler.
!Code
***/
//{{{
if(!version.extensions.BlogLayout)
{ //# ensure that the plugin is only installed once
version.extensions.BlogLayout = { installed: true }
};
(function($) { //set up alias for jQuery
config.macros.BlogLayout =
{
//*******collapseTiddlers variables********//
AUTO_SUMMARISE_FRONT_PAGE: true, //collapse all default tiddlers on first load (other tiddlers are unaffected)
MAX_HEIGHT: 200, //max height of tiddler content in pixels (default value)
//*******recentPosts variables*************//
POST_DISPLAY_COUNT: 5, //maximum number of posts to display
POST_TAG_NAME: "blog" //all posts that you want displayed in date order need to be tagged with this.
}
config.macros.BlogLayout.collapseMe = function(tiddlerRoot,defaultHeight)
//collapse tiddlerRoot
{
if (!store.getTiddler($(tiddlerRoot).attr("tiddler")))
{
return;
}
custHeight = store.getTiddler($(tiddlerRoot).attr("tiddler")).fields["collapseHeight"] || defaultHeight || this.MAX_HEIGHT;
customHeight = parseInt(custHeight);
//if the post is too big
if (($(tiddlerRoot).children('.viewer').height() > customHeight)&&(customHeight != -1))
{
//limit height of tiddler
$(tiddlerRoot).children('.viewer').css('overflow','hidden').css('height',customHeight);
//create a link
myLink = document.createElement("a");
myLink.href = "javascript:;";
myLink.onclick = function() {return config.macros.BlogLayout.expandClick(tiddlerRoot);};
myLink.innerHTML = "Read More...";
myLink.className = "button";
$("<div />").addClass('readMore').append(myLink).css("margin-top","3px").appendTo($(tiddlerRoot));
}
}
config.macros.BlogLayout.collapseTiddlers = function(defaultHeight)
//collapse all currently open tiddlers
{
$(".tiddler").each(
function() {
if(this.style.display == "none")
{
$(this).attr("collapseMeLater",(defaultHeight)?(defaultHeight+""):"null");
}
else
{
return config.macros.BlogLayout.collapseMe($(this),defaultHeight)
}
}
)
}
config.macros.BlogLayout.expandClick = function(tiddlerToExpand)
{
$(tiddlerToExpand).children(".readMore").css('display','none');
$(tiddlerToExpand).children(".viewer").css('overflow','visible').css('height','');
}
config.macros.BlogLayout.showNextTiddlers = function(clickedLink)
{
var divs = clickedLink.nextSibling;
$(clickedLink).hide();
$(clickedLink).remove();
var stopping = false;
while((!stopping)&&(divs))
{
$(divs).show();
if (divs.className == "showMorePosts")
{
stopping = true;
break;
}
else if ($(divs).attr("collapseMeLater"))
{
if ($(divs).attr("collapseMeLater") == "null")
{
this.collapseMe($(divs));
}
else
{
this.collapseMe($(divs),$(divs).attr("collapseMeLater"));
}
$(divs).removeAttr("collapseMeLater");
}
divs = divs.nextSibling;
}
}
config.macros.BlogLayout.recentTiddlersByTag = function(tagName,maxPosts)
//view all tiddlers with tagName by date order
{
story.closeAllTiddlers(); //clear screen ready for display
$(".showMorePosts").remove();
tiddlers = store.filterTiddlers("[tag["+tagName+"]][sort[-created]]");
var count = 0;
var currMax = maxPosts;
var justChanged = false;
while (count < tiddlers.length)
{
if (count == currMax)
{
$("<div />").addClass("showMorePosts").text("More Posts...").css("display","none").click(function(){return config.macros.BlogLayout.showNextTiddlers(this);}).appendTo("#tiddlerDisplay");
currMax += maxPosts;
}
story.displayTiddler("bottom",tiddlers[count].title,DEFAULT_VIEW_TEMPLATE,false,false);
if (count >= maxPosts)
{
//hide the tiddler
$(story.getTiddler(tiddlers[count].title)).css("display","none");
}
count += 1;
}
//hide all but the first More Posts...
if ($(".showMorePosts").length > 0)
{
$(".showMorePosts")[0].style.display = "block";
}
}
config.macros.BlogLayout.autoRecentTiddlers = function()
{
if(!window.location.hash)
{
this.recentTiddlersByTag(this.POST_TAG_NAME,this.POST_DISPLAY_COUNT);
}
}
config.shadowTiddlers['DefaultTiddlers'] = "[tag["+config.macros.BlogLayout+"]][sort[-created]]";
window.original_restart = window.restart;
window.restart = function()
{
window.original_restart();
if (config.macros.BlogLayout.POST_DISPLAY_COUNT != -1)
{
config.macros.BlogLayout.autoRecentTiddlers(); //call this to ensure number of posts is limited
}
if ((config.macros.BlogLayout.AUTO_SUMMARISE_FRONT_PAGE)&&(!window.location.hash))
{
$(document).ready(function() {config.macros.BlogLayout.collapseTiddlers()});
}
}
//$(document).ready(config.macros.BlogLayout.collapseTiddlers());
config.macros.setCollapseHeightHere ={
handler: function(place,macroName,params,wikifier,paramString,tiddler)
{
dontCollapse = params[0];
if (dontCollapse)
{
tiddler.fields['collapseHeight'] = -1;
}
else
{
tiddler.fields['collapseHeight'] = (place.clientHeight > 0)?(place.clientHeight - 4):(place.offsetHeight - 4);
tiddler.fields['collapseHeight'] += "";
}
}
}
config.macros.collapseThisTiddler ={
handler: function(place,macroName,params,wikifier,paramString,tiddler)
{
if ((params[0])&&(!tiddler.fields['collapseHeight']))
{
tiddler.fields['collapseHeight'] = params[0];
}
config.macros.BlogLayout.collapseMe($(story.getTiddler(tiddler.title)));
}
}
config.macros.BlogLayout.collapseRecentByTag = function(tagName,maxPosts,collapsePosts,defaultHeight)
{
this.recentTiddlersByTag(tagName,maxPosts);
if (collapsePosts)
{
this.collapseTiddlers(defaultHeight);
}
}
//params[0] = name of link
//params[1] = tagName
//params[2] = maxPosts
//params[3] = collapse posts. Values are true/false. default is true.
//params[4] = default value for collapsing posts by
config.macros.recentByTagLink ={
handler: function(place,macroName,params,wikifier,paramString,tiddler)
{
//check parameters supplied
var tagName, maxPosts, collapsePosts, linkName,defaultHeight;
tagName = params[1] || config.macros.BlogLayout.POST_TAG_NAME;
maxPosts = params[2] || config.macros.BlogLayout.POST_DISPLAY_COUNT;
collapsePosts = params[3] || (config.macros.BlogLayout.AUTO_SUMMARISE_FRONT_PAGE?1:0);
defaultHeight = params[4] || config.macros.BlogLayout.MAX_HEIGHT;
linkName = params[0] || tagName;
collapse = (collapsePosts == 1)?true:false;
var tagLink = document.createElement("a");
tagLink.href = "javascript:;";
tagLink.onclick = function() {return config.macros.BlogLayout.collapseRecentByTag(tagName,maxPosts,collapse,defaultHeight);};
tagLink.innerHTML = linkName;
$(place).append(tagLink);
}
}
})(jQuery)
config.shadowTiddlers.StylesheetBlogLayout = ".showMorePosts {margin: 5px 5px 20px 5px; cursor: pointer; width: 100%; text-align: center; border: 1px solid #c0c0c0; }\n" +
".readMore {}\n" +
".readMore .button {}";
store.addNotification("StylesheetBlogLayout",refreshStyles);
//}}}
''Update'' - These instructions have been superseded. Please see [[CGI and TiddlyWeb 1.0|http://bengillies.net/.a/recipes/sitecontent/tiddlers/CGI%20and%20TiddlyWeb%201.0]] for up to date instructions
Following on from my original post [[Running on TiddlyWeb, Part One]], there has recently been an update to the index.cgi file to make installing clearer and easier to understand. The new file can be found on github at http://github.com/tiddlyweb/tiddlyweb/blob/master/index.cgi. This simplifies the first stage of the install procedure to the following:
*Run [[this script|http://peak.telecommunity.com/dist/virtual-python.py]] from python.
*Run [[this script|http://peak.telecommunity.com/dist/ez_setup.py]] from the new instance of python created in your home directory.
*Type {{{easy_install -U tiddlyweb}}} into a command line and hit enter.
*TiddlyWeb should now be installed. Type {{{twanager instance <instance_name>}}} somewhere in your home directory.
*Copy [[index.cgi|http://github.com/tiddlyweb/tiddlyweb/blob/master/index.cgi]] to somewhere in your public_html folder.
*Move the tiddlywebconfig.py file from your instance to a different folder.
<<setCollapseHeightHere>>
That first part should be fairly similar to the old method of installing. The main difference comes in how you edit the index.cgi and tiddlywebconfig.py files. So from here, go into index.cgi and change {{{tiddlywebconfig_dir = <path to your tiddlywebconfig.py directory>}}}. Then change {{{os.environ['PYTHON_EGG_CACHE'] = <path to your ".python-eggs" folder>}}}. With virtual python, this is "~/.python-eggs". Also, make sure that the path to python at the top is the path to your virtual python install.
Save that and go into tiddlywebconfig.py. enter the following information:
{{{
'server_store': ['text', {'store_root': '<path to your instance>/store'}],
'server_prefix': '<path to your index.cgi file>',
'server_host': {
'scheme': 'http',
'host': '<your domain name here>',
'port': '80'},
}}}
Hopefully, this should make things simpler to understand, and easier to use. So from this point, continue the install as before to get tiddlyweb up and running.
<<closeAll>><<permaview>><<newTiddler>><<newJournal 'DD MMM YYYY'>><<saveChanges>><<slider chkSliderOptionsPanel OptionsPanel 'options »' 'Change TiddlyWiki advanced options'>>
/***
Inspired by k2
!General
***/
/*{{{*/
body {
background: #EDEDED;
}
#contentWrapper{
background: #fff;
border:1px solid #DDD;
margin: 0 auto;
width: 780px;
padding:0;
}
/*}}}*/
/***
!Links
***/
/*{{{*/
a,
a.tiddlyLink,
a.button,
a.externalLink,
#sidebarOptions .sliderPanel a{
color: #1D65BC;
text-decoration: none;
background: transparent;
border: 0;
}
a:hover,
a.tiddlyLink:hover,
a.button:hover,
a.externalLink:hover,
#sidebarOptions .sliderPanel a:hover
{
border: 0;
color: #1D65BC;
text-decoration: underline;
background:transparent;
}
.button:active {background:#1d65bc; border:0;}
.viewer .button:active, .viewer .marked, .viewer .highlight {
color: #fff !important;
background: #3371a3;
border: 0;
}
/*}}}*/
/***
!Header
***/
/*{{{*/
.gradient {margin-top:20px; background:#3371A3;}
.titleLine{padding: 80px 40px 32px 40px;}
.titleLine a:hover{color:#fff; border-bottom:1px dotted #eee; text-decoration:none;}
.titleLine a{color:#fff; border-bottom:1px dotted #ccc;}
.siteTitle {
font-size: 2.2em;
font-weight: bold;
color:#fff;
}
.siteSubtitle {
font-size: 1.0em;
display: block;
margin: .3em auto 1em;
color:#fff;
}
/*}}}*/
/***
!TopMenu
***/
/*{{{*/
#topMenu br {display:none; }
#topMenu { background: #3371A3; font-size:1em; }
#topMenu { padding:5px 32px; }
#topMenu .button, #topMenu .tiddlyLink, #topMenu .externalLink, #topMenu a{
margin-left:0.1em; margin-right:0.1em;
padding:0.5em;
color:white; font-weight:normal;
}
#topMenu a.button:hover, #topMenu a.tiddlyLink:hover, #topMenu .externalLink:hover, #topMenu a:hover{ background:#fff; color:#333; text-decoration:none;}
.searchBar {float:right; font-size:0.9em;}
.searchBar .button {display:block; border:none; color:#ccc;}
.searchBar .button:hover{border:none; color:#eee;}
.searchBar input{
border: 1px inset #1d65bc; background:#dbdee3;
}
.searchBar input:focus {
border: 1px inset #3371a3; background:#fff;
}
/*}}}*/
/***
!Display
***/
/***
!!!Display General
***/
/*{{{*/
#displayArea { margin: 0em 15.7em 0em 1em; }
#displayFooter {
clear: both;
}
#displayFooter {
clear: both;
}
#tiddlerDisplay{padding-top:1em;}
/*}}}*/
/***
!!!Tiddler
***/
/*{{{*/
.tiddler {margin-bottom:1em; padding-bottom:1em;}
.tiddler {padding-left:2em;}
.title {color:#333; font-size:1.8em; border-bottom:1px solid #333; padding-bottom:0.3px;}
.subtitle { font-size:90%; color:#bbb; padding-left:0.25em; margin-top:0.1em; }
.shadow .title {
color: #aaa;
}
h1,h2,h3,h4,h5 { color: #333; background: transparent; padding-bottom:2px; border-bottom: 1px dotted #666; }
* html .viewer pre {
margin-left: 0em;
}
.viewer hr {
border: 0;
border-top: solid 1px #333;
margin: 0 8em;
color: #333;
}
.viewer a.button {color:#000; border:1px solid #1D65BC; font-weight:bold;}
.viewer a.button:hover{color:#fff; background:#3371a3; text-decoration:none;}
.tagClear {clear:none;}
.toolbar .button {color:#bbb; border:none;}
.toolbar .button:hover, .toolbar .highlight, .toolbar .marked, .toolbar a.button:active {background:transparent; color:#111; border:none; text-decoration:underline;}
.tiddler {border-bottom:3px solid #EEF1F3; padding-bottom:2em; padding-top:0em;}
.title {border-bottom:none; margin-right:8em;}
h1,h2,h3,h4,h5 { color: #333; background: transparent; padding-bottom:2px; border-bottom: none; }
.viewer pre, .viewer code {
border: 1px solid #B2B6BE;
background: #EBEEF1;}
.tagging, .tagged {
border: 1px solid #dbdee3;
background-color: #ebeef1;
}
.selected .tagging, .selected .tagged {
background-color: #dbdee3;
border: 1px solid #B2B6BE;
}
.tagging .listTitle, .tagged .listTitle {
color: #bbb;
}
.selected .tagging .listTitle, .selected .tagged .listTitle {
color: #014;
}
.tagging .button:hover, .tagged .button:hover {
border: none; background:transparent; text-decoration:underline; color:#014;
}
.tagged .highlight, .tagged .marked, .tagged a.button:active {text-decoration:underline; background:transparent; color:#014;}
.tagging .button, .tagged .button {
color:#bbb;
}
.selected .tagging .button, .selected .tagged .button {
color:#014;
}
.viewer blockquote {
border-left:7px solid #ebeef1;
}
.viewer table {
border: 1px solid #3371a3;
}
.viewer th, thead td {
background: #3371a3;
border: 1px solid #3371a3;
color: #fff;
}
.viewer td, .viewer tr {
border: 1px solid #3371a3;
}
/*}}}*/
/***
!!!Editor
***/
/*{{{*/
* html .editor textarea, * html .editor input {
width: 98%;
}
.editor input, .editor textarea {
border: 1px solid #1d65bc; background:#ebeef1;
}
.editor {padding-top:0.3em;}
.editor textarea:focus, .editor input:focus {
border: 1px inset #3371a3; background:#fff;
}
/*}}}*/
/***
!Sidebar
***/
/*{{{*/
#sidebar{
position:relative;
float:right;
margin-bottom:1em;
display:inline;
width: 16em;
}
#sidebar .tabSelected, #sidebar .tabSected:hover {
color: #000;
background: #dbdee3;
border-top: solid 1px #B2B6BE;
border-left: solid 1px #B2B6BE;
border-right: solid 1px #B2B6BE;
border-bottom:solid 1px #dbdee3 !important;
padding-bottom:1px;
text-decoration:none;
}
#sidebarOptions, #sidebarTabs {border-left: 1px solid #B2B6BE;}
#sidebarTabs {border-bottom: 1px solid #B2B6BE;}
#sidebar .tabUnselected, #sidebar .tabUnselected:hover {
color: #F0F3F5;
background: #B2B6BE ;
border: solid 1px #B2B6BE ;
padding-bottom:1px;
}
#sidebarTabs .tabContents {border:none; background:#DBDEE3; }
#sidebarTabs .tabContents {border-top:1px solid #B2B6BE;}
#sidebarTabs .tabContents .tabContents {border-left:1px solid #b2b6be;}
#sidebarOptions .sliderPanel {
background: #EBEEF1; border:none;
}
#sidebarOptions input {
border: 1px solid #1d65bc;
}
#sidebarOptions input:hover, #sidebarOptions input:active, #sidebarOptions input:focus {
border: 1px inset #3371a3;
}
#sidebar {background: #EBEEF1 ; right:0;}
#sidebar .button:active, #sidebar .marked, #sidebar .highlight {color:#014; background:transparent;text-decoration:none}
/*}}}*/
/***
!!Popups
***/
/*{{{*/
.popup {
background: #3371a3;
border: 1px solid #333;
}
.popup hr {
color: #333;
background: #333;
border-bottom: 1px;
}
.popup li.disabled {
color: #333;
}
.popup li a, .popup li a:visited {
color: #eee;
border: none;
}
.popup li a:hover {
background: #3371a3;
color: #fff;
border: none;
text-decoration:underline;
}
/*}}}*/
/***
!!Message Area
***/
/*{{{*/
#messageArea {
border: 2px dashed #3371a3;
background: #dbdee3;
color: #fff;
font-size:90%;
}
#messageArea .button {
color: #1d65bc;
background: #ebeef1;
text-decoration:none;
font-weight:bold;
border:none;
}
#messageArea a.button {color:#1d65bc;}
#messageArea .button:hover {text-decoration:underline;}
/*}}}*/
/***
!!Tabs
***/
/*{{{*/
.viewer .tabSelected, .viewer .tabSelected:hover{
color: #014;
background: #eee;
border-left: 1px solid #B2B6BE;
border-top: 1px solid #B2B6BE;
border-right: 1px solid #B2B6BE;
}
.viewer .tabUnselected, .viewer .tabUnselected:hover {
color: #fff;
background: #B2B6BE;
}
. viewer .tabContents {
color: #014;
background: #ebeef1;
border: 1px solid #B2B6BE;
}
/*}}}*/
.blog h2, .blog h3, .blog h4{
margin:0;
padding:0;
border-bottom:none;
}
.blog {margin-left:1.5em;}
.blog .excerpt {
margin:0;
margin-top:0.3em;
padding: 0;
margin-left:1em;
padding-left:1em;
font-size:90%;
border-left:1px solid #ddd;
}
#tiddlerWhatsNew h1, #tiddlerWhatsNew h2 {border-bottom:none;}
div[tags~="RecentUpdates"], div[tags~="lewcidExtension"] {margin-bottom: 2em;}
#topMenu .fontResizer {float:right;}
#topMenu .fontResizer .button{border:1px solid #3371A3;}
#topMenu .fontResizer .button:hover {border:1px solid #fff; color:#3371A3;}
#sidebarTabs .txtMainTab .tiddlyLinkExisting {
font-weight: normal;
font-style: normal;
}
#sidebarTabs .txtMoreTab .tiddlyLinkExisting {
font-weight: bold;
font-style: normal;
}
<!--{{{-->
<link rel='alternate' type='application/rss+xml' title='RSS' href='.a/bags/blog/tiddlers.atom?select=tag:blog;sort=-created;' />
<link href="http://openid.bengillies.net/" rel="openid.server"/>
<link href="http://openid.bengillies.net/" rel="openid.delegate"/>
<!--}}}-->
Recently, I've been looking at TiddlyWeb with a view to securing content and protecting from cross site scripting attacks. Luckily, this is all pretty simple in TiddlyWeb, which has the concept of Validators built right in and ready to extend at will.
Validators are essentially run on any tiddler that has been PUT to a bag by a user without "accept" permission. By "accept" permission, I mean that the accept field in the policy file for that bag has been set, but does not contain their username or role. You can have any number of validators running, and load in more by using the standard TiddlyWeb plugin model.
So, onto my Validators. I have written 2 validators: One for generic HTML, and one specifically for TiddlyWebWiki.
<<setCollapseHeightHere>>
The HTML Validator is based on the Beautiful Soup package for Python, and works by providing a whitelist of allowed HTML tags and attributes, and simply removing anything that doesn't match it. The code is rather simple (thanks to Beautiful Soup), and has been taken and modified slightly from [[here|http://www.djangosnippets.org/snippets/205/]]. To use it, add or remove any tags/attributes as required from the two lists in the plugin, and add to system_plugins in tiddlywebconfig.py as usual.
The TiddlyWebWiki validator, as you might expect, is even simpler. All it does is remove the systemConfig tag (if present) and reject any tiddlers whose name is in a blacklist (MarkupPreHead for example). Again, you can add to this list as necessary.
That's it, hopefully you'll find one or the other useful.
Both plugins are available on my [[GitHub|http://github.com/bengillies/]] account.
[[html_validator.py|http://github.com/bengillies/TiddlyWeb-Plugins/blob/1f76489fe677e813bfcd682bf9b745dc2c9683ac/validators/html_validator.py]] - validate incoming tiddlers against a whitelist of allowed tags/attributes
[[tiddlywiki_validator.py|http://github.com/bengillies/TiddlyWeb-Plugins/blob/1f76489fe677e813bfcd682bf9b745dc2c9683ac/validators/tiddlywiki_validator.py]] - validate incoming tiddlers against a blacklist of tiddler titles and remove systemConfig tags
I have a Delicious account where you can see all the links that I find interesting, useful, or both.
http://delicious.com/bengillies
Now that TiddlyWeb has hit the 1.0 milestone, I figured it was about time to blog the latest and greatest way to get it running using cgi. I've simplified the instructions somewhat so that, apart from editing a few config files, and changing the paths to your specific paths, you should be able to copy/paste most of it.
Just one final note before we get going, these instructions are for Linux/*nix systems only, so they should work on most versions of Linux, Mac OSX, BSD, etc. If you are using a Windows system, you probably want the [[Installing TiddlyWeb on Windows|http://tiddlyweb.peermore.com/wiki/recipes/docs/tiddlers/Installing%20on%20Windows]] instructions.
!Installation
Download virtualenv-x.x.x.tar.gz (replace x.x.x with the latest version number) from http://pypi.python.org/pypi/virtualenv and put it into your home directory. Then, from you home directory, do the following:
{{{
tar -xvzf virtualenv-x.x.x.tar.gz
cp virtualenv-x.x.x/virtualenv.py ~
python virtualenv.py ~
source bin/activate
easy_install -U pip
pip install -U virtualenv
pip install -U tiddlywebwiki
twinstance <instance_name>
mkdir .python-eggs
mkdir twconfig
mv <instance_name>/tiddlywebconfig.py twconfig/tiddlywebconfig.py
touch twconfig/tiddlyweb.log
wget http://github.com/tiddlyweb/tiddlyweb/raw/master/index.cgi
chmod +x index.cgi
mv index.cgi public_html/cgi-bin/
}}}
!Configuration
Nano is a simple text editor that you can use to make some changes to index.cgi. Feel free to use whatever you like, though bear in mind that nano is probably the easiest to start off with.
{{{
cd public_html/cgi-bin
nano index.cgi
}}}
So, change the line at the top to point to your local version of python (type in "which python" to find this out). It should end up similar to the following:
{{{
#!/home/bengillies/bin/python
}}}
Also, change the lines that look like:
{{{
tiddlywebconfig_dir = '/tmp'
os.environ['PYTHON_EGG_CACHE'] = '/tmp'
}}}
To something like (replacing the start with your home directory:
{{{
tiddlywebconfig_dir = '/home/bengillies/twconfig/'
os.environ['PYTHON_EGG_CACHE'] = '/home/bengillies/.python-eggs'
}}}
You can save with ctrl-o, and then exit with ctrl-x (assuming you're using nano). Now, we need to alter the tiddlywebconfig.py file to finish things off:
{{{
cd ~/twconfig
nano tiddlywebconfig.py
}}}
Go down to the last but one line (the one before the closing }), and start a new line after it. Enter information so the the file looks like this:
{{{
# A basic configuration.
# Run "pydoc tiddlyweb.config" for details on configuration items.
config = {
'system_plugins': ['tiddlywebwiki'],
'secret': 'f433fbfcf1871ea99ddb75e97ff5512f5513414d', #this string will be different in your file. Just leave it as it is.
'twanager_plugins': ['tiddlywebwiki'],
'server_store': ['text', {'store_root': '/home/bengillies/<instance_name>/store'}],
'server_prefix': '/cgi-bin/index.cgi',
'server_host': {
'scheme': 'http',
'host': '<your domain name here>',
'port': '80'
},
}
}}}
That's it. You should now be able to point your web browser to http://your_domain.com/cgi-bin/index.cgi/recipes/default/tiddlers.wiki and start editing.
For more instructions on how to customise your tiddlyweb installation, it's probably best to look directly on the TiddlyWeb site at http://tiddlyweb.com
Since I announced TiddlyWebPages, I've been meaning to write a quick tutorial explaining how to make templates and define custom URLs for use on your site. In this post, I'll just cover templates and save URLs for another day. So let's start off at the point where you've installed TiddlyWeb, you have some content in your store, and you've installed TiddlyWebPages.
Right, the first step is to create a bag called templates and a bag called urls. Within the templates bag, you'll need to create a "wrapper" template. This will be the wrapper for all your content, so you can add links to your CSS definitions and Javascript files (or anything else) here without needing to include such things in every template. It needs to be called "Default" as it's the first one (and you need some sort of default right?) and while you can add other wrappers, we're not going into that quite yet. For now though, get the Default tiddler in the TiddlyWebPages GitHub directory at [[http://github.com/bengillies/TiddlyWeb-Plugins/raw/master/tw_pages/Default.tid]] and put it into your templates bag. You'll notice a couple of extra fields. list_tiddlers lets you specify the default template to use with a list of tiddlers (be it a bag, recipe or search results) while single_tiddlers does the same thing but for displaying a single tiddler only.
!Your First Template
Now that's done, you can start adding templates. Now, as templates are all tiddlers themselves, you may want to just copy some from somebody else (my [[templates|/.a/bags/templates/tiddlers]] for example), but as this post is entitled "How to make a template", we'll assume you want to start from scratch.
Create a tiddler in the templates bag entitled "List". This will simply list all tiddlers in the bag/recipe that you are in, showing both the title, and the text. Next, enter the following information into the body of the tiddler:
{{{
{% for tiddler in base %}
<h1>{{tiddler.title}}</h1>
{{tiddler.text}}
{% endfor %}
}}}
Let's take a look at that line by line shall we?
{{{
{% for tiddler in base %}
}}}
This line starts a loop over all tiddlers, giving each tiddler in the list (called base, short for "base tiddlers") a name that you can refer to it with of "tiddler". "base" is a value that TiddlyWebPages provides you with to access the base set of tiddlers in the bag/recipe.
You can add other tiddlers, pre-formatted with another template, by specifying the template/recipe combination as a field in the template you want to include it in. For example, let's say you have created a "Header" template already and want to include it in your "List" template. Let's say that the tiddler that defines your header, is returned by the "my_header" recipe. To do that, you'd add a field to the "List" template specifying the "Header" template and the "my_header" recipe:
{{{
Header: my_recipe?select=title:ListHeading
}}}
In this definition, "Header" is the title of the field, and specifies the template you want to use, while "my_recipe" specifies the recipe that you want to use. Anything after the question mark is run as a filter on the recipe. In this case, we are selecting the ListHeading tiddler from the recipe. The ability to filter recipes is especially useful for sorting tiddlers, as recipes cannot sort by default.
Adding this Header into the template, would produce a result as follows:
{{{
{{extra['Header']}}
{% for tiddler in base %}
<h1>{{tiddler.title}}</h1>
{{tiddler.text}}
{% endfor %}
}}}
Notice the new variable called "extra". This contains the HTML for any extra templates you have specified in the tiddler's "fields" (as above). This gives two main variables that you can use: base; and extra. Other variables are: prefix - any server prefix you have defined in tiddlywebconfig.py; query - a key/value list of all items in the query string of the url; root_vars - a key/value list of any patterns that you have specified in your URL (by default, this will contain the tiddler name, in root_vars['tiddler'], the recipe name in root_vars['recipe'] and the bag name in root_vars['bag']).
{{{
<h1>{{tiddler.title}}</h1>
}}}
You can put HTML wherever you like, which can form the basis of your template. The tiddler.title is enclosed in double braces - this tells TiddlyWebPages (actually the Jinja2 templating engine) that tiddler.title is a variable and you want to write its value into the page. The {% ... %} pattern tells Jinja2 that you want to run something without writing the output to the page. In the example above, this would be the for loop.
{{{
{{tiddler.text}}
{% endfor %}
}}}
These lines just output the text of the tiddler and close the for loop. Optionally, you can choose to wikify the text by passing it through a filter and specifying a recipe name to use when creating links. If you did this with the recipe "sitecontent" (the recipe on my site that contains all my tiddlers) then you'd end up with the following:
{{{
{{tiddler.text|wikifier('sitecontent')}}
}}}
In addition to all the standard [[Jinja filters|http://jinja.pocoo.org/2/documentation/templates#builtin-filters]] and the wikifier, there is another filter called "shorten" that you can use to safely shorten HTML without worrying about cutting off closing tags.
Now that you've done that, you'll want to test it. So go to the tiddlers in one of your bags, and append ".List" to the end of tiddlers to see the result. You can try this on my site by going to http://bengillies.net/.a/recipes/blog/tiddlers.ContentList (my template is a bit more complicated though).
Finally, please read through the Jinja2 documentation to get a better idea of what you can accomplish. You can find it at http://jinja.pocoo.org/2/documentation/templates
You can also view my Templates at http://bengillies.net/.a/bags/templates/tiddlers If you want to use any of them yourself.