Welcome to TiddlyWiki created by Jeremy Ruston, Copyright © 2007 UnaMesa Association
/***
| ''Name:''|CalendarPlugin|
| ''Description:''|Display monthly and yearly calendars.|
| ''Version:''|1.5.1.1 (modified from 1.5.1 for personal use)|
| ''Last Modified:''|27 January 2012 (by ~Secret-HQ)|
| ''Author:''|Eric Shulman<br>Original plugin authored by ~SteveRumsby<br>Modified by Scott Simmons (~Secret-HQ) for personal use|
| ''Shadow Tiddlers:''|[[CalendarPlugin_StyleSheet]]<br>[[CalendarPluginConfig]]|
| ''Requires:''|no additional tiddlers (self-contained)|
| ''Core Version:''|2.1|
| ''Source:''|[[http://www.TiddlyTools.com/#CalendarPlugin|http://www.TiddlyTools.com/#CalendarPlugin]]|
| ''License:''|unknown|
''NOTE:''
For //enhanced// date popup display, optionally install:
*[[DatePlugin]]
*[[ReminderMacros|http://remindermacros.tiddlyspot.com/]]
/***
!Usage
|{{{<<calendar>>}}}|full-year calendar for the current year|
|{{{<<calendar year>>}}}|full-year calendar for the specified year|
|{{{<<calendar year month>>}}}|one month calendar for the specified month and year|
|{{{<<calendar thismonth>>}}}|one month calendar for the current month|
|{{{<<calendar lastmonth>>}}}|one month calendar for last month|
|{{{<<calendar nextmonth>>}}}|one month calendar for next month|
|{{{<<calendar +n>>}}}<br>{{{<<calendar -n>>}}}|one month calendar for a month +/- 'n' months from now|
***/
/***
!!Configuration:
|''First day of week:''<br>{{{config.options.txtCalFirstDay}}}|<<option txtCalFirstDay>>|(Monday = 0, Sunday = 6)|
|''First day of weekend:''<br>{{{config.options.txtCalStartOfWeekend}}}|<<option txtCalStartOfWeekend>>|(Monday = 0, Sunday = 6)|
<<option chkDisplayWeekNumbers>> Display week numbers //(note: Monday will be used as the start of the week)//
|''Week number display format:''<br>{{{config.options.txtWeekNumberDisplayFormat }}}|<<option txtWeekNumberDisplayFormat >>|
|''Week number link format:''<br>{{{config.options.txtWeekNumberLinkFormat }}}|<<option txtWeekNumberLinkFormat >>|
Access additional configuration options by opening the CalendarPluginConfig shadow tiddler, saving it, and tagging it {{{systemConfig}}}.
***/
/***
!Version
***/
//{{{
version.extensions.CalendarPlugin = { major:1, minor:5, revision:1, date:new Date(2011,1,4), source:"http://www.TiddlyTools.com/#CalendarPlugin" };
//}}}
/***
!!History
|altRows|k
| date|version|changes|h
| 27 January 2012|1.5.1.1|@@color(RED):(~Secret-HQ)@@<br>Merged new version (1.5.1) from ~TiddlyTools with ~Secret-HQ tweaks from earlier versions.<br>Cleaned up and reformatted history and documentation.<br>Created a shadow tiddler with default CalendarPluginConfig values for overriding the default settings of CalendarPlugin.<br>Added user agent detection to determine whether CalendarPlugin_StyleSheet should include "display: block" (for IE) or exclude it (for other browsers).|
| 4 January 2011|1.5.1|Corrected parameter handling for {{{<<calendar year>>}}} to show entire year instead of just first month.<br>In {{{createCalendarMonthHeader()}}}, fixed next/previous month year calculation (using {{{parseInt()}}} to convert to numeric value).<br>Code reduction (setting options).|
| 22 August 2011|1.5.0.2|@@color(RED):(~Secret-HQ)@@<br>Moved styles into a shadow tiddler; the 100% width wasn't working after updating the ~TiddlyWiki core to version 2.6.4.|
| 7 January 2011|1.5.0.1|@@color(RED):(~Secret-HQ)@@<br>Tweaked styles to make the calendar scalable-width in the sidebar.|
| 31 April 2009|1.5.0|Rewrote {{{onClickCalendarDate()}}} (popup handler) and added {{{config.options.txtCalendarReminderTags}}}.<br>Partial code reduction/cleanup.<br>Assigned true version number (1.5.0).|
| 10 September 2008|--|Added {{{+n}}} (and {{{-n}}}) param to permit display of relative months. (E.g., {{{+6}}} means //six months from now//; {{{-3}}} means //three months ago//. Based on suggestion from Jean.|
| 17 June 2008|--|Added support for {{{config.macros.calendar.todaybg}}}.|
| 27 February 2008|--|In {{{handler()}}}, DON'T set hard-coded default date format, so that //customized// value (pre-defined in {{{config.macros.calendar.journalDateFmt}}}) is used.|
| 17 February 2008|--|In {{{createCalendarYear()}}}, fixed next/previous year calculation (using {{{parseInt()}}} to convert to numeric value).<br>Also, used {{{journalDateFmt}}} for date linking when NOT using [[DatePlugin]].|
| 16 February 2008|--|In {{{createCalendarDay()}}}, week numbers now created as ~TiddlyLinks, allowing quick creation/navigation to 'weekly' journals (based on request from Kashgarinn).|
| 8 January 2008|--|In {{{createCalendarMonthHeader()}}}, 'month year' heading is now created as ~TiddlyLink, allowing quick creation/navigation to 'month-at-a-time' journals.|
| 30 November 2007|--|Added {{{return false}}} to onclick handlers (to prevent IE from opening blank pages).|
| 23 August 2006|--|Added handling for weeknumbers (code supplied by Martin Budden. (See ''wn**'' comment marks.)<br>Also, incorporated update by Jeremy Sheeley to add caching for reminders. (See [[ReminderMacros]], if installed.)|
| 30 October 2005|--|In {{{config.macros.calendar.handler()}}}, used {{{tbody}}} element for IE compatibility.<br>Also, fixed year calculation for IE's {{{getYear()}}} function (which returns "2005" instead of "105").<br>Also, in {{{createCalendarDays()}}}, use {{{showDate()}}} function (see [[DatePlugin]], if installed) to render auto-styled date with linked popup. Updated calendar stylesheet definition to use {{{.calendar}}} class-specific selectors and added text centering and margin settings.|
| 29 May 2006|--|Added {{{journalDateFmt}}} handling.|
***/
/***
!Code
***/
//{{{
// COOKIE OPTIONS
var opts={
txtCalFirstDay: 0,
txtCalStartOfWeekend: 5,
chkDisplayWeekNumbers: false,
txtCalFirstDay: 0,
txtWeekNumberDisplayFormat: 'w0WW',
txtWeekNumberLinkFormat: 'YYYY-w0WW',
txtCalendarReminderTags: 'reminder'
};
for (var id in opts) if (config.options[id]===undefined) config.options[id]=opts[id];
// INTERNAL CONFIGURATION
config.macros.calendar = {
monthnames:['Jan','Feb','Mar','Apr','May','Jun','Jul','Aug','Sep','Oct','Nov','Dec'],
daynames:['M','T','W','T','F','S','S'],
todaybg:'#ccccff',
weekendbg:'#c0c0c0',
monthbg:'#e0e0e0',
holidaybg:'#ffc0c0',
journalDateFmt:'DD MMM YYYY',
monthdays:[31,28,31,30,31,30,31,31,30,31,30,31],
holidays:[ ] // for customization see [[CalendarPluginConfig]]
};
//}}}
//{{{
function calendarIsHoliday(date)
{
var longHoliday = date.formatString('0DD/0MM/YYYY');
var shortHoliday = date.formatString('0DD/0MM');
for(var i = 0; i < config.macros.calendar.holidays.length; i++) {
if( config.macros.calendar.holidays[i]==longHoliday
|| config.macros.calendar.holidays[i]==shortHoliday)
return true;
}
return false;
}
//}}}
//{{{
config.macros.calendar.handler = function(place,macroName,params) {
var calendar = createTiddlyElement(place, 'table', null, 'calendar', null);
var tbody = createTiddlyElement(calendar, 'tbody');
var today = new Date();
var year = today.getYear();
if (year<1900) year+=1900;
// get journal format from SideBarOptions (ELS 5/29/06 - suggested by MartinBudden)
var text = store.getTiddlerText('SideBarOptions');
var re = new RegExp('<<(?:newJournal)([^>]*)>>','mg'); var fm = re.exec(text);
if (fm && fm[1]!=null) { var pa=fm[1].readMacroParams(); if (pa[0]) this.journalDateFmt = pa[0]; }
var month=-1;
if (params[0] == 'thismonth') {
var month=today.getMonth();
} else if (params[0] == 'lastmonth') {
var month = today.getMonth()-1; if (month==-1) { month=11; year--; }
} else if (params[0] == 'nextmonth') {
var month = today.getMonth()+1; if (month>11) { month=0; year++; }
} else if (params[0]&&'+-'.indexOf(params[0].substr(0,1))!=-1) {
var month = today.getMonth()+parseInt(params[0]);
if (month>11) { year+=Math.floor(month/12); month%=12; };
if (month<0) { year+=Math.floor(month/12); month=12+month%12; }
} else if (params[0]) {
year = params[0];
if(params[1]) {
month=parseInt(params[1])-1;
if (month>11) month=11; if (month<0) month=0;
}
}
if (month!=-1) {
cacheReminders(new Date(year, month, 1, 0, 0), 31);
createCalendarOneMonth(tbody, year, month);
} else {
cacheReminders(new Date(year, 0, 1, 0, 0), 366);
createCalendarYear(tbody, year);
}
window.reminderCacheForCalendar = null;
}
//}}}
//{{{
// cache used to store reminders while the calendar is being rendered
// it will be renulled after the calendar is fully rendered.
window.reminderCacheForCalendar = null;
//}}}
//{{{
function cacheReminders(date, leadtime)
{
if (window.findTiddlersWithReminders == null) return;
window.reminderCacheForCalendar = {};
var leadtimeHash = [];
leadtimeHash [0] = 0;
leadtimeHash [1] = leadtime;
var t = findTiddlersWithReminders(date, leadtimeHash, null, 1);
for(var i = 0; i < t.length; i++) {
//just tag it in the cache, so that when we're drawing days, we can bold this one.
window.reminderCacheForCalendar[t[i]['matchedDate']] = 'reminder:' + t[i]['params']['title'];
}
}
//}}}
//{{{
function createCalendarOneMonth(calendar, year, mon)
{
var row = createTiddlyElement(calendar, 'tr');
createCalendarMonthHeader(calendar, row, config.macros.calendar.monthnames[mon]+' '+year, true, year, mon);
row = createTiddlyElement(calendar, 'tr');
createCalendarDayHeader(row, 1);
createCalendarDayRowsSingle(calendar, year, mon);
}
//}}}
//{{{
function createCalendarMonth(calendar, year, mon)
{
var row = createTiddlyElement(calendar, 'tr');
createCalendarMonthHeader(calendar, row, config.macros.calendar.monthnames[mon]+' '+ year, false, year, mon);
row = createTiddlyElement(calendar, 'tr');
createCalendarDayHeader(row, 1);
createCalendarDayRowsSingle(calendar, year, mon);
}
//}}}
//{{{
function createCalendarYear(calendar, year)
{
var row;
row = createTiddlyElement(calendar, 'tr');
var back = createTiddlyElement(row, 'td');
var backHandler = function() {
removeChildren(calendar);
createCalendarYear(calendar, parseInt(year)-1);
return false; // consume click
};
createTiddlyButton(back, '<', 'Previous year', backHandler);
back.align = 'center';
var yearHeader = createTiddlyElement(row, 'td', null, 'calendarYear', year);
yearHeader.align = 'center';
yearHeader.setAttribute('colSpan',config.options.chkDisplayWeekNumbers?22:19);//wn**
var fwd = createTiddlyElement(row, 'td');
var fwdHandler = function() {
removeChildren(calendar);
createCalendarYear(calendar, parseInt(year)+1);
return false; // consume click
};
createTiddlyButton(fwd, '>', 'Next year', fwdHandler);
fwd.align = 'center';
createCalendarMonthRow(calendar, year, 0);
createCalendarMonthRow(calendar, year, 3);
createCalendarMonthRow(calendar, year, 6);
createCalendarMonthRow(calendar, year, 9);
}
//}}}
//{{{
function createCalendarMonthRow(cal, year, mon)
{
var row = createTiddlyElement(cal, 'tr');
createCalendarMonthHeader(cal, row, config.macros.calendar.monthnames[mon], false, year, mon);
createCalendarMonthHeader(cal, row, config.macros.calendar.monthnames[mon+1], false, year, mon);
createCalendarMonthHeader(cal, row, config.macros.calendar.monthnames[mon+2], false, year, mon);
row = createTiddlyElement(cal, 'tr');
createCalendarDayHeader(row, 3);
createCalendarDayRows(cal, year, mon);
}
//}}}
//{{{
function createCalendarMonthHeader(cal, row, name, nav, year, mon)
{
var month;
if (nav) {
var back = createTiddlyElement(row, 'td');
back.align = 'center';
back.style.background = config.macros.calendar.monthbg;
var backMonHandler = function() {
var newyear = year;
var newmon = mon-1;
if(newmon == -1) { newmon = 11; newyear = parseInt(newyear)-1;}
removeChildren(cal);
cacheReminders(new Date(newyear, newmon , 1, 0, 0), 31);
createCalendarOneMonth(cal, newyear, newmon);
return false; // consume click
};
createTiddlyButton(back, '<', 'Previous month', backMonHandler);
month = createTiddlyElement(row, 'td', null, 'calendarMonthname')
createTiddlyLink(month,name,true);
month.setAttribute('colSpan', config.options.chkDisplayWeekNumbers?6:5);//wn**
var fwd = createTiddlyElement(row, 'td');
fwd.align = 'center';
fwd.style.background = config.macros.calendar.monthbg;
var fwdMonHandler = function() {
var newyear = year;
var newmon = mon+1;
if(newmon == 12) { newmon = 0; newyear = parseInt(newyear)+1;}
removeChildren(cal);
cacheReminders(new Date(newyear, newmon , 1, 0, 0), 31);
createCalendarOneMonth(cal, newyear, newmon);
return false; // consume click
};
createTiddlyButton(fwd, '>', 'Next month', fwdMonHandler);
} else {
month = createTiddlyElement(row, 'td', null, 'calendarMonthname', name)
month.setAttribute('colSpan',config.options.chkDisplayWeekNumbers?8:7);//wn**
}
month.align = 'center';
month.style.background = config.macros.calendar.monthbg;
}
//}}}
//{{{
function createCalendarDayHeader(row, num)
{
var cell;
for(var i = 0; i < num; i++) {
if (config.options.chkDisplayWeekNumbers) createTiddlyElement(row, 'td');//wn**
for(var j = 0; j < 7; j++) {
var d = j + (config.options.txtCalFirstDay - 0);
if(d > 6) d = d - 7;
cell = createTiddlyElement(row, 'td', null, null, config.macros.calendar.daynames[d]);
if(d == (config.options.txtCalStartOfWeekend-0) || d == (config.options.txtCalStartOfWeekend-0+1))
cell.style.background = config.macros.calendar.weekendbg;
}
}
}
//}}}
//{{{
function createCalendarDays(row, col, first, max, year, mon) {
var i;
if (config.options.chkDisplayWeekNumbers){
if (first<=max) {
var ww = new Date(year,mon,first);
var td=createTiddlyElement(row, 'td');//wn**
var link=createTiddlyLink(td,ww.formatString(config.options.txtWeekNumberLinkFormat),false);
link.appendChild(document.createTextNode(
ww.formatString(config.options.txtWeekNumberDisplayFormat)));
}
else createTiddlyElement(row, 'td');//wn**
}
for(i = 0; i < col; i++)
createTiddlyElement(row, 'td');
var day = first;
for(i = col; i < 7; i++) {
var d = i + (config.options.txtCalFirstDay - 0);
if(d > 6) d = d - 7;
var daycell = createTiddlyElement(row, 'td');
var isaWeekend=((d==(config.options.txtCalStartOfWeekend-0)
|| d==(config.options.txtCalStartOfWeekend-0+1))?true:false);
if(day > 0 && day <= max) {
var celldate = new Date(year, mon, day);
// ELS 10/30/05 - use <<date>> macro's showDate() function to create popup
// ELS 05/29/06 - use journalDateFmt
if (window.showDate) showDate(daycell,celldate,'popup','DD',
config.macros.calendar.journalDateFmt,true, isaWeekend);
else {
if(isaWeekend) daycell.style.background = config.macros.calendar.weekendbg;
var title = celldate.formatString(config.macros.calendar.journalDateFmt);
if(calendarIsHoliday(celldate))
daycell.style.background = config.macros.calendar.holidaybg;
var now=new Date();
if ((now-celldate>=0) && (now-celldate<86400000)) // is today?
daycell.style.background = config.macros.calendar.todaybg;
if(window.findTiddlersWithReminders == null) {
var link = createTiddlyLink(daycell, title, false);
link.appendChild(document.createTextNode(day));
} else
var button = createTiddlyButton(daycell, day, title, onClickCalendarDate);
}
}
day++;
}
}
//}}}
//{{{
// Create a pop-up containing:
// * a link to a tiddler for this date
// * a 'new tiddler' link to add a reminder for this date
// * links to current reminders for this date
// NOTE: this code is only used if [[ReminderMacros]] is installed AND [[DatePlugin]] is //not// installed.
function onClickCalendarDate(ev) { ev=ev||window.event;
var d=new Date(this.getAttribute('title')); var date=d.formatString(config.macros.calendar.journalDateFmt);
var p=Popup.create(this); if (!p) return;
createTiddlyLink(createTiddlyElement(p,'li'),date,true);
var rem='\\n\\<\\<reminder day:%0 month:%1 year:%2 title: \\>\\>';
rem=rem.format([d.getDate(),d.getMonth()+1,d.getYear()+1900]);
var cmd="<<newTiddler label:[[new reminder...]] prompt:[[add a new reminder to '%0']]"
+" title:[[%0]] text:{{store.getTiddlerText('%0','')+'%1'}} tag:%2>>";
wikify(cmd.format([date,rem,config.options.txtCalendarReminderTags]),p);
createTiddlyElement(p,'hr');
var t=findTiddlersWithReminders(d,[0,31],null,1);
for(var i=0; i<t.length; i++) {
var link=createTiddlyLink(createTiddlyElement(p,'li'), t[i].tiddler, false);
link.appendChild(document.createTextNode(t[i]['params']['title']));
}
Popup.show(); ev.cancelBubble=true; if (ev.stopPropagation) ev.stopPropagation(); return false;
}
//}}}
//{{{
function calendarMaxDays(year, mon)
{
var max = config.macros.calendar.monthdays[mon];
if(mon == 1 && (year % 4) == 0 && ((year % 100) != 0 || (year % 400) == 0)) max++;
return max;
}
//}}}
//{{{
function createCalendarDayRows(cal, year, mon)
{
var row = createTiddlyElement(cal, 'tr');
var first1 = (new Date(year, mon, 1)).getDay() -1 - (config.options.txtCalFirstDay-0);
if(first1 < 0) first1 = first1 + 7;
var day1 = -first1 + 1;
var first2 = (new Date(year, mon+1, 1)).getDay() -1 - (config.options.txtCalFirstDay-0);
if(first2 < 0) first2 = first2 + 7;
var day2 = -first2 + 1;
var first3 = (new Date(year, mon+2, 1)).getDay() -1 - (config.options.txtCalFirstDay-0);
if(first3 < 0) first3 = first3 + 7;
var day3 = -first3 + 1;
var max1 = calendarMaxDays(year, mon);
var max2 = calendarMaxDays(year, mon+1);
var max3 = calendarMaxDays(year, mon+2);
while(day1 <= max1 || day2 <= max2 || day3 <= max3) {
row = createTiddlyElement(cal, 'tr');
createCalendarDays(row, 0, day1, max1, year, mon); day1 += 7;
createCalendarDays(row, 0, day2, max2, year, mon+1); day2 += 7;
createCalendarDays(row, 0, day3, max3, year, mon+2); day3 += 7;
}
}
//}}}
//{{{
function createCalendarDayRowsSingle(cal, year, mon)
{
var row = createTiddlyElement(cal, 'tr');
var first1 = (new Date(year, mon, 1)).getDay() -1 - (config.options.txtCalFirstDay-0);
if(first1 < 0) first1 = first1+ 7;
var day1 = -first1 + 1;
var max1 = calendarMaxDays(year, mon);
while(day1 <= max1) {
row = createTiddlyElement(cal, 'tr');
createCalendarDays(row, 0, day1, max1, year, mon); day1 += 7;
}
}
//}}}
/***
!!Templates (Shadow Tiddlers)
***/
//{{{
config.shadowTiddlers["CalendarPluginConfig"] = "/***"+"\n"+"This tiddler overrides some of the default settings of CalendarPlugin and is shadowed in CalendarPlugin. To use it, edit this tiddler and add the {{{systemConfig}}} tag to it."+"\n"+"***/";
config.shadowTiddlers["CalendarPluginConfig"] += "\n"+"\n";
config.shadowTiddlers["CalendarPluginConfig"] += "/***"+"\n"+"''Override cookie settings:''"+"\n"+"***/"+"\n";
config.shadowTiddlers["CalendarPluginConfig"] += "//{{{"+"\n";
config.shadowTiddlers["CalendarPluginConfig"] += "config.options.txtCalFirstDay=6;"+"\n";
config.shadowTiddlers["CalendarPluginConfig"] += "config.options.txtCalStartOfWeekend=4;"+"\n";
config.shadowTiddlers["CalendarPluginConfig"] += "//}}}"+"\n";
config.shadowTiddlers["CalendarPluginConfig"] += "\n";
config.shadowTiddlers["CalendarPluginConfig"] += "/***"+"\n"+"''Override internal default settings:''"+"\n"+"***/"+"\n";
config.shadowTiddlers["CalendarPluginConfig"] += "//{{{"+"\n";
config.shadowTiddlers["CalendarPluginConfig"] += "config.macros.calendar.journalDateFmt=\"0DD MMM YYYY (DDD)\";"+"\n";
config.shadowTiddlers["CalendarPluginConfig"] += "//}}}"+"\n";
config.shadowTiddlers["CalendarPluginConfig"] += "\n";
config.shadowTiddlers["CalendarPluginConfig"] += "/***"+"\n"+"''Override fixed-date annual holidays:''"+"\n"+"***/"+"\n";
config.shadowTiddlers["CalendarPluginConfig"] += "//{{{"+"\n";
config.shadowTiddlers["CalendarPluginConfig"] += "config.macros.date = {"+"\n";
config.shadowTiddlers["CalendarPluginConfig"] += " holidays: [ '01/01', '02/14', '04/01', '05/01', '07/04', '10/31', '11/24', '12/24', '12/25', '12/26', '12/31' ]"+"\n";
config.shadowTiddlers["CalendarPluginConfig"] += " // 'New Year\\'s Day', 'Valentine\\'s Day', 'April Fool\\'s Day', 'May Day', 'Independence Day (U.S.)', 'Halloween', 'Thanksgiving (U.S.)', 'Christmas Eve', 'Christmas', 'Boxing Day', 'New Year\\'s Eve'"+"\n";
config.shadowTiddlers["CalendarPluginConfig"] += "};"+"\n";
config.shadowTiddlers["CalendarPluginConfig"] += "//}}}";
//}}}
/***
!!Styles
***/
//{{{
browserUserAgent = navigator.appName;
if (browserUserAgent == "Microsoft Internet Explorer") {
config.shadowTiddlers["CalendarPlugin_StyleSheet"] = "/*{{{*/"+"\n";
config.shadowTiddlers["CalendarPlugin_StyleSheet"] += ".calendar {"+"\n";
config.shadowTiddlers["CalendarPlugin_StyleSheet"] += " width:100%;"+"\n";
config.shadowTiddlers["CalendarPlugin_StyleSheet"] += "}"+"\n";
config.shadowTiddlers["CalendarPlugin_StyleSheet"] += "\n";
config.shadowTiddlers["CalendarPlugin_StyleSheet"] += ".calendar,"+"\n";
config.shadowTiddlers["CalendarPlugin_StyleSheet"] += ".calendar table,"+"\n";
config.shadowTiddlers["CalendarPlugin_StyleSheet"] += ".calendar th,"+"\n";
config.shadowTiddlers["CalendarPlugin_StyleSheet"] += ".calendar tr,"+"\n";
config.shadowTiddlers["CalendarPlugin_StyleSheet"] += ".calendar td {"+"\n";
config.shadowTiddlers["CalendarPlugin_StyleSheet"] += " display:block;"+"\n"; // only if userAgent = MSIE
config.shadowTiddlers["CalendarPlugin_StyleSheet"] += " text-align:center;"+"\n";
config.shadowTiddlers["CalendarPlugin_StyleSheet"] += "}"+"\n";
config.shadowTiddlers["CalendarPlugin_StyleSheet"] += "\n";
config.shadowTiddlers["CalendarPlugin_StyleSheet"] += ".calendar,"+"\n";
config.shadowTiddlers["CalendarPlugin_StyleSheet"] += ".calendar a {"+"\n";
config.shadowTiddlers["CalendarPlugin_StyleSheet"] += " margin:0px !important;"+"\n";
config.shadowTiddlers["CalendarPlugin_StyleSheet"] += " padding:0px !important;"+"\n";
config.shadowTiddlers["CalendarPlugin_StyleSheet"] += "}"+"\n";
config.shadowTiddlers["CalendarPlugin_StyleSheet"] += "\n";
config.shadowTiddlers["CalendarPlugin_StyleSheet"] += ".fixedWidthCalendar {"+"\n";
config.shadowTiddlers["CalendarPlugin_StyleSheet"] += " width:140px !important;"+"\n";
config.shadowTiddlers["CalendarPlugin_StyleSheet"] += "}"+"\n";
config.shadowTiddlers["CalendarPlugin_StyleSheet"] += "/*}}}*/";
}
else if (browserUserAgent != "Microsoft Internet Explorer") {
config.shadowTiddlers["CalendarPlugin_StyleSheet"] = "/*{{{*/"+"\n";
config.shadowTiddlers["CalendarPlugin_StyleSheet"] += ".calendar {"+"\n";
config.shadowTiddlers["CalendarPlugin_StyleSheet"] += " width:100%;"+"\n";
config.shadowTiddlers["CalendarPlugin_StyleSheet"] += "}"+"\n";
config.shadowTiddlers["CalendarPlugin_StyleSheet"] += "\n";
config.shadowTiddlers["CalendarPlugin_StyleSheet"] += ".calendar,"+"\n";
config.shadowTiddlers["CalendarPlugin_StyleSheet"] += ".calendar table,"+"\n";
config.shadowTiddlers["CalendarPlugin_StyleSheet"] += ".calendar th,"+"\n";
config.shadowTiddlers["CalendarPlugin_StyleSheet"] += ".calendar tr,"+"\n";
config.shadowTiddlers["CalendarPlugin_StyleSheet"] += ".calendar td {"+"\n";
config.shadowTiddlers["CalendarPlugin_StyleSheet"] += " text-align:center;"+"\n";
config.shadowTiddlers["CalendarPlugin_StyleSheet"] += "}"+"\n";
config.shadowTiddlers["CalendarPlugin_StyleSheet"] += "\n";
config.shadowTiddlers["CalendarPlugin_StyleSheet"] += ".calendar,"+"\n";
config.shadowTiddlers["CalendarPlugin_StyleSheet"] += ".calendar a {"+"\n";
config.shadowTiddlers["CalendarPlugin_StyleSheet"] += " margin:0px !important;"+"\n";
config.shadowTiddlers["CalendarPlugin_StyleSheet"] += " padding:0px !important;"+"\n";
config.shadowTiddlers["CalendarPlugin_StyleSheet"] += "}"+"\n";
config.shadowTiddlers["CalendarPlugin_StyleSheet"] += "\n";
config.shadowTiddlers["CalendarPlugin_StyleSheet"] += ".fixedWidthCalendar {"+"\n";
config.shadowTiddlers["CalendarPlugin_StyleSheet"] += " width:140px !important;"+"\n";
config.shadowTiddlers["CalendarPlugin_StyleSheet"] += "}"+"\n";
config.shadowTiddlers["CalendarPlugin_StyleSheet"] += "/*}}}*/";
}
//}}}
//{{{
store.addNotification("CalendarPlugin_StyleSheet",refreshStyles);
//}}}
/***
This tiddler overrides some of the default settings of CalendarPlugin and is shadowed in CalendarPlugin. To use it, edit this tiddler and add the {{{systemConfig}}} tag to it.
***/
/***
''Override cookie settings:''
***/
//{{{
config.options.txtCalFirstDay=6;
config.options.txtCalStartOfWeekend=4;
//}}}
/***
''Override internal default settings:''
***/
//{{{
config.macros.calendar.journalDateFmt="0DD MMM YYYY (DDD)";
//}}}
/***
''Override fixed-date annual holidays:''
***/
//{{{
config.macros.date = {
holidays: [ '01/01', '02/14', '04/01', '05/01', '07/04', '10/31', '11/24', '12/24', '12/25', '12/26', '12/31' ]
// 'New Year\'s Day', 'Valentine\'s Day', 'April Fool\'s Day', 'May Day', 'Independence Day (U.S.)', 'Halloween', 'Thanksgiving (U.S.)', 'Christmas Eve', 'Christmas', 'Boxing Day', 'New Year\'s Eve'
};
//}}}
/*{{{*/
.calendar {
width:100%;
}
.calendar,
.calendar table,
.calendar th,
.calendar tr,
.calendar td {
text-align:center;
}
.calendar,
.calendar a {
margin:0px !important;
padding:0px !important;
}
.fixedWidthCalendar {
width:140px !important;
}
/*}}}*/
/***
|Name|CheckboxPlugin|
|Source|http://www.TiddlyTools.com/#CheckboxPlugin|
|Documentation|http://www.TiddlyTools.com/#CheckboxPluginInfo|
|Version|2.4.0|
|Author|Eric Shulman|
|License|http://www.TiddlyTools.com/#LegalStatements|
|~CoreVersion|2.1|
|Type|plugin|
|Description|Add checkboxes to your tiddler content|
This plugin extends the TiddlyWiki syntax to allow definition of checkboxes that can be embedded directly in tiddler content. Checkbox states are preserved by:
* by setting/removing tags on specified tiddlers,
* or, by setting custom field values on specified tiddlers,
* or, by saving to a locally-stored cookie ID,
* or, automatically modifying the tiddler content (deprecated)
When an ID is assigned to the checkbox, it enables direct programmatic access to the checkbox DOM element, as well as creating an entry in TiddlyWiki's config.options[ID] internal data. In addition to tracking the checkbox state, you can also specify custom javascript for programmatic initialization and onClick event handling for any checkbox, so you can provide specialized side-effects in response to state changes.
!!!!!Documentation
>see [[CheckboxPluginInfo]]
!!!!!Revisions
<<<
2008.01.08 [*.*.*] plugin size reduction: documentation moved to [[CheckboxPluginInfo]]
2008.01.05 [2.4.0] set global "window.place" to current checkbox element when processing checkbox clicks. This allows init/beforeClick/afterClick handlers to reference RELATIVE elements, including using "story.findContainingTiddler(place)". Also, wrap handlers in "function()" so "return" can be used within handler code.
|please see [[CheckboxPluginInfo]] for additional revision details|
2005.12.07 [0.9.0] initial BETA release
<<<
!!!!!Code
***/
//{{{
version.extensions.CheckboxPlugin = {major: 2, minor: 4, revision:0 , date: new Date(2008,1,5)};
//}}}
//{{{
config.checkbox = { refresh: { tagged:true, tagging:true, container:true } };
config.formatters.push( {
name: "checkbox",
match: "\\[[xX_ ][\\]\\=\\(\\{]",
lookahead: "\\[([xX_ ])(=[^\\s\\(\\]{]+)?(\\([^\\)]*\\))?({[^}]*})?({[^}]*})?({[^}]*})?\\]",
handler: function(w) {
var lookaheadRegExp = new RegExp(this.lookahead,"mg");
lookaheadRegExp.lastIndex = w.matchStart;
var lookaheadMatch = lookaheadRegExp.exec(w.source)
if(lookaheadMatch && lookaheadMatch.index == w.matchStart) {
// get params
var checked=(lookaheadMatch[1].toUpperCase()=="X");
var id=lookaheadMatch[2];
var target=lookaheadMatch[3];
if (target) target=target.substr(1,target.length-2).trim(); // trim off parentheses
var fn_init=lookaheadMatch[4];
var fn_clickBefore=lookaheadMatch[5];
var fn_clickAfter=lookaheadMatch[6];
var tid=story.findContainingTiddler(w.output); if (tid) tid=tid.getAttribute("tiddler");
var srctid=w.tiddler?w.tiddler.title:null;
config.macros.checkbox.create(w.output,tid,srctid,w.matchStart+1,checked,id,target,config.checkbox.refresh,fn_init,fn_clickBefore,fn_clickAfter);
w.nextMatch = lookaheadMatch.index + lookaheadMatch[0].length;
}
}
} );
config.macros.checkbox = {
handler: function(place,macroName,params,wikifier,paramString,tiddler) {
if(!(tiddler instanceof Tiddler)) { // if no tiddler passed in try to find one
var here=story.findContainingTiddler(place);
if (here) tiddler=store.getTiddler(here.getAttribute("tiddler"))
}
var srcpos=0; // "inline X" not applicable to macro syntax
var target=params.shift(); if (!target) target="";
var defaultState=params[0]=="checked"; if (defaultState) params.shift();
var id=params.shift(); if (id && !id.length) id=null;
var fn_init=params.shift(); if (fn_init && !fn_init.length) fn_init=null;
var fn_clickBefore=params.shift();
if (fn_clickBefore && !fn_clickBefore.length) fn_clickBefore=null;
var fn_clickAfter=params.shift();
if (fn_clickAfter && !fn_clickAfter.length) fn_clickAfter=null;
var refresh={ tagged:true, tagging:true, container:false };
this.create(place,tiddler.title,tiddler.title,0,defaultState,id,target,refresh,fn_init,fn_clickBefore,fn_clickAfter);
},
create: function(place,tid,srctid,srcpos,defaultState,id,target,refresh,fn_init,fn_clickBefore,fn_clickAfter) {
// create checkbox element
var c = document.createElement("input");
c.setAttribute("type","checkbox");
c.onclick=this.onClickCheckbox;
c.srctid=srctid; // remember source tiddler
c.srcpos=srcpos; // remember location of "X"
c.container=tid; // containing tiddler (may be null if not in a tiddler)
c.tiddler=tid; // default target tiddler
c.refresh = {};
c.refresh.container = refresh.container;
c.refresh.tagged = refresh.tagged;
c.refresh.tagging = refresh.tagging;
place.appendChild(c);
// set default state
c.checked=defaultState;
// track state in config.options.ID
if (id) {
c.id=id.substr(1); // trim off leading "="
if (config.options[c.id]!=undefined)
c.checked=config.options[c.id];
else
config.options[c.id]=c.checked;
}
// track state in (tiddlername|tagname) or (fieldname@tiddlername)
if (target) {
var pos=target.indexOf("@");
if (pos!=-1) {
c.field=pos?target.substr(0,pos):"checked"; // get fieldname (or use default "checked")
c.tiddler=target.substr(pos+1); // get specified tiddler name (if any)
if (!c.tiddler || !c.tiddler.length) c.tiddler=tid; // if tiddler not specified, default == container
if (store.getValue(c.tiddler,c.field)!=undefined)
c.checked=(store.getValue(c.tiddler,c.field)=="true"); // set checkbox from saved state
} else {
var pos=target.indexOf("|"); if (pos==-1) var pos=target.indexOf(":");
c.tag=target;
if (pos==0) c.tag=target.substr(1); // trim leading "|" or ":"
if (pos>0) { c.tiddler=target.substr(0,pos); c.tag=target.substr(pos+1); }
if (!c.tag.length) c.tag="checked";
var t=store.getTiddler(c.tiddler);
if (t && t.tags)
c.checked=t.isTagged(c.tag); // set checkbox from saved state
}
}
// trim off surrounding { and } delimiters from init/click handlers
if (fn_init) c.fn_init="(function(){"+fn_init.trim().substr(1,fn_init.length-2)+"})()";
if (fn_clickBefore) c.fn_clickBefore="(function(){"+fn_clickBefore.trim().substr(1,fn_clickBefore.length-2)+"})()";
if (fn_clickAfter) c.fn_clickAfter="(function(){"+fn_clickAfter.trim().substr(1,fn_clickAfter.length-2)+"})()";
c.init=true; c.onclick(); c.init=false; // compute initial state and save in tiddler/config/cookie
},
onClickCheckbox: function(event) {
window.place=this;
if (this.init && this.fn_init) // custom function hook to set initial state (run only once)
{ try { eval(this.fn_init); } catch(e) { displayMessage("Checkbox init error: "+e.toString()); } }
if (!this.init && this.fn_clickBefore) // custom function hook to override changes in checkbox state
{ try { eval(this.fn_clickBefore) } catch(e) { displayMessage("Checkbox onClickBefore error: "+e.toString()); } }
if (this.id)
// save state in config AND cookie (only when ID starts with 'chk')
{ config.options[this.id]=this.checked; if (this.id.substr(0,3)=="chk") saveOptionCookie(this.id); }
if (this.srctid && this.srcpos>0 && (!this.id || this.id.substr(0,3)!="chk") && !this.tag && !this.field) {
// save state in tiddler content only if not using cookie, tag or field tracking
var t=store.getTiddler(this.srctid); // put X in original source tiddler (if any)
if (t && this.checked!=(t.text.substr(this.srcpos,1).toUpperCase()=="X")) { // if changed
t.set(null,t.text.substr(0,this.srcpos)+(this.checked?"X":"_")+t.text.substr(this.srcpos+1),null,null,t.tags);
if (!story.isDirty(t.title)) story.refreshTiddler(t.title,null,true);
store.setDirty(true);
}
}
if (this.field) {
if (this.checked && !store.tiddlerExists(this.tiddler))
store.saveTiddler(this.tiddler,this.tiddler,"",config.options.txtUserName,new Date());
// set the field value in the target tiddler
store.setValue(this.tiddler,this.field,this.checked?"true":"false");
// DEBUG: displayMessage(this.field+"@"+this.tiddler+" is "+this.checked);
}
if (this.tag) {
if (this.checked && !store.tiddlerExists(this.tiddler))
store.saveTiddler(this.tiddler,this.tiddler,"",config.options.txtUserName,new Date());
var t=store.getTiddler(this.tiddler);
if (t) {
var tagged=(t.tags && t.tags.indexOf(this.tag)!=-1);
if (this.checked && !tagged) { t.tags.push(this.tag); store.setDirty(true); }
if (!this.checked && tagged) { t.tags.splice(t.tags.indexOf(this.tag),1); store.setDirty(true); }
}
// if tag state has been changed, update display of corresponding tiddlers (unless they are in edit mode...)
if (this.checked!=tagged) {
if (this.refresh.tagged) {
if (!story.isDirty(this.tiddler)) // the TAGGED tiddler in view mode
story.refreshTiddler(this.tiddler,null,true);
else // the TAGGED tiddler in edit mode (with tags field)
config.macros.checkbox.refreshEditorTagField(this.tiddler,this.tag,this.checked);
}
if (this.refresh.tagging)
if (!story.isDirty(this.tag)) story.refreshTiddler(this.tag,null,true); // the TAGGING tiddler
}
}
if (!this.init && this.fn_clickAfter) // custom function hook to react to changes in checkbox state
{ try { eval(this.fn_clickAfter) } catch(e) { displayMessage("Checkbox onClickAfter error: "+e.toString()); } }
// refresh containing tiddler (but not during initial rendering, or we get an infinite loop!) (and not when editing container)
if (!this.init && this.refresh.container && this.container!=this.tiddler)
if (!story.isDirty(this.container)) story.refreshTiddler(this.container,null,true); // the tiddler CONTAINING the checkbox
return true;
},
refreshEditorTagField: function(title,tag,set) {
var tagfield=story.getTiddlerField(title,"tags");
if (!tagfield||tagfield.getAttribute("edit")!="tags") return; // if no tags field in editor (i.e., custom template)
var tags=tagfield.value.readBracketedList();
if (tags.contains(tag)==set) return; // if no change needed
if (set) tags.push(tag); // add tag
else tags.splice(tags.indexOf(tag),1); // remove tag
for (var t=0;t<tags.length;t++) tags[t]=String.encodeTiddlyLink(tags[t]);
tagfield.value=tags.join(" "); // reassemble tag string (with brackets as needed)
return;
}
}
//}}}
|Name|CheckboxPluginInfo|
|Source|http://www.TiddlyTools.com/#CheckboxPlugin|
|Documentation|http://www.TiddlyTools.com/#CheckboxPluginInfo|
|Version|2.4.0|
|Author|Eric Shulman|
|License|http://www.TiddlyTools.com/#LegalStatements|
|~CoreVersion|2.1|
|Type|documentation|
|Description|documentation for CheckboxPlugin|
This plugin extends the TiddlyWiki syntax to allow definition of checkboxes that can be embedded directly in tiddler content. Checkbox states are preserved by:
* setting/removing tags on specified tiddlers,
* or, setting custom field values on specified tiddlers,
* or, saving to a locally-stored cookie ID,
* or, automatically modifying the tiddler source content (deprecated).
When an ID is assigned to the checkbox, it enables direct programmatic access to the checkbox DOM element, as well as creating an entry in TiddlyWiki's config.options[ID] internal data. In addition to tracking the checkbox state, you can also specify custom javascript for programmatic initialization and onClick event handling for any checkbox, so you can provide specialized side-effects in response to state changes.
!!!!!Inline (wiki syntax) Usage
<<<
//{{{
[ ]or[_] and [x]or[X]
//}}}
Simple checkboxes using 'Inline X' storage. The current unchecked/checked state is indicated by the character between the {{{[}}} and {{{]}}} brackets ("_" means unchecked, "X" means checked). When you click on a checkbox, the current state is retained by directly modifying the tiddler content to place the corresponding "_" or "X" character in between the brackets.
>//''NOTE: 'Inline X' syntax has been deprecated...'' This storage format only works properly for checkboxes that are directly embedded and accessed from content in a single tiddler. However, if that tiddler is 'transcluded' into another (by using the {{{<<tiddler TiddlerName>>}}} macro), the 'Inline X' will be ''erroneously stored in the containing tiddler's source content, resulting in corrupted content in that tiddler.'' For anything but the most simple of "to do list" uses, you should select from the various alternative storage methods described below...//
//{{{
[x=id]
//}}}
Assign an optional ID to the checkbox so you can use {{{document.getElementByID("id")}}} to manipulate the checkbox DOM element, as well as tracking the current checkbox state in {{{config.options["id"]}}}. If the ID starts with "chk" the checkbox state will also be saved in a cookie, so it can be automatically restored whenever the checkbox is re-rendered (overrides any default {{{[x]}}} or {{{[_]}}} value). If a cookie value is kept, the "_" or "X" character in the tiddler content remains unchanged, and is only applied as the default when a cookie-based value is not currently defined.
//{{{
[x(title|tag)] or [x(title:tag)]
//}}}
Initializes and tracks the current checkbox state by setting or removing a particular tag value from a specified tiddler. If you omit the tiddler title (and the | or : separator), the specified tag is assigned to the current tiddler. If you omit the tag value, as in {{{(title|)}}}, the default tag, {{{checked}}}, is assumed. Omitting both the title and tag, {{{()}}}, tracks the checkbox state by setting the "checked" tag on the current tiddler. When tag tracking is used, the "_" or "X" character in the tiddler content remains unchanged, and is not used to set or track the checkbox state. If a tiddler title named in the tag does not exist, the checkbox state defaults to the "inline X" value. If this value is //checked//, or is subsequently changed to //checked//, it will automatically create the missing tiddler and then add the tag to it. //''NOTE: beginning with version 2.1.2 of this plugin, the "|" separator is the preferred separator between the title and tag name, as it avoids syntactic ambiguity when ":" is used within tiddler titles or tag names.''//
//{{{
[x(field@tiddler)]
//}}}
Initializes and tracks the current checkbox state by setting a particular custom field value from a specified tiddler. If you omit the tiddler title (but not the "@" separator), the specified field on the current tiddler is used. If you omit the field name, as in {{{(@tiddler)}}}, a default fieldname of {{{checked}}} is assumed. Omitting both the field and the tiddler title, {{{(@)}}}, defaults to setting the "checked" field on the current tiddler. When field tracking is used, the "_" or "X" character in the tiddler content remains unchanged, and is not used to set or track the checkbox state. If the tiddler title named in the parameter does not exist, the checkbox state defaults to the "inline X" value. If this value is //checked// or is subsequently changed to //checked//, it will automatically create the missing tiddler and then add the field to it.
//{{{
[x{javascript}{javascript}{javascript}]
//}}}
You can define optional javascript code segments to add custom initialization and/or 'onClick' handlers to a checkbox. The current checkbox state (and it's other DOM attributes) can be set or read from within these code segments by reference to a globally-defined context object, "place" (which can also be referenced as "window.place").
The first code segment will be executed when the checkbox is initially displayed, so that you can programmatically determine it's starting checked/unchecked state. The second code segment (if present) is executed whenever the checkbox is clicked, but //before the regular checkbox processing in performed// ("onClickBefore"), so that you can apply programmed responses or intercept and override the checkbox state based on custom logic. The third code segment (if present) is executed whenver the checkbox is clicked, //after the regular checkbox processing has completed// ("onClickAfter"), so that you can include "side-effect" processing based on the checkbox state just applied.
>Note: if you want to use the default checkbox initialization processing with a custom onClickBefore/After function, use this syntax:
>{{{[x(tag){}{javascript}]}}} or {{{[x(tag){}{}{javascript}]}}}
<<<
!!!!!Macro usage
<<<
In addition to embedded checkboxes using the wiki syntax described above, a ''macro-based syntax'' is also provided, for use in templates where wiki syntax cannot be directly used. This macro syntax can also be used in tiddler content, as an alternative to the wiki syntax. When embedded in [[PageTemplate]], [[ViewTemplate]], or [[EditTemplate]] (or custom alternative templates), use the following macro syntax:
//{{{
<span macro="checkbox target checked id onInit onClickBefore onClickAfter"></span>
//}}}
or, when embedded in tiddler content, use the following macro syntax:
//{{{
<<checkbox target checked id onInit onClickBefore onClickAfter>>
//}}}
where:
''target''
>is either a tag reference (e.g., ''tagname|tiddlername'') or a field reference (e.g. ''fieldname@tiddlername''), as described above.
''checked'' (optional)
>is a keyword that sets the initial state of the checkbox to "checked". When omitted, the default checkbox state is "unchecked".
''id'' (optional)
>specifies an internal config.options.* ID, as described above. If the ID begins with "chk", a cookie-based persistent value will be created to track the checkbox state in between sessions.
''onInit'' (optional)
>contains a javascript event handler to be performed when the checkbox is initially rendered (see details above).
''onClickBefore'' and/or ''onClickAfter'' (optional)
>contains a javascript event handler to be performed each time the checkbox is clicked (see details above). //note: to use the default onInit handler with a custom onClickBefore/After handler, use "" (empty quotes) or {} (empty function) as a placeholder for the onInit and/or onClickBefore parameters//
<<<
!!!!!Examples
<<<
''checked and unchecked static default ("inline X") values:''
//{{{
[X] label
[_] label
//}}}
>[X] label
>[_] label
''document-based value (id='demo', no cookie):''
//{{{
[_=demo] label
//}}}
>[_=demo] label
''cookie-based value (id='chkDemo'):''
//{{{
[_=chkDemo] label
//}}}
>[_=chkDemo] label
''tag-based value (TogglyTagging):''
//{{{
[_(CheckboxPluginInfo|demotag)]
[_(CheckboxPluginInfo|demotag){place.refresh.tagged=place.refresh.container=false}]
//}}}
>[_(CheckboxPluginInfo|demotag)] toggle 'demotag' (and refresh tiddler display)
>[_(CheckboxPluginInfo|demotag){place.refresh.tagged=place.refresh.container=false}] toggle 'demotag' (no refresh)
''field-based values:''
//{{{
[_(demofield@CheckboxPluginInfo)] demofield@CheckboxPluginInfo
[_(demofield@)] demofield@ (equivalent to demonfield@ current tiddler)
[_(checked@CheckboxPluginInfo)] checked@CheckboxPluginInfo
[_(@CheckboxPluginInfo)] @CheckboxPluginInfo
[_(@)] @ (equivalent to checked@ current tiddler)
//}}}
>[_(demofield@CheckboxPluginInfo)] demofield@CheckboxPluginInfo
>[_(demofield@)] demofield@ (current tiddler)
>[_(checked@CheckboxPluginInfo)] checked@CheckboxPluginInfo
>[_(@CheckboxPluginInfo)] @CheckboxPluginInfo
>[_(@)] toggle field: @ (defaults to "checked@here")
>click to view current: <<toolbar fields>>
''custom init and onClick functions:''
//{{{
[X{place.checked=true}{alert(place.checked?"on":"off")}] message box with checkbox state
//}}}
>[X{place.checked=true}{alert(place.checked?"on":"off")}] message box with checkbox state
''retrieving option values:''
>config.options['demo']=<script>return config.options['demo']?"true":"false";</script>
>config.options['chkDemo']=<script>return config.options['chkDemo']?"true":"false";</script>
<<<
!!!!!Configuration
<<<
Normally, when a checkbox state is changed, the affected tiddlers are automatically re-rendered, so that any checkbox-dependent dynamic content can be updated. There are three possible tiddlers to be re-rendered, depending upon where the checkbox is placed, and what kind of storage method it is using.
*''container'': the tiddler in which the checkbox is displayed. (e.g., this tiddler)
*''tagged'': the tiddler that is being tagged (e.g., "~MyTask" when tagging "~MyTask:done")
*''tagging'': the "tag tiddler" (e.g., "~done" when tagging "~MyTask:done")
You can set the default refresh handling for all checkboxes in your document by using the following javascript syntax either in a systemConfig plugin, or as an inline script. (Substitute true/false values as desired):
{{{config.checkbox.refresh = { tagged:true, tagging:true, container:true };}}}
You can also override these defaults for any given checkbox by using an initialization function to set one or more of the refresh options. For example:
{{{[_{place.refresh.container=false}]}}}
<<<
!!!!!Revisions
<<<
2008.01.08 [*.*.*] plugin size reduction: documentation moved to [[CheckboxPluginInfo]]
2008.01.05 2.4.0 set global "window.place" to current checkbox element when processing checkbox clicks. This allows init/beforeClick/afterClick handlers to reference RELATIVE elements, including using "story.findContainingTiddler(place)". Also, wrap handlers in "function()" so "return" can be used within handler code.
2008.01.02 2.3.0 split optional custom onClick handling into separate onClickBefore and onClickAfter handlers. The onClickBefore handler permits interception of the click BEFORE the checkbox is set. onClickAfter allows follow-on 'side-effect' processing to occur AFTER the checkbox is set.
2007.12.04 [*.*.*] update for TW2.3.0: replaced deprecated core functions, regexps, and macros
2007.08.06 2.2.5 supress automatic refresh of any tiddler that is currently being edited. Ensures that current tiddler edit sessions are not prematurely discarded (losing any changes). However, if checkbox changes a tag on a tiddler being edited, update the "tags" input field (if any) so that saving the edited tiddler correctly reflects any changes due to checkbox activity... see refreshEditorTagField().
2007.07.13 - 2.2.4 in handler(), fix srctid reference (was "w.tiddler", should have been "w.tiddler.title"). This fixes css 'inline X' plus fatal macro error when using PartTiddlerPlugin. Thanks to cmari for reporting the problem and UdoBorkowski for finding the code error.
2007.06.21 - 2.2.3 suppress automatic refresh of tiddler when using macro-syntax to prevent premature end of tiddler editing session.
2007.06.20 - 2.2.2 fixed handling for 'inline X' when checkboxes are contained in a 'trancluded' tiddler. Now, regardless of where an inline X checkbox appears, the X will be placed in the originating source tiddler, rather than the tiddler in which the checkbox appears.
2007.06.17 - 2.2.1 Refactored code to add checkbox //macro// syntax for use in templates (e.g., {{{macro="checkbox ..."}}}. Also, code cleanup of existing tag handling.
2007.06.16 - 2.2.0 added support for tracking checkbox states using tiddler fields via "(fieldname@tiddlername)" syntax.
2006.05.04 - 2.1.3 fix use of findContainingTiddler() to check for a non-null return value, so that checkboxes won't crash when used outside of tiddler display context (such as in header, sidebar or mainmenu)
2006.03.11 - 2.1.2 added "|" as delimiter to tag-based storage syntax (e.g. "tiddler|tag") to avoid parsing ambiguity when tiddler titles or tag names contain ":". Using ":" as a delimiter is still supported but is deprecated in favor of the new "|" usage. Based on a problem reported by JeffMason.
2006.02.25 - 2.1.0 added configuration options to enable/disable forced refresh of tiddlers when toggling tags
2006.02.23 - 2.0.4 when toggling tags, force refresh of the tiddler containing the checkbox.
2006.02.23 - 2.0.3 when toggling tags, force refresh of the 'tagged tiddler' so that tag-related tiddler content (such as "to-do" lists) can be re-rendered.
2006.02.23 - 2.0.2 when using tag-based storage, allow use [[ and ]] to quote tiddler or tag names that contain spaces:
{{{[x([[Tiddler with spaces]]:[[tag with spaces]])]}}}
2006.01.10 - 2.0.1 when toggling tags, force refresh of the 'tagging tiddler'. For example, if you toggle the "systemConfig" tag on a plugin, the corresponding "systemConfig" TIDDLER will be automatically refreshed (if currently displayed), so that the 'tagged' list in that tiddler will remain up-to-date.
2006.01.04 - 2.0.0 update for ~TW2.0
2005.12.27 - 1.1.2 Fix lookAhead regExp handling for {{{[x=id]}}}, which had been including the "]" in the extracted ID.
Added check for "chk" prefix on ID before calling saveOptionCookie()
2005.12.26 - 1.1.2 Corrected use of toUpperCase() in tiddler re-write code when comparing {{{[X]}}} in tiddler content with checkbox state. Fixes a problem where simple checkboxes could be set, but never cleared.
2005.12.26 - 1.1.0 Revise syntax so all optional parameters are included INSIDE the [ and ] brackets. Backward compatibility with older syntax is supported, so content changes are not required when upgrading to the current version of this plugin. Based on a suggestion by GeoffSlocock
2005.12.25 - 1.0.0 added support for tracking checkbox state using tags ("TogglyTagging")
Revised version number for official post-beta release.
2005.12.08 - 0.9.3 support separate 'init' and 'onclick' function definitions.
2005.12.08 - 0.9.2 clean up lookahead pattern
2005.12.07 - 0.9.1 only update tiddler source content if checkbox state is actually different. Eliminates unnecessary tiddler changes (and 'unsaved changes' warnings)
2005.12.07 - 0.9.0 initial BETA release
<<<
/***
| ''Name:''|DatePlugin|
| ''Description:''|Formatted dates plus popup menu with 'journal' link, changes and (optional) reminders.|
| ''Version:''|2.7.3.1 (modified from 2.7.3 for personal use)|
| ''Last Modified:''|29 January 2012 (by ~Secret-HQ)|
| ''Author:''|Eric Shulman<br>Modified by Scott Simmons (~Secret-HQ) for personal use|
| ''Shadow Tiddlers:''|[[DatePluginConfig]]|
| ''Requires:''|no additional tiddlers (self-contained)|
| ''Related:''|[[CalendarPlugin]]|
| ''Core Version:''|2.1|
| ''Source:''|[[http://www.TiddlyTools.com/#DatePlugin|http://www.TiddlyTools.com/#DatePlugin]]<br>[[http://www.TiddlyTools.com/#DatePluginConfig|http://www.TiddlyTools.com/#DatePluginConfig]]<br>[[http://www.TiddlyTools.com/#DatePluginInfo|http://www.TiddlyTools.com/#DatePluginInfo]]|
| ''License:''|http://www.TiddlyTools.com/#LegalStatements|
***/
/***
!Usage
This plugin provides a general approach to displaying formatted dates and/or links and popups that permit easy navigation and management of tiddlers based on their creation/modification dates.
Install this plugin, add the tag {{{systemConfig}}}, and reload your ~TiddlyWiki to make the plugin available.
!!Documentation
This plugin displays formatted dates, for the specified year, month, day using number values or mathematical expressions such as (Y+1) or (D+30). Optionally, you can create a link to a 'dated tiddler' for quick blogging or create a popup menu that includes the dated tiddler link plus links to tiddlers that were created/changed on that date, or are tagged with that date and, if the [[ReminderMacros|http://remindermacros.tiddlyspot.com/]] plugin is installed, any pending reminders for next month. There is also a public API, so other plugins can embed a variety of formatted date output, links, and/or popup menus.
{{{
<<date mode date format linkformat>>
}}}
//All parameters are optional.//
*''mode''<br>is one of:
**''display'' (default)<br>shows a formatted date
**''link''<br>creates a link to a specific 'date titled' tiddler
**''popup''<br>creates a popup command containing a dated tiddler link, plus links to changes and reminders.
*''date'' (or ''tiddler'' or ''tiddler:title'' or ''today'' or ''filedate'')<br>enter ANY date (not just today) as space-separated year, month, and day parameters (e.g., 2011 4 23). You can use pre-defined variables, Y, M, and D for the current year, month and day, repectively. These variables can be combined with simple mathematical expressions to calculate ''relative dates'' (e.g., D+1 = tomorrow, M-1 = last month, Y+1= next year, etc.) Alternatively, you can use special keywords in place of the year/month/day parameters to access tiddler and file dates:
**''tiddler'' displays the modification date of the current tiddler.
**''tiddler://name-of-tiddler//'' displays the modification date of a specific tiddler.
**''today'' shows the current date.
**''filedate'' shows the modification date of the entire document.
*{{block{
''format'' (and ''linkformat'') (default: YYYY.0MM.0DD)<br>uses standard ~TiddlyWiki date formatting syntax to specify the title of the target tiddler.
>''DDD'' - day of week in full (eg, "Monday"), ''DD'' - day of month, ''0DD'' - adds leading zero
>''MMM'' - month in full (eg, "July"), ''MM'' - month number, ''0MM'' - adds leading zero
>''YYYY'' - full year, ''YY'' - two digit year, ''hh'' - hours, ''mm'' - minutes, ''ss'' - seconds
>
>//note: use of hh, mm or ss format codes is only supported with ''tiddler'', ''today'' or ''filedate'' values//
}}}
*{{block{
''linkformat''<br>specify an alternative date format so that the title of a 'dated tiddler' link can have a format that differs from the date's displayed format. The default tooltip is same as the title of the linked tiddler. You can customize the tooltip by modifying the definition in [[DatePluginConfig]]:
{{{
config.macros.date.tipformat="YYYY.0MM.0DD"; // 'dated tiddler' tooltip format
}}}
}}}
You can adjust the 'lead time' for display of [[reminders|http://remindermacros.tiddlyspot.com/]] by modifying the definition in [[DatePluginConfig]]:
{{{
config.macros.date.leadtime=30; // find reminders up to 30 days from now
}}}
In addition to the macro syntax, DatePlugin also provides a public javascript API so that other plugins that work with dates (such as calendar generators, etc.) can quickly incorporate date formatted links or popups into their output:
{{{
showDate(place, date, mode, format, linkformat, autostyle, weekend);
}}}
Note that the javascript API supports two //optional// true/false parameters, in addition those provided by the macro interface:
*''autostyle''<br>font/background styles of formatted dates are automatically adjusted to show the date's status: 'today' is boxed, 'changes' are bold, 'reminders' are underlined, weekends, holidays, changes, and reminders each have a different background color to make them more visibly distinct from one another.
*''weekend''<br>true=day is a weekend, false=day is a weekday, default=automatically determine if a given date falls on a weekend.
!!!Examples
{{{The current date: <<date>>}}}
>The current date: <<date>>
{{{The current time: <<date today "0hh:0mm:0ss">>}}}
>The current time: <<date today "0hh:0mm:0ss">>
{{{Today's blog: <<date link today "DDD, MMM DDth, YYYY">>}}}
>Today's blog: <<date link today "DDD, MMM DDth, YYYY">>
{{{Recent blogs/changes/reminders: <<date popup Y M D-1 "yesterday">> <<date popup today "today">> <<date popup Y M D+1 "tomorrow">>}}}
>Recent blogs/changes/reminders: <<date popup Y M D-1 "yesterday">> <<date popup today "today">> <<date popup Y M D+1 "tomorrow">>
{{{The first day of next month will be a <<date Y M+1 1 "DDD">>}}}
>The first day of next month will be a <<date Y M+1 1 "DDD">>
{{{This tiddler (DatePlugin) was last updated on: <<date tiddler "DDD, MMM DDth, YYYY">>}}}
>This tiddler (DatePlugin) was last updated on: <<date tiddler "DDD, MMM DDth, YYYY">>
{{{The SiteUrl was last updated on: <<date tiddler:SiteUrl "DDD, MMM DDth, YYYY">>}}}
>The SiteUrl was last updated on: <<date tiddler:SiteUrl "DDD, MMM DDth, YYYY">>
{{{This document was last saved on <<date filedate "DDD, MMM DDth, YYYY at 0hh:0mm:0ss">>}}}
>This document was last saved on <<date filedate "DDD, MMM DDth, YYYY at 0hh:0mm:0ss">>
{{{<<date Y 07 24 "MMM DDth, YYYY">> will be a <<date Y 07 24 "DDD">>}}}
><<date Y 07 24 "MMM DDth, YYYY">> will be a <<date Y 07 24 "DDD">>
***/
/***
!Configuration
<<option chkDatePopupHideCreated>> omit 'created' section from date popups
<<option chkDatePopupHideChanged>> omit 'changed' section from date popups
<<option chkDatePopupHideTagged>> omit 'tagged' section from date popups
<<option chkDatePopupHideReminders>> omit 'reminders' section from date popups
<<option chkShowJulianDate>> display Julian day number (1-365) below current date
See [[DatePluginConfig]] for additional configuration settings for use in calendar displays, including:
*date formats
*color-coded backgrounds
*annual fixed-date holidays
*weekends
***/
/***
!Version
***/
//{{{
version.extensions.PluginName = { major:2, minor:7, revision:3, date:new Date(2011,4,23), source:"http://www.TiddlyTools.com/#DatePlugin" };
//}}}
/***
!!History
|altRows|k
| date|version|changes|h
| 29 January 2012|2.7.3.1|@@color(RED):(~Secret-HQ)@@<br>Moved DatePluginInfo back into main plugin to make the plugin self-contained.<br>Moved DatePluginConfig into a shadow tiddler, also to make the plugin self-contained.<br>Cleaned up and reformatted history and documentation.|
| 23 April 2011|2.7.3|Added {{{config.macros.date.tipformat}}} for custom mouseover tooltip and {{{config.macros.date.leadtime}}} for custom reminder leadtime (default = 90 days).|
| 4 January 2011|2.7.2.1|@@color(RED):(~Secret-HQ)@@<br>Modified Eric's styles slightly to remove border on today's date.|
| 15 December 2010|2.7.2|Omit date highlighting when hiding popup items (created/changed/tagged/reminders).|
| 31 May 2009|2.7.1|In {{{addRemindersToPopup()}}}, "new reminder...." command now uses {{{<<newTiddler>>}}} macro. Also, general code reduction/cleanup.|
| 8 March 2008|2.7.0|In {{{addModifiedsToPopup()}}}, if a tiddler was created on the specified date, don't list it in the 'changed' section of the popup. Based on a request from Kashgarinn.|
| 31 January 2008|2.6.0|Refactored date style logic into separate {{{setDateStyle()}}} function so it can be overridden by a custom definition. See [[DatePluginConfig]].|
| 11 January 2008|2.5.0|Added options to selectively suppress created/changes/tagged/reminders popup content.|
| 8 January 2008|*.*.*|Plugin size reduction: documentation moved to DatePluginInfo.|
| 21 November 2007|2.4.0|Added {{{hasTagged()}}} and {{{addTaggedToPopup()}}} to list any tiddlers that have been tagged using the title of the dated journal tiddler as a tag value (i.e., the tiddlers that will be listed in the standard "tagging" display when viewing the journal tiddler itself). Based on a request from Coby.|
| 20 June 2007|2.3.1|In {{{onClickDatePopup()}}}, use {{{Popup.show()}}} instead of deprecated {{{ScrollToTiddlerPopup()}}}. Fixes fatal error that prevents popups from being properly displayed.|
| 31 May 2007|2.3.0|List "created" tiddlers in date popup.<br>Also, force re-cache of created/modified indices when displaying current date and store.isDirty(), so that popup is kept in sync with tiddler changes.|
| 9 May 2006|2.2.1|Added {{{todaybg}}} handling to set background color of current date.<br>Also, honor {{{excludeLists}}} tag when getting lists of tiddlers. Based on suggestions by Mark Hulme.|
| 5 May 2006|2.2.0|Added {{{linkedbg}}} handling to set background color when a 'dated tiddler' exists. Based on a suggestion by Mark Hulme.|
| 8 March 2006|2.1.2|Add 'override leadtime' flag param in call to {{{findTiddlersWithReminders()}}}, and add "Enter a title" default text to new reminder handler. Thanks to Jeremy Sheeley for these additional tweaks.|
| 6 March 2006|2.1.0|Function {{{hasReminders()}}} nows uses {{{window.reminderCacheForCalendar[]}}} when present. If calendar cache is not present, {{{indexReminders()}}} now uses {{{findTiddlersWithReminders()}}} with a 90-day look ahead to check for reminders.<br>Also, switched default background colors for autostyled dates: reminders are now greenish ("c0ffee") and holidays are now reddish ("ffaace").|
| 14 February 2006|2.0.5|When {{{readOnly}}} is set (by TW core), omit "new reminders..." popup menu item and, if a "dated tiddler" does not already exist, display the date as simple text instead of a link.|
| 5 February 2006|2.0.4|Added {{{var}}} to variables that were unintentionally global. Avoids FireFox 1.5.0.1 crash bug when referencing global variables.|
| 18 January 2006|2.0.3|In 1.2.x the tiddler editor's text area control was given an element ID = {{{("tiddlerBody"+title)}}}, so that it was easy to locate this field and programmatically modify its content. With the addition of configuration templates in 2.x, the textarea no longer has an ID assigned. To find this control, we now look through all the child nodes of the tiddler editor to locate a "textarea" control where {{{attribute("edit")}}} = {{{"text"}}} and then append the new reminder to the contents of that control.|
| 11 January 2006|2.0.2|Correct 'weekend' override detection logic in {{{showDate()}}}.|
| 10 January 2006|2.0.1|Allow custom-defined weekend days (default defined in {{{config.macros.date.weekend[]}}} array).<br>Added {{{flag}}} param to {{{showDate()}}} API to override internal {{{weekend[]}}} array.|
| 27 December 2005|2.0.0|Update for TW2.0.<br>Added parameter handling for {{{linkformat}}}.|
| 21 December 2005|1.2.2|FF's {{{date.getYear()}}} function returns 105 (for the current year, 2005). When calculating a date value from {{{Y}}}, {{{M}}}, and {{{D}}} expressions, the plugin adds 1900 to the returned year value to get the current year number. But IE's {{{date.getYear()}}} already returns 2005. As a result, plugin calculated date values on IE were incorrect (e.g., 3905 instead of 2005). Adding +1900 is now conditional, so the values will be correct on both browsers.|
| 7 November 2005|1.2.1|Added support for "tiddler" dynamic date parameter.|
| 6 November 2005|1.2.0|Added support for "tiddler:title" dynamic date parameter.|
| 3 November 2005|1.1.2|When a reminder doesn't have a specified title parameter, use the title of the tiddler that contains the reminder as "fallback" text in the popup menu. Based on a suggestion from ~BenjaminKudria.|
| 3 November 2005|1.1.1|Temporarily bypass {{{hasReminders()}}} logic to avoid excessive overhead from generating the {{{indexReminders()}}} cache. While reminders can still appear in the popup menu, they just won't be indicated by auto-styling the date number that is displayed. This single change saves approx. 60% overhead (5 second delay reduced to under 2 seconds).|
| 1 November 2005|1.1.0|Corrected logic in {{{hasModifieds()}}} and {{{hasReminders()}}} so caching of indexed modifieds and reminders is done just once, as intended. This should hopefully speed up calendar generators and other plugins that render multiple dates...|
| 31 October 2005|1.0.1|Documentation and code cleanup.|
| 31 October 2005|1.0.0|Initial public release.|
| 30 October 2005|0.9.0|Pre-release.|
***/
/***
!Code
***/
//{{{
config.macros.date = {
format: 'YYYY.0MM.0DD', // default date display format
linkformat: 'YYYY.0MM.0DD', // 'dated tiddler' link format
tipformat: 'YYYY.0MM.0DD', // 'dated tiddler' link tooltip format
leadtime: 31, // find reminders up to 31 days from now
linkedbg: '#babb1e', // 'babble'
todaybg: '#ffab1e', // 'fable'
weekendbg: '#c0c0c0', // 'cocoa'
holidaybg: '#ffaace', // 'face'
createdbg: '#bbeeff', // 'beef'
modifiedsbg: '#bbeeff', // 'beef'
remindersbg: '#c0ffee', // 'coffee'
weekend: [ 1,0,0,0,0,0,1 ], // [ day index values: sun=0, mon=1, tue=2, wed=3, thu=4, fri=5, sat=6 ],
holidays: [ '01/01', '07/04', '07/24', '11/24' ]
// NewYearsDay, IndependenceDay(US), Eric's Birthday (hooray!), Thanksgiving(US)
};
config.macros.date.handler = function(place,macroName,params)
{
// default: display current date
var now =new Date();
var date=now;
var mode='display';
if (params[0]&&['display','popup','link'].contains(params[0].toLowerCase()))
{ mode=params[0]; params.shift(); }
if (!params[0] || params[0]=='today')
{ params.shift(); }
else if (params[0]=='filedate')
{ date=new Date(document.lastModified); params.shift(); }
else if (params[0]=='tiddler')
{ date=store.getTiddler(story.findContainingTiddler(place).id.substr(7)).modified; params.shift(); }
else if (params[0].substr(0,8)=='tiddler:')
{ var t; if ((t=store.getTiddler(params[0].substr(8)))) date=t.modified; params.shift(); }
else {
var y = eval(params.shift().replace(/Y/ig,(now.getYear()<1900)?now.getYear()+1900:now.getYear()));
var m = eval(params.shift().replace(/M/ig,now.getMonth()+1));
var d = eval(params.shift().replace(/D/ig,now.getDate()+0));
date = new Date(y,m-1,d);
}
// date format with optional custom override
var format=this.format; if (params[0]) format=params.shift();
var linkformat=this.linkformat; if (params[0]) linkformat=params.shift();
showDate(place,date,mode,format,linkformat);
}
window.showDate=showDate;
function showDate(place,date,mode,format,linkformat,autostyle,weekend)
{
mode =mode||'display';
format =format||config.macros.date.format;
linkformat=linkformat||config.macros.date.linkformat;
// format the date output
var title=date.formatString(format);
var linkto=date.formatString(linkformat);
var tip=date.formatString(config.macros.date.tipformat);
// just show the formatted output
if (mode=='display') { place.appendChild(document.createTextNode(title)); return; }
// link to a 'dated tiddler'
var link = createTiddlyLink(place, linkto, false);
link.appendChild(document.createTextNode(title));
link.title = linkto; // MODIFIED BY SECRET-HQ; DEFAULT VALUE: tip;
link.date = date;
link.format = format;
link.linkformat = linkformat;
// if using a popup menu, replace click handler for dated tiddler link
// with handler for popup and make link text non-italic (i.e., an 'existing link' look)
if (mode=='popup') {
link.onclick = onClickDatePopup;
link.style.fontStyle='normal';
}
// format the popup link to show what kind of info it contains (for use with calendar generators)
if (autostyle) setDateStyle(place,link,weekend);
}
//}}}
//{{{
// NOTE: This function provides default logic for setting the date style when displayed in a calendar
// To customize the date style logic, please see[[DatePluginConfig]]
function setDateStyle(place,link,weekend) {
// alias variable names for code readability
var date=link.date;
var fmt=link.linkformat;
var linkto=date.formatString(fmt);
var cmd=config.macros.date;
var co=config.options; // abbrev
if ((weekend!==undefined?weekend:isWeekend(date))&&(cmd.weekendbg!=''))
{ place.style.background = cmd.weekendbg; }
if (hasModifieds(date)||hasCreateds(date)||hasTagged(date,fmt))
{ link.style.fontStyle='normal'; link.style.fontWeight='bold'; }
if (hasReminders(date))
{ link.style.textDecoration='underline'; }
if (isToday(date))
{ link.style.border='none'; } // MODIFIED BY SECRET-HQ; DEFAULT VALUE: "1px solid black";
if (isHoliday(date)&&(cmd.holidaybg!=''))
{ place.style.background = cmd.holidaybg; }
if (hasCreateds(date)&&(cmd.createdbg!=''))
{ place.style.background = cmd.createdbg; }
if (hasModifieds(date)&&(cmd.modifiedsbg!=''))
{ place.style.background = cmd.modifiedsbg; }
if ((hasTagged(date,fmt)||store.tiddlerExists(linkto))&&(cmd.linkedbg!=''))
{ place.style.background = cmd.linkedbg; }
if (hasReminders(date)&&(cmd.remindersbg!=''))
{ place.style.background = cmd.remindersbg; }
if (isToday(date)&&(cmd.todaybg!=''))
{ place.style.background = cmd.todaybg; }
if (config.options.chkShowJulianDate) { // optional display of Julian date numbers
var m=[0,31,59,90,120,151,181,212,243,273,304,334];
var d=date.getDate()+m[date.getMonth()];
var y=date.getFullYear();
if (date.getMonth()>1 && (y%4==0 && y%100!=0) || y%400==0)
d++; // after February in a leap year
wikify('@@font-size:80%;<br>'+d+'@@',place);
}
}
//}}}
//{{{
function isToday(date) // returns true if date is today
{ var now=new Date(); return ((now-date>=0) && (now-date<86400000)); }
function isWeekend(date) // returns true if date is a weekend
{ return (config.macros.date.weekend[date.getDay()]); }
function isHoliday(date) // returns true if date is a holiday
{
var longHoliday = date.formatString('0MM/0DD/YYYY');
var shortHoliday = date.formatString('0MM/0DD');
for(var i = 0; i < config.macros.date.holidays.length; i++) {
var holiday=config.macros.date.holidays[i];
if (holiday==longHoliday||holiday==shortHoliday) return true;
}
return false;
}
//}}}
//{{{
// Event handler for clicking on a day popup
function onClickDatePopup(e) { e=e||window.event;
var p=Popup.create(this); if (!p) return false;
// always show dated tiddler link (or just date, if readOnly) at the top...
if (!readOnly || store.tiddlerExists(this.date.formatString(this.linkformat)))
createTiddlyLink(createTiddlyElement(p,'li'),this.date.formatString(this.linkformat),true);
else
createTiddlyText(createTiddlyElement(p,'li'),this.date.formatString(this.linkformat));
addCreatedsToPopup(p,this.date,this.format);
addModifiedsToPopup(p,this.date,this.format);
addTaggedToPopup(p,this.date,this.linkformat);
addRemindersToPopup(p,this.date,this.linkformat);
Popup.show(); e.cancelBubble=true; if(e.stopPropagation)e.stopPropagation(); return false;
}
//}}}
//{{{
function indexCreateds() // build list of tiddlers, hash indexed by creation date
{
var createds= { };
var tiddlers = store.getTiddlers('title','excludeLists');
for (var t = 0; t < tiddlers.length; t++) {
var date = tiddlers[t].created.formatString('YYYY0MM0DD')
if (!createds[date])
createds[date]=new Array();
createds[date].push(tiddlers[t].title);
}
return createds;
}
function hasCreateds(date) // returns true if date has created tiddlers
{
if (config.options.chkDatePopupHideCreated) return false;
if (!config.macros.date.createds) config.macros.date.createds=indexCreateds();
return (config.macros.date.createds[date.formatString('YYYY0MM0DD')]!=undefined);
}
function addCreatedsToPopup(p,when,format)
{
if (config.options.chkDatePopupHideCreated) return false;
var force=(store.isDirty() && when.formatString('YYYY0MM0DD')==new Date().formatString('YYYY0MM0DD'));
if (force || !config.macros.date.createds) config.macros.date.createds=indexCreateds();
var indent=String.fromCharCode(160)+String.fromCharCode(160);
var createds = config.macros.date.createds[when.formatString('YYYY0MM0DD')];
if (createds) {
createds.sort();
var e=createTiddlyElement(p,'div',null,null,'created ('+createds.length+')');
for(var t=0; t<createds.length; t++) {
var link=createTiddlyLink(createTiddlyElement(p,'li'),createds[t],false);
link.appendChild(document.createTextNode(indent+createds[t]));
}
}
}
//}}}
//{{{
function indexModifieds() // build list of tiddlers, hash indexed by modification date
{
var modifieds= { };
var tiddlers = store.getTiddlers('title','excludeLists');
for (var t = 0; t < tiddlers.length; t++) {
var date = tiddlers[t].modified.formatString('YYYY0MM0DD')
if (!modifieds[date])
modifieds[date]=new Array();
modifieds[date].push(tiddlers[t].title);
}
return modifieds;
}
function hasModifieds(date) // returns true if date has modified tiddlers
{
if (config.options.chkDatePopupHideChanged) return false;
if (!config.macros.date.modifieds) config.macros.date.modifieds = indexModifieds();
return (config.macros.date.modifieds[date.formatString('YYYY0MM0DD')]!=undefined);
}
function addModifiedsToPopup(p,when,format)
{
if (config.options.chkDatePopupHideChanged) return false;
var date=when.formatString('YYYY0MM0DD');
var force=(store.isDirty() && date==new Date().formatString('YYYY0MM0DD'));
if (force || !config.macros.date.modifieds) config.macros.date.modifieds=indexModifieds();
var indent=String.fromCharCode(160)+String.fromCharCode(160);
var mods = config.macros.date.modifieds[date];
if (mods) {
// if a tiddler was created on this date, don't list it in the 'changed' section
if (config.macros.date.createds && config.macros.date.createds[date]) {
var temp=[];
for(var t=0; t<mods.length; t++)
if (!config.macros.date.createds[date].contains(mods[t]))
temp.push(mods[t]);
mods=temp;
}
mods.sort();
var e=createTiddlyElement(p,'div',null,null,'changed ('+mods.length+')');
for(var t=0; t<mods.length; t++) {
var link=createTiddlyLink(createTiddlyElement(p,'li'),mods[t],false);
link.appendChild(document.createTextNode(indent+mods[t]));
}
}
}
//}}}
//{{{
function hasTagged(date,format) // returns true if date is tagging other tiddlers
{
if (config.options.chkDatePopupHideTagged) return false;
return store.getTaggedTiddlers(date.formatString(format)).length>0;
}
function addTaggedToPopup(p,when,format)
{
if (config.options.chkDatePopupHideTagged) return false;
var indent=String.fromCharCode(160)+String.fromCharCode(160);
var tagged=store.getTaggedTiddlers(when.formatString(format));
if (tagged.length) var e=createTiddlyElement(p,'div',null,null,'tagged ('+tagged.length+')');
for(var t=0; t<tagged.length; t++) {
var link=createTiddlyLink(createTiddlyElement(p,'li'),tagged[t].title,false);
link.appendChild(document.createTextNode(indent+tagged[t].title));
}
}
//}}}
//{{{
function indexReminders(date,leadtime) // build list of tiddlers with reminders, hash indexed by reminder date
{
var reminders = { };
if(window.findTiddlersWithReminders!=undefined) { // reminder plugin is installed
var t = findTiddlersWithReminders(date, [0,leadtime], null, null, 1);
for(var i=0; i<t.length; i++) reminders[t[i].matchedDate]=true;
}
return reminders;
}
function hasReminders(date) // returns true if date has reminders
{
if (config.options.chkDatePopupHideReminders) return false;
if (window.reminderCacheForCalendar)
return window.reminderCacheForCalendar[date]; // use calendar cache
if (!config.macros.date.reminders)
config.macros.date.reminders = indexReminders(date,config.macros.date.leadtime); // create a reminder cache
return (config.macros.date.reminders[date]);
}
function addRemindersToPopup(p,when,format)
{
if (config.options.chkDatePopupHideReminders) return false;
if(window.findTiddlersWithReminders==undefined) return; // reminder plugin not installed
var indent = String.fromCharCode(160)+String.fromCharCode(160);
var reminders=findTiddlersWithReminders(when, [0,config.macros.date.leadtime],null,null,1);
createTiddlyElement(p,'div',null,null,'reminders ('+(reminders.length||'none')+')');
for(var t=0; t<reminders.length; t++) {
link = createTiddlyLink(createTiddlyElement(p,'li'),reminders[t].tiddler,false);
var diff=reminders[t].diff;
diff=(diff<1)?'Today':((diff==1)?'Tomorrow':diff+' days');
var txt=(reminders[t].params['title'])?reminders[t].params['title']:reminders[t].tiddler;
link.appendChild(document.createTextNode(indent+diff+' - '+txt));
}
if (readOnly) return; // readonly... omit 'new reminder...' command
var rem='\\<\\<reminder day:%0 month:%1 year:%2 title:"Enter a reminder title here"\\>\\>';
rem=rem.format([when.getDate(),when.getMonth()+1,when.getYear()+1900]);
var cmd="<<newTiddler label:[["+indent+"new reminder...]] prompt:[[add a reminder to '%0']]"
+" title:[[%0]] text:{{var t=store.getTiddlerText('%0','');t+(t.length?'\\n':'')+'%1'}} tag:%2>>";
wikify(cmd.format([when.formatString(format),rem,config.options.txtCalendarReminderTags||'']),
createTiddlyElement(p,'li'));
}
//}}}
/***
!!Templates (Shadow Tiddlers)
***/
//{{{
config.shadowTiddlers["DatePluginConfig"] = "/***"+"\n"+"This tiddler overrides some of the default settings of DatePlugin and is shadowed in DatePlugin. To use it, edit this tiddler and add the {{{systemConfig}}} tag to it."+"\n"+"***/";
config.shadowTiddlers["DatePluginConfig"] += "\n\n";
config.shadowTiddlers["DatePluginConfig"] += "/***"+"\n"+"''Default popup content display options (can be overridden by cookies):''"+"\n"+"***/"+"\n";
config.shadowTiddlers["DatePluginConfig"] += "//{{{"+"\n"+"if (config.options.chkDatePopupHideCreated===undefined)"+"\n"+" { config.options.chkDatePopupHideCreated=false; }"+"\n"+"if (config.options.chkDatePopupHideChanged===undefined)"+"\n"+" { config.options.chkDatePopupHideChanged=false; }"+"\n"+"if (config.options.chkDatePopupHideTagged===undefined)"+"\n"+" { config.options.chkDatePopupHideTagged=false; }"+"\n"+"if (config.options.chkDatePopupHideReminders===undefined)"+"\n"+" { config.options.chkDatePopupHideReminders=false; }"+"\n"+"//}}}";
config.shadowTiddlers["DatePluginConfig"] += "\n\n";
config.shadowTiddlers["DatePluginConfig"] += "/***"+"\n"+"''Show Julian date number below regular date:''"+"\n"+"***/"+"\n";
config.shadowTiddlers["DatePluginConfig"] += "//{{{"+"\n"+"if (config.options.chkShowJulianDate===undefined)"+"\n"+" { config.options.chkShowJulianDate=false; }"+"\n"+"//}}}";
config.shadowTiddlers["DatePluginConfig"] += "\n\n";
config.shadowTiddlers["DatePluginConfig"] += "/***"+"\n"+"''Fixed-date annual holidays:''"+"\n"+"***/"+"\n";
config.shadowTiddlers["DatePluginConfig"] += "//{{{"+"\n"+"config.macros.date.holidays=["+"\n"+" \"01/01\", // NewYearsDay,"+"\n"+" \"07/04\", // US Independence Day"+"\n"+" \"07/24\" // Eric's Birthday (hooray!)"+"\n"+"];"+"\n"+"//}}}";
config.shadowTiddlers["DatePluginConfig"] += "\n\n";
config.shadowTiddlers["DatePluginConfig"] += "/***"+"\n"+"''Weekend map (1=weekend, 0=weekday):''"+"\n"+"***/"+"\n";
config.shadowTiddlers["DatePluginConfig"] += "//{{{"+"\n"+"config.macros.date.weekend=[ 1,0,0,0,0,0,1 ]; // day index values: sun=0, mon=1, tue=2, wed=3, thu=4, fri=5, sat=6"+"\n"+"//}}}";
config.shadowTiddlers["DatePluginConfig"] += "\n\n";
config.shadowTiddlers["DatePluginConfig"] += "/***"+"\n"+"''Date display/link formats:''"+"\n"+"***/"+"\n";
config.shadowTiddlers["DatePluginConfig"] += "//{{{"+"\n"+"config.macros.date.format=\"YYYY.0MM.0DD\"; // default date display format"+"\n"+"config.macros.date.linkformat=\"YYYY.0MM.0DD\"; // 'dated tiddler' link format"+"\n"+"config.macros.date.tipformat=\"YYYY.0MM.0DD\"; // 'dated tiddler' tooltip format"+"\n"+"//}}}";
config.shadowTiddlers["DatePluginConfig"] += "\n\n";
config.shadowTiddlers["DatePluginConfig"] += "/***"+"\n"+"''Reminder lead time:''"+"\n"+"***/"+"\n";
config.shadowTiddlers["DatePluginConfig"] += "//{{{"+"\n"+"config.macros.date.leadtime=31; // find reminders up to 31 days from now"+"\n"+"//}}}";
config.shadowTiddlers["DatePluginConfig"] += "\n\n";
config.shadowTiddlers["DatePluginConfig"] += "/***"+"\n"+"!!Calendar Styles"+"\n\n"+"When displaying a calendar (see [[CalendarPlugin]]), you can customize the colors/styles that are applied to the calendar dates by modifying the values and/or functions below:"+"\n"+"***/";
config.shadowTiddlers["DatePluginConfig"] += "\n\n";
config.shadowTiddlers["DatePluginConfig"] += "/***"+"\n"+"''Default calendar colors:''"+"\n"+"***/"+"\n";
config.shadowTiddlers["DatePluginConfig"] += "//{{{"+"\n"+"config.macros.date.weekendbg=\"#c0c0c0\"; // 'cocoa'"+"\n"+"config.macros.date.holidaybg=\"#ffaace\"; // 'face'"+"\n"+"config.macros.date.createdbg=\"#bbeeff\"; // 'beef'"+"\n"+"config.macros.date.modifiedsbg=\"#bbeeff\"; // 'beef'"+"\n"+"config.macros.date.linkedbg=\"#babb1e\"; // 'babble'"+"\n"+"config.macros.date.remindersbg=\"#c0ffee\"; // 'coffee'"+"\n"+"//}}}";
config.shadowTiddlers["DatePluginConfig"] += "\n\n";
config.shadowTiddlers["DatePluginConfig"] += "/***"+"\n"+"''Apply calendar styles:''"+"\n"+"***/"+"\n";
config.shadowTiddlers["DatePluginConfig"] += "//{{{"+"\n"+"function setDateStyle(place,link,weekend) {"+"\n\n"+" // alias variable names for code readability"+"\n"+" var date=link.date;"+"\n"+" var fmt=link.linkformat;"+"\n"+" var linkto=date.formatString(fmt);"+"\n"+" var cmd=config.macros.date;"+"\n\n"+" if ((weekend!==undefined?weekend:isWeekend(date))&&(cmd.weekendbg!=\"\"))"+"\n"+" { place.style.background = cmd.weekendbg; }"+"\n"+" if (hasModifieds(date)||hasCreateds(date)||hasTagged(date,fmt))"+"\n"+" { link.style.fontStyle=\"normal\"; link.style.fontWeight=\"bold\"; }"+"\n"+" if (hasReminders(date))"+"\n"+" { link.style.textDecoration=\"underline\"; }"+"\n"+" if (isToday(date))"+"\n"+" { link.style.border=\"1px solid black\"; }"+"\n"+" if (isHoliday(date)&&(cmd.holidaybg!=\"\"))"+"\n"+" { place.style.background = cmd.holidaybg; }"+"\n"+" if (hasCreateds(date)&&(cmd.createdbg!=\"\"))"+"\n"+" { place.style.background = cmd.createdbg; }"+"\n"+" if (hasModifieds(date)&&(cmd.modifiedsbg!=\"\"))"+"\n"+" { place.style.background = cmd.modifiedsbg; }"+"\n"+" if ((hasTagged(date,fmt)||store.tiddlerExists(linkto))&&(cmd.linkedbg!=\"\"))"+"\n"+" { place.style.background = cmd.linkedbg; }"+"\n"+" if (hasReminders(date)&&(cmd.remindersbg!=\"\"))"+"\n"+" { place.style.background = cmd.remindersbg; }"+"\n"+" if (isToday(date)&&(cmd.todaybg!=\"\"))"+"\n"+" { place.style.background = cmd.todaybg; }"+"\n"+" if (config.options.chkShowJulianDate) {"+"\n"+" var m=[0,31,59,90,120,151,181,212,243,273,304,334];"+"\n"+" var d=date.getDate()+m[date.getMonth()];"+"\n"+" var y=date.getFullYear();"+"\n"+" if (date.getMonth()>1 && (y%4==0 && y%100!=0) || y%400==0) d++; // after February in a leap year"+"\n"+" wikify(\"@@font-size:80%;<br>\"+d+\"@@\",place);"+"\n"+" }"+"\n\n"+" var t=store.getTiddlerText(linkto,'')"+"\n\n"+" if (config.options.chkInlineCalendarJournals && t.length)"+"\n"+" { wikify('<br>'+t,place); }"+"\n\n"+"}"+"\n"+"//}}}";
//}}}
[[WelcomeToTiddlyspot]] [[GettingStarted]]
See http://www.checkettsweb.com/oldindex.html
<tabs mytabs>
<tab Inline Formatting>
|sortable|k
|Option|Syntax|Output|h
|bold font|{{{''bold''}}}|''bold''|
|italic type|{{{//italic//}}}|//italic//|
|underlined text|{{{__underlined__}}}|__underlined__|
|strikethrough text|{{{--strikethrough--}}}|--strikethrough--|
|superscript text|{{{^^super^^script}}}|^^super^^script|
|subscript text|{{{~~sub~~script}}}|~~sub~~script|
|highlighted text|{{{@@highlighted@@}}}|@@highlighted@@|
|preformatted text|<html><code>{{{preformatted}}}</code></html>|{{{preformatted}}}|
</tab>
<tab Block Elements>
!!Headings
{{{
!Heading 1
!!Heading 2
!!!Heading 3
!!!!Heading 4
!!!!!Heading 5
}}}
<<<
!Heading 1
!!Heading 2
!!!Heading 3
!!!!Heading 4
!!!!!Heading 5
<<<
</tab>
<tab Lists>
{{{
* unordered list, level 1
** unordered list, level 2
*** unordered list, level 3
# ordered list, level 1
## ordered list, level 2
### unordered list, level 3
; definition list, term
: definition list, description
to embed items in the list on the same line of the same list item put {{block{
}}}
<<<
* unordered list, level 1
** unordered list, level 2
*** unordered list, level 3
# ordered list, level 1
## ordered list, level 2
### unordered list, level 3
; definition list, term
: definition list, description
# one {{block{
{{{
code item
}}}
}}}
# two
<<<
</tab>
<tab Blockquotes>
{{{
> blockquote, level 1
>> blockquote, level 2
>>> blockquote, level 3
<<<
blockquote
<<<
}}}
<<<
> blockquote, level 1
>> blockquote, level 2
>>> blockquote, level 3
> blockquote
<<<
!!Preformatted Text
<html><pre>
{{{
preformatted (e.g. code)
}}}
</pre></html>
<<<
{{{
preformatted (e.g. code)
}}}
<<<
</tab>
<tab Tables>
{{{
|CssClass|k
|!heading column 1|!heading column 2|
|row 1, column 1|row 1, column 2|
|row 2, column 1|row 2, column 2|
|>|COLSPAN|
|ROWSPAN| … |
|~| … |
|CssProperty:value;…| … |
|caption|c
}}}
''Annotation:''
* The {{{>}}} marker creates a "colspan", causing the current cell to merge with the one to the right.
* The {{{~}}} marker creates a "rowspan", causing the current cell to merge with the one above.
<<<
|CssClass|k
|!heading column 1|!heading column 2|
|row 1, column 1|row 1, column 2|
|row 2, column 1|row 2, column 2|
|>|COLSPAN|
|ROWSPAN| … |
|~| … |
|CssProperty:value;…| … |
|caption|c
<<<
</tab>
<tab Images>
/% TODO %/
cf. [[TiddlyWiki.com|http://www.tiddlywiki.com/#EmbeddedImages]]
{{{
[img[Motovun Jack.jpg]]
[img[http://tiddlywiki.com/favicon.ico]]
}}}
</tab>
<tab Hyperlinks>
* [[WikiWords|WikiWord]] are automatically transformed to hyperlinks to the respective tiddler
** the automatic transformation can be suppressed by preceding the respective WikiWord with a tilde ({{{~}}}): {{{~WikiWord}}}
* [[PrettyLinks]] are enclosed in square brackets and contain the desired tiddler name: {{{[[tiddler name]]}}}
** optionally, a custom title or description can be added, separated by a pipe character ({{{|}}}): {{{[[title|target]]}}}<br>''N.B.:'' In this case, the target can also be any website (i.e. URL).
</tab>
<tab Custom Styling>
* {{{@@CssProperty:value;CssProperty:value;…@@}}}<br>''N.B.:'' CSS color definitions should use lowercase letters to prevent the inadvertent creation of WikiWords.
* <html><code>{{customCssClass{…}}}</code></html>
* raw HTML can be inserted by enclosing the respective code in HTML tags: {{{<html> … </html>}}}
</tab>
<tab Special Markers>
* {{{<br>}}} forces a manual line break
* {{{----}}} creates a horizontal ruler
* [[HTML entities|http://www.tiddlywiki.com/#HtmlEntities]]
* {{{<<macroName>>}}} calls the respective [[macro|Macros]]
* To hide text within a tiddler so that it is not displayed, it can be wrapped in {{{/%}}} and {{{%/}}}.<br/>This can be a useful trick for hiding drafts or annotating complex markup.
* To prevent wiki markup from taking effect for a particular section, that section can be enclosed in three double quotes: e.g. {{{"""WikiWord"""}}}.
</tab>
</tabs>
On Sunday, May 02, 2010 12:58:20 PM, YourName imported 4 tiddlers from
[[C:\Users\SpeedDevil\Desktop\empty.html|C:\Users\SpeedDevil\Desktop\empty.html]]:
<<<
#[[StyleSheet]] - added
#[[center]] - added
#[[floatleft]] - added
#[[floatright]] - added
<<<
//{{{
config.formatters.unshift( {
name: "inlinetabs",
match: "\\<tabs",
lookaheadRegExp: /(?:<tabs (.*)>\n)((?:.|\n)*?)(?:\n<\/tabs>)/mg,
handler: function(w)
{
this.lookaheadRegExp.lastIndex = w.matchStart;
var lookaheadMatch = this.lookaheadRegExp.exec(w.source)
if(lookaheadMatch && lookaheadMatch.index == w.matchStart)
{
var cookie = lookaheadMatch[1];
var wrapper = createTiddlyElement(null,"div",null,cookie);
var tabset = createTiddlyElement(wrapper,"div",null,"tabset");
tabset.setAttribute("cookie",cookie);
var validTab = false;
var firstTab = '';
var tabregexp = /(?:<tab (.*)>)(?:(?:\n)?)((?:.|\n)*?)(?:<\/tab>)/mg;
while((m = tabregexp.exec(lookaheadMatch[2])) != null)
{
if (firstTab == '') firstTab = m[1];
var tab = createTiddlyButton(tabset,m[1],m[1],story.onClickInlineTab,"tab tabUnselected");
tab.setAttribute("tab",m[1]);
tab.setAttribute("content",m[2]);
tab.title = m[1];
if(config.options[cookie] == m[1])
validTab = true;
}
if(!validTab)
config.options[cookie] = firstTab;
w.output.appendChild(wrapper);
story.switchInlineTab(tabset,config.options[cookie]);
w.nextMatch = this.lookaheadRegExp.lastIndex;
}
}
})
Story.prototype.switchInlineTab = function(tabset,tab)
{
var cookie = tabset.getAttribute("cookie");
var theTab = null
var nodes = tabset.childNodes;
for(var t=0; t<nodes.length; t++)
if(nodes[t].getAttribute && nodes[t].getAttribute("tab") == tab)
{
theTab = nodes[t];
theTab.className = "tab tabSelected";
}
else
nodes[t].className = "tab tabUnselected"
if(theTab)
{
if(tabset.nextSibling && tabset.nextSibling.className == "tabContents")
tabset.parentNode.removeChild(tabset.nextSibling);
var tabContent = createTiddlyElement(null,"div",null,"tabContents");
tabset.parentNode.insertBefore(tabContent,tabset.nextSibling);
wikify(theTab.getAttribute("content"),tabContent);
if(cookie)
{
config.options[cookie] = tab;
saveOptionCookie(cookie);
}
}
}
Story.prototype.onClickInlineTab = function(e)
{
story.switchInlineTab(this.parentNode,this.getAttribute("tab"));
return false;
}
//}}}
/***
|''Name:''|LoadRemoteFileThroughProxy (previous LoadRemoteFileHijack)|
|''Description:''|When the TiddlyWiki file is located on the web (view over http) the content of [[SiteProxy]] tiddler is added in front of the file url. If [[SiteProxy]] does not exist "/proxy/" is added. |
|''Version:''|1.1.0|
|''Date:''|mar 17, 2007|
|''Source:''|http://tiddlywiki.bidix.info/#LoadRemoteFileHijack|
|''Author:''|BidiX (BidiX (at) bidix (dot) info)|
|''License:''|[[BSD open source license|http://tiddlywiki.bidix.info/#%5B%5BBSD%20open%20source%20license%5D%5D ]]|
|''~CoreVersion:''|2.2.0|
***/
//{{{
version.extensions.LoadRemoteFileThroughProxy = {
major: 1, minor: 1, revision: 0,
date: new Date("mar 17, 2007"),
source: "http://tiddlywiki.bidix.info/#LoadRemoteFileThroughProxy"};
if (!window.bidix) window.bidix = {}; // bidix namespace
if (!bidix.core) bidix.core = {};
bidix.core.loadRemoteFile = loadRemoteFile;
loadRemoteFile = function(url,callback,params)
{
if ((document.location.toString().substr(0,4) == "http") && (url.substr(0,4) == "http")){
url = store.getTiddlerText("SiteProxy", "/proxy/") + url;
}
return bidix.core.loadRemoteFile(url,callback,params);
}
//}}}
[[WelcomeToTiddlyspot]]
[[GettingStarted]]
[[TiddlyLinks]]
[[Formatting]]
/***
|''Name:''|PasswordOptionPlugin|
|''Description:''|Extends TiddlyWiki options with non encrypted password option.|
|''Version:''|1.0.2|
|''Date:''|Apr 19, 2007|
|''Source:''|http://tiddlywiki.bidix.info/#PasswordOptionPlugin|
|''Author:''|BidiX (BidiX (at) bidix (dot) info)|
|''License:''|[[BSD open source license|http://tiddlywiki.bidix.info/#%5B%5BBSD%20open%20source%20license%5D%5D ]]|
|''~CoreVersion:''|2.2.0 (Beta 5)|
***/
//{{{
version.extensions.PasswordOptionPlugin = {
major: 1, minor: 0, revision: 2,
date: new Date("Apr 19, 2007"),
source: 'http://tiddlywiki.bidix.info/#PasswordOptionPlugin',
author: 'BidiX (BidiX (at) bidix (dot) info',
license: '[[BSD open source license|http://tiddlywiki.bidix.info/#%5B%5BBSD%20open%20source%20license%5D%5D]]',
coreVersion: '2.2.0 (Beta 5)'
};
config.macros.option.passwordCheckboxLabel = "Save this password on this computer";
config.macros.option.passwordInputType = "password"; // password | text
setStylesheet(".pasOptionInput {width: 11em;}\n","passwordInputTypeStyle");
merge(config.macros.option.types, {
'pas': {
elementType: "input",
valueField: "value",
eventName: "onkeyup",
className: "pasOptionInput",
typeValue: config.macros.option.passwordInputType,
create: function(place,type,opt,className,desc) {
// password field
config.macros.option.genericCreate(place,'pas',opt,className,desc);
// checkbox linked with this password "save this password on this computer"
config.macros.option.genericCreate(place,'chk','chk'+opt,className,desc);
// text savePasswordCheckboxLabel
place.appendChild(document.createTextNode(config.macros.option.passwordCheckboxLabel));
},
onChange: config.macros.option.genericOnChange
}
});
merge(config.optionHandlers['chk'], {
get: function(name) {
// is there an option linked with this chk ?
var opt = name.substr(3);
if (config.options[opt])
saveOptionCookie(opt);
return config.options[name] ? "true" : "false";
}
});
merge(config.optionHandlers, {
'pas': {
get: function(name) {
if (config.options["chk"+name]) {
return encodeCookie(config.options[name].toString());
} else {
return "";
}
},
set: function(name,value) {config.options[name] = decodeCookie(value);}
}
});
// need to reload options to load passwordOptions
loadOptionsCookie();
/*
if (!config.options['pasPassword'])
config.options['pasPassword'] = '';
merge(config.optionsDesc,{
pasPassword: "Test password"
});
*/
//}}}
/***
|Name|SearchOptionsPlugin|
|Source|http://www.TiddlyTools.com/#SearchOptionsPlugin|
|Documentation|http://www.TiddlyTools.com/#SearchOptionsPluginInfo|
|Version|3.0.7|
|Author|Eric Shulman|
|License|http://www.TiddlyTools.com/#LegalStatements|
|~CoreVersion|2.1|
|Related|[[SearchOptionsPluginInfo]]|
|Type|plugin|
|Description|extend core search function with additional user-configurable options|
Adds extra options to core search function including selecting which data items to search, enabling/disabling incremental key-by-key searches, and generating a ''list of matching tiddlers'' instead of immediately displaying all matches. This plugin also adds syntax for rendering 'search links' within tiddler content to embed one-click searches using pre-defined 'hard-coded' search terms.
!!!!!Documentation
>see [[SearchOptionsPluginInfo]]
!!!!!Configuration
<<<
Search in:
<<option chkSearchTitles>> titles <<option chkSearchText>> text <<option chkSearchTags>> tags <<option chkSearchFields>> fields <<option chkSearchShadows>> shadows
<<option chkSearchHighlight>> Highlight matching text in displayed tiddlers
<<option chkSearchList>> Show list of matches
<<option chkSearchListTiddler>> Write list to [[SearchResults]] tiddler
<<option chkSearchTitlesFirst>> Show title matches first
<<option chkSearchByDate>> Sort matching tiddlers by modification date (most recent first)
<<option chkIncrementalSearch>> Incremental key-by-key search: {{twochar{<<option txtIncrementalSearchMin>>}}} or more characters, {{threechar{<<option txtIncrementalSearchDelay>>}}} msec delay
<<option chkSearchOpenTiddlers>> Search only in tiddlers that are currently displayed
<<option chkSearchExcludeTags>> Exclude tiddlers tagged with: <<option txtSearchExcludeTags>>
<<<
!!!!!Revisions
<<<
2010.02.25 3.0.7 in formatSearchResults_list, added declaration of local 'co' variable
|please see [[SearchOptionsPluginInfo]] for additional revision details|
2005.10.18 1.0.0 Initial Release
<<<
!!!!!Code
***/
//{{{
version.extensions.SearchOptionsPlugin= {major: 3, minor: 0, revision: 7, date: new Date(2010,2,25)};
var defaults={
chkSearchTitles: true,
chkSearchText: true,
chkSearchTags: true,
chkSearchFields: true,
chkSearchTitlesFirst: true,
chkSearchList: true,
chkSearchHighlight: true,
chkSearchListTiddler: true,
chkSearchByDate: false,
chkIncrementalSearch: true,
chkSearchShadows: true,
chkSearchOpenTiddlers: false,
chkSearchExcludeTags: true,
txtSearchExcludeTags: 'excludeSearch',
txtIncrementalSearchDelay: 500,
txtIncrementalSearchMin: 3
}; for (var id in defaults) if (config.options[id]===undefined)
config.options[id]=defaults[id];
if (config.macros.search.reportTitle==undefined)
config.macros.search.reportTitle="SearchResults"; // note: not a cookie!
config.macros.search.label+="\xa0"; // a little bit of space just because it looks better
//}}}
// // searchLink: {{{[search[text to find]] OR [search[text to display|text to find]]}}}
//{{{
config.formatters.push( {
name: "searchLink",
match: "\\[search\\[",
lookaheadRegExp: /\[search\[(.*?)(?:\|(.*?))?\]\]/mg,
prompt: "search for: '%0'",
handler: function(w)
{
this.lookaheadRegExp.lastIndex = w.matchStart;
var lookaheadMatch = this.lookaheadRegExp.exec(w.source);
if(lookaheadMatch && lookaheadMatch.index == w.matchStart) {
var label=lookaheadMatch[1];
var text=lookaheadMatch[2]||label;
var prompt=this.prompt.format([text]);
var btn=createTiddlyButton(w.output,label,prompt,
function(){story.search(this.getAttribute("searchText"))},"searchLink");
btn.setAttribute("searchText",text);
w.nextMatch = this.lookaheadRegExp.lastIndex;
}
}
});
//}}}
// // incremental search uses option settings instead of hard-coded delay and minimum input values
//{{{
var fn=config.macros.search.onKeyPress;
fn=fn.toString().replace(/500/g, "config.options.txtIncrementalSearchDelay||500");
fn=fn.toString().replace(/> 2/g, ">=(config.options.txtIncrementalSearchMin||3)");
eval("config.macros.search.onKeyPress="+fn);
//}}}
// // REPLACE story.search() for option to "show search results in a list"
//{{{
Story.prototype.search = function(text,useCaseSensitive,useRegExp)
{
var co=config.options; // abbrev
var re=new RegExp(useRegExp ? text : text.escapeRegExp(),useCaseSensitive ? "mg" : "img");
if (config.options.chkSearchHighlight) highlightHack=re;
var matches = store.search(re,co.chkSearchByDate?"modified":"title","");
if (co.chkSearchByDate) matches=matches.reverse(); // most recent first
var q = useRegExp ? "/" : "'";
clearMessage();
if (!matches.length) {
if (co.chkSearchListTiddler) discardSearchResults();
displayMessage(config.macros.search.failureMsg.format([q+text+q]));
} else {
if (co.chkSearchList||co.chkSearchListTiddler)
reportSearchResults(text,matches);
else {
var titles = []; for(var t=0; t<matches.length; t++) titles.push(matches[t].title);
this.closeAllTiddlers(); story.displayTiddlers(null,titles);
displayMessage(config.macros.search.successMsg.format([matches.length, q+text+q]));
}
}
highlightHack = null;
}
//}}}
// // REPLACE store.search() for enhanced searching/sorting options
//{{{
TiddlyWiki.prototype.search = function(searchRegExp,sortField,excludeTag,match)
{
var co=config.options; // abbrev
var tids = this.reverseLookup("tags",excludeTag,!!match,sortField);
var opened=[]; story.forEachTiddler(function(tid,elem){opened.push(tid);});
// eliminate tiddlers tagged with excluded tags
if (co.chkSearchExcludeTags&&co.txtSearchExcludeTags.length) {
var ex=co.txtSearchExcludeTags.readBracketedList();
var temp=[]; for(var t=tids.length-1; t>=0; t--)
if (!tids[t].tags.containsAny(ex)) temp.push(tids[t]);
tids=temp;
}
// scan for matching titles first...
var results = [];
if (co.chkSearchTitles) {
for(var t=0; t<tids.length; t++) {
if (co.chkSearchOpenTiddlers && !opened.contains(tids[t].title)) continue;
if(tids[t].title.search(searchRegExp)!=-1) results.push(tids[t]);
}
if (co.chkSearchShadows)
for (var t in config.shadowTiddlers) {
if (co.chkSearchOpenTiddlers && !opened.contains(t)) continue;
if ((t.search(searchRegExp)!=-1) && !store.tiddlerExists(t))
results.push((new Tiddler()).assign(t,config.shadowTiddlers[t]));
}
}
// then scan for matching text, tags, or field data
for(var t=0; t<tids.length; t++) {
if (co.chkSearchOpenTiddlers && !opened.contains(tids[t].title)) continue;
if (co.chkSearchText && tids[t].text.search(searchRegExp)!=-1)
results.pushUnique(tids[t]);
if (co.chkSearchTags && tids[t].tags.join(" ").search(searchRegExp)!=-1)
results.pushUnique(tids[t]);
if (co.chkSearchFields && store.forEachField!=undefined)
store.forEachField(tids[t],
function(tid,field,val) {
if (val.search(searchRegExp)!=-1) results.pushUnique(tids[t]);
},
true); // extended fields only
}
// then check for matching text in shadows
if (co.chkSearchShadows)
for (var t in config.shadowTiddlers) {
if (co.chkSearchOpenTiddlers && !opened.contains(t)) continue;
if ((config.shadowTiddlers[t].search(searchRegExp)!=-1) && !store.tiddlerExists(t))
results.pushUnique((new Tiddler()).assign(t,config.shadowTiddlers[t]));
}
// if not 'titles first', or sorting by modification date,
// re-sort results to so titles, text, tag and field matches are mixed together
if(!sortField) sortField = "title";
var bySortField=function(a,b){
if(a[sortField]==b[sortField])return(0);else return(a[sortField]<b[sortField])?-1:+1;
}
if (!co.chkSearchTitlesFirst || co.chkSearchByDate) results.sort(bySortField);
return results;
}
//}}}
// // HIJACK core {{{<<search>>}}} macro to add "report" and "simple inline" output
//{{{
config.macros.search.SOP_handler=config.macros.search.handler;
config.macros.search.handler = function(place,macroName,params)
{
// if "report", use SearchOptionsPlugin report generator for inline output
if (params[1]&¶ms[1].substr(0,6)=="report") {
var keyword=params[0];
var options=params[1].split("=")[1]; // split "report=option+option+..."
var heading=params[2]?params[2].unescapeLineBreaks():"";
var matches=store.search(new RegExp(keyword.escapeRegExp(),"img"),"title","excludeSearch");
if (matches.length) wikify(heading+window.formatSearchResults(keyword,matches,options),place);
} else if (params[1]) {
var keyword=params[0];
var heading=params[1]?params[1].unescapeLineBreaks():"";
var seperator=params[2]?params[2].unescapeLineBreaks():", ";
var matches=store.search(new RegExp(keyword.escapeRegExp(),"img"),"title","excludeSearch");
if (matches.length) {
var out=[];
for (var m=0; m<matches.length; m++) out.push("[["+matches[m].title+"]]");
wikify(heading+out.join(seperator),place);
}
} else
config.macros.search.SOP_handler.apply(this,arguments);
};
//}}}
// // SearchResults panel handling
//{{{
setStylesheet(".searchResults { padding:1em 1em 0 1em; }","searchResults"); // matches std tiddler padding
config.macros.search.createPanel=function(text,matches,body) {
function getByClass(e,c) { var d=e.getElementsByTagName("div");
for (var i=0;i<d.length;i++) if (hasClass(d[i],c)) return d[i]; }
var panel=createTiddlyElement(null,"div","searchPanel","searchPanel");
this.renderPanel(panel,text,matches,body);
var oldpanel=document.getElementById("searchPanel");
if (!oldpanel) { // insert new panel just above tiddlers
var da=document.getElementById("displayArea");
da.insertBefore(panel,da.firstChild);
} else { // if panel exists
var oldwrap=getByClass(oldpanel,"searchResults");
var newwrap=getByClass(panel,"searchResults");
// if no prior content, just insert new content
if (!oldwrap) oldpanel.insertBefore(newwrap,null);
else { // swap search results content but leave containing panel intact
oldwrap.style.display='block'; // unfold wrapper if needed
var i=oldwrap.getElementsByTagName("input")[0]; // get input field
if (i) { var pos=this.getCursorPos(i); i.onblur=null; } // get cursor pos, ignore blur
oldpanel.replaceChild(newwrap,oldwrap);
panel=oldpanel; // use existing panel
}
}
this.showPanel(true,pos);
return panel;
}
config.macros.search.renderPanel=function(panel,text,matches,body) {
var wrap=createTiddlyElement(panel,"div",null,"searchResults");
wrap.onmouseover = function(e){ addClass(this,"selected"); }
wrap.onmouseout = function(e){ removeClass(this,"selected"); }
// create toolbar: "open all", "fold/unfold", "close"
var tb=createTiddlyElement(wrap,"div",null,"toolbar");
var b=createTiddlyButton(tb, "open all", "open all matching tiddlers", function() {
story.displayTiddlers(null,this.getAttribute("list").readBracketedList()); return false; },"button");
var list=""; for(var t=0;t<matches.length;t++) list+='[['+matches[t].title+']] ';
b.setAttribute("list",list);
var b=createTiddlyButton(tb, "fold", "toggle display of search results", function() {
config.macros.search.foldPanel(this); return false; },"button");
var b=createTiddlyButton(tb, "close", "dismiss search results", function() {
config.macros.search.showPanel(false); return false; },"button");
createTiddlyText(createTiddlyElement(wrap,"div",null,"title"),"Search for: "+text); // title
wikify(body,createTiddlyElement(wrap,"div",null,"viewer")); // report
return panel;
}
config.macros.search.showPanel=function(show,pos) {
var panel=document.getElementById("searchPanel");
var i=panel.getElementsByTagName("input")[0];
i.onfocus=show?function(){config.macros.search.stayFocused(true);}:null;
i.onblur=show?function(){config.macros.search.stayFocused(false);}:null;
if (show && panel.style.display=="block") { // if shown, grab focus, restore cursor
if (i&&this.stayFocused()) { i.focus(); this.setCursorPos(i,pos); }
return;
}
if(!config.options.chkAnimate) {
panel.style.display=show?"block":"none";
if (!show) { removeChildren(panel); config.macros.search.stayFocused(false); }
} else {
var s=new Slider(panel,show,false,show?"none":"children");
s.callback=function(e,p){e.style.overflow="visible";}
anim.startAnimating(s);
}
return panel;
}
config.macros.search.foldPanel=function(button) {
var d=document.getElementById("searchPanel").getElementsByTagName("div");
for (var i=0;i<d.length;i++) if (hasClass(d[i],"viewer")) var v=d[i]; if (!v) return;
var show=v.style.display=="none";
if(!config.options.chkAnimate)
v.style.display=show?"block":"none";
else {
var s=new Slider(v,show,false,"none");
s.callback=function(e,p){e.style.overflow="visible";}
anim.startAnimating(s);
}
button.innerHTML=show?"fold":"unfold";
return false;
}
config.macros.search.stayFocused=function(keep) { // TRUE/FALSE=set value, no args=get value
if (keep===undefined) return this.keepReportInFocus;
this.keepReportInFocus=keep;
return keep
}
config.macros.search.getCursorPos=function(i) {
var s=0; var e=0; if (!i) return { start:s, end:e };
try {
if (i.setSelectionRange) // FF
{ s=i.selectionStart; e=i.selectionEnd; }
if (document.selection && document.selection.createRange) { // IE
var r=document.selection.createRange().duplicate();
var len=r.text.length; s=0-r.moveStart('character',-100000); e=s+len;
}
}catch(e){};
return { start:s, end:e };
}
config.macros.search.setCursorPos=function(i,pos) {
if (!i||!pos) return; var s=pos.start; var e=pos.end;
if (i.setSelectionRange) //FF
i.setSelectionRange(s,e);
if (i.createTextRange) // IE
{ var r=i.createTextRange(); r.collapse(true); r.moveStart("character",s); r.select(); }
}
//}}}
// // SearchResults report generation
// note: these functions are defined globally, so they can be more easily redefined to customize report formats//
//{{{
if (!window.reportSearchResults) window.reportSearchResults=function(text,matches)
{
var cms=config.macros.search; // abbrev
var body=window.formatSearchResults(text,matches);
if (!config.options.chkSearchListTiddler) // show #searchResults panel
window.scrollTo(0,ensureVisible(cms.createPanel(text,matches,body)));
else { // write [[SearchResults]] tiddler
var title=cms.reportTitle;
var who=config.options.txtUserName;
var when=new Date();
var tags="excludeLists excludeSearch temporary";
var tid=store.getTiddler(title); if (!tid) tid=new Tiddler();
tid.set(title,body,who,when,tags);
store.addTiddler(tid);
story.closeTiddler(title);
story.displayTiddler(null,title);
}
}
if (!window.formatSearchResults) window.formatSearchResults=function(text,matches,opt)
{
var body='';
var title=config.macros.search.reportTitle
var q = config.options.chkRegExpSearch ? "/" : "'";
if (!opt) var opt="all";
var parts=opt.split("+");
for (var i=0; i<parts.length; i++) { var p=parts[i].toLowerCase();
if (p=="again"||p=="all") body+=window.formatSearchResults_again(text,matches);
if (p=="summary"||p=="all") body+=window.formatSearchResults_summary(text,matches);
if (p=="list"||p=="all") body+=window.formatSearchResults_list(text,matches);
if (p=="buttons"||p=="all") body+=window.formatSearchResults_buttons(text,matches);
}
return body;
}
if (!window.formatSearchResults_again) window.formatSearchResults_again=function(text,matches)
{
var title=config.macros.search.reportTitle
var body='';
// search again
body+='{{span{<<search "'+text.replace(/"/g,'"')+'">> /%\n';
body+='%/<html><input type="button" value="search again"';
body+=' onclick="var t=this.parentNode.parentNode.getElementsByTagName(\'input\')[0];';
body+=' config.macros.search.doSearch(t); return false;">';
body+=' <a href="javascript:;" onclick="';
body+=' var e=this.parentNode.nextSibling;';
body+=' var show=e.style.display!=\'block\';';
body+=' if(!config.options.chkAnimate) e.style.display=show?\'block\':\'none\';';
body+=' else anim.startAnimating(new Slider(e,show,false,\'none\'));';
body+=' return false;">options...</a>';
body+='</html>@@display:none;border-left:1px dotted;margin-left:1em;padding:0;padding-left:.5em;font-size:90%;/%\n';
body+=' %/<<option chkSearchTitles>>titles /%\n';
body+=' %/<<option chkSearchText>>text /%\n';
body+=' %/<<option chkSearchTags>>tags /%\n';
body+=' %/<<option chkSearchFields>>fields /%\n';
body+=' %/<<option chkSearchShadows>>shadows\n';
body+=' <<option chkCaseSensitiveSearch>>case-sensitive /%\n';
body+=' %/<<option chkRegExpSearch>>text patterns /%\n';
body+=' %/<<option chkSearchByDate>>sorted by date\n';
body+=' <<option chkSearchHighlight>> highlight matching text in displayed tiddlers\n';
body+=' <<option chkIncrementalSearch>>incremental key-by-key search: /%\n';
body+=' %/{{twochar{<<option txtIncrementalSearchMin>>}}} or more characters, /%\n';
body+=' %/{{threechar{<<option txtIncrementalSearchDelay>>}}} msec delay\n';
body+=' <<option chkSearchOpenTiddlers>> search only in tiddlers that are currently displayed\n';
body+=' <<option chkSearchExcludeTags>>exclude tiddlers tagged with:\n';
body+=' {{editor{<<option txtSearchExcludeTags>>}}}/%\n';
body+='%/@@}}}\n\n';
return body;
}
if (!window.formatSearchResults_summary) window.formatSearchResults_summary=function(text,matches)
{
// summary: nn tiddlers found matching '...', options used
var body='';
var co=config.options; // abbrev
var title=config.macros.search.reportTitle
var q = co.chkRegExpSearch ? "/" : "'";
body+="''"+config.macros.search.successMsg.format([matches.length,q+"{{{"+text+"}}}"+q])+"''\n";
var opts=[];
if (co.chkSearchTitles) opts.push("titles");
if (co.chkSearchText) opts.push("text");
if (co.chkSearchTags) opts.push("tags");
if (co.chkSearchFields) opts.push("fields");
if (co.chkSearchShadows) opts.push("shadows");
if (co.chkSearchOpenTiddlers) body+="^^//search limited to displayed tiddlers only//^^\n";
body+="~~ searched in "+opts.join(" + ")+"~~\n";
body+=(co.chkCaseSensitiveSearch||co.chkRegExpSearch?"^^ using ":"")
+(co.chkCaseSensitiveSearch?"case-sensitive ":"")
+(co.chkRegExpSearch?"pattern ":"")
+(co.chkCaseSensitiveSearch||co.chkRegExpSearch?"matching^^\n":"");
return body;
}
if (!window.formatSearchResults_list) window.formatSearchResults_list=function(text,matches)
{
// bullet list of links to matching tiddlers
var body='';
var co=config.options; // abbrev
var pattern=co.chkRegExpSearch?text:text.escapeRegExp();
var sensitive=co.chkCaseSensitiveSearch?"mg":"img";
var link='{{tiddlyLinkExisting{<html><nowiki><a href="javascript:;" onclick="'
+'if(config.options.chkSearchHighlight)'
+' highlightHack=new RegExp(\x27'+pattern+'\x27.escapeRegExp(),\x27'+sensitive+'\x27);'
+'story.displayTiddler(null,\x27%0\x27);'
+'highlightHack = null; return false;'
+'" title="%2">%1</a></html>}}}';
for(var t=0;t<matches.length;t++) {
body+="* ";
if (co.chkSearchByDate)
body+=matches[t].modified.formatString('YYYY.0MM.0DD 0hh:0mm')+" ";
var title=matches[t].title;
var fixup=title.replace(/'/g,"\\x27").replace(/"/g,"\\x22");
var tid=store.getTiddler(title);
var tip=tid?tid.getSubtitle():''; tip=tip.replace(/"/g,""");
body+=link.format([fixup,title,tip])+'\n';
}
return body;
}
if (!window.formatSearchResults_buttons) window.formatSearchResults_buttons=function(text,matches)
{
// embed buttons only if writing SearchResults to tiddler
if (!config.options.chkSearchListTiddler) return "";
// "open all" button
var title=config.macros.search.reportTitle;
var body="";
body+="@@diplay:block;<html><input type=\"button\" href=\"javascript:;\" "
+"onclick=\"story.displayTiddlers(null,[";
for(var t=0;t<matches.length;t++)
body+="'"+matches[t].title.replace(/\'/mg,"\\'")+"'"+((t<matches.length-1)?", ":"");
body+="],1);\" accesskey=\"O\" value=\"open all matching tiddlers\"></html> ";
// "discard SearchResults" button
body+="<html><input type=\"button\" href=\"javascript:;\" "
+"onclick=\"discardSearchResults()\" value=\"discard "+title+"\"></html>";
body+="@@\n";
return body;
}
if (!window.discardSearchResults) window.discardSearchResults=function()
{
// remove the tiddler
story.closeTiddler(config.macros.search.reportTitle);
store.deleteTiddler(config.macros.search.reportTitle);
store.notify(config.macros.search.reportTitle,true);
}
//}}}
/***
|Name|SearchOptionsPluginInfo|
|Source|http://www.TiddlyTools.com/#SearchOptionsPlugin|
|Documentation|http://www.TiddlyTools.com/#SearchOptionsPluginInfo|
|Version|3.0.7|
|Author|Eric Shulman|
|License|http://www.TiddlyTools.com/#LegalStatements|
|~CoreVersion|2.1|
|Type|documentation|
|Description|Documentation for SearchOptionsPlugin|
Extend core search function with additional user-configurable options including selecting which data items to search, enabling/disabling incremental key-by-key searches, and generating a ''list of matching tiddler'' instead of immediately displaying all matches. This plugin also adds syntax for rendering 'search links' within tiddler content to embed one-click searches using pre-defined 'hard-coded' search terms.
!!!!!Search link Syntax
<<<
To insert a 'search link' into tiddler content, you can write:
{{{
[search[text to find]]
}}}
or
{{{
[search[text to display|text to find]]
}}}
Clicking on the resulting search link will trigger the search functionality, just as if the specified 'text to find' had been entered into the standard search input field usually displayed in the document sidebar.
<<<
!!!!!Inline output: search macro syntax
<<<
Alternatively, to embed search results lists directly into your tiddler content, you can use:
{{{
<<search "text" report>> (report is a literal keyword)
<<search "text" "heading" "separator">> (simple inline generator)
}}}
<<<
!!!!!Inline output examples:
<<<
*+++*[<<search "wood">>]>...
<<search "wood">>
===
*+++*[<<search "wood" "/%%/">>]>...
<<search "wood" "/%%/">>
===
*+++*[<<search "wood" "See also: ">>]>...
<<search "wood" "See also: ">>
===
*+++*[<<search "wood" "See also:\n*" "\n*">>]>...
<<search "wood" "See also:\n*" "\n*">>
===
*+++*[<<search "wood" report=list "See also:">>]>...
<<search "wood" report=list "See Also:" >>
===
*+++*[<<search "wood" report>>]>...
<<search "wood" report>>
===
*+++*[<<search "wood" report=>>]>...
<<search "wood" report=>>
===
*+++*[<<search "wood" report=all>>]>...
<<search "wood" report=all>>
===
*+++*[<<search "wood" report=summary+buttons+again+list>>]>...
<<search "wood" report=summary+buttons+again+list>>
===
*+++*[<<search "wood" report=summary+again>>]>...
<<search "wood" report=summary+again>>
===
*+++*[<<search "wood" report=summary>>]>...
<<search "wood" report=summary>>
===
<<<
!!!!!Configuration
<<<
Search in:
<<option chkSearchTitles>> titles <<option chkSearchText>> text <<option chkSearchTags>> tags <<option chkSearchFields>> fields <<option chkSearchShadows>> shadows
{{{<<option chkSearchTitles>> <<option chkSearchText>> <<option chkSearchTags>>}}}
{{{<<option chkSearchFields>> <<option chkSearchShadows>>}}}
<<option chkSearchHighlight>> Highlight matching text in displayed tiddlers {{{<<option chkSearchHighlight>>}}}
<<option chkSearchList>> Show list of matches {{{<<option chkSearchList>>}}}
<<option chkSearchListTiddler>> Write list to [[SearchResults]] tiddler {{{<<option chkSearchListTiddler>>}}}
<<option chkSearchTitlesFirst>> Show title matches first {{{<<option chkSearchTitlesFirst>>}}}
<<option chkSearchByDate>> Sort matching tiddlers by date {{{<<option chkSearchByDate>>}}}
<<option chkIncrementalSearch>> Incremental key-by-key search: {{twochar{<<option txtIncrementalSearchMin>>}}} or more characters, {{threechar{<<option txtIncrementalSearchDelay>>}}} msec delay
{{{<<option chkSearchIncremental>> <<option txtSearchIncrementalMin>> <<option txtSearchIncrementalDelay>>}}}
<<option chkSearchOpenTiddlers>> Search only in tiddlers that are currently displayed {{{<<option chkSearchOpenTiddlers>>}}}
<<option chkSearchExcludeTags>> Exclude tiddlers tagged with: <<option txtSearchExcludeTags>>
{{{<<option chkSearchExcludeTags>>}}} {{{<<option txtSearchExcludeTags>>}}}
<<<
!!!!!Revisions
<<<
2010.02.25 3.0.7 in formatSearchResults_list, added declaration of local 'co' variable
2009.09.22 3.0.6 in TiddlyWiki.prototype.search, added 'match' param for core compatibility
2009.01.16 3.0.5 added chkSearchOpenTiddlers option to limit searches to displayed tiddlers only
2009.01.15 3.0.4 in formatSearchResults_list(), corrected link generation to properly handle single-quotes and double-quotes in tiddler titles
2009.01.09 3.0.3 added chkSearchHighlight to optionally disable highlighting of matched text
2009.01.05 3.0.2 in formatSearchResults_list(), set/clear 'highlightHack' via HTML links so that search term will be highlighted when displaying tiddlers.
2008.10.14 3.0.1 changed panel class from "tiddler" to "searchPanel" and added style definition for "searchPanel". Fixes ticket #771 (in IE, links from search results were reporting errors due to "fake" tiddler class wrapper)
2008.10.02 3.0.0 added optional list of tags to use for excluding tiddler from searches (default="excludeLists").
2008.09.24 2.9.9 performance improvment to reportSearchResults(): when rendering a real SearchResults tiddler, store.notify() isn't needed since the results tiddler is always explicitly closed and redrawn each time.
2008.09.20 2.9.8 corrected createPanel() and renderPanel() so toolbar will be correctly shown/hidden on mouseover/mouseout.
2008.09.19 2.9.7 fixes to panel handling for IE, Safari, and others. Changed panel id to #searchPanel and added .searchResults CSS class wrapper around panel content. Fixed fold/unfold handling.
2008.09.18 2.9.6 refactored panel handling code, added 'fold/unfold' panel toolbar command, added dynamic 'title' (shows search term), added txtIncrementalSearchMin option
2008.09.17 2.9.5 added focus and cursor handling for 'search again' field in #searchResults DIV report so that an incremental key-by-key search doesn't interfere with continuous typing into the field.
2008.09.17 2.9.4 fix 'flicker' when updating #searchResults DIV by wikify()ing to an 'offscreen' DIV and then using replaceChild() instead of using removeChildren() followed by wikify()
2008.09.16 2.9.3 changed report layout, added "search again" and collapsible 'options' section with incremental search checkbox and "txtIncrementalSearchDelay" timer tweak to onKeyPress()
2008.08.25 2.9.2 added animation to search results DIV. Also, the #searchResults DOM element is only auto-created if it does not exist ... and when closed, the DIV is simply hidden rather than removed. This allows custom placement of search results report in the PageTemplate definition.
2008.08.23 2.9.1 story column search results uses {{{<<moveablePanel>>}}}
2008.08.22 2.9.0 default is now to show search results at top of story column, similar to FND's SimpleSearchPlugin display, with an option to generate SearchResults tiddler as before. Also changed 'chkSearchIncremental' to 'chkIncrementalSearch' to match core option variable
2008.08.12 2.8.2 change default for chkSearchByDate back to FALSE, and adjusted "list" and "again" output formats (minor tweaks requested by PhilWhitehouse for use on TiddlyWiki.com)
2008.08.11 2.8.1 changed defaults for chkSearchTitlesFirst, chkSearchList and chkSearchShadows to TRUE to enable enhanced search results output as soon as plugin is installed.
2008.06.21 2.8.0 added extended syntax for {{{<<search "text" report heading>> and <<search "text" "heading" "seperator">>}}}
2008.05.03 2.7.1 in searchLink formatter handler(), use separate setAttribute() call instead of passing attribs to createTiddlyButton(). Avoids conflict with errant code in TiddlerNotesPlugin (v2.1 26/10/07)
2008.04.29 2.7.0 added searchLink formatter (syntax: {{{[search[text]]}}} or {{{[search[display|text]]}}})
2008.04.08 2.6.2 don't automatically add options to AdvancedOptions shadow tiddler
2007.02.17 2.6.1 added redefinition of config.macros.search.onKeyPress() to restore check to bypass key-by-key searching (i.e., when chkSearchIncremental==false), which had been unintentionally removed with v2.6.0
2007.02.13 2.6.0 remove redefinition of config.macros.search.handler since core now includes handling for ENTER key.
2007.02.08 2.5.1 include 'temporary' tag when creating SearchResults (for use with TemporaryTiddlersPlugin)
2007.01.29 2.5.0 added support for "sort results by date". Default is to sort alphabetically (standard). When sorted by dates, most recent changes are shown first
2006.10.10 2.4.0 added support for "search in tiddler data" (tiddler.fields) Default is to search extended data.
2006.04.06 2.3.0 added support for "search in shadow tiddlers". Default is *not* to search in the shadows (i.e. standard TW behavior). Note: if a shadow tiddler has a 'real' counterpart, only the real tiddler is searched, since the shadow is inaccessible for viewing/editing.
2006.02.03 2.2.1 rewrite timeout clearing code and blank search text handling to match 2.0.4 core release changes. note that core no longer permits "blank=all" searches, so neither does this plugin. To search for all, use "." with text patterns enabled.
2006.02.02 2.2.0 in search.handler(), KeyHandler() function clears 'left over' timeout when search input is < 3 chars. Prevents searching on shorter text when shortened by rapid backspaces (<500msec)
2006.02.01 2.1.9 in Story.prototype.search(), correct inverted logic for using/not using regular expressions when searching
also, blank search text now presents "No search text. Continue anyway?" confirm() message box, so search on blank can still be processed if desired by user.
2006.02.01 2.1.8 in doSearch(), added alert/return if search text is blank
2006.01.20 2.1.7 fixed setting of config.macros.search.reportTitle so that Tweaks can override it.
2006.01.19 2.1.6 improved SearchResults formatting, added a "search again" form to the report (based on a suggestion from MorrisGray)
define results report title using config.macros.search.reportTitle instead of hard-coding the tiddler title
2006.01.18 2.1.5 Created separate functions for reportSearchResults(text,matches) and discardSearchResults(), so that other developers can create alternative report generators.
2006.01.17 2.1.4 Use regExp.search() instead of regExp.test() to scan for matches. Correctd the problem where only half the matching tiddlers (the odd-numbered ones) were being reported.
2006.01.15 2.1.3 Added information (date/time, username, search options used) to SearchResults output
2006.01.10 2.1.2 use displayTiddlers() to render matched tiddlers. This lets you display multiple matching tiddlers, even if SinglePageModePlugin is enabled.
2006.01.08 2.1.1 corrected invalid variable reference, "txt.value" to "text" in story.search()
2006.01.08 2.1.0 re-write to match new store.search(), store.search.handler() and story.search() functions.
2005.12.30 2.0.0 Upgraded to TW2.0. When rendering SearchResults tiddler, closeTiddler() first to ensure display is refreshed.
2005.12.26 1.4.0 added option to search for matching text in tiddler tags
2005.12.21 1.3.7 use \\ to 'escape' single quotes in tiddler titles when generating "Open all matching tiddlers" link. Also, added access key: "O", to trigger "open all" link. Based on a suggestion by UdoBorkowski.
2005.12.18 1.3.6 call displayMessage() AFTER showing matching tiddlers so message is not cleared too soon
2005.12.17 1.3.5 if no matches found, just display message and delete any existing SearchResults tiddler.
2005.12.17 1.3.4 use {/%%/{/%%/{ and }/%%/}/%%/} to 'escape' display text in SearchResults tiddler to ensure that formatting contained in search string is not rendered. Based on a suggestion by UdoBorkowski.
2005.12.14 1.3.3 tag SearchResults tiddler with 'excludeSearch' so it won't list itself in subsequent searches. Based on a suggestion by UdoBorkowski.
2005.12.14 1.3.2 added "open all matching tiddlers..." link to search results output. Based on a suggestion by UdoBorkowski.
2005.12.10 1.3.1 added "discard search results" link to end of search list tiddler output for quick self-removal of 'SearchResults' tiddler.
2005.12.01 1.3.0 added chkSearchIncremental to enable/disable 'incremental' searching (i.e., search after each keystroke) (default is ENABLED).
added handling for Enter key so it can be used to start a search. Based on a suggestion by LyallPearce
2005.11.25 1.2.1 renamed from SearchTitleOrTextPlugin to SearchOptionsPlugin
2005.11.25 1.2.0 added chkSearchList option. Based on a suggestion by RodneyGomes
2005.10.19 1.1.0 added chkSearchTitlesFirst option. Based on a suggestion by ChristianHauck
2005.10.18 1.0.0 Initial Release. Based on a suggestion by LyallPearce.
<<<
{{span{<<search "search">> /%
%/<html><input type="button" value="search again" onclick="var t=this.parentNode.parentNode.getElementsByTagName('input')[0]; config.macros.search.doSearch(t); return false;"> <a href="javascript:;" onclick=" var e=this.parentNode.nextSibling; var show=e.style.display!='block'; if(!config.options.chkAnimate) e.style.display=show?'block':'none'; else anim.startAnimating(new Slider(e,show,false,'none')); return false;">options...</a></html>@@display:none;border-left:1px dotted;margin-left:1em;padding:0;padding-left:.5em;font-size:90%;/%
%/<<option chkSearchTitles>>titles /%
%/<<option chkSearchText>>text /%
%/<<option chkSearchTags>>tags /%
%/<<option chkSearchFields>>fields /%
%/<<option chkSearchShadows>>shadows
<<option chkCaseSensitiveSearch>>case-sensitive /%
%/<<option chkRegExpSearch>>text patterns /%
%/<<option chkSearchByDate>>sorted by date
<<option chkSearchHighlight>> highlight matching text in displayed tiddlers
<<option chkIncrementalSearch>>incremental key-by-key search: /%
%/{{twochar{<<option txtIncrementalSearchMin>>}}} or more characters, /%
%/{{threechar{<<option txtIncrementalSearchDelay>>}}} msec delay
<<option chkSearchOpenTiddlers>> search only in tiddlers that are currently displayed
<<option chkSearchExcludeTags>>exclude tiddlers tagged with:
{{editor{<<option txtSearchExcludeTags>>}}}/%
%/@@}}}
''4 tiddlers found matching '{{{search}}}'''
~~ searched in titles + text + tags + fields + shadows~~
* {{tiddlyLinkExisting{<html><nowiki><a href="javascript:;" onclick="if(config.options.chkSearchHighlight) highlightHack=new RegExp('search'.escapeRegExp(),'img');story.displayTiddler(null,'SearchOptionsPluginInfo');highlightHack = null; return false;" title="SearchOptionsPluginInfo - YourName, Mon 01 Apr 2013 09:12:00 PM PDT">SearchOptionsPluginInfo</a></html>}}}
* {{tiddlyLinkExisting{<html><nowiki><a href="javascript:;" onclick="if(config.options.chkSearchHighlight) highlightHack=new RegExp('search'.escapeRegExp(),'img');story.displayTiddler(null,'SearchOptionsPlugin');highlightHack = null; return false;" title="SearchOptionsPlugin - YourName, Mon 01 Apr 2013 09:11:00 PM PDT">SearchOptionsPlugin</a></html>}}}
* {{tiddlyLinkExisting{<html><nowiki><a href="javascript:;" onclick="if(config.options.chkSearchHighlight) highlightHack=new RegExp('search'.escapeRegExp(),'img');story.displayTiddler(null,'SideBarOptions');highlightHack = null; return false;" title="">SideBarOptions</a></html>}}}
* {{tiddlyLinkExisting{<html><nowiki><a href="javascript:;" onclick="if(config.options.chkSearchHighlight) highlightHack=new RegExp('search'.escapeRegExp(),'img');story.displayTiddler(null,'OptionsPanel');highlightHack = null; return false;" title="">OptionsPanel</a></html>}}}
@@diplay:block;<html><input type="button" href="javascript:;" onclick="story.displayTiddlers(null,['SearchOptionsPluginInfo', 'SearchOptionsPlugin', 'SideBarOptions', 'OptionsPanel'],1);" accesskey="O" value="open all matching tiddlers"></html> <html><input type="button" href="javascript:;" onclick="discardSearchResults()" value="discard SearchResults"></html>@@
/***
|''Name''|ShowDown|
|''Description''|Allows to use MarkDown syntax in a tiddler|
|''Documentation''|http://tobibeer.tiddlyspace.com/#ShowDown|
|''Author''|Tobias Beer|
|''Contributions''|Mario Pietsch, Paul Downey|
|''Version''|0.9.1|
|''Requires''|https://raw.github.com/tobibeer/TiddlyWikiPlugins/master/resources/ShowDown/ShowDown.js|
|''Source''|https://raw.github.com/tobibeer/TiddlyWikiPlugins/master/plugins/ShowDown.js|
|''License''|[[Creative Commons Attribution-ShareAlike 2.5 License|http://creativecommons.org/licenses/by-sa/2.5/]]|
|''~CoreVersion''|2.5.3|
|''Type''|plugin|
!Installation
Install ShowDown.js first and tag it <<tag systemConfig>>.
!Code
***/
//{{{
config.formatters.push({
name: "showdown",
match: "§§§",
lookaheadRegExp: /\s?§§§((?:.|\n)*?)§§§\s?/mg,
handler: function (w) {
var match, t;
this.lookaheadRegExp.lastIndex = w.matchStart;
match = this.lookaheadRegExp.exec(w.source);
if (match && match.index == w.matchStart) {
t = (new Showdown.converter()).makeHtml(match[1]);
wikify(
'<html>' + t + '</html>',
createTiddlyElement(w.output, 'div', null, 'showdown')
)
w.nextMatch = match.index + match[0].length;
}
}
})
//}}}
//
// showdown.js -- A javascript port of Markdown.
//
// Copyright (c) 2007 John Fraser.
//
// Original Markdown Copyright (c) 2004-2005 John Gruber
// <http://daringfireball.net/projects/markdown/>
//
// Redistributable under a BSD-style open source license.
// See license.txt for more information.
//
// The full source distribution is at:
//
// A A L
// T C A
// T K B
//
// <http://www.attacklab.net/>
//
//
// Wherever possible, Showdown is a straight, line-by-line port
// of the Perl version of Markdown.
//
// This is not a normal parser design; it's basically just a
// series of string substitutions. It's hard to read and
// maintain this way, but keeping Showdown close to the original
// design makes it easier to port new features.
//
// More importantly, Showdown behaves like markdown.pl in most
// edge cases. So web applications can do client-side preview
// in Javascript, and then build identical HTML on the server.
//
// This port needs the new RegExp functionality of ECMA 262,
// 3rd Edition (i.e. Javascript 1.5). Most modern web browsers
// should do fine. Even with the new regular expression features,
// We do a lot of work to emulate Perl's regex functionality.
// The tricky changes in this file mostly have the "attacklab:"
// label. Major or self-explanatory changes don't.
//
// Smart diff tools like Araxis Merge will be able to match up
// this file with markdown.pl in a useful way. A little tweaking
// helps: in a copy of markdown.pl, replace "#" with "//" and
// replace "$text" with "text". Be sure to ignore whitespace
// and line endings.
//
//
// Showdown usage:
//
// var text = "Markdown *rocks*.";
//
// var converter = new Showdown.converter();
// var html = converter.makeHtml(text);
//
// alert(html);
//
// Note: move the sample code to the bottom of this
// file before uncommenting it.
//
//
// Showdown namespace
//
var Showdown = {extensions: {}};
//
// forEach
//
var forEach = Showdown.forEach = function (obj, callback) {
if (typeof obj.forEach === 'function') {
obj.forEach(callback);
} else {
var i, len = obj.length;
for (i = 0; i < len; i++) {
callback(obj[i], i, obj);
}
}
};
//
// Standard extension naming
//
var stdExtName = function (s) {
return s.replace(/[_-]||\s/g, '').toLowerCase();
};
//
// converter
//
// Wraps all "globals" so that the only thing
// exposed is makeHtml().
//
Showdown.converter = function (converter_options) {
//
// Globals:
//
// Global hashes, used by various utility routines
var g_urls;
var g_titles;
var g_html_blocks;
// Used to track when we're inside an ordered or unordered list
// (see _ProcessListItems() for details):
var g_list_level = 0;
// Global extensions
var g_lang_extensions = [];
var g_output_modifiers = [];
//
// Automatic Extension Loading (node only):
//
if (typeof module !== 'undefined' && typeof exports !== 'undefined' && typeof require !== 'undefined') {
var fs = require('fs');
if (fs) {
// Search extensions folder
var extensions = fs.readdirSync((__dirname || '.') + '/extensions').filter(function (file) {
return ~file.indexOf('.js');
}).map(function (file) {
return file.replace(/\.js$/, '');
});
// Load extensions into Showdown namespace
Showdown.forEach(extensions, function (ext) {
var name = stdExtName(ext);
Showdown.extensions[name] = require('./extensions/' + ext);
});
}
}
this.makeHtml = function (text) {
//
// Main function. The order in which other subs are called here is
// essential. Link and image substitutions need to happen before
// _EscapeSpecialCharsWithinTagAttributes(), so that any *'s or _'s in the <a>
// and <img> tags get encoded.
//
// Clear the global hashes. If we don't clear these, you get conflicts
// from other articles when generating a page which contains more than
// one article (e.g. an index page that shows the N most recent
// articles):
g_urls = {};
g_titles = {};
g_html_blocks = [];
// attacklab: Replace ~ with ~T
// This lets us use tilde as an escape char to avoid md5 hashes
// The choice of character is arbitray; anything that isn't
// magic in Markdown will work.
text = text.replace(/~/g, "~T");
// attacklab: Replace $ with ~D
// RegExp interprets $ as a special character
// when it's in a replacement string
text = text.replace(/\$/g, "~D");
// Standardize line endings
text = text.replace(/\r\n/g, "\n"); // DOS to Unix
text = text.replace(/\r/g, "\n"); // Mac to Unix
// Make sure text begins and ends with a couple of newlines:
text = "\n\n" + text + "\n\n";
// Convert all tabs to spaces.
text = _Detab(text);
// Strip any lines consisting only of spaces and tabs.
// This makes subsequent regexen easier to write, because we can
// match consecutive blank lines with /\n+/ instead of something
// contorted like /[ \t]*\n+/ .
text = text.replace(/^[ \t]+$/mg, "");
// Run language extensions
Showdown.forEach(g_lang_extensions, function (x) {
text = _ExecuteExtension(x, text);
});
// Handle github codeblocks prior to running HashHTML so that
// HTML contained within the codeblock gets escaped propertly
text = _DoGithubCodeBlocks(text);
// Turn block-level HTML blocks into hash entries
text = _HashHTMLBlocks(text);
// Strip link definitions, store in hashes.
text = _StripLinkDefinitions(text);
text = _RunBlockGamut(text);
text = _UnescapeSpecialChars(text);
// attacklab: Restore dollar signs
text = text.replace(/~D/g, "$$");
// attacklab: Restore tildes
text = text.replace(/~T/g, "~");
// Run output modifiers
Showdown.forEach(g_output_modifiers, function (x) {
text = _ExecuteExtension(x, text);
});
return text;
};
//
// Options:
//
// Parse extensions options into separate arrays
if (converter_options && converter_options.extensions) {
var self = this;
// Iterate over each plugin
Showdown.forEach(converter_options.extensions, function (plugin) {
// Assume it's a bundled plugin if a string is given
if (typeof plugin === 'string') {
plugin = Showdown.extensions[stdExtName(plugin)];
}
if (typeof plugin === 'function') {
// Iterate over each extension within that plugin
Showdown.forEach(plugin(self), function (ext) {
// Sort extensions by type
if (ext.type) {
if (ext.type === 'language' || ext.type === 'lang') {
g_lang_extensions.push(ext);
} else if (ext.type === 'output' || ext.type === 'html') {
g_output_modifiers.push(ext);
}
} else {
// Assume language extension
g_output_modifiers.push(ext);
}
});
} else {
throw "Extension '" + plugin + "' could not be loaded. It was either not found or is not a valid extension.";
}
});
}
var _ExecuteExtension = function (ext, text) {
if (ext.regex) {
var re = new RegExp(ext.regex, 'g');
return text.replace(re, ext.replace);
} else if (ext.filter) {
return ext.filter(text);
}
};
var _StripLinkDefinitions = function (text) {
//
// Strips link definitions from text, stores the URLs and titles in
// hash references.
//
// Link defs are in the form: ^[id]: url "optional title"
/*
var text = text.replace(/
^[ ]{0,3}\[(.+)\]: // id = $1 attacklab: g_tab_width - 1
[ \t]*
\n? // maybe *one* newline
[ \t]*
<?(\S+?)>? // url = $2
[ \t]*
\n? // maybe one newline
[ \t]*
(?:
(\n*) // any lines skipped = $3 attacklab: lookbehind removed
["(]
(.+?) // title = $4
[")]
[ \t]*
)? // title is optional
(?:\n+|$)
/gm,
function(){...});
*/
// attacklab: sentinel workarounds for lack of \A and \Z, safari\khtml bug
text += "~0";
text = text.replace(/^[ ]{0,3}\[(.+)\]:[ \t]*\n?[ \t]*<?(\S+?)>?[ \t]*\n?[ \t]*(?:(\n*)["(](.+?)[")][ \t]*)?(?:\n+|(?=~0))/gm,
function (wholeMatch, m1, m2, m3, m4) {
m1 = m1.toLowerCase();
g_urls[m1] = _EncodeAmpsAndAngles(m2); // Link IDs are case-insensitive
if (m3) {
// Oops, found blank lines, so it's not a title.
// Put back the parenthetical statement we stole.
return m3 + m4;
} else if (m4) {
g_titles[m1] = m4.replace(/"/g, """);
}
// Completely remove the definition from the text
return "";
}
);
// attacklab: strip sentinel
text = text.replace(/~0/, "");
return text;
}
var _HashHTMLBlocks = function (text) {
// attacklab: Double up blank lines to reduce lookaround
text = text.replace(/\n/g, "\n\n");
// Hashify HTML blocks:
// We only want to do this for block-level HTML tags, such as headers,
// lists, and tables. That's because we still want to wrap <p>s around
// "paragraphs" that are wrapped in non-block-level tags, such as anchors,
// phrase emphasis, and spans. The list of tags we're looking for is
// hard-coded:
var block_tags_a = "p|div|h[1-6]|blockquote|pre|table|dl|ol|ul|script|noscript|form|fieldset|iframe|math|ins|del|style|section|header|footer|nav|article|aside";
var block_tags_b = "p|div|h[1-6]|blockquote|pre|table|dl|ol|ul|script|noscript|form|fieldset|iframe|math|style|section|header|footer|nav|article|aside";
// First, look for nested blocks, e.g.:
// <div>
// <div>
// tags for inner block must be indented.
// </div>
// </div>
//
// The outermost tags must start at the left margin for this to match, and
// the inner nested divs must be indented.
// We need to do this before the next, more liberal match, because the next
// match will start at the first `<div>` and stop at the first `</div>`.
// attacklab: This regex can be expensive when it fails.
/*
var text = text.replace(/
( // save in $1
^ // start of line (with /m)
<($block_tags_a) // start tag = $2
\b // word break
// attacklab: hack around khtml/pcre bug...
[^\r]*?\n // any number of lines, minimally matching
</\2> // the matching end tag
[ \t]* // trailing spaces/tabs
(?=\n+) // followed by a newline
) // attacklab: there are sentinel newlines at end of document
/gm,function(){...}};
*/
text = text.replace(/^(<(p|div|h[1-6]|blockquote|pre|table|dl|ol|ul|script|noscript|form|fieldset|iframe|math|ins|del)\b[^\r]*?\n<\/\2>[ \t]*(?=\n+))/gm, hashElement);
//
// Now match more liberally, simply from `\n<tag>` to `</tag>\n`
//
/*
var text = text.replace(/
( // save in $1
^ // start of line (with /m)
<($block_tags_b) // start tag = $2
\b // word break
// attacklab: hack around khtml/pcre bug...
[^\r]*? // any number of lines, minimally matching
</\2> // the matching end tag
[ \t]* // trailing spaces/tabs
(?=\n+) // followed by a newline
) // attacklab: there are sentinel newlines at end of document
/gm,function(){...}};
*/
text = text.replace(/^(<(p|div|h[1-6]|blockquote|pre|table|dl|ol|ul|script|noscript|form|fieldset|iframe|math|style|section|header|footer|nav|article|aside)\b[^\r]*?<\/\2>[ \t]*(?=\n+)\n)/gm, hashElement);
// Special case just for <hr />. It was easier to make a special case than
// to make the other regex more complicated.
/*
text = text.replace(/
( // save in $1
\n\n // Starting after a blank line
[ ]{0,3}
(<(hr) // start tag = $2
\b // word break
([^<>])*? //
\/?>) // the matching end tag
[ \t]*
(?=\n{2,}) // followed by a blank line
)
/g,hashElement);
*/
text = text.replace(/(\n[ ]{0,3}(<(hr)\b([^<>])*?\/?>)[ \t]*(?=\n{2,}))/g, hashElement);
// Special case for standalone HTML comments:
/*
text = text.replace(/
( // save in $1
\n\n // Starting after a blank line
[ ]{0,3} // attacklab: g_tab_width - 1
<!
(--[^\r]*?--\s*)+
>
[ \t]*
(?=\n{2,}) // followed by a blank line
)
/g,hashElement);
*/
text = text.replace(/(\n\n[ ]{0,3}<!(--[^\r]*?--\s*)+>[ \t]*(?=\n{2,}))/g, hashElement);
// PHP and ASP-style processor instructions (<?...?> and <%...%>)
/*
text = text.replace(/
(?:
\n\n // Starting after a blank line
)
( // save in $1
[ ]{0,3} // attacklab: g_tab_width - 1
(?:
<([?%]) // $2
[^\r]*?
\2>
)
[ \t]*
(?=\n{2,}) // followed by a blank line
)
/g,hashElement);
*/
text = text.replace(/(?:\n\n)([ ]{0,3}(?:<([?%])[^\r]*?\2>)[ \t]*(?=\n{2,}))/g, hashElement);
// attacklab: Undo double lines (see comment at top of this function)
text = text.replace(/\n\n/g, "\n");
return text;
}
var hashElement = function (wholeMatch, m1) {
var blockText = m1;
// Undo double lines
blockText = blockText.replace(/\n\n/g, "\n");
blockText = blockText.replace(/^\n/, "");
// strip trailing blank lines
blockText = blockText.replace(/\n+$/g, "");
// Replace the element text with a marker ("~KxK" where x is its key)
blockText = "\n\n~K" + (g_html_blocks.push(blockText) - 1) + "K\n\n";
return blockText;
};
var _RunBlockGamut = function (text) {
//
// These are all the transformations that form block-level
// tags like paragraphs, headers, and list items.
//
text = _DoHeaders(text);
// Do Horizontal Rules:
var key = hashBlock("<hr />");
text = text.replace(/^[ ]{0,2}([ ]?\*[ ]?){3,}[ \t]*$/gm, key);
text = text.replace(/^[ ]{0,2}([ ]?\-[ ]?){3,}[ \t]*$/gm, key);
text = text.replace(/^[ ]{0,2}([ ]?\_[ ]?){3,}[ \t]*$/gm, key);
text = _DoLists(text);
text = _DoCodeBlocks(text);
text = _DoBlockQuotes(text);
// We already ran _HashHTMLBlocks() before, in Markdown(), but that
// was to escape raw HTML in the original Markdown source. This time,
// we're escaping the markup we've just created, so that we don't wrap
// <p> tags around block-level tags.
text = _HashHTMLBlocks(text);
text = _FormParagraphs(text);
return text;
};
var _RunSpanGamut = function (text) {
//
// These are all the transformations that occur *within* block-level
// tags like paragraphs, headers, and list items.
//
text = _DoCodeSpans(text);
text = _EscapeSpecialCharsWithinTagAttributes(text);
text = _EncodeBackslashEscapes(text);
// Process anchor and image tags. Images must come first,
// because ![foo][f] looks like an anchor.
text = _DoImages(text);
text = _DoAnchors(text);
// Make links out of things like `<http://example.com/>`
// Must come after _DoAnchors(), because you can use < and >
// delimiters in inline links like [this](<url>).
text = _DoAutoLinks(text);
text = _EncodeAmpsAndAngles(text);
text = _DoItalicsAndBold(text);
// Do hard breaks:
text = text.replace(/ +\n/g, " <br />\n");
return text;
}
var _EscapeSpecialCharsWithinTagAttributes = function (text) {
//
// Within tags -- meaning between < and > -- encode [\ ` * _] so they
// don't conflict with their use in Markdown for code, italics and strong.
//
// Build a regex to find HTML tags and comments. See Friedl's
// "Mastering Regular Expressions", 2nd Ed., pp. 200-201.
var regex = /(<[a-z\/!$]("[^"]*"|'[^']*'|[^'">])*>|<!(--.*?--\s*)+>)/gi;
text = text.replace(regex, function (wholeMatch) {
var tag = wholeMatch.replace(/(.)<\/?code>(?=.)/g, "$1`");
tag = escapeCharacters(tag, "\\`*_");
return tag;
});
return text;
}
var _DoAnchors = function (text) {
//
// Turn Markdown link shortcuts into XHTML <a> tags.
//
//
// First, handle reference-style links: [link text] [id]
//
/*
text = text.replace(/
( // wrap whole match in $1
\[
(
(?:
\[[^\]]*\] // allow brackets nested one level
|
[^\[] // or anything else
)*
)
\]
[ ]? // one optional space
(?:\n[ ]*)? // one optional newline followed by spaces
\[
(.*?) // id = $3
\]
)()()()() // pad remaining backreferences
/g,_DoAnchors_callback);
*/
text = text.replace(/(\[((?:\[[^\]]*\]|[^\[\]])*)\][ ]?(?:\n[ ]*)?\[(.*?)\])()()()()/g, writeAnchorTag);
//
// Next, inline-style links: [link text](url "optional title")
//
/*
text = text.replace(/
( // wrap whole match in $1
\[
(
(?:
\[[^\]]*\] // allow brackets nested one level
|
[^\[\]] // or anything else
)
)
\]
\( // literal paren
[ \t]*
() // no id, so leave $3 empty
<?(.*?)>? // href = $4
[ \t]*
( // $5
(['"]) // quote char = $6
(.*?) // Title = $7
\6 // matching quote
[ \t]* // ignore any spaces/tabs between closing quote and )
)? // title is optional
\)
)
/g,writeAnchorTag);
*/
text = text.replace(/(\[((?:\[[^\]]*\]|[^\[\]])*)\]\([ \t]*()<?(.*?(?:\(.*?\).*?)?)>?[ \t]*((['"])(.*?)\6[ \t]*)?\))/g, writeAnchorTag);
//
// Last, handle reference-style shortcuts: [link text]
// These must come last in case you've also got [link test][1]
// or [link test](/foo)
//
/*
text = text.replace(/
( // wrap whole match in $1
\[
([^\[\]]+) // link text = $2; can't contain '[' or ']'
\]
)()()()()() // pad rest of backreferences
/g, writeAnchorTag);
*/
text = text.replace(/(\[([^\[\]]+)\])()()()()()/g, writeAnchorTag);
return text;
}
var writeAnchorTag = function (wholeMatch, m1, m2, m3, m4, m5, m6, m7) {
if (m7 == undefined) m7 = "";
var whole_match = m1;
var link_text = m2;
var link_id = m3.toLowerCase();
var url = m4;
var title = m7;
if (url == "") {
if (link_id == "") {
// lower-case and turn embedded newlines into spaces
link_id = link_text.toLowerCase().replace(/ ?\n/g, " ");
}
url = "#" + link_id;
if (g_urls[link_id] != undefined) {
url = g_urls[link_id];
if (g_titles[link_id] != undefined) {
title = g_titles[link_id];
}
}
else {
if (whole_match.search(/\(\s*\)$/m) > -1) {
// Special case for explicit empty url
url = "";
} else {
return whole_match;
}
}
}
url = escapeCharacters(url, "*_");
var result = "<a href=\"" + url + "\"";
if (title != "") {
title = title.replace(/"/g, """);
title = escapeCharacters(title, "*_");
result += " title=\"" + title + "\"";
}
result += ">" + link_text + "</a>";
return result;
}
var _DoImages = function (text) {
//
// Turn Markdown image shortcuts into <img> tags.
//
//
// First, handle reference-style labeled images: ![alt text][id]
//
/*
text = text.replace(/
( // wrap whole match in $1
!\[
(.*?) // alt text = $2
\]
[ ]? // one optional space
(?:\n[ ]*)? // one optional newline followed by spaces
\[
(.*?) // id = $3
\]
)()()()() // pad rest of backreferences
/g,writeImageTag);
*/
text = text.replace(/(!\[(.*?)\][ ]?(?:\n[ ]*)?\[(.*?)\])()()()()/g, writeImageTag);
//
// Next, handle inline images: 
// Don't forget: encode * and _
/*
text = text.replace(/
( // wrap whole match in $1
!\[
(.*?) // alt text = $2
\]
\s? // One optional whitespace character
\( // literal paren
[ \t]*
() // no id, so leave $3 empty
<?(\S+?)>? // src url = $4
[ \t]*
( // $5
(['"]) // quote char = $6
(.*?) // title = $7
\6 // matching quote
[ \t]*
)? // title is optional
\)
)
/g,writeImageTag);
*/
text = text.replace(/(!\[(.*?)\]\s?\([ \t]*()<?(\S+?)>?[ \t]*((['"])(.*?)\6[ \t]*)?\))/g, writeImageTag);
return text;
}
var writeImageTag = function (wholeMatch, m1, m2, m3, m4, m5, m6, m7) {
var whole_match = m1;
var alt_text = m2;
var link_id = m3.toLowerCase();
var url = m4;
var title = m7;
if (!title) title = "";
if (url == "") {
if (link_id == "") {
// lower-case and turn embedded newlines into spaces
link_id = alt_text.toLowerCase().replace(/ ?\n/g, " ");
}
url = "#" + link_id;
if (g_urls[link_id] != undefined) {
url = g_urls[link_id];
if (g_titles[link_id] != undefined) {
title = g_titles[link_id];
}
}
else {
return whole_match;
}
}
alt_text = alt_text.replace(/"/g, """);
url = escapeCharacters(url, "*_");
var result = "<img src=\"" + url + "\" alt=\"" + alt_text + "\"";
// attacklab: Markdown.pl adds empty title attributes to images.
// Replicate this bug.
//if (title != "") {
title = title.replace(/"/g, """);
title = escapeCharacters(title, "*_");
result += " title=\"" + title + "\"";
//}
result += " />";
return result;
}
var _DoHeaders = function (text) {
// Setext-style headers:
// Header 1
// ========
//
// Header 2
// --------
//
text = text.replace(/^(.+)[ \t]*\n=+[ \t]*\n+/gm,
function (wholeMatch, m1) {
return hashBlock('<h1 id="' + headerId(m1) + '">' + _RunSpanGamut(m1) + "</h1>");
});
text = text.replace(/^(.+)[ \t]*\n-+[ \t]*\n+/gm,
function (matchFound, m1) {
return hashBlock('<h2 id="' + headerId(m1) + '">' + _RunSpanGamut(m1) + "</h2>");
});
// atx-style headers:
// # Header 1
// ## Header 2
// ## Header 2 with closing hashes ##
// ...
// ###### Header 6
//
/*
text = text.replace(/
^(\#{1,6}) // $1 = string of #'s
[ \t]*
(.+?) // $2 = Header text
[ \t]*
\#* // optional closing #'s (not counted)
\n+
/gm, function() {...});
*/
text = text.replace(/^(\#{1,6})[ \t]*(.+?)[ \t]*\#*\n+/gm,
function (wholeMatch, m1, m2) {
var h_level = m1.length;
return hashBlock("<h" + h_level + ' id="' + headerId(m2) + '">' + _RunSpanGamut(m2) + "</h" + h_level + ">");
});
function headerId(m) {
return m.replace(/[^\w]/g, '').toLowerCase();
}
return text;
}
// This declaration keeps Dojo compressor from outputting garbage:
var _ProcessListItems;
var _DoLists = function (text) {
//
// Form HTML ordered (numbered) and unordered (bulleted) lists.
//
// attacklab: add sentinel to hack around khtml/safari bug:
// http://bugs.webkit.org/show_bug.cgi?id=11231
text += "~0";
// Re-usable pattern to match any entirel ul or ol list:
/*
var whole_list = /
( // $1 = whole list
( // $2
[ ]{0,3} // attacklab: g_tab_width - 1
([*+-]|\d+[.]) // $3 = first list item marker
[ \t]+
)
[^\r]+?
( // $4
~0 // sentinel for workaround; should be $
|
\n{2,}
(?=\S)
(?! // Negative lookahead for another list item marker
[ \t]*
(?:[*+-]|\d+[.])[ \t]+
)
)
)/g
*/
var whole_list = /^(([ ]{0,3}([*+-]|\d+[.])[ \t]+)[^\r]+?(~0|\n{2,}(?=\S)(?![ \t]*(?:[*+-]|\d+[.])[ \t]+)))/gm;
if (g_list_level) {
text = text.replace(whole_list, function (wholeMatch, m1, m2) {
var list = m1;
var list_type = (m2.search(/[*+-]/g) > -1) ? "ul" : "ol";
// Turn double returns into triple returns, so that we can make a
// paragraph for the last item in a list, if necessary:
list = list.replace(/\n{2,}/g, "\n\n\n");
;
var result = _ProcessListItems(list);
// Trim any trailing whitespace, to put the closing `</$list_type>`
// up on the preceding line, to get it past the current stupid
// HTML block parser. This is a hack to work around the terrible
// hack that is the HTML block parser.
result = result.replace(/\s+$/, "");
result = "<" + list_type + ">" + result + "</" + list_type + ">\n";
return result;
});
} else {
whole_list = /(\n\n|^\n?)(([ ]{0,3}([*+-]|\d+[.])[ \t]+)[^\r]+?(~0|\n{2,}(?=\S)(?![ \t]*(?:[*+-]|\d+[.])[ \t]+)))/g;
text = text.replace(whole_list, function (wholeMatch, m1, m2, m3) {
var runup = m1;
var list = m2;
var list_type = (m3.search(/[*+-]/g) > -1) ? "ul" : "ol";
// Turn double returns into triple returns, so that we can make a
// paragraph for the last item in a list, if necessary:
var list = list.replace(/\n{2,}/g, "\n\n\n");
;
var result = _ProcessListItems(list);
result = runup + "<" + list_type + ">\n" + result + "</" + list_type + ">\n";
return result;
});
}
// attacklab: strip sentinel
text = text.replace(/~0/, "");
return text;
}
_ProcessListItems = function (list_str) {
//
// Process the contents of a single ordered or unordered list, splitting it
// into individual list items.
//
// The $g_list_level global keeps track of when we're inside a list.
// Each time we enter a list, we increment it; when we leave a list,
// we decrement. If it's zero, we're not in a list anymore.
//
// We do this because when we're not inside a list, we want to treat
// something like this:
//
// I recommend upgrading to version
// 8. Oops, now this line is treated
// as a sub-list.
//
// As a single paragraph, despite the fact that the second line starts
// with a digit-period-space sequence.
//
// Whereas when we're inside a list (or sub-list), that line will be
// treated as the start of a sub-list. What a kludge, huh? This is
// an aspect of Markdown's syntax that's hard to parse perfectly
// without resorting to mind-reading. Perhaps the solution is to
// change the syntax rules such that sub-lists must start with a
// starting cardinal number; e.g. "1." or "a.".
g_list_level++;
// trim trailing blank lines:
list_str = list_str.replace(/\n{2,}$/, "\n");
// attacklab: add sentinel to emulate \z
list_str += "~0";
/*
list_str = list_str.replace(/
(\n)? // leading line = $1
(^[ \t]*) // leading whitespace = $2
([*+-]|\d+[.]) [ \t]+ // list marker = $3
([^\r]+? // list item text = $4
(\n{1,2}))
(?= \n* (~0 | \2 ([*+-]|\d+[.]) [ \t]+))
/gm, function(){...});
*/
list_str = list_str.replace(/(\n)?(^[ \t]*)([*+-]|\d+[.])[ \t]+([^\r]+?(\n{1,2}))(?=\n*(~0|\2([*+-]|\d+[.])[ \t]+))/gm,
function (wholeMatch, m1, m2, m3, m4) {
var item = m4;
var leading_line = m1;
var leading_space = m2;
if (leading_line || (item.search(/\n{2,}/) > -1)) {
item = _RunBlockGamut(_Outdent(item));
}
else {
// Recursion for sub-lists:
item = _DoLists(_Outdent(item));
item = item.replace(/\n$/, ""); // chomp(item)
item = _RunSpanGamut(item);
}
return "<li>" + item + "</li>\n";
}
);
// attacklab: strip sentinel
list_str = list_str.replace(/~0/g, "");
g_list_level--;
return list_str;
}
var _DoCodeBlocks = function (text) {
//
// Process Markdown `<pre><code>` blocks.
//
/*
text = text.replace(text,
/(?:\n\n|^)
( // $1 = the code block -- one or more lines, starting with a space/tab
(?:
(?:[ ]{4}|\t) // Lines must start with a tab or a tab-width of spaces - attacklab: g_tab_width
.*\n+
)+
)
(\n*[ ]{0,3}[^ \t\n]|(?=~0)) // attacklab: g_tab_width
/g,function(){...});
*/
// attacklab: sentinel workarounds for lack of \A and \Z, safari\khtml bug
text += "~0";
text = text.replace(/(?:\n\n|^)((?:(?:[ ]{4}|\t).*\n+)+)(\n*[ ]{0,3}[^ \t\n]|(?=~0))/g,
function (wholeMatch, m1, m2) {
var codeblock = m1;
var nextChar = m2;
codeblock = _EncodeCode(_Outdent(codeblock));
codeblock = _Detab(codeblock);
codeblock = codeblock.replace(/^\n+/g, ""); // trim leading newlines
codeblock = codeblock.replace(/\n+$/g, ""); // trim trailing whitespace
codeblock = "<pre><code>" + codeblock + "\n</code></pre>";
return hashBlock(codeblock) + nextChar;
}
);
// attacklab: strip sentinel
text = text.replace(/~0/, "");
return text;
};
var _DoGithubCodeBlocks = function (text) {
//
// Process Github-style code blocks
// Example:
// ```ruby
// def hello_world(x)
// puts "Hello, #{x}"
// end
// ```
//
// attacklab: sentinel workarounds for lack of \A and \Z, safari\khtml bug
text += "~0";
text = text.replace(/(?:^|\n)```(.*)\n([\s\S]*?)\n```/g,
function (wholeMatch, m1, m2) {
var language = m1;
var codeblock = m2;
codeblock = _EncodeCode(codeblock);
codeblock = _Detab(codeblock);
codeblock = codeblock.replace(/^\n+/g, ""); // trim leading newlines
codeblock = codeblock.replace(/\n+$/g, ""); // trim trailing whitespace
codeblock = "<pre><code" + (language ? " class=\"" + language + '"' : "") + ">" + codeblock + "\n</code></pre>";
return hashBlock(codeblock);
}
);
// attacklab: strip sentinel
text = text.replace(/~0/, "");
return text;
}
var hashBlock = function (text) {
text = text.replace(/(^\n+|\n+$)/g, "");
return "\n\n~K" + (g_html_blocks.push(text) - 1) + "K\n\n";
}
var _DoCodeSpans = function (text) {
//
// * Backtick quotes are used for <code></code> spans.
//
// * You can use multiple backticks as the delimiters if you want to
// include literal backticks in the code span. So, this input:
//
// Just type ``foo `bar` baz`` at the prompt.
//
// Will translate to:
//
// <p>Just type <code>foo `bar` baz</code> at the prompt.</p>
//
// There's no arbitrary limit to the number of backticks you
// can use as delimters. If you need three consecutive backticks
// in your code, use four for delimiters, etc.
//
// * You can use spaces to get literal backticks at the edges:
//
// ... type `` `bar` `` ...
//
// Turns to:
//
// ... type <code>`bar`</code> ...
//
/*
text = text.replace(/
(^|[^\\]) // Character before opening ` can't be a backslash
(`+) // $2 = Opening run of `
( // $3 = The code block
[^\r]*?
[^`] // attacklab: work around lack of lookbehind
)
\2 // Matching closer
(?!`)
/gm, function(){...});
*/
text = text.replace(/(^|[^\\])(`+)([^\r]*?[^`])\2(?!`)/gm,
function (wholeMatch, m1, m2, m3, m4) {
var c = m3;
c = c.replace(/^([ \t]*)/g, ""); // leading whitespace
c = c.replace(/[ \t]*$/g, ""); // trailing whitespace
c = _EncodeCode(c);
return m1 + "<code>" + c + "</code>";
});
return text;
}
var _EncodeCode = function (text) {
//
// Encode/escape certain characters inside Markdown code runs.
// The point is that in code, these characters are literals,
// and lose their special Markdown meanings.
//
// Encode all ampersands; HTML entities are not
// entities within a Markdown code span.
text = text.replace(/&/g, "&");
// Do the angle bracket song and dance:
text = text.replace(/</g, "<");
text = text.replace(/>/g, ">");
// Now, escape characters that are magic in Markdown:
text = escapeCharacters(text, "\*_{}[]\\", false);
// jj the line above breaks this:
//---
//* Item
// 1. Subitem
// special char: *
//---
return text;
}
var _DoItalicsAndBold = function (text) {
// <strong> must go first:
text = text.replace(/(\*\*|__)(?=\S)([^\r]*?\S[*_]*)\1/g,
"<strong>$2</strong>");
text = text.replace(/(\*|_)(?=\S)([^\r]*?\S)\1/g,
"<em>$2</em>");
return text;
}
var _DoBlockQuotes = function (text) {
/*
text = text.replace(/
( // Wrap whole match in $1
(
^[ \t]*>[ \t]? // '>' at the start of a line
.+\n // rest of the first line
(.+\n)* // subsequent consecutive lines
\n* // blanks
)+
)
/gm, function(){...});
*/
text = text.replace(/((^[ \t]*>[ \t]?.+\n(.+\n)*\n*)+)/gm,
function (wholeMatch, m1) {
var bq = m1;
// attacklab: hack around Konqueror 3.5.4 bug:
// "----------bug".replace(/^-/g,"") == "bug"
bq = bq.replace(/^[ \t]*>[ \t]?/gm, "~0"); // trim one level of quoting
// attacklab: clean up hack
bq = bq.replace(/~0/g, "");
bq = bq.replace(/^[ \t]+$/gm, ""); // trim whitespace-only lines
bq = _RunBlockGamut(bq); // recurse
bq = bq.replace(/(^|\n)/g, "$1 ");
// These leading spaces screw with <pre> content, so we need to fix that:
bq = bq.replace(
/(\s*<pre>[^\r]+?<\/pre>)/gm,
function (wholeMatch, m1) {
var pre = m1;
// attacklab: hack around Konqueror 3.5.4 bug:
pre = pre.replace(/^ /mg, "~0");
pre = pre.replace(/~0/g, "");
return pre;
});
return hashBlock("<blockquote>\n" + bq + "\n</blockquote>");
});
return text;
}
var _FormParagraphs = function (text) {
//
// Params:
// $text - string to process with html <p> tags
//
// Strip leading and trailing lines:
text = text.replace(/^\n+/g, "");
text = text.replace(/\n+$/g, "");
var grafs = text.split(/\n{2,}/g);
var grafsOut = [];
//
// Wrap <p> tags.
//
var end = grafs.length;
for (var i = 0; i < end; i++) {
var str = grafs[i];
// if this is an HTML marker, copy it
if (str.search(/~K(\d+)K/g) >= 0) {
grafsOut.push(str);
}
else if (str.search(/\S/) >= 0) {
str = _RunSpanGamut(str);
str = str.replace(/^([ \t]*)/g, "<p>");
str += "</p>"
grafsOut.push(str);
}
}
//
// Unhashify HTML blocks
//
end = grafsOut.length;
for (var i = 0; i < end; i++) {
// if this is a marker for an html block...
while (grafsOut[i].search(/~K(\d+)K/) >= 0) {
var blockText = g_html_blocks[RegExp.$1];
blockText = blockText.replace(/\$/g, "$$$$"); // Escape any dollar signs
grafsOut[i] = grafsOut[i].replace(/~K\d+K/, blockText);
}
}
return grafsOut.join("\n\n");
}
var _EncodeAmpsAndAngles = function (text) {
// Smart processing for ampersands and angle brackets that need to be encoded.
// Ampersand-encoding based entirely on Nat Irons's Amputator MT plugin:
// http://bumppo.net/projects/amputator/
text = text.replace(/&(?!#?[xX]?(?:[0-9a-fA-F]+|\w+);)/g, "&");
// Encode naked <'s
text = text.replace(/<(?![a-z\/?\$!])/gi, "<");
return text;
}
var _EncodeBackslashEscapes = function (text) {
//
// Parameter: String.
// Returns: The string, with after processing the following backslash
// escape sequences.
//
// attacklab: The polite way to do this is with the new
// escapeCharacters() function:
//
// text = escapeCharacters(text,"\\",true);
// text = escapeCharacters(text,"`*_{}[]()>#+-.!",true);
//
// ...but we're sidestepping its use of the (slow) RegExp constructor
// as an optimization for Firefox. This function gets called a LOT.
text = text.replace(/\\(\\)/g, escapeCharacters_callback);
text = text.replace(/\\([`*_{}\[\]()>#+-.!])/g, escapeCharacters_callback);
return text;
}
var _DoAutoLinks = function (text) {
text = text.replace(/<((https?|ftp|dict):[^'">\s]+)>/gi, "<a href=\"$1\">$1</a>");
// Email addresses: <address@domain.foo>
/*
text = text.replace(/
<
(?:mailto:)?
(
[-.\w]+
\@
[-a-z0-9]+(\.[-a-z0-9]+)*\.[a-z]+
)
>
/gi, _DoAutoLinks_callback());
*/
text = text.replace(/<(?:mailto:)?([-.\w]+\@[-a-z0-9]+(\.[-a-z0-9]+)*\.[a-z]+)>/gi,
function (wholeMatch, m1) {
return _EncodeEmailAddress(_UnescapeSpecialChars(m1));
}
);
return text;
}
var _EncodeEmailAddress = function (addr) {
//
// Input: an email address, e.g. "foo@example.com"
//
// Output: the email address as a mailto link, with each character
// of the address encoded as either a decimal or hex entity, in
// the hopes of foiling most address harvesting spam bots. E.g.:
//
// <a href="mailto:foo@e
// xample.com">foo
// @example.com</a>
//
// Based on a filter by Matthew Wickline, posted to the BBEdit-Talk
// mailing list: <http://tinyurl.com/yu7ue>
//
var encode = [
function (ch) {
return "&#" + ch.charCodeAt(0) + ";";
},
function (ch) {
return "&#x" + ch.charCodeAt(0).toString(16) + ";";
},
function (ch) {
return ch;
}
];
addr = "mailto:" + addr;
addr = addr.replace(/./g, function (ch) {
if (ch == "@") {
// this *must* be encoded. I insist.
ch = encode[Math.floor(Math.random() * 2)](ch);
} else if (ch != ":") {
// leave ':' alone (to spot mailto: later)
var r = Math.random();
// roughly 10% raw, 45% hex, 45% dec
ch = (
r > .9 ? encode[2](ch) :
r > .45 ? encode[1](ch) :
encode[0](ch)
);
}
return ch;
});
addr = "<a href=\"" + addr + "\">" + addr + "</a>";
addr = addr.replace(/">.+:/g, "\">"); // strip the mailto: from the visible part
return addr;
}
var _UnescapeSpecialChars = function (text) {
//
// Swap back in all the special characters we've hidden.
//
text = text.replace(/~E(\d+)E/g,
function (wholeMatch, m1) {
var charCodeToReplace = parseInt(m1);
return String.fromCharCode(charCodeToReplace);
}
);
return text;
}
var _Outdent = function (text) {
//
// Remove one level of line-leading tabs or spaces
//
// attacklab: hack around Konqueror 3.5.4 bug:
// "----------bug".replace(/^-/g,"") == "bug"
text = text.replace(/^(\t|[ ]{1,4})/gm, "~0"); // attacklab: g_tab_width
// attacklab: clean up hack
text = text.replace(/~0/g, "")
return text;
}
var _Detab = function (text) {
// attacklab: Detab's completely rewritten for speed.
// In perl we could fix it by anchoring the regexp with \G.
// In javascript we're less fortunate.
// expand first n-1 tabs
text = text.replace(/\t(?=\t)/g, " "); // attacklab: g_tab_width
// replace the nth with two sentinels
text = text.replace(/\t/g, "~A~B");
// use the sentinel to anchor our regex so it doesn't explode
text = text.replace(/~B(.+?)~A/g,
function (wholeMatch, m1, m2) {
var leadingText = m1;
var numSpaces = 4 - leadingText.length % 4; // attacklab: g_tab_width
// there *must* be a better way to do this:
for (var i = 0; i < numSpaces; i++) leadingText += " ";
return leadingText;
}
);
// clean up sentinels
text = text.replace(/~A/g, " "); // attacklab: g_tab_width
text = text.replace(/~B/g, "");
return text;
}
//
// attacklab: Utility functions
//
var escapeCharacters = function (text, charsToEscape, afterBackslash) {
// First we have to escape the escape characters so that
// we can build a character class out of them
var regexString = "([" + charsToEscape.replace(/([\[\]\\])/g, "\\$1") + "])";
if (afterBackslash) {
regexString = "\\\\" + regexString;
}
var regex = new RegExp(regexString, "g");
text = text.replace(regex, escapeCharacters_callback);
return text;
}
var escapeCharacters_callback = function (wholeMatch, m1) {
var charCodeToEscape = m1.charCodeAt(0);
return "~E" + charCodeToEscape + "E";
}
} // end of Showdown.converter
// export
if (typeof module !== 'undefined') module.exports = Showdown;
// stolen from AMD branch of underscore
// AMD define happens at the end for compatibility with AMD loaders
// that don't enforce next-turn semantics on modules.
if (typeof define === 'function' && define.amd) {
define('showdown', function () {
return Showdown;
});
}
<hr><<slider chkSliderSideBarCalendar SideBarCalendar "â–¼ calendar" "Display calendar">>
<<slider chkSliderMenu_Archives SliderMenu_Archives "â–¼ archives" "Archives from previous years">><hr><<slider chkSliderSideBarTabsToggleMenu SideBarTabsToggleMenu "â–¼ tiddlers" "Displays lists of tiddlers">>
<<tabs txtMainTab "Timeline" "Timeline" TabTimeline "All" "All tiddlers" TabAll "Tags" "All tags" TabTags "More" "More lists" TabMore>>
/***
For master styles, refer to StyleSheetLayout, StyleSheetPrint, and StyleSheetColors when making changes.
***/
/***
!Global Styles /% ============================================================= %/
***/
/*{{{*/
* html .tiddler {
height:1%;
}
body {
font-size:0.7em; // formerly 0.7em;
font-family:verdana,tahoma,calibri; // formerly verdana,tahoma,calibri;
margin:0;
padding:0;
}
.viewer img {
padding:4px;
}
/*}}}*/
/***
!!Buttons /% ================= %/
***/
/*{{{*/
.button {
border:1px transparent solid;
}
.button:hover {
border:1px [[ColorPalette::SecondaryMid]] solid;
}
.button:active {
border:1px [[ColorPalette::SecondaryDark]] solid;
}
/*}}}*/
/***
!!Headings and Subheadings /% ================= %/
By default, {{{H4}}}, {{{H5}}}, and {{{H6}}} have different line paddings than {{{H1}}}, {{{H2}}}, and {{{H3}}} in the ~TiddlyWiki core code; I've overridden that here by assigning the same values to {{{H1}}}, {{{H2}}}, {{{H3}}}, {{{H4}}}, {{{H5}}}, and {{{H6}}}.
I've also added styling to make subheads decrease in width as the values rise, making it easier to identify sub-headings when viewing tiddlers.
***/
/*{{{*/
h1, h2, h3, h4, h5, h6 {
font-weight:bold;
text-decoration:none;
background-color:[[ColorPalette::SecondaryPale]];
border-bottom:2px solid [[ColorPalette::TertiaryLight]];
border-right:2px solid [[ColorPalette::TertiaryLight]];
}
h1, h2, h3, h4, h5, h6 {
padding-bottom:1px;
margin-top:1.2em;
margin-bottom:0.3em;
}
h1 {
font-size:1.4em;
text-transform:uppercase;
}
h2 {
font-size:1.35em;
padding-left:10px;
margin-right:5%;
}
h3 {
font-size:1.3em;
padding-left:20px;
margin-right:10%;
}
h4 {
font-size:1.25em;
padding-left:30px;
margin-right:15%;
}
h5 {
font-size:1.2em;
padding-left:40px;
margin-right:20%;
}
h6 {
font-size:1.15em;
padding-left:50px;
margin-right:25%;
}
/*}}}*/
/***
!!Horizontal Rules /% ================= %/
***/
/*{{{*/
.viewer hr {
border:0;
border-top:solid 1px [[ColorPalette::TertiaryDark]];
color:[[ColorPalette::TertiaryDark]];
}
/*}}}*/
/***
!!Lists /% ================= %/
***/
/*{{{*/
.viewer ul,
.viewer ol {
margin-top:0em;
margin-bottom:0em;
margin-left:0.5em;
padding-top:0em;
padding-bottom:0em;
padding-left:1.5em;
}
.viewer ul li,
.viewer ol li {
margin:0.5em inherit;
}
/*}}}*/
/***
!!Superscripts & Subscripts /% ================= %/
***/
/*{{{*/
sup, sub {
height:0;
line-height:1;
vertical-align:baseline;
_vertical-align:bottom;
position:relative;
}
sup {
bottom:1ex;
}
sub {
top:0.5ex;
}
/*}}}*/
/***
!!Strikethrough /% ================= %/
***/
/*{{{*/
strike {
color:#606060;
text-decoration:line-through;
}
/*}}}*/
/***
!!Blockquoting /% ================= %/
***/
/*{{{*/
.viewer blockquote {
margin-top:0px;
margin-bottom:0px;
padding-bottom:0px;
}
/*}}}*/
/***
Custom CSS "blockquote" style, so that blockquoting can be invoked with <html><code>{{blockquote{</code></html> and <html><code>}}}</code></html> around a block of text instead of having to preface every line with {{{>}}}. (Created before I knew ~TiddlyWiki natively supports {{{<<<}}} and {{{<<<}}} for multi-line blockquoting, this style remains intact to allow nested blockquotes -- and to support all those tiddlers created before I knew of the native solution.)
***/
/*{{{*/
.viewer .blockquote {
border-left:3px solid [[ColorPalette::TertiaryDark]];
margin-top:0px;
margin-bottom:0px;
margin-left:2.5em;
padding-left:0.8em;
padding-bottom:0px;
}
/*}}}*/
/***
!!Pre-Formatted Text (And Code Blocks) /% ================= %/
***/
/*{{{*/
.viewer pre {
font-family:Consolas,Courier New,Courier;
padding:0.5em;
margin-left:0.5em;
font-size:inherit; // formerly 1.2em; formerly 10pt;
line-height:1.4em;
white-space:pre-wrap;
overflow:auto;
color:[[ColorPalette::Foreground]];
background:[[ColorPalette::TertiaryLessPale]];
border:1px solid [[ColorPalette::SecondaryMid]];
}
.viewer code {
font-family:Consolas,Courier New,Courier;
font-size:inherit; // formerly 1.2em;
line-height:1.4em;
color:[[ColorPalette::SecondaryDark]];
}
code.escaped {
font-family:Consolas,Courier New,Courier;
white-space:pre-wrap;
}
.bluescreen {
font-family:Consolas,Courier New,Courier;
padding:0.5em;
margin-left:0.5em;
font-size:10pt; // formerly 1.2em;
line-height:1.4em;
white-space:pre-wrap;
overflow:auto;
color:#FFFFFF;
background:#0000FF;
border:1px solid [[ColorPalette::PrimaryLight]];
}
/*}}}*/
/***
!!Text Input Fields /% ================= %/
***/
/*{{{*/
.txtOptionInput,
.wideInput {
width:10.5em;
background:[[ColorPalette::SecondaryPale]];
}
/*}}}*/
/***
!Layout Styles /% ============================================================= %/
***/
/***
!!Title & Header /% ================= %/
***/
/*{{{*/
.headerForeground {
position:absolute;
left:0px;
top:0px;
padding:2em 0 1em 1em;
}
.headerShadow {
position:relative;
left:-1px;
top:-1px;
padding:2em 0 1em 1em;
}
.siteTitle {
font-family:calibri;
font-size:2.8em;
font-weight:bold;
}
.siteSubtitle {
font-family:calibri;
font-size:1.4em;
}
.headerMenu {
position:absolute;
right:16.5em;
bottom:0px;
padding:0;
}
.siteControls {
margin:0;
padding:0;
font-family:calibri;
font-size:1.1em;
color:[[ColorPalette::Background]];
}
.siteControls a {
font-weight:bold;
color:[[ColorPalette::Background]];
border:none;
text-decoration:none;
padding:4px;
border-top-left-radius:2px;
border-top-right-radius:2px;
}
.siteControls a:hover {
font-weight:bold;
color:[[ColorPalette::PrimaryDark]];
background:[[ColorPalette::Background]];
border:none;
text-decoration:none;
border-top-left-radius:2px;
border-top-right-radius:2px;
}
.siteControls a:active {
font-weight:bold;
color:[[ColorPalette::PrimaryDark]];
background:[[ColorPalette::Background]];
border-top-left-radius:2px;
border-top-right-radius:2px;
}
/*}}}*/
/***
!!Message Area ("Save" Dialogue) /% ================= %/
***/
/*{{{*/
#messageArea {
position:absolute;
top:0.3em; // formerly 1.5em;
right:0;
margin:0.5em;
padding:0.5em;
z-index:2000;
_position:absolute;
}
/*}}}*/
/***
!!Main Menu (Left Nav/Primary Navigation) /% ================= %/
***/
/*{{{*/
#mainMenu {
position:absolute;
left:0;
padding:1.1em 0.3em 0.1em 0em;
width:13.5em;
text-align:right;
line-height:1.4em;
font-size:1em;
}
#mainMenu a {
display:block;
}
#mainMenu .sliderPanel a {
display:inline;
}
#mainMenu .leftNavSectionTop {
border-top:1px solid [[ColorPalette::TertiaryDark]];
padding:0.5em 1.1em 0em 0em;
}
#mainMenu .leftNavSection {
border-top:1px solid [[ColorPalette::TertiaryDark]];
padding:0.5em 0.5em 0em 0em;
}
#mainMenu .leftNavConfigSection {
border:1px solid [[ColorPalette::TertiaryDark]];
border-left:none;
border-radius-topright:1em;
border-radius-bottomright:1em;
padding:0.5em 0.5em 0.5em 0em;
background-color:[[ColorPalette::TertiaryLessPale]];
}
#mainMenu .button {
font-weight:bold;
}
#mainMenu .sliderPanel {
margin-right:0.5em;
margin-left:0.6em;
padding:0.5em;
font-size:1em;
border-style:dotted;
border-width:0px 1px 1px 0px;
border-color:[[ColorPalette::SecondaryMid]];
}
/*}}}*/
/***
Bold for existing tiddlers, italics for non-existing tiddlers:
***/
/*{{{*/
#mainMenu .tiddlyLinkExisting {
font-weight:bold;
font-style:normal;
}
#mainMenu .tiddlyLinkNonExisting {
font-weight:normal;
font-style:italic;
}
/*}}}*/
/***
!!Sidebar (Control Panel/Options Panel) /% ================= %/
***/
/*{{{*/
#sidebar {
position:absolute;
right:4px;
width:18.8em;
font-size:.9em;
}
#sidebar hr {
height:1px;
border:0;
color:[[ColorPalette::TertiaryDark]];
background:[[ColorPalette::TertiaryDark]];
}
#sidebarOptions {
padding:0;
padding-top:0.3em;
}
#sidebarOptions a {
margin:0;
padding:0.2em 0;
display:block;
}
#sidebarOptions input {
margin:0;
margin-right:0.2em;
margin-bottom:0.2em;
}
#sidebarOptions .sliderPanel {
margin-left:0.5em;
margin-right:0.6em
padding:0.5em;
font-size:1em;
border-style:dotted;
border-width:0px 0px 1px 1px;
border-color:[[ColorPalette::SecondaryMid]];
background:[[ColorPalette::Background]];
}
#sidebarOptions .sliderPanel input {
margin:0 0 0.3em 0;
}
#sidebarOptions .sliderPanel a {
font-weight:bold;
display:inline;
padding:0;
}
#sidebarTabs .tab,
#sidebarTabs .sliderPanel .tab {
border-top-left-radius:3px;
border-top-right-radius:3px;
padding:2px 3px;
}
#sidebarTabs a {
display:block;
}
#sidebarTabs .tabContents {
width: 17.5em;
overflow:hidden;
}
#sidebarTabs .sliderPanel {
font-size:1em;
}
#sidebarTabs .sliderPanel a {
display:inline;
}
/*}}}*/
/***
!!Display Area (Main Content Area) /% ================= %/
***/
/*{{{*/
#displayArea {
margin:1em 18em; 0 14em;
}
.tiddler {
border-top:1px solid [[ColorPalette::PrimaryLight]];
border-right:2px solid [[ColorPalette::PrimaryLight]];
border-bottom:2px solid [[ColorPalette::PrimaryLight]];
border-left:1px solid [[ColorPalette::PrimaryLight]];
margin:0.5em;
background:[[ColorPalette::Background]];
padding: 0.5em;
border-radius:1em; /* rounded corners for tiddlers */
}
/*}}}*/
/***
!Tiddler Styles /% ============================================================= %/
***/
/***
!!Headers /% ================= %/
Add this in to give the tiddler header a background shade consistent with the current ~ColorPalette:
<html><code>.title, .subtitle, .subtitle2, .toolbar, .wordcount {background-color:[[ColorPalette::SecondaryPale]];}</code></html>
***/
/***
!!!Toolbar /% =========== %/
***/
/*{{{*/
.toolbar {
color:[[ColorPalette::PrimaryMid]];
}
.toolbar a {
color:[[ColorPalette::TertiaryLight]];
border-radius:2px;
}
.selected .toolbar a {
color:[[ColorPalette::TertiaryMid]];
border-radius:2px;
}
.selected .toolbar a:hover {
color:[[ColorPalette::Foreground]];
border-radius:2px;
}
/*}}}*/
/***
!!!Title, Subtitles, and Word Count /% =========== %/
***/
/*{{{*/
.subtitle {
font-size:1.1em;
}
.subtitle2 {
font-size:0.75em;
}
.wordcount {
font-size:0.75em;
font-weight:bold;
}
/*}}}*/
/***
!!Tagging /% ================= %/
***/
/*{{{*/
.tagging {
margin:0.5em 0.5em 0.5em 0;
float:right;
display:none;
}
.tagged {
margin:0.5em 0.5em 0.5em 0;
float:right;
}
/*}}}*/
/***
!!Edit View /% ================= %/
***/
/*{{{*/
.editor input,
.editor textarea {
display:block;
width:100%;
font-family:Consolas,Courier New,Courier;
font-size:10pt;
background:[[ColorPalette::SecondaryPale]];
}
/*}}}*/
/***
!Tables /% ============================================================= %/
***/
/***
!!Centered Table /% ================= %/
Invoke with <html><code>{{centerThis{</code></html> at beginning of table and <html><code>}}}</code></html> at end.
***/
/*{{{*/
.viewer, .sidebarOptions, .sidebarTabs div.centerThis {
text-align:center;
}
.viewer, .sidebarOptions, .sidebarTabs div.centerThis table {
margin:0 auto;
text-align:left;
}
/*}}}*/
/***
!!Floating Tables (And Other Things) /% ================= %/
Invoke with <html><code>{{floatRight{</code></html> or <html><code>{{floatLeft{</code></html> at beginning of table and <html><code>}}}</code></html> at end ''or'' by adding {{{|floatRight|k}}} (or {{{|floatLeft|k}}}) at beginning of table.
***/
/*{{{*/
.viewer .floatRight {
float:right;
}
.viewer .floatLeft {
float:left;
}
.viewer .centerThis {
text-align:center;
margin:0 auto;
}
/*}}}*/
/***
!!Header Rows /% ================= %/
***/
/*{{{*/
.viewer thead,
.viewer thead td {
font-weight:bold;
}
/*}}}*/
/***
!!Cell Alignment /% ================= %/
***/
/*{{{*/
.viewer td {
vertical-align:top;
font-weight:normal;
}
.viewer caption,
.twtable caption {
text-align:left;
}
/*}}}*/
/***
!!Table Row Shading /% ================= %/
***/
/*{{{*/
.viewer tr {
background-color:[[ColorPalette::Background]];
}
/*}}}*/
/***
!!!Alternating Table Row Shading /% =========== %/
Invoke this style with {{{|altRows|k}}} at beginning of table. (~TiddlyWiki automatically assigns {{{.oddRow}}} and {{{.evenRow}}} classes to {{{<TR>}}}s.)
***/
/*{{{*/
.viewer .altRows tr.oddRow {
background-color:[[ColorPalette::Background]];
}
.viewer .altRows tr.evenRow {
background-color:[[ColorPalette::TertiaryLessPale]];
}
/*}}}*/
/***
!!!Borderless Table /% =========== %/
Invoke this style with {{{|borderless|k}}} at beginning of table.
***/
/*{{{*/
.viewer table.borderless,
.viewer table.borderless * {
border: 0;
}
/*}}}*/
/***
!Custom CSS Classes /% ============================================================= %/
***/
/***
!!Sidebars /% ================= %/
***/
/*{{{*/
.sidebar,
.sidebarRight,
.sidebarLeft {
background-color:[[ColorPalette::SecondaryLight]];
border:2px solid [[ColorPalette::SecondaryMid]];
border-radius:0.5em;
padding:0.6em;
}
.sidebar {
margin:0.3em 5% 0.3em 5%;
}
.sidebarRight {
margin:0.3em;
width:20%;
float:right;
}
.sidebarLeft {
margin:0.3em;
width:20%;
float:left;
}
/*}}}*/
/***
!!Thumbnails /% ================= %/
***/
/*{{{*/
.viewer .thumbs,
.twtable .thumbs {
float:right;
width:124px
}
.thumb img {
width:200px;
float:right;
}
/*}}}*/
/***
!Supplemental Style Sheets /% ============================================================= %/
***/
/*{{{*/
[[StyleSheet_AVweb]]
/*}}}*/
.viewer .tableRWrapper {
float: right;
}
.viewer .tableLWrapper {
float: left;
}
.viewer div.centeredTable {
text-align: center;
}
.viewer div.centeredTable table {
margin: 0 auto;
text-align: left;
}
.viewer table.borderless,
.viewer table.borderless * {
border: 0;
}
.tabSelected {
color: #000;
background: #fff;
border-top: solid 1px #ccc;
border-left: solid 1px #ccc;
border-right: solid 1px #ccc;
border-bottom: none;
}
.viewer .tabSelected:hover{color:#000;}
.viewer .tabSelected {font-weight:bold;}
.tabUnselected {
color: #999;
background: #eee;
border-top: solid 1px #ccc;
border-left: solid 1px #ccc;
border-right: solid 1px #ccc;
border-bottom: solid 1px #ccc;
padding-bottom:1px;
}
.tabContents {
background: #fff;
color: #000;
}
/***
|''Name:''|TableSortingPlugin|
|''Description:''|Dynamically sort tables by clicking on column headers|
|''Author:''|Saq Imtiaz ( lewcid@gmail.com )|
|''Source:''|http://tw.lewcid.org/#TableSortingPlugin|
|''Code Repository:''|http://tw.lewcid.org/svn/plugins|
|''Version:''|2.02|
|''Date:''|25-01-2008|
|''License:''|[[Creative Commons Attribution-ShareAlike 3.0 License|http://creativecommons.org/licenses/by-sa/3.0/]]|
|''~CoreVersion:''|2.2.3|
!!Usage:
* Make sure your table has a header row
** {{{|Name|Phone Number|Address|h}}}<br> Note the /h/ that denote a header row
* Give the table a class of 'sortable'
** {{{
|sortable|k
|Name|Phone Number|Address|h
}}}<br>Note the /k/ that denotes a class name being assigned to the table.
* To disallow sorting by a column, place {{{<<nosort>>}}} in it's header
* To automatically sort a table by a column, place {{{<<autosort>>}}} in the header for that column
** Or to sort automatically but in reverse order, use {{{<<autosort reverse>>}}}
!!Example:
|sortable|k
|Name |Salary |Extension |Performance |File Size |Start date |h
|ZBloggs, Fred |$12000.00 |1353 |+1.2 |74.2Kb |Aug 19, 2003 21:34:00 |
|ABloggs, Fred |$12000.00 |1353 |1.2 |3350b |09/18/2003 |
|CBloggs, Fred |$12000 |1353 |1.200 |55.2Kb |August 18, 2003 |
|DBloggs, Fred |$12000.00 |1353 |1.2 |2100b |07/18/2003 |
|Bloggs, Fred |$12000.00 |1353 |01.20 |6.156Mb |08/17/2003 05:43 |
|Turvey, Kevin |$191200.00 |2342 |-33 |1b |02/05/1979 |
|Mbogo, Arnold |$32010.12 |2755 |-21.673 |1.2Gb |09/08/1998 |
|Shakespeare, Bill |£122000.00|3211 |6 |33.22Gb |12/11/1961 |
|Shakespeare, Hamlet |£9000 |9005 |-8 |3Gb |01/01/2002 |
|Fitz, Marvin |€3300.30 |5554 |+5 |4Kb |05/22/1995 |
***/
// /%
//!BEGIN-PLUGIN-CODE
config.tableSorting = {
darrow: "\u2193",
uarrow: "\u2191",
getText : function (o) {
var p = o.cells[SORT_INDEX];
return p.innerText || p.textContent || '';
},
sortTable : function (o,rev) {
SORT_INDEX = o.getAttribute("index");
var c = config.tableSorting;
var T = findRelated(o.parentNode,"TABLE");
if(T.tBodies[0].rows.length<=1)
return;
var itm = "";
var i = 0;
while (itm == "" && i < T.tBodies[0].rows.length) {
itm = c.getText(T.tBodies[0].rows[i]).trim();
i++;
}
if (itm == "")
return;
var r = [];
var S = o.getElementsByTagName("span")[0];
c.fn = c.sortAlpha;
if(!isNaN(Date.parse(itm)))
c.fn = c.sortDate;
else if(itm.match(/^[$|£|€|\+|\-]{0,1}\d*\.{0,1}\d+$/))
c.fn = c.sortNumber;
else if(itm.match(/^\d*\.{0,1}\d+[K|M|G]{0,1}b$/))
c.fn = c.sortFile;
for(i=0; i<T.tBodies[0].rows.length; i++) {
r[i]=T.tBodies[0].rows[i];
}
r.sort(c.reSort);
if(S.firstChild.nodeValue==c.darrow || rev) {
r.reverse();
S.firstChild.nodeValue=c.uarrow;
}
else
S.firstChild.nodeValue=c.darrow;
var thead = T.getElementsByTagName('thead')[0];
var headers = thead.rows[thead.rows.length-1].cells;
for(var k=0; k<headers.length; k++) {
if(!hasClass(headers[k],"nosort"))
addClass(headers[k].getElementsByTagName("span")[0],"hidden");
}
removeClass(S,"hidden");
for(i=0; i<r.length; i++) {
T.tBodies[0].appendChild(r[i]);
c.stripe(r[i],i);
for(var j=0; j<r[i].cells.length;j++){
removeClass(r[i].cells[j],"sortedCol");
}
addClass(r[i].cells[SORT_INDEX],"sortedCol");
}
},
stripe : function (e,i){
var cl = ["oddRow","evenRow"];
i&1? cl.reverse() : cl;
removeClass(e,cl[1]);
addClass(e,cl[0]);
},
sortNumber : function(v) {
var x = parseFloat(this.getText(v).replace(/[^0-9.-]/g,''));
return isNaN(x)? 0: x;
},
sortDate : function(v) {
return Date.parse(this.getText(v));
},
sortAlpha : function(v) {
return this.getText(v).toLowerCase();
},
sortFile : function(v) {
var j, q = config.messages.sizeTemplates, s = this.getText(v);
for (var i=0; i<q.length; i++) {
if ((j = s.toLowerCase().indexOf(q[i].template.replace("%0\u00a0","").toLowerCase())) != -1)
return q[i].unit * s.substr(0,j);
}
return parseFloat(s);
},
reSort : function(a,b){
var c = config.tableSorting;
var aa = c.fn(a);
var bb = c.fn(b);
return ((aa==bb)? 0 : ((aa<bb)? -1:1));
}
};
Story.prototype.tSort_refreshTiddler = Story.prototype.refreshTiddler;
Story.prototype.refreshTiddler = function(title,template,force,customFields,defaultText){
var elem = this.tSort_refreshTiddler.apply(this,arguments);
if(elem){
var tables = elem.getElementsByTagName("TABLE");
var c = config.tableSorting;
for(var i=0; i<tables.length; i++){
if(hasClass(tables[i],"sortable")){
var x = null, rev, table = tables[i], thead = table.getElementsByTagName('thead')[0], headers = thead.rows[thead.rows.length-1].cells;
for (var j=0; j<headers.length; j++){
var h = headers[j];
if (hasClass(h,"nosort"))
continue;
h.setAttribute("index",j);
h.onclick = function(){c.sortTable(this); return false;};
h.ondblclick = stopEvent;
if(h.getElementsByTagName("span").length == 0)
createTiddlyElement(h,"span",null,"hidden",c.uarrow);
if(!x && hasClass(h,"autosort")) {
x = j;
rev = hasClass(h,"reverse");
}
}
if(x)
c.sortTable(headers[x],rev);
}
}
}
return elem;
};
setStylesheet("table.sortable span.hidden {visibility:hidden;}\n"+
"table.sortable thead {cursor:pointer;}\n"+
"table.sortable .nosort {cursor:default;}\n"+
"table.sortable td.sortedCol {background:#ffc;}","TableSortingPluginStyles");
function stopEvent(e){
var ev = e? e : window.event;
ev.cancelBubble = true;
if (ev.stopPropagation) ev.stopPropagation();
return false;
}
config.macros.nosort={
handler : function(place){
addClass(place,"nosort");
}
};
config.macros.autosort={
handler : function(place,m,p,w,pS){
addClass(place,"autosort"+" "+pS);
}
};
//!END-PLUGIN-CODE
// %/
http://tiddlyvault.tiddlyspot.com/
http://tiddlywiki.org/wiki/Popular_Plugin_Sites
http://www.dcubed.ca/Welcome_to_d-cubed.html
http://rumkin.com/tools/tiddlywiki/
http://www.methods.co.nz/asciidoc/index.html#_introduction
http://www.tiddlytools.com/
http://plugins.tiddlywiki.org/plugins/
http://mgsd.tiddlyspot.com
http://giffmex.tiddlyspot.com/
http://nluoma.tiddlyspot.com/
https://web.archive.org/web/20120219145053/http://tiddlythemes.com/#%5B%5BAll%20themes%5D%5D
https://web.archive.org/web/20120212190104/http://tiddlythemes.com/empties/Monochrome.html
https://web.archive.org/web/20120508220346/http://tiddlythemes.com/empties/Mocha.html
https://web.archive.org/web/20120122052046/http://tiddlythemes.com/empties/K2WS.html
https://web.archive.org/web/20120224080933/http://tiddlythemes.com/empties/MPTW.html
https://web.archive.org/web/20120124011733/http://tiddlythemes.com/empties/D3Gtd.html
http://cookbook.tiddlyspot.com/
http://remindermacros.tiddlyspot.com/
http://iwalton.tiddlyspot.com/
http://puppystudio.tiddlyspot.com/
http://www.math.ist.utl.pt/~psoares/addons.html
https://web.archive.org/web/20120107131734/http://www.math.ist.utl.pt/~psoares/addons_2.4.html
http://tiddlywiki.abego-software.de/
http://blog.jeffreykishner.com/2014/01/17/a-tiddlywiki-filter-to-list-due-dates.html
/***
Description: Contains the stuff you need to use Tiddlyspot
Note, you also need UploadPlugin, PasswordOptionPlugin and LoadRemoteFileThroughProxy
from http://tiddlywiki.bidix.info for a complete working Tiddlyspot site.
***/
//{{{
// edit this if you are migrating sites or retrofitting an existing TW
config.tiddlyspotSiteId = 'css';
// make it so you can by default see edit controls via http
config.options.chkHttpReadOnly = false;
window.readOnly = false; // make sure of it (for tw 2.2)
window.showBackstage = true; // show backstage too
// disable autosave in d3
if (window.location.protocol != "file:")
config.options.chkGTDLazyAutoSave = false;
// tweak shadow tiddlers to add upload button, password entry box etc
with (config.shadowTiddlers) {
SiteUrl = 'http://wiki.elder-geek.net'+config.tiddlyspotSiteId;
SideBarOptions = SideBarOptions.replace(/(<<saveChanges>>)/,"$1<<tiddler TspotSidebar>>");
OptionsPanel = OptionsPanel.replace(/^/,"<<tiddler TspotOptions>>");
DefaultTiddlers = DefaultTiddlers.replace(/^/,"[[WelcomeToTiddlyspot]] ");
MainMenu = MainMenu.replace(/^/,"[[WelcomeToTiddlyspot]] ");
}
// create some shadow tiddler content
merge(config.shadowTiddlers,{
'TspotOptions':[
"tiddlyspot password:",
"<<option pasUploadPassword>>",
""
].join("\n"),
'TspotControls':[
"| tiddlyspot password:|<<option pasUploadPassword>>|",
"| site management:|<<upload http://wiki.elder-geek.net/" + config.tiddlyspotSiteId + "/store.php index.html . . " + config.tiddlyspotSiteId + ">>//(requires tiddlyspot password)//<br>[[control panel|http://wiki.elder-geek.net/" + config.tiddlyspotSiteId + "/controlpanel]], [[download (go offline)|http://wiki.elder-geek.net/" + config.tiddlyspotSiteId + "/download]]|",
"| links:|[[tiddlyspot.com|http://tiddlyspot.com/]], [[FAQs|http://faq.tiddlyspot.com/]], [[blog|http://tiddlyspot.blogspot.com/]], email [[support|mailto:support@tiddlyspot.com]] & [[feedback|mailto:feedback@tiddlyspot.com]], [[donate|http://tiddlyspot.com/?page=donate]]|"
].join("\n"),
'WelcomeToTiddlyspot':[
"This document is a ~TiddlyWiki from tiddlyspot.com. A ~TiddlyWiki is an electronic notebook that is great for managing todo lists, personal information, and all sorts of things.",
"",
"@@font-weight:bold;font-size:1.3em;color:#444; //What now?// @@ Before you can save any changes, you need to enter your password in the form below. Then configure privacy and other site settings at your [[control panel|http://wiki.elder-geek.net/" + config.tiddlyspotSiteId + "/controlpanel]] (your control panel username is //" + config.tiddlyspotSiteId + "//).",
"<<tiddler TspotControls>>",
"See also GettingStarted.",
"",
"@@font-weight:bold;font-size:1.3em;color:#444; //Working online// @@ You can edit this ~TiddlyWiki right now, and save your changes using the \"save to web\" button in the column on the right.",
"",
"@@font-weight:bold;font-size:1.3em;color:#444; //Working offline// @@ A fully functioning copy of this ~TiddlyWiki can be saved onto your hard drive or USB stick. You can make changes and save them locally without being connected to the Internet. When you're ready to sync up again, just click \"upload\" and your ~TiddlyWiki will be saved back to tiddlyspot.com.",
"",
"@@font-weight:bold;font-size:1.3em;color:#444; //Help!// @@ Find out more about ~TiddlyWiki at [[TiddlyWiki.com|http://tiddlywiki.com]]. Also visit [[TiddlyWiki.org|http://tiddlywiki.org]] for documentation on learning and using ~TiddlyWiki. New users are especially welcome on the [[TiddlyWiki mailing list|http://groups.google.com/group/TiddlyWiki]], which is an excellent place to ask questions and get help. If you have a tiddlyspot related problem email [[tiddlyspot support|mailto:support@tiddlyspot.com]].",
"",
"@@font-weight:bold;font-size:1.3em;color:#444; //Enjoy :)// @@ We hope you like using your tiddlyspot.com site. Please email [[feedback@tiddlyspot.com|mailto:feedback@tiddlyspot.com]] with any comments or suggestions."
].join("\n"),
'TspotSidebar':[
"<<upload http://wiki.elder-geek.net/" + config.tiddlyspotSiteId + "/store.php index.html . . " + config.tiddlyspotSiteId + ">><html><a href='http://wiki.elder-geek.net/" + config.tiddlyspotSiteId + "/download' class='button'>download</a></html>"
].join("\n")
});
//}}}
| !date | !user | !location | !storeUrl | !uploadDir | !toFilename | !backupdir | !origin |
| 15/03/2015 00:52:16 | FredW | [[/|http://wiki.elder-geek.net/css/]] | [[store.php|http://wiki.elder-geek.net/css/store.php]] | . | [[index.html | http://wiki.elder-geek.net/css/index.html]] | . |
| 16/03/2015 08:03:21 | YourName | [[/|http://wiki.elder-geek.net/css/]] | [[store.php|http://wiki.elder-geek.net/css/store.php]] | . | [[index.html | http://wiki.elder-geek.net/css/index.html]] | |
| 16/03/2015 09:59:55 | YourName | [[/|http://wiki.elder-geek.net/css/]] | [[store.php|http://wiki.elder-geek.net/css/store.php]] | . | [[index.html | http://wiki.elder-geek.net/css/index.html]] | |
| 16/03/2015 15:20:20 | YourName | [[/|http://wiki.elder-geek.net/css/]] | [[store.php|http://wiki.elder-geek.net/css/store.php]] | . | [[index.html | http://wiki.elder-geek.net/css/index.html]] | |
| 16/03/2015 15:22:46 | YourName | [[/|http://wiki.elder-geek.net/css/]] | [[store.php|http://wiki.elder-geek.net/css/store.php]] | . | [[index.html | http://wiki.elder-geek.net/css/index.html]] | . |
| 24/03/2015 22:11:47 | FredW | [[/|http://wiki.elder-geek.net/css/]] | [[store.php|http://wiki.elder-geek.net/css/store.php]] | . | [[index.html | http://wiki.elder-geek.net/css/index.html]] | . |
| 04/05/2015 19:41:28 | FredW | [[/|http://wiki.elder-geek.net/css/]] | [[store.php|http://wiki.elder-geek.net/css/store.php]] | . | [[index.html | http://wiki.elder-geek.net/css/index.html]] | . | ok |
| 04/05/2015 20:02:17 | FredW | [[/|http://wiki.elder-geek.net/css/]] | [[store.php|http://wiki.elder-geek.net/css/store.php]] | . | [[index.html | http://wiki.elder-geek.net/css/index.html]] | . | ok |
| 05/05/2015 17:56:21 | FredW | [[/|http://wiki.elder-geek.net/css/]] | [[store.php|http://wiki.elder-geek.net/css/store.php]] | . | [[index.html | http://wiki.elder-geek.net/css/index.html]] | . | ok |
| 05/05/2015 17:57:33 | FredW | [[/|http://wiki.elder-geek.net/css/]] | [[store.php|http://wiki.elder-geek.net/css/store.php]] | . | [[index.html | http://wiki.elder-geek.net/css/index.html]] | . |
/***
|''Name:''|UploadPlugin|
|''Description:''|Save to web a TiddlyWiki|
|''Version:''|4.1.3|
|''Date:''|Feb 24, 2008|
|''Source:''|http://tiddlywiki.bidix.info/#UploadPlugin|
|''Documentation:''|http://tiddlywiki.bidix.info/#UploadPluginDoc|
|''Author:''|BidiX (BidiX (at) bidix (dot) info)|
|''License:''|[[BSD open source license|http://tiddlywiki.bidix.info/#%5B%5BBSD%20open%20source%20license%5D%5D ]]|
|''~CoreVersion:''|2.2.0|
|''Requires:''|PasswordOptionPlugin|
***/
//{{{
version.extensions.UploadPlugin = {
major: 4, minor: 1, revision: 3,
date: new Date("Feb 24, 2008"),
source: 'http://tiddlywiki.bidix.info/#UploadPlugin',
author: 'BidiX (BidiX (at) bidix (dot) info',
coreVersion: '2.2.0'
};
//
// Environment
//
if (!window.bidix) window.bidix = {}; // bidix namespace
bidix.debugMode = false; // true to activate both in Plugin and UploadService
//
// Upload Macro
//
config.macros.upload = {
// default values
defaultBackupDir: '', //no backup
defaultStoreScript: "store.php",
defaultToFilename: "index.html",
defaultUploadDir: ".",
authenticateUser: true // UploadService Authenticate User
};
config.macros.upload.label = {
promptOption: "Save and Upload this TiddlyWiki with UploadOptions",
promptParamMacro: "Save and Upload this TiddlyWiki in %0",
saveLabel: "save to web",
saveToDisk: "save to disk",
uploadLabel: "upload"
};
config.macros.upload.messages = {
noStoreUrl: "No store URL in parmeters or options",
usernameOrPasswordMissing: "Username or password missing"
};
config.macros.upload.handler = function(place,macroName,params) {
if (readOnly)
return;
var label;
if (document.location.toString().substr(0,4) == "http")
label = this.label.saveLabel;
else
label = this.label.uploadLabel;
var prompt;
if (params[0]) {
prompt = this.label.promptParamMacro.toString().format([this.destFile(params[0],
(params[1] ? params[1]:bidix.basename(window.location.toString())), params[3])]);
} else {
prompt = this.label.promptOption;
}
createTiddlyButton(place, label, prompt, function() {config.macros.upload.action(params);}, null, null, this.accessKey);
};
config.macros.upload.action = function(params)
{
// for missing macro parameter set value from options
if (!params) params = {};
var storeUrl = params[0] ? params[0] : config.options.txtUploadStoreUrl;
var toFilename = params[1] ? params[1] : config.options.txtUploadFilename;
var backupDir = params[2] ? params[2] : config.options.txtUploadBackupDir;
var uploadDir = params[3] ? params[3] : config.options.txtUploadDir;
var username = params[4] ? params[4] : config.options.txtUploadUserName;
var password = config.options.pasUploadPassword; // for security reason no password as macro parameter
// for still missing parameter set default value
if ((!storeUrl) && (document.location.toString().substr(0,4) == "http"))
storeUrl = bidix.dirname(document.location.toString())+'/'+config.macros.upload.defaultStoreScript;
if (storeUrl.substr(0,4) != "http")
storeUrl = bidix.dirname(document.location.toString()) +'/'+ storeUrl;
if (!toFilename)
toFilename = bidix.basename(window.location.toString());
if (!toFilename)
toFilename = config.macros.upload.defaultToFilename;
if (!uploadDir)
uploadDir = config.macros.upload.defaultUploadDir;
if (!backupDir)
backupDir = config.macros.upload.defaultBackupDir;
// report error if still missing
if (!storeUrl) {
alert(config.macros.upload.messages.noStoreUrl);
clearMessage();
return false;
}
if (config.macros.upload.authenticateUser && (!username || !password)) {
alert(config.macros.upload.messages.usernameOrPasswordMissing);
clearMessage();
return false;
}
bidix.upload.uploadChanges(false,null,storeUrl, toFilename, uploadDir, backupDir, username, password);
return false;
};
config.macros.upload.destFile = function(storeUrl, toFilename, uploadDir)
{
if (!storeUrl)
return null;
var dest = bidix.dirname(storeUrl);
if (uploadDir && uploadDir != '.')
dest = dest + '/' + uploadDir;
dest = dest + '/' + toFilename;
return dest;
};
//
// uploadOptions Macro
//
config.macros.uploadOptions = {
handler: function(place,macroName,params) {
var wizard = new Wizard();
wizard.createWizard(place,this.wizardTitle);
wizard.addStep(this.step1Title,this.step1Html);
var markList = wizard.getElement("markList");
var listWrapper = document.createElement("div");
markList.parentNode.insertBefore(listWrapper,markList);
wizard.setValue("listWrapper",listWrapper);
this.refreshOptions(listWrapper,false);
var uploadCaption;
if (document.location.toString().substr(0,4) == "http")
uploadCaption = config.macros.upload.label.saveLabel;
else
uploadCaption = config.macros.upload.label.uploadLabel;
wizard.setButtons([
{caption: uploadCaption, tooltip: config.macros.upload.label.promptOption,
onClick: config.macros.upload.action},
{caption: this.cancelButton, tooltip: this.cancelButtonPrompt, onClick: this.onCancel}
]);
},
options: [
"txtUploadUserName",
"pasUploadPassword",
"txtUploadStoreUrl",
"txtUploadDir",
"txtUploadFilename",
"txtUploadBackupDir",
"chkUploadLog",
"txtUploadLogMaxLine"
],
refreshOptions: function(listWrapper) {
var opts = [];
for(i=0; i<this.options.length; i++) {
var opt = {};
opts.push();
opt.option = "";
n = this.options[i];
opt.name = n;
opt.lowlight = !config.optionsDesc[n];
opt.description = opt.lowlight ? this.unknownDescription : config.optionsDesc[n];
opts.push(opt);
}
var listview = ListView.create(listWrapper,opts,this.listViewTemplate);
for(n=0; n<opts.length; n++) {
var type = opts[n].name.substr(0,3);
var h = config.macros.option.types[type];
if (h && h.create) {
h.create(opts[n].colElements['option'],type,opts[n].name,opts[n].name,"no");
}
}
},
onCancel: function(e)
{
backstage.switchTab(null);
return false;
},
wizardTitle: "Upload with options",
step1Title: "These options are saved in cookies in your browser",
step1Html: "<input type='hidden' name='markList'></input><br>",
cancelButton: "Cancel",
cancelButtonPrompt: "Cancel prompt",
listViewTemplate: {
columns: [
{name: 'Description', field: 'description', title: "Description", type: 'WikiText'},
{name: 'Option', field: 'option', title: "Option", type: 'String'},
{name: 'Name', field: 'name', title: "Name", type: 'String'}
],
rowClasses: [
{className: 'lowlight', field: 'lowlight'}
]}
};
//
// upload functions
//
if (!bidix.upload) bidix.upload = {};
if (!bidix.upload.messages) bidix.upload.messages = {
//from saving
invalidFileError: "The original file '%0' does not appear to be a valid TiddlyWiki",
backupSaved: "Backup saved",
backupFailed: "Failed to upload backup file",
rssSaved: "RSS feed uploaded",
rssFailed: "Failed to upload RSS feed file",
emptySaved: "Empty template uploaded",
emptyFailed: "Failed to upload empty template file",
mainSaved: "Main TiddlyWiki file uploaded",
mainFailed: "Failed to upload main TiddlyWiki file. Your changes have not been saved",
//specific upload
loadOriginalHttpPostError: "Can't get original file",
aboutToSaveOnHttpPost: 'About to upload on %0 ...',
storePhpNotFound: "The store script '%0' was not found."
};
bidix.upload.uploadChanges = function(onlyIfDirty,tiddlers,storeUrl,toFilename,uploadDir,backupDir,username,password)
{
var callback = function(status,uploadParams,original,url,xhr) {
if (!status) {
displayMessage(bidix.upload.messages.loadOriginalHttpPostError);
return;
}
if (bidix.debugMode)
alert(original.substr(0,500)+"\n...");
// Locate the storeArea div's
var posDiv = locateStoreArea(original);
if((posDiv[0] == -1) || (posDiv[1] == -1)) {
alert(config.messages.invalidFileError.format([localPath]));
return;
}
bidix.upload.uploadRss(uploadParams,original,posDiv);
};
if(onlyIfDirty && !store.isDirty())
return;
clearMessage();
// save on localdisk ?
if (document.location.toString().substr(0,4) == "file") {
var path = document.location.toString();
var localPath = getLocalPath(path);
saveChanges();
}
// get original
var uploadParams = new Array(storeUrl,toFilename,uploadDir,backupDir,username,password);
var originalPath = document.location.toString();
// If url is a directory : add index.html
if (originalPath.charAt(originalPath.length-1) == "/")
originalPath = originalPath + "index.html";
var dest = config.macros.upload.destFile(storeUrl,toFilename,uploadDir);
var log = new bidix.UploadLog();
log.startUpload(storeUrl, dest, uploadDir, backupDir);
displayMessage(bidix.upload.messages.aboutToSaveOnHttpPost.format([dest]));
if (bidix.debugMode)
alert("about to execute Http - GET on "+originalPath);
var r = doHttp("GET",originalPath,null,null,username,password,callback,uploadParams,null);
if (typeof r == "string")
displayMessage(r);
return r;
};
bidix.upload.uploadRss = function(uploadParams,original,posDiv)
{
var callback = function(status,params,responseText,url,xhr) {
if(status) {
var destfile = responseText.substring(responseText.indexOf("destfile:")+9,responseText.indexOf("\n", responseText.indexOf("destfile:")));
displayMessage(bidix.upload.messages.rssSaved,bidix.dirname(url)+'/'+destfile);
bidix.upload.uploadMain(params[0],params[1],params[2]);
} else {
displayMessage(bidix.upload.messages.rssFailed);
}
};
// do uploadRss
if(config.options.chkGenerateAnRssFeed) {
var rssPath = uploadParams[1].substr(0,uploadParams[1].lastIndexOf(".")) + ".xml";
var rssUploadParams = new Array(uploadParams[0],rssPath,uploadParams[2],'',uploadParams[4],uploadParams[5]);
var rssString = generateRss();
// no UnicodeToUTF8 conversion needed when location is "file" !!!
if (document.location.toString().substr(0,4) != "file")
rssString = convertUnicodeToUTF8(rssString);
bidix.upload.httpUpload(rssUploadParams,rssString,callback,Array(uploadParams,original,posDiv));
} else {
bidix.upload.uploadMain(uploadParams,original,posDiv);
}
};
bidix.upload.uploadMain = function(uploadParams,original,posDiv)
{
var callback = function(status,params,responseText,url,xhr) {
var log = new bidix.UploadLog();
if(status) {
// if backupDir specified
if ((params[3]) && (responseText.indexOf("backupfile:") > -1)) {
var backupfile = responseText.substring(responseText.indexOf("backupfile:")+11,responseText.indexOf("\n", responseText.indexOf("backupfile:")));
displayMessage(bidix.upload.messages.backupSaved,bidix.dirname(url)+'/'+backupfile);
}
var destfile = responseText.substring(responseText.indexOf("destfile:")+9,responseText.indexOf("\n", responseText.indexOf("destfile:")));
displayMessage(bidix.upload.messages.mainSaved,bidix.dirname(url)+'/'+destfile);
store.setDirty(false);
log.endUpload("ok");
} else {
alert(bidix.upload.messages.mainFailed);
displayMessage(bidix.upload.messages.mainFailed);
log.endUpload("failed");
}
};
// do uploadMain
var revised = bidix.upload.updateOriginal(original,posDiv);
bidix.upload.httpUpload(uploadParams,revised,callback,uploadParams);
};
bidix.upload.httpUpload = function(uploadParams,data,callback,params)
{
var localCallback = function(status,params,responseText,url,xhr) {
url = (url.indexOf("nocache=") < 0 ? url : url.substring(0,url.indexOf("nocache=")-1));
if (xhr.status == 404)
alert(bidix.upload.messages.storePhpNotFound.format([url]));
if ((bidix.debugMode) || (responseText.indexOf("Debug mode") >= 0 )) {
alert(responseText);
if (responseText.indexOf("Debug mode") >= 0 )
responseText = responseText.substring(responseText.indexOf("\n\n")+2);
} else if (responseText.charAt(0) != '0')
alert(responseText);
if (responseText.charAt(0) != '0')
status = null;
callback(status,params,responseText,url,xhr);
};
// do httpUpload
var boundary = "---------------------------"+"AaB03x";
var uploadFormName = "UploadPlugin";
// compose headers data
var sheader = "";
sheader += "--" + boundary + "\r\nContent-disposition: form-data; name=\"";
sheader += uploadFormName +"\"\r\n\r\n";
sheader += "backupDir="+uploadParams[3] +
";user=" + uploadParams[4] +
";password=" + uploadParams[5] +
";uploaddir=" + uploadParams[2];
if (bidix.debugMode)
sheader += ";debug=1";
sheader += ";;\r\n";
sheader += "\r\n" + "--" + boundary + "\r\n";
sheader += "Content-disposition: form-data; name=\"userfile\"; filename=\""+uploadParams[1]+"\"\r\n";
sheader += "Content-Type: text/html;charset=UTF-8" + "\r\n";
sheader += "Content-Length: " + data.length + "\r\n\r\n";
// compose trailer data
var strailer = new String();
strailer = "\r\n--" + boundary + "--\r\n";
data = sheader + data + strailer;
if (bidix.debugMode) alert("about to execute Http - POST on "+uploadParams[0]+"\n with \n"+data.substr(0,500)+ " ... ");
var r = doHttp("POST",uploadParams[0],data,"multipart/form-data; ;charset=UTF-8; boundary="+boundary,uploadParams[4],uploadParams[5],localCallback,params,null);
if (typeof r == "string")
displayMessage(r);
return r;
};
// same as Saving's updateOriginal but without convertUnicodeToUTF8 calls
bidix.upload.updateOriginal = function(original, posDiv)
{
if (!posDiv)
posDiv = locateStoreArea(original);
if((posDiv[0] == -1) || (posDiv[1] == -1)) {
alert(config.messages.invalidFileError.format([localPath]));
return;
}
var revised = original.substr(0,posDiv[0] + startSaveArea.length) + "\n" +
store.allTiddlersAsHtml() + "\n" +
original.substr(posDiv[1]);
var newSiteTitle = getPageTitle().htmlEncode();
revised = revised.replaceChunk("<title"+">","</title"+">"," " + newSiteTitle + " ");
revised = updateMarkupBlock(revised,"PRE-HEAD","MarkupPreHead");
revised = updateMarkupBlock(revised,"POST-HEAD","MarkupPostHead");
revised = updateMarkupBlock(revised,"PRE-BODY","MarkupPreBody");
revised = updateMarkupBlock(revised,"POST-SCRIPT","MarkupPostBody");
return revised;
};
//
// UploadLog
//
// config.options.chkUploadLog :
// false : no logging
// true : logging
// config.options.txtUploadLogMaxLine :
// -1 : no limit
// 0 : no Log lines but UploadLog is still in place
// n : the last n lines are only kept
// NaN : no limit (-1)
bidix.UploadLog = function() {
if (!config.options.chkUploadLog)
return; // this.tiddler = null
this.tiddler = store.getTiddler("UploadLog");
if (!this.tiddler) {
this.tiddler = new Tiddler();
this.tiddler.title = "UploadLog";
this.tiddler.text = "| !date | !user | !location | !storeUrl | !uploadDir | !toFilename | !backupdir | !origin |";
this.tiddler.created = new Date();
this.tiddler.modifier = config.options.txtUserName;
this.tiddler.modified = new Date();
store.addTiddler(this.tiddler);
}
return this;
};
bidix.UploadLog.prototype.addText = function(text) {
if (!this.tiddler)
return;
// retrieve maxLine when we need it
var maxLine = parseInt(config.options.txtUploadLogMaxLine,10);
if (isNaN(maxLine))
maxLine = -1;
// add text
if (maxLine != 0)
this.tiddler.text = this.tiddler.text + text;
// Trunck to maxLine
if (maxLine >= 0) {
var textArray = this.tiddler.text.split('\n');
if (textArray.length > maxLine + 1)
textArray.splice(1,textArray.length-1-maxLine);
this.tiddler.text = textArray.join('\n');
}
// update tiddler fields
this.tiddler.modifier = config.options.txtUserName;
this.tiddler.modified = new Date();
store.addTiddler(this.tiddler);
// refresh and notifiy for immediate update
story.refreshTiddler(this.tiddler.title);
store.notify(this.tiddler.title, true);
};
bidix.UploadLog.prototype.startUpload = function(storeUrl, toFilename, uploadDir, backupDir) {
if (!this.tiddler)
return;
var now = new Date();
var text = "\n| ";
var filename = bidix.basename(document.location.toString());
if (!filename) filename = '/';
text += now.formatString("0DD/0MM/YYYY 0hh:0mm:0ss") +" | ";
text += config.options.txtUserName + " | ";
text += "[["+filename+"|"+location + "]] |";
text += " [[" + bidix.basename(storeUrl) + "|" + storeUrl + "]] | ";
text += uploadDir + " | ";
text += "[[" + bidix.basename(toFilename) + " | " +toFilename + "]] | ";
text += backupDir + " |";
this.addText(text);
};
bidix.UploadLog.prototype.endUpload = function(status) {
if (!this.tiddler)
return;
this.addText(" "+status+" |");
};
//
// Utilities
//
bidix.checkPlugin = function(plugin, major, minor, revision) {
var ext = version.extensions[plugin];
if (!
(ext &&
((ext.major > major) ||
((ext.major == major) && (ext.minor > minor)) ||
((ext.major == major) && (ext.minor == minor) && (ext.revision >= revision))))) {
// write error in PluginManager
if (pluginInfo)
pluginInfo.log.push("Requires " + plugin + " " + major + "." + minor + "." + revision);
eval(plugin); // generate an error : "Error: ReferenceError: xxxx is not defined"
}
};
bidix.dirname = function(filePath) {
if (!filePath)
return;
var lastpos;
if ((lastpos = filePath.lastIndexOf("/")) != -1) {
return filePath.substring(0, lastpos);
} else {
return filePath.substring(0, filePath.lastIndexOf("\\"));
}
};
bidix.basename = function(filePath) {
if (!filePath)
return;
var lastpos;
if ((lastpos = filePath.lastIndexOf("#")) != -1)
filePath = filePath.substring(0, lastpos);
if ((lastpos = filePath.lastIndexOf("/")) != -1) {
return filePath.substring(lastpos + 1);
} else
return filePath.substring(filePath.lastIndexOf("\\")+1);
};
bidix.initOption = function(name,value) {
if (!config.options[name])
config.options[name] = value;
};
//
// Initializations
//
// require PasswordOptionPlugin 1.0.1 or better
bidix.checkPlugin("PasswordOptionPlugin", 1, 0, 1);
// styleSheet
setStylesheet('.txtUploadStoreUrl, .txtUploadBackupDir, .txtUploadDir {width: 22em;}',"uploadPluginStyles");
//optionsDesc
merge(config.optionsDesc,{
txtUploadStoreUrl: "Url of the UploadService script (default: store.php)",
txtUploadFilename: "Filename of the uploaded file (default: in index.html)",
txtUploadDir: "Relative Directory where to store the file (default: . (downloadService directory))",
txtUploadBackupDir: "Relative Directory where to backup the file. If empty no backup. (default: ''(empty))",
txtUploadUserName: "Upload Username",
pasUploadPassword: "Upload Password",
chkUploadLog: "do Logging in UploadLog (default: true)",
txtUploadLogMaxLine: "Maximum of lines in UploadLog (default: 10)"
});
// Options Initializations
bidix.initOption('txtUploadStoreUrl','');
bidix.initOption('txtUploadFilename','');
bidix.initOption('txtUploadDir','');
bidix.initOption('txtUploadBackupDir','');
bidix.initOption('txtUploadUserName','');
bidix.initOption('pasUploadPassword','');
bidix.initOption('chkUploadLog',true);
bidix.initOption('txtUploadLogMaxLine','10');
// Backstage
merge(config.tasks,{
uploadOptions: {text: "upload", tooltip: "Change UploadOptions and Upload", content: '<<uploadOptions>>'}
});
config.backstageTasks.push("uploadOptions");
//}}}
This document is a ~TiddlyWiki from tiddlyspot.com. A ~TiddlyWiki is an electronic notebook that is great for managing todo lists, personal information, and all sorts of things.
@@font-weight:bold;font-size:1.3em;color:#444; //What now?// @@ Before you can save any changes, you need to enter your password in the form below. Then configure privacy and other site settings at your [[control panel|http://blank.tiddlyspot.com/controlpanel]] (your control panel username is //css//).
<<tiddler TspotControls>>
See also GettingStarted.
@@font-weight:bold;font-size:1.3em;color:#444; //Working online// @@ You can edit this ~TiddlyWiki right now, and save your changes using the "save to web" button in the column on the right.
@@font-weight:bold;font-size:1.3em;color:#444; //Working offline// @@ A fully functioning copy of this ~TiddlyWiki can be saved onto your hard drive or USB stick. You can make changes and save them locally without being connected to the Internet. When you're ready to sync up again, just click "upload" and your ~TiddlyWiki will be saved back to tiddlyspot.com.
@@font-weight:bold;font-size:1.3em;color:#444; //Help!// @@ Find out more about ~TiddlyWiki at [[TiddlyWiki.com|http://tiddlywiki.com]]. Also visit [[TiddlyWiki.org|http://tiddlywiki.org]] for documentation on learning and using ~TiddlyWiki. New users are especially welcome on the [[TiddlyWiki mailing list|http://groups.google.com/group/TiddlyWiki]], which is an excellent place to ask questions and get help. If you have a tiddlyspot related problem email [[tiddlyspot support|mailto:support@tiddlyspot.com]].
@@font-weight:bold;font-size:1.3em;color:#444; //Enjoy :)// @@ We hope you like using your tiddlyspot.com site. Please email [[feedback@tiddlyspot.com|mailto:feedback@tiddlyspot.com]] with any comments or suggestions.
<<tiddler [[YourSearch Help]]>>
!Field Search
With the Field Search you can restrict your search to certain fields of a tiddler, e.g only search the tags or only the titles. The general form is //fieldname//'':''//textToSearch// (e.g. {{{title:intro}}}). In addition one-character shortcuts are also supported for the standard fields {{{title}}}, {{{text}}} and {{{tags}}}:
|!What you want|!What you type|!Example|
|Search ''titles only''|start word with ''!''|{{{!jonny}}} (shortcut for {{{title:jonny}}})|
|Search ''contents/text only''|start word with ''%''|{{{%football}}} (shortcut for {{{text:football}}})|
|Search ''tags only''|start word with ''#''|{{{#Plugin}}} (shortcut for {{{tags:Plugin}}})|
Using this feature you may also search the extended fields ("Metadata") introduced with TiddlyWiki 2.1, e.g. use {{{priority:1}}} to find all tiddlers with the priority field set to "1".
You may search a word in more than one field. E.g. {{{!#Plugin}}} (or {{{title:tags:Plugin}}} in the "long form") finds tiddlers containing "Plugin" either in the title or in the tags (but does not look for "Plugin" in the text).
!Boolean Search
The Boolean Search is useful when searching for multiple words.
|!What you want|!What you type|!Example|
|''All words'' must exist|List of words|{{{jonny jeremy}}} (or {{{jonny and jeremy}}})|
|''At least one word'' must exist|Separate words by ''or''|{{{jonny or jeremy}}}|
|A word ''must not exist''|Start word with ''-''|{{{-jonny}}} (or {{{not jonny}}})|
''Note:'' When you specify two words, separated with a space, YourSearch finds all tiddlers that contain both words, but not necessarily next to each other. If you want to find a sequence of word, e.g. '{{{John Brown}}}', you need to put the words into quotes. I.e. you type: {{{"john brown"}}}.
Using parenthesis you may change the default "left to right" evaluation of the boolean search. E.g. {{{not (jonny or jeremy)}}} finds all tiddlers that contain neither "jonny" nor "jeremy. In contrast to this {{{not jonny or jeremy}}} (i.e. without parenthesis) finds all tiddlers that either don't contain "jonny" or that contain "jeremy".
!'Exact Word' Search
By default a search result all matches that 'contain' the searched text. E.g. if you search for {{{Task}}} you will get all tiddlers containing 'Task', but also '~CompletedTask', '~TaskForce' etc.
If you only want to get the tiddlers that contain 'exactly the word' you need to prefix it with a '='. E.g. typing '=Task' will find the tiddlers that contain the word 'Task', ignoring words that just contain 'Task' as a substring.
!~CaseSensitiveSearch and ~RegExpSearch
The standard search options ~CaseSensitiveSearch and ~RegExpSearch are fully supported by YourSearch. However when ''~RegExpSearch'' is on Filtered and Boolean Search are disabled.
In addition you may do a "regular expression" search even with the ''~RegExpSearch'' set to false by directly entering the regular expression into the search field, framed with {{{/.../}}}.
Example: {{{/m[ae][iy]er/}}} will find all tiddlers that contain either "maier", "mayer", "meier" or "meyer".
!~JavaScript Expression Filtering
If you are familiar with JavaScript programming and know some TiddlyWiki internals you may also use JavaScript expression for the search. Just enter a JavaScript boolean expression into the search field, framed with {{{ { ... } }}}. In the code refer to the variable tiddler and evaluate to {{{true}}} when the given tiddler should be included in the result.
Example: {{{ { tiddler.modified > new Date("Jul 4, 2005")} }}} returns all tiddler modified after July 4th, 2005.
!Combined Search
You are free to combine the various search options.
''Examples''
|!What you type|!Result|
|{{{!jonny !jeremy -%football}}}|all tiddlers with both {{{jonny}}} and {{{jeremy}}} in its titles, but no {{{football}}} in content.|
|{{{#=Task}}}|All tiddlers tagged with 'Task' (the exact word). Tags named '~CompletedTask', '~TaskForce' etc. are not considered.|
!Access Keys
You are encouraged to use the access keys (also called "shortcut" keys) for the most frequently used operations. For quick reference these shortcuts are also mentioned in the tooltip for the various buttons etc.
|!Key|!Operation|
|{{{Alt-F}}}|''The most important keystroke'': It moves the cursor to the search input field so you can directly start typing your query. Pressing {{{Alt-F}}} will also display the previous search result. This way you can quickly display multiple tiddlers using "Press {{{Alt-F}}}. Select tiddler." sequences.|
|{{{ESC}}}|Closes the [[YourSearch Result]]. When the [[YourSearch Result]] is already closed and the cursor is in the search input field the field's content is cleared so you start a new query.|
|{{{Alt-1}}}, {{{Alt-2}}},... |Pressing these keys opens the first, second etc. tiddler from the result list.|
|{{{Alt-O}}}|Opens all found tiddlers.|
|{{{Alt-P}}}|Toggles the 'Preview Text' mode.|
|{{{Alt-'<'}}}, {{{Alt-'>'}}}|Displays the previous or next page in the [[YourSearch Result]].|
|{{{Return}}}|When you have turned off the 'as you type' search mode pressing the {{{Return}}} key actually starts the search (as does pressing the 'search' button).|
//If some of these shortcuts don't work for you check your browser if you have other extensions installed that already "use" these shortcuts.//
|>|!YourSearch Options|
|>|<<option chkUseYourSearch>> Use 'Your Search'|
|!|<<option chkPreviewText>> Show Text Preview|
|!|<<option chkSearchAsYouType>> 'Search As You Type' Mode (No RETURN required to start search)|
|!|Default Search Filter:<<option chkSearchInTitle>>Title ('!') <<option chkSearchInText>>Text ('%') <<option chkSearchInTags>>Tags ('#') <<option chkSearchExtendedFields>>Extended Fields<html><br><font size="-2">The fields of a tiddlers that are searched when you don't explicitly specify a filter in the search text <br>(Explictly specify fields using one or more '!', '%', '#' or 'fieldname:' prefix before the word/text to find).</font></html>|
|!|Number of items on search result page: <<option txtItemsPerPage>>|
|!|Number of items on search result page with preview text: <<option txtItemsPerPageWithPreview>>|
<!--{{{-->
<span class='yourSearchNumber' macro='foundTiddler number'></span>
<span class='yourSearchTitle' macro='foundTiddler title'/></span> -
<span macro='foundTiddler field includeURL'/></span> -
<span class='yourSearchTags' macro='foundTiddler field tags 50'/></span>
<span macro="yourSearch if previewText"><div class='yourSearchText' macro='foundTiddler field text 250'/></div></span>
<!--}}}-->
/***
| ''Name:''|YourSearchPlugin|
| ''Description:''|Automatically replaces text strings in tiddlers when the tiddler is saved.|
| ''~CoreVersion:''|2.1.0|
| ''Original Version:''|2.1.5 (2010-02-16)|
| ''Modified Version:''|2.1.5.1|
| ''Last Modified:''|16 May 2011|
| ''Location:''|http://tiddlywiki.abego-software.de/#YourSearchPlugin|
| ''Requires:''|[[YourSearch]]<br>[[YourSearch Help]]<br>[[YourSearch Options]]<br>[[YourSearchItemTemplate]]<br>[[YourSearchResultTemplate]]<br>[[YourSearchStyleSheet]]<br>(built-in shadow tiddlers)|
| ''Browser:''|Firefox 1.0.4+; Firefox 1.5; ~InternetExplorer 6.0|
| ''Author:''|Udo Borkowski (ub [at] abego-software [dot] de)|
| ''Community:''|[[del.icio.us|http://del.icio.us/post?url=http://tiddlywiki.abego-software.de/index.html%23YourSearchPlugin]]|
| ''License:''|[[BSD open source license (abego Software)|http://www.abego-software.de/legal/apl-v10.html]]|
| ''Copyright:''|© 2005-2010 [[abego Software|http://www.abego-software.de]]|
***/
/***
!About YourSearch
YourSearch gives you a bunch of new features to simplify and speed up your daily searches in TiddlyWiki. It seamlessly integrates into the standard TiddlyWiki search: just start typing into the 'search' field and explore!
For more information see [[Help|YourSearch Help]].
!Compatibility
This plugin requires TiddlyWiki 2.1.
Check the [[archive|http://tiddlywiki.abego-software.de/archive]] for ~YourSearchPlugins supporting older versions of TiddlyWiki.
!Revision history
* v2.1.5.1 (2011-05-16)
** Modified by Scott Simmons (Secret-HQ) to specify a color for {{{.yourSearchResult .summary}}}, which previously defaulted to the Foreground color in the ColorPalette (and disappeared when using the ~ColorPaletteDevFireModified ~SystemPalette).
* v2.1.5 (2010-02-16)
** Fixed problems with CSS and search textfield. Thanks to Guido Glatzel for reporting.
* v2.1.4 (2009-09-04)
** Fixed "this command is not supported" error under IE 8. Thanks to rouilj for reporting. (For details see: http://groups.google.com/group/TiddlyWiki/browse_thread/thread/cffee3254381e478)
* v2.1.3 (2008-04-16)
** Fixed problem with Firefox3. Thanks to Andreas Hoefler for reporting.
* v2.1.2 (2008-03-17)
** Bug: on IE (6.0) the first letter is dropped from the search string. Thanks to Kashgarinn and Nick Padfield for reporting.
* v2.1.1 (2007-03-11)
** Extend "New tiddler" feature: Ctrl-Return invokes the "new tiddler" feature (create tiddler based on search text)
** Extend "New tiddler" feature: tiddler's text and tags may also be specified (see abego.parseNewTiddlerCommandLine)
** Support searching for URLs (like http://www.example.com)
** Provided extended public API (abego.YourSearch.getFoundTiddlers/getQuery/onShowResult)
** Clear MessageBox when search field gets focus (so the box no longer hides the search field)
** Reset search result when TiddlyWiki is changed
** Fix function abego.BoolExp
* v2.1.0 (2006-10-12)
** Release version with TiddlyWiki 2.1 support
*** Support (Extended) Field search
*** Support parenthesis in Boolean Search
*** Support direct regular expression input
*** Support JavaScript Expressions for filtering
*** "new tiddler" feature (create tiddler based on search text)
* v2.0.2 (2006-02-13)
** Bugfix for Firefox 1.5.0.1 related to the "Show prefix" checkbox. Thanks to Ted Pavlic for reporting and to BramChen for fixing.
** Internal
*** Make "JSLint" conform
* v2.0.1 (2006-02-05)
** Support "Exact Word Match" (use '=' to prefix word)
** Support default filter settings (when no filter flags are given in search term)
** Rework on the "less than 3 chars search text" feature (thanks to EricShulman)
** Better support SinglePageMode when doing "Open all tiddlers" (thanks to EricShulman)
** Support Firefox 1.5.0.1
** Bug: Fixed a hilite bug in "classic search mode" (thanks to EricShulman)
* v2.0.0 (2006-01-16)
** Add User Interface
* v1.0.1 (2006-01-06)
** Support TiddlyWiki 2.0
* v1.0.0 (2005-12-28)
** initial version
!Source Code
***/
//{{{
//============================================================================
//============================================================================
// YourSearchPlugin
//============================================================================
//============================================================================
// Ensure that the Plugin is only installed once.
//
if (!version.extensions.YourSearchPlugin) {
version.extensions.YourSearchPlugin = {
major: 2, minor: 1, revision: 5,
source: "http://tiddlywiki.abego-software.de/#YourSearchPlugin",
licence: "[[BSD open source license (abego Software)|http://www.abego-software.de/legal/apl-v10.html]]",
copyright: "Copyright (c) abego Software GmbH, 2005-2010 (www.abego-software.de)"
};
if (!window.abego) window.abego = {};
// define the Array forEach when not yet defined (e.g. by Mozilla)
if (!Array.forEach) {
Array.forEach = function(obj, callback, thisObj) {
for (var i = 0,len = obj.length; i < len; i++)
callback.call(thisObj, obj[i], i, obj);
};
Array.prototype.forEach = function(callback, thisObj) {
for (var i = 0,len = this.length; i < len; i++)
callback.call(thisObj, this[i], i, this);
};
}
abego.toInt = function(s, defaultValue) {
if (!s) return defaultValue;
var n = parseInt(s);
return (n == NaN) ? defaultValue : n;
};
abego.createEllipsis = function(place) {
var e = createTiddlyElement(place,"span");
e.innerHTML = "…";
};
//#concept Object
//
abego.shallowCopy = function(object) {
if (!object)
return object;
var result = {};
for (var n in object)
result[n] = object[n];
return result;
};
// Returns a shallow copy of the options, or a new, empty object if options is null/undefined.
//
// @param options [may be null/undefined]
//
//#concept Object, Options
//#import abego.shallowCopy
//
abego.copyOptions = function(options) {
return !options ? {} : abego.shallowCopy(options);
};
//#import abego.define-namespace
// returns the number of occurances of s in the text
abego.countStrings = function(text, s) {
if (!s)
return 0;
var len = s.length;
var n = 0;
var lastIndex = 0;
while (1) {
var i = text.indexOf(s, lastIndex);
if (i < 0)
return n;
n++;
lastIndex = i+len;
}
return n;
};// Returns the content of the first "braced" text {...}
// Also takes care of nested braces
//
// Returns undefined when no braced text is found or it is not properly nested
//
// @param [optional] when defined and a braced text is found lastIndexRef.lastIndex will contain the index of the char following the (final) closing brace on return.
//
abego.getBracedText = function(text, offset,lastIndexRef) {
if (!offset) offset = 0;
var re = /\{([^\}]*)\}/gm;
re.lastIndex = offset;
var m = re.exec(text);
if (m) {
// The matching stopped at the first closing brace.
// But if the matched text contains opening braces
// this is not the final closing brace.
// Handle this case specially, find the "corresponding" closing brace
var s = m[1];
var nExtraOpenBrace = abego.countStrings(s,"{");
if (!nExtraOpenBrace) {
if (lastIndexRef)
lastIndexRef.lastIndex = re.lastIndex;
// simple case: no nested braces
return s;
}
// special case: "nested braces"
var len = text.length;
for (var i = re.lastIndex; i < len && nExtraOpenBrace; i++) {
var c = text.charAt(i);
if (c == "{")
nExtraOpenBrace++;
else if (c == "}")
nExtraOpenBrace--;
}
if (!nExtraOpenBrace) {
// found the corresponding "}".
if (lastIndexRef)
lastIndexRef.lastIndex = i-1;
return text.substring(m.index+1, i-1);
}
}
// no return means: return undefined;
};
// Returns an array with those items from the array that pass the given test
//
// @param test an one-arg boolean function that returns true when the item should be added.
// @param testObj [optional] the receiver for the test function (global if undefined or null)
// @param result [optional] an array. When define the selected items are added to this array, otherwise a new array is used.
//
//#import Array.prototype.forEach
//
abego.select = function(array,test,testObj,result) {
if (!result) result = [];
array.forEach(function(t) {
if (test.call(testObj,t))
result.push(t);
});
return result;
};
// A portable way to "consume an event"
//
// (Uses "stopPropagation" and "preventDefault", but will also "cancelBubble",
// even though this is a "non-standard method" , just in case).
//
abego.consumeEvent = function(e) {
if (e.stopPropagation) e.stopPropagation();
if (e.preventDefault) e.preventDefault();
e.cancelBubble = true;
e.returnValue = true;
};
// Class abego.TiddlerFilterTerm =================================================================
//
// Used to check if a tiddler contains a given text.
//
// A list of fields (standard and/or extended) may be specified to restrict the search to certain fields.
//
// When no explicit fields are given the fields defined by defaultFields are checked, plus all extended
// fields (when options.withExtendedFields is true).
//
// @param options [may be null/undefined]
// options.fields @seeParam abego.MultiFieldRegExpTester.fields
// options.withExtendedFields @seeParam abego.MultiFieldRegExpTester.withExtendedFields
// options.caseSensitive [Default: false]
// options.fullWordMatch [Default: false]
// options.textIsRegExp [Default: false] when true the given text is already a regExp
//
//#import abego.MultiFieldRegExpTester
//
abego.TiddlerFilterTerm = function(text,options) {
if (!options) options = {};
var reText = text;
if (!options.textIsRegExp) {
reText = text.escapeRegExp();
if (options.fullWordMatch)
reText = "\\b"+reText+"\\b";
}
var regExp = new RegExp(reText, "m"+(options.caseSensitive ? "" : "i"));
this.tester = new abego.MultiFieldRegExpTester(regExp, options.fields, options.withExtendedFields);
}
abego.TiddlerFilterTerm.prototype.test = function(tiddler) {
return this.tester.test(tiddler);
}
// Recognize a string like
// "Some Title. Some content text #Tag1 #Tag2 Tag3"
// with the tags and the text being optional.
// Also the period at the end of the title is optional when no content text is specified)
//
// Returns the result in an object with properties "title" and "params",
// with "params" following the parseParams format, containing the "tag" and "text" arguments.
//
abego.parseNewTiddlerCommandLine = function(s) {
var m = /(.*?)\.(?:\s+|$)([^#]*)(#.*)?/.exec(s);
if (!m)
m = /([^#]*)()(#.*)?/.exec(s);
if (m) {
var r;
if (m[3]) {
var s2 = m[3].replace(/#/g,"");
r = s2.parseParams("tag");
} else
r = [[]];
// add the text parameter
var text = m[2]?m[2].trim():"";
r.push({name: "text", value: text});
r[0].text = [text];
return {title: m[1].trim(), params: r};
} else
return {title: s.trim(),params: [[]]};
}
// options.defaultFields [@seeOptionDefault abego.TiddlerFilterTerm.fields] fields to check when no fields are explicitly specified in queryText.
// options.withExtendedFields [@seeOptionDefault abego.TiddlerFilterTerm.withExtendedFields] when true and no fields are explicitly specified in queryText also the extended fields are considered (in addition to the ones in defaultFields).
// @seeOptions abego.TiddlerFilterTerm (-fields -fullWordMatch -withExtendedFields)
//
//#import abego.getBracedText
//#import abego.copyOptions
//#import abego.TiddlerFilterTerm
//
abego.parseTiddlerFilterTerm = function(queryText,offset,options) {
// group 1: {...} (JavaScript expression)
// group 2: '=' (full word match (optional))
// group 3: [!%#] (field selection short cuts)
// group 4: fieldName ':'
// group 5: String literal "..."
// group 6: RegExp literal /.../
// group 7: scheme '://' nonSpaceChars
// group 8: word
var re = /\s*(?:(?:\{([^\}]*)\})|(?:(=)|([#%!])|(?:(\w+)\s*\:(?!\/\/))|(?:(?:("(?:(?:\\")|[^"])+")|(?:\/((?:(?:\\\/)|[^\/])+)\/)|(\w+\:\/\/[^\s]+)|([^\s\)\-\"]+)))))/mg; // " <- The syntax highlighting of my editors gets confused without this quote
var shortCuts = {'!':'title','%':'text','#':'tags'};
var fieldNames = {};
var fullWordMatch;
re.lastIndex = offset;
while (1) {
var i = re.lastIndex;
var m = re.exec(queryText);
if (!m || m.index != i)
throw "Word or String literal expected";
if (m[1]) {
var lastIndexRef = {};
var code = abego.getBracedText(queryText,0,lastIndexRef);
if (!code)
throw "Invalid {...} syntax";
var f = Function("tiddler","return ("+code+");");
return {func: f,
lastIndex:lastIndexRef.lastIndex,
markRE: null};
}
if (m[2])
fullWordMatch = true;
else if (m[3])
fieldNames[shortCuts[m[3]]] = 1;
else if (m[4])
fieldNames[m[4]] = 1;
else {
var textIsRegExp = m[6];
var text = m[5] ? window.eval(m[5]) : m[6] ? m[6] : m[7] ? m[7] : m[8];
var options = abego.copyOptions(options);
options.fullWordMatch = fullWordMatch;
options.textIsRegExp = textIsRegExp;
var fields = [];
for (var n in fieldNames)
fields.push(n);
if (fields.length == 0) {
options.fields = options.defaultFields;
} else {
options.fields = fields;
options.withExtendedFields = false;
}
var term = new abego.TiddlerFilterTerm(text,options);
var markREText = textIsRegExp ? text : text.escapeRegExp();
if (markREText && fullWordMatch)
markREText = "\\b"+markREText+"\\b";
return {func: function(tiddler) {return term.test(tiddler);},
lastIndex:re.lastIndex,
markRE: markREText ? "(?:"+markREText+")" : null};
}
}
};
// Class abego.BoolExp =================================================================
//
// Allows the execution/evaluation of a boolean expression, according to this syntax:
//
// boolExpression : unaryExpression (("AND"|"OR"|"&&"|"||")? unaryExpression)*
// ;
//
// unaryExpression : ("not"|"-")? primaryExpression
// ;
//
// primaryExpression : "(" boolExpression ")"
// | Term
// ;
//
// For flexibility the Term syntax is defined by a separate parse function.
//
// Notice that there is no precedence between "AND" and "OR" operators, i.e. they are evaluated from left to right.
//
// To evaluate the expression in a given context use code like this:
//
// var be = new abego.BoolExp(s, termParseFunc);
// var result = be.exec(context);
//
// @param s the text defining the expression
// @param parseTermFunc a Function(text,offset,options) that parses the text starting at offset for a "Term" and returns an object with properties {func: Function(context), lastIndex: ...}. func is the function to be used to evaluate the term in the given context.
// @param options [may be null/undefined] (is also passed to the parseTermFunc)
// options.defaultOperationIs_OR [Default: false] When true the concatenation of unaryExpressions (without an operator) is interpreted as an "OR", otherwise as an "AND".
// options.caseSensitive [default: false]
//
abego.BoolExp = function(s, parseTermFunc, options) {
this.s = s;
var defaultOperationIs_OR = options && options.defaultOperationIs_OR;
var reStart = /\s*(?:(\-|not)|(\())/gi; // group 1: NOT, group2 "("
var reCloseParenthesis = /\s*\)/g; // match )
var reAndOr = /\s*(?:(and|\&\&)|(or|\|\|))/gi; // group 1: AND, group 2: OR
var reNonWhiteSpace = /\s*[^\)\s]/g;
var reNot_Parenthesis = /\s*(\-|not)?(\s*\()?/gi;
var parseBoolExpression; //#Pre-declare function name to avoid problem with "shrinkSafe"
var parseUnaryExpression = function(offset) {
reNot_Parenthesis.lastIndex = offset;
var m = reNot_Parenthesis.exec(s);
var negate;
var result;
if (m && m.index == offset) {
offset += m[0].length;
negate = m[1];
if (m[2]) {
// case: (...)
var e = parseBoolExpression(offset);
reCloseParenthesis.lastIndex = e.lastIndex;
if (!reCloseParenthesis.exec(s))
throw "Missing ')'";
result = {func: e.func, lastIndex: reCloseParenthesis.lastIndex, markRE: e.markRE};
}
}
if (!result)
result = parseTermFunc(s,offset,options);
if (negate) {
result.func = (function(f){return function(context) {return !f(context);}})(result.func);
// don't mark patterns that are negated
// (This is essential since the marking may also be used to calculate "ranks". If we
// would also count the negated matches (i.e. that should not exist) the rank may get too high)
result.markRE = null;
}
return result;
};
parseBoolExpression = function(offset) {
var result = parseUnaryExpression(offset);
while (1) {
var l = result.lastIndex;
reAndOr.lastIndex = l;
var m = reAndOr.exec(s);
var isOrCase;
var nextExp;
if (m && m.index == l) {
isOrCase = !m[1];
nextExp = parseUnaryExpression(reAndOr.lastIndex);
} else {
// no "AND" or "OR" found.
// Maybe it is a concatenations of parseUnaryExpression without operators
try {
nextExp = parseUnaryExpression(l);
} catch (e) {
// no unary expression follows. We are done
return result;
}
isOrCase = defaultOperationIs_OR;
}
result.func = (function(func1, func2, isOrCase) {
return isOrCase
? function(context) {return func1(context) || func2(context);}
: function(context) {return func1(context) && func2(context);};
})(result.func,nextExp.func,isOrCase);
result.lastIndex = nextExp.lastIndex;
if (!result.markRE)
result.markRE = nextExp.markRE;
else if (nextExp.markRE)
result.markRE = result.markRE + "|" + nextExp.markRE;
}
};
var expr = parseBoolExpression(0);
this.evalFunc = expr.func;
if (expr.markRE)
this.markRegExp = new RegExp(expr.markRE, options.caseSensitive ? "mg" : "img");
}
abego.BoolExp.prototype.exec = function() {
return this.evalFunc.apply(this,arguments);
};
abego.BoolExp.prototype.getMarkRegExp = function() {
return this.markRegExp;
};
abego.BoolExp.prototype.toString = function() {
return this.s;
};
// Class abego.MultiFieldRegExpTester ==================================================================
//
// @param fields [optional; Default: ["title","text","tags"]] array of names of fields to be considered
// @param withExtendedFields [optional; Default: false] when true also extended fields are considered (in addition to the ones given in 'fields')
//
abego.MultiFieldRegExpTester = function(re, fields, withExtendedFields) {
this.re = re;
this.fields = fields ? fields : ["title","text","tags"];
this.withExtendedFields = withExtendedFields;
}
// Returns the name of the first field found that value succeeds the given test,
// or null when no such field is found
//
abego.MultiFieldRegExpTester.prototype.test = function(tiddler) {
var re = this.re;
// Check the fields explicitly specified
for (var i = 0; i < this.fields.length; i++) {
var s = store.getValue(tiddler, this.fields[i]);
if (typeof s == "string" && re.test(s))
return this.fields[i];
}
// Check the extended fields (if required)
if (this.withExtendedFields)
return store.forEachField(
tiddler,
function(tiddler, fieldName, value) {
return typeof value == "string" && re.test(value)?fieldName:null;
}, true);
return null;
}
// Class abego.TiddlerQuery ==================================================================
//
//#import abego.select
//#import abego.MultiFieldRegExpTester
//
abego.TiddlerQuery = function(queryText,caseSensitive,useRegExp,defaultFields,withExtendedFields) {
if (useRegExp) {
this.regExp = new RegExp(queryText, caseSensitive ? "mg" : "img");
this.tester = new abego.MultiFieldRegExpTester(this.regExp, defaultFields, withExtendedFields);
} else {
this.expr = new abego.BoolExp(
queryText,
abego.parseTiddlerFilterTerm, {
defaultFields: defaultFields,
caseSensitive: caseSensitive,
withExtendedFields: withExtendedFields});
}
this.getQueryText = function() {
return queryText;
};
this.getUseRegExp = function() {
return useRegExp;
};
this.getCaseSensitive = function() {
return caseSensitive;
};
this.getDefaultFields = function() {
return defaultFields;
};
this.getWithExtendedFields = function() {
return withExtendedFields;
};
}
// Returns true iff the query includes the given tiddler
//
// @param tiddler [may be null/undefined]
//
abego.TiddlerQuery.prototype.test = function(tiddler) {
if (!tiddler) return false;
if (this.regExp) {
return this.tester.test(tiddler);
}
return this.expr.exec(tiddler);
};
// Returns an array with those tiddlers from the tiddlers array that match the query.
//
abego.TiddlerQuery.prototype.filter = function(tiddlers) {
return abego.select(tiddlers,this.test,this);
};
abego.TiddlerQuery.prototype.getMarkRegExp = function() {
if (this.regExp) {
// Only use the regExp for marking when it does not match the empty string.
return "".search(this.regExp) >= 0 ? null : this.regExp;
}
return this.expr.getMarkRegExp();
};
abego.TiddlerQuery.prototype.toString = function() {
return (this.regExp ? this.regExp : this.expr).toString();
};
// Class abego.PageWiseRenderer ================================================
//
// Subclass or instance must implement getItemsPerPage function;
// They should also implement onPageChanged and refresh the container of the
// PageWiseRenderer on that event.
//
//#import abego.toInt
//
abego.PageWiseRenderer = function() {
this.firstIndexOnPage = 0; // The index of the first item of the lastResults list displayed on the search result page
};
merge(abego.PageWiseRenderer.prototype, {
setItems: function(items) {
this.items = items;
this.setFirstIndexOnPage(0);
},
// Maximum number of pages listed in the navigation bar (before or after the current page)
//
getMaxPagesInNavigation: function() {
return 10;
},
getItemsCount: function(items) {
return this.items ? this.items.length : 0;
},
getCurrentPageIndex: function() {
return Math.floor(this.firstIndexOnPage / this.getItemsPerPage());
},
getLastPageIndex: function() {
return Math.floor((this.getItemsCount()-1) / this.getItemsPerPage())
},
setFirstIndexOnPage: function(index) {
this.firstIndexOnPage = Math.min(Math.max(0, index), this.getItemsCount()-1);
},
getFirstIndexOnPage: function() {
// Ensure that the firstIndexOnPage is really a page start.
// This may have become violated when getItemsPerPage has changed,
// (e.g. when switching between previewText and simple mode.)
this.firstIndexOnPage = Math.floor(this.firstIndexOnPage / this.getItemsPerPage()) * this.getItemsPerPage();
return this.firstIndexOnPage;
},
getLastIndexOnPage: function() {
return Math.min(this.getFirstIndexOnPage()+this.getItemsPerPage()-1, this.getItemsCount()-1);
},
onPageChanged: function(pageIndex,oldPageIndex) {
},
renderPage: function(itemRenderer) {
if (itemRenderer.beginRendering)
itemRenderer.beginRendering(this);
try {
// When there are items found add them to the result page (pagewise)
if (this.getItemsCount()) {
// Add the items of the current page
var lastIndex = this.getLastIndexOnPage();
var iInPage = -1;
for (var i=this.getFirstIndexOnPage(); i <= lastIndex; i++) {
iInPage++;
itemRenderer.render(this,this.items[i],i,iInPage);
}
}
} finally {
if (itemRenderer.endRendering)
itemRenderer.endRendering(this);
}
},
addPageNavigation: function(place) {
if (!this.getItemsCount()) return;
var self = this;
var onNaviButtonClick = function(e) {
if (!e) var e = window.event;
abego.consumeEvent(e);
var pageIndex = abego.toInt(this.getAttribute("page"),0);
var oldPageIndex = self.getCurrentPageIndex();
if (pageIndex == oldPageIndex)
return;
var index = pageIndex * self.getItemsPerPage();
self.setFirstIndexOnPage(index);
self.onPageChanged(pageIndex,oldPageIndex);
};
var button;
var currentPageIndex = this.getCurrentPageIndex();
var lastPageIndex = this.getLastPageIndex();
if (currentPageIndex > 0) {
button = createTiddlyButton(place, "Previous", "Go to previous page (Shortcut: Alt-'<')", onNaviButtonClick, "prev");
button.setAttribute("page",(currentPageIndex-1).toString());
button.setAttribute("accessKey","<");
}
for (var i = -this.getMaxPagesInNavigation(); i < this.getMaxPagesInNavigation(); i++) {
var pageIndex = currentPageIndex+i;
if (pageIndex < 0) continue;
if (pageIndex > lastPageIndex) break;
var pageNo = (i+currentPageIndex+1).toString();
var buttonClass = pageIndex == currentPageIndex ? "currentPage" : "otherPage";
button = createTiddlyButton(place, pageNo, "Go to page %0".format([pageNo]), onNaviButtonClick, buttonClass);
button.setAttribute("page",(pageIndex).toString());
}
if (currentPageIndex < lastPageIndex) {
button = createTiddlyButton(place, "Next", "Go to next page (Shortcut: Alt-'>')", onNaviButtonClick, "next");
button.setAttribute("page",(currentPageIndex+1).toString());
button.setAttribute("accessKey",">");
}
}
});
// Class abego.LimitedTextRenderer ===========================================================
//
// Renders a given text, ensuring that a given limit of number of characters
// is not exceeded.
//
// A "markRegExp" may be specified. Substring matching this regular expression
// ("matched strings") are rendered with the class "marked".
//
// if the given text is longer than the limit the matched strings are preferred
// to be included in the rendered text (with some leading and trailing "context text").
//
// Example:
// var renderer = new abego.LimitedTextRenderer();
//
// var place = ... // a DOM element that should contain the rendered (limited) text
// var s = "This is another 'Hello World' example, as saying 'Hello' is always nice. So let's say it again: >Hello!<";
// var maxLen = 50;
// var markRE = /hello/gi;
// renderer.render(place,s,maxLen,markRE);
//
//#import abego.createEllipsis
//
abego.LimitedTextRenderer = function() {
var minMatchWithContextSize = 40;
var maxMovementForWordCorrection = 4; // When a "match" context starts or end on a word the context borders may be changed to at most this amount to include or exclude the word.
//----------------------------------------------------------------------------
//
// Ranges
//
// Objects with a "start" and "end" property (not a specific class).
//
// In a corresponding "Ranges array" these objects are sorted by their start
// and no Range object intersects/touches any other in the array.
//
//----------------------------------------------------------------------------
// Adds the Range [startIndex,endIndex[ to the ranges, ensuring that the Ranges
// in the array are sorted by their start and no Range object
// intersects/touches any other in the array (i.e. possibly the new Range is
// "merged" with existing ranges)
//
// @param ranges array of Range objects
//
var addRange = function(ranges, startIndex, endIndex) {
var n = ranges.length;
// When there are no ranges in ranges, just add it.
if (n == 0) {
ranges.push({start: startIndex, end: endIndex});
return;
}
var i = 0;
for (; i < n; i++) {
var range = ranges[i];
// find the first range that intersects or "touches" [startIndex, endIndex[
if (range.start <= endIndex && startIndex <= range.end) {
// Found.
var r;
// find the first range behind the new range that does not interfere
var rIndex = i+1;
for (; rIndex < n; rIndex++) {
r = ranges[rIndex];
if (r.start > endIndex || startIndex > range.end) {
break;
}
}
// Replace the ranges i to rIndex-1 with the union of the new range with these ranges.
var unionStart = startIndex;
var unionEnd = endIndex;
for (var j = i; j < rIndex; j++) {
r = ranges[j];
unionStart = Math.min(unionStart, r.start);
unionEnd = Math.max(unionEnd, r.end);
}
ranges.splice(i, rIndex-i, {start: unionStart, end: unionEnd});
return;
}
// if we found a range R that is right of the new range there is no
// intersection and we can insert the new range before R.
if (range.start > endIndex) {
break;
}
}
// When we are here the new range does not interfere with any range in ranges and
// i is the index of the first range right to it (or ranges.length, when the new range
// becomes the right most range).
ranges.splice(i, 0, {start: startIndex, end: endIndex});
};
// Returns the total size of all Ranges in ranges
//
var getTotalRangesSize = function(ranges) {
var totalRangeSize = 0;
for (var i=0; i < ranges.length; i++) {
var range = ranges[i];
totalRangeSize += range.end-range.start;
}
return totalRangeSize;
};
//----------------------------------------------------------------------------
var isWordChar = function(c) {
return (c >= "a" && c <= "z") || (c >= "A" && c <= "Z") || c == "_";
};
// Returns the bounds of the word in s around offset as a {start: , end:} object.
//
// Returns null when the char at offset is not a word char.
//
var getWordBounds = function(s, offset) {
// Handle the "offset is not in word" case
if (!isWordChar(s[offset])) return null;
for (var i = offset-1; i >= 0 && isWordChar(s[i]); i--)
{/*empty*/}
var startIndex = i+1;
var n = s.length;
for (i = offset+1; i < n && isWordChar(s[i]); i++)
{/*empty*/}
return {start: startIndex, end: i};
};
var moveToWordBorder = function(s, offset, isStartOffset) {
var wordBounds;
if (isStartOffset) {
wordBounds = getWordBounds(s, offset);
} else {
if (offset <= 0) return offset;
wordBounds = getWordBounds(s, offset-1);
}
if (!wordBounds) return offset;
if (isStartOffset) {
if (wordBounds.start >= offset-maxMovementForWordCorrection) return wordBounds.start;
if (wordBounds.end <= offset+maxMovementForWordCorrection) return wordBounds.end;
} else {
if (wordBounds.end <= offset+maxMovementForWordCorrection) return wordBounds.end;
if (wordBounds.start >= offset-maxMovementForWordCorrection) return wordBounds.start;
}
return offset;
};
// Splits s into a sequence of "matched" and "unmatched" substrings, using the
// matchRegExp to do the matching.
//
// Returns an array of objects with a "text" property containing the substring text.
// Substrings that are "matches" also contain a boolean "isMatch" property set to true.
//
// @param matchRegExp [may be null] when null no matching is performed and the returned
// array just contains one item with s as its text
//
var getTextAndMatchArray = function(s, matchRegExp) {
var result = [];
if (matchRegExp) {
var startIndex = 0;
var n = s.length;
var currentLen = 0;
do {
matchRegExp.lastIndex = startIndex;
var match = matchRegExp.exec(s);
if (match) {
if (startIndex < match.index) {
var t = s.substring(startIndex, match.index);
result.push({text:t});
}
result.push({text:match[0], isMatch:true});
startIndex = match.index + match[0].length;
} else {
result.push({text: s.substr(startIndex)});
break;
}
} while (true);
} else {
result.push({text: s});
}
return result;
};
var getMatchedTextCount = function(textAndMatches) {
var result = 0;
for (var i=0; i < textAndMatches.length; i++) {
if (textAndMatches[i].isMatch) {
result++;
}
}
return result;
};
var getContextRangeAround = function(s, startIndex, endIndex, matchCount, maxLen) {
// Partition the available space into equal sized areas for each match and one
// for the text start.
// But the size should not go below a certain limit
var size = Math.max(Math.floor(maxLen/(matchCount+1)), minMatchWithContextSize);
// Substract the size of the range to get the size of the context.
var contextSize = Math.max(size-(endIndex-startIndex), 0);
// Two thirds of the context should be before the match, one third after.
var contextEnd = Math.min(Math.floor(endIndex+contextSize/3), s.length);
var contextStart = Math.max(contextEnd - size, 0);
// If the contextStart/End is inside a word and the end of the word is
// close move the pointers accordingly to make the text more readable.
contextStart = moveToWordBorder(s, contextStart, true);
contextEnd = moveToWordBorder(s, contextEnd, false);
return {start: contextStart, end: contextEnd};
};
// Get all ranges around matched substrings with their contexts
//
var getMatchedTextWithContextRanges = function(textAndMatches, s, maxLen) {
var ranges = [];
var matchCount = getMatchedTextCount(textAndMatches);
var pos = 0;
for (var i=0; i < textAndMatches.length; i++) {
var t = textAndMatches[i];
var text = t.text;
if (t.isMatch) {
var range = getContextRangeAround(s, pos, pos+text.length, matchCount, maxLen);
addRange(ranges, range.start, range.end);
}
pos += text.length;
}
return ranges;
};
var fillUpRanges = function(s, ranges, maxLen) {
var remainingLen = maxLen - getTotalRangesSize(ranges);
while (remainingLen > 0) {
if (ranges.length == 0) {
// No matches added yet. Make one large range.
addRange(ranges, 0, moveToWordBorder(s, maxLen, false));
return;
} else {
var range = ranges[0];
var startIndex;
var maxEndIndex;
if (range.start == 0) {
// The first range already starts at the beginning of the string.
// When there is a second range fill to the next range start or to the maxLen.
startIndex = range.end;
if (ranges.length > 1) {
maxEndIndex = ranges[1].start;
} else {
// Only one range. Add a range after that with the complete remaining len
// (corrected to "beautify" the output)
addRange(ranges, startIndex, moveToWordBorder(s, startIndex+remainingLen, false));
return;
}
} else {
// There is unused space between the start of the text and the first range.
startIndex = 0;
maxEndIndex = range.start;
}
var endIndex = Math.min(maxEndIndex, startIndex+remainingLen);
addRange(ranges, startIndex, endIndex);
remainingLen -= (endIndex-startIndex);
}
}
};
// Write the given ranges of s, using textAndMatches for marking portions of the text.
//
var writeRanges = function(place, s, textAndMatches, ranges, maxLen) {
if (ranges.length == 0) return;
// Processes the text between startIndex and endIndex of the textAndMatches
// "writes" them (as DOM elements) at the given place, possibly as "marked" text.
//
// When endIndex is not the end of the full text an ellisis is appended.
//
var writeTextAndMatchRange = function(place, s, textAndMatches, startIndex, endIndex) {
var t;
var text;
// find the first text item to write
var pos = 0;
var i = 0;
var offset = 0;
for (;i < textAndMatches.length; i++) {
t = textAndMatches[i];
text = t.text;
if (startIndex < pos+text.length) {
offset = startIndex - pos;
break;
}
pos += text.length;
}
var remainingLen = endIndex - startIndex;
for (; i < textAndMatches.length && remainingLen > 0; i++) {
t = textAndMatches[i];
text = t.text.substr(offset);
offset = 0;
if (text.length > remainingLen) text = text.substr(0,remainingLen);
if (t.isMatch) {
createTiddlyElement(place,"span",null,"marked",text);
} else {
createTiddlyText(place, text);
}
remainingLen -= text.length;
}
if (endIndex < s.length) {
abego.createEllipsis(place);
}
};
// When the first range is not at the start of the text write an ellipsis("...")
// (Ellipses between ranges are written in the writeTextAndMatchRange method)
if (ranges[0].start > 0) abego.createEllipsis(place);
var remainingLen = maxLen;
for (var i = 0; i < ranges.length && remainingLen > 0; i++) {
var range = ranges[i];
var len = Math.min(range.end - range.start, remainingLen);
writeTextAndMatchRange(place, s, textAndMatches, range.start, range.start+len);
remainingLen -= len;
}
};
this.render = function(place,s,maxLen,markRegExp) {
if (s.length < maxLen) maxLen = s.length;
var textAndMatches = getTextAndMatchArray(s, markRegExp);
var ranges = getMatchedTextWithContextRanges(textAndMatches, s, maxLen);
// When the maxLen is not yet reached add more ranges
// starting from the beginning until either maxLen or
// the end of the string is reached.
fillUpRanges(s, ranges, maxLen);
writeRanges(place, s, textAndMatches, ranges, maxLen);
};
};
(function() {
function alertAndThrow(msg) {
alert(msg);
throw msg;
};
if (version.major < 2 || (version.major == 2 && version.minor < 1))
alertAndThrow("YourSearchPlugin requires TiddlyWiki 2.1 or newer.\n\nCheck the archive for YourSearch plugins\nsupporting older versions of TiddlyWiki.\n\nArchive: http://tiddlywiki.abego-software.de/archive");
abego.YourSearch = {};
//----------------------------------------------------------------------------
// The Search Core
//----------------------------------------------------------------------------
// Model Variables
var lastResults; // Array of tiddlers that matched the last search
var lastQuery; // The last Search query (TiddlerQuery)
var setLastResults = function(array) {
lastResults = array;
};
var getLastResults = function() {
return lastResults ? lastResults : [];
};
var getLastResultsCount = function() {
return lastResults ? lastResults.length : 0;
};
// Standard Ranking Weights
var matchInTitleWeight = 4;
var precisionInTitleWeight = 10;
var matchInTagsWeight = 2;
var getMatchCount = function(s, re) {
var m = s.match(re);
return m ? m.length : 0;
};
var standardRankFunction = function(tiddler, query) {
// Count the matches in the title and the tags
var markRE = query.getMarkRegExp();
if (!markRE) return 1;
var matchesInTitle = tiddler.title.match(markRE);
var nMatchesInTitle = matchesInTitle ? matchesInTitle.length : 0;
var nMatchesInTags = getMatchCount(tiddler.getTags(), markRE);
// Calculate the "precision" of the matches in the title as the ratio of
// the length of the matches to the total length of the title.
var lengthOfMatchesInTitle = matchesInTitle ? matchesInTitle.join("").length : 0;
var precisionInTitle = tiddler.title.length > 0 ? lengthOfMatchesInTitle/tiddler.title.length : 0;
// calculate a weighted score
var rank= nMatchesInTitle * matchInTitleWeight
+ nMatchesInTags * matchInTagsWeight
+ precisionInTitle * precisionInTitleWeight
+ 1;
return rank;
};
// @return Tiddler[]
//
var findMatches = function(store, searchText,caseSensitive,useRegExp,sortField,excludeTag) {
lastQuery = null;
var candidates = store.reverseLookup("tags",excludeTag,false);
try {
var defaultFields = [];
if (config.options.chkSearchInTitle) defaultFields.push("title");
if (config.options.chkSearchInText) defaultFields.push("text");
if (config.options.chkSearchInTags) defaultFields.push("tags");
lastQuery = new abego.TiddlerQuery(
searchText,caseSensitive, useRegExp,defaultFields,config.options.chkSearchExtendedFields);
} catch (e) {
// when an invalid query is given no tiddlers are matched
return [];
}
var results = lastQuery.filter(candidates);
// Rank the results
var rankFunction = abego.YourSearch.getRankFunction();
for (var i = 0; i < results.length; i++) {
var tiddler = results[i];
var rank = rankFunction(tiddler, lastQuery);
// Add the rank information to the tiddler.
// This is used during the sorting, but it may also
// be used in the result, e.g. to display some "relevance"
// information in the result
tiddler.searchRank = rank;
}
// sort the result, taking care of the rank and the sortField
if(!sortField) {
sortField = "title";
}
var sortFunction = function (a,b) {
var searchRankDiff = a.searchRank - b.searchRank;
if (searchRankDiff == 0) {
if (a[sortField] == b[sortField]) {
return(0);
} else {
return (a[sortField] < b[sortField]) ? -1 : +1;
}
} else {
return (searchRankDiff > 0) ? -1 : +1;
}
};
results.sort(sortFunction);
return results;
};
//----------------------------------------------------------------------------
// The Search UI (Result page)
//----------------------------------------------------------------------------
// Visual appearance of the result page
var maxCharsInTitle = 80;
var maxCharsInTags = 50;
var maxCharsInText = 250;
var maxCharsInField = 50;
var itemsPerPageDefault = 25; // Default maximum number of items on one search result page
var itemsPerPageWithPreviewDefault = 10; // Default maximum number of items on one search result page when PreviewText is on
// DOM IDs
var yourSearchResultID = "yourSearchResult";
var yourSearchResultItemsID = "yourSearchResultItems";
var lastSearchText; // The last search text, as passed to findMatches
var resultElement; // The (popup) DOM element containing the search result [may be null]
var searchInputField; // The "search" input field
var searchButton; // The "search" button
var lastNewTiddlerButton;
var initStylesheet = function() {
if (version.extensions.YourSearchPlugin.styleSheetInited)
return;
version.extensions.YourSearchPlugin.styleSheetInited = true;
setStylesheet(store.getTiddlerText("YourSearchStyleSheet"),"yourSearch");
}
var isResultOpen = function() {
return resultElement != null && resultElement.parentNode == document.body;
};
var closeResult = function() {
if (isResultOpen()) {
document.body.removeChild(resultElement);
}
};
// Closes the Search Result window and displays the tiddler
// defined by the "tiddlyLink" attribute of this element
//
var closeResultAndDisplayTiddler = function(e)
{
closeResult();
var title = this.getAttribute("tiddlyLink");
if(title) {
var withHilite = this.getAttribute("withHilite");
var oldHighlightHack = highlightHack;
if (withHilite && withHilite=="true" && lastQuery) {
highlightHack = lastQuery.getMarkRegExp();
}
story.displayTiddler(this,title);
highlightHack = oldHighlightHack;
}
return(false);
};
// Adjusts the resultElement's size and position, relative to the search input field.
//
var adjustResultPositionAndSize = function() {
if (!searchInputField) return;
var root = searchInputField;
// Position the result below the root and resize it if necessary.
var rootLeft = findPosX(root);
var rootTop = findPosY(root);
var rootHeight = root.offsetHeight;
var popupLeft = rootLeft;
var popupTop = rootTop + rootHeight;
// Make sure the result is not wider than the window
var winWidth = findWindowWidth();
if (winWidth < resultElement.offsetWidth) {
resultElement.style.width = (winWidth - 100)+"px";
winWidth = findWindowWidth();
}
// Ensure that the left and right of the result are not
// clipped by the window. Move it to the left or right, if necessary.
var popupWidth = resultElement.offsetWidth;
if(popupLeft + popupWidth > winWidth)
popupLeft = winWidth - popupWidth-30;
if (popupLeft < 0) popupLeft = 0;
// Do the actual moving
resultElement.style.left = popupLeft + "px";
resultElement.style.top = popupTop + "px";
resultElement.style.display = "block";
};
var scrollVisible = function() {
// Scroll the window to make the result page (and the search Input field) visible.
if (resultElement) window.scrollTo(0,ensureVisible(resultElement));
if (searchInputField) window.scrollTo(0,ensureVisible(searchInputField));
};
// Makes sure the result page has a good size and position and visible
// (may scroll the window)
//
var ensureResultIsDisplayedNicely = function() {
adjustResultPositionAndSize();
scrollVisible();
};
var indexInPage; // The index (in the current page) of the tiddler currently rendered.
var currentTiddler; // While rendering the page the tiddler that is currently rendered.
var pager = new abego.PageWiseRenderer();
var MyItemRenderer = function(parent) {
// Load the template how to display the items that represent a found tiddler
this.itemHtml = store.getTiddlerText("YourSearchItemTemplate");
if (!this.itemHtml) alertAndThrow("YourSearchItemTemplate not found");
// Locate the node that shall contain the list of found tiddlers
this.place = document.getElementById(yourSearchResultItemsID);
if(!this.place)
this.place = createTiddlyElement(parent,"div",yourSearchResultItemsID);
};
merge(MyItemRenderer.prototype,{
render: function(pager,object,index,indexOnPage) {
// Define global variables, referenced by macros during applyHtmlMacros
indexInPage = indexOnPage;
currentTiddler = object;
var item = createTiddlyElement(this.place,"div",null, "yourSearchItem");
item.innerHTML = this.itemHtml;
applyHtmlMacros(item,null);
refreshElements(item,null);
},
endRendering: function(pager) {
// The currentTiddler must only be defined while rendering the found tiddlers
currentTiddler = null;
}
});
// Refreshes the content of the result with the current search result
// of the selected page.
//
// Assumes that the result is already open.
//
var refreshResult = function() {
if (!resultElement || !searchInputField) return;
// Load the template for the YourSearchResult
var html = store.getTiddlerText("YourSearchResultTemplate");
if (!html) html = "<b>Tiddler YourSearchResultTemplate not found</b>";
resultElement.innerHTML = html;
// Expand the template macros etc.
applyHtmlMacros(resultElement,null);
refreshElements(resultElement,null);
var itemRenderer = new MyItemRenderer(resultElement);
pager.renderPage(itemRenderer);
ensureResultIsDisplayedNicely();
};
pager.getItemsPerPage = function() {
var n = (config.options.chkPreviewText)
? abego.toInt(config.options.txtItemsPerPageWithPreview, itemsPerPageWithPreviewDefault)
: abego.toInt(config.options.txtItemsPerPage, itemsPerPageDefault);
return (n > 0) ? n : 1;
};
pager.onPageChanged = function() {
refreshResult();
};
var reopenResultIfApplicable = function() {
if (searchInputField == null || !config.options.chkUseYourSearch) return;
if ((searchInputField.value == lastSearchText) && lastSearchText && !isResultOpen()) {
// For speedup we check re-use the previously created resultElement, if possible.
if (resultElement && (resultElement.parentNode != document.body)) {
document.body.appendChild(resultElement);
ensureResultIsDisplayedNicely();
} else {
abego.YourSearch.onShowResult(true);
}
}
};
var invalidateResult = function() {
closeResult();
resultElement = null;
lastSearchText = null;
};
//-------------------------------------------------------------------------
// Close the search result page when the user clicks on the document
// (and not into the searchInputField, on the search button or in the result)
// or presses the ESC key
// Returns true if e is either self or a descendant (child, grandchild,...) of self.
//
// @param self DOM:Element
// @param e DOM:Element or null
//
var isDescendantOrSelf = function(self, e) {
while (e != null) {
if (self == e) return true;
e = e.parentNode;
}
return false;
};
var onDocumentClick = function(e) {
if (e.target == searchInputField) return;
if (e.target == searchButton) return;
if (resultElement && isDescendantOrSelf(resultElement, e.target)) return;
closeResult();
};
var onDocumentKeyup = function(e) {
// Close the search result page when the user presses "ESC"
if (e.keyCode == 27) closeResult();
};
addEvent(document,"click",onDocumentClick);
addEvent(document,"keyup",onDocumentKeyup);
// Our Search Macro Hijack Function ==========================================
// Helper
var myStorySearch = function(text,useCaseSensitive,useRegExp)
{
lastSearchText = text;
setLastResults(findMatches(store, text,useCaseSensitive,useRegExp,"title","excludeSearch"));
abego.YourSearch.onShowResult();
};
var myMacroSearchHandler = function(place,macroName,params,wikifier,paramString,tiddler)
{
initStylesheet();
lastSearchText = "";
var searchTimeout = null;
var doSearch = function(txt)
{
if (config.options.chkUseYourSearch)
myStorySearch(txt.value,config.options.chkCaseSensitiveSearch,config.options.chkRegExpSearch);
else
story.search(txt.value,config.options.chkCaseSensitiveSearch,config.options.chkRegExpSearch);
lastSearchText = txt.value;
};
var clickHandler = function(e)
{
doSearch(searchInputField);
return false;
};
var keyHandler = function(e)
{
if (!e) var e = window.event;
searchInputField = this;
switch(e.keyCode)
{
case 13:
if (e.ctrlKey && lastNewTiddlerButton && isResultOpen())
lastNewTiddlerButton.onclick.apply(lastNewTiddlerButton,[e]);
else
doSearch(this);
break;
case 27:
// When the result is open, close it,
// otherwise clear the content of the input field
if (isResultOpen()) {
closeResult();
} else {
this.value = "";
clearMessage();
}
break;
}
if (String.fromCharCode(e.keyCode) == this.accessKey || e.altKey)
{
reopenResultIfApplicable();
}
if(this.value.length<3 && searchTimeout) clearTimeout(searchTimeout);
if(this.value.length > 2)
{
if (this.value != lastSearchText)
{
if (!config.options.chkUseYourSearch || config.options.chkSearchAsYouType)
{
if(searchTimeout)
clearTimeout(searchTimeout);
var txt = this;
searchTimeout = setTimeout(function() {doSearch(txt);},500);
}
}
else
{
if(searchTimeout)
clearTimeout(searchTimeout);
}
};
if (this.value.length == 0)
{
closeResult();
}
};
var focusHandler = function(e)
{
this.select();
clearMessage();
reopenResultIfApplicable();
};
var args = paramString.parseParams("list",null,true);
var buttonAtRight = getFlag(args, "buttonAtRight");
var sizeTextbox = getParam(args, "sizeTextbox", this.sizeTextbox);
var btn;
if (!buttonAtRight)
btn = createTiddlyButton(place,this.label,this.prompt,clickHandler);
var txt = createTiddlyElement(null,"input",null,"txtOptionInput searchField",null);
if(params[0])
txt.value = params[0];
txt.onkeyup = keyHandler;
txt.onfocus = focusHandler;
txt.setAttribute("size",sizeTextbox);
txt.setAttribute("accessKey",this.accessKey);
txt.setAttribute("autocomplete","off");
if(config.browser.isSafari)
{
txt.setAttribute("type","search");
txt.setAttribute("results","5");
}
else
txt.setAttribute("type","text");
if(place)
place.appendChild(txt);
if (buttonAtRight)
btn = createTiddlyButton(place,this.label,this.prompt,clickHandler);
searchInputField = txt;
searchButton = btn;
};
//----------------------------------------------------------------------------
// Support for Macros
//----------------------------------------------------------------------------
var openAllFoundTiddlers = function() {
closeResult();
var results = getLastResults();
var n = results.length;
if (n) {
var titles=[];
for(var i = 0; i<n; i++)
titles.push(results[i].title);
story.displayTiddlers(null,titles);
}
};
var createOptionWithRefresh = function(place, optionParams, wikifier,tiddler) {
invokeMacro(place,"option",optionParams,wikifier,tiddler);
// The option macro appended the component at the end of the place.
var elem = place.lastChild;
var oldOnClick = elem.onclick;
elem.onclick = function(e) {
var result = oldOnClick.apply(this, arguments);
refreshResult();
return result;
};
return elem;
};
var removeTextDecoration = function(s) {
var removeThis = ["''", "{{{", "}}}", "//", "<<<", "/***", "***/"];
var reText = "";
for (var i = 0; i < removeThis.length; i++) {
if (i != 0) reText += "|";
reText += "("+removeThis[i].escapeRegExp()+")";
}
return s.replace(new RegExp(reText, "mg"), "").trim();
};
// Returns the "shortcut number" of the currentTiddler.
// I.e. When the user presses Alt-n the given tiddler is opened/display.
//
// @return 0-9 or -1 when no number is defined
//
var getShortCutNumber = function() {
var i = indexInPage;
return (i >= 0 && i <= 9)
? (i < 9 ? (i+1) : 0)
: -1;
};
var limitedTextRenderer = new abego.LimitedTextRenderer();
var renderLimitedText = function(place, s, maxLen) {
limitedTextRenderer.render(place,s,maxLen,lastQuery.getMarkRegExp())
}
// When any tiddler are changed reset the result.
//
var oldTiddlyWikiSaveTiddler = TiddlyWiki.prototype.saveTiddler;
TiddlyWiki.prototype.saveTiddler = function(title,newTitle,newBody,modifier,modified,tags,fields) {
oldTiddlyWikiSaveTiddler.apply(this, arguments);
invalidateResult();
};
var oldTiddlyWikiRemoveTiddler = TiddlyWiki.prototype.removeTiddler;
TiddlyWiki.prototype.removeTiddler = function(title) {
oldTiddlyWikiRemoveTiddler.apply(this, arguments);
invalidateResult();
};
//----------------------------------------------------------------------------
// Macros
//----------------------------------------------------------------------------
// ====Macro yourSearch ================================================
config.macros.yourSearch = {
// Standard Properties
label: "yourSearch",
prompt: "Gives access to the current/last YourSearch result",
handler: function(place,macroName,params,wikifier,paramString,tiddler) {
if (params.length == 0) return;
var name = params[0];
var func = config.macros.yourSearch.funcs[name];
if (func) func(place,macroName,params,wikifier,paramString,tiddler);
},
tests: {
"true" : function() {return true;},
"false" : function() {return false;},
"found" : function() {return getLastResultsCount() > 0;},
"previewText" : function() {return config.options.chkPreviewText;}
},
funcs: {
itemRange: function(place) {
if (getLastResultsCount()) {
var lastIndex = pager.getLastIndexOnPage();
var s = "%0 - %1".format([pager.getFirstIndexOnPage()+1,lastIndex+1]);
createTiddlyText(place, s);
}
},
count: function(place) {
createTiddlyText(place, getLastResultsCount().toString());
},
query: function(place) {
if (lastQuery) {
createTiddlyText(place, lastQuery.toString());
}
},
version: function(place) {
var t = "YourSearch %0.%1.%2".format(
[version.extensions.YourSearchPlugin.major,
version.extensions.YourSearchPlugin.minor,
version.extensions.YourSearchPlugin.revision]);
var e = createTiddlyElement(place, "a");
e.setAttribute("href", "http://tiddlywiki.abego-software.de/#YourSearchPlugin");
e.innerHTML = '<font color="black" face="Arial, Helvetica, sans-serif">'+t+'<font>';
},
copyright: function(place) {
var e = createTiddlyElement(place, "a");
e.setAttribute("href", "http://www.abego-software.de");
e.innerHTML = '<font color="black" face="Arial, Helvetica, sans-serif">© 2005-2008 <b><font color="red">abego</font></b> Software<font>';
},
newTiddlerButton: function(place) {
if (lastQuery) {
var r = abego.parseNewTiddlerCommandLine(lastQuery.getQueryText());
var btn = config.macros.newTiddler.createNewTiddlerButton(place,r.title,r.params,"new tiddler","Create a new tiddler based on search text. (Shortcut: Ctrl-Enter; Separators: '.', '#')",null,"text");
// Close the result before the new tiddler is created.
var oldOnClick = btn.onclick;
btn.onclick = function() {
closeResult();
oldOnClick.apply(this,arguments);
}
lastNewTiddlerButton = btn;
}
},
linkButton: function(place,macroName,params,wikifier,paramString,tiddler) {
if (params < 2) return;
var tiddlyLink = params[1];
var text = params < 3 ? tiddlyLink : params[2];
var tooltip = params < 4 ? text : params[3];
var accessKey = params < 5 ? null : params[4];
var btn = createTiddlyButton(place,text,tooltip,closeResultAndDisplayTiddler,null,null, accessKey);
btn.setAttribute("tiddlyLink",tiddlyLink);
},
closeButton: function(place,macroName,params,wikifier,paramString,tiddler) {
var button = createTiddlyButton(place, "close", "Close the Search Results (Shortcut: ESC)", closeResult);
},
openAllButton: function(place,macroName,params,wikifier,paramString,tiddler) {
var n = getLastResultsCount();
if (n == 0) return;
var title = n == 1 ? "open tiddler" : "open all %0 tiddlers".format([n]);
var button = createTiddlyButton(place, title, "Open all found tiddlers (Shortcut: Alt-O)", openAllFoundTiddlers);
button.setAttribute("accessKey","O");
},
naviBar: function(place,macroName,params,wikifier,paramString,tiddler) {
pager.addPageNavigation(place);
},
"if": function(place,macroName,params,wikifier,paramString,tiddler) {
if (params.length < 2) return;
var testName = params[1];
var negate = (testName == "not");
if (negate) {
if (params.length < 3) return;
testName = params[2];
}
var test = config.macros.yourSearch.tests[testName];
var showIt = false;
try {
if (test) {
showIt = test(place,macroName,params,wikifier,paramString,tiddler) != negate;
} else {
// When no predefined test is specified try to evaluate it as a JavaScript expression.
showIt = (!eval(testName)) == negate;
}
} catch (ex) {
}
if (!showIt) {
place.style.display="none";
}
},
chkPreviewText: function(place,macroName,params,wikifier,paramString,tiddler) {
var optionParams = params.slice(1).join(" ");
var elem = createOptionWithRefresh(place, "chkPreviewText", wikifier,tiddler);
elem.setAttribute("accessKey", "P");
elem.title = "Show text preview of found tiddlers (Shortcut: Alt-P)";
return elem;
}
}
};
// ====Macro foundTiddler ================================================
config.macros.foundTiddler = {
// Standard Properties
label: "foundTiddler",
prompt: "Provides information on the tiddler currently processed on the YourSearch result page",
handler: function(place,macroName,params,wikifier,paramString,tiddler) {
var name = params[0];
var func = config.macros.foundTiddler.funcs[name];
if (func) func(place,macroName,params,wikifier,paramString,tiddler);
},
funcs: {
title: function(place,macroName,params,wikifier,paramString,tiddler) {
if (!currentTiddler) return;
var shortcutNumber = getShortCutNumber();
var tooltip = shortcutNumber >= 0
? "Open tiddler (Shortcut: Alt-%0)".format([shortcutNumber.toString()])
: "Open tiddler";
var btn = createTiddlyButton(place,null,tooltip,closeResultAndDisplayTiddler,null);
btn.setAttribute("tiddlyLink",currentTiddler.title);
btn.setAttribute("withHilite","true");
renderLimitedText(btn, currentTiddler.title, maxCharsInTitle);
if (shortcutNumber >= 0) {
btn.setAttribute("accessKey",shortcutNumber.toString());
}
},
tags: function(place,macroName,params,wikifier,paramString,tiddler) {
if (!currentTiddler) return;
renderLimitedText(place, currentTiddler.getTags(), maxCharsInTags);
},
text: function(place,macroName,params,wikifier,paramString,tiddler) {
if (!currentTiddler) return;
renderLimitedText(place, removeTextDecoration(currentTiddler.text), maxCharsInText);
},
field: function(place,macroName,params,wikifier,paramString,tiddler) {
if (!currentTiddler) return;
var name = params[1];
var len = params.length > 2 ? abego.toInt(params[2],maxCharsInField) : maxCharsInField;
var v = store.getValue(currentTiddler,name);
if (v)
renderLimitedText(place, removeTextDecoration(v), len);
},
// Renders the "shortcut number" of the current tiddler, to indicate to the user
// what number to "Alt-press" to open the tiddler.
//
number: function(place,macroName,params,wikifier,paramString,tiddler) {
var numberToDisplay = getShortCutNumber();
if (numberToDisplay >= 0) {
var text = "%0)".format([numberToDisplay.toString()]);
createTiddlyElement(place,"span",null,"shortcutNumber",text);
}
}
}
};
//----------------------------------------------------------------------------
// Configuration Stuff
//----------------------------------------------------------------------------
var opts = {chkUseYourSearch:true,
chkPreviewText:true,
chkSearchAsYouType:true,
chkSearchInTitle:true,
chkSearchInText:true,
chkSearchInTags:true,
chkSearchExtendedFields:true,
txtItemsPerPage:itemsPerPageDefault,
txtItemsPerPageWithPreview:itemsPerPageWithPreviewDefault};
for (var n in opts)
if (config.options[n] == undefined) config.options[n] = opts[n];
//----------------------------------------------------------------------------
// Shadow Tiddlers
//----------------------------------------------------------------------------
config.shadowTiddlers.AdvancedOptions += "\n<<option chkUseYourSearch>> Use 'Your Search' //([[more options|YourSearch Options]]) ([[help|YourSearch Help]])// ";
config.shadowTiddlers["YourSearch Help"] =
"!Field Search\nWith the Field Search you can restrict your search to certain fields of a tiddler, e.g"+
" only search the tags or only the titles. The general form is //fieldname//'':''//textToSearch// (e."+
"g. {{{title:intro}}}). In addition one-character shortcuts are also supported for the standard field"+
"s {{{title}}}, {{{text}}} and {{{tags}}}:\n|!What you want|!What you type|!Example|\n|Search ''titles "+
"only''|start word with ''!''|{{{!jonny}}} (shortcut for {{{title:jonny}}})|\n|Search ''contents/text "+
"only''|start word with ''%''|{{{%football}}} (shortcut for {{{text:football}}})|\n|Search ''tags only"+
"''|start word with ''#''|{{{#Plugin}}} (shortcut for {{{tags:Plugin}}})|\n\nUsing this feature you may"+
" also search the extended fields (\"Metadata\") introduced with TiddlyWiki 2.1, e.g. use {{{priority:1"+
"}}} to find all tiddlers with the priority field set to \"1\".\n\nYou may search a word in more than one"+
" field. E.g. {{{!#Plugin}}} (or {{{title:tags:Plugin}}} in the \"long form\") finds tiddlers containin"+
"g \"Plugin\" either in the title or in the tags (but does not look for \"Plugin\" in the text). \n\n!Boole"+
"an Search\nThe Boolean Search is useful when searching for multiple words.\n|!What you want|!What you "+
"type|!Example|\n|''All words'' must exist|List of words|{{{jonny jeremy}}} (or {{{jonny and jeremy}}}"+
")|\n|''At least one word'' must exist|Separate words by ''or''|{{{jonny or jeremy}}}|\n|A word ''must "+
"not exist''|Start word with ''-''|{{{-jonny}}} (or {{{not jonny}}})|\n\n''Note:'' When you specify two"+
" words, separated with a space, YourSearch finds all tiddlers that contain both words, but not neces"+
"sarily next to each other. If you want to find a sequence of word, e.g. '{{{John Brown}}}', you need"+
" to put the words into quotes. I.e. you type: {{{\"john brown\"}}}.\n\nUsing parenthesis you may change "+
"the default \"left to right\" evaluation of the boolean search. E.g. {{{not (jonny or jeremy)}}} finds"+
" all tiddlers that contain neither \"jonny\" nor \"jeremy. In contrast to this {{{not jonny or jeremy}}"+
"} (i.e. without parenthesis) finds all tiddlers that either don't contain \"jonny\" or that contain \"j"+
"eremy\".\n\n!'Exact Word' Search\nBy default a search result all matches that 'contain' the searched tex"+
"t. E.g. if you search for {{{Task}}} you will get all tiddlers containing 'Task', but also '~Complet"+
"edTask', '~TaskForce' etc.\n\nIf you only want to get the tiddlers that contain 'exactly the word' you"+
" need to prefix it with a '='. E.g. typing '=Task' will find the tiddlers that contain the word 'Tas"+
"k', ignoring words that just contain 'Task' as a substring.\n\n!~CaseSensitiveSearch and ~RegExpSearch"+
"\nThe standard search options ~CaseSensitiveSearch and ~RegExpSearch are fully supported by YourSearc"+
"h. However when ''~RegExpSearch'' is on Filtered and Boolean Search are disabled.\n\nIn addition you m"+
"ay do a \"regular expression\" search even with the ''~RegExpSearch'' set to false by directly enterin"+
"g the regular expression into the search field, framed with {{{/.../}}}. \n\nExample: {{{/m[ae][iy]er/"+
"}}} will find all tiddlers that contain either \"maier\", \"mayer\", \"meier\" or \"meyer\".\n\n!~JavaScript E"+
"xpression Filtering\nIf you are familiar with JavaScript programming and know some TiddlyWiki interna"+
"ls you may also use JavaScript expression for the search. Just enter a JavaScript boolean expression"+
" into the search field, framed with {{{ { ... } }}}. In the code refer to the variable tiddler and e"+
"valuate to {{{true}}} when the given tiddler should be included in the result. \n\nExample: {{{ { tidd"+
"ler.modified > new Date(\"Jul 4, 2005\")} }}} returns all tiddler modified after July 4th, 2005.\n\n!Com"+
"bined Search\nYou are free to combine the various search options. \n\n''Examples''\n|!What you type|!Res"+
"ult|\n|{{{!jonny !jeremy -%football}}}|all tiddlers with both {{{jonny}}} and {{{jeremy}}} in its tit"+
"les, but no {{{football}}} in content.|\n|{{{#=Task}}}|All tiddlers tagged with 'Task' (the exact wor"+
"d). Tags named '~CompletedTask', '~TaskForce' etc. are not considered.|\n\n!Access Keys\nYou are encour"+
"aged to use the access keys (also called \"shortcut\" keys) for the most frequently used operations. F"+
"or quick reference these shortcuts are also mentioned in the tooltip for the various buttons etc.\n\n|"+
"!Key|!Operation|\n|{{{Alt-F}}}|''The most important keystroke'': It moves the cursor to the search in"+
"put field so you can directly start typing your query. Pressing {{{Alt-F}}} will also display the pr"+
"evious search result. This way you can quickly display multiple tiddlers using \"Press {{{Alt-F}}}. S"+
"elect tiddler.\" sequences.|\n|{{{ESC}}}|Closes the [[YourSearch Result]]. When the [[YourSearch Resul"+
"t]] is already closed and the cursor is in the search input field the field's content is cleared so "+
"you start a new query.|\n|{{{Alt-1}}}, {{{Alt-2}}},... |Pressing these keys opens the first, second e"+
"tc. tiddler from the result list.|\n|{{{Alt-O}}}|Opens all found tiddlers.|\n|{{{Alt-P}}}|Toggles the "+
"'Preview Text' mode.|\n|{{{Alt-'<'}}}, {{{Alt-'>'}}}|Displays the previous or next page in the [[Your"+
"Search Result]].|\n|{{{Return}}}|When you have turned off the 'as you type' search mode pressing the "+
"{{{Return}}} key actually starts the search (as does pressing the 'search' button).|\n\n//If some of t"+
"hese shortcuts don't work for you check your browser if you have other extensions installed that alr"+
"eady \"use\" these shortcuts.//";
config.shadowTiddlers["YourSearch Options"] =
"|>|!YourSearch Options|\n|>|<<option chkUseYourSearch>> Use 'Your Search'|\n|!|<<option chkPreviewText"+
">> Show Text Preview|\n|!|<<option chkSearchAsYouType>> 'Search As You Type' Mode (No RETURN required"+
" to start search)|\n|!|Default Search Filter:<<option chkSearchInTitle>>Title ('!') <<option chk"+
"SearchInText>>Text ('%') <<option chkSearchInTags>>Tags ('#') <<option chkSearchExtendedFiel"+
"ds>>Extended Fields<html><br><font size=\"-2\">The fields of a tiddlers that are searched when you don"+
"'t explicitly specify a filter in the search text <br>(Explictly specify fields using one or more '!"+
"', '%', '#' or 'fieldname:' prefix before the word/text to find).</font></html>|\n|!|Number of items "+
"on search result page: <<option txtItemsPerPage>>|\n|!|Number of items on search result page with pre"+
"view text: <<option txtItemsPerPageWithPreview>>|\n";
config.shadowTiddlers["YourSearchStyleSheet"] =
"/***\n!~YourSearchResult Stylesheet\n***/\n/*{{{*/\n.yourSearchResult {\n\tposition: absolute;\n\twidth: 800"+
"px;\n\n\tpadding: 0.2em;\n\tlist-style: none;\n\tmargin: 0;\n\n\tbackground: #ffd;\n\tborder: 1px solid DarkGra"+
"y;\n}\n\n/*}}}*/\n/***\n!!Summary Section\n***/\n/*{{{*/\n.yourSearchResult .summary {\n\tcolor: #808080;\n\tborder-bottom-width:"+
" thin;\n\tborder-bottom-style: solid;\n\tborder-bottom-color: #999999;\n\tpadding-bottom: 4px;\n}\n\n.yourSea"+
"rchRange, .yourSearchCount, .yourSearchQuery {\n\tfont-weight: bold;\n}\n\n.yourSearchResult .summary ."+
"button {\n\tfont-size: 10px;\n\n\tpadding-left: 0.3em;\n\tpadding-right: 0.3em;\n}\n\n.yourSearchResult .summa"+
"ry .chkBoxLabel {\n\tfont-size: 10px;\n\n\tpadding-right: 0.3em;\n}\n\n/*}}}*/\n/***\n!!Items Area\n***/\n/*{{{*"+
"/\n.yourSearchResult .marked {\n\tbackground: none;\n\tfont-weight: bold;\n}\n\n.yourSearchItem {\n\tmargin-to"+
"p: 2px;\n}\n\n.yourSearchNumber {\n\tcolor: #808080;\n}\n\n\n.yourSearchTags {\n\tcolor: #008000;\n}\n\n.yourSearc"+
"hText {\n\tcolor: #808080;\n\tmargin-bottom: 6px;\n}\n\n/*}}}*/\n/***\n!!Footer\n***/\n/*{{{*/\n.yourSearchFoote"+
"r {\n\tmargin-top: 8px;\n\tborder-top-width: thin;\n\tborder-top-style: solid;\n\tborder-top-color: #999999;"+
"\n}\n\n.yourSearchFooter a:hover{\n\tbackground: none;\n\tcolor: none;\n}\n/*}}}*/\n/***\n!!Navigation Bar\n***/"+
"\n/*{{{*/\n.yourSearchNaviBar a {\n\tfont-size: 16px;\n\tmargin-left: 4px;\n\tmargin-right: 4px;\n\tcolor: bla"+
"ck;\n\ttext-decoration: underline;\n}\n\n.yourSearchNaviBar a:hover {\n\tbackground-color: none;\n}\n\n.yourSe"+
"archNaviBar .prev {\n\tfont-weight: bold;\n\tcolor: blue;\n}\n\n.yourSearchNaviBar .currentPage {\n\tcolor: #"+
"FF0000;\n\tfont-weight: bold;\n\ttext-decoration: none;\n}\n\n.yourSearchNaviBar .next {\n\tfont-weight: bold"+
";\n\tcolor: blue;\n}\n/*}}}*/\n";
config.shadowTiddlers["YourSearchResultTemplate"] =
"<!--\n{{{\n-->\n<span macro=\"yourSearch if found\">\n<!-- The Summary Header ============================"+
"================ -->\n<table class=\"summary\" border=\"0\" width=\"100%\" cellspacing=\"0\" cellpadding=\"0\">"+
"<tbody>\n <tr>\n\t<td align=\"left\">\n\t\tYourSearch Result <span class=\"yourSearchRange\" macro=\"yourSearc"+
"h itemRange\"></span>\n\t\t of <span class=\"yourSearchCount\" macro=\"yourSearch count\"></span>\n"+
"\t\tfor <span class=\"yourSearchQuery\" macro=\"yourSearch query\"></span>\n\t</td>\n\t<td class=\"yourSea"+
"rchButtons\" align=\"right\">\n\t\t<span macro=\"yourSearch chkPreviewText\"></span><span class=\"chkBoxLabel"+
"\">preview text</span>\n\t\t<span macro=\"yourSearch newTiddlerButton\"></span>\n\t\t<span macro=\"yourSearch openAllButton\"></span>\n\t\t<span macro=\"yourSearch lin"+
"kButton 'YourSearch Options' options 'Configure YourSearch'\"></span>\n\t\t<span macro=\"yourSearch linkB"+
"utton 'YourSearch Help' help 'Get help how to use YourSearch'\"></span>\n\t\t<span macro=\"yourSearch clo"+
"seButton\"></span>\n\t</td>\n </tr>\n</tbody></table>\n\n<!-- The List of Found Tiddlers ================="+
"=========================== -->\n<div id=\"yourSearchResultItems\" itemsPerPage=\"25\" itemsPerPageWithPr"+
"eview=\"10\"></div>\n\n<!-- The Footer (with the Navigation) ==========================================="+
"= -->\n<table class=\"yourSearchFooter\" border=\"0\" width=\"100%\" cellspacing=\"0\" cellpadding=\"0\"><tbody"+
">\n <tr>\n\t<td align=\"left\">\n\t\tResult page: <span class=\"yourSearchNaviBar\" macro=\"yourSearch naviBar"+
"\"></span>\n\t</td>\n\t<td align=\"right\"><span macro=\"yourSearch version\"></span>, <span macro=\"yourSearc"+
"h copyright\"></span>\n\t</td>\n </tr>\n</tbody></table>\n<!-- end of the 'tiddlers found' case ========="+
"================================== -->\n</span>\n\n\n<!-- The \"No tiddlers found\" case ================="+
"========================== -->\n<span macro=\"yourSearch if not found\">\n<table class=\"summary\" border="+
"\"0\" width=\"100%\" cellspacing=\"0\" cellpadding=\"0\"><tbody>\n <tr>\n\t<td align=\"left\">\n\t\tYourSearch Resu"+
"lt: No tiddlers found for <span class=\"yourSearchQuery\" macro=\"yourSearch query\"></span>.\n\t</td>\n\t<t"+
"d class=\"yourSearchButtons\" align=\"right\">\n\t\t<span macro=\"yourSearch newTiddlerButton\"></span>\n\t\t<span macro=\"yourSearch linkButton 'YourSearch Options'"+
" options 'Configure YourSearch'\"></span>\n\t\t<span macro=\"yourSearch linkButton 'YourSearch Help' help"+
" 'Get help how to use YourSearch'\"></span>\n\t\t<span macro=\"yourSearch closeButton\"></span>\n\t</td>\n <"+
"/tr>\n</tbody></table>\n</span>\n\n\n<!--\n}}}\n-->\n";
config.shadowTiddlers["YourSearchItemTemplate"] =
"<!--\n{{{\n-->\n<span class='yourSearchNumber' macro='foundTiddler number'></span>\n<span class='yourSea"+
"rchTitle' macro='foundTiddler title'/></span> - \n<span class='yourSearchTags' macro='found"+
"Tiddler field tags 50'/></span>\n<span macro=\"yourSearch if previewText\"><div class='yourSearchText' macro='fo"+
"undTiddler field text 250'/></div></span>\n<!--\n}}}\n-->";
config.shadowTiddlers["YourSearch"] = "<<tiddler [[YourSearch Help]]>>";
config.shadowTiddlers["YourSearch Result"] = "The popup-like window displaying the result of a YourSearch query.";
//----------------------------------------------------------------------------
// Install YourSearch
//----------------------------------------------------------------------------
// Overwrite the TiddlyWiki search handler and verify after a while
// that nobody else has overwritten it.
config.macros.search.handler = myMacroSearchHandler;
var checkForOtherHijacker = function() {
// Check that still our search handler is installed
if (config.macros.search.handler != myMacroSearchHandler) {
alert(
"Message from YourSearchPlugin:\n\n\nAnother plugin has disabled the 'Your Search' features.\n\n\nYou may "+
"disable the other plugin or change the load order of \nthe plugins (by changing the names of the tidd"+
"lers)\nto enable the 'Your Search' features.");
}
};
setTimeout(checkForOtherHijacker, 5000);
// === Public API =================================
abego.YourSearch.getStandardRankFunction = function() {
return standardRankFunction;
};
abego.YourSearch.getRankFunction = function() {
return abego.YourSearch.getStandardRankFunction();
};
abego.YourSearch.getCurrentTiddler = function() {
return currentTiddler;
};
abego.YourSearch.closeResult = function() {
closeResult();
};
// Returns an array of tiddlers that matched the last search
abego.YourSearch.getFoundTiddlers = function() {
return lastResults;
};
// The last Search query (TiddlerQuery), or null
abego.YourSearch.getQuery = function() {
return lastQuery;
};
abego.YourSearch.onShowResult = function(useOldResult) {
highlightHack = lastQuery ? lastQuery.getMarkRegExp() : null;
if (!useOldResult)
pager.setItems(getLastResults());
if (!resultElement) {
resultElement = createTiddlyElement(document.body,"div",yourSearchResultID,"yourSearchResult");
} else if (resultElement.parentNode != document.body) {
document.body.appendChild(resultElement);
}
refreshResult();
highlightHack = null;
};
})();
} // of "install only once"
// Used Globals (for JSLint) ==============
// ... JavaScript Core
/*global alert,clearTimeout,confirm */
// ... TiddlyWiki Core
/*global Tiddler, applyHtmlMacros, clearMessage, createTiddlyElement, createTiddlyButton, createTiddlyText, ensureVisible ,findPosX, highlightHack, findPosY,findWindowWidth, invokeMacro, saveChanges, refreshElements, story */
//}}}
/***
!Licence and Copyright
Copyright (c) abego Software ~GmbH, 2005-2010 ([[www.abego-software.de|http://www.abego-software.de]])
Redistribution and use in source and binary forms, with or without modification,
are permitted provided that the following conditions are met:
Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.
Redistributions in binary form must reproduce the above copyright notice, this
list of conditions and the following disclaimer in the documentation and/or other
materials provided with the distribution.
Neither the name of abego Software nor the names of its contributors may be
used to endorse or promote products derived from this software without specific
prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY
EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT
SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH
DAMAGE.
***/
<!--
{{{
-->
<span macro="yourSearch if found">
<!-- The Summary Header ============================================ -->
<table class="summary" border="0" width="100%" cellspacing="0" cellpadding="0"><tbody>
<tr>
<td align="left">
YourSearch Result <span class="yourSearchRange" macro="yourSearch itemRange"></span>
of <span class="yourSearchCount" macro="yourSearch count"></span>
for <span class="yourSearchQuery" macro="yourSearch query"></span>
</td>
<td class="yourSearchButtons" align="right">
<span macro="yourSearch chkPreviewText"></span><span class="chkBoxLabel">preview text</span>
<span macro="yourSearch newTiddlerButton"></span>
<span macro="yourSearch openAllButton"></span>
<span macro="yourSearch linkButton 'YourSearch Options' options 'Configure YourSearch'"></span>
<span macro="yourSearch linkButton 'YourSearch Help' help 'Get help how to use YourSearch'"></span>
<span macro="yourSearch closeButton"></span>
</td>
</tr>
</tbody></table>
<!-- The List of Found Tiddlers ============================================ -->
<div id="yourSearchResultItems" itemsPerPage="25" itemsPerPageWithPreview="10"></div>
<!-- The Footer (with the Navigation) ============================================ -->
<table class="yourSearchFooter" border="0" width="100%" cellspacing="0" cellpadding="0"><tbody>
<tr>
<td align="left">
Result page: <span class="yourSearchNaviBar" macro="yourSearch naviBar"></span>
</td>
<td align="right"><span macro="yourSearch version"></span>, <span macro="yourSearch copyright"></span>
</td>
</tr>
</tbody></table>
<!-- end of the 'tiddlers found' case =========================================== -->
</span>
<!-- The "No tiddlers found" case =========================================== -->
<span macro="yourSearch if not found">
<table class="summary" border="0" width="100%" cellspacing="0" cellpadding="0"><tbody>
<tr>
<td align="left">
YourSearch Result: No tiddlers found for <span class="yourSearchQuery" macro="yourSearch query"></span>.
</td>
<td class="yourSearchButtons" align="right">
<span macro="yourSearch newTiddlerButton"></span>
<span macro="yourSearch linkButton 'YourSearch Options' options 'Configure YourSearch'"></span>
<span macro="yourSearch linkButton 'YourSearch Help' help 'Get help how to use YourSearch'"></span>
<span macro="yourSearch closeButton"></span>
</td>
</tr>
</tbody></table>
</span>
<!--
}}}
-->
/***
!~YourSearchResult Stylesheet
***/
/*{{{*/
.yourSearchResult {
position: absolute;
width: 800px;
padding: 0.2em;
list-style: none;
margin: 0;
background: #ffd;
border: 1px solid DarkGray;
}
/*}}}*/
/***
!!Summary Section
***/
/*{{{*/
.yourSearchResult .summary {
color: #808080;
border-bottom-width: thin;
border-bottom-style: solid;
border-bottom-color: #999999;
padding-bottom: 4px;
}
.yourSearchRange, .yourSearchCount, .yourSearchQuery {
font-weight: bold;
}
.yourSearchResult .summary .button {
font-size: 10px;
padding-left: 0.3em;
padding-right: 0.3em;
}
.yourSearchResult .summary .chkBoxLabel {
font-size: 10px;
padding-right: 0.3em;
}
/*}}}*/
/***
!!Items Area
***/
/*{{{*/
.yourSearchResult .marked {
background: none;
font-weight: bold;
}
.yourSearchItem {
margin-top: 2px;
}
.yourSearchNumber {
color: #808080;
}
.yourSearchTags {
color: #008000;
}
.yourSearchText {
color: #808080;
margin-bottom: 6px;
}
/*}}}*/
/***
!!Footer
***/
/*{{{*/
.yourSearchFooter {
margin-top: 8px;
border-top-width: thin;
border-top-style: solid;
border-top-color: #999999;
}
.yourSearchFooter a:hover{
background: none;
color: none;
}
/*}}}*/
/***
!!Navigation Bar
***/
/*{{{*/
.yourSearchNaviBar a {
font-size: 16px;
margin-left: 4px;
margin-right: 4px;
color: black;
text-decoration: underline;
}
.yourSearchNaviBar a:hover {
background-color: none;
}
.yourSearchNaviBar .prev {
font-weight: bold;
color: blue;
}
.yourSearchNaviBar .currentPage {
color: #FF0000;
font-weight: bold;
text-decoration: none;
}
.yourSearchNaviBar .next {
font-weight: bold;
color: blue;
}
/*}}}*/
https://css-tricks.com/replace-the-image-in-an-img-with-css/
http://www.w3schools.com/css/css_image_sprites.asp
{{{
#home {
width: 46px;
height: 44px;
background: url(img_navsprites.gif) 0 0;
}}}
Example explained:
* <img id="home" src="img_trans.gif"> - Only defines a small transparent image because the src attribute cannot be empty. The displayed image will be the background image we specify in CSS
* width: 46px; height: 44px; - Defines the portion of the image we want to use
* background: url(img_navsprites.gif) 0 0; - Defines the background image and its position (left 0px, top 0px)